diff --git a/.eslintrc.json b/.eslintrc.json index 41062f958e4544..bdb1813613c210 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -40,7 +40,10 @@ "XRMediaBinding": "readonly", "CodeMirror": "readonly", "esprima": "readonly", - "jsonlint": "readonly" + "jsonlint": "readonly", + "VideoFrame": "readonly", + "VideoDecoder": "readonly", + "Float16Array": "readonly" }, "rules": { "no-throw-literal": [ @@ -56,6 +59,30 @@ "destructuring": "any", "ignoreReadBeforeAssign": false } + ], + "no-irregular-whitespace": [ + "error" + ], + "no-duplicate-imports": [ + "error" + ], + "prefer-spread": "error", + "valid-jsdoc": [ + "error", + { + "requireReturn": false, + "requireReturnType": true, + "requireParamDescription": false, + "requireReturnDescription": false, + "requireParamType": true, + "preferType": { + "Any": "any", + "Boolean": "boolean", + "Number": "number", + "object": "Object", + "String": "string" + } + } ] } } diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 176ed7e4a3058d..f95d9835377634 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -34,8 +34,10 @@ body: attributes: label: Live example value: | - * [jsfiddle-latest-release](https://jsfiddle.net/g3atw6k5/) - * [jsfiddle-dev](https://jsfiddle.net/hjqw94c5/) + * [jsfiddle-latest-release WebGLRenderer](https://jsfiddle.net/3mrkqyea/) + * [jsfiddle-dev WebGLRenderer](https://jsfiddle.net/gcqx26jv/) + * [jsfiddle-latest-release WebGPURenderer](https://jsfiddle.net/mnqr9oj0/) + * [jsfiddle-dev WebGPURenderer](https://jsfiddle.net/xno7bmw0/) validations: required: true - type: textarea @@ -72,6 +74,7 @@ body: - Firefox - Safari - Edge + - Quest Browser - type: dropdown id: os attributes: @@ -81,5 +84,6 @@ body: - Windows - MacOS - Linux + - ChromeOS - Android - iOS diff --git a/.github/codeql-config.yml b/.github/codeql-config.yml index d397d52e1bf691..a31e881530d9eb 100644 --- a/.github/codeql-config.yml +++ b/.github/codeql-config.yml @@ -5,3 +5,5 @@ paths-ignore: - "examples/jsm/loaders/ifc/**/*.*" - "build/*.*" - "manual/3rdparty/**/*.*" + - "utils/docs/template/static/scripts/fuse/**/*.*" + - "utils/docs/template/static/scripts/prettify/**/*.*" diff --git a/.github/renovate.json b/.github/renovate.json index 59306918fe3901..d37fc66c0e1c56 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -1,7 +1,8 @@ { "extends": [ "config:base", - ":disableDependencyDashboard" + ":disableDependencyDashboard", + "helpers:pinGitHubActionDigests" ], "timezone": "Asia/Tokyo", "schedule": ["after 1am and before 7am every monday"], @@ -11,6 +12,12 @@ "matchUpdateTypes": ["patch", "minor", "pin", "digest"], "groupName": "devDependencies (non-major)", "automerge": true + }, + { + "description": "ESLint v9 requires flat configs, not yet supported by our plugins. See https://github.com/mrdoob/three.js/pull/28354#issuecomment-2106528332", + "matchPackageNames": ["eslint"], + "matchUpdateTypes": ["major"], + "enabled": false } ] } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9146c9936daa21..73b89634fecfa4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,11 +6,6 @@ on: - 'build/**' - 'docs/**' - 'files/**' - push: - paths-ignore: - - 'build/**' - - 'docs/**' - - 'files/**' permissions: contents: read @@ -21,9 +16,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Git checkout - uses: actions/checkout@v3 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Install Node - uses: actions/setup-node@v3 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: 18 cache: 'npm' @@ -38,9 +33,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Git checkout - uses: actions/checkout@v3 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Install Node - uses: actions/setup-node@v3 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: 18 cache: 'npm' @@ -50,6 +45,23 @@ jobs: - name: === Unit testing === run: npm run test-unit + circular: + name: Circular dependencies testing + runs-on: ubuntu-latest + steps: + - name: Git checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - name: Install Node + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version: 18 + cache: 'npm' + - name: Install dependencies + run: npm ci + + - name: === Circular dependencies testing === + run: npm run test-circular-deps + e2e: name: E2E testing runs-on: ${{ matrix.os }} @@ -57,30 +69,30 @@ jobs: strategy: fail-fast: false matrix: - os: [ windows-latest, ubuntu-latest, macos-latest ] + os: [ windows-latest ] CI: [ 0, 1, 2, 3 ] env: CI: ${{ matrix.CI }} steps: - name: Git checkout - uses: actions/checkout@v3 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Install Node - uses: actions/setup-node@v3 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: 18 cache: 'npm' - name: Install dependencies run: npm ci - name: Build - run: npm run build + run: npm run build-module - name: === E2E testing === run: npm run test-e2e - name: Upload output screenshots - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 if: always() with: - name: Output screenshots + name: Output screenshots-${{ matrix.os }}-${{ matrix.CI }} path: test/e2e/output-screenshots if-no-files-found: ignore @@ -89,9 +101,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Git checkout - uses: actions/checkout@v3 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Install Node - uses: actions/setup-node@v3 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: 18 cache: 'npm' diff --git a/.github/workflows/codeql-code-scanning.yml b/.github/workflows/codeql-code-scanning.yml index dfd9bf36c79bc4..af6c54274c77a3 100644 --- a/.github/workflows/codeql-code-scanning.yml +++ b/.github/workflows/codeql-code-scanning.yml @@ -26,20 +26,20 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@39edc492dbe16b1465b0cafca41432d857bdb31a # v3 with: languages: ${{ matrix.language }} config-file: ./.github/codeql-config.yml queries: security-and-quality - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@39edc492dbe16b1465b0cafca41432d857bdb31a # v3 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@39edc492dbe16b1465b0cafca41432d857bdb31a # v3 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/read-size.yml b/.github/workflows/read-size.yml index 16173259cd0efe..109fb963ab47b5 100644 --- a/.github/workflows/read-size.yml +++ b/.github/workflows/read-size.yml @@ -20,11 +20,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Git checkout - uses: actions/checkout@v3 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Install Node - uses: actions/setup-node@v3 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: - node-version: 18 + node-version: 20 cache: 'npm' - name: Install dependencies run: npm ci @@ -35,18 +35,33 @@ jobs: - name: Read bundle sizes id: read-size run: | - FILESIZE=$(stat --format=%s build/three.module.min.js) + WEBGL_FILESIZE=$(stat --format=%s build/three.module.min.js) gzip -k build/three.module.min.js - FILESIZE_GZIP=$(stat --format=%s build/three.module.min.js.gz) - TREESHAKEN=$(stat --format=%s test/treeshake/index.bundle.min.js) + WEBGL_FILESIZE_GZIP=$(stat --format=%s build/three.module.min.js.gz) + WEBGL_TREESHAKEN=$(stat --format=%s test/treeshake/index.bundle.min.js) gzip -k test/treeshake/index.bundle.min.js - TREESHAKEN_GZIP=$(stat --format=%s test/treeshake/index.bundle.min.js.gz) + WEBGL_TREESHAKEN_GZIP=$(stat --format=%s test/treeshake/index.bundle.min.js.gz) + + WEBGPU_FILESIZE=$(stat --format=%s build/three.webgpu.min.js) + gzip -k build/three.webgpu.min.js + WEBGPU_FILESIZE_GZIP=$(stat --format=%s build/three.webgpu.min.js.gz) + WEBGPU_TREESHAKEN=$(stat --format=%s test/treeshake/index.webgpu.bundle.min.js) + gzip -k test/treeshake/index.webgpu.bundle.min.js + WEBGPU_TREESHAKEN_GZIP=$(stat --format=%s test/treeshake/index.webgpu.bundle.min.js.gz) + + WEBGPU_NODES_FILESIZE=$(stat --format=%s build/three.webgpu.nodes.min.js) + gzip -k build/three.webgpu.nodes.min.js + WEBGPU_NODES_FILESIZE_GZIP=$(stat --format=%s build/three.webgpu.nodes.min.js.gz) + WEBGPU_NODES_TREESHAKEN=$(stat --format=%s test/treeshake/index.webgpu.nodes.bundle.min.js) + gzip -k test/treeshake/index.webgpu.nodes.bundle.min.js + WEBGPU_NODES_TREESHAKEN_GZIP=$(stat --format=%s test/treeshake/index.webgpu.nodes.bundle.min.js.gz) + PR=${{ github.event.pull_request.number }} # write the output in a json file to upload it as artifact - node -pe "JSON.stringify({ filesize: $FILESIZE, gzip: $FILESIZE_GZIP, treeshaken: $TREESHAKEN, treeshakenGzip: $TREESHAKEN_GZIP, pr: $PR })" > sizes.json + node -pe "JSON.stringify({ filesize: $WEBGL_FILESIZE, gzip: $WEBGL_FILESIZE_GZIP, treeshaken: $WEBGL_TREESHAKEN, treeshakenGzip: $WEBGL_TREESHAKEN_GZIP, filesize2: $WEBGPU_FILESIZE, gzip2: $WEBGPU_FILESIZE_GZIP, treeshaken2: $WEBGPU_TREESHAKEN, treeshakenGzip2: $WEBGPU_TREESHAKEN_GZIP, filesize3: $WEBGPU_NODES_FILESIZE, gzip3: $WEBGPU_NODES_FILESIZE_GZIP, treeshaken3: $WEBGPU_NODES_TREESHAKEN, treeshakenGzip3: $WEBGPU_NODES_TREESHAKEN_GZIP, pr: $PR })" > sizes.json - name: Upload artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: sizes path: sizes.json diff --git a/.github/workflows/report-size.yml b/.github/workflows/report-size.yml index a1a6782b96f7ca..5e5a909288a472 100644 --- a/.github/workflows/report-size.yml +++ b/.github/workflows/report-size.yml @@ -9,7 +9,7 @@ on: # This workflow needs to be run with "pull-requests: write" permissions to # be able to comment on the pull request. We can't checkout the PR code # in this workflow. -# Reference: +# Reference: # https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ permissions: pull-requests: write @@ -29,7 +29,7 @@ jobs: # Using actions/download-artifact doesn't work here # https://github.com/actions/download-artifact/issues/60 - name: Download artifact - uses: actions/github-script@v6 + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 id: download-artifact with: result-encoding: string @@ -56,11 +56,11 @@ jobs: # This runs on the base branch of the PR, meaning "dev" - name: Git checkout - uses: actions/checkout@v3 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Install Node - uses: actions/setup-node@v3 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: - node-version: 18 + node-version: 20 cache: 'npm' - name: Install dependencies run: npm ci @@ -71,69 +71,181 @@ jobs: - name: Read sizes id: read-size run: | - FILESIZE_BASE=$(stat --format=%s build/three.module.min.js) + WEBGL_FILESIZE_BASE=$(stat --format=%s build/three.module.min.js) gzip -k build/three.module.min.js - FILESIZE_BASE_GZIP=$(stat --format=%s build/three.module.min.js.gz) - TREESHAKEN_BASE=$(stat --format=%s test/treeshake/index.bundle.min.js) + WEBGL_FILESIZE_BASE_GZIP=$(stat --format=%s build/three.module.min.js.gz) + WEBGL_TREESHAKEN_BASE=$(stat --format=%s test/treeshake/index.bundle.min.js) gzip -k test/treeshake/index.bundle.min.js - TREESHAKEN_BASE_GZIP=$(stat --format=%s test/treeshake/index.bundle.min.js.gz) + WEBGL_TREESHAKEN_BASE_GZIP=$(stat --format=%s test/treeshake/index.bundle.min.js.gz) + + WEBGPU_FILESIZE_BASE=$(stat --format=%s build/three.webgpu.min.js) + gzip -k build/three.webgpu.min.js + WEBGPU_FILESIZE_BASE_GZIP=$(stat --format=%s build/three.webgpu.min.js.gz) + WEBGPU_TREESHAKEN_BASE=$(stat --format=%s test/treeshake/index.webgpu.bundle.min.js) + gzip -k test/treeshake/index.webgpu.bundle.min.js + WEBGPU_TREESHAKEN_BASE_GZIP=$(stat --format=%s test/treeshake/index.webgpu.bundle.min.js.gz) + + WEBGPU_NODES_FILESIZE_BASE=$(stat --format=%s build/three.webgpu.nodes.min.js) + gzip -k build/three.webgpu.nodes.min.js + WEBGPU_NODES_FILESIZE_BASE_GZIP=$(stat --format=%s build/three.webgpu.nodes.min.js.gz) + WEBGPU_NODES_TREESHAKEN_BASE=$(stat --format=%s test/treeshake/index.webgpu.nodes.bundle.min.js) + gzip -k test/treeshake/index.webgpu.nodes.bundle.min.js + WEBGPU_NODES_TREESHAKEN_BASE_GZIP=$(stat --format=%s test/treeshake/index.webgpu.nodes.bundle.min.js.gz) # log to console - echo "FILESIZE_BASE=$FILESIZE_BASE" - echo "FILESIZE_BASE_GZIP=$FILESIZE_BASE_GZIP" - echo "TREESHAKEN_BASE=$TREESHAKEN_BASE" - echo "TREESHAKEN_BASE_GZIP=$TREESHAKEN_BASE_GZIP" + echo "WEBGL_FILESIZE_BASE=$WEBGL_FILESIZE_BASE" + echo "WEBGL_FILESIZE_BASE_GZIP=$WEBGL_FILESIZE_BASE_GZIP" + echo "WEBGL_TREESHAKEN_BASE=$WEBGL_TREESHAKEN_BASE" + echo "WEBGL_TREESHAKEN_BASE_GZIP=$WEBGL_TREESHAKEN_BASE_GZIP" + + echo "WEBGL_FILESIZE_BASE=$WEBGL_FILESIZE_BASE" >> $GITHUB_OUTPUT + echo "WEBGL_FILESIZE_BASE_GZIP=$WEBGL_FILESIZE_BASE_GZIP" >> $GITHUB_OUTPUT + echo "WEBGL_TREESHAKEN_BASE=$WEBGL_TREESHAKEN_BASE" >> $GITHUB_OUTPUT + echo "WEBGL_TREESHAKEN_BASE_GZIP=$WEBGL_TREESHAKEN_BASE_GZIP" >> $GITHUB_OUTPUT + + echo "WEBGPU_FILESIZE_BASE=$WEBGPU_FILESIZE_BASE" + echo "WEBGPU_FILESIZE_BASE_GZIP=$WEBGPU_FILESIZE_BASE_GZIP" + echo "WEBGPU_TREESHAKEN_BASE=$WEBGPU_TREESHAKEN_BASE" + echo "WEBGPU_TREESHAKEN_BASE_GZIP=$WEBGPU_TREESHAKEN_BASE_GZIP" - echo "FILESIZE_BASE=$FILESIZE_BASE" >> $GITHUB_OUTPUT - echo "FILESIZE_BASE_GZIP=$FILESIZE_BASE_GZIP" >> $GITHUB_OUTPUT - echo "TREESHAKEN_BASE=$TREESHAKEN_BASE" >> $GITHUB_OUTPUT - echo "TREESHAKEN_BASE_GZIP=$TREESHAKEN_BASE_GZIP" >> $GITHUB_OUTPUT + echo "WEBGPU_FILESIZE_BASE=$WEBGPU_FILESIZE_BASE" >> $GITHUB_OUTPUT + echo "WEBGPU_FILESIZE_BASE_GZIP=$WEBGPU_FILESIZE_BASE_GZIP" >> $GITHUB_OUTPUT + echo "WEBGPU_TREESHAKEN_BASE=$WEBGPU_TREESHAKEN_BASE" >> $GITHUB_OUTPUT + echo "WEBGPU_TREESHAKEN_BASE_GZIP=$WEBGPU_TREESHAKEN_BASE_GZIP" >> $GITHUB_OUTPUT + + echo "WEBGPU_NODES_FILESIZE_BASE=$WEBGPU_NODES_FILESIZE_BASE" + echo "WEBGPU_NODES_FILESIZE_BASE_GZIP=$WEBGPU_NODES_FILESIZE_BASE_GZIP" + echo "WEBGPU_NODES_TREESHAKEN_BASE=$WEBGPU_NODES_TREESHAKEN_BASE" + echo "WEBGPU_NODES_TREESHAKEN_BASE_GZIP=$WEBGPU_NODES_TREESHAKEN_BASE_GZIP" + + echo "WEBGPU_NODES_FILESIZE_BASE=$WEBGPU_NODES_FILESIZE_BASE" >> $GITHUB_OUTPUT + echo "WEBGPU_NODES_FILESIZE_BASE_GZIP=$WEBGPU_NODES_FILESIZE_BASE_GZIP" >> $GITHUB_OUTPUT + echo "WEBGPU_NODES_TREESHAKEN_BASE=$WEBGPU_NODES_TREESHAKEN_BASE" >> $GITHUB_OUTPUT + echo "WEBGPU_NODES_TREESHAKEN_BASE_GZIP=$WEBGPU_NODES_TREESHAKEN_BASE_GZIP" >> $GITHUB_OUTPUT - name: Format sizes id: format # It's important these are passed as env variables. # https://securitylab.github.com/research/github-actions-untrusted-input/ env: - FILESIZE: ${{ fromJSON(steps.download-artifact.outputs.result).filesize }} - FILESIZE_GZIP: ${{ fromJSON(steps.download-artifact.outputs.result).gzip }} - FILESIZE_BASE: ${{ steps.read-size.outputs.FILESIZE_BASE }} - FILESIZE_BASE_GZIP: ${{ steps.read-size.outputs.FILESIZE_BASE_GZIP }} - TREESHAKEN: ${{ fromJSON(steps.download-artifact.outputs.result).treeshaken }} - TREESHAKEN_GZIP: ${{ fromJSON(steps.download-artifact.outputs.result).treeshakenGzip }} - TREESHAKEN_BASE: ${{ steps.read-size.outputs.TREESHAKEN_BASE }} - TREESHAKEN_BASE_GZIP: ${{ steps.read-size.outputs.TREESHAKEN_BASE_GZIP }} + WEBGL_FILESIZE: ${{ fromJSON(steps.download-artifact.outputs.result).filesize }} + WEBGL_FILESIZE_GZIP: ${{ fromJSON(steps.download-artifact.outputs.result).gzip }} + WEBGL_FILESIZE_BASE: ${{ steps.read-size.outputs.WEBGL_FILESIZE_BASE }} + WEBGL_FILESIZE_BASE_GZIP: ${{ steps.read-size.outputs.WEBGL_FILESIZE_BASE_GZIP }} + WEBGL_TREESHAKEN: ${{ fromJSON(steps.download-artifact.outputs.result).treeshaken }} + WEBGL_TREESHAKEN_GZIP: ${{ fromJSON(steps.download-artifact.outputs.result).treeshakenGzip }} + WEBGL_TREESHAKEN_BASE: ${{ steps.read-size.outputs.WEBGL_TREESHAKEN_BASE }} + WEBGL_TREESHAKEN_BASE_GZIP: ${{ steps.read-size.outputs.WEBGL_TREESHAKEN_BASE_GZIP }} + WEBGPU_FILESIZE: ${{ fromJSON(steps.download-artifact.outputs.result).filesize2 }} + WEBGPU_FILESIZE_GZIP: ${{ fromJSON(steps.download-artifact.outputs.result).gzip2 }} + WEBGPU_FILESIZE_BASE: ${{ steps.read-size.outputs.WEBGPU_FILESIZE_BASE }} + WEBGPU_FILESIZE_BASE_GZIP: ${{ steps.read-size.outputs.WEBGPU_FILESIZE_BASE_GZIP }} + WEBGPU_TREESHAKEN: ${{ fromJSON(steps.download-artifact.outputs.result).treeshaken2 }} + WEBGPU_TREESHAKEN_GZIP: ${{ fromJSON(steps.download-artifact.outputs.result).treeshakenGzip2 }} + WEBGPU_TREESHAKEN_BASE: ${{ steps.read-size.outputs.WEBGPU_TREESHAKEN_BASE }} + WEBGPU_TREESHAKEN_BASE_GZIP: ${{ steps.read-size.outputs.WEBGPU_TREESHAKEN_BASE_GZIP }} + WEBGPU_NODES_FILESIZE: ${{ fromJSON(steps.download-artifact.outputs.result).filesize3 }} + WEBGPU_NODES_FILESIZE_GZIP: ${{ fromJSON(steps.download-artifact.outputs.result).gzip3 }} + WEBGPU_NODES_FILESIZE_BASE: ${{ steps.read-size.outputs.WEBGPU_NODES_FILESIZE_BASE }} + WEBGPU_NODES_FILESIZE_BASE_GZIP: ${{ steps.read-size.outputs.WEBGPU_NODES_FILESIZE_BASE_GZIP }} + WEBGPU_NODES_TREESHAKEN: ${{ fromJSON(steps.download-artifact.outputs.result).treeshaken3 }} + WEBGPU_NODES_TREESHAKEN_GZIP: ${{ fromJSON(steps.download-artifact.outputs.result).treeshakenGzip3 }} + WEBGPU_NODES_TREESHAKEN_BASE: ${{ steps.read-size.outputs.WEBGPU_NODES_TREESHAKEN_BASE }} + WEBGPU_NODES_TREESHAKEN_BASE_GZIP: ${{ steps.read-size.outputs.WEBGPU_NODES_TREESHAKEN_BASE_GZIP }} run: | - FILESIZE_FORM=$(node ./test/treeshake/utils/format-size.js "$FILESIZE") - FILESIZE_GZIP_FORM=$(node ./test/treeshake/utils/format-size.js "$FILESIZE_GZIP") - FILESIZE_BASE_FORM=$(node ./test/treeshake/utils/format-size.js "$FILESIZE_BASE") - FILESIZE_BASE_GZIP_FORM=$(node ./test/treeshake/utils/format-size.js "$FILESIZE_BASE_GZIP") - FILESIZE_DIFF=$(node ./test/treeshake/utils/format-diff.js "$FILESIZE" "$FILESIZE_BASE") - TREESHAKEN_FORM=$(node ./test/treeshake/utils/format-size.js "$TREESHAKEN") - TREESHAKEN_GZIP_FORM=$(node ./test/treeshake/utils/format-size.js "$TREESHAKEN_GZIP") - TREESHAKEN_BASE_FORM=$(node ./test/treeshake/utils/format-size.js "$TREESHAKEN_BASE") - TREESHAKEN_BASE_GZIP_FORM=$(node ./test/treeshake/utils/format-size.js "$TREESHAKEN_BASE_GZIP") - TREESHAKEN_DIFF=$(node ./test/treeshake/utils/format-diff.js "$TREESHAKEN" "$TREESHAKEN_BASE") - - echo "FILESIZE=$FILESIZE_FORM" >> $GITHUB_OUTPUT - echo "FILESIZE_GZIP=$FILESIZE_GZIP_FORM" >> $GITHUB_OUTPUT - echo "FILESIZE_BASE=$FILESIZE_BASE_FORM" >> $GITHUB_OUTPUT - echo "FILESIZE_BASE_GZIP=$FILESIZE_BASE_GZIP_FORM" >> $GITHUB_OUTPUT - echo "FILESIZE_DIFF=$FILESIZE_DIFF" >> $GITHUB_OUTPUT - echo "TREESHAKEN=$TREESHAKEN_FORM" >> $GITHUB_OUTPUT - echo "TREESHAKEN_GZIP=$TREESHAKEN_GZIP_FORM" >> $GITHUB_OUTPUT - echo "TREESHAKEN_BASE=$TREESHAKEN_BASE_FORM" >> $GITHUB_OUTPUT - echo "TREESHAKEN_BASE_GZIP=$TREESHAKEN_BASE_GZIP_FORM" >> $GITHUB_OUTPUT - echo "TREESHAKEN_DIFF=$TREESHAKEN_DIFF" >> $GITHUB_OUTPUT + WEBGL_FILESIZE_FORM=$(node ./test/treeshake/utils/format-size.js "$WEBGL_FILESIZE") + WEBGL_FILESIZE_GZIP_FORM=$(node ./test/treeshake/utils/format-size.js "$WEBGL_FILESIZE_GZIP") + WEBGL_FILESIZE_BASE_FORM=$(node ./test/treeshake/utils/format-size.js "$WEBGL_FILESIZE_BASE") + WEBGL_FILESIZE_BASE_GZIP_FORM=$(node ./test/treeshake/utils/format-size.js "$WEBGL_FILESIZE_BASE_GZIP") + WEBGL_FILESIZE_DIFF=$(node ./test/treeshake/utils/format-diff.js "$WEBGL_FILESIZE" "$WEBGL_FILESIZE_BASE") + WEBGL_FILESIZE_DIFF_GZIP=$(node ./test/treeshake/utils/format-diff.js "$WEBGL_FILESIZE_GZIP" "$WEBGL_FILESIZE_BASE_GZIP") + + WEBGL_TREESHAKEN_FORM=$(node ./test/treeshake/utils/format-size.js "$WEBGL_TREESHAKEN") + WEBGL_TREESHAKEN_GZIP_FORM=$(node ./test/treeshake/utils/format-size.js "$WEBGL_TREESHAKEN_GZIP") + WEBGL_TREESHAKEN_BASE_FORM=$(node ./test/treeshake/utils/format-size.js "$WEBGL_TREESHAKEN_BASE") + WEBGL_TREESHAKEN_BASE_GZIP_FORM=$(node ./test/treeshake/utils/format-size.js "$WEBGL_TREESHAKEN_BASE_GZIP") + WEBGL_TREESHAKEN_DIFF=$(node ./test/treeshake/utils/format-diff.js "$WEBGL_TREESHAKEN" "$WEBGL_TREESHAKEN_BASE") + WEBGL_TREESHAKEN_DIFF_GZIP=$(node ./test/treeshake/utils/format-diff.js "$WEBGL_TREESHAKEN_GZIP" "$WEBGL_TREESHAKEN_BASE_GZIP") + + WEBGPU_FILESIZE_FORM=$(node ./test/treeshake/utils/format-size.js "$WEBGPU_FILESIZE") + WEBGPU_FILESIZE_GZIP_FORM=$(node ./test/treeshake/utils/format-size.js "$WEBGPU_FILESIZE_GZIP") + WEBGPU_FILESIZE_BASE_FORM=$(node ./test/treeshake/utils/format-size.js "$WEBGPU_FILESIZE_BASE") + WEBGPU_FILESIZE_BASE_GZIP_FORM=$(node ./test/treeshake/utils/format-size.js "$WEBGPU_FILESIZE_BASE_GZIP") + WEBGPU_FILESIZE_DIFF=$(node ./test/treeshake/utils/format-diff.js "$WEBGPU_FILESIZE" "$WEBGPU_FILESIZE_BASE") + WEBGPU_FILESIZE_DIFF_GZIP=$(node ./test/treeshake/utils/format-diff.js "$WEBGPU_FILESIZE_GZIP" "$WEBGPU_FILESIZE_BASE_GZIP") + + WEBGPU_TREESHAKEN_FORM=$(node ./test/treeshake/utils/format-size.js "$WEBGPU_TREESHAKEN") + WEBGPU_TREESHAKEN_GZIP_FORM=$(node ./test/treeshake/utils/format-size.js "$WEBGPU_TREESHAKEN_GZIP") + WEBGPU_TREESHAKEN_BASE_FORM=$(node ./test/treeshake/utils/format-size.js "$WEBGPU_TREESHAKEN_BASE") + WEBGPU_TREESHAKEN_BASE_GZIP_FORM=$(node ./test/treeshake/utils/format-size.js "$WEBGPU_TREESHAKEN_BASE_GZIP") + WEBGPU_TREESHAKEN_DIFF=$(node ./test/treeshake/utils/format-diff.js "$WEBGPU_TREESHAKEN" "$WEBGPU_TREESHAKEN_BASE") + WEBGPU_TREESHAKEN_DIFF_GZIP=$(node ./test/treeshake/utils/format-diff.js "$WEBGPU_TREESHAKEN_GZIP" "$WEBGPU_TREESHAKEN_BASE_GZIP") + + WEBGPU_NODES_FILESIZE_FORM=$(node ./test/treeshake/utils/format-size.js "$WEBGPU_NODES_FILESIZE") + WEBGPU_NODES_FILESIZE_GZIP_FORM=$(node ./test/treeshake/utils/format-size.js "$WEBGPU_NODES_FILESIZE_GZIP") + WEBGPU_NODES_FILESIZE_BASE_FORM=$(node ./test/treeshake/utils/format-size.js "$WEBGPU_NODES_FILESIZE_BASE") + WEBGPU_NODES_FILESIZE_BASE_GZIP_FORM=$(node ./test/treeshake/utils/format-size.js "$WEBGPU_NODES_FILESIZE_BASE_GZIP") + WEBGPU_NODES_FILESIZE_DIFF=$(node ./test/treeshake/utils/format-diff.js "$WEBGPU_NODES_FILESIZE" "$WEBGPU_NODES_FILESIZE_BASE") + WEBGPU_NODES_FILESIZE_DIFF_GZIP=$(node ./test/treeshake/utils/format-diff.js "$WEBGPU_NODES_FILESIZE_GZIP" "$WEBGPU_NODES_FILESIZE_BASE_GZIP") + + WEBGPU_NODES_TREESHAKEN_FORM=$(node ./test/treeshake/utils/format-size.js "$WEBGPU_NODES_TREESHAKEN") + WEBGPU_NODES_TREESHAKEN_GZIP_FORM=$(node ./test/treeshake/utils/format-size.js "$WEBGPU_NODES_TREESHAKEN_GZIP") + WEBGPU_NODES_TREESHAKEN_BASE_FORM=$(node ./test/treeshake/utils/format-size.js "$WEBGPU_NODES_TREESHAKEN_BASE") + WEBGPU_NODES_TREESHAKEN_BASE_GZIP_FORM=$(node ./test/treeshake/utils/format-size.js "$WEBGPU_NODES_TREESHAKEN_BASE_GZIP") + WEBGPU_NODES_TREESHAKEN_DIFF=$(node ./test/treeshake/utils/format-diff.js "$WEBGPU_NODES_TREESHAKEN" "$WEBGPU_NODES_TREESHAKEN_BASE") + WEBGPU_NODES_TREESHAKEN_DIFF_GZIP=$(node ./test/treeshake/utils/format-diff.js "$WEBGPU_NODES_TREESHAKEN_GZIP" "$WEBGPU_NODES_TREESHAKEN_BASE_GZIP") + + echo "WEBGL_FILESIZE=$WEBGL_FILESIZE_FORM" >> $GITHUB_OUTPUT + echo "WEBGL_FILESIZE_GZIP=$WEBGL_FILESIZE_GZIP_FORM" >> $GITHUB_OUTPUT + echo "WEBGL_FILESIZE_BASE=$WEBGL_FILESIZE_BASE_FORM" >> $GITHUB_OUTPUT + echo "WEBGL_FILESIZE_BASE_GZIP=$WEBGL_FILESIZE_BASE_GZIP_FORM" >> $GITHUB_OUTPUT + echo "WEBGL_FILESIZE_DIFF=$WEBGL_FILESIZE_DIFF" >> $GITHUB_OUTPUT + echo "WEBGL_FILESIZE_DIFF_GZIP=$WEBGL_FILESIZE_DIFF_GZIP" >> $GITHUB_OUTPUT + + echo "WEBGL_TREESHAKEN=$WEBGL_TREESHAKEN_FORM" >> $GITHUB_OUTPUT + echo "WEBGL_TREESHAKEN_GZIP=$WEBGL_TREESHAKEN_GZIP_FORM" >> $GITHUB_OUTPUT + echo "WEBGL_TREESHAKEN_BASE=$WEBGL_TREESHAKEN_BASE_FORM" >> $GITHUB_OUTPUT + echo "WEBGL_TREESHAKEN_BASE_GZIP=$WEBGL_TREESHAKEN_BASE_GZIP_FORM" >> $GITHUB_OUTPUT + echo "WEBGL_TREESHAKEN_DIFF=$WEBGL_TREESHAKEN_DIFF" >> $GITHUB_OUTPUT + echo "WEBGL_TREESHAKEN_DIFF_GZIP=$WEBGL_TREESHAKEN_DIFF_GZIP" >> $GITHUB_OUTPUT + + echo "WEBGPU_FILESIZE=$WEBGPU_FILESIZE_FORM" >> $GITHUB_OUTPUT + echo "WEBGPU_FILESIZE_GZIP=$WEBGPU_FILESIZE_GZIP_FORM" >> $GITHUB_OUTPUT + echo "WEBGPU_FILESIZE_BASE=$WEBGPU_FILESIZE_BASE_FORM" >> $GITHUB_OUTPUT + echo "WEBGPU_FILESIZE_BASE_GZIP=$WEBGPU_FILESIZE_BASE_GZIP_FORM" >> $GITHUB_OUTPUT + echo "WEBGPU_FILESIZE_DIFF=$WEBGPU_FILESIZE_DIFF" >> $GITHUB_OUTPUT + echo "WEBGPU_FILESIZE_DIFF_GZIP=$WEBGPU_FILESIZE_DIFF_GZIP" >> $GITHUB_OUTPUT + + echo "WEBGPU_TREESHAKEN=$WEBGPU_TREESHAKEN_FORM" >> $GITHUB_OUTPUT + echo "WEBGPU_TREESHAKEN_GZIP=$WEBGPU_TREESHAKEN_GZIP_FORM" >> $GITHUB_OUTPUT + echo "WEBGPU_TREESHAKEN_BASE=$WEBGPU_TREESHAKEN_BASE_FORM" >> $GITHUB_OUTPUT + echo "WEBGPU_TREESHAKEN_BASE_GZIP=$WEBGPU_TREESHAKEN_BASE_GZIP_FORM" >> $GITHUB_OUTPUT + echo "WEBGPU_TREESHAKEN_DIFF=$WEBGPU_TREESHAKEN_DIFF" >> $GITHUB_OUTPUT + echo "WEBGPU_TREESHAKEN_DIFF_GZIP=$WEBGPU_TREESHAKEN_DIFF_GZIP" >> $GITHUB_OUTPUT + + echo "WEBGPU_NODES_FILESIZE=$WEBGPU_NODES_FILESIZE_FORM" >> $GITHUB_OUTPUT + echo "WEBGPU_NODES_FILESIZE_GZIP=$WEBGPU_NODES_FILESIZE_GZIP_FORM" >> $GITHUB_OUTPUT + echo "WEBGPU_NODES_FILESIZE_BASE=$WEBGPU_NODES_FILESIZE_BASE_FORM" >> $GITHUB_OUTPUT + echo "WEBGPU_NODES_FILESIZE_BASE_GZIP=$WEBGPU_NODES_FILESIZE_BASE_GZIP_FORM" >> $GITHUB_OUTPUT + echo "WEBGPU_NODES_FILESIZE_DIFF=$WEBGPU_NODES_FILESIZE_DIFF" >> $GITHUB_OUTPUT + echo "WEBGPU_NODES_FILESIZE_DIFF_GZIP=$WEBGPU_NODES_FILESIZE_DIFF_GZIP" >> $GITHUB_OUTPUT + + echo "WEBGPU_NODES_TREESHAKEN=$WEBGPU_NODES_TREESHAKEN_FORM" >> $GITHUB_OUTPUT + echo "WEBGPU_NODES_TREESHAKEN_GZIP=$WEBGPU_NODES_TREESHAKEN_GZIP_FORM" >> $GITHUB_OUTPUT + echo "WEBGPU_NODES_TREESHAKEN_BASE=$WEBGPU_NODES_TREESHAKEN_BASE_FORM" >> $GITHUB_OUTPUT + echo "WEBGPU_NODES_TREESHAKEN_BASE_GZIP=$WEBGPU_NODES_TREESHAKEN_BASE_GZIP_FORM" >> $GITHUB_OUTPUT + echo "WEBGPU_NODES_TREESHAKEN_DIFF=$WEBGPU_NODES_TREESHAKEN_DIFF" >> $GITHUB_OUTPUT + echo "WEBGPU_NODES_TREESHAKEN_DIFF_GZIP=$WEBGPU_NODES_TREESHAKEN_DIFF_GZIP" >> $GITHUB_OUTPUT - name: Find existing comment - uses: peter-evans/find-comment@v2 + uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3 id: find-comment with: issue-number: ${{ fromJSON(steps.download-artifact.outputs.result).pr }} comment-author: 'github-actions[bot]' body-includes: Bundle size - name: Comment on PR - uses: peter-evans/create-or-update-comment@v3 + uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4 with: issue-number: ${{ fromJSON(steps.download-artifact.outputs.result).pr }} comment-id: ${{ steps.find-comment.outputs.comment-id }} @@ -143,14 +255,18 @@ jobs: _Full ESM build, minified and gzipped._ - | Filesize `${{ github.ref_name }}` | Filesize PR | Diff | - |----------|---------|------| - | ${{ steps.format.outputs.FILESIZE_BASE }} (${{ steps.format.outputs.FILESIZE_BASE_GZIP }}) | ${{ steps.format.outputs.FILESIZE }} (${{ steps.format.outputs.FILESIZE_GZIP }}) | ${{ steps.format.outputs.FILESIZE_DIFF }} | + || Before | After | Diff | + |:-:|:-:|:-:|:-:| + | WebGL | ${{ steps.format.outputs.WEBGL_FILESIZE_BASE }}
**${{ steps.format.outputs.WEBGL_FILESIZE_BASE_GZIP }}** | ${{ steps.format.outputs.WEBGL_FILESIZE }}
**${{ steps.format.outputs.WEBGL_FILESIZE_GZIP }}** | ${{ steps.format.outputs.WEBGL_FILESIZE_DIFF }}
**${{ steps.format.outputs.WEBGL_FILESIZE_DIFF_GZIP }}** | + | WebGPU | ${{ steps.format.outputs.WEBGPU_FILESIZE_BASE }}
**${{ steps.format.outputs.WEBGPU_FILESIZE_BASE_GZIP }}** | ${{ steps.format.outputs.WEBGPU_FILESIZE }}
**${{ steps.format.outputs.WEBGPU_FILESIZE_GZIP }}** | ${{ steps.format.outputs.WEBGPU_FILESIZE_DIFF }}
**${{ steps.format.outputs.WEBGPU_FILESIZE_DIFF_GZIP }}** | + | WebGPU Nodes | ${{ steps.format.outputs.WEBGPU_NODES_FILESIZE_BASE }}
**${{ steps.format.outputs.WEBGPU_NODES_FILESIZE_BASE_GZIP }}** | ${{ steps.format.outputs.WEBGPU_NODES_FILESIZE }}
**${{ steps.format.outputs.WEBGPU_NODES_FILESIZE_GZIP }}** | ${{ steps.format.outputs.WEBGPU_NODES_FILESIZE_DIFF }}
**${{ steps.format.outputs.WEBGPU_NODES_FILESIZE_DIFF_GZIP }}** | ### 🌳 Bundle size after tree-shaking _Minimal build including a renderer, camera, empty scene, and dependencies._ - | Filesize `${{ github.ref_name }}` | Filesize PR | Diff | - |----------|---------|------| - | ${{ steps.format.outputs.TREESHAKEN_BASE }} (${{ steps.format.outputs.TREESHAKEN_BASE_GZIP }}) | ${{ steps.format.outputs.TREESHAKEN }} (${{ steps.format.outputs.TREESHAKEN_GZIP }}) | ${{ steps.format.outputs.TREESHAKEN_DIFF }} | + || Before | After | Diff | + |:-:|:-:|:-:|:-:| + | WebGL | ${{ steps.format.outputs.WEBGL_TREESHAKEN_BASE }}
**${{ steps.format.outputs.WEBGL_TREESHAKEN_BASE_GZIP }}** | ${{ steps.format.outputs.WEBGL_TREESHAKEN }}
**${{ steps.format.outputs.WEBGL_TREESHAKEN_GZIP }}** | ${{ steps.format.outputs.WEBGL_TREESHAKEN_DIFF }}
**${{ steps.format.outputs.WEBGL_TREESHAKEN_DIFF_GZIP }}** | + | WebGPU | ${{ steps.format.outputs.WEBGPU_TREESHAKEN_BASE }}
**${{ steps.format.outputs.WEBGPU_TREESHAKEN_BASE_GZIP }}** | ${{ steps.format.outputs.WEBGPU_TREESHAKEN }}
**${{ steps.format.outputs.WEBGPU_TREESHAKEN_GZIP }}** | ${{ steps.format.outputs.WEBGPU_TREESHAKEN_DIFF }}
**${{ steps.format.outputs.WEBGPU_TREESHAKEN_DIFF_GZIP }}** | + | WebGPU Nodes | ${{ steps.format.outputs.WEBGPU_NODES_TREESHAKEN_BASE }}
**${{ steps.format.outputs.WEBGPU_NODES_TREESHAKEN_BASE_GZIP }}** | ${{ steps.format.outputs.WEBGPU_NODES_TREESHAKEN }}
**${{ steps.format.outputs.WEBGPU_NODES_TREESHAKEN_GZIP }}** | ${{ steps.format.outputs.WEBGPU_NODES_TREESHAKEN_DIFF }}
**${{ steps.format.outputs.WEBGPU_NODES_TREESHAKEN_DIFF_GZIP }}** | diff --git a/.gitignore b/.gitignore index 50df59a8212b6c..9bbf0fbacf2f7a 100644 --- a/.gitignore +++ b/.gitignore @@ -8,11 +8,16 @@ npm-debug.log .vs/ test/unit/build +test/treeshake/index-src.bundle.min.js test/treeshake/index.bundle.js test/treeshake/index.bundle.min.js -test/treeshake/index-src.bundle.min.js +test/treeshake/index.webgpu.bundle.js +test/treeshake/index.webgpu.bundle.min.js +test/treeshake/index.webgpu.nodes.bundle.js +test/treeshake/index.webgpu.nodes.bundle.min.js test/treeshake/stats.html test/e2e/chromium test/e2e/output-screenshots -**/node_modules \ No newline at end of file +**/node_modules +**/docs_new \ No newline at end of file diff --git a/LICENSE b/LICENSE index d07e209686512b..cf781430404b0a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License -Copyright © 2010-2023 three.js authors +Copyright © 2010-2025 three.js authors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index a032a35c7a140f..9357702490c6e7 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,12 @@ [![NPM Package][npm]][npm-url] [![Build Size][build-size]][build-size-url] [![NPM Downloads][npm-downloads]][npmtrends-url] -[![DeepScan][deepscan]][deepscan-url] [![Discord][discord]][discord-url] +[![DeepWiki][deepwiki]][deepwiki-url] #### JavaScript 3D library -The aim of the project is to create an easy to use, lightweight, cross-browser, general purpose 3D library. The current builds only include a WebGL renderer but WebGPU (experimental), SVG and CSS3D renderers are also available as addons. +The aim of the project is to create an easy-to-use, lightweight, cross-browser, general-purpose 3D library. The current builds only include WebGL and WebGPU renderers but SVG and CSS3D renderers are also available as addons. [Examples](https://threejs.org/examples/) — [Docs](https://threejs.org/docs/) — @@ -26,9 +26,11 @@ This code creates a scene, a camera, and a geometric cube, and it adds the cube ```javascript import * as THREE from 'three'; +const width = window.innerWidth, height = window.innerHeight; + // init -const camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.01, 10 ); +const camera = new THREE.PerspectiveCamera( 70, width / height, 0.01, 10 ); camera.position.z = 1; const scene = new THREE.Scene(); @@ -40,13 +42,13 @@ const mesh = new THREE.Mesh( geometry, material ); scene.add( mesh ); const renderer = new THREE.WebGLRenderer( { antialias: true } ); -renderer.setSize( window.innerWidth, window.innerHeight ); -renderer.setAnimationLoop( animation ); +renderer.setSize( width, height ); +renderer.setAnimationLoop( animate ); document.body.appendChild( renderer.domElement ); // animation -function animation( time ) { +function animate( time ) { mesh.rotation.x = time / 2000; mesh.rotation.y = time / 1000; @@ -56,7 +58,7 @@ function animation( time ) { } ``` -If everything went well, you should see [this](https://jsfiddle.net/7u84j6kp/). +If everything goes well, you should see [this](https://jsfiddle.net/v98k6oze/). ### Cloning this repository @@ -77,8 +79,8 @@ git clone --depth=1 https://github.com/mrdoob/three.js.git [build-size-url]: https://bundlephobia.com/result?p=three [npm-downloads]: https://img.shields.io/npm/dw/three [npmtrends-url]: https://www.npmtrends.com/three -[deepscan]: https://deepscan.io/api/teams/16600/projects/19901/branches/525701/badge/grade.svg -[deepscan-url]: https://deepscan.io/dashboard#view=project&tid=16600&pid=19901&bid=525701 [discord]: https://img.shields.io/discord/685241246557667386 [discord-url]: https://discord.gg/56GBJwAnUS +[deepwiki]: https://deepwiki.com/badge.svg +[deepwiki-url]: https://deepwiki.com/mrdoob/three.js diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000000000..6b009001faf3e0 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,18 @@ +# Security Policy + +If you have discovered a security vulnerability in this project, please report it +privately. **Do not disclose it as a public issue.** This gives us time to work with you +to fix the issue before public exposure, reducing the chance that the exploit will be +used before a patch is released. + +You may submit the report in the following ways: + +- Send an email to hello@mrdoob.com. + +Please provide the following information in your report: + +- A description of the vulnerability and its impact. +- How to reproduce the issue. + +This project is maintained by volunteers on a reasonable-effort basis. As such, +we ask that you give us 90 days to work on a fix before public exposure. diff --git a/build/three.cjs b/build/three.cjs index ac5e638ceb46dd..62aaf1b11d8d1f 100644 --- a/build/three.cjs +++ b/build/three.cjs @@ -1,215 +1,1708 @@ /** * @license - * Copyright 2010-2023 Three.js Authors + * Copyright 2010-2025 Three.js Authors * SPDX-License-Identifier: MIT */ 'use strict'; -const REVISION = '153dev'; +const REVISION = '178'; +/** + * Represents mouse buttons and interaction types in context of controls. + * + * @type {ConstantsMouse} + * @constant + */ const MOUSE = { LEFT: 0, MIDDLE: 1, RIGHT: 2, ROTATE: 0, DOLLY: 1, PAN: 2 }; + +/** + * Represents touch interaction types in context of controls. + * + * @type {ConstantsTouch} + * @constant + */ const TOUCH = { ROTATE: 0, PAN: 1, DOLLY_PAN: 2, DOLLY_ROTATE: 3 }; + +/** + * Disables face culling. + * + * @type {number} + * @constant + */ const CullFaceNone = 0; + +/** + * Culls back faces. + * + * @type {number} + * @constant + */ const CullFaceBack = 1; + +/** + * Culls front faces. + * + * @type {number} + * @constant + */ const CullFaceFront = 2; + +/** + * Culls both front and back faces. + * + * @type {number} + * @constant + */ const CullFaceFrontBack = 3; + +/** + * Gives unfiltered shadow maps - fastest, but lowest quality. + * + * @type {number} + * @constant + */ const BasicShadowMap = 0; + +/** + * Filters shadow maps using the Percentage-Closer Filtering (PCF) algorithm. + * + * @type {number} + * @constant + */ const PCFShadowMap = 1; + +/** + * Filters shadow maps using the Percentage-Closer Filtering (PCF) algorithm with + * better soft shadows especially when using low-resolution shadow maps. + * + * @type {number} + * @constant + */ const PCFSoftShadowMap = 2; + +/** + * Filters shadow maps using the Variance Shadow Map (VSM) algorithm. + * When using VSMShadowMap all shadow receivers will also cast shadows. + * + * @type {number} + * @constant + */ const VSMShadowMap = 3; + +/** + * Only front faces are rendered. + * + * @type {number} + * @constant + */ const FrontSide = 0; + +/** + * Only back faces are rendered. + * + * @type {number} + * @constant + */ const BackSide = 1; + +/** + * Both front and back faces are rendered. + * + * @type {number} + * @constant + */ const DoubleSide = 2; -const TwoPassDoubleSide = 2; // r149 + +/** + * No blending is performed which effectively disables + * alpha transparency. + * + * @type {number} + * @constant + */ const NoBlending = 0; + +/** + * The default blending. + * + * @type {number} + * @constant + */ const NormalBlending = 1; + +/** + * Represents additive blending. + * + * @type {number} + * @constant + */ const AdditiveBlending = 2; + +/** + * Represents subtractive blending. + * + * @type {number} + * @constant + */ const SubtractiveBlending = 3; + +/** + * Represents multiply blending. + * + * @type {number} + * @constant + */ const MultiplyBlending = 4; + +/** + * Represents custom blending. + * + * @type {number} + * @constant + */ const CustomBlending = 5; + +/** + * A `source + destination` blending equation. + * + * @type {number} + * @constant + */ const AddEquation = 100; + +/** + * A `source - destination` blending equation. + * + * @type {number} + * @constant + */ const SubtractEquation = 101; + +/** + * A `destination - source` blending equation. + * + * @type {number} + * @constant + */ const ReverseSubtractEquation = 102; + +/** + * A blend equation that uses the minimum of source and destination. + * + * @type {number} + * @constant + */ const MinEquation = 103; + +/** + * A blend equation that uses the maximum of source and destination. + * + * @type {number} + * @constant + */ const MaxEquation = 104; + +/** + * Multiplies all colors by `0`. + * + * @type {number} + * @constant + */ const ZeroFactor = 200; + +/** + * Multiplies all colors by `1`. + * + * @type {number} + * @constant + */ const OneFactor = 201; + +/** + * Multiplies all colors by the source colors. + * + * @type {number} + * @constant + */ const SrcColorFactor = 202; + +/** + * Multiplies all colors by `1` minus each source color. + * + * @type {number} + * @constant + */ const OneMinusSrcColorFactor = 203; + +/** + * Multiplies all colors by the source alpha value. + * + * @type {number} + * @constant + */ const SrcAlphaFactor = 204; + +/** + * Multiplies all colors by 1 minus the source alpha value. + * + * @type {number} + * @constant + */ const OneMinusSrcAlphaFactor = 205; + +/** + * Multiplies all colors by the destination alpha value. + * + * @type {number} + * @constant + */ const DstAlphaFactor = 206; + +/** + * Multiplies all colors by `1` minus the destination alpha value. + * + * @type {number} + * @constant + */ const OneMinusDstAlphaFactor = 207; + +/** + * Multiplies all colors by the destination color. + * + * @type {number} + * @constant + */ const DstColorFactor = 208; + +/** + * Multiplies all colors by `1` minus each destination color. + * + * @type {number} + * @constant + */ const OneMinusDstColorFactor = 209; + +/** + * Multiplies the RGB colors by the smaller of either the source alpha + * value or the value of `1` minus the destination alpha value. The alpha + * value is multiplied by `1`. + * + * @type {number} + * @constant + */ const SrcAlphaSaturateFactor = 210; + +/** + * Multiplies all colors by a constant color. + * + * @type {number} + * @constant + */ +const ConstantColorFactor = 211; + +/** + * Multiplies all colors by `1` minus a constant color. + * + * @type {number} + * @constant + */ +const OneMinusConstantColorFactor = 212; + +/** + * Multiplies all colors by a constant alpha value. + * + * @type {number} + * @constant + */ +const ConstantAlphaFactor = 213; + +/** + * Multiplies all colors by 1 minus a constant alpha value. + * + * @type {number} + * @constant + */ +const OneMinusConstantAlphaFactor = 214; + +/** + * Never pass. + * + * @type {number} + * @constant + */ const NeverDepth = 0; + +/** + * Always pass. + * + * @type {number} + * @constant + */ const AlwaysDepth = 1; + +/** + * Pass if the incoming value is less than the depth buffer value. + * + * @type {number} + * @constant + */ const LessDepth = 2; + +/** + * Pass if the incoming value is less than or equal to the depth buffer value. + * + * @type {number} + * @constant + */ const LessEqualDepth = 3; + +/** + * Pass if the incoming value equals the depth buffer value. + * + * @type {number} + * @constant + */ const EqualDepth = 4; + +/** + * Pass if the incoming value is greater than or equal to the depth buffer value. + * + * @type {number} + * @constant + */ const GreaterEqualDepth = 5; + +/** + * Pass if the incoming value is greater than the depth buffer value. + * + * @type {number} + * @constant + */ const GreaterDepth = 6; + +/** + * Pass if the incoming value is not equal to the depth buffer value. + * + * @type {number} + * @constant + */ const NotEqualDepth = 7; + +/** + * Multiplies the environment map color with the surface color. + * + * @type {number} + * @constant + */ const MultiplyOperation = 0; + +/** + * Uses reflectivity to blend between the two colors. + * + * @type {number} + * @constant + */ const MixOperation = 1; + +/** + * Adds the two colors. + * + * @type {number} + * @constant + */ const AddOperation = 2; + +/** + * No tone mapping is applied. + * + * @type {number} + * @constant + */ const NoToneMapping = 0; + +/** + * Linear tone mapping. + * + * @type {number} + * @constant + */ const LinearToneMapping = 1; + +/** + * Reinhard tone mapping. + * + * @type {number} + * @constant + */ const ReinhardToneMapping = 2; + +/** + * Cineon tone mapping. + * + * @type {number} + * @constant + */ const CineonToneMapping = 3; + +/** + * ACES Filmic tone mapping. + * + * @type {number} + * @constant + */ const ACESFilmicToneMapping = 4; + +/** + * Custom tone mapping. + * + * Expects a custom implementation by modifying shader code of the material's fragment shader. + * + * @type {number} + * @constant + */ const CustomToneMapping = 5; +/** + * AgX tone mapping. + * + * @type {number} + * @constant + */ +const AgXToneMapping = 6; + +/** + * Neutral tone mapping. + * + * Implementation based on the Khronos 3D Commerce Group standard tone mapping. + * + * @type {number} + * @constant + */ +const NeutralToneMapping = 7; + +/** + * The skinned mesh shares the same world space as the skeleton. + * + * @type {string} + * @constant + */ +const AttachedBindMode = 'attached'; + +/** + * The skinned mesh does not share the same world space as the skeleton. + * This is useful when a skeleton is shared across multiple skinned meshes. + * + * @type {string} + * @constant + */ +const DetachedBindMode = 'detached'; + +/** + * Maps textures using the geometry's UV coordinates. + * + * @type {number} + * @constant + */ const UVMapping = 300; + +/** + * Reflection mapping for cube textures. + * + * @type {number} + * @constant + */ const CubeReflectionMapping = 301; + +/** + * Refraction mapping for cube textures. + * + * @type {number} + * @constant + */ const CubeRefractionMapping = 302; + +/** + * Reflection mapping for equirectangular textures. + * + * @type {number} + * @constant + */ const EquirectangularReflectionMapping = 303; + +/** + * Refraction mapping for equirectangular textures. + * + * @type {number} + * @constant + */ const EquirectangularRefractionMapping = 304; + +/** + * Reflection mapping for PMREM textures. + * + * @type {number} + * @constant + */ const CubeUVReflectionMapping = 306; + +/** + * The texture will simply repeat to infinity. + * + * @type {number} + * @constant + */ const RepeatWrapping = 1000; + +/** + * The last pixel of the texture stretches to the edge of the mesh. + * + * @type {number} + * @constant + */ const ClampToEdgeWrapping = 1001; + +/** + * The texture will repeats to infinity, mirroring on each repeat. + * + * @type {number} + * @constant + */ const MirroredRepeatWrapping = 1002; + +/** + * Returns the value of the texture element that is nearest (in Manhattan distance) + * to the specified texture coordinates. + * + * @type {number} + * @constant + */ const NearestFilter = 1003; + +/** + * Chooses the mipmap that most closely matches the size of the pixel being textured + * and uses the `NearestFilter` criterion (the texel nearest to the center of the pixel) + * to produce a texture value. + * + * @type {number} + * @constant + */ const NearestMipmapNearestFilter = 1004; -const NearestMipMapNearestFilter = 1004; +const NearestMipMapNearestFilter = 1004; // legacy + +/** + * Chooses the two mipmaps that most closely match the size of the pixel being textured and + * uses the `NearestFilter` criterion to produce a texture value from each mipmap. + * The final texture value is a weighted average of those two values. + * + * @type {number} + * @constant + */ const NearestMipmapLinearFilter = 1005; -const NearestMipMapLinearFilter = 1005; +const NearestMipMapLinearFilter = 1005; // legacy + +/** + * Returns the weighted average of the four texture elements that are closest to the specified + * texture coordinates, and can include items wrapped or repeated from other parts of a texture, + * depending on the values of `wrapS` and `wrapT`, and on the exact mapping. + * + * @type {number} + * @constant + */ const LinearFilter = 1006; + +/** + * Chooses the mipmap that most closely matches the size of the pixel being textured and uses + * the `LinearFilter` criterion (a weighted average of the four texels that are closest to the + * center of the pixel) to produce a texture value. + * + * @type {number} + * @constant + */ const LinearMipmapNearestFilter = 1007; -const LinearMipMapNearestFilter = 1007; +const LinearMipMapNearestFilter = 1007; // legacy + +/** + * Chooses the two mipmaps that most closely match the size of the pixel being textured and uses + * the `LinearFilter` criterion to produce a texture value from each mipmap. The final texture value + * is a weighted average of those two values. + * + * @type {number} + * @constant + */ const LinearMipmapLinearFilter = 1008; -const LinearMipMapLinearFilter = 1008; +const LinearMipMapLinearFilter = 1008; // legacy + +/** + * An unsigned byte data type for textures. + * + * @type {number} + * @constant + */ const UnsignedByteType = 1009; + +/** + * A byte data type for textures. + * + * @type {number} + * @constant + */ const ByteType = 1010; + +/** + * A short data type for textures. + * + * @type {number} + * @constant + */ const ShortType = 1011; + +/** + * An unsigned short data type for textures. + * + * @type {number} + * @constant + */ const UnsignedShortType = 1012; + +/** + * An int data type for textures. + * + * @type {number} + * @constant + */ const IntType = 1013; + +/** + * An unsigned int data type for textures. + * + * @type {number} + * @constant + */ const UnsignedIntType = 1014; + +/** + * A float data type for textures. + * + * @type {number} + * @constant + */ const FloatType = 1015; + +/** + * A half float data type for textures. + * + * @type {number} + * @constant + */ const HalfFloatType = 1016; + +/** + * An unsigned short 4_4_4_4 (packed) data type for textures. + * + * @type {number} + * @constant + */ const UnsignedShort4444Type = 1017; + +/** + * An unsigned short 5_5_5_1 (packed) data type for textures. + * + * @type {number} + * @constant + */ const UnsignedShort5551Type = 1018; + +/** + * An unsigned int 24_8 data type for textures. + * + * @type {number} + * @constant + */ const UnsignedInt248Type = 1020; + +/** + * An unsigned int 5_9_9_9 (packed) data type for textures. + * + * @type {number} + * @constant + */ +const UnsignedInt5999Type = 35902; + +/** + * Discards the red, green and blue components and reads just the alpha component. + * + * @type {number} + * @constant + */ const AlphaFormat = 1021; + +/** + * Discards the alpha component and reads the red, green and blue component. + * + * @type {number} + * @constant + */ +const RGBFormat = 1022; + +/** + * Reads the red, green, blue and alpha components. + * + * @type {number} + * @constant + */ const RGBAFormat = 1023; -const LuminanceFormat = 1024; -const LuminanceAlphaFormat = 1025; + +/** + * Reads each element as a single depth value, converts it to floating point, and clamps to the range `[0,1]`. + * + * @type {number} + * @constant + */ const DepthFormat = 1026; + +/** + * Reads each element is a pair of depth and stencil values. The depth component of the pair is interpreted as + * in `DepthFormat`. The stencil component is interpreted based on the depth + stencil internal format. + * + * @type {number} + * @constant + */ const DepthStencilFormat = 1027; + +/** + * Discards the green, blue and alpha components and reads just the red component. + * + * @type {number} + * @constant + */ const RedFormat = 1028; + +/** + * Discards the green, blue and alpha components and reads just the red component. The texels are read as integers instead of floating point. + * + * @type {number} + * @constant + */ const RedIntegerFormat = 1029; + +/** + * Discards the alpha, and blue components and reads the red, and green components. + * + * @type {number} + * @constant + */ const RGFormat = 1030; + +/** + * Discards the alpha, and blue components and reads the red, and green components. The texels are read as integers instead of floating point. + * + * @type {number} + * @constant + */ const RGIntegerFormat = 1031; + +/** + * Discards the alpha component and reads the red, green and blue component. The texels are read as integers instead of floating point. + * + * @type {number} + * @constant + */ +const RGBIntegerFormat = 1032; + +/** + * Reads the red, green, blue and alpha components. The texels are read as integers instead of floating point. + * + * @type {number} + * @constant + */ const RGBAIntegerFormat = 1033; +/** + * A DXT1-compressed image in an RGB image format. + * + * @type {number} + * @constant + */ const RGB_S3TC_DXT1_Format = 33776; + +/** + * A DXT1-compressed image in an RGB image format with a simple on/off alpha value. + * + * @type {number} + * @constant + */ const RGBA_S3TC_DXT1_Format = 33777; + +/** + * A DXT3-compressed image in an RGBA image format. Compared to a 32-bit RGBA texture, it offers 4:1 compression. + * + * @type {number} + * @constant + */ const RGBA_S3TC_DXT3_Format = 33778; + +/** + * A DXT5-compressed image in an RGBA image format. It also provides a 4:1 compression, but differs to the DXT3 + * compression in how the alpha compression is done. + * + * @type {number} + * @constant + */ const RGBA_S3TC_DXT5_Format = 33779; + +/** + * PVRTC RGB compression in 4-bit mode. One block for each 4×4 pixels. + * + * @type {number} + * @constant + */ const RGB_PVRTC_4BPPV1_Format = 35840; + +/** + * PVRTC RGB compression in 2-bit mode. One block for each 8×4 pixels. + * + * @type {number} + * @constant + */ const RGB_PVRTC_2BPPV1_Format = 35841; + +/** + * PVRTC RGBA compression in 4-bit mode. One block for each 4×4 pixels. + * + * @type {number} + * @constant + */ const RGBA_PVRTC_4BPPV1_Format = 35842; + +/** + * PVRTC RGBA compression in 2-bit mode. One block for each 8×4 pixels. + * + * @type {number} + * @constant + */ const RGBA_PVRTC_2BPPV1_Format = 35843; + +/** + * ETC1 RGB format. + * + * @type {number} + * @constant + */ const RGB_ETC1_Format = 36196; + +/** + * ETC2 RGB format. + * + * @type {number} + * @constant + */ const RGB_ETC2_Format = 37492; + +/** + * ETC2 RGBA format. + * + * @type {number} + * @constant + */ const RGBA_ETC2_EAC_Format = 37496; + +/** + * ASTC RGBA 4x4 format. + * + * @type {number} + * @constant + */ const RGBA_ASTC_4x4_Format = 37808; + +/** + * ASTC RGBA 5x4 format. + * + * @type {number} + * @constant + */ const RGBA_ASTC_5x4_Format = 37809; + +/** + * ASTC RGBA 5x5 format. + * + * @type {number} + * @constant + */ const RGBA_ASTC_5x5_Format = 37810; + +/** + * ASTC RGBA 6x5 format. + * + * @type {number} + * @constant + */ const RGBA_ASTC_6x5_Format = 37811; + +/** + * ASTC RGBA 6x6 format. + * + * @type {number} + * @constant + */ const RGBA_ASTC_6x6_Format = 37812; + +/** + * ASTC RGBA 8x5 format. + * + * @type {number} + * @constant + */ const RGBA_ASTC_8x5_Format = 37813; + +/** + * ASTC RGBA 8x6 format. + * + * @type {number} + * @constant + */ const RGBA_ASTC_8x6_Format = 37814; + +/** + * ASTC RGBA 8x8 format. + * + * @type {number} + * @constant + */ const RGBA_ASTC_8x8_Format = 37815; + +/** + * ASTC RGBA 10x5 format. + * + * @type {number} + * @constant + */ const RGBA_ASTC_10x5_Format = 37816; + +/** + * ASTC RGBA 10x6 format. + * + * @type {number} + * @constant + */ const RGBA_ASTC_10x6_Format = 37817; + +/** + * ASTC RGBA 10x8 format. + * + * @type {number} + * @constant + */ const RGBA_ASTC_10x8_Format = 37818; + +/** + * ASTC RGBA 10x10 format. + * + * @type {number} + * @constant + */ const RGBA_ASTC_10x10_Format = 37819; + +/** + * ASTC RGBA 12x10 format. + * + * @type {number} + * @constant + */ const RGBA_ASTC_12x10_Format = 37820; + +/** + * ASTC RGBA 12x12 format. + * + * @type {number} + * @constant + */ const RGBA_ASTC_12x12_Format = 37821; + +/** + * BPTC RGBA format. + * + * @type {number} + * @constant + */ const RGBA_BPTC_Format = 36492; + +/** + * BPTC Signed RGB format. + * + * @type {number} + * @constant + */ +const RGB_BPTC_SIGNED_Format = 36494; + +/** + * BPTC Unsigned RGB format. + * + * @type {number} + * @constant + */ +const RGB_BPTC_UNSIGNED_Format = 36495; + +/** + * RGTC1 Red format. + * + * @type {number} + * @constant + */ const RED_RGTC1_Format = 36283; + +/** + * RGTC1 Signed Red format. + * + * @type {number} + * @constant + */ const SIGNED_RED_RGTC1_Format = 36284; -const RED_GREEN_RGTC2_Format = 36285; + +/** + * RGTC2 Red Green format. + * + * @type {number} + * @constant + */ +const RED_GREEN_RGTC2_Format = 36285; + +/** + * RGTC2 Signed Red Green format. + * + * @type {number} + * @constant + */ const SIGNED_RED_GREEN_RGTC2_Format = 36286; + +/** + * Animations are played once. + * + * @type {number} + * @constant + */ const LoopOnce = 2200; + +/** + * Animations are played with a chosen number of repetitions, each time jumping from + * the end of the clip directly to its beginning. + * + * @type {number} + * @constant + */ const LoopRepeat = 2201; + +/** + * Animations are played with a chosen number of repetitions, alternately playing forward + * and backward. + * + * @type {number} + * @constant + */ const LoopPingPong = 2202; + +/** + * Discrete interpolation mode for keyframe tracks. + * + * @type {number} + * @constant + */ const InterpolateDiscrete = 2300; + +/** + * Linear interpolation mode for keyframe tracks. + * + * @type {number} + * @constant + */ const InterpolateLinear = 2301; + +/** + * Smooth interpolation mode for keyframe tracks. + * + * @type {number} + * @constant + */ const InterpolateSmooth = 2302; + +/** + * Zero curvature ending for animations. + * + * @type {number} + * @constant + */ const ZeroCurvatureEnding = 2400; + +/** + * Zero slope ending for animations. + * + * @type {number} + * @constant + */ const ZeroSlopeEnding = 2401; + +/** + * Wrap around ending for animations. + * + * @type {number} + * @constant + */ const WrapAroundEnding = 2402; + +/** + * Default animation blend mode. + * + * @type {number} + * @constant + */ const NormalAnimationBlendMode = 2500; + +/** + * Additive animation blend mode. Can be used to layer motions on top of + * each other to build complex performances from smaller re-usable assets. + * + * @type {number} + * @constant + */ const AdditiveAnimationBlendMode = 2501; + +/** + * For every three vertices draw a single triangle. + * + * @type {number} + * @constant + */ const TrianglesDrawMode = 0; + +/** + * For each vertex draw a triangle from the last three vertices. + * + * @type {number} + * @constant + */ const TriangleStripDrawMode = 1; + +/** + * For each vertex draw a triangle from the first vertex and the last two vertices. + * + * @type {number} + * @constant + */ const TriangleFanDrawMode = 2; -/** @deprecated Use LinearSRGBColorSpace or NoColorSpace in three.js r152+. */ -const LinearEncoding = 3000; -/** @deprecated Use SRGBColorSpace in three.js r152+. */ -const sRGBEncoding = 3001; + +/** + * Basic depth packing. + * + * @type {number} + * @constant + */ const BasicDepthPacking = 3200; + +/** + * A depth value is packed into 32 bit RGBA. + * + * @type {number} + * @constant + */ const RGBADepthPacking = 3201; + +/** + * A depth value is packed into 24 bit RGB. + * + * @type {number} + * @constant + */ +const RGBDepthPacking = 3202; + +/** + * A depth value is packed into 16 bit RG. + * + * @type {number} + * @constant + */ +const RGDepthPacking = 3203; + +/** + * Normal information is relative to the underlying surface. + * + * @type {number} + * @constant + */ const TangentSpaceNormalMap = 0; + +/** + * Normal information is relative to the object orientation. + * + * @type {number} + * @constant + */ const ObjectSpaceNormalMap = 1; // Color space string identifiers, matching CSS Color Module Level 4 and WebGPU names where available. + +/** + * No color space. + * + * @type {string} + * @constant + */ const NoColorSpace = ''; + +/** + * sRGB color space. + * + * @type {string} + * @constant + */ const SRGBColorSpace = 'srgb'; + +/** + * sRGB-linear color space. + * + * @type {string} + * @constant + */ const LinearSRGBColorSpace = 'srgb-linear'; -const DisplayP3ColorSpace = 'display-p3'; +/** + * Linear transfer function. + * + * @type {string} + * @constant + */ +const LinearTransfer = 'linear'; + +/** + * sRGB transfer function. + * + * @type {string} + * @constant + */ +const SRGBTransfer = 'srgb'; + +/** + * Sets the stencil buffer value to `0`. + * + * @type {number} + * @constant + */ const ZeroStencilOp = 0; + +/** + * Keeps the current value. + * + * @type {number} + * @constant + */ const KeepStencilOp = 7680; + +/** + * Sets the stencil buffer value to the specified reference value. + * + * @type {number} + * @constant + */ const ReplaceStencilOp = 7681; + +/** + * Increments the current stencil buffer value. Clamps to the maximum representable unsigned value. + * + * @type {number} + * @constant + */ const IncrementStencilOp = 7682; + +/** + * Decrements the current stencil buffer value. Clamps to `0`. + * + * @type {number} + * @constant + */ const DecrementStencilOp = 7683; + +/** + * Increments the current stencil buffer value. Wraps stencil buffer value to zero when incrementing + * the maximum representable unsigned value. + * + * @type {number} + * @constant + */ const IncrementWrapStencilOp = 34055; + +/** + * Decrements the current stencil buffer value. Wraps stencil buffer value to the maximum representable + * unsigned value when decrementing a stencil buffer value of `0`. + * + * @type {number} + * @constant + */ const DecrementWrapStencilOp = 34056; + +/** + * Inverts the current stencil buffer value bitwise. + * + * @type {number} + * @constant + */ const InvertStencilOp = 5386; +/** + * Will never return true. + * + * @type {number} + * @constant + */ const NeverStencilFunc = 512; + +/** + * Will return true if the stencil reference value is less than the current stencil value. + * + * @type {number} + * @constant + */ const LessStencilFunc = 513; + +/** + * Will return true if the stencil reference value is equal to the current stencil value. + * + * @type {number} + * @constant + */ const EqualStencilFunc = 514; + +/** + * Will return true if the stencil reference value is less than or equal to the current stencil value. + * + * @type {number} + * @constant + */ const LessEqualStencilFunc = 515; + +/** + * Will return true if the stencil reference value is greater than the current stencil value. + * + * @type {number} + * @constant + */ const GreaterStencilFunc = 516; + +/** + * Will return true if the stencil reference value is not equal to the current stencil value. + * + * @type {number} + * @constant + */ const NotEqualStencilFunc = 517; + +/** + * Will return true if the stencil reference value is greater than or equal to the current stencil value. + * + * @type {number} + * @constant + */ const GreaterEqualStencilFunc = 518; + +/** + * Will always return true. + * + * @type {number} + * @constant + */ const AlwaysStencilFunc = 519; +/** + * Never pass. + * + * @type {number} + * @constant + */ const NeverCompare = 512; + +/** + * Pass if the incoming value is less than the texture value. + * + * @type {number} + * @constant + */ const LessCompare = 513; + +/** + * Pass if the incoming value equals the texture value. + * + * @type {number} + * @constant + */ const EqualCompare = 514; + +/** + * Pass if the incoming value is less than or equal to the texture value. + * + * @type {number} + * @constant + */ const LessEqualCompare = 515; + +/** + * Pass if the incoming value is greater than the texture value. + * + * @type {number} + * @constant + */ const GreaterCompare = 516; + +/** + * Pass if the incoming value is not equal to the texture value. + * + * @type {number} + * @constant + */ const NotEqualCompare = 517; + +/** + * Pass if the incoming value is greater than or equal to the texture value. + * + * @type {number} + * @constant + */ const GreaterEqualCompare = 518; + +/** + * Always pass. + * + * @type {number} + * @constant + */ const AlwaysCompare = 519; +/** + * The contents are intended to be specified once by the application, and used many + * times as the source for drawing and image specification commands. + * + * @type {number} + * @constant + */ const StaticDrawUsage = 35044; + +/** + * The contents are intended to be respecified repeatedly by the application, and + * used many times as the source for drawing and image specification commands. + * + * @type {number} + * @constant + */ const DynamicDrawUsage = 35048; + +/** + * The contents are intended to be specified once by the application, and used at most + * a few times as the source for drawing and image specification commands. + * + * @type {number} + * @constant + */ const StreamDrawUsage = 35040; + +/** + * The contents are intended to be specified once by reading data from the 3D API, and queried + * many times by the application. + * + * @type {number} + * @constant + */ const StaticReadUsage = 35045; + +/** + * The contents are intended to be respecified repeatedly by reading data from the 3D API, and queried + * many times by the application. + * + * @type {number} + * @constant + */ const DynamicReadUsage = 35049; + +/** + * The contents are intended to be specified once by reading data from the 3D API, and queried at most + * a few times by the application + * + * @type {number} + * @constant + */ const StreamReadUsage = 35041; + +/** + * The contents are intended to be specified once by reading data from the 3D API, and used many times as + * the source for WebGL drawing and image specification commands. + * + * @type {number} + * @constant + */ const StaticCopyUsage = 35046; + +/** + * The contents are intended to be respecified repeatedly by reading data from the 3D API, and used many times + * as the source for WebGL drawing and image specification commands. + * + * @type {number} + * @constant + */ const DynamicCopyUsage = 35050; + +/** + * The contents are intended to be specified once by reading data from the 3D API, and used at most a few times + * as the source for WebGL drawing and image specification commands. + * + * @type {number} + * @constant + */ const StreamCopyUsage = 35042; +/** + * GLSL 1 shader code. + * + * @type {string} + * @constant + */ const GLSL1 = '100'; + +/** + * GLSL 3 shader code. + * + * @type {string} + * @constant + */ const GLSL3 = '300 es'; -const _SRGBAFormat = 1035; // fallback for WebGL 1 +/** + * WebGL coordinate system. + * + * @type {number} + * @constant + */ +const WebGLCoordinateSystem = 2000; + +/** + * WebGPU coordinate system. + * + * @type {number} + * @constant + */ +const WebGPUCoordinateSystem = 2001; + +/** + * Represents the different timestamp query types. + * + * @type {ConstantsTimestampQuery} + * @constant + */ +const TimestampQuery = { + COMPUTE: 'compute', + RENDER: 'render' +}; + +/** + * Represents mouse buttons and interaction types in context of controls. + * + * @type {ConstantsInterpolationSamplingType} + * @constant + */ +const InterpolationSamplingType = { + PERSPECTIVE: 'perspective', + LINEAR: 'linear', + FLAT: 'flat' +}; + +/** + * Represents the different interpolation sampling modes. + * + * @type {ConstantsInterpolationSamplingMode} + * @constant + */ +const InterpolationSamplingMode = { + NORMAL: 'normal', + CENTROID: 'centroid', + SAMPLE: 'sample', + FIRST: 'first', + EITHER: 'either' +}; + +/** + * This type represents mouse buttons and interaction types in context of controls. + * + * @typedef {Object} ConstantsMouse + * @property {number} MIDDLE - The left mouse button. + * @property {number} LEFT - The middle mouse button. + * @property {number} RIGHT - The right mouse button. + * @property {number} ROTATE - A rotate interaction. + * @property {number} DOLLY - A dolly interaction. + * @property {number} PAN - A pan interaction. + **/ + +/** + * This type represents touch interaction types in context of controls. + * + * @typedef {Object} ConstantsTouch + * @property {number} ROTATE - A rotate interaction. + * @property {number} PAN - A pan interaction. + * @property {number} DOLLY_PAN - The dolly-pan interaction. + * @property {number} DOLLY_ROTATE - A dolly-rotate interaction. + **/ + +/** + * This type represents the different timestamp query types. + * + * @typedef {Object} ConstantsTimestampQuery + * @property {string} COMPUTE - A `compute` timestamp query. + * @property {string} RENDER - A `render` timestamp query. + **/ + +/** + * Represents the different interpolation sampling types. + * + * @typedef {Object} ConstantsInterpolationSamplingType + * @property {string} PERSPECTIVE - Perspective-correct interpolation. + * @property {string} LINEAR - Linear interpolation. + * @property {string} FLAT - Flat interpolation. + */ /** - * https://github.com/mrdoob/eventdispatcher.js/ + * Represents the different interpolation sampling modes. + * + * @typedef {Object} ConstantsInterpolationSamplingMode + * @property {string} NORMAL - Normal sampling mode. + * @property {string} CENTROID - Centroid sampling mode. + * @property {string} SAMPLE - Sample-specific sampling mode. + * @property {string} FLAT_FIRST - Flat interpolation using the first vertex. + * @property {string} FLAT_EITHER - Flat interpolation using either vertex. */ +/** + * This modules allows to dispatch event objects on custom JavaScript objects. + * + * Main repository: [eventdispatcher.js]{@link https://github.com/mrdoob/eventdispatcher.js/} + * + * Code Example: + * ```js + * class Car extends EventDispatcher { + * start() { + * this.dispatchEvent( { type: 'start', message: 'vroom vroom!' } ); + * } + *}; + * + * // Using events with the custom object + * const car = new Car(); + * car.addEventListener( 'start', function ( event ) { + * alert( event.message ); + * } ); + * + * car.start(); + * ``` + */ class EventDispatcher { + /** + * Adds the given event listener to the given event type. + * + * @param {string} type - The type of event to listen to. + * @param {Function} listener - The function that gets called when the event is fired. + */ addEventListener( type, listener ) { if ( this._listeners === undefined ) this._listeners = {}; @@ -222,7 +1715,7 @@ class EventDispatcher { } - if ( listeners[ type ].indexOf( listener ) === - 1 ) { + if ( listeners[ type ].indexOf( listener ) === -1 ) { listeners[ type ].push( listener ); @@ -230,28 +1723,42 @@ class EventDispatcher { } + /** + * Returns `true` if the given event listener has been added to the given event type. + * + * @param {string} type - The type of event. + * @param {Function} listener - The listener to check. + * @return {boolean} Whether the given event listener has been added to the given event type. + */ hasEventListener( type, listener ) { - if ( this._listeners === undefined ) return false; - const listeners = this._listeners; - return listeners[ type ] !== undefined && listeners[ type ].indexOf( listener ) !== - 1; + if ( listeners === undefined ) return false; + + return listeners[ type ] !== undefined && listeners[ type ].indexOf( listener ) !== -1; } + /** + * Removes the given event listener from the given event type. + * + * @param {string} type - The type of event. + * @param {Function} listener - The listener to remove. + */ removeEventListener( type, listener ) { - if ( this._listeners === undefined ) return; - const listeners = this._listeners; + + if ( listeners === undefined ) return; + const listenerArray = listeners[ type ]; if ( listenerArray !== undefined ) { const index = listenerArray.indexOf( listener ); - if ( index !== - 1 ) { + if ( index !== -1 ) { listenerArray.splice( index, 1 ); @@ -261,11 +1768,17 @@ class EventDispatcher { } + /** + * Dispatches an event object. + * + * @param {Object} event - The event that gets fired. + */ dispatchEvent( event ) { - if ( this._listeners === undefined ) return; - const listeners = this._listeners; + + if ( listeners === undefined ) return; + const listenerArray = listeners[ event.type ]; if ( listenerArray !== undefined ) { @@ -297,9 +1810,16 @@ let _seed = 1234567; const DEG2RAD = Math.PI / 180; const RAD2DEG = 180 / Math.PI; -// http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/21963136#21963136 +/** + * Generate a [UUID]{@link https://en.wikipedia.org/wiki/Universally_unique_identifier} + * (universally unique identifier). + * + * @return {string} The UUID. + */ function generateUUID() { + // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/21963136#21963136 + const d0 = Math.random() * 0xffffffff | 0; const d1 = Math.random() * 0xffffffff | 0; const d2 = Math.random() * 0xffffffff | 0; @@ -314,30 +1834,66 @@ function generateUUID() { } +/** + * Clamps the given value between min and max. + * + * @param {number} value - The value to clamp. + * @param {number} min - The min value. + * @param {number} max - The max value. + * @return {number} The clamped value. + */ function clamp( value, min, max ) { return Math.max( min, Math.min( max, value ) ); } -// compute euclidean modulo of m % n -// https://en.wikipedia.org/wiki/Modulo_operation +/** + * Computes the Euclidean modulo of the given parameters that + * is `( ( n % m ) + m ) % m`. + * + * @param {number} n - The first parameter. + * @param {number} m - The second parameter. + * @return {number} The Euclidean modulo. + */ function euclideanModulo( n, m ) { + // https://en.wikipedia.org/wiki/Modulo_operation + return ( ( n % m ) + m ) % m; } -// Linear mapping from range to range +/** + * Performs a linear mapping from range `` to range `` + * for the given value. + * + * @param {number} x - The value to be mapped. + * @param {number} a1 - Minimum value for range A. + * @param {number} a2 - Maximum value for range A. + * @param {number} b1 - Minimum value for range B. + * @param {number} b2 - Maximum value for range B. + * @return {number} The mapped value. + */ function mapLinear( x, a1, a2, b1, b2 ) { return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 ); } -// https://www.gamedev.net/tutorials/programming/general-and-gameplay-programming/inverse-lerp-a-super-useful-yet-often-overlooked-function-r5230/ +/** + * Returns the percentage in the closed interval `[0, 1]` of the given value + * between the start and end point. + * + * @param {number} x - The start point + * @param {number} y - The end point. + * @param {number} value - A value between start and end. + * @return {number} The interpolation factor. + */ function inverseLerp( x, y, value ) { + // https://www.gamedev.net/tutorials/programming/general-and-gameplay-programming/inverse-lerp-a-super-useful-yet-often-overlooked-function-r5230/ + if ( x !== y ) { return ( value - x ) / ( y - x ); @@ -350,28 +1906,66 @@ function inverseLerp( x, y, value ) { } -// https://en.wikipedia.org/wiki/Linear_interpolation +/** + * Returns a value linearly interpolated from two known points based on the given interval - + * `t = 0` will return `x` and `t = 1` will return `y`. + * + * @param {number} x - The start point + * @param {number} y - The end point. + * @param {number} t - The interpolation factor in the closed interval `[0, 1]`. + * @return {number} The interpolated value. + */ function lerp( x, y, t ) { return ( 1 - t ) * x + t * y; } -// http://www.rorydriscoll.com/2016/03/07/frame-rate-independent-damping-using-lerp/ +/** + * Smoothly interpolate a number from `x` to `y` in a spring-like manner using a delta + * time to maintain frame rate independent movement. For details, see + * [Frame rate independent damping using lerp]{@link http://www.rorydriscoll.com/2016/03/07/frame-rate-independent-damping-using-lerp/}. + * + * @param {number} x - The current point. + * @param {number} y - The target point. + * @param {number} lambda - A higher lambda value will make the movement more sudden, + * and a lower value will make the movement more gradual. + * @param {number} dt - Delta time in seconds. + * @return {number} The interpolated value. + */ function damp( x, y, lambda, dt ) { return lerp( x, y, 1 - Math.exp( - lambda * dt ) ); } -// https://www.desmos.com/calculator/vcsjnyz7x4 +/** + * Returns a value that alternates between `0` and the given `length` parameter. + * + * @param {number} x - The value to pingpong. + * @param {number} [length=1] - The positive value the function will pingpong to. + * @return {number} The alternated value. + */ function pingpong( x, length = 1 ) { + // https://www.desmos.com/calculator/vcsjnyz7x4 + return length - Math.abs( euclideanModulo( x, length * 2 ) - length ); } -// http://en.wikipedia.org/wiki/Smoothstep +/** + * Returns a value in the range `[0,1]` that represents the percentage that `x` has + * moved between `min` and `max`, but smoothed or slowed down the closer `x` is to + * the `min` and `max`. + * + * See [Smoothstep]{@link http://en.wikipedia.org/wiki/Smoothstep} for more details. + * + * @param {number} x - The value to evaluate based on its position between min and max. + * @param {number} min - The min value. Any x value below min will be `0`. + * @param {number} max - The max value. Any x value above max will be `1`. + * @return {number} The alternated value. + */ function smoothstep( x, min, max ) { if ( x <= min ) return 0; @@ -383,6 +1977,15 @@ function smoothstep( x, min, max ) { } +/** + * A [variation on smoothstep]{@link https://en.wikipedia.org/wiki/Smoothstep#Variations} + * that has zero 1st and 2nd order derivatives at x=0 and x=1. + * + * @param {number} x - The value to evaluate based on its position between min and max. + * @param {number} min - The min value. Any x value below min will be `0`. + * @param {number} max - The max value. Any x value above max will be `1`. + * @return {number} The alternated value. + */ function smootherstep( x, min, max ) { if ( x <= min ) return 0; @@ -394,28 +1997,50 @@ function smootherstep( x, min, max ) { } -// Random integer from interval +/** + * Returns a random integer from `` interval. + * + * @param {number} low - The lower value boundary. + * @param {number} high - The upper value boundary + * @return {number} A random integer. + */ function randInt( low, high ) { return low + Math.floor( Math.random() * ( high - low + 1 ) ); } -// Random float from interval +/** + * Returns a random float from `` interval. + * + * @param {number} low - The lower value boundary. + * @param {number} high - The upper value boundary + * @return {number} A random float. + */ function randFloat( low, high ) { return low + Math.random() * ( high - low ); } -// Random float from <-range/2, range/2> interval +/** + * Returns a random integer from `<-range/2, range/2>` interval. + * + * @param {number} range - Defines the value range. + * @return {number} A random float. + */ function randFloatSpread( range ) { return range * ( 0.5 - Math.random() ); } -// Deterministic pseudo-random float in the interval [ 0, 1 ] +/** + * Returns a deterministic pseudo-random float in the interval `[0, 1]`. + * + * @param {number} [s] - The integer seed. + * @return {number} A random float. + */ function seededRandom( s ) { if ( s !== undefined ) _seed = s; @@ -432,44 +2057,81 @@ function seededRandom( s ) { } +/** + * Converts degrees to radians. + * + * @param {number} degrees - A value in degrees. + * @return {number} The converted value in radians. + */ function degToRad( degrees ) { return degrees * DEG2RAD; } +/** + * Converts radians to degrees. + * + * @param {number} radians - A value in radians. + * @return {number} The converted value in degrees. + */ function radToDeg( radians ) { return radians * RAD2DEG; } +/** + * Returns `true` if the given number is a power of two. + * + * @param {number} value - The value to check. + * @return {boolean} Whether the given number is a power of two or not. + */ function isPowerOfTwo( value ) { return ( value & ( value - 1 ) ) === 0 && value !== 0; } +/** + * Returns the smallest power of two that is greater than or equal to the given number. + * + * @param {number} value - The value to find a POT for. + * @return {number} The smallest power of two that is greater than or equal to the given number. + */ function ceilPowerOfTwo( value ) { return Math.pow( 2, Math.ceil( Math.log( value ) / Math.LN2 ) ); } +/** + * Returns the largest power of two that is less than or equal to the given number. + * + * @param {number} value - The value to find a POT for. + * @return {number} The largest power of two that is less than or equal to the given number. + */ function floorPowerOfTwo( value ) { return Math.pow( 2, Math.floor( Math.log( value ) / Math.LN2 ) ); } +/** + * Sets the given quaternion from the [Intrinsic Proper Euler Angles]{@link https://en.wikipedia.org/wiki/Euler_angles} + * defined by the given angles and order. + * + * Rotations are applied to the axes in the order specified by order: + * rotation by angle `a` is applied first, then by angle `b`, then by angle `c`. + * + * @param {Quaternion} q - The quaternion to set. + * @param {number} a - The rotation applied to the first axis, in radians. + * @param {number} b - The rotation applied to the second axis, in radians. + * @param {number} c - The rotation applied to the third axis, in radians. + * @param {('XYX'|'XZX'|'YXY'|'YZY'|'ZXZ'|'ZYZ')} order - A string specifying the axes order. + */ function setQuaternionFromProperEuler( q, a, b, c, order ) { - // Intrinsic Proper Euler Angles - see https://en.wikipedia.org/wiki/Euler_angles - - // rotations are applied to the axes in the order specified by 'order' - // rotation by angle 'a' is applied first, then by angle 'b', then by angle 'c' - // angles are in radians - const cos = Math.cos; const sin = Math.sin; @@ -518,6 +2180,13 @@ function setQuaternionFromProperEuler( q, a, b, c, order ) { } +/** + * Denormalizes the given value according to the given typed array. + * + * @param {number} value - The value to denormalize. + * @param {TypedArray} array - The typed array that defines the data type of the value. + * @return {number} The denormalize (float) value in the range `[0,1]`. + */ function denormalize( value, array ) { switch ( array.constructor ) { @@ -540,15 +2209,15 @@ function denormalize( value, array ) { case Int32Array: - return Math.max( value / 2147483647.0, - 1.0 ); + return Math.max( value / 2147483647.0, -1 ); case Int16Array: - return Math.max( value / 32767.0, - 1.0 ); + return Math.max( value / 32767.0, -1 ); case Int8Array: - return Math.max( value / 127.0, - 1.0 ); + return Math.max( value / 127.0, -1 ); default: @@ -558,6 +2227,13 @@ function denormalize( value, array ) { } +/** + * Normalizes the given value according to the given typed array. + * + * @param {number} value - The float value in the range `[0,1]` to normalize. + * @param {TypedArray} array - The typed array that defines the data type of the value. + * @return {number} The normalize value. + */ function normalize( value, array ) { switch ( array.constructor ) { @@ -598,44 +2274,321 @@ function normalize( value, array ) { } +/** + * @class + * @classdesc A collection of math utility functions. + * @hideconstructor + */ const MathUtils = { DEG2RAD: DEG2RAD, RAD2DEG: RAD2DEG, + /** + * Generate a [UUID]{@link https://en.wikipedia.org/wiki/Universally_unique_identifier} + * (universally unique identifier). + * + * @static + * @method + * @return {string} The UUID. + */ generateUUID: generateUUID, + /** + * Clamps the given value between min and max. + * + * @static + * @method + * @param {number} value - The value to clamp. + * @param {number} min - The min value. + * @param {number} max - The max value. + * @return {number} The clamped value. + */ clamp: clamp, + /** + * Computes the Euclidean modulo of the given parameters that + * is `( ( n % m ) + m ) % m`. + * + * @static + * @method + * @param {number} n - The first parameter. + * @param {number} m - The second parameter. + * @return {number} The Euclidean modulo. + */ euclideanModulo: euclideanModulo, + /** + * Performs a linear mapping from range `` to range `` + * for the given value. + * + * @static + * @method + * @param {number} x - The value to be mapped. + * @param {number} a1 - Minimum value for range A. + * @param {number} a2 - Maximum value for range A. + * @param {number} b1 - Minimum value for range B. + * @param {number} b2 - Maximum value for range B. + * @return {number} The mapped value. + */ mapLinear: mapLinear, + /** + * Returns the percentage in the closed interval `[0, 1]` of the given value + * between the start and end point. + * + * @static + * @method + * @param {number} x - The start point + * @param {number} y - The end point. + * @param {number} value - A value between start and end. + * @return {number} The interpolation factor. + */ inverseLerp: inverseLerp, + /** + * Returns a value linearly interpolated from two known points based on the given interval - + * `t = 0` will return `x` and `t = 1` will return `y`. + * + * @static + * @method + * @param {number} x - The start point + * @param {number} y - The end point. + * @param {number} t - The interpolation factor in the closed interval `[0, 1]`. + * @return {number} The interpolated value. + */ lerp: lerp, + /** + * Smoothly interpolate a number from `x` to `y` in a spring-like manner using a delta + * time to maintain frame rate independent movement. For details, see + * [Frame rate independent damping using lerp]{@link http://www.rorydriscoll.com/2016/03/07/frame-rate-independent-damping-using-lerp/}. + * + * @static + * @method + * @param {number} x - The current point. + * @param {number} y - The target point. + * @param {number} lambda - A higher lambda value will make the movement more sudden, + * and a lower value will make the movement more gradual. + * @param {number} dt - Delta time in seconds. + * @return {number} The interpolated value. + */ damp: damp, + /** + * Returns a value that alternates between `0` and the given `length` parameter. + * + * @static + * @method + * @param {number} x - The value to pingpong. + * @param {number} [length=1] - The positive value the function will pingpong to. + * @return {number} The alternated value. + */ pingpong: pingpong, + /** + * Returns a value in the range `[0,1]` that represents the percentage that `x` has + * moved between `min` and `max`, but smoothed or slowed down the closer `x` is to + * the `min` and `max`. + * + * See [Smoothstep]{@link http://en.wikipedia.org/wiki/Smoothstep} for more details. + * + * @static + * @method + * @param {number} x - The value to evaluate based on its position between min and max. + * @param {number} min - The min value. Any x value below min will be `0`. + * @param {number} max - The max value. Any x value above max will be `1`. + * @return {number} The alternated value. + */ smoothstep: smoothstep, + /** + * A [variation on smoothstep]{@link https://en.wikipedia.org/wiki/Smoothstep#Variations} + * that has zero 1st and 2nd order derivatives at x=0 and x=1. + * + * @static + * @method + * @param {number} x - The value to evaluate based on its position between min and max. + * @param {number} min - The min value. Any x value below min will be `0`. + * @param {number} max - The max value. Any x value above max will be `1`. + * @return {number} The alternated value. + */ smootherstep: smootherstep, + /** + * Returns a random integer from `` interval. + * + * @static + * @method + * @param {number} low - The lower value boundary. + * @param {number} high - The upper value boundary + * @return {number} A random integer. + */ randInt: randInt, + /** + * Returns a random float from `` interval. + * + * @static + * @method + * @param {number} low - The lower value boundary. + * @param {number} high - The upper value boundary + * @return {number} A random float. + */ randFloat: randFloat, + /** + * Returns a random integer from `<-range/2, range/2>` interval. + * + * @static + * @method + * @param {number} range - Defines the value range. + * @return {number} A random float. + */ randFloatSpread: randFloatSpread, + /** + * Returns a deterministic pseudo-random float in the interval `[0, 1]`. + * + * @static + * @method + * @param {number} [s] - The integer seed. + * @return {number} A random float. + */ seededRandom: seededRandom, + /** + * Converts degrees to radians. + * + * @static + * @method + * @param {number} degrees - A value in degrees. + * @return {number} The converted value in radians. + */ degToRad: degToRad, + /** + * Converts radians to degrees. + * + * @static + * @method + * @param {number} radians - A value in radians. + * @return {number} The converted value in degrees. + */ radToDeg: radToDeg, + /** + * Returns `true` if the given number is a power of two. + * + * @static + * @method + * @param {number} value - The value to check. + * @return {boolean} Whether the given number is a power of two or not. + */ isPowerOfTwo: isPowerOfTwo, + /** + * Returns the smallest power of two that is greater than or equal to the given number. + * + * @static + * @method + * @param {number} value - The value to find a POT for. + * @return {number} The smallest power of two that is greater than or equal to the given number. + */ ceilPowerOfTwo: ceilPowerOfTwo, + /** + * Returns the largest power of two that is less than or equal to the given number. + * + * @static + * @method + * @param {number} value - The value to find a POT for. + * @return {number} The largest power of two that is less than or equal to the given number. + */ floorPowerOfTwo: floorPowerOfTwo, + /** + * Sets the given quaternion from the [Intrinsic Proper Euler Angles]{@link https://en.wikipedia.org/wiki/Euler_angles} + * defined by the given angles and order. + * + * Rotations are applied to the axes in the order specified by order: + * rotation by angle `a` is applied first, then by angle `b`, then by angle `c`. + * + * @static + * @method + * @param {Quaternion} q - The quaternion to set. + * @param {number} a - The rotation applied to the first axis, in radians. + * @param {number} b - The rotation applied to the second axis, in radians. + * @param {number} c - The rotation applied to the third axis, in radians. + * @param {('XYX'|'XZX'|'YXY'|'YZY'|'ZXZ'|'ZYZ')} order - A string specifying the axes order. + */ setQuaternionFromProperEuler: setQuaternionFromProperEuler, + /** + * Normalizes the given value according to the given typed array. + * + * @static + * @method + * @param {number} value - The float value in the range `[0,1]` to normalize. + * @param {TypedArray} array - The typed array that defines the data type of the value. + * @return {number} The normalize value. + */ normalize: normalize, + /** + * Denormalizes the given value according to the given typed array. + * + * @static + * @method + * @param {number} value - The value to denormalize. + * @param {TypedArray} array - The typed array that defines the data type of the value. + * @return {number} The denormalize (float) value in the range `[0,1]`. + */ denormalize: denormalize }; +/** + * Class representing a 2D vector. A 2D vector is an ordered pair of numbers + * (labeled x and y), which can be used to represent a number of things, such as: + * + * - A point in 2D space (i.e. a position on a plane). + * - A direction and length across a plane. In three.js the length will + * always be the Euclidean distance(straight-line distance) from `(0, 0)` to `(x, y)` + * and the direction is also measured from `(0, 0)` towards `(x, y)`. + * - Any arbitrary ordered pair of numbers. + * + * There are other things a 2D vector can be used to represent, such as + * momentum vectors, complex numbers and so on, however these are the most + * common uses in three.js. + * + * Iterating through a vector instance will yield its components `(x, y)` in + * the corresponding order. + * ```js + * const a = new THREE.Vector2( 0, 1 ); + * + * //no arguments; will be initialised to (0, 0) + * const b = new THREE.Vector2( ); + * + * const d = a.distanceTo( b ); + * ``` + */ class Vector2 { + /** + * Constructs a new 2D vector. + * + * @param {number} [x=0] - The x value of this vector. + * @param {number} [y=0] - The y value of this vector. + */ constructor( x = 0, y = 0 ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ Vector2.prototype.isVector2 = true; + /** + * The x value of this vector. + * + * @type {number} + */ this.x = x; + + /** + * The y value of this vector. + * + * @type {number} + */ this.y = y; } + /** + * Alias for {@link Vector2#x}. + * + * @type {number} + */ get width() { return this.x; @@ -648,6 +2601,11 @@ class Vector2 { } + /** + * Alias for {@link Vector2#y}. + * + * @type {number} + */ get height() { return this.y; @@ -660,6 +2618,13 @@ class Vector2 { } + /** + * Sets the vector components. + * + * @param {number} x - The value of the x component. + * @param {number} y - The value of the y component. + * @return {Vector2} A reference to this vector. + */ set( x, y ) { this.x = x; @@ -669,6 +2634,12 @@ class Vector2 { } + /** + * Sets the vector components to the same value. + * + * @param {number} scalar - The value to set for all vector components. + * @return {Vector2} A reference to this vector. + */ setScalar( scalar ) { this.x = scalar; @@ -678,6 +2649,12 @@ class Vector2 { } + /** + * Sets the vector's x component to the given value + * + * @param {number} x - The value to set. + * @return {Vector2} A reference to this vector. + */ setX( x ) { this.x = x; @@ -686,6 +2663,12 @@ class Vector2 { } + /** + * Sets the vector's y component to the given value + * + * @param {number} y - The value to set. + * @return {Vector2} A reference to this vector. + */ setY( y ) { this.y = y; @@ -694,6 +2677,13 @@ class Vector2 { } + /** + * Allows to set a vector component with an index. + * + * @param {number} index - The component index. `0` equals to x, `1` equals to y. + * @param {number} value - The value to set. + * @return {Vector2} A reference to this vector. + */ setComponent( index, value ) { switch ( index ) { @@ -708,6 +2698,12 @@ class Vector2 { } + /** + * Returns the value of the vector component which matches the given index. + * + * @param {number} index - The component index. `0` equals to x, `1` equals to y. + * @return {number} A vector component value. + */ getComponent( index ) { switch ( index ) { @@ -720,12 +2716,23 @@ class Vector2 { } + /** + * Returns a new vector with copied values from this instance. + * + * @return {Vector2} A clone of this instance. + */ clone() { return new this.constructor( this.x, this.y ); } + /** + * Copies the values of the given vector to this instance. + * + * @param {Vector2} v - The vector to copy. + * @return {Vector2} A reference to this vector. + */ copy( v ) { this.x = v.x; @@ -735,6 +2742,12 @@ class Vector2 { } + /** + * Adds the given vector to this instance. + * + * @param {Vector2} v - The vector to add. + * @return {Vector2} A reference to this vector. + */ add( v ) { this.x += v.x; @@ -744,6 +2757,12 @@ class Vector2 { } + /** + * Adds the given scalar value to all components of this instance. + * + * @param {number} s - The scalar to add. + * @return {Vector2} A reference to this vector. + */ addScalar( s ) { this.x += s; @@ -753,6 +2772,13 @@ class Vector2 { } + /** + * Adds the given vectors and stores the result in this instance. + * + * @param {Vector2} a - The first vector. + * @param {Vector2} b - The second vector. + * @return {Vector2} A reference to this vector. + */ addVectors( a, b ) { this.x = a.x + b.x; @@ -762,6 +2788,13 @@ class Vector2 { } + /** + * Adds the given vector scaled by the given factor to this instance. + * + * @param {Vector2} v - The vector. + * @param {number} s - The factor that scales `v`. + * @return {Vector2} A reference to this vector. + */ addScaledVector( v, s ) { this.x += v.x * s; @@ -771,6 +2804,12 @@ class Vector2 { } + /** + * Subtracts the given vector from this instance. + * + * @param {Vector2} v - The vector to subtract. + * @return {Vector2} A reference to this vector. + */ sub( v ) { this.x -= v.x; @@ -780,6 +2819,12 @@ class Vector2 { } + /** + * Subtracts the given scalar value from all components of this instance. + * + * @param {number} s - The scalar to subtract. + * @return {Vector2} A reference to this vector. + */ subScalar( s ) { this.x -= s; @@ -789,6 +2834,13 @@ class Vector2 { } + /** + * Subtracts the given vectors and stores the result in this instance. + * + * @param {Vector2} a - The first vector. + * @param {Vector2} b - The second vector. + * @return {Vector2} A reference to this vector. + */ subVectors( a, b ) { this.x = a.x - b.x; @@ -798,6 +2850,12 @@ class Vector2 { } + /** + * Multiplies the given vector with this instance. + * + * @param {Vector2} v - The vector to multiply. + * @return {Vector2} A reference to this vector. + */ multiply( v ) { this.x *= v.x; @@ -807,6 +2865,12 @@ class Vector2 { } + /** + * Multiplies the given scalar value with all components of this instance. + * + * @param {number} scalar - The scalar to multiply. + * @return {Vector2} A reference to this vector. + */ multiplyScalar( scalar ) { this.x *= scalar; @@ -816,6 +2880,12 @@ class Vector2 { } + /** + * Divides this instance by the given vector. + * + * @param {Vector2} v - The vector to divide. + * @return {Vector2} A reference to this vector. + */ divide( v ) { this.x /= v.x; @@ -825,12 +2895,25 @@ class Vector2 { } + /** + * Divides this vector by the given scalar. + * + * @param {number} scalar - The scalar to divide. + * @return {Vector2} A reference to this vector. + */ divideScalar( scalar ) { return this.multiplyScalar( 1 / scalar ); } + /** + * Multiplies this vector (with an implicit 1 as the 3rd component) by + * the given 3x3 matrix. + * + * @param {Matrix3} m - The matrix to apply. + * @return {Vector2} A reference to this vector. + */ applyMatrix3( m ) { const x = this.x, y = this.y; @@ -843,6 +2926,13 @@ class Vector2 { } + /** + * If this vector's x or y value is greater than the given vector's x or y + * value, replace that value with the corresponding min value. + * + * @param {Vector2} v - The vector. + * @return {Vector2} A reference to this vector. + */ min( v ) { this.x = Math.min( this.x, v.x ); @@ -852,6 +2942,13 @@ class Vector2 { } + /** + * If this vector's x or y value is less than the given vector's x or y + * value, replace that value with the corresponding max value. + * + * @param {Vector2} v - The vector. + * @return {Vector2} A reference to this vector. + */ max( v ) { this.x = Math.max( this.x, v.x ); @@ -861,34 +2958,69 @@ class Vector2 { } + /** + * If this vector's x or y value is greater than the max vector's x or y + * value, it is replaced by the corresponding value. + * If this vector's x or y value is less than the min vector's x or y value, + * it is replaced by the corresponding value. + * + * @param {Vector2} min - The minimum x and y values. + * @param {Vector2} max - The maximum x and y values in the desired range. + * @return {Vector2} A reference to this vector. + */ clamp( min, max ) { // assumes min < max, componentwise - this.x = Math.max( min.x, Math.min( max.x, this.x ) ); - this.y = Math.max( min.y, Math.min( max.y, this.y ) ); + this.x = clamp( this.x, min.x, max.x ); + this.y = clamp( this.y, min.y, max.y ); return this; } + /** + * If this vector's x or y values are greater than the max value, they are + * replaced by the max value. + * If this vector's x or y values are less than the min value, they are + * replaced by the min value. + * + * @param {number} minVal - The minimum value the components will be clamped to. + * @param {number} maxVal - The maximum value the components will be clamped to. + * @return {Vector2} A reference to this vector. + */ clampScalar( minVal, maxVal ) { - this.x = Math.max( minVal, Math.min( maxVal, this.x ) ); - this.y = Math.max( minVal, Math.min( maxVal, this.y ) ); + this.x = clamp( this.x, minVal, maxVal ); + this.y = clamp( this.y, minVal, maxVal ); return this; } + /** + * If this vector's length is greater than the max value, it is replaced by + * the max value. + * If this vector's length is less than the min value, it is replaced by the + * min value. + * + * @param {number} min - The minimum value the vector length will be clamped to. + * @param {number} max - The maximum value the vector length will be clamped to. + * @return {Vector2} A reference to this vector. + */ clampLength( min, max ) { const length = this.length(); - return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) ); + return this.divideScalar( length || 1 ).multiplyScalar( clamp( length, min, max ) ); } + /** + * The components of this vector are rounded down to the nearest integer value. + * + * @return {Vector2} A reference to this vector. + */ floor() { this.x = Math.floor( this.x ); @@ -898,6 +3030,11 @@ class Vector2 { } + /** + * The components of this vector are rounded up to the nearest integer value. + * + * @return {Vector2} A reference to this vector. + */ ceil() { this.x = Math.ceil( this.x ); @@ -907,6 +3044,11 @@ class Vector2 { } + /** + * The components of this vector are rounded to the nearest integer value + * + * @return {Vector2} A reference to this vector. + */ round() { this.x = Math.round( this.x ); @@ -916,15 +3058,26 @@ class Vector2 { } + /** + * The components of this vector are rounded towards zero (up if negative, + * down if positive) to an integer value. + * + * @return {Vector2} A reference to this vector. + */ roundToZero() { - this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x ); - this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y ); + this.x = Math.trunc( this.x ); + this.y = Math.trunc( this.y ); return this; } + /** + * Inverts this vector - i.e. sets x = -x and y = -y. + * + * @return {Vector2} A reference to this vector. + */ negate() { this.x = - this.x; @@ -934,52 +3087,96 @@ class Vector2 { } + /** + * Calculates the dot product of the given vector with this instance. + * + * @param {Vector2} v - The vector to compute the dot product with. + * @return {number} The result of the dot product. + */ dot( v ) { return this.x * v.x + this.y * v.y; } + /** + * Calculates the cross product of the given vector with this instance. + * + * @param {Vector2} v - The vector to compute the cross product with. + * @return {number} The result of the cross product. + */ cross( v ) { return this.x * v.y - this.y * v.x; } + /** + * Computes the square of the Euclidean length (straight-line length) from + * (0, 0) to (x, y). If you are comparing the lengths of vectors, you should + * compare the length squared instead as it is slightly more efficient to calculate. + * + * @return {number} The square length of this vector. + */ lengthSq() { return this.x * this.x + this.y * this.y; } + /** + * Computes the Euclidean length (straight-line length) from (0, 0) to (x, y). + * + * @return {number} The length of this vector. + */ length() { return Math.sqrt( this.x * this.x + this.y * this.y ); } + /** + * Computes the Manhattan length of this vector. + * + * @return {number} The length of this vector. + */ manhattanLength() { return Math.abs( this.x ) + Math.abs( this.y ); } + /** + * Converts this vector to a unit vector - that is, sets it equal to a vector + * with the same direction as this one, but with a vector length of `1`. + * + * @return {Vector2} A reference to this vector. + */ normalize() { return this.divideScalar( this.length() || 1 ); } + /** + * Computes the angle in radians of this vector with respect to the positive x-axis. + * + * @return {number} The angle in radians. + */ angle() { - // computes the angle in radians with respect to the positive x-axis - const angle = Math.atan2( - this.y, - this.x ) + Math.PI; return angle; } + /** + * Returns the angle between the given vector and this instance in radians. + * + * @param {Vector2} v - The vector to compute the angle with. + * @return {number} The angle in radians. + */ angleTo( v ) { const denominator = Math.sqrt( this.lengthSq() * v.lengthSq() ); @@ -990,16 +3187,30 @@ class Vector2 { // clamp, to handle numerical problems - return Math.acos( clamp( theta, - 1, 1 ) ); + return Math.acos( clamp( theta, -1, 1 ) ); } + /** + * Computes the distance from the given vector to this instance. + * + * @param {Vector2} v - The vector to compute the distance to. + * @return {number} The distance. + */ distanceTo( v ) { return Math.sqrt( this.distanceToSquared( v ) ); } + /** + * Computes the squared distance from the given vector to this instance. + * If you are just comparing the distance with another distance, you should compare + * the distance squared instead as it is slightly more efficient to calculate. + * + * @param {Vector2} v - The vector to compute the squared distance to. + * @return {number} The squared distance. + */ distanceToSquared( v ) { const dx = this.x - v.x, dy = this.y - v.y; @@ -1007,18 +3218,40 @@ class Vector2 { } + /** + * Computes the Manhattan distance from the given vector to this instance. + * + * @param {Vector2} v - The vector to compute the Manhattan distance to. + * @return {number} The Manhattan distance. + */ manhattanDistanceTo( v ) { return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y ); } + /** + * Sets this vector to a vector with the same direction as this one, but + * with the specified length. + * + * @param {number} length - The new length of this vector. + * @return {Vector2} A reference to this vector. + */ setLength( length ) { return this.normalize().multiplyScalar( length ); } + /** + * Linearly interpolates between the given vector and this instance, where + * alpha is the percent distance along the line - alpha = 0 will be this + * vector, and alpha = 1 will be the given one. + * + * @param {Vector2} v - The vector to interpolate towards. + * @param {number} alpha - The interpolation factor, typically in the closed interval `[0, 1]`. + * @return {Vector2} A reference to this vector. + */ lerp( v, alpha ) { this.x += ( v.x - this.x ) * alpha; @@ -1028,6 +3261,16 @@ class Vector2 { } + /** + * Linearly interpolates between the given vectors, where alpha is the percent + * distance along the line - alpha = 0 will be first vector, and alpha = 1 will + * be the second one. The result is stored in this instance. + * + * @param {Vector2} v1 - The first vector. + * @param {Vector2} v2 - The second vector. + * @param {number} alpha - The interpolation factor, typically in the closed interval `[0, 1]`. + * @return {Vector2} A reference to this vector. + */ lerpVectors( v1, v2, alpha ) { this.x = v1.x + ( v2.x - v1.x ) * alpha; @@ -1037,12 +3280,26 @@ class Vector2 { } + /** + * Returns `true` if this vector is equal with the given one. + * + * @param {Vector2} v - The vector to test for equality. + * @return {boolean} Whether this vector is equal with the given one. + */ equals( v ) { return ( ( v.x === this.x ) && ( v.y === this.y ) ); } + /** + * Sets this vector's x value to be `array[ offset ]` and y + * value to be `array[ offset + 1 ]`. + * + * @param {Array} array - An array holding the vector component values. + * @param {number} [offset=0] - The offset into the array. + * @return {Vector2} A reference to this vector. + */ fromArray( array, offset = 0 ) { this.x = array[ offset ]; @@ -1052,6 +3309,14 @@ class Vector2 { } + /** + * Writes the components of this vector to the given array. If no array is provided, + * the method returns a new instance. + * + * @param {Array} [array=[]] - The target array holding the vector components. + * @param {number} [offset=0] - Index of the first element in the array. + * @return {Array} The vector components. + */ toArray( array = [], offset = 0 ) { array[ offset ] = this.x; @@ -1061,6 +3326,13 @@ class Vector2 { } + /** + * Sets the components of this vector from the given buffer attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute holding vector data. + * @param {number} index - The index into the attribute. + * @return {Vector2} A reference to this vector. + */ fromBufferAttribute( attribute, index ) { this.x = attribute.getX( index ); @@ -1070,6 +3342,13 @@ class Vector2 { } + /** + * Rotates this vector around the given center by the given angle. + * + * @param {Vector2} center - The point around which to rotate. + * @param {number} angle - The angle to rotate, in radians. + * @return {Vector2} A reference to this vector. + */ rotateAround( center, angle ) { const c = Math.cos( angle ), s = Math.sin( angle ); @@ -1084,6 +3363,12 @@ class Vector2 { } + /** + * Sets each component of this vector to a pseudo-random value between `0` and + * `1`, excluding `1`. + * + * @return {Vector2} A reference to this vector. + */ random() { this.x = Math.random(); @@ -1102,2767 +3387,4420 @@ class Vector2 { } -class Matrix3 { +/** + * Class for representing a Quaternion. Quaternions are used in three.js to represent rotations. + * + * Iterating through a vector instance will yield its components `(x, y, z, w)` in + * the corresponding order. + * + * Note that three.js expects Quaternions to be normalized. + * ```js + * const quaternion = new THREE.Quaternion(); + * quaternion.setFromAxisAngle( new THREE.Vector3( 0, 1, 0 ), Math.PI / 2 ); + * + * const vector = new THREE.Vector3( 1, 0, 0 ); + * vector.applyQuaternion( quaternion ); + * ``` + */ +class Quaternion { - constructor( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) { + /** + * Constructs a new quaternion. + * + * @param {number} [x=0] - The x value of this quaternion. + * @param {number} [y=0] - The y value of this quaternion. + * @param {number} [z=0] - The z value of this quaternion. + * @param {number} [w=1] - The w value of this quaternion. + */ + constructor( x = 0, y = 0, z = 0, w = 1 ) { - Matrix3.prototype.isMatrix3 = true; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isQuaternion = true; - this.elements = [ + this._x = x; + this._y = y; + this._z = z; + this._w = w; - 1, 0, 0, - 0, 1, 0, - 0, 0, 1 + } - ]; + /** + * Interpolates between two quaternions via SLERP. This implementation assumes the + * quaternion data are managed in flat arrays. + * + * @param {Array} dst - The destination array. + * @param {number} dstOffset - An offset into the destination array. + * @param {Array} src0 - The source array of the first quaternion. + * @param {number} srcOffset0 - An offset into the first source array. + * @param {Array} src1 - The source array of the second quaternion. + * @param {number} srcOffset1 - An offset into the second source array. + * @param {number} t - The interpolation factor in the range `[0,1]`. + * @see {@link Quaternion#slerp} + */ + static slerpFlat( dst, dstOffset, src0, srcOffset0, src1, srcOffset1, t ) { - if ( n11 !== undefined ) { + // fuzz-free, array-based Quaternion SLERP operation - this.set( n11, n12, n13, n21, n22, n23, n31, n32, n33 ); + let x0 = src0[ srcOffset0 + 0 ], + y0 = src0[ srcOffset0 + 1 ], + z0 = src0[ srcOffset0 + 2 ], + w0 = src0[ srcOffset0 + 3 ]; - } + const x1 = src1[ srcOffset1 + 0 ], + y1 = src1[ srcOffset1 + 1 ], + z1 = src1[ srcOffset1 + 2 ], + w1 = src1[ srcOffset1 + 3 ]; - } + if ( t === 0 ) { - set( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) { + dst[ dstOffset + 0 ] = x0; + dst[ dstOffset + 1 ] = y0; + dst[ dstOffset + 2 ] = z0; + dst[ dstOffset + 3 ] = w0; + return; - const te = this.elements; + } - te[ 0 ] = n11; te[ 1 ] = n21; te[ 2 ] = n31; - te[ 3 ] = n12; te[ 4 ] = n22; te[ 5 ] = n32; - te[ 6 ] = n13; te[ 7 ] = n23; te[ 8 ] = n33; + if ( t === 1 ) { - return this; + dst[ dstOffset + 0 ] = x1; + dst[ dstOffset + 1 ] = y1; + dst[ dstOffset + 2 ] = z1; + dst[ dstOffset + 3 ] = w1; + return; - } + } - identity() { + if ( w0 !== w1 || x0 !== x1 || y0 !== y1 || z0 !== z1 ) { - this.set( + let s = 1 - t; + const cos = x0 * x1 + y0 * y1 + z0 * z1 + w0 * w1, + dir = ( cos >= 0 ? 1 : -1 ), + sqrSin = 1 - cos * cos; - 1, 0, 0, - 0, 1, 0, - 0, 0, 1 + // Skip the Slerp for tiny steps to avoid numeric problems: + if ( sqrSin > Number.EPSILON ) { - ); + const sin = Math.sqrt( sqrSin ), + len = Math.atan2( sin, cos * dir ); - return this; + s = Math.sin( s * len ) / sin; + t = Math.sin( t * len ) / sin; - } + } - copy( m ) { + const tDir = t * dir; - const te = this.elements; - const me = m.elements; + x0 = x0 * s + x1 * tDir; + y0 = y0 * s + y1 * tDir; + z0 = z0 * s + z1 * tDir; + w0 = w0 * s + w1 * tDir; - te[ 0 ] = me[ 0 ]; te[ 1 ] = me[ 1 ]; te[ 2 ] = me[ 2 ]; - te[ 3 ] = me[ 3 ]; te[ 4 ] = me[ 4 ]; te[ 5 ] = me[ 5 ]; - te[ 6 ] = me[ 6 ]; te[ 7 ] = me[ 7 ]; te[ 8 ] = me[ 8 ]; + // Normalize in case we just did a lerp: + if ( s === 1 - t ) { - return this; + const f = 1 / Math.sqrt( x0 * x0 + y0 * y0 + z0 * z0 + w0 * w0 ); - } + x0 *= f; + y0 *= f; + z0 *= f; + w0 *= f; - extractBasis( xAxis, yAxis, zAxis ) { + } - xAxis.setFromMatrix3Column( this, 0 ); - yAxis.setFromMatrix3Column( this, 1 ); - zAxis.setFromMatrix3Column( this, 2 ); + } - return this; + dst[ dstOffset ] = x0; + dst[ dstOffset + 1 ] = y0; + dst[ dstOffset + 2 ] = z0; + dst[ dstOffset + 3 ] = w0; } - setFromMatrix4( m ) { - - const me = m.elements; + /** + * Multiplies two quaternions. This implementation assumes the quaternion data are managed + * in flat arrays. + * + * @param {Array} dst - The destination array. + * @param {number} dstOffset - An offset into the destination array. + * @param {Array} src0 - The source array of the first quaternion. + * @param {number} srcOffset0 - An offset into the first source array. + * @param {Array} src1 - The source array of the second quaternion. + * @param {number} srcOffset1 - An offset into the second source array. + * @return {Array} The destination array. + * @see {@link Quaternion#multiplyQuaternions}. + */ + static multiplyQuaternionsFlat( dst, dstOffset, src0, srcOffset0, src1, srcOffset1 ) { - this.set( + const x0 = src0[ srcOffset0 ]; + const y0 = src0[ srcOffset0 + 1 ]; + const z0 = src0[ srcOffset0 + 2 ]; + const w0 = src0[ srcOffset0 + 3 ]; - me[ 0 ], me[ 4 ], me[ 8 ], - me[ 1 ], me[ 5 ], me[ 9 ], - me[ 2 ], me[ 6 ], me[ 10 ] + const x1 = src1[ srcOffset1 ]; + const y1 = src1[ srcOffset1 + 1 ]; + const z1 = src1[ srcOffset1 + 2 ]; + const w1 = src1[ srcOffset1 + 3 ]; - ); + dst[ dstOffset ] = x0 * w1 + w0 * x1 + y0 * z1 - z0 * y1; + dst[ dstOffset + 1 ] = y0 * w1 + w0 * y1 + z0 * x1 - x0 * z1; + dst[ dstOffset + 2 ] = z0 * w1 + w0 * z1 + x0 * y1 - y0 * x1; + dst[ dstOffset + 3 ] = w0 * w1 - x0 * x1 - y0 * y1 - z0 * z1; - return this; + return dst; } - multiply( m ) { + /** + * The x value of this quaternion. + * + * @type {number} + * @default 0 + */ + get x() { - return this.multiplyMatrices( this, m ); + return this._x; } - premultiply( m ) { + set x( value ) { - return this.multiplyMatrices( m, this ); + this._x = value; + this._onChangeCallback(); } - multiplyMatrices( a, b ) { + /** + * The y value of this quaternion. + * + * @type {number} + * @default 0 + */ + get y() { - const ae = a.elements; - const be = b.elements; - const te = this.elements; + return this._y; - const a11 = ae[ 0 ], a12 = ae[ 3 ], a13 = ae[ 6 ]; - const a21 = ae[ 1 ], a22 = ae[ 4 ], a23 = ae[ 7 ]; - const a31 = ae[ 2 ], a32 = ae[ 5 ], a33 = ae[ 8 ]; + } - const b11 = be[ 0 ], b12 = be[ 3 ], b13 = be[ 6 ]; - const b21 = be[ 1 ], b22 = be[ 4 ], b23 = be[ 7 ]; - const b31 = be[ 2 ], b32 = be[ 5 ], b33 = be[ 8 ]; + set y( value ) { - te[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31; - te[ 3 ] = a11 * b12 + a12 * b22 + a13 * b32; - te[ 6 ] = a11 * b13 + a12 * b23 + a13 * b33; + this._y = value; + this._onChangeCallback(); - te[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31; - te[ 4 ] = a21 * b12 + a22 * b22 + a23 * b32; - te[ 7 ] = a21 * b13 + a22 * b23 + a23 * b33; + } - te[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31; - te[ 5 ] = a31 * b12 + a32 * b22 + a33 * b32; - te[ 8 ] = a31 * b13 + a32 * b23 + a33 * b33; + /** + * The z value of this quaternion. + * + * @type {number} + * @default 0 + */ + get z() { - return this; + return this._z; } - multiplyScalar( s ) { - - const te = this.elements; - - te[ 0 ] *= s; te[ 3 ] *= s; te[ 6 ] *= s; - te[ 1 ] *= s; te[ 4 ] *= s; te[ 7 ] *= s; - te[ 2 ] *= s; te[ 5 ] *= s; te[ 8 ] *= s; + set z( value ) { - return this; + this._z = value; + this._onChangeCallback(); } - determinant() { - - const te = this.elements; - - const a = te[ 0 ], b = te[ 1 ], c = te[ 2 ], - d = te[ 3 ], e = te[ 4 ], f = te[ 5 ], - g = te[ 6 ], h = te[ 7 ], i = te[ 8 ]; + /** + * The w value of this quaternion. + * + * @type {number} + * @default 1 + */ + get w() { - return a * e * i - a * f * h - b * d * i + b * f * g + c * d * h - c * e * g; + return this._w; } - invert() { - - const te = this.elements, + set w( value ) { - n11 = te[ 0 ], n21 = te[ 1 ], n31 = te[ 2 ], - n12 = te[ 3 ], n22 = te[ 4 ], n32 = te[ 5 ], - n13 = te[ 6 ], n23 = te[ 7 ], n33 = te[ 8 ], + this._w = value; + this._onChangeCallback(); - t11 = n33 * n22 - n32 * n23, - t12 = n32 * n13 - n33 * n12, - t13 = n23 * n12 - n22 * n13, + } - det = n11 * t11 + n21 * t12 + n31 * t13; + /** + * Sets the quaternion components. + * + * @param {number} x - The x value of this quaternion. + * @param {number} y - The y value of this quaternion. + * @param {number} z - The z value of this quaternion. + * @param {number} w - The w value of this quaternion. + * @return {Quaternion} A reference to this quaternion. + */ + set( x, y, z, w ) { - if ( det === 0 ) return this.set( 0, 0, 0, 0, 0, 0, 0, 0, 0 ); + this._x = x; + this._y = y; + this._z = z; + this._w = w; - const detInv = 1 / det; + this._onChangeCallback(); - te[ 0 ] = t11 * detInv; - te[ 1 ] = ( n31 * n23 - n33 * n21 ) * detInv; - te[ 2 ] = ( n32 * n21 - n31 * n22 ) * detInv; + return this; - te[ 3 ] = t12 * detInv; - te[ 4 ] = ( n33 * n11 - n31 * n13 ) * detInv; - te[ 5 ] = ( n31 * n12 - n32 * n11 ) * detInv; + } - te[ 6 ] = t13 * detInv; - te[ 7 ] = ( n21 * n13 - n23 * n11 ) * detInv; - te[ 8 ] = ( n22 * n11 - n21 * n12 ) * detInv; + /** + * Returns a new quaternion with copied values from this instance. + * + * @return {Quaternion} A clone of this instance. + */ + clone() { - return this; + return new this.constructor( this._x, this._y, this._z, this._w ); } - transpose() { + /** + * Copies the values of the given quaternion to this instance. + * + * @param {Quaternion} quaternion - The quaternion to copy. + * @return {Quaternion} A reference to this quaternion. + */ + copy( quaternion ) { - let tmp; - const m = this.elements; + this._x = quaternion.x; + this._y = quaternion.y; + this._z = quaternion.z; + this._w = quaternion.w; - tmp = m[ 1 ]; m[ 1 ] = m[ 3 ]; m[ 3 ] = tmp; - tmp = m[ 2 ]; m[ 2 ] = m[ 6 ]; m[ 6 ] = tmp; - tmp = m[ 5 ]; m[ 5 ] = m[ 7 ]; m[ 7 ] = tmp; + this._onChangeCallback(); return this; } - getNormalMatrix( matrix4 ) { + /** + * Sets this quaternion from the rotation specified by the given + * Euler angles. + * + * @param {Euler} euler - The Euler angles. + * @param {boolean} [update=true] - Whether the internal `onChange` callback should be executed or not. + * @return {Quaternion} A reference to this quaternion. + */ + setFromEuler( euler, update = true ) { - return this.setFromMatrix4( matrix4 ).invert().transpose(); + const x = euler._x, y = euler._y, z = euler._z, order = euler._order; - } + // http://www.mathworks.com/matlabcentral/fileexchange/ + // 20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/ + // content/SpinCalc.m - transposeIntoArray( r ) { + const cos = Math.cos; + const sin = Math.sin; - const m = this.elements; + const c1 = cos( x / 2 ); + const c2 = cos( y / 2 ); + const c3 = cos( z / 2 ); - r[ 0 ] = m[ 0 ]; - r[ 1 ] = m[ 3 ]; - r[ 2 ] = m[ 6 ]; - r[ 3 ] = m[ 1 ]; - r[ 4 ] = m[ 4 ]; - r[ 5 ] = m[ 7 ]; - r[ 6 ] = m[ 2 ]; - r[ 7 ] = m[ 5 ]; - r[ 8 ] = m[ 8 ]; + const s1 = sin( x / 2 ); + const s2 = sin( y / 2 ); + const s3 = sin( z / 2 ); - return this; + switch ( order ) { - } + case 'XYZ': + this._x = s1 * c2 * c3 + c1 * s2 * s3; + this._y = c1 * s2 * c3 - s1 * c2 * s3; + this._z = c1 * c2 * s3 + s1 * s2 * c3; + this._w = c1 * c2 * c3 - s1 * s2 * s3; + break; - setUvTransform( tx, ty, sx, sy, rotation, cx, cy ) { + case 'YXZ': + this._x = s1 * c2 * c3 + c1 * s2 * s3; + this._y = c1 * s2 * c3 - s1 * c2 * s3; + this._z = c1 * c2 * s3 - s1 * s2 * c3; + this._w = c1 * c2 * c3 + s1 * s2 * s3; + break; - const c = Math.cos( rotation ); - const s = Math.sin( rotation ); + case 'ZXY': + this._x = s1 * c2 * c3 - c1 * s2 * s3; + this._y = c1 * s2 * c3 + s1 * c2 * s3; + this._z = c1 * c2 * s3 + s1 * s2 * c3; + this._w = c1 * c2 * c3 - s1 * s2 * s3; + break; - this.set( - sx * c, sx * s, - sx * ( c * cx + s * cy ) + cx + tx, - - sy * s, sy * c, - sy * ( - s * cx + c * cy ) + cy + ty, - 0, 0, 1 - ); + case 'ZYX': + this._x = s1 * c2 * c3 - c1 * s2 * s3; + this._y = c1 * s2 * c3 + s1 * c2 * s3; + this._z = c1 * c2 * s3 - s1 * s2 * c3; + this._w = c1 * c2 * c3 + s1 * s2 * s3; + break; - return this; + case 'YZX': + this._x = s1 * c2 * c3 + c1 * s2 * s3; + this._y = c1 * s2 * c3 + s1 * c2 * s3; + this._z = c1 * c2 * s3 - s1 * s2 * c3; + this._w = c1 * c2 * c3 - s1 * s2 * s3; + break; - } + case 'XZY': + this._x = s1 * c2 * c3 - c1 * s2 * s3; + this._y = c1 * s2 * c3 - s1 * c2 * s3; + this._z = c1 * c2 * s3 + s1 * s2 * c3; + this._w = c1 * c2 * c3 + s1 * s2 * s3; + break; - // + default: + console.warn( 'THREE.Quaternion: .setFromEuler() encountered an unknown order: ' + order ); - scale( sx, sy ) { + } - this.premultiply( _m3.makeScale( sx, sy ) ); + if ( update === true ) this._onChangeCallback(); return this; } - rotate( theta ) { - - this.premultiply( _m3.makeRotation( - theta ) ); + /** + * Sets this quaternion from the given axis and angle. + * + * @param {Vector3} axis - The normalized axis. + * @param {number} angle - The angle in radians. + * @return {Quaternion} A reference to this quaternion. + */ + setFromAxisAngle( axis, angle ) { - return this; + // http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm - } + const halfAngle = angle / 2, s = Math.sin( halfAngle ); - translate( tx, ty ) { + this._x = axis.x * s; + this._y = axis.y * s; + this._z = axis.z * s; + this._w = Math.cos( halfAngle ); - this.premultiply( _m3.makeTranslation( tx, ty ) ); + this._onChangeCallback(); return this; } - // for 2D Transforms + /** + * Sets this quaternion from the given rotation matrix. + * + * @param {Matrix4} m - A 4x4 matrix of which the upper 3x3 of matrix is a pure rotation matrix (i.e. unscaled). + * @return {Quaternion} A reference to this quaternion. + */ + setFromRotationMatrix( m ) { - makeTranslation( x, y ) { + // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm - this.set( + // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) - 1, 0, x, - 0, 1, y, - 0, 0, 1 + const te = m.elements, - ); + m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ], + m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ], + m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ], - return this; + trace = m11 + m22 + m33; - } + if ( trace > 0 ) { - makeRotation( theta ) { + const s = 0.5 / Math.sqrt( trace + 1.0 ); - // counterclockwise + this._w = 0.25 / s; + this._x = ( m32 - m23 ) * s; + this._y = ( m13 - m31 ) * s; + this._z = ( m21 - m12 ) * s; - const c = Math.cos( theta ); - const s = Math.sin( theta ); + } else if ( m11 > m22 && m11 > m33 ) { - this.set( + const s = 2.0 * Math.sqrt( 1.0 + m11 - m22 - m33 ); - c, - s, 0, - s, c, 0, - 0, 0, 1 + this._w = ( m32 - m23 ) / s; + this._x = 0.25 * s; + this._y = ( m12 + m21 ) / s; + this._z = ( m13 + m31 ) / s; - ); + } else if ( m22 > m33 ) { - return this; + const s = 2.0 * Math.sqrt( 1.0 + m22 - m11 - m33 ); - } + this._w = ( m13 - m31 ) / s; + this._x = ( m12 + m21 ) / s; + this._y = 0.25 * s; + this._z = ( m23 + m32 ) / s; - makeScale( x, y ) { + } else { - this.set( + const s = 2.0 * Math.sqrt( 1.0 + m33 - m11 - m22 ); - x, 0, 0, - 0, y, 0, - 0, 0, 1 + this._w = ( m21 - m12 ) / s; + this._x = ( m13 + m31 ) / s; + this._y = ( m23 + m32 ) / s; + this._z = 0.25 * s; - ); + } + + this._onChangeCallback(); return this; } - // - - equals( matrix ) { + /** + * Sets this quaternion to the rotation required to rotate the direction vector + * `vFrom` to the direction vector `vTo`. + * + * @param {Vector3} vFrom - The first (normalized) direction vector. + * @param {Vector3} vTo - The second (normalized) direction vector. + * @return {Quaternion} A reference to this quaternion. + */ + setFromUnitVectors( vFrom, vTo ) { - const te = this.elements; - const me = matrix.elements; + // assumes direction vectors vFrom and vTo are normalized - for ( let i = 0; i < 9; i ++ ) { + let r = vFrom.dot( vTo ) + 1; - if ( te[ i ] !== me[ i ] ) return false; + if ( r < 1e-8 ) { // the epsilon value has been discussed in #31286 - } + // vFrom and vTo point in opposite directions - return true; + r = 0; - } + if ( Math.abs( vFrom.x ) > Math.abs( vFrom.z ) ) { - fromArray( array, offset = 0 ) { + this._x = - vFrom.y; + this._y = vFrom.x; + this._z = 0; + this._w = r; - for ( let i = 0; i < 9; i ++ ) { + } else { - this.elements[ i ] = array[ i + offset ]; + this._x = 0; + this._y = - vFrom.z; + this._z = vFrom.y; + this._w = r; - } + } - return this; + } else { - } + // crossVectors( vFrom, vTo ); // inlined to avoid cyclic dependency on Vector3 - toArray( array = [], offset = 0 ) { + this._x = vFrom.y * vTo.z - vFrom.z * vTo.y; + this._y = vFrom.z * vTo.x - vFrom.x * vTo.z; + this._z = vFrom.x * vTo.y - vFrom.y * vTo.x; + this._w = r; - const te = this.elements; + } - array[ offset ] = te[ 0 ]; - array[ offset + 1 ] = te[ 1 ]; - array[ offset + 2 ] = te[ 2 ]; + return this.normalize(); - array[ offset + 3 ] = te[ 3 ]; - array[ offset + 4 ] = te[ 4 ]; - array[ offset + 5 ] = te[ 5 ]; + } - array[ offset + 6 ] = te[ 6 ]; - array[ offset + 7 ] = te[ 7 ]; - array[ offset + 8 ] = te[ 8 ]; + /** + * Returns the angle between this quaternion and the given one in radians. + * + * @param {Quaternion} q - The quaternion to compute the angle with. + * @return {number} The angle in radians. + */ + angleTo( q ) { - return array; + return 2 * Math.acos( Math.abs( clamp( this.dot( q ), -1, 1 ) ) ); } - clone() { + /** + * Rotates this quaternion by a given angular step to the given quaternion. + * The method ensures that the final quaternion will not overshoot `q`. + * + * @param {Quaternion} q - The target quaternion. + * @param {number} step - The angular step in radians. + * @return {Quaternion} A reference to this quaternion. + */ + rotateTowards( q, step ) { - return new this.constructor().fromArray( this.elements ); + const angle = this.angleTo( q ); - } + if ( angle === 0 ) return this; -} + const t = Math.min( 1, step / angle ); -const _m3 = /*@__PURE__*/ new Matrix3(); + this.slerp( q, t ); -function arrayNeedsUint32( array ) { + return this; - // assumes larger values usually on last + } - for ( let i = array.length - 1; i >= 0; -- i ) { + /** + * Sets this quaternion to the identity quaternion; that is, to the + * quaternion that represents "no rotation". + * + * @return {Quaternion} A reference to this quaternion. + */ + identity() { - if ( array[ i ] >= 65535 ) return true; // account for PRIMITIVE_RESTART_FIXED_INDEX, #24565 + return this.set( 0, 0, 0, 1 ); } - return false; + /** + * Inverts this quaternion via {@link Quaternion#conjugate}. The + * quaternion is assumed to have unit length. + * + * @return {Quaternion} A reference to this quaternion. + */ + invert() { -} + return this.conjugate(); -const TYPED_ARRAYS = { - Int8Array: Int8Array, - Uint8Array: Uint8Array, - Uint8ClampedArray: Uint8ClampedArray, - Int16Array: Int16Array, - Uint16Array: Uint16Array, - Int32Array: Int32Array, - Uint32Array: Uint32Array, - Float32Array: Float32Array, - Float64Array: Float64Array -}; + } -function getTypedArray( type, buffer ) { + /** + * Returns the rotational conjugate of this quaternion. The conjugate of a + * quaternion represents the same rotation in the opposite direction about + * the rotational axis. + * + * @return {Quaternion} A reference to this quaternion. + */ + conjugate() { - return new TYPED_ARRAYS[ type ]( buffer ); + this._x *= -1; + this._y *= -1; + this._z *= -1; -} + this._onChangeCallback(); -function createElementNS( name ) { + return this; - return document.createElementNS( 'http://www.w3.org/1999/xhtml', name ); + } -} + /** + * Calculates the dot product of this quaternion and the given one. + * + * @param {Quaternion} v - The quaternion to compute the dot product with. + * @return {number} The result of the dot product. + */ + dot( v ) { -const _cache = {}; + return this._x * v._x + this._y * v._y + this._z * v._z + this._w * v._w; -function warnOnce( message ) { + } - if ( message in _cache ) return; + /** + * Computes the squared Euclidean length (straight-line length) of this quaternion, + * considered as a 4 dimensional vector. This can be useful if you are comparing the + * lengths of two quaternions, as this is a slightly more efficient calculation than + * {@link Quaternion#length}. + * + * @return {number} The squared Euclidean length. + */ + lengthSq() { - _cache[ message ] = true; + return this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w; - console.warn( message ); + } -} + /** + * Computes the Euclidean length (straight-line length) of this quaternion, + * considered as a 4 dimensional vector. + * + * @return {number} The Euclidean length. + */ + length() { -function SRGBToLinear( c ) { + return Math.sqrt( this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w ); - return ( c < 0.04045 ) ? c * 0.0773993808 : Math.pow( c * 0.9478672986 + 0.0521327014, 2.4 ); + } -} + /** + * Normalizes this quaternion - that is, calculated the quaternion that performs + * the same rotation as this one, but has a length equal to `1`. + * + * @return {Quaternion} A reference to this quaternion. + */ + normalize() { -function LinearToSRGB( c ) { + let l = this.length(); - return ( c < 0.0031308 ) ? c * 12.92 : 1.055 * ( Math.pow( c, 0.41666 ) ) - 0.055; + if ( l === 0 ) { -} + this._x = 0; + this._y = 0; + this._z = 0; + this._w = 1; -/** - * Matrices converting P3 <-> Rec. 709 primaries, without gamut mapping - * or clipping. Based on W3C specifications for sRGB and Display P3, - * and ICC specifications for the D50 connection space. Values in/out - * are _linear_ sRGB and _linear_ Display P3. - * - * Note that both sRGB and Display P3 use the sRGB transfer functions. - * - * Reference: - * - http://www.russellcottrell.com/photo/matrixCalculator.htm - */ + } else { -const LINEAR_SRGB_TO_LINEAR_DISPLAY_P3 = /*@__PURE__*/ new Matrix3().fromArray( [ - 0.8224621, 0.0331941, 0.0170827, - 0.1775380, 0.9668058, 0.0723974, - - 0.0000001, 0.0000001, 0.9105199 -] ); + l = 1 / l; -const LINEAR_DISPLAY_P3_TO_LINEAR_SRGB = /*@__PURE__*/ new Matrix3().fromArray( [ - 1.2249401, - 0.0420569, - 0.0196376, - - 0.2249404, 1.0420571, - 0.0786361, - 0.0000001, 0.0000000, 1.0982735 -] ); + this._x = this._x * l; + this._y = this._y * l; + this._z = this._z * l; + this._w = this._w * l; -function DisplayP3ToLinearSRGB( color ) { + } - // Display P3 uses the sRGB transfer functions - return color.convertSRGBToLinear().applyMatrix3( LINEAR_DISPLAY_P3_TO_LINEAR_SRGB ); + this._onChangeCallback(); -} + return this; -function LinearSRGBToDisplayP3( color ) { + } - // Display P3 uses the sRGB transfer functions - return color.applyMatrix3( LINEAR_SRGB_TO_LINEAR_DISPLAY_P3 ).convertLinearToSRGB(); + /** + * Multiplies this quaternion by the given one. + * + * @param {Quaternion} q - The quaternion. + * @return {Quaternion} A reference to this quaternion. + */ + multiply( q ) { -} + return this.multiplyQuaternions( this, q ); -// Conversions from to Linear-sRGB reference space. -const TO_LINEAR = { - [ LinearSRGBColorSpace ]: ( color ) => color, - [ SRGBColorSpace ]: ( color ) => color.convertSRGBToLinear(), - [ DisplayP3ColorSpace ]: DisplayP3ToLinearSRGB, -}; + } -// Conversions to from Linear-sRGB reference space. -const FROM_LINEAR = { - [ LinearSRGBColorSpace ]: ( color ) => color, - [ SRGBColorSpace ]: ( color ) => color.convertLinearToSRGB(), - [ DisplayP3ColorSpace ]: LinearSRGBToDisplayP3, -}; + /** + * Pre-multiplies this quaternion by the given one. + * + * @param {Quaternion} q - The quaternion. + * @return {Quaternion} A reference to this quaternion. + */ + premultiply( q ) { -const ColorManagement = { + return this.multiplyQuaternions( q, this ); - enabled: true, + } - get legacyMode() { + /** + * Multiplies the given quaternions and stores the result in this instance. + * + * @param {Quaternion} a - The first quaternion. + * @param {Quaternion} b - The second quaternion. + * @return {Quaternion} A reference to this quaternion. + */ + multiplyQuaternions( a, b ) { - console.warn( 'THREE.ColorManagement: .legacyMode=false renamed to .enabled=true in r150.' ); + // from http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/code/index.htm - return ! this.enabled; + const qax = a._x, qay = a._y, qaz = a._z, qaw = a._w; + const qbx = b._x, qby = b._y, qbz = b._z, qbw = b._w; - }, + this._x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby; + this._y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz; + this._z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx; + this._w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz; - set legacyMode( legacyMode ) { + this._onChangeCallback(); - console.warn( 'THREE.ColorManagement: .legacyMode=false renamed to .enabled=true in r150.' ); + return this; - this.enabled = ! legacyMode; + } - }, + /** + * Performs a spherical linear interpolation between quaternions. + * + * @param {Quaternion} qb - The target quaternion. + * @param {number} t - The interpolation factor in the closed interval `[0, 1]`. + * @return {Quaternion} A reference to this quaternion. + */ + slerp( qb, t ) { - get workingColorSpace() { + if ( t === 0 ) return this; + if ( t === 1 ) return this.copy( qb ); - return LinearSRGBColorSpace; + const x = this._x, y = this._y, z = this._z, w = this._w; - }, + // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/ - set workingColorSpace( colorSpace ) { + let cosHalfTheta = w * qb._w + x * qb._x + y * qb._y + z * qb._z; - console.warn( 'THREE.ColorManagement: .workingColorSpace is readonly.' ); + if ( cosHalfTheta < 0 ) { - }, + this._w = - qb._w; + this._x = - qb._x; + this._y = - qb._y; + this._z = - qb._z; - convert: function ( color, sourceColorSpace, targetColorSpace ) { + cosHalfTheta = - cosHalfTheta; - if ( this.enabled === false || sourceColorSpace === targetColorSpace || ! sourceColorSpace || ! targetColorSpace ) { + } else { - return color; + this.copy( qb ); } - const sourceToLinear = TO_LINEAR[ sourceColorSpace ]; - const targetFromLinear = FROM_LINEAR[ targetColorSpace ]; + if ( cosHalfTheta >= 1.0 ) { - if ( sourceToLinear === undefined || targetFromLinear === undefined ) { + this._w = w; + this._x = x; + this._y = y; + this._z = z; - throw new Error( `Unsupported color space conversion, "${ sourceColorSpace }" to "${ targetColorSpace }".` ); + return this; } - return targetFromLinear( sourceToLinear( color ) ); - - }, - - fromWorkingColorSpace: function ( color, targetColorSpace ) { + const sqrSinHalfTheta = 1.0 - cosHalfTheta * cosHalfTheta; - return this.convert( color, this.workingColorSpace, targetColorSpace ); + if ( sqrSinHalfTheta <= Number.EPSILON ) { - }, + const s = 1 - t; + this._w = s * w + t * this._w; + this._x = s * x + t * this._x; + this._y = s * y + t * this._y; + this._z = s * z + t * this._z; - toWorkingColorSpace: function ( color, sourceColorSpace ) { + this.normalize(); // normalize calls _onChangeCallback() - return this.convert( color, sourceColorSpace, this.workingColorSpace ); + return this; - }, + } -}; + const sinHalfTheta = Math.sqrt( sqrSinHalfTheta ); + const halfTheta = Math.atan2( sinHalfTheta, cosHalfTheta ); + const ratioA = Math.sin( ( 1 - t ) * halfTheta ) / sinHalfTheta, + ratioB = Math.sin( t * halfTheta ) / sinHalfTheta; -let _canvas; + this._w = ( w * ratioA + this._w * ratioB ); + this._x = ( x * ratioA + this._x * ratioB ); + this._y = ( y * ratioA + this._y * ratioB ); + this._z = ( z * ratioA + this._z * ratioB ); -class ImageUtils { + this._onChangeCallback(); - static getDataURL( image ) { + return this; - if ( /^data:/i.test( image.src ) ) { + } - return image.src; + /** + * Performs a spherical linear interpolation between the given quaternions + * and stores the result in this quaternion. + * + * @param {Quaternion} qa - The source quaternion. + * @param {Quaternion} qb - The target quaternion. + * @param {number} t - The interpolation factor in the closed interval `[0, 1]`. + * @return {Quaternion} A reference to this quaternion. + */ + slerpQuaternions( qa, qb, t ) { - } + return this.copy( qa ).slerp( qb, t ); - if ( typeof HTMLCanvasElement === 'undefined' ) { + } - return image.src; + /** + * Sets this quaternion to a uniformly random, normalized quaternion. + * + * @return {Quaternion} A reference to this quaternion. + */ + random() { - } + // Ken Shoemake + // Uniform random rotations + // D. Kirk, editor, Graphics Gems III, pages 124-132. Academic Press, New York, 1992. - let canvas; + const theta1 = 2 * Math.PI * Math.random(); + const theta2 = 2 * Math.PI * Math.random(); - if ( image instanceof HTMLCanvasElement ) { + const x0 = Math.random(); + const r1 = Math.sqrt( 1 - x0 ); + const r2 = Math.sqrt( x0 ); - canvas = image; + return this.set( + r1 * Math.sin( theta1 ), + r1 * Math.cos( theta1 ), + r2 * Math.sin( theta2 ), + r2 * Math.cos( theta2 ), + ); - } else { + } - if ( _canvas === undefined ) _canvas = createElementNS( 'canvas' ); + /** + * Returns `true` if this quaternion is equal with the given one. + * + * @param {Quaternion} quaternion - The quaternion to test for equality. + * @return {boolean} Whether this quaternion is equal with the given one. + */ + equals( quaternion ) { - _canvas.width = image.width; - _canvas.height = image.height; + return ( quaternion._x === this._x ) && ( quaternion._y === this._y ) && ( quaternion._z === this._z ) && ( quaternion._w === this._w ); - const context = _canvas.getContext( '2d' ); + } - if ( image instanceof ImageData ) { + /** + * Sets this quaternion's components from the given array. + * + * @param {Array} array - An array holding the quaternion component values. + * @param {number} [offset=0] - The offset into the array. + * @return {Quaternion} A reference to this quaternion. + */ + fromArray( array, offset = 0 ) { - context.putImageData( image, 0, 0 ); + this._x = array[ offset ]; + this._y = array[ offset + 1 ]; + this._z = array[ offset + 2 ]; + this._w = array[ offset + 3 ]; - } else { + this._onChangeCallback(); - context.drawImage( image, 0, 0, image.width, image.height ); + return this; - } + } - canvas = _canvas; + /** + * Writes the components of this quaternion to the given array. If no array is provided, + * the method returns a new instance. + * + * @param {Array} [array=[]] - The target array holding the quaternion components. + * @param {number} [offset=0] - Index of the first element in the array. + * @return {Array} The quaternion components. + */ + toArray( array = [], offset = 0 ) { - } + array[ offset ] = this._x; + array[ offset + 1 ] = this._y; + array[ offset + 2 ] = this._z; + array[ offset + 3 ] = this._w; - if ( canvas.width > 2048 || canvas.height > 2048 ) { + return array; - console.warn( 'THREE.ImageUtils.getDataURL: Image converted to jpg for performance reasons', image ); + } - return canvas.toDataURL( 'image/jpeg', 0.6 ); + /** + * Sets the components of this quaternion from the given buffer attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute holding quaternion data. + * @param {number} index - The index into the attribute. + * @return {Quaternion} A reference to this quaternion. + */ + fromBufferAttribute( attribute, index ) { - } else { + this._x = attribute.getX( index ); + this._y = attribute.getY( index ); + this._z = attribute.getZ( index ); + this._w = attribute.getW( index ); - return canvas.toDataURL( 'image/png' ); + this._onChangeCallback(); - } + return this; } - static sRGBToLinear( image ) { + /** + * This methods defines the serialization result of this class. Returns the + * numerical elements of this quaternion in an array of format `[x, y, z, w]`. + * + * @return {Array} The serialized quaternion. + */ + toJSON() { - if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || - ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) || - ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) { + return this.toArray(); - const canvas = createElementNS( 'canvas' ); + } - canvas.width = image.width; - canvas.height = image.height; + _onChange( callback ) { - const context = canvas.getContext( '2d' ); - context.drawImage( image, 0, 0, image.width, image.height ); + this._onChangeCallback = callback; - const imageData = context.getImageData( 0, 0, image.width, image.height ); - const data = imageData.data; + return this; - for ( let i = 0; i < data.length; i ++ ) { + } - data[ i ] = SRGBToLinear( data[ i ] / 255 ) * 255; + _onChangeCallback() {} - } + *[ Symbol.iterator ]() { - context.putImageData( imageData, 0, 0 ); + yield this._x; + yield this._y; + yield this._z; + yield this._w; - return canvas; + } - } else if ( image.data ) { +} - const data = image.data.slice( 0 ); +/** + * Class representing a 3D vector. A 3D vector is an ordered triplet of numbers + * (labeled x, y and z), which can be used to represent a number of things, such as: + * + * - A point in 3D space. + * - A direction and length in 3D space. In three.js the length will + * always be the Euclidean distance(straight-line distance) from `(0, 0, 0)` to `(x, y, z)` + * and the direction is also measured from `(0, 0, 0)` towards `(x, y, z)`. + * - Any arbitrary ordered triplet of numbers. + * + * There are other things a 3D vector can be used to represent, such as + * momentum vectors and so on, however these are the most + * common uses in three.js. + * + * Iterating through a vector instance will yield its components `(x, y, z)` in + * the corresponding order. + * ```js + * const a = new THREE.Vector3( 0, 1, 0 ); + * + * //no arguments; will be initialised to (0, 0, 0) + * const b = new THREE.Vector3( ); + * + * const d = a.distanceTo( b ); + * ``` + */ +class Vector3 { - for ( let i = 0; i < data.length; i ++ ) { + /** + * Constructs a new 3D vector. + * + * @param {number} [x=0] - The x value of this vector. + * @param {number} [y=0] - The y value of this vector. + * @param {number} [z=0] - The z value of this vector. + */ + constructor( x = 0, y = 0, z = 0 ) { - if ( data instanceof Uint8Array || data instanceof Uint8ClampedArray ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + Vector3.prototype.isVector3 = true; - data[ i ] = Math.floor( SRGBToLinear( data[ i ] / 255 ) * 255 ); + /** + * The x value of this vector. + * + * @type {number} + */ + this.x = x; - } else { + /** + * The y value of this vector. + * + * @type {number} + */ + this.y = y; - // assuming float + /** + * The z value of this vector. + * + * @type {number} + */ + this.z = z; - data[ i ] = SRGBToLinear( data[ i ] ); + } - } + /** + * Sets the vector components. + * + * @param {number} x - The value of the x component. + * @param {number} y - The value of the y component. + * @param {number} z - The value of the z component. + * @return {Vector3} A reference to this vector. + */ + set( x, y, z ) { - } + if ( z === undefined ) z = this.z; // sprite.scale.set(x,y) - return { - data: data, - width: image.width, - height: image.height - }; + this.x = x; + this.y = y; + this.z = z; - } else { + return this; - console.warn( 'THREE.ImageUtils.sRGBToLinear(): Unsupported image type. No color space conversion applied.' ); - return image; + } - } + /** + * Sets the vector components to the same value. + * + * @param {number} scalar - The value to set for all vector components. + * @return {Vector3} A reference to this vector. + */ + setScalar( scalar ) { - } + this.x = scalar; + this.y = scalar; + this.z = scalar; -} + return this; -let sourceId = 0; + } -class Source { + /** + * Sets the vector's x component to the given value + * + * @param {number} x - The value to set. + * @return {Vector3} A reference to this vector. + */ + setX( x ) { - constructor( data = null ) { + this.x = x; - this.isSource = true; + return this; - Object.defineProperty( this, 'id', { value: sourceId ++ } ); + } - this.uuid = generateUUID(); + /** + * Sets the vector's y component to the given value + * + * @param {number} y - The value to set. + * @return {Vector3} A reference to this vector. + */ + setY( y ) { - this.data = data; + this.y = y; - this.version = 0; + return this; } - set needsUpdate( value ) { + /** + * Sets the vector's z component to the given value + * + * @param {number} z - The value to set. + * @return {Vector3} A reference to this vector. + */ + setZ( z ) { - if ( value === true ) this.version ++; + this.z = z; - } + return this; - toJSON( meta ) { + } - const isRootObject = ( meta === undefined || typeof meta === 'string' ); + /** + * Allows to set a vector component with an index. + * + * @param {number} index - The component index. `0` equals to x, `1` equals to y, `2` equals to z. + * @param {number} value - The value to set. + * @return {Vector3} A reference to this vector. + */ + setComponent( index, value ) { - if ( ! isRootObject && meta.images[ this.uuid ] !== undefined ) { + switch ( index ) { - return meta.images[ this.uuid ]; + case 0: this.x = value; break; + case 1: this.y = value; break; + case 2: this.z = value; break; + default: throw new Error( 'index is out of range: ' + index ); } - const output = { - uuid: this.uuid, - url: '' - }; + return this; - const data = this.data; - - if ( data !== null ) { - - let url; - - if ( Array.isArray( data ) ) { - - // cube texture - - url = []; - - for ( let i = 0, l = data.length; i < l; i ++ ) { - - if ( data[ i ].isDataTexture ) { + } - url.push( serializeImage( data[ i ].image ) ); + /** + * Returns the value of the vector component which matches the given index. + * + * @param {number} index - The component index. `0` equals to x, `1` equals to y, `2` equals to z. + * @return {number} A vector component value. + */ + getComponent( index ) { - } else { + switch ( index ) { - url.push( serializeImage( data[ i ] ) ); + case 0: return this.x; + case 1: return this.y; + case 2: return this.z; + default: throw new Error( 'index is out of range: ' + index ); - } + } - } + } - } else { + /** + * Returns a new vector with copied values from this instance. + * + * @return {Vector3} A clone of this instance. + */ + clone() { - // texture + return new this.constructor( this.x, this.y, this.z ); - url = serializeImage( data ); + } - } + /** + * Copies the values of the given vector to this instance. + * + * @param {Vector3} v - The vector to copy. + * @return {Vector3} A reference to this vector. + */ + copy( v ) { - output.url = url; + this.x = v.x; + this.y = v.y; + this.z = v.z; - } + return this; - if ( ! isRootObject ) { + } - meta.images[ this.uuid ] = output; + /** + * Adds the given vector to this instance. + * + * @param {Vector3} v - The vector to add. + * @return {Vector3} A reference to this vector. + */ + add( v ) { - } + this.x += v.x; + this.y += v.y; + this.z += v.z; - return output; + return this; } -} - -function serializeImage( image ) { + /** + * Adds the given scalar value to all components of this instance. + * + * @param {number} s - The scalar to add. + * @return {Vector3} A reference to this vector. + */ + addScalar( s ) { - if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || - ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) || - ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) { + this.x += s; + this.y += s; + this.z += s; - // default images + return this; - return ImageUtils.getDataURL( image ); + } - } else { + /** + * Adds the given vectors and stores the result in this instance. + * + * @param {Vector3} a - The first vector. + * @param {Vector3} b - The second vector. + * @return {Vector3} A reference to this vector. + */ + addVectors( a, b ) { - if ( image.data ) { + this.x = a.x + b.x; + this.y = a.y + b.y; + this.z = a.z + b.z; - // images of DataTexture + return this; - return { - data: Array.from( image.data ), - width: image.width, - height: image.height, - type: image.data.constructor.name - }; + } - } else { + /** + * Adds the given vector scaled by the given factor to this instance. + * + * @param {Vector3|Vector4} v - The vector. + * @param {number} s - The factor that scales `v`. + * @return {Vector3} A reference to this vector. + */ + addScaledVector( v, s ) { - console.warn( 'THREE.Texture: Unable to serialize Texture.' ); - return {}; + this.x += v.x * s; + this.y += v.y * s; + this.z += v.z * s; - } + return this; } -} - -let textureId = 0; + /** + * Subtracts the given vector from this instance. + * + * @param {Vector3} v - The vector to subtract. + * @return {Vector3} A reference to this vector. + */ + sub( v ) { -class Texture extends EventDispatcher { + this.x -= v.x; + this.y -= v.y; + this.z -= v.z; - constructor( image = Texture.DEFAULT_IMAGE, mapping = Texture.DEFAULT_MAPPING, wrapS = ClampToEdgeWrapping, wrapT = ClampToEdgeWrapping, magFilter = LinearFilter, minFilter = LinearMipmapLinearFilter, format = RGBAFormat, type = UnsignedByteType, anisotropy = Texture.DEFAULT_ANISOTROPY, colorSpace = NoColorSpace ) { + return this; - super(); + } - this.isTexture = true; + /** + * Subtracts the given scalar value from all components of this instance. + * + * @param {number} s - The scalar to subtract. + * @return {Vector3} A reference to this vector. + */ + subScalar( s ) { - Object.defineProperty( this, 'id', { value: textureId ++ } ); + this.x -= s; + this.y -= s; + this.z -= s; - this.uuid = generateUUID(); + return this; - this.name = ''; + } - this.source = new Source( image ); - this.mipmaps = []; + /** + * Subtracts the given vectors and stores the result in this instance. + * + * @param {Vector3} a - The first vector. + * @param {Vector3} b - The second vector. + * @return {Vector3} A reference to this vector. + */ + subVectors( a, b ) { - this.mapping = mapping; - this.channel = 0; + this.x = a.x - b.x; + this.y = a.y - b.y; + this.z = a.z - b.z; - this.wrapS = wrapS; - this.wrapT = wrapT; + return this; - this.magFilter = magFilter; - this.minFilter = minFilter; + } - this.anisotropy = anisotropy; + /** + * Multiplies the given vector with this instance. + * + * @param {Vector3} v - The vector to multiply. + * @return {Vector3} A reference to this vector. + */ + multiply( v ) { - this.format = format; - this.internalFormat = null; - this.type = type; + this.x *= v.x; + this.y *= v.y; + this.z *= v.z; - this.offset = new Vector2( 0, 0 ); - this.repeat = new Vector2( 1, 1 ); - this.center = new Vector2( 0, 0 ); - this.rotation = 0; + return this; - this.matrixAutoUpdate = true; - this.matrix = new Matrix3(); + } - this.generateMipmaps = true; - this.premultiplyAlpha = false; - this.flipY = true; - this.unpackAlignment = 4; // valid values: 1, 2, 4, 8 (see http://www.khronos.org/opengles/sdk/docs/man/xhtml/glPixelStorei.xml) + /** + * Multiplies the given scalar value with all components of this instance. + * + * @param {number} scalar - The scalar to multiply. + * @return {Vector3} A reference to this vector. + */ + multiplyScalar( scalar ) { - if ( typeof colorSpace === 'string' ) { + this.x *= scalar; + this.y *= scalar; + this.z *= scalar; - this.colorSpace = colorSpace; + return this; - } else { // @deprecated, r152 + } - warnOnce( 'THREE.Texture: Property .encoding has been replaced by .colorSpace.' ); - this.colorSpace = colorSpace === sRGBEncoding ? SRGBColorSpace : NoColorSpace; + /** + * Multiplies the given vectors and stores the result in this instance. + * + * @param {Vector3} a - The first vector. + * @param {Vector3} b - The second vector. + * @return {Vector3} A reference to this vector. + */ + multiplyVectors( a, b ) { - } + this.x = a.x * b.x; + this.y = a.y * b.y; + this.z = a.z * b.z; + return this; - this.userData = {}; + } - this.version = 0; - this.onUpdate = null; + /** + * Applies the given Euler rotation to this vector. + * + * @param {Euler} euler - The Euler angles. + * @return {Vector3} A reference to this vector. + */ + applyEuler( euler ) { - this.isRenderTargetTexture = false; // indicates whether a texture belongs to a render target or not - this.needsPMREMUpdate = false; // indicates whether this texture should be processed by PMREMGenerator or not (only relevant for render target textures) + return this.applyQuaternion( _quaternion$4.setFromEuler( euler ) ); } - get image() { + /** + * Applies a rotation specified by an axis and an angle to this vector. + * + * @param {Vector3} axis - A normalized vector representing the rotation axis. + * @param {number} angle - The angle in radians. + * @return {Vector3} A reference to this vector. + */ + applyAxisAngle( axis, angle ) { - return this.source.data; + return this.applyQuaternion( _quaternion$4.setFromAxisAngle( axis, angle ) ); } - set image( value = null ) { - - this.source.data = value; + /** + * Multiplies this vector with the given 3x3 matrix. + * + * @param {Matrix3} m - The 3x3 matrix. + * @return {Vector3} A reference to this vector. + */ + applyMatrix3( m ) { - } + const x = this.x, y = this.y, z = this.z; + const e = m.elements; - updateMatrix() { + this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ] * z; + this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ] * z; + this.z = e[ 2 ] * x + e[ 5 ] * y + e[ 8 ] * z; - this.matrix.setUvTransform( this.offset.x, this.offset.y, this.repeat.x, this.repeat.y, this.rotation, this.center.x, this.center.y ); + return this; } - clone() { + /** + * Multiplies this vector by the given normal matrix and normalizes + * the result. + * + * @param {Matrix3} m - The normal matrix. + * @return {Vector3} A reference to this vector. + */ + applyNormalMatrix( m ) { - return new this.constructor().copy( this ); + return this.applyMatrix3( m ).normalize(); } - copy( source ) { - - this.name = source.name; - - this.source = source.source; - this.mipmaps = source.mipmaps.slice( 0 ); + /** + * Multiplies this vector (with an implicit 1 in the 4th dimension) by m, and + * divides by perspective. + * + * @param {Matrix4} m - The matrix to apply. + * @return {Vector3} A reference to this vector. + */ + applyMatrix4( m ) { - this.mapping = source.mapping; - this.channel = source.channel; + const x = this.x, y = this.y, z = this.z; + const e = m.elements; - this.wrapS = source.wrapS; - this.wrapT = source.wrapT; + const w = 1 / ( e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] ); - this.magFilter = source.magFilter; - this.minFilter = source.minFilter; + this.x = ( e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] ) * w; + this.y = ( e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] ) * w; + this.z = ( e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] ) * w; - this.anisotropy = source.anisotropy; + return this; - this.format = source.format; - this.internalFormat = source.internalFormat; - this.type = source.type; + } - this.offset.copy( source.offset ); - this.repeat.copy( source.repeat ); - this.center.copy( source.center ); - this.rotation = source.rotation; + /** + * Applies the given Quaternion to this vector. + * + * @param {Quaternion} q - The Quaternion. + * @return {Vector3} A reference to this vector. + */ + applyQuaternion( q ) { - this.matrixAutoUpdate = source.matrixAutoUpdate; - this.matrix.copy( source.matrix ); + // quaternion q is assumed to have unit length - this.generateMipmaps = source.generateMipmaps; - this.premultiplyAlpha = source.premultiplyAlpha; - this.flipY = source.flipY; - this.unpackAlignment = source.unpackAlignment; - this.colorSpace = source.colorSpace; + const vx = this.x, vy = this.y, vz = this.z; + const qx = q.x, qy = q.y, qz = q.z, qw = q.w; - this.userData = JSON.parse( JSON.stringify( source.userData ) ); + // t = 2 * cross( q.xyz, v ); + const tx = 2 * ( qy * vz - qz * vy ); + const ty = 2 * ( qz * vx - qx * vz ); + const tz = 2 * ( qx * vy - qy * vx ); - this.needsUpdate = true; + // v + q.w * t + cross( q.xyz, t ); + this.x = vx + qw * tx + qy * tz - qz * ty; + this.y = vy + qw * ty + qz * tx - qx * tz; + this.z = vz + qw * tz + qx * ty - qy * tx; return this; } - toJSON( meta ) { + /** + * Projects this vector from world space into the camera's normalized + * device coordinate (NDC) space. + * + * @param {Camera} camera - The camera. + * @return {Vector3} A reference to this vector. + */ + project( camera ) { - const isRootObject = ( meta === undefined || typeof meta === 'string' ); + return this.applyMatrix4( camera.matrixWorldInverse ).applyMatrix4( camera.projectionMatrix ); - if ( ! isRootObject && meta.textures[ this.uuid ] !== undefined ) { + } - return meta.textures[ this.uuid ]; + /** + * Unprojects this vector from the camera's normalized device coordinate (NDC) + * space into world space. + * + * @param {Camera} camera - The camera. + * @return {Vector3} A reference to this vector. + */ + unproject( camera ) { - } + return this.applyMatrix4( camera.projectionMatrixInverse ).applyMatrix4( camera.matrixWorld ); - const output = { + } - metadata: { - version: 4.6, - type: 'Texture', - generator: 'Texture.toJSON' - }, + /** + * Transforms the direction of this vector by a matrix (the upper left 3 x 3 + * subset of the given 4x4 matrix and then normalizes the result. + * + * @param {Matrix4} m - The matrix. + * @return {Vector3} A reference to this vector. + */ + transformDirection( m ) { - uuid: this.uuid, - name: this.name, + // input: THREE.Matrix4 affine matrix + // vector interpreted as a direction - image: this.source.toJSON( meta ).uuid, + const x = this.x, y = this.y, z = this.z; + const e = m.elements; - mapping: this.mapping, - channel: this.channel, + this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z; + this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z; + this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z; - repeat: [ this.repeat.x, this.repeat.y ], - offset: [ this.offset.x, this.offset.y ], - center: [ this.center.x, this.center.y ], - rotation: this.rotation, + return this.normalize(); - wrap: [ this.wrapS, this.wrapT ], + } - format: this.format, - internalFormat: this.internalFormat, - type: this.type, - colorSpace: this.colorSpace, + /** + * Divides this instance by the given vector. + * + * @param {Vector3} v - The vector to divide. + * @return {Vector3} A reference to this vector. + */ + divide( v ) { - minFilter: this.minFilter, - magFilter: this.magFilter, - anisotropy: this.anisotropy, + this.x /= v.x; + this.y /= v.y; + this.z /= v.z; - flipY: this.flipY, + return this; - generateMipmaps: this.generateMipmaps, - premultiplyAlpha: this.premultiplyAlpha, - unpackAlignment: this.unpackAlignment + } - }; + /** + * Divides this vector by the given scalar. + * + * @param {number} scalar - The scalar to divide. + * @return {Vector3} A reference to this vector. + */ + divideScalar( scalar ) { - if ( Object.keys( this.userData ).length > 0 ) output.userData = this.userData; + return this.multiplyScalar( 1 / scalar ); - if ( ! isRootObject ) { + } - meta.textures[ this.uuid ] = output; + /** + * If this vector's x, y or z value is greater than the given vector's x, y or z + * value, replace that value with the corresponding min value. + * + * @param {Vector3} v - The vector. + * @return {Vector3} A reference to this vector. + */ + min( v ) { - } + this.x = Math.min( this.x, v.x ); + this.y = Math.min( this.y, v.y ); + this.z = Math.min( this.z, v.z ); - return output; + return this; } - dispose() { + /** + * If this vector's x, y or z value is less than the given vector's x, y or z + * value, replace that value with the corresponding max value. + * + * @param {Vector3} v - The vector. + * @return {Vector3} A reference to this vector. + */ + max( v ) { - this.dispatchEvent( { type: 'dispose' } ); + this.x = Math.max( this.x, v.x ); + this.y = Math.max( this.y, v.y ); + this.z = Math.max( this.z, v.z ); + + return this; } - transformUv( uv ) { + /** + * If this vector's x, y or z value is greater than the max vector's x, y or z + * value, it is replaced by the corresponding value. + * If this vector's x, y or z value is less than the min vector's x, y or z value, + * it is replaced by the corresponding value. + * + * @param {Vector3} min - The minimum x, y and z values. + * @param {Vector3} max - The maximum x, y and z values in the desired range. + * @return {Vector3} A reference to this vector. + */ + clamp( min, max ) { - if ( this.mapping !== UVMapping ) return uv; + // assumes min < max, componentwise - uv.applyMatrix3( this.matrix ); + this.x = clamp( this.x, min.x, max.x ); + this.y = clamp( this.y, min.y, max.y ); + this.z = clamp( this.z, min.z, max.z ); - if ( uv.x < 0 || uv.x > 1 ) { + return this; - switch ( this.wrapS ) { + } - case RepeatWrapping: + /** + * If this vector's x, y or z values are greater than the max value, they are + * replaced by the max value. + * If this vector's x, y or z values are less than the min value, they are + * replaced by the min value. + * + * @param {number} minVal - The minimum value the components will be clamped to. + * @param {number} maxVal - The maximum value the components will be clamped to. + * @return {Vector3} A reference to this vector. + */ + clampScalar( minVal, maxVal ) { - uv.x = uv.x - Math.floor( uv.x ); - break; + this.x = clamp( this.x, minVal, maxVal ); + this.y = clamp( this.y, minVal, maxVal ); + this.z = clamp( this.z, minVal, maxVal ); - case ClampToEdgeWrapping: + return this; - uv.x = uv.x < 0 ? 0 : 1; - break; + } - case MirroredRepeatWrapping: + /** + * If this vector's length is greater than the max value, it is replaced by + * the max value. + * If this vector's length is less than the min value, it is replaced by the + * min value. + * + * @param {number} min - The minimum value the vector length will be clamped to. + * @param {number} max - The maximum value the vector length will be clamped to. + * @return {Vector3} A reference to this vector. + */ + clampLength( min, max ) { - if ( Math.abs( Math.floor( uv.x ) % 2 ) === 1 ) { + const length = this.length(); - uv.x = Math.ceil( uv.x ) - uv.x; + return this.divideScalar( length || 1 ).multiplyScalar( clamp( length, min, max ) ); - } else { + } - uv.x = uv.x - Math.floor( uv.x ); + /** + * The components of this vector are rounded down to the nearest integer value. + * + * @return {Vector3} A reference to this vector. + */ + floor() { - } + this.x = Math.floor( this.x ); + this.y = Math.floor( this.y ); + this.z = Math.floor( this.z ); - break; + return this; - } + } - } + /** + * The components of this vector are rounded up to the nearest integer value. + * + * @return {Vector3} A reference to this vector. + */ + ceil() { - if ( uv.y < 0 || uv.y > 1 ) { + this.x = Math.ceil( this.x ); + this.y = Math.ceil( this.y ); + this.z = Math.ceil( this.z ); - switch ( this.wrapT ) { + return this; - case RepeatWrapping: + } - uv.y = uv.y - Math.floor( uv.y ); - break; + /** + * The components of this vector are rounded to the nearest integer value + * + * @return {Vector3} A reference to this vector. + */ + round() { - case ClampToEdgeWrapping: + this.x = Math.round( this.x ); + this.y = Math.round( this.y ); + this.z = Math.round( this.z ); - uv.y = uv.y < 0 ? 0 : 1; - break; + return this; - case MirroredRepeatWrapping: + } - if ( Math.abs( Math.floor( uv.y ) % 2 ) === 1 ) { + /** + * The components of this vector are rounded towards zero (up if negative, + * down if positive) to an integer value. + * + * @return {Vector3} A reference to this vector. + */ + roundToZero() { - uv.y = Math.ceil( uv.y ) - uv.y; + this.x = Math.trunc( this.x ); + this.y = Math.trunc( this.y ); + this.z = Math.trunc( this.z ); - } else { + return this; - uv.y = uv.y - Math.floor( uv.y ); + } - } + /** + * Inverts this vector - i.e. sets x = -x, y = -y and z = -z. + * + * @return {Vector3} A reference to this vector. + */ + negate() { - break; + this.x = - this.x; + this.y = - this.y; + this.z = - this.z; - } + return this; - } + } - if ( this.flipY ) { + /** + * Calculates the dot product of the given vector with this instance. + * + * @param {Vector3} v - The vector to compute the dot product with. + * @return {number} The result of the dot product. + */ + dot( v ) { - uv.y = 1 - uv.y; + return this.x * v.x + this.y * v.y + this.z * v.z; - } + } - return uv; + // TODO lengthSquared? - } + /** + * Computes the square of the Euclidean length (straight-line length) from + * (0, 0, 0) to (x, y, z). If you are comparing the lengths of vectors, you should + * compare the length squared instead as it is slightly more efficient to calculate. + * + * @return {number} The square length of this vector. + */ + lengthSq() { - set needsUpdate( value ) { + return this.x * this.x + this.y * this.y + this.z * this.z; - if ( value === true ) { + } - this.version ++; - this.source.needsUpdate = true; + /** + * Computes the Euclidean length (straight-line length) from (0, 0, 0) to (x, y, z). + * + * @return {number} The length of this vector. + */ + length() { - } + return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z ); } - get encoding() { // @deprecated, r152 + /** + * Computes the Manhattan length of this vector. + * + * @return {number} The length of this vector. + */ + manhattanLength() { - warnOnce( 'THREE.Texture: Property .encoding has been replaced by .colorSpace.' ); - return this.colorSpace === SRGBColorSpace ? sRGBEncoding : LinearEncoding; + return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ); } - set encoding( encoding ) { // @deprecated, r152 + /** + * Converts this vector to a unit vector - that is, sets it equal to a vector + * with the same direction as this one, but with a vector length of `1`. + * + * @return {Vector3} A reference to this vector. + */ + normalize() { - warnOnce( 'THREE.Texture: Property .encoding has been replaced by .colorSpace.' ); - this.colorSpace = encoding === sRGBEncoding ? SRGBColorSpace : NoColorSpace; + return this.divideScalar( this.length() || 1 ); } -} + /** + * Sets this vector to a vector with the same direction as this one, but + * with the specified length. + * + * @param {number} length - The new length of this vector. + * @return {Vector3} A reference to this vector. + */ + setLength( length ) { -Texture.DEFAULT_IMAGE = null; -Texture.DEFAULT_MAPPING = UVMapping; -Texture.DEFAULT_ANISOTROPY = 1; + return this.normalize().multiplyScalar( length ); -class Vector4 { + } - constructor( x = 0, y = 0, z = 0, w = 1 ) { + /** + * Linearly interpolates between the given vector and this instance, where + * alpha is the percent distance along the line - alpha = 0 will be this + * vector, and alpha = 1 will be the given one. + * + * @param {Vector3} v - The vector to interpolate towards. + * @param {number} alpha - The interpolation factor, typically in the closed interval `[0, 1]`. + * @return {Vector3} A reference to this vector. + */ + lerp( v, alpha ) { - Vector4.prototype.isVector4 = true; + this.x += ( v.x - this.x ) * alpha; + this.y += ( v.y - this.y ) * alpha; + this.z += ( v.z - this.z ) * alpha; - this.x = x; - this.y = y; - this.z = z; - this.w = w; + return this; } - get width() { + /** + * Linearly interpolates between the given vectors, where alpha is the percent + * distance along the line - alpha = 0 will be first vector, and alpha = 1 will + * be the second one. The result is stored in this instance. + * + * @param {Vector3} v1 - The first vector. + * @param {Vector3} v2 - The second vector. + * @param {number} alpha - The interpolation factor, typically in the closed interval `[0, 1]`. + * @return {Vector3} A reference to this vector. + */ + lerpVectors( v1, v2, alpha ) { - return this.z; + this.x = v1.x + ( v2.x - v1.x ) * alpha; + this.y = v1.y + ( v2.y - v1.y ) * alpha; + this.z = v1.z + ( v2.z - v1.z ) * alpha; + + return this; } - set width( value ) { + /** + * Calculates the cross product of the given vector with this instance. + * + * @param {Vector3} v - The vector to compute the cross product with. + * @return {Vector3} The result of the cross product. + */ + cross( v ) { - this.z = value; + return this.crossVectors( this, v ); } - get height() { - - return this.w; + /** + * Calculates the cross product of the given vectors and stores the result + * in this instance. + * + * @param {Vector3} a - The first vector. + * @param {Vector3} b - The second vector. + * @return {Vector3} A reference to this vector. + */ + crossVectors( a, b ) { - } + const ax = a.x, ay = a.y, az = a.z; + const bx = b.x, by = b.y, bz = b.z; - set height( value ) { + this.x = ay * bz - az * by; + this.y = az * bx - ax * bz; + this.z = ax * by - ay * bx; - this.w = value; + return this; } - set( x, y, z, w ) { + /** + * Projects this vector onto the given one. + * + * @param {Vector3} v - The vector to project to. + * @return {Vector3} A reference to this vector. + */ + projectOnVector( v ) { - this.x = x; - this.y = y; - this.z = z; - this.w = w; + const denominator = v.lengthSq(); - return this; + if ( denominator === 0 ) return this.set( 0, 0, 0 ); + + const scalar = v.dot( this ) / denominator; + + return this.copy( v ).multiplyScalar( scalar ); } - setScalar( scalar ) { + /** + * Projects this vector onto a plane by subtracting this + * vector projected onto the plane's normal from this vector. + * + * @param {Vector3} planeNormal - The plane normal. + * @return {Vector3} A reference to this vector. + */ + projectOnPlane( planeNormal ) { - this.x = scalar; - this.y = scalar; - this.z = scalar; - this.w = scalar; + _vector$c.copy( this ).projectOnVector( planeNormal ); - return this; + return this.sub( _vector$c ); } - setX( x ) { - - this.x = x; + /** + * Reflects this vector off a plane orthogonal to the given normal vector. + * + * @param {Vector3} normal - The (normalized) normal vector. + * @return {Vector3} A reference to this vector. + */ + reflect( normal ) { - return this; + return this.sub( _vector$c.copy( normal ).multiplyScalar( 2 * this.dot( normal ) ) ); } + /** + * Returns the angle between the given vector and this instance in radians. + * + * @param {Vector3} v - The vector to compute the angle with. + * @return {number} The angle in radians. + */ + angleTo( v ) { - setY( y ) { + const denominator = Math.sqrt( this.lengthSq() * v.lengthSq() ); - this.y = y; + if ( denominator === 0 ) return Math.PI / 2; - return this; + const theta = this.dot( v ) / denominator; - } + // clamp, to handle numerical problems - setZ( z ) { + return Math.acos( clamp( theta, -1, 1 ) ); - this.z = z; + } - return this; + /** + * Computes the distance from the given vector to this instance. + * + * @param {Vector3} v - The vector to compute the distance to. + * @return {number} The distance. + */ + distanceTo( v ) { + + return Math.sqrt( this.distanceToSquared( v ) ); } - setW( w ) { + /** + * Computes the squared distance from the given vector to this instance. + * If you are just comparing the distance with another distance, you should compare + * the distance squared instead as it is slightly more efficient to calculate. + * + * @param {Vector3} v - The vector to compute the squared distance to. + * @return {number} The squared distance. + */ + distanceToSquared( v ) { - this.w = w; + const dx = this.x - v.x, dy = this.y - v.y, dz = this.z - v.z; - return this; + return dx * dx + dy * dy + dz * dz; } - setComponent( index, value ) { + /** + * Computes the Manhattan distance from the given vector to this instance. + * + * @param {Vector3} v - The vector to compute the Manhattan distance to. + * @return {number} The Manhattan distance. + */ + manhattanDistanceTo( v ) { - switch ( index ) { + return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y ) + Math.abs( this.z - v.z ); - case 0: this.x = value; break; - case 1: this.y = value; break; - case 2: this.z = value; break; - case 3: this.w = value; break; - default: throw new Error( 'index is out of range: ' + index ); + } - } + /** + * Sets the vector components from the given spherical coordinates. + * + * @param {Spherical} s - The spherical coordinates. + * @return {Vector3} A reference to this vector. + */ + setFromSpherical( s ) { - return this; + return this.setFromSphericalCoords( s.radius, s.phi, s.theta ); } - getComponent( index ) { + /** + * Sets the vector components from the given spherical coordinates. + * + * @param {number} radius - The radius. + * @param {number} phi - The phi angle in radians. + * @param {number} theta - The theta angle in radians. + * @return {Vector3} A reference to this vector. + */ + setFromSphericalCoords( radius, phi, theta ) { - switch ( index ) { + const sinPhiRadius = Math.sin( phi ) * radius; - case 0: return this.x; - case 1: return this.y; - case 2: return this.z; - case 3: return this.w; - default: throw new Error( 'index is out of range: ' + index ); + this.x = sinPhiRadius * Math.sin( theta ); + this.y = Math.cos( phi ) * radius; + this.z = sinPhiRadius * Math.cos( theta ); - } + return this; } - clone() { + /** + * Sets the vector components from the given cylindrical coordinates. + * + * @param {Cylindrical} c - The cylindrical coordinates. + * @return {Vector3} A reference to this vector. + */ + setFromCylindrical( c ) { - return new this.constructor( this.x, this.y, this.z, this.w ); + return this.setFromCylindricalCoords( c.radius, c.theta, c.y ); } - copy( v ) { + /** + * Sets the vector components from the given cylindrical coordinates. + * + * @param {number} radius - The radius. + * @param {number} theta - The theta angle in radians. + * @param {number} y - The y value. + * @return {Vector3} A reference to this vector. + */ + setFromCylindricalCoords( radius, theta, y ) { - this.x = v.x; - this.y = v.y; - this.z = v.z; - this.w = ( v.w !== undefined ) ? v.w : 1; + this.x = radius * Math.sin( theta ); + this.y = y; + this.z = radius * Math.cos( theta ); return this; } - add( v ) { + /** + * Sets the vector components to the position elements of the + * given transformation matrix. + * + * @param {Matrix4} m - The 4x4 matrix. + * @return {Vector3} A reference to this vector. + */ + setFromMatrixPosition( m ) { - this.x += v.x; - this.y += v.y; - this.z += v.z; - this.w += v.w; + const e = m.elements; + + this.x = e[ 12 ]; + this.y = e[ 13 ]; + this.z = e[ 14 ]; return this; } - addScalar( s ) { + /** + * Sets the vector components to the scale elements of the + * given transformation matrix. + * + * @param {Matrix4} m - The 4x4 matrix. + * @return {Vector3} A reference to this vector. + */ + setFromMatrixScale( m ) { - this.x += s; - this.y += s; - this.z += s; - this.w += s; + const sx = this.setFromMatrixColumn( m, 0 ).length(); + const sy = this.setFromMatrixColumn( m, 1 ).length(); + const sz = this.setFromMatrixColumn( m, 2 ).length(); + + this.x = sx; + this.y = sy; + this.z = sz; return this; } - addVectors( a, b ) { + /** + * Sets the vector components from the specified matrix column. + * + * @param {Matrix4} m - The 4x4 matrix. + * @param {number} index - The column index. + * @return {Vector3} A reference to this vector. + */ + setFromMatrixColumn( m, index ) { - this.x = a.x + b.x; - this.y = a.y + b.y; - this.z = a.z + b.z; - this.w = a.w + b.w; + return this.fromArray( m.elements, index * 4 ); - return this; + } + + /** + * Sets the vector components from the specified matrix column. + * + * @param {Matrix3} m - The 3x3 matrix. + * @param {number} index - The column index. + * @return {Vector3} A reference to this vector. + */ + setFromMatrix3Column( m, index ) { + + return this.fromArray( m.elements, index * 3 ); } - addScaledVector( v, s ) { + /** + * Sets the vector components from the given Euler angles. + * + * @param {Euler} e - The Euler angles to set. + * @return {Vector3} A reference to this vector. + */ + setFromEuler( e ) { - this.x += v.x * s; - this.y += v.y * s; - this.z += v.z * s; - this.w += v.w * s; + this.x = e._x; + this.y = e._y; + this.z = e._z; return this; } - sub( v ) { + /** + * Sets the vector components from the RGB components of the + * given color. + * + * @param {Color} c - The color to set. + * @return {Vector3} A reference to this vector. + */ + setFromColor( c ) { - this.x -= v.x; - this.y -= v.y; - this.z -= v.z; - this.w -= v.w; + this.x = c.r; + this.y = c.g; + this.z = c.b; return this; } - subScalar( s ) { + /** + * Returns `true` if this vector is equal with the given one. + * + * @param {Vector3} v - The vector to test for equality. + * @return {boolean} Whether this vector is equal with the given one. + */ + equals( v ) { - this.x -= s; - this.y -= s; - this.z -= s; - this.w -= s; + return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) ); + + } + + /** + * Sets this vector's x value to be `array[ offset ]`, y value to be `array[ offset + 1 ]` + * and z value to be `array[ offset + 2 ]`. + * + * @param {Array} array - An array holding the vector component values. + * @param {number} [offset=0] - The offset into the array. + * @return {Vector3} A reference to this vector. + */ + fromArray( array, offset = 0 ) { + + this.x = array[ offset ]; + this.y = array[ offset + 1 ]; + this.z = array[ offset + 2 ]; return this; } - subVectors( a, b ) { + /** + * Writes the components of this vector to the given array. If no array is provided, + * the method returns a new instance. + * + * @param {Array} [array=[]] - The target array holding the vector components. + * @param {number} [offset=0] - Index of the first element in the array. + * @return {Array} The vector components. + */ + toArray( array = [], offset = 0 ) { - this.x = a.x - b.x; - this.y = a.y - b.y; - this.z = a.z - b.z; - this.w = a.w - b.w; + array[ offset ] = this.x; + array[ offset + 1 ] = this.y; + array[ offset + 2 ] = this.z; - return this; + return array; } - multiply( v ) { + /** + * Sets the components of this vector from the given buffer attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute holding vector data. + * @param {number} index - The index into the attribute. + * @return {Vector3} A reference to this vector. + */ + fromBufferAttribute( attribute, index ) { - this.x *= v.x; - this.y *= v.y; - this.z *= v.z; - this.w *= v.w; + this.x = attribute.getX( index ); + this.y = attribute.getY( index ); + this.z = attribute.getZ( index ); return this; } - multiplyScalar( scalar ) { + /** + * Sets each component of this vector to a pseudo-random value between `0` and + * `1`, excluding `1`. + * + * @return {Vector3} A reference to this vector. + */ + random() { - this.x *= scalar; - this.y *= scalar; - this.z *= scalar; - this.w *= scalar; + this.x = Math.random(); + this.y = Math.random(); + this.z = Math.random(); return this; } - applyMatrix4( m ) { + /** + * Sets this vector to a uniformly random point on a unit sphere. + * + * @return {Vector3} A reference to this vector. + */ + randomDirection() { - const x = this.x, y = this.y, z = this.z, w = this.w; - const e = m.elements; + // https://mathworld.wolfram.com/SpherePointPicking.html - this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] * w; - this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] * w; - this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] * w; - this.w = e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] * w; + const theta = Math.random() * Math.PI * 2; + const u = Math.random() * 2 - 1; + const c = Math.sqrt( 1 - u * u ); + + this.x = c * Math.cos( theta ); + this.y = u; + this.z = c * Math.sin( theta ); return this; } - divideScalar( scalar ) { + *[ Symbol.iterator ]() { - return this.multiplyScalar( 1 / scalar ); + yield this.x; + yield this.y; + yield this.z; } - setAxisAngleFromQuaternion( q ) { +} - // http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToAngle/index.htm +const _vector$c = /*@__PURE__*/ new Vector3(); +const _quaternion$4 = /*@__PURE__*/ new Quaternion(); - // q is assumed to be normalized +/** + * Represents a 3x3 matrix. + * + * A Note on Row-Major and Column-Major Ordering: + * + * The constructor and {@link Matrix3#set} method take arguments in + * [row-major]{@link https://en.wikipedia.org/wiki/Row-_and_column-major_order#Column-major_order} + * order, while internally they are stored in the {@link Matrix3#elements} array in column-major order. + * This means that calling: + * ```js + * const m = new THREE.Matrix(); + * m.set( 11, 12, 13, + * 21, 22, 23, + * 31, 32, 33 ); + * ``` + * will result in the elements array containing: + * ```js + * m.elements = [ 11, 21, 31, + * 12, 22, 32, + * 13, 23, 33 ]; + * ``` + * and internally all calculations are performed using column-major ordering. + * However, as the actual ordering makes no difference mathematically and + * most people are used to thinking about matrices in row-major order, the + * three.js documentation shows matrices in row-major order. Just bear in + * mind that if you are reading the source code, you'll have to take the + * transpose of any matrices outlined here to make sense of the calculations. + */ +class Matrix3 { - this.w = 2 * Math.acos( q.w ); + /** + * Constructs a new 3x3 matrix. The arguments are supposed to be + * in row-major order. If no arguments are provided, the constructor + * initializes the matrix as an identity matrix. + * + * @param {number} [n11] - 1-1 matrix element. + * @param {number} [n12] - 1-2 matrix element. + * @param {number} [n13] - 1-3 matrix element. + * @param {number} [n21] - 2-1 matrix element. + * @param {number} [n22] - 2-2 matrix element. + * @param {number} [n23] - 2-3 matrix element. + * @param {number} [n31] - 3-1 matrix element. + * @param {number} [n32] - 3-2 matrix element. + * @param {number} [n33] - 3-3 matrix element. + */ + constructor( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) { - const s = Math.sqrt( 1 - q.w * q.w ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + Matrix3.prototype.isMatrix3 = true; - if ( s < 0.0001 ) { + /** + * A column-major list of matrix values. + * + * @type {Array} + */ + this.elements = [ - this.x = 1; - this.y = 0; - this.z = 0; + 1, 0, 0, + 0, 1, 0, + 0, 0, 1 - } else { + ]; - this.x = q.x / s; - this.y = q.y / s; - this.z = q.z / s; + if ( n11 !== undefined ) { - } + this.set( n11, n12, n13, n21, n22, n23, n31, n32, n33 ); - return this; + } } - setAxisAngleFromRotationMatrix( m ) { + /** + * Sets the elements of the matrix.The arguments are supposed to be + * in row-major order. + * + * @param {number} [n11] - 1-1 matrix element. + * @param {number} [n12] - 1-2 matrix element. + * @param {number} [n13] - 1-3 matrix element. + * @param {number} [n21] - 2-1 matrix element. + * @param {number} [n22] - 2-2 matrix element. + * @param {number} [n23] - 2-3 matrix element. + * @param {number} [n31] - 3-1 matrix element. + * @param {number} [n32] - 3-2 matrix element. + * @param {number} [n33] - 3-3 matrix element. + * @return {Matrix3} A reference to this matrix. + */ + set( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) { - // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToAngle/index.htm + const te = this.elements; - // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) + te[ 0 ] = n11; te[ 1 ] = n21; te[ 2 ] = n31; + te[ 3 ] = n12; te[ 4 ] = n22; te[ 5 ] = n32; + te[ 6 ] = n13; te[ 7 ] = n23; te[ 8 ] = n33; - let angle, x, y, z; // variables for result - const epsilon = 0.01, // margin to allow for rounding errors - epsilon2 = 0.1, // margin to distinguish between 0 and 180 degrees + return this; - te = m.elements, + } - m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ], - m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ], - m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ]; + /** + * Sets this matrix to the 3x3 identity matrix. + * + * @return {Matrix3} A reference to this matrix. + */ + identity() { - if ( ( Math.abs( m12 - m21 ) < epsilon ) && - ( Math.abs( m13 - m31 ) < epsilon ) && - ( Math.abs( m23 - m32 ) < epsilon ) ) { + this.set( - // singularity found - // first check for identity matrix which must have +1 for all terms - // in leading diagonal and zero in other terms + 1, 0, 0, + 0, 1, 0, + 0, 0, 1 - if ( ( Math.abs( m12 + m21 ) < epsilon2 ) && - ( Math.abs( m13 + m31 ) < epsilon2 ) && - ( Math.abs( m23 + m32 ) < epsilon2 ) && - ( Math.abs( m11 + m22 + m33 - 3 ) < epsilon2 ) ) { + ); - // this singularity is identity matrix so angle = 0 + return this; - this.set( 1, 0, 0, 0 ); + } - return this; // zero angle, arbitrary axis + /** + * Copies the values of the given matrix to this instance. + * + * @param {Matrix3} m - The matrix to copy. + * @return {Matrix3} A reference to this matrix. + */ + copy( m ) { - } + const te = this.elements; + const me = m.elements; - // otherwise this singularity is angle = 180 + te[ 0 ] = me[ 0 ]; te[ 1 ] = me[ 1 ]; te[ 2 ] = me[ 2 ]; + te[ 3 ] = me[ 3 ]; te[ 4 ] = me[ 4 ]; te[ 5 ] = me[ 5 ]; + te[ 6 ] = me[ 6 ]; te[ 7 ] = me[ 7 ]; te[ 8 ] = me[ 8 ]; - angle = Math.PI; + return this; - const xx = ( m11 + 1 ) / 2; - const yy = ( m22 + 1 ) / 2; - const zz = ( m33 + 1 ) / 2; - const xy = ( m12 + m21 ) / 4; - const xz = ( m13 + m31 ) / 4; - const yz = ( m23 + m32 ) / 4; + } - if ( ( xx > yy ) && ( xx > zz ) ) { + /** + * Extracts the basis of this matrix into the three axis vectors provided. + * + * @param {Vector3} xAxis - The basis's x axis. + * @param {Vector3} yAxis - The basis's y axis. + * @param {Vector3} zAxis - The basis's z axis. + * @return {Matrix3} A reference to this matrix. + */ + extractBasis( xAxis, yAxis, zAxis ) { - // m11 is the largest diagonal term + xAxis.setFromMatrix3Column( this, 0 ); + yAxis.setFromMatrix3Column( this, 1 ); + zAxis.setFromMatrix3Column( this, 2 ); - if ( xx < epsilon ) { + return this; - x = 0; - y = 0.707106781; - z = 0.707106781; + } - } else { + /** + * Set this matrix to the upper 3x3 matrix of the given 4x4 matrix. + * + * @param {Matrix4} m - The 4x4 matrix. + * @return {Matrix3} A reference to this matrix. + */ + setFromMatrix4( m ) { - x = Math.sqrt( xx ); - y = xy / x; - z = xz / x; + const me = m.elements; - } + this.set( - } else if ( yy > zz ) { + me[ 0 ], me[ 4 ], me[ 8 ], + me[ 1 ], me[ 5 ], me[ 9 ], + me[ 2 ], me[ 6 ], me[ 10 ] - // m22 is the largest diagonal term + ); - if ( yy < epsilon ) { + return this; - x = 0.707106781; - y = 0; - z = 0.707106781; + } - } else { + /** + * Post-multiplies this matrix by the given 3x3 matrix. + * + * @param {Matrix3} m - The matrix to multiply with. + * @return {Matrix3} A reference to this matrix. + */ + multiply( m ) { - y = Math.sqrt( yy ); - x = xy / y; - z = yz / y; + return this.multiplyMatrices( this, m ); - } + } - } else { + /** + * Pre-multiplies this matrix by the given 3x3 matrix. + * + * @param {Matrix3} m - The matrix to multiply with. + * @return {Matrix3} A reference to this matrix. + */ + premultiply( m ) { - // m33 is the largest diagonal term so base result on this + return this.multiplyMatrices( m, this ); - if ( zz < epsilon ) { + } - x = 0.707106781; - y = 0.707106781; - z = 0; + /** + * Multiples the given 3x3 matrices and stores the result + * in this matrix. + * + * @param {Matrix3} a - The first matrix. + * @param {Matrix3} b - The second matrix. + * @return {Matrix3} A reference to this matrix. + */ + multiplyMatrices( a, b ) { - } else { + const ae = a.elements; + const be = b.elements; + const te = this.elements; - z = Math.sqrt( zz ); - x = xz / z; - y = yz / z; + const a11 = ae[ 0 ], a12 = ae[ 3 ], a13 = ae[ 6 ]; + const a21 = ae[ 1 ], a22 = ae[ 4 ], a23 = ae[ 7 ]; + const a31 = ae[ 2 ], a32 = ae[ 5 ], a33 = ae[ 8 ]; - } + const b11 = be[ 0 ], b12 = be[ 3 ], b13 = be[ 6 ]; + const b21 = be[ 1 ], b22 = be[ 4 ], b23 = be[ 7 ]; + const b31 = be[ 2 ], b32 = be[ 5 ], b33 = be[ 8 ]; - } - - this.set( x, y, z, angle ); + te[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31; + te[ 3 ] = a11 * b12 + a12 * b22 + a13 * b32; + te[ 6 ] = a11 * b13 + a12 * b23 + a13 * b33; - return this; // return 180 deg rotation + te[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31; + te[ 4 ] = a21 * b12 + a22 * b22 + a23 * b32; + te[ 7 ] = a21 * b13 + a22 * b23 + a23 * b33; - } + te[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31; + te[ 5 ] = a31 * b12 + a32 * b22 + a33 * b32; + te[ 8 ] = a31 * b13 + a32 * b23 + a33 * b33; - // as we have reached here there are no singularities so we can handle normally + return this; - let s = Math.sqrt( ( m32 - m23 ) * ( m32 - m23 ) + - ( m13 - m31 ) * ( m13 - m31 ) + - ( m21 - m12 ) * ( m21 - m12 ) ); // used to normalize + } - if ( Math.abs( s ) < 0.001 ) s = 1; + /** + * Multiplies every component of the matrix by the given scalar. + * + * @param {number} s - The scalar. + * @return {Matrix3} A reference to this matrix. + */ + multiplyScalar( s ) { - // prevent divide by zero, should not happen if matrix is orthogonal and should be - // caught by singularity test above, but I've left it in just in case + const te = this.elements; - this.x = ( m32 - m23 ) / s; - this.y = ( m13 - m31 ) / s; - this.z = ( m21 - m12 ) / s; - this.w = Math.acos( ( m11 + m22 + m33 - 1 ) / 2 ); + te[ 0 ] *= s; te[ 3 ] *= s; te[ 6 ] *= s; + te[ 1 ] *= s; te[ 4 ] *= s; te[ 7 ] *= s; + te[ 2 ] *= s; te[ 5 ] *= s; te[ 8 ] *= s; return this; } - min( v ) { + /** + * Computes and returns the determinant of this matrix. + * + * @return {number} The determinant. + */ + determinant() { - this.x = Math.min( this.x, v.x ); - this.y = Math.min( this.y, v.y ); - this.z = Math.min( this.z, v.z ); - this.w = Math.min( this.w, v.w ); + const te = this.elements; - return this; + const a = te[ 0 ], b = te[ 1 ], c = te[ 2 ], + d = te[ 3 ], e = te[ 4 ], f = te[ 5 ], + g = te[ 6 ], h = te[ 7 ], i = te[ 8 ]; + + return a * e * i - a * f * h - b * d * i + b * f * g + c * d * h - c * e * g; } - max( v ) { + /** + * Inverts this matrix, using the [analytic method]{@link https://en.wikipedia.org/wiki/Invertible_matrix#Analytic_solution}. + * You can not invert with a determinant of zero. If you attempt this, the method produces + * a zero matrix instead. + * + * @return {Matrix3} A reference to this matrix. + */ + invert() { - this.x = Math.max( this.x, v.x ); - this.y = Math.max( this.y, v.y ); - this.z = Math.max( this.z, v.z ); - this.w = Math.max( this.w, v.w ); + const te = this.elements, - return this; + n11 = te[ 0 ], n21 = te[ 1 ], n31 = te[ 2 ], + n12 = te[ 3 ], n22 = te[ 4 ], n32 = te[ 5 ], + n13 = te[ 6 ], n23 = te[ 7 ], n33 = te[ 8 ], - } + t11 = n33 * n22 - n32 * n23, + t12 = n32 * n13 - n33 * n12, + t13 = n23 * n12 - n22 * n13, - clamp( min, max ) { + det = n11 * t11 + n21 * t12 + n31 * t13; - // assumes min < max, componentwise + if ( det === 0 ) return this.set( 0, 0, 0, 0, 0, 0, 0, 0, 0 ); + + const detInv = 1 / det; + + te[ 0 ] = t11 * detInv; + te[ 1 ] = ( n31 * n23 - n33 * n21 ) * detInv; + te[ 2 ] = ( n32 * n21 - n31 * n22 ) * detInv; + + te[ 3 ] = t12 * detInv; + te[ 4 ] = ( n33 * n11 - n31 * n13 ) * detInv; + te[ 5 ] = ( n31 * n12 - n32 * n11 ) * detInv; - this.x = Math.max( min.x, Math.min( max.x, this.x ) ); - this.y = Math.max( min.y, Math.min( max.y, this.y ) ); - this.z = Math.max( min.z, Math.min( max.z, this.z ) ); - this.w = Math.max( min.w, Math.min( max.w, this.w ) ); + te[ 6 ] = t13 * detInv; + te[ 7 ] = ( n21 * n13 - n23 * n11 ) * detInv; + te[ 8 ] = ( n22 * n11 - n21 * n12 ) * detInv; return this; } - clampScalar( minVal, maxVal ) { + /** + * Transposes this matrix in place. + * + * @return {Matrix3} A reference to this matrix. + */ + transpose() { + + let tmp; + const m = this.elements; - this.x = Math.max( minVal, Math.min( maxVal, this.x ) ); - this.y = Math.max( minVal, Math.min( maxVal, this.y ) ); - this.z = Math.max( minVal, Math.min( maxVal, this.z ) ); - this.w = Math.max( minVal, Math.min( maxVal, this.w ) ); + tmp = m[ 1 ]; m[ 1 ] = m[ 3 ]; m[ 3 ] = tmp; + tmp = m[ 2 ]; m[ 2 ] = m[ 6 ]; m[ 6 ] = tmp; + tmp = m[ 5 ]; m[ 5 ] = m[ 7 ]; m[ 7 ] = tmp; return this; } - clampLength( min, max ) { - - const length = this.length(); + /** + * Computes the normal matrix which is the inverse transpose of the upper + * left 3x3 portion of the given 4x4 matrix. + * + * @param {Matrix4} matrix4 - The 4x4 matrix. + * @return {Matrix3} A reference to this matrix. + */ + getNormalMatrix( matrix4 ) { - return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) ); + return this.setFromMatrix4( matrix4 ).invert().transpose(); } - floor() { + /** + * Transposes this matrix into the supplied array, and returns itself unchanged. + * + * @param {Array} r - An array to store the transposed matrix elements. + * @return {Matrix3} A reference to this matrix. + */ + transposeIntoArray( r ) { - this.x = Math.floor( this.x ); - this.y = Math.floor( this.y ); - this.z = Math.floor( this.z ); - this.w = Math.floor( this.w ); + const m = this.elements; + + r[ 0 ] = m[ 0 ]; + r[ 1 ] = m[ 3 ]; + r[ 2 ] = m[ 6 ]; + r[ 3 ] = m[ 1 ]; + r[ 4 ] = m[ 4 ]; + r[ 5 ] = m[ 7 ]; + r[ 6 ] = m[ 2 ]; + r[ 7 ] = m[ 5 ]; + r[ 8 ] = m[ 8 ]; return this; } - ceil() { + /** + * Sets the UV transform matrix from offset, repeat, rotation, and center. + * + * @param {number} tx - Offset x. + * @param {number} ty - Offset y. + * @param {number} sx - Repeat x. + * @param {number} sy - Repeat y. + * @param {number} rotation - Rotation, in radians. Positive values rotate counterclockwise. + * @param {number} cx - Center x of rotation. + * @param {number} cy - Center y of rotation + * @return {Matrix3} A reference to this matrix. + */ + setUvTransform( tx, ty, sx, sy, rotation, cx, cy ) { - this.x = Math.ceil( this.x ); - this.y = Math.ceil( this.y ); - this.z = Math.ceil( this.z ); - this.w = Math.ceil( this.w ); + const c = Math.cos( rotation ); + const s = Math.sin( rotation ); + + this.set( + sx * c, sx * s, - sx * ( c * cx + s * cy ) + cx + tx, + - sy * s, sy * c, - sy * ( - s * cx + c * cy ) + cy + ty, + 0, 0, 1 + ); return this; } - round() { + /** + * Scales this matrix with the given scalar values. + * + * @param {number} sx - The amount to scale in the X axis. + * @param {number} sy - The amount to scale in the Y axis. + * @return {Matrix3} A reference to this matrix. + */ + scale( sx, sy ) { - this.x = Math.round( this.x ); - this.y = Math.round( this.y ); - this.z = Math.round( this.z ); - this.w = Math.round( this.w ); + this.premultiply( _m3.makeScale( sx, sy ) ); return this; } - roundToZero() { + /** + * Rotates this matrix by the given angle. + * + * @param {number} theta - The rotation in radians. + * @return {Matrix3} A reference to this matrix. + */ + rotate( theta ) { - this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x ); - this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y ); - this.z = ( this.z < 0 ) ? Math.ceil( this.z ) : Math.floor( this.z ); - this.w = ( this.w < 0 ) ? Math.ceil( this.w ) : Math.floor( this.w ); + this.premultiply( _m3.makeRotation( - theta ) ); return this; } - negate() { + /** + * Translates this matrix by the given scalar values. + * + * @param {number} tx - The amount to translate in the X axis. + * @param {number} ty - The amount to translate in the Y axis. + * @return {Matrix3} A reference to this matrix. + */ + translate( tx, ty ) { - this.x = - this.x; - this.y = - this.y; - this.z = - this.z; - this.w = - this.w; + this.premultiply( _m3.makeTranslation( tx, ty ) ); return this; } - dot( v ) { + // for 2D Transforms - return this.x * v.x + this.y * v.y + this.z * v.z + this.w * v.w; + /** + * Sets this matrix as a 2D translation transform. + * + * @param {number|Vector2} x - The amount to translate in the X axis or alternatively a translation vector. + * @param {number} y - The amount to translate in the Y axis. + * @return {Matrix3} A reference to this matrix. + */ + makeTranslation( x, y ) { - } + if ( x.isVector2 ) { - lengthSq() { + this.set( - return this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w; + 1, 0, x.x, + 0, 1, x.y, + 0, 0, 1 - } + ); - length() { + } else { - return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w ); + this.set( - } + 1, 0, x, + 0, 1, y, + 0, 0, 1 - manhattanLength() { + ); - return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ) + Math.abs( this.w ); + } + + return this; } - normalize() { + /** + * Sets this matrix as a 2D rotational transformation. + * + * @param {number} theta - The rotation in radians. + * @return {Matrix3} A reference to this matrix. + */ + makeRotation( theta ) { - return this.divideScalar( this.length() || 1 ); + // counterclockwise - } + const c = Math.cos( theta ); + const s = Math.sin( theta ); - setLength( length ) { + this.set( - return this.normalize().multiplyScalar( length ); + c, - s, 0, + s, c, 0, + 0, 0, 1 + + ); + + return this; } - lerp( v, alpha ) { + /** + * Sets this matrix as a 2D scale transform. + * + * @param {number} x - The amount to scale in the X axis. + * @param {number} y - The amount to scale in the Y axis. + * @return {Matrix3} A reference to this matrix. + */ + makeScale( x, y ) { - this.x += ( v.x - this.x ) * alpha; - this.y += ( v.y - this.y ) * alpha; - this.z += ( v.z - this.z ) * alpha; - this.w += ( v.w - this.w ) * alpha; + this.set( + + x, 0, 0, + 0, y, 0, + 0, 0, 1 + + ); return this; } - lerpVectors( v1, v2, alpha ) { + /** + * Returns `true` if this matrix is equal with the given one. + * + * @param {Matrix3} matrix - The matrix to test for equality. + * @return {boolean} Whether this matrix is equal with the given one. + */ + equals( matrix ) { - this.x = v1.x + ( v2.x - v1.x ) * alpha; - this.y = v1.y + ( v2.y - v1.y ) * alpha; - this.z = v1.z + ( v2.z - v1.z ) * alpha; - this.w = v1.w + ( v2.w - v1.w ) * alpha; + const te = this.elements; + const me = matrix.elements; - return this; + for ( let i = 0; i < 9; i ++ ) { - } + if ( te[ i ] !== me[ i ] ) return false; - equals( v ) { + } - return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) && ( v.w === this.w ) ); + return true; } + /** + * Sets the elements of the matrix from the given array. + * + * @param {Array} array - The matrix elements in column-major order. + * @param {number} [offset=0] - Index of the first element in the array. + * @return {Matrix3} A reference to this matrix. + */ fromArray( array, offset = 0 ) { - this.x = array[ offset ]; - this.y = array[ offset + 1 ]; - this.z = array[ offset + 2 ]; - this.w = array[ offset + 3 ]; + for ( let i = 0; i < 9; i ++ ) { + + this.elements[ i ] = array[ i + offset ]; + + } return this; } + /** + * Writes the elements of this matrix to the given array. If no array is provided, + * the method returns a new instance. + * + * @param {Array} [array=[]] - The target array holding the matrix elements in column-major order. + * @param {number} [offset=0] - Index of the first element in the array. + * @return {Array} The matrix elements in column-major order. + */ toArray( array = [], offset = 0 ) { - array[ offset ] = this.x; - array[ offset + 1 ] = this.y; - array[ offset + 2 ] = this.z; - array[ offset + 3 ] = this.w; + const te = this.elements; + + array[ offset ] = te[ 0 ]; + array[ offset + 1 ] = te[ 1 ]; + array[ offset + 2 ] = te[ 2 ]; + + array[ offset + 3 ] = te[ 3 ]; + array[ offset + 4 ] = te[ 4 ]; + array[ offset + 5 ] = te[ 5 ]; + + array[ offset + 6 ] = te[ 6 ]; + array[ offset + 7 ] = te[ 7 ]; + array[ offset + 8 ] = te[ 8 ]; return array; } - fromBufferAttribute( attribute, index ) { - - this.x = attribute.getX( index ); - this.y = attribute.getY( index ); - this.z = attribute.getZ( index ); - this.w = attribute.getW( index ); + /** + * Returns a matrix with copied values from this instance. + * + * @return {Matrix3} A clone of this instance. + */ + clone() { - return this; + return new this.constructor().fromArray( this.elements ); } - random() { +} - this.x = Math.random(); - this.y = Math.random(); - this.z = Math.random(); - this.w = Math.random(); +const _m3 = /*@__PURE__*/ new Matrix3(); - return this; +function arrayNeedsUint32( array ) { - } + // assumes larger values usually on last - *[ Symbol.iterator ]() { + for ( let i = array.length - 1; i >= 0; -- i ) { - yield this.x; - yield this.y; - yield this.z; - yield this.w; + if ( array[ i ] >= 65535 ) return true; // account for PRIMITIVE_RESTART_FIXED_INDEX, #24565 } -} + return false; -/* - In options, we can specify: - * Texture parameters for an auto-generated target texture - * depthBuffer/stencilBuffer: Booleans to indicate if we should generate these buffers -*/ -class WebGLRenderTarget extends EventDispatcher { +} - constructor( width = 1, height = 1, options = {} ) { +const TYPED_ARRAYS = { + Int8Array: Int8Array, + Uint8Array: Uint8Array, + Uint8ClampedArray: Uint8ClampedArray, + Int16Array: Int16Array, + Uint16Array: Uint16Array, + Int32Array: Int32Array, + Uint32Array: Uint32Array, + Float32Array: Float32Array, + Float64Array: Float64Array +}; - super(); +function getTypedArray( type, buffer ) { - this.isWebGLRenderTarget = true; + return new TYPED_ARRAYS[ type ]( buffer ); - this.width = width; - this.height = height; - this.depth = 1; +} - this.scissor = new Vector4( 0, 0, width, height ); - this.scissorTest = false; +function createElementNS( name ) { - this.viewport = new Vector4( 0, 0, width, height ); + return document.createElementNS( 'http://www.w3.org/1999/xhtml', name ); - const image = { width: width, height: height, depth: 1 }; +} - if ( options.encoding !== undefined ) { +function createCanvasElement() { - // @deprecated, r152 - warnOnce( 'THREE.WebGLRenderTarget: option.encoding has been replaced by option.colorSpace.' ); - options.colorSpace = options.encoding === sRGBEncoding ? SRGBColorSpace : NoColorSpace; + const canvas = createElementNS( 'canvas' ); + canvas.style.display = 'block'; + return canvas; - } +} - this.texture = new Texture( image, options.mapping, options.wrapS, options.wrapT, options.magFilter, options.minFilter, options.format, options.type, options.anisotropy, options.colorSpace ); - this.texture.isRenderTargetTexture = true; +const _cache = {}; - this.texture.flipY = false; - this.texture.generateMipmaps = options.generateMipmaps !== undefined ? options.generateMipmaps : false; - this.texture.internalFormat = options.internalFormat !== undefined ? options.internalFormat : null; - this.texture.minFilter = options.minFilter !== undefined ? options.minFilter : LinearFilter; +function warnOnce( message ) { - this.depthBuffer = options.depthBuffer !== undefined ? options.depthBuffer : true; - this.stencilBuffer = options.stencilBuffer !== undefined ? options.stencilBuffer : false; + if ( message in _cache ) return; - this.depthTexture = options.depthTexture !== undefined ? options.depthTexture : null; + _cache[ message ] = true; - this.samples = options.samples !== undefined ? options.samples : 0; + console.warn( message ); - } +} - setSize( width, height, depth = 1 ) { +function probeAsync( gl, sync, interval ) { - if ( this.width !== width || this.height !== height || this.depth !== depth ) { + return new Promise( function ( resolve, reject ) { - this.width = width; - this.height = height; - this.depth = depth; + function probe() { - this.texture.image.width = width; - this.texture.image.height = height; - this.texture.image.depth = depth; + switch ( gl.clientWaitSync( sync, gl.SYNC_FLUSH_COMMANDS_BIT, 0 ) ) { - this.dispose(); + case gl.WAIT_FAILED: + reject(); + break; - } + case gl.TIMEOUT_EXPIRED: + setTimeout( probe, interval ); + break; - this.viewport.set( 0, 0, width, height ); - this.scissor.set( 0, 0, width, height ); + default: + resolve(); - } + } - clone() { + } - return new this.constructor().copy( this ); + setTimeout( probe, interval ); - } + } ); - copy( source ) { +} - this.width = source.width; - this.height = source.height; - this.depth = source.depth; +function toNormalizedProjectionMatrix( projectionMatrix ) { - this.scissor.copy( source.scissor ); - this.scissorTest = source.scissorTest; + const m = projectionMatrix.elements; - this.viewport.copy( source.viewport ); + // Convert [-1, 1] to [0, 1] projection matrix + m[ 2 ] = 0.5 * m[ 2 ] + 0.5 * m[ 3 ]; + m[ 6 ] = 0.5 * m[ 6 ] + 0.5 * m[ 7 ]; + m[ 10 ] = 0.5 * m[ 10 ] + 0.5 * m[ 11 ]; + m[ 14 ] = 0.5 * m[ 14 ] + 0.5 * m[ 15 ]; - this.texture = source.texture.clone(); - this.texture.isRenderTargetTexture = true; +} - // ensure image object is not shared, see #20328 +function toReversedProjectionMatrix( projectionMatrix ) { - const image = Object.assign( {}, source.texture.image ); - this.texture.source = new Source( image ); + const m = projectionMatrix.elements; + const isPerspectiveMatrix = m[ 11 ] === -1; - this.depthBuffer = source.depthBuffer; - this.stencilBuffer = source.stencilBuffer; + // Reverse [0, 1] projection matrix + if ( isPerspectiveMatrix ) { - if ( source.depthTexture !== null ) this.depthTexture = source.depthTexture.clone(); + m[ 10 ] = - m[ 10 ] - 1; + m[ 14 ] = - m[ 14 ]; - this.samples = source.samples; + } else { - return this; + m[ 10 ] = - m[ 10 ]; + m[ 14 ] = - m[ 14 ] + 1; } - dispose() { +} - this.dispatchEvent( { type: 'dispose' } ); +const LINEAR_REC709_TO_XYZ = /*@__PURE__*/ new Matrix3().set( + 0.4123908, 0.3575843, 0.1804808, + 0.2126390, 0.7151687, 0.0721923, + 0.0193308, 0.1191948, 0.9505322 +); - } +const XYZ_TO_LINEAR_REC709 = /*@__PURE__*/ new Matrix3().set( + 3.2409699, -1.5373832, -0.4986108, + -0.9692436, 1.8759675, 0.0415551, + 0.0556301, -0.203977, 1.0569715 +); -} +function createColorManagement() { -class DataArrayTexture extends Texture { + const ColorManagement = { - constructor( data = null, width = 1, height = 1, depth = 1 ) { + enabled: true, - super( null ); + workingColorSpace: LinearSRGBColorSpace, - this.isDataArrayTexture = true; + /** + * Implementations of supported color spaces. + * + * Required: + * - primaries: chromaticity coordinates [ rx ry gx gy bx by ] + * - whitePoint: reference white [ x y ] + * - transfer: transfer function (pre-defined) + * - toXYZ: Matrix3 RGB to XYZ transform + * - fromXYZ: Matrix3 XYZ to RGB transform + * - luminanceCoefficients: RGB luminance coefficients + * + * Optional: + * - outputColorSpaceConfig: { drawingBufferColorSpace: ColorSpace } + * - workingColorSpaceConfig: { unpackColorSpace: ColorSpace } + * + * Reference: + * - https://www.russellcottrell.com/photo/matrixCalculator.htm + */ + spaces: {}, - this.image = { data, width, height, depth }; + convert: function ( color, sourceColorSpace, targetColorSpace ) { - this.magFilter = NearestFilter; - this.minFilter = NearestFilter; + if ( this.enabled === false || sourceColorSpace === targetColorSpace || ! sourceColorSpace || ! targetColorSpace ) { - this.wrapR = ClampToEdgeWrapping; + return color; - this.generateMipmaps = false; - this.flipY = false; - this.unpackAlignment = 1; + } - } + if ( this.spaces[ sourceColorSpace ].transfer === SRGBTransfer ) { -} + color.r = SRGBToLinear( color.r ); + color.g = SRGBToLinear( color.g ); + color.b = SRGBToLinear( color.b ); -class WebGLArrayRenderTarget extends WebGLRenderTarget { + } - constructor( width = 1, height = 1, depth = 1 ) { + if ( this.spaces[ sourceColorSpace ].primaries !== this.spaces[ targetColorSpace ].primaries ) { - super( width, height ); + color.applyMatrix3( this.spaces[ sourceColorSpace ].toXYZ ); + color.applyMatrix3( this.spaces[ targetColorSpace ].fromXYZ ); - this.isWebGLArrayRenderTarget = true; + } - this.depth = depth; + if ( this.spaces[ targetColorSpace ].transfer === SRGBTransfer ) { - this.texture = new DataArrayTexture( null, width, height, depth ); + color.r = LinearToSRGB( color.r ); + color.g = LinearToSRGB( color.g ); + color.b = LinearToSRGB( color.b ); - this.texture.isRenderTargetTexture = true; + } - } + return color; -} + }, -class Data3DTexture extends Texture { + workingToColorSpace: function ( color, targetColorSpace ) { - constructor( data = null, width = 1, height = 1, depth = 1 ) { + return this.convert( color, this.workingColorSpace, targetColorSpace ); - // We're going to add .setXXX() methods for setting properties later. - // Users can still set in DataTexture3D directly. - // - // const texture = new THREE.DataTexture3D( data, width, height, depth ); - // texture.anisotropy = 16; - // - // See #14839 + }, - super( null ); + colorSpaceToWorking: function ( color, sourceColorSpace ) { - this.isData3DTexture = true; + return this.convert( color, sourceColorSpace, this.workingColorSpace ); - this.image = { data, width, height, depth }; + }, - this.magFilter = NearestFilter; - this.minFilter = NearestFilter; + getPrimaries: function ( colorSpace ) { - this.wrapR = ClampToEdgeWrapping; + return this.spaces[ colorSpace ].primaries; - this.generateMipmaps = false; - this.flipY = false; - this.unpackAlignment = 1; + }, - } + getTransfer: function ( colorSpace ) { -} + if ( colorSpace === NoColorSpace ) return LinearTransfer; -class WebGL3DRenderTarget extends WebGLRenderTarget { + return this.spaces[ colorSpace ].transfer; - constructor( width = 1, height = 1, depth = 1 ) { + }, - super( width, height ); + getLuminanceCoefficients: function ( target, colorSpace = this.workingColorSpace ) { - this.isWebGL3DRenderTarget = true; + return target.fromArray( this.spaces[ colorSpace ].luminanceCoefficients ); - this.depth = depth; + }, - this.texture = new Data3DTexture( null, width, height, depth ); + define: function ( colorSpaces ) { - this.texture.isRenderTargetTexture = true; + Object.assign( this.spaces, colorSpaces ); - } + }, -} + // Internal APIs -class WebGLMultipleRenderTargets extends WebGLRenderTarget { + _getMatrix: function ( targetMatrix, sourceColorSpace, targetColorSpace ) { - constructor( width = 1, height = 1, count = 1, options = {} ) { + return targetMatrix + .copy( this.spaces[ sourceColorSpace ].toXYZ ) + .multiply( this.spaces[ targetColorSpace ].fromXYZ ); - super( width, height, options ); + }, - this.isWebGLMultipleRenderTargets = true; + _getDrawingBufferColorSpace: function ( colorSpace ) { - const texture = this.texture; + return this.spaces[ colorSpace ].outputColorSpaceConfig.drawingBufferColorSpace; - this.texture = []; + }, - for ( let i = 0; i < count; i ++ ) { + _getUnpackColorSpace: function ( colorSpace = this.workingColorSpace ) { - this.texture[ i ] = texture.clone(); - this.texture[ i ].isRenderTargetTexture = true; + return this.spaces[ colorSpace ].workingColorSpaceConfig.unpackColorSpace; - } + }, - } + // Deprecated - setSize( width, height, depth = 1 ) { + fromWorkingColorSpace: function ( color, targetColorSpace ) { - if ( this.width !== width || this.height !== height || this.depth !== depth ) { + warnOnce( 'THREE.ColorManagement: .fromWorkingColorSpace() has been renamed to .workingToColorSpace().' ); // @deprecated, r177 - this.width = width; - this.height = height; - this.depth = depth; + return ColorManagement.workingToColorSpace( color, targetColorSpace ); - for ( let i = 0, il = this.texture.length; i < il; i ++ ) { + }, - this.texture[ i ].image.width = width; - this.texture[ i ].image.height = height; - this.texture[ i ].image.depth = depth; + toWorkingColorSpace: function ( color, sourceColorSpace ) { - } + warnOnce( 'THREE.ColorManagement: .toWorkingColorSpace() has been renamed to .colorSpaceToWorking().' ); // @deprecated, r177 - this.dispose(); + return ColorManagement.colorSpaceToWorking( color, sourceColorSpace ); - } + }, - this.viewport.set( 0, 0, width, height ); - this.scissor.set( 0, 0, width, height ); + }; - return this; + /****************************************************************************** + * sRGB definitions + */ - } + const REC709_PRIMARIES = [ 0.640, 0.330, 0.300, 0.600, 0.150, 0.060 ]; + const REC709_LUMINANCE_COEFFICIENTS = [ 0.2126, 0.7152, 0.0722 ]; + const D65 = [ 0.3127, 0.3290 ]; + + ColorManagement.define( { + + [ LinearSRGBColorSpace ]: { + primaries: REC709_PRIMARIES, + whitePoint: D65, + transfer: LinearTransfer, + toXYZ: LINEAR_REC709_TO_XYZ, + fromXYZ: XYZ_TO_LINEAR_REC709, + luminanceCoefficients: REC709_LUMINANCE_COEFFICIENTS, + workingColorSpaceConfig: { unpackColorSpace: SRGBColorSpace }, + outputColorSpaceConfig: { drawingBufferColorSpace: SRGBColorSpace } + }, - copy( source ) { + [ SRGBColorSpace ]: { + primaries: REC709_PRIMARIES, + whitePoint: D65, + transfer: SRGBTransfer, + toXYZ: LINEAR_REC709_TO_XYZ, + fromXYZ: XYZ_TO_LINEAR_REC709, + luminanceCoefficients: REC709_LUMINANCE_COEFFICIENTS, + outputColorSpaceConfig: { drawingBufferColorSpace: SRGBColorSpace } + }, - this.dispose(); + } ); - this.width = source.width; - this.height = source.height; - this.depth = source.depth; + return ColorManagement; - this.scissor.copy( source.scissor ); - this.scissorTest = source.scissorTest; +} - this.viewport.copy( source.viewport ); +const ColorManagement = /*@__PURE__*/ createColorManagement(); - this.depthBuffer = source.depthBuffer; - this.stencilBuffer = source.stencilBuffer; +function SRGBToLinear( c ) { - if ( source.depthTexture !== null ) this.depthTexture = source.depthTexture.clone(); + return ( c < 0.04045 ) ? c * 0.0773993808 : Math.pow( c * 0.9478672986 + 0.0521327014, 2.4 ); - this.texture.length = 0; +} - for ( let i = 0, il = source.texture.length; i < il; i ++ ) { +function LinearToSRGB( c ) { - this.texture[ i ] = source.texture[ i ].clone(); - this.texture[ i ].isRenderTargetTexture = true; + return ( c < 0.0031308 ) ? c * 12.92 : 1.055 * ( Math.pow( c, 0.41666 ) ) - 0.055; - } +} - return this; +let _canvas; - } +/** + * A class containing utility functions for images. + * + * @hideconstructor + */ +class ImageUtils { -} + /** + * Returns a data URI containing a representation of the given image. + * + * @param {(HTMLImageElement|HTMLCanvasElement)} image - The image object. + * @param {string} [type='image/png'] - Indicates the image format. + * @return {string} The data URI. + */ + static getDataURL( image, type = 'image/png' ) { -class Quaternion { + if ( /^data:/i.test( image.src ) ) { - constructor( x = 0, y = 0, z = 0, w = 1 ) { + return image.src; - this.isQuaternion = true; + } - this._x = x; - this._y = y; - this._z = z; - this._w = w; - - } - - static slerpFlat( dst, dstOffset, src0, srcOffset0, src1, srcOffset1, t ) { - - // fuzz-free, array-based Quaternion SLERP operation - - let x0 = src0[ srcOffset0 + 0 ], - y0 = src0[ srcOffset0 + 1 ], - z0 = src0[ srcOffset0 + 2 ], - w0 = src0[ srcOffset0 + 3 ]; - - const x1 = src1[ srcOffset1 + 0 ], - y1 = src1[ srcOffset1 + 1 ], - z1 = src1[ srcOffset1 + 2 ], - w1 = src1[ srcOffset1 + 3 ]; - - if ( t === 0 ) { - - dst[ dstOffset + 0 ] = x0; - dst[ dstOffset + 1 ] = y0; - dst[ dstOffset + 2 ] = z0; - dst[ dstOffset + 3 ] = w0; - return; - - } - - if ( t === 1 ) { + if ( typeof HTMLCanvasElement === 'undefined' ) { - dst[ dstOffset + 0 ] = x1; - dst[ dstOffset + 1 ] = y1; - dst[ dstOffset + 2 ] = z1; - dst[ dstOffset + 3 ] = w1; - return; + return image.src; } - if ( w0 !== w1 || x0 !== x1 || y0 !== y1 || z0 !== z1 ) { + let canvas; - let s = 1 - t; - const cos = x0 * x1 + y0 * y1 + z0 * z1 + w0 * w1, - dir = ( cos >= 0 ? 1 : - 1 ), - sqrSin = 1 - cos * cos; + if ( image instanceof HTMLCanvasElement ) { - // Skip the Slerp for tiny steps to avoid numeric problems: - if ( sqrSin > Number.EPSILON ) { + canvas = image; - const sin = Math.sqrt( sqrSin ), - len = Math.atan2( sin, cos * dir ); + } else { - s = Math.sin( s * len ) / sin; - t = Math.sin( t * len ) / sin; + if ( _canvas === undefined ) _canvas = createElementNS( 'canvas' ); - } + _canvas.width = image.width; + _canvas.height = image.height; - const tDir = t * dir; + const context = _canvas.getContext( '2d' ); - x0 = x0 * s + x1 * tDir; - y0 = y0 * s + y1 * tDir; - z0 = z0 * s + z1 * tDir; - w0 = w0 * s + w1 * tDir; + if ( image instanceof ImageData ) { - // Normalize in case we just did a lerp: - if ( s === 1 - t ) { + context.putImageData( image, 0, 0 ); - const f = 1 / Math.sqrt( x0 * x0 + y0 * y0 + z0 * z0 + w0 * w0 ); + } else { - x0 *= f; - y0 *= f; - z0 *= f; - w0 *= f; + context.drawImage( image, 0, 0, image.width, image.height ); } + canvas = _canvas; + } - dst[ dstOffset ] = x0; - dst[ dstOffset + 1 ] = y0; - dst[ dstOffset + 2 ] = z0; - dst[ dstOffset + 3 ] = w0; + return canvas.toDataURL( type ); } - static multiplyQuaternionsFlat( dst, dstOffset, src0, srcOffset0, src1, srcOffset1 ) { - - const x0 = src0[ srcOffset0 ]; - const y0 = src0[ srcOffset0 + 1 ]; - const z0 = src0[ srcOffset0 + 2 ]; - const w0 = src0[ srcOffset0 + 3 ]; + /** + * Converts the given sRGB image data to linear color space. + * + * @param {(HTMLImageElement|HTMLCanvasElement|ImageBitmap|Object)} image - The image object. + * @return {HTMLCanvasElement|Object} The converted image. + */ + static sRGBToLinear( image ) { - const x1 = src1[ srcOffset1 ]; - const y1 = src1[ srcOffset1 + 1 ]; - const z1 = src1[ srcOffset1 + 2 ]; - const w1 = src1[ srcOffset1 + 3 ]; + if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || + ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) || + ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) { - dst[ dstOffset ] = x0 * w1 + w0 * x1 + y0 * z1 - z0 * y1; - dst[ dstOffset + 1 ] = y0 * w1 + w0 * y1 + z0 * x1 - x0 * z1; - dst[ dstOffset + 2 ] = z0 * w1 + w0 * z1 + x0 * y1 - y0 * x1; - dst[ dstOffset + 3 ] = w0 * w1 - x0 * x1 - y0 * y1 - z0 * z1; + const canvas = createElementNS( 'canvas' ); - return dst; + canvas.width = image.width; + canvas.height = image.height; - } + const context = canvas.getContext( '2d' ); + context.drawImage( image, 0, 0, image.width, image.height ); - get x() { + const imageData = context.getImageData( 0, 0, image.width, image.height ); + const data = imageData.data; - return this._x; + for ( let i = 0; i < data.length; i ++ ) { - } + data[ i ] = SRGBToLinear( data[ i ] / 255 ) * 255; - set x( value ) { + } - this._x = value; - this._onChangeCallback(); + context.putImageData( imageData, 0, 0 ); - } + return canvas; - get y() { + } else if ( image.data ) { - return this._y; + const data = image.data.slice( 0 ); - } + for ( let i = 0; i < data.length; i ++ ) { - set y( value ) { + if ( data instanceof Uint8Array || data instanceof Uint8ClampedArray ) { - this._y = value; - this._onChangeCallback(); + data[ i ] = Math.floor( SRGBToLinear( data[ i ] / 255 ) * 255 ); - } + } else { - get z() { + // assuming float - return this._z; + data[ i ] = SRGBToLinear( data[ i ] ); - } + } - set z( value ) { + } - this._z = value; - this._onChangeCallback(); + return { + data: data, + width: image.width, + height: image.height + }; - } + } else { - get w() { + console.warn( 'THREE.ImageUtils.sRGBToLinear(): Unsupported image type. No color space conversion applied.' ); + return image; - return this._w; + } } - set w( value ) { +} - this._w = value; - this._onChangeCallback(); +let _sourceId = 0; - } +/** + * Represents the data source of a texture. + * + * The main purpose of this class is to decouple the data definition from the texture + * definition so the same data can be used with multiple texture instances. + */ +class Source { - set( x, y, z, w ) { + /** + * Constructs a new video texture. + * + * @param {any} [data=null] - The data definition of a texture. + */ + constructor( data = null ) { - this._x = x; - this._y = y; - this._z = z; - this._w = w; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSource = true; - this._onChangeCallback(); + /** + * The ID of the source. + * + * @name Source#id + * @type {number} + * @readonly + */ + Object.defineProperty( this, 'id', { value: _sourceId ++ } ); - return this; + /** + * The UUID of the source. + * + * @type {string} + * @readonly + */ + this.uuid = generateUUID(); - } + /** + * The data definition of a texture. + * + * @type {any} + */ + this.data = data; - clone() { + /** + * This property is only relevant when {@link Source#needsUpdate} is set to `true` and + * provides more control on how texture data should be processed. When `dataReady` is set + * to `false`, the engine performs the memory allocation (if necessary) but does not transfer + * the data into the GPU memory. + * + * @type {boolean} + * @default true + */ + this.dataReady = true; - return new this.constructor( this._x, this._y, this._z, this._w ); + /** + * This starts at `0` and counts how many times {@link Source#needsUpdate} is set to `true`. + * + * @type {number} + * @readonly + * @default 0 + */ + this.version = 0; } - copy( quaternion ) { - - this._x = quaternion.x; - this._y = quaternion.y; - this._z = quaternion.z; - this._w = quaternion.w; + getSize( target ) { - this._onChangeCallback(); + const data = this.data; - return this; + if ( data instanceof HTMLVideoElement ) { - } + target.set( data.videoWidth, data.videoHeight ); - setFromEuler( euler, update ) { + } else if ( data !== null ) { - const x = euler._x, y = euler._y, z = euler._z, order = euler._order; + target.set( data.width, data.height, data.depth || 0 ); - // http://www.mathworks.com/matlabcentral/fileexchange/ - // 20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/ - // content/SpinCalc.m + } else { - const cos = Math.cos; - const sin = Math.sin; + target.set( 0, 0, 0 ); - const c1 = cos( x / 2 ); - const c2 = cos( y / 2 ); - const c3 = cos( z / 2 ); + } - const s1 = sin( x / 2 ); - const s2 = sin( y / 2 ); - const s3 = sin( z / 2 ); + return target; - switch ( order ) { + } - case 'XYZ': - this._x = s1 * c2 * c3 + c1 * s2 * s3; - this._y = c1 * s2 * c3 - s1 * c2 * s3; - this._z = c1 * c2 * s3 + s1 * s2 * c3; - this._w = c1 * c2 * c3 - s1 * s2 * s3; - break; + /** + * When the property is set to `true`, the engine allocates the memory + * for the texture (if necessary) and triggers the actual texture upload + * to the GPU next time the source is used. + * + * @type {boolean} + * @default false + * @param {boolean} value + */ + set needsUpdate( value ) { - case 'YXZ': - this._x = s1 * c2 * c3 + c1 * s2 * s3; - this._y = c1 * s2 * c3 - s1 * c2 * s3; - this._z = c1 * c2 * s3 - s1 * s2 * c3; - this._w = c1 * c2 * c3 + s1 * s2 * s3; - break; + if ( value === true ) this.version ++; - case 'ZXY': - this._x = s1 * c2 * c3 - c1 * s2 * s3; - this._y = c1 * s2 * c3 + s1 * c2 * s3; - this._z = c1 * c2 * s3 + s1 * s2 * c3; - this._w = c1 * c2 * c3 - s1 * s2 * s3; - break; + } - case 'ZYX': - this._x = s1 * c2 * c3 - c1 * s2 * s3; - this._y = c1 * s2 * c3 + s1 * c2 * s3; - this._z = c1 * c2 * s3 - s1 * s2 * c3; - this._w = c1 * c2 * c3 + s1 * s2 * s3; - break; + /** + * Serializes the source into JSON. + * + * @param {?(Object|string)} meta - An optional value holding meta information about the serialization. + * @return {Object} A JSON object representing the serialized source. + * @see {@link ObjectLoader#parse} + */ + toJSON( meta ) { - case 'YZX': - this._x = s1 * c2 * c3 + c1 * s2 * s3; - this._y = c1 * s2 * c3 + s1 * c2 * s3; - this._z = c1 * c2 * s3 - s1 * s2 * c3; - this._w = c1 * c2 * c3 - s1 * s2 * s3; - break; + const isRootObject = ( meta === undefined || typeof meta === 'string' ); - case 'XZY': - this._x = s1 * c2 * c3 - c1 * s2 * s3; - this._y = c1 * s2 * c3 - s1 * c2 * s3; - this._z = c1 * c2 * s3 + s1 * s2 * c3; - this._w = c1 * c2 * c3 + s1 * s2 * s3; - break; + if ( ! isRootObject && meta.images[ this.uuid ] !== undefined ) { - default: - console.warn( 'THREE.Quaternion: .setFromEuler() encountered an unknown order: ' + order ); + return meta.images[ this.uuid ]; } - if ( update !== false ) this._onChangeCallback(); - - return this; - - } - - setFromAxisAngle( axis, angle ) { - - // http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm - - // assumes axis is normalized - - const halfAngle = angle / 2, s = Math.sin( halfAngle ); - - this._x = axis.x * s; - this._y = axis.y * s; - this._z = axis.z * s; - this._w = Math.cos( halfAngle ); + const output = { + uuid: this.uuid, + url: '' + }; - this._onChangeCallback(); + const data = this.data; - return this; + if ( data !== null ) { - } + let url; - setFromRotationMatrix( m ) { + if ( Array.isArray( data ) ) { - // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm + // cube texture - // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) + url = []; - const te = m.elements, + for ( let i = 0, l = data.length; i < l; i ++ ) { - m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ], - m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ], - m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ], + if ( data[ i ].isDataTexture ) { - trace = m11 + m22 + m33; + url.push( serializeImage( data[ i ].image ) ); - if ( trace > 0 ) { + } else { - const s = 0.5 / Math.sqrt( trace + 1.0 ); + url.push( serializeImage( data[ i ] ) ); - this._w = 0.25 / s; - this._x = ( m32 - m23 ) * s; - this._y = ( m13 - m31 ) * s; - this._z = ( m21 - m12 ) * s; + } - } else if ( m11 > m22 && m11 > m33 ) { + } - const s = 2.0 * Math.sqrt( 1.0 + m11 - m22 - m33 ); + } else { - this._w = ( m32 - m23 ) / s; - this._x = 0.25 * s; - this._y = ( m12 + m21 ) / s; - this._z = ( m13 + m31 ) / s; + // texture - } else if ( m22 > m33 ) { + url = serializeImage( data ); - const s = 2.0 * Math.sqrt( 1.0 + m22 - m11 - m33 ); + } - this._w = ( m13 - m31 ) / s; - this._x = ( m12 + m21 ) / s; - this._y = 0.25 * s; - this._z = ( m23 + m32 ) / s; + output.url = url; - } else { + } - const s = 2.0 * Math.sqrt( 1.0 + m33 - m11 - m22 ); + if ( ! isRootObject ) { - this._w = ( m21 - m12 ) / s; - this._x = ( m13 + m31 ) / s; - this._y = ( m23 + m32 ) / s; - this._z = 0.25 * s; + meta.images[ this.uuid ] = output; } - this._onChangeCallback(); - - return this; + return output; } - setFromUnitVectors( vFrom, vTo ) { - - // assumes direction vectors vFrom and vTo are normalized - - let r = vFrom.dot( vTo ) + 1; +} - if ( r < Number.EPSILON ) { +function serializeImage( image ) { - // vFrom and vTo point in opposite directions + if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || + ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) || + ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) { - r = 0; + // default images - if ( Math.abs( vFrom.x ) > Math.abs( vFrom.z ) ) { + return ImageUtils.getDataURL( image ); - this._x = - vFrom.y; - this._y = vFrom.x; - this._z = 0; - this._w = r; + } else { - } else { + if ( image.data ) { - this._x = 0; - this._y = - vFrom.z; - this._z = vFrom.y; - this._w = r; + // images of DataTexture - } + return { + data: Array.from( image.data ), + width: image.width, + height: image.height, + type: image.data.constructor.name + }; } else { - // crossVectors( vFrom, vTo ); // inlined to avoid cyclic dependency on Vector3 - - this._x = vFrom.y * vTo.z - vFrom.z * vTo.y; - this._y = vFrom.z * vTo.x - vFrom.x * vTo.z; - this._z = vFrom.x * vTo.y - vFrom.y * vTo.x; - this._w = r; + console.warn( 'THREE.Texture: Unable to serialize Texture.' ); + return {}; } - return this.normalize(); - } - angleTo( q ) { - - return 2 * Math.acos( Math.abs( clamp( this.dot( q ), - 1, 1 ) ) ); +} - } +let _textureId = 0; - rotateTowards( q, step ) { +const _tempVec3 = /*@__PURE__*/ new Vector3(); - const angle = this.angleTo( q ); +/** + * Base class for all textures. + * + * Note: After the initial use of a texture, its dimensions, format, and type + * cannot be changed. Instead, call {@link Texture#dispose} on the texture and instantiate a new one. + * + * @augments EventDispatcher + */ +class Texture extends EventDispatcher { - if ( angle === 0 ) return this; + /** + * Constructs a new texture. + * + * @param {?Object} [image=Texture.DEFAULT_IMAGE] - The image holding the texture data. + * @param {number} [mapping=Texture.DEFAULT_MAPPING] - The texture mapping. + * @param {number} [wrapS=ClampToEdgeWrapping] - The wrapS value. + * @param {number} [wrapT=ClampToEdgeWrapping] - The wrapT value. + * @param {number} [magFilter=LinearFilter] - The mag filter value. + * @param {number} [minFilter=LinearMipmapLinearFilter] - The min filter value. + * @param {number} [format=RGBAFormat] - The texture format. + * @param {number} [type=UnsignedByteType] - The texture type. + * @param {number} [anisotropy=Texture.DEFAULT_ANISOTROPY] - The anisotropy value. + * @param {string} [colorSpace=NoColorSpace] - The color space. + */ + constructor( image = Texture.DEFAULT_IMAGE, mapping = Texture.DEFAULT_MAPPING, wrapS = ClampToEdgeWrapping, wrapT = ClampToEdgeWrapping, magFilter = LinearFilter, minFilter = LinearMipmapLinearFilter, format = RGBAFormat, type = UnsignedByteType, anisotropy = Texture.DEFAULT_ANISOTROPY, colorSpace = NoColorSpace ) { - const t = Math.min( 1, step / angle ); + super(); - this.slerp( q, t ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isTexture = true; - return this; + /** + * The ID of the texture. + * + * @name Texture#id + * @type {number} + * @readonly + */ + Object.defineProperty( this, 'id', { value: _textureId ++ } ); - } + /** + * The UUID of the material. + * + * @type {string} + * @readonly + */ + this.uuid = generateUUID(); - identity() { + /** + * The name of the material. + * + * @type {string} + */ + this.name = ''; - return this.set( 0, 0, 0, 1 ); + /** + * The data definition of a texture. A reference to the data source can be + * shared across textures. This is often useful in context of spritesheets + * where multiple textures render the same data but with different texture + * transformations. + * + * @type {Source} + */ + this.source = new Source( image ); - } + /** + * An array holding user-defined mipmaps. + * + * @type {Array} + */ + this.mipmaps = []; - invert() { + /** + * How the texture is applied to the object. The value `UVMapping` + * is the default, where texture or uv coordinates are used to apply the map. + * + * @type {(UVMapping|CubeReflectionMapping|CubeRefractionMapping|EquirectangularReflectionMapping|EquirectangularRefractionMapping|CubeUVReflectionMapping)} + * @default UVMapping + */ + this.mapping = mapping; - // quaternion is assumed to have unit length + /** + * Lets you select the uv attribute to map the texture to. `0` for `uv`, + * `1` for `uv1`, `2` for `uv2` and `3` for `uv3`. + * + * @type {number} + * @default 0 + */ + this.channel = 0; - return this.conjugate(); + /** + * This defines how the texture is wrapped horizontally and corresponds to + * *U* in UV mapping. + * + * @type {(RepeatWrapping|ClampToEdgeWrapping|MirroredRepeatWrapping)} + * @default ClampToEdgeWrapping + */ + this.wrapS = wrapS; - } + /** + * This defines how the texture is wrapped horizontally and corresponds to + * *V* in UV mapping. + * + * @type {(RepeatWrapping|ClampToEdgeWrapping|MirroredRepeatWrapping)} + * @default ClampToEdgeWrapping + */ + this.wrapT = wrapT; - conjugate() { + /** + * How the texture is sampled when a texel covers more than one pixel. + * + * @type {(NearestFilter|NearestMipmapNearestFilter|NearestMipmapLinearFilter|LinearFilter|LinearMipmapNearestFilter|LinearMipmapLinearFilter)} + * @default LinearFilter + */ + this.magFilter = magFilter; - this._x *= - 1; - this._y *= - 1; - this._z *= - 1; + /** + * How the texture is sampled when a texel covers less than one pixel. + * + * @type {(NearestFilter|NearestMipmapNearestFilter|NearestMipmapLinearFilter|LinearFilter|LinearMipmapNearestFilter|LinearMipmapLinearFilter)} + * @default LinearMipmapLinearFilter + */ + this.minFilter = minFilter; - this._onChangeCallback(); + /** + * The number of samples taken along the axis through the pixel that has the + * highest density of texels. By default, this value is `1`. A higher value + * gives a less blurry result than a basic mipmap, at the cost of more + * texture samples being used. + * + * @type {number} + * @default 0 + */ + this.anisotropy = anisotropy; - return this; + /** + * The format of the texture. + * + * @type {number} + * @default RGBAFormat + */ + this.format = format; - } + /** + * The default internal format is derived from {@link Texture#format} and {@link Texture#type} and + * defines how the texture data is going to be stored on the GPU. + * + * This property allows to overwrite the default format. + * + * @type {?string} + * @default null + */ + this.internalFormat = null; - dot( v ) { + /** + * The data type of the texture. + * + * @type {number} + * @default UnsignedByteType + */ + this.type = type; - return this._x * v._x + this._y * v._y + this._z * v._z + this._w * v._w; + /** + * How much a single repetition of the texture is offset from the beginning, + * in each direction U and V. Typical range is `0.0` to `1.0`. + * + * @type {Vector2} + * @default (0,0) + */ + this.offset = new Vector2( 0, 0 ); - } + /** + * How many times the texture is repeated across the surface, in each + * direction U and V. If repeat is set greater than `1` in either direction, + * the corresponding wrap parameter should also be set to `RepeatWrapping` + * or `MirroredRepeatWrapping` to achieve the desired tiling effect. + * + * @type {Vector2} + * @default (1,1) + */ + this.repeat = new Vector2( 1, 1 ); - lengthSq() { + /** + * The point around which rotation occurs. A value of `(0.5, 0.5)` corresponds + * to the center of the texture. Default is `(0, 0)`, the lower left. + * + * @type {Vector2} + * @default (0,0) + */ + this.center = new Vector2( 0, 0 ); - return this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w; + /** + * How much the texture is rotated around the center point, in radians. + * Positive values are counter-clockwise. + * + * @type {number} + * @default 0 + */ + this.rotation = 0; - } + /** + * Whether to update the texture's uv-transformation {@link Texture#matrix} + * from the properties {@link Texture#offset}, {@link Texture#repeat}, + * {@link Texture#rotation}, and {@link Texture#center}. + * + * Set this to `false` if you are specifying the uv-transform matrix directly. + * + * @type {boolean} + * @default true + */ + this.matrixAutoUpdate = true; - length() { + /** + * The uv-transformation matrix of the texture. + * + * @type {Matrix3} + */ + this.matrix = new Matrix3(); - return Math.sqrt( this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w ); + /** + * Whether to generate mipmaps (if possible) for a texture. + * + * Set this to `false` if you are creating mipmaps manually. + * + * @type {boolean} + * @default true + */ + this.generateMipmaps = true; - } + /** + * If set to `true`, the alpha channel, if present, is multiplied into the + * color channels when the texture is uploaded to the GPU. + * + * Note that this property has no effect when using `ImageBitmap`. You need to + * configure premultiply alpha on bitmap creation instead. + * + * @type {boolean} + * @default false + */ + this.premultiplyAlpha = false; - normalize() { + /** + * If set to `true`, the texture is flipped along the vertical axis when + * uploaded to the GPU. + * + * Note that this property has no effect when using `ImageBitmap`. You need to + * configure the flip on bitmap creation instead. + * + * @type {boolean} + * @default true + */ + this.flipY = true; - let l = this.length(); + /** + * Specifies the alignment requirements for the start of each pixel row in memory. + * The allowable values are `1` (byte-alignment), `2` (rows aligned to even-numbered bytes), + * `4` (word-alignment), and `8` (rows start on double-word boundaries). + * + * @type {number} + * @default 4 + */ + this.unpackAlignment = 4; // valid values: 1, 2, 4, 8 (see http://www.khronos.org/opengles/sdk/docs/man/xhtml/glPixelStorei.xml) - if ( l === 0 ) { + /** + * Textures containing color data should be annotated with `SRGBColorSpace` or `LinearSRGBColorSpace`. + * + * @type {string} + * @default NoColorSpace + */ + this.colorSpace = colorSpace; - this._x = 0; - this._y = 0; - this._z = 0; - this._w = 1; + /** + * An object that can be used to store custom data about the texture. It + * should not hold references to functions as these will not be cloned. + * + * @type {Object} + */ + this.userData = {}; - } else { + /** + * This can be used to only update a subregion or specific rows of the texture (for example, just the + * first 3 rows). Use the `addUpdateRange()` function to add ranges to this array. + * + * @type {Array} + */ + this.updateRanges = []; - l = 1 / l; + /** + * This starts at `0` and counts how many times {@link Texture#needsUpdate} is set to `true`. + * + * @type {number} + * @readonly + * @default 0 + */ + this.version = 0; - this._x = this._x * l; - this._y = this._y * l; - this._z = this._z * l; - this._w = this._w * l; + /** + * A callback function, called when the texture is updated (e.g., when + * {@link Texture#needsUpdate} has been set to true and then the texture is used). + * + * @type {?Function} + * @default null + */ + this.onUpdate = null; - } + /** + * An optional back reference to the textures render target. + * + * @type {?(RenderTarget|WebGLRenderTarget)} + * @default null + */ + this.renderTarget = null; - this._onChangeCallback(); + /** + * Indicates whether a texture belongs to a render target or not. + * + * @type {boolean} + * @readonly + * @default false + */ + this.isRenderTargetTexture = false; - return this; + /** + * Indicates if a texture should be handled like a texture array. + * + * @type {boolean} + * @readonly + * @default false + */ + this.isArrayTexture = image && image.depth && image.depth > 1 ? true : false; + + /** + * Indicates whether this texture should be processed by `PMREMGenerator` or not + * (only relevant for render target textures). + * + * @type {number} + * @readonly + * @default 0 + */ + this.pmremVersion = 0; } - multiply( q ) { + /** + * The width of the texture in pixels. + */ + get width() { - return this.multiplyQuaternions( this, q ); + return this.source.getSize( _tempVec3 ).x; } - premultiply( q ) { + /** + * The height of the texture in pixels. + */ + get height() { - return this.multiplyQuaternions( q, this ); + return this.source.getSize( _tempVec3 ).y; } - multiplyQuaternions( a, b ) { + /** + * The depth of the texture in pixels. + */ + get depth() { - // from http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/code/index.htm + return this.source.getSize( _tempVec3 ).z; - const qax = a._x, qay = a._y, qaz = a._z, qaw = a._w; - const qbx = b._x, qby = b._y, qbz = b._z, qbw = b._w; + } - this._x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby; - this._y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz; - this._z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx; - this._w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz; + /** + * The image object holding the texture data. + * + * @type {?Object} + */ + get image() { - this._onChangeCallback(); + return this.source.data; - return this; + } + + set image( value = null ) { + + this.source.data = value; } - slerp( qb, t ) { + /** + * Updates the texture transformation matrix from the from the properties {@link Texture#offset}, + * {@link Texture#repeat}, {@link Texture#rotation}, and {@link Texture#center}. + */ + updateMatrix() { - if ( t === 0 ) return this; - if ( t === 1 ) return this.copy( qb ); + this.matrix.setUvTransform( this.offset.x, this.offset.y, this.repeat.x, this.repeat.y, this.rotation, this.center.x, this.center.y ); - const x = this._x, y = this._y, z = this._z, w = this._w; + } - // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/ + /** + * Adds a range of data in the data texture to be updated on the GPU. + * + * @param {number} start - Position at which to start update. + * @param {number} count - The number of components to update. + */ + addUpdateRange( start, count ) { - let cosHalfTheta = w * qb._w + x * qb._x + y * qb._y + z * qb._z; + this.updateRanges.push( { start, count } ); - if ( cosHalfTheta < 0 ) { + } - this._w = - qb._w; - this._x = - qb._x; - this._y = - qb._y; - this._z = - qb._z; + /** + * Clears the update ranges. + */ + clearUpdateRanges() { - cosHalfTheta = - cosHalfTheta; + this.updateRanges.length = 0; - } else { + } - this.copy( qb ); + /** + * Returns a new texture with copied values from this instance. + * + * @return {Texture} A clone of this instance. + */ + clone() { - } + return new this.constructor().copy( this ); - if ( cosHalfTheta >= 1.0 ) { + } - this._w = w; - this._x = x; - this._y = y; - this._z = z; + /** + * Copies the values of the given texture to this instance. + * + * @param {Texture} source - The texture to copy. + * @return {Texture} A reference to this instance. + */ + copy( source ) { - return this; + this.name = source.name; - } + this.source = source.source; + this.mipmaps = source.mipmaps.slice( 0 ); - const sqrSinHalfTheta = 1.0 - cosHalfTheta * cosHalfTheta; + this.mapping = source.mapping; + this.channel = source.channel; - if ( sqrSinHalfTheta <= Number.EPSILON ) { + this.wrapS = source.wrapS; + this.wrapT = source.wrapT; - const s = 1 - t; - this._w = s * w + t * this._w; - this._x = s * x + t * this._x; - this._y = s * y + t * this._y; - this._z = s * z + t * this._z; + this.magFilter = source.magFilter; + this.minFilter = source.minFilter; - this.normalize(); - this._onChangeCallback(); + this.anisotropy = source.anisotropy; - return this; + this.format = source.format; + this.internalFormat = source.internalFormat; + this.type = source.type; - } + this.offset.copy( source.offset ); + this.repeat.copy( source.repeat ); + this.center.copy( source.center ); + this.rotation = source.rotation; - const sinHalfTheta = Math.sqrt( sqrSinHalfTheta ); - const halfTheta = Math.atan2( sinHalfTheta, cosHalfTheta ); - const ratioA = Math.sin( ( 1 - t ) * halfTheta ) / sinHalfTheta, - ratioB = Math.sin( t * halfTheta ) / sinHalfTheta; + this.matrixAutoUpdate = source.matrixAutoUpdate; + this.matrix.copy( source.matrix ); - this._w = ( w * ratioA + this._w * ratioB ); - this._x = ( x * ratioA + this._x * ratioB ); - this._y = ( y * ratioA + this._y * ratioB ); - this._z = ( z * ratioA + this._z * ratioB ); + this.generateMipmaps = source.generateMipmaps; + this.premultiplyAlpha = source.premultiplyAlpha; + this.flipY = source.flipY; + this.unpackAlignment = source.unpackAlignment; + this.colorSpace = source.colorSpace; - this._onChangeCallback(); + this.renderTarget = source.renderTarget; + this.isRenderTargetTexture = source.isRenderTargetTexture; + this.isArrayTexture = source.isArrayTexture; + + this.userData = JSON.parse( JSON.stringify( source.userData ) ); + + this.needsUpdate = true; return this; } - slerpQuaternions( qa, qb, t ) { + /** + * Sets this texture's properties based on `values`. + * @param {Object} values - A container with texture parameters. + */ + setValues( values ) { - return this.copy( qa ).slerp( qb, t ); + for ( const key in values ) { + + const newValue = values[ key ]; + + if ( newValue === undefined ) { + + console.warn( `THREE.Texture.setValues(): parameter '${ key }' has value of undefined.` ); + continue; + + } + + const currentValue = this[ key ]; + + if ( currentValue === undefined ) { + + console.warn( `THREE.Texture.setValues(): property '${ key }' does not exist.` ); + continue; + + } + + if ( ( currentValue && newValue ) && ( currentValue.isVector2 && newValue.isVector2 ) ) { + + currentValue.copy( newValue ); + + } else if ( ( currentValue && newValue ) && ( currentValue.isVector3 && newValue.isVector3 ) ) { + + currentValue.copy( newValue ); + + } else if ( ( currentValue && newValue ) && ( currentValue.isMatrix3 && newValue.isMatrix3 ) ) { + + currentValue.copy( newValue ); + + } else { + + this[ key ] = newValue; + + } + + } } - random() { + /** + * Serializes the texture into JSON. + * + * @param {?(Object|string)} meta - An optional value holding meta information about the serialization. + * @return {Object} A JSON object representing the serialized texture. + * @see {@link ObjectLoader#parse} + */ + toJSON( meta ) { - // Derived from http://planning.cs.uiuc.edu/node198.html - // Note, this source uses w, x, y, z ordering, - // so we swap the order below. + const isRootObject = ( meta === undefined || typeof meta === 'string' ); - const u1 = Math.random(); - const sqrt1u1 = Math.sqrt( 1 - u1 ); - const sqrtu1 = Math.sqrt( u1 ); + if ( ! isRootObject && meta.textures[ this.uuid ] !== undefined ) { - const u2 = 2 * Math.PI * Math.random(); + return meta.textures[ this.uuid ]; - const u3 = 2 * Math.PI * Math.random(); + } - return this.set( - sqrt1u1 * Math.cos( u2 ), - sqrtu1 * Math.sin( u3 ), - sqrtu1 * Math.cos( u3 ), - sqrt1u1 * Math.sin( u2 ), - ); + const output = { + + metadata: { + version: 4.7, + type: 'Texture', + generator: 'Texture.toJSON' + }, + + uuid: this.uuid, + name: this.name, + + image: this.source.toJSON( meta ).uuid, + + mapping: this.mapping, + channel: this.channel, + + repeat: [ this.repeat.x, this.repeat.y ], + offset: [ this.offset.x, this.offset.y ], + center: [ this.center.x, this.center.y ], + rotation: this.rotation, + + wrap: [ this.wrapS, this.wrapT ], + + format: this.format, + internalFormat: this.internalFormat, + type: this.type, + colorSpace: this.colorSpace, + + minFilter: this.minFilter, + magFilter: this.magFilter, + anisotropy: this.anisotropy, + + flipY: this.flipY, + + generateMipmaps: this.generateMipmaps, + premultiplyAlpha: this.premultiplyAlpha, + unpackAlignment: this.unpackAlignment + + }; + + if ( Object.keys( this.userData ).length > 0 ) output.userData = this.userData; + + if ( ! isRootObject ) { + + meta.textures[ this.uuid ] = output; + + } + + return output; } - equals( quaternion ) { + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + * + * @fires Texture#dispose + */ + dispose() { - return ( quaternion._x === this._x ) && ( quaternion._y === this._y ) && ( quaternion._z === this._z ) && ( quaternion._w === this._w ); + /** + * Fires when the texture has been disposed of. + * + * @event Texture#dispose + * @type {Object} + */ + this.dispatchEvent( { type: 'dispose' } ); } - fromArray( array, offset = 0 ) { + /** + * Transforms the given uv vector with the textures uv transformation matrix. + * + * @param {Vector2} uv - The uv vector. + * @return {Vector2} The transformed uv vector. + */ + transformUv( uv ) { - this._x = array[ offset ]; - this._y = array[ offset + 1 ]; - this._z = array[ offset + 2 ]; - this._w = array[ offset + 3 ]; + if ( this.mapping !== UVMapping ) return uv; - this._onChangeCallback(); + uv.applyMatrix3( this.matrix ); - return this; + if ( uv.x < 0 || uv.x > 1 ) { - } + switch ( this.wrapS ) { - toArray( array = [], offset = 0 ) { + case RepeatWrapping: - array[ offset ] = this._x; - array[ offset + 1 ] = this._y; - array[ offset + 2 ] = this._z; - array[ offset + 3 ] = this._w; + uv.x = uv.x - Math.floor( uv.x ); + break; - return array; + case ClampToEdgeWrapping: - } + uv.x = uv.x < 0 ? 0 : 1; + break; - fromBufferAttribute( attribute, index ) { + case MirroredRepeatWrapping: - this._x = attribute.getX( index ); - this._y = attribute.getY( index ); - this._z = attribute.getZ( index ); - this._w = attribute.getW( index ); + if ( Math.abs( Math.floor( uv.x ) % 2 ) === 1 ) { - return this; + uv.x = Math.ceil( uv.x ) - uv.x; - } + } else { - toJSON() { + uv.x = uv.x - Math.floor( uv.x ); - return this.toArray(); + } + + break; + + } + + } + + if ( uv.y < 0 || uv.y > 1 ) { + + switch ( this.wrapT ) { + + case RepeatWrapping: + + uv.y = uv.y - Math.floor( uv.y ); + break; + + case ClampToEdgeWrapping: + + uv.y = uv.y < 0 ? 0 : 1; + break; + + case MirroredRepeatWrapping: + + if ( Math.abs( Math.floor( uv.y ) % 2 ) === 1 ) { + + uv.y = Math.ceil( uv.y ) - uv.y; + + } else { + + uv.y = uv.y - Math.floor( uv.y ); + + } + + break; + + } + + } + + if ( this.flipY ) { + + uv.y = 1 - uv.y; + + } + + return uv; } - _onChange( callback ) { + /** + * Setting this property to `true` indicates the engine the texture + * must be updated in the next render. This triggers a texture upload + * to the GPU and ensures correct texture parameter configuration. + * + * @type {boolean} + * @default false + * @param {boolean} value + */ + set needsUpdate( value ) { - this._onChangeCallback = callback; + if ( value === true ) { - return this; + this.version ++; + this.source.needsUpdate = true; + + } } - _onChangeCallback() {} + /** + * Setting this property to `true` indicates the engine the PMREM + * must be regenerated. + * + * @type {boolean} + * @default false + * @param {boolean} value + */ + set needsPMREMUpdate( value ) { - *[ Symbol.iterator ]() { + if ( value === true ) { - yield this._x; - yield this._y; - yield this._z; - yield this._w; + this.pmremVersion ++; + + } } } -class Vector3 { +/** + * The default image for all textures. + * + * @static + * @type {?Image} + * @default null + */ +Texture.DEFAULT_IMAGE = null; - constructor( x = 0, y = 0, z = 0 ) { +/** + * The default mapping for all textures. + * + * @static + * @type {number} + * @default UVMapping + */ +Texture.DEFAULT_MAPPING = UVMapping; - Vector3.prototype.isVector3 = true; +/** + * The default anisotropy value for all textures. + * + * @static + * @type {number} + * @default 1 + */ +Texture.DEFAULT_ANISOTROPY = 1; + +/** + * Class representing a 4D vector. A 4D vector is an ordered quadruplet of numbers + * (labeled x, y, z and w), which can be used to represent a number of things, such as: + * + * - A point in 4D space. + * - A direction and length in 4D space. In three.js the length will + * always be the Euclidean distance(straight-line distance) from `(0, 0, 0, 0)` to `(x, y, z, w)` + * and the direction is also measured from `(0, 0, 0, 0)` towards `(x, y, z, w)`. + * - Any arbitrary ordered quadruplet of numbers. + * + * There are other things a 4D vector can be used to represent, however these + * are the most common uses in *three.js*. + * + * Iterating through a vector instance will yield its components `(x, y, z, w)` in + * the corresponding order. + * ```js + * const a = new THREE.Vector4( 0, 1, 0, 0 ); + * + * //no arguments; will be initialised to (0, 0, 0, 1) + * const b = new THREE.Vector4( ); + * + * const d = a.dot( b ); + * ``` + */ +class Vector4 { + + /** + * Constructs a new 4D vector. + * + * @param {number} [x=0] - The x value of this vector. + * @param {number} [y=0] - The y value of this vector. + * @param {number} [z=0] - The z value of this vector. + * @param {number} [w=1] - The w value of this vector. + */ + constructor( x = 0, y = 0, z = 0, w = 1 ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + Vector4.prototype.isVector4 = true; + + /** + * The x value of this vector. + * + * @type {number} + */ this.x = x; + + /** + * The y value of this vector. + * + * @type {number} + */ this.y = y; + + /** + * The z value of this vector. + * + * @type {number} + */ this.z = z; + /** + * The w value of this vector. + * + * @type {number} + */ + this.w = w; + } - set( x, y, z ) { + /** + * Alias for {@link Vector4#z}. + * + * @type {number} + */ + get width() { - if ( z === undefined ) z = this.z; // sprite.scale.set(x,y) + return this.z; + + } + + set width( value ) { + + this.z = value; + + } + + /** + * Alias for {@link Vector4#w}. + * + * @type {number} + */ + get height() { + + return this.w; + + } + + set height( value ) { + + this.w = value; + + } + + /** + * Sets the vector components. + * + * @param {number} x - The value of the x component. + * @param {number} y - The value of the y component. + * @param {number} z - The value of the z component. + * @param {number} w - The value of the w component. + * @return {Vector4} A reference to this vector. + */ + set( x, y, z, w ) { this.x = x; this.y = y; this.z = z; + this.w = w; return this; } + /** + * Sets the vector components to the same value. + * + * @param {number} scalar - The value to set for all vector components. + * @return {Vector4} A reference to this vector. + */ setScalar( scalar ) { this.x = scalar; this.y = scalar; this.z = scalar; + this.w = scalar; return this; } + /** + * Sets the vector's x component to the given value + * + * @param {number} x - The value to set. + * @return {Vector4} A reference to this vector. + */ setX( x ) { this.x = x; @@ -3871,6 +7809,12 @@ class Vector3 { } + /** + * Sets the vector's y component to the given value + * + * @param {number} y - The value to set. + * @return {Vector4} A reference to this vector. + */ setY( y ) { this.y = y; @@ -3879,6 +7823,12 @@ class Vector3 { } + /** + * Sets the vector's z component to the given value + * + * @param {number} z - The value to set. + * @return {Vector4} A reference to this vector. + */ setZ( z ) { this.z = z; @@ -3887,6 +7837,28 @@ class Vector3 { } + /** + * Sets the vector's w component to the given value + * + * @param {number} w - The value to set. + * @return {Vector4} A reference to this vector. + */ + setW( w ) { + + this.w = w; + + return this; + + } + + /** + * Allows to set a vector component with an index. + * + * @param {number} index - The component index. `0` equals to x, `1` equals to y, + * `2` equals to z, `3` equals to w. + * @param {number} value - The value to set. + * @return {Vector4} A reference to this vector. + */ setComponent( index, value ) { switch ( index ) { @@ -3894,6 +7866,7 @@ class Vector3 { case 0: this.x = value; break; case 1: this.y = value; break; case 2: this.z = value; break; + case 3: this.w = value; break; default: throw new Error( 'index is out of range: ' + index ); } @@ -3902,6 +7875,13 @@ class Vector3 { } + /** + * Returns the value of the vector component which matches the given index. + * + * @param {number} index - The component index. `0` equals to x, `1` equals to y, + * `2` equals to z, `3` equals to w. + * @return {number} A vector component value. + */ getComponent( index ) { switch ( index ) { @@ -3909,658 +7889,1605 @@ class Vector3 { case 0: return this.x; case 1: return this.y; case 2: return this.z; + case 3: return this.w; default: throw new Error( 'index is out of range: ' + index ); } } + /** + * Returns a new vector with copied values from this instance. + * + * @return {Vector4} A clone of this instance. + */ clone() { - return new this.constructor( this.x, this.y, this.z ); + return new this.constructor( this.x, this.y, this.z, this.w ); } + /** + * Copies the values of the given vector to this instance. + * + * @param {Vector3|Vector4} v - The vector to copy. + * @return {Vector4} A reference to this vector. + */ copy( v ) { this.x = v.x; this.y = v.y; this.z = v.z; + this.w = ( v.w !== undefined ) ? v.w : 1; return this; } + /** + * Adds the given vector to this instance. + * + * @param {Vector4} v - The vector to add. + * @return {Vector4} A reference to this vector. + */ add( v ) { this.x += v.x; this.y += v.y; this.z += v.z; + this.w += v.w; return this; } + /** + * Adds the given scalar value to all components of this instance. + * + * @param {number} s - The scalar to add. + * @return {Vector4} A reference to this vector. + */ addScalar( s ) { this.x += s; this.y += s; this.z += s; + this.w += s; return this; } + /** + * Adds the given vectors and stores the result in this instance. + * + * @param {Vector4} a - The first vector. + * @param {Vector4} b - The second vector. + * @return {Vector4} A reference to this vector. + */ addVectors( a, b ) { this.x = a.x + b.x; this.y = a.y + b.y; this.z = a.z + b.z; + this.w = a.w + b.w; return this; } + /** + * Adds the given vector scaled by the given factor to this instance. + * + * @param {Vector4} v - The vector. + * @param {number} s - The factor that scales `v`. + * @return {Vector4} A reference to this vector. + */ addScaledVector( v, s ) { this.x += v.x * s; this.y += v.y * s; this.z += v.z * s; + this.w += v.w * s; return this; } + /** + * Subtracts the given vector from this instance. + * + * @param {Vector4} v - The vector to subtract. + * @return {Vector4} A reference to this vector. + */ sub( v ) { this.x -= v.x; this.y -= v.y; this.z -= v.z; + this.w -= v.w; return this; } + /** + * Subtracts the given scalar value from all components of this instance. + * + * @param {number} s - The scalar to subtract. + * @return {Vector4} A reference to this vector. + */ subScalar( s ) { this.x -= s; this.y -= s; this.z -= s; + this.w -= s; return this; } + /** + * Subtracts the given vectors and stores the result in this instance. + * + * @param {Vector4} a - The first vector. + * @param {Vector4} b - The second vector. + * @return {Vector4} A reference to this vector. + */ subVectors( a, b ) { this.x = a.x - b.x; this.y = a.y - b.y; this.z = a.z - b.z; + this.w = a.w - b.w; return this; } + /** + * Multiplies the given vector with this instance. + * + * @param {Vector4} v - The vector to multiply. + * @return {Vector4} A reference to this vector. + */ multiply( v ) { this.x *= v.x; this.y *= v.y; this.z *= v.z; + this.w *= v.w; return this; } + /** + * Multiplies the given scalar value with all components of this instance. + * + * @param {number} scalar - The scalar to multiply. + * @return {Vector4} A reference to this vector. + */ multiplyScalar( scalar ) { this.x *= scalar; this.y *= scalar; this.z *= scalar; + this.w *= scalar; return this; } - multiplyVectors( a, b ) { + /** + * Multiplies this vector with the given 4x4 matrix. + * + * @param {Matrix4} m - The 4x4 matrix. + * @return {Vector4} A reference to this vector. + */ + applyMatrix4( m ) { - this.x = a.x * b.x; - this.y = a.y * b.y; - this.z = a.z * b.z; + const x = this.x, y = this.y, z = this.z, w = this.w; + const e = m.elements; + + this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] * w; + this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] * w; + this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] * w; + this.w = e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] * w; return this; } - applyEuler( euler ) { + /** + * Divides this instance by the given vector. + * + * @param {Vector4} v - The vector to divide. + * @return {Vector4} A reference to this vector. + */ + divide( v ) { - return this.applyQuaternion( _quaternion$4.setFromEuler( euler ) ); + this.x /= v.x; + this.y /= v.y; + this.z /= v.z; + this.w /= v.w; + + return this; } - applyAxisAngle( axis, angle ) { + /** + * Divides this vector by the given scalar. + * + * @param {number} scalar - The scalar to divide. + * @return {Vector4} A reference to this vector. + */ + divideScalar( scalar ) { - return this.applyQuaternion( _quaternion$4.setFromAxisAngle( axis, angle ) ); + return this.multiplyScalar( 1 / scalar ); } - applyMatrix3( m ) { + /** + * Sets the x, y and z components of this + * vector to the quaternion's axis and w to the angle. + * + * @param {Quaternion} q - The Quaternion to set. + * @return {Vector4} A reference to this vector. + */ + setAxisAngleFromQuaternion( q ) { - const x = this.x, y = this.y, z = this.z; - const e = m.elements; + // http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToAngle/index.htm - this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ] * z; - this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ] * z; - this.z = e[ 2 ] * x + e[ 5 ] * y + e[ 8 ] * z; + // q is assumed to be normalized + + this.w = 2 * Math.acos( q.w ); + + const s = Math.sqrt( 1 - q.w * q.w ); + + if ( s < 0.0001 ) { + + this.x = 1; + this.y = 0; + this.z = 0; + + } else { + + this.x = q.x / s; + this.y = q.y / s; + this.z = q.z / s; + + } return this; } - applyNormalMatrix( m ) { + /** + * Sets the x, y and z components of this + * vector to the axis of rotation and w to the angle. + * + * @param {Matrix4} m - A 4x4 matrix of which the upper left 3x3 matrix is a pure rotation matrix. + * @return {Vector4} A reference to this vector. + */ + setAxisAngleFromRotationMatrix( m ) { - return this.applyMatrix3( m ).normalize(); + // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToAngle/index.htm - } + // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) - applyMatrix4( m ) { + let angle, x, y, z; // variables for result + const epsilon = 0.01, // margin to allow for rounding errors + epsilon2 = 0.1, // margin to distinguish between 0 and 180 degrees - const x = this.x, y = this.y, z = this.z; - const e = m.elements; + te = m.elements, - const w = 1 / ( e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] ); + m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ], + m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ], + m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ]; - this.x = ( e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] ) * w; - this.y = ( e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] ) * w; - this.z = ( e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] ) * w; + if ( ( Math.abs( m12 - m21 ) < epsilon ) && + ( Math.abs( m13 - m31 ) < epsilon ) && + ( Math.abs( m23 - m32 ) < epsilon ) ) { - return this; + // singularity found + // first check for identity matrix which must have +1 for all terms + // in leading diagonal and zero in other terms - } + if ( ( Math.abs( m12 + m21 ) < epsilon2 ) && + ( Math.abs( m13 + m31 ) < epsilon2 ) && + ( Math.abs( m23 + m32 ) < epsilon2 ) && + ( Math.abs( m11 + m22 + m33 - 3 ) < epsilon2 ) ) { - applyQuaternion( q ) { + // this singularity is identity matrix so angle = 0 - const x = this.x, y = this.y, z = this.z; - const qx = q.x, qy = q.y, qz = q.z, qw = q.w; + this.set( 1, 0, 0, 0 ); - // calculate quat * vector + return this; // zero angle, arbitrary axis - const ix = qw * x + qy * z - qz * y; - const iy = qw * y + qz * x - qx * z; - const iz = qw * z + qx * y - qy * x; - const iw = - qx * x - qy * y - qz * z; + } - // calculate result * inverse quat + // otherwise this singularity is angle = 180 - this.x = ix * qw + iw * - qx + iy * - qz - iz * - qy; - this.y = iy * qw + iw * - qy + iz * - qx - ix * - qz; - this.z = iz * qw + iw * - qz + ix * - qy - iy * - qx; + angle = Math.PI; - return this; + const xx = ( m11 + 1 ) / 2; + const yy = ( m22 + 1 ) / 2; + const zz = ( m33 + 1 ) / 2; + const xy = ( m12 + m21 ) / 4; + const xz = ( m13 + m31 ) / 4; + const yz = ( m23 + m32 ) / 4; - } + if ( ( xx > yy ) && ( xx > zz ) ) { - project( camera ) { + // m11 is the largest diagonal term - return this.applyMatrix4( camera.matrixWorldInverse ).applyMatrix4( camera.projectionMatrix ); + if ( xx < epsilon ) { - } + x = 0; + y = 0.707106781; + z = 0.707106781; - unproject( camera ) { + } else { - return this.applyMatrix4( camera.projectionMatrixInverse ).applyMatrix4( camera.matrixWorld ); + x = Math.sqrt( xx ); + y = xy / x; + z = xz / x; - } + } - transformDirection( m ) { + } else if ( yy > zz ) { - // input: THREE.Matrix4 affine matrix - // vector interpreted as a direction + // m22 is the largest diagonal term - const x = this.x, y = this.y, z = this.z; - const e = m.elements; + if ( yy < epsilon ) { - this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z; - this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z; - this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z; + x = 0.707106781; + y = 0; + z = 0.707106781; - return this.normalize(); + } else { - } + y = Math.sqrt( yy ); + x = xy / y; + z = yz / y; - divide( v ) { + } - this.x /= v.x; - this.y /= v.y; - this.z /= v.z; + } else { + + // m33 is the largest diagonal term so base result on this + + if ( zz < epsilon ) { + + x = 0.707106781; + y = 0.707106781; + z = 0; + + } else { + + z = Math.sqrt( zz ); + x = xz / z; + y = yz / z; + + } + + } + + this.set( x, y, z, angle ); + + return this; // return 180 deg rotation + + } + + // as we have reached here there are no singularities so we can handle normally + + let s = Math.sqrt( ( m32 - m23 ) * ( m32 - m23 ) + + ( m13 - m31 ) * ( m13 - m31 ) + + ( m21 - m12 ) * ( m21 - m12 ) ); // used to normalize + + if ( Math.abs( s ) < 0.001 ) s = 1; + + // prevent divide by zero, should not happen if matrix is orthogonal and should be + // caught by singularity test above, but I've left it in just in case + + this.x = ( m32 - m23 ) / s; + this.y = ( m13 - m31 ) / s; + this.z = ( m21 - m12 ) / s; + this.w = Math.acos( ( m11 + m22 + m33 - 1 ) / 2 ); return this; } - divideScalar( scalar ) { + /** + * Sets the vector components to the position elements of the + * given transformation matrix. + * + * @param {Matrix4} m - The 4x4 matrix. + * @return {Vector4} A reference to this vector. + */ + setFromMatrixPosition( m ) { - return this.multiplyScalar( 1 / scalar ); + const e = m.elements; + + this.x = e[ 12 ]; + this.y = e[ 13 ]; + this.z = e[ 14 ]; + this.w = e[ 15 ]; + + return this; } + /** + * If this vector's x, y, z or w value is greater than the given vector's x, y, z or w + * value, replace that value with the corresponding min value. + * + * @param {Vector4} v - The vector. + * @return {Vector4} A reference to this vector. + */ min( v ) { this.x = Math.min( this.x, v.x ); this.y = Math.min( this.y, v.y ); this.z = Math.min( this.z, v.z ); + this.w = Math.min( this.w, v.w ); return this; } + /** + * If this vector's x, y, z or w value is less than the given vector's x, y, z or w + * value, replace that value with the corresponding max value. + * + * @param {Vector4} v - The vector. + * @return {Vector4} A reference to this vector. + */ max( v ) { this.x = Math.max( this.x, v.x ); this.y = Math.max( this.y, v.y ); this.z = Math.max( this.z, v.z ); + this.w = Math.max( this.w, v.w ); return this; } + /** + * If this vector's x, y, z or w value is greater than the max vector's x, y, z or w + * value, it is replaced by the corresponding value. + * If this vector's x, y, z or w value is less than the min vector's x, y, z or w value, + * it is replaced by the corresponding value. + * + * @param {Vector4} min - The minimum x, y and z values. + * @param {Vector4} max - The maximum x, y and z values in the desired range. + * @return {Vector4} A reference to this vector. + */ clamp( min, max ) { // assumes min < max, componentwise - this.x = Math.max( min.x, Math.min( max.x, this.x ) ); - this.y = Math.max( min.y, Math.min( max.y, this.y ) ); - this.z = Math.max( min.z, Math.min( max.z, this.z ) ); + this.x = clamp( this.x, min.x, max.x ); + this.y = clamp( this.y, min.y, max.y ); + this.z = clamp( this.z, min.z, max.z ); + this.w = clamp( this.w, min.w, max.w ); return this; } + /** + * If this vector's x, y, z or w values are greater than the max value, they are + * replaced by the max value. + * If this vector's x, y, z or w values are less than the min value, they are + * replaced by the min value. + * + * @param {number} minVal - The minimum value the components will be clamped to. + * @param {number} maxVal - The maximum value the components will be clamped to. + * @return {Vector4} A reference to this vector. + */ clampScalar( minVal, maxVal ) { - this.x = Math.max( minVal, Math.min( maxVal, this.x ) ); - this.y = Math.max( minVal, Math.min( maxVal, this.y ) ); - this.z = Math.max( minVal, Math.min( maxVal, this.z ) ); + this.x = clamp( this.x, minVal, maxVal ); + this.y = clamp( this.y, minVal, maxVal ); + this.z = clamp( this.z, minVal, maxVal ); + this.w = clamp( this.w, minVal, maxVal ); return this; } + /** + * If this vector's length is greater than the max value, it is replaced by + * the max value. + * If this vector's length is less than the min value, it is replaced by the + * min value. + * + * @param {number} min - The minimum value the vector length will be clamped to. + * @param {number} max - The maximum value the vector length will be clamped to. + * @return {Vector4} A reference to this vector. + */ clampLength( min, max ) { const length = this.length(); - return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) ); + return this.divideScalar( length || 1 ).multiplyScalar( clamp( length, min, max ) ); } + /** + * The components of this vector are rounded down to the nearest integer value. + * + * @return {Vector4} A reference to this vector. + */ floor() { this.x = Math.floor( this.x ); this.y = Math.floor( this.y ); this.z = Math.floor( this.z ); + this.w = Math.floor( this.w ); return this; } + /** + * The components of this vector are rounded up to the nearest integer value. + * + * @return {Vector4} A reference to this vector. + */ ceil() { this.x = Math.ceil( this.x ); this.y = Math.ceil( this.y ); this.z = Math.ceil( this.z ); + this.w = Math.ceil( this.w ); return this; } + /** + * The components of this vector are rounded to the nearest integer value + * + * @return {Vector4} A reference to this vector. + */ round() { this.x = Math.round( this.x ); this.y = Math.round( this.y ); this.z = Math.round( this.z ); + this.w = Math.round( this.w ); return this; } + /** + * The components of this vector are rounded towards zero (up if negative, + * down if positive) to an integer value. + * + * @return {Vector4} A reference to this vector. + */ roundToZero() { - this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x ); - this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y ); - this.z = ( this.z < 0 ) ? Math.ceil( this.z ) : Math.floor( this.z ); + this.x = Math.trunc( this.x ); + this.y = Math.trunc( this.y ); + this.z = Math.trunc( this.z ); + this.w = Math.trunc( this.w ); return this; } + /** + * Inverts this vector - i.e. sets x = -x, y = -y, z = -z, w = -w. + * + * @return {Vector4} A reference to this vector. + */ negate() { this.x = - this.x; this.y = - this.y; this.z = - this.z; + this.w = - this.w; return this; } + /** + * Calculates the dot product of the given vector with this instance. + * + * @param {Vector4} v - The vector to compute the dot product with. + * @return {number} The result of the dot product. + */ dot( v ) { - return this.x * v.x + this.y * v.y + this.z * v.z; + return this.x * v.x + this.y * v.y + this.z * v.z + this.w * v.w; } - // TODO lengthSquared? - + /** + * Computes the square of the Euclidean length (straight-line length) from + * (0, 0, 0, 0) to (x, y, z, w). If you are comparing the lengths of vectors, you should + * compare the length squared instead as it is slightly more efficient to calculate. + * + * @return {number} The square length of this vector. + */ lengthSq() { - return this.x * this.x + this.y * this.y + this.z * this.z; + return this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w; } + /** + * Computes the Euclidean length (straight-line length) from (0, 0, 0, 0) to (x, y, z, w). + * + * @return {number} The length of this vector. + */ length() { - return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z ); + return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w ); } + /** + * Computes the Manhattan length of this vector. + * + * @return {number} The length of this vector. + */ manhattanLength() { - return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ); + return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ) + Math.abs( this.w ); } + /** + * Converts this vector to a unit vector - that is, sets it equal to a vector + * with the same direction as this one, but with a vector length of `1`. + * + * @return {Vector4} A reference to this vector. + */ normalize() { return this.divideScalar( this.length() || 1 ); } + /** + * Sets this vector to a vector with the same direction as this one, but + * with the specified length. + * + * @param {number} length - The new length of this vector. + * @return {Vector4} A reference to this vector. + */ setLength( length ) { return this.normalize().multiplyScalar( length ); } + /** + * Linearly interpolates between the given vector and this instance, where + * alpha is the percent distance along the line - alpha = 0 will be this + * vector, and alpha = 1 will be the given one. + * + * @param {Vector4} v - The vector to interpolate towards. + * @param {number} alpha - The interpolation factor, typically in the closed interval `[0, 1]`. + * @return {Vector4} A reference to this vector. + */ lerp( v, alpha ) { this.x += ( v.x - this.x ) * alpha; this.y += ( v.y - this.y ) * alpha; this.z += ( v.z - this.z ) * alpha; + this.w += ( v.w - this.w ) * alpha; return this; } + /** + * Linearly interpolates between the given vectors, where alpha is the percent + * distance along the line - alpha = 0 will be first vector, and alpha = 1 will + * be the second one. The result is stored in this instance. + * + * @param {Vector4} v1 - The first vector. + * @param {Vector4} v2 - The second vector. + * @param {number} alpha - The interpolation factor, typically in the closed interval `[0, 1]`. + * @return {Vector4} A reference to this vector. + */ lerpVectors( v1, v2, alpha ) { this.x = v1.x + ( v2.x - v1.x ) * alpha; this.y = v1.y + ( v2.y - v1.y ) * alpha; this.z = v1.z + ( v2.z - v1.z ) * alpha; + this.w = v1.w + ( v2.w - v1.w ) * alpha; return this; } - cross( v ) { + /** + * Returns `true` if this vector is equal with the given one. + * + * @param {Vector4} v - The vector to test for equality. + * @return {boolean} Whether this vector is equal with the given one. + */ + equals( v ) { - return this.crossVectors( this, v ); + return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) && ( v.w === this.w ) ); } - crossVectors( a, b ) { - - const ax = a.x, ay = a.y, az = a.z; - const bx = b.x, by = b.y, bz = b.z; + /** + * Sets this vector's x value to be `array[ offset ]`, y value to be `array[ offset + 1 ]`, + * z value to be `array[ offset + 2 ]`, w value to be `array[ offset + 3 ]`. + * + * @param {Array} array - An array holding the vector component values. + * @param {number} [offset=0] - The offset into the array. + * @return {Vector4} A reference to this vector. + */ + fromArray( array, offset = 0 ) { - this.x = ay * bz - az * by; - this.y = az * bx - ax * bz; - this.z = ax * by - ay * bx; + this.x = array[ offset ]; + this.y = array[ offset + 1 ]; + this.z = array[ offset + 2 ]; + this.w = array[ offset + 3 ]; return this; } - projectOnVector( v ) { - - const denominator = v.lengthSq(); - - if ( denominator === 0 ) return this.set( 0, 0, 0 ); + /** + * Writes the components of this vector to the given array. If no array is provided, + * the method returns a new instance. + * + * @param {Array} [array=[]] - The target array holding the vector components. + * @param {number} [offset=0] - Index of the first element in the array. + * @return {Array} The vector components. + */ + toArray( array = [], offset = 0 ) { - const scalar = v.dot( this ) / denominator; + array[ offset ] = this.x; + array[ offset + 1 ] = this.y; + array[ offset + 2 ] = this.z; + array[ offset + 3 ] = this.w; - return this.copy( v ).multiplyScalar( scalar ); + return array; } - projectOnPlane( planeNormal ) { + /** + * Sets the components of this vector from the given buffer attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute holding vector data. + * @param {number} index - The index into the attribute. + * @return {Vector4} A reference to this vector. + */ + fromBufferAttribute( attribute, index ) { - _vector$b.copy( this ).projectOnVector( planeNormal ); + this.x = attribute.getX( index ); + this.y = attribute.getY( index ); + this.z = attribute.getZ( index ); + this.w = attribute.getW( index ); - return this.sub( _vector$b ); + return this; } - reflect( normal ) { + /** + * Sets each component of this vector to a pseudo-random value between `0` and + * `1`, excluding `1`. + * + * @return {Vector4} A reference to this vector. + */ + random() { - // reflect incident vector off plane orthogonal to normal - // normal is assumed to have unit length + this.x = Math.random(); + this.y = Math.random(); + this.z = Math.random(); + this.w = Math.random(); - return this.sub( _vector$b.copy( normal ).multiplyScalar( 2 * this.dot( normal ) ) ); + return this; } - angleTo( v ) { - - const denominator = Math.sqrt( this.lengthSq() * v.lengthSq() ); - - if ( denominator === 0 ) return Math.PI / 2; - - const theta = this.dot( v ) / denominator; - - // clamp, to handle numerical problems + *[ Symbol.iterator ]() { - return Math.acos( clamp( theta, - 1, 1 ) ); + yield this.x; + yield this.y; + yield this.z; + yield this.w; } - distanceTo( v ) { - - return Math.sqrt( this.distanceToSquared( v ) ); - - } +} - distanceToSquared( v ) { +/** + * A render target is a buffer where the video card draws pixels for a scene + * that is being rendered in the background. It is used in different effects, + * such as applying postprocessing to a rendered image before displaying it + * on the screen. + * + * @augments EventDispatcher + */ +class RenderTarget extends EventDispatcher { - const dx = this.x - v.x, dy = this.y - v.y, dz = this.z - v.z; + /** + * Render target options. + * + * @typedef {Object} RenderTarget~Options + * @property {boolean} [generateMipmaps=false] - Whether to generate mipmaps or not. + * @property {number} [magFilter=LinearFilter] - The mag filter. + * @property {number} [minFilter=LinearFilter] - The min filter. + * @property {number} [format=RGBAFormat] - The texture format. + * @property {number} [type=UnsignedByteType] - The texture type. + * @property {?string} [internalFormat=null] - The texture's internal format. + * @property {number} [wrapS=ClampToEdgeWrapping] - The texture's uv wrapping mode. + * @property {number} [wrapT=ClampToEdgeWrapping] - The texture's uv wrapping mode. + * @property {number} [anisotropy=1] - The texture's anisotropy value. + * @property {string} [colorSpace=NoColorSpace] - The texture's color space. + * @property {boolean} [depthBuffer=true] - Whether to allocate a depth buffer or not. + * @property {boolean} [stencilBuffer=false] - Whether to allocate a stencil buffer or not. + * @property {boolean} [resolveDepthBuffer=true] - Whether to resolve the depth buffer or not. + * @property {boolean} [resolveStencilBuffer=true] - Whether to resolve the stencil buffer or not. + * @property {?Texture} [depthTexture=null] - Reference to a depth texture. + * @property {number} [samples=0] - The MSAA samples count. + * @property {number} [count=1] - Defines the number of color attachments . Must be at least `1`. + * @property {number} [depth=1] - The texture depth. + * @property {boolean} [multiview=false] - Whether this target is used for multiview rendering. + */ - return dx * dx + dy * dy + dz * dz; + /** + * Constructs a new render target. + * + * @param {number} [width=1] - The width of the render target. + * @param {number} [height=1] - The height of the render target. + * @param {RenderTarget~Options} [options] - The configuration object. + */ + constructor( width = 1, height = 1, options = {} ) { - } + super(); - manhattanDistanceTo( v ) { + options = Object.assign( { + generateMipmaps: false, + internalFormat: null, + minFilter: LinearFilter, + depthBuffer: true, + stencilBuffer: false, + resolveDepthBuffer: true, + resolveStencilBuffer: true, + depthTexture: null, + samples: 0, + count: 1, + depth: 1, + multiview: false + }, options ); - return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y ) + Math.abs( this.z - v.z ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isRenderTarget = true; - } + /** + * The width of the render target. + * + * @type {number} + * @default 1 + */ + this.width = width; - setFromSpherical( s ) { + /** + * The height of the render target. + * + * @type {number} + * @default 1 + */ + this.height = height; - return this.setFromSphericalCoords( s.radius, s.phi, s.theta ); + /** + * The depth of the render target. + * + * @type {number} + * @default 1 + */ + this.depth = options.depth; - } + /** + * A rectangular area inside the render target's viewport. Fragments that are + * outside the area will be discarded. + * + * @type {Vector4} + * @default (0,0,width,height) + */ + this.scissor = new Vector4( 0, 0, width, height ); - setFromSphericalCoords( radius, phi, theta ) { + /** + * Indicates whether the scissor test should be enabled when rendering into + * this render target or not. + * + * @type {boolean} + * @default false + */ + this.scissorTest = false; - const sinPhiRadius = Math.sin( phi ) * radius; + /** + * A rectangular area representing the render target's viewport. + * + * @type {Vector4} + * @default (0,0,width,height) + */ + this.viewport = new Vector4( 0, 0, width, height ); - this.x = sinPhiRadius * Math.sin( theta ); - this.y = Math.cos( phi ) * radius; - this.z = sinPhiRadius * Math.cos( theta ); + const image = { width: width, height: height, depth: options.depth }; - return this; + const texture = new Texture( image ); - } + /** + * An array of textures. Each color attachment is represented as a separate texture. + * Has at least a single entry for the default color attachment. + * + * @type {Array} + */ + this.textures = []; - setFromCylindrical( c ) { + const count = options.count; + for ( let i = 0; i < count; i ++ ) { - return this.setFromCylindricalCoords( c.radius, c.theta, c.y ); + this.textures[ i ] = texture.clone(); + this.textures[ i ].isRenderTargetTexture = true; + this.textures[ i ].renderTarget = this; - } + } - setFromCylindricalCoords( radius, theta, y ) { + this._setTextureOptions( options ); - this.x = radius * Math.sin( theta ); - this.y = y; - this.z = radius * Math.cos( theta ); + /** + * Whether to allocate a depth buffer or not. + * + * @type {boolean} + * @default true + */ + this.depthBuffer = options.depthBuffer; - return this; + /** + * Whether to allocate a stencil buffer or not. + * + * @type {boolean} + * @default false + */ + this.stencilBuffer = options.stencilBuffer; - } + /** + * Whether to resolve the depth buffer or not. + * + * @type {boolean} + * @default true + */ + this.resolveDepthBuffer = options.resolveDepthBuffer; - setFromMatrixPosition( m ) { + /** + * Whether to resolve the stencil buffer or not. + * + * @type {boolean} + * @default true + */ + this.resolveStencilBuffer = options.resolveStencilBuffer; - const e = m.elements; + this._depthTexture = null; + this.depthTexture = options.depthTexture; - this.x = e[ 12 ]; - this.y = e[ 13 ]; - this.z = e[ 14 ]; + /** + * The number of MSAA samples. + * + * A value of `0` disables MSAA. + * + * @type {number} + * @default 0 + */ + this.samples = options.samples; - return this; + /** + * Whether to this target is used in multiview rendering. + * + * @type {boolean} + * @default false + */ + this.multiview = options.multiview; } - setFromMatrixScale( m ) { - - const sx = this.setFromMatrixColumn( m, 0 ).length(); - const sy = this.setFromMatrixColumn( m, 1 ).length(); - const sz = this.setFromMatrixColumn( m, 2 ).length(); + _setTextureOptions( options = {} ) { - this.x = sx; - this.y = sy; - this.z = sz; + const values = { + minFilter: LinearFilter, + generateMipmaps: false, + flipY: false, + internalFormat: null + }; - return this; + if ( options.mapping !== undefined ) values.mapping = options.mapping; + if ( options.wrapS !== undefined ) values.wrapS = options.wrapS; + if ( options.wrapT !== undefined ) values.wrapT = options.wrapT; + if ( options.wrapR !== undefined ) values.wrapR = options.wrapR; + if ( options.magFilter !== undefined ) values.magFilter = options.magFilter; + if ( options.minFilter !== undefined ) values.minFilter = options.minFilter; + if ( options.format !== undefined ) values.format = options.format; + if ( options.type !== undefined ) values.type = options.type; + if ( options.anisotropy !== undefined ) values.anisotropy = options.anisotropy; + if ( options.colorSpace !== undefined ) values.colorSpace = options.colorSpace; + if ( options.flipY !== undefined ) values.flipY = options.flipY; + if ( options.generateMipmaps !== undefined ) values.generateMipmaps = options.generateMipmaps; + if ( options.internalFormat !== undefined ) values.internalFormat = options.internalFormat; - } + for ( let i = 0; i < this.textures.length; i ++ ) { - setFromMatrixColumn( m, index ) { + const texture = this.textures[ i ]; + texture.setValues( values ); - return this.fromArray( m.elements, index * 4 ); + } } - setFromMatrix3Column( m, index ) { + /** + * The texture representing the default color attachment. + * + * @type {Texture} + */ + get texture() { - return this.fromArray( m.elements, index * 3 ); + return this.textures[ 0 ]; } - setFromEuler( e ) { - - this.x = e._x; - this.y = e._y; - this.z = e._z; + set texture( value ) { - return this; + this.textures[ 0 ] = value; } - setFromColor( c ) { + set depthTexture( current ) { - this.x = c.r; - this.y = c.g; - this.z = c.b; + if ( this._depthTexture !== null ) this._depthTexture.renderTarget = null; + if ( current !== null ) current.renderTarget = this; - return this; + this._depthTexture = current; } - equals( v ) { + /** + * Instead of saving the depth in a renderbuffer, a texture + * can be used instead which is useful for further processing + * e.g. in context of post-processing. + * + * @type {?DepthTexture} + * @default null + */ + get depthTexture() { - return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) ); + return this._depthTexture; } - fromArray( array, offset = 0 ) { + /** + * Sets the size of this render target. + * + * @param {number} width - The width. + * @param {number} height - The height. + * @param {number} [depth=1] - The depth. + */ + setSize( width, height, depth = 1 ) { - this.x = array[ offset ]; - this.y = array[ offset + 1 ]; - this.z = array[ offset + 2 ]; + if ( this.width !== width || this.height !== height || this.depth !== depth ) { + + this.width = width; + this.height = height; + this.depth = depth; + + for ( let i = 0, il = this.textures.length; i < il; i ++ ) { + + this.textures[ i ].image.width = width; + this.textures[ i ].image.height = height; + this.textures[ i ].image.depth = depth; + this.textures[ i ].isArrayTexture = this.textures[ i ].image.depth > 1; + + } + + this.dispose(); + + } + + this.viewport.set( 0, 0, width, height ); + this.scissor.set( 0, 0, width, height ); + + } + + /** + * Returns a new render target with copied values from this instance. + * + * @return {RenderTarget} A clone of this instance. + */ + clone() { + + return new this.constructor().copy( this ); + + } + + /** + * Copies the settings of the given render target. This is a structural copy so + * no resources are shared between render targets after the copy. That includes + * all MRT textures and the depth texture. + * + * @param {RenderTarget} source - The render target to copy. + * @return {RenderTarget} A reference to this instance. + */ + copy( source ) { + + this.width = source.width; + this.height = source.height; + this.depth = source.depth; + + this.scissor.copy( source.scissor ); + this.scissorTest = source.scissorTest; + + this.viewport.copy( source.viewport ); + + this.textures.length = 0; + + for ( let i = 0, il = source.textures.length; i < il; i ++ ) { + + this.textures[ i ] = source.textures[ i ].clone(); + this.textures[ i ].isRenderTargetTexture = true; + this.textures[ i ].renderTarget = this; + + // ensure image object is not shared, see #20328 + + const image = Object.assign( {}, source.textures[ i ].image ); + this.textures[ i ].source = new Source( image ); + + } + + this.depthBuffer = source.depthBuffer; + this.stencilBuffer = source.stencilBuffer; + + this.resolveDepthBuffer = source.resolveDepthBuffer; + this.resolveStencilBuffer = source.resolveStencilBuffer; + + if ( source.depthTexture !== null ) this.depthTexture = source.depthTexture.clone(); + + this.samples = source.samples; return this; } - toArray( array = [], offset = 0 ) { + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + * + * @fires RenderTarget#dispose + */ + dispose() { - array[ offset ] = this.x; - array[ offset + 1 ] = this.y; - array[ offset + 2 ] = this.z; + this.dispatchEvent( { type: 'dispose' } ); - return array; + } + +} + +/** + * A render target used in context of {@link WebGLRenderer}. + * + * @augments RenderTarget + */ +class WebGLRenderTarget extends RenderTarget { + + /** + * Constructs a new 3D render target. + * + * @param {number} [width=1] - The width of the render target. + * @param {number} [height=1] - The height of the render target. + * @param {RenderTarget~Options} [options] - The configuration object. + */ + constructor( width = 1, height = 1, options = {} ) { + + super( width, height, options ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isWebGLRenderTarget = true; } - fromBufferAttribute( attribute, index ) { +} - this.x = attribute.getX( index ); - this.y = attribute.getY( index ); - this.z = attribute.getZ( index ); +/** + * Creates an array of textures directly from raw buffer data. + * + * @augments Texture + */ +class DataArrayTexture extends Texture { - return this; + /** + * Constructs a new data array texture. + * + * @param {?TypedArray} [data=null] - The buffer data. + * @param {number} [width=1] - The width of the texture. + * @param {number} [height=1] - The height of the texture. + * @param {number} [depth=1] - The depth of the texture. + */ + constructor( data = null, width = 1, height = 1, depth = 1 ) { + + super( null ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isDataArrayTexture = true; + + /** + * The image definition of a data texture. + * + * @type {{data:TypedArray,width:number,height:number,depth:number}} + */ + this.image = { data, width, height, depth }; + + /** + * How the texture is sampled when a texel covers more than one pixel. + * + * Overwritten and set to `NearestFilter` by default. + * + * @type {(NearestFilter|NearestMipmapNearestFilter|NearestMipmapLinearFilter|LinearFilter|LinearMipmapNearestFilter|LinearMipmapLinearFilter)} + * @default NearestFilter + */ + this.magFilter = NearestFilter; + + /** + * How the texture is sampled when a texel covers less than one pixel. + * + * Overwritten and set to `NearestFilter` by default. + * + * @type {(NearestFilter|NearestMipmapNearestFilter|NearestMipmapLinearFilter|LinearFilter|LinearMipmapNearestFilter|LinearMipmapLinearFilter)} + * @default NearestFilter + */ + this.minFilter = NearestFilter; + + /** + * This defines how the texture is wrapped in the depth and corresponds to + * *W* in UVW mapping. + * + * @type {(RepeatWrapping|ClampToEdgeWrapping|MirroredRepeatWrapping)} + * @default ClampToEdgeWrapping + */ + this.wrapR = ClampToEdgeWrapping; + + /** + * Whether to generate mipmaps (if possible) for a texture. + * + * Overwritten and set to `false` by default. + * + * @type {boolean} + * @default false + */ + this.generateMipmaps = false; + + /** + * If set to `true`, the texture is flipped along the vertical axis when + * uploaded to the GPU. + * + * Overwritten and set to `false` by default. + * + * @type {boolean} + * @default false + */ + this.flipY = false; + + /** + * Specifies the alignment requirements for the start of each pixel row in memory. + * + * Overwritten and set to `1` by default. + * + * @type {boolean} + * @default 1 + */ + this.unpackAlignment = 1; + + /** + * A set of all layers which need to be updated in the texture. + * + * @type {Set} + */ + this.layerUpdates = new Set(); } - random() { + /** + * Describes that a specific layer of the texture needs to be updated. + * Normally when {@link Texture#needsUpdate} is set to `true`, the + * entire data texture array is sent to the GPU. Marking specific + * layers will only transmit subsets of all mipmaps associated with a + * specific depth in the array which is often much more performant. + * + * @param {number} layerIndex - The layer index that should be updated. + */ + addLayerUpdate( layerIndex ) { - this.x = Math.random(); - this.y = Math.random(); - this.z = Math.random(); + this.layerUpdates.add( layerIndex ); - return this; + } + + /** + * Resets the layer updates registry. + */ + clearLayerUpdates() { + + this.layerUpdates.clear(); } - randomDirection() { +} + +/** + * An array render target used in context of {@link WebGLRenderer}. + * + * @augments WebGLRenderTarget + */ +class WebGLArrayRenderTarget extends WebGLRenderTarget { + + /** + * Constructs a new array render target. + * + * @param {number} [width=1] - The width of the render target. + * @param {number} [height=1] - The height of the render target. + * @param {number} [depth=1] - The height of the render target. + * @param {RenderTarget~Options} [options] - The configuration object. + */ + constructor( width = 1, height = 1, depth = 1, options = {} ) { + + super( width, height, options ); - // Derived from https://mathworld.wolfram.com/SpherePointPicking.html + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isWebGLArrayRenderTarget = true; - const u = ( Math.random() - 0.5 ) * 2; - const t = Math.random() * Math.PI * 2; - const f = Math.sqrt( 1 - u ** 2 ); + this.depth = depth; - this.x = f * Math.cos( t ); - this.y = f * Math.sin( t ); - this.z = u; + /** + * Overwritten with a different texture type. + * + * @type {DataArrayTexture} + */ + this.texture = new DataArrayTexture( null, width, height, depth ); + this._setTextureOptions( options ); - return this; + this.texture.isRenderTargetTexture = true; } - *[ Symbol.iterator ]() { +} - yield this.x; - yield this.y; - yield this.z; +/** + * Creates a three-dimensional texture from raw data, with parameters to + * divide it into width, height, and depth. + * + * @augments Texture + */ +class Data3DTexture extends Texture { + + /** + * Constructs a new data array texture. + * + * @param {?TypedArray} [data=null] - The buffer data. + * @param {number} [width=1] - The width of the texture. + * @param {number} [height=1] - The height of the texture. + * @param {number} [depth=1] - The depth of the texture. + */ + constructor( data = null, width = 1, height = 1, depth = 1 ) { + + // We're going to add .setXXX() methods for setting properties later. + // Users can still set in Data3DTexture directly. + // + // const texture = new THREE.Data3DTexture( data, width, height, depth ); + // texture.anisotropy = 16; + // + // See #14839 + + super( null ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isData3DTexture = true; + + /** + * The image definition of a data texture. + * + * @type {{data:TypedArray,width:number,height:number,depth:number}} + */ + this.image = { data, width, height, depth }; + + /** + * How the texture is sampled when a texel covers more than one pixel. + * + * Overwritten and set to `NearestFilter` by default. + * + * @type {(NearestFilter|NearestMipmapNearestFilter|NearestMipmapLinearFilter|LinearFilter|LinearMipmapNearestFilter|LinearMipmapLinearFilter)} + * @default NearestFilter + */ + this.magFilter = NearestFilter; + + /** + * How the texture is sampled when a texel covers less than one pixel. + * + * Overwritten and set to `NearestFilter` by default. + * + * @type {(NearestFilter|NearestMipmapNearestFilter|NearestMipmapLinearFilter|LinearFilter|LinearMipmapNearestFilter|LinearMipmapLinearFilter)} + * @default NearestFilter + */ + this.minFilter = NearestFilter; + + /** + * This defines how the texture is wrapped in the depth and corresponds to + * *W* in UVW mapping. + * + * @type {(RepeatWrapping|ClampToEdgeWrapping|MirroredRepeatWrapping)} + * @default ClampToEdgeWrapping + */ + this.wrapR = ClampToEdgeWrapping; + + /** + * Whether to generate mipmaps (if possible) for a texture. + * + * Overwritten and set to `false` by default. + * + * @type {boolean} + * @default false + */ + this.generateMipmaps = false; + + /** + * If set to `true`, the texture is flipped along the vertical axis when + * uploaded to the GPU. + * + * Overwritten and set to `false` by default. + * + * @type {boolean} + * @default false + */ + this.flipY = false; + + /** + * Specifies the alignment requirements for the start of each pixel row in memory. + * + * Overwritten and set to `1` by default. + * + * @type {boolean} + * @default 1 + */ + this.unpackAlignment = 1; } } -const _vector$b = /*@__PURE__*/ new Vector3(); -const _quaternion$4 = /*@__PURE__*/ new Quaternion(); +/** + * A 3D render target used in context of {@link WebGLRenderer}. + * + * @augments WebGLRenderTarget + */ +class WebGL3DRenderTarget extends WebGLRenderTarget { + + /** + * Constructs a new 3D render target. + * + * @param {number} [width=1] - The width of the render target. + * @param {number} [height=1] - The height of the render target. + * @param {number} [depth=1] - The height of the render target. + * @param {RenderTarget~Options} [options] - The configuration object. + */ + constructor( width = 1, height = 1, depth = 1, options = {} ) { + + super( width, height, options ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isWebGL3DRenderTarget = true; + + this.depth = depth; + + /** + * Overwritten with a different texture type. + * + * @type {Data3DTexture} + */ + this.texture = new Data3DTexture( null, width, height, depth ); + this._setTextureOptions( options ); + + this.texture.isRenderTargetTexture = true; + + } + +} +/** + * Represents an axis-aligned bounding box (AABB) in 3D space. + */ class Box3 { + /** + * Constructs a new bounding box. + * + * @param {Vector3} [min=(Infinity,Infinity,Infinity)] - A vector representing the lower boundary of the box. + * @param {Vector3} [max=(-Infinity,-Infinity,-Infinity)] - A vector representing the upper boundary of the box. + */ constructor( min = new Vector3( + Infinity, + Infinity, + Infinity ), max = new Vector3( - Infinity, - Infinity, - Infinity ) ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isBox3 = true; + /** + * The lower boundary of the box. + * + * @type {Vector3} + */ this.min = min; + + /** + * The upper boundary of the box. + * + * @type {Vector3} + */ this.max = max; } + /** + * Sets the lower and upper boundaries of this box. + * Please note that this method only copies the values from the given objects. + * + * @param {Vector3} min - The lower boundary of the box. + * @param {Vector3} max - The upper boundary of the box. + * @return {Box3} A reference to this bounding box. + */ set( min, max ) { this.min.copy( min ); @@ -4570,13 +9497,20 @@ class Box3 { } + /** + * Sets the upper and lower bounds of this box so it encloses the position data + * in the given array. + * + * @param {Array} array - An array holding 3D position data. + * @return {Box3} A reference to this bounding box. + */ setFromArray( array ) { this.makeEmpty(); for ( let i = 0, il = array.length; i < il; i += 3 ) { - this.expandByPoint( _vector$a.fromArray( array, i ) ); + this.expandByPoint( _vector$b.fromArray( array, i ) ); } @@ -4584,13 +9518,20 @@ class Box3 { } + /** + * Sets the upper and lower bounds of this box so it encloses the position data + * in the given buffer attribute. + * + * @param {BufferAttribute} attribute - A buffer attribute holding 3D position data. + * @return {Box3} A reference to this bounding box. + */ setFromBufferAttribute( attribute ) { this.makeEmpty(); for ( let i = 0, il = attribute.count; i < il; i ++ ) { - this.expandByPoint( _vector$a.fromBufferAttribute( attribute, i ) ); + this.expandByPoint( _vector$b.fromBufferAttribute( attribute, i ) ); } @@ -4598,6 +9539,13 @@ class Box3 { } + /** + * Sets the upper and lower bounds of this box so it encloses the position data + * in the given array. + * + * @param {Array} points - An array holding 3D position data as instances of {@link Vector3}. + * @return {Box3} A reference to this bounding box. + */ setFromPoints( points ) { this.makeEmpty(); @@ -4612,9 +9560,17 @@ class Box3 { } + /** + * Centers this box on the given center vector and sets this box's width, height and + * depth to the given size values. + * + * @param {Vector3} center - The center of the box. + * @param {Vector3} size - The x, y and z dimensions of the box. + * @return {Box3} A reference to this bounding box. + */ setFromCenterAndSize( center, size ) { - const halfSize = _vector$a.copy( size ).multiplyScalar( 0.5 ); + const halfSize = _vector$b.copy( size ).multiplyScalar( 0.5 ); this.min.copy( center ).sub( halfSize ); this.max.copy( center ).add( halfSize ); @@ -4623,6 +9579,16 @@ class Box3 { } + /** + * Computes the world-axis-aligned bounding box for the given 3D object + * (including its children), accounting for the object's, and children's, + * world transforms. The function may result in a larger box than strictly necessary. + * + * @param {Object3D} object - The 3D object to compute the bounding box for. + * @param {boolean} [precise=false] - If set to `true`, the method computes the smallest + * world-axis-aligned bounding box at the expense of more computation. + * @return {Box3} A reference to this bounding box. + */ setFromObject( object, precise = false ) { this.makeEmpty(); @@ -4631,12 +9597,23 @@ class Box3 { } + /** + * Returns a new box with copied values from this instance. + * + * @return {Box3} A clone of this instance. + */ clone() { return new this.constructor().copy( this ); } + /** + * Copies the values of the given box to this instance. + * + * @param {Box3} box - The box to copy. + * @return {Box3} A reference to this bounding box. + */ copy( box ) { this.min.copy( box.min ); @@ -4646,6 +9623,11 @@ class Box3 { } + /** + * Makes this box empty which means in encloses a zero space in 3D. + * + * @return {Box3} A reference to this bounding box. + */ makeEmpty() { this.min.x = this.min.y = this.min.z = + Infinity; @@ -4655,6 +9637,13 @@ class Box3 { } + /** + * Returns true if this box includes zero points within its bounds. + * Note that a box with equal lower and upper bounds still includes one + * point, the one both bounds share. + * + * @return {boolean} Whether this box is empty or not. + */ isEmpty() { // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes @@ -4663,18 +9652,36 @@ class Box3 { } + /** + * Returns the center point of this box. + * + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The center point. + */ getCenter( target ) { return this.isEmpty() ? target.set( 0, 0, 0 ) : target.addVectors( this.min, this.max ).multiplyScalar( 0.5 ); } + /** + * Returns the dimensions of this box. + * + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The size. + */ getSize( target ) { return this.isEmpty() ? target.set( 0, 0, 0 ) : target.subVectors( this.max, this.min ); } + /** + * Expands the boundaries of this box to include the given point. + * + * @param {Vector3} point - The point that should be included by the bounding box. + * @return {Box3} A reference to this bounding box. + */ expandByPoint( point ) { this.min.min( point ); @@ -4684,6 +9691,16 @@ class Box3 { } + /** + * Expands this box equilaterally by the given vector. The width of this + * box will be expanded by the x component of the vector in both + * directions. The height of this box will be expanded by the y component of + * the vector in both directions. The depth of this box will be + * expanded by the z component of the vector in both directions. + * + * @param {Vector3} vector - The vector that should expand the bounding box. + * @return {Box3} A reference to this bounding box. + */ expandByVector( vector ) { this.min.sub( vector ); @@ -4693,6 +9710,13 @@ class Box3 { } + /** + * Expands each dimension of the box by the given scalar. If negative, the + * dimensions of the box will be contracted. + * + * @param {number} scalar - The scalar value that should expand the bounding box. + * @return {Box3} A reference to this bounding box. + */ expandByScalar( scalar ) { this.min.addScalar( - scalar ); @@ -4702,6 +9726,17 @@ class Box3 { } + /** + * Expands the boundaries of this box to include the given 3D object and + * its children, accounting for the object's, and children's, world + * transforms. The function may result in a larger box than strictly + * necessary (unless the precise parameter is set to true). + * + * @param {Object3D} object - The 3D object that should expand the bounding box. + * @param {boolean} precise - If set to `true`, the method expands the bounding box + * as little as necessary at the expense of more computation. + * @return {Box3} A reference to this bounding box. + */ expandByObject( object, precise = false ) { // Computes the world-axis-aligned bounding box of an object (including its children), @@ -4709,50 +9744,67 @@ class Box3 { object.updateWorldMatrix( false, false ); - if ( object.boundingBox !== undefined ) { + const geometry = object.geometry; + + if ( geometry !== undefined ) { - if ( object.boundingBox === null ) { + const positionAttribute = geometry.getAttribute( 'position' ); - object.computeBoundingBox(); + // precise AABB computation based on vertex data requires at least a position attribute. + // instancing isn't supported so far and uses the normal (conservative) code path. - } + if ( precise === true && positionAttribute !== undefined && object.isInstancedMesh !== true ) { - _box$3.copy( object.boundingBox ); - _box$3.applyMatrix4( object.matrixWorld ); + for ( let i = 0, l = positionAttribute.count; i < l; i ++ ) { - this.union( _box$3 ); + if ( object.isMesh === true ) { - } else { + object.getVertexPosition( i, _vector$b ); - const geometry = object.geometry; + } else { + + _vector$b.fromBufferAttribute( positionAttribute, i ); + + } - if ( geometry !== undefined ) { + _vector$b.applyMatrix4( object.matrixWorld ); + this.expandByPoint( _vector$b ); - if ( precise && geometry.attributes !== undefined && geometry.attributes.position !== undefined ) { + } + + } else { + + if ( object.boundingBox !== undefined ) { - const position = geometry.attributes.position; - for ( let i = 0, l = position.count; i < l; i ++ ) { + // object-level bounding box - _vector$a.fromBufferAttribute( position, i ).applyMatrix4( object.matrixWorld ); - this.expandByPoint( _vector$a ); + if ( object.boundingBox === null ) { + + object.computeBoundingBox(); } + _box$4.copy( object.boundingBox ); + + } else { + // geometry-level bounding box + if ( geometry.boundingBox === null ) { geometry.computeBoundingBox(); } - _box$3.copy( geometry.boundingBox ); - _box$3.applyMatrix4( object.matrixWorld ); - - this.union( _box$3 ); + _box$4.copy( geometry.boundingBox ); } + _box$4.applyMatrix4( object.matrixWorld ); + + this.union( _box$4 ); + } } @@ -4769,14 +9821,27 @@ class Box3 { } + /** + * Returns `true` if the given point lies within or on the boundaries of this box. + * + * @param {Vector3} point - The point to test. + * @return {boolean} Whether the bounding box contains the given point or not. + */ containsPoint( point ) { - return point.x < this.min.x || point.x > this.max.x || - point.y < this.min.y || point.y > this.max.y || - point.z < this.min.z || point.z > this.max.z ? false : true; + return point.x >= this.min.x && point.x <= this.max.x && + point.y >= this.min.y && point.y <= this.max.y && + point.z >= this.min.z && point.z <= this.max.z; } + /** + * Returns `true` if this bounding box includes the entirety of the given bounding box. + * If this box and the given one are identical, this function also returns `true`. + * + * @param {Box3} box - The bounding box to test. + * @return {boolean} Whether the bounding box contains the given bounding box or not. + */ containsBox( box ) { return this.min.x <= box.min.x && box.max.x <= this.max.x && @@ -4785,6 +9850,13 @@ class Box3 { } + /** + * Returns a point as a proportion of this box's width, height and depth. + * + * @param {Vector3} point - A point in 3D space. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} A point as a proportion of this box's width, height and depth. + */ getParameter( point, target ) { // This can potentially have a divide by zero if the box @@ -4798,25 +9870,43 @@ class Box3 { } + /** + * Returns `true` if the given bounding box intersects with this bounding box. + * + * @param {Box3} box - The bounding box to test. + * @return {boolean} Whether the given bounding box intersects with this bounding box. + */ intersectsBox( box ) { // using 6 splitting planes to rule out intersections. - return box.max.x < this.min.x || box.min.x > this.max.x || - box.max.y < this.min.y || box.min.y > this.max.y || - box.max.z < this.min.z || box.min.z > this.max.z ? false : true; + return box.max.x >= this.min.x && box.min.x <= this.max.x && + box.max.y >= this.min.y && box.min.y <= this.max.y && + box.max.z >= this.min.z && box.min.z <= this.max.z; } + /** + * Returns `true` if the given bounding sphere intersects with this bounding box. + * + * @param {Sphere} sphere - The bounding sphere to test. + * @return {boolean} Whether the given bounding sphere intersects with this bounding box. + */ intersectsSphere( sphere ) { // Find the point on the AABB closest to the sphere center. - this.clampPoint( sphere.center, _vector$a ); + this.clampPoint( sphere.center, _vector$b ); // If that point is inside the sphere, the AABB and sphere intersect. - return _vector$a.distanceToSquared( sphere.center ) <= ( sphere.radius * sphere.radius ); + return _vector$b.distanceToSquared( sphere.center ) <= ( sphere.radius * sphere.radius ); } + /** + * Returns `true` if the given plane intersects with this bounding box. + * + * @param {Plane} plane - The plane to test. + * @return {boolean} Whether the given plane intersects with this bounding box. + */ intersectsPlane( plane ) { // We compute the minimum and maximum dot product values. If those values @@ -4864,6 +9954,12 @@ class Box3 { } + /** + * Returns `true` if the given triangle intersects with this bounding box. + * + * @param {Triangle} triangle - The triangle to test. + * @return {boolean} Whether the given triangle intersects with this bounding box. + */ intersectsTriangle( triangle ) { if ( this.isEmpty() ) { @@ -4877,14 +9973,14 @@ class Box3 { _extents.subVectors( this.max, _center ); // translate triangle to aabb origin - _v0$2.subVectors( triangle.a, _center ); + _v0$3.subVectors( triangle.a, _center ); _v1$7.subVectors( triangle.b, _center ); _v2$4.subVectors( triangle.c, _center ); // compute edge vectors for triangle - _f0.subVectors( _v1$7, _v0$2 ); + _f0.subVectors( _v1$7, _v0$3 ); _f1.subVectors( _v2$4, _v1$7 ); - _f2.subVectors( _v0$2, _v2$4 ); + _f2.subVectors( _v0$3, _v2$4 ); // test against axes that are given by cross product combinations of the edges of the triangle and the edges of the aabb // make an axis testing of each of the 3 sides of the aabb against each of the 3 sides of the triangle = 9 axis of separation @@ -4894,7 +9990,7 @@ class Box3 { _f0.z, 0, - _f0.x, _f1.z, 0, - _f1.x, _f2.z, 0, - _f2.x, - _f0.y, _f0.x, 0, - _f1.y, _f1.x, 0, - _f2.y, _f2.x, 0 ]; - if ( ! satForAxes( axes, _v0$2, _v1$7, _v2$4, _extents ) ) { + if ( ! satForAxes( axes, _v0$3, _v1$7, _v2$4, _extents ) ) { return false; @@ -4902,7 +9998,7 @@ class Box3 { // test 3 face normals from the aabb axes = [ 1, 0, 0, 0, 1, 0, 0, 0, 1 ]; - if ( ! satForAxes( axes, _v0$2, _v1$7, _v2$4, _extents ) ) { + if ( ! satForAxes( axes, _v0$3, _v1$7, _v2$4, _extents ) ) { return false; @@ -4913,22 +10009,42 @@ class Box3 { _triangleNormal.crossVectors( _f0, _f1 ); axes = [ _triangleNormal.x, _triangleNormal.y, _triangleNormal.z ]; - return satForAxes( axes, _v0$2, _v1$7, _v2$4, _extents ); + return satForAxes( axes, _v0$3, _v1$7, _v2$4, _extents ); } + /** + * Clamps the given point within the bounds of this box. + * + * @param {Vector3} point - The point to clamp. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The clamped point. + */ clampPoint( point, target ) { return target.copy( point ).clamp( this.min, this.max ); } + /** + * Returns the euclidean distance from any edge of this box to the specified point. If + * the given point lies inside of this box, the distance will be `0`. + * + * @param {Vector3} point - The point to compute the distance to. + * @return {number} The euclidean distance. + */ distanceToPoint( point ) { - return this.clampPoint( point, _vector$a ).distanceTo( point ); + return this.clampPoint( point, _vector$b ).distanceTo( point ); } + /** + * Returns a bounding sphere that encloses this bounding box. + * + * @param {Sphere} target - The target sphere that is used to store the method's result. + * @return {Sphere} The bounding sphere that encloses this bounding box. + */ getBoundingSphere( target ) { if ( this.isEmpty() ) { @@ -4939,7 +10055,7 @@ class Box3 { this.getCenter( target.center ); - target.radius = this.getSize( _vector$a ).length() * 0.5; + target.radius = this.getSize( _vector$b ).length() * 0.5; } @@ -4947,6 +10063,15 @@ class Box3 { } + /** + * Computes the intersection of this bounding box and the given one, setting the upper + * bound of this box to the lesser of the two boxes' upper bounds and the + * lower bound of this box to the greater of the two boxes' lower bounds. If + * there's no overlap, makes this box empty. + * + * @param {Box3} box - The bounding box to intersect with. + * @return {Box3} A reference to this bounding box. + */ intersect( box ) { this.min.max( box.min ); @@ -4959,6 +10084,14 @@ class Box3 { } + /** + * Computes the union of this box and another and the given one, setting the upper + * bound of this box to the greater of the two boxes' upper bounds and the + * lower bound of this box to the lesser of the two boxes' lower bounds. + * + * @param {Box3} box - The bounding box that will be unioned with this instance. + * @return {Box3} A reference to this bounding box. + */ union( box ) { this.min.min( box.min ); @@ -4968,6 +10101,12 @@ class Box3 { } + /** + * Transforms this bounding box by the given 4x4 transformation matrix. + * + * @param {Matrix4} matrix - The transformation matrix. + * @return {Box3} A reference to this bounding box. + */ applyMatrix4( matrix ) { // transform of empty box is an empty box. @@ -4989,6 +10128,13 @@ class Box3 { } + /** + * Adds the given offset to both the upper and lower bounds of this bounding box, + * effectively moving it in 3D space. + * + * @param {Vector3} offset - The offset that should be used to translate the bounding box. + * @return {Box3} A reference to this bounding box. + */ translate( offset ) { this.min.add( offset ); @@ -4998,12 +10144,46 @@ class Box3 { } + /** + * Returns `true` if this bounding box is equal with the given one. + * + * @param {Box3} box - The box to test for equality. + * @return {boolean} Whether this bounding box is equal with the given one. + */ equals( box ) { return box.min.equals( this.min ) && box.max.equals( this.max ); } + /** + * Returns a serialized structure of the bounding box. + * + * @return {Object} Serialized structure with fields representing the object state. + */ + toJSON() { + + return { + min: this.min.toArray(), + max: this.max.toArray() + }; + + } + + /** + * Returns a serialized structure of the bounding box. + * + * @param {Object} json - The serialized json to set the box from. + * @return {Box3} A reference to this bounding box. + */ + fromJSON( json ) { + + this.min.fromArray( json.min ); + this.max.fromArray( json.max ); + return this; + + } + } const _points = [ @@ -5017,13 +10197,13 @@ const _points = [ /*@__PURE__*/ new Vector3() ]; -const _vector$a = /*@__PURE__*/ new Vector3(); +const _vector$b = /*@__PURE__*/ new Vector3(); -const _box$3 = /*@__PURE__*/ new Box3(); +const _box$4 = /*@__PURE__*/ new Box3(); // triangle centered vertices -const _v0$2 = /*@__PURE__*/ new Vector3(); +const _v0$3 = /*@__PURE__*/ new Vector3(); const _v1$7 = /*@__PURE__*/ new Vector3(); const _v2$4 = /*@__PURE__*/ new Vector3(); @@ -5064,19 +10244,56 @@ function satForAxes( axes, v0, v1, v2, extents ) { } -const _box$2 = /*@__PURE__*/ new Box3(); +const _box$3 = /*@__PURE__*/ new Box3(); const _v1$6 = /*@__PURE__*/ new Vector3(); const _v2$3 = /*@__PURE__*/ new Vector3(); +/** + * An analytical 3D sphere defined by a center and radius. This class is mainly + * used as a Bounding Sphere for 3D objects. + */ class Sphere { - constructor( center = new Vector3(), radius = - 1 ) { + /** + * Constructs a new sphere. + * + * @param {Vector3} [center=(0,0,0)] - The center of the sphere + * @param {number} [radius=-1] - The radius of the sphere. + */ + constructor( center = new Vector3(), radius = -1 ) { + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSphere = true; + /** + * The center of the sphere + * + * @type {Vector3} + */ this.center = center; + + /** + * The radius of the sphere. + * + * @type {number} + */ this.radius = radius; } + /** + * Sets the sphere's components by copying the given values. + * + * @param {Vector3} center - The center. + * @param {number} radius - The radius. + * @return {Sphere} A reference to this sphere. + */ set( center, radius ) { this.center.copy( center ); @@ -5086,6 +10303,16 @@ class Sphere { } + /** + * Computes the minimum bounding sphere for list of points. + * If the optional center point is given, it is used as the sphere's + * center. Otherwise, the center of the axis-aligned bounding box + * encompassing the points is calculated. + * + * @param {Array} points - A list of points in 3D space. + * @param {Vector3} [optionalCenter] - The center of the sphere. + * @return {Sphere} A reference to this sphere. + */ setFromPoints( points, optionalCenter ) { const center = this.center; @@ -5096,7 +10323,7 @@ class Sphere { } else { - _box$2.setFromPoints( points ).getCenter( center ); + _box$3.setFromPoints( points ).getCenter( center ); } @@ -5114,6 +10341,12 @@ class Sphere { } + /** + * Copies the values of the given sphere to this instance. + * + * @param {Sphere} sphere - The sphere to copy. + * @return {Sphere} A reference to this sphere. + */ copy( sphere ) { this.center.copy( sphere.center ); @@ -5123,33 +10356,67 @@ class Sphere { } + /** + * Returns `true` if the sphere is empty (the radius set to a negative number). + * + * Spheres with a radius of `0` contain only their center point and are not + * considered to be empty. + * + * @return {boolean} Whether this sphere is empty or not. + */ isEmpty() { return ( this.radius < 0 ); } + /** + * Makes this sphere empty which means in encloses a zero space in 3D. + * + * @return {Sphere} A reference to this sphere. + */ makeEmpty() { this.center.set( 0, 0, 0 ); - this.radius = - 1; + this.radius = -1; return this; } + /** + * Returns `true` if this sphere contains the given point inclusive of + * the surface of the sphere. + * + * @param {Vector3} point - The point to check. + * @return {boolean} Whether this sphere contains the given point or not. + */ containsPoint( point ) { return ( point.distanceToSquared( this.center ) <= ( this.radius * this.radius ) ); } + /** + * Returns the closest distance from the boundary of the sphere to the + * given point. If the sphere contains the point, the distance will + * be negative. + * + * @param {Vector3} point - The point to compute the distance to. + * @return {number} The distance to the point. + */ distanceToPoint( point ) { return ( point.distanceTo( this.center ) - this.radius ); } + /** + * Returns `true` if this sphere intersects with the given one. + * + * @param {Sphere} sphere - The sphere to test. + * @return {boolean} Whether this sphere intersects with the given one or not. + */ intersectsSphere( sphere ) { const radiusSum = this.radius + sphere.radius; @@ -5158,18 +10425,39 @@ class Sphere { } + /** + * Returns `true` if this sphere intersects with the given box. + * + * @param {Box3} box - The box to test. + * @return {boolean} Whether this sphere intersects with the given box or not. + */ intersectsBox( box ) { return box.intersectsSphere( this ); } + /** + * Returns `true` if this sphere intersects with the given plane. + * + * @param {Plane} plane - The plane to test. + * @return {boolean} Whether this sphere intersects with the given plane or not. + */ intersectsPlane( plane ) { return Math.abs( plane.distanceToPoint( this.center ) ) <= this.radius; } + /** + * Clamps a point within the sphere. If the point is outside the sphere, it + * will clamp it to the closest point on the edge of the sphere. Points + * already inside the sphere will not be affected. + * + * @param {Vector3} point - The plane to clamp. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The clamped point. + */ clampPoint( point, target ) { const deltaLengthSq = this.center.distanceToSquared( point ); @@ -5187,6 +10475,12 @@ class Sphere { } + /** + * Returns a bounding box that encloses this sphere. + * + * @param {Box3} target - The target box that is used to store the method's result. + * @return {Box3} The bounding box that encloses this sphere. + */ getBoundingBox( target ) { if ( this.isEmpty() ) { @@ -5204,6 +10498,12 @@ class Sphere { } + /** + * Transforms this sphere with the given 4x4 transformation matrix. + * + * @param {Matrix4} matrix - The transformation matrix. + * @return {Sphere} A reference to this sphere. + */ applyMatrix4( matrix ) { this.center.applyMatrix4( matrix ); @@ -5213,6 +10513,12 @@ class Sphere { } + /** + * Translates the sphere's center by the given offset. + * + * @param {Vector3} offset - The offset. + * @return {Sphere} A reference to this sphere. + */ translate( offset ) { this.center.add( offset ); @@ -5221,6 +10527,12 @@ class Sphere { } + /** + * Expands the boundaries of this sphere to include the given point. + * + * @param {Vector3} point - The point to include. + * @return {Sphere} A reference to this sphere. + */ expandByPoint( point ) { if ( this.isEmpty() ) { @@ -5255,6 +10567,12 @@ class Sphere { } + /** + * Expands this sphere to enclose both the original sphere and the given sphere. + * + * @param {Sphere} sphere - The sphere to include. + * @return {Sphere} A reference to this sphere. + */ union( sphere ) { if ( sphere.isEmpty() ) { @@ -5289,21 +10607,60 @@ class Sphere { } + /** + * Returns `true` if this sphere is equal with the given one. + * + * @param {Sphere} sphere - The sphere to test for equality. + * @return {boolean} Whether this bounding sphere is equal with the given one. + */ equals( sphere ) { return sphere.center.equals( this.center ) && ( sphere.radius === this.radius ); } + /** + * Returns a new sphere with copied values from this instance. + * + * @return {Sphere} A clone of this instance. + */ clone() { return new this.constructor().copy( this ); } + /** + * Returns a serialized structure of the bounding sphere. + * + * @return {Object} Serialized structure with fields representing the object state. + */ + toJSON() { + + return { + radius: this.radius, + center: this.center.toArray() + }; + + } + + /** + * Returns a serialized structure of the bounding sphere. + * + * @param {Object} json - The serialized json to set the sphere from. + * @return {Box3} A reference to this bounding sphere. + */ + fromJSON( json ) { + + this.radius = json.radius; + this.center.fromArray( json.center ); + return this; + + } + } -const _vector$9 = /*@__PURE__*/ new Vector3(); +const _vector$a = /*@__PURE__*/ new Vector3(); const _segCenter = /*@__PURE__*/ new Vector3(); const _segDir = /*@__PURE__*/ new Vector3(); const _diff = /*@__PURE__*/ new Vector3(); @@ -5312,15 +10669,45 @@ const _edge1 = /*@__PURE__*/ new Vector3(); const _edge2 = /*@__PURE__*/ new Vector3(); const _normal$1 = /*@__PURE__*/ new Vector3(); +/** + * A ray that emits from an origin in a certain direction. The class is used by + * {@link Raycaster} to assist with raycasting. Raycasting is used for + * mouse picking (working out what objects in the 3D space the mouse is over) + * amongst other things. + */ class Ray { - constructor( origin = new Vector3(), direction = new Vector3( 0, 0, - 1 ) ) { + /** + * Constructs a new ray. + * + * @param {Vector3} [origin=(0,0,0)] - The origin of the ray. + * @param {Vector3} [direction=(0,0,-1)] - The (normalized) direction of the ray. + */ + constructor( origin = new Vector3(), direction = new Vector3( 0, 0, -1 ) ) { + /** + * The origin of the ray. + * + * @type {Vector3} + */ this.origin = origin; + + /** + * The (normalized) direction of the ray. + * + * @type {Vector3} + */ this.direction = direction; } + /** + * Sets the ray's components by copying the given values. + * + * @param {Vector3} origin - The origin. + * @param {Vector3} direction - The direction. + * @return {Ray} A reference to this ray. + */ set( origin, direction ) { this.origin.copy( origin ); @@ -5330,7 +10717,13 @@ class Ray { } - copy( ray ) { + /** + * Copies the values of the given ray to this instance. + * + * @param {Ray} ray - The ray to copy. + * @return {Ray} A reference to this ray. + */ + copy( ray ) { this.origin.copy( ray.origin ); this.direction.copy( ray.direction ); @@ -5339,12 +10732,25 @@ class Ray { } + /** + * Returns a vector that is located at a given distance along this ray. + * + * @param {number} t - The distance along the ray to retrieve a position for. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} A position on the ray. + */ at( t, target ) { return target.copy( this.origin ).addScaledVector( this.direction, t ); } + /** + * Adjusts the direction of the ray to point at the given vector in world space. + * + * @param {Vector3} v - The target position. + * @return {Ray} A reference to this ray. + */ lookAt( v ) { this.direction.copy( v ).sub( this.origin ).normalize(); @@ -5353,14 +10759,27 @@ class Ray { } + /** + * Shift the origin of this ray along its direction by the given distance. + * + * @param {number} t - The distance along the ray to interpolate. + * @return {Ray} A reference to this ray. + */ recast( t ) { - this.origin.copy( this.at( t, _vector$9 ) ); + this.origin.copy( this.at( t, _vector$a ) ); return this; } + /** + * Returns the point along this ray that is closest to the given point. + * + * @param {Vector3} point - A point in 3D space to get the closet location on the ray for. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The closest point on this ray. + */ closestPointToPoint( point, target ) { target.subVectors( point, this.origin ); @@ -5377,15 +10796,27 @@ class Ray { } + /** + * Returns the distance of the closest approach between this ray and the given point. + * + * @param {Vector3} point - A point in 3D space to compute the distance to. + * @return {number} The distance. + */ distanceToPoint( point ) { return Math.sqrt( this.distanceSqToPoint( point ) ); } + /** + * Returns the squared distance of the closest approach between this ray and the given point. + * + * @param {Vector3} point - A point in 3D space to compute the distance to. + * @return {number} The squared distance. + */ distanceSqToPoint( point ) { - const directionDistance = _vector$9.subVectors( point, this.origin ).dot( this.direction ); + const directionDistance = _vector$a.subVectors( point, this.origin ).dot( this.direction ); // point behind the ray @@ -5395,12 +10826,21 @@ class Ray { } - _vector$9.copy( this.origin ).addScaledVector( this.direction, directionDistance ); + _vector$a.copy( this.origin ).addScaledVector( this.direction, directionDistance ); - return _vector$9.distanceToSquared( point ); + return _vector$a.distanceToSquared( point ); } + /** + * Returns the squared distance between this ray and the given line segment. + * + * @param {Vector3} v0 - The start point of the line segment. + * @param {Vector3} v1 - The end point of the line segment. + * @param {Vector3} [optionalPointOnRay] - When provided, it receives the point on this ray that is closest to the segment. + * @param {Vector3} [optionalPointOnSegment] - When provided, it receives the point on the line segment that is closest to this ray. + * @return {number} The squared distance. + */ distanceSqToSegment( v0, v1, optionalPointOnRay, optionalPointOnSegment ) { // from https://github.com/pmjoniak/GeometricTools/blob/master/GTEngine/Include/Mathematics/GteDistRaySegment.h @@ -5520,11 +10960,19 @@ class Ray { } + /** + * Intersects this ray with the given sphere, returning the intersection + * point or `null` if there is no intersection. + * + * @param {Sphere} sphere - The sphere to intersect. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {?Vector3} The intersection point. + */ intersectSphere( sphere, target ) { - _vector$9.subVectors( sphere.center, this.origin ); - const tca = _vector$9.dot( this.direction ); - const d2 = _vector$9.dot( _vector$9 ) - tca * tca; + _vector$a.subVectors( sphere.center, this.origin ); + const tca = _vector$a.dot( this.direction ); + const d2 = _vector$a.dot( _vector$a ) - tca * tca; const radius2 = sphere.radius * sphere.radius; if ( d2 > radius2 ) return null; @@ -5550,12 +10998,27 @@ class Ray { } + /** + * Returns `true` if this ray intersects with the given sphere. + * + * @param {Sphere} sphere - The sphere to intersect. + * @return {boolean} Whether this ray intersects with the given sphere or not. + */ intersectsSphere( sphere ) { + if ( sphere.radius < 0 ) return false; // handle empty spheres, see #31187 + return this.distanceSqToPoint( sphere.center ) <= ( sphere.radius * sphere.radius ); } + /** + * Computes the distance from the ray's origin to the given plane. Returns `null` if the ray + * does not intersect with the plane. + * + * @param {Plane} plane - The plane to compute the distance to. + * @return {?number} Whether this ray intersects with the given sphere or not. + */ distanceToPlane( plane ) { const denominator = plane.normal.dot( this.direction ); @@ -5583,6 +11046,14 @@ class Ray { } + /** + * Intersects this ray with the given plane, returning the intersection + * point or `null` if there is no intersection. + * + * @param {Plane} plane - The plane to intersect. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {?Vector3} The intersection point. + */ intersectPlane( plane, target ) { const t = this.distanceToPlane( plane ); @@ -5597,6 +11068,12 @@ class Ray { } + /** + * Returns `true` if this ray intersects with the given plane. + * + * @param {Plane} plane - The plane to intersect. + * @return {boolean} Whether this ray intersects with the given plane or not. + */ intersectsPlane( plane ) { // check if the ray lies on the plane first @@ -5623,6 +11100,14 @@ class Ray { } + /** + * Intersects this ray with the given bounding box, returning the intersection + * point or `null` if there is no intersection. + * + * @param {Box3} box - The box to intersect. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {?Vector3} The intersection point. + */ intersectBox( box, target ) { let tmin, tmax, tymin, tymax, tzmin, tzmax; @@ -5689,12 +11174,29 @@ class Ray { } + /** + * Returns `true` if this ray intersects with the given box. + * + * @param {Box3} box - The box to intersect. + * @return {boolean} Whether this ray intersects with the given box or not. + */ intersectsBox( box ) { - return this.intersectBox( box, _vector$9 ) !== null; + return this.intersectBox( box, _vector$a ) !== null; } + /** + * Intersects this ray with the given triangle, returning the intersection + * point or `null` if there is no intersection. + * + * @param {Vector3} a - The first vertex of the triangle. + * @param {Vector3} b - The second vertex of the triangle. + * @param {Vector3} c - The third vertex of the triangle. + * @param {boolean} backfaceCulling - Whether to use backface culling or not. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {?Vector3} The intersection point. + */ intersectTriangle( a, b, c, backfaceCulling, target ) { // Compute the offset origin, edges, and normal. @@ -5720,7 +11222,7 @@ class Ray { } else if ( DdN < 0 ) { - sign = - 1; + sign = -1; DdN = - DdN; } else { @@ -5770,6 +11272,12 @@ class Ray { } + /** + * Transforms this ray with the given 4x4 transformation matrix. + * + * @param {Matrix4} matrix4 - The transformation matrix. + * @return {Ray} A reference to this ray. + */ applyMatrix4( matrix4 ) { this.origin.applyMatrix4( matrix4 ); @@ -5779,12 +11287,23 @@ class Ray { } + /** + * Returns `true` if this ray is equal with the given one. + * + * @param {Ray} ray - The ray to test for equality. + * @return {boolean} Whether this ray is equal with the given one. + */ equals( ray ) { return ray.origin.equals( this.origin ) && ray.direction.equals( this.direction ); } + /** + * Returns a new ray with copied values from this instance. + * + * @return {Ray} A clone of this instance. + */ clone() { return new this.constructor().copy( this ); @@ -5793,12 +11312,84 @@ class Ray { } +/** + * Represents a 4x4 matrix. + * + * The most common use of a 4x4 matrix in 3D computer graphics is as a transformation matrix. + * For an introduction to transformation matrices as used in WebGL, check out [this tutorial]{@link https://www.opengl-tutorial.org/beginners-tutorials/tutorial-3-matrices} + * + * This allows a 3D vector representing a point in 3D space to undergo + * transformations such as translation, rotation, shear, scale, reflection, + * orthogonal or perspective projection and so on, by being multiplied by the + * matrix. This is known as `applying` the matrix to the vector. + * + * A Note on Row-Major and Column-Major Ordering: + * + * The constructor and {@link Matrix3#set} method take arguments in + * [row-major]{@link https://en.wikipedia.org/wiki/Row-_and_column-major_order#Column-major_order} + * order, while internally they are stored in the {@link Matrix3#elements} array in column-major order. + * This means that calling: + * ```js + * const m = new THREE.Matrix4(); + * m.set( 11, 12, 13, 14, + * 21, 22, 23, 24, + * 31, 32, 33, 34, + * 41, 42, 43, 44 ); + * ``` + * will result in the elements array containing: + * ```js + * m.elements = [ 11, 21, 31, 41, + * 12, 22, 32, 42, + * 13, 23, 33, 43, + * 14, 24, 34, 44 ]; + * ``` + * and internally all calculations are performed using column-major ordering. + * However, as the actual ordering makes no difference mathematically and + * most people are used to thinking about matrices in row-major order, the + * three.js documentation shows matrices in row-major order. Just bear in + * mind that if you are reading the source code, you'll have to take the + * transpose of any matrices outlined here to make sense of the calculations. + */ class Matrix4 { + /** + * Constructs a new 4x4 matrix. The arguments are supposed to be + * in row-major order. If no arguments are provided, the constructor + * initializes the matrix as an identity matrix. + * + * @param {number} [n11] - 1-1 matrix element. + * @param {number} [n12] - 1-2 matrix element. + * @param {number} [n13] - 1-3 matrix element. + * @param {number} [n14] - 1-4 matrix element. + * @param {number} [n21] - 2-1 matrix element. + * @param {number} [n22] - 2-2 matrix element. + * @param {number} [n23] - 2-3 matrix element. + * @param {number} [n24] - 2-4 matrix element. + * @param {number} [n31] - 3-1 matrix element. + * @param {number} [n32] - 3-2 matrix element. + * @param {number} [n33] - 3-3 matrix element. + * @param {number} [n34] - 3-4 matrix element. + * @param {number} [n41] - 4-1 matrix element. + * @param {number} [n42] - 4-2 matrix element. + * @param {number} [n43] - 4-3 matrix element. + * @param {number} [n44] - 4-4 matrix element. + */ constructor( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ Matrix4.prototype.isMatrix4 = true; + /** + * A column-major list of matrix values. + * + * @type {Array} + */ this.elements = [ 1, 0, 0, 0, @@ -5816,6 +11407,28 @@ class Matrix4 { } + /** + * Sets the elements of the matrix.The arguments are supposed to be + * in row-major order. + * + * @param {number} [n11] - 1-1 matrix element. + * @param {number} [n12] - 1-2 matrix element. + * @param {number} [n13] - 1-3 matrix element. + * @param {number} [n14] - 1-4 matrix element. + * @param {number} [n21] - 2-1 matrix element. + * @param {number} [n22] - 2-2 matrix element. + * @param {number} [n23] - 2-3 matrix element. + * @param {number} [n24] - 2-4 matrix element. + * @param {number} [n31] - 3-1 matrix element. + * @param {number} [n32] - 3-2 matrix element. + * @param {number} [n33] - 3-3 matrix element. + * @param {number} [n34] - 3-4 matrix element. + * @param {number} [n41] - 4-1 matrix element. + * @param {number} [n42] - 4-2 matrix element. + * @param {number} [n43] - 4-3 matrix element. + * @param {number} [n44] - 4-4 matrix element. + * @return {Matrix4} A reference to this matrix. + */ set( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ) { const te = this.elements; @@ -5829,6 +11442,11 @@ class Matrix4 { } + /** + * Sets this matrix to the 4x4 identity matrix. + * + * @return {Matrix4} A reference to this matrix. + */ identity() { this.set( @@ -5844,12 +11462,23 @@ class Matrix4 { } + /** + * Returns a matrix with copied values from this instance. + * + * @return {Matrix4} A clone of this instance. + */ clone() { return new Matrix4().fromArray( this.elements ); } + /** + * Copies the values of the given matrix to this instance. + * + * @param {Matrix4} m - The matrix to copy. + * @return {Matrix4} A reference to this matrix. + */ copy( m ) { const te = this.elements; @@ -5864,6 +11493,13 @@ class Matrix4 { } + /** + * Copies the translation component of the given matrix + * into this matrix's translation component. + * + * @param {Matrix4} m - The matrix to copy the translation component. + * @return {Matrix4} A reference to this matrix. + */ copyPosition( m ) { const te = this.elements, me = m.elements; @@ -5876,6 +11512,12 @@ class Matrix4 { } + /** + * Set the upper 3x3 elements of this matrix to the values of given 3x3 matrix. + * + * @param {Matrix3} m - The 3x3 matrix. + * @return {Matrix4} A reference to this matrix. + */ setFromMatrix3( m ) { const me = m.elements; @@ -5893,6 +11535,14 @@ class Matrix4 { } + /** + * Extracts the basis of this matrix into the three axis vectors provided. + * + * @param {Vector3} xAxis - The basis's x axis. + * @param {Vector3} yAxis - The basis's y axis. + * @param {Vector3} zAxis - The basis's z axis. + * @return {Matrix4} A reference to this matrix. + */ extractBasis( xAxis, yAxis, zAxis ) { xAxis.setFromMatrixColumn( this, 0 ); @@ -5903,6 +11553,14 @@ class Matrix4 { } + /** + * Sets the given basis vectors to this matrix. + * + * @param {Vector3} xAxis - The basis's x axis. + * @param {Vector3} yAxis - The basis's y axis. + * @param {Vector3} zAxis - The basis's z axis. + * @return {Matrix4} A reference to this matrix. + */ makeBasis( xAxis, yAxis, zAxis ) { this.set( @@ -5916,10 +11574,17 @@ class Matrix4 { } + /** + * Extracts the rotation component of the given matrix + * into this matrix's rotation component. + * + * Note: This method does not support reflection matrices. + * + * @param {Matrix4} m - The matrix. + * @return {Matrix4} A reference to this matrix. + */ extractRotation( m ) { - // this method does not support reflection matrices - const te = this.elements; const me = m.elements; @@ -5951,6 +11616,16 @@ class Matrix4 { } + /** + * Sets the rotation component (the upper left 3x3 matrix) of this matrix to + * the rotation specified by the given Euler angles. The rest of + * the matrix is set to the identity. Depending on the {@link Euler#order}, + * there are six possible outcomes. See [this page]{@link https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix} + * for a complete list. + * + * @param {Euler} euler - The Euler angles. + * @return {Matrix4} A reference to this matrix. + */ makeRotationFromEuler( euler ) { const te = this.elements; @@ -6073,12 +11748,29 @@ class Matrix4 { } + /** + * Sets the rotation component of this matrix to the rotation specified by + * the given Quaternion as outlined [here]{@link https://en.wikipedia.org/wiki/Rotation_matrix#Quaternion} + * The rest of the matrix is set to the identity. + * + * @param {Quaternion} q - The Quaternion. + * @return {Matrix4} A reference to this matrix. + */ makeRotationFromQuaternion( q ) { return this.compose( _zero, q, _one ); } + /** + * Sets the rotation component of the transformation matrix, looking from `eye` towards + * `target`, and oriented by the up-direction. + * + * @param {Vector3} eye - The eye vector. + * @param {Vector3} target - The target vector. + * @param {Vector3} up - The up vector. + * @return {Matrix4} A reference to this matrix. + */ lookAt( eye, target, up ) { const te = this.elements; @@ -6126,18 +11818,38 @@ class Matrix4 { } + /** + * Post-multiplies this matrix by the given 4x4 matrix. + * + * @param {Matrix4} m - The matrix to multiply with. + * @return {Matrix4} A reference to this matrix. + */ multiply( m ) { return this.multiplyMatrices( this, m ); } + /** + * Pre-multiplies this matrix by the given 4x4 matrix. + * + * @param {Matrix4} m - The matrix to multiply with. + * @return {Matrix4} A reference to this matrix. + */ premultiply( m ) { return this.multiplyMatrices( m, this ); } + /** + * Multiples the given 4x4 matrices and stores the result + * in this matrix. + * + * @param {Matrix4} a - The first matrix. + * @param {Matrix4} b - The second matrix. + * @return {Matrix4} A reference to this matrix. + */ multiplyMatrices( a, b ) { const ae = a.elements; @@ -6178,6 +11890,12 @@ class Matrix4 { } + /** + * Multiplies every component of the matrix by the given scalar. + * + * @param {number} s - The scalar. + * @return {Matrix4} A reference to this matrix. + */ multiplyScalar( s ) { const te = this.elements; @@ -6191,6 +11909,13 @@ class Matrix4 { } + /** + * Computes and returns the determinant of this matrix. + * + * Based on the method outlined [here]{@link http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.html}. + * + * @return {number} The determinant. + */ determinant() { const te = this.elements; @@ -6201,7 +11926,6 @@ class Matrix4 { const n41 = te[ 3 ], n42 = te[ 7 ], n43 = te[ 11 ], n44 = te[ 15 ]; //TODO: make this more efficient - //( based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm ) return ( n41 * ( @@ -6241,6 +11965,11 @@ class Matrix4 { } + /** + * Transposes this matrix in place. + * + * @return {Matrix4} A reference to this matrix. + */ transpose() { const te = this.elements; @@ -6258,6 +11987,15 @@ class Matrix4 { } + /** + * Sets the position component for this matrix from the given vector, + * without affecting the rest of the matrix. + * + * @param {number|Vector3} x - The x component of the vector or alternatively the vector object. + * @param {number} y - The y component of the vector. + * @param {number} z - The z component of the vector. + * @return {Matrix4} A reference to this matrix. + */ setPosition( x, y, z ) { const te = this.elements; @@ -6280,6 +12018,13 @@ class Matrix4 { } + /** + * Inverts this matrix, using the [analytic method]{@link https://en.wikipedia.org/wiki/Invertible_matrix#Analytic_solution}. + * You can not invert with a determinant of zero. If you attempt this, the method produces + * a zero matrix instead. + * + * @return {Matrix4} A reference to this matrix. + */ invert() { // based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm @@ -6325,6 +12070,12 @@ class Matrix4 { } + /** + * Multiplies the columns of this matrix by the given vector. + * + * @param {Vector3} v - The scale vector. + * @return {Matrix4} A reference to this matrix. + */ scale( v ) { const te = this.elements; @@ -6339,6 +12090,11 @@ class Matrix4 { } + /** + * Gets the maximum scale value of the three axes. + * + * @return {number} The maximum scale. + */ getMaxScaleOnAxis() { const te = this.elements; @@ -6351,21 +12107,51 @@ class Matrix4 { } + /** + * Sets this matrix as a translation transform from the given vector. + * + * @param {number|Vector3} x - The amount to translate in the X axis or alternatively a translation vector. + * @param {number} y - The amount to translate in the Y axis. + * @param {number} z - The amount to translate in the z axis. + * @return {Matrix4} A reference to this matrix. + */ makeTranslation( x, y, z ) { - this.set( + if ( x.isVector3 ) { - 1, 0, 0, x, - 0, 1, 0, y, - 0, 0, 1, z, - 0, 0, 0, 1 + this.set( - ); + 1, 0, 0, x.x, + 0, 1, 0, x.y, + 0, 0, 1, x.z, + 0, 0, 0, 1 + + ); + + } else { + + this.set( + + 1, 0, 0, x, + 0, 1, 0, y, + 0, 0, 1, z, + 0, 0, 0, 1 + + ); + + } return this; } + /** + * Sets this matrix as a rotational transformation around the X axis by + * the given angle. + * + * @param {number} theta - The rotation in radians. + * @return {Matrix4} A reference to this matrix. + */ makeRotationX( theta ) { const c = Math.cos( theta ), s = Math.sin( theta ); @@ -6383,6 +12169,13 @@ class Matrix4 { } + /** + * Sets this matrix as a rotational transformation around the Y axis by + * the given angle. + * + * @param {number} theta - The rotation in radians. + * @return {Matrix4} A reference to this matrix. + */ makeRotationY( theta ) { const c = Math.cos( theta ), s = Math.sin( theta ); @@ -6400,6 +12193,13 @@ class Matrix4 { } + /** + * Sets this matrix as a rotational transformation around the Z axis by + * the given angle. + * + * @param {number} theta - The rotation in radians. + * @return {Matrix4} A reference to this matrix. + */ makeRotationZ( theta ) { const c = Math.cos( theta ), s = Math.sin( theta ); @@ -6417,6 +12217,17 @@ class Matrix4 { } + /** + * Sets this matrix as a rotational transformation around the given axis by + * the given angle. + * + * This is a somewhat controversial but mathematically sound alternative to + * rotating via Quaternions. See the discussion [here]{@link https://www.gamedev.net/articles/programming/math-and-physics/do-we-really-need-quaternions-r1199}. + * + * @param {Vector3} axis - The normalized rotation axis. + * @param {number} angle - The rotation in radians. + * @return {Matrix4} A reference to this matrix. + */ makeRotationAxis( axis, angle ) { // Based on http://www.gamedev.net/reference/articles/article1199.asp @@ -6440,6 +12251,14 @@ class Matrix4 { } + /** + * Sets this matrix as a scale transformation. + * + * @param {number} x - The amount to scale in the X axis. + * @param {number} y - The amount to scale in the Y axis. + * @param {number} z - The amount to scale in the Z axis. + * @return {Matrix4} A reference to this matrix. + */ makeScale( x, y, z ) { this.set( @@ -6455,6 +12274,17 @@ class Matrix4 { } + /** + * Sets this matrix as a shear transformation. + * + * @param {number} xy - The amount to shear X by Y. + * @param {number} xz - The amount to shear X by Z. + * @param {number} yx - The amount to shear Y by X. + * @param {number} yz - The amount to shear Y by Z. + * @param {number} zx - The amount to shear Z by X. + * @param {number} zy - The amount to shear Z by Y. + * @return {Matrix4} A reference to this matrix. + */ makeShear( xy, xz, yx, yz, zx, zy ) { this.set( @@ -6470,6 +12300,15 @@ class Matrix4 { } + /** + * Sets this matrix to the transformation composed of the given position, + * rotation (Quaternion) and scale. + * + * @param {Vector3} position - The position vector. + * @param {Quaternion} quaternion - The rotation as a Quaternion. + * @param {Vector3} scale - The scale vector. + * @return {Matrix4} A reference to this matrix. + */ compose( position, quaternion, scale ) { const te = this.elements; @@ -6506,6 +12345,19 @@ class Matrix4 { } + /** + * Decomposes this matrix into its position, rotation and scale components + * and provides the result in the given objects. + * + * Note: Not all matrices are decomposable in this way. For example, if an + * object has a non-uniformly scaled parent, then the object's world matrix + * may not be decomposable, and this method may not be appropriate. + * + * @param {Vector3} position - The position vector. + * @param {Quaternion} quaternion - The rotation as a Quaternion. + * @param {Vector3} scale - The scale vector. + * @return {Matrix4} A reference to this matrix. + */ decompose( position, quaternion, scale ) { const te = this.elements; @@ -6523,25 +12375,25 @@ class Matrix4 { position.z = te[ 14 ]; // scale the rotation part - _m1$2.copy( this ); + _m1$4.copy( this ); const invSX = 1 / sx; const invSY = 1 / sy; const invSZ = 1 / sz; - _m1$2.elements[ 0 ] *= invSX; - _m1$2.elements[ 1 ] *= invSX; - _m1$2.elements[ 2 ] *= invSX; + _m1$4.elements[ 0 ] *= invSX; + _m1$4.elements[ 1 ] *= invSX; + _m1$4.elements[ 2 ] *= invSX; - _m1$2.elements[ 4 ] *= invSY; - _m1$2.elements[ 5 ] *= invSY; - _m1$2.elements[ 6 ] *= invSY; + _m1$4.elements[ 4 ] *= invSY; + _m1$4.elements[ 5 ] *= invSY; + _m1$4.elements[ 6 ] *= invSY; - _m1$2.elements[ 8 ] *= invSZ; - _m1$2.elements[ 9 ] *= invSZ; - _m1$2.elements[ 10 ] *= invSZ; + _m1$4.elements[ 8 ] *= invSZ; + _m1$4.elements[ 9 ] *= invSZ; + _m1$4.elements[ 10 ] *= invSZ; - quaternion.setFromRotationMatrix( _m1$2 ); + quaternion.setFromRotationMatrix( _m1$4 ); scale.x = sx; scale.y = sy; @@ -6551,7 +12403,20 @@ class Matrix4 { } - makePerspective( left, right, top, bottom, near, far ) { + /** + * Creates a perspective projection matrix. This is used internally by + * {@link PerspectiveCamera#updateProjectionMatrix}. + + * @param {number} left - Left boundary of the viewing frustum at the near plane. + * @param {number} right - Right boundary of the viewing frustum at the near plane. + * @param {number} top - Top boundary of the viewing frustum at the near plane. + * @param {number} bottom - Bottom boundary of the viewing frustum at the near plane. + * @param {number} near - The distance from the camera to the near plane. + * @param {number} far - The distance from the camera to the far plane. + * @param {(WebGLCoordinateSystem|WebGPUCoordinateSystem)} [coordinateSystem=WebGLCoordinateSystem] - The coordinate system. + * @return {Matrix4} A reference to this matrix. + */ + makePerspective( left, right, top, bottom, near, far, coordinateSystem = WebGLCoordinateSystem ) { const te = this.elements; const x = 2 * near / ( right - left ); @@ -6559,19 +12424,48 @@ class Matrix4 { const a = ( right + left ) / ( right - left ); const b = ( top + bottom ) / ( top - bottom ); - const c = - ( far + near ) / ( far - near ); - const d = - 2 * far * near / ( far - near ); - te[ 0 ] = x; te[ 4 ] = 0; te[ 8 ] = a; te[ 12 ] = 0; - te[ 1 ] = 0; te[ 5 ] = y; te[ 9 ] = b; te[ 13 ] = 0; - te[ 2 ] = 0; te[ 6 ] = 0; te[ 10 ] = c; te[ 14 ] = d; - te[ 3 ] = 0; te[ 7 ] = 0; te[ 11 ] = - 1; te[ 15 ] = 0; + let c, d; + + if ( coordinateSystem === WebGLCoordinateSystem ) { + + c = - ( far + near ) / ( far - near ); + d = ( -2 * far * near ) / ( far - near ); + + } else if ( coordinateSystem === WebGPUCoordinateSystem ) { + + c = - far / ( far - near ); + d = ( - far * near ) / ( far - near ); + + } else { + + throw new Error( 'THREE.Matrix4.makePerspective(): Invalid coordinate system: ' + coordinateSystem ); + + } + + te[ 0 ] = x; te[ 4 ] = 0; te[ 8 ] = a; te[ 12 ] = 0; + te[ 1 ] = 0; te[ 5 ] = y; te[ 9 ] = b; te[ 13 ] = 0; + te[ 2 ] = 0; te[ 6 ] = 0; te[ 10 ] = c; te[ 14 ] = d; + te[ 3 ] = 0; te[ 7 ] = 0; te[ 11 ] = -1; te[ 15 ] = 0; return this; } - makeOrthographic( left, right, top, bottom, near, far ) { + /** + * Creates a orthographic projection matrix. This is used internally by + * {@link OrthographicCamera#updateProjectionMatrix}. + + * @param {number} left - Left boundary of the viewing frustum at the near plane. + * @param {number} right - Right boundary of the viewing frustum at the near plane. + * @param {number} top - Top boundary of the viewing frustum at the near plane. + * @param {number} bottom - Bottom boundary of the viewing frustum at the near plane. + * @param {number} near - The distance from the camera to the near plane. + * @param {number} far - The distance from the camera to the far plane. + * @param {(WebGLCoordinateSystem|WebGPUCoordinateSystem)} [coordinateSystem=WebGLCoordinateSystem] - The coordinate system. + * @return {Matrix4} A reference to this matrix. + */ + makeOrthographic( left, right, top, bottom, near, far, coordinateSystem = WebGLCoordinateSystem ) { const te = this.elements; const w = 1.0 / ( right - left ); @@ -6580,17 +12474,40 @@ class Matrix4 { const x = ( right + left ) * w; const y = ( top + bottom ) * h; - const z = ( far + near ) * p; - te[ 0 ] = 2 * w; te[ 4 ] = 0; te[ 8 ] = 0; te[ 12 ] = - x; - te[ 1 ] = 0; te[ 5 ] = 2 * h; te[ 9 ] = 0; te[ 13 ] = - y; - te[ 2 ] = 0; te[ 6 ] = 0; te[ 10 ] = - 2 * p; te[ 14 ] = - z; - te[ 3 ] = 0; te[ 7 ] = 0; te[ 11 ] = 0; te[ 15 ] = 1; + let z, zInv; + + if ( coordinateSystem === WebGLCoordinateSystem ) { + + z = ( far + near ) * p; + zInv = -2 * p; + + } else if ( coordinateSystem === WebGPUCoordinateSystem ) { + + z = near * p; + zInv = -1 * p; + + } else { + + throw new Error( 'THREE.Matrix4.makeOrthographic(): Invalid coordinate system: ' + coordinateSystem ); + + } + + te[ 0 ] = 2 * w; te[ 4 ] = 0; te[ 8 ] = 0; te[ 12 ] = - x; + te[ 1 ] = 0; te[ 5 ] = 2 * h; te[ 9 ] = 0; te[ 13 ] = - y; + te[ 2 ] = 0; te[ 6 ] = 0; te[ 10 ] = zInv; te[ 14 ] = - z; + te[ 3 ] = 0; te[ 7 ] = 0; te[ 11 ] = 0; te[ 15 ] = 1; return this; } + /** + * Returns `true` if this matrix is equal with the given one. + * + * @param {Matrix4} matrix - The matrix to test for equality. + * @return {boolean} Whether this matrix is equal with the given one. + */ equals( matrix ) { const te = this.elements; @@ -6606,6 +12523,13 @@ class Matrix4 { } + /** + * Sets the elements of the matrix from the given array. + * + * @param {Array} array - The matrix elements in column-major order. + * @param {number} [offset=0] - Index of the first element in the array. + * @return {Matrix4} A reference to this matrix. + */ fromArray( array, offset = 0 ) { for ( let i = 0; i < 16; i ++ ) { @@ -6618,6 +12542,14 @@ class Matrix4 { } + /** + * Writes the elements of this matrix to the given array. If no array is provided, + * the method returns a new instance. + * + * @param {Array} [array=[]] - The target array holding the matrix elements in column-major order. + * @param {number} [offset=0] - Index of the first element in the array. + * @return {Array} The matrix elements in column-major order. + */ toArray( array = [], offset = 0 ) { const te = this.elements; @@ -6649,20 +12581,51 @@ class Matrix4 { } const _v1$5 = /*@__PURE__*/ new Vector3(); -const _m1$2 = /*@__PURE__*/ new Matrix4(); +const _m1$4 = /*@__PURE__*/ new Matrix4(); const _zero = /*@__PURE__*/ new Vector3( 0, 0, 0 ); const _one = /*@__PURE__*/ new Vector3( 1, 1, 1 ); const _x = /*@__PURE__*/ new Vector3(); const _y = /*@__PURE__*/ new Vector3(); const _z = /*@__PURE__*/ new Vector3(); -const _matrix = /*@__PURE__*/ new Matrix4(); +const _matrix$2 = /*@__PURE__*/ new Matrix4(); const _quaternion$3 = /*@__PURE__*/ new Quaternion(); +/** + * A class representing Euler angles. + * + * Euler angles describe a rotational transformation by rotating an object on + * its various axes in specified amounts per axis, and a specified axis + * order. + * + * Iterating through an instance will yield its components (x, y, z, + * order) in the corresponding order. + * + * ```js + * const a = new THREE.Euler( 0, 1, 1.57, 'XYZ' ); + * const b = new THREE.Vector3( 1, 0, 1 ); + * b.applyEuler(a); + * ``` + */ class Euler { + /** + * Constructs a new euler instance. + * + * @param {number} [x=0] - The angle of the x axis in radians. + * @param {number} [y=0] - The angle of the y axis in radians. + * @param {number} [z=0] - The angle of the z axis in radians. + * @param {string} [order=Euler.DEFAULT_ORDER] - A string representing the order that the rotations are applied. + */ constructor( x = 0, y = 0, z = 0, order = Euler.DEFAULT_ORDER ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isEuler = true; this._x = x; @@ -6672,6 +12635,12 @@ class Euler { } + /** + * The angle of the x axis in radians. + * + * @type {number} + * @default 0 + */ get x() { return this._x; @@ -6685,6 +12654,12 @@ class Euler { } + /** + * The angle of the y axis in radians. + * + * @type {number} + * @default 0 + */ get y() { return this._y; @@ -6698,6 +12673,12 @@ class Euler { } + /** + * The angle of the z axis in radians. + * + * @type {number} + * @default 0 + */ get z() { return this._z; @@ -6711,6 +12692,12 @@ class Euler { } + /** + * A string representing the order that the rotations are applied. + * + * @type {string} + * @default 'XYZ' + */ get order() { return this._order; @@ -6724,6 +12711,15 @@ class Euler { } + /** + * Sets the Euler components. + * + * @param {number} x - The angle of the x axis in radians. + * @param {number} y - The angle of the y axis in radians. + * @param {number} z - The angle of the z axis in radians. + * @param {string} [order] - A string representing the order that the rotations are applied. + * @return {Euler} A reference to this Euler instance. + */ set( x, y, z, order = this._order ) { this._x = x; @@ -6737,12 +12733,23 @@ class Euler { } + /** + * Returns a new Euler instance with copied values from this instance. + * + * @return {Euler} A clone of this instance. + */ clone() { return new this.constructor( this._x, this._y, this._z, this._order ); } + /** + * Copies the values of the given Euler instance to this instance. + * + * @param {Euler} euler - The Euler instance to copy. + * @return {Euler} A reference to this Euler instance. + */ copy( euler ) { this._x = euler._x; @@ -6756,10 +12763,16 @@ class Euler { } + /** + * Sets the angles of this Euler instance from a pure rotation matrix. + * + * @param {Matrix4} m - A 4x4 matrix of which the upper 3x3 of matrix is a pure rotation matrix (i.e. unscaled). + * @param {string} [order] - A string representing the order that the rotations are applied. + * @param {boolean} [update=true] - Whether the internal `onChange` callback should be executed or not. + * @return {Euler} A reference to this Euler instance. + */ setFromRotationMatrix( m, order = this._order, update = true ) { - // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) - const te = m.elements; const m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ]; const m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ]; @@ -6769,7 +12782,7 @@ class Euler { case 'XYZ': - this._y = Math.asin( clamp( m13, - 1, 1 ) ); + this._y = Math.asin( clamp( m13, -1, 1 ) ); if ( Math.abs( m13 ) < 0.9999999 ) { @@ -6787,7 +12800,7 @@ class Euler { case 'YXZ': - this._x = Math.asin( - clamp( m23, - 1, 1 ) ); + this._x = Math.asin( - clamp( m23, -1, 1 ) ); if ( Math.abs( m23 ) < 0.9999999 ) { @@ -6805,7 +12818,7 @@ class Euler { case 'ZXY': - this._x = Math.asin( clamp( m32, - 1, 1 ) ); + this._x = Math.asin( clamp( m32, -1, 1 ) ); if ( Math.abs( m32 ) < 0.9999999 ) { @@ -6823,7 +12836,7 @@ class Euler { case 'ZYX': - this._y = Math.asin( - clamp( m31, - 1, 1 ) ); + this._y = Math.asin( - clamp( m31, -1, 1 ) ); if ( Math.abs( m31 ) < 0.9999999 ) { @@ -6841,7 +12854,7 @@ class Euler { case 'YZX': - this._z = Math.asin( clamp( m21, - 1, 1 ) ); + this._z = Math.asin( clamp( m21, -1, 1 ) ); if ( Math.abs( m21 ) < 0.9999999 ) { @@ -6859,7 +12872,7 @@ class Euler { case 'XZY': - this._z = Math.asin( - clamp( m12, - 1, 1 ) ); + this._z = Math.asin( - clamp( m12, -1, 1 ) ); if ( Math.abs( m12 ) < 0.9999999 ) { @@ -6889,36 +12902,73 @@ class Euler { } + /** + * Sets the angles of this Euler instance from a normalized quaternion. + * + * @param {Quaternion} q - A normalized Quaternion. + * @param {string} [order] - A string representing the order that the rotations are applied. + * @param {boolean} [update=true] - Whether the internal `onChange` callback should be executed or not. + * @return {Euler} A reference to this Euler instance. + */ setFromQuaternion( q, order, update ) { - _matrix.makeRotationFromQuaternion( q ); + _matrix$2.makeRotationFromQuaternion( q ); - return this.setFromRotationMatrix( _matrix, order, update ); + return this.setFromRotationMatrix( _matrix$2, order, update ); } + /** + * Sets the angles of this Euler instance from the given vector. + * + * @param {Vector3} v - The vector. + * @param {string} [order] - A string representing the order that the rotations are applied. + * @return {Euler} A reference to this Euler instance. + */ setFromVector3( v, order = this._order ) { return this.set( v.x, v.y, v.z, order ); } + /** + * Resets the euler angle with a new order by creating a quaternion from this + * euler angle and then setting this euler angle with the quaternion and the + * new order. + * + * Warning: This discards revolution information. + * + * @param {string} [newOrder] - A string representing the new order that the rotations are applied. + * @return {Euler} A reference to this Euler instance. + */ reorder( newOrder ) { - // WARNING: this discards revolution information -bhouston - _quaternion$3.setFromEuler( this ); return this.setFromQuaternion( _quaternion$3, newOrder ); } + /** + * Returns `true` if this Euler instance is equal with the given one. + * + * @param {Euler} euler - The Euler instance to test for equality. + * @return {boolean} Whether this Euler instance is equal with the given one. + */ equals( euler ) { return ( euler._x === this._x ) && ( euler._y === this._y ) && ( euler._z === this._z ) && ( euler._order === this._order ); } + /** + * Sets this Euler instance's components to values from the given array. The first three + * entries of the array are assign to the x,y and z components. An optional fourth entry + * defines the Euler order. + * + * @param {Array} array - An array holding the Euler component values. + * @return {Euler} A reference to this Euler instance. + */ fromArray( array ) { this._x = array[ 0 ]; @@ -6932,6 +12982,14 @@ class Euler { } + /** + * Writes the components of this Euler instance to the given array. If no array is provided, + * the method returns a new instance. + * + * @param {Array} [array=[]] - The target array holding the Euler components. + * @param {number} [offset=0] - Index of the first element in the array. + * @return {Array} The Euler components. + */ toArray( array = [], offset = 0 ) { array[ offset ] = this._x; @@ -6964,61 +13022,129 @@ class Euler { } +/** + * The default Euler angle order. + * + * @static + * @type {string} + * @default 'XYZ' + */ Euler.DEFAULT_ORDER = 'XYZ'; +/** + * A layers object assigns an 3D object to 1 or more of 32 + * layers numbered `0` to `31` - internally the layers are stored as a + * bit mask], and by default all 3D objects are a member of layer `0`. + * + * This can be used to control visibility - an object must share a layer with + * a camera to be visible when that camera's view is + * rendered. + * + * All classes that inherit from {@link Object3D} have an `layers` property which + * is an instance of this class. + */ class Layers { + /** + * Constructs a new layers instance, with membership + * initially set to layer `0`. + */ constructor() { + /** + * A bit mask storing which of the 32 layers this layers object is currently + * a member of. + * + * @type {number} + */ this.mask = 1 | 0; } - set( channel ) { + /** + * Sets membership to the given layer, and remove membership all other layers. + * + * @param {number} layer - The layer to set. + */ + set( layer ) { - this.mask = ( 1 << channel | 0 ) >>> 0; + this.mask = ( 1 << layer | 0 ) >>> 0; } - enable( channel ) { + /** + * Adds membership of the given layer. + * + * @param {number} layer - The layer to enable. + */ + enable( layer ) { - this.mask |= 1 << channel | 0; + this.mask |= 1 << layer | 0; } + /** + * Adds membership to all layers. + */ enableAll() { this.mask = 0xffffffff | 0; } - toggle( channel ) { + /** + * Toggles the membership of the given layer. + * + * @param {number} layer - The layer to toggle. + */ + toggle( layer ) { - this.mask ^= 1 << channel | 0; + this.mask ^= 1 << layer | 0; } - disable( channel ) { + /** + * Removes membership of the given layer. + * + * @param {number} layer - The layer to enable. + */ + disable( layer ) { - this.mask &= ~ ( 1 << channel | 0 ); + this.mask &= ~ ( 1 << layer | 0 ); } + /** + * Removes the membership from all layers. + */ disableAll() { this.mask = 0; } + /** + * Returns `true` if this and the given layers object have at least one + * layer in common. + * + * @param {Layers} layers - The layers to test. + * @return {boolean } Whether this and the given layers object have at least one layer in common or not. + */ test( layers ) { return ( this.mask & layers.mask ) !== 0; } - isEnabled( channel ) { + /** + * Returns `true` if the given layer is enabled. + * + * @param {number} layer - The layer to test. + * @return {boolean } Whether the given layer is enabled or not. + */ + isEnabled( layer ) { - return ( this.mask & ( 1 << channel | 0 ) ) !== 0; + return ( this.mask & ( 1 << layer | 0 ) ) !== 0; } @@ -7028,7 +13154,7 @@ let _object3DId = 0; const _v1$4 = /*@__PURE__*/ new Vector3(); const _q1 = /*@__PURE__*/ new Quaternion(); -const _m1$1 = /*@__PURE__*/ new Matrix4(); +const _m1$3 = /*@__PURE__*/ new Matrix4(); const _target = /*@__PURE__*/ new Vector3(); const _position$3 = /*@__PURE__*/ new Vector3(); @@ -7039,27 +13165,118 @@ const _xAxis = /*@__PURE__*/ new Vector3( 1, 0, 0 ); const _yAxis = /*@__PURE__*/ new Vector3( 0, 1, 0 ); const _zAxis = /*@__PURE__*/ new Vector3( 0, 0, 1 ); +/** + * Fires when the object has been added to its parent object. + * + * @event Object3D#added + * @type {Object} + */ const _addedEvent = { type: 'added' }; + +/** + * Fires when the object has been removed from its parent object. + * + * @event Object3D#removed + * @type {Object} + */ const _removedEvent = { type: 'removed' }; +/** + * Fires when a new child object has been added. + * + * @event Object3D#childadded + * @type {Object} + */ +const _childaddedEvent = { type: 'childadded', child: null }; + +/** + * Fires when a child object has been removed. + * + * @event Object3D#childremoved + * @type {Object} + */ +const _childremovedEvent = { type: 'childremoved', child: null }; + +/** + * This is the base class for most objects in three.js and provides a set of + * properties and methods for manipulating objects in 3D space. + * + * @augments EventDispatcher + */ class Object3D extends EventDispatcher { + /** + * Constructs a new 3D object. + */ constructor() { super(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isObject3D = true; + /** + * The ID of the 3D object. + * + * @name Object3D#id + * @type {number} + * @readonly + */ Object.defineProperty( this, 'id', { value: _object3DId ++ } ); + /** + * The UUID of the 3D object. + * + * @type {string} + * @readonly + */ this.uuid = generateUUID(); + /** + * The name of the 3D object. + * + * @type {string} + */ this.name = ''; + + /** + * The type property is used for detecting the object type + * in context of serialization/deserialization. + * + * @type {string} + * @readonly + */ this.type = 'Object3D'; + /** + * A reference to the parent object. + * + * @type {?Object3D} + * @default null + */ this.parent = null; + + /** + * An array holding the child 3D objects of this instance. + * + * @type {Array} + */ this.children = []; + /** + * Defines the `up` direction of the 3D object which influences + * the orientation via methods like {@link Object3D#lookAt}. + * + * The default values for all 3D objects is defined by `Object3D.DEFAULT_UP`. + * + * @type {Vector3} + */ this.up = Object3D.DEFAULT_UP.clone(); const position = new Vector3(); @@ -7083,61 +13300,268 @@ class Object3D extends EventDispatcher { quaternion._onChange( onQuaternionChange ); Object.defineProperties( this, { + /** + * Represents the object's local position. + * + * @name Object3D#position + * @type {Vector3} + * @default (0,0,0) + */ position: { configurable: true, enumerable: true, value: position }, + /** + * Represents the object's local rotation as Euler angles, in radians. + * + * @name Object3D#rotation + * @type {Euler} + * @default (0,0,0) + */ rotation: { configurable: true, enumerable: true, value: rotation }, + /** + * Represents the object's local rotation as Quaternions. + * + * @name Object3D#quaternion + * @type {Quaternion} + */ quaternion: { configurable: true, enumerable: true, value: quaternion }, + /** + * Represents the object's local scale. + * + * @name Object3D#scale + * @type {Vector3} + * @default (1,1,1) + */ scale: { configurable: true, enumerable: true, value: scale }, + /** + * Represents the object's model-view matrix. + * + * @name Object3D#modelViewMatrix + * @type {Matrix4} + */ modelViewMatrix: { value: new Matrix4() }, + /** + * Represents the object's normal matrix. + * + * @name Object3D#normalMatrix + * @type {Matrix3} + */ normalMatrix: { value: new Matrix3() } } ); + /** + * Represents the object's transformation matrix in local space. + * + * @type {Matrix4} + */ this.matrix = new Matrix4(); + + /** + * Represents the object's transformation matrix in world space. + * If the 3D object has no parent, then it's identical to the local transformation matrix + * + * @type {Matrix4} + */ this.matrixWorld = new Matrix4(); + /** + * When set to `true`, the engine automatically computes the local matrix from position, + * rotation and scale every frame. + * + * The default values for all 3D objects is defined by `Object3D.DEFAULT_MATRIX_AUTO_UPDATE`. + * + * @type {boolean} + * @default true + */ this.matrixAutoUpdate = Object3D.DEFAULT_MATRIX_AUTO_UPDATE; - this.matrixWorldNeedsUpdate = false; + /** + * When set to `true`, the engine automatically computes the world matrix from the current local + * matrix and the object's transformation hierarchy. + * + * The default values for all 3D objects is defined by `Object3D.DEFAULT_MATRIX_WORLD_AUTO_UPDATE`. + * + * @type {boolean} + * @default true + */ this.matrixWorldAutoUpdate = Object3D.DEFAULT_MATRIX_WORLD_AUTO_UPDATE; // checked by the renderer + /** + * When set to `true`, it calculates the world matrix in that frame and resets this property + * to `false`. + * + * @type {boolean} + * @default false + */ + this.matrixWorldNeedsUpdate = false; + + /** + * The layer membership of the 3D object. The 3D object is only visible if it has + * at least one layer in common with the camera in use. This property can also be + * used to filter out unwanted objects in ray-intersection tests when using {@link Raycaster}. + * + * @type {Layers} + */ this.layers = new Layers(); + + /** + * When set to `true`, the 3D object gets rendered. + * + * @type {boolean} + * @default true + */ this.visible = true; + /** + * When set to `true`, the 3D object gets rendered into shadow maps. + * + * @type {boolean} + * @default false + */ this.castShadow = false; + + /** + * When set to `true`, the 3D object is affected by shadows in the scene. + * + * @type {boolean} + * @default false + */ this.receiveShadow = false; + /** + * When set to `true`, the 3D object is honored by view frustum culling. + * + * @type {boolean} + * @default true + */ this.frustumCulled = true; + + /** + * This value allows the default rendering order of scene graph objects to be + * overridden although opaque and transparent objects remain sorted independently. + * When this property is set for an instance of {@link Group},all descendants + * objects will be sorted and rendered together. Sorting is from lowest to highest + * render order. + * + * @type {number} + * @default 0 + */ this.renderOrder = 0; + /** + * An array holding the animation clips of the 3D object. + * + * @type {Array} + */ this.animations = []; + /** + * Custom depth material to be used when rendering to the depth map. Can only be used + * in context of meshes. When shadow-casting with a {@link DirectionalLight} or {@link SpotLight}, + * if you are modifying vertex positions in the vertex shader you must specify a custom depth + * material for proper shadows. + * + * Only relevant in context of {@link WebGLRenderer}. + * + * @type {(Material|undefined)} + * @default undefined + */ + this.customDepthMaterial = undefined; + + /** + * Same as {@link Object3D#customDepthMaterial}, but used with {@link PointLight}. + * + * Only relevant in context of {@link WebGLRenderer}. + * + * @type {(Material|undefined)} + * @default undefined + */ + this.customDistanceMaterial = undefined; + + /** + * An object that can be used to store custom data about the 3D object. It + * should not hold references to functions as these will not be cloned. + * + * @type {Object} + */ this.userData = {}; } + /** + * A callback that is executed immediately before a 3D object is rendered to a shadow map. + * + * @param {Renderer|WebGLRenderer} renderer - The renderer. + * @param {Object3D} object - The 3D object. + * @param {Camera} camera - The camera that is used to render the scene. + * @param {Camera} shadowCamera - The shadow camera. + * @param {BufferGeometry} geometry - The 3D object's geometry. + * @param {Material} depthMaterial - The depth material. + * @param {Object} group - The geometry group data. + */ + onBeforeShadow( /* renderer, object, camera, shadowCamera, geometry, depthMaterial, group */ ) {} + + /** + * A callback that is executed immediately after a 3D object is rendered to a shadow map. + * + * @param {Renderer|WebGLRenderer} renderer - The renderer. + * @param {Object3D} object - The 3D object. + * @param {Camera} camera - The camera that is used to render the scene. + * @param {Camera} shadowCamera - The shadow camera. + * @param {BufferGeometry} geometry - The 3D object's geometry. + * @param {Material} depthMaterial - The depth material. + * @param {Object} group - The geometry group data. + */ + onAfterShadow( /* renderer, object, camera, shadowCamera, geometry, depthMaterial, group */ ) {} + + /** + * A callback that is executed immediately before a 3D object is rendered. + * + * @param {Renderer|WebGLRenderer} renderer - The renderer. + * @param {Object3D} object - The 3D object. + * @param {Camera} camera - The camera that is used to render the scene. + * @param {BufferGeometry} geometry - The 3D object's geometry. + * @param {Material} material - The 3D object's material. + * @param {Object} group - The geometry group data. + */ onBeforeRender( /* renderer, scene, camera, geometry, material, group */ ) {} + /** + * A callback that is executed immediately after a 3D object is rendered. + * + * @param {Renderer|WebGLRenderer} renderer - The renderer. + * @param {Object3D} object - The 3D object. + * @param {Camera} camera - The camera that is used to render the scene. + * @param {BufferGeometry} geometry - The 3D object's geometry. + * @param {Material} material - The 3D object's material. + * @param {Object} group - The geometry group data. + */ onAfterRender( /* renderer, scene, camera, geometry, material, group */ ) {} + /** + * Applies the given transformation matrix to the object and updates the object's position, + * rotation and scale. + * + * @param {Matrix4} matrix - The transformation matrix. + */ applyMatrix4( matrix ) { if ( this.matrixAutoUpdate ) this.updateMatrix(); @@ -7148,6 +13572,12 @@ class Object3D extends EventDispatcher { } + /** + * Applies a rotation represented by given the quaternion to the 3D object. + * + * @param {Quaternion} q - The quaternion. + * @return {Object3D} A reference to this instance. + */ applyQuaternion( q ) { this.quaternion.premultiply( q ); @@ -7156,6 +13586,12 @@ class Object3D extends EventDispatcher { } + /** + * Sets the given rotation represented as an axis/angle couple to the 3D object. + * + * @param {Vector3} axis - The (normalized) axis vector. + * @param {number} angle - The angle in radians. + */ setRotationFromAxisAngle( axis, angle ) { // assumes axis is normalized @@ -7164,12 +13600,23 @@ class Object3D extends EventDispatcher { } + /** + * Sets the given rotation represented as Euler angles to the 3D object. + * + * @param {Euler} euler - The Euler angles. + */ setRotationFromEuler( euler ) { this.quaternion.setFromEuler( euler, true ); } + /** + * Sets the given rotation represented as rotation matrix to the 3D object. + * + * @param {Matrix4} m - Although a 4x4 matrix is expected, the upper 3x3 portion must be + * a pure rotation matrix (i.e, unscaled). + */ setRotationFromMatrix( m ) { // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) @@ -7178,6 +13625,11 @@ class Object3D extends EventDispatcher { } + /** + * Sets the given rotation represented as a Quaternion to the 3D object. + * + * @param {Quaternion} q - The Quaternion + */ setRotationFromQuaternion( q ) { // assumes q is normalized @@ -7186,6 +13638,13 @@ class Object3D extends EventDispatcher { } + /** + * Rotates the 3D object along an axis in local space. + * + * @param {Vector3} axis - The (normalized) axis vector. + * @param {number} angle - The angle in radians. + * @return {Object3D} A reference to this instance. + */ rotateOnAxis( axis, angle ) { // rotate object on axis in object space @@ -7199,6 +13658,13 @@ class Object3D extends EventDispatcher { } + /** + * Rotates the 3D object along an axis in world space. + * + * @param {Vector3} axis - The (normalized) axis vector. + * @param {number} angle - The angle in radians. + * @return {Object3D} A reference to this instance. + */ rotateOnWorldAxis( axis, angle ) { // rotate object on axis in world space @@ -7213,24 +13679,49 @@ class Object3D extends EventDispatcher { } + /** + * Rotates the 3D object around its X axis in local space. + * + * @param {number} angle - The angle in radians. + * @return {Object3D} A reference to this instance. + */ rotateX( angle ) { return this.rotateOnAxis( _xAxis, angle ); } + /** + * Rotates the 3D object around its Y axis in local space. + * + * @param {number} angle - The angle in radians. + * @return {Object3D} A reference to this instance. + */ rotateY( angle ) { return this.rotateOnAxis( _yAxis, angle ); } + /** + * Rotates the 3D object around its Z axis in local space. + * + * @param {number} angle - The angle in radians. + * @return {Object3D} A reference to this instance. + */ rotateZ( angle ) { return this.rotateOnAxis( _zAxis, angle ); } + /** + * Translate the 3D object by a distance along the given axis in local space. + * + * @param {Vector3} axis - The (normalized) axis vector. + * @param {number} distance - The distance in world units. + * @return {Object3D} A reference to this instance. + */ translateOnAxis( axis, distance ) { // translate object by distance along axis in object space @@ -7244,24 +13735,48 @@ class Object3D extends EventDispatcher { } + /** + * Translate the 3D object by a distance along its X-axis in local space. + * + * @param {number} distance - The distance in world units. + * @return {Object3D} A reference to this instance. + */ translateX( distance ) { return this.translateOnAxis( _xAxis, distance ); } + /** + * Translate the 3D object by a distance along its Y-axis in local space. + * + * @param {number} distance - The distance in world units. + * @return {Object3D} A reference to this instance. + */ translateY( distance ) { return this.translateOnAxis( _yAxis, distance ); } + /** + * Translate the 3D object by a distance along its Z-axis in local space. + * + * @param {number} distance - The distance in world units. + * @return {Object3D} A reference to this instance. + */ translateZ( distance ) { return this.translateOnAxis( _zAxis, distance ); } + /** + * Converts the given vector from this 3D object's local space to world space. + * + * @param {Vector3} vector - The vector to convert. + * @return {Vector3} The converted vector. + */ localToWorld( vector ) { this.updateWorldMatrix( true, false ); @@ -7270,14 +13785,29 @@ class Object3D extends EventDispatcher { } + /** + * Converts the given vector from this 3D object's word space to local space. + * + * @param {Vector3} vector - The vector to convert. + * @return {Vector3} The converted vector. + */ worldToLocal( vector ) { this.updateWorldMatrix( true, false ); - return vector.applyMatrix4( _m1$1.copy( this.matrixWorld ).invert() ); + return vector.applyMatrix4( _m1$3.copy( this.matrixWorld ).invert() ); } + /** + * Rotates the object to face a point in world space. + * + * This method does not support objects having non-uniformly-scaled parent(s). + * + * @param {number|Vector3} x - The x coordinate in world space. Alternatively, a vector representing a position in world space + * @param {number} [y] - The y coordinate in world space. + * @param {number} [z] - The z coordinate in world space. + */ lookAt( x, y, z ) { // This method does not support objects having non-uniformly-scaled parent(s) @@ -7300,26 +13830,36 @@ class Object3D extends EventDispatcher { if ( this.isCamera || this.isLight ) { - _m1$1.lookAt( _position$3, _target, this.up ); + _m1$3.lookAt( _position$3, _target, this.up ); } else { - _m1$1.lookAt( _target, _position$3, this.up ); + _m1$3.lookAt( _target, _position$3, this.up ); } - this.quaternion.setFromRotationMatrix( _m1$1 ); + this.quaternion.setFromRotationMatrix( _m1$3 ); if ( parent ) { - _m1$1.extractRotation( parent.matrixWorld ); - _q1.setFromRotationMatrix( _m1$1 ); + _m1$3.extractRotation( parent.matrixWorld ); + _q1.setFromRotationMatrix( _m1$3 ); this.quaternion.premultiply( _q1.invert() ); } } + /** + * Adds the given 3D object as a child to this 3D object. An arbitrary number of + * objects may be added. Any current parent on an object passed in here will be + * removed, since an object can have at most one parent. + * + * @fires Object3D#added + * @fires Object3D#childadded + * @param {Object3D} object - The 3D object to add. + * @return {Object3D} A reference to this instance. + */ add( object ) { if ( arguments.length > 1 ) { @@ -7343,17 +13883,16 @@ class Object3D extends EventDispatcher { if ( object && object.isObject3D ) { - if ( object.parent !== null ) { - - object.parent.remove( object ); - - } - + object.removeFromParent(); object.parent = this; this.children.push( object ); object.dispatchEvent( _addedEvent ); + _childaddedEvent.child = object; + this.dispatchEvent( _childaddedEvent ); + _childaddedEvent.child = null; + } else { console.error( 'THREE.Object3D.add: object not an instance of THREE.Object3D.', object ); @@ -7364,6 +13903,15 @@ class Object3D extends EventDispatcher { } + /** + * Removes the given 3D object as child from this 3D object. + * An arbitrary number of objects may be removed. + * + * @fires Object3D#removed + * @fires Object3D#childremoved + * @param {Object3D} object - The 3D object to remove. + * @return {Object3D} A reference to this instance. + */ remove( object ) { if ( arguments.length > 1 ) { @@ -7380,19 +13928,30 @@ class Object3D extends EventDispatcher { const index = this.children.indexOf( object ); - if ( index !== - 1 ) { + if ( index !== -1 ) { object.parent = null; this.children.splice( index, 1 ); object.dispatchEvent( _removedEvent ); + _childremovedEvent.child = object; + this.dispatchEvent( _childremovedEvent ); + _childremovedEvent.child = null; + } return this; } + /** + * Removes this 3D object from its current parent. + * + * @fires Object3D#removed + * @fires Object3D#childremoved + * @return {Object3D} A reference to this instance. + */ removeFromParent() { const parent = this.parent; @@ -7407,25 +13966,28 @@ class Object3D extends EventDispatcher { } + /** + * Removes all child objects. + * + * @fires Object3D#removed + * @fires Object3D#childremoved + * @return {Object3D} A reference to this instance. + */ clear() { - for ( let i = 0; i < this.children.length; i ++ ) { - - const object = this.children[ i ]; - - object.parent = null; - - object.dispatchEvent( _removedEvent ); - - } - - this.children.length = 0; - - return this; - + return this.remove( ... this.children ); } + /** + * Adds the given 3D object as a child of this 3D object, while maintaining the object's world + * transform. This method does not support scene graphs having non-uniformly-scaled nodes(s). + * + * @fires Object3D#added + * @fires Object3D#childadded + * @param {Object3D} object - The 3D object to attach. + * @return {Object3D} A reference to this instance. + */ attach( object ) { // adds object as a child of this, while maintaining the object's world transform @@ -7434,38 +13996,68 @@ class Object3D extends EventDispatcher { this.updateWorldMatrix( true, false ); - _m1$1.copy( this.matrixWorld ).invert(); + _m1$3.copy( this.matrixWorld ).invert(); if ( object.parent !== null ) { object.parent.updateWorldMatrix( true, false ); - _m1$1.multiply( object.parent.matrixWorld ); + _m1$3.multiply( object.parent.matrixWorld ); } - object.applyMatrix4( _m1$1 ); + object.applyMatrix4( _m1$3 ); - this.add( object ); + object.removeFromParent(); + object.parent = this; + this.children.push( object ); object.updateWorldMatrix( false, true ); + object.dispatchEvent( _addedEvent ); + + _childaddedEvent.child = object; + this.dispatchEvent( _childaddedEvent ); + _childaddedEvent.child = null; + return this; } + /** + * Searches through the 3D object and its children, starting with the 3D object + * itself, and returns the first with a matching ID. + * + * @param {number} id - The id. + * @return {Object3D|undefined} The found 3D object. Returns `undefined` if no 3D object has been found. + */ getObjectById( id ) { return this.getObjectByProperty( 'id', id ); } + /** + * Searches through the 3D object and its children, starting with the 3D object + * itself, and returns the first with a matching name. + * + * @param {string} name - The name. + * @return {Object3D|undefined} The found 3D object. Returns `undefined` if no 3D object has been found. + */ getObjectByName( name ) { return this.getObjectByProperty( 'name', name ); } + /** + * Searches through the 3D object and its children, starting with the 3D object + * itself, and returns the first with a matching property value. + * + * @param {string} name - The name of the property. + * @param {any} value - The value. + * @return {Object3D|undefined} The found 3D object. Returns `undefined` if no 3D object has been found. + */ getObjectByProperty( name, value ) { if ( this[ name ] === value ) return this; @@ -7487,21 +14079,24 @@ class Object3D extends EventDispatcher { } - getObjectsByProperty( name, value ) { - - let result = []; + /** + * Searches through the 3D object and its children, starting with the 3D object + * itself, and returns all 3D objects with a matching property value. + * + * @param {string} name - The name of the property. + * @param {any} value - The value. + * @param {Array} result - The method stores the result in this array. + * @return {Array} The found 3D objects. + */ + getObjectsByProperty( name, value, result = [] ) { if ( this[ name ] === value ) result.push( this ); - for ( let i = 0, l = this.children.length; i < l; i ++ ) { - - const childResult = this.children[ i ].getObjectsByProperty( name, value ); - - if ( childResult.length > 0 ) { + const children = this.children; - result = result.concat( childResult ); + for ( let i = 0, l = children.length; i < l; i ++ ) { - } + children[ i ].getObjectsByProperty( name, value, result ); } @@ -7509,6 +14104,12 @@ class Object3D extends EventDispatcher { } + /** + * Returns a vector representing the position of the 3D object in world space. + * + * @param {Vector3} target - The target vector the result is stored to. + * @return {Vector3} The 3D object's position in world space. + */ getWorldPosition( target ) { this.updateWorldMatrix( true, false ); @@ -7517,6 +14118,12 @@ class Object3D extends EventDispatcher { } + /** + * Returns a Quaternion representing the position of the 3D object in world space. + * + * @param {Quaternion} target - The target Quaternion the result is stored to. + * @return {Quaternion} The 3D object's rotation in world space. + */ getWorldQuaternion( target ) { this.updateWorldMatrix( true, false ); @@ -7527,6 +14134,12 @@ class Object3D extends EventDispatcher { } + /** + * Returns a vector representing the scale of the 3D object in world space. + * + * @param {Vector3} target - The target vector the result is stored to. + * @return {Vector3} The 3D object's scale in world space. + */ getWorldScale( target ) { this.updateWorldMatrix( true, false ); @@ -7537,6 +14150,12 @@ class Object3D extends EventDispatcher { } + /** + * Returns a vector representing the ("look") direction of the 3D object in world space. + * + * @param {Vector3} target - The target vector the result is stored to. + * @return {Vector3} The 3D object's direction in world space. + */ getWorldDirection( target ) { this.updateWorldMatrix( true, false ); @@ -7547,8 +14166,24 @@ class Object3D extends EventDispatcher { } + /** + * Abstract method to get intersections between a casted ray and this + * 3D object. Renderable 3D objects such as {@link Mesh}, {@link Line} or {@link Points} + * implement this method in order to use raycasting. + * + * @abstract + * @param {Raycaster} raycaster - The raycaster. + * @param {Array} intersects - An array holding the result of the method. + */ raycast( /* raycaster, intersects */ ) {} + /** + * Executes the callback on this 3D object and all descendants. + * + * Note: Modifying the scene graph inside the callback is discouraged. + * + * @param {Function} callback - A callback function that allows to process the current 3D object. + */ traverse( callback ) { callback( this ); @@ -7563,6 +14198,14 @@ class Object3D extends EventDispatcher { } + /** + * Like {@link Object3D#traverse}, but the callback will only be executed for visible 3D objects. + * Descendants of invisible 3D objects are not traversed. + * + * Note: Modifying the scene graph inside the callback is discouraged. + * + * @param {Function} callback - A callback function that allows to process the current 3D object. + */ traverseVisible( callback ) { if ( this.visible === false ) return; @@ -7579,6 +14222,13 @@ class Object3D extends EventDispatcher { } + /** + * Like {@link Object3D#traverse}, but the callback will only be executed for all ancestors. + * + * Note: Modifying the scene graph inside the callback is discouraged. + * + * @param {Function} callback - A callback function that allows to process the current 3D object. + */ traverseAncestors( callback ) { const parent = this.parent; @@ -7593,6 +14243,10 @@ class Object3D extends EventDispatcher { } + /** + * Updates the transformation matrix in local space by computing it from the current + * position, rotation and scale values. + */ updateMatrix() { this.matrix.compose( this.position, this.quaternion, this.scale ); @@ -7601,19 +14255,34 @@ class Object3D extends EventDispatcher { } + /** + * Updates the transformation matrix in world space of this 3D objects and its descendants. + * + * To ensure correct results, this method also recomputes the 3D object's transformation matrix in + * local space. The computation of the local and world matrix can be controlled with the + * {@link Object3D#matrixAutoUpdate} and {@link Object3D#matrixWorldAutoUpdate} flags which are both + * `true` by default. Set these flags to `false` if you need more control over the update matrix process. + * + * @param {boolean} [force=false] - When set to `true`, a recomputation of world matrices is forced even + * when {@link Object3D#matrixWorldAutoUpdate} is set to `false`. + */ updateMatrixWorld( force ) { if ( this.matrixAutoUpdate ) this.updateMatrix(); if ( this.matrixWorldNeedsUpdate || force ) { - if ( this.parent === null ) { + if ( this.matrixWorldAutoUpdate === true ) { - this.matrixWorld.copy( this.matrix ); + if ( this.parent === null ) { - } else { + this.matrixWorld.copy( this.matrix ); - this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix ); + } else { + + this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix ); + + } } @@ -7623,7 +14292,7 @@ class Object3D extends EventDispatcher { } - // update children + // make sure descendants are updated if required const children = this.children; @@ -7631,21 +14300,24 @@ class Object3D extends EventDispatcher { const child = children[ i ]; - if ( child.matrixWorldAutoUpdate === true || force === true ) { - - child.updateMatrixWorld( force ); - - } + child.updateMatrixWorld( force ); } } + /** + * An alternative version of {@link Object3D#updateMatrixWorld} with more control over the + * update of ancestor and descendant nodes. + * + * @param {boolean} [updateParents=false] Whether ancestor nodes should be updated or not. + * @param {boolean} [updateChildren=false] Whether descendant nodes should be updated or not. + */ updateWorldMatrix( updateParents, updateChildren ) { const parent = this.parent; - if ( updateParents === true && parent !== null && parent.matrixWorldAutoUpdate === true ) { + if ( updateParents === true && parent !== null ) { parent.updateWorldMatrix( true, false ); @@ -7653,17 +14325,21 @@ class Object3D extends EventDispatcher { if ( this.matrixAutoUpdate ) this.updateMatrix(); - if ( this.parent === null ) { + if ( this.matrixWorldAutoUpdate === true ) { - this.matrixWorld.copy( this.matrix ); + if ( this.parent === null ) { - } else { + this.matrixWorld.copy( this.matrix ); - this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix ); + } else { + + this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix ); + + } } - // update children + // make sure descendants are updated if ( updateChildren === true ) { @@ -7673,11 +14349,7 @@ class Object3D extends EventDispatcher { const child = children[ i ]; - if ( child.matrixWorldAutoUpdate === true ) { - - child.updateWorldMatrix( false, true ); - - } + child.updateWorldMatrix( false, true ); } @@ -7685,6 +14357,13 @@ class Object3D extends EventDispatcher { } + /** + * Serializes the 3D object into JSON. + * + * @param {?(Object|string)} meta - An optional value holding meta information about the serialization. + * @return {Object} A JSON object representing the serialized 3D object. + * @see {@link ObjectLoader#parse} + */ toJSON( meta ) { // meta is a string when called from JSON.stringify @@ -7710,7 +14389,7 @@ class Object3D extends EventDispatcher { }; output.metadata = { - version: 4.6, + version: 4.7, type: 'Object', generator: 'Object3D.toJSON' }; @@ -7749,6 +14428,59 @@ class Object3D extends EventDispatcher { } + if ( this.isBatchedMesh ) { + + object.type = 'BatchedMesh'; + object.perObjectFrustumCulled = this.perObjectFrustumCulled; + object.sortObjects = this.sortObjects; + + object.drawRanges = this._drawRanges; + object.reservedRanges = this._reservedRanges; + + object.geometryInfo = this._geometryInfo.map( info => ( { + ...info, + boundingBox: info.boundingBox ? info.boundingBox.toJSON() : undefined, + boundingSphere: info.boundingSphere ? info.boundingSphere.toJSON() : undefined + } ) ); + object.instanceInfo = this._instanceInfo.map( info => ( { ...info } ) ); + + object.availableInstanceIds = this._availableInstanceIds.slice(); + object.availableGeometryIds = this._availableGeometryIds.slice(); + + object.nextIndexStart = this._nextIndexStart; + object.nextVertexStart = this._nextVertexStart; + object.geometryCount = this._geometryCount; + + object.maxInstanceCount = this._maxInstanceCount; + object.maxVertexCount = this._maxVertexCount; + object.maxIndexCount = this._maxIndexCount; + + object.geometryInitialized = this._geometryInitialized; + + object.matricesTexture = this._matricesTexture.toJSON( meta ); + + object.indirectTexture = this._indirectTexture.toJSON( meta ); + + if ( this._colorsTexture !== null ) { + + object.colorsTexture = this._colorsTexture.toJSON( meta ); + + } + + if ( this.boundingSphere !== null ) { + + object.boundingSphere = this.boundingSphere.toJSON(); + + } + + if ( this.boundingBox !== null ) { + + object.boundingBox = this.boundingBox.toJSON(); + + } + + } + // function serialize( library, element ) { @@ -7928,12 +14660,25 @@ class Object3D extends EventDispatcher { } + /** + * Returns a new 3D object with copied values from this instance. + * + * @param {boolean} [recursive=true] - When set to `true`, descendants of the 3D object are also cloned. + * @return {Object3D} A clone of this instance. + */ clone( recursive ) { return new this.constructor().copy( this, recursive ); } + /** + * Copies the values of the given 3D object to this instance. + * + * @param {Object3D} source - The 3D object to copy. + * @param {boolean} [recursive=true] - When set to `true`, descendants of the 3D object are cloned. + * @return {Object3D} A reference to this instance. + */ copy( source, recursive = true ) { this.name = source.name; @@ -7949,9 +14694,9 @@ class Object3D extends EventDispatcher { this.matrixWorld.copy( source.matrixWorld ); this.matrixAutoUpdate = source.matrixAutoUpdate; - this.matrixWorldNeedsUpdate = source.matrixWorldNeedsUpdate; this.matrixWorldAutoUpdate = source.matrixWorldAutoUpdate; + this.matrixWorldNeedsUpdate = source.matrixWorldNeedsUpdate; this.layers.mask = source.layers.mask; this.visible = source.visible; @@ -7962,7 +14707,7 @@ class Object3D extends EventDispatcher { this.frustumCulled = source.frustumCulled; this.renderOrder = source.renderOrder; - this.animations = source.animations; + this.animations = source.animations.slice(); this.userData = JSON.parse( JSON.stringify( source.userData ) ); @@ -7983,14 +14728,40 @@ class Object3D extends EventDispatcher { } +/** + * The default up direction for objects, also used as the default + * position for {@link DirectionalLight} and {@link HemisphereLight}. + * + * @static + * @type {Vector3} + * @default (0,1,0) + */ Object3D.DEFAULT_UP = /*@__PURE__*/ new Vector3( 0, 1, 0 ); + +/** + * The default setting for {@link Object3D#matrixAutoUpdate} for + * newly created 3D objects. + * + * @static + * @type {boolean} + * @default true + */ Object3D.DEFAULT_MATRIX_AUTO_UPDATE = true; + +/** + * The default setting for {@link Object3D#matrixWorldAutoUpdate} for + * newly created 3D objects. + * + * @static + * @type {boolean} + * @default true + */ Object3D.DEFAULT_MATRIX_WORLD_AUTO_UPDATE = true; -const _v0$1 = /*@__PURE__*/ new Vector3(); +const _v0$2 = /*@__PURE__*/ new Vector3(); const _v1$3 = /*@__PURE__*/ new Vector3(); const _v2$2 = /*@__PURE__*/ new Vector3(); -const _v3$1 = /*@__PURE__*/ new Vector3(); +const _v3$2 = /*@__PURE__*/ new Vector3(); const _vab = /*@__PURE__*/ new Vector3(); const _vac = /*@__PURE__*/ new Vector3(); @@ -7999,23 +14770,61 @@ const _vap = /*@__PURE__*/ new Vector3(); const _vbp = /*@__PURE__*/ new Vector3(); const _vcp = /*@__PURE__*/ new Vector3(); -let warnedGetUV = false; +const _v40 = /*@__PURE__*/ new Vector4(); +const _v41 = /*@__PURE__*/ new Vector4(); +const _v42 = /*@__PURE__*/ new Vector4(); +/** + * A geometric triangle as defined by three vectors representing its three corners. + */ class Triangle { + /** + * Constructs a new triangle. + * + * @param {Vector3} [a=(0,0,0)] - The first corner of the triangle. + * @param {Vector3} [b=(0,0,0)] - The second corner of the triangle. + * @param {Vector3} [c=(0,0,0)] - The third corner of the triangle. + */ constructor( a = new Vector3(), b = new Vector3(), c = new Vector3() ) { + /** + * The first corner of the triangle. + * + * @type {Vector3} + */ this.a = a; + + /** + * The second corner of the triangle. + * + * @type {Vector3} + */ this.b = b; + + /** + * The third corner of the triangle. + * + * @type {Vector3} + */ this.c = c; } + /** + * Computes the normal vector of a triangle. + * + * @param {Vector3} a - The first corner of the triangle. + * @param {Vector3} b - The second corner of the triangle. + * @param {Vector3} c - The third corner of the triangle. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The triangle's normal. + */ static getNormal( a, b, c, target ) { target.subVectors( c, b ); - _v0$1.subVectors( a, b ); - target.cross( _v0$1 ); + _v0$2.subVectors( a, b ); + target.cross( _v0$2 ); const targetLengthSq = target.lengthSq(); if ( targetLengthSq > 0 ) { @@ -8028,17 +14837,28 @@ class Triangle { } - // static/instance method to calculate barycentric coordinates - // based on: http://www.blackpawn.com/texts/pointinpoly/default.html + /** + * Computes a barycentric coordinates from the given vector. + * Returns `null` if the triangle is degenerate. + * + * @param {Vector3} point - A point in 3D space. + * @param {Vector3} a - The first corner of the triangle. + * @param {Vector3} b - The second corner of the triangle. + * @param {Vector3} c - The third corner of the triangle. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {?Vector3} The barycentric coordinates for the given point + */ static getBarycoord( point, a, b, c, target ) { - _v0$1.subVectors( c, a ); + // based on: http://www.blackpawn.com/texts/pointinpoly/default.html + + _v0$2.subVectors( c, a ); _v1$3.subVectors( b, a ); _v2$2.subVectors( point, a ); - const dot00 = _v0$1.dot( _v0$1 ); - const dot01 = _v0$1.dot( _v1$3 ); - const dot02 = _v0$1.dot( _v2$2 ); + const dot00 = _v0$2.dot( _v0$2 ); + const dot01 = _v0$2.dot( _v1$3 ); + const dot02 = _v0$2.dot( _v2$2 ); const dot11 = _v1$3.dot( _v1$3 ); const dot12 = _v1$3.dot( _v2$2 ); @@ -8047,9 +14867,8 @@ class Triangle { // collinear or singular triangle if ( denom === 0 ) { - // arbitrary location outside of triangle? - // not sure if this is the best idea, maybe should be returning undefined - return target.set( - 2, - 1, - 1 ); + target.set( 0, 0, 0 ); + return null; } @@ -8062,51 +14881,122 @@ class Triangle { } + /** + * Returns `true` if the given point, when projected onto the plane of the + * triangle, lies within the triangle. + * + * @param {Vector3} point - The point in 3D space to test. + * @param {Vector3} a - The first corner of the triangle. + * @param {Vector3} b - The second corner of the triangle. + * @param {Vector3} c - The third corner of the triangle. + * @return {boolean} Whether the given point, when projected onto the plane of the + * triangle, lies within the triangle or not. + */ static containsPoint( point, a, b, c ) { - this.getBarycoord( point, a, b, c, _v3$1 ); + // if the triangle is degenerate then we can't contain a point + if ( this.getBarycoord( point, a, b, c, _v3$2 ) === null ) { - return ( _v3$1.x >= 0 ) && ( _v3$1.y >= 0 ) && ( ( _v3$1.x + _v3$1.y ) <= 1 ); + return false; - } + } + + return ( _v3$2.x >= 0 ) && ( _v3$2.y >= 0 ) && ( ( _v3$2.x + _v3$2.y ) <= 1 ); - static getUV( point, p1, p2, p3, uv1, uv2, uv3, target ) { // @deprecated, r151 + } - if ( warnedGetUV === false ) { + /** + * Computes the value barycentrically interpolated for the given point on the + * triangle. Returns `null` if the triangle is degenerate. + * + * @param {Vector3} point - Position of interpolated point. + * @param {Vector3} p1 - The first corner of the triangle. + * @param {Vector3} p2 - The second corner of the triangle. + * @param {Vector3} p3 - The third corner of the triangle. + * @param {Vector3} v1 - Value to interpolate of first vertex. + * @param {Vector3} v2 - Value to interpolate of second vertex. + * @param {Vector3} v3 - Value to interpolate of third vertex. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {?Vector3} The interpolated value. + */ + static getInterpolation( point, p1, p2, p3, v1, v2, v3, target ) { - console.warn( 'THREE.Triangle.getUV() has been renamed to THREE.Triangle.getInterpolation().' ); + if ( this.getBarycoord( point, p1, p2, p3, _v3$2 ) === null ) { - warnedGetUV = true; + target.x = 0; + target.y = 0; + if ( 'z' in target ) target.z = 0; + if ( 'w' in target ) target.w = 0; + return null; } - return this.getInterpolation( point, p1, p2, p3, uv1, uv2, uv3, target ); + target.setScalar( 0 ); + target.addScaledVector( v1, _v3$2.x ); + target.addScaledVector( v2, _v3$2.y ); + target.addScaledVector( v3, _v3$2.z ); + + return target; } - static getInterpolation( point, p1, p2, p3, v1, v2, v3, target ) { + /** + * Computes the value barycentrically interpolated for the given attribute and indices. + * + * @param {BufferAttribute} attr - The attribute to interpolate. + * @param {number} i1 - Index of first vertex. + * @param {number} i2 - Index of second vertex. + * @param {number} i3 - Index of third vertex. + * @param {Vector3} barycoord - The barycoordinate value to use to interpolate. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The interpolated attribute value. + */ + static getInterpolatedAttribute( attr, i1, i2, i3, barycoord, target ) { + + _v40.setScalar( 0 ); + _v41.setScalar( 0 ); + _v42.setScalar( 0 ); - this.getBarycoord( point, p1, p2, p3, _v3$1 ); + _v40.fromBufferAttribute( attr, i1 ); + _v41.fromBufferAttribute( attr, i2 ); + _v42.fromBufferAttribute( attr, i3 ); target.setScalar( 0 ); - target.addScaledVector( v1, _v3$1.x ); - target.addScaledVector( v2, _v3$1.y ); - target.addScaledVector( v3, _v3$1.z ); + target.addScaledVector( _v40, barycoord.x ); + target.addScaledVector( _v41, barycoord.y ); + target.addScaledVector( _v42, barycoord.z ); return target; } + /** + * Returns `true` if the triangle is oriented towards the given direction. + * + * @param {Vector3} a - The first corner of the triangle. + * @param {Vector3} b - The second corner of the triangle. + * @param {Vector3} c - The third corner of the triangle. + * @param {Vector3} direction - The (normalized) direction vector. + * @return {boolean} Whether the triangle is oriented towards the given direction or not. + */ static isFrontFacing( a, b, c, direction ) { - _v0$1.subVectors( c, b ); + _v0$2.subVectors( c, b ); _v1$3.subVectors( a, b ); // strictly front facing - return ( _v0$1.cross( _v1$3 ).dot( direction ) < 0 ) ? true : false; + return ( _v0$2.cross( _v1$3 ).dot( direction ) < 0 ) ? true : false; } + /** + * Sets the triangle's vertices by copying the given values. + * + * @param {Vector3} a - The first corner of the triangle. + * @param {Vector3} b - The second corner of the triangle. + * @param {Vector3} c - The third corner of the triangle. + * @return {Triangle} A reference to this triangle. + */ set( a, b, c ) { this.a.copy( a ); @@ -8117,6 +15007,15 @@ class Triangle { } + /** + * Sets the triangle's vertices by copying the given array values. + * + * @param {Array} points - An array with 3D points. + * @param {number} i0 - The array index representing the first corner of the triangle. + * @param {number} i1 - The array index representing the second corner of the triangle. + * @param {number} i2 - The array index representing the third corner of the triangle. + * @return {Triangle} A reference to this triangle. + */ setFromPointsAndIndices( points, i0, i1, i2 ) { this.a.copy( points[ i0 ] ); @@ -8127,6 +15026,15 @@ class Triangle { } + /** + * Sets the triangle's vertices by copying the given attribute values. + * + * @param {BufferAttribute} attribute - A buffer attribute with 3D points data. + * @param {number} i0 - The attribute index representing the first corner of the triangle. + * @param {number} i1 - The attribute index representing the second corner of the triangle. + * @param {number} i2 - The attribute index representing the third corner of the triangle. + * @return {Triangle} A reference to this triangle. + */ setFromAttributeAndIndices( attribute, i0, i1, i2 ) { this.a.fromBufferAttribute( attribute, i0 ); @@ -8137,12 +15045,23 @@ class Triangle { } + /** + * Returns a new triangle with copied values from this instance. + * + * @return {Triangle} A clone of this instance. + */ clone() { return new this.constructor().copy( this ); } + /** + * Copies the values of the given triangle to this instance. + * + * @param {Triangle} triangle - The triangle to copy. + * @return {Triangle} A reference to this triangle. + */ copy( triangle ) { this.a.copy( triangle.a ); @@ -8153,77 +15072,132 @@ class Triangle { } + /** + * Computes the area of the triangle. + * + * @return {number} The triangle's area. + */ getArea() { - _v0$1.subVectors( this.c, this.b ); + _v0$2.subVectors( this.c, this.b ); _v1$3.subVectors( this.a, this.b ); - return _v0$1.cross( _v1$3 ).length() * 0.5; + return _v0$2.cross( _v1$3 ).length() * 0.5; } + /** + * Computes the midpoint of the triangle. + * + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The triangle's midpoint. + */ getMidpoint( target ) { return target.addVectors( this.a, this.b ).add( this.c ).multiplyScalar( 1 / 3 ); } + /** + * Computes the normal of the triangle. + * + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The triangle's normal. + */ getNormal( target ) { return Triangle.getNormal( this.a, this.b, this.c, target ); } + /** + * Computes a plane the triangle lies within. + * + * @param {Plane} target - The target vector that is used to store the method's result. + * @return {Plane} The plane the triangle lies within. + */ getPlane( target ) { return target.setFromCoplanarPoints( this.a, this.b, this.c ); } + /** + * Computes a barycentric coordinates from the given vector. + * Returns `null` if the triangle is degenerate. + * + * @param {Vector3} point - A point in 3D space. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {?Vector3} The barycentric coordinates for the given point + */ getBarycoord( point, target ) { return Triangle.getBarycoord( point, this.a, this.b, this.c, target ); } - getUV( point, uv1, uv2, uv3, target ) { // @deprecated, r151 - - if ( warnedGetUV === false ) { - - console.warn( 'THREE.Triangle.getUV() has been renamed to THREE.Triangle.getInterpolation().' ); - - warnedGetUV = true; - - } - - return Triangle.getInterpolation( point, this.a, this.b, this.c, uv1, uv2, uv3, target ); - - } - + /** + * Computes the value barycentrically interpolated for the given point on the + * triangle. Returns `null` if the triangle is degenerate. + * + * @param {Vector3} point - Position of interpolated point. + * @param {Vector3} v1 - Value to interpolate of first vertex. + * @param {Vector3} v2 - Value to interpolate of second vertex. + * @param {Vector3} v3 - Value to interpolate of third vertex. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {?Vector3} The interpolated value. + */ getInterpolation( point, v1, v2, v3, target ) { return Triangle.getInterpolation( point, this.a, this.b, this.c, v1, v2, v3, target ); } + /** + * Returns `true` if the given point, when projected onto the plane of the + * triangle, lies within the triangle. + * + * @param {Vector3} point - The point in 3D space to test. + * @return {boolean} Whether the given point, when projected onto the plane of the + * triangle, lies within the triangle or not. + */ containsPoint( point ) { return Triangle.containsPoint( point, this.a, this.b, this.c ); } + /** + * Returns `true` if the triangle is oriented towards the given direction. + * + * @param {Vector3} direction - The (normalized) direction vector. + * @return {boolean} Whether the triangle is oriented towards the given direction or not. + */ isFrontFacing( direction ) { return Triangle.isFrontFacing( this.a, this.b, this.c, direction ); } + /** + * Returns `true` if this triangle intersects with the given box. + * + * @param {Box3} box - The box to intersect. + * @return {boolean} Whether this triangle intersects with the given box or not. + */ intersectsBox( box ) { return box.intersectsTriangle( this ); } + /** + * Returns the closest point on the triangle to the given point. + * + * @param {Vector3} p - The point to compute the closest point for. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The closest point on the triangle. + */ closestPointToPoint( p, target ) { const a = this.a, b = this.b, c = this.c; @@ -8305,6 +15279,12 @@ class Triangle { } + /** + * Returns `true` if this triangle is equal with the given one. + * + * @param {Triangle} triangle - The triangle to test for equality. + * @return {boolean} Whether this triangle is equal with the given one. + */ equals( triangle ) { return triangle.a.equals( this.a ) && triangle.b.equals( this.b ) && triangle.c.equals( this.c ); @@ -8313,774 +15293,405 @@ class Triangle { } -let materialId = 0; +const _colorKeywords = { 'aliceblue': 0xF0F8FF, 'antiquewhite': 0xFAEBD7, 'aqua': 0x00FFFF, 'aquamarine': 0x7FFFD4, 'azure': 0xF0FFFF, + 'beige': 0xF5F5DC, 'bisque': 0xFFE4C4, 'black': 0x000000, 'blanchedalmond': 0xFFEBCD, 'blue': 0x0000FF, 'blueviolet': 0x8A2BE2, + 'brown': 0xA52A2A, 'burlywood': 0xDEB887, 'cadetblue': 0x5F9EA0, 'chartreuse': 0x7FFF00, 'chocolate': 0xD2691E, 'coral': 0xFF7F50, + 'cornflowerblue': 0x6495ED, 'cornsilk': 0xFFF8DC, 'crimson': 0xDC143C, 'cyan': 0x00FFFF, 'darkblue': 0x00008B, 'darkcyan': 0x008B8B, + 'darkgoldenrod': 0xB8860B, 'darkgray': 0xA9A9A9, 'darkgreen': 0x006400, 'darkgrey': 0xA9A9A9, 'darkkhaki': 0xBDB76B, 'darkmagenta': 0x8B008B, + 'darkolivegreen': 0x556B2F, 'darkorange': 0xFF8C00, 'darkorchid': 0x9932CC, 'darkred': 0x8B0000, 'darksalmon': 0xE9967A, 'darkseagreen': 0x8FBC8F, + 'darkslateblue': 0x483D8B, 'darkslategray': 0x2F4F4F, 'darkslategrey': 0x2F4F4F, 'darkturquoise': 0x00CED1, 'darkviolet': 0x9400D3, + 'deeppink': 0xFF1493, 'deepskyblue': 0x00BFFF, 'dimgray': 0x696969, 'dimgrey': 0x696969, 'dodgerblue': 0x1E90FF, 'firebrick': 0xB22222, + 'floralwhite': 0xFFFAF0, 'forestgreen': 0x228B22, 'fuchsia': 0xFF00FF, 'gainsboro': 0xDCDCDC, 'ghostwhite': 0xF8F8FF, 'gold': 0xFFD700, + 'goldenrod': 0xDAA520, 'gray': 0x808080, 'green': 0x008000, 'greenyellow': 0xADFF2F, 'grey': 0x808080, 'honeydew': 0xF0FFF0, 'hotpink': 0xFF69B4, + 'indianred': 0xCD5C5C, 'indigo': 0x4B0082, 'ivory': 0xFFFFF0, 'khaki': 0xF0E68C, 'lavender': 0xE6E6FA, 'lavenderblush': 0xFFF0F5, 'lawngreen': 0x7CFC00, + 'lemonchiffon': 0xFFFACD, 'lightblue': 0xADD8E6, 'lightcoral': 0xF08080, 'lightcyan': 0xE0FFFF, 'lightgoldenrodyellow': 0xFAFAD2, 'lightgray': 0xD3D3D3, + 'lightgreen': 0x90EE90, 'lightgrey': 0xD3D3D3, 'lightpink': 0xFFB6C1, 'lightsalmon': 0xFFA07A, 'lightseagreen': 0x20B2AA, 'lightskyblue': 0x87CEFA, + 'lightslategray': 0x778899, 'lightslategrey': 0x778899, 'lightsteelblue': 0xB0C4DE, 'lightyellow': 0xFFFFE0, 'lime': 0x00FF00, 'limegreen': 0x32CD32, + 'linen': 0xFAF0E6, 'magenta': 0xFF00FF, 'maroon': 0x800000, 'mediumaquamarine': 0x66CDAA, 'mediumblue': 0x0000CD, 'mediumorchid': 0xBA55D3, + 'mediumpurple': 0x9370DB, 'mediumseagreen': 0x3CB371, 'mediumslateblue': 0x7B68EE, 'mediumspringgreen': 0x00FA9A, 'mediumturquoise': 0x48D1CC, + 'mediumvioletred': 0xC71585, 'midnightblue': 0x191970, 'mintcream': 0xF5FFFA, 'mistyrose': 0xFFE4E1, 'moccasin': 0xFFE4B5, 'navajowhite': 0xFFDEAD, + 'navy': 0x000080, 'oldlace': 0xFDF5E6, 'olive': 0x808000, 'olivedrab': 0x6B8E23, 'orange': 0xFFA500, 'orangered': 0xFF4500, 'orchid': 0xDA70D6, + 'palegoldenrod': 0xEEE8AA, 'palegreen': 0x98FB98, 'paleturquoise': 0xAFEEEE, 'palevioletred': 0xDB7093, 'papayawhip': 0xFFEFD5, 'peachpuff': 0xFFDAB9, + 'peru': 0xCD853F, 'pink': 0xFFC0CB, 'plum': 0xDDA0DD, 'powderblue': 0xB0E0E6, 'purple': 0x800080, 'rebeccapurple': 0x663399, 'red': 0xFF0000, 'rosybrown': 0xBC8F8F, + 'royalblue': 0x4169E1, 'saddlebrown': 0x8B4513, 'salmon': 0xFA8072, 'sandybrown': 0xF4A460, 'seagreen': 0x2E8B57, 'seashell': 0xFFF5EE, + 'sienna': 0xA0522D, 'silver': 0xC0C0C0, 'skyblue': 0x87CEEB, 'slateblue': 0x6A5ACD, 'slategray': 0x708090, 'slategrey': 0x708090, 'snow': 0xFFFAFA, + 'springgreen': 0x00FF7F, 'steelblue': 0x4682B4, 'tan': 0xD2B48C, 'teal': 0x008080, 'thistle': 0xD8BFD8, 'tomato': 0xFF6347, 'turquoise': 0x40E0D0, + 'violet': 0xEE82EE, 'wheat': 0xF5DEB3, 'white': 0xFFFFFF, 'whitesmoke': 0xF5F5F5, 'yellow': 0xFFFF00, 'yellowgreen': 0x9ACD32 }; -class Material extends EventDispatcher { +const _hslA = { h: 0, s: 0, l: 0 }; +const _hslB = { h: 0, s: 0, l: 0 }; - constructor() { +function hue2rgb( p, q, t ) { - super(); + if ( t < 0 ) t += 1; + if ( t > 1 ) t -= 1; + if ( t < 1 / 6 ) return p + ( q - p ) * 6 * t; + if ( t < 1 / 2 ) return q; + if ( t < 2 / 3 ) return p + ( q - p ) * 6 * ( 2 / 3 - t ); + return p; - this.isMaterial = true; +} - Object.defineProperty( this, 'id', { value: materialId ++ } ); +/** + * A Color instance is represented by RGB components in the linear working + * color space, which defaults to `LinearSRGBColorSpace`. Inputs + * conventionally using `SRGBColorSpace` (such as hexadecimals and CSS + * strings) are converted to the working color space automatically. + * + * ```js + * // converted automatically from SRGBColorSpace to LinearSRGBColorSpace + * const color = new THREE.Color().setHex( 0x112233 ); + * ``` + * Source color spaces may be specified explicitly, to ensure correct conversions. + * ```js + * // assumed already LinearSRGBColorSpace; no conversion + * const color = new THREE.Color().setRGB( 0.5, 0.5, 0.5 ); + * + * // converted explicitly from SRGBColorSpace to LinearSRGBColorSpace + * const color = new THREE.Color().setRGB( 0.5, 0.5, 0.5, SRGBColorSpace ); + * ``` + * If THREE.ColorManagement is disabled, no conversions occur. For details, + * see Color management. Iterating through a Color instance will yield + * its components (r, g, b) in the corresponding order. A Color can be initialised + * in any of the following ways: + * ```js + * //empty constructor - will default white + * const color1 = new THREE.Color(); + * + * //Hexadecimal color (recommended) + * const color2 = new THREE.Color( 0xff0000 ); + * + * //RGB string + * const color3 = new THREE.Color("rgb(255, 0, 0)"); + * const color4 = new THREE.Color("rgb(100%, 0%, 0%)"); + * + * //X11 color name - all 140 color names are supported. + * //Note the lack of CamelCase in the name + * const color5 = new THREE.Color( 'skyblue' ); + * //HSL string + * const color6 = new THREE.Color("hsl(0, 100%, 50%)"); + * + * //Separate RGB values between 0 and 1 + * const color7 = new THREE.Color( 1, 0, 0 ); + * ``` + */ +class Color { - this.uuid = generateUUID(); + /** + * Constructs a new color. + * + * Note that standard method of specifying color in three.js is with a hexadecimal triplet, + * and that method is used throughout the rest of the documentation. + * + * @param {(number|string|Color)} [r] - The red component of the color. If `g` and `b` are + * not provided, it can be hexadecimal triplet, a CSS-style string or another `Color` instance. + * @param {number} [g] - The green component. + * @param {number} [b] - The blue component. + */ + constructor( r, g, b ) { - this.name = ''; - this.type = 'Material'; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isColor = true; - this.blending = NormalBlending; - this.side = FrontSide; - this.vertexColors = false; + /** + * The red component. + * + * @type {number} + * @default 1 + */ + this.r = 1; - this.opacity = 1; - this.transparent = false; + /** + * The green component. + * + * @type {number} + * @default 1 + */ + this.g = 1; - this.blendSrc = SrcAlphaFactor; - this.blendDst = OneMinusSrcAlphaFactor; - this.blendEquation = AddEquation; - this.blendSrcAlpha = null; - this.blendDstAlpha = null; - this.blendEquationAlpha = null; + /** + * The blue component. + * + * @type {number} + * @default 1 + */ + this.b = 1; - this.depthFunc = LessEqualDepth; - this.depthTest = true; - this.depthWrite = true; + return this.set( r, g, b ); - this.stencilWriteMask = 0xff; - this.stencilFunc = AlwaysStencilFunc; - this.stencilRef = 0; - this.stencilFuncMask = 0xff; - this.stencilFail = KeepStencilOp; - this.stencilZFail = KeepStencilOp; - this.stencilZPass = KeepStencilOp; - this.stencilWrite = false; + } - this.clippingPlanes = null; - this.clipIntersection = false; - this.clipShadows = false; + /** + * Sets the colors's components from the given values. + * + * @param {(number|string|Color)} [r] - The red component of the color. If `g` and `b` are + * not provided, it can be hexadecimal triplet, a CSS-style string or another `Color` instance. + * @param {number} [g] - The green component. + * @param {number} [b] - The blue component. + * @return {Color} A reference to this color. + */ + set( r, g, b ) { - this.shadowSide = null; + if ( g === undefined && b === undefined ) { - this.colorWrite = true; + // r is THREE.Color, hex or string - this.precision = null; // override the renderer's default precision for this material + const value = r; - this.polygonOffset = false; - this.polygonOffsetFactor = 0; - this.polygonOffsetUnits = 0; + if ( value && value.isColor ) { - this.dithering = false; + this.copy( value ); - this.alphaToCoverage = false; - this.premultipliedAlpha = false; - this.forceSinglePass = false; + } else if ( typeof value === 'number' ) { - this.visible = true; + this.setHex( value ); - this.toneMapped = true; + } else if ( typeof value === 'string' ) { - this.userData = {}; + this.setStyle( value ); - this.version = 0; + } - this._alphaTest = 0; + } else { - } + this.setRGB( r, g, b ); - get alphaTest() { + } - return this._alphaTest; + return this; } - set alphaTest( value ) { - - if ( this._alphaTest > 0 !== value > 0 ) { - - this.version ++; + /** + * Sets the colors's components to the given scalar value. + * + * @param {number} scalar - The scalar value. + * @return {Color} A reference to this color. + */ + setScalar( scalar ) { - } + this.r = scalar; + this.g = scalar; + this.b = scalar; - this._alphaTest = value; + return this; } - onBuild( /* shaderobject, renderer */ ) {} + /** + * Sets this color from a hexadecimal value. + * + * @param {number} hex - The hexadecimal value. + * @param {string} [colorSpace=SRGBColorSpace] - The color space. + * @return {Color} A reference to this color. + */ + setHex( hex, colorSpace = SRGBColorSpace ) { - onBeforeRender( /* renderer, scene, camera, geometry, object, group */ ) {} + hex = Math.floor( hex ); - onBeforeCompile( /* shaderobject, renderer */ ) {} + this.r = ( hex >> 16 & 255 ) / 255; + this.g = ( hex >> 8 & 255 ) / 255; + this.b = ( hex & 255 ) / 255; - customProgramCacheKey() { + ColorManagement.colorSpaceToWorking( this, colorSpace ); - return this.onBeforeCompile.toString(); + return this; } - setValues( values ) { - - if ( values === undefined ) return; - - for ( const key in values ) { - - const newValue = values[ key ]; - - if ( newValue === undefined ) { + /** + * Sets this color from RGB values. + * + * @param {number} r - Red channel value between `0.0` and `1.0`. + * @param {number} g - Green channel value between `0.0` and `1.0`. + * @param {number} b - Blue channel value between `0.0` and `1.0`. + * @param {string} [colorSpace=ColorManagement.workingColorSpace] - The color space. + * @return {Color} A reference to this color. + */ + setRGB( r, g, b, colorSpace = ColorManagement.workingColorSpace ) { - console.warn( `THREE.Material: parameter '${ key }' has value of undefined.` ); - continue; + this.r = r; + this.g = g; + this.b = b; - } + ColorManagement.colorSpaceToWorking( this, colorSpace ); - const currentValue = this[ key ]; + return this; - if ( currentValue === undefined ) { + } - console.warn( `THREE.Material: '${ key }' is not a property of THREE.${ this.type }.` ); - continue; + /** + * Sets this color from RGB values. + * + * @param {number} h - Hue value between `0.0` and `1.0`. + * @param {number} s - Saturation value between `0.0` and `1.0`. + * @param {number} l - Lightness value between `0.0` and `1.0`. + * @param {string} [colorSpace=ColorManagement.workingColorSpace] - The color space. + * @return {Color} A reference to this color. + */ + setHSL( h, s, l, colorSpace = ColorManagement.workingColorSpace ) { - } + // h,s,l ranges are in 0.0 - 1.0 + h = euclideanModulo( h, 1 ); + s = clamp( s, 0, 1 ); + l = clamp( l, 0, 1 ); - if ( currentValue && currentValue.isColor ) { + if ( s === 0 ) { - currentValue.set( newValue ); + this.r = this.g = this.b = l; - } else if ( ( currentValue && currentValue.isVector3 ) && ( newValue && newValue.isVector3 ) ) { + } else { - currentValue.copy( newValue ); + const p = l <= 0.5 ? l * ( 1 + s ) : l + s - ( l * s ); + const q = ( 2 * l ) - p; - } else { + this.r = hue2rgb( q, p, h + 1 / 3 ); + this.g = hue2rgb( q, p, h ); + this.b = hue2rgb( q, p, h - 1 / 3 ); - this[ key ] = newValue; + } - } + ColorManagement.colorSpaceToWorking( this, colorSpace ); - } + return this; } - toJSON( meta ) { + /** + * Sets this color from a CSS-style string. For example, `rgb(250, 0,0)`, + * `rgb(100%, 0%, 0%)`, `hsl(0, 100%, 50%)`, `#ff0000`, `#f00`, or `red` ( or + * any [X11 color name]{@link https://en.wikipedia.org/wiki/X11_color_names#Color_name_chart} - + * all 140 color names are supported). + * + * @param {string} style - Color as a CSS-style string. + * @param {string} [colorSpace=SRGBColorSpace] - The color space. + * @return {Color} A reference to this color. + */ + setStyle( style, colorSpace = SRGBColorSpace ) { - const isRootObject = ( meta === undefined || typeof meta === 'string' ); + function handleAlpha( string ) { - if ( isRootObject ) { + if ( string === undefined ) return; - meta = { - textures: {}, - images: {} - }; + if ( parseFloat( string ) < 1 ) { - } + console.warn( 'THREE.Color: Alpha component of ' + style + ' will be ignored.' ); - const data = { - metadata: { - version: 4.6, - type: 'Material', - generator: 'Material.toJSON' } - }; - // standard Material serialization - data.uuid = this.uuid; - data.type = this.type; + } - if ( this.name !== '' ) data.name = this.name; - if ( this.color && this.color.isColor ) data.color = this.color.getHex(); + let m; - if ( this.roughness !== undefined ) data.roughness = this.roughness; - if ( this.metalness !== undefined ) data.metalness = this.metalness; + if ( m = /^(\w+)\(([^\)]*)\)/.exec( style ) ) { - if ( this.sheen !== undefined ) data.sheen = this.sheen; - if ( this.sheenColor && this.sheenColor.isColor ) data.sheenColor = this.sheenColor.getHex(); - if ( this.sheenRoughness !== undefined ) data.sheenRoughness = this.sheenRoughness; - if ( this.emissive && this.emissive.isColor ) data.emissive = this.emissive.getHex(); - if ( this.emissiveIntensity && this.emissiveIntensity !== 1 ) data.emissiveIntensity = this.emissiveIntensity; + // rgb / hsl - if ( this.specular && this.specular.isColor ) data.specular = this.specular.getHex(); - if ( this.specularIntensity !== undefined ) data.specularIntensity = this.specularIntensity; - if ( this.specularColor && this.specularColor.isColor ) data.specularColor = this.specularColor.getHex(); - if ( this.shininess !== undefined ) data.shininess = this.shininess; - if ( this.clearcoat !== undefined ) data.clearcoat = this.clearcoat; - if ( this.clearcoatRoughness !== undefined ) data.clearcoatRoughness = this.clearcoatRoughness; + let color; + const name = m[ 1 ]; + const components = m[ 2 ]; - if ( this.clearcoatMap && this.clearcoatMap.isTexture ) { + switch ( name ) { - data.clearcoatMap = this.clearcoatMap.toJSON( meta ).uuid; + case 'rgb': + case 'rgba': - } + if ( color = /^\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) { - if ( this.clearcoatRoughnessMap && this.clearcoatRoughnessMap.isTexture ) { + // rgb(255,0,0) rgba(255,0,0,0.5) - data.clearcoatRoughnessMap = this.clearcoatRoughnessMap.toJSON( meta ).uuid; + handleAlpha( color[ 4 ] ); - } + return this.setRGB( + Math.min( 255, parseInt( color[ 1 ], 10 ) ) / 255, + Math.min( 255, parseInt( color[ 2 ], 10 ) ) / 255, + Math.min( 255, parseInt( color[ 3 ], 10 ) ) / 255, + colorSpace + ); - if ( this.clearcoatNormalMap && this.clearcoatNormalMap.isTexture ) { + } - data.clearcoatNormalMap = this.clearcoatNormalMap.toJSON( meta ).uuid; - data.clearcoatNormalScale = this.clearcoatNormalScale.toArray(); + if ( color = /^\s*(\d+)\%\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) { - } + // rgb(100%,0%,0%) rgba(100%,0%,0%,0.5) - if ( this.iridescence !== undefined ) data.iridescence = this.iridescence; - if ( this.iridescenceIOR !== undefined ) data.iridescenceIOR = this.iridescenceIOR; - if ( this.iridescenceThicknessRange !== undefined ) data.iridescenceThicknessRange = this.iridescenceThicknessRange; + handleAlpha( color[ 4 ] ); - if ( this.iridescenceMap && this.iridescenceMap.isTexture ) { + return this.setRGB( + Math.min( 100, parseInt( color[ 1 ], 10 ) ) / 100, + Math.min( 100, parseInt( color[ 2 ], 10 ) ) / 100, + Math.min( 100, parseInt( color[ 3 ], 10 ) ) / 100, + colorSpace + ); - data.iridescenceMap = this.iridescenceMap.toJSON( meta ).uuid; + } - } + break; - if ( this.iridescenceThicknessMap && this.iridescenceThicknessMap.isTexture ) { + case 'hsl': + case 'hsla': - data.iridescenceThicknessMap = this.iridescenceThicknessMap.toJSON( meta ).uuid; + if ( color = /^\s*(\d*\.?\d+)\s*,\s*(\d*\.?\d+)\%\s*,\s*(\d*\.?\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) { - } + // hsl(120,50%,50%) hsla(120,50%,50%,0.5) - if ( this.anisotropy !== undefined ) data.anisotropy = this.anisotropy; - if ( this.anisotropyRotation !== undefined ) data.anisotropyRotation = this.anisotropyRotation; + handleAlpha( color[ 4 ] ); - if ( this.anisotropyMap && this.anisotropyMap.isTexture ) { + return this.setHSL( + parseFloat( color[ 1 ] ) / 360, + parseFloat( color[ 2 ] ) / 100, + parseFloat( color[ 3 ] ) / 100, + colorSpace + ); - data.anisotropyMap = this.anisotropyMap.toJSON( meta ).uuid; + } - } + break; - if ( this.map && this.map.isTexture ) data.map = this.map.toJSON( meta ).uuid; - if ( this.matcap && this.matcap.isTexture ) data.matcap = this.matcap.toJSON( meta ).uuid; - if ( this.alphaMap && this.alphaMap.isTexture ) data.alphaMap = this.alphaMap.toJSON( meta ).uuid; + default: - if ( this.lightMap && this.lightMap.isTexture ) { + console.warn( 'THREE.Color: Unknown color model ' + style ); - data.lightMap = this.lightMap.toJSON( meta ).uuid; - data.lightMapIntensity = this.lightMapIntensity; + } - } + } else if ( m = /^\#([A-Fa-f\d]+)$/.exec( style ) ) { - if ( this.aoMap && this.aoMap.isTexture ) { + // hex color - data.aoMap = this.aoMap.toJSON( meta ).uuid; - data.aoMapIntensity = this.aoMapIntensity; + const hex = m[ 1 ]; + const size = hex.length; - } + if ( size === 3 ) { - if ( this.bumpMap && this.bumpMap.isTexture ) { + // #ff0 + return this.setRGB( + parseInt( hex.charAt( 0 ), 16 ) / 15, + parseInt( hex.charAt( 1 ), 16 ) / 15, + parseInt( hex.charAt( 2 ), 16 ) / 15, + colorSpace + ); - data.bumpMap = this.bumpMap.toJSON( meta ).uuid; - data.bumpScale = this.bumpScale; + } else if ( size === 6 ) { - } + // #ff0000 + return this.setHex( parseInt( hex, 16 ), colorSpace ); - if ( this.normalMap && this.normalMap.isTexture ) { + } else { - data.normalMap = this.normalMap.toJSON( meta ).uuid; - data.normalMapType = this.normalMapType; - data.normalScale = this.normalScale.toArray(); + console.warn( 'THREE.Color: Invalid hex color ' + style ); - } + } - if ( this.displacementMap && this.displacementMap.isTexture ) { + } else if ( style && style.length > 0 ) { - data.displacementMap = this.displacementMap.toJSON( meta ).uuid; - data.displacementScale = this.displacementScale; - data.displacementBias = this.displacementBias; - - } - - if ( this.roughnessMap && this.roughnessMap.isTexture ) data.roughnessMap = this.roughnessMap.toJSON( meta ).uuid; - if ( this.metalnessMap && this.metalnessMap.isTexture ) data.metalnessMap = this.metalnessMap.toJSON( meta ).uuid; - - if ( this.emissiveMap && this.emissiveMap.isTexture ) data.emissiveMap = this.emissiveMap.toJSON( meta ).uuid; - if ( this.specularMap && this.specularMap.isTexture ) data.specularMap = this.specularMap.toJSON( meta ).uuid; - if ( this.specularIntensityMap && this.specularIntensityMap.isTexture ) data.specularIntensityMap = this.specularIntensityMap.toJSON( meta ).uuid; - if ( this.specularColorMap && this.specularColorMap.isTexture ) data.specularColorMap = this.specularColorMap.toJSON( meta ).uuid; - - if ( this.envMap && this.envMap.isTexture ) { - - data.envMap = this.envMap.toJSON( meta ).uuid; - - if ( this.combine !== undefined ) data.combine = this.combine; - - } - - if ( this.envMapIntensity !== undefined ) data.envMapIntensity = this.envMapIntensity; - if ( this.reflectivity !== undefined ) data.reflectivity = this.reflectivity; - if ( this.refractionRatio !== undefined ) data.refractionRatio = this.refractionRatio; - - if ( this.gradientMap && this.gradientMap.isTexture ) { - - data.gradientMap = this.gradientMap.toJSON( meta ).uuid; - - } - - if ( this.transmission !== undefined ) data.transmission = this.transmission; - if ( this.transmissionMap && this.transmissionMap.isTexture ) data.transmissionMap = this.transmissionMap.toJSON( meta ).uuid; - if ( this.thickness !== undefined ) data.thickness = this.thickness; - if ( this.thicknessMap && this.thicknessMap.isTexture ) data.thicknessMap = this.thicknessMap.toJSON( meta ).uuid; - if ( this.attenuationDistance !== undefined && this.attenuationDistance !== Infinity ) data.attenuationDistance = this.attenuationDistance; - if ( this.attenuationColor !== undefined ) data.attenuationColor = this.attenuationColor.getHex(); - - if ( this.size !== undefined ) data.size = this.size; - if ( this.shadowSide !== null ) data.shadowSide = this.shadowSide; - if ( this.sizeAttenuation !== undefined ) data.sizeAttenuation = this.sizeAttenuation; - - if ( this.blending !== NormalBlending ) data.blending = this.blending; - if ( this.side !== FrontSide ) data.side = this.side; - if ( this.vertexColors ) data.vertexColors = true; - - if ( this.opacity < 1 ) data.opacity = this.opacity; - if ( this.transparent === true ) data.transparent = this.transparent; - - data.depthFunc = this.depthFunc; - data.depthTest = this.depthTest; - data.depthWrite = this.depthWrite; - data.colorWrite = this.colorWrite; - - data.stencilWrite = this.stencilWrite; - data.stencilWriteMask = this.stencilWriteMask; - data.stencilFunc = this.stencilFunc; - data.stencilRef = this.stencilRef; - data.stencilFuncMask = this.stencilFuncMask; - data.stencilFail = this.stencilFail; - data.stencilZFail = this.stencilZFail; - data.stencilZPass = this.stencilZPass; - - // rotation (SpriteMaterial) - if ( this.rotation !== undefined && this.rotation !== 0 ) data.rotation = this.rotation; - - if ( this.polygonOffset === true ) data.polygonOffset = true; - if ( this.polygonOffsetFactor !== 0 ) data.polygonOffsetFactor = this.polygonOffsetFactor; - if ( this.polygonOffsetUnits !== 0 ) data.polygonOffsetUnits = this.polygonOffsetUnits; - - if ( this.linewidth !== undefined && this.linewidth !== 1 ) data.linewidth = this.linewidth; - if ( this.dashSize !== undefined ) data.dashSize = this.dashSize; - if ( this.gapSize !== undefined ) data.gapSize = this.gapSize; - if ( this.scale !== undefined ) data.scale = this.scale; - - if ( this.dithering === true ) data.dithering = true; - - if ( this.alphaTest > 0 ) data.alphaTest = this.alphaTest; - if ( this.alphaToCoverage === true ) data.alphaToCoverage = this.alphaToCoverage; - if ( this.premultipliedAlpha === true ) data.premultipliedAlpha = this.premultipliedAlpha; - if ( this.forceSinglePass === true ) data.forceSinglePass = this.forceSinglePass; - - if ( this.wireframe === true ) data.wireframe = this.wireframe; - if ( this.wireframeLinewidth > 1 ) data.wireframeLinewidth = this.wireframeLinewidth; - if ( this.wireframeLinecap !== 'round' ) data.wireframeLinecap = this.wireframeLinecap; - if ( this.wireframeLinejoin !== 'round' ) data.wireframeLinejoin = this.wireframeLinejoin; - - if ( this.flatShading === true ) data.flatShading = this.flatShading; - - if ( this.visible === false ) data.visible = false; - - if ( this.toneMapped === false ) data.toneMapped = false; - - if ( this.fog === false ) data.fog = false; - - if ( Object.keys( this.userData ).length > 0 ) data.userData = this.userData; - - // TODO: Copied from Object3D.toJSON - - function extractFromCache( cache ) { - - const values = []; - - for ( const key in cache ) { - - const data = cache[ key ]; - delete data.metadata; - values.push( data ); - - } - - return values; - - } - - if ( isRootObject ) { - - const textures = extractFromCache( meta.textures ); - const images = extractFromCache( meta.images ); - - if ( textures.length > 0 ) data.textures = textures; - if ( images.length > 0 ) data.images = images; - - } - - return data; - - } - - clone() { - - return new this.constructor().copy( this ); - - } - - copy( source ) { - - this.name = source.name; - - this.blending = source.blending; - this.side = source.side; - this.vertexColors = source.vertexColors; - - this.opacity = source.opacity; - this.transparent = source.transparent; - - this.blendSrc = source.blendSrc; - this.blendDst = source.blendDst; - this.blendEquation = source.blendEquation; - this.blendSrcAlpha = source.blendSrcAlpha; - this.blendDstAlpha = source.blendDstAlpha; - this.blendEquationAlpha = source.blendEquationAlpha; - - this.depthFunc = source.depthFunc; - this.depthTest = source.depthTest; - this.depthWrite = source.depthWrite; - - this.stencilWriteMask = source.stencilWriteMask; - this.stencilFunc = source.stencilFunc; - this.stencilRef = source.stencilRef; - this.stencilFuncMask = source.stencilFuncMask; - this.stencilFail = source.stencilFail; - this.stencilZFail = source.stencilZFail; - this.stencilZPass = source.stencilZPass; - this.stencilWrite = source.stencilWrite; - - const srcPlanes = source.clippingPlanes; - let dstPlanes = null; - - if ( srcPlanes !== null ) { - - const n = srcPlanes.length; - dstPlanes = new Array( n ); - - for ( let i = 0; i !== n; ++ i ) { - - dstPlanes[ i ] = srcPlanes[ i ].clone(); - - } - - } - - this.clippingPlanes = dstPlanes; - this.clipIntersection = source.clipIntersection; - this.clipShadows = source.clipShadows; - - this.shadowSide = source.shadowSide; - - this.colorWrite = source.colorWrite; - - this.precision = source.precision; - - this.polygonOffset = source.polygonOffset; - this.polygonOffsetFactor = source.polygonOffsetFactor; - this.polygonOffsetUnits = source.polygonOffsetUnits; - - this.dithering = source.dithering; - - this.alphaTest = source.alphaTest; - this.alphaToCoverage = source.alphaToCoverage; - this.premultipliedAlpha = source.premultipliedAlpha; - this.forceSinglePass = source.forceSinglePass; - - this.visible = source.visible; - - this.toneMapped = source.toneMapped; - - this.userData = JSON.parse( JSON.stringify( source.userData ) ); - - return this; - - } - - dispose() { - - this.dispatchEvent( { type: 'dispose' } ); - - } - - set needsUpdate( value ) { - - if ( value === true ) this.version ++; - - } - -} - -const _colorKeywords = { 'aliceblue': 0xF0F8FF, 'antiquewhite': 0xFAEBD7, 'aqua': 0x00FFFF, 'aquamarine': 0x7FFFD4, 'azure': 0xF0FFFF, - 'beige': 0xF5F5DC, 'bisque': 0xFFE4C4, 'black': 0x000000, 'blanchedalmond': 0xFFEBCD, 'blue': 0x0000FF, 'blueviolet': 0x8A2BE2, - 'brown': 0xA52A2A, 'burlywood': 0xDEB887, 'cadetblue': 0x5F9EA0, 'chartreuse': 0x7FFF00, 'chocolate': 0xD2691E, 'coral': 0xFF7F50, - 'cornflowerblue': 0x6495ED, 'cornsilk': 0xFFF8DC, 'crimson': 0xDC143C, 'cyan': 0x00FFFF, 'darkblue': 0x00008B, 'darkcyan': 0x008B8B, - 'darkgoldenrod': 0xB8860B, 'darkgray': 0xA9A9A9, 'darkgreen': 0x006400, 'darkgrey': 0xA9A9A9, 'darkkhaki': 0xBDB76B, 'darkmagenta': 0x8B008B, - 'darkolivegreen': 0x556B2F, 'darkorange': 0xFF8C00, 'darkorchid': 0x9932CC, 'darkred': 0x8B0000, 'darksalmon': 0xE9967A, 'darkseagreen': 0x8FBC8F, - 'darkslateblue': 0x483D8B, 'darkslategray': 0x2F4F4F, 'darkslategrey': 0x2F4F4F, 'darkturquoise': 0x00CED1, 'darkviolet': 0x9400D3, - 'deeppink': 0xFF1493, 'deepskyblue': 0x00BFFF, 'dimgray': 0x696969, 'dimgrey': 0x696969, 'dodgerblue': 0x1E90FF, 'firebrick': 0xB22222, - 'floralwhite': 0xFFFAF0, 'forestgreen': 0x228B22, 'fuchsia': 0xFF00FF, 'gainsboro': 0xDCDCDC, 'ghostwhite': 0xF8F8FF, 'gold': 0xFFD700, - 'goldenrod': 0xDAA520, 'gray': 0x808080, 'green': 0x008000, 'greenyellow': 0xADFF2F, 'grey': 0x808080, 'honeydew': 0xF0FFF0, 'hotpink': 0xFF69B4, - 'indianred': 0xCD5C5C, 'indigo': 0x4B0082, 'ivory': 0xFFFFF0, 'khaki': 0xF0E68C, 'lavender': 0xE6E6FA, 'lavenderblush': 0xFFF0F5, 'lawngreen': 0x7CFC00, - 'lemonchiffon': 0xFFFACD, 'lightblue': 0xADD8E6, 'lightcoral': 0xF08080, 'lightcyan': 0xE0FFFF, 'lightgoldenrodyellow': 0xFAFAD2, 'lightgray': 0xD3D3D3, - 'lightgreen': 0x90EE90, 'lightgrey': 0xD3D3D3, 'lightpink': 0xFFB6C1, 'lightsalmon': 0xFFA07A, 'lightseagreen': 0x20B2AA, 'lightskyblue': 0x87CEFA, - 'lightslategray': 0x778899, 'lightslategrey': 0x778899, 'lightsteelblue': 0xB0C4DE, 'lightyellow': 0xFFFFE0, 'lime': 0x00FF00, 'limegreen': 0x32CD32, - 'linen': 0xFAF0E6, 'magenta': 0xFF00FF, 'maroon': 0x800000, 'mediumaquamarine': 0x66CDAA, 'mediumblue': 0x0000CD, 'mediumorchid': 0xBA55D3, - 'mediumpurple': 0x9370DB, 'mediumseagreen': 0x3CB371, 'mediumslateblue': 0x7B68EE, 'mediumspringgreen': 0x00FA9A, 'mediumturquoise': 0x48D1CC, - 'mediumvioletred': 0xC71585, 'midnightblue': 0x191970, 'mintcream': 0xF5FFFA, 'mistyrose': 0xFFE4E1, 'moccasin': 0xFFE4B5, 'navajowhite': 0xFFDEAD, - 'navy': 0x000080, 'oldlace': 0xFDF5E6, 'olive': 0x808000, 'olivedrab': 0x6B8E23, 'orange': 0xFFA500, 'orangered': 0xFF4500, 'orchid': 0xDA70D6, - 'palegoldenrod': 0xEEE8AA, 'palegreen': 0x98FB98, 'paleturquoise': 0xAFEEEE, 'palevioletred': 0xDB7093, 'papayawhip': 0xFFEFD5, 'peachpuff': 0xFFDAB9, - 'peru': 0xCD853F, 'pink': 0xFFC0CB, 'plum': 0xDDA0DD, 'powderblue': 0xB0E0E6, 'purple': 0x800080, 'rebeccapurple': 0x663399, 'red': 0xFF0000, 'rosybrown': 0xBC8F8F, - 'royalblue': 0x4169E1, 'saddlebrown': 0x8B4513, 'salmon': 0xFA8072, 'sandybrown': 0xF4A460, 'seagreen': 0x2E8B57, 'seashell': 0xFFF5EE, - 'sienna': 0xA0522D, 'silver': 0xC0C0C0, 'skyblue': 0x87CEEB, 'slateblue': 0x6A5ACD, 'slategray': 0x708090, 'slategrey': 0x708090, 'snow': 0xFFFAFA, - 'springgreen': 0x00FF7F, 'steelblue': 0x4682B4, 'tan': 0xD2B48C, 'teal': 0x008080, 'thistle': 0xD8BFD8, 'tomato': 0xFF6347, 'turquoise': 0x40E0D0, - 'violet': 0xEE82EE, 'wheat': 0xF5DEB3, 'white': 0xFFFFFF, 'whitesmoke': 0xF5F5F5, 'yellow': 0xFFFF00, 'yellowgreen': 0x9ACD32 }; - -const _hslA = { h: 0, s: 0, l: 0 }; -const _hslB = { h: 0, s: 0, l: 0 }; - -function hue2rgb( p, q, t ) { - - if ( t < 0 ) t += 1; - if ( t > 1 ) t -= 1; - if ( t < 1 / 6 ) return p + ( q - p ) * 6 * t; - if ( t < 1 / 2 ) return q; - if ( t < 2 / 3 ) return p + ( q - p ) * 6 * ( 2 / 3 - t ); - return p; - -} - -class Color { - - constructor( r, g, b ) { - - this.isColor = true; - - this.r = 1; - this.g = 1; - this.b = 1; - - return this.set( r, g, b ); - - } - - set( r, g, b ) { - - if ( g === undefined && b === undefined ) { - - // r is THREE.Color, hex or string - - const value = r; - - if ( value && value.isColor ) { - - this.copy( value ); - - } else if ( typeof value === 'number' ) { - - this.setHex( value ); - - } else if ( typeof value === 'string' ) { - - this.setStyle( value ); - - } - - } else { - - this.setRGB( r, g, b ); - - } - - return this; - - } - - setScalar( scalar ) { - - this.r = scalar; - this.g = scalar; - this.b = scalar; - - return this; - - } - - setHex( hex, colorSpace = SRGBColorSpace ) { - - hex = Math.floor( hex ); - - this.r = ( hex >> 16 & 255 ) / 255; - this.g = ( hex >> 8 & 255 ) / 255; - this.b = ( hex & 255 ) / 255; - - ColorManagement.toWorkingColorSpace( this, colorSpace ); - - return this; - - } - - setRGB( r, g, b, colorSpace = ColorManagement.workingColorSpace ) { - - this.r = r; - this.g = g; - this.b = b; - - ColorManagement.toWorkingColorSpace( this, colorSpace ); - - return this; - - } - - setHSL( h, s, l, colorSpace = ColorManagement.workingColorSpace ) { - - // h,s,l ranges are in 0.0 - 1.0 - h = euclideanModulo( h, 1 ); - s = clamp( s, 0, 1 ); - l = clamp( l, 0, 1 ); - - if ( s === 0 ) { - - this.r = this.g = this.b = l; - - } else { - - const p = l <= 0.5 ? l * ( 1 + s ) : l + s - ( l * s ); - const q = ( 2 * l ) - p; - - this.r = hue2rgb( q, p, h + 1 / 3 ); - this.g = hue2rgb( q, p, h ); - this.b = hue2rgb( q, p, h - 1 / 3 ); - - } - - ColorManagement.toWorkingColorSpace( this, colorSpace ); - - return this; - - } - - setStyle( style, colorSpace = SRGBColorSpace ) { - - function handleAlpha( string ) { - - if ( string === undefined ) return; - - if ( parseFloat( string ) < 1 ) { - - console.warn( 'THREE.Color: Alpha component of ' + style + ' will be ignored.' ); - - } - - } - - - let m; - - if ( m = /^(\w+)\(([^\)]*)\)/.exec( style ) ) { - - // rgb / hsl - - let color; - const name = m[ 1 ]; - const components = m[ 2 ]; - - switch ( name ) { - - case 'rgb': - case 'rgba': - - if ( color = /^\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) { - - // rgb(255,0,0) rgba(255,0,0,0.5) - - handleAlpha( color[ 4 ] ); - - return this.setRGB( - Math.min( 255, parseInt( color[ 1 ], 10 ) ) / 255, - Math.min( 255, parseInt( color[ 2 ], 10 ) ) / 255, - Math.min( 255, parseInt( color[ 3 ], 10 ) ) / 255, - colorSpace - ); - - } - - if ( color = /^\s*(\d+)\%\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) { - - // rgb(100%,0%,0%) rgba(100%,0%,0%,0.5) - - handleAlpha( color[ 4 ] ); - - return this.setRGB( - Math.min( 100, parseInt( color[ 1 ], 10 ) ) / 100, - Math.min( 100, parseInt( color[ 2 ], 10 ) ) / 100, - Math.min( 100, parseInt( color[ 3 ], 10 ) ) / 100, - colorSpace - ); - - } - - break; - - case 'hsl': - case 'hsla': - - if ( color = /^\s*(\d*\.?\d+)\s*,\s*(\d*\.?\d+)\%\s*,\s*(\d*\.?\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) { - - // hsl(120,50%,50%) hsla(120,50%,50%,0.5) - - handleAlpha( color[ 4 ] ); - - return this.setHSL( - parseFloat( color[ 1 ] ) / 360, - parseFloat( color[ 2 ] ) / 100, - parseFloat( color[ 3 ] ) / 100, - colorSpace - ); - - } - - break; - - default: - - console.warn( 'THREE.Color: Unknown color model ' + style ); - - } - - } else if ( m = /^\#([A-Fa-f\d]+)$/.exec( style ) ) { - - // hex color - - const hex = m[ 1 ]; - const size = hex.length; - - if ( size === 3 ) { - - // #ff0 - return this.setRGB( - parseInt( hex.charAt( 0 ), 16 ) / 15, - parseInt( hex.charAt( 1 ), 16 ) / 15, - parseInt( hex.charAt( 2 ), 16 ) / 15, - colorSpace - ); - - } else if ( size === 6 ) { - - // #ff0000 - return this.setHex( parseInt( hex, 16 ), colorSpace ); - - } else { - - console.warn( 'THREE.Color: Invalid hex color ' + style ); - - } - - } else if ( style && style.length > 0 ) { - - return this.setColorName( style, colorSpace ); + return this.setColorName( style, colorSpace ); } @@ -9088,6 +15699,19 @@ class Color { } + /** + * Sets this color from a color name. Faster than {@link Color#setStyle} if + * you don't need the other CSS-style formats. + * + * For convenience, the list of names is exposed in `Color.NAMES` as a hash. + * ```js + * Color.NAMES.aliceblue // returns 0xF0F8FF + * ``` + * + * @param {string} style - The color name. + * @param {string} [colorSpace=SRGBColorSpace] - The color space. + * @return {Color} A reference to this color. + */ setColorName( style, colorSpace = SRGBColorSpace ) { // color keywords @@ -9109,12 +15733,23 @@ class Color { } + /** + * Returns a new color with copied values from this instance. + * + * @return {Color} A clone of this instance. + */ clone() { return new this.constructor( this.r, this.g, this.b ); } + /** + * Copies the values of the given color to this instance. + * + * @param {Color} color - The color to copy. + * @return {Color} A reference to this color. + */ copy( color ) { this.r = color.r; @@ -9125,6 +15760,13 @@ class Color { } + /** + * Copies the given color into this color, and then converts this color from + * `SRGBColorSpace` to `LinearSRGBColorSpace`. + * + * @param {Color} color - The color to copy/convert. + * @return {Color} A reference to this color. + */ copySRGBToLinear( color ) { this.r = SRGBToLinear( color.r ); @@ -9135,6 +15777,13 @@ class Color { } + /** + * Copies the given color into this color, and then converts this color from + * `LinearSRGBColorSpace` to `SRGBColorSpace`. + * + * @param {Color} color - The color to copy/convert. + * @return {Color} A reference to this color. + */ copyLinearToSRGB( color ) { this.r = LinearToSRGB( color.r ); @@ -9145,6 +15794,11 @@ class Color { } + /** + * Converts this color from `SRGBColorSpace` to `LinearSRGBColorSpace`. + * + * @return {Color} A reference to this color. + */ convertSRGBToLinear() { this.copySRGBToLinear( this ); @@ -9153,6 +15807,11 @@ class Color { } + /** + * Converts this color from `LinearSRGBColorSpace` to `SRGBColorSpace`. + * + * @return {Color} A reference to this color. + */ convertLinearToSRGB() { this.copyLinearToSRGB( this ); @@ -9161,25 +15820,45 @@ class Color { } + /** + * Returns the hexadecimal value of this color. + * + * @param {string} [colorSpace=SRGBColorSpace] - The color space. + * @return {number} The hexadecimal value. + */ getHex( colorSpace = SRGBColorSpace ) { - ColorManagement.fromWorkingColorSpace( _color.copy( this ), colorSpace ); + ColorManagement.workingToColorSpace( _color.copy( this ), colorSpace ); return Math.round( clamp( _color.r * 255, 0, 255 ) ) * 65536 + Math.round( clamp( _color.g * 255, 0, 255 ) ) * 256 + Math.round( clamp( _color.b * 255, 0, 255 ) ); } + /** + * Returns the hexadecimal value of this color as a string (for example, 'FFFFFF'). + * + * @param {string} [colorSpace=SRGBColorSpace] - The color space. + * @return {string} The hexadecimal value as a string. + */ getHexString( colorSpace = SRGBColorSpace ) { - return ( '000000' + this.getHex( colorSpace ).toString( 16 ) ).slice( - 6 ); + return ( '000000' + this.getHex( colorSpace ).toString( 16 ) ).slice( -6 ); } + /** + * Converts the colors RGB values into the HSL format and stores them into the + * given target object. + * + * @param {{h:number,s:number,l:number}} target - The target object that is used to store the method's result. + * @param {string} [colorSpace=ColorManagement.workingColorSpace] - The color space. + * @return {{h:number,s:number,l:number}} The HSL representation of this color. + */ getHSL( target, colorSpace = ColorManagement.workingColorSpace ) { // h,s,l ranges are in 0.0 - 1.0 - ColorManagement.fromWorkingColorSpace( _color.copy( this ), colorSpace ); + ColorManagement.workingToColorSpace( _color.copy( this ), colorSpace ); const r = _color.r, g = _color.g, b = _color.b; @@ -9220,9 +15899,16 @@ class Color { } + /** + * Returns the RGB values of this color and stores them into the given target object. + * + * @param {Color} target - The target color that is used to store the method's result. + * @param {string} [colorSpace=ColorManagement.workingColorSpace] - The color space. + * @return {Color} The RGB representation of this color. + */ getRGB( target, colorSpace = ColorManagement.workingColorSpace ) { - ColorManagement.fromWorkingColorSpace( _color.copy( this ), colorSpace ); + ColorManagement.workingToColorSpace( _color.copy( this ), colorSpace ); target.r = _color.r; target.g = _color.g; @@ -9232,9 +15918,15 @@ class Color { } + /** + * Returns the value of this color as a CSS style string. Example: `rgb(255,0,0)`. + * + * @param {string} [colorSpace=SRGBColorSpace] - The color space. + * @return {string} The CSS representation of this color. + */ getStyle( colorSpace = SRGBColorSpace ) { - ColorManagement.fromWorkingColorSpace( _color.copy( this ), colorSpace ); + ColorManagement.workingToColorSpace( _color.copy( this ), colorSpace ); const r = _color.r, g = _color.g, b = _color.b; @@ -9249,20 +15941,32 @@ class Color { } + /** + * Adds the given HSL values to this color's values. + * Internally, this converts the color's RGB values to HSL, adds HSL + * and then converts the color back to RGB. + * + * @param {number} h - Hue value between `0.0` and `1.0`. + * @param {number} s - Saturation value between `0.0` and `1.0`. + * @param {number} l - Lightness value between `0.0` and `1.0`. + * @return {Color} A reference to this color. + */ offsetHSL( h, s, l ) { this.getHSL( _hslA ); - _hslA.h += h; _hslA.s += s; _hslA.l += l; - - this.setHSL( _hslA.h, _hslA.s, _hslA.l ); - - return this; + return this.setHSL( _hslA.h + h, _hslA.s + s, _hslA.l + l ); } - add( color ) { - + /** + * Adds the RGB values of the given color to the RGB values of this color. + * + * @param {Color} color - The color to add. + * @return {Color} A reference to this color. + */ + add( color ) { + this.r += color.r; this.g += color.g; this.b += color.b; @@ -9271,6 +15975,13 @@ class Color { } + /** + * Adds the RGB values of the given colors and stores the result in this instance. + * + * @param {Color} color1 - The first color. + * @param {Color} color2 - The second color. + * @return {Color} A reference to this color. + */ addColors( color1, color2 ) { this.r = color1.r + color2.r; @@ -9281,6 +15992,12 @@ class Color { } + /** + * Adds the given scalar value to the RGB values of this color. + * + * @param {number} s - The scalar to add. + * @return {Color} A reference to this color. + */ addScalar( s ) { this.r += s; @@ -9291,6 +16008,12 @@ class Color { } + /** + * Subtracts the RGB values of the given color from the RGB values of this color. + * + * @param {Color} color - The color to subtract. + * @return {Color} A reference to this color. + */ sub( color ) { this.r = Math.max( 0, this.r - color.r ); @@ -9301,6 +16024,12 @@ class Color { } + /** + * Multiplies the RGB values of the given color with the RGB values of this color. + * + * @param {Color} color - The color to multiply. + * @return {Color} A reference to this color. + */ multiply( color ) { this.r *= color.r; @@ -9311,6 +16040,12 @@ class Color { } + /** + * Multiplies the given scalar value with the RGB values of this color. + * + * @param {number} s - The scalar to multiply. + * @return {Color} A reference to this color. + */ multiplyScalar( s ) { this.r *= s; @@ -9321,6 +16056,15 @@ class Color { } + /** + * Linearly interpolates this color's RGB values toward the RGB values of the + * given color. The alpha argument can be thought of as the ratio between + * the two colors, where `0.0` is this color and `1.0` is the first argument. + * + * @param {Color} color - The color to converge on. + * @param {number} alpha - The interpolation factor in the closed interval `[0,1]`. + * @return {Color} A reference to this color. + */ lerp( color, alpha ) { this.r += ( color.r - this.r ) * alpha; @@ -9331,6 +16075,16 @@ class Color { } + /** + * Linearly interpolates between the given colors and stores the result in this instance. + * The alpha argument can be thought of as the ratio between the two colors, where `0.0` + * is the first and `1.0` is the second color. + * + * @param {Color} color1 - The first color. + * @param {Color} color2 - The second color. + * @param {number} alpha - The interpolation factor in the closed interval `[0,1]`. + * @return {Color} A reference to this color. + */ lerpColors( color1, color2, alpha ) { this.r = color1.r + ( color2.r - color1.r ) * alpha; @@ -9341,6 +16095,17 @@ class Color { } + /** + * Linearly interpolates this color's HSL values toward the HSL values of the + * given color. It differs from {@link Color#lerp} by not interpolating straight + * from one color to the other, but instead going through all the hues in between + * those two colors. The alpha argument can be thought of as the ratio between + * the two colors, where 0.0 is this color and 1.0 is the first argument. + * + * @param {Color} color - The color to converge on. + * @param {number} alpha - The interpolation factor in the closed interval `[0,1]`. + * @return {Color} A reference to this color. + */ lerpHSL( color, alpha ) { this.getHSL( _hslA ); @@ -9356,6 +16121,12 @@ class Color { } + /** + * Sets the color's RGB components from the given 3D vector. + * + * @param {Vector3} v - The vector to set. + * @return {Color} A reference to this color. + */ setFromVector3( v ) { this.r = v.x; @@ -9366,6 +16137,12 @@ class Color { } + /** + * Transforms this color with the given 3x3 matrix. + * + * @param {Matrix3} m - The matrix. + * @return {Color} A reference to this color. + */ applyMatrix3( m ) { const r = this.r, g = this.g, b = this.b; @@ -9379,12 +16156,25 @@ class Color { } + /** + * Returns `true` if this color is equal with the given one. + * + * @param {Color} c - The color to test for equality. + * @return {boolean} Whether this bounding color is equal with the given one. + */ equals( c ) { return ( c.r === this.r ) && ( c.g === this.g ) && ( c.b === this.b ); } + /** + * Sets this color's RGB components from the given array. + * + * @param {Array} array - An array holding the RGB values. + * @param {number} [offset=0] - The offset into the array. + * @return {Color} A reference to this color. + */ fromArray( array, offset = 0 ) { this.r = array[ offset ]; @@ -9395,6 +16185,14 @@ class Color { } + /** + * Writes the RGB components of this color to the given array. If no array is provided, + * the method returns a new instance. + * + * @param {Array} [array=[]] - The target array holding the color components. + * @param {number} [offset=0] - Index of the first element in the array. + * @return {Array} The color components. + */ toArray( array = [], offset = 0 ) { array[ offset ] = this.r; @@ -9405,6 +16203,13 @@ class Color { } + /** + * Sets the components of this color from the given buffer attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute holding color data. + * @param {number} index - The index into the attribute. + * @return {Color} A reference to this color. + */ fromBufferAttribute( attribute, index ) { this.r = attribute.getX( index ); @@ -9415,6 +16220,12 @@ class Color { } + /** + * This methods defines the serialization result of this class. Returns the color + * as a hexadecimal value. + * + * @return {number} The hexadecimal value. + */ toJSON() { return this.getHex(); @@ -9433,628 +16244,1215 @@ class Color { const _color = /*@__PURE__*/ new Color(); +/** + * A dictionary with X11 color names. + * + * Note that multiple words such as Dark Orange become the string 'darkorange'. + * + * @static + * @type {Object} + */ Color.NAMES = _colorKeywords; -class MeshBasicMaterial extends Material { - - constructor( parameters ) { +let _materialId = 0; - super(); +/** + * Abstract base class for materials. + * + * Materials define the appearance of renderable 3D objects. + * + * @abstract + * @augments EventDispatcher + */ +class Material extends EventDispatcher { - this.isMeshBasicMaterial = true; + /** + * Constructs a new material. + */ + constructor() { - this.type = 'MeshBasicMaterial'; + super(); - this.color = new Color( 0xffffff ); // emissive + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMaterial = true; - this.map = null; + /** + * The ID of the material. + * + * @name Material#id + * @type {number} + * @readonly + */ + Object.defineProperty( this, 'id', { value: _materialId ++ } ); - this.lightMap = null; - this.lightMapIntensity = 1.0; + /** + * The UUID of the material. + * + * @type {string} + * @readonly + */ + this.uuid = generateUUID(); - this.aoMap = null; - this.aoMapIntensity = 1.0; + /** + * The name of the material. + * + * @type {string} + */ + this.name = ''; - this.specularMap = null; + /** + * The type property is used for detecting the object type + * in context of serialization/deserialization. + * + * @type {string} + * @readonly + */ + this.type = 'Material'; - this.alphaMap = null; + /** + * Defines the blending type of the material. + * + * It must be set to `CustomBlending` if custom blending properties like + * {@link Material#blendSrc}, {@link Material#blendDst} or {@link Material#blendEquation} + * should have any effect. + * + * @type {(NoBlending|NormalBlending|AdditiveBlending|SubtractiveBlending|MultiplyBlending|CustomBlending)} + * @default NormalBlending + */ + this.blending = NormalBlending; - this.envMap = null; - this.combine = MultiplyOperation; - this.reflectivity = 1; - this.refractionRatio = 0.98; + /** + * Defines which side of faces will be rendered - front, back or both. + * + * @type {(FrontSide|BackSide|DoubleSide)} + * @default FrontSide + */ + this.side = FrontSide; - this.wireframe = false; - this.wireframeLinewidth = 1; - this.wireframeLinecap = 'round'; - this.wireframeLinejoin = 'round'; + /** + * If set to `true`, vertex colors should be used. + * + * The engine supports RGB and RGBA vertex colors depending on whether a three (RGB) or + * four (RGBA) component color buffer attribute is used. + * + * @type {boolean} + * @default false + */ + this.vertexColors = false; - this.fog = true; + /** + * Defines how transparent the material is. + * A value of `0.0` indicates fully transparent, `1.0` is fully opaque. + * + * If the {@link Material#transparent} is not set to `true`, + * the material will remain fully opaque and this value will only affect its color. + * + * @type {number} + * @default 1 + */ + this.opacity = 1; - this.setValues( parameters ); + /** + * Defines whether this material is transparent. This has an effect on + * rendering as transparent objects need special treatment and are rendered + * after non-transparent objects. + * + * When set to true, the extent to which the material is transparent is + * controlled by {@link Material#opacity}. + * + * @type {boolean} + * @default false + */ + this.transparent = false; - } + /** + * Enables alpha hashed transparency, an alternative to {@link Material#transparent} or + * {@link Material#alphaTest}. The material will not be rendered if opacity is lower than + * a random threshold. Randomization introduces some grain or noise, but approximates alpha + * blending without the associated problems of sorting. Using TAA can reduce the resulting noise. + * + * @type {boolean} + * @default false + */ + this.alphaHash = false; - copy( source ) { + /** + * Defines the blending source factor. + * + * @type {(ZeroFactor|OneFactor|SrcColorFactor|OneMinusSrcColorFactor|SrcAlphaFactor|OneMinusSrcAlphaFactor|DstAlphaFactor|OneMinusDstAlphaFactor|DstColorFactor|OneMinusDstColorFactor|SrcAlphaSaturateFactor|ConstantColorFactor|OneMinusConstantColorFactor|ConstantAlphaFactor|OneMinusConstantAlphaFactor)} + * @default SrcAlphaFactor + */ + this.blendSrc = SrcAlphaFactor; - super.copy( source ); + /** + * Defines the blending destination factor. + * + * @type {(ZeroFactor|OneFactor|SrcColorFactor|OneMinusSrcColorFactor|SrcAlphaFactor|OneMinusSrcAlphaFactor|DstAlphaFactor|OneMinusDstAlphaFactor|DstColorFactor|OneMinusDstColorFactor|SrcAlphaSaturateFactor|ConstantColorFactor|OneMinusConstantColorFactor|ConstantAlphaFactor|OneMinusConstantAlphaFactor)} + * @default OneMinusSrcAlphaFactor + */ + this.blendDst = OneMinusSrcAlphaFactor; - this.color.copy( source.color ); + /** + * Defines the blending equation. + * + * @type {(AddEquation|SubtractEquation|ReverseSubtractEquation|MinEquation|MaxEquation)} + * @default AddEquation + */ + this.blendEquation = AddEquation; - this.map = source.map; + /** + * Defines the blending source alpha factor. + * + * @type {?(ZeroFactor|OneFactor|SrcColorFactor|OneMinusSrcColorFactor|SrcAlphaFactor|OneMinusSrcAlphaFactor|DstAlphaFactor|OneMinusDstAlphaFactor|DstColorFactor|OneMinusDstColorFactor|SrcAlphaSaturateFactor|ConstantColorFactor|OneMinusConstantColorFactor|ConstantAlphaFactor|OneMinusConstantAlphaFactor)} + * @default null + */ + this.blendSrcAlpha = null; - this.lightMap = source.lightMap; - this.lightMapIntensity = source.lightMapIntensity; + /** + * Defines the blending destination alpha factor. + * + * @type {?(ZeroFactor|OneFactor|SrcColorFactor|OneMinusSrcColorFactor|SrcAlphaFactor|OneMinusSrcAlphaFactor|DstAlphaFactor|OneMinusDstAlphaFactor|DstColorFactor|OneMinusDstColorFactor|SrcAlphaSaturateFactor|ConstantColorFactor|OneMinusConstantColorFactor|ConstantAlphaFactor|OneMinusConstantAlphaFactor)} + * @default null + */ + this.blendDstAlpha = null; - this.aoMap = source.aoMap; - this.aoMapIntensity = source.aoMapIntensity; + /** + * Defines the blending equation of the alpha channel. + * + * @type {?(AddEquation|SubtractEquation|ReverseSubtractEquation|MinEquation|MaxEquation)} + * @default null + */ + this.blendEquationAlpha = null; - this.specularMap = source.specularMap; + /** + * Represents the RGB values of the constant blend color. + * + * This property has only an effect when using custom blending with `ConstantColor` or `OneMinusConstantColor`. + * + * @type {Color} + * @default (0,0,0) + */ + this.blendColor = new Color( 0, 0, 0 ); - this.alphaMap = source.alphaMap; + /** + * Represents the alpha value of the constant blend color. + * + * This property has only an effect when using custom blending with `ConstantAlpha` or `OneMinusConstantAlpha`. + * + * @type {number} + * @default 0 + */ + this.blendAlpha = 0; - this.envMap = source.envMap; - this.combine = source.combine; - this.reflectivity = source.reflectivity; - this.refractionRatio = source.refractionRatio; + /** + * Defines the depth function. + * + * @type {(NeverDepth|AlwaysDepth|LessDepth|LessEqualDepth|EqualDepth|GreaterEqualDepth|GreaterDepth|NotEqualDepth)} + * @default LessEqualDepth + */ + this.depthFunc = LessEqualDepth; - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; - this.wireframeLinecap = source.wireframeLinecap; - this.wireframeLinejoin = source.wireframeLinejoin; + /** + * Whether to have depth test enabled when rendering this material. + * When the depth test is disabled, the depth write will also be implicitly disabled. + * + * @type {boolean} + * @default true + */ + this.depthTest = true; - this.fog = source.fog; + /** + * Whether rendering this material has any effect on the depth buffer. + * + * When drawing 2D overlays it can be useful to disable the depth writing in + * order to layer several things together without creating z-index artifacts. + * + * @type {boolean} + * @default true + */ + this.depthWrite = true; - return this; + /** + * The bit mask to use when writing to the stencil buffer. + * + * @type {number} + * @default 0xff + */ + this.stencilWriteMask = 0xff; - } + /** + * The stencil comparison function to use. + * + * @type {NeverStencilFunc|LessStencilFunc|EqualStencilFunc|LessEqualStencilFunc|GreaterStencilFunc|NotEqualStencilFunc|GreaterEqualStencilFunc|AlwaysStencilFunc} + * @default AlwaysStencilFunc + */ + this.stencilFunc = AlwaysStencilFunc; -} + /** + * The value to use when performing stencil comparisons or stencil operations. + * + * @type {number} + * @default 0 + */ + this.stencilRef = 0; -// Fast Half Float Conversions, http://www.fox-toolkit.org/ftp/fasthalffloatconversion.pdf + /** + * The bit mask to use when comparing against the stencil buffer. + * + * @type {number} + * @default 0xff + */ + this.stencilFuncMask = 0xff; -const _tables = /*@__PURE__*/ _generateTables(); + /** + * Which stencil operation to perform when the comparison function returns `false`. + * + * @type {ZeroStencilOp|KeepStencilOp|ReplaceStencilOp|IncrementStencilOp|DecrementStencilOp|IncrementWrapStencilOp|DecrementWrapStencilOp|InvertStencilOp} + * @default KeepStencilOp + */ + this.stencilFail = KeepStencilOp; -function _generateTables() { + /** + * Which stencil operation to perform when the comparison function returns + * `true` but the depth test fails. + * + * @type {ZeroStencilOp|KeepStencilOp|ReplaceStencilOp|IncrementStencilOp|DecrementStencilOp|IncrementWrapStencilOp|DecrementWrapStencilOp|InvertStencilOp} + * @default KeepStencilOp + */ + this.stencilZFail = KeepStencilOp; - // float32 to float16 helpers + /** + * Which stencil operation to perform when the comparison function returns + * `true` and the depth test passes. + * + * @type {ZeroStencilOp|KeepStencilOp|ReplaceStencilOp|IncrementStencilOp|DecrementStencilOp|IncrementWrapStencilOp|DecrementWrapStencilOp|InvertStencilOp} + * @default KeepStencilOp + */ + this.stencilZPass = KeepStencilOp; - const buffer = new ArrayBuffer( 4 ); - const floatView = new Float32Array( buffer ); - const uint32View = new Uint32Array( buffer ); + /** + * Whether stencil operations are performed against the stencil buffer. In + * order to perform writes or comparisons against the stencil buffer this + * value must be `true`. + * + * @type {boolean} + * @default false + */ + this.stencilWrite = false; - const baseTable = new Uint32Array( 512 ); - const shiftTable = new Uint32Array( 512 ); + /** + * User-defined clipping planes specified as THREE.Plane objects in world + * space. These planes apply to the objects this material is attached to. + * Points in space whose signed distance to the plane is negative are clipped + * (not rendered). This requires {@link WebGLRenderer#localClippingEnabled} to + * be `true`. + * + * @type {?Array} + * @default null + */ + this.clippingPlanes = null; - for ( let i = 0; i < 256; ++ i ) { + /** + * Changes the behavior of clipping planes so that only their intersection is + * clipped, rather than their union. + * + * @type {boolean} + * @default false + */ + this.clipIntersection = false; - const e = i - 127; + /** + * Defines whether to clip shadows according to the clipping planes specified + * on this material. + * + * @type {boolean} + * @default false + */ + this.clipShadows = false; - // very small number (0, -0) + /** + * Defines which side of faces cast shadows. If `null`, the side casting shadows + * is determined as follows: + * + * - When {@link Material#side} is set to `FrontSide`, the back side cast shadows. + * - When {@link Material#side} is set to `BackSide`, the front side cast shadows. + * - When {@link Material#side} is set to `DoubleSide`, both sides cast shadows. + * + * @type {?(FrontSide|BackSide|DoubleSide)} + * @default null + */ + this.shadowSide = null; - if ( e < - 27 ) { + /** + * Whether to render the material's color. + * + * This can be used in conjunction with {@link Object3D#renderOder} to create invisible + * objects that occlude other objects. + * + * @type {boolean} + * @default true + */ + this.colorWrite = true; - baseTable[ i ] = 0x0000; - baseTable[ i | 0x100 ] = 0x8000; - shiftTable[ i ] = 24; - shiftTable[ i | 0x100 ] = 24; + /** + * Override the renderer's default precision for this material. + * + * @type {?('highp'|'mediump'|'lowp')} + * @default null + */ + this.precision = null; - // small number (denorm) + /** + * Whether to use polygon offset or not. When enabled, each fragment's depth value will + * be offset after it is interpolated from the depth values of the appropriate vertices. + * The offset is added before the depth test is performed and before the value is written + * into the depth buffer. + * + * Can be useful for rendering hidden-line images, for applying decals to surfaces, and for + * rendering solids with highlighted edges. + * + * @type {boolean} + * @default false + */ + this.polygonOffset = false; - } else if ( e < - 14 ) { + /** + * Specifies a scale factor that is used to create a variable depth offset for each polygon. + * + * @type {number} + * @default 0 + */ + this.polygonOffsetFactor = 0; - baseTable[ i ] = 0x0400 >> ( - e - 14 ); - baseTable[ i | 0x100 ] = ( 0x0400 >> ( - e - 14 ) ) | 0x8000; - shiftTable[ i ] = - e - 1; - shiftTable[ i | 0x100 ] = - e - 1; + /** + * Is multiplied by an implementation-specific value to create a constant depth offset. + * + * @type {number} + * @default 0 + */ + this.polygonOffsetUnits = 0; - // normal number + /** + * Whether to apply dithering to the color to remove the appearance of banding. + * + * @type {boolean} + * @default false + */ + this.dithering = false; - } else if ( e <= 15 ) { + /** + * Whether alpha to coverage should be enabled or not. Can only be used with MSAA-enabled contexts + * (meaning when the renderer was created with *antialias* parameter set to `true`). Enabling this + * will smooth aliasing on clip plane edges and alphaTest-clipped edges. + * + * @type {boolean} + * @default false + */ + this.alphaToCoverage = false; - baseTable[ i ] = ( e + 15 ) << 10; - baseTable[ i | 0x100 ] = ( ( e + 15 ) << 10 ) | 0x8000; - shiftTable[ i ] = 13; - shiftTable[ i | 0x100 ] = 13; + /** + * Whether to premultiply the alpha (transparency) value. + * + * @type {boolean} + * @default false + */ + this.premultipliedAlpha = false; - // large number (Infinity, -Infinity) + /** + * Whether double-sided, transparent objects should be rendered with a single pass or not. + * + * The engine renders double-sided, transparent objects with two draw calls (back faces first, + * then front faces) to mitigate transparency artifacts. There are scenarios however where this + * approach produces no quality gains but still doubles draw calls e.g. when rendering flat + * vegetation like grass sprites. In these cases, set the `forceSinglePass` flag to `true` to + * disable the two pass rendering to avoid performance issues. + * + * @type {boolean} + * @default false + */ + this.forceSinglePass = false; - } else if ( e < 128 ) { + /** + * Whether it's possible to override the material with {@link Scene#overrideMaterial} or not. + * + * @type {boolean} + * @default true + */ + this.allowOverride = true; - baseTable[ i ] = 0x7c00; - baseTable[ i | 0x100 ] = 0xfc00; - shiftTable[ i ] = 24; - shiftTable[ i | 0x100 ] = 24; + /** + * Defines whether 3D objects using this material are visible. + * + * @type {boolean} + * @default true + */ + this.visible = true; - // stay (NaN, Infinity, -Infinity) + /** + * Defines whether this material is tone mapped according to the renderer's tone mapping setting. + * + * It is ignored when rendering to a render target or using post processing or when using + * `WebGPURenderer`. In all these cases, all materials are honored by tone mapping. + * + * @type {boolean} + * @default true + */ + this.toneMapped = true; - } else { + /** + * An object that can be used to store custom data about the Material. It + * should not hold references to functions as these will not be cloned. + * + * @type {Object} + */ + this.userData = {}; - baseTable[ i ] = 0x7c00; - baseTable[ i | 0x100 ] = 0xfc00; - shiftTable[ i ] = 13; - shiftTable[ i | 0x100 ] = 13; + /** + * This starts at `0` and counts how many times {@link Material#needsUpdate} is set to `true`. + * + * @type {number} + * @readonly + * @default 0 + */ + this.version = 0; - } + this._alphaTest = 0; } - // float16 to float32 helpers + /** + * Sets the alpha value to be used when running an alpha test. The material + * will not be rendered if the opacity is lower than this value. + * + * @type {number} + * @readonly + * @default 0 + */ + get alphaTest() { - const mantissaTable = new Uint32Array( 2048 ); - const exponentTable = new Uint32Array( 64 ); - const offsetTable = new Uint32Array( 64 ); + return this._alphaTest; - for ( let i = 1; i < 1024; ++ i ) { + } - let m = i << 13; // zero pad mantissa bits - let e = 0; // zero exponent + set alphaTest( value ) { - // normalized - while ( ( m & 0x00800000 ) === 0 ) { + if ( this._alphaTest > 0 !== value > 0 ) { - m <<= 1; - e -= 0x00800000; // decrement exponent + this.version ++; } - m &= ~ 0x00800000; // clear leading 1 bit - e += 0x38800000; // adjust bias - - mantissaTable[ i ] = m | e; + this._alphaTest = value; } - for ( let i = 1024; i < 2048; ++ i ) { - - mantissaTable[ i ] = 0x38000000 + ( ( i - 1024 ) << 13 ); + /** + * An optional callback that is executed immediately before the material is used to render a 3D object. + * + * This method can only be used when rendering with {@link WebGLRenderer}. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {Scene} scene - The scene. + * @param {Camera} camera - The camera that is used to render the scene. + * @param {BufferGeometry} geometry - The 3D object's geometry. + * @param {Object3D} object - The 3D object. + * @param {Object} group - The geometry group data. + */ + onBeforeRender( /* renderer, scene, camera, geometry, object, group */ ) {} - } + /** + * An optional callback that is executed immediately before the shader + * program is compiled. This function is called with the shader source code + * as a parameter. Useful for the modification of built-in materials. + * + * This method can only be used when rendering with {@link WebGLRenderer}. The + * recommended approach when customizing materials is to use `WebGPURenderer` with the new + * Node Material system and [TSL]{@link https://github.com/mrdoob/three.js/wiki/Three.js-Shading-Language}. + * + * @param {{vertexShader:string,fragmentShader:string,uniforms:Object}} shaderobject - The object holds the uniforms and the vertex and fragment shader source. + * @param {WebGLRenderer} renderer - A reference to the renderer. + */ + onBeforeCompile( /* shaderobject, renderer */ ) {} - for ( let i = 1; i < 31; ++ i ) { + /** + * In case {@link Material#onBeforeCompile} is used, this callback can be used to identify + * values of settings used in `onBeforeCompile()`, so three.js can reuse a cached + * shader or recompile the shader for this material as needed. + * + * This method can only be used when rendering with {@link WebGLRenderer}. + * + * @return {string} The custom program cache key. + */ + customProgramCacheKey() { - exponentTable[ i ] = i << 23; + return this.onBeforeCompile.toString(); } - exponentTable[ 31 ] = 0x47800000; - exponentTable[ 32 ] = 0x80000000; + /** + * This method can be used to set default values from parameter objects. + * It is a generic implementation so it can be used with different types + * of materials. + * + * @param {Object} [values] - The material values to set. + */ + setValues( values ) { - for ( let i = 33; i < 63; ++ i ) { + if ( values === undefined ) return; - exponentTable[ i ] = 0x80000000 + ( ( i - 32 ) << 23 ); + for ( const key in values ) { - } + const newValue = values[ key ]; - exponentTable[ 63 ] = 0xc7800000; + if ( newValue === undefined ) { - for ( let i = 1; i < 64; ++ i ) { + console.warn( `THREE.Material: parameter '${ key }' has value of undefined.` ); + continue; - if ( i !== 32 ) { + } - offsetTable[ i ] = 1024; + const currentValue = this[ key ]; - } + if ( currentValue === undefined ) { - } + console.warn( `THREE.Material: '${ key }' is not a property of THREE.${ this.type }.` ); + continue; - return { - floatView: floatView, - uint32View: uint32View, - baseTable: baseTable, - shiftTable: shiftTable, - mantissaTable: mantissaTable, - exponentTable: exponentTable, - offsetTable: offsetTable - }; + } -} + if ( currentValue && currentValue.isColor ) { -// float32 to float16 + currentValue.set( newValue ); -function toHalfFloat( val ) { + } else if ( ( currentValue && currentValue.isVector3 ) && ( newValue && newValue.isVector3 ) ) { - if ( Math.abs( val ) > 65504 ) console.warn( 'THREE.DataUtils.toHalfFloat(): Value out of range.' ); + currentValue.copy( newValue ); - val = clamp( val, - 65504, 65504 ); + } else { - _tables.floatView[ 0 ] = val; - const f = _tables.uint32View[ 0 ]; - const e = ( f >> 23 ) & 0x1ff; - return _tables.baseTable[ e ] + ( ( f & 0x007fffff ) >> _tables.shiftTable[ e ] ); + this[ key ] = newValue; -} + } -// float16 to float32 + } -function fromHalfFloat( val ) { + } - const m = val >> 10; - _tables.uint32View[ 0 ] = _tables.mantissaTable[ _tables.offsetTable[ m ] + ( val & 0x3ff ) ] + _tables.exponentTable[ m ]; - return _tables.floatView[ 0 ]; + /** + * Serializes the material into JSON. + * + * @param {?(Object|string)} meta - An optional value holding meta information about the serialization. + * @return {Object} A JSON object representing the serialized material. + * @see {@link ObjectLoader#parse} + */ + toJSON( meta ) { -} + const isRootObject = ( meta === undefined || typeof meta === 'string' ); -const DataUtils = { - toHalfFloat: toHalfFloat, - fromHalfFloat: fromHalfFloat, -}; + if ( isRootObject ) { -const _vector$8 = /*@__PURE__*/ new Vector3(); -const _vector2$1 = /*@__PURE__*/ new Vector2(); + meta = { + textures: {}, + images: {} + }; -class BufferAttribute { + } - constructor( array, itemSize, normalized = false ) { + const data = { + metadata: { + version: 4.7, + type: 'Material', + generator: 'Material.toJSON' + } + }; - if ( Array.isArray( array ) ) { + // standard Material serialization + data.uuid = this.uuid; + data.type = this.type; - throw new TypeError( 'THREE.BufferAttribute: array should be a Typed Array.' ); + if ( this.name !== '' ) data.name = this.name; - } + if ( this.color && this.color.isColor ) data.color = this.color.getHex(); - this.isBufferAttribute = true; + if ( this.roughness !== undefined ) data.roughness = this.roughness; + if ( this.metalness !== undefined ) data.metalness = this.metalness; - this.name = ''; + if ( this.sheen !== undefined ) data.sheen = this.sheen; + if ( this.sheenColor && this.sheenColor.isColor ) data.sheenColor = this.sheenColor.getHex(); + if ( this.sheenRoughness !== undefined ) data.sheenRoughness = this.sheenRoughness; + if ( this.emissive && this.emissive.isColor ) data.emissive = this.emissive.getHex(); + if ( this.emissiveIntensity !== undefined && this.emissiveIntensity !== 1 ) data.emissiveIntensity = this.emissiveIntensity; - this.array = array; - this.itemSize = itemSize; - this.count = array !== undefined ? array.length / itemSize : 0; - this.normalized = normalized; + if ( this.specular && this.specular.isColor ) data.specular = this.specular.getHex(); + if ( this.specularIntensity !== undefined ) data.specularIntensity = this.specularIntensity; + if ( this.specularColor && this.specularColor.isColor ) data.specularColor = this.specularColor.getHex(); + if ( this.shininess !== undefined ) data.shininess = this.shininess; + if ( this.clearcoat !== undefined ) data.clearcoat = this.clearcoat; + if ( this.clearcoatRoughness !== undefined ) data.clearcoatRoughness = this.clearcoatRoughness; - this.usage = StaticDrawUsage; - this.updateRange = { offset: 0, count: - 1 }; + if ( this.clearcoatMap && this.clearcoatMap.isTexture ) { - this.version = 0; + data.clearcoatMap = this.clearcoatMap.toJSON( meta ).uuid; - } + } - onUploadCallback() {} + if ( this.clearcoatRoughnessMap && this.clearcoatRoughnessMap.isTexture ) { - set needsUpdate( value ) { + data.clearcoatRoughnessMap = this.clearcoatRoughnessMap.toJSON( meta ).uuid; - if ( value === true ) this.version ++; + } - } + if ( this.clearcoatNormalMap && this.clearcoatNormalMap.isTexture ) { - setUsage( value ) { + data.clearcoatNormalMap = this.clearcoatNormalMap.toJSON( meta ).uuid; + data.clearcoatNormalScale = this.clearcoatNormalScale.toArray(); - this.usage = value; + } - return this; + if ( this.dispersion !== undefined ) data.dispersion = this.dispersion; - } + if ( this.iridescence !== undefined ) data.iridescence = this.iridescence; + if ( this.iridescenceIOR !== undefined ) data.iridescenceIOR = this.iridescenceIOR; + if ( this.iridescenceThicknessRange !== undefined ) data.iridescenceThicknessRange = this.iridescenceThicknessRange; - copy( source ) { + if ( this.iridescenceMap && this.iridescenceMap.isTexture ) { - this.name = source.name; - this.array = new source.array.constructor( source.array ); - this.itemSize = source.itemSize; - this.count = source.count; - this.normalized = source.normalized; + data.iridescenceMap = this.iridescenceMap.toJSON( meta ).uuid; - this.usage = source.usage; + } - return this; + if ( this.iridescenceThicknessMap && this.iridescenceThicknessMap.isTexture ) { - } + data.iridescenceThicknessMap = this.iridescenceThicknessMap.toJSON( meta ).uuid; - copyAt( index1, attribute, index2 ) { + } - index1 *= this.itemSize; - index2 *= attribute.itemSize; + if ( this.anisotropy !== undefined ) data.anisotropy = this.anisotropy; + if ( this.anisotropyRotation !== undefined ) data.anisotropyRotation = this.anisotropyRotation; - for ( let i = 0, l = this.itemSize; i < l; i ++ ) { + if ( this.anisotropyMap && this.anisotropyMap.isTexture ) { - this.array[ index1 + i ] = attribute.array[ index2 + i ]; + data.anisotropyMap = this.anisotropyMap.toJSON( meta ).uuid; } - return this; + if ( this.map && this.map.isTexture ) data.map = this.map.toJSON( meta ).uuid; + if ( this.matcap && this.matcap.isTexture ) data.matcap = this.matcap.toJSON( meta ).uuid; + if ( this.alphaMap && this.alphaMap.isTexture ) data.alphaMap = this.alphaMap.toJSON( meta ).uuid; - } + if ( this.lightMap && this.lightMap.isTexture ) { - copyArray( array ) { + data.lightMap = this.lightMap.toJSON( meta ).uuid; + data.lightMapIntensity = this.lightMapIntensity; - this.array.set( array ); + } - return this; + if ( this.aoMap && this.aoMap.isTexture ) { - } + data.aoMap = this.aoMap.toJSON( meta ).uuid; + data.aoMapIntensity = this.aoMapIntensity; - applyMatrix3( m ) { + } - if ( this.itemSize === 2 ) { + if ( this.bumpMap && this.bumpMap.isTexture ) { - for ( let i = 0, l = this.count; i < l; i ++ ) { - - _vector2$1.fromBufferAttribute( this, i ); - _vector2$1.applyMatrix3( m ); - - this.setXY( i, _vector2$1.x, _vector2$1.y ); - - } - - } else if ( this.itemSize === 3 ) { - - for ( let i = 0, l = this.count; i < l; i ++ ) { - - _vector$8.fromBufferAttribute( this, i ); - _vector$8.applyMatrix3( m ); - - this.setXYZ( i, _vector$8.x, _vector$8.y, _vector$8.z ); - - } + data.bumpMap = this.bumpMap.toJSON( meta ).uuid; + data.bumpScale = this.bumpScale; } - return this; - - } - - applyMatrix4( m ) { + if ( this.normalMap && this.normalMap.isTexture ) { - for ( let i = 0, l = this.count; i < l; i ++ ) { + data.normalMap = this.normalMap.toJSON( meta ).uuid; + data.normalMapType = this.normalMapType; + data.normalScale = this.normalScale.toArray(); - _vector$8.fromBufferAttribute( this, i ); + } - _vector$8.applyMatrix4( m ); + if ( this.displacementMap && this.displacementMap.isTexture ) { - this.setXYZ( i, _vector$8.x, _vector$8.y, _vector$8.z ); + data.displacementMap = this.displacementMap.toJSON( meta ).uuid; + data.displacementScale = this.displacementScale; + data.displacementBias = this.displacementBias; } - return this; - - } - - applyNormalMatrix( m ) { + if ( this.roughnessMap && this.roughnessMap.isTexture ) data.roughnessMap = this.roughnessMap.toJSON( meta ).uuid; + if ( this.metalnessMap && this.metalnessMap.isTexture ) data.metalnessMap = this.metalnessMap.toJSON( meta ).uuid; - for ( let i = 0, l = this.count; i < l; i ++ ) { + if ( this.emissiveMap && this.emissiveMap.isTexture ) data.emissiveMap = this.emissiveMap.toJSON( meta ).uuid; + if ( this.specularMap && this.specularMap.isTexture ) data.specularMap = this.specularMap.toJSON( meta ).uuid; + if ( this.specularIntensityMap && this.specularIntensityMap.isTexture ) data.specularIntensityMap = this.specularIntensityMap.toJSON( meta ).uuid; + if ( this.specularColorMap && this.specularColorMap.isTexture ) data.specularColorMap = this.specularColorMap.toJSON( meta ).uuid; - _vector$8.fromBufferAttribute( this, i ); + if ( this.envMap && this.envMap.isTexture ) { - _vector$8.applyNormalMatrix( m ); + data.envMap = this.envMap.toJSON( meta ).uuid; - this.setXYZ( i, _vector$8.x, _vector$8.y, _vector$8.z ); + if ( this.combine !== undefined ) data.combine = this.combine; } - return this; - - } - - transformDirection( m ) { - - for ( let i = 0, l = this.count; i < l; i ++ ) { - - _vector$8.fromBufferAttribute( this, i ); + if ( this.envMapRotation !== undefined ) data.envMapRotation = this.envMapRotation.toArray(); + if ( this.envMapIntensity !== undefined ) data.envMapIntensity = this.envMapIntensity; + if ( this.reflectivity !== undefined ) data.reflectivity = this.reflectivity; + if ( this.refractionRatio !== undefined ) data.refractionRatio = this.refractionRatio; - _vector$8.transformDirection( m ); + if ( this.gradientMap && this.gradientMap.isTexture ) { - this.setXYZ( i, _vector$8.x, _vector$8.y, _vector$8.z ); + data.gradientMap = this.gradientMap.toJSON( meta ).uuid; } - return this; - - } - - set( value, offset = 0 ) { + if ( this.transmission !== undefined ) data.transmission = this.transmission; + if ( this.transmissionMap && this.transmissionMap.isTexture ) data.transmissionMap = this.transmissionMap.toJSON( meta ).uuid; + if ( this.thickness !== undefined ) data.thickness = this.thickness; + if ( this.thicknessMap && this.thicknessMap.isTexture ) data.thicknessMap = this.thicknessMap.toJSON( meta ).uuid; + if ( this.attenuationDistance !== undefined && this.attenuationDistance !== Infinity ) data.attenuationDistance = this.attenuationDistance; + if ( this.attenuationColor !== undefined ) data.attenuationColor = this.attenuationColor.getHex(); - // Matching BufferAttribute constructor, do not normalize the array. - this.array.set( value, offset ); + if ( this.size !== undefined ) data.size = this.size; + if ( this.shadowSide !== null ) data.shadowSide = this.shadowSide; + if ( this.sizeAttenuation !== undefined ) data.sizeAttenuation = this.sizeAttenuation; - return this; + if ( this.blending !== NormalBlending ) data.blending = this.blending; + if ( this.side !== FrontSide ) data.side = this.side; + if ( this.vertexColors === true ) data.vertexColors = true; - } + if ( this.opacity < 1 ) data.opacity = this.opacity; + if ( this.transparent === true ) data.transparent = true; + + if ( this.blendSrc !== SrcAlphaFactor ) data.blendSrc = this.blendSrc; + if ( this.blendDst !== OneMinusSrcAlphaFactor ) data.blendDst = this.blendDst; + if ( this.blendEquation !== AddEquation ) data.blendEquation = this.blendEquation; + if ( this.blendSrcAlpha !== null ) data.blendSrcAlpha = this.blendSrcAlpha; + if ( this.blendDstAlpha !== null ) data.blendDstAlpha = this.blendDstAlpha; + if ( this.blendEquationAlpha !== null ) data.blendEquationAlpha = this.blendEquationAlpha; + if ( this.blendColor && this.blendColor.isColor ) data.blendColor = this.blendColor.getHex(); + if ( this.blendAlpha !== 0 ) data.blendAlpha = this.blendAlpha; + + if ( this.depthFunc !== LessEqualDepth ) data.depthFunc = this.depthFunc; + if ( this.depthTest === false ) data.depthTest = this.depthTest; + if ( this.depthWrite === false ) data.depthWrite = this.depthWrite; + if ( this.colorWrite === false ) data.colorWrite = this.colorWrite; + + if ( this.stencilWriteMask !== 0xff ) data.stencilWriteMask = this.stencilWriteMask; + if ( this.stencilFunc !== AlwaysStencilFunc ) data.stencilFunc = this.stencilFunc; + if ( this.stencilRef !== 0 ) data.stencilRef = this.stencilRef; + if ( this.stencilFuncMask !== 0xff ) data.stencilFuncMask = this.stencilFuncMask; + if ( this.stencilFail !== KeepStencilOp ) data.stencilFail = this.stencilFail; + if ( this.stencilZFail !== KeepStencilOp ) data.stencilZFail = this.stencilZFail; + if ( this.stencilZPass !== KeepStencilOp ) data.stencilZPass = this.stencilZPass; + if ( this.stencilWrite === true ) data.stencilWrite = this.stencilWrite; - getX( index ) { + // rotation (SpriteMaterial) + if ( this.rotation !== undefined && this.rotation !== 0 ) data.rotation = this.rotation; - let x = this.array[ index * this.itemSize ]; + if ( this.polygonOffset === true ) data.polygonOffset = true; + if ( this.polygonOffsetFactor !== 0 ) data.polygonOffsetFactor = this.polygonOffsetFactor; + if ( this.polygonOffsetUnits !== 0 ) data.polygonOffsetUnits = this.polygonOffsetUnits; - if ( this.normalized ) x = denormalize( x, this.array ); + if ( this.linewidth !== undefined && this.linewidth !== 1 ) data.linewidth = this.linewidth; + if ( this.dashSize !== undefined ) data.dashSize = this.dashSize; + if ( this.gapSize !== undefined ) data.gapSize = this.gapSize; + if ( this.scale !== undefined ) data.scale = this.scale; - return x; + if ( this.dithering === true ) data.dithering = true; - } + if ( this.alphaTest > 0 ) data.alphaTest = this.alphaTest; + if ( this.alphaHash === true ) data.alphaHash = true; + if ( this.alphaToCoverage === true ) data.alphaToCoverage = true; + if ( this.premultipliedAlpha === true ) data.premultipliedAlpha = true; + if ( this.forceSinglePass === true ) data.forceSinglePass = true; - setX( index, x ) { + if ( this.wireframe === true ) data.wireframe = true; + if ( this.wireframeLinewidth > 1 ) data.wireframeLinewidth = this.wireframeLinewidth; + if ( this.wireframeLinecap !== 'round' ) data.wireframeLinecap = this.wireframeLinecap; + if ( this.wireframeLinejoin !== 'round' ) data.wireframeLinejoin = this.wireframeLinejoin; - if ( this.normalized ) x = normalize( x, this.array ); + if ( this.flatShading === true ) data.flatShading = true; - this.array[ index * this.itemSize ] = x; + if ( this.visible === false ) data.visible = false; - return this; + if ( this.toneMapped === false ) data.toneMapped = false; - } + if ( this.fog === false ) data.fog = false; - getY( index ) { + if ( Object.keys( this.userData ).length > 0 ) data.userData = this.userData; - let y = this.array[ index * this.itemSize + 1 ]; + // TODO: Copied from Object3D.toJSON - if ( this.normalized ) y = denormalize( y, this.array ); + function extractFromCache( cache ) { - return y; + const values = []; - } + for ( const key in cache ) { - setY( index, y ) { + const data = cache[ key ]; + delete data.metadata; + values.push( data ); - if ( this.normalized ) y = normalize( y, this.array ); + } - this.array[ index * this.itemSize + 1 ] = y; + return values; - return this; + } - } + if ( isRootObject ) { - getZ( index ) { + const textures = extractFromCache( meta.textures ); + const images = extractFromCache( meta.images ); - let z = this.array[ index * this.itemSize + 2 ]; + if ( textures.length > 0 ) data.textures = textures; + if ( images.length > 0 ) data.images = images; - if ( this.normalized ) z = denormalize( z, this.array ); + } - return z; + return data; } - setZ( index, z ) { - - if ( this.normalized ) z = normalize( z, this.array ); - - this.array[ index * this.itemSize + 2 ] = z; + /** + * Returns a new material with copied values from this instance. + * + * @return {Material} A clone of this instance. + */ + clone() { - return this; + return new this.constructor().copy( this ); } - getW( index ) { - - let w = this.array[ index * this.itemSize + 3 ]; + /** + * Copies the values of the given material to this instance. + * + * @param {Material} source - The material to copy. + * @return {Material} A reference to this instance. + */ + copy( source ) { - if ( this.normalized ) w = denormalize( w, this.array ); + this.name = source.name; - return w; + this.blending = source.blending; + this.side = source.side; + this.vertexColors = source.vertexColors; - } + this.opacity = source.opacity; + this.transparent = source.transparent; - setW( index, w ) { + this.blendSrc = source.blendSrc; + this.blendDst = source.blendDst; + this.blendEquation = source.blendEquation; + this.blendSrcAlpha = source.blendSrcAlpha; + this.blendDstAlpha = source.blendDstAlpha; + this.blendEquationAlpha = source.blendEquationAlpha; + this.blendColor.copy( source.blendColor ); + this.blendAlpha = source.blendAlpha; - if ( this.normalized ) w = normalize( w, this.array ); + this.depthFunc = source.depthFunc; + this.depthTest = source.depthTest; + this.depthWrite = source.depthWrite; - this.array[ index * this.itemSize + 3 ] = w; + this.stencilWriteMask = source.stencilWriteMask; + this.stencilFunc = source.stencilFunc; + this.stencilRef = source.stencilRef; + this.stencilFuncMask = source.stencilFuncMask; + this.stencilFail = source.stencilFail; + this.stencilZFail = source.stencilZFail; + this.stencilZPass = source.stencilZPass; + this.stencilWrite = source.stencilWrite; - return this; + const srcPlanes = source.clippingPlanes; + let dstPlanes = null; - } + if ( srcPlanes !== null ) { - setXY( index, x, y ) { + const n = srcPlanes.length; + dstPlanes = new Array( n ); - index *= this.itemSize; + for ( let i = 0; i !== n; ++ i ) { - if ( this.normalized ) { + dstPlanes[ i ] = srcPlanes[ i ].clone(); - x = normalize( x, this.array ); - y = normalize( y, this.array ); + } } - this.array[ index + 0 ] = x; - this.array[ index + 1 ] = y; + this.clippingPlanes = dstPlanes; + this.clipIntersection = source.clipIntersection; + this.clipShadows = source.clipShadows; - return this; + this.shadowSide = source.shadowSide; - } + this.colorWrite = source.colorWrite; - setXYZ( index, x, y, z ) { + this.precision = source.precision; - index *= this.itemSize; + this.polygonOffset = source.polygonOffset; + this.polygonOffsetFactor = source.polygonOffsetFactor; + this.polygonOffsetUnits = source.polygonOffsetUnits; - if ( this.normalized ) { + this.dithering = source.dithering; - x = normalize( x, this.array ); - y = normalize( y, this.array ); - z = normalize( z, this.array ); + this.alphaTest = source.alphaTest; + this.alphaHash = source.alphaHash; + this.alphaToCoverage = source.alphaToCoverage; + this.premultipliedAlpha = source.premultipliedAlpha; + this.forceSinglePass = source.forceSinglePass; - } + this.visible = source.visible; - this.array[ index + 0 ] = x; - this.array[ index + 1 ] = y; - this.array[ index + 2 ] = z; + this.toneMapped = source.toneMapped; + + this.userData = JSON.parse( JSON.stringify( source.userData ) ); return this; } - setXYZW( index, x, y, z, w ) { - - index *= this.itemSize; - - if ( this.normalized ) { - - x = normalize( x, this.array ); - y = normalize( y, this.array ); - z = normalize( z, this.array ); - w = normalize( w, this.array ); - - } - - this.array[ index + 0 ] = x; - this.array[ index + 1 ] = y; - this.array[ index + 2 ] = z; - this.array[ index + 3 ] = w; + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + * + * @fires Material#dispose + */ + dispose() { - return this; + /** + * Fires when the material has been disposed of. + * + * @event Material#dispose + * @type {Object} + */ + this.dispatchEvent( { type: 'dispose' } ); } - onUpload( callback ) { - - this.onUploadCallback = callback; + /** + * Setting this property to `true` indicates the engine the material + * needs to be recompiled. + * + * @type {boolean} + * @default false + * @param {boolean} value + */ + set needsUpdate( value ) { - return this; + if ( value === true ) this.version ++; } - clone() { - - return new this.constructor( this.array, this.itemSize ).copy( this ); +} - } +/** + * A material for drawing geometries in a simple shaded (flat or wireframe) way. + * + * This material is not affected by lights. + * + * @augments Material + */ +class MeshBasicMaterial extends Material { - toJSON() { + /** + * Constructs a new mesh basic material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { - const data = { - itemSize: this.itemSize, - type: this.array.constructor.name, - array: Array.from( this.array ), - normalized: this.normalized - }; + super(); - if ( this.name !== '' ) data.name = this.name; - if ( this.usage !== StaticDrawUsage ) data.usage = this.usage; - if ( this.updateRange.offset !== 0 || this.updateRange.count !== - 1 ) data.updateRange = this.updateRange; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshBasicMaterial = true; - return data; + this.type = 'MeshBasicMaterial'; - } + /** + * Color of the material. + * + * @type {Color} + * @default (1,1,1) + */ + this.color = new Color( 0xffffff ); // emissive - copyColorsArray() { // @deprecated, r144 + /** + * The color map. May optionally include an alpha channel, typically combined + * with {@link Material#transparent} or {@link Material#alphaTest}. The texture map + * color is modulated by the diffuse `color`. + * + * @type {?Texture} + * @default null + */ + this.map = null; - console.error( 'THREE.BufferAttribute: copyColorsArray() was removed in r144.' ); + /** + * The light map. Requires a second set of UVs. + * + * @type {?Texture} + * @default null + */ + this.lightMap = null; - } + /** + * Intensity of the baked light. + * + * @type {number} + * @default 1 + */ + this.lightMapIntensity = 1.0; - copyVector2sArray() { // @deprecated, r144 + /** + * The red channel of this texture is used as the ambient occlusion map. + * Requires a second set of UVs. + * + * @type {?Texture} + * @default null + */ + this.aoMap = null; - console.error( 'THREE.BufferAttribute: copyVector2sArray() was removed in r144.' ); + /** + * Intensity of the ambient occlusion effect. Range is `[0,1]`, where `0` + * disables ambient occlusion. Where intensity is `1` and the AO map's + * red channel is also `1`, ambient light is fully occluded on a surface. + * + * @type {number} + * @default 1 + */ + this.aoMapIntensity = 1.0; - } + /** + * Specular map used by the material. + * + * @type {?Texture} + * @default null + */ + this.specularMap = null; - copyVector3sArray() { // @deprecated, r144 + /** + * The alpha map is a grayscale texture that controls the opacity across the + * surface (black: fully transparent; white: fully opaque). + * + * Only the color of the texture is used, ignoring the alpha channel if one + * exists. For RGB and RGBA textures, the renderer will use the green channel + * when sampling this texture due to the extra bit of precision provided for + * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and + * luminance/alpha textures will also still work as expected. + * + * @type {?Texture} + * @default null + */ + this.alphaMap = null; - console.error( 'THREE.BufferAttribute: copyVector3sArray() was removed in r144.' ); + /** + * The environment map. + * + * @type {?Texture} + * @default null + */ + this.envMap = null; - } + /** + * The rotation of the environment map in radians. + * + * @type {Euler} + * @default (0,0,0) + */ + this.envMapRotation = new Euler(); - copyVector4sArray() { // @deprecated, r144 + /** + * How to combine the result of the surface's color with the environment map, if any. + * + * When set to `MixOperation`, the {@link MeshBasicMaterial#reflectivity} is used to + * blend between the two colors. + * + * @type {(MultiplyOperation|MixOperation|AddOperation)} + * @default MultiplyOperation + */ + this.combine = MultiplyOperation; - console.error( 'THREE.BufferAttribute: copyVector4sArray() was removed in r144.' ); + /** + * How much the environment map affects the surface. + * The valid range is between `0` (no reflections) and `1` (full reflections). + * + * @type {number} + * @default 1 + */ + this.reflectivity = 1; - } + /** + * The index of refraction (IOR) of air (approximately 1) divided by the + * index of refraction of the material. It is used with environment mapping + * modes {@link CubeRefractionMapping} and {@link EquirectangularRefractionMapping}. + * The refraction ratio should not exceed `1`. + * + * @type {number} + * @default 0.98 + */ + this.refractionRatio = 0.98; -} + /** + * Renders the geometry as a wireframe. + * + * @type {boolean} + * @default false + */ + this.wireframe = false; -// + /** + * Controls the thickness of the wireframe. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {number} + * @default 1 + */ + this.wireframeLinewidth = 1; -class Int8BufferAttribute extends BufferAttribute { + /** + * Defines appearance of wireframe ends. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ + this.wireframeLinecap = 'round'; - constructor( array, itemSize, normalized ) { + /** + * Defines appearance of wireframe joints. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ + this.wireframeLinejoin = 'round'; - super( new Int8Array( array ), itemSize, normalized ); + /** + * Whether the material is affected by fog or not. + * + * @type {boolean} + * @default true + */ + this.fog = true; - this.gpuType = FloatType; + this.setValues( parameters ); } @@ -10062,29 +17460,32 @@ class Int8BufferAttribute extends BufferAttribute { super.copy( source ); - this.gpuType = source.gpuType; - - return this; - - } - -} + this.color.copy( source.color ); -class Uint8BufferAttribute extends BufferAttribute { + this.map = source.map; - constructor( array, itemSize, normalized ) { + this.lightMap = source.lightMap; + this.lightMapIntensity = source.lightMapIntensity; - super( new Uint8Array( array ), itemSize, normalized ); + this.aoMap = source.aoMap; + this.aoMapIntensity = source.aoMapIntensity; - this.gpuType = FloatType; + this.specularMap = source.specularMap; - } + this.alphaMap = source.alphaMap; - copy( source ) { + this.envMap = source.envMap; + this.envMapRotation.copy( source.envMapRotation ); + this.combine = source.combine; + this.reflectivity = source.reflectivity; + this.refractionRatio = source.refractionRatio; - super.copy( source ); + this.wireframe = source.wireframe; + this.wireframeLinewidth = source.wireframeLinewidth; + this.wireframeLinecap = source.wireframeLinecap; + this.wireframeLinejoin = source.wireframeLinejoin; - this.gpuType = source.gpuType; + this.fog = source.fog; return this; @@ -10092,309 +17493,514 @@ class Uint8BufferAttribute extends BufferAttribute { } -class Uint8ClampedBufferAttribute extends BufferAttribute { - - constructor( array, itemSize, normalized ) { +// Fast Half Float Conversions, http://www.fox-toolkit.org/ftp/fasthalffloatconversion.pdf - super( new Uint8ClampedArray( array ), itemSize, normalized ); +const _tables = /*@__PURE__*/ _generateTables(); - } +function _generateTables() { -} + // float32 to float16 helpers -class Int16BufferAttribute extends BufferAttribute { + const buffer = new ArrayBuffer( 4 ); + const floatView = new Float32Array( buffer ); + const uint32View = new Uint32Array( buffer ); - constructor( array, itemSize, normalized ) { + const baseTable = new Uint32Array( 512 ); + const shiftTable = new Uint32Array( 512 ); - super( new Int16Array( array ), itemSize, normalized ); + for ( let i = 0; i < 256; ++ i ) { - this.gpuType = FloatType; + const e = i - 127; - } + // very small number (0, -0) - copy( source ) { + if ( e < -27 ) { - super.copy( source ); + baseTable[ i ] = 0x0000; + baseTable[ i | 0x100 ] = 0x8000; + shiftTable[ i ] = 24; + shiftTable[ i | 0x100 ] = 24; - this.gpuType = source.gpuType; + // small number (denorm) - return this; + } else if ( e < -14 ) { - } + baseTable[ i ] = 0x0400 >> ( - e - 14 ); + baseTable[ i | 0x100 ] = ( 0x0400 >> ( - e - 14 ) ) | 0x8000; + shiftTable[ i ] = - e - 1; + shiftTable[ i | 0x100 ] = - e - 1; -} + // normal number -class Uint16BufferAttribute extends BufferAttribute { + } else if ( e <= 15 ) { - constructor( array, itemSize, normalized ) { + baseTable[ i ] = ( e + 15 ) << 10; + baseTable[ i | 0x100 ] = ( ( e + 15 ) << 10 ) | 0x8000; + shiftTable[ i ] = 13; + shiftTable[ i | 0x100 ] = 13; - super( new Uint16Array( array ), itemSize, normalized ); + // large number (Infinity, -Infinity) - this.gpuType = FloatType; + } else if ( e < 128 ) { - } + baseTable[ i ] = 0x7c00; + baseTable[ i | 0x100 ] = 0xfc00; + shiftTable[ i ] = 24; + shiftTable[ i | 0x100 ] = 24; - copy( source ) { + // stay (NaN, Infinity, -Infinity) - super.copy( source ); + } else { - this.gpuType = source.gpuType; + baseTable[ i ] = 0x7c00; + baseTable[ i | 0x100 ] = 0xfc00; + shiftTable[ i ] = 13; + shiftTable[ i | 0x100 ] = 13; - return this; + } } -} + // float16 to float32 helpers -class Int32BufferAttribute extends BufferAttribute { + const mantissaTable = new Uint32Array( 2048 ); + const exponentTable = new Uint32Array( 64 ); + const offsetTable = new Uint32Array( 64 ); - constructor( array, itemSize, normalized ) { + for ( let i = 1; i < 1024; ++ i ) { - super( new Int32Array( array ), itemSize, normalized ); + let m = i << 13; // zero pad mantissa bits + let e = 0; // zero exponent - } + // normalized + while ( ( m & 0x00800000 ) === 0 ) { -} + m <<= 1; + e -= 0x00800000; // decrement exponent -class Uint32BufferAttribute extends BufferAttribute { + } - constructor( array, itemSize, normalized ) { + m &= -8388609; // clear leading 1 bit + e += 0x38800000; // adjust bias - super( new Uint32Array( array ), itemSize, normalized ); + mantissaTable[ i ] = m | e; } -} + for ( let i = 1024; i < 2048; ++ i ) { -class Float16BufferAttribute extends BufferAttribute { + mantissaTable[ i ] = 0x38000000 + ( ( i - 1024 ) << 13 ); - constructor( array, itemSize, normalized ) { + } - super( new Uint16Array( array ), itemSize, normalized ); + for ( let i = 1; i < 31; ++ i ) { - this.isFloat16BufferAttribute = true; + exponentTable[ i ] = i << 23; } - getX( index ) { - - let x = fromHalfFloat( this.array[ index * this.itemSize ] ); + exponentTable[ 31 ] = 0x47800000; + exponentTable[ 32 ] = 0x80000000; - if ( this.normalized ) x = denormalize( x, this.array ); + for ( let i = 33; i < 63; ++ i ) { - return x; + exponentTable[ i ] = 0x80000000 + ( ( i - 32 ) << 23 ); } - setX( index, x ) { + exponentTable[ 63 ] = 0xc7800000; - if ( this.normalized ) x = normalize( x, this.array ); + for ( let i = 1; i < 64; ++ i ) { - this.array[ index * this.itemSize ] = toHalfFloat( x ); + if ( i !== 32 ) { - return this; + offsetTable[ i ] = 1024; - } + } - getY( index ) { + } - let y = fromHalfFloat( this.array[ index * this.itemSize + 1 ] ); + return { + floatView: floatView, + uint32View: uint32View, + baseTable: baseTable, + shiftTable: shiftTable, + mantissaTable: mantissaTable, + exponentTable: exponentTable, + offsetTable: offsetTable + }; - if ( this.normalized ) y = denormalize( y, this.array ); +} - return y; +/** + * Returns a half precision floating point value (FP16) from the given single + * precision floating point value (FP32). + * + * @param {number} val - A single precision floating point value. + * @return {number} The FP16 value. + */ +function toHalfFloat( val ) { - } + if ( Math.abs( val ) > 65504 ) console.warn( 'THREE.DataUtils.toHalfFloat(): Value out of range.' ); - setY( index, y ) { + val = clamp( val, -65504, 65504 ); - if ( this.normalized ) y = normalize( y, this.array ); + _tables.floatView[ 0 ] = val; + const f = _tables.uint32View[ 0 ]; + const e = ( f >> 23 ) & 0x1ff; + return _tables.baseTable[ e ] + ( ( f & 0x007fffff ) >> _tables.shiftTable[ e ] ); - this.array[ index * this.itemSize + 1 ] = toHalfFloat( y ); +} - return this; +/** + * Returns a single precision floating point value (FP32) from the given half + * precision floating point value (FP16). + * + * @param {number} val - A half precision floating point value. + * @return {number} The FP32 value. + */ +function fromHalfFloat( val ) { - } + const m = val >> 10; + _tables.uint32View[ 0 ] = _tables.mantissaTable[ _tables.offsetTable[ m ] + ( val & 0x3ff ) ] + _tables.exponentTable[ m ]; + return _tables.floatView[ 0 ]; - getZ( index ) { +} - let z = fromHalfFloat( this.array[ index * this.itemSize + 2 ] ); +/** + * A class containing utility functions for data. + * + * @hideconstructor + */ +class DataUtils { - if ( this.normalized ) z = denormalize( z, this.array ); + /** + * Returns a half precision floating point value (FP16) from the given single + * precision floating point value (FP32). + * + * @param {number} val - A single precision floating point value. + * @return {number} The FP16 value. + */ + static toHalfFloat( val ) { - return z; + return toHalfFloat( val ); } - setZ( index, z ) { + /** + * Returns a single precision floating point value (FP32) from the given half + * precision floating point value (FP16). + * + * @param {number} val - A half precision floating point value. + * @return {number} The FP32 value. + */ + static fromHalfFloat( val ) { - if ( this.normalized ) z = normalize( z, this.array ); + return fromHalfFloat( val ); - this.array[ index * this.itemSize + 2 ] = toHalfFloat( z ); + } - return this; +} - } +const _vector$9 = /*@__PURE__*/ new Vector3(); +const _vector2$1 = /*@__PURE__*/ new Vector2(); - getW( index ) { +let _id$3 = 0; - let w = fromHalfFloat( this.array[ index * this.itemSize + 3 ] ); +/** + * This class stores data for an attribute (such as vertex positions, face + * indices, normals, colors, UVs, and any custom attributes ) associated with + * a geometry, which allows for more efficient passing of data to the GPU. + * + * When working with vector-like data, the `fromBufferAttribute( attribute, index )` + * helper methods on vector and color class might be helpful. E.g. {@link Vector3#fromBufferAttribute}. + */ +class BufferAttribute { - if ( this.normalized ) w = denormalize( w, this.array ); + /** + * Constructs a new buffer attribute. + * + * @param {TypedArray} array - The array holding the attribute data. + * @param {number} itemSize - The item size. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + */ + constructor( array, itemSize, normalized = false ) { - return w; + if ( Array.isArray( array ) ) { - } + throw new TypeError( 'THREE.BufferAttribute: array should be a Typed Array.' ); - setW( index, w ) { + } - if ( this.normalized ) w = normalize( w, this.array ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isBufferAttribute = true; - this.array[ index * this.itemSize + 3 ] = toHalfFloat( w ); + /** + * The ID of the buffer attribute. + * + * @name BufferAttribute#id + * @type {number} + * @readonly + */ + Object.defineProperty( this, 'id', { value: _id$3 ++ } ); - return this; + /** + * The name of the buffer attribute. + * + * @type {string} + */ + this.name = ''; - } + /** + * The array holding the attribute data. It should have `itemSize * numVertices` + * elements, where `numVertices` is the number of vertices in the associated geometry. + * + * @type {TypedArray} + */ + this.array = array; - setXY( index, x, y ) { + /** + * The number of values of the array that should be associated with a particular vertex. + * For instance, if this attribute is storing a 3-component vector (such as a position, + * normal, or color), then the value should be `3`. + * + * @type {number} + */ + this.itemSize = itemSize; - index *= this.itemSize; + /** + * Represents the number of items this buffer attribute stores. It is internally computed + * by dividing the `array` length by the `itemSize`. + * + * @type {number} + * @readonly + */ + this.count = array !== undefined ? array.length / itemSize : 0; - if ( this.normalized ) { + /** + * Applies to integer data only. Indicates how the underlying data in the buffer maps to + * the values in the GLSL code. For instance, if `array` is an instance of `UInt16Array`, + * and `normalized` is `true`, the values `0 - +65535` in the array data will be mapped to + * `0.0f - +1.0f` in the GLSL attribute. If `normalized` is `false`, the values will be converted + * to floats unmodified, i.e. `65535` becomes `65535.0f`. + * + * @type {boolean} + */ + this.normalized = normalized; - x = normalize( x, this.array ); - y = normalize( y, this.array ); + /** + * Defines the intended usage pattern of the data store for optimization purposes. + * + * Note: After the initial use of a buffer, its usage cannot be changed. Instead, + * instantiate a new one and set the desired usage before the next render. + * + * @type {(StaticDrawUsage|DynamicDrawUsage|StreamDrawUsage|StaticReadUsage|DynamicReadUsage|StreamReadUsage|StaticCopyUsage|DynamicCopyUsage|StreamCopyUsage)} + * @default StaticDrawUsage + */ + this.usage = StaticDrawUsage; - } + /** + * This can be used to only update some components of stored vectors (for example, just the + * component related to color). Use the `addUpdateRange()` function to add ranges to this array. + * + * @type {Array} + */ + this.updateRanges = []; - this.array[ index + 0 ] = toHalfFloat( x ); - this.array[ index + 1 ] = toHalfFloat( y ); + /** + * Configures the bound GPU type for use in shaders. + * + * Note: this only has an effect for integer arrays and is not configurable for float arrays. + * For lower precision float types, use `Float16BufferAttribute`. + * + * @type {(FloatType|IntType)} + * @default FloatType + */ + this.gpuType = FloatType; - return this; + /** + * A version number, incremented every time the `needsUpdate` is set to `true`. + * + * @type {number} + */ + this.version = 0; } - setXYZ( index, x, y, z ) { + /** + * A callback function that is executed after the renderer has transferred the attribute + * array data to the GPU. + */ + onUploadCallback() {} - index *= this.itemSize; + /** + * Flag to indicate that this attribute has changed and should be re-sent to + * the GPU. Set this to `true` when you modify the value of the array. + * + * @type {number} + * @default false + * @param {boolean} value + */ + set needsUpdate( value ) { - if ( this.normalized ) { + if ( value === true ) this.version ++; - x = normalize( x, this.array ); - y = normalize( y, this.array ); - z = normalize( z, this.array ); + } - } + /** + * Sets the usage of this buffer attribute. + * + * @param {(StaticDrawUsage|DynamicDrawUsage|StreamDrawUsage|StaticReadUsage|DynamicReadUsage|StreamReadUsage|StaticCopyUsage|DynamicCopyUsage|StreamCopyUsage)} value - The usage to set. + * @return {BufferAttribute} A reference to this buffer attribute. + */ + setUsage( value ) { - this.array[ index + 0 ] = toHalfFloat( x ); - this.array[ index + 1 ] = toHalfFloat( y ); - this.array[ index + 2 ] = toHalfFloat( z ); + this.usage = value; return this; } - setXYZW( index, x, y, z, w ) { - - index *= this.itemSize; - - if ( this.normalized ) { + /** + * Adds a range of data in the data array to be updated on the GPU. + * + * @param {number} start - Position at which to start update. + * @param {number} count - The number of components to update. + */ + addUpdateRange( start, count ) { - x = normalize( x, this.array ); - y = normalize( y, this.array ); - z = normalize( z, this.array ); - w = normalize( w, this.array ); + this.updateRanges.push( { start, count } ); - } + } - this.array[ index + 0 ] = toHalfFloat( x ); - this.array[ index + 1 ] = toHalfFloat( y ); - this.array[ index + 2 ] = toHalfFloat( z ); - this.array[ index + 3 ] = toHalfFloat( w ); + /** + * Clears the update ranges. + */ + clearUpdateRanges() { - return this; + this.updateRanges.length = 0; } -} - + /** + * Copies the values of the given buffer attribute to this instance. + * + * @param {BufferAttribute} source - The buffer attribute to copy. + * @return {BufferAttribute} A reference to this instance. + */ + copy( source ) { -class Float32BufferAttribute extends BufferAttribute { + this.name = source.name; + this.array = new source.array.constructor( source.array ); + this.itemSize = source.itemSize; + this.count = source.count; + this.normalized = source.normalized; - constructor( array, itemSize, normalized ) { + this.usage = source.usage; + this.gpuType = source.gpuType; - super( new Float32Array( array ), itemSize, normalized ); + return this; } -} + /** + * Copies a vector from the given buffer attribute to this one. The start + * and destination position in the attribute buffers are represented by the + * given indices. + * + * @param {number} index1 - The destination index into this buffer attribute. + * @param {BufferAttribute} attribute - The buffer attribute to copy from. + * @param {number} index2 - The source index into the given buffer attribute. + * @return {BufferAttribute} A reference to this instance. + */ + copyAt( index1, attribute, index2 ) { -class Float64BufferAttribute extends BufferAttribute { + index1 *= this.itemSize; + index2 *= attribute.itemSize; - constructor( array, itemSize, normalized ) { + for ( let i = 0, l = this.itemSize; i < l; i ++ ) { - super( new Float64Array( array ), itemSize, normalized ); + this.array[ index1 + i ] = attribute.array[ index2 + i ]; - } + } -} + return this; -let _id$1 = 0; + } -const _m1 = /*@__PURE__*/ new Matrix4(); -const _obj = /*@__PURE__*/ new Object3D(); -const _offset = /*@__PURE__*/ new Vector3(); -const _box$1 = /*@__PURE__*/ new Box3(); -const _boxMorphTargets = /*@__PURE__*/ new Box3(); -const _vector$7 = /*@__PURE__*/ new Vector3(); + /** + * Copies the given array data into this buffer attribute. + * + * @param {(TypedArray|Array)} array - The array to copy. + * @return {BufferAttribute} A reference to this instance. + */ + copyArray( array ) { -class BufferGeometry extends EventDispatcher { + this.array.set( array ); - constructor() { + return this; - super(); + } - this.isBufferGeometry = true; + /** + * Applies the given 3x3 matrix to the given attribute. Works with + * item size `2` and `3`. + * + * @param {Matrix3} m - The matrix to apply. + * @return {BufferAttribute} A reference to this instance. + */ + applyMatrix3( m ) { - Object.defineProperty( this, 'id', { value: _id$1 ++ } ); + if ( this.itemSize === 2 ) { - this.uuid = generateUUID(); + for ( let i = 0, l = this.count; i < l; i ++ ) { - this.name = ''; - this.type = 'BufferGeometry'; + _vector2$1.fromBufferAttribute( this, i ); + _vector2$1.applyMatrix3( m ); - this.index = null; - this.attributes = {}; + this.setXY( i, _vector2$1.x, _vector2$1.y ); - this.morphAttributes = {}; - this.morphTargetsRelative = false; + } - this.groups = []; + } else if ( this.itemSize === 3 ) { - this.boundingBox = null; - this.boundingSphere = null; + for ( let i = 0, l = this.count; i < l; i ++ ) { - this.drawRange = { start: 0, count: Infinity }; + _vector$9.fromBufferAttribute( this, i ); + _vector$9.applyMatrix3( m ); - this.userData = {}; + this.setXYZ( i, _vector$9.x, _vector$9.y, _vector$9.z ); - } + } - getIndex() { + } - return this.index; + return this; } - setIndex( index ) { + /** + * Applies the given 4x4 matrix to the given attribute. Only works with + * item size `3`. + * + * @param {Matrix4} m - The matrix to apply. + * @return {BufferAttribute} A reference to this instance. + */ + applyMatrix4( m ) { - if ( Array.isArray( index ) ) { + for ( let i = 0, l = this.count; i < l; i ++ ) { - this.index = new ( arrayNeedsUint32( index ) ? Uint32BufferAttribute : Uint16BufferAttribute )( index, 1 ); + _vector$9.fromBufferAttribute( this, i ); - } else { + _vector$9.applyMatrix4( m ); - this.index = index; + this.setXYZ( i, _vector$9.x, _vector$9.y, _vector$9.z ); } @@ -10402,15642 +18008,28196 @@ class BufferGeometry extends EventDispatcher { } - getAttribute( name ) { + /** + * Applies the given 3x3 normal matrix to the given attribute. Only works with + * item size `3`. + * + * @param {Matrix3} m - The normal matrix to apply. + * @return {BufferAttribute} A reference to this instance. + */ + applyNormalMatrix( m ) { - return this.attributes[ name ]; + for ( let i = 0, l = this.count; i < l; i ++ ) { - } + _vector$9.fromBufferAttribute( this, i ); - setAttribute( name, attribute ) { + _vector$9.applyNormalMatrix( m ); - this.attributes[ name ] = attribute; + this.setXYZ( i, _vector$9.x, _vector$9.y, _vector$9.z ); + + } return this; } - deleteAttribute( name ) { + /** + * Applies the given 4x4 matrix to the given attribute. Only works with + * item size `3` and with direction vectors. + * + * @param {Matrix4} m - The matrix to apply. + * @return {BufferAttribute} A reference to this instance. + */ + transformDirection( m ) { - delete this.attributes[ name ]; + for ( let i = 0, l = this.count; i < l; i ++ ) { - return this; + _vector$9.fromBufferAttribute( this, i ); - } + _vector$9.transformDirection( m ); - hasAttribute( name ) { + this.setXYZ( i, _vector$9.x, _vector$9.y, _vector$9.z ); - return this.attributes[ name ] !== undefined; + } - } + return this; - addGroup( start, count, materialIndex = 0 ) { + } - this.groups.push( { + /** + * Sets the given array data in the buffer attribute. + * + * @param {(TypedArray|Array)} value - The array data to set. + * @param {number} [offset=0] - The offset in this buffer attribute's array. + * @return {BufferAttribute} A reference to this instance. + */ + set( value, offset = 0 ) { - start: start, - count: count, - materialIndex: materialIndex + // Matching BufferAttribute constructor, do not normalize the array. + this.array.set( value, offset ); - } ); + return this; } - clearGroups() { - - this.groups = []; + /** + * Returns the given component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} component - The component index. + * @return {number} The returned value. + */ + getComponent( index, component ) { - } + let value = this.array[ index * this.itemSize + component ]; - setDrawRange( start, count ) { + if ( this.normalized ) value = denormalize( value, this.array ); - this.drawRange.start = start; - this.drawRange.count = count; + return value; } - applyMatrix4( matrix ) { + /** + * Sets the given value to the given component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} component - The component index. + * @param {number} value - The value to set. + * @return {BufferAttribute} A reference to this instance. + */ + setComponent( index, component, value ) { - const position = this.attributes.position; + if ( this.normalized ) value = normalize( value, this.array ); - if ( position !== undefined ) { + this.array[ index * this.itemSize + component ] = value; - position.applyMatrix4( matrix ); + return this; - position.needsUpdate = true; + } - } + /** + * Returns the x component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @return {number} The x component. + */ + getX( index ) { - const normal = this.attributes.normal; + let x = this.array[ index * this.itemSize ]; - if ( normal !== undefined ) { + if ( this.normalized ) x = denormalize( x, this.array ); - const normalMatrix = new Matrix3().getNormalMatrix( matrix ); + return x; - normal.applyNormalMatrix( normalMatrix ); + } - normal.needsUpdate = true; + /** + * Sets the x component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} x - The value to set. + * @return {BufferAttribute} A reference to this instance. + */ + setX( index, x ) { - } + if ( this.normalized ) x = normalize( x, this.array ); - const tangent = this.attributes.tangent; + this.array[ index * this.itemSize ] = x; - if ( tangent !== undefined ) { + return this; - tangent.transformDirection( matrix ); + } - tangent.needsUpdate = true; + /** + * Returns the y component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @return {number} The y component. + */ + getY( index ) { - } + let y = this.array[ index * this.itemSize + 1 ]; - if ( this.boundingBox !== null ) { + if ( this.normalized ) y = denormalize( y, this.array ); - this.computeBoundingBox(); + return y; - } + } - if ( this.boundingSphere !== null ) { + /** + * Sets the y component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} y - The value to set. + * @return {BufferAttribute} A reference to this instance. + */ + setY( index, y ) { - this.computeBoundingSphere(); + if ( this.normalized ) y = normalize( y, this.array ); - } + this.array[ index * this.itemSize + 1 ] = y; return this; } - applyQuaternion( q ) { + /** + * Returns the z component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @return {number} The z component. + */ + getZ( index ) { - _m1.makeRotationFromQuaternion( q ); + let z = this.array[ index * this.itemSize + 2 ]; - this.applyMatrix4( _m1 ); + if ( this.normalized ) z = denormalize( z, this.array ); - return this; + return z; } - rotateX( angle ) { - - // rotate geometry around world x-axis + /** + * Sets the z component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} z - The value to set. + * @return {BufferAttribute} A reference to this instance. + */ + setZ( index, z ) { - _m1.makeRotationX( angle ); + if ( this.normalized ) z = normalize( z, this.array ); - this.applyMatrix4( _m1 ); + this.array[ index * this.itemSize + 2 ] = z; return this; } - rotateY( angle ) { - - // rotate geometry around world y-axis + /** + * Returns the w component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @return {number} The w component. + */ + getW( index ) { - _m1.makeRotationY( angle ); + let w = this.array[ index * this.itemSize + 3 ]; - this.applyMatrix4( _m1 ); + if ( this.normalized ) w = denormalize( w, this.array ); - return this; + return w; } - rotateZ( angle ) { - - // rotate geometry around world z-axis + /** + * Sets the w component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} w - The value to set. + * @return {BufferAttribute} A reference to this instance. + */ + setW( index, w ) { - _m1.makeRotationZ( angle ); + if ( this.normalized ) w = normalize( w, this.array ); - this.applyMatrix4( _m1 ); + this.array[ index * this.itemSize + 3 ] = w; return this; } - translate( x, y, z ) { + /** + * Sets the x and y component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} x - The value for the x component to set. + * @param {number} y - The value for the y component to set. + * @return {BufferAttribute} A reference to this instance. + */ + setXY( index, x, y ) { - // translate geometry + index *= this.itemSize; + + if ( this.normalized ) { + + x = normalize( x, this.array ); + y = normalize( y, this.array ); - _m1.makeTranslation( x, y, z ); + } - this.applyMatrix4( _m1 ); + this.array[ index + 0 ] = x; + this.array[ index + 1 ] = y; return this; } - scale( x, y, z ) { + /** + * Sets the x, y and z component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} x - The value for the x component to set. + * @param {number} y - The value for the y component to set. + * @param {number} z - The value for the z component to set. + * @return {BufferAttribute} A reference to this instance. + */ + setXYZ( index, x, y, z ) { - // scale geometry + index *= this.itemSize; - _m1.makeScale( x, y, z ); + if ( this.normalized ) { + + x = normalize( x, this.array ); + y = normalize( y, this.array ); + z = normalize( z, this.array ); + + } - this.applyMatrix4( _m1 ); + this.array[ index + 0 ] = x; + this.array[ index + 1 ] = y; + this.array[ index + 2 ] = z; return this; } - lookAt( vector ) { + /** + * Sets the x, y, z and w component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} x - The value for the x component to set. + * @param {number} y - The value for the y component to set. + * @param {number} z - The value for the z component to set. + * @param {number} w - The value for the w component to set. + * @return {BufferAttribute} A reference to this instance. + */ + setXYZW( index, x, y, z, w ) { - _obj.lookAt( vector ); + index *= this.itemSize; - _obj.updateMatrix(); + if ( this.normalized ) { - this.applyMatrix4( _obj.matrix ); + x = normalize( x, this.array ); + y = normalize( y, this.array ); + z = normalize( z, this.array ); + w = normalize( w, this.array ); - return this; + } - } + this.array[ index + 0 ] = x; + this.array[ index + 1 ] = y; + this.array[ index + 2 ] = z; + this.array[ index + 3 ] = w; - center() { + return this; - this.computeBoundingBox(); + } - this.boundingBox.getCenter( _offset ).negate(); + /** + * Sets the given callback function that is executed after the Renderer has transferred + * the attribute array data to the GPU. Can be used to perform clean-up operations after + * the upload when attribute data are not needed anymore on the CPU side. + * + * @param {Function} callback - The `onUpload()` callback. + * @return {BufferAttribute} A reference to this instance. + */ + onUpload( callback ) { - this.translate( _offset.x, _offset.y, _offset.z ); + this.onUploadCallback = callback; return this; } - setFromPoints( points ) { + /** + * Returns a new buffer attribute with copied values from this instance. + * + * @return {BufferAttribute} A clone of this instance. + */ + clone() { - const position = []; + return new this.constructor( this.array, this.itemSize ).copy( this ); - for ( let i = 0, l = points.length; i < l; i ++ ) { + } - const point = points[ i ]; - position.push( point.x, point.y, point.z || 0 ); + /** + * Serializes the buffer attribute into JSON. + * + * @return {Object} A JSON object representing the serialized buffer attribute. + */ + toJSON() { - } + const data = { + itemSize: this.itemSize, + type: this.array.constructor.name, + array: Array.from( this.array ), + normalized: this.normalized + }; - this.setAttribute( 'position', new Float32BufferAttribute( position, 3 ) ); + if ( this.name !== '' ) data.name = this.name; + if ( this.usage !== StaticDrawUsage ) data.usage = this.usage; - return this; + return data; } - computeBoundingBox() { +} - if ( this.boundingBox === null ) { +/** + * Convenient class that can be used when creating a `Int8` buffer attribute with + * a plain `Array` instance. + * + * @augments BufferAttribute + */ +class Int8BufferAttribute extends BufferAttribute { - this.boundingBox = new Box3(); + /** + * Constructs a new buffer attribute. + * + * @param {(Array|Int8Array)} array - The array holding the attribute data. + * @param {number} itemSize - The item size. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + */ + constructor( array, itemSize, normalized ) { - } + super( new Int8Array( array ), itemSize, normalized ); - const position = this.attributes.position; - const morphAttributesPosition = this.morphAttributes.position; + } - if ( position && position.isGLBufferAttribute ) { +} - console.error( 'THREE.BufferGeometry.computeBoundingBox(): GLBufferAttribute requires a manual bounding box. Alternatively set "mesh.frustumCulled" to "false".', this ); +/** + * Convenient class that can be used when creating a `UInt8` buffer attribute with + * a plain `Array` instance. + * + * @augments BufferAttribute + */ +class Uint8BufferAttribute extends BufferAttribute { - this.boundingBox.set( - new Vector3( - Infinity, - Infinity, - Infinity ), - new Vector3( + Infinity, + Infinity, + Infinity ) - ); - - return; + /** + * Constructs a new buffer attribute. + * + * @param {(Array|Uint8Array)} array - The array holding the attribute data. + * @param {number} itemSize - The item size. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + */ + constructor( array, itemSize, normalized ) { - } + super( new Uint8Array( array ), itemSize, normalized ); - if ( position !== undefined ) { + } - this.boundingBox.setFromBufferAttribute( position ); +} - // process morph attributes if present +/** + * Convenient class that can be used when creating a `UInt8Clamped` buffer attribute with + * a plain `Array` instance. + * + * @augments BufferAttribute + */ +class Uint8ClampedBufferAttribute extends BufferAttribute { - if ( morphAttributesPosition ) { + /** + * Constructs a new buffer attribute. + * + * @param {(Array|Uint8ClampedArray)} array - The array holding the attribute data. + * @param {number} itemSize - The item size. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + */ + constructor( array, itemSize, normalized ) { - for ( let i = 0, il = morphAttributesPosition.length; i < il; i ++ ) { + super( new Uint8ClampedArray( array ), itemSize, normalized ); - const morphAttribute = morphAttributesPosition[ i ]; - _box$1.setFromBufferAttribute( morphAttribute ); + } - if ( this.morphTargetsRelative ) { +} - _vector$7.addVectors( this.boundingBox.min, _box$1.min ); - this.boundingBox.expandByPoint( _vector$7 ); +/** + * Convenient class that can be used when creating a `Int16` buffer attribute with + * a plain `Array` instance. + * + * @augments BufferAttribute + */ +class Int16BufferAttribute extends BufferAttribute { - _vector$7.addVectors( this.boundingBox.max, _box$1.max ); - this.boundingBox.expandByPoint( _vector$7 ); + /** + * Constructs a new buffer attribute. + * + * @param {(Array|Int16Array)} array - The array holding the attribute data. + * @param {number} itemSize - The item size. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + */ + constructor( array, itemSize, normalized ) { - } else { + super( new Int16Array( array ), itemSize, normalized ); - this.boundingBox.expandByPoint( _box$1.min ); - this.boundingBox.expandByPoint( _box$1.max ); + } - } +} - } +/** + * Convenient class that can be used when creating a `UInt16` buffer attribute with + * a plain `Array` instance. + * + * @augments BufferAttribute + */ +class Uint16BufferAttribute extends BufferAttribute { - } + /** + * Constructs a new buffer attribute. + * + * @param {(Array|Uint16Array)} array - The array holding the attribute data. + * @param {number} itemSize - The item size. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + */ + constructor( array, itemSize, normalized ) { - } else { + super( new Uint16Array( array ), itemSize, normalized ); - this.boundingBox.makeEmpty(); + } - } +} - if ( isNaN( this.boundingBox.min.x ) || isNaN( this.boundingBox.min.y ) || isNaN( this.boundingBox.min.z ) ) { +/** + * Convenient class that can be used when creating a `Int32` buffer attribute with + * a plain `Array` instance. + * + * @augments BufferAttribute + */ +class Int32BufferAttribute extends BufferAttribute { - console.error( 'THREE.BufferGeometry.computeBoundingBox(): Computed min/max have NaN values. The "position" attribute is likely to have NaN values.', this ); + /** + * Constructs a new buffer attribute. + * + * @param {(Array|Int32Array)} array - The array holding the attribute data. + * @param {number} itemSize - The item size. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + */ + constructor( array, itemSize, normalized ) { - } + super( new Int32Array( array ), itemSize, normalized ); } - computeBoundingSphere() { - - if ( this.boundingSphere === null ) { +} - this.boundingSphere = new Sphere(); +/** + * Convenient class that can be used when creating a `UInt32` buffer attribute with + * a plain `Array` instance. + * + * @augments BufferAttribute + */ +class Uint32BufferAttribute extends BufferAttribute { - } + /** + * Constructs a new buffer attribute. + * + * @param {(Array|Uint32Array)} array - The array holding the attribute data. + * @param {number} itemSize - The item size. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + */ + constructor( array, itemSize, normalized ) { - const position = this.attributes.position; - const morphAttributesPosition = this.morphAttributes.position; + super( new Uint32Array( array ), itemSize, normalized ); - if ( position && position.isGLBufferAttribute ) { + } - console.error( 'THREE.BufferGeometry.computeBoundingSphere(): GLBufferAttribute requires a manual bounding sphere. Alternatively set "mesh.frustumCulled" to "false".', this ); +} - this.boundingSphere.set( new Vector3(), Infinity ); +/** + * Convenient class that can be used when creating a `Float16` buffer attribute with + * a plain `Array` instance. + * + * This class automatically converts to and from FP16 via `Uint16Array` since `Float16Array` + * browser support is still problematic. + * + * @augments BufferAttribute + */ +class Float16BufferAttribute extends BufferAttribute { - return; + /** + * Constructs a new buffer attribute. + * + * @param {(Array|Uint16Array)} array - The array holding the attribute data. + * @param {number} itemSize - The item size. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + */ + constructor( array, itemSize, normalized ) { - } + super( new Uint16Array( array ), itemSize, normalized ); - if ( position ) { + this.isFloat16BufferAttribute = true; - // first, find the center of the bounding sphere + } - const center = this.boundingSphere.center; + getX( index ) { - _box$1.setFromBufferAttribute( position ); + let x = fromHalfFloat( this.array[ index * this.itemSize ] ); - // process morph attributes if present + if ( this.normalized ) x = denormalize( x, this.array ); - if ( morphAttributesPosition ) { + return x; - for ( let i = 0, il = morphAttributesPosition.length; i < il; i ++ ) { + } - const morphAttribute = morphAttributesPosition[ i ]; - _boxMorphTargets.setFromBufferAttribute( morphAttribute ); + setX( index, x ) { - if ( this.morphTargetsRelative ) { + if ( this.normalized ) x = normalize( x, this.array ); - _vector$7.addVectors( _box$1.min, _boxMorphTargets.min ); - _box$1.expandByPoint( _vector$7 ); + this.array[ index * this.itemSize ] = toHalfFloat( x ); - _vector$7.addVectors( _box$1.max, _boxMorphTargets.max ); - _box$1.expandByPoint( _vector$7 ); + return this; - } else { + } - _box$1.expandByPoint( _boxMorphTargets.min ); - _box$1.expandByPoint( _boxMorphTargets.max ); + getY( index ) { - } + let y = fromHalfFloat( this.array[ index * this.itemSize + 1 ] ); - } + if ( this.normalized ) y = denormalize( y, this.array ); - } + return y; - _box$1.getCenter( center ); + } - // second, try to find a boundingSphere with a radius smaller than the - // boundingSphere of the boundingBox: sqrt(3) smaller in the best case + setY( index, y ) { - let maxRadiusSq = 0; + if ( this.normalized ) y = normalize( y, this.array ); - for ( let i = 0, il = position.count; i < il; i ++ ) { + this.array[ index * this.itemSize + 1 ] = toHalfFloat( y ); - _vector$7.fromBufferAttribute( position, i ); + return this; - maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( _vector$7 ) ); + } - } + getZ( index ) { - // process morph attributes if present + let z = fromHalfFloat( this.array[ index * this.itemSize + 2 ] ); - if ( morphAttributesPosition ) { + if ( this.normalized ) z = denormalize( z, this.array ); - for ( let i = 0, il = morphAttributesPosition.length; i < il; i ++ ) { + return z; - const morphAttribute = morphAttributesPosition[ i ]; - const morphTargetsRelative = this.morphTargetsRelative; + } - for ( let j = 0, jl = morphAttribute.count; j < jl; j ++ ) { + setZ( index, z ) { - _vector$7.fromBufferAttribute( morphAttribute, j ); + if ( this.normalized ) z = normalize( z, this.array ); - if ( morphTargetsRelative ) { + this.array[ index * this.itemSize + 2 ] = toHalfFloat( z ); - _offset.fromBufferAttribute( position, j ); - _vector$7.add( _offset ); + return this; - } + } - maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( _vector$7 ) ); + getW( index ) { - } + let w = fromHalfFloat( this.array[ index * this.itemSize + 3 ] ); - } + if ( this.normalized ) w = denormalize( w, this.array ); - } + return w; - this.boundingSphere.radius = Math.sqrt( maxRadiusSq ); + } - if ( isNaN( this.boundingSphere.radius ) ) { + setW( index, w ) { - console.error( 'THREE.BufferGeometry.computeBoundingSphere(): Computed radius is NaN. The "position" attribute is likely to have NaN values.', this ); + if ( this.normalized ) w = normalize( w, this.array ); - } + this.array[ index * this.itemSize + 3 ] = toHalfFloat( w ); - } + return this; } - computeTangents() { - - const index = this.index; - const attributes = this.attributes; + setXY( index, x, y ) { - // based on http://www.terathon.com/code/tangent.html - // (per vertex tangents) + index *= this.itemSize; - if ( index === null || - attributes.position === undefined || - attributes.normal === undefined || - attributes.uv === undefined ) { + if ( this.normalized ) { - console.error( 'THREE.BufferGeometry: .computeTangents() failed. Missing required attributes (index, position, normal or uv)' ); - return; + x = normalize( x, this.array ); + y = normalize( y, this.array ); } - const indices = index.array; - const positions = attributes.position.array; - const normals = attributes.normal.array; - const uvs = attributes.uv.array; - - const nVertices = positions.length / 3; - - if ( this.hasAttribute( 'tangent' ) === false ) { + this.array[ index + 0 ] = toHalfFloat( x ); + this.array[ index + 1 ] = toHalfFloat( y ); - this.setAttribute( 'tangent', new BufferAttribute( new Float32Array( 4 * nVertices ), 4 ) ); + return this; - } + } - const tangents = this.getAttribute( 'tangent' ).array; + setXYZ( index, x, y, z ) { - const tan1 = [], tan2 = []; + index *= this.itemSize; - for ( let i = 0; i < nVertices; i ++ ) { + if ( this.normalized ) { - tan1[ i ] = new Vector3(); - tan2[ i ] = new Vector3(); + x = normalize( x, this.array ); + y = normalize( y, this.array ); + z = normalize( z, this.array ); } - const vA = new Vector3(), - vB = new Vector3(), - vC = new Vector3(), - - uvA = new Vector2(), - uvB = new Vector2(), - uvC = new Vector2(), - - sdir = new Vector3(), - tdir = new Vector3(); + this.array[ index + 0 ] = toHalfFloat( x ); + this.array[ index + 1 ] = toHalfFloat( y ); + this.array[ index + 2 ] = toHalfFloat( z ); - function handleTriangle( a, b, c ) { + return this; - vA.fromArray( positions, a * 3 ); - vB.fromArray( positions, b * 3 ); - vC.fromArray( positions, c * 3 ); + } - uvA.fromArray( uvs, a * 2 ); - uvB.fromArray( uvs, b * 2 ); - uvC.fromArray( uvs, c * 2 ); + setXYZW( index, x, y, z, w ) { - vB.sub( vA ); - vC.sub( vA ); + index *= this.itemSize; - uvB.sub( uvA ); - uvC.sub( uvA ); + if ( this.normalized ) { - const r = 1.0 / ( uvB.x * uvC.y - uvC.x * uvB.y ); + x = normalize( x, this.array ); + y = normalize( y, this.array ); + z = normalize( z, this.array ); + w = normalize( w, this.array ); - // silently ignore degenerate uv triangles having coincident or colinear vertices + } - if ( ! isFinite( r ) ) return; + this.array[ index + 0 ] = toHalfFloat( x ); + this.array[ index + 1 ] = toHalfFloat( y ); + this.array[ index + 2 ] = toHalfFloat( z ); + this.array[ index + 3 ] = toHalfFloat( w ); - sdir.copy( vB ).multiplyScalar( uvC.y ).addScaledVector( vC, - uvB.y ).multiplyScalar( r ); - tdir.copy( vC ).multiplyScalar( uvB.x ).addScaledVector( vB, - uvC.x ).multiplyScalar( r ); + return this; - tan1[ a ].add( sdir ); - tan1[ b ].add( sdir ); - tan1[ c ].add( sdir ); + } - tan2[ a ].add( tdir ); - tan2[ b ].add( tdir ); - tan2[ c ].add( tdir ); +} - } +/** + * Convenient class that can be used when creating a `Float32` buffer attribute with + * a plain `Array` instance. + * + * @augments BufferAttribute + */ +class Float32BufferAttribute extends BufferAttribute { - let groups = this.groups; + /** + * Constructs a new buffer attribute. + * + * @param {(Array|Float32Array)} array - The array holding the attribute data. + * @param {number} itemSize - The item size. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + */ + constructor( array, itemSize, normalized ) { - if ( groups.length === 0 ) { + super( new Float32Array( array ), itemSize, normalized ); - groups = [ { - start: 0, - count: indices.length - } ]; + } - } +} - for ( let i = 0, il = groups.length; i < il; ++ i ) { +let _id$2 = 0; - const group = groups[ i ]; +const _m1$2 = /*@__PURE__*/ new Matrix4(); +const _obj = /*@__PURE__*/ new Object3D(); +const _offset = /*@__PURE__*/ new Vector3(); +const _box$2 = /*@__PURE__*/ new Box3(); +const _boxMorphTargets = /*@__PURE__*/ new Box3(); +const _vector$8 = /*@__PURE__*/ new Vector3(); - const start = group.start; - const count = group.count; +/** + * A representation of mesh, line, or point geometry. Includes vertex + * positions, face indices, normals, colors, UVs, and custom attributes + * within buffers, reducing the cost of passing all this data to the GPU. + * + * ```js + * const geometry = new THREE.BufferGeometry(); + * // create a simple square shape. We duplicate the top left and bottom right + * // vertices because each vertex needs to appear once per triangle. + * const vertices = new Float32Array( [ + * -1.0, -1.0, 1.0, // v0 + * 1.0, -1.0, 1.0, // v1 + * 1.0, 1.0, 1.0, // v2 + * + * 1.0, 1.0, 1.0, // v3 + * -1.0, 1.0, 1.0, // v4 + * -1.0, -1.0, 1.0 // v5 + * ] ); + * // itemSize = 3 because there are 3 values (components) per vertex + * geometry.setAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) ); + * const material = new THREE.MeshBasicMaterial( { color: 0xff0000 } ); + * const mesh = new THREE.Mesh( geometry, material ); + * ``` + * + * @augments EventDispatcher + */ +class BufferGeometry extends EventDispatcher { - for ( let j = start, jl = start + count; j < jl; j += 3 ) { + /** + * Constructs a new geometry. + */ + constructor() { - handleTriangle( - indices[ j + 0 ], - indices[ j + 1 ], - indices[ j + 2 ] - ); + super(); - } + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isBufferGeometry = true; - } + /** + * The ID of the geometry. + * + * @name BufferGeometry#id + * @type {number} + * @readonly + */ + Object.defineProperty( this, 'id', { value: _id$2 ++ } ); - const tmp = new Vector3(), tmp2 = new Vector3(); - const n = new Vector3(), n2 = new Vector3(); + /** + * The UUID of the geometry. + * + * @type {string} + * @readonly + */ + this.uuid = generateUUID(); - function handleVertex( v ) { + /** + * The name of the geometry. + * + * @type {string} + */ + this.name = ''; + this.type = 'BufferGeometry'; - n.fromArray( normals, v * 3 ); - n2.copy( n ); + /** + * Allows for vertices to be re-used across multiple triangles; this is + * called using "indexed triangles". Each triangle is associated with the + * indices of three vertices. This attribute therefore stores the index of + * each vertex for each triangular face. If this attribute is not set, the + * renderer assumes that each three contiguous positions represent a single triangle. + * + * @type {?BufferAttribute} + * @default null + */ + this.index = null; - const t = tan1[ v ]; + /** + * A (storage) buffer attribute which was generated with a compute shader and + * now defines indirect draw calls. + * + * Can only be used with {@link WebGPURenderer} and a WebGPU backend. + * + * @type {?BufferAttribute} + * @default null + */ + this.indirect = null; - // Gram-Schmidt orthogonalize + /** + * This dictionary has as id the name of the attribute to be set and as value + * the buffer attribute to set it to. Rather than accessing this property directly, + * use `setAttribute()` and `getAttribute()` to access attributes of this geometry. + * + * @type {Object} + */ + this.attributes = {}; - tmp.copy( t ); - tmp.sub( n.multiplyScalar( n.dot( t ) ) ).normalize(); + /** + * This dictionary holds the morph targets of the geometry. + * + * Note: Once the geometry has been rendered, the morph attribute data cannot + * be changed. You will have to call `dispose()?, and create a new geometry instance. + * + * @type {Object} + */ + this.morphAttributes = {}; - // Calculate handedness + /** + * Used to control the morph target behavior; when set to `true`, the morph + * target data is treated as relative offsets, rather than as absolute + * positions/normals. + * + * @type {boolean} + * @default false + */ + this.morphTargetsRelative = false; - tmp2.crossVectors( n2, t ); - const test = tmp2.dot( tan2[ v ] ); - const w = ( test < 0.0 ) ? - 1.0 : 1.0; + /** + * Split the geometry into groups, each of which will be rendered in a + * separate draw call. This allows an array of materials to be used with the geometry. + * + * Use `addGroup()` and `clearGroups()` to edit groups, rather than modifying this array directly. + * + * Every vertex and index must belong to exactly one group — groups must not share vertices or + * indices, and must not leave vertices or indices unused. + * + * @type {Array} + */ + this.groups = []; - tangents[ v * 4 ] = tmp.x; - tangents[ v * 4 + 1 ] = tmp.y; - tangents[ v * 4 + 2 ] = tmp.z; - tangents[ v * 4 + 3 ] = w; + /** + * Bounding box for the geometry which can be calculated with `computeBoundingBox()`. + * + * @type {Box3} + * @default null + */ + this.boundingBox = null; - } + /** + * Bounding sphere for the geometry which can be calculated with `computeBoundingSphere()`. + * + * @type {Sphere} + * @default null + */ + this.boundingSphere = null; - for ( let i = 0, il = groups.length; i < il; ++ i ) { + /** + * Determines the part of the geometry to render. This should not be set directly, + * instead use `setDrawRange()`. + * + * @type {{start:number,count:number}} + */ + this.drawRange = { start: 0, count: Infinity }; - const group = groups[ i ]; + /** + * An object that can be used to store custom data about the geometry. + * It should not hold references to functions as these will not be cloned. + * + * @type {Object} + */ + this.userData = {}; - const start = group.start; - const count = group.count; + } - for ( let j = start, jl = start + count; j < jl; j += 3 ) { + /** + * Returns the index of this geometry. + * + * @return {?BufferAttribute} The index. Returns `null` if no index is defined. + */ + getIndex() { - handleVertex( indices[ j + 0 ] ); - handleVertex( indices[ j + 1 ] ); - handleVertex( indices[ j + 2 ] ); + return this.index; - } + } - } + /** + * Sets the given index to this geometry. + * + * @param {Array|BufferAttribute} index - The index to set. + * @return {BufferGeometry} A reference to this instance. + */ + setIndex( index ) { - } + if ( Array.isArray( index ) ) { - computeVertexNormals() { + this.index = new ( arrayNeedsUint32( index ) ? Uint32BufferAttribute : Uint16BufferAttribute )( index, 1 ); - const index = this.index; - const positionAttribute = this.getAttribute( 'position' ); + } else { - if ( positionAttribute !== undefined ) { + this.index = index; - let normalAttribute = this.getAttribute( 'normal' ); + } - if ( normalAttribute === undefined ) { + return this; - normalAttribute = new BufferAttribute( new Float32Array( positionAttribute.count * 3 ), 3 ); - this.setAttribute( 'normal', normalAttribute ); + } - } else { + /** + * Sets the given indirect attribute to this geometry. + * + * @param {BufferAttribute} indirect - The attribute holding indirect draw calls. + * @return {BufferGeometry} A reference to this instance. + */ + setIndirect( indirect ) { - // reset existing normals to zero + this.indirect = indirect; - for ( let i = 0, il = normalAttribute.count; i < il; i ++ ) { + return this; - normalAttribute.setXYZ( i, 0, 0, 0 ); + } - } + /** + * Returns the indirect attribute of this geometry. + * + * @return {?BufferAttribute} The indirect attribute. Returns `null` if no indirect attribute is defined. + */ + getIndirect() { - } + return this.indirect; - const pA = new Vector3(), pB = new Vector3(), pC = new Vector3(); - const nA = new Vector3(), nB = new Vector3(), nC = new Vector3(); - const cb = new Vector3(), ab = new Vector3(); + } - // indexed elements + /** + * Returns the buffer attribute for the given name. + * + * @param {string} name - The attribute name. + * @return {BufferAttribute|InterleavedBufferAttribute|undefined} The buffer attribute. + * Returns `undefined` if not attribute has been found. + */ + getAttribute( name ) { - if ( index ) { + return this.attributes[ name ]; - for ( let i = 0, il = index.count; i < il; i += 3 ) { + } - const vA = index.getX( i + 0 ); - const vB = index.getX( i + 1 ); - const vC = index.getX( i + 2 ); + /** + * Sets the given attribute for the given name. + * + * @param {string} name - The attribute name. + * @param {BufferAttribute|InterleavedBufferAttribute} attribute - The attribute to set. + * @return {BufferGeometry} A reference to this instance. + */ + setAttribute( name, attribute ) { - pA.fromBufferAttribute( positionAttribute, vA ); - pB.fromBufferAttribute( positionAttribute, vB ); - pC.fromBufferAttribute( positionAttribute, vC ); + this.attributes[ name ] = attribute; - cb.subVectors( pC, pB ); - ab.subVectors( pA, pB ); - cb.cross( ab ); + return this; - nA.fromBufferAttribute( normalAttribute, vA ); - nB.fromBufferAttribute( normalAttribute, vB ); - nC.fromBufferAttribute( normalAttribute, vC ); + } - nA.add( cb ); - nB.add( cb ); - nC.add( cb ); + /** + * Deletes the attribute for the given name. + * + * @param {string} name - The attribute name to delete. + * @return {BufferGeometry} A reference to this instance. + */ + deleteAttribute( name ) { - normalAttribute.setXYZ( vA, nA.x, nA.y, nA.z ); - normalAttribute.setXYZ( vB, nB.x, nB.y, nB.z ); - normalAttribute.setXYZ( vC, nC.x, nC.y, nC.z ); + delete this.attributes[ name ]; - } + return this; - } else { + } - // non-indexed elements (unconnected triangle soup) + /** + * Returns `true` if this geometry has an attribute for the given name. + * + * @param {string} name - The attribute name. + * @return {boolean} Whether this geometry has an attribute for the given name or not. + */ + hasAttribute( name ) { - for ( let i = 0, il = positionAttribute.count; i < il; i += 3 ) { + return this.attributes[ name ] !== undefined; - pA.fromBufferAttribute( positionAttribute, i + 0 ); - pB.fromBufferAttribute( positionAttribute, i + 1 ); - pC.fromBufferAttribute( positionAttribute, i + 2 ); + } - cb.subVectors( pC, pB ); - ab.subVectors( pA, pB ); - cb.cross( ab ); + /** + * Adds a group to this geometry. + * + * @param {number} start - The first element in this draw call. That is the first + * vertex for non-indexed geometry, otherwise the first triangle index. + * @param {number} count - Specifies how many vertices (or indices) are part of this group. + * @param {number} [materialIndex=0] - The material array index to use. + */ + addGroup( start, count, materialIndex = 0 ) { - normalAttribute.setXYZ( i + 0, cb.x, cb.y, cb.z ); - normalAttribute.setXYZ( i + 1, cb.x, cb.y, cb.z ); - normalAttribute.setXYZ( i + 2, cb.x, cb.y, cb.z ); + this.groups.push( { - } + start: start, + count: count, + materialIndex: materialIndex - } + } ); - this.normalizeNormals(); + } - normalAttribute.needsUpdate = true; + /** + * Clears all groups. + */ + clearGroups() { - } + this.groups = []; } - merge() { // @deprecated, r144 + /** + * Sets the draw range for this geometry. + * + * @param {number} start - The first vertex for non-indexed geometry, otherwise the first triangle index. + * @param {number} count - For non-indexed BufferGeometry, `count` is the number of vertices to render. + * For indexed BufferGeometry, `count` is the number of indices to render. + */ + setDrawRange( start, count ) { - console.error( 'THREE.BufferGeometry.merge() has been removed. Use THREE.BufferGeometryUtils.mergeGeometries() instead.' ); - return this; + this.drawRange.start = start; + this.drawRange.count = count; } - normalizeNormals() { - - const normals = this.attributes.normal; + /** + * Applies the given 4x4 transformation matrix to the geometry. + * + * @param {Matrix4} matrix - The matrix to apply. + * @return {BufferGeometry} A reference to this instance. + */ + applyMatrix4( matrix ) { - for ( let i = 0, il = normals.count; i < il; i ++ ) { + const position = this.attributes.position; - _vector$7.fromBufferAttribute( normals, i ); + if ( position !== undefined ) { - _vector$7.normalize(); + position.applyMatrix4( matrix ); - normals.setXYZ( i, _vector$7.x, _vector$7.y, _vector$7.z ); + position.needsUpdate = true; } - } + const normal = this.attributes.normal; - toNonIndexed() { + if ( normal !== undefined ) { - function convertBufferAttribute( attribute, indices ) { - - const array = attribute.array; - const itemSize = attribute.itemSize; - const normalized = attribute.normalized; + const normalMatrix = new Matrix3().getNormalMatrix( matrix ); - const array2 = new array.constructor( indices.length * itemSize ); + normal.applyNormalMatrix( normalMatrix ); - let index = 0, index2 = 0; + normal.needsUpdate = true; - for ( let i = 0, l = indices.length; i < l; i ++ ) { + } - if ( attribute.isInterleavedBufferAttribute ) { + const tangent = this.attributes.tangent; - index = indices[ i ] * attribute.data.stride + attribute.offset; + if ( tangent !== undefined ) { - } else { + tangent.transformDirection( matrix ); - index = indices[ i ] * itemSize; + tangent.needsUpdate = true; - } + } - for ( let j = 0; j < itemSize; j ++ ) { + if ( this.boundingBox !== null ) { - array2[ index2 ++ ] = array[ index ++ ]; + this.computeBoundingBox(); - } + } - } + if ( this.boundingSphere !== null ) { - return new BufferAttribute( array2, itemSize, normalized ); + this.computeBoundingSphere(); } - // + return this; - if ( this.index === null ) { + } - console.warn( 'THREE.BufferGeometry.toNonIndexed(): BufferGeometry is already non-indexed.' ); - return this; + /** + * Applies the rotation represented by the Quaternion to the geometry. + * + * @param {Quaternion} q - The Quaternion to apply. + * @return {BufferGeometry} A reference to this instance. + */ + applyQuaternion( q ) { - } + _m1$2.makeRotationFromQuaternion( q ); - const geometry2 = new BufferGeometry(); + this.applyMatrix4( _m1$2 ); - const indices = this.index.array; - const attributes = this.attributes; + return this; - // attributes + } - for ( const name in attributes ) { + /** + * Rotates the geometry about the X axis. This is typically done as a one time + * operation, and not during a loop. Use {@link Object3D#rotation} for typical + * real-time mesh rotation. + * + * @param {number} angle - The angle in radians. + * @return {BufferGeometry} A reference to this instance. + */ + rotateX( angle ) { - const attribute = attributes[ name ]; + // rotate geometry around world x-axis - const newAttribute = convertBufferAttribute( attribute, indices ); + _m1$2.makeRotationX( angle ); - geometry2.setAttribute( name, newAttribute ); + this.applyMatrix4( _m1$2 ); - } + return this; - // morph attributes + } - const morphAttributes = this.morphAttributes; + /** + * Rotates the geometry about the Y axis. This is typically done as a one time + * operation, and not during a loop. Use {@link Object3D#rotation} for typical + * real-time mesh rotation. + * + * @param {number} angle - The angle in radians. + * @return {BufferGeometry} A reference to this instance. + */ + rotateY( angle ) { - for ( const name in morphAttributes ) { + // rotate geometry around world y-axis - const morphArray = []; - const morphAttribute = morphAttributes[ name ]; // morphAttribute: array of Float32BufferAttributes + _m1$2.makeRotationY( angle ); - for ( let i = 0, il = morphAttribute.length; i < il; i ++ ) { + this.applyMatrix4( _m1$2 ); - const attribute = morphAttribute[ i ]; + return this; - const newAttribute = convertBufferAttribute( attribute, indices ); + } - morphArray.push( newAttribute ); + /** + * Rotates the geometry about the Z axis. This is typically done as a one time + * operation, and not during a loop. Use {@link Object3D#rotation} for typical + * real-time mesh rotation. + * + * @param {number} angle - The angle in radians. + * @return {BufferGeometry} A reference to this instance. + */ + rotateZ( angle ) { - } + // rotate geometry around world z-axis - geometry2.morphAttributes[ name ] = morphArray; + _m1$2.makeRotationZ( angle ); - } + this.applyMatrix4( _m1$2 ); - geometry2.morphTargetsRelative = this.morphTargetsRelative; + return this; - // groups + } - const groups = this.groups; + /** + * Translates the geometry. This is typically done as a one time + * operation, and not during a loop. Use {@link Object3D#position} for typical + * real-time mesh rotation. + * + * @param {number} x - The x offset. + * @param {number} y - The y offset. + * @param {number} z - The z offset. + * @return {BufferGeometry} A reference to this instance. + */ + translate( x, y, z ) { - for ( let i = 0, l = groups.length; i < l; i ++ ) { + // translate geometry - const group = groups[ i ]; - geometry2.addGroup( group.start, group.count, group.materialIndex ); + _m1$2.makeTranslation( x, y, z ); - } + this.applyMatrix4( _m1$2 ); - return geometry2; + return this; } - toJSON() { + /** + * Scales the geometry. This is typically done as a one time + * operation, and not during a loop. Use {@link Object3D#scale} for typical + * real-time mesh rotation. + * + * @param {number} x - The x scale. + * @param {number} y - The y scale. + * @param {number} z - The z scale. + * @return {BufferGeometry} A reference to this instance. + */ + scale( x, y, z ) { - const data = { - metadata: { - version: 4.6, - type: 'BufferGeometry', - generator: 'BufferGeometry.toJSON' - } - }; + // scale geometry - // standard BufferGeometry serialization + _m1$2.makeScale( x, y, z ); - data.uuid = this.uuid; - data.type = this.type; - if ( this.name !== '' ) data.name = this.name; - if ( Object.keys( this.userData ).length > 0 ) data.userData = this.userData; + this.applyMatrix4( _m1$2 ); - if ( this.parameters !== undefined ) { + return this; - const parameters = this.parameters; + } - for ( const key in parameters ) { + /** + * Rotates the geometry to face a point in 3D space. This is typically done as a one time + * operation, and not during a loop. Use {@link Object3D#lookAt} for typical + * real-time mesh rotation. + * + * @param {Vector3} vector - The target point. + * @return {BufferGeometry} A reference to this instance. + */ + lookAt( vector ) { - if ( parameters[ key ] !== undefined ) data[ key ] = parameters[ key ]; + _obj.lookAt( vector ); - } + _obj.updateMatrix(); - return data; + this.applyMatrix4( _obj.matrix ); - } + return this; - // for simplicity the code assumes attributes are not shared across geometries, see #15811 + } - data.data = { attributes: {} }; + /** + * Center the geometry based on its bounding box. + * + * @return {BufferGeometry} A reference to this instance. + */ + center() { - const index = this.index; + this.computeBoundingBox(); - if ( index !== null ) { + this.boundingBox.getCenter( _offset ).negate(); - data.data.index = { - type: index.array.constructor.name, - array: Array.prototype.slice.call( index.array ) - }; + this.translate( _offset.x, _offset.y, _offset.z ); - } + return this; - const attributes = this.attributes; + } - for ( const key in attributes ) { + /** + * Defines a geometry by creating a `position` attribute based on the given array of points. The array + * can hold 2D or 3D vectors. When using two-dimensional data, the `z` coordinate for all vertices is + * set to `0`. + * + * If the method is used with an existing `position` attribute, the vertex data are overwritten with the + * data from the array. The length of the array must match the vertex count. + * + * @param {Array|Array} points - The points. + * @return {BufferGeometry} A reference to this instance. + */ + setFromPoints( points ) { - const attribute = attributes[ key ]; + const positionAttribute = this.getAttribute( 'position' ); - data.data.attributes[ key ] = attribute.toJSON( data.data ); + if ( positionAttribute === undefined ) { - } + const position = []; - const morphAttributes = {}; - let hasMorphAttributes = false; + for ( let i = 0, l = points.length; i < l; i ++ ) { - for ( const key in this.morphAttributes ) { + const point = points[ i ]; + position.push( point.x, point.y, point.z || 0 ); - const attributeArray = this.morphAttributes[ key ]; + } - const array = []; + this.setAttribute( 'position', new Float32BufferAttribute( position, 3 ) ); - for ( let i = 0, il = attributeArray.length; i < il; i ++ ) { + } else { - const attribute = attributeArray[ i ]; + const l = Math.min( points.length, positionAttribute.count ); // make sure data do not exceed buffer size - array.push( attribute.toJSON( data.data ) ); + for ( let i = 0; i < l; i ++ ) { - } + const point = points[ i ]; + positionAttribute.setXYZ( i, point.x, point.y, point.z || 0 ); - if ( array.length > 0 ) { + } - morphAttributes[ key ] = array; + if ( points.length > positionAttribute.count ) { - hasMorphAttributes = true; + console.warn( 'THREE.BufferGeometry: Buffer size too small for points data. Use .dispose() and create a new geometry.' ); } - } - - if ( hasMorphAttributes ) { - - data.data.morphAttributes = morphAttributes; - data.data.morphTargetsRelative = this.morphTargetsRelative; + positionAttribute.needsUpdate = true; } - const groups = this.groups; - - if ( groups.length > 0 ) { - - data.data.groups = JSON.parse( JSON.stringify( groups ) ); + return this; - } + } - const boundingSphere = this.boundingSphere; + /** + * Computes the bounding box of the geometry, and updates the `boundingBox` member. + * The bounding box is not computed by the engine; it must be computed by your app. + * You may need to recompute the bounding box if the geometry vertices are modified. + */ + computeBoundingBox() { - if ( boundingSphere !== null ) { + if ( this.boundingBox === null ) { - data.data.boundingSphere = { - center: boundingSphere.center.toArray(), - radius: boundingSphere.radius - }; + this.boundingBox = new Box3(); } - return data; + const position = this.attributes.position; + const morphAttributesPosition = this.morphAttributes.position; - } + if ( position && position.isGLBufferAttribute ) { - clone() { + console.error( 'THREE.BufferGeometry.computeBoundingBox(): GLBufferAttribute requires a manual bounding box.', this ); - return new this.constructor().copy( this ); + this.boundingBox.set( + new Vector3( - Infinity, - Infinity, - Infinity ), + new Vector3( + Infinity, + Infinity, + Infinity ) + ); - } + return; - copy( source ) { + } - // reset + if ( position !== undefined ) { - this.index = null; - this.attributes = {}; - this.morphAttributes = {}; - this.groups = []; - this.boundingBox = null; - this.boundingSphere = null; + this.boundingBox.setFromBufferAttribute( position ); - // used for storing cloned, shared data + // process morph attributes if present - const data = {}; + if ( morphAttributesPosition ) { - // name + for ( let i = 0, il = morphAttributesPosition.length; i < il; i ++ ) { - this.name = source.name; + const morphAttribute = morphAttributesPosition[ i ]; + _box$2.setFromBufferAttribute( morphAttribute ); - // index + if ( this.morphTargetsRelative ) { - const index = source.index; + _vector$8.addVectors( this.boundingBox.min, _box$2.min ); + this.boundingBox.expandByPoint( _vector$8 ); - if ( index !== null ) { + _vector$8.addVectors( this.boundingBox.max, _box$2.max ); + this.boundingBox.expandByPoint( _vector$8 ); - this.setIndex( index.clone( data ) ); + } else { - } + this.boundingBox.expandByPoint( _box$2.min ); + this.boundingBox.expandByPoint( _box$2.max ); - // attributes + } - const attributes = source.attributes; + } - for ( const name in attributes ) { + } - const attribute = attributes[ name ]; - this.setAttribute( name, attribute.clone( data ) ); + } else { - } + this.boundingBox.makeEmpty(); - // morph attributes + } - const morphAttributes = source.morphAttributes; + if ( isNaN( this.boundingBox.min.x ) || isNaN( this.boundingBox.min.y ) || isNaN( this.boundingBox.min.z ) ) { - for ( const name in morphAttributes ) { + console.error( 'THREE.BufferGeometry.computeBoundingBox(): Computed min/max have NaN values. The "position" attribute is likely to have NaN values.', this ); - const array = []; - const morphAttribute = morphAttributes[ name ]; // morphAttribute: array of Float32BufferAttributes + } - for ( let i = 0, l = morphAttribute.length; i < l; i ++ ) { + } - array.push( morphAttribute[ i ].clone( data ) ); + /** + * Computes the bounding sphere of the geometry, and updates the `boundingSphere` member. + * The engine automatically computes the bounding sphere when it is needed, e.g., for ray casting or view frustum culling. + * You may need to recompute the bounding sphere if the geometry vertices are modified. + */ + computeBoundingSphere() { - } + if ( this.boundingSphere === null ) { - this.morphAttributes[ name ] = array; + this.boundingSphere = new Sphere(); } - this.morphTargetsRelative = source.morphTargetsRelative; + const position = this.attributes.position; + const morphAttributesPosition = this.morphAttributes.position; - // groups + if ( position && position.isGLBufferAttribute ) { - const groups = source.groups; + console.error( 'THREE.BufferGeometry.computeBoundingSphere(): GLBufferAttribute requires a manual bounding sphere.', this ); - for ( let i = 0, l = groups.length; i < l; i ++ ) { + this.boundingSphere.set( new Vector3(), Infinity ); - const group = groups[ i ]; - this.addGroup( group.start, group.count, group.materialIndex ); + return; } - // bounding box - - const boundingBox = source.boundingBox; + if ( position ) { - if ( boundingBox !== null ) { + // first, find the center of the bounding sphere - this.boundingBox = boundingBox.clone(); + const center = this.boundingSphere.center; - } + _box$2.setFromBufferAttribute( position ); - // bounding sphere + // process morph attributes if present - const boundingSphere = source.boundingSphere; + if ( morphAttributesPosition ) { - if ( boundingSphere !== null ) { + for ( let i = 0, il = morphAttributesPosition.length; i < il; i ++ ) { - this.boundingSphere = boundingSphere.clone(); + const morphAttribute = morphAttributesPosition[ i ]; + _boxMorphTargets.setFromBufferAttribute( morphAttribute ); - } + if ( this.morphTargetsRelative ) { - // draw range + _vector$8.addVectors( _box$2.min, _boxMorphTargets.min ); + _box$2.expandByPoint( _vector$8 ); - this.drawRange.start = source.drawRange.start; - this.drawRange.count = source.drawRange.count; + _vector$8.addVectors( _box$2.max, _boxMorphTargets.max ); + _box$2.expandByPoint( _vector$8 ); - // user data + } else { - this.userData = source.userData; + _box$2.expandByPoint( _boxMorphTargets.min ); + _box$2.expandByPoint( _boxMorphTargets.max ); - return this; + } - } + } - dispose() { + } - this.dispatchEvent( { type: 'dispose' } ); + _box$2.getCenter( center ); - } + // second, try to find a boundingSphere with a radius smaller than the + // boundingSphere of the boundingBox: sqrt(3) smaller in the best case -} + let maxRadiusSq = 0; -const _inverseMatrix$3 = /*@__PURE__*/ new Matrix4(); -const _ray$3 = /*@__PURE__*/ new Ray(); -const _sphere$5 = /*@__PURE__*/ new Sphere(); -const _sphereHitAt = /*@__PURE__*/ new Vector3(); + for ( let i = 0, il = position.count; i < il; i ++ ) { -const _vA$1 = /*@__PURE__*/ new Vector3(); -const _vB$1 = /*@__PURE__*/ new Vector3(); -const _vC$1 = /*@__PURE__*/ new Vector3(); + _vector$8.fromBufferAttribute( position, i ); -const _tempA = /*@__PURE__*/ new Vector3(); -const _morphA = /*@__PURE__*/ new Vector3(); + maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( _vector$8 ) ); -const _uvA$1 = /*@__PURE__*/ new Vector2(); -const _uvB$1 = /*@__PURE__*/ new Vector2(); -const _uvC$1 = /*@__PURE__*/ new Vector2(); + } -const _normalA = /*@__PURE__*/ new Vector3(); -const _normalB = /*@__PURE__*/ new Vector3(); -const _normalC = /*@__PURE__*/ new Vector3(); + // process morph attributes if present -const _intersectionPoint = /*@__PURE__*/ new Vector3(); -const _intersectionPointWorld = /*@__PURE__*/ new Vector3(); + if ( morphAttributesPosition ) { -class Mesh extends Object3D { + for ( let i = 0, il = morphAttributesPosition.length; i < il; i ++ ) { - constructor( geometry = new BufferGeometry(), material = new MeshBasicMaterial() ) { + const morphAttribute = morphAttributesPosition[ i ]; + const morphTargetsRelative = this.morphTargetsRelative; - super(); + for ( let j = 0, jl = morphAttribute.count; j < jl; j ++ ) { - this.isMesh = true; + _vector$8.fromBufferAttribute( morphAttribute, j ); - this.type = 'Mesh'; + if ( morphTargetsRelative ) { - this.geometry = geometry; - this.material = material; + _offset.fromBufferAttribute( position, j ); + _vector$8.add( _offset ); - this.updateMorphTargets(); + } - } + maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( _vector$8 ) ); - copy( source, recursive ) { + } - super.copy( source, recursive ); + } - if ( source.morphTargetInfluences !== undefined ) { + } - this.morphTargetInfluences = source.morphTargetInfluences.slice(); + this.boundingSphere.radius = Math.sqrt( maxRadiusSq ); - } + if ( isNaN( this.boundingSphere.radius ) ) { - if ( source.morphTargetDictionary !== undefined ) { + console.error( 'THREE.BufferGeometry.computeBoundingSphere(): Computed radius is NaN. The "position" attribute is likely to have NaN values.', this ); - this.morphTargetDictionary = Object.assign( {}, source.morphTargetDictionary ); + } } - this.material = source.material; - this.geometry = source.geometry; + } - return this; + /** + * Calculates and adds a tangent attribute to this geometry. + * + * The computation is only supported for indexed geometries and if position, normal, and uv attributes + * are defined. When using a tangent space normal map, prefer the MikkTSpace algorithm provided by + * {@link BufferGeometryUtils#computeMikkTSpaceTangents} instead. + */ + computeTangents() { - } + const index = this.index; + const attributes = this.attributes; - updateMorphTargets() { + // based on http://www.terathon.com/code/tangent.html + // (per vertex tangents) - const geometry = this.geometry; + if ( index === null || + attributes.position === undefined || + attributes.normal === undefined || + attributes.uv === undefined ) { - const morphAttributes = geometry.morphAttributes; - const keys = Object.keys( morphAttributes ); + console.error( 'THREE.BufferGeometry: .computeTangents() failed. Missing required attributes (index, position, normal or uv)' ); + return; - if ( keys.length > 0 ) { + } - const morphAttribute = morphAttributes[ keys[ 0 ] ]; + const positionAttribute = attributes.position; + const normalAttribute = attributes.normal; + const uvAttribute = attributes.uv; - if ( morphAttribute !== undefined ) { + if ( this.hasAttribute( 'tangent' ) === false ) { - this.morphTargetInfluences = []; - this.morphTargetDictionary = {}; + this.setAttribute( 'tangent', new BufferAttribute( new Float32Array( 4 * positionAttribute.count ), 4 ) ); - for ( let m = 0, ml = morphAttribute.length; m < ml; m ++ ) { + } - const name = morphAttribute[ m ].name || String( m ); + const tangentAttribute = this.getAttribute( 'tangent' ); - this.morphTargetInfluences.push( 0 ); - this.morphTargetDictionary[ name ] = m; + const tan1 = [], tan2 = []; - } + for ( let i = 0; i < positionAttribute.count; i ++ ) { - } + tan1[ i ] = new Vector3(); + tan2[ i ] = new Vector3(); } - } + const vA = new Vector3(), + vB = new Vector3(), + vC = new Vector3(), - getVertexPosition( index, target ) { + uvA = new Vector2(), + uvB = new Vector2(), + uvC = new Vector2(), - const geometry = this.geometry; - const position = geometry.attributes.position; - const morphPosition = geometry.morphAttributes.position; - const morphTargetsRelative = geometry.morphTargetsRelative; + sdir = new Vector3(), + tdir = new Vector3(); - target.fromBufferAttribute( position, index ); + function handleTriangle( a, b, c ) { - const morphInfluences = this.morphTargetInfluences; + vA.fromBufferAttribute( positionAttribute, a ); + vB.fromBufferAttribute( positionAttribute, b ); + vC.fromBufferAttribute( positionAttribute, c ); - if ( morphPosition && morphInfluences ) { + uvA.fromBufferAttribute( uvAttribute, a ); + uvB.fromBufferAttribute( uvAttribute, b ); + uvC.fromBufferAttribute( uvAttribute, c ); - _morphA.set( 0, 0, 0 ); + vB.sub( vA ); + vC.sub( vA ); - for ( let i = 0, il = morphPosition.length; i < il; i ++ ) { + uvB.sub( uvA ); + uvC.sub( uvA ); - const influence = morphInfluences[ i ]; - const morphAttribute = morphPosition[ i ]; + const r = 1.0 / ( uvB.x * uvC.y - uvC.x * uvB.y ); - if ( influence === 0 ) continue; + // silently ignore degenerate uv triangles having coincident or colinear vertices - _tempA.fromBufferAttribute( morphAttribute, index ); + if ( ! isFinite( r ) ) return; - if ( morphTargetsRelative ) { + sdir.copy( vB ).multiplyScalar( uvC.y ).addScaledVector( vC, - uvB.y ).multiplyScalar( r ); + tdir.copy( vC ).multiplyScalar( uvB.x ).addScaledVector( vB, - uvC.x ).multiplyScalar( r ); - _morphA.addScaledVector( _tempA, influence ); + tan1[ a ].add( sdir ); + tan1[ b ].add( sdir ); + tan1[ c ].add( sdir ); - } else { + tan2[ a ].add( tdir ); + tan2[ b ].add( tdir ); + tan2[ c ].add( tdir ); - _morphA.addScaledVector( _tempA.sub( target ), influence ); + } - } + let groups = this.groups; - } + if ( groups.length === 0 ) { - target.add( _morphA ); + groups = [ { + start: 0, + count: index.count + } ]; } - return target; + for ( let i = 0, il = groups.length; i < il; ++ i ) { - } + const group = groups[ i ]; - raycast( raycaster, intersects ) { + const start = group.start; + const count = group.count; - const geometry = this.geometry; - const material = this.material; - const matrixWorld = this.matrixWorld; + for ( let j = start, jl = start + count; j < jl; j += 3 ) { - if ( material === undefined ) return; + handleTriangle( + index.getX( j + 0 ), + index.getX( j + 1 ), + index.getX( j + 2 ) + ); - // test with bounding sphere in world space + } - if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); + } - _sphere$5.copy( geometry.boundingSphere ); - _sphere$5.applyMatrix4( matrixWorld ); + const tmp = new Vector3(), tmp2 = new Vector3(); + const n = new Vector3(), n2 = new Vector3(); - // check distance from ray origin to bounding sphere + function handleVertex( v ) { - _ray$3.copy( raycaster.ray ).recast( raycaster.near ); + n.fromBufferAttribute( normalAttribute, v ); + n2.copy( n ); - if ( _sphere$5.containsPoint( _ray$3.origin ) === false ) { + const t = tan1[ v ]; - if ( _ray$3.intersectSphere( _sphere$5, _sphereHitAt ) === null ) return; + // Gram-Schmidt orthogonalize - if ( _ray$3.origin.distanceToSquared( _sphereHitAt ) > ( raycaster.far - raycaster.near ) ** 2 ) return; + tmp.copy( t ); + tmp.sub( n.multiplyScalar( n.dot( t ) ) ).normalize(); - } + // Calculate handedness - // convert ray to local space of mesh + tmp2.crossVectors( n2, t ); + const test = tmp2.dot( tan2[ v ] ); + const w = ( test < 0.0 ) ? -1 : 1.0; - _inverseMatrix$3.copy( matrixWorld ).invert(); - _ray$3.copy( raycaster.ray ).applyMatrix4( _inverseMatrix$3 ); + tangentAttribute.setXYZW( v, tmp.x, tmp.y, tmp.z, w ); - // test with bounding box in local space + } - if ( geometry.boundingBox !== null ) { + for ( let i = 0, il = groups.length; i < il; ++ i ) { - if ( _ray$3.intersectsBox( geometry.boundingBox ) === false ) return; + const group = groups[ i ]; - } + const start = group.start; + const count = group.count; - // test for intersections with geometry + for ( let j = start, jl = start + count; j < jl; j += 3 ) { - this._computeIntersections( raycaster, intersects, _ray$3 ); + handleVertex( index.getX( j + 0 ) ); + handleVertex( index.getX( j + 1 ) ); + handleVertex( index.getX( j + 2 ) ); - } + } - _computeIntersections( raycaster, intersects, rayLocalSpace ) { + } - let intersection; - - const geometry = this.geometry; - const material = this.material; - - const index = geometry.index; - const position = geometry.attributes.position; - const uv = geometry.attributes.uv; - const uv1 = geometry.attributes.uv1; - const normal = geometry.attributes.normal; - const groups = geometry.groups; - const drawRange = geometry.drawRange; - - if ( index !== null ) { - - // indexed buffer geometry - - if ( Array.isArray( material ) ) { - - for ( let i = 0, il = groups.length; i < il; i ++ ) { - - const group = groups[ i ]; - const groupMaterial = material[ group.materialIndex ]; - - const start = Math.max( group.start, drawRange.start ); - const end = Math.min( index.count, Math.min( ( group.start + group.count ), ( drawRange.start + drawRange.count ) ) ); - - for ( let j = start, jl = end; j < jl; j += 3 ) { - - const a = index.getX( j ); - const b = index.getX( j + 1 ); - const c = index.getX( j + 2 ); + } - intersection = checkGeometryIntersection( this, groupMaterial, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c ); + /** + * Computes vertex normals for the given vertex data. For indexed geometries, the method sets + * each vertex normal to be the average of the face normals of the faces that share that vertex. + * For non-indexed geometries, vertices are not shared, and the method sets each vertex normal + * to be the same as the face normal. + */ + computeVertexNormals() { - if ( intersection ) { + const index = this.index; + const positionAttribute = this.getAttribute( 'position' ); - intersection.faceIndex = Math.floor( j / 3 ); // triangle number in indexed buffer semantics - intersection.face.materialIndex = group.materialIndex; - intersects.push( intersection ); + if ( positionAttribute !== undefined ) { - } + let normalAttribute = this.getAttribute( 'normal' ); - } + if ( normalAttribute === undefined ) { - } + normalAttribute = new BufferAttribute( new Float32Array( positionAttribute.count * 3 ), 3 ); + this.setAttribute( 'normal', normalAttribute ); } else { - const start = Math.max( 0, drawRange.start ); - const end = Math.min( index.count, ( drawRange.start + drawRange.count ) ); - - for ( let i = start, il = end; i < il; i += 3 ) { - - const a = index.getX( i ); - const b = index.getX( i + 1 ); - const c = index.getX( i + 2 ); - - intersection = checkGeometryIntersection( this, material, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c ); - - if ( intersection ) { + // reset existing normals to zero - intersection.faceIndex = Math.floor( i / 3 ); // triangle number in indexed buffer semantics - intersects.push( intersection ); + for ( let i = 0, il = normalAttribute.count; i < il; i ++ ) { - } + normalAttribute.setXYZ( i, 0, 0, 0 ); } } - } else if ( position !== undefined ) { - - // non-indexed buffer geometry - - if ( Array.isArray( material ) ) { - - for ( let i = 0, il = groups.length; i < il; i ++ ) { + const pA = new Vector3(), pB = new Vector3(), pC = new Vector3(); + const nA = new Vector3(), nB = new Vector3(), nC = new Vector3(); + const cb = new Vector3(), ab = new Vector3(); - const group = groups[ i ]; - const groupMaterial = material[ group.materialIndex ]; + // indexed elements - const start = Math.max( group.start, drawRange.start ); - const end = Math.min( position.count, Math.min( ( group.start + group.count ), ( drawRange.start + drawRange.count ) ) ); + if ( index ) { - for ( let j = start, jl = end; j < jl; j += 3 ) { + for ( let i = 0, il = index.count; i < il; i += 3 ) { - const a = j; - const b = j + 1; - const c = j + 2; + const vA = index.getX( i + 0 ); + const vB = index.getX( i + 1 ); + const vC = index.getX( i + 2 ); - intersection = checkGeometryIntersection( this, groupMaterial, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c ); + pA.fromBufferAttribute( positionAttribute, vA ); + pB.fromBufferAttribute( positionAttribute, vB ); + pC.fromBufferAttribute( positionAttribute, vC ); - if ( intersection ) { + cb.subVectors( pC, pB ); + ab.subVectors( pA, pB ); + cb.cross( ab ); - intersection.faceIndex = Math.floor( j / 3 ); // triangle number in non-indexed buffer semantics - intersection.face.materialIndex = group.materialIndex; - intersects.push( intersection ); + nA.fromBufferAttribute( normalAttribute, vA ); + nB.fromBufferAttribute( normalAttribute, vB ); + nC.fromBufferAttribute( normalAttribute, vC ); - } + nA.add( cb ); + nB.add( cb ); + nC.add( cb ); - } + normalAttribute.setXYZ( vA, nA.x, nA.y, nA.z ); + normalAttribute.setXYZ( vB, nB.x, nB.y, nB.z ); + normalAttribute.setXYZ( vC, nC.x, nC.y, nC.z ); } } else { - const start = Math.max( 0, drawRange.start ); - const end = Math.min( position.count, ( drawRange.start + drawRange.count ) ); - - for ( let i = start, il = end; i < il; i += 3 ) { - - const a = i; - const b = i + 1; - const c = i + 2; + // non-indexed elements (unconnected triangle soup) - intersection = checkGeometryIntersection( this, material, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c ); + for ( let i = 0, il = positionAttribute.count; i < il; i += 3 ) { - if ( intersection ) { + pA.fromBufferAttribute( positionAttribute, i + 0 ); + pB.fromBufferAttribute( positionAttribute, i + 1 ); + pC.fromBufferAttribute( positionAttribute, i + 2 ); - intersection.faceIndex = Math.floor( i / 3 ); // triangle number in non-indexed buffer semantics - intersects.push( intersection ); + cb.subVectors( pC, pB ); + ab.subVectors( pA, pB ); + cb.cross( ab ); - } + normalAttribute.setXYZ( i + 0, cb.x, cb.y, cb.z ); + normalAttribute.setXYZ( i + 1, cb.x, cb.y, cb.z ); + normalAttribute.setXYZ( i + 2, cb.x, cb.y, cb.z ); } } + this.normalizeNormals(); + + normalAttribute.needsUpdate = true; + } } -} + /** + * Ensures every normal vector in a geometry will have a magnitude of `1`. This will + * correct lighting on the geometry surfaces. + */ + normalizeNormals() { -function checkIntersection( object, material, raycaster, ray, pA, pB, pC, point ) { + const normals = this.attributes.normal; - let intersect; + for ( let i = 0, il = normals.count; i < il; i ++ ) { - if ( material.side === BackSide ) { + _vector$8.fromBufferAttribute( normals, i ); - intersect = ray.intersectTriangle( pC, pB, pA, true, point ); + _vector$8.normalize(); - } else { + normals.setXYZ( i, _vector$8.x, _vector$8.y, _vector$8.z ); - intersect = ray.intersectTriangle( pA, pB, pC, ( material.side === FrontSide ), point ); + } } - if ( intersect === null ) return null; - - _intersectionPointWorld.copy( point ); - _intersectionPointWorld.applyMatrix4( object.matrixWorld ); + /** + * Return a new non-index version of this indexed geometry. If the geometry + * is already non-indexed, the method is a NOOP. + * + * @return {BufferGeometry} The non-indexed version of this indexed geometry. + */ + toNonIndexed() { - const distance = raycaster.ray.origin.distanceTo( _intersectionPointWorld ); + function convertBufferAttribute( attribute, indices ) { - if ( distance < raycaster.near || distance > raycaster.far ) return null; + const array = attribute.array; + const itemSize = attribute.itemSize; + const normalized = attribute.normalized; - return { - distance: distance, - point: _intersectionPointWorld.clone(), - object: object - }; + const array2 = new array.constructor( indices.length * itemSize ); -} + let index = 0, index2 = 0; -function checkGeometryIntersection( object, material, raycaster, ray, uv, uv1, normal, a, b, c ) { + for ( let i = 0, l = indices.length; i < l; i ++ ) { - object.getVertexPosition( a, _vA$1 ); - object.getVertexPosition( b, _vB$1 ); - object.getVertexPosition( c, _vC$1 ); + if ( attribute.isInterleavedBufferAttribute ) { - const intersection = checkIntersection( object, material, raycaster, ray, _vA$1, _vB$1, _vC$1, _intersectionPoint ); + index = indices[ i ] * attribute.data.stride + attribute.offset; - if ( intersection ) { + } else { - if ( uv ) { + index = indices[ i ] * itemSize; - _uvA$1.fromBufferAttribute( uv, a ); - _uvB$1.fromBufferAttribute( uv, b ); - _uvC$1.fromBufferAttribute( uv, c ); + } - intersection.uv = Triangle.getInterpolation( _intersectionPoint, _vA$1, _vB$1, _vC$1, _uvA$1, _uvB$1, _uvC$1, new Vector2() ); + for ( let j = 0; j < itemSize; j ++ ) { - } + array2[ index2 ++ ] = array[ index ++ ]; - if ( uv1 ) { + } - _uvA$1.fromBufferAttribute( uv1, a ); - _uvB$1.fromBufferAttribute( uv1, b ); - _uvC$1.fromBufferAttribute( uv1, c ); + } - intersection.uv1 = Triangle.getInterpolation( _intersectionPoint, _vA$1, _vB$1, _vC$1, _uvA$1, _uvB$1, _uvC$1, new Vector2() ); - intersection.uv2 = intersection.uv1; // @deprecated, r152 + return new BufferAttribute( array2, itemSize, normalized ); } - if ( normal ) { + // - _normalA.fromBufferAttribute( normal, a ); - _normalB.fromBufferAttribute( normal, b ); - _normalC.fromBufferAttribute( normal, c ); + if ( this.index === null ) { - intersection.normal = Triangle.getInterpolation( _intersectionPoint, _vA$1, _vB$1, _vC$1, _normalA, _normalB, _normalC, new Vector3() ); + console.warn( 'THREE.BufferGeometry.toNonIndexed(): BufferGeometry is already non-indexed.' ); + return this; - if ( intersection.normal.dot( ray.direction ) > 0 ) { + } - intersection.normal.multiplyScalar( - 1 ); + const geometry2 = new BufferGeometry(); - } + const indices = this.index.array; + const attributes = this.attributes; - } + // attributes - const face = { - a: a, - b: b, - c: c, - normal: new Vector3(), - materialIndex: 0 - }; + for ( const name in attributes ) { - Triangle.getNormal( _vA$1, _vB$1, _vC$1, face.normal ); + const attribute = attributes[ name ]; - intersection.face = face; + const newAttribute = convertBufferAttribute( attribute, indices ); - } + geometry2.setAttribute( name, newAttribute ); - return intersection; + } -} + // morph attributes -class BoxGeometry extends BufferGeometry { + const morphAttributes = this.morphAttributes; - constructor( width = 1, height = 1, depth = 1, widthSegments = 1, heightSegments = 1, depthSegments = 1 ) { + for ( const name in morphAttributes ) { - super(); + const morphArray = []; + const morphAttribute = morphAttributes[ name ]; // morphAttribute: array of Float32BufferAttributes - this.type = 'BoxGeometry'; + for ( let i = 0, il = morphAttribute.length; i < il; i ++ ) { - this.parameters = { - width: width, - height: height, - depth: depth, - widthSegments: widthSegments, - heightSegments: heightSegments, - depthSegments: depthSegments - }; + const attribute = morphAttribute[ i ]; - const scope = this; + const newAttribute = convertBufferAttribute( attribute, indices ); - // segments + morphArray.push( newAttribute ); - widthSegments = Math.floor( widthSegments ); - heightSegments = Math.floor( heightSegments ); - depthSegments = Math.floor( depthSegments ); + } - // buffers + geometry2.morphAttributes[ name ] = morphArray; - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; + } - // helper variables + geometry2.morphTargetsRelative = this.morphTargetsRelative; - let numberOfVertices = 0; - let groupStart = 0; + // groups - // build each side of the box geometry + const groups = this.groups; - buildPlane( 'z', 'y', 'x', - 1, - 1, depth, height, width, depthSegments, heightSegments, 0 ); // px - buildPlane( 'z', 'y', 'x', 1, - 1, depth, height, - width, depthSegments, heightSegments, 1 ); // nx - buildPlane( 'x', 'z', 'y', 1, 1, width, depth, height, widthSegments, depthSegments, 2 ); // py - buildPlane( 'x', 'z', 'y', 1, - 1, width, depth, - height, widthSegments, depthSegments, 3 ); // ny - buildPlane( 'x', 'y', 'z', 1, - 1, width, height, depth, widthSegments, heightSegments, 4 ); // pz - buildPlane( 'x', 'y', 'z', - 1, - 1, width, height, - depth, widthSegments, heightSegments, 5 ); // nz + for ( let i = 0, l = groups.length; i < l; i ++ ) { - // build geometry + const group = groups[ i ]; + geometry2.addGroup( group.start, group.count, group.materialIndex ); - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + } - function buildPlane( u, v, w, udir, vdir, width, height, depth, gridX, gridY, materialIndex ) { + return geometry2; - const segmentWidth = width / gridX; - const segmentHeight = height / gridY; + } - const widthHalf = width / 2; - const heightHalf = height / 2; - const depthHalf = depth / 2; + /** + * Serializes the geometry into JSON. + * + * @return {Object} A JSON object representing the serialized geometry. + */ + toJSON() { - const gridX1 = gridX + 1; - const gridY1 = gridY + 1; + const data = { + metadata: { + version: 4.7, + type: 'BufferGeometry', + generator: 'BufferGeometry.toJSON' + } + }; - let vertexCounter = 0; - let groupCount = 0; + // standard BufferGeometry serialization - const vector = new Vector3(); + data.uuid = this.uuid; + data.type = this.type; + if ( this.name !== '' ) data.name = this.name; + if ( Object.keys( this.userData ).length > 0 ) data.userData = this.userData; - // generate vertices, normals and uvs + if ( this.parameters !== undefined ) { - for ( let iy = 0; iy < gridY1; iy ++ ) { + const parameters = this.parameters; - const y = iy * segmentHeight - heightHalf; + for ( const key in parameters ) { - for ( let ix = 0; ix < gridX1; ix ++ ) { + if ( parameters[ key ] !== undefined ) data[ key ] = parameters[ key ]; - const x = ix * segmentWidth - widthHalf; + } - // set values to correct vector component + return data; - vector[ u ] = x * udir; - vector[ v ] = y * vdir; - vector[ w ] = depthHalf; + } - // now apply vector to vertex buffer + // for simplicity the code assumes attributes are not shared across geometries, see #15811 - vertices.push( vector.x, vector.y, vector.z ); + data.data = { attributes: {} }; - // set values to correct vector component + const index = this.index; - vector[ u ] = 0; - vector[ v ] = 0; - vector[ w ] = depth > 0 ? 1 : - 1; + if ( index !== null ) { - // now apply vector to normal buffer + data.data.index = { + type: index.array.constructor.name, + array: Array.prototype.slice.call( index.array ) + }; - normals.push( vector.x, vector.y, vector.z ); + } - // uvs + const attributes = this.attributes; - uvs.push( ix / gridX ); - uvs.push( 1 - ( iy / gridY ) ); + for ( const key in attributes ) { - // counters + const attribute = attributes[ key ]; - vertexCounter += 1; + data.data.attributes[ key ] = attribute.toJSON( data.data ); - } + } - } + const morphAttributes = {}; + let hasMorphAttributes = false; - // indices + for ( const key in this.morphAttributes ) { - // 1. you need three indices to draw a single face - // 2. a single segment consists of two faces - // 3. so we need to generate six (2*3) indices per segment + const attributeArray = this.morphAttributes[ key ]; - for ( let iy = 0; iy < gridY; iy ++ ) { + const array = []; - for ( let ix = 0; ix < gridX; ix ++ ) { + for ( let i = 0, il = attributeArray.length; i < il; i ++ ) { - const a = numberOfVertices + ix + gridX1 * iy; - const b = numberOfVertices + ix + gridX1 * ( iy + 1 ); - const c = numberOfVertices + ( ix + 1 ) + gridX1 * ( iy + 1 ); - const d = numberOfVertices + ( ix + 1 ) + gridX1 * iy; + const attribute = attributeArray[ i ]; - // faces + array.push( attribute.toJSON( data.data ) ); - indices.push( a, b, d ); - indices.push( b, c, d ); + } - // increase counter + if ( array.length > 0 ) { - groupCount += 6; + morphAttributes[ key ] = array; - } + hasMorphAttributes = true; } - // add a group to the geometry. this will ensure multi material support + } - scope.addGroup( groupStart, groupCount, materialIndex ); + if ( hasMorphAttributes ) { - // calculate new start value for groups + data.data.morphAttributes = morphAttributes; + data.data.morphTargetsRelative = this.morphTargetsRelative; - groupStart += groupCount; + } - // update total number of vertices + const groups = this.groups; - numberOfVertices += vertexCounter; + if ( groups.length > 0 ) { + + data.data.groups = JSON.parse( JSON.stringify( groups ) ); } - } + const boundingSphere = this.boundingSphere; - copy( source ) { + if ( boundingSphere !== null ) { - super.copy( source ); + data.data.boundingSphere = boundingSphere.toJSON(); - this.parameters = Object.assign( {}, source.parameters ); + } - return this; + return data; } - static fromJSON( data ) { + /** + * Returns a new geometry with copied values from this instance. + * + * @return {BufferGeometry} A clone of this instance. + */ + clone() { - return new BoxGeometry( data.width, data.height, data.depth, data.widthSegments, data.heightSegments, data.depthSegments ); + return new this.constructor().copy( this ); } -} - -/** - * Uniform Utilities - */ - -function cloneUniforms( src ) { - - const dst = {}; + /** + * Copies the values of the given geometry to this instance. + * + * @param {BufferGeometry} source - The geometry to copy. + * @return {BufferGeometry} A reference to this instance. + */ + copy( source ) { - for ( const u in src ) { + // reset - dst[ u ] = {}; + this.index = null; + this.attributes = {}; + this.morphAttributes = {}; + this.groups = []; + this.boundingBox = null; + this.boundingSphere = null; - for ( const p in src[ u ] ) { + // used for storing cloned, shared data - const property = src[ u ][ p ]; + const data = {}; - if ( property && ( property.isColor || - property.isMatrix3 || property.isMatrix4 || - property.isVector2 || property.isVector3 || property.isVector4 || - property.isTexture || property.isQuaternion ) ) { + // name - if ( property.isRenderTargetTexture ) { + this.name = source.name; - console.warn( 'UniformsUtils: Textures of render targets cannot be cloned via cloneUniforms() or mergeUniforms().' ); - dst[ u ][ p ] = null; + // index - } else { + const index = source.index; - dst[ u ][ p ] = property.clone(); + if ( index !== null ) { - } + this.setIndex( index.clone() ); - } else if ( Array.isArray( property ) ) { + } - dst[ u ][ p ] = property.slice(); + // attributes - } else { + const attributes = source.attributes; - dst[ u ][ p ] = property; + for ( const name in attributes ) { - } + const attribute = attributes[ name ]; + this.setAttribute( name, attribute.clone( data ) ); } - } - - return dst; + // morph attributes -} + const morphAttributes = source.morphAttributes; -function mergeUniforms( uniforms ) { + for ( const name in morphAttributes ) { - const merged = {}; + const array = []; + const morphAttribute = morphAttributes[ name ]; // morphAttribute: array of Float32BufferAttributes - for ( let u = 0; u < uniforms.length; u ++ ) { + for ( let i = 0, l = morphAttribute.length; i < l; i ++ ) { - const tmp = cloneUniforms( uniforms[ u ] ); + array.push( morphAttribute[ i ].clone( data ) ); - for ( const p in tmp ) { + } - merged[ p ] = tmp[ p ]; + this.morphAttributes[ name ] = array; } - } + this.morphTargetsRelative = source.morphTargetsRelative; - return merged; + // groups -} + const groups = source.groups; -function cloneUniformsGroups( src ) { + for ( let i = 0, l = groups.length; i < l; i ++ ) { - const dst = []; + const group = groups[ i ]; + this.addGroup( group.start, group.count, group.materialIndex ); - for ( let u = 0; u < src.length; u ++ ) { + } - dst.push( src[ u ].clone() ); + // bounding box - } + const boundingBox = source.boundingBox; - return dst; + if ( boundingBox !== null ) { -} + this.boundingBox = boundingBox.clone(); -function getUnlitUniformColorSpace( renderer ) { + } - if ( renderer.getRenderTarget() === null ) { + // bounding sphere - // https://github.com/mrdoob/three.js/pull/23937#issuecomment-1111067398 - return renderer.outputColorSpace; + const boundingSphere = source.boundingSphere; - } + if ( boundingSphere !== null ) { - return LinearSRGBColorSpace; + this.boundingSphere = boundingSphere.clone(); -} + } -// Legacy + // draw range -const UniformsUtils = { clone: cloneUniforms, merge: mergeUniforms }; + this.drawRange.start = source.drawRange.start; + this.drawRange.count = source.drawRange.count; -var default_vertex = "void main() {\n\tgl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n}"; + // user data -var default_fragment = "void main() {\n\tgl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0 );\n}"; + this.userData = source.userData; -class ShaderMaterial extends Material { + return this; - constructor( parameters ) { + } - super(); + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + * + * @fires BufferGeometry#dispose + */ + dispose() { - this.isShaderMaterial = true; + this.dispatchEvent( { type: 'dispose' } ); - this.type = 'ShaderMaterial'; + } - this.defines = {}; - this.uniforms = {}; - this.uniformsGroups = []; +} - this.vertexShader = default_vertex; - this.fragmentShader = default_fragment; +const _inverseMatrix$3 = /*@__PURE__*/ new Matrix4(); +const _ray$3 = /*@__PURE__*/ new Ray(); +const _sphere$6 = /*@__PURE__*/ new Sphere(); +const _sphereHitAt = /*@__PURE__*/ new Vector3(); - this.linewidth = 1; +const _vA$1 = /*@__PURE__*/ new Vector3(); +const _vB$1 = /*@__PURE__*/ new Vector3(); +const _vC$1 = /*@__PURE__*/ new Vector3(); - this.wireframe = false; - this.wireframeLinewidth = 1; +const _tempA = /*@__PURE__*/ new Vector3(); +const _morphA = /*@__PURE__*/ new Vector3(); - this.fog = false; // set to use scene fog - this.lights = false; // set to use scene lights - this.clipping = false; // set to use user-defined clipping planes +const _intersectionPoint = /*@__PURE__*/ new Vector3(); +const _intersectionPointWorld = /*@__PURE__*/ new Vector3(); - this.forceSinglePass = true; +/** + * Class representing triangular polygon mesh based objects. + * + * ```js + * const geometry = new THREE.BoxGeometry( 1, 1, 1 ); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + * const mesh = new THREE.Mesh( geometry, material ); + * scene.add( mesh ); + * ``` + * + * @augments Object3D + */ +class Mesh extends Object3D { - this.extensions = { - derivatives: false, // set to use derivatives - fragDepth: false, // set to use fragment depth values - drawBuffers: false, // set to use draw buffers - shaderTextureLOD: false // set to use shader texture LOD - }; + /** + * Constructs a new mesh. + * + * @param {BufferGeometry} [geometry] - The mesh geometry. + * @param {Material|Array} [material] - The mesh material. + */ + constructor( geometry = new BufferGeometry(), material = new MeshBasicMaterial() ) { - // When rendered geometry doesn't include these attributes but the material does, - // use these default values in WebGL. This avoids errors when buffer data is missing. - this.defaultAttributeValues = { - 'color': [ 1, 1, 1 ], - 'uv': [ 0, 0 ], - 'uv1': [ 0, 0 ] - }; + super(); - this.index0AttributeName = undefined; - this.uniformsNeedUpdate = false; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMesh = true; - this.glslVersion = null; + this.type = 'Mesh'; - if ( parameters !== undefined ) { + /** + * The mesh geometry. + * + * @type {BufferGeometry} + */ + this.geometry = geometry; - this.setValues( parameters ); + /** + * The mesh material. + * + * @type {Material|Array} + * @default MeshBasicMaterial + */ + this.material = material; - } + /** + * A dictionary representing the morph targets in the geometry. The key is the + * morph targets name, the value its attribute index. This member is `undefined` + * by default and only set when morph targets are detected in the geometry. + * + * @type {Object|undefined} + * @default undefined + */ + this.morphTargetDictionary = undefined; + + /** + * An array of weights typically in the range `[0,1]` that specify how much of the morph + * is applied. This member is `undefined` by default and only set when morph targets are + * detected in the geometry. + * + * @type {Array|undefined} + * @default undefined + */ + this.morphTargetInfluences = undefined; + + /** + * The number of instances of this mesh. + * Can only be used with {@link WebGPURenderer}. + * + * @type {number} + * @default 1 + */ + this.count = 1; + + this.updateMorphTargets(); } - copy( source ) { + copy( source, recursive ) { - super.copy( source ); + super.copy( source, recursive ); - this.fragmentShader = source.fragmentShader; - this.vertexShader = source.vertexShader; + if ( source.morphTargetInfluences !== undefined ) { - this.uniforms = cloneUniforms( source.uniforms ); - this.uniformsGroups = cloneUniformsGroups( source.uniformsGroups ); + this.morphTargetInfluences = source.morphTargetInfluences.slice(); - this.defines = Object.assign( {}, source.defines ); + } - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; + if ( source.morphTargetDictionary !== undefined ) { - this.fog = source.fog; - this.lights = source.lights; - this.clipping = source.clipping; + this.morphTargetDictionary = Object.assign( {}, source.morphTargetDictionary ); - this.extensions = Object.assign( {}, source.extensions ); + } - this.glslVersion = source.glslVersion; + this.material = Array.isArray( source.material ) ? source.material.slice() : source.material; + this.geometry = source.geometry; return this; } - toJSON( meta ) { + /** + * Sets the values of {@link Mesh#morphTargetDictionary} and {@link Mesh#morphTargetInfluences} + * to make sure existing morph targets can influence this 3D object. + */ + updateMorphTargets() { - const data = super.toJSON( meta ); + const geometry = this.geometry; - data.glslVersion = this.glslVersion; - data.uniforms = {}; + const morphAttributes = geometry.morphAttributes; + const keys = Object.keys( morphAttributes ); - for ( const name in this.uniforms ) { + if ( keys.length > 0 ) { - const uniform = this.uniforms[ name ]; - const value = uniform.value; + const morphAttribute = morphAttributes[ keys[ 0 ] ]; - if ( value && value.isTexture ) { + if ( morphAttribute !== undefined ) { - data.uniforms[ name ] = { - type: 't', - value: value.toJSON( meta ).uuid - }; + this.morphTargetInfluences = []; + this.morphTargetDictionary = {}; - } else if ( value && value.isColor ) { + for ( let m = 0, ml = morphAttribute.length; m < ml; m ++ ) { - data.uniforms[ name ] = { - type: 'c', - value: value.getHex() - }; + const name = morphAttribute[ m ].name || String( m ); - } else if ( value && value.isVector2 ) { + this.morphTargetInfluences.push( 0 ); + this.morphTargetDictionary[ name ] = m; - data.uniforms[ name ] = { - type: 'v2', - value: value.toArray() - }; + } - } else if ( value && value.isVector3 ) { + } - data.uniforms[ name ] = { - type: 'v3', - value: value.toArray() - }; + } - } else if ( value && value.isVector4 ) { + } - data.uniforms[ name ] = { - type: 'v4', - value: value.toArray() - }; + /** + * Returns the local-space position of the vertex at the given index, taking into + * account the current animation state of both morph targets and skinning. + * + * @param {number} index - The vertex index. + * @param {Vector3} target - The target object that is used to store the method's result. + * @return {Vector3} The vertex position in local space. + */ + getVertexPosition( index, target ) { - } else if ( value && value.isMatrix3 ) { + const geometry = this.geometry; + const position = geometry.attributes.position; + const morphPosition = geometry.morphAttributes.position; + const morphTargetsRelative = geometry.morphTargetsRelative; - data.uniforms[ name ] = { - type: 'm3', - value: value.toArray() - }; + target.fromBufferAttribute( position, index ); - } else if ( value && value.isMatrix4 ) { + const morphInfluences = this.morphTargetInfluences; - data.uniforms[ name ] = { - type: 'm4', - value: value.toArray() - }; + if ( morphPosition && morphInfluences ) { - } else { + _morphA.set( 0, 0, 0 ); - data.uniforms[ name ] = { - value: value - }; + for ( let i = 0, il = morphPosition.length; i < il; i ++ ) { - // note: the array variants v2v, v3v, v4v, m4v and tv are not supported so far + const influence = morphInfluences[ i ]; + const morphAttribute = morphPosition[ i ]; - } + if ( influence === 0 ) continue; - } + _tempA.fromBufferAttribute( morphAttribute, index ); - if ( Object.keys( this.defines ).length > 0 ) data.defines = this.defines; + if ( morphTargetsRelative ) { - data.vertexShader = this.vertexShader; - data.fragmentShader = this.fragmentShader; + _morphA.addScaledVector( _tempA, influence ); - data.lights = this.lights; - data.clipping = this.clipping; + } else { - const extensions = {}; + _morphA.addScaledVector( _tempA.sub( target ), influence ); - for ( const key in this.extensions ) { + } - if ( this.extensions[ key ] === true ) extensions[ key ] = true; + } - } + target.add( _morphA ); - if ( Object.keys( extensions ).length > 0 ) data.extensions = extensions; + } - return data; + return target; } -} + /** + * Computes intersection points between a casted ray and this line. + * + * @param {Raycaster} raycaster - The raycaster. + * @param {Array} intersects - The target array that holds the intersection points. + */ + raycast( raycaster, intersects ) { -class Camera extends Object3D { + const geometry = this.geometry; + const material = this.material; + const matrixWorld = this.matrixWorld; - constructor() { + if ( material === undefined ) return; - super(); + // test with bounding sphere in world space - this.isCamera = true; + if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); - this.type = 'Camera'; + _sphere$6.copy( geometry.boundingSphere ); + _sphere$6.applyMatrix4( matrixWorld ); - this.matrixWorldInverse = new Matrix4(); + // check distance from ray origin to bounding sphere - this.projectionMatrix = new Matrix4(); - this.projectionMatrixInverse = new Matrix4(); + _ray$3.copy( raycaster.ray ).recast( raycaster.near ); - } + if ( _sphere$6.containsPoint( _ray$3.origin ) === false ) { - copy( source, recursive ) { + if ( _ray$3.intersectSphere( _sphere$6, _sphereHitAt ) === null ) return; - super.copy( source, recursive ); + if ( _ray$3.origin.distanceToSquared( _sphereHitAt ) > ( raycaster.far - raycaster.near ) ** 2 ) return; - this.matrixWorldInverse.copy( source.matrixWorldInverse ); + } - this.projectionMatrix.copy( source.projectionMatrix ); - this.projectionMatrixInverse.copy( source.projectionMatrixInverse ); + // convert ray to local space of mesh - return this; + _inverseMatrix$3.copy( matrixWorld ).invert(); + _ray$3.copy( raycaster.ray ).applyMatrix4( _inverseMatrix$3 ); - } + // test with bounding box in local space - getWorldDirection( target ) { + if ( geometry.boundingBox !== null ) { - this.updateWorldMatrix( true, false ); + if ( _ray$3.intersectsBox( geometry.boundingBox ) === false ) return; - const e = this.matrixWorld.elements; + } + + // test for intersections with geometry - return target.set( - e[ 8 ], - e[ 9 ], - e[ 10 ] ).normalize(); + this._computeIntersections( raycaster, intersects, _ray$3 ); } - updateMatrixWorld( force ) { + _computeIntersections( raycaster, intersects, rayLocalSpace ) { - super.updateMatrixWorld( force ); + let intersection; - this.matrixWorldInverse.copy( this.matrixWorld ).invert(); + const geometry = this.geometry; + const material = this.material; - } + const index = geometry.index; + const position = geometry.attributes.position; + const uv = geometry.attributes.uv; + const uv1 = geometry.attributes.uv1; + const normal = geometry.attributes.normal; + const groups = geometry.groups; + const drawRange = geometry.drawRange; - updateWorldMatrix( updateParents, updateChildren ) { + if ( index !== null ) { - super.updateWorldMatrix( updateParents, updateChildren ); + // indexed buffer geometry - this.matrixWorldInverse.copy( this.matrixWorld ).invert(); + if ( Array.isArray( material ) ) { - } + for ( let i = 0, il = groups.length; i < il; i ++ ) { - clone() { + const group = groups[ i ]; + const groupMaterial = material[ group.materialIndex ]; - return new this.constructor().copy( this ); + const start = Math.max( group.start, drawRange.start ); + const end = Math.min( index.count, Math.min( ( group.start + group.count ), ( drawRange.start + drawRange.count ) ) ); - } + for ( let j = start, jl = end; j < jl; j += 3 ) { -} + const a = index.getX( j ); + const b = index.getX( j + 1 ); + const c = index.getX( j + 2 ); -class PerspectiveCamera extends Camera { + intersection = checkGeometryIntersection( this, groupMaterial, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c ); - constructor( fov = 50, aspect = 1, near = 0.1, far = 2000 ) { + if ( intersection ) { - super(); + intersection.faceIndex = Math.floor( j / 3 ); // triangle number in indexed buffer semantics + intersection.face.materialIndex = group.materialIndex; + intersects.push( intersection ); - this.isPerspectiveCamera = true; + } - this.type = 'PerspectiveCamera'; + } - this.fov = fov; - this.zoom = 1; + } - this.near = near; - this.far = far; - this.focus = 10; + } else { - this.aspect = aspect; - this.view = null; + const start = Math.max( 0, drawRange.start ); + const end = Math.min( index.count, ( drawRange.start + drawRange.count ) ); - this.filmGauge = 35; // width of the film (default in millimeters) - this.filmOffset = 0; // horizontal film offset (same unit as gauge) + for ( let i = start, il = end; i < il; i += 3 ) { - this.updateProjectionMatrix(); + const a = index.getX( i ); + const b = index.getX( i + 1 ); + const c = index.getX( i + 2 ); - } + intersection = checkGeometryIntersection( this, material, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c ); - copy( source, recursive ) { + if ( intersection ) { - super.copy( source, recursive ); + intersection.faceIndex = Math.floor( i / 3 ); // triangle number in indexed buffer semantics + intersects.push( intersection ); - this.fov = source.fov; - this.zoom = source.zoom; + } - this.near = source.near; - this.far = source.far; - this.focus = source.focus; + } - this.aspect = source.aspect; - this.view = source.view === null ? null : Object.assign( {}, source.view ); + } - this.filmGauge = source.filmGauge; - this.filmOffset = source.filmOffset; + } else if ( position !== undefined ) { - return this; + // non-indexed buffer geometry - } + if ( Array.isArray( material ) ) { - /** - * Sets the FOV by focal length in respect to the current .filmGauge. - * - * The default film gauge is 35, so that the focal length can be specified for - * a 35mm (full frame) camera. - * - * Values for focal length and film gauge must have the same unit. - */ - setFocalLength( focalLength ) { + for ( let i = 0, il = groups.length; i < il; i ++ ) { - /** see {@link http://www.bobatkins.com/photography/technical/field_of_view.html} */ - const vExtentSlope = 0.5 * this.getFilmHeight() / focalLength; + const group = groups[ i ]; + const groupMaterial = material[ group.materialIndex ]; - this.fov = RAD2DEG * 2 * Math.atan( vExtentSlope ); - this.updateProjectionMatrix(); + const start = Math.max( group.start, drawRange.start ); + const end = Math.min( position.count, Math.min( ( group.start + group.count ), ( drawRange.start + drawRange.count ) ) ); - } + for ( let j = start, jl = end; j < jl; j += 3 ) { - /** - * Calculates the focal length from the current .fov and .filmGauge. - */ - getFocalLength() { + const a = j; + const b = j + 1; + const c = j + 2; - const vExtentSlope = Math.tan( DEG2RAD * 0.5 * this.fov ); + intersection = checkGeometryIntersection( this, groupMaterial, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c ); - return 0.5 * this.getFilmHeight() / vExtentSlope; + if ( intersection ) { - } + intersection.faceIndex = Math.floor( j / 3 ); // triangle number in non-indexed buffer semantics + intersection.face.materialIndex = group.materialIndex; + intersects.push( intersection ); - getEffectiveFOV() { + } - return RAD2DEG * 2 * Math.atan( - Math.tan( DEG2RAD * 0.5 * this.fov ) / this.zoom ); + } - } + } - getFilmWidth() { + } else { - // film not completely covered in portrait format (aspect < 1) - return this.filmGauge * Math.min( this.aspect, 1 ); + const start = Math.max( 0, drawRange.start ); + const end = Math.min( position.count, ( drawRange.start + drawRange.count ) ); - } + for ( let i = start, il = end; i < il; i += 3 ) { - getFilmHeight() { + const a = i; + const b = i + 1; + const c = i + 2; - // film not completely covered in landscape format (aspect > 1) - return this.filmGauge / Math.max( this.aspect, 1 ); + intersection = checkGeometryIntersection( this, material, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c ); - } + if ( intersection ) { - /** - * Sets an offset in a larger frustum. This is useful for multi-window or - * multi-monitor/multi-machine setups. - * - * For example, if you have 3x2 monitors and each monitor is 1920x1080 and - * the monitors are in grid like this - * - * +---+---+---+ - * | A | B | C | - * +---+---+---+ - * | D | E | F | - * +---+---+---+ - * - * then for each monitor you would call it like this - * - * const w = 1920; - * const h = 1080; - * const fullWidth = w * 3; - * const fullHeight = h * 2; - * - * --A-- - * camera.setViewOffset( fullWidth, fullHeight, w * 0, h * 0, w, h ); - * --B-- - * camera.setViewOffset( fullWidth, fullHeight, w * 1, h * 0, w, h ); - * --C-- - * camera.setViewOffset( fullWidth, fullHeight, w * 2, h * 0, w, h ); - * --D-- - * camera.setViewOffset( fullWidth, fullHeight, w * 0, h * 1, w, h ); - * --E-- - * camera.setViewOffset( fullWidth, fullHeight, w * 1, h * 1, w, h ); - * --F-- - * camera.setViewOffset( fullWidth, fullHeight, w * 2, h * 1, w, h ); - * - * Note there is no reason monitors have to be the same size or in a grid. - */ - setViewOffset( fullWidth, fullHeight, x, y, width, height ) { + intersection.faceIndex = Math.floor( i / 3 ); // triangle number in non-indexed buffer semantics + intersects.push( intersection ); - this.aspect = fullWidth / fullHeight; + } - if ( this.view === null ) { + } - this.view = { - enabled: true, - fullWidth: 1, - fullHeight: 1, - offsetX: 0, - offsetY: 0, - width: 1, - height: 1 - }; + } } - this.view.enabled = true; - this.view.fullWidth = fullWidth; - this.view.fullHeight = fullHeight; - this.view.offsetX = x; - this.view.offsetY = y; - this.view.width = width; - this.view.height = height; + } - this.updateProjectionMatrix(); +} - } +function checkIntersection$1( object, material, raycaster, ray, pA, pB, pC, point ) { - clearViewOffset() { + let intersect; - if ( this.view !== null ) { + if ( material.side === BackSide ) { - this.view.enabled = false; + intersect = ray.intersectTriangle( pC, pB, pA, true, point ); - } + } else { - this.updateProjectionMatrix(); + intersect = ray.intersectTriangle( pA, pB, pC, ( material.side === FrontSide ), point ); } - updateProjectionMatrix() { + if ( intersect === null ) return null; - const near = this.near; - let top = near * Math.tan( DEG2RAD * 0.5 * this.fov ) / this.zoom; - let height = 2 * top; - let width = this.aspect * height; - let left = - 0.5 * width; - const view = this.view; + _intersectionPointWorld.copy( point ); + _intersectionPointWorld.applyMatrix4( object.matrixWorld ); - if ( this.view !== null && this.view.enabled ) { + const distance = raycaster.ray.origin.distanceTo( _intersectionPointWorld ); - const fullWidth = view.fullWidth, - fullHeight = view.fullHeight; + if ( distance < raycaster.near || distance > raycaster.far ) return null; - left += view.offsetX * width / fullWidth; - top -= view.offsetY * height / fullHeight; - width *= view.width / fullWidth; - height *= view.height / fullHeight; + return { + distance: distance, + point: _intersectionPointWorld.clone(), + object: object + }; - } +} - const skew = this.filmOffset; - if ( skew !== 0 ) left += near * skew / this.getFilmWidth(); +function checkGeometryIntersection( object, material, raycaster, ray, uv, uv1, normal, a, b, c ) { - this.projectionMatrix.makePerspective( left, left + width, top, top - height, near, this.far ); + object.getVertexPosition( a, _vA$1 ); + object.getVertexPosition( b, _vB$1 ); + object.getVertexPosition( c, _vC$1 ); - this.projectionMatrixInverse.copy( this.projectionMatrix ).invert(); + const intersection = checkIntersection$1( object, material, raycaster, ray, _vA$1, _vB$1, _vC$1, _intersectionPoint ); - } + if ( intersection ) { - toJSON( meta ) { + const barycoord = new Vector3(); + Triangle.getBarycoord( _intersectionPoint, _vA$1, _vB$1, _vC$1, barycoord ); - const data = super.toJSON( meta ); + if ( uv ) { - data.object.fov = this.fov; - data.object.zoom = this.zoom; + intersection.uv = Triangle.getInterpolatedAttribute( uv, a, b, c, barycoord, new Vector2() ); - data.object.near = this.near; - data.object.far = this.far; - data.object.focus = this.focus; + } - data.object.aspect = this.aspect; + if ( uv1 ) { - if ( this.view !== null ) data.object.view = Object.assign( {}, this.view ); + intersection.uv1 = Triangle.getInterpolatedAttribute( uv1, a, b, c, barycoord, new Vector2() ); - data.object.filmGauge = this.filmGauge; - data.object.filmOffset = this.filmOffset; + } - return data; + if ( normal ) { - } + intersection.normal = Triangle.getInterpolatedAttribute( normal, a, b, c, barycoord, new Vector3() ); -} + if ( intersection.normal.dot( ray.direction ) > 0 ) { -const fov = - 90; // negative fov is not an error -const aspect = 1; + intersection.normal.multiplyScalar( -1 ); -class CubeCamera extends Object3D { + } - constructor( near, far, renderTarget ) { + } - super(); - - this.type = 'CubeCamera'; - - this.renderTarget = renderTarget; - - const cameraPX = new PerspectiveCamera( fov, aspect, near, far ); - cameraPX.layers = this.layers; - cameraPX.up.set( 0, 1, 0 ); - cameraPX.lookAt( 1, 0, 0 ); - this.add( cameraPX ); - - const cameraNX = new PerspectiveCamera( fov, aspect, near, far ); - cameraNX.layers = this.layers; - cameraNX.up.set( 0, 1, 0 ); - cameraNX.lookAt( - 1, 0, 0 ); - this.add( cameraNX ); - - const cameraPY = new PerspectiveCamera( fov, aspect, near, far ); - cameraPY.layers = this.layers; - cameraPY.up.set( 0, 0, - 1 ); - cameraPY.lookAt( 0, 1, 0 ); - this.add( cameraPY ); - - const cameraNY = new PerspectiveCamera( fov, aspect, near, far ); - cameraNY.layers = this.layers; - cameraNY.up.set( 0, 0, 1 ); - cameraNY.lookAt( 0, - 1, 0 ); - this.add( cameraNY ); + const face = { + a: a, + b: b, + c: c, + normal: new Vector3(), + materialIndex: 0 + }; - const cameraPZ = new PerspectiveCamera( fov, aspect, near, far ); - cameraPZ.layers = this.layers; - cameraPZ.up.set( 0, 1, 0 ); - cameraPZ.lookAt( 0, 0, 1 ); - this.add( cameraPZ ); + Triangle.getNormal( _vA$1, _vB$1, _vC$1, face.normal ); - const cameraNZ = new PerspectiveCamera( fov, aspect, near, far ); - cameraNZ.layers = this.layers; - cameraNZ.up.set( 0, 1, 0 ); - cameraNZ.lookAt( 0, 0, - 1 ); - this.add( cameraNZ ); + intersection.face = face; + intersection.barycoord = barycoord; } - update( renderer, scene ) { - - if ( this.parent === null ) this.updateMatrixWorld(); - - const renderTarget = this.renderTarget; + return intersection; - const [ cameraPX, cameraNX, cameraPY, cameraNY, cameraPZ, cameraNZ ] = this.children; +} - const currentRenderTarget = renderer.getRenderTarget(); +/** + * A geometry class for a rectangular cuboid with a given width, height, and depth. + * On creation, the cuboid is centred on the origin, with each edge parallel to one + * of the axes. + * + * ```js + * const geometry = new THREE.BoxGeometry( 1, 1, 1 ); + * const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); + * const cube = new THREE.Mesh( geometry, material ); + * scene.add( cube ); + * ``` + * + * @augments BufferGeometry + */ +class BoxGeometry extends BufferGeometry { - const currentToneMapping = renderer.toneMapping; - const currentXrEnabled = renderer.xr.enabled; + /** + * Constructs a new box geometry. + * + * @param {number} [width=1] - The width. That is, the length of the edges parallel to the X axis. + * @param {number} [height=1] - The height. That is, the length of the edges parallel to the Y axis. + * @param {number} [depth=1] - The depth. That is, the length of the edges parallel to the Z axis. + * @param {number} [widthSegments=1] - Number of segmented rectangular faces along the width of the sides. + * @param {number} [heightSegments=1] - Number of segmented rectangular faces along the height of the sides. + * @param {number} [depthSegments=1] - Number of segmented rectangular faces along the depth of the sides. + */ + constructor( width = 1, height = 1, depth = 1, widthSegments = 1, heightSegments = 1, depthSegments = 1 ) { - renderer.toneMapping = NoToneMapping; - renderer.xr.enabled = false; + super(); - const generateMipmaps = renderTarget.texture.generateMipmaps; + this.type = 'BoxGeometry'; - renderTarget.texture.generateMipmaps = false; + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + width: width, + height: height, + depth: depth, + widthSegments: widthSegments, + heightSegments: heightSegments, + depthSegments: depthSegments + }; - renderer.setRenderTarget( renderTarget, 0 ); - renderer.render( scene, cameraPX ); + const scope = this; - renderer.setRenderTarget( renderTarget, 1 ); - renderer.render( scene, cameraNX ); + // segments - renderer.setRenderTarget( renderTarget, 2 ); - renderer.render( scene, cameraPY ); + widthSegments = Math.floor( widthSegments ); + heightSegments = Math.floor( heightSegments ); + depthSegments = Math.floor( depthSegments ); - renderer.setRenderTarget( renderTarget, 3 ); - renderer.render( scene, cameraNY ); + // buffers - renderer.setRenderTarget( renderTarget, 4 ); - renderer.render( scene, cameraPZ ); + const indices = []; + const vertices = []; + const normals = []; + const uvs = []; - renderTarget.texture.generateMipmaps = generateMipmaps; + // helper variables - renderer.setRenderTarget( renderTarget, 5 ); - renderer.render( scene, cameraNZ ); + let numberOfVertices = 0; + let groupStart = 0; - renderer.setRenderTarget( currentRenderTarget ); + // build each side of the box geometry - renderer.toneMapping = currentToneMapping; - renderer.xr.enabled = currentXrEnabled; + buildPlane( 'z', 'y', 'x', -1, -1, depth, height, width, depthSegments, heightSegments, 0 ); // px + buildPlane( 'z', 'y', 'x', 1, -1, depth, height, - width, depthSegments, heightSegments, 1 ); // nx + buildPlane( 'x', 'z', 'y', 1, 1, width, depth, height, widthSegments, depthSegments, 2 ); // py + buildPlane( 'x', 'z', 'y', 1, -1, width, depth, - height, widthSegments, depthSegments, 3 ); // ny + buildPlane( 'x', 'y', 'z', 1, -1, width, height, depth, widthSegments, heightSegments, 4 ); // pz + buildPlane( 'x', 'y', 'z', -1, -1, width, height, - depth, widthSegments, heightSegments, 5 ); // nz - renderTarget.texture.needsPMREMUpdate = true; + // build geometry - } + this.setIndex( indices ); + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); -} + function buildPlane( u, v, w, udir, vdir, width, height, depth, gridX, gridY, materialIndex ) { -class CubeTexture extends Texture { + const segmentWidth = width / gridX; + const segmentHeight = height / gridY; - constructor( images, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, colorSpace ) { + const widthHalf = width / 2; + const heightHalf = height / 2; + const depthHalf = depth / 2; - images = images !== undefined ? images : []; - mapping = mapping !== undefined ? mapping : CubeReflectionMapping; + const gridX1 = gridX + 1; + const gridY1 = gridY + 1; - super( images, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, colorSpace ); + let vertexCounter = 0; + let groupCount = 0; - this.isCubeTexture = true; + const vector = new Vector3(); - this.flipY = false; + // generate vertices, normals and uvs - } + for ( let iy = 0; iy < gridY1; iy ++ ) { - get images() { + const y = iy * segmentHeight - heightHalf; - return this.image; + for ( let ix = 0; ix < gridX1; ix ++ ) { - } + const x = ix * segmentWidth - widthHalf; - set images( value ) { + // set values to correct vector component - this.image = value; + vector[ u ] = x * udir; + vector[ v ] = y * vdir; + vector[ w ] = depthHalf; - } + // now apply vector to vertex buffer -} + vertices.push( vector.x, vector.y, vector.z ); -class WebGLCubeRenderTarget extends WebGLRenderTarget { + // set values to correct vector component - constructor( size = 1, options = {} ) { + vector[ u ] = 0; + vector[ v ] = 0; + vector[ w ] = depth > 0 ? 1 : -1; - super( size, size, options ); + // now apply vector to normal buffer - this.isWebGLCubeRenderTarget = true; + normals.push( vector.x, vector.y, vector.z ); - const image = { width: size, height: size, depth: 1 }; - const images = [ image, image, image, image, image, image ]; + // uvs - if ( options.encoding !== undefined ) { + uvs.push( ix / gridX ); + uvs.push( 1 - ( iy / gridY ) ); - // @deprecated, r152 - warnOnce( 'THREE.WebGLCubeRenderTarget: option.encoding has been replaced by option.colorSpace.' ); - options.colorSpace = options.encoding === sRGBEncoding ? SRGBColorSpace : NoColorSpace; + // counters - } + vertexCounter += 1; - this.texture = new CubeTexture( images, options.mapping, options.wrapS, options.wrapT, options.magFilter, options.minFilter, options.format, options.type, options.anisotropy, options.colorSpace ); + } - // By convention -- likely based on the RenderMan spec from the 1990's -- cube maps are specified by WebGL (and three.js) - // in a coordinate system in which positive-x is to the right when looking up the positive-z axis -- in other words, - // in a left-handed coordinate system. By continuing this convention, preexisting cube maps continued to render correctly. + } - // three.js uses a right-handed coordinate system. So environment maps used in three.js appear to have px and nx swapped - // and the flag isRenderTargetTexture controls this conversion. The flip is not required when using WebGLCubeRenderTarget.texture - // as a cube texture (this is detected when isRenderTargetTexture is set to true for cube textures). + // indices - this.texture.isRenderTargetTexture = true; + // 1. you need three indices to draw a single face + // 2. a single segment consists of two faces + // 3. so we need to generate six (2*3) indices per segment - this.texture.generateMipmaps = options.generateMipmaps !== undefined ? options.generateMipmaps : false; - this.texture.minFilter = options.minFilter !== undefined ? options.minFilter : LinearFilter; + for ( let iy = 0; iy < gridY; iy ++ ) { - } + for ( let ix = 0; ix < gridX; ix ++ ) { - fromEquirectangularTexture( renderer, texture ) { + const a = numberOfVertices + ix + gridX1 * iy; + const b = numberOfVertices + ix + gridX1 * ( iy + 1 ); + const c = numberOfVertices + ( ix + 1 ) + gridX1 * ( iy + 1 ); + const d = numberOfVertices + ( ix + 1 ) + gridX1 * iy; - this.texture.type = texture.type; - this.texture.colorSpace = texture.colorSpace; + // faces - this.texture.generateMipmaps = texture.generateMipmaps; - this.texture.minFilter = texture.minFilter; - this.texture.magFilter = texture.magFilter; + indices.push( a, b, d ); + indices.push( b, c, d ); - const shader = { + // increase counter - uniforms: { - tEquirect: { value: null }, - }, + groupCount += 6; - vertexShader: /* glsl */` + } - varying vec3 vWorldDirection; + } - vec3 transformDirection( in vec3 dir, in mat4 matrix ) { + // add a group to the geometry. this will ensure multi material support - return normalize( ( matrix * vec4( dir, 0.0 ) ).xyz ); + scope.addGroup( groupStart, groupCount, materialIndex ); - } + // calculate new start value for groups - void main() { + groupStart += groupCount; - vWorldDirection = transformDirection( position, modelMatrix ); + // update total number of vertices - #include - #include + numberOfVertices += vertexCounter; - } - `, + } - fragmentShader: /* glsl */` + } - uniform sampler2D tEquirect; + copy( source ) { - varying vec3 vWorldDirection; + super.copy( source ); - #include + this.parameters = Object.assign( {}, source.parameters ); - void main() { + return this; - vec3 direction = normalize( vWorldDirection ); + } - vec2 sampleUV = equirectUv( direction ); + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {BoxGeometry} A new instance. + */ + static fromJSON( data ) { - gl_FragColor = texture2D( tEquirect, sampleUV ); + return new BoxGeometry( data.width, data.height, data.depth, data.widthSegments, data.heightSegments, data.depthSegments ); - } - ` - }; + } - const geometry = new BoxGeometry( 5, 5, 5 ); +} - const material = new ShaderMaterial( { +// Uniform Utilities - name: 'CubemapFromEquirect', +function cloneUniforms( src ) { - uniforms: cloneUniforms( shader.uniforms ), - vertexShader: shader.vertexShader, - fragmentShader: shader.fragmentShader, - side: BackSide, - blending: NoBlending + const dst = {}; - } ); + for ( const u in src ) { - material.uniforms.tEquirect.value = texture; + dst[ u ] = {}; - const mesh = new Mesh( geometry, material ); + for ( const p in src[ u ] ) { - const currentMinFilter = texture.minFilter; + const property = src[ u ][ p ]; - // Avoid blurred poles - if ( texture.minFilter === LinearMipmapLinearFilter ) texture.minFilter = LinearFilter; + if ( property && ( property.isColor || + property.isMatrix3 || property.isMatrix4 || + property.isVector2 || property.isVector3 || property.isVector4 || + property.isTexture || property.isQuaternion ) ) { - const camera = new CubeCamera( 1, 10, this ); - camera.update( renderer, mesh ); + if ( property.isRenderTargetTexture ) { - texture.minFilter = currentMinFilter; + console.warn( 'UniformsUtils: Textures of render targets cannot be cloned via cloneUniforms() or mergeUniforms().' ); + dst[ u ][ p ] = null; - mesh.geometry.dispose(); - mesh.material.dispose(); + } else { - return this; + dst[ u ][ p ] = property.clone(); - } + } - clear( renderer, color, depth, stencil ) { + } else if ( Array.isArray( property ) ) { - const currentRenderTarget = renderer.getRenderTarget(); + dst[ u ][ p ] = property.slice(); - for ( let i = 0; i < 6; i ++ ) { + } else { - renderer.setRenderTarget( this, i ); + dst[ u ][ p ] = property; - renderer.clear( color, depth, stencil ); + } } - renderer.setRenderTarget( currentRenderTarget ); - } -} - -const _vector1 = /*@__PURE__*/ new Vector3(); -const _vector2 = /*@__PURE__*/ new Vector3(); -const _normalMatrix = /*@__PURE__*/ new Matrix3(); - -class Plane { + return dst; - constructor( normal = new Vector3( 1, 0, 0 ), constant = 0 ) { +} - this.isPlane = true; +function mergeUniforms( uniforms ) { - // normal is assumed to be normalized + const merged = {}; - this.normal = normal; - this.constant = constant; + for ( let u = 0; u < uniforms.length; u ++ ) { - } + const tmp = cloneUniforms( uniforms[ u ] ); - set( normal, constant ) { + for ( const p in tmp ) { - this.normal.copy( normal ); - this.constant = constant; + merged[ p ] = tmp[ p ]; - return this; + } } - setComponents( x, y, z, w ) { - - this.normal.set( x, y, z ); - this.constant = w; + return merged; - return this; +} - } +function cloneUniformsGroups( src ) { - setFromNormalAndCoplanarPoint( normal, point ) { + const dst = []; - this.normal.copy( normal ); - this.constant = - point.dot( this.normal ); + for ( let u = 0; u < src.length; u ++ ) { - return this; + dst.push( src[ u ].clone() ); } - setFromCoplanarPoints( a, b, c ) { + return dst; - const normal = _vector1.subVectors( c, b ).cross( _vector2.subVectors( a, b ) ).normalize(); +} - // Q: should an error be thrown if normal is zero (e.g. degenerate plane)? +function getUnlitUniformColorSpace( renderer ) { - this.setFromNormalAndCoplanarPoint( normal, a ); + const currentRenderTarget = renderer.getRenderTarget(); - return this; + if ( currentRenderTarget === null ) { - } + // https://github.com/mrdoob/three.js/pull/23937#issuecomment-1111067398 + return renderer.outputColorSpace; - copy( plane ) { + } - this.normal.copy( plane.normal ); - this.constant = plane.constant; + // https://github.com/mrdoob/three.js/issues/27868 + if ( currentRenderTarget.isXRRenderTarget === true ) { - return this; + return currentRenderTarget.texture.colorSpace; } - normalize() { + return ColorManagement.workingColorSpace; - // Note: will lead to a divide by zero if the plane is invalid. +} - const inverseNormalLength = 1.0 / this.normal.length(); - this.normal.multiplyScalar( inverseNormalLength ); - this.constant *= inverseNormalLength; +// Legacy - return this; +const UniformsUtils = { clone: cloneUniforms, merge: mergeUniforms }; - } +var default_vertex = "void main() {\n\tgl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n}"; - negate() { +var default_fragment = "void main() {\n\tgl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0 );\n}"; - this.constant *= - 1; - this.normal.negate(); +/** + * A material rendered with custom shaders. A shader is a small program written in GLSL. + * that runs on the GPU. You may want to use a custom shader if you need to implement an + * effect not included with any of the built-in materials. + * + * There are the following notes to bear in mind when using a `ShaderMaterial`: + * + * - `ShaderMaterial` can only be used with {@link WebGLRenderer}. + * - Built in attributes and uniforms are passed to the shaders along with your code. If + * you don't want that, use {@link RawShaderMaterial} instead. + * - You can use the directive `#pragma unroll_loop_start` and `#pragma unroll_loop_end` + * in order to unroll a `for` loop in GLSL by the shader preprocessor. The directive has + * to be placed right above the loop. The loop formatting has to correspond to a defined standard. + * - The loop has to be [normalized]{@link https://en.wikipedia.org/wiki/Normalized_loop}. + * - The loop variable has to be *i*. + * - The value `UNROLLED_LOOP_INDEX` will be replaced with the explicitly + * value of *i* for the given iteration and can be used in preprocessor + * statements. + * + * ```js + * const material = new THREE.ShaderMaterial( { + * uniforms: { + * time: { value: 1.0 }, + * resolution: { value: new THREE.Vector2() } + * }, + * vertexShader: document.getElementById( 'vertexShader' ).textContent, + * fragmentShader: document.getElementById( 'fragmentShader' ).textContent + * } ); + * ``` + * + * @augments Material + */ +class ShaderMaterial extends Material { - return this; + /** + * Constructs a new shader material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { - } + super(); - distanceToPoint( point ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isShaderMaterial = true; - return this.normal.dot( point ) + this.constant; + this.type = 'ShaderMaterial'; - } + /** + * Defines custom constants using `#define` directives within the GLSL code + * for both the vertex shader and the fragment shader; each key/value pair + * yields another directive. + * ```js + * defines: { + * FOO: 15, + * BAR: true + * } + * ``` + * Yields the lines: + * ``` + * #define FOO 15 + * #define BAR true + * ``` + * + * @type {Object} + */ + this.defines = {}; - distanceToSphere( sphere ) { + /** + * An object of the form: + * ```js + * { + * "uniform1": { value: 1.0 }, + * "uniform2": { value: 2 } + * } + * ``` + * specifying the uniforms to be passed to the shader code; keys are uniform + * names, values are definitions of the form + * ``` + * { + * value: 1.0 + * } + * ``` + * where `value` is the value of the uniform. Names must match the name of + * the uniform, as defined in the GLSL code. Note that uniforms are refreshed + * on every frame, so updating the value of the uniform will immediately + * update the value available to the GLSL code. + * + * @type {Object} + */ + this.uniforms = {}; - return this.distanceToPoint( sphere.center ) - sphere.radius; + /** + * An array holding uniforms groups for configuring UBOs. + * + * @type {Array} + */ + this.uniformsGroups = []; - } + /** + * Vertex shader GLSL code. This is the actual code for the shader. + * + * @type {string} + */ + this.vertexShader = default_vertex; - projectPoint( point, target ) { + /** + * Fragment shader GLSL code. This is the actual code for the shader. + * + * @type {string} + */ + this.fragmentShader = default_fragment; - return target.copy( point ).addScaledVector( this.normal, - this.distanceToPoint( point ) ); + /** + * Controls line thickness or lines. + * + * WebGL and WebGPU ignore this setting and always render line primitives with a + * width of one pixel. + * + * @type {number} + * @default 1 + */ + this.linewidth = 1; - } + /** + * Renders the geometry as a wireframe. + * + * @type {boolean} + * @default false + */ + this.wireframe = false; - intersectLine( line, target ) { + /** + * Controls the thickness of the wireframe. + * + * WebGL and WebGPU ignore this property and always render + * 1 pixel wide lines. + * + * @type {number} + * @default 1 + */ + this.wireframeLinewidth = 1; - const direction = line.delta( _vector1 ); + /** + * Define whether the material color is affected by global fog settings; `true` + * to pass fog uniforms to the shader. + * + * @type {boolean} + * @default false + */ + this.fog = false; - const denominator = this.normal.dot( direction ); + /** + * Defines whether this material uses lighting; `true` to pass uniform data + * related to lighting to this shader. + * + * @type {boolean} + * @default false + */ + this.lights = false; - if ( denominator === 0 ) { + /** + * Defines whether this material supports clipping; `true` to let the renderer + * pass the clippingPlanes uniform. + * + * @type {boolean} + * @default false + */ + this.clipping = false; - // line is coplanar, return origin - if ( this.distanceToPoint( line.start ) === 0 ) { + /** + * Overwritten and set to `true` by default. + * + * @type {boolean} + * @default true + */ + this.forceSinglePass = true; - return target.copy( line.start ); + /** + * This object allows to enable certain WebGL 2 extensions. + * + * - clipCullDistance: set to `true` to use vertex shader clipping + * - multiDraw: set to `true` to use vertex shader multi_draw / enable gl_DrawID + * + * @type {{clipCullDistance:false,multiDraw:false}} + */ + this.extensions = { + clipCullDistance: false, // set to use vertex shader clipping + multiDraw: false // set to use vertex shader multi_draw / enable gl_DrawID + }; - } + /** + * When the rendered geometry doesn't include these attributes but the + * material does, these default values will be passed to the shaders. This + * avoids errors when buffer data is missing. + * + * - color: [ 1, 1, 1 ] + * - uv: [ 0, 0 ] + * - uv1: [ 0, 0 ] + * + * @type {Object} + */ + this.defaultAttributeValues = { + 'color': [ 1, 1, 1 ], + 'uv': [ 0, 0 ], + 'uv1': [ 0, 0 ] + }; - // Unsure if this is the correct method to handle this case. - return null; + /** + * If set, this calls [gl.bindAttribLocation]{@link https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/bindAttribLocation} + * to bind a generic vertex index to an attribute variable. + * + * @type {string|undefined} + * @default undefined + */ + this.index0AttributeName = undefined; - } + /** + * Can be used to force a uniform update while changing uniforms in + * {@link Object3D#onBeforeRender}. + * + * @type {boolean} + * @default false + */ + this.uniformsNeedUpdate = false; - const t = - ( line.start.dot( this.normal ) + this.constant ) / denominator; + /** + * Defines the GLSL version of custom shader code. + * + * @type {?(GLSL1|GLSL3)} + * @default null + */ + this.glslVersion = null; - if ( t < 0 || t > 1 ) { + if ( parameters !== undefined ) { - return null; + this.setValues( parameters ); } - return target.copy( line.start ).addScaledVector( direction, t ); - } - intersectsLine( line ) { + copy( source ) { - // Note: this tests if a line intersects the plane, not whether it (or its end-points) are coplanar with it. + super.copy( source ); - const startSign = this.distanceToPoint( line.start ); - const endSign = this.distanceToPoint( line.end ); + this.fragmentShader = source.fragmentShader; + this.vertexShader = source.vertexShader; - return ( startSign < 0 && endSign > 0 ) || ( endSign < 0 && startSign > 0 ); + this.uniforms = cloneUniforms( source.uniforms ); + this.uniformsGroups = cloneUniformsGroups( source.uniformsGroups ); - } + this.defines = Object.assign( {}, source.defines ); - intersectsBox( box ) { + this.wireframe = source.wireframe; + this.wireframeLinewidth = source.wireframeLinewidth; - return box.intersectsPlane( this ); + this.fog = source.fog; + this.lights = source.lights; + this.clipping = source.clipping; - } + this.extensions = Object.assign( {}, source.extensions ); - intersectsSphere( sphere ) { + this.glslVersion = source.glslVersion; - return sphere.intersectsPlane( this ); + return this; } - coplanarPoint( target ) { - - return target.copy( this.normal ).multiplyScalar( - this.constant ); + toJSON( meta ) { - } + const data = super.toJSON( meta ); - applyMatrix4( matrix, optionalNormalMatrix ) { + data.glslVersion = this.glslVersion; + data.uniforms = {}; - const normalMatrix = optionalNormalMatrix || _normalMatrix.getNormalMatrix( matrix ); + for ( const name in this.uniforms ) { - const referencePoint = this.coplanarPoint( _vector1 ).applyMatrix4( matrix ); + const uniform = this.uniforms[ name ]; + const value = uniform.value; - const normal = this.normal.applyMatrix3( normalMatrix ).normalize(); + if ( value && value.isTexture ) { - this.constant = - referencePoint.dot( normal ); + data.uniforms[ name ] = { + type: 't', + value: value.toJSON( meta ).uuid + }; - return this; + } else if ( value && value.isColor ) { - } + data.uniforms[ name ] = { + type: 'c', + value: value.getHex() + }; - translate( offset ) { + } else if ( value && value.isVector2 ) { - this.constant -= offset.dot( this.normal ); + data.uniforms[ name ] = { + type: 'v2', + value: value.toArray() + }; - return this; + } else if ( value && value.isVector3 ) { - } - - equals( plane ) { + data.uniforms[ name ] = { + type: 'v3', + value: value.toArray() + }; - return plane.normal.equals( this.normal ) && ( plane.constant === this.constant ); + } else if ( value && value.isVector4 ) { - } + data.uniforms[ name ] = { + type: 'v4', + value: value.toArray() + }; - clone() { + } else if ( value && value.isMatrix3 ) { - return new this.constructor().copy( this ); + data.uniforms[ name ] = { + type: 'm3', + value: value.toArray() + }; - } + } else if ( value && value.isMatrix4 ) { -} + data.uniforms[ name ] = { + type: 'm4', + value: value.toArray() + }; -const _sphere$4 = /*@__PURE__*/ new Sphere(); -const _vector$6 = /*@__PURE__*/ new Vector3(); + } else { -class Frustum { + data.uniforms[ name ] = { + value: value + }; - constructor( p0 = new Plane(), p1 = new Plane(), p2 = new Plane(), p3 = new Plane(), p4 = new Plane(), p5 = new Plane() ) { + // note: the array variants v2v, v3v, v4v, m4v and tv are not supported so far - this.planes = [ p0, p1, p2, p3, p4, p5 ]; + } - } + } - set( p0, p1, p2, p3, p4, p5 ) { + if ( Object.keys( this.defines ).length > 0 ) data.defines = this.defines; - const planes = this.planes; + data.vertexShader = this.vertexShader; + data.fragmentShader = this.fragmentShader; - planes[ 0 ].copy( p0 ); - planes[ 1 ].copy( p1 ); - planes[ 2 ].copy( p2 ); - planes[ 3 ].copy( p3 ); - planes[ 4 ].copy( p4 ); - planes[ 5 ].copy( p5 ); + data.lights = this.lights; + data.clipping = this.clipping; - return this; + const extensions = {}; - } + for ( const key in this.extensions ) { - copy( frustum ) { + if ( this.extensions[ key ] === true ) extensions[ key ] = true; - const planes = this.planes; + } - for ( let i = 0; i < 6; i ++ ) { + if ( Object.keys( extensions ).length > 0 ) data.extensions = extensions; - planes[ i ].copy( frustum.planes[ i ] ); + return data; - } + } - return this; +} - } +/** + * Abstract base class for cameras. This class should always be inherited + * when you build a new camera. + * + * @abstract + * @augments Object3D + */ +class Camera extends Object3D { - setFromProjectionMatrix( m ) { + /** + * Constructs a new camera. + */ + constructor() { - const planes = this.planes; - const me = m.elements; - const me0 = me[ 0 ], me1 = me[ 1 ], me2 = me[ 2 ], me3 = me[ 3 ]; - const me4 = me[ 4 ], me5 = me[ 5 ], me6 = me[ 6 ], me7 = me[ 7 ]; - const me8 = me[ 8 ], me9 = me[ 9 ], me10 = me[ 10 ], me11 = me[ 11 ]; - const me12 = me[ 12 ], me13 = me[ 13 ], me14 = me[ 14 ], me15 = me[ 15 ]; + super(); - planes[ 0 ].setComponents( me3 - me0, me7 - me4, me11 - me8, me15 - me12 ).normalize(); - planes[ 1 ].setComponents( me3 + me0, me7 + me4, me11 + me8, me15 + me12 ).normalize(); - planes[ 2 ].setComponents( me3 + me1, me7 + me5, me11 + me9, me15 + me13 ).normalize(); - planes[ 3 ].setComponents( me3 - me1, me7 - me5, me11 - me9, me15 - me13 ).normalize(); - planes[ 4 ].setComponents( me3 - me2, me7 - me6, me11 - me10, me15 - me14 ).normalize(); - planes[ 5 ].setComponents( me3 + me2, me7 + me6, me11 + me10, me15 + me14 ).normalize(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isCamera = true; - return this; + this.type = 'Camera'; - } + /** + * The inverse of the camera's world matrix. + * + * @type {Matrix4} + */ + this.matrixWorldInverse = new Matrix4(); - intersectsObject( object ) { + /** + * The camera's projection matrix. + * + * @type {Matrix4} + */ + this.projectionMatrix = new Matrix4(); - if ( object.boundingSphere !== undefined ) { + /** + * The inverse of the camera's projection matrix. + * + * @type {Matrix4} + */ + this.projectionMatrixInverse = new Matrix4(); - if ( object.boundingSphere === null ) object.computeBoundingSphere(); + /** + * The coordinate system in which the camera is used. + * + * @type {(WebGLCoordinateSystem|WebGPUCoordinateSystem)} + */ + this.coordinateSystem = WebGLCoordinateSystem; - _sphere$4.copy( object.boundingSphere ).applyMatrix4( object.matrixWorld ); + } - } else { + copy( source, recursive ) { - const geometry = object.geometry; + super.copy( source, recursive ); - if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); + this.matrixWorldInverse.copy( source.matrixWorldInverse ); - _sphere$4.copy( geometry.boundingSphere ).applyMatrix4( object.matrixWorld ); + this.projectionMatrix.copy( source.projectionMatrix ); + this.projectionMatrixInverse.copy( source.projectionMatrixInverse ); - } + this.coordinateSystem = source.coordinateSystem; - return this.intersectsSphere( _sphere$4 ); + return this; } - intersectsSprite( sprite ) { - - _sphere$4.center.set( 0, 0, 0 ); - _sphere$4.radius = 0.7071067811865476; - _sphere$4.applyMatrix4( sprite.matrixWorld ); + /** + * Returns a vector representing the ("look") direction of the 3D object in world space. + * + * This method is overwritten since cameras have a different forward vector compared to other + * 3D objects. A camera looks down its local, negative z-axis by default. + * + * @param {Vector3} target - The target vector the result is stored to. + * @return {Vector3} The 3D object's direction in world space. + */ + getWorldDirection( target ) { - return this.intersectsSphere( _sphere$4 ); + return super.getWorldDirection( target ).negate(); } - intersectsSphere( sphere ) { - - const planes = this.planes; - const center = sphere.center; - const negRadius = - sphere.radius; - - for ( let i = 0; i < 6; i ++ ) { + updateMatrixWorld( force ) { - const distance = planes[ i ].distanceToPoint( center ); + super.updateMatrixWorld( force ); - if ( distance < negRadius ) { + this.matrixWorldInverse.copy( this.matrixWorld ).invert(); - return false; + } - } + updateWorldMatrix( updateParents, updateChildren ) { - } + super.updateWorldMatrix( updateParents, updateChildren ); - return true; + this.matrixWorldInverse.copy( this.matrixWorld ).invert(); } - intersectsBox( box ) { + clone() { - const planes = this.planes; + return new this.constructor().copy( this ); - for ( let i = 0; i < 6; i ++ ) { + } - const plane = planes[ i ]; +} - // corner at max distance +const _v3$1 = /*@__PURE__*/ new Vector3(); +const _minTarget = /*@__PURE__*/ new Vector2(); +const _maxTarget = /*@__PURE__*/ new Vector2(); - _vector$6.x = plane.normal.x > 0 ? box.max.x : box.min.x; - _vector$6.y = plane.normal.y > 0 ? box.max.y : box.min.y; - _vector$6.z = plane.normal.z > 0 ? box.max.z : box.min.z; +/** + * Camera that uses [perspective projection]{@link https://en.wikipedia.org/wiki/Perspective_(graphical)}. + * + * This projection mode is designed to mimic the way the human eye sees. It + * is the most common projection mode used for rendering a 3D scene. + * + * ```js + * const camera = new THREE.PerspectiveCamera( 45, width / height, 1, 1000 ); + * scene.add( camera ); + * ``` + * + * @augments Camera + */ +class PerspectiveCamera extends Camera { - if ( plane.distanceToPoint( _vector$6 ) < 0 ) { + /** + * Constructs a new perspective camera. + * + * @param {number} [fov=50] - The vertical field of view. + * @param {number} [aspect=1] - The aspect ratio. + * @param {number} [near=0.1] - The camera's near plane. + * @param {number} [far=2000] - The camera's far plane. + */ + constructor( fov = 50, aspect = 1, near = 0.1, far = 2000 ) { - return false; + super(); - } + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isPerspectiveCamera = true; - } + this.type = 'PerspectiveCamera'; - return true; + /** + * The vertical field of view, from bottom to top of view, + * in degrees. + * + * @type {number} + * @default 50 + */ + this.fov = fov; - } + /** + * The zoom factor of the camera. + * + * @type {number} + * @default 1 + */ + this.zoom = 1; - containsPoint( point ) { + /** + * The camera's near plane. The valid range is greater than `0` + * and less than the current value of {@link PerspectiveCamera#far}. + * + * Note that, unlike for the {@link OrthographicCamera}, `0` is not a + * valid value for a perspective camera's near plane. + * + * @type {number} + * @default 0.1 + */ + this.near = near; - const planes = this.planes; + /** + * The camera's far plane. Must be greater than the + * current value of {@link PerspectiveCamera#near}. + * + * @type {number} + * @default 2000 + */ + this.far = far; - for ( let i = 0; i < 6; i ++ ) { + /** + * Object distance used for stereoscopy and depth-of-field effects. This + * parameter does not influence the projection matrix unless a + * {@link StereoCamera} is being used. + * + * @type {number} + * @default 10 + */ + this.focus = 10; - if ( planes[ i ].distanceToPoint( point ) < 0 ) { + /** + * The aspect ratio, usually the canvas width / canvas height. + * + * @type {number} + * @default 1 + */ + this.aspect = aspect; - return false; + /** + * Represents the frustum window specification. This property should not be edited + * directly but via {@link PerspectiveCamera#setViewOffset} and {@link PerspectiveCamera#clearViewOffset}. + * + * @type {?Object} + * @default null + */ + this.view = null; - } + /** + * Film size used for the larger axis. Default is `35` (millimeters). This + * parameter does not influence the projection matrix unless {@link PerspectiveCamera#filmOffset} + * is set to a nonzero value. + * + * @type {number} + * @default 35 + */ + this.filmGauge = 35; - } + /** + * Horizontal off-center offset in the same unit as {@link PerspectiveCamera#filmGauge}. + * + * @type {number} + * @default 0 + */ + this.filmOffset = 0; - return true; + this.updateProjectionMatrix(); } - clone() { + copy( source, recursive ) { - return new this.constructor().copy( this ); + super.copy( source, recursive ); - } + this.fov = source.fov; + this.zoom = source.zoom; -} + this.near = source.near; + this.far = source.far; + this.focus = source.focus; -function WebGLAnimation() { + this.aspect = source.aspect; + this.view = source.view === null ? null : Object.assign( {}, source.view ); - let context = null; - let isAnimating = false; - let animationLoop = null; - let requestId = null; + this.filmGauge = source.filmGauge; + this.filmOffset = source.filmOffset; - function onAnimationFrame( time, frame ) { + return this; - animationLoop( time, frame ); + } - requestId = context.requestAnimationFrame( onAnimationFrame ); + /** + * Sets the FOV by focal length in respect to the current {@link PerspectiveCamera#filmGauge}. + * + * The default film gauge is 35, so that the focal length can be specified for + * a 35mm (full frame) camera. + * + * @param {number} focalLength - Values for focal length and film gauge must have the same unit. + */ + setFocalLength( focalLength ) { + + /** see {@link http://www.bobatkins.com/photography/technical/field_of_view.html} */ + const vExtentSlope = 0.5 * this.getFilmHeight() / focalLength; + + this.fov = RAD2DEG * 2 * Math.atan( vExtentSlope ); + this.updateProjectionMatrix(); } - return { + /** + * Returns the focal length from the current {@link PerspectiveCamera#fov} and + * {@link PerspectiveCamera#filmGauge}. + * + * @return {number} The computed focal length. + */ + getFocalLength() { - start: function () { + const vExtentSlope = Math.tan( DEG2RAD * 0.5 * this.fov ); - if ( isAnimating === true ) return; - if ( animationLoop === null ) return; + return 0.5 * this.getFilmHeight() / vExtentSlope; - requestId = context.requestAnimationFrame( onAnimationFrame ); + } - isAnimating = true; + /** + * Returns the current vertical field of view angle in degrees considering {@link PerspectiveCamera#zoom}. + * + * @return {number} The effective FOV. + */ + getEffectiveFOV() { - }, + return RAD2DEG * 2 * Math.atan( + Math.tan( DEG2RAD * 0.5 * this.fov ) / this.zoom ); - stop: function () { + } - context.cancelAnimationFrame( requestId ); + /** + * Returns the width of the image on the film. If {@link PerspectiveCamera#aspect} is greater than or + * equal to one (landscape format), the result equals {@link PerspectiveCamera#filmGauge}. + * + * @return {number} The film width. + */ + getFilmWidth() { - isAnimating = false; + // film not completely covered in portrait format (aspect < 1) + return this.filmGauge * Math.min( this.aspect, 1 ); - }, + } - setAnimationLoop: function ( callback ) { + /** + * Returns the height of the image on the film. If {@link PerspectiveCamera#aspect} is greater than or + * equal to one (landscape format), the result equals {@link PerspectiveCamera#filmGauge}. + * + * @return {number} The film width. + */ + getFilmHeight() { - animationLoop = callback; + // film not completely covered in landscape format (aspect > 1) + return this.filmGauge / Math.max( this.aspect, 1 ); - }, + } - setContext: function ( value ) { + /** + * Computes the 2D bounds of the camera's viewable rectangle at a given distance along the viewing direction. + * Sets `minTarget` and `maxTarget` to the coordinates of the lower-left and upper-right corners of the view rectangle. + * + * @param {number} distance - The viewing distance. + * @param {Vector2} minTarget - The lower-left corner of the view rectangle is written into this vector. + * @param {Vector2} maxTarget - The upper-right corner of the view rectangle is written into this vector. + */ + getViewBounds( distance, minTarget, maxTarget ) { - context = value; + _v3$1.set( -1, -1, 0.5 ).applyMatrix4( this.projectionMatrixInverse ); - } + minTarget.set( _v3$1.x, _v3$1.y ).multiplyScalar( - distance / _v3$1.z ); - }; + _v3$1.set( 1, 1, 0.5 ).applyMatrix4( this.projectionMatrixInverse ); -} + maxTarget.set( _v3$1.x, _v3$1.y ).multiplyScalar( - distance / _v3$1.z ); -function WebGLAttributes( gl, capabilities ) { + } - const isWebGL2 = capabilities.isWebGL2; + /** + * Computes the width and height of the camera's viewable rectangle at a given distance along the viewing direction. + * + * @param {number} distance - The viewing distance. + * @param {Vector2} target - The target vector that is used to store result where x is width and y is height. + * @returns {Vector2} The view size. + */ + getViewSize( distance, target ) { - const buffers = new WeakMap(); + this.getViewBounds( distance, _minTarget, _maxTarget ); - function createBuffer( attribute, bufferType ) { + return target.subVectors( _maxTarget, _minTarget ); - const array = attribute.array; - const usage = attribute.usage; + } - const buffer = gl.createBuffer(); + /** + * Sets an offset in a larger frustum. This is useful for multi-window or + * multi-monitor/multi-machine setups. + * + * For example, if you have 3x2 monitors and each monitor is 1920x1080 and + * the monitors are in grid like this + *``` + * +---+---+---+ + * | A | B | C | + * +---+---+---+ + * | D | E | F | + * +---+---+---+ + *``` + * then for each monitor you would call it like this: + *```js + * const w = 1920; + * const h = 1080; + * const fullWidth = w * 3; + * const fullHeight = h * 2; + * + * // --A-- + * camera.setViewOffset( fullWidth, fullHeight, w * 0, h * 0, w, h ); + * // --B-- + * camera.setViewOffset( fullWidth, fullHeight, w * 1, h * 0, w, h ); + * // --C-- + * camera.setViewOffset( fullWidth, fullHeight, w * 2, h * 0, w, h ); + * // --D-- + * camera.setViewOffset( fullWidth, fullHeight, w * 0, h * 1, w, h ); + * // --E-- + * camera.setViewOffset( fullWidth, fullHeight, w * 1, h * 1, w, h ); + * // --F-- + * camera.setViewOffset( fullWidth, fullHeight, w * 2, h * 1, w, h ); + * ``` + * + * Note there is no reason monitors have to be the same size or in a grid. + * + * @param {number} fullWidth - The full width of multiview setup. + * @param {number} fullHeight - The full height of multiview setup. + * @param {number} x - The horizontal offset of the subcamera. + * @param {number} y - The vertical offset of the subcamera. + * @param {number} width - The width of subcamera. + * @param {number} height - The height of subcamera. + */ + setViewOffset( fullWidth, fullHeight, x, y, width, height ) { - gl.bindBuffer( bufferType, buffer ); - gl.bufferData( bufferType, array, usage ); + this.aspect = fullWidth / fullHeight; - attribute.onUploadCallback(); + if ( this.view === null ) { - let type; + this.view = { + enabled: true, + fullWidth: 1, + fullHeight: 1, + offsetX: 0, + offsetY: 0, + width: 1, + height: 1 + }; - if ( array instanceof Float32Array ) { + } - type = gl.FLOAT; + this.view.enabled = true; + this.view.fullWidth = fullWidth; + this.view.fullHeight = fullHeight; + this.view.offsetX = x; + this.view.offsetY = y; + this.view.width = width; + this.view.height = height; - } else if ( array instanceof Uint16Array ) { + this.updateProjectionMatrix(); - if ( attribute.isFloat16BufferAttribute ) { + } - if ( isWebGL2 ) { + /** + * Removes the view offset from the projection matrix. + */ + clearViewOffset() { - type = gl.HALF_FLOAT; + if ( this.view !== null ) { - } else { + this.view.enabled = false; - throw new Error( 'THREE.WebGLAttributes: Usage of Float16BufferAttribute requires WebGL2.' ); + } - } + this.updateProjectionMatrix(); - } else { + } - type = gl.UNSIGNED_SHORT; + /** + * Updates the camera's projection matrix. Must be called after any change of + * camera properties. + */ + updateProjectionMatrix() { - } + const near = this.near; + let top = near * Math.tan( DEG2RAD * 0.5 * this.fov ) / this.zoom; + let height = 2 * top; + let width = this.aspect * height; + let left = -0.5 * width; + const view = this.view; - } else if ( array instanceof Int16Array ) { + if ( this.view !== null && this.view.enabled ) { - type = gl.SHORT; + const fullWidth = view.fullWidth, + fullHeight = view.fullHeight; - } else if ( array instanceof Uint32Array ) { + left += view.offsetX * width / fullWidth; + top -= view.offsetY * height / fullHeight; + width *= view.width / fullWidth; + height *= view.height / fullHeight; - type = gl.UNSIGNED_INT; + } - } else if ( array instanceof Int32Array ) { + const skew = this.filmOffset; + if ( skew !== 0 ) left += near * skew / this.getFilmWidth(); - type = gl.INT; + this.projectionMatrix.makePerspective( left, left + width, top, top - height, near, this.far, this.coordinateSystem ); - } else if ( array instanceof Int8Array ) { + this.projectionMatrixInverse.copy( this.projectionMatrix ).invert(); - type = gl.BYTE; + } - } else if ( array instanceof Uint8Array ) { + toJSON( meta ) { - type = gl.UNSIGNED_BYTE; + const data = super.toJSON( meta ); - } else if ( array instanceof Uint8ClampedArray ) { + data.object.fov = this.fov; + data.object.zoom = this.zoom; - type = gl.UNSIGNED_BYTE; + data.object.near = this.near; + data.object.far = this.far; + data.object.focus = this.focus; - } else { + data.object.aspect = this.aspect; - throw new Error( 'THREE.WebGLAttributes: Unsupported buffer data format: ' + array ); + if ( this.view !== null ) data.object.view = Object.assign( {}, this.view ); - } + data.object.filmGauge = this.filmGauge; + data.object.filmOffset = this.filmOffset; - return { - buffer: buffer, - type: type, - bytesPerElement: array.BYTES_PER_ELEMENT, - version: attribute.version - }; + return data; } - function updateBuffer( buffer, attribute, bufferType ) { +} - const array = attribute.array; - const updateRange = attribute.updateRange; +const fov = -90; // negative fov is not an error +const aspect = 1; - gl.bindBuffer( bufferType, buffer ); +/** + * A special type of camera that is positioned in 3D space to render its surroundings into a + * cube render target. The render target can then be used as an environment map for rendering + * realtime reflections in your scene. + * + * ```js + * // Create cube render target + * const cubeRenderTarget = new THREE.WebGLCubeRenderTarget( 256, { generateMipmaps: true, minFilter: THREE.LinearMipmapLinearFilter } ); + * + * // Create cube camera + * const cubeCamera = new THREE.CubeCamera( 1, 100000, cubeRenderTarget ); + * scene.add( cubeCamera ); + * + * // Create car + * const chromeMaterial = new THREE.MeshLambertMaterial( { color: 0xffffff, envMap: cubeRenderTarget.texture } ); + * const car = new THREE.Mesh( carGeometry, chromeMaterial ); + * scene.add( car ); + * + * // Update the render target cube + * car.visible = false; + * cubeCamera.position.copy( car.position ); + * cubeCamera.update( renderer, scene ); + * + * // Render the scene + * car.visible = true; + * renderer.render( scene, camera ); + * ``` + * + * @augments Object3D + */ +class CubeCamera extends Object3D { - if ( updateRange.count === - 1 ) { + /** + * Constructs a new cube camera. + * + * @param {number} near - The camera's near plane. + * @param {number} far - The camera's far plane. + * @param {WebGLCubeRenderTarget} renderTarget - The cube render target. + */ + constructor( near, far, renderTarget ) { - // Not using update ranges + super(); - gl.bufferSubData( bufferType, 0, array ); + this.type = 'CubeCamera'; - } else { + /** + * A reference to the cube render target. + * + * @type {WebGLCubeRenderTarget} + */ + this.renderTarget = renderTarget; - if ( isWebGL2 ) { + /** + * The current active coordinate system. + * + * @type {?(WebGLCoordinateSystem|WebGPUCoordinateSystem)} + * @default null + */ + this.coordinateSystem = null; - gl.bufferSubData( bufferType, updateRange.offset * array.BYTES_PER_ELEMENT, - array, updateRange.offset, updateRange.count ); + /** + * The current active mipmap level + * + * @type {number} + * @default 0 + */ + this.activeMipmapLevel = 0; - } else { + const cameraPX = new PerspectiveCamera( fov, aspect, near, far ); + cameraPX.layers = this.layers; + this.add( cameraPX ); - gl.bufferSubData( bufferType, updateRange.offset * array.BYTES_PER_ELEMENT, - array.subarray( updateRange.offset, updateRange.offset + updateRange.count ) ); + const cameraNX = new PerspectiveCamera( fov, aspect, near, far ); + cameraNX.layers = this.layers; + this.add( cameraNX ); - } + const cameraPY = new PerspectiveCamera( fov, aspect, near, far ); + cameraPY.layers = this.layers; + this.add( cameraPY ); - updateRange.count = - 1; // reset range + const cameraNY = new PerspectiveCamera( fov, aspect, near, far ); + cameraNY.layers = this.layers; + this.add( cameraNY ); - } + const cameraPZ = new PerspectiveCamera( fov, aspect, near, far ); + cameraPZ.layers = this.layers; + this.add( cameraPZ ); - attribute.onUploadCallback(); + const cameraNZ = new PerspectiveCamera( fov, aspect, near, far ); + cameraNZ.layers = this.layers; + this.add( cameraNZ ); } - // + /** + * Must be called when the coordinate system of the cube camera is changed. + */ + updateCoordinateSystem() { - function get( attribute ) { + const coordinateSystem = this.coordinateSystem; - if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; + const cameras = this.children.concat(); - return buffers.get( attribute ); + const [ cameraPX, cameraNX, cameraPY, cameraNY, cameraPZ, cameraNZ ] = cameras; - } + for ( const camera of cameras ) this.remove( camera ); - function remove( attribute ) { + if ( coordinateSystem === WebGLCoordinateSystem ) { - if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; + cameraPX.up.set( 0, 1, 0 ); + cameraPX.lookAt( 1, 0, 0 ); - const data = buffers.get( attribute ); + cameraNX.up.set( 0, 1, 0 ); + cameraNX.lookAt( -1, 0, 0 ); - if ( data ) { + cameraPY.up.set( 0, 0, -1 ); + cameraPY.lookAt( 0, 1, 0 ); - gl.deleteBuffer( data.buffer ); + cameraNY.up.set( 0, 0, 1 ); + cameraNY.lookAt( 0, -1, 0 ); - buffers.delete( attribute ); + cameraPZ.up.set( 0, 1, 0 ); + cameraPZ.lookAt( 0, 0, 1 ); - } + cameraNZ.up.set( 0, 1, 0 ); + cameraNZ.lookAt( 0, 0, -1 ); - } + } else if ( coordinateSystem === WebGPUCoordinateSystem ) { - function update( attribute, bufferType ) { + cameraPX.up.set( 0, -1, 0 ); + cameraPX.lookAt( -1, 0, 0 ); - if ( attribute.isGLBufferAttribute ) { + cameraNX.up.set( 0, -1, 0 ); + cameraNX.lookAt( 1, 0, 0 ); - const cached = buffers.get( attribute ); + cameraPY.up.set( 0, 0, 1 ); + cameraPY.lookAt( 0, 1, 0 ); - if ( ! cached || cached.version < attribute.version ) { + cameraNY.up.set( 0, 0, -1 ); + cameraNY.lookAt( 0, -1, 0 ); - buffers.set( attribute, { - buffer: attribute.buffer, - type: attribute.type, - bytesPerElement: attribute.elementSize, - version: attribute.version - } ); + cameraPZ.up.set( 0, -1, 0 ); + cameraPZ.lookAt( 0, 0, 1 ); - } + cameraNZ.up.set( 0, -1, 0 ); + cameraNZ.lookAt( 0, 0, -1 ); - return; + } else { + + throw new Error( 'THREE.CubeCamera.updateCoordinateSystem(): Invalid coordinate system: ' + coordinateSystem ); } - if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; + for ( const camera of cameras ) { - const data = buffers.get( attribute ); + this.add( camera ); - if ( data === undefined ) { + camera.updateMatrixWorld(); - buffers.set( attribute, createBuffer( attribute, bufferType ) ); + } - } else if ( data.version < attribute.version ) { + } - updateBuffer( data.buffer, attribute, bufferType ); + /** + * Calling this method will render the given scene with the given renderer + * into the cube render target of the camera. + * + * @param {(Renderer|WebGLRenderer)} renderer - The renderer. + * @param {Scene} scene - The scene to render. + */ + update( renderer, scene ) { - data.version = attribute.version; + if ( this.parent === null ) this.updateMatrixWorld(); + + const { renderTarget, activeMipmapLevel } = this; + + if ( this.coordinateSystem !== renderer.coordinateSystem ) { + + this.coordinateSystem = renderer.coordinateSystem; + + this.updateCoordinateSystem(); } + const [ cameraPX, cameraNX, cameraPY, cameraNY, cameraPZ, cameraNZ ] = this.children; + + const currentRenderTarget = renderer.getRenderTarget(); + const currentActiveCubeFace = renderer.getActiveCubeFace(); + const currentActiveMipmapLevel = renderer.getActiveMipmapLevel(); + + const currentXrEnabled = renderer.xr.enabled; + + renderer.xr.enabled = false; + + const generateMipmaps = renderTarget.texture.generateMipmaps; + + renderTarget.texture.generateMipmaps = false; + + renderer.setRenderTarget( renderTarget, 0, activeMipmapLevel ); + renderer.render( scene, cameraPX ); + + renderer.setRenderTarget( renderTarget, 1, activeMipmapLevel ); + renderer.render( scene, cameraNX ); + + renderer.setRenderTarget( renderTarget, 2, activeMipmapLevel ); + renderer.render( scene, cameraPY ); + + renderer.setRenderTarget( renderTarget, 3, activeMipmapLevel ); + renderer.render( scene, cameraNY ); + + renderer.setRenderTarget( renderTarget, 4, activeMipmapLevel ); + renderer.render( scene, cameraPZ ); + + // mipmaps are generated during the last call of render() + // at this point, all sides of the cube render target are defined + + renderTarget.texture.generateMipmaps = generateMipmaps; + + renderer.setRenderTarget( renderTarget, 5, activeMipmapLevel ); + renderer.render( scene, cameraNZ ); + + renderer.setRenderTarget( currentRenderTarget, currentActiveCubeFace, currentActiveMipmapLevel ); + + renderer.xr.enabled = currentXrEnabled; + + renderTarget.texture.needsPMREMUpdate = true; + } - return { +} - get: get, - remove: remove, - update: update +/** + * Creates a cube texture made up of six images. + * + * ```js + * const loader = new THREE.CubeTextureLoader(); + * loader.setPath( 'textures/cube/pisa/' ); + * + * const textureCube = loader.load( [ + * 'px.png', 'nx.png', 'py.png', 'ny.png', 'pz.png', 'nz.png' + * ] ); + * + * const material = new THREE.MeshBasicMaterial( { color: 0xffffff, envMap: textureCube } ); + * ``` + * + * @augments Texture + */ +class CubeTexture extends Texture { - }; + /** + * Constructs a new cube texture. + * + * @param {Array} [images=[]] - An array holding a image for each side of a cube. + * @param {number} [mapping=CubeReflectionMapping] - The texture mapping. + * @param {number} [wrapS=ClampToEdgeWrapping] - The wrapS value. + * @param {number} [wrapT=ClampToEdgeWrapping] - The wrapT value. + * @param {number} [magFilter=LinearFilter] - The mag filter value. + * @param {number} [minFilter=LinearMipmapLinearFilter] - The min filter value. + * @param {number} [format=RGBAFormat] - The texture format. + * @param {number} [type=UnsignedByteType] - The texture type. + * @param {number} [anisotropy=Texture.DEFAULT_ANISOTROPY] - The anisotropy value. + * @param {string} [colorSpace=NoColorSpace] - The color space value. + */ + constructor( images = [], mapping = CubeReflectionMapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, colorSpace ) { + + super( images, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, colorSpace ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isCubeTexture = true; + + /** + * If set to `true`, the texture is flipped along the vertical axis when + * uploaded to the GPU. + * + * Overwritten and set to `false` by default. + * + * @type {boolean} + * @default false + */ + this.flipY = false; + + } + + /** + * Alias for {@link CubeTexture#image}. + * + * @type {Array} + */ + get images() { + + return this.image; + + } + + set images( value ) { + + this.image = value; + + } } -class PlaneGeometry extends BufferGeometry { +/** + * A cube render target used in context of {@link WebGLRenderer}. + * + * @augments WebGLRenderTarget + */ +class WebGLCubeRenderTarget extends WebGLRenderTarget { - constructor( width = 1, height = 1, widthSegments = 1, heightSegments = 1 ) { + /** + * Constructs a new cube render target. + * + * @param {number} [size=1] - The size of the render target. + * @param {RenderTarget~Options} [options] - The configuration object. + */ + constructor( size = 1, options = {} ) { - super(); + super( size, size, options ); - this.type = 'PlaneGeometry'; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isWebGLCubeRenderTarget = true; - this.parameters = { - width: width, - height: height, - widthSegments: widthSegments, - heightSegments: heightSegments - }; + const image = { width: size, height: size, depth: 1 }; + const images = [ image, image, image, image, image, image ]; - const width_half = width / 2; - const height_half = height / 2; + /** + * Overwritten with a different texture type. + * + * @type {DataArrayTexture} + */ + this.texture = new CubeTexture( images ); + this._setTextureOptions( options ); - const gridX = Math.floor( widthSegments ); - const gridY = Math.floor( heightSegments ); + // By convention -- likely based on the RenderMan spec from the 1990's -- cube maps are specified by WebGL (and three.js) + // in a coordinate system in which positive-x is to the right when looking up the positive-z axis -- in other words, + // in a left-handed coordinate system. By continuing this convention, preexisting cube maps continued to render correctly. - const gridX1 = gridX + 1; - const gridY1 = gridY + 1; + // three.js uses a right-handed coordinate system. So environment maps used in three.js appear to have px and nx swapped + // and the flag isRenderTargetTexture controls this conversion. The flip is not required when using WebGLCubeRenderTarget.texture + // as a cube texture (this is detected when isRenderTargetTexture is set to true for cube textures). - const segment_width = width / gridX; - const segment_height = height / gridY; + this.texture.isRenderTargetTexture = true; - // + } - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; + /** + * Converts the given equirectangular texture to a cube map. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {Texture} texture - The equirectangular texture. + * @return {WebGLCubeRenderTarget} A reference to this cube render target. + */ + fromEquirectangularTexture( renderer, texture ) { - for ( let iy = 0; iy < gridY1; iy ++ ) { + this.texture.type = texture.type; + this.texture.colorSpace = texture.colorSpace; - const y = iy * segment_height - height_half; + this.texture.generateMipmaps = texture.generateMipmaps; + this.texture.minFilter = texture.minFilter; + this.texture.magFilter = texture.magFilter; - for ( let ix = 0; ix < gridX1; ix ++ ) { + const shader = { - const x = ix * segment_width - width_half; + uniforms: { + tEquirect: { value: null }, + }, - vertices.push( x, - y, 0 ); + vertexShader: /* glsl */` - normals.push( 0, 0, 1 ); + varying vec3 vWorldDirection; - uvs.push( ix / gridX ); - uvs.push( 1 - ( iy / gridY ) ); + vec3 transformDirection( in vec3 dir, in mat4 matrix ) { - } + return normalize( ( matrix * vec4( dir, 0.0 ) ).xyz ); - } + } - for ( let iy = 0; iy < gridY; iy ++ ) { + void main() { - for ( let ix = 0; ix < gridX; ix ++ ) { + vWorldDirection = transformDirection( position, modelMatrix ); - const a = ix + gridX1 * iy; - const b = ix + gridX1 * ( iy + 1 ); - const c = ( ix + 1 ) + gridX1 * ( iy + 1 ); - const d = ( ix + 1 ) + gridX1 * iy; + #include + #include - indices.push( a, b, d ); - indices.push( b, c, d ); + } + `, - } + fragmentShader: /* glsl */` - } + uniform sampler2D tEquirect; - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + varying vec3 vWorldDirection; - } + #include - copy( source ) { + void main() { - super.copy( source ); + vec3 direction = normalize( vWorldDirection ); - this.parameters = Object.assign( {}, source.parameters ); + vec2 sampleUV = equirectUv( direction ); - return this; + gl_FragColor = texture2D( tEquirect, sampleUV ); - } + } + ` + }; - static fromJSON( data ) { + const geometry = new BoxGeometry( 5, 5, 5 ); - return new PlaneGeometry( data.width, data.height, data.widthSegments, data.heightSegments ); + const material = new ShaderMaterial( { - } + name: 'CubemapFromEquirect', -} + uniforms: cloneUniforms( shader.uniforms ), + vertexShader: shader.vertexShader, + fragmentShader: shader.fragmentShader, + side: BackSide, + blending: NoBlending -var alphamap_fragment = "#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, vAlphaMapUv ).g;\n#endif"; + } ); -var alphamap_pars_fragment = "#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif"; + material.uniforms.tEquirect.value = texture; -var alphatest_fragment = "#ifdef USE_ALPHATEST\n\tif ( diffuseColor.a < alphaTest ) discard;\n#endif"; + const mesh = new Mesh( geometry, material ); -var alphatest_pars_fragment = "#ifdef USE_ALPHATEST\n\tuniform float alphaTest;\n#endif"; + const currentMinFilter = texture.minFilter; -var aomap_fragment = "#ifdef USE_AOMAP\n\tfloat ambientOcclusion = ( texture2D( aoMap, vAoMapUv ).r - 1.0 ) * aoMapIntensity + 1.0;\n\treflectedLight.indirectDiffuse *= ambientOcclusion;\n\t#if defined( USE_ENVMAP ) && defined( STANDARD )\n\t\tfloat dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );\n\t\treflectedLight.indirectSpecular *= computeSpecularOcclusion( dotNV, ambientOcclusion, material.roughness );\n\t#endif\n#endif"; + // Avoid blurred poles + if ( texture.minFilter === LinearMipmapLinearFilter ) texture.minFilter = LinearFilter; -var aomap_pars_fragment = "#ifdef USE_AOMAP\n\tuniform sampler2D aoMap;\n\tuniform float aoMapIntensity;\n#endif"; + const camera = new CubeCamera( 1, 10, this ); + camera.update( renderer, mesh ); -var begin_vertex = "vec3 transformed = vec3( position );"; + texture.minFilter = currentMinFilter; -var beginnormal_vertex = "vec3 objectNormal = vec3( normal );\n#ifdef USE_TANGENT\n\tvec3 objectTangent = vec3( tangent.xyz );\n#endif"; + mesh.geometry.dispose(); + mesh.material.dispose(); -var bsdfs = "float G_BlinnPhong_Implicit( ) {\n\treturn 0.25;\n}\nfloat D_BlinnPhong( const in float shininess, const in float dotNH ) {\n\treturn RECIPROCAL_PI * ( shininess * 0.5 + 1.0 ) * pow( dotNH, shininess );\n}\nvec3 BRDF_BlinnPhong( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in vec3 specularColor, const in float shininess ) {\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\tvec3 F = F_Schlick( specularColor, 1.0, dotVH );\n\tfloat G = G_BlinnPhong_Implicit( );\n\tfloat D = D_BlinnPhong( shininess, dotNH );\n\treturn F * ( G * D );\n} // validated"; + return this; -var iridescence_fragment = "#ifdef USE_IRIDESCENCE\n\tconst mat3 XYZ_TO_REC709 = mat3(\n\t\t 3.2404542, -0.9692660, 0.0556434,\n\t\t-1.5371385, 1.8760108, -0.2040259,\n\t\t-0.4985314, 0.0415560, 1.0572252\n\t);\n\tvec3 Fresnel0ToIor( vec3 fresnel0 ) {\n\t\tvec3 sqrtF0 = sqrt( fresnel0 );\n\t\treturn ( vec3( 1.0 ) + sqrtF0 ) / ( vec3( 1.0 ) - sqrtF0 );\n\t}\n\tvec3 IorToFresnel0( vec3 transmittedIor, float incidentIor ) {\n\t\treturn pow2( ( transmittedIor - vec3( incidentIor ) ) / ( transmittedIor + vec3( incidentIor ) ) );\n\t}\n\tfloat IorToFresnel0( float transmittedIor, float incidentIor ) {\n\t\treturn pow2( ( transmittedIor - incidentIor ) / ( transmittedIor + incidentIor ));\n\t}\n\tvec3 evalSensitivity( float OPD, vec3 shift ) {\n\t\tfloat phase = 2.0 * PI * OPD * 1.0e-9;\n\t\tvec3 val = vec3( 5.4856e-13, 4.4201e-13, 5.2481e-13 );\n\t\tvec3 pos = vec3( 1.6810e+06, 1.7953e+06, 2.2084e+06 );\n\t\tvec3 var = vec3( 4.3278e+09, 9.3046e+09, 6.6121e+09 );\n\t\tvec3 xyz = val * sqrt( 2.0 * PI * var ) * cos( pos * phase + shift ) * exp( - pow2( phase ) * var );\n\t\txyz.x += 9.7470e-14 * sqrt( 2.0 * PI * 4.5282e+09 ) * cos( 2.2399e+06 * phase + shift[ 0 ] ) * exp( - 4.5282e+09 * pow2( phase ) );\n\t\txyz /= 1.0685e-7;\n\t\tvec3 rgb = XYZ_TO_REC709 * xyz;\n\t\treturn rgb;\n\t}\n\tvec3 evalIridescence( float outsideIOR, float eta2, float cosTheta1, float thinFilmThickness, vec3 baseF0 ) {\n\t\tvec3 I;\n\t\tfloat iridescenceIOR = mix( outsideIOR, eta2, smoothstep( 0.0, 0.03, thinFilmThickness ) );\n\t\tfloat sinTheta2Sq = pow2( outsideIOR / iridescenceIOR ) * ( 1.0 - pow2( cosTheta1 ) );\n\t\tfloat cosTheta2Sq = 1.0 - sinTheta2Sq;\n\t\tif ( cosTheta2Sq < 0.0 ) {\n\t\t\t return vec3( 1.0 );\n\t\t}\n\t\tfloat cosTheta2 = sqrt( cosTheta2Sq );\n\t\tfloat R0 = IorToFresnel0( iridescenceIOR, outsideIOR );\n\t\tfloat R12 = F_Schlick( R0, 1.0, cosTheta1 );\n\t\tfloat R21 = R12;\n\t\tfloat T121 = 1.0 - R12;\n\t\tfloat phi12 = 0.0;\n\t\tif ( iridescenceIOR < outsideIOR ) phi12 = PI;\n\t\tfloat phi21 = PI - phi12;\n\t\tvec3 baseIOR = Fresnel0ToIor( clamp( baseF0, 0.0, 0.9999 ) );\t\tvec3 R1 = IorToFresnel0( baseIOR, iridescenceIOR );\n\t\tvec3 R23 = F_Schlick( R1, 1.0, cosTheta2 );\n\t\tvec3 phi23 = vec3( 0.0 );\n\t\tif ( baseIOR[ 0 ] < iridescenceIOR ) phi23[ 0 ] = PI;\n\t\tif ( baseIOR[ 1 ] < iridescenceIOR ) phi23[ 1 ] = PI;\n\t\tif ( baseIOR[ 2 ] < iridescenceIOR ) phi23[ 2 ] = PI;\n\t\tfloat OPD = 2.0 * iridescenceIOR * thinFilmThickness * cosTheta2;\n\t\tvec3 phi = vec3( phi21 ) + phi23;\n\t\tvec3 R123 = clamp( R12 * R23, 1e-5, 0.9999 );\n\t\tvec3 r123 = sqrt( R123 );\n\t\tvec3 Rs = pow2( T121 ) * R23 / ( vec3( 1.0 ) - R123 );\n\t\tvec3 C0 = R12 + Rs;\n\t\tI = C0;\n\t\tvec3 Cm = Rs - T121;\n\t\tfor ( int m = 1; m <= 2; ++ m ) {\n\t\t\tCm *= r123;\n\t\t\tvec3 Sm = 2.0 * evalSensitivity( float( m ) * OPD, float( m ) * phi );\n\t\t\tI += Cm * Sm;\n\t\t}\n\t\treturn max( I, vec3( 0.0 ) );\n\t}\n#endif"; + } -var bumpmap_pars_fragment = "#ifdef USE_BUMPMAP\n\tuniform sampler2D bumpMap;\n\tuniform float bumpScale;\n\tvec2 dHdxy_fwd() {\n\t\tvec2 dSTdx = dFdx( vBumpMapUv );\n\t\tvec2 dSTdy = dFdy( vBumpMapUv );\n\t\tfloat Hll = bumpScale * texture2D( bumpMap, vBumpMapUv ).x;\n\t\tfloat dBx = bumpScale * texture2D( bumpMap, vBumpMapUv + dSTdx ).x - Hll;\n\t\tfloat dBy = bumpScale * texture2D( bumpMap, vBumpMapUv + dSTdy ).x - Hll;\n\t\treturn vec2( dBx, dBy );\n\t}\n\tvec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy, float faceDirection ) {\n\t\tvec3 vSigmaX = dFdx( surf_pos.xyz );\n\t\tvec3 vSigmaY = dFdy( surf_pos.xyz );\n\t\tvec3 vN = surf_norm;\n\t\tvec3 R1 = cross( vSigmaY, vN );\n\t\tvec3 R2 = cross( vN, vSigmaX );\n\t\tfloat fDet = dot( vSigmaX, R1 ) * faceDirection;\n\t\tvec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );\n\t\treturn normalize( abs( fDet ) * surf_norm - vGrad );\n\t}\n#endif"; + /** + * Clears this cube render target. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {boolean} [color=true] - Whether the color buffer should be cleared or not. + * @param {boolean} [depth=true] - Whether the depth buffer should be cleared or not. + * @param {boolean} [stencil=true] - Whether the stencil buffer should be cleared or not. + */ + clear( renderer, color = true, depth = true, stencil = true ) { -var clipping_planes_fragment = "#if NUM_CLIPPING_PLANES > 0\n\tvec4 plane;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) {\n\t\tplane = clippingPlanes[ i ];\n\t\tif ( dot( vClipPosition, plane.xyz ) > plane.w ) discard;\n\t}\n\t#pragma unroll_loop_end\n\t#if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES\n\t\tbool clipped = true;\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) {\n\t\t\tplane = clippingPlanes[ i ];\n\t\t\tclipped = ( dot( vClipPosition, plane.xyz ) > plane.w ) && clipped;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t\tif ( clipped ) discard;\n\t#endif\n#endif"; + const currentRenderTarget = renderer.getRenderTarget(); -var clipping_planes_pars_fragment = "#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n\tuniform vec4 clippingPlanes[ NUM_CLIPPING_PLANES ];\n#endif"; + for ( let i = 0; i < 6; i ++ ) { -var clipping_planes_pars_vertex = "#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n#endif"; + renderer.setRenderTarget( this, i ); -var clipping_planes_vertex = "#if NUM_CLIPPING_PLANES > 0\n\tvClipPosition = - mvPosition.xyz;\n#endif"; + renderer.clear( color, depth, stencil ); -var color_fragment = "#if defined( USE_COLOR_ALPHA )\n\tdiffuseColor *= vColor;\n#elif defined( USE_COLOR )\n\tdiffuseColor.rgb *= vColor;\n#endif"; + } -var color_pars_fragment = "#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR )\n\tvarying vec3 vColor;\n#endif"; + renderer.setRenderTarget( currentRenderTarget ); -var color_pars_vertex = "#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR )\n\tvarying vec3 vColor;\n#endif"; + } -var color_vertex = "#if defined( USE_COLOR_ALPHA )\n\tvColor = vec4( 1.0 );\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR )\n\tvColor = vec3( 1.0 );\n#endif\n#ifdef USE_COLOR\n\tvColor *= color;\n#endif\n#ifdef USE_INSTANCING_COLOR\n\tvColor.xyz *= instanceColor.xyz;\n#endif"; +} -var common = "#define PI 3.141592653589793\n#define PI2 6.283185307179586\n#define PI_HALF 1.5707963267948966\n#define RECIPROCAL_PI 0.3183098861837907\n#define RECIPROCAL_PI2 0.15915494309189535\n#define EPSILON 1e-6\n#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\n#define whiteComplement( a ) ( 1.0 - saturate( a ) )\nfloat pow2( const in float x ) { return x*x; }\nvec3 pow2( const in vec3 x ) { return x*x; }\nfloat pow3( const in float x ) { return x*x*x; }\nfloat pow4( const in float x ) { float x2 = x*x; return x2*x2; }\nfloat max3( const in vec3 v ) { return max( max( v.x, v.y ), v.z ); }\nfloat average( const in vec3 v ) { return dot( v, vec3( 0.3333333 ) ); }\nhighp float rand( const in vec2 uv ) {\n\tconst highp float a = 12.9898, b = 78.233, c = 43758.5453;\n\thighp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI );\n\treturn fract( sin( sn ) * c );\n}\n#ifdef HIGH_PRECISION\n\tfloat precisionSafeLength( vec3 v ) { return length( v ); }\n#else\n\tfloat precisionSafeLength( vec3 v ) {\n\t\tfloat maxComponent = max3( abs( v ) );\n\t\treturn length( v / maxComponent ) * maxComponent;\n\t}\n#endif\nstruct IncidentLight {\n\tvec3 color;\n\tvec3 direction;\n\tbool visible;\n};\nstruct ReflectedLight {\n\tvec3 directDiffuse;\n\tvec3 directSpecular;\n\tvec3 indirectDiffuse;\n\tvec3 indirectSpecular;\n};\nstruct GeometricContext {\n\tvec3 position;\n\tvec3 normal;\n\tvec3 viewDir;\n#ifdef USE_CLEARCOAT\n\tvec3 clearcoatNormal;\n#endif\n};\nvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n}\nvec3 inverseTransformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( vec4( dir, 0.0 ) * matrix ).xyz );\n}\nmat3 transposeMat3( const in mat3 m ) {\n\tmat3 tmp;\n\ttmp[ 0 ] = vec3( m[ 0 ].x, m[ 1 ].x, m[ 2 ].x );\n\ttmp[ 1 ] = vec3( m[ 0 ].y, m[ 1 ].y, m[ 2 ].y );\n\ttmp[ 2 ] = vec3( m[ 0 ].z, m[ 1 ].z, m[ 2 ].z );\n\treturn tmp;\n}\nfloat luminance( const in vec3 rgb ) {\n\tconst vec3 weights = vec3( 0.2126729, 0.7151522, 0.0721750 );\n\treturn dot( weights, rgb );\n}\nbool isPerspectiveMatrix( mat4 m ) {\n\treturn m[ 2 ][ 3 ] == - 1.0;\n}\nvec2 equirectUv( in vec3 dir ) {\n\tfloat u = atan( dir.z, dir.x ) * RECIPROCAL_PI2 + 0.5;\n\tfloat v = asin( clamp( dir.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\n\treturn vec2( u, v );\n}\nvec3 BRDF_Lambert( const in vec3 diffuseColor ) {\n\treturn RECIPROCAL_PI * diffuseColor;\n}\nvec3 F_Schlick( const in vec3 f0, const in float f90, const in float dotVH ) {\n\tfloat fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );\n\treturn f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );\n}\nfloat F_Schlick( const in float f0, const in float f90, const in float dotVH ) {\n\tfloat fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );\n\treturn f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );\n} // validated"; +/** + * This is almost identical to an {@link Object3D}. Its purpose is to + * make working with groups of objects syntactically clearer. + * + * ```js + * // Create a group and add the two cubes. + * // These cubes can now be rotated / scaled etc as a group. + * const group = new THREE.Group(); + * + * group.add( meshA ); + * group.add( meshB ); + * + * scene.add( group ); + * ``` + * + * @augments Object3D + */ +class Group extends Object3D { -var cube_uv_reflection_fragment = "#ifdef ENVMAP_TYPE_CUBE_UV\n\t#define cubeUV_minMipLevel 4.0\n\t#define cubeUV_minTileSize 16.0\n\tfloat getFace( vec3 direction ) {\n\t\tvec3 absDirection = abs( direction );\n\t\tfloat face = - 1.0;\n\t\tif ( absDirection.x > absDirection.z ) {\n\t\t\tif ( absDirection.x > absDirection.y )\n\t\t\t\tface = direction.x > 0.0 ? 0.0 : 3.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t} else {\n\t\t\tif ( absDirection.z > absDirection.y )\n\t\t\t\tface = direction.z > 0.0 ? 2.0 : 5.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t}\n\t\treturn face;\n\t}\n\tvec2 getUV( vec3 direction, float face ) {\n\t\tvec2 uv;\n\t\tif ( face == 0.0 ) {\n\t\t\tuv = vec2( direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 1.0 ) {\n\t\t\tuv = vec2( - direction.x, - direction.z ) / abs( direction.y );\n\t\t} else if ( face == 2.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.y ) / abs( direction.z );\n\t\t} else if ( face == 3.0 ) {\n\t\t\tuv = vec2( - direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 4.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.z ) / abs( direction.y );\n\t\t} else {\n\t\t\tuv = vec2( direction.x, direction.y ) / abs( direction.z );\n\t\t}\n\t\treturn 0.5 * ( uv + 1.0 );\n\t}\n\tvec3 bilinearCubeUV( sampler2D envMap, vec3 direction, float mipInt ) {\n\t\tfloat face = getFace( direction );\n\t\tfloat filterInt = max( cubeUV_minMipLevel - mipInt, 0.0 );\n\t\tmipInt = max( mipInt, cubeUV_minMipLevel );\n\t\tfloat faceSize = exp2( mipInt );\n\t\thighp vec2 uv = getUV( direction, face ) * ( faceSize - 2.0 ) + 1.0;\n\t\tif ( face > 2.0 ) {\n\t\t\tuv.y += faceSize;\n\t\t\tface -= 3.0;\n\t\t}\n\t\tuv.x += face * faceSize;\n\t\tuv.x += filterInt * 3.0 * cubeUV_minTileSize;\n\t\tuv.y += 4.0 * ( exp2( CUBEUV_MAX_MIP ) - faceSize );\n\t\tuv.x *= CUBEUV_TEXEL_WIDTH;\n\t\tuv.y *= CUBEUV_TEXEL_HEIGHT;\n\t\t#ifdef texture2DGradEXT\n\t\t\treturn texture2DGradEXT( envMap, uv, vec2( 0.0 ), vec2( 0.0 ) ).rgb;\n\t\t#else\n\t\t\treturn texture2D( envMap, uv ).rgb;\n\t\t#endif\n\t}\n\t#define cubeUV_r0 1.0\n\t#define cubeUV_v0 0.339\n\t#define cubeUV_m0 - 2.0\n\t#define cubeUV_r1 0.8\n\t#define cubeUV_v1 0.276\n\t#define cubeUV_m1 - 1.0\n\t#define cubeUV_r4 0.4\n\t#define cubeUV_v4 0.046\n\t#define cubeUV_m4 2.0\n\t#define cubeUV_r5 0.305\n\t#define cubeUV_v5 0.016\n\t#define cubeUV_m5 3.0\n\t#define cubeUV_r6 0.21\n\t#define cubeUV_v6 0.0038\n\t#define cubeUV_m6 4.0\n\tfloat roughnessToMip( float roughness ) {\n\t\tfloat mip = 0.0;\n\t\tif ( roughness >= cubeUV_r1 ) {\n\t\t\tmip = ( cubeUV_r0 - roughness ) * ( cubeUV_m1 - cubeUV_m0 ) / ( cubeUV_r0 - cubeUV_r1 ) + cubeUV_m0;\n\t\t} else if ( roughness >= cubeUV_r4 ) {\n\t\t\tmip = ( cubeUV_r1 - roughness ) * ( cubeUV_m4 - cubeUV_m1 ) / ( cubeUV_r1 - cubeUV_r4 ) + cubeUV_m1;\n\t\t} else if ( roughness >= cubeUV_r5 ) {\n\t\t\tmip = ( cubeUV_r4 - roughness ) * ( cubeUV_m5 - cubeUV_m4 ) / ( cubeUV_r4 - cubeUV_r5 ) + cubeUV_m4;\n\t\t} else if ( roughness >= cubeUV_r6 ) {\n\t\t\tmip = ( cubeUV_r5 - roughness ) * ( cubeUV_m6 - cubeUV_m5 ) / ( cubeUV_r5 - cubeUV_r6 ) + cubeUV_m5;\n\t\t} else {\n\t\t\tmip = - 2.0 * log2( 1.16 * roughness );\t\t}\n\t\treturn mip;\n\t}\n\tvec4 textureCubeUV( sampler2D envMap, vec3 sampleDir, float roughness ) {\n\t\tfloat mip = clamp( roughnessToMip( roughness ), cubeUV_m0, CUBEUV_MAX_MIP );\n\t\tfloat mipF = fract( mip );\n\t\tfloat mipInt = floor( mip );\n\t\tvec3 color0 = bilinearCubeUV( envMap, sampleDir, mipInt );\n\t\tif ( mipF == 0.0 ) {\n\t\t\treturn vec4( color0, 1.0 );\n\t\t} else {\n\t\t\tvec3 color1 = bilinearCubeUV( envMap, sampleDir, mipInt + 1.0 );\n\t\t\treturn vec4( mix( color0, color1, mipF ), 1.0 );\n\t\t}\n\t}\n#endif"; + constructor() { -var defaultnormal_vertex = "vec3 transformedNormal = objectNormal;\n#ifdef USE_INSTANCING\n\tmat3 m = mat3( instanceMatrix );\n\ttransformedNormal /= vec3( dot( m[ 0 ], m[ 0 ] ), dot( m[ 1 ], m[ 1 ] ), dot( m[ 2 ], m[ 2 ] ) );\n\ttransformedNormal = m * transformedNormal;\n#endif\ntransformedNormal = normalMatrix * transformedNormal;\n#ifdef FLIP_SIDED\n\ttransformedNormal = - transformedNormal;\n#endif\n#ifdef USE_TANGENT\n\tvec3 transformedTangent = ( modelViewMatrix * vec4( objectTangent, 0.0 ) ).xyz;\n\t#ifdef FLIP_SIDED\n\t\ttransformedTangent = - transformedTangent;\n\t#endif\n#endif"; + super(); -var displacementmap_pars_vertex = "#ifdef USE_DISPLACEMENTMAP\n\tuniform sampler2D displacementMap;\n\tuniform float displacementScale;\n\tuniform float displacementBias;\n#endif"; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isGroup = true; -var displacementmap_vertex = "#ifdef USE_DISPLACEMENTMAP\n\ttransformed += normalize( objectNormal ) * ( texture2D( displacementMap, vDisplacementMapUv ).x * displacementScale + displacementBias );\n#endif"; + this.type = 'Group'; -var emissivemap_fragment = "#ifdef USE_EMISSIVEMAP\n\tvec4 emissiveColor = texture2D( emissiveMap, vEmissiveMapUv );\n\ttotalEmissiveRadiance *= emissiveColor.rgb;\n#endif"; + } -var emissivemap_pars_fragment = "#ifdef USE_EMISSIVEMAP\n\tuniform sampler2D emissiveMap;\n#endif"; +} -var encodings_fragment = "gl_FragColor = linearToOutputTexel( gl_FragColor );"; +const _moveEvent = { type: 'move' }; -var encodings_pars_fragment = "vec4 LinearToLinear( in vec4 value ) {\n\treturn value;\n}\nvec4 LinearTosRGB( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a );\n}"; +/** + * Class for representing a XR controller with its + * different coordinate systems. + * + * @private + */ +class WebXRController { -var envmap_fragment = "#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvec3 cameraToFrag;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToFrag = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToFrag = normalize( vWorldPosition - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvec3 reflectVec = reflect( cameraToFrag, worldNormal );\n\t\t#else\n\t\t\tvec3 reflectVec = refract( cameraToFrag, worldNormal, refractionRatio );\n\t\t#endif\n\t#else\n\t\tvec3 reflectVec = vReflect;\n\t#endif\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 envColor = textureCube( envMap, vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );\n\t#else\n\t\tvec4 envColor = vec4( 0.0 );\n\t#endif\n\t#ifdef ENVMAP_BLENDING_MULTIPLY\n\t\toutgoingLight = mix( outgoingLight, outgoingLight * envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_MIX )\n\t\toutgoingLight = mix( outgoingLight, envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_ADD )\n\t\toutgoingLight += envColor.xyz * specularStrength * reflectivity;\n\t#endif\n#endif"; + /** + * Constructs a new XR controller. + */ + constructor() { -var envmap_common_pars_fragment = "#ifdef USE_ENVMAP\n\tuniform float envMapIntensity;\n\tuniform float flipEnvMap;\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tuniform samplerCube envMap;\n\t#else\n\t\tuniform sampler2D envMap;\n\t#endif\n\t\n#endif"; + /** + * A group representing the target ray space + * of the XR controller. + * + * @private + * @type {?Group} + * @default null + */ + this._targetRay = null; -var envmap_pars_fragment = "#ifdef USE_ENVMAP\n\tuniform float reflectivity;\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( LAMBERT )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\tvarying vec3 vWorldPosition;\n\t\tuniform float refractionRatio;\n\t#else\n\t\tvarying vec3 vReflect;\n\t#endif\n#endif"; + /** + * A group representing the grip space + * of the XR controller. + * + * @private + * @type {?Group} + * @default null + */ + this._grip = null; -var envmap_pars_vertex = "#ifdef USE_ENVMAP\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( LAMBERT )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\t\n\t\tvarying vec3 vWorldPosition;\n\t#else\n\t\tvarying vec3 vReflect;\n\t\tuniform float refractionRatio;\n\t#endif\n#endif"; + /** + * A group representing the hand space + * of the XR controller. + * + * @private + * @type {?Group} + * @default null + */ + this._hand = null; -var envmap_vertex = "#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvWorldPosition = worldPosition.xyz;\n\t#else\n\t\tvec3 cameraToVertex;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToVertex = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToVertex = normalize( worldPosition.xyz - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvReflect = reflect( cameraToVertex, worldNormal );\n\t\t#else\n\t\t\tvReflect = refract( cameraToVertex, worldNormal, refractionRatio );\n\t\t#endif\n\t#endif\n#endif"; + } -var fog_vertex = "#ifdef USE_FOG\n\tvFogDepth = - mvPosition.z;\n#endif"; + /** + * Returns a group representing the hand space of the XR controller. + * + * @return {Group} A group representing the hand space of the XR controller. + */ + getHandSpace() { -var fog_pars_vertex = "#ifdef USE_FOG\n\tvarying float vFogDepth;\n#endif"; + if ( this._hand === null ) { -var fog_fragment = "#ifdef USE_FOG\n\t#ifdef FOG_EXP2\n\t\tfloat fogFactor = 1.0 - exp( - fogDensity * fogDensity * vFogDepth * vFogDepth );\n\t#else\n\t\tfloat fogFactor = smoothstep( fogNear, fogFar, vFogDepth );\n\t#endif\n\tgl_FragColor.rgb = mix( gl_FragColor.rgb, fogColor, fogFactor );\n#endif"; + this._hand = new Group(); + this._hand.matrixAutoUpdate = false; + this._hand.visible = false; -var fog_pars_fragment = "#ifdef USE_FOG\n\tuniform vec3 fogColor;\n\tvarying float vFogDepth;\n\t#ifdef FOG_EXP2\n\t\tuniform float fogDensity;\n\t#else\n\t\tuniform float fogNear;\n\t\tuniform float fogFar;\n\t#endif\n#endif"; + this._hand.joints = {}; + this._hand.inputState = { pinching: false }; -var gradientmap_pars_fragment = "#ifdef USE_GRADIENTMAP\n\tuniform sampler2D gradientMap;\n#endif\nvec3 getGradientIrradiance( vec3 normal, vec3 lightDirection ) {\n\tfloat dotNL = dot( normal, lightDirection );\n\tvec2 coord = vec2( dotNL * 0.5 + 0.5, 0.0 );\n\t#ifdef USE_GRADIENTMAP\n\t\treturn vec3( texture2D( gradientMap, coord ).r );\n\t#else\n\t\tvec2 fw = fwidth( coord ) * 0.5;\n\t\treturn mix( vec3( 0.7 ), vec3( 1.0 ), smoothstep( 0.7 - fw.x, 0.7 + fw.x, coord.x ) );\n\t#endif\n}"; + } -var lightmap_fragment = "#ifdef USE_LIGHTMAP\n\tvec4 lightMapTexel = texture2D( lightMap, vLightMapUv );\n\tvec3 lightMapIrradiance = lightMapTexel.rgb * lightMapIntensity;\n\treflectedLight.indirectDiffuse += lightMapIrradiance;\n#endif"; + return this._hand; -var lightmap_pars_fragment = "#ifdef USE_LIGHTMAP\n\tuniform sampler2D lightMap;\n\tuniform float lightMapIntensity;\n#endif"; + } -var lights_lambert_fragment = "LambertMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularStrength = specularStrength;"; + /** + * Returns a group representing the target ray space of the XR controller. + * + * @return {Group} A group representing the target ray space of the XR controller. + */ + getTargetRaySpace() { -var lights_lambert_pars_fragment = "varying vec3 vViewPosition;\nstruct LambertMaterial {\n\tvec3 diffuseColor;\n\tfloat specularStrength;\n};\nvoid RE_Direct_Lambert( const in IncidentLight directLight, const in GeometricContext geometry, const in LambertMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Lambert( const in vec3 irradiance, const in GeometricContext geometry, const in LambertMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Lambert\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Lambert"; + if ( this._targetRay === null ) { -var lights_pars_begin = "uniform bool receiveShadow;\nuniform vec3 ambientLightColor;\nuniform vec3 lightProbe[ 9 ];\nvec3 shGetIrradianceAt( in vec3 normal, in vec3 shCoefficients[ 9 ] ) {\n\tfloat x = normal.x, y = normal.y, z = normal.z;\n\tvec3 result = shCoefficients[ 0 ] * 0.886227;\n\tresult += shCoefficients[ 1 ] * 2.0 * 0.511664 * y;\n\tresult += shCoefficients[ 2 ] * 2.0 * 0.511664 * z;\n\tresult += shCoefficients[ 3 ] * 2.0 * 0.511664 * x;\n\tresult += shCoefficients[ 4 ] * 2.0 * 0.429043 * x * y;\n\tresult += shCoefficients[ 5 ] * 2.0 * 0.429043 * y * z;\n\tresult += shCoefficients[ 6 ] * ( 0.743125 * z * z - 0.247708 );\n\tresult += shCoefficients[ 7 ] * 2.0 * 0.429043 * x * z;\n\tresult += shCoefficients[ 8 ] * 0.429043 * ( x * x - y * y );\n\treturn result;\n}\nvec3 getLightProbeIrradiance( const in vec3 lightProbe[ 9 ], const in vec3 normal ) {\n\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\tvec3 irradiance = shGetIrradianceAt( worldNormal, lightProbe );\n\treturn irradiance;\n}\nvec3 getAmbientLightIrradiance( const in vec3 ambientLightColor ) {\n\tvec3 irradiance = ambientLightColor;\n\treturn irradiance;\n}\nfloat getDistanceAttenuation( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) {\n\t#if defined ( LEGACY_LIGHTS )\n\t\tif ( cutoffDistance > 0.0 && decayExponent > 0.0 ) {\n\t\t\treturn pow( saturate( - lightDistance / cutoffDistance + 1.0 ), decayExponent );\n\t\t}\n\t\treturn 1.0;\n\t#else\n\t\tfloat distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );\n\t\tif ( cutoffDistance > 0.0 ) {\n\t\t\tdistanceFalloff *= pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );\n\t\t}\n\t\treturn distanceFalloff;\n\t#endif\n}\nfloat getSpotAttenuation( const in float coneCosine, const in float penumbraCosine, const in float angleCosine ) {\n\treturn smoothstep( coneCosine, penumbraCosine, angleCosine );\n}\n#if NUM_DIR_LIGHTS > 0\n\tstruct DirectionalLight {\n\t\tvec3 direction;\n\t\tvec3 color;\n\t};\n\tuniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ];\n\tvoid getDirectionalLightInfo( const in DirectionalLight directionalLight, const in GeometricContext geometry, out IncidentLight light ) {\n\t\tlight.color = directionalLight.color;\n\t\tlight.direction = directionalLight.direction;\n\t\tlight.visible = true;\n\t}\n#endif\n#if NUM_POINT_LIGHTS > 0\n\tstruct PointLight {\n\t\tvec3 position;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t};\n\tuniform PointLight pointLights[ NUM_POINT_LIGHTS ];\n\tvoid getPointLightInfo( const in PointLight pointLight, const in GeometricContext geometry, out IncidentLight light ) {\n\t\tvec3 lVector = pointLight.position - geometry.position;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat lightDistance = length( lVector );\n\t\tlight.color = pointLight.color;\n\t\tlight.color *= getDistanceAttenuation( lightDistance, pointLight.distance, pointLight.decay );\n\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t}\n#endif\n#if NUM_SPOT_LIGHTS > 0\n\tstruct SpotLight {\n\t\tvec3 position;\n\t\tvec3 direction;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t\tfloat coneCos;\n\t\tfloat penumbraCos;\n\t};\n\tuniform SpotLight spotLights[ NUM_SPOT_LIGHTS ];\n\tvoid getSpotLightInfo( const in SpotLight spotLight, const in GeometricContext geometry, out IncidentLight light ) {\n\t\tvec3 lVector = spotLight.position - geometry.position;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat angleCos = dot( light.direction, spotLight.direction );\n\t\tfloat spotAttenuation = getSpotAttenuation( spotLight.coneCos, spotLight.penumbraCos, angleCos );\n\t\tif ( spotAttenuation > 0.0 ) {\n\t\t\tfloat lightDistance = length( lVector );\n\t\t\tlight.color = spotLight.color * spotAttenuation;\n\t\t\tlight.color *= getDistanceAttenuation( lightDistance, spotLight.distance, spotLight.decay );\n\t\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t\t} else {\n\t\t\tlight.color = vec3( 0.0 );\n\t\t\tlight.visible = false;\n\t\t}\n\t}\n#endif\n#if NUM_RECT_AREA_LIGHTS > 0\n\tstruct RectAreaLight {\n\t\tvec3 color;\n\t\tvec3 position;\n\t\tvec3 halfWidth;\n\t\tvec3 halfHeight;\n\t};\n\tuniform sampler2D ltc_1;\tuniform sampler2D ltc_2;\n\tuniform RectAreaLight rectAreaLights[ NUM_RECT_AREA_LIGHTS ];\n#endif\n#if NUM_HEMI_LIGHTS > 0\n\tstruct HemisphereLight {\n\t\tvec3 direction;\n\t\tvec3 skyColor;\n\t\tvec3 groundColor;\n\t};\n\tuniform HemisphereLight hemisphereLights[ NUM_HEMI_LIGHTS ];\n\tvec3 getHemisphereLightIrradiance( const in HemisphereLight hemiLight, const in vec3 normal ) {\n\t\tfloat dotNL = dot( normal, hemiLight.direction );\n\t\tfloat hemiDiffuseWeight = 0.5 * dotNL + 0.5;\n\t\tvec3 irradiance = mix( hemiLight.groundColor, hemiLight.skyColor, hemiDiffuseWeight );\n\t\treturn irradiance;\n\t}\n#endif"; + this._targetRay = new Group(); + this._targetRay.matrixAutoUpdate = false; + this._targetRay.visible = false; + this._targetRay.hasLinearVelocity = false; + this._targetRay.linearVelocity = new Vector3(); + this._targetRay.hasAngularVelocity = false; + this._targetRay.angularVelocity = new Vector3(); -var envmap_physical_pars_fragment = "#ifdef USE_ENVMAP\n\tvec3 getIBLIrradiance( const in vec3 normal ) {\n\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, worldNormal, 1.0 );\n\t\t\treturn PI * envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n\tvec3 getIBLRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness ) {\n\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\tvec3 reflectVec = reflect( - viewDir, normal );\n\t\t\treflectVec = normalize( mix( reflectVec, normal, roughness * roughness) );\n\t\t\treflectVec = inverseTransformDirection( reflectVec, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, reflectVec, roughness );\n\t\t\treturn envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n\t#ifdef USE_ANISOTROPY\n\t\tvec3 getIBLAnisotropyRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness, const in vec3 bitangent, const in float anisotropy ) {\n\t\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\t\tvec3 bentNormal = cross( bitangent, viewDir );\n\t\t\t\tbentNormal = normalize( cross( bentNormal, bitangent ) );\n\t\t\t\tbentNormal = normalize( mix( bentNormal, normal, pow2( pow2( 1.0 - anisotropy * ( 1.0 - roughness ) ) ) ) );\n\t\t\t\treturn getIBLRadiance( viewDir, bentNormal, roughness );\n\t\t\t#else\n\t\t\t\treturn vec3( 0.0 );\n\t\t\t#endif\n\t\t}\n\t#endif\n#endif"; + } -var lights_toon_fragment = "ToonMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;"; + return this._targetRay; -var lights_toon_pars_fragment = "varying vec3 vViewPosition;\nstruct ToonMaterial {\n\tvec3 diffuseColor;\n};\nvoid RE_Direct_Toon( const in IncidentLight directLight, const in GeometricContext geometry, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\tvec3 irradiance = getGradientIrradiance( geometry.normal, directLight.direction ) * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Toon( const in vec3 irradiance, const in GeometricContext geometry, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Toon\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Toon"; + } -var lights_phong_fragment = "BlinnPhongMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularColor = specular;\nmaterial.specularShininess = shininess;\nmaterial.specularStrength = specularStrength;"; + /** + * Returns a group representing the grip space of the XR controller. + * + * @return {Group} A group representing the grip space of the XR controller. + */ + getGripSpace() { -var lights_phong_pars_fragment = "varying vec3 vViewPosition;\nstruct BlinnPhongMaterial {\n\tvec3 diffuseColor;\n\tvec3 specularColor;\n\tfloat specularShininess;\n\tfloat specularStrength;\n};\nvoid RE_Direct_BlinnPhong( const in IncidentLight directLight, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n\treflectedLight.directSpecular += irradiance * BRDF_BlinnPhong( directLight.direction, geometry.viewDir, geometry.normal, material.specularColor, material.specularShininess ) * material.specularStrength;\n}\nvoid RE_IndirectDiffuse_BlinnPhong( const in vec3 irradiance, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_BlinnPhong\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_BlinnPhong"; + if ( this._grip === null ) { -var lights_physical_fragment = "PhysicalMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb * ( 1.0 - metalnessFactor );\nvec3 dxy = max( abs( dFdx( geometryNormal ) ), abs( dFdy( geometryNormal ) ) );\nfloat geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z );\nmaterial.roughness = max( roughnessFactor, 0.0525 );material.roughness += geometryRoughness;\nmaterial.roughness = min( material.roughness, 1.0 );\n#ifdef IOR\n\tmaterial.ior = ior;\n\t#ifdef USE_SPECULAR\n\t\tfloat specularIntensityFactor = specularIntensity;\n\t\tvec3 specularColorFactor = specularColor;\n\t\t#ifdef USE_SPECULAR_COLORMAP\n\t\t\tspecularColorFactor *= texture2D( specularColorMap, vSpecularColorMapUv ).rgb;\n\t\t#endif\n\t\t#ifdef USE_SPECULAR_INTENSITYMAP\n\t\t\tspecularIntensityFactor *= texture2D( specularIntensityMap, vSpecularIntensityMapUv ).a;\n\t\t#endif\n\t\tmaterial.specularF90 = mix( specularIntensityFactor, 1.0, metalnessFactor );\n\t#else\n\t\tfloat specularIntensityFactor = 1.0;\n\t\tvec3 specularColorFactor = vec3( 1.0 );\n\t\tmaterial.specularF90 = 1.0;\n\t#endif\n\tmaterial.specularColor = mix( min( pow2( ( material.ior - 1.0 ) / ( material.ior + 1.0 ) ) * specularColorFactor, vec3( 1.0 ) ) * specularIntensityFactor, diffuseColor.rgb, metalnessFactor );\n#else\n\tmaterial.specularColor = mix( vec3( 0.04 ), diffuseColor.rgb, metalnessFactor );\n\tmaterial.specularF90 = 1.0;\n#endif\n#ifdef USE_CLEARCOAT\n\tmaterial.clearcoat = clearcoat;\n\tmaterial.clearcoatRoughness = clearcoatRoughness;\n\tmaterial.clearcoatF0 = vec3( 0.04 );\n\tmaterial.clearcoatF90 = 1.0;\n\t#ifdef USE_CLEARCOATMAP\n\t\tmaterial.clearcoat *= texture2D( clearcoatMap, vClearcoatMapUv ).x;\n\t#endif\n\t#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\t\tmaterial.clearcoatRoughness *= texture2D( clearcoatRoughnessMap, vClearcoatRoughnessMapUv ).y;\n\t#endif\n\tmaterial.clearcoat = saturate( material.clearcoat );\tmaterial.clearcoatRoughness = max( material.clearcoatRoughness, 0.0525 );\n\tmaterial.clearcoatRoughness += geometryRoughness;\n\tmaterial.clearcoatRoughness = min( material.clearcoatRoughness, 1.0 );\n#endif\n#ifdef USE_IRIDESCENCE\n\tmaterial.iridescence = iridescence;\n\tmaterial.iridescenceIOR = iridescenceIOR;\n\t#ifdef USE_IRIDESCENCEMAP\n\t\tmaterial.iridescence *= texture2D( iridescenceMap, vIridescenceMapUv ).r;\n\t#endif\n\t#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\t\tmaterial.iridescenceThickness = (iridescenceThicknessMaximum - iridescenceThicknessMinimum) * texture2D( iridescenceThicknessMap, vIridescenceThicknessMapUv ).g + iridescenceThicknessMinimum;\n\t#else\n\t\tmaterial.iridescenceThickness = iridescenceThicknessMaximum;\n\t#endif\n#endif\n#ifdef USE_SHEEN\n\tmaterial.sheenColor = sheenColor;\n\t#ifdef USE_SHEEN_COLORMAP\n\t\tmaterial.sheenColor *= texture2D( sheenColorMap, vSheenColorMapUv ).rgb;\n\t#endif\n\tmaterial.sheenRoughness = clamp( sheenRoughness, 0.07, 1.0 );\n\t#ifdef USE_SHEEN_ROUGHNESSMAP\n\t\tmaterial.sheenRoughness *= texture2D( sheenRoughnessMap, vSheenRoughnessMapUv ).a;\n\t#endif\n#endif\n#ifdef USE_ANISOTROPY\n\t#ifdef USE_ANISOTROPYMAP\n\t\tmat2 anisotropyMat = mat2( anisotropyVector.x, anisotropyVector.y, - anisotropyVector.y, anisotropyVector.x );\n\t\tvec3 anisotropyPolar = texture2D( anisotropyMap, vAnisotropyMapUv ).rgb;\n\t\tvec2 anisotropyV = anisotropyMat * normalize( 2.0 * anisotropyPolar.rg - vec2( 1.0 ) ) * anisotropyPolar.b;\n\t#else\n\t\tvec2 anisotropyV = anisotropyVector;\n\t#endif\n\tmaterial.anisotropy = length( anisotropyV );\n\tanisotropyV /= material.anisotropy;\n\tmaterial.anisotropy = saturate( material.anisotropy );\n\tmaterial.alphaT = mix( pow2( material.roughness ), 1.0, pow2( material.anisotropy ) );\n\tmaterial.anisotropyT = tbn[ 0 ] * anisotropyV.x - tbn[ 1 ] * anisotropyV.y;\n\tmaterial.anisotropyB = tbn[ 1 ] * anisotropyV.x + tbn[ 0 ] * anisotropyV.y;\n#endif"; + this._grip = new Group(); + this._grip.matrixAutoUpdate = false; + this._grip.visible = false; + this._grip.hasLinearVelocity = false; + this._grip.linearVelocity = new Vector3(); + this._grip.hasAngularVelocity = false; + this._grip.angularVelocity = new Vector3(); -var lights_physical_pars_fragment = "struct PhysicalMaterial {\n\tvec3 diffuseColor;\n\tfloat roughness;\n\tvec3 specularColor;\n\tfloat specularF90;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat clearcoat;\n\t\tfloat clearcoatRoughness;\n\t\tvec3 clearcoatF0;\n\t\tfloat clearcoatF90;\n\t#endif\n\t#ifdef USE_IRIDESCENCE\n\t\tfloat iridescence;\n\t\tfloat iridescenceIOR;\n\t\tfloat iridescenceThickness;\n\t\tvec3 iridescenceFresnel;\n\t\tvec3 iridescenceF0;\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tvec3 sheenColor;\n\t\tfloat sheenRoughness;\n\t#endif\n\t#ifdef IOR\n\t\tfloat ior;\n\t#endif\n\t#ifdef USE_TRANSMISSION\n\t\tfloat transmission;\n\t\tfloat transmissionAlpha;\n\t\tfloat thickness;\n\t\tfloat attenuationDistance;\n\t\tvec3 attenuationColor;\n\t#endif\n\t#ifdef USE_ANISOTROPY\n\t\tfloat anisotropy;\n\t\tfloat alphaT;\n\t\tvec3 anisotropyT;\n\t\tvec3 anisotropyB;\n\t#endif\n};\nvec3 clearcoatSpecular = vec3( 0.0 );\nvec3 sheenSpecular = vec3( 0.0 );\nvec3 Schlick_to_F0( const in vec3 f, const in float f90, const in float dotVH ) {\n float x = clamp( 1.0 - dotVH, 0.0, 1.0 );\n float x2 = x * x;\n float x5 = clamp( x * x2 * x2, 0.0, 0.9999 );\n return ( f - vec3( f90 ) * x5 ) / ( 1.0 - x5 );\n}\nfloat V_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const in float dotNV ) {\n\tfloat a2 = pow2( alpha );\n\tfloat gv = dotNL * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );\n\tfloat gl = dotNV * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );\n\treturn 0.5 / max( gv + gl, EPSILON );\n}\nfloat D_GGX( const in float alpha, const in float dotNH ) {\n\tfloat a2 = pow2( alpha );\n\tfloat denom = pow2( dotNH ) * ( a2 - 1.0 ) + 1.0;\n\treturn RECIPROCAL_PI * a2 / pow2( denom );\n}\n#ifdef USE_ANISOTROPY\n\tfloat V_GGX_SmithCorrelated_Anisotropic( const in float alphaT, const in float alphaB, const in float dotTV, const in float dotBV, const in float dotTL, const in float dotBL, const in float dotNV, const in float dotNL ) {\n\t\tfloat gv = dotNL * length( vec3( alphaT * dotTV, alphaB * dotBV, dotNV ) );\n\t\tfloat gl = dotNV * length( vec3( alphaT * dotTL, alphaB * dotBL, dotNL ) );\n\t\tfloat v = 0.5 / ( gv + gl );\n\t\treturn saturate(v);\n\t}\n\tfloat D_GGX_Anisotropic( const in float alphaT, const in float alphaB, const in float dotNH, const in float dotTH, const in float dotBH ) {\n\t\tfloat a2 = alphaT * alphaB;\n\t\thighp vec3 v = vec3( alphaB * dotTH, alphaT * dotBH, a2 * dotNH );\n\t\thighp float v2 = dot( v, v );\n\t\tfloat w2 = a2 / v2;\n\t\treturn RECIPROCAL_PI * a2 * pow2 ( w2 );\n\t}\n#endif\n#ifdef USE_CLEARCOAT\n\tvec3 BRDF_GGX_Clearcoat( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in PhysicalMaterial material) {\n\t\tvec3 f0 = material.clearcoatF0;\n\t\tfloat f90 = material.clearcoatF90;\n\t\tfloat roughness = material.clearcoatRoughness;\n\t\tfloat alpha = pow2( roughness );\n\t\tvec3 halfDir = normalize( lightDir + viewDir );\n\t\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\t\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\t\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\t\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\t\tvec3 F = F_Schlick( f0, f90, dotVH );\n\t\tfloat V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\t\tfloat D = D_GGX( alpha, dotNH );\n\t\treturn F * ( V * D );\n\t}\n#endif\nvec3 BRDF_GGX( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in PhysicalMaterial material ) {\n\tvec3 f0 = material.specularColor;\n\tfloat f90 = material.specularF90;\n\tfloat roughness = material.roughness;\n\tfloat alpha = pow2( roughness );\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\tvec3 F = F_Schlick( f0, f90, dotVH );\n\t#ifdef USE_IRIDESCENCE\n\t\tF = mix( F, material.iridescenceFresnel, material.iridescence );\n\t#endif\n\t#ifdef USE_ANISOTROPY\n\t\tfloat dotTL = dot( material.anisotropyT, lightDir );\n\t\tfloat dotTV = dot( material.anisotropyT, viewDir );\n\t\tfloat dotTH = dot( material.anisotropyT, halfDir );\n\t\tfloat dotBL = dot( material.anisotropyB, lightDir );\n\t\tfloat dotBV = dot( material.anisotropyB, viewDir );\n\t\tfloat dotBH = dot( material.anisotropyB, halfDir );\n\t\tfloat V = V_GGX_SmithCorrelated_Anisotropic( material.alphaT, alpha, dotTV, dotBV, dotTL, dotBL, dotNV, dotNL );\n\t\tfloat D = D_GGX_Anisotropic( material.alphaT, alpha, dotNH, dotTH, dotBH );\n\t#else\n\t\tfloat V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\t\tfloat D = D_GGX( alpha, dotNH );\n\t#endif\n\treturn F * ( V * D );\n}\nvec2 LTC_Uv( const in vec3 N, const in vec3 V, const in float roughness ) {\n\tconst float LUT_SIZE = 64.0;\n\tconst float LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE;\n\tconst float LUT_BIAS = 0.5 / LUT_SIZE;\n\tfloat dotNV = saturate( dot( N, V ) );\n\tvec2 uv = vec2( roughness, sqrt( 1.0 - dotNV ) );\n\tuv = uv * LUT_SCALE + LUT_BIAS;\n\treturn uv;\n}\nfloat LTC_ClippedSphereFormFactor( const in vec3 f ) {\n\tfloat l = length( f );\n\treturn max( ( l * l + f.z ) / ( l + 1.0 ), 0.0 );\n}\nvec3 LTC_EdgeVectorFormFactor( const in vec3 v1, const in vec3 v2 ) {\n\tfloat x = dot( v1, v2 );\n\tfloat y = abs( x );\n\tfloat a = 0.8543985 + ( 0.4965155 + 0.0145206 * y ) * y;\n\tfloat b = 3.4175940 + ( 4.1616724 + y ) * y;\n\tfloat v = a / b;\n\tfloat theta_sintheta = ( x > 0.0 ) ? v : 0.5 * inversesqrt( max( 1.0 - x * x, 1e-7 ) ) - v;\n\treturn cross( v1, v2 ) * theta_sintheta;\n}\nvec3 LTC_Evaluate( const in vec3 N, const in vec3 V, const in vec3 P, const in mat3 mInv, const in vec3 rectCoords[ 4 ] ) {\n\tvec3 v1 = rectCoords[ 1 ] - rectCoords[ 0 ];\n\tvec3 v2 = rectCoords[ 3 ] - rectCoords[ 0 ];\n\tvec3 lightNormal = cross( v1, v2 );\n\tif( dot( lightNormal, P - rectCoords[ 0 ] ) < 0.0 ) return vec3( 0.0 );\n\tvec3 T1, T2;\n\tT1 = normalize( V - N * dot( V, N ) );\n\tT2 = - cross( N, T1 );\n\tmat3 mat = mInv * transposeMat3( mat3( T1, T2, N ) );\n\tvec3 coords[ 4 ];\n\tcoords[ 0 ] = mat * ( rectCoords[ 0 ] - P );\n\tcoords[ 1 ] = mat * ( rectCoords[ 1 ] - P );\n\tcoords[ 2 ] = mat * ( rectCoords[ 2 ] - P );\n\tcoords[ 3 ] = mat * ( rectCoords[ 3 ] - P );\n\tcoords[ 0 ] = normalize( coords[ 0 ] );\n\tcoords[ 1 ] = normalize( coords[ 1 ] );\n\tcoords[ 2 ] = normalize( coords[ 2 ] );\n\tcoords[ 3 ] = normalize( coords[ 3 ] );\n\tvec3 vectorFormFactor = vec3( 0.0 );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 0 ], coords[ 1 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 1 ], coords[ 2 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 2 ], coords[ 3 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 3 ], coords[ 0 ] );\n\tfloat result = LTC_ClippedSphereFormFactor( vectorFormFactor );\n\treturn vec3( result );\n}\n#if defined( USE_SHEEN )\nfloat D_Charlie( float roughness, float dotNH ) {\n\tfloat alpha = pow2( roughness );\n\tfloat invAlpha = 1.0 / alpha;\n\tfloat cos2h = dotNH * dotNH;\n\tfloat sin2h = max( 1.0 - cos2h, 0.0078125 );\n\treturn ( 2.0 + invAlpha ) * pow( sin2h, invAlpha * 0.5 ) / ( 2.0 * PI );\n}\nfloat V_Neubelt( float dotNV, float dotNL ) {\n\treturn saturate( 1.0 / ( 4.0 * ( dotNL + dotNV - dotNL * dotNV ) ) );\n}\nvec3 BRDF_Sheen( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, vec3 sheenColor, const in float sheenRoughness ) {\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat D = D_Charlie( sheenRoughness, dotNH );\n\tfloat V = V_Neubelt( dotNV, dotNL );\n\treturn sheenColor * ( D * V );\n}\n#endif\nfloat IBLSheenBRDF( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat r2 = roughness * roughness;\n\tfloat a = roughness < 0.25 ? -339.2 * r2 + 161.4 * roughness - 25.9 : -8.48 * r2 + 14.3 * roughness - 9.95;\n\tfloat b = roughness < 0.25 ? 44.0 * r2 - 23.7 * roughness + 3.26 : 1.97 * r2 - 3.27 * roughness + 0.72;\n\tfloat DG = exp( a * dotNV + b ) + ( roughness < 0.25 ? 0.0 : 0.1 * ( roughness - 0.25 ) );\n\treturn saturate( DG * RECIPROCAL_PI );\n}\nvec2 DFGApprox( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tconst vec4 c0 = vec4( - 1, - 0.0275, - 0.572, 0.022 );\n\tconst vec4 c1 = vec4( 1, 0.0425, 1.04, - 0.04 );\n\tvec4 r = roughness * c0 + c1;\n\tfloat a004 = min( r.x * r.x, exp2( - 9.28 * dotNV ) ) * r.x + r.y;\n\tvec2 fab = vec2( - 1.04, 1.04 ) * a004 + r.zw;\n\treturn fab;\n}\nvec3 EnvironmentBRDF( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness ) {\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\treturn specularColor * fab.x + specularF90 * fab.y;\n}\n#ifdef USE_IRIDESCENCE\nvoid computeMultiscatteringIridescence( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float iridescence, const in vec3 iridescenceF0, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#else\nvoid computeMultiscattering( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#endif\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\t#ifdef USE_IRIDESCENCE\n\t\tvec3 Fr = mix( specularColor, iridescenceF0, iridescence );\n\t#else\n\t\tvec3 Fr = specularColor;\n\t#endif\n\tvec3 FssEss = Fr * fab.x + specularF90 * fab.y;\n\tfloat Ess = fab.x + fab.y;\n\tfloat Ems = 1.0 - Ess;\n\tvec3 Favg = Fr + ( 1.0 - Fr ) * 0.047619;\tvec3 Fms = FssEss * Favg / ( 1.0 - Ems * Favg );\n\tsingleScatter += FssEss;\n\tmultiScatter += Fms * Ems;\n}\n#if NUM_RECT_AREA_LIGHTS > 0\n\tvoid RE_Direct_RectArea_Physical( const in RectAreaLight rectAreaLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\t\tvec3 normal = geometry.normal;\n\t\tvec3 viewDir = geometry.viewDir;\n\t\tvec3 position = geometry.position;\n\t\tvec3 lightPos = rectAreaLight.position;\n\t\tvec3 halfWidth = rectAreaLight.halfWidth;\n\t\tvec3 halfHeight = rectAreaLight.halfHeight;\n\t\tvec3 lightColor = rectAreaLight.color;\n\t\tfloat roughness = material.roughness;\n\t\tvec3 rectCoords[ 4 ];\n\t\trectCoords[ 0 ] = lightPos + halfWidth - halfHeight;\t\trectCoords[ 1 ] = lightPos - halfWidth - halfHeight;\n\t\trectCoords[ 2 ] = lightPos - halfWidth + halfHeight;\n\t\trectCoords[ 3 ] = lightPos + halfWidth + halfHeight;\n\t\tvec2 uv = LTC_Uv( normal, viewDir, roughness );\n\t\tvec4 t1 = texture2D( ltc_1, uv );\n\t\tvec4 t2 = texture2D( ltc_2, uv );\n\t\tmat3 mInv = mat3(\n\t\t\tvec3( t1.x, 0, t1.y ),\n\t\t\tvec3( 0, 1, 0 ),\n\t\t\tvec3( t1.z, 0, t1.w )\n\t\t);\n\t\tvec3 fresnel = ( material.specularColor * t2.x + ( vec3( 1.0 ) - material.specularColor ) * t2.y );\n\t\treflectedLight.directSpecular += lightColor * fresnel * LTC_Evaluate( normal, viewDir, position, mInv, rectCoords );\n\t\treflectedLight.directDiffuse += lightColor * material.diffuseColor * LTC_Evaluate( normal, viewDir, position, mat3( 1.0 ), rectCoords );\n\t}\n#endif\nvoid RE_Direct_Physical( const in IncidentLight directLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNLcc = saturate( dot( geometry.clearcoatNormal, directLight.direction ) );\n\t\tvec3 ccIrradiance = dotNLcc * directLight.color;\n\t\tclearcoatSpecular += ccIrradiance * BRDF_GGX_Clearcoat( directLight.direction, geometry.viewDir, geometry.clearcoatNormal, material );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tsheenSpecular += irradiance * BRDF_Sheen( directLight.direction, geometry.viewDir, geometry.normal, material.sheenColor, material.sheenRoughness );\n\t#endif\n\treflectedLight.directSpecular += irradiance * BRDF_GGX( directLight.direction, geometry.viewDir, geometry.normal, material );\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Physical( const in vec3 irradiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 irradiance, const in vec3 clearcoatRadiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatSpecular += clearcoatRadiance * EnvironmentBRDF( geometry.clearcoatNormal, geometry.viewDir, material.clearcoatF0, material.clearcoatF90, material.clearcoatRoughness );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tsheenSpecular += irradiance * material.sheenColor * IBLSheenBRDF( geometry.normal, geometry.viewDir, material.sheenRoughness );\n\t#endif\n\tvec3 singleScattering = vec3( 0.0 );\n\tvec3 multiScattering = vec3( 0.0 );\n\tvec3 cosineWeightedIrradiance = irradiance * RECIPROCAL_PI;\n\t#ifdef USE_IRIDESCENCE\n\t\tcomputeMultiscatteringIridescence( geometry.normal, geometry.viewDir, material.specularColor, material.specularF90, material.iridescence, material.iridescenceFresnel, material.roughness, singleScattering, multiScattering );\n\t#else\n\t\tcomputeMultiscattering( geometry.normal, geometry.viewDir, material.specularColor, material.specularF90, material.roughness, singleScattering, multiScattering );\n\t#endif\n\tvec3 totalScattering = singleScattering + multiScattering;\n\tvec3 diffuse = material.diffuseColor * ( 1.0 - max( max( totalScattering.r, totalScattering.g ), totalScattering.b ) );\n\treflectedLight.indirectSpecular += radiance * singleScattering;\n\treflectedLight.indirectSpecular += multiScattering * cosineWeightedIrradiance;\n\treflectedLight.indirectDiffuse += diffuse * cosineWeightedIrradiance;\n}\n#define RE_Direct\t\t\t\tRE_Direct_Physical\n#define RE_Direct_RectArea\t\tRE_Direct_RectArea_Physical\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Physical\n#define RE_IndirectSpecular\t\tRE_IndirectSpecular_Physical\nfloat computeSpecularOcclusion( const in float dotNV, const in float ambientOcclusion, const in float roughness ) {\n\treturn saturate( pow( dotNV + ambientOcclusion, exp2( - 16.0 * roughness - 1.0 ) ) - 1.0 + ambientOcclusion );\n}"; + } -var lights_fragment_begin = "\nGeometricContext geometry;\ngeometry.position = - vViewPosition;\ngeometry.normal = normal;\ngeometry.viewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( vViewPosition );\n#ifdef USE_CLEARCOAT\n\tgeometry.clearcoatNormal = clearcoatNormal;\n#endif\n#ifdef USE_IRIDESCENCE\n\tfloat dotNVi = saturate( dot( normal, geometry.viewDir ) );\n\tif ( material.iridescenceThickness == 0.0 ) {\n\t\tmaterial.iridescence = 0.0;\n\t} else {\n\t\tmaterial.iridescence = saturate( material.iridescence );\n\t}\n\tif ( material.iridescence > 0.0 ) {\n\t\tmaterial.iridescenceFresnel = evalIridescence( 1.0, material.iridescenceIOR, dotNVi, material.iridescenceThickness, material.specularColor );\n\t\tmaterial.iridescenceF0 = Schlick_to_F0( material.iridescenceFresnel, 1.0, dotNVi );\n\t}\n#endif\nIncidentLight directLight;\n#if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct )\n\tPointLight pointLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tpointLight = pointLights[ i ];\n\t\tgetPointLightInfo( pointLight, geometry, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_POINT_LIGHT_SHADOWS )\n\t\tpointLightShadow = pointLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getPointShadow( pointShadowMap[ i ], pointLightShadow.shadowMapSize, pointLightShadow.shadowBias, pointLightShadow.shadowRadius, vPointShadowCoord[ i ], pointLightShadow.shadowCameraNear, pointLightShadow.shadowCameraFar ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct )\n\tSpotLight spotLight;\n\tvec4 spotColor;\n\tvec3 spotLightCoord;\n\tbool inSpotLightMap;\n\t#if defined( USE_SHADOWMAP ) && NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tspotLight = spotLights[ i ];\n\t\tgetSpotLightInfo( spotLight, geometry, directLight );\n\t\t#if ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )\n\t\t#define SPOT_LIGHT_MAP_INDEX UNROLLED_LOOP_INDEX\n\t\t#elif ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\t#define SPOT_LIGHT_MAP_INDEX NUM_SPOT_LIGHT_MAPS\n\t\t#else\n\t\t#define SPOT_LIGHT_MAP_INDEX ( UNROLLED_LOOP_INDEX - NUM_SPOT_LIGHT_SHADOWS + NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )\n\t\t#endif\n\t\t#if ( SPOT_LIGHT_MAP_INDEX < NUM_SPOT_LIGHT_MAPS )\n\t\t\tspotLightCoord = vSpotLightCoord[ i ].xyz / vSpotLightCoord[ i ].w;\n\t\t\tinSpotLightMap = all( lessThan( abs( spotLightCoord * 2. - 1. ), vec3( 1.0 ) ) );\n\t\t\tspotColor = texture2D( spotLightMap[ SPOT_LIGHT_MAP_INDEX ], spotLightCoord.xy );\n\t\t\tdirectLight.color = inSpotLightMap ? directLight.color * spotColor.rgb : directLight.color;\n\t\t#endif\n\t\t#undef SPOT_LIGHT_MAP_INDEX\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\tspotLightShadow = spotLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( spotShadowMap[ i ], spotLightShadow.shadowMapSize, spotLightShadow.shadowBias, spotLightShadow.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct )\n\tDirectionalLight directionalLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tdirectionalLight = directionalLights[ i ];\n\t\tgetDirectionalLightInfo( directionalLight, geometry, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )\n\t\tdirectionalLightShadow = directionalLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea )\n\tRectAreaLight rectAreaLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) {\n\t\trectAreaLight = rectAreaLights[ i ];\n\t\tRE_Direct_RectArea( rectAreaLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if defined( RE_IndirectDiffuse )\n\tvec3 iblIrradiance = vec3( 0.0 );\n\tvec3 irradiance = getAmbientLightIrradiance( ambientLightColor );\n\tirradiance += getLightProbeIrradiance( lightProbe, geometry.normal );\n\t#if ( NUM_HEMI_LIGHTS > 0 )\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n\t\t\tirradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry.normal );\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n#endif\n#if defined( RE_IndirectSpecular )\n\tvec3 radiance = vec3( 0.0 );\n\tvec3 clearcoatRadiance = vec3( 0.0 );\n#endif"; + return this._grip; -var lights_fragment_maps = "#if defined( RE_IndirectDiffuse )\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel = texture2D( lightMap, vLightMapUv );\n\t\tvec3 lightMapIrradiance = lightMapTexel.rgb * lightMapIntensity;\n\t\tirradiance += lightMapIrradiance;\n\t#endif\n\t#if defined( USE_ENVMAP ) && defined( STANDARD ) && defined( ENVMAP_TYPE_CUBE_UV )\n\t\tiblIrradiance += getIBLIrradiance( geometry.normal );\n\t#endif\n#endif\n#if defined( USE_ENVMAP ) && defined( RE_IndirectSpecular )\n\t#ifdef USE_ANISOTROPY\n\t\tradiance += getIBLAnisotropyRadiance( geometry.viewDir, geometry.normal, material.roughness, material.anisotropyB, material.anisotropy );\n\t#else\n\t\tradiance += getIBLRadiance( geometry.viewDir, geometry.normal, material.roughness );\n\t#endif\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatRadiance += getIBLRadiance( geometry.viewDir, geometry.clearcoatNormal, material.clearcoatRoughness );\n\t#endif\n#endif"; + } -var lights_fragment_end = "#if defined( RE_IndirectDiffuse )\n\tRE_IndirectDiffuse( irradiance, geometry, material, reflectedLight );\n#endif\n#if defined( RE_IndirectSpecular )\n\tRE_IndirectSpecular( radiance, iblIrradiance, clearcoatRadiance, geometry, material, reflectedLight );\n#endif"; + /** + * Dispatches the given event to the groups representing + * the different coordinate spaces of the XR controller. + * + * @param {Object} event - The event to dispatch. + * @return {WebXRController} A reference to this instance. + */ + dispatchEvent( event ) { -var logdepthbuf_fragment = "#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tgl_FragDepthEXT = vIsPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\n#endif"; + if ( this._targetRay !== null ) { -var logdepthbuf_pars_fragment = "#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tuniform float logDepthBufFC;\n\tvarying float vFragDepth;\n\tvarying float vIsPerspective;\n#endif"; + this._targetRay.dispatchEvent( event ); -var logdepthbuf_pars_vertex = "#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvarying float vFragDepth;\n\t\tvarying float vIsPerspective;\n\t#else\n\t\tuniform float logDepthBufFC;\n\t#endif\n#endif"; + } -var logdepthbuf_vertex = "#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvFragDepth = 1.0 + gl_Position.w;\n\t\tvIsPerspective = float( isPerspectiveMatrix( projectionMatrix ) );\n\t#else\n\t\tif ( isPerspectiveMatrix( projectionMatrix ) ) {\n\t\t\tgl_Position.z = log2( max( EPSILON, gl_Position.w + 1.0 ) ) * logDepthBufFC - 1.0;\n\t\t\tgl_Position.z *= gl_Position.w;\n\t\t}\n\t#endif\n#endif"; + if ( this._grip !== null ) { -var map_fragment = "#ifdef USE_MAP\n\tdiffuseColor *= texture2D( map, vMapUv );\n#endif"; + this._grip.dispatchEvent( event ); -var map_pars_fragment = "#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif"; + } -var map_particle_fragment = "#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\t#if defined( USE_POINTS_UV )\n\t\tvec2 uv = vUv;\n\t#else\n\t\tvec2 uv = ( uvTransform * vec3( gl_PointCoord.x, 1.0 - gl_PointCoord.y, 1 ) ).xy;\n\t#endif\n#endif\n#ifdef USE_MAP\n\tdiffuseColor *= texture2D( map, uv );\n#endif\n#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, uv ).g;\n#endif"; + if ( this._hand !== null ) { -var map_particle_pars_fragment = "#if defined( USE_POINTS_UV )\n\tvarying vec2 vUv;\n#else\n\t#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\t\tuniform mat3 uvTransform;\n\t#endif\n#endif\n#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif\n#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif"; + this._hand.dispatchEvent( event ); -var metalnessmap_fragment = "float metalnessFactor = metalness;\n#ifdef USE_METALNESSMAP\n\tvec4 texelMetalness = texture2D( metalnessMap, vMetalnessMapUv );\n\tmetalnessFactor *= texelMetalness.b;\n#endif"; + } -var metalnessmap_pars_fragment = "#ifdef USE_METALNESSMAP\n\tuniform sampler2D metalnessMap;\n#endif"; + return this; -var morphcolor_vertex = "#if defined( USE_MORPHCOLORS ) && defined( MORPHTARGETS_TEXTURE )\n\tvColor *= morphTargetBaseInfluence;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t#if defined( USE_COLOR_ALPHA )\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ) * morphTargetInfluences[ i ];\n\t\t#elif defined( USE_COLOR )\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ).rgb * morphTargetInfluences[ i ];\n\t\t#endif\n\t}\n#endif"; + } -var morphnormal_vertex = "#ifdef USE_MORPHNORMALS\n\tobjectNormal *= morphTargetBaseInfluence;\n\t#ifdef MORPHTARGETS_TEXTURE\n\t\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) objectNormal += getMorph( gl_VertexID, i, 1 ).xyz * morphTargetInfluences[ i ];\n\t\t}\n\t#else\n\t\tobjectNormal += morphNormal0 * morphTargetInfluences[ 0 ];\n\t\tobjectNormal += morphNormal1 * morphTargetInfluences[ 1 ];\n\t\tobjectNormal += morphNormal2 * morphTargetInfluences[ 2 ];\n\t\tobjectNormal += morphNormal3 * morphTargetInfluences[ 3 ];\n\t#endif\n#endif"; + /** + * Connects the controller with the given XR input source. + * + * @param {XRInputSource} inputSource - The input source. + * @return {WebXRController} A reference to this instance. + */ + connect( inputSource ) { -var morphtarget_pars_vertex = "#ifdef USE_MORPHTARGETS\n\tuniform float morphTargetBaseInfluence;\n\t#ifdef MORPHTARGETS_TEXTURE\n\t\tuniform float morphTargetInfluences[ MORPHTARGETS_COUNT ];\n\t\tuniform sampler2DArray morphTargetsTexture;\n\t\tuniform ivec2 morphTargetsTextureSize;\n\t\tvec4 getMorph( const in int vertexIndex, const in int morphTargetIndex, const in int offset ) {\n\t\t\tint texelIndex = vertexIndex * MORPHTARGETS_TEXTURE_STRIDE + offset;\n\t\t\tint y = texelIndex / morphTargetsTextureSize.x;\n\t\t\tint x = texelIndex - y * morphTargetsTextureSize.x;\n\t\t\tivec3 morphUV = ivec3( x, y, morphTargetIndex );\n\t\t\treturn texelFetch( morphTargetsTexture, morphUV, 0 );\n\t\t}\n\t#else\n\t\t#ifndef USE_MORPHNORMALS\n\t\t\tuniform float morphTargetInfluences[ 8 ];\n\t\t#else\n\t\t\tuniform float morphTargetInfluences[ 4 ];\n\t\t#endif\n\t#endif\n#endif"; + if ( inputSource && inputSource.hand ) { -var morphtarget_vertex = "#ifdef USE_MORPHTARGETS\n\ttransformed *= morphTargetBaseInfluence;\n\t#ifdef MORPHTARGETS_TEXTURE\n\t\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) transformed += getMorph( gl_VertexID, i, 0 ).xyz * morphTargetInfluences[ i ];\n\t\t}\n\t#else\n\t\ttransformed += morphTarget0 * morphTargetInfluences[ 0 ];\n\t\ttransformed += morphTarget1 * morphTargetInfluences[ 1 ];\n\t\ttransformed += morphTarget2 * morphTargetInfluences[ 2 ];\n\t\ttransformed += morphTarget3 * morphTargetInfluences[ 3 ];\n\t\t#ifndef USE_MORPHNORMALS\n\t\t\ttransformed += morphTarget4 * morphTargetInfluences[ 4 ];\n\t\t\ttransformed += morphTarget5 * morphTargetInfluences[ 5 ];\n\t\t\ttransformed += morphTarget6 * morphTargetInfluences[ 6 ];\n\t\t\ttransformed += morphTarget7 * morphTargetInfluences[ 7 ];\n\t\t#endif\n\t#endif\n#endif"; + const hand = this._hand; -var normal_fragment_begin = "float faceDirection = gl_FrontFacing ? 1.0 : - 1.0;\n#ifdef FLAT_SHADED\n\tvec3 fdx = dFdx( vViewPosition );\n\tvec3 fdy = dFdy( vViewPosition );\n\tvec3 normal = normalize( cross( fdx, fdy ) );\n#else\n\tvec3 normal = normalize( vNormal );\n\t#ifdef DOUBLE_SIDED\n\t\tnormal *= faceDirection;\n\t#endif\n#endif\n#if defined( USE_NORMALMAP_TANGENTSPACE ) || defined( USE_CLEARCOAT_NORMALMAP ) || defined( USE_ANISOTROPY )\n\t#ifdef USE_TANGENT\n\t\tmat3 tbn = mat3( normalize( vTangent ), normalize( vBitangent ), normal );\n\t#else\n\t\tmat3 tbn = getTangentFrame( - vViewPosition, normal, vNormalMapUv );\n\t#endif\n\t#if defined( DOUBLE_SIDED ) && ! defined( FLAT_SHADED )\n\t\ttbn[0] *= faceDirection;\n\t\ttbn[1] *= faceDirection;\n\t#endif\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\t#ifdef USE_TANGENT\n\t\tmat3 tbn2 = mat3( normalize( vTangent ), normalize( vBitangent ), normal );\n\t#else\n\t\tmat3 tbn2 = getTangentFrame( - vViewPosition, normal, vClearcoatNormalMapUv );\n\t#endif\n\t#if defined( DOUBLE_SIDED ) && ! defined( FLAT_SHADED )\n\t\ttbn2[0] *= faceDirection;\n\t\ttbn2[1] *= faceDirection;\n\t#endif\n#endif\nvec3 geometryNormal = normal;"; + if ( hand ) { -var normal_fragment_maps = "#ifdef USE_NORMALMAP_OBJECTSPACE\n\tnormal = texture2D( normalMap, vNormalMapUv ).xyz * 2.0 - 1.0;\n\t#ifdef FLIP_SIDED\n\t\tnormal = - normal;\n\t#endif\n\t#ifdef DOUBLE_SIDED\n\t\tnormal = normal * faceDirection;\n\t#endif\n\tnormal = normalize( normalMatrix * normal );\n#elif defined( USE_NORMALMAP_TANGENTSPACE )\n\tvec3 mapN = texture2D( normalMap, vNormalMapUv ).xyz * 2.0 - 1.0;\n\tmapN.xy *= normalScale;\n\tnormal = normalize( tbn * mapN );\n#elif defined( USE_BUMPMAP )\n\tnormal = perturbNormalArb( - vViewPosition, normal, dHdxy_fwd(), faceDirection );\n#endif"; + for ( const inputjoint of inputSource.hand.values() ) { -var normal_pars_fragment = "#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif"; + // Initialize hand with joints when connected + this._getHandJoint( hand, inputjoint ); -var normal_pars_vertex = "#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif"; + } -var normal_vertex = "#ifndef FLAT_SHADED\n\tvNormal = normalize( transformedNormal );\n\t#ifdef USE_TANGENT\n\t\tvTangent = normalize( transformedTangent );\n\t\tvBitangent = normalize( cross( vNormal, vTangent ) * tangent.w );\n\t#endif\n#endif"; + } -var normalmap_pars_fragment = "#ifdef USE_NORMALMAP\n\tuniform sampler2D normalMap;\n\tuniform vec2 normalScale;\n#endif\n#ifdef USE_NORMALMAP_OBJECTSPACE\n\tuniform mat3 normalMatrix;\n#endif\n#if ! defined ( USE_TANGENT ) && ( defined ( USE_NORMALMAP_TANGENTSPACE ) || defined ( USE_CLEARCOAT_NORMALMAP ) || defined( USE_ANISOTROPY ) )\n\tmat3 getTangentFrame( vec3 eye_pos, vec3 surf_norm, vec2 uv ) {\n\t\tvec3 q0 = dFdx( eye_pos.xyz );\n\t\tvec3 q1 = dFdy( eye_pos.xyz );\n\t\tvec2 st0 = dFdx( uv.st );\n\t\tvec2 st1 = dFdy( uv.st );\n\t\tvec3 N = surf_norm;\n\t\tvec3 q1perp = cross( q1, N );\n\t\tvec3 q0perp = cross( N, q0 );\n\t\tvec3 T = q1perp * st0.x + q0perp * st1.x;\n\t\tvec3 B = q1perp * st0.y + q0perp * st1.y;\n\t\tfloat det = max( dot( T, T ), dot( B, B ) );\n\t\tfloat scale = ( det == 0.0 ) ? 0.0 : inversesqrt( det );\n\t\treturn mat3( T * scale, B * scale, N );\n\t}\n#endif"; + } -var clearcoat_normal_fragment_begin = "#ifdef USE_CLEARCOAT\n\tvec3 clearcoatNormal = geometryNormal;\n#endif"; + this.dispatchEvent( { type: 'connected', data: inputSource } ); -var clearcoat_normal_fragment_maps = "#ifdef USE_CLEARCOAT_NORMALMAP\n\tvec3 clearcoatMapN = texture2D( clearcoatNormalMap, vClearcoatNormalMapUv ).xyz * 2.0 - 1.0;\n\tclearcoatMapN.xy *= clearcoatNormalScale;\n\tclearcoatNormal = normalize( tbn2 * clearcoatMapN );\n#endif"; + return this; -var clearcoat_pars_fragment = "#ifdef USE_CLEARCOATMAP\n\tuniform sampler2D clearcoatMap;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tuniform sampler2D clearcoatNormalMap;\n\tuniform vec2 clearcoatNormalScale;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tuniform sampler2D clearcoatRoughnessMap;\n#endif"; + } -var iridescence_pars_fragment = "#ifdef USE_IRIDESCENCEMAP\n\tuniform sampler2D iridescenceMap;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tuniform sampler2D iridescenceThicknessMap;\n#endif"; + /** + * Disconnects the controller from the given XR input source. + * + * @param {XRInputSource} inputSource - The input source. + * @return {WebXRController} A reference to this instance. + */ + disconnect( inputSource ) { -var output_fragment = "#ifdef OPAQUE\ndiffuseColor.a = 1.0;\n#endif\n#ifdef USE_TRANSMISSION\ndiffuseColor.a *= material.transmissionAlpha;\n#endif\ngl_FragColor = vec4( outgoingLight, diffuseColor.a );"; + this.dispatchEvent( { type: 'disconnected', data: inputSource } ); -var packing = "vec3 packNormalToRGB( const in vec3 normal ) {\n\treturn normalize( normal ) * 0.5 + 0.5;\n}\nvec3 unpackRGBToNormal( const in vec3 rgb ) {\n\treturn 2.0 * rgb.xyz - 1.0;\n}\nconst float PackUpscale = 256. / 255.;const float UnpackDownscale = 255. / 256.;\nconst vec3 PackFactors = vec3( 256. * 256. * 256., 256. * 256., 256. );\nconst vec4 UnpackFactors = UnpackDownscale / vec4( PackFactors, 1. );\nconst float ShiftRight8 = 1. / 256.;\nvec4 packDepthToRGBA( const in float v ) {\n\tvec4 r = vec4( fract( v * PackFactors ), v );\n\tr.yzw -= r.xyz * ShiftRight8;\treturn r * PackUpscale;\n}\nfloat unpackRGBAToDepth( const in vec4 v ) {\n\treturn dot( v, UnpackFactors );\n}\nvec2 packDepthToRG( in highp float v ) {\n\treturn packDepthToRGBA( v ).yx;\n}\nfloat unpackRGToDepth( const in highp vec2 v ) {\n\treturn unpackRGBAToDepth( vec4( v.xy, 0.0, 0.0 ) );\n}\nvec4 pack2HalfToRGBA( vec2 v ) {\n\tvec4 r = vec4( v.x, fract( v.x * 255.0 ), v.y, fract( v.y * 255.0 ) );\n\treturn vec4( r.x - r.y / 255.0, r.y, r.z - r.w / 255.0, r.w );\n}\nvec2 unpackRGBATo2Half( vec4 v ) {\n\treturn vec2( v.x + ( v.y / 255.0 ), v.z + ( v.w / 255.0 ) );\n}\nfloat viewZToOrthographicDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( viewZ + near ) / ( near - far );\n}\nfloat orthographicDepthToViewZ( const in float depth, const in float near, const in float far ) {\n\treturn depth * ( near - far ) - near;\n}\nfloat viewZToPerspectiveDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( ( near + viewZ ) * far ) / ( ( far - near ) * viewZ );\n}\nfloat perspectiveDepthToViewZ( const in float depth, const in float near, const in float far ) {\n\treturn ( near * far ) / ( ( far - near ) * depth - far );\n}"; + if ( this._targetRay !== null ) { -var premultiplied_alpha_fragment = "#ifdef PREMULTIPLIED_ALPHA\n\tgl_FragColor.rgb *= gl_FragColor.a;\n#endif"; + this._targetRay.visible = false; -var project_vertex = "vec4 mvPosition = vec4( transformed, 1.0 );\n#ifdef USE_INSTANCING\n\tmvPosition = instanceMatrix * mvPosition;\n#endif\nmvPosition = modelViewMatrix * mvPosition;\ngl_Position = projectionMatrix * mvPosition;"; + } -var dithering_fragment = "#ifdef DITHERING\n\tgl_FragColor.rgb = dithering( gl_FragColor.rgb );\n#endif"; + if ( this._grip !== null ) { -var dithering_pars_fragment = "#ifdef DITHERING\n\tvec3 dithering( vec3 color ) {\n\t\tfloat grid_position = rand( gl_FragCoord.xy );\n\t\tvec3 dither_shift_RGB = vec3( 0.25 / 255.0, -0.25 / 255.0, 0.25 / 255.0 );\n\t\tdither_shift_RGB = mix( 2.0 * dither_shift_RGB, -2.0 * dither_shift_RGB, grid_position );\n\t\treturn color + dither_shift_RGB;\n\t}\n#endif"; + this._grip.visible = false; -var roughnessmap_fragment = "float roughnessFactor = roughness;\n#ifdef USE_ROUGHNESSMAP\n\tvec4 texelRoughness = texture2D( roughnessMap, vRoughnessMapUv );\n\troughnessFactor *= texelRoughness.g;\n#endif"; + } -var roughnessmap_pars_fragment = "#ifdef USE_ROUGHNESSMAP\n\tuniform sampler2D roughnessMap;\n#endif"; + if ( this._hand !== null ) { -var shadowmap_pars_fragment = "#if NUM_SPOT_LIGHT_COORDS > 0\n\tvarying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ];\n#endif\n#if NUM_SPOT_LIGHT_MAPS > 0\n\tuniform sampler2D spotLightMap[ NUM_SPOT_LIGHT_MAPS ];\n#endif\n#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D directionalShadowMap[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D spotShadowMap[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D pointShadowMap[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n\tfloat texture2DCompare( sampler2D depths, vec2 uv, float compare ) {\n\t\treturn step( compare, unpackRGBAToDepth( texture2D( depths, uv ) ) );\n\t}\n\tvec2 texture2DDistribution( sampler2D shadow, vec2 uv ) {\n\t\treturn unpackRGBATo2Half( texture2D( shadow, uv ) );\n\t}\n\tfloat VSMShadow (sampler2D shadow, vec2 uv, float compare ){\n\t\tfloat occlusion = 1.0;\n\t\tvec2 distribution = texture2DDistribution( shadow, uv );\n\t\tfloat hard_shadow = step( compare , distribution.x );\n\t\tif (hard_shadow != 1.0 ) {\n\t\t\tfloat distance = compare - distribution.x ;\n\t\t\tfloat variance = max( 0.00000, distribution.y * distribution.y );\n\t\t\tfloat softness_probability = variance / (variance + distance * distance );\t\t\tsoftness_probability = clamp( ( softness_probability - 0.3 ) / ( 0.95 - 0.3 ), 0.0, 1.0 );\t\t\tocclusion = clamp( max( hard_shadow, softness_probability ), 0.0, 1.0 );\n\t\t}\n\t\treturn occlusion;\n\t}\n\tfloat getShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord ) {\n\t\tfloat shadow = 1.0;\n\t\tshadowCoord.xyz /= shadowCoord.w;\n\t\tshadowCoord.z += shadowBias;\n\t\tbool inFrustum = shadowCoord.x >= 0.0 && shadowCoord.x <= 1.0 && shadowCoord.y >= 0.0 && shadowCoord.y <= 1.0;\n\t\tbool frustumTest = inFrustum && shadowCoord.z <= 1.0;\n\t\tif ( frustumTest ) {\n\t\t#if defined( SHADOWMAP_TYPE_PCF )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx0 = - texelSize.x * shadowRadius;\n\t\t\tfloat dy0 = - texelSize.y * shadowRadius;\n\t\t\tfloat dx1 = + texelSize.x * shadowRadius;\n\t\t\tfloat dy1 = + texelSize.y * shadowRadius;\n\t\t\tfloat dx2 = dx0 / 2.0;\n\t\t\tfloat dy2 = dy0 / 2.0;\n\t\t\tfloat dx3 = dx1 / 2.0;\n\t\t\tfloat dy3 = dy1 / 2.0;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy1 ), shadowCoord.z )\n\t\t\t) * ( 1.0 / 17.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_PCF_SOFT )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx = texelSize.x;\n\t\t\tfloat dy = texelSize.y;\n\t\t\tvec2 uv = shadowCoord.xy;\n\t\t\tvec2 f = fract( uv * shadowMapSize + 0.5 );\n\t\t\tuv -= f * texelSize;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, uv, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( dx, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( 0.0, dy ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + texelSize, shadowCoord.z ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, dy ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( 0.0, -dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 0.0, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( mix( texture2DCompare( shadowMap, uv + vec2( -dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t f.x ),\n\t\t\t\t\t mix( texture2DCompare( shadowMap, uv + vec2( -dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t f.x ),\n\t\t\t\t\t f.y )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_VSM )\n\t\t\tshadow = VSMShadow( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#else\n\t\t\tshadow = texture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#endif\n\t\t}\n\t\treturn shadow;\n\t}\n\tvec2 cubeToUV( vec3 v, float texelSizeY ) {\n\t\tvec3 absV = abs( v );\n\t\tfloat scaleToCube = 1.0 / max( absV.x, max( absV.y, absV.z ) );\n\t\tabsV *= scaleToCube;\n\t\tv *= scaleToCube * ( 1.0 - 2.0 * texelSizeY );\n\t\tvec2 planar = v.xy;\n\t\tfloat almostATexel = 1.5 * texelSizeY;\n\t\tfloat almostOne = 1.0 - almostATexel;\n\t\tif ( absV.z >= almostOne ) {\n\t\t\tif ( v.z > 0.0 )\n\t\t\t\tplanar.x = 4.0 - v.x;\n\t\t} else if ( absV.x >= almostOne ) {\n\t\t\tfloat signX = sign( v.x );\n\t\t\tplanar.x = v.z * signX + 2.0 * signX;\n\t\t} else if ( absV.y >= almostOne ) {\n\t\t\tfloat signY = sign( v.y );\n\t\t\tplanar.x = v.x + 2.0 * signY + 2.0;\n\t\t\tplanar.y = v.z * signY - 2.0;\n\t\t}\n\t\treturn vec2( 0.125, 0.25 ) * planar + vec2( 0.375, 0.75 );\n\t}\n\tfloat getPointShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord, float shadowCameraNear, float shadowCameraFar ) {\n\t\tvec2 texelSize = vec2( 1.0 ) / ( shadowMapSize * vec2( 4.0, 2.0 ) );\n\t\tvec3 lightToPosition = shadowCoord.xyz;\n\t\tfloat dp = ( length( lightToPosition ) - shadowCameraNear ) / ( shadowCameraFar - shadowCameraNear );\t\tdp += shadowBias;\n\t\tvec3 bd3D = normalize( lightToPosition );\n\t\t#if defined( SHADOWMAP_TYPE_PCF ) || defined( SHADOWMAP_TYPE_PCF_SOFT ) || defined( SHADOWMAP_TYPE_VSM )\n\t\t\tvec2 offset = vec2( - 1, 1 ) * shadowRadius * texelSize.y;\n\t\t\treturn (\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxx, texelSize.y ), dp )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#else\n\t\t\treturn texture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp );\n\t\t#endif\n\t}\n#endif"; + this._hand.visible = false; -var shadowmap_pars_vertex = "#if NUM_SPOT_LIGHT_COORDS > 0\n\tuniform mat4 spotLightMatrix[ NUM_SPOT_LIGHT_COORDS ];\n\tvarying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ];\n#endif\n#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform mat4 directionalShadowMatrix[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform mat4 pointShadowMatrix[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n#endif"; + } -var shadowmap_vertex = "#if ( defined( USE_SHADOWMAP ) && ( NUM_DIR_LIGHT_SHADOWS > 0 || NUM_POINT_LIGHT_SHADOWS > 0 ) ) || ( NUM_SPOT_LIGHT_COORDS > 0 )\n\tvec3 shadowWorldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\tvec4 shadowWorldPosition;\n#endif\n#if defined( USE_SHADOWMAP )\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * directionalLightShadows[ i ].shadowNormalBias, 0 );\n\t\t\tvDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * shadowWorldPosition;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * pointLightShadows[ i ].shadowNormalBias, 0 );\n\t\t\tvPointShadowCoord[ i ] = pointShadowMatrix[ i ] * shadowWorldPosition;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n#endif\n#if NUM_SPOT_LIGHT_COORDS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_COORDS; i ++ ) {\n\t\tshadowWorldPosition = worldPosition;\n\t\t#if ( defined( USE_SHADOWMAP ) && UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\t\tshadowWorldPosition.xyz += shadowWorldNormal * spotLightShadows[ i ].shadowNormalBias;\n\t\t#endif\n\t\tvSpotLightCoord[ i ] = spotLightMatrix[ i ] * shadowWorldPosition;\n\t}\n\t#pragma unroll_loop_end\n#endif"; + return this; -var shadowmask_pars_fragment = "float getShadowMask() {\n\tfloat shadow = 1.0;\n\t#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\tdirectionalLight = directionalLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_SHADOWS; i ++ ) {\n\t\tspotLight = spotLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowBias, spotLight.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\tpointLight = pointLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#endif\n\treturn shadow;\n}"; + } -var skinbase_vertex = "#ifdef USE_SKINNING\n\tmat4 boneMatX = getBoneMatrix( skinIndex.x );\n\tmat4 boneMatY = getBoneMatrix( skinIndex.y );\n\tmat4 boneMatZ = getBoneMatrix( skinIndex.z );\n\tmat4 boneMatW = getBoneMatrix( skinIndex.w );\n#endif"; + /** + * Updates the controller with the given input source, XR frame and reference space. + * This updates the transformations of the groups that represent the different + * coordinate systems of the controller. + * + * @param {XRInputSource} inputSource - The input source. + * @param {XRFrame} frame - The XR frame. + * @param {XRReferenceSpace} referenceSpace - The reference space. + * @return {WebXRController} A reference to this instance. + */ + update( inputSource, frame, referenceSpace ) { -var skinning_pars_vertex = "#ifdef USE_SKINNING\n\tuniform mat4 bindMatrix;\n\tuniform mat4 bindMatrixInverse;\n\tuniform highp sampler2D boneTexture;\n\tuniform int boneTextureSize;\n\tmat4 getBoneMatrix( const in float i ) {\n\t\tfloat j = i * 4.0;\n\t\tfloat x = mod( j, float( boneTextureSize ) );\n\t\tfloat y = floor( j / float( boneTextureSize ) );\n\t\tfloat dx = 1.0 / float( boneTextureSize );\n\t\tfloat dy = 1.0 / float( boneTextureSize );\n\t\ty = dy * ( y + 0.5 );\n\t\tvec4 v1 = texture2D( boneTexture, vec2( dx * ( x + 0.5 ), y ) );\n\t\tvec4 v2 = texture2D( boneTexture, vec2( dx * ( x + 1.5 ), y ) );\n\t\tvec4 v3 = texture2D( boneTexture, vec2( dx * ( x + 2.5 ), y ) );\n\t\tvec4 v4 = texture2D( boneTexture, vec2( dx * ( x + 3.5 ), y ) );\n\t\tmat4 bone = mat4( v1, v2, v3, v4 );\n\t\treturn bone;\n\t}\n#endif"; + let inputPose = null; + let gripPose = null; + let handPose = null; -var skinning_vertex = "#ifdef USE_SKINNING\n\tvec4 skinVertex = bindMatrix * vec4( transformed, 1.0 );\n\tvec4 skinned = vec4( 0.0 );\n\tskinned += boneMatX * skinVertex * skinWeight.x;\n\tskinned += boneMatY * skinVertex * skinWeight.y;\n\tskinned += boneMatZ * skinVertex * skinWeight.z;\n\tskinned += boneMatW * skinVertex * skinWeight.w;\n\ttransformed = ( bindMatrixInverse * skinned ).xyz;\n#endif"; + const targetRay = this._targetRay; + const grip = this._grip; + const hand = this._hand; -var skinnormal_vertex = "#ifdef USE_SKINNING\n\tmat4 skinMatrix = mat4( 0.0 );\n\tskinMatrix += skinWeight.x * boneMatX;\n\tskinMatrix += skinWeight.y * boneMatY;\n\tskinMatrix += skinWeight.z * boneMatZ;\n\tskinMatrix += skinWeight.w * boneMatW;\n\tskinMatrix = bindMatrixInverse * skinMatrix * bindMatrix;\n\tobjectNormal = vec4( skinMatrix * vec4( objectNormal, 0.0 ) ).xyz;\n\t#ifdef USE_TANGENT\n\t\tobjectTangent = vec4( skinMatrix * vec4( objectTangent, 0.0 ) ).xyz;\n\t#endif\n#endif"; + if ( inputSource && frame.session.visibilityState !== 'visible-blurred' ) { -var specularmap_fragment = "float specularStrength;\n#ifdef USE_SPECULARMAP\n\tvec4 texelSpecular = texture2D( specularMap, vSpecularMapUv );\n\tspecularStrength = texelSpecular.r;\n#else\n\tspecularStrength = 1.0;\n#endif"; + if ( hand && inputSource.hand ) { -var specularmap_pars_fragment = "#ifdef USE_SPECULARMAP\n\tuniform sampler2D specularMap;\n#endif"; + handPose = true; -var tonemapping_fragment = "#if defined( TONE_MAPPING )\n\tgl_FragColor.rgb = toneMapping( gl_FragColor.rgb );\n#endif"; + for ( const inputjoint of inputSource.hand.values() ) { -var tonemapping_pars_fragment = "#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\nuniform float toneMappingExposure;\nvec3 LinearToneMapping( vec3 color ) {\n\treturn toneMappingExposure * color;\n}\nvec3 ReinhardToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\treturn saturate( color / ( vec3( 1.0 ) + color ) );\n}\nvec3 OptimizedCineonToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\tcolor = max( vec3( 0.0 ), color - 0.004 );\n\treturn pow( ( color * ( 6.2 * color + 0.5 ) ) / ( color * ( 6.2 * color + 1.7 ) + 0.06 ), vec3( 2.2 ) );\n}\nvec3 RRTAndODTFit( vec3 v ) {\n\tvec3 a = v * ( v + 0.0245786 ) - 0.000090537;\n\tvec3 b = v * ( 0.983729 * v + 0.4329510 ) + 0.238081;\n\treturn a / b;\n}\nvec3 ACESFilmicToneMapping( vec3 color ) {\n\tconst mat3 ACESInputMat = mat3(\n\t\tvec3( 0.59719, 0.07600, 0.02840 ),\t\tvec3( 0.35458, 0.90834, 0.13383 ),\n\t\tvec3( 0.04823, 0.01566, 0.83777 )\n\t);\n\tconst mat3 ACESOutputMat = mat3(\n\t\tvec3( 1.60475, -0.10208, -0.00327 ),\t\tvec3( -0.53108, 1.10813, -0.07276 ),\n\t\tvec3( -0.07367, -0.00605, 1.07602 )\n\t);\n\tcolor *= toneMappingExposure / 0.6;\n\tcolor = ACESInputMat * color;\n\tcolor = RRTAndODTFit( color );\n\tcolor = ACESOutputMat * color;\n\treturn saturate( color );\n}\nvec3 CustomToneMapping( vec3 color ) { return color; }"; + // Update the joints groups with the XRJoint poses + const jointPose = frame.getJointPose( inputjoint, referenceSpace ); -var transmission_fragment = "#ifdef USE_TRANSMISSION\n\tmaterial.transmission = transmission;\n\tmaterial.transmissionAlpha = 1.0;\n\tmaterial.thickness = thickness;\n\tmaterial.attenuationDistance = attenuationDistance;\n\tmaterial.attenuationColor = attenuationColor;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tmaterial.transmission *= texture2D( transmissionMap, vTransmissionMapUv ).r;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tmaterial.thickness *= texture2D( thicknessMap, vThicknessMapUv ).g;\n\t#endif\n\tvec3 pos = vWorldPosition;\n\tvec3 v = normalize( cameraPosition - pos );\n\tvec3 n = inverseTransformDirection( normal, viewMatrix );\n\tvec4 transmitted = getIBLVolumeRefraction(\n\t\tn, v, material.roughness, material.diffuseColor, material.specularColor, material.specularF90,\n\t\tpos, modelMatrix, viewMatrix, projectionMatrix, material.ior, material.thickness,\n\t\tmaterial.attenuationColor, material.attenuationDistance );\n\tmaterial.transmissionAlpha = mix( material.transmissionAlpha, transmitted.a, material.transmission );\n\ttotalDiffuse = mix( totalDiffuse, transmitted.rgb, material.transmission );\n#endif"; + // The transform of this joint will be updated with the joint pose on each frame + const joint = this._getHandJoint( hand, inputjoint ); -var transmission_pars_fragment = "#ifdef USE_TRANSMISSION\n\tuniform float transmission;\n\tuniform float thickness;\n\tuniform float attenuationDistance;\n\tuniform vec3 attenuationColor;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tuniform sampler2D transmissionMap;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tuniform sampler2D thicknessMap;\n\t#endif\n\tuniform vec2 transmissionSamplerSize;\n\tuniform sampler2D transmissionSamplerMap;\n\tuniform mat4 modelMatrix;\n\tuniform mat4 projectionMatrix;\n\tvarying vec3 vWorldPosition;\n\tfloat w0( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * ( a * ( - a + 3.0 ) - 3.0 ) + 1.0 );\n\t}\n\tfloat w1( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * a * ( 3.0 * a - 6.0 ) + 4.0 );\n\t}\n\tfloat w2( float a ){\n\t\treturn ( 1.0 / 6.0 ) * ( a * ( a * ( - 3.0 * a + 3.0 ) + 3.0 ) + 1.0 );\n\t}\n\tfloat w3( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * a * a );\n\t}\n\tfloat g0( float a ) {\n\t\treturn w0( a ) + w1( a );\n\t}\n\tfloat g1( float a ) {\n\t\treturn w2( a ) + w3( a );\n\t}\n\tfloat h0( float a ) {\n\t\treturn - 1.0 + w1( a ) / ( w0( a ) + w1( a ) );\n\t}\n\tfloat h1( float a ) {\n\t\treturn 1.0 + w3( a ) / ( w2( a ) + w3( a ) );\n\t}\n\tvec4 bicubic( sampler2D tex, vec2 uv, vec4 texelSize, float lod ) {\n\t\tuv = uv * texelSize.zw + 0.5;\n\t\tvec2 iuv = floor( uv );\n\t\tvec2 fuv = fract( uv );\n\t\tfloat g0x = g0( fuv.x );\n\t\tfloat g1x = g1( fuv.x );\n\t\tfloat h0x = h0( fuv.x );\n\t\tfloat h1x = h1( fuv.x );\n\t\tfloat h0y = h0( fuv.y );\n\t\tfloat h1y = h1( fuv.y );\n\t\tvec2 p0 = ( vec2( iuv.x + h0x, iuv.y + h0y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p1 = ( vec2( iuv.x + h1x, iuv.y + h0y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p2 = ( vec2( iuv.x + h0x, iuv.y + h1y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p3 = ( vec2( iuv.x + h1x, iuv.y + h1y ) - 0.5 ) * texelSize.xy;\n\t\treturn g0( fuv.y ) * ( g0x * textureLod( tex, p0, lod ) + g1x * textureLod( tex, p1, lod ) ) +\n\t\t\tg1( fuv.y ) * ( g0x * textureLod( tex, p2, lod ) + g1x * textureLod( tex, p3, lod ) );\n\t}\n\tvec4 textureBicubic( sampler2D sampler, vec2 uv, float lod ) {\n\t\tvec2 fLodSize = vec2( textureSize( sampler, int( lod ) ) );\n\t\tvec2 cLodSize = vec2( textureSize( sampler, int( lod + 1.0 ) ) );\n\t\tvec2 fLodSizeInv = 1.0 / fLodSize;\n\t\tvec2 cLodSizeInv = 1.0 / cLodSize;\n\t\tvec4 fSample = bicubic( sampler, uv, vec4( fLodSizeInv, fLodSize ), floor( lod ) );\n\t\tvec4 cSample = bicubic( sampler, uv, vec4( cLodSizeInv, cLodSize ), ceil( lod ) );\n\t\treturn mix( fSample, cSample, fract( lod ) );\n\t}\n\tvec3 getVolumeTransmissionRay( const in vec3 n, const in vec3 v, const in float thickness, const in float ior, const in mat4 modelMatrix ) {\n\t\tvec3 refractionVector = refract( - v, normalize( n ), 1.0 / ior );\n\t\tvec3 modelScale;\n\t\tmodelScale.x = length( vec3( modelMatrix[ 0 ].xyz ) );\n\t\tmodelScale.y = length( vec3( modelMatrix[ 1 ].xyz ) );\n\t\tmodelScale.z = length( vec3( modelMatrix[ 2 ].xyz ) );\n\t\treturn normalize( refractionVector ) * thickness * modelScale;\n\t}\n\tfloat applyIorToRoughness( const in float roughness, const in float ior ) {\n\t\treturn roughness * clamp( ior * 2.0 - 2.0, 0.0, 1.0 );\n\t}\n\tvec4 getTransmissionSample( const in vec2 fragCoord, const in float roughness, const in float ior ) {\n\t\tfloat lod = log2( transmissionSamplerSize.x ) * applyIorToRoughness( roughness, ior );\n\t\treturn textureBicubic( transmissionSamplerMap, fragCoord.xy, lod );\n\t}\n\tvec3 volumeAttenuation( const in float transmissionDistance, const in vec3 attenuationColor, const in float attenuationDistance ) {\n\t\tif ( isinf( attenuationDistance ) ) {\n\t\t\treturn vec3( 1.0 );\n\t\t} else {\n\t\t\tvec3 attenuationCoefficient = -log( attenuationColor ) / attenuationDistance;\n\t\t\tvec3 transmittance = exp( - attenuationCoefficient * transmissionDistance );\t\t\treturn transmittance;\n\t\t}\n\t}\n\tvec4 getIBLVolumeRefraction( const in vec3 n, const in vec3 v, const in float roughness, const in vec3 diffuseColor,\n\t\tconst in vec3 specularColor, const in float specularF90, const in vec3 position, const in mat4 modelMatrix,\n\t\tconst in mat4 viewMatrix, const in mat4 projMatrix, const in float ior, const in float thickness,\n\t\tconst in vec3 attenuationColor, const in float attenuationDistance ) {\n\t\tvec3 transmissionRay = getVolumeTransmissionRay( n, v, thickness, ior, modelMatrix );\n\t\tvec3 refractedRayExit = position + transmissionRay;\n\t\tvec4 ndcPos = projMatrix * viewMatrix * vec4( refractedRayExit, 1.0 );\n\t\tvec2 refractionCoords = ndcPos.xy / ndcPos.w;\n\t\trefractionCoords += 1.0;\n\t\trefractionCoords /= 2.0;\n\t\tvec4 transmittedLight = getTransmissionSample( refractionCoords, roughness, ior );\n\t\tvec3 transmittance = diffuseColor * volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance );\n\t\tvec3 attenuatedColor = transmittance * transmittedLight.rgb;\n\t\tvec3 F = EnvironmentBRDF( n, v, specularColor, specularF90, roughness );\n\t\tfloat transmittanceFactor = ( transmittance.r + transmittance.g + transmittance.b ) / 3.0;\n\t\treturn vec4( ( 1.0 - F ) * attenuatedColor, 1.0 - ( 1.0 - transmittedLight.a ) * transmittanceFactor );\n\t}\n#endif"; + if ( jointPose !== null ) { -var uv_pars_fragment = "#ifdef USE_UV\n\tvarying vec2 vUv;\n#endif\n#ifdef USE_MAP\n\tvarying vec2 vMapUv;\n#endif\n#ifdef USE_ALPHAMAP\n\tvarying vec2 vAlphaMapUv;\n#endif\n#ifdef USE_LIGHTMAP\n\tvarying vec2 vLightMapUv;\n#endif\n#ifdef USE_AOMAP\n\tvarying vec2 vAoMapUv;\n#endif\n#ifdef USE_BUMPMAP\n\tvarying vec2 vBumpMapUv;\n#endif\n#ifdef USE_NORMALMAP\n\tvarying vec2 vNormalMapUv;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tvarying vec2 vEmissiveMapUv;\n#endif\n#ifdef USE_METALNESSMAP\n\tvarying vec2 vMetalnessMapUv;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tvarying vec2 vRoughnessMapUv;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tvarying vec2 vAnisotropyMapUv;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tvarying vec2 vClearcoatMapUv;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tvarying vec2 vClearcoatNormalMapUv;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tvarying vec2 vClearcoatRoughnessMapUv;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tvarying vec2 vIridescenceMapUv;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tvarying vec2 vIridescenceThicknessMapUv;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tvarying vec2 vSheenColorMapUv;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tvarying vec2 vSheenRoughnessMapUv;\n#endif\n#ifdef USE_SPECULARMAP\n\tvarying vec2 vSpecularMapUv;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tvarying vec2 vSpecularColorMapUv;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tvarying vec2 vSpecularIntensityMapUv;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tuniform mat3 transmissionMapTransform;\n\tvarying vec2 vTransmissionMapUv;\n#endif\n#ifdef USE_THICKNESSMAP\n\tuniform mat3 thicknessMapTransform;\n\tvarying vec2 vThicknessMapUv;\n#endif"; + joint.matrix.fromArray( jointPose.transform.matrix ); + joint.matrix.decompose( joint.position, joint.rotation, joint.scale ); + joint.matrixWorldNeedsUpdate = true; + joint.jointRadius = jointPose.radius; -var uv_pars_vertex = "#ifdef USE_UV\n\tvarying vec2 vUv;\n#endif\n#ifdef USE_MAP\n\tuniform mat3 mapTransform;\n\tvarying vec2 vMapUv;\n#endif\n#ifdef USE_ALPHAMAP\n\tuniform mat3 alphaMapTransform;\n\tvarying vec2 vAlphaMapUv;\n#endif\n#ifdef USE_LIGHTMAP\n\tuniform mat3 lightMapTransform;\n\tvarying vec2 vLightMapUv;\n#endif\n#ifdef USE_AOMAP\n\tuniform mat3 aoMapTransform;\n\tvarying vec2 vAoMapUv;\n#endif\n#ifdef USE_BUMPMAP\n\tuniform mat3 bumpMapTransform;\n\tvarying vec2 vBumpMapUv;\n#endif\n#ifdef USE_NORMALMAP\n\tuniform mat3 normalMapTransform;\n\tvarying vec2 vNormalMapUv;\n#endif\n#ifdef USE_DISPLACEMENTMAP\n\tuniform mat3 displacementMapTransform;\n\tvarying vec2 vDisplacementMapUv;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tuniform mat3 emissiveMapTransform;\n\tvarying vec2 vEmissiveMapUv;\n#endif\n#ifdef USE_METALNESSMAP\n\tuniform mat3 metalnessMapTransform;\n\tvarying vec2 vMetalnessMapUv;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tuniform mat3 roughnessMapTransform;\n\tvarying vec2 vRoughnessMapUv;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tvarying vec2 vAnisotropyMapUv;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tuniform mat3 clearcoatMapTransform;\n\tvarying vec2 vClearcoatMapUv;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tuniform mat3 clearcoatNormalMapTransform;\n\tvarying vec2 vClearcoatNormalMapUv;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tuniform mat3 clearcoatRoughnessMapTransform;\n\tvarying vec2 vClearcoatRoughnessMapUv;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tuniform mat3 sheenColorMapTransform;\n\tvarying vec2 vSheenColorMapUv;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tuniform mat3 sheenRoughnessMapTransform;\n\tvarying vec2 vSheenRoughnessMapUv;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tuniform mat3 iridescenceMapTransform;\n\tvarying vec2 vIridescenceMapUv;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tuniform mat3 iridescenceThicknessMapTransform;\n\tvarying vec2 vIridescenceThicknessMapUv;\n#endif\n#ifdef USE_SPECULARMAP\n\tuniform mat3 specularMapTransform;\n\tvarying vec2 vSpecularMapUv;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tuniform mat3 specularColorMapTransform;\n\tvarying vec2 vSpecularColorMapUv;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tuniform mat3 specularIntensityMapTransform;\n\tvarying vec2 vSpecularIntensityMapUv;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tuniform mat3 transmissionMapTransform;\n\tvarying vec2 vTransmissionMapUv;\n#endif\n#ifdef USE_THICKNESSMAP\n\tuniform mat3 thicknessMapTransform;\n\tvarying vec2 vThicknessMapUv;\n#endif"; + } -var uv_vertex = "#ifdef USE_UV\n\tvUv = vec3( uv, 1 ).xy;\n#endif\n#ifdef USE_MAP\n\tvMapUv = ( mapTransform * vec3( MAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ALPHAMAP\n\tvAlphaMapUv = ( alphaMapTransform * vec3( ALPHAMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_LIGHTMAP\n\tvLightMapUv = ( lightMapTransform * vec3( LIGHTMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_AOMAP\n\tvAoMapUv = ( aoMapTransform * vec3( AOMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_BUMPMAP\n\tvBumpMapUv = ( bumpMapTransform * vec3( BUMPMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_NORMALMAP\n\tvNormalMapUv = ( normalMapTransform * vec3( NORMALMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_DISPLACEMENTMAP\n\tvDisplacementMapUv = ( displacementMapTransform * vec3( DISPLACEMENTMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tvEmissiveMapUv = ( emissiveMapTransform * vec3( EMISSIVEMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_METALNESSMAP\n\tvMetalnessMapUv = ( metalnessMapTransform * vec3( METALNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tvRoughnessMapUv = ( roughnessMapTransform * vec3( ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tvAnisotropyMapUv = ( vec3( ANISOTROPYMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tvClearcoatMapUv = ( clearcoatMapTransform * vec3( CLEARCOATMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tvClearcoatNormalMapUv = ( clearcoatNormalMapTransform * vec3( CLEARCOAT_NORMALMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tvClearcoatRoughnessMapUv = ( clearcoatRoughnessMapTransform * vec3( CLEARCOAT_ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tvIridescenceMapUv = ( iridescenceMapTransform * vec3( IRIDESCENCEMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tvIridescenceThicknessMapUv = ( iridescenceThicknessMapTransform * vec3( IRIDESCENCE_THICKNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tvSheenColorMapUv = ( sheenColorMapTransform * vec3( SHEEN_COLORMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tvSheenRoughnessMapUv = ( sheenRoughnessMapTransform * vec3( SHEEN_ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULARMAP\n\tvSpecularMapUv = ( specularMapTransform * vec3( SPECULARMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tvSpecularColorMapUv = ( specularColorMapTransform * vec3( SPECULAR_COLORMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tvSpecularIntensityMapUv = ( specularIntensityMapTransform * vec3( SPECULAR_INTENSITYMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tvTransmissionMapUv = ( transmissionMapTransform * vec3( TRANSMISSIONMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_THICKNESSMAP\n\tvThicknessMapUv = ( thicknessMapTransform * vec3( THICKNESSMAP_UV, 1 ) ).xy;\n#endif"; + joint.visible = jointPose !== null; -var worldpos_vertex = "#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined ( USE_SHADOWMAP ) || defined ( USE_TRANSMISSION ) || NUM_SPOT_LIGHT_COORDS > 0\n\tvec4 worldPosition = vec4( transformed, 1.0 );\n\t#ifdef USE_INSTANCING\n\t\tworldPosition = instanceMatrix * worldPosition;\n\t#endif\n\tworldPosition = modelMatrix * worldPosition;\n#endif"; + } -const vertex$h = "varying vec2 vUv;\nuniform mat3 uvTransform;\nvoid main() {\n\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n\tgl_Position = vec4( position.xy, 1.0, 1.0 );\n}"; + // Custom events -const fragment$h = "uniform sampler2D t2D;\nuniform float backgroundIntensity;\nvarying vec2 vUv;\nvoid main() {\n\tvec4 texColor = texture2D( t2D, vUv );\n\ttexColor.rgb *= backgroundIntensity;\n\tgl_FragColor = texColor;\n\t#include \n\t#include \n}"; + // Check pinchz + const indexTip = hand.joints[ 'index-finger-tip' ]; + const thumbTip = hand.joints[ 'thumb-tip' ]; + const distance = indexTip.position.distanceTo( thumbTip.position ); -const vertex$g = "varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n\tgl_Position.z = gl_Position.w;\n}"; + const distanceToPinch = 0.02; + const threshold = 0.005; -const fragment$g = "#ifdef ENVMAP_TYPE_CUBE\n\tuniform samplerCube envMap;\n#elif defined( ENVMAP_TYPE_CUBE_UV )\n\tuniform sampler2D envMap;\n#endif\nuniform float flipEnvMap;\nuniform float backgroundBlurriness;\nuniform float backgroundIntensity;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 texColor = textureCube( envMap, vec3( flipEnvMap * vWorldDirection.x, vWorldDirection.yz ) );\n\t#elif defined( ENVMAP_TYPE_CUBE_UV )\n\t\tvec4 texColor = textureCubeUV( envMap, vWorldDirection, backgroundBlurriness );\n\t#else\n\t\tvec4 texColor = vec4( 0.0, 0.0, 0.0, 1.0 );\n\t#endif\n\ttexColor.rgb *= backgroundIntensity;\n\tgl_FragColor = texColor;\n\t#include \n\t#include \n}"; + if ( hand.inputState.pinching && distance > distanceToPinch + threshold ) { -const vertex$f = "varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n\tgl_Position.z = gl_Position.w;\n}"; + hand.inputState.pinching = false; + this.dispatchEvent( { + type: 'pinchend', + handedness: inputSource.handedness, + target: this + } ); -const fragment$f = "uniform samplerCube tCube;\nuniform float tFlip;\nuniform float opacity;\nvarying vec3 vWorldDirection;\nvoid main() {\n\tvec4 texColor = textureCube( tCube, vec3( tFlip * vWorldDirection.x, vWorldDirection.yz ) );\n\tgl_FragColor = texColor;\n\tgl_FragColor.a *= opacity;\n\t#include \n\t#include \n}"; + } else if ( ! hand.inputState.pinching && distance <= distanceToPinch - threshold ) { -const vertex$e = "#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvHighPrecisionZW = gl_Position.zw;\n}"; + hand.inputState.pinching = true; + this.dispatchEvent( { + type: 'pinchstart', + handedness: inputSource.handedness, + target: this + } ); -const fragment$e = "#if DEPTH_PACKING == 3200\n\tuniform float opacity;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( 1.0 );\n\t#if DEPTH_PACKING == 3200\n\t\tdiffuseColor.a = opacity;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\tfloat fragCoordZ = 0.5 * vHighPrecisionZW[0] / vHighPrecisionZW[1] + 0.5;\n\t#if DEPTH_PACKING == 3200\n\t\tgl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity );\n\t#elif DEPTH_PACKING == 3201\n\t\tgl_FragColor = packDepthToRGBA( fragCoordZ );\n\t#endif\n}"; + } -const vertex$d = "#define DISTANCE\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvWorldPosition = worldPosition.xyz;\n}"; + } else { -const fragment$d = "#define DISTANCE\nuniform vec3 referencePosition;\nuniform float nearDistance;\nuniform float farDistance;\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main () {\n\t#include \n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include \n\t#include \n\t#include \n\tfloat dist = length( vWorldPosition - referencePosition );\n\tdist = ( dist - nearDistance ) / ( farDistance - nearDistance );\n\tdist = saturate( dist );\n\tgl_FragColor = packDepthToRGBA( dist );\n}"; + if ( grip !== null && inputSource.gripSpace ) { -const vertex$c = "varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n}"; + gripPose = frame.getPose( inputSource.gripSpace, referenceSpace ); -const fragment$c = "uniform sampler2D tEquirect;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvec3 direction = normalize( vWorldDirection );\n\tvec2 sampleUV = equirectUv( direction );\n\tgl_FragColor = texture2D( tEquirect, sampleUV );\n\t#include \n\t#include \n}"; + if ( gripPose !== null ) { -const vertex$b = "uniform float scale;\nattribute float lineDistance;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvLineDistance = scale * lineDistance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; + grip.matrix.fromArray( gripPose.transform.matrix ); + grip.matrix.decompose( grip.position, grip.rotation, grip.scale ); + grip.matrixWorldNeedsUpdate = true; -const fragment$b = "uniform vec3 diffuse;\nuniform float opacity;\nuniform float dashSize;\nuniform float totalSize;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tif ( mod( vLineDistance, totalSize ) > dashSize ) {\n\t\tdiscard;\n\t}\n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; + if ( gripPose.linearVelocity ) { -const vertex$a = "#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#if defined ( USE_ENVMAP ) || defined ( USE_SKINNING )\n\t\t#include \n\t\t#include \n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; + grip.hasLinearVelocity = true; + grip.linearVelocity.copy( gripPose.linearVelocity ); -const fragment$a = "uniform vec3 diffuse;\nuniform float opacity;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel = texture2D( lightMap, vLightMapUv );\n\t\treflectedLight.indirectDiffuse += lightMapTexel.rgb * lightMapIntensity * RECIPROCAL_PI;\n\t#else\n\t\treflectedLight.indirectDiffuse += vec3( 1.0 );\n\t#endif\n\t#include \n\treflectedLight.indirectDiffuse *= diffuseColor.rgb;\n\tvec3 outgoingLight = reflectedLight.indirectDiffuse;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; + } else { -const vertex$9 = "#define LAMBERT\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n\t#include \n}"; + grip.hasLinearVelocity = false; -const fragment$9 = "#define LAMBERT\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; + } -const vertex$8 = "#define MATCAP\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n}"; + if ( gripPose.angularVelocity ) { -const fragment$8 = "#define MATCAP\nuniform vec3 diffuse;\nuniform float opacity;\nuniform sampler2D matcap;\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 viewDir = normalize( vViewPosition );\n\tvec3 x = normalize( vec3( viewDir.z, 0.0, - viewDir.x ) );\n\tvec3 y = cross( viewDir, x );\n\tvec2 uv = vec2( dot( x, normal ), dot( y, normal ) ) * 0.495 + 0.5;\n\t#ifdef USE_MATCAP\n\t\tvec4 matcapColor = texture2D( matcap, uv );\n\t#else\n\t\tvec4 matcapColor = vec4( vec3( mix( 0.2, 0.8, uv.y ) ), 1.0 );\n\t#endif\n\tvec3 outgoingLight = diffuseColor.rgb * matcapColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; + grip.hasAngularVelocity = true; + grip.angularVelocity.copy( gripPose.angularVelocity ); -const vertex$7 = "#define NORMAL\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvarying vec3 vViewPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvViewPosition = - mvPosition.xyz;\n#endif\n}"; + } else { -const fragment$7 = "#define NORMAL\nuniform float opacity;\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvarying vec3 vViewPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\tgl_FragColor = vec4( packNormalToRGB( normal ), opacity );\n\t#ifdef OPAQUE\n\t\tgl_FragColor.a = 1.0;\n\t#endif\n}"; + grip.hasAngularVelocity = false; -const vertex$6 = "#define PHONG\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n\t#include \n}"; + } -const fragment$6 = "#define PHONG\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform vec3 specular;\nuniform float shininess;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; + } -const vertex$5 = "#define STANDARD\nvarying vec3 vViewPosition;\n#ifdef USE_TRANSMISSION\n\tvarying vec3 vWorldPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n#ifdef USE_TRANSMISSION\n\tvWorldPosition = worldPosition.xyz;\n#endif\n}"; + } -const fragment$5 = "#define STANDARD\n#ifdef PHYSICAL\n\t#define IOR\n\t#define USE_SPECULAR\n#endif\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float roughness;\nuniform float metalness;\nuniform float opacity;\n#ifdef IOR\n\tuniform float ior;\n#endif\n#ifdef USE_SPECULAR\n\tuniform float specularIntensity;\n\tuniform vec3 specularColor;\n\t#ifdef USE_SPECULAR_COLORMAP\n\t\tuniform sampler2D specularColorMap;\n\t#endif\n\t#ifdef USE_SPECULAR_INTENSITYMAP\n\t\tuniform sampler2D specularIntensityMap;\n\t#endif\n#endif\n#ifdef USE_CLEARCOAT\n\tuniform float clearcoat;\n\tuniform float clearcoatRoughness;\n#endif\n#ifdef USE_IRIDESCENCE\n\tuniform float iridescence;\n\tuniform float iridescenceIOR;\n\tuniform float iridescenceThicknessMinimum;\n\tuniform float iridescenceThicknessMaximum;\n#endif\n#ifdef USE_SHEEN\n\tuniform vec3 sheenColor;\n\tuniform float sheenRoughness;\n\t#ifdef USE_SHEEN_COLORMAP\n\t\tuniform sampler2D sheenColorMap;\n\t#endif\n\t#ifdef USE_SHEEN_ROUGHNESSMAP\n\t\tuniform sampler2D sheenRoughnessMap;\n\t#endif\n#endif\n#ifdef USE_ANISOTROPY\n\tuniform vec2 anisotropyVector;\n\t#ifdef USE_ANISOTROPYMAP\n\t\tuniform sampler2D anisotropyMap;\n\t#endif\n#endif\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 totalDiffuse = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse;\n\tvec3 totalSpecular = reflectedLight.directSpecular + reflectedLight.indirectSpecular;\n\t#include \n\tvec3 outgoingLight = totalDiffuse + totalSpecular + totalEmissiveRadiance;\n\t#ifdef USE_SHEEN\n\t\tfloat sheenEnergyComp = 1.0 - 0.157 * max3( material.sheenColor );\n\t\toutgoingLight = outgoingLight * sheenEnergyComp + sheenSpecular;\n\t#endif\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNVcc = saturate( dot( geometry.clearcoatNormal, geometry.viewDir ) );\n\t\tvec3 Fcc = F_Schlick( material.clearcoatF0, material.clearcoatF90, dotNVcc );\n\t\toutgoingLight = outgoingLight * ( 1.0 - material.clearcoat * Fcc ) + clearcoatSpecular * material.clearcoat;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; + } -const vertex$4 = "#define TOON\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n}"; + if ( targetRay !== null ) { -const fragment$4 = "#define TOON\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; + inputPose = frame.getPose( inputSource.targetRaySpace, referenceSpace ); -const vertex$3 = "uniform float size;\nuniform float scale;\n#include \n#include \n#include \n#include \n#include \n#include \n#ifdef USE_POINTS_UV\n\tvarying vec2 vUv;\n\tuniform mat3 uvTransform;\n#endif\nvoid main() {\n\t#ifdef USE_POINTS_UV\n\t\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tgl_PointSize = size;\n\t#ifdef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) gl_PointSize *= ( scale / - mvPosition.z );\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n}"; + // Some runtimes (namely Vive Cosmos with Vive OpenXR Runtime) have only grip space and ray space is equal to it + if ( inputPose === null && gripPose !== null ) { -const fragment$3 = "uniform vec3 diffuse;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; + inputPose = gripPose; -const vertex$2 = "#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; + } -const fragment$2 = "uniform vec3 color;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tgl_FragColor = vec4( color, opacity * ( 1.0 - getShadowMask() ) );\n\t#include \n\t#include \n\t#include \n}"; + if ( inputPose !== null ) { -const vertex$1 = "uniform float rotation;\nuniform vec2 center;\n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 mvPosition = modelViewMatrix * vec4( 0.0, 0.0, 0.0, 1.0 );\n\tvec2 scale;\n\tscale.x = length( vec3( modelMatrix[ 0 ].x, modelMatrix[ 0 ].y, modelMatrix[ 0 ].z ) );\n\tscale.y = length( vec3( modelMatrix[ 1 ].x, modelMatrix[ 1 ].y, modelMatrix[ 1 ].z ) );\n\t#ifndef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) scale *= - mvPosition.z;\n\t#endif\n\tvec2 alignedPosition = ( position.xy - ( center - vec2( 0.5 ) ) ) * scale;\n\tvec2 rotatedPosition;\n\trotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;\n\trotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;\n\tmvPosition.xy += rotatedPosition;\n\tgl_Position = projectionMatrix * mvPosition;\n\t#include \n\t#include \n\t#include \n}"; + targetRay.matrix.fromArray( inputPose.transform.matrix ); + targetRay.matrix.decompose( targetRay.position, targetRay.rotation, targetRay.scale ); + targetRay.matrixWorldNeedsUpdate = true; -const fragment$1 = "uniform vec3 diffuse;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n}"; + if ( inputPose.linearVelocity ) { -const ShaderChunk = { - alphamap_fragment: alphamap_fragment, - alphamap_pars_fragment: alphamap_pars_fragment, - alphatest_fragment: alphatest_fragment, - alphatest_pars_fragment: alphatest_pars_fragment, - aomap_fragment: aomap_fragment, - aomap_pars_fragment: aomap_pars_fragment, - begin_vertex: begin_vertex, - beginnormal_vertex: beginnormal_vertex, - bsdfs: bsdfs, - iridescence_fragment: iridescence_fragment, - bumpmap_pars_fragment: bumpmap_pars_fragment, - clipping_planes_fragment: clipping_planes_fragment, - clipping_planes_pars_fragment: clipping_planes_pars_fragment, - clipping_planes_pars_vertex: clipping_planes_pars_vertex, - clipping_planes_vertex: clipping_planes_vertex, - color_fragment: color_fragment, - color_pars_fragment: color_pars_fragment, - color_pars_vertex: color_pars_vertex, - color_vertex: color_vertex, - common: common, - cube_uv_reflection_fragment: cube_uv_reflection_fragment, - defaultnormal_vertex: defaultnormal_vertex, - displacementmap_pars_vertex: displacementmap_pars_vertex, - displacementmap_vertex: displacementmap_vertex, - emissivemap_fragment: emissivemap_fragment, - emissivemap_pars_fragment: emissivemap_pars_fragment, - encodings_fragment: encodings_fragment, - encodings_pars_fragment: encodings_pars_fragment, - envmap_fragment: envmap_fragment, - envmap_common_pars_fragment: envmap_common_pars_fragment, - envmap_pars_fragment: envmap_pars_fragment, - envmap_pars_vertex: envmap_pars_vertex, - envmap_physical_pars_fragment: envmap_physical_pars_fragment, - envmap_vertex: envmap_vertex, - fog_vertex: fog_vertex, - fog_pars_vertex: fog_pars_vertex, - fog_fragment: fog_fragment, - fog_pars_fragment: fog_pars_fragment, - gradientmap_pars_fragment: gradientmap_pars_fragment, - lightmap_fragment: lightmap_fragment, - lightmap_pars_fragment: lightmap_pars_fragment, - lights_lambert_fragment: lights_lambert_fragment, - lights_lambert_pars_fragment: lights_lambert_pars_fragment, - lights_pars_begin: lights_pars_begin, - lights_toon_fragment: lights_toon_fragment, - lights_toon_pars_fragment: lights_toon_pars_fragment, - lights_phong_fragment: lights_phong_fragment, - lights_phong_pars_fragment: lights_phong_pars_fragment, - lights_physical_fragment: lights_physical_fragment, - lights_physical_pars_fragment: lights_physical_pars_fragment, - lights_fragment_begin: lights_fragment_begin, - lights_fragment_maps: lights_fragment_maps, - lights_fragment_end: lights_fragment_end, - logdepthbuf_fragment: logdepthbuf_fragment, - logdepthbuf_pars_fragment: logdepthbuf_pars_fragment, - logdepthbuf_pars_vertex: logdepthbuf_pars_vertex, - logdepthbuf_vertex: logdepthbuf_vertex, - map_fragment: map_fragment, - map_pars_fragment: map_pars_fragment, - map_particle_fragment: map_particle_fragment, - map_particle_pars_fragment: map_particle_pars_fragment, - metalnessmap_fragment: metalnessmap_fragment, - metalnessmap_pars_fragment: metalnessmap_pars_fragment, - morphcolor_vertex: morphcolor_vertex, - morphnormal_vertex: morphnormal_vertex, - morphtarget_pars_vertex: morphtarget_pars_vertex, - morphtarget_vertex: morphtarget_vertex, - normal_fragment_begin: normal_fragment_begin, - normal_fragment_maps: normal_fragment_maps, - normal_pars_fragment: normal_pars_fragment, - normal_pars_vertex: normal_pars_vertex, - normal_vertex: normal_vertex, - normalmap_pars_fragment: normalmap_pars_fragment, - clearcoat_normal_fragment_begin: clearcoat_normal_fragment_begin, - clearcoat_normal_fragment_maps: clearcoat_normal_fragment_maps, - clearcoat_pars_fragment: clearcoat_pars_fragment, - iridescence_pars_fragment: iridescence_pars_fragment, - output_fragment: output_fragment, - packing: packing, - premultiplied_alpha_fragment: premultiplied_alpha_fragment, - project_vertex: project_vertex, - dithering_fragment: dithering_fragment, - dithering_pars_fragment: dithering_pars_fragment, - roughnessmap_fragment: roughnessmap_fragment, - roughnessmap_pars_fragment: roughnessmap_pars_fragment, - shadowmap_pars_fragment: shadowmap_pars_fragment, - shadowmap_pars_vertex: shadowmap_pars_vertex, - shadowmap_vertex: shadowmap_vertex, - shadowmask_pars_fragment: shadowmask_pars_fragment, - skinbase_vertex: skinbase_vertex, - skinning_pars_vertex: skinning_pars_vertex, - skinning_vertex: skinning_vertex, - skinnormal_vertex: skinnormal_vertex, - specularmap_fragment: specularmap_fragment, - specularmap_pars_fragment: specularmap_pars_fragment, - tonemapping_fragment: tonemapping_fragment, - tonemapping_pars_fragment: tonemapping_pars_fragment, - transmission_fragment: transmission_fragment, - transmission_pars_fragment: transmission_pars_fragment, - uv_pars_fragment: uv_pars_fragment, - uv_pars_vertex: uv_pars_vertex, - uv_vertex: uv_vertex, - worldpos_vertex: worldpos_vertex, + targetRay.hasLinearVelocity = true; + targetRay.linearVelocity.copy( inputPose.linearVelocity ); + + } else { + + targetRay.hasLinearVelocity = false; + + } + + if ( inputPose.angularVelocity ) { + + targetRay.hasAngularVelocity = true; + targetRay.angularVelocity.copy( inputPose.angularVelocity ); + + } else { + + targetRay.hasAngularVelocity = false; + + } + + this.dispatchEvent( _moveEvent ); + + } + + } + + + } + + if ( targetRay !== null ) { + + targetRay.visible = ( inputPose !== null ); + + } + + if ( grip !== null ) { + + grip.visible = ( gripPose !== null ); + + } + + if ( hand !== null ) { + + hand.visible = ( handPose !== null ); + + } + + return this; + + } + + /** + * Returns a group representing the hand joint for the given input joint. + * + * @private + * @param {Group} hand - The group representing the hand space. + * @param {XRJointSpace} inputjoint - The hand joint data. + * @return {Group} A group representing the hand joint for the given input joint. + */ + _getHandJoint( hand, inputjoint ) { + + if ( hand.joints[ inputjoint.jointName ] === undefined ) { + + const joint = new Group(); + joint.matrixAutoUpdate = false; + joint.visible = false; + hand.joints[ inputjoint.jointName ] = joint; + + hand.add( joint ); + + } + + return hand.joints[ inputjoint.jointName ]; + + } + +} + +/** + * This class can be used to define an exponential squared fog, + * which gives a clear view near the camera and a faster than exponentially + * densening fog farther from the camera. + * + * ```js + * const scene = new THREE.Scene(); + * scene.fog = new THREE.FogExp2( 0xcccccc, 0.002 ); + * ``` + */ +class FogExp2 { + + /** + * Constructs a new fog. + * + * @param {number|Color} color - The fog's color. + * @param {number} [density=0.00025] - Defines how fast the fog will grow dense. + */ + constructor( color, density = 0.00025 ) { + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isFogExp2 = true; + + /** + * The name of the fog. + * + * @type {string} + */ + this.name = ''; + + /** + * The fog's color. + * + * @type {Color} + */ + this.color = new Color( color ); + + /** + * Defines how fast the fog will grow dense. + * + * @type {number} + * @default 0.00025 + */ + this.density = density; + + } + + /** + * Returns a new fog with copied values from this instance. + * + * @return {FogExp2} A clone of this instance. + */ + clone() { + + return new FogExp2( this.color, this.density ); + + } + + /** + * Serializes the fog into JSON. + * + * @param {?(Object|string)} meta - An optional value holding meta information about the serialization. + * @return {Object} A JSON object representing the serialized fog + */ + toJSON( /* meta */ ) { + + return { + type: 'FogExp2', + name: this.name, + color: this.color.getHex(), + density: this.density + }; + + } + +} + +/** + * This class can be used to define a linear fog that grows linearly denser + * with the distance. + * + * ```js + * const scene = new THREE.Scene(); + * scene.fog = new THREE.Fog( 0xcccccc, 10, 15 ); + * ``` + */ +class Fog { + + /** + * Constructs a new fog. + * + * @param {number|Color} color - The fog's color. + * @param {number} [near=1] - The minimum distance to start applying fog. + * @param {number} [far=1000] - The maximum distance at which fog stops being calculated and applied. + */ + constructor( color, near = 1, far = 1000 ) { + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isFog = true; + + /** + * The name of the fog. + * + * @type {string} + */ + this.name = ''; + + /** + * The fog's color. + * + * @type {Color} + */ + this.color = new Color( color ); + + /** + * The minimum distance to start applying fog. Objects that are less than + * `near` units from the active camera won't be affected by fog. + * + * @type {number} + * @default 1 + */ + this.near = near; + + /** + * The maximum distance at which fog stops being calculated and applied. + * Objects that are more than `far` units away from the active camera won't + * be affected by fog. + * + * @type {number} + * @default 1000 + */ + this.far = far; + + } + + /** + * Returns a new fog with copied values from this instance. + * + * @return {Fog} A clone of this instance. + */ + clone() { + + return new Fog( this.color, this.near, this.far ); + + } + + /** + * Serializes the fog into JSON. + * + * @param {?(Object|string)} meta - An optional value holding meta information about the serialization. + * @return {Object} A JSON object representing the serialized fog + */ + toJSON( /* meta */ ) { + + return { + type: 'Fog', + name: this.name, + color: this.color.getHex(), + near: this.near, + far: this.far + }; + + } + +} + +/** + * Scenes allow you to set up what is to be rendered and where by three.js. + * This is where you place 3D objects like meshes, lines or lights. + * + * @augments Object3D + */ +class Scene extends Object3D { + + /** + * Constructs a new scene. + */ + constructor() { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isScene = true; + + this.type = 'Scene'; + + /** + * Defines the background of the scene. Valid inputs are: + * + * - A color for defining a uniform colored background. + * - A texture for defining a (flat) textured background. + * - Cube textures or equirectangular textures for defining a skybox. + * + * @type {?(Color|Texture)} + * @default null + */ + this.background = null; + + /** + * Sets the environment map for all physical materials in the scene. However, + * it's not possible to overwrite an existing texture assigned to the `envMap` + * material property. + * + * @type {?Texture} + * @default null + */ + this.environment = null; + + /** + * A fog instance defining the type of fog that affects everything + * rendered in the scene. + * + * @type {?(Fog|FogExp2)} + * @default null + */ + this.fog = null; + + /** + * Sets the blurriness of the background. Only influences environment maps + * assigned to {@link Scene#background}. Valid input is a float between `0` + * and `1`. + * + * @type {number} + * @default 0 + */ + this.backgroundBlurriness = 0; + + /** + * Attenuates the color of the background. Only applies to background textures. + * + * @type {number} + * @default 1 + */ + this.backgroundIntensity = 1; + + /** + * The rotation of the background in radians. Only influences environment maps + * assigned to {@link Scene#background}. + * + * @type {Euler} + * @default (0,0,0) + */ + this.backgroundRotation = new Euler(); + + /** + * Attenuates the color of the environment. Only influences environment maps + * assigned to {@link Scene#environment}. + * + * @type {number} + * @default 1 + */ + this.environmentIntensity = 1; + + /** + * The rotation of the environment map in radians. Only influences physical materials + * in the scene when {@link Scene#environment} is used. + * + * @type {Euler} + * @default (0,0,0) + */ + this.environmentRotation = new Euler(); + + /** + * Forces everything in the scene to be rendered with the defined material. It is possible + * to exclude materials from override by setting {@link Material#allowOverride} to `false`. + * + * @type {?Material} + * @default null + */ + this.overrideMaterial = null; + + if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) { + + __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'observe', { detail: this } ) ); + + } + + } + + copy( source, recursive ) { + + super.copy( source, recursive ); + + if ( source.background !== null ) this.background = source.background.clone(); + if ( source.environment !== null ) this.environment = source.environment.clone(); + if ( source.fog !== null ) this.fog = source.fog.clone(); + + this.backgroundBlurriness = source.backgroundBlurriness; + this.backgroundIntensity = source.backgroundIntensity; + this.backgroundRotation.copy( source.backgroundRotation ); + + this.environmentIntensity = source.environmentIntensity; + this.environmentRotation.copy( source.environmentRotation ); + + if ( source.overrideMaterial !== null ) this.overrideMaterial = source.overrideMaterial.clone(); + + this.matrixAutoUpdate = source.matrixAutoUpdate; + + return this; + + } + + toJSON( meta ) { + + const data = super.toJSON( meta ); + + if ( this.fog !== null ) data.object.fog = this.fog.toJSON(); + + if ( this.backgroundBlurriness > 0 ) data.object.backgroundBlurriness = this.backgroundBlurriness; + if ( this.backgroundIntensity !== 1 ) data.object.backgroundIntensity = this.backgroundIntensity; + data.object.backgroundRotation = this.backgroundRotation.toArray(); + + if ( this.environmentIntensity !== 1 ) data.object.environmentIntensity = this.environmentIntensity; + data.object.environmentRotation = this.environmentRotation.toArray(); + + return data; + + } + +} + +/** + * "Interleaved" means that multiple attributes, possibly of different types, + * (e.g., position, normal, uv, color) are packed into a single array buffer. + * + * An introduction into interleaved arrays can be found here: [Interleaved array basics]{@link https://blog.tojicode.com/2011/05/interleaved-array-basics.html} + */ +class InterleavedBuffer { + + /** + * Constructs a new interleaved buffer. + * + * @param {TypedArray} array - A typed array with a shared buffer storing attribute data. + * @param {number} stride - The number of typed-array elements per vertex. + */ + constructor( array, stride ) { + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isInterleavedBuffer = true; + + /** + * A typed array with a shared buffer storing attribute data. + * + * @type {TypedArray} + */ + this.array = array; + + /** + * The number of typed-array elements per vertex. + * + * @type {number} + */ + this.stride = stride; + + /** + * The total number of elements in the array + * + * @type {number} + * @readonly + */ + this.count = array !== undefined ? array.length / stride : 0; + + /** + * Defines the intended usage pattern of the data store for optimization purposes. + * + * Note: After the initial use of a buffer, its usage cannot be changed. Instead, + * instantiate a new one and set the desired usage before the next render. + * + * @type {(StaticDrawUsage|DynamicDrawUsage|StreamDrawUsage|StaticReadUsage|DynamicReadUsage|StreamReadUsage|StaticCopyUsage|DynamicCopyUsage|StreamCopyUsage)} + * @default StaticDrawUsage + */ + this.usage = StaticDrawUsage; + + /** + * This can be used to only update some components of stored vectors (for example, just the + * component related to color). Use the `addUpdateRange()` function to add ranges to this array. + * + * @type {Array} + */ + this.updateRanges = []; + + /** + * A version number, incremented every time the `needsUpdate` is set to `true`. + * + * @type {number} + */ + this.version = 0; + + /** + * The UUID of the interleaved buffer. + * + * @type {string} + * @readonly + */ + this.uuid = generateUUID(); + + } + + /** + * A callback function that is executed after the renderer has transferred the attribute array + * data to the GPU. + */ + onUploadCallback() {} + + /** + * Flag to indicate that this attribute has changed and should be re-sent to + * the GPU. Set this to `true` when you modify the value of the array. + * + * @type {number} + * @default false + * @param {boolean} value + */ + set needsUpdate( value ) { + + if ( value === true ) this.version ++; + + } + + /** + * Sets the usage of this interleaved buffer. + * + * @param {(StaticDrawUsage|DynamicDrawUsage|StreamDrawUsage|StaticReadUsage|DynamicReadUsage|StreamReadUsage|StaticCopyUsage|DynamicCopyUsage|StreamCopyUsage)} value - The usage to set. + * @return {InterleavedBuffer} A reference to this interleaved buffer. + */ + setUsage( value ) { + + this.usage = value; + + return this; + + } + + /** + * Adds a range of data in the data array to be updated on the GPU. + * + * @param {number} start - Position at which to start update. + * @param {number} count - The number of components to update. + */ + addUpdateRange( start, count ) { + + this.updateRanges.push( { start, count } ); + + } + + /** + * Clears the update ranges. + */ + clearUpdateRanges() { + + this.updateRanges.length = 0; + + } + + /** + * Copies the values of the given interleaved buffer to this instance. + * + * @param {InterleavedBuffer} source - The interleaved buffer to copy. + * @return {InterleavedBuffer} A reference to this instance. + */ + copy( source ) { + + this.array = new source.array.constructor( source.array ); + this.count = source.count; + this.stride = source.stride; + this.usage = source.usage; + + return this; + + } + + /** + * Copies a vector from the given interleaved buffer to this one. The start + * and destination position in the attribute buffers are represented by the + * given indices. + * + * @param {number} index1 - The destination index into this interleaved buffer. + * @param {InterleavedBuffer} interleavedBuffer - The interleaved buffer to copy from. + * @param {number} index2 - The source index into the given interleaved buffer. + * @return {InterleavedBuffer} A reference to this instance. + */ + copyAt( index1, interleavedBuffer, index2 ) { + + index1 *= this.stride; + index2 *= interleavedBuffer.stride; + + for ( let i = 0, l = this.stride; i < l; i ++ ) { + + this.array[ index1 + i ] = interleavedBuffer.array[ index2 + i ]; + + } + + return this; + + } + + /** + * Sets the given array data in the interleaved buffer. + * + * @param {(TypedArray|Array)} value - The array data to set. + * @param {number} [offset=0] - The offset in this interleaved buffer's array. + * @return {InterleavedBuffer} A reference to this instance. + */ + set( value, offset = 0 ) { + + this.array.set( value, offset ); + + return this; + + } + + /** + * Returns a new interleaved buffer with copied values from this instance. + * + * @param {Object} [data] - An object with shared array buffers that allows to retain shared structures. + * @return {InterleavedBuffer} A clone of this instance. + */ + clone( data ) { + + if ( data.arrayBuffers === undefined ) { + + data.arrayBuffers = {}; + + } + + if ( this.array.buffer._uuid === undefined ) { + + this.array.buffer._uuid = generateUUID(); + + } + + if ( data.arrayBuffers[ this.array.buffer._uuid ] === undefined ) { + + data.arrayBuffers[ this.array.buffer._uuid ] = this.array.slice( 0 ).buffer; + + } + + const array = new this.array.constructor( data.arrayBuffers[ this.array.buffer._uuid ] ); + + const ib = new this.constructor( array, this.stride ); + ib.setUsage( this.usage ); + + return ib; + + } + + /** + * Sets the given callback function that is executed after the Renderer has transferred + * the array data to the GPU. Can be used to perform clean-up operations after + * the upload when data are not needed anymore on the CPU side. + * + * @param {Function} callback - The `onUpload()` callback. + * @return {InterleavedBuffer} A reference to this instance. + */ + onUpload( callback ) { + + this.onUploadCallback = callback; + + return this; + + } + + /** + * Serializes the interleaved buffer into JSON. + * + * @param {Object} [data] - An optional value holding meta information about the serialization. + * @return {Object} A JSON object representing the serialized interleaved buffer. + */ + toJSON( data ) { + + if ( data.arrayBuffers === undefined ) { + + data.arrayBuffers = {}; + + } + + // generate UUID for array buffer if necessary + + if ( this.array.buffer._uuid === undefined ) { + + this.array.buffer._uuid = generateUUID(); + + } + + if ( data.arrayBuffers[ this.array.buffer._uuid ] === undefined ) { + + data.arrayBuffers[ this.array.buffer._uuid ] = Array.from( new Uint32Array( this.array.buffer ) ); + + } + + // + + return { + uuid: this.uuid, + buffer: this.array.buffer._uuid, + type: this.array.constructor.name, + stride: this.stride + }; + + } + +} + +const _vector$7 = /*@__PURE__*/ new Vector3(); + +/** + * An alternative version of a buffer attribute with interleaved data. Interleaved + * attributes share a common interleaved data storage ({@link InterleavedBuffer}) and refer with + * different offsets into the buffer. + */ +class InterleavedBufferAttribute { + + /** + * Constructs a new interleaved buffer attribute. + * + * @param {InterleavedBuffer} interleavedBuffer - The buffer holding the interleaved data. + * @param {number} itemSize - The item size. + * @param {number} offset - The attribute offset into the buffer. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + */ + constructor( interleavedBuffer, itemSize, offset, normalized = false ) { + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isInterleavedBufferAttribute = true; + + /** + * The name of the buffer attribute. + * + * @type {string} + */ + this.name = ''; + + /** + * The buffer holding the interleaved data. + * + * @type {InterleavedBuffer} + */ + this.data = interleavedBuffer; + + /** + * The item size, see {@link BufferAttribute#itemSize}. + * + * @type {number} + */ + this.itemSize = itemSize; + + /** + * The attribute offset into the buffer. + * + * @type {number} + */ + this.offset = offset; + + /** + * Whether the data are normalized or not, see {@link BufferAttribute#normalized} + * + * @type {InterleavedBuffer} + */ + this.normalized = normalized; + + } + + /** + * The item count of this buffer attribute. + * + * @type {number} + * @readonly + */ + get count() { + + return this.data.count; + + } + + /** + * The array holding the interleaved buffer attribute data. + * + * @type {TypedArray} + */ + get array() { + + return this.data.array; + + } + + /** + * Flag to indicate that this attribute has changed and should be re-sent to + * the GPU. Set this to `true` when you modify the value of the array. + * + * @type {number} + * @default false + * @param {boolean} value + */ + set needsUpdate( value ) { + + this.data.needsUpdate = value; + + } + + /** + * Applies the given 4x4 matrix to the given attribute. Only works with + * item size `3`. + * + * @param {Matrix4} m - The matrix to apply. + * @return {InterleavedBufferAttribute} A reference to this instance. + */ + applyMatrix4( m ) { + + for ( let i = 0, l = this.data.count; i < l; i ++ ) { + + _vector$7.fromBufferAttribute( this, i ); + + _vector$7.applyMatrix4( m ); + + this.setXYZ( i, _vector$7.x, _vector$7.y, _vector$7.z ); + + } + + return this; + + } + + /** + * Applies the given 3x3 normal matrix to the given attribute. Only works with + * item size `3`. + * + * @param {Matrix3} m - The normal matrix to apply. + * @return {InterleavedBufferAttribute} A reference to this instance. + */ + applyNormalMatrix( m ) { + + for ( let i = 0, l = this.count; i < l; i ++ ) { + + _vector$7.fromBufferAttribute( this, i ); + + _vector$7.applyNormalMatrix( m ); + + this.setXYZ( i, _vector$7.x, _vector$7.y, _vector$7.z ); + + } + + return this; + + } + + /** + * Applies the given 4x4 matrix to the given attribute. Only works with + * item size `3` and with direction vectors. + * + * @param {Matrix4} m - The matrix to apply. + * @return {InterleavedBufferAttribute} A reference to this instance. + */ + transformDirection( m ) { + + for ( let i = 0, l = this.count; i < l; i ++ ) { + + _vector$7.fromBufferAttribute( this, i ); + + _vector$7.transformDirection( m ); + + this.setXYZ( i, _vector$7.x, _vector$7.y, _vector$7.z ); + + } + + return this; + + } + + /** + * Returns the given component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} component - The component index. + * @return {number} The returned value. + */ + getComponent( index, component ) { + + let value = this.array[ index * this.data.stride + this.offset + component ]; + + if ( this.normalized ) value = denormalize( value, this.array ); + + return value; + + } + + /** + * Sets the given value to the given component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} component - The component index. + * @param {number} value - The value to set. + * @return {InterleavedBufferAttribute} A reference to this instance. + */ + setComponent( index, component, value ) { + + if ( this.normalized ) value = normalize( value, this.array ); + + this.data.array[ index * this.data.stride + this.offset + component ] = value; + + return this; + + } + + /** + * Sets the x component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} x - The value to set. + * @return {InterleavedBufferAttribute} A reference to this instance. + */ + setX( index, x ) { + + if ( this.normalized ) x = normalize( x, this.array ); + + this.data.array[ index * this.data.stride + this.offset ] = x; + + return this; + + } + + /** + * Sets the y component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} y - The value to set. + * @return {InterleavedBufferAttribute} A reference to this instance. + */ + setY( index, y ) { + + if ( this.normalized ) y = normalize( y, this.array ); + + this.data.array[ index * this.data.stride + this.offset + 1 ] = y; + + return this; + + } + + /** + * Sets the z component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} z - The value to set. + * @return {InterleavedBufferAttribute} A reference to this instance. + */ + setZ( index, z ) { + + if ( this.normalized ) z = normalize( z, this.array ); + + this.data.array[ index * this.data.stride + this.offset + 2 ] = z; + + return this; + + } + + /** + * Sets the w component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} w - The value to set. + * @return {InterleavedBufferAttribute} A reference to this instance. + */ + setW( index, w ) { + + if ( this.normalized ) w = normalize( w, this.array ); + + this.data.array[ index * this.data.stride + this.offset + 3 ] = w; + + return this; + + } + + /** + * Returns the x component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @return {number} The x component. + */ + getX( index ) { + + let x = this.data.array[ index * this.data.stride + this.offset ]; + + if ( this.normalized ) x = denormalize( x, this.array ); + + return x; + + } + + /** + * Returns the y component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @return {number} The y component. + */ + getY( index ) { + + let y = this.data.array[ index * this.data.stride + this.offset + 1 ]; + + if ( this.normalized ) y = denormalize( y, this.array ); + + return y; + + } + + /** + * Returns the z component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @return {number} The z component. + */ + getZ( index ) { + + let z = this.data.array[ index * this.data.stride + this.offset + 2 ]; + + if ( this.normalized ) z = denormalize( z, this.array ); + + return z; + + } + + /** + * Returns the w component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @return {number} The w component. + */ + getW( index ) { + + let w = this.data.array[ index * this.data.stride + this.offset + 3 ]; + + if ( this.normalized ) w = denormalize( w, this.array ); + + return w; + + } + + /** + * Sets the x and y component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} x - The value for the x component to set. + * @param {number} y - The value for the y component to set. + * @return {InterleavedBufferAttribute} A reference to this instance. + */ + setXY( index, x, y ) { + + index = index * this.data.stride + this.offset; + + if ( this.normalized ) { + + x = normalize( x, this.array ); + y = normalize( y, this.array ); + + } + + this.data.array[ index + 0 ] = x; + this.data.array[ index + 1 ] = y; + + return this; + + } + + /** + * Sets the x, y and z component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} x - The value for the x component to set. + * @param {number} y - The value for the y component to set. + * @param {number} z - The value for the z component to set. + * @return {InterleavedBufferAttribute} A reference to this instance. + */ + setXYZ( index, x, y, z ) { + + index = index * this.data.stride + this.offset; + + if ( this.normalized ) { + + x = normalize( x, this.array ); + y = normalize( y, this.array ); + z = normalize( z, this.array ); + + } + + this.data.array[ index + 0 ] = x; + this.data.array[ index + 1 ] = y; + this.data.array[ index + 2 ] = z; + + return this; + + } + + /** + * Sets the x, y, z and w component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} x - The value for the x component to set. + * @param {number} y - The value for the y component to set. + * @param {number} z - The value for the z component to set. + * @param {number} w - The value for the w component to set. + * @return {InterleavedBufferAttribute} A reference to this instance. + */ + setXYZW( index, x, y, z, w ) { + + index = index * this.data.stride + this.offset; + + if ( this.normalized ) { + + x = normalize( x, this.array ); + y = normalize( y, this.array ); + z = normalize( z, this.array ); + w = normalize( w, this.array ); + + } + + this.data.array[ index + 0 ] = x; + this.data.array[ index + 1 ] = y; + this.data.array[ index + 2 ] = z; + this.data.array[ index + 3 ] = w; + + return this; + + } + + /** + * Returns a new buffer attribute with copied values from this instance. + * + * If no parameter is provided, cloning an interleaved buffer attribute will de-interleave buffer data. + * + * @param {Object} [data] - An object with interleaved buffers that allows to retain the interleaved property. + * @return {BufferAttribute|InterleavedBufferAttribute} A clone of this instance. + */ + clone( data ) { + + if ( data === undefined ) { + + console.log( 'THREE.InterleavedBufferAttribute.clone(): Cloning an interleaved buffer attribute will de-interleave buffer data.' ); + + const array = []; + + for ( let i = 0; i < this.count; i ++ ) { + + const index = i * this.data.stride + this.offset; + + for ( let j = 0; j < this.itemSize; j ++ ) { + + array.push( this.data.array[ index + j ] ); + + } + + } + + return new BufferAttribute( new this.array.constructor( array ), this.itemSize, this.normalized ); + + } else { + + if ( data.interleavedBuffers === undefined ) { + + data.interleavedBuffers = {}; + + } + + if ( data.interleavedBuffers[ this.data.uuid ] === undefined ) { + + data.interleavedBuffers[ this.data.uuid ] = this.data.clone( data ); + + } + + return new InterleavedBufferAttribute( data.interleavedBuffers[ this.data.uuid ], this.itemSize, this.offset, this.normalized ); + + } + + } + + /** + * Serializes the buffer attribute into JSON. + * + * If no parameter is provided, cloning an interleaved buffer attribute will de-interleave buffer data. + * + * @param {Object} [data] - An optional value holding meta information about the serialization. + * @return {Object} A JSON object representing the serialized buffer attribute. + */ + toJSON( data ) { + + if ( data === undefined ) { + + console.log( 'THREE.InterleavedBufferAttribute.toJSON(): Serializing an interleaved buffer attribute will de-interleave buffer data.' ); + + const array = []; + + for ( let i = 0; i < this.count; i ++ ) { + + const index = i * this.data.stride + this.offset; + + for ( let j = 0; j < this.itemSize; j ++ ) { + + array.push( this.data.array[ index + j ] ); + + } + + } + + // de-interleave data and save it as an ordinary buffer attribute for now + + return { + itemSize: this.itemSize, + type: this.array.constructor.name, + array: array, + normalized: this.normalized + }; + + } else { + + // save as true interleaved attribute + + if ( data.interleavedBuffers === undefined ) { + + data.interleavedBuffers = {}; + + } + + if ( data.interleavedBuffers[ this.data.uuid ] === undefined ) { + + data.interleavedBuffers[ this.data.uuid ] = this.data.toJSON( data ); + + } + + return { + isInterleavedBufferAttribute: true, + itemSize: this.itemSize, + data: this.data.uuid, + offset: this.offset, + normalized: this.normalized + }; + + } + + } + +} + +/** + * A material for rendering instances of {@link Sprite}. + * + * ```js + * const map = new THREE.TextureLoader().load( 'textures/sprite.png' ); + * const material = new THREE.SpriteMaterial( { map: map, color: 0xffffff } ); + * + * const sprite = new THREE.Sprite( material ); + * sprite.scale.set(200, 200, 1) + * scene.add( sprite ); + * ``` + * + * @augments Material + */ +class SpriteMaterial extends Material { + + /** + * Constructs a new sprite material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSpriteMaterial = true; + + this.type = 'SpriteMaterial'; + + /** + * Color of the material. + * + * @type {Color} + * @default (1,1,1) + */ + this.color = new Color( 0xffffff ); + + /** + * The color map. May optionally include an alpha channel, typically combined + * with {@link Material#transparent} or {@link Material#alphaTest}. The texture map + * color is modulated by the diffuse `color`. + * + * @type {?Texture} + * @default null + */ + this.map = null; + + /** + * The alpha map is a grayscale texture that controls the opacity across the + * surface (black: fully transparent; white: fully opaque). + * + * Only the color of the texture is used, ignoring the alpha channel if one + * exists. For RGB and RGBA textures, the renderer will use the green channel + * when sampling this texture due to the extra bit of precision provided for + * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and + * luminance/alpha textures will also still work as expected. + * + * @type {?Texture} + * @default null + */ + this.alphaMap = null; + + /** + * The rotation of the sprite in radians. + * + * @type {number} + * @default 0 + */ + this.rotation = 0; + + /** + * Specifies whether size of the sprite is attenuated by the camera depth (perspective camera only). + * + * @type {boolean} + * @default true + */ + this.sizeAttenuation = true; + + /** + * Overwritten since sprite materials are transparent + * by default. + * + * @type {boolean} + * @default true + */ + this.transparent = true; + + /** + * Whether the material is affected by fog or not. + * + * @type {boolean} + * @default true + */ + this.fog = true; + + this.setValues( parameters ); + + } + + copy( source ) { + + super.copy( source ); + + this.color.copy( source.color ); + + this.map = source.map; + + this.alphaMap = source.alphaMap; + + this.rotation = source.rotation; + + this.sizeAttenuation = source.sizeAttenuation; + + this.fog = source.fog; + + return this; + + } + +} + +let _geometry; + +const _intersectPoint = /*@__PURE__*/ new Vector3(); +const _worldScale = /*@__PURE__*/ new Vector3(); +const _mvPosition = /*@__PURE__*/ new Vector3(); + +const _alignedPosition = /*@__PURE__*/ new Vector2(); +const _rotatedPosition = /*@__PURE__*/ new Vector2(); +const _viewWorldMatrix = /*@__PURE__*/ new Matrix4(); + +const _vA = /*@__PURE__*/ new Vector3(); +const _vB = /*@__PURE__*/ new Vector3(); +const _vC = /*@__PURE__*/ new Vector3(); + +const _uvA = /*@__PURE__*/ new Vector2(); +const _uvB = /*@__PURE__*/ new Vector2(); +const _uvC = /*@__PURE__*/ new Vector2(); + +/** + * A sprite is a plane that always faces towards the camera, generally with a + * partially transparent texture applied. + * + * Sprites do not cast shadows, setting {@link Object3D#castShadow} to `true` will + * have no effect. + * + * ```js + * const map = new THREE.TextureLoader().load( 'sprite.png' ); + * const material = new THREE.SpriteMaterial( { map: map } ); + * + * const sprite = new THREE.Sprite( material ); + * scene.add( sprite ); + * ``` + * + * @augments Object3D + */ +class Sprite extends Object3D { + + /** + * Constructs a new sprite. + * + * @param {SpriteMaterial} [material] - The sprite material. + */ + constructor( material = new SpriteMaterial() ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSprite = true; + + this.type = 'Sprite'; + + if ( _geometry === undefined ) { + + _geometry = new BufferGeometry(); + + const float32Array = new Float32Array( [ + -0.5, -0.5, 0, 0, 0, + 0.5, -0.5, 0, 1, 0, + 0.5, 0.5, 0, 1, 1, + -0.5, 0.5, 0, 0, 1 + ] ); + + const interleavedBuffer = new InterleavedBuffer( float32Array, 5 ); + + _geometry.setIndex( [ 0, 1, 2, 0, 2, 3 ] ); + _geometry.setAttribute( 'position', new InterleavedBufferAttribute( interleavedBuffer, 3, 0, false ) ); + _geometry.setAttribute( 'uv', new InterleavedBufferAttribute( interleavedBuffer, 2, 3, false ) ); + + } + + /** + * The sprite geometry. + * + * @type {BufferGeometry} + */ + this.geometry = _geometry; + + /** + * The sprite material. + * + * @type {SpriteMaterial} + */ + this.material = material; + + /** + * The sprite's anchor point, and the point around which the sprite rotates. + * A value of `(0.5, 0.5)` corresponds to the midpoint of the sprite. A value + * of `(0, 0)` corresponds to the lower left corner of the sprite. + * + * @type {Vector2} + * @default (0.5,0.5) + */ + this.center = new Vector2( 0.5, 0.5 ); + + /** + * The number of instances of this sprite. + * Can only be used with {@link WebGPURenderer}. + * + * @type {number} + * @default 1 + */ + this.count = 1; + + } + + /** + * Computes intersection points between a casted ray and this sprite. + * + * @param {Raycaster} raycaster - The raycaster. + * @param {Array} intersects - The target array that holds the intersection points. + */ + raycast( raycaster, intersects ) { + + if ( raycaster.camera === null ) { + + console.error( 'THREE.Sprite: "Raycaster.camera" needs to be set in order to raycast against sprites.' ); + + } + + _worldScale.setFromMatrixScale( this.matrixWorld ); + + _viewWorldMatrix.copy( raycaster.camera.matrixWorld ); + this.modelViewMatrix.multiplyMatrices( raycaster.camera.matrixWorldInverse, this.matrixWorld ); + + _mvPosition.setFromMatrixPosition( this.modelViewMatrix ); + + if ( raycaster.camera.isPerspectiveCamera && this.material.sizeAttenuation === false ) { + + _worldScale.multiplyScalar( - _mvPosition.z ); + + } + + const rotation = this.material.rotation; + let sin, cos; + + if ( rotation !== 0 ) { + + cos = Math.cos( rotation ); + sin = Math.sin( rotation ); + + } + + const center = this.center; + + transformVertex( _vA.set( -0.5, -0.5, 0 ), _mvPosition, center, _worldScale, sin, cos ); + transformVertex( _vB.set( 0.5, -0.5, 0 ), _mvPosition, center, _worldScale, sin, cos ); + transformVertex( _vC.set( 0.5, 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos ); + + _uvA.set( 0, 0 ); + _uvB.set( 1, 0 ); + _uvC.set( 1, 1 ); + + // check first triangle + let intersect = raycaster.ray.intersectTriangle( _vA, _vB, _vC, false, _intersectPoint ); + + if ( intersect === null ) { + + // check second triangle + transformVertex( _vB.set( -0.5, 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos ); + _uvB.set( 0, 1 ); + + intersect = raycaster.ray.intersectTriangle( _vA, _vC, _vB, false, _intersectPoint ); + if ( intersect === null ) { + + return; + + } + + } + + const distance = raycaster.ray.origin.distanceTo( _intersectPoint ); + + if ( distance < raycaster.near || distance > raycaster.far ) return; + + intersects.push( { + + distance: distance, + point: _intersectPoint.clone(), + uv: Triangle.getInterpolation( _intersectPoint, _vA, _vB, _vC, _uvA, _uvB, _uvC, new Vector2() ), + face: null, + object: this + + } ); + + } + + copy( source, recursive ) { + + super.copy( source, recursive ); + + if ( source.center !== undefined ) this.center.copy( source.center ); + + this.material = source.material; + + return this; + + } + +} + +function transformVertex( vertexPosition, mvPosition, center, scale, sin, cos ) { + + // compute position in camera space + _alignedPosition.subVectors( vertexPosition, center ).addScalar( 0.5 ).multiply( scale ); + + // to check if rotation is not zero + if ( sin !== undefined ) { + + _rotatedPosition.x = ( cos * _alignedPosition.x ) - ( sin * _alignedPosition.y ); + _rotatedPosition.y = ( sin * _alignedPosition.x ) + ( cos * _alignedPosition.y ); + + } else { + + _rotatedPosition.copy( _alignedPosition ); + + } + + + vertexPosition.copy( mvPosition ); + vertexPosition.x += _rotatedPosition.x; + vertexPosition.y += _rotatedPosition.y; + + // transform to world space + vertexPosition.applyMatrix4( _viewWorldMatrix ); + +} + +const _v1$2 = /*@__PURE__*/ new Vector3(); +const _v2$1 = /*@__PURE__*/ new Vector3(); + +/** + * A component for providing a basic Level of Detail (LOD) mechanism. + * + * Every LOD level is associated with an object, and rendering can be switched + * between them at the distances specified. Typically you would create, say, + * three meshes, one for far away (low detail), one for mid range (medium + * detail) and one for close up (high detail). + * + * ```js + * const lod = new THREE.LOD(); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + * + * //Create spheres with 3 levels of detail and create new LOD levels for them + * for( let i = 0; i < 3; i++ ) { + * + * const geometry = new THREE.IcosahedronGeometry( 10, 3 - i ); + * const mesh = new THREE.Mesh( geometry, material ); + * lod.addLevel( mesh, i * 75 ); + * + * } + * + * scene.add( lod ); + * ``` + * + * @augments Object3D + */ +class LOD extends Object3D { + + /** + * Constructs a new LOD. + */ + constructor() { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLOD = true; + + /** + * The current LOD index. + * + * @private + * @type {number} + * @default 0 + */ + this._currentLevel = 0; + + this.type = 'LOD'; + + Object.defineProperties( this, { + /** + * This array holds the LOD levels. + * + * @name LOD#levels + * @type {Array<{object:Object3D,distance:number,hysteresis:number}>} + */ + levels: { + enumerable: true, + value: [] + } + } ); + + /** + * Whether the LOD object is updated automatically by the renderer per frame + * or not. If set to `false`, you have to call {@link LOD#update} in the + * render loop by yourself. + * + * @type {boolean} + * @default true + */ + this.autoUpdate = true; + + } + + copy( source ) { + + super.copy( source, false ); + + const levels = source.levels; + + for ( let i = 0, l = levels.length; i < l; i ++ ) { + + const level = levels[ i ]; + + this.addLevel( level.object.clone(), level.distance, level.hysteresis ); + + } + + this.autoUpdate = source.autoUpdate; + + return this; + + } + + /** + * Adds a mesh that will display at a certain distance and greater. Typically + * the further away the distance, the lower the detail on the mesh. + * + * @param {Object3D} object - The 3D object to display at this level. + * @param {number} [distance=0] - The distance at which to display this level of detail. + * @param {number} [hysteresis=0] - Threshold used to avoid flickering at LOD boundaries, as a fraction of distance. + * @return {LOD} A reference to this instance. + */ + addLevel( object, distance = 0, hysteresis = 0 ) { + + distance = Math.abs( distance ); + + const levels = this.levels; + + let l; + + for ( l = 0; l < levels.length; l ++ ) { + + if ( distance < levels[ l ].distance ) { + + break; + + } + + } + + levels.splice( l, 0, { distance: distance, hysteresis: hysteresis, object: object } ); + + this.add( object ); + + return this; + + } + + /** + * Removes an existing level, based on the distance from the camera. + * Returns `true` when the level has been removed. Otherwise `false`. + * + * @param {number} distance - Distance of the level to remove. + * @return {boolean} Whether the level has been removed or not. + */ + removeLevel( distance ) { + + const levels = this.levels; + + for ( let i = 0; i < levels.length; i ++ ) { + + if ( levels[ i ].distance === distance ) { + + const removedElements = levels.splice( i, 1 ); + this.remove( removedElements[ 0 ].object ); + + return true; + + } + + } + + return false; + + } + + /** + * Returns the currently active LOD level index. + * + * @return {number} The current active LOD level index. + */ + getCurrentLevel() { + + return this._currentLevel; + + } + + /** + * Returns a reference to the first 3D object that is greater than + * the given distance. + * + * @param {number} distance - The LOD distance. + * @return {Object3D|null} The found 3D object. `null` if no 3D object has been found. + */ + getObjectForDistance( distance ) { + + const levels = this.levels; + + if ( levels.length > 0 ) { + + let i, l; + + for ( i = 1, l = levels.length; i < l; i ++ ) { + + let levelDistance = levels[ i ].distance; + + if ( levels[ i ].object.visible ) { + + levelDistance -= levelDistance * levels[ i ].hysteresis; + + } + + if ( distance < levelDistance ) { + + break; + + } + + } + + return levels[ i - 1 ].object; + + } + + return null; + + } + + /** + * Computes intersection points between a casted ray and this LOD. + * + * @param {Raycaster} raycaster - The raycaster. + * @param {Array} intersects - The target array that holds the intersection points. + */ + raycast( raycaster, intersects ) { + + const levels = this.levels; + + if ( levels.length > 0 ) { + + _v1$2.setFromMatrixPosition( this.matrixWorld ); + + const distance = raycaster.ray.origin.distanceTo( _v1$2 ); + + this.getObjectForDistance( distance ).raycast( raycaster, intersects ); + + } + + } + + /** + * Updates the LOD by computing which LOD level should be visible according + * to the current distance of the given camera. + * + * @param {Camera} camera - The camera the scene is rendered with. + */ + update( camera ) { + + const levels = this.levels; + + if ( levels.length > 1 ) { + + _v1$2.setFromMatrixPosition( camera.matrixWorld ); + _v2$1.setFromMatrixPosition( this.matrixWorld ); + + const distance = _v1$2.distanceTo( _v2$1 ) / camera.zoom; + + levels[ 0 ].object.visible = true; + + let i, l; + + for ( i = 1, l = levels.length; i < l; i ++ ) { + + let levelDistance = levels[ i ].distance; + + if ( levels[ i ].object.visible ) { + + levelDistance -= levelDistance * levels[ i ].hysteresis; + + } + + if ( distance >= levelDistance ) { + + levels[ i - 1 ].object.visible = false; + levels[ i ].object.visible = true; + + } else { + + break; + + } + + } + + this._currentLevel = i - 1; + + for ( ; i < l; i ++ ) { + + levels[ i ].object.visible = false; + + } + + } + + } + + toJSON( meta ) { + + const data = super.toJSON( meta ); + + if ( this.autoUpdate === false ) data.object.autoUpdate = false; + + data.object.levels = []; + + const levels = this.levels; + + for ( let i = 0, l = levels.length; i < l; i ++ ) { + + const level = levels[ i ]; + + data.object.levels.push( { + object: level.object.uuid, + distance: level.distance, + hysteresis: level.hysteresis + } ); + + } + + return data; + + } + +} + +const _basePosition = /*@__PURE__*/ new Vector3(); + +const _skinIndex = /*@__PURE__*/ new Vector4(); +const _skinWeight = /*@__PURE__*/ new Vector4(); + +const _vector3 = /*@__PURE__*/ new Vector3(); +const _matrix4 = /*@__PURE__*/ new Matrix4(); +const _vertex = /*@__PURE__*/ new Vector3(); + +const _sphere$5 = /*@__PURE__*/ new Sphere(); +const _inverseMatrix$2 = /*@__PURE__*/ new Matrix4(); +const _ray$2 = /*@__PURE__*/ new Ray(); + +/** + * A mesh that has a {@link Skeleton} that can then be used to animate the + * vertices of the geometry with skinning/skeleton animation. + * + * Next to a valid skeleton, the skinned mesh requires skin indices and weights + * as buffer attributes in its geometry. These attribute define which bones affect a single + * vertex to a certain extend. + * + * Typically skinned meshes are not created manually but loaders like {@link GLTFLoader} + * or {@link FBXLoader } import respective models. + * + * @augments Mesh + */ +class SkinnedMesh extends Mesh { + + /** + * Constructs a new skinned mesh. + * + * @param {BufferGeometry} [geometry] - The mesh geometry. + * @param {Material|Array} [material] - The mesh material. + */ + constructor( geometry, material ) { + + super( geometry, material ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSkinnedMesh = true; + + this.type = 'SkinnedMesh'; + + /** + * `AttachedBindMode` means the skinned mesh shares the same world space as the skeleton. + * This is not true when using `DetachedBindMode` which is useful when sharing a skeleton + * across multiple skinned meshes. + * + * @type {(AttachedBindMode|DetachedBindMode)} + * @default AttachedBindMode + */ + this.bindMode = AttachedBindMode; + + /** + * The base matrix that is used for the bound bone transforms. + * + * @type {Matrix4} + */ + this.bindMatrix = new Matrix4(); + + /** + * The base matrix that is used for resetting the bound bone transforms. + * + * @type {Matrix4} + */ + this.bindMatrixInverse = new Matrix4(); + + /** + * The bounding box of the skinned mesh. Can be computed via {@link SkinnedMesh#computeBoundingBox}. + * + * @type {?Box3} + * @default null + */ + this.boundingBox = null; + + /** + * The bounding sphere of the skinned mesh. Can be computed via {@link SkinnedMesh#computeBoundingSphere}. + * + * @type {?Sphere} + * @default null + */ + this.boundingSphere = null; + + } + + /** + * Computes the bounding box of the skinned mesh, and updates {@link SkinnedMesh#boundingBox}. + * The bounding box is not automatically computed by the engine; this method must be called by your app. + * If the skinned mesh is animated, the bounding box should be recomputed per frame in order to reflect + * the current animation state. + */ + computeBoundingBox() { + + const geometry = this.geometry; + + if ( this.boundingBox === null ) { + + this.boundingBox = new Box3(); + + } + + this.boundingBox.makeEmpty(); + + const positionAttribute = geometry.getAttribute( 'position' ); + + for ( let i = 0; i < positionAttribute.count; i ++ ) { + + this.getVertexPosition( i, _vertex ); + this.boundingBox.expandByPoint( _vertex ); + + } + + } + + /** + * Computes the bounding sphere of the skinned mesh, and updates {@link SkinnedMesh#boundingSphere}. + * The bounding sphere is automatically computed by the engine once when it is needed, e.g., for ray casting + * and view frustum culling. If the skinned mesh is animated, the bounding sphere should be recomputed + * per frame in order to reflect the current animation state. + */ + computeBoundingSphere() { + + const geometry = this.geometry; + + if ( this.boundingSphere === null ) { + + this.boundingSphere = new Sphere(); + + } + + this.boundingSphere.makeEmpty(); + + const positionAttribute = geometry.getAttribute( 'position' ); + + for ( let i = 0; i < positionAttribute.count; i ++ ) { + + this.getVertexPosition( i, _vertex ); + this.boundingSphere.expandByPoint( _vertex ); + + } + + } + + copy( source, recursive ) { + + super.copy( source, recursive ); + + this.bindMode = source.bindMode; + this.bindMatrix.copy( source.bindMatrix ); + this.bindMatrixInverse.copy( source.bindMatrixInverse ); + + this.skeleton = source.skeleton; + + if ( source.boundingBox !== null ) this.boundingBox = source.boundingBox.clone(); + if ( source.boundingSphere !== null ) this.boundingSphere = source.boundingSphere.clone(); + + return this; + + } + + raycast( raycaster, intersects ) { + + const material = this.material; + const matrixWorld = this.matrixWorld; + + if ( material === undefined ) return; + + // test with bounding sphere in world space + + if ( this.boundingSphere === null ) this.computeBoundingSphere(); + + _sphere$5.copy( this.boundingSphere ); + _sphere$5.applyMatrix4( matrixWorld ); + + if ( raycaster.ray.intersectsSphere( _sphere$5 ) === false ) return; + + // convert ray to local space of skinned mesh + + _inverseMatrix$2.copy( matrixWorld ).invert(); + _ray$2.copy( raycaster.ray ).applyMatrix4( _inverseMatrix$2 ); + + // test with bounding box in local space + + if ( this.boundingBox !== null ) { + + if ( _ray$2.intersectsBox( this.boundingBox ) === false ) return; + + } + + // test for intersections with geometry + + this._computeIntersections( raycaster, intersects, _ray$2 ); + + } + + getVertexPosition( index, target ) { + + super.getVertexPosition( index, target ); + + this.applyBoneTransform( index, target ); + + return target; + + } + + /** + * Binds the given skeleton to the skinned mesh. + * + * @param {Skeleton} skeleton - The skeleton to bind. + * @param {Matrix4} [bindMatrix] - The bind matrix. If no bind matrix is provided, + * the skinned mesh's world matrix will be used instead. + */ + bind( skeleton, bindMatrix ) { + + this.skeleton = skeleton; + + if ( bindMatrix === undefined ) { + + this.updateMatrixWorld( true ); + + this.skeleton.calculateInverses(); + + bindMatrix = this.matrixWorld; + + } + + this.bindMatrix.copy( bindMatrix ); + this.bindMatrixInverse.copy( bindMatrix ).invert(); + + } + + /** + * This method sets the skinned mesh in the rest pose). + */ + pose() { + + this.skeleton.pose(); + + } + + /** + * Normalizes the skin weights which are defined as a buffer attribute + * in the skinned mesh's geometry. + */ + normalizeSkinWeights() { + + const vector = new Vector4(); + + const skinWeight = this.geometry.attributes.skinWeight; + + for ( let i = 0, l = skinWeight.count; i < l; i ++ ) { + + vector.fromBufferAttribute( skinWeight, i ); + + const scale = 1.0 / vector.manhattanLength(); + + if ( scale !== Infinity ) { + + vector.multiplyScalar( scale ); + + } else { + + vector.set( 1, 0, 0, 0 ); // do something reasonable + + } + + skinWeight.setXYZW( i, vector.x, vector.y, vector.z, vector.w ); + + } + + } + + updateMatrixWorld( force ) { + + super.updateMatrixWorld( force ); + + if ( this.bindMode === AttachedBindMode ) { + + this.bindMatrixInverse.copy( this.matrixWorld ).invert(); + + } else if ( this.bindMode === DetachedBindMode ) { + + this.bindMatrixInverse.copy( this.bindMatrix ).invert(); + + } else { + + console.warn( 'THREE.SkinnedMesh: Unrecognized bindMode: ' + this.bindMode ); + + } + + } + + /** + * Applies the bone transform associated with the given index to the given + * vertex position. Returns the updated vector. + * + * @param {number} index - The vertex index. + * @param {Vector3} target - The target object that is used to store the method's result. + * the skinned mesh's world matrix will be used instead. + * @return {Vector3} The updated vertex position. + */ + applyBoneTransform( index, target ) { + + const skeleton = this.skeleton; + const geometry = this.geometry; + + _skinIndex.fromBufferAttribute( geometry.attributes.skinIndex, index ); + _skinWeight.fromBufferAttribute( geometry.attributes.skinWeight, index ); + + _basePosition.copy( target ).applyMatrix4( this.bindMatrix ); + + target.set( 0, 0, 0 ); + + for ( let i = 0; i < 4; i ++ ) { + + const weight = _skinWeight.getComponent( i ); + + if ( weight !== 0 ) { + + const boneIndex = _skinIndex.getComponent( i ); + + _matrix4.multiplyMatrices( skeleton.bones[ boneIndex ].matrixWorld, skeleton.boneInverses[ boneIndex ] ); + + target.addScaledVector( _vector3.copy( _basePosition ).applyMatrix4( _matrix4 ), weight ); + + } + + } + + return target.applyMatrix4( this.bindMatrixInverse ); + + } + +} + +/** + * A bone which is part of a {@link Skeleton}. The skeleton in turn is used by + * the {@link SkinnedMesh}. + * + * ```js + * const root = new THREE.Bone(); + * const child = new THREE.Bone(); + * + * root.add( child ); + * child.position.y = 5; + * ``` + * + * @augments Object3D + */ +class Bone extends Object3D { + + /** + * Constructs a new bone. + */ + constructor() { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isBone = true; + + this.type = 'Bone'; + + } + +} + +/** + * Creates a texture directly from raw buffer data. + * + * The interpretation of the data depends on type and format: If the type is + * `UnsignedByteType`, a `Uint8Array` will be useful for addressing the + * texel data. If the format is `RGBAFormat`, data needs four values for + * one texel; Red, Green, Blue and Alpha (typically the opacity). + * + * @augments Texture + */ +class DataTexture extends Texture { + + /** + * Constructs a new data texture. + * + * @param {?TypedArray} [data=null] - The buffer data. + * @param {number} [width=1] - The width of the texture. + * @param {number} [height=1] - The height of the texture. + * @param {number} [format=RGBAFormat] - The texture format. + * @param {number} [type=UnsignedByteType] - The texture type. + * @param {number} [mapping=Texture.DEFAULT_MAPPING] - The texture mapping. + * @param {number} [wrapS=ClampToEdgeWrapping] - The wrapS value. + * @param {number} [wrapT=ClampToEdgeWrapping] - The wrapT value. + * @param {number} [magFilter=NearestFilter] - The mag filter value. + * @param {number} [minFilter=NearestFilter] - The min filter value. + * @param {number} [anisotropy=Texture.DEFAULT_ANISOTROPY] - The anisotropy value. + * @param {string} [colorSpace=NoColorSpace] - The color space. + */ + constructor( data = null, width = 1, height = 1, format, type, mapping, wrapS, wrapT, magFilter = NearestFilter, minFilter = NearestFilter, anisotropy, colorSpace ) { + + super( null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, colorSpace ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isDataTexture = true; + + /** + * The image definition of a data texture. + * + * @type {{data:TypedArray,width:number,height:number}} + */ + this.image = { data: data, width: width, height: height }; + + /** + * Whether to generate mipmaps (if possible) for a texture. + * + * Overwritten and set to `false` by default. + * + * @type {boolean} + * @default false + */ + this.generateMipmaps = false; + + /** + * If set to `true`, the texture is flipped along the vertical axis when + * uploaded to the GPU. + * + * Overwritten and set to `false` by default. + * + * @type {boolean} + * @default false + */ + this.flipY = false; + + /** + * Specifies the alignment requirements for the start of each pixel row in memory. + * + * Overwritten and set to `1` by default. + * + * @type {boolean} + * @default 1 + */ + this.unpackAlignment = 1; + + } + +} + +const _offsetMatrix = /*@__PURE__*/ new Matrix4(); +const _identityMatrix = /*@__PURE__*/ new Matrix4(); + +/** + * Class for representing the armatures in `three.js`. The skeleton + * is defined by a hierarchy of bones. + * + * ```js + * const bones = []; + * + * const shoulder = new THREE.Bone(); + * const elbow = new THREE.Bone(); + * const hand = new THREE.Bone(); + * + * shoulder.add( elbow ); + * elbow.add( hand ); + * + * bones.push( shoulder , elbow, hand); + * + * shoulder.position.y = -5; + * elbow.position.y = 0; + * hand.position.y = 5; + * + * const armSkeleton = new THREE.Skeleton( bones ); + * ``` + */ +class Skeleton { + + /** + * Constructs a new skeleton. + * + * @param {Array} [bones] - An array of bones. + * @param {Array} [boneInverses] - An array of bone inverse matrices. + * If not provided, these matrices will be computed automatically via {@link Skeleton#calculateInverses}. + */ + constructor( bones = [], boneInverses = [] ) { + + this.uuid = generateUUID(); + + /** + * An array of bones defining the skeleton. + * + * @type {Array} + */ + this.bones = bones.slice( 0 ); + + /** + * An array of bone inverse matrices. + * + * @type {Array} + */ + this.boneInverses = boneInverses; + + /** + * An array buffer holding the bone data. + * Input data for {@link Skeleton#boneTexture}. + * + * @type {?Float32Array} + * @default null + */ + this.boneMatrices = null; + + /** + * A texture holding the bone data for use + * in the vertex shader. + * + * @type {?DataTexture} + * @default null + */ + this.boneTexture = null; + + this.init(); + + } + + /** + * Initializes the skeleton. This method gets automatically called by the constructor + * but depending on how the skeleton is created it might be necessary to call this method + * manually. + */ + init() { + + const bones = this.bones; + const boneInverses = this.boneInverses; + + this.boneMatrices = new Float32Array( bones.length * 16 ); + + // calculate inverse bone matrices if necessary + + if ( boneInverses.length === 0 ) { + + this.calculateInverses(); + + } else { + + // handle special case + + if ( bones.length !== boneInverses.length ) { + + console.warn( 'THREE.Skeleton: Number of inverse bone matrices does not match amount of bones.' ); + + this.boneInverses = []; + + for ( let i = 0, il = this.bones.length; i < il; i ++ ) { + + this.boneInverses.push( new Matrix4() ); + + } + + } + + } + + } + + /** + * Computes the bone inverse matrices. This method resets {@link Skeleton#boneInverses} + * and fills it with new matrices. + */ + calculateInverses() { + + this.boneInverses.length = 0; + + for ( let i = 0, il = this.bones.length; i < il; i ++ ) { + + const inverse = new Matrix4(); + + if ( this.bones[ i ] ) { + + inverse.copy( this.bones[ i ].matrixWorld ).invert(); + + } + + this.boneInverses.push( inverse ); + + } + + } + + /** + * Resets the skeleton to the base pose. + */ + pose() { + + // recover the bind-time world matrices + + for ( let i = 0, il = this.bones.length; i < il; i ++ ) { + + const bone = this.bones[ i ]; + + if ( bone ) { + + bone.matrixWorld.copy( this.boneInverses[ i ] ).invert(); + + } + + } + + // compute the local matrices, positions, rotations and scales + + for ( let i = 0, il = this.bones.length; i < il; i ++ ) { + + const bone = this.bones[ i ]; + + if ( bone ) { + + if ( bone.parent && bone.parent.isBone ) { + + bone.matrix.copy( bone.parent.matrixWorld ).invert(); + bone.matrix.multiply( bone.matrixWorld ); + + } else { + + bone.matrix.copy( bone.matrixWorld ); + + } + + bone.matrix.decompose( bone.position, bone.quaternion, bone.scale ); + + } + + } + + } + + /** + * Resets the skeleton to the base pose. + */ + update() { + + const bones = this.bones; + const boneInverses = this.boneInverses; + const boneMatrices = this.boneMatrices; + const boneTexture = this.boneTexture; + + // flatten bone matrices to array + + for ( let i = 0, il = bones.length; i < il; i ++ ) { + + // compute the offset between the current and the original transform + + const matrix = bones[ i ] ? bones[ i ].matrixWorld : _identityMatrix; + + _offsetMatrix.multiplyMatrices( matrix, boneInverses[ i ] ); + _offsetMatrix.toArray( boneMatrices, i * 16 ); + + } + + if ( boneTexture !== null ) { + + boneTexture.needsUpdate = true; + + } + + } + + /** + * Returns a new skeleton with copied values from this instance. + * + * @return {Skeleton} A clone of this instance. + */ + clone() { + + return new Skeleton( this.bones, this.boneInverses ); + + } + + /** + * Computes a data texture for passing bone data to the vertex shader. + * + * @return {Skeleton} A reference of this instance. + */ + computeBoneTexture() { + + // layout (1 matrix = 4 pixels) + // RGBA RGBA RGBA RGBA (=> column1, column2, column3, column4) + // with 8x8 pixel texture max 16 bones * 4 pixels = (8 * 8) + // 16x16 pixel texture max 64 bones * 4 pixels = (16 * 16) + // 32x32 pixel texture max 256 bones * 4 pixels = (32 * 32) + // 64x64 pixel texture max 1024 bones * 4 pixels = (64 * 64) + + let size = Math.sqrt( this.bones.length * 4 ); // 4 pixels needed for 1 matrix + size = Math.ceil( size / 4 ) * 4; + size = Math.max( size, 4 ); + + const boneMatrices = new Float32Array( size * size * 4 ); // 4 floats per RGBA pixel + boneMatrices.set( this.boneMatrices ); // copy current values + + const boneTexture = new DataTexture( boneMatrices, size, size, RGBAFormat, FloatType ); + boneTexture.needsUpdate = true; + + this.boneMatrices = boneMatrices; + this.boneTexture = boneTexture; + + return this; + + } + + /** + * Searches through the skeleton's bone array and returns the first with a + * matching name. + * + * @param {string} name - The name of the bone. + * @return {Bone|undefined} The found bone. `undefined` if no bone has been found. + */ + getBoneByName( name ) { + + for ( let i = 0, il = this.bones.length; i < il; i ++ ) { + + const bone = this.bones[ i ]; + + if ( bone.name === name ) { + + return bone; + + } + + } + + return undefined; + + } + + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose( ) { + + if ( this.boneTexture !== null ) { + + this.boneTexture.dispose(); + + this.boneTexture = null; + + } + + } + + /** + * Setups the skeleton by the given JSON and bones. + * + * @param {Object} json - The skeleton as serialized JSON. + * @param {Object} bones - An array of bones. + * @return {Skeleton} A reference of this instance. + */ + fromJSON( json, bones ) { + + this.uuid = json.uuid; + + for ( let i = 0, l = json.bones.length; i < l; i ++ ) { + + const uuid = json.bones[ i ]; + let bone = bones[ uuid ]; + + if ( bone === undefined ) { + + console.warn( 'THREE.Skeleton: No bone found with UUID:', uuid ); + bone = new Bone(); + + } + + this.bones.push( bone ); + this.boneInverses.push( new Matrix4().fromArray( json.boneInverses[ i ] ) ); + + } + + this.init(); + + return this; + + } + + /** + * Serializes the skeleton into JSON. + * + * @return {Object} A JSON object representing the serialized skeleton. + * @see {@link ObjectLoader#parse} + */ + toJSON() { + + const data = { + metadata: { + version: 4.7, + type: 'Skeleton', + generator: 'Skeleton.toJSON' + }, + bones: [], + boneInverses: [] + }; + + data.uuid = this.uuid; + + const bones = this.bones; + const boneInverses = this.boneInverses; + + for ( let i = 0, l = bones.length; i < l; i ++ ) { + + const bone = bones[ i ]; + data.bones.push( bone.uuid ); + + const boneInverse = boneInverses[ i ]; + data.boneInverses.push( boneInverse.toArray() ); + + } + + return data; + + } + +} + +/** + * An instanced version of a buffer attribute. + * + * @augments BufferAttribute + */ +class InstancedBufferAttribute extends BufferAttribute { + + /** + * Constructs a new instanced buffer attribute. + * + * @param {TypedArray} array - The array holding the attribute data. + * @param {number} itemSize - The item size. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + * @param {number} [meshPerAttribute=1] - How often a value of this buffer attribute should be repeated. + */ + constructor( array, itemSize, normalized, meshPerAttribute = 1 ) { + + super( array, itemSize, normalized ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isInstancedBufferAttribute = true; + + /** + * Defines how often a value of this buffer attribute should be repeated. A + * value of one means that each value of the instanced attribute is used for + * a single instance. A value of two means that each value is used for two + * consecutive instances (and so on). + * + * @type {number} + * @default 1 + */ + this.meshPerAttribute = meshPerAttribute; + + } + + copy( source ) { + + super.copy( source ); + + this.meshPerAttribute = source.meshPerAttribute; + + return this; + + } + + toJSON() { + + const data = super.toJSON(); + + data.meshPerAttribute = this.meshPerAttribute; + + data.isInstancedBufferAttribute = true; + + return data; + + } + +} + +const _instanceLocalMatrix = /*@__PURE__*/ new Matrix4(); +const _instanceWorldMatrix = /*@__PURE__*/ new Matrix4(); + +const _instanceIntersects = []; + +const _box3 = /*@__PURE__*/ new Box3(); +const _identity = /*@__PURE__*/ new Matrix4(); +const _mesh$1 = /*@__PURE__*/ new Mesh(); +const _sphere$4 = /*@__PURE__*/ new Sphere(); + +/** + * A special version of a mesh with instanced rendering support. Use + * this class if you have to render a large number of objects with the same + * geometry and material(s) but with different world transformations. The usage + * of 'InstancedMesh' will help you to reduce the number of draw calls and thus + * improve the overall rendering performance in your application. + * + * @augments Mesh + */ +class InstancedMesh extends Mesh { + + /** + * Constructs a new instanced mesh. + * + * @param {BufferGeometry} [geometry] - The mesh geometry. + * @param {Material|Array} [material] - The mesh material. + * @param {number} count - The number of instances. + */ + constructor( geometry, material, count ) { + + super( geometry, material ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isInstancedMesh = true; + + /** + * Represents the local transformation of all instances. You have to set its + * {@link BufferAttribute#needsUpdate} flag to true if you modify instanced data + * via {@link InstancedMesh#setMatrixAt}. + * + * @type {InstancedBufferAttribute} + */ + this.instanceMatrix = new InstancedBufferAttribute( new Float32Array( count * 16 ), 16 ); + + /** + * Represents the color of all instances. You have to set its + * {@link BufferAttribute#needsUpdate} flag to true if you modify instanced data + * via {@link InstancedMesh#setColorAt}. + * + * @type {?InstancedBufferAttribute} + * @default null + */ + this.instanceColor = null; + + /** + * Represents the morph target weights of all instances. You have to set its + * {@link Texture#needsUpdate} flag to true if you modify instanced data + * via {@link InstancedMesh#setMorphAt}. + * + * @type {?DataTexture} + * @default null + */ + this.morphTexture = null; + + /** + * The number of instances. + * + * @type {number} + */ + this.count = count; + + /** + * The bounding box of the instanced mesh. Can be computed via {@link InstancedMesh#computeBoundingBox}. + * + * @type {?Box3} + * @default null + */ + this.boundingBox = null; + + /** + * The bounding sphere of the instanced mesh. Can be computed via {@link InstancedMesh#computeBoundingSphere}. + * + * @type {?Sphere} + * @default null + */ + this.boundingSphere = null; + + for ( let i = 0; i < count; i ++ ) { + + this.setMatrixAt( i, _identity ); + + } + + } + + /** + * Computes the bounding box of the instanced mesh, and updates {@link InstancedMesh#boundingBox}. + * The bounding box is not automatically computed by the engine; this method must be called by your app. + * You may need to recompute the bounding box if an instance is transformed via {@link InstancedMesh#setMatrixAt}. + */ + computeBoundingBox() { + + const geometry = this.geometry; + const count = this.count; + + if ( this.boundingBox === null ) { + + this.boundingBox = new Box3(); + + } + + if ( geometry.boundingBox === null ) { + + geometry.computeBoundingBox(); + + } + + this.boundingBox.makeEmpty(); + + for ( let i = 0; i < count; i ++ ) { + + this.getMatrixAt( i, _instanceLocalMatrix ); + + _box3.copy( geometry.boundingBox ).applyMatrix4( _instanceLocalMatrix ); + + this.boundingBox.union( _box3 ); + + } + + } + + /** + * Computes the bounding sphere of the instanced mesh, and updates {@link InstancedMesh#boundingSphere} + * The engine automatically computes the bounding sphere when it is needed, e.g., for ray casting or view frustum culling. + * You may need to recompute the bounding sphere if an instance is transformed via {@link InstancedMesh#setMatrixAt}. + */ + computeBoundingSphere() { + + const geometry = this.geometry; + const count = this.count; + + if ( this.boundingSphere === null ) { + + this.boundingSphere = new Sphere(); + + } + + if ( geometry.boundingSphere === null ) { + + geometry.computeBoundingSphere(); + + } + + this.boundingSphere.makeEmpty(); + + for ( let i = 0; i < count; i ++ ) { + + this.getMatrixAt( i, _instanceLocalMatrix ); + + _sphere$4.copy( geometry.boundingSphere ).applyMatrix4( _instanceLocalMatrix ); + + this.boundingSphere.union( _sphere$4 ); + + } + + } + + copy( source, recursive ) { + + super.copy( source, recursive ); + + this.instanceMatrix.copy( source.instanceMatrix ); + + if ( source.morphTexture !== null ) this.morphTexture = source.morphTexture.clone(); + if ( source.instanceColor !== null ) this.instanceColor = source.instanceColor.clone(); + + this.count = source.count; + + if ( source.boundingBox !== null ) this.boundingBox = source.boundingBox.clone(); + if ( source.boundingSphere !== null ) this.boundingSphere = source.boundingSphere.clone(); + + return this; + + } + + /** + * Gets the color of the defined instance. + * + * @param {number} index - The instance index. + * @param {Color} color - The target object that is used to store the method's result. + */ + getColorAt( index, color ) { + + color.fromArray( this.instanceColor.array, index * 3 ); + + } + + /** + * Gets the local transformation matrix of the defined instance. + * + * @param {number} index - The instance index. + * @param {Matrix4} matrix - The target object that is used to store the method's result. + */ + getMatrixAt( index, matrix ) { + + matrix.fromArray( this.instanceMatrix.array, index * 16 ); + + } + + /** + * Gets the morph target weights of the defined instance. + * + * @param {number} index - The instance index. + * @param {Mesh} object - The target object that is used to store the method's result. + */ + getMorphAt( index, object ) { + + const objectInfluences = object.morphTargetInfluences; + + const array = this.morphTexture.source.data.data; + + const len = objectInfluences.length + 1; // All influences + the baseInfluenceSum + + const dataIndex = index * len + 1; // Skip the baseInfluenceSum at the beginning + + for ( let i = 0; i < objectInfluences.length; i ++ ) { + + objectInfluences[ i ] = array[ dataIndex + i ]; + + } + + } + + raycast( raycaster, intersects ) { + + const matrixWorld = this.matrixWorld; + const raycastTimes = this.count; + + _mesh$1.geometry = this.geometry; + _mesh$1.material = this.material; + + if ( _mesh$1.material === undefined ) return; + + // test with bounding sphere first + + if ( this.boundingSphere === null ) this.computeBoundingSphere(); + + _sphere$4.copy( this.boundingSphere ); + _sphere$4.applyMatrix4( matrixWorld ); + + if ( raycaster.ray.intersectsSphere( _sphere$4 ) === false ) return; + + // now test each instance + + for ( let instanceId = 0; instanceId < raycastTimes; instanceId ++ ) { + + // calculate the world matrix for each instance + + this.getMatrixAt( instanceId, _instanceLocalMatrix ); + + _instanceWorldMatrix.multiplyMatrices( matrixWorld, _instanceLocalMatrix ); + + // the mesh represents this single instance + + _mesh$1.matrixWorld = _instanceWorldMatrix; + + _mesh$1.raycast( raycaster, _instanceIntersects ); + + // process the result of raycast + + for ( let i = 0, l = _instanceIntersects.length; i < l; i ++ ) { + + const intersect = _instanceIntersects[ i ]; + intersect.instanceId = instanceId; + intersect.object = this; + intersects.push( intersect ); + + } + + _instanceIntersects.length = 0; + + } + + } + + /** + * Sets the given color to the defined instance. Make sure you set the `needsUpdate` flag of + * {@link InstancedMesh#instanceColor} to `true` after updating all the colors. + * + * @param {number} index - The instance index. + * @param {Color} color - The instance color. + */ + setColorAt( index, color ) { + + if ( this.instanceColor === null ) { + + this.instanceColor = new InstancedBufferAttribute( new Float32Array( this.instanceMatrix.count * 3 ).fill( 1 ), 3 ); + + } + + color.toArray( this.instanceColor.array, index * 3 ); + + } + + /** + * Sets the given local transformation matrix to the defined instance. Make sure you set the `needsUpdate` flag of + * {@link InstancedMesh#instanceMatrix} to `true` after updating all the colors. + * + * @param {number} index - The instance index. + * @param {Matrix4} matrix - The local transformation. + */ + setMatrixAt( index, matrix ) { + + matrix.toArray( this.instanceMatrix.array, index * 16 ); + + } + + /** + * Sets the morph target weights to the defined instance. Make sure you set the `needsUpdate` flag of + * {@link InstancedMesh#morphTexture} to `true` after updating all the influences. + * + * @param {number} index - The instance index. + * @param {Mesh} object - A mesh which `morphTargetInfluences` property containing the morph target weights + * of a single instance. + */ + setMorphAt( index, object ) { + + const objectInfluences = object.morphTargetInfluences; + + const len = objectInfluences.length + 1; // morphBaseInfluence + all influences + + if ( this.morphTexture === null ) { + + this.morphTexture = new DataTexture( new Float32Array( len * this.count ), len, this.count, RedFormat, FloatType ); + + } + + const array = this.morphTexture.source.data.data; + + let morphInfluencesSum = 0; + + for ( let i = 0; i < objectInfluences.length; i ++ ) { + + morphInfluencesSum += objectInfluences[ i ]; + + } + + const morphBaseInfluence = this.geometry.morphTargetsRelative ? 1 : 1 - morphInfluencesSum; + + const dataIndex = len * index; + + array[ dataIndex ] = morphBaseInfluence; + + array.set( objectInfluences, dataIndex + 1 ); + + } + + updateMorphTargets() { + + } + + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose() { + + this.dispatchEvent( { type: 'dispose' } ); + + if ( this.morphTexture !== null ) { + + this.morphTexture.dispose(); + this.morphTexture = null; + + } + + } + +} + +const _vector1 = /*@__PURE__*/ new Vector3(); +const _vector2 = /*@__PURE__*/ new Vector3(); +const _normalMatrix = /*@__PURE__*/ new Matrix3(); + +/** + * A two dimensional surface that extends infinitely in 3D space, represented + * in [Hessian normal form]{@link http://mathworld.wolfram.com/HessianNormalForm.html} + * by a unit length normal vector and a constant. + */ +class Plane { + + /** + * Constructs a new plane. + * + * @param {Vector3} [normal=(1,0,0)] - A unit length vector defining the normal of the plane. + * @param {number} [constant=0] - The signed distance from the origin to the plane. + */ + constructor( normal = new Vector3( 1, 0, 0 ), constant = 0 ) { + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isPlane = true; + + /** + * A unit length vector defining the normal of the plane. + * + * @type {Vector3} + */ + this.normal = normal; + + /** + * The signed distance from the origin to the plane. + * + * @type {number} + * @default 0 + */ + this.constant = constant; + + } + + /** + * Sets the plane components by copying the given values. + * + * @param {Vector3} normal - The normal. + * @param {number} constant - The constant. + * @return {Plane} A reference to this plane. + */ + set( normal, constant ) { + + this.normal.copy( normal ); + this.constant = constant; + + return this; + + } + + /** + * Sets the plane components by defining `x`, `y`, `z` as the + * plane normal and `w` as the constant. + * + * @param {number} x - The value for the normal's x component. + * @param {number} y - The value for the normal's y component. + * @param {number} z - The value for the normal's z component. + * @param {number} w - The constant value. + * @return {Plane} A reference to this plane. + */ + setComponents( x, y, z, w ) { + + this.normal.set( x, y, z ); + this.constant = w; + + return this; + + } + + /** + * Sets the plane from the given normal and coplanar point (that is a point + * that lies onto the plane). + * + * @param {Vector3} normal - The normal. + * @param {Vector3} point - A coplanar point. + * @return {Plane} A reference to this plane. + */ + setFromNormalAndCoplanarPoint( normal, point ) { + + this.normal.copy( normal ); + this.constant = - point.dot( this.normal ); + + return this; + + } + + /** + * Sets the plane from three coplanar points. The winding order is + * assumed to be counter-clockwise, and determines the direction of + * the plane normal. + * + * @param {Vector3} a - The first coplanar point. + * @param {Vector3} b - The second coplanar point. + * @param {Vector3} c - The third coplanar point. + * @return {Plane} A reference to this plane. + */ + setFromCoplanarPoints( a, b, c ) { + + const normal = _vector1.subVectors( c, b ).cross( _vector2.subVectors( a, b ) ).normalize(); + + // Q: should an error be thrown if normal is zero (e.g. degenerate plane)? + + this.setFromNormalAndCoplanarPoint( normal, a ); + + return this; + + } + + /** + * Copies the values of the given plane to this instance. + * + * @param {Plane} plane - The plane to copy. + * @return {Plane} A reference to this plane. + */ + copy( plane ) { + + this.normal.copy( plane.normal ); + this.constant = plane.constant; + + return this; + + } + + /** + * Normalizes the plane normal and adjusts the constant accordingly. + * + * @return {Plane} A reference to this plane. + */ + normalize() { + + // Note: will lead to a divide by zero if the plane is invalid. + + const inverseNormalLength = 1.0 / this.normal.length(); + this.normal.multiplyScalar( inverseNormalLength ); + this.constant *= inverseNormalLength; + + return this; + + } + + /** + * Negates both the plane normal and the constant. + * + * @return {Plane} A reference to this plane. + */ + negate() { + + this.constant *= -1; + this.normal.negate(); + + return this; + + } + + /** + * Returns the signed distance from the given point to this plane. + * + * @param {Vector3} point - The point to compute the distance for. + * @return {number} The signed distance. + */ + distanceToPoint( point ) { + + return this.normal.dot( point ) + this.constant; + + } + + /** + * Returns the signed distance from the given sphere to this plane. + * + * @param {Sphere} sphere - The sphere to compute the distance for. + * @return {number} The signed distance. + */ + distanceToSphere( sphere ) { + + return this.distanceToPoint( sphere.center ) - sphere.radius; + + } + + /** + * Projects a the given point onto the plane. + * + * @param {Vector3} point - The point to project. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The projected point on the plane. + */ + projectPoint( point, target ) { + + return target.copy( point ).addScaledVector( this.normal, - this.distanceToPoint( point ) ); + + } + + /** + * Returns the intersection point of the passed line and the plane. Returns + * `null` if the line does not intersect. Returns the line's starting point if + * the line is coplanar with the plane. + * + * @param {Line3} line - The line to compute the intersection for. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {?Vector3} The intersection point. + */ + intersectLine( line, target ) { + + const direction = line.delta( _vector1 ); + + const denominator = this.normal.dot( direction ); + + if ( denominator === 0 ) { + + // line is coplanar, return origin + if ( this.distanceToPoint( line.start ) === 0 ) { + + return target.copy( line.start ); + + } + + // Unsure if this is the correct method to handle this case. + return null; + + } + + const t = - ( line.start.dot( this.normal ) + this.constant ) / denominator; + + if ( t < 0 || t > 1 ) { + + return null; + + } + + return target.copy( line.start ).addScaledVector( direction, t ); + + } + + /** + * Returns `true` if the given line segment intersects with (passes through) the plane. + * + * @param {Line3} line - The line to test. + * @return {boolean} Whether the given line segment intersects with the plane or not. + */ + intersectsLine( line ) { + + // Note: this tests if a line intersects the plane, not whether it (or its end-points) are coplanar with it. + + const startSign = this.distanceToPoint( line.start ); + const endSign = this.distanceToPoint( line.end ); + + return ( startSign < 0 && endSign > 0 ) || ( endSign < 0 && startSign > 0 ); + + } + + /** + * Returns `true` if the given bounding box intersects with the plane. + * + * @param {Box3} box - The bounding box to test. + * @return {boolean} Whether the given bounding box intersects with the plane or not. + */ + intersectsBox( box ) { + + return box.intersectsPlane( this ); + + } + + /** + * Returns `true` if the given bounding sphere intersects with the plane. + * + * @param {Sphere} sphere - The bounding sphere to test. + * @return {boolean} Whether the given bounding sphere intersects with the plane or not. + */ + intersectsSphere( sphere ) { + + return sphere.intersectsPlane( this ); + + } + + /** + * Returns a coplanar vector to the plane, by calculating the + * projection of the normal at the origin onto the plane. + * + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The coplanar point. + */ + coplanarPoint( target ) { + + return target.copy( this.normal ).multiplyScalar( - this.constant ); + + } + + /** + * Apply a 4x4 matrix to the plane. The matrix must be an affine, homogeneous transform. + * + * The optional normal matrix can be pre-computed like so: + * ```js + * const optionalNormalMatrix = new THREE.Matrix3().getNormalMatrix( matrix ); + * ``` + * + * @param {Matrix4} matrix - The transformation matrix. + * @param {Matrix4} [optionalNormalMatrix] - A pre-computed normal matrix. + * @return {Plane} A reference to this plane. + */ + applyMatrix4( matrix, optionalNormalMatrix ) { + + const normalMatrix = optionalNormalMatrix || _normalMatrix.getNormalMatrix( matrix ); + + const referencePoint = this.coplanarPoint( _vector1 ).applyMatrix4( matrix ); + + const normal = this.normal.applyMatrix3( normalMatrix ).normalize(); + + this.constant = - referencePoint.dot( normal ); + + return this; + + } + + /** + * Translates the plane by the distance defined by the given offset vector. + * Note that this only affects the plane constant and will not affect the normal vector. + * + * @param {Vector3} offset - The offset vector. + * @return {Plane} A reference to this plane. + */ + translate( offset ) { + + this.constant -= offset.dot( this.normal ); + + return this; + + } + + /** + * Returns `true` if this plane is equal with the given one. + * + * @param {Plane} plane - The plane to test for equality. + * @return {boolean} Whether this plane is equal with the given one. + */ + equals( plane ) { + + return plane.normal.equals( this.normal ) && ( plane.constant === this.constant ); + + } + + /** + * Returns a new plane with copied values from this instance. + * + * @return {Plane} A clone of this instance. + */ + clone() { + + return new this.constructor().copy( this ); + + } + +} + +const _sphere$3 = /*@__PURE__*/ new Sphere(); +const _defaultSpriteCenter = /*@__PURE__*/ new Vector2( 0.5, 0.5 ); +const _vector$6 = /*@__PURE__*/ new Vector3(); + +/** + * Frustums are used to determine what is inside the camera's field of view. + * They help speed up the rendering process - objects which lie outside a camera's + * frustum can safely be excluded from rendering. + * + * This class is mainly intended for use internally by a renderer. + */ +class Frustum { + + /** + * Constructs a new frustum. + * + * @param {Plane} [p0] - The first plane that encloses the frustum. + * @param {Plane} [p1] - The second plane that encloses the frustum. + * @param {Plane} [p2] - The third plane that encloses the frustum. + * @param {Plane} [p3] - The fourth plane that encloses the frustum. + * @param {Plane} [p4] - The fifth plane that encloses the frustum. + * @param {Plane} [p5] - The sixth plane that encloses the frustum. + */ + constructor( p0 = new Plane(), p1 = new Plane(), p2 = new Plane(), p3 = new Plane(), p4 = new Plane(), p5 = new Plane() ) { + + /** + * This array holds the planes that enclose the frustum. + * + * @type {Array} + */ + this.planes = [ p0, p1, p2, p3, p4, p5 ]; + + } + + /** + * Sets the frustum planes by copying the given planes. + * + * @param {Plane} [p0] - The first plane that encloses the frustum. + * @param {Plane} [p1] - The second plane that encloses the frustum. + * @param {Plane} [p2] - The third plane that encloses the frustum. + * @param {Plane} [p3] - The fourth plane that encloses the frustum. + * @param {Plane} [p4] - The fifth plane that encloses the frustum. + * @param {Plane} [p5] - The sixth plane that encloses the frustum. + * @return {Frustum} A reference to this frustum. + */ + set( p0, p1, p2, p3, p4, p5 ) { + + const planes = this.planes; + + planes[ 0 ].copy( p0 ); + planes[ 1 ].copy( p1 ); + planes[ 2 ].copy( p2 ); + planes[ 3 ].copy( p3 ); + planes[ 4 ].copy( p4 ); + planes[ 5 ].copy( p5 ); + + return this; + + } + + /** + * Copies the values of the given frustum to this instance. + * + * @param {Frustum} frustum - The frustum to copy. + * @return {Frustum} A reference to this frustum. + */ + copy( frustum ) { + + const planes = this.planes; + + for ( let i = 0; i < 6; i ++ ) { + + planes[ i ].copy( frustum.planes[ i ] ); + + } + + return this; + + } + + /** + * Sets the frustum planes from the given projection matrix. + * + * @param {Matrix4} m - The projection matrix. + * @param {(WebGLCoordinateSystem|WebGPUCoordinateSystem)} coordinateSystem - The coordinate system. + * @return {Frustum} A reference to this frustum. + */ + setFromProjectionMatrix( m, coordinateSystem = WebGLCoordinateSystem ) { + + const planes = this.planes; + const me = m.elements; + const me0 = me[ 0 ], me1 = me[ 1 ], me2 = me[ 2 ], me3 = me[ 3 ]; + const me4 = me[ 4 ], me5 = me[ 5 ], me6 = me[ 6 ], me7 = me[ 7 ]; + const me8 = me[ 8 ], me9 = me[ 9 ], me10 = me[ 10 ], me11 = me[ 11 ]; + const me12 = me[ 12 ], me13 = me[ 13 ], me14 = me[ 14 ], me15 = me[ 15 ]; + + planes[ 0 ].setComponents( me3 - me0, me7 - me4, me11 - me8, me15 - me12 ).normalize(); + planes[ 1 ].setComponents( me3 + me0, me7 + me4, me11 + me8, me15 + me12 ).normalize(); + planes[ 2 ].setComponents( me3 + me1, me7 + me5, me11 + me9, me15 + me13 ).normalize(); + planes[ 3 ].setComponents( me3 - me1, me7 - me5, me11 - me9, me15 - me13 ).normalize(); + planes[ 4 ].setComponents( me3 - me2, me7 - me6, me11 - me10, me15 - me14 ).normalize(); + + if ( coordinateSystem === WebGLCoordinateSystem ) { + + planes[ 5 ].setComponents( me3 + me2, me7 + me6, me11 + me10, me15 + me14 ).normalize(); + + } else if ( coordinateSystem === WebGPUCoordinateSystem ) { + + planes[ 5 ].setComponents( me2, me6, me10, me14 ).normalize(); + + } else { + + throw new Error( 'THREE.Frustum.setFromProjectionMatrix(): Invalid coordinate system: ' + coordinateSystem ); + + } + + return this; + + } + + /** + * Returns `true` if the 3D object's bounding sphere is intersecting this frustum. + * + * Note that the 3D object must have a geometry so that the bounding sphere can be calculated. + * + * @param {Object3D} object - The 3D object to test. + * @return {boolean} Whether the 3D object's bounding sphere is intersecting this frustum or not. + */ + intersectsObject( object ) { + + if ( object.boundingSphere !== undefined ) { + + if ( object.boundingSphere === null ) object.computeBoundingSphere(); + + _sphere$3.copy( object.boundingSphere ).applyMatrix4( object.matrixWorld ); + + } else { + + const geometry = object.geometry; + + if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); + + _sphere$3.copy( geometry.boundingSphere ).applyMatrix4( object.matrixWorld ); + + } + + return this.intersectsSphere( _sphere$3 ); + + } + + /** + * Returns `true` if the given sprite is intersecting this frustum. + * + * @param {Sprite} sprite - The sprite to test. + * @return {boolean} Whether the sprite is intersecting this frustum or not. + */ + intersectsSprite( sprite ) { + + _sphere$3.center.set( 0, 0, 0 ); + + const offset = _defaultSpriteCenter.distanceTo( sprite.center ); + + _sphere$3.radius = 0.7071067811865476 + offset; + _sphere$3.applyMatrix4( sprite.matrixWorld ); + + return this.intersectsSphere( _sphere$3 ); + + } + + /** + * Returns `true` if the given bounding sphere is intersecting this frustum. + * + * @param {Sphere} sphere - The bounding sphere to test. + * @return {boolean} Whether the bounding sphere is intersecting this frustum or not. + */ + intersectsSphere( sphere ) { + + const planes = this.planes; + const center = sphere.center; + const negRadius = - sphere.radius; + + for ( let i = 0; i < 6; i ++ ) { + + const distance = planes[ i ].distanceToPoint( center ); + + if ( distance < negRadius ) { + + return false; + + } + + } + + return true; + + } + + /** + * Returns `true` if the given bounding box is intersecting this frustum. + * + * @param {Box3} box - The bounding box to test. + * @return {boolean} Whether the bounding box is intersecting this frustum or not. + */ + intersectsBox( box ) { + + const planes = this.planes; + + for ( let i = 0; i < 6; i ++ ) { + + const plane = planes[ i ]; + + // corner at max distance + + _vector$6.x = plane.normal.x > 0 ? box.max.x : box.min.x; + _vector$6.y = plane.normal.y > 0 ? box.max.y : box.min.y; + _vector$6.z = plane.normal.z > 0 ? box.max.z : box.min.z; + + if ( plane.distanceToPoint( _vector$6 ) < 0 ) { + + return false; + + } + + } + + return true; + + } + + /** + * Returns `true` if the given point lies within the frustum. + * + * @param {Vector3} point - The point to test. + * @return {boolean} Whether the point lies within this frustum or not. + */ + containsPoint( point ) { + + const planes = this.planes; + + for ( let i = 0; i < 6; i ++ ) { + + if ( planes[ i ].distanceToPoint( point ) < 0 ) { + + return false; + + } + + } + + return true; + + } + + /** + * Returns a new frustum with copied values from this instance. + * + * @return {Frustum} A clone of this instance. + */ + clone() { + + return new this.constructor().copy( this ); + + } + +} + +const _projScreenMatrix$2 = /*@__PURE__*/ new Matrix4(); +const _frustum$1 = /*@__PURE__*/ new Frustum(); + +/** + * FrustumArray is used to determine if an object is visible in at least one camera + * from an array of cameras. This is particularly useful for multi-view renderers. +*/ +class FrustumArray { + + /** + * Constructs a new frustum array. + * + */ + constructor() { + + /** + * The coordinate system to use. + * + * @type {WebGLCoordinateSystem|WebGPUCoordinateSystem} + * @default WebGLCoordinateSystem + */ + this.coordinateSystem = WebGLCoordinateSystem; + + } + + /** + * Returns `true` if the 3D object's bounding sphere is intersecting any frustum + * from the camera array. + * + * @param {Object3D} object - The 3D object to test. + * @param {Object} cameraArray - An object with a cameras property containing an array of cameras. + * @return {boolean} Whether the 3D object is visible in any camera. + */ + intersectsObject( object, cameraArray ) { + + if ( ! cameraArray.isArrayCamera || cameraArray.cameras.length === 0 ) { + + return false; + + } + + for ( let i = 0; i < cameraArray.cameras.length; i ++ ) { + + const camera = cameraArray.cameras[ i ]; + + _projScreenMatrix$2.multiplyMatrices( + camera.projectionMatrix, + camera.matrixWorldInverse + ); + + _frustum$1.setFromProjectionMatrix( + _projScreenMatrix$2, + this.coordinateSystem + ); + + if ( _frustum$1.intersectsObject( object ) ) { + + return true; // Object is visible in at least one camera + + } + + } + + return false; // Not visible in any camera + + } + + /** + * Returns `true` if the given sprite is intersecting any frustum + * from the camera array. + * + * @param {Sprite} sprite - The sprite to test. + * @param {Object} cameraArray - An object with a cameras property containing an array of cameras. + * @return {boolean} Whether the sprite is visible in any camera. + */ + intersectsSprite( sprite, cameraArray ) { + + if ( ! cameraArray || ! cameraArray.cameras || cameraArray.cameras.length === 0 ) { + + return false; + + } + + for ( let i = 0; i < cameraArray.cameras.length; i ++ ) { + + const camera = cameraArray.cameras[ i ]; + + _projScreenMatrix$2.multiplyMatrices( + camera.projectionMatrix, + camera.matrixWorldInverse + ); + + _frustum$1.setFromProjectionMatrix( + _projScreenMatrix$2, + this.coordinateSystem + ); + + if ( _frustum$1.intersectsSprite( sprite ) ) { + + return true; // Sprite is visible in at least one camera + + } + + } + + return false; // Not visible in any camera + + } + + /** + * Returns `true` if the given bounding sphere is intersecting any frustum + * from the camera array. + * + * @param {Sphere} sphere - The bounding sphere to test. + * @param {Object} cameraArray - An object with a cameras property containing an array of cameras. + * @return {boolean} Whether the sphere is visible in any camera. + */ + intersectsSphere( sphere, cameraArray ) { + + if ( ! cameraArray || ! cameraArray.cameras || cameraArray.cameras.length === 0 ) { + + return false; + + } + + for ( let i = 0; i < cameraArray.cameras.length; i ++ ) { + + const camera = cameraArray.cameras[ i ]; + + _projScreenMatrix$2.multiplyMatrices( + camera.projectionMatrix, + camera.matrixWorldInverse + ); + + _frustum$1.setFromProjectionMatrix( + _projScreenMatrix$2, + this.coordinateSystem + ); + + if ( _frustum$1.intersectsSphere( sphere ) ) { + + return true; // Sphere is visible in at least one camera + + } + + } + + return false; // Not visible in any camera + + } + + /** + * Returns `true` if the given bounding box is intersecting any frustum + * from the camera array. + * + * @param {Box3} box - The bounding box to test. + * @param {Object} cameraArray - An object with a cameras property containing an array of cameras. + * @return {boolean} Whether the box is visible in any camera. + */ + intersectsBox( box, cameraArray ) { + + if ( ! cameraArray || ! cameraArray.cameras || cameraArray.cameras.length === 0 ) { + + return false; + + } + + for ( let i = 0; i < cameraArray.cameras.length; i ++ ) { + + const camera = cameraArray.cameras[ i ]; + + _projScreenMatrix$2.multiplyMatrices( + camera.projectionMatrix, + camera.matrixWorldInverse + ); + + _frustum$1.setFromProjectionMatrix( + _projScreenMatrix$2, + this.coordinateSystem + ); + + if ( _frustum$1.intersectsBox( box ) ) { + + return true; // Box is visible in at least one camera + + } + + } + + return false; // Not visible in any camera + + } + + /** + * Returns `true` if the given point lies within any frustum + * from the camera array. + * + * @param {Vector3} point - The point to test. + * @param {Object} cameraArray - An object with a cameras property containing an array of cameras. + * @return {boolean} Whether the point is visible in any camera. + */ + containsPoint( point, cameraArray ) { + + if ( ! cameraArray || ! cameraArray.cameras || cameraArray.cameras.length === 0 ) { + + return false; + + } + + for ( let i = 0; i < cameraArray.cameras.length; i ++ ) { + + const camera = cameraArray.cameras[ i ]; + + _projScreenMatrix$2.multiplyMatrices( + camera.projectionMatrix, + camera.matrixWorldInverse + ); + + _frustum$1.setFromProjectionMatrix( + _projScreenMatrix$2, + this.coordinateSystem + ); + + if ( _frustum$1.containsPoint( point ) ) { + + return true; // Point is visible in at least one camera + + } + + } + + return false; // Not visible in any camera + + } + + /** + * Returns a new frustum array with copied values from this instance. + * + * @return {FrustumArray} A clone of this instance. + */ + clone() { + + return new FrustumArray(); + + } + +} + +function ascIdSort( a, b ) { + + return a - b; + +} + +function sortOpaque( a, b ) { + + return a.z - b.z; + +} + +function sortTransparent( a, b ) { + + return b.z - a.z; + +} + +class MultiDrawRenderList { + + constructor() { + + this.index = 0; + this.pool = []; + this.list = []; + + } - background_vert: vertex$h, - background_frag: fragment$h, - backgroundCube_vert: vertex$g, - backgroundCube_frag: fragment$g, - cube_vert: vertex$f, - cube_frag: fragment$f, - depth_vert: vertex$e, - depth_frag: fragment$e, - distanceRGBA_vert: vertex$d, - distanceRGBA_frag: fragment$d, - equirect_vert: vertex$c, - equirect_frag: fragment$c, - linedashed_vert: vertex$b, - linedashed_frag: fragment$b, - meshbasic_vert: vertex$a, - meshbasic_frag: fragment$a, - meshlambert_vert: vertex$9, - meshlambert_frag: fragment$9, - meshmatcap_vert: vertex$8, - meshmatcap_frag: fragment$8, - meshnormal_vert: vertex$7, - meshnormal_frag: fragment$7, - meshphong_vert: vertex$6, - meshphong_frag: fragment$6, - meshphysical_vert: vertex$5, - meshphysical_frag: fragment$5, - meshtoon_vert: vertex$4, - meshtoon_frag: fragment$4, - points_vert: vertex$3, - points_frag: fragment$3, - shadow_vert: vertex$2, - shadow_frag: fragment$2, - sprite_vert: vertex$1, - sprite_frag: fragment$1 -}; + push( start, count, z, index ) { + + const pool = this.pool; + const list = this.list; + if ( this.index >= pool.length ) { + + pool.push( { + + start: -1, + count: -1, + z: -1, + index: -1, + + } ); + + } + + const item = pool[ this.index ]; + list.push( item ); + this.index ++; + + item.start = start; + item.count = count; + item.z = z; + item.index = index; + + } + + reset() { + + this.list.length = 0; + this.index = 0; + + } + +} + +const _matrix$1 = /*@__PURE__*/ new Matrix4(); +const _whiteColor = /*@__PURE__*/ new Color( 1, 1, 1 ); +const _frustum = /*@__PURE__*/ new Frustum(); +const _frustumArray = /*@__PURE__*/ new FrustumArray(); +const _box$1 = /*@__PURE__*/ new Box3(); +const _sphere$2 = /*@__PURE__*/ new Sphere(); +const _vector$5 = /*@__PURE__*/ new Vector3(); +const _forward$1 = /*@__PURE__*/ new Vector3(); +const _temp = /*@__PURE__*/ new Vector3(); +const _renderList = /*@__PURE__*/ new MultiDrawRenderList(); +const _mesh = /*@__PURE__*/ new Mesh(); +const _batchIntersects = []; + +// copies data from attribute "src" into "target" starting at "targetOffset" +function copyAttributeData( src, target, targetOffset = 0 ) { + + const itemSize = target.itemSize; + if ( src.isInterleavedBufferAttribute || src.array.constructor !== target.array.constructor ) { + + // use the component getters and setters if the array data cannot + // be copied directly + const vertexCount = src.count; + for ( let i = 0; i < vertexCount; i ++ ) { + + for ( let c = 0; c < itemSize; c ++ ) { + + target.setComponent( i + targetOffset, c, src.getComponent( i, c ) ); + + } + + } + + } else { + + // faster copy approach using typed array set function + target.array.set( src.array, targetOffset * itemSize ); + + } + + target.needsUpdate = true; + +} + +// safely copies array contents to a potentially smaller array +function copyArrayContents( src, target ) { + + if ( src.constructor !== target.constructor ) { + + // if arrays are of a different type (eg due to index size increasing) then data must be per-element copied + const len = Math.min( src.length, target.length ); + for ( let i = 0; i < len; i ++ ) { + + target[ i ] = src[ i ]; + + } + + } else { + + // if the arrays use the same data layout we can use a fast block copy + const len = Math.min( src.length, target.length ); + target.set( new src.constructor( src.buffer, 0, len ) ); + + } + +} + +/** + * A special version of a mesh with multi draw batch rendering support. Use + * this class if you have to render a large number of objects with the same + * material but with different geometries or world transformations. The usage of + * `BatchedMesh` will help you to reduce the number of draw calls and thus improve the overall + * rendering performance in your application. + * + * ```js + * const box = new THREE.BoxGeometry( 1, 1, 1 ); + * const sphere = new THREE.SphereGeometry( 1, 12, 12 ); + * const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); + * + * // initialize and add geometries into the batched mesh + * const batchedMesh = new BatchedMesh( 10, 5000, 10000, material ); + * const boxGeometryId = batchedMesh.addGeometry( box ); + * const sphereGeometryId = batchedMesh.addGeometry( sphere ); + * + * // create instances of those geometries + * const boxInstancedId1 = batchedMesh.addInstance( boxGeometryId ); + * const boxInstancedId2 = batchedMesh.addInstance( boxGeometryId ); + * + * const sphereInstancedId1 = batchedMesh.addInstance( sphereGeometryId ); + * const sphereInstancedId2 = batchedMesh.addInstance( sphereGeometryId ); + * + * // position the geometries + * batchedMesh.setMatrixAt( boxInstancedId1, boxMatrix1 ); + * batchedMesh.setMatrixAt( boxInstancedId2, boxMatrix2 ); + * + * batchedMesh.setMatrixAt( sphereInstancedId1, sphereMatrix1 ); + * batchedMesh.setMatrixAt( sphereInstancedId2, sphereMatrix2 ); + * + * scene.add( batchedMesh ); + * ``` + * + * @augments Mesh + */ +class BatchedMesh extends Mesh { + + /** + * Constructs a new batched mesh. + * + * @param {number} maxInstanceCount - The maximum number of individual instances planned to be added and rendered. + * @param {number} maxVertexCount - The maximum number of vertices to be used by all unique geometries. + * @param {number} [maxIndexCount=maxVertexCount*2] - The maximum number of indices to be used by all unique geometries + * @param {Material|Array} [material] - The mesh material. + */ + constructor( maxInstanceCount, maxVertexCount, maxIndexCount = maxVertexCount * 2, material ) { + + super( new BufferGeometry(), material ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isBatchedMesh = true; + + /** + * When set ot `true`, the individual objects of a batch are frustum culled. + * + * @type {boolean} + * @default true + */ + this.perObjectFrustumCulled = true; + + /** + * When set to `true`, the individual objects of a batch are sorted to improve overdraw-related artifacts. + * If the material is marked as "transparent" objects are rendered back to front and if not then they are + * rendered front to back. + * + * @type {boolean} + * @default true + */ + this.sortObjects = true; + + /** + * The bounding box of the batched mesh. Can be computed via {@link BatchedMesh#computeBoundingBox}. + * + * @type {?Box3} + * @default null + */ + this.boundingBox = null; + + /** + * The bounding sphere of the batched mesh. Can be computed via {@link BatchedMesh#computeBoundingSphere}. + * + * @type {?Sphere} + * @default null + */ + this.boundingSphere = null; + + /** + * Takes a sort a function that is run before render. The function takes a list of instances to + * sort and a camera. The objects in the list include a "z" field to perform a depth-ordered + * sort with. + * + * @type {?Function} + * @default null + */ + this.customSort = null; + + // stores visible, active, and geometry id per instance and reserved buffer ranges for geometries + this._instanceInfo = []; + this._geometryInfo = []; + + // instance, geometry ids that have been set as inactive, and are available to be overwritten + this._availableInstanceIds = []; + this._availableGeometryIds = []; + + // used to track where the next point is that geometry should be inserted + this._nextIndexStart = 0; + this._nextVertexStart = 0; + this._geometryCount = 0; + + // flags + this._visibilityChanged = true; + this._geometryInitialized = false; + + // cached user options + this._maxInstanceCount = maxInstanceCount; + this._maxVertexCount = maxVertexCount; + this._maxIndexCount = maxIndexCount; + + // buffers for multi draw + this._multiDrawCounts = new Int32Array( maxInstanceCount ); + this._multiDrawStarts = new Int32Array( maxInstanceCount ); + this._multiDrawCount = 0; + this._multiDrawInstances = null; + + // Local matrix per geometry by using data texture + this._matricesTexture = null; + this._indirectTexture = null; + this._colorsTexture = null; + + this._initMatricesTexture(); + this._initIndirectTexture(); + + } + + /** + * The maximum number of individual instances that can be stored in the batch. + * + * @type {number} + * @readonly + */ + get maxInstanceCount() { + + return this._maxInstanceCount; + + } + + /** + * The instance count. + * + * @type {number} + * @readonly + */ + get instanceCount() { + + return this._instanceInfo.length - this._availableInstanceIds.length; + + } + + /** + * The number of unused vertices. + * + * @type {number} + * @readonly + */ + get unusedVertexCount() { + + return this._maxVertexCount - this._nextVertexStart; + + } + + /** + * The number of unused indices. + * + * @type {number} + * @readonly + */ + get unusedIndexCount() { + + return this._maxIndexCount - this._nextIndexStart; + + } + + _initMatricesTexture() { + + // layout (1 matrix = 4 pixels) + // RGBA RGBA RGBA RGBA (=> column1, column2, column3, column4) + // with 8x8 pixel texture max 16 matrices * 4 pixels = (8 * 8) + // 16x16 pixel texture max 64 matrices * 4 pixels = (16 * 16) + // 32x32 pixel texture max 256 matrices * 4 pixels = (32 * 32) + // 64x64 pixel texture max 1024 matrices * 4 pixels = (64 * 64) + + let size = Math.sqrt( this._maxInstanceCount * 4 ); // 4 pixels needed for 1 matrix + size = Math.ceil( size / 4 ) * 4; + size = Math.max( size, 4 ); + + const matricesArray = new Float32Array( size * size * 4 ); // 4 floats per RGBA pixel + const matricesTexture = new DataTexture( matricesArray, size, size, RGBAFormat, FloatType ); + + this._matricesTexture = matricesTexture; + + } + + _initIndirectTexture() { + + let size = Math.sqrt( this._maxInstanceCount ); + size = Math.ceil( size ); + + const indirectArray = new Uint32Array( size * size ); + const indirectTexture = new DataTexture( indirectArray, size, size, RedIntegerFormat, UnsignedIntType ); + + this._indirectTexture = indirectTexture; + + } + + _initColorsTexture() { + + let size = Math.sqrt( this._maxInstanceCount ); + size = Math.ceil( size ); + + // 4 floats per RGBA pixel initialized to white + const colorsArray = new Float32Array( size * size * 4 ).fill( 1 ); + const colorsTexture = new DataTexture( colorsArray, size, size, RGBAFormat, FloatType ); + colorsTexture.colorSpace = ColorManagement.workingColorSpace; + + this._colorsTexture = colorsTexture; + + } + + _initializeGeometry( reference ) { + + const geometry = this.geometry; + const maxVertexCount = this._maxVertexCount; + const maxIndexCount = this._maxIndexCount; + if ( this._geometryInitialized === false ) { + + for ( const attributeName in reference.attributes ) { + + const srcAttribute = reference.getAttribute( attributeName ); + const { array, itemSize, normalized } = srcAttribute; + + const dstArray = new array.constructor( maxVertexCount * itemSize ); + const dstAttribute = new BufferAttribute( dstArray, itemSize, normalized ); + + geometry.setAttribute( attributeName, dstAttribute ); + + } + + if ( reference.getIndex() !== null ) { + + // Reserve last u16 index for primitive restart. + const indexArray = maxVertexCount > 65535 + ? new Uint32Array( maxIndexCount ) + : new Uint16Array( maxIndexCount ); + + geometry.setIndex( new BufferAttribute( indexArray, 1 ) ); + + } + + this._geometryInitialized = true; + + } + + } + + // Make sure the geometry is compatible with the existing combined geometry attributes + _validateGeometry( geometry ) { + + // check to ensure the geometries are using consistent attributes and indices + const batchGeometry = this.geometry; + if ( Boolean( geometry.getIndex() ) !== Boolean( batchGeometry.getIndex() ) ) { + + throw new Error( 'THREE.BatchedMesh: All geometries must consistently have "index".' ); + + } + + for ( const attributeName in batchGeometry.attributes ) { + + if ( ! geometry.hasAttribute( attributeName ) ) { + + throw new Error( `THREE.BatchedMesh: Added geometry missing "${ attributeName }". All geometries must have consistent attributes.` ); + + } + + const srcAttribute = geometry.getAttribute( attributeName ); + const dstAttribute = batchGeometry.getAttribute( attributeName ); + if ( srcAttribute.itemSize !== dstAttribute.itemSize || srcAttribute.normalized !== dstAttribute.normalized ) { + + throw new Error( 'THREE.BatchedMesh: All attributes must have a consistent itemSize and normalized value.' ); + + } + + } + + } + + /** + * Validates the instance defined by the given ID. + * + * @param {number} instanceId - The instance to validate. + */ + validateInstanceId( instanceId ) { + + const instanceInfo = this._instanceInfo; + if ( instanceId < 0 || instanceId >= instanceInfo.length || instanceInfo[ instanceId ].active === false ) { + + throw new Error( `THREE.BatchedMesh: Invalid instanceId ${instanceId}. Instance is either out of range or has been deleted.` ); + + } + + } + + /** + * Validates the geometry defined by the given ID. + * + * @param {number} geometryId - The geometry to validate. + */ + validateGeometryId( geometryId ) { + + const geometryInfoList = this._geometryInfo; + if ( geometryId < 0 || geometryId >= geometryInfoList.length || geometryInfoList[ geometryId ].active === false ) { + + throw new Error( `THREE.BatchedMesh: Invalid geometryId ${geometryId}. Geometry is either out of range or has been deleted.` ); + + } + + } + + /** + * Takes a sort a function that is run before render. The function takes a list of instances to + * sort and a camera. The objects in the list include a "z" field to perform a depth-ordered sort with. + * + * @param {Function} func - The custom sort function. + * @return {BatchedMesh} A reference to this batched mesh. + */ + setCustomSort( func ) { + + this.customSort = func; + return this; + + } + + /** + * Computes the bounding box, updating {@link BatchedMesh#boundingBox}. + * Bounding boxes aren't computed by default. They need to be explicitly computed, + * otherwise they are `null`. + */ + computeBoundingBox() { + + if ( this.boundingBox === null ) { + + this.boundingBox = new Box3(); + + } + + const boundingBox = this.boundingBox; + const instanceInfo = this._instanceInfo; + + boundingBox.makeEmpty(); + for ( let i = 0, l = instanceInfo.length; i < l; i ++ ) { + + if ( instanceInfo[ i ].active === false ) continue; + + const geometryId = instanceInfo[ i ].geometryIndex; + this.getMatrixAt( i, _matrix$1 ); + this.getBoundingBoxAt( geometryId, _box$1 ).applyMatrix4( _matrix$1 ); + boundingBox.union( _box$1 ); + + } + + } + + /** + * Computes the bounding sphere, updating {@link BatchedMesh#boundingSphere}. + * Bounding spheres aren't computed by default. They need to be explicitly computed, + * otherwise they are `null`. + */ + computeBoundingSphere() { + + if ( this.boundingSphere === null ) { + + this.boundingSphere = new Sphere(); + + } + + const boundingSphere = this.boundingSphere; + const instanceInfo = this._instanceInfo; + + boundingSphere.makeEmpty(); + for ( let i = 0, l = instanceInfo.length; i < l; i ++ ) { + + if ( instanceInfo[ i ].active === false ) continue; + + const geometryId = instanceInfo[ i ].geometryIndex; + this.getMatrixAt( i, _matrix$1 ); + this.getBoundingSphereAt( geometryId, _sphere$2 ).applyMatrix4( _matrix$1 ); + boundingSphere.union( _sphere$2 ); + + } + + } + + /** + * Adds a new instance to the batch using the geometry of the given ID and returns + * a new id referring to the new instance to be used by other functions. + * + * @param {number} geometryId - The ID of a previously added geometry via {@link BatchedMesh#addGeometry}. + * @return {number} The instance ID. + */ + addInstance( geometryId ) { + + const atCapacity = this._instanceInfo.length >= this.maxInstanceCount; + + // ensure we're not over geometry + if ( atCapacity && this._availableInstanceIds.length === 0 ) { + + throw new Error( 'THREE.BatchedMesh: Maximum item count reached.' ); + + } + + const instanceInfo = { + visible: true, + active: true, + geometryIndex: geometryId, + }; + + let drawId = null; + + // Prioritize using previously freed instance ids + if ( this._availableInstanceIds.length > 0 ) { + + this._availableInstanceIds.sort( ascIdSort ); + + drawId = this._availableInstanceIds.shift(); + this._instanceInfo[ drawId ] = instanceInfo; + + } else { + + drawId = this._instanceInfo.length; + this._instanceInfo.push( instanceInfo ); + + } + + const matricesTexture = this._matricesTexture; + _matrix$1.identity().toArray( matricesTexture.image.data, drawId * 16 ); + matricesTexture.needsUpdate = true; + + const colorsTexture = this._colorsTexture; + if ( colorsTexture ) { + + _whiteColor.toArray( colorsTexture.image.data, drawId * 4 ); + colorsTexture.needsUpdate = true; + + } + + this._visibilityChanged = true; + return drawId; + + } + + /** + * Adds the given geometry to the batch and returns the associated + * geometry id referring to it to be used in other functions. + * + * @param {BufferGeometry} geometry - The geometry to add. + * @param {number} [reservedVertexCount=-1] - Optional parameter specifying the amount of + * vertex buffer space to reserve for the added geometry. This is necessary if it is planned + * to set a new geometry at this index at a later time that is larger than the original geometry. + * Defaults to the length of the given geometry vertex buffer. + * @param {number} [reservedIndexCount=-1] - Optional parameter specifying the amount of index + * buffer space to reserve for the added geometry. This is necessary if it is planned to set a + * new geometry at this index at a later time that is larger than the original geometry. Defaults to + * the length of the given geometry index buffer. + * @return {number} The geometry ID. + */ + addGeometry( geometry, reservedVertexCount = -1, reservedIndexCount = -1 ) { + + this._initializeGeometry( geometry ); + + this._validateGeometry( geometry ); + + const geometryInfo = { + // geometry information + vertexStart: -1, + vertexCount: -1, + reservedVertexCount: -1, + + indexStart: -1, + indexCount: -1, + reservedIndexCount: -1, + + // draw range information + start: -1, + count: -1, + + // state + boundingBox: null, + boundingSphere: null, + active: true, + }; + + const geometryInfoList = this._geometryInfo; + geometryInfo.vertexStart = this._nextVertexStart; + geometryInfo.reservedVertexCount = reservedVertexCount === -1 ? geometry.getAttribute( 'position' ).count : reservedVertexCount; + + const index = geometry.getIndex(); + const hasIndex = index !== null; + if ( hasIndex ) { + + geometryInfo.indexStart = this._nextIndexStart; + geometryInfo.reservedIndexCount = reservedIndexCount === -1 ? index.count : reservedIndexCount; + + } + + if ( + geometryInfo.indexStart !== -1 && + geometryInfo.indexStart + geometryInfo.reservedIndexCount > this._maxIndexCount || + geometryInfo.vertexStart + geometryInfo.reservedVertexCount > this._maxVertexCount + ) { + + throw new Error( 'THREE.BatchedMesh: Reserved space request exceeds the maximum buffer size.' ); + + } + + // update id + let geometryId; + if ( this._availableGeometryIds.length > 0 ) { + + this._availableGeometryIds.sort( ascIdSort ); + + geometryId = this._availableGeometryIds.shift(); + geometryInfoList[ geometryId ] = geometryInfo; + + + } else { + + geometryId = this._geometryCount; + this._geometryCount ++; + geometryInfoList.push( geometryInfo ); + + } + + // update the geometry + this.setGeometryAt( geometryId, geometry ); + + // increment the next geometry position + this._nextIndexStart = geometryInfo.indexStart + geometryInfo.reservedIndexCount; + this._nextVertexStart = geometryInfo.vertexStart + geometryInfo.reservedVertexCount; + + return geometryId; + + } + + /** + * Replaces the geometry at the given ID with the provided geometry. Throws an error if there + * is not enough space reserved for geometry. Calling this will change all instances that are + * rendering that geometry. + * + * @param {number} geometryId - The ID of the geometry that should be replaced with the given geometry. + * @param {BufferGeometry} geometry - The new geometry. + * @return {number} The geometry ID. + */ + setGeometryAt( geometryId, geometry ) { + + if ( geometryId >= this._geometryCount ) { + + throw new Error( 'THREE.BatchedMesh: Maximum geometry count reached.' ); + + } + + this._validateGeometry( geometry ); + + const batchGeometry = this.geometry; + const hasIndex = batchGeometry.getIndex() !== null; + const dstIndex = batchGeometry.getIndex(); + const srcIndex = geometry.getIndex(); + const geometryInfo = this._geometryInfo[ geometryId ]; + if ( + hasIndex && + srcIndex.count > geometryInfo.reservedIndexCount || + geometry.attributes.position.count > geometryInfo.reservedVertexCount + ) { + + throw new Error( 'THREE.BatchedMesh: Reserved space not large enough for provided geometry.' ); + + } + + // copy geometry buffer data over + const vertexStart = geometryInfo.vertexStart; + const reservedVertexCount = geometryInfo.reservedVertexCount; + geometryInfo.vertexCount = geometry.getAttribute( 'position' ).count; + + for ( const attributeName in batchGeometry.attributes ) { + + // copy attribute data + const srcAttribute = geometry.getAttribute( attributeName ); + const dstAttribute = batchGeometry.getAttribute( attributeName ); + copyAttributeData( srcAttribute, dstAttribute, vertexStart ); + + // fill the rest in with zeroes + const itemSize = srcAttribute.itemSize; + for ( let i = srcAttribute.count, l = reservedVertexCount; i < l; i ++ ) { + + const index = vertexStart + i; + for ( let c = 0; c < itemSize; c ++ ) { + + dstAttribute.setComponent( index, c, 0 ); + + } + + } + + dstAttribute.needsUpdate = true; + dstAttribute.addUpdateRange( vertexStart * itemSize, reservedVertexCount * itemSize ); + + } + + // copy index + if ( hasIndex ) { + + const indexStart = geometryInfo.indexStart; + const reservedIndexCount = geometryInfo.reservedIndexCount; + geometryInfo.indexCount = geometry.getIndex().count; + + // copy index data over + for ( let i = 0; i < srcIndex.count; i ++ ) { + + dstIndex.setX( indexStart + i, vertexStart + srcIndex.getX( i ) ); + + } + + // fill the rest in with zeroes + for ( let i = srcIndex.count, l = reservedIndexCount; i < l; i ++ ) { + + dstIndex.setX( indexStart + i, vertexStart ); + + } + + dstIndex.needsUpdate = true; + dstIndex.addUpdateRange( indexStart, geometryInfo.reservedIndexCount ); + + } + + // update the draw range + geometryInfo.start = hasIndex ? geometryInfo.indexStart : geometryInfo.vertexStart; + geometryInfo.count = hasIndex ? geometryInfo.indexCount : geometryInfo.vertexCount; + + // store the bounding boxes + geometryInfo.boundingBox = null; + if ( geometry.boundingBox !== null ) { + + geometryInfo.boundingBox = geometry.boundingBox.clone(); + + } + + geometryInfo.boundingSphere = null; + if ( geometry.boundingSphere !== null ) { + + geometryInfo.boundingSphere = geometry.boundingSphere.clone(); + + } + + this._visibilityChanged = true; + return geometryId; + + } + + /** + * Deletes the geometry defined by the given ID from this batch. Any instances referencing + * this geometry will also be removed as a side effect. + * + * @param {number} geometryId - The ID of the geometry to remove from the batch. + * @return {BatchedMesh} A reference to this batched mesh. + */ + deleteGeometry( geometryId ) { + + const geometryInfoList = this._geometryInfo; + if ( geometryId >= geometryInfoList.length || geometryInfoList[ geometryId ].active === false ) { + + return this; + + } + + // delete any instances associated with this geometry + const instanceInfo = this._instanceInfo; + for ( let i = 0, l = instanceInfo.length; i < l; i ++ ) { + + if ( instanceInfo[ i ].active && instanceInfo[ i ].geometryIndex === geometryId ) { + + this.deleteInstance( i ); + + } + + } + + geometryInfoList[ geometryId ].active = false; + this._availableGeometryIds.push( geometryId ); + this._visibilityChanged = true; + + return this; + + } + + /** + * Deletes an existing instance from the batch using the given ID. + * + * @param {number} instanceId - The ID of the instance to remove from the batch. + * @return {BatchedMesh} A reference to this batched mesh. + */ + deleteInstance( instanceId ) { + + this.validateInstanceId( instanceId ); + + this._instanceInfo[ instanceId ].active = false; + this._availableInstanceIds.push( instanceId ); + this._visibilityChanged = true; + + return this; + + } + + /** + * Repacks the sub geometries in [name] to remove any unused space remaining from + * previously deleted geometry, freeing up space to add new geometry. + * + * @param {number} instanceId - The ID of the instance to remove from the batch. + * @return {BatchedMesh} A reference to this batched mesh. + */ + optimize() { + + // track the next indices to copy data to + let nextVertexStart = 0; + let nextIndexStart = 0; + + // Iterate over all geometry ranges in order sorted from earliest in the geometry buffer to latest + // in the geometry buffer. Because draw range objects can be reused there is no guarantee of their order. + const geometryInfoList = this._geometryInfo; + const indices = geometryInfoList + .map( ( e, i ) => i ) + .sort( ( a, b ) => { + + return geometryInfoList[ a ].vertexStart - geometryInfoList[ b ].vertexStart; + + } ); + + const geometry = this.geometry; + for ( let i = 0, l = geometryInfoList.length; i < l; i ++ ) { + + // if a geometry range is inactive then don't copy anything + const index = indices[ i ]; + const geometryInfo = geometryInfoList[ index ]; + if ( geometryInfo.active === false ) { + + continue; + + } + + // if a geometry contains an index buffer then shift it, as well + if ( geometry.index !== null ) { + + if ( geometryInfo.indexStart !== nextIndexStart ) { + + const { indexStart, vertexStart, reservedIndexCount } = geometryInfo; + const index = geometry.index; + const array = index.array; + + // shift the index pointers based on how the vertex data will shift + // adjusting the index must happen first so the original vertex start value is available + const elementDelta = nextVertexStart - vertexStart; + for ( let j = indexStart; j < indexStart + reservedIndexCount; j ++ ) { + + array[ j ] = array[ j ] + elementDelta; + + } + + index.array.copyWithin( nextIndexStart, indexStart, indexStart + reservedIndexCount ); + index.addUpdateRange( nextIndexStart, reservedIndexCount ); + + geometryInfo.indexStart = nextIndexStart; + + } + + nextIndexStart += geometryInfo.reservedIndexCount; + + } + + // if a geometry needs to be moved then copy attribute data to overwrite unused space + if ( geometryInfo.vertexStart !== nextVertexStart ) { + + const { vertexStart, reservedVertexCount } = geometryInfo; + const attributes = geometry.attributes; + for ( const key in attributes ) { + + const attribute = attributes[ key ]; + const { array, itemSize } = attribute; + array.copyWithin( nextVertexStart * itemSize, vertexStart * itemSize, ( vertexStart + reservedVertexCount ) * itemSize ); + attribute.addUpdateRange( nextVertexStart * itemSize, reservedVertexCount * itemSize ); + + } + + geometryInfo.vertexStart = nextVertexStart; + + } + + nextVertexStart += geometryInfo.reservedVertexCount; + geometryInfo.start = geometry.index ? geometryInfo.indexStart : geometryInfo.vertexStart; + + // step the next geometry points to the shifted position + this._nextIndexStart = geometry.index ? geometryInfo.indexStart + geometryInfo.reservedIndexCount : 0; + this._nextVertexStart = geometryInfo.vertexStart + geometryInfo.reservedVertexCount; + + } + + return this; + + } + + /** + * Returns the bounding box for the given geometry. + * + * @param {number} geometryId - The ID of the geometry to return the bounding box for. + * @param {Box3} target - The target object that is used to store the method's result. + * @return {Box3|null} The geometry's bounding box. Returns `null` if no geometry has been found for the given ID. + */ + getBoundingBoxAt( geometryId, target ) { + + if ( geometryId >= this._geometryCount ) { + + return null; + + } + + // compute bounding box + const geometry = this.geometry; + const geometryInfo = this._geometryInfo[ geometryId ]; + if ( geometryInfo.boundingBox === null ) { + + const box = new Box3(); + const index = geometry.index; + const position = geometry.attributes.position; + for ( let i = geometryInfo.start, l = geometryInfo.start + geometryInfo.count; i < l; i ++ ) { + + let iv = i; + if ( index ) { + + iv = index.getX( iv ); + + } + + box.expandByPoint( _vector$5.fromBufferAttribute( position, iv ) ); + + } + + geometryInfo.boundingBox = box; + + } + + target.copy( geometryInfo.boundingBox ); + return target; + + } + + /** + * Returns the bounding sphere for the given geometry. + * + * @param {number} geometryId - The ID of the geometry to return the bounding sphere for. + * @param {Sphere} target - The target object that is used to store the method's result. + * @return {Sphere|null} The geometry's bounding sphere. Returns `null` if no geometry has been found for the given ID. + */ + getBoundingSphereAt( geometryId, target ) { + + if ( geometryId >= this._geometryCount ) { -/** - * Uniforms library for shared webgl shaders - */ + return null; -const UniformsLib = { + } - common: { + // compute bounding sphere + const geometry = this.geometry; + const geometryInfo = this._geometryInfo[ geometryId ]; + if ( geometryInfo.boundingSphere === null ) { - diffuse: { value: /*@__PURE__*/ new Color( 0xffffff ) }, - opacity: { value: 1.0 }, + const sphere = new Sphere(); + this.getBoundingBoxAt( geometryId, _box$1 ); + _box$1.getCenter( sphere.center ); - map: { value: null }, - mapTransform: { value: /*@__PURE__*/ new Matrix3() }, + const index = geometry.index; + const position = geometry.attributes.position; - alphaMap: { value: null }, - alphaMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + let maxRadiusSq = 0; + for ( let i = geometryInfo.start, l = geometryInfo.start + geometryInfo.count; i < l; i ++ ) { - alphaTest: { value: 0 } + let iv = i; + if ( index ) { - }, + iv = index.getX( iv ); - specularmap: { + } - specularMap: { value: null }, - specularMapTransform: { value: /*@__PURE__*/ new Matrix3() } + _vector$5.fromBufferAttribute( position, iv ); + maxRadiusSq = Math.max( maxRadiusSq, sphere.center.distanceToSquared( _vector$5 ) ); - }, + } - envmap: { + sphere.radius = Math.sqrt( maxRadiusSq ); + geometryInfo.boundingSphere = sphere; - envMap: { value: null }, - flipEnvMap: { value: - 1 }, - reflectivity: { value: 1.0 }, // basic, lambert, phong - ior: { value: 1.5 }, // physical - refractionRatio: { value: 0.98 }, // basic, lambert, phong + } - }, + target.copy( geometryInfo.boundingSphere ); + return target; - aomap: { + } - aoMap: { value: null }, - aoMapIntensity: { value: 1 }, - aoMapTransform: { value: /*@__PURE__*/ new Matrix3() } + /** + * Sets the given local transformation matrix to the defined instance. + * Negatively scaled matrices are not supported. + * + * @param {number} instanceId - The ID of an instance to set the matrix of. + * @param {Matrix4} matrix - A 4x4 matrix representing the local transformation of a single instance. + * @return {BatchedMesh} A reference to this batched mesh. + */ + setMatrixAt( instanceId, matrix ) { - }, + this.validateInstanceId( instanceId ); - lightmap: { + const matricesTexture = this._matricesTexture; + const matricesArray = this._matricesTexture.image.data; + matrix.toArray( matricesArray, instanceId * 16 ); + matricesTexture.needsUpdate = true; - lightMap: { value: null }, - lightMapIntensity: { value: 1 }, - lightMapTransform: { value: /*@__PURE__*/ new Matrix3() } + return this; - }, + } - bumpmap: { + /** + * Returns the local transformation matrix of the defined instance. + * + * @param {number} instanceId - The ID of an instance to get the matrix of. + * @param {Matrix4} matrix - The target object that is used to store the method's result. + * @return {Matrix4} The instance's local transformation matrix. + */ + getMatrixAt( instanceId, matrix ) { - bumpMap: { value: null }, - bumpMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - bumpScale: { value: 1 } + this.validateInstanceId( instanceId ); + return matrix.fromArray( this._matricesTexture.image.data, instanceId * 16 ); - }, + } - normalmap: { + /** + * Sets the given color to the defined instance. + * + * @param {number} instanceId - The ID of an instance to set the color of. + * @param {Color} color - The color to set the instance to. + * @return {BatchedMesh} A reference to this batched mesh. + */ + setColorAt( instanceId, color ) { - normalMap: { value: null }, - normalMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - normalScale: { value: /*@__PURE__*/ new Vector2( 1, 1 ) } + this.validateInstanceId( instanceId ); - }, + if ( this._colorsTexture === null ) { - displacementmap: { + this._initColorsTexture(); - displacementMap: { value: null }, - displacementMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - displacementScale: { value: 1 }, - displacementBias: { value: 0 } + } - }, + color.toArray( this._colorsTexture.image.data, instanceId * 4 ); + this._colorsTexture.needsUpdate = true; - emissivemap: { + return this; - emissiveMap: { value: null }, - emissiveMapTransform: { value: /*@__PURE__*/ new Matrix3() } + } - }, + /** + * Returns the color of the defined instance. + * + * @param {number} instanceId - The ID of an instance to get the color of. + * @param {Color} color - The target object that is used to store the method's result. + * @return {Color} The instance's color. + */ + getColorAt( instanceId, color ) { - metalnessmap: { + this.validateInstanceId( instanceId ); + return color.fromArray( this._colorsTexture.image.data, instanceId * 4 ); - metalnessMap: { value: null }, - metalnessMapTransform: { value: /*@__PURE__*/ new Matrix3() } + } - }, + /** + * Sets the visibility of the instance. + * + * @param {number} instanceId - The id of the instance to set the visibility of. + * @param {boolean} visible - Whether the instance is visible or not. + * @return {BatchedMesh} A reference to this batched mesh. + */ + setVisibleAt( instanceId, visible ) { - roughnessmap: { + this.validateInstanceId( instanceId ); - roughnessMap: { value: null }, - roughnessMapTransform: { value: /*@__PURE__*/ new Matrix3() } + if ( this._instanceInfo[ instanceId ].visible === visible ) { - }, + return this; - gradientmap: { + } - gradientMap: { value: null } + this._instanceInfo[ instanceId ].visible = visible; + this._visibilityChanged = true; - }, + return this; - fog: { + } - fogDensity: { value: 0.00025 }, - fogNear: { value: 1 }, - fogFar: { value: 2000 }, - fogColor: { value: /*@__PURE__*/ new Color( 0xffffff ) } + /** + * Returns the visibility state of the defined instance. + * + * @param {number} instanceId - The ID of an instance to get the visibility state of. + * @return {boolean} Whether the instance is visible or not. + */ + getVisibleAt( instanceId ) { - }, + this.validateInstanceId( instanceId ); - lights: { + return this._instanceInfo[ instanceId ].visible; - ambientLightColor: { value: [] }, + } - lightProbe: { value: [] }, + /** + * Sets the geometry ID of the instance at the given index. + * + * @param {number} instanceId - The ID of the instance to set the geometry ID of. + * @param {number} geometryId - The geometry ID to be use by the instance. + * @return {BatchedMesh} A reference to this batched mesh. + */ + setGeometryIdAt( instanceId, geometryId ) { - directionalLights: { value: [], properties: { - direction: {}, - color: {} - } }, + this.validateInstanceId( instanceId ); + this.validateGeometryId( geometryId ); - directionalLightShadows: { value: [], properties: { - shadowBias: {}, - shadowNormalBias: {}, - shadowRadius: {}, - shadowMapSize: {} - } }, + this._instanceInfo[ instanceId ].geometryIndex = geometryId; - directionalShadowMap: { value: [] }, - directionalShadowMatrix: { value: [] }, + return this; - spotLights: { value: [], properties: { - color: {}, - position: {}, - direction: {}, - distance: {}, - coneCos: {}, - penumbraCos: {}, - decay: {} - } }, + } - spotLightShadows: { value: [], properties: { - shadowBias: {}, - shadowNormalBias: {}, - shadowRadius: {}, - shadowMapSize: {} - } }, + /** + * Returns the geometry ID of the defined instance. + * + * @param {number} instanceId - The ID of an instance to get the geometry ID of. + * @return {number} The instance's geometry ID. + */ + getGeometryIdAt( instanceId ) { - spotLightMap: { value: [] }, - spotShadowMap: { value: [] }, - spotLightMatrix: { value: [] }, + this.validateInstanceId( instanceId ); - pointLights: { value: [], properties: { - color: {}, - position: {}, - decay: {}, - distance: {} - } }, + return this._instanceInfo[ instanceId ].geometryIndex; - pointLightShadows: { value: [], properties: { - shadowBias: {}, - shadowNormalBias: {}, - shadowRadius: {}, - shadowMapSize: {}, - shadowCameraNear: {}, - shadowCameraFar: {} - } }, + } - pointShadowMap: { value: [] }, - pointShadowMatrix: { value: [] }, + /** + * Get the range representing the subset of triangles related to the attached geometry, + * indicating the starting offset and count, or `null` if invalid. + * + * @param {number} geometryId - The id of the geometry to get the range of. + * @param {Object} [target] - The target object that is used to store the method's result. + * @return {{ + * vertexStart:number,vertexCount:number,reservedVertexCount:number, + * indexStart:number,indexCount:number,reservedIndexCount:number, + * start:number,count:number + * }} The result object with range data. + */ + getGeometryRangeAt( geometryId, target = {} ) { - hemisphereLights: { value: [], properties: { - direction: {}, - skyColor: {}, - groundColor: {} - } }, + this.validateGeometryId( geometryId ); - // TODO (abelnation): RectAreaLight BRDF data needs to be moved from example to main src - rectAreaLights: { value: [], properties: { - color: {}, - position: {}, - width: {}, - height: {} - } }, + const geometryInfo = this._geometryInfo[ geometryId ]; + target.vertexStart = geometryInfo.vertexStart; + target.vertexCount = geometryInfo.vertexCount; + target.reservedVertexCount = geometryInfo.reservedVertexCount; - ltc_1: { value: null }, - ltc_2: { value: null } + target.indexStart = geometryInfo.indexStart; + target.indexCount = geometryInfo.indexCount; + target.reservedIndexCount = geometryInfo.reservedIndexCount; - }, + target.start = geometryInfo.start; + target.count = geometryInfo.count; - points: { + return target; - diffuse: { value: /*@__PURE__*/ new Color( 0xffffff ) }, - opacity: { value: 1.0 }, - size: { value: 1.0 }, - scale: { value: 1.0 }, - map: { value: null }, - alphaMap: { value: null }, - alphaTest: { value: 0 }, - uvTransform: { value: /*@__PURE__*/ new Matrix3() } + } - }, + /** + * Resizes the necessary buffers to support the provided number of instances. + * If the provided arguments shrink the number of instances but there are not enough + * unused Ids at the end of the list then an error is thrown. + * + * @param {number} maxInstanceCount - The max number of individual instances that can be added and rendered by the batch. + */ + setInstanceCount( maxInstanceCount ) { - sprite: { + // shrink the available instances as much as possible + const availableInstanceIds = this._availableInstanceIds; + const instanceInfo = this._instanceInfo; + availableInstanceIds.sort( ascIdSort ); + while ( availableInstanceIds[ availableInstanceIds.length - 1 ] === instanceInfo.length ) { - diffuse: { value: /*@__PURE__*/ new Color( 0xffffff ) }, - opacity: { value: 1.0 }, - center: { value: /*@__PURE__*/ new Vector2( 0.5, 0.5 ) }, - rotation: { value: 0.0 }, - map: { value: null }, - mapTransform: { value: /*@__PURE__*/ new Matrix3() }, - alphaMap: { value: null }, - alphaTest: { value: 0 } + instanceInfo.pop(); + availableInstanceIds.pop(); + + } + + // throw an error if it can't be shrunk to the desired size + if ( maxInstanceCount < instanceInfo.length ) { + + throw new Error( `BatchedMesh: Instance ids outside the range ${ maxInstanceCount } are being used. Cannot shrink instance count.` ); + + } + + // copy the multi draw counts + const multiDrawCounts = new Int32Array( maxInstanceCount ); + const multiDrawStarts = new Int32Array( maxInstanceCount ); + copyArrayContents( this._multiDrawCounts, multiDrawCounts ); + copyArrayContents( this._multiDrawStarts, multiDrawStarts ); + + this._multiDrawCounts = multiDrawCounts; + this._multiDrawStarts = multiDrawStarts; + this._maxInstanceCount = maxInstanceCount; + + // update texture data for instance sampling + const indirectTexture = this._indirectTexture; + const matricesTexture = this._matricesTexture; + const colorsTexture = this._colorsTexture; + + indirectTexture.dispose(); + this._initIndirectTexture(); + copyArrayContents( indirectTexture.image.data, this._indirectTexture.image.data ); + + matricesTexture.dispose(); + this._initMatricesTexture(); + copyArrayContents( matricesTexture.image.data, this._matricesTexture.image.data ); + + if ( colorsTexture ) { + + colorsTexture.dispose(); + this._initColorsTexture(); + copyArrayContents( colorsTexture.image.data, this._colorsTexture.image.data ); + + } } -}; + /** + * Resizes the available space in the batch's vertex and index buffer attributes to the provided sizes. + * If the provided arguments shrink the geometry buffers but there is not enough unused space at the + * end of the geometry attributes then an error is thrown. + * + * @param {number} maxVertexCount - The maximum number of vertices to be used by all unique geometries to resize to. + * @param {number} maxIndexCount - The maximum number of indices to be used by all unique geometries to resize to. + */ + setGeometrySize( maxVertexCount, maxIndexCount ) { -const ShaderLib = { + // Check if we can shrink to the requested vertex attribute size + const validRanges = [ ...this._geometryInfo ].filter( info => info.active ); + const requiredVertexLength = Math.max( ...validRanges.map( range => range.vertexStart + range.reservedVertexCount ) ); + if ( requiredVertexLength > maxVertexCount ) { - basic: { + throw new Error( `BatchedMesh: Geometry vertex values are being used outside the range ${ maxIndexCount }. Cannot shrink further.` ); - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.specularmap, - UniformsLib.envmap, - UniformsLib.aomap, - UniformsLib.lightmap, - UniformsLib.fog - ] ), + } - vertexShader: ShaderChunk.meshbasic_vert, - fragmentShader: ShaderChunk.meshbasic_frag + // Check if we can shrink to the requested index attribute size + if ( this.geometry.index ) { - }, + const requiredIndexLength = Math.max( ...validRanges.map( range => range.indexStart + range.reservedIndexCount ) ); + if ( requiredIndexLength > maxIndexCount ) { - lambert: { + throw new Error( `BatchedMesh: Geometry index values are being used outside the range ${ maxIndexCount }. Cannot shrink further.` ); - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.specularmap, - UniformsLib.envmap, - UniformsLib.aomap, - UniformsLib.lightmap, - UniformsLib.emissivemap, - UniformsLib.bumpmap, - UniformsLib.normalmap, - UniformsLib.displacementmap, - UniformsLib.fog, - UniformsLib.lights, - { - emissive: { value: /*@__PURE__*/ new Color( 0x000000 ) } } - ] ), - vertexShader: ShaderChunk.meshlambert_vert, - fragmentShader: ShaderChunk.meshlambert_frag + } - }, + // - phong: { + // dispose of the previous geometry + const oldGeometry = this.geometry; + oldGeometry.dispose(); - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.specularmap, - UniformsLib.envmap, - UniformsLib.aomap, - UniformsLib.lightmap, - UniformsLib.emissivemap, - UniformsLib.bumpmap, - UniformsLib.normalmap, - UniformsLib.displacementmap, - UniformsLib.fog, - UniformsLib.lights, - { - emissive: { value: /*@__PURE__*/ new Color( 0x000000 ) }, - specular: { value: /*@__PURE__*/ new Color( 0x111111 ) }, - shininess: { value: 30 } - } - ] ), + // recreate the geometry needed based on the previous variant + this._maxVertexCount = maxVertexCount; + this._maxIndexCount = maxIndexCount; - vertexShader: ShaderChunk.meshphong_vert, - fragmentShader: ShaderChunk.meshphong_frag + if ( this._geometryInitialized ) { - }, + this._geometryInitialized = false; + this.geometry = new BufferGeometry(); + this._initializeGeometry( oldGeometry ); - standard: { + } + + // copy data from the previous geometry + const geometry = this.geometry; + if ( oldGeometry.index ) { + + copyArrayContents( oldGeometry.index.array, geometry.index.array ); + + } + + for ( const key in oldGeometry.attributes ) { + + copyArrayContents( oldGeometry.attributes[ key ].array, geometry.attributes[ key ].array ); + + } + + } + + raycast( raycaster, intersects ) { + + const instanceInfo = this._instanceInfo; + const geometryInfoList = this._geometryInfo; + const matrixWorld = this.matrixWorld; + const batchGeometry = this.geometry; + + // iterate over each geometry + _mesh.material = this.material; + _mesh.geometry.index = batchGeometry.index; + _mesh.geometry.attributes = batchGeometry.attributes; + if ( _mesh.geometry.boundingBox === null ) { + + _mesh.geometry.boundingBox = new Box3(); + + } + + if ( _mesh.geometry.boundingSphere === null ) { + + _mesh.geometry.boundingSphere = new Sphere(); + + } + + for ( let i = 0, l = instanceInfo.length; i < l; i ++ ) { + + if ( ! instanceInfo[ i ].visible || ! instanceInfo[ i ].active ) { + + continue; - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.envmap, - UniformsLib.aomap, - UniformsLib.lightmap, - UniformsLib.emissivemap, - UniformsLib.bumpmap, - UniformsLib.normalmap, - UniformsLib.displacementmap, - UniformsLib.roughnessmap, - UniformsLib.metalnessmap, - UniformsLib.fog, - UniformsLib.lights, - { - emissive: { value: /*@__PURE__*/ new Color( 0x000000 ) }, - roughness: { value: 1.0 }, - metalness: { value: 0.0 }, - envMapIntensity: { value: 1 } // temporary } - ] ), - vertexShader: ShaderChunk.meshphysical_vert, - fragmentShader: ShaderChunk.meshphysical_frag + const geometryId = instanceInfo[ i ].geometryIndex; + const geometryInfo = geometryInfoList[ geometryId ]; + _mesh.geometry.setDrawRange( geometryInfo.start, geometryInfo.count ); - }, + // get the intersects + this.getMatrixAt( i, _mesh.matrixWorld ).premultiply( matrixWorld ); + this.getBoundingBoxAt( geometryId, _mesh.geometry.boundingBox ); + this.getBoundingSphereAt( geometryId, _mesh.geometry.boundingSphere ); + _mesh.raycast( raycaster, _batchIntersects ); - toon: { + // add batch id to the intersects + for ( let j = 0, l = _batchIntersects.length; j < l; j ++ ) { + + const intersect = _batchIntersects[ j ]; + intersect.object = this; + intersect.batchId = i; + intersects.push( intersect ); - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.aomap, - UniformsLib.lightmap, - UniformsLib.emissivemap, - UniformsLib.bumpmap, - UniformsLib.normalmap, - UniformsLib.displacementmap, - UniformsLib.gradientmap, - UniformsLib.fog, - UniformsLib.lights, - { - emissive: { value: /*@__PURE__*/ new Color( 0x000000 ) } } - ] ), - vertexShader: ShaderChunk.meshtoon_vert, - fragmentShader: ShaderChunk.meshtoon_frag + _batchIntersects.length = 0; - }, + } - matcap: { + _mesh.material = null; + _mesh.geometry.index = null; + _mesh.geometry.attributes = {}; + _mesh.geometry.setDrawRange( 0, Infinity ); + + } + + copy( source ) { + + super.copy( source ); + + this.geometry = source.geometry.clone(); + this.perObjectFrustumCulled = source.perObjectFrustumCulled; + this.sortObjects = source.sortObjects; + this.boundingBox = source.boundingBox !== null ? source.boundingBox.clone() : null; + this.boundingSphere = source.boundingSphere !== null ? source.boundingSphere.clone() : null; + + this._geometryInfo = source._geometryInfo.map( info => ( { + ...info, + + boundingBox: info.boundingBox !== null ? info.boundingBox.clone() : null, + boundingSphere: info.boundingSphere !== null ? info.boundingSphere.clone() : null, + } ) ); + this._instanceInfo = source._instanceInfo.map( info => ( { ...info } ) ); + + this._availableInstanceIds = source._availableInstanceIds.slice(); + this._availableGeometryIds = source._availableGeometryIds.slice(); + + this._nextIndexStart = source._nextIndexStart; + this._nextVertexStart = source._nextVertexStart; + this._geometryCount = source._geometryCount; + + this._maxInstanceCount = source._maxInstanceCount; + this._maxVertexCount = source._maxVertexCount; + this._maxIndexCount = source._maxIndexCount; + + this._geometryInitialized = source._geometryInitialized; + this._multiDrawCounts = source._multiDrawCounts.slice(); + this._multiDrawStarts = source._multiDrawStarts.slice(); + + this._indirectTexture = source._indirectTexture.clone(); + this._indirectTexture.image.data = this._indirectTexture.image.data.slice(); + + this._matricesTexture = source._matricesTexture.clone(); + this._matricesTexture.image.data = this._matricesTexture.image.data.slice(); + + if ( this._colorsTexture !== null ) { + + this._colorsTexture = source._colorsTexture.clone(); + this._colorsTexture.image.data = this._colorsTexture.image.data.slice(); + + } + + return this; + + } + + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose() { + + // Assuming the geometry is not shared with other meshes + this.geometry.dispose(); + + this._matricesTexture.dispose(); + this._matricesTexture = null; + + this._indirectTexture.dispose(); + this._indirectTexture = null; + + if ( this._colorsTexture !== null ) { + + this._colorsTexture.dispose(); + this._colorsTexture = null; + + } + + } + + onBeforeRender( renderer, scene, camera, geometry, material/*, _group*/ ) { + + // if visibility has not changed and frustum culling and object sorting is not required + // then skip iterating over all items + if ( ! this._visibilityChanged && ! this.perObjectFrustumCulled && ! this.sortObjects ) { + + return; + + } + + // the indexed version of the multi draw function requires specifying the start + // offset in bytes. + const index = geometry.getIndex(); + const bytesPerElement = index === null ? 1 : index.array.BYTES_PER_ELEMENT; + + const instanceInfo = this._instanceInfo; + const multiDrawStarts = this._multiDrawStarts; + const multiDrawCounts = this._multiDrawCounts; + const geometryInfoList = this._geometryInfo; + const perObjectFrustumCulled = this.perObjectFrustumCulled; + const indirectTexture = this._indirectTexture; + const indirectArray = indirectTexture.image.data; + + const frustum = camera.isArrayCamera ? _frustumArray : _frustum; + // prepare the frustum in the local frame + if ( perObjectFrustumCulled && ! camera.isArrayCamera ) { + + _matrix$1 + .multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ) + .multiply( this.matrixWorld ); + _frustum.setFromProjectionMatrix( + _matrix$1, + renderer.coordinateSystem + ); + + } + + let multiDrawCount = 0; + if ( this.sortObjects ) { + + // get the camera position in the local frame + _matrix$1.copy( this.matrixWorld ).invert(); + _vector$5.setFromMatrixPosition( camera.matrixWorld ).applyMatrix4( _matrix$1 ); + _forward$1.set( 0, 0, -1 ).transformDirection( camera.matrixWorld ).transformDirection( _matrix$1 ); + + for ( let i = 0, l = instanceInfo.length; i < l; i ++ ) { + + if ( instanceInfo[ i ].visible && instanceInfo[ i ].active ) { + + const geometryId = instanceInfo[ i ].geometryIndex; + + // get the bounds in world space + this.getMatrixAt( i, _matrix$1 ); + this.getBoundingSphereAt( geometryId, _sphere$2 ).applyMatrix4( _matrix$1 ); + + // determine whether the batched geometry is within the frustum + let culled = false; + if ( perObjectFrustumCulled ) { + + culled = ! frustum.intersectsSphere( _sphere$2, camera ); + + } + + if ( ! culled ) { + + // get the distance from camera used for sorting + const geometryInfo = geometryInfoList[ geometryId ]; + const z = _temp.subVectors( _sphere$2.center, _vector$5 ).dot( _forward$1 ); + _renderList.push( geometryInfo.start, geometryInfo.count, z, i ); + + } + + } - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.bumpmap, - UniformsLib.normalmap, - UniformsLib.displacementmap, - UniformsLib.fog, - { - matcap: { value: null } } - ] ), - vertexShader: ShaderChunk.meshmatcap_vert, - fragmentShader: ShaderChunk.meshmatcap_frag + // Sort the draw ranges and prep for rendering + const list = _renderList.list; + const customSort = this.customSort; + if ( customSort === null ) { - }, + list.sort( material.transparent ? sortTransparent : sortOpaque ); - points: { + } else { - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.points, - UniformsLib.fog - ] ), + customSort.call( this, list, camera ); - vertexShader: ShaderChunk.points_vert, - fragmentShader: ShaderChunk.points_frag + } - }, + for ( let i = 0, l = list.length; i < l; i ++ ) { - dashed: { + const item = list[ i ]; + multiDrawStarts[ multiDrawCount ] = item.start * bytesPerElement; + multiDrawCounts[ multiDrawCount ] = item.count; + indirectArray[ multiDrawCount ] = item.index; + multiDrawCount ++; - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.fog, - { - scale: { value: 1 }, - dashSize: { value: 1 }, - totalSize: { value: 2 } } - ] ), - vertexShader: ShaderChunk.linedashed_vert, - fragmentShader: ShaderChunk.linedashed_frag + _renderList.reset(); - }, + } else { - depth: { + for ( let i = 0, l = instanceInfo.length; i < l; i ++ ) { - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.displacementmap - ] ), + if ( instanceInfo[ i ].visible && instanceInfo[ i ].active ) { - vertexShader: ShaderChunk.depth_vert, - fragmentShader: ShaderChunk.depth_frag + const geometryId = instanceInfo[ i ].geometryIndex; - }, + // determine whether the batched geometry is within the frustum + let culled = false; + if ( perObjectFrustumCulled ) { - normal: { + // get the bounds in world space + this.getMatrixAt( i, _matrix$1 ); + this.getBoundingSphereAt( geometryId, _sphere$2 ).applyMatrix4( _matrix$1 ); + culled = ! frustum.intersectsSphere( _sphere$2, camera ); + + } + + if ( ! culled ) { + + const geometryInfo = geometryInfoList[ geometryId ]; + multiDrawStarts[ multiDrawCount ] = geometryInfo.start * bytesPerElement; + multiDrawCounts[ multiDrawCount ] = geometryInfo.count; + indirectArray[ multiDrawCount ] = i; + multiDrawCount ++; + + } + + } - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.bumpmap, - UniformsLib.normalmap, - UniformsLib.displacementmap, - { - opacity: { value: 1.0 } } - ] ), - vertexShader: ShaderChunk.meshnormal_vert, - fragmentShader: ShaderChunk.meshnormal_frag + } + + indirectTexture.needsUpdate = true; + this._multiDrawCount = multiDrawCount; + this._visibilityChanged = false; + + } + + onBeforeShadow( renderer, object, camera, shadowCamera, geometry, depthMaterial/* , group */ ) { + + this.onBeforeRender( renderer, null, shadowCamera, geometry, depthMaterial ); + + } + +} + +/** + * A material for rendering line primitives. + * + * Materials define the appearance of renderable 3D objects. + * + * ```js + * const material = new THREE.LineBasicMaterial( { color: 0xffffff } ); + * ``` + * + * @augments Material + */ +class LineBasicMaterial extends Material { + + /** + * Constructs a new line basic material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLineBasicMaterial = true; + + this.type = 'LineBasicMaterial'; - }, + /** + * Color of the material. + * + * @type {Color} + * @default (1,1,1) + */ + this.color = new Color( 0xffffff ); - sprite: { + /** + * Sets the color of the lines using data from a texture. The texture map + * color is modulated by the diffuse `color`. + * + * @type {?Texture} + * @default null + */ + this.map = null; - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.sprite, - UniformsLib.fog - ] ), + /** + * Controls line thickness or lines. + * + * Can only be used with {@link SVGRenderer}. WebGL and WebGPU + * ignore this setting and always render line primitives with a + * width of one pixel. + * + * @type {number} + * @default 1 + */ + this.linewidth = 1; - vertexShader: ShaderChunk.sprite_vert, - fragmentShader: ShaderChunk.sprite_frag + /** + * Defines appearance of line ends. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('butt'|'round'|'square')} + * @default 'round' + */ + this.linecap = 'round'; - }, + /** + * Defines appearance of line joints. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ + this.linejoin = 'round'; - background: { + /** + * Whether the material is affected by fog or not. + * + * @type {boolean} + * @default true + */ + this.fog = true; - uniforms: { - uvTransform: { value: /*@__PURE__*/ new Matrix3() }, - t2D: { value: null }, - backgroundIntensity: { value: 1 } - }, + this.setValues( parameters ); - vertexShader: ShaderChunk.background_vert, - fragmentShader: ShaderChunk.background_frag + } - }, + copy( source ) { - backgroundCube: { + super.copy( source ); - uniforms: { - envMap: { value: null }, - flipEnvMap: { value: - 1 }, - backgroundBlurriness: { value: 0 }, - backgroundIntensity: { value: 1 } - }, + this.color.copy( source.color ); - vertexShader: ShaderChunk.backgroundCube_vert, - fragmentShader: ShaderChunk.backgroundCube_frag + this.map = source.map; - }, + this.linewidth = source.linewidth; + this.linecap = source.linecap; + this.linejoin = source.linejoin; - cube: { + this.fog = source.fog; - uniforms: { - tCube: { value: null }, - tFlip: { value: - 1 }, - opacity: { value: 1.0 } - }, + return this; - vertexShader: ShaderChunk.cube_vert, - fragmentShader: ShaderChunk.cube_frag + } - }, +} - equirect: { +const _vStart = /*@__PURE__*/ new Vector3(); +const _vEnd = /*@__PURE__*/ new Vector3(); - uniforms: { - tEquirect: { value: null }, - }, +const _inverseMatrix$1 = /*@__PURE__*/ new Matrix4(); +const _ray$1 = /*@__PURE__*/ new Ray(); +const _sphere$1 = /*@__PURE__*/ new Sphere(); - vertexShader: ShaderChunk.equirect_vert, - fragmentShader: ShaderChunk.equirect_frag +const _intersectPointOnRay = /*@__PURE__*/ new Vector3(); +const _intersectPointOnSegment = /*@__PURE__*/ new Vector3(); - }, +/** + * A continuous line. The line are rendered by connecting consecutive + * vertices with straight lines. + * + * ```js + * const material = new THREE.LineBasicMaterial( { color: 0x0000ff } ); + * + * const points = []; + * points.push( new THREE.Vector3( - 10, 0, 0 ) ); + * points.push( new THREE.Vector3( 0, 10, 0 ) ); + * points.push( new THREE.Vector3( 10, 0, 0 ) ); + * + * const geometry = new THREE.BufferGeometry().setFromPoints( points ); + * + * const line = new THREE.Line( geometry, material ); + * scene.add( line ); + * ``` + * + * @augments Object3D + */ +class Line extends Object3D { - distanceRGBA: { + /** + * Constructs a new line. + * + * @param {BufferGeometry} [geometry] - The line geometry. + * @param {Material|Array} [material] - The line material. + */ + constructor( geometry = new BufferGeometry(), material = new LineBasicMaterial() ) { - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.displacementmap, - { - referencePosition: { value: /*@__PURE__*/ new Vector3() }, - nearDistance: { value: 1 }, - farDistance: { value: 1000 } - } - ] ), + super(); - vertexShader: ShaderChunk.distanceRGBA_vert, - fragmentShader: ShaderChunk.distanceRGBA_frag + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLine = true; - }, + this.type = 'Line'; - shadow: { + /** + * The line geometry. + * + * @type {BufferGeometry} + */ + this.geometry = geometry; - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.lights, - UniformsLib.fog, - { - color: { value: /*@__PURE__*/ new Color( 0x00000 ) }, - opacity: { value: 1.0 } - }, - ] ), + /** + * The line material. + * + * @type {Material|Array} + * @default LineBasicMaterial + */ + this.material = material; - vertexShader: ShaderChunk.shadow_vert, - fragmentShader: ShaderChunk.shadow_frag + /** + * A dictionary representing the morph targets in the geometry. The key is the + * morph targets name, the value its attribute index. This member is `undefined` + * by default and only set when morph targets are detected in the geometry. + * + * @type {Object|undefined} + * @default undefined + */ + this.morphTargetDictionary = undefined; - } + /** + * An array of weights typically in the range `[0,1]` that specify how much of the morph + * is applied. This member is `undefined` by default and only set when morph targets are + * detected in the geometry. + * + * @type {Array|undefined} + * @default undefined + */ + this.morphTargetInfluences = undefined; -}; + this.updateMorphTargets(); -ShaderLib.physical = { + } - uniforms: /*@__PURE__*/ mergeUniforms( [ - ShaderLib.standard.uniforms, - { - clearcoat: { value: 0 }, - clearcoatMap: { value: null }, - clearcoatMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - clearcoatNormalMap: { value: null }, - clearcoatNormalMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - clearcoatNormalScale: { value: /*@__PURE__*/ new Vector2( 1, 1 ) }, - clearcoatRoughness: { value: 0 }, - clearcoatRoughnessMap: { value: null }, - clearcoatRoughnessMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - iridescence: { value: 0 }, - iridescenceMap: { value: null }, - iridescenceMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - iridescenceIOR: { value: 1.3 }, - iridescenceThicknessMinimum: { value: 100 }, - iridescenceThicknessMaximum: { value: 400 }, - iridescenceThicknessMap: { value: null }, - iridescenceThicknessMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - sheen: { value: 0 }, - sheenColor: { value: /*@__PURE__*/ new Color( 0x000000 ) }, - sheenColorMap: { value: null }, - sheenColorMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - sheenRoughness: { value: 1 }, - sheenRoughnessMap: { value: null }, - sheenRoughnessMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - transmission: { value: 0 }, - transmissionMap: { value: null }, - transmissionMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - transmissionSamplerSize: { value: /*@__PURE__*/ new Vector2() }, - transmissionSamplerMap: { value: null }, - thickness: { value: 0 }, - thicknessMap: { value: null }, - thicknessMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - attenuationDistance: { value: 0 }, - attenuationColor: { value: /*@__PURE__*/ new Color( 0x000000 ) }, - specularColor: { value: /*@__PURE__*/ new Color( 1, 1, 1 ) }, - specularColorMap: { value: null }, - specularColorMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - specularIntensity: { value: 1 }, - specularIntensityMap: { value: null }, - specularIntensityMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - anisotropyVector: { value: /*@__PURE__*/ new Vector2() }, - anisotropyMap: { value: null }, - } - ] ), + copy( source, recursive ) { - vertexShader: ShaderChunk.meshphysical_vert, - fragmentShader: ShaderChunk.meshphysical_frag + super.copy( source, recursive ); -}; + this.material = Array.isArray( source.material ) ? source.material.slice() : source.material; + this.geometry = source.geometry; -const _rgb = { r: 0, b: 0, g: 0 }; + return this; -function WebGLBackground( renderer, cubemaps, cubeuvmaps, state, objects, alpha, premultipliedAlpha ) { + } - const clearColor = new Color( 0x000000 ); - let clearAlpha = alpha === true ? 0 : 1; + /** + * Computes an array of distance values which are necessary for rendering dashed lines. + * For each vertex in the geometry, the method calculates the cumulative length from the + * current point to the very beginning of the line. + * + * @return {Line} A reference to this line. + */ + computeLineDistances() { - let planeMesh; - let boxMesh; + const geometry = this.geometry; - let currentBackground = null; - let currentBackgroundVersion = 0; - let currentTonemapping = null; + // we assume non-indexed geometry - function render( renderList, scene ) { + if ( geometry.index === null ) { - let forceClear = false; - let background = scene.isScene === true ? scene.background : null; + const positionAttribute = geometry.attributes.position; + const lineDistances = [ 0 ]; - if ( background && background.isTexture ) { + for ( let i = 1, l = positionAttribute.count; i < l; i ++ ) { - const usePMREM = scene.backgroundBlurriness > 0; // use PMREM if the user wants to blur the background - background = ( usePMREM ? cubeuvmaps : cubemaps ).get( background ); + _vStart.fromBufferAttribute( positionAttribute, i - 1 ); + _vEnd.fromBufferAttribute( positionAttribute, i ); - } + lineDistances[ i ] = lineDistances[ i - 1 ]; + lineDistances[ i ] += _vStart.distanceTo( _vEnd ); - if ( background === null ) { + } - setClear( clearColor, clearAlpha ); + geometry.setAttribute( 'lineDistance', new Float32BufferAttribute( lineDistances, 1 ) ); - } else if ( background && background.isColor ) { + } else { - setClear( background, 1 ); - forceClear = true; + console.warn( 'THREE.Line.computeLineDistances(): Computation only possible with non-indexed BufferGeometry.' ); } - const xr = renderer.xr; - const environmentBlendMode = xr.getEnvironmentBlendMode(); - - switch ( environmentBlendMode ) { + return this; - case 'opaque': - forceClear = true; - break; + } - case 'additive': - state.buffers.color.setClear( 0, 0, 0, 1, premultipliedAlpha ); - forceClear = true; - break; + /** + * Computes intersection points between a casted ray and this line. + * + * @param {Raycaster} raycaster - The raycaster. + * @param {Array} intersects - The target array that holds the intersection points. + */ + raycast( raycaster, intersects ) { - case 'alpha-blend': - state.buffers.color.setClear( 0, 0, 0, 0, premultipliedAlpha ); - forceClear = true; - break; + const geometry = this.geometry; + const matrixWorld = this.matrixWorld; + const threshold = raycaster.params.Line.threshold; + const drawRange = geometry.drawRange; - } + // Checking boundingSphere distance to ray - if ( renderer.autoClear || forceClear ) { + if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); - renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil ); + _sphere$1.copy( geometry.boundingSphere ); + _sphere$1.applyMatrix4( matrixWorld ); + _sphere$1.radius += threshold; - } + if ( raycaster.ray.intersectsSphere( _sphere$1 ) === false ) return; - if ( background && ( background.isCubeTexture || background.mapping === CubeUVReflectionMapping ) ) { + // - if ( boxMesh === undefined ) { + _inverseMatrix$1.copy( matrixWorld ).invert(); + _ray$1.copy( raycaster.ray ).applyMatrix4( _inverseMatrix$1 ); - boxMesh = new Mesh( - new BoxGeometry( 1, 1, 1 ), - new ShaderMaterial( { - name: 'BackgroundCubeMaterial', - uniforms: cloneUniforms( ShaderLib.backgroundCube.uniforms ), - vertexShader: ShaderLib.backgroundCube.vertexShader, - fragmentShader: ShaderLib.backgroundCube.fragmentShader, - side: BackSide, - depthTest: false, - depthWrite: false, - fog: false - } ) - ); + const localThreshold = threshold / ( ( this.scale.x + this.scale.y + this.scale.z ) / 3 ); + const localThresholdSq = localThreshold * localThreshold; - boxMesh.geometry.deleteAttribute( 'normal' ); - boxMesh.geometry.deleteAttribute( 'uv' ); + const step = this.isLineSegments ? 2 : 1; - boxMesh.onBeforeRender = function ( renderer, scene, camera ) { + const index = geometry.index; + const attributes = geometry.attributes; + const positionAttribute = attributes.position; - this.matrixWorld.copyPosition( camera.matrixWorld ); + if ( index !== null ) { - }; + const start = Math.max( 0, drawRange.start ); + const end = Math.min( index.count, ( drawRange.start + drawRange.count ) ); - // add "envMap" material property so the renderer can evaluate it like for built-in materials - Object.defineProperty( boxMesh.material, 'envMap', { + for ( let i = start, l = end - 1; i < l; i += step ) { - get: function () { + const a = index.getX( i ); + const b = index.getX( i + 1 ); - return this.uniforms.envMap.value; + const intersect = checkIntersection( this, raycaster, _ray$1, localThresholdSq, a, b, i ); - } + if ( intersect ) { - } ); + intersects.push( intersect ); - objects.update( boxMesh ); + } } - boxMesh.material.uniforms.envMap.value = background; - boxMesh.material.uniforms.flipEnvMap.value = ( background.isCubeTexture && background.isRenderTargetTexture === false ) ? - 1 : 1; - boxMesh.material.uniforms.backgroundBlurriness.value = scene.backgroundBlurriness; - boxMesh.material.uniforms.backgroundIntensity.value = scene.backgroundIntensity; - boxMesh.material.toneMapped = ( background.colorSpace === SRGBColorSpace ) ? false : true; + if ( this.isLineLoop ) { - if ( currentBackground !== background || - currentBackgroundVersion !== background.version || - currentTonemapping !== renderer.toneMapping ) { + const a = index.getX( end - 1 ); + const b = index.getX( start ); - boxMesh.material.needsUpdate = true; + const intersect = checkIntersection( this, raycaster, _ray$1, localThresholdSq, a, b, end - 1 ); - currentBackground = background; - currentBackgroundVersion = background.version; - currentTonemapping = renderer.toneMapping; + if ( intersect ) { + + intersects.push( intersect ); + + } } - boxMesh.layers.enableAll(); + } else { - // push to the pre-sorted opaque render list - renderList.unshift( boxMesh, boxMesh.geometry, boxMesh.material, 0, 0, null ); + const start = Math.max( 0, drawRange.start ); + const end = Math.min( positionAttribute.count, ( drawRange.start + drawRange.count ) ); - } else if ( background && background.isTexture ) { + for ( let i = start, l = end - 1; i < l; i += step ) { - if ( planeMesh === undefined ) { + const intersect = checkIntersection( this, raycaster, _ray$1, localThresholdSq, i, i + 1, i ); - planeMesh = new Mesh( - new PlaneGeometry( 2, 2 ), - new ShaderMaterial( { - name: 'BackgroundMaterial', - uniforms: cloneUniforms( ShaderLib.background.uniforms ), - vertexShader: ShaderLib.background.vertexShader, - fragmentShader: ShaderLib.background.fragmentShader, - side: FrontSide, - depthTest: false, - depthWrite: false, - fog: false - } ) - ); + if ( intersect ) { - planeMesh.geometry.deleteAttribute( 'normal' ); + intersects.push( intersect ); - // add "map" material property so the renderer can evaluate it like for built-in materials - Object.defineProperty( planeMesh.material, 'map', { + } - get: function () { + } - return this.uniforms.t2D.value; + if ( this.isLineLoop ) { - } + const intersect = checkIntersection( this, raycaster, _ray$1, localThresholdSq, end - 1, start, end - 1 ); - } ); + if ( intersect ) { - objects.update( planeMesh ); + intersects.push( intersect ); - } + } - planeMesh.material.uniforms.t2D.value = background; - planeMesh.material.uniforms.backgroundIntensity.value = scene.backgroundIntensity; - planeMesh.material.toneMapped = ( background.colorSpace === SRGBColorSpace ) ? false : true; + } - if ( background.matrixAutoUpdate === true ) { + } - background.updateMatrix(); + } - } + /** + * Sets the values of {@link Line#morphTargetDictionary} and {@link Line#morphTargetInfluences} + * to make sure existing morph targets can influence this 3D object. + */ + updateMorphTargets() { - planeMesh.material.uniforms.uvTransform.value.copy( background.matrix ); + const geometry = this.geometry; - if ( currentBackground !== background || - currentBackgroundVersion !== background.version || - currentTonemapping !== renderer.toneMapping ) { + const morphAttributes = geometry.morphAttributes; + const keys = Object.keys( morphAttributes ); - planeMesh.material.needsUpdate = true; + if ( keys.length > 0 ) { - currentBackground = background; - currentBackgroundVersion = background.version; - currentTonemapping = renderer.toneMapping; + const morphAttribute = morphAttributes[ keys[ 0 ] ]; - } + if ( morphAttribute !== undefined ) { - planeMesh.layers.enableAll(); + this.morphTargetInfluences = []; + this.morphTargetDictionary = {}; - // push to the pre-sorted opaque render list - renderList.unshift( planeMesh, planeMesh.geometry, planeMesh.material, 0, 0, null ); + for ( let m = 0, ml = morphAttribute.length; m < ml; m ++ ) { - } + const name = morphAttribute[ m ].name || String( m ); - } + this.morphTargetInfluences.push( 0 ); + this.morphTargetDictionary[ name ] = m; - function setClear( color, alpha ) { + } - color.getRGB( _rgb, getUnlitUniformColorSpace( renderer ) ); + } - state.buffers.color.setClear( _rgb.r, _rgb.g, _rgb.b, alpha, premultipliedAlpha ); + } } - return { +} - getClearColor: function () { +function checkIntersection( object, raycaster, ray, thresholdSq, a, b, i ) { - return clearColor; + const positionAttribute = object.geometry.attributes.position; - }, - setClearColor: function ( color, alpha = 1 ) { + _vStart.fromBufferAttribute( positionAttribute, a ); + _vEnd.fromBufferAttribute( positionAttribute, b ); - clearColor.set( color ); - clearAlpha = alpha; - setClear( clearColor, clearAlpha ); + const distSq = ray.distanceSqToSegment( _vStart, _vEnd, _intersectPointOnRay, _intersectPointOnSegment ); - }, - getClearAlpha: function () { + if ( distSq > thresholdSq ) return; - return clearAlpha; + _intersectPointOnRay.applyMatrix4( object.matrixWorld ); // Move back to world space for distance calculation - }, - setClearAlpha: function ( alpha ) { + const distance = raycaster.ray.origin.distanceTo( _intersectPointOnRay ); - clearAlpha = alpha; - setClear( clearColor, clearAlpha ); + if ( distance < raycaster.near || distance > raycaster.far ) return; - }, - render: render + return { + + distance: distance, + // What do we want? intersection point on the ray or on the segment?? + // point: raycaster.ray.at( distance ), + point: _intersectPointOnSegment.clone().applyMatrix4( object.matrixWorld ), + index: i, + face: null, + faceIndex: null, + barycoord: null, + object: object }; } -function WebGLBindingStates( gl, extensions, attributes, capabilities ) { +const _start = /*@__PURE__*/ new Vector3(); +const _end = /*@__PURE__*/ new Vector3(); - const maxVertexAttributes = gl.getParameter( gl.MAX_VERTEX_ATTRIBS ); +/** + * A series of lines drawn between pairs of vertices. + * + * @augments Line + */ +class LineSegments extends Line { - const extension = capabilities.isWebGL2 ? null : extensions.get( 'OES_vertex_array_object' ); - const vaoAvailable = capabilities.isWebGL2 || extension !== null; + /** + * Constructs a new line segments. + * + * @param {BufferGeometry} [geometry] - The line geometry. + * @param {Material|Array} [material] - The line material. + */ + constructor( geometry, material ) { - const bindingStates = {}; + super( geometry, material ); - const defaultState = createBindingState( null ); - let currentState = defaultState; - let forceUpdate = false; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLineSegments = true; - function setup( object, material, program, geometry, index ) { + this.type = 'LineSegments'; - let updateBuffers = false; + } - if ( vaoAvailable ) { + computeLineDistances() { - const state = getBindingState( geometry, program, material ); + const geometry = this.geometry; - if ( currentState !== state ) { + // we assume non-indexed geometry - currentState = state; - bindVertexArrayObject( currentState.object ); + if ( geometry.index === null ) { - } + const positionAttribute = geometry.attributes.position; + const lineDistances = []; + + for ( let i = 0, l = positionAttribute.count; i < l; i += 2 ) { + + _start.fromBufferAttribute( positionAttribute, i ); + _end.fromBufferAttribute( positionAttribute, i + 1 ); + + lineDistances[ i ] = ( i === 0 ) ? 0 : lineDistances[ i - 1 ]; + lineDistances[ i + 1 ] = lineDistances[ i ] + _start.distanceTo( _end ); - updateBuffers = needsUpdate( object, geometry, program, index ); + } - if ( updateBuffers ) saveCache( object, geometry, program, index ); + geometry.setAttribute( 'lineDistance', new Float32BufferAttribute( lineDistances, 1 ) ); } else { - const wireframe = ( material.wireframe === true ); + console.warn( 'THREE.LineSegments.computeLineDistances(): Computation only possible with non-indexed BufferGeometry.' ); - if ( currentState.geometry !== geometry.id || - currentState.program !== program.id || - currentState.wireframe !== wireframe ) { + } - currentState.geometry = geometry.id; - currentState.program = program.id; - currentState.wireframe = wireframe; + return this; - updateBuffers = true; + } - } +} - } +/** + * A continuous line. This is nearly the same as {@link Line} the only difference + * is that the last vertex is connected with the first vertex in order to close + * the line to form a loop. + * + * @augments Line + */ +class LineLoop extends Line { - if ( index !== null ) { + /** + * Constructs a new line loop. + * + * @param {BufferGeometry} [geometry] - The line geometry. + * @param {Material|Array} [material] - The line material. + */ + constructor( geometry, material ) { - attributes.update( index, gl.ELEMENT_ARRAY_BUFFER ); + super( geometry, material ); - } + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLineLoop = true; - if ( updateBuffers || forceUpdate ) { + this.type = 'LineLoop'; - forceUpdate = false; + } - setupVertexAttributes( object, material, program, geometry ); +} - if ( index !== null ) { +/** + * A material for rendering point primitives. + * + * Materials define the appearance of renderable 3D objects. + * + * ```js + * const vertices = []; + * + * for ( let i = 0; i < 10000; i ++ ) { + * const x = THREE.MathUtils.randFloatSpread( 2000 ); + * const y = THREE.MathUtils.randFloatSpread( 2000 ); + * const z = THREE.MathUtils.randFloatSpread( 2000 ); + * + * vertices.push( x, y, z ); + * } + * + * const geometry = new THREE.BufferGeometry(); + * geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) ); + * const material = new THREE.PointsMaterial( { color: 0x888888 } ); + * const points = new THREE.Points( geometry, material ); + * scene.add( points ); + * ``` + * + * @augments Material + */ +class PointsMaterial extends Material { - gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, attributes.get( index ).buffer ); + /** + * Constructs a new points material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { - } + super(); - } + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isPointsMaterial = true; - } + this.type = 'PointsMaterial'; - function createVertexArrayObject() { + /** + * Color of the material. + * + * @type {Color} + * @default (1,1,1) + */ + this.color = new Color( 0xffffff ); + + /** + * The color map. May optionally include an alpha channel, typically combined + * with {@link Material#transparent} or {@link Material#alphaTest}. The texture map + * color is modulated by the diffuse `color`. + * + * @type {?Texture} + * @default null + */ + this.map = null; + + /** + * The alpha map is a grayscale texture that controls the opacity across the + * surface (black: fully transparent; white: fully opaque). + * + * Only the color of the texture is used, ignoring the alpha channel if one + * exists. For RGB and RGBA textures, the renderer will use the green channel + * when sampling this texture due to the extra bit of precision provided for + * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and + * luminance/alpha textures will also still work as expected. + * + * @type {?Texture} + * @default null + */ + this.alphaMap = null; + + /** + * Defines the size of the points in pixels. + * + * Might be capped if the value exceeds hardware dependent parameters like [gl.ALIASED_POINT_SIZE_RANGE]{@link https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/getParamete}. + * + * @type {number} + * @default 1 + */ + this.size = 1; + + /** + * Specifies whether size of individual points is attenuated by the camera depth (perspective camera only). + * + * @type {boolean} + * @default true + */ + this.sizeAttenuation = true; - if ( capabilities.isWebGL2 ) return gl.createVertexArray(); + /** + * Whether the material is affected by fog or not. + * + * @type {boolean} + * @default true + */ + this.fog = true; - return extension.createVertexArrayOES(); + this.setValues( parameters ); } - function bindVertexArrayObject( vao ) { + copy( source ) { - if ( capabilities.isWebGL2 ) return gl.bindVertexArray( vao ); + super.copy( source ); - return extension.bindVertexArrayOES( vao ); + this.color.copy( source.color ); - } + this.map = source.map; - function deleteVertexArrayObject( vao ) { + this.alphaMap = source.alphaMap; + + this.size = source.size; + this.sizeAttenuation = source.sizeAttenuation; - if ( capabilities.isWebGL2 ) return gl.deleteVertexArray( vao ); + this.fog = source.fog; - return extension.deleteVertexArrayOES( vao ); + return this; } - function getBindingState( geometry, program, material ) { - - const wireframe = ( material.wireframe === true ); +} - let programMap = bindingStates[ geometry.id ]; +const _inverseMatrix = /*@__PURE__*/ new Matrix4(); +const _ray = /*@__PURE__*/ new Ray(); +const _sphere = /*@__PURE__*/ new Sphere(); +const _position$2 = /*@__PURE__*/ new Vector3(); - if ( programMap === undefined ) { +/** + * A class for displaying points or point clouds. + * + * @augments Object3D + */ +class Points extends Object3D { - programMap = {}; - bindingStates[ geometry.id ] = programMap; + /** + * Constructs a new point cloud. + * + * @param {BufferGeometry} [geometry] - The points geometry. + * @param {Material|Array} [material] - The points material. + */ + constructor( geometry = new BufferGeometry(), material = new PointsMaterial() ) { - } + super(); - let stateMap = programMap[ program.id ]; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isPoints = true; - if ( stateMap === undefined ) { + this.type = 'Points'; - stateMap = {}; - programMap[ program.id ] = stateMap; + /** + * The points geometry. + * + * @type {BufferGeometry} + */ + this.geometry = geometry; - } + /** + * The line material. + * + * @type {Material|Array} + * @default PointsMaterial + */ + this.material = material; - let state = stateMap[ wireframe ]; + /** + * A dictionary representing the morph targets in the geometry. The key is the + * morph targets name, the value its attribute index. This member is `undefined` + * by default and only set when morph targets are detected in the geometry. + * + * @type {Object|undefined} + * @default undefined + */ + this.morphTargetDictionary = undefined; - if ( state === undefined ) { + /** + * An array of weights typically in the range `[0,1]` that specify how much of the morph + * is applied. This member is `undefined` by default and only set when morph targets are + * detected in the geometry. + * + * @type {Array|undefined} + * @default undefined + */ + this.morphTargetInfluences = undefined; - state = createBindingState( createVertexArrayObject() ); - stateMap[ wireframe ] = state; + this.updateMorphTargets(); - } + } - return state; + copy( source, recursive ) { - } + super.copy( source, recursive ); - function createBindingState( vao ) { + this.material = Array.isArray( source.material ) ? source.material.slice() : source.material; + this.geometry = source.geometry; - const newAttributes = []; - const enabledAttributes = []; - const attributeDivisors = []; + return this; - for ( let i = 0; i < maxVertexAttributes; i ++ ) { + } - newAttributes[ i ] = 0; - enabledAttributes[ i ] = 0; - attributeDivisors[ i ] = 0; + /** + * Computes intersection points between a casted ray and this point cloud. + * + * @param {Raycaster} raycaster - The raycaster. + * @param {Array} intersects - The target array that holds the intersection points. + */ + raycast( raycaster, intersects ) { - } + const geometry = this.geometry; + const matrixWorld = this.matrixWorld; + const threshold = raycaster.params.Points.threshold; + const drawRange = geometry.drawRange; - return { + // Checking boundingSphere distance to ray - // for backward compatibility on non-VAO support browser - geometry: null, - program: null, - wireframe: false, + if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); - newAttributes: newAttributes, - enabledAttributes: enabledAttributes, - attributeDivisors: attributeDivisors, - object: vao, - attributes: {}, - index: null + _sphere.copy( geometry.boundingSphere ); + _sphere.applyMatrix4( matrixWorld ); + _sphere.radius += threshold; - }; + if ( raycaster.ray.intersectsSphere( _sphere ) === false ) return; - } + // - function needsUpdate( object, geometry, program, index ) { + _inverseMatrix.copy( matrixWorld ).invert(); + _ray.copy( raycaster.ray ).applyMatrix4( _inverseMatrix ); - const cachedAttributes = currentState.attributes; - const geometryAttributes = geometry.attributes; + const localThreshold = threshold / ( ( this.scale.x + this.scale.y + this.scale.z ) / 3 ); + const localThresholdSq = localThreshold * localThreshold; - let attributesNum = 0; + const index = geometry.index; + const attributes = geometry.attributes; + const positionAttribute = attributes.position; - const programAttributes = program.getAttributes(); + if ( index !== null ) { - for ( const name in programAttributes ) { + const start = Math.max( 0, drawRange.start ); + const end = Math.min( index.count, ( drawRange.start + drawRange.count ) ); - const programAttribute = programAttributes[ name ]; + for ( let i = start, il = end; i < il; i ++ ) { - if ( programAttribute.location >= 0 ) { + const a = index.getX( i ); - const cachedAttribute = cachedAttributes[ name ]; - let geometryAttribute = geometryAttributes[ name ]; + _position$2.fromBufferAttribute( positionAttribute, a ); - if ( geometryAttribute === undefined ) { + testPoint( _position$2, a, localThresholdSq, matrixWorld, raycaster, intersects, this ); - if ( name === 'instanceMatrix' && object.instanceMatrix ) geometryAttribute = object.instanceMatrix; - if ( name === 'instanceColor' && object.instanceColor ) geometryAttribute = object.instanceColor; + } - } + } else { - if ( cachedAttribute === undefined ) return true; + const start = Math.max( 0, drawRange.start ); + const end = Math.min( positionAttribute.count, ( drawRange.start + drawRange.count ) ); - if ( cachedAttribute.attribute !== geometryAttribute ) return true; + for ( let i = start, l = end; i < l; i ++ ) { - if ( geometryAttribute && cachedAttribute.data !== geometryAttribute.data ) return true; + _position$2.fromBufferAttribute( positionAttribute, i ); - attributesNum ++; + testPoint( _position$2, i, localThresholdSq, matrixWorld, raycaster, intersects, this ); } } - if ( currentState.attributesNum !== attributesNum ) return true; - - if ( currentState.index !== index ) return true; - - return false; - } - function saveCache( object, geometry, program, index ) { - - const cache = {}; - const attributes = geometry.attributes; - let attributesNum = 0; - - const programAttributes = program.getAttributes(); - - for ( const name in programAttributes ) { + /** + * Sets the values of {@link Points#morphTargetDictionary} and {@link Points#morphTargetInfluences} + * to make sure existing morph targets can influence this 3D object. + */ + updateMorphTargets() { - const programAttribute = programAttributes[ name ]; + const geometry = this.geometry; - if ( programAttribute.location >= 0 ) { + const morphAttributes = geometry.morphAttributes; + const keys = Object.keys( morphAttributes ); - let attribute = attributes[ name ]; + if ( keys.length > 0 ) { - if ( attribute === undefined ) { + const morphAttribute = morphAttributes[ keys[ 0 ] ]; - if ( name === 'instanceMatrix' && object.instanceMatrix ) attribute = object.instanceMatrix; - if ( name === 'instanceColor' && object.instanceColor ) attribute = object.instanceColor; + if ( morphAttribute !== undefined ) { - } + this.morphTargetInfluences = []; + this.morphTargetDictionary = {}; - const data = {}; - data.attribute = attribute; + for ( let m = 0, ml = morphAttribute.length; m < ml; m ++ ) { - if ( attribute && attribute.data ) { + const name = morphAttribute[ m ].name || String( m ); - data.data = attribute.data; + this.morphTargetInfluences.push( 0 ); + this.morphTargetDictionary[ name ] = m; } - cache[ name ] = data; - - attributesNum ++; - } } - currentState.attributes = cache; - currentState.attributesNum = attributesNum; + } - currentState.index = index; +} - } +function testPoint( point, index, localThresholdSq, matrixWorld, raycaster, intersects, object ) { - function initAttributes() { + const rayPointDistanceSq = _ray.distanceSqToPoint( point ); - const newAttributes = currentState.newAttributes; + if ( rayPointDistanceSq < localThresholdSq ) { - for ( let i = 0, il = newAttributes.length; i < il; i ++ ) { + const intersectPoint = new Vector3(); - newAttributes[ i ] = 0; + _ray.closestPointToPoint( point, intersectPoint ); + intersectPoint.applyMatrix4( matrixWorld ); - } + const distance = raycaster.ray.origin.distanceTo( intersectPoint ); - } + if ( distance < raycaster.near || distance > raycaster.far ) return; - function enableAttribute( attribute ) { + intersects.push( { - enableAttributeAndDivisor( attribute, 0 ); + distance: distance, + distanceToRay: Math.sqrt( rayPointDistanceSq ), + point: intersectPoint, + index: index, + face: null, + faceIndex: null, + barycoord: null, + object: object + + } ); } - function enableAttributeAndDivisor( attribute, meshPerAttribute ) { +} - const newAttributes = currentState.newAttributes; - const enabledAttributes = currentState.enabledAttributes; - const attributeDivisors = currentState.attributeDivisors; +/** + * A texture for use with a video. + * + * ```js + * // assuming you have created a HTML video element with id="video" + * const video = document.getElementById( 'video' ); + * const texture = new THREE.VideoTexture( video ); + * ``` + * + * Note: After the initial use of a texture, its dimensions, format, and type + * cannot be changed. Instead, call {@link Texture#dispose} on the texture and instantiate a new one. + * + * @augments Texture + */ +class VideoTexture extends Texture { - newAttributes[ attribute ] = 1; + /** + * Constructs a new video texture. + * + * @param {HTMLVideoElement} video - The video element to use as a data source for the texture. + * @param {number} [mapping=Texture.DEFAULT_MAPPING] - The texture mapping. + * @param {number} [wrapS=ClampToEdgeWrapping] - The wrapS value. + * @param {number} [wrapT=ClampToEdgeWrapping] - The wrapT value. + * @param {number} [magFilter=LinearFilter] - The mag filter value. + * @param {number} [minFilter=LinearFilter] - The min filter value. + * @param {number} [format=RGBAFormat] - The texture format. + * @param {number} [type=UnsignedByteType] - The texture type. + * @param {number} [anisotropy=Texture.DEFAULT_ANISOTROPY] - The anisotropy value. + */ + constructor( video, mapping, wrapS, wrapT, magFilter = LinearFilter, minFilter = LinearFilter, format, type, anisotropy ) { - if ( enabledAttributes[ attribute ] === 0 ) { + super( video, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); - gl.enableVertexAttribArray( attribute ); - enabledAttributes[ attribute ] = 1; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isVideoTexture = true; - } + /** + * Whether to generate mipmaps (if possible) for a texture. + * + * Overwritten and set to `false` by default. + * + * @type {boolean} + * @default false + */ + this.generateMipmaps = false; - if ( attributeDivisors[ attribute ] !== meshPerAttribute ) { + const scope = this; - const extension = capabilities.isWebGL2 ? gl : extensions.get( 'ANGLE_instanced_arrays' ); + function updateVideo() { - extension[ capabilities.isWebGL2 ? 'vertexAttribDivisor' : 'vertexAttribDivisorANGLE' ]( attribute, meshPerAttribute ); - attributeDivisors[ attribute ] = meshPerAttribute; + scope.needsUpdate = true; + video.requestVideoFrameCallback( updateVideo ); } - } - - function disableUnusedAttributes() { - - const newAttributes = currentState.newAttributes; - const enabledAttributes = currentState.enabledAttributes; + if ( 'requestVideoFrameCallback' in video ) { - for ( let i = 0, il = enabledAttributes.length; i < il; i ++ ) { + video.requestVideoFrameCallback( updateVideo ); - if ( enabledAttributes[ i ] !== newAttributes[ i ] ) { + } - gl.disableVertexAttribArray( i ); - enabledAttributes[ i ] = 0; + } - } + clone() { - } + return new this.constructor( this.image ).copy( this ); } - function vertexAttribPointer( index, size, type, normalized, stride, offset, integer ) { - - if ( integer === true ) { + /** + * This method is called automatically by the renderer and sets {@link Texture#needsUpdate} + * to `true` every time a new frame is available. + * + * Only relevant if `requestVideoFrameCallback` is not supported in the browser. + */ + update() { - gl.vertexAttribIPointer( index, size, type, stride, offset ); + const video = this.image; + const hasVideoFrameCallback = 'requestVideoFrameCallback' in video; - } else { + if ( hasVideoFrameCallback === false && video.readyState >= video.HAVE_CURRENT_DATA ) { - gl.vertexAttribPointer( index, size, type, normalized, stride, offset ); + this.needsUpdate = true; } } - function setupVertexAttributes( object, material, program, geometry ) { - - if ( capabilities.isWebGL2 === false && ( object.isInstancedMesh || geometry.isInstancedBufferGeometry ) ) { - - if ( extensions.get( 'ANGLE_instanced_arrays' ) === null ) return; - - } +} - initAttributes(); +/** + * This class can be used as an alternative way to define video data. Instead of using + * an instance of `HTMLVideoElement` like with `VideoTexture`, `VideoFrameTexture` expects each frame is + * defined manually via {@link VideoFrameTexture#setFrame}. A typical use case for this module is when + * video frames are decoded with the WebCodecs API. + * + * ```js + * const texture = new THREE.VideoFrameTexture(); + * texture.setFrame( frame ); + * ``` + * + * @augments VideoTexture + */ +class VideoFrameTexture extends VideoTexture { - const geometryAttributes = geometry.attributes; + /** + * Constructs a new video frame texture. + * + * @param {number} [mapping=Texture.DEFAULT_MAPPING] - The texture mapping. + * @param {number} [wrapS=ClampToEdgeWrapping] - The wrapS value. + * @param {number} [wrapT=ClampToEdgeWrapping] - The wrapT value. + * @param {number} [magFilter=LinearFilter] - The mag filter value. + * @param {number} [minFilter=LinearFilter] - The min filter value. + * @param {number} [format=RGBAFormat] - The texture format. + * @param {number} [type=UnsignedByteType] - The texture type. + * @param {number} [anisotropy=Texture.DEFAULT_ANISOTROPY] - The anisotropy value. + */ + constructor( mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) { - const programAttributes = program.getAttributes(); + super( {}, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); - const materialDefaultAttributeValues = material.defaultAttributeValues; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isVideoFrameTexture = true; - for ( const name in programAttributes ) { + } - const programAttribute = programAttributes[ name ]; + /** + * This method overwritten with an empty implementation since + * this type of texture is updated via `setFrame()`. + */ + update() {} - if ( programAttribute.location >= 0 ) { + clone() { - let geometryAttribute = geometryAttributes[ name ]; + return new this.constructor().copy( this ); // restoring Texture.clone() - if ( geometryAttribute === undefined ) { + } - if ( name === 'instanceMatrix' && object.instanceMatrix ) geometryAttribute = object.instanceMatrix; - if ( name === 'instanceColor' && object.instanceColor ) geometryAttribute = object.instanceColor; + /** + * Sets the current frame of the video. This will automatically update the texture + * so the data can be used for rendering. + * + * @param {VideoFrame} frame - The video frame. + */ + setFrame( frame ) { - } + this.image = frame; + this.needsUpdate = true; - if ( geometryAttribute !== undefined ) { + } - const normalized = geometryAttribute.normalized; - const size = geometryAttribute.itemSize; +} - const attribute = attributes.get( geometryAttribute ); +/** + * This class can only be used in combination with `copyFramebufferToTexture()` methods + * of renderers. It extracts the contents of the current bound framebuffer and provides it + * as a texture for further usage. + * + * ```js + * const pixelRatio = window.devicePixelRatio; + * const textureSize = 128 * pixelRatio; + * + * const frameTexture = new FramebufferTexture( textureSize, textureSize ); + * + * // calculate start position for copying part of the frame data + * const vector = new Vector2(); + * vector.x = ( window.innerWidth * pixelRatio / 2 ) - ( textureSize / 2 ); + * vector.y = ( window.innerHeight * pixelRatio / 2 ) - ( textureSize / 2 ); + * + * renderer.render( scene, camera ); + * + * // copy part of the rendered frame into the framebuffer texture + * renderer.copyFramebufferToTexture( frameTexture, vector ); + * ``` + * + * @augments Texture + */ +class FramebufferTexture extends Texture { - // TODO Attribute may not be available on context restore + /** + * Constructs a new framebuffer texture. + * + * @param {number} width - The width of the texture. + * @param {number} height - The height of the texture. + */ + constructor( width, height ) { - if ( attribute === undefined ) continue; + super( { width, height } ); - const buffer = attribute.buffer; - const type = attribute.type; - const bytesPerElement = attribute.bytesPerElement; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isFramebufferTexture = true; - // check for integer attributes (WebGL 2 only) + /** + * How the texture is sampled when a texel covers more than one pixel. + * + * Overwritten and set to `NearestFilter` by default to disable filtering. + * + * @type {(NearestFilter|NearestMipmapNearestFilter|NearestMipmapLinearFilter|LinearFilter|LinearMipmapNearestFilter|LinearMipmapLinearFilter)} + * @default NearestFilter + */ + this.magFilter = NearestFilter; - const integer = ( capabilities.isWebGL2 === true && ( type === gl.INT || type === gl.UNSIGNED_INT || geometryAttribute.gpuType === IntType ) ); + /** + * How the texture is sampled when a texel covers less than one pixel. + * + * Overwritten and set to `NearestFilter` by default to disable filtering. + * + * @type {(NearestFilter|NearestMipmapNearestFilter|NearestMipmapLinearFilter|LinearFilter|LinearMipmapNearestFilter|LinearMipmapLinearFilter)} + * @default NearestFilter + */ + this.minFilter = NearestFilter; - if ( geometryAttribute.isInterleavedBufferAttribute ) { + /** + * Whether to generate mipmaps (if possible) for a texture. + * + * Overwritten and set to `false` by default. + * + * @type {boolean} + * @default false + */ + this.generateMipmaps = false; - const data = geometryAttribute.data; - const stride = data.stride; - const offset = geometryAttribute.offset; + this.needsUpdate = true; - if ( data.isInstancedInterleavedBuffer ) { + } - for ( let i = 0; i < programAttribute.locationSize; i ++ ) { +} - enableAttributeAndDivisor( programAttribute.location + i, data.meshPerAttribute ); +/** + * Creates a texture based on data in compressed form. + * + * These texture are usually loaded with {@link CompressedTextureLoader}. + * + * @augments Texture + */ +class CompressedTexture extends Texture { - } + /** + * Constructs a new compressed texture. + * + * @param {Array} mipmaps - This array holds for all mipmaps (including the bases mip) + * the data and dimensions. + * @param {number} width - The width of the texture. + * @param {number} height - The height of the texture. + * @param {number} [format=RGBAFormat] - The texture format. + * @param {number} [type=UnsignedByteType] - The texture type. + * @param {number} [mapping=Texture.DEFAULT_MAPPING] - The texture mapping. + * @param {number} [wrapS=ClampToEdgeWrapping] - The wrapS value. + * @param {number} [wrapT=ClampToEdgeWrapping] - The wrapT value. + * @param {number} [magFilter=LinearFilter] - The mag filter value. + * @param {number} [minFilter=LinearMipmapLinearFilter] - The min filter value. + * @param {number} [anisotropy=Texture.DEFAULT_ANISOTROPY] - The anisotropy value. + * @param {string} [colorSpace=NoColorSpace] - The color space. + */ + constructor( mipmaps, width, height, format, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, colorSpace ) { - if ( object.isInstancedMesh !== true && geometry._maxInstanceCount === undefined ) { + super( null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, colorSpace ); - geometry._maxInstanceCount = data.meshPerAttribute * data.count; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isCompressedTexture = true; - } + /** + * The image property of a compressed texture just defines its dimensions. + * + * @type {{width:number,height:number}} + */ + this.image = { width: width, height: height }; - } else { + /** + * This array holds for all mipmaps (including the bases mip) the data and dimensions. + * + * @type {Array} + */ + this.mipmaps = mipmaps; - for ( let i = 0; i < programAttribute.locationSize; i ++ ) { + /** + * If set to `true`, the texture is flipped along the vertical axis when + * uploaded to the GPU. + * + * Overwritten and set to `false` by default since it is not possible to + * flip compressed textures. + * + * @type {boolean} + * @default false + * @readonly + */ + this.flipY = false; - enableAttribute( programAttribute.location + i ); + /** + * Whether to generate mipmaps (if possible) for a texture. + * + * Overwritten and set to `false` by default since it is not + * possible to generate mipmaps for compressed data. Mipmaps + * must be embedded in the compressed texture file. + * + * @type {boolean} + * @default false + * @readonly + */ + this.generateMipmaps = false; - } + } - } +} - gl.bindBuffer( gl.ARRAY_BUFFER, buffer ); +/** + * Creates a texture 2D array based on data in compressed form. + * + * These texture are usually loaded with {@link CompressedTextureLoader}. + * + * @augments CompressedTexture + */ +class CompressedArrayTexture extends CompressedTexture { - for ( let i = 0; i < programAttribute.locationSize; i ++ ) { + /** + * Constructs a new compressed array texture. + * + * @param {Array} mipmaps - This array holds for all mipmaps (including the bases mip) + * the data and dimensions. + * @param {number} width - The width of the texture. + * @param {number} height - The height of the texture. + * @param {number} depth - The depth of the texture. + * @param {number} [format=RGBAFormat] - The min filter value. + * @param {number} [type=UnsignedByteType] - The min filter value. + */ + constructor( mipmaps, width, height, depth, format, type ) { - vertexAttribPointer( - programAttribute.location + i, - size / programAttribute.locationSize, - type, - normalized, - stride * bytesPerElement, - ( offset + ( size / programAttribute.locationSize ) * i ) * bytesPerElement, - integer - ); + super( mipmaps, width, height, format, type ); - } + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isCompressedArrayTexture = true; - } else { + /** + * The image property of a compressed texture just defines its dimensions. + * + * @name CompressedArrayTexture#image + * @type {{width:number,height:number,depth:number}} + */ + this.image.depth = depth; - if ( geometryAttribute.isInstancedBufferAttribute ) { + /** + * This defines how the texture is wrapped in the depth and corresponds to + * *W* in UVW mapping. + * + * @type {(RepeatWrapping|ClampToEdgeWrapping|MirroredRepeatWrapping)} + * @default ClampToEdgeWrapping + */ + this.wrapR = ClampToEdgeWrapping; - for ( let i = 0; i < programAttribute.locationSize; i ++ ) { + /** + * A set of all layers which need to be updated in the texture. + * + * @type {Set} + */ + this.layerUpdates = new Set(); - enableAttributeAndDivisor( programAttribute.location + i, geometryAttribute.meshPerAttribute ); + } - } + /** + * Describes that a specific layer of the texture needs to be updated. + * Normally when {@link Texture#needsUpdate} is set to `true`, the + * entire compressed texture array is sent to the GPU. Marking specific + * layers will only transmit subsets of all mipmaps associated with a + * specific depth in the array which is often much more performant. + * + * @param {number} layerIndex - The layer index that should be updated. + */ + addLayerUpdate( layerIndex ) { - if ( object.isInstancedMesh !== true && geometry._maxInstanceCount === undefined ) { + this.layerUpdates.add( layerIndex ); - geometry._maxInstanceCount = geometryAttribute.meshPerAttribute * geometryAttribute.count; + } - } + /** + * Resets the layer updates registry. + */ + clearLayerUpdates() { - } else { + this.layerUpdates.clear(); - for ( let i = 0; i < programAttribute.locationSize; i ++ ) { + } - enableAttribute( programAttribute.location + i ); +} - } +/** + * Creates a cube texture based on data in compressed form. + * + * These texture are usually loaded with {@link CompressedTextureLoader}. + * + * @augments CompressedTexture + */ +class CompressedCubeTexture extends CompressedTexture { - } + /** + * Constructs a new compressed texture. + * + * @param {Array} images - An array of compressed textures. + * @param {number} [format=RGBAFormat] - The texture format. + * @param {number} [type=UnsignedByteType] - The texture type. + */ + constructor( images, format, type ) { - gl.bindBuffer( gl.ARRAY_BUFFER, buffer ); + super( undefined, images[ 0 ].width, images[ 0 ].height, format, type, CubeReflectionMapping ); - for ( let i = 0; i < programAttribute.locationSize; i ++ ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isCompressedCubeTexture = true; - vertexAttribPointer( - programAttribute.location + i, - size / programAttribute.locationSize, - type, - normalized, - size * bytesPerElement, - ( size / programAttribute.locationSize ) * i * bytesPerElement, - integer - ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isCubeTexture = true; - } + this.image = images; - } + } - } else if ( materialDefaultAttributeValues !== undefined ) { +} - const value = materialDefaultAttributeValues[ name ]; +/** + * Creates a texture from a canvas element. + * + * This is almost the same as the base texture class, except that it sets {@link Texture#needsUpdate} + * to `true` immediately since a canvas can directly be used for rendering. + * + * @augments Texture + */ +class CanvasTexture extends Texture { - if ( value !== undefined ) { + /** + * Constructs a new texture. + * + * @param {HTMLCanvasElement} [canvas] - The HTML canvas element. + * @param {number} [mapping=Texture.DEFAULT_MAPPING] - The texture mapping. + * @param {number} [wrapS=ClampToEdgeWrapping] - The wrapS value. + * @param {number} [wrapT=ClampToEdgeWrapping] - The wrapT value. + * @param {number} [magFilter=LinearFilter] - The mag filter value. + * @param {number} [minFilter=LinearMipmapLinearFilter] - The min filter value. + * @param {number} [format=RGBAFormat] - The texture format. + * @param {number} [type=UnsignedByteType] - The texture type. + * @param {number} [anisotropy=Texture.DEFAULT_ANISOTROPY] - The anisotropy value. + */ + constructor( canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) { - switch ( value.length ) { + super( canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); - case 2: - gl.vertexAttrib2fv( programAttribute.location, value ); - break; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isCanvasTexture = true; - case 3: - gl.vertexAttrib3fv( programAttribute.location, value ); - break; + this.needsUpdate = true; - case 4: - gl.vertexAttrib4fv( programAttribute.location, value ); - break; + } - default: - gl.vertexAttrib1fv( programAttribute.location, value ); +} - } +/** + * This class can be used to automatically save the depth information of a + * rendering into a texture. + * + * @augments Texture + */ +class DepthTexture extends Texture { - } + /** + * Constructs a new depth texture. + * + * @param {number} width - The width of the texture. + * @param {number} height - The height of the texture. + * @param {number} [type=UnsignedIntType] - The texture type. + * @param {number} [mapping=Texture.DEFAULT_MAPPING] - The texture mapping. + * @param {number} [wrapS=ClampToEdgeWrapping] - The wrapS value. + * @param {number} [wrapT=ClampToEdgeWrapping] - The wrapT value. + * @param {number} [magFilter=LinearFilter] - The mag filter value. + * @param {number} [minFilter=LinearFilter] - The min filter value. + * @param {number} [anisotropy=Texture.DEFAULT_ANISOTROPY] - The anisotropy value. + * @param {number} [format=DepthFormat] - The texture format. + * @param {number} [depth=1] - The depth of the texture. + */ + constructor( width, height, type = UnsignedIntType, mapping, wrapS, wrapT, magFilter = NearestFilter, minFilter = NearestFilter, anisotropy, format = DepthFormat, depth = 1 ) { - } + if ( format !== DepthFormat && format !== DepthStencilFormat ) { - } + throw new Error( 'DepthTexture format must be either THREE.DepthFormat or THREE.DepthStencilFormat' ); } - disableUnusedAttributes(); + const image = { width: width, height: height, depth: depth }; - } + super( image, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); - function dispose() { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isDepthTexture = true; - reset(); + /** + * If set to `true`, the texture is flipped along the vertical axis when + * uploaded to the GPU. + * + * Overwritten and set to `false` by default. + * + * @type {boolean} + * @default false + */ + this.flipY = false; - for ( const geometryId in bindingStates ) { + /** + * Whether to generate mipmaps (if possible) for a texture. + * + * Overwritten and set to `false` by default. + * + * @type {boolean} + * @default false + */ + this.generateMipmaps = false; - const programMap = bindingStates[ geometryId ]; + /** + * Code corresponding to the depth compare function. + * + * @type {?(NeverCompare|LessCompare|EqualCompare|LessEqualCompare|GreaterCompare|NotEqualCompare|GreaterEqualCompare|AlwaysCompare)} + * @default null + */ + this.compareFunction = null; - for ( const programId in programMap ) { + } - const stateMap = programMap[ programId ]; - for ( const wireframe in stateMap ) { + copy( source ) { - deleteVertexArrayObject( stateMap[ wireframe ].object ); + super.copy( source ); - delete stateMap[ wireframe ]; + this.source = new Source( Object.assign( {}, source.image ) ); // see #30540 + this.compareFunction = source.compareFunction; - } + return this; - delete programMap[ programId ]; + } - } + toJSON( meta ) { - delete bindingStates[ geometryId ]; + const data = super.toJSON( meta ); - } + if ( this.compareFunction !== null ) data.compareFunction = this.compareFunction; - } + return data; - function releaseStatesOfGeometry( geometry ) { + } - if ( bindingStates[ geometry.id ] === undefined ) return; +} - const programMap = bindingStates[ geometry.id ]; +/** + * A geometry class for representing a capsule. + * + * ```js + * const geometry = new THREE.CapsuleGeometry( 1, 1, 4, 8, 1 ); + * const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); + * const capsule = new THREE.Mesh( geometry, material ); + * scene.add( capsule ); + * ``` + * + * @augments BufferGeometry + */ +class CapsuleGeometry extends BufferGeometry { - for ( const programId in programMap ) { + /** + * Constructs a new capsule geometry. + * + * @param {number} [radius=1] - Radius of the capsule. + * @param {number} [height=1] - Height of the middle section. + * @param {number} [capSegments=4] - Number of curve segments used to build each cap. + * @param {number} [radialSegments=8] - Number of segmented faces around the circumference of the capsule. Must be an integer >= 3. + * @param {number} [heightSegments=1] - Number of rows of faces along the height of the middle section. Must be an integer >= 1. + */ + constructor( radius = 1, height = 1, capSegments = 4, radialSegments = 8, heightSegments = 1 ) { - const stateMap = programMap[ programId ]; + super(); - for ( const wireframe in stateMap ) { + this.type = 'CapsuleGeometry'; - deleteVertexArrayObject( stateMap[ wireframe ].object ); + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + radius: radius, + height: height, + capSegments: capSegments, + radialSegments: radialSegments, + heightSegments: heightSegments, + }; - delete stateMap[ wireframe ]; + height = Math.max( 0, height ); + capSegments = Math.max( 1, Math.floor( capSegments ) ); + radialSegments = Math.max( 3, Math.floor( radialSegments ) ); + heightSegments = Math.max( 1, Math.floor( heightSegments ) ); - } + // buffers - delete programMap[ programId ]; + const indices = []; + const vertices = []; + const normals = []; + const uvs = []; - } + // helper variables - delete bindingStates[ geometry.id ]; + const halfHeight = height / 2; + const capArcLength = ( Math.PI / 2 ) * radius; + const cylinderPartLength = height; + const totalArcLength = 2 * capArcLength + cylinderPartLength; - } + const numVerticalSegments = capSegments * 2 + heightSegments; + const verticesPerRow = radialSegments + 1; - function releaseStatesOfProgram( program ) { + const normal = new Vector3(); + const vertex = new Vector3(); - for ( const geometryId in bindingStates ) { + // generate vertices, normals, and uvs - const programMap = bindingStates[ geometryId ]; + for ( let iy = 0; iy <= numVerticalSegments; iy ++ ) { - if ( programMap[ program.id ] === undefined ) continue; + let currentArcLength = 0; + let profileY = 0; + let profileRadius = 0; + let normalYComponent = 0; - const stateMap = programMap[ program.id ]; + if ( iy <= capSegments ) { - for ( const wireframe in stateMap ) { + // bottom cap + const segmentProgress = iy / capSegments; + const angle = ( segmentProgress * Math.PI ) / 2; + profileY = - halfHeight - radius * Math.cos( angle ); + profileRadius = radius * Math.sin( angle ); + normalYComponent = - radius * Math.cos( angle ); + currentArcLength = segmentProgress * capArcLength; - deleteVertexArrayObject( stateMap[ wireframe ].object ); + } else if ( iy <= capSegments + heightSegments ) { - delete stateMap[ wireframe ]; + // middle section + const segmentProgress = ( iy - capSegments ) / heightSegments; + profileY = - halfHeight + segmentProgress * height; + profileRadius = radius; + normalYComponent = 0; + currentArcLength = capArcLength + segmentProgress * cylinderPartLength; - } + } else { - delete programMap[ program.id ]; + // top cap + const segmentProgress = + ( iy - capSegments - heightSegments ) / capSegments; + const angle = ( segmentProgress * Math.PI ) / 2; + profileY = halfHeight + radius * Math.sin( angle ); + profileRadius = radius * Math.cos( angle ); + normalYComponent = radius * Math.sin( angle ); + currentArcLength = + capArcLength + cylinderPartLength + segmentProgress * capArcLength; - } + } - } + const v = Math.max( 0, Math.min( 1, currentArcLength / totalArcLength ) ); - function reset() { - resetDefaultState(); - forceUpdate = true; + // special case for the poles - if ( currentState === defaultState ) return; + let uOffset = 0; - currentState = defaultState; - bindVertexArrayObject( currentState.object ); + if ( iy === 0 ) { - } + uOffset = 0.5 / radialSegments; - // for backward-compatibility + } else if ( iy === numVerticalSegments ) { - function resetDefaultState() { + uOffset = -0.5 / radialSegments; - defaultState.geometry = null; - defaultState.program = null; - defaultState.wireframe = false; + } - } + for ( let ix = 0; ix <= radialSegments; ix ++ ) { - return { + const u = ix / radialSegments; + const theta = u * Math.PI * 2; - setup: setup, - reset: reset, - resetDefaultState: resetDefaultState, - dispose: dispose, - releaseStatesOfGeometry: releaseStatesOfGeometry, - releaseStatesOfProgram: releaseStatesOfProgram, + const sinTheta = Math.sin( theta ); + const cosTheta = Math.cos( theta ); - initAttributes: initAttributes, - enableAttribute: enableAttribute, - disableUnusedAttributes: disableUnusedAttributes + // vertex - }; + vertex.x = - profileRadius * cosTheta; + vertex.y = profileY; + vertex.z = profileRadius * sinTheta; + vertices.push( vertex.x, vertex.y, vertex.z ); -} + // normal -function WebGLBufferRenderer( gl, extensions, info, capabilities ) { + normal.set( + - profileRadius * cosTheta, + normalYComponent, + profileRadius * sinTheta + ); + normal.normalize(); + normals.push( normal.x, normal.y, normal.z ); - const isWebGL2 = capabilities.isWebGL2; + // uv - let mode; + uvs.push( u + uOffset, v ); - function setMode( value ) { + } - mode = value; + if ( iy > 0 ) { - } + const prevIndexRow = ( iy - 1 ) * verticesPerRow; + for ( let ix = 0; ix < radialSegments; ix ++ ) { - function render( start, count ) { + const i1 = prevIndexRow + ix; + const i2 = prevIndexRow + ix + 1; + const i3 = iy * verticesPerRow + ix; + const i4 = iy * verticesPerRow + ix + 1; - gl.drawArrays( mode, start, count ); + indices.push( i1, i2, i3 ); + indices.push( i2, i4, i3 ); - info.update( count, mode, 1 ); + } - } + } - function renderInstances( start, count, primcount ) { + } - if ( primcount === 0 ) return; + // build geometry - let extension, methodName; + this.setIndex( indices ); + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - if ( isWebGL2 ) { + } - extension = gl; - methodName = 'drawArraysInstanced'; + copy( source ) { - } else { + super.copy( source ); - extension = extensions.get( 'ANGLE_instanced_arrays' ); - methodName = 'drawArraysInstancedANGLE'; + this.parameters = Object.assign( {}, source.parameters ); - if ( extension === null ) { + return this; - console.error( 'THREE.WebGLBufferRenderer: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.' ); - return; + } - } + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {CapsuleGeometry} A new instance. + */ + static fromJSON( data ) { - } + return new CapsuleGeometry( data.radius, data.height, data.capSegments, data.radialSegments, data.heightSegments ); - extension[ methodName ]( mode, start, count, primcount ); + } - info.update( count, mode, primcount ); +} - } +/** + * A simple shape of Euclidean geometry. It is constructed from a + * number of triangular segments that are oriented around a central point and + * extend as far out as a given radius. It is built counter-clockwise from a + * start angle and a given central angle. It can also be used to create + * regular polygons, where the number of segments determines the number of + * sides. + * + * ```js + * const geometry = new THREE.CircleGeometry( 5, 32 ); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + * const circle = new THREE.Mesh( geometry, material ); + * scene.add( circle ) + * ``` + * + * @augments BufferGeometry + */ +class CircleGeometry extends BufferGeometry { - // + /** + * Constructs a new circle geometry. + * + * @param {number} [radius=1] - Radius of the circle. + * @param {number} [segments=32] - Number of segments (triangles), minimum = `3`. + * @param {number} [thetaStart=0] - Start angle for first segment in radians. + * @param {number} [thetaLength=Math.PI*2] - The central angle, often called theta, + * of the circular sector in radians. The default value results in a complete circle. + */ + constructor( radius = 1, segments = 32, thetaStart = 0, thetaLength = Math.PI * 2 ) { - this.setMode = setMode; - this.render = render; - this.renderInstances = renderInstances; + super(); -} + this.type = 'CircleGeometry'; -function WebGLCapabilities( gl, extensions, parameters ) { + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + radius: radius, + segments: segments, + thetaStart: thetaStart, + thetaLength: thetaLength + }; - let maxAnisotropy; + segments = Math.max( 3, segments ); - function getMaxAnisotropy() { + // buffers - if ( maxAnisotropy !== undefined ) return maxAnisotropy; + const indices = []; + const vertices = []; + const normals = []; + const uvs = []; - if ( extensions.has( 'EXT_texture_filter_anisotropic' ) === true ) { + // helper variables - const extension = extensions.get( 'EXT_texture_filter_anisotropic' ); + const vertex = new Vector3(); + const uv = new Vector2(); - maxAnisotropy = gl.getParameter( extension.MAX_TEXTURE_MAX_ANISOTROPY_EXT ); + // center point - } else { + vertices.push( 0, 0, 0 ); + normals.push( 0, 0, 1 ); + uvs.push( 0.5, 0.5 ); - maxAnisotropy = 0; + for ( let s = 0, i = 3; s <= segments; s ++, i += 3 ) { - } + const segment = thetaStart + s / segments * thetaLength; - return maxAnisotropy; + // vertex - } + vertex.x = radius * Math.cos( segment ); + vertex.y = radius * Math.sin( segment ); - function getMaxPrecision( precision ) { + vertices.push( vertex.x, vertex.y, vertex.z ); - if ( precision === 'highp' ) { + // normal - if ( gl.getShaderPrecisionFormat( gl.VERTEX_SHADER, gl.HIGH_FLOAT ).precision > 0 && - gl.getShaderPrecisionFormat( gl.FRAGMENT_SHADER, gl.HIGH_FLOAT ).precision > 0 ) { + normals.push( 0, 0, 1 ); - return 'highp'; + // uvs - } + uv.x = ( vertices[ i ] / radius + 1 ) / 2; + uv.y = ( vertices[ i + 1 ] / radius + 1 ) / 2; - precision = 'mediump'; + uvs.push( uv.x, uv.y ); } - if ( precision === 'mediump' ) { - - if ( gl.getShaderPrecisionFormat( gl.VERTEX_SHADER, gl.MEDIUM_FLOAT ).precision > 0 && - gl.getShaderPrecisionFormat( gl.FRAGMENT_SHADER, gl.MEDIUM_FLOAT ).precision > 0 ) { + // indices - return 'mediump'; + for ( let i = 1; i <= segments; i ++ ) { - } + indices.push( i, i + 1, 0 ); } - return 'lowp'; + // build geometry + + this.setIndex( indices ); + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); } - const isWebGL2 = typeof WebGL2RenderingContext !== 'undefined' && gl.constructor.name === 'WebGL2RenderingContext'; + copy( source ) { - let precision = parameters.precision !== undefined ? parameters.precision : 'highp'; - const maxPrecision = getMaxPrecision( precision ); + super.copy( source ); - if ( maxPrecision !== precision ) { + this.parameters = Object.assign( {}, source.parameters ); - console.warn( 'THREE.WebGLRenderer:', precision, 'not supported, using', maxPrecision, 'instead.' ); - precision = maxPrecision; + return this; } - const drawBuffers = isWebGL2 || extensions.has( 'WEBGL_draw_buffers' ); - - const logarithmicDepthBuffer = parameters.logarithmicDepthBuffer === true; + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {CircleGeometry} A new instance. + */ + static fromJSON( data ) { - const maxTextures = gl.getParameter( gl.MAX_TEXTURE_IMAGE_UNITS ); - const maxVertexTextures = gl.getParameter( gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS ); - const maxTextureSize = gl.getParameter( gl.MAX_TEXTURE_SIZE ); - const maxCubemapSize = gl.getParameter( gl.MAX_CUBE_MAP_TEXTURE_SIZE ); + return new CircleGeometry( data.radius, data.segments, data.thetaStart, data.thetaLength ); - const maxAttributes = gl.getParameter( gl.MAX_VERTEX_ATTRIBS ); - const maxVertexUniforms = gl.getParameter( gl.MAX_VERTEX_UNIFORM_VECTORS ); - const maxVaryings = gl.getParameter( gl.MAX_VARYING_VECTORS ); - const maxFragmentUniforms = gl.getParameter( gl.MAX_FRAGMENT_UNIFORM_VECTORS ); + } - const vertexTextures = maxVertexTextures > 0; - const floatFragmentTextures = isWebGL2 || extensions.has( 'OES_texture_float' ); - const floatVertexTextures = vertexTextures && floatFragmentTextures; +} - const maxSamples = isWebGL2 ? gl.getParameter( gl.MAX_SAMPLES ) : 0; +/** + * A geometry class for representing a cylinder. + * + * ```js + * const geometry = new THREE.CylinderGeometry( 5, 5, 20, 32 ); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + * const cylinder = new THREE.Mesh( geometry, material ); + * scene.add( cylinder ); + * ``` + * + * @augments BufferGeometry + */ +class CylinderGeometry extends BufferGeometry { - return { + /** + * Constructs a new cylinder geometry. + * + * @param {number} [radiusTop=1] - Radius of the cylinder at the top. + * @param {number} [radiusBottom=1] - Radius of the cylinder at the bottom. + * @param {number} [height=1] - Height of the cylinder. + * @param {number} [radialSegments=32] - Number of segmented faces around the circumference of the cylinder. + * @param {number} [heightSegments=1] - Number of rows of faces along the height of the cylinder. + * @param {boolean} [openEnded=false] - Whether the base of the cylinder is open or capped. + * @param {number} [thetaStart=0] - Start angle for first segment, in radians. + * @param {number} [thetaLength=Math.PI*2] - The central angle, often called theta, of the circular sector, in radians. + * The default value results in a complete cylinder. + */ + constructor( radiusTop = 1, radiusBottom = 1, height = 1, radialSegments = 32, heightSegments = 1, openEnded = false, thetaStart = 0, thetaLength = Math.PI * 2 ) { - isWebGL2: isWebGL2, + super(); - drawBuffers: drawBuffers, + this.type = 'CylinderGeometry'; - getMaxAnisotropy: getMaxAnisotropy, - getMaxPrecision: getMaxPrecision, + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + radiusTop: radiusTop, + radiusBottom: radiusBottom, + height: height, + radialSegments: radialSegments, + heightSegments: heightSegments, + openEnded: openEnded, + thetaStart: thetaStart, + thetaLength: thetaLength + }; - precision: precision, - logarithmicDepthBuffer: logarithmicDepthBuffer, + const scope = this; - maxTextures: maxTextures, - maxVertexTextures: maxVertexTextures, - maxTextureSize: maxTextureSize, - maxCubemapSize: maxCubemapSize, + radialSegments = Math.floor( radialSegments ); + heightSegments = Math.floor( heightSegments ); - maxAttributes: maxAttributes, - maxVertexUniforms: maxVertexUniforms, - maxVaryings: maxVaryings, - maxFragmentUniforms: maxFragmentUniforms, + // buffers - vertexTextures: vertexTextures, - floatFragmentTextures: floatFragmentTextures, - floatVertexTextures: floatVertexTextures, + const indices = []; + const vertices = []; + const normals = []; + const uvs = []; - maxSamples: maxSamples + // helper variables - }; + let index = 0; + const indexArray = []; + const halfHeight = height / 2; + let groupStart = 0; -} + // generate geometry -function WebGLClipping( properties ) { + generateTorso(); - const scope = this; + if ( openEnded === false ) { - let globalState = null, - numGlobalPlanes = 0, - localClippingEnabled = false, - renderingShadows = false; + if ( radiusTop > 0 ) generateCap( true ); + if ( radiusBottom > 0 ) generateCap( false ); - const plane = new Plane(), - viewNormalMatrix = new Matrix3(), + } - uniform = { value: null, needsUpdate: false }; + // build geometry - this.uniform = uniform; - this.numPlanes = 0; - this.numIntersection = 0; + this.setIndex( indices ); + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - this.init = function ( planes, enableLocalClipping ) { + function generateTorso() { - const enabled = - planes.length !== 0 || - enableLocalClipping || - // enable state of previous frame - the clipping code has to - // run another frame in order to reset the state: - numGlobalPlanes !== 0 || - localClippingEnabled; + const normal = new Vector3(); + const vertex = new Vector3(); - localClippingEnabled = enableLocalClipping; + let groupCount = 0; - numGlobalPlanes = planes.length; + // this will be used to calculate the normal + const slope = ( radiusBottom - radiusTop ) / height; - return enabled; + // generate vertices, normals and uvs - }; + for ( let y = 0; y <= heightSegments; y ++ ) { - this.beginShadows = function () { + const indexRow = []; - renderingShadows = true; - projectPlanes( null ); + const v = y / heightSegments; - }; + // calculate the radius of the current row - this.endShadows = function () { + const radius = v * ( radiusBottom - radiusTop ) + radiusTop; - renderingShadows = false; + for ( let x = 0; x <= radialSegments; x ++ ) { - }; + const u = x / radialSegments; - this.setGlobalState = function ( planes, camera ) { + const theta = u * thetaLength + thetaStart; - globalState = projectPlanes( planes, camera, 0 ); + const sinTheta = Math.sin( theta ); + const cosTheta = Math.cos( theta ); - }; + // vertex - this.setState = function ( material, camera, useCache ) { + vertex.x = radius * sinTheta; + vertex.y = - v * height + halfHeight; + vertex.z = radius * cosTheta; + vertices.push( vertex.x, vertex.y, vertex.z ); - const planes = material.clippingPlanes, - clipIntersection = material.clipIntersection, - clipShadows = material.clipShadows; + // normal - const materialProperties = properties.get( material ); + normal.set( sinTheta, slope, cosTheta ).normalize(); + normals.push( normal.x, normal.y, normal.z ); - if ( ! localClippingEnabled || planes === null || planes.length === 0 || renderingShadows && ! clipShadows ) { + // uv - // there's no local clipping + uvs.push( u, 1 - v ); - if ( renderingShadows ) { + // save index of vertex in respective row - // there's no global clipping + indexRow.push( index ++ ); - projectPlanes( null ); + } - } else { + // now save vertices of the row in our index array - resetGlobalState(); + indexArray.push( indexRow ); } - } else { + // generate indices - const nGlobal = renderingShadows ? 0 : numGlobalPlanes, - lGlobal = nGlobal * 4; + for ( let x = 0; x < radialSegments; x ++ ) { - let dstArray = materialProperties.clippingState || null; + for ( let y = 0; y < heightSegments; y ++ ) { - uniform.value = dstArray; // ensure unique state + // we use the index array to access the correct indices - dstArray = projectPlanes( planes, camera, lGlobal, useCache ); + const a = indexArray[ y ][ x ]; + const b = indexArray[ y + 1 ][ x ]; + const c = indexArray[ y + 1 ][ x + 1 ]; + const d = indexArray[ y ][ x + 1 ]; - for ( let i = 0; i !== lGlobal; ++ i ) { + // faces - dstArray[ i ] = globalState[ i ]; + if ( radiusTop > 0 || y !== 0 ) { - } + indices.push( a, b, d ); + groupCount += 3; - materialProperties.clippingState = dstArray; - this.numIntersection = clipIntersection ? this.numPlanes : 0; - this.numPlanes += nGlobal; + } - } + if ( radiusBottom > 0 || y !== heightSegments - 1 ) { + indices.push( b, c, d ); + groupCount += 3; - }; + } - function resetGlobalState() { + } - if ( uniform.value !== globalState ) { + } - uniform.value = globalState; - uniform.needsUpdate = numGlobalPlanes > 0; + // add a group to the geometry. this will ensure multi material support - } + scope.addGroup( groupStart, groupCount, 0 ); - scope.numPlanes = numGlobalPlanes; - scope.numIntersection = 0; + // calculate new start value for groups - } + groupStart += groupCount; - function projectPlanes( planes, camera, dstOffset, skipTransform ) { + } - const nPlanes = planes !== null ? planes.length : 0; - let dstArray = null; + function generateCap( top ) { - if ( nPlanes !== 0 ) { + // save the index of the first center vertex + const centerIndexStart = index; - dstArray = uniform.value; + const uv = new Vector2(); + const vertex = new Vector3(); - if ( skipTransform !== true || dstArray === null ) { + let groupCount = 0; - const flatSize = dstOffset + nPlanes * 4, - viewMatrix = camera.matrixWorldInverse; + const radius = ( top === true ) ? radiusTop : radiusBottom; + const sign = ( top === true ) ? 1 : -1; - viewNormalMatrix.getNormalMatrix( viewMatrix ); + // first we generate the center vertex data of the cap. + // because the geometry needs one set of uvs per face, + // we must generate a center vertex per face/segment - if ( dstArray === null || dstArray.length < flatSize ) { + for ( let x = 1; x <= radialSegments; x ++ ) { + + // vertex + + vertices.push( 0, halfHeight * sign, 0 ); - dstArray = new Float32Array( flatSize ); + // normal - } + normals.push( 0, sign, 0 ); - for ( let i = 0, i4 = dstOffset; i !== nPlanes; ++ i, i4 += 4 ) { + // uv - plane.copy( planes[ i ] ).applyMatrix4( viewMatrix, viewNormalMatrix ); + uvs.push( 0.5, 0.5 ); - plane.normal.toArray( dstArray, i4 ); - dstArray[ i4 + 3 ] = plane.constant; + // increase index - } + index ++; } - uniform.value = dstArray; - uniform.needsUpdate = true; + // save the index of the last center vertex + const centerIndexEnd = index; - } + // now we generate the surrounding vertices, normals and uvs - scope.numPlanes = nPlanes; - scope.numIntersection = 0; + for ( let x = 0; x <= radialSegments; x ++ ) { - return dstArray; + const u = x / radialSegments; + const theta = u * thetaLength + thetaStart; - } + const cosTheta = Math.cos( theta ); + const sinTheta = Math.sin( theta ); -} + // vertex -function WebGLCubeMaps( renderer ) { + vertex.x = radius * sinTheta; + vertex.y = halfHeight * sign; + vertex.z = radius * cosTheta; + vertices.push( vertex.x, vertex.y, vertex.z ); - let cubemaps = new WeakMap(); + // normal - function mapTextureMapping( texture, mapping ) { + normals.push( 0, sign, 0 ); - if ( mapping === EquirectangularReflectionMapping ) { + // uv - texture.mapping = CubeReflectionMapping; + uv.x = ( cosTheta * 0.5 ) + 0.5; + uv.y = ( sinTheta * 0.5 * sign ) + 0.5; + uvs.push( uv.x, uv.y ); - } else if ( mapping === EquirectangularRefractionMapping ) { + // increase index - texture.mapping = CubeRefractionMapping; + index ++; - } + } - return texture; + // generate indices - } + for ( let x = 0; x < radialSegments; x ++ ) { - function get( texture ) { + const c = centerIndexStart + x; + const i = centerIndexEnd + x; - if ( texture && texture.isTexture && texture.isRenderTargetTexture === false ) { + if ( top === true ) { - const mapping = texture.mapping; + // face top - if ( mapping === EquirectangularReflectionMapping || mapping === EquirectangularRefractionMapping ) { + indices.push( i, i + 1, c ); - if ( cubemaps.has( texture ) ) { + } else { - const cubemap = cubemaps.get( texture ).texture; - return mapTextureMapping( cubemap, texture.mapping ); + // face bottom - } else { + indices.push( i + 1, i, c ); - const image = texture.image; + } - if ( image && image.height > 0 ) { + groupCount += 3; - const renderTarget = new WebGLCubeRenderTarget( image.height / 2 ); - renderTarget.fromEquirectangularTexture( renderer, texture ); - cubemaps.set( texture, renderTarget ); + } - texture.addEventListener( 'dispose', onTextureDispose ); + // add a group to the geometry. this will ensure multi material support - return mapTextureMapping( renderTarget.texture, texture.mapping ); + scope.addGroup( groupStart, groupCount, top === true ? 1 : 2 ); - } else { + // calculate new start value for groups - // image not yet ready. try the conversion next frame + groupStart += groupCount; - return null; + } - } + } - } + copy( source ) { - } + super.copy( source ); - } + this.parameters = Object.assign( {}, source.parameters ); - return texture; + return this; } - function onTextureDispose( event ) { + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {CylinderGeometry} A new instance. + */ + static fromJSON( data ) { - const texture = event.target; + return new CylinderGeometry( data.radiusTop, data.radiusBottom, data.height, data.radialSegments, data.heightSegments, data.openEnded, data.thetaStart, data.thetaLength ); - texture.removeEventListener( 'dispose', onTextureDispose ); + } - const cubemap = cubemaps.get( texture ); +} - if ( cubemap !== undefined ) { +/** + * A geometry class for representing a cone. + * + * ```js + * const geometry = new THREE.ConeGeometry( 5, 20, 32 ); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + * const cone = new THREE.Mesh(geometry, material ); + * scene.add( cone ); + * ``` + * + * @augments CylinderGeometry + */ +class ConeGeometry extends CylinderGeometry { - cubemaps.delete( texture ); - cubemap.dispose(); + /** + * Constructs a new cone geometry. + * + * @param {number} [radius=1] - Radius of the cone base. + * @param {number} [height=1] - Height of the cone. + * @param {number} [radialSegments=32] - Number of segmented faces around the circumference of the cone. + * @param {number} [heightSegments=1] - Number of rows of faces along the height of the cone. + * @param {boolean} [openEnded=false] - Whether the base of the cone is open or capped. + * @param {number} [thetaStart=0] - Start angle for first segment, in radians. + * @param {number} [thetaLength=Math.PI*2] - The central angle, often called theta, of the circular sector, in radians. + * The default value results in a complete cone. + */ + constructor( radius = 1, height = 1, radialSegments = 32, heightSegments = 1, openEnded = false, thetaStart = 0, thetaLength = Math.PI * 2 ) { - } + super( 0, radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ); + + this.type = 'ConeGeometry'; + + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + radius: radius, + height: height, + radialSegments: radialSegments, + heightSegments: heightSegments, + openEnded: openEnded, + thetaStart: thetaStart, + thetaLength: thetaLength + }; } - function dispose() { + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {ConeGeometry} A new instance. + */ + static fromJSON( data ) { - cubemaps = new WeakMap(); + return new ConeGeometry( data.radius, data.height, data.radialSegments, data.heightSegments, data.openEnded, data.thetaStart, data.thetaLength ); } - return { - get: get, - dispose: dispose - }; - } -class OrthographicCamera extends Camera { +/** + * A polyhedron is a solid in three dimensions with flat faces. This class + * will take an array of vertices, project them onto a sphere, and then + * divide them up to the desired level of detail. + * + * @augments BufferGeometry + */ +class PolyhedronGeometry extends BufferGeometry { - constructor( left = - 1, right = 1, top = 1, bottom = - 1, near = 0.1, far = 2000 ) { + /** + * Constructs a new polyhedron geometry. + * + * @param {Array} [vertices] - A flat array of vertices describing the base shape. + * @param {Array} [indices] - A flat array of indices describing the base shape. + * @param {number} [radius=1] - The radius of the shape. + * @param {number} [detail=0] - How many levels to subdivide the geometry. The more detail, the smoother the shape. + */ + constructor( vertices = [], indices = [], radius = 1, detail = 0 ) { super(); - this.isOrthographicCamera = true; + this.type = 'PolyhedronGeometry'; - this.type = 'OrthographicCamera'; + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + vertices: vertices, + indices: indices, + radius: radius, + detail: detail + }; - this.zoom = 1; - this.view = null; + // default buffer data - this.left = left; - this.right = right; - this.top = top; - this.bottom = bottom; + const vertexBuffer = []; + const uvBuffer = []; - this.near = near; - this.far = far; + // the subdivision creates the vertex buffer data - this.updateProjectionMatrix(); + subdivide( detail ); - } + // all vertices should lie on a conceptual sphere with a given radius - copy( source, recursive ) { + applyRadius( radius ); - super.copy( source, recursive ); + // finally, create the uv data - this.left = source.left; - this.right = source.right; - this.top = source.top; - this.bottom = source.bottom; - this.near = source.near; - this.far = source.far; + generateUVs(); - this.zoom = source.zoom; - this.view = source.view === null ? null : Object.assign( {}, source.view ); + // build non-indexed geometry - return this; + this.setAttribute( 'position', new Float32BufferAttribute( vertexBuffer, 3 ) ); + this.setAttribute( 'normal', new Float32BufferAttribute( vertexBuffer.slice(), 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvBuffer, 2 ) ); - } + if ( detail === 0 ) { - setViewOffset( fullWidth, fullHeight, x, y, width, height ) { + this.computeVertexNormals(); // flat normals - if ( this.view === null ) { + } else { - this.view = { - enabled: true, - fullWidth: 1, - fullHeight: 1, - offsetX: 0, - offsetY: 0, - width: 1, - height: 1 - }; + this.normalizeNormals(); // smooth normals } - this.view.enabled = true; - this.view.fullWidth = fullWidth; - this.view.fullHeight = fullHeight; - this.view.offsetX = x; - this.view.offsetY = y; - this.view.width = width; - this.view.height = height; + // helper functions - this.updateProjectionMatrix(); + function subdivide( detail ) { - } + const a = new Vector3(); + const b = new Vector3(); + const c = new Vector3(); - clearViewOffset() { + // iterate over all faces and apply a subdivision with the given detail value - if ( this.view !== null ) { + for ( let i = 0; i < indices.length; i += 3 ) { - this.view.enabled = false; + // get the vertices of the face - } + getVertexByIndex( indices[ i + 0 ], a ); + getVertexByIndex( indices[ i + 1 ], b ); + getVertexByIndex( indices[ i + 2 ], c ); - this.updateProjectionMatrix(); + // perform subdivision - } + subdivideFace( a, b, c, detail ); - updateProjectionMatrix() { + } - const dx = ( this.right - this.left ) / ( 2 * this.zoom ); - const dy = ( this.top - this.bottom ) / ( 2 * this.zoom ); - const cx = ( this.right + this.left ) / 2; - const cy = ( this.top + this.bottom ) / 2; + } - let left = cx - dx; - let right = cx + dx; - let top = cy + dy; - let bottom = cy - dy; + function subdivideFace( a, b, c, detail ) { - if ( this.view !== null && this.view.enabled ) { + const cols = detail + 1; - const scaleW = ( this.right - this.left ) / this.view.fullWidth / this.zoom; - const scaleH = ( this.top - this.bottom ) / this.view.fullHeight / this.zoom; + // we use this multidimensional array as a data structure for creating the subdivision - left += scaleW * this.view.offsetX; - right = left + scaleW * this.view.width; - top -= scaleH * this.view.offsetY; - bottom = top - scaleH * this.view.height; + const v = []; - } + // construct all of the vertices for this subdivision - this.projectionMatrix.makeOrthographic( left, right, top, bottom, this.near, this.far ); + for ( let i = 0; i <= cols; i ++ ) { - this.projectionMatrixInverse.copy( this.projectionMatrix ).invert(); + v[ i ] = []; - } + const aj = a.clone().lerp( c, i / cols ); + const bj = b.clone().lerp( c, i / cols ); - toJSON( meta ) { + const rows = cols - i; - const data = super.toJSON( meta ); + for ( let j = 0; j <= rows; j ++ ) { - data.object.zoom = this.zoom; - data.object.left = this.left; - data.object.right = this.right; - data.object.top = this.top; - data.object.bottom = this.bottom; - data.object.near = this.near; - data.object.far = this.far; + if ( j === 0 && i === cols ) { - if ( this.view !== null ) data.object.view = Object.assign( {}, this.view ); + v[ i ][ j ] = aj; - return data; + } else { - } + v[ i ][ j ] = aj.clone().lerp( bj, j / rows ); -} + } -const LOD_MIN = 4; + } -// The standard deviations (radians) associated with the extra mips. These are -// chosen to approximate a Trowbridge-Reitz distribution function times the -// geometric shadowing function. These sigma values squared must match the -// variance #defines in cube_uv_reflection_fragment.glsl.js. -const EXTRA_LOD_SIGMA = [ 0.125, 0.215, 0.35, 0.446, 0.526, 0.582 ]; + } -// The maximum length of the blur for loop. Smaller sigmas will use fewer -// samples and exit early, but not recompile the shader. -const MAX_SAMPLES = 20; + // construct all of the faces -const _flatCamera = /*@__PURE__*/ new OrthographicCamera(); -const _clearColor = /*@__PURE__*/ new Color(); -let _oldTarget = null; + for ( let i = 0; i < cols; i ++ ) { -// Golden Ratio -const PHI = ( 1 + Math.sqrt( 5 ) ) / 2; -const INV_PHI = 1 / PHI; + for ( let j = 0; j < 2 * ( cols - i ) - 1; j ++ ) { -// Vertices of a dodecahedron (except the opposites, which represent the -// same axis), used as axis directions evenly spread on a sphere. -const _axisDirections = [ - /*@__PURE__*/ new Vector3( 1, 1, 1 ), - /*@__PURE__*/ new Vector3( - 1, 1, 1 ), - /*@__PURE__*/ new Vector3( 1, 1, - 1 ), - /*@__PURE__*/ new Vector3( - 1, 1, - 1 ), - /*@__PURE__*/ new Vector3( 0, PHI, INV_PHI ), - /*@__PURE__*/ new Vector3( 0, PHI, - INV_PHI ), - /*@__PURE__*/ new Vector3( INV_PHI, 0, PHI ), - /*@__PURE__*/ new Vector3( - INV_PHI, 0, PHI ), - /*@__PURE__*/ new Vector3( PHI, INV_PHI, 0 ), - /*@__PURE__*/ new Vector3( - PHI, INV_PHI, 0 ) ]; + const k = Math.floor( j / 2 ); -/** - * This class generates a Prefiltered, Mipmapped Radiance Environment Map - * (PMREM) from a cubeMap environment texture. This allows different levels of - * blur to be quickly accessed based on material roughness. It is packed into a - * special CubeUV format that allows us to perform custom interpolation so that - * we can support nonlinear formats such as RGBE. Unlike a traditional mipmap - * chain, it only goes down to the LOD_MIN level (above), and then creates extra - * even more filtered 'mips' at the same LOD_MIN resolution, associated with - * higher roughness levels. In this way we maintain resolution to smoothly - * interpolate diffuse lighting while limiting sampling computation. - * - * Paper: Fast, Accurate Image-Based Lighting - * https://drive.google.com/file/d/15y8r_UpKlU9SvV4ILb0C3qCPecS8pvLz/view -*/ + if ( j % 2 === 0 ) { -class PMREMGenerator { + pushVertex( v[ i ][ k + 1 ] ); + pushVertex( v[ i + 1 ][ k ] ); + pushVertex( v[ i ][ k ] ); - constructor( renderer ) { + } else { - this._renderer = renderer; - this._pingPongRenderTarget = null; + pushVertex( v[ i ][ k + 1 ] ); + pushVertex( v[ i + 1 ][ k + 1 ] ); + pushVertex( v[ i + 1 ][ k ] ); - this._lodMax = 0; - this._cubeSize = 0; - this._lodPlanes = []; - this._sizeLods = []; - this._sigmas = []; + } - this._blurMaterial = null; - this._cubemapMaterial = null; - this._equirectMaterial = null; + } - this._compileMaterial( this._blurMaterial ); + } - } + } - /** - * Generates a PMREM from a supplied Scene, which can be faster than using an - * image if networking bandwidth is low. Optional sigma specifies a blur radius - * in radians to be applied to the scene before PMREM generation. Optional near - * and far planes ensure the scene is rendered in its entirety (the cubeCamera - * is placed at the origin). - */ - fromScene( scene, sigma = 0, near = 0.1, far = 100 ) { + function applyRadius( radius ) { - _oldTarget = this._renderer.getRenderTarget(); + const vertex = new Vector3(); - this._setSize( 256 ); + // iterate over the entire buffer and apply the radius to each vertex - const cubeUVRenderTarget = this._allocateTargets(); - cubeUVRenderTarget.depthBuffer = true; + for ( let i = 0; i < vertexBuffer.length; i += 3 ) { - this._sceneToCubeUV( scene, near, far, cubeUVRenderTarget ); + vertex.x = vertexBuffer[ i + 0 ]; + vertex.y = vertexBuffer[ i + 1 ]; + vertex.z = vertexBuffer[ i + 2 ]; - if ( sigma > 0 ) { + vertex.normalize().multiplyScalar( radius ); - this._blur( cubeUVRenderTarget, 0, 0, sigma ); + vertexBuffer[ i + 0 ] = vertex.x; + vertexBuffer[ i + 1 ] = vertex.y; + vertexBuffer[ i + 2 ] = vertex.z; + + } } - this._applyPMREM( cubeUVRenderTarget ); - this._cleanup( cubeUVRenderTarget ); + function generateUVs() { - return cubeUVRenderTarget; + const vertex = new Vector3(); - } + for ( let i = 0; i < vertexBuffer.length; i += 3 ) { - /** - * Generates a PMREM from an equirectangular texture, which can be either LDR - * or HDR. The ideal input image size is 1k (1024 x 512), - * as this matches best with the 256 x 256 cubemap output. - */ - fromEquirectangular( equirectangular, renderTarget = null ) { + vertex.x = vertexBuffer[ i + 0 ]; + vertex.y = vertexBuffer[ i + 1 ]; + vertex.z = vertexBuffer[ i + 2 ]; - return this._fromTexture( equirectangular, renderTarget ); + const u = azimuth( vertex ) / 2 / Math.PI + 0.5; + const v = inclination( vertex ) / Math.PI + 0.5; + uvBuffer.push( u, 1 - v ); - } + } - /** - * Generates a PMREM from an cubemap texture, which can be either LDR - * or HDR. The ideal input cube size is 256 x 256, - * as this matches best with the 256 x 256 cubemap output. - */ - fromCubemap( cubemap, renderTarget = null ) { + correctUVs(); - return this._fromTexture( cubemap, renderTarget ); + correctSeam(); - } + } - /** - * Pre-compiles the cubemap shader. You can get faster start-up by invoking this method during - * your texture's network fetch for increased concurrency. - */ - compileCubemapShader() { + function correctSeam() { - if ( this._cubemapMaterial === null ) { + // handle case when face straddles the seam, see #3269 - this._cubemapMaterial = _getCubemapMaterial(); - this._compileMaterial( this._cubemapMaterial ); + for ( let i = 0; i < uvBuffer.length; i += 6 ) { - } + // uv data of a single face - } + const x0 = uvBuffer[ i + 0 ]; + const x1 = uvBuffer[ i + 2 ]; + const x2 = uvBuffer[ i + 4 ]; - /** - * Pre-compiles the equirectangular shader. You can get faster start-up by invoking this method during - * your texture's network fetch for increased concurrency. - */ - compileEquirectangularShader() { + const max = Math.max( x0, x1, x2 ); + const min = Math.min( x0, x1, x2 ); - if ( this._equirectMaterial === null ) { + // 0.9 is somewhat arbitrary - this._equirectMaterial = _getEquirectMaterial(); - this._compileMaterial( this._equirectMaterial ); + if ( max > 0.9 && min < 0.1 ) { - } + if ( x0 < 0.2 ) uvBuffer[ i + 0 ] += 1; + if ( x1 < 0.2 ) uvBuffer[ i + 2 ] += 1; + if ( x2 < 0.2 ) uvBuffer[ i + 4 ] += 1; - } + } - /** - * Disposes of the PMREMGenerator's internal memory. Note that PMREMGenerator is a static class, - * so you should not need more than one PMREMGenerator object. If you do, calling dispose() on - * one of them will cause any others to also become unusable. - */ - dispose() { + } - this._dispose(); + } - if ( this._cubemapMaterial !== null ) this._cubemapMaterial.dispose(); - if ( this._equirectMaterial !== null ) this._equirectMaterial.dispose(); + function pushVertex( vertex ) { - } + vertexBuffer.push( vertex.x, vertex.y, vertex.z ); - // private interface + } - _setSize( cubeSize ) { + function getVertexByIndex( index, vertex ) { - this._lodMax = Math.floor( Math.log2( cubeSize ) ); - this._cubeSize = Math.pow( 2, this._lodMax ); + const stride = index * 3; - } + vertex.x = vertices[ stride + 0 ]; + vertex.y = vertices[ stride + 1 ]; + vertex.z = vertices[ stride + 2 ]; - _dispose() { + } - if ( this._blurMaterial !== null ) this._blurMaterial.dispose(); + function correctUVs() { - if ( this._pingPongRenderTarget !== null ) this._pingPongRenderTarget.dispose(); + const a = new Vector3(); + const b = new Vector3(); + const c = new Vector3(); - for ( let i = 0; i < this._lodPlanes.length; i ++ ) { + const centroid = new Vector3(); - this._lodPlanes[ i ].dispose(); + const uvA = new Vector2(); + const uvB = new Vector2(); + const uvC = new Vector2(); - } + for ( let i = 0, j = 0; i < vertexBuffer.length; i += 9, j += 6 ) { - } + a.set( vertexBuffer[ i + 0 ], vertexBuffer[ i + 1 ], vertexBuffer[ i + 2 ] ); + b.set( vertexBuffer[ i + 3 ], vertexBuffer[ i + 4 ], vertexBuffer[ i + 5 ] ); + c.set( vertexBuffer[ i + 6 ], vertexBuffer[ i + 7 ], vertexBuffer[ i + 8 ] ); - _cleanup( outputTarget ) { + uvA.set( uvBuffer[ j + 0 ], uvBuffer[ j + 1 ] ); + uvB.set( uvBuffer[ j + 2 ], uvBuffer[ j + 3 ] ); + uvC.set( uvBuffer[ j + 4 ], uvBuffer[ j + 5 ] ); - this._renderer.setRenderTarget( _oldTarget ); - outputTarget.scissorTest = false; - _setViewport( outputTarget, 0, 0, outputTarget.width, outputTarget.height ); + centroid.copy( a ).add( b ).add( c ).divideScalar( 3 ); + + const azi = azimuth( centroid ); + + correctUV( uvA, j + 0, a, azi ); + correctUV( uvB, j + 2, b, azi ); + correctUV( uvC, j + 4, c, azi ); - } + } - _fromTexture( texture, renderTarget ) { + } - if ( texture.mapping === CubeReflectionMapping || texture.mapping === CubeRefractionMapping ) { + function correctUV( uv, stride, vector, azimuth ) { - this._setSize( texture.image.length === 0 ? 16 : ( texture.image[ 0 ].width || texture.image[ 0 ].image.width ) ); + if ( ( azimuth < 0 ) && ( uv.x === 1 ) ) { - } else { // Equirectangular + uvBuffer[ stride ] = uv.x - 1; - this._setSize( texture.image.width / 4 ); + } - } + if ( ( vector.x === 0 ) && ( vector.z === 0 ) ) { - _oldTarget = this._renderer.getRenderTarget(); + uvBuffer[ stride ] = azimuth / 2 / Math.PI + 0.5; - const cubeUVRenderTarget = renderTarget || this._allocateTargets(); - this._textureToCubeUV( texture, cubeUVRenderTarget ); - this._applyPMREM( cubeUVRenderTarget ); - this._cleanup( cubeUVRenderTarget ); + } - return cubeUVRenderTarget; + } - } + // Angle around the Y axis, counter-clockwise when looking from above. - _allocateTargets() { + function azimuth( vector ) { - const width = 3 * Math.max( this._cubeSize, 16 * 7 ); - const height = 4 * this._cubeSize; + return Math.atan2( vector.z, - vector.x ); - const params = { - magFilter: LinearFilter, - minFilter: LinearFilter, - generateMipmaps: false, - type: HalfFloatType, - format: RGBAFormat, - colorSpace: LinearSRGBColorSpace, - depthBuffer: false - }; + } - const cubeUVRenderTarget = _createRenderTarget( width, height, params ); - if ( this._pingPongRenderTarget === null || this._pingPongRenderTarget.width !== width || this._pingPongRenderTarget.height !== height ) { + // Angle above the XZ plane. - if ( this._pingPongRenderTarget !== null ) { + function inclination( vector ) { - this._dispose(); + return Math.atan2( - vector.y, Math.sqrt( ( vector.x * vector.x ) + ( vector.z * vector.z ) ) ); - } + } - this._pingPongRenderTarget = _createRenderTarget( width, height, params ); + } - const { _lodMax } = this; - ( { sizeLods: this._sizeLods, lodPlanes: this._lodPlanes, sigmas: this._sigmas } = _createPlanes( _lodMax ) ); + copy( source ) { - this._blurMaterial = _getBlurShader( _lodMax, width, height ); + super.copy( source ); - } + this.parameters = Object.assign( {}, source.parameters ); - return cubeUVRenderTarget; + return this; } - _compileMaterial( material ) { + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {PolyhedronGeometry} A new instance. + */ + static fromJSON( data ) { - const tmpMesh = new Mesh( this._lodPlanes[ 0 ], material ); - this._renderer.compile( tmpMesh, _flatCamera ); + return new PolyhedronGeometry( data.vertices, data.indices, data.radius, data.details ); } - _sceneToCubeUV( scene, near, far, cubeUVRenderTarget ) { +} - const fov = 90; - const aspect = 1; - const cubeCamera = new PerspectiveCamera( fov, aspect, near, far ); - const upSign = [ 1, - 1, 1, 1, 1, 1 ]; - const forwardSign = [ 1, 1, 1, - 1, - 1, - 1 ]; - const renderer = this._renderer; +/** + * A geometry class for representing a dodecahedron. + * + * ```js + * const geometry = new THREE.DodecahedronGeometry(); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + * const dodecahedron = new THREE.Mesh( geometry, material ); + * scene.add( dodecahedron ); + * ``` + * + * @augments PolyhedronGeometry + */ +class DodecahedronGeometry extends PolyhedronGeometry { - const originalAutoClear = renderer.autoClear; - const toneMapping = renderer.toneMapping; - renderer.getClearColor( _clearColor ); + /** + * Constructs a new dodecahedron geometry. + * + * @param {number} [radius=1] - Radius of the dodecahedron. + * @param {number} [detail=0] - Setting this to a value greater than `0` adds vertices making it no longer a dodecahedron. + */ + constructor( radius = 1, detail = 0 ) { - renderer.toneMapping = NoToneMapping; - renderer.autoClear = false; + const t = ( 1 + Math.sqrt( 5 ) ) / 2; + const r = 1 / t; - const backgroundMaterial = new MeshBasicMaterial( { - name: 'PMREM.Background', - side: BackSide, - depthWrite: false, - depthTest: false, - } ); + const vertices = [ - const backgroundBox = new Mesh( new BoxGeometry(), backgroundMaterial ); + // (±1, ±1, ±1) + -1, -1, -1, -1, -1, 1, + -1, 1, -1, -1, 1, 1, + 1, -1, -1, 1, -1, 1, + 1, 1, -1, 1, 1, 1, - let useSolidColor = false; - const background = scene.background; + // (0, ±1/φ, ±φ) + 0, - r, - t, 0, - r, t, + 0, r, - t, 0, r, t, - if ( background ) { + // (±1/φ, ±φ, 0) + - r, - t, 0, - r, t, 0, + r, - t, 0, r, t, 0, - if ( background.isColor ) { + // (±φ, 0, ±1/φ) + - t, 0, - r, t, 0, - r, + - t, 0, r, t, 0, r + ]; - backgroundMaterial.color.copy( background ); - scene.background = null; - useSolidColor = true; + const indices = [ + 3, 11, 7, 3, 7, 15, 3, 15, 13, + 7, 19, 17, 7, 17, 6, 7, 6, 15, + 17, 4, 8, 17, 8, 10, 17, 10, 6, + 8, 0, 16, 8, 16, 2, 8, 2, 10, + 0, 12, 1, 0, 1, 18, 0, 18, 16, + 6, 10, 2, 6, 2, 13, 6, 13, 15, + 2, 16, 18, 2, 18, 3, 2, 3, 13, + 18, 1, 9, 18, 9, 11, 18, 11, 3, + 4, 14, 12, 4, 12, 0, 4, 0, 8, + 11, 9, 5, 11, 5, 19, 11, 19, 7, + 19, 5, 14, 19, 14, 4, 19, 4, 17, + 1, 12, 14, 1, 14, 5, 1, 5, 9 + ]; - } + super( vertices, indices, radius, detail ); - } else { + this.type = 'DodecahedronGeometry'; - backgroundMaterial.color.copy( _clearColor ); - useSolidColor = true; + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + radius: radius, + detail: detail + }; - } + } - for ( let i = 0; i < 6; i ++ ) { + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {DodecahedronGeometry} A new instance. + */ + static fromJSON( data ) { - const col = i % 3; + return new DodecahedronGeometry( data.radius, data.detail ); - if ( col === 0 ) { + } - cubeCamera.up.set( 0, upSign[ i ], 0 ); - cubeCamera.lookAt( forwardSign[ i ], 0, 0 ); +} - } else if ( col === 1 ) { +const _v0$1 = /*@__PURE__*/ new Vector3(); +const _v1$1 = /*@__PURE__*/ new Vector3(); +const _normal = /*@__PURE__*/ new Vector3(); +const _triangle = /*@__PURE__*/ new Triangle(); - cubeCamera.up.set( 0, 0, upSign[ i ] ); - cubeCamera.lookAt( 0, forwardSign[ i ], 0 ); +/** + * Can be used as a helper object to view the edges of a geometry. + * + * ```js + * const geometry = new THREE.BoxGeometry(); + * const edges = new THREE.EdgesGeometry( geometry ); + * const line = new THREE.LineSegments( edges ); + * scene.add( line ); + * ``` + * + * Note: It is not yet possible to serialize/deserialize instances of this class. + * + * @augments BufferGeometry + */ +class EdgesGeometry extends BufferGeometry { - } else { + /** + * Constructs a new edges geometry. + * + * @param {?BufferGeometry} [geometry=null] - The geometry. + * @param {number} [thresholdAngle=1] - An edge is only rendered if the angle (in degrees) + * between the face normals of the adjoining faces exceeds this value. + */ + constructor( geometry = null, thresholdAngle = 1 ) { - cubeCamera.up.set( 0, upSign[ i ], 0 ); - cubeCamera.lookAt( 0, 0, forwardSign[ i ] ); + super(); - } + this.type = 'EdgesGeometry'; - const size = this._cubeSize; + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + geometry: geometry, + thresholdAngle: thresholdAngle + }; - _setViewport( cubeUVRenderTarget, col * size, i > 2 ? size : 0, size, size ); + if ( geometry !== null ) { - renderer.setRenderTarget( cubeUVRenderTarget ); + const precisionPoints = 4; + const precision = Math.pow( 10, precisionPoints ); + const thresholdDot = Math.cos( DEG2RAD * thresholdAngle ); - if ( useSolidColor ) { + const indexAttr = geometry.getIndex(); + const positionAttr = geometry.getAttribute( 'position' ); + const indexCount = indexAttr ? indexAttr.count : positionAttr.count; - renderer.render( backgroundBox, cubeCamera ); + const indexArr = [ 0, 0, 0 ]; + const vertKeys = [ 'a', 'b', 'c' ]; + const hashes = new Array( 3 ); - } + const edgeData = {}; + const vertices = []; + for ( let i = 0; i < indexCount; i += 3 ) { - renderer.render( scene, cubeCamera ); + if ( indexAttr ) { - } + indexArr[ 0 ] = indexAttr.getX( i ); + indexArr[ 1 ] = indexAttr.getX( i + 1 ); + indexArr[ 2 ] = indexAttr.getX( i + 2 ); - backgroundBox.geometry.dispose(); - backgroundBox.material.dispose(); + } else { - renderer.toneMapping = toneMapping; - renderer.autoClear = originalAutoClear; - scene.background = background; + indexArr[ 0 ] = i; + indexArr[ 1 ] = i + 1; + indexArr[ 2 ] = i + 2; - } + } - _textureToCubeUV( texture, cubeUVRenderTarget ) { + const { a, b, c } = _triangle; + a.fromBufferAttribute( positionAttr, indexArr[ 0 ] ); + b.fromBufferAttribute( positionAttr, indexArr[ 1 ] ); + c.fromBufferAttribute( positionAttr, indexArr[ 2 ] ); + _triangle.getNormal( _normal ); - const renderer = this._renderer; + // create hashes for the edge from the vertices + hashes[ 0 ] = `${ Math.round( a.x * precision ) },${ Math.round( a.y * precision ) },${ Math.round( a.z * precision ) }`; + hashes[ 1 ] = `${ Math.round( b.x * precision ) },${ Math.round( b.y * precision ) },${ Math.round( b.z * precision ) }`; + hashes[ 2 ] = `${ Math.round( c.x * precision ) },${ Math.round( c.y * precision ) },${ Math.round( c.z * precision ) }`; - const isCubeTexture = ( texture.mapping === CubeReflectionMapping || texture.mapping === CubeRefractionMapping ); + // skip degenerate triangles + if ( hashes[ 0 ] === hashes[ 1 ] || hashes[ 1 ] === hashes[ 2 ] || hashes[ 2 ] === hashes[ 0 ] ) { - if ( isCubeTexture ) { + continue; - if ( this._cubemapMaterial === null ) { + } - this._cubemapMaterial = _getCubemapMaterial(); + // iterate over every edge + for ( let j = 0; j < 3; j ++ ) { - } + // get the first and next vertex making up the edge + const jNext = ( j + 1 ) % 3; + const vecHash0 = hashes[ j ]; + const vecHash1 = hashes[ jNext ]; + const v0 = _triangle[ vertKeys[ j ] ]; + const v1 = _triangle[ vertKeys[ jNext ] ]; - this._cubemapMaterial.uniforms.flipEnvMap.value = ( texture.isRenderTargetTexture === false ) ? - 1 : 1; + const hash = `${ vecHash0 }_${ vecHash1 }`; + const reverseHash = `${ vecHash1 }_${ vecHash0 }`; - } else { + if ( reverseHash in edgeData && edgeData[ reverseHash ] ) { - if ( this._equirectMaterial === null ) { + // if we found a sibling edge add it into the vertex array if + // it meets the angle threshold and delete the edge from the map. + if ( _normal.dot( edgeData[ reverseHash ].normal ) <= thresholdDot ) { - this._equirectMaterial = _getEquirectMaterial(); + vertices.push( v0.x, v0.y, v0.z ); + vertices.push( v1.x, v1.y, v1.z ); - } + } - } + edgeData[ reverseHash ] = null; - const material = isCubeTexture ? this._cubemapMaterial : this._equirectMaterial; - const mesh = new Mesh( this._lodPlanes[ 0 ], material ); + } else if ( ! ( hash in edgeData ) ) { - const uniforms = material.uniforms; + // if we've already got an edge here then skip adding a new one + edgeData[ hash ] = { - uniforms[ 'envMap' ].value = texture; + index0: indexArr[ j ], + index1: indexArr[ jNext ], + normal: _normal.clone(), - const size = this._cubeSize; + }; - _setViewport( cubeUVRenderTarget, 0, 0, 3 * size, 2 * size ); + } - renderer.setRenderTarget( cubeUVRenderTarget ); - renderer.render( mesh, _flatCamera ); + } - } + } - _applyPMREM( cubeUVRenderTarget ) { + // iterate over all remaining, unmatched edges and add them to the vertex array + for ( const key in edgeData ) { - const renderer = this._renderer; - const autoClear = renderer.autoClear; - renderer.autoClear = false; + if ( edgeData[ key ] ) { + + const { index0, index1 } = edgeData[ key ]; + _v0$1.fromBufferAttribute( positionAttr, index0 ); + _v1$1.fromBufferAttribute( positionAttr, index1 ); - for ( let i = 1; i < this._lodPlanes.length; i ++ ) { + vertices.push( _v0$1.x, _v0$1.y, _v0$1.z ); + vertices.push( _v1$1.x, _v1$1.y, _v1$1.z ); - const sigma = Math.sqrt( this._sigmas[ i ] * this._sigmas[ i ] - this._sigmas[ i - 1 ] * this._sigmas[ i - 1 ] ); + } - const poleAxis = _axisDirections[ ( i - 1 ) % _axisDirections.length ]; + } - this._blur( cubeUVRenderTarget, i - 1, i, sigma, poleAxis ); + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); } - renderer.autoClear = autoClear; - } - /** - * This is a two-pass Gaussian blur for a cubemap. Normally this is done - * vertically and horizontally, but this breaks down on a cube. Here we apply - * the blur latitudinally (around the poles), and then longitudinally (towards - * the poles) to approximate the orthogonally-separable blur. It is least - * accurate at the poles, but still does a decent job. - */ - _blur( cubeUVRenderTarget, lodIn, lodOut, sigma, poleAxis ) { + copy( source ) { - const pingPongRenderTarget = this._pingPongRenderTarget; + super.copy( source ); - this._halfBlur( - cubeUVRenderTarget, - pingPongRenderTarget, - lodIn, - lodOut, - sigma, - 'latitudinal', - poleAxis ); + this.parameters = Object.assign( {}, source.parameters ); - this._halfBlur( - pingPongRenderTarget, - cubeUVRenderTarget, - lodOut, - lodOut, - sigma, - 'longitudinal', - poleAxis ); + return this; } - _halfBlur( targetIn, targetOut, lodIn, lodOut, sigmaRadians, direction, poleAxis ) { - - const renderer = this._renderer; - const blurMaterial = this._blurMaterial; +} - if ( direction !== 'latitudinal' && direction !== 'longitudinal' ) { +/** + * An abstract base class for creating an analytic curve object that contains methods + * for interpolation. + * + * @abstract + */ +class Curve { - console.error( - 'blur direction must be either latitudinal or longitudinal!' ); + /** + * Constructs a new curve. + */ + constructor() { - } + /** + * The type property is used for detecting the object type + * in context of serialization/deserialization. + * + * @type {string} + * @readonly + */ + this.type = 'Curve'; - // Number of standard deviations at which to cut off the discrete approximation. - const STANDARD_DEVIATIONS = 3; + /** + * This value determines the amount of divisions when calculating the + * cumulative segment lengths of a curve via {@link Curve#getLengths}. To ensure + * precision when using methods like {@link Curve#getSpacedPoints}, it is + * recommended to increase the value of this property if the curve is very large. + * + * @type {number} + * @default 200 + */ + this.arcLengthDivisions = 200; - const blurMesh = new Mesh( this._lodPlanes[ lodOut ], blurMaterial ); - const blurUniforms = blurMaterial.uniforms; + /** + * Must be set to `true` if the curve parameters have changed. + * + * @type {boolean} + * @default false + */ + this.needsUpdate = false; - const pixels = this._sizeLods[ lodIn ] - 1; - const radiansPerPixel = isFinite( sigmaRadians ) ? Math.PI / ( 2 * pixels ) : 2 * Math.PI / ( 2 * MAX_SAMPLES - 1 ); - const sigmaPixels = sigmaRadians / radiansPerPixel; - const samples = isFinite( sigmaRadians ) ? 1 + Math.floor( STANDARD_DEVIATIONS * sigmaPixels ) : MAX_SAMPLES; + /** + * An internal cache that holds precomputed curve length values. + * + * @private + * @type {?Array} + * @default null + */ + this.cacheArcLengths = null; - if ( samples > MAX_SAMPLES ) { + } - console.warn( `sigmaRadians, ${ - sigmaRadians}, is too large and will clip, as it requested ${ - samples} samples when the maximum is set to ${MAX_SAMPLES}` ); + /** + * This method returns a vector in 2D or 3D space (depending on the curve definition) + * for the given interpolation factor. + * + * @abstract + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {(Vector2|Vector3)} [optionalTarget] - The optional target vector the result is written to. + * @return {(Vector2|Vector3)} The position on the curve. It can be a 2D or 3D vector depending on the curve definition. + */ + getPoint( /* t, optionalTarget */ ) { - } + console.warn( 'THREE.Curve: .getPoint() not implemented.' ); - const weights = []; - let sum = 0; + } - for ( let i = 0; i < MAX_SAMPLES; ++ i ) { + /** + * This method returns a vector in 2D or 3D space (depending on the curve definition) + * for the given interpolation factor. Unlike {@link Curve#getPoint}, this method honors the length + * of the curve which equidistant samples. + * + * @param {number} u - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {(Vector2|Vector3)} [optionalTarget] - The optional target vector the result is written to. + * @return {(Vector2|Vector3)} The position on the curve. It can be a 2D or 3D vector depending on the curve definition. + */ + getPointAt( u, optionalTarget ) { - const x = i / sigmaPixels; - const weight = Math.exp( - x * x / 2 ); - weights.push( weight ); + const t = this.getUtoTmapping( u ); + return this.getPoint( t, optionalTarget ); - if ( i === 0 ) { + } - sum += weight; + /** + * This method samples the curve via {@link Curve#getPoint} and returns an array of points representing + * the curve shape. + * + * @param {number} [divisions=5] - The number of divisions. + * @return {Array<(Vector2|Vector3)>} An array holding the sampled curve values. The number of points is `divisions + 1`. + */ + getPoints( divisions = 5 ) { - } else if ( i < samples ) { + const points = []; - sum += 2 * weight; + for ( let d = 0; d <= divisions; d ++ ) { - } + points.push( this.getPoint( d / divisions ) ); } - for ( let i = 0; i < weights.length; i ++ ) { - - weights[ i ] = weights[ i ] / sum; + return points; - } + } - blurUniforms[ 'envMap' ].value = targetIn.texture; - blurUniforms[ 'samples' ].value = samples; - blurUniforms[ 'weights' ].value = weights; - blurUniforms[ 'latitudinal' ].value = direction === 'latitudinal'; + // Get sequence of points using getPointAt( u ) - if ( poleAxis ) { + /** + * This method samples the curve via {@link Curve#getPointAt} and returns an array of points representing + * the curve shape. Unlike {@link Curve#getPoints}, this method returns equi-spaced points across the entire + * curve. + * + * @param {number} [divisions=5] - The number of divisions. + * @return {Array<(Vector2|Vector3)>} An array holding the sampled curve values. The number of points is `divisions + 1`. + */ + getSpacedPoints( divisions = 5 ) { - blurUniforms[ 'poleAxis' ].value = poleAxis; + const points = []; - } + for ( let d = 0; d <= divisions; d ++ ) { - const { _lodMax } = this; - blurUniforms[ 'dTheta' ].value = radiansPerPixel; - blurUniforms[ 'mipInt' ].value = _lodMax - lodIn; + points.push( this.getPointAt( d / divisions ) ); - const outputSize = this._sizeLods[ lodOut ]; - const x = 3 * outputSize * ( lodOut > _lodMax - LOD_MIN ? lodOut - _lodMax + LOD_MIN : 0 ); - const y = 4 * ( this._cubeSize - outputSize ); + } - _setViewport( targetOut, x, y, 3 * outputSize, 2 * outputSize ); - renderer.setRenderTarget( targetOut ); - renderer.render( blurMesh, _flatCamera ); + return points; } -} - + /** + * Returns the total arc length of the curve. + * + * @return {number} The length of the curve. + */ + getLength() { + const lengths = this.getLengths(); + return lengths[ lengths.length - 1 ]; -function _createPlanes( lodMax ) { + } - const lodPlanes = []; - const sizeLods = []; - const sigmas = []; + /** + * Returns an array of cumulative segment lengths of the curve. + * + * @param {number} [divisions=this.arcLengthDivisions] - The number of divisions. + * @return {Array} An array holding the cumulative segment lengths. + */ + getLengths( divisions = this.arcLengthDivisions ) { - let lod = lodMax; + if ( this.cacheArcLengths && + ( this.cacheArcLengths.length === divisions + 1 ) && + ! this.needsUpdate ) { - const totalLods = lodMax - LOD_MIN + 1 + EXTRA_LOD_SIGMA.length; + return this.cacheArcLengths; - for ( let i = 0; i < totalLods; i ++ ) { + } - const sizeLod = Math.pow( 2, lod ); - sizeLods.push( sizeLod ); - let sigma = 1.0 / sizeLod; + this.needsUpdate = false; - if ( i > lodMax - LOD_MIN ) { + const cache = []; + let current, last = this.getPoint( 0 ); + let sum = 0; - sigma = EXTRA_LOD_SIGMA[ i - lodMax + LOD_MIN - 1 ]; + cache.push( 0 ); - } else if ( i === 0 ) { + for ( let p = 1; p <= divisions; p ++ ) { - sigma = 0; + current = this.getPoint( p / divisions ); + sum += current.distanceTo( last ); + cache.push( sum ); + last = current; } - sigmas.push( sigma ); + this.cacheArcLengths = cache; - const texelSize = 1.0 / ( sizeLod - 2 ); - const min = - texelSize; - const max = 1 + texelSize; - const uv1 = [ min, min, max, min, max, max, min, min, max, max, min, max ]; + return cache; // { sums: cache, sum: sum }; Sum is in the last element. - const cubeFaces = 6; - const vertices = 6; - const positionSize = 3; - const uvSize = 2; - const faceIndexSize = 1; + } - const position = new Float32Array( positionSize * vertices * cubeFaces ); - const uv = new Float32Array( uvSize * vertices * cubeFaces ); - const faceIndex = new Float32Array( faceIndexSize * vertices * cubeFaces ); + /** + * Update the cumulative segment distance cache. The method must be called + * every time curve parameters are changed. If an updated curve is part of a + * composed curve like {@link CurvePath}, this method must be called on the + * composed curve, too. + */ + updateArcLengths() { - for ( let face = 0; face < cubeFaces; face ++ ) { + this.needsUpdate = true; + this.getLengths(); - const x = ( face % 3 ) * 2 / 3 - 1; - const y = face > 2 ? 0 : - 1; - const coordinates = [ - x, y, 0, - x + 2 / 3, y, 0, - x + 2 / 3, y + 1, 0, - x, y, 0, - x + 2 / 3, y + 1, 0, - x, y + 1, 0 - ]; - position.set( coordinates, positionSize * vertices * face ); - uv.set( uv1, uvSize * vertices * face ); - const fill = [ face, face, face, face, face, face ]; - faceIndex.set( fill, faceIndexSize * vertices * face ); + } + + /** + * Given an interpolation factor in the range `[0,1]`, this method returns an updated + * interpolation factor in the same range that can be ued to sample equidistant points + * from a curve. + * + * @param {number} u - The interpolation factor. + * @param {?number} distance - An optional distance on the curve. + * @return {number} The updated interpolation factor. + */ + getUtoTmapping( u, distance = null ) { - } + const arcLengths = this.getLengths(); - const planes = new BufferGeometry(); - planes.setAttribute( 'position', new BufferAttribute( position, positionSize ) ); - planes.setAttribute( 'uv', new BufferAttribute( uv, uvSize ) ); - planes.setAttribute( 'faceIndex', new BufferAttribute( faceIndex, faceIndexSize ) ); - lodPlanes.push( planes ); + let i = 0; + const il = arcLengths.length; - if ( lod > LOD_MIN ) { + let targetArcLength; // The targeted u distance value to get - lod --; + if ( distance ) { + + targetArcLength = distance; + + } else { + + targetArcLength = u * arcLengths[ il - 1 ]; } - } + // binary search for the index with largest value smaller than target u distance - return { lodPlanes, sizeLods, sigmas }; + let low = 0, high = il - 1, comparison; -} + while ( low <= high ) { -function _createRenderTarget( width, height, params ) { + i = Math.floor( low + ( high - low ) / 2 ); // less likely to overflow, though probably not issue here, JS doesn't really have integers, all numbers are floats - const cubeUVRenderTarget = new WebGLRenderTarget( width, height, params ); - cubeUVRenderTarget.texture.mapping = CubeUVReflectionMapping; - cubeUVRenderTarget.texture.name = 'PMREM.cubeUv'; - cubeUVRenderTarget.scissorTest = true; - return cubeUVRenderTarget; + comparison = arcLengths[ i ] - targetArcLength; -} + if ( comparison < 0 ) { -function _setViewport( target, x, y, width, height ) { + low = i + 1; - target.viewport.set( x, y, width, height ); - target.scissor.set( x, y, width, height ); + } else if ( comparison > 0 ) { -} + high = i - 1; -function _getBlurShader( lodMax, width, height ) { + } else { - const weights = new Float32Array( MAX_SAMPLES ); - const poleAxis = new Vector3( 0, 1, 0 ); - const shaderMaterial = new ShaderMaterial( { + high = i; + break; - name: 'SphericalGaussianBlur', + // DONE - defines: { - 'n': MAX_SAMPLES, - 'CUBEUV_TEXEL_WIDTH': 1.0 / width, - 'CUBEUV_TEXEL_HEIGHT': 1.0 / height, - 'CUBEUV_MAX_MIP': `${lodMax}.0`, - }, + } - uniforms: { - 'envMap': { value: null }, - 'samples': { value: 1 }, - 'weights': { value: weights }, - 'latitudinal': { value: false }, - 'dTheta': { value: 0 }, - 'mipInt': { value: 0 }, - 'poleAxis': { value: poleAxis } - }, + } - vertexShader: _getCommonVertexShader(), + i = high; - fragmentShader: /* glsl */` + if ( arcLengths[ i ] === targetArcLength ) { - precision mediump float; - precision mediump int; + return i / ( il - 1 ); - varying vec3 vOutputDirection; + } - uniform sampler2D envMap; - uniform int samples; - uniform float weights[ n ]; - uniform bool latitudinal; - uniform float dTheta; - uniform float mipInt; - uniform vec3 poleAxis; + // we could get finer grain at lengths, or use simple interpolation between two points - #define ENVMAP_TYPE_CUBE_UV - #include + const lengthBefore = arcLengths[ i ]; + const lengthAfter = arcLengths[ i + 1 ]; - vec3 getSample( float theta, vec3 axis ) { + const segmentLength = lengthAfter - lengthBefore; - float cosTheta = cos( theta ); - // Rodrigues' axis-angle rotation - vec3 sampleDirection = vOutputDirection * cosTheta - + cross( axis, vOutputDirection ) * sin( theta ) - + axis * dot( axis, vOutputDirection ) * ( 1.0 - cosTheta ); + // determine where we are between the 'before' and 'after' points - return bilinearCubeUV( envMap, sampleDirection, mipInt ); + const segmentFraction = ( targetArcLength - lengthBefore ) / segmentLength; - } + // add that fractional amount to t - void main() { + const t = ( i + segmentFraction ) / ( il - 1 ); - vec3 axis = latitudinal ? poleAxis : cross( poleAxis, vOutputDirection ); + return t; - if ( all( equal( axis, vec3( 0.0 ) ) ) ) { + } - axis = vec3( vOutputDirection.z, 0.0, - vOutputDirection.x ); + /** + * Returns a unit vector tangent for the given interpolation factor. + * If the derived curve does not implement its tangent derivation, + * two points a small delta apart will be used to find its gradient + * which seems to give a reasonable approximation. + * + * @param {number} t - The interpolation factor. + * @param {(Vector2|Vector3)} [optionalTarget] - The optional target vector the result is written to. + * @return {(Vector2|Vector3)} The tangent vector. + */ + getTangent( t, optionalTarget ) { - } + const delta = 0.0001; + let t1 = t - delta; + let t2 = t + delta; - axis = normalize( axis ); + // Capping in case of danger - gl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 ); - gl_FragColor.rgb += weights[ 0 ] * getSample( 0.0, axis ); + if ( t1 < 0 ) t1 = 0; + if ( t2 > 1 ) t2 = 1; - for ( int i = 1; i < n; i++ ) { + const pt1 = this.getPoint( t1 ); + const pt2 = this.getPoint( t2 ); - if ( i >= samples ) { + const tangent = optionalTarget || ( ( pt1.isVector2 ) ? new Vector2() : new Vector3() ); - break; + tangent.copy( pt2 ).sub( pt1 ).normalize(); - } + return tangent; - float theta = dTheta * float( i ); - gl_FragColor.rgb += weights[ i ] * getSample( -1.0 * theta, axis ); - gl_FragColor.rgb += weights[ i ] * getSample( theta, axis ); + } - } + /** + * Same as {@link Curve#getTangent} but with equidistant samples. + * + * @param {number} u - The interpolation factor. + * @param {(Vector2|Vector3)} [optionalTarget] - The optional target vector the result is written to. + * @return {(Vector2|Vector3)} The tangent vector. + * @see {@link Curve#getPointAt} + */ + getTangentAt( u, optionalTarget ) { - } - `, + const t = this.getUtoTmapping( u ); + return this.getTangent( t, optionalTarget ); - blending: NoBlending, - depthTest: false, - depthWrite: false + } - } ); + /** + * Generates the Frenet Frames. Requires a curve definition in 3D space. Used + * in geometries like {@link TubeGeometry} or {@link ExtrudeGeometry}. + * + * @param {number} segments - The number of segments. + * @param {boolean} [closed=false] - Whether the curve is closed or not. + * @return {{tangents: Array, normals: Array, binormals: Array}} The Frenet Frames. + */ + computeFrenetFrames( segments, closed = false ) { - return shaderMaterial; + // see http://www.cs.indiana.edu/pub/techreports/TR425.pdf -} + const normal = new Vector3(); -function _getEquirectMaterial() { + const tangents = []; + const normals = []; + const binormals = []; - return new ShaderMaterial( { + const vec = new Vector3(); + const mat = new Matrix4(); - name: 'EquirectangularToCubeUV', + // compute the tangent vectors for each segment on the curve - uniforms: { - 'envMap': { value: null } - }, + for ( let i = 0; i <= segments; i ++ ) { - vertexShader: _getCommonVertexShader(), + const u = i / segments; - fragmentShader: /* glsl */` + tangents[ i ] = this.getTangentAt( u, new Vector3() ); - precision mediump float; - precision mediump int; + } - varying vec3 vOutputDirection; + // select an initial normal vector perpendicular to the first tangent vector, + // and in the direction of the minimum tangent xyz component - uniform sampler2D envMap; + normals[ 0 ] = new Vector3(); + binormals[ 0 ] = new Vector3(); + let min = Number.MAX_VALUE; + const tx = Math.abs( tangents[ 0 ].x ); + const ty = Math.abs( tangents[ 0 ].y ); + const tz = Math.abs( tangents[ 0 ].z ); - #include + if ( tx <= min ) { - void main() { + min = tx; + normal.set( 1, 0, 0 ); - vec3 outputDirection = normalize( vOutputDirection ); - vec2 uv = equirectUv( outputDirection ); + } - gl_FragColor = vec4( texture2D ( envMap, uv ).rgb, 1.0 ); + if ( ty <= min ) { - } - `, + min = ty; + normal.set( 0, 1, 0 ); - blending: NoBlending, - depthTest: false, - depthWrite: false + } - } ); + if ( tz <= min ) { -} + normal.set( 0, 0, 1 ); -function _getCubemapMaterial() { + } - return new ShaderMaterial( { + vec.crossVectors( tangents[ 0 ], normal ).normalize(); - name: 'CubemapToCubeUV', + normals[ 0 ].crossVectors( tangents[ 0 ], vec ); + binormals[ 0 ].crossVectors( tangents[ 0 ], normals[ 0 ] ); - uniforms: { - 'envMap': { value: null }, - 'flipEnvMap': { value: - 1 } - }, - vertexShader: _getCommonVertexShader(), + // compute the slowly-varying normal and binormal vectors for each segment on the curve - fragmentShader: /* glsl */` + for ( let i = 1; i <= segments; i ++ ) { - precision mediump float; - precision mediump int; + normals[ i ] = normals[ i - 1 ].clone(); - uniform float flipEnvMap; + binormals[ i ] = binormals[ i - 1 ].clone(); - varying vec3 vOutputDirection; + vec.crossVectors( tangents[ i - 1 ], tangents[ i ] ); - uniform samplerCube envMap; + if ( vec.length() > Number.EPSILON ) { - void main() { + vec.normalize(); - gl_FragColor = textureCube( envMap, vec3( flipEnvMap * vOutputDirection.x, vOutputDirection.yz ) ); + const theta = Math.acos( clamp( tangents[ i - 1 ].dot( tangents[ i ] ), -1, 1 ) ); // clamp for floating pt errors - } - `, + normals[ i ].applyMatrix4( mat.makeRotationAxis( vec, theta ) ); - blending: NoBlending, - depthTest: false, - depthWrite: false + } - } ); + binormals[ i ].crossVectors( tangents[ i ], normals[ i ] ); -} + } -function _getCommonVertexShader() { + // if the curve is closed, postprocess the vectors so the first and last normal vectors are the same - return /* glsl */` + if ( closed === true ) { - precision mediump float; - precision mediump int; + let theta = Math.acos( clamp( normals[ 0 ].dot( normals[ segments ] ), -1, 1 ) ); + theta /= segments; - attribute float faceIndex; + if ( tangents[ 0 ].dot( vec.crossVectors( normals[ 0 ], normals[ segments ] ) ) > 0 ) { - varying vec3 vOutputDirection; + theta = - theta; - // RH coordinate system; PMREM face-indexing convention - vec3 getDirection( vec2 uv, float face ) { + } - uv = 2.0 * uv - 1.0; + for ( let i = 1; i <= segments; i ++ ) { - vec3 direction = vec3( uv, 1.0 ); + // twist a little... + normals[ i ].applyMatrix4( mat.makeRotationAxis( tangents[ i ], theta * i ) ); + binormals[ i ].crossVectors( tangents[ i ], normals[ i ] ); - if ( face == 0.0 ) { + } - direction = direction.zyx; // ( 1, v, u ) pos x + } - } else if ( face == 1.0 ) { + return { + tangents: tangents, + normals: normals, + binormals: binormals + }; - direction = direction.xzy; - direction.xz *= -1.0; // ( -u, 1, -v ) pos y + } - } else if ( face == 2.0 ) { + /** + * Returns a new curve with copied values from this instance. + * + * @return {Curve} A clone of this instance. + */ + clone() { - direction.x *= -1.0; // ( -u, v, 1 ) pos z + return new this.constructor().copy( this ); - } else if ( face == 3.0 ) { + } - direction = direction.zyx; - direction.xz *= -1.0; // ( -1, v, -u ) neg x + /** + * Copies the values of the given curve to this instance. + * + * @param {Curve} source - The curve to copy. + * @return {Curve} A reference to this curve. + */ + copy( source ) { - } else if ( face == 4.0 ) { + this.arcLengthDivisions = source.arcLengthDivisions; - direction = direction.xzy; - direction.xy *= -1.0; // ( -u, -1, v ) neg y + return this; - } else if ( face == 5.0 ) { + } - direction.z *= -1.0; // ( u, v, -1 ) neg z + /** + * Serializes the curve into JSON. + * + * @return {Object} A JSON object representing the serialized curve. + * @see {@link ObjectLoader#parse} + */ + toJSON() { + const data = { + metadata: { + version: 4.7, + type: 'Curve', + generator: 'Curve.toJSON' } + }; - return direction; + data.arcLengthDivisions = this.arcLengthDivisions; + data.type = this.type; - } + return data; - void main() { + } - vOutputDirection = getDirection( uv, faceIndex ); - gl_Position = vec4( position, 1.0 ); + /** + * Deserializes the curve from the given JSON. + * + * @param {Object} json - The JSON holding the serialized curve. + * @return {Curve} A reference to this curve. + */ + fromJSON( json ) { - } - `; + this.arcLengthDivisions = json.arcLengthDivisions; -} + return this; -function WebGLCubeUVMaps( renderer ) { + } - let cubeUVmaps = new WeakMap(); +} - let pmremGenerator = null; +/** + * A curve representing an ellipse. + * + * ```js + * const curve = new THREE.EllipseCurve( + * 0, 0, + * 10, 10, + * 0, 2 * Math.PI, + * false, + * 0 + * ); + * + * const points = curve.getPoints( 50 ); + * const geometry = new THREE.BufferGeometry().setFromPoints( points ); + * + * const material = new THREE.LineBasicMaterial( { color: 0xff0000 } ); + * + * // Create the final object to add to the scene + * const ellipse = new THREE.Line( geometry, material ); + * ``` + * + * @augments Curve + */ +class EllipseCurve extends Curve { - function get( texture ) { + /** + * Constructs a new ellipse curve. + * + * @param {number} [aX=0] - The X center of the ellipse. + * @param {number} [aY=0] - The Y center of the ellipse. + * @param {number} [xRadius=1] - The radius of the ellipse in the x direction. + * @param {number} [yRadius=1] - The radius of the ellipse in the y direction. + * @param {number} [aStartAngle=0] - The start angle of the curve in radians starting from the positive X axis. + * @param {number} [aEndAngle=Math.PI*2] - The end angle of the curve in radians starting from the positive X axis. + * @param {boolean} [aClockwise=false] - Whether the ellipse is drawn clockwise or not. + * @param {number} [aRotation=0] - The rotation angle of the ellipse in radians, counterclockwise from the positive X axis. + */ + constructor( aX = 0, aY = 0, xRadius = 1, yRadius = 1, aStartAngle = 0, aEndAngle = Math.PI * 2, aClockwise = false, aRotation = 0 ) { - if ( texture && texture.isTexture ) { + super(); - const mapping = texture.mapping; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isEllipseCurve = true; - const isEquirectMap = ( mapping === EquirectangularReflectionMapping || mapping === EquirectangularRefractionMapping ); - const isCubeMap = ( mapping === CubeReflectionMapping || mapping === CubeRefractionMapping ); + this.type = 'EllipseCurve'; - // equirect/cube map to cubeUV conversion + /** + * The X center of the ellipse. + * + * @type {number} + * @default 0 + */ + this.aX = aX; - if ( isEquirectMap || isCubeMap ) { + /** + * The Y center of the ellipse. + * + * @type {number} + * @default 0 + */ + this.aY = aY; - if ( texture.isRenderTargetTexture && texture.needsPMREMUpdate === true ) { + /** + * The radius of the ellipse in the x direction. + * Setting the this value equal to the {@link EllipseCurve#yRadius} will result in a circle. + * + * @type {number} + * @default 1 + */ + this.xRadius = xRadius; - texture.needsPMREMUpdate = false; + /** + * The radius of the ellipse in the y direction. + * Setting the this value equal to the {@link EllipseCurve#xRadius} will result in a circle. + * + * @type {number} + * @default 1 + */ + this.yRadius = yRadius; - let renderTarget = cubeUVmaps.get( texture ); + /** + * The start angle of the curve in radians starting from the positive X axis. + * + * @type {number} + * @default 0 + */ + this.aStartAngle = aStartAngle; - if ( pmremGenerator === null ) pmremGenerator = new PMREMGenerator( renderer ); + /** + * The end angle of the curve in radians starting from the positive X axis. + * + * @type {number} + * @default Math.PI*2 + */ + this.aEndAngle = aEndAngle; - renderTarget = isEquirectMap ? pmremGenerator.fromEquirectangular( texture, renderTarget ) : pmremGenerator.fromCubemap( texture, renderTarget ); - cubeUVmaps.set( texture, renderTarget ); + /** + * Whether the ellipse is drawn clockwise or not. + * + * @type {boolean} + * @default false + */ + this.aClockwise = aClockwise; - return renderTarget.texture; + /** + * The rotation angle of the ellipse in radians, counterclockwise from the positive X axis. + * + * @type {number} + * @default 0 + */ + this.aRotation = aRotation; - } else { + } - if ( cubeUVmaps.has( texture ) ) { + /** + * Returns a point on the curve. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector2} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector2} The position on the curve. + */ + getPoint( t, optionalTarget = new Vector2() ) { - return cubeUVmaps.get( texture ).texture; + const point = optionalTarget; - } else { + const twoPi = Math.PI * 2; + let deltaAngle = this.aEndAngle - this.aStartAngle; + const samePoints = Math.abs( deltaAngle ) < Number.EPSILON; - const image = texture.image; + // ensures that deltaAngle is 0 .. 2 PI + while ( deltaAngle < 0 ) deltaAngle += twoPi; + while ( deltaAngle > twoPi ) deltaAngle -= twoPi; - if ( ( isEquirectMap && image && image.height > 0 ) || ( isCubeMap && image && isCubeTextureComplete( image ) ) ) { + if ( deltaAngle < Number.EPSILON ) { - if ( pmremGenerator === null ) pmremGenerator = new PMREMGenerator( renderer ); + if ( samePoints ) { - const renderTarget = isEquirectMap ? pmremGenerator.fromEquirectangular( texture ) : pmremGenerator.fromCubemap( texture ); - cubeUVmaps.set( texture, renderTarget ); + deltaAngle = 0; - texture.addEventListener( 'dispose', onTextureDispose ); + } else { - return renderTarget.texture; + deltaAngle = twoPi; - } else { + } - // image not yet ready. try the conversion next frame + } - return null; + if ( this.aClockwise === true && ! samePoints ) { - } + if ( deltaAngle === twoPi ) { - } + deltaAngle = - twoPi; - } + } else { + + deltaAngle = deltaAngle - twoPi; } } - return texture; + const angle = this.aStartAngle + t * deltaAngle; + let x = this.aX + this.xRadius * Math.cos( angle ); + let y = this.aY + this.yRadius * Math.sin( angle ); + + if ( this.aRotation !== 0 ) { + + const cos = Math.cos( this.aRotation ); + const sin = Math.sin( this.aRotation ); + + const tx = x - this.aX; + const ty = y - this.aY; + + // Rotate the point about the center of the ellipse. + x = tx * cos - ty * sin + this.aX; + y = tx * sin + ty * cos + this.aY; + + } + + return point.set( x, y ); } - function isCubeTextureComplete( image ) { + copy( source ) { - let count = 0; - const length = 6; + super.copy( source ); - for ( let i = 0; i < length; i ++ ) { + this.aX = source.aX; + this.aY = source.aY; - if ( image[ i ] !== undefined ) count ++; + this.xRadius = source.xRadius; + this.yRadius = source.yRadius; - } + this.aStartAngle = source.aStartAngle; + this.aEndAngle = source.aEndAngle; - return count === length; + this.aClockwise = source.aClockwise; + this.aRotation = source.aRotation; + + return this; } - function onTextureDispose( event ) { + toJSON() { - const texture = event.target; + const data = super.toJSON(); - texture.removeEventListener( 'dispose', onTextureDispose ); + data.aX = this.aX; + data.aY = this.aY; - const cubemapUV = cubeUVmaps.get( texture ); + data.xRadius = this.xRadius; + data.yRadius = this.yRadius; - if ( cubemapUV !== undefined ) { + data.aStartAngle = this.aStartAngle; + data.aEndAngle = this.aEndAngle; - cubeUVmaps.delete( texture ); - cubemapUV.dispose(); + data.aClockwise = this.aClockwise; - } + data.aRotation = this.aRotation; + + return data; } - function dispose() { + fromJSON( json ) { - cubeUVmaps = new WeakMap(); + super.fromJSON( json ); - if ( pmremGenerator !== null ) { + this.aX = json.aX; + this.aY = json.aY; - pmremGenerator.dispose(); - pmremGenerator = null; + this.xRadius = json.xRadius; + this.yRadius = json.yRadius; - } + this.aStartAngle = json.aStartAngle; + this.aEndAngle = json.aEndAngle; - } + this.aClockwise = json.aClockwise; - return { - get: get, - dispose: dispose - }; + this.aRotation = json.aRotation; -} + return this; -function WebGLExtensions( gl ) { + } - const extensions = {}; +} - function getExtension( name ) { +/** + * A curve representing an arc. + * + * @augments EllipseCurve + */ +class ArcCurve extends EllipseCurve { - if ( extensions[ name ] !== undefined ) { + /** + * Constructs a new arc curve. + * + * @param {number} [aX=0] - The X center of the ellipse. + * @param {number} [aY=0] - The Y center of the ellipse. + * @param {number} [aRadius=1] - The radius of the ellipse in the x direction. + * @param {number} [aStartAngle=0] - The start angle of the curve in radians starting from the positive X axis. + * @param {number} [aEndAngle=Math.PI*2] - The end angle of the curve in radians starting from the positive X axis. + * @param {boolean} [aClockwise=false] - Whether the ellipse is drawn clockwise or not. + */ + constructor( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) { - return extensions[ name ]; + super( aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise ); - } + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isArcCurve = true; - let extension; + this.type = 'ArcCurve'; - switch ( name ) { + } - case 'WEBGL_depth_texture': - extension = gl.getExtension( 'WEBGL_depth_texture' ) || gl.getExtension( 'MOZ_WEBGL_depth_texture' ) || gl.getExtension( 'WEBKIT_WEBGL_depth_texture' ); - break; +} - case 'EXT_texture_filter_anisotropic': - extension = gl.getExtension( 'EXT_texture_filter_anisotropic' ) || gl.getExtension( 'MOZ_EXT_texture_filter_anisotropic' ) || gl.getExtension( 'WEBKIT_EXT_texture_filter_anisotropic' ); - break; +function CubicPoly() { - case 'WEBGL_compressed_texture_s3tc': - extension = gl.getExtension( 'WEBGL_compressed_texture_s3tc' ) || gl.getExtension( 'MOZ_WEBGL_compressed_texture_s3tc' ) || gl.getExtension( 'WEBKIT_WEBGL_compressed_texture_s3tc' ); - break; + /** + * Centripetal CatmullRom Curve - which is useful for avoiding + * cusps and self-intersections in non-uniform catmull rom curves. + * http://www.cemyuksel.com/research/catmullrom_param/catmullrom.pdf + * + * curve.type accepts centripetal(default), chordal and catmullrom + * curve.tension is used for catmullrom which defaults to 0.5 + */ - case 'WEBGL_compressed_texture_pvrtc': - extension = gl.getExtension( 'WEBGL_compressed_texture_pvrtc' ) || gl.getExtension( 'WEBKIT_WEBGL_compressed_texture_pvrtc' ); - break; + /* + Based on an optimized c++ solution in + - http://stackoverflow.com/questions/9489736/catmull-rom-curve-with-no-cusps-and-no-self-intersections/ + - http://ideone.com/NoEbVM - default: - extension = gl.getExtension( name ); + This CubicPoly class could be used for reusing some variables and calculations, + but for three.js curve use, it could be possible inlined and flatten into a single function call + which can be placed in CurveUtils. + */ - } + let c0 = 0, c1 = 0, c2 = 0, c3 = 0; - extensions[ name ] = extension; + /* + * Compute coefficients for a cubic polynomial + * p(s) = c0 + c1*s + c2*s^2 + c3*s^3 + * such that + * p(0) = x0, p(1) = x1 + * and + * p'(0) = t0, p'(1) = t1. + */ + function init( x0, x1, t0, t1 ) { - return extension; + c0 = x0; + c1 = t0; + c2 = -3 * x0 + 3 * x1 - 2 * t0 - t1; + c3 = 2 * x0 - 2 * x1 + t0 + t1; } return { - has: function ( name ) { + initCatmullRom: function ( x0, x1, x2, x3, tension ) { - return getExtension( name ) !== null; + init( x1, x2, tension * ( x2 - x0 ), tension * ( x3 - x1 ) ); }, - init: function ( capabilities ) { - - if ( capabilities.isWebGL2 ) { - - getExtension( 'EXT_color_buffer_float' ); - - } else { + initNonuniformCatmullRom: function ( x0, x1, x2, x3, dt0, dt1, dt2 ) { - getExtension( 'WEBGL_depth_texture' ); - getExtension( 'OES_texture_float' ); - getExtension( 'OES_texture_half_float' ); - getExtension( 'OES_texture_half_float_linear' ); - getExtension( 'OES_standard_derivatives' ); - getExtension( 'OES_element_index_uint' ); - getExtension( 'OES_vertex_array_object' ); - getExtension( 'ANGLE_instanced_arrays' ); + // compute tangents when parameterized in [t1,t2] + let t1 = ( x1 - x0 ) / dt0 - ( x2 - x0 ) / ( dt0 + dt1 ) + ( x2 - x1 ) / dt1; + let t2 = ( x2 - x1 ) / dt1 - ( x3 - x1 ) / ( dt1 + dt2 ) + ( x3 - x2 ) / dt2; - } + // rescale tangents for parametrization in [0,1] + t1 *= dt1; + t2 *= dt1; - getExtension( 'OES_texture_float_linear' ); - getExtension( 'EXT_color_buffer_half_float' ); - getExtension( 'WEBGL_multisampled_render_to_texture' ); + init( x1, x2, t1, t2 ); }, - get: function ( name ) { + calc: function ( t ) { - const extension = getExtension( name ); + const t2 = t * t; + const t3 = t2 * t; + return c0 + c1 * t + c2 * t2 + c3 * t3; - if ( extension === null ) { + } - console.warn( 'THREE.WebGLRenderer: ' + name + ' extension not supported.' ); + }; - } +} - return extension; +// - } +const tmp = /*@__PURE__*/ new Vector3(); +const px = /*@__PURE__*/ new CubicPoly(); +const py = /*@__PURE__*/ new CubicPoly(); +const pz = /*@__PURE__*/ new CubicPoly(); - }; +/** + * A curve representing a Catmull-Rom spline. + * + * ```js + * //Create a closed wavey loop + * const curve = new THREE.CatmullRomCurve3( [ + * new THREE.Vector3( -10, 0, 10 ), + * new THREE.Vector3( -5, 5, 5 ), + * new THREE.Vector3( 0, 0, 0 ), + * new THREE.Vector3( 5, -5, 5 ), + * new THREE.Vector3( 10, 0, 10 ) + * ] ); + * + * const points = curve.getPoints( 50 ); + * const geometry = new THREE.BufferGeometry().setFromPoints( points ); + * + * const material = new THREE.LineBasicMaterial( { color: 0xff0000 } ); + * + * // Create the final object to add to the scene + * const curveObject = new THREE.Line( geometry, material ); + * ``` + * + * @augments Curve + */ +class CatmullRomCurve3 extends Curve { -} + /** + * Constructs a new Catmull-Rom curve. + * + * @param {Array} [points] - An array of 3D points defining the curve. + * @param {boolean} [closed=false] - Whether the curve is closed or not. + * @param {('centripetal'|'chordal'|'catmullrom')} [curveType='centripetal'] - The curve type. + * @param {number} [tension=0.5] - Tension of the curve. + */ + constructor( points = [], closed = false, curveType = 'centripetal', tension = 0.5 ) { -function WebGLGeometries( gl, attributes, info, bindingStates ) { + super(); - const geometries = {}; - const wireframeAttributes = new WeakMap(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isCatmullRomCurve3 = true; - function onGeometryDispose( event ) { + this.type = 'CatmullRomCurve3'; - const geometry = event.target; + /** + * An array of 3D points defining the curve. + * + * @type {Array} + */ + this.points = points; - if ( geometry.index !== null ) { + /** + * Whether the curve is closed or not. + * + * @type {boolean} + * @default false + */ + this.closed = closed; - attributes.remove( geometry.index ); + /** + * The curve type. + * + * @type {('centripetal'|'chordal'|'catmullrom')} + * @default 'centripetal' + */ + this.curveType = curveType; - } + /** + * Tension of the curve. + * + * @type {number} + * @default 0.5 + */ + this.tension = tension; - for ( const name in geometry.attributes ) { + } - attributes.remove( geometry.attributes[ name ] ); + /** + * Returns a point on the curve. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector3} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector3} The position on the curve. + */ + getPoint( t, optionalTarget = new Vector3() ) { - } + const point = optionalTarget; - for ( const name in geometry.morphAttributes ) { + const points = this.points; + const l = points.length; + + const p = ( l - ( this.closed ? 0 : 1 ) ) * t; + let intPoint = Math.floor( p ); + let weight = p - intPoint; - const array = geometry.morphAttributes[ name ]; + if ( this.closed ) { - for ( let i = 0, l = array.length; i < l; i ++ ) { + intPoint += intPoint > 0 ? 0 : ( Math.floor( Math.abs( intPoint ) / l ) + 1 ) * l; - attributes.remove( array[ i ] ); + } else if ( weight === 0 && intPoint === l - 1 ) { - } + intPoint = l - 2; + weight = 1; } - geometry.removeEventListener( 'dispose', onGeometryDispose ); + let p0, p3; // 4 points (p1 & p2 defined below) - delete geometries[ geometry.id ]; + if ( this.closed || intPoint > 0 ) { - const attribute = wireframeAttributes.get( geometry ); + p0 = points[ ( intPoint - 1 ) % l ]; - if ( attribute ) { + } else { - attributes.remove( attribute ); - wireframeAttributes.delete( geometry ); + // extrapolate first point + tmp.subVectors( points[ 0 ], points[ 1 ] ).add( points[ 0 ] ); + p0 = tmp; } - bindingStates.releaseStatesOfGeometry( geometry ); + const p1 = points[ intPoint % l ]; + const p2 = points[ ( intPoint + 1 ) % l ]; - if ( geometry.isInstancedBufferGeometry === true ) { + if ( this.closed || intPoint + 2 < l ) { - delete geometry._maxInstanceCount; + p3 = points[ ( intPoint + 2 ) % l ]; + + } else { + + // extrapolate last point + tmp.subVectors( points[ l - 1 ], points[ l - 2 ] ).add( points[ l - 1 ] ); + p3 = tmp; } - // + if ( this.curveType === 'centripetal' || this.curveType === 'chordal' ) { - info.memory.geometries --; + // init Centripetal / Chordal Catmull-Rom + const pow = this.curveType === 'chordal' ? 0.5 : 0.25; + let dt0 = Math.pow( p0.distanceToSquared( p1 ), pow ); + let dt1 = Math.pow( p1.distanceToSquared( p2 ), pow ); + let dt2 = Math.pow( p2.distanceToSquared( p3 ), pow ); - } + // safety check for repeated points + if ( dt1 < 1e-4 ) dt1 = 1.0; + if ( dt0 < 1e-4 ) dt0 = dt1; + if ( dt2 < 1e-4 ) dt2 = dt1; - function get( object, geometry ) { + px.initNonuniformCatmullRom( p0.x, p1.x, p2.x, p3.x, dt0, dt1, dt2 ); + py.initNonuniformCatmullRom( p0.y, p1.y, p2.y, p3.y, dt0, dt1, dt2 ); + pz.initNonuniformCatmullRom( p0.z, p1.z, p2.z, p3.z, dt0, dt1, dt2 ); - if ( geometries[ geometry.id ] === true ) return geometry; + } else if ( this.curveType === 'catmullrom' ) { - geometry.addEventListener( 'dispose', onGeometryDispose ); + px.initCatmullRom( p0.x, p1.x, p2.x, p3.x, this.tension ); + py.initCatmullRom( p0.y, p1.y, p2.y, p3.y, this.tension ); + pz.initCatmullRom( p0.z, p1.z, p2.z, p3.z, this.tension ); - geometries[ geometry.id ] = true; + } - info.memory.geometries ++; + point.set( + px.calc( weight ), + py.calc( weight ), + pz.calc( weight ) + ); - return geometry; + return point; } - function update( geometry ) { + copy( source ) { - const geometryAttributes = geometry.attributes; + super.copy( source ); - // Updating index buffer in VAO now. See WebGLBindingStates. + this.points = []; - for ( const name in geometryAttributes ) { + for ( let i = 0, l = source.points.length; i < l; i ++ ) { - attributes.update( geometryAttributes[ name ], gl.ARRAY_BUFFER ); + const point = source.points[ i ]; + + this.points.push( point.clone() ); } - // morph targets + this.closed = source.closed; + this.curveType = source.curveType; + this.tension = source.tension; - const morphAttributes = geometry.morphAttributes; + return this; - for ( const name in morphAttributes ) { + } + + toJSON() { - const array = morphAttributes[ name ]; + const data = super.toJSON(); - for ( let i = 0, l = array.length; i < l; i ++ ) { + data.points = []; - attributes.update( array[ i ], gl.ARRAY_BUFFER ); + for ( let i = 0, l = this.points.length; i < l; i ++ ) { - } + const point = this.points[ i ]; + data.points.push( point.toArray() ); } - } + data.closed = this.closed; + data.curveType = this.curveType; + data.tension = this.tension; - function updateWireframeAttribute( geometry ) { + return data; - const indices = []; + } - const geometryIndex = geometry.index; - const geometryPosition = geometry.attributes.position; - let version = 0; + fromJSON( json ) { - if ( geometryIndex !== null ) { + super.fromJSON( json ); - const array = geometryIndex.array; - version = geometryIndex.version; + this.points = []; - for ( let i = 0, l = array.length; i < l; i += 3 ) { + for ( let i = 0, l = json.points.length; i < l; i ++ ) { - const a = array[ i + 0 ]; - const b = array[ i + 1 ]; - const c = array[ i + 2 ]; + const point = json.points[ i ]; + this.points.push( new Vector3().fromArray( point ) ); - indices.push( a, b, b, c, c, a ); + } - } + this.closed = json.closed; + this.curveType = json.curveType; + this.tension = json.tension; - } else { + return this; - const array = geometryPosition.array; - version = geometryPosition.version; + } - for ( let i = 0, l = ( array.length / 3 ) - 1; i < l; i += 3 ) { +} - const a = i + 0; - const b = i + 1; - const c = i + 2; +// Bezier Curves formulas obtained from: https://en.wikipedia.org/wiki/B%C3%A9zier_curve - indices.push( a, b, b, c, c, a ); +/** + * Computes a point on a Catmull-Rom spline. + * + * @param {number} t - The interpolation factor. + * @param {number} p0 - The first control point. + * @param {number} p1 - The second control point. + * @param {number} p2 - The third control point. + * @param {number} p3 - The fourth control point. + * @return {number} The calculated point on a Catmull-Rom spline. + */ +function CatmullRom( t, p0, p1, p2, p3 ) { - } + const v0 = ( p2 - p0 ) * 0.5; + const v1 = ( p3 - p1 ) * 0.5; + const t2 = t * t; + const t3 = t * t2; + return ( 2 * p1 - 2 * p2 + v0 + v1 ) * t3 + ( -3 * p1 + 3 * p2 - 2 * v0 - v1 ) * t2 + v0 * t + p1; - } +} - const attribute = new ( arrayNeedsUint32( indices ) ? Uint32BufferAttribute : Uint16BufferAttribute )( indices, 1 ); - attribute.version = version; +// - // Updating index buffer in VAO now. See WebGLBindingStates +function QuadraticBezierP0( t, p ) { - // + const k = 1 - t; + return k * k * p; - const previousAttribute = wireframeAttributes.get( geometry ); +} - if ( previousAttribute ) attributes.remove( previousAttribute ); +function QuadraticBezierP1( t, p ) { - // + return 2 * ( 1 - t ) * t * p; - wireframeAttributes.set( geometry, attribute ); +} - } +function QuadraticBezierP2( t, p ) { - function getWireframeAttribute( geometry ) { + return t * t * p; - const currentAttribute = wireframeAttributes.get( geometry ); +} - if ( currentAttribute ) { +/** + * Computes a point on a Quadratic Bezier curve. + * + * @param {number} t - The interpolation factor. + * @param {number} p0 - The first control point. + * @param {number} p1 - The second control point. + * @param {number} p2 - The third control point. + * @return {number} The calculated point on a Quadratic Bezier curve. + */ +function QuadraticBezier( t, p0, p1, p2 ) { - const geometryIndex = geometry.index; + return QuadraticBezierP0( t, p0 ) + QuadraticBezierP1( t, p1 ) + + QuadraticBezierP2( t, p2 ); - if ( geometryIndex !== null ) { +} - // if the attribute is obsolete, create a new one +// - if ( currentAttribute.version < geometryIndex.version ) { +function CubicBezierP0( t, p ) { - updateWireframeAttribute( geometry ); + const k = 1 - t; + return k * k * k * p; - } +} - } +function CubicBezierP1( t, p ) { - } else { + const k = 1 - t; + return 3 * k * k * t * p; - updateWireframeAttribute( geometry ); +} - } +function CubicBezierP2( t, p ) { - return wireframeAttributes.get( geometry ); + return 3 * ( 1 - t ) * t * t * p; - } +} - return { +function CubicBezierP3( t, p ) { - get: get, - update: update, + return t * t * t * p; - getWireframeAttribute: getWireframeAttribute +} - }; +/** + * Computes a point on a Cubic Bezier curve. + * + * @param {number} t - The interpolation factor. + * @param {number} p0 - The first control point. + * @param {number} p1 - The second control point. + * @param {number} p2 - The third control point. + * @param {number} p3 - The fourth control point. + * @return {number} The calculated point on a Cubic Bezier curve. + */ +function CubicBezier( t, p0, p1, p2, p3 ) { + + return CubicBezierP0( t, p0 ) + CubicBezierP1( t, p1 ) + CubicBezierP2( t, p2 ) + + CubicBezierP3( t, p3 ); } -function WebGLIndexedBufferRenderer( gl, extensions, info, capabilities ) { +/** + * A curve representing a 2D Cubic Bezier curve. + * + * ```js + * const curve = new THREE.CubicBezierCurve( + * new THREE.Vector2( - 0, 0 ), + * new THREE.Vector2( - 5, 15 ), + * new THREE.Vector2( 20, 15 ), + * new THREE.Vector2( 10, 0 ) + * ); + * + * const points = curve.getPoints( 50 ); + * const geometry = new THREE.BufferGeometry().setFromPoints( points ); + * + * const material = new THREE.LineBasicMaterial( { color: 0xff0000 } ); + * + * // Create the final object to add to the scene + * const curveObject = new THREE.Line( geometry, material ); + * ``` + * + * @augments Curve + */ +class CubicBezierCurve extends Curve { - const isWebGL2 = capabilities.isWebGL2; + /** + * Constructs a new Cubic Bezier curve. + * + * @param {Vector2} [v0] - The start point. + * @param {Vector2} [v1] - The first control point. + * @param {Vector2} [v2] - The second control point. + * @param {Vector2} [v3] - The end point. + */ + constructor( v0 = new Vector2(), v1 = new Vector2(), v2 = new Vector2(), v3 = new Vector2() ) { - let mode; + super(); - function setMode( value ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isCubicBezierCurve = true; - mode = value; + this.type = 'CubicBezierCurve'; - } + /** + * The start point. + * + * @type {Vector2} + */ + this.v0 = v0; - let type, bytesPerElement; + /** + * The first control point. + * + * @type {Vector2} + */ + this.v1 = v1; - function setIndex( value ) { + /** + * The second control point. + * + * @type {Vector2} + */ + this.v2 = v2; - type = value.type; - bytesPerElement = value.bytesPerElement; + /** + * The end point. + * + * @type {Vector2} + */ + this.v3 = v3; } - function render( start, count ) { - - gl.drawElements( mode, count, type, start * bytesPerElement ); - - info.update( count, mode, 1 ); + /** + * Returns a point on the curve. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector2} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector2} The position on the curve. + */ + getPoint( t, optionalTarget = new Vector2() ) { - } + const point = optionalTarget; - function renderInstances( start, count, primcount ) { + const v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3; - if ( primcount === 0 ) return; + point.set( + CubicBezier( t, v0.x, v1.x, v2.x, v3.x ), + CubicBezier( t, v0.y, v1.y, v2.y, v3.y ) + ); - let extension, methodName; + return point; - if ( isWebGL2 ) { + } - extension = gl; - methodName = 'drawElementsInstanced'; + copy( source ) { - } else { + super.copy( source ); - extension = extensions.get( 'ANGLE_instanced_arrays' ); - methodName = 'drawElementsInstancedANGLE'; + this.v0.copy( source.v0 ); + this.v1.copy( source.v1 ); + this.v2.copy( source.v2 ); + this.v3.copy( source.v3 ); - if ( extension === null ) { + return this; - console.error( 'THREE.WebGLIndexedBufferRenderer: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.' ); - return; + } - } + toJSON() { - } + const data = super.toJSON(); - extension[ methodName ]( mode, count, type, start * bytesPerElement, primcount ); + data.v0 = this.v0.toArray(); + data.v1 = this.v1.toArray(); + data.v2 = this.v2.toArray(); + data.v3 = this.v3.toArray(); - info.update( count, mode, primcount ); + return data; } - // - - this.setMode = setMode; - this.setIndex = setIndex; - this.render = render; - this.renderInstances = renderInstances; + fromJSON( json ) { -} + super.fromJSON( json ); -function WebGLInfo( gl ) { + this.v0.fromArray( json.v0 ); + this.v1.fromArray( json.v1 ); + this.v2.fromArray( json.v2 ); + this.v3.fromArray( json.v3 ); - const memory = { - geometries: 0, - textures: 0 - }; + return this; - const render = { - frame: 0, - calls: 0, - triangles: 0, - points: 0, - lines: 0 - }; + } - function update( count, mode, instanceCount ) { +} - render.calls ++; +/** + * A curve representing a 3D Cubic Bezier curve. + * + * @augments Curve + */ +class CubicBezierCurve3 extends Curve { - switch ( mode ) { + /** + * Constructs a new Cubic Bezier curve. + * + * @param {Vector3} [v0] - The start point. + * @param {Vector3} [v1] - The first control point. + * @param {Vector3} [v2] - The second control point. + * @param {Vector3} [v3] - The end point. + */ + constructor( v0 = new Vector3(), v1 = new Vector3(), v2 = new Vector3(), v3 = new Vector3() ) { - case gl.TRIANGLES: - render.triangles += instanceCount * ( count / 3 ); - break; + super(); - case gl.LINES: - render.lines += instanceCount * ( count / 2 ); - break; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isCubicBezierCurve3 = true; - case gl.LINE_STRIP: - render.lines += instanceCount * ( count - 1 ); - break; + this.type = 'CubicBezierCurve3'; - case gl.LINE_LOOP: - render.lines += instanceCount * count; - break; + /** + * The start point. + * + * @type {Vector3} + */ + this.v0 = v0; - case gl.POINTS: - render.points += instanceCount * count; - break; + /** + * The first control point. + * + * @type {Vector3} + */ + this.v1 = v1; - default: - console.error( 'THREE.WebGLInfo: Unknown draw mode:', mode ); - break; + /** + * The second control point. + * + * @type {Vector3} + */ + this.v2 = v2; - } + /** + * The end point. + * + * @type {Vector3} + */ + this.v3 = v3; } - function reset() { - - render.calls = 0; - render.triangles = 0; - render.points = 0; - render.lines = 0; + /** + * Returns a point on the curve. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector3} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector3} The position on the curve. + */ + getPoint( t, optionalTarget = new Vector3() ) { - } + const point = optionalTarget; - return { - memory: memory, - render: render, - programs: null, - autoReset: true, - reset: reset, - update: update - }; + const v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3; -} + point.set( + CubicBezier( t, v0.x, v1.x, v2.x, v3.x ), + CubicBezier( t, v0.y, v1.y, v2.y, v3.y ), + CubicBezier( t, v0.z, v1.z, v2.z, v3.z ) + ); -function numericalSort( a, b ) { + return point; - return a[ 0 ] - b[ 0 ]; + } -} + copy( source ) { -function absNumericalSort( a, b ) { + super.copy( source ); - return Math.abs( b[ 1 ] ) - Math.abs( a[ 1 ] ); + this.v0.copy( source.v0 ); + this.v1.copy( source.v1 ); + this.v2.copy( source.v2 ); + this.v3.copy( source.v3 ); -} + return this; -function WebGLMorphtargets( gl, capabilities, textures ) { + } - const influencesList = {}; - const morphInfluences = new Float32Array( 8 ); - const morphTextures = new WeakMap(); - const morph = new Vector4(); + toJSON() { - const workInfluences = []; + const data = super.toJSON(); - for ( let i = 0; i < 8; i ++ ) { + data.v0 = this.v0.toArray(); + data.v1 = this.v1.toArray(); + data.v2 = this.v2.toArray(); + data.v3 = this.v3.toArray(); - workInfluences[ i ] = [ i, 0 ]; + return data; } - function update( object, geometry, program ) { + fromJSON( json ) { - const objectInfluences = object.morphTargetInfluences; + super.fromJSON( json ); + + this.v0.fromArray( json.v0 ); + this.v1.fromArray( json.v1 ); + this.v2.fromArray( json.v2 ); + this.v3.fromArray( json.v3 ); - if ( capabilities.isWebGL2 === true ) { + return this; - // instead of using attributes, the WebGL 2 code path encodes morph targets - // into an array of data textures. Each layer represents a single morph target. + } - const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color; - const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0; +} - let entry = morphTextures.get( geometry ); +/** + * A curve representing a 2D line segment. + * + * @augments Curve + */ +class LineCurve extends Curve { - if ( entry === undefined || entry.count !== morphTargetsCount ) { + /** + * Constructs a new line curve. + * + * @param {Vector2} [v1] - The start point. + * @param {Vector2} [v2] - The end point. + */ + constructor( v1 = new Vector2(), v2 = new Vector2() ) { - if ( entry !== undefined ) entry.texture.dispose(); + super(); - const hasMorphPosition = geometry.morphAttributes.position !== undefined; - const hasMorphNormals = geometry.morphAttributes.normal !== undefined; - const hasMorphColors = geometry.morphAttributes.color !== undefined; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLineCurve = true; - const morphTargets = geometry.morphAttributes.position || []; - const morphNormals = geometry.morphAttributes.normal || []; - const morphColors = geometry.morphAttributes.color || []; + this.type = 'LineCurve'; - let vertexDataCount = 0; + /** + * The start point. + * + * @type {Vector2} + */ + this.v1 = v1; - if ( hasMorphPosition === true ) vertexDataCount = 1; - if ( hasMorphNormals === true ) vertexDataCount = 2; - if ( hasMorphColors === true ) vertexDataCount = 3; + /** + * The end point. + * + * @type {Vector2} + */ + this.v2 = v2; - let width = geometry.attributes.position.count * vertexDataCount; - let height = 1; + } - if ( width > capabilities.maxTextureSize ) { + /** + * Returns a point on the line. + * + * @param {number} t - A interpolation factor representing a position on the line. Must be in the range `[0,1]`. + * @param {Vector2} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector2} The position on the line. + */ + getPoint( t, optionalTarget = new Vector2() ) { - height = Math.ceil( width / capabilities.maxTextureSize ); - width = capabilities.maxTextureSize; + const point = optionalTarget; - } + if ( t === 1 ) { - const buffer = new Float32Array( width * height * 4 * morphTargetsCount ); + point.copy( this.v2 ); - const texture = new DataArrayTexture( buffer, width, height, morphTargetsCount ); - texture.type = FloatType; - texture.needsUpdate = true; + } else { - // fill buffer + point.copy( this.v2 ).sub( this.v1 ); + point.multiplyScalar( t ).add( this.v1 ); - const vertexDataStride = vertexDataCount * 4; + } - for ( let i = 0; i < morphTargetsCount; i ++ ) { + return point; - const morphTarget = morphTargets[ i ]; - const morphNormal = morphNormals[ i ]; - const morphColor = morphColors[ i ]; + } - const offset = width * height * 4 * i; + // Line curve is linear, so we can overwrite default getPointAt + getPointAt( u, optionalTarget ) { - for ( let j = 0; j < morphTarget.count; j ++ ) { + return this.getPoint( u, optionalTarget ); - const stride = j * vertexDataStride; + } - if ( hasMorphPosition === true ) { + getTangent( t, optionalTarget = new Vector2() ) { - morph.fromBufferAttribute( morphTarget, j ); + return optionalTarget.subVectors( this.v2, this.v1 ).normalize(); - buffer[ offset + stride + 0 ] = morph.x; - buffer[ offset + stride + 1 ] = morph.y; - buffer[ offset + stride + 2 ] = morph.z; - buffer[ offset + stride + 3 ] = 0; + } - } + getTangentAt( u, optionalTarget ) { - if ( hasMorphNormals === true ) { + return this.getTangent( u, optionalTarget ); - morph.fromBufferAttribute( morphNormal, j ); + } - buffer[ offset + stride + 4 ] = morph.x; - buffer[ offset + stride + 5 ] = morph.y; - buffer[ offset + stride + 6 ] = morph.z; - buffer[ offset + stride + 7 ] = 0; + copy( source ) { - } + super.copy( source ); - if ( hasMorphColors === true ) { + this.v1.copy( source.v1 ); + this.v2.copy( source.v2 ); - morph.fromBufferAttribute( morphColor, j ); + return this; - buffer[ offset + stride + 8 ] = morph.x; - buffer[ offset + stride + 9 ] = morph.y; - buffer[ offset + stride + 10 ] = morph.z; - buffer[ offset + stride + 11 ] = ( morphColor.itemSize === 4 ) ? morph.w : 1; + } - } + toJSON() { - } + const data = super.toJSON(); - } + data.v1 = this.v1.toArray(); + data.v2 = this.v2.toArray(); - entry = { - count: morphTargetsCount, - texture: texture, - size: new Vector2( width, height ) - }; + return data; - morphTextures.set( geometry, entry ); + } - function disposeTexture() { + fromJSON( json ) { - texture.dispose(); + super.fromJSON( json ); - morphTextures.delete( geometry ); + this.v1.fromArray( json.v1 ); + this.v2.fromArray( json.v2 ); - geometry.removeEventListener( 'dispose', disposeTexture ); + return this; - } + } - geometry.addEventListener( 'dispose', disposeTexture ); +} - } +/** + * A curve representing a 3D line segment. + * + * @augments Curve + */ +class LineCurve3 extends Curve { - // + /** + * Constructs a new line curve. + * + * @param {Vector3} [v1] - The start point. + * @param {Vector3} [v2] - The end point. + */ + constructor( v1 = new Vector3(), v2 = new Vector3() ) { - let morphInfluencesSum = 0; + super(); - for ( let i = 0; i < objectInfluences.length; i ++ ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLineCurve3 = true; - morphInfluencesSum += objectInfluences[ i ]; + this.type = 'LineCurve3'; - } + /** + * The start point. + * + * @type {Vector3} + */ + this.v1 = v1; - const morphBaseInfluence = geometry.morphTargetsRelative ? 1 : 1 - morphInfluencesSum; + /** + * The end point. + * + * @type {Vector2} + */ + this.v2 = v2; - program.getUniforms().setValue( gl, 'morphTargetBaseInfluence', morphBaseInfluence ); - program.getUniforms().setValue( gl, 'morphTargetInfluences', objectInfluences ); + } - program.getUniforms().setValue( gl, 'morphTargetsTexture', entry.texture, textures ); - program.getUniforms().setValue( gl, 'morphTargetsTextureSize', entry.size ); + /** + * Returns a point on the line. + * + * @param {number} t - A interpolation factor representing a position on the line. Must be in the range `[0,1]`. + * @param {Vector3} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector3} The position on the line. + */ + getPoint( t, optionalTarget = new Vector3() ) { + const point = optionalTarget; - } else { + if ( t === 1 ) { - // When object doesn't have morph target influences defined, we treat it as a 0-length array - // This is important to make sure we set up morphTargetBaseInfluence / morphTargetInfluences + point.copy( this.v2 ); - const length = objectInfluences === undefined ? 0 : objectInfluences.length; + } else { - let influences = influencesList[ geometry.id ]; + point.copy( this.v2 ).sub( this.v1 ); + point.multiplyScalar( t ).add( this.v1 ); - if ( influences === undefined || influences.length !== length ) { + } - // initialise list + return point; - influences = []; + } - for ( let i = 0; i < length; i ++ ) { + // Line curve is linear, so we can overwrite default getPointAt + getPointAt( u, optionalTarget ) { - influences[ i ] = [ i, 0 ]; + return this.getPoint( u, optionalTarget ); - } + } - influencesList[ geometry.id ] = influences; + getTangent( t, optionalTarget = new Vector3() ) { - } + return optionalTarget.subVectors( this.v2, this.v1 ).normalize(); - // Collect influences + } - for ( let i = 0; i < length; i ++ ) { + getTangentAt( u, optionalTarget ) { - const influence = influences[ i ]; + return this.getTangent( u, optionalTarget ); - influence[ 0 ] = i; - influence[ 1 ] = objectInfluences[ i ]; + } - } + copy( source ) { - influences.sort( absNumericalSort ); + super.copy( source ); - for ( let i = 0; i < 8; i ++ ) { + this.v1.copy( source.v1 ); + this.v2.copy( source.v2 ); - if ( i < length && influences[ i ][ 1 ] ) { + return this; - workInfluences[ i ][ 0 ] = influences[ i ][ 0 ]; - workInfluences[ i ][ 1 ] = influences[ i ][ 1 ]; + } - } else { + toJSON() { - workInfluences[ i ][ 0 ] = Number.MAX_SAFE_INTEGER; - workInfluences[ i ][ 1 ] = 0; + const data = super.toJSON(); - } + data.v1 = this.v1.toArray(); + data.v2 = this.v2.toArray(); - } + return data; - workInfluences.sort( numericalSort ); + } - const morphTargets = geometry.morphAttributes.position; - const morphNormals = geometry.morphAttributes.normal; + fromJSON( json ) { - let morphInfluencesSum = 0; + super.fromJSON( json ); - for ( let i = 0; i < 8; i ++ ) { + this.v1.fromArray( json.v1 ); + this.v2.fromArray( json.v2 ); - const influence = workInfluences[ i ]; - const index = influence[ 0 ]; - const value = influence[ 1 ]; + return this; - if ( index !== Number.MAX_SAFE_INTEGER && value ) { + } - if ( morphTargets && geometry.getAttribute( 'morphTarget' + i ) !== morphTargets[ index ] ) { +} - geometry.setAttribute( 'morphTarget' + i, morphTargets[ index ] ); +/** + * A curve representing a 2D Quadratic Bezier curve. + * + * ```js + * const curve = new THREE.QuadraticBezierCurve( + * new THREE.Vector2( - 10, 0 ), + * new THREE.Vector2( 20, 15 ), + * new THREE.Vector2( 10, 0 ) + * ) + * + * const points = curve.getPoints( 50 ); + * const geometry = new THREE.BufferGeometry().setFromPoints( points ); + * + * const material = new THREE.LineBasicMaterial( { color: 0xff0000 } ); + * + * // Create the final object to add to the scene + * const curveObject = new THREE.Line( geometry, material ); + * ``` + * + * @augments Curve + */ +class QuadraticBezierCurve extends Curve { - } + /** + * Constructs a new Quadratic Bezier curve. + * + * @param {Vector2} [v0] - The start point. + * @param {Vector2} [v1] - The control point. + * @param {Vector2} [v2] - The end point. + */ + constructor( v0 = new Vector2(), v1 = new Vector2(), v2 = new Vector2() ) { - if ( morphNormals && geometry.getAttribute( 'morphNormal' + i ) !== morphNormals[ index ] ) { + super(); - geometry.setAttribute( 'morphNormal' + i, morphNormals[ index ] ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isQuadraticBezierCurve = true; - } + this.type = 'QuadraticBezierCurve'; - morphInfluences[ i ] = value; - morphInfluencesSum += value; + /** + * The start point. + * + * @type {Vector2} + */ + this.v0 = v0; - } else { + /** + * The control point. + * + * @type {Vector2} + */ + this.v1 = v1; - if ( morphTargets && geometry.hasAttribute( 'morphTarget' + i ) === true ) { + /** + * The end point. + * + * @type {Vector2} + */ + this.v2 = v2; - geometry.deleteAttribute( 'morphTarget' + i ); + } - } + /** + * Returns a point on the curve. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector2} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector2} The position on the curve. + */ + getPoint( t, optionalTarget = new Vector2() ) { - if ( morphNormals && geometry.hasAttribute( 'morphNormal' + i ) === true ) { + const point = optionalTarget; - geometry.deleteAttribute( 'morphNormal' + i ); + const v0 = this.v0, v1 = this.v1, v2 = this.v2; - } + point.set( + QuadraticBezier( t, v0.x, v1.x, v2.x ), + QuadraticBezier( t, v0.y, v1.y, v2.y ) + ); - morphInfluences[ i ] = 0; + return point; - } + } - } + copy( source ) { - // GLSL shader uses formula baseinfluence * base + sum(target * influence) - // This allows us to switch between absolute morphs and relative morphs without changing shader code - // When baseinfluence = 1 - sum(influence), the above is equivalent to sum((target - base) * influence) - const morphBaseInfluence = geometry.morphTargetsRelative ? 1 : 1 - morphInfluencesSum; + super.copy( source ); - program.getUniforms().setValue( gl, 'morphTargetBaseInfluence', morphBaseInfluence ); - program.getUniforms().setValue( gl, 'morphTargetInfluences', morphInfluences ); + this.v0.copy( source.v0 ); + this.v1.copy( source.v1 ); + this.v2.copy( source.v2 ); - } + return this; } - return { + toJSON() { - update: update + const data = super.toJSON(); - }; + data.v0 = this.v0.toArray(); + data.v1 = this.v1.toArray(); + data.v2 = this.v2.toArray(); -} + return data; -function WebGLObjects( gl, geometries, attributes, info ) { + } - let updateMap = new WeakMap(); + fromJSON( json ) { - function update( object ) { + super.fromJSON( json ); - const frame = info.render.frame; + this.v0.fromArray( json.v0 ); + this.v1.fromArray( json.v1 ); + this.v2.fromArray( json.v2 ); - const geometry = object.geometry; - const buffergeometry = geometries.get( object, geometry ); + return this; - // Update once per frame + } - if ( updateMap.get( buffergeometry ) !== frame ) { +} - geometries.update( buffergeometry ); +/** + * A curve representing a 3D Quadratic Bezier curve. + * + * @augments Curve + */ +class QuadraticBezierCurve3 extends Curve { - updateMap.set( buffergeometry, frame ); + /** + * Constructs a new Quadratic Bezier curve. + * + * @param {Vector3} [v0] - The start point. + * @param {Vector3} [v1] - The control point. + * @param {Vector3} [v2] - The end point. + */ + constructor( v0 = new Vector3(), v1 = new Vector3(), v2 = new Vector3() ) { - } + super(); - if ( object.isInstancedMesh ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isQuadraticBezierCurve3 = true; - if ( object.hasEventListener( 'dispose', onInstancedMeshDispose ) === false ) { + this.type = 'QuadraticBezierCurve3'; - object.addEventListener( 'dispose', onInstancedMeshDispose ); + /** + * The start point. + * + * @type {Vector3} + */ + this.v0 = v0; - } + /** + * The control point. + * + * @type {Vector3} + */ + this.v1 = v1; - attributes.update( object.instanceMatrix, gl.ARRAY_BUFFER ); + /** + * The end point. + * + * @type {Vector3} + */ + this.v2 = v2; - if ( object.instanceColor !== null ) { + } - attributes.update( object.instanceColor, gl.ARRAY_BUFFER ); + /** + * Returns a point on the curve. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector3} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector3} The position on the curve. + */ + getPoint( t, optionalTarget = new Vector3() ) { - } + const point = optionalTarget; - } + const v0 = this.v0, v1 = this.v1, v2 = this.v2; - return buffergeometry; + point.set( + QuadraticBezier( t, v0.x, v1.x, v2.x ), + QuadraticBezier( t, v0.y, v1.y, v2.y ), + QuadraticBezier( t, v0.z, v1.z, v2.z ) + ); + + return point; } - function dispose() { + copy( source ) { - updateMap = new WeakMap(); + super.copy( source ); - } + this.v0.copy( source.v0 ); + this.v1.copy( source.v1 ); + this.v2.copy( source.v2 ); - function onInstancedMeshDispose( event ) { + return this; - const instancedMesh = event.target; + } - instancedMesh.removeEventListener( 'dispose', onInstancedMeshDispose ); + toJSON() { - attributes.remove( instancedMesh.instanceMatrix ); + const data = super.toJSON(); - if ( instancedMesh.instanceColor !== null ) attributes.remove( instancedMesh.instanceColor ); + data.v0 = this.v0.toArray(); + data.v1 = this.v1.toArray(); + data.v2 = this.v2.toArray(); + + return data; } - return { + fromJSON( json ) { - update: update, - dispose: dispose + super.fromJSON( json ); - }; + this.v0.fromArray( json.v0 ); + this.v1.fromArray( json.v1 ); + this.v2.fromArray( json.v2 ); + + return this; + + } } /** - * Uniforms of a program. - * Those form a tree structure with a special top-level container for the root, - * which you get by calling 'new WebGLUniforms( gl, program )'. - * - * - * Properties of inner nodes including the top-level container: + * A curve representing a 2D spline curve. * - * .seq - array of nested uniforms - * .map - nested uniforms by name - * - * - * Methods of all nodes except the top-level container: - * - * .setValue( gl, value, [textures] ) - * - * uploads a uniform value(s) - * the 'textures' parameter is needed for sampler uniforms - * - * - * Static methods of the top-level container (textures factorizations): - * - * .upload( gl, seq, values, textures ) - * - * sets uniforms in 'seq' to 'values[id].value' - * - * .seqWithValue( seq, values ) : filteredSeq - * - * filters 'seq' entries with corresponding entry in values - * - * - * Methods of the top-level container (textures factorizations): + * ```js + * // Create a sine-like wave + * const curve = new THREE.SplineCurve( [ + * new THREE.Vector2( -10, 0 ), + * new THREE.Vector2( -5, 5 ), + * new THREE.Vector2( 0, 0 ), + * new THREE.Vector2( 5, -5 ), + * new THREE.Vector2( 10, 0 ) + * ] ); * - * .setValue( gl, name, value, textures ) - * - * sets uniform with name 'name' to 'value' + * const points = curve.getPoints( 50 ); + * const geometry = new THREE.BufferGeometry().setFromPoints( points ); * - * .setOptional( gl, obj, prop ) + * const material = new THREE.LineBasicMaterial( { color: 0xff0000 } ); * - * like .set for an optional property of the object + * // Create the final object to add to the scene + * const splineObject = new THREE.Line( geometry, material ); + * ``` * + * @augments Curve */ +class SplineCurve extends Curve { + /** + * Constructs a new 2D spline curve. + * + * @param {Array} [points] - An array of 2D points defining the curve. + */ + constructor( points = [] ) { -const emptyTexture = /*@__PURE__*/ new Texture(); -const emptyArrayTexture = /*@__PURE__*/ new DataArrayTexture(); -const empty3dTexture = /*@__PURE__*/ new Data3DTexture(); -const emptyCubeTexture = /*@__PURE__*/ new CubeTexture(); + super(); -// --- Utilities --- + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSplineCurve = true; -// Array Caches (provide typed arrays for temporary by size) + this.type = 'SplineCurve'; -const arrayCacheF32 = []; -const arrayCacheI32 = []; + /** + * An array of 2D points defining the curve. + * + * @type {Array} + */ + this.points = points; -// Float32Array caches used for uploading Matrix uniforms + } -const mat4array = new Float32Array( 16 ); -const mat3array = new Float32Array( 9 ); -const mat2array = new Float32Array( 4 ); + /** + * Returns a point on the curve. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector2} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector2} The position on the curve. + */ + getPoint( t, optionalTarget = new Vector2() ) { -// Flattening for arrays of vectors and matrices + const point = optionalTarget; -function flatten( array, nBlocks, blockSize ) { + const points = this.points; + const p = ( points.length - 1 ) * t; - const firstElem = array[ 0 ]; + const intPoint = Math.floor( p ); + const weight = p - intPoint; - if ( firstElem <= 0 || firstElem > 0 ) return array; - // unoptimized: ! isNaN( firstElem ) - // see http://jacksondunstan.com/articles/983 + const p0 = points[ intPoint === 0 ? intPoint : intPoint - 1 ]; + const p1 = points[ intPoint ]; + const p2 = points[ intPoint > points.length - 2 ? points.length - 1 : intPoint + 1 ]; + const p3 = points[ intPoint > points.length - 3 ? points.length - 1 : intPoint + 2 ]; - const n = nBlocks * blockSize; - let r = arrayCacheF32[ n ]; + point.set( + CatmullRom( weight, p0.x, p1.x, p2.x, p3.x ), + CatmullRom( weight, p0.y, p1.y, p2.y, p3.y ) + ); - if ( r === undefined ) { + return point; - r = new Float32Array( n ); - arrayCacheF32[ n ] = r; + } + + copy( source ) { + + super.copy( source ); + + this.points = []; + + for ( let i = 0, l = source.points.length; i < l; i ++ ) { + + const point = source.points[ i ]; + + this.points.push( point.clone() ); + + } + + return this; } - if ( nBlocks !== 0 ) { + toJSON() { - firstElem.toArray( r, 0 ); + const data = super.toJSON(); - for ( let i = 1, offset = 0; i !== nBlocks; ++ i ) { + data.points = []; - offset += blockSize; - array[ i ].toArray( r, offset ); + for ( let i = 0, l = this.points.length; i < l; i ++ ) { + + const point = this.points[ i ]; + data.points.push( point.toArray() ); } + return data; + } - return r; + fromJSON( json ) { -} + super.fromJSON( json ); -function arraysEqual( a, b ) { + this.points = []; - if ( a.length !== b.length ) return false; + for ( let i = 0, l = json.points.length; i < l; i ++ ) { - for ( let i = 0, l = a.length; i < l; i ++ ) { + const point = json.points[ i ]; + this.points.push( new Vector2().fromArray( point ) ); + + } - if ( a[ i ] !== b[ i ] ) return false; + return this; } - return true; - } -function copyArray( a, b ) { +var Curves = /*#__PURE__*/Object.freeze({ + __proto__: null, + ArcCurve: ArcCurve, + CatmullRomCurve3: CatmullRomCurve3, + CubicBezierCurve: CubicBezierCurve, + CubicBezierCurve3: CubicBezierCurve3, + EllipseCurve: EllipseCurve, + LineCurve: LineCurve, + LineCurve3: LineCurve3, + QuadraticBezierCurve: QuadraticBezierCurve, + QuadraticBezierCurve3: QuadraticBezierCurve3, + SplineCurve: SplineCurve +}); - for ( let i = 0, l = b.length; i < l; i ++ ) { +/** + * A base class extending {@link Curve}. `CurvePath` is simply an + * array of connected curves, but retains the API of a curve. + * + * @augments Curve + */ +class CurvePath extends Curve { - a[ i ] = b[ i ]; + /** + * Constructs a new curve path. + */ + constructor() { - } + super(); -} + this.type = 'CurvePath'; -// Texture unit allocation + /** + * An array of curves defining the + * path. + * + * @type {Array} + */ + this.curves = []; -function allocTexUnits( textures, n ) { + /** + * Whether the path should automatically be closed + * by a line curve. + * + * @type {boolean} + * @default false + */ + this.autoClose = false; - let r = arrayCacheI32[ n ]; + } - if ( r === undefined ) { + /** + * Adds a curve to this curve path. + * + * @param {Curve} curve - The curve to add. + */ + add( curve ) { - r = new Int32Array( n ); - arrayCacheI32[ n ] = r; + this.curves.push( curve ); } - for ( let i = 0; i !== n; ++ i ) { + /** + * Adds a line curve to close the path. + * + * @return {CurvePath} A reference to this curve path. + */ + closePath() { - r[ i ] = textures.allocateTextureUnit(); + // Add a line curve if start and end of lines are not connected + const startPoint = this.curves[ 0 ].getPoint( 0 ); + const endPoint = this.curves[ this.curves.length - 1 ].getPoint( 1 ); - } + if ( ! startPoint.equals( endPoint ) ) { - return r; + const lineType = ( startPoint.isVector2 === true ) ? 'LineCurve' : 'LineCurve3'; + this.curves.push( new Curves[ lineType ]( endPoint, startPoint ) ); -} + } -// --- Setters --- + return this; -// Note: Defining these methods externally, because they come in a bunch -// and this way their names minify. + } -// Single scalar + /** + * This method returns a vector in 2D or 3D space (depending on the curve definitions) + * for the given interpolation factor. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {(Vector2|Vector3)} [optionalTarget] - The optional target vector the result is written to. + * @return {?(Vector2|Vector3)} The position on the curve. It can be a 2D or 3D vector depending on the curve definition. + */ + getPoint( t, optionalTarget ) { -function setValueV1f( gl, v ) { + // To get accurate point with reference to + // entire path distance at time t, + // following has to be done: - const cache = this.cache; + // 1. Length of each sub path have to be known + // 2. Locate and identify type of curve + // 3. Get t for the curve + // 4. Return curve.getPointAt(t') - if ( cache[ 0 ] === v ) return; + const d = t * this.getLength(); + const curveLengths = this.getCurveLengths(); + let i = 0; - gl.uniform1f( this.addr, v ); + // To think about boundaries points. - cache[ 0 ] = v; + while ( i < curveLengths.length ) { -} + if ( curveLengths[ i ] >= d ) { -// Single float vector (from flat array or THREE.VectorN) + const diff = curveLengths[ i ] - d; + const curve = this.curves[ i ]; -function setValueV2f( gl, v ) { + const segmentLength = curve.getLength(); + const u = segmentLength === 0 ? 0 : 1 - diff / segmentLength; - const cache = this.cache; + return curve.getPointAt( u, optionalTarget ); - if ( v.x !== undefined ) { + } - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y ) { + i ++; - gl.uniform2f( this.addr, v.x, v.y ); + } - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; + return null; - } + // loop where sum != 0, sum > d , sum+1 } The curve lengths. + */ + getCurveLengths() { - gl.uniform3f( this.addr, v.x, v.y, v.z ); + // Compute lengths and cache them + // We cannot overwrite getLengths() because UtoT mapping uses it. + // We use cache values if curves and cache array are same length - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; - cache[ 2 ] = v.z; + if ( this.cacheLengths && this.cacheLengths.length === this.curves.length ) { + + return this.cacheLengths; } - } else if ( v.r !== undefined ) { + // Get length of sub-curve + // Push sums into cached array - if ( cache[ 0 ] !== v.r || cache[ 1 ] !== v.g || cache[ 2 ] !== v.b ) { + const lengths = []; + let sums = 0; - gl.uniform3f( this.addr, v.r, v.g, v.b ); + for ( let i = 0, l = this.curves.length; i < l; i ++ ) { - cache[ 0 ] = v.r; - cache[ 1 ] = v.g; - cache[ 2 ] = v.b; + sums += this.curves[ i ].getLength(); + lengths.push( sums ); } - } else { - - if ( arraysEqual( cache, v ) ) return; - - gl.uniform3fv( this.addr, v ); + this.cacheLengths = lengths; - copyArray( cache, v ); + return lengths; } -} + getSpacedPoints( divisions = 40 ) { -function setValueV4f( gl, v ) { + const points = []; - const cache = this.cache; + for ( let i = 0; i <= divisions; i ++ ) { - if ( v.x !== undefined ) { + points.push( this.getPoint( i / divisions ) ); - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z || cache[ 3 ] !== v.w ) { + } - gl.uniform4f( this.addr, v.x, v.y, v.z, v.w ); + if ( this.autoClose ) { - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; - cache[ 2 ] = v.z; - cache[ 3 ] = v.w; + points.push( points[ 0 ] ); } - } else { - - if ( arraysEqual( cache, v ) ) return; + return points; - gl.uniform4fv( this.addr, v ); + } - copyArray( cache, v ); + getPoints( divisions = 12 ) { - } + const points = []; + let last; -} + for ( let i = 0, curves = this.curves; i < curves.length; i ++ ) { -// Single matrix (from flat array or THREE.MatrixN) + const curve = curves[ i ]; + const resolution = curve.isEllipseCurve ? divisions * 2 + : ( curve.isLineCurve || curve.isLineCurve3 ) ? 1 + : curve.isSplineCurve ? divisions * curve.points.length + : divisions; -function setValueM2( gl, v ) { + const pts = curve.getPoints( resolution ); - const cache = this.cache; - const elements = v.elements; + for ( let j = 0; j < pts.length; j ++ ) { - if ( elements === undefined ) { + const point = pts[ j ]; - if ( arraysEqual( cache, v ) ) return; + if ( last && last.equals( point ) ) continue; // ensures no consecutive points are duplicates - gl.uniformMatrix2fv( this.addr, false, v ); + points.push( point ); + last = point; - copyArray( cache, v ); + } - } else { + } - if ( arraysEqual( cache, elements ) ) return; + if ( this.autoClose && points.length > 1 && ! points[ points.length - 1 ].equals( points[ 0 ] ) ) { - mat2array.set( elements ); + points.push( points[ 0 ] ); - gl.uniformMatrix2fv( this.addr, false, mat2array ); + } - copyArray( cache, elements ); + return points; } -} + copy( source ) { -function setValueM3( gl, v ) { + super.copy( source ); - const cache = this.cache; - const elements = v.elements; + this.curves = []; - if ( elements === undefined ) { + for ( let i = 0, l = source.curves.length; i < l; i ++ ) { - if ( arraysEqual( cache, v ) ) return; + const curve = source.curves[ i ]; - gl.uniformMatrix3fv( this.addr, false, v ); + this.curves.push( curve.clone() ); - copyArray( cache, v ); + } - } else { + this.autoClose = source.autoClose; - if ( arraysEqual( cache, elements ) ) return; + return this; - mat3array.set( elements ); + } - gl.uniformMatrix3fv( this.addr, false, mat3array ); + toJSON() { - copyArray( cache, elements ); + const data = super.toJSON(); - } + data.autoClose = this.autoClose; + data.curves = []; -} + for ( let i = 0, l = this.curves.length; i < l; i ++ ) { -function setValueM4( gl, v ) { + const curve = this.curves[ i ]; + data.curves.push( curve.toJSON() ); - const cache = this.cache; - const elements = v.elements; + } - if ( elements === undefined ) { + return data; - if ( arraysEqual( cache, v ) ) return; + } - gl.uniformMatrix4fv( this.addr, false, v ); + fromJSON( json ) { - copyArray( cache, v ); + super.fromJSON( json ); - } else { + this.autoClose = json.autoClose; + this.curves = []; - if ( arraysEqual( cache, elements ) ) return; + for ( let i = 0, l = json.curves.length; i < l; i ++ ) { - mat4array.set( elements ); + const curve = json.curves[ i ]; + this.curves.push( new Curves[ curve.type ]().fromJSON( curve ) ); - gl.uniformMatrix4fv( this.addr, false, mat4array ); + } - copyArray( cache, elements ); + return this; } } -// Single integer / boolean - -function setValueV1i( gl, v ) { +/** + * A 2D path representation. The class provides methods for creating paths + * and contours of 2D shapes similar to the 2D Canvas API. + * + * ```js + * const path = new THREE.Path(); + * + * path.lineTo( 0, 0.8 ); + * path.quadraticCurveTo( 0, 1, 0.2, 1 ); + * path.lineTo( 1, 1 ); + * + * const points = path.getPoints(); + * + * const geometry = new THREE.BufferGeometry().setFromPoints( points ); + * const material = new THREE.LineBasicMaterial( { color: 0xffffff } ); + * + * const line = new THREE.Line( geometry, material ); + * scene.add( line ); + * ``` + * + * @augments CurvePath + */ +class Path extends CurvePath { - const cache = this.cache; + /** + * Constructs a new path. + * + * @param {Array} [points] - An array of 2D points defining the path. + */ + constructor( points ) { - if ( cache[ 0 ] === v ) return; + super(); - gl.uniform1i( this.addr, v ); + this.type = 'Path'; - cache[ 0 ] = v; + /** + * The current offset of the path. Any new curve added will start here. + * + * @type {Vector2} + */ + this.currentPoint = new Vector2(); -} + if ( points ) { -// Single integer / boolean vector (from flat array or THREE.VectorN) + this.setFromPoints( points ); -function setValueV2i( gl, v ) { + } - const cache = this.cache; + } - if ( v.x !== undefined ) { + /** + * Creates a path from the given list of points. The points are added + * to the path as instances of {@link LineCurve}. + * + * @param {Array} points - An array of 2D points. + * @return {Path} A reference to this path. + */ + setFromPoints( points ) { - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y ) { + this.moveTo( points[ 0 ].x, points[ 0 ].y ); - gl.uniform2i( this.addr, v.x, v.y ); + for ( let i = 1, l = points.length; i < l; i ++ ) { - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; + this.lineTo( points[ i ].x, points[ i ].y ); } - } else { - - if ( arraysEqual( cache, v ) ) return; - - gl.uniform2iv( this.addr, v ); - - copyArray( cache, v ); + return this; } -} - -function setValueV3i( gl, v ) { - - const cache = this.cache; - - if ( v.x !== undefined ) { - - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z ) { + /** + * Moves {@link Path#currentPoint} to the given point. + * + * @param {number} x - The x coordinate. + * @param {number} y - The y coordinate. + * @return {Path} A reference to this path. + */ + moveTo( x, y ) { - gl.uniform3i( this.addr, v.x, v.y, v.z ); + this.currentPoint.set( x, y ); // TODO consider referencing vectors instead of copying? - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; - cache[ 2 ] = v.z; + return this; - } + } - } else { + /** + * Adds an instance of {@link LineCurve} to the path by connecting + * the current point with the given one. + * + * @param {number} x - The x coordinate of the end point. + * @param {number} y - The y coordinate of the end point. + * @return {Path} A reference to this path. + */ + lineTo( x, y ) { - if ( arraysEqual( cache, v ) ) return; + const curve = new LineCurve( this.currentPoint.clone(), new Vector2( x, y ) ); + this.curves.push( curve ); - gl.uniform3iv( this.addr, v ); + this.currentPoint.set( x, y ); - copyArray( cache, v ); + return this; } -} - -function setValueV4i( gl, v ) { + /** + * Adds an instance of {@link QuadraticBezierCurve} to the path by connecting + * the current point with the given one. + * + * @param {number} aCPx - The x coordinate of the control point. + * @param {number} aCPy - The y coordinate of the control point. + * @param {number} aX - The x coordinate of the end point. + * @param {number} aY - The y coordinate of the end point. + * @return {Path} A reference to this path. + */ + quadraticCurveTo( aCPx, aCPy, aX, aY ) { - const cache = this.cache; + const curve = new QuadraticBezierCurve( + this.currentPoint.clone(), + new Vector2( aCPx, aCPy ), + new Vector2( aX, aY ) + ); - if ( v.x !== undefined ) { + this.curves.push( curve ); - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z || cache[ 3 ] !== v.w ) { + this.currentPoint.set( aX, aY ); - gl.uniform4i( this.addr, v.x, v.y, v.z, v.w ); + return this; - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; - cache[ 2 ] = v.z; - cache[ 3 ] = v.w; + } - } + /** + * Adds an instance of {@link CubicBezierCurve} to the path by connecting + * the current point with the given one. + * + * @param {number} aCP1x - The x coordinate of the first control point. + * @param {number} aCP1y - The y coordinate of the first control point. + * @param {number} aCP2x - The x coordinate of the second control point. + * @param {number} aCP2y - The y coordinate of the second control point. + * @param {number} aX - The x coordinate of the end point. + * @param {number} aY - The y coordinate of the end point. + * @return {Path} A reference to this path. + */ + bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) { - } else { + const curve = new CubicBezierCurve( + this.currentPoint.clone(), + new Vector2( aCP1x, aCP1y ), + new Vector2( aCP2x, aCP2y ), + new Vector2( aX, aY ) + ); - if ( arraysEqual( cache, v ) ) return; + this.curves.push( curve ); - gl.uniform4iv( this.addr, v ); + this.currentPoint.set( aX, aY ); - copyArray( cache, v ); + return this; } -} - -// Single unsigned integer + /** + * Adds an instance of {@link SplineCurve} to the path by connecting + * the current point with the given list of points. + * + * @param {Array} pts - An array of points in 2D space. + * @return {Path} A reference to this path. + */ + splineThru( pts ) { -function setValueV1ui( gl, v ) { + const npts = [ this.currentPoint.clone() ].concat( pts ); - const cache = this.cache; + const curve = new SplineCurve( npts ); + this.curves.push( curve ); - if ( cache[ 0 ] === v ) return; + this.currentPoint.copy( pts[ pts.length - 1 ] ); - gl.uniform1ui( this.addr, v ); + return this; - cache[ 0 ] = v; + } -} + /** + * Adds an arc as an instance of {@link EllipseCurve} to the path, positioned relative + * to the current point. + * + * @param {number} [aX=0] - The x coordinate of the center of the arc offsetted from the previous curve. + * @param {number} [aY=0] - The y coordinate of the center of the arc offsetted from the previous curve. + * @param {number} [aRadius=1] - The radius of the arc. + * @param {number} [aStartAngle=0] - The start angle in radians. + * @param {number} [aEndAngle=Math.PI*2] - The end angle in radians. + * @param {boolean} [aClockwise=false] - Whether to sweep the arc clockwise or not. + * @return {Path} A reference to this path. + */ + arc( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) { -// Single unsigned integer vector (from flat array or THREE.VectorN) + const x0 = this.currentPoint.x; + const y0 = this.currentPoint.y; -function setValueV2ui( gl, v ) { + this.absarc( aX + x0, aY + y0, aRadius, + aStartAngle, aEndAngle, aClockwise ); - const cache = this.cache; + return this; - if ( v.x !== undefined ) { + } - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y ) { + /** + * Adds an absolutely positioned arc as an instance of {@link EllipseCurve} to the path. + * + * @param {number} [aX=0] - The x coordinate of the center of the arc. + * @param {number} [aY=0] - The y coordinate of the center of the arc. + * @param {number} [aRadius=1] - The radius of the arc. + * @param {number} [aStartAngle=0] - The start angle in radians. + * @param {number} [aEndAngle=Math.PI*2] - The end angle in radians. + * @param {boolean} [aClockwise=false] - Whether to sweep the arc clockwise or not. + * @return {Path} A reference to this path. + */ + absarc( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) { - gl.uniform2ui( this.addr, v.x, v.y ); + this.absellipse( aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise ); - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; + return this; - } + } - } else { + /** + * Adds an ellipse as an instance of {@link EllipseCurve} to the path, positioned relative + * to the current point + * + * @param {number} [aX=0] - The x coordinate of the center of the ellipse offsetted from the previous curve. + * @param {number} [aY=0] - The y coordinate of the center of the ellipse offsetted from the previous curve. + * @param {number} [xRadius=1] - The radius of the ellipse in the x axis. + * @param {number} [yRadius=1] - The radius of the ellipse in the y axis. + * @param {number} [aStartAngle=0] - The start angle in radians. + * @param {number} [aEndAngle=Math.PI*2] - The end angle in radians. + * @param {boolean} [aClockwise=false] - Whether to sweep the ellipse clockwise or not. + * @param {number} [aRotation=0] - The rotation angle of the ellipse in radians, counterclockwise from the positive X axis. + * @return {Path} A reference to this path. + */ + ellipse( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) { - if ( arraysEqual( cache, v ) ) return; + const x0 = this.currentPoint.x; + const y0 = this.currentPoint.y; - gl.uniform2uiv( this.addr, v ); + this.absellipse( aX + x0, aY + y0, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ); - copyArray( cache, v ); + return this; } -} + /** + * Adds an absolutely positioned ellipse as an instance of {@link EllipseCurve} to the path. + * + * @param {number} [aX=0] - The x coordinate of the absolute center of the ellipse. + * @param {number} [aY=0] - The y coordinate of the absolute center of the ellipse. + * @param {number} [xRadius=1] - The radius of the ellipse in the x axis. + * @param {number} [yRadius=1] - The radius of the ellipse in the y axis. + * @param {number} [aStartAngle=0] - The start angle in radians. + * @param {number} [aEndAngle=Math.PI*2] - The end angle in radians. + * @param {boolean} [aClockwise=false] - Whether to sweep the ellipse clockwise or not. + * @param {number} [aRotation=0] - The rotation angle of the ellipse in radians, counterclockwise from the positive X axis. + * @return {Path} A reference to this path. + */ + absellipse( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) { -function setValueV3ui( gl, v ) { + const curve = new EllipseCurve( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ); - const cache = this.cache; + if ( this.curves.length > 0 ) { - if ( v.x !== undefined ) { + // if a previous curve is present, attempt to join + const firstPoint = curve.getPoint( 0 ); - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z ) { + if ( ! firstPoint.equals( this.currentPoint ) ) { - gl.uniform3ui( this.addr, v.x, v.y, v.z ); + this.lineTo( firstPoint.x, firstPoint.y ); - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; - cache[ 2 ] = v.z; + } } - } else { - - if ( arraysEqual( cache, v ) ) return; + this.curves.push( curve ); - gl.uniform3uiv( this.addr, v ); + const lastPoint = curve.getPoint( 1 ); + this.currentPoint.copy( lastPoint ); - copyArray( cache, v ); + return this; } -} + copy( source ) { -function setValueV4ui( gl, v ) { + super.copy( source ); - const cache = this.cache; + this.currentPoint.copy( source.currentPoint ); - if ( v.x !== undefined ) { + return this; - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z || cache[ 3 ] !== v.w ) { + } - gl.uniform4ui( this.addr, v.x, v.y, v.z, v.w ); + toJSON() { - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; - cache[ 2 ] = v.z; - cache[ 3 ] = v.w; + const data = super.toJSON(); - } + data.currentPoint = this.currentPoint.toArray(); - } else { + return data; - if ( arraysEqual( cache, v ) ) return; + } - gl.uniform4uiv( this.addr, v ); + fromJSON( json ) { + + super.fromJSON( json ); + + this.currentPoint.fromArray( json.currentPoint ); - copyArray( cache, v ); + return this; } } +/** + * Defines an arbitrary 2d shape plane using paths with optional holes. It + * can be used with {@link ExtrudeGeometry}, {@link ShapeGeometry}, to get + * points, or to get triangulated faces. + * + * ```js + * const heartShape = new THREE.Shape(); + * + * heartShape.moveTo( 25, 25 ); + * heartShape.bezierCurveTo( 25, 25, 20, 0, 0, 0 ); + * heartShape.bezierCurveTo( - 30, 0, - 30, 35, - 30, 35 ); + * heartShape.bezierCurveTo( - 30, 55, - 10, 77, 25, 95 ); + * heartShape.bezierCurveTo( 60, 77, 80, 55, 80, 35 ); + * heartShape.bezierCurveTo( 80, 35, 80, 0, 50, 0 ); + * heartShape.bezierCurveTo( 35, 0, 25, 25, 25, 25 ); + * + * const extrudeSettings = { + * depth: 8, + * bevelEnabled: true, + * bevelSegments: 2, + * steps: 2, + * bevelSize: 1, + * bevelThickness: 1 + * }; + * + * const geometry = new THREE.ExtrudeGeometry( heartShape, extrudeSettings ); + * const mesh = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial() ); + * ``` + * + * @augments Path + */ +class Shape extends Path { -// Single texture (2D / Cube) + /** + * Constructs a new shape. + * + * @param {Array} [points] - An array of 2D points defining the shape. + */ + constructor( points ) { -function setValueT1( gl, v, textures ) { + super( points ); - const cache = this.cache; - const unit = textures.allocateTextureUnit(); + /** + * The UUID of the shape. + * + * @type {string} + * @readonly + */ + this.uuid = generateUUID(); - if ( cache[ 0 ] !== unit ) { + this.type = 'Shape'; - gl.uniform1i( this.addr, unit ); - cache[ 0 ] = unit; + /** + * Defines the holes in the shape. Hole definitions must use the + * opposite winding order (CW/CCW) than the outer shape. + * + * @type {Array} + * @readonly + */ + this.holes = []; } - textures.setTexture2D( v || emptyTexture, unit ); + /** + * Returns an array representing each contour of the holes + * as a list of 2D points. + * + * @param {number} divisions - The fineness of the result. + * @return {Array>} The holes as a series of 2D points. + */ + getPointsHoles( divisions ) { -} + const holesPts = []; -function setValueT3D1( gl, v, textures ) { + for ( let i = 0, l = this.holes.length; i < l; i ++ ) { - const cache = this.cache; - const unit = textures.allocateTextureUnit(); + holesPts[ i ] = this.holes[ i ].getPoints( divisions ); - if ( cache[ 0 ] !== unit ) { + } - gl.uniform1i( this.addr, unit ); - cache[ 0 ] = unit; + return holesPts; } - textures.setTexture3D( v || empty3dTexture, unit ); - -} + // get points of shape and holes (keypoints based on segments parameter) -function setValueT6( gl, v, textures ) { + /** + * Returns an object that holds contour data for the shape and its holes as + * arrays of 2D points. + * + * @param {number} divisions - The fineness of the result. + * @return {{shape:Array,holes:Array>}} An object with contour data. + */ + extractPoints( divisions ) { - const cache = this.cache; - const unit = textures.allocateTextureUnit(); + return { - if ( cache[ 0 ] !== unit ) { + shape: this.getPoints( divisions ), + holes: this.getPointsHoles( divisions ) - gl.uniform1i( this.addr, unit ); - cache[ 0 ] = unit; + }; } - textures.setTextureCube( v || emptyCubeTexture, unit ); + copy( source ) { -} + super.copy( source ); -function setValueT2DArray1( gl, v, textures ) { + this.holes = []; - const cache = this.cache; - const unit = textures.allocateTextureUnit(); + for ( let i = 0, l = source.holes.length; i < l; i ++ ) { - if ( cache[ 0 ] !== unit ) { + const hole = source.holes[ i ]; - gl.uniform1i( this.addr, unit ); - cache[ 0 ] = unit; + this.holes.push( hole.clone() ); + + } + + return this; } - textures.setTexture2DArray( v || emptyArrayTexture, unit ); + toJSON() { -} + const data = super.toJSON(); -// Helper to pick the right setter for the singular case + data.uuid = this.uuid; + data.holes = []; -function getSingularSetter( type ) { + for ( let i = 0, l = this.holes.length; i < l; i ++ ) { - switch ( type ) { + const hole = this.holes[ i ]; + data.holes.push( hole.toJSON() ); - case 0x1406: return setValueV1f; // FLOAT - case 0x8b50: return setValueV2f; // _VEC2 - case 0x8b51: return setValueV3f; // _VEC3 - case 0x8b52: return setValueV4f; // _VEC4 + } - case 0x8b5a: return setValueM2; // _MAT2 - case 0x8b5b: return setValueM3; // _MAT3 - case 0x8b5c: return setValueM4; // _MAT4 + return data; - case 0x1404: case 0x8b56: return setValueV1i; // INT, BOOL - case 0x8b53: case 0x8b57: return setValueV2i; // _VEC2 - case 0x8b54: case 0x8b58: return setValueV3i; // _VEC3 - case 0x8b55: case 0x8b59: return setValueV4i; // _VEC4 + } - case 0x1405: return setValueV1ui; // UINT - case 0x8dc6: return setValueV2ui; // _VEC2 - case 0x8dc7: return setValueV3ui; // _VEC3 - case 0x8dc8: return setValueV4ui; // _VEC4 + fromJSON( json ) { - case 0x8b5e: // SAMPLER_2D - case 0x8d66: // SAMPLER_EXTERNAL_OES - case 0x8dca: // INT_SAMPLER_2D - case 0x8dd2: // UNSIGNED_INT_SAMPLER_2D - case 0x8b62: // SAMPLER_2D_SHADOW - return setValueT1; + super.fromJSON( json ); - case 0x8b5f: // SAMPLER_3D - case 0x8dcb: // INT_SAMPLER_3D - case 0x8dd3: // UNSIGNED_INT_SAMPLER_3D - return setValueT3D1; + this.uuid = json.uuid; + this.holes = []; - case 0x8b60: // SAMPLER_CUBE - case 0x8dcc: // INT_SAMPLER_CUBE - case 0x8dd4: // UNSIGNED_INT_SAMPLER_CUBE - case 0x8dc5: // SAMPLER_CUBE_SHADOW - return setValueT6; + for ( let i = 0, l = json.holes.length; i < l; i ++ ) { - case 0x8dc1: // SAMPLER_2D_ARRAY - case 0x8dcf: // INT_SAMPLER_2D_ARRAY - case 0x8dd7: // UNSIGNED_INT_SAMPLER_2D_ARRAY - case 0x8dc4: // SAMPLER_2D_ARRAY_SHADOW - return setValueT2DArray1; + const hole = json.holes[ i ]; + this.holes.push( new Path().fromJSON( hole ) ); + + } + + return this; } } +/* eslint-disable */ +// copy of mapbox/earcut version 3.0.1 +// https://github.com/mapbox/earcut/tree/v3.0.1 -// Array of scalars +function earcut(data, holeIndices, dim = 2) { -function setValueV1fArray( gl, v ) { + const hasHoles = holeIndices && holeIndices.length; + const outerLen = hasHoles ? holeIndices[0] * dim : data.length; + let outerNode = linkedList(data, 0, outerLen, dim, true); + const triangles = []; - gl.uniform1fv( this.addr, v ); + if (!outerNode || outerNode.next === outerNode.prev) return triangles; -} + let minX, minY, invSize; -// Array of vectors (from flat array or array of THREE.VectorN) + if (hasHoles) outerNode = eliminateHoles(data, holeIndices, outerNode, dim); -function setValueV2fArray( gl, v ) { + // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox + if (data.length > 80 * dim) { + minX = Infinity; + minY = Infinity; + let maxX = -Infinity; + let maxY = -Infinity; - const data = flatten( v, this.size, 2 ); + for (let i = dim; i < outerLen; i += dim) { + const x = data[i]; + const y = data[i + 1]; + if (x < minX) minX = x; + if (y < minY) minY = y; + if (x > maxX) maxX = x; + if (y > maxY) maxY = y; + } - gl.uniform2fv( this.addr, data ); + // minX, minY and invSize are later used to transform coords into integers for z-order calculation + invSize = Math.max(maxX - minX, maxY - minY); + invSize = invSize !== 0 ? 32767 / invSize : 0; + } + + earcutLinked(outerNode, triangles, dim, minX, minY, invSize, 0); + return triangles; } -function setValueV3fArray( gl, v ) { +// create a circular doubly linked list from polygon points in the specified winding order +function linkedList(data, start, end, dim, clockwise) { + let last; - const data = flatten( v, this.size, 3 ); + if (clockwise === (signedArea(data, start, end, dim) > 0)) { + for (let i = start; i < end; i += dim) last = insertNode(i / dim | 0, data[i], data[i + 1], last); + } else { + for (let i = end - dim; i >= start; i -= dim) last = insertNode(i / dim | 0, data[i], data[i + 1], last); + } - gl.uniform3fv( this.addr, data ); + if (last && equals(last, last.next)) { + removeNode(last); + last = last.next; + } + return last; } -function setValueV4fArray( gl, v ) { - - const data = flatten( v, this.size, 4 ); - - gl.uniform4fv( this.addr, data ); - +// eliminate colinear or duplicate points +function filterPoints(start, end) { + if (!start) return start; + if (!end) end = start; + + let p = start, + again; + do { + again = false; + + if (!p.steiner && (equals(p, p.next) || area(p.prev, p, p.next) === 0)) { + removeNode(p); + p = end = p.prev; + if (p === p.next) break; + again = true; + + } else { + p = p.next; + } + } while (again || p !== end); + + return end; } -// Array of matrices (from flat array or array of THREE.MatrixN) +// main ear slicing loop which triangulates a polygon (given as a linked list) +function earcutLinked(ear, triangles, dim, minX, minY, invSize, pass) { + if (!ear) return; -function setValueM2Array( gl, v ) { + // interlink polygon nodes in z-order + if (!pass && invSize) indexCurve(ear, minX, minY, invSize); - const data = flatten( v, this.size, 4 ); + let stop = ear; - gl.uniformMatrix2fv( this.addr, false, data ); + // iterate through ears, slicing them one by one + while (ear.prev !== ear.next) { + const prev = ear.prev; + const next = ear.next; -} + if (invSize ? isEarHashed(ear, minX, minY, invSize) : isEar(ear)) { + triangles.push(prev.i, ear.i, next.i); // cut off the triangle -function setValueM3Array( gl, v ) { + removeNode(ear); - const data = flatten( v, this.size, 9 ); + // skipping the next vertex leads to less sliver triangles + ear = next.next; + stop = next.next; - gl.uniformMatrix3fv( this.addr, false, data ); + continue; + } -} + ear = next; -function setValueM4Array( gl, v ) { + // if we looped through the whole remaining polygon and can't find any more ears + if (ear === stop) { + // try filtering points and slicing again + if (!pass) { + earcutLinked(filterPoints(ear), triangles, dim, minX, minY, invSize, 1); - const data = flatten( v, this.size, 16 ); + // if this didn't work, try curing all small self-intersections locally + } else if (pass === 1) { + ear = cureLocalIntersections(filterPoints(ear), triangles); + earcutLinked(ear, triangles, dim, minX, minY, invSize, 2); - gl.uniformMatrix4fv( this.addr, false, data ); + // as a last resort, try splitting the remaining polygon into two + } else if (pass === 2) { + splitEarcut(ear, triangles, dim, minX, minY, invSize); + } + break; + } + } } -// Array of integer / boolean +// check whether a polygon node forms a valid ear with adjacent nodes +function isEar(ear) { + const a = ear.prev, + b = ear, + c = ear.next; + + if (area(a, b, c) >= 0) return false; // reflex, can't be an ear + + // now make sure we don't have other points inside the potential ear + const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y; + + // triangle bbox + const x0 = Math.min(ax, bx, cx), + y0 = Math.min(ay, by, cy), + x1 = Math.max(ax, bx, cx), + y1 = Math.max(ay, by, cy); + + let p = c.next; + while (p !== a) { + if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && + pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, p.x, p.y) && + area(p.prev, p, p.next) >= 0) return false; + p = p.next; + } + + return true; +} -function setValueV1iArray( gl, v ) { +function isEarHashed(ear, minX, minY, invSize) { + const a = ear.prev, + b = ear, + c = ear.next; + + if (area(a, b, c) >= 0) return false; // reflex, can't be an ear + + const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y; + + // triangle bbox + const x0 = Math.min(ax, bx, cx), + y0 = Math.min(ay, by, cy), + x1 = Math.max(ax, bx, cx), + y1 = Math.max(ay, by, cy); + + // z-order range for the current triangle bbox; + const minZ = zOrder(x0, y0, minX, minY, invSize), + maxZ = zOrder(x1, y1, minX, minY, invSize); + + let p = ear.prevZ, + n = ear.nextZ; + + // look for points inside the triangle in both directions + while (p && p.z >= minZ && n && n.z <= maxZ) { + if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c && + pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false; + p = p.prevZ; + + if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c && + pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false; + n = n.nextZ; + } + + // look for remaining points in decreasing z-order + while (p && p.z >= minZ) { + if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c && + pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false; + p = p.prevZ; + } + + // look for remaining points in increasing z-order + while (n && n.z <= maxZ) { + if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c && + pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false; + n = n.nextZ; + } + + return true; +} - gl.uniform1iv( this.addr, v ); +// go through all polygon nodes and cure small local self-intersections +function cureLocalIntersections(start, triangles) { + let p = start; + do { + const a = p.prev, + b = p.next.next; -} + if (!equals(a, b) && intersects(a, p, p.next, b) && locallyInside(a, b) && locallyInside(b, a)) { -// Array of integer / boolean vectors (from flat array) + triangles.push(a.i, p.i, b.i); -function setValueV2iArray( gl, v ) { + // remove two nodes involved + removeNode(p); + removeNode(p.next); - gl.uniform2iv( this.addr, v ); + p = start = b; + } + p = p.next; + } while (p !== start); + return filterPoints(p); } -function setValueV3iArray( gl, v ) { +// try splitting polygon into two and triangulate them independently +function splitEarcut(start, triangles, dim, minX, minY, invSize) { + // look for a valid diagonal that divides the polygon into two + let a = start; + do { + let b = a.next.next; + while (b !== a.prev) { + if (a.i !== b.i && isValidDiagonal(a, b)) { + // split the polygon in two by the diagonal + let c = splitPolygon(a, b); + + // filter colinear points around the cuts + a = filterPoints(a, a.next); + c = filterPoints(c, c.next); + + // run earcut on each half + earcutLinked(a, triangles, dim, minX, minY, invSize, 0); + earcutLinked(c, triangles, dim, minX, minY, invSize, 0); + return; + } + b = b.next; + } + a = a.next; + } while (a !== start); +} - gl.uniform3iv( this.addr, v ); +// link every hole into the outer loop, producing a single-ring polygon without holes +function eliminateHoles(data, holeIndices, outerNode, dim) { + const queue = []; -} + for (let i = 0, len = holeIndices.length; i < len; i++) { + const start = holeIndices[i] * dim; + const end = i < len - 1 ? holeIndices[i + 1] * dim : data.length; + const list = linkedList(data, start, end, dim, false); + if (list === list.next) list.steiner = true; + queue.push(getLeftmost(list)); + } -function setValueV4iArray( gl, v ) { + queue.sort(compareXYSlope); - gl.uniform4iv( this.addr, v ); + // process holes from left to right + for (let i = 0; i < queue.length; i++) { + outerNode = eliminateHole(queue[i], outerNode); + } + return outerNode; } -// Array of unsigned integer +function compareXYSlope(a, b) { + let result = a.x - b.x; + // when the left-most point of 2 holes meet at a vertex, sort the holes counterclockwise so that when we find + // the bridge to the outer shell is always the point that they meet at. + if (result === 0) { + result = a.y - b.y; + if (result === 0) { + const aSlope = (a.next.y - a.y) / (a.next.x - a.x); + const bSlope = (b.next.y - b.y) / (b.next.x - b.x); + result = aSlope - bSlope; + } + } + return result; +} -function setValueV1uiArray( gl, v ) { +// find a bridge between vertices that connects hole with an outer ring and and link it +function eliminateHole(hole, outerNode) { + const bridge = findHoleBridge(hole, outerNode); + if (!bridge) { + return outerNode; + } - gl.uniform1uiv( this.addr, v ); + const bridgeReverse = splitPolygon(bridge, hole); + // filter collinear points around the cuts + filterPoints(bridgeReverse, bridgeReverse.next); + return filterPoints(bridge, bridge.next); } -// Array of unsigned integer vectors (from flat array) +// David Eberly's algorithm for finding a bridge between hole and outer polygon +function findHoleBridge(hole, outerNode) { + let p = outerNode; + const hx = hole.x; + const hy = hole.y; + let qx = -Infinity; + let m; + + // find a segment intersected by a ray from the hole's leftmost point to the left; + // segment's endpoint with lesser x will be potential connection point + // unless they intersect at a vertex, then choose the vertex + if (equals(hole, p)) return p; + do { + if (equals(hole, p.next)) return p.next; + else if (hy <= p.y && hy >= p.next.y && p.next.y !== p.y) { + const x = p.x + (hy - p.y) * (p.next.x - p.x) / (p.next.y - p.y); + if (x <= hx && x > qx) { + qx = x; + m = p.x < p.next.x ? p : p.next; + if (x === hx) return m; // hole touches outer segment; pick leftmost endpoint + } + } + p = p.next; + } while (p !== outerNode); + + if (!m) return null; + + // look for points inside the triangle of hole point, segment intersection and endpoint; + // if there are no points found, we have a valid connection; + // otherwise choose the point of the minimum angle with the ray as connection point + + const stop = m; + const mx = m.x; + const my = m.y; + let tanMin = Infinity; + + p = m; + + do { + if (hx >= p.x && p.x >= mx && hx !== p.x && + pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y)) { + + const tan = Math.abs(hy - p.y) / (hx - p.x); // tangential + + if (locallyInside(p, hole) && + (tan < tanMin || (tan === tanMin && (p.x > m.x || (p.x === m.x && sectorContainsSector(m, p)))))) { + m = p; + tanMin = tan; + } + } + + p = p.next; + } while (p !== stop); + + return m; +} -function setValueV2uiArray( gl, v ) { +// whether sector in vertex m contains sector in vertex p in the same coordinates +function sectorContainsSector(m, p) { + return area(m.prev, m, p.prev) < 0 && area(p.next, m, m.next) < 0; +} - gl.uniform2uiv( this.addr, v ); +// interlink polygon nodes in z-order +function indexCurve(start, minX, minY, invSize) { + let p = start; + do { + if (p.z === 0) p.z = zOrder(p.x, p.y, minX, minY, invSize); + p.prevZ = p.prev; + p.nextZ = p.next; + p = p.next; + } while (p !== start); + + p.prevZ.nextZ = null; + p.prevZ = null; + + sortLinked(p); +} +// Simon Tatham's linked list merge sort algorithm +// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html +function sortLinked(list) { + let numMerges; + let inSize = 1; + + do { + let p = list; + let e; + list = null; + let tail = null; + numMerges = 0; + + while (p) { + numMerges++; + let q = p; + let pSize = 0; + for (let i = 0; i < inSize; i++) { + pSize++; + q = q.nextZ; + if (!q) break; + } + let qSize = inSize; + + while (pSize > 0 || (qSize > 0 && q)) { + + if (pSize !== 0 && (qSize === 0 || !q || p.z <= q.z)) { + e = p; + p = p.nextZ; + pSize--; + } else { + e = q; + q = q.nextZ; + qSize--; + } + + if (tail) tail.nextZ = e; + else list = e; + + e.prevZ = tail; + tail = e; + } + + p = q; + } + + tail.nextZ = null; + inSize *= 2; + + } while (numMerges > 1); + + return list; } -function setValueV3uiArray( gl, v ) { +// z-order of a point given coords and inverse of the longer side of data bbox +function zOrder(x, y, minX, minY, invSize) { + // coords are transformed into non-negative 15-bit integer range + x = (x - minX) * invSize | 0; + y = (y - minY) * invSize | 0; + + x = (x | (x << 8)) & 0x00FF00FF; + x = (x | (x << 4)) & 0x0F0F0F0F; + x = (x | (x << 2)) & 0x33333333; + x = (x | (x << 1)) & 0x55555555; + + y = (y | (y << 8)) & 0x00FF00FF; + y = (y | (y << 4)) & 0x0F0F0F0F; + y = (y | (y << 2)) & 0x33333333; + y = (y | (y << 1)) & 0x55555555; + + return x | (y << 1); +} - gl.uniform3uiv( this.addr, v ); +// find the leftmost node of a polygon ring +function getLeftmost(start) { + let p = start, + leftmost = start; + do { + if (p.x < leftmost.x || (p.x === leftmost.x && p.y < leftmost.y)) leftmost = p; + p = p.next; + } while (p !== start); + + return leftmost; +} +// check if a point lies within a convex triangle +function pointInTriangle(ax, ay, bx, by, cx, cy, px, py) { + return (cx - px) * (ay - py) >= (ax - px) * (cy - py) && + (ax - px) * (by - py) >= (bx - px) * (ay - py) && + (bx - px) * (cy - py) >= (cx - px) * (by - py); } -function setValueV4uiArray( gl, v ) { +// check if a point lies within a convex triangle but false if its equal to the first point of the triangle +function pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, px, py) { + return !(ax === px && ay === py) && pointInTriangle(ax, ay, bx, by, cx, cy, px, py); +} - gl.uniform4uiv( this.addr, v ); +// check if a diagonal between two polygon nodes is valid (lies in polygon interior) +function isValidDiagonal(a, b) { + return a.next.i !== b.i && a.prev.i !== b.i && !intersectsPolygon(a, b) && // dones't intersect other edges + (locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b) && // locally visible + (area(a.prev, a, b.prev) || area(a, b.prev, b)) || // does not create opposite-facing sectors + equals(a, b) && area(a.prev, a, a.next) > 0 && area(b.prev, b, b.next) > 0); // special zero-length case +} +// signed area of a triangle +function area(p, q, r) { + return (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y); } +// check if two points are equal +function equals(p1, p2) { + return p1.x === p2.x && p1.y === p2.y; +} -// Array of textures (2D / 3D / Cube / 2DArray) +// check if two segments intersect +function intersects(p1, q1, p2, q2) { + const o1 = sign(area(p1, q1, p2)); + const o2 = sign(area(p1, q1, q2)); + const o3 = sign(area(p2, q2, p1)); + const o4 = sign(area(p2, q2, q1)); -function setValueT1Array( gl, v, textures ) { + if (o1 !== o2 && o3 !== o4) return true; // general case - const cache = this.cache; + if (o1 === 0 && onSegment(p1, p2, q1)) return true; // p1, q1 and p2 are collinear and p2 lies on p1q1 + if (o2 === 0 && onSegment(p1, q2, q1)) return true; // p1, q1 and q2 are collinear and q2 lies on p1q1 + if (o3 === 0 && onSegment(p2, p1, q2)) return true; // p2, q2 and p1 are collinear and p1 lies on p2q2 + if (o4 === 0 && onSegment(p2, q1, q2)) return true; // p2, q2 and q1 are collinear and q1 lies on p2q2 - const n = v.length; + return false; +} - const units = allocTexUnits( textures, n ); +// for collinear points p, q, r, check if point q lies on segment pr +function onSegment(p, q, r) { + return q.x <= Math.max(p.x, r.x) && q.x >= Math.min(p.x, r.x) && q.y <= Math.max(p.y, r.y) && q.y >= Math.min(p.y, r.y); +} - if ( ! arraysEqual( cache, units ) ) { +function sign(num) { + return num > 0 ? 1 : num < 0 ? -1 : 0; +} - gl.uniform1iv( this.addr, units ); +// check if a polygon diagonal intersects any polygon segments +function intersectsPolygon(a, b) { + let p = a; + do { + if (p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i && + intersects(p, p.next, a, b)) return true; + p = p.next; + } while (p !== a); + + return false; +} - copyArray( cache, units ); +// check if a polygon diagonal is locally inside the polygon +function locallyInside(a, b) { + return area(a.prev, a, a.next) < 0 ? + area(a, b, a.next) >= 0 && area(a, a.prev, b) >= 0 : + area(a, b, a.prev) < 0 || area(a, a.next, b) < 0; +} - } +// check if the middle point of a polygon diagonal is inside the polygon +function middleInside(a, b) { + let p = a; + let inside = false; + const px = (a.x + b.x) / 2; + const py = (a.y + b.y) / 2; + do { + if (((p.y > py) !== (p.next.y > py)) && p.next.y !== p.y && + (px < (p.next.x - p.x) * (py - p.y) / (p.next.y - p.y) + p.x)) + inside = !inside; + p = p.next; + } while (p !== a); + + return inside; +} - for ( let i = 0; i !== n; ++ i ) { +// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two; +// if one belongs to the outer ring and another to a hole, it merges it into a single ring +function splitPolygon(a, b) { + const a2 = createNode(a.i, a.x, a.y), + b2 = createNode(b.i, b.x, b.y), + an = a.next, + bp = b.prev; - textures.setTexture2D( v[ i ] || emptyTexture, units[ i ] ); + a.next = b; + b.prev = a; - } + a2.next = an; + an.prev = a2; -} + b2.next = a2; + a2.prev = b2; -function setValueT3DArray( gl, v, textures ) { + bp.next = b2; + b2.prev = bp; - const cache = this.cache; + return b2; +} - const n = v.length; +// create a node and optionally link it with previous one (in a circular doubly linked list) +function insertNode(i, x, y, last) { + const p = createNode(i, x, y); + + if (!last) { + p.prev = p; + p.next = p; + + } else { + p.next = last.next; + p.prev = last; + last.next.prev = p; + last.next = p; + } + return p; +} - const units = allocTexUnits( textures, n ); +function removeNode(p) { + p.next.prev = p.prev; + p.prev.next = p.next; - if ( ! arraysEqual( cache, units ) ) { + if (p.prevZ) p.prevZ.nextZ = p.nextZ; + if (p.nextZ) p.nextZ.prevZ = p.prevZ; +} - gl.uniform1iv( this.addr, units ); +function createNode(i, x, y) { + return { + i, // vertex index in coordinates array + x, y, // vertex coordinates + prev: null, // previous and next vertex nodes in a polygon ring + next: null, + z: 0, // z-order curve value + prevZ: null, // previous and next nodes in z-order + nextZ: null, + steiner: false // indicates whether this is a steiner point + }; +} - copyArray( cache, units ); +function signedArea(data, start, end, dim) { + let sum = 0; + for (let i = start, j = end - dim; i < end; i += dim) { + sum += (data[j] - data[i]) * (data[i + 1] + data[j + 1]); + j = i; + } + return sum; +} - } +class Earcut { - for ( let i = 0; i !== n; ++ i ) { + /** + * Triangulates the given shape definition by returning an array of triangles. + * + * @param {Array} data - An array with 2D points. + * @param {Array} holeIndices - An array with indices defining holes. + * @param {number} [dim=2] - The number of coordinates per vertex in the input array. + * @return {Array} An array representing the triangulated faces. Each face is defined by three consecutive numbers + * representing vertex indices. + */ + static triangulate( data, holeIndices, dim = 2 ) { - textures.setTexture3D( v[ i ] || empty3dTexture, units[ i ] ); + return earcut( data, holeIndices, dim ); } } -function setValueT6Array( gl, v, textures ) { +/** + * A class containing utility functions for shapes. + * + * @hideconstructor + */ +class ShapeUtils { - const cache = this.cache; + /** + * Calculate area of a ( 2D ) contour polygon. + * + * @param {Array} contour - An array of 2D points. + * @return {number} The area. + */ + static area( contour ) { - const n = v.length; + const n = contour.length; + let a = 0.0; - const units = allocTexUnits( textures, n ); + for ( let p = n - 1, q = 0; q < n; p = q ++ ) { - if ( ! arraysEqual( cache, units ) ) { + a += contour[ p ].x * contour[ q ].y - contour[ q ].x * contour[ p ].y; - gl.uniform1iv( this.addr, units ); + } - copyArray( cache, units ); + return a * 0.5; } - for ( let i = 0; i !== n; ++ i ) { + /** + * Returns `true` if the given contour uses a clockwise winding order. + * + * @param {Array} pts - An array of 2D points defining a polygon. + * @return {boolean} Whether the given contour uses a clockwise winding order or not. + */ + static isClockWise( pts ) { - textures.setTextureCube( v[ i ] || emptyCubeTexture, units[ i ] ); + return ShapeUtils.area( pts ) < 0; } -} + /** + * Triangulates the given shape definition. + * + * @param {Array} contour - An array of 2D points defining the contour. + * @param {Array>} holes - An array that holds arrays of 2D points defining the holes. + * @return {Array>} An array that holds for each face definition an array with three indices. + */ + static triangulateShape( contour, holes ) { -function setValueT2DArrayArray( gl, v, textures ) { + const vertices = []; // flat array of vertices like [ x0,y0, x1,y1, x2,y2, ... ] + const holeIndices = []; // array of hole indices + const faces = []; // final array of vertex indices like [ [ a,b,d ], [ b,c,d ] ] - const cache = this.cache; + removeDupEndPts( contour ); + addContour( vertices, contour ); - const n = v.length; + // - const units = allocTexUnits( textures, n ); + let holeIndex = contour.length; - if ( ! arraysEqual( cache, units ) ) { + holes.forEach( removeDupEndPts ); - gl.uniform1iv( this.addr, units ); + for ( let i = 0; i < holes.length; i ++ ) { - copyArray( cache, units ); + holeIndices.push( holeIndex ); + holeIndex += holes[ i ].length; + addContour( vertices, holes[ i ] ); - } + } - for ( let i = 0; i !== n; ++ i ) { + // - textures.setTexture2DArray( v[ i ] || emptyArrayTexture, units[ i ] ); + const triangles = Earcut.triangulate( vertices, holeIndices ); - } + // -} + for ( let i = 0; i < triangles.length; i += 3 ) { + faces.push( triangles.slice( i, i + 3 ) ); -// Helper to pick the right setter for a pure (bottom-level) array + } -function getPureArraySetter( type ) { + return faces; - switch ( type ) { + } - case 0x1406: return setValueV1fArray; // FLOAT - case 0x8b50: return setValueV2fArray; // _VEC2 - case 0x8b51: return setValueV3fArray; // _VEC3 - case 0x8b52: return setValueV4fArray; // _VEC4 +} - case 0x8b5a: return setValueM2Array; // _MAT2 - case 0x8b5b: return setValueM3Array; // _MAT3 - case 0x8b5c: return setValueM4Array; // _MAT4 +function removeDupEndPts( points ) { - case 0x1404: case 0x8b56: return setValueV1iArray; // INT, BOOL - case 0x8b53: case 0x8b57: return setValueV2iArray; // _VEC2 - case 0x8b54: case 0x8b58: return setValueV3iArray; // _VEC3 - case 0x8b55: case 0x8b59: return setValueV4iArray; // _VEC4 + const l = points.length; - case 0x1405: return setValueV1uiArray; // UINT - case 0x8dc6: return setValueV2uiArray; // _VEC2 - case 0x8dc7: return setValueV3uiArray; // _VEC3 - case 0x8dc8: return setValueV4uiArray; // _VEC4 + if ( l > 2 && points[ l - 1 ].equals( points[ 0 ] ) ) { - case 0x8b5e: // SAMPLER_2D - case 0x8d66: // SAMPLER_EXTERNAL_OES - case 0x8dca: // INT_SAMPLER_2D - case 0x8dd2: // UNSIGNED_INT_SAMPLER_2D - case 0x8b62: // SAMPLER_2D_SHADOW - return setValueT1Array; + points.pop(); - case 0x8b5f: // SAMPLER_3D - case 0x8dcb: // INT_SAMPLER_3D - case 0x8dd3: // UNSIGNED_INT_SAMPLER_3D - return setValueT3DArray; + } - case 0x8b60: // SAMPLER_CUBE - case 0x8dcc: // INT_SAMPLER_CUBE - case 0x8dd4: // UNSIGNED_INT_SAMPLER_CUBE - case 0x8dc5: // SAMPLER_CUBE_SHADOW - return setValueT6Array; +} - case 0x8dc1: // SAMPLER_2D_ARRAY - case 0x8dcf: // INT_SAMPLER_2D_ARRAY - case 0x8dd7: // UNSIGNED_INT_SAMPLER_2D_ARRAY - case 0x8dc4: // SAMPLER_2D_ARRAY_SHADOW - return setValueT2DArrayArray; +function addContour( vertices, contour ) { + + for ( let i = 0; i < contour.length; i ++ ) { + + vertices.push( contour[ i ].x ); + vertices.push( contour[ i ].y ); } } -// --- Uniform Classes --- +/** + * Creates extruded geometry from a path shape. + * + * ```js + * const length = 12, width = 8; + * + * const shape = new THREE.Shape(); + * shape.moveTo( 0,0 ); + * shape.lineTo( 0, width ); + * shape.lineTo( length, width ); + * shape.lineTo( length, 0 ); + * shape.lineTo( 0, 0 ); + * + * const geometry = new THREE.ExtrudeGeometry( shape ); + * const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); + * const mesh = new THREE.Mesh( geometry, material ) ; + * scene.add( mesh ); + * ``` + * + * @augments BufferGeometry + */ +class ExtrudeGeometry extends BufferGeometry { -class SingleUniform { + /** + * Constructs a new extrude geometry. + * + * @param {Shape|Array} [shapes] - A shape or an array of shapes. + * @param {ExtrudeGeometry~Options} [options] - The extrude settings. + */ + constructor( shapes = new Shape( [ new Vector2( 0.5, 0.5 ), new Vector2( -0.5, 0.5 ), new Vector2( -0.5, -0.5 ), new Vector2( 0.5, -0.5 ) ] ), options = {} ) { - constructor( id, activeInfo, addr ) { + super(); - this.id = id; - this.addr = addr; - this.cache = []; - this.setValue = getSingularSetter( activeInfo.type ); + this.type = 'ExtrudeGeometry'; - // this.path = activeInfo.name; // DEBUG + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + shapes: shapes, + options: options + }; - } + shapes = Array.isArray( shapes ) ? shapes : [ shapes ]; -} + const scope = this; -class PureArrayUniform { + const verticesArray = []; + const uvArray = []; - constructor( id, activeInfo, addr ) { + for ( let i = 0, l = shapes.length; i < l; i ++ ) { - this.id = id; - this.addr = addr; - this.cache = []; - this.size = activeInfo.size; - this.setValue = getPureArraySetter( activeInfo.type ); + const shape = shapes[ i ]; + addShape( shape ); - // this.path = activeInfo.name; // DEBUG + } - } + // build geometry -} + this.setAttribute( 'position', new Float32BufferAttribute( verticesArray, 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvArray, 2 ) ); -class StructuredUniform { + this.computeVertexNormals(); - constructor( id ) { + // functions - this.id = id; + function addShape( shape ) { - this.seq = []; - this.map = {}; + const placeholder = []; - } + // options - setValue( gl, value, textures ) { + const curveSegments = options.curveSegments !== undefined ? options.curveSegments : 12; + const steps = options.steps !== undefined ? options.steps : 1; + const depth = options.depth !== undefined ? options.depth : 1; - const seq = this.seq; + let bevelEnabled = options.bevelEnabled !== undefined ? options.bevelEnabled : true; + let bevelThickness = options.bevelThickness !== undefined ? options.bevelThickness : 0.2; + let bevelSize = options.bevelSize !== undefined ? options.bevelSize : bevelThickness - 0.1; + let bevelOffset = options.bevelOffset !== undefined ? options.bevelOffset : 0; + let bevelSegments = options.bevelSegments !== undefined ? options.bevelSegments : 3; - for ( let i = 0, n = seq.length; i !== n; ++ i ) { + const extrudePath = options.extrudePath; - const u = seq[ i ]; - u.setValue( gl, value[ u.id ], textures ); + const uvgen = options.UVGenerator !== undefined ? options.UVGenerator : WorldUVGenerator; - } + // - } + let extrudePts, extrudeByPath = false; + let splineTube, binormal, normal, position2; -} + if ( extrudePath ) { -// --- Top-level --- + extrudePts = extrudePath.getSpacedPoints( steps ); -// Parser - builds up the property tree from the path strings + extrudeByPath = true; + bevelEnabled = false; // bevels not supported for path extrusion -const RePathPart = /(\w+)(\])?(\[|\.)?/g; + // SETUP TNB variables -// extracts -// - the identifier (member name or array index) -// - followed by an optional right bracket (found when array index) -// - followed by an optional left bracket or dot (type of subscript) -// -// Note: These portions can be read in a non-overlapping fashion and -// allow straightforward parsing of the hierarchy that WebGL encodes -// in the uniform names. + // TODO1 - have a .isClosed in spline? -function addUniform( container, uniformObject ) { + splineTube = extrudePath.computeFrenetFrames( steps, false ); - container.seq.push( uniformObject ); - container.map[ uniformObject.id ] = uniformObject; + // console.log(splineTube, 'splineTube', splineTube.normals.length, 'steps', steps, 'extrudePts', extrudePts.length); -} + binormal = new Vector3(); + normal = new Vector3(); + position2 = new Vector3(); -function parseUniform( activeInfo, addr, container ) { + } - const path = activeInfo.name, - pathLength = path.length; + // Safeguards if bevels are not enabled - // reset RegExp object, because of the early exit of a previous run - RePathPart.lastIndex = 0; + if ( ! bevelEnabled ) { - while ( true ) { + bevelSegments = 0; + bevelThickness = 0; + bevelSize = 0; + bevelOffset = 0; + + } + + // Variables initialization - const match = RePathPart.exec( path ), - matchEnd = RePathPart.lastIndex; + const shapePoints = shape.extractPoints( curveSegments ); - let id = match[ 1 ]; - const idIsIndex = match[ 2 ] === ']', - subscript = match[ 3 ]; + let vertices = shapePoints.shape; + const holes = shapePoints.holes; - if ( idIsIndex ) id = id | 0; // convert to integer + const reverse = ! ShapeUtils.isClockWise( vertices ); - if ( subscript === undefined || subscript === '[' && matchEnd + 2 === pathLength ) { + if ( reverse ) { - // bare name or "pure" bottom-level array "[0]" suffix + vertices = vertices.reverse(); - addUniform( container, subscript === undefined ? - new SingleUniform( id, activeInfo, addr ) : - new PureArrayUniform( id, activeInfo, addr ) ); + // Maybe we should also check if holes are in the opposite direction, just to be safe ... - break; + for ( let h = 0, hl = holes.length; h < hl; h ++ ) { - } else { + const ahole = holes[ h ]; - // step into inner node / create it in case it doesn't exist + if ( ShapeUtils.isClockWise( ahole ) ) { - const map = container.map; - let next = map[ id ]; + holes[ h ] = ahole.reverse(); - if ( next === undefined ) { + } - next = new StructuredUniform( id ); - addUniform( container, next ); + } } - container = next; + /**Merges index-adjacent points that are within a threshold distance of each other. Array is modified in-place. Threshold distance is empirical, and scaled based on the magnitude of point coordinates. + * @param {Array} points + */ + function mergeOverlappingPoints( points ) { - } + const THRESHOLD = 1e-10; + const THRESHOLD_SQ = THRESHOLD * THRESHOLD; + let prevPos = points[ 0 ]; + for ( let i = 1; i <= points.length; i ++ ) { - } + const currentIndex = i % points.length; + const currentPos = points[ currentIndex ]; + const dx = currentPos.x - prevPos.x; + const dy = currentPos.y - prevPos.y; + const distSq = dx * dx + dy * dy; -} + const scalingFactorSqrt = Math.max( + Math.abs( currentPos.x ), + Math.abs( currentPos.y ), + Math.abs( prevPos.x ), + Math.abs( prevPos.y ) + ); + const thresholdSqScaled = THRESHOLD_SQ * scalingFactorSqrt * scalingFactorSqrt; + if ( distSq <= thresholdSqScaled ) { -// Root Container + points.splice( currentIndex, 1 ); + i --; + continue; -class WebGLUniforms { + } - constructor( gl, program ) { + prevPos = currentPos; - this.seq = []; - this.map = {}; + } - const n = gl.getProgramParameter( program, gl.ACTIVE_UNIFORMS ); + } - for ( let i = 0; i < n; ++ i ) { + mergeOverlappingPoints( vertices ); + holes.forEach( mergeOverlappingPoints ); - const info = gl.getActiveUniform( program, i ), - addr = gl.getUniformLocation( program, info.name ); + const numHoles = holes.length; - parseUniform( info, addr, this ); + /* Vertices */ - } + const contour = vertices; // vertices has all points but contour has only points of circumference - } + for ( let h = 0; h < numHoles; h ++ ) { - setValue( gl, name, value, textures ) { + const ahole = holes[ h ]; - const u = this.map[ name ]; + vertices = vertices.concat( ahole ); - if ( u !== undefined ) u.setValue( gl, value, textures ); + } - } - setOptional( gl, object, name ) { + function scalePt2( pt, vec, size ) { - const v = object[ name ]; + if ( ! vec ) console.error( 'THREE.ExtrudeGeometry: vec does not exist' ); - if ( v !== undefined ) this.setValue( gl, name, v ); + return pt.clone().addScaledVector( vec, size ); - } + } - static upload( gl, seq, values, textures ) { + const vlen = vertices.length; - for ( let i = 0, n = seq.length; i !== n; ++ i ) { - const u = seq[ i ], - v = values[ u.id ]; + // Find directions for point movement - if ( v.needsUpdate !== false ) { - // note: always updating when .needsUpdate is undefined - u.setValue( gl, v.value, textures ); + function getBevelVec( inPt, inPrev, inNext ) { - } + // computes for inPt the corresponding point inPt' on a new contour + // shifted by 1 unit (length of normalized vector) to the left + // if we walk along contour clockwise, this new contour is outside the old one + // + // inPt' is the intersection of the two lines parallel to the two + // adjacent edges of inPt at a distance of 1 unit on the left side. - } + let v_trans_x, v_trans_y, shrink_by; // resulting translation vector for inPt - } + // good reading for geometry algorithms (here: line-line intersection) + // http://geomalgorithms.com/a05-_intersect-1.html - static seqWithValue( seq, values ) { + const v_prev_x = inPt.x - inPrev.x, + v_prev_y = inPt.y - inPrev.y; + const v_next_x = inNext.x - inPt.x, + v_next_y = inNext.y - inPt.y; - const r = []; + const v_prev_lensq = ( v_prev_x * v_prev_x + v_prev_y * v_prev_y ); - for ( let i = 0, n = seq.length; i !== n; ++ i ) { + // check for collinear edges + const collinear0 = ( v_prev_x * v_next_y - v_prev_y * v_next_x ); - const u = seq[ i ]; - if ( u.id in values ) r.push( u ); + if ( Math.abs( collinear0 ) > Number.EPSILON ) { - } + // not collinear - return r; + // length of vectors for normalizing - } + const v_prev_len = Math.sqrt( v_prev_lensq ); + const v_next_len = Math.sqrt( v_next_x * v_next_x + v_next_y * v_next_y ); -} + // shift adjacent points by unit vectors to the left -function WebGLShader( gl, type, string ) { + const ptPrevShift_x = ( inPrev.x - v_prev_y / v_prev_len ); + const ptPrevShift_y = ( inPrev.y + v_prev_x / v_prev_len ); - const shader = gl.createShader( type ); + const ptNextShift_x = ( inNext.x - v_next_y / v_next_len ); + const ptNextShift_y = ( inNext.y + v_next_x / v_next_len ); - gl.shaderSource( shader, string ); - gl.compileShader( shader ); + // scaling factor for v_prev to intersection point - return shader; + const sf = ( ( ptNextShift_x - ptPrevShift_x ) * v_next_y - + ( ptNextShift_y - ptPrevShift_y ) * v_next_x ) / + ( v_prev_x * v_next_y - v_prev_y * v_next_x ); -} + // vector from inPt to intersection point -let programIdCount = 0; + v_trans_x = ( ptPrevShift_x + v_prev_x * sf - inPt.x ); + v_trans_y = ( ptPrevShift_y + v_prev_y * sf - inPt.y ); -function handleSource( string, errorLine ) { + // Don't normalize!, otherwise sharp corners become ugly + // but prevent crazy spikes + const v_trans_lensq = ( v_trans_x * v_trans_x + v_trans_y * v_trans_y ); + if ( v_trans_lensq <= 2 ) { - const lines = string.split( '\n' ); - const lines2 = []; + return new Vector2( v_trans_x, v_trans_y ); - const from = Math.max( errorLine - 6, 0 ); - const to = Math.min( errorLine + 6, lines.length ); + } else { - for ( let i = from; i < to; i ++ ) { + shrink_by = Math.sqrt( v_trans_lensq / 2 ); - const line = i + 1; - lines2.push( `${line === errorLine ? '>' : ' '} ${line}: ${lines[ i ]}` ); + } - } + } else { - return lines2.join( '\n' ); + // handle special case of collinear edges -} + let direction_eq = false; // assumes: opposite -function getEncodingComponents( colorSpace ) { + if ( v_prev_x > Number.EPSILON ) { - switch ( colorSpace ) { + if ( v_next_x > Number.EPSILON ) { - case LinearSRGBColorSpace: - return [ 'Linear', '( value )' ]; - case SRGBColorSpace: - return [ 'sRGB', '( value )' ]; - default: - console.warn( 'THREE.WebGLProgram: Unsupported color space:', colorSpace ); - return [ 'Linear', '( value )' ]; + direction_eq = true; - } + } -} + } else { -function getShaderErrors( gl, shader, type ) { + if ( v_prev_x < - Number.EPSILON ) { - const status = gl.getShaderParameter( shader, gl.COMPILE_STATUS ); - const errors = gl.getShaderInfoLog( shader ).trim(); + if ( v_next_x < - Number.EPSILON ) { - if ( status && errors === '' ) return ''; + direction_eq = true; - const errorMatches = /ERROR: 0:(\d+)/.exec( errors ); - if ( errorMatches ) { + } - // --enable-privileged-webgl-extension - // console.log( '**' + type + '**', gl.getExtension( 'WEBGL_debug_shaders' ).getTranslatedShaderSource( shader ) ); + } else { - const errorLine = parseInt( errorMatches[ 1 ] ); - return type.toUpperCase() + '\n\n' + errors + '\n\n' + handleSource( gl.getShaderSource( shader ), errorLine ); + if ( Math.sign( v_prev_y ) === Math.sign( v_next_y ) ) { - } else { + direction_eq = true; - return errors; + } - } + } -} + } -function getTexelEncodingFunction( functionName, colorSpace ) { + if ( direction_eq ) { - const components = getEncodingComponents( colorSpace ); - return 'vec4 ' + functionName + '( vec4 value ) { return LinearTo' + components[ 0 ] + components[ 1 ] + '; }'; + // console.log("Warning: lines are a straight sequence"); + v_trans_x = - v_prev_y; + v_trans_y = v_prev_x; + shrink_by = Math.sqrt( v_prev_lensq ); -} + } else { -function getToneMappingFunction( functionName, toneMapping ) { + // console.log("Warning: lines are a straight spike"); + v_trans_x = v_prev_x; + v_trans_y = v_prev_y; + shrink_by = Math.sqrt( v_prev_lensq / 2 ); - let toneMappingName; + } - switch ( toneMapping ) { + } - case LinearToneMapping: - toneMappingName = 'Linear'; - break; + return new Vector2( v_trans_x / shrink_by, v_trans_y / shrink_by ); - case ReinhardToneMapping: - toneMappingName = 'Reinhard'; - break; + } - case CineonToneMapping: - toneMappingName = 'OptimizedCineon'; - break; - case ACESFilmicToneMapping: - toneMappingName = 'ACESFilmic'; - break; + const contourMovements = []; - case CustomToneMapping: - toneMappingName = 'Custom'; - break; + for ( let i = 0, il = contour.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) { - default: - console.warn( 'THREE.WebGLProgram: Unsupported toneMapping:', toneMapping ); - toneMappingName = 'Linear'; + if ( j === il ) j = 0; + if ( k === il ) k = 0; - } + // (j)---(i)---(k) + // console.log('i,j,k', i, j , k) - return 'vec3 ' + functionName + '( vec3 color ) { return ' + toneMappingName + 'ToneMapping( color ); }'; + contourMovements[ i ] = getBevelVec( contour[ i ], contour[ j ], contour[ k ] ); -} + } -function generateExtensions( parameters ) { + const holesMovements = []; + let oneHoleMovements, verticesMovements = contourMovements.concat(); - const chunks = [ - ( parameters.extensionDerivatives || !! parameters.envMapCubeUVHeight || parameters.bumpMap || parameters.normalMapTangentSpace || parameters.clearcoatNormalMap || parameters.flatShading || parameters.shaderID === 'physical' ) ? '#extension GL_OES_standard_derivatives : enable' : '', - ( parameters.extensionFragDepth || parameters.logarithmicDepthBuffer ) && parameters.rendererExtensionFragDepth ? '#extension GL_EXT_frag_depth : enable' : '', - ( parameters.extensionDrawBuffers && parameters.rendererExtensionDrawBuffers ) ? '#extension GL_EXT_draw_buffers : require' : '', - ( parameters.extensionShaderTextureLOD || parameters.envMap || parameters.transmission ) && parameters.rendererExtensionShaderTextureLod ? '#extension GL_EXT_shader_texture_lod : enable' : '' - ]; + for ( let h = 0, hl = numHoles; h < hl; h ++ ) { - return chunks.filter( filterEmptyLine ).join( '\n' ); + const ahole = holes[ h ]; -} + oneHoleMovements = []; -function generateDefines( defines ) { + for ( let i = 0, il = ahole.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) { - const chunks = []; + if ( j === il ) j = 0; + if ( k === il ) k = 0; - for ( const name in defines ) { + // (j)---(i)---(k) + oneHoleMovements[ i ] = getBevelVec( ahole[ i ], ahole[ j ], ahole[ k ] ); - const value = defines[ name ]; + } - if ( value === false ) continue; + holesMovements.push( oneHoleMovements ); + verticesMovements = verticesMovements.concat( oneHoleMovements ); - chunks.push( '#define ' + name + ' ' + value ); + } - } + let faces; - return chunks.join( '\n' ); + if ( bevelSegments === 0 ) { -} + faces = ShapeUtils.triangulateShape( contour, holes ); -function fetchAttributeLocations( gl, program ) { + } else { - const attributes = {}; + const contractedContourVertices = []; + const expandedHoleVertices = []; - const n = gl.getProgramParameter( program, gl.ACTIVE_ATTRIBUTES ); + // Loop bevelSegments, 1 for the front, 1 for the back - for ( let i = 0; i < n; i ++ ) { + for ( let b = 0; b < bevelSegments; b ++ ) { - const info = gl.getActiveAttrib( program, i ); - const name = info.name; + //for ( b = bevelSegments; b > 0; b -- ) { - let locationSize = 1; - if ( info.type === gl.FLOAT_MAT2 ) locationSize = 2; - if ( info.type === gl.FLOAT_MAT3 ) locationSize = 3; - if ( info.type === gl.FLOAT_MAT4 ) locationSize = 4; + const t = b / bevelSegments; + const z = bevelThickness * Math.cos( t * Math.PI / 2 ); + const bs = bevelSize * Math.sin( t * Math.PI / 2 ) + bevelOffset; - // console.log( 'THREE.WebGLProgram: ACTIVE VERTEX ATTRIBUTE:', name, i ); + // contract shape - attributes[ name ] = { - type: info.type, - location: gl.getAttribLocation( program, name ), - locationSize: locationSize - }; + for ( let i = 0, il = contour.length; i < il; i ++ ) { - } + const vert = scalePt2( contour[ i ], contourMovements[ i ], bs ); - return attributes; + v( vert.x, vert.y, - z ); + if ( t === 0 ) contractedContourVertices.push( vert ); -} + } -function filterEmptyLine( string ) { + // expand holes - return string !== ''; + for ( let h = 0, hl = numHoles; h < hl; h ++ ) { -} + const ahole = holes[ h ]; + oneHoleMovements = holesMovements[ h ]; + const oneHoleVertices = []; + for ( let i = 0, il = ahole.length; i < il; i ++ ) { -function replaceLightNums( string, parameters ) { + const vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs ); - const numSpotLightCoords = parameters.numSpotLightShadows + parameters.numSpotLightMaps - parameters.numSpotLightShadowsWithMaps; + v( vert.x, vert.y, - z ); + if ( t === 0 ) oneHoleVertices.push( vert ); - return string - .replace( /NUM_DIR_LIGHTS/g, parameters.numDirLights ) - .replace( /NUM_SPOT_LIGHTS/g, parameters.numSpotLights ) - .replace( /NUM_SPOT_LIGHT_MAPS/g, parameters.numSpotLightMaps ) - .replace( /NUM_SPOT_LIGHT_COORDS/g, numSpotLightCoords ) - .replace( /NUM_RECT_AREA_LIGHTS/g, parameters.numRectAreaLights ) - .replace( /NUM_POINT_LIGHTS/g, parameters.numPointLights ) - .replace( /NUM_HEMI_LIGHTS/g, parameters.numHemiLights ) - .replace( /NUM_DIR_LIGHT_SHADOWS/g, parameters.numDirLightShadows ) - .replace( /NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS/g, parameters.numSpotLightShadowsWithMaps ) - .replace( /NUM_SPOT_LIGHT_SHADOWS/g, parameters.numSpotLightShadows ) - .replace( /NUM_POINT_LIGHT_SHADOWS/g, parameters.numPointLightShadows ); + } -} + if ( t === 0 ) expandedHoleVertices.push( oneHoleVertices ); -function replaceClippingPlaneNums( string, parameters ) { + } - return string - .replace( /NUM_CLIPPING_PLANES/g, parameters.numClippingPlanes ) - .replace( /UNION_CLIPPING_PLANES/g, ( parameters.numClippingPlanes - parameters.numClipIntersection ) ); + } -} + faces = ShapeUtils.triangulateShape( contractedContourVertices, expandedHoleVertices ); -// Resolve Includes + } -const includePattern = /^[ \t]*#include +<([\w\d./]+)>/gm; + const flen = faces.length; -function resolveIncludes( string ) { + const bs = bevelSize + bevelOffset; - return string.replace( includePattern, includeReplacer ); + // Back facing vertices -} + for ( let i = 0; i < vlen; i ++ ) { -function includeReplacer( match, include ) { + const vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ]; - const string = ShaderChunk[ include ]; + if ( ! extrudeByPath ) { - if ( string === undefined ) { + v( vert.x, vert.y, 0 ); - throw new Error( 'Can not resolve #include <' + include + '>' ); + } else { - } + // v( vert.x, vert.y + extrudePts[ 0 ].y, extrudePts[ 0 ].x ); - return resolveIncludes( string ); + normal.copy( splineTube.normals[ 0 ] ).multiplyScalar( vert.x ); + binormal.copy( splineTube.binormals[ 0 ] ).multiplyScalar( vert.y ); -} + position2.copy( extrudePts[ 0 ] ).add( normal ).add( binormal ); -// Unroll Loops + v( position2.x, position2.y, position2.z ); -const unrollLoopPattern = /#pragma unroll_loop_start\s+for\s*\(\s*int\s+i\s*=\s*(\d+)\s*;\s*i\s*<\s*(\d+)\s*;\s*i\s*\+\+\s*\)\s*{([\s\S]+?)}\s+#pragma unroll_loop_end/g; + } -function unrollLoops( string ) { + } - return string.replace( unrollLoopPattern, loopReplacer ); + // Add stepped vertices... + // Including front facing vertices -} + for ( let s = 1; s <= steps; s ++ ) { -function loopReplacer( match, start, end, snippet ) { + for ( let i = 0; i < vlen; i ++ ) { - let string = ''; + const vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ]; - for ( let i = parseInt( start ); i < parseInt( end ); i ++ ) { + if ( ! extrudeByPath ) { - string += snippet - .replace( /\[\s*i\s*\]/g, '[ ' + i + ' ]' ) - .replace( /UNROLLED_LOOP_INDEX/g, i ); + v( vert.x, vert.y, depth / steps * s ); - } + } else { - return string; + // v( vert.x, vert.y + extrudePts[ s - 1 ].y, extrudePts[ s - 1 ].x ); -} + normal.copy( splineTube.normals[ s ] ).multiplyScalar( vert.x ); + binormal.copy( splineTube.binormals[ s ] ).multiplyScalar( vert.y ); -// + position2.copy( extrudePts[ s ] ).add( normal ).add( binormal ); -function generatePrecision( parameters ) { + v( position2.x, position2.y, position2.z ); - let precisionstring = 'precision ' + parameters.precision + ' float;\nprecision ' + parameters.precision + ' int;'; + } - if ( parameters.precision === 'highp' ) { + } - precisionstring += '\n#define HIGH_PRECISION'; + } - } else if ( parameters.precision === 'mediump' ) { - precisionstring += '\n#define MEDIUM_PRECISION'; + // Add bevel segments planes - } else if ( parameters.precision === 'lowp' ) { + //for ( b = 1; b <= bevelSegments; b ++ ) { + for ( let b = bevelSegments - 1; b >= 0; b -- ) { - precisionstring += '\n#define LOW_PRECISION'; + const t = b / bevelSegments; + const z = bevelThickness * Math.cos( t * Math.PI / 2 ); + const bs = bevelSize * Math.sin( t * Math.PI / 2 ) + bevelOffset; - } + // contract shape - return precisionstring; + for ( let i = 0, il = contour.length; i < il; i ++ ) { -} + const vert = scalePt2( contour[ i ], contourMovements[ i ], bs ); + v( vert.x, vert.y, depth + z ); -function generateShadowMapTypeDefine( parameters ) { + } - let shadowMapTypeDefine = 'SHADOWMAP_TYPE_BASIC'; + // expand holes - if ( parameters.shadowMapType === PCFShadowMap ) { + for ( let h = 0, hl = holes.length; h < hl; h ++ ) { - shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF'; + const ahole = holes[ h ]; + oneHoleMovements = holesMovements[ h ]; - } else if ( parameters.shadowMapType === PCFSoftShadowMap ) { + for ( let i = 0, il = ahole.length; i < il; i ++ ) { - shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF_SOFT'; + const vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs ); - } else if ( parameters.shadowMapType === VSMShadowMap ) { + if ( ! extrudeByPath ) { - shadowMapTypeDefine = 'SHADOWMAP_TYPE_VSM'; + v( vert.x, vert.y, depth + z ); - } + } else { - return shadowMapTypeDefine; + v( vert.x, vert.y + extrudePts[ steps - 1 ].y, extrudePts[ steps - 1 ].x + z ); -} + } -function generateEnvMapTypeDefine( parameters ) { + } - let envMapTypeDefine = 'ENVMAP_TYPE_CUBE'; + } - if ( parameters.envMap ) { + } - switch ( parameters.envMapMode ) { + /* Faces */ - case CubeReflectionMapping: - case CubeRefractionMapping: - envMapTypeDefine = 'ENVMAP_TYPE_CUBE'; - break; + // Top and bottom faces - case CubeUVReflectionMapping: - envMapTypeDefine = 'ENVMAP_TYPE_CUBE_UV'; - break; + buildLidFaces(); - } + // Sides faces - } + buildSideFaces(); - return envMapTypeDefine; -} + ///// Internal functions -function generateEnvMapModeDefine( parameters ) { + function buildLidFaces() { - let envMapModeDefine = 'ENVMAP_MODE_REFLECTION'; + const start = verticesArray.length / 3; - if ( parameters.envMap ) { + if ( bevelEnabled ) { - switch ( parameters.envMapMode ) { + let layer = 0; // steps + 1 + let offset = vlen * layer; - case CubeRefractionMapping: + // Bottom faces - envMapModeDefine = 'ENVMAP_MODE_REFRACTION'; - break; + for ( let i = 0; i < flen; i ++ ) { - } + const face = faces[ i ]; + f3( face[ 2 ] + offset, face[ 1 ] + offset, face[ 0 ] + offset ); - } + } - return envMapModeDefine; + layer = steps + bevelSegments * 2; + offset = vlen * layer; -} + // Top faces -function generateEnvMapBlendingDefine( parameters ) { + for ( let i = 0; i < flen; i ++ ) { - let envMapBlendingDefine = 'ENVMAP_BLENDING_NONE'; + const face = faces[ i ]; + f3( face[ 0 ] + offset, face[ 1 ] + offset, face[ 2 ] + offset ); - if ( parameters.envMap ) { + } - switch ( parameters.combine ) { + } else { - case MultiplyOperation: - envMapBlendingDefine = 'ENVMAP_BLENDING_MULTIPLY'; - break; + // Bottom faces - case MixOperation: - envMapBlendingDefine = 'ENVMAP_BLENDING_MIX'; - break; + for ( let i = 0; i < flen; i ++ ) { - case AddOperation: - envMapBlendingDefine = 'ENVMAP_BLENDING_ADD'; - break; + const face = faces[ i ]; + f3( face[ 2 ], face[ 1 ], face[ 0 ] ); - } + } - } + // Top faces - return envMapBlendingDefine; + for ( let i = 0; i < flen; i ++ ) { -} + const face = faces[ i ]; + f3( face[ 0 ] + vlen * steps, face[ 1 ] + vlen * steps, face[ 2 ] + vlen * steps ); -function generateCubeUVSize( parameters ) { + } - const imageHeight = parameters.envMapCubeUVHeight; + } - if ( imageHeight === null ) return null; + scope.addGroup( start, verticesArray.length / 3 - start, 0 ); - const maxMip = Math.log2( imageHeight ) - 2; + } - const texelHeight = 1.0 / imageHeight; + // Create faces for the z-sides of the shape - const texelWidth = 1.0 / ( 3 * Math.max( Math.pow( 2, maxMip ), 7 * 16 ) ); + function buildSideFaces() { - return { texelWidth, texelHeight, maxMip }; + const start = verticesArray.length / 3; + let layeroffset = 0; + sidewalls( contour, layeroffset ); + layeroffset += contour.length; -} + for ( let h = 0, hl = holes.length; h < hl; h ++ ) { -function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) { + const ahole = holes[ h ]; + sidewalls( ahole, layeroffset ); - // TODO Send this event to Three.js DevTools - // console.log( 'WebGLProgram', cacheKey ); + //, true + layeroffset += ahole.length; + + } - const gl = renderer.getContext(); - const defines = parameters.defines; + scope.addGroup( start, verticesArray.length / 3 - start, 1 ); - let vertexShader = parameters.vertexShader; - let fragmentShader = parameters.fragmentShader; - const shadowMapTypeDefine = generateShadowMapTypeDefine( parameters ); - const envMapTypeDefine = generateEnvMapTypeDefine( parameters ); - const envMapModeDefine = generateEnvMapModeDefine( parameters ); - const envMapBlendingDefine = generateEnvMapBlendingDefine( parameters ); - const envMapCubeUVSize = generateCubeUVSize( parameters ); + } - const customExtensions = parameters.isWebGL2 ? '' : generateExtensions( parameters ); + function sidewalls( contour, layeroffset ) { - const customDefines = generateDefines( defines ); + let i = contour.length; - const program = gl.createProgram(); + while ( -- i >= 0 ) { - let prefixVertex, prefixFragment; - let versionString = parameters.glslVersion ? '#version ' + parameters.glslVersion + '\n' : ''; + const j = i; + let k = i - 1; + if ( k < 0 ) k = contour.length - 1; - if ( parameters.isRawShaderMaterial ) { + //console.log('b', i,j, i-1, k,vertices.length); - prefixVertex = [ + for ( let s = 0, sl = ( steps + bevelSegments * 2 ); s < sl; s ++ ) { - customDefines + const slen1 = vlen * s; + const slen2 = vlen * ( s + 1 ); - ].filter( filterEmptyLine ).join( '\n' ); + const a = layeroffset + j + slen1, + b = layeroffset + k + slen1, + c = layeroffset + k + slen2, + d = layeroffset + j + slen2; - if ( prefixVertex.length > 0 ) { + f4( a, b, c, d ); - prefixVertex += '\n'; + } - } + } - prefixFragment = [ + } - customExtensions, - customDefines + function v( x, y, z ) { - ].filter( filterEmptyLine ).join( '\n' ); + placeholder.push( x ); + placeholder.push( y ); + placeholder.push( z ); - if ( prefixFragment.length > 0 ) { + } - prefixFragment += '\n'; - } + function f3( a, b, c ) { - } else { + addVertex( a ); + addVertex( b ); + addVertex( c ); - prefixVertex = [ + const nextIndex = verticesArray.length / 3; + const uvs = uvgen.generateTopUV( scope, verticesArray, nextIndex - 3, nextIndex - 2, nextIndex - 1 ); - generatePrecision( parameters ), + addUV( uvs[ 0 ] ); + addUV( uvs[ 1 ] ); + addUV( uvs[ 2 ] ); - '#define SHADER_NAME ' + parameters.shaderName, + } - customDefines, + function f4( a, b, c, d ) { - parameters.instancing ? '#define USE_INSTANCING' : '', - parameters.instancingColor ? '#define USE_INSTANCING_COLOR' : '', + addVertex( a ); + addVertex( b ); + addVertex( d ); - parameters.useFog && parameters.fog ? '#define USE_FOG' : '', - parameters.useFog && parameters.fogExp2 ? '#define FOG_EXP2' : '', + addVertex( b ); + addVertex( c ); + addVertex( d ); - parameters.map ? '#define USE_MAP' : '', - parameters.envMap ? '#define USE_ENVMAP' : '', - parameters.envMap ? '#define ' + envMapModeDefine : '', - parameters.lightMap ? '#define USE_LIGHTMAP' : '', - parameters.aoMap ? '#define USE_AOMAP' : '', - parameters.bumpMap ? '#define USE_BUMPMAP' : '', - parameters.normalMap ? '#define USE_NORMALMAP' : '', - parameters.normalMapObjectSpace ? '#define USE_NORMALMAP_OBJECTSPACE' : '', - parameters.normalMapTangentSpace ? '#define USE_NORMALMAP_TANGENTSPACE' : '', - parameters.displacementMap ? '#define USE_DISPLACEMENTMAP' : '', - parameters.emissiveMap ? '#define USE_EMISSIVEMAP' : '', - parameters.anisotropyMap ? '#define USE_ANISOTROPYMAP' : '', + const nextIndex = verticesArray.length / 3; + const uvs = uvgen.generateSideWallUV( scope, verticesArray, nextIndex - 6, nextIndex - 3, nextIndex - 2, nextIndex - 1 ); - parameters.clearcoatMap ? '#define USE_CLEARCOATMAP' : '', - parameters.clearcoatRoughnessMap ? '#define USE_CLEARCOAT_ROUGHNESSMAP' : '', - parameters.clearcoatNormalMap ? '#define USE_CLEARCOAT_NORMALMAP' : '', + addUV( uvs[ 0 ] ); + addUV( uvs[ 1 ] ); + addUV( uvs[ 3 ] ); - parameters.iridescenceMap ? '#define USE_IRIDESCENCEMAP' : '', - parameters.iridescenceThicknessMap ? '#define USE_IRIDESCENCE_THICKNESSMAP' : '', + addUV( uvs[ 1 ] ); + addUV( uvs[ 2 ] ); + addUV( uvs[ 3 ] ); - parameters.specularMap ? '#define USE_SPECULARMAP' : '', - parameters.specularColorMap ? '#define USE_SPECULAR_COLORMAP' : '', - parameters.specularIntensityMap ? '#define USE_SPECULAR_INTENSITYMAP' : '', + } - parameters.roughnessMap ? '#define USE_ROUGHNESSMAP' : '', - parameters.metalnessMap ? '#define USE_METALNESSMAP' : '', - parameters.alphaMap ? '#define USE_ALPHAMAP' : '', + function addVertex( index ) { - parameters.transmission ? '#define USE_TRANSMISSION' : '', - parameters.transmissionMap ? '#define USE_TRANSMISSIONMAP' : '', - parameters.thicknessMap ? '#define USE_THICKNESSMAP' : '', + verticesArray.push( placeholder[ index * 3 + 0 ] ); + verticesArray.push( placeholder[ index * 3 + 1 ] ); + verticesArray.push( placeholder[ index * 3 + 2 ] ); - parameters.sheenColorMap ? '#define USE_SHEEN_COLORMAP' : '', - parameters.sheenRoughnessMap ? '#define USE_SHEEN_ROUGHNESSMAP' : '', + } - // - parameters.mapUv ? '#define MAP_UV ' + parameters.mapUv : '', - parameters.alphaMapUv ? '#define ALPHAMAP_UV ' + parameters.alphaMapUv : '', - parameters.lightMapUv ? '#define LIGHTMAP_UV ' + parameters.lightMapUv : '', - parameters.aoMapUv ? '#define AOMAP_UV ' + parameters.aoMapUv : '', - parameters.emissiveMapUv ? '#define EMISSIVEMAP_UV ' + parameters.emissiveMapUv : '', - parameters.bumpMapUv ? '#define BUMPMAP_UV ' + parameters.bumpMapUv : '', - parameters.normalMapUv ? '#define NORMALMAP_UV ' + parameters.normalMapUv : '', - parameters.displacementMapUv ? '#define DISPLACEMENTMAP_UV ' + parameters.displacementMapUv : '', + function addUV( vector2 ) { - parameters.metalnessMapUv ? '#define METALNESSMAP_UV ' + parameters.metalnessMapUv : '', - parameters.roughnessMapUv ? '#define ROUGHNESSMAP_UV ' + parameters.roughnessMapUv : '', + uvArray.push( vector2.x ); + uvArray.push( vector2.y ); - parameters.anisotropyMapUv ? '#define ANISOTROPYMAP_UV ' + parameters.anisotropyMapUv : '', + } - parameters.clearcoatMapUv ? '#define CLEARCOATMAP_UV ' + parameters.clearcoatMapUv : '', - parameters.clearcoatNormalMapUv ? '#define CLEARCOAT_NORMALMAP_UV ' + parameters.clearcoatNormalMapUv : '', - parameters.clearcoatRoughnessMapUv ? '#define CLEARCOAT_ROUGHNESSMAP_UV ' + parameters.clearcoatRoughnessMapUv : '', + } - parameters.iridescenceMapUv ? '#define IRIDESCENCEMAP_UV ' + parameters.iridescenceMapUv : '', - parameters.iridescenceThicknessMapUv ? '#define IRIDESCENCE_THICKNESSMAP_UV ' + parameters.iridescenceThicknessMapUv : '', + } - parameters.sheenColorMapUv ? '#define SHEEN_COLORMAP_UV ' + parameters.sheenColorMapUv : '', - parameters.sheenRoughnessMapUv ? '#define SHEEN_ROUGHNESSMAP_UV ' + parameters.sheenRoughnessMapUv : '', + copy( source ) { - parameters.specularMapUv ? '#define SPECULARMAP_UV ' + parameters.specularMapUv : '', - parameters.specularColorMapUv ? '#define SPECULAR_COLORMAP_UV ' + parameters.specularColorMapUv : '', - parameters.specularIntensityMapUv ? '#define SPECULAR_INTENSITYMAP_UV ' + parameters.specularIntensityMapUv : '', + super.copy( source ); - parameters.transmissionMapUv ? '#define TRANSMISSIONMAP_UV ' + parameters.transmissionMapUv : '', - parameters.thicknessMapUv ? '#define THICKNESSMAP_UV ' + parameters.thicknessMapUv : '', + this.parameters = Object.assign( {}, source.parameters ); - // + return this; - parameters.vertexTangents ? '#define USE_TANGENT' : '', - parameters.vertexColors ? '#define USE_COLOR' : '', - parameters.vertexAlphas ? '#define USE_COLOR_ALPHA' : '', - parameters.vertexUv1s ? '#define USE_UV1' : '', - parameters.vertexUv2s ? '#define USE_UV2' : '', - parameters.vertexUv3s ? '#define USE_UV3' : '', + } - parameters.pointsUvs ? '#define USE_POINTS_UV' : '', + toJSON() { - parameters.flatShading ? '#define FLAT_SHADED' : '', + const data = super.toJSON(); - parameters.skinning ? '#define USE_SKINNING' : '', + const shapes = this.parameters.shapes; + const options = this.parameters.options; - parameters.morphTargets ? '#define USE_MORPHTARGETS' : '', - parameters.morphNormals && parameters.flatShading === false ? '#define USE_MORPHNORMALS' : '', - ( parameters.morphColors && parameters.isWebGL2 ) ? '#define USE_MORPHCOLORS' : '', - ( parameters.morphTargetsCount > 0 && parameters.isWebGL2 ) ? '#define MORPHTARGETS_TEXTURE' : '', - ( parameters.morphTargetsCount > 0 && parameters.isWebGL2 ) ? '#define MORPHTARGETS_TEXTURE_STRIDE ' + parameters.morphTextureStride : '', - ( parameters.morphTargetsCount > 0 && parameters.isWebGL2 ) ? '#define MORPHTARGETS_COUNT ' + parameters.morphTargetsCount : '', - parameters.doubleSided ? '#define DOUBLE_SIDED' : '', - parameters.flipSided ? '#define FLIP_SIDED' : '', + return toJSON$1( shapes, options, data ); - parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '', - parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '', + } - parameters.sizeAttenuation ? '#define USE_SIZEATTENUATION' : '', + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @param {Array} shapes - An array of shapes. + * @return {ExtrudeGeometry} A new instance. + */ + static fromJSON( data, shapes ) { - parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '', - ( parameters.logarithmicDepthBuffer && parameters.rendererExtensionFragDepth ) ? '#define USE_LOGDEPTHBUF_EXT' : '', + const geometryShapes = []; - 'uniform mat4 modelMatrix;', - 'uniform mat4 modelViewMatrix;', - 'uniform mat4 projectionMatrix;', - 'uniform mat4 viewMatrix;', - 'uniform mat3 normalMatrix;', - 'uniform vec3 cameraPosition;', - 'uniform bool isOrthographic;', + for ( let j = 0, jl = data.shapes.length; j < jl; j ++ ) { - '#ifdef USE_INSTANCING', + const shape = shapes[ data.shapes[ j ] ]; - ' attribute mat4 instanceMatrix;', + geometryShapes.push( shape ); - '#endif', + } - '#ifdef USE_INSTANCING_COLOR', + const extrudePath = data.options.extrudePath; - ' attribute vec3 instanceColor;', + if ( extrudePath !== undefined ) { - '#endif', + data.options.extrudePath = new Curves[ extrudePath.type ]().fromJSON( extrudePath ); - 'attribute vec3 position;', - 'attribute vec3 normal;', - 'attribute vec2 uv;', + } - '#ifdef USE_UV1', + return new ExtrudeGeometry( geometryShapes, data.options ); - ' attribute vec2 uv1;', + } - '#endif', +} - '#ifdef USE_UV2', +const WorldUVGenerator = { - ' attribute vec2 uv2;', + generateTopUV: function ( geometry, vertices, indexA, indexB, indexC ) { - '#endif', + const a_x = vertices[ indexA * 3 ]; + const a_y = vertices[ indexA * 3 + 1 ]; + const b_x = vertices[ indexB * 3 ]; + const b_y = vertices[ indexB * 3 + 1 ]; + const c_x = vertices[ indexC * 3 ]; + const c_y = vertices[ indexC * 3 + 1 ]; - '#ifdef USE_UV3', + return [ + new Vector2( a_x, a_y ), + new Vector2( b_x, b_y ), + new Vector2( c_x, c_y ) + ]; - ' attribute vec2 uv3;', + }, - '#endif', + generateSideWallUV: function ( geometry, vertices, indexA, indexB, indexC, indexD ) { - '#ifdef USE_TANGENT', + const a_x = vertices[ indexA * 3 ]; + const a_y = vertices[ indexA * 3 + 1 ]; + const a_z = vertices[ indexA * 3 + 2 ]; + const b_x = vertices[ indexB * 3 ]; + const b_y = vertices[ indexB * 3 + 1 ]; + const b_z = vertices[ indexB * 3 + 2 ]; + const c_x = vertices[ indexC * 3 ]; + const c_y = vertices[ indexC * 3 + 1 ]; + const c_z = vertices[ indexC * 3 + 2 ]; + const d_x = vertices[ indexD * 3 ]; + const d_y = vertices[ indexD * 3 + 1 ]; + const d_z = vertices[ indexD * 3 + 2 ]; - ' attribute vec4 tangent;', + if ( Math.abs( a_y - b_y ) < Math.abs( a_x - b_x ) ) { - '#endif', + return [ + new Vector2( a_x, 1 - a_z ), + new Vector2( b_x, 1 - b_z ), + new Vector2( c_x, 1 - c_z ), + new Vector2( d_x, 1 - d_z ) + ]; - '#if defined( USE_COLOR_ALPHA )', + } else { - ' attribute vec4 color;', + return [ + new Vector2( a_y, 1 - a_z ), + new Vector2( b_y, 1 - b_z ), + new Vector2( c_y, 1 - c_z ), + new Vector2( d_y, 1 - d_z ) + ]; - '#elif defined( USE_COLOR )', + } - ' attribute vec3 color;', + } - '#endif', +}; - '#if ( defined( USE_MORPHTARGETS ) && ! defined( MORPHTARGETS_TEXTURE ) )', +function toJSON$1( shapes, options, data ) { - ' attribute vec3 morphTarget0;', - ' attribute vec3 morphTarget1;', - ' attribute vec3 morphTarget2;', - ' attribute vec3 morphTarget3;', + data.shapes = []; - ' #ifdef USE_MORPHNORMALS', + if ( Array.isArray( shapes ) ) { - ' attribute vec3 morphNormal0;', - ' attribute vec3 morphNormal1;', - ' attribute vec3 morphNormal2;', - ' attribute vec3 morphNormal3;', + for ( let i = 0, l = shapes.length; i < l; i ++ ) { - ' #else', + const shape = shapes[ i ]; - ' attribute vec3 morphTarget4;', - ' attribute vec3 morphTarget5;', - ' attribute vec3 morphTarget6;', - ' attribute vec3 morphTarget7;', + data.shapes.push( shape.uuid ); - ' #endif', + } - '#endif', + } else { - '#ifdef USE_SKINNING', + data.shapes.push( shapes.uuid ); - ' attribute vec4 skinIndex;', - ' attribute vec4 skinWeight;', + } - '#endif', + data.options = Object.assign( {}, options ); - '\n' + if ( options.extrudePath !== undefined ) data.options.extrudePath = options.extrudePath.toJSON(); - ].filter( filterEmptyLine ).join( '\n' ); + return data; - prefixFragment = [ +} - customExtensions, +/** + * A geometry class for representing an icosahedron. + * + * ```js + * const geometry = new THREE.IcosahedronGeometry(); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + * const icosahedron = new THREE.Mesh( geometry, material ); + * scene.add( icosahedron ); + * ``` + * + * @augments PolyhedronGeometry + */ +class IcosahedronGeometry extends PolyhedronGeometry { - generatePrecision( parameters ), + /** + * Constructs a new icosahedron geometry. + * + * @param {number} [radius=1] - Radius of the icosahedron. + * @param {number} [detail=0] - Setting this to a value greater than `0` adds vertices making it no longer a icosahedron. + */ + constructor( radius = 1, detail = 0 ) { - '#define SHADER_NAME ' + parameters.shaderName, + const t = ( 1 + Math.sqrt( 5 ) ) / 2; - customDefines, + const vertices = [ + -1, t, 0, 1, t, 0, -1, - t, 0, 1, - t, 0, + 0, -1, t, 0, 1, t, 0, -1, - t, 0, 1, - t, + t, 0, -1, t, 0, 1, - t, 0, -1, - t, 0, 1 + ]; - parameters.useFog && parameters.fog ? '#define USE_FOG' : '', - parameters.useFog && parameters.fogExp2 ? '#define FOG_EXP2' : '', + const indices = [ + 0, 11, 5, 0, 5, 1, 0, 1, 7, 0, 7, 10, 0, 10, 11, + 1, 5, 9, 5, 11, 4, 11, 10, 2, 10, 7, 6, 7, 1, 8, + 3, 9, 4, 3, 4, 2, 3, 2, 6, 3, 6, 8, 3, 8, 9, + 4, 9, 5, 2, 4, 11, 6, 2, 10, 8, 6, 7, 9, 8, 1 + ]; - parameters.map ? '#define USE_MAP' : '', - parameters.matcap ? '#define USE_MATCAP' : '', - parameters.envMap ? '#define USE_ENVMAP' : '', - parameters.envMap ? '#define ' + envMapTypeDefine : '', - parameters.envMap ? '#define ' + envMapModeDefine : '', - parameters.envMap ? '#define ' + envMapBlendingDefine : '', - envMapCubeUVSize ? '#define CUBEUV_TEXEL_WIDTH ' + envMapCubeUVSize.texelWidth : '', - envMapCubeUVSize ? '#define CUBEUV_TEXEL_HEIGHT ' + envMapCubeUVSize.texelHeight : '', - envMapCubeUVSize ? '#define CUBEUV_MAX_MIP ' + envMapCubeUVSize.maxMip + '.0' : '', - parameters.lightMap ? '#define USE_LIGHTMAP' : '', - parameters.aoMap ? '#define USE_AOMAP' : '', - parameters.bumpMap ? '#define USE_BUMPMAP' : '', - parameters.normalMap ? '#define USE_NORMALMAP' : '', - parameters.normalMapObjectSpace ? '#define USE_NORMALMAP_OBJECTSPACE' : '', - parameters.normalMapTangentSpace ? '#define USE_NORMALMAP_TANGENTSPACE' : '', - parameters.emissiveMap ? '#define USE_EMISSIVEMAP' : '', + super( vertices, indices, radius, detail ); - parameters.anisotropy ? '#define USE_ANISOTROPY' : '', - parameters.anisotropyMap ? '#define USE_ANISOTROPYMAP' : '', + this.type = 'IcosahedronGeometry'; - parameters.clearcoat ? '#define USE_CLEARCOAT' : '', - parameters.clearcoatMap ? '#define USE_CLEARCOATMAP' : '', - parameters.clearcoatRoughnessMap ? '#define USE_CLEARCOAT_ROUGHNESSMAP' : '', - parameters.clearcoatNormalMap ? '#define USE_CLEARCOAT_NORMALMAP' : '', + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + radius: radius, + detail: detail + }; - parameters.iridescence ? '#define USE_IRIDESCENCE' : '', - parameters.iridescenceMap ? '#define USE_IRIDESCENCEMAP' : '', - parameters.iridescenceThicknessMap ? '#define USE_IRIDESCENCE_THICKNESSMAP' : '', + } - parameters.specularMap ? '#define USE_SPECULARMAP' : '', - parameters.specularColorMap ? '#define USE_SPECULAR_COLORMAP' : '', - parameters.specularIntensityMap ? '#define USE_SPECULAR_INTENSITYMAP' : '', + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {IcosahedronGeometry} A new instance. + */ + static fromJSON( data ) { - parameters.roughnessMap ? '#define USE_ROUGHNESSMAP' : '', - parameters.metalnessMap ? '#define USE_METALNESSMAP' : '', + return new IcosahedronGeometry( data.radius, data.detail ); - parameters.alphaMap ? '#define USE_ALPHAMAP' : '', - parameters.alphaTest ? '#define USE_ALPHATEST' : '', + } - parameters.sheen ? '#define USE_SHEEN' : '', - parameters.sheenColorMap ? '#define USE_SHEEN_COLORMAP' : '', - parameters.sheenRoughnessMap ? '#define USE_SHEEN_ROUGHNESSMAP' : '', +} - parameters.transmission ? '#define USE_TRANSMISSION' : '', - parameters.transmissionMap ? '#define USE_TRANSMISSIONMAP' : '', - parameters.thicknessMap ? '#define USE_THICKNESSMAP' : '', +/** + * Creates meshes with axial symmetry like vases. The lathe rotates around the Y axis. + * + * ```js + * const points = []; + * for ( let i = 0; i < 10; i ++ ) { + * points.push( new THREE.Vector2( Math.sin( i * 0.2 ) * 10 + 5, ( i - 5 ) * 2 ) ); + * } + * const geometry = new THREE.LatheGeometry( points ); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + * const lathe = new THREE.Mesh( geometry, material ); + * scene.add( lathe ); + * ``` + * + * @augments BufferGeometry + */ +class LatheGeometry extends BufferGeometry { - parameters.vertexTangents ? '#define USE_TANGENT' : '', - parameters.vertexColors || parameters.instancingColor ? '#define USE_COLOR' : '', - parameters.vertexAlphas ? '#define USE_COLOR_ALPHA' : '', - parameters.vertexUv1s ? '#define USE_UV1' : '', - parameters.vertexUv2s ? '#define USE_UV2' : '', - parameters.vertexUv3s ? '#define USE_UV3' : '', + /** + * Constructs a new lathe geometry. + * + * @param {Array} [points] - An array of points in 2D space. The x-coordinate of each point + * must be greater than zero. + * @param {number} [segments=12] - The number of circumference segments to generate. + * @param {number} [phiStart=0] - The starting angle in radians. + * @param {number} [phiLength=Math.PI*2] - The radian (0 to 2PI) range of the lathed section 2PI is a + * closed lathe, less than 2PI is a portion. + */ + constructor( points = [ new Vector2( 0, -0.5 ), new Vector2( 0.5, 0 ), new Vector2( 0, 0.5 ) ], segments = 12, phiStart = 0, phiLength = Math.PI * 2 ) { - parameters.pointsUvs ? '#define USE_POINTS_UV' : '', + super(); - parameters.gradientMap ? '#define USE_GRADIENTMAP' : '', + this.type = 'LatheGeometry'; - parameters.flatShading ? '#define FLAT_SHADED' : '', + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + points: points, + segments: segments, + phiStart: phiStart, + phiLength: phiLength + }; - parameters.doubleSided ? '#define DOUBLE_SIDED' : '', - parameters.flipSided ? '#define FLIP_SIDED' : '', + segments = Math.floor( segments ); - parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '', - parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '', + // clamp phiLength so it's in range of [ 0, 2PI ] - parameters.premultipliedAlpha ? '#define PREMULTIPLIED_ALPHA' : '', + phiLength = clamp( phiLength, 0, Math.PI * 2 ); - parameters.useLegacyLights ? '#define LEGACY_LIGHTS' : '', + // buffers - parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '', - ( parameters.logarithmicDepthBuffer && parameters.rendererExtensionFragDepth ) ? '#define USE_LOGDEPTHBUF_EXT' : '', + const indices = []; + const vertices = []; + const uvs = []; + const initNormals = []; + const normals = []; - 'uniform mat4 viewMatrix;', - 'uniform vec3 cameraPosition;', - 'uniform bool isOrthographic;', + // helper variables - ( parameters.toneMapping !== NoToneMapping ) ? '#define TONE_MAPPING' : '', - ( parameters.toneMapping !== NoToneMapping ) ? ShaderChunk[ 'tonemapping_pars_fragment' ] : '', // this code is required here because it is used by the toneMapping() function defined below - ( parameters.toneMapping !== NoToneMapping ) ? getToneMappingFunction( 'toneMapping', parameters.toneMapping ) : '', + const inverseSegments = 1.0 / segments; + const vertex = new Vector3(); + const uv = new Vector2(); + const normal = new Vector3(); + const curNormal = new Vector3(); + const prevNormal = new Vector3(); + let dx = 0; + let dy = 0; - parameters.dithering ? '#define DITHERING' : '', - parameters.opaque ? '#define OPAQUE' : '', + // pre-compute normals for initial "meridian" - ShaderChunk[ 'encodings_pars_fragment' ], // this code is required here because it is used by the various encoding/decoding function defined below - getTexelEncodingFunction( 'linearToOutputTexel', parameters.outputColorSpace ), + for ( let j = 0; j <= ( points.length - 1 ); j ++ ) { - parameters.useDepthPacking ? '#define DEPTH_PACKING ' + parameters.depthPacking : '', + switch ( j ) { - '\n' + case 0: // special handling for 1st vertex on path - ].filter( filterEmptyLine ).join( '\n' ); + dx = points[ j + 1 ].x - points[ j ].x; + dy = points[ j + 1 ].y - points[ j ].y; - } + normal.x = dy * 1.0; + normal.y = - dx; + normal.z = dy * 0.0; - vertexShader = resolveIncludes( vertexShader ); - vertexShader = replaceLightNums( vertexShader, parameters ); - vertexShader = replaceClippingPlaneNums( vertexShader, parameters ); + prevNormal.copy( normal ); - fragmentShader = resolveIncludes( fragmentShader ); - fragmentShader = replaceLightNums( fragmentShader, parameters ); - fragmentShader = replaceClippingPlaneNums( fragmentShader, parameters ); + normal.normalize(); - vertexShader = unrollLoops( vertexShader ); - fragmentShader = unrollLoops( fragmentShader ); + initNormals.push( normal.x, normal.y, normal.z ); - if ( parameters.isWebGL2 && parameters.isRawShaderMaterial !== true ) { + break; - // GLSL 3.0 conversion for built-in materials and ShaderMaterial + case ( points.length - 1 ): // special handling for last Vertex on path - versionString = '#version 300 es\n'; + initNormals.push( prevNormal.x, prevNormal.y, prevNormal.z ); - prefixVertex = [ - 'precision mediump sampler2DArray;', - '#define attribute in', - '#define varying out', - '#define texture2D texture' - ].join( '\n' ) + '\n' + prefixVertex; + break; - prefixFragment = [ - '#define varying in', - ( parameters.glslVersion === GLSL3 ) ? '' : 'layout(location = 0) out highp vec4 pc_fragColor;', - ( parameters.glslVersion === GLSL3 ) ? '' : '#define gl_FragColor pc_fragColor', - '#define gl_FragDepthEXT gl_FragDepth', - '#define texture2D texture', - '#define textureCube texture', - '#define texture2DProj textureProj', - '#define texture2DLodEXT textureLod', - '#define texture2DProjLodEXT textureProjLod', - '#define textureCubeLodEXT textureLod', - '#define texture2DGradEXT textureGrad', - '#define texture2DProjGradEXT textureProjGrad', - '#define textureCubeGradEXT textureGrad' - ].join( '\n' ) + '\n' + prefixFragment; + default: // default handling for all vertices in between - } + dx = points[ j + 1 ].x - points[ j ].x; + dy = points[ j + 1 ].y - points[ j ].y; - const vertexGlsl = versionString + prefixVertex + vertexShader; - const fragmentGlsl = versionString + prefixFragment + fragmentShader; + normal.x = dy * 1.0; + normal.y = - dx; + normal.z = dy * 0.0; - // console.log( '*VERTEX*', vertexGlsl ); - // console.log( '*FRAGMENT*', fragmentGlsl ); + curNormal.copy( normal ); - const glVertexShader = WebGLShader( gl, gl.VERTEX_SHADER, vertexGlsl ); - const glFragmentShader = WebGLShader( gl, gl.FRAGMENT_SHADER, fragmentGlsl ); + normal.x += prevNormal.x; + normal.y += prevNormal.y; + normal.z += prevNormal.z; - gl.attachShader( program, glVertexShader ); - gl.attachShader( program, glFragmentShader ); + normal.normalize(); - // Force a particular attribute to index 0. + initNormals.push( normal.x, normal.y, normal.z ); - if ( parameters.index0AttributeName !== undefined ) { + prevNormal.copy( curNormal ); - gl.bindAttribLocation( program, 0, parameters.index0AttributeName ); + } - } else if ( parameters.morphTargets === true ) { + } - // programs with morphTargets displace position out of attribute 0 - gl.bindAttribLocation( program, 0, 'position' ); + // generate vertices, uvs and normals - } + for ( let i = 0; i <= segments; i ++ ) { - gl.linkProgram( program ); + const phi = phiStart + i * inverseSegments * phiLength; - // check for link errors - if ( renderer.debug.checkShaderErrors ) { + const sin = Math.sin( phi ); + const cos = Math.cos( phi ); - const programLog = gl.getProgramInfoLog( program ).trim(); - const vertexLog = gl.getShaderInfoLog( glVertexShader ).trim(); - const fragmentLog = gl.getShaderInfoLog( glFragmentShader ).trim(); + for ( let j = 0; j <= ( points.length - 1 ); j ++ ) { - let runnable = true; - let haveDiagnostics = true; + // vertex - if ( gl.getProgramParameter( program, gl.LINK_STATUS ) === false ) { + vertex.x = points[ j ].x * sin; + vertex.y = points[ j ].y; + vertex.z = points[ j ].x * cos; - runnable = false; + vertices.push( vertex.x, vertex.y, vertex.z ); - if ( typeof renderer.debug.onShaderError === 'function' ) { + // uv - renderer.debug.onShaderError( gl, program, glVertexShader, glFragmentShader ); + uv.x = i / segments; + uv.y = j / ( points.length - 1 ); - } else { + uvs.push( uv.x, uv.y ); - // default error reporting + // normal - const vertexErrors = getShaderErrors( gl, glVertexShader, 'vertex' ); - const fragmentErrors = getShaderErrors( gl, glFragmentShader, 'fragment' ); + const x = initNormals[ 3 * j + 0 ] * sin; + const y = initNormals[ 3 * j + 1 ]; + const z = initNormals[ 3 * j + 0 ] * cos; - console.error( - 'THREE.WebGLProgram: Shader Error ' + gl.getError() + ' - ' + - 'VALIDATE_STATUS ' + gl.getProgramParameter( program, gl.VALIDATE_STATUS ) + '\n\n' + - 'Program Info Log: ' + programLog + '\n' + - vertexErrors + '\n' + - fragmentErrors - ); + normals.push( x, y, z ); } - } else if ( programLog !== '' ) { + } - console.warn( 'THREE.WebGLProgram: Program Info Log:', programLog ); + // indices - } else if ( vertexLog === '' || fragmentLog === '' ) { + for ( let i = 0; i < segments; i ++ ) { - haveDiagnostics = false; + for ( let j = 0; j < ( points.length - 1 ); j ++ ) { - } + const base = j + i * points.length; - if ( haveDiagnostics ) { + const a = base; + const b = base + points.length; + const c = base + points.length + 1; + const d = base + 1; - this.diagnostics = { + // faces - runnable: runnable, + indices.push( a, b, d ); + indices.push( c, d, b ); - programLog: programLog, + } - vertexShader: { + } - log: vertexLog, - prefix: prefixVertex + // build geometry - }, + this.setIndex( indices ); + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - fragmentShader: { + } - log: fragmentLog, - prefix: prefixFragment + copy( source ) { - } + super.copy( source ); - }; + this.parameters = Object.assign( {}, source.parameters ); - } + return this; } - // Clean up - - // Crashes in iOS9 and iOS10. #18402 - // gl.detachShader( program, glVertexShader ); - // gl.detachShader( program, glFragmentShader ); + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {LatheGeometry} A new instance. + */ + static fromJSON( data ) { - gl.deleteShader( glVertexShader ); - gl.deleteShader( glFragmentShader ); + return new LatheGeometry( data.points, data.segments, data.phiStart, data.phiLength ); - // set up caching for uniform locations + } - let cachedUniforms; +} - this.getUniforms = function () { +/** + * A geometry class for representing an octahedron. + * + * ```js + * const geometry = new THREE.OctahedronGeometry(); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + * const octahedron = new THREE.Mesh( geometry, material ); + * scene.add( octahedron ); + * ``` + * + * @augments PolyhedronGeometry + */ +class OctahedronGeometry extends PolyhedronGeometry { - if ( cachedUniforms === undefined ) { + /** + * Constructs a new octahedron geometry. + * + * @param {number} [radius=1] - Radius of the octahedron. + * @param {number} [detail=0] - Setting this to a value greater than `0` adds vertices making it no longer a octahedron. + */ + constructor( radius = 1, detail = 0 ) { - cachedUniforms = new WebGLUniforms( gl, program ); + const vertices = [ + 1, 0, 0, -1, 0, 0, 0, 1, 0, + 0, -1, 0, 0, 0, 1, 0, 0, -1 + ]; - } + const indices = [ + 0, 2, 4, 0, 4, 3, 0, 3, 5, + 0, 5, 2, 1, 2, 5, 1, 5, 3, + 1, 3, 4, 1, 4, 2 + ]; - return cachedUniforms; + super( vertices, indices, radius, detail ); - }; + this.type = 'OctahedronGeometry'; - // set up caching for attribute locations + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + radius: radius, + detail: detail + }; - let cachedAttributes; + } - this.getAttributes = function () { + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {OctahedronGeometry} A new instance. + */ + static fromJSON( data ) { - if ( cachedAttributes === undefined ) { + return new OctahedronGeometry( data.radius, data.detail ); - cachedAttributes = fetchAttributeLocations( gl, program ); + } - } +} - return cachedAttributes; +/** + * A geometry class for representing a plane. + * + * ```js + * const geometry = new THREE.PlaneGeometry( 1, 1 ); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00, side: THREE.DoubleSide } ); + * const plane = new THREE.Mesh( geometry, material ); + * scene.add( plane ); + * ``` + * + * @augments BufferGeometry + */ +class PlaneGeometry extends BufferGeometry { - }; + /** + * Constructs a new plane geometry. + * + * @param {number} [width=1] - The width along the X axis. + * @param {number} [height=1] - The height along the Y axis + * @param {number} [widthSegments=1] - The number of segments along the X axis. + * @param {number} [heightSegments=1] - The number of segments along the Y axis. + */ + constructor( width = 1, height = 1, widthSegments = 1, heightSegments = 1 ) { - // free resource + super(); - this.destroy = function () { + this.type = 'PlaneGeometry'; - bindingStates.releaseStatesOfProgram( this ); + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + width: width, + height: height, + widthSegments: widthSegments, + heightSegments: heightSegments + }; - gl.deleteProgram( program ); - this.program = undefined; + const width_half = width / 2; + const height_half = height / 2; - }; + const gridX = Math.floor( widthSegments ); + const gridY = Math.floor( heightSegments ); - // + const gridX1 = gridX + 1; + const gridY1 = gridY + 1; - this.name = parameters.shaderName; - this.id = programIdCount ++; - this.cacheKey = cacheKey; - this.usedTimes = 1; - this.program = program; - this.vertexShader = glVertexShader; - this.fragmentShader = glFragmentShader; + const segment_width = width / gridX; + const segment_height = height / gridY; - return this; + // -} + const indices = []; + const vertices = []; + const normals = []; + const uvs = []; -let _id = 0; + for ( let iy = 0; iy < gridY1; iy ++ ) { -class WebGLShaderCache { + const y = iy * segment_height - height_half; - constructor() { + for ( let ix = 0; ix < gridX1; ix ++ ) { - this.shaderCache = new Map(); - this.materialCache = new Map(); + const x = ix * segment_width - width_half; - } + vertices.push( x, - y, 0 ); - update( material ) { + normals.push( 0, 0, 1 ); - const vertexShader = material.vertexShader; - const fragmentShader = material.fragmentShader; + uvs.push( ix / gridX ); + uvs.push( 1 - ( iy / gridY ) ); - const vertexShaderStage = this._getShaderStage( vertexShader ); - const fragmentShaderStage = this._getShaderStage( fragmentShader ); + } - const materialShaders = this._getShaderCacheForMaterial( material ); + } - if ( materialShaders.has( vertexShaderStage ) === false ) { + for ( let iy = 0; iy < gridY; iy ++ ) { - materialShaders.add( vertexShaderStage ); - vertexShaderStage.usedTimes ++; + for ( let ix = 0; ix < gridX; ix ++ ) { - } + const a = ix + gridX1 * iy; + const b = ix + gridX1 * ( iy + 1 ); + const c = ( ix + 1 ) + gridX1 * ( iy + 1 ); + const d = ( ix + 1 ) + gridX1 * iy; - if ( materialShaders.has( fragmentShaderStage ) === false ) { + indices.push( a, b, d ); + indices.push( b, c, d ); - materialShaders.add( fragmentShaderStage ); - fragmentShaderStage.usedTimes ++; + } } - return this; + this.setIndex( indices ); + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); } - remove( material ) { - - const materialShaders = this.materialCache.get( material ); + copy( source ) { - for ( const shaderStage of materialShaders ) { + super.copy( source ); - shaderStage.usedTimes --; + this.parameters = Object.assign( {}, source.parameters ); - if ( shaderStage.usedTimes === 0 ) this.shaderCache.delete( shaderStage.code ); + return this; - } + } - this.materialCache.delete( material ); + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {PlaneGeometry} A new instance. + */ + static fromJSON( data ) { - return this; + return new PlaneGeometry( data.width, data.height, data.widthSegments, data.heightSegments ); } - getVertexShaderID( material ) { +} - return this._getShaderStage( material.vertexShader ).id; +/** + * A class for generating a two-dimensional ring geometry. + * + * ```js + * const geometry = new THREE.RingGeometry( 1, 5, 32 ); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00, side: THREE.DoubleSide } ); + * const mesh = new THREE.Mesh( geometry, material ); + * scene.add( mesh ); + * ``` + * + * @augments BufferGeometry + */ +class RingGeometry extends BufferGeometry { - } + /** + * Constructs a new ring geometry. + * + * @param {number} [innerRadius=0.5] - The inner radius of the ring. + * @param {number} [outerRadius=1] - The outer radius of the ring. + * @param {number} [thetaSegments=32] - Number of segments. A higher number means the ring will be more round. Minimum is `3`. + * @param {number} [phiSegments=1] - Number of segments per ring segment. Minimum is `1`. + * @param {number} [thetaStart=0] - Starting angle in radians. + * @param {number} [thetaLength=Math.PI*2] - Central angle in radians. + */ + constructor( innerRadius = 0.5, outerRadius = 1, thetaSegments = 32, phiSegments = 1, thetaStart = 0, thetaLength = Math.PI * 2 ) { - getFragmentShaderID( material ) { + super(); - return this._getShaderStage( material.fragmentShader ).id; + this.type = 'RingGeometry'; - } + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + innerRadius: innerRadius, + outerRadius: outerRadius, + thetaSegments: thetaSegments, + phiSegments: phiSegments, + thetaStart: thetaStart, + thetaLength: thetaLength + }; - dispose() { + thetaSegments = Math.max( 3, thetaSegments ); + phiSegments = Math.max( 1, phiSegments ); - this.shaderCache.clear(); - this.materialCache.clear(); + // buffers - } + const indices = []; + const vertices = []; + const normals = []; + const uvs = []; - _getShaderCacheForMaterial( material ) { + // some helper variables - const cache = this.materialCache; - let set = cache.get( material ); + let radius = innerRadius; + const radiusStep = ( ( outerRadius - innerRadius ) / phiSegments ); + const vertex = new Vector3(); + const uv = new Vector2(); - if ( set === undefined ) { + // generate vertices, normals and uvs - set = new Set(); - cache.set( material, set ); + for ( let j = 0; j <= phiSegments; j ++ ) { - } + for ( let i = 0; i <= thetaSegments; i ++ ) { - return set; + // values are generate from the inside of the ring to the outside - } + const segment = thetaStart + i / thetaSegments * thetaLength; - _getShaderStage( code ) { + // vertex - const cache = this.shaderCache; - let stage = cache.get( code ); + vertex.x = radius * Math.cos( segment ); + vertex.y = radius * Math.sin( segment ); - if ( stage === undefined ) { + vertices.push( vertex.x, vertex.y, vertex.z ); - stage = new WebGLShaderStage( code ); - cache.set( code, stage ); + // normal - } + normals.push( 0, 0, 1 ); - return stage; + // uv - } + uv.x = ( vertex.x / outerRadius + 1 ) / 2; + uv.y = ( vertex.y / outerRadius + 1 ) / 2; -} + uvs.push( uv.x, uv.y ); -class WebGLShaderStage { + } - constructor( code ) { + // increase the radius for next row of vertices - this.id = _id ++; + radius += radiusStep; - this.code = code; - this.usedTimes = 0; + } - } + // indices -} + for ( let j = 0; j < phiSegments; j ++ ) { -function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities, bindingStates, clipping ) { + const thetaSegmentLevel = j * ( thetaSegments + 1 ); - const _programLayers = new Layers(); - const _customShaders = new WebGLShaderCache(); - const programs = []; + for ( let i = 0; i < thetaSegments; i ++ ) { - const IS_WEBGL2 = capabilities.isWebGL2; - const logarithmicDepthBuffer = capabilities.logarithmicDepthBuffer; - const SUPPORTS_VERTEX_TEXTURES = capabilities.vertexTextures; + const segment = i + thetaSegmentLevel; - let precision = capabilities.precision; + const a = segment; + const b = segment + thetaSegments + 1; + const c = segment + thetaSegments + 2; + const d = segment + 1; - const shaderIDs = { - MeshDepthMaterial: 'depth', - MeshDistanceMaterial: 'distanceRGBA', - MeshNormalMaterial: 'normal', - MeshBasicMaterial: 'basic', - MeshLambertMaterial: 'lambert', - MeshPhongMaterial: 'phong', - MeshToonMaterial: 'toon', - MeshStandardMaterial: 'physical', - MeshPhysicalMaterial: 'physical', - MeshMatcapMaterial: 'matcap', - LineBasicMaterial: 'basic', - LineDashedMaterial: 'dashed', - PointsMaterial: 'points', - ShadowMaterial: 'shadow', - SpriteMaterial: 'sprite' - }; + // faces - function getChannel( value ) { + indices.push( a, b, d ); + indices.push( b, c, d ); - if ( value === 0 ) return 'uv'; + } - return `uv${ value }`; + } + + // build geometry + + this.setIndex( indices ); + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); } - function getParameters( material, lights, shadows, scene, object ) { + copy( source ) { - const fog = scene.fog; - const geometry = object.geometry; - const environment = material.isMeshStandardMaterial ? scene.environment : null; + super.copy( source ); - const envMap = ( material.isMeshStandardMaterial ? cubeuvmaps : cubemaps ).get( material.envMap || environment ); - const envMapCubeUVHeight = ( !! envMap ) && ( envMap.mapping === CubeUVReflectionMapping ) ? envMap.image.height : null; + this.parameters = Object.assign( {}, source.parameters ); - const shaderID = shaderIDs[ material.type ]; + return this; - // heuristics to create shader parameters according to lights in the scene - // (not to blow over maxLights budget) + } - if ( material.precision !== null ) { + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {RingGeometry} A new instance. + */ + static fromJSON( data ) { - precision = capabilities.getMaxPrecision( material.precision ); + return new RingGeometry( data.innerRadius, data.outerRadius, data.thetaSegments, data.phiSegments, data.thetaStart, data.thetaLength ); - if ( precision !== material.precision ) { + } - console.warn( 'THREE.WebGLProgram.getParameters:', material.precision, 'not supported, using', precision, 'instead.' ); +} - } +/** + * Creates an one-sided polygonal geometry from one or more path shapes. + * + * ```js + * const arcShape = new THREE.Shape() + * .moveTo( 5, 1 ) + * .absarc( 1, 1, 4, 0, Math.PI * 2, false ); + * + * const geometry = new THREE.ShapeGeometry( arcShape ); + * const material = new THREE.MeshBasicMaterial( { color: 0x00ff00, side: THREE.DoubleSide } ); + * const mesh = new THREE.Mesh( geometry, material ) ; + * scene.add( mesh ); + * ``` + * + * @augments BufferGeometry + */ +class ShapeGeometry extends BufferGeometry { - } + /** + * Constructs a new shape geometry. + * + * @param {Shape|Array} [shapes] - A shape or an array of shapes. + * @param {number} [curveSegments=12] - Number of segments per shape. + */ + constructor( shapes = new Shape( [ new Vector2( 0, 0.5 ), new Vector2( -0.5, -0.5 ), new Vector2( 0.5, -0.5 ) ] ), curveSegments = 12 ) { - // + super(); - const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color; - const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0; + this.type = 'ShapeGeometry'; - let morphTextureStride = 0; + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + shapes: shapes, + curveSegments: curveSegments + }; - if ( geometry.morphAttributes.position !== undefined ) morphTextureStride = 1; - if ( geometry.morphAttributes.normal !== undefined ) morphTextureStride = 2; - if ( geometry.morphAttributes.color !== undefined ) morphTextureStride = 3; + // buffers - // + const indices = []; + const vertices = []; + const normals = []; + const uvs = []; - let vertexShader, fragmentShader; - let customVertexShaderID, customFragmentShaderID; + // helper variables - if ( shaderID ) { + let groupStart = 0; + let groupCount = 0; - const shader = ShaderLib[ shaderID ]; + // allow single and array values for "shapes" parameter - vertexShader = shader.vertexShader; - fragmentShader = shader.fragmentShader; + if ( Array.isArray( shapes ) === false ) { + + addShape( shapes ); } else { - vertexShader = material.vertexShader; - fragmentShader = material.fragmentShader; + for ( let i = 0; i < shapes.length; i ++ ) { - _customShaders.update( material ); + addShape( shapes[ i ] ); - customVertexShaderID = _customShaders.getVertexShaderID( material ); - customFragmentShaderID = _customShaders.getFragmentShaderID( material ); + this.addGroup( groupStart, groupCount, i ); // enables MultiMaterial support + + groupStart += groupCount; + groupCount = 0; + + } } - const currentRenderTarget = renderer.getRenderTarget(); + // build geometry - const IS_INSTANCEDMESH = object.isInstancedMesh === true; + this.setIndex( indices ); + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - const HAS_MAP = !! material.map; - const HAS_MATCAP = !! material.matcap; - const HAS_ENVMAP = !! envMap; - const HAS_AOMAP = !! material.aoMap; - const HAS_LIGHTMAP = !! material.lightMap; - const HAS_BUMPMAP = !! material.bumpMap; - const HAS_NORMALMAP = !! material.normalMap; - const HAS_DISPLACEMENTMAP = !! material.displacementMap; - const HAS_EMISSIVEMAP = !! material.emissiveMap; - const HAS_METALNESSMAP = !! material.metalnessMap; - const HAS_ROUGHNESSMAP = !! material.roughnessMap; + // helper functions - const HAS_ANISOTROPY = material.anisotropy > 0; - const HAS_CLEARCOAT = material.clearcoat > 0; - const HAS_IRIDESCENCE = material.iridescence > 0; - const HAS_SHEEN = material.sheen > 0; - const HAS_TRANSMISSION = material.transmission > 0; + function addShape( shape ) { - const HAS_ANISOTROPYMAP = HAS_ANISOTROPY && !! material.anisotropyMap; + const indexOffset = vertices.length / 3; + const points = shape.extractPoints( curveSegments ); - const HAS_CLEARCOATMAP = HAS_CLEARCOAT && !! material.clearcoatMap; - const HAS_CLEARCOAT_NORMALMAP = HAS_CLEARCOAT && !! material.clearcoatNormalMap; - const HAS_CLEARCOAT_ROUGHNESSMAP = HAS_CLEARCOAT && !! material.clearcoatRoughnessMap; + let shapeVertices = points.shape; + const shapeHoles = points.holes; - const HAS_IRIDESCENCEMAP = HAS_IRIDESCENCE && !! material.iridescenceMap; - const HAS_IRIDESCENCE_THICKNESSMAP = HAS_IRIDESCENCE && !! material.iridescenceThicknessMap; + // check direction of vertices - const HAS_SHEEN_COLORMAP = HAS_SHEEN && !! material.sheenColorMap; - const HAS_SHEEN_ROUGHNESSMAP = HAS_SHEEN && !! material.sheenRoughnessMap; + if ( ShapeUtils.isClockWise( shapeVertices ) === false ) { - const HAS_SPECULARMAP = !! material.specularMap; - const HAS_SPECULAR_COLORMAP = !! material.specularColorMap; - const HAS_SPECULAR_INTENSITYMAP = !! material.specularIntensityMap; + shapeVertices = shapeVertices.reverse(); - const HAS_TRANSMISSIONMAP = HAS_TRANSMISSION && !! material.transmissionMap; - const HAS_THICKNESSMAP = HAS_TRANSMISSION && !! material.thicknessMap; + } - const HAS_GRADIENTMAP = !! material.gradientMap; + for ( let i = 0, l = shapeHoles.length; i < l; i ++ ) { - const HAS_ALPHAMAP = !! material.alphaMap; + const shapeHole = shapeHoles[ i ]; - const HAS_ALPHATEST = material.alphaTest > 0; + if ( ShapeUtils.isClockWise( shapeHole ) === true ) { - const HAS_EXTENSIONS = !! material.extensions; + shapeHoles[ i ] = shapeHole.reverse(); - const HAS_ATTRIBUTE_UV1 = !! geometry.attributes.uv1; - const HAS_ATTRIBUTE_UV2 = !! geometry.attributes.uv2; - const HAS_ATTRIBUTE_UV3 = !! geometry.attributes.uv3; + } - const parameters = { + } - isWebGL2: IS_WEBGL2, + const faces = ShapeUtils.triangulateShape( shapeVertices, shapeHoles ); - shaderID: shaderID, - shaderName: material.type, + // join vertices of inner and outer paths to a single array - vertexShader: vertexShader, - fragmentShader: fragmentShader, - defines: material.defines, + for ( let i = 0, l = shapeHoles.length; i < l; i ++ ) { - customVertexShaderID: customVertexShaderID, - customFragmentShaderID: customFragmentShaderID, + const shapeHole = shapeHoles[ i ]; + shapeVertices = shapeVertices.concat( shapeHole ); - isRawShaderMaterial: material.isRawShaderMaterial === true, - glslVersion: material.glslVersion, + } - precision: precision, + // vertices, normals, uvs - instancing: IS_INSTANCEDMESH, - instancingColor: IS_INSTANCEDMESH && object.instanceColor !== null, + for ( let i = 0, l = shapeVertices.length; i < l; i ++ ) { + + const vertex = shapeVertices[ i ]; - supportsVertexTextures: SUPPORTS_VERTEX_TEXTURES, - outputColorSpace: ( currentRenderTarget === null ) ? renderer.outputColorSpace : ( currentRenderTarget.isXRRenderTarget === true ? currentRenderTarget.texture.colorSpace : LinearSRGBColorSpace ), + vertices.push( vertex.x, vertex.y, 0 ); + normals.push( 0, 0, 1 ); + uvs.push( vertex.x, vertex.y ); // world uvs - map: HAS_MAP, - matcap: HAS_MATCAP, - envMap: HAS_ENVMAP, - envMapMode: HAS_ENVMAP && envMap.mapping, - envMapCubeUVHeight: envMapCubeUVHeight, - aoMap: HAS_AOMAP, - lightMap: HAS_LIGHTMAP, - bumpMap: HAS_BUMPMAP, - normalMap: HAS_NORMALMAP, - displacementMap: SUPPORTS_VERTEX_TEXTURES && HAS_DISPLACEMENTMAP, - emissiveMap: HAS_EMISSIVEMAP, + } - normalMapObjectSpace: HAS_NORMALMAP && material.normalMapType === ObjectSpaceNormalMap, - normalMapTangentSpace: HAS_NORMALMAP && material.normalMapType === TangentSpaceNormalMap, + // indices - metalnessMap: HAS_METALNESSMAP, - roughnessMap: HAS_ROUGHNESSMAP, + for ( let i = 0, l = faces.length; i < l; i ++ ) { - anisotropy: HAS_ANISOTROPY, - anisotropyMap: HAS_ANISOTROPYMAP, + const face = faces[ i ]; - clearcoat: HAS_CLEARCOAT, - clearcoatMap: HAS_CLEARCOATMAP, - clearcoatNormalMap: HAS_CLEARCOAT_NORMALMAP, - clearcoatRoughnessMap: HAS_CLEARCOAT_ROUGHNESSMAP, + const a = face[ 0 ] + indexOffset; + const b = face[ 1 ] + indexOffset; + const c = face[ 2 ] + indexOffset; - iridescence: HAS_IRIDESCENCE, - iridescenceMap: HAS_IRIDESCENCEMAP, - iridescenceThicknessMap: HAS_IRIDESCENCE_THICKNESSMAP, + indices.push( a, b, c ); + groupCount += 3; - sheen: HAS_SHEEN, - sheenColorMap: HAS_SHEEN_COLORMAP, - sheenRoughnessMap: HAS_SHEEN_ROUGHNESSMAP, + } - specularMap: HAS_SPECULARMAP, - specularColorMap: HAS_SPECULAR_COLORMAP, - specularIntensityMap: HAS_SPECULAR_INTENSITYMAP, + } - transmission: HAS_TRANSMISSION, - transmissionMap: HAS_TRANSMISSIONMAP, - thicknessMap: HAS_THICKNESSMAP, + } - gradientMap: HAS_GRADIENTMAP, + copy( source ) { - opaque: material.transparent === false && material.blending === NormalBlending, + super.copy( source ); - alphaMap: HAS_ALPHAMAP, - alphaTest: HAS_ALPHATEST, + this.parameters = Object.assign( {}, source.parameters ); - combine: material.combine, + return this; - // + } - mapUv: HAS_MAP && getChannel( material.map.channel ), - aoMapUv: HAS_AOMAP && getChannel( material.aoMap.channel ), - lightMapUv: HAS_LIGHTMAP && getChannel( material.lightMap.channel ), - bumpMapUv: HAS_BUMPMAP && getChannel( material.bumpMap.channel ), - normalMapUv: HAS_NORMALMAP && getChannel( material.normalMap.channel ), - displacementMapUv: HAS_DISPLACEMENTMAP && getChannel( material.displacementMap.channel ), - emissiveMapUv: HAS_EMISSIVEMAP && getChannel( material.emissiveMap.channel ), + toJSON() { - metalnessMapUv: HAS_METALNESSMAP && getChannel( material.metalnessMap.channel ), - roughnessMapUv: HAS_ROUGHNESSMAP && getChannel( material.roughnessMap.channel ), + const data = super.toJSON(); - anisotropyMapUv: HAS_ANISOTROPYMAP && getChannel( material.anisotropyMap.channel ), + const shapes = this.parameters.shapes; - clearcoatMapUv: HAS_CLEARCOATMAP && getChannel( material.clearcoatMap.channel ), - clearcoatNormalMapUv: HAS_CLEARCOAT_NORMALMAP && getChannel( material.clearcoatNormalMap.channel ), - clearcoatRoughnessMapUv: HAS_CLEARCOAT_ROUGHNESSMAP && getChannel( material.clearcoatRoughnessMap.channel ), + return toJSON( shapes, data ); - iridescenceMapUv: HAS_IRIDESCENCEMAP && getChannel( material.iridescenceMap.channel ), - iridescenceThicknessMapUv: HAS_IRIDESCENCE_THICKNESSMAP && getChannel( material.iridescenceThicknessMap.channel ), + } - sheenColorMapUv: HAS_SHEEN_COLORMAP && getChannel( material.sheenColorMap.channel ), - sheenRoughnessMapUv: HAS_SHEEN_ROUGHNESSMAP && getChannel( material.sheenRoughnessMap.channel ), + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @param {Array} shapes - An array of shapes. + * @return {ShapeGeometry} A new instance. + */ + static fromJSON( data, shapes ) { - specularMapUv: HAS_SPECULARMAP && getChannel( material.specularMap.channel ), - specularColorMapUv: HAS_SPECULAR_COLORMAP && getChannel( material.specularColorMap.channel ), - specularIntensityMapUv: HAS_SPECULAR_INTENSITYMAP && getChannel( material.specularIntensityMap.channel ), + const geometryShapes = []; - transmissionMapUv: HAS_TRANSMISSIONMAP && getChannel( material.transmissionMap.channel ), - thicknessMapUv: HAS_THICKNESSMAP && getChannel( material.thicknessMap.channel ), + for ( let j = 0, jl = data.shapes.length; j < jl; j ++ ) { - alphaMapUv: HAS_ALPHAMAP && getChannel( material.alphaMap.channel ), + const shape = shapes[ data.shapes[ j ] ]; - // + geometryShapes.push( shape ); - vertexTangents: !! geometry.attributes.tangent && ( HAS_NORMALMAP || HAS_ANISOTROPY ), - vertexColors: material.vertexColors, - vertexAlphas: material.vertexColors === true && !! geometry.attributes.color && geometry.attributes.color.itemSize === 4, - vertexUv1s: HAS_ATTRIBUTE_UV1, - vertexUv2s: HAS_ATTRIBUTE_UV2, - vertexUv3s: HAS_ATTRIBUTE_UV3, + } - pointsUvs: object.isPoints === true && !! geometry.attributes.uv && ( HAS_MAP || HAS_ALPHAMAP ), + return new ShapeGeometry( geometryShapes, data.curveSegments ); - fog: !! fog, - useFog: material.fog === true, - fogExp2: ( fog && fog.isFogExp2 ), + } - flatShading: material.flatShading === true, +} - sizeAttenuation: material.sizeAttenuation === true, - logarithmicDepthBuffer: logarithmicDepthBuffer, +function toJSON( shapes, data ) { - skinning: object.isSkinnedMesh === true, + data.shapes = []; - morphTargets: geometry.morphAttributes.position !== undefined, - morphNormals: geometry.morphAttributes.normal !== undefined, - morphColors: geometry.morphAttributes.color !== undefined, - morphTargetsCount: morphTargetsCount, - morphTextureStride: morphTextureStride, + if ( Array.isArray( shapes ) ) { - numDirLights: lights.directional.length, - numPointLights: lights.point.length, - numSpotLights: lights.spot.length, - numSpotLightMaps: lights.spotLightMap.length, - numRectAreaLights: lights.rectArea.length, - numHemiLights: lights.hemi.length, + for ( let i = 0, l = shapes.length; i < l; i ++ ) { - numDirLightShadows: lights.directionalShadowMap.length, - numPointLightShadows: lights.pointShadowMap.length, - numSpotLightShadows: lights.spotShadowMap.length, - numSpotLightShadowsWithMaps: lights.numSpotLightShadowsWithMaps, + const shape = shapes[ i ]; - numClippingPlanes: clipping.numPlanes, - numClipIntersection: clipping.numIntersection, + data.shapes.push( shape.uuid ); - dithering: material.dithering, + } - shadowMapEnabled: renderer.shadowMap.enabled && shadows.length > 0, - shadowMapType: renderer.shadowMap.type, + } else { - toneMapping: material.toneMapped ? renderer.toneMapping : NoToneMapping, - useLegacyLights: renderer.useLegacyLights, + data.shapes.push( shapes.uuid ); - premultipliedAlpha: material.premultipliedAlpha, + } - doubleSided: material.side === DoubleSide, - flipSided: material.side === BackSide, + return data; - useDepthPacking: material.depthPacking >= 0, - depthPacking: material.depthPacking || 0, +} - index0AttributeName: material.index0AttributeName, +/** + * A class for generating a sphere geometry. + * + * ```js + * const geometry = new THREE.SphereGeometry( 15, 32, 16 ); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + * const sphere = new THREE.Mesh( geometry, material ); + * scene.add( sphere ); + * ``` + * + * @augments BufferGeometry + */ +class SphereGeometry extends BufferGeometry { - extensionDerivatives: HAS_EXTENSIONS && material.extensions.derivatives === true, - extensionFragDepth: HAS_EXTENSIONS && material.extensions.fragDepth === true, - extensionDrawBuffers: HAS_EXTENSIONS && material.extensions.drawBuffers === true, - extensionShaderTextureLOD: HAS_EXTENSIONS && material.extensions.shaderTextureLOD === true, + /** + * Constructs a new sphere geometry. + * + * @param {number} [radius=1] - The sphere radius. + * @param {number} [widthSegments=32] - The number of horizontal segments. Minimum value is `3`. + * @param {number} [heightSegments=16] - The number of vertical segments. Minimum value is `2`. + * @param {number} [phiStart=0] - The horizontal starting angle in radians. + * @param {number} [phiLength=Math.PI*2] - The horizontal sweep angle size. + * @param {number} [thetaStart=0] - The vertical starting angle in radians. + * @param {number} [thetaLength=Math.PI] - The vertical sweep angle size. + */ + constructor( radius = 1, widthSegments = 32, heightSegments = 16, phiStart = 0, phiLength = Math.PI * 2, thetaStart = 0, thetaLength = Math.PI ) { - rendererExtensionFragDepth: IS_WEBGL2 || extensions.has( 'EXT_frag_depth' ), - rendererExtensionDrawBuffers: IS_WEBGL2 || extensions.has( 'WEBGL_draw_buffers' ), - rendererExtensionShaderTextureLod: IS_WEBGL2 || extensions.has( 'EXT_shader_texture_lod' ), + super(); - customProgramCacheKey: material.customProgramCacheKey() + this.type = 'SphereGeometry'; + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + radius: radius, + widthSegments: widthSegments, + heightSegments: heightSegments, + phiStart: phiStart, + phiLength: phiLength, + thetaStart: thetaStart, + thetaLength: thetaLength }; - return parameters; + widthSegments = Math.max( 3, Math.floor( widthSegments ) ); + heightSegments = Math.max( 2, Math.floor( heightSegments ) ); - } + const thetaEnd = Math.min( thetaStart + thetaLength, Math.PI ); - function getProgramCacheKey( parameters ) { + let index = 0; + const grid = []; - const array = []; + const vertex = new Vector3(); + const normal = new Vector3(); - if ( parameters.shaderID ) { + // buffers - array.push( parameters.shaderID ); + const indices = []; + const vertices = []; + const normals = []; + const uvs = []; - } else { + // generate vertices, normals and uvs - array.push( parameters.customVertexShaderID ); - array.push( parameters.customFragmentShaderID ); + for ( let iy = 0; iy <= heightSegments; iy ++ ) { - } + const verticesRow = []; - if ( parameters.defines !== undefined ) { + const v = iy / heightSegments; - for ( const name in parameters.defines ) { + // special case for the poles - array.push( name ); - array.push( parameters.defines[ name ] ); + let uOffset = 0; - } + if ( iy === 0 && thetaStart === 0 ) { - } + uOffset = 0.5 / widthSegments; - if ( parameters.isRawShaderMaterial === false ) { + } else if ( iy === heightSegments && thetaEnd === Math.PI ) { - getProgramCacheKeyParameters( array, parameters ); - getProgramCacheKeyBooleans( array, parameters ); - array.push( renderer.outputColorSpace ); + uOffset = -0.5 / widthSegments; - } + } - array.push( parameters.customProgramCacheKey ); + for ( let ix = 0; ix <= widthSegments; ix ++ ) { - return array.join(); + const u = ix / widthSegments; - } + // vertex - function getProgramCacheKeyParameters( array, parameters ) { + vertex.x = - radius * Math.cos( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength ); + vertex.y = radius * Math.cos( thetaStart + v * thetaLength ); + vertex.z = radius * Math.sin( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength ); - array.push( parameters.precision ); - array.push( parameters.outputColorSpace ); - array.push( parameters.envMapMode ); - array.push( parameters.envMapCubeUVHeight ); - array.push( parameters.mapUv ); - array.push( parameters.alphaMapUv ); - array.push( parameters.lightMapUv ); - array.push( parameters.aoMapUv ); - array.push( parameters.bumpMapUv ); - array.push( parameters.normalMapUv ); - array.push( parameters.displacementMapUv ); - array.push( parameters.emissiveMapUv ); - array.push( parameters.metalnessMapUv ); - array.push( parameters.roughnessMapUv ); - array.push( parameters.anisotropyMapUv ); - array.push( parameters.clearcoatMapUv ); - array.push( parameters.clearcoatNormalMapUv ); - array.push( parameters.clearcoatRoughnessMapUv ); - array.push( parameters.iridescenceMapUv ); - array.push( parameters.iridescenceThicknessMapUv ); - array.push( parameters.sheenColorMapUv ); - array.push( parameters.sheenRoughnessMapUv ); - array.push( parameters.specularMapUv ); - array.push( parameters.specularColorMapUv ); - array.push( parameters.specularIntensityMapUv ); - array.push( parameters.transmissionMapUv ); - array.push( parameters.thicknessMapUv ); - array.push( parameters.combine ); - array.push( parameters.fogExp2 ); - array.push( parameters.sizeAttenuation ); - array.push( parameters.morphTargetsCount ); - array.push( parameters.morphAttributeCount ); - array.push( parameters.numDirLights ); - array.push( parameters.numPointLights ); - array.push( parameters.numSpotLights ); - array.push( parameters.numSpotLightMaps ); - array.push( parameters.numHemiLights ); - array.push( parameters.numRectAreaLights ); - array.push( parameters.numDirLightShadows ); - array.push( parameters.numPointLightShadows ); - array.push( parameters.numSpotLightShadows ); - array.push( parameters.numSpotLightShadowsWithMaps ); - array.push( parameters.shadowMapType ); - array.push( parameters.toneMapping ); - array.push( parameters.numClippingPlanes ); - array.push( parameters.numClipIntersection ); - array.push( parameters.depthPacking ); + vertices.push( vertex.x, vertex.y, vertex.z ); - } + // normal - function getProgramCacheKeyBooleans( array, parameters ) { + normal.copy( vertex ).normalize(); + normals.push( normal.x, normal.y, normal.z ); - _programLayers.disableAll(); + // uv - if ( parameters.isWebGL2 ) - _programLayers.enable( 0 ); - if ( parameters.supportsVertexTextures ) - _programLayers.enable( 1 ); - if ( parameters.instancing ) - _programLayers.enable( 2 ); - if ( parameters.instancingColor ) - _programLayers.enable( 3 ); - if ( parameters.matcap ) - _programLayers.enable( 4 ); - if ( parameters.envMap ) - _programLayers.enable( 5 ); - if ( parameters.normalMapObjectSpace ) - _programLayers.enable( 6 ); - if ( parameters.normalMapTangentSpace ) - _programLayers.enable( 7 ); - if ( parameters.clearcoat ) - _programLayers.enable( 8 ); - if ( parameters.iridescence ) - _programLayers.enable( 9 ); - if ( parameters.alphaTest ) - _programLayers.enable( 10 ); - if ( parameters.vertexColors ) - _programLayers.enable( 11 ); - if ( parameters.vertexAlphas ) - _programLayers.enable( 12 ); - if ( parameters.vertexUv1s ) - _programLayers.enable( 13 ); - if ( parameters.vertexUv2s ) - _programLayers.enable( 14 ); - if ( parameters.vertexUv3s ) - _programLayers.enable( 15 ); - if ( parameters.vertexTangents ) - _programLayers.enable( 16 ); - if ( parameters.anisotropy ) - _programLayers.enable( 17 ); + uvs.push( u + uOffset, 1 - v ); - array.push( _programLayers.mask ); - _programLayers.disableAll(); + verticesRow.push( index ++ ); - if ( parameters.fog ) - _programLayers.enable( 0 ); - if ( parameters.useFog ) - _programLayers.enable( 1 ); - if ( parameters.flatShading ) - _programLayers.enable( 2 ); - if ( parameters.logarithmicDepthBuffer ) - _programLayers.enable( 3 ); - if ( parameters.skinning ) - _programLayers.enable( 4 ); - if ( parameters.morphTargets ) - _programLayers.enable( 5 ); - if ( parameters.morphNormals ) - _programLayers.enable( 6 ); - if ( parameters.morphColors ) - _programLayers.enable( 7 ); - if ( parameters.premultipliedAlpha ) - _programLayers.enable( 8 ); - if ( parameters.shadowMapEnabled ) - _programLayers.enable( 9 ); - if ( parameters.useLegacyLights ) - _programLayers.enable( 10 ); - if ( parameters.doubleSided ) - _programLayers.enable( 11 ); - if ( parameters.flipSided ) - _programLayers.enable( 12 ); - if ( parameters.useDepthPacking ) - _programLayers.enable( 13 ); - if ( parameters.dithering ) - _programLayers.enable( 14 ); - if ( parameters.transmission ) - _programLayers.enable( 15 ); - if ( parameters.sheen ) - _programLayers.enable( 16 ); - if ( parameters.opaque ) - _programLayers.enable( 17 ); - if ( parameters.pointsUvs ) - _programLayers.enable( 18 ); + } - array.push( _programLayers.mask ); + grid.push( verticesRow ); - } + } - function getUniforms( material ) { + // indices - const shaderID = shaderIDs[ material.type ]; - let uniforms; + for ( let iy = 0; iy < heightSegments; iy ++ ) { - if ( shaderID ) { + for ( let ix = 0; ix < widthSegments; ix ++ ) { - const shader = ShaderLib[ shaderID ]; - uniforms = UniformsUtils.clone( shader.uniforms ); + const a = grid[ iy ][ ix + 1 ]; + const b = grid[ iy ][ ix ]; + const c = grid[ iy + 1 ][ ix ]; + const d = grid[ iy + 1 ][ ix + 1 ]; - } else { + if ( iy !== 0 || thetaStart > 0 ) indices.push( a, b, d ); + if ( iy !== heightSegments - 1 || thetaEnd < Math.PI ) indices.push( b, c, d ); - uniforms = material.uniforms; + } } - return uniforms; + // build geometry + + this.setIndex( indices ); + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); } - function acquireProgram( parameters, cacheKey ) { + copy( source ) { - let program; + super.copy( source ); - // Check if code has been already compiled - for ( let p = 0, pl = programs.length; p < pl; p ++ ) { + this.parameters = Object.assign( {}, source.parameters ); - const preexistingProgram = programs[ p ]; + return this; - if ( preexistingProgram.cacheKey === cacheKey ) { + } - program = preexistingProgram; - ++ program.usedTimes; + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {SphereGeometry} A new instance. + */ + static fromJSON( data ) { - break; + return new SphereGeometry( data.radius, data.widthSegments, data.heightSegments, data.phiStart, data.phiLength, data.thetaStart, data.thetaLength ); - } + } - } +} - if ( program === undefined ) { +/** + * A geometry class for representing an tetrahedron. + * + * ```js + * const geometry = new THREE.TetrahedronGeometry(); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + * const tetrahedron = new THREE.Mesh( geometry, material ); + * scene.add( tetrahedron ); + * ``` + * + * @augments PolyhedronGeometry + */ +class TetrahedronGeometry extends PolyhedronGeometry { - program = new WebGLProgram( renderer, cacheKey, parameters, bindingStates ); - programs.push( program ); + /** + * Constructs a new tetrahedron geometry. + * + * @param {number} [radius=1] - Radius of the tetrahedron. + * @param {number} [detail=0] - Setting this to a value greater than `0` adds vertices making it no longer a tetrahedron. + */ + constructor( radius = 1, detail = 0 ) { - } + const vertices = [ + 1, 1, 1, -1, -1, 1, -1, 1, -1, 1, -1, -1 + ]; - return program; + const indices = [ + 2, 1, 0, 0, 3, 2, 1, 3, 0, 2, 3, 1 + ]; - } + super( vertices, indices, radius, detail ); - function releaseProgram( program ) { + this.type = 'TetrahedronGeometry'; - if ( -- program.usedTimes === 0 ) { + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + radius: radius, + detail: detail + }; - // Remove from unordered set - const i = programs.indexOf( program ); - programs[ i ] = programs[ programs.length - 1 ]; - programs.pop(); + } - // Free WebGL resources - program.destroy(); + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {TetrahedronGeometry} A new instance. + */ + static fromJSON( data ) { - } + return new TetrahedronGeometry( data.radius, data.detail ); } - function releaseShaderCache( material ) { +} - _customShaders.remove( material ); +/** + * A geometry class for representing an torus. + * + * ```js + * const geometry = new THREE.TorusGeometry( 10, 3, 16, 100 ); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + * const torus = new THREE.Mesh( geometry, material ); + * scene.add( torus ); + * ``` + * + * @augments BufferGeometry + */ +class TorusGeometry extends BufferGeometry { - } + /** + * Constructs a new torus geometry. + * + * @param {number} [radius=1] - Radius of the torus, from the center of the torus to the center of the tube. + * @param {number} [tube=0.4] - Radius of the tube. Must be smaller than `radius`. + * @param {number} [radialSegments=12] - The number of radial segments. + * @param {number} [tubularSegments=48] - The number of tubular segments. + * @param {number} [arc=Math.PI*2] - Central angle in radians. + */ + constructor( radius = 1, tube = 0.4, radialSegments = 12, tubularSegments = 48, arc = Math.PI * 2 ) { - function dispose() { + super(); - _customShaders.dispose(); + this.type = 'TorusGeometry'; - } + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + radius: radius, + tube: tube, + radialSegments: radialSegments, + tubularSegments: tubularSegments, + arc: arc + }; - return { - getParameters: getParameters, - getProgramCacheKey: getProgramCacheKey, - getUniforms: getUniforms, - acquireProgram: acquireProgram, - releaseProgram: releaseProgram, - releaseShaderCache: releaseShaderCache, - // Exposed for resource monitoring & error feedback via renderer.info: - programs: programs, - dispose: dispose - }; + radialSegments = Math.floor( radialSegments ); + tubularSegments = Math.floor( tubularSegments ); -} + // buffers -function WebGLProperties() { + const indices = []; + const vertices = []; + const normals = []; + const uvs = []; - let properties = new WeakMap(); + // helper variables + + const center = new Vector3(); + const vertex = new Vector3(); + const normal = new Vector3(); + + // generate vertices, normals and uvs + + for ( let j = 0; j <= radialSegments; j ++ ) { + + for ( let i = 0; i <= tubularSegments; i ++ ) { + + const u = i / tubularSegments * arc; + const v = j / radialSegments * Math.PI * 2; - function get( object ) { + // vertex - let map = properties.get( object ); + vertex.x = ( radius + tube * Math.cos( v ) ) * Math.cos( u ); + vertex.y = ( radius + tube * Math.cos( v ) ) * Math.sin( u ); + vertex.z = tube * Math.sin( v ); - if ( map === undefined ) { + vertices.push( vertex.x, vertex.y, vertex.z ); - map = {}; - properties.set( object, map ); + // normal - } + center.x = radius * Math.cos( u ); + center.y = radius * Math.sin( u ); + normal.subVectors( vertex, center ).normalize(); - return map; + normals.push( normal.x, normal.y, normal.z ); - } + // uv - function remove( object ) { + uvs.push( i / tubularSegments ); + uvs.push( j / radialSegments ); - properties.delete( object ); + } - } + } - function update( object, key, value ) { + // generate indices - properties.get( object )[ key ] = value; + for ( let j = 1; j <= radialSegments; j ++ ) { - } + for ( let i = 1; i <= tubularSegments; i ++ ) { - function dispose() { + // indices - properties = new WeakMap(); + const a = ( tubularSegments + 1 ) * j + i - 1; + const b = ( tubularSegments + 1 ) * ( j - 1 ) + i - 1; + const c = ( tubularSegments + 1 ) * ( j - 1 ) + i; + const d = ( tubularSegments + 1 ) * j + i; - } + // faces - return { - get: get, - remove: remove, - update: update, - dispose: dispose - }; + indices.push( a, b, d ); + indices.push( b, c, d ); -} + } -function painterSortStable( a, b ) { + } - if ( a.groupOrder !== b.groupOrder ) { + // build geometry - return a.groupOrder - b.groupOrder; + this.setIndex( indices ); + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - } else if ( a.renderOrder !== b.renderOrder ) { + } - return a.renderOrder - b.renderOrder; + copy( source ) { - } else if ( a.material.id !== b.material.id ) { + super.copy( source ); - return a.material.id - b.material.id; + this.parameters = Object.assign( {}, source.parameters ); - } else if ( a.z !== b.z ) { + return this; - return a.z - b.z; + } - } else { + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {TorusGeometry} A new instance. + */ + static fromJSON( data ) { - return a.id - b.id; + return new TorusGeometry( data.radius, data.tube, data.radialSegments, data.tubularSegments, data.arc ); } } -function reversePainterSortStable( a, b ) { +/** + * Creates a torus knot, the particular shape of which is defined by a pair + * of coprime integers, p and q. If p and q are not coprime, the result will + * be a torus link. + * + * ```js + * const geometry = new THREE.TorusKnotGeometry( 10, 3, 100, 16 ); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + * const torusKnot = new THREE.Mesh( geometry, material ); + * scene.add( torusKnot ); + * ``` + * + * @augments BufferGeometry + */ +class TorusKnotGeometry extends BufferGeometry { - if ( a.groupOrder !== b.groupOrder ) { + /** + * Constructs a new torus knot geometry. + * + * @param {number} [radius=1] - Radius of the torus knot. + * @param {number} [tube=0.4] - Radius of the tube. + * @param {number} [tubularSegments=64] - The number of tubular segments. + * @param {number} [radialSegments=8] - The number of radial segments. + * @param {number} [p=2] - This value determines, how many times the geometry winds around its axis of rotational symmetry. + * @param {number} [q=3] - This value determines, how many times the geometry winds around a circle in the interior of the torus. + */ + constructor( radius = 1, tube = 0.4, tubularSegments = 64, radialSegments = 8, p = 2, q = 3 ) { - return a.groupOrder - b.groupOrder; + super(); - } else if ( a.renderOrder !== b.renderOrder ) { + this.type = 'TorusKnotGeometry'; - return a.renderOrder - b.renderOrder; + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + radius: radius, + tube: tube, + tubularSegments: tubularSegments, + radialSegments: radialSegments, + p: p, + q: q + }; - } else if ( a.z !== b.z ) { + tubularSegments = Math.floor( tubularSegments ); + radialSegments = Math.floor( radialSegments ); - return b.z - a.z; + // buffers - } else { + const indices = []; + const vertices = []; + const normals = []; + const uvs = []; - return a.id - b.id; + // helper variables - } + const vertex = new Vector3(); + const normal = new Vector3(); -} + const P1 = new Vector3(); + const P2 = new Vector3(); + const B = new Vector3(); + const T = new Vector3(); + const N = new Vector3(); -function WebGLRenderList() { + // generate vertices, normals and uvs - const renderItems = []; - let renderItemsIndex = 0; + for ( let i = 0; i <= tubularSegments; ++ i ) { - const opaque = []; - const transmissive = []; - const transparent = []; + // the radian "u" is used to calculate the position on the torus curve of the current tubular segment - function init() { + const u = i / tubularSegments * p * Math.PI * 2; - renderItemsIndex = 0; + // now we calculate two points. P1 is our current position on the curve, P2 is a little farther ahead. + // these points are used to create a special "coordinate space", which is necessary to calculate the correct vertex positions - opaque.length = 0; - transmissive.length = 0; - transparent.length = 0; + calculatePositionOnCurve( u, p, q, radius, P1 ); + calculatePositionOnCurve( u + 0.01, p, q, radius, P2 ); - } + // calculate orthonormal basis - function getNextRenderItem( object, geometry, material, groupOrder, z, group ) { + T.subVectors( P2, P1 ); + N.addVectors( P2, P1 ); + B.crossVectors( T, N ); + N.crossVectors( B, T ); - let renderItem = renderItems[ renderItemsIndex ]; + // normalize B, N. T can be ignored, we don't use it - if ( renderItem === undefined ) { + B.normalize(); + N.normalize(); - renderItem = { - id: object.id, - object: object, - geometry: geometry, - material: material, - groupOrder: groupOrder, - renderOrder: object.renderOrder, - z: z, - group: group - }; + for ( let j = 0; j <= radialSegments; ++ j ) { - renderItems[ renderItemsIndex ] = renderItem; + // now calculate the vertices. they are nothing more than an extrusion of the torus curve. + // because we extrude a shape in the xy-plane, there is no need to calculate a z-value. - } else { + const v = j / radialSegments * Math.PI * 2; + const cx = - tube * Math.cos( v ); + const cy = tube * Math.sin( v ); - renderItem.id = object.id; - renderItem.object = object; - renderItem.geometry = geometry; - renderItem.material = material; - renderItem.groupOrder = groupOrder; - renderItem.renderOrder = object.renderOrder; - renderItem.z = z; - renderItem.group = group; + // now calculate the final vertex position. + // first we orient the extrusion with our basis vectors, then we add it to the current position on the curve - } + vertex.x = P1.x + ( cx * N.x + cy * B.x ); + vertex.y = P1.y + ( cx * N.y + cy * B.y ); + vertex.z = P1.z + ( cx * N.z + cy * B.z ); - renderItemsIndex ++; + vertices.push( vertex.x, vertex.y, vertex.z ); - return renderItem; + // normal (P1 is always the center/origin of the extrusion, thus we can use it to calculate the normal) - } + normal.subVectors( vertex, P1 ).normalize(); - function push( object, geometry, material, groupOrder, z, group ) { + normals.push( normal.x, normal.y, normal.z ); - const renderItem = getNextRenderItem( object, geometry, material, groupOrder, z, group ); + // uv - if ( material.transmission > 0.0 ) { + uvs.push( i / tubularSegments ); + uvs.push( j / radialSegments ); - transmissive.push( renderItem ); + } - } else if ( material.transparent === true ) { + } - transparent.push( renderItem ); + // generate indices - } else { + for ( let j = 1; j <= tubularSegments; j ++ ) { - opaque.push( renderItem ); + for ( let i = 1; i <= radialSegments; i ++ ) { - } + // indices - } + const a = ( radialSegments + 1 ) * ( j - 1 ) + ( i - 1 ); + const b = ( radialSegments + 1 ) * j + ( i - 1 ); + const c = ( radialSegments + 1 ) * j + i; + const d = ( radialSegments + 1 ) * ( j - 1 ) + i; - function unshift( object, geometry, material, groupOrder, z, group ) { + // faces - const renderItem = getNextRenderItem( object, geometry, material, groupOrder, z, group ); + indices.push( a, b, d ); + indices.push( b, c, d ); - if ( material.transmission > 0.0 ) { + } - transmissive.unshift( renderItem ); + } - } else if ( material.transparent === true ) { + // build geometry - transparent.unshift( renderItem ); + this.setIndex( indices ); + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - } else { + // this function calculates the current position on the torus curve - opaque.unshift( renderItem ); + function calculatePositionOnCurve( u, p, q, radius, position ) { + + const cu = Math.cos( u ); + const su = Math.sin( u ); + const quOverP = q / p * u; + const cs = Math.cos( quOverP ); + + position.x = radius * ( 2 + cs ) * 0.5 * cu; + position.y = radius * ( 2 + cs ) * su * 0.5; + position.z = radius * Math.sin( quOverP ) * 0.5; } } - function sort( customOpaqueSort, customTransparentSort ) { + copy( source ) { - if ( opaque.length > 1 ) opaque.sort( customOpaqueSort || painterSortStable ); - if ( transmissive.length > 1 ) transmissive.sort( customTransparentSort || reversePainterSortStable ); - if ( transparent.length > 1 ) transparent.sort( customTransparentSort || reversePainterSortStable ); + super.copy( source ); + + this.parameters = Object.assign( {}, source.parameters ); + + return this; } - function finish() { + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {TorusKnotGeometry} A new instance. + */ + static fromJSON( data ) { - // Clear references from inactive renderItems in the list + return new TorusKnotGeometry( data.radius, data.tube, data.tubularSegments, data.radialSegments, data.p, data.q ); - for ( let i = renderItemsIndex, il = renderItems.length; i < il; i ++ ) { + } - const renderItem = renderItems[ i ]; +} - if ( renderItem.id === null ) break; +/** + * Creates a tube that extrudes along a 3D curve. + * + * ```js + * class CustomSinCurve extends THREE.Curve { + * + * getPoint( t, optionalTarget = new THREE.Vector3() ) { + * + * const tx = t * 3 - 1.5; + * const ty = Math.sin( 2 * Math.PI * t ); + * const tz = 0; + * + * return optionalTarget.set( tx, ty, tz ); + * } + * + * } + * + * const path = new CustomSinCurve( 10 ); + * const geometry = new THREE.TubeGeometry( path, 20, 2, 8, false ); + * const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); + * const mesh = new THREE.Mesh( geometry, material ); + * scene.add( mesh ); + * ``` + * + * @augments BufferGeometry + */ +class TubeGeometry extends BufferGeometry { - renderItem.id = null; - renderItem.object = null; - renderItem.geometry = null; - renderItem.material = null; - renderItem.group = null; + /** + * Constructs a new tube geometry. + * + * @param {Curve} [path=QuadraticBezierCurve3] - A 3D curve defining the path of the tube. + * @param {number} [tubularSegments=64] - The number of segments that make up the tube. + * @param {number} [radius=1] -The radius of the tube. + * @param {number} [radialSegments=8] - The number of segments that make up the cross-section. + * @param {boolean} [closed=false] - Whether the tube is closed or not. + */ + constructor( path = new QuadraticBezierCurve3( new Vector3( -1, -1, 0 ), new Vector3( -1, 1, 0 ), new Vector3( 1, 1, 0 ) ), tubularSegments = 64, radius = 1, radialSegments = 8, closed = false ) { - } + super(); - } + this.type = 'TubeGeometry'; - return { + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + path: path, + tubularSegments: tubularSegments, + radius: radius, + radialSegments: radialSegments, + closed: closed + }; - opaque: opaque, - transmissive: transmissive, - transparent: transparent, + const frames = path.computeFrenetFrames( tubularSegments, closed ); - init: init, - push: push, - unshift: unshift, - finish: finish, + // expose internals - sort: sort - }; + this.tangents = frames.tangents; + this.normals = frames.normals; + this.binormals = frames.binormals; -} + // helper variables -function WebGLRenderLists() { + const vertex = new Vector3(); + const normal = new Vector3(); + const uv = new Vector2(); + let P = new Vector3(); - let lists = new WeakMap(); + // buffer - function get( scene, renderCallDepth ) { + const vertices = []; + const normals = []; + const uvs = []; + const indices = []; - const listArray = lists.get( scene ); - let list; + // create buffer data - if ( listArray === undefined ) { + generateBufferData(); - list = new WebGLRenderList(); - lists.set( scene, [ list ] ); + // build geometry - } else { + this.setIndex( indices ); + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - if ( renderCallDepth >= listArray.length ) { + // functions - list = new WebGLRenderList(); - listArray.push( list ); + function generateBufferData() { - } else { + for ( let i = 0; i < tubularSegments; i ++ ) { - list = listArray[ renderCallDepth ]; + generateSegment( i ); } - } + // if the geometry is not closed, generate the last row of vertices and normals + // at the regular position on the given path + // + // if the geometry is closed, duplicate the first row of vertices and normals (uvs will differ) - return list; + generateSegment( ( closed === false ) ? tubularSegments : 0 ); - } + // uvs are generated in a separate function. + // this makes it easy compute correct values for closed geometries - function dispose() { + generateUVs(); - lists = new WeakMap(); + // finally create faces - } + generateIndices(); - return { - get: get, - dispose: dispose - }; + } -} + function generateSegment( i ) { -function UniformsCache() { + // we use getPointAt to sample evenly distributed points from the given path - const lights = {}; + P = path.getPointAt( i / tubularSegments, P ); - return { + // retrieve corresponding normal and binormal - get: function ( light ) { + const N = frames.normals[ i ]; + const B = frames.binormals[ i ]; - if ( lights[ light.id ] !== undefined ) { + // generate normals and vertices for the current segment - return lights[ light.id ]; + for ( let j = 0; j <= radialSegments; j ++ ) { - } + const v = j / radialSegments * Math.PI * 2; - let uniforms; + const sin = Math.sin( v ); + const cos = - Math.cos( v ); - switch ( light.type ) { + // normal - case 'DirectionalLight': - uniforms = { - direction: new Vector3(), - color: new Color() - }; - break; + normal.x = ( cos * N.x + sin * B.x ); + normal.y = ( cos * N.y + sin * B.y ); + normal.z = ( cos * N.z + sin * B.z ); + normal.normalize(); - case 'SpotLight': - uniforms = { - position: new Vector3(), - direction: new Vector3(), - color: new Color(), - distance: 0, - coneCos: 0, - penumbraCos: 0, - decay: 0 - }; - break; + normals.push( normal.x, normal.y, normal.z ); - case 'PointLight': - uniforms = { - position: new Vector3(), - color: new Color(), - distance: 0, - decay: 0 - }; - break; + // vertex - case 'HemisphereLight': - uniforms = { - direction: new Vector3(), - skyColor: new Color(), - groundColor: new Color() - }; - break; + vertex.x = P.x + radius * normal.x; + vertex.y = P.y + radius * normal.y; + vertex.z = P.z + radius * normal.z; - case 'RectAreaLight': - uniforms = { - color: new Color(), - position: new Vector3(), - halfWidth: new Vector3(), - halfHeight: new Vector3() - }; - break; + vertices.push( vertex.x, vertex.y, vertex.z ); } - lights[ light.id ] = uniforms; + } - return uniforms; + function generateIndices() { - } + for ( let j = 1; j <= tubularSegments; j ++ ) { - }; + for ( let i = 1; i <= radialSegments; i ++ ) { -} + const a = ( radialSegments + 1 ) * ( j - 1 ) + ( i - 1 ); + const b = ( radialSegments + 1 ) * j + ( i - 1 ); + const c = ( radialSegments + 1 ) * j + i; + const d = ( radialSegments + 1 ) * ( j - 1 ) + i; -function ShadowUniformsCache() { + // faces - const lights = {}; + indices.push( a, b, d ); + indices.push( b, c, d ); - return { + } - get: function ( light ) { + } - if ( lights[ light.id ] !== undefined ) { + } - return lights[ light.id ]; + function generateUVs() { - } + for ( let i = 0; i <= tubularSegments; i ++ ) { - let uniforms; + for ( let j = 0; j <= radialSegments; j ++ ) { - switch ( light.type ) { + uv.x = i / tubularSegments; + uv.y = j / radialSegments; - case 'DirectionalLight': - uniforms = { - shadowBias: 0, - shadowNormalBias: 0, - shadowRadius: 1, - shadowMapSize: new Vector2() - }; - break; + uvs.push( uv.x, uv.y ); - case 'SpotLight': - uniforms = { - shadowBias: 0, - shadowNormalBias: 0, - shadowRadius: 1, - shadowMapSize: new Vector2() - }; - break; + } - case 'PointLight': - uniforms = { - shadowBias: 0, - shadowNormalBias: 0, - shadowRadius: 1, - shadowMapSize: new Vector2(), - shadowCameraNear: 1, - shadowCameraFar: 1000 - }; - break; + } + + } + + } - // TODO (abelnation): set RectAreaLight shadow uniforms + copy( source ) { - } + super.copy( source ); - lights[ light.id ] = uniforms; + this.parameters = Object.assign( {}, source.parameters ); - return uniforms; + return this; - } + } - }; + toJSON() { -} + const data = super.toJSON(); + data.path = this.parameters.path.toJSON(); + return data; -let nextVersion = 0; + } -function shadowCastingAndTexturingLightsFirst( lightA, lightB ) { + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {TubeGeometry} A new instance. + */ + static fromJSON( data ) { - return ( lightB.castShadow ? 2 : 0 ) - ( lightA.castShadow ? 2 : 0 ) + ( lightB.map ? 1 : 0 ) - ( lightA.map ? 1 : 0 ); + // This only works for built-in curves (e.g. CatmullRomCurve3). + // User defined curves or instances of CurvePath will not be deserialized. + return new TubeGeometry( + new Curves[ data.path.type ]().fromJSON( data.path ), + data.tubularSegments, + data.radius, + data.radialSegments, + data.closed + ); + + } } -function WebGLLights( extensions, capabilities ) { +/** + * Can be used as a helper object to visualize a geometry as a wireframe. + * + * ```js + * const geometry = new THREE.SphereGeometry(); + * + * const wireframe = new THREE.WireframeGeometry( geometry ); + * + * const line = new THREE.LineSegments( wireframe ); + * line.material.depthWrite = false; + * line.material.opacity = 0.25; + * line.material.transparent = true; + * + * scene.add( line ); + * ``` + * + * Note: It is not yet possible to serialize/deserialize instances of this class. + * + * @augments BufferGeometry + */ +class WireframeGeometry extends BufferGeometry { - const cache = new UniformsCache(); + /** + * Constructs a new wireframe geometry. + * + * @param {?BufferGeometry} [geometry=null] - The geometry. + */ + constructor( geometry = null ) { - const shadowCache = ShadowUniformsCache(); + super(); - const state = { + this.type = 'WireframeGeometry'; - version: 0, + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + geometry: geometry + }; - hash: { - directionalLength: - 1, - pointLength: - 1, - spotLength: - 1, - rectAreaLength: - 1, - hemiLength: - 1, - - numDirectionalShadows: - 1, - numPointShadows: - 1, - numSpotShadows: - 1, - numSpotMaps: - 1 - }, + if ( geometry !== null ) { - ambient: [ 0, 0, 0 ], - probe: [], - directional: [], - directionalShadow: [], - directionalShadowMap: [], - directionalShadowMatrix: [], - spot: [], - spotLightMap: [], - spotShadow: [], - spotShadowMap: [], - spotLightMatrix: [], - rectArea: [], - rectAreaLTC1: null, - rectAreaLTC2: null, - point: [], - pointShadow: [], - pointShadowMap: [], - pointShadowMatrix: [], - hemi: [], - numSpotLightShadowsWithMaps: 0 + // buffer - }; + const vertices = []; + const edges = new Set(); - for ( let i = 0; i < 9; i ++ ) state.probe.push( new Vector3() ); + // helper variables - const vector3 = new Vector3(); - const matrix4 = new Matrix4(); - const matrix42 = new Matrix4(); + const start = new Vector3(); + const end = new Vector3(); - function setup( lights, useLegacyLights ) { + if ( geometry.index !== null ) { - let r = 0, g = 0, b = 0; + // indexed BufferGeometry - for ( let i = 0; i < 9; i ++ ) state.probe[ i ].set( 0, 0, 0 ); + const position = geometry.attributes.position; + const indices = geometry.index; + let groups = geometry.groups; - let directionalLength = 0; - let pointLength = 0; - let spotLength = 0; - let rectAreaLength = 0; - let hemiLength = 0; + if ( groups.length === 0 ) { - let numDirectionalShadows = 0; - let numPointShadows = 0; - let numSpotShadows = 0; - let numSpotMaps = 0; - let numSpotShadowsWithMaps = 0; + groups = [ { start: 0, count: indices.count, materialIndex: 0 } ]; - // ordering : [shadow casting + map texturing, map texturing, shadow casting, none ] - lights.sort( shadowCastingAndTexturingLightsFirst ); + } - // artist-friendly light intensity scaling factor - const scaleFactor = ( useLegacyLights === true ) ? Math.PI : 1; + // create a data structure that contains all edges without duplicates - for ( let i = 0, l = lights.length; i < l; i ++ ) { + for ( let o = 0, ol = groups.length; o < ol; ++ o ) { - const light = lights[ i ]; + const group = groups[ o ]; - const color = light.color; - const intensity = light.intensity; - const distance = light.distance; + const groupStart = group.start; + const groupCount = group.count; - const shadowMap = ( light.shadow && light.shadow.map ) ? light.shadow.map.texture : null; + for ( let i = groupStart, l = ( groupStart + groupCount ); i < l; i += 3 ) { - if ( light.isAmbientLight ) { + for ( let j = 0; j < 3; j ++ ) { - r += color.r * intensity * scaleFactor; - g += color.g * intensity * scaleFactor; - b += color.b * intensity * scaleFactor; + const index1 = indices.getX( i + j ); + const index2 = indices.getX( i + ( j + 1 ) % 3 ); - } else if ( light.isLightProbe ) { + start.fromBufferAttribute( position, index1 ); + end.fromBufferAttribute( position, index2 ); - for ( let j = 0; j < 9; j ++ ) { + if ( isUniqueEdge( start, end, edges ) === true ) { - state.probe[ j ].addScaledVector( light.sh.coefficients[ j ], intensity ); + vertices.push( start.x, start.y, start.z ); + vertices.push( end.x, end.y, end.z ); + + } + + } + + } } - } else if ( light.isDirectionalLight ) { + } else { - const uniforms = cache.get( light ); + // non-indexed BufferGeometry - uniforms.color.copy( light.color ).multiplyScalar( light.intensity * scaleFactor ); + const position = geometry.attributes.position; - if ( light.castShadow ) { + for ( let i = 0, l = ( position.count / 3 ); i < l; i ++ ) { - const shadow = light.shadow; + for ( let j = 0; j < 3; j ++ ) { - const shadowUniforms = shadowCache.get( light ); + // three edges per triangle, an edge is represented as (index1, index2) + // e.g. the first triangle has the following edges: (0,1),(1,2),(2,0) - shadowUniforms.shadowBias = shadow.bias; - shadowUniforms.shadowNormalBias = shadow.normalBias; - shadowUniforms.shadowRadius = shadow.radius; - shadowUniforms.shadowMapSize = shadow.mapSize; + const index1 = 3 * i + j; + const index2 = 3 * i + ( ( j + 1 ) % 3 ); - state.directionalShadow[ directionalLength ] = shadowUniforms; - state.directionalShadowMap[ directionalLength ] = shadowMap; - state.directionalShadowMatrix[ directionalLength ] = light.shadow.matrix; + start.fromBufferAttribute( position, index1 ); + end.fromBufferAttribute( position, index2 ); - numDirectionalShadows ++; + if ( isUniqueEdge( start, end, edges ) === true ) { + + vertices.push( start.x, start.y, start.z ); + vertices.push( end.x, end.y, end.z ); + + } + + } } - state.directional[ directionalLength ] = uniforms; + } - directionalLength ++; + // build geometry - } else if ( light.isSpotLight ) { + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - const uniforms = cache.get( light ); + } - uniforms.position.setFromMatrixPosition( light.matrixWorld ); + } - uniforms.color.copy( color ).multiplyScalar( intensity * scaleFactor ); - uniforms.distance = distance; + copy( source ) { - uniforms.coneCos = Math.cos( light.angle ); - uniforms.penumbraCos = Math.cos( light.angle * ( 1 - light.penumbra ) ); - uniforms.decay = light.decay; + super.copy( source ); - state.spot[ spotLength ] = uniforms; + this.parameters = Object.assign( {}, source.parameters ); - const shadow = light.shadow; + return this; - if ( light.map ) { + } - state.spotLightMap[ numSpotMaps ] = light.map; - numSpotMaps ++; +} - // make sure the lightMatrix is up to date - // TODO : do it if required only - shadow.updateMatrices( light ); +function isUniqueEdge( start, end, edges ) { - if ( light.castShadow ) numSpotShadowsWithMaps ++; + const hash1 = `${start.x},${start.y},${start.z}-${end.x},${end.y},${end.z}`; + const hash2 = `${end.x},${end.y},${end.z}-${start.x},${start.y},${start.z}`; // coincident edge - } + if ( edges.has( hash1 ) === true || edges.has( hash2 ) === true ) { - state.spotLightMatrix[ spotLength ] = shadow.matrix; + return false; - if ( light.castShadow ) { + } else { - const shadowUniforms = shadowCache.get( light ); + edges.add( hash1 ); + edges.add( hash2 ); + return true; - shadowUniforms.shadowBias = shadow.bias; - shadowUniforms.shadowNormalBias = shadow.normalBias; - shadowUniforms.shadowRadius = shadow.radius; - shadowUniforms.shadowMapSize = shadow.mapSize; + } - state.spotShadow[ spotLength ] = shadowUniforms; - state.spotShadowMap[ spotLength ] = shadowMap; +} - numSpotShadows ++; +var Geometries = /*#__PURE__*/Object.freeze({ + __proto__: null, + BoxGeometry: BoxGeometry, + CapsuleGeometry: CapsuleGeometry, + CircleGeometry: CircleGeometry, + ConeGeometry: ConeGeometry, + CylinderGeometry: CylinderGeometry, + DodecahedronGeometry: DodecahedronGeometry, + EdgesGeometry: EdgesGeometry, + ExtrudeGeometry: ExtrudeGeometry, + IcosahedronGeometry: IcosahedronGeometry, + LatheGeometry: LatheGeometry, + OctahedronGeometry: OctahedronGeometry, + PlaneGeometry: PlaneGeometry, + PolyhedronGeometry: PolyhedronGeometry, + RingGeometry: RingGeometry, + ShapeGeometry: ShapeGeometry, + SphereGeometry: SphereGeometry, + TetrahedronGeometry: TetrahedronGeometry, + TorusGeometry: TorusGeometry, + TorusKnotGeometry: TorusKnotGeometry, + TubeGeometry: TubeGeometry, + WireframeGeometry: WireframeGeometry +}); - } +/** + * This material can receive shadows, but otherwise is completely transparent. + * + * ```js + * const geometry = new THREE.PlaneGeometry( 2000, 2000 ); + * geometry.rotateX( - Math.PI / 2 ); + * + * const material = new THREE.ShadowMaterial(); + * material.opacity = 0.2; + * + * const plane = new THREE.Mesh( geometry, material ); + * plane.position.y = -200; + * plane.receiveShadow = true; + * scene.add( plane ); + * ``` + * + * @augments Material + */ +class ShadowMaterial extends Material { - spotLength ++; + /** + * Constructs a new shadow material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { - } else if ( light.isRectAreaLight ) { + super(); - const uniforms = cache.get( light ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isShadowMaterial = true; - uniforms.color.copy( color ).multiplyScalar( intensity ); + this.type = 'ShadowMaterial'; - uniforms.halfWidth.set( light.width * 0.5, 0.0, 0.0 ); - uniforms.halfHeight.set( 0.0, light.height * 0.5, 0.0 ); + /** + * Color of the material. + * + * @type {Color} + * @default (0,0,0) + */ + this.color = new Color( 0x000000 ); - state.rectArea[ rectAreaLength ] = uniforms; + /** + * Overwritten since shadow materials are transparent + * by default. + * + * @type {boolean} + * @default true + */ + this.transparent = true; - rectAreaLength ++; + /** + * Whether the material is affected by fog or not. + * + * @type {boolean} + * @default true + */ + this.fog = true; - } else if ( light.isPointLight ) { + this.setValues( parameters ); - const uniforms = cache.get( light ); + } - uniforms.color.copy( light.color ).multiplyScalar( light.intensity * scaleFactor ); - uniforms.distance = light.distance; - uniforms.decay = light.decay; + copy( source ) { - if ( light.castShadow ) { + super.copy( source ); - const shadow = light.shadow; + this.color.copy( source.color ); - const shadowUniforms = shadowCache.get( light ); + this.fog = source.fog; - shadowUniforms.shadowBias = shadow.bias; - shadowUniforms.shadowNormalBias = shadow.normalBias; - shadowUniforms.shadowRadius = shadow.radius; - shadowUniforms.shadowMapSize = shadow.mapSize; - shadowUniforms.shadowCameraNear = shadow.camera.near; - shadowUniforms.shadowCameraFar = shadow.camera.far; + return this; - state.pointShadow[ pointLength ] = shadowUniforms; - state.pointShadowMap[ pointLength ] = shadowMap; - state.pointShadowMatrix[ pointLength ] = light.shadow.matrix; + } - numPointShadows ++; +} - } +/** + * This class works just like {@link ShaderMaterial}, except that definitions + * of built-in uniforms and attributes are not automatically prepended to the + * GLSL shader code. + * + * `RawShaderMaterial` can only be used with {@link WebGLRenderer}. + * + * @augments ShaderMaterial + */ +class RawShaderMaterial extends ShaderMaterial { - state.point[ pointLength ] = uniforms; + /** + * Constructs a new raw shader material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { - pointLength ++; + super( parameters ); - } else if ( light.isHemisphereLight ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isRawShaderMaterial = true; - const uniforms = cache.get( light ); + this.type = 'RawShaderMaterial'; - uniforms.skyColor.copy( light.color ).multiplyScalar( intensity * scaleFactor ); - uniforms.groundColor.copy( light.groundColor ).multiplyScalar( intensity * scaleFactor ); + } - state.hemi[ hemiLength ] = uniforms; +} - hemiLength ++; +/** + * A standard physically based material, using Metallic-Roughness workflow. + * + * Physically based rendering (PBR) has recently become the standard in many + * 3D applications, such as [Unity]{@link https://blogs.unity3d.com/2014/10/29/physically-based-shading-in-unity-5-a-primer/}, + * [Unreal]{@link https://docs.unrealengine.com/latest/INT/Engine/Rendering/Materials/PhysicallyBased/} and + * [3D Studio Max]{@link http://area.autodesk.com/blogs/the-3ds-max-blog/what039s-new-for-rendering-in-3ds-max-2017}. + * + * This approach differs from older approaches in that instead of using + * approximations for the way in which light interacts with a surface, a + * physically correct model is used. The idea is that, instead of tweaking + * materials to look good under specific lighting, a material can be created + * that will react 'correctly' under all lighting scenarios. + * + * In practice this gives a more accurate and realistic looking result than + * the {@link MeshLambertMaterial} or {@link MeshPhongMaterial}, at the cost of + * being somewhat more computationally expensive. `MeshStandardMaterial` uses per-fragment + * shading. + * + * Note that for best results you should always specify an environment map when using this material. + * + * For a non-technical introduction to the concept of PBR and how to set up a + * PBR material, check out these articles by the people at [marmoset]{@link https://www.marmoset.co}: + * + * - [Basic Theory of Physically Based Rendering]{@link https://www.marmoset.co/posts/basic-theory-of-physically-based-rendering/} + * - [Physically Based Rendering and You Can Too]{@link https://www.marmoset.co/posts/physically-based-rendering-and-you-can-too/} + * + * Technical details of the approach used in three.js (and most other PBR systems) can be found is this + * [paper from Disney]{@link https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf} + * (pdf), by Brent Burley. + * + * @augments Material + */ +class MeshStandardMaterial extends Material { - } + /** + * Constructs a new mesh standard material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { - } + super(); - if ( rectAreaLength > 0 ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshStandardMaterial = true; - if ( capabilities.isWebGL2 ) { + this.type = 'MeshStandardMaterial'; - // WebGL 2 + this.defines = { 'STANDARD': '' }; - state.rectAreaLTC1 = UniformsLib.LTC_FLOAT_1; - state.rectAreaLTC2 = UniformsLib.LTC_FLOAT_2; + /** + * Color of the material. + * + * @type {Color} + * @default (1,1,1) + */ + this.color = new Color( 0xffffff ); // diffuse - } else { + /** + * How rough the material appears. `0.0` means a smooth mirror reflection, `1.0` + * means fully diffuse. If `roughnessMap` is also provided, + * both values are multiplied. + * + * @type {number} + * @default 1 + */ + this.roughness = 1.0; - // WebGL 1 + /** + * How much the material is like a metal. Non-metallic materials such as wood + * or stone use `0.0`, metallic use `1.0`, with nothing (usually) in between. + * A value between `0.0` and `1.0` could be used for a rusty metal look. + * If `metalnessMap` is also provided, both values are multiplied. + * + * @type {number} + * @default 0 + */ + this.metalness = 0.0; - if ( extensions.has( 'OES_texture_float_linear' ) === true ) { + /** + * The color map. May optionally include an alpha channel, typically combined + * with {@link Material#transparent} or {@link Material#alphaTest}. The texture map + * color is modulated by the diffuse `color`. + * + * @type {?Texture} + * @default null + */ + this.map = null; - state.rectAreaLTC1 = UniformsLib.LTC_FLOAT_1; - state.rectAreaLTC2 = UniformsLib.LTC_FLOAT_2; + /** + * The light map. Requires a second set of UVs. + * + * @type {?Texture} + * @default null + */ + this.lightMap = null; - } else if ( extensions.has( 'OES_texture_half_float_linear' ) === true ) { + /** + * Intensity of the baked light. + * + * @type {number} + * @default 1 + */ + this.lightMapIntensity = 1.0; - state.rectAreaLTC1 = UniformsLib.LTC_HALF_1; - state.rectAreaLTC2 = UniformsLib.LTC_HALF_2; + /** + * The red channel of this texture is used as the ambient occlusion map. + * Requires a second set of UVs. + * + * @type {?Texture} + * @default null + */ + this.aoMap = null; - } else { + /** + * Intensity of the ambient occlusion effect. Range is `[0,1]`, where `0` + * disables ambient occlusion. Where intensity is `1` and the AO map's + * red channel is also `1`, ambient light is fully occluded on a surface. + * + * @type {number} + * @default 1 + */ + this.aoMapIntensity = 1.0; - console.error( 'THREE.WebGLRenderer: Unable to use RectAreaLight. Missing WebGL extensions.' ); + /** + * Emissive (light) color of the material, essentially a solid color + * unaffected by other lighting. + * + * @type {Color} + * @default (0,0,0) + */ + this.emissive = new Color( 0x000000 ); - } + /** + * Intensity of the emissive light. Modulates the emissive color. + * + * @type {number} + * @default 1 + */ + this.emissiveIntensity = 1.0; - } + /** + * Set emissive (glow) map. The emissive map color is modulated by the + * emissive color and the emissive intensity. If you have an emissive map, + * be sure to set the emissive color to something other than black. + * + * @type {?Texture} + * @default null + */ + this.emissiveMap = null; - } + /** + * The texture to create a bump map. The black and white values map to the + * perceived depth in relation to the lights. Bump doesn't actually affect + * the geometry of the object, only the lighting. If a normal map is defined + * this will be ignored. + * + * @type {?Texture} + * @default null + */ + this.bumpMap = null; - state.ambient[ 0 ] = r; - state.ambient[ 1 ] = g; - state.ambient[ 2 ] = b; + /** + * How much the bump map affects the material. Typical range is `[0,1]`. + * + * @type {number} + * @default 1 + */ + this.bumpScale = 1; - const hash = state.hash; + /** + * The texture to create a normal map. The RGB values affect the surface + * normal for each pixel fragment and change the way the color is lit. Normal + * maps do not change the actual shape of the surface, only the lighting. In + * case the material has a normal map authored using the left handed + * convention, the `y` component of `normalScale` should be negated to compensate + * for the different handedness. + * + * @type {?Texture} + * @default null + */ + this.normalMap = null; - if ( hash.directionalLength !== directionalLength || - hash.pointLength !== pointLength || - hash.spotLength !== spotLength || - hash.rectAreaLength !== rectAreaLength || - hash.hemiLength !== hemiLength || - hash.numDirectionalShadows !== numDirectionalShadows || - hash.numPointShadows !== numPointShadows || - hash.numSpotShadows !== numSpotShadows || - hash.numSpotMaps !== numSpotMaps ) { + /** + * The type of normal map. + * + * @type {(TangentSpaceNormalMap|ObjectSpaceNormalMap)} + * @default TangentSpaceNormalMap + */ + this.normalMapType = TangentSpaceNormalMap; - state.directional.length = directionalLength; - state.spot.length = spotLength; - state.rectArea.length = rectAreaLength; - state.point.length = pointLength; - state.hemi.length = hemiLength; + /** + * How much the normal map affects the material. Typical value range is `[0,1]`. + * + * @type {Vector2} + * @default (1,1) + */ + this.normalScale = new Vector2( 1, 1 ); - state.directionalShadow.length = numDirectionalShadows; - state.directionalShadowMap.length = numDirectionalShadows; - state.pointShadow.length = numPointShadows; - state.pointShadowMap.length = numPointShadows; - state.spotShadow.length = numSpotShadows; - state.spotShadowMap.length = numSpotShadows; - state.directionalShadowMatrix.length = numDirectionalShadows; - state.pointShadowMatrix.length = numPointShadows; - state.spotLightMatrix.length = numSpotShadows + numSpotMaps - numSpotShadowsWithMaps; - state.spotLightMap.length = numSpotMaps; - state.numSpotLightShadowsWithMaps = numSpotShadowsWithMaps; + /** + * The displacement map affects the position of the mesh's vertices. Unlike + * other maps which only affect the light and shade of the material the + * displaced vertices can cast shadows, block other objects, and otherwise + * act as real geometry. The displacement texture is an image where the value + * of each pixel (white being the highest) is mapped against, and + * repositions, the vertices of the mesh. + * + * @type {?Texture} + * @default null + */ + this.displacementMap = null; - hash.directionalLength = directionalLength; - hash.pointLength = pointLength; - hash.spotLength = spotLength; - hash.rectAreaLength = rectAreaLength; - hash.hemiLength = hemiLength; + /** + * How much the displacement map affects the mesh (where black is no + * displacement, and white is maximum displacement). Without a displacement + * map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementScale = 1; - hash.numDirectionalShadows = numDirectionalShadows; - hash.numPointShadows = numPointShadows; - hash.numSpotShadows = numSpotShadows; - hash.numSpotMaps = numSpotMaps; + /** + * The offset of the displacement map's values on the mesh's vertices. + * The bias is added to the scaled sample of the displacement map. + * Without a displacement map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementBias = 0; - state.version = nextVersion ++; + /** + * The green channel of this texture is used to alter the roughness of the + * material. + * + * @type {?Texture} + * @default null + */ + this.roughnessMap = null; - } + /** + * The blue channel of this texture is used to alter the metalness of the + * material. + * + * @type {?Texture} + * @default null + */ + this.metalnessMap = null; - } + /** + * The alpha map is a grayscale texture that controls the opacity across the + * surface (black: fully transparent; white: fully opaque). + * + * Only the color of the texture is used, ignoring the alpha channel if one + * exists. For RGB and RGBA textures, the renderer will use the green channel + * when sampling this texture due to the extra bit of precision provided for + * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and + * luminance/alpha textures will also still work as expected. + * + * @type {?Texture} + * @default null + */ + this.alphaMap = null; - function setupView( lights, camera ) { + /** + * The environment map. To ensure a physically correct rendering, environment maps + * are internally pre-processed with {@link PMREMGenerator}. + * + * @type {?Texture} + * @default null + */ + this.envMap = null; - let directionalLength = 0; - let pointLength = 0; - let spotLength = 0; - let rectAreaLength = 0; - let hemiLength = 0; + /** + * The rotation of the environment map in radians. + * + * @type {Euler} + * @default (0,0,0) + */ + this.envMapRotation = new Euler(); - const viewMatrix = camera.matrixWorldInverse; + /** + * Scales the effect of the environment map by multiplying its color. + * + * @type {number} + * @default 1 + */ + this.envMapIntensity = 1.0; - for ( let i = 0, l = lights.length; i < l; i ++ ) { + /** + * Renders the geometry as a wireframe. + * + * @type {boolean} + * @default false + */ + this.wireframe = false; - const light = lights[ i ]; + /** + * Controls the thickness of the wireframe. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {number} + * @default 1 + */ + this.wireframeLinewidth = 1; - if ( light.isDirectionalLight ) { + /** + * Defines appearance of wireframe ends. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ + this.wireframeLinecap = 'round'; + + /** + * Defines appearance of wireframe joints. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ + this.wireframeLinejoin = 'round'; + + /** + * Whether the material is rendered with flat shading or not. + * + * @type {boolean} + * @default false + */ + this.flatShading = false; + + /** + * Whether the material is affected by fog or not. + * + * @type {boolean} + * @default true + */ + this.fog = true; + + this.setValues( parameters ); - const uniforms = state.directional[ directionalLength ]; + } - uniforms.direction.setFromMatrixPosition( light.matrixWorld ); - vector3.setFromMatrixPosition( light.target.matrixWorld ); - uniforms.direction.sub( vector3 ); - uniforms.direction.transformDirection( viewMatrix ); + copy( source ) { - directionalLength ++; + super.copy( source ); - } else if ( light.isSpotLight ) { + this.defines = { 'STANDARD': '' }; - const uniforms = state.spot[ spotLength ]; + this.color.copy( source.color ); + this.roughness = source.roughness; + this.metalness = source.metalness; - uniforms.position.setFromMatrixPosition( light.matrixWorld ); - uniforms.position.applyMatrix4( viewMatrix ); + this.map = source.map; - uniforms.direction.setFromMatrixPosition( light.matrixWorld ); - vector3.setFromMatrixPosition( light.target.matrixWorld ); - uniforms.direction.sub( vector3 ); - uniforms.direction.transformDirection( viewMatrix ); + this.lightMap = source.lightMap; + this.lightMapIntensity = source.lightMapIntensity; - spotLength ++; + this.aoMap = source.aoMap; + this.aoMapIntensity = source.aoMapIntensity; - } else if ( light.isRectAreaLight ) { + this.emissive.copy( source.emissive ); + this.emissiveMap = source.emissiveMap; + this.emissiveIntensity = source.emissiveIntensity; - const uniforms = state.rectArea[ rectAreaLength ]; + this.bumpMap = source.bumpMap; + this.bumpScale = source.bumpScale; - uniforms.position.setFromMatrixPosition( light.matrixWorld ); - uniforms.position.applyMatrix4( viewMatrix ); + this.normalMap = source.normalMap; + this.normalMapType = source.normalMapType; + this.normalScale.copy( source.normalScale ); - // extract local rotation of light to derive width/height half vectors - matrix42.identity(); - matrix4.copy( light.matrixWorld ); - matrix4.premultiply( viewMatrix ); - matrix42.extractRotation( matrix4 ); + this.displacementMap = source.displacementMap; + this.displacementScale = source.displacementScale; + this.displacementBias = source.displacementBias; - uniforms.halfWidth.set( light.width * 0.5, 0.0, 0.0 ); - uniforms.halfHeight.set( 0.0, light.height * 0.5, 0.0 ); + this.roughnessMap = source.roughnessMap; - uniforms.halfWidth.applyMatrix4( matrix42 ); - uniforms.halfHeight.applyMatrix4( matrix42 ); + this.metalnessMap = source.metalnessMap; - rectAreaLength ++; + this.alphaMap = source.alphaMap; - } else if ( light.isPointLight ) { + this.envMap = source.envMap; + this.envMapRotation.copy( source.envMapRotation ); + this.envMapIntensity = source.envMapIntensity; - const uniforms = state.point[ pointLength ]; + this.wireframe = source.wireframe; + this.wireframeLinewidth = source.wireframeLinewidth; + this.wireframeLinecap = source.wireframeLinecap; + this.wireframeLinejoin = source.wireframeLinejoin; - uniforms.position.setFromMatrixPosition( light.matrixWorld ); - uniforms.position.applyMatrix4( viewMatrix ); + this.flatShading = source.flatShading; - pointLength ++; + this.fog = source.fog; - } else if ( light.isHemisphereLight ) { + return this; - const uniforms = state.hemi[ hemiLength ]; + } - uniforms.direction.setFromMatrixPosition( light.matrixWorld ); - uniforms.direction.transformDirection( viewMatrix ); +} - hemiLength ++; +/** + * An extension of the {@link MeshStandardMaterial}, providing more advanced + * physically-based rendering properties: + * + * - Anisotropy: Ability to represent the anisotropic property of materials + * as observable with brushed metals. + * - Clearcoat: Some materials — like car paints, carbon fiber, and wet surfaces — require + * a clear, reflective layer on top of another layer that may be irregular or rough. + * Clearcoat approximates this effect, without the need for a separate transparent surface. + * - Iridescence: Allows to render the effect where hue varies depending on the viewing + * angle and illumination angle. This can be seen on soap bubbles, oil films, or on the + * wings of many insects. + * - Physically-based transparency: One limitation of {@link Material#opacity} is that highly + * transparent materials are less reflective. Physically-based transmission provides a more + * realistic option for thin, transparent surfaces like glass. + * - Advanced reflectivity: More flexible reflectivity for non-metallic materials. + * - Sheen: Can be used for representing cloth and fabric materials. + * + * As a result of these complex shading features, `MeshPhysicalMaterial` has a + * higher performance cost, per pixel, than other three.js materials. Most + * effects are disabled by default, and add cost as they are enabled. For + * best results, always specify an environment map when using this material. + * + * @augments MeshStandardMaterial + */ +class MeshPhysicalMaterial extends MeshStandardMaterial { - } + /** + * Constructs a new mesh physical material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { - } + super(); - } + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshPhysicalMaterial = true; - return { - setup: setup, - setupView: setupView, - state: state - }; + this.defines = { -} + 'STANDARD': '', + 'PHYSICAL': '' -function WebGLRenderState( extensions, capabilities ) { + }; - const lights = new WebGLLights( extensions, capabilities ); + this.type = 'MeshPhysicalMaterial'; - const lightsArray = []; - const shadowsArray = []; + /** + * The rotation of the anisotropy in tangent, bitangent space, measured in radians + * counter-clockwise from the tangent. When `anisotropyMap` is present, this + * property provides additional rotation to the vectors in the texture. + * + * @type {number} + * @default 1 + */ + this.anisotropyRotation = 0; - function init() { + /** + * Red and green channels represent the anisotropy direction in `[-1, 1]` tangent, + * bitangent space, to be rotated by `anisotropyRotation`. The blue channel + * contains strength as `[0, 1]` to be multiplied by `anisotropy`. + * + * @type {?Texture} + * @default null + */ + this.anisotropyMap = null; - lightsArray.length = 0; - shadowsArray.length = 0; + /** + * The red channel of this texture is multiplied against `clearcoat`, + * for per-pixel control over a coating's intensity. + * + * @type {?Texture} + * @default null + */ + this.clearcoatMap = null; - } + /** + * Roughness of the clear coat layer, from `0.0` to `1.0`. + * + * @type {number} + * @default 0 + */ + this.clearcoatRoughness = 0.0; - function pushLight( light ) { + /** + * The green channel of this texture is multiplied against + * `clearcoatRoughness`, for per-pixel control over a coating's roughness. + * + * @type {?Texture} + * @default null + */ + this.clearcoatRoughnessMap = null; - lightsArray.push( light ); + /** + * How much `clearcoatNormalMap` affects the clear coat layer, from + * `(0,0)` to `(1,1)`. + * + * @type {Vector2} + * @default (1,1) + */ + this.clearcoatNormalScale = new Vector2( 1, 1 ); - } + /** + * Can be used to enable independent normals for the clear coat layer. + * + * @type {?Texture} + * @default null + */ + this.clearcoatNormalMap = null; - function pushShadow( shadowLight ) { + /** + * Index-of-refraction for non-metallic materials, from `1.0` to `2.333`. + * + * @type {number} + * @default 1.5 + */ + this.ior = 1.5; - shadowsArray.push( shadowLight ); + /** + * Degree of reflectivity, from `0.0` to `1.0`. Default is `0.5`, which + * corresponds to an index-of-refraction of `1.5`. + * + * This models the reflectivity of non-metallic materials. It has no effect + * when `metalness` is `1.0` + * + * @name MeshPhysicalMaterial#reflectivity + * @type {number} + * @default 0.5 + */ + Object.defineProperty( this, 'reflectivity', { + get: function () { - } + return ( clamp( 2.5 * ( this.ior - 1 ) / ( this.ior + 1 ), 0, 1 ) ); - function setupLights( useLegacyLights ) { + }, + set: function ( reflectivity ) { - lights.setup( lightsArray, useLegacyLights ); + this.ior = ( 1 + 0.4 * reflectivity ) / ( 1 - 0.4 * reflectivity ); - } + } + } ); - function setupLightsView( camera ) { + /** + * The red channel of this texture is multiplied against `iridescence`, for per-pixel + * control over iridescence. + * + * @type {?Texture} + * @default null + */ + this.iridescenceMap = null; - lights.setupView( lightsArray, camera ); + /** + * Strength of the iridescence RGB color shift effect, represented by an index-of-refraction. + * Between `1.0` to `2.333`. + * + * @type {number} + * @default 1.3 + */ + this.iridescenceIOR = 1.3; - } + /** + *Array of exactly 2 elements, specifying minimum and maximum thickness of the iridescence layer. + Thickness of iridescence layer has an equivalent effect of the one `thickness` has on `ior`. + * + * @type {Array} + * @default [100,400] + */ + this.iridescenceThicknessRange = [ 100, 400 ]; - const state = { - lightsArray: lightsArray, - shadowsArray: shadowsArray, + /** + * A texture that defines the thickness of the iridescence layer, stored in the green channel. + * Minimum and maximum values of thickness are defined by `iridescenceThicknessRange` array: + * - `0.0` in the green channel will result in thickness equal to first element of the array. + * - `1.0` in the green channel will result in thickness equal to second element of the array. + * - Values in-between will linearly interpolate between the elements of the array. + * + * @type {?Texture} + * @default null + */ + this.iridescenceThicknessMap = null; - lights: lights - }; + /** + * The sheen tint. + * + * @type {Color} + * @default (0,0,0) + */ + this.sheenColor = new Color( 0x000000 ); - return { - init: init, - state: state, - setupLights: setupLights, - setupLightsView: setupLightsView, + /** + * The RGB channels of this texture are multiplied against `sheenColor`, for per-pixel control + * over sheen tint. + * + * @type {?Texture} + * @default null + */ + this.sheenColorMap = null; - pushLight: pushLight, - pushShadow: pushShadow - }; + /** + * Roughness of the sheen layer, from `0.0` to `1.0`. + * + * @type {number} + * @default 1 + */ + this.sheenRoughness = 1.0; -} + /** + * The alpha channel of this texture is multiplied against `sheenRoughness`, for per-pixel control + * over sheen roughness. + * + * @type {?Texture} + * @default null + */ + this.sheenRoughnessMap = null; -function WebGLRenderStates( extensions, capabilities ) { + /** + * The red channel of this texture is multiplied against `transmission`, for per-pixel control over + * optical transparency. + * + * @type {?Texture} + * @default null + */ + this.transmissionMap = null; - let renderStates = new WeakMap(); + /** + * The thickness of the volume beneath the surface. The value is given in the + * coordinate space of the mesh. If the value is `0` the material is + * thin-walled. Otherwise the material is a volume boundary. + * + * @type {number} + * @default 0 + */ + this.thickness = 0; - function get( scene, renderCallDepth = 0 ) { + /** + * A texture that defines the thickness, stored in the green channel. This will + * be multiplied by `thickness`. + * + * @type {?Texture} + * @default null + */ + this.thicknessMap = null; - const renderStateArray = renderStates.get( scene ); - let renderState; + /** + * Density of the medium given as the average distance that light travels in + * the medium before interacting with a particle. The value is given in world + * space units, and must be greater than zero. + * + * @type {number} + * @default Infinity + */ + this.attenuationDistance = Infinity; - if ( renderStateArray === undefined ) { + /** + * The color that white light turns into due to absorption when reaching the + * attenuation distance. + * + * @type {Color} + * @default (1,1,1) + */ + this.attenuationColor = new Color( 1, 1, 1 ); - renderState = new WebGLRenderState( extensions, capabilities ); - renderStates.set( scene, [ renderState ] ); + /** + * A float that scales the amount of specular reflection for non-metals only. + * When set to zero, the model is effectively Lambertian. From `0.0` to `1.0`. + * + * @type {number} + * @default 1 + */ + this.specularIntensity = 1.0; - } else { + /** + * The alpha channel of this texture is multiplied against `specularIntensity`, + * for per-pixel control over specular intensity. + * + * @type {?Texture} + * @default null + */ + this.specularIntensityMap = null; - if ( renderCallDepth >= renderStateArray.length ) { + /** + * Tints the specular reflection at normal incidence for non-metals only. + * + * @type {Color} + * @default (1,1,1) + */ + this.specularColor = new Color( 1, 1, 1 ); - renderState = new WebGLRenderState( extensions, capabilities ); - renderStateArray.push( renderState ); + /** + * The RGB channels of this texture are multiplied against `specularColor`, + * for per-pixel control over specular color. + * + * @type {?Texture} + * @default null + */ + this.specularColorMap = null; - } else { + this._anisotropy = 0; + this._clearcoat = 0; + this._dispersion = 0; + this._iridescence = 0; + this._sheen = 0.0; + this._transmission = 0; - renderState = renderStateArray[ renderCallDepth ]; + this.setValues( parameters ); - } + } - } + /** + * The anisotropy strength. + * + * @type {number} + * @default 0 + */ + get anisotropy() { - return renderState; + return this._anisotropy; } - function dispose() { + set anisotropy( value ) { - renderStates = new WeakMap(); + if ( this._anisotropy > 0 !== value > 0 ) { - } + this.version ++; - return { - get: get, - dispose: dispose - }; + } -} + this._anisotropy = value; -class MeshDepthMaterial extends Material { + } - constructor( parameters ) { + /** + * Represents the intensity of the clear coat layer, from `0.0` to `1.0`. Use + * clear coat related properties to enable multilayer materials that have a + * thin translucent layer over the base layer. + * + * @type {number} + * @default 0 + */ + get clearcoat() { - super(); + return this._clearcoat; - this.isMeshDepthMaterial = true; + } - this.type = 'MeshDepthMaterial'; + set clearcoat( value ) { - this.depthPacking = BasicDepthPacking; + if ( this._clearcoat > 0 !== value > 0 ) { - this.map = null; + this.version ++; - this.alphaMap = null; + } - this.displacementMap = null; - this.displacementScale = 1; - this.displacementBias = 0; + this._clearcoat = value; - this.wireframe = false; - this.wireframeLinewidth = 1; + } + /** + * The intensity of the iridescence layer, simulating RGB color shift based on the angle between + * the surface and the viewer, from `0.0` to `1.0`. + * + * @type {number} + * @default 0 + */ + get iridescence() { - this.setValues( parameters ); + return this._iridescence; } - copy( source ) { + set iridescence( value ) { - super.copy( source ); + if ( this._iridescence > 0 !== value > 0 ) { - this.depthPacking = source.depthPacking; + this.version ++; - this.map = source.map; + } - this.alphaMap = source.alphaMap; + this._iridescence = value; - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; + } - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; + /** + * Defines the strength of the angular separation of colors (chromatic aberration) transmitting + * through a relatively clear volume. Any value zero or larger is valid, the typical range of + * realistic values is `[0, 1]`. This property can be only be used with transmissive objects. + * + * @type {number} + * @default 0 + */ + get dispersion() { - return this; + return this._dispersion; } -} + set dispersion( value ) { -class MeshDistanceMaterial extends Material { + if ( this._dispersion > 0 !== value > 0 ) { - constructor( parameters ) { + this.version ++; - super(); + } - this.isMeshDistanceMaterial = true; + this._dispersion = value; - this.type = 'MeshDistanceMaterial'; + } - this.map = null; + /** + * The intensity of the sheen layer, from `0.0` to `1.0`. + * + * @type {number} + * @default 0 + */ + get sheen() { - this.alphaMap = null; + return this._sheen; - this.displacementMap = null; - this.displacementScale = 1; - this.displacementBias = 0; + } - this.setValues( parameters ); + set sheen( value ) { - } + if ( this._sheen > 0 !== value > 0 ) { - copy( source ) { + this.version ++; - super.copy( source ); + } - this.map = source.map; + this._sheen = value; - this.alphaMap = source.alphaMap; + } - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; + /** + * Degree of transmission (or optical transparency), from `0.0` to `1.0`. + * + * Thin, transparent or semitransparent, plastic or glass materials remain + * largely reflective even if they are fully transmissive. The transmission + * property can be used to model these materials. + * + * When transmission is non-zero, `opacity` should be set to `1`. + * + * @type {number} + * @default 0 + */ + get transmission() { - return this; + return this._transmission; } -} + set transmission( value ) { -const vertex = "void main() {\n\tgl_Position = vec4( position, 1.0 );\n}"; + if ( this._transmission > 0 !== value > 0 ) { -const fragment = "uniform sampler2D shadow_pass;\nuniform vec2 resolution;\nuniform float radius;\n#include \nvoid main() {\n\tconst float samples = float( VSM_SAMPLES );\n\tfloat mean = 0.0;\n\tfloat squared_mean = 0.0;\n\tfloat uvStride = samples <= 1.0 ? 0.0 : 2.0 / ( samples - 1.0 );\n\tfloat uvStart = samples <= 1.0 ? 0.0 : - 1.0;\n\tfor ( float i = 0.0; i < samples; i ++ ) {\n\t\tfloat uvOffset = uvStart + i * uvStride;\n\t\t#ifdef HORIZONTAL_PASS\n\t\t\tvec2 distribution = unpackRGBATo2Half( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( uvOffset, 0.0 ) * radius ) / resolution ) );\n\t\t\tmean += distribution.x;\n\t\t\tsquared_mean += distribution.y * distribution.y + distribution.x * distribution.x;\n\t\t#else\n\t\t\tfloat depth = unpackRGBAToDepth( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( 0.0, uvOffset ) * radius ) / resolution ) );\n\t\t\tmean += depth;\n\t\t\tsquared_mean += depth * depth;\n\t\t#endif\n\t}\n\tmean = mean / samples;\n\tsquared_mean = squared_mean / samples;\n\tfloat std_dev = sqrt( squared_mean - mean * mean );\n\tgl_FragColor = pack2HalfToRGBA( vec2( mean, std_dev ) );\n}"; + this.version ++; -function WebGLShadowMap( _renderer, _objects, _capabilities ) { + } - let _frustum = new Frustum(); + this._transmission = value; - const _shadowMapSize = new Vector2(), - _viewportSize = new Vector2(), + } - _viewport = new Vector4(), + copy( source ) { - _depthMaterial = new MeshDepthMaterial( { depthPacking: RGBADepthPacking } ), - _distanceMaterial = new MeshDistanceMaterial(), + super.copy( source ); - _materialCache = {}, + this.defines = { - _maxTextureSize = _capabilities.maxTextureSize; + 'STANDARD': '', + 'PHYSICAL': '' - const shadowSide = { [ FrontSide ]: BackSide, [ BackSide ]: FrontSide, [ DoubleSide ]: DoubleSide }; + }; - const shadowMaterialVertical = new ShaderMaterial( { - defines: { - VSM_SAMPLES: 8 - }, - uniforms: { - shadow_pass: { value: null }, - resolution: { value: new Vector2() }, - radius: { value: 4.0 } - }, + this.anisotropy = source.anisotropy; + this.anisotropyRotation = source.anisotropyRotation; + this.anisotropyMap = source.anisotropyMap; - vertexShader: vertex, - fragmentShader: fragment + this.clearcoat = source.clearcoat; + this.clearcoatMap = source.clearcoatMap; + this.clearcoatRoughness = source.clearcoatRoughness; + this.clearcoatRoughnessMap = source.clearcoatRoughnessMap; + this.clearcoatNormalMap = source.clearcoatNormalMap; + this.clearcoatNormalScale.copy( source.clearcoatNormalScale ); - } ); + this.dispersion = source.dispersion; + this.ior = source.ior; - const shadowMaterialHorizontal = shadowMaterialVertical.clone(); - shadowMaterialHorizontal.defines.HORIZONTAL_PASS = 1; + this.iridescence = source.iridescence; + this.iridescenceMap = source.iridescenceMap; + this.iridescenceIOR = source.iridescenceIOR; + this.iridescenceThicknessRange = [ ...source.iridescenceThicknessRange ]; + this.iridescenceThicknessMap = source.iridescenceThicknessMap; - const fullScreenTri = new BufferGeometry(); - fullScreenTri.setAttribute( - 'position', - new BufferAttribute( - new Float32Array( [ - 1, - 1, 0.5, 3, - 1, 0.5, - 1, 3, 0.5 ] ), - 3 - ) - ); + this.sheen = source.sheen; + this.sheenColor.copy( source.sheenColor ); + this.sheenColorMap = source.sheenColorMap; + this.sheenRoughness = source.sheenRoughness; + this.sheenRoughnessMap = source.sheenRoughnessMap; - const fullScreenMesh = new Mesh( fullScreenTri, shadowMaterialVertical ); + this.transmission = source.transmission; + this.transmissionMap = source.transmissionMap; - const scope = this; + this.thickness = source.thickness; + this.thicknessMap = source.thicknessMap; + this.attenuationDistance = source.attenuationDistance; + this.attenuationColor.copy( source.attenuationColor ); - this.enabled = false; + this.specularIntensity = source.specularIntensity; + this.specularIntensityMap = source.specularIntensityMap; + this.specularColor.copy( source.specularColor ); + this.specularColorMap = source.specularColorMap; - this.autoUpdate = true; - this.needsUpdate = false; + return this; - this.type = PCFShadowMap; - let _previousType = this.type; + } - this.render = function ( lights, scene, camera ) { +} - if ( scope.enabled === false ) return; - if ( scope.autoUpdate === false && scope.needsUpdate === false ) return; +/** + * A material for shiny surfaces with specular highlights. + * + * The material uses a non-physically based [Blinn-Phong]{@link https://en.wikipedia.org/wiki/Blinn-Phong_shading_model} + * model for calculating reflectance. Unlike the Lambertian model used in the + * {@link MeshLambertMaterial} this can simulate shiny surfaces with specular + * highlights (such as varnished wood). `MeshPhongMaterial` uses per-fragment shading. + * + * Performance will generally be greater when using this material over the + * {@link MeshStandardMaterial} or {@link MeshPhysicalMaterial}, at the cost of + * some graphical accuracy. + * + * @augments Material + */ +class MeshPhongMaterial extends Material { - if ( lights.length === 0 ) return; + /** + * Constructs a new mesh phong material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { - const currentRenderTarget = _renderer.getRenderTarget(); - const activeCubeFace = _renderer.getActiveCubeFace(); - const activeMipmapLevel = _renderer.getActiveMipmapLevel(); + super(); - const _state = _renderer.state; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshPhongMaterial = true; - // Set GL state for depth map. - _state.setBlending( NoBlending ); - _state.buffers.color.setClear( 1, 1, 1, 1 ); - _state.buffers.depth.setTest( true ); - _state.setScissorTest( false ); + this.type = 'MeshPhongMaterial'; - // check for shadow map type changes + /** + * Color of the material. + * + * @type {Color} + * @default (1,1,1) + */ + this.color = new Color( 0xffffff ); // diffuse - const toVSM = ( _previousType !== VSMShadowMap && this.type === VSMShadowMap ); - const fromVSM = ( _previousType === VSMShadowMap && this.type !== VSMShadowMap ); + /** + * Specular color of the material. The default color is set to `0x111111` (very dark grey) + * + * This defines how shiny the material is and the color of its shine. + * + * @type {Color} + */ + this.specular = new Color( 0x111111 ); - // render depth map + /** + * How shiny the specular highlight is; a higher value gives a sharper highlight. + * + * @type {number} + * @default 30 + */ + this.shininess = 30; - for ( let i = 0, il = lights.length; i < il; i ++ ) { + /** + * The color map. May optionally include an alpha channel, typically combined + * with {@link Material#transparent} or {@link Material#alphaTest}. The texture map + * color is modulated by the diffuse `color`. + * + * @type {?Texture} + * @default null + */ + this.map = null; - const light = lights[ i ]; - const shadow = light.shadow; + /** + * The light map. Requires a second set of UVs. + * + * @type {?Texture} + * @default null + */ + this.lightMap = null; - if ( shadow === undefined ) { + /** + * Intensity of the baked light. + * + * @type {number} + * @default 1 + */ + this.lightMapIntensity = 1.0; - console.warn( 'THREE.WebGLShadowMap:', light, 'has no shadow.' ); - continue; + /** + * The red channel of this texture is used as the ambient occlusion map. + * Requires a second set of UVs. + * + * @type {?Texture} + * @default null + */ + this.aoMap = null; - } + /** + * Intensity of the ambient occlusion effect. Range is `[0,1]`, where `0` + * disables ambient occlusion. Where intensity is `1` and the AO map's + * red channel is also `1`, ambient light is fully occluded on a surface. + * + * @type {number} + * @default 1 + */ + this.aoMapIntensity = 1.0; - if ( shadow.autoUpdate === false && shadow.needsUpdate === false ) continue; + /** + * Emissive (light) color of the material, essentially a solid color + * unaffected by other lighting. + * + * @type {Color} + * @default (0,0,0) + */ + this.emissive = new Color( 0x000000 ); - _shadowMapSize.copy( shadow.mapSize ); + /** + * Intensity of the emissive light. Modulates the emissive color. + * + * @type {number} + * @default 1 + */ + this.emissiveIntensity = 1.0; - const shadowFrameExtents = shadow.getFrameExtents(); + /** + * Set emissive (glow) map. The emissive map color is modulated by the + * emissive color and the emissive intensity. If you have an emissive map, + * be sure to set the emissive color to something other than black. + * + * @type {?Texture} + * @default null + */ + this.emissiveMap = null; - _shadowMapSize.multiply( shadowFrameExtents ); + /** + * The texture to create a bump map. The black and white values map to the + * perceived depth in relation to the lights. Bump doesn't actually affect + * the geometry of the object, only the lighting. If a normal map is defined + * this will be ignored. + * + * @type {?Texture} + * @default null + */ + this.bumpMap = null; + + /** + * How much the bump map affects the material. Typical range is `[0,1]`. + * + * @type {number} + * @default 1 + */ + this.bumpScale = 1; - _viewportSize.copy( shadow.mapSize ); + /** + * The texture to create a normal map. The RGB values affect the surface + * normal for each pixel fragment and change the way the color is lit. Normal + * maps do not change the actual shape of the surface, only the lighting. In + * case the material has a normal map authored using the left handed + * convention, the `y` component of `normalScale` should be negated to compensate + * for the different handedness. + * + * @type {?Texture} + * @default null + */ + this.normalMap = null; - if ( _shadowMapSize.x > _maxTextureSize || _shadowMapSize.y > _maxTextureSize ) { + /** + * The type of normal map. + * + * @type {(TangentSpaceNormalMap|ObjectSpaceNormalMap)} + * @default TangentSpaceNormalMap + */ + this.normalMapType = TangentSpaceNormalMap; - if ( _shadowMapSize.x > _maxTextureSize ) { + /** + * How much the normal map affects the material. Typical value range is `[0,1]`. + * + * @type {Vector2} + * @default (1,1) + */ + this.normalScale = new Vector2( 1, 1 ); - _viewportSize.x = Math.floor( _maxTextureSize / shadowFrameExtents.x ); - _shadowMapSize.x = _viewportSize.x * shadowFrameExtents.x; - shadow.mapSize.x = _viewportSize.x; + /** + * The displacement map affects the position of the mesh's vertices. Unlike + * other maps which only affect the light and shade of the material the + * displaced vertices can cast shadows, block other objects, and otherwise + * act as real geometry. The displacement texture is an image where the value + * of each pixel (white being the highest) is mapped against, and + * repositions, the vertices of the mesh. + * + * @type {?Texture} + * @default null + */ + this.displacementMap = null; - } + /** + * How much the displacement map affects the mesh (where black is no + * displacement, and white is maximum displacement). Without a displacement + * map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementScale = 1; - if ( _shadowMapSize.y > _maxTextureSize ) { + /** + * The offset of the displacement map's values on the mesh's vertices. + * The bias is added to the scaled sample of the displacement map. + * Without a displacement map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementBias = 0; - _viewportSize.y = Math.floor( _maxTextureSize / shadowFrameExtents.y ); - _shadowMapSize.y = _viewportSize.y * shadowFrameExtents.y; - shadow.mapSize.y = _viewportSize.y; + /** + * The specular map value affects both how much the specular surface + * highlight contributes and how much of the environment map affects the + * surface. + * + * @type {?Texture} + * @default null + */ + this.specularMap = null; - } + /** + * The alpha map is a grayscale texture that controls the opacity across the + * surface (black: fully transparent; white: fully opaque). + * + * Only the color of the texture is used, ignoring the alpha channel if one + * exists. For RGB and RGBA textures, the renderer will use the green channel + * when sampling this texture due to the extra bit of precision provided for + * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and + * luminance/alpha textures will also still work as expected. + * + * @type {?Texture} + * @default null + */ + this.alphaMap = null; - } + /** + * The environment map. + * + * @type {?Texture} + * @default null + */ + this.envMap = null; - if ( shadow.map === null || toVSM === true || fromVSM === true ) { + /** + * The rotation of the environment map in radians. + * + * @type {Euler} + * @default (0,0,0) + */ + this.envMapRotation = new Euler(); - const pars = ( this.type !== VSMShadowMap ) ? { minFilter: NearestFilter, magFilter: NearestFilter } : {}; + /** + * How to combine the result of the surface's color with the environment map, if any. + * + * When set to `MixOperation`, the {@link MeshBasicMaterial#reflectivity} is used to + * blend between the two colors. + * + * @type {(MultiplyOperation|MixOperation|AddOperation)} + * @default MultiplyOperation + */ + this.combine = MultiplyOperation; - if ( shadow.map !== null ) { + /** + * How much the environment map affects the surface. + * The valid range is between `0` (no reflections) and `1` (full reflections). + * + * @type {number} + * @default 1 + */ + this.reflectivity = 1; - shadow.map.dispose(); + /** + * The index of refraction (IOR) of air (approximately 1) divided by the + * index of refraction of the material. It is used with environment mapping + * modes {@link CubeRefractionMapping} and {@link EquirectangularRefractionMapping}. + * The refraction ratio should not exceed `1`. + * + * @type {number} + * @default 0.98 + */ + this.refractionRatio = 0.98; - } + /** + * Renders the geometry as a wireframe. + * + * @type {boolean} + * @default false + */ + this.wireframe = false; - shadow.map = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y, pars ); - shadow.map.texture.name = light.name + '.shadowMap'; + /** + * Controls the thickness of the wireframe. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {number} + * @default 1 + */ + this.wireframeLinewidth = 1; - shadow.camera.updateProjectionMatrix(); + /** + * Defines appearance of wireframe ends. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ + this.wireframeLinecap = 'round'; - } + /** + * Defines appearance of wireframe joints. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ + this.wireframeLinejoin = 'round'; - _renderer.setRenderTarget( shadow.map ); - _renderer.clear(); + /** + * Whether the material is rendered with flat shading or not. + * + * @type {boolean} + * @default false + */ + this.flatShading = false; - const viewportCount = shadow.getViewportCount(); + /** + * Whether the material is affected by fog or not. + * + * @type {boolean} + * @default true + */ + this.fog = true; - for ( let vp = 0; vp < viewportCount; vp ++ ) { + this.setValues( parameters ); - const viewport = shadow.getViewport( vp ); + } - _viewport.set( - _viewportSize.x * viewport.x, - _viewportSize.y * viewport.y, - _viewportSize.x * viewport.z, - _viewportSize.y * viewport.w - ); + copy( source ) { - _state.viewport( _viewport ); + super.copy( source ); - shadow.updateMatrices( light, vp ); + this.color.copy( source.color ); + this.specular.copy( source.specular ); + this.shininess = source.shininess; - _frustum = shadow.getFrustum(); + this.map = source.map; - renderObject( scene, camera, shadow.camera, light, this.type ); + this.lightMap = source.lightMap; + this.lightMapIntensity = source.lightMapIntensity; - } + this.aoMap = source.aoMap; + this.aoMapIntensity = source.aoMapIntensity; - // do blur pass for VSM + this.emissive.copy( source.emissive ); + this.emissiveMap = source.emissiveMap; + this.emissiveIntensity = source.emissiveIntensity; - if ( shadow.isPointLightShadow !== true && this.type === VSMShadowMap ) { + this.bumpMap = source.bumpMap; + this.bumpScale = source.bumpScale; - VSMPass( shadow, camera ); + this.normalMap = source.normalMap; + this.normalMapType = source.normalMapType; + this.normalScale.copy( source.normalScale ); - } + this.displacementMap = source.displacementMap; + this.displacementScale = source.displacementScale; + this.displacementBias = source.displacementBias; - shadow.needsUpdate = false; + this.specularMap = source.specularMap; - } + this.alphaMap = source.alphaMap; - _previousType = this.type; + this.envMap = source.envMap; + this.envMapRotation.copy( source.envMapRotation ); + this.combine = source.combine; + this.reflectivity = source.reflectivity; + this.refractionRatio = source.refractionRatio; - scope.needsUpdate = false; + this.wireframe = source.wireframe; + this.wireframeLinewidth = source.wireframeLinewidth; + this.wireframeLinecap = source.wireframeLinecap; + this.wireframeLinejoin = source.wireframeLinejoin; - _renderer.setRenderTarget( currentRenderTarget, activeCubeFace, activeMipmapLevel ); + this.flatShading = source.flatShading; - }; + this.fog = source.fog; - function VSMPass( shadow, camera ) { + return this; - const geometry = _objects.update( fullScreenMesh ); + } - if ( shadowMaterialVertical.defines.VSM_SAMPLES !== shadow.blurSamples ) { +} - shadowMaterialVertical.defines.VSM_SAMPLES = shadow.blurSamples; - shadowMaterialHorizontal.defines.VSM_SAMPLES = shadow.blurSamples; +/** + * A material implementing toon shading. + * + * @augments Material + */ +class MeshToonMaterial extends Material { - shadowMaterialVertical.needsUpdate = true; - shadowMaterialHorizontal.needsUpdate = true; + /** + * Constructs a new mesh toon material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { - } + super(); - if ( shadow.mapPass === null ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshToonMaterial = true; - shadow.mapPass = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y ); + this.defines = { 'TOON': '' }; - } + this.type = 'MeshToonMaterial'; - // vertical pass + /** + * Color of the material. + * + * @type {Color} + * @default (1,1,1) + */ + this.color = new Color( 0xffffff ); - shadowMaterialVertical.uniforms.shadow_pass.value = shadow.map.texture; - shadowMaterialVertical.uniforms.resolution.value = shadow.mapSize; - shadowMaterialVertical.uniforms.radius.value = shadow.radius; - _renderer.setRenderTarget( shadow.mapPass ); - _renderer.clear(); - _renderer.renderBufferDirect( camera, null, geometry, shadowMaterialVertical, fullScreenMesh, null ); + /** + * The color map. May optionally include an alpha channel, typically combined + * with {@link Material#transparent} or {@link Material#alphaTest}. The texture map + * color is modulated by the diffuse `color`. + * + * @type {?Texture} + * @default null + */ + this.map = null; - // horizontal pass + /** + * Gradient map for toon shading. It's required to set + * {@link Texture#minFilter} and {@link Texture#magFilter} to {@linkNearestFilter} + * when using this type of texture. + * + * @type {?Texture} + * @default null + */ + this.gradientMap = null; - shadowMaterialHorizontal.uniforms.shadow_pass.value = shadow.mapPass.texture; - shadowMaterialHorizontal.uniforms.resolution.value = shadow.mapSize; - shadowMaterialHorizontal.uniforms.radius.value = shadow.radius; - _renderer.setRenderTarget( shadow.map ); - _renderer.clear(); - _renderer.renderBufferDirect( camera, null, geometry, shadowMaterialHorizontal, fullScreenMesh, null ); + /** + * The light map. Requires a second set of UVs. + * + * @type {?Texture} + * @default null + */ + this.lightMap = null; - } + /** + * Intensity of the baked light. + * + * @type {number} + * @default 1 + */ + this.lightMapIntensity = 1.0; - function getDepthMaterial( object, material, light, type ) { + /** + * The red channel of this texture is used as the ambient occlusion map. + * Requires a second set of UVs. + * + * @type {?Texture} + * @default null + */ + this.aoMap = null; - let result = null; + /** + * Intensity of the ambient occlusion effect. Range is `[0,1]`, where `0` + * disables ambient occlusion. Where intensity is `1` and the AO map's + * red channel is also `1`, ambient light is fully occluded on a surface. + * + * @type {number} + * @default 1 + */ + this.aoMapIntensity = 1.0; - const customMaterial = ( light.isPointLight === true ) ? object.customDistanceMaterial : object.customDepthMaterial; + /** + * Emissive (light) color of the material, essentially a solid color + * unaffected by other lighting. + * + * @type {Color} + * @default (0,0,0) + */ + this.emissive = new Color( 0x000000 ); - if ( customMaterial !== undefined ) { + /** + * Intensity of the emissive light. Modulates the emissive color. + * + * @type {number} + * @default 1 + */ + this.emissiveIntensity = 1.0; - result = customMaterial; + /** + * Set emissive (glow) map. The emissive map color is modulated by the + * emissive color and the emissive intensity. If you have an emissive map, + * be sure to set the emissive color to something other than black. + * + * @type {?Texture} + * @default null + */ + this.emissiveMap = null; - } else { + /** + * The texture to create a bump map. The black and white values map to the + * perceived depth in relation to the lights. Bump doesn't actually affect + * the geometry of the object, only the lighting. If a normal map is defined + * this will be ignored. + * + * @type {?Texture} + * @default null + */ + this.bumpMap = null; - result = ( light.isPointLight === true ) ? _distanceMaterial : _depthMaterial; + /** + * How much the bump map affects the material. Typical range is `[0,1]`. + * + * @type {number} + * @default 1 + */ + this.bumpScale = 1; - if ( ( _renderer.localClippingEnabled && material.clipShadows === true && Array.isArray( material.clippingPlanes ) && material.clippingPlanes.length !== 0 ) || - ( material.displacementMap && material.displacementScale !== 0 ) || - ( material.alphaMap && material.alphaTest > 0 ) || - ( material.map && material.alphaTest > 0 ) ) { + /** + * The texture to create a normal map. The RGB values affect the surface + * normal for each pixel fragment and change the way the color is lit. Normal + * maps do not change the actual shape of the surface, only the lighting. In + * case the material has a normal map authored using the left handed + * convention, the `y` component of `normalScale` should be negated to compensate + * for the different handedness. + * + * @type {?Texture} + * @default null + */ + this.normalMap = null; - // in this case we need a unique material instance reflecting the - // appropriate state + /** + * The type of normal map. + * + * @type {(TangentSpaceNormalMap|ObjectSpaceNormalMap)} + * @default TangentSpaceNormalMap + */ + this.normalMapType = TangentSpaceNormalMap; - const keyA = result.uuid, keyB = material.uuid; + /** + * How much the normal map affects the material. Typical value range is `[0,1]`. + * + * @type {Vector2} + * @default (1,1) + */ + this.normalScale = new Vector2( 1, 1 ); - let materialsForVariant = _materialCache[ keyA ]; + /** + * The displacement map affects the position of the mesh's vertices. Unlike + * other maps which only affect the light and shade of the material the + * displaced vertices can cast shadows, block other objects, and otherwise + * act as real geometry. The displacement texture is an image where the value + * of each pixel (white being the highest) is mapped against, and + * repositions, the vertices of the mesh. + * + * @type {?Texture} + * @default null + */ + this.displacementMap = null; - if ( materialsForVariant === undefined ) { + /** + * How much the displacement map affects the mesh (where black is no + * displacement, and white is maximum displacement). Without a displacement + * map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementScale = 1; - materialsForVariant = {}; - _materialCache[ keyA ] = materialsForVariant; + /** + * The offset of the displacement map's values on the mesh's vertices. + * The bias is added to the scaled sample of the displacement map. + * Without a displacement map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementBias = 0; - } + /** + * The alpha map is a grayscale texture that controls the opacity across the + * surface (black: fully transparent; white: fully opaque). + * + * Only the color of the texture is used, ignoring the alpha channel if one + * exists. For RGB and RGBA textures, the renderer will use the green channel + * when sampling this texture due to the extra bit of precision provided for + * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and + * luminance/alpha textures will also still work as expected. + * + * @type {?Texture} + * @default null + */ + this.alphaMap = null; - let cachedMaterial = materialsForVariant[ keyB ]; + /** + * Renders the geometry as a wireframe. + * + * @type {boolean} + * @default false + */ + this.wireframe = false; - if ( cachedMaterial === undefined ) { + /** + * Controls the thickness of the wireframe. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {number} + * @default 1 + */ + this.wireframeLinewidth = 1; - cachedMaterial = result.clone(); - materialsForVariant[ keyB ] = cachedMaterial; + /** + * Defines appearance of wireframe ends. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ + this.wireframeLinecap = 'round'; - } + /** + * Defines appearance of wireframe joints. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ + this.wireframeLinejoin = 'round'; - result = cachedMaterial; + /** + * Whether the material is affected by fog or not. + * + * @type {boolean} + * @default true + */ + this.fog = true; - } + this.setValues( parameters ); - } + } - result.visible = material.visible; - result.wireframe = material.wireframe; + copy( source ) { - if ( type === VSMShadowMap ) { + super.copy( source ); - result.side = ( material.shadowSide !== null ) ? material.shadowSide : material.side; + this.color.copy( source.color ); - } else { + this.map = source.map; + this.gradientMap = source.gradientMap; - result.side = ( material.shadowSide !== null ) ? material.shadowSide : shadowSide[ material.side ]; + this.lightMap = source.lightMap; + this.lightMapIntensity = source.lightMapIntensity; - } + this.aoMap = source.aoMap; + this.aoMapIntensity = source.aoMapIntensity; - result.alphaMap = material.alphaMap; - result.alphaTest = material.alphaTest; - result.map = material.map; + this.emissive.copy( source.emissive ); + this.emissiveMap = source.emissiveMap; + this.emissiveIntensity = source.emissiveIntensity; - result.clipShadows = material.clipShadows; - result.clippingPlanes = material.clippingPlanes; - result.clipIntersection = material.clipIntersection; + this.bumpMap = source.bumpMap; + this.bumpScale = source.bumpScale; - result.displacementMap = material.displacementMap; - result.displacementScale = material.displacementScale; - result.displacementBias = material.displacementBias; + this.normalMap = source.normalMap; + this.normalMapType = source.normalMapType; + this.normalScale.copy( source.normalScale ); - result.wireframeLinewidth = material.wireframeLinewidth; - result.linewidth = material.linewidth; + this.displacementMap = source.displacementMap; + this.displacementScale = source.displacementScale; + this.displacementBias = source.displacementBias; - if ( light.isPointLight === true && result.isMeshDistanceMaterial === true ) { + this.alphaMap = source.alphaMap; - const materialProperties = _renderer.properties.get( result ); - materialProperties.light = light; + this.wireframe = source.wireframe; + this.wireframeLinewidth = source.wireframeLinewidth; + this.wireframeLinecap = source.wireframeLinecap; + this.wireframeLinejoin = source.wireframeLinejoin; - } + this.fog = source.fog; - return result; + return this; } - function renderObject( object, camera, shadowCamera, light, type ) { +} - if ( object.visible === false ) return; +/** + * A material that maps the normal vectors to RGB colors. + * + * @augments Material + */ +class MeshNormalMaterial extends Material { - const visible = object.layers.test( camera.layers ); + /** + * Constructs a new mesh normal material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { - if ( visible && ( object.isMesh || object.isLine || object.isPoints ) ) { + super(); - if ( ( object.castShadow || ( object.receiveShadow && type === VSMShadowMap ) ) && ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshNormalMaterial = true; - object.modelViewMatrix.multiplyMatrices( shadowCamera.matrixWorldInverse, object.matrixWorld ); + this.type = 'MeshNormalMaterial'; - const geometry = _objects.update( object ); - const material = object.material; + /** + * The texture to create a bump map. The black and white values map to the + * perceived depth in relation to the lights. Bump doesn't actually affect + * the geometry of the object, only the lighting. If a normal map is defined + * this will be ignored. + * + * @type {?Texture} + * @default null + */ + this.bumpMap = null; - if ( Array.isArray( material ) ) { + /** + * How much the bump map affects the material. Typical range is `[0,1]`. + * + * @type {number} + * @default 1 + */ + this.bumpScale = 1; - const groups = geometry.groups; + /** + * The texture to create a normal map. The RGB values affect the surface + * normal for each pixel fragment and change the way the color is lit. Normal + * maps do not change the actual shape of the surface, only the lighting. In + * case the material has a normal map authored using the left handed + * convention, the `y` component of `normalScale` should be negated to compensate + * for the different handedness. + * + * @type {?Texture} + * @default null + */ + this.normalMap = null; - for ( let k = 0, kl = groups.length; k < kl; k ++ ) { + /** + * The type of normal map. + * + * @type {(TangentSpaceNormalMap|ObjectSpaceNormalMap)} + * @default TangentSpaceNormalMap + */ + this.normalMapType = TangentSpaceNormalMap; - const group = groups[ k ]; - const groupMaterial = material[ group.materialIndex ]; + /** + * How much the normal map affects the material. Typical value range is `[0,1]`. + * + * @type {Vector2} + * @default (1,1) + */ + this.normalScale = new Vector2( 1, 1 ); - if ( groupMaterial && groupMaterial.visible ) { + /** + * The displacement map affects the position of the mesh's vertices. Unlike + * other maps which only affect the light and shade of the material the + * displaced vertices can cast shadows, block other objects, and otherwise + * act as real geometry. The displacement texture is an image where the value + * of each pixel (white being the highest) is mapped against, and + * repositions, the vertices of the mesh. + * + * @type {?Texture} + * @default null + */ + this.displacementMap = null; - const depthMaterial = getDepthMaterial( object, groupMaterial, light, type ); + /** + * How much the displacement map affects the mesh (where black is no + * displacement, and white is maximum displacement). Without a displacement + * map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementScale = 1; - _renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, group ); + /** + * The offset of the displacement map's values on the mesh's vertices. + * The bias is added to the scaled sample of the displacement map. + * Without a displacement map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementBias = 0; - } + /** + * Renders the geometry as a wireframe. + * + * @type {boolean} + * @default false + */ + this.wireframe = false; - } + /** + * Controls the thickness of the wireframe. + * + * WebGL and WebGPU ignore this property and always render + * 1 pixel wide lines. + * + * @type {number} + * @default 1 + */ + this.wireframeLinewidth = 1; - } else if ( material.visible ) { + /** + * Whether the material is rendered with flat shading or not. + * + * @type {boolean} + * @default false + */ + this.flatShading = false; - const depthMaterial = getDepthMaterial( object, material, light, type ); + this.setValues( parameters ); - _renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, null ); + } - } + copy( source ) { - } + super.copy( source ); - } + this.bumpMap = source.bumpMap; + this.bumpScale = source.bumpScale; - const children = object.children; + this.normalMap = source.normalMap; + this.normalMapType = source.normalMapType; + this.normalScale.copy( source.normalScale ); - for ( let i = 0, l = children.length; i < l; i ++ ) { + this.displacementMap = source.displacementMap; + this.displacementScale = source.displacementScale; + this.displacementBias = source.displacementBias; - renderObject( children[ i ], camera, shadowCamera, light, type ); + this.wireframe = source.wireframe; + this.wireframeLinewidth = source.wireframeLinewidth; - } + this.flatShading = source.flatShading; + + return this; } } -function WebGLState( gl, extensions, capabilities ) { - - const isWebGL2 = capabilities.isWebGL2; +/** + * A material for non-shiny surfaces, without specular highlights. + * + * The material uses a non-physically based [Lambertian]{@link https://en.wikipedia.org/wiki/Lambertian_reflectance} + * model for calculating reflectance. This can simulate some surfaces (such + * as untreated wood or stone) well, but cannot simulate shiny surfaces with + * specular highlights (such as varnished wood). `MeshLambertMaterial` uses per-fragment + * shading. + * + * Due to the simplicity of the reflectance and illumination models, + * performance will be greater when using this material over the + * {@link MeshPhongMaterial}, {@link MeshStandardMaterial} or + * {@link MeshPhysicalMaterial}, at the cost of some graphical accuracy. + * + * @augments Material + */ +class MeshLambertMaterial extends Material { - function ColorBuffer() { + /** + * Constructs a new mesh lambert material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { - let locked = false; + super(); - const color = new Vector4(); - let currentColorMask = null; - const currentColorClear = new Vector4( 0, 0, 0, 0 ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshLambertMaterial = true; - return { + this.type = 'MeshLambertMaterial'; - setMask: function ( colorMask ) { + /** + * Color of the material. + * + * @type {Color} + * @default (1,1,1) + */ + this.color = new Color( 0xffffff ); // diffuse - if ( currentColorMask !== colorMask && ! locked ) { + /** + * The color map. May optionally include an alpha channel, typically combined + * with {@link Material#transparent} or {@link Material#alphaTest}. The texture map + * color is modulated by the diffuse `color`. + * + * @type {?Texture} + * @default null + */ + this.map = null; - gl.colorMask( colorMask, colorMask, colorMask, colorMask ); - currentColorMask = colorMask; + /** + * The light map. Requires a second set of UVs. + * + * @type {?Texture} + * @default null + */ + this.lightMap = null; - } + /** + * Intensity of the baked light. + * + * @type {number} + * @default 1 + */ + this.lightMapIntensity = 1.0; - }, + /** + * The red channel of this texture is used as the ambient occlusion map. + * Requires a second set of UVs. + * + * @type {?Texture} + * @default null + */ + this.aoMap = null; - setLocked: function ( lock ) { + /** + * Intensity of the ambient occlusion effect. Range is `[0,1]`, where `0` + * disables ambient occlusion. Where intensity is `1` and the AO map's + * red channel is also `1`, ambient light is fully occluded on a surface. + * + * @type {number} + * @default 1 + */ + this.aoMapIntensity = 1.0; - locked = lock; + /** + * Emissive (light) color of the material, essentially a solid color + * unaffected by other lighting. + * + * @type {Color} + * @default (0,0,0) + */ + this.emissive = new Color( 0x000000 ); - }, + /** + * Intensity of the emissive light. Modulates the emissive color. + * + * @type {number} + * @default 1 + */ + this.emissiveIntensity = 1.0; - setClear: function ( r, g, b, a, premultipliedAlpha ) { + /** + * Set emissive (glow) map. The emissive map color is modulated by the + * emissive color and the emissive intensity. If you have an emissive map, + * be sure to set the emissive color to something other than black. + * + * @type {?Texture} + * @default null + */ + this.emissiveMap = null; - if ( premultipliedAlpha === true ) { + /** + * The texture to create a bump map. The black and white values map to the + * perceived depth in relation to the lights. Bump doesn't actually affect + * the geometry of the object, only the lighting. If a normal map is defined + * this will be ignored. + * + * @type {?Texture} + * @default null + */ + this.bumpMap = null; - r *= a; g *= a; b *= a; + /** + * How much the bump map affects the material. Typical range is `[0,1]`. + * + * @type {number} + * @default 1 + */ + this.bumpScale = 1; - } + /** + * The texture to create a normal map. The RGB values affect the surface + * normal for each pixel fragment and change the way the color is lit. Normal + * maps do not change the actual shape of the surface, only the lighting. In + * case the material has a normal map authored using the left handed + * convention, the `y` component of `normalScale` should be negated to compensate + * for the different handedness. + * + * @type {?Texture} + * @default null + */ + this.normalMap = null; - color.set( r, g, b, a ); + /** + * The type of normal map. + * + * @type {(TangentSpaceNormalMap|ObjectSpaceNormalMap)} + * @default TangentSpaceNormalMap + */ + this.normalMapType = TangentSpaceNormalMap; - if ( currentColorClear.equals( color ) === false ) { + /** + * How much the normal map affects the material. Typical value range is `[0,1]`. + * + * @type {Vector2} + * @default (1,1) + */ + this.normalScale = new Vector2( 1, 1 ); - gl.clearColor( r, g, b, a ); - currentColorClear.copy( color ); + /** + * The displacement map affects the position of the mesh's vertices. Unlike + * other maps which only affect the light and shade of the material the + * displaced vertices can cast shadows, block other objects, and otherwise + * act as real geometry. The displacement texture is an image where the value + * of each pixel (white being the highest) is mapped against, and + * repositions, the vertices of the mesh. + * + * @type {?Texture} + * @default null + */ + this.displacementMap = null; - } + /** + * How much the displacement map affects the mesh (where black is no + * displacement, and white is maximum displacement). Without a displacement + * map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementScale = 1; - }, + /** + * The offset of the displacement map's values on the mesh's vertices. + * The bias is added to the scaled sample of the displacement map. + * Without a displacement map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementBias = 0; - reset: function () { + /** + * Specular map used by the material. + * + * @type {?Texture} + * @default null + */ + this.specularMap = null; - locked = false; + /** + * The alpha map is a grayscale texture that controls the opacity across the + * surface (black: fully transparent; white: fully opaque). + * + * Only the color of the texture is used, ignoring the alpha channel if one + * exists. For RGB and RGBA textures, the renderer will use the green channel + * when sampling this texture due to the extra bit of precision provided for + * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and + * luminance/alpha textures will also still work as expected. + * + * @type {?Texture} + * @default null + */ + this.alphaMap = null; - currentColorMask = null; - currentColorClear.set( - 1, 0, 0, 0 ); // set to invalid state + /** + * The environment map. + * + * @type {?Texture} + * @default null + */ + this.envMap = null; - } + /** + * The rotation of the environment map in radians. + * + * @type {Euler} + * @default (0,0,0) + */ + this.envMapRotation = new Euler(); - }; + /** + * How to combine the result of the surface's color with the environment map, if any. + * + * When set to `MixOperation`, the {@link MeshBasicMaterial#reflectivity} is used to + * blend between the two colors. + * + * @type {(MultiplyOperation|MixOperation|AddOperation)} + * @default MultiplyOperation + */ + this.combine = MultiplyOperation; - } + /** + * How much the environment map affects the surface. + * The valid range is between `0` (no reflections) and `1` (full reflections). + * + * @type {number} + * @default 1 + */ + this.reflectivity = 1; - function DepthBuffer() { + /** + * The index of refraction (IOR) of air (approximately 1) divided by the + * index of refraction of the material. It is used with environment mapping + * modes {@link CubeRefractionMapping} and {@link EquirectangularRefractionMapping}. + * The refraction ratio should not exceed `1`. + * + * @type {number} + * @default 0.98 + */ + this.refractionRatio = 0.98; - let locked = false; + /** + * Renders the geometry as a wireframe. + * + * @type {boolean} + * @default false + */ + this.wireframe = false; - let currentDepthMask = null; - let currentDepthFunc = null; - let currentDepthClear = null; + /** + * Controls the thickness of the wireframe. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {number} + * @default 1 + */ + this.wireframeLinewidth = 1; - return { + /** + * Defines appearance of wireframe ends. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ + this.wireframeLinecap = 'round'; - setTest: function ( depthTest ) { + /** + * Defines appearance of wireframe joints. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ + this.wireframeLinejoin = 'round'; - if ( depthTest ) { + /** + * Whether the material is rendered with flat shading or not. + * + * @type {boolean} + * @default false + */ + this.flatShading = false; - enable( gl.DEPTH_TEST ); + /** + * Whether the material is affected by fog or not. + * + * @type {boolean} + * @default true + */ + this.fog = true; - } else { + this.setValues( parameters ); - disable( gl.DEPTH_TEST ); + } - } + copy( source ) { - }, + super.copy( source ); - setMask: function ( depthMask ) { + this.color.copy( source.color ); - if ( currentDepthMask !== depthMask && ! locked ) { + this.map = source.map; - gl.depthMask( depthMask ); - currentDepthMask = depthMask; + this.lightMap = source.lightMap; + this.lightMapIntensity = source.lightMapIntensity; - } + this.aoMap = source.aoMap; + this.aoMapIntensity = source.aoMapIntensity; - }, + this.emissive.copy( source.emissive ); + this.emissiveMap = source.emissiveMap; + this.emissiveIntensity = source.emissiveIntensity; - setFunc: function ( depthFunc ) { + this.bumpMap = source.bumpMap; + this.bumpScale = source.bumpScale; - if ( currentDepthFunc !== depthFunc ) { + this.normalMap = source.normalMap; + this.normalMapType = source.normalMapType; + this.normalScale.copy( source.normalScale ); - switch ( depthFunc ) { + this.displacementMap = source.displacementMap; + this.displacementScale = source.displacementScale; + this.displacementBias = source.displacementBias; - case NeverDepth: + this.specularMap = source.specularMap; - gl.depthFunc( gl.NEVER ); - break; + this.alphaMap = source.alphaMap; - case AlwaysDepth: + this.envMap = source.envMap; + this.envMapRotation.copy( source.envMapRotation ); + this.combine = source.combine; + this.reflectivity = source.reflectivity; + this.refractionRatio = source.refractionRatio; - gl.depthFunc( gl.ALWAYS ); - break; + this.wireframe = source.wireframe; + this.wireframeLinewidth = source.wireframeLinewidth; + this.wireframeLinecap = source.wireframeLinecap; + this.wireframeLinejoin = source.wireframeLinejoin; - case LessDepth: + this.flatShading = source.flatShading; - gl.depthFunc( gl.LESS ); - break; + this.fog = source.fog; - case LessEqualDepth: + return this; - gl.depthFunc( gl.LEQUAL ); - break; + } - case EqualDepth: +} - gl.depthFunc( gl.EQUAL ); - break; +/** + * A material for drawing geometry by depth. Depth is based off of the camera + * near and far plane. White is nearest, black is farthest. + * + * @augments Material + */ +class MeshDepthMaterial extends Material { - case GreaterEqualDepth: + /** + * Constructs a new mesh depth material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { - gl.depthFunc( gl.GEQUAL ); - break; + super(); - case GreaterDepth: + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshDepthMaterial = true; - gl.depthFunc( gl.GREATER ); - break; + this.type = 'MeshDepthMaterial'; - case NotEqualDepth: + /** + * Type for depth packing. + * + * @type {(BasicDepthPacking|RGBADepthPacking|RGBDepthPacking|RGDepthPacking)} + * @default BasicDepthPacking + */ + this.depthPacking = BasicDepthPacking; - gl.depthFunc( gl.NOTEQUAL ); - break; + /** + * The color map. May optionally include an alpha channel, typically combined + * with {@link Material#transparent} or {@link Material#alphaTest}. + * + * @type {?Texture} + * @default null + */ + this.map = null; - default: + /** + * The alpha map is a grayscale texture that controls the opacity across the + * surface (black: fully transparent; white: fully opaque). + * + * Only the color of the texture is used, ignoring the alpha channel if one + * exists. For RGB and RGBA textures, the renderer will use the green channel + * when sampling this texture due to the extra bit of precision provided for + * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and + * luminance/alpha textures will also still work as expected. + * + * @type {?Texture} + * @default null + */ + this.alphaMap = null; - gl.depthFunc( gl.LEQUAL ); + /** + * The displacement map affects the position of the mesh's vertices. Unlike + * other maps which only affect the light and shade of the material the + * displaced vertices can cast shadows, block other objects, and otherwise + * act as real geometry. The displacement texture is an image where the value + * of each pixel (white being the highest) is mapped against, and + * repositions, the vertices of the mesh. + * + * @type {?Texture} + * @default null + */ + this.displacementMap = null; - } + /** + * How much the displacement map affects the mesh (where black is no + * displacement, and white is maximum displacement). Without a displacement + * map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementScale = 1; - currentDepthFunc = depthFunc; + /** + * The offset of the displacement map's values on the mesh's vertices. + * The bias is added to the scaled sample of the displacement map. + * Without a displacement map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementBias = 0; - } + /** + * Renders the geometry as a wireframe. + * + * @type {boolean} + * @default false + */ + this.wireframe = false; - }, + /** + * Controls the thickness of the wireframe. + * + * WebGL and WebGPU ignore this property and always render + * 1 pixel wide lines. + * + * @type {number} + * @default 1 + */ + this.wireframeLinewidth = 1; - setLocked: function ( lock ) { + this.setValues( parameters ); - locked = lock; + } - }, + copy( source ) { - setClear: function ( depth ) { + super.copy( source ); - if ( currentDepthClear !== depth ) { + this.depthPacking = source.depthPacking; - gl.clearDepth( depth ); - currentDepthClear = depth; + this.map = source.map; - } + this.alphaMap = source.alphaMap; - }, + this.displacementMap = source.displacementMap; + this.displacementScale = source.displacementScale; + this.displacementBias = source.displacementBias; - reset: function () { + this.wireframe = source.wireframe; + this.wireframeLinewidth = source.wireframeLinewidth; - locked = false; + return this; - currentDepthMask = null; - currentDepthFunc = null; - currentDepthClear = null; + } - } +} - }; +/** + * A material used internally for implementing shadow mapping with + * point lights. + * + * Can also be used to customize the shadow casting of an object by assigning + * an instance of `MeshDistanceMaterial` to {@link Object3D#customDistanceMaterial}. + * The following examples demonstrates this approach in order to ensure + * transparent parts of objects do no cast shadows. + * + * @augments Material + */ +class MeshDistanceMaterial extends Material { - } + /** + * Constructs a new mesh distance material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { - function StencilBuffer() { + super(); - let locked = false; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshDistanceMaterial = true; - let currentStencilMask = null; - let currentStencilFunc = null; - let currentStencilRef = null; - let currentStencilFuncMask = null; - let currentStencilFail = null; - let currentStencilZFail = null; - let currentStencilZPass = null; - let currentStencilClear = null; + this.type = 'MeshDistanceMaterial'; - return { + /** + * The color map. May optionally include an alpha channel, typically combined + * with {@link Material#transparent} or {@link Material#alphaTest}. + * + * @type {?Texture} + * @default null + */ + this.map = null; - setTest: function ( stencilTest ) { + /** + * The alpha map is a grayscale texture that controls the opacity across the + * surface (black: fully transparent; white: fully opaque). + * + * Only the color of the texture is used, ignoring the alpha channel if one + * exists. For RGB and RGBA textures, the renderer will use the green channel + * when sampling this texture due to the extra bit of precision provided for + * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and + * luminance/alpha textures will also still work as expected. + * + * @type {?Texture} + * @default null + */ + this.alphaMap = null; - if ( ! locked ) { + /** + * The displacement map affects the position of the mesh's vertices. Unlike + * other maps which only affect the light and shade of the material the + * displaced vertices can cast shadows, block other objects, and otherwise + * act as real geometry. The displacement texture is an image where the value + * of each pixel (white being the highest) is mapped against, and + * repositions, the vertices of the mesh. + * + * @type {?Texture} + * @default null + */ + this.displacementMap = null; - if ( stencilTest ) { + /** + * How much the displacement map affects the mesh (where black is no + * displacement, and white is maximum displacement). Without a displacement + * map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementScale = 1; - enable( gl.STENCIL_TEST ); + /** + * The offset of the displacement map's values on the mesh's vertices. + * The bias is added to the scaled sample of the displacement map. + * Without a displacement map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementBias = 0; - } else { + this.setValues( parameters ); - disable( gl.STENCIL_TEST ); + } - } + copy( source ) { - } + super.copy( source ); - }, + this.map = source.map; - setMask: function ( stencilMask ) { + this.alphaMap = source.alphaMap; - if ( currentStencilMask !== stencilMask && ! locked ) { + this.displacementMap = source.displacementMap; + this.displacementScale = source.displacementScale; + this.displacementBias = source.displacementBias; - gl.stencilMask( stencilMask ); - currentStencilMask = stencilMask; + return this; - } + } - }, +} - setFunc: function ( stencilFunc, stencilRef, stencilMask ) { +/** + * This material is defined by a MatCap (or Lit Sphere) texture, which encodes the + * material color and shading. + * + * `MeshMatcapMaterial` does not respond to lights since the matcap image file encodes + * baked lighting. It will cast a shadow onto an object that receives shadows + * (and shadow clipping works), but it will not self-shadow or receive + * shadows. + * + * @augments Material + */ +class MeshMatcapMaterial extends Material { - if ( currentStencilFunc !== stencilFunc || - currentStencilRef !== stencilRef || - currentStencilFuncMask !== stencilMask ) { + /** + * Constructs a new mesh matcap material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { - gl.stencilFunc( stencilFunc, stencilRef, stencilMask ); + super(); - currentStencilFunc = stencilFunc; - currentStencilRef = stencilRef; - currentStencilFuncMask = stencilMask; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshMatcapMaterial = true; - } + this.defines = { 'MATCAP': '' }; - }, + this.type = 'MeshMatcapMaterial'; - setOp: function ( stencilFail, stencilZFail, stencilZPass ) { + /** + * Color of the material. + * + * @type {Color} + * @default (1,1,1) + */ + this.color = new Color( 0xffffff ); // diffuse - if ( currentStencilFail !== stencilFail || - currentStencilZFail !== stencilZFail || - currentStencilZPass !== stencilZPass ) { + /** + * The matcap map. + * + * @type {?Texture} + * @default null + */ + this.matcap = null; - gl.stencilOp( stencilFail, stencilZFail, stencilZPass ); + /** + * The color map. May optionally include an alpha channel, typically combined + * with {@link Material#transparent} or {@link Material#alphaTest}. The texture map + * color is modulated by the diffuse `color`. + * + * @type {?Texture} + * @default null + */ + this.map = null; - currentStencilFail = stencilFail; - currentStencilZFail = stencilZFail; - currentStencilZPass = stencilZPass; + /** + * The texture to create a bump map. The black and white values map to the + * perceived depth in relation to the lights. Bump doesn't actually affect + * the geometry of the object, only the lighting. If a normal map is defined + * this will be ignored. + * + * @type {?Texture} + * @default null + */ + this.bumpMap = null; - } + /** + * How much the bump map affects the material. Typical range is `[0,1]`. + * + * @type {number} + * @default 1 + */ + this.bumpScale = 1; - }, + /** + * The texture to create a normal map. The RGB values affect the surface + * normal for each pixel fragment and change the way the color is lit. Normal + * maps do not change the actual shape of the surface, only the lighting. In + * case the material has a normal map authored using the left handed + * convention, the `y` component of `normalScale` should be negated to compensate + * for the different handedness. + * + * @type {?Texture} + * @default null + */ + this.normalMap = null; - setLocked: function ( lock ) { + /** + * The type of normal map. + * + * @type {(TangentSpaceNormalMap|ObjectSpaceNormalMap)} + * @default TangentSpaceNormalMap + */ + this.normalMapType = TangentSpaceNormalMap; - locked = lock; + /** + * How much the normal map affects the material. Typical value range is `[0,1]`. + * + * @type {Vector2} + * @default (1,1) + */ + this.normalScale = new Vector2( 1, 1 ); - }, + /** + * The displacement map affects the position of the mesh's vertices. Unlike + * other maps which only affect the light and shade of the material the + * displaced vertices can cast shadows, block other objects, and otherwise + * act as real geometry. The displacement texture is an image where the value + * of each pixel (white being the highest) is mapped against, and + * repositions, the vertices of the mesh. + * + * @type {?Texture} + * @default null + */ + this.displacementMap = null; - setClear: function ( stencil ) { + /** + * How much the displacement map affects the mesh (where black is no + * displacement, and white is maximum displacement). Without a displacement + * map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementScale = 1; - if ( currentStencilClear !== stencil ) { + /** + * The offset of the displacement map's values on the mesh's vertices. + * The bias is added to the scaled sample of the displacement map. + * Without a displacement map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementBias = 0; - gl.clearStencil( stencil ); - currentStencilClear = stencil; + /** + * The alpha map is a grayscale texture that controls the opacity across the + * surface (black: fully transparent; white: fully opaque). + * + * Only the color of the texture is used, ignoring the alpha channel if one + * exists. For RGB and RGBA textures, the renderer will use the green channel + * when sampling this texture due to the extra bit of precision provided for + * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and + * luminance/alpha textures will also still work as expected. + * + * @type {?Texture} + * @default null + */ + this.alphaMap = null; - } + /** + * Whether the material is rendered with flat shading or not. + * + * @type {boolean} + * @default false + */ + this.flatShading = false; - }, + /** + * Whether the material is affected by fog or not. + * + * @type {boolean} + * @default true + */ + this.fog = true; - reset: function () { + this.setValues( parameters ); - locked = false; + } - currentStencilMask = null; - currentStencilFunc = null; - currentStencilRef = null; - currentStencilFuncMask = null; - currentStencilFail = null; - currentStencilZFail = null; - currentStencilZPass = null; - currentStencilClear = null; - } + copy( source ) { - }; + super.copy( source ); - } + this.defines = { 'MATCAP': '' }; - // + this.color.copy( source.color ); - const colorBuffer = new ColorBuffer(); - const depthBuffer = new DepthBuffer(); - const stencilBuffer = new StencilBuffer(); + this.matcap = source.matcap; - const uboBindings = new WeakMap(); - const uboProgramMap = new WeakMap(); + this.map = source.map; - let enabledCapabilities = {}; + this.bumpMap = source.bumpMap; + this.bumpScale = source.bumpScale; - let currentBoundFramebuffers = {}; - let currentDrawbuffers = new WeakMap(); - let defaultDrawbuffers = []; + this.normalMap = source.normalMap; + this.normalMapType = source.normalMapType; + this.normalScale.copy( source.normalScale ); - let currentProgram = null; + this.displacementMap = source.displacementMap; + this.displacementScale = source.displacementScale; + this.displacementBias = source.displacementBias; - let currentBlendingEnabled = false; - let currentBlending = null; - let currentBlendEquation = null; - let currentBlendSrc = null; - let currentBlendDst = null; - let currentBlendEquationAlpha = null; - let currentBlendSrcAlpha = null; - let currentBlendDstAlpha = null; - let currentPremultipledAlpha = false; + this.alphaMap = source.alphaMap; - let currentFlipSided = null; - let currentCullFace = null; + this.flatShading = source.flatShading; - let currentLineWidth = null; + this.fog = source.fog; - let currentPolygonOffsetFactor = null; - let currentPolygonOffsetUnits = null; + return this; - const maxTextures = gl.getParameter( gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS ); + } - let lineWidthAvailable = false; - let version = 0; - const glVersion = gl.getParameter( gl.VERSION ); +} - if ( glVersion.indexOf( 'WebGL' ) !== - 1 ) { +/** + * A material for rendering line primitives. + * + * Materials define the appearance of renderable 3D objects. + * + * ```js + * const material = new THREE.LineDashedMaterial( { + * color: 0xffffff, + * scale: 1, + * dashSize: 3, + * gapSize: 1, + * } ); + * ``` + * + * @augments LineBasicMaterial + */ +class LineDashedMaterial extends LineBasicMaterial { - version = parseFloat( /^WebGL (\d)/.exec( glVersion )[ 1 ] ); - lineWidthAvailable = ( version >= 1.0 ); + /** + * Constructs a new line dashed material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { - } else if ( glVersion.indexOf( 'OpenGL ES' ) !== - 1 ) { + super(); - version = parseFloat( /^OpenGL ES (\d)/.exec( glVersion )[ 1 ] ); - lineWidthAvailable = ( version >= 2.0 ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLineDashedMaterial = true; + this.type = 'LineDashedMaterial'; - } + /** + * The scale of the dashed part of a line. + * + * @type {number} + * @default 1 + */ + this.scale = 1; - let currentTextureSlot = null; - let currentBoundTextures = {}; + /** + * The size of the dash. This is both the gap with the stroke. + * + * @type {number} + * @default 3 + */ + this.dashSize = 3; - const scissorParam = gl.getParameter( gl.SCISSOR_BOX ); - const viewportParam = gl.getParameter( gl.VIEWPORT ); + /** + * The size of the gap. + * + * @type {number} + * @default 1 + */ + this.gapSize = 1; - const currentScissor = new Vector4().fromArray( scissorParam ); - const currentViewport = new Vector4().fromArray( viewportParam ); + this.setValues( parameters ); - function createTexture( type, target, count, dimensions ) { + } - const data = new Uint8Array( 4 ); // 4 is required to match default unpack alignment of 4. - const texture = gl.createTexture(); + copy( source ) { - gl.bindTexture( type, texture ); - gl.texParameteri( type, gl.TEXTURE_MIN_FILTER, gl.NEAREST ); - gl.texParameteri( type, gl.TEXTURE_MAG_FILTER, gl.NEAREST ); + super.copy( source ); - for ( let i = 0; i < count; i ++ ) { + this.scale = source.scale; + this.dashSize = source.dashSize; + this.gapSize = source.gapSize; - if ( isWebGL2 && ( type === gl.TEXTURE_3D || type === gl.TEXTURE_2D_ARRAY ) ) { + return this; - gl.texImage3D( target, 0, gl.RGBA, 1, 1, dimensions, 0, gl.RGBA, gl.UNSIGNED_BYTE, data ); + } - } else { +} - gl.texImage2D( target + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, data ); +/** + * Converts an array to a specific type. + * + * @param {TypedArray|Array} array - The array to convert. + * @param {TypedArray.constructor} type - The constructor of a typed array that defines the new type. + * @return {TypedArray} The converted array. + */ +function convertArray( array, type ) { - } + if ( ! array || array.constructor === type ) return array; - } + if ( typeof type.BYTES_PER_ELEMENT === 'number' ) { - return texture; + return new type( array ); // create typed array } - const emptyTextures = {}; - emptyTextures[ gl.TEXTURE_2D ] = createTexture( gl.TEXTURE_2D, gl.TEXTURE_2D, 1 ); - emptyTextures[ gl.TEXTURE_CUBE_MAP ] = createTexture( gl.TEXTURE_CUBE_MAP, gl.TEXTURE_CUBE_MAP_POSITIVE_X, 6 ); + return Array.prototype.slice.call( array ); // create Array - if ( isWebGL2 ) { +} + +/** + * Returns `true` if the given object is a typed array. + * + * @param {any} object - The object to check. + * @return {boolean} Whether the given object is a typed array. + */ +function isTypedArray( object ) { - emptyTextures[ gl.TEXTURE_2D_ARRAY ] = createTexture( gl.TEXTURE_2D_ARRAY, gl.TEXTURE_2D_ARRAY, 1, 1 ); - emptyTextures[ gl.TEXTURE_3D ] = createTexture( gl.TEXTURE_3D, gl.TEXTURE_3D, 1, 1 ); + return ArrayBuffer.isView( object ) && ! ( object instanceof DataView ); - } +} - // init +/** + * Returns an array by which times and values can be sorted. + * + * @param {Array} times - The keyframe time values. + * @return {Array} The array. + */ +function getKeyframeOrder( times ) { - colorBuffer.setClear( 0, 0, 0, 1 ); - depthBuffer.setClear( 1 ); - stencilBuffer.setClear( 0 ); + function compareTime( i, j ) { - enable( gl.DEPTH_TEST ); - depthBuffer.setFunc( LessEqualDepth ); + return times[ i ] - times[ j ]; - setFlipSided( false ); - setCullFace( CullFaceBack ); - enable( gl.CULL_FACE ); + } - setBlending( NoBlending ); + const n = times.length; + const result = new Array( n ); + for ( let i = 0; i !== n; ++ i ) result[ i ] = i; - // + result.sort( compareTime ); - function enable( id ) { + return result; - if ( enabledCapabilities[ id ] !== true ) { +} - gl.enable( id ); - enabledCapabilities[ id ] = true; +/** + * Sorts the given array by the previously computed order via `getKeyframeOrder()`. + * + * @param {Array} values - The values to sort. + * @param {number} stride - The stride. + * @param {Array} order - The sort order. + * @return {Array} The sorted values. + */ +function sortedArray( values, stride, order ) { - } + const nValues = values.length; + const result = new values.constructor( nValues ); - } + for ( let i = 0, dstOffset = 0; dstOffset !== nValues; ++ i ) { - function disable( id ) { + const srcOffset = order[ i ] * stride; - if ( enabledCapabilities[ id ] !== false ) { + for ( let j = 0; j !== stride; ++ j ) { - gl.disable( id ); - enabledCapabilities[ id ] = false; + result[ dstOffset ++ ] = values[ srcOffset + j ]; } } - function bindFramebuffer( target, framebuffer ) { - - if ( currentBoundFramebuffers[ target ] !== framebuffer ) { + return result; - gl.bindFramebuffer( target, framebuffer ); +} - currentBoundFramebuffers[ target ] = framebuffer; +/** + * Used for parsing AOS keyframe formats. + * + * @param {Array} jsonKeys - A list of JSON keyframes. + * @param {Array} times - This array will be filled with keyframe times by this function. + * @param {Array} values - This array will be filled with keyframe values by this function. + * @param {string} valuePropertyName - The name of the property to use. + */ +function flattenJSON( jsonKeys, times, values, valuePropertyName ) { - if ( isWebGL2 ) { + let i = 1, key = jsonKeys[ 0 ]; - // gl.DRAW_FRAMEBUFFER is equivalent to gl.FRAMEBUFFER + while ( key !== undefined && key[ valuePropertyName ] === undefined ) { - if ( target === gl.DRAW_FRAMEBUFFER ) { + key = jsonKeys[ i ++ ]; - currentBoundFramebuffers[ gl.FRAMEBUFFER ] = framebuffer; + } - } + if ( key === undefined ) return; // no data - if ( target === gl.FRAMEBUFFER ) { + let value = key[ valuePropertyName ]; + if ( value === undefined ) return; // no data - currentBoundFramebuffers[ gl.DRAW_FRAMEBUFFER ] = framebuffer; + if ( Array.isArray( value ) ) { - } + do { - } + value = key[ valuePropertyName ]; - return true; + if ( value !== undefined ) { - } + times.push( key.time ); + values.push( ...value ); // push all elements - return false; + } - } + key = jsonKeys[ i ++ ]; - function drawBuffers( renderTarget, framebuffer ) { + } while ( key !== undefined ); - let drawBuffers = defaultDrawbuffers; + } else if ( value.toArray !== undefined ) { - let needsUpdate = false; + // ...assume THREE.Math-ish - if ( renderTarget ) { + do { - drawBuffers = currentDrawbuffers.get( framebuffer ); + value = key[ valuePropertyName ]; - if ( drawBuffers === undefined ) { + if ( value !== undefined ) { - drawBuffers = []; - currentDrawbuffers.set( framebuffer, drawBuffers ); + times.push( key.time ); + value.toArray( values, values.length ); } - if ( renderTarget.isWebGLMultipleRenderTargets ) { + key = jsonKeys[ i ++ ]; - const textures = renderTarget.texture; + } while ( key !== undefined ); - if ( drawBuffers.length !== textures.length || drawBuffers[ 0 ] !== gl.COLOR_ATTACHMENT0 ) { + } else { - for ( let i = 0, il = textures.length; i < il; i ++ ) { + // otherwise push as-is - drawBuffers[ i ] = gl.COLOR_ATTACHMENT0 + i; + do { - } + value = key[ valuePropertyName ]; - drawBuffers.length = textures.length; + if ( value !== undefined ) { - needsUpdate = true; + times.push( key.time ); + values.push( value ); - } + } - } else { + key = jsonKeys[ i ++ ]; - if ( drawBuffers[ 0 ] !== gl.COLOR_ATTACHMENT0 ) { + } while ( key !== undefined ); - drawBuffers[ 0 ] = gl.COLOR_ATTACHMENT0; + } - needsUpdate = true; +} - } +/** + * Creates a new clip, containing only the segment of the original clip between the given frames. + * + * @param {AnimationClip} sourceClip - The values to sort. + * @param {string} name - The name of the clip. + * @param {number} startFrame - The start frame. + * @param {number} endFrame - The end frame. + * @param {number} [fps=30] - The FPS. + * @return {AnimationClip} The new sub clip. + */ +function subclip( sourceClip, name, startFrame, endFrame, fps = 30 ) { - } + const clip = sourceClip.clone(); - } else { + clip.name = name; - if ( drawBuffers[ 0 ] !== gl.BACK ) { + const tracks = []; - drawBuffers[ 0 ] = gl.BACK; + for ( let i = 0; i < clip.tracks.length; ++ i ) { - needsUpdate = true; + const track = clip.tracks[ i ]; + const valueSize = track.getValueSize(); - } + const times = []; + const values = []; - } + for ( let j = 0; j < track.times.length; ++ j ) { - if ( needsUpdate ) { + const frame = track.times[ j ] * fps; - if ( capabilities.isWebGL2 ) { + if ( frame < startFrame || frame >= endFrame ) continue; - gl.drawBuffers( drawBuffers ); + times.push( track.times[ j ] ); - } else { + for ( let k = 0; k < valueSize; ++ k ) { - extensions.get( 'WEBGL_draw_buffers' ).drawBuffersWEBGL( drawBuffers ); + values.push( track.values[ j * valueSize + k ] ); } } + if ( times.length === 0 ) continue; + + track.times = convertArray( times, track.times.constructor ); + track.values = convertArray( values, track.values.constructor ); + + tracks.push( track ); } - function useProgram( program ) { + clip.tracks = tracks; - if ( currentProgram !== program ) { + // find minimum .times value across all tracks in the trimmed clip - gl.useProgram( program ); + let minStartTime = Infinity; - currentProgram = program; + for ( let i = 0; i < clip.tracks.length; ++ i ) { - return true; + if ( minStartTime > clip.tracks[ i ].times[ 0 ] ) { - } + minStartTime = clip.tracks[ i ].times[ 0 ]; - return false; + } } - const equationToGL = { - [ AddEquation ]: gl.FUNC_ADD, - [ SubtractEquation ]: gl.FUNC_SUBTRACT, - [ ReverseSubtractEquation ]: gl.FUNC_REVERSE_SUBTRACT - }; + // shift all tracks such that clip begins at t=0 - if ( isWebGL2 ) { + for ( let i = 0; i < clip.tracks.length; ++ i ) { - equationToGL[ MinEquation ] = gl.MIN; - equationToGL[ MaxEquation ] = gl.MAX; + clip.tracks[ i ].shift( -1 * minStartTime ); - } else { + } - const extension = extensions.get( 'EXT_blend_minmax' ); + clip.resetDuration(); - if ( extension !== null ) { + return clip; - equationToGL[ MinEquation ] = extension.MIN_EXT; - equationToGL[ MaxEquation ] = extension.MAX_EXT; +} - } +/** + * Converts the keyframes of the given animation clip to an additive format. + * + * @param {AnimationClip} targetClip - The clip to make additive. + * @param {number} [referenceFrame=0] - The reference frame. + * @param {AnimationClip} [referenceClip=targetClip] - The reference clip. + * @param {number} [fps=30] - The FPS. + * @return {AnimationClip} The updated clip which is now additive. + */ +function makeClipAdditive( targetClip, referenceFrame = 0, referenceClip = targetClip, fps = 30 ) { - } + if ( fps <= 0 ) fps = 30; - const factorToGL = { - [ ZeroFactor ]: gl.ZERO, - [ OneFactor ]: gl.ONE, - [ SrcColorFactor ]: gl.SRC_COLOR, - [ SrcAlphaFactor ]: gl.SRC_ALPHA, - [ SrcAlphaSaturateFactor ]: gl.SRC_ALPHA_SATURATE, - [ DstColorFactor ]: gl.DST_COLOR, - [ DstAlphaFactor ]: gl.DST_ALPHA, - [ OneMinusSrcColorFactor ]: gl.ONE_MINUS_SRC_COLOR, - [ OneMinusSrcAlphaFactor ]: gl.ONE_MINUS_SRC_ALPHA, - [ OneMinusDstColorFactor ]: gl.ONE_MINUS_DST_COLOR, - [ OneMinusDstAlphaFactor ]: gl.ONE_MINUS_DST_ALPHA - }; + const numTracks = referenceClip.tracks.length; + const referenceTime = referenceFrame / fps; - function setBlending( blending, blendEquation, blendSrc, blendDst, blendEquationAlpha, blendSrcAlpha, blendDstAlpha, premultipliedAlpha ) { + // Make each track's values relative to the values at the reference frame + for ( let i = 0; i < numTracks; ++ i ) { - if ( blending === NoBlending ) { + const referenceTrack = referenceClip.tracks[ i ]; + const referenceTrackType = referenceTrack.ValueTypeName; - if ( currentBlendingEnabled === true ) { + // Skip this track if it's non-numeric + if ( referenceTrackType === 'bool' || referenceTrackType === 'string' ) continue; - disable( gl.BLEND ); - currentBlendingEnabled = false; + // Find the track in the target clip whose name and type matches the reference track + const targetTrack = targetClip.tracks.find( function ( track ) { - } + return track.name === referenceTrack.name + && track.ValueTypeName === referenceTrackType; - return; + } ); - } + if ( targetTrack === undefined ) continue; - if ( currentBlendingEnabled === false ) { + let referenceOffset = 0; + const referenceValueSize = referenceTrack.getValueSize(); - enable( gl.BLEND ); - currentBlendingEnabled = true; + if ( referenceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) { + + referenceOffset = referenceValueSize / 3; } - if ( blending !== CustomBlending ) { + let targetOffset = 0; + const targetValueSize = targetTrack.getValueSize(); - if ( blending !== currentBlending || premultipliedAlpha !== currentPremultipledAlpha ) { + if ( targetTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) { - if ( currentBlendEquation !== AddEquation || currentBlendEquationAlpha !== AddEquation ) { + targetOffset = targetValueSize / 3; - gl.blendEquation( gl.FUNC_ADD ); + } - currentBlendEquation = AddEquation; - currentBlendEquationAlpha = AddEquation; + const lastIndex = referenceTrack.times.length - 1; + let referenceValue; - } + // Find the value to subtract out of the track + if ( referenceTime <= referenceTrack.times[ 0 ] ) { - if ( premultipliedAlpha ) { + // Reference frame is earlier than the first keyframe, so just use the first keyframe + const startIndex = referenceOffset; + const endIndex = referenceValueSize - referenceOffset; + referenceValue = referenceTrack.values.slice( startIndex, endIndex ); - switch ( blending ) { + } else if ( referenceTime >= referenceTrack.times[ lastIndex ] ) { - case NormalBlending: - gl.blendFuncSeparate( gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA ); - break; + // Reference frame is after the last keyframe, so just use the last keyframe + const startIndex = lastIndex * referenceValueSize + referenceOffset; + const endIndex = startIndex + referenceValueSize - referenceOffset; + referenceValue = referenceTrack.values.slice( startIndex, endIndex ); - case AdditiveBlending: - gl.blendFunc( gl.ONE, gl.ONE ); - break; + } else { - case SubtractiveBlending: - gl.blendFuncSeparate( gl.ZERO, gl.ONE_MINUS_SRC_COLOR, gl.ZERO, gl.ONE ); - break; + // Interpolate to the reference value + const interpolant = referenceTrack.createInterpolant(); + const startIndex = referenceOffset; + const endIndex = referenceValueSize - referenceOffset; + interpolant.evaluate( referenceTime ); + referenceValue = interpolant.resultBuffer.slice( startIndex, endIndex ); - case MultiplyBlending: - gl.blendFuncSeparate( gl.ZERO, gl.SRC_COLOR, gl.ZERO, gl.SRC_ALPHA ); - break; + } - default: - console.error( 'THREE.WebGLState: Invalid blending: ', blending ); - break; + // Conjugate the quaternion + if ( referenceTrackType === 'quaternion' ) { - } + const referenceQuat = new Quaternion().fromArray( referenceValue ).normalize().conjugate(); + referenceQuat.toArray( referenceValue ); - } else { + } - switch ( blending ) { + // Subtract the reference value from all of the track values - case NormalBlending: - gl.blendFuncSeparate( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA ); - break; + const numTimes = targetTrack.times.length; + for ( let j = 0; j < numTimes; ++ j ) { - case AdditiveBlending: - gl.blendFunc( gl.SRC_ALPHA, gl.ONE ); - break; + const valueStart = j * targetValueSize + targetOffset; - case SubtractiveBlending: - gl.blendFuncSeparate( gl.ZERO, gl.ONE_MINUS_SRC_COLOR, gl.ZERO, gl.ONE ); - break; + if ( referenceTrackType === 'quaternion' ) { - case MultiplyBlending: - gl.blendFunc( gl.ZERO, gl.SRC_COLOR ); - break; + // Multiply the conjugate for quaternion track types + Quaternion.multiplyQuaternionsFlat( + targetTrack.values, + valueStart, + referenceValue, + 0, + targetTrack.values, + valueStart + ); - default: - console.error( 'THREE.WebGLState: Invalid blending: ', blending ); - break; + } else { - } + const valueEnd = targetValueSize - targetOffset * 2; - } + // Subtract each value for all other numeric track types + for ( let k = 0; k < valueEnd; ++ k ) { - currentBlendSrc = null; - currentBlendDst = null; - currentBlendSrcAlpha = null; - currentBlendDstAlpha = null; + targetTrack.values[ valueStart + k ] -= referenceValue[ k ]; - currentBlending = blending; - currentPremultipledAlpha = premultipliedAlpha; + } } - return; - } - // custom blending - - blendEquationAlpha = blendEquationAlpha || blendEquation; - blendSrcAlpha = blendSrcAlpha || blendSrc; - blendDstAlpha = blendDstAlpha || blendDst; + } - if ( blendEquation !== currentBlendEquation || blendEquationAlpha !== currentBlendEquationAlpha ) { + targetClip.blendMode = AdditiveAnimationBlendMode; - gl.blendEquationSeparate( equationToGL[ blendEquation ], equationToGL[ blendEquationAlpha ] ); + return targetClip; - currentBlendEquation = blendEquation; - currentBlendEquationAlpha = blendEquationAlpha; +} - } +/** + * A class with various methods to assist with animations. + * + * @hideconstructor + */ +class AnimationUtils { - if ( blendSrc !== currentBlendSrc || blendDst !== currentBlendDst || blendSrcAlpha !== currentBlendSrcAlpha || blendDstAlpha !== currentBlendDstAlpha ) { + /** + * Converts an array to a specific type + * + * @static + * @param {TypedArray|Array} array - The array to convert. + * @param {TypedArray.constructor} type - The constructor of a type array. + * @return {TypedArray} The converted array + */ + static convertArray( array, type ) { - gl.blendFuncSeparate( factorToGL[ blendSrc ], factorToGL[ blendDst ], factorToGL[ blendSrcAlpha ], factorToGL[ blendDstAlpha ] ); + return convertArray( array, type ); - currentBlendSrc = blendSrc; - currentBlendDst = blendDst; - currentBlendSrcAlpha = blendSrcAlpha; - currentBlendDstAlpha = blendDstAlpha; + } - } + /** + * Returns `true` if the given object is a typed array. + * + * @static + * @param {any} object - The object to check. + * @return {boolean} Whether the given object is a typed array. + */ + static isTypedArray( object ) { - currentBlending = blending; - currentPremultipledAlpha = false; + return isTypedArray( object ); } - function setMaterial( material, frontFaceCW ) { + /** + * Returns an array by which times and values can be sorted. + * + * @static + * @param {Array} times - The keyframe time values. + * @return {Array} The array. + */ + static getKeyframeOrder( times ) { - material.side === DoubleSide - ? disable( gl.CULL_FACE ) - : enable( gl.CULL_FACE ); + return getKeyframeOrder( times ); - let flipSided = ( material.side === BackSide ); - if ( frontFaceCW ) flipSided = ! flipSided; + } - setFlipSided( flipSided ); + /** + * Sorts the given array by the previously computed order via `getKeyframeOrder()`. + * + * @static + * @param {Array} values - The values to sort. + * @param {number} stride - The stride. + * @param {Array} order - The sort order. + * @return {Array} The sorted values. + */ + static sortedArray( values, stride, order ) { - ( material.blending === NormalBlending && material.transparent === false ) - ? setBlending( NoBlending ) - : setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst, material.blendEquationAlpha, material.blendSrcAlpha, material.blendDstAlpha, material.premultipliedAlpha ); + return sortedArray( values, stride, order ); - depthBuffer.setFunc( material.depthFunc ); - depthBuffer.setTest( material.depthTest ); - depthBuffer.setMask( material.depthWrite ); - colorBuffer.setMask( material.colorWrite ); + } - const stencilWrite = material.stencilWrite; - stencilBuffer.setTest( stencilWrite ); - if ( stencilWrite ) { + /** + * Used for parsing AOS keyframe formats. + * + * @static + * @param {Array} jsonKeys - A list of JSON keyframes. + * @param {Array} times - This array will be filled with keyframe times by this method. + * @param {Array} values - This array will be filled with keyframe values by this method. + * @param {string} valuePropertyName - The name of the property to use. + */ + static flattenJSON( jsonKeys, times, values, valuePropertyName ) { - stencilBuffer.setMask( material.stencilWriteMask ); - stencilBuffer.setFunc( material.stencilFunc, material.stencilRef, material.stencilFuncMask ); - stencilBuffer.setOp( material.stencilFail, material.stencilZFail, material.stencilZPass ); + flattenJSON( jsonKeys, times, values, valuePropertyName ); - } + } - setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits ); + /** + * Creates a new clip, containing only the segment of the original clip between the given frames. + * + * @static + * @param {AnimationClip} sourceClip - The values to sort. + * @param {string} name - The name of the clip. + * @param {number} startFrame - The start frame. + * @param {number} endFrame - The end frame. + * @param {number} [fps=30] - The FPS. + * @return {AnimationClip} The new sub clip. + */ + static subclip( sourceClip, name, startFrame, endFrame, fps = 30 ) { - material.alphaToCoverage === true - ? enable( gl.SAMPLE_ALPHA_TO_COVERAGE ) - : disable( gl.SAMPLE_ALPHA_TO_COVERAGE ); + return subclip( sourceClip, name, startFrame, endFrame, fps ); } - // - - function setFlipSided( flipSided ) { - - if ( currentFlipSided !== flipSided ) { + /** + * Converts the keyframes of the given animation clip to an additive format. + * + * @static + * @param {AnimationClip} targetClip - The clip to make additive. + * @param {number} [referenceFrame=0] - The reference frame. + * @param {AnimationClip} [referenceClip=targetClip] - The reference clip. + * @param {number} [fps=30] - The FPS. + * @return {AnimationClip} The updated clip which is now additive. + */ + static makeClipAdditive( targetClip, referenceFrame = 0, referenceClip = targetClip, fps = 30 ) { - if ( flipSided ) { + return makeClipAdditive( targetClip, referenceFrame, referenceClip, fps ); - gl.frontFace( gl.CW ); + } - } else { +} - gl.frontFace( gl.CCW ); +/** + * Abstract base class of interpolants over parametric samples. + * + * The parameter domain is one dimensional, typically the time or a path + * along a curve defined by the data. + * + * The sample values can have any dimensionality and derived classes may + * apply special interpretations to the data. + * + * This class provides the interval seek in a Template Method, deferring + * the actual interpolation to derived classes. + * + * Time complexity is O(1) for linear access crossing at most two points + * and O(log N) for random access, where N is the number of positions. + * + * References: {@link http://www.oodesign.com/template-method-pattern.html} + * + * @abstract + */ +class Interpolant { - } + /** + * Constructs a new interpolant. + * + * @param {TypedArray} parameterPositions - The parameter positions hold the interpolation factors. + * @param {TypedArray} sampleValues - The sample values. + * @param {number} sampleSize - The sample size + * @param {TypedArray} [resultBuffer] - The result buffer. + */ + constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { - currentFlipSided = flipSided; + /** + * The parameter positions. + * + * @type {TypedArray} + */ + this.parameterPositions = parameterPositions; - } + /** + * A cache index. + * + * @private + * @type {number} + * @default 0 + */ + this._cachedIndex = 0; - } + /** + * The result buffer. + * + * @type {TypedArray} + */ + this.resultBuffer = resultBuffer !== undefined ? resultBuffer : new sampleValues.constructor( sampleSize ); - function setCullFace( cullFace ) { + /** + * The sample values. + * + * @type {TypedArray} + */ + this.sampleValues = sampleValues; - if ( cullFace !== CullFaceNone ) { + /** + * The value size. + * + * @type {TypedArray} + */ + this.valueSize = sampleSize; - enable( gl.CULL_FACE ); + /** + * The interpolation settings. + * + * @type {?Object} + * @default null + */ + this.settings = null; - if ( cullFace !== currentCullFace ) { + /** + * The default settings object. + * + * @type {Object} + */ + this.DefaultSettings_ = {}; - if ( cullFace === CullFaceBack ) { + } - gl.cullFace( gl.BACK ); + /** + * Evaluate the interpolant at position `t`. + * + * @param {number} t - The interpolation factor. + * @return {TypedArray} The result buffer. + */ + evaluate( t ) { - } else if ( cullFace === CullFaceFront ) { + const pp = this.parameterPositions; + let i1 = this._cachedIndex, + t1 = pp[ i1 ], + t0 = pp[ i1 - 1 ]; - gl.cullFace( gl.FRONT ); + validate_interval: { - } else { + seek: { - gl.cullFace( gl.FRONT_AND_BACK ); + let right; - } + linear_scan: { - } + //- See http://jsperf.com/comparison-to-undefined/3 + //- slower code: + //- + //- if ( t >= t1 || t1 === undefined ) { + forward_scan: if ( ! ( t < t1 ) ) { - } else { + for ( let giveUpAt = i1 + 2; ; ) { - disable( gl.CULL_FACE ); + if ( t1 === undefined ) { - } + if ( t < t0 ) break forward_scan; - currentCullFace = cullFace; + // after end - } + i1 = pp.length; + this._cachedIndex = i1; + return this.copySampleValue_( i1 - 1 ); - function setLineWidth( width ) { + } - if ( width !== currentLineWidth ) { + if ( i1 === giveUpAt ) break; // this loop - if ( lineWidthAvailable ) gl.lineWidth( width ); + t0 = t1; + t1 = pp[ ++ i1 ]; - currentLineWidth = width; + if ( t < t1 ) { - } + // we have arrived at the sought interval + break seek; - } + } - function setPolygonOffset( polygonOffset, factor, units ) { + } - if ( polygonOffset ) { + // prepare binary search on the right side of the index + right = pp.length; + break linear_scan; - enable( gl.POLYGON_OFFSET_FILL ); + } - if ( currentPolygonOffsetFactor !== factor || currentPolygonOffsetUnits !== units ) { + //- slower code: + //- if ( t < t0 || t0 === undefined ) { + if ( ! ( t >= t0 ) ) { - gl.polygonOffset( factor, units ); + // looping? - currentPolygonOffsetFactor = factor; - currentPolygonOffsetUnits = units; + const t1global = pp[ 1 ]; - } + if ( t < t1global ) { - } else { + i1 = 2; // + 1, using the scan for the details + t0 = t1global; - disable( gl.POLYGON_OFFSET_FILL ); + } - } + // linear reverse scan - } + for ( let giveUpAt = i1 - 2; ; ) { - function setScissorTest( scissorTest ) { + if ( t0 === undefined ) { - if ( scissorTest ) { + // before start - enable( gl.SCISSOR_TEST ); + this._cachedIndex = 0; + return this.copySampleValue_( 0 ); - } else { + } - disable( gl.SCISSOR_TEST ); + if ( i1 === giveUpAt ) break; // this loop - } + t1 = t0; + t0 = pp[ -- i1 - 1 ]; - } + if ( t >= t0 ) { - // texture + // we have arrived at the sought interval + break seek; - function activeTexture( webglSlot ) { + } - if ( webglSlot === undefined ) webglSlot = gl.TEXTURE0 + maxTextures - 1; + } - if ( currentTextureSlot !== webglSlot ) { + // prepare binary search on the left side of the index + right = i1; + i1 = 0; + break linear_scan; - gl.activeTexture( webglSlot ); - currentTextureSlot = webglSlot; + } - } + // the interval is valid - } + break validate_interval; - function bindTexture( webglType, webglTexture, webglSlot ) { + } // linear scan - if ( webglSlot === undefined ) { + // binary search - if ( currentTextureSlot === null ) { + while ( i1 < right ) { - webglSlot = gl.TEXTURE0 + maxTextures - 1; + const mid = ( i1 + right ) >>> 1; - } else { + if ( t < pp[ mid ] ) { - webglSlot = currentTextureSlot; + right = mid; - } + } else { - } + i1 = mid + 1; - let boundTexture = currentBoundTextures[ webglSlot ]; + } - if ( boundTexture === undefined ) { + } - boundTexture = { type: undefined, texture: undefined }; - currentBoundTextures[ webglSlot ] = boundTexture; + t1 = pp[ i1 ]; + t0 = pp[ i1 - 1 ]; - } + // check boundary cases, again - if ( boundTexture.type !== webglType || boundTexture.texture !== webglTexture ) { + if ( t0 === undefined ) { - if ( currentTextureSlot !== webglSlot ) { + this._cachedIndex = 0; + return this.copySampleValue_( 0 ); - gl.activeTexture( webglSlot ); - currentTextureSlot = webglSlot; + } - } + if ( t1 === undefined ) { - gl.bindTexture( webglType, webglTexture || emptyTextures[ webglType ] ); + i1 = pp.length; + this._cachedIndex = i1; + return this.copySampleValue_( i1 - 1 ); - boundTexture.type = webglType; - boundTexture.texture = webglTexture; + } - } + } // seek - } + this._cachedIndex = i1; - function unbindTexture() { + this.intervalChanged_( i1, t0, t1 ); - const boundTexture = currentBoundTextures[ currentTextureSlot ]; + } // validate_interval - if ( boundTexture !== undefined && boundTexture.type !== undefined ) { + return this.interpolate_( i1, t0, t, t1 ); - gl.bindTexture( boundTexture.type, null ); + } - boundTexture.type = undefined; - boundTexture.texture = undefined; + /** + * Returns the interpolation settings. + * + * @return {Object} The interpolation settings. + */ + getSettings_() { - } + return this.settings || this.DefaultSettings_; } - function compressedTexImage2D() { + /** + * Copies a sample value to the result buffer. + * + * @param {number} index - An index into the sample value buffer. + * @return {TypedArray} The result buffer. + */ + copySampleValue_( index ) { - try { + // copies a sample value to the result buffer - gl.compressedTexImage2D.apply( gl, arguments ); + const result = this.resultBuffer, + values = this.sampleValues, + stride = this.valueSize, + offset = index * stride; - } catch ( error ) { + for ( let i = 0; i !== stride; ++ i ) { - console.error( 'THREE.WebGLState:', error ); + result[ i ] = values[ offset + i ]; } - } + return result; - function compressedTexImage3D() { + } - try { + /** + * Copies a sample value to the result buffer. + * + * @abstract + * @param {number} i1 - An index into the sample value buffer. + * @param {number} t0 - The previous interpolation factor. + * @param {number} t - The current interpolation factor. + * @param {number} t1 - The next interpolation factor. + * @return {TypedArray} The result buffer. + */ + interpolate_( /* i1, t0, t, t1 */ ) { - gl.compressedTexImage3D.apply( gl, arguments ); + throw new Error( 'call to abstract method' ); + // implementations shall return this.resultBuffer - } catch ( error ) { + } - console.error( 'THREE.WebGLState:', error ); + /** + * Optional method that is executed when the interval has changed. + * + * @param {number} i1 - An index into the sample value buffer. + * @param {number} t0 - The previous interpolation factor. + * @param {number} t - The current interpolation factor. + */ + intervalChanged_( /* i1, t0, t1 */ ) { - } + // empty } - function texSubImage2D() { +} - try { +/** + * Fast and simple cubic spline interpolant. + * + * It was derived from a Hermitian construction setting the first derivative + * at each sample position to the linear slope between neighboring positions + * over their parameter interval. + * + * @augments Interpolant + */ +class CubicInterpolant extends Interpolant { - gl.texSubImage2D.apply( gl, arguments ); + /** + * Constructs a new cubic interpolant. + * + * @param {TypedArray} parameterPositions - The parameter positions hold the interpolation factors. + * @param {TypedArray} sampleValues - The sample values. + * @param {number} sampleSize - The sample size + * @param {TypedArray} [resultBuffer] - The result buffer. + */ + constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { - } catch ( error ) { + super( parameterPositions, sampleValues, sampleSize, resultBuffer ); - console.error( 'THREE.WebGLState:', error ); + this._weightPrev = -0; + this._offsetPrev = -0; + this._weightNext = -0; + this._offsetNext = -0; - } + this.DefaultSettings_ = { + + endingStart: ZeroCurvatureEnding, + endingEnd: ZeroCurvatureEnding + + }; } - function texSubImage3D() { + intervalChanged_( i1, t0, t1 ) { - try { + const pp = this.parameterPositions; + let iPrev = i1 - 2, + iNext = i1 + 1, - gl.texSubImage3D.apply( gl, arguments ); + tPrev = pp[ iPrev ], + tNext = pp[ iNext ]; - } catch ( error ) { + if ( tPrev === undefined ) { - console.error( 'THREE.WebGLState:', error ); + switch ( this.getSettings_().endingStart ) { - } + case ZeroSlopeEnding: - } + // f'(t0) = 0 + iPrev = i1; + tPrev = 2 * t0 - t1; - function compressedTexSubImage2D() { + break; - try { + case WrapAroundEnding: - gl.compressedTexSubImage2D.apply( gl, arguments ); + // use the other end of the curve + iPrev = pp.length - 2; + tPrev = t0 + pp[ iPrev ] - pp[ iPrev + 1 ]; - } catch ( error ) { + break; - console.error( 'THREE.WebGLState:', error ); + default: // ZeroCurvatureEnding - } + // f''(t0) = 0 a.k.a. Natural Spline + iPrev = i1; + tPrev = t1; - } + } - function compressedTexSubImage3D() { + } - try { + if ( tNext === undefined ) { - gl.compressedTexSubImage3D.apply( gl, arguments ); + switch ( this.getSettings_().endingEnd ) { - } catch ( error ) { + case ZeroSlopeEnding: - console.error( 'THREE.WebGLState:', error ); + // f'(tN) = 0 + iNext = i1; + tNext = 2 * t1 - t0; - } + break; - } + case WrapAroundEnding: - function texStorage2D() { + // use the other end of the curve + iNext = 1; + tNext = t1 + pp[ 1 ] - pp[ 0 ]; - try { + break; - gl.texStorage2D.apply( gl, arguments ); + default: // ZeroCurvatureEnding - } catch ( error ) { + // f''(tN) = 0, a.k.a. Natural Spline + iNext = i1 - 1; + tNext = t0; - console.error( 'THREE.WebGLState:', error ); + } } - } - - function texStorage3D() { + const halfDt = ( t1 - t0 ) * 0.5, + stride = this.valueSize; - try { + this._weightPrev = halfDt / ( t0 - tPrev ); + this._weightNext = halfDt / ( tNext - t1 ); + this._offsetPrev = iPrev * stride; + this._offsetNext = iNext * stride; - gl.texStorage3D.apply( gl, arguments ); + } - } catch ( error ) { + interpolate_( i1, t0, t, t1 ) { - console.error( 'THREE.WebGLState:', error ); + const result = this.resultBuffer, + values = this.sampleValues, + stride = this.valueSize, - } + o1 = i1 * stride, o0 = o1 - stride, + oP = this._offsetPrev, oN = this._offsetNext, + wP = this._weightPrev, wN = this._weightNext, - } + p = ( t - t0 ) / ( t1 - t0 ), + pp = p * p, + ppp = pp * p; - function texImage2D() { + // evaluate polynomials - try { + const sP = - wP * ppp + 2 * wP * pp - wP * p; + const s0 = ( 1 + wP ) * ppp + ( -1.5 - 2 * wP ) * pp + ( -0.5 + wP ) * p + 1; + const s1 = ( -1 - wN ) * ppp + ( 1.5 + wN ) * pp + 0.5 * p; + const sN = wN * ppp - wN * pp; - gl.texImage2D.apply( gl, arguments ); + // combine data linearly - } catch ( error ) { + for ( let i = 0; i !== stride; ++ i ) { - console.error( 'THREE.WebGLState:', error ); + result[ i ] = + sP * values[ oP + i ] + + s0 * values[ o0 + i ] + + s1 * values[ o1 + i ] + + sN * values[ oN + i ]; } + return result; + } - function texImage3D() { +} - try { +/** + * A basic linear interpolant. + * + * @augments Interpolant + */ +class LinearInterpolant extends Interpolant { - gl.texImage3D.apply( gl, arguments ); + /** + * Constructs a new linear interpolant. + * + * @param {TypedArray} parameterPositions - The parameter positions hold the interpolation factors. + * @param {TypedArray} sampleValues - The sample values. + * @param {number} sampleSize - The sample size + * @param {TypedArray} [resultBuffer] - The result buffer. + */ + constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { - } catch ( error ) { + super( parameterPositions, sampleValues, sampleSize, resultBuffer ); - console.error( 'THREE.WebGLState:', error ); + } - } + interpolate_( i1, t0, t, t1 ) { - } + const result = this.resultBuffer, + values = this.sampleValues, + stride = this.valueSize, - // + offset1 = i1 * stride, + offset0 = offset1 - stride, - function scissor( scissor ) { + weight1 = ( t - t0 ) / ( t1 - t0 ), + weight0 = 1 - weight1; - if ( currentScissor.equals( scissor ) === false ) { + for ( let i = 0; i !== stride; ++ i ) { - gl.scissor( scissor.x, scissor.y, scissor.z, scissor.w ); - currentScissor.copy( scissor ); + result[ i ] = + values[ offset0 + i ] * weight0 + + values[ offset1 + i ] * weight1; } + return result; + } - function viewport( viewport ) { +} - if ( currentViewport.equals( viewport ) === false ) { +/** + * Interpolant that evaluates to the sample value at the position preceding + * the parameter. + * + * @augments Interpolant + */ +class DiscreteInterpolant extends Interpolant { - gl.viewport( viewport.x, viewport.y, viewport.z, viewport.w ); - currentViewport.copy( viewport ); + /** + * Constructs a new discrete interpolant. + * + * @param {TypedArray} parameterPositions - The parameter positions hold the interpolation factors. + * @param {TypedArray} sampleValues - The sample values. + * @param {number} sampleSize - The sample size + * @param {TypedArray} [resultBuffer] - The result buffer. + */ + constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { - } + super( parameterPositions, sampleValues, sampleSize, resultBuffer ); } - function updateUBOMapping( uniformsGroup, program ) { + interpolate_( i1 /*, t0, t, t1 */ ) { - let mapping = uboProgramMap.get( program ); + return this.copySampleValue_( i1 - 1 ); - if ( mapping === undefined ) { + } - mapping = new WeakMap(); +} - uboProgramMap.set( program, mapping ); +/** + * Represents s a timed sequence of keyframes, which are composed of lists of + * times and related values, and which are used to animate a specific property + * of an object. + */ +class KeyframeTrack { - } + /** + * Constructs a new keyframe track. + * + * @param {string} name - The keyframe track's name. + * @param {Array} times - A list of keyframe times. + * @param {Array} values - A list of keyframe values. + * @param {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth)} [interpolation] - The interpolation type. + */ + constructor( name, times, values, interpolation ) { - let blockIndex = mapping.get( uniformsGroup ); + if ( name === undefined ) throw new Error( 'THREE.KeyframeTrack: track name is undefined' ); + if ( times === undefined || times.length === 0 ) throw new Error( 'THREE.KeyframeTrack: no keyframes in track named ' + name ); - if ( blockIndex === undefined ) { + /** + * The track's name can refer to morph targets or bones or + * possibly other values within an animated object. See {@link PropertyBinding#parseTrackName} + * for the forms of strings that can be parsed for property binding. + * + * @type {string} + */ + this.name = name; - blockIndex = gl.getUniformBlockIndex( program, uniformsGroup.name ); + /** + * The keyframe times. + * + * @type {Float32Array} + */ + this.times = convertArray( times, this.TimeBufferType ); - mapping.set( uniformsGroup, blockIndex ); + /** + * The keyframe values. + * + * @type {Float32Array} + */ + this.values = convertArray( values, this.ValueBufferType ); - } + this.setInterpolation( interpolation || this.DefaultInterpolation ); } - function uniformBlockBinding( uniformsGroup, program ) { - - const mapping = uboProgramMap.get( program ); - const blockIndex = mapping.get( uniformsGroup ); + /** + * Converts the keyframe track to JSON. + * + * @static + * @param {KeyframeTrack} track - The keyframe track to serialize. + * @return {Object} The serialized keyframe track as JSON. + */ + static toJSON( track ) { - if ( uboBindings.get( program ) !== blockIndex ) { + const trackType = track.constructor; - // bind shader specific block index to global block point - gl.uniformBlockBinding( program, blockIndex, uniformsGroup.__bindingPointIndex ); + let json; - uboBindings.set( program, blockIndex ); + // derived classes can define a static toJSON method + if ( trackType.toJSON !== this.toJSON ) { - } + json = trackType.toJSON( track ); - } + } else { - // + // by default, we assume the data can be serialized as-is + json = { - function reset() { + 'name': track.name, + 'times': convertArray( track.times, Array ), + 'values': convertArray( track.values, Array ) - // reset state + }; - gl.disable( gl.BLEND ); - gl.disable( gl.CULL_FACE ); - gl.disable( gl.DEPTH_TEST ); - gl.disable( gl.POLYGON_OFFSET_FILL ); - gl.disable( gl.SCISSOR_TEST ); - gl.disable( gl.STENCIL_TEST ); - gl.disable( gl.SAMPLE_ALPHA_TO_COVERAGE ); + const interpolation = track.getInterpolation(); - gl.blendEquation( gl.FUNC_ADD ); - gl.blendFunc( gl.ONE, gl.ZERO ); - gl.blendFuncSeparate( gl.ONE, gl.ZERO, gl.ONE, gl.ZERO ); + if ( interpolation !== track.DefaultInterpolation ) { - gl.colorMask( true, true, true, true ); - gl.clearColor( 0, 0, 0, 0 ); + json.interpolation = interpolation; - gl.depthMask( true ); - gl.depthFunc( gl.LESS ); - gl.clearDepth( 1 ); + } - gl.stencilMask( 0xffffffff ); - gl.stencilFunc( gl.ALWAYS, 0, 0xffffffff ); - gl.stencilOp( gl.KEEP, gl.KEEP, gl.KEEP ); - gl.clearStencil( 0 ); + } - gl.cullFace( gl.BACK ); - gl.frontFace( gl.CCW ); + json.type = track.ValueTypeName; // mandatory - gl.polygonOffset( 0, 0 ); + return json; - gl.activeTexture( gl.TEXTURE0 ); + } - gl.bindFramebuffer( gl.FRAMEBUFFER, null ); + /** + * Factory method for creating a new discrete interpolant. + * + * @static + * @param {TypedArray} [result] - The result buffer. + * @return {DiscreteInterpolant} The new interpolant. + */ + InterpolantFactoryMethodDiscrete( result ) { - if ( isWebGL2 === true ) { + return new DiscreteInterpolant( this.times, this.values, this.getValueSize(), result ); - gl.bindFramebuffer( gl.DRAW_FRAMEBUFFER, null ); - gl.bindFramebuffer( gl.READ_FRAMEBUFFER, null ); + } - } + /** + * Factory method for creating a new linear interpolant. + * + * @static + * @param {TypedArray} [result] - The result buffer. + * @return {LinearInterpolant} The new interpolant. + */ + InterpolantFactoryMethodLinear( result ) { - gl.useProgram( null ); + return new LinearInterpolant( this.times, this.values, this.getValueSize(), result ); - gl.lineWidth( 1 ); + } - gl.scissor( 0, 0, gl.canvas.width, gl.canvas.height ); - gl.viewport( 0, 0, gl.canvas.width, gl.canvas.height ); + /** + * Factory method for creating a new smooth interpolant. + * + * @static + * @param {TypedArray} [result] - The result buffer. + * @return {CubicInterpolant} The new interpolant. + */ + InterpolantFactoryMethodSmooth( result ) { - // reset internals + return new CubicInterpolant( this.times, this.values, this.getValueSize(), result ); - enabledCapabilities = {}; + } - currentTextureSlot = null; - currentBoundTextures = {}; + /** + * Defines the interpolation factor method for this keyframe track. + * + * @param {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth)} interpolation - The interpolation type. + * @return {KeyframeTrack} A reference to this keyframe track. + */ + setInterpolation( interpolation ) { - currentBoundFramebuffers = {}; - currentDrawbuffers = new WeakMap(); - defaultDrawbuffers = []; + let factoryMethod; - currentProgram = null; + switch ( interpolation ) { - currentBlendingEnabled = false; - currentBlending = null; - currentBlendEquation = null; - currentBlendSrc = null; - currentBlendDst = null; - currentBlendEquationAlpha = null; - currentBlendSrcAlpha = null; - currentBlendDstAlpha = null; - currentPremultipledAlpha = false; + case InterpolateDiscrete: - currentFlipSided = null; - currentCullFace = null; + factoryMethod = this.InterpolantFactoryMethodDiscrete; - currentLineWidth = null; + break; - currentPolygonOffsetFactor = null; - currentPolygonOffsetUnits = null; + case InterpolateLinear: - currentScissor.set( 0, 0, gl.canvas.width, gl.canvas.height ); - currentViewport.set( 0, 0, gl.canvas.width, gl.canvas.height ); + factoryMethod = this.InterpolantFactoryMethodLinear; - colorBuffer.reset(); - depthBuffer.reset(); - stencilBuffer.reset(); + break; - } + case InterpolateSmooth: - return { + factoryMethod = this.InterpolantFactoryMethodSmooth; - buffers: { - color: colorBuffer, - depth: depthBuffer, - stencil: stencilBuffer - }, + break; - enable: enable, - disable: disable, + } - bindFramebuffer: bindFramebuffer, - drawBuffers: drawBuffers, + if ( factoryMethod === undefined ) { - useProgram: useProgram, + const message = 'unsupported interpolation for ' + + this.ValueTypeName + ' keyframe track named ' + this.name; - setBlending: setBlending, - setMaterial: setMaterial, + if ( this.createInterpolant === undefined ) { - setFlipSided: setFlipSided, - setCullFace: setCullFace, + // fall back to default, unless the default itself is messed up + if ( interpolation !== this.DefaultInterpolation ) { - setLineWidth: setLineWidth, - setPolygonOffset: setPolygonOffset, + this.setInterpolation( this.DefaultInterpolation ); - setScissorTest: setScissorTest, + } else { - activeTexture: activeTexture, - bindTexture: bindTexture, - unbindTexture: unbindTexture, - compressedTexImage2D: compressedTexImage2D, - compressedTexImage3D: compressedTexImage3D, - texImage2D: texImage2D, - texImage3D: texImage3D, + throw new Error( message ); // fatal, in this case - updateUBOMapping: updateUBOMapping, - uniformBlockBinding: uniformBlockBinding, + } - texStorage2D: texStorage2D, - texStorage3D: texStorage3D, - texSubImage2D: texSubImage2D, - texSubImage3D: texSubImage3D, - compressedTexSubImage2D: compressedTexSubImage2D, - compressedTexSubImage3D: compressedTexSubImage3D, + } - scissor: scissor, - viewport: viewport, + console.warn( 'THREE.KeyframeTrack:', message ); + return this; - reset: reset + } - }; + this.createInterpolant = factoryMethod; -} + return this; -function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, info ) { + } - const isWebGL2 = capabilities.isWebGL2; - const maxTextures = capabilities.maxTextures; - const maxCubemapSize = capabilities.maxCubemapSize; - const maxTextureSize = capabilities.maxTextureSize; - const maxSamples = capabilities.maxSamples; - const multisampledRTTExt = extensions.has( 'WEBGL_multisampled_render_to_texture' ) ? extensions.get( 'WEBGL_multisampled_render_to_texture' ) : null; - const supportsInvalidateFramebuffer = typeof navigator === 'undefined' ? false : /OculusBrowser/g.test( navigator.userAgent ); + /** + * Returns the current interpolation type. + * + * @return {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth)} The interpolation type. + */ + getInterpolation() { - const _videoTextures = new WeakMap(); - let _canvas; + switch ( this.createInterpolant ) { - const _sources = new WeakMap(); // maps WebglTexture objects to instances of Source + case this.InterpolantFactoryMethodDiscrete: - // cordova iOS (as of 5.0) still uses UIWebView, which provides OffscreenCanvas, - // also OffscreenCanvas.getContext("webgl"), but not OffscreenCanvas.getContext("2d")! - // Some implementations may only implement OffscreenCanvas partially (e.g. lacking 2d). + return InterpolateDiscrete; - let useOffscreenCanvas = false; + case this.InterpolantFactoryMethodLinear: - try { + return InterpolateLinear; - useOffscreenCanvas = typeof OffscreenCanvas !== 'undefined' - // eslint-disable-next-line compat/compat - && ( new OffscreenCanvas( 1, 1 ).getContext( '2d' ) ) !== null; + case this.InterpolantFactoryMethodSmooth: - } catch ( err ) { + return InterpolateSmooth; - // Ignore any errors + } } - function createCanvas( width, height ) { - - // Use OffscreenCanvas when available. Specially needed in web workers + /** + * Returns the value size. + * + * @return {number} The value size. + */ + getValueSize() { - return useOffscreenCanvas ? - // eslint-disable-next-line compat/compat - new OffscreenCanvas( width, height ) : createElementNS( 'canvas' ); + return this.values.length / this.times.length; } - function resizeImage( image, needsPowerOfTwo, needsNewCanvas, maxSize ) { + /** + * Moves all keyframes either forward or backward in time. + * + * @param {number} timeOffset - The offset to move the time values. + * @return {KeyframeTrack} A reference to this keyframe track. + */ + shift( timeOffset ) { - let scale = 1; + if ( timeOffset !== 0.0 ) { - // handle case if texture exceeds max size + const times = this.times; + + for ( let i = 0, n = times.length; i !== n; ++ i ) { - if ( image.width > maxSize || image.height > maxSize ) { + times[ i ] += timeOffset; - scale = maxSize / Math.max( image.width, image.height ); + } } - // only perform resize if necessary + return this; - if ( scale < 1 || needsPowerOfTwo === true ) { + } - // only perform resize for certain image types + /** + * Scale all keyframe times by a factor (useful for frame - seconds conversions). + * + * @param {number} timeScale - The time scale. + * @return {KeyframeTrack} A reference to this keyframe track. + */ + scale( timeScale ) { - if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || - ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) || - ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) { + if ( timeScale !== 1.0 ) { - const floor = needsPowerOfTwo ? floorPowerOfTwo : Math.floor; + const times = this.times; - const width = floor( scale * image.width ); - const height = floor( scale * image.height ); + for ( let i = 0, n = times.length; i !== n; ++ i ) { - if ( _canvas === undefined ) _canvas = createCanvas( width, height ); + times[ i ] *= timeScale; - // cube textures can't reuse the same canvas + } - const canvas = needsNewCanvas ? createCanvas( width, height ) : _canvas; + } - canvas.width = width; - canvas.height = height; + return this; - const context = canvas.getContext( '2d' ); - context.drawImage( image, 0, 0, width, height ); + } - console.warn( 'THREE.WebGLRenderer: Texture has been resized from (' + image.width + 'x' + image.height + ') to (' + width + 'x' + height + ').' ); + /** + * Removes keyframes before and after animation without changing any values within the defined time range. + * + * Note: The method does not shift around keys to the start of the track time, because for interpolated + * keys this will change their values + * + * @param {number} startTime - The start time. + * @param {number} endTime - The end time. + * @return {KeyframeTrack} A reference to this keyframe track. + */ + trim( startTime, endTime ) { - return canvas; + const times = this.times, + nKeys = times.length; - } else { + let from = 0, + to = nKeys - 1; - if ( 'data' in image ) { + while ( from !== nKeys && times[ from ] < startTime ) { - console.warn( 'THREE.WebGLRenderer: Image in DataTexture is too big (' + image.width + 'x' + image.height + ').' ); + ++ from; - } + } - return image; + while ( to !== -1 && times[ to ] > endTime ) { - } + -- to; } - return image; + ++ to; // inclusive -> exclusive bound - } + if ( from !== 0 || to !== nKeys ) { - function isPowerOfTwo$1( image ) { + // empty tracks are forbidden, so keep at least one keyframe + if ( from >= to ) { - return isPowerOfTwo( image.width ) && isPowerOfTwo( image.height ); + to = Math.max( to, 1 ); + from = to - 1; - } + } - function textureNeedsPowerOfTwo( texture ) { + const stride = this.getValueSize(); + this.times = times.slice( from, to ); + this.values = this.values.slice( from * stride, to * stride ); - if ( isWebGL2 ) return false; + } - return ( texture.wrapS !== ClampToEdgeWrapping || texture.wrapT !== ClampToEdgeWrapping ) || - ( texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter ); + return this; } - function textureNeedsGenerateMipmaps( texture, supportsMips ) { + /** + * Performs minimal validation on the keyframe track. Returns `true` if the values + * are valid. + * + * @return {boolean} Whether the keyframes are valid or not. + */ + validate() { - return texture.generateMipmaps && supportsMips && - texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter; + let valid = true; - } + const valueSize = this.getValueSize(); + if ( valueSize - Math.floor( valueSize ) !== 0 ) { - function generateMipmap( target ) { + console.error( 'THREE.KeyframeTrack: Invalid value size in track.', this ); + valid = false; - _gl.generateMipmap( target ); + } - } + const times = this.times, + values = this.values, - function getInternalFormat( internalFormatName, glFormat, glType, colorSpace, forceLinearTransfer = false ) { + nKeys = times.length; - if ( isWebGL2 === false ) return glFormat; + if ( nKeys === 0 ) { - if ( internalFormatName !== null ) { + console.error( 'THREE.KeyframeTrack: Track is empty.', this ); + valid = false; - if ( _gl[ internalFormatName ] !== undefined ) return _gl[ internalFormatName ]; + } - console.warn( 'THREE.WebGLRenderer: Attempt to use non-existing WebGL internal format \'' + internalFormatName + '\'' ); + let prevTime = null; - } + for ( let i = 0; i !== nKeys; i ++ ) { - let internalFormat = glFormat; + const currTime = times[ i ]; - if ( glFormat === _gl.RED ) { + if ( typeof currTime === 'number' && isNaN( currTime ) ) { - if ( glType === _gl.FLOAT ) internalFormat = _gl.R32F; - if ( glType === _gl.HALF_FLOAT ) internalFormat = _gl.R16F; - if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = _gl.R8; + console.error( 'THREE.KeyframeTrack: Time is not a valid number.', this, i, currTime ); + valid = false; + break; - } + } - if ( glFormat === _gl.RG ) { + if ( prevTime !== null && prevTime > currTime ) { - if ( glType === _gl.FLOAT ) internalFormat = _gl.RG32F; - if ( glType === _gl.HALF_FLOAT ) internalFormat = _gl.RG16F; - if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = _gl.RG8; + console.error( 'THREE.KeyframeTrack: Out of order keys.', this, i, currTime, prevTime ); + valid = false; + break; + + } + + prevTime = currTime; } - if ( glFormat === _gl.RGBA ) { + if ( values !== undefined ) { - if ( glType === _gl.FLOAT ) internalFormat = _gl.RGBA32F; - if ( glType === _gl.HALF_FLOAT ) internalFormat = _gl.RGBA16F; - if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = ( colorSpace === SRGBColorSpace && forceLinearTransfer === false ) ? _gl.SRGB8_ALPHA8 : _gl.RGBA8; - if ( glType === _gl.UNSIGNED_SHORT_4_4_4_4 ) internalFormat = _gl.RGBA4; - if ( glType === _gl.UNSIGNED_SHORT_5_5_5_1 ) internalFormat = _gl.RGB5_A1; + if ( isTypedArray( values ) ) { - } + for ( let i = 0, n = values.length; i !== n; ++ i ) { - if ( internalFormat === _gl.R16F || internalFormat === _gl.R32F || - internalFormat === _gl.RG16F || internalFormat === _gl.RG32F || - internalFormat === _gl.RGBA16F || internalFormat === _gl.RGBA32F ) { + const value = values[ i ]; - extensions.get( 'EXT_color_buffer_float' ); + if ( isNaN( value ) ) { - } + console.error( 'THREE.KeyframeTrack: Value is not a valid number.', this, i, value ); + valid = false; + break; - return internalFormat; + } - } + } - function getMipLevels( texture, image, supportsMips ) { + } - if ( textureNeedsGenerateMipmaps( texture, supportsMips ) === true || ( texture.isFramebufferTexture && texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter ) ) { + } - return Math.log2( Math.max( image.width, image.height ) ) + 1; + return valid; - } else if ( texture.mipmaps !== undefined && texture.mipmaps.length > 0 ) { + } - // user-defined mipmaps + /** + * Optimizes this keyframe track by removing equivalent sequential keys (which are + * common in morph target sequences). + * + * @return {AnimationClip} A reference to this animation clip. + */ + optimize() { - return texture.mipmaps.length; + // (0,0,0,0,1,1,1,0,0,0,0,0,0,0) --> (0,0,1,1,0,0) - } else if ( texture.isCompressedTexture && Array.isArray( texture.image ) ) { + // times or values may be shared with other tracks, so overwriting is unsafe + const times = this.times.slice(), + values = this.values.slice(), + stride = this.getValueSize(), - return image.mipmaps.length; + smoothInterpolation = this.getInterpolation() === InterpolateSmooth, - } else { + lastIndex = times.length - 1; - // texture without mipmaps (only base level) + let writeIndex = 1; - return 1; + for ( let i = 1; i < lastIndex; ++ i ) { - } + let keep = false; - } + const time = times[ i ]; + const timeNext = times[ i + 1 ]; - // Fallback filters for non-power-of-2 textures + // remove adjacent keyframes scheduled at the same time - function filterFallback( f ) { + if ( time !== timeNext && ( i !== 1 || time !== times[ 0 ] ) ) { - if ( f === NearestFilter || f === NearestMipmapNearestFilter || f === NearestMipmapLinearFilter ) { + if ( ! smoothInterpolation ) { - return _gl.NEAREST; + // remove unnecessary keyframes same as their neighbors - } + const offset = i * stride, + offsetP = offset - stride, + offsetN = offset + stride; - return _gl.LINEAR; + for ( let j = 0; j !== stride; ++ j ) { - } + const value = values[ offset + j ]; - // + if ( value !== values[ offsetP + j ] || + value !== values[ offsetN + j ] ) { - function onTextureDispose( event ) { + keep = true; + break; - const texture = event.target; + } - texture.removeEventListener( 'dispose', onTextureDispose ); + } - deallocateTexture( texture ); + } else { - if ( texture.isVideoTexture ) { + keep = true; - _videoTextures.delete( texture ); + } - } + } - } + // in-place compaction - function onRenderTargetDispose( event ) { + if ( keep ) { - const renderTarget = event.target; + if ( i !== writeIndex ) { - renderTarget.removeEventListener( 'dispose', onRenderTargetDispose ); + times[ writeIndex ] = times[ i ]; - deallocateRenderTarget( renderTarget ); + const readOffset = i * stride, + writeOffset = writeIndex * stride; - } + for ( let j = 0; j !== stride; ++ j ) { - // + values[ writeOffset + j ] = values[ readOffset + j ]; - function deallocateTexture( texture ) { + } - const textureProperties = properties.get( texture ); + } - if ( textureProperties.__webglInit === undefined ) return; + ++ writeIndex; - // check if it's necessary to remove the WebGLTexture object + } - const source = texture.source; - const webglTextures = _sources.get( source ); + } - if ( webglTextures ) { + // flush last keyframe (compaction looks ahead) - const webglTexture = webglTextures[ textureProperties.__cacheKey ]; - webglTexture.usedTimes --; + if ( lastIndex > 0 ) { - // the WebGLTexture object is not used anymore, remove it + times[ writeIndex ] = times[ lastIndex ]; - if ( webglTexture.usedTimes === 0 ) { + for ( let readOffset = lastIndex * stride, writeOffset = writeIndex * stride, j = 0; j !== stride; ++ j ) { - deleteTexture( texture ); + values[ writeOffset + j ] = values[ readOffset + j ]; } - // remove the weak map entry if no WebGLTexture uses the source anymore + ++ writeIndex; - if ( Object.keys( webglTextures ).length === 0 ) { + } - _sources.delete( source ); + if ( writeIndex !== times.length ) { - } + this.times = times.slice( 0, writeIndex ); + this.values = values.slice( 0, writeIndex * stride ); + + } else { + + this.times = times; + this.values = values; } - properties.remove( texture ); + return this; } - function deleteTexture( texture ) { + /** + * Returns a new keyframe track with copied values from this instance. + * + * @return {KeyframeTrack} A clone of this instance. + */ + clone() { - const textureProperties = properties.get( texture ); - _gl.deleteTexture( textureProperties.__webglTexture ); + const times = this.times.slice(); + const values = this.values.slice(); - const source = texture.source; - const webglTextures = _sources.get( source ); - delete webglTextures[ textureProperties.__cacheKey ]; + const TypedKeyframeTrack = this.constructor; + const track = new TypedKeyframeTrack( this.name, times, values ); - info.memory.textures --; + // Interpolant argument to constructor is not saved, so copy the factory method directly. + track.createInterpolant = this.createInterpolant; + + return track; } - function deallocateRenderTarget( renderTarget ) { +} - const texture = renderTarget.texture; +/** + * The value type name. + * + * @type {String} + * @default '' + */ +KeyframeTrack.prototype.ValueTypeName = ''; - const renderTargetProperties = properties.get( renderTarget ); - const textureProperties = properties.get( texture ); +/** + * The time buffer type of this keyframe track. + * + * @type {TypedArray|Array} + * @default Float32Array.constructor + */ +KeyframeTrack.prototype.TimeBufferType = Float32Array; - if ( textureProperties.__webglTexture !== undefined ) { +/** + * The value buffer type of this keyframe track. + * + * @type {TypedArray|Array} + * @default Float32Array.constructor + */ +KeyframeTrack.prototype.ValueBufferType = Float32Array; - _gl.deleteTexture( textureProperties.__webglTexture ); +/** + * The default interpolation type of this keyframe track. + * + * @type {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth)} + * @default InterpolateLinear + */ +KeyframeTrack.prototype.DefaultInterpolation = InterpolateLinear; - info.memory.textures --; +/** + * A track for boolean keyframe values. + * + * @augments KeyframeTrack + */ +class BooleanKeyframeTrack extends KeyframeTrack { - } + /** + * Constructs a new boolean keyframe track. + * + * This keyframe track type has no `interpolation` parameter because the + * interpolation is always discrete. + * + * @param {string} name - The keyframe track's name. + * @param {Array} times - A list of keyframe times. + * @param {Array} values - A list of keyframe values. + */ + constructor( name, times, values ) { - if ( renderTarget.depthTexture ) { + super( name, times, values ); - renderTarget.depthTexture.dispose(); + } - } +} - if ( renderTarget.isWebGLCubeRenderTarget ) { +/** + * The value type name. + * + * @type {String} + * @default 'bool' + */ +BooleanKeyframeTrack.prototype.ValueTypeName = 'bool'; - for ( let i = 0; i < 6; i ++ ) { +/** + * The value buffer type of this keyframe track. + * + * @type {TypedArray|Array} + * @default Array.constructor + */ +BooleanKeyframeTrack.prototype.ValueBufferType = Array; - _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer[ i ] ); - if ( renderTargetProperties.__webglDepthbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthbuffer[ i ] ); +/** + * The default interpolation type of this keyframe track. + * + * @type {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth)} + * @default InterpolateDiscrete + */ +BooleanKeyframeTrack.prototype.DefaultInterpolation = InterpolateDiscrete; +BooleanKeyframeTrack.prototype.InterpolantFactoryMethodLinear = undefined; +BooleanKeyframeTrack.prototype.InterpolantFactoryMethodSmooth = undefined; - } +/** + * A track for color keyframe values. + * + * @augments KeyframeTrack + */ +class ColorKeyframeTrack extends KeyframeTrack { - } else { + /** + * Constructs a new color keyframe track. + * + * @param {string} name - The keyframe track's name. + * @param {Array} times - A list of keyframe times. + * @param {Array} values - A list of keyframe values. + * @param {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth)} [interpolation] - The interpolation type. + */ + constructor( name, times, values, interpolation ) { - _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer ); - if ( renderTargetProperties.__webglDepthbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthbuffer ); - if ( renderTargetProperties.__webglMultisampledFramebuffer ) _gl.deleteFramebuffer( renderTargetProperties.__webglMultisampledFramebuffer ); + super( name, times, values, interpolation ); - if ( renderTargetProperties.__webglColorRenderbuffer ) { + } - for ( let i = 0; i < renderTargetProperties.__webglColorRenderbuffer.length; i ++ ) { +} - if ( renderTargetProperties.__webglColorRenderbuffer[ i ] ) _gl.deleteRenderbuffer( renderTargetProperties.__webglColorRenderbuffer[ i ] ); +/** + * The value type name. + * + * @type {String} + * @default 'color' + */ +ColorKeyframeTrack.prototype.ValueTypeName = 'color'; - } +/** + * A track for numeric keyframe values. + * + * @augments KeyframeTrack + */ +class NumberKeyframeTrack extends KeyframeTrack { - } + /** + * Constructs a new number keyframe track. + * + * @param {string} name - The keyframe track's name. + * @param {Array} times - A list of keyframe times. + * @param {Array} values - A list of keyframe values. + * @param {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth)} [interpolation] - The interpolation type. + */ + constructor( name, times, values, interpolation ) { - if ( renderTargetProperties.__webglDepthRenderbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthRenderbuffer ); + super( name, times, values, interpolation ); - } + } + +} + +/** + * The value type name. + * + * @type {String} + * @default 'number' + */ +NumberKeyframeTrack.prototype.ValueTypeName = 'number'; - if ( renderTarget.isWebGLMultipleRenderTargets ) { +/** + * Spherical linear unit quaternion interpolant. + * + * @augments Interpolant + */ +class QuaternionLinearInterpolant extends Interpolant { + + /** + * Constructs a new SLERP interpolant. + * + * @param {TypedArray} parameterPositions - The parameter positions hold the interpolation factors. + * @param {TypedArray} sampleValues - The sample values. + * @param {number} sampleSize - The sample size + * @param {TypedArray} [resultBuffer] - The result buffer. + */ + constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { - for ( let i = 0, il = texture.length; i < il; i ++ ) { + super( parameterPositions, sampleValues, sampleSize, resultBuffer ); - const attachmentProperties = properties.get( texture[ i ] ); + } - if ( attachmentProperties.__webglTexture ) { + interpolate_( i1, t0, t, t1 ) { - _gl.deleteTexture( attachmentProperties.__webglTexture ); + const result = this.resultBuffer, + values = this.sampleValues, + stride = this.valueSize, - info.memory.textures --; + alpha = ( t - t0 ) / ( t1 - t0 ); - } + let offset = i1 * stride; - properties.remove( texture[ i ] ); + for ( let end = offset + stride; offset !== end; offset += 4 ) { - } + Quaternion.slerpFlat( result, 0, values, offset - stride, values, offset, alpha ); } - properties.remove( texture ); - properties.remove( renderTarget ); + return result; } - // +} - let textureUnits = 0; +/** + * A track for Quaternion keyframe values. + * + * @augments KeyframeTrack + */ +class QuaternionKeyframeTrack extends KeyframeTrack { - function resetTextureUnits() { + /** + * Constructs a new Quaternion keyframe track. + * + * @param {string} name - The keyframe track's name. + * @param {Array} times - A list of keyframe times. + * @param {Array} values - A list of keyframe values. + * @param {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth)} [interpolation] - The interpolation type. + */ + constructor( name, times, values, interpolation ) { - textureUnits = 0; + super( name, times, values, interpolation ); } - function allocateTextureUnit() { + /** + * Overwritten so the method returns Quaternion based interpolant. + * + * @static + * @param {TypedArray} [result] - The result buffer. + * @return {QuaternionLinearInterpolant} The new interpolant. + */ + InterpolantFactoryMethodLinear( result ) { - const textureUnit = textureUnits; + return new QuaternionLinearInterpolant( this.times, this.values, this.getValueSize(), result ); - if ( textureUnit >= maxTextures ) { + } - console.warn( 'THREE.WebGLTextures: Trying to use ' + textureUnit + ' texture units while this GPU supports only ' + maxTextures ); +} - } +/** + * The value type name. + * + * @type {String} + * @default 'quaternion' + */ +QuaternionKeyframeTrack.prototype.ValueTypeName = 'quaternion'; +// ValueBufferType is inherited +// DefaultInterpolation is inherited; +QuaternionKeyframeTrack.prototype.InterpolantFactoryMethodSmooth = undefined; - textureUnits += 1; +/** + * A track for string keyframe values. + * + * @augments KeyframeTrack + */ +class StringKeyframeTrack extends KeyframeTrack { - return textureUnit; + /** + * Constructs a new string keyframe track. + * + * This keyframe track type has no `interpolation` parameter because the + * interpolation is always discrete. + * + * @param {string} name - The keyframe track's name. + * @param {Array} times - A list of keyframe times. + * @param {Array} values - A list of keyframe values. + */ + constructor( name, times, values ) { + + super( name, times, values ); } - function getTextureCacheKey( texture ) { +} - const array = []; +/** + * The value type name. + * + * @type {String} + * @default 'string' + */ +StringKeyframeTrack.prototype.ValueTypeName = 'string'; - array.push( texture.wrapS ); - array.push( texture.wrapT ); - array.push( texture.wrapR || 0 ); - array.push( texture.magFilter ); - array.push( texture.minFilter ); - array.push( texture.anisotropy ); - array.push( texture.internalFormat ); - array.push( texture.format ); - array.push( texture.type ); - array.push( texture.generateMipmaps ); - array.push( texture.premultiplyAlpha ); - array.push( texture.flipY ); - array.push( texture.unpackAlignment ); - array.push( texture.colorSpace ); +/** + * The value buffer type of this keyframe track. + * + * @type {TypedArray|Array} + * @default Array.constructor + */ +StringKeyframeTrack.prototype.ValueBufferType = Array; + +/** + * The default interpolation type of this keyframe track. + * + * @type {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth)} + * @default InterpolateDiscrete + */ +StringKeyframeTrack.prototype.DefaultInterpolation = InterpolateDiscrete; +StringKeyframeTrack.prototype.InterpolantFactoryMethodLinear = undefined; +StringKeyframeTrack.prototype.InterpolantFactoryMethodSmooth = undefined; + +/** + * A track for vector keyframe values. + * + * @augments KeyframeTrack + */ +class VectorKeyframeTrack extends KeyframeTrack { + + /** + * Constructs a new vector keyframe track. + * + * @param {string} name - The keyframe track's name. + * @param {Array} times - A list of keyframe times. + * @param {Array} values - A list of keyframe values. + * @param {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth)} [interpolation] - The interpolation type. + */ + constructor( name, times, values, interpolation ) { - return array.join(); + super( name, times, values, interpolation ); } - // - - function setTexture2D( texture, slot ) { - - const textureProperties = properties.get( texture ); +} - if ( texture.isVideoTexture ) updateVideoTexture( texture ); +/** + * The value type name. + * + * @type {String} + * @default 'vector' + */ +VectorKeyframeTrack.prototype.ValueTypeName = 'vector'; - if ( texture.isRenderTargetTexture === false && texture.version > 0 && textureProperties.__version !== texture.version ) { +/** + * A reusable set of keyframe tracks which represent an animation. + */ +class AnimationClip { - const image = texture.image; + /** + * Constructs a new animation clip. + * + * Note: Instead of instantiating an AnimationClip directly with the constructor, you can + * use the static interface of this class for creating clips. In most cases though, animation clips + * will automatically be created by loaders when importing animated 3D assets. + * + * @param {string} [name=''] - The clip's name. + * @param {number} [duration=-1] - The clip's duration in seconds. If a negative value is passed, + * the duration will be calculated from the passed keyframes. + * @param {Array} tracks - An array of keyframe tracks. + * @param {(NormalAnimationBlendMode|AdditiveAnimationBlendMode)} [blendMode=NormalAnimationBlendMode] - Defines how the animation + * is blended/combined when two or more animations are simultaneously played. + */ + constructor( name = '', duration = -1, tracks = [], blendMode = NormalAnimationBlendMode ) { - if ( image === null ) { + /** + * The clip's name. + * + * @type {string} + */ + this.name = name; - console.warn( 'THREE.WebGLRenderer: Texture marked for update but no image data found.' ); + /** + * An array of keyframe tracks. + * + * @type {Array} + */ + this.tracks = tracks; - } else if ( image.complete === false ) { + /** + * The clip's duration in seconds. + * + * @type {number} + */ + this.duration = duration; - console.warn( 'THREE.WebGLRenderer: Texture marked for update but image is incomplete' ); + /** + * Defines how the animation is blended/combined when two or more animations + * are simultaneously played. + * + * @type {(NormalAnimationBlendMode|AdditiveAnimationBlendMode)} + */ + this.blendMode = blendMode; - } else { + /** + * The UUID of the animation clip. + * + * @type {string} + * @readonly + */ + this.uuid = generateUUID(); - uploadTexture( textureProperties, texture, slot ); - return; + // this means it should figure out its duration by scanning the tracks + if ( this.duration < 0 ) { - } + this.resetDuration(); } - state.bindTexture( _gl.TEXTURE_2D, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); - } - function setTexture2DArray( texture, slot ) { + /** + * Factory method for creating an animation clip from the given JSON. + * + * @static + * @param {Object} json - The serialized animation clip. + * @return {AnimationClip} The new animation clip. + */ + static parse( json ) { - const textureProperties = properties.get( texture ); + const tracks = [], + jsonTracks = json.tracks, + frameTime = 1.0 / ( json.fps || 1.0 ); - if ( texture.version > 0 && textureProperties.__version !== texture.version ) { + for ( let i = 0, n = jsonTracks.length; i !== n; ++ i ) { - uploadTexture( textureProperties, texture, slot ); - return; + tracks.push( parseKeyframeTrack( jsonTracks[ i ] ).scale( frameTime ) ); } - state.bindTexture( _gl.TEXTURE_2D_ARRAY, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); + const clip = new this( json.name, json.duration, tracks, json.blendMode ); + clip.uuid = json.uuid; + + return clip; } - function setTexture3D( texture, slot ) { + /** + * Serializes the given animation clip into JSON. + * + * @static + * @param {AnimationClip} clip - The animation clip to serialize. + * @return {Object} The JSON object. + */ + static toJSON( clip ) { - const textureProperties = properties.get( texture ); + const tracks = [], + clipTracks = clip.tracks; - if ( texture.version > 0 && textureProperties.__version !== texture.version ) { + const json = { - uploadTexture( textureProperties, texture, slot ); - return; + 'name': clip.name, + 'duration': clip.duration, + 'tracks': tracks, + 'uuid': clip.uuid, + 'blendMode': clip.blendMode + + }; + + for ( let i = 0, n = clipTracks.length; i !== n; ++ i ) { + + tracks.push( KeyframeTrack.toJSON( clipTracks[ i ] ) ); } - state.bindTexture( _gl.TEXTURE_3D, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); + return json; } - function setTextureCube( texture, slot ) { + /** + * Returns a new animation clip from the passed morph targets array of a + * geometry, taking a name and the number of frames per second. + * + * Note: The fps parameter is required, but the animation speed can be + * overridden via {@link AnimationAction#setDuration}. + * + * @static + * @param {string} name - The name of the animation clip. + * @param {Array} morphTargetSequence - A sequence of morph targets. + * @param {number} fps - The Frames-Per-Second value. + * @param {boolean} noLoop - Whether the clip should be no loop or not. + * @return {AnimationClip} The new animation clip. + */ + static CreateFromMorphTargetSequence( name, morphTargetSequence, fps, noLoop ) { - const textureProperties = properties.get( texture ); + const numMorphTargets = morphTargetSequence.length; + const tracks = []; - if ( texture.version > 0 && textureProperties.__version !== texture.version ) { + for ( let i = 0; i < numMorphTargets; i ++ ) { - uploadCubeTexture( textureProperties, texture, slot ); - return; + let times = []; + let values = []; - } + times.push( + ( i + numMorphTargets - 1 ) % numMorphTargets, + i, + ( i + 1 ) % numMorphTargets ); - state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); + values.push( 0, 1, 0 ); - } + const order = getKeyframeOrder( times ); + times = sortedArray( times, 1, order ); + values = sortedArray( values, 1, order ); - const wrappingToGL = { - [ RepeatWrapping ]: _gl.REPEAT, - [ ClampToEdgeWrapping ]: _gl.CLAMP_TO_EDGE, - [ MirroredRepeatWrapping ]: _gl.MIRRORED_REPEAT - }; + // if there is a key at the first frame, duplicate it as the + // last frame as well for perfect loop. + if ( ! noLoop && times[ 0 ] === 0 ) { - const filterToGL = { - [ NearestFilter ]: _gl.NEAREST, - [ NearestMipmapNearestFilter ]: _gl.NEAREST_MIPMAP_NEAREST, - [ NearestMipmapLinearFilter ]: _gl.NEAREST_MIPMAP_LINEAR, + times.push( numMorphTargets ); + values.push( values[ 0 ] ); - [ LinearFilter ]: _gl.LINEAR, - [ LinearMipmapNearestFilter ]: _gl.LINEAR_MIPMAP_NEAREST, - [ LinearMipmapLinearFilter ]: _gl.LINEAR_MIPMAP_LINEAR - }; + } - const compareToGL = { - [ NeverCompare ]: _gl.NEVER, - [ AlwaysCompare ]: _gl.ALWAYS, - [ LessCompare ]: _gl.LESS, - [ LessEqualCompare ]: _gl.LEQUAL, - [ EqualCompare ]: _gl.EQUAL, - [ GreaterEqualCompare ]: _gl.GEQUAL, - [ GreaterCompare ]: _gl.GREATER, - [ NotEqualCompare ]: _gl.NOTEQUAL - }; + tracks.push( + new NumberKeyframeTrack( + '.morphTargetInfluences[' + morphTargetSequence[ i ].name + ']', + times, values + ).scale( 1.0 / fps ) ); - function setTextureParameters( textureType, texture, supportsMips ) { + } - if ( supportsMips ) { + return new this( name, -1, tracks ); - _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_S, wrappingToGL[ texture.wrapS ] ); - _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_T, wrappingToGL[ texture.wrapT ] ); + } - if ( textureType === _gl.TEXTURE_3D || textureType === _gl.TEXTURE_2D_ARRAY ) { + /** + * Searches for an animation clip by name, taking as its first parameter + * either an array of clips, or a mesh or geometry that contains an + * array named "animations" property. + * + * @static + * @param {(Array|Object3D)} objectOrClipArray - The array or object to search through. + * @param {string} name - The name to search for. + * @return {?AnimationClip} The found animation clip. Returns `null` if no clip has been found. + */ + static findByName( objectOrClipArray, name ) { - _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_R, wrappingToGL[ texture.wrapR ] ); + let clipArray = objectOrClipArray; - } + if ( ! Array.isArray( objectOrClipArray ) ) { - _gl.texParameteri( textureType, _gl.TEXTURE_MAG_FILTER, filterToGL[ texture.magFilter ] ); - _gl.texParameteri( textureType, _gl.TEXTURE_MIN_FILTER, filterToGL[ texture.minFilter ] ); + const o = objectOrClipArray; + clipArray = o.geometry && o.geometry.animations || o.animations; - } else { + } - _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_S, _gl.CLAMP_TO_EDGE ); - _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_T, _gl.CLAMP_TO_EDGE ); + for ( let i = 0; i < clipArray.length; i ++ ) { - if ( textureType === _gl.TEXTURE_3D || textureType === _gl.TEXTURE_2D_ARRAY ) { + if ( clipArray[ i ].name === name ) { - _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_R, _gl.CLAMP_TO_EDGE ); + return clipArray[ i ]; } - if ( texture.wrapS !== ClampToEdgeWrapping || texture.wrapT !== ClampToEdgeWrapping ) { - - console.warn( 'THREE.WebGLRenderer: Texture is not power of two. Texture.wrapS and Texture.wrapT should be set to THREE.ClampToEdgeWrapping.' ); + } - } + return null; - _gl.texParameteri( textureType, _gl.TEXTURE_MAG_FILTER, filterFallback( texture.magFilter ) ); - _gl.texParameteri( textureType, _gl.TEXTURE_MIN_FILTER, filterFallback( texture.minFilter ) ); + } - if ( texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter ) { + /** + * Returns an array of new AnimationClips created from the morph target + * sequences of a geometry, trying to sort morph target names into + * animation-group-based patterns like "Walk_001, Walk_002, Run_001, Run_002...". + * + * See {@link MD2Loader#parse} as an example for how the method should be used. + * + * @static + * @param {Array} morphTargets - A sequence of morph targets. + * @param {number} fps - The Frames-Per-Second value. + * @param {boolean} noLoop - Whether the clip should be no loop or not. + * @return {Array} An array of new animation clips. + */ + static CreateClipsFromMorphTargetSequences( morphTargets, fps, noLoop ) { - console.warn( 'THREE.WebGLRenderer: Texture is not power of two. Texture.minFilter should be set to THREE.NearestFilter or THREE.LinearFilter.' ); + const animationToMorphTargets = {}; - } + // tested with https://regex101.com/ on trick sequences + // such flamingo_flyA_003, flamingo_run1_003, crdeath0059 + const pattern = /^([\w-]*?)([\d]+)$/; - } + // sort morph target names into animation groups based + // patterns like Walk_001, Walk_002, Run_001, Run_002 + for ( let i = 0, il = morphTargets.length; i < il; i ++ ) { - if ( texture.compareFunction ) { + const morphTarget = morphTargets[ i ]; + const parts = morphTarget.name.match( pattern ); - _gl.texParameteri( textureType, _gl.TEXTURE_COMPARE_MODE, _gl.COMPARE_REF_TO_TEXTURE ); - _gl.texParameteri( textureType, _gl.TEXTURE_COMPARE_FUNC, compareToGL[ texture.compareFunction ] ); + if ( parts && parts.length > 1 ) { - } + const name = parts[ 1 ]; - if ( extensions.has( 'EXT_texture_filter_anisotropic' ) === true ) { + let animationMorphTargets = animationToMorphTargets[ name ]; - const extension = extensions.get( 'EXT_texture_filter_anisotropic' ); + if ( ! animationMorphTargets ) { - if ( texture.magFilter === NearestFilter ) return; - if ( texture.minFilter !== NearestMipmapLinearFilter && texture.minFilter !== LinearMipmapLinearFilter ) return; - if ( texture.type === FloatType && extensions.has( 'OES_texture_float_linear' ) === false ) return; // verify extension for WebGL 1 and WebGL 2 - if ( isWebGL2 === false && ( texture.type === HalfFloatType && extensions.has( 'OES_texture_half_float_linear' ) === false ) ) return; // verify extension for WebGL 1 only + animationToMorphTargets[ name ] = animationMorphTargets = []; - if ( texture.anisotropy > 1 || properties.get( texture ).__currentAnisotropy ) { + } - _gl.texParameterf( textureType, extension.TEXTURE_MAX_ANISOTROPY_EXT, Math.min( texture.anisotropy, capabilities.getMaxAnisotropy() ) ); - properties.get( texture ).__currentAnisotropy = texture.anisotropy; + animationMorphTargets.push( morphTarget ); } } - } - - function initTexture( textureProperties, texture ) { + const clips = []; - let forceUpload = false; + for ( const name in animationToMorphTargets ) { - if ( textureProperties.__webglInit === undefined ) { + clips.push( this.CreateFromMorphTargetSequence( name, animationToMorphTargets[ name ], fps, noLoop ) ); - textureProperties.__webglInit = true; + } - texture.addEventListener( 'dispose', onTextureDispose ); + return clips; - } + } - // create Source <-> WebGLTextures mapping if necessary + /** + * Parses the `animation.hierarchy` format and returns a new animation clip. + * + * @static + * @deprecated since r175. + * @param {Object} animation - A serialized animation clip as JSON. + * @param {Array} bones - An array of bones. + * @return {?AnimationClip} The new animation clip. + */ + static parseAnimation( animation, bones ) { - const source = texture.source; - let webglTextures = _sources.get( source ); + console.warn( 'THREE.AnimationClip: parseAnimation() is deprecated and will be removed with r185' ); - if ( webglTextures === undefined ) { + if ( ! animation ) { - webglTextures = {}; - _sources.set( source, webglTextures ); + console.error( 'THREE.AnimationClip: No animation in JSONLoader data.' ); + return null; } - // check if there is already a WebGLTexture object for the given texture parameters - - const textureCacheKey = getTextureCacheKey( texture ); + const addNonemptyTrack = function ( trackType, trackName, animationKeys, propertyName, destTracks ) { - if ( textureCacheKey !== textureProperties.__cacheKey ) { + // only return track if there are actually keys. + if ( animationKeys.length !== 0 ) { - // if not, create a new instance of WebGLTexture + const times = []; + const values = []; - if ( webglTextures[ textureCacheKey ] === undefined ) { + flattenJSON( animationKeys, times, values, propertyName ); - // create new entry + // empty keys are filtered out, so check again + if ( times.length !== 0 ) { - webglTextures[ textureCacheKey ] = { - texture: _gl.createTexture(), - usedTimes: 0 - }; + destTracks.push( new trackType( trackName, times, values ) ); - info.memory.textures ++; + } - // when a new instance of WebGLTexture was created, a texture upload is required - // even if the image contents are identical + } - forceUpload = true; + }; - } + const tracks = []; - webglTextures[ textureCacheKey ].usedTimes ++; + const clipName = animation.name || 'default'; + const fps = animation.fps || 30; + const blendMode = animation.blendMode; - // every time the texture cache key changes, it's necessary to check if an instance of - // WebGLTexture can be deleted in order to avoid a memory leak. + // automatic length determination in AnimationClip. + let duration = animation.length || -1; - const webglTexture = webglTextures[ textureProperties.__cacheKey ]; + const hierarchyTracks = animation.hierarchy || []; - if ( webglTexture !== undefined ) { + for ( let h = 0; h < hierarchyTracks.length; h ++ ) { - webglTextures[ textureProperties.__cacheKey ].usedTimes --; + const animationKeys = hierarchyTracks[ h ].keys; - if ( webglTexture.usedTimes === 0 ) { + // skip empty tracks + if ( ! animationKeys || animationKeys.length === 0 ) continue; - deleteTexture( texture ); + // process morph targets + if ( animationKeys[ 0 ].morphTargets ) { - } + // figure out all morph targets used in this track + const morphTargetNames = {}; - } + let k; - // store references to cache key and WebGLTexture object + for ( k = 0; k < animationKeys.length; k ++ ) { - textureProperties.__cacheKey = textureCacheKey; - textureProperties.__webglTexture = webglTextures[ textureCacheKey ].texture; + if ( animationKeys[ k ].morphTargets ) { - } + for ( let m = 0; m < animationKeys[ k ].morphTargets.length; m ++ ) { - return forceUpload; + morphTargetNames[ animationKeys[ k ].morphTargets[ m ] ] = -1; - } + } - function uploadTexture( textureProperties, texture, slot ) { + } - let textureType = _gl.TEXTURE_2D; + } - if ( texture.isDataArrayTexture || texture.isCompressedArrayTexture ) textureType = _gl.TEXTURE_2D_ARRAY; - if ( texture.isData3DTexture ) textureType = _gl.TEXTURE_3D; + // create a track for each morph target with all zero + // morphTargetInfluences except for the keys in which + // the morphTarget is named. + for ( const morphTargetName in morphTargetNames ) { - const forceUpload = initTexture( textureProperties, texture ); - const source = texture.source; + const times = []; + const values = []; - state.bindTexture( textureType, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); + for ( let m = 0; m !== animationKeys[ k ].morphTargets.length; ++ m ) { - const sourceProperties = properties.get( source ); + const animationKey = animationKeys[ k ]; - if ( source.version !== sourceProperties.__version || forceUpload === true ) { + times.push( animationKey.time ); + values.push( ( animationKey.morphTarget === morphTargetName ) ? 1 : 0 ); - state.activeTexture( _gl.TEXTURE0 + slot ); + } - _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY ); - _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha ); - _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, texture.unpackAlignment ); - _gl.pixelStorei( _gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, _gl.NONE ); + tracks.push( new NumberKeyframeTrack( '.morphTargetInfluence[' + morphTargetName + ']', times, values ) ); - const needsPowerOfTwo = textureNeedsPowerOfTwo( texture ) && isPowerOfTwo$1( texture.image ) === false; - let image = resizeImage( texture.image, needsPowerOfTwo, false, maxTextureSize ); - image = verifyColorSpace( texture, image ); + } - const supportsMips = isPowerOfTwo$1( image ) || isWebGL2, - glFormat = utils.convert( texture.format, texture.colorSpace ); + duration = morphTargetNames.length * fps; - let glType = utils.convert( texture.type ), - glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace ); + } else { - setTextureParameters( textureType, texture, supportsMips ); + // ...assume skeletal animation - let mipmap; - const mipmaps = texture.mipmaps; + const boneName = '.bones[' + bones[ h ].name + ']'; - const useTexStorage = ( isWebGL2 && texture.isVideoTexture !== true ); - const allocateMemory = ( sourceProperties.__version === undefined ) || ( forceUpload === true ); - const levels = getMipLevels( texture, image, supportsMips ); + addNonemptyTrack( + VectorKeyframeTrack, boneName + '.position', + animationKeys, 'pos', tracks ); - if ( texture.isDepthTexture ) { + addNonemptyTrack( + QuaternionKeyframeTrack, boneName + '.quaternion', + animationKeys, 'rot', tracks ); - // populate depth texture with dummy data + addNonemptyTrack( + VectorKeyframeTrack, boneName + '.scale', + animationKeys, 'scl', tracks ); - glInternalFormat = _gl.DEPTH_COMPONENT; + } - if ( isWebGL2 ) { + } - if ( texture.type === FloatType ) { + if ( tracks.length === 0 ) { - glInternalFormat = _gl.DEPTH_COMPONENT32F; + return null; - } else if ( texture.type === UnsignedIntType ) { + } - glInternalFormat = _gl.DEPTH_COMPONENT24; + const clip = new this( clipName, duration, tracks, blendMode ); - } else if ( texture.type === UnsignedInt248Type ) { + return clip; - glInternalFormat = _gl.DEPTH24_STENCIL8; + } - } else { + /** + * Sets the duration of this clip to the duration of its longest keyframe track. + * + * @return {AnimationClip} A reference to this animation clip. + */ + resetDuration() { - glInternalFormat = _gl.DEPTH_COMPONENT16; // WebGL2 requires sized internalformat for glTexImage2D + const tracks = this.tracks; + let duration = 0; - } + for ( let i = 0, n = tracks.length; i !== n; ++ i ) { - } else { + const track = this.tracks[ i ]; - if ( texture.type === FloatType ) { + duration = Math.max( duration, track.times[ track.times.length - 1 ] ); - console.error( 'WebGLRenderer: Floating point depth texture requires WebGL2.' ); + } - } + this.duration = duration; - } + return this; - // validation checks for WebGL 1 + } - if ( texture.format === DepthFormat && glInternalFormat === _gl.DEPTH_COMPONENT ) { + /** + * Trims all tracks to the clip's duration. + * + * @return {AnimationClip} A reference to this animation clip. + */ + trim() { - // The error INVALID_OPERATION is generated by texImage2D if format and internalformat are - // DEPTH_COMPONENT and type is not UNSIGNED_SHORT or UNSIGNED_INT - // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/) - if ( texture.type !== UnsignedShortType && texture.type !== UnsignedIntType ) { + for ( let i = 0; i < this.tracks.length; i ++ ) { - console.warn( 'THREE.WebGLRenderer: Use UnsignedShortType or UnsignedIntType for DepthFormat DepthTexture.' ); + this.tracks[ i ].trim( 0, this.duration ); - texture.type = UnsignedIntType; - glType = utils.convert( texture.type ); + } - } + return this; - } + } - if ( texture.format === DepthStencilFormat && glInternalFormat === _gl.DEPTH_COMPONENT ) { + /** + * Performs minimal validation on each track in the clip. Returns `true` if all + * tracks are valid. + * + * @return {boolean} Whether the clip's keyframes are valid or not. + */ + validate() { - // Depth stencil textures need the DEPTH_STENCIL internal format - // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/) - glInternalFormat = _gl.DEPTH_STENCIL; + let valid = true; - // The error INVALID_OPERATION is generated by texImage2D if format and internalformat are - // DEPTH_STENCIL and type is not UNSIGNED_INT_24_8_WEBGL. - // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/) - if ( texture.type !== UnsignedInt248Type ) { + for ( let i = 0; i < this.tracks.length; i ++ ) { - console.warn( 'THREE.WebGLRenderer: Use UnsignedInt248Type for DepthStencilFormat DepthTexture.' ); + valid = valid && this.tracks[ i ].validate(); - texture.type = UnsignedInt248Type; - glType = utils.convert( texture.type ); + } - } + return valid; - } + } - // + /** + * Optimizes each track by removing equivalent sequential keys (which are + * common in morph target sequences). + * + * @return {AnimationClip} A reference to this animation clip. + */ + optimize() { - if ( allocateMemory ) { + for ( let i = 0; i < this.tracks.length; i ++ ) { - if ( useTexStorage ) { + this.tracks[ i ].optimize(); - state.texStorage2D( _gl.TEXTURE_2D, 1, glInternalFormat, image.width, image.height ); + } - } else { + return this; - state.texImage2D( _gl.TEXTURE_2D, 0, glInternalFormat, image.width, image.height, 0, glFormat, glType, null ); + } - } + /** + * Returns a new animation clip with copied values from this instance. + * + * @return {AnimationClip} A clone of this instance. + */ + clone() { - } + const tracks = []; - } else if ( texture.isDataTexture ) { + for ( let i = 0; i < this.tracks.length; i ++ ) { - // use manually created mipmaps if available - // if there are no manual mipmaps - // set 0 level mipmap and then use GL to generate other mipmap levels + tracks.push( this.tracks[ i ].clone() ); - if ( mipmaps.length > 0 && supportsMips ) { + } - if ( useTexStorage && allocateMemory ) { + return new this.constructor( this.name, this.duration, tracks, this.blendMode ); - state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, mipmaps[ 0 ].width, mipmaps[ 0 ].height ); + } - } + /** + * Serializes this animation clip into JSON. + * + * @return {Object} The JSON object. + */ + toJSON() { - for ( let i = 0, il = mipmaps.length; i < il; i ++ ) { + return this.constructor.toJSON( this ); - mipmap = mipmaps[ i ]; + } - if ( useTexStorage ) { +} - state.texSubImage2D( _gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, glType, mipmap.data ); +function getTrackTypeForValueTypeName( typeName ) { - } else { + switch ( typeName.toLowerCase() ) { - state.texImage2D( _gl.TEXTURE_2D, i, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); + case 'scalar': + case 'double': + case 'float': + case 'number': + case 'integer': - } + return NumberKeyframeTrack; - } + case 'vector': + case 'vector2': + case 'vector3': + case 'vector4': - texture.generateMipmaps = false; + return VectorKeyframeTrack; - } else { + case 'color': - if ( useTexStorage ) { + return ColorKeyframeTrack; - if ( allocateMemory ) { + case 'quaternion': - state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, image.width, image.height ); + return QuaternionKeyframeTrack; - } + case 'bool': + case 'boolean': - state.texSubImage2D( _gl.TEXTURE_2D, 0, 0, 0, image.width, image.height, glFormat, glType, image.data ); + return BooleanKeyframeTrack; - } else { + case 'string': - state.texImage2D( _gl.TEXTURE_2D, 0, glInternalFormat, image.width, image.height, 0, glFormat, glType, image.data ); + return StringKeyframeTrack; - } + } - } + throw new Error( 'THREE.KeyframeTrack: Unsupported typeName: ' + typeName ); - } else if ( texture.isCompressedTexture ) { +} - if ( texture.isCompressedArrayTexture ) { +function parseKeyframeTrack( json ) { - if ( useTexStorage && allocateMemory ) { + if ( json.type === undefined ) { - state.texStorage3D( _gl.TEXTURE_2D_ARRAY, levels, glInternalFormat, mipmaps[ 0 ].width, mipmaps[ 0 ].height, image.depth ); + throw new Error( 'THREE.KeyframeTrack: track type undefined, can not parse' ); - } + } - for ( let i = 0, il = mipmaps.length; i < il; i ++ ) { + const trackType = getTrackTypeForValueTypeName( json.type ); - mipmap = mipmaps[ i ]; + if ( json.times === undefined ) { - if ( texture.format !== RGBAFormat ) { + const times = [], values = []; - if ( glFormat !== null ) { + flattenJSON( json.keys, times, values, 'value' ); - if ( useTexStorage ) { + json.times = times; + json.values = values; - state.compressedTexSubImage3D( _gl.TEXTURE_2D_ARRAY, i, 0, 0, 0, mipmap.width, mipmap.height, image.depth, glFormat, mipmap.data, 0, 0 ); + } - } else { + // derived classes can define a static parse method + if ( trackType.parse !== undefined ) { - state.compressedTexImage3D( _gl.TEXTURE_2D_ARRAY, i, glInternalFormat, mipmap.width, mipmap.height, image.depth, 0, mipmap.data, 0, 0 ); + return trackType.parse( json ); - } + } else { - } else { + // by default, we assume a constructor compatible with the base + return new trackType( json.name, json.times, json.values, json.interpolation ); - console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()' ); + } - } +} - } else { +/** + * @class + * @classdesc A simple caching system, used internally by {@link FileLoader}. + * To enable caching across all loaders that use {@link FileLoader}, add `THREE.Cache.enabled = true.` once in your app. + * @hideconstructor + */ +const Cache = { - if ( useTexStorage ) { + /** + * Whether caching is enabled or not. + * + * @static + * @type {boolean} + * @default false + */ + enabled: false, - state.texSubImage3D( _gl.TEXTURE_2D_ARRAY, i, 0, 0, 0, mipmap.width, mipmap.height, image.depth, glFormat, glType, mipmap.data ); + /** + * A dictionary that holds cached files. + * + * @static + * @type {Object} + */ + files: {}, - } else { + /** + * Adds a cache entry with a key to reference the file. If this key already + * holds a file, it is overwritten. + * + * @static + * @param {string} key - The key to reference the cached file. + * @param {Object} file - The file to be cached. + */ + add: function ( key, file ) { - state.texImage3D( _gl.TEXTURE_2D_ARRAY, i, glInternalFormat, mipmap.width, mipmap.height, image.depth, 0, glFormat, glType, mipmap.data ); + if ( this.enabled === false ) return; - } + // console.log( 'THREE.Cache', 'Adding key:', key ); - } + this.files[ key ] = file; - } + }, - } else { + /** + * Gets the cached value for the given key. + * + * @static + * @param {string} key - The key to reference the cached file. + * @return {Object|undefined} The cached file. If the key does not exist `undefined` is returned. + */ + get: function ( key ) { - if ( useTexStorage && allocateMemory ) { + if ( this.enabled === false ) return; - state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, mipmaps[ 0 ].width, mipmaps[ 0 ].height ); + // console.log( 'THREE.Cache', 'Checking key:', key ); - } + return this.files[ key ]; - for ( let i = 0, il = mipmaps.length; i < il; i ++ ) { + }, - mipmap = mipmaps[ i ]; + /** + * Removes the cached file associated with the given key. + * + * @static + * @param {string} key - The key to reference the cached file. + */ + remove: function ( key ) { - if ( texture.format !== RGBAFormat ) { + delete this.files[ key ]; - if ( glFormat !== null ) { + }, - if ( useTexStorage ) { + /** + * Remove all values from the cache. + * + * @static + */ + clear: function () { - state.compressedTexSubImage2D( _gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, mipmap.data ); + this.files = {}; - } else { + } - state.compressedTexImage2D( _gl.TEXTURE_2D, i, glInternalFormat, mipmap.width, mipmap.height, 0, mipmap.data ); +}; - } +/** + * Handles and keeps track of loaded and pending data. A default global + * instance of this class is created and used by loaders if not supplied + * manually. + * + * In general that should be sufficient, however there are times when it can + * be useful to have separate loaders - for example if you want to show + * separate loading bars for objects and textures. + * + * ```js + * const manager = new THREE.LoadingManager(); + * manager.onLoad = () => console.log( 'Loading complete!' ); + * + * const loader1 = new OBJLoader( manager ); + * const loader2 = new ColladaLoader( manager ); + * ``` + */ +class LoadingManager { - } else { + /** + * Constructs a new loading manager. + * + * @param {Function} [onLoad] - Executes when all items have been loaded. + * @param {Function} [onProgress] - Executes when single items have been loaded. + * @param {Function} [onError] - Executes when an error occurs. + */ + constructor( onLoad, onProgress, onError ) { - console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()' ); + const scope = this; - } + let isLoading = false; + let itemsLoaded = 0; + let itemsTotal = 0; + let urlModifier = undefined; + const handlers = []; - } else { + // Refer to #5689 for the reason why we don't set .onStart + // in the constructor - if ( useTexStorage ) { + /** + * Executes when an item starts loading. + * + * @type {Function|undefined} + * @default undefined + */ + this.onStart = undefined; - state.texSubImage2D( _gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, glType, mipmap.data ); + /** + * Executes when all items have been loaded. + * + * @type {Function|undefined} + * @default undefined + */ + this.onLoad = onLoad; - } else { + /** + * Executes when single items have been loaded. + * + * @type {Function|undefined} + * @default undefined + */ + this.onProgress = onProgress; - state.texImage2D( _gl.TEXTURE_2D, i, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); + /** + * Executes when an error occurs. + * + * @type {Function|undefined} + * @default undefined + */ + this.onError = onError; - } + /** + * This should be called by any loader using the manager when the loader + * starts loading an item. + * + * @param {string} url - The URL to load. + */ + this.itemStart = function ( url ) { - } + itemsTotal ++; - } + if ( isLoading === false ) { - } + if ( scope.onStart !== undefined ) { - } else if ( texture.isDataArrayTexture ) { + scope.onStart( url, itemsLoaded, itemsTotal ); - if ( useTexStorage ) { + } - if ( allocateMemory ) { + } - state.texStorage3D( _gl.TEXTURE_2D_ARRAY, levels, glInternalFormat, image.width, image.height, image.depth ); + isLoading = true; - } + }; - state.texSubImage3D( _gl.TEXTURE_2D_ARRAY, 0, 0, 0, 0, image.width, image.height, image.depth, glFormat, glType, image.data ); + /** + * This should be called by any loader using the manager when the loader + * ended loading an item. + * + * @param {string} url - The URL of the loaded item. + */ + this.itemEnd = function ( url ) { - } else { + itemsLoaded ++; - state.texImage3D( _gl.TEXTURE_2D_ARRAY, 0, glInternalFormat, image.width, image.height, image.depth, 0, glFormat, glType, image.data ); + if ( scope.onProgress !== undefined ) { - } + scope.onProgress( url, itemsLoaded, itemsTotal ); - } else if ( texture.isData3DTexture ) { + } - if ( useTexStorage ) { + if ( itemsLoaded === itemsTotal ) { - if ( allocateMemory ) { + isLoading = false; - state.texStorage3D( _gl.TEXTURE_3D, levels, glInternalFormat, image.width, image.height, image.depth ); + if ( scope.onLoad !== undefined ) { - } + scope.onLoad(); - state.texSubImage3D( _gl.TEXTURE_3D, 0, 0, 0, 0, image.width, image.height, image.depth, glFormat, glType, image.data ); + } - } else { + } - state.texImage3D( _gl.TEXTURE_3D, 0, glInternalFormat, image.width, image.height, image.depth, 0, glFormat, glType, image.data ); + }; - } + /** + * This should be called by any loader using the manager when the loader + * encounters an error when loading an item. + * + * @param {string} url - The URL of the item that produces an error. + */ + this.itemError = function ( url ) { - } else if ( texture.isFramebufferTexture ) { + if ( scope.onError !== undefined ) { - if ( allocateMemory ) { + scope.onError( url ); - if ( useTexStorage ) { + } - state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, image.width, image.height ); + }; - } else { + /** + * Given a URL, uses the URL modifier callback (if any) and returns a + * resolved URL. If no URL modifier is set, returns the original URL. + * + * @param {string} url - The URL to load. + * @return {string} The resolved URL. + */ + this.resolveURL = function ( url ) { - let width = image.width, height = image.height; + if ( urlModifier ) { - for ( let i = 0; i < levels; i ++ ) { + return urlModifier( url ); - state.texImage2D( _gl.TEXTURE_2D, i, glInternalFormat, width, height, 0, glFormat, glType, null ); + } - width >>= 1; - height >>= 1; + return url; - } + }; - } + /** + * If provided, the callback will be passed each resource URL before a + * request is sent. The callback may return the original URL, or a new URL to + * override loading behavior. This behavior can be used to load assets from + * .ZIP files, drag-and-drop APIs, and Data URIs. + * + * ```js + * const blobs = {'fish.gltf': blob1, 'diffuse.png': blob2, 'normal.png': blob3}; + * + * const manager = new THREE.LoadingManager(); + * + * // Initialize loading manager with URL callback. + * const objectURLs = []; + * manager.setURLModifier( ( url ) => { + * + * url = URL.createObjectURL( blobs[ url ] ); + * objectURLs.push( url ); + * return url; + * + * } ); + * + * // Load as usual, then revoke the blob URLs. + * const loader = new GLTFLoader( manager ); + * loader.load( 'fish.gltf', (gltf) => { + * + * scene.add( gltf.scene ); + * objectURLs.forEach( ( url ) => URL.revokeObjectURL( url ) ); + * + * } ); + * ``` + * + * @param {function(string):string} transform - URL modifier callback. Called with an URL and must return a resolved URL. + * @return {LoadingManager} A reference to this loading manager. + */ + this.setURLModifier = function ( transform ) { - } + urlModifier = transform; - } else { + return this; - // regular Texture (image, video, canvas) + }; - // use manually created mipmaps if available - // if there are no manual mipmaps - // set 0 level mipmap and then use GL to generate other mipmap levels + /** + * Registers a loader with the given regular expression. Can be used to + * define what loader should be used in order to load specific files. A + * typical use case is to overwrite the default loader for textures. + * + * ```js + * // add handler for TGA textures + * manager.addHandler( /\.tga$/i, new TGALoader() ); + * ``` + * + * @param {string} regex - A regular expression. + * @param {Loader} loader - A loader that should handle matched cases. + * @return {LoadingManager} A reference to this loading manager. + */ + this.addHandler = function ( regex, loader ) { - if ( mipmaps.length > 0 && supportsMips ) { + handlers.push( regex, loader ); - if ( useTexStorage && allocateMemory ) { + return this; - state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, mipmaps[ 0 ].width, mipmaps[ 0 ].height ); + }; - } + /** + * Removes the loader for the given regular expression. + * + * @param {string} regex - A regular expression. + * @return {LoadingManager} A reference to this loading manager. + */ + this.removeHandler = function ( regex ) { - for ( let i = 0, il = mipmaps.length; i < il; i ++ ) { + const index = handlers.indexOf( regex ); - mipmap = mipmaps[ i ]; + if ( index !== -1 ) { - if ( useTexStorage ) { + handlers.splice( index, 2 ); - state.texSubImage2D( _gl.TEXTURE_2D, i, 0, 0, glFormat, glType, mipmap ); + } - } else { + return this; - state.texImage2D( _gl.TEXTURE_2D, i, glInternalFormat, glFormat, glType, mipmap ); + }; - } + /** + * Can be used to retrieve the registered loader for the given file path. + * + * @param {string} file - The file path. + * @return {?Loader} The registered loader. Returns `null` if no loader was found. + */ + this.getHandler = function ( file ) { - } + for ( let i = 0, l = handlers.length; i < l; i += 2 ) { - texture.generateMipmaps = false; + const regex = handlers[ i ]; + const loader = handlers[ i + 1 ]; - } else { + if ( regex.global ) regex.lastIndex = 0; // see #17920 - if ( useTexStorage ) { + if ( regex.test( file ) ) { - if ( allocateMemory ) { + return loader; - state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, image.width, image.height ); + } - } + } - state.texSubImage2D( _gl.TEXTURE_2D, 0, 0, 0, glFormat, glType, image ); + return null; - } else { + }; - state.texImage2D( _gl.TEXTURE_2D, 0, glInternalFormat, glFormat, glType, image ); + } - } +} - } +/** + * The global default loading manager. + * + * @constant + * @type {LoadingManager} + */ +const DefaultLoadingManager = /*@__PURE__*/ new LoadingManager(); - } +/** + * Abstract base class for loaders. + * + * @abstract + */ +class Loader { - if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) { + /** + * Constructs a new loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ + constructor( manager ) { - generateMipmap( textureType ); + /** + * The loading manager. + * + * @type {LoadingManager} + * @default DefaultLoadingManager + */ + this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager; - } + /** + * The crossOrigin string to implement CORS for loading the url from a + * different domain that allows CORS. + * + * @type {string} + * @default 'anonymous' + */ + this.crossOrigin = 'anonymous'; - sourceProperties.__version = source.version; + /** + * Whether the XMLHttpRequest uses credentials. + * + * @type {boolean} + * @default false + */ + this.withCredentials = false; - if ( texture.onUpdate ) texture.onUpdate( texture ); + /** + * The base path from which the asset will be loaded. + * + * @type {string} + */ + this.path = ''; - } + /** + * The base path from which additional resources like textures will be loaded. + * + * @type {string} + */ + this.resourcePath = ''; - textureProperties.__version = texture.version; + /** + * The [request header]{@link https://developer.mozilla.org/en-US/docs/Glossary/Request_header} + * used in HTTP request. + * + * @type {Object} + */ + this.requestHeader = {}; } - function uploadCubeTexture( textureProperties, texture, slot ) { + /** + * This method needs to be implemented by all concrete loaders. It holds the + * logic for loading assets from the backend. + * + * @param {string} url - The path/URL of the file to be loaded. + * @param {Function} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} [onProgress] - Executed while the loading is in progress. + * @param {onErrorCallback} [onError] - Executed when errors occur. + */ + load( /* url, onLoad, onProgress, onError */ ) {} - if ( texture.image.length !== 6 ) return; + /** + * A async version of {@link Loader#load}. + * + * @param {string} url - The path/URL of the file to be loaded. + * @param {onProgressCallback} [onProgress] - Executed while the loading is in progress. + * @return {Promise} A Promise that resolves when the asset has been loaded. + */ + loadAsync( url, onProgress ) { - const forceUpload = initTexture( textureProperties, texture ); - const source = texture.source; + const scope = this; - state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); + return new Promise( function ( resolve, reject ) { - const sourceProperties = properties.get( source ); + scope.load( url, resolve, onProgress, reject ); - if ( source.version !== sourceProperties.__version || forceUpload === true ) { + } ); - state.activeTexture( _gl.TEXTURE0 + slot ); + } - _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY ); - _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha ); - _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, texture.unpackAlignment ); - _gl.pixelStorei( _gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, _gl.NONE ); + /** + * This method needs to be implemented by all concrete loaders. It holds the + * logic for parsing the asset into three.js entities. + * + * @param {any} data - The data to parse. + */ + parse( /* data */ ) {} - const isCompressed = ( texture.isCompressedTexture || texture.image[ 0 ].isCompressedTexture ); - const isDataTexture = ( texture.image[ 0 ] && texture.image[ 0 ].isDataTexture ); + /** + * Sets the `crossOrigin` String to implement CORS for loading the URL + * from a different domain that allows CORS. + * + * @param {string} crossOrigin - The `crossOrigin` value. + * @return {Loader} A reference to this instance. + */ + setCrossOrigin( crossOrigin ) { - const cubeImage = []; + this.crossOrigin = crossOrigin; + return this; - for ( let i = 0; i < 6; i ++ ) { + } - if ( ! isCompressed && ! isDataTexture ) { + /** + * Whether the XMLHttpRequest uses credentials such as cookies, authorization + * headers or TLS client certificates, see [XMLHttpRequest.withCredentials]{@link https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials}. + * + * Note: This setting has no effect if you are loading files locally or from the same domain. + * + * @param {boolean} value - The `withCredentials` value. + * @return {Loader} A reference to this instance. + */ + setWithCredentials( value ) { - cubeImage[ i ] = resizeImage( texture.image[ i ], false, true, maxCubemapSize ); + this.withCredentials = value; + return this; - } else { + } - cubeImage[ i ] = isDataTexture ? texture.image[ i ].image : texture.image[ i ]; + /** + * Sets the base path for the asset. + * + * @param {string} path - The base path. + * @return {Loader} A reference to this instance. + */ + setPath( path ) { - } + this.path = path; + return this; - cubeImage[ i ] = verifyColorSpace( texture, cubeImage[ i ] ); + } - } + /** + * Sets the base path for dependent resources like textures. + * + * @param {string} resourcePath - The resource path. + * @return {Loader} A reference to this instance. + */ + setResourcePath( resourcePath ) { - const image = cubeImage[ 0 ], - supportsMips = isPowerOfTwo$1( image ) || isWebGL2, - glFormat = utils.convert( texture.format, texture.colorSpace ), - glType = utils.convert( texture.type ), - glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace ); + this.resourcePath = resourcePath; + return this; - const useTexStorage = ( isWebGL2 && texture.isVideoTexture !== true ); - const allocateMemory = ( sourceProperties.__version === undefined ) || ( forceUpload === true ); - let levels = getMipLevels( texture, image, supportsMips ); + } - setTextureParameters( _gl.TEXTURE_CUBE_MAP, texture, supportsMips ); + /** + * Sets the given request header. + * + * @param {Object} requestHeader - A [request header]{@link https://developer.mozilla.org/en-US/docs/Glossary/Request_header} + * for configuring the HTTP request. + * @return {Loader} A reference to this instance. + */ + setRequestHeader( requestHeader ) { - let mipmaps; + this.requestHeader = requestHeader; + return this; - if ( isCompressed ) { + } - if ( useTexStorage && allocateMemory ) { +} - state.texStorage2D( _gl.TEXTURE_CUBE_MAP, levels, glInternalFormat, image.width, image.height ); +/** + * Callback for onProgress in loaders. + * + * @callback onProgressCallback + * @param {ProgressEvent} event - An instance of `ProgressEvent` that represents the current loading status. + */ - } +/** + * Callback for onError in loaders. + * + * @callback onErrorCallback + * @param {Error} error - The error which occurred during the loading process. + */ - for ( let i = 0; i < 6; i ++ ) { +/** + * The default material name that is used by loaders + * when creating materials for loaded 3D objects. + * + * Note: Not all loaders might honor this setting. + * + * @static + * @type {string} + * @default '__DEFAULT' + */ +Loader.DEFAULT_MATERIAL_NAME = '__DEFAULT'; - mipmaps = cubeImage[ i ].mipmaps; +const loading = {}; - for ( let j = 0; j < mipmaps.length; j ++ ) { +class HttpError extends Error { - const mipmap = mipmaps[ j ]; + constructor( message, response ) { - if ( texture.format !== RGBAFormat ) { + super( message ); + this.response = response; - if ( glFormat !== null ) { + } - if ( useTexStorage ) { +} - state.compressedTexSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, 0, 0, mipmap.width, mipmap.height, glFormat, mipmap.data ); +/** + * A low level class for loading resources with the Fetch API, used internally by + * most loaders. It can also be used directly to load any file type that does + * not have a loader. + * + * This loader supports caching. If you want to use it, add `THREE.Cache.enabled = true;` + * once to your application. + * + * ```js + * const loader = new THREE.FileLoader(); + * const data = await loader.loadAsync( 'example.txt' ); + * ``` + * + * @augments Loader + */ +class FileLoader extends Loader { - } else { + /** + * Constructs a new file loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ + constructor( manager ) { - state.compressedTexImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glInternalFormat, mipmap.width, mipmap.height, 0, mipmap.data ); + super( manager ); - } + /** + * The expected mime type. + * + * @type {string} + */ + this.mimeType = ''; - } else { + /** + * The expected response type. + * + * @type {('arraybuffer'|'blob'|'document'|'json'|'')} + * @default '' + */ + this.responseType = ''; - console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .setTextureCube()' ); + } - } + /** + * Starts loading from the given URL and pass the loaded response to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(any)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} [onProgress] - Executed while the loading is in progress. + * @param {onErrorCallback} [onError] - Executed when errors occur. + * @return {any|undefined} The cached resource if available. + */ + load( url, onLoad, onProgress, onError ) { - } else { + if ( url === undefined ) url = ''; - if ( useTexStorage ) { + if ( this.path !== undefined ) url = this.path + url; - state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, 0, 0, mipmap.width, mipmap.height, glFormat, glType, mipmap.data ); + url = this.manager.resolveURL( url ); - } else { + const cached = Cache.get( `file:${url}` ); - state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); + if ( cached !== undefined ) { - } + this.manager.itemStart( url ); - } + setTimeout( () => { - } + if ( onLoad ) onLoad( cached ); - } + this.manager.itemEnd( url ); - } else { + }, 0 ); - mipmaps = texture.mipmaps; + return cached; - if ( useTexStorage && allocateMemory ) { + } - // TODO: Uniformly handle mipmap definitions - // Normal textures and compressed cube textures define base level + mips with their mipmap array - // Uncompressed cube textures use their mipmap array only for mips (no base level) + // Check if request is duplicate - if ( mipmaps.length > 0 ) levels ++; + if ( loading[ url ] !== undefined ) { - state.texStorage2D( _gl.TEXTURE_CUBE_MAP, levels, glInternalFormat, cubeImage[ 0 ].width, cubeImage[ 0 ].height ); + loading[ url ].push( { - } + onLoad: onLoad, + onProgress: onProgress, + onError: onError - for ( let i = 0; i < 6; i ++ ) { + } ); - if ( isDataTexture ) { + return; - if ( useTexStorage ) { + } - state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, 0, 0, cubeImage[ i ].width, cubeImage[ i ].height, glFormat, glType, cubeImage[ i ].data ); + // Initialise array for duplicate requests + loading[ url ] = []; - } else { + loading[ url ].push( { + onLoad: onLoad, + onProgress: onProgress, + onError: onError, + } ); - state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, cubeImage[ i ].width, cubeImage[ i ].height, 0, glFormat, glType, cubeImage[ i ].data ); + // create request + const req = new Request( url, { + headers: new Headers( this.requestHeader ), + credentials: this.withCredentials ? 'include' : 'same-origin', + // An abort controller could be added within a future PR + } ); - } + // record states ( avoid data race ) + const mimeType = this.mimeType; + const responseType = this.responseType; - for ( let j = 0; j < mipmaps.length; j ++ ) { + // start the fetch + fetch( req ) + .then( response => { - const mipmap = mipmaps[ j ]; - const mipmapImage = mipmap.image[ i ].image; + if ( response.status === 200 || response.status === 0 ) { - if ( useTexStorage ) { + // Some browsers return HTTP Status 0 when using non-http protocol + // e.g. 'file://' or 'data://'. Handle as success. - state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, 0, 0, mipmapImage.width, mipmapImage.height, glFormat, glType, mipmapImage.data ); + if ( response.status === 0 ) { - } else { + console.warn( 'THREE.FileLoader: HTTP Status 0 received.' ); - state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, glInternalFormat, mipmapImage.width, mipmapImage.height, 0, glFormat, glType, mipmapImage.data ); + } - } + // Workaround: Checking if response.body === undefined for Alipay browser #23548 - } + if ( typeof ReadableStream === 'undefined' || response.body === undefined || response.body.getReader === undefined ) { - } else { + return response; - if ( useTexStorage ) { + } - state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, 0, 0, glFormat, glType, cubeImage[ i ] ); + const callbacks = loading[ url ]; + const reader = response.body.getReader(); - } else { + // Nginx needs X-File-Size check + // https://serverfault.com/questions/482875/why-does-nginx-remove-content-length-header-for-chunked-content + const contentLength = response.headers.get( 'X-File-Size' ) || response.headers.get( 'Content-Length' ); + const total = contentLength ? parseInt( contentLength ) : 0; + const lengthComputable = total !== 0; + let loaded = 0; - state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, glFormat, glType, cubeImage[ i ] ); + // periodically read data into the new stream tracking while download progress + const stream = new ReadableStream( { + start( controller ) { - } + readData(); - for ( let j = 0; j < mipmaps.length; j ++ ) { + function readData() { - const mipmap = mipmaps[ j ]; + reader.read().then( ( { done, value } ) => { - if ( useTexStorage ) { + if ( done ) { - state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, 0, 0, glFormat, glType, mipmap.image[ i ] ); + controller.close(); - } else { + } else { - state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, glInternalFormat, glFormat, glType, mipmap.image[ i ] ); + loaded += value.byteLength; + + const event = new ProgressEvent( 'progress', { lengthComputable, loaded, total } ); + for ( let i = 0, il = callbacks.length; i < il; i ++ ) { - } + const callback = callbacks[ i ]; + if ( callback.onProgress ) callback.onProgress( event ); - } + } - } + controller.enqueue( value ); + readData(); - } + } - } + }, ( e ) => { - if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) { + controller.error( e ); - // We assume images for cube map have the same size. - generateMipmap( _gl.TEXTURE_CUBE_MAP ); + } ); - } + } - sourceProperties.__version = source.version; + } - if ( texture.onUpdate ) texture.onUpdate( texture ); + } ); - } + return new Response( stream ); - textureProperties.__version = texture.version; + } else { - } + throw new HttpError( `fetch for "${response.url}" responded with ${response.status}: ${response.statusText}`, response ); - // Render targets + } - // Setup storage for target texture and bind it to correct framebuffer - function setupFrameBufferTexture( framebuffer, renderTarget, texture, attachment, textureTarget ) { + } ) + .then( response => { - const glFormat = utils.convert( texture.format, texture.colorSpace ); - const glType = utils.convert( texture.type ); - const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace ); - const renderTargetProperties = properties.get( renderTarget ); + switch ( responseType ) { - if ( ! renderTargetProperties.__hasExternalTextures ) { + case 'arraybuffer': - if ( textureTarget === _gl.TEXTURE_3D || textureTarget === _gl.TEXTURE_2D_ARRAY ) { + return response.arrayBuffer(); - state.texImage3D( textureTarget, 0, glInternalFormat, renderTarget.width, renderTarget.height, renderTarget.depth, 0, glFormat, glType, null ); + case 'blob': - } else { + return response.blob(); - state.texImage2D( textureTarget, 0, glInternalFormat, renderTarget.width, renderTarget.height, 0, glFormat, glType, null ); + case 'document': - } + return response.text() + .then( text => { - } + const parser = new DOMParser(); + return parser.parseFromString( text, mimeType ); - state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); + } ); - if ( useMultisampledRTT( renderTarget ) ) { + case 'json': - multisampledRTTExt.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, attachment, textureTarget, properties.get( texture ).__webglTexture, 0, getRenderTargetSamples( renderTarget ) ); + return response.json(); - } else if ( textureTarget === _gl.TEXTURE_2D || ( textureTarget >= _gl.TEXTURE_CUBE_MAP_POSITIVE_X && textureTarget <= _gl.TEXTURE_CUBE_MAP_NEGATIVE_Z ) ) { // see #24753 + default: - _gl.framebufferTexture2D( _gl.FRAMEBUFFER, attachment, textureTarget, properties.get( texture ).__webglTexture, 0 ); + if ( mimeType === '' ) { - } + return response.text(); - state.bindFramebuffer( _gl.FRAMEBUFFER, null ); + } else { - } + // sniff encoding + const re = /charset="?([^;"\s]*)"?/i; + const exec = re.exec( mimeType ); + const label = exec && exec[ 1 ] ? exec[ 1 ].toLowerCase() : undefined; + const decoder = new TextDecoder( label ); + return response.arrayBuffer().then( ab => decoder.decode( ab ) ); + } - // Setup storage for internal depth/stencil buffers and bind to correct framebuffer - function setupRenderBufferStorage( renderbuffer, renderTarget, isMultisample ) { + } - _gl.bindRenderbuffer( _gl.RENDERBUFFER, renderbuffer ); + } ) + .then( data => { - if ( renderTarget.depthBuffer && ! renderTarget.stencilBuffer ) { + // Add to cache only on HTTP success, so that we do not cache + // error response bodies as proper responses to requests. + Cache.add( `file:${url}`, data ); - let glInternalFormat = _gl.DEPTH_COMPONENT16; + const callbacks = loading[ url ]; + delete loading[ url ]; - if ( isMultisample || useMultisampledRTT( renderTarget ) ) { + for ( let i = 0, il = callbacks.length; i < il; i ++ ) { - const depthTexture = renderTarget.depthTexture; + const callback = callbacks[ i ]; + if ( callback.onLoad ) callback.onLoad( data ); - if ( depthTexture && depthTexture.isDepthTexture ) { + } - if ( depthTexture.type === FloatType ) { + } ) + .catch( err => { - glInternalFormat = _gl.DEPTH_COMPONENT32F; + // Abort errors and other errors are handled the same - } else if ( depthTexture.type === UnsignedIntType ) { + const callbacks = loading[ url ]; - glInternalFormat = _gl.DEPTH_COMPONENT24; + if ( callbacks === undefined ) { - } + // When onLoad was called and url was deleted in `loading` + this.manager.itemError( url ); + throw err; } - const samples = getRenderTargetSamples( renderTarget ); - - if ( useMultisampledRTT( renderTarget ) ) { - - multisampledRTTExt.renderbufferStorageMultisampleEXT( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); + delete loading[ url ]; - } else { + for ( let i = 0, il = callbacks.length; i < il; i ++ ) { - _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); + const callback = callbacks[ i ]; + if ( callback.onError ) callback.onError( err ); } - } else { + this.manager.itemError( url ); - _gl.renderbufferStorage( _gl.RENDERBUFFER, glInternalFormat, renderTarget.width, renderTarget.height ); + } ) + .finally( () => { - } + this.manager.itemEnd( url ); - _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer ); + } ); - } else if ( renderTarget.depthBuffer && renderTarget.stencilBuffer ) { + this.manager.itemStart( url ); - const samples = getRenderTargetSamples( renderTarget ); + } + + /** + * Sets the expected response type. + * + * @param {('arraybuffer'|'blob'|'document'|'json'|'')} value - The response type. + * @return {FileLoader} A reference to this file loader. + */ + setResponseType( value ) { - if ( isMultisample && useMultisampledRTT( renderTarget ) === false ) { + this.responseType = value; + return this; - _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, _gl.DEPTH24_STENCIL8, renderTarget.width, renderTarget.height ); + } - } else if ( useMultisampledRTT( renderTarget ) ) { + /** + * Sets the expected mime type of the loaded file. + * + * @param {string} value - The mime type. + * @return {FileLoader} A reference to this file loader. + */ + setMimeType( value ) { - multisampledRTTExt.renderbufferStorageMultisampleEXT( _gl.RENDERBUFFER, samples, _gl.DEPTH24_STENCIL8, renderTarget.width, renderTarget.height ); + this.mimeType = value; + return this; - } else { + } - _gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.DEPTH_STENCIL, renderTarget.width, renderTarget.height ); +} - } +/** + * Class for loading animation clips in the JSON format. The files are internally + * loaded via {@link FileLoader}. + * + * ```js + * const loader = new THREE.AnimationLoader(); + * const animations = await loader.loadAsync( 'animations/animation.js' ); + * ``` + * + * @augments Loader + */ +class AnimationLoader extends Loader { + /** + * Constructs a new animation loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ + constructor( manager ) { - _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer ); + super( manager ); - } else { + } - const textures = renderTarget.isWebGLMultipleRenderTargets === true ? renderTarget.texture : [ renderTarget.texture ]; + /** + * Starts loading from the given URL and pass the loaded animations as an array + * holding instances of {@link AnimationClip} to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(Array)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ + load( url, onLoad, onProgress, onError ) { - for ( let i = 0; i < textures.length; i ++ ) { + const scope = this; - const texture = textures[ i ]; + const loader = new FileLoader( this.manager ); + loader.setPath( this.path ); + loader.setRequestHeader( this.requestHeader ); + loader.setWithCredentials( this.withCredentials ); + loader.load( url, function ( text ) { - const glFormat = utils.convert( texture.format, texture.colorSpace ); - const glType = utils.convert( texture.type ); - const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace ); - const samples = getRenderTargetSamples( renderTarget ); + try { - if ( isMultisample && useMultisampledRTT( renderTarget ) === false ) { + onLoad( scope.parse( JSON.parse( text ) ) ); - _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); + } catch ( e ) { - } else if ( useMultisampledRTT( renderTarget ) ) { + if ( onError ) { - multisampledRTTExt.renderbufferStorageMultisampleEXT( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); + onError( e ); } else { - _gl.renderbufferStorage( _gl.RENDERBUFFER, glInternalFormat, renderTarget.width, renderTarget.height ); + console.error( e ); } - } + scope.manager.itemError( url ); - } + } - _gl.bindRenderbuffer( _gl.RENDERBUFFER, null ); + }, onProgress, onError ); } - // Setup resources for a Depth Texture for a FBO (needs an extension) - function setupDepthTexture( framebuffer, renderTarget ) { + /** + * Parses the given JSON object and returns an array of animation clips. + * + * @param {Object} json - The serialized animation clips. + * @return {Array} The parsed animation clips. + */ + parse( json ) { - const isCube = ( renderTarget && renderTarget.isWebGLCubeRenderTarget ); - if ( isCube ) throw new Error( 'Depth Texture with cube render targets is not supported' ); + const animations = []; - state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); + for ( let i = 0; i < json.length; i ++ ) { - if ( ! ( renderTarget.depthTexture && renderTarget.depthTexture.isDepthTexture ) ) { + const clip = AnimationClip.parse( json[ i ] ); - throw new Error( 'renderTarget.depthTexture must be an instance of THREE.DepthTexture' ); + animations.push( clip ); } - // upload an empty depth texture with framebuffer size - if ( ! properties.get( renderTarget.depthTexture ).__webglTexture || - renderTarget.depthTexture.image.width !== renderTarget.width || - renderTarget.depthTexture.image.height !== renderTarget.height ) { - - renderTarget.depthTexture.image.width = renderTarget.width; - renderTarget.depthTexture.image.height = renderTarget.height; - renderTarget.depthTexture.needsUpdate = true; - - } + return animations; - setTexture2D( renderTarget.depthTexture, 0 ); + } - const webglDepthTexture = properties.get( renderTarget.depthTexture ).__webglTexture; - const samples = getRenderTargetSamples( renderTarget ); +} - if ( renderTarget.depthTexture.format === DepthFormat ) { +/** + * Abstract base class for loading compressed texture formats S3TC, ASTC or ETC. + * Textures are internally loaded via {@link FileLoader}. + * + * Derived classes have to implement the `parse()` method which holds the parsing + * for the respective format. + * + * @abstract + * @augments Loader + */ +class CompressedTextureLoader extends Loader { - if ( useMultisampledRTT( renderTarget ) ) { + /** + * Constructs a new compressed texture loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ + constructor( manager ) { - multisampledRTTExt.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0, samples ); + super( manager ); - } else { + } - _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0 ); + /** + * Starts loading from the given URL and passes the loaded compressed texture + * to the `onLoad()` callback. The method also returns a new texture object which can + * directly be used for material creation. If you do it this way, the texture + * may pop up in your scene once the respective loading process is finished. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(CompressedTexture)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + * @return {CompressedTexture} The compressed texture. + */ + load( url, onLoad, onProgress, onError ) { - } + const scope = this; - } else if ( renderTarget.depthTexture.format === DepthStencilFormat ) { + const images = []; - if ( useMultisampledRTT( renderTarget ) ) { + const texture = new CompressedTexture(); - multisampledRTTExt.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0, samples ); + const loader = new FileLoader( this.manager ); + loader.setPath( this.path ); + loader.setResponseType( 'arraybuffer' ); + loader.setRequestHeader( this.requestHeader ); + loader.setWithCredentials( scope.withCredentials ); - } else { + let loaded = 0; - _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0 ); + function loadTexture( i ) { - } + loader.load( url[ i ], function ( buffer ) { - } else { + const texDatas = scope.parse( buffer, true ); - throw new Error( 'Unknown depthTexture format' ); + images[ i ] = { + width: texDatas.width, + height: texDatas.height, + format: texDatas.format, + mipmaps: texDatas.mipmaps + }; - } + loaded += 1; - } + if ( loaded === 6 ) { - // Setup GL resources for a non-texture depth buffer - function setupDepthRenderbuffer( renderTarget ) { + if ( texDatas.mipmapCount === 1 ) texture.minFilter = LinearFilter; - const renderTargetProperties = properties.get( renderTarget ); - const isCube = ( renderTarget.isWebGLCubeRenderTarget === true ); + texture.image = images; + texture.format = texDatas.format; + texture.needsUpdate = true; - if ( renderTarget.depthTexture && ! renderTargetProperties.__autoAllocateDepthBuffer ) { + if ( onLoad ) onLoad( texture ); - if ( isCube ) throw new Error( 'target.depthTexture not supported in Cube render targets' ); + } - setupDepthTexture( renderTargetProperties.__webglFramebuffer, renderTarget ); + }, onProgress, onError ); - } else { + } - if ( isCube ) { + if ( Array.isArray( url ) ) { - renderTargetProperties.__webglDepthbuffer = []; + for ( let i = 0, il = url.length; i < il; ++ i ) { - for ( let i = 0; i < 6; i ++ ) { + loadTexture( i ); - state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer[ i ] ); - renderTargetProperties.__webglDepthbuffer[ i ] = _gl.createRenderbuffer(); - setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer[ i ], renderTarget, false ); + } - } + } else { - } else { + // compressed cubemap texture stored in a single DDS file - state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); - renderTargetProperties.__webglDepthbuffer = _gl.createRenderbuffer(); - setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer, renderTarget, false ); + loader.load( url, function ( buffer ) { - } + const texDatas = scope.parse( buffer, true ); - } + if ( texDatas.isCubemap ) { - state.bindFramebuffer( _gl.FRAMEBUFFER, null ); + const faces = texDatas.mipmaps.length / texDatas.mipmapCount; - } + for ( let f = 0; f < faces; f ++ ) { - // rebind framebuffer with external textures - function rebindTextures( renderTarget, colorTexture, depthTexture ) { + images[ f ] = { mipmaps: [] }; - const renderTargetProperties = properties.get( renderTarget ); + for ( let i = 0; i < texDatas.mipmapCount; i ++ ) { - if ( colorTexture !== undefined ) { + images[ f ].mipmaps.push( texDatas.mipmaps[ f * texDatas.mipmapCount + i ] ); + images[ f ].format = texDatas.format; + images[ f ].width = texDatas.width; + images[ f ].height = texDatas.height; - setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, renderTarget.texture, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D ); + } - } + } - if ( depthTexture !== undefined ) { + texture.image = images; - setupDepthRenderbuffer( renderTarget ); + } else { - } + texture.image.width = texDatas.width; + texture.image.height = texDatas.height; + texture.mipmaps = texDatas.mipmaps; - } + } - // Set up GL resources for the render target - function setupRenderTarget( renderTarget ) { + if ( texDatas.mipmapCount === 1 ) { - const texture = renderTarget.texture; + texture.minFilter = LinearFilter; - const renderTargetProperties = properties.get( renderTarget ); - const textureProperties = properties.get( texture ); + } - renderTarget.addEventListener( 'dispose', onRenderTargetDispose ); + texture.format = texDatas.format; + texture.needsUpdate = true; - if ( renderTarget.isWebGLMultipleRenderTargets !== true ) { + if ( onLoad ) onLoad( texture ); - if ( textureProperties.__webglTexture === undefined ) { + }, onProgress, onError ); - textureProperties.__webglTexture = _gl.createTexture(); + } - } + return texture; - textureProperties.__version = texture.version; - info.memory.textures ++; + } - } +} - const isCube = ( renderTarget.isWebGLCubeRenderTarget === true ); - const isMultipleRenderTargets = ( renderTarget.isWebGLMultipleRenderTargets === true ); - const supportsMips = isPowerOfTwo$1( renderTarget ) || isWebGL2; +const _loading = new WeakMap(); - // Setup framebuffer +/** + * A loader for loading images. The class loads images with the HTML `Image` API. + * + * ```js + * const loader = new THREE.ImageLoader(); + * const image = await loader.loadAsync( 'image.png' ); + * ``` + * Please note that `ImageLoader` has dropped support for progress + * events in `r84`. For an `ImageLoader` that supports progress events, see + * [this thread]{@link https://github.com/mrdoob/three.js/issues/10439#issuecomment-275785639}. + * + * @augments Loader + */ +class ImageLoader extends Loader { - if ( isCube ) { + /** + * Constructs a new image loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ + constructor( manager ) { - renderTargetProperties.__webglFramebuffer = []; + super( manager ); - for ( let i = 0; i < 6; i ++ ) { + } - renderTargetProperties.__webglFramebuffer[ i ] = _gl.createFramebuffer(); + /** + * Starts loading from the given URL and passes the loaded image + * to the `onLoad()` callback. The method also returns a new `Image` object which can + * directly be used for texture creation. If you do it this way, the texture + * may pop up in your scene once the respective loading process is finished. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(Image)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Unsupported in this loader. + * @param {onErrorCallback} onError - Executed when errors occur. + * @return {Image} The image. + */ + load( url, onLoad, onProgress, onError ) { - } + if ( this.path !== undefined ) url = this.path + url; - } else { + url = this.manager.resolveURL( url ); - renderTargetProperties.__webglFramebuffer = _gl.createFramebuffer(); + const scope = this; - if ( isMultipleRenderTargets ) { + const cached = Cache.get( `image:${url}` ); - if ( capabilities.drawBuffers ) { + if ( cached !== undefined ) { - const textures = renderTarget.texture; + if ( cached.complete === true ) { - for ( let i = 0, il = textures.length; i < il; i ++ ) { + scope.manager.itemStart( url ); - const attachmentProperties = properties.get( textures[ i ] ); + setTimeout( function () { - if ( attachmentProperties.__webglTexture === undefined ) { + if ( onLoad ) onLoad( cached ); - attachmentProperties.__webglTexture = _gl.createTexture(); + scope.manager.itemEnd( url ); - info.memory.textures ++; + }, 0 ); - } + } else { - } + let arr = _loading.get( cached ); - } else { + if ( arr === undefined ) { - console.warn( 'THREE.WebGLRenderer: WebGLMultipleRenderTargets can only be used with WebGL2 or WEBGL_draw_buffers extension.' ); + arr = []; + _loading.set( cached, arr ); } - } - - if ( ( isWebGL2 && renderTarget.samples > 0 ) && useMultisampledRTT( renderTarget ) === false ) { - - const textures = isMultipleRenderTargets ? texture : [ texture ]; + arr.push( { onLoad, onError } ); - renderTargetProperties.__webglMultisampledFramebuffer = _gl.createFramebuffer(); - renderTargetProperties.__webglColorRenderbuffer = []; + } - state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); + return cached; - for ( let i = 0; i < textures.length; i ++ ) { + } - const texture = textures[ i ]; - renderTargetProperties.__webglColorRenderbuffer[ i ] = _gl.createRenderbuffer(); + const image = createElementNS( 'img' ); - _gl.bindRenderbuffer( _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] ); + function onImageLoad() { - const glFormat = utils.convert( texture.format, texture.colorSpace ); - const glType = utils.convert( texture.type ); - const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace, renderTarget.isXRRenderTarget === true ); - const samples = getRenderTargetSamples( renderTarget ); - _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); + removeEventListeners(); - _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] ); + if ( onLoad ) onLoad( this ); - } + // - _gl.bindRenderbuffer( _gl.RENDERBUFFER, null ); + const callbacks = _loading.get( this ) || []; - if ( renderTarget.depthBuffer ) { + for ( let i = 0; i < callbacks.length; i ++ ) { - renderTargetProperties.__webglDepthRenderbuffer = _gl.createRenderbuffer(); - setupRenderBufferStorage( renderTargetProperties.__webglDepthRenderbuffer, renderTarget, true ); + const callback = callbacks[ i ]; + if ( callback.onLoad ) callback.onLoad( this ); - } + } - state.bindFramebuffer( _gl.FRAMEBUFFER, null ); + _loading.delete( this ); - } + scope.manager.itemEnd( url ); } - // Setup color buffer + function onImageError( event ) { - if ( isCube ) { + removeEventListeners(); - state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__webglTexture ); - setTextureParameters( _gl.TEXTURE_CUBE_MAP, texture, supportsMips ); + if ( onError ) onError( event ); - for ( let i = 0; i < 6; i ++ ) { + Cache.remove( `image:${url}` ); - setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer[ i ], renderTarget, texture, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i ); + // - } + const callbacks = _loading.get( this ) || []; - if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) { + for ( let i = 0; i < callbacks.length; i ++ ) { - generateMipmap( _gl.TEXTURE_CUBE_MAP ); + const callback = callbacks[ i ]; + if ( callback.onError ) callback.onError( event ); } - state.unbindTexture(); - - } else if ( isMultipleRenderTargets ) { - - const textures = renderTarget.texture; - - for ( let i = 0, il = textures.length; i < il; i ++ ) { - - const attachment = textures[ i ]; - const attachmentProperties = properties.get( attachment ); + _loading.delete( this ); - state.bindTexture( _gl.TEXTURE_2D, attachmentProperties.__webglTexture ); - setTextureParameters( _gl.TEXTURE_2D, attachment, supportsMips ); - setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, attachment, _gl.COLOR_ATTACHMENT0 + i, _gl.TEXTURE_2D ); - if ( textureNeedsGenerateMipmaps( attachment, supportsMips ) ) { + scope.manager.itemError( url ); + scope.manager.itemEnd( url ); - generateMipmap( _gl.TEXTURE_2D ); + } - } + function removeEventListeners() { - } + image.removeEventListener( 'load', onImageLoad, false ); + image.removeEventListener( 'error', onImageError, false ); - state.unbindTexture(); + } - } else { + image.addEventListener( 'load', onImageLoad, false ); + image.addEventListener( 'error', onImageError, false ); - let glTextureType = _gl.TEXTURE_2D; + if ( url.slice( 0, 5 ) !== 'data:' ) { - if ( renderTarget.isWebGL3DRenderTarget || renderTarget.isWebGLArrayRenderTarget ) { + if ( this.crossOrigin !== undefined ) image.crossOrigin = this.crossOrigin; - if ( isWebGL2 ) { + } - glTextureType = renderTarget.isWebGL3DRenderTarget ? _gl.TEXTURE_3D : _gl.TEXTURE_2D_ARRAY; + Cache.add( `image:${url}`, image ); + scope.manager.itemStart( url ); - } else { + image.src = url; - console.error( 'THREE.WebGLTextures: THREE.Data3DTexture and THREE.DataArrayTexture only supported with WebGL2.' ); + return image; - } + } - } +} - state.bindTexture( glTextureType, textureProperties.__webglTexture ); - setTextureParameters( glTextureType, texture, supportsMips ); - setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, texture, _gl.COLOR_ATTACHMENT0, glTextureType ); +/** + * Class for loading cube textures. Images are internally loaded via {@link ImageLoader}. + * + * The loader returns an instance of {@link CubeTexture} and expects the cube map to + * be defined as six separate images representing the sides of a cube. Other cube map definitions + * like vertical and horizontal cross, column and row layouts are not supported. + * + * Note that, by convention, cube maps are specified in a coordinate system + * in which positive-x is to the right when looking up the positive-z axis -- + * in other words, using a left-handed coordinate system. Since three.js uses + * a right-handed coordinate system, environment maps used in three.js will + * have pos-x and neg-x swapped. + * + * The loaded cube texture is in sRGB color space. Meaning {@link Texture#colorSpace} + * is set to `SRGBColorSpace` by default. + * + * ```js + * const loader = new THREE.CubeTextureLoader().setPath( 'textures/cubeMaps/' ); + * const cubeTexture = await loader.loadAsync( [ + * 'px.png', 'nx.png', 'py.png', 'ny.png', 'pz.png', 'nz.png' + * ] ); + * scene.background = cubeTexture; + * ``` + * + * @augments Loader + */ +class CubeTextureLoader extends Loader { - if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) { + /** + * Constructs a new cube texture loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ + constructor( manager ) { - generateMipmap( glTextureType ); + super( manager ); - } + } - state.unbindTexture(); + /** + * Starts loading from the given URL and pass the fully loaded cube texture + * to the `onLoad()` callback. The method also returns a new cube texture object which can + * directly be used for material creation. If you do it this way, the cube texture + * may pop up in your scene once the respective loading process is finished. + * + * @param {Array} urls - Array of 6 URLs to images, one for each side of the + * cube texture. The urls should be specified in the following order: pos-x, + * neg-x, pos-y, neg-y, pos-z, neg-z. An array of data URIs are allowed as well. + * @param {function(CubeTexture)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Unsupported in this loader. + * @param {onErrorCallback} onError - Executed when errors occur. + * @return {CubeTexture} The cube texture. + */ + load( urls, onLoad, onProgress, onError ) { - } + const texture = new CubeTexture(); + texture.colorSpace = SRGBColorSpace; - // Setup depth and stencil buffers + const loader = new ImageLoader( this.manager ); + loader.setCrossOrigin( this.crossOrigin ); + loader.setPath( this.path ); - if ( renderTarget.depthBuffer ) { + let loaded = 0; - setupDepthRenderbuffer( renderTarget ); + function loadTexture( i ) { - } + loader.load( urls[ i ], function ( image ) { - } + texture.images[ i ] = image; - function updateRenderTargetMipmap( renderTarget ) { + loaded ++; - const supportsMips = isPowerOfTwo$1( renderTarget ) || isWebGL2; + if ( loaded === 6 ) { - const textures = renderTarget.isWebGLMultipleRenderTargets === true ? renderTarget.texture : [ renderTarget.texture ]; + texture.needsUpdate = true; - for ( let i = 0, il = textures.length; i < il; i ++ ) { + if ( onLoad ) onLoad( texture ); - const texture = textures[ i ]; + } - if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) { + }, undefined, onError ); - const target = renderTarget.isWebGLCubeRenderTarget ? _gl.TEXTURE_CUBE_MAP : _gl.TEXTURE_2D; - const webglTexture = properties.get( texture ).__webglTexture; + } - state.bindTexture( target, webglTexture ); - generateMipmap( target ); - state.unbindTexture(); + for ( let i = 0; i < urls.length; ++ i ) { - } + loadTexture( i ); } - } + return texture; - function updateMultisampleRenderTarget( renderTarget ) { + } - if ( ( isWebGL2 && renderTarget.samples > 0 ) && useMultisampledRTT( renderTarget ) === false ) { +} - const textures = renderTarget.isWebGLMultipleRenderTargets ? renderTarget.texture : [ renderTarget.texture ]; - const width = renderTarget.width; - const height = renderTarget.height; - let mask = _gl.COLOR_BUFFER_BIT; - const invalidationArray = []; - const depthStyle = renderTarget.stencilBuffer ? _gl.DEPTH_STENCIL_ATTACHMENT : _gl.DEPTH_ATTACHMENT; - const renderTargetProperties = properties.get( renderTarget ); - const isMultipleRenderTargets = ( renderTarget.isWebGLMultipleRenderTargets === true ); +/** + * Abstract base class for loading binary texture formats RGBE, EXR or TGA. + * Textures are internally loaded via {@link FileLoader}. + * + * Derived classes have to implement the `parse()` method which holds the parsing + * for the respective format. + * + * @abstract + * @augments Loader + */ +class DataTextureLoader extends Loader { - // If MRT we need to remove FBO attachments - if ( isMultipleRenderTargets ) { + /** + * Constructs a new data texture loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ + constructor( manager ) { - for ( let i = 0; i < textures.length; i ++ ) { + super( manager ); - state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); - _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.RENDERBUFFER, null ); + } - state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); - _gl.framebufferTexture2D( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.TEXTURE_2D, null, 0 ); + /** + * Starts loading from the given URL and passes the loaded data texture + * to the `onLoad()` callback. The method also returns a new texture object which can + * directly be used for material creation. If you do it this way, the texture + * may pop up in your scene once the respective loading process is finished. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(DataTexture)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + * @return {DataTexture} The data texture. + */ + load( url, onLoad, onProgress, onError ) { - } + const scope = this; - } + const texture = new DataTexture(); - state.bindFramebuffer( _gl.READ_FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); - state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); + const loader = new FileLoader( this.manager ); + loader.setResponseType( 'arraybuffer' ); + loader.setRequestHeader( this.requestHeader ); + loader.setPath( this.path ); + loader.setWithCredentials( scope.withCredentials ); + loader.load( url, function ( buffer ) { - for ( let i = 0; i < textures.length; i ++ ) { + let texData; - invalidationArray.push( _gl.COLOR_ATTACHMENT0 + i ); + try { - if ( renderTarget.depthBuffer ) { + texData = scope.parse( buffer ); - invalidationArray.push( depthStyle ); + } catch ( error ) { - } + if ( onError !== undefined ) { - const ignoreDepthValues = ( renderTargetProperties.__ignoreDepthValues !== undefined ) ? renderTargetProperties.__ignoreDepthValues : false; + onError( error ); - if ( ignoreDepthValues === false ) { + } else { - if ( renderTarget.depthBuffer ) mask |= _gl.DEPTH_BUFFER_BIT; - if ( renderTarget.stencilBuffer ) mask |= _gl.STENCIL_BUFFER_BIT; + console.error( error ); + return; } - if ( isMultipleRenderTargets ) { + } - _gl.framebufferRenderbuffer( _gl.READ_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] ); + if ( texData.image !== undefined ) { - } + texture.image = texData.image; - if ( ignoreDepthValues === true ) { + } else if ( texData.data !== undefined ) { - _gl.invalidateFramebuffer( _gl.READ_FRAMEBUFFER, [ depthStyle ] ); - _gl.invalidateFramebuffer( _gl.DRAW_FRAMEBUFFER, [ depthStyle ] ); + texture.image.width = texData.width; + texture.image.height = texData.height; + texture.image.data = texData.data; - } + } - if ( isMultipleRenderTargets ) { + texture.wrapS = texData.wrapS !== undefined ? texData.wrapS : ClampToEdgeWrapping; + texture.wrapT = texData.wrapT !== undefined ? texData.wrapT : ClampToEdgeWrapping; - const webglTexture = properties.get( textures[ i ] ).__webglTexture; - _gl.framebufferTexture2D( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D, webglTexture, 0 ); + texture.magFilter = texData.magFilter !== undefined ? texData.magFilter : LinearFilter; + texture.minFilter = texData.minFilter !== undefined ? texData.minFilter : LinearFilter; - } + texture.anisotropy = texData.anisotropy !== undefined ? texData.anisotropy : 1; - _gl.blitFramebuffer( 0, 0, width, height, 0, 0, width, height, mask, _gl.NEAREST ); + if ( texData.colorSpace !== undefined ) { - if ( supportsInvalidateFramebuffer ) { + texture.colorSpace = texData.colorSpace; - _gl.invalidateFramebuffer( _gl.READ_FRAMEBUFFER, invalidationArray ); + } - } + if ( texData.flipY !== undefined ) { + texture.flipY = texData.flipY; } - state.bindFramebuffer( _gl.READ_FRAMEBUFFER, null ); - state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, null ); + if ( texData.format !== undefined ) { - // If MRT since pre-blit we removed the FBO we need to reconstruct the attachments - if ( isMultipleRenderTargets ) { + texture.format = texData.format; - for ( let i = 0; i < textures.length; i ++ ) { + } - state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); - _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] ); + if ( texData.type !== undefined ) { - const webglTexture = properties.get( textures[ i ] ).__webglTexture; + texture.type = texData.type; - state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); - _gl.framebufferTexture2D( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.TEXTURE_2D, webglTexture, 0 ); + } - } + if ( texData.mipmaps !== undefined ) { + + texture.mipmaps = texData.mipmaps; + texture.minFilter = LinearMipmapLinearFilter; // presumably... } - state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); + if ( texData.mipmapCount === 1 ) { - } + texture.minFilter = LinearFilter; - } + } - function getRenderTargetSamples( renderTarget ) { + if ( texData.generateMipmaps !== undefined ) { - return Math.min( maxSamples, renderTarget.samples ); + texture.generateMipmaps = texData.generateMipmaps; - } + } - function useMultisampledRTT( renderTarget ) { + texture.needsUpdate = true; - const renderTargetProperties = properties.get( renderTarget ); + if ( onLoad ) onLoad( texture, texData ); - return isWebGL2 && renderTarget.samples > 0 && extensions.has( 'WEBGL_multisampled_render_to_texture' ) === true && renderTargetProperties.__useRenderToTexture !== false; + }, onProgress, onError ); - } - function updateVideoTexture( texture ) { + return texture; - const frame = info.render.frame; + } - // Check the last frame we updated the VideoTexture +} - if ( _videoTextures.get( texture ) !== frame ) { +/** + * Class for loading textures. Images are internally + * loaded via {@link ImageLoader}. + * + * ```js + * const loader = new THREE.TextureLoader(); + * const texture = await loader.loadAsync( 'textures/land_ocean_ice_cloud_2048.jpg' ); + * + * const material = new THREE.MeshBasicMaterial( { map:texture } ); + * ``` + * Please note that `TextureLoader` has dropped support for progress + * events in `r84`. For a `TextureLoader` that supports progress events, see + * [this thread]{@link https://github.com/mrdoob/three.js/issues/10439#issuecomment-293260145}. + * + * @augments Loader + */ +class TextureLoader extends Loader { - _videoTextures.set( texture, frame ); - texture.update(); + /** + * Constructs a new texture loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ + constructor( manager ) { - } + super( manager ); } - function verifyColorSpace( texture, image ) { + /** + * Starts loading from the given URL and pass the fully loaded texture + * to the `onLoad()` callback. The method also returns a new texture object which can + * directly be used for material creation. If you do it this way, the texture + * may pop up in your scene once the respective loading process is finished. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(Texture)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Unsupported in this loader. + * @param {onErrorCallback} onError - Executed when errors occur. + * @return {Texture} The texture. + */ + load( url, onLoad, onProgress, onError ) { - const colorSpace = texture.colorSpace; - const format = texture.format; - const type = texture.type; + const texture = new Texture(); - if ( texture.isCompressedTexture === true || texture.format === _SRGBAFormat ) return image; + const loader = new ImageLoader( this.manager ); + loader.setCrossOrigin( this.crossOrigin ); + loader.setPath( this.path ); - if ( colorSpace !== LinearSRGBColorSpace && colorSpace !== NoColorSpace ) { + loader.load( url, function ( image ) { - // sRGB + texture.image = image; + texture.needsUpdate = true; - if ( colorSpace === SRGBColorSpace ) { + if ( onLoad !== undefined ) { - if ( isWebGL2 === false ) { + onLoad( texture ); - // in WebGL 1, try to use EXT_sRGB extension and unsized formats + } - if ( extensions.has( 'EXT_sRGB' ) === true && format === RGBAFormat ) { + }, onProgress, onError ); - texture.format = _SRGBAFormat; + return texture; - // it's not possible to generate mips in WebGL 1 with this extension + } - texture.minFilter = LinearFilter; - texture.generateMipmaps = false; +} - } else { +/** + * Abstract base class for lights - all other light types inherit the + * properties and methods described here. + * + * @abstract + * @augments Object3D + */ +class Light extends Object3D { - // slow fallback (CPU decode) + /** + * Constructs a new light. + * + * @param {(number|Color|string)} [color=0xffffff] - The light's color. + * @param {number} [intensity=1] - The light's strength/intensity. + */ + constructor( color, intensity = 1 ) { - image = ImageUtils.sRGBToLinear( image ); + super(); - } + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLight = true; - } else { + this.type = 'Light'; - // in WebGL 2 uncompressed textures can only be sRGB encoded if they have the RGBA8 format + /** + * The light's color. + * + * @type {Color} + */ + this.color = new Color( color ); - if ( format !== RGBAFormat || type !== UnsignedByteType ) { + /** + * The light's intensity. + * + * @type {number} + * @default 1 + */ + this.intensity = intensity; - console.warn( 'THREE.WebGLTextures: sRGB encoded textures have to use RGBAFormat and UnsignedByteType.' ); + } - } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose() { - } + // Empty here in base class; some subclasses override. - } else { + } - console.error( 'THREE.WebGLTextures: Unsupported texture color space:', colorSpace ); + copy( source, recursive ) { - } + super.copy( source, recursive ); - } + this.color.copy( source.color ); + this.intensity = source.intensity; - return image; + return this; } - // - - this.allocateTextureUnit = allocateTextureUnit; - this.resetTextureUnits = resetTextureUnits; - - this.setTexture2D = setTexture2D; - this.setTexture2DArray = setTexture2DArray; - this.setTexture3D = setTexture3D; - this.setTextureCube = setTextureCube; - this.rebindTextures = rebindTextures; - this.setupRenderTarget = setupRenderTarget; - this.updateRenderTargetMipmap = updateRenderTargetMipmap; - this.updateMultisampleRenderTarget = updateMultisampleRenderTarget; - this.setupDepthRenderbuffer = setupDepthRenderbuffer; - this.setupFrameBufferTexture = setupFrameBufferTexture; - this.useMultisampledRTT = useMultisampledRTT; + toJSON( meta ) { -} + const data = super.toJSON( meta ); -function WebGLUtils( gl, extensions, capabilities ) { + data.object.color = this.color.getHex(); + data.object.intensity = this.intensity; - const isWebGL2 = capabilities.isWebGL2; + if ( this.groundColor !== undefined ) data.object.groundColor = this.groundColor.getHex(); - function convert( p, colorSpace = NoColorSpace ) { + if ( this.distance !== undefined ) data.object.distance = this.distance; + if ( this.angle !== undefined ) data.object.angle = this.angle; + if ( this.decay !== undefined ) data.object.decay = this.decay; + if ( this.penumbra !== undefined ) data.object.penumbra = this.penumbra; - let extension; + if ( this.shadow !== undefined ) data.object.shadow = this.shadow.toJSON(); + if ( this.target !== undefined ) data.object.target = this.target.uuid; - if ( p === UnsignedByteType ) return gl.UNSIGNED_BYTE; - if ( p === UnsignedShort4444Type ) return gl.UNSIGNED_SHORT_4_4_4_4; - if ( p === UnsignedShort5551Type ) return gl.UNSIGNED_SHORT_5_5_5_1; + return data; - if ( p === ByteType ) return gl.BYTE; - if ( p === ShortType ) return gl.SHORT; - if ( p === UnsignedShortType ) return gl.UNSIGNED_SHORT; - if ( p === IntType ) return gl.INT; - if ( p === UnsignedIntType ) return gl.UNSIGNED_INT; - if ( p === FloatType ) return gl.FLOAT; + } - if ( p === HalfFloatType ) { +} - if ( isWebGL2 ) return gl.HALF_FLOAT; +/** + * A light source positioned directly above the scene, with color fading from + * the sky color to the ground color. + * + * This light cannot be used to cast shadows. + * + * ```js + * const light = new THREE.HemisphereLight( 0xffffbb, 0x080820, 1 ); + * scene.add( light ); + * ``` + * + * @augments Light + */ +class HemisphereLight extends Light { - extension = extensions.get( 'OES_texture_half_float' ); + /** + * Constructs a new hemisphere light. + * + * @param {(number|Color|string)} [skyColor=0xffffff] - The light's sky color. + * @param {(number|Color|string)} [groundColor=0xffffff] - The light's ground color. + * @param {number} [intensity=1] - The light's strength/intensity. + */ + constructor( skyColor, groundColor, intensity ) { - if ( extension !== null ) { + super( skyColor, intensity ); - return extension.HALF_FLOAT_OES; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isHemisphereLight = true; - } else { + this.type = 'HemisphereLight'; - return null; + this.position.copy( Object3D.DEFAULT_UP ); + this.updateMatrix(); - } + /** + * The light's ground color. + * + * @type {Color} + */ + this.groundColor = new Color( groundColor ); - } + } - if ( p === AlphaFormat ) return gl.ALPHA; - if ( p === RGBAFormat ) return gl.RGBA; - if ( p === LuminanceFormat ) return gl.LUMINANCE; - if ( p === LuminanceAlphaFormat ) return gl.LUMINANCE_ALPHA; - if ( p === DepthFormat ) return gl.DEPTH_COMPONENT; - if ( p === DepthStencilFormat ) return gl.DEPTH_STENCIL; + copy( source, recursive ) { - // WebGL 1 sRGB fallback + super.copy( source, recursive ); - if ( p === _SRGBAFormat ) { + this.groundColor.copy( source.groundColor ); - extension = extensions.get( 'EXT_sRGB' ); + return this; - if ( extension !== null ) { + } - return extension.SRGB_ALPHA_EXT; +} - } else { +const _projScreenMatrix$1 = /*@__PURE__*/ new Matrix4(); +const _lightPositionWorld$1 = /*@__PURE__*/ new Vector3(); +const _lookTarget$1 = /*@__PURE__*/ new Vector3(); - return null; +/** + * Abstract base class for light shadow classes. These classes + * represent the shadow configuration for different light types. + * + * @abstract + */ +class LightShadow { - } + /** + * Constructs a new light shadow. + * + * @param {Camera} camera - The light's view of the world. + */ + constructor( camera ) { - } + /** + * The light's view of the world. + * + * @type {Camera} + */ + this.camera = camera; - // WebGL2 formats. + /** + * The intensity of the shadow. The default is `1`. + * Valid values are in the range `[0, 1]`. + * + * @type {number} + * @default 1 + */ + this.intensity = 1; - if ( p === RedFormat ) return gl.RED; - if ( p === RedIntegerFormat ) return gl.RED_INTEGER; - if ( p === RGFormat ) return gl.RG; - if ( p === RGIntegerFormat ) return gl.RG_INTEGER; - if ( p === RGBAIntegerFormat ) return gl.RGBA_INTEGER; + /** + * Shadow map bias, how much to add or subtract from the normalized depth + * when deciding whether a surface is in shadow. + * + * The default is `0`. Very tiny adjustments here (in the order of `0.0001`) + * may help reduce artifacts in shadows. + * + * @type {number} + * @default 0 + */ + this.bias = 0; - // S3TC + /** + * Defines how much the position used to query the shadow map is offset along + * the object normal. The default is `0`. Increasing this value can be used to + * reduce shadow acne especially in large scenes where light shines onto + * geometry at a shallow angle. The cost is that shadows may appear distorted. + * + * @type {number} + * @default 0 + */ + this.normalBias = 0; - if ( p === RGB_S3TC_DXT1_Format || p === RGBA_S3TC_DXT1_Format || p === RGBA_S3TC_DXT3_Format || p === RGBA_S3TC_DXT5_Format ) { + /** + * Setting this to values greater than 1 will blur the edges of the shadow. + * High values will cause unwanted banding effects in the shadows - a greater + * map size will allow for a higher value to be used here before these effects + * become visible. + * + * The property has no effect when the shadow map type is `PCFSoftShadowMap` and + * and it is recommended to increase softness by decreasing the shadow map size instead. + * + * The property has no effect when the shadow map type is `BasicShadowMap`. + * + * @type {number} + * @default 1 + */ + this.radius = 1; - if ( colorSpace === SRGBColorSpace ) { + /** + * The amount of samples to use when blurring a VSM shadow map. + * + * @type {number} + * @default 8 + */ + this.blurSamples = 8; - extension = extensions.get( 'WEBGL_compressed_texture_s3tc_srgb' ); + /** + * Defines the width and height of the shadow map. Higher values give better quality + * shadows at the cost of computation time. Values must be powers of two. + * + * @type {Vector2} + * @default (512,512) + */ + this.mapSize = new Vector2( 512, 512 ); - if ( extension !== null ) { + /** + * The type of shadow texture. The default is `UnsignedByteType`. + * + * @type {number} + * @default UnsignedByteType + */ + this.mapType = UnsignedByteType; - if ( p === RGB_S3TC_DXT1_Format ) return extension.COMPRESSED_SRGB_S3TC_DXT1_EXT; - if ( p === RGBA_S3TC_DXT1_Format ) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT; - if ( p === RGBA_S3TC_DXT3_Format ) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT; - if ( p === RGBA_S3TC_DXT5_Format ) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT; + /** + * The depth map generated using the internal camera; a location beyond a + * pixel's depth is in shadow. Computed internally during rendering. + * + * @type {?RenderTarget} + * @default null + */ + this.map = null; - } else { + /** + * The distribution map generated using the internal camera; an occlusion is + * calculated based on the distribution of depths. Computed internally during + * rendering. + * + * @type {?RenderTarget} + * @default null + */ + this.mapPass = null; - return null; + /** + * Model to shadow camera space, to compute location and depth in shadow map. + * This is computed internally during rendering. + * + * @type {Matrix4} + */ + this.matrix = new Matrix4(); - } + /** + * Enables automatic updates of the light's shadow. If you do not require dynamic + * lighting / shadows, you may set this to `false`. + * + * @type {boolean} + * @default true + */ + this.autoUpdate = true; - } else { + /** + * When set to `true`, shadow maps will be updated in the next `render` call. + * If you have set {@link LightShadow#autoUpdate} to `false`, you will need to + * set this property to `true` and then make a render call to update the light's shadow. + * + * @type {boolean} + * @default false + */ + this.needsUpdate = false; - extension = extensions.get( 'WEBGL_compressed_texture_s3tc' ); + this._frustum = new Frustum(); + this._frameExtents = new Vector2( 1, 1 ); - if ( extension !== null ) { + this._viewportCount = 1; - if ( p === RGB_S3TC_DXT1_Format ) return extension.COMPRESSED_RGB_S3TC_DXT1_EXT; - if ( p === RGBA_S3TC_DXT1_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT1_EXT; - if ( p === RGBA_S3TC_DXT3_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT3_EXT; - if ( p === RGBA_S3TC_DXT5_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT5_EXT; + this._viewports = [ - } else { + new Vector4( 0, 0, 1, 1 ) - return null; + ]; - } + } - } + /** + * Used internally by the renderer to get the number of viewports that need + * to be rendered for this shadow. + * + * @return {number} The viewport count. + */ + getViewportCount() { - } + return this._viewportCount; - // PVRTC + } - if ( p === RGB_PVRTC_4BPPV1_Format || p === RGB_PVRTC_2BPPV1_Format || p === RGBA_PVRTC_4BPPV1_Format || p === RGBA_PVRTC_2BPPV1_Format ) { + /** + * Gets the shadow cameras frustum. Used internally by the renderer to cull objects. + * + * @return {Frustum} The shadow camera frustum. + */ + getFrustum() { - extension = extensions.get( 'WEBGL_compressed_texture_pvrtc' ); + return this._frustum; - if ( extension !== null ) { + } - if ( p === RGB_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_4BPPV1_IMG; - if ( p === RGB_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_2BPPV1_IMG; - if ( p === RGBA_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; - if ( p === RGBA_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG; + /** + * Update the matrices for the camera and shadow, used internally by the renderer. + * + * @param {Light} light - The light for which the shadow is being rendered. + */ + updateMatrices( light ) { - } else { + const shadowCamera = this.camera; + const shadowMatrix = this.matrix; - return null; + _lightPositionWorld$1.setFromMatrixPosition( light.matrixWorld ); + shadowCamera.position.copy( _lightPositionWorld$1 ); - } + _lookTarget$1.setFromMatrixPosition( light.target.matrixWorld ); + shadowCamera.lookAt( _lookTarget$1 ); + shadowCamera.updateMatrixWorld(); - } + _projScreenMatrix$1.multiplyMatrices( shadowCamera.projectionMatrix, shadowCamera.matrixWorldInverse ); + this._frustum.setFromProjectionMatrix( _projScreenMatrix$1 ); - // ETC1 + shadowMatrix.set( + 0.5, 0.0, 0.0, 0.5, + 0.0, 0.5, 0.0, 0.5, + 0.0, 0.0, 0.5, 0.5, + 0.0, 0.0, 0.0, 1.0 + ); - if ( p === RGB_ETC1_Format ) { + shadowMatrix.multiply( _projScreenMatrix$1 ); - extension = extensions.get( 'WEBGL_compressed_texture_etc1' ); + } - if ( extension !== null ) { + /** + * Returns a viewport definition for the given viewport index. + * + * @param {number} viewportIndex - The viewport index. + * @return {Vector4} The viewport. + */ + getViewport( viewportIndex ) { - return extension.COMPRESSED_RGB_ETC1_WEBGL; + return this._viewports[ viewportIndex ]; - } else { + } - return null; + /** + * Returns the frame extends. + * + * @return {Vector2} The frame extends. + */ + getFrameExtents() { - } + return this._frameExtents; - } + } - // ETC2 + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose() { - if ( p === RGB_ETC2_Format || p === RGBA_ETC2_EAC_Format ) { + if ( this.map ) { - extension = extensions.get( 'WEBGL_compressed_texture_etc' ); + this.map.dispose(); - if ( extension !== null ) { + } - if ( p === RGB_ETC2_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ETC2 : extension.COMPRESSED_RGB8_ETC2; - if ( p === RGBA_ETC2_EAC_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ETC2_EAC : extension.COMPRESSED_RGBA8_ETC2_EAC; + if ( this.mapPass ) { - } else { + this.mapPass.dispose(); - return null; + } - } + } - } + /** + * Copies the values of the given light shadow instance to this instance. + * + * @param {LightShadow} source - The light shadow to copy. + * @return {LightShadow} A reference to this light shadow instance. + */ + copy( source ) { - // ASTC + this.camera = source.camera.clone(); - if ( p === RGBA_ASTC_4x4_Format || p === RGBA_ASTC_5x4_Format || p === RGBA_ASTC_5x5_Format || - p === RGBA_ASTC_6x5_Format || p === RGBA_ASTC_6x6_Format || p === RGBA_ASTC_8x5_Format || - p === RGBA_ASTC_8x6_Format || p === RGBA_ASTC_8x8_Format || p === RGBA_ASTC_10x5_Format || - p === RGBA_ASTC_10x6_Format || p === RGBA_ASTC_10x8_Format || p === RGBA_ASTC_10x10_Format || - p === RGBA_ASTC_12x10_Format || p === RGBA_ASTC_12x12_Format ) { + this.intensity = source.intensity; - extension = extensions.get( 'WEBGL_compressed_texture_astc' ); + this.bias = source.bias; + this.radius = source.radius; - if ( extension !== null ) { + this.autoUpdate = source.autoUpdate; + this.needsUpdate = source.needsUpdate; + this.normalBias = source.normalBias; + this.blurSamples = source.blurSamples; - if ( p === RGBA_ASTC_4x4_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR : extension.COMPRESSED_RGBA_ASTC_4x4_KHR; - if ( p === RGBA_ASTC_5x4_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR : extension.COMPRESSED_RGBA_ASTC_5x4_KHR; - if ( p === RGBA_ASTC_5x5_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR : extension.COMPRESSED_RGBA_ASTC_5x5_KHR; - if ( p === RGBA_ASTC_6x5_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR : extension.COMPRESSED_RGBA_ASTC_6x5_KHR; - if ( p === RGBA_ASTC_6x6_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR : extension.COMPRESSED_RGBA_ASTC_6x6_KHR; - if ( p === RGBA_ASTC_8x5_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR : extension.COMPRESSED_RGBA_ASTC_8x5_KHR; - if ( p === RGBA_ASTC_8x6_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR : extension.COMPRESSED_RGBA_ASTC_8x6_KHR; - if ( p === RGBA_ASTC_8x8_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR : extension.COMPRESSED_RGBA_ASTC_8x8_KHR; - if ( p === RGBA_ASTC_10x5_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR : extension.COMPRESSED_RGBA_ASTC_10x5_KHR; - if ( p === RGBA_ASTC_10x6_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR : extension.COMPRESSED_RGBA_ASTC_10x6_KHR; - if ( p === RGBA_ASTC_10x8_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR : extension.COMPRESSED_RGBA_ASTC_10x8_KHR; - if ( p === RGBA_ASTC_10x10_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR : extension.COMPRESSED_RGBA_ASTC_10x10_KHR; - if ( p === RGBA_ASTC_12x10_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR : extension.COMPRESSED_RGBA_ASTC_12x10_KHR; - if ( p === RGBA_ASTC_12x12_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR : extension.COMPRESSED_RGBA_ASTC_12x12_KHR; + this.mapSize.copy( source.mapSize ); - } else { + return this; - return null; + } - } + /** + * Returns a new light shadow instance with copied values from this instance. + * + * @return {LightShadow} A clone of this instance. + */ + clone() { - } + return new this.constructor().copy( this ); - // BPTC + } - if ( p === RGBA_BPTC_Format ) { + /** + * Serializes the light shadow into JSON. + * + * @return {Object} A JSON object representing the serialized light shadow. + * @see {@link ObjectLoader#parse} + */ + toJSON() { - extension = extensions.get( 'EXT_texture_compression_bptc' ); + const object = {}; - if ( extension !== null ) { + if ( this.intensity !== 1 ) object.intensity = this.intensity; + if ( this.bias !== 0 ) object.bias = this.bias; + if ( this.normalBias !== 0 ) object.normalBias = this.normalBias; + if ( this.radius !== 1 ) object.radius = this.radius; + if ( this.mapSize.x !== 512 || this.mapSize.y !== 512 ) object.mapSize = this.mapSize.toArray(); - if ( p === RGBA_BPTC_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT : extension.COMPRESSED_RGBA_BPTC_UNORM_EXT; + object.camera = this.camera.toJSON( false ).object; + delete object.camera.matrix; - } else { + return object; - return null; + } - } +} - } +/** + * Represents the shadow configuration of directional lights. + * + * @augments LightShadow + */ +class SpotLightShadow extends LightShadow { - // RGTC + /** + * Constructs a new spot light shadow. + */ + constructor() { - if ( p === RED_RGTC1_Format || p === SIGNED_RED_RGTC1_Format || p === RED_GREEN_RGTC2_Format || p === SIGNED_RED_GREEN_RGTC2_Format ) { + super( new PerspectiveCamera( 50, 1, 0.5, 500 ) ); - extension = extensions.get( 'EXT_texture_compression_rgtc' ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSpotLightShadow = true; - if ( extension !== null ) { + /** + * Used to focus the shadow camera. The camera's field of view is set as a + * percentage of the spotlight's field-of-view. Range is `[0, 1]`. + * + * @type {number} + * @default 1 + */ + this.focus = 1; - if ( p === RGBA_BPTC_Format ) return extension.COMPRESSED_RED_RGTC1_EXT; - if ( p === SIGNED_RED_RGTC1_Format ) return extension.COMPRESSED_SIGNED_RED_RGTC1_EXT; - if ( p === RED_GREEN_RGTC2_Format ) return extension.COMPRESSED_RED_GREEN_RGTC2_EXT; - if ( p === SIGNED_RED_GREEN_RGTC2_Format ) return extension.COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT; + /** + * Texture aspect ratio. + * + * @type {number} + * @default 1 + */ + this.aspect = 1; - } else { + } - return null; + updateMatrices( light ) { - } + const camera = this.camera; - } + const fov = RAD2DEG * 2 * light.angle * this.focus; + const aspect = ( this.mapSize.width / this.mapSize.height ) * this.aspect; + const far = light.distance || camera.far; - // + if ( fov !== camera.fov || aspect !== camera.aspect || far !== camera.far ) { - if ( p === UnsignedInt248Type ) { + camera.fov = fov; + camera.aspect = aspect; + camera.far = far; + camera.updateProjectionMatrix(); - if ( isWebGL2 ) return gl.UNSIGNED_INT_24_8; + } - extension = extensions.get( 'WEBGL_depth_texture' ); + super.updateMatrices( light ); - if ( extension !== null ) { + } - return extension.UNSIGNED_INT_24_8_WEBGL; + copy( source ) { - } else { + super.copy( source ); - return null; + this.focus = source.focus; - } + return this; - } + } - // if "p" can't be resolved, assume the user defines a WebGL constant as a string (fallback/workaround for packed RGB formats) +} - return ( gl[ p ] !== undefined ) ? gl[ p ] : null; +/** + * This light gets emitted from a single point in one direction, along a cone + * that increases in size the further from the light it gets. + * + * This light can cast shadows - see the {@link SpotLightShadow} for details. + * + * ```js + * // white spotlight shining from the side, modulated by a texture + * const spotLight = new THREE.SpotLight( 0xffffff ); + * spotLight.position.set( 100, 1000, 100 ); + * spotLight.map = new THREE.TextureLoader().load( url ); + * + * spotLight.castShadow = true; + * spotLight.shadow.mapSize.width = 1024; + * spotLight.shadow.mapSize.height = 1024; + * spotLight.shadow.camera.near = 500; + * spotLight.shadow.camera.far = 4000; + * spotLight.shadow.camera.fov = 30;s + * ``` + * + * @augments Light + */ +class SpotLight extends Light { - } + /** + * Constructs a new spot light. + * + * @param {(number|Color|string)} [color=0xffffff] - The light's color. + * @param {number} [intensity=1] - The light's strength/intensity measured in candela (cd). + * @param {number} [distance=0] - Maximum range of the light. `0` means no limit. + * @param {number} [angle=Math.PI/3] - Maximum angle of light dispersion from its direction whose upper bound is `Math.PI/2`. + * @param {number} [penumbra=0] - Percent of the spotlight cone that is attenuated due to penumbra. Value range is `[0,1]`. + * @param {number} [decay=2] - The amount the light dims along the distance of the light. + */ + constructor( color, intensity, distance = 0, angle = Math.PI / 3, penumbra = 0, decay = 2 ) { - return { convert: convert }; + super( color, intensity ); -} + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSpotLight = true; -class ArrayCamera extends PerspectiveCamera { + this.type = 'SpotLight'; - constructor( array = [] ) { + this.position.copy( Object3D.DEFAULT_UP ); + this.updateMatrix(); - super(); + /** + * The spot light points from its position to the + * target's position. + * + * For the target's position to be changed to anything other + * than the default, it must be added to the scene. + * + * It is also possible to set the target to be another 3D object + * in the scene. The light will now track the target object. + * + * @type {Object3D} + */ + this.target = new Object3D(); - this.isArrayCamera = true; + /** + * Maximum range of the light. `0` means no limit. + * + * @type {number} + * @default 0 + */ + this.distance = distance; - this.cameras = array; + /** + * Maximum angle of light dispersion from its direction whose upper bound is `Math.PI/2`. + * + * @type {number} + * @default Math.PI/3 + */ + this.angle = angle; - } + /** + * Percent of the spotlight cone that is attenuated due to penumbra. + * Value range is `[0,1]`. + * + * @type {number} + * @default 0 + */ + this.penumbra = penumbra; -} + /** + * The amount the light dims along the distance of the light. In context of + * physically-correct rendering the default value should not be changed. + * + * @type {number} + * @default 2 + */ + this.decay = decay; -class Group extends Object3D { + /** + * A texture used to modulate the color of the light. The spot light + * color is mixed with the RGB value of this texture, with a ratio + * corresponding to its alpha value. The cookie-like masking effect is + * reproduced using pixel values (0, 0, 0, 1-cookie_value). + * + * *Warning*: This property is disabled if {@link Object3D#castShadow} is set to `false`. + * + * @type {?Texture} + * @default null + */ + this.map = null; - constructor() { + /** + * This property holds the light's shadow configuration. + * + * @type {SpotLightShadow} + */ + this.shadow = new SpotLightShadow(); - super(); + } - this.isGroup = true; + /** + * The light's power. Power is the luminous power of the light measured in lumens (lm). + * Changing the power will also change the light's intensity. + * + * @type {number} + */ + get power() { - this.type = 'Group'; + // compute the light's luminous power (in lumens) from its intensity (in candela) + // by convention for a spotlight, luminous power (lm) = π * luminous intensity (cd) + return this.intensity * Math.PI; } -} + set power( power ) { -const _moveEvent = { type: 'move' }; + // set the light's intensity (in candela) from the desired luminous power (in lumens) + this.intensity = power / Math.PI; -class WebXRController { + } - constructor() { + dispose() { - this._targetRay = null; - this._grip = null; - this._hand = null; + this.shadow.dispose(); } - getHandSpace() { + copy( source, recursive ) { - if ( this._hand === null ) { + super.copy( source, recursive ); - this._hand = new Group(); - this._hand.matrixAutoUpdate = false; - this._hand.visible = false; + this.distance = source.distance; + this.angle = source.angle; + this.penumbra = source.penumbra; + this.decay = source.decay; - this._hand.joints = {}; - this._hand.inputState = { pinching: false }; + this.target = source.target.clone(); - } + this.shadow = source.shadow.clone(); - return this._hand; + return this; } - getTargetRaySpace() { +} - if ( this._targetRay === null ) { +const _projScreenMatrix = /*@__PURE__*/ new Matrix4(); +const _lightPositionWorld = /*@__PURE__*/ new Vector3(); +const _lookTarget = /*@__PURE__*/ new Vector3(); - this._targetRay = new Group(); - this._targetRay.matrixAutoUpdate = false; - this._targetRay.visible = false; - this._targetRay.hasLinearVelocity = false; - this._targetRay.linearVelocity = new Vector3(); - this._targetRay.hasAngularVelocity = false; - this._targetRay.angularVelocity = new Vector3(); +/** + * Represents the shadow configuration of point lights. + * + * @augments LightShadow + */ +class PointLightShadow extends LightShadow { - } + /** + * Constructs a new point light shadow. + */ + constructor() { - return this._targetRay; + super( new PerspectiveCamera( 90, 1, 0.5, 500 ) ); - } + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isPointLightShadow = true; - getGripSpace() { + this._frameExtents = new Vector2( 4, 2 ); - if ( this._grip === null ) { + this._viewportCount = 6; - this._grip = new Group(); - this._grip.matrixAutoUpdate = false; - this._grip.visible = false; - this._grip.hasLinearVelocity = false; - this._grip.linearVelocity = new Vector3(); - this._grip.hasAngularVelocity = false; - this._grip.angularVelocity = new Vector3(); + this._viewports = [ + // These viewports map a cube-map onto a 2D texture with the + // following orientation: + // + // xzXZ + // y Y + // + // X - Positive x direction + // x - Negative x direction + // Y - Positive y direction + // y - Negative y direction + // Z - Positive z direction + // z - Negative z direction - } + // positive X + new Vector4( 2, 1, 1, 1 ), + // negative X + new Vector4( 0, 1, 1, 1 ), + // positive Z + new Vector4( 3, 1, 1, 1 ), + // negative Z + new Vector4( 1, 1, 1, 1 ), + // positive Y + new Vector4( 3, 0, 1, 1 ), + // negative Y + new Vector4( 1, 0, 1, 1 ) + ]; - return this._grip; + this._cubeDirections = [ + new Vector3( 1, 0, 0 ), new Vector3( -1, 0, 0 ), new Vector3( 0, 0, 1 ), + new Vector3( 0, 0, -1 ), new Vector3( 0, 1, 0 ), new Vector3( 0, -1, 0 ) + ]; - } + this._cubeUps = [ + new Vector3( 0, 1, 0 ), new Vector3( 0, 1, 0 ), new Vector3( 0, 1, 0 ), + new Vector3( 0, 1, 0 ), new Vector3( 0, 0, 1 ), new Vector3( 0, 0, -1 ) + ]; - dispatchEvent( event ) { + } - if ( this._targetRay !== null ) { + /** + * Update the matrices for the camera and shadow, used internally by the renderer. + * + * @param {Light} light - The light for which the shadow is being rendered. + * @param {number} [viewportIndex=0] - The viewport index. + */ + updateMatrices( light, viewportIndex = 0 ) { - this._targetRay.dispatchEvent( event ); + const camera = this.camera; + const shadowMatrix = this.matrix; - } + const far = light.distance || camera.far; - if ( this._grip !== null ) { + if ( far !== camera.far ) { - this._grip.dispatchEvent( event ); + camera.far = far; + camera.updateProjectionMatrix(); } - if ( this._hand !== null ) { + _lightPositionWorld.setFromMatrixPosition( light.matrixWorld ); + camera.position.copy( _lightPositionWorld ); - this._hand.dispatchEvent( event ); + _lookTarget.copy( camera.position ); + _lookTarget.add( this._cubeDirections[ viewportIndex ] ); + camera.up.copy( this._cubeUps[ viewportIndex ] ); + camera.lookAt( _lookTarget ); + camera.updateMatrixWorld(); - } + shadowMatrix.makeTranslation( - _lightPositionWorld.x, - _lightPositionWorld.y, - _lightPositionWorld.z ); - return this; + _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); + this._frustum.setFromProjectionMatrix( _projScreenMatrix ); } - connect( inputSource ) { +} - if ( inputSource && inputSource.hand ) { +/** + * A light that gets emitted from a single point in all directions. A common + * use case for this is to replicate the light emitted from a bare + * lightbulb. + * + * This light can cast shadows - see the {@link PointLightShadow} for details. + * + * ```js + * const light = new THREE.PointLight( 0xff0000, 1, 100 ); + * light.position.set( 50, 50, 50 ); + * scene.add( light ); + * ``` + * + * @augments Light + */ +class PointLight extends Light { - const hand = this._hand; + /** + * Constructs a new point light. + * + * @param {(number|Color|string)} [color=0xffffff] - The light's color. + * @param {number} [intensity=1] - The light's strength/intensity measured in candela (cd). + * @param {number} [distance=0] - Maximum range of the light. `0` means no limit. + * @param {number} [decay=2] - The amount the light dims along the distance of the light. + */ + constructor( color, intensity, distance = 0, decay = 2 ) { - if ( hand ) { + super( color, intensity ); - for ( const inputjoint of inputSource.hand.values() ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isPointLight = true; - // Initialize hand with joints when connected - this._getHandJoint( hand, inputjoint ); + this.type = 'PointLight'; - } + /** + * When distance is zero, light will attenuate according to inverse-square + * law to infinite distance. When distance is non-zero, light will attenuate + * according to inverse-square law until near the distance cutoff, where it + * will then attenuate quickly and smoothly to 0. Inherently, cutoffs are not + * physically correct. + * + * @type {number} + * @default 0 + */ + this.distance = distance; - } + /** + * The amount the light dims along the distance of the light. In context of + * physically-correct rendering the default value should not be changed. + * + * @type {number} + * @default 2 + */ + this.decay = decay; - } + /** + * This property holds the light's shadow configuration. + * + * @type {PointLightShadow} + */ + this.shadow = new PointLightShadow(); - this.dispatchEvent( { type: 'connected', data: inputSource } ); + } - return this; + /** + * The light's power. Power is the luminous power of the light measured in lumens (lm). + * Changing the power will also change the light's intensity. + * + * @type {number} + */ + get power() { - } + // compute the light's luminous power (in lumens) from its intensity (in candela) + // for an isotropic light source, luminous power (lm) = 4 π luminous intensity (cd) + return this.intensity * 4 * Math.PI; - disconnect( inputSource ) { + } - this.dispatchEvent( { type: 'disconnected', data: inputSource } ); + set power( power ) { - if ( this._targetRay !== null ) { + // set the light's intensity (in candela) from the desired luminous power (in lumens) + this.intensity = power / ( 4 * Math.PI ); - this._targetRay.visible = false; + } - } + dispose() { - if ( this._grip !== null ) { + this.shadow.dispose(); - this._grip.visible = false; + } - } + copy( source, recursive ) { - if ( this._hand !== null ) { + super.copy( source, recursive ); - this._hand.visible = false; + this.distance = source.distance; + this.decay = source.decay; - } + this.shadow = source.shadow.clone(); return this; } - update( inputSource, frame, referenceSpace ) { +} - let inputPose = null; - let gripPose = null; - let handPose = null; +/** + * Camera that uses [orthographic projection]{@link https://en.wikipedia.org/wiki/Orthographic_projection}. + * + * In this projection mode, an object's size in the rendered image stays + * constant regardless of its distance from the camera. This can be useful + * for rendering 2D scenes and UI elements, amongst other things. + * + * ```js + * const camera = new THREE.OrthographicCamera( width / - 2, width / 2, height / 2, height / - 2, 1, 1000 ); + * scene.add( camera ); + * ``` + * + * @augments Camera + */ +class OrthographicCamera extends Camera { - const targetRay = this._targetRay; - const grip = this._grip; - const hand = this._hand; + /** + * Constructs a new orthographic camera. + * + * @param {number} [left=-1] - The left plane of the camera's frustum. + * @param {number} [right=1] - The right plane of the camera's frustum. + * @param {number} [top=1] - The top plane of the camera's frustum. + * @param {number} [bottom=-1] - The bottom plane of the camera's frustum. + * @param {number} [near=0.1] - The camera's near plane. + * @param {number} [far=2000] - The camera's far plane. + */ + constructor( left = -1, right = 1, top = 1, bottom = -1, near = 0.1, far = 2000 ) { - if ( inputSource && frame.session.visibilityState !== 'visible-blurred' ) { + super(); - if ( hand && inputSource.hand ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isOrthographicCamera = true; - handPose = true; + this.type = 'OrthographicCamera'; - for ( const inputjoint of inputSource.hand.values() ) { + /** + * The zoom factor of the camera. + * + * @type {number} + * @default 1 + */ + this.zoom = 1; - // Update the joints groups with the XRJoint poses - const jointPose = frame.getJointPose( inputjoint, referenceSpace ); + /** + * Represents the frustum window specification. This property should not be edited + * directly but via {@link PerspectiveCamera#setViewOffset} and {@link PerspectiveCamera#clearViewOffset}. + * + * @type {?Object} + * @default null + */ + this.view = null; - // The transform of this joint will be updated with the joint pose on each frame - const joint = this._getHandJoint( hand, inputjoint ); + /** + * The left plane of the camera's frustum. + * + * @type {number} + * @default -1 + */ + this.left = left; - if ( jointPose !== null ) { + /** + * The right plane of the camera's frustum. + * + * @type {number} + * @default 1 + */ + this.right = right; - joint.matrix.fromArray( jointPose.transform.matrix ); - joint.matrix.decompose( joint.position, joint.rotation, joint.scale ); - joint.matrixWorldNeedsUpdate = true; - joint.jointRadius = jointPose.radius; + /** + * The top plane of the camera's frustum. + * + * @type {number} + * @default 1 + */ + this.top = top; - } + /** + * The bottom plane of the camera's frustum. + * + * @type {number} + * @default -1 + */ + this.bottom = bottom; - joint.visible = jointPose !== null; + /** + * The camera's near plane. The valid range is greater than `0` + * and less than the current value of {@link OrthographicCamera#far}. + * + * Note that, unlike for the {@link PerspectiveCamera}, `0` is a + * valid value for an orthographic camera's near plane. + * + * @type {number} + * @default 0.1 + */ + this.near = near; - } + /** + * The camera's far plane. Must be greater than the + * current value of {@link OrthographicCamera#near}. + * + * @type {number} + * @default 2000 + */ + this.far = far; - // Custom events + this.updateProjectionMatrix(); - // Check pinchz - const indexTip = hand.joints[ 'index-finger-tip' ]; - const thumbTip = hand.joints[ 'thumb-tip' ]; - const distance = indexTip.position.distanceTo( thumbTip.position ); + } - const distanceToPinch = 0.02; - const threshold = 0.005; + copy( source, recursive ) { - if ( hand.inputState.pinching && distance > distanceToPinch + threshold ) { + super.copy( source, recursive ); - hand.inputState.pinching = false; - this.dispatchEvent( { - type: 'pinchend', - handedness: inputSource.handedness, - target: this - } ); + this.left = source.left; + this.right = source.right; + this.top = source.top; + this.bottom = source.bottom; + this.near = source.near; + this.far = source.far; - } else if ( ! hand.inputState.pinching && distance <= distanceToPinch - threshold ) { + this.zoom = source.zoom; + this.view = source.view === null ? null : Object.assign( {}, source.view ); - hand.inputState.pinching = true; - this.dispatchEvent( { - type: 'pinchstart', - handedness: inputSource.handedness, - target: this - } ); + return this; - } + } - } else { + /** + * Sets an offset in a larger frustum. This is useful for multi-window or + * multi-monitor/multi-machine setups. + * + * @param {number} fullWidth - The full width of multiview setup. + * @param {number} fullHeight - The full height of multiview setup. + * @param {number} x - The horizontal offset of the subcamera. + * @param {number} y - The vertical offset of the subcamera. + * @param {number} width - The width of subcamera. + * @param {number} height - The height of subcamera. + * @see {@link PerspectiveCamera#setViewOffset} + */ + setViewOffset( fullWidth, fullHeight, x, y, width, height ) { - if ( grip !== null && inputSource.gripSpace ) { + if ( this.view === null ) { - gripPose = frame.getPose( inputSource.gripSpace, referenceSpace ); + this.view = { + enabled: true, + fullWidth: 1, + fullHeight: 1, + offsetX: 0, + offsetY: 0, + width: 1, + height: 1 + }; - if ( gripPose !== null ) { + } - grip.matrix.fromArray( gripPose.transform.matrix ); - grip.matrix.decompose( grip.position, grip.rotation, grip.scale ); - grip.matrixWorldNeedsUpdate = true; + this.view.enabled = true; + this.view.fullWidth = fullWidth; + this.view.fullHeight = fullHeight; + this.view.offsetX = x; + this.view.offsetY = y; + this.view.width = width; + this.view.height = height; - if ( gripPose.linearVelocity ) { + this.updateProjectionMatrix(); - grip.hasLinearVelocity = true; - grip.linearVelocity.copy( gripPose.linearVelocity ); + } - } else { + /** + * Removes the view offset from the projection matrix. + */ + clearViewOffset() { - grip.hasLinearVelocity = false; + if ( this.view !== null ) { - } + this.view.enabled = false; - if ( gripPose.angularVelocity ) { + } - grip.hasAngularVelocity = true; - grip.angularVelocity.copy( gripPose.angularVelocity ); + this.updateProjectionMatrix(); - } else { + } - grip.hasAngularVelocity = false; + /** + * Updates the camera's projection matrix. Must be called after any change of + * camera properties. + */ + updateProjectionMatrix() { - } + const dx = ( this.right - this.left ) / ( 2 * this.zoom ); + const dy = ( this.top - this.bottom ) / ( 2 * this.zoom ); + const cx = ( this.right + this.left ) / 2; + const cy = ( this.top + this.bottom ) / 2; - } + let left = cx - dx; + let right = cx + dx; + let top = cy + dy; + let bottom = cy - dy; - } + if ( this.view !== null && this.view.enabled ) { - } + const scaleW = ( this.right - this.left ) / this.view.fullWidth / this.zoom; + const scaleH = ( this.top - this.bottom ) / this.view.fullHeight / this.zoom; - if ( targetRay !== null ) { + left += scaleW * this.view.offsetX; + right = left + scaleW * this.view.width; + top -= scaleH * this.view.offsetY; + bottom = top - scaleH * this.view.height; - inputPose = frame.getPose( inputSource.targetRaySpace, referenceSpace ); + } - // Some runtimes (namely Vive Cosmos with Vive OpenXR Runtime) have only grip space and ray space is equal to it - if ( inputPose === null && gripPose !== null ) { + this.projectionMatrix.makeOrthographic( left, right, top, bottom, this.near, this.far, this.coordinateSystem ); - inputPose = gripPose; + this.projectionMatrixInverse.copy( this.projectionMatrix ).invert(); - } + } - if ( inputPose !== null ) { + toJSON( meta ) { - targetRay.matrix.fromArray( inputPose.transform.matrix ); - targetRay.matrix.decompose( targetRay.position, targetRay.rotation, targetRay.scale ); - targetRay.matrixWorldNeedsUpdate = true; + const data = super.toJSON( meta ); - if ( inputPose.linearVelocity ) { + data.object.zoom = this.zoom; + data.object.left = this.left; + data.object.right = this.right; + data.object.top = this.top; + data.object.bottom = this.bottom; + data.object.near = this.near; + data.object.far = this.far; - targetRay.hasLinearVelocity = true; - targetRay.linearVelocity.copy( inputPose.linearVelocity ); + if ( this.view !== null ) data.object.view = Object.assign( {}, this.view ); - } else { + return data; - targetRay.hasLinearVelocity = false; + } - } +} - if ( inputPose.angularVelocity ) { +/** + * Represents the shadow configuration of directional lights. + * + * @augments LightShadow + */ +class DirectionalLightShadow extends LightShadow { - targetRay.hasAngularVelocity = true; - targetRay.angularVelocity.copy( inputPose.angularVelocity ); + /** + * Constructs a new directional light shadow. + */ + constructor() { - } else { + super( new OrthographicCamera( -5, 5, 5, -5, 0.5, 500 ) ); - targetRay.hasAngularVelocity = false; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isDirectionalLightShadow = true; - } + } - this.dispatchEvent( _moveEvent ); +} + +/** + * A light that gets emitted in a specific direction. This light will behave + * as though it is infinitely far away and the rays produced from it are all + * parallel. The common use case for this is to simulate daylight; the sun is + * far enough away that its position can be considered to be infinite, and + * all light rays coming from it are parallel. + * + * A common point of confusion for directional lights is that setting the + * rotation has no effect. This is because three.js's DirectionalLight is the + * equivalent to what is often called a 'Target Direct Light' in other + * applications. + * + * This means that its direction is calculated as pointing from the light's + * {@link Object3D#position} to the {@link DirectionalLight#target} position + * (as opposed to a 'Free Direct Light' that just has a rotation + * component). + * + * This light can cast shadows - see the {@link DirectionalLightShadow} for details. + * + * ```js + * // White directional light at half intensity shining from the top. + * const directionalLight = new THREE.DirectionalLight( 0xffffff, 0.5 ); + * scene.add( directionalLight ); + * ``` + * + * @augments Light + */ +class DirectionalLight extends Light { + + /** + * Constructs a new directional light. + * + * @param {(number|Color|string)} [color=0xffffff] - The light's color. + * @param {number} [intensity=1] - The light's strength/intensity. + */ + constructor( color, intensity ) { - } + super( color, intensity ); - } + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isDirectionalLight = true; + this.type = 'DirectionalLight'; - } + this.position.copy( Object3D.DEFAULT_UP ); + this.updateMatrix(); - if ( targetRay !== null ) { + /** + * The directional light points from its position to the + * target's position. + * + * For the target's position to be changed to anything other + * than the default, it must be added to the scene. + * + * It is also possible to set the target to be another 3D object + * in the scene. The light will now track the target object. + * + * @type {Object3D} + */ + this.target = new Object3D(); - targetRay.visible = ( inputPose !== null ); + /** + * This property holds the light's shadow configuration. + * + * @type {DirectionalLightShadow} + */ + this.shadow = new DirectionalLightShadow(); - } + } - if ( grip !== null ) { + dispose() { - grip.visible = ( gripPose !== null ); + this.shadow.dispose(); - } + } - if ( hand !== null ) { + copy( source ) { - hand.visible = ( handPose !== null ); + super.copy( source ); - } + this.target = source.target.clone(); + this.shadow = source.shadow.clone(); return this; } - // private method - - _getHandJoint( hand, inputjoint ) { +} - if ( hand.joints[ inputjoint.jointName ] === undefined ) { +/** + * This light globally illuminates all objects in the scene equally. + * + * It cannot be used to cast shadows as it does not have a direction. + * + * ```js + * const light = new THREE.AmbientLight( 0x404040 ); // soft white light + * scene.add( light ); + * ``` + * + * @augments Light + */ +class AmbientLight extends Light { - const joint = new Group(); - joint.matrixAutoUpdate = false; - joint.visible = false; - hand.joints[ inputjoint.jointName ] = joint; + /** + * Constructs a new ambient light. + * + * @param {(number|Color|string)} [color=0xffffff] - The light's color. + * @param {number} [intensity=1] - The light's strength/intensity. + */ + constructor( color, intensity ) { - hand.add( joint ); + super( color, intensity ); - } + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isAmbientLight = true; - return hand.joints[ inputjoint.jointName ]; + this.type = 'AmbientLight'; } } -class DepthTexture extends Texture { +/** + * This class emits light uniformly across the face a rectangular plane. + * This light type can be used to simulate light sources such as bright + * windows or strip lighting. + * + * Important Notes: + * + * - There is no shadow support. + * - Only PBR materials are supported. + * - You have to include `RectAreaLightUniformsLib` (`WebGLRenderer`) or `RectAreaLightTexturesLib` (`WebGPURenderer`) + * into your app and init the uniforms/textures. + * + * ```js + * RectAreaLightUniformsLib.init(); // only relevant for WebGLRenderer + * THREE.RectAreaLightNode.setLTC( RectAreaLightTexturesLib.init() ); // only relevant for WebGPURenderer + * + * const intensity = 1; const width = 10; const height = 10; + * const rectLight = new THREE.RectAreaLight( 0xffffff, intensity, width, height ); + * rectLight.position.set( 5, 5, 0 ); + * rectLight.lookAt( 0, 0, 0 ); + * scene.add( rectLight ) + * ``` + * + * @augments Light + */ +class RectAreaLight extends Light { - constructor( width, height, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, format ) { + /** + * Constructs a new area light. + * + * @param {(number|Color|string)} [color=0xffffff] - The light's color. + * @param {number} [intensity=1] - The light's strength/intensity. + * @param {number} [width=10] - The width of the light. + * @param {number} [height=10] - The height of the light. + */ + constructor( color, intensity, width = 10, height = 10 ) { - format = format !== undefined ? format : DepthFormat; + super( color, intensity ); - if ( format !== DepthFormat && format !== DepthStencilFormat ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isRectAreaLight = true; - throw new Error( 'DepthTexture format must be either THREE.DepthFormat or THREE.DepthStencilFormat' ); + this.type = 'RectAreaLight'; - } + /** + * The width of the light. + * + * @type {number} + * @default 10 + */ + this.width = width; - if ( type === undefined && format === DepthFormat ) type = UnsignedIntType; - if ( type === undefined && format === DepthStencilFormat ) type = UnsignedInt248Type; + /** + * The height of the light. + * + * @type {number} + * @default 10 + */ + this.height = height; - super( null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); + } - this.isDepthTexture = true; + /** + * The light's power. Power is the luminous power of the light measured in lumens (lm). + * Changing the power will also change the light's intensity. + * + * @type {number} + */ + get power() { - this.image = { width: width, height: height }; + // compute the light's luminous power (in lumens) from its intensity (in nits) + return this.intensity * this.width * this.height * Math.PI; - this.magFilter = magFilter !== undefined ? magFilter : NearestFilter; - this.minFilter = minFilter !== undefined ? minFilter : NearestFilter; + } - this.flipY = false; - this.generateMipmaps = false; + set power( power ) { - this.compareFunction = null; + // set the light's intensity (in nits) from the desired luminous power (in lumens) + this.intensity = power / ( this.width * this.height * Math.PI ); } - copy( source ) { super.copy( source ); - this.compareFunction = source.compareFunction; + this.width = source.width; + this.height = source.height; return this; @@ -26047,7 +46207,8 @@ class DepthTexture extends Texture { const data = super.toJSON( meta ); - if ( this.compareFunction !== null ) data.compareFunction = this.compareFunction; + data.object.width = this.width; + data.object.height = this.height; return data; @@ -26055,427 +46216,668 @@ class DepthTexture extends Texture { } -class WebXRManager extends EventDispatcher { +/** + * Represents a third-order spherical harmonics (SH). Light probes use this class + * to encode lighting information. + * + * - Primary reference: {@link https://graphics.stanford.edu/papers/envmap/envmap.pdf} + * - Secondary reference: {@link https://www.ppsloan.org/publications/StupidSH36.pdf} + */ +class SphericalHarmonics3 { - constructor( renderer, gl ) { + /** + * Constructs a new spherical harmonics. + */ + constructor() { - super(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSphericalHarmonics3 = true; - const scope = this; + /** + * An array holding the (9) SH coefficients. + * + * @type {Array} + */ + this.coefficients = []; - let session = null; + for ( let i = 0; i < 9; i ++ ) { - let framebufferScaleFactor = 1.0; + this.coefficients.push( new Vector3() ); - let referenceSpace = null; - let referenceSpaceType = 'local-floor'; - // Set default foveation to maximum. - let foveation = 1.0; - let customReferenceSpace = null; + } - let pose = null; - let glBinding = null; - let glProjLayer = null; - let glBaseLayer = null; - let xrFrame = null; - const attributes = gl.getContextAttributes(); - let initialRenderTarget = null; - let newRenderTarget = null; + } - const controllers = []; - const controllerInputSources = []; + /** + * Sets the given SH coefficients to this instance by copying + * the values. + * + * @param {Array} coefficients - The SH coefficients. + * @return {SphericalHarmonics3} A reference to this spherical harmonics. + */ + set( coefficients ) { - const planes = new Set(); - const planesLastChangedTimes = new Map(); + for ( let i = 0; i < 9; i ++ ) { - // + this.coefficients[ i ].copy( coefficients[ i ] ); - let userCamera = null; + } - const cameraL = new PerspectiveCamera(); - cameraL.layers.enable( 1 ); - cameraL.viewport = new Vector4(); + return this; - const cameraR = new PerspectiveCamera(); - cameraR.layers.enable( 2 ); - cameraR.viewport = new Vector4(); + } - const cameras = [ cameraL, cameraR ]; + /** + * Sets all SH coefficients to `0`. + * + * @return {SphericalHarmonics3} A reference to this spherical harmonics. + */ + zero() { - const cameraXR = new ArrayCamera(); - cameraXR.layers.enable( 1 ); - cameraXR.layers.enable( 2 ); + for ( let i = 0; i < 9; i ++ ) { - let _currentDepthNear = null; - let _currentDepthFar = null; + this.coefficients[ i ].set( 0, 0, 0 ); - // + } - this.cameraAutoUpdate = true; // @deprecated, r153 - this.enabled = false; + return this; - this.isPresenting = false; + } - this.getCamera = function () {}; // @deprecated, r153 + /** + * Returns the radiance in the direction of the given normal. + * + * @param {Vector3} normal - The normal vector (assumed to be unit length) + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The radiance. + */ + getAt( normal, target ) { - this.setUserCamera = function ( value ) { + // normal is assumed to be unit length - userCamera = value; + const x = normal.x, y = normal.y, z = normal.z; - }; + const coeff = this.coefficients; - this.getController = function ( index ) { + // band 0 + target.copy( coeff[ 0 ] ).multiplyScalar( 0.282095 ); - let controller = controllers[ index ]; + // band 1 + target.addScaledVector( coeff[ 1 ], 0.488603 * y ); + target.addScaledVector( coeff[ 2 ], 0.488603 * z ); + target.addScaledVector( coeff[ 3 ], 0.488603 * x ); - if ( controller === undefined ) { + // band 2 + target.addScaledVector( coeff[ 4 ], 1.092548 * ( x * y ) ); + target.addScaledVector( coeff[ 5 ], 1.092548 * ( y * z ) ); + target.addScaledVector( coeff[ 6 ], 0.315392 * ( 3.0 * z * z - 1.0 ) ); + target.addScaledVector( coeff[ 7 ], 1.092548 * ( x * z ) ); + target.addScaledVector( coeff[ 8 ], 0.546274 * ( x * x - y * y ) ); - controller = new WebXRController(); - controllers[ index ] = controller; + return target; - } + } - return controller.getTargetRaySpace(); + /** + * Returns the irradiance (radiance convolved with cosine lobe) in the + * direction of the given normal. + * + * @param {Vector3} normal - The normal vector (assumed to be unit length) + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The irradiance. + */ + getIrradianceAt( normal, target ) { - }; + // normal is assumed to be unit length - this.getControllerGrip = function ( index ) { + const x = normal.x, y = normal.y, z = normal.z; - let controller = controllers[ index ]; + const coeff = this.coefficients; - if ( controller === undefined ) { + // band 0 + target.copy( coeff[ 0 ] ).multiplyScalar( 0.886227 ); // π * 0.282095 - controller = new WebXRController(); - controllers[ index ] = controller; + // band 1 + target.addScaledVector( coeff[ 1 ], 2.0 * 0.511664 * y ); // ( 2 * π / 3 ) * 0.488603 + target.addScaledVector( coeff[ 2 ], 2.0 * 0.511664 * z ); + target.addScaledVector( coeff[ 3 ], 2.0 * 0.511664 * x ); - } + // band 2 + target.addScaledVector( coeff[ 4 ], 2.0 * 0.429043 * x * y ); // ( π / 4 ) * 1.092548 + target.addScaledVector( coeff[ 5 ], 2.0 * 0.429043 * y * z ); + target.addScaledVector( coeff[ 6 ], 0.743125 * z * z - 0.247708 ); // ( π / 4 ) * 0.315392 * 3 + target.addScaledVector( coeff[ 7 ], 2.0 * 0.429043 * x * z ); + target.addScaledVector( coeff[ 8 ], 0.429043 * ( x * x - y * y ) ); // ( π / 4 ) * 0.546274 - return controller.getGripSpace(); + return target; - }; + } - this.getHand = function ( index ) { + /** + * Adds the given SH to this instance. + * + * @param {SphericalHarmonics3} sh - The SH to add. + * @return {SphericalHarmonics3} A reference to this spherical harmonics. + */ + add( sh ) { - let controller = controllers[ index ]; + for ( let i = 0; i < 9; i ++ ) { - if ( controller === undefined ) { + this.coefficients[ i ].add( sh.coefficients[ i ] ); - controller = new WebXRController(); - controllers[ index ] = controller; + } - } + return this; - return controller.getHandSpace(); + } - }; + /** + * A convenience method for performing {@link SphericalHarmonics3#add} and + * {@link SphericalHarmonics3#scale} at once. + * + * @param {SphericalHarmonics3} sh - The SH to add. + * @param {number} s - The scale factor. + * @return {SphericalHarmonics3} A reference to this spherical harmonics. + */ + addScaledSH( sh, s ) { - // + for ( let i = 0; i < 9; i ++ ) { - function onSessionEvent( event ) { + this.coefficients[ i ].addScaledVector( sh.coefficients[ i ], s ); - const controllerIndex = controllerInputSources.indexOf( event.inputSource ); + } - if ( controllerIndex === - 1 ) { + return this; - return; + } - } + /** + * Scales this SH by the given scale factor. + * + * @param {number} s - The scale factor. + * @return {SphericalHarmonics3} A reference to this spherical harmonics. + */ + scale( s ) { - const controller = controllers[ controllerIndex ]; + for ( let i = 0; i < 9; i ++ ) { - if ( controller !== undefined ) { + this.coefficients[ i ].multiplyScalar( s ); - controller.update( event.inputSource, event.frame, customReferenceSpace || referenceSpace ); - controller.dispatchEvent( { type: event.type, data: event.inputSource } ); + } - } + return this; - } + } - function onSessionEnd() { + /** + * Linear interpolates between the given SH and this instance by the given + * alpha factor. + * + * @param {SphericalHarmonics3} sh - The SH to interpolate with. + * @param {number} alpha - The alpha factor. + * @return {SphericalHarmonics3} A reference to this spherical harmonics. + */ + lerp( sh, alpha ) { - session.removeEventListener( 'select', onSessionEvent ); - session.removeEventListener( 'selectstart', onSessionEvent ); - session.removeEventListener( 'selectend', onSessionEvent ); - session.removeEventListener( 'squeeze', onSessionEvent ); - session.removeEventListener( 'squeezestart', onSessionEvent ); - session.removeEventListener( 'squeezeend', onSessionEvent ); - session.removeEventListener( 'end', onSessionEnd ); - session.removeEventListener( 'inputsourceschange', onInputSourcesChange ); + for ( let i = 0; i < 9; i ++ ) { - for ( let i = 0; i < controllers.length; i ++ ) { + this.coefficients[ i ].lerp( sh.coefficients[ i ], alpha ); - const inputSource = controllerInputSources[ i ]; + } - if ( inputSource === null ) continue; + return this; - controllerInputSources[ i ] = null; + } - controllers[ i ].disconnect( inputSource ); + /** + * Returns `true` if this spherical harmonics is equal with the given one. + * + * @param {SphericalHarmonics3} sh - The spherical harmonics to test for equality. + * @return {boolean} Whether this spherical harmonics is equal with the given one. + */ + equals( sh ) { + + for ( let i = 0; i < 9; i ++ ) { + + if ( ! this.coefficients[ i ].equals( sh.coefficients[ i ] ) ) { + + return false; } - _currentDepthNear = null; - _currentDepthFar = null; + } - // restore framebuffer/rendering state + return true; - renderer.setRenderTarget( initialRenderTarget ); + } - glBaseLayer = null; - glProjLayer = null; - glBinding = null; - session = null; - newRenderTarget = null; + /** + * Copies the values of the given spherical harmonics to this instance. + * + * @param {SphericalHarmonics3} sh - The spherical harmonics to copy. + * @return {SphericalHarmonics3} A reference to this spherical harmonics. + */ + copy( sh ) { - // + return this.set( sh.coefficients ); - animation.stop(); + } - scope.isPresenting = false; + /** + * Returns a new spherical harmonics with copied values from this instance. + * + * @return {SphericalHarmonics3} A clone of this instance. + */ + clone() { - scope.dispatchEvent( { type: 'sessionend' } ); + return new this.constructor().copy( this ); + + } + + /** + * Sets the SH coefficients of this instance from the given array. + * + * @param {Array} array - An array holding the SH coefficients. + * @param {number} [offset=0] - The array offset where to start copying. + * @return {SphericalHarmonics3} A clone of this instance. + */ + fromArray( array, offset = 0 ) { + + const coefficients = this.coefficients; + + for ( let i = 0; i < 9; i ++ ) { + + coefficients[ i ].fromArray( array, offset + ( i * 3 ) ); } - this.setFramebufferScaleFactor = function ( value ) { + return this; - framebufferScaleFactor = value; + } - if ( scope.isPresenting === true ) { + /** + * Returns an array with the SH coefficients, or copies them into the provided + * array. The coefficients are represented as numbers. + * + * @param {Array} [array=[]] - The target array. + * @param {number} [offset=0] - The array offset where to start copying. + * @return {Array} An array with flat SH coefficients. + */ + toArray( array = [], offset = 0 ) { - console.warn( 'THREE.WebXRManager: Cannot change framebuffer scale while presenting.' ); + const coefficients = this.coefficients; - } + for ( let i = 0; i < 9; i ++ ) { - }; + coefficients[ i ].toArray( array, offset + ( i * 3 ) ); - this.setReferenceSpaceType = function ( value ) { + } - referenceSpaceType = value; + return array; - if ( scope.isPresenting === true ) { + } - console.warn( 'THREE.WebXRManager: Cannot change reference space type while presenting.' ); + /** + * Computes the SH basis for the given normal vector. + * + * @param {Vector3} normal - The normal. + * @param {Array} shBasis - The target array holding the SH basis. + */ + static getBasisAt( normal, shBasis ) { - } + // normal is assumed to be unit length - }; + const x = normal.x, y = normal.y, z = normal.z; - this.getReferenceSpace = function () { + // band 0 + shBasis[ 0 ] = 0.282095; - return customReferenceSpace || referenceSpace; + // band 1 + shBasis[ 1 ] = 0.488603 * y; + shBasis[ 2 ] = 0.488603 * z; + shBasis[ 3 ] = 0.488603 * x; - }; + // band 2 + shBasis[ 4 ] = 1.092548 * x * y; + shBasis[ 5 ] = 1.092548 * y * z; + shBasis[ 6 ] = 0.315392 * ( 3 * z * z - 1 ); + shBasis[ 7 ] = 1.092548 * x * z; + shBasis[ 8 ] = 0.546274 * ( x * x - y * y ); - this.setReferenceSpace = function ( space ) { + } - customReferenceSpace = space; +} - }; +/** + * Light probes are an alternative way of adding light to a 3D scene. Unlike + * classical light sources (e.g. directional, point or spot lights), light + * probes do not emit light. Instead they store information about light + * passing through 3D space. During rendering, the light that hits a 3D + * object is approximated by using the data from the light probe. + * + * Light probes are usually created from (radiance) environment maps. The + * class {@link LightProbeGenerator} can be used to create light probes from + * cube textures or render targets. However, light estimation data could also + * be provided in other forms e.g. by WebXR. This enables the rendering of + * augmented reality content that reacts to real world lighting. + * + * The current probe implementation in three.js supports so-called diffuse + * light probes. This type of light probe is functionally equivalent to an + * irradiance environment map. + * + * @augments Light + */ +class LightProbe extends Light { - this.getBaseLayer = function () { + /** + * Constructs a new light probe. + * + * @param {SphericalHarmonics3} sh - The spherical harmonics which represents encoded lighting information. + * @param {number} [intensity=1] - The light's strength/intensity. + */ + constructor( sh = new SphericalHarmonics3(), intensity = 1 ) { - return glProjLayer !== null ? glProjLayer : glBaseLayer; + super( undefined, intensity ); - }; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLightProbe = true; - this.getBinding = function () { + /** + * A light probe uses spherical harmonics to encode lighting information. + * + * @type {SphericalHarmonics3} + */ + this.sh = sh; - return glBinding; + } - }; + copy( source ) { - this.getFrame = function () { + super.copy( source ); - return xrFrame; + this.sh.copy( source.sh ); - }; + return this; - this.getSession = function () { + } - return session; + /** + * Deserializes the light prove from the given JSON. + * + * @param {Object} json - The JSON holding the serialized light probe. + * @return {LightProbe} A reference to this light probe. + */ + fromJSON( json ) { - }; + this.intensity = json.intensity; // TODO: Move this bit to Light.fromJSON(); + this.sh.fromArray( json.sh ); - this.setSession = async function ( value ) { + return this; - session = value; + } - if ( session !== null ) { + toJSON( meta ) { - initialRenderTarget = renderer.getRenderTarget(); + const data = super.toJSON( meta ); - session.addEventListener( 'select', onSessionEvent ); - session.addEventListener( 'selectstart', onSessionEvent ); - session.addEventListener( 'selectend', onSessionEvent ); - session.addEventListener( 'squeeze', onSessionEvent ); - session.addEventListener( 'squeezestart', onSessionEvent ); - session.addEventListener( 'squeezeend', onSessionEvent ); - session.addEventListener( 'end', onSessionEnd ); - session.addEventListener( 'inputsourceschange', onInputSourcesChange ); + data.object.sh = this.sh.toArray(); - if ( attributes.xrCompatible !== true ) { + return data; - await gl.makeXRCompatible(); + } - } +} - if ( ( session.renderState.layers === undefined ) || ( renderer.capabilities.isWebGL2 === false ) ) { +/** + * Class for loading geometries. The files are internally + * loaded via {@link FileLoader}. + * + * ```js + * const loader = new THREE.MaterialLoader(); + * const material = await loader.loadAsync( 'material.json' ); + * ``` + * This loader does not support node materials. Use {@link NodeMaterialLoader} instead. + * + * @augments Loader + */ +class MaterialLoader extends Loader { - const layerInit = { - antialias: ( session.renderState.layers === undefined ) ? attributes.antialias : true, - alpha: true, - depth: attributes.depth, - stencil: attributes.stencil, - framebufferScaleFactor: framebufferScaleFactor - }; + /** + * Constructs a new material loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ + constructor( manager ) { - glBaseLayer = new XRWebGLLayer( session, gl, layerInit ); + super( manager ); - session.updateRenderState( { baseLayer: glBaseLayer } ); + /** + * A dictionary holding textures used by the material. + * + * @type {Object} + */ + this.textures = {}; - newRenderTarget = new WebGLRenderTarget( - glBaseLayer.framebufferWidth, - glBaseLayer.framebufferHeight, - { - format: RGBAFormat, - type: UnsignedByteType, - colorSpace: renderer.outputColorSpace, - stencilBuffer: attributes.stencil - } - ); + } - } else { + /** + * Starts loading from the given URL and pass the loaded material to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(Material)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const loader = new FileLoader( scope.manager ); + loader.setPath( scope.path ); + loader.setRequestHeader( scope.requestHeader ); + loader.setWithCredentials( scope.withCredentials ); + loader.load( url, function ( text ) { - let depthFormat = null; - let depthType = null; - let glDepthFormat = null; + try { - if ( attributes.depth ) { + onLoad( scope.parse( JSON.parse( text ) ) ); - glDepthFormat = attributes.stencil ? gl.DEPTH24_STENCIL8 : gl.DEPTH_COMPONENT24; - depthFormat = attributes.stencil ? DepthStencilFormat : DepthFormat; - depthType = attributes.stencil ? UnsignedInt248Type : UnsignedIntType; + } catch ( e ) { - } + if ( onError ) { - const projectionlayerInit = { - colorFormat: gl.RGBA8, - depthFormat: glDepthFormat, - scaleFactor: framebufferScaleFactor - }; + onError( e ); - glBinding = new XRWebGLBinding( session, gl ); + } else { - glProjLayer = glBinding.createProjectionLayer( projectionlayerInit ); + console.error( e ); - session.updateRenderState( { layers: [ glProjLayer ] } ); + } - newRenderTarget = new WebGLRenderTarget( - glProjLayer.textureWidth, - glProjLayer.textureHeight, - { - format: RGBAFormat, - type: UnsignedByteType, - depthTexture: new DepthTexture( glProjLayer.textureWidth, glProjLayer.textureHeight, depthType, undefined, undefined, undefined, undefined, undefined, undefined, depthFormat ), - stencilBuffer: attributes.stencil, - colorSpace: renderer.outputColorSpace, - samples: attributes.antialias ? 4 : 0 - } ); + scope.manager.itemError( url ); - const renderTargetProperties = renderer.properties.get( newRenderTarget ); - renderTargetProperties.__ignoreDepthValues = glProjLayer.ignoreDepthValues; + } - } + }, onProgress, onError ); - newRenderTarget.isXRRenderTarget = true; // TODO Remove this when possible, see #23278 + } - this.setFoveation( foveation ); + /** + * Parses the given JSON object and returns a material. + * + * @param {Object} json - The serialized material. + * @return {Material} The parsed material. + */ + parse( json ) { - customReferenceSpace = null; - referenceSpace = await session.requestReferenceSpace( referenceSpaceType ); + const textures = this.textures; - animation.setContext( session ); - animation.start(); + function getTexture( name ) { - scope.isPresenting = true; + if ( textures[ name ] === undefined ) { - scope.dispatchEvent( { type: 'sessionstart' } ); + console.warn( 'THREE.MaterialLoader: Undefined texture', name ); } - }; + return textures[ name ]; - this.getEnvironmentBlendMode = function () { + } - if ( session !== null ) { + const material = this.createMaterialFromType( json.type ); - return session.environmentBlendMode; + if ( json.uuid !== undefined ) material.uuid = json.uuid; + if ( json.name !== undefined ) material.name = json.name; + if ( json.color !== undefined && material.color !== undefined ) material.color.setHex( json.color ); + if ( json.roughness !== undefined ) material.roughness = json.roughness; + if ( json.metalness !== undefined ) material.metalness = json.metalness; + if ( json.sheen !== undefined ) material.sheen = json.sheen; + if ( json.sheenColor !== undefined ) material.sheenColor = new Color().setHex( json.sheenColor ); + if ( json.sheenRoughness !== undefined ) material.sheenRoughness = json.sheenRoughness; + if ( json.emissive !== undefined && material.emissive !== undefined ) material.emissive.setHex( json.emissive ); + if ( json.specular !== undefined && material.specular !== undefined ) material.specular.setHex( json.specular ); + if ( json.specularIntensity !== undefined ) material.specularIntensity = json.specularIntensity; + if ( json.specularColor !== undefined && material.specularColor !== undefined ) material.specularColor.setHex( json.specularColor ); + if ( json.shininess !== undefined ) material.shininess = json.shininess; + if ( json.clearcoat !== undefined ) material.clearcoat = json.clearcoat; + if ( json.clearcoatRoughness !== undefined ) material.clearcoatRoughness = json.clearcoatRoughness; + if ( json.dispersion !== undefined ) material.dispersion = json.dispersion; + if ( json.iridescence !== undefined ) material.iridescence = json.iridescence; + if ( json.iridescenceIOR !== undefined ) material.iridescenceIOR = json.iridescenceIOR; + if ( json.iridescenceThicknessRange !== undefined ) material.iridescenceThicknessRange = json.iridescenceThicknessRange; + if ( json.transmission !== undefined ) material.transmission = json.transmission; + if ( json.thickness !== undefined ) material.thickness = json.thickness; + if ( json.attenuationDistance !== undefined ) material.attenuationDistance = json.attenuationDistance; + if ( json.attenuationColor !== undefined && material.attenuationColor !== undefined ) material.attenuationColor.setHex( json.attenuationColor ); + if ( json.anisotropy !== undefined ) material.anisotropy = json.anisotropy; + if ( json.anisotropyRotation !== undefined ) material.anisotropyRotation = json.anisotropyRotation; + if ( json.fog !== undefined ) material.fog = json.fog; + if ( json.flatShading !== undefined ) material.flatShading = json.flatShading; + if ( json.blending !== undefined ) material.blending = json.blending; + if ( json.combine !== undefined ) material.combine = json.combine; + if ( json.side !== undefined ) material.side = json.side; + if ( json.shadowSide !== undefined ) material.shadowSide = json.shadowSide; + if ( json.opacity !== undefined ) material.opacity = json.opacity; + if ( json.transparent !== undefined ) material.transparent = json.transparent; + if ( json.alphaTest !== undefined ) material.alphaTest = json.alphaTest; + if ( json.alphaHash !== undefined ) material.alphaHash = json.alphaHash; + if ( json.depthFunc !== undefined ) material.depthFunc = json.depthFunc; + if ( json.depthTest !== undefined ) material.depthTest = json.depthTest; + if ( json.depthWrite !== undefined ) material.depthWrite = json.depthWrite; + if ( json.colorWrite !== undefined ) material.colorWrite = json.colorWrite; + if ( json.blendSrc !== undefined ) material.blendSrc = json.blendSrc; + if ( json.blendDst !== undefined ) material.blendDst = json.blendDst; + if ( json.blendEquation !== undefined ) material.blendEquation = json.blendEquation; + if ( json.blendSrcAlpha !== undefined ) material.blendSrcAlpha = json.blendSrcAlpha; + if ( json.blendDstAlpha !== undefined ) material.blendDstAlpha = json.blendDstAlpha; + if ( json.blendEquationAlpha !== undefined ) material.blendEquationAlpha = json.blendEquationAlpha; + if ( json.blendColor !== undefined && material.blendColor !== undefined ) material.blendColor.setHex( json.blendColor ); + if ( json.blendAlpha !== undefined ) material.blendAlpha = json.blendAlpha; + if ( json.stencilWriteMask !== undefined ) material.stencilWriteMask = json.stencilWriteMask; + if ( json.stencilFunc !== undefined ) material.stencilFunc = json.stencilFunc; + if ( json.stencilRef !== undefined ) material.stencilRef = json.stencilRef; + if ( json.stencilFuncMask !== undefined ) material.stencilFuncMask = json.stencilFuncMask; + if ( json.stencilFail !== undefined ) material.stencilFail = json.stencilFail; + if ( json.stencilZFail !== undefined ) material.stencilZFail = json.stencilZFail; + if ( json.stencilZPass !== undefined ) material.stencilZPass = json.stencilZPass; + if ( json.stencilWrite !== undefined ) material.stencilWrite = json.stencilWrite; - } + if ( json.wireframe !== undefined ) material.wireframe = json.wireframe; + if ( json.wireframeLinewidth !== undefined ) material.wireframeLinewidth = json.wireframeLinewidth; + if ( json.wireframeLinecap !== undefined ) material.wireframeLinecap = json.wireframeLinecap; + if ( json.wireframeLinejoin !== undefined ) material.wireframeLinejoin = json.wireframeLinejoin; - }; + if ( json.rotation !== undefined ) material.rotation = json.rotation; - function onInputSourcesChange( event ) { + if ( json.linewidth !== undefined ) material.linewidth = json.linewidth; + if ( json.dashSize !== undefined ) material.dashSize = json.dashSize; + if ( json.gapSize !== undefined ) material.gapSize = json.gapSize; + if ( json.scale !== undefined ) material.scale = json.scale; - // Notify disconnected + if ( json.polygonOffset !== undefined ) material.polygonOffset = json.polygonOffset; + if ( json.polygonOffsetFactor !== undefined ) material.polygonOffsetFactor = json.polygonOffsetFactor; + if ( json.polygonOffsetUnits !== undefined ) material.polygonOffsetUnits = json.polygonOffsetUnits; - for ( let i = 0; i < event.removed.length; i ++ ) { + if ( json.dithering !== undefined ) material.dithering = json.dithering; - const inputSource = event.removed[ i ]; - const index = controllerInputSources.indexOf( inputSource ); + if ( json.alphaToCoverage !== undefined ) material.alphaToCoverage = json.alphaToCoverage; + if ( json.premultipliedAlpha !== undefined ) material.premultipliedAlpha = json.premultipliedAlpha; + if ( json.forceSinglePass !== undefined ) material.forceSinglePass = json.forceSinglePass; - if ( index >= 0 ) { + if ( json.visible !== undefined ) material.visible = json.visible; - controllerInputSources[ index ] = null; - controllers[ index ].disconnect( inputSource ); + if ( json.toneMapped !== undefined ) material.toneMapped = json.toneMapped; - } + if ( json.userData !== undefined ) material.userData = json.userData; - } + if ( json.vertexColors !== undefined ) { - // Notify connected + if ( typeof json.vertexColors === 'number' ) { - for ( let i = 0; i < event.added.length; i ++ ) { + material.vertexColors = ( json.vertexColors > 0 ) ? true : false; - const inputSource = event.added[ i ]; + } else { - let controllerIndex = controllerInputSources.indexOf( inputSource ); + material.vertexColors = json.vertexColors; - if ( controllerIndex === - 1 ) { + } - // Assign input source a controller that currently has no input source + } - for ( let i = 0; i < controllers.length; i ++ ) { + // Shader Material - if ( i >= controllerInputSources.length ) { + if ( json.uniforms !== undefined ) { - controllerInputSources.push( inputSource ); - controllerIndex = i; - break; + for ( const name in json.uniforms ) { - } else if ( controllerInputSources[ i ] === null ) { + const uniform = json.uniforms[ name ]; - controllerInputSources[ i ] = inputSource; - controllerIndex = i; - break; + material.uniforms[ name ] = {}; - } + switch ( uniform.type ) { - } + case 't': + material.uniforms[ name ].value = getTexture( uniform.value ); + break; - // If all controllers do currently receive input we ignore new ones + case 'c': + material.uniforms[ name ].value = new Color().setHex( uniform.value ); + break; - if ( controllerIndex === - 1 ) break; + case 'v2': + material.uniforms[ name ].value = new Vector2().fromArray( uniform.value ); + break; - } + case 'v3': + material.uniforms[ name ].value = new Vector3().fromArray( uniform.value ); + break; - const controller = controllers[ controllerIndex ]; + case 'v4': + material.uniforms[ name ].value = new Vector4().fromArray( uniform.value ); + break; - if ( controller ) { + case 'm3': + material.uniforms[ name ].value = new Matrix3().fromArray( uniform.value ); + break; - controller.connect( inputSource ); + case 'm4': + material.uniforms[ name ].value = new Matrix4().fromArray( uniform.value ); + break; + + default: + material.uniforms[ name ].value = uniform.value; } @@ -26483,851 +46885,1014 @@ class WebXRManager extends EventDispatcher { } - // + if ( json.defines !== undefined ) material.defines = json.defines; + if ( json.vertexShader !== undefined ) material.vertexShader = json.vertexShader; + if ( json.fragmentShader !== undefined ) material.fragmentShader = json.fragmentShader; + if ( json.glslVersion !== undefined ) material.glslVersion = json.glslVersion; - const cameraLPos = new Vector3(); - const cameraRPos = new Vector3(); + if ( json.extensions !== undefined ) { - /** - * Assumes 2 cameras that are parallel and share an X-axis, and that - * the cameras' projection and world matrices have already been set. - * And that near and far planes are identical for both cameras. - * Visualization of this technique: https://computergraphics.stackexchange.com/a/4765 - */ - function setProjectionFromUnion( camera, cameraL, cameraR ) { + for ( const key in json.extensions ) { - cameraLPos.setFromMatrixPosition( cameraL.matrixWorld ); - cameraRPos.setFromMatrixPosition( cameraR.matrixWorld ); + material.extensions[ key ] = json.extensions[ key ]; - const ipd = cameraLPos.distanceTo( cameraRPos ); + } - const projL = cameraL.projectionMatrix.elements; - const projR = cameraR.projectionMatrix.elements; + } - // VR systems will have identical far and near planes, and - // most likely identical top and bottom frustum extents. - // Use the left camera for these values. - const near = projL[ 14 ] / ( projL[ 10 ] - 1 ); - const far = projL[ 14 ] / ( projL[ 10 ] + 1 ); - const topFov = ( projL[ 9 ] + 1 ) / projL[ 5 ]; - const bottomFov = ( projL[ 9 ] - 1 ) / projL[ 5 ]; + if ( json.lights !== undefined ) material.lights = json.lights; + if ( json.clipping !== undefined ) material.clipping = json.clipping; - const leftFov = ( projL[ 8 ] - 1 ) / projL[ 0 ]; - const rightFov = ( projR[ 8 ] + 1 ) / projR[ 0 ]; - const left = near * leftFov; - const right = near * rightFov; + // for PointsMaterial - // Calculate the new camera's position offset from the - // left camera. xOffset should be roughly half `ipd`. - const zOffset = ipd / ( - leftFov + rightFov ); - const xOffset = zOffset * - leftFov; + if ( json.size !== undefined ) material.size = json.size; + if ( json.sizeAttenuation !== undefined ) material.sizeAttenuation = json.sizeAttenuation; - // TODO: Better way to apply this offset? - cameraL.matrixWorld.decompose( camera.position, camera.quaternion, camera.scale ); - camera.translateX( xOffset ); - camera.translateZ( zOffset ); - camera.matrixWorld.compose( camera.position, camera.quaternion, camera.scale ); - camera.matrixWorldInverse.copy( camera.matrixWorld ).invert(); + // maps - // Find the union of the frustum values of the cameras and scale - // the values so that the near plane's position does not change in world space, - // although must now be relative to the new union camera. - const near2 = near + zOffset; - const far2 = far + zOffset; - const left2 = left - xOffset; - const right2 = right + ( ipd - xOffset ); - const top2 = topFov * far / far2 * near2; - const bottom2 = bottomFov * far / far2 * near2; + if ( json.map !== undefined ) material.map = getTexture( json.map ); + if ( json.matcap !== undefined ) material.matcap = getTexture( json.matcap ); - camera.projectionMatrix.makePerspective( left2, right2, top2, bottom2, near2, far2 ); - camera.projectionMatrixInverse.copy( camera.projectionMatrix ).invert(); + if ( json.alphaMap !== undefined ) material.alphaMap = getTexture( json.alphaMap ); - } + if ( json.bumpMap !== undefined ) material.bumpMap = getTexture( json.bumpMap ); + if ( json.bumpScale !== undefined ) material.bumpScale = json.bumpScale; - function updateCamera( camera, parent ) { + if ( json.normalMap !== undefined ) material.normalMap = getTexture( json.normalMap ); + if ( json.normalMapType !== undefined ) material.normalMapType = json.normalMapType; + if ( json.normalScale !== undefined ) { - if ( parent === null ) { + let normalScale = json.normalScale; - camera.matrixWorld.copy( camera.matrix ); + if ( Array.isArray( normalScale ) === false ) { - } else { + // Blender exporter used to export a scalar. See #7459 - camera.matrixWorld.multiplyMatrices( parent.matrixWorld, camera.matrix ); + normalScale = [ normalScale, normalScale ]; } - camera.matrixWorldInverse.copy( camera.matrixWorld ).invert(); + material.normalScale = new Vector2().fromArray( normalScale ); } - this.updateCameraXR = function ( camera ) { - - if ( session === null ) return camera; + if ( json.displacementMap !== undefined ) material.displacementMap = getTexture( json.displacementMap ); + if ( json.displacementScale !== undefined ) material.displacementScale = json.displacementScale; + if ( json.displacementBias !== undefined ) material.displacementBias = json.displacementBias; - if ( userCamera ) { + if ( json.roughnessMap !== undefined ) material.roughnessMap = getTexture( json.roughnessMap ); + if ( json.metalnessMap !== undefined ) material.metalnessMap = getTexture( json.metalnessMap ); - camera = userCamera; + if ( json.emissiveMap !== undefined ) material.emissiveMap = getTexture( json.emissiveMap ); + if ( json.emissiveIntensity !== undefined ) material.emissiveIntensity = json.emissiveIntensity; - } + if ( json.specularMap !== undefined ) material.specularMap = getTexture( json.specularMap ); + if ( json.specularIntensityMap !== undefined ) material.specularIntensityMap = getTexture( json.specularIntensityMap ); + if ( json.specularColorMap !== undefined ) material.specularColorMap = getTexture( json.specularColorMap ); - cameraXR.near = cameraR.near = cameraL.near = camera.near; - cameraXR.far = cameraR.far = cameraL.far = camera.far; + if ( json.envMap !== undefined ) material.envMap = getTexture( json.envMap ); + if ( json.envMapRotation !== undefined ) material.envMapRotation.fromArray( json.envMapRotation ); + if ( json.envMapIntensity !== undefined ) material.envMapIntensity = json.envMapIntensity; - if ( _currentDepthNear !== cameraXR.near || _currentDepthFar !== cameraXR.far ) { + if ( json.reflectivity !== undefined ) material.reflectivity = json.reflectivity; + if ( json.refractionRatio !== undefined ) material.refractionRatio = json.refractionRatio; - // Note that the new renderState won't apply until the next frame. See #18320 + if ( json.lightMap !== undefined ) material.lightMap = getTexture( json.lightMap ); + if ( json.lightMapIntensity !== undefined ) material.lightMapIntensity = json.lightMapIntensity; - session.updateRenderState( { - depthNear: cameraXR.near, - depthFar: cameraXR.far - } ); + if ( json.aoMap !== undefined ) material.aoMap = getTexture( json.aoMap ); + if ( json.aoMapIntensity !== undefined ) material.aoMapIntensity = json.aoMapIntensity; - _currentDepthNear = cameraXR.near; - _currentDepthFar = cameraXR.far; + if ( json.gradientMap !== undefined ) material.gradientMap = getTexture( json.gradientMap ); - } + if ( json.clearcoatMap !== undefined ) material.clearcoatMap = getTexture( json.clearcoatMap ); + if ( json.clearcoatRoughnessMap !== undefined ) material.clearcoatRoughnessMap = getTexture( json.clearcoatRoughnessMap ); + if ( json.clearcoatNormalMap !== undefined ) material.clearcoatNormalMap = getTexture( json.clearcoatNormalMap ); + if ( json.clearcoatNormalScale !== undefined ) material.clearcoatNormalScale = new Vector2().fromArray( json.clearcoatNormalScale ); - const parent = camera.parent; - const cameras = cameraXR.cameras; + if ( json.iridescenceMap !== undefined ) material.iridescenceMap = getTexture( json.iridescenceMap ); + if ( json.iridescenceThicknessMap !== undefined ) material.iridescenceThicknessMap = getTexture( json.iridescenceThicknessMap ); - updateCamera( cameraXR, parent ); + if ( json.transmissionMap !== undefined ) material.transmissionMap = getTexture( json.transmissionMap ); + if ( json.thicknessMap !== undefined ) material.thicknessMap = getTexture( json.thicknessMap ); - for ( let i = 0; i < cameras.length; i ++ ) { + if ( json.anisotropyMap !== undefined ) material.anisotropyMap = getTexture( json.anisotropyMap ); - updateCamera( cameras[ i ], parent ); + if ( json.sheenColorMap !== undefined ) material.sheenColorMap = getTexture( json.sheenColorMap ); + if ( json.sheenRoughnessMap !== undefined ) material.sheenRoughnessMap = getTexture( json.sheenRoughnessMap ); - } + return material; - // update projection matrix for proper view frustum culling + } - if ( cameras.length === 2 ) { + /** + * Textures are not embedded in the material JSON so they have + * to be injected before the loading process starts. + * + * @param {Object} value - A dictionary holding textures for material properties. + * @return {MaterialLoader} A reference to this material loader. + */ + setTextures( value ) { - setProjectionFromUnion( cameraXR, cameraL, cameraR ); + this.textures = value; + return this; - } else { + } - // assume single camera setup (AR) + /** + * Creates a material for the given type. + * + * @param {string} type - The material type. + * @return {Material} The new material. + */ + createMaterialFromType( type ) { - cameraXR.projectionMatrix.copy( cameraL.projectionMatrix ); + return MaterialLoader.createMaterialFromType( type ); - } + } - // update user camera and its children + /** + * Creates a material for the given type. + * + * @static + * @param {string} type - The material type. + * @return {Material} The new material. + */ + static createMaterialFromType( type ) { - if ( userCamera ) { + const materialLib = { + ShadowMaterial, + SpriteMaterial, + RawShaderMaterial, + ShaderMaterial, + PointsMaterial, + MeshPhysicalMaterial, + MeshStandardMaterial, + MeshPhongMaterial, + MeshToonMaterial, + MeshNormalMaterial, + MeshLambertMaterial, + MeshDepthMaterial, + MeshDistanceMaterial, + MeshBasicMaterial, + MeshMatcapMaterial, + LineDashedMaterial, + LineBasicMaterial, + Material + }; - updateUserCamera( cameraXR, parent ); + return new materialLib[ type ](); - } + } - return cameraXR; +} - }; +/** + * A class with loader utility functions. + */ +class LoaderUtils { - function updateUserCamera( cameraXR, parent ) { + /** + * Extracts the base URL from the given URL. + * + * @param {string} url -The URL to extract the base URL from. + * @return {string} The extracted base URL. + */ + static extractUrlBase( url ) { - const camera = userCamera; + const index = url.lastIndexOf( '/' ); - if ( parent === null ) { + if ( index === -1 ) return './'; - camera.matrix.copy( cameraXR.matrixWorld ); + return url.slice( 0, index + 1 ); - } else { + } - camera.matrix.copy( parent.matrixWorld ); - camera.matrix.invert(); - camera.matrix.multiply( cameraXR.matrixWorld ); + /** + * Resolves relative URLs against the given path. Absolute paths, data urls, + * and blob URLs will be returned as is. Invalid URLs will return an empty + * string. + * + * @param {string} url -The URL to resolve. + * @param {string} path - The base path for relative URLs to be resolved against. + * @return {string} The resolved URL. + */ + static resolveURL( url, path ) { - } + // Invalid URL + if ( typeof url !== 'string' || url === '' ) return ''; - camera.matrix.decompose( camera.position, camera.quaternion, camera.scale ); - camera.updateMatrixWorld( true ); + // Host Relative URL + if ( /^https?:\/\//i.test( path ) && /^\//.test( url ) ) { - const children = camera.children; + path = path.replace( /(^https?:\/\/[^\/]+).*/i, '$1' ); - for ( let i = 0, l = children.length; i < l; i ++ ) { + } - children[ i ].updateMatrixWorld( true ); + // Absolute URL http://,https://,// + if ( /^(https?:)?\/\//i.test( url ) ) return url; - } + // Data URI + if ( /^data:.*,.*$/i.test( url ) ) return url; - camera.projectionMatrix.copy( cameraXR.projectionMatrix ); - camera.projectionMatrixInverse.copy( cameraXR.projectionMatrixInverse ); + // Blob URL + if ( /^blob:.*$/i.test( url ) ) return url; - if ( camera.isPerspectiveCamera ) { + // Relative URL + return path + url; - camera.fov = RAD2DEG * 2 * Math.atan( 1 / camera.projectionMatrix.elements[ 5 ] ); - camera.zoom = 1; + } - } +} - } +/** + * An instanced version of a geometry. + */ +class InstancedBufferGeometry extends BufferGeometry { - this.getFoveation = function () { + /** + * Constructs a new instanced buffer geometry. + */ + constructor() { - if ( glProjLayer === null && glBaseLayer === null ) { + super(); - return undefined; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isInstancedBufferGeometry = true; - } + this.type = 'InstancedBufferGeometry'; - return foveation; + /** + * The instance count. + * + * @type {number} + * @default Infinity + */ + this.instanceCount = Infinity; - }; + } - this.setFoveation = function ( value ) { + copy( source ) { - // 0 = no foveation = full resolution - // 1 = maximum foveation = the edges render at lower resolution + super.copy( source ); - foveation = value; + this.instanceCount = source.instanceCount; - if ( glProjLayer !== null ) { + return this; - glProjLayer.fixedFoveation = value; + } - } + toJSON() { - if ( glBaseLayer !== null && glBaseLayer.fixedFoveation !== undefined ) { + const data = super.toJSON(); - glBaseLayer.fixedFoveation = value; + data.instanceCount = this.instanceCount; - } + data.isInstancedBufferGeometry = true; - }; + return data; - this.getPlanes = function () { + } - return planes; +} - }; +/** + * Class for loading geometries. The files are internally + * loaded via {@link FileLoader}. + * + * ```js + * const loader = new THREE.BufferGeometryLoader(); + * const geometry = await loader.loadAsync( 'models/json/pressure.json' ); + * + * const material = new THREE.MeshBasicMaterial( { color: 0xF5F5F5 } ); + * const object = new THREE.Mesh( geometry, material ); + * scene.add( object ); + * ``` + * + * @augments Loader + */ +class BufferGeometryLoader extends Loader { - // Animation Loop + /** + * Constructs a new geometry loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ + constructor( manager ) { - let onAnimationFrameCallback = null; + super( manager ); - function onAnimationFrame( time, frame ) { + } - pose = frame.getViewerPose( customReferenceSpace || referenceSpace ); - xrFrame = frame; + /** + * Starts loading from the given URL and pass the loaded geometry to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(BufferGeometry)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ + load( url, onLoad, onProgress, onError ) { - if ( pose !== null ) { + const scope = this; - const views = pose.views; + const loader = new FileLoader( scope.manager ); + loader.setPath( scope.path ); + loader.setRequestHeader( scope.requestHeader ); + loader.setWithCredentials( scope.withCredentials ); + loader.load( url, function ( text ) { - if ( glBaseLayer !== null ) { + try { - renderer.setRenderTargetFramebuffer( newRenderTarget, glBaseLayer.framebuffer ); - renderer.setRenderTarget( newRenderTarget ); + onLoad( scope.parse( JSON.parse( text ) ) ); - } + } catch ( e ) { - let cameraXRNeedsUpdate = false; + if ( onError ) { - // check if it's necessary to rebuild cameraXR's camera list + onError( e ); - if ( views.length !== cameraXR.cameras.length ) { + } else { - cameraXR.cameras.length = 0; - cameraXRNeedsUpdate = true; + console.error( e ); } - for ( let i = 0; i < views.length; i ++ ) { + scope.manager.itemError( url ); - const view = views[ i ]; + } - let viewport = null; + }, onProgress, onError ); + + } + + /** + * Parses the given JSON object and returns a geometry. + * + * @param {Object} json - The serialized geometry. + * @return {BufferGeometry} The parsed geometry. + */ + parse( json ) { - if ( glBaseLayer !== null ) { + const interleavedBufferMap = {}; + const arrayBufferMap = {}; - viewport = glBaseLayer.getViewport( view ); + function getInterleavedBuffer( json, uuid ) { - } else { + if ( interleavedBufferMap[ uuid ] !== undefined ) return interleavedBufferMap[ uuid ]; - const glSubImage = glBinding.getViewSubImage( glProjLayer, view ); - viewport = glSubImage.viewport; + const interleavedBuffers = json.interleavedBuffers; + const interleavedBuffer = interleavedBuffers[ uuid ]; - // For side-by-side projection, we only produce a single texture for both eyes. - if ( i === 0 ) { + const buffer = getArrayBuffer( json, interleavedBuffer.buffer ); - renderer.setRenderTargetTextures( - newRenderTarget, - glSubImage.colorTexture, - glProjLayer.ignoreDepthValues ? undefined : glSubImage.depthStencilTexture ); + const array = getTypedArray( interleavedBuffer.type, buffer ); + const ib = new InterleavedBuffer( array, interleavedBuffer.stride ); + ib.uuid = interleavedBuffer.uuid; - renderer.setRenderTarget( newRenderTarget ); + interleavedBufferMap[ uuid ] = ib; - } + return ib; - } + } - let camera = cameras[ i ]; + function getArrayBuffer( json, uuid ) { - if ( camera === undefined ) { + if ( arrayBufferMap[ uuid ] !== undefined ) return arrayBufferMap[ uuid ]; - camera = new PerspectiveCamera(); - camera.layers.enable( i ); - camera.viewport = new Vector4(); - cameras[ i ] = camera; + const arrayBuffers = json.arrayBuffers; + const arrayBuffer = arrayBuffers[ uuid ]; - } + const ab = new Uint32Array( arrayBuffer ).buffer; - camera.matrix.fromArray( view.transform.matrix ); - camera.matrix.decompose( camera.position, camera.quaternion, camera.scale ); - camera.projectionMatrix.fromArray( view.projectionMatrix ); - camera.projectionMatrixInverse.copy( camera.projectionMatrix ).invert(); - camera.viewport.set( viewport.x, viewport.y, viewport.width, viewport.height ); + arrayBufferMap[ uuid ] = ab; - if ( i === 0 ) { + return ab; - cameraXR.matrix.copy( camera.matrix ); - cameraXR.matrix.decompose( cameraXR.position, cameraXR.quaternion, cameraXR.scale ); + } - } + const geometry = json.isInstancedBufferGeometry ? new InstancedBufferGeometry() : new BufferGeometry(); - if ( cameraXRNeedsUpdate === true ) { + const index = json.data.index; - cameraXR.cameras.push( camera ); + if ( index !== undefined ) { - } + const typedArray = getTypedArray( index.type, index.array ); + geometry.setIndex( new BufferAttribute( typedArray, 1 ) ); - } + } - } + const attributes = json.data.attributes; - // + for ( const key in attributes ) { - for ( let i = 0; i < controllers.length; i ++ ) { + const attribute = attributes[ key ]; + let bufferAttribute; - const inputSource = controllerInputSources[ i ]; - const controller = controllers[ i ]; + if ( attribute.isInterleavedBufferAttribute ) { - if ( inputSource !== null && controller !== undefined ) { + const interleavedBuffer = getInterleavedBuffer( json.data, attribute.data ); + bufferAttribute = new InterleavedBufferAttribute( interleavedBuffer, attribute.itemSize, attribute.offset, attribute.normalized ); - controller.update( inputSource, frame, customReferenceSpace || referenceSpace ); + } else { - } + const typedArray = getTypedArray( attribute.type, attribute.array ); + const bufferAttributeConstr = attribute.isInstancedBufferAttribute ? InstancedBufferAttribute : BufferAttribute; + bufferAttribute = new bufferAttributeConstr( typedArray, attribute.itemSize, attribute.normalized ); } - if ( onAnimationFrameCallback ) onAnimationFrameCallback( time, frame ); - - if ( frame.detectedPlanes ) { + if ( attribute.name !== undefined ) bufferAttribute.name = attribute.name; + if ( attribute.usage !== undefined ) bufferAttribute.setUsage( attribute.usage ); - scope.dispatchEvent( { type: 'planesdetected', data: frame.detectedPlanes } ); + geometry.setAttribute( key, bufferAttribute ); - let planesToRemove = null; + } - for ( const plane of planes ) { + const morphAttributes = json.data.morphAttributes; - if ( ! frame.detectedPlanes.has( plane ) ) { + if ( morphAttributes ) { - if ( planesToRemove === null ) { + for ( const key in morphAttributes ) { - planesToRemove = []; + const attributeArray = morphAttributes[ key ]; - } + const array = []; - planesToRemove.push( plane ); + for ( let i = 0, il = attributeArray.length; i < il; i ++ ) { - } + const attribute = attributeArray[ i ]; + let bufferAttribute; - } + if ( attribute.isInterleavedBufferAttribute ) { - if ( planesToRemove !== null ) { + const interleavedBuffer = getInterleavedBuffer( json.data, attribute.data ); + bufferAttribute = new InterleavedBufferAttribute( interleavedBuffer, attribute.itemSize, attribute.offset, attribute.normalized ); - for ( const plane of planesToRemove ) { + } else { - planes.delete( plane ); - planesLastChangedTimes.delete( plane ); - scope.dispatchEvent( { type: 'planeremoved', data: plane } ); + const typedArray = getTypedArray( attribute.type, attribute.array ); + bufferAttribute = new BufferAttribute( typedArray, attribute.itemSize, attribute.normalized ); } + if ( attribute.name !== undefined ) bufferAttribute.name = attribute.name; + array.push( bufferAttribute ); + } - for ( const plane of frame.detectedPlanes ) { + geometry.morphAttributes[ key ] = array; + + } - if ( ! planes.has( plane ) ) { + } - planes.add( plane ); - planesLastChangedTimes.set( plane, frame.lastChangedTime ); - scope.dispatchEvent( { type: 'planeadded', data: plane } ); + const morphTargetsRelative = json.data.morphTargetsRelative; - } else { + if ( morphTargetsRelative ) { - const lastKnownTime = planesLastChangedTimes.get( plane ); + geometry.morphTargetsRelative = true; - if ( plane.lastChangedTime > lastKnownTime ) { + } - planesLastChangedTimes.set( plane, plane.lastChangedTime ); - scope.dispatchEvent( { type: 'planechanged', data: plane } ); + const groups = json.data.groups || json.data.drawcalls || json.data.offsets; - } + if ( groups !== undefined ) { - } + for ( let i = 0, n = groups.length; i !== n; ++ i ) { - } + const group = groups[ i ]; - } + geometry.addGroup( group.start, group.count, group.materialIndex ); - xrFrame = null; + } } - const animation = new WebGLAnimation(); + const boundingSphere = json.data.boundingSphere; - animation.setAnimationLoop( onAnimationFrame ); + if ( boundingSphere !== undefined ) { - this.setAnimationLoop = function ( callback ) { + geometry.boundingSphere = new Sphere().fromJSON( boundingSphere ); - onAnimationFrameCallback = callback; + } - }; + if ( json.name ) geometry.name = json.name; + if ( json.userData ) geometry.userData = json.userData; - this.dispose = function () {}; + return geometry; } } -function WebGLMaterials( renderer, properties ) { - - function refreshTransformUniform( map, uniform ) { - - if ( map.matrixAutoUpdate === true ) { - - map.updateMatrix(); +/** + * A loader for loading a JSON resource in the [JSON Object/Scene format]{@link https://github.com/mrdoob/three.js/wiki/JSON-Object-Scene-format-4}. + * The files are internally loaded via {@link FileLoader}. + * + * ```js + * const loader = new THREE.ObjectLoader(); + * const obj = await loader.loadAsync( 'models/json/example.json' ); + * scene.add( obj ); + * + * // Alternatively, to parse a previously loaded JSON structure + * const object = await loader.parseAsync( a_json_object ); + * scene.add( object ); + * ``` + * + * @augments Loader + */ +class ObjectLoader extends Loader { - } + /** + * Constructs a new object loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ + constructor( manager ) { - uniform.value.copy( map.matrix ); + super( manager ); } - function refreshFogUniforms( uniforms, fog ) { + /** + * Starts loading from the given URL and pass the loaded 3D object to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(Object3D)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ + load( url, onLoad, onProgress, onError ) { - fog.color.getRGB( uniforms.fogColor.value, getUnlitUniformColorSpace( renderer ) ); + const scope = this; - if ( fog.isFog ) { + const path = ( this.path === '' ) ? LoaderUtils.extractUrlBase( url ) : this.path; + this.resourcePath = this.resourcePath || path; - uniforms.fogNear.value = fog.near; - uniforms.fogFar.value = fog.far; + const loader = new FileLoader( this.manager ); + loader.setPath( this.path ); + loader.setRequestHeader( this.requestHeader ); + loader.setWithCredentials( this.withCredentials ); + loader.load( url, function ( text ) { - } else if ( fog.isFogExp2 ) { + let json = null; - uniforms.fogDensity.value = fog.density; + try { - } + json = JSON.parse( text ); - } + } catch ( error ) { - function refreshMaterialUniforms( uniforms, material, pixelRatio, height, transmissionRenderTarget ) { + if ( onError !== undefined ) onError( error ); - if ( material.isMeshBasicMaterial ) { + console.error( 'THREE:ObjectLoader: Can\'t parse ' + url + '.', error.message ); - refreshUniformsCommon( uniforms, material ); + return; - } else if ( material.isMeshLambertMaterial ) { + } - refreshUniformsCommon( uniforms, material ); + const metadata = json.metadata; - } else if ( material.isMeshToonMaterial ) { + if ( metadata === undefined || metadata.type === undefined || metadata.type.toLowerCase() === 'geometry' ) { - refreshUniformsCommon( uniforms, material ); - refreshUniformsToon( uniforms, material ); + if ( onError !== undefined ) onError( new Error( 'THREE.ObjectLoader: Can\'t load ' + url ) ); - } else if ( material.isMeshPhongMaterial ) { + console.error( 'THREE.ObjectLoader: Can\'t load ' + url ); + return; - refreshUniformsCommon( uniforms, material ); - refreshUniformsPhong( uniforms, material ); + } - } else if ( material.isMeshStandardMaterial ) { + scope.parse( json, onLoad ); - refreshUniformsCommon( uniforms, material ); - refreshUniformsStandard( uniforms, material ); + }, onProgress, onError ); - if ( material.isMeshPhysicalMaterial ) { + } - refreshUniformsPhysical( uniforms, material, transmissionRenderTarget ); + /** + * Async version of {@link ObjectLoader#load}. + * + * @async + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @return {Promise} A Promise that resolves with the loaded 3D object. + */ + async loadAsync( url, onProgress ) { - } + const scope = this; - } else if ( material.isMeshMatcapMaterial ) { + const path = ( this.path === '' ) ? LoaderUtils.extractUrlBase( url ) : this.path; + this.resourcePath = this.resourcePath || path; - refreshUniformsCommon( uniforms, material ); - refreshUniformsMatcap( uniforms, material ); + const loader = new FileLoader( this.manager ); + loader.setPath( this.path ); + loader.setRequestHeader( this.requestHeader ); + loader.setWithCredentials( this.withCredentials ); - } else if ( material.isMeshDepthMaterial ) { + const text = await loader.loadAsync( url, onProgress ); - refreshUniformsCommon( uniforms, material ); + const json = JSON.parse( text ); - } else if ( material.isMeshDistanceMaterial ) { + const metadata = json.metadata; - refreshUniformsCommon( uniforms, material ); - refreshUniformsDistance( uniforms, material ); + if ( metadata === undefined || metadata.type === undefined || metadata.type.toLowerCase() === 'geometry' ) { - } else if ( material.isMeshNormalMaterial ) { + throw new Error( 'THREE.ObjectLoader: Can\'t load ' + url ); - refreshUniformsCommon( uniforms, material ); + } - } else if ( material.isLineBasicMaterial ) { + return await scope.parseAsync( json ); - refreshUniformsLine( uniforms, material ); + } - if ( material.isLineDashedMaterial ) { + /** + * Parses the given JSON. This is used internally by {@link ObjectLoader#load} + * but can also be used directly to parse a previously loaded JSON structure. + * + * @param {Object} json - The serialized 3D object. + * @param {onLoad} onLoad - Executed when all resources (e.g. textures) have been fully loaded. + * @return {Object3D} The parsed 3D object. + */ + parse( json, onLoad ) { - refreshUniformsDash( uniforms, material ); + const animations = this.parseAnimations( json.animations ); + const shapes = this.parseShapes( json.shapes ); + const geometries = this.parseGeometries( json.geometries, shapes ); - } + const images = this.parseImages( json.images, function () { - } else if ( material.isPointsMaterial ) { + if ( onLoad !== undefined ) onLoad( object ); - refreshUniformsPoints( uniforms, material, pixelRatio, height ); + } ); - } else if ( material.isSpriteMaterial ) { + const textures = this.parseTextures( json.textures, images ); + const materials = this.parseMaterials( json.materials, textures ); - refreshUniformsSprites( uniforms, material ); + const object = this.parseObject( json.object, geometries, materials, textures, animations ); + const skeletons = this.parseSkeletons( json.skeletons, object ); - } else if ( material.isShadowMaterial ) { + this.bindSkeletons( object, skeletons ); + this.bindLightTargets( object ); - uniforms.color.value.copy( material.color ); - uniforms.opacity.value = material.opacity; + // - } else if ( material.isShaderMaterial ) { + if ( onLoad !== undefined ) { - material.uniformsNeedUpdate = false; // #15581 + let hasImages = false; - } + for ( const uuid in images ) { - } + if ( images[ uuid ].data instanceof HTMLImageElement ) { - function refreshUniformsCommon( uniforms, material ) { + hasImages = true; + break; - uniforms.opacity.value = material.opacity; + } - if ( material.color ) { + } - uniforms.diffuse.value.copy( material.color ); + if ( hasImages === false ) onLoad( object ); } - if ( material.emissive ) { + return object; - uniforms.emissive.value.copy( material.emissive ).multiplyScalar( material.emissiveIntensity ); + } - } + /** + * Async version of {@link ObjectLoader#parse}. + * + * @param {Object} json - The serialized 3D object. + * @return {Promise} A Promise that resolves with the parsed 3D object. + */ + async parseAsync( json ) { - if ( material.map ) { + const animations = this.parseAnimations( json.animations ); + const shapes = this.parseShapes( json.shapes ); + const geometries = this.parseGeometries( json.geometries, shapes ); - uniforms.map.value = material.map; + const images = await this.parseImagesAsync( json.images ); - refreshTransformUniform( material.map, uniforms.mapTransform ); + const textures = this.parseTextures( json.textures, images ); + const materials = this.parseMaterials( json.materials, textures ); - } + const object = this.parseObject( json.object, geometries, materials, textures, animations ); + const skeletons = this.parseSkeletons( json.skeletons, object ); - if ( material.alphaMap ) { + this.bindSkeletons( object, skeletons ); + this.bindLightTargets( object ); - uniforms.alphaMap.value = material.alphaMap; + return object; - refreshTransformUniform( material.alphaMap, uniforms.alphaMapTransform ); + } - } + // internals - if ( material.bumpMap ) { + parseShapes( json ) { - uniforms.bumpMap.value = material.bumpMap; + const shapes = {}; - refreshTransformUniform( material.bumpMap, uniforms.bumpMapTransform ); + if ( json !== undefined ) { - uniforms.bumpScale.value = material.bumpScale; + for ( let i = 0, l = json.length; i < l; i ++ ) { - if ( material.side === BackSide ) { + const shape = new Shape().fromJSON( json[ i ] ); - uniforms.bumpScale.value *= - 1; + shapes[ shape.uuid ] = shape; } } - if ( material.normalMap ) { + return shapes; - uniforms.normalMap.value = material.normalMap; + } - refreshTransformUniform( material.normalMap, uniforms.normalMapTransform ); + parseSkeletons( json, object ) { - uniforms.normalScale.value.copy( material.normalScale ); + const skeletons = {}; + const bones = {}; - if ( material.side === BackSide ) { + // generate bone lookup table - uniforms.normalScale.value.negate(); + object.traverse( function ( child ) { - } + if ( child.isBone ) bones[ child.uuid ] = child; - } + } ); - if ( material.displacementMap ) { + // create skeletons - uniforms.displacementMap.value = material.displacementMap; + if ( json !== undefined ) { - refreshTransformUniform( material.displacementMap, uniforms.displacementMapTransform ); + for ( let i = 0, l = json.length; i < l; i ++ ) { - uniforms.displacementScale.value = material.displacementScale; - uniforms.displacementBias.value = material.displacementBias; + const skeleton = new Skeleton().fromJSON( json[ i ], bones ); + + skeletons[ skeleton.uuid ] = skeleton; + + } } - if ( material.emissiveMap ) { + return skeletons; - uniforms.emissiveMap.value = material.emissiveMap; + } - refreshTransformUniform( material.emissiveMap, uniforms.emissiveMapTransform ); + parseGeometries( json, shapes ) { - } + const geometries = {}; - if ( material.specularMap ) { + if ( json !== undefined ) { - uniforms.specularMap.value = material.specularMap; + const bufferGeometryLoader = new BufferGeometryLoader(); - refreshTransformUniform( material.specularMap, uniforms.specularMapTransform ); + for ( let i = 0, l = json.length; i < l; i ++ ) { - } + let geometry; + const data = json[ i ]; - if ( material.alphaTest > 0 ) { + switch ( data.type ) { - uniforms.alphaTest.value = material.alphaTest; + case 'BufferGeometry': + case 'InstancedBufferGeometry': - } + geometry = bufferGeometryLoader.parse( data ); + break; - const envMap = properties.get( material ).envMap; + default: - if ( envMap ) { + if ( data.type in Geometries ) { - uniforms.envMap.value = envMap; + geometry = Geometries[ data.type ].fromJSON( data, shapes ); - uniforms.flipEnvMap.value = ( envMap.isCubeTexture && envMap.isRenderTargetTexture === false ) ? - 1 : 1; + } else { - uniforms.reflectivity.value = material.reflectivity; - uniforms.ior.value = material.ior; - uniforms.refractionRatio.value = material.refractionRatio; + console.warn( `THREE.ObjectLoader: Unsupported geometry type "${ data.type }"` ); - } + } - if ( material.lightMap ) { + } - uniforms.lightMap.value = material.lightMap; + geometry.uuid = data.uuid; - // artist-friendly light intensity scaling factor - const scaleFactor = ( renderer.useLegacyLights === true ) ? Math.PI : 1; + if ( data.name !== undefined ) geometry.name = data.name; + if ( data.userData !== undefined ) geometry.userData = data.userData; - uniforms.lightMapIntensity.value = material.lightMapIntensity * scaleFactor; + geometries[ data.uuid ] = geometry; - refreshTransformUniform( material.lightMap, uniforms.lightMapTransform ); + } } - if ( material.aoMap ) { + return geometries; - uniforms.aoMap.value = material.aoMap; - uniforms.aoMapIntensity.value = material.aoMapIntensity; + } - refreshTransformUniform( material.aoMap, uniforms.aoMapTransform ); + parseMaterials( json, textures ) { - } + const cache = {}; // MultiMaterial + const materials = {}; - } + if ( json !== undefined ) { - function refreshUniformsLine( uniforms, material ) { + const loader = new MaterialLoader(); + loader.setTextures( textures ); - uniforms.diffuse.value.copy( material.color ); - uniforms.opacity.value = material.opacity; + for ( let i = 0, l = json.length; i < l; i ++ ) { - if ( material.map ) { + const data = json[ i ]; - uniforms.map.value = material.map; + if ( cache[ data.uuid ] === undefined ) { - refreshTransformUniform( material.map, uniforms.mapTransform ); + cache[ data.uuid ] = loader.parse( data ); - } + } - } + materials[ data.uuid ] = cache[ data.uuid ]; - function refreshUniformsDash( uniforms, material ) { + } - uniforms.dashSize.value = material.dashSize; - uniforms.totalSize.value = material.dashSize + material.gapSize; - uniforms.scale.value = material.scale; + } + + return materials; } - function refreshUniformsPoints( uniforms, material, pixelRatio, height ) { + parseAnimations( json ) { - uniforms.diffuse.value.copy( material.color ); - uniforms.opacity.value = material.opacity; - uniforms.size.value = material.size * pixelRatio; - uniforms.scale.value = height * 0.5; + const animations = {}; - if ( material.map ) { + if ( json !== undefined ) { - uniforms.map.value = material.map; + for ( let i = 0; i < json.length; i ++ ) { - refreshTransformUniform( material.map, uniforms.uvTransform ); + const data = json[ i ]; - } + const clip = AnimationClip.parse( data ); - if ( material.alphaMap ) { + animations[ clip.uuid ] = clip; - uniforms.alphaMap.value = material.alphaMap; + } } - if ( material.alphaTest > 0 ) { + return animations; - uniforms.alphaTest.value = material.alphaTest; + } - } + parseImages( json, onLoad ) { - } + const scope = this; + const images = {}; - function refreshUniformsSprites( uniforms, material ) { + let loader; - uniforms.diffuse.value.copy( material.color ); - uniforms.opacity.value = material.opacity; - uniforms.rotation.value = material.rotation; + function loadImage( url ) { - if ( material.map ) { + scope.manager.itemStart( url ); - uniforms.map.value = material.map; + return loader.load( url, function () { - refreshTransformUniform( material.map, uniforms.mapTransform ); + scope.manager.itemEnd( url ); - } + }, undefined, function () { - if ( material.alphaMap ) { + scope.manager.itemError( url ); + scope.manager.itemEnd( url ); - uniforms.alphaMap.value = material.alphaMap; + } ); } - if ( material.alphaTest > 0 ) { - - uniforms.alphaTest.value = material.alphaTest; + function deserializeImage( image ) { - } + if ( typeof image === 'string' ) { - } + const url = image; - function refreshUniformsPhong( uniforms, material ) { + const path = /^(\/\/)|([a-z]+:(\/\/)?)/i.test( url ) ? url : scope.resourcePath + url; - uniforms.specular.value.copy( material.specular ); - uniforms.shininess.value = Math.max( material.shininess, 1e-4 ); // to prevent pow( 0.0, 0.0 ) + return loadImage( path ); - } + } else { - function refreshUniformsToon( uniforms, material ) { + if ( image.data ) { - if ( material.gradientMap ) { + return { + data: getTypedArray( image.type, image.data ), + width: image.width, + height: image.height + }; - uniforms.gradientMap.value = material.gradientMap; + } else { - } + return null; - } + } - function refreshUniformsStandard( uniforms, material ) { + } - uniforms.metalness.value = material.metalness; + } - if ( material.metalnessMap ) { + if ( json !== undefined && json.length > 0 ) { - uniforms.metalnessMap.value = material.metalnessMap; + const manager = new LoadingManager( onLoad ); - refreshTransformUniform( material.metalnessMap, uniforms.metalnessMapTransform ); + loader = new ImageLoader( manager ); + loader.setCrossOrigin( this.crossOrigin ); - } + for ( let i = 0, il = json.length; i < il; i ++ ) { - uniforms.roughness.value = material.roughness; + const image = json[ i ]; + const url = image.url; - if ( material.roughnessMap ) { + if ( Array.isArray( url ) ) { - uniforms.roughnessMap.value = material.roughnessMap; + // load array of images e.g CubeTexture - refreshTransformUniform( material.roughnessMap, uniforms.roughnessMapTransform ); + const imageArray = []; - } + for ( let j = 0, jl = url.length; j < jl; j ++ ) { - const envMap = properties.get( material ).envMap; + const currentUrl = url[ j ]; - if ( envMap ) { + const deserializedImage = deserializeImage( currentUrl ); - //uniforms.envMap.value = material.envMap; // part of uniforms common - uniforms.envMapIntensity.value = material.envMapIntensity; + if ( deserializedImage !== null ) { - } + if ( deserializedImage instanceof HTMLImageElement ) { - } + imageArray.push( deserializedImage ); - function refreshUniformsPhysical( uniforms, material, transmissionRenderTarget ) { + } else { - uniforms.ior.value = material.ior; // also part of uniforms common + // special case: handle array of data textures for cube textures - if ( material.sheen > 0 ) { + imageArray.push( new DataTexture( deserializedImage.data, deserializedImage.width, deserializedImage.height ) ); - uniforms.sheenColor.value.copy( material.sheenColor ).multiplyScalar( material.sheen ); + } - uniforms.sheenRoughness.value = material.sheenRoughness; + } - if ( material.sheenColorMap ) { + } - uniforms.sheenColorMap.value = material.sheenColorMap; + images[ image.uuid ] = new Source( imageArray ); - refreshTransformUniform( material.sheenColorMap, uniforms.sheenColorMapTransform ); + } else { - } + // load single image - if ( material.sheenRoughnessMap ) { + const deserializedImage = deserializeImage( image.url ); + images[ image.uuid ] = new Source( deserializedImage ); - uniforms.sheenRoughnessMap.value = material.sheenRoughnessMap; - refreshTransformUniform( material.sheenRoughnessMap, uniforms.sheenRoughnessMapTransform ); + } } } - if ( material.clearcoat > 0 ) { - - uniforms.clearcoat.value = material.clearcoat; - uniforms.clearcoatRoughness.value = material.clearcoatRoughness; + return images; - if ( material.clearcoatMap ) { + } - uniforms.clearcoatMap.value = material.clearcoatMap; + async parseImagesAsync( json ) { - refreshTransformUniform( material.clearcoatMap, uniforms.clearcoatMapTransform ); + const scope = this; + const images = {}; - } + let loader; - if ( material.clearcoatRoughnessMap ) { + async function deserializeImage( image ) { - uniforms.clearcoatRoughnessMap.value = material.clearcoatRoughnessMap; + if ( typeof image === 'string' ) { - refreshTransformUniform( material.clearcoatRoughnessMap, uniforms.clearcoatRoughnessMapTransform ); + const url = image; - } + const path = /^(\/\/)|([a-z]+:(\/\/)?)/i.test( url ) ? url : scope.resourcePath + url; - if ( material.clearcoatNormalMap ) { + return await loader.loadAsync( path ); - uniforms.clearcoatNormalMap.value = material.clearcoatNormalMap; + } else { - refreshTransformUniform( material.clearcoatNormalMap, uniforms.clearcoatNormalMapTransform ); + if ( image.data ) { - uniforms.clearcoatNormalScale.value.copy( material.clearcoatNormalScale ); + return { + data: getTypedArray( image.type, image.data ), + width: image.width, + height: image.height + }; - if ( material.side === BackSide ) { + } else { - uniforms.clearcoatNormalScale.value.negate(); + return null; } @@ -27335,9866 +47900,12877 @@ function WebGLMaterials( renderer, properties ) { } - if ( material.iridescence > 0 ) { - - uniforms.iridescence.value = material.iridescence; - uniforms.iridescenceIOR.value = material.iridescenceIOR; - uniforms.iridescenceThicknessMinimum.value = material.iridescenceThicknessRange[ 0 ]; - uniforms.iridescenceThicknessMaximum.value = material.iridescenceThicknessRange[ 1 ]; - - if ( material.iridescenceMap ) { - - uniforms.iridescenceMap.value = material.iridescenceMap; - - refreshTransformUniform( material.iridescenceMap, uniforms.iridescenceMapTransform ); + if ( json !== undefined && json.length > 0 ) { - } + loader = new ImageLoader( this.manager ); + loader.setCrossOrigin( this.crossOrigin ); - if ( material.iridescenceThicknessMap ) { + for ( let i = 0, il = json.length; i < il; i ++ ) { - uniforms.iridescenceThicknessMap.value = material.iridescenceThicknessMap; + const image = json[ i ]; + const url = image.url; - refreshTransformUniform( material.iridescenceThicknessMap, uniforms.iridescenceThicknessMapTransform ); + if ( Array.isArray( url ) ) { - } + // load array of images e.g CubeTexture - } + const imageArray = []; - if ( material.transmission > 0 ) { + for ( let j = 0, jl = url.length; j < jl; j ++ ) { - uniforms.transmission.value = material.transmission; - uniforms.transmissionSamplerMap.value = transmissionRenderTarget.texture; - uniforms.transmissionSamplerSize.value.set( transmissionRenderTarget.width, transmissionRenderTarget.height ); + const currentUrl = url[ j ]; - if ( material.transmissionMap ) { + const deserializedImage = await deserializeImage( currentUrl ); - uniforms.transmissionMap.value = material.transmissionMap; + if ( deserializedImage !== null ) { - refreshTransformUniform( material.transmissionMap, uniforms.transmissionMapTransform ); + if ( deserializedImage instanceof HTMLImageElement ) { - } + imageArray.push( deserializedImage ); - uniforms.thickness.value = material.thickness; + } else { - if ( material.thicknessMap ) { + // special case: handle array of data textures for cube textures - uniforms.thicknessMap.value = material.thicknessMap; + imageArray.push( new DataTexture( deserializedImage.data, deserializedImage.width, deserializedImage.height ) ); - refreshTransformUniform( material.thicknessMap, uniforms.thicknessMapTransform ); + } - } + } - uniforms.attenuationDistance.value = material.attenuationDistance; - uniforms.attenuationColor.value.copy( material.attenuationColor ); + } - } + images[ image.uuid ] = new Source( imageArray ); - if ( material.anisotropy > 0 ) { + } else { - uniforms.anisotropyVector.value.set( material.anisotropy * Math.cos( material.anisotropyRotation ), material.anisotropy * Math.sin( material.anisotropyRotation ) ); + // load single image - if ( material.anisotropyMap ) { + const deserializedImage = await deserializeImage( image.url ); + images[ image.uuid ] = new Source( deserializedImage ); - uniforms.anisotropyMap.value = material.anisotropyMap; + } } } - uniforms.specularIntensity.value = material.specularIntensity; - uniforms.specularColor.value.copy( material.specularColor ); - - if ( material.specularColorMap ) { + return images; - uniforms.specularColorMap.value = material.specularColorMap; + } - refreshTransformUniform( material.specularColorMap, uniforms.specularColorMapTransform ); + parseTextures( json, images ) { - } + function parseConstant( value, type ) { - if ( material.specularIntensityMap ) { + if ( typeof value === 'number' ) return value; - uniforms.specularIntensityMap.value = material.specularIntensityMap; + console.warn( 'THREE.ObjectLoader.parseTexture: Constant should be in numeric form.', value ); - refreshTransformUniform( material.specularIntensityMap, uniforms.specularIntensityMapTransform ); + return type[ value ]; } - } + const textures = {}; - function refreshUniformsMatcap( uniforms, material ) { + if ( json !== undefined ) { - if ( material.matcap ) { + for ( let i = 0, l = json.length; i < l; i ++ ) { - uniforms.matcap.value = material.matcap; + const data = json[ i ]; - } + if ( data.image === undefined ) { - } + console.warn( 'THREE.ObjectLoader: No "image" specified for', data.uuid ); - function refreshUniformsDistance( uniforms, material ) { + } - const light = properties.get( material ).light; + if ( images[ data.image ] === undefined ) { - uniforms.referencePosition.value.setFromMatrixPosition( light.matrixWorld ); - uniforms.nearDistance.value = light.shadow.camera.near; - uniforms.farDistance.value = light.shadow.camera.far; + console.warn( 'THREE.ObjectLoader: Undefined image', data.image ); - } + } - return { - refreshFogUniforms: refreshFogUniforms, - refreshMaterialUniforms: refreshMaterialUniforms - }; + const source = images[ data.image ]; + const image = source.data; -} + let texture; -function WebGLUniformsGroups( gl, info, capabilities, state ) { + if ( Array.isArray( image ) ) { - let buffers = {}; - let updateList = {}; - let allocatedBindingPoints = []; + texture = new CubeTexture(); - const maxBindingPoints = ( capabilities.isWebGL2 ) ? gl.getParameter( gl.MAX_UNIFORM_BUFFER_BINDINGS ) : 0; // binding points are global whereas block indices are per shader program + if ( image.length === 6 ) texture.needsUpdate = true; - function bind( uniformsGroup, program ) { + } else { - const webglProgram = program.program; - state.uniformBlockBinding( uniformsGroup, webglProgram ); + if ( image && image.data ) { - } + texture = new DataTexture(); - function update( uniformsGroup, program ) { + } else { - let buffer = buffers[ uniformsGroup.id ]; + texture = new Texture(); - if ( buffer === undefined ) { + } - prepareUniformsGroup( uniformsGroup ); + if ( image ) texture.needsUpdate = true; // textures can have undefined image data - buffer = createBuffer( uniformsGroup ); - buffers[ uniformsGroup.id ] = buffer; + } - uniformsGroup.addEventListener( 'dispose', onUniformsGroupsDispose ); + texture.source = source; - } + texture.uuid = data.uuid; - // ensure to update the binding points/block indices mapping for this program + if ( data.name !== undefined ) texture.name = data.name; - const webglProgram = program.program; - state.updateUBOMapping( uniformsGroup, webglProgram ); + if ( data.mapping !== undefined ) texture.mapping = parseConstant( data.mapping, TEXTURE_MAPPING ); + if ( data.channel !== undefined ) texture.channel = data.channel; - // update UBO once per frame + if ( data.offset !== undefined ) texture.offset.fromArray( data.offset ); + if ( data.repeat !== undefined ) texture.repeat.fromArray( data.repeat ); + if ( data.center !== undefined ) texture.center.fromArray( data.center ); + if ( data.rotation !== undefined ) texture.rotation = data.rotation; - const frame = info.render.frame; + if ( data.wrap !== undefined ) { - if ( updateList[ uniformsGroup.id ] !== frame ) { + texture.wrapS = parseConstant( data.wrap[ 0 ], TEXTURE_WRAPPING ); + texture.wrapT = parseConstant( data.wrap[ 1 ], TEXTURE_WRAPPING ); - updateBufferData( uniformsGroup ); + } - updateList[ uniformsGroup.id ] = frame; + if ( data.format !== undefined ) texture.format = data.format; + if ( data.internalFormat !== undefined ) texture.internalFormat = data.internalFormat; + if ( data.type !== undefined ) texture.type = data.type; + if ( data.colorSpace !== undefined ) texture.colorSpace = data.colorSpace; - } + if ( data.minFilter !== undefined ) texture.minFilter = parseConstant( data.minFilter, TEXTURE_FILTER ); + if ( data.magFilter !== undefined ) texture.magFilter = parseConstant( data.magFilter, TEXTURE_FILTER ); + if ( data.anisotropy !== undefined ) texture.anisotropy = data.anisotropy; - } + if ( data.flipY !== undefined ) texture.flipY = data.flipY; - function createBuffer( uniformsGroup ) { + if ( data.generateMipmaps !== undefined ) texture.generateMipmaps = data.generateMipmaps; + if ( data.premultiplyAlpha !== undefined ) texture.premultiplyAlpha = data.premultiplyAlpha; + if ( data.unpackAlignment !== undefined ) texture.unpackAlignment = data.unpackAlignment; + if ( data.compareFunction !== undefined ) texture.compareFunction = data.compareFunction; - // the setup of an UBO is independent of a particular shader program but global + if ( data.userData !== undefined ) texture.userData = data.userData; - const bindingPointIndex = allocateBindingPointIndex(); - uniformsGroup.__bindingPointIndex = bindingPointIndex; + textures[ data.uuid ] = texture; - const buffer = gl.createBuffer(); - const size = uniformsGroup.__size; - const usage = uniformsGroup.usage; + } - gl.bindBuffer( gl.UNIFORM_BUFFER, buffer ); - gl.bufferData( gl.UNIFORM_BUFFER, size, usage ); - gl.bindBuffer( gl.UNIFORM_BUFFER, null ); - gl.bindBufferBase( gl.UNIFORM_BUFFER, bindingPointIndex, buffer ); + } - return buffer; + return textures; } - function allocateBindingPointIndex() { + parseObject( data, geometries, materials, textures, animations ) { - for ( let i = 0; i < maxBindingPoints; i ++ ) { + let object; - if ( allocatedBindingPoints.indexOf( i ) === - 1 ) { + function getGeometry( name ) { - allocatedBindingPoints.push( i ); - return i; + if ( geometries[ name ] === undefined ) { + + console.warn( 'THREE.ObjectLoader: Undefined geometry', name ); } + return geometries[ name ]; + } - console.error( 'THREE.WebGLRenderer: Maximum number of simultaneously usable uniforms groups reached.' ); + function getMaterial( name ) { - return 0; + if ( name === undefined ) return undefined; - } + if ( Array.isArray( name ) ) { - function updateBufferData( uniformsGroup ) { + const array = []; - const buffer = buffers[ uniformsGroup.id ]; - const uniforms = uniformsGroup.uniforms; - const cache = uniformsGroup.__cache; + for ( let i = 0, l = name.length; i < l; i ++ ) { - gl.bindBuffer( gl.UNIFORM_BUFFER, buffer ); + const uuid = name[ i ]; - for ( let i = 0, il = uniforms.length; i < il; i ++ ) { + if ( materials[ uuid ] === undefined ) { - const uniform = uniforms[ i ]; + console.warn( 'THREE.ObjectLoader: Undefined material', uuid ); - // partly update the buffer if necessary + } - if ( hasUniformChanged( uniform, i, cache ) === true ) { + array.push( materials[ uuid ] ); - const offset = uniform.__offset; + } - const values = Array.isArray( uniform.value ) ? uniform.value : [ uniform.value ]; + return array; - let arrayOffset = 0; + } - for ( let i = 0; i < values.length; i ++ ) { + if ( materials[ name ] === undefined ) { - const value = values[ i ]; + console.warn( 'THREE.ObjectLoader: Undefined material', name ); - const info = getUniformSize( value ); + } - if ( typeof value === 'number' ) { + return materials[ name ]; - uniform.__data[ 0 ] = value; - gl.bufferSubData( gl.UNIFORM_BUFFER, offset + arrayOffset, uniform.__data ); + } - } else if ( value.isMatrix3 ) { + function getTexture( uuid ) { - // manually converting 3x3 to 3x4 + if ( textures[ uuid ] === undefined ) { - uniform.__data[ 0 ] = value.elements[ 0 ]; - uniform.__data[ 1 ] = value.elements[ 1 ]; - uniform.__data[ 2 ] = value.elements[ 2 ]; - uniform.__data[ 3 ] = value.elements[ 0 ]; - uniform.__data[ 4 ] = value.elements[ 3 ]; - uniform.__data[ 5 ] = value.elements[ 4 ]; - uniform.__data[ 6 ] = value.elements[ 5 ]; - uniform.__data[ 7 ] = value.elements[ 0 ]; - uniform.__data[ 8 ] = value.elements[ 6 ]; - uniform.__data[ 9 ] = value.elements[ 7 ]; - uniform.__data[ 10 ] = value.elements[ 8 ]; - uniform.__data[ 11 ] = value.elements[ 0 ]; + console.warn( 'THREE.ObjectLoader: Undefined texture', uuid ); - } else { + } - value.toArray( uniform.__data, arrayOffset ); + return textures[ uuid ]; - arrayOffset += info.storage / Float32Array.BYTES_PER_ELEMENT; + } - } + let geometry, material; - } + switch ( data.type ) { - gl.bufferSubData( gl.UNIFORM_BUFFER, offset, uniform.__data ); + case 'Scene': - } + object = new Scene(); - } + if ( data.background !== undefined ) { - gl.bindBuffer( gl.UNIFORM_BUFFER, null ); + if ( Number.isInteger( data.background ) ) { - } + object.background = new Color( data.background ); - function hasUniformChanged( uniform, index, cache ) { + } else { - const value = uniform.value; + object.background = getTexture( data.background ); - if ( cache[ index ] === undefined ) { + } - // cache entry does not exist so far + } - if ( typeof value === 'number' ) { + if ( data.environment !== undefined ) { - cache[ index ] = value; + object.environment = getTexture( data.environment ); - } else { + } - const values = Array.isArray( value ) ? value : [ value ]; + if ( data.fog !== undefined ) { - const tempValues = []; + if ( data.fog.type === 'Fog' ) { - for ( let i = 0; i < values.length; i ++ ) { + object.fog = new Fog( data.fog.color, data.fog.near, data.fog.far ); - tempValues.push( values[ i ].clone() ); + } else if ( data.fog.type === 'FogExp2' ) { - } + object.fog = new FogExp2( data.fog.color, data.fog.density ); - cache[ index ] = tempValues; + } - } + if ( data.fog.name !== '' ) { - return true; + object.fog.name = data.fog.name; - } else { + } - // compare current value with cached entry + } - if ( typeof value === 'number' ) { + if ( data.backgroundBlurriness !== undefined ) object.backgroundBlurriness = data.backgroundBlurriness; + if ( data.backgroundIntensity !== undefined ) object.backgroundIntensity = data.backgroundIntensity; + if ( data.backgroundRotation !== undefined ) object.backgroundRotation.fromArray( data.backgroundRotation ); - if ( cache[ index ] !== value ) { + if ( data.environmentIntensity !== undefined ) object.environmentIntensity = data.environmentIntensity; + if ( data.environmentRotation !== undefined ) object.environmentRotation.fromArray( data.environmentRotation ); - cache[ index ] = value; - return true; + break; - } + case 'PerspectiveCamera': - } else { + object = new PerspectiveCamera( data.fov, data.aspect, data.near, data.far ); - const cachedObjects = Array.isArray( cache[ index ] ) ? cache[ index ] : [ cache[ index ] ]; - const values = Array.isArray( value ) ? value : [ value ]; + if ( data.focus !== undefined ) object.focus = data.focus; + if ( data.zoom !== undefined ) object.zoom = data.zoom; + if ( data.filmGauge !== undefined ) object.filmGauge = data.filmGauge; + if ( data.filmOffset !== undefined ) object.filmOffset = data.filmOffset; + if ( data.view !== undefined ) object.view = Object.assign( {}, data.view ); - for ( let i = 0; i < cachedObjects.length; i ++ ) { + break; - const cachedObject = cachedObjects[ i ]; + case 'OrthographicCamera': - if ( cachedObject.equals( values[ i ] ) === false ) { + object = new OrthographicCamera( data.left, data.right, data.top, data.bottom, data.near, data.far ); - cachedObject.copy( values[ i ] ); - return true; + if ( data.zoom !== undefined ) object.zoom = data.zoom; + if ( data.view !== undefined ) object.view = Object.assign( {}, data.view ); - } + break; - } + case 'AmbientLight': - } + object = new AmbientLight( data.color, data.intensity ); - } + break; - return false; + case 'DirectionalLight': - } + object = new DirectionalLight( data.color, data.intensity ); + object.target = data.target || ''; - function prepareUniformsGroup( uniformsGroup ) { + break; - // determine total buffer size according to the STD140 layout - // Hint: STD140 is the only supported layout in WebGL 2 + case 'PointLight': - const uniforms = uniformsGroup.uniforms; + object = new PointLight( data.color, data.intensity, data.distance, data.decay ); - let offset = 0; // global buffer offset in bytes - const chunkSize = 16; // size of a chunk in bytes - let chunkOffset = 0; // offset within a single chunk in bytes + break; - for ( let i = 0, l = uniforms.length; i < l; i ++ ) { + case 'RectAreaLight': - const uniform = uniforms[ i ]; + object = new RectAreaLight( data.color, data.intensity, data.width, data.height ); - const infos = { - boundary: 0, // bytes - storage: 0 // bytes - }; + break; - const values = Array.isArray( uniform.value ) ? uniform.value : [ uniform.value ]; + case 'SpotLight': - for ( let j = 0, jl = values.length; j < jl; j ++ ) { + object = new SpotLight( data.color, data.intensity, data.distance, data.angle, data.penumbra, data.decay ); + object.target = data.target || ''; - const value = values[ j ]; + break; - const info = getUniformSize( value ); + case 'HemisphereLight': - infos.boundary += info.boundary; - infos.storage += info.storage; + object = new HemisphereLight( data.color, data.groundColor, data.intensity ); - } + break; - // the following two properties will be used for partial buffer updates + case 'LightProbe': - uniform.__data = new Float32Array( infos.storage / Float32Array.BYTES_PER_ELEMENT ); - uniform.__offset = offset; + object = new LightProbe().fromJSON( data ); - // + break; - if ( i > 0 ) { + case 'SkinnedMesh': - chunkOffset = offset % chunkSize; + geometry = getGeometry( data.geometry ); + material = getMaterial( data.material ); - const remainingSizeInChunk = chunkSize - chunkOffset; + object = new SkinnedMesh( geometry, material ); - // check for chunk overflow + if ( data.bindMode !== undefined ) object.bindMode = data.bindMode; + if ( data.bindMatrix !== undefined ) object.bindMatrix.fromArray( data.bindMatrix ); + if ( data.skeleton !== undefined ) object.skeleton = data.skeleton; - if ( chunkOffset !== 0 && ( remainingSizeInChunk - infos.boundary ) < 0 ) { + break; - // add padding and adjust offset + case 'Mesh': - offset += ( chunkSize - chunkOffset ); - uniform.__offset = offset; + geometry = getGeometry( data.geometry ); + material = getMaterial( data.material ); - } + object = new Mesh( geometry, material ); - } + break; - offset += infos.storage; + case 'InstancedMesh': - } + geometry = getGeometry( data.geometry ); + material = getMaterial( data.material ); + const count = data.count; + const instanceMatrix = data.instanceMatrix; + const instanceColor = data.instanceColor; - // ensure correct final padding + object = new InstancedMesh( geometry, material, count ); + object.instanceMatrix = new InstancedBufferAttribute( new Float32Array( instanceMatrix.array ), 16 ); + if ( instanceColor !== undefined ) object.instanceColor = new InstancedBufferAttribute( new Float32Array( instanceColor.array ), instanceColor.itemSize ); - chunkOffset = offset % chunkSize; + break; - if ( chunkOffset > 0 ) offset += ( chunkSize - chunkOffset ); + case 'BatchedMesh': - // + geometry = getGeometry( data.geometry ); + material = getMaterial( data.material ); - uniformsGroup.__size = offset; - uniformsGroup.__cache = {}; + object = new BatchedMesh( data.maxInstanceCount, data.maxVertexCount, data.maxIndexCount, material ); + object.geometry = geometry; + object.perObjectFrustumCulled = data.perObjectFrustumCulled; + object.sortObjects = data.sortObjects; - return this; + object._drawRanges = data.drawRanges; + object._reservedRanges = data.reservedRanges; - } + object._geometryInfo = data.geometryInfo.map( info => { - function getUniformSize( value ) { + let box = null; + let sphere = null; + if ( info.boundingBox !== undefined ) { - const info = { - boundary: 0, // bytes - storage: 0 // bytes - }; + box = new Box3().fromJSON( info.boundingBox ); - // determine sizes according to STD140 + } - if ( typeof value === 'number' ) { + if ( info.boundingSphere !== undefined ) { - // float/int + sphere = new Sphere().fromJSON( info.boundingSphere ); - info.boundary = 4; - info.storage = 4; + } - } else if ( value.isVector2 ) { + return { + ...info, + boundingBox: box, + boundingSphere: sphere + }; - // vec2 + } ); + object._instanceInfo = data.instanceInfo; - info.boundary = 8; - info.storage = 8; + object._availableInstanceIds = data._availableInstanceIds; + object._availableGeometryIds = data._availableGeometryIds; - } else if ( value.isVector3 || value.isColor ) { + object._nextIndexStart = data.nextIndexStart; + object._nextVertexStart = data.nextVertexStart; + object._geometryCount = data.geometryCount; - // vec3 + object._maxInstanceCount = data.maxInstanceCount; + object._maxVertexCount = data.maxVertexCount; + object._maxIndexCount = data.maxIndexCount; - info.boundary = 16; - info.storage = 12; // evil: vec3 must start on a 16-byte boundary but it only consumes 12 bytes + object._geometryInitialized = data.geometryInitialized; - } else if ( value.isVector4 ) { + object._matricesTexture = getTexture( data.matricesTexture.uuid ); - // vec4 + object._indirectTexture = getTexture( data.indirectTexture.uuid ); - info.boundary = 16; - info.storage = 16; + if ( data.colorsTexture !== undefined ) { - } else if ( value.isMatrix3 ) { + object._colorsTexture = getTexture( data.colorsTexture.uuid ); - // mat3 (in STD140 a 3x3 matrix is represented as 3x4) + } - info.boundary = 48; - info.storage = 48; + if ( data.boundingSphere !== undefined ) { - } else if ( value.isMatrix4 ) { + object.boundingSphere = new Sphere().fromJSON( data.boundingSphere ); - // mat4 + } - info.boundary = 64; - info.storage = 64; + if ( data.boundingBox !== undefined ) { - } else if ( value.isTexture ) { + object.boundingBox = new Box3().fromJSON( data.boundingBox ); - console.warn( 'THREE.WebGLRenderer: Texture samplers can not be part of an uniforms group.' ); + } - } else { + break; - console.warn( 'THREE.WebGLRenderer: Unsupported uniform value type.', value ); + case 'LOD': - } + object = new LOD(); - return info; + break; - } + case 'Line': - function onUniformsGroupsDispose( event ) { + object = new Line( getGeometry( data.geometry ), getMaterial( data.material ) ); - const uniformsGroup = event.target; + break; - uniformsGroup.removeEventListener( 'dispose', onUniformsGroupsDispose ); + case 'LineLoop': - const index = allocatedBindingPoints.indexOf( uniformsGroup.__bindingPointIndex ); - allocatedBindingPoints.splice( index, 1 ); + object = new LineLoop( getGeometry( data.geometry ), getMaterial( data.material ) ); - gl.deleteBuffer( buffers[ uniformsGroup.id ] ); + break; - delete buffers[ uniformsGroup.id ]; - delete updateList[ uniformsGroup.id ]; + case 'LineSegments': - } + object = new LineSegments( getGeometry( data.geometry ), getMaterial( data.material ) ); - function dispose() { + break; - for ( const id in buffers ) { + case 'PointCloud': + case 'Points': - gl.deleteBuffer( buffers[ id ] ); + object = new Points( getGeometry( data.geometry ), getMaterial( data.material ) ); - } + break; - allocatedBindingPoints = []; - buffers = {}; - updateList = {}; + case 'Sprite': - } + object = new Sprite( getMaterial( data.material ) ); - return { + break; - bind: bind, - update: update, + case 'Group': - dispose: dispose + object = new Group(); - }; + break; -} + case 'Bone': -function createCanvasElement() { + object = new Bone(); - const canvas = createElementNS( 'canvas' ); - canvas.style.display = 'block'; - return canvas; + break; -} + default: -class WebGLRenderer { + object = new Object3D(); - constructor( parameters = {} ) { + } - const { - canvas = createCanvasElement(), - context = null, - depth = true, - stencil = true, - alpha = false, - antialias = false, - premultipliedAlpha = true, - preserveDrawingBuffer = false, - powerPreference = 'default', - failIfMajorPerformanceCaveat = false, - } = parameters; + object.uuid = data.uuid; - this.isWebGLRenderer = true; + if ( data.name !== undefined ) object.name = data.name; - let _alpha; + if ( data.matrix !== undefined ) { - if ( context !== null ) { + object.matrix.fromArray( data.matrix ); - _alpha = context.getContextAttributes().alpha; + if ( data.matrixAutoUpdate !== undefined ) object.matrixAutoUpdate = data.matrixAutoUpdate; + if ( object.matrixAutoUpdate ) object.matrix.decompose( object.position, object.quaternion, object.scale ); } else { - _alpha = alpha; + if ( data.position !== undefined ) object.position.fromArray( data.position ); + if ( data.rotation !== undefined ) object.rotation.fromArray( data.rotation ); + if ( data.quaternion !== undefined ) object.quaternion.fromArray( data.quaternion ); + if ( data.scale !== undefined ) object.scale.fromArray( data.scale ); } - const uintClearColor = new Uint32Array( 4 ); - const intClearColor = new Int32Array( 4 ); + if ( data.up !== undefined ) object.up.fromArray( data.up ); - let currentRenderList = null; - let currentRenderState = null; + if ( data.castShadow !== undefined ) object.castShadow = data.castShadow; + if ( data.receiveShadow !== undefined ) object.receiveShadow = data.receiveShadow; - // render() can be called from within a callback triggered by another render. - // We track this so that the nested render call gets its list and state isolated from the parent render call. + if ( data.shadow ) { - const renderListStack = []; - const renderStateStack = []; + if ( data.shadow.intensity !== undefined ) object.shadow.intensity = data.shadow.intensity; + if ( data.shadow.bias !== undefined ) object.shadow.bias = data.shadow.bias; + if ( data.shadow.normalBias !== undefined ) object.shadow.normalBias = data.shadow.normalBias; + if ( data.shadow.radius !== undefined ) object.shadow.radius = data.shadow.radius; + if ( data.shadow.mapSize !== undefined ) object.shadow.mapSize.fromArray( data.shadow.mapSize ); + if ( data.shadow.camera !== undefined ) object.shadow.camera = this.parseObject( data.shadow.camera ); - // public properties + } - this.domElement = canvas; + if ( data.visible !== undefined ) object.visible = data.visible; + if ( data.frustumCulled !== undefined ) object.frustumCulled = data.frustumCulled; + if ( data.renderOrder !== undefined ) object.renderOrder = data.renderOrder; + if ( data.userData !== undefined ) object.userData = data.userData; + if ( data.layers !== undefined ) object.layers.mask = data.layers; - // Debug configuration container - this.debug = { + if ( data.children !== undefined ) { - /** - * Enables error checking and reporting when shader programs are being compiled - * @type {boolean} - */ - checkShaderErrors: true, - /** - * Callback for custom error reporting. - * @type {?Function} - */ - onShaderError: null - }; + const children = data.children; - // clearing + for ( let i = 0; i < children.length; i ++ ) { - this.autoClear = true; - this.autoClearColor = true; - this.autoClearDepth = true; - this.autoClearStencil = true; + object.add( this.parseObject( children[ i ], geometries, materials, textures, animations ) ); - // scene graph + } - this.sortObjects = true; + } - // user-defined clipping + if ( data.animations !== undefined ) { - this.clippingPlanes = []; - this.localClippingEnabled = false; + const objectAnimations = data.animations; - // physically based shading + for ( let i = 0; i < objectAnimations.length; i ++ ) { - this.outputColorSpace = SRGBColorSpace; + const uuid = objectAnimations[ i ]; - // physical lights + object.animations.push( animations[ uuid ] ); - this.useLegacyLights = true; + } - // tone mapping + } - this.toneMapping = NoToneMapping; - this.toneMappingExposure = 1.0; + if ( data.type === 'LOD' ) { - // internal properties + if ( data.autoUpdate !== undefined ) object.autoUpdate = data.autoUpdate; - const _this = this; + const levels = data.levels; - let _isContextLost = false; + for ( let l = 0; l < levels.length; l ++ ) { - // internal state cache + const level = levels[ l ]; + const child = object.getObjectByProperty( 'uuid', level.object ); - let _currentActiveCubeFace = 0; - let _currentActiveMipmapLevel = 0; - let _currentRenderTarget = null; - let _currentMaterialId = - 1; + if ( child !== undefined ) { - let _currentCamera = null; + object.addLevel( child, level.distance, level.hysteresis ); - const _currentViewport = new Vector4(); - const _currentScissor = new Vector4(); - let _currentScissorTest = null; + } - const _currentClearColor = new Color( 0x000000 ); - let _currentClearAlpha = 0; + } - // + } - let _width = canvas.width; - let _height = canvas.height; + return object; - let _pixelRatio = 1; - let _opaqueSort = null; - let _transparentSort = null; + } - const _viewport = new Vector4( 0, 0, _width, _height ); - const _scissor = new Vector4( 0, 0, _width, _height ); - let _scissorTest = false; + bindSkeletons( object, skeletons ) { - // frustum + if ( Object.keys( skeletons ).length === 0 ) return; - const _frustum = new Frustum(); + object.traverse( function ( child ) { - // clipping + if ( child.isSkinnedMesh === true && child.skeleton !== undefined ) { - let _clippingEnabled = false; - let _localClippingEnabled = false; + const skeleton = skeletons[ child.skeleton ]; - // transmission + if ( skeleton === undefined ) { - let _transmissionRenderTarget = null; + console.warn( 'THREE.ObjectLoader: No skeleton found with UUID:', child.skeleton ); - // camera matrices cache + } else { - const _projScreenMatrix = new Matrix4(); + child.bind( skeleton, child.bindMatrix ); - const _vector3 = new Vector3(); + } - const _emptyScene = { background: null, fog: null, environment: null, overrideMaterial: null, isScene: true }; + } - function getTargetPixelRatio() { + } ); - return _currentRenderTarget === null ? _pixelRatio : 1; + } - } + bindLightTargets( object ) { - // initialize + object.traverse( function ( child ) { - let _gl = context; + if ( child.isDirectionalLight || child.isSpotLight ) { - function getContext( contextNames, contextAttributes ) { + const uuid = child.target; - for ( let i = 0; i < contextNames.length; i ++ ) { + const target = object.getObjectByProperty( 'uuid', uuid ); - const contextName = contextNames[ i ]; - const context = canvas.getContext( contextName, contextAttributes ); - if ( context !== null ) return context; + if ( target !== undefined ) { - } + child.target = target; - return null; + } else { - } + child.target = new Object3D(); - try { + } - const contextAttributes = { - alpha: true, - depth, - stencil, - antialias, - premultipliedAlpha, - preserveDrawingBuffer, - powerPreference, - failIfMajorPerformanceCaveat, - }; + } - // OffscreenCanvas does not have setAttribute, see #22811 - if ( 'setAttribute' in canvas ) canvas.setAttribute( 'data-engine', `three.js r${REVISION}` ); + } ); - // event listeners must be registered before WebGL context is created, see #12753 - canvas.addEventListener( 'webglcontextlost', onContextLost, false ); - canvas.addEventListener( 'webglcontextrestored', onContextRestore, false ); - canvas.addEventListener( 'webglcontextcreationerror', onContextCreationError, false ); + } - if ( _gl === null ) { +} + +const TEXTURE_MAPPING = { + UVMapping: UVMapping, + CubeReflectionMapping: CubeReflectionMapping, + CubeRefractionMapping: CubeRefractionMapping, + EquirectangularReflectionMapping: EquirectangularReflectionMapping, + EquirectangularRefractionMapping: EquirectangularRefractionMapping, + CubeUVReflectionMapping: CubeUVReflectionMapping +}; + +const TEXTURE_WRAPPING = { + RepeatWrapping: RepeatWrapping, + ClampToEdgeWrapping: ClampToEdgeWrapping, + MirroredRepeatWrapping: MirroredRepeatWrapping +}; - const contextNames = [ 'webgl2', 'webgl', 'experimental-webgl' ]; +const TEXTURE_FILTER = { + NearestFilter: NearestFilter, + NearestMipmapNearestFilter: NearestMipmapNearestFilter, + NearestMipmapLinearFilter: NearestMipmapLinearFilter, + LinearFilter: LinearFilter, + LinearMipmapNearestFilter: LinearMipmapNearestFilter, + LinearMipmapLinearFilter: LinearMipmapLinearFilter +}; - if ( _this.isWebGL1Renderer === true ) { +const _errorMap = new WeakMap(); - contextNames.shift(); +/** + * A loader for loading images as an [ImageBitmap]{@link https://developer.mozilla.org/en-US/docs/Web/API/ImageBitmap}. + * An `ImageBitmap` provides an asynchronous and resource efficient pathway to prepare + * textures for rendering. + * + * Note that {@link Texture#flipY} and {@link Texture#premultiplyAlpha} are ignored with image bitmaps. + * They needs these configuration on bitmap creation unlike regular images need them on uploading to GPU. + * + * You need to set the equivalent options via {@link ImageBitmapLoader#setOptions} instead. + * + * Also note that unlike {@link FileLoader}, this loader avoids multiple concurrent requests to the same URL only if `Cache` is enabled. + * + * ```js + * const loader = new THREE.ImageBitmapLoader(); + * loader.setOptions( { imageOrientation: 'flipY' } ); // set options if needed + * const imageBitmap = await loader.loadAsync( 'image.png' ); + * + * const texture = new THREE.Texture( imageBitmap ); + * texture.needsUpdate = true; + * ``` + * + * @augments Loader + */ +class ImageBitmapLoader extends Loader { - } + /** + * Constructs a new image bitmap loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ + constructor( manager ) { - _gl = getContext( contextNames, contextAttributes ); + super( manager ); - if ( _gl === null ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isImageBitmapLoader = true; - if ( getContext( contextNames ) ) { + if ( typeof createImageBitmap === 'undefined' ) { - throw new Error( 'Error creating WebGL context with your selected attributes.' ); + console.warn( 'THREE.ImageBitmapLoader: createImageBitmap() not supported.' ); - } else { + } - throw new Error( 'Error creating WebGL context.' ); + if ( typeof fetch === 'undefined' ) { - } + console.warn( 'THREE.ImageBitmapLoader: fetch() not supported.' ); - } + } - } + /** + * Represents the loader options. + * + * @type {Object} + * @default {premultiplyAlpha:'none'} + */ + this.options = { premultiplyAlpha: 'none' }; - if ( _gl instanceof WebGLRenderingContext ) { // @deprecated, r153 + } - console.warn( 'THREE.WebGLRenderer: WebGL 1 support was deprecated in r153 and will be removed in r163.' ); + /** + * Sets the given loader options. The structure of the object must match the `options` parameter of + * [createImageBitmap]{@link https://developer.mozilla.org/en-US/docs/Web/API/Window/createImageBitmap}. + * + * @param {Object} options - The loader options to set. + * @return {ImageBitmapLoader} A reference to this image bitmap loader. + */ + setOptions( options ) { - } + this.options = options; - // Some experimental-webgl implementations do not have getShaderPrecisionFormat + return this; - if ( _gl.getShaderPrecisionFormat === undefined ) { + } - _gl.getShaderPrecisionFormat = function () { + /** + * Starts loading from the given URL and pass the loaded image bitmap to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(ImageBitmap)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Unsupported in this loader. + * @param {onErrorCallback} onError - Executed when errors occur. + * @return {ImageBitmap|undefined} The image bitmap. + */ + load( url, onLoad, onProgress, onError ) { - return { 'rangeMin': 1, 'rangeMax': 1, 'precision': 1 }; + if ( url === undefined ) url = ''; - }; + if ( this.path !== undefined ) url = this.path + url; - } + url = this.manager.resolveURL( url ); - } catch ( error ) { + const scope = this; - console.error( 'THREE.WebGLRenderer: ' + error.message ); - throw error; + const cached = Cache.get( `image-bitmap:${url}` ); - } + if ( cached !== undefined ) { - let extensions, capabilities, state, info; - let properties, textures, cubemaps, cubeuvmaps, attributes, geometries, objects; - let programCache, materials, renderLists, renderStates, clipping, shadowMap; + scope.manager.itemStart( url ); - let background, morphtargets, bufferRenderer, indexedBufferRenderer; + // If cached is a promise, wait for it to resolve + if ( cached.then ) { - let utils, bindingStates, uniformsGroups; + cached.then( imageBitmap => { - function initGLContext() { + // check if there is an error for the cached promise - extensions = new WebGLExtensions( _gl ); + if ( _errorMap.has( cached ) === true ) { - capabilities = new WebGLCapabilities( _gl, extensions, parameters ); + if ( onError ) onError( _errorMap.get( cached ) ); - extensions.init( capabilities ); + scope.manager.itemError( url ); + scope.manager.itemEnd( url ); - utils = new WebGLUtils( _gl, extensions, capabilities ); + } else { - state = new WebGLState( _gl, extensions, capabilities ); + if ( onLoad ) onLoad( imageBitmap ); - info = new WebGLInfo( _gl ); - properties = new WebGLProperties(); - textures = new WebGLTextures( _gl, extensions, state, properties, capabilities, utils, info ); - cubemaps = new WebGLCubeMaps( _this ); - cubeuvmaps = new WebGLCubeUVMaps( _this ); - attributes = new WebGLAttributes( _gl, capabilities ); - bindingStates = new WebGLBindingStates( _gl, extensions, attributes, capabilities ); - geometries = new WebGLGeometries( _gl, attributes, info, bindingStates ); - objects = new WebGLObjects( _gl, geometries, attributes, info ); - morphtargets = new WebGLMorphtargets( _gl, capabilities, textures ); - clipping = new WebGLClipping( properties ); - programCache = new WebGLPrograms( _this, cubemaps, cubeuvmaps, extensions, capabilities, bindingStates, clipping ); - materials = new WebGLMaterials( _this, properties ); - renderLists = new WebGLRenderLists(); - renderStates = new WebGLRenderStates( extensions, capabilities ); - background = new WebGLBackground( _this, cubemaps, cubeuvmaps, state, objects, _alpha, premultipliedAlpha ); - shadowMap = new WebGLShadowMap( _this, objects, capabilities ); - uniformsGroups = new WebGLUniformsGroups( _gl, info, capabilities, state ); + scope.manager.itemEnd( url ); - bufferRenderer = new WebGLBufferRenderer( _gl, extensions, info, capabilities ); - indexedBufferRenderer = new WebGLIndexedBufferRenderer( _gl, extensions, info, capabilities ); + return imageBitmap; - info.programs = programCache.programs; + } - _this.capabilities = capabilities; - _this.extensions = extensions; - _this.properties = properties; - _this.renderLists = renderLists; - _this.shadowMap = shadowMap; - _this.state = state; - _this.info = info; + } ); - } + return; - initGLContext(); + } - // xr + // If cached is not a promise (i.e., it's already an imageBitmap) + setTimeout( function () { - const xr = new WebXRManager( _this, _gl ); + if ( onLoad ) onLoad( cached ); - this.xr = xr; + scope.manager.itemEnd( url ); - // API + }, 0 ); - this.getContext = function () { + return cached; - return _gl; + } - }; + const fetchOptions = {}; + fetchOptions.credentials = ( this.crossOrigin === 'anonymous' ) ? 'same-origin' : 'include'; + fetchOptions.headers = this.requestHeader; - this.getContextAttributes = function () { + const promise = fetch( url, fetchOptions ).then( function ( res ) { - return _gl.getContextAttributes(); + return res.blob(); - }; + } ).then( function ( blob ) { - this.forceContextLoss = function () { + return createImageBitmap( blob, Object.assign( scope.options, { colorSpaceConversion: 'none' } ) ); - const extension = extensions.get( 'WEBGL_lose_context' ); - if ( extension ) extension.loseContext(); + } ).then( function ( imageBitmap ) { - }; + Cache.add( `image-bitmap:${url}`, imageBitmap ); - this.forceContextRestore = function () { + if ( onLoad ) onLoad( imageBitmap ); - const extension = extensions.get( 'WEBGL_lose_context' ); - if ( extension ) extension.restoreContext(); + scope.manager.itemEnd( url ); - }; + return imageBitmap; - this.getPixelRatio = function () { + } ).catch( function ( e ) { - return _pixelRatio; + if ( onError ) onError( e ); - }; + _errorMap.set( promise, e ); - this.setPixelRatio = function ( value ) { + Cache.remove( `image-bitmap:${url}` ); - if ( value === undefined ) return; + scope.manager.itemError( url ); + scope.manager.itemEnd( url ); - _pixelRatio = value; + } ); - this.setSize( _width, _height, false ); + Cache.add( `image-bitmap:${url}`, promise ); + scope.manager.itemStart( url ); - }; + } - this.getSize = function ( target ) { +} - return target.set( _width, _height ); +let _context; - }; +/** + * Manages the global audio context in the engine. + * + * @hideconstructor + */ +class AudioContext { - this.setSize = function ( width, height, updateStyle = true ) { + /** + * Returns the global native audio context. + * + * @return {AudioContext} The native audio context. + */ + static getContext() { - if ( xr.isPresenting ) { + if ( _context === undefined ) { - console.warn( 'THREE.WebGLRenderer: Can\'t change size while VR device is presenting.' ); - return; + _context = new ( window.AudioContext || window.webkitAudioContext )(); - } + } - _width = width; - _height = height; + return _context; - canvas.width = Math.floor( width * _pixelRatio ); - canvas.height = Math.floor( height * _pixelRatio ); + } - if ( updateStyle === true ) { + /** + * Allows to set the global native audio context from outside. + * + * @param {AudioContext} value - The native context to set. + */ + static setContext( value ) { - canvas.style.width = width + 'px'; - canvas.style.height = height + 'px'; + _context = value; - } + } - this.setViewport( 0, 0, width, height ); +} - }; +/** + * Class for loading audio buffers. Audios are internally + * loaded via {@link FileLoader}. + * + * ```js + * const audioListener = new THREE.AudioListener(); + * const ambientSound = new THREE.Audio( audioListener ); + * + * const loader = new THREE.AudioLoader(); + * const audioBuffer = await loader.loadAsync( 'audio/ambient_ocean.ogg' ); + * + * ambientSound.setBuffer( audioBuffer ); + * ambientSound.play(); + * ``` + * + * @augments Loader + */ +class AudioLoader extends Loader { - this.getDrawingBufferSize = function ( target ) { + /** + * Constructs a new audio loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ + constructor( manager ) { - return target.set( _width * _pixelRatio, _height * _pixelRatio ).floor(); + super( manager ); - }; + } - this.setDrawingBufferSize = function ( width, height, pixelRatio ) { + /** + * Starts loading from the given URL and passes the loaded audio buffer + * to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(AudioBuffer)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ + load( url, onLoad, onProgress, onError ) { - _width = width; - _height = height; + const scope = this; - _pixelRatio = pixelRatio; + const loader = new FileLoader( this.manager ); + loader.setResponseType( 'arraybuffer' ); + loader.setPath( this.path ); + loader.setRequestHeader( this.requestHeader ); + loader.setWithCredentials( this.withCredentials ); + loader.load( url, function ( buffer ) { - canvas.width = Math.floor( width * pixelRatio ); - canvas.height = Math.floor( height * pixelRatio ); + try { - this.setViewport( 0, 0, width, height ); + // Create a copy of the buffer. The `decodeAudioData` method + // detaches the buffer when complete, preventing reuse. + const bufferCopy = buffer.slice( 0 ); - }; + const context = AudioContext.getContext(); + context.decodeAudioData( bufferCopy, function ( audioBuffer ) { - this.getCurrentViewport = function ( target ) { + onLoad( audioBuffer ); - return target.copy( _currentViewport ); + } ).catch( handleError ); - }; + } catch ( e ) { - this.getViewport = function ( target ) { + handleError( e ); - return target.copy( _viewport ); + } - }; + }, onProgress, onError ); - this.setViewport = function ( x, y, width, height ) { + function handleError( e ) { - if ( x.isVector4 ) { + if ( onError ) { - _viewport.set( x.x, x.y, x.z, x.w ); + onError( e ); } else { - _viewport.set( x, y, width, height ); + console.error( e ); } - state.viewport( _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ).floor() ); + scope.manager.itemError( url ); - }; + } - this.getScissor = function ( target ) { + } - return target.copy( _scissor ); +} - }; +const _eyeRight = /*@__PURE__*/ new Matrix4(); +const _eyeLeft = /*@__PURE__*/ new Matrix4(); +const _projectionMatrix = /*@__PURE__*/ new Matrix4(); - this.setScissor = function ( x, y, width, height ) { +/** + * A special type of camera that uses two perspective cameras with + * stereoscopic projection. Can be used for rendering stereo effects + * like [3D Anaglyph]{@link https://en.wikipedia.org/wiki/Anaglyph_3D} or + * [Parallax Barrier]{@link https://en.wikipedia.org/wiki/parallax_barrier}. + */ +class StereoCamera { - if ( x.isVector4 ) { + /** + * Constructs a new stereo camera. + */ + constructor() { - _scissor.set( x.x, x.y, x.z, x.w ); + /** + * The type property is used for detecting the object type + * in context of serialization/deserialization. + * + * @type {string} + * @readonly + */ + this.type = 'StereoCamera'; - } else { + /** + * The aspect. + * + * @type {number} + * @default 1 + */ + this.aspect = 1; - _scissor.set( x, y, width, height ); + /** + * The eye separation which represents the distance + * between the left and right camera. + * + * @type {number} + * @default 0.064 + */ + this.eyeSep = 0.064; - } + /** + * The camera representing the left eye. This is added to layer `1` so objects to be + * rendered by the left camera must also be added to this layer. + * + * @type {PerspectiveCamera} + */ + this.cameraL = new PerspectiveCamera(); + this.cameraL.layers.enable( 1 ); + this.cameraL.matrixAutoUpdate = false; - state.scissor( _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ).floor() ); + /** + * The camera representing the right eye. This is added to layer `2` so objects to be + * rendered by the right camera must also be added to this layer. + * + * @type {PerspectiveCamera} + */ + this.cameraR = new PerspectiveCamera(); + this.cameraR.layers.enable( 2 ); + this.cameraR.matrixAutoUpdate = false; + this._cache = { + focus: null, + fov: null, + aspect: null, + near: null, + far: null, + zoom: null, + eyeSep: null }; - this.getScissorTest = function () { + } - return _scissorTest; + /** + * Updates the stereo camera based on the given perspective camera. + * + * @param {PerspectiveCamera} camera - The perspective camera. + */ + update( camera ) { - }; + const cache = this._cache; - this.setScissorTest = function ( boolean ) { + const needsUpdate = cache.focus !== camera.focus || cache.fov !== camera.fov || + cache.aspect !== camera.aspect * this.aspect || cache.near !== camera.near || + cache.far !== camera.far || cache.zoom !== camera.zoom || cache.eyeSep !== this.eyeSep; - state.setScissorTest( _scissorTest = boolean ); + if ( needsUpdate ) { - }; + cache.focus = camera.focus; + cache.fov = camera.fov; + cache.aspect = camera.aspect * this.aspect; + cache.near = camera.near; + cache.far = camera.far; + cache.zoom = camera.zoom; + cache.eyeSep = this.eyeSep; - this.setOpaqueSort = function ( method ) { + // Off-axis stereoscopic effect based on + // http://paulbourke.net/stereographics/stereorender/ - _opaqueSort = method; + _projectionMatrix.copy( camera.projectionMatrix ); + const eyeSepHalf = cache.eyeSep / 2; + const eyeSepOnProjection = eyeSepHalf * cache.near / cache.focus; + const ymax = ( cache.near * Math.tan( DEG2RAD * cache.fov * 0.5 ) ) / cache.zoom; + let xmin, xmax; - }; + // translate xOffset - this.setTransparentSort = function ( method ) { + _eyeLeft.elements[ 12 ] = - eyeSepHalf; + _eyeRight.elements[ 12 ] = eyeSepHalf; - _transparentSort = method; + // for left eye - }; + xmin = - ymax * cache.aspect + eyeSepOnProjection; + xmax = ymax * cache.aspect + eyeSepOnProjection; - // Clearing + _projectionMatrix.elements[ 0 ] = 2 * cache.near / ( xmax - xmin ); + _projectionMatrix.elements[ 8 ] = ( xmax + xmin ) / ( xmax - xmin ); - this.getClearColor = function ( target ) { + this.cameraL.projectionMatrix.copy( _projectionMatrix ); - return target.copy( background.getClearColor() ); + // for right eye - }; + xmin = - ymax * cache.aspect - eyeSepOnProjection; + xmax = ymax * cache.aspect - eyeSepOnProjection; - this.setClearColor = function () { + _projectionMatrix.elements[ 0 ] = 2 * cache.near / ( xmax - xmin ); + _projectionMatrix.elements[ 8 ] = ( xmax + xmin ) / ( xmax - xmin ); - background.setClearColor.apply( background, arguments ); + this.cameraR.projectionMatrix.copy( _projectionMatrix ); - }; + } - this.getClearAlpha = function () { + this.cameraL.matrixWorld.copy( camera.matrixWorld ).multiply( _eyeLeft ); + this.cameraR.matrixWorld.copy( camera.matrixWorld ).multiply( _eyeRight ); - return background.getClearAlpha(); + } - }; +} - this.setClearAlpha = function () { +/** + * This type of camera can be used in order to efficiently render a scene with a + * predefined set of cameras. This is an important performance aspect for + * rendering VR scenes. + * + * An instance of `ArrayCamera` always has an array of sub cameras. It's mandatory + * to define for each sub camera the `viewport` property which determines the + * part of the viewport that is rendered with this camera. + * + * @augments PerspectiveCamera + */ +class ArrayCamera extends PerspectiveCamera { - background.setClearAlpha.apply( background, arguments ); + /** + * Constructs a new array camera. + * + * @param {Array} [array=[]] - An array of perspective sub cameras. + */ + constructor( array = [] ) { - }; + super(); - this.clear = function ( color = true, depth = true, stencil = true ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isArrayCamera = true; - let bits = 0; + /** + * Whether this camera is used with multiview rendering or not. + * + * @type {boolean} + * @readonly + * @default false + */ + this.isMultiViewCamera = false; - if ( color ) { + /** + * An array of perspective sub cameras. + * + * @type {Array} + */ + this.cameras = array; - // check if we're trying to clear an integer target - let isIntegerFormat = false; - if ( _currentRenderTarget !== null ) { + } - const targetFormat = _currentRenderTarget.texture.format; - isIntegerFormat = targetFormat === RGBAIntegerFormat || - targetFormat === RGIntegerFormat || - targetFormat === RedIntegerFormat; +} - } +/** + * Class for keeping track of time. + */ +class Clock { - // use the appropriate clear functions to clear the target if it's a signed - // or unsigned integer target - if ( isIntegerFormat ) { + /** + * Constructs a new clock. + * + * @param {boolean} [autoStart=true] - Whether to automatically start the clock when + * `getDelta()` is called for the first time. + */ + constructor( autoStart = true ) { - const targetType = _currentRenderTarget.texture.type; - const isUnsignedType = targetType === UnsignedByteType || - targetType === UnsignedIntType || - targetType === UnsignedShortType || - targetType === UnsignedInt248Type || - targetType === UnsignedShort4444Type || - targetType === UnsignedShort5551Type; + /** + * If set to `true`, the clock starts automatically when `getDelta()` is called + * for the first time. + * + * @type {boolean} + * @default true + */ + this.autoStart = autoStart; - const clearColor = background.getClearColor(); - const a = background.getClearAlpha(); - const r = clearColor.r; - const g = clearColor.g; - const b = clearColor.b; + /** + * Holds the time at which the clock's `start()` method was last called. + * + * @type {number} + * @default 0 + */ + this.startTime = 0; - const __webglFramebuffer = properties.get( _currentRenderTarget ).__webglFramebuffer; + /** + * Holds the time at which the clock's `start()`, `getElapsedTime()` or + * `getDelta()` methods were last called. + * + * @type {number} + * @default 0 + */ + this.oldTime = 0; - if ( isUnsignedType ) { + /** + * Keeps track of the total time that the clock has been running. + * + * @type {number} + * @default 0 + */ + this.elapsedTime = 0; - uintClearColor[ 0 ] = r; - uintClearColor[ 1 ] = g; - uintClearColor[ 2 ] = b; - uintClearColor[ 3 ] = a; - _gl.clearBufferuiv( _gl.COLOR, __webglFramebuffer, uintClearColor ); + /** + * Whether the clock is running or not. + * + * @type {boolean} + * @default true + */ + this.running = false; - } else { + } - intClearColor[ 0 ] = r; - intClearColor[ 1 ] = g; - intClearColor[ 2 ] = b; - intClearColor[ 3 ] = a; - _gl.clearBufferiv( _gl.COLOR, __webglFramebuffer, intClearColor ); + /** + * Starts the clock. When `autoStart` is set to `true`, the method is automatically + * called by the class. + */ + start() { - } + this.startTime = performance.now(); - } else { + this.oldTime = this.startTime; + this.elapsedTime = 0; + this.running = true; - bits |= _gl.COLOR_BUFFER_BIT; + } - } + /** + * Stops the clock. + */ + stop() { - } + this.getElapsedTime(); + this.running = false; + this.autoStart = false; - if ( depth ) bits |= _gl.DEPTH_BUFFER_BIT; - if ( stencil ) bits |= _gl.STENCIL_BUFFER_BIT; + } - _gl.clear( bits ); + /** + * Returns the elapsed time in seconds. + * + * @return {number} The elapsed time. + */ + getElapsedTime() { - }; + this.getDelta(); + return this.elapsedTime; - this.clearColor = function () { + } - this.clear( true, false, false ); + /** + * Returns the delta time in seconds. + * + * @return {number} The delta time. + */ + getDelta() { - }; + let diff = 0; - this.clearDepth = function () { + if ( this.autoStart && ! this.running ) { - this.clear( false, true, false ); + this.start(); + return 0; - }; + } - this.clearStencil = function () { + if ( this.running ) { - this.clear( false, false, true ); + const newTime = performance.now(); - }; + diff = ( newTime - this.oldTime ) / 1000; + this.oldTime = newTime; - // + this.elapsedTime += diff; - this.dispose = function () { + } - canvas.removeEventListener( 'webglcontextlost', onContextLost, false ); - canvas.removeEventListener( 'webglcontextrestored', onContextRestore, false ); - canvas.removeEventListener( 'webglcontextcreationerror', onContextCreationError, false ); + return diff; - renderLists.dispose(); - renderStates.dispose(); - properties.dispose(); - cubemaps.dispose(); - cubeuvmaps.dispose(); - objects.dispose(); - bindingStates.dispose(); - uniformsGroups.dispose(); - programCache.dispose(); + } - xr.dispose(); +} - xr.removeEventListener( 'sessionstart', onXRSessionStart ); - xr.removeEventListener( 'sessionend', onXRSessionEnd ); +const _position$1 = /*@__PURE__*/ new Vector3(); +const _quaternion$1 = /*@__PURE__*/ new Quaternion(); +const _scale$1 = /*@__PURE__*/ new Vector3(); - if ( _transmissionRenderTarget ) { +const _forward = /*@__PURE__*/ new Vector3(); +const _up = /*@__PURE__*/ new Vector3(); - _transmissionRenderTarget.dispose(); - _transmissionRenderTarget = null; +/** + * The class represents a virtual listener of the all positional and non-positional audio effects + * in the scene. A three.js application usually creates a single listener. It is a mandatory + * constructor parameter for audios entities like {@link Audio} and {@link PositionalAudio}. + * + * In most cases, the listener object is a child of the camera. So the 3D transformation of the + * camera represents the 3D transformation of the listener. + * + * @augments Object3D + */ +class AudioListener extends Object3D { - } + /** + * Constructs a new audio listener. + */ + constructor() { - animation.stop(); + super(); - }; + this.type = 'AudioListener'; - // Events + /** + * The native audio context. + * + * @type {AudioContext} + * @readonly + */ + this.context = AudioContext.getContext(); - function onContextLost( event ) { + /** + * The gain node used for volume control. + * + * @type {GainNode} + * @readonly + */ + this.gain = this.context.createGain(); + this.gain.connect( this.context.destination ); - event.preventDefault(); + /** + * An optional filter. + * + * Defined via {@link AudioListener#setFilter}. + * + * @type {?AudioNode} + * @default null + * @readonly + */ + this.filter = null; - console.log( 'THREE.WebGLRenderer: Context Lost.' ); + /** + * Time delta values required for `linearRampToValueAtTime()` usage. + * + * @type {number} + * @default 0 + * @readonly + */ + this.timeDelta = 0; - _isContextLost = true; + // private - } + this._clock = new Clock(); - function onContextRestore( /* event */ ) { + } - console.log( 'THREE.WebGLRenderer: Context Restored.' ); + /** + * Returns the listener's input node. + * + * This method is used by other audio nodes to connect to this listener. + * + * @return {GainNode} The input node. + */ + getInput() { - _isContextLost = false; + return this.gain; - const infoAutoReset = info.autoReset; - const shadowMapEnabled = shadowMap.enabled; - const shadowMapAutoUpdate = shadowMap.autoUpdate; - const shadowMapNeedsUpdate = shadowMap.needsUpdate; - const shadowMapType = shadowMap.type; + } - initGLContext(); + /** + * Removes the current filter from this listener. + * + * @return {AudioListener} A reference to this listener. + */ + removeFilter() { - info.autoReset = infoAutoReset; - shadowMap.enabled = shadowMapEnabled; - shadowMap.autoUpdate = shadowMapAutoUpdate; - shadowMap.needsUpdate = shadowMapNeedsUpdate; - shadowMap.type = shadowMapType; + if ( this.filter !== null ) { + + this.gain.disconnect( this.filter ); + this.filter.disconnect( this.context.destination ); + this.gain.connect( this.context.destination ); + this.filter = null; } - function onContextCreationError( event ) { + return this; - console.error( 'THREE.WebGLRenderer: A WebGL context could not be created. Reason: ', event.statusMessage ); + } - } + /** + * Returns the current set filter. + * + * @return {?AudioNode} The filter. + */ + getFilter() { - function onMaterialDispose( event ) { + return this.filter; - const material = event.target; + } - material.removeEventListener( 'dispose', onMaterialDispose ); + /** + * Sets the given filter to this listener. + * + * @param {AudioNode} value - The filter to set. + * @return {AudioListener} A reference to this listener. + */ + setFilter( value ) { - deallocateMaterial( material ); + if ( this.filter !== null ) { - } + this.gain.disconnect( this.filter ); + this.filter.disconnect( this.context.destination ); - // Buffer deallocation + } else { - function deallocateMaterial( material ) { + this.gain.disconnect( this.context.destination ); - releaseMaterialProgramReferences( material ); + } - properties.remove( material ); + this.filter = value; + this.gain.connect( this.filter ); + this.filter.connect( this.context.destination ); - } + return this; + } - function releaseMaterialProgramReferences( material ) { + /** + * Returns the applications master volume. + * + * @return {number} The master volume. + */ + getMasterVolume() { - const programs = properties.get( material ).programs; + return this.gain.gain.value; - if ( programs !== undefined ) { + } - programs.forEach( function ( program ) { + /** + * Sets the applications master volume. This volume setting affects + * all audio nodes in the scene. + * + * @param {number} value - The master volume to set. + * @return {AudioListener} A reference to this listener. + */ + setMasterVolume( value ) { - programCache.releaseProgram( program ); + this.gain.gain.setTargetAtTime( value, this.context.currentTime, 0.01 ); - } ); + return this; - if ( material.isShaderMaterial ) { + } - programCache.releaseShaderCache( material ); + updateMatrixWorld( force ) { - } + super.updateMatrixWorld( force ); - } + const listener = this.context.listener; - } + this.timeDelta = this._clock.getDelta(); - // Buffer rendering + this.matrixWorld.decompose( _position$1, _quaternion$1, _scale$1 ); - this.renderBufferDirect = function ( camera, scene, geometry, material, object, group ) { + // the initial forward and up directions must be orthogonal + _forward.set( 0, 0, -1 ).applyQuaternion( _quaternion$1 ); + _up.set( 0, 1, 0 ).applyQuaternion( _quaternion$1 ); - if ( scene === null ) scene = _emptyScene; // renderBufferDirect second parameter used to be fog (could be null) + if ( listener.positionX ) { - const frontFaceCW = ( object.isMesh && object.matrixWorld.determinant() < 0 ); + // code path for Chrome (see #14393) - const program = setProgram( camera, scene, geometry, material, object ); + const endTime = this.context.currentTime + this.timeDelta; - state.setMaterial( material, frontFaceCW ); + listener.positionX.linearRampToValueAtTime( _position$1.x, endTime ); + listener.positionY.linearRampToValueAtTime( _position$1.y, endTime ); + listener.positionZ.linearRampToValueAtTime( _position$1.z, endTime ); + listener.forwardX.linearRampToValueAtTime( _forward.x, endTime ); + listener.forwardY.linearRampToValueAtTime( _forward.y, endTime ); + listener.forwardZ.linearRampToValueAtTime( _forward.z, endTime ); + listener.upX.linearRampToValueAtTime( _up.x, endTime ); + listener.upY.linearRampToValueAtTime( _up.y, endTime ); + listener.upZ.linearRampToValueAtTime( _up.z, endTime ); - // + } else { - let index = geometry.index; - let rangeFactor = 1; + listener.setPosition( _position$1.x, _position$1.y, _position$1.z ); + listener.setOrientation( _forward.x, _forward.y, _forward.z, _up.x, _up.y, _up.z ); - if ( material.wireframe === true ) { + } - index = geometries.getWireframeAttribute( geometry ); - rangeFactor = 2; + } - } +} - // +/** + * Represents a non-positional ( global ) audio object. + * + * This and related audio modules make use of the [Web Audio API]{@link https://www.w3.org/TR/webaudio-1.1/}. + * + * ```js + * // create an AudioListener and add it to the camera + * const listener = new THREE.AudioListener(); + * camera.add( listener ); + * + * // create a global audio source + * const sound = new THREE.Audio( listener ); + * + * // load a sound and set it as the Audio object's buffer + * const audioLoader = new THREE.AudioLoader(); + * audioLoader.load( 'sounds/ambient.ogg', function( buffer ) { + * sound.setBuffer( buffer ); + * sound.setLoop( true ); + * sound.setVolume( 0.5 ); + * sound.play(); + * }); + * ``` + * + * @augments Object3D + */ +class Audio extends Object3D { - const drawRange = geometry.drawRange; - const position = geometry.attributes.position; + /** + * Constructs a new audio. + * + * @param {AudioListener} listener - The global audio listener. + */ + constructor( listener ) { - let drawStart = drawRange.start * rangeFactor; - let drawEnd = ( drawRange.start + drawRange.count ) * rangeFactor; + super(); - if ( group !== null ) { + this.type = 'Audio'; - drawStart = Math.max( drawStart, group.start * rangeFactor ); - drawEnd = Math.min( drawEnd, ( group.start + group.count ) * rangeFactor ); + /** + * The global audio listener. + * + * @type {AudioListener} + * @readonly + */ + this.listener = listener; - } + /** + * The audio context. + * + * @type {AudioContext} + * @readonly + */ + this.context = listener.context; - if ( index !== null ) { + /** + * The gain node used for volume control. + * + * @type {GainNode} + * @readonly + */ + this.gain = this.context.createGain(); + this.gain.connect( listener.getInput() ); - drawStart = Math.max( drawStart, 0 ); - drawEnd = Math.min( drawEnd, index.count ); + /** + * Whether to start playback automatically or not. + * + * @type {boolean} + * @default false + */ + this.autoplay = false; - } else if ( position !== undefined && position !== null ) { + /** + * A reference to an audio buffer. + * + * Defined via {@link Audio#setBuffer}. + * + * @type {?AudioBuffer} + * @default null + * @readonly + */ + this.buffer = null; - drawStart = Math.max( drawStart, 0 ); - drawEnd = Math.min( drawEnd, position.count ); + /** + * Modify pitch, measured in cents. +/- 100 is a semitone. + * +/- 1200 is an octave. + * + * Defined via {@link Audio#setDetune}. + * + * @type {number} + * @default 0 + * @readonly + */ + this.detune = 0; - } + /** + * Whether the audio should loop or not. + * + * Defined via {@link Audio#setLoop}. + * + * @type {boolean} + * @default false + * @readonly + */ + this.loop = false; - const drawCount = drawEnd - drawStart; + /** + * Defines where in the audio buffer the replay should + * start, in seconds. + * + * @type {number} + * @default 0 + */ + this.loopStart = 0; - if ( drawCount < 0 || drawCount === Infinity ) return; + /** + * Defines where in the audio buffer the replay should + * stop, in seconds. + * + * @type {number} + * @default 0 + */ + this.loopEnd = 0; - // + /** + * An offset to the time within the audio buffer the playback + * should begin, in seconds. + * + * @type {number} + * @default 0 + */ + this.offset = 0; - bindingStates.setup( object, material, program, geometry, index ); + /** + * Overrides the default duration of the audio. + * + * @type {undefined|number} + * @default undefined + */ + this.duration = undefined; - let attribute; - let renderer = bufferRenderer; + /** + * The playback speed. + * + * Defined via {@link Audio#setPlaybackRate}. + * + * @type {number} + * @readonly + * @default 1 + */ + this.playbackRate = 1; - if ( index !== null ) { + /** + * Indicates whether the audio is playing or not. + * + * This flag will be automatically set when using {@link Audio#play}, + * {@link Audio#pause}, {@link Audio#stop}. + * + * @type {boolean} + * @readonly + * @default false + */ + this.isPlaying = false; - attribute = attributes.get( index ); + /** + * Indicates whether the audio playback can be controlled + * with method like {@link Audio#play} or {@link Audio#pause}. + * + * This flag will be automatically set when audio sources are + * defined. + * + * @type {boolean} + * @readonly + * @default true + */ + this.hasPlaybackControl = true; - renderer = indexedBufferRenderer; - renderer.setIndex( attribute ); + /** + * Holds a reference to the current audio source. + * + * The property is automatically by one of the `set*()` methods. + * + * @type {?AudioNode} + * @readonly + * @default null + */ + this.source = null; - } + /** + * Defines the source type. + * + * The property is automatically by one of the `set*()` methods. + * + * @type {('empty'|'audioNode'|'mediaNode'|'mediaStreamNode'|'buffer')} + * @readonly + * @default 'empty' + */ + this.sourceType = 'empty'; - // + this._startedAt = 0; + this._progress = 0; + this._connected = false; - if ( object.isMesh ) { + /** + * Can be used to apply a variety of low-order filters to create + * more complex sound effects e.g. via `BiquadFilterNode`. + * + * The property is automatically set by {@link Audio#setFilters}. + * + * @type {Array} + * @readonly + */ + this.filters = []; - if ( material.wireframe === true ) { + } - state.setLineWidth( material.wireframeLinewidth * getTargetPixelRatio() ); - renderer.setMode( _gl.LINES ); + /** + * Returns the output audio node. + * + * @return {GainNode} The output node. + */ + getOutput() { - } else { + return this.gain; - renderer.setMode( _gl.TRIANGLES ); + } - } + /** + * Sets the given audio node as the source of this instance. + * + * {@link Audio#sourceType} is set to `audioNode` and {@link Audio#hasPlaybackControl} to `false`. + * + * @param {AudioNode} audioNode - The audio node like an instance of `OscillatorNode`. + * @return {Audio} A reference to this instance. + */ + setNodeSource( audioNode ) { - } else if ( object.isLine ) { + this.hasPlaybackControl = false; + this.sourceType = 'audioNode'; + this.source = audioNode; + this.connect(); - let lineWidth = material.linewidth; + return this; - if ( lineWidth === undefined ) lineWidth = 1; // Not using Line*Material + } - state.setLineWidth( lineWidth * getTargetPixelRatio() ); + /** + * Sets the given media element as the source of this instance. + * + * {@link Audio#sourceType} is set to `mediaNode` and {@link Audio#hasPlaybackControl} to `false`. + * + * @param {HTMLMediaElement} mediaElement - The media element. + * @return {Audio} A reference to this instance. + */ + setMediaElementSource( mediaElement ) { - if ( object.isLineSegments ) { + this.hasPlaybackControl = false; + this.sourceType = 'mediaNode'; + this.source = this.context.createMediaElementSource( mediaElement ); + this.connect(); - renderer.setMode( _gl.LINES ); + return this; - } else if ( object.isLineLoop ) { + } - renderer.setMode( _gl.LINE_LOOP ); + /** + * Sets the given media stream as the source of this instance. + * + * {@link Audio#sourceType} is set to `mediaStreamNode` and {@link Audio#hasPlaybackControl} to `false`. + * + * @param {MediaStream} mediaStream - The media stream. + * @return {Audio} A reference to this instance. + */ + setMediaStreamSource( mediaStream ) { - } else { + this.hasPlaybackControl = false; + this.sourceType = 'mediaStreamNode'; + this.source = this.context.createMediaStreamSource( mediaStream ); + this.connect(); - renderer.setMode( _gl.LINE_STRIP ); + return this; - } + } - } else if ( object.isPoints ) { + /** + * Sets the given audio buffer as the source of this instance. + * + * {@link Audio#sourceType} is set to `buffer` and {@link Audio#hasPlaybackControl} to `true`. + * + * @param {AudioBuffer} audioBuffer - The audio buffer. + * @return {Audio} A reference to this instance. + */ + setBuffer( audioBuffer ) { - renderer.setMode( _gl.POINTS ); + this.buffer = audioBuffer; + this.sourceType = 'buffer'; - } else if ( object.isSprite ) { + if ( this.autoplay ) this.play(); - renderer.setMode( _gl.TRIANGLES ); + return this; - } + } - if ( object.isInstancedMesh ) { + /** + * Starts the playback of the audio. + * + * Can only be used with compatible audio sources that allow playback control. + * + * @param {number} [delay=0] - The delay, in seconds, at which the audio should start playing. + * @return {Audio|undefined} A reference to this instance. + */ + play( delay = 0 ) { - renderer.renderInstances( drawStart, drawCount, object.count ); + if ( this.isPlaying === true ) { - } else if ( geometry.isInstancedBufferGeometry ) { + console.warn( 'THREE.Audio: Audio is already playing.' ); + return; - const maxInstanceCount = geometry._maxInstanceCount !== undefined ? geometry._maxInstanceCount : Infinity; - const instanceCount = Math.min( geometry.instanceCount, maxInstanceCount ); + } - renderer.renderInstances( drawStart, drawCount, instanceCount ); + if ( this.hasPlaybackControl === false ) { - } else { + console.warn( 'THREE.Audio: this Audio has no playback control.' ); + return; - renderer.render( drawStart, drawCount ); + } - } + this._startedAt = this.context.currentTime + delay; - }; + const source = this.context.createBufferSource(); + source.buffer = this.buffer; + source.loop = this.loop; + source.loopStart = this.loopStart; + source.loopEnd = this.loopEnd; + source.onended = this.onEnded.bind( this ); + source.start( this._startedAt, this._progress + this.offset, this.duration ); - // Compile + this.isPlaying = true; - this.compile = function ( scene, camera ) { + this.source = source; - function prepare( material, scene, object ) { + this.setDetune( this.detune ); + this.setPlaybackRate( this.playbackRate ); - if ( material.transparent === true && material.side === DoubleSide && material.forceSinglePass === false ) { + return this.connect(); - material.side = BackSide; - material.needsUpdate = true; - getProgram( material, scene, object ); + } - material.side = FrontSide; - material.needsUpdate = true; - getProgram( material, scene, object ); + /** + * Pauses the playback of the audio. + * + * Can only be used with compatible audio sources that allow playback control. + * + * @return {Audio|undefined} A reference to this instance. + */ + pause() { - material.side = DoubleSide; + if ( this.hasPlaybackControl === false ) { - } else { + console.warn( 'THREE.Audio: this Audio has no playback control.' ); + return; - getProgram( material, scene, object ); + } - } + if ( this.isPlaying === true ) { - } + // update current progress - currentRenderState = renderStates.get( scene ); - currentRenderState.init(); + this._progress += Math.max( this.context.currentTime - this._startedAt, 0 ) * this.playbackRate; - renderStateStack.push( currentRenderState ); + if ( this.loop === true ) { - scene.traverseVisible( function ( object ) { + // ensure _progress does not exceed duration with looped audios - if ( object.isLight && object.layers.test( camera.layers ) ) { + this._progress = this._progress % ( this.duration || this.buffer.duration ); - currentRenderState.pushLight( object ); + } - if ( object.castShadow ) { + this.source.stop(); + this.source.onended = null; - currentRenderState.pushShadow( object ); + this.isPlaying = false; - } + } - } + return this; - } ); + } - currentRenderState.setupLights( _this.useLegacyLights ); + /** + * Stops the playback of the audio. + * + * Can only be used with compatible audio sources that allow playback control. + * + * @param {number} [delay=0] - The delay, in seconds, at which the audio should stop playing. + * @return {Audio|undefined} A reference to this instance. + */ + stop( delay = 0 ) { - scene.traverse( function ( object ) { + if ( this.hasPlaybackControl === false ) { - const material = object.material; + console.warn( 'THREE.Audio: this Audio has no playback control.' ); + return; - if ( material ) { + } - if ( Array.isArray( material ) ) { + this._progress = 0; - for ( let i = 0; i < material.length; i ++ ) { + if ( this.source !== null ) { - const material2 = material[ i ]; + this.source.stop( this.context.currentTime + delay ); + this.source.onended = null; - prepare( material2, scene, object ); + } - } + this.isPlaying = false; - } else { + return this; - prepare( material, scene, object ); + } - } + /** + * Connects to the audio source. This is used internally on + * initialisation and when setting / removing filters. + * + * @return {Audio} A reference to this instance. + */ + connect() { - } + if ( this.filters.length > 0 ) { - } ); + this.source.connect( this.filters[ 0 ] ); - renderStateStack.pop(); - currentRenderState = null; + for ( let i = 1, l = this.filters.length; i < l; i ++ ) { - }; + this.filters[ i - 1 ].connect( this.filters[ i ] ); - // Animation Loop + } - let onAnimationFrameCallback = null; + this.filters[ this.filters.length - 1 ].connect( this.getOutput() ); - function onAnimationFrame( time ) { + } else { - if ( onAnimationFrameCallback ) onAnimationFrameCallback( time ); + this.source.connect( this.getOutput() ); } - function onXRSessionStart() { + this._connected = true; - animation.stop(); + return this; - } + } - function onXRSessionEnd() { + /** + * Disconnects to the audio source. This is used internally on + * initialisation and when setting / removing filters. + * + * @return {Audio|undefined} A reference to this instance. + */ + disconnect() { - animation.start(); + if ( this._connected === false ) { - } + return; - const animation = new WebGLAnimation(); - animation.setAnimationLoop( onAnimationFrame ); + } - if ( typeof self !== 'undefined' ) animation.setContext( self ); + if ( this.filters.length > 0 ) { - this.setAnimationLoop = function ( callback ) { + this.source.disconnect( this.filters[ 0 ] ); - onAnimationFrameCallback = callback; - xr.setAnimationLoop( callback ); + for ( let i = 1, l = this.filters.length; i < l; i ++ ) { - ( callback === null ) ? animation.stop() : animation.start(); + this.filters[ i - 1 ].disconnect( this.filters[ i ] ); - }; + } - xr.addEventListener( 'sessionstart', onXRSessionStart ); - xr.addEventListener( 'sessionend', onXRSessionEnd ); + this.filters[ this.filters.length - 1 ].disconnect( this.getOutput() ); - // Rendering + } else { - this.render = function ( scene, camera ) { + this.source.disconnect( this.getOutput() ); - if ( camera !== undefined && camera.isCamera !== true ) { + } - console.error( 'THREE.WebGLRenderer.render: camera is not an instance of THREE.Camera.' ); - return; + this._connected = false; - } + return this; - if ( _isContextLost === true ) return; + } - // update scene graph + /** + * Returns the current set filters. + * + * @return {Array} The list of filters. + */ + getFilters() { - if ( scene.matrixWorldAutoUpdate === true ) scene.updateMatrixWorld(); + return this.filters; - // update camera matrices and frustum + } - if ( camera.parent === null && camera.matrixWorldAutoUpdate === true ) camera.updateMatrixWorld(); + /** + * Sets an array of filters and connects them with the audio source. + * + * @param {Array} [value] - A list of filters. + * @return {Audio} A reference to this instance. + */ + setFilters( value ) { - if ( xr.enabled === true && xr.isPresenting === true ) { + if ( ! value ) value = []; - camera = xr.updateCameraXR( camera ); // use XR camera for rendering + if ( this._connected === true ) { - } + this.disconnect(); + this.filters = value.slice(); + this.connect(); - // - if ( scene.isScene === true ) scene.onBeforeRender( _this, scene, camera, _currentRenderTarget ); + } else { - currentRenderState = renderStates.get( scene, renderStateStack.length ); - currentRenderState.init(); + this.filters = value.slice(); - renderStateStack.push( currentRenderState ); + } - _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); - _frustum.setFromProjectionMatrix( _projScreenMatrix ); + return this; - _localClippingEnabled = this.localClippingEnabled; - _clippingEnabled = clipping.init( this.clippingPlanes, _localClippingEnabled ); + } - currentRenderList = renderLists.get( scene, renderListStack.length ); - currentRenderList.init(); + /** + * Defines the detuning of oscillation in cents. + * + * @param {number} value - The detuning of oscillation in cents. + * @return {Audio} A reference to this instance. + */ + setDetune( value ) { - renderListStack.push( currentRenderList ); + this.detune = value; - projectObject( scene, camera, 0, _this.sortObjects ); + if ( this.isPlaying === true && this.source.detune !== undefined ) { - currentRenderList.finish(); + this.source.detune.setTargetAtTime( this.detune, this.context.currentTime, 0.01 ); - if ( _this.sortObjects === true ) { + } - currentRenderList.sort( _opaqueSort, _transparentSort ); + return this; - } + } - // + /** + * Returns the detuning of oscillation in cents. + * + * @return {number} The detuning of oscillation in cents. + */ + getDetune() { - if ( _clippingEnabled === true ) clipping.beginShadows(); + return this.detune; - const shadowsArray = currentRenderState.state.shadowsArray; + } - shadowMap.render( shadowsArray, scene, camera ); + /** + * Returns the first filter in the list of filters. + * + * @return {AudioNode|undefined} The first filter in the list of filters. + */ + getFilter() { - if ( _clippingEnabled === true ) clipping.endShadows(); + return this.getFilters()[ 0 ]; - // + } - if ( this.info.autoReset === true ) this.info.reset(); + /** + * Applies a single filter node to the audio. + * + * @param {AudioNode} [filter] - The filter to set. + * @return {Audio} A reference to this instance. + */ + setFilter( filter ) { - this.info.render.frame ++; + return this.setFilters( filter ? [ filter ] : [] ); - // + } - background.render( currentRenderList, scene ); + /** + * Sets the playback rate. + * + * Can only be used with compatible audio sources that allow playback control. + * + * @param {number} [value] - The playback rate to set. + * @return {Audio|undefined} A reference to this instance. + */ + setPlaybackRate( value ) { - // render scene + if ( this.hasPlaybackControl === false ) { - currentRenderState.setupLights( _this.useLegacyLights ); + console.warn( 'THREE.Audio: this Audio has no playback control.' ); + return; - if ( camera.isArrayCamera ) { + } - const cameras = camera.cameras; + this.playbackRate = value; - for ( let i = 0, l = cameras.length; i < l; i ++ ) { + if ( this.isPlaying === true ) { - const camera2 = cameras[ i ]; + this.source.playbackRate.setTargetAtTime( this.playbackRate, this.context.currentTime, 0.01 ); - renderScene( currentRenderList, scene, camera2, camera2.viewport ); + } - } + return this; - } else { + } - renderScene( currentRenderList, scene, camera ); + /** + * Returns the current playback rate. - } + * @return {number} The playback rate. + */ + getPlaybackRate() { - // + return this.playbackRate; - if ( _currentRenderTarget !== null ) { + } - // resolve multisample renderbuffers to a single-sample texture if necessary + /** + * Automatically called when playback finished. + */ + onEnded() { - textures.updateMultisampleRenderTarget( _currentRenderTarget ); + this.isPlaying = false; + this._progress = 0; - // Generate mipmap if we're using any kind of mipmap filtering + } - textures.updateRenderTargetMipmap( _currentRenderTarget ); + /** + * Returns the loop flag. + * + * Can only be used with compatible audio sources that allow playback control. + * + * @return {boolean} Whether the audio should loop or not. + */ + getLoop() { - } + if ( this.hasPlaybackControl === false ) { - // + console.warn( 'THREE.Audio: this Audio has no playback control.' ); + return false; - if ( scene.isScene === true ) scene.onAfterRender( _this, scene, camera ); + } - // _gl.finish(); + return this.loop; - bindingStates.resetDefaultState(); - _currentMaterialId = - 1; - _currentCamera = null; + } - renderStateStack.pop(); + /** + * Sets the loop flag. + * + * Can only be used with compatible audio sources that allow playback control. + * + * @param {boolean} value - Whether the audio should loop or not. + * @return {Audio|undefined} A reference to this instance. + */ + setLoop( value ) { - if ( renderStateStack.length > 0 ) { + if ( this.hasPlaybackControl === false ) { - currentRenderState = renderStateStack[ renderStateStack.length - 1 ]; + console.warn( 'THREE.Audio: this Audio has no playback control.' ); + return; - } else { + } - currentRenderState = null; + this.loop = value; - } + if ( this.isPlaying === true ) { - renderListStack.pop(); + this.source.loop = this.loop; - if ( renderListStack.length > 0 ) { + } - currentRenderList = renderListStack[ renderListStack.length - 1 ]; + return this; - } else { + } - currentRenderList = null; + /** + * Sets the loop start value which defines where in the audio buffer the replay should + * start, in seconds. + * + * @param {number} value - The loop start value. + * @return {Audio} A reference to this instance. + */ + setLoopStart( value ) { - } + this.loopStart = value; - }; + return this; - function projectObject( object, camera, groupOrder, sortObjects ) { + } - if ( object.visible === false ) return; + /** + * Sets the loop end value which defines where in the audio buffer the replay should + * stop, in seconds. + * + * @param {number} value - The loop end value. + * @return {Audio} A reference to this instance. + */ + setLoopEnd( value ) { - const visible = object.layers.test( camera.layers ); + this.loopEnd = value; - if ( visible ) { + return this; - if ( object.isGroup ) { + } - groupOrder = object.renderOrder; + /** + * Returns the volume. + * + * @return {number} The volume. + */ + getVolume() { - } else if ( object.isLOD ) { + return this.gain.gain.value; - if ( object.autoUpdate === true ) object.update( camera ); + } - } else if ( object.isLight ) { + /** + * Sets the volume. + * + * @param {number} value - The volume to set. + * @return {Audio} A reference to this instance. + */ + setVolume( value ) { - currentRenderState.pushLight( object ); + this.gain.gain.setTargetAtTime( value, this.context.currentTime, 0.01 ); - if ( object.castShadow ) { + return this; - currentRenderState.pushShadow( object ); + } - } + copy( source, recursive ) { - } else if ( object.isSprite ) { + super.copy( source, recursive ); - if ( ! object.frustumCulled || _frustum.intersectsSprite( object ) ) { + if ( source.sourceType !== 'buffer' ) { - if ( sortObjects ) { + console.warn( 'THREE.Audio: Audio source type cannot be copied.' ); - _vector3.setFromMatrixPosition( object.matrixWorld ) - .applyMatrix4( _projScreenMatrix ); + return this; - } + } - const geometry = objects.update( object ); - const material = object.material; + this.autoplay = source.autoplay; - if ( material.visible ) { + this.buffer = source.buffer; + this.detune = source.detune; + this.loop = source.loop; + this.loopStart = source.loopStart; + this.loopEnd = source.loopEnd; + this.offset = source.offset; + this.duration = source.duration; + this.playbackRate = source.playbackRate; + this.hasPlaybackControl = source.hasPlaybackControl; + this.sourceType = source.sourceType; - currentRenderList.push( object, geometry, material, groupOrder, _vector3.z, null ); + this.filters = source.filters.slice(); - } + return this; - } + } - } else if ( object.isMesh || object.isLine || object.isPoints ) { + clone( recursive ) { - if ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) { + return new this.constructor( this.listener ).copy( this, recursive ); - if ( object.isSkinnedMesh ) { + } - // update skeleton only once in a frame +} - if ( object.skeleton.frame !== info.render.frame ) { +const _position = /*@__PURE__*/ new Vector3(); +const _quaternion = /*@__PURE__*/ new Quaternion(); +const _scale = /*@__PURE__*/ new Vector3(); +const _orientation = /*@__PURE__*/ new Vector3(); - object.skeleton.update(); - object.skeleton.frame = info.render.frame; +/** + * Represents a positional audio object. + * + * ```js + * // create an AudioListener and add it to the camera + * const listener = new THREE.AudioListener(); + * camera.add( listener ); + * + * // create the PositionalAudio object (passing in the listener) + * const sound = new THREE.PositionalAudio( listener ); + * + * // load a sound and set it as the PositionalAudio object's buffer + * const audioLoader = new THREE.AudioLoader(); + * audioLoader.load( 'sounds/song.ogg', function( buffer ) { + * sound.setBuffer( buffer ); + * sound.setRefDistance( 20 ); + * sound.play(); + * }); + * + * // create an object for the sound to play from + * const sphere = new THREE.SphereGeometry( 20, 32, 16 ); + * const material = new THREE.MeshPhongMaterial( { color: 0xff2200 } ); + * const mesh = new THREE.Mesh( sphere, material ); + * scene.add( mesh ); + * + * // finally add the sound to the mesh + * mesh.add( sound ); + * + * @augments Audio + */ +class PositionalAudio extends Audio { - } + /** + * Constructs a positional audio. + * + * @param {AudioListener} listener - The global audio listener. + */ + constructor( listener ) { - } + super( listener ); - const geometry = objects.update( object ); - const material = object.material; + /** + * The panner node represents the location, direction, and behavior of an audio + * source in 3D space. + * + * @type {PannerNode} + * @readonly + */ + this.panner = this.context.createPanner(); + this.panner.panningModel = 'HRTF'; + this.panner.connect( this.gain ); - if ( sortObjects ) { + } - if ( object.boundingSphere !== undefined ) { + connect() { - if ( object.boundingSphere === null ) object.computeBoundingSphere(); - _vector3.copy( object.boundingSphere.center ); + super.connect(); - } else { + this.panner.connect( this.gain ); - if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); - _vector3.copy( geometry.boundingSphere.center ); + return this; - } + } - _vector3 - .applyMatrix4( object.matrixWorld ) - .applyMatrix4( _projScreenMatrix ); + disconnect() { - } + super.disconnect(); - if ( Array.isArray( material ) ) { + this.panner.disconnect( this.gain ); - const groups = geometry.groups; + return this; - for ( let i = 0, l = groups.length; i < l; i ++ ) { + } - const group = groups[ i ]; - const groupMaterial = material[ group.materialIndex ]; + getOutput() { - if ( groupMaterial && groupMaterial.visible ) { + return this.panner; - currentRenderList.push( object, geometry, groupMaterial, groupOrder, _vector3.z, group ); + } - } + /** + * Returns the current reference distance. + * + * @return {number} The reference distance. + */ + getRefDistance() { - } + return this.panner.refDistance; - } else if ( material.visible ) { + } - currentRenderList.push( object, geometry, material, groupOrder, _vector3.z, null ); + /** + * Defines the reference distance for reducing volume as the audio source moves + * further from the listener – i.e. the distance at which the volume reduction + * starts taking effect. + * + * @param {number} value - The reference distance to set. + * @return {PositionalAudio} A reference to this instance. + */ + setRefDistance( value ) { - } + this.panner.refDistance = value; - } + return this; - } + } - } + /** + * Returns the current rolloff factor. + * + * @return {number} The rolloff factor. + */ + getRolloffFactor() { - const children = object.children; + return this.panner.rolloffFactor; - for ( let i = 0, l = children.length; i < l; i ++ ) { + } - projectObject( children[ i ], camera, groupOrder, sortObjects ); + /** + * Defines how quickly the volume is reduced as the source moves away from the listener. + * + * @param {number} value - The rolloff factor. + * @return {PositionalAudio} A reference to this instance. + */ + setRolloffFactor( value ) { - } + this.panner.rolloffFactor = value; - } + return this; - function renderScene( currentRenderList, scene, camera, viewport ) { + } - const opaqueObjects = currentRenderList.opaque; - const transmissiveObjects = currentRenderList.transmissive; - const transparentObjects = currentRenderList.transparent; + /** + * Returns the current distance model. + * + * @return {('linear'|'inverse'|'exponential')} The distance model. + */ + getDistanceModel() { - currentRenderState.setupLightsView( camera ); + return this.panner.distanceModel; - if ( _clippingEnabled === true ) clipping.setGlobalState( _this.clippingPlanes, camera ); + } - if ( transmissiveObjects.length > 0 ) renderTransmissionPass( opaqueObjects, transmissiveObjects, scene, camera ); + /** + * Defines which algorithm to use to reduce the volume of the audio source + * as it moves away from the listener. + * + * Read [the spec]{@link https://www.w3.org/TR/webaudio-1.1/#enumdef-distancemodeltype} + * for more details. + * + * @param {('linear'|'inverse'|'exponential')} value - The distance model to set. + * @return {PositionalAudio} A reference to this instance. + */ + setDistanceModel( value ) { - if ( viewport ) state.viewport( _currentViewport.copy( viewport ) ); + this.panner.distanceModel = value; - if ( opaqueObjects.length > 0 ) renderObjects( opaqueObjects, scene, camera ); - if ( transmissiveObjects.length > 0 ) renderObjects( transmissiveObjects, scene, camera ); - if ( transparentObjects.length > 0 ) renderObjects( transparentObjects, scene, camera ); + return this; - // Ensure depth buffer writing is enabled so it can be cleared on next render + } - state.buffers.depth.setTest( true ); - state.buffers.depth.setMask( true ); - state.buffers.color.setMask( true ); + /** + * Returns the current max distance. + * + * @return {number} The max distance. + */ + getMaxDistance() { - state.setPolygonOffset( false ); + return this.panner.maxDistance; - } + } - function renderTransmissionPass( opaqueObjects, transmissiveObjects, scene, camera ) { + /** + * Defines the maximum distance between the audio source and the listener, + * after which the volume is not reduced any further. + * + * This value is used only by the `linear` distance model. + * + * @param {number} value - The max distance. + * @return {PositionalAudio} A reference to this instance. + */ + setMaxDistance( value ) { - if ( _transmissionRenderTarget === null ) { + this.panner.maxDistance = value; - const isWebGL2 = capabilities.isWebGL2; + return this; - _transmissionRenderTarget = new WebGLRenderTarget( 1024, 1024, { - generateMipmaps: true, - type: extensions.has( 'EXT_color_buffer_half_float' ) ? HalfFloatType : UnsignedByteType, - minFilter: LinearMipmapLinearFilter, - samples: ( isWebGL2 && antialias === true ) ? 4 : 0 - } ); + } - // debug + /** + * Sets the directional cone in which the audio can be listened. + * + * @param {number} coneInnerAngle - An angle, in degrees, of a cone inside of which there will be no volume reduction. + * @param {number} coneOuterAngle - An angle, in degrees, of a cone outside of which the volume will be reduced by a constant value, defined by the `coneOuterGain` parameter. + * @param {number} coneOuterGain - The amount of volume reduction outside the cone defined by the `coneOuterAngle`. When set to `0`, no sound can be heard. + * @return {PositionalAudio} A reference to this instance. + */ + setDirectionalCone( coneInnerAngle, coneOuterAngle, coneOuterGain ) { - /* - const geometry = new PlaneGeometry(); - const material = new MeshBasicMaterial( { map: _transmissionRenderTarget.texture } ); + this.panner.coneInnerAngle = coneInnerAngle; + this.panner.coneOuterAngle = coneOuterAngle; + this.panner.coneOuterGain = coneOuterGain; - const mesh = new Mesh( geometry, material ); - scene.add( mesh ); - */ + return this; - } + } - // + updateMatrixWorld( force ) { - const currentRenderTarget = _this.getRenderTarget(); - _this.setRenderTarget( _transmissionRenderTarget ); + super.updateMatrixWorld( force ); - _this.getClearColor( _currentClearColor ); - _currentClearAlpha = _this.getClearAlpha(); - if ( _currentClearAlpha < 1 ) _this.setClearColor( 0xffffff, 0.5 ); + if ( this.hasPlaybackControl === true && this.isPlaying === false ) return; - _this.clear(); + this.matrixWorld.decompose( _position, _quaternion, _scale ); - // Turn off the features which can affect the frag color for opaque objects pass. - // Otherwise they are applied twice in opaque objects pass and transmission objects pass. - const currentToneMapping = _this.toneMapping; - _this.toneMapping = NoToneMapping; + _orientation.set( 0, 0, 1 ).applyQuaternion( _quaternion ); - renderObjects( opaqueObjects, scene, camera ); + const panner = this.panner; - textures.updateMultisampleRenderTarget( _transmissionRenderTarget ); - textures.updateRenderTargetMipmap( _transmissionRenderTarget ); + if ( panner.positionX ) { - let renderTargetNeedsUpdate = false; + // code path for Chrome and Firefox (see #14393) - for ( let i = 0, l = transmissiveObjects.length; i < l; i ++ ) { + const endTime = this.context.currentTime + this.listener.timeDelta; - const renderItem = transmissiveObjects[ i ]; + panner.positionX.linearRampToValueAtTime( _position.x, endTime ); + panner.positionY.linearRampToValueAtTime( _position.y, endTime ); + panner.positionZ.linearRampToValueAtTime( _position.z, endTime ); + panner.orientationX.linearRampToValueAtTime( _orientation.x, endTime ); + panner.orientationY.linearRampToValueAtTime( _orientation.y, endTime ); + panner.orientationZ.linearRampToValueAtTime( _orientation.z, endTime ); - const object = renderItem.object; - const geometry = renderItem.geometry; - const material = renderItem.material; - const group = renderItem.group; + } else { - if ( material.side === DoubleSide && object.layers.test( camera.layers ) ) { + panner.setPosition( _position.x, _position.y, _position.z ); + panner.setOrientation( _orientation.x, _orientation.y, _orientation.z ); - const currentSide = material.side; + } - material.side = BackSide; - material.needsUpdate = true; + } - renderObject( object, scene, camera, geometry, material, group ); +} - material.side = currentSide; - material.needsUpdate = true; +/** + * This class can be used to analyse audio data. + * + * ```js + * // create an AudioListener and add it to the camera + * const listener = new THREE.AudioListener(); + * camera.add( listener ); + * + * // create an Audio source + * const sound = new THREE.Audio( listener ); + * + * // load a sound and set it as the Audio object's buffer + * const audioLoader = new THREE.AudioLoader(); + * audioLoader.load( 'sounds/ambient.ogg', function( buffer ) { + * sound.setBuffer( buffer ); + * sound.setLoop(true); + * sound.setVolume(0.5); + * sound.play(); + * }); + * + * // create an AudioAnalyser, passing in the sound and desired fftSize + * const analyser = new THREE.AudioAnalyser( sound, 32 ); + * + * // get the average frequency of the sound + * const data = analyser.getAverageFrequency(); + * ``` + */ +class AudioAnalyser { - renderTargetNeedsUpdate = true; + /** + * Constructs a new audio analyzer. + * + * @param {Audio} audio - The audio to analyze. + * @param {number} [fftSize=2048] - The window size in samples that is used when performing a Fast Fourier Transform (FFT) to get frequency domain data. + */ + constructor( audio, fftSize = 2048 ) { - } + /** + * The global audio listener. + * + * @type {AnalyserNode} + */ + this.analyser = audio.context.createAnalyser(); + this.analyser.fftSize = fftSize; - } + /** + * Holds the analyzed data. + * + * @type {Uint8Array} + */ + this.data = new Uint8Array( this.analyser.frequencyBinCount ); - if ( renderTargetNeedsUpdate === true ) { + audio.getOutput().connect( this.analyser ); - textures.updateMultisampleRenderTarget( _transmissionRenderTarget ); - textures.updateRenderTargetMipmap( _transmissionRenderTarget ); + } - } + /** + * Returns an array with frequency data of the audio. + * + * Each item in the array represents the decibel value for a specific frequency. + * The frequencies are spread linearly from 0 to 1/2 of the sample rate. + * For example, for 48000 sample rate, the last item of the array will represent + * the decibel value for 24000 Hz. + * + * @return {Uint8Array} The frequency data. + */ + getFrequencyData() { - _this.setRenderTarget( currentRenderTarget ); + this.analyser.getByteFrequencyData( this.data ); - _this.setClearColor( _currentClearColor, _currentClearAlpha ); + return this.data; - _this.toneMapping = currentToneMapping; + } - } + /** + * Returns the average of the frequencies returned by {@link AudioAnalyser#getFrequencyData}. + * + * @return {number} The average frequency. + */ + getAverageFrequency() { - function renderObjects( renderList, scene, camera ) { + let value = 0; + const data = this.getFrequencyData(); - const overrideMaterial = scene.isScene === true ? scene.overrideMaterial : null; + for ( let i = 0; i < data.length; i ++ ) { - for ( let i = 0, l = renderList.length; i < l; i ++ ) { + value += data[ i ]; - const renderItem = renderList[ i ]; + } - const object = renderItem.object; - const geometry = renderItem.geometry; - const material = overrideMaterial === null ? renderItem.material : overrideMaterial; - const group = renderItem.group; + return value / data.length; - if ( object.layers.test( camera.layers ) ) { + } - renderObject( object, scene, camera, geometry, material, group ); +} - } +/** + * Buffered scene graph property that allows weighted accumulation; used internally. + */ +class PropertyMixer { - } + /** + * Constructs a new property mixer. + * + * @param {PropertyBinding} binding - The property binding. + * @param {string} typeName - The keyframe track type name. + * @param {number} valueSize - The keyframe track value size. + */ + constructor( binding, typeName, valueSize ) { - } + /** + * The property binding. + * + * @type {PropertyBinding} + */ + this.binding = binding; - function renderObject( object, scene, camera, geometry, material, group ) { + /** + * The keyframe track value size. + * + * @type {number} + */ + this.valueSize = valueSize; - object.onBeforeRender( _this, scene, camera, geometry, material, group ); + let mixFunction, + mixFunctionAdditive, + setIdentity; - object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld ); - object.normalMatrix.getNormalMatrix( object.modelViewMatrix ); + // buffer layout: [ incoming | accu0 | accu1 | orig | addAccu | (optional work) ] + // + // interpolators can use .buffer as their .result + // the data then goes to 'incoming' + // + // 'accu0' and 'accu1' are used frame-interleaved for + // the cumulative result and are compared to detect + // changes + // + // 'orig' stores the original state of the property + // + // 'add' is used for additive cumulative results + // + // 'work' is optional and is only present for quaternion types. It is used + // to store intermediate quaternion multiplication results - material.onBeforeRender( _this, scene, camera, geometry, object, group ); + switch ( typeName ) { - if ( material.transparent === true && material.side === DoubleSide && material.forceSinglePass === false ) { + case 'quaternion': + mixFunction = this._slerp; + mixFunctionAdditive = this._slerpAdditive; + setIdentity = this._setAdditiveIdentityQuaternion; - material.side = BackSide; - material.needsUpdate = true; - _this.renderBufferDirect( camera, scene, geometry, material, object, group ); + this.buffer = new Float64Array( valueSize * 6 ); + this._workIndex = 5; + break; - material.side = FrontSide; - material.needsUpdate = true; - _this.renderBufferDirect( camera, scene, geometry, material, object, group ); + case 'string': + case 'bool': + mixFunction = this._select; - material.side = DoubleSide; + // Use the regular mix function and for additive on these types, + // additive is not relevant for non-numeric types + mixFunctionAdditive = this._select; - } else { + setIdentity = this._setAdditiveIdentityOther; - _this.renderBufferDirect( camera, scene, geometry, material, object, group ); + this.buffer = new Array( valueSize * 5 ); + break; - } + default: + mixFunction = this._lerp; + mixFunctionAdditive = this._lerpAdditive; + setIdentity = this._setAdditiveIdentityNumeric; - object.onAfterRender( _this, scene, camera, geometry, material, group ); + this.buffer = new Float64Array( valueSize * 5 ); } - function getProgram( material, scene, object ) { + this._mixBufferRegion = mixFunction; + this._mixBufferRegionAdditive = mixFunctionAdditive; + this._setIdentity = setIdentity; + this._origIndex = 3; + this._addIndex = 4; - if ( scene.isScene !== true ) scene = _emptyScene; // scene could be a Mesh, Line, Points, ... + /** + * TODO + * + * @type {number} + * @default 0 + */ + this.cumulativeWeight = 0; - const materialProperties = properties.get( material ); + /** + * TODO + * + * @type {number} + * @default 0 + */ + this.cumulativeWeightAdditive = 0; - const lights = currentRenderState.state.lights; - const shadowsArray = currentRenderState.state.shadowsArray; + /** + * TODO + * + * @type {number} + * @default 0 + */ + this.useCount = 0; - const lightsStateVersion = lights.state.version; + /** + * TODO + * + * @type {number} + * @default 0 + */ + this.referenceCount = 0; - const parameters = programCache.getParameters( material, lights.state, shadowsArray, scene, object ); - const programCacheKey = programCache.getProgramCacheKey( parameters ); + } - let programs = materialProperties.programs; + /** + * Accumulates data in the `incoming` region into `accu`. + * + * @param {number} accuIndex - The accumulation index. + * @param {number} weight - The weight. + */ + accumulate( accuIndex, weight ) { - // always update environment and fog - changing these trigger an getProgram call, but it's possible that the program doesn't change + // note: happily accumulating nothing when weight = 0, the caller knows + // the weight and shouldn't have made the call in the first place - materialProperties.environment = material.isMeshStandardMaterial ? scene.environment : null; - materialProperties.fog = scene.fog; - materialProperties.envMap = ( material.isMeshStandardMaterial ? cubeuvmaps : cubemaps ).get( material.envMap || materialProperties.environment ); + const buffer = this.buffer, + stride = this.valueSize, + offset = accuIndex * stride + stride; - if ( programs === undefined ) { + let currentWeight = this.cumulativeWeight; - // new material + if ( currentWeight === 0 ) { - material.addEventListener( 'dispose', onMaterialDispose ); + // accuN := incoming * weight - programs = new Map(); - materialProperties.programs = programs; + for ( let i = 0; i !== stride; ++ i ) { + + buffer[ offset + i ] = buffer[ i ]; } - let program = programs.get( programCacheKey ); + currentWeight = weight; - if ( program !== undefined ) { + } else { - // early out if program and light state is identical + // accuN := accuN + incoming * weight - if ( materialProperties.currentProgram === program && materialProperties.lightsStateVersion === lightsStateVersion ) { + currentWeight += weight; + const mix = weight / currentWeight; + this._mixBufferRegion( buffer, offset, 0, mix, stride ); - updateCommonMaterialProperties( material, parameters ); + } - return program; + this.cumulativeWeight = currentWeight; - } + } - } else { + /** + * Accumulates data in the `incoming` region into `add`. + * + * @param {number} weight - The weight. + */ + accumulateAdditive( weight ) { - parameters.uniforms = programCache.getUniforms( material ); + const buffer = this.buffer, + stride = this.valueSize, + offset = stride * this._addIndex; - material.onBuild( object, parameters, _this ); + if ( this.cumulativeWeightAdditive === 0 ) { - material.onBeforeCompile( parameters, _this ); + // add = identity - program = programCache.acquireProgram( parameters, programCacheKey ); - programs.set( programCacheKey, program ); + this._setIdentity(); - materialProperties.uniforms = parameters.uniforms; + } - } + // add := add + incoming * weight - const uniforms = materialProperties.uniforms; + this._mixBufferRegionAdditive( buffer, offset, 0, weight, stride ); + this.cumulativeWeightAdditive += weight; - if ( ( ! material.isShaderMaterial && ! material.isRawShaderMaterial ) || material.clipping === true ) { + } - uniforms.clippingPlanes = clipping.uniform; + /** + * Applies the state of `accu` to the binding when accus differ. + * + * @param {number} accuIndex - The accumulation index. + */ + apply( accuIndex ) { - } + const stride = this.valueSize, + buffer = this.buffer, + offset = accuIndex * stride + stride, - updateCommonMaterialProperties( material, parameters ); + weight = this.cumulativeWeight, + weightAdditive = this.cumulativeWeightAdditive, - // store the light setup it was created for + binding = this.binding; - materialProperties.needsLights = materialNeedsLights( material ); - materialProperties.lightsStateVersion = lightsStateVersion; + this.cumulativeWeight = 0; + this.cumulativeWeightAdditive = 0; - if ( materialProperties.needsLights ) { + if ( weight < 1 ) { - // wire up the material to this renderer's lighting state + // accuN := accuN + original * ( 1 - cumulativeWeight ) - uniforms.ambientLightColor.value = lights.state.ambient; - uniforms.lightProbe.value = lights.state.probe; - uniforms.directionalLights.value = lights.state.directional; - uniforms.directionalLightShadows.value = lights.state.directionalShadow; - uniforms.spotLights.value = lights.state.spot; - uniforms.spotLightShadows.value = lights.state.spotShadow; - uniforms.rectAreaLights.value = lights.state.rectArea; - uniforms.ltc_1.value = lights.state.rectAreaLTC1; - uniforms.ltc_2.value = lights.state.rectAreaLTC2; - uniforms.pointLights.value = lights.state.point; - uniforms.pointLightShadows.value = lights.state.pointShadow; - uniforms.hemisphereLights.value = lights.state.hemi; + const originalValueOffset = stride * this._origIndex; - uniforms.directionalShadowMap.value = lights.state.directionalShadowMap; - uniforms.directionalShadowMatrix.value = lights.state.directionalShadowMatrix; - uniforms.spotShadowMap.value = lights.state.spotShadowMap; - uniforms.spotLightMatrix.value = lights.state.spotLightMatrix; - uniforms.spotLightMap.value = lights.state.spotLightMap; - uniforms.pointShadowMap.value = lights.state.pointShadowMap; - uniforms.pointShadowMatrix.value = lights.state.pointShadowMatrix; - // TODO (abelnation): add area lights shadow info to uniforms + this._mixBufferRegion( + buffer, offset, originalValueOffset, 1 - weight, stride ); - } + } - const progUniforms = program.getUniforms(); - const uniformsList = WebGLUniforms.seqWithValue( progUniforms.seq, uniforms ); + if ( weightAdditive > 0 ) { - materialProperties.currentProgram = program; - materialProperties.uniformsList = uniformsList; + // accuN := accuN + additive accuN - return program; + this._mixBufferRegionAdditive( buffer, offset, this._addIndex * stride, 1, stride ); } - function updateCommonMaterialProperties( material, parameters ) { + for ( let i = stride, e = stride + stride; i !== e; ++ i ) { - const materialProperties = properties.get( material ); + if ( buffer[ i ] !== buffer[ i + stride ] ) { - materialProperties.outputColorSpace = parameters.outputColorSpace; - materialProperties.instancing = parameters.instancing; - materialProperties.skinning = parameters.skinning; - materialProperties.morphTargets = parameters.morphTargets; - materialProperties.morphNormals = parameters.morphNormals; - materialProperties.morphColors = parameters.morphColors; - materialProperties.morphTargetsCount = parameters.morphTargetsCount; - materialProperties.numClippingPlanes = parameters.numClippingPlanes; - materialProperties.numIntersection = parameters.numClipIntersection; - materialProperties.vertexAlphas = parameters.vertexAlphas; - materialProperties.vertexTangents = parameters.vertexTangents; - materialProperties.toneMapping = parameters.toneMapping; + // value has changed -> update scene graph - } + binding.setValue( buffer, offset ); + break; - function setProgram( camera, scene, geometry, material, object ) { + } - if ( scene.isScene !== true ) scene = _emptyScene; // scene could be a Mesh, Line, Points, ... + } - textures.resetTextureUnits(); + } - const fog = scene.fog; - const environment = material.isMeshStandardMaterial ? scene.environment : null; - const colorSpace = ( _currentRenderTarget === null ) ? _this.outputColorSpace : ( _currentRenderTarget.isXRRenderTarget === true ? _currentRenderTarget.texture.colorSpace : LinearSRGBColorSpace ); - const envMap = ( material.isMeshStandardMaterial ? cubeuvmaps : cubemaps ).get( material.envMap || environment ); - const vertexAlphas = material.vertexColors === true && !! geometry.attributes.color && geometry.attributes.color.itemSize === 4; - const vertexTangents = !! geometry.attributes.tangent && ( !! material.normalMap || material.anisotropy > 0 ); - const morphTargets = !! geometry.morphAttributes.position; - const morphNormals = !! geometry.morphAttributes.normal; - const morphColors = !! geometry.morphAttributes.color; - const toneMapping = material.toneMapped ? _this.toneMapping : NoToneMapping; - const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color; - const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0; + /** + * Remembers the state of the bound property and copy it to both accus. + */ + saveOriginalState() { - const materialProperties = properties.get( material ); - const lights = currentRenderState.state.lights; + const binding = this.binding; - if ( _clippingEnabled === true ) { + const buffer = this.buffer, + stride = this.valueSize, - if ( _localClippingEnabled === true || camera !== _currentCamera ) { + originalValueOffset = stride * this._origIndex; - const useCache = - camera === _currentCamera && - material.id === _currentMaterialId; + binding.getValue( buffer, originalValueOffset ); - // we might want to call this function with some ClippingGroup - // object instead of the material, once it becomes feasible - // (#8465, #8379) - clipping.setState( material, camera, useCache ); + // accu[0..1] := orig -- initially detect changes against the original + for ( let i = stride, e = originalValueOffset; i !== e; ++ i ) { - } + buffer[ i ] = buffer[ originalValueOffset + ( i % stride ) ]; - } + } - // + // Add to identity for additive + this._setIdentity(); - let needsProgramChange = false; + this.cumulativeWeight = 0; + this.cumulativeWeightAdditive = 0; - if ( material.version === materialProperties.__version ) { + } - if ( materialProperties.needsLights && ( materialProperties.lightsStateVersion !== lights.state.version ) ) { + /** + * Applies the state previously taken via {@link PropertyMixer#saveOriginalState} to the binding. + */ + restoreOriginalState() { - needsProgramChange = true; + const originalValueOffset = this.valueSize * 3; + this.binding.setValue( this.buffer, originalValueOffset ); - } else if ( materialProperties.outputColorSpace !== colorSpace ) { + } - needsProgramChange = true; + // internals - } else if ( object.isInstancedMesh && materialProperties.instancing === false ) { + _setAdditiveIdentityNumeric() { - needsProgramChange = true; + const startIndex = this._addIndex * this.valueSize; + const endIndex = startIndex + this.valueSize; - } else if ( ! object.isInstancedMesh && materialProperties.instancing === true ) { + for ( let i = startIndex; i < endIndex; i ++ ) { - needsProgramChange = true; + this.buffer[ i ] = 0; - } else if ( object.isSkinnedMesh && materialProperties.skinning === false ) { + } - needsProgramChange = true; + } - } else if ( ! object.isSkinnedMesh && materialProperties.skinning === true ) { + _setAdditiveIdentityQuaternion() { - needsProgramChange = true; + this._setAdditiveIdentityNumeric(); + this.buffer[ this._addIndex * this.valueSize + 3 ] = 1; + + } - } else if ( materialProperties.envMap !== envMap ) { + _setAdditiveIdentityOther() { - needsProgramChange = true; + const startIndex = this._origIndex * this.valueSize; + const targetIndex = this._addIndex * this.valueSize; - } else if ( material.fog === true && materialProperties.fog !== fog ) { + for ( let i = 0; i < this.valueSize; i ++ ) { - needsProgramChange = true; + this.buffer[ targetIndex + i ] = this.buffer[ startIndex + i ]; - } else if ( materialProperties.numClippingPlanes !== undefined && - ( materialProperties.numClippingPlanes !== clipping.numPlanes || - materialProperties.numIntersection !== clipping.numIntersection ) ) { + } - needsProgramChange = true; + } - } else if ( materialProperties.vertexAlphas !== vertexAlphas ) { - needsProgramChange = true; + // mix functions - } else if ( materialProperties.vertexTangents !== vertexTangents ) { + _select( buffer, dstOffset, srcOffset, t, stride ) { - needsProgramChange = true; + if ( t >= 0.5 ) { - } else if ( materialProperties.morphTargets !== morphTargets ) { + for ( let i = 0; i !== stride; ++ i ) { - needsProgramChange = true; + buffer[ dstOffset + i ] = buffer[ srcOffset + i ]; - } else if ( materialProperties.morphNormals !== morphNormals ) { + } - needsProgramChange = true; + } - } else if ( materialProperties.morphColors !== morphColors ) { + } - needsProgramChange = true; + _slerp( buffer, dstOffset, srcOffset, t ) { - } else if ( materialProperties.toneMapping !== toneMapping ) { + Quaternion.slerpFlat( buffer, dstOffset, buffer, dstOffset, buffer, srcOffset, t ); - needsProgramChange = true; + } - } else if ( capabilities.isWebGL2 === true && materialProperties.morphTargetsCount !== morphTargetsCount ) { + _slerpAdditive( buffer, dstOffset, srcOffset, t, stride ) { - needsProgramChange = true; + const workOffset = this._workIndex * stride; - } + // Store result in intermediate buffer offset + Quaternion.multiplyQuaternionsFlat( buffer, workOffset, buffer, dstOffset, buffer, srcOffset ); - } else { + // Slerp to the intermediate result + Quaternion.slerpFlat( buffer, dstOffset, buffer, dstOffset, buffer, workOffset, t ); - needsProgramChange = true; - materialProperties.__version = material.version; + } - } + _lerp( buffer, dstOffset, srcOffset, t, stride ) { - // + const s = 1 - t; - let program = materialProperties.currentProgram; + for ( let i = 0; i !== stride; ++ i ) { - if ( needsProgramChange === true ) { + const j = dstOffset + i; - program = getProgram( material, scene, object ); + buffer[ j ] = buffer[ j ] * s + buffer[ srcOffset + i ] * t; - } + } - let refreshProgram = false; - let refreshMaterial = false; - let refreshLights = false; + } - const p_uniforms = program.getUniforms(), - m_uniforms = materialProperties.uniforms; + _lerpAdditive( buffer, dstOffset, srcOffset, t, stride ) { - if ( state.useProgram( program.program ) ) { + for ( let i = 0; i !== stride; ++ i ) { - refreshProgram = true; - refreshMaterial = true; - refreshLights = true; + const j = dstOffset + i; - } + buffer[ j ] = buffer[ j ] + buffer[ srcOffset + i ] * t; - if ( material.id !== _currentMaterialId ) { + } - _currentMaterialId = material.id; + } - refreshMaterial = true; +} - } +// Characters [].:/ are reserved for track binding syntax. +const _RESERVED_CHARS_RE = '\\[\\]\\.:\\/'; +const _reservedRe = new RegExp( '[' + _RESERVED_CHARS_RE + ']', 'g' ); - if ( refreshProgram || _currentCamera !== camera ) { +// Attempts to allow node names from any language. ES5's `\w` regexp matches +// only latin characters, and the unicode \p{L} is not yet supported. So +// instead, we exclude reserved characters and match everything else. +const _wordChar = '[^' + _RESERVED_CHARS_RE + ']'; +const _wordCharOrDot = '[^' + _RESERVED_CHARS_RE.replace( '\\.', '' ) + ']'; - p_uniforms.setValue( _gl, 'projectionMatrix', camera.projectionMatrix ); +// Parent directories, delimited by '/' or ':'. Currently unused, but must +// be matched to parse the rest of the track name. +const _directoryRe = /*@__PURE__*/ /((?:WC+[\/:])*)/.source.replace( 'WC', _wordChar ); - if ( capabilities.logarithmicDepthBuffer ) { +// Target node. May contain word characters (a-zA-Z0-9_) and '.' or '-'. +const _nodeRe = /*@__PURE__*/ /(WCOD+)?/.source.replace( 'WCOD', _wordCharOrDot ); - p_uniforms.setValue( _gl, 'logDepthBufFC', - 2.0 / ( Math.log( camera.far + 1.0 ) / Math.LN2 ) ); +// Object on target node, and accessor. May not contain reserved +// characters. Accessor may contain any character except closing bracket. +const _objectRe = /*@__PURE__*/ /(?:\.(WC+)(?:\[(.+)\])?)?/.source.replace( 'WC', _wordChar ); - } +// Property and accessor. May not contain reserved characters. Accessor may +// contain any non-bracket characters. +const _propertyRe = /*@__PURE__*/ /\.(WC+)(?:\[(.+)\])?/.source.replace( 'WC', _wordChar ); - if ( _currentCamera !== camera ) { +const _trackRe = new RegExp( '' + + '^' + + _directoryRe + + _nodeRe + + _objectRe + + _propertyRe + + '$' +); - _currentCamera = camera; +const _supportedObjectNames = [ 'material', 'materials', 'bones', 'map' ]; - // lighting uniforms depend on the camera so enforce an update - // now, in case this material supports lights - or later, when - // the next material that does gets activated: +class Composite { - refreshMaterial = true; // set to true on material change - refreshLights = true; // remains set until update done + constructor( targetGroup, path, optionalParsedPath ) { - } + const parsedPath = optionalParsedPath || PropertyBinding.parseTrackName( path ); - // load material specific uniforms - // (shader material also gets them for the sake of genericity) + this._targetGroup = targetGroup; + this._bindings = targetGroup.subscribe_( path, parsedPath ); - if ( material.isShaderMaterial || - material.isMeshPhongMaterial || - material.isMeshToonMaterial || - material.isMeshStandardMaterial || - material.envMap ) { + } - const uCamPos = p_uniforms.map.cameraPosition; + getValue( array, offset ) { - if ( uCamPos !== undefined ) { + this.bind(); // bind all binding - uCamPos.setValue( _gl, - _vector3.setFromMatrixPosition( camera.matrixWorld ) ); + const firstValidIndex = this._targetGroup.nCachedObjects_, + binding = this._bindings[ firstValidIndex ]; - } + // and only call .getValue on the first + if ( binding !== undefined ) binding.getValue( array, offset ); - } + } - if ( material.isMeshPhongMaterial || - material.isMeshToonMaterial || - material.isMeshLambertMaterial || - material.isMeshBasicMaterial || - material.isMeshStandardMaterial || - material.isShaderMaterial ) { + setValue( array, offset ) { - p_uniforms.setValue( _gl, 'isOrthographic', camera.isOrthographicCamera === true ); + const bindings = this._bindings; - } + for ( let i = this._targetGroup.nCachedObjects_, n = bindings.length; i !== n; ++ i ) { - if ( material.isMeshPhongMaterial || - material.isMeshToonMaterial || - material.isMeshLambertMaterial || - material.isMeshBasicMaterial || - material.isMeshStandardMaterial || - material.isShaderMaterial || - material.isShadowMaterial || - object.isSkinnedMesh ) { + bindings[ i ].setValue( array, offset ); - p_uniforms.setValue( _gl, 'viewMatrix', camera.matrixWorldInverse ); + } - } + } - } + bind() { - // skinning and morph target uniforms must be set even if material didn't change - // auto-setting of texture unit for bone and morph texture must go before other textures - // otherwise textures used for skinning and morphing can take over texture units reserved for other material textures + const bindings = this._bindings; - if ( object.isSkinnedMesh ) { + for ( let i = this._targetGroup.nCachedObjects_, n = bindings.length; i !== n; ++ i ) { - p_uniforms.setOptional( _gl, object, 'bindMatrix' ); - p_uniforms.setOptional( _gl, object, 'bindMatrixInverse' ); + bindings[ i ].bind(); - const skeleton = object.skeleton; + } - if ( skeleton ) { + } - if ( capabilities.floatVertexTextures ) { + unbind() { - if ( skeleton.boneTexture === null ) skeleton.computeBoneTexture(); + const bindings = this._bindings; - p_uniforms.setValue( _gl, 'boneTexture', skeleton.boneTexture, textures ); - p_uniforms.setValue( _gl, 'boneTextureSize', skeleton.boneTextureSize ); + for ( let i = this._targetGroup.nCachedObjects_, n = bindings.length; i !== n; ++ i ) { - } else { + bindings[ i ].unbind(); - console.warn( 'THREE.WebGLRenderer: SkinnedMesh can only be used with WebGL 2. With WebGL 1 OES_texture_float and vertex textures support is required.' ); + } - } + } - } +} - } +// Note: This class uses a State pattern on a per-method basis: +// 'bind' sets 'this.getValue' / 'setValue' and shadows the +// prototype version of these methods with one that represents +// the bound state. When the property is not found, the methods +// become no-ops. - const morphAttributes = geometry.morphAttributes; - if ( morphAttributes.position !== undefined || morphAttributes.normal !== undefined || ( morphAttributes.color !== undefined && capabilities.isWebGL2 === true ) ) { +/** + * This holds a reference to a real property in the scene graph; used internally. + */ +class PropertyBinding { - morphtargets.update( object, geometry, program ); + /** + * Constructs a new property binding. + * + * @param {Object} rootNode - The root node. + * @param {string} path - The path. + * @param {?Object} [parsedPath] - The parsed path. + */ + constructor( rootNode, path, parsedPath ) { - } + /** + * The object path to the animated property. + * + * @type {string} + */ + this.path = path; - if ( refreshMaterial || materialProperties.receiveShadow !== object.receiveShadow ) { + /** + * An object holding information about the path. + * + * @type {Object} + */ + this.parsedPath = parsedPath || PropertyBinding.parseTrackName( path ); - materialProperties.receiveShadow = object.receiveShadow; - p_uniforms.setValue( _gl, 'receiveShadow', object.receiveShadow ); + /** + * The object owns the animated property. + * + * @type {?Object} + */ + this.node = PropertyBinding.findNode( rootNode, this.parsedPath.nodeName ); - } + /** + * The root node. + * + * @type {Object3D|Skeleton} + */ + this.rootNode = rootNode; - // https://github.com/mrdoob/three.js/pull/24467#issuecomment-1209031512 + // initial state of these methods that calls 'bind' + this.getValue = this._getValue_unbound; + this.setValue = this._setValue_unbound; - if ( material.isMeshGouraudMaterial && material.envMap !== null ) { + } - m_uniforms.envMap.value = envMap; - m_uniforms.flipEnvMap.value = ( envMap.isCubeTexture && envMap.isRenderTargetTexture === false ) ? - 1 : 1; + /** + * Factory method for creating a property binding from the given parameters. + * + * @static + * @param {Object} root - The root node. + * @param {string} path - The path. + * @param {?Object} [parsedPath] - The parsed path. + * @return {PropertyBinding|Composite} The created property binding or composite. + */ + static create( root, path, parsedPath ) { - } + if ( ! ( root && root.isAnimationObjectGroup ) ) { - if ( refreshMaterial ) { + return new PropertyBinding( root, path, parsedPath ); - p_uniforms.setValue( _gl, 'toneMappingExposure', _this.toneMappingExposure ); + } else { - if ( materialProperties.needsLights ) { + return new PropertyBinding.Composite( root, path, parsedPath ); - // the current material requires lighting info + } - // note: all lighting uniforms are always set correctly - // they simply reference the renderer's state for their - // values - // - // use the current material's .needsUpdate flags to set - // the GL state when required + } - markUniformsLightsNeedsUpdate( m_uniforms, refreshLights ); + /** + * Replaces spaces with underscores and removes unsupported characters from + * node names, to ensure compatibility with parseTrackName(). + * + * @param {string} name - Node name to be sanitized. + * @return {string} The sanitized node name. + */ + static sanitizeNodeName( name ) { - } + return name.replace( /\s/g, '_' ).replace( _reservedRe, '' ); - // refresh uniforms common to several materials + } - if ( fog && material.fog === true ) { + /** + * Parses the given track name (an object path to an animated property) and + * returns an object with information about the path. Matches strings in the following forms: + * + * - nodeName.property + * - nodeName.property[accessor] + * - nodeName.material.property[accessor] + * - uuid.property[accessor] + * - uuid.objectName[objectIndex].propertyName[propertyIndex] + * - parentName/nodeName.property + * - parentName/parentName/nodeName.property[index] + * - .bone[Armature.DEF_cog].position + * - scene:helium_balloon_model:helium_balloon_model.position + * + * @static + * @param {string} trackName - The track name to parse. + * @return {Object} The parsed track name as an object. + */ + static parseTrackName( trackName ) { - materials.refreshFogUniforms( m_uniforms, fog ); + const matches = _trackRe.exec( trackName ); - } + if ( matches === null ) { - materials.refreshMaterialUniforms( m_uniforms, material, _pixelRatio, _height, _transmissionRenderTarget ); + throw new Error( 'PropertyBinding: Cannot parse trackName: ' + trackName ); - WebGLUniforms.upload( _gl, materialProperties.uniformsList, m_uniforms, textures ); + } - } + const results = { + // directoryName: matches[ 1 ], // (tschw) currently unused + nodeName: matches[ 2 ], + objectName: matches[ 3 ], + objectIndex: matches[ 4 ], + propertyName: matches[ 5 ], // required + propertyIndex: matches[ 6 ] + }; - if ( material.isShaderMaterial && material.uniformsNeedUpdate === true ) { + const lastDot = results.nodeName && results.nodeName.lastIndexOf( '.' ); - WebGLUniforms.upload( _gl, materialProperties.uniformsList, m_uniforms, textures ); - material.uniformsNeedUpdate = false; + if ( lastDot !== undefined && lastDot !== -1 ) { - } + const objectName = results.nodeName.substring( lastDot + 1 ); - if ( material.isSpriteMaterial ) { + // Object names must be checked against an allowlist. Otherwise, there + // is no way to parse 'foo.bar.baz': 'baz' must be a property, but + // 'bar' could be the objectName, or part of a nodeName (which can + // include '.' characters). + if ( _supportedObjectNames.indexOf( objectName ) !== -1 ) { - p_uniforms.setValue( _gl, 'center', object.center ); + results.nodeName = results.nodeName.substring( 0, lastDot ); + results.objectName = objectName; } - // common matrices + } - p_uniforms.setValue( _gl, 'modelViewMatrix', object.modelViewMatrix ); - p_uniforms.setValue( _gl, 'normalMatrix', object.normalMatrix ); - p_uniforms.setValue( _gl, 'modelMatrix', object.matrixWorld ); + if ( results.propertyName === null || results.propertyName.length === 0 ) { - // UBOs + throw new Error( 'PropertyBinding: can not parse propertyName from trackName: ' + trackName ); - if ( material.isShaderMaterial || material.isRawShaderMaterial ) { + } - const groups = material.uniformsGroups; + return results; - for ( let i = 0, l = groups.length; i < l; i ++ ) { + } - if ( capabilities.isWebGL2 ) { + /** + * Searches for a node in the hierarchy of the given root object by the given + * node name. + * + * @static + * @param {Object} root - The root object. + * @param {string|number} nodeName - The name of the node. + * @return {?Object} The found node. Returns `null` if no object was found. + */ + static findNode( root, nodeName ) { - const group = groups[ i ]; + if ( nodeName === undefined || nodeName === '' || nodeName === '.' || nodeName === -1 || nodeName === root.name || nodeName === root.uuid ) { - uniformsGroups.update( group, program ); - uniformsGroups.bind( group, program ); + return root; - } else { + } - console.warn( 'THREE.WebGLRenderer: Uniform Buffer Objects can only be used with WebGL 2.' ); + // search into skeleton bones. + if ( root.skeleton ) { - } + const bone = root.skeleton.getBoneByName( nodeName ); - } + if ( bone !== undefined ) { - } + return bone; - return program; + } } - // If uniforms are marked as clean, they don't need to be loaded to the GPU. - - function markUniformsLightsNeedsUpdate( uniforms, value ) { - - uniforms.ambientLightColor.needsUpdate = value; - uniforms.lightProbe.needsUpdate = value; - - uniforms.directionalLights.needsUpdate = value; - uniforms.directionalLightShadows.needsUpdate = value; - uniforms.pointLights.needsUpdate = value; - uniforms.pointLightShadows.needsUpdate = value; - uniforms.spotLights.needsUpdate = value; - uniforms.spotLightShadows.needsUpdate = value; - uniforms.rectAreaLights.needsUpdate = value; - uniforms.hemisphereLights.needsUpdate = value; + // search into node subtree. + if ( root.children ) { - } + const searchNodeSubtree = function ( children ) { - function materialNeedsLights( material ) { + for ( let i = 0; i < children.length; i ++ ) { - return material.isMeshLambertMaterial || material.isMeshToonMaterial || material.isMeshPhongMaterial || - material.isMeshStandardMaterial || material.isShadowMaterial || - ( material.isShaderMaterial && material.lights === true ); + const childNode = children[ i ]; - } + if ( childNode.name === nodeName || childNode.uuid === nodeName ) { - this.getActiveCubeFace = function () { + return childNode; - return _currentActiveCubeFace; + } - }; + const result = searchNodeSubtree( childNode.children ); - this.getActiveMipmapLevel = function () { + if ( result ) return result; - return _currentActiveMipmapLevel; + } - }; + return null; - this.getRenderTarget = function () { + }; - return _currentRenderTarget; + const subTreeNode = searchNodeSubtree( root.children ); - }; + if ( subTreeNode ) { - this.setRenderTargetTextures = function ( renderTarget, colorTexture, depthTexture ) { + return subTreeNode; - properties.get( renderTarget.texture ).__webglTexture = colorTexture; - properties.get( renderTarget.depthTexture ).__webglTexture = depthTexture; + } - const renderTargetProperties = properties.get( renderTarget ); - renderTargetProperties.__hasExternalTextures = true; + } - if ( renderTargetProperties.__hasExternalTextures ) { + return null; - renderTargetProperties.__autoAllocateDepthBuffer = depthTexture === undefined; + } - if ( ! renderTargetProperties.__autoAllocateDepthBuffer ) { + // these are used to "bind" a nonexistent property + _getValue_unavailable() {} + _setValue_unavailable() {} - // The multisample_render_to_texture extension doesn't work properly if there - // are midframe flushes and an external depth buffer. Disable use of the extension. - if ( extensions.has( 'WEBGL_multisampled_render_to_texture' ) === true ) { + // Getters - console.warn( 'THREE.WebGLRenderer: Render-to-texture extension was disabled because an external texture was provided' ); - renderTargetProperties.__useRenderToTexture = false; + _getValue_direct( buffer, offset ) { - } + buffer[ offset ] = this.targetObject[ this.propertyName ]; - } + } - } + _getValue_array( buffer, offset ) { - }; + const source = this.resolvedProperty; - this.setRenderTargetFramebuffer = function ( renderTarget, defaultFramebuffer ) { + for ( let i = 0, n = source.length; i !== n; ++ i ) { - const renderTargetProperties = properties.get( renderTarget ); - renderTargetProperties.__webglFramebuffer = defaultFramebuffer; - renderTargetProperties.__useDefaultFramebuffer = defaultFramebuffer === undefined; + buffer[ offset ++ ] = source[ i ]; - }; + } - this.setRenderTarget = function ( renderTarget, activeCubeFace = 0, activeMipmapLevel = 0 ) { + } - _currentRenderTarget = renderTarget; - _currentActiveCubeFace = activeCubeFace; - _currentActiveMipmapLevel = activeMipmapLevel; + _getValue_arrayElement( buffer, offset ) { - let useDefaultFramebuffer = true; - let framebuffer = null; - let isCube = false; - let isRenderTarget3D = false; + buffer[ offset ] = this.resolvedProperty[ this.propertyIndex ]; - if ( renderTarget ) { + } - const renderTargetProperties = properties.get( renderTarget ); + _getValue_toArray( buffer, offset ) { - if ( renderTargetProperties.__useDefaultFramebuffer !== undefined ) { + this.resolvedProperty.toArray( buffer, offset ); - // We need to make sure to rebind the framebuffer. - state.bindFramebuffer( _gl.FRAMEBUFFER, null ); - useDefaultFramebuffer = false; + } - } else if ( renderTargetProperties.__webglFramebuffer === undefined ) { + // Direct - textures.setupRenderTarget( renderTarget ); + _setValue_direct( buffer, offset ) { - } else if ( renderTargetProperties.__hasExternalTextures ) { + this.targetObject[ this.propertyName ] = buffer[ offset ]; - // Color and depth texture must be rebound in order for the swapchain to update. - textures.rebindTextures( renderTarget, properties.get( renderTarget.texture ).__webglTexture, properties.get( renderTarget.depthTexture ).__webglTexture ); + } - } + _setValue_direct_setNeedsUpdate( buffer, offset ) { - const texture = renderTarget.texture; + this.targetObject[ this.propertyName ] = buffer[ offset ]; + this.targetObject.needsUpdate = true; - if ( texture.isData3DTexture || texture.isDataArrayTexture || texture.isCompressedArrayTexture ) { + } - isRenderTarget3D = true; + _setValue_direct_setMatrixWorldNeedsUpdate( buffer, offset ) { - } + this.targetObject[ this.propertyName ] = buffer[ offset ]; + this.targetObject.matrixWorldNeedsUpdate = true; - const __webglFramebuffer = properties.get( renderTarget ).__webglFramebuffer; + } - if ( renderTarget.isWebGLCubeRenderTarget ) { + // EntireArray - framebuffer = __webglFramebuffer[ activeCubeFace ]; - isCube = true; + _setValue_array( buffer, offset ) { - } else if ( ( capabilities.isWebGL2 && renderTarget.samples > 0 ) && textures.useMultisampledRTT( renderTarget ) === false ) { + const dest = this.resolvedProperty; - framebuffer = properties.get( renderTarget ).__webglMultisampledFramebuffer; + for ( let i = 0, n = dest.length; i !== n; ++ i ) { - } else { + dest[ i ] = buffer[ offset ++ ]; - framebuffer = __webglFramebuffer; + } - } + } - _currentViewport.copy( renderTarget.viewport ); - _currentScissor.copy( renderTarget.scissor ); - _currentScissorTest = renderTarget.scissorTest; + _setValue_array_setNeedsUpdate( buffer, offset ) { - } else { + const dest = this.resolvedProperty; - _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ).floor(); - _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ).floor(); - _currentScissorTest = _scissorTest; + for ( let i = 0, n = dest.length; i !== n; ++ i ) { - } + dest[ i ] = buffer[ offset ++ ]; - const framebufferBound = state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); + } - if ( framebufferBound && capabilities.drawBuffers && useDefaultFramebuffer ) { + this.targetObject.needsUpdate = true; - state.drawBuffers( renderTarget, framebuffer ); + } - } + _setValue_array_setMatrixWorldNeedsUpdate( buffer, offset ) { - state.viewport( _currentViewport ); - state.scissor( _currentScissor ); - state.setScissorTest( _currentScissorTest ); + const dest = this.resolvedProperty; - if ( isCube ) { + for ( let i = 0, n = dest.length; i !== n; ++ i ) { - const textureProperties = properties.get( renderTarget.texture ); - _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + activeCubeFace, textureProperties.__webglTexture, activeMipmapLevel ); + dest[ i ] = buffer[ offset ++ ]; - } else if ( isRenderTarget3D ) { + } - const textureProperties = properties.get( renderTarget.texture ); - const layer = activeCubeFace || 0; - _gl.framebufferTextureLayer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, textureProperties.__webglTexture, activeMipmapLevel || 0, layer ); + this.targetObject.matrixWorldNeedsUpdate = true; - } + } - _currentMaterialId = - 1; // reset current material to ensure correct uniform bindings + // ArrayElement - }; + _setValue_arrayElement( buffer, offset ) { - this.readRenderTargetPixels = function ( renderTarget, x, y, width, height, buffer, activeCubeFaceIndex ) { + this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ]; - if ( ! ( renderTarget && renderTarget.isWebGLRenderTarget ) ) { + } - console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.' ); - return; + _setValue_arrayElement_setNeedsUpdate( buffer, offset ) { - } + this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ]; + this.targetObject.needsUpdate = true; - let framebuffer = properties.get( renderTarget ).__webglFramebuffer; + } - if ( renderTarget.isWebGLCubeRenderTarget && activeCubeFaceIndex !== undefined ) { + _setValue_arrayElement_setMatrixWorldNeedsUpdate( buffer, offset ) { - framebuffer = framebuffer[ activeCubeFaceIndex ]; + this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ]; + this.targetObject.matrixWorldNeedsUpdate = true; - } + } - if ( framebuffer ) { + // HasToFromArray - state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); + _setValue_fromArray( buffer, offset ) { - try { + this.resolvedProperty.fromArray( buffer, offset ); - const texture = renderTarget.texture; - const textureFormat = texture.format; - const textureType = texture.type; + } - if ( textureFormat !== RGBAFormat && utils.convert( textureFormat ) !== _gl.getParameter( _gl.IMPLEMENTATION_COLOR_READ_FORMAT ) ) { + _setValue_fromArray_setNeedsUpdate( buffer, offset ) { - console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in RGBA or implementation defined format.' ); - return; + this.resolvedProperty.fromArray( buffer, offset ); + this.targetObject.needsUpdate = true; - } + } - const halfFloatSupportedByExt = ( textureType === HalfFloatType ) && ( extensions.has( 'EXT_color_buffer_half_float' ) || ( capabilities.isWebGL2 && extensions.has( 'EXT_color_buffer_float' ) ) ); + _setValue_fromArray_setMatrixWorldNeedsUpdate( buffer, offset ) { - if ( textureType !== UnsignedByteType && utils.convert( textureType ) !== _gl.getParameter( _gl.IMPLEMENTATION_COLOR_READ_TYPE ) && // Edge and Chrome Mac < 52 (#9513) - ! ( textureType === FloatType && ( capabilities.isWebGL2 || extensions.has( 'OES_texture_float' ) || extensions.has( 'WEBGL_color_buffer_float' ) ) ) && // Chrome Mac >= 52 and Firefox - ! halfFloatSupportedByExt ) { + this.resolvedProperty.fromArray( buffer, offset ); + this.targetObject.matrixWorldNeedsUpdate = true; - console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in UnsignedByteType or implementation defined type.' ); - return; + } - } + _getValue_unbound( targetArray, offset ) { - // the following if statement ensures valid read requests (no out-of-bounds pixels, see #8604) + this.bind(); + this.getValue( targetArray, offset ); - if ( ( x >= 0 && x <= ( renderTarget.width - width ) ) && ( y >= 0 && y <= ( renderTarget.height - height ) ) ) { + } - _gl.readPixels( x, y, width, height, utils.convert( textureFormat ), utils.convert( textureType ), buffer ); + _setValue_unbound( sourceArray, offset ) { - } + this.bind(); + this.setValue( sourceArray, offset ); - } finally { + } - // restore framebuffer of current render target if necessary + /** + * Creates a getter / setter pair for the property tracked by this binding. + */ + bind() { - const framebuffer = ( _currentRenderTarget !== null ) ? properties.get( _currentRenderTarget ).__webglFramebuffer : null; - state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); + let targetObject = this.node; + const parsedPath = this.parsedPath; - } + const objectName = parsedPath.objectName; + const propertyName = parsedPath.propertyName; + let propertyIndex = parsedPath.propertyIndex; - } + if ( ! targetObject ) { - }; + targetObject = PropertyBinding.findNode( this.rootNode, parsedPath.nodeName ); - this.copyFramebufferToTexture = function ( position, texture, level = 0 ) { + this.node = targetObject; - const levelScale = Math.pow( 2, - level ); - const width = Math.floor( texture.image.width * levelScale ); - const height = Math.floor( texture.image.height * levelScale ); + } - textures.setTexture2D( texture, 0 ); + // set fail state so we can just 'return' on error + this.getValue = this._getValue_unavailable; + this.setValue = this._setValue_unavailable; - _gl.copyTexSubImage2D( _gl.TEXTURE_2D, level, 0, 0, position.x, position.y, width, height ); + // ensure there is a value node + if ( ! targetObject ) { - state.unbindTexture(); + console.warn( 'THREE.PropertyBinding: No target node found for track: ' + this.path + '.' ); + return; - }; + } - this.copyTextureToTexture = function ( position, srcTexture, dstTexture, level = 0 ) { + if ( objectName ) { - const width = srcTexture.image.width; - const height = srcTexture.image.height; - const glFormat = utils.convert( dstTexture.format ); - const glType = utils.convert( dstTexture.type ); + let objectIndex = parsedPath.objectIndex; - textures.setTexture2D( dstTexture, 0 ); + // special cases were we need to reach deeper into the hierarchy to get the face materials.... + switch ( objectName ) { - // As another texture upload may have changed pixelStorei - // parameters, make sure they are correct for the dstTexture - _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, dstTexture.flipY ); - _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, dstTexture.premultiplyAlpha ); - _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, dstTexture.unpackAlignment ); + case 'materials': - if ( srcTexture.isDataTexture ) { + if ( ! targetObject.material ) { - _gl.texSubImage2D( _gl.TEXTURE_2D, level, position.x, position.y, width, height, glFormat, glType, srcTexture.image.data ); + console.error( 'THREE.PropertyBinding: Can not bind to material as node does not have a material.', this ); + return; - } else { + } - if ( srcTexture.isCompressedTexture ) { + if ( ! targetObject.material.materials ) { - _gl.compressedTexSubImage2D( _gl.TEXTURE_2D, level, position.x, position.y, srcTexture.mipmaps[ 0 ].width, srcTexture.mipmaps[ 0 ].height, glFormat, srcTexture.mipmaps[ 0 ].data ); + console.error( 'THREE.PropertyBinding: Can not bind to material.materials as node.material does not have a materials array.', this ); + return; - } else { + } - _gl.texSubImage2D( _gl.TEXTURE_2D, level, position.x, position.y, glFormat, glType, srcTexture.image ); + targetObject = targetObject.material.materials; - } + break; - } + case 'bones': - // Generate mipmaps only when copying level 0 - if ( level === 0 && dstTexture.generateMipmaps ) _gl.generateMipmap( _gl.TEXTURE_2D ); + if ( ! targetObject.skeleton ) { - state.unbindTexture(); + console.error( 'THREE.PropertyBinding: Can not bind to bones as node does not have a skeleton.', this ); + return; - }; + } - this.copyTextureToTexture3D = function ( sourceBox, position, srcTexture, dstTexture, level = 0 ) { + // potential future optimization: skip this if propertyIndex is already an integer + // and convert the integer string to a true integer. - if ( _this.isWebGL1Renderer ) { + targetObject = targetObject.skeleton.bones; - console.warn( 'THREE.WebGLRenderer.copyTextureToTexture3D: can only be used with WebGL2.' ); - return; + // support resolving morphTarget names into indices. + for ( let i = 0; i < targetObject.length; i ++ ) { - } + if ( targetObject[ i ].name === objectIndex ) { - const width = sourceBox.max.x - sourceBox.min.x + 1; - const height = sourceBox.max.y - sourceBox.min.y + 1; - const depth = sourceBox.max.z - sourceBox.min.z + 1; - const glFormat = utils.convert( dstTexture.format ); - const glType = utils.convert( dstTexture.type ); - let glTarget; + objectIndex = i; + break; - if ( dstTexture.isData3DTexture ) { + } - textures.setTexture3D( dstTexture, 0 ); - glTarget = _gl.TEXTURE_3D; + } - } else if ( dstTexture.isDataArrayTexture ) { + break; - textures.setTexture2DArray( dstTexture, 0 ); - glTarget = _gl.TEXTURE_2D_ARRAY; + case 'map': - } else { + if ( 'map' in targetObject ) { - console.warn( 'THREE.WebGLRenderer.copyTextureToTexture3D: only supports THREE.DataTexture3D and THREE.DataTexture2DArray.' ); - return; + targetObject = targetObject.map; + break; - } + } - _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, dstTexture.flipY ); - _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, dstTexture.premultiplyAlpha ); - _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, dstTexture.unpackAlignment ); + if ( ! targetObject.material ) { - const unpackRowLen = _gl.getParameter( _gl.UNPACK_ROW_LENGTH ); - const unpackImageHeight = _gl.getParameter( _gl.UNPACK_IMAGE_HEIGHT ); - const unpackSkipPixels = _gl.getParameter( _gl.UNPACK_SKIP_PIXELS ); - const unpackSkipRows = _gl.getParameter( _gl.UNPACK_SKIP_ROWS ); - const unpackSkipImages = _gl.getParameter( _gl.UNPACK_SKIP_IMAGES ); + console.error( 'THREE.PropertyBinding: Can not bind to material as node does not have a material.', this ); + return; - const image = srcTexture.isCompressedTexture ? srcTexture.mipmaps[ 0 ] : srcTexture.image; + } - _gl.pixelStorei( _gl.UNPACK_ROW_LENGTH, image.width ); - _gl.pixelStorei( _gl.UNPACK_IMAGE_HEIGHT, image.height ); - _gl.pixelStorei( _gl.UNPACK_SKIP_PIXELS, sourceBox.min.x ); - _gl.pixelStorei( _gl.UNPACK_SKIP_ROWS, sourceBox.min.y ); - _gl.pixelStorei( _gl.UNPACK_SKIP_IMAGES, sourceBox.min.z ); + if ( ! targetObject.material.map ) { - if ( srcTexture.isDataTexture || srcTexture.isData3DTexture ) { + console.error( 'THREE.PropertyBinding: Can not bind to material.map as node.material does not have a map.', this ); + return; - _gl.texSubImage3D( glTarget, level, position.x, position.y, position.z, width, height, depth, glFormat, glType, image.data ); + } - } else { + targetObject = targetObject.material.map; + break; - if ( srcTexture.isCompressedArrayTexture ) { + default: - console.warn( 'THREE.WebGLRenderer.copyTextureToTexture3D: untested support for compressed srcTexture.' ); - _gl.compressedTexSubImage3D( glTarget, level, position.x, position.y, position.z, width, height, depth, glFormat, image.data ); + if ( targetObject[ objectName ] === undefined ) { - } else { + console.error( 'THREE.PropertyBinding: Can not bind to objectName of node undefined.', this ); + return; - _gl.texSubImage3D( glTarget, level, position.x, position.y, position.z, width, height, depth, glFormat, glType, image ); + } - } + targetObject = targetObject[ objectName ]; } - _gl.pixelStorei( _gl.UNPACK_ROW_LENGTH, unpackRowLen ); - _gl.pixelStorei( _gl.UNPACK_IMAGE_HEIGHT, unpackImageHeight ); - _gl.pixelStorei( _gl.UNPACK_SKIP_PIXELS, unpackSkipPixels ); - _gl.pixelStorei( _gl.UNPACK_SKIP_ROWS, unpackSkipRows ); - _gl.pixelStorei( _gl.UNPACK_SKIP_IMAGES, unpackSkipImages ); - // Generate mipmaps only when copying level 0 - if ( level === 0 && dstTexture.generateMipmaps ) _gl.generateMipmap( glTarget ); + if ( objectIndex !== undefined ) { - state.unbindTexture(); + if ( targetObject[ objectIndex ] === undefined ) { - }; + console.error( 'THREE.PropertyBinding: Trying to bind to objectIndex of objectName, but is undefined.', this, targetObject ); + return; - this.initTexture = function ( texture ) { + } - if ( texture.isCubeTexture ) { + targetObject = targetObject[ objectIndex ]; - textures.setTextureCube( texture, 0 ); + } - } else if ( texture.isData3DTexture ) { + } - textures.setTexture3D( texture, 0 ); + // resolve property + const nodeProperty = targetObject[ propertyName ]; - } else if ( texture.isDataArrayTexture || texture.isCompressedArrayTexture ) { + if ( nodeProperty === undefined ) { - textures.setTexture2DArray( texture, 0 ); + const nodeName = parsedPath.nodeName; - } else { + console.error( 'THREE.PropertyBinding: Trying to update property for track: ' + nodeName + + '.' + propertyName + ' but it wasn\'t found.', targetObject ); + return; - textures.setTexture2D( texture, 0 ); + } - } + // determine versioning scheme + let versioning = this.Versioning.None; - state.unbindTexture(); + this.targetObject = targetObject; - }; + if ( targetObject.isMaterial === true ) { - this.resetState = function () { + versioning = this.Versioning.NeedsUpdate; - _currentActiveCubeFace = 0; - _currentActiveMipmapLevel = 0; - _currentRenderTarget = null; + } else if ( targetObject.isObject3D === true ) { - state.reset(); - bindingStates.reset(); + versioning = this.Versioning.MatrixWorldNeedsUpdate; - }; + } - if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) { + // determine how the property gets bound + let bindingType = this.BindingType.Direct; - __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'observe', { detail: this } ) ); + if ( propertyIndex !== undefined ) { - } + // access a sub element of the property array (only primitives are supported right now) - } + if ( propertyName === 'morphTargetInfluences' ) { - get physicallyCorrectLights() { // @deprecated, r150 + // potential optimization, skip this if propertyIndex is already an integer, and convert the integer string to a true integer. - console.warn( 'THREE.WebGLRenderer: the property .physicallyCorrectLights has been removed. Set renderer.useLegacyLights instead.' ); - return ! this.useLegacyLights; + // support resolving morphTarget names into indices. + if ( ! targetObject.geometry ) { - } + console.error( 'THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.', this ); + return; - set physicallyCorrectLights( value ) { // @deprecated, r150 + } - console.warn( 'THREE.WebGLRenderer: the property .physicallyCorrectLights has been removed. Set renderer.useLegacyLights instead.' ); - this.useLegacyLights = ! value; + if ( ! targetObject.geometry.morphAttributes ) { - } + console.error( 'THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.morphAttributes.', this ); + return; - get outputEncoding() { // @deprecated, r152 + } - console.warn( 'THREE.WebGLRenderer: Property .outputEncoding has been removed. Use .outputColorSpace instead.' ); - return this.outputColorSpace === SRGBColorSpace ? sRGBEncoding : LinearEncoding; + if ( targetObject.morphTargetDictionary[ propertyIndex ] !== undefined ) { - } + propertyIndex = targetObject.morphTargetDictionary[ propertyIndex ]; - set outputEncoding( encoding ) { // @deprecated, r152 + } - console.warn( 'THREE.WebGLRenderer: Property .outputEncoding has been removed. Use .outputColorSpace instead.' ); - this.outputColorSpace = encoding === sRGBEncoding ? SRGBColorSpace : LinearSRGBColorSpace; + } - } + bindingType = this.BindingType.ArrayElement; -} + this.resolvedProperty = nodeProperty; + this.propertyIndex = propertyIndex; -class WebGL1Renderer extends WebGLRenderer {} + } else if ( nodeProperty.fromArray !== undefined && nodeProperty.toArray !== undefined ) { -WebGL1Renderer.prototype.isWebGL1Renderer = true; + // must use copy for Object3D.Euler/Quaternion -class FogExp2 { + bindingType = this.BindingType.HasFromToArray; - constructor( color, density = 0.00025 ) { + this.resolvedProperty = nodeProperty; - this.isFogExp2 = true; + } else if ( Array.isArray( nodeProperty ) ) { - this.name = ''; + bindingType = this.BindingType.EntireArray; - this.color = new Color( color ); - this.density = density; + this.resolvedProperty = nodeProperty; - } + } else { - clone() { + this.propertyName = propertyName; - return new FogExp2( this.color, this.density ); + } + + // select getter / setter + this.getValue = this.GetterByBindingType[ bindingType ]; + this.setValue = this.SetterByBindingTypeAndVersioning[ bindingType ][ versioning ]; } - toJSON( /* meta */ ) { + /** + * Unbinds the property. + */ + unbind() { - return { - type: 'FogExp2', - color: this.color.getHex(), - density: this.density - }; + this.node = null; + + // back to the prototype version of getValue / setValue + // note: avoiding to mutate the shape of 'this' via 'delete' + this.getValue = this._getValue_unbound; + this.setValue = this._setValue_unbound; } } -class Fog { - - constructor( color, near = 1, far = 1000 ) { - - this.isFog = true; - - this.name = ''; +PropertyBinding.Composite = Composite; - this.color = new Color( color ); +PropertyBinding.prototype.BindingType = { + Direct: 0, + EntireArray: 1, + ArrayElement: 2, + HasFromToArray: 3 +}; - this.near = near; - this.far = far; +PropertyBinding.prototype.Versioning = { + None: 0, + NeedsUpdate: 1, + MatrixWorldNeedsUpdate: 2 +}; - } +PropertyBinding.prototype.GetterByBindingType = [ - clone() { + PropertyBinding.prototype._getValue_direct, + PropertyBinding.prototype._getValue_array, + PropertyBinding.prototype._getValue_arrayElement, + PropertyBinding.prototype._getValue_toArray, - return new Fog( this.color, this.near, this.far ); +]; - } +PropertyBinding.prototype.SetterByBindingTypeAndVersioning = [ - toJSON( /* meta */ ) { + [ + // Direct + PropertyBinding.prototype._setValue_direct, + PropertyBinding.prototype._setValue_direct_setNeedsUpdate, + PropertyBinding.prototype._setValue_direct_setMatrixWorldNeedsUpdate, - return { - type: 'Fog', - color: this.color.getHex(), - near: this.near, - far: this.far - }; + ], [ - } + // EntireArray -} + PropertyBinding.prototype._setValue_array, + PropertyBinding.prototype._setValue_array_setNeedsUpdate, + PropertyBinding.prototype._setValue_array_setMatrixWorldNeedsUpdate, -class Scene extends Object3D { + ], [ - constructor() { + // ArrayElement + PropertyBinding.prototype._setValue_arrayElement, + PropertyBinding.prototype._setValue_arrayElement_setNeedsUpdate, + PropertyBinding.prototype._setValue_arrayElement_setMatrixWorldNeedsUpdate, - super(); + ], [ - this.isScene = true; + // HasToFromArray + PropertyBinding.prototype._setValue_fromArray, + PropertyBinding.prototype._setValue_fromArray_setNeedsUpdate, + PropertyBinding.prototype._setValue_fromArray_setMatrixWorldNeedsUpdate, - this.type = 'Scene'; + ] - this.background = null; - this.environment = null; - this.fog = null; +]; - this.backgroundBlurriness = 0; - this.backgroundIntensity = 1; +/** + * A group of objects that receives a shared animation state. + * + * Usage: + * + * - Add objects you would otherwise pass as 'root' to the + * constructor or the .clipAction method of AnimationMixer. + * - Instead pass this object as 'root'. + * - You can also add and remove objects later when the mixer is running. + * + * Note: + * + * - Objects of this class appear as one object to the mixer, + * so cache control of the individual objects must be done on the group. + * + * Limitation: + * + * - The animated properties must be compatible among the all objects in the group. + * - A single property can either be controlled through a target group or directly, but not both. + */ +class AnimationObjectGroup { - this.overrideMaterial = null; + /** + * Constructs a new animation group. + * + * @param {...Object3D} arguments - An arbitrary number of 3D objects that share the same animation state. + */ + constructor() { - if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isAnimationObjectGroup = true; - __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'observe', { detail: this } ) ); + /** + * The UUID of the 3D object. + * + * @type {string} + * @readonly + */ + this.uuid = generateUUID(); - } + // cached objects followed by the active ones + this._objects = Array.prototype.slice.call( arguments ); - } + this.nCachedObjects_ = 0; // threshold + // note: read by PropertyBinding.Composite - copy( source, recursive ) { + const indices = {}; + this._indicesByUUID = indices; // for bookkeeping - super.copy( source, recursive ); + for ( let i = 0, n = arguments.length; i !== n; ++ i ) { - if ( source.background !== null ) this.background = source.background.clone(); - if ( source.environment !== null ) this.environment = source.environment.clone(); - if ( source.fog !== null ) this.fog = source.fog.clone(); + indices[ arguments[ i ].uuid ] = i; - this.backgroundBlurriness = source.backgroundBlurriness; - this.backgroundIntensity = source.backgroundIntensity; + } - if ( source.overrideMaterial !== null ) this.overrideMaterial = source.overrideMaterial.clone(); + this._paths = []; // inside: string + this._parsedPaths = []; // inside: { we don't care, here } + this._bindings = []; // inside: Array< PropertyBinding > + this._bindingsIndicesByPath = {}; // inside: indices in these arrays - this.matrixAutoUpdate = source.matrixAutoUpdate; + const scope = this; - return this; + this.stats = { - } + objects: { + get total() { - toJSON( meta ) { + return scope._objects.length; - const data = super.toJSON( meta ); + }, + get inUse() { - if ( this.fog !== null ) data.object.fog = this.fog.toJSON(); - if ( this.backgroundBlurriness > 0 ) data.object.backgroundBlurriness = this.backgroundBlurriness; - if ( this.backgroundIntensity !== 1 ) data.object.backgroundIntensity = this.backgroundIntensity; + return this.total - scope.nCachedObjects_; - return data; + } + }, + get bindingsPerObject() { - } + return scope._bindings.length; - get autoUpdate() { // @deprecated, r144 + } - console.warn( 'THREE.Scene: autoUpdate was renamed to matrixWorldAutoUpdate in r144.' ); - return this.matrixWorldAutoUpdate; + }; } - set autoUpdate( value ) { // @deprecated, r144 + /** + * Adds an arbitrary number of objects to this animation group. + * + * @param {...Object3D} arguments - The 3D objects to add. + */ + add() { - console.warn( 'THREE.Scene: autoUpdate was renamed to matrixWorldAutoUpdate in r144.' ); - this.matrixWorldAutoUpdate = value; + const objects = this._objects, + indicesByUUID = this._indicesByUUID, + paths = this._paths, + parsedPaths = this._parsedPaths, + bindings = this._bindings, + nBindings = bindings.length; - } + let knownObject = undefined, + nObjects = objects.length, + nCachedObjects = this.nCachedObjects_; -} + for ( let i = 0, n = arguments.length; i !== n; ++ i ) { -class InterleavedBuffer { + const object = arguments[ i ], + uuid = object.uuid; + let index = indicesByUUID[ uuid ]; - constructor( array, stride ) { + if ( index === undefined ) { - this.isInterleavedBuffer = true; + // unknown object -> add it to the ACTIVE region - this.array = array; - this.stride = stride; - this.count = array !== undefined ? array.length / stride : 0; + index = nObjects ++; + indicesByUUID[ uuid ] = index; + objects.push( object ); - this.usage = StaticDrawUsage; - this.updateRange = { offset: 0, count: - 1 }; + // accounting is done, now do the same for all bindings - this.version = 0; + for ( let j = 0, m = nBindings; j !== m; ++ j ) { - this.uuid = generateUUID(); + bindings[ j ].push( new PropertyBinding( object, paths[ j ], parsedPaths[ j ] ) ); - } + } - onUploadCallback() {} + } else if ( index < nCachedObjects ) { - set needsUpdate( value ) { + knownObject = objects[ index ]; - if ( value === true ) this.version ++; + // move existing object to the ACTIVE region - } + const firstActiveIndex = -- nCachedObjects, + lastCachedObject = objects[ firstActiveIndex ]; - setUsage( value ) { + indicesByUUID[ lastCachedObject.uuid ] = index; + objects[ index ] = lastCachedObject; - this.usage = value; + indicesByUUID[ uuid ] = firstActiveIndex; + objects[ firstActiveIndex ] = object; - return this; + // accounting is done, now do the same for all bindings - } + for ( let j = 0, m = nBindings; j !== m; ++ j ) { - copy( source ) { + const bindingsForPath = bindings[ j ], + lastCached = bindingsForPath[ firstActiveIndex ]; - this.array = new source.array.constructor( source.array ); - this.count = source.count; - this.stride = source.stride; - this.usage = source.usage; + let binding = bindingsForPath[ index ]; - return this; + bindingsForPath[ index ] = lastCached; - } + if ( binding === undefined ) { - copyAt( index1, attribute, index2 ) { + // since we do not bother to create new bindings + // for objects that are cached, the binding may + // or may not exist - index1 *= this.stride; - index2 *= attribute.stride; + binding = new PropertyBinding( object, paths[ j ], parsedPaths[ j ] ); - for ( let i = 0, l = this.stride; i < l; i ++ ) { + } - this.array[ index1 + i ] = attribute.array[ index2 + i ]; + bindingsForPath[ firstActiveIndex ] = binding; - } + } - return this; + } else if ( objects[ index ] !== knownObject ) { - } + console.error( 'THREE.AnimationObjectGroup: Different objects with the same UUID ' + + 'detected. Clean the caches or recreate your infrastructure when reloading scenes.' ); - set( value, offset = 0 ) { + } // else the object is already where we want it to be - this.array.set( value, offset ); + } // for arguments - return this; + this.nCachedObjects_ = nCachedObjects; } - clone( data ) { + /** + * Removes an arbitrary number of objects to this animation group + * + * @param {...Object3D} arguments - The 3D objects to remove. + */ + remove() { - if ( data.arrayBuffers === undefined ) { + const objects = this._objects, + indicesByUUID = this._indicesByUUID, + bindings = this._bindings, + nBindings = bindings.length; + + let nCachedObjects = this.nCachedObjects_; - data.arrayBuffers = {}; + for ( let i = 0, n = arguments.length; i !== n; ++ i ) { - } + const object = arguments[ i ], + uuid = object.uuid, + index = indicesByUUID[ uuid ]; - if ( this.array.buffer._uuid === undefined ) { + if ( index !== undefined && index >= nCachedObjects ) { - this.array.buffer._uuid = generateUUID(); + // move existing object into the CACHED region - } + const lastCachedIndex = nCachedObjects ++, + firstActiveObject = objects[ lastCachedIndex ]; - if ( data.arrayBuffers[ this.array.buffer._uuid ] === undefined ) { + indicesByUUID[ firstActiveObject.uuid ] = index; + objects[ index ] = firstActiveObject; - data.arrayBuffers[ this.array.buffer._uuid ] = this.array.slice( 0 ).buffer; + indicesByUUID[ uuid ] = lastCachedIndex; + objects[ lastCachedIndex ] = object; - } + // accounting is done, now do the same for all bindings - const array = new this.array.constructor( data.arrayBuffers[ this.array.buffer._uuid ] ); + for ( let j = 0, m = nBindings; j !== m; ++ j ) { - const ib = new this.constructor( array, this.stride ); - ib.setUsage( this.usage ); + const bindingsForPath = bindings[ j ], + firstActive = bindingsForPath[ lastCachedIndex ], + binding = bindingsForPath[ index ]; - return ib; + bindingsForPath[ index ] = firstActive; + bindingsForPath[ lastCachedIndex ] = binding; - } + } - onUpload( callback ) { + } - this.onUploadCallback = callback; + } // for arguments - return this; + this.nCachedObjects_ = nCachedObjects; } - toJSON( data ) { + /** + * Deallocates all memory resources for the passed 3D objects of this animation group. + * + * @param {...Object3D} arguments - The 3D objects to uncache. + */ + uncache() { - if ( data.arrayBuffers === undefined ) { + const objects = this._objects, + indicesByUUID = this._indicesByUUID, + bindings = this._bindings, + nBindings = bindings.length; - data.arrayBuffers = {}; + let nCachedObjects = this.nCachedObjects_, + nObjects = objects.length; - } + for ( let i = 0, n = arguments.length; i !== n; ++ i ) { - // generate UUID for array buffer if necessary + const object = arguments[ i ], + uuid = object.uuid, + index = indicesByUUID[ uuid ]; - if ( this.array.buffer._uuid === undefined ) { + if ( index !== undefined ) { - this.array.buffer._uuid = generateUUID(); + delete indicesByUUID[ uuid ]; - } + if ( index < nCachedObjects ) { - if ( data.arrayBuffers[ this.array.buffer._uuid ] === undefined ) { + // object is cached, shrink the CACHED region - data.arrayBuffers[ this.array.buffer._uuid ] = Array.from( new Uint32Array( this.array.buffer ) ); + const firstActiveIndex = -- nCachedObjects, + lastCachedObject = objects[ firstActiveIndex ], + lastIndex = -- nObjects, + lastObject = objects[ lastIndex ]; - } + // last cached object takes this object's place + indicesByUUID[ lastCachedObject.uuid ] = index; + objects[ index ] = lastCachedObject; - // + // last object goes to the activated slot and pop + indicesByUUID[ lastObject.uuid ] = firstActiveIndex; + objects[ firstActiveIndex ] = lastObject; + objects.pop(); - return { - uuid: this.uuid, - buffer: this.array.buffer._uuid, - type: this.array.constructor.name, - stride: this.stride - }; + // accounting is done, now do the same for all bindings - } + for ( let j = 0, m = nBindings; j !== m; ++ j ) { -} + const bindingsForPath = bindings[ j ], + lastCached = bindingsForPath[ firstActiveIndex ], + last = bindingsForPath[ lastIndex ]; -const _vector$5 = /*@__PURE__*/ new Vector3(); + bindingsForPath[ index ] = lastCached; + bindingsForPath[ firstActiveIndex ] = last; + bindingsForPath.pop(); -class InterleavedBufferAttribute { + } - constructor( interleavedBuffer, itemSize, offset, normalized = false ) { + } else { - this.isInterleavedBufferAttribute = true; + // object is active, just swap with the last and pop - this.name = ''; + const lastIndex = -- nObjects, + lastObject = objects[ lastIndex ]; - this.data = interleavedBuffer; - this.itemSize = itemSize; - this.offset = offset; + if ( lastIndex > 0 ) { - this.normalized = normalized; + indicesByUUID[ lastObject.uuid ] = index; - } + } - get count() { + objects[ index ] = lastObject; + objects.pop(); - return this.data.count; + // accounting is done, now do the same for all bindings - } + for ( let j = 0, m = nBindings; j !== m; ++ j ) { - get array() { + const bindingsForPath = bindings[ j ]; - return this.data.array; + bindingsForPath[ index ] = bindingsForPath[ lastIndex ]; + bindingsForPath.pop(); - } + } - set needsUpdate( value ) { + } // cached or active - this.data.needsUpdate = value; + } // if object is known - } + } // for arguments - applyMatrix4( m ) { + this.nCachedObjects_ = nCachedObjects; - for ( let i = 0, l = this.data.count; i < l; i ++ ) { + } - _vector$5.fromBufferAttribute( this, i ); + // Internal interface used by befriended PropertyBinding.Composite: - _vector$5.applyMatrix4( m ); + subscribe_( path, parsedPath ) { - this.setXYZ( i, _vector$5.x, _vector$5.y, _vector$5.z ); + // returns an array of bindings for the given path that is changed + // according to the contained objects in the group - } + const indicesByPath = this._bindingsIndicesByPath; + let index = indicesByPath[ path ]; + const bindings = this._bindings; - return this; + if ( index !== undefined ) return bindings[ index ]; - } + const paths = this._paths, + parsedPaths = this._parsedPaths, + objects = this._objects, + nObjects = objects.length, + nCachedObjects = this.nCachedObjects_, + bindingsForPath = new Array( nObjects ); - applyNormalMatrix( m ) { + index = bindings.length; - for ( let i = 0, l = this.count; i < l; i ++ ) { + indicesByPath[ path ] = index; - _vector$5.fromBufferAttribute( this, i ); + paths.push( path ); + parsedPaths.push( parsedPath ); + bindings.push( bindingsForPath ); - _vector$5.applyNormalMatrix( m ); + for ( let i = nCachedObjects, n = objects.length; i !== n; ++ i ) { - this.setXYZ( i, _vector$5.x, _vector$5.y, _vector$5.z ); + const object = objects[ i ]; + bindingsForPath[ i ] = new PropertyBinding( object, path, parsedPath ); } - return this; + return bindingsForPath; } - transformDirection( m ) { + unsubscribe_( path ) { - for ( let i = 0, l = this.count; i < l; i ++ ) { + // tells the group to forget about a property path and no longer + // update the array previously obtained with 'subscribe_' - _vector$5.fromBufferAttribute( this, i ); + const indicesByPath = this._bindingsIndicesByPath, + index = indicesByPath[ path ]; - _vector$5.transformDirection( m ); + if ( index !== undefined ) { - this.setXYZ( i, _vector$5.x, _vector$5.y, _vector$5.z ); + const paths = this._paths, + parsedPaths = this._parsedPaths, + bindings = this._bindings, + lastBindingsIndex = bindings.length - 1, + lastBindings = bindings[ lastBindingsIndex ], + lastBindingsPath = path[ lastBindingsIndex ]; - } + indicesByPath[ lastBindingsPath ] = index; - return this; + bindings[ index ] = lastBindings; + bindings.pop(); - } + parsedPaths[ index ] = parsedPaths[ lastBindingsIndex ]; + parsedPaths.pop(); - setX( index, x ) { + paths[ index ] = paths[ lastBindingsIndex ]; + paths.pop(); - if ( this.normalized ) x = normalize( x, this.array ); + } - this.data.array[ index * this.data.stride + this.offset ] = x; + } - return this; +} - } +/** + * An instance of `AnimationAction` schedules the playback of an animation which is + * stored in {@link AnimationClip}. + */ +class AnimationAction { - setY( index, y ) { + /** + * Constructs a new animation action. + * + * @param {AnimationMixer} mixer - The mixer that is controlled by this action. + * @param {AnimationClip} clip - The animation clip that holds the actual keyframes. + * @param {?Object3D} [localRoot=null] - The root object on which this action is performed. + * @param {(NormalAnimationBlendMode|AdditiveAnimationBlendMode)} [blendMode] - The blend mode. + */ + constructor( mixer, clip, localRoot = null, blendMode = clip.blendMode ) { - if ( this.normalized ) y = normalize( y, this.array ); + this._mixer = mixer; + this._clip = clip; + this._localRoot = localRoot; - this.data.array[ index * this.data.stride + this.offset + 1 ] = y; + /** + * Defines how the animation is blended/combined when two or more animations + * are simultaneously played. + * + * @type {(NormalAnimationBlendMode|AdditiveAnimationBlendMode)} + */ + this.blendMode = blendMode; - return this; + const tracks = clip.tracks, + nTracks = tracks.length, + interpolants = new Array( nTracks ); - } + const interpolantSettings = { + endingStart: ZeroCurvatureEnding, + endingEnd: ZeroCurvatureEnding + }; - setZ( index, z ) { + for ( let i = 0; i !== nTracks; ++ i ) { - if ( this.normalized ) z = normalize( z, this.array ); + const interpolant = tracks[ i ].createInterpolant( null ); + interpolants[ i ] = interpolant; + interpolant.settings = interpolantSettings; - this.data.array[ index * this.data.stride + this.offset + 2 ] = z; + } - return this; + this._interpolantSettings = interpolantSettings; - } + this._interpolants = interpolants; // bound by the mixer - setW( index, w ) { + // inside: PropertyMixer (managed by the mixer) + this._propertyBindings = new Array( nTracks ); - if ( this.normalized ) w = normalize( w, this.array ); + this._cacheIndex = null; // for the memory manager + this._byClipCacheIndex = null; // for the memory manager - this.data.array[ index * this.data.stride + this.offset + 3 ] = w; + this._timeScaleInterpolant = null; + this._weightInterpolant = null; - return this; + /** + * The loop mode, set via {@link AnimationAction#setLoop}. + * + * @type {(LoopRepeat|LoopOnce|LoopPingPong)} + * @default LoopRepeat + */ + this.loop = LoopRepeat; + this._loopCount = -1; - } + // global mixer time when the action is to be started + // it's set back to 'null' upon start of the action + this._startTime = null; - getX( index ) { + /** + * The local time of this action (in seconds, starting with `0`). + * + * The value gets clamped or wrapped to `[0,clip.duration]` (according to the + * loop state). + * + * @type {number} + * @default Infinity + */ + this.time = 0; - let x = this.data.array[ index * this.data.stride + this.offset ]; + /** + * Scaling factor for the {@link AnimationAction#time}. A value of `0` causes the + * animation to pause. Negative values cause the animation to play backwards. + * + * @type {number} + * @default 1 + */ + this.timeScale = 1; + this._effectiveTimeScale = 1; - if ( this.normalized ) x = denormalize( x, this.array ); + /** + * The degree of influence of this action (in the interval `[0, 1]`). Values + * between `0` (no impact) and `1` (full impact) can be used to blend between + * several actions. + * + * @type {number} + * @default 1 + */ + this.weight = 1; + this._effectiveWeight = 1; - return x; + /** + * The number of repetitions of the performed clip over the course of this action. + * Can be set via {@link AnimationAction#setLoop}. + * + * Setting this number has no effect if {@link AnimationAction#loop} is set to + * `THREE:LoopOnce`. + * + * @type {number} + * @default Infinity + */ + this.repetitions = Infinity; - } + /** + * If set to `true`, the playback of the action is paused. + * + * @type {boolean} + * @default false + */ + this.paused = false; - getY( index ) { + /** + * If set to `false`, the action is disabled so it has no impact. + * + * When the action is re-enabled, the animation continues from its current + * time (setting `enabled` to `false` doesn't reset the action). + * + * @type {boolean} + * @default true + */ + this.enabled = true; - let y = this.data.array[ index * this.data.stride + this.offset + 1 ]; + /** + * If set to true the animation will automatically be paused on its last frame. + * + * If set to false, {@link AnimationAction#enabled} will automatically be switched + * to `false` when the last loop of the action has finished, so that this action has + * no further impact. + * + * Note: This member has no impact if the action is interrupted (it + * has only an effect if its last loop has really finished). + * + * @type {boolean} + * @default false + */ + this.clampWhenFinished = false; - if ( this.normalized ) y = denormalize( y, this.array ); + /** + * Enables smooth interpolation without separate clips for start, loop and end. + * + * @type {boolean} + * @default true + */ + this.zeroSlopeAtStart = true; - return y; + /** + * Enables smooth interpolation without separate clips for start, loop and end. + * + * @type {boolean} + * @default true + */ + this.zeroSlopeAtEnd = true; } - getZ( index ) { - - let z = this.data.array[ index * this.data.stride + this.offset + 2 ]; + /** + * Starts the playback of the animation. + * + * @return {AnimationAction} A reference to this animation action. + */ + play() { - if ( this.normalized ) z = denormalize( z, this.array ); + this._mixer._activateAction( this ); - return z; + return this; } - getW( index ) { - - let w = this.data.array[ index * this.data.stride + this.offset + 3 ]; + /** + * Stops the playback of the animation. + * + * @return {AnimationAction} A reference to this animation action. + */ + stop() { - if ( this.normalized ) w = denormalize( w, this.array ); + this._mixer._deactivateAction( this ); - return w; + return this.reset(); } - setXY( index, x, y ) { + /** + * Resets the playback of the animation. + * + * @return {AnimationAction} A reference to this animation action. + */ + reset() { - index = index * this.data.stride + this.offset; + this.paused = false; + this.enabled = true; - if ( this.normalized ) { + this.time = 0; // restart clip + this._loopCount = -1;// forget previous loops + this._startTime = null;// forget scheduling - x = normalize( x, this.array ); - y = normalize( y, this.array ); + return this.stopFading().stopWarping(); - } + } - this.data.array[ index + 0 ] = x; - this.data.array[ index + 1 ] = y; + /** + * Returns `true` if the animation is running. + * + * @return {boolean} Whether the animation is running or not. + */ + isRunning() { - return this; + return this.enabled && ! this.paused && this.timeScale !== 0 && + this._startTime === null && this._mixer._isActiveAction( this ); } - setXYZ( index, x, y, z ) { - - index = index * this.data.stride + this.offset; + /** + * Returns `true` when {@link AnimationAction#play} has been called. + * + * @return {boolean} Whether the animation is scheduled or not. + */ + isScheduled() { - if ( this.normalized ) { + return this._mixer._isActiveAction( this ); - x = normalize( x, this.array ); - y = normalize( y, this.array ); - z = normalize( z, this.array ); + } - } + /** + * Defines the time when the animation should start. + * + * @param {number} time - The start time in seconds. + * @return {AnimationAction} A reference to this animation action. + */ + startAt( time ) { - this.data.array[ index + 0 ] = x; - this.data.array[ index + 1 ] = y; - this.data.array[ index + 2 ] = z; + this._startTime = time; return this; } - setXYZW( index, x, y, z, w ) { - - index = index * this.data.stride + this.offset; - - if ( this.normalized ) { - - x = normalize( x, this.array ); - y = normalize( y, this.array ); - z = normalize( z, this.array ); - w = normalize( w, this.array ); - - } + /** + * Configures the loop settings for this action. + * + * @param {(LoopRepeat|LoopOnce|LoopPingPong)} mode - The loop mode. + * @param {number} repetitions - The number of repetitions. + * @return {AnimationAction} A reference to this animation action. + */ + setLoop( mode, repetitions ) { - this.data.array[ index + 0 ] = x; - this.data.array[ index + 1 ] = y; - this.data.array[ index + 2 ] = z; - this.data.array[ index + 3 ] = w; + this.loop = mode; + this.repetitions = repetitions; return this; } - clone( data ) { + /** + * Sets the effective weight of this action. + * + * An action has no effect and thus an effective weight of zero when the + * action is disabled. + * + * @param {number} weight - The weight to set. + * @return {AnimationAction} A reference to this animation action. + */ + setEffectiveWeight( weight ) { - if ( data === undefined ) { + this.weight = weight; - console.log( 'THREE.InterleavedBufferAttribute.clone(): Cloning an interleaved buffer attribute will de-interleave buffer data.' ); + // note: same logic as when updated at runtime + this._effectiveWeight = this.enabled ? weight : 0; - const array = []; + return this.stopFading(); - for ( let i = 0; i < this.count; i ++ ) { + } - const index = i * this.data.stride + this.offset; + /** + * Returns the effective weight of this action. + * + * @return {number} The effective weight. + */ + getEffectiveWeight() { - for ( let j = 0; j < this.itemSize; j ++ ) { + return this._effectiveWeight; - array.push( this.data.array[ index + j ] ); + } - } + /** + * Fades the animation in by increasing its weight gradually from `0` to `1`, + * within the passed time interval. + * + * @param {number} duration - The duration of the fade. + * @return {AnimationAction} A reference to this animation action. + */ + fadeIn( duration ) { - } + return this._scheduleFading( duration, 0, 1 ); - return new BufferAttribute( new this.array.constructor( array ), this.itemSize, this.normalized ); + } - } else { + /** + * Fades the animation out by decreasing its weight gradually from `1` to `0`, + * within the passed time interval. + * + * @param {number} duration - The duration of the fade. + * @return {AnimationAction} A reference to this animation action. + */ + fadeOut( duration ) { - if ( data.interleavedBuffers === undefined ) { + return this._scheduleFading( duration, 1, 0 ); - data.interleavedBuffers = {}; + } - } + /** + * Causes this action to fade in and the given action to fade out, + * within the passed time interval. + * + * @param {AnimationAction} fadeOutAction - The animation action to fade out. + * @param {number} duration - The duration of the fade. + * @param {boolean} [warp=false] - Whether warping should be used or not. + * @return {AnimationAction} A reference to this animation action. + */ + crossFadeFrom( fadeOutAction, duration, warp = false ) { - if ( data.interleavedBuffers[ this.data.uuid ] === undefined ) { + fadeOutAction.fadeOut( duration ); + this.fadeIn( duration ); - data.interleavedBuffers[ this.data.uuid ] = this.data.clone( data ); + if ( warp === true ) { - } + const fadeInDuration = this._clip.duration, + fadeOutDuration = fadeOutAction._clip.duration, - return new InterleavedBufferAttribute( data.interleavedBuffers[ this.data.uuid ], this.itemSize, this.offset, this.normalized ); + startEndRatio = fadeOutDuration / fadeInDuration, + endStartRatio = fadeInDuration / fadeOutDuration; - } + fadeOutAction.warp( 1.0, startEndRatio, duration ); + this.warp( endStartRatio, 1.0, duration ); - } + } - toJSON( data ) { + return this; - if ( data === undefined ) { + } - console.log( 'THREE.InterleavedBufferAttribute.toJSON(): Serializing an interleaved buffer attribute will de-interleave buffer data.' ); + /** + * Causes this action to fade out and the given action to fade in, + * within the passed time interval. + * + * @param {AnimationAction} fadeInAction - The animation action to fade in. + * @param {number} duration - The duration of the fade. + * @param {boolean} [warp=false] - Whether warping should be used or not. + * @return {AnimationAction} A reference to this animation action. + */ + crossFadeTo( fadeInAction, duration, warp = false ) { - const array = []; + return fadeInAction.crossFadeFrom( this, duration, warp ); - for ( let i = 0; i < this.count; i ++ ) { + } - const index = i * this.data.stride + this.offset; + /** + * Stops any fading which is applied to this action. + * + * @return {AnimationAction} A reference to this animation action. + */ + stopFading() { - for ( let j = 0; j < this.itemSize; j ++ ) { + const weightInterpolant = this._weightInterpolant; - array.push( this.data.array[ index + j ] ); + if ( weightInterpolant !== null ) { - } + this._weightInterpolant = null; + this._mixer._takeBackControlInterpolant( weightInterpolant ); - } + } - // de-interleave data and save it as an ordinary buffer attribute for now + return this; - return { - itemSize: this.itemSize, - type: this.array.constructor.name, - array: array, - normalized: this.normalized - }; + } - } else { + /** + * Sets the effective time scale of this action. + * + * An action has no effect and thus an effective time scale of zero when the + * action is paused. + * + * @param {number} timeScale - The time scale to set. + * @return {AnimationAction} A reference to this animation action. + */ + setEffectiveTimeScale( timeScale ) { - // save as true interleaved attribute + this.timeScale = timeScale; + this._effectiveTimeScale = this.paused ? 0 : timeScale; - if ( data.interleavedBuffers === undefined ) { + return this.stopWarping(); - data.interleavedBuffers = {}; + } - } + /** + * Returns the effective time scale of this action. + * + * @return {number} The effective time scale. + */ + getEffectiveTimeScale() { - if ( data.interleavedBuffers[ this.data.uuid ] === undefined ) { + return this._effectiveTimeScale; - data.interleavedBuffers[ this.data.uuid ] = this.data.toJSON( data ); + } - } + /** + * Sets the duration for a single loop of this action. + * + * @param {number} duration - The duration to set. + * @return {AnimationAction} A reference to this animation action. + */ + setDuration( duration ) { - return { - isInterleavedBufferAttribute: true, - itemSize: this.itemSize, - data: this.data.uuid, - offset: this.offset, - normalized: this.normalized - }; + this.timeScale = this._clip.duration / duration; - } + return this.stopWarping(); } -} + /** + * Synchronizes this action with the passed other action. + * + * @param {AnimationAction} action - The action to sync with. + * @return {AnimationAction} A reference to this animation action. + */ + syncWith( action ) { -class SpriteMaterial extends Material { + this.time = action.time; + this.timeScale = action.timeScale; - constructor( parameters ) { + return this.stopWarping(); - super(); + } - this.isSpriteMaterial = true; + /** + * Decelerates this animation's speed to `0` within the passed time interval. + * + * @param {number} duration - The duration. + * @return {AnimationAction} A reference to this animation action. + */ + halt( duration ) { - this.type = 'SpriteMaterial'; + return this.warp( this._effectiveTimeScale, 0, duration ); - this.color = new Color( 0xffffff ); + } - this.map = null; + /** + * Changes the playback speed, within the passed time interval, by modifying + * {@link AnimationAction#timeScale} gradually from `startTimeScale` to + * `endTimeScale`. + * + * @param {number} startTimeScale - The start time scale. + * @param {number} endTimeScale - The end time scale. + * @param {number} duration - The duration. + * @return {AnimationAction} A reference to this animation action. + */ + warp( startTimeScale, endTimeScale, duration ) { - this.alphaMap = null; + const mixer = this._mixer, + now = mixer.time, + timeScale = this.timeScale; - this.rotation = 0; + let interpolant = this._timeScaleInterpolant; - this.sizeAttenuation = true; + if ( interpolant === null ) { - this.transparent = true; + interpolant = mixer._lendControlInterpolant(); + this._timeScaleInterpolant = interpolant; - this.fog = true; + } - this.setValues( parameters ); + const times = interpolant.parameterPositions, + values = interpolant.sampleValues; - } + times[ 0 ] = now; + times[ 1 ] = now + duration; - copy( source ) { + values[ 0 ] = startTimeScale / timeScale; + values[ 1 ] = endTimeScale / timeScale; - super.copy( source ); + return this; - this.color.copy( source.color ); + } - this.map = source.map; + /** + * Stops any scheduled warping which is applied to this action. + * + * @return {AnimationAction} A reference to this animation action. + */ + stopWarping() { - this.alphaMap = source.alphaMap; + const timeScaleInterpolant = this._timeScaleInterpolant; - this.rotation = source.rotation; + if ( timeScaleInterpolant !== null ) { - this.sizeAttenuation = source.sizeAttenuation; + this._timeScaleInterpolant = null; + this._mixer._takeBackControlInterpolant( timeScaleInterpolant ); - this.fog = source.fog; + } return this; } -} - -let _geometry; + /** + * Returns the animation mixer of this animation action. + * + * @return {AnimationMixer} The animation mixer. + */ + getMixer() { -const _intersectPoint = /*@__PURE__*/ new Vector3(); -const _worldScale = /*@__PURE__*/ new Vector3(); -const _mvPosition = /*@__PURE__*/ new Vector3(); + return this._mixer; -const _alignedPosition = /*@__PURE__*/ new Vector2(); -const _rotatedPosition = /*@__PURE__*/ new Vector2(); -const _viewWorldMatrix = /*@__PURE__*/ new Matrix4(); + } -const _vA = /*@__PURE__*/ new Vector3(); -const _vB = /*@__PURE__*/ new Vector3(); -const _vC = /*@__PURE__*/ new Vector3(); + /** + * Returns the animation clip of this animation action. + * + * @return {AnimationClip} The animation clip. + */ + getClip() { -const _uvA = /*@__PURE__*/ new Vector2(); -const _uvB = /*@__PURE__*/ new Vector2(); -const _uvC = /*@__PURE__*/ new Vector2(); + return this._clip; -class Sprite extends Object3D { + } - constructor( material ) { + /** + * Returns the root object of this animation action. + * + * @return {Object3D} The root object. + */ + getRoot() { - super(); + return this._localRoot || this._mixer._root; - this.isSprite = true; + } - this.type = 'Sprite'; + // Interna - if ( _geometry === undefined ) { + _update( time, deltaTime, timeDirection, accuIndex ) { - _geometry = new BufferGeometry(); + // called by the mixer - const float32Array = new Float32Array( [ - - 0.5, - 0.5, 0, 0, 0, - 0.5, - 0.5, 0, 1, 0, - 0.5, 0.5, 0, 1, 1, - - 0.5, 0.5, 0, 0, 1 - ] ); + if ( ! this.enabled ) { - const interleavedBuffer = new InterleavedBuffer( float32Array, 5 ); + // call ._updateWeight() to update ._effectiveWeight - _geometry.setIndex( [ 0, 1, 2, 0, 2, 3 ] ); - _geometry.setAttribute( 'position', new InterleavedBufferAttribute( interleavedBuffer, 3, 0, false ) ); - _geometry.setAttribute( 'uv', new InterleavedBufferAttribute( interleavedBuffer, 2, 3, false ) ); + this._updateWeight( time ); + return; } - this.geometry = _geometry; - this.material = ( material !== undefined ) ? material : new SpriteMaterial(); + const startTime = this._startTime; - this.center = new Vector2( 0.5, 0.5 ); + if ( startTime !== null ) { - } + // check for scheduled start of action - raycast( raycaster, intersects ) { + const timeRunning = ( time - startTime ) * timeDirection; + if ( timeRunning < 0 || timeDirection === 0 ) { - if ( raycaster.camera === null ) { + deltaTime = 0; - console.error( 'THREE.Sprite: "Raycaster.camera" needs to be set in order to raycast against sprites.' ); + } else { - } - _worldScale.setFromMatrixScale( this.matrixWorld ); + this._startTime = null; // unschedule + deltaTime = timeDirection * timeRunning; - _viewWorldMatrix.copy( raycaster.camera.matrixWorld ); - this.modelViewMatrix.multiplyMatrices( raycaster.camera.matrixWorldInverse, this.matrixWorld ); + } - _mvPosition.setFromMatrixPosition( this.modelViewMatrix ); + } - if ( raycaster.camera.isPerspectiveCamera && this.material.sizeAttenuation === false ) { + // apply time scale and advance time - _worldScale.multiplyScalar( - _mvPosition.z ); + deltaTime *= this._updateTimeScale( time ); + const clipTime = this._updateTime( deltaTime ); - } + // note: _updateTime may disable the action resulting in + // an effective weight of 0 - const rotation = this.material.rotation; - let sin, cos; + const weight = this._updateWeight( time ); - if ( rotation !== 0 ) { + if ( weight > 0 ) { - cos = Math.cos( rotation ); - sin = Math.sin( rotation ); + const interpolants = this._interpolants; + const propertyMixers = this._propertyBindings; - } + switch ( this.blendMode ) { - const center = this.center; + case AdditiveAnimationBlendMode: - transformVertex( _vA.set( - 0.5, - 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos ); - transformVertex( _vB.set( 0.5, - 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos ); - transformVertex( _vC.set( 0.5, 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos ); + for ( let j = 0, m = interpolants.length; j !== m; ++ j ) { - _uvA.set( 0, 0 ); - _uvB.set( 1, 0 ); - _uvC.set( 1, 1 ); + interpolants[ j ].evaluate( clipTime ); + propertyMixers[ j ].accumulateAdditive( weight ); - // check first triangle - let intersect = raycaster.ray.intersectTriangle( _vA, _vB, _vC, false, _intersectPoint ); + } - if ( intersect === null ) { + break; - // check second triangle - transformVertex( _vB.set( - 0.5, 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos ); - _uvB.set( 0, 1 ); + case NormalAnimationBlendMode: + default: - intersect = raycaster.ray.intersectTriangle( _vA, _vC, _vB, false, _intersectPoint ); - if ( intersect === null ) { + for ( let j = 0, m = interpolants.length; j !== m; ++ j ) { - return; + interpolants[ j ].evaluate( clipTime ); + propertyMixers[ j ].accumulate( accuIndex, weight ); + + } } } - const distance = raycaster.ray.origin.distanceTo( _intersectPoint ); + } - if ( distance < raycaster.near || distance > raycaster.far ) return; + _updateWeight( time ) { - intersects.push( { + let weight = 0; - distance: distance, - point: _intersectPoint.clone(), - uv: Triangle.getInterpolation( _intersectPoint, _vA, _vB, _vC, _uvA, _uvB, _uvC, new Vector2() ), - face: null, - object: this + if ( this.enabled ) { - } ); + weight = this.weight; + const interpolant = this._weightInterpolant; - } + if ( interpolant !== null ) { - copy( source, recursive ) { + const interpolantValue = interpolant.evaluate( time )[ 0 ]; - super.copy( source, recursive ); + weight *= interpolantValue; - if ( source.center !== undefined ) this.center.copy( source.center ); + if ( time > interpolant.parameterPositions[ 1 ] ) { - this.material = source.material; + this.stopFading(); - return this; + if ( interpolantValue === 0 ) { - } + // faded out, disable + this.enabled = false; -} + } -function transformVertex( vertexPosition, mvPosition, center, scale, sin, cos ) { + } - // compute position in camera space - _alignedPosition.subVectors( vertexPosition, center ).addScalar( 0.5 ).multiply( scale ); + } - // to check if rotation is not zero - if ( sin !== undefined ) { + } - _rotatedPosition.x = ( cos * _alignedPosition.x ) - ( sin * _alignedPosition.y ); - _rotatedPosition.y = ( sin * _alignedPosition.x ) + ( cos * _alignedPosition.y ); + this._effectiveWeight = weight; + return weight; - } else { + } - _rotatedPosition.copy( _alignedPosition ); + _updateTimeScale( time ) { - } + let timeScale = 0; + if ( ! this.paused ) { - vertexPosition.copy( mvPosition ); - vertexPosition.x += _rotatedPosition.x; - vertexPosition.y += _rotatedPosition.y; + timeScale = this.timeScale; - // transform to world space - vertexPosition.applyMatrix4( _viewWorldMatrix ); + const interpolant = this._timeScaleInterpolant; -} + if ( interpolant !== null ) { -const _v1$2 = /*@__PURE__*/ new Vector3(); -const _v2$1 = /*@__PURE__*/ new Vector3(); + const interpolantValue = interpolant.evaluate( time )[ 0 ]; -class LOD extends Object3D { + timeScale *= interpolantValue; - constructor() { + if ( time > interpolant.parameterPositions[ 1 ] ) { - super(); + this.stopWarping(); - this._currentLevel = 0; + if ( timeScale === 0 ) { - this.type = 'LOD'; + // motion has halted, pause + this.paused = true; - Object.defineProperties( this, { - levels: { - enumerable: true, - value: [] - }, - isLOD: { - value: true, - } - } ); + } else { - this.autoUpdate = true; + // warp done - apply final time scale + this.timeScale = timeScale; - } + } - copy( source ) { + } - super.copy( source, false ); + } - const levels = source.levels; + } - for ( let i = 0, l = levels.length; i < l; i ++ ) { + this._effectiveTimeScale = timeScale; + return timeScale; - const level = levels[ i ]; + } - this.addLevel( level.object.clone(), level.distance, level.hysteresis ); + _updateTime( deltaTime ) { - } + const duration = this._clip.duration; + const loop = this.loop; - this.autoUpdate = source.autoUpdate; + let time = this.time + deltaTime; + let loopCount = this._loopCount; - return this; + const pingPong = ( loop === LoopPingPong ); - } + if ( deltaTime === 0 ) { - addLevel( object, distance = 0, hysteresis = 0 ) { + if ( loopCount === -1 ) return time; - distance = Math.abs( distance ); + return ( pingPong && ( loopCount & 1 ) === 1 ) ? duration - time : time; - const levels = this.levels; + } - let l; + if ( loop === LoopOnce ) { - for ( l = 0; l < levels.length; l ++ ) { + if ( loopCount === -1 ) { - if ( distance < levels[ l ].distance ) { + // just started - break; + this._loopCount = 0; + this._setEndings( true, true, false ); } - } + handle_stop: { - levels.splice( l, 0, { distance: distance, hysteresis: hysteresis, object: object } ); + if ( time >= duration ) { - this.add( object ); + time = duration; - return this; + } else if ( time < 0 ) { - } + time = 0; - getCurrentLevel() { + } else { - return this._currentLevel; + this.time = time; - } + break handle_stop; + } + if ( this.clampWhenFinished ) this.paused = true; + else this.enabled = false; - getObjectForDistance( distance ) { + this.time = time; - const levels = this.levels; + this._mixer.dispatchEvent( { + type: 'finished', action: this, + direction: deltaTime < 0 ? -1 : 1 + } ); - if ( levels.length > 0 ) { + } - let i, l; + } else { // repetitive Repeat or PingPong - for ( i = 1, l = levels.length; i < l; i ++ ) { + if ( loopCount === -1 ) { - let levelDistance = levels[ i ].distance; + // just started - if ( levels[ i ].object.visible ) { + if ( deltaTime >= 0 ) { - levelDistance -= levelDistance * levels[ i ].hysteresis; + loopCount = 0; - } + this._setEndings( true, this.repetitions === 0, pingPong ); - if ( distance < levelDistance ) { + } else { - break; + // when looping in reverse direction, the initial + // transition through zero counts as a repetition, + // so leave loopCount at -1 + + this._setEndings( this.repetitions === 0, true, pingPong ); } } - return levels[ i - 1 ].object; - - } + if ( time >= duration || time < 0 ) { - return null; + // wrap around - } + const loopDelta = Math.floor( time / duration ); // signed + time -= duration * loopDelta; - raycast( raycaster, intersects ) { + loopCount += Math.abs( loopDelta ); - const levels = this.levels; + const pending = this.repetitions - loopCount; - if ( levels.length > 0 ) { + if ( pending <= 0 ) { - _v1$2.setFromMatrixPosition( this.matrixWorld ); + // have to stop (switch state, clamp time, fire event) - const distance = raycaster.ray.origin.distanceTo( _v1$2 ); + if ( this.clampWhenFinished ) this.paused = true; + else this.enabled = false; - this.getObjectForDistance( distance ).raycast( raycaster, intersects ); + time = deltaTime > 0 ? duration : 0; - } + this.time = time; - } + this._mixer.dispatchEvent( { + type: 'finished', action: this, + direction: deltaTime > 0 ? 1 : -1 + } ); - update( camera ) { + } else { - const levels = this.levels; + // keep running - if ( levels.length > 1 ) { + if ( pending === 1 ) { - _v1$2.setFromMatrixPosition( camera.matrixWorld ); - _v2$1.setFromMatrixPosition( this.matrixWorld ); + // entering the last round - const distance = _v1$2.distanceTo( _v2$1 ) / camera.zoom; + const atStart = deltaTime < 0; + this._setEndings( atStart, ! atStart, pingPong ); - levels[ 0 ].object.visible = true; + } else { - let i, l; + this._setEndings( false, false, pingPong ); - for ( i = 1, l = levels.length; i < l; i ++ ) { + } - let levelDistance = levels[ i ].distance; + this._loopCount = loopCount; - if ( levels[ i ].object.visible ) { + this.time = time; - levelDistance -= levelDistance * levels[ i ].hysteresis; + this._mixer.dispatchEvent( { + type: 'loop', action: this, loopDelta: loopDelta + } ); } - if ( distance >= levelDistance ) { - - levels[ i - 1 ].object.visible = false; - levels[ i ].object.visible = true; - - } else { - - break; + } else { - } + this.time = time; } - this._currentLevel = i - 1; + if ( pingPong && ( loopCount & 1 ) === 1 ) { - for ( ; i < l; i ++ ) { + // invert time for the "pong round" - levels[ i ].object.visible = false; + return duration - time; } } + return time; + } - toJSON( meta ) { + _setEndings( atStart, atEnd, pingPong ) { - const data = super.toJSON( meta ); + const settings = this._interpolantSettings; - if ( this.autoUpdate === false ) data.object.autoUpdate = false; + if ( pingPong ) { - data.object.levels = []; + settings.endingStart = ZeroSlopeEnding; + settings.endingEnd = ZeroSlopeEnding; - const levels = this.levels; + } else { - for ( let i = 0, l = levels.length; i < l; i ++ ) { + // assuming for LoopOnce atStart == atEnd == true - const level = levels[ i ]; + if ( atStart ) { - data.object.levels.push( { - object: level.object.uuid, - distance: level.distance, - hysteresis: level.hysteresis - } ); + settings.endingStart = this.zeroSlopeAtStart ? ZeroSlopeEnding : ZeroCurvatureEnding; - } + } else { - return data; + settings.endingStart = WrapAroundEnding; + + } + + if ( atEnd ) { + + settings.endingEnd = this.zeroSlopeAtEnd ? ZeroSlopeEnding : ZeroCurvatureEnding; + + } else { + + settings.endingEnd = WrapAroundEnding; + + } + + } } -} + _scheduleFading( duration, weightNow, weightThen ) { -const _basePosition = /*@__PURE__*/ new Vector3(); + const mixer = this._mixer, now = mixer.time; + let interpolant = this._weightInterpolant; -const _skinIndex = /*@__PURE__*/ new Vector4(); -const _skinWeight = /*@__PURE__*/ new Vector4(); + if ( interpolant === null ) { -const _vector3 = /*@__PURE__*/ new Vector3(); -const _matrix4 = /*@__PURE__*/ new Matrix4(); -const _vertex = /*@__PURE__*/ new Vector3(); + interpolant = mixer._lendControlInterpolant(); + this._weightInterpolant = interpolant; -const _sphere$3 = /*@__PURE__*/ new Sphere(); -const _inverseMatrix$2 = /*@__PURE__*/ new Matrix4(); -const _ray$2 = /*@__PURE__*/ new Ray(); + } -class SkinnedMesh extends Mesh { + const times = interpolant.parameterPositions, + values = interpolant.sampleValues; - constructor( geometry, material ) { + times[ 0 ] = now; + values[ 0 ] = weightNow; + times[ 1 ] = now + duration; + values[ 1 ] = weightThen; - super( geometry, material ); + return this; - this.isSkinnedMesh = true; + } - this.type = 'SkinnedMesh'; +} + +const _controlInterpolantsResultBuffer = new Float32Array( 1 ); + +/** + * `AnimationMixer` is a player for animations on a particular object in + * the scene. When multiple objects in the scene are animated independently, + * one `AnimationMixer` may be used for each object. + */ +class AnimationMixer extends EventDispatcher { + + /** + * Constructs a new animation mixer. + * + * @param {Object3D} root - The object whose animations shall be played by this mixer. + */ + constructor( root ) { + + super(); + + this._root = root; + this._initMemoryManager(); + this._accuIndex = 0; - this.bindMode = 'attached'; - this.bindMatrix = new Matrix4(); - this.bindMatrixInverse = new Matrix4(); + /** + * The global mixer time (in seconds; starting with `0` on the mixer's creation). + * + * @type {number} + * @default 0 + */ + this.time = 0; - this.boundingBox = null; - this.boundingSphere = null; + /** + * A scaling factor for the global time. + * + * Note: Setting this member to `0` and later back to `1` is a + * possibility to pause/unpause all actions that are controlled by this + * mixer. + * + * @type {number} + * @default 1 + */ + this.timeScale = 1.0; } - computeBoundingBox() { + _bindAction( action, prototypeAction ) { - const geometry = this.geometry; + const root = action._localRoot || this._root, + tracks = action._clip.tracks, + nTracks = tracks.length, + bindings = action._propertyBindings, + interpolants = action._interpolants, + rootUuid = root.uuid, + bindingsByRoot = this._bindingsByRootAndName; - if ( this.boundingBox === null ) { + let bindingsByName = bindingsByRoot[ rootUuid ]; - this.boundingBox = new Box3(); + if ( bindingsByName === undefined ) { + + bindingsByName = {}; + bindingsByRoot[ rootUuid ] = bindingsByName; } - this.boundingBox.makeEmpty(); + for ( let i = 0; i !== nTracks; ++ i ) { - const positionAttribute = geometry.getAttribute( 'position' ); + const track = tracks[ i ], + trackName = track.name; - for ( let i = 0; i < positionAttribute.count; i ++ ) { + let binding = bindingsByName[ trackName ]; - _vertex.fromBufferAttribute( positionAttribute, i ); - this.applyBoneTransform( i, _vertex ); - this.boundingBox.expandByPoint( _vertex ); + if ( binding !== undefined ) { - } + ++ binding.referenceCount; + bindings[ i ] = binding; - } + } else { - computeBoundingSphere() { + binding = bindings[ i ]; - const geometry = this.geometry; + if ( binding !== undefined ) { - if ( this.boundingSphere === null ) { + // existing binding, make sure the cache knows - this.boundingSphere = new Sphere(); + if ( binding._cacheIndex === null ) { - } + ++ binding.referenceCount; + this._addInactiveBinding( binding, rootUuid, trackName ); - this.boundingSphere.makeEmpty(); + } - const positionAttribute = geometry.getAttribute( 'position' ); + continue; - for ( let i = 0; i < positionAttribute.count; i ++ ) { + } - _vertex.fromBufferAttribute( positionAttribute, i ); - this.applyBoneTransform( i, _vertex ); - this.boundingSphere.expandByPoint( _vertex ); + const path = prototypeAction && prototypeAction. + _propertyBindings[ i ].binding.parsedPath; - } + binding = new PropertyMixer( + PropertyBinding.create( root, trackName, path ), + track.ValueTypeName, track.getValueSize() ); - } + ++ binding.referenceCount; + this._addInactiveBinding( binding, rootUuid, trackName ); - copy( source, recursive ) { + bindings[ i ] = binding; - super.copy( source, recursive ); + } - this.bindMode = source.bindMode; - this.bindMatrix.copy( source.bindMatrix ); - this.bindMatrixInverse.copy( source.bindMatrixInverse ); + interpolants[ i ].resultBuffer = binding.buffer; - this.skeleton = source.skeleton; + } - if ( source.boundingBox !== null ) this.boundingBox = source.boundingBox.clone(); - if ( source.boundingSphere !== null ) this.boundingSphere = source.boundingSphere.clone(); + } - return this; + _activateAction( action ) { - } + if ( ! this._isActiveAction( action ) ) { - raycast( raycaster, intersects ) { + if ( action._cacheIndex === null ) { - const material = this.material; - const matrixWorld = this.matrixWorld; + // this action has been forgotten by the cache, but the user + // appears to be still using it -> rebind - if ( material === undefined ) return; + const rootUuid = ( action._localRoot || this._root ).uuid, + clipUuid = action._clip.uuid, + actionsForClip = this._actionsByClip[ clipUuid ]; - // test with bounding sphere in world space + this._bindAction( action, + actionsForClip && actionsForClip.knownActions[ 0 ] ); - if ( this.boundingSphere === null ) this.computeBoundingSphere(); + this._addInactiveAction( action, clipUuid, rootUuid ); - _sphere$3.copy( this.boundingSphere ); - _sphere$3.applyMatrix4( matrixWorld ); + } - if ( raycaster.ray.intersectsSphere( _sphere$3 ) === false ) return; + const bindings = action._propertyBindings; - // convert ray to local space of skinned mesh + // increment reference counts / sort out state + for ( let i = 0, n = bindings.length; i !== n; ++ i ) { - _inverseMatrix$2.copy( matrixWorld ).invert(); - _ray$2.copy( raycaster.ray ).applyMatrix4( _inverseMatrix$2 ); + const binding = bindings[ i ]; - // test with bounding box in local space + if ( binding.useCount ++ === 0 ) { - if ( this.boundingBox !== null ) { + this._lendBinding( binding ); + binding.saveOriginalState(); - if ( _ray$2.intersectsBox( this.boundingBox ) === false ) return; + } - } + } - // test for intersections with geometry + this._lendAction( action ); - this._computeIntersections( raycaster, intersects, _ray$2 ); + } } - getVertexPosition( index, target ) { - - super.getVertexPosition( index, target ); + _deactivateAction( action ) { - this.applyBoneTransform( index, target ); + if ( this._isActiveAction( action ) ) { - return target; + const bindings = action._propertyBindings; - } + // decrement reference counts / sort out state + for ( let i = 0, n = bindings.length; i !== n; ++ i ) { - bind( skeleton, bindMatrix ) { + const binding = bindings[ i ]; - this.skeleton = skeleton; + if ( -- binding.useCount === 0 ) { - if ( bindMatrix === undefined ) { + binding.restoreOriginalState(); + this._takeBackBinding( binding ); - this.updateMatrixWorld( true ); + } - this.skeleton.calculateInverses(); + } - bindMatrix = this.matrixWorld; + this._takeBackAction( action ); } - this.bindMatrix.copy( bindMatrix ); - this.bindMatrixInverse.copy( bindMatrix ).invert(); - } - pose() { + // Memory manager - this.skeleton.pose(); + _initMemoryManager() { - } + this._actions = []; // 'nActiveActions' followed by inactive ones + this._nActiveActions = 0; - normalizeSkinWeights() { + this._actionsByClip = {}; + // inside: + // { + // knownActions: Array< AnimationAction > - used as prototypes + // actionByRoot: AnimationAction - lookup + // } - const vector = new Vector4(); - const skinWeight = this.geometry.attributes.skinWeight; + this._bindings = []; // 'nActiveBindings' followed by inactive ones + this._nActiveBindings = 0; - for ( let i = 0, l = skinWeight.count; i < l; i ++ ) { + this._bindingsByRootAndName = {}; // inside: Map< name, PropertyMixer > - vector.fromBufferAttribute( skinWeight, i ); - const scale = 1.0 / vector.manhattanLength(); + this._controlInterpolants = []; // same game as above + this._nActiveControlInterpolants = 0; - if ( scale !== Infinity ) { + const scope = this; - vector.multiplyScalar( scale ); + this.stats = { - } else { + actions: { + get total() { - vector.set( 1, 0, 0, 0 ); // do something reasonable + return scope._actions.length; - } + }, + get inUse() { - skinWeight.setXYZW( i, vector.x, vector.y, vector.z, vector.w ); + return scope._nActiveActions; - } + } + }, + bindings: { + get total() { - } + return scope._bindings.length; - updateMatrixWorld( force ) { + }, + get inUse() { - super.updateMatrixWorld( force ); + return scope._nActiveBindings; + + } + }, + controlInterpolants: { + get total() { - if ( this.bindMode === 'attached' ) { + return scope._controlInterpolants.length; - this.bindMatrixInverse.copy( this.matrixWorld ).invert(); + }, + get inUse() { - } else if ( this.bindMode === 'detached' ) { + return scope._nActiveControlInterpolants; - this.bindMatrixInverse.copy( this.bindMatrix ).invert(); + } + } - } else { + }; - console.warn( 'THREE.SkinnedMesh: Unrecognized bindMode: ' + this.bindMode ); + } - } + // Memory management for AnimationAction objects + + _isActiveAction( action ) { + + const index = action._cacheIndex; + return index !== null && index < this._nActiveActions; } - applyBoneTransform( index, vector ) { + _addInactiveAction( action, clipUuid, rootUuid ) { - const skeleton = this.skeleton; - const geometry = this.geometry; + const actions = this._actions, + actionsByClip = this._actionsByClip; - _skinIndex.fromBufferAttribute( geometry.attributes.skinIndex, index ); - _skinWeight.fromBufferAttribute( geometry.attributes.skinWeight, index ); + let actionsForClip = actionsByClip[ clipUuid ]; - _basePosition.copy( vector ).applyMatrix4( this.bindMatrix ); + if ( actionsForClip === undefined ) { - vector.set( 0, 0, 0 ); + actionsForClip = { - for ( let i = 0; i < 4; i ++ ) { + knownActions: [ action ], + actionByRoot: {} - const weight = _skinWeight.getComponent( i ); + }; - if ( weight !== 0 ) { + action._byClipCacheIndex = 0; - const boneIndex = _skinIndex.getComponent( i ); + actionsByClip[ clipUuid ] = actionsForClip; - _matrix4.multiplyMatrices( skeleton.bones[ boneIndex ].matrixWorld, skeleton.boneInverses[ boneIndex ] ); + } else { - vector.addScaledVector( _vector3.copy( _basePosition ).applyMatrix4( _matrix4 ), weight ); + const knownActions = actionsForClip.knownActions; - } + action._byClipCacheIndex = knownActions.length; + knownActions.push( action ); } - return vector.applyMatrix4( this.bindMatrixInverse ); + action._cacheIndex = actions.length; + actions.push( action ); + + actionsForClip.actionByRoot[ rootUuid ] = action; } - boneTransform( index, vector ) { // @deprecated, r151 - - console.warn( 'THREE.SkinnedMesh: .boneTransform() was renamed to .applyBoneTransform() in r151.' ); - return this.applyBoneTransform( index, vector ); + _removeInactiveAction( action ) { - } + const actions = this._actions, + lastInactiveAction = actions[ actions.length - 1 ], + cacheIndex = action._cacheIndex; + lastInactiveAction._cacheIndex = cacheIndex; + actions[ cacheIndex ] = lastInactiveAction; + actions.pop(); -} + action._cacheIndex = null; -class Bone extends Object3D { - constructor() { + const clipUuid = action._clip.uuid, + actionsByClip = this._actionsByClip, + actionsForClip = actionsByClip[ clipUuid ], + knownActionsForClip = actionsForClip.knownActions, - super(); + lastKnownAction = + knownActionsForClip[ knownActionsForClip.length - 1 ], - this.isBone = true; + byClipCacheIndex = action._byClipCacheIndex; - this.type = 'Bone'; + lastKnownAction._byClipCacheIndex = byClipCacheIndex; + knownActionsForClip[ byClipCacheIndex ] = lastKnownAction; + knownActionsForClip.pop(); - } + action._byClipCacheIndex = null; -} -class DataTexture extends Texture { + const actionByRoot = actionsForClip.actionByRoot, + rootUuid = ( action._localRoot || this._root ).uuid; - constructor( data = null, width = 1, height = 1, format, type, mapping, wrapS, wrapT, magFilter = NearestFilter, minFilter = NearestFilter, anisotropy, colorSpace ) { + delete actionByRoot[ rootUuid ]; - super( null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, colorSpace ); + if ( knownActionsForClip.length === 0 ) { - this.isDataTexture = true; + delete actionsByClip[ clipUuid ]; - this.image = { data: data, width: width, height: height }; + } - this.generateMipmaps = false; - this.flipY = false; - this.unpackAlignment = 1; + this._removeInactiveBindingsForAction( action ); } -} - -const _offsetMatrix = /*@__PURE__*/ new Matrix4(); -const _identityMatrix = /*@__PURE__*/ new Matrix4(); + _removeInactiveBindingsForAction( action ) { -class Skeleton { + const bindings = action._propertyBindings; - constructor( bones = [], boneInverses = [] ) { + for ( let i = 0, n = bindings.length; i !== n; ++ i ) { - this.uuid = generateUUID(); + const binding = bindings[ i ]; - this.bones = bones.slice( 0 ); - this.boneInverses = boneInverses; - this.boneMatrices = null; + if ( -- binding.referenceCount === 0 ) { - this.boneTexture = null; - this.boneTextureSize = 0; + this._removeInactiveBinding( binding ); - this.frame = - 1; + } - this.init(); + } } - init() { - - const bones = this.bones; - const boneInverses = this.boneInverses; + _lendAction( action ) { - this.boneMatrices = new Float32Array( bones.length * 16 ); + // [ active actions | inactive actions ] + // [ active actions >| inactive actions ] + // s a + // <-swap-> + // a s - // calculate inverse bone matrices if necessary + const actions = this._actions, + prevIndex = action._cacheIndex, - if ( boneInverses.length === 0 ) { + lastActiveIndex = this._nActiveActions ++, - this.calculateInverses(); + firstInactiveAction = actions[ lastActiveIndex ]; - } else { + action._cacheIndex = lastActiveIndex; + actions[ lastActiveIndex ] = action; - // handle special case + firstInactiveAction._cacheIndex = prevIndex; + actions[ prevIndex ] = firstInactiveAction; - if ( bones.length !== boneInverses.length ) { + } - console.warn( 'THREE.Skeleton: Number of inverse bone matrices does not match amount of bones.' ); + _takeBackAction( action ) { - this.boneInverses = []; + // [ active actions | inactive actions ] + // [ active actions |< inactive actions ] + // a s + // <-swap-> + // s a - for ( let i = 0, il = this.bones.length; i < il; i ++ ) { + const actions = this._actions, + prevIndex = action._cacheIndex, - this.boneInverses.push( new Matrix4() ); + firstInactiveIndex = -- this._nActiveActions, - } + lastActiveAction = actions[ firstInactiveIndex ]; - } + action._cacheIndex = firstInactiveIndex; + actions[ firstInactiveIndex ] = action; - } + lastActiveAction._cacheIndex = prevIndex; + actions[ prevIndex ] = lastActiveAction; } - calculateInverses() { + // Memory management for PropertyMixer objects - this.boneInverses.length = 0; + _addInactiveBinding( binding, rootUuid, trackName ) { - for ( let i = 0, il = this.bones.length; i < il; i ++ ) { + const bindingsByRoot = this._bindingsByRootAndName, + bindings = this._bindings; - const inverse = new Matrix4(); + let bindingByName = bindingsByRoot[ rootUuid ]; - if ( this.bones[ i ] ) { + if ( bindingByName === undefined ) { - inverse.copy( this.bones[ i ].matrixWorld ).invert(); + bindingByName = {}; + bindingsByRoot[ rootUuid ] = bindingByName; - } + } - this.boneInverses.push( inverse ); + bindingByName[ trackName ] = binding; - } + binding._cacheIndex = bindings.length; + bindings.push( binding ); } - pose() { + _removeInactiveBinding( binding ) { - // recover the bind-time world matrices + const bindings = this._bindings, + propBinding = binding.binding, + rootUuid = propBinding.rootNode.uuid, + trackName = propBinding.path, + bindingsByRoot = this._bindingsByRootAndName, + bindingByName = bindingsByRoot[ rootUuid ], - for ( let i = 0, il = this.bones.length; i < il; i ++ ) { + lastInactiveBinding = bindings[ bindings.length - 1 ], + cacheIndex = binding._cacheIndex; - const bone = this.bones[ i ]; + lastInactiveBinding._cacheIndex = cacheIndex; + bindings[ cacheIndex ] = lastInactiveBinding; + bindings.pop(); - if ( bone ) { + delete bindingByName[ trackName ]; - bone.matrixWorld.copy( this.boneInverses[ i ] ).invert(); + if ( Object.keys( bindingByName ).length === 0 ) { - } + delete bindingsByRoot[ rootUuid ]; } - // compute the local matrices, positions, rotations and scales + } - for ( let i = 0, il = this.bones.length; i < il; i ++ ) { + _lendBinding( binding ) { - const bone = this.bones[ i ]; + const bindings = this._bindings, + prevIndex = binding._cacheIndex, - if ( bone ) { + lastActiveIndex = this._nActiveBindings ++, - if ( bone.parent && bone.parent.isBone ) { + firstInactiveBinding = bindings[ lastActiveIndex ]; - bone.matrix.copy( bone.parent.matrixWorld ).invert(); - bone.matrix.multiply( bone.matrixWorld ); + binding._cacheIndex = lastActiveIndex; + bindings[ lastActiveIndex ] = binding; - } else { + firstInactiveBinding._cacheIndex = prevIndex; + bindings[ prevIndex ] = firstInactiveBinding; - bone.matrix.copy( bone.matrixWorld ); + } - } + _takeBackBinding( binding ) { - bone.matrix.decompose( bone.position, bone.quaternion, bone.scale ); + const bindings = this._bindings, + prevIndex = binding._cacheIndex, - } + firstInactiveIndex = -- this._nActiveBindings, - } + lastActiveBinding = bindings[ firstInactiveIndex ]; - } + binding._cacheIndex = firstInactiveIndex; + bindings[ firstInactiveIndex ] = binding; - update() { + lastActiveBinding._cacheIndex = prevIndex; + bindings[ prevIndex ] = lastActiveBinding; - const bones = this.bones; - const boneInverses = this.boneInverses; - const boneMatrices = this.boneMatrices; - const boneTexture = this.boneTexture; + } - // flatten bone matrices to array - for ( let i = 0, il = bones.length; i < il; i ++ ) { + // Memory management of Interpolants for weight and time scale - // compute the offset between the current and the original transform + _lendControlInterpolant() { - const matrix = bones[ i ] ? bones[ i ].matrixWorld : _identityMatrix; + const interpolants = this._controlInterpolants, + lastActiveIndex = this._nActiveControlInterpolants ++; - _offsetMatrix.multiplyMatrices( matrix, boneInverses[ i ] ); - _offsetMatrix.toArray( boneMatrices, i * 16 ); + let interpolant = interpolants[ lastActiveIndex ]; - } + if ( interpolant === undefined ) { - if ( boneTexture !== null ) { + interpolant = new LinearInterpolant( + new Float32Array( 2 ), new Float32Array( 2 ), + 1, _controlInterpolantsResultBuffer ); - boneTexture.needsUpdate = true; + interpolant.__cacheIndex = lastActiveIndex; + interpolants[ lastActiveIndex ] = interpolant; } + return interpolant; + } - clone() { + _takeBackControlInterpolant( interpolant ) { - return new Skeleton( this.bones, this.boneInverses ); + const interpolants = this._controlInterpolants, + prevIndex = interpolant.__cacheIndex, - } + firstInactiveIndex = -- this._nActiveControlInterpolants, - computeBoneTexture() { + lastActiveInterpolant = interpolants[ firstInactiveIndex ]; - // layout (1 matrix = 4 pixels) - // RGBA RGBA RGBA RGBA (=> column1, column2, column3, column4) - // with 8x8 pixel texture max 16 bones * 4 pixels = (8 * 8) - // 16x16 pixel texture max 64 bones * 4 pixels = (16 * 16) - // 32x32 pixel texture max 256 bones * 4 pixels = (32 * 32) - // 64x64 pixel texture max 1024 bones * 4 pixels = (64 * 64) + interpolant.__cacheIndex = firstInactiveIndex; + interpolants[ firstInactiveIndex ] = interpolant; - let size = Math.sqrt( this.bones.length * 4 ); // 4 pixels needed for 1 matrix - size = ceilPowerOfTwo( size ); - size = Math.max( size, 4 ); + lastActiveInterpolant.__cacheIndex = prevIndex; + interpolants[ prevIndex ] = lastActiveInterpolant; - const boneMatrices = new Float32Array( size * size * 4 ); // 4 floats per RGBA pixel - boneMatrices.set( this.boneMatrices ); // copy current values + } - const boneTexture = new DataTexture( boneMatrices, size, size, RGBAFormat, FloatType ); - boneTexture.needsUpdate = true; + /** + * Returns an instance of {@link AnimationAction} for the passed clip. + * + * If an action fitting the clip and root parameters doesn't yet exist, it + * will be created by this method. Calling this method several times with the + * same clip and root parameters always returns the same action. + * + * @param {AnimationClip|string} clip - An animation clip or alternatively the name of the animation clip. + * @param {Object3D} [optionalRoot] - An alternative root object. + * @param {(NormalAnimationBlendMode|AdditiveAnimationBlendMode)} [blendMode] - The blend mode. + * @return {?AnimationAction} The animation action. + */ + clipAction( clip, optionalRoot, blendMode ) { - this.boneMatrices = boneMatrices; - this.boneTexture = boneTexture; - this.boneTextureSize = size; + const root = optionalRoot || this._root, + rootUuid = root.uuid; - return this; + let clipObject = typeof clip === 'string' ? AnimationClip.findByName( root, clip ) : clip; - } + const clipUuid = clipObject !== null ? clipObject.uuid : clip; + + const actionsForClip = this._actionsByClip[ clipUuid ]; + let prototypeAction = null; - getBoneByName( name ) { + if ( blendMode === undefined ) { - for ( let i = 0, il = this.bones.length; i < il; i ++ ) { + if ( clipObject !== null ) { - const bone = this.bones[ i ]; + blendMode = clipObject.blendMode; - if ( bone.name === name ) { + } else { - return bone; + blendMode = NormalAnimationBlendMode; } } - return undefined; + if ( actionsForClip !== undefined ) { - } + const existingAction = actionsForClip.actionByRoot[ rootUuid ]; - dispose( ) { + if ( existingAction !== undefined && existingAction.blendMode === blendMode ) { - if ( this.boneTexture !== null ) { + return existingAction; - this.boneTexture.dispose(); + } - this.boneTexture = null; + // we know the clip, so we don't have to parse all + // the bindings again but can just copy + prototypeAction = actionsForClip.knownActions[ 0 ]; + + // also, take the clip from the prototype action + if ( clipObject === null ) + clipObject = prototypeAction._clip; } - } + // clip must be known when specified via string + if ( clipObject === null ) return null; - fromJSON( json, bones ) { + // allocate all resources required to run it + const newAction = new AnimationAction( this, clipObject, optionalRoot, blendMode ); - this.uuid = json.uuid; + this._bindAction( newAction, prototypeAction ); - for ( let i = 0, l = json.bones.length; i < l; i ++ ) { + // and make the action known to the memory manager + this._addInactiveAction( newAction, clipUuid, rootUuid ); - const uuid = json.bones[ i ]; - let bone = bones[ uuid ]; + return newAction; - if ( bone === undefined ) { + } - console.warn( 'THREE.Skeleton: No bone found with UUID:', uuid ); - bone = new Bone(); + /** + * Returns an existing animation action for the passed clip. + * + * @param {AnimationClip|string} clip - An animation clip or alternatively the name of the animation clip. + * @param {Object3D} [optionalRoot] - An alternative root object. + * @return {?AnimationAction} The animation action. Returns `null` if no action was found. + */ + existingAction( clip, optionalRoot ) { - } + const root = optionalRoot || this._root, + rootUuid = root.uuid, - this.bones.push( bone ); - this.boneInverses.push( new Matrix4().fromArray( json.boneInverses[ i ] ) ); + clipObject = typeof clip === 'string' ? + AnimationClip.findByName( root, clip ) : clip, - } + clipUuid = clipObject ? clipObject.uuid : clip, - this.init(); + actionsForClip = this._actionsByClip[ clipUuid ]; - return this; + if ( actionsForClip !== undefined ) { - } + return actionsForClip.actionByRoot[ rootUuid ] || null; - toJSON() { + } - const data = { - metadata: { - version: 4.6, - type: 'Skeleton', - generator: 'Skeleton.toJSON' - }, - bones: [], - boneInverses: [] - }; + return null; - data.uuid = this.uuid; + } - const bones = this.bones; - const boneInverses = this.boneInverses; + /** + * Deactivates all previously scheduled actions on this mixer. + * + * @return {AnimationMixer} A reference to thi animation mixer. + */ + stopAllAction() { - for ( let i = 0, l = bones.length; i < l; i ++ ) { + const actions = this._actions, + nActions = this._nActiveActions; - const bone = bones[ i ]; - data.bones.push( bone.uuid ); + for ( let i = nActions - 1; i >= 0; -- i ) { - const boneInverse = boneInverses[ i ]; - data.boneInverses.push( boneInverse.toArray() ); + actions[ i ].stop(); } - return data; + return this; } -} + /** + * Advances the global mixer time and updates the animation. + * + * This is usually done in the render loop by passing the delta + * time from {@link Clock} or {@link Timer}. + * + * @param {number} deltaTime - The delta time in seconds. + * @return {AnimationMixer} A reference to thi animation mixer. + */ + update( deltaTime ) { -class InstancedBufferAttribute extends BufferAttribute { + deltaTime *= this.timeScale; - constructor( array, itemSize, normalized, meshPerAttribute = 1 ) { + const actions = this._actions, + nActions = this._nActiveActions, - super( array, itemSize, normalized ); + time = this.time += deltaTime, + timeDirection = Math.sign( deltaTime ), - this.isInstancedBufferAttribute = true; + accuIndex = this._accuIndex ^= 1; - this.meshPerAttribute = meshPerAttribute; + // run active actions - } + for ( let i = 0; i !== nActions; ++ i ) { - copy( source ) { + const action = actions[ i ]; - super.copy( source ); + action._update( time, deltaTime, timeDirection, accuIndex ); - this.meshPerAttribute = source.meshPerAttribute; + } + + // update scene graph + + const bindings = this._bindings, + nBindings = this._nActiveBindings; + + for ( let i = 0; i !== nBindings; ++ i ) { + + bindings[ i ].apply( accuIndex ); + + } return this; } - toJSON() { + /** + * Sets the global mixer to a specific time and updates the animation accordingly. + * + * This is useful when you need to jump to an exact time in an animation. The + * input parameter will be scaled by {@link AnimationMixer#timeScale} + * + * @param {number} time - The time to set in seconds. + * @return {AnimationMixer} A reference to thi animation mixer. + */ + setTime( time ) { - const data = super.toJSON(); + this.time = 0; // Zero out time attribute for AnimationMixer object; + for ( let i = 0; i < this._actions.length; i ++ ) { - data.meshPerAttribute = this.meshPerAttribute; + this._actions[ i ].time = 0; // Zero out time attribute for all associated AnimationAction objects. - data.isInstancedBufferAttribute = true; + } - return data; + return this.update( time ); // Update used to set exact time. Returns "this" AnimationMixer object. } -} + /** + * Returns this mixer's root object. + * + * @return {Object3D} The mixer's root object. + */ + getRoot() { -const _instanceLocalMatrix = /*@__PURE__*/ new Matrix4(); -const _instanceWorldMatrix = /*@__PURE__*/ new Matrix4(); + return this._root; -const _instanceIntersects = []; + } -const _box3 = /*@__PURE__*/ new Box3(); -const _identity = /*@__PURE__*/ new Matrix4(); -const _mesh = /*@__PURE__*/ new Mesh(); -const _sphere$2 = /*@__PURE__*/ new Sphere(); + /** + * Deallocates all memory resources for a clip. Before using this method make + * sure to call {@link AnimationAction#stop} for all related actions. + * + * @param {AnimationClip} clip - The clip to uncache. + */ + uncacheClip( clip ) { -class InstancedMesh extends Mesh { + const actions = this._actions, + clipUuid = clip.uuid, + actionsByClip = this._actionsByClip, + actionsForClip = actionsByClip[ clipUuid ]; - constructor( geometry, material, count ) { + if ( actionsForClip !== undefined ) { - super( geometry, material ); + // note: just calling _removeInactiveAction would mess up the + // iteration state and also require updating the state we can + // just throw away - this.isInstancedMesh = true; + const actionsToRemove = actionsForClip.knownActions; - this.instanceMatrix = new InstancedBufferAttribute( new Float32Array( count * 16 ), 16 ); - this.instanceColor = null; + for ( let i = 0, n = actionsToRemove.length; i !== n; ++ i ) { - this.count = count; + const action = actionsToRemove[ i ]; - this.boundingBox = null; - this.boundingSphere = null; + this._deactivateAction( action ); - for ( let i = 0; i < count; i ++ ) { + const cacheIndex = action._cacheIndex, + lastInactiveAction = actions[ actions.length - 1 ]; - this.setMatrixAt( i, _identity ); + action._cacheIndex = null; + action._byClipCacheIndex = null; + + lastInactiveAction._cacheIndex = cacheIndex; + actions[ cacheIndex ] = lastInactiveAction; + actions.pop(); + + this._removeInactiveBindingsForAction( action ); + + } + + delete actionsByClip[ clipUuid ]; } } - computeBoundingBox() { + /** + * Deallocates all memory resources for a root object. Before using this + * method make sure to call {@link AnimationAction#stop} for all related + * actions or alternatively {@link AnimationMixer#stopAllAction} when the + * mixer operates on a single root. + * + * @param {Object3D} root - The root object to uncache. + */ + uncacheRoot( root ) { - const geometry = this.geometry; - const count = this.count; + const rootUuid = root.uuid, + actionsByClip = this._actionsByClip; - if ( this.boundingBox === null ) { + for ( const clipUuid in actionsByClip ) { - this.boundingBox = new Box3(); + const actionByRoot = actionsByClip[ clipUuid ].actionByRoot, + action = actionByRoot[ rootUuid ]; - } + if ( action !== undefined ) { - if ( geometry.boundingBox === null ) { + this._deactivateAction( action ); + this._removeInactiveAction( action ); - geometry.computeBoundingBox(); + } } - this.boundingBox.makeEmpty(); + const bindingsByRoot = this._bindingsByRootAndName, + bindingByName = bindingsByRoot[ rootUuid ]; - for ( let i = 0; i < count; i ++ ) { + if ( bindingByName !== undefined ) { - this.getMatrixAt( i, _instanceLocalMatrix ); + for ( const trackName in bindingByName ) { - _box3.copy( geometry.boundingBox ).applyMatrix4( _instanceLocalMatrix ); + const binding = bindingByName[ trackName ]; + binding.restoreOriginalState(); + this._removeInactiveBinding( binding ); - this.boundingBox.union( _box3 ); + } } } - computeBoundingSphere() { + /** + * Deallocates all memory resources for an action. The action is identified by the + * given clip and an optional root object. Before using this method make + * sure to call {@link AnimationAction#stop} to deactivate the action. + * + * @param {AnimationClip|string} clip - An animation clip or alternatively the name of the animation clip. + * @param {Object3D} [optionalRoot] - An alternative root object. + */ + uncacheAction( clip, optionalRoot ) { - const geometry = this.geometry; - const count = this.count; + const action = this.existingAction( clip, optionalRoot ); - if ( this.boundingSphere === null ) { + if ( action !== null ) { - this.boundingSphere = new Sphere(); + this._deactivateAction( action ); + this._removeInactiveAction( action ); } - if ( geometry.boundingSphere === null ) { + } - geometry.computeBoundingSphere(); +} - } +/** + * Represents a 3D render target. + * + * @augments RenderTarget + */ +class RenderTarget3D extends RenderTarget { - this.boundingSphere.makeEmpty(); + /** + * Constructs a new 3D render target. + * + * @param {number} [width=1] - The width of the render target. + * @param {number} [height=1] - The height of the render target. + * @param {number} [depth=1] - The height of the render target. + * @param {RenderTarget~Options} [options] - The configuration object. + */ + constructor( width = 1, height = 1, depth = 1, options = {} ) { - for ( let i = 0; i < count; i ++ ) { + super( width, height, options ); - this.getMatrixAt( i, _instanceLocalMatrix ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isRenderTarget3D = true; - _sphere$2.copy( geometry.boundingSphere ).applyMatrix4( _instanceLocalMatrix ); + this.depth = depth; - this.boundingSphere.union( _sphere$2 ); + /** + * Overwritten with a different texture type. + * + * @type {Data3DTexture} + */ + this.texture = new Data3DTexture( null, width, height, depth ); + this._setTextureOptions( options ); - } + this.texture.isRenderTargetTexture = true; } - copy( source, recursive ) { +} - super.copy( source, recursive ); +/** + * Represents a uniform which is a global shader variable. They are passed to shader programs. + * + * When declaring a uniform of a {@link ShaderMaterial}, it is declared by value or by object. + * ```js + * uniforms: { + * time: { value: 1.0 }, + * resolution: new Uniform( new Vector2() ) + * }; + * ``` + * Since this class can only be used in context of {@link ShaderMaterial}, it is only supported + * in {@link WebGLRenderer}. + */ +class Uniform { - this.instanceMatrix.copy( source.instanceMatrix ); + /** + * Constructs a new uniform. + * + * @param {any} value - The uniform value. + */ + constructor( value ) { - if ( source.instanceColor !== null ) this.instanceColor = source.instanceColor.clone(); + /** + * The uniform value. + * + * @type {any} + */ + this.value = value; - this.count = source.count; + } - if ( source.boundingBox !== null ) this.boundingBox = source.boundingBox.clone(); - if ( source.boundingSphere !== null ) this.boundingSphere = source.boundingSphere.clone(); + /** + * Returns a new uniform with copied values from this instance. + * If the value has a `clone()` method, the value is cloned as well. + * + * @return {Uniform} A clone of this instance. + */ + clone() { - return this; + return new Uniform( this.value.clone === undefined ? this.value : this.value.clone() ); } - getColorAt( index, color ) { +} - color.fromArray( this.instanceColor.array, index * 3 ); +let _id$1 = 0; - } +/** + * A class for managing multiple uniforms in a single group. The renderer will process + * such a definition as a single UBO. + * + * Since this class can only be used in context of {@link ShaderMaterial}, it is only supported + * in {@link WebGLRenderer}. + * + * @augments EventDispatcher + */ +class UniformsGroup extends EventDispatcher { - getMatrixAt( index, matrix ) { + /** + * Constructs a new uniforms group. + */ + constructor() { - matrix.fromArray( this.instanceMatrix.array, index * 16 ); + super(); - } + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isUniformsGroup = true; - raycast( raycaster, intersects ) { + /** + * The ID of the 3D object. + * + * @name UniformsGroup#id + * @type {number} + * @readonly + */ + Object.defineProperty( this, 'id', { value: _id$1 ++ } ); - const matrixWorld = this.matrixWorld; - const raycastTimes = this.count; + /** + * The name of the uniforms group. + * + * @type {string} + */ + this.name = ''; - _mesh.geometry = this.geometry; - _mesh.material = this.material; + /** + * The buffer usage. + * + * @type {(StaticDrawUsage|DynamicDrawUsage|StreamDrawUsage|StaticReadUsage|DynamicReadUsage|StreamReadUsage|StaticCopyUsage|DynamicCopyUsage|StreamCopyUsage)} + * @default StaticDrawUsage + */ + this.usage = StaticDrawUsage; - if ( _mesh.material === undefined ) return; + /** + * An array holding the uniforms. + * + * @type {Array} + */ + this.uniforms = []; - // test with bounding sphere first + } - if ( this.boundingSphere === null ) this.computeBoundingSphere(); + /** + * Adds the given uniform to this uniforms group. + * + * @param {Uniform} uniform - The uniform to add. + * @return {UniformsGroup} A reference to this uniforms group. + */ + add( uniform ) { - _sphere$2.copy( this.boundingSphere ); - _sphere$2.applyMatrix4( matrixWorld ); + this.uniforms.push( uniform ); - if ( raycaster.ray.intersectsSphere( _sphere$2 ) === false ) return; + return this; - // now test each instance + } - for ( let instanceId = 0; instanceId < raycastTimes; instanceId ++ ) { + /** + * Removes the given uniform from this uniforms group. + * + * @param {Uniform} uniform - The uniform to remove. + * @return {UniformsGroup} A reference to this uniforms group. + */ + remove( uniform ) { - // calculate the world matrix for each instance + const index = this.uniforms.indexOf( uniform ); - this.getMatrixAt( instanceId, _instanceLocalMatrix ); + if ( index !== -1 ) this.uniforms.splice( index, 1 ); - _instanceWorldMatrix.multiplyMatrices( matrixWorld, _instanceLocalMatrix ); + return this; - // the mesh represents this single instance + } - _mesh.matrixWorld = _instanceWorldMatrix; + /** + * Sets the name of this uniforms group. + * + * @param {string} name - The name to set. + * @return {UniformsGroup} A reference to this uniforms group. + */ + setName( name ) { - _mesh.raycast( raycaster, _instanceIntersects ); + this.name = name; - // process the result of raycast + return this; - for ( let i = 0, l = _instanceIntersects.length; i < l; i ++ ) { + } - const intersect = _instanceIntersects[ i ]; - intersect.instanceId = instanceId; - intersect.object = this; - intersects.push( intersect ); + /** + * Sets the usage of this uniforms group. + * + * @param {(StaticDrawUsage|DynamicDrawUsage|StreamDrawUsage|StaticReadUsage|DynamicReadUsage|StreamReadUsage|StaticCopyUsage|DynamicCopyUsage|StreamCopyUsage)} value - The usage to set. + * @return {UniformsGroup} A reference to this uniforms group. + */ + setUsage( value ) { - } + this.usage = value; - _instanceIntersects.length = 0; + return this; - } + } + + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + * + * @fires Texture#dispose + */ + dispose() { + + this.dispatchEvent( { type: 'dispose' } ); } - setColorAt( index, color ) { + /** + * Copies the values of the given uniforms group to this instance. + * + * @param {UniformsGroup} source - The uniforms group to copy. + * @return {UniformsGroup} A reference to this uniforms group. + */ + copy( source ) { - if ( this.instanceColor === null ) { + this.name = source.name; + this.usage = source.usage; - this.instanceColor = new InstancedBufferAttribute( new Float32Array( this.instanceMatrix.count * 3 ), 3 ); + const uniformsSource = source.uniforms; - } + this.uniforms.length = 0; - color.toArray( this.instanceColor.array, index * 3 ); + for ( let i = 0, l = uniformsSource.length; i < l; i ++ ) { - } + const uniforms = Array.isArray( uniformsSource[ i ] ) ? uniformsSource[ i ] : [ uniformsSource[ i ] ]; - setMatrixAt( index, matrix ) { + for ( let j = 0; j < uniforms.length; j ++ ) { - matrix.toArray( this.instanceMatrix.array, index * 16 ); + this.uniforms.push( uniforms[ j ].clone() ); - } + } - updateMorphTargets() { + } + + return this; } - dispose() { + /** + * Returns a new uniforms group with copied values from this instance. + * + * @return {UniformsGroup} A clone of this instance. + */ + clone() { - this.dispatchEvent( { type: 'dispose' } ); + return new this.constructor().copy( this ); } } -class LineBasicMaterial extends Material { +/** + * An instanced version of an interleaved buffer. + * + * @augments InterleavedBuffer + */ +class InstancedInterleavedBuffer extends InterleavedBuffer { - constructor( parameters ) { + /** + * Constructs a new instanced interleaved buffer. + * + * @param {TypedArray} array - A typed array with a shared buffer storing attribute data. + * @param {number} stride - The number of typed-array elements per vertex. + * @param {number} [meshPerAttribute=1] - Defines how often a value of this interleaved buffer should be repeated. + */ + constructor( array, stride, meshPerAttribute = 1 ) { - super(); + super( array, stride ); - this.isLineBasicMaterial = true; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isInstancedInterleavedBuffer = true; - this.type = 'LineBasicMaterial'; + /** + * Defines how often a value of this buffer attribute should be repeated, + * see {@link InstancedBufferAttribute#meshPerAttribute}. + * + * @type {number} + * @default 1 + */ + this.meshPerAttribute = meshPerAttribute; - this.color = new Color( 0xffffff ); + } - this.map = null; + copy( source ) { - this.linewidth = 1; - this.linecap = 'round'; - this.linejoin = 'round'; + super.copy( source ); - this.fog = true; + this.meshPerAttribute = source.meshPerAttribute; - this.setValues( parameters ); + return this; } + clone( data ) { - copy( source ) { + const ib = super.clone( data ); - super.copy( source ); + ib.meshPerAttribute = this.meshPerAttribute; - this.color.copy( source.color ); + return ib; - this.map = source.map; + } - this.linewidth = source.linewidth; - this.linecap = source.linecap; - this.linejoin = source.linejoin; + toJSON( data ) { - this.fog = source.fog; + const json = super.toJSON( data ); - return this; + json.isInstancedInterleavedBuffer = true; + json.meshPerAttribute = this.meshPerAttribute; + + return json; } } -const _start$1 = /*@__PURE__*/ new Vector3(); -const _end$1 = /*@__PURE__*/ new Vector3(); -const _inverseMatrix$1 = /*@__PURE__*/ new Matrix4(); -const _ray$1 = /*@__PURE__*/ new Ray(); -const _sphere$1 = /*@__PURE__*/ new Sphere(); - -class Line extends Object3D { - - constructor( geometry = new BufferGeometry(), material = new LineBasicMaterial() ) { +/** + * An alternative version of a buffer attribute with more control over the VBO. + * + * The renderer does not construct a VBO for this kind of attribute. Instead, it uses + * whatever VBO is passed in constructor and can later be altered via the `buffer` property. + * + * The most common use case for this class is when some kind of GPGPU calculation interferes + * or even produces the VBOs in question. + * + * Notice that this class can only be used with {@link WebGLRenderer}. + */ +class GLBufferAttribute { - super(); + /** + * Constructs a new GL buffer attribute. + * + * @param {WebGLBuffer} buffer - The native WebGL buffer. + * @param {number} type - The native data type (e.g. `gl.FLOAT`). + * @param {number} itemSize - The item size. + * @param {number} elementSize - The corresponding size (in bytes) for the given `type` parameter. + * @param {number} count - The expected number of vertices in VBO. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + */ + constructor( buffer, type, itemSize, elementSize, count, normalized = false ) { - this.isLine = true; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isGLBufferAttribute = true; - this.type = 'Line'; + /** + * The name of the buffer attribute. + * + * @type {string} + */ + this.name = ''; - this.geometry = geometry; - this.material = material; + /** + * The native WebGL buffer. + * + * @type {WebGLBuffer} + */ + this.buffer = buffer; - this.updateMorphTargets(); + /** + * The native data type. + * + * @type {number} + */ + this.type = type; - } + /** + * The item size, see {@link BufferAttribute#itemSize}. + * + * @type {number} + */ + this.itemSize = itemSize; - copy( source, recursive ) { + /** + * The corresponding size (in bytes) for the given `type` parameter. + * + * @type {number} + */ + this.elementSize = elementSize; - super.copy( source, recursive ); + /** + * The expected number of vertices in VBO. + * + * @type {number} + */ + this.count = count; - this.material = source.material; - this.geometry = source.geometry; + /** + * Applies to integer data only. Indicates how the underlying data in the buffer maps to + * the values in the GLSL code. For instance, if `buffer` contains data of `gl.UNSIGNED_SHORT`, + * and `normalized` is `true`, the values `0 - +65535` in the buffer data will be mapped to + * `0.0f - +1.0f` in the GLSL attribute. If `normalized` is `false`, the values will be converted + * to floats unmodified, i.e. `65535` becomes `65535.0f`. + * + * @type {boolean} + */ + this.normalized = normalized; - return this; + /** + * A version number, incremented every time the `needsUpdate` is set to `true`. + * + * @type {number} + */ + this.version = 0; } - computeLineDistances() { + /** + * Flag to indicate that this attribute has changed and should be re-sent to + * the GPU. Set this to `true` when you modify the value of the array. + * + * @type {number} + * @default false + * @param {boolean} value + */ + set needsUpdate( value ) { - const geometry = this.geometry; + if ( value === true ) this.version ++; - // we assume non-indexed geometry + } - if ( geometry.index === null ) { + /** + * Sets the given native WebGL buffer. + * + * @param {WebGLBuffer} buffer - The buffer to set. + * @return {BufferAttribute} A reference to this instance. + */ + setBuffer( buffer ) { - const positionAttribute = geometry.attributes.position; - const lineDistances = [ 0 ]; + this.buffer = buffer; - for ( let i = 1, l = positionAttribute.count; i < l; i ++ ) { + return this; - _start$1.fromBufferAttribute( positionAttribute, i - 1 ); - _end$1.fromBufferAttribute( positionAttribute, i ); + } - lineDistances[ i ] = lineDistances[ i - 1 ]; - lineDistances[ i ] += _start$1.distanceTo( _end$1 ); + /** + * Sets the given native data type and element size. + * + * @param {number} type - The native data type (e.g. `gl.FLOAT`). + * @param {number} elementSize - The corresponding size (in bytes) for the given `type` parameter. + * @return {BufferAttribute} A reference to this instance. + */ + setType( type, elementSize ) { - } + this.type = type; + this.elementSize = elementSize; - geometry.setAttribute( 'lineDistance', new Float32BufferAttribute( lineDistances, 1 ) ); + return this; - } else { + } - console.warn( 'THREE.Line.computeLineDistances(): Computation only possible with non-indexed BufferGeometry.' ); + /** + * Sets the item size. + * + * @param {number} itemSize - The item size. + * @return {BufferAttribute} A reference to this instance. + */ + setItemSize( itemSize ) { - } + this.itemSize = itemSize; return this; } - raycast( raycaster, intersects ) { + /** + * Sets the count (the expected number of vertices in VBO). + * + * @param {number} count - The count. + * @return {BufferAttribute} A reference to this instance. + */ + setCount( count ) { - const geometry = this.geometry; - const matrixWorld = this.matrixWorld; - const threshold = raycaster.params.Line.threshold; - const drawRange = geometry.drawRange; + this.count = count; - // Checking boundingSphere distance to ray + return this; - if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); + } - _sphere$1.copy( geometry.boundingSphere ); - _sphere$1.applyMatrix4( matrixWorld ); - _sphere$1.radius += threshold; +} - if ( raycaster.ray.intersectsSphere( _sphere$1 ) === false ) return; +const _matrix = /*@__PURE__*/ new Matrix4(); - // +/** + * This class is designed to assist with raycasting. Raycasting is used for + * mouse picking (working out what objects in the 3d space the mouse is over) + * amongst other things. + */ +class Raycaster { - _inverseMatrix$1.copy( matrixWorld ).invert(); - _ray$1.copy( raycaster.ray ).applyMatrix4( _inverseMatrix$1 ); + /** + * Constructs a new raycaster. + * + * @param {Vector3} origin - The origin vector where the ray casts from. + * @param {Vector3} direction - The (normalized) direction vector that gives direction to the ray. + * @param {number} [near=0] - All results returned are further away than near. Near can't be negative. + * @param {number} [far=Infinity] - All results returned are closer than far. Far can't be lower than near. + */ + constructor( origin, direction, near = 0, far = Infinity ) { - const localThreshold = threshold / ( ( this.scale.x + this.scale.y + this.scale.z ) / 3 ); - const localThresholdSq = localThreshold * localThreshold; + /** + * The ray used for raycasting. + * + * @type {Ray} + */ + this.ray = new Ray( origin, direction ); - const vStart = new Vector3(); - const vEnd = new Vector3(); - const interSegment = new Vector3(); - const interRay = new Vector3(); - const step = this.isLineSegments ? 2 : 1; + /** + * All results returned are further away than near. Near can't be negative. + * + * @type {number} + * @default 0 + */ + this.near = near; - const index = geometry.index; - const attributes = geometry.attributes; - const positionAttribute = attributes.position; + /** + * All results returned are further away than near. Near can't be negative. + * + * @type {number} + * @default Infinity + */ + this.far = far; - if ( index !== null ) { + /** + * The camera to use when raycasting against view-dependent objects such as + * billboarded objects like sprites. This field can be set manually or + * is set when calling `setFromCamera()`. + * + * @type {?Camera} + * @default null + */ + this.camera = null; - const start = Math.max( 0, drawRange.start ); - const end = Math.min( index.count, ( drawRange.start + drawRange.count ) ); + /** + * Allows to selectively ignore 3D objects when performing intersection tests. + * The following code example ensures that only 3D objects on layer `1` will be + * honored by raycaster. + * ```js + * raycaster.layers.set( 1 ); + * object.layers.enable( 1 ); + * ``` + * + * @type {Layers} + */ + this.layers = new Layers(); - for ( let i = start, l = end - 1; i < l; i += step ) { - const a = index.getX( i ); - const b = index.getX( i + 1 ); + /** + * A parameter object that configures the raycasting. It has the structure: + * + * ``` + * { + * Mesh: {}, + * Line: { threshold: 1 }, + * LOD: {}, + * Points: { threshold: 1 }, + * Sprite: {} + * } + * ``` + * Where `threshold` is the precision of the raycaster when intersecting objects, in world units. + * + * @type {Object} + */ + this.params = { + Mesh: {}, + Line: { threshold: 1 }, + LOD: {}, + Points: { threshold: 1 }, + Sprite: {} + }; - vStart.fromBufferAttribute( positionAttribute, a ); - vEnd.fromBufferAttribute( positionAttribute, b ); + } - const distSq = _ray$1.distanceSqToSegment( vStart, vEnd, interRay, interSegment ); + /** + * Updates the ray with a new origin and direction by copying the values from the arguments. + * + * @param {Vector3} origin - The origin vector where the ray casts from. + * @param {Vector3} direction - The (normalized) direction vector that gives direction to the ray. + */ + set( origin, direction ) { - if ( distSq > localThresholdSq ) continue; + // direction is assumed to be normalized (for accurate distance calculations) - interRay.applyMatrix4( this.matrixWorld ); //Move back to world space for distance calculation + this.ray.set( origin, direction ); - const distance = raycaster.ray.origin.distanceTo( interRay ); + } - if ( distance < raycaster.near || distance > raycaster.far ) continue; + /** + * Uses the given coordinates and camera to compute a new origin and direction for the internal ray. + * + * @param {Vector2} coords - 2D coordinates of the mouse, in normalized device coordinates (NDC). + * X and Y components should be between `-1` and `1`. + * @param {Camera} camera - The camera from which the ray should originate. + */ + setFromCamera( coords, camera ) { - intersects.push( { + if ( camera.isPerspectiveCamera ) { - distance: distance, - // What do we want? intersection point on the ray or on the segment?? - // point: raycaster.ray.at( distance ), - point: interSegment.clone().applyMatrix4( this.matrixWorld ), - index: i, - face: null, - faceIndex: null, - object: this + this.ray.origin.setFromMatrixPosition( camera.matrixWorld ); + this.ray.direction.set( coords.x, coords.y, 0.5 ).unproject( camera ).sub( this.ray.origin ).normalize(); + this.camera = camera; - } ); + } else if ( camera.isOrthographicCamera ) { - } + this.ray.origin.set( coords.x, coords.y, ( camera.near + camera.far ) / ( camera.near - camera.far ) ).unproject( camera ); // set origin in plane of camera + this.ray.direction.set( 0, 0, -1 ).transformDirection( camera.matrixWorld ); + this.camera = camera; } else { - const start = Math.max( 0, drawRange.start ); - const end = Math.min( positionAttribute.count, ( drawRange.start + drawRange.count ) ); - - for ( let i = start, l = end - 1; i < l; i += step ) { - - vStart.fromBufferAttribute( positionAttribute, i ); - vEnd.fromBufferAttribute( positionAttribute, i + 1 ); - - const distSq = _ray$1.distanceSqToSegment( vStart, vEnd, interRay, interSegment ); - - if ( distSq > localThresholdSq ) continue; - - interRay.applyMatrix4( this.matrixWorld ); //Move back to world space for distance calculation - - const distance = raycaster.ray.origin.distanceTo( interRay ); + console.error( 'THREE.Raycaster: Unsupported camera type: ' + camera.type ); - if ( distance < raycaster.near || distance > raycaster.far ) continue; + } - intersects.push( { + } - distance: distance, - // What do we want? intersection point on the ray or on the segment?? - // point: raycaster.ray.at( distance ), - point: interSegment.clone().applyMatrix4( this.matrixWorld ), - index: i, - face: null, - faceIndex: null, - object: this + /** + * Uses the given WebXR controller to compute a new origin and direction for the internal ray. + * + * @param {WebXRController} controller - The controller to copy the position and direction from. + * @return {Raycaster} A reference to this raycaster. + */ + setFromXRController( controller ) { - } ); + _matrix.identity().extractRotation( controller.matrixWorld ); - } + this.ray.origin.setFromMatrixPosition( controller.matrixWorld ); + this.ray.direction.set( 0, 0, -1 ).applyMatrix4( _matrix ); - } + return this; } - updateMorphTargets() { - - const geometry = this.geometry; + /** + * The intersection point of a raycaster intersection test. + * @typedef {Object} Raycaster~Intersection + * @property {number} distance - The distance from the ray's origin to the intersection point. + * @property {number} distanceToRay - Some 3D objects e.g. {@link Points} provide the distance of the + * intersection to the nearest point on the ray. For other objects it will be `undefined`. + * @property {Vector3} point - The intersection point, in world coordinates. + * @property {Object} face - The face that has been intersected. + * @property {number} faceIndex - The face index. + * @property {Object3D} object - The 3D object that has been intersected. + * @property {Vector2} uv - U,V coordinates at point of intersection. + * @property {Vector2} uv1 - Second set of U,V coordinates at point of intersection. + * @property {Vector3} uv1 - Interpolated normal vector at point of intersection. + * @property {number} instanceId - The index number of the instance where the ray + * intersects the {@link InstancedMesh}. + */ - const morphAttributes = geometry.morphAttributes; - const keys = Object.keys( morphAttributes ); + /** + * Checks all intersection between the ray and the object with or without the + * descendants. Intersections are returned sorted by distance, closest first. + * + * `Raycaster` delegates to the `raycast()` method of the passed 3D object, when + * evaluating whether the ray intersects the object or not. This allows meshes to respond + * differently to ray casting than lines or points. + * + * Note that for meshes, faces must be pointed towards the origin of the ray in order + * to be detected; intersections of the ray passing through the back of a face will not + * be detected. To raycast against both faces of an object, you'll want to set {@link Material#side} + * to `THREE.DoubleSide`. + * + * @param {Object3D} object - The 3D object to check for intersection with the ray. + * @param {boolean} [recursive=true] - If set to `true`, it also checks all descendants. + * Otherwise it only checks intersection with the object. + * @param {Array} [intersects=[]] The target array that holds the result of the method. + * @return {Array} An array holding the intersection points. + */ + intersectObject( object, recursive = true, intersects = [] ) { - if ( keys.length > 0 ) { + intersect( object, this, intersects, recursive ); - const morphAttribute = morphAttributes[ keys[ 0 ] ]; + intersects.sort( ascSort ); - if ( morphAttribute !== undefined ) { + return intersects; - this.morphTargetInfluences = []; - this.morphTargetDictionary = {}; + } - for ( let m = 0, ml = morphAttribute.length; m < ml; m ++ ) { + /** + * Checks all intersection between the ray and the objects with or without + * the descendants. Intersections are returned sorted by distance, closest first. + * + * @param {Array} objects - The 3D objects to check for intersection with the ray. + * @param {boolean} [recursive=true] - If set to `true`, it also checks all descendants. + * Otherwise it only checks intersection with the object. + * @param {Array} [intersects=[]] The target array that holds the result of the method. + * @return {Array} An array holding the intersection points. + */ + intersectObjects( objects, recursive = true, intersects = [] ) { - const name = morphAttribute[ m ].name || String( m ); + for ( let i = 0, l = objects.length; i < l; i ++ ) { - this.morphTargetInfluences.push( 0 ); - this.morphTargetDictionary[ name ] = m; + intersect( objects[ i ], this, intersects, recursive ); - } + } - } + intersects.sort( ascSort ); - } + return intersects; } } -const _start = /*@__PURE__*/ new Vector3(); -const _end = /*@__PURE__*/ new Vector3(); - -class LineSegments extends Line { - - constructor( geometry, material ) { - - super( geometry, material ); - - this.isLineSegments = true; - - this.type = 'LineSegments'; - - } +function ascSort( a, b ) { - computeLineDistances() { + return a.distance - b.distance; - const geometry = this.geometry; +} - // we assume non-indexed geometry +function intersect( object, raycaster, intersects, recursive ) { - if ( geometry.index === null ) { + let propagate = true; - const positionAttribute = geometry.attributes.position; - const lineDistances = []; + if ( object.layers.test( raycaster.layers ) ) { - for ( let i = 0, l = positionAttribute.count; i < l; i += 2 ) { + const result = object.raycast( raycaster, intersects ); - _start.fromBufferAttribute( positionAttribute, i ); - _end.fromBufferAttribute( positionAttribute, i + 1 ); + if ( result === false ) propagate = false; - lineDistances[ i ] = ( i === 0 ) ? 0 : lineDistances[ i - 1 ]; - lineDistances[ i + 1 ] = lineDistances[ i ] + _start.distanceTo( _end ); + } - } + if ( propagate === true && recursive === true ) { - geometry.setAttribute( 'lineDistance', new Float32BufferAttribute( lineDistances, 1 ) ); + const children = object.children; - } else { + for ( let i = 0, l = children.length; i < l; i ++ ) { - console.warn( 'THREE.LineSegments.computeLineDistances(): Computation only possible with non-indexed BufferGeometry.' ); + intersect( children[ i ], raycaster, intersects, true ); } - return this; - } } -class LineLoop extends Line { +/** + * This class can be used to represent points in 3D space as + * [Spherical coordinates]{@link https://en.wikipedia.org/wiki/Spherical_coordinate_system}. + */ +class Spherical { - constructor( geometry, material ) { + /** + * Constructs a new spherical. + * + * @param {number} [radius=1] - The radius, or the Euclidean distance (straight-line distance) from the point to the origin. + * @param {number} [phi=0] - The polar angle in radians from the y (up) axis. + * @param {number} [theta=0] - The equator/azimuthal angle in radians around the y (up) axis. + */ + constructor( radius = 1, phi = 0, theta = 0 ) { - super( geometry, material ); + /** + * The radius, or the Euclidean distance (straight-line distance) from the point to the origin. + * + * @type {number} + * @default 1 + */ + this.radius = radius; - this.isLineLoop = true; + /** + * The polar angle in radians from the y (up) axis. + * + * @type {number} + * @default 0 + */ + this.phi = phi; - this.type = 'LineLoop'; + /** + * The equator/azimuthal angle in radians around the y (up) axis. + * + * @type {number} + * @default 0 + */ + this.theta = theta; } -} + /** + * Sets the spherical components by copying the given values. + * + * @param {number} radius - The radius. + * @param {number} phi - The polar angle. + * @param {number} theta - The azimuthal angle. + * @return {Spherical} A reference to this spherical. + */ + set( radius, phi, theta ) { -class PointsMaterial extends Material { + this.radius = radius; + this.phi = phi; + this.theta = theta; - constructor( parameters ) { + return this; - super(); + } - this.isPointsMaterial = true; + /** + * Copies the values of the given spherical to this instance. + * + * @param {Spherical} other - The spherical to copy. + * @return {Spherical} A reference to this spherical. + */ + copy( other ) { - this.type = 'PointsMaterial'; + this.radius = other.radius; + this.phi = other.phi; + this.theta = other.theta; - this.color = new Color( 0xffffff ); + return this; - this.map = null; + } - this.alphaMap = null; + /** + * Restricts the polar angle [page:.phi phi] to be between `0.000001` and pi - + * `0.000001`. + * + * @return {Spherical} A reference to this spherical. + */ + makeSafe() { - this.size = 1; - this.sizeAttenuation = true; + const EPS = 0.000001; + this.phi = clamp( this.phi, EPS, Math.PI - EPS ); - this.fog = true; + return this; - this.setValues( parameters ); + } + + /** + * Sets the spherical components from the given vector which is assumed to hold + * Cartesian coordinates. + * + * @param {Vector3} v - The vector to set. + * @return {Spherical} A reference to this spherical. + */ + setFromVector3( v ) { + + return this.setFromCartesianCoords( v.x, v.y, v.z ); } - copy( source ) { + /** + * Sets the spherical components from the given Cartesian coordinates. + * + * @param {number} x - The x value. + * @param {number} y - The x value. + * @param {number} z - The x value. + * @return {Spherical} A reference to this spherical. + */ + setFromCartesianCoords( x, y, z ) { - super.copy( source ); + this.radius = Math.sqrt( x * x + y * y + z * z ); - this.color.copy( source.color ); + if ( this.radius === 0 ) { - this.map = source.map; + this.theta = 0; + this.phi = 0; - this.alphaMap = source.alphaMap; + } else { - this.size = source.size; - this.sizeAttenuation = source.sizeAttenuation; + this.theta = Math.atan2( x, z ); + this.phi = Math.acos( clamp( y / this.radius, -1, 1 ) ); - this.fog = source.fog; + } return this; } -} + /** + * Returns a new spherical with copied values from this instance. + * + * @return {Spherical} A clone of this instance. + */ + clone() { -const _inverseMatrix = /*@__PURE__*/ new Matrix4(); -const _ray = /*@__PURE__*/ new Ray(); -const _sphere = /*@__PURE__*/ new Sphere(); -const _position$2 = /*@__PURE__*/ new Vector3(); + return new this.constructor().copy( this ); -class Points extends Object3D { + } - constructor( geometry = new BufferGeometry(), material = new PointsMaterial() ) { +} - super(); +/** + * This class can be used to represent points in 3D space as + * [Cylindrical coordinates]{@link https://en.wikipedia.org/wiki/Cylindrical_coordinate_system}. + */ +class Cylindrical { - this.isPoints = true; + /** + * Constructs a new cylindrical. + * + * @param {number} [radius=1] - The distance from the origin to a point in the x-z plane. + * @param {number} [theta=0] - A counterclockwise angle in the x-z plane measured in radians from the positive z-axis. + * @param {number} [y=0] - The height above the x-z plane. + */ + constructor( radius = 1, theta = 0, y = 0 ) { - this.type = 'Points'; + /** + * The distance from the origin to a point in the x-z plane. + * + * @type {number} + * @default 1 + */ + this.radius = radius; - this.geometry = geometry; - this.material = material; + /** + * A counterclockwise angle in the x-z plane measured in radians from the positive z-axis. + * + * @type {number} + * @default 0 + */ + this.theta = theta; - this.updateMorphTargets(); + /** + * The height above the x-z plane. + * + * @type {number} + * @default 0 + */ + this.y = y; } - copy( source, recursive ) { - - super.copy( source, recursive ); + /** + * Sets the cylindrical components by copying the given values. + * + * @param {number} radius - The radius. + * @param {number} theta - The theta angle. + * @param {number} y - The height value. + * @return {Cylindrical} A reference to this cylindrical. + */ + set( radius, theta, y ) { - this.material = source.material; - this.geometry = source.geometry; + this.radius = radius; + this.theta = theta; + this.y = y; return this; } - raycast( raycaster, intersects ) { - - const geometry = this.geometry; - const matrixWorld = this.matrixWorld; - const threshold = raycaster.params.Points.threshold; - const drawRange = geometry.drawRange; - - // Checking boundingSphere distance to ray + /** + * Copies the values of the given cylindrical to this instance. + * + * @param {Cylindrical} other - The cylindrical to copy. + * @return {Cylindrical} A reference to this cylindrical. + */ + copy( other ) { - if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); + this.radius = other.radius; + this.theta = other.theta; + this.y = other.y; - _sphere.copy( geometry.boundingSphere ); - _sphere.applyMatrix4( matrixWorld ); - _sphere.radius += threshold; + return this; - if ( raycaster.ray.intersectsSphere( _sphere ) === false ) return; + } - // + /** + * Sets the cylindrical components from the given vector which is assumed to hold + * Cartesian coordinates. + * + * @param {Vector3} v - The vector to set. + * @return {Cylindrical} A reference to this cylindrical. + */ + setFromVector3( v ) { - _inverseMatrix.copy( matrixWorld ).invert(); - _ray.copy( raycaster.ray ).applyMatrix4( _inverseMatrix ); + return this.setFromCartesianCoords( v.x, v.y, v.z ); - const localThreshold = threshold / ( ( this.scale.x + this.scale.y + this.scale.z ) / 3 ); - const localThresholdSq = localThreshold * localThreshold; + } - const index = geometry.index; - const attributes = geometry.attributes; - const positionAttribute = attributes.position; + /** + * Sets the cylindrical components from the given Cartesian coordinates. + * + * @param {number} x - The x value. + * @param {number} y - The x value. + * @param {number} z - The x value. + * @return {Cylindrical} A reference to this cylindrical. + */ + setFromCartesianCoords( x, y, z ) { - if ( index !== null ) { + this.radius = Math.sqrt( x * x + z * z ); + this.theta = Math.atan2( x, z ); + this.y = y; - const start = Math.max( 0, drawRange.start ); - const end = Math.min( index.count, ( drawRange.start + drawRange.count ) ); + return this; - for ( let i = start, il = end; i < il; i ++ ) { + } - const a = index.getX( i ); + /** + * Returns a new cylindrical with copied values from this instance. + * + * @return {Cylindrical} A clone of this instance. + */ + clone() { - _position$2.fromBufferAttribute( positionAttribute, a ); + return new this.constructor().copy( this ); - testPoint( _position$2, a, localThresholdSq, matrixWorld, raycaster, intersects, this ); + } - } +} - } else { +/** + * Represents a 2x2 matrix. + * + * A Note on Row-Major and Column-Major Ordering: + * + * The constructor and {@link Matrix2#set} method take arguments in + * [row-major]{@link https://en.wikipedia.org/wiki/Row-_and_column-major_order#Column-major_order} + * order, while internally they are stored in the {@link Matrix2#elements} array in column-major order. + * This means that calling: + * ```js + * const m = new THREE.Matrix2(); + * m.set( 11, 12, + * 21, 22 ); + * ``` + * will result in the elements array containing: + * ```js + * m.elements = [ 11, 21, + * 12, 22 ]; + * ``` + * and internally all calculations are performed using column-major ordering. + * However, as the actual ordering makes no difference mathematically and + * most people are used to thinking about matrices in row-major order, the + * three.js documentation shows matrices in row-major order. Just bear in + * mind that if you are reading the source code, you'll have to take the + * transpose of any matrices outlined here to make sense of the calculations. + */ +class Matrix2 { - const start = Math.max( 0, drawRange.start ); - const end = Math.min( positionAttribute.count, ( drawRange.start + drawRange.count ) ); + /** + * Constructs a new 2x2 matrix. The arguments are supposed to be + * in row-major order. If no arguments are provided, the constructor + * initializes the matrix as an identity matrix. + * + * @param {number} [n11] - 1-1 matrix element. + * @param {number} [n12] - 1-2 matrix element. + * @param {number} [n21] - 2-1 matrix element. + * @param {number} [n22] - 2-2 matrix element. + */ + constructor( n11, n12, n21, n22 ) { - for ( let i = start, l = end; i < l; i ++ ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + Matrix2.prototype.isMatrix2 = true; - _position$2.fromBufferAttribute( positionAttribute, i ); + /** + * A column-major list of matrix values. + * + * @type {Array} + */ + this.elements = [ + 1, 0, + 0, 1, + ]; - testPoint( _position$2, i, localThresholdSq, matrixWorld, raycaster, intersects, this ); + if ( n11 !== undefined ) { - } + this.set( n11, n12, n21, n22 ); } } - updateMorphTargets() { - - const geometry = this.geometry; - - const morphAttributes = geometry.morphAttributes; - const keys = Object.keys( morphAttributes ); - - if ( keys.length > 0 ) { - - const morphAttribute = morphAttributes[ keys[ 0 ] ]; - - if ( morphAttribute !== undefined ) { + /** + * Sets this matrix to the 2x2 identity matrix. + * + * @return {Matrix2} A reference to this matrix. + */ + identity() { - this.morphTargetInfluences = []; - this.morphTargetDictionary = {}; + this.set( + 1, 0, + 0, 1, + ); - for ( let m = 0, ml = morphAttribute.length; m < ml; m ++ ) { + return this; - const name = morphAttribute[ m ].name || String( m ); + } - this.morphTargetInfluences.push( 0 ); - this.morphTargetDictionary[ name ] = m; + /** + * Sets the elements of the matrix from the given array. + * + * @param {Array} array - The matrix elements in column-major order. + * @param {number} [offset=0] - Index of the first element in the array. + * @return {Matrix2} A reference to this matrix. + */ + fromArray( array, offset = 0 ) { - } + for ( let i = 0; i < 4; i ++ ) { - } + this.elements[ i ] = array[ i + offset ]; } + return this; + } -} + /** + * Sets the elements of the matrix.The arguments are supposed to be + * in row-major order. + * + * @param {number} n11 - 1-1 matrix element. + * @param {number} n12 - 1-2 matrix element. + * @param {number} n21 - 2-1 matrix element. + * @param {number} n22 - 2-2 matrix element. + * @return {Matrix2} A reference to this matrix. + */ + set( n11, n12, n21, n22 ) { -function testPoint( point, index, localThresholdSq, matrixWorld, raycaster, intersects, object ) { + const te = this.elements; - const rayPointDistanceSq = _ray.distanceSqToPoint( point ); + te[ 0 ] = n11; te[ 2 ] = n12; + te[ 1 ] = n21; te[ 3 ] = n22; - if ( rayPointDistanceSq < localThresholdSq ) { + return this; - const intersectPoint = new Vector3(); + } - _ray.closestPointToPoint( point, intersectPoint ); - intersectPoint.applyMatrix4( matrixWorld ); +} - const distance = raycaster.ray.origin.distanceTo( intersectPoint ); +const _vector$4 = /*@__PURE__*/ new Vector2(); - if ( distance < raycaster.near || distance > raycaster.far ) return; +/** + * Represents an axis-aligned bounding box (AABB) in 2D space. + */ +class Box2 { - intersects.push( { + /** + * Constructs a new bounding box. + * + * @param {Vector2} [min=(Infinity,Infinity)] - A vector representing the lower boundary of the box. + * @param {Vector2} [max=(-Infinity,-Infinity)] - A vector representing the upper boundary of the box. + */ + constructor( min = new Vector2( + Infinity, + Infinity ), max = new Vector2( - Infinity, - Infinity ) ) { - distance: distance, - distanceToRay: Math.sqrt( rayPointDistanceSq ), - point: intersectPoint, - index: index, - face: null, - object: object + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isBox2 = true; - } ); + /** + * The lower boundary of the box. + * + * @type {Vector2} + */ + this.min = min; + + /** + * The upper boundary of the box. + * + * @type {Vector2} + */ + this.max = max; } -} + /** + * Sets the lower and upper boundaries of this box. + * Please note that this method only copies the values from the given objects. + * + * @param {Vector2} min - The lower boundary of the box. + * @param {Vector2} max - The upper boundary of the box. + * @return {Box2} A reference to this bounding box. + */ + set( min, max ) { -class VideoTexture extends Texture { + this.min.copy( min ); + this.max.copy( max ); - constructor( video, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) { + return this; - super( video, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); + } - this.isVideoTexture = true; + /** + * Sets the upper and lower bounds of this box so it encloses the position data + * in the given array. + * + * @param {Array} points - An array holding 2D position data as instances of {@link Vector2}. + * @return {Box2} A reference to this bounding box. + */ + setFromPoints( points ) { - this.minFilter = minFilter !== undefined ? minFilter : LinearFilter; - this.magFilter = magFilter !== undefined ? magFilter : LinearFilter; + this.makeEmpty(); - this.generateMipmaps = false; + for ( let i = 0, il = points.length; i < il; i ++ ) { - const scope = this; + this.expandByPoint( points[ i ] ); - function updateVideo() { + } - scope.needsUpdate = true; - video.requestVideoFrameCallback( updateVideo ); + return this; - } + } - if ( 'requestVideoFrameCallback' in video ) { + /** + * Centers this box on the given center vector and sets this box's width, height and + * depth to the given size values. + * + * @param {Vector2} center - The center of the box. + * @param {Vector2} size - The x and y dimensions of the box. + * @return {Box2} A reference to this bounding box. + */ + setFromCenterAndSize( center, size ) { - video.requestVideoFrameCallback( updateVideo ); + const halfSize = _vector$4.copy( size ).multiplyScalar( 0.5 ); + this.min.copy( center ).sub( halfSize ); + this.max.copy( center ).add( halfSize ); - } + return this; } + /** + * Returns a new box with copied values from this instance. + * + * @return {Box2} A clone of this instance. + */ clone() { - return new this.constructor( this.image ).copy( this ); + return new this.constructor().copy( this ); } - update() { + /** + * Copies the values of the given box to this instance. + * + * @param {Box2} box - The box to copy. + * @return {Box2} A reference to this bounding box. + */ + copy( box ) { - const video = this.image; - const hasVideoFrameCallback = 'requestVideoFrameCallback' in video; + this.min.copy( box.min ); + this.max.copy( box.max ); - if ( hasVideoFrameCallback === false && video.readyState >= video.HAVE_CURRENT_DATA ) { + return this; - this.needsUpdate = true; + } - } + /** + * Makes this box empty which means in encloses a zero space in 2D. + * + * @return {Box2} A reference to this bounding box. + */ + makeEmpty() { - } + this.min.x = this.min.y = + Infinity; + this.max.x = this.max.y = - Infinity; -} + return this; -class FramebufferTexture extends Texture { + } - constructor( width, height ) { + /** + * Returns true if this box includes zero points within its bounds. + * Note that a box with equal lower and upper bounds still includes one + * point, the one both bounds share. + * + * @return {boolean} Whether this box is empty or not. + */ + isEmpty() { - super( { width, height } ); + // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes - this.isFramebufferTexture = true; + return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y ); - this.magFilter = NearestFilter; - this.minFilter = NearestFilter; + } - this.generateMipmaps = false; + /** + * Returns the center point of this box. + * + * @param {Vector2} target - The target vector that is used to store the method's result. + * @return {Vector2} The center point. + */ + getCenter( target ) { - this.needsUpdate = true; + return this.isEmpty() ? target.set( 0, 0 ) : target.addVectors( this.min, this.max ).multiplyScalar( 0.5 ); } -} + /** + * Returns the dimensions of this box. + * + * @param {Vector2} target - The target vector that is used to store the method's result. + * @return {Vector2} The size. + */ + getSize( target ) { -class CompressedTexture extends Texture { + return this.isEmpty() ? target.set( 0, 0 ) : target.subVectors( this.max, this.min ); - constructor( mipmaps, width, height, format, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, colorSpace ) { + } - super( null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, colorSpace ); + /** + * Expands the boundaries of this box to include the given point. + * + * @param {Vector2} point - The point that should be included by the bounding box. + * @return {Box2} A reference to this bounding box. + */ + expandByPoint( point ) { - this.isCompressedTexture = true; + this.min.min( point ); + this.max.max( point ); - this.image = { width: width, height: height }; - this.mipmaps = mipmaps; + return this; - // no flipping for cube textures - // (also flipping doesn't work for compressed textures ) + } - this.flipY = false; + /** + * Expands this box equilaterally by the given vector. The width of this + * box will be expanded by the x component of the vector in both + * directions. The height of this box will be expanded by the y component of + * the vector in both directions. + * + * @param {Vector2} vector - The vector that should expand the bounding box. + * @return {Box2} A reference to this bounding box. + */ + expandByVector( vector ) { - // can't generate mipmaps for compressed textures - // mips must be embedded in DDS files + this.min.sub( vector ); + this.max.add( vector ); - this.generateMipmaps = false; + return this; } -} + /** + * Expands each dimension of the box by the given scalar. If negative, the + * dimensions of the box will be contracted. + * + * @param {number} scalar - The scalar value that should expand the bounding box. + * @return {Box2} A reference to this bounding box. + */ + expandByScalar( scalar ) { -class CompressedArrayTexture extends CompressedTexture { + this.min.addScalar( - scalar ); + this.max.addScalar( scalar ); - constructor( mipmaps, width, height, depth, format, type ) { + return this; - super( mipmaps, width, height, format, type ); + } - this.isCompressedArrayTexture = true; - this.image.depth = depth; - this.wrapR = ClampToEdgeWrapping; + /** + * Returns `true` if the given point lies within or on the boundaries of this box. + * + * @param {Vector2} point - The point to test. + * @return {boolean} Whether the bounding box contains the given point or not. + */ + containsPoint( point ) { + + return point.x >= this.min.x && point.x <= this.max.x && + point.y >= this.min.y && point.y <= this.max.y; } -} + /** + * Returns `true` if this bounding box includes the entirety of the given bounding box. + * If this box and the given one are identical, this function also returns `true`. + * + * @param {Box2} box - The bounding box to test. + * @return {boolean} Whether the bounding box contains the given bounding box or not. + */ + containsBox( box ) { -class CanvasTexture extends Texture { + return this.min.x <= box.min.x && box.max.x <= this.max.x && + this.min.y <= box.min.y && box.max.y <= this.max.y; - constructor( canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) { + } - super( canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); + /** + * Returns a point as a proportion of this box's width and height. + * + * @param {Vector2} point - A point in 2D space. + * @param {Vector2} target - The target vector that is used to store the method's result. + * @return {Vector2} A point as a proportion of this box's width and height. + */ + getParameter( point, target ) { - this.isCanvasTexture = true; + // This can potentially have a divide by zero if the box + // has a size dimension of 0. - this.needsUpdate = true; + return target.set( + ( point.x - this.min.x ) / ( this.max.x - this.min.x ), + ( point.y - this.min.y ) / ( this.max.y - this.min.y ) + ); } -} + /** + * Returns `true` if the given bounding box intersects with this bounding box. + * + * @param {Box2} box - The bounding box to test. + * @return {boolean} Whether the given bounding box intersects with this bounding box. + */ + intersectsBox( box ) { -/** - * Extensible curve object. - * - * Some common of curve methods: - * .getPoint( t, optionalTarget ), .getTangent( t, optionalTarget ) - * .getPointAt( u, optionalTarget ), .getTangentAt( u, optionalTarget ) - * .getPoints(), .getSpacedPoints() - * .getLength() - * .updateArcLengths() - * - * This following curves inherit from THREE.Curve: - * - * -- 2D curves -- - * THREE.ArcCurve - * THREE.CubicBezierCurve - * THREE.EllipseCurve - * THREE.LineCurve - * THREE.QuadraticBezierCurve - * THREE.SplineCurve - * - * -- 3D curves -- - * THREE.CatmullRomCurve3 - * THREE.CubicBezierCurve3 - * THREE.LineCurve3 - * THREE.QuadraticBezierCurve3 - * - * A series of curves can be represented as a THREE.CurvePath. - * - **/ + // using 4 splitting planes to rule out intersections -class Curve { + return box.max.x >= this.min.x && box.min.x <= this.max.x && + box.max.y >= this.min.y && box.min.y <= this.max.y; - constructor() { + } - this.type = 'Curve'; + /** + * Clamps the given point within the bounds of this box. + * + * @param {Vector2} point - The point to clamp. + * @param {Vector2} target - The target vector that is used to store the method's result. + * @return {Vector2} The clamped point. + */ + clampPoint( point, target ) { - this.arcLengthDivisions = 200; + return target.copy( point ).clamp( this.min, this.max ); } - // Virtual base class method to overwrite and implement in subclasses - // - t [0 .. 1] - - getPoint( /* t, optionalTarget */ ) { + /** + * Returns the euclidean distance from any edge of this box to the specified point. If + * the given point lies inside of this box, the distance will be `0`. + * + * @param {Vector2} point - The point to compute the distance to. + * @return {number} The euclidean distance. + */ + distanceToPoint( point ) { - console.warn( 'THREE.Curve: .getPoint() not implemented.' ); - return null; + return this.clampPoint( point, _vector$4 ).distanceTo( point ); } - // Get point at relative position in curve according to arc length - // - u [0 .. 1] + /** + * Computes the intersection of this bounding box and the given one, setting the upper + * bound of this box to the lesser of the two boxes' upper bounds and the + * lower bound of this box to the greater of the two boxes' lower bounds. If + * there's no overlap, makes this box empty. + * + * @param {Box2} box - The bounding box to intersect with. + * @return {Box2} A reference to this bounding box. + */ + intersect( box ) { - getPointAt( u, optionalTarget ) { + this.min.max( box.min ); + this.max.min( box.max ); - const t = this.getUtoTmapping( u ); - return this.getPoint( t, optionalTarget ); + if ( this.isEmpty() ) this.makeEmpty(); + + return this; } - // Get sequence of points using getPoint( t ) + /** + * Computes the union of this box and another and the given one, setting the upper + * bound of this box to the greater of the two boxes' upper bounds and the + * lower bound of this box to the lesser of the two boxes' lower bounds. + * + * @param {Box2} box - The bounding box that will be unioned with this instance. + * @return {Box2} A reference to this bounding box. + */ + union( box ) { - getPoints( divisions = 5 ) { + this.min.min( box.min ); + this.max.max( box.max ); - const points = []; + return this; - for ( let d = 0; d <= divisions; d ++ ) { + } - points.push( this.getPoint( d / divisions ) ); + /** + * Adds the given offset to both the upper and lower bounds of this bounding box, + * effectively moving it in 2D space. + * + * @param {Vector2} offset - The offset that should be used to translate the bounding box. + * @return {Box2} A reference to this bounding box. + */ + translate( offset ) { - } + this.min.add( offset ); + this.max.add( offset ); - return points; + return this; } - // Get sequence of points using getPointAt( u ) + /** + * Returns `true` if this bounding box is equal with the given one. + * + * @param {Box2} box - The box to test for equality. + * @return {boolean} Whether this bounding box is equal with the given one. + */ + equals( box ) { - getSpacedPoints( divisions = 5 ) { + return box.min.equals( this.min ) && box.max.equals( this.max ); - const points = []; + } - for ( let d = 0; d <= divisions; d ++ ) { +} - points.push( this.getPointAt( d / divisions ) ); +const _startP = /*@__PURE__*/ new Vector3(); +const _startEnd = /*@__PURE__*/ new Vector3(); - } +/** + * An analytical line segment in 3D space represented by a start and end point. + */ +class Line3 { - return points; + /** + * Constructs a new line segment. + * + * @param {Vector3} [start=(0,0,0)] - Start of the line segment. + * @param {Vector3} [end=(0,0,0)] - End of the line segment. + */ + constructor( start = new Vector3(), end = new Vector3() ) { + + /** + * Start of the line segment. + * + * @type {Vector3} + */ + this.start = start; + + /** + * End of the line segment. + * + * @type {Vector3} + */ + this.end = end; } - // Get total curve arc length + /** + * Sets the start and end values by copying the given vectors. + * + * @param {Vector3} start - The start point. + * @param {Vector3} end - The end point. + * @return {Line3} A reference to this line segment. + */ + set( start, end ) { - getLength() { + this.start.copy( start ); + this.end.copy( end ); - const lengths = this.getLengths(); - return lengths[ lengths.length - 1 ]; + return this; } - // Get list of cumulative segment lengths - - getLengths( divisions = this.arcLengthDivisions ) { + /** + * Copies the values of the given line segment to this instance. + * + * @param {Line3} line - The line segment to copy. + * @return {Line3} A reference to this line segment. + */ + copy( line ) { - if ( this.cacheArcLengths && - ( this.cacheArcLengths.length === divisions + 1 ) && - ! this.needsUpdate ) { + this.start.copy( line.start ); + this.end.copy( line.end ); - return this.cacheArcLengths; + return this; - } + } - this.needsUpdate = false; + /** + * Returns the center of the line segment. + * + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The center point. + */ + getCenter( target ) { - const cache = []; - let current, last = this.getPoint( 0 ); - let sum = 0; + return target.addVectors( this.start, this.end ).multiplyScalar( 0.5 ); - cache.push( 0 ); + } - for ( let p = 1; p <= divisions; p ++ ) { + /** + * Returns the delta vector of the line segment's start and end point. + * + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The delta vector. + */ + delta( target ) { - current = this.getPoint( p / divisions ); - sum += current.distanceTo( last ); - cache.push( sum ); - last = current; + return target.subVectors( this.end, this.start ); - } + } - this.cacheArcLengths = cache; + /** + * Returns the squared Euclidean distance between the line' start and end point. + * + * @return {number} The squared Euclidean distance. + */ + distanceSq() { - return cache; // { sums: cache, sum: sum }; Sum is in the last element. + return this.start.distanceToSquared( this.end ); } - updateArcLengths() { + /** + * Returns the Euclidean distance between the line' start and end point. + * + * @return {number} The Euclidean distance. + */ + distance() { - this.needsUpdate = true; - this.getLengths(); + return this.start.distanceTo( this.end ); } - // Given u ( 0 .. 1 ), get a t to find p. This gives you points which are equidistant + /** + * Returns a vector at a certain position along the line segment. + * + * @param {number} t - A value between `[0,1]` to represent a position along the line segment. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The delta vector. + */ + at( t, target ) { - getUtoTmapping( u, distance ) { + return this.delta( target ).multiplyScalar( t ).add( this.start ); - const arcLengths = this.getLengths(); + } - let i = 0; - const il = arcLengths.length; + /** + * Returns a point parameter based on the closest point as projected on the line segment. + * + * @param {Vector3} point - The point for which to return a point parameter. + * @param {boolean} clampToLine - Whether to clamp the result to the range `[0,1]` or not. + * @return {number} The point parameter. + */ + closestPointToPointParameter( point, clampToLine ) { - let targetArcLength; // The targeted u distance value to get + _startP.subVectors( point, this.start ); + _startEnd.subVectors( this.end, this.start ); - if ( distance ) { + const startEnd2 = _startEnd.dot( _startEnd ); + const startEnd_startP = _startEnd.dot( _startP ); - targetArcLength = distance; + let t = startEnd_startP / startEnd2; - } else { + if ( clampToLine ) { - targetArcLength = u * arcLengths[ il - 1 ]; + t = clamp( t, 0, 1 ); } - // binary search for the index with largest value smaller than target u distance - - let low = 0, high = il - 1, comparison; + return t; - while ( low <= high ) { + } - i = Math.floor( low + ( high - low ) / 2 ); // less likely to overflow, though probably not issue here, JS doesn't really have integers, all numbers are floats + /** + * Returns the closets point on the line for a given point. + * + * @param {Vector3} point - The point to compute the closest point on the line for. + * @param {boolean} clampToLine - Whether to clamp the result to the range `[0,1]` or not. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The closest point on the line. + */ + closestPointToPoint( point, clampToLine, target ) { - comparison = arcLengths[ i ] - targetArcLength; + const t = this.closestPointToPointParameter( point, clampToLine ); - if ( comparison < 0 ) { + return this.delta( target ).multiplyScalar( t ).add( this.start ); - low = i + 1; + } - } else if ( comparison > 0 ) { + /** + * Applies a 4x4 transformation matrix to this line segment. + * + * @param {Matrix4} matrix - The transformation matrix. + * @return {Line3} A reference to this line segment. + */ + applyMatrix4( matrix ) { - high = i - 1; + this.start.applyMatrix4( matrix ); + this.end.applyMatrix4( matrix ); - } else { + return this; - high = i; - break; + } - // DONE + /** + * Returns `true` if this line segment is equal with the given one. + * + * @param {Line3} line - The line segment to test for equality. + * @return {boolean} Whether this line segment is equal with the given one. + */ + equals( line ) { - } + return line.start.equals( this.start ) && line.end.equals( this.end ); - } + } - i = high; + /** + * Returns a new line segment with copied values from this instance. + * + * @return {Line3} A clone of this instance. + */ + clone() { - if ( arcLengths[ i ] === targetArcLength ) { + return new this.constructor().copy( this ); - return i / ( il - 1 ); + } - } +} - // we could get finer grain at lengths, or use simple interpolation between two points +const _vector$3 = /*@__PURE__*/ new Vector3(); - const lengthBefore = arcLengths[ i ]; - const lengthAfter = arcLengths[ i + 1 ]; +/** + * This displays a cone shaped helper object for a {@link SpotLight}. + * + * ```js + * const spotLight = new THREE.SpotLight( 0xffffff ); + * spotLight.position.set( 10, 10, 10 ); + * scene.add( spotLight ); + * + * const spotLightHelper = new THREE.SpotLightHelper( spotLight ); + * scene.add( spotLightHelper ); + * ``` + * + * @augments Object3D + */ +class SpotLightHelper extends Object3D { - const segmentLength = lengthAfter - lengthBefore; + /** + * Constructs a new spot light helper. + * + * @param {HemisphereLight} light - The light to be visualized. + * @param {number|Color|string} [color] - The helper's color. If not set, the helper will take + * the color of the light. + */ + constructor( light, color ) { - // determine where we are between the 'before' and 'after' points + super(); - const segmentFraction = ( targetArcLength - lengthBefore ) / segmentLength; + /** + * The light being visualized. + * + * @type {SpotLight} + */ + this.light = light; - // add that fractional amount to t + this.matrixAutoUpdate = false; - const t = ( i + segmentFraction ) / ( il - 1 ); + /** + * The color parameter passed in the constructor. + * If not set, the helper will take the color of the light. + * + * @type {number|Color|string} + */ + this.color = color; - return t; + this.type = 'SpotLightHelper'; - } + const geometry = new BufferGeometry(); - // Returns a unit vector tangent at t - // In case any sub curve does not implement its tangent derivation, - // 2 points a small delta apart will be used to find its gradient - // which seems to give a reasonable approximation + const positions = [ + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 1, 0, 1, + 0, 0, 0, -1, 0, 1, + 0, 0, 0, 0, 1, 1, + 0, 0, 0, 0, -1, 1 + ]; - getTangent( t, optionalTarget ) { + for ( let i = 0, j = 1, l = 32; i < l; i ++, j ++ ) { - const delta = 0.0001; - let t1 = t - delta; - let t2 = t + delta; + const p1 = ( i / l ) * Math.PI * 2; + const p2 = ( j / l ) * Math.PI * 2; - // Capping in case of danger + positions.push( + Math.cos( p1 ), Math.sin( p1 ), 1, + Math.cos( p2 ), Math.sin( p2 ), 1 + ); - if ( t1 < 0 ) t1 = 0; - if ( t2 > 1 ) t2 = 1; + } - const pt1 = this.getPoint( t1 ); - const pt2 = this.getPoint( t2 ); + geometry.setAttribute( 'position', new Float32BufferAttribute( positions, 3 ) ); - const tangent = optionalTarget || ( ( pt1.isVector2 ) ? new Vector2() : new Vector3() ); + const material = new LineBasicMaterial( { fog: false, toneMapped: false } ); - tangent.copy( pt2 ).sub( pt1 ).normalize(); + this.cone = new LineSegments( geometry, material ); + this.add( this.cone ); - return tangent; + this.update(); } - getTangentAt( u, optionalTarget ) { + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose() { - const t = this.getUtoTmapping( u ); - return this.getTangent( t, optionalTarget ); + this.cone.geometry.dispose(); + this.cone.material.dispose(); } - computeFrenetFrames( segments, closed ) { + /** + * Updates the helper to match the position and direction of the + * light being visualized. + */ + update() { - // see http://www.cs.indiana.edu/pub/techreports/TR425.pdf + this.light.updateWorldMatrix( true, false ); + this.light.target.updateWorldMatrix( true, false ); - const normal = new Vector3(); + // update the local matrix based on the parent and light target transforms + if ( this.parent ) { - const tangents = []; - const normals = []; - const binormals = []; + this.parent.updateWorldMatrix( true ); - const vec = new Vector3(); - const mat = new Matrix4(); + this.matrix + .copy( this.parent.matrixWorld ) + .invert() + .multiply( this.light.matrixWorld ); - // compute the tangent vectors for each segment on the curve + } else { - for ( let i = 0; i <= segments; i ++ ) { + this.matrix.copy( this.light.matrixWorld ); - const u = i / segments; + } - tangents[ i ] = this.getTangentAt( u, new Vector3() ); + this.matrixWorld.copy( this.light.matrixWorld ); - } + const coneLength = this.light.distance ? this.light.distance : 1000; + const coneWidth = coneLength * Math.tan( this.light.angle ); - // select an initial normal vector perpendicular to the first tangent vector, - // and in the direction of the minimum tangent xyz component + this.cone.scale.set( coneWidth, coneWidth, coneLength ); - normals[ 0 ] = new Vector3(); - binormals[ 0 ] = new Vector3(); - let min = Number.MAX_VALUE; - const tx = Math.abs( tangents[ 0 ].x ); - const ty = Math.abs( tangents[ 0 ].y ); - const tz = Math.abs( tangents[ 0 ].z ); + _vector$3.setFromMatrixPosition( this.light.target.matrixWorld ); - if ( tx <= min ) { + this.cone.lookAt( _vector$3 ); + + if ( this.color !== undefined ) { + + this.cone.material.color.set( this.color ); + + } else { - min = tx; - normal.set( 1, 0, 0 ); + this.cone.material.color.copy( this.light.color ); } - if ( ty <= min ) { + } - min = ty; - normal.set( 0, 1, 0 ); +} - } +const _vector$2 = /*@__PURE__*/ new Vector3(); +const _boneMatrix = /*@__PURE__*/ new Matrix4(); +const _matrixWorldInv = /*@__PURE__*/ new Matrix4(); - if ( tz <= min ) { +/** + * A helper object to assist with visualizing a {@link Skeleton}. + * + * ```js + * const helper = new THREE.SkeletonHelper( skinnedMesh ); + * scene.add( helper ); + * ``` + * + * @augments LineSegments + */ +class SkeletonHelper extends LineSegments { - normal.set( 0, 0, 1 ); + /** + * Constructs a new hemisphere light helper. + * + * @param {Object3D} object - Usually an instance of {@link SkinnedMesh}. However, any 3D object + * can be used if it represents a hierarchy of bones (see {@link Bone}). + */ + constructor( object ) { - } + const bones = getBoneList( object ); - vec.crossVectors( tangents[ 0 ], normal ).normalize(); + const geometry = new BufferGeometry(); - normals[ 0 ].crossVectors( tangents[ 0 ], vec ); - binormals[ 0 ].crossVectors( tangents[ 0 ], normals[ 0 ] ); + const vertices = []; + const colors = []; + const color1 = new Color( 0, 0, 1 ); + const color2 = new Color( 0, 1, 0 ); - // compute the slowly-varying normal and binormal vectors for each segment on the curve + for ( let i = 0; i < bones.length; i ++ ) { - for ( let i = 1; i <= segments; i ++ ) { + const bone = bones[ i ]; - normals[ i ] = normals[ i - 1 ].clone(); + if ( bone.parent && bone.parent.isBone ) { - binormals[ i ] = binormals[ i - 1 ].clone(); + vertices.push( 0, 0, 0 ); + vertices.push( 0, 0, 0 ); + colors.push( color1.r, color1.g, color1.b ); + colors.push( color2.r, color2.g, color2.b ); - vec.crossVectors( tangents[ i - 1 ], tangents[ i ] ); + } - if ( vec.length() > Number.EPSILON ) { + } - vec.normalize(); + geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + geometry.setAttribute( 'color', new Float32BufferAttribute( colors, 3 ) ); - const theta = Math.acos( clamp( tangents[ i - 1 ].dot( tangents[ i ] ), - 1, 1 ) ); // clamp for floating pt errors + const material = new LineBasicMaterial( { vertexColors: true, depthTest: false, depthWrite: false, toneMapped: false, transparent: true } ); - normals[ i ].applyMatrix4( mat.makeRotationAxis( vec, theta ) ); + super( geometry, material ); - } + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSkeletonHelper = true; - binormals[ i ].crossVectors( tangents[ i ], normals[ i ] ); + this.type = 'SkeletonHelper'; - } + /** + * The object being visualized. + * + * @type {Object3D} + */ + this.root = object; - // if the curve is closed, postprocess the vectors so the first and last normal vectors are the same + /** + * The list of bones that the helper visualizes. + * + * @type {Array} + */ + this.bones = bones; - if ( closed === true ) { + this.matrix = object.matrixWorld; + this.matrixAutoUpdate = false; - let theta = Math.acos( clamp( normals[ 0 ].dot( normals[ segments ] ), - 1, 1 ) ); - theta /= segments; + } - if ( tangents[ 0 ].dot( vec.crossVectors( normals[ 0 ], normals[ segments ] ) ) > 0 ) { + updateMatrixWorld( force ) { - theta = - theta; + const bones = this.bones; - } + const geometry = this.geometry; + const position = geometry.getAttribute( 'position' ); - for ( let i = 1; i <= segments; i ++ ) { + _matrixWorldInv.copy( this.root.matrixWorld ).invert(); - // twist a little... - normals[ i ].applyMatrix4( mat.makeRotationAxis( tangents[ i ], theta * i ) ); - binormals[ i ].crossVectors( tangents[ i ], normals[ i ] ); + for ( let i = 0, j = 0; i < bones.length; i ++ ) { + + const bone = bones[ i ]; + + if ( bone.parent && bone.parent.isBone ) { + + _boneMatrix.multiplyMatrices( _matrixWorldInv, bone.matrixWorld ); + _vector$2.setFromMatrixPosition( _boneMatrix ); + position.setXYZ( j, _vector$2.x, _vector$2.y, _vector$2.z ); + + _boneMatrix.multiplyMatrices( _matrixWorldInv, bone.parent.matrixWorld ); + _vector$2.setFromMatrixPosition( _boneMatrix ); + position.setXYZ( j + 1, _vector$2.x, _vector$2.y, _vector$2.z ); + + j += 2; } } - return { - tangents: tangents, - normals: normals, - binormals: binormals - }; + geometry.getAttribute( 'position' ).needsUpdate = true; + + super.updateMatrixWorld( force ); } - clone() { + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose() { - return new this.constructor().copy( this ); + this.geometry.dispose(); + this.material.dispose(); } - copy( source ) { - - this.arcLengthDivisions = source.arcLengthDivisions; - - return this; +} - } - toJSON() { +function getBoneList( object ) { - const data = { - metadata: { - version: 4.6, - type: 'Curve', - generator: 'Curve.toJSON' - } - }; + const boneList = []; - data.arcLengthDivisions = this.arcLengthDivisions; - data.type = this.type; + if ( object.isBone === true ) { - return data; + boneList.push( object ); } - fromJSON( json ) { - - this.arcLengthDivisions = json.arcLengthDivisions; + for ( let i = 0; i < object.children.length; i ++ ) { - return this; + boneList.push( ...getBoneList( object.children[ i ] ) ); } + return boneList; + } -class EllipseCurve extends Curve { +/** + * This displays a helper object consisting of a spherical mesh for + * visualizing an instance of {@link PointLight}. + * + * ```js + * const pointLight = new THREE.PointLight( 0xff0000, 1, 100 ); + * pointLight.position.set( 10, 10, 10 ); + * scene.add( pointLight ); + * + * const sphereSize = 1; + * const pointLightHelper = new THREE.PointLightHelper( pointLight, sphereSize ); + * scene.add( pointLightHelper ); + * ``` + * + * @augments Mesh + */ +class PointLightHelper extends Mesh { - constructor( aX = 0, aY = 0, xRadius = 1, yRadius = 1, aStartAngle = 0, aEndAngle = Math.PI * 2, aClockwise = false, aRotation = 0 ) { + /** + * Constructs a new point light helper. + * + * @param {PointLight} light - The light to be visualized. + * @param {number} [sphereSize=1] - The size of the sphere helper. + * @param {number|Color|string} [color] - The helper's color. If not set, the helper will take + * the color of the light. + */ + constructor( light, sphereSize, color ) { - super(); + const geometry = new SphereGeometry( sphereSize, 4, 2 ); + const material = new MeshBasicMaterial( { wireframe: true, fog: false, toneMapped: false } ); - this.isEllipseCurve = true; + super( geometry, material ); - this.type = 'EllipseCurve'; + /** + * The light being visualized. + * + * @type {HemisphereLight} + */ + this.light = light; - this.aX = aX; - this.aY = aY; + /** + * The color parameter passed in the constructor. + * If not set, the helper will take the color of the light. + * + * @type {number|Color|string} + */ + this.color = color; - this.xRadius = xRadius; - this.yRadius = yRadius; + this.type = 'PointLightHelper'; - this.aStartAngle = aStartAngle; - this.aEndAngle = aEndAngle; + this.matrix = this.light.matrixWorld; + this.matrixAutoUpdate = false; - this.aClockwise = aClockwise; + this.update(); - this.aRotation = aRotation; - } + /* + // TODO: delete this comment? + const distanceGeometry = new THREE.IcosahedronGeometry( 1, 2 ); + const distanceMaterial = new THREE.MeshBasicMaterial( { color: hexColor, fog: false, wireframe: true, opacity: 0.1, transparent: true } ); - getPoint( t, optionalTarget ) { + this.lightSphere = new THREE.Mesh( bulbGeometry, bulbMaterial ); + this.lightDistance = new THREE.Mesh( distanceGeometry, distanceMaterial ); - const point = optionalTarget || new Vector2(); + const d = light.distance; - const twoPi = Math.PI * 2; - let deltaAngle = this.aEndAngle - this.aStartAngle; - const samePoints = Math.abs( deltaAngle ) < Number.EPSILON; + if ( d === 0.0 ) { - // ensures that deltaAngle is 0 .. 2 PI - while ( deltaAngle < 0 ) deltaAngle += twoPi; - while ( deltaAngle > twoPi ) deltaAngle -= twoPi; + this.lightDistance.visible = false; - if ( deltaAngle < Number.EPSILON ) { + } else { - if ( samePoints ) { + this.lightDistance.scale.set( d, d, d ); - deltaAngle = 0; + } - } else { + this.add( this.lightDistance ); + */ - deltaAngle = twoPi; + } - } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose() { - } + this.geometry.dispose(); + this.material.dispose(); - if ( this.aClockwise === true && ! samePoints ) { + } - if ( deltaAngle === twoPi ) { + /** + * Updates the helper to match the position of the + * light being visualized. + */ + update() { - deltaAngle = - twoPi; + this.light.updateWorldMatrix( true, false ); - } else { + if ( this.color !== undefined ) { - deltaAngle = deltaAngle - twoPi; + this.material.color.set( this.color ); - } + } else { + + this.material.color.copy( this.light.color ); } - const angle = this.aStartAngle + t * deltaAngle; - let x = this.aX + this.xRadius * Math.cos( angle ); - let y = this.aY + this.yRadius * Math.sin( angle ); + /* + const d = this.light.distance; - if ( this.aRotation !== 0 ) { + if ( d === 0.0 ) { - const cos = Math.cos( this.aRotation ); - const sin = Math.sin( this.aRotation ); + this.lightDistance.visible = false; - const tx = x - this.aX; - const ty = y - this.aY; + } else { - // Rotate the point about the center of the ellipse. - x = tx * cos - ty * sin + this.aX; - y = tx * sin + ty * cos + this.aY; + this.lightDistance.visible = true; + this.lightDistance.scale.set( d, d, d ); } - - return point.set( x, y ); + */ } - copy( source ) { +} - super.copy( source ); +const _vector$1 = /*@__PURE__*/ new Vector3(); +const _color1 = /*@__PURE__*/ new Color(); +const _color2 = /*@__PURE__*/ new Color(); - this.aX = source.aX; - this.aY = source.aY; +/** + * Creates a visual aid consisting of a spherical mesh for a + * given {@link HemisphereLight}. + * + * ```js + * const light = new THREE.HemisphereLight( 0xffffbb, 0x080820, 1 ); + * const helper = new THREE.HemisphereLightHelper( light, 5 ); + * scene.add( helper ); + * ``` + * + * @augments Object3D + */ +class HemisphereLightHelper extends Object3D { - this.xRadius = source.xRadius; - this.yRadius = source.yRadius; + /** + * Constructs a new hemisphere light helper. + * + * @param {HemisphereLight} light - The light to be visualized. + * @param {number} [size=1] - The size of the mesh used to visualize the light. + * @param {number|Color|string} [color] - The helper's color. If not set, the helper will take + * the color of the light. + */ + constructor( light, size, color ) { - this.aStartAngle = source.aStartAngle; - this.aEndAngle = source.aEndAngle; + super(); - this.aClockwise = source.aClockwise; + /** + * The light being visualized. + * + * @type {HemisphereLight} + */ + this.light = light; - this.aRotation = source.aRotation; + this.matrix = light.matrixWorld; + this.matrixAutoUpdate = false; - return this; + /** + * The color parameter passed in the constructor. + * If not set, the helper will take the color of the light. + * + * @type {number|Color|string} + */ + this.color = color; - } + this.type = 'HemisphereLightHelper'; - toJSON() { + const geometry = new OctahedronGeometry( size ); + geometry.rotateY( Math.PI * 0.5 ); - const data = super.toJSON(); + this.material = new MeshBasicMaterial( { wireframe: true, fog: false, toneMapped: false } ); + if ( this.color === undefined ) this.material.vertexColors = true; - data.aX = this.aX; - data.aY = this.aY; + const position = geometry.getAttribute( 'position' ); + const colors = new Float32Array( position.count * 3 ); - data.xRadius = this.xRadius; - data.yRadius = this.yRadius; + geometry.setAttribute( 'color', new BufferAttribute( colors, 3 ) ); - data.aStartAngle = this.aStartAngle; - data.aEndAngle = this.aEndAngle; + this.add( new Mesh( geometry, this.material ) ); - data.aClockwise = this.aClockwise; + this.update(); - data.aRotation = this.aRotation; + } - return data; + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose() { + + this.children[ 0 ].geometry.dispose(); + this.children[ 0 ].material.dispose(); } - fromJSON( json ) { + /** + * Updates the helper to match the position and direction of the + * light being visualized. + */ + update() { - super.fromJSON( json ); + const mesh = this.children[ 0 ]; - this.aX = json.aX; - this.aY = json.aY; + if ( this.color !== undefined ) { - this.xRadius = json.xRadius; - this.yRadius = json.yRadius; + this.material.color.set( this.color ); - this.aStartAngle = json.aStartAngle; - this.aEndAngle = json.aEndAngle; + } else { - this.aClockwise = json.aClockwise; + const colors = mesh.geometry.getAttribute( 'color' ); - this.aRotation = json.aRotation; + _color1.copy( this.light.color ); + _color2.copy( this.light.groundColor ); - return this; + for ( let i = 0, l = colors.count; i < l; i ++ ) { - } + const color = ( i < ( l / 2 ) ) ? _color1 : _color2; -} + colors.setXYZ( i, color.r, color.g, color.b ); -class ArcCurve extends EllipseCurve { + } - constructor( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) { + colors.needsUpdate = true; - super( aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise ); + } - this.isArcCurve = true; + this.light.updateWorldMatrix( true, false ); - this.type = 'ArcCurve'; + mesh.lookAt( _vector$1.setFromMatrixPosition( this.light.matrixWorld ).negate() ); } } /** - * Centripetal CatmullRom Curve - which is useful for avoiding - * cusps and self-intersections in non-uniform catmull rom curves. - * http://www.cemyuksel.com/research/catmullrom_param/catmullrom.pdf + * The helper is an object to define grids. Grids are two-dimensional + * arrays of lines. + * + * ```js + * const size = 10; + * const divisions = 10; * - * curve.type accepts centripetal(default), chordal and catmullrom - * curve.tension is used for catmullrom which defaults to 0.5 + * const gridHelper = new THREE.GridHelper( size, divisions ); + * scene.add( gridHelper ); + * ``` + * + * @augments LineSegments */ +class GridHelper extends LineSegments { + /** + * Constructs a new grid helper. + * + * @param {number} [size=10] - The size of the grid. + * @param {number} [divisions=10] - The number of divisions across the grid. + * @param {number|Color|string} [color1=0x444444] - The color of the center line. + * @param {number|Color|string} [color2=0x888888] - The color of the lines of the grid. + */ + constructor( size = 10, divisions = 10, color1 = 0x444444, color2 = 0x888888 ) { -/* -Based on an optimized c++ solution in - - http://stackoverflow.com/questions/9489736/catmull-rom-curve-with-no-cusps-and-no-self-intersections/ - - http://ideone.com/NoEbVM + color1 = new Color( color1 ); + color2 = new Color( color2 ); -This CubicPoly class could be used for reusing some variables and calculations, -but for three.js curve use, it could be possible inlined and flatten into a single function call -which can be placed in CurveUtils. -*/ + const center = divisions / 2; + const step = size / divisions; + const halfSize = size / 2; -function CubicPoly() { + const vertices = [], colors = []; - let c0 = 0, c1 = 0, c2 = 0, c3 = 0; + for ( let i = 0, j = 0, k = - halfSize; i <= divisions; i ++, k += step ) { - /* - * Compute coefficients for a cubic polynomial - * p(s) = c0 + c1*s + c2*s^2 + c3*s^3 - * such that - * p(0) = x0, p(1) = x1 - * and - * p'(0) = t0, p'(1) = t1. - */ - function init( x0, x1, t0, t1 ) { + vertices.push( - halfSize, 0, k, halfSize, 0, k ); + vertices.push( k, 0, - halfSize, k, 0, halfSize ); - c0 = x0; - c1 = t0; - c2 = - 3 * x0 + 3 * x1 - 2 * t0 - t1; - c3 = 2 * x0 - 2 * x1 + t0 + t1; + const color = i === center ? color1 : color2; + + color.toArray( colors, j ); j += 3; + color.toArray( colors, j ); j += 3; + color.toArray( colors, j ); j += 3; + color.toArray( colors, j ); j += 3; + + } + + const geometry = new BufferGeometry(); + geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + geometry.setAttribute( 'color', new Float32BufferAttribute( colors, 3 ) ); + + const material = new LineBasicMaterial( { vertexColors: true, toneMapped: false } ); + + super( geometry, material ); + + this.type = 'GridHelper'; } - return { + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose() { - initCatmullRom: function ( x0, x1, x2, x3, tension ) { + this.geometry.dispose(); + this.material.dispose(); - init( x1, x2, tension * ( x2 - x0 ), tension * ( x3 - x1 ) ); + } - }, +} - initNonuniformCatmullRom: function ( x0, x1, x2, x3, dt0, dt1, dt2 ) { +/** + * This helper is an object to define polar grids. Grids are + * two-dimensional arrays of lines. + * + * ```js + * const radius = 10; + * const sectors = 16; + * const rings = 8; + * const divisions = 64; + * + * const helper = new THREE.PolarGridHelper( radius, sectors, rings, divisions ); + * scene.add( helper ); + * ``` + * + * @augments LineSegments + */ +class PolarGridHelper extends LineSegments { - // compute tangents when parameterized in [t1,t2] - let t1 = ( x1 - x0 ) / dt0 - ( x2 - x0 ) / ( dt0 + dt1 ) + ( x2 - x1 ) / dt1; - let t2 = ( x2 - x1 ) / dt1 - ( x3 - x1 ) / ( dt1 + dt2 ) + ( x3 - x2 ) / dt2; + /** + * Constructs a new polar grid helper. + * + * @param {number} [radius=10] - The radius of the polar grid. This can be any positive number. + * @param {number} [sectors=16] - The number of sectors the grid will be divided into. This can be any positive integer. + * @param {number} [rings=16] - The number of rings. This can be any positive integer. + * @param {number} [divisions=64] - The number of line segments used for each circle. This can be any positive integer. + * @param {number|Color|string} [color1=0x444444] - The first color used for grid elements. + * @param {number|Color|string} [color2=0x888888] - The second color used for grid elements. + */ + constructor( radius = 10, sectors = 16, rings = 8, divisions = 64, color1 = 0x444444, color2 = 0x888888 ) { - // rescale tangents for parametrization in [0,1] - t1 *= dt1; - t2 *= dt1; + color1 = new Color( color1 ); + color2 = new Color( color2 ); - init( x1, x2, t1, t2 ); + const vertices = []; + const colors = []; - }, + // create the sectors - calc: function ( t ) { + if ( sectors > 1 ) { - const t2 = t * t; - const t3 = t2 * t; - return c0 + c1 * t + c2 * t2 + c3 * t3; + for ( let i = 0; i < sectors; i ++ ) { - } + const v = ( i / sectors ) * ( Math.PI * 2 ); - }; + const x = Math.sin( v ) * radius; + const z = Math.cos( v ) * radius; -} + vertices.push( 0, 0, 0 ); + vertices.push( x, 0, z ); -// + const color = ( i & 1 ) ? color1 : color2; -const tmp = /*@__PURE__*/ new Vector3(); -const px = /*@__PURE__*/ new CubicPoly(); -const py = /*@__PURE__*/ new CubicPoly(); -const pz = /*@__PURE__*/ new CubicPoly(); + colors.push( color.r, color.g, color.b ); + colors.push( color.r, color.g, color.b ); -class CatmullRomCurve3 extends Curve { + } - constructor( points = [], closed = false, curveType = 'centripetal', tension = 0.5 ) { + } - super(); + // create the rings - this.isCatmullRomCurve3 = true; + for ( let i = 0; i < rings; i ++ ) { - this.type = 'CatmullRomCurve3'; + const color = ( i & 1 ) ? color1 : color2; - this.points = points; - this.closed = closed; - this.curveType = curveType; - this.tension = tension; + const r = radius - ( radius / rings * i ); - } + for ( let j = 0; j < divisions; j ++ ) { - getPoint( t, optionalTarget = new Vector3() ) { + // first vertex - const point = optionalTarget; + let v = ( j / divisions ) * ( Math.PI * 2 ); - const points = this.points; - const l = points.length; + let x = Math.sin( v ) * r; + let z = Math.cos( v ) * r; - const p = ( l - ( this.closed ? 0 : 1 ) ) * t; - let intPoint = Math.floor( p ); - let weight = p - intPoint; + vertices.push( x, 0, z ); + colors.push( color.r, color.g, color.b ); - if ( this.closed ) { + // second vertex - intPoint += intPoint > 0 ? 0 : ( Math.floor( Math.abs( intPoint ) / l ) + 1 ) * l; + v = ( ( j + 1 ) / divisions ) * ( Math.PI * 2 ); - } else if ( weight === 0 && intPoint === l - 1 ) { + x = Math.sin( v ) * r; + z = Math.cos( v ) * r; - intPoint = l - 2; - weight = 1; + vertices.push( x, 0, z ); + colors.push( color.r, color.g, color.b ); + + } } - let p0, p3; // 4 points (p1 & p2 defined below) + const geometry = new BufferGeometry(); + geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + geometry.setAttribute( 'color', new Float32BufferAttribute( colors, 3 ) ); - if ( this.closed || intPoint > 0 ) { + const material = new LineBasicMaterial( { vertexColors: true, toneMapped: false } ); - p0 = points[ ( intPoint - 1 ) % l ]; + super( geometry, material ); + + this.type = 'PolarGridHelper'; + + } + + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose() { - } else { + this.geometry.dispose(); + this.material.dispose(); - // extrapolate first point - tmp.subVectors( points[ 0 ], points[ 1 ] ).add( points[ 0 ] ); - p0 = tmp; + } - } +} - const p1 = points[ intPoint % l ]; - const p2 = points[ ( intPoint + 1 ) % l ]; +const _v1 = /*@__PURE__*/ new Vector3(); +const _v2 = /*@__PURE__*/ new Vector3(); +const _v3 = /*@__PURE__*/ new Vector3(); - if ( this.closed || intPoint + 2 < l ) { +/** + * Helper object to assist with visualizing a {@link DirectionalLight}'s + * effect on the scene. This consists of plane and a line representing the + * light's position and direction. + * + * ```js + * const light = new THREE.DirectionalLight( 0xFFFFFF ); + * scene.add( light ); + * + * const helper = new THREE.DirectionalLightHelper( light, 5 ); + * scene.add( helper ); + * ``` + * + * @augments Object3D + */ +class DirectionalLightHelper extends Object3D { - p3 = points[ ( intPoint + 2 ) % l ]; + /** + * Constructs a new directional light helper. + * + * @param {DirectionalLight} light - The light to be visualized. + * @param {number} [size=1] - The dimensions of the plane. + * @param {number|Color|string} [color] - The helper's color. If not set, the helper will take + * the color of the light. + */ + constructor( light, size, color ) { - } else { + super(); - // extrapolate last point - tmp.subVectors( points[ l - 1 ], points[ l - 2 ] ).add( points[ l - 1 ] ); - p3 = tmp; + /** + * The light being visualized. + * + * @type {DirectionalLight} + */ + this.light = light; - } + this.matrix = light.matrixWorld; + this.matrixAutoUpdate = false; - if ( this.curveType === 'centripetal' || this.curveType === 'chordal' ) { + /** + * The color parameter passed in the constructor. + * If not set, the helper will take the color of the light. + * + * @type {number|Color|string} + */ + this.color = color; - // init Centripetal / Chordal Catmull-Rom - const pow = this.curveType === 'chordal' ? 0.5 : 0.25; - let dt0 = Math.pow( p0.distanceToSquared( p1 ), pow ); - let dt1 = Math.pow( p1.distanceToSquared( p2 ), pow ); - let dt2 = Math.pow( p2.distanceToSquared( p3 ), pow ); + this.type = 'DirectionalLightHelper'; - // safety check for repeated points - if ( dt1 < 1e-4 ) dt1 = 1.0; - if ( dt0 < 1e-4 ) dt0 = dt1; - if ( dt2 < 1e-4 ) dt2 = dt1; + if ( size === undefined ) size = 1; - px.initNonuniformCatmullRom( p0.x, p1.x, p2.x, p3.x, dt0, dt1, dt2 ); - py.initNonuniformCatmullRom( p0.y, p1.y, p2.y, p3.y, dt0, dt1, dt2 ); - pz.initNonuniformCatmullRom( p0.z, p1.z, p2.z, p3.z, dt0, dt1, dt2 ); + let geometry = new BufferGeometry(); + geometry.setAttribute( 'position', new Float32BufferAttribute( [ + - size, size, 0, + size, size, 0, + size, - size, 0, + - size, - size, 0, + - size, size, 0 + ], 3 ) ); - } else if ( this.curveType === 'catmullrom' ) { + const material = new LineBasicMaterial( { fog: false, toneMapped: false } ); - px.initCatmullRom( p0.x, p1.x, p2.x, p3.x, this.tension ); - py.initCatmullRom( p0.y, p1.y, p2.y, p3.y, this.tension ); - pz.initCatmullRom( p0.z, p1.z, p2.z, p3.z, this.tension ); + /** + * Contains the line showing the location of the directional light. + * + * @type {Line} + */ + this.lightPlane = new Line( geometry, material ); + this.add( this.lightPlane ); - } + geometry = new BufferGeometry(); + geometry.setAttribute( 'position', new Float32BufferAttribute( [ 0, 0, 0, 0, 0, 1 ], 3 ) ); - point.set( - px.calc( weight ), - py.calc( weight ), - pz.calc( weight ) - ); + /** + * Represents the target line of the directional light. + * + * @type {Line} + */ + this.targetLine = new Line( geometry, material ); + this.add( this.targetLine ); - return point; + this.update(); } - copy( source ) { + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose() { - super.copy( source ); + this.lightPlane.geometry.dispose(); + this.lightPlane.material.dispose(); + this.targetLine.geometry.dispose(); + this.targetLine.material.dispose(); - this.points = []; + } - for ( let i = 0, l = source.points.length; i < l; i ++ ) { + /** + * Updates the helper to match the position and direction of the + * light being visualized. + */ + update() { - const point = source.points[ i ]; + this.light.updateWorldMatrix( true, false ); + this.light.target.updateWorldMatrix( true, false ); - this.points.push( point.clone() ); + _v1.setFromMatrixPosition( this.light.matrixWorld ); + _v2.setFromMatrixPosition( this.light.target.matrixWorld ); + _v3.subVectors( _v2, _v1 ); - } + this.lightPlane.lookAt( _v2 ); - this.closed = source.closed; - this.curveType = source.curveType; - this.tension = source.tension; + if ( this.color !== undefined ) { - return this; + this.lightPlane.material.color.set( this.color ); + this.targetLine.material.color.set( this.color ); - } + } else { - toJSON() { + this.lightPlane.material.color.copy( this.light.color ); + this.targetLine.material.color.copy( this.light.color ); - const data = super.toJSON(); + } - data.points = []; + this.targetLine.lookAt( _v2 ); + this.targetLine.scale.z = _v3.length(); - for ( let i = 0, l = this.points.length; i < l; i ++ ) { + } - const point = this.points[ i ]; - data.points.push( point.toArray() ); +} - } +const _vector = /*@__PURE__*/ new Vector3(); +const _camera = /*@__PURE__*/ new Camera(); - data.closed = this.closed; - data.curveType = this.curveType; - data.tension = this.tension; +/** + * This helps with visualizing what a camera contains in its frustum. It + * visualizes the frustum of a camera using a line segments. + * + * Based on frustum visualization in [lightgl.js shadowmap example]{@link https://github.com/evanw/lightgl.js/blob/master/tests/shadowmap.html}. + * + * `CameraHelper` must be a child of the scene. + * + * ```js + * const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); + * const helper = new THREE.CameraHelper( camera ); + * scene.add( helper ); + * ``` + * + * @augments LineSegments + */ +class CameraHelper extends LineSegments { - return data; + /** + * Constructs a new arrow helper. + * + * @param {Camera} camera - The camera to visualize. + */ + constructor( camera ) { - } + const geometry = new BufferGeometry(); + const material = new LineBasicMaterial( { color: 0xffffff, vertexColors: true, toneMapped: false } ); - fromJSON( json ) { + const vertices = []; + const colors = []; - super.fromJSON( json ); + const pointMap = {}; - this.points = []; + // near - for ( let i = 0, l = json.points.length; i < l; i ++ ) { + addLine( 'n1', 'n2' ); + addLine( 'n2', 'n4' ); + addLine( 'n4', 'n3' ); + addLine( 'n3', 'n1' ); - const point = json.points[ i ]; - this.points.push( new Vector3().fromArray( point ) ); + // far - } + addLine( 'f1', 'f2' ); + addLine( 'f2', 'f4' ); + addLine( 'f4', 'f3' ); + addLine( 'f3', 'f1' ); - this.closed = json.closed; - this.curveType = json.curveType; - this.tension = json.tension; + // sides - return this; + addLine( 'n1', 'f1' ); + addLine( 'n2', 'f2' ); + addLine( 'n3', 'f3' ); + addLine( 'n4', 'f4' ); - } + // cone -} + addLine( 'p', 'n1' ); + addLine( 'p', 'n2' ); + addLine( 'p', 'n3' ); + addLine( 'p', 'n4' ); -/** - * Bezier Curves formulas obtained from - * https://en.wikipedia.org/wiki/B%C3%A9zier_curve - */ + // up -function CatmullRom( t, p0, p1, p2, p3 ) { + addLine( 'u1', 'u2' ); + addLine( 'u2', 'u3' ); + addLine( 'u3', 'u1' ); - const v0 = ( p2 - p0 ) * 0.5; - const v1 = ( p3 - p1 ) * 0.5; - const t2 = t * t; - const t3 = t * t2; - return ( 2 * p1 - 2 * p2 + v0 + v1 ) * t3 + ( - 3 * p1 + 3 * p2 - 2 * v0 - v1 ) * t2 + v0 * t + p1; + // target -} + addLine( 'c', 't' ); + addLine( 'p', 'c' ); -// + // cross -function QuadraticBezierP0( t, p ) { + addLine( 'cn1', 'cn2' ); + addLine( 'cn3', 'cn4' ); - const k = 1 - t; - return k * k * p; + addLine( 'cf1', 'cf2' ); + addLine( 'cf3', 'cf4' ); -} + function addLine( a, b ) { -function QuadraticBezierP1( t, p ) { + addPoint( a ); + addPoint( b ); - return 2 * ( 1 - t ) * t * p; + } -} + function addPoint( id ) { -function QuadraticBezierP2( t, p ) { + vertices.push( 0, 0, 0 ); + colors.push( 0, 0, 0 ); - return t * t * p; + if ( pointMap[ id ] === undefined ) { -} + pointMap[ id ] = []; -function QuadraticBezier( t, p0, p1, p2 ) { + } - return QuadraticBezierP0( t, p0 ) + QuadraticBezierP1( t, p1 ) + - QuadraticBezierP2( t, p2 ); + pointMap[ id ].push( ( vertices.length / 3 ) - 1 ); -} + } -// + geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + geometry.setAttribute( 'color', new Float32BufferAttribute( colors, 3 ) ); -function CubicBezierP0( t, p ) { + super( geometry, material ); - const k = 1 - t; - return k * k * k * p; + this.type = 'CameraHelper'; -} + /** + * The camera being visualized. + * + * @type {Camera} + */ + this.camera = camera; + if ( this.camera.updateProjectionMatrix ) this.camera.updateProjectionMatrix(); -function CubicBezierP1( t, p ) { + this.matrix = camera.matrixWorld; + this.matrixAutoUpdate = false; - const k = 1 - t; - return 3 * k * k * t * p; + /** + * This contains the points used to visualize the camera. + * + * @type {Object>} + */ + this.pointMap = pointMap; -} + this.update(); -function CubicBezierP2( t, p ) { + // colors - return 3 * ( 1 - t ) * t * t * p; + const colorFrustum = new Color( 0xffaa00 ); + const colorCone = new Color( 0xff0000 ); + const colorUp = new Color( 0x00aaff ); + const colorTarget = new Color( 0xffffff ); + const colorCross = new Color( 0x333333 ); -} + this.setColors( colorFrustum, colorCone, colorUp, colorTarget, colorCross ); -function CubicBezierP3( t, p ) { + } - return t * t * t * p; + /** + * Defines the colors of the helper. + * + * @param {Color} frustum - The frustum line color. + * @param {Color} cone - The cone line color. + * @param {Color} up - The up line color. + * @param {Color} target - The target line color. + * @param {Color} cross - The cross line color. + */ + setColors( frustum, cone, up, target, cross ) { -} + const geometry = this.geometry; -function CubicBezier( t, p0, p1, p2, p3 ) { + const colorAttribute = geometry.getAttribute( 'color' ); - return CubicBezierP0( t, p0 ) + CubicBezierP1( t, p1 ) + CubicBezierP2( t, p2 ) + - CubicBezierP3( t, p3 ); + // near -} + colorAttribute.setXYZ( 0, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 1, frustum.r, frustum.g, frustum.b ); // n1, n2 + colorAttribute.setXYZ( 2, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 3, frustum.r, frustum.g, frustum.b ); // n2, n4 + colorAttribute.setXYZ( 4, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 5, frustum.r, frustum.g, frustum.b ); // n4, n3 + colorAttribute.setXYZ( 6, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 7, frustum.r, frustum.g, frustum.b ); // n3, n1 -class CubicBezierCurve extends Curve { + // far - constructor( v0 = new Vector2(), v1 = new Vector2(), v2 = new Vector2(), v3 = new Vector2() ) { + colorAttribute.setXYZ( 8, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 9, frustum.r, frustum.g, frustum.b ); // f1, f2 + colorAttribute.setXYZ( 10, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 11, frustum.r, frustum.g, frustum.b ); // f2, f4 + colorAttribute.setXYZ( 12, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 13, frustum.r, frustum.g, frustum.b ); // f4, f3 + colorAttribute.setXYZ( 14, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 15, frustum.r, frustum.g, frustum.b ); // f3, f1 - super(); + // sides - this.isCubicBezierCurve = true; + colorAttribute.setXYZ( 16, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 17, frustum.r, frustum.g, frustum.b ); // n1, f1 + colorAttribute.setXYZ( 18, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 19, frustum.r, frustum.g, frustum.b ); // n2, f2 + colorAttribute.setXYZ( 20, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 21, frustum.r, frustum.g, frustum.b ); // n3, f3 + colorAttribute.setXYZ( 22, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 23, frustum.r, frustum.g, frustum.b ); // n4, f4 - this.type = 'CubicBezierCurve'; + // cone - this.v0 = v0; - this.v1 = v1; - this.v2 = v2; - this.v3 = v3; + colorAttribute.setXYZ( 24, cone.r, cone.g, cone.b ); colorAttribute.setXYZ( 25, cone.r, cone.g, cone.b ); // p, n1 + colorAttribute.setXYZ( 26, cone.r, cone.g, cone.b ); colorAttribute.setXYZ( 27, cone.r, cone.g, cone.b ); // p, n2 + colorAttribute.setXYZ( 28, cone.r, cone.g, cone.b ); colorAttribute.setXYZ( 29, cone.r, cone.g, cone.b ); // p, n3 + colorAttribute.setXYZ( 30, cone.r, cone.g, cone.b ); colorAttribute.setXYZ( 31, cone.r, cone.g, cone.b ); // p, n4 - } + // up - getPoint( t, optionalTarget = new Vector2() ) { + colorAttribute.setXYZ( 32, up.r, up.g, up.b ); colorAttribute.setXYZ( 33, up.r, up.g, up.b ); // u1, u2 + colorAttribute.setXYZ( 34, up.r, up.g, up.b ); colorAttribute.setXYZ( 35, up.r, up.g, up.b ); // u2, u3 + colorAttribute.setXYZ( 36, up.r, up.g, up.b ); colorAttribute.setXYZ( 37, up.r, up.g, up.b ); // u3, u1 - const point = optionalTarget; + // target - const v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3; + colorAttribute.setXYZ( 38, target.r, target.g, target.b ); colorAttribute.setXYZ( 39, target.r, target.g, target.b ); // c, t + colorAttribute.setXYZ( 40, cross.r, cross.g, cross.b ); colorAttribute.setXYZ( 41, cross.r, cross.g, cross.b ); // p, c - point.set( - CubicBezier( t, v0.x, v1.x, v2.x, v3.x ), - CubicBezier( t, v0.y, v1.y, v2.y, v3.y ) - ); + // cross - return point; + colorAttribute.setXYZ( 42, cross.r, cross.g, cross.b ); colorAttribute.setXYZ( 43, cross.r, cross.g, cross.b ); // cn1, cn2 + colorAttribute.setXYZ( 44, cross.r, cross.g, cross.b ); colorAttribute.setXYZ( 45, cross.r, cross.g, cross.b ); // cn3, cn4 - } + colorAttribute.setXYZ( 46, cross.r, cross.g, cross.b ); colorAttribute.setXYZ( 47, cross.r, cross.g, cross.b ); // cf1, cf2 + colorAttribute.setXYZ( 48, cross.r, cross.g, cross.b ); colorAttribute.setXYZ( 49, cross.r, cross.g, cross.b ); // cf3, cf4 - copy( source ) { + colorAttribute.needsUpdate = true; - super.copy( source ); + } - this.v0.copy( source.v0 ); - this.v1.copy( source.v1 ); - this.v2.copy( source.v2 ); - this.v3.copy( source.v3 ); + /** + * Updates the helper based on the projection matrix of the camera. + */ + update() { - return this; + const geometry = this.geometry; + const pointMap = this.pointMap; - } + const w = 1, h = 1; - toJSON() { + // we need just camera projection matrix inverse + // world matrix must be identity - const data = super.toJSON(); + _camera.projectionMatrixInverse.copy( this.camera.projectionMatrixInverse ); - data.v0 = this.v0.toArray(); - data.v1 = this.v1.toArray(); - data.v2 = this.v2.toArray(); - data.v3 = this.v3.toArray(); + // Adjust z values based on coordinate system + const nearZ = this.camera.coordinateSystem === WebGLCoordinateSystem ? -1 : 0; - return data; + // center / target + setPoint( 'c', pointMap, geometry, _camera, 0, 0, nearZ ); + setPoint( 't', pointMap, geometry, _camera, 0, 0, 1 ); - } + // near - fromJSON( json ) { + setPoint( 'n1', pointMap, geometry, _camera, - w, - h, nearZ ); + setPoint( 'n2', pointMap, geometry, _camera, w, - h, nearZ ); + setPoint( 'n3', pointMap, geometry, _camera, - w, h, nearZ ); + setPoint( 'n4', pointMap, geometry, _camera, w, h, nearZ ); - super.fromJSON( json ); + // far - this.v0.fromArray( json.v0 ); - this.v1.fromArray( json.v1 ); - this.v2.fromArray( json.v2 ); - this.v3.fromArray( json.v3 ); + setPoint( 'f1', pointMap, geometry, _camera, - w, - h, 1 ); + setPoint( 'f2', pointMap, geometry, _camera, w, - h, 1 ); + setPoint( 'f3', pointMap, geometry, _camera, - w, h, 1 ); + setPoint( 'f4', pointMap, geometry, _camera, w, h, 1 ); - return this; + // up - } + setPoint( 'u1', pointMap, geometry, _camera, w * 0.7, h * 1.1, nearZ ); + setPoint( 'u2', pointMap, geometry, _camera, - w * 0.7, h * 1.1, nearZ ); + setPoint( 'u3', pointMap, geometry, _camera, 0, h * 2, nearZ ); -} + // cross -class CubicBezierCurve3 extends Curve { + setPoint( 'cf1', pointMap, geometry, _camera, - w, 0, 1 ); + setPoint( 'cf2', pointMap, geometry, _camera, w, 0, 1 ); + setPoint( 'cf3', pointMap, geometry, _camera, 0, - h, 1 ); + setPoint( 'cf4', pointMap, geometry, _camera, 0, h, 1 ); - constructor( v0 = new Vector3(), v1 = new Vector3(), v2 = new Vector3(), v3 = new Vector3() ) { + setPoint( 'cn1', pointMap, geometry, _camera, - w, 0, nearZ ); + setPoint( 'cn2', pointMap, geometry, _camera, w, 0, nearZ ); + setPoint( 'cn3', pointMap, geometry, _camera, 0, - h, nearZ ); + setPoint( 'cn4', pointMap, geometry, _camera, 0, h, nearZ ); - super(); + geometry.getAttribute( 'position' ).needsUpdate = true; - this.isCubicBezierCurve3 = true; + } - this.type = 'CubicBezierCurve3'; + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose() { - this.v0 = v0; - this.v1 = v1; - this.v2 = v2; - this.v3 = v3; + this.geometry.dispose(); + this.material.dispose(); } - getPoint( t, optionalTarget = new Vector3() ) { +} - const point = optionalTarget; - const v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3; +function setPoint( point, pointMap, geometry, camera, x, y, z ) { - point.set( - CubicBezier( t, v0.x, v1.x, v2.x, v3.x ), - CubicBezier( t, v0.y, v1.y, v2.y, v3.y ), - CubicBezier( t, v0.z, v1.z, v2.z, v3.z ) - ); + _vector.set( x, y, z ).unproject( camera ); - return point; + const points = pointMap[ point ]; - } + if ( points !== undefined ) { - copy( source ) { + const position = geometry.getAttribute( 'position' ); - super.copy( source ); + for ( let i = 0, l = points.length; i < l; i ++ ) { - this.v0.copy( source.v0 ); - this.v1.copy( source.v1 ); - this.v2.copy( source.v2 ); - this.v3.copy( source.v3 ); + position.setXYZ( points[ i ], _vector.x, _vector.y, _vector.z ); - return this; + } } - toJSON() { +} - const data = super.toJSON(); +const _box = /*@__PURE__*/ new Box3(); - data.v0 = this.v0.toArray(); - data.v1 = this.v1.toArray(); - data.v2 = this.v2.toArray(); - data.v3 = this.v3.toArray(); +/** + * Helper object to graphically show the world-axis-aligned bounding box + * around an object. The actual bounding box is handled with {@link Box3}, + * this is just a visual helper for debugging. It can be automatically + * resized with {@link BoxHelper#update} when the object it's created from + * is transformed. Note that the object must have a geometry for this to work, + * so it won't work with sprites. + * + * ```js + * const sphere = new THREE.SphereGeometry(); + * const object = new THREE.Mesh( sphere, new THREE.MeshBasicMaterial( 0xff0000 ) ); + * const box = new THREE.BoxHelper( object, 0xffff00 ); + * scene.add( box ); + * ``` + * + * @augments LineSegments + */ +class BoxHelper extends LineSegments { - return data; + /** + * Constructs a new box helper. + * + * @param {Object3D} [object] - The 3D object to show the world-axis-aligned bounding box. + * @param {number|Color|string} [color=0xffff00] - The box's color. + */ + constructor( object, color = 0xffff00 ) { - } + const indices = new Uint16Array( [ 0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7 ] ); + const positions = new Float32Array( 8 * 3 ); - fromJSON( json ) { + const geometry = new BufferGeometry(); + geometry.setIndex( new BufferAttribute( indices, 1 ) ); + geometry.setAttribute( 'position', new BufferAttribute( positions, 3 ) ); - super.fromJSON( json ); + super( geometry, new LineBasicMaterial( { color: color, toneMapped: false } ) ); - this.v0.fromArray( json.v0 ); - this.v1.fromArray( json.v1 ); - this.v2.fromArray( json.v2 ); - this.v3.fromArray( json.v3 ); + /** + * The 3D object being visualized. + * + * @type {Object3D} + */ + this.object = object; + this.type = 'BoxHelper'; - return this; + this.matrixAutoUpdate = false; + + this.update(); } -} + /** + * Updates the helper's geometry to match the dimensions of the object, + * including any children. + */ + update() { -class LineCurve extends Curve { + if ( this.object !== undefined ) { - constructor( v1 = new Vector2(), v2 = new Vector2() ) { + _box.setFromObject( this.object ); - super(); + } - this.isLineCurve = true; + if ( _box.isEmpty() ) return; - this.type = 'LineCurve'; + const min = _box.min; + const max = _box.max; - this.v1 = v1; - this.v2 = v2; + /* + 5____4 + 1/___0/| + | 6__|_7 + 2/___3/ - } + 0: max.x, max.y, max.z + 1: min.x, max.y, max.z + 2: min.x, min.y, max.z + 3: max.x, min.y, max.z + 4: max.x, max.y, min.z + 5: min.x, max.y, min.z + 6: min.x, min.y, min.z + 7: max.x, min.y, min.z + */ - getPoint( t, optionalTarget = new Vector2() ) { + const position = this.geometry.attributes.position; + const array = position.array; - const point = optionalTarget; + array[ 0 ] = max.x; array[ 1 ] = max.y; array[ 2 ] = max.z; + array[ 3 ] = min.x; array[ 4 ] = max.y; array[ 5 ] = max.z; + array[ 6 ] = min.x; array[ 7 ] = min.y; array[ 8 ] = max.z; + array[ 9 ] = max.x; array[ 10 ] = min.y; array[ 11 ] = max.z; + array[ 12 ] = max.x; array[ 13 ] = max.y; array[ 14 ] = min.z; + array[ 15 ] = min.x; array[ 16 ] = max.y; array[ 17 ] = min.z; + array[ 18 ] = min.x; array[ 19 ] = min.y; array[ 20 ] = min.z; + array[ 21 ] = max.x; array[ 22 ] = min.y; array[ 23 ] = min.z; - if ( t === 1 ) { + position.needsUpdate = true; - point.copy( this.v2 ); + this.geometry.computeBoundingSphere(); - } else { + } - point.copy( this.v2 ).sub( this.v1 ); - point.multiplyScalar( t ).add( this.v1 ); + /** + * Updates the wireframe box for the passed object. + * + * @param {Object3D} object - The 3D object to create the helper for. + * @return {BoxHelper} A reference to this instance. + */ + setFromObject( object ) { - } + this.object = object; + this.update(); - return point; + return this; } - // Line curve is linear, so we can overwrite default getPointAt - getPointAt( u, optionalTarget ) { - - return this.getPoint( u, optionalTarget ); + copy( source, recursive ) { - } + super.copy( source, recursive ); - getTangent( t, optionalTarget = new Vector2() ) { + this.object = source.object; - return optionalTarget.subVectors( this.v2, this.v1 ).normalize(); + return this; } - getTangentAt( u, optionalTarget ) { + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose() { - return this.getTangent( u, optionalTarget ); + this.geometry.dispose(); + this.material.dispose(); } - copy( source ) { - - super.copy( source ); - - this.v1.copy( source.v1 ); - this.v2.copy( source.v2 ); +} - return this; +/** + * A helper object to visualize an instance of {@link Box3}. + * + * ```js + * const box = new THREE.Box3(); + * box.setFromCenterAndSize( new THREE.Vector3( 1, 1, 1 ), new THREE.Vector3( 2, 1, 3 ) ); + * + * const helper = new THREE.Box3Helper( box, 0xffff00 ); + * scene.add( helper ) + * ``` + * + * @augments LineSegments + */ +class Box3Helper extends LineSegments { - } + /** + * Constructs a new box3 helper. + * + * @param {Box3} box - The box to visualize. + * @param {number|Color|string} [color=0xffff00] - The box's color. + */ + constructor( box, color = 0xffff00 ) { - toJSON() { + const indices = new Uint16Array( [ 0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7 ] ); - const data = super.toJSON(); + const positions = [ 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, 1, 1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1 ]; - data.v1 = this.v1.toArray(); - data.v2 = this.v2.toArray(); + const geometry = new BufferGeometry(); - return data; + geometry.setIndex( new BufferAttribute( indices, 1 ) ); - } + geometry.setAttribute( 'position', new Float32BufferAttribute( positions, 3 ) ); - fromJSON( json ) { + super( geometry, new LineBasicMaterial( { color: color, toneMapped: false } ) ); - super.fromJSON( json ); + /** + * The box being visualized. + * + * @type {Box3} + */ + this.box = box; - this.v1.fromArray( json.v1 ); - this.v2.fromArray( json.v2 ); + this.type = 'Box3Helper'; - return this; + this.geometry.computeBoundingSphere(); } -} + updateMatrixWorld( force ) { -class LineCurve3 extends Curve { + const box = this.box; - constructor( v1 = new Vector3(), v2 = new Vector3() ) { + if ( box.isEmpty() ) return; - super(); + box.getCenter( this.position ); - this.isLineCurve3 = true; + box.getSize( this.scale ); - this.type = 'LineCurve3'; + this.scale.multiplyScalar( 0.5 ); - this.v1 = v1; - this.v2 = v2; + super.updateMatrixWorld( force ); } - getPoint( t, optionalTarget = new Vector3() ) { - - const point = optionalTarget; - if ( t === 1 ) { + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose() { - point.copy( this.v2 ); + this.geometry.dispose(); + this.material.dispose(); - } else { + } - point.copy( this.v2 ).sub( this.v1 ); - point.multiplyScalar( t ).add( this.v1 ); +} - } +/** + * A helper object to visualize an instance of {@link Plane}. + * + * ```js + * const plane = new THREE.Plane( new THREE.Vector3( 1, 1, 0.2 ), 3 ); + * const helper = new THREE.PlaneHelper( plane, 1, 0xffff00 ); + * scene.add( helper ); + * ``` + * + * @augments Line + */ +class PlaneHelper extends Line { - return point; + /** + * Constructs a new plane helper. + * + * @param {Plane} plane - The plane to be visualized. + * @param {number} [size=1] - The side length of plane helper. + * @param {number|Color|string} [hex=0xffff00] - The helper's color. + */ + constructor( plane, size = 1, hex = 0xffff00 ) { - } - // Line curve is linear, so we can overwrite default getPointAt - getPointAt( u, optionalTarget ) { + const color = hex; - return this.getPoint( u, optionalTarget ); + const positions = [ 1, -1, 0, -1, 1, 0, -1, -1, 0, 1, 1, 0, -1, 1, 0, -1, -1, 0, 1, -1, 0, 1, 1, 0 ]; - } + const geometry = new BufferGeometry(); + geometry.setAttribute( 'position', new Float32BufferAttribute( positions, 3 ) ); + geometry.computeBoundingSphere(); - getTangent( t, optionalTarget = new Vector3() ) { + super( geometry, new LineBasicMaterial( { color: color, toneMapped: false } ) ); - return optionalTarget.subVectors( this.v2, this.v1 ).normalize(); + this.type = 'PlaneHelper'; - } + /** + * The plane being visualized. + * + * @type {Plane} + */ + this.plane = plane; - getTangentAt( u, optionalTarget ) { + /** + * The side length of plane helper. + * + * @type {number} + * @default 1 + */ + this.size = size; - return this.getTangent( u, optionalTarget ); + const positions2 = [ 1, 1, 0, -1, 1, 0, -1, -1, 0, 1, 1, 0, -1, -1, 0, 1, -1, 0 ]; - } + const geometry2 = new BufferGeometry(); + geometry2.setAttribute( 'position', new Float32BufferAttribute( positions2, 3 ) ); + geometry2.computeBoundingSphere(); - copy( source ) { + this.add( new Mesh( geometry2, new MeshBasicMaterial( { color: color, opacity: 0.2, transparent: true, depthWrite: false, toneMapped: false } ) ) ); - super.copy( source ); + } - this.v1.copy( source.v1 ); - this.v2.copy( source.v2 ); + updateMatrixWorld( force ) { - return this; + this.position.set( 0, 0, 0 ); - } - toJSON() { + this.scale.set( 0.5 * this.size, 0.5 * this.size, 1 ); - const data = super.toJSON(); + this.lookAt( this.plane.normal ); - data.v1 = this.v1.toArray(); - data.v2 = this.v2.toArray(); + this.translateZ( - this.plane.constant ); - return data; + super.updateMatrixWorld( force ); } - fromJSON( json ) { - - super.fromJSON( json ); - this.v1.fromArray( json.v1 ); - this.v2.fromArray( json.v2 ); + /** + * Updates the helper to match the position and direction of the + * light being visualized. + */ + dispose() { - return this; + this.geometry.dispose(); + this.material.dispose(); + this.children[ 0 ].geometry.dispose(); + this.children[ 0 ].material.dispose(); } } -class QuadraticBezierCurve extends Curve { +const _axis = /*@__PURE__*/ new Vector3(); +let _lineGeometry, _coneGeometry; - constructor( v0 = new Vector2(), v1 = new Vector2(), v2 = new Vector2() ) { +/** + * An 3D arrow object for visualizing directions. + * + * ```js + * const dir = new THREE.Vector3( 1, 2, 0 ); + * + * //normalize the direction vector (convert to vector of length 1) + * dir.normalize(); + * + * const origin = new THREE.Vector3( 0, 0, 0 ); + * const length = 1; + * const hex = 0xffff00; + * + * const arrowHelper = new THREE.ArrowHelper( dir, origin, length, hex ); + * scene.add( arrowHelper ); + * ``` + * + * @augments Object3D + */ +class ArrowHelper extends Object3D { + + /** + * Constructs a new arrow helper. + * + * @param {Vector3} [dir=(0, 0, 1)] - The (normalized) direction vector. + * @param {Vector3} [origin=(0, 0, 0)] - Point at which the arrow starts. + * @param {number} [length=1] - Length of the arrow in world units. + * @param {(number|Color|string)} [color=0xffff00] - Color of the arrow. + * @param {number} [headLength=length*0.2] - The length of the head of the arrow. + * @param {number} [headWidth=headLength*0.2] - The width of the head of the arrow. + */ + constructor( dir = new Vector3( 0, 0, 1 ), origin = new Vector3( 0, 0, 0 ), length = 1, color = 0xffff00, headLength = length * 0.2, headWidth = headLength * 0.2 ) { super(); - this.isQuadraticBezierCurve = true; + this.type = 'ArrowHelper'; - this.type = 'QuadraticBezierCurve'; + if ( _lineGeometry === undefined ) { - this.v0 = v0; - this.v1 = v1; - this.v2 = v2; + _lineGeometry = new BufferGeometry(); + _lineGeometry.setAttribute( 'position', new Float32BufferAttribute( [ 0, 0, 0, 0, 1, 0 ], 3 ) ); - } + _coneGeometry = new ConeGeometry( 0.5, 1, 5, 1 ); + _coneGeometry.translate( 0, -0.5, 0 ); - getPoint( t, optionalTarget = new Vector2() ) { + } - const point = optionalTarget; + this.position.copy( origin ); - const v0 = this.v0, v1 = this.v1, v2 = this.v2; + /** + * The line part of the arrow helper. + * + * @type {Line} + */ + this.line = new Line( _lineGeometry, new LineBasicMaterial( { color: color, toneMapped: false } ) ); + this.line.matrixAutoUpdate = false; + this.add( this.line ); - point.set( - QuadraticBezier( t, v0.x, v1.x, v2.x ), - QuadraticBezier( t, v0.y, v1.y, v2.y ) - ); + /** + * The cone part of the arrow helper. + * + * @type {Mesh} + */ + this.cone = new Mesh( _coneGeometry, new MeshBasicMaterial( { color: color, toneMapped: false } ) ); + this.cone.matrixAutoUpdate = false; + this.add( this.cone ); - return point; + this.setDirection( dir ); + this.setLength( length, headLength, headWidth ); } - copy( source ) { + /** + * Sets the direction of the helper. + * + * @param {Vector3} dir - The normalized direction vector. + */ + setDirection( dir ) { - super.copy( source ); + // dir is assumed to be normalized - this.v0.copy( source.v0 ); - this.v1.copy( source.v1 ); - this.v2.copy( source.v2 ); + if ( dir.y > 0.99999 ) { - return this; + this.quaternion.set( 0, 0, 0, 1 ); - } + } else if ( dir.y < -0.99999 ) { - toJSON() { + this.quaternion.set( 1, 0, 0, 0 ); - const data = super.toJSON(); + } else { - data.v0 = this.v0.toArray(); - data.v1 = this.v1.toArray(); - data.v2 = this.v2.toArray(); + _axis.set( dir.z, 0, - dir.x ).normalize(); - return data; + const radians = Math.acos( dir.y ); - } + this.quaternion.setFromAxisAngle( _axis, radians ); - fromJSON( json ) { + } - super.fromJSON( json ); + } - this.v0.fromArray( json.v0 ); - this.v1.fromArray( json.v1 ); - this.v2.fromArray( json.v2 ); + /** + * Sets the length of the helper. + * + * @param {number} length - Length of the arrow in world units. + * @param {number} [headLength=length*0.2] - The length of the head of the arrow. + * @param {number} [headWidth=headLength*0.2] - The width of the head of the arrow. + */ + setLength( length, headLength = length * 0.2, headWidth = headLength * 0.2 ) { - return this; + this.line.scale.set( 1, Math.max( 0.0001, length - headLength ), 1 ); // see #17458 + this.line.updateMatrix(); + + this.cone.scale.set( headWidth, headLength, headWidth ); + this.cone.position.y = length; + this.cone.updateMatrix(); } -} + /** + * Sets the color of the helper. + * + * @param {number|Color|string} color - The color to set. + */ + setColor( color ) { -class QuadraticBezierCurve3 extends Curve { + this.line.material.color.set( color ); + this.cone.material.color.set( color ); - constructor( v0 = new Vector3(), v1 = new Vector3(), v2 = new Vector3() ) { + } - super(); + copy( source ) { - this.isQuadraticBezierCurve3 = true; + super.copy( source, false ); - this.type = 'QuadraticBezierCurve3'; + this.line.copy( source.line ); + this.cone.copy( source.cone ); - this.v0 = v0; - this.v1 = v1; - this.v2 = v2; + return this; } - getPoint( t, optionalTarget = new Vector3() ) { + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose() { - const point = optionalTarget; + this.line.geometry.dispose(); + this.line.material.dispose(); + this.cone.geometry.dispose(); + this.cone.material.dispose(); - const v0 = this.v0, v1 = this.v1, v2 = this.v2; + } - point.set( - QuadraticBezier( t, v0.x, v1.x, v2.x ), - QuadraticBezier( t, v0.y, v1.y, v2.y ), - QuadraticBezier( t, v0.z, v1.z, v2.z ) - ); +} - return point; +/** + * An axis object to visualize the 3 axes in a simple way. + * The X axis is red. The Y axis is green. The Z axis is blue. + * + * ```js + * const axesHelper = new THREE.AxesHelper( 5 ); + * scene.add( axesHelper ); + * ``` + * + * @augments LineSegments + */ +class AxesHelper extends LineSegments { - } + /** + * Constructs a new axes helper. + * + * @param {number} [size=1] - Size of the lines representing the axes. + */ + constructor( size = 1 ) { - copy( source ) { + const vertices = [ + 0, 0, 0, size, 0, 0, + 0, 0, 0, 0, size, 0, + 0, 0, 0, 0, 0, size + ]; - super.copy( source ); + const colors = [ + 1, 0, 0, 1, 0.6, 0, + 0, 1, 0, 0.6, 1, 0, + 0, 0, 1, 0, 0.6, 1 + ]; - this.v0.copy( source.v0 ); - this.v1.copy( source.v1 ); - this.v2.copy( source.v2 ); + const geometry = new BufferGeometry(); + geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + geometry.setAttribute( 'color', new Float32BufferAttribute( colors, 3 ) ); - return this; + const material = new LineBasicMaterial( { vertexColors: true, toneMapped: false } ); - } + super( geometry, material ); - toJSON() { + this.type = 'AxesHelper'; - const data = super.toJSON(); + } - data.v0 = this.v0.toArray(); - data.v1 = this.v1.toArray(); - data.v2 = this.v2.toArray(); + /** + * Defines the colors of the axes helper. + * + * @param {number|Color|string} xAxisColor - The color for the x axis. + * @param {number|Color|string} yAxisColor - The color for the y axis. + * @param {number|Color|string} zAxisColor - The color for the z axis. + * @return {AxesHelper} A reference to this axes helper. + */ + setColors( xAxisColor, yAxisColor, zAxisColor ) { - return data; + const color = new Color(); + const array = this.geometry.attributes.color.array; - } + color.set( xAxisColor ); + color.toArray( array, 0 ); + color.toArray( array, 3 ); - fromJSON( json ) { + color.set( yAxisColor ); + color.toArray( array, 6 ); + color.toArray( array, 9 ); - super.fromJSON( json ); + color.set( zAxisColor ); + color.toArray( array, 12 ); + color.toArray( array, 15 ); - this.v0.fromArray( json.v0 ); - this.v1.fromArray( json.v1 ); - this.v2.fromArray( json.v2 ); + this.geometry.attributes.color.needsUpdate = true; return this; } -} - -class SplineCurve extends Curve { + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose() { - constructor( points = [] ) { + this.geometry.dispose(); + this.material.dispose(); - super(); + } - this.isSplineCurve = true; +} - this.type = 'SplineCurve'; +/** + * This class is used to convert a series of paths to an array of + * shapes. It is specifically used in context of fonts and SVG. + */ +class ShapePath { - this.points = points; + /** + * Constructs a new shape path. + */ + constructor() { - } + this.type = 'ShapePath'; - getPoint( t, optionalTarget = new Vector2() ) { + /** + * The color of the shape. + * + * @type {Color} + */ + this.color = new Color(); - const point = optionalTarget; + /** + * The paths that have been generated for this shape. + * + * @type {Array} + * @default null + */ + this.subPaths = []; - const points = this.points; - const p = ( points.length - 1 ) * t; + /** + * The current path that is being generated. + * + * @type {?Path} + * @default null + */ + this.currentPath = null; - const intPoint = Math.floor( p ); - const weight = p - intPoint; + } - const p0 = points[ intPoint === 0 ? intPoint : intPoint - 1 ]; - const p1 = points[ intPoint ]; - const p2 = points[ intPoint > points.length - 2 ? points.length - 1 : intPoint + 1 ]; - const p3 = points[ intPoint > points.length - 3 ? points.length - 1 : intPoint + 2 ]; + /** + * Creates a new path and moves it current point to the given one. + * + * @param {number} x - The x coordinate. + * @param {number} y - The y coordinate. + * @return {ShapePath} A reference to this shape path. + */ + moveTo( x, y ) { - point.set( - CatmullRom( weight, p0.x, p1.x, p2.x, p3.x ), - CatmullRom( weight, p0.y, p1.y, p2.y, p3.y ) - ); + this.currentPath = new Path(); + this.subPaths.push( this.currentPath ); + this.currentPath.moveTo( x, y ); - return point; + return this; } - copy( source ) { - - super.copy( source ); + /** + * Adds an instance of {@link LineCurve} to the path by connecting + * the current point with the given one. + * + * @param {number} x - The x coordinate of the end point. + * @param {number} y - The y coordinate of the end point. + * @return {ShapePath} A reference to this shape path. + */ + lineTo( x, y ) { - this.points = []; + this.currentPath.lineTo( x, y ); - for ( let i = 0, l = source.points.length; i < l; i ++ ) { + return this; - const point = source.points[ i ]; + } - this.points.push( point.clone() ); + /** + * Adds an instance of {@link QuadraticBezierCurve} to the path by connecting + * the current point with the given one. + * + * @param {number} aCPx - The x coordinate of the control point. + * @param {number} aCPy - The y coordinate of the control point. + * @param {number} aX - The x coordinate of the end point. + * @param {number} aY - The y coordinate of the end point. + * @return {ShapePath} A reference to this shape path. + */ + quadraticCurveTo( aCPx, aCPy, aX, aY ) { - } + this.currentPath.quadraticCurveTo( aCPx, aCPy, aX, aY ); return this; } - toJSON() { + /** + * Adds an instance of {@link CubicBezierCurve} to the path by connecting + * the current point with the given one. + * + * @param {number} aCP1x - The x coordinate of the first control point. + * @param {number} aCP1y - The y coordinate of the first control point. + * @param {number} aCP2x - The x coordinate of the second control point. + * @param {number} aCP2y - The y coordinate of the second control point. + * @param {number} aX - The x coordinate of the end point. + * @param {number} aY - The y coordinate of the end point. + * @return {ShapePath} A reference to this shape path. + */ + bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) { - const data = super.toJSON(); + this.currentPath.bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ); - data.points = []; + return this; - for ( let i = 0, l = this.points.length; i < l; i ++ ) { + } - const point = this.points[ i ]; - data.points.push( point.toArray() ); + /** + * Adds an instance of {@link SplineCurve} to the path by connecting + * the current point with the given list of points. + * + * @param {Array} pts - An array of points in 2D space. + * @return {ShapePath} A reference to this shape path. + */ + splineThru( pts ) { - } + this.currentPath.splineThru( pts ); - return data; + return this; } - fromJSON( json ) { - - super.fromJSON( json ); - - this.points = []; + /** + * Converts the paths into an array of shapes. + * + * @param {boolean} isCCW - By default solid shapes are defined clockwise (CW) and holes are defined counterclockwise (CCW). + * If this flag is set to `true`, then those are flipped. + * @return {Array} An array of shapes. + */ + toShapes( isCCW ) { - for ( let i = 0, l = json.points.length; i < l; i ++ ) { + function toShapesNoHoles( inSubpaths ) { - const point = json.points[ i ]; - this.points.push( new Vector2().fromArray( point ) ); + const shapes = []; - } + for ( let i = 0, l = inSubpaths.length; i < l; i ++ ) { - return this; + const tmpPath = inSubpaths[ i ]; - } + const tmpShape = new Shape(); + tmpShape.curves = tmpPath.curves; -} + shapes.push( tmpShape ); -var Curves = /*#__PURE__*/Object.freeze({ - __proto__: null, - ArcCurve: ArcCurve, - CatmullRomCurve3: CatmullRomCurve3, - CubicBezierCurve: CubicBezierCurve, - CubicBezierCurve3: CubicBezierCurve3, - EllipseCurve: EllipseCurve, - LineCurve: LineCurve, - LineCurve3: LineCurve3, - QuadraticBezierCurve: QuadraticBezierCurve, - QuadraticBezierCurve3: QuadraticBezierCurve3, - SplineCurve: SplineCurve -}); + } -/************************************************************** - * Curved Path - a curve path is simply a array of connected - * curves, but retains the api of a curve - **************************************************************/ + return shapes; -class CurvePath extends Curve { + } - constructor() { + function isPointInsidePolygon( inPt, inPolygon ) { - super(); + const polyLen = inPolygon.length; - this.type = 'CurvePath'; + // inPt on polygon contour => immediate success or + // toggling of inside/outside at every single! intersection point of an edge + // with the horizontal line through inPt, left of inPt + // not counting lowerY endpoints of edges and whole edges on that line + let inside = false; + for ( let p = polyLen - 1, q = 0; q < polyLen; p = q ++ ) { - this.curves = []; - this.autoClose = false; // Automatically closes the path + let edgeLowPt = inPolygon[ p ]; + let edgeHighPt = inPolygon[ q ]; - } + let edgeDx = edgeHighPt.x - edgeLowPt.x; + let edgeDy = edgeHighPt.y - edgeLowPt.y; - add( curve ) { + if ( Math.abs( edgeDy ) > Number.EPSILON ) { - this.curves.push( curve ); + // not parallel + if ( edgeDy < 0 ) { - } + edgeLowPt = inPolygon[ q ]; edgeDx = - edgeDx; + edgeHighPt = inPolygon[ p ]; edgeDy = - edgeDy; - closePath() { + } - // Add a line curve if start and end of lines are not connected - const startPoint = this.curves[ 0 ].getPoint( 0 ); - const endPoint = this.curves[ this.curves.length - 1 ].getPoint( 1 ); + if ( ( inPt.y < edgeLowPt.y ) || ( inPt.y > edgeHighPt.y ) ) continue; - if ( ! startPoint.equals( endPoint ) ) { + if ( inPt.y === edgeLowPt.y ) { - this.curves.push( new LineCurve( endPoint, startPoint ) ); + if ( inPt.x === edgeLowPt.x ) return true; // inPt is on contour ? + // continue; // no intersection or edgeLowPt => doesn't count !!! - } + } else { - } + const perpEdge = edgeDy * ( inPt.x - edgeLowPt.x ) - edgeDx * ( inPt.y - edgeLowPt.y ); + if ( perpEdge === 0 ) return true; // inPt is on contour ? + if ( perpEdge < 0 ) continue; + inside = ! inside; // true intersection left of inPt - // To get accurate point with reference to - // entire path distance at time t, - // following has to be done: + } - // 1. Length of each sub path have to be known - // 2. Locate and identify type of curve - // 3. Get t for the curve - // 4. Return curve.getPointAt(t') + } else { - getPoint( t, optionalTarget ) { + // parallel or collinear + if ( inPt.y !== edgeLowPt.y ) continue; // parallel + // edge lies on the same horizontal line as inPt + if ( ( ( edgeHighPt.x <= inPt.x ) && ( inPt.x <= edgeLowPt.x ) ) || + ( ( edgeLowPt.x <= inPt.x ) && ( inPt.x <= edgeHighPt.x ) ) ) return true; // inPt: Point on contour ! + // continue; - const d = t * this.getLength(); - const curveLengths = this.getCurveLengths(); - let i = 0; + } - // To think about boundaries points. + } - while ( i < curveLengths.length ) { + return inside; - if ( curveLengths[ i ] >= d ) { + } - const diff = curveLengths[ i ] - d; - const curve = this.curves[ i ]; + const isClockWise = ShapeUtils.isClockWise; - const segmentLength = curve.getLength(); - const u = segmentLength === 0 ? 0 : 1 - diff / segmentLength; + const subPaths = this.subPaths; + if ( subPaths.length === 0 ) return []; - return curve.getPointAt( u, optionalTarget ); + let solid, tmpPath, tmpShape; + const shapes = []; - } + if ( subPaths.length === 1 ) { - i ++; + tmpPath = subPaths[ 0 ]; + tmpShape = new Shape(); + tmpShape.curves = tmpPath.curves; + shapes.push( tmpShape ); + return shapes; } - return null; + let holesFirst = ! isClockWise( subPaths[ 0 ].getPoints() ); + holesFirst = isCCW ? ! holesFirst : holesFirst; - // loop where sum != 0, sum > d , sum+1 probably all Shapes with wrong orientation + if ( ! newShapes[ 0 ] ) return toShapesNoHoles( subPaths ); - sums += this.curves[ i ].getLength(); - lengths.push( sums ); - } + if ( newShapes.length > 1 ) { - this.cacheLengths = lengths; + let ambiguous = false; + let toChange = 0; - return lengths; + for ( let sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) { - } + betterShapeHoles[ sIdx ] = []; - getSpacedPoints( divisions = 40 ) { + } - const points = []; + for ( let sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) { - for ( let i = 0; i <= divisions; i ++ ) { + const sho = newShapeHoles[ sIdx ]; - points.push( this.getPoint( i / divisions ) ); + for ( let hIdx = 0; hIdx < sho.length; hIdx ++ ) { - } + const ho = sho[ hIdx ]; + let hole_unassigned = true; - if ( this.autoClose ) { + for ( let s2Idx = 0; s2Idx < newShapes.length; s2Idx ++ ) { - points.push( points[ 0 ] ); + if ( isPointInsidePolygon( ho.p, newShapes[ s2Idx ].p ) ) { - } + if ( sIdx !== s2Idx ) toChange ++; - return points; + if ( hole_unassigned ) { - } + hole_unassigned = false; + betterShapeHoles[ s2Idx ].push( ho ); - getPoints( divisions = 12 ) { + } else { - const points = []; - let last; + ambiguous = true; - for ( let i = 0, curves = this.curves; i < curves.length; i ++ ) { + } - const curve = curves[ i ]; - const resolution = curve.isEllipseCurve ? divisions * 2 - : ( curve.isLineCurve || curve.isLineCurve3 ) ? 1 - : curve.isSplineCurve ? divisions * curve.points.length - : divisions; + } - const pts = curve.getPoints( resolution ); + } - for ( let j = 0; j < pts.length; j ++ ) { + if ( hole_unassigned ) { - const point = pts[ j ]; + betterShapeHoles[ sIdx ].push( ho ); - if ( last && last.equals( point ) ) continue; // ensures no consecutive points are duplicates + } - points.push( point ); - last = point; + } } - } + if ( toChange > 0 && ambiguous === false ) { - if ( this.autoClose && points.length > 1 && ! points[ points.length - 1 ].equals( points[ 0 ] ) ) { + newShapeHoles = betterShapeHoles; - points.push( points[ 0 ] ); + } } - return points; - - } - - copy( source ) { + let tmpHoles; - super.copy( source ); + for ( let i = 0, il = newShapes.length; i < il; i ++ ) { - this.curves = []; + tmpShape = newShapes[ i ].s; + shapes.push( tmpShape ); + tmpHoles = newShapeHoles[ i ]; - for ( let i = 0, l = source.curves.length; i < l; i ++ ) { + for ( let j = 0, jl = tmpHoles.length; j < jl; j ++ ) { - const curve = source.curves[ i ]; + tmpShape.holes.push( tmpHoles[ j ].h ); - this.curves.push( curve.clone() ); + } } - this.autoClose = source.autoClose; + //console.log("shape", shapes); - return this; + return shapes; } - toJSON() { - - const data = super.toJSON(); +} - data.autoClose = this.autoClose; - data.curves = []; +/** + * Abstract base class for controls. + * + * @abstract + * @augments EventDispatcher + */ +class Controls extends EventDispatcher { - for ( let i = 0, l = this.curves.length; i < l; i ++ ) { + /** + * Constructs a new controls instance. + * + * @param {Object3D} object - The object that is managed by the controls. + * @param {?HTMLDOMElement} domElement - The HTML element used for event listeners. + */ + constructor( object, domElement = null ) { - const curve = this.curves[ i ]; - data.curves.push( curve.toJSON() ); + super(); - } + /** + * The object that is managed by the controls. + * + * @type {Object3D} + */ + this.object = object; - return data; + /** + * The HTML element used for event listeners. + * + * @type {?HTMLDOMElement} + * @default null + */ + this.domElement = domElement; - } + /** + * Whether the controls responds to user input or not. + * + * @type {boolean} + * @default true + */ + this.enabled = true; - fromJSON( json ) { + /** + * The internal state of the controls. + * + * @type {number} + * @default -1 + */ + this.state = -1; - super.fromJSON( json ); + /** + * This object defines the keyboard input of the controls. + * + * @type {Object} + */ + this.keys = {}; - this.autoClose = json.autoClose; - this.curves = []; + /** + * This object defines what type of actions are assigned to the available mouse buttons. + * It depends on the control implementation what kind of mouse buttons and actions are supported. + * + * @type {{LEFT: ?number, MIDDLE: ?number, RIGHT: ?number}} + */ + this.mouseButtons = { LEFT: null, MIDDLE: null, RIGHT: null }; - for ( let i = 0, l = json.curves.length; i < l; i ++ ) { + /** + * This object defines what type of actions are assigned to what kind of touch interaction. + * It depends on the control implementation what kind of touch interaction and actions are supported. + * + * @type {{ONE: ?number, TWO: ?number}} + */ + this.touches = { ONE: null, TWO: null }; - const curve = json.curves[ i ]; - this.curves.push( new Curves[ curve.type ]().fromJSON( curve ) ); + } - } + /** + * Connects the controls to the DOM. This method has so called "side effects" since + * it adds the module's event listeners to the DOM. + * + * @param {HTMLDOMElement} element - The DOM element to connect to. + */ + connect( element ) { - return this; + if ( element === undefined ) { - } + console.warn( 'THREE.Controls: connect() now requires an element.' ); // @deprecated, the warning can be removed with r185 + return; -} + } -class Path extends CurvePath { + if ( this.domElement !== null ) this.disconnect(); - constructor( points ) { + this.domElement = element; - super(); + } - this.type = 'Path'; + /** + * Disconnects the controls from the DOM. + */ + disconnect() {} - this.currentPoint = new Vector2(); + /** + * Call this method if you no longer want use to the controls. It frees all internal + * resources and removes all event listeners. + */ + dispose() {} - if ( points ) { + /** + * Controls should implement this method if they have to update their internal state + * per simulation step. + * + * @param {number} [delta] - The time delta in seconds. + */ + update( /* delta */ ) {} - this.setFromPoints( points ); +} - } +/** + * Scales the texture as large as possible within its surface without cropping + * or stretching the texture. The method preserves the original aspect ratio of + * the texture. Akin to CSS `object-fit: contain` + * + * @param {Texture} texture - The texture. + * @param {number} aspect - The texture's aspect ratio. + * @return {Texture} The updated texture. + */ +function contain( texture, aspect ) { - } + const imageAspect = ( texture.image && texture.image.width ) ? texture.image.width / texture.image.height : 1; - setFromPoints( points ) { + if ( imageAspect > aspect ) { - this.moveTo( points[ 0 ].x, points[ 0 ].y ); + texture.repeat.x = 1; + texture.repeat.y = imageAspect / aspect; - for ( let i = 1, l = points.length; i < l; i ++ ) { + texture.offset.x = 0; + texture.offset.y = ( 1 - texture.repeat.y ) / 2; - this.lineTo( points[ i ].x, points[ i ].y ); + } else { - } + texture.repeat.x = aspect / imageAspect; + texture.repeat.y = 1; - return this; + texture.offset.x = ( 1 - texture.repeat.x ) / 2; + texture.offset.y = 0; } - moveTo( x, y ) { + return texture; - this.currentPoint.set( x, y ); // TODO consider referencing vectors instead of copying? +} - return this; +/** + * Scales the texture to the smallest possible size to fill the surface, leaving + * no empty space. The method preserves the original aspect ratio of the texture. + * Akin to CSS `object-fit: cover`. + * + * @param {Texture} texture - The texture. + * @param {number} aspect - The texture's aspect ratio. + * @return {Texture} The updated texture. + */ +function cover( texture, aspect ) { - } + const imageAspect = ( texture.image && texture.image.width ) ? texture.image.width / texture.image.height : 1; - lineTo( x, y ) { + if ( imageAspect > aspect ) { - const curve = new LineCurve( this.currentPoint.clone(), new Vector2( x, y ) ); - this.curves.push( curve ); + texture.repeat.x = aspect / imageAspect; + texture.repeat.y = 1; - this.currentPoint.set( x, y ); + texture.offset.x = ( 1 - texture.repeat.x ) / 2; + texture.offset.y = 0; - return this; + } else { + + texture.repeat.x = 1; + texture.repeat.y = imageAspect / aspect; + + texture.offset.x = 0; + texture.offset.y = ( 1 - texture.repeat.y ) / 2; } - quadraticCurveTo( aCPx, aCPy, aX, aY ) { + return texture; - const curve = new QuadraticBezierCurve( - this.currentPoint.clone(), - new Vector2( aCPx, aCPy ), - new Vector2( aX, aY ) - ); +} - this.curves.push( curve ); +/** + * Configures the texture to the default transformation. Akin to CSS `object-fit: fill`. + * + * @param {Texture} texture - The texture. + * @return {Texture} The updated texture. + */ +function fill( texture ) { - this.currentPoint.set( aX, aY ); + texture.repeat.x = 1; + texture.repeat.y = 1; - return this; + texture.offset.x = 0; + texture.offset.y = 0; - } + return texture; - bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) { +} - const curve = new CubicBezierCurve( - this.currentPoint.clone(), - new Vector2( aCP1x, aCP1y ), - new Vector2( aCP2x, aCP2y ), - new Vector2( aX, aY ) - ); +/** + * Determines how many bytes must be used to represent the texture. + * + * @param {number} width - The width of the texture. + * @param {number} height - The height of the texture. + * @param {number} format - The texture's format. + * @param {number} type - The texture's type. + * @return {number} The byte length. + */ +function getByteLength( width, height, format, type ) { + + const typeByteLength = getTextureTypeByteLength( type ); + + switch ( format ) { + + // https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glTexImage2D.xhtml + case AlphaFormat: + return width * height; + case RedFormat: + return ( ( width * height ) / typeByteLength.components ) * typeByteLength.byteLength; + case RedIntegerFormat: + return ( ( width * height ) / typeByteLength.components ) * typeByteLength.byteLength; + case RGFormat: + return ( ( width * height * 2 ) / typeByteLength.components ) * typeByteLength.byteLength; + case RGIntegerFormat: + return ( ( width * height * 2 ) / typeByteLength.components ) * typeByteLength.byteLength; + case RGBFormat: + return ( ( width * height * 3 ) / typeByteLength.components ) * typeByteLength.byteLength; + case RGBAFormat: + return ( ( width * height * 4 ) / typeByteLength.components ) * typeByteLength.byteLength; + case RGBAIntegerFormat: + return ( ( width * height * 4 ) / typeByteLength.components ) * typeByteLength.byteLength; + + // https://registry.khronos.org/webgl/extensions/WEBGL_compressed_texture_s3tc_srgb/ + case RGB_S3TC_DXT1_Format: + case RGBA_S3TC_DXT1_Format: + return Math.floor( ( width + 3 ) / 4 ) * Math.floor( ( height + 3 ) / 4 ) * 8; + case RGBA_S3TC_DXT3_Format: + case RGBA_S3TC_DXT5_Format: + return Math.floor( ( width + 3 ) / 4 ) * Math.floor( ( height + 3 ) / 4 ) * 16; + + // https://registry.khronos.org/webgl/extensions/WEBGL_compressed_texture_pvrtc/ + case RGB_PVRTC_2BPPV1_Format: + case RGBA_PVRTC_2BPPV1_Format: + return ( Math.max( width, 16 ) * Math.max( height, 8 ) ) / 4; + case RGB_PVRTC_4BPPV1_Format: + case RGBA_PVRTC_4BPPV1_Format: + return ( Math.max( width, 8 ) * Math.max( height, 8 ) ) / 2; + + // https://registry.khronos.org/webgl/extensions/WEBGL_compressed_texture_etc/ + case RGB_ETC1_Format: + case RGB_ETC2_Format: + return Math.floor( ( width + 3 ) / 4 ) * Math.floor( ( height + 3 ) / 4 ) * 8; + case RGBA_ETC2_EAC_Format: + return Math.floor( ( width + 3 ) / 4 ) * Math.floor( ( height + 3 ) / 4 ) * 16; + + // https://registry.khronos.org/webgl/extensions/WEBGL_compressed_texture_astc/ + case RGBA_ASTC_4x4_Format: + return Math.floor( ( width + 3 ) / 4 ) * Math.floor( ( height + 3 ) / 4 ) * 16; + case RGBA_ASTC_5x4_Format: + return Math.floor( ( width + 4 ) / 5 ) * Math.floor( ( height + 3 ) / 4 ) * 16; + case RGBA_ASTC_5x5_Format: + return Math.floor( ( width + 4 ) / 5 ) * Math.floor( ( height + 4 ) / 5 ) * 16; + case RGBA_ASTC_6x5_Format: + return Math.floor( ( width + 5 ) / 6 ) * Math.floor( ( height + 4 ) / 5 ) * 16; + case RGBA_ASTC_6x6_Format: + return Math.floor( ( width + 5 ) / 6 ) * Math.floor( ( height + 5 ) / 6 ) * 16; + case RGBA_ASTC_8x5_Format: + return Math.floor( ( width + 7 ) / 8 ) * Math.floor( ( height + 4 ) / 5 ) * 16; + case RGBA_ASTC_8x6_Format: + return Math.floor( ( width + 7 ) / 8 ) * Math.floor( ( height + 5 ) / 6 ) * 16; + case RGBA_ASTC_8x8_Format: + return Math.floor( ( width + 7 ) / 8 ) * Math.floor( ( height + 7 ) / 8 ) * 16; + case RGBA_ASTC_10x5_Format: + return Math.floor( ( width + 9 ) / 10 ) * Math.floor( ( height + 4 ) / 5 ) * 16; + case RGBA_ASTC_10x6_Format: + return Math.floor( ( width + 9 ) / 10 ) * Math.floor( ( height + 5 ) / 6 ) * 16; + case RGBA_ASTC_10x8_Format: + return Math.floor( ( width + 9 ) / 10 ) * Math.floor( ( height + 7 ) / 8 ) * 16; + case RGBA_ASTC_10x10_Format: + return Math.floor( ( width + 9 ) / 10 ) * Math.floor( ( height + 9 ) / 10 ) * 16; + case RGBA_ASTC_12x10_Format: + return Math.floor( ( width + 11 ) / 12 ) * Math.floor( ( height + 9 ) / 10 ) * 16; + case RGBA_ASTC_12x12_Format: + return Math.floor( ( width + 11 ) / 12 ) * Math.floor( ( height + 11 ) / 12 ) * 16; + + // https://registry.khronos.org/webgl/extensions/EXT_texture_compression_bptc/ + case RGBA_BPTC_Format: + case RGB_BPTC_SIGNED_Format: + case RGB_BPTC_UNSIGNED_Format: + return Math.ceil( width / 4 ) * Math.ceil( height / 4 ) * 16; + + // https://registry.khronos.org/webgl/extensions/EXT_texture_compression_rgtc/ + case RED_RGTC1_Format: + case SIGNED_RED_RGTC1_Format: + return Math.ceil( width / 4 ) * Math.ceil( height / 4 ) * 8; + case RED_GREEN_RGTC2_Format: + case SIGNED_RED_GREEN_RGTC2_Format: + return Math.ceil( width / 4 ) * Math.ceil( height / 4 ) * 16; + + } + + throw new Error( + `Unable to determine texture byte length for ${format} format.`, + ); - this.curves.push( curve ); +} - this.currentPoint.set( aX, aY ); +function getTextureTypeByteLength( type ) { - return this; + switch ( type ) { + + case UnsignedByteType: + case ByteType: + return { byteLength: 1, components: 1 }; + case UnsignedShortType: + case ShortType: + case HalfFloatType: + return { byteLength: 2, components: 1 }; + case UnsignedShort4444Type: + case UnsignedShort5551Type: + return { byteLength: 2, components: 4 }; + case UnsignedIntType: + case IntType: + case FloatType: + return { byteLength: 4, components: 1 }; + case UnsignedInt5999Type: + return { byteLength: 4, components: 3 }; } - splineThru( pts /*Array of Vector*/ ) { + throw new Error( `Unknown texture type ${type}.` ); - const npts = [ this.currentPoint.clone() ].concat( pts ); +} - const curve = new SplineCurve( npts ); - this.curves.push( curve ); +/** + * A class containing utility functions for textures. + * + * @hideconstructor + */ +class TextureUtils { - this.currentPoint.copy( pts[ pts.length - 1 ] ); + /** + * Scales the texture as large as possible within its surface without cropping + * or stretching the texture. The method preserves the original aspect ratio of + * the texture. Akin to CSS `object-fit: contain` + * + * @param {Texture} texture - The texture. + * @param {number} aspect - The texture's aspect ratio. + * @return {Texture} The updated texture. + */ + static contain( texture, aspect ) { - return this; + return contain( texture, aspect ); } - arc( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) { + /** + * Scales the texture to the smallest possible size to fill the surface, leaving + * no empty space. The method preserves the original aspect ratio of the texture. + * Akin to CSS `object-fit: cover`. + * + * @param {Texture} texture - The texture. + * @param {number} aspect - The texture's aspect ratio. + * @return {Texture} The updated texture. + */ + static cover( texture, aspect ) { - const x0 = this.currentPoint.x; - const y0 = this.currentPoint.y; + return cover( texture, aspect ); - this.absarc( aX + x0, aY + y0, aRadius, - aStartAngle, aEndAngle, aClockwise ); + } - return this; + /** + * Configures the texture to the default transformation. Akin to CSS `object-fit: fill`. + * + * @param {Texture} texture - The texture. + * @return {Texture} The updated texture. + */ + static fill( texture ) { - } + return fill( texture ); - absarc( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) { + } - this.absellipse( aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise ); + /** + * Determines how many bytes must be used to represent the texture. + * + * @param {number} width - The width of the texture. + * @param {number} height - The height of the texture. + * @param {number} format - The texture's format. + * @param {number} type - The texture's type. + * @return {number} The byte length. + */ + static getByteLength( width, height, format, type ) { - return this; + return getByteLength( width, height, format, type ); } - ellipse( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) { +} - const x0 = this.currentPoint.x; - const y0 = this.currentPoint.y; +if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) { - this.absellipse( aX + x0, aY + y0, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ); + __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'register', { detail: { + revision: REVISION, + } } ) ); - return this; +} - } +if ( typeof window !== 'undefined' ) { - absellipse( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) { + if ( window.__THREE__ ) { - const curve = new EllipseCurve( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ); + console.warn( 'WARNING: Multiple instances of Three.js being imported.' ); - if ( this.curves.length > 0 ) { + } else { - // if a previous curve is present, attempt to join - const firstPoint = curve.getPoint( 0 ); + window.__THREE__ = REVISION; - if ( ! firstPoint.equals( this.currentPoint ) ) { + } - this.lineTo( firstPoint.x, firstPoint.y ); +} - } +function WebGLAnimation() { - } + let context = null; + let isAnimating = false; + let animationLoop = null; + let requestId = null; - this.curves.push( curve ); + function onAnimationFrame( time, frame ) { - const lastPoint = curve.getPoint( 1 ); - this.currentPoint.copy( lastPoint ); + animationLoop( time, frame ); - return this; + requestId = context.requestAnimationFrame( onAnimationFrame ); } - copy( source ) { - - super.copy( source ); + return { - this.currentPoint.copy( source.currentPoint ); + start: function () { - return this; + if ( isAnimating === true ) return; + if ( animationLoop === null ) return; - } + requestId = context.requestAnimationFrame( onAnimationFrame ); - toJSON() { + isAnimating = true; - const data = super.toJSON(); + }, - data.currentPoint = this.currentPoint.toArray(); + stop: function () { - return data; + context.cancelAnimationFrame( requestId ); - } + isAnimating = false; - fromJSON( json ) { + }, - super.fromJSON( json ); + setAnimationLoop: function ( callback ) { - this.currentPoint.fromArray( json.currentPoint ); + animationLoop = callback; - return this; + }, - } + setContext: function ( value ) { -} + context = value; -class LatheGeometry extends BufferGeometry { + } - constructor( points = [ new Vector2( 0, - 0.5 ), new Vector2( 0.5, 0 ), new Vector2( 0, 0.5 ) ], segments = 12, phiStart = 0, phiLength = Math.PI * 2 ) { + }; - super(); +} - this.type = 'LatheGeometry'; +function WebGLAttributes( gl ) { - this.parameters = { - points: points, - segments: segments, - phiStart: phiStart, - phiLength: phiLength - }; + const buffers = new WeakMap(); - segments = Math.floor( segments ); + function createBuffer( attribute, bufferType ) { - // clamp phiLength so it's in range of [ 0, 2PI ] + const array = attribute.array; + const usage = attribute.usage; + const size = array.byteLength; - phiLength = clamp( phiLength, 0, Math.PI * 2 ); + const buffer = gl.createBuffer(); - // buffers + gl.bindBuffer( bufferType, buffer ); + gl.bufferData( bufferType, array, usage ); - const indices = []; - const vertices = []; - const uvs = []; - const initNormals = []; - const normals = []; + attribute.onUploadCallback(); - // helper variables + let type; - const inverseSegments = 1.0 / segments; - const vertex = new Vector3(); - const uv = new Vector2(); - const normal = new Vector3(); - const curNormal = new Vector3(); - const prevNormal = new Vector3(); - let dx = 0; - let dy = 0; + if ( array instanceof Float32Array ) { - // pre-compute normals for initial "meridian" + type = gl.FLOAT; - for ( let j = 0; j <= ( points.length - 1 ); j ++ ) { + } else if ( typeof Float16Array !== 'undefined' && array instanceof Float16Array ) { - switch ( j ) { + type = gl.HALF_FLOAT; - case 0: // special handling for 1st vertex on path + } else if ( array instanceof Uint16Array ) { - dx = points[ j + 1 ].x - points[ j ].x; - dy = points[ j + 1 ].y - points[ j ].y; + if ( attribute.isFloat16BufferAttribute ) { - normal.x = dy * 1.0; - normal.y = - dx; - normal.z = dy * 0.0; + type = gl.HALF_FLOAT; - prevNormal.copy( normal ); + } else { - normal.normalize(); + type = gl.UNSIGNED_SHORT; - initNormals.push( normal.x, normal.y, normal.z ); + } - break; + } else if ( array instanceof Int16Array ) { - case ( points.length - 1 ): // special handling for last Vertex on path + type = gl.SHORT; - initNormals.push( prevNormal.x, prevNormal.y, prevNormal.z ); + } else if ( array instanceof Uint32Array ) { - break; + type = gl.UNSIGNED_INT; - default: // default handling for all vertices in between + } else if ( array instanceof Int32Array ) { - dx = points[ j + 1 ].x - points[ j ].x; - dy = points[ j + 1 ].y - points[ j ].y; + type = gl.INT; - normal.x = dy * 1.0; - normal.y = - dx; - normal.z = dy * 0.0; + } else if ( array instanceof Int8Array ) { - curNormal.copy( normal ); + type = gl.BYTE; - normal.x += prevNormal.x; - normal.y += prevNormal.y; - normal.z += prevNormal.z; + } else if ( array instanceof Uint8Array ) { - normal.normalize(); + type = gl.UNSIGNED_BYTE; - initNormals.push( normal.x, normal.y, normal.z ); + } else if ( array instanceof Uint8ClampedArray ) { - prevNormal.copy( curNormal ); + type = gl.UNSIGNED_BYTE; - } + } else { + + throw new Error( 'THREE.WebGLAttributes: Unsupported buffer data format: ' + array ); } - // generate vertices, uvs and normals + return { + buffer: buffer, + type: type, + bytesPerElement: array.BYTES_PER_ELEMENT, + version: attribute.version, + size: size + }; - for ( let i = 0; i <= segments; i ++ ) { + } - const phi = phiStart + i * inverseSegments * phiLength; + function updateBuffer( buffer, attribute, bufferType ) { - const sin = Math.sin( phi ); - const cos = Math.cos( phi ); + const array = attribute.array; + const updateRanges = attribute.updateRanges; - for ( let j = 0; j <= ( points.length - 1 ); j ++ ) { + gl.bindBuffer( bufferType, buffer ); - // vertex + if ( updateRanges.length === 0 ) { - vertex.x = points[ j ].x * sin; - vertex.y = points[ j ].y; - vertex.z = points[ j ].x * cos; + // Not using update ranges + gl.bufferSubData( bufferType, 0, array ); - vertices.push( vertex.x, vertex.y, vertex.z ); + } else { - // uv + // Before applying update ranges, we merge any adjacent / overlapping + // ranges to reduce load on `gl.bufferSubData`. Empirically, this has led + // to performance improvements for applications which make heavy use of + // update ranges. Likely due to GPU command overhead. + // + // Note that to reduce garbage collection between frames, we merge the + // update ranges in-place. This is safe because this method will clear the + // update ranges once updated. - uv.x = i / segments; - uv.y = j / ( points.length - 1 ); + updateRanges.sort( ( a, b ) => a.start - b.start ); - uvs.push( uv.x, uv.y ); + // To merge the update ranges in-place, we work from left to right in the + // existing updateRanges array, merging ranges. This may result in a final + // array which is smaller than the original. This index tracks the last + // index representing a merged range, any data after this index can be + // trimmed once the merge algorithm is completed. + let mergeIndex = 0; - // normal + for ( let i = 1; i < updateRanges.length; i ++ ) { - const x = initNormals[ 3 * j + 0 ] * sin; - const y = initNormals[ 3 * j + 1 ]; - const z = initNormals[ 3 * j + 0 ] * cos; + const previousRange = updateRanges[ mergeIndex ]; + const range = updateRanges[ i ]; - normals.push( x, y, z ); + // We add one here to merge adjacent ranges. This is safe because ranges + // operate over positive integers. + if ( range.start <= previousRange.start + previousRange.count + 1 ) { - } + previousRange.count = Math.max( + previousRange.count, + range.start + range.count - previousRange.start + ); - } + } else { - // indices + ++ mergeIndex; + updateRanges[ mergeIndex ] = range; - for ( let i = 0; i < segments; i ++ ) { + } - for ( let j = 0; j < ( points.length - 1 ); j ++ ) { + } - const base = j + i * points.length; + // Trim the array to only contain the merged ranges. + updateRanges.length = mergeIndex + 1; - const a = base; - const b = base + points.length; - const c = base + points.length + 1; - const d = base + 1; + for ( let i = 0, l = updateRanges.length; i < l; i ++ ) { - // faces + const range = updateRanges[ i ]; - indices.push( a, b, d ); - indices.push( c, d, b ); + gl.bufferSubData( bufferType, range.start * array.BYTES_PER_ELEMENT, + array, range.start, range.count ); } - } + attribute.clearUpdateRanges(); - // build geometry + } - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + attribute.onUploadCallback(); } - copy( source ) { + // - super.copy( source ); + function get( attribute ) { - this.parameters = Object.assign( {}, source.parameters ); + if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; - return this; + return buffers.get( attribute ); } - static fromJSON( data ) { - - return new LatheGeometry( data.points, data.segments, data.phiStart, data.phiLength ); + function remove( attribute ) { - } + if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; -} + const data = buffers.get( attribute ); -class CapsuleGeometry extends LatheGeometry { + if ( data ) { - constructor( radius = 1, length = 1, capSegments = 4, radialSegments = 8 ) { + gl.deleteBuffer( data.buffer ); - const path = new Path(); - path.absarc( 0, - length / 2, radius, Math.PI * 1.5, 0 ); - path.absarc( 0, length / 2, radius, 0, Math.PI * 0.5 ); + buffers.delete( attribute ); - super( path.getPoints( capSegments ), radialSegments ); + } - this.type = 'CapsuleGeometry'; + } - this.parameters = { - radius: radius, - height: length, - capSegments: capSegments, - radialSegments: radialSegments, - }; + function update( attribute, bufferType ) { - } + if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; - static fromJSON( data ) { + if ( attribute.isGLBufferAttribute ) { - return new CapsuleGeometry( data.radius, data.length, data.capSegments, data.radialSegments ); + const cached = buffers.get( attribute ); - } + if ( ! cached || cached.version < attribute.version ) { -} + buffers.set( attribute, { + buffer: attribute.buffer, + type: attribute.type, + bytesPerElement: attribute.elementSize, + version: attribute.version + } ); -class CircleGeometry extends BufferGeometry { + } - constructor( radius = 1, segments = 32, thetaStart = 0, thetaLength = Math.PI * 2 ) { + return; - super(); + } - this.type = 'CircleGeometry'; + const data = buffers.get( attribute ); - this.parameters = { - radius: radius, - segments: segments, - thetaStart: thetaStart, - thetaLength: thetaLength - }; + if ( data === undefined ) { - segments = Math.max( 3, segments ); + buffers.set( attribute, createBuffer( attribute, bufferType ) ); - // buffers + } else if ( data.version < attribute.version ) { - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; + if ( data.size !== attribute.array.byteLength ) { - // helper variables + throw new Error( 'THREE.WebGLAttributes: The size of the buffer attribute\'s array buffer does not match the original size. Resizing buffer attributes is not supported.' ); - const vertex = new Vector3(); - const uv = new Vector2(); + } - // center point + updateBuffer( data.buffer, attribute, bufferType ); - vertices.push( 0, 0, 0 ); - normals.push( 0, 0, 1 ); - uvs.push( 0.5, 0.5 ); + data.version = attribute.version; - for ( let s = 0, i = 3; s <= segments; s ++, i += 3 ) { + } - const segment = thetaStart + s / segments * thetaLength; + } - // vertex + return { - vertex.x = radius * Math.cos( segment ); - vertex.y = radius * Math.sin( segment ); + get: get, + remove: remove, + update: update - vertices.push( vertex.x, vertex.y, vertex.z ); + }; - // normal +} - normals.push( 0, 0, 1 ); +var alphahash_fragment = "#ifdef USE_ALPHAHASH\n\tif ( diffuseColor.a < getAlphaHashThreshold( vPosition ) ) discard;\n#endif"; - // uvs +var alphahash_pars_fragment = "#ifdef USE_ALPHAHASH\n\tconst float ALPHA_HASH_SCALE = 0.05;\n\tfloat hash2D( vec2 value ) {\n\t\treturn fract( 1.0e4 * sin( 17.0 * value.x + 0.1 * value.y ) * ( 0.1 + abs( sin( 13.0 * value.y + value.x ) ) ) );\n\t}\n\tfloat hash3D( vec3 value ) {\n\t\treturn hash2D( vec2( hash2D( value.xy ), value.z ) );\n\t}\n\tfloat getAlphaHashThreshold( vec3 position ) {\n\t\tfloat maxDeriv = max(\n\t\t\tlength( dFdx( position.xyz ) ),\n\t\t\tlength( dFdy( position.xyz ) )\n\t\t);\n\t\tfloat pixScale = 1.0 / ( ALPHA_HASH_SCALE * maxDeriv );\n\t\tvec2 pixScales = vec2(\n\t\t\texp2( floor( log2( pixScale ) ) ),\n\t\t\texp2( ceil( log2( pixScale ) ) )\n\t\t);\n\t\tvec2 alpha = vec2(\n\t\t\thash3D( floor( pixScales.x * position.xyz ) ),\n\t\t\thash3D( floor( pixScales.y * position.xyz ) )\n\t\t);\n\t\tfloat lerpFactor = fract( log2( pixScale ) );\n\t\tfloat x = ( 1.0 - lerpFactor ) * alpha.x + lerpFactor * alpha.y;\n\t\tfloat a = min( lerpFactor, 1.0 - lerpFactor );\n\t\tvec3 cases = vec3(\n\t\t\tx * x / ( 2.0 * a * ( 1.0 - a ) ),\n\t\t\t( x - 0.5 * a ) / ( 1.0 - a ),\n\t\t\t1.0 - ( ( 1.0 - x ) * ( 1.0 - x ) / ( 2.0 * a * ( 1.0 - a ) ) )\n\t\t);\n\t\tfloat threshold = ( x < ( 1.0 - a ) )\n\t\t\t? ( ( x < a ) ? cases.x : cases.y )\n\t\t\t: cases.z;\n\t\treturn clamp( threshold , 1.0e-6, 1.0 );\n\t}\n#endif"; - uv.x = ( vertices[ i ] / radius + 1 ) / 2; - uv.y = ( vertices[ i + 1 ] / radius + 1 ) / 2; +var alphamap_fragment = "#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, vAlphaMapUv ).g;\n#endif"; - uvs.push( uv.x, uv.y ); +var alphamap_pars_fragment = "#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif"; - } +var alphatest_fragment = "#ifdef USE_ALPHATEST\n\t#ifdef ALPHA_TO_COVERAGE\n\tdiffuseColor.a = smoothstep( alphaTest, alphaTest + fwidth( diffuseColor.a ), diffuseColor.a );\n\tif ( diffuseColor.a == 0.0 ) discard;\n\t#else\n\tif ( diffuseColor.a < alphaTest ) discard;\n\t#endif\n#endif"; - // indices +var alphatest_pars_fragment = "#ifdef USE_ALPHATEST\n\tuniform float alphaTest;\n#endif"; - for ( let i = 1; i <= segments; i ++ ) { +var aomap_fragment = "#ifdef USE_AOMAP\n\tfloat ambientOcclusion = ( texture2D( aoMap, vAoMapUv ).r - 1.0 ) * aoMapIntensity + 1.0;\n\treflectedLight.indirectDiffuse *= ambientOcclusion;\n\t#if defined( USE_CLEARCOAT ) \n\t\tclearcoatSpecularIndirect *= ambientOcclusion;\n\t#endif\n\t#if defined( USE_SHEEN ) \n\t\tsheenSpecularIndirect *= ambientOcclusion;\n\t#endif\n\t#if defined( USE_ENVMAP ) && defined( STANDARD )\n\t\tfloat dotNV = saturate( dot( geometryNormal, geometryViewDir ) );\n\t\treflectedLight.indirectSpecular *= computeSpecularOcclusion( dotNV, ambientOcclusion, material.roughness );\n\t#endif\n#endif"; - indices.push( i, i + 1, 0 ); +var aomap_pars_fragment = "#ifdef USE_AOMAP\n\tuniform sampler2D aoMap;\n\tuniform float aoMapIntensity;\n#endif"; - } +var batching_pars_vertex = "#ifdef USE_BATCHING\n\t#if ! defined( GL_ANGLE_multi_draw )\n\t#define gl_DrawID _gl_DrawID\n\tuniform int _gl_DrawID;\n\t#endif\n\tuniform highp sampler2D batchingTexture;\n\tuniform highp usampler2D batchingIdTexture;\n\tmat4 getBatchingMatrix( const in float i ) {\n\t\tint size = textureSize( batchingTexture, 0 ).x;\n\t\tint j = int( i ) * 4;\n\t\tint x = j % size;\n\t\tint y = j / size;\n\t\tvec4 v1 = texelFetch( batchingTexture, ivec2( x, y ), 0 );\n\t\tvec4 v2 = texelFetch( batchingTexture, ivec2( x + 1, y ), 0 );\n\t\tvec4 v3 = texelFetch( batchingTexture, ivec2( x + 2, y ), 0 );\n\t\tvec4 v4 = texelFetch( batchingTexture, ivec2( x + 3, y ), 0 );\n\t\treturn mat4( v1, v2, v3, v4 );\n\t}\n\tfloat getIndirectIndex( const in int i ) {\n\t\tint size = textureSize( batchingIdTexture, 0 ).x;\n\t\tint x = i % size;\n\t\tint y = i / size;\n\t\treturn float( texelFetch( batchingIdTexture, ivec2( x, y ), 0 ).r );\n\t}\n#endif\n#ifdef USE_BATCHING_COLOR\n\tuniform sampler2D batchingColorTexture;\n\tvec3 getBatchingColor( const in float i ) {\n\t\tint size = textureSize( batchingColorTexture, 0 ).x;\n\t\tint j = int( i );\n\t\tint x = j % size;\n\t\tint y = j / size;\n\t\treturn texelFetch( batchingColorTexture, ivec2( x, y ), 0 ).rgb;\n\t}\n#endif"; - // build geometry +var batching_vertex = "#ifdef USE_BATCHING\n\tmat4 batchingMatrix = getBatchingMatrix( getIndirectIndex( gl_DrawID ) );\n#endif"; - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); +var begin_vertex = "vec3 transformed = vec3( position );\n#ifdef USE_ALPHAHASH\n\tvPosition = vec3( position );\n#endif"; - } +var beginnormal_vertex = "vec3 objectNormal = vec3( normal );\n#ifdef USE_TANGENT\n\tvec3 objectTangent = vec3( tangent.xyz );\n#endif"; - copy( source ) { +var bsdfs = "float G_BlinnPhong_Implicit( ) {\n\treturn 0.25;\n}\nfloat D_BlinnPhong( const in float shininess, const in float dotNH ) {\n\treturn RECIPROCAL_PI * ( shininess * 0.5 + 1.0 ) * pow( dotNH, shininess );\n}\nvec3 BRDF_BlinnPhong( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in vec3 specularColor, const in float shininess ) {\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\tvec3 F = F_Schlick( specularColor, 1.0, dotVH );\n\tfloat G = G_BlinnPhong_Implicit( );\n\tfloat D = D_BlinnPhong( shininess, dotNH );\n\treturn F * ( G * D );\n} // validated"; - super.copy( source ); +var iridescence_fragment = "#ifdef USE_IRIDESCENCE\n\tconst mat3 XYZ_TO_REC709 = mat3(\n\t\t 3.2404542, -0.9692660, 0.0556434,\n\t\t-1.5371385, 1.8760108, -0.2040259,\n\t\t-0.4985314, 0.0415560, 1.0572252\n\t);\n\tvec3 Fresnel0ToIor( vec3 fresnel0 ) {\n\t\tvec3 sqrtF0 = sqrt( fresnel0 );\n\t\treturn ( vec3( 1.0 ) + sqrtF0 ) / ( vec3( 1.0 ) - sqrtF0 );\n\t}\n\tvec3 IorToFresnel0( vec3 transmittedIor, float incidentIor ) {\n\t\treturn pow2( ( transmittedIor - vec3( incidentIor ) ) / ( transmittedIor + vec3( incidentIor ) ) );\n\t}\n\tfloat IorToFresnel0( float transmittedIor, float incidentIor ) {\n\t\treturn pow2( ( transmittedIor - incidentIor ) / ( transmittedIor + incidentIor ));\n\t}\n\tvec3 evalSensitivity( float OPD, vec3 shift ) {\n\t\tfloat phase = 2.0 * PI * OPD * 1.0e-9;\n\t\tvec3 val = vec3( 5.4856e-13, 4.4201e-13, 5.2481e-13 );\n\t\tvec3 pos = vec3( 1.6810e+06, 1.7953e+06, 2.2084e+06 );\n\t\tvec3 var = vec3( 4.3278e+09, 9.3046e+09, 6.6121e+09 );\n\t\tvec3 xyz = val * sqrt( 2.0 * PI * var ) * cos( pos * phase + shift ) * exp( - pow2( phase ) * var );\n\t\txyz.x += 9.7470e-14 * sqrt( 2.0 * PI * 4.5282e+09 ) * cos( 2.2399e+06 * phase + shift[ 0 ] ) * exp( - 4.5282e+09 * pow2( phase ) );\n\t\txyz /= 1.0685e-7;\n\t\tvec3 rgb = XYZ_TO_REC709 * xyz;\n\t\treturn rgb;\n\t}\n\tvec3 evalIridescence( float outsideIOR, float eta2, float cosTheta1, float thinFilmThickness, vec3 baseF0 ) {\n\t\tvec3 I;\n\t\tfloat iridescenceIOR = mix( outsideIOR, eta2, smoothstep( 0.0, 0.03, thinFilmThickness ) );\n\t\tfloat sinTheta2Sq = pow2( outsideIOR / iridescenceIOR ) * ( 1.0 - pow2( cosTheta1 ) );\n\t\tfloat cosTheta2Sq = 1.0 - sinTheta2Sq;\n\t\tif ( cosTheta2Sq < 0.0 ) {\n\t\t\treturn vec3( 1.0 );\n\t\t}\n\t\tfloat cosTheta2 = sqrt( cosTheta2Sq );\n\t\tfloat R0 = IorToFresnel0( iridescenceIOR, outsideIOR );\n\t\tfloat R12 = F_Schlick( R0, 1.0, cosTheta1 );\n\t\tfloat T121 = 1.0 - R12;\n\t\tfloat phi12 = 0.0;\n\t\tif ( iridescenceIOR < outsideIOR ) phi12 = PI;\n\t\tfloat phi21 = PI - phi12;\n\t\tvec3 baseIOR = Fresnel0ToIor( clamp( baseF0, 0.0, 0.9999 ) );\t\tvec3 R1 = IorToFresnel0( baseIOR, iridescenceIOR );\n\t\tvec3 R23 = F_Schlick( R1, 1.0, cosTheta2 );\n\t\tvec3 phi23 = vec3( 0.0 );\n\t\tif ( baseIOR[ 0 ] < iridescenceIOR ) phi23[ 0 ] = PI;\n\t\tif ( baseIOR[ 1 ] < iridescenceIOR ) phi23[ 1 ] = PI;\n\t\tif ( baseIOR[ 2 ] < iridescenceIOR ) phi23[ 2 ] = PI;\n\t\tfloat OPD = 2.0 * iridescenceIOR * thinFilmThickness * cosTheta2;\n\t\tvec3 phi = vec3( phi21 ) + phi23;\n\t\tvec3 R123 = clamp( R12 * R23, 1e-5, 0.9999 );\n\t\tvec3 r123 = sqrt( R123 );\n\t\tvec3 Rs = pow2( T121 ) * R23 / ( vec3( 1.0 ) - R123 );\n\t\tvec3 C0 = R12 + Rs;\n\t\tI = C0;\n\t\tvec3 Cm = Rs - T121;\n\t\tfor ( int m = 1; m <= 2; ++ m ) {\n\t\t\tCm *= r123;\n\t\t\tvec3 Sm = 2.0 * evalSensitivity( float( m ) * OPD, float( m ) * phi );\n\t\t\tI += Cm * Sm;\n\t\t}\n\t\treturn max( I, vec3( 0.0 ) );\n\t}\n#endif"; - this.parameters = Object.assign( {}, source.parameters ); +var bumpmap_pars_fragment = "#ifdef USE_BUMPMAP\n\tuniform sampler2D bumpMap;\n\tuniform float bumpScale;\n\tvec2 dHdxy_fwd() {\n\t\tvec2 dSTdx = dFdx( vBumpMapUv );\n\t\tvec2 dSTdy = dFdy( vBumpMapUv );\n\t\tfloat Hll = bumpScale * texture2D( bumpMap, vBumpMapUv ).x;\n\t\tfloat dBx = bumpScale * texture2D( bumpMap, vBumpMapUv + dSTdx ).x - Hll;\n\t\tfloat dBy = bumpScale * texture2D( bumpMap, vBumpMapUv + dSTdy ).x - Hll;\n\t\treturn vec2( dBx, dBy );\n\t}\n\tvec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy, float faceDirection ) {\n\t\tvec3 vSigmaX = normalize( dFdx( surf_pos.xyz ) );\n\t\tvec3 vSigmaY = normalize( dFdy( surf_pos.xyz ) );\n\t\tvec3 vN = surf_norm;\n\t\tvec3 R1 = cross( vSigmaY, vN );\n\t\tvec3 R2 = cross( vN, vSigmaX );\n\t\tfloat fDet = dot( vSigmaX, R1 ) * faceDirection;\n\t\tvec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );\n\t\treturn normalize( abs( fDet ) * surf_norm - vGrad );\n\t}\n#endif"; - return this; +var clipping_planes_fragment = "#if NUM_CLIPPING_PLANES > 0\n\tvec4 plane;\n\t#ifdef ALPHA_TO_COVERAGE\n\t\tfloat distanceToPlane, distanceGradient;\n\t\tfloat clipOpacity = 1.0;\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) {\n\t\t\tplane = clippingPlanes[ i ];\n\t\t\tdistanceToPlane = - dot( vClipPosition, plane.xyz ) + plane.w;\n\t\t\tdistanceGradient = fwidth( distanceToPlane ) / 2.0;\n\t\t\tclipOpacity *= smoothstep( - distanceGradient, distanceGradient, distanceToPlane );\n\t\t\tif ( clipOpacity == 0.0 ) discard;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t\t#if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES\n\t\t\tfloat unionClipOpacity = 1.0;\n\t\t\t#pragma unroll_loop_start\n\t\t\tfor ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) {\n\t\t\t\tplane = clippingPlanes[ i ];\n\t\t\t\tdistanceToPlane = - dot( vClipPosition, plane.xyz ) + plane.w;\n\t\t\t\tdistanceGradient = fwidth( distanceToPlane ) / 2.0;\n\t\t\t\tunionClipOpacity *= 1.0 - smoothstep( - distanceGradient, distanceGradient, distanceToPlane );\n\t\t\t}\n\t\t\t#pragma unroll_loop_end\n\t\t\tclipOpacity *= 1.0 - unionClipOpacity;\n\t\t#endif\n\t\tdiffuseColor.a *= clipOpacity;\n\t\tif ( diffuseColor.a == 0.0 ) discard;\n\t#else\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) {\n\t\t\tplane = clippingPlanes[ i ];\n\t\t\tif ( dot( vClipPosition, plane.xyz ) > plane.w ) discard;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t\t#if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES\n\t\t\tbool clipped = true;\n\t\t\t#pragma unroll_loop_start\n\t\t\tfor ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) {\n\t\t\t\tplane = clippingPlanes[ i ];\n\t\t\t\tclipped = ( dot( vClipPosition, plane.xyz ) > plane.w ) && clipped;\n\t\t\t}\n\t\t\t#pragma unroll_loop_end\n\t\t\tif ( clipped ) discard;\n\t\t#endif\n\t#endif\n#endif"; - } +var clipping_planes_pars_fragment = "#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n\tuniform vec4 clippingPlanes[ NUM_CLIPPING_PLANES ];\n#endif"; - static fromJSON( data ) { +var clipping_planes_pars_vertex = "#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n#endif"; - return new CircleGeometry( data.radius, data.segments, data.thetaStart, data.thetaLength ); +var clipping_planes_vertex = "#if NUM_CLIPPING_PLANES > 0\n\tvClipPosition = - mvPosition.xyz;\n#endif"; - } +var color_fragment = "#if defined( USE_COLOR_ALPHA )\n\tdiffuseColor *= vColor;\n#elif defined( USE_COLOR )\n\tdiffuseColor.rgb *= vColor;\n#endif"; -} +var color_pars_fragment = "#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR )\n\tvarying vec3 vColor;\n#endif"; -class CylinderGeometry extends BufferGeometry { +var color_pars_vertex = "#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR ) || defined( USE_BATCHING_COLOR )\n\tvarying vec3 vColor;\n#endif"; - constructor( radiusTop = 1, radiusBottom = 1, height = 1, radialSegments = 32, heightSegments = 1, openEnded = false, thetaStart = 0, thetaLength = Math.PI * 2 ) { +var color_vertex = "#if defined( USE_COLOR_ALPHA )\n\tvColor = vec4( 1.0 );\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR ) || defined( USE_BATCHING_COLOR )\n\tvColor = vec3( 1.0 );\n#endif\n#ifdef USE_COLOR\n\tvColor *= color;\n#endif\n#ifdef USE_INSTANCING_COLOR\n\tvColor.xyz *= instanceColor.xyz;\n#endif\n#ifdef USE_BATCHING_COLOR\n\tvec3 batchingColor = getBatchingColor( getIndirectIndex( gl_DrawID ) );\n\tvColor.xyz *= batchingColor.xyz;\n#endif"; - super(); +var common = "#define PI 3.141592653589793\n#define PI2 6.283185307179586\n#define PI_HALF 1.5707963267948966\n#define RECIPROCAL_PI 0.3183098861837907\n#define RECIPROCAL_PI2 0.15915494309189535\n#define EPSILON 1e-6\n#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\n#define whiteComplement( a ) ( 1.0 - saturate( a ) )\nfloat pow2( const in float x ) { return x*x; }\nvec3 pow2( const in vec3 x ) { return x*x; }\nfloat pow3( const in float x ) { return x*x*x; }\nfloat pow4( const in float x ) { float x2 = x*x; return x2*x2; }\nfloat max3( const in vec3 v ) { return max( max( v.x, v.y ), v.z ); }\nfloat average( const in vec3 v ) { return dot( v, vec3( 0.3333333 ) ); }\nhighp float rand( const in vec2 uv ) {\n\tconst highp float a = 12.9898, b = 78.233, c = 43758.5453;\n\thighp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI );\n\treturn fract( sin( sn ) * c );\n}\n#ifdef HIGH_PRECISION\n\tfloat precisionSafeLength( vec3 v ) { return length( v ); }\n#else\n\tfloat precisionSafeLength( vec3 v ) {\n\t\tfloat maxComponent = max3( abs( v ) );\n\t\treturn length( v / maxComponent ) * maxComponent;\n\t}\n#endif\nstruct IncidentLight {\n\tvec3 color;\n\tvec3 direction;\n\tbool visible;\n};\nstruct ReflectedLight {\n\tvec3 directDiffuse;\n\tvec3 directSpecular;\n\tvec3 indirectDiffuse;\n\tvec3 indirectSpecular;\n};\n#ifdef USE_ALPHAHASH\n\tvarying vec3 vPosition;\n#endif\nvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n}\nvec3 inverseTransformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( vec4( dir, 0.0 ) * matrix ).xyz );\n}\nmat3 transposeMat3( const in mat3 m ) {\n\tmat3 tmp;\n\ttmp[ 0 ] = vec3( m[ 0 ].x, m[ 1 ].x, m[ 2 ].x );\n\ttmp[ 1 ] = vec3( m[ 0 ].y, m[ 1 ].y, m[ 2 ].y );\n\ttmp[ 2 ] = vec3( m[ 0 ].z, m[ 1 ].z, m[ 2 ].z );\n\treturn tmp;\n}\nbool isPerspectiveMatrix( mat4 m ) {\n\treturn m[ 2 ][ 3 ] == - 1.0;\n}\nvec2 equirectUv( in vec3 dir ) {\n\tfloat u = atan( dir.z, dir.x ) * RECIPROCAL_PI2 + 0.5;\n\tfloat v = asin( clamp( dir.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\n\treturn vec2( u, v );\n}\nvec3 BRDF_Lambert( const in vec3 diffuseColor ) {\n\treturn RECIPROCAL_PI * diffuseColor;\n}\nvec3 F_Schlick( const in vec3 f0, const in float f90, const in float dotVH ) {\n\tfloat fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );\n\treturn f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );\n}\nfloat F_Schlick( const in float f0, const in float f90, const in float dotVH ) {\n\tfloat fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );\n\treturn f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );\n} // validated"; - this.type = 'CylinderGeometry'; +var cube_uv_reflection_fragment = "#ifdef ENVMAP_TYPE_CUBE_UV\n\t#define cubeUV_minMipLevel 4.0\n\t#define cubeUV_minTileSize 16.0\n\tfloat getFace( vec3 direction ) {\n\t\tvec3 absDirection = abs( direction );\n\t\tfloat face = - 1.0;\n\t\tif ( absDirection.x > absDirection.z ) {\n\t\t\tif ( absDirection.x > absDirection.y )\n\t\t\t\tface = direction.x > 0.0 ? 0.0 : 3.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t} else {\n\t\t\tif ( absDirection.z > absDirection.y )\n\t\t\t\tface = direction.z > 0.0 ? 2.0 : 5.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t}\n\t\treturn face;\n\t}\n\tvec2 getUV( vec3 direction, float face ) {\n\t\tvec2 uv;\n\t\tif ( face == 0.0 ) {\n\t\t\tuv = vec2( direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 1.0 ) {\n\t\t\tuv = vec2( - direction.x, - direction.z ) / abs( direction.y );\n\t\t} else if ( face == 2.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.y ) / abs( direction.z );\n\t\t} else if ( face == 3.0 ) {\n\t\t\tuv = vec2( - direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 4.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.z ) / abs( direction.y );\n\t\t} else {\n\t\t\tuv = vec2( direction.x, direction.y ) / abs( direction.z );\n\t\t}\n\t\treturn 0.5 * ( uv + 1.0 );\n\t}\n\tvec3 bilinearCubeUV( sampler2D envMap, vec3 direction, float mipInt ) {\n\t\tfloat face = getFace( direction );\n\t\tfloat filterInt = max( cubeUV_minMipLevel - mipInt, 0.0 );\n\t\tmipInt = max( mipInt, cubeUV_minMipLevel );\n\t\tfloat faceSize = exp2( mipInt );\n\t\thighp vec2 uv = getUV( direction, face ) * ( faceSize - 2.0 ) + 1.0;\n\t\tif ( face > 2.0 ) {\n\t\t\tuv.y += faceSize;\n\t\t\tface -= 3.0;\n\t\t}\n\t\tuv.x += face * faceSize;\n\t\tuv.x += filterInt * 3.0 * cubeUV_minTileSize;\n\t\tuv.y += 4.0 * ( exp2( CUBEUV_MAX_MIP ) - faceSize );\n\t\tuv.x *= CUBEUV_TEXEL_WIDTH;\n\t\tuv.y *= CUBEUV_TEXEL_HEIGHT;\n\t\t#ifdef texture2DGradEXT\n\t\t\treturn texture2DGradEXT( envMap, uv, vec2( 0.0 ), vec2( 0.0 ) ).rgb;\n\t\t#else\n\t\t\treturn texture2D( envMap, uv ).rgb;\n\t\t#endif\n\t}\n\t#define cubeUV_r0 1.0\n\t#define cubeUV_m0 - 2.0\n\t#define cubeUV_r1 0.8\n\t#define cubeUV_m1 - 1.0\n\t#define cubeUV_r4 0.4\n\t#define cubeUV_m4 2.0\n\t#define cubeUV_r5 0.305\n\t#define cubeUV_m5 3.0\n\t#define cubeUV_r6 0.21\n\t#define cubeUV_m6 4.0\n\tfloat roughnessToMip( float roughness ) {\n\t\tfloat mip = 0.0;\n\t\tif ( roughness >= cubeUV_r1 ) {\n\t\t\tmip = ( cubeUV_r0 - roughness ) * ( cubeUV_m1 - cubeUV_m0 ) / ( cubeUV_r0 - cubeUV_r1 ) + cubeUV_m0;\n\t\t} else if ( roughness >= cubeUV_r4 ) {\n\t\t\tmip = ( cubeUV_r1 - roughness ) * ( cubeUV_m4 - cubeUV_m1 ) / ( cubeUV_r1 - cubeUV_r4 ) + cubeUV_m1;\n\t\t} else if ( roughness >= cubeUV_r5 ) {\n\t\t\tmip = ( cubeUV_r4 - roughness ) * ( cubeUV_m5 - cubeUV_m4 ) / ( cubeUV_r4 - cubeUV_r5 ) + cubeUV_m4;\n\t\t} else if ( roughness >= cubeUV_r6 ) {\n\t\t\tmip = ( cubeUV_r5 - roughness ) * ( cubeUV_m6 - cubeUV_m5 ) / ( cubeUV_r5 - cubeUV_r6 ) + cubeUV_m5;\n\t\t} else {\n\t\t\tmip = - 2.0 * log2( 1.16 * roughness );\t\t}\n\t\treturn mip;\n\t}\n\tvec4 textureCubeUV( sampler2D envMap, vec3 sampleDir, float roughness ) {\n\t\tfloat mip = clamp( roughnessToMip( roughness ), cubeUV_m0, CUBEUV_MAX_MIP );\n\t\tfloat mipF = fract( mip );\n\t\tfloat mipInt = floor( mip );\n\t\tvec3 color0 = bilinearCubeUV( envMap, sampleDir, mipInt );\n\t\tif ( mipF == 0.0 ) {\n\t\t\treturn vec4( color0, 1.0 );\n\t\t} else {\n\t\t\tvec3 color1 = bilinearCubeUV( envMap, sampleDir, mipInt + 1.0 );\n\t\t\treturn vec4( mix( color0, color1, mipF ), 1.0 );\n\t\t}\n\t}\n#endif"; - this.parameters = { - radiusTop: radiusTop, - radiusBottom: radiusBottom, - height: height, - radialSegments: radialSegments, - heightSegments: heightSegments, - openEnded: openEnded, - thetaStart: thetaStart, - thetaLength: thetaLength - }; +var defaultnormal_vertex = "vec3 transformedNormal = objectNormal;\n#ifdef USE_TANGENT\n\tvec3 transformedTangent = objectTangent;\n#endif\n#ifdef USE_BATCHING\n\tmat3 bm = mat3( batchingMatrix );\n\ttransformedNormal /= vec3( dot( bm[ 0 ], bm[ 0 ] ), dot( bm[ 1 ], bm[ 1 ] ), dot( bm[ 2 ], bm[ 2 ] ) );\n\ttransformedNormal = bm * transformedNormal;\n\t#ifdef USE_TANGENT\n\t\ttransformedTangent = bm * transformedTangent;\n\t#endif\n#endif\n#ifdef USE_INSTANCING\n\tmat3 im = mat3( instanceMatrix );\n\ttransformedNormal /= vec3( dot( im[ 0 ], im[ 0 ] ), dot( im[ 1 ], im[ 1 ] ), dot( im[ 2 ], im[ 2 ] ) );\n\ttransformedNormal = im * transformedNormal;\n\t#ifdef USE_TANGENT\n\t\ttransformedTangent = im * transformedTangent;\n\t#endif\n#endif\ntransformedNormal = normalMatrix * transformedNormal;\n#ifdef FLIP_SIDED\n\ttransformedNormal = - transformedNormal;\n#endif\n#ifdef USE_TANGENT\n\ttransformedTangent = ( modelViewMatrix * vec4( transformedTangent, 0.0 ) ).xyz;\n\t#ifdef FLIP_SIDED\n\t\ttransformedTangent = - transformedTangent;\n\t#endif\n#endif"; - const scope = this; +var displacementmap_pars_vertex = "#ifdef USE_DISPLACEMENTMAP\n\tuniform sampler2D displacementMap;\n\tuniform float displacementScale;\n\tuniform float displacementBias;\n#endif"; - radialSegments = Math.floor( radialSegments ); - heightSegments = Math.floor( heightSegments ); +var displacementmap_vertex = "#ifdef USE_DISPLACEMENTMAP\n\ttransformed += normalize( objectNormal ) * ( texture2D( displacementMap, vDisplacementMapUv ).x * displacementScale + displacementBias );\n#endif"; - // buffers +var emissivemap_fragment = "#ifdef USE_EMISSIVEMAP\n\tvec4 emissiveColor = texture2D( emissiveMap, vEmissiveMapUv );\n\t#ifdef DECODE_VIDEO_TEXTURE_EMISSIVE\n\t\temissiveColor = sRGBTransferEOTF( emissiveColor );\n\t#endif\n\ttotalEmissiveRadiance *= emissiveColor.rgb;\n#endif"; - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; +var emissivemap_pars_fragment = "#ifdef USE_EMISSIVEMAP\n\tuniform sampler2D emissiveMap;\n#endif"; - // helper variables +var colorspace_fragment = "gl_FragColor = linearToOutputTexel( gl_FragColor );"; - let index = 0; - const indexArray = []; - const halfHeight = height / 2; - let groupStart = 0; +var colorspace_pars_fragment = "vec4 LinearTransferOETF( in vec4 value ) {\n\treturn value;\n}\nvec4 sRGBTransferEOTF( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), value.rgb * 0.0773993808, vec3( lessThanEqual( value.rgb, vec3( 0.04045 ) ) ) ), value.a );\n}\nvec4 sRGBTransferOETF( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a );\n}"; - // generate geometry +var envmap_fragment = "#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvec3 cameraToFrag;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToFrag = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToFrag = normalize( vWorldPosition - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvec3 reflectVec = reflect( cameraToFrag, worldNormal );\n\t\t#else\n\t\t\tvec3 reflectVec = refract( cameraToFrag, worldNormal, refractionRatio );\n\t\t#endif\n\t#else\n\t\tvec3 reflectVec = vReflect;\n\t#endif\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 envColor = textureCube( envMap, envMapRotation * vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );\n\t#else\n\t\tvec4 envColor = vec4( 0.0 );\n\t#endif\n\t#ifdef ENVMAP_BLENDING_MULTIPLY\n\t\toutgoingLight = mix( outgoingLight, outgoingLight * envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_MIX )\n\t\toutgoingLight = mix( outgoingLight, envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_ADD )\n\t\toutgoingLight += envColor.xyz * specularStrength * reflectivity;\n\t#endif\n#endif"; - generateTorso(); +var envmap_common_pars_fragment = "#ifdef USE_ENVMAP\n\tuniform float envMapIntensity;\n\tuniform float flipEnvMap;\n\tuniform mat3 envMapRotation;\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tuniform samplerCube envMap;\n\t#else\n\t\tuniform sampler2D envMap;\n\t#endif\n\t\n#endif"; - if ( openEnded === false ) { +var envmap_pars_fragment = "#ifdef USE_ENVMAP\n\tuniform float reflectivity;\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( LAMBERT )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\tvarying vec3 vWorldPosition;\n\t\tuniform float refractionRatio;\n\t#else\n\t\tvarying vec3 vReflect;\n\t#endif\n#endif"; - if ( radiusTop > 0 ) generateCap( true ); - if ( radiusBottom > 0 ) generateCap( false ); +var envmap_pars_vertex = "#ifdef USE_ENVMAP\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( LAMBERT )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\t\n\t\tvarying vec3 vWorldPosition;\n\t#else\n\t\tvarying vec3 vReflect;\n\t\tuniform float refractionRatio;\n\t#endif\n#endif"; - } +var envmap_vertex = "#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvWorldPosition = worldPosition.xyz;\n\t#else\n\t\tvec3 cameraToVertex;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToVertex = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToVertex = normalize( worldPosition.xyz - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvReflect = reflect( cameraToVertex, worldNormal );\n\t\t#else\n\t\t\tvReflect = refract( cameraToVertex, worldNormal, refractionRatio );\n\t\t#endif\n\t#endif\n#endif"; - // build geometry +var fog_vertex = "#ifdef USE_FOG\n\tvFogDepth = - mvPosition.z;\n#endif"; - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); +var fog_pars_vertex = "#ifdef USE_FOG\n\tvarying float vFogDepth;\n#endif"; - function generateTorso() { +var fog_fragment = "#ifdef USE_FOG\n\t#ifdef FOG_EXP2\n\t\tfloat fogFactor = 1.0 - exp( - fogDensity * fogDensity * vFogDepth * vFogDepth );\n\t#else\n\t\tfloat fogFactor = smoothstep( fogNear, fogFar, vFogDepth );\n\t#endif\n\tgl_FragColor.rgb = mix( gl_FragColor.rgb, fogColor, fogFactor );\n#endif"; - const normal = new Vector3(); - const vertex = new Vector3(); +var fog_pars_fragment = "#ifdef USE_FOG\n\tuniform vec3 fogColor;\n\tvarying float vFogDepth;\n\t#ifdef FOG_EXP2\n\t\tuniform float fogDensity;\n\t#else\n\t\tuniform float fogNear;\n\t\tuniform float fogFar;\n\t#endif\n#endif"; - let groupCount = 0; +var gradientmap_pars_fragment = "#ifdef USE_GRADIENTMAP\n\tuniform sampler2D gradientMap;\n#endif\nvec3 getGradientIrradiance( vec3 normal, vec3 lightDirection ) {\n\tfloat dotNL = dot( normal, lightDirection );\n\tvec2 coord = vec2( dotNL * 0.5 + 0.5, 0.0 );\n\t#ifdef USE_GRADIENTMAP\n\t\treturn vec3( texture2D( gradientMap, coord ).r );\n\t#else\n\t\tvec2 fw = fwidth( coord ) * 0.5;\n\t\treturn mix( vec3( 0.7 ), vec3( 1.0 ), smoothstep( 0.7 - fw.x, 0.7 + fw.x, coord.x ) );\n\t#endif\n}"; - // this will be used to calculate the normal - const slope = ( radiusBottom - radiusTop ) / height; +var lightmap_pars_fragment = "#ifdef USE_LIGHTMAP\n\tuniform sampler2D lightMap;\n\tuniform float lightMapIntensity;\n#endif"; - // generate vertices, normals and uvs +var lights_lambert_fragment = "LambertMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularStrength = specularStrength;"; - for ( let y = 0; y <= heightSegments; y ++ ) { +var lights_lambert_pars_fragment = "varying vec3 vViewPosition;\nstruct LambertMaterial {\n\tvec3 diffuseColor;\n\tfloat specularStrength;\n};\nvoid RE_Direct_Lambert( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in LambertMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometryNormal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Lambert( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in LambertMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Lambert\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Lambert"; - const indexRow = []; +var lights_pars_begin = "uniform bool receiveShadow;\nuniform vec3 ambientLightColor;\n#if defined( USE_LIGHT_PROBES )\n\tuniform vec3 lightProbe[ 9 ];\n#endif\nvec3 shGetIrradianceAt( in vec3 normal, in vec3 shCoefficients[ 9 ] ) {\n\tfloat x = normal.x, y = normal.y, z = normal.z;\n\tvec3 result = shCoefficients[ 0 ] * 0.886227;\n\tresult += shCoefficients[ 1 ] * 2.0 * 0.511664 * y;\n\tresult += shCoefficients[ 2 ] * 2.0 * 0.511664 * z;\n\tresult += shCoefficients[ 3 ] * 2.0 * 0.511664 * x;\n\tresult += shCoefficients[ 4 ] * 2.0 * 0.429043 * x * y;\n\tresult += shCoefficients[ 5 ] * 2.0 * 0.429043 * y * z;\n\tresult += shCoefficients[ 6 ] * ( 0.743125 * z * z - 0.247708 );\n\tresult += shCoefficients[ 7 ] * 2.0 * 0.429043 * x * z;\n\tresult += shCoefficients[ 8 ] * 0.429043 * ( x * x - y * y );\n\treturn result;\n}\nvec3 getLightProbeIrradiance( const in vec3 lightProbe[ 9 ], const in vec3 normal ) {\n\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\tvec3 irradiance = shGetIrradianceAt( worldNormal, lightProbe );\n\treturn irradiance;\n}\nvec3 getAmbientLightIrradiance( const in vec3 ambientLightColor ) {\n\tvec3 irradiance = ambientLightColor;\n\treturn irradiance;\n}\nfloat getDistanceAttenuation( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) {\n\tfloat distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );\n\tif ( cutoffDistance > 0.0 ) {\n\t\tdistanceFalloff *= pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );\n\t}\n\treturn distanceFalloff;\n}\nfloat getSpotAttenuation( const in float coneCosine, const in float penumbraCosine, const in float angleCosine ) {\n\treturn smoothstep( coneCosine, penumbraCosine, angleCosine );\n}\n#if NUM_DIR_LIGHTS > 0\n\tstruct DirectionalLight {\n\t\tvec3 direction;\n\t\tvec3 color;\n\t};\n\tuniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ];\n\tvoid getDirectionalLightInfo( const in DirectionalLight directionalLight, out IncidentLight light ) {\n\t\tlight.color = directionalLight.color;\n\t\tlight.direction = directionalLight.direction;\n\t\tlight.visible = true;\n\t}\n#endif\n#if NUM_POINT_LIGHTS > 0\n\tstruct PointLight {\n\t\tvec3 position;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t};\n\tuniform PointLight pointLights[ NUM_POINT_LIGHTS ];\n\tvoid getPointLightInfo( const in PointLight pointLight, const in vec3 geometryPosition, out IncidentLight light ) {\n\t\tvec3 lVector = pointLight.position - geometryPosition;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat lightDistance = length( lVector );\n\t\tlight.color = pointLight.color;\n\t\tlight.color *= getDistanceAttenuation( lightDistance, pointLight.distance, pointLight.decay );\n\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t}\n#endif\n#if NUM_SPOT_LIGHTS > 0\n\tstruct SpotLight {\n\t\tvec3 position;\n\t\tvec3 direction;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t\tfloat coneCos;\n\t\tfloat penumbraCos;\n\t};\n\tuniform SpotLight spotLights[ NUM_SPOT_LIGHTS ];\n\tvoid getSpotLightInfo( const in SpotLight spotLight, const in vec3 geometryPosition, out IncidentLight light ) {\n\t\tvec3 lVector = spotLight.position - geometryPosition;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat angleCos = dot( light.direction, spotLight.direction );\n\t\tfloat spotAttenuation = getSpotAttenuation( spotLight.coneCos, spotLight.penumbraCos, angleCos );\n\t\tif ( spotAttenuation > 0.0 ) {\n\t\t\tfloat lightDistance = length( lVector );\n\t\t\tlight.color = spotLight.color * spotAttenuation;\n\t\t\tlight.color *= getDistanceAttenuation( lightDistance, spotLight.distance, spotLight.decay );\n\t\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t\t} else {\n\t\t\tlight.color = vec3( 0.0 );\n\t\t\tlight.visible = false;\n\t\t}\n\t}\n#endif\n#if NUM_RECT_AREA_LIGHTS > 0\n\tstruct RectAreaLight {\n\t\tvec3 color;\n\t\tvec3 position;\n\t\tvec3 halfWidth;\n\t\tvec3 halfHeight;\n\t};\n\tuniform sampler2D ltc_1;\tuniform sampler2D ltc_2;\n\tuniform RectAreaLight rectAreaLights[ NUM_RECT_AREA_LIGHTS ];\n#endif\n#if NUM_HEMI_LIGHTS > 0\n\tstruct HemisphereLight {\n\t\tvec3 direction;\n\t\tvec3 skyColor;\n\t\tvec3 groundColor;\n\t};\n\tuniform HemisphereLight hemisphereLights[ NUM_HEMI_LIGHTS ];\n\tvec3 getHemisphereLightIrradiance( const in HemisphereLight hemiLight, const in vec3 normal ) {\n\t\tfloat dotNL = dot( normal, hemiLight.direction );\n\t\tfloat hemiDiffuseWeight = 0.5 * dotNL + 0.5;\n\t\tvec3 irradiance = mix( hemiLight.groundColor, hemiLight.skyColor, hemiDiffuseWeight );\n\t\treturn irradiance;\n\t}\n#endif"; - const v = y / heightSegments; +var envmap_physical_pars_fragment = "#ifdef USE_ENVMAP\n\tvec3 getIBLIrradiance( const in vec3 normal ) {\n\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, envMapRotation * worldNormal, 1.0 );\n\t\t\treturn PI * envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n\tvec3 getIBLRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness ) {\n\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\tvec3 reflectVec = reflect( - viewDir, normal );\n\t\t\treflectVec = normalize( mix( reflectVec, normal, roughness * roughness) );\n\t\t\treflectVec = inverseTransformDirection( reflectVec, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, envMapRotation * reflectVec, roughness );\n\t\t\treturn envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n\t#ifdef USE_ANISOTROPY\n\t\tvec3 getIBLAnisotropyRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness, const in vec3 bitangent, const in float anisotropy ) {\n\t\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\t\tvec3 bentNormal = cross( bitangent, viewDir );\n\t\t\t\tbentNormal = normalize( cross( bentNormal, bitangent ) );\n\t\t\t\tbentNormal = normalize( mix( bentNormal, normal, pow2( pow2( 1.0 - anisotropy * ( 1.0 - roughness ) ) ) ) );\n\t\t\t\treturn getIBLRadiance( viewDir, bentNormal, roughness );\n\t\t\t#else\n\t\t\t\treturn vec3( 0.0 );\n\t\t\t#endif\n\t\t}\n\t#endif\n#endif"; - // calculate the radius of the current row +var lights_toon_fragment = "ToonMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;"; - const radius = v * ( radiusBottom - radiusTop ) + radiusTop; +var lights_toon_pars_fragment = "varying vec3 vViewPosition;\nstruct ToonMaterial {\n\tvec3 diffuseColor;\n};\nvoid RE_Direct_Toon( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\tvec3 irradiance = getGradientIrradiance( geometryNormal, directLight.direction ) * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Toon( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Toon\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Toon"; - for ( let x = 0; x <= radialSegments; x ++ ) { +var lights_phong_fragment = "BlinnPhongMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularColor = specular;\nmaterial.specularShininess = shininess;\nmaterial.specularStrength = specularStrength;"; - const u = x / radialSegments; +var lights_phong_pars_fragment = "varying vec3 vViewPosition;\nstruct BlinnPhongMaterial {\n\tvec3 diffuseColor;\n\tvec3 specularColor;\n\tfloat specularShininess;\n\tfloat specularStrength;\n};\nvoid RE_Direct_BlinnPhong( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometryNormal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n\treflectedLight.directSpecular += irradiance * BRDF_BlinnPhong( directLight.direction, geometryViewDir, geometryNormal, material.specularColor, material.specularShininess ) * material.specularStrength;\n}\nvoid RE_IndirectDiffuse_BlinnPhong( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_BlinnPhong\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_BlinnPhong"; - const theta = u * thetaLength + thetaStart; +var lights_physical_fragment = "PhysicalMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb * ( 1.0 - metalnessFactor );\nvec3 dxy = max( abs( dFdx( nonPerturbedNormal ) ), abs( dFdy( nonPerturbedNormal ) ) );\nfloat geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z );\nmaterial.roughness = max( roughnessFactor, 0.0525 );material.roughness += geometryRoughness;\nmaterial.roughness = min( material.roughness, 1.0 );\n#ifdef IOR\n\tmaterial.ior = ior;\n\t#ifdef USE_SPECULAR\n\t\tfloat specularIntensityFactor = specularIntensity;\n\t\tvec3 specularColorFactor = specularColor;\n\t\t#ifdef USE_SPECULAR_COLORMAP\n\t\t\tspecularColorFactor *= texture2D( specularColorMap, vSpecularColorMapUv ).rgb;\n\t\t#endif\n\t\t#ifdef USE_SPECULAR_INTENSITYMAP\n\t\t\tspecularIntensityFactor *= texture2D( specularIntensityMap, vSpecularIntensityMapUv ).a;\n\t\t#endif\n\t\tmaterial.specularF90 = mix( specularIntensityFactor, 1.0, metalnessFactor );\n\t#else\n\t\tfloat specularIntensityFactor = 1.0;\n\t\tvec3 specularColorFactor = vec3( 1.0 );\n\t\tmaterial.specularF90 = 1.0;\n\t#endif\n\tmaterial.specularColor = mix( min( pow2( ( material.ior - 1.0 ) / ( material.ior + 1.0 ) ) * specularColorFactor, vec3( 1.0 ) ) * specularIntensityFactor, diffuseColor.rgb, metalnessFactor );\n#else\n\tmaterial.specularColor = mix( vec3( 0.04 ), diffuseColor.rgb, metalnessFactor );\n\tmaterial.specularF90 = 1.0;\n#endif\n#ifdef USE_CLEARCOAT\n\tmaterial.clearcoat = clearcoat;\n\tmaterial.clearcoatRoughness = clearcoatRoughness;\n\tmaterial.clearcoatF0 = vec3( 0.04 );\n\tmaterial.clearcoatF90 = 1.0;\n\t#ifdef USE_CLEARCOATMAP\n\t\tmaterial.clearcoat *= texture2D( clearcoatMap, vClearcoatMapUv ).x;\n\t#endif\n\t#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\t\tmaterial.clearcoatRoughness *= texture2D( clearcoatRoughnessMap, vClearcoatRoughnessMapUv ).y;\n\t#endif\n\tmaterial.clearcoat = saturate( material.clearcoat );\tmaterial.clearcoatRoughness = max( material.clearcoatRoughness, 0.0525 );\n\tmaterial.clearcoatRoughness += geometryRoughness;\n\tmaterial.clearcoatRoughness = min( material.clearcoatRoughness, 1.0 );\n#endif\n#ifdef USE_DISPERSION\n\tmaterial.dispersion = dispersion;\n#endif\n#ifdef USE_IRIDESCENCE\n\tmaterial.iridescence = iridescence;\n\tmaterial.iridescenceIOR = iridescenceIOR;\n\t#ifdef USE_IRIDESCENCEMAP\n\t\tmaterial.iridescence *= texture2D( iridescenceMap, vIridescenceMapUv ).r;\n\t#endif\n\t#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\t\tmaterial.iridescenceThickness = (iridescenceThicknessMaximum - iridescenceThicknessMinimum) * texture2D( iridescenceThicknessMap, vIridescenceThicknessMapUv ).g + iridescenceThicknessMinimum;\n\t#else\n\t\tmaterial.iridescenceThickness = iridescenceThicknessMaximum;\n\t#endif\n#endif\n#ifdef USE_SHEEN\n\tmaterial.sheenColor = sheenColor;\n\t#ifdef USE_SHEEN_COLORMAP\n\t\tmaterial.sheenColor *= texture2D( sheenColorMap, vSheenColorMapUv ).rgb;\n\t#endif\n\tmaterial.sheenRoughness = clamp( sheenRoughness, 0.07, 1.0 );\n\t#ifdef USE_SHEEN_ROUGHNESSMAP\n\t\tmaterial.sheenRoughness *= texture2D( sheenRoughnessMap, vSheenRoughnessMapUv ).a;\n\t#endif\n#endif\n#ifdef USE_ANISOTROPY\n\t#ifdef USE_ANISOTROPYMAP\n\t\tmat2 anisotropyMat = mat2( anisotropyVector.x, anisotropyVector.y, - anisotropyVector.y, anisotropyVector.x );\n\t\tvec3 anisotropyPolar = texture2D( anisotropyMap, vAnisotropyMapUv ).rgb;\n\t\tvec2 anisotropyV = anisotropyMat * normalize( 2.0 * anisotropyPolar.rg - vec2( 1.0 ) ) * anisotropyPolar.b;\n\t#else\n\t\tvec2 anisotropyV = anisotropyVector;\n\t#endif\n\tmaterial.anisotropy = length( anisotropyV );\n\tif( material.anisotropy == 0.0 ) {\n\t\tanisotropyV = vec2( 1.0, 0.0 );\n\t} else {\n\t\tanisotropyV /= material.anisotropy;\n\t\tmaterial.anisotropy = saturate( material.anisotropy );\n\t}\n\tmaterial.alphaT = mix( pow2( material.roughness ), 1.0, pow2( material.anisotropy ) );\n\tmaterial.anisotropyT = tbn[ 0 ] * anisotropyV.x + tbn[ 1 ] * anisotropyV.y;\n\tmaterial.anisotropyB = tbn[ 1 ] * anisotropyV.x - tbn[ 0 ] * anisotropyV.y;\n#endif"; - const sinTheta = Math.sin( theta ); - const cosTheta = Math.cos( theta ); +var lights_physical_pars_fragment = "struct PhysicalMaterial {\n\tvec3 diffuseColor;\n\tfloat roughness;\n\tvec3 specularColor;\n\tfloat specularF90;\n\tfloat dispersion;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat clearcoat;\n\t\tfloat clearcoatRoughness;\n\t\tvec3 clearcoatF0;\n\t\tfloat clearcoatF90;\n\t#endif\n\t#ifdef USE_IRIDESCENCE\n\t\tfloat iridescence;\n\t\tfloat iridescenceIOR;\n\t\tfloat iridescenceThickness;\n\t\tvec3 iridescenceFresnel;\n\t\tvec3 iridescenceF0;\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tvec3 sheenColor;\n\t\tfloat sheenRoughness;\n\t#endif\n\t#ifdef IOR\n\t\tfloat ior;\n\t#endif\n\t#ifdef USE_TRANSMISSION\n\t\tfloat transmission;\n\t\tfloat transmissionAlpha;\n\t\tfloat thickness;\n\t\tfloat attenuationDistance;\n\t\tvec3 attenuationColor;\n\t#endif\n\t#ifdef USE_ANISOTROPY\n\t\tfloat anisotropy;\n\t\tfloat alphaT;\n\t\tvec3 anisotropyT;\n\t\tvec3 anisotropyB;\n\t#endif\n};\nvec3 clearcoatSpecularDirect = vec3( 0.0 );\nvec3 clearcoatSpecularIndirect = vec3( 0.0 );\nvec3 sheenSpecularDirect = vec3( 0.0 );\nvec3 sheenSpecularIndirect = vec3(0.0 );\nvec3 Schlick_to_F0( const in vec3 f, const in float f90, const in float dotVH ) {\n float x = clamp( 1.0 - dotVH, 0.0, 1.0 );\n float x2 = x * x;\n float x5 = clamp( x * x2 * x2, 0.0, 0.9999 );\n return ( f - vec3( f90 ) * x5 ) / ( 1.0 - x5 );\n}\nfloat V_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const in float dotNV ) {\n\tfloat a2 = pow2( alpha );\n\tfloat gv = dotNL * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );\n\tfloat gl = dotNV * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );\n\treturn 0.5 / max( gv + gl, EPSILON );\n}\nfloat D_GGX( const in float alpha, const in float dotNH ) {\n\tfloat a2 = pow2( alpha );\n\tfloat denom = pow2( dotNH ) * ( a2 - 1.0 ) + 1.0;\n\treturn RECIPROCAL_PI * a2 / pow2( denom );\n}\n#ifdef USE_ANISOTROPY\n\tfloat V_GGX_SmithCorrelated_Anisotropic( const in float alphaT, const in float alphaB, const in float dotTV, const in float dotBV, const in float dotTL, const in float dotBL, const in float dotNV, const in float dotNL ) {\n\t\tfloat gv = dotNL * length( vec3( alphaT * dotTV, alphaB * dotBV, dotNV ) );\n\t\tfloat gl = dotNV * length( vec3( alphaT * dotTL, alphaB * dotBL, dotNL ) );\n\t\tfloat v = 0.5 / ( gv + gl );\n\t\treturn saturate(v);\n\t}\n\tfloat D_GGX_Anisotropic( const in float alphaT, const in float alphaB, const in float dotNH, const in float dotTH, const in float dotBH ) {\n\t\tfloat a2 = alphaT * alphaB;\n\t\thighp vec3 v = vec3( alphaB * dotTH, alphaT * dotBH, a2 * dotNH );\n\t\thighp float v2 = dot( v, v );\n\t\tfloat w2 = a2 / v2;\n\t\treturn RECIPROCAL_PI * a2 * pow2 ( w2 );\n\t}\n#endif\n#ifdef USE_CLEARCOAT\n\tvec3 BRDF_GGX_Clearcoat( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in PhysicalMaterial material) {\n\t\tvec3 f0 = material.clearcoatF0;\n\t\tfloat f90 = material.clearcoatF90;\n\t\tfloat roughness = material.clearcoatRoughness;\n\t\tfloat alpha = pow2( roughness );\n\t\tvec3 halfDir = normalize( lightDir + viewDir );\n\t\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\t\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\t\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\t\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\t\tvec3 F = F_Schlick( f0, f90, dotVH );\n\t\tfloat V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\t\tfloat D = D_GGX( alpha, dotNH );\n\t\treturn F * ( V * D );\n\t}\n#endif\nvec3 BRDF_GGX( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in PhysicalMaterial material ) {\n\tvec3 f0 = material.specularColor;\n\tfloat f90 = material.specularF90;\n\tfloat roughness = material.roughness;\n\tfloat alpha = pow2( roughness );\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\tvec3 F = F_Schlick( f0, f90, dotVH );\n\t#ifdef USE_IRIDESCENCE\n\t\tF = mix( F, material.iridescenceFresnel, material.iridescence );\n\t#endif\n\t#ifdef USE_ANISOTROPY\n\t\tfloat dotTL = dot( material.anisotropyT, lightDir );\n\t\tfloat dotTV = dot( material.anisotropyT, viewDir );\n\t\tfloat dotTH = dot( material.anisotropyT, halfDir );\n\t\tfloat dotBL = dot( material.anisotropyB, lightDir );\n\t\tfloat dotBV = dot( material.anisotropyB, viewDir );\n\t\tfloat dotBH = dot( material.anisotropyB, halfDir );\n\t\tfloat V = V_GGX_SmithCorrelated_Anisotropic( material.alphaT, alpha, dotTV, dotBV, dotTL, dotBL, dotNV, dotNL );\n\t\tfloat D = D_GGX_Anisotropic( material.alphaT, alpha, dotNH, dotTH, dotBH );\n\t#else\n\t\tfloat V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\t\tfloat D = D_GGX( alpha, dotNH );\n\t#endif\n\treturn F * ( V * D );\n}\nvec2 LTC_Uv( const in vec3 N, const in vec3 V, const in float roughness ) {\n\tconst float LUT_SIZE = 64.0;\n\tconst float LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE;\n\tconst float LUT_BIAS = 0.5 / LUT_SIZE;\n\tfloat dotNV = saturate( dot( N, V ) );\n\tvec2 uv = vec2( roughness, sqrt( 1.0 - dotNV ) );\n\tuv = uv * LUT_SCALE + LUT_BIAS;\n\treturn uv;\n}\nfloat LTC_ClippedSphereFormFactor( const in vec3 f ) {\n\tfloat l = length( f );\n\treturn max( ( l * l + f.z ) / ( l + 1.0 ), 0.0 );\n}\nvec3 LTC_EdgeVectorFormFactor( const in vec3 v1, const in vec3 v2 ) {\n\tfloat x = dot( v1, v2 );\n\tfloat y = abs( x );\n\tfloat a = 0.8543985 + ( 0.4965155 + 0.0145206 * y ) * y;\n\tfloat b = 3.4175940 + ( 4.1616724 + y ) * y;\n\tfloat v = a / b;\n\tfloat theta_sintheta = ( x > 0.0 ) ? v : 0.5 * inversesqrt( max( 1.0 - x * x, 1e-7 ) ) - v;\n\treturn cross( v1, v2 ) * theta_sintheta;\n}\nvec3 LTC_Evaluate( const in vec3 N, const in vec3 V, const in vec3 P, const in mat3 mInv, const in vec3 rectCoords[ 4 ] ) {\n\tvec3 v1 = rectCoords[ 1 ] - rectCoords[ 0 ];\n\tvec3 v2 = rectCoords[ 3 ] - rectCoords[ 0 ];\n\tvec3 lightNormal = cross( v1, v2 );\n\tif( dot( lightNormal, P - rectCoords[ 0 ] ) < 0.0 ) return vec3( 0.0 );\n\tvec3 T1, T2;\n\tT1 = normalize( V - N * dot( V, N ) );\n\tT2 = - cross( N, T1 );\n\tmat3 mat = mInv * transposeMat3( mat3( T1, T2, N ) );\n\tvec3 coords[ 4 ];\n\tcoords[ 0 ] = mat * ( rectCoords[ 0 ] - P );\n\tcoords[ 1 ] = mat * ( rectCoords[ 1 ] - P );\n\tcoords[ 2 ] = mat * ( rectCoords[ 2 ] - P );\n\tcoords[ 3 ] = mat * ( rectCoords[ 3 ] - P );\n\tcoords[ 0 ] = normalize( coords[ 0 ] );\n\tcoords[ 1 ] = normalize( coords[ 1 ] );\n\tcoords[ 2 ] = normalize( coords[ 2 ] );\n\tcoords[ 3 ] = normalize( coords[ 3 ] );\n\tvec3 vectorFormFactor = vec3( 0.0 );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 0 ], coords[ 1 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 1 ], coords[ 2 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 2 ], coords[ 3 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 3 ], coords[ 0 ] );\n\tfloat result = LTC_ClippedSphereFormFactor( vectorFormFactor );\n\treturn vec3( result );\n}\n#if defined( USE_SHEEN )\nfloat D_Charlie( float roughness, float dotNH ) {\n\tfloat alpha = pow2( roughness );\n\tfloat invAlpha = 1.0 / alpha;\n\tfloat cos2h = dotNH * dotNH;\n\tfloat sin2h = max( 1.0 - cos2h, 0.0078125 );\n\treturn ( 2.0 + invAlpha ) * pow( sin2h, invAlpha * 0.5 ) / ( 2.0 * PI );\n}\nfloat V_Neubelt( float dotNV, float dotNL ) {\n\treturn saturate( 1.0 / ( 4.0 * ( dotNL + dotNV - dotNL * dotNV ) ) );\n}\nvec3 BRDF_Sheen( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, vec3 sheenColor, const in float sheenRoughness ) {\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat D = D_Charlie( sheenRoughness, dotNH );\n\tfloat V = V_Neubelt( dotNV, dotNL );\n\treturn sheenColor * ( D * V );\n}\n#endif\nfloat IBLSheenBRDF( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat r2 = roughness * roughness;\n\tfloat a = roughness < 0.25 ? -339.2 * r2 + 161.4 * roughness - 25.9 : -8.48 * r2 + 14.3 * roughness - 9.95;\n\tfloat b = roughness < 0.25 ? 44.0 * r2 - 23.7 * roughness + 3.26 : 1.97 * r2 - 3.27 * roughness + 0.72;\n\tfloat DG = exp( a * dotNV + b ) + ( roughness < 0.25 ? 0.0 : 0.1 * ( roughness - 0.25 ) );\n\treturn saturate( DG * RECIPROCAL_PI );\n}\nvec2 DFGApprox( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tconst vec4 c0 = vec4( - 1, - 0.0275, - 0.572, 0.022 );\n\tconst vec4 c1 = vec4( 1, 0.0425, 1.04, - 0.04 );\n\tvec4 r = roughness * c0 + c1;\n\tfloat a004 = min( r.x * r.x, exp2( - 9.28 * dotNV ) ) * r.x + r.y;\n\tvec2 fab = vec2( - 1.04, 1.04 ) * a004 + r.zw;\n\treturn fab;\n}\nvec3 EnvironmentBRDF( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness ) {\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\treturn specularColor * fab.x + specularF90 * fab.y;\n}\n#ifdef USE_IRIDESCENCE\nvoid computeMultiscatteringIridescence( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float iridescence, const in vec3 iridescenceF0, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#else\nvoid computeMultiscattering( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#endif\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\t#ifdef USE_IRIDESCENCE\n\t\tvec3 Fr = mix( specularColor, iridescenceF0, iridescence );\n\t#else\n\t\tvec3 Fr = specularColor;\n\t#endif\n\tvec3 FssEss = Fr * fab.x + specularF90 * fab.y;\n\tfloat Ess = fab.x + fab.y;\n\tfloat Ems = 1.0 - Ess;\n\tvec3 Favg = Fr + ( 1.0 - Fr ) * 0.047619;\tvec3 Fms = FssEss * Favg / ( 1.0 - Ems * Favg );\n\tsingleScatter += FssEss;\n\tmultiScatter += Fms * Ems;\n}\n#if NUM_RECT_AREA_LIGHTS > 0\n\tvoid RE_Direct_RectArea_Physical( const in RectAreaLight rectAreaLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\t\tvec3 normal = geometryNormal;\n\t\tvec3 viewDir = geometryViewDir;\n\t\tvec3 position = geometryPosition;\n\t\tvec3 lightPos = rectAreaLight.position;\n\t\tvec3 halfWidth = rectAreaLight.halfWidth;\n\t\tvec3 halfHeight = rectAreaLight.halfHeight;\n\t\tvec3 lightColor = rectAreaLight.color;\n\t\tfloat roughness = material.roughness;\n\t\tvec3 rectCoords[ 4 ];\n\t\trectCoords[ 0 ] = lightPos + halfWidth - halfHeight;\t\trectCoords[ 1 ] = lightPos - halfWidth - halfHeight;\n\t\trectCoords[ 2 ] = lightPos - halfWidth + halfHeight;\n\t\trectCoords[ 3 ] = lightPos + halfWidth + halfHeight;\n\t\tvec2 uv = LTC_Uv( normal, viewDir, roughness );\n\t\tvec4 t1 = texture2D( ltc_1, uv );\n\t\tvec4 t2 = texture2D( ltc_2, uv );\n\t\tmat3 mInv = mat3(\n\t\t\tvec3( t1.x, 0, t1.y ),\n\t\t\tvec3( 0, 1, 0 ),\n\t\t\tvec3( t1.z, 0, t1.w )\n\t\t);\n\t\tvec3 fresnel = ( material.specularColor * t2.x + ( vec3( 1.0 ) - material.specularColor ) * t2.y );\n\t\treflectedLight.directSpecular += lightColor * fresnel * LTC_Evaluate( normal, viewDir, position, mInv, rectCoords );\n\t\treflectedLight.directDiffuse += lightColor * material.diffuseColor * LTC_Evaluate( normal, viewDir, position, mat3( 1.0 ), rectCoords );\n\t}\n#endif\nvoid RE_Direct_Physical( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometryNormal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNLcc = saturate( dot( geometryClearcoatNormal, directLight.direction ) );\n\t\tvec3 ccIrradiance = dotNLcc * directLight.color;\n\t\tclearcoatSpecularDirect += ccIrradiance * BRDF_GGX_Clearcoat( directLight.direction, geometryViewDir, geometryClearcoatNormal, material );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tsheenSpecularDirect += irradiance * BRDF_Sheen( directLight.direction, geometryViewDir, geometryNormal, material.sheenColor, material.sheenRoughness );\n\t#endif\n\treflectedLight.directSpecular += irradiance * BRDF_GGX( directLight.direction, geometryViewDir, geometryNormal, material );\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Physical( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 irradiance, const in vec3 clearcoatRadiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatSpecularIndirect += clearcoatRadiance * EnvironmentBRDF( geometryClearcoatNormal, geometryViewDir, material.clearcoatF0, material.clearcoatF90, material.clearcoatRoughness );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tsheenSpecularIndirect += irradiance * material.sheenColor * IBLSheenBRDF( geometryNormal, geometryViewDir, material.sheenRoughness );\n\t#endif\n\tvec3 singleScattering = vec3( 0.0 );\n\tvec3 multiScattering = vec3( 0.0 );\n\tvec3 cosineWeightedIrradiance = irradiance * RECIPROCAL_PI;\n\t#ifdef USE_IRIDESCENCE\n\t\tcomputeMultiscatteringIridescence( geometryNormal, geometryViewDir, material.specularColor, material.specularF90, material.iridescence, material.iridescenceFresnel, material.roughness, singleScattering, multiScattering );\n\t#else\n\t\tcomputeMultiscattering( geometryNormal, geometryViewDir, material.specularColor, material.specularF90, material.roughness, singleScattering, multiScattering );\n\t#endif\n\tvec3 totalScattering = singleScattering + multiScattering;\n\tvec3 diffuse = material.diffuseColor * ( 1.0 - max( max( totalScattering.r, totalScattering.g ), totalScattering.b ) );\n\treflectedLight.indirectSpecular += radiance * singleScattering;\n\treflectedLight.indirectSpecular += multiScattering * cosineWeightedIrradiance;\n\treflectedLight.indirectDiffuse += diffuse * cosineWeightedIrradiance;\n}\n#define RE_Direct\t\t\t\tRE_Direct_Physical\n#define RE_Direct_RectArea\t\tRE_Direct_RectArea_Physical\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Physical\n#define RE_IndirectSpecular\t\tRE_IndirectSpecular_Physical\nfloat computeSpecularOcclusion( const in float dotNV, const in float ambientOcclusion, const in float roughness ) {\n\treturn saturate( pow( dotNV + ambientOcclusion, exp2( - 16.0 * roughness - 1.0 ) ) - 1.0 + ambientOcclusion );\n}"; - // vertex +var lights_fragment_begin = "\nvec3 geometryPosition = - vViewPosition;\nvec3 geometryNormal = normal;\nvec3 geometryViewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( vViewPosition );\nvec3 geometryClearcoatNormal = vec3( 0.0 );\n#ifdef USE_CLEARCOAT\n\tgeometryClearcoatNormal = clearcoatNormal;\n#endif\n#ifdef USE_IRIDESCENCE\n\tfloat dotNVi = saturate( dot( normal, geometryViewDir ) );\n\tif ( material.iridescenceThickness == 0.0 ) {\n\t\tmaterial.iridescence = 0.0;\n\t} else {\n\t\tmaterial.iridescence = saturate( material.iridescence );\n\t}\n\tif ( material.iridescence > 0.0 ) {\n\t\tmaterial.iridescenceFresnel = evalIridescence( 1.0, material.iridescenceIOR, dotNVi, material.iridescenceThickness, material.specularColor );\n\t\tmaterial.iridescenceF0 = Schlick_to_F0( material.iridescenceFresnel, 1.0, dotNVi );\n\t}\n#endif\nIncidentLight directLight;\n#if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct )\n\tPointLight pointLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tpointLight = pointLights[ i ];\n\t\tgetPointLightInfo( pointLight, geometryPosition, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_POINT_LIGHT_SHADOWS )\n\t\tpointLightShadow = pointLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getPointShadow( pointShadowMap[ i ], pointLightShadow.shadowMapSize, pointLightShadow.shadowIntensity, pointLightShadow.shadowBias, pointLightShadow.shadowRadius, vPointShadowCoord[ i ], pointLightShadow.shadowCameraNear, pointLightShadow.shadowCameraFar ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct )\n\tSpotLight spotLight;\n\tvec4 spotColor;\n\tvec3 spotLightCoord;\n\tbool inSpotLightMap;\n\t#if defined( USE_SHADOWMAP ) && NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tspotLight = spotLights[ i ];\n\t\tgetSpotLightInfo( spotLight, geometryPosition, directLight );\n\t\t#if ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )\n\t\t#define SPOT_LIGHT_MAP_INDEX UNROLLED_LOOP_INDEX\n\t\t#elif ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\t#define SPOT_LIGHT_MAP_INDEX NUM_SPOT_LIGHT_MAPS\n\t\t#else\n\t\t#define SPOT_LIGHT_MAP_INDEX ( UNROLLED_LOOP_INDEX - NUM_SPOT_LIGHT_SHADOWS + NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )\n\t\t#endif\n\t\t#if ( SPOT_LIGHT_MAP_INDEX < NUM_SPOT_LIGHT_MAPS )\n\t\t\tspotLightCoord = vSpotLightCoord[ i ].xyz / vSpotLightCoord[ i ].w;\n\t\t\tinSpotLightMap = all( lessThan( abs( spotLightCoord * 2. - 1. ), vec3( 1.0 ) ) );\n\t\t\tspotColor = texture2D( spotLightMap[ SPOT_LIGHT_MAP_INDEX ], spotLightCoord.xy );\n\t\t\tdirectLight.color = inSpotLightMap ? directLight.color * spotColor.rgb : directLight.color;\n\t\t#endif\n\t\t#undef SPOT_LIGHT_MAP_INDEX\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\tspotLightShadow = spotLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( spotShadowMap[ i ], spotLightShadow.shadowMapSize, spotLightShadow.shadowIntensity, spotLightShadow.shadowBias, spotLightShadow.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct )\n\tDirectionalLight directionalLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tdirectionalLight = directionalLights[ i ];\n\t\tgetDirectionalLightInfo( directionalLight, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )\n\t\tdirectionalLightShadow = directionalLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowIntensity, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea )\n\tRectAreaLight rectAreaLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) {\n\t\trectAreaLight = rectAreaLights[ i ];\n\t\tRE_Direct_RectArea( rectAreaLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if defined( RE_IndirectDiffuse )\n\tvec3 iblIrradiance = vec3( 0.0 );\n\tvec3 irradiance = getAmbientLightIrradiance( ambientLightColor );\n\t#if defined( USE_LIGHT_PROBES )\n\t\tirradiance += getLightProbeIrradiance( lightProbe, geometryNormal );\n\t#endif\n\t#if ( NUM_HEMI_LIGHTS > 0 )\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n\t\t\tirradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometryNormal );\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n#endif\n#if defined( RE_IndirectSpecular )\n\tvec3 radiance = vec3( 0.0 );\n\tvec3 clearcoatRadiance = vec3( 0.0 );\n#endif"; - vertex.x = radius * sinTheta; - vertex.y = - v * height + halfHeight; - vertex.z = radius * cosTheta; - vertices.push( vertex.x, vertex.y, vertex.z ); +var lights_fragment_maps = "#if defined( RE_IndirectDiffuse )\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel = texture2D( lightMap, vLightMapUv );\n\t\tvec3 lightMapIrradiance = lightMapTexel.rgb * lightMapIntensity;\n\t\tirradiance += lightMapIrradiance;\n\t#endif\n\t#if defined( USE_ENVMAP ) && defined( STANDARD ) && defined( ENVMAP_TYPE_CUBE_UV )\n\t\tiblIrradiance += getIBLIrradiance( geometryNormal );\n\t#endif\n#endif\n#if defined( USE_ENVMAP ) && defined( RE_IndirectSpecular )\n\t#ifdef USE_ANISOTROPY\n\t\tradiance += getIBLAnisotropyRadiance( geometryViewDir, geometryNormal, material.roughness, material.anisotropyB, material.anisotropy );\n\t#else\n\t\tradiance += getIBLRadiance( geometryViewDir, geometryNormal, material.roughness );\n\t#endif\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatRadiance += getIBLRadiance( geometryViewDir, geometryClearcoatNormal, material.clearcoatRoughness );\n\t#endif\n#endif"; - // normal +var lights_fragment_end = "#if defined( RE_IndirectDiffuse )\n\tRE_IndirectDiffuse( irradiance, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n#endif\n#if defined( RE_IndirectSpecular )\n\tRE_IndirectSpecular( radiance, iblIrradiance, clearcoatRadiance, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n#endif"; - normal.set( sinTheta, slope, cosTheta ).normalize(); - normals.push( normal.x, normal.y, normal.z ); +var logdepthbuf_fragment = "#if defined( USE_LOGDEPTHBUF )\n\tgl_FragDepth = vIsPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\n#endif"; - // uv +var logdepthbuf_pars_fragment = "#if defined( USE_LOGDEPTHBUF )\n\tuniform float logDepthBufFC;\n\tvarying float vFragDepth;\n\tvarying float vIsPerspective;\n#endif"; - uvs.push( u, 1 - v ); +var logdepthbuf_pars_vertex = "#ifdef USE_LOGDEPTHBUF\n\tvarying float vFragDepth;\n\tvarying float vIsPerspective;\n#endif"; - // save index of vertex in respective row +var logdepthbuf_vertex = "#ifdef USE_LOGDEPTHBUF\n\tvFragDepth = 1.0 + gl_Position.w;\n\tvIsPerspective = float( isPerspectiveMatrix( projectionMatrix ) );\n#endif"; - indexRow.push( index ++ ); +var map_fragment = "#ifdef USE_MAP\n\tvec4 sampledDiffuseColor = texture2D( map, vMapUv );\n\t#ifdef DECODE_VIDEO_TEXTURE\n\t\tsampledDiffuseColor = sRGBTransferEOTF( sampledDiffuseColor );\n\t#endif\n\tdiffuseColor *= sampledDiffuseColor;\n#endif"; - } +var map_pars_fragment = "#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif"; - // now save vertices of the row in our index array +var map_particle_fragment = "#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\t#if defined( USE_POINTS_UV )\n\t\tvec2 uv = vUv;\n\t#else\n\t\tvec2 uv = ( uvTransform * vec3( gl_PointCoord.x, 1.0 - gl_PointCoord.y, 1 ) ).xy;\n\t#endif\n#endif\n#ifdef USE_MAP\n\tdiffuseColor *= texture2D( map, uv );\n#endif\n#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, uv ).g;\n#endif"; - indexArray.push( indexRow ); +var map_particle_pars_fragment = "#if defined( USE_POINTS_UV )\n\tvarying vec2 vUv;\n#else\n\t#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\t\tuniform mat3 uvTransform;\n\t#endif\n#endif\n#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif\n#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif"; - } +var metalnessmap_fragment = "float metalnessFactor = metalness;\n#ifdef USE_METALNESSMAP\n\tvec4 texelMetalness = texture2D( metalnessMap, vMetalnessMapUv );\n\tmetalnessFactor *= texelMetalness.b;\n#endif"; - // generate indices +var metalnessmap_pars_fragment = "#ifdef USE_METALNESSMAP\n\tuniform sampler2D metalnessMap;\n#endif"; - for ( let x = 0; x < radialSegments; x ++ ) { +var morphinstance_vertex = "#ifdef USE_INSTANCING_MORPH\n\tfloat morphTargetInfluences[ MORPHTARGETS_COUNT ];\n\tfloat morphTargetBaseInfluence = texelFetch( morphTexture, ivec2( 0, gl_InstanceID ), 0 ).r;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\tmorphTargetInfluences[i] = texelFetch( morphTexture, ivec2( i + 1, gl_InstanceID ), 0 ).r;\n\t}\n#endif"; - for ( let y = 0; y < heightSegments; y ++ ) { +var morphcolor_vertex = "#if defined( USE_MORPHCOLORS )\n\tvColor *= morphTargetBaseInfluence;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t#if defined( USE_COLOR_ALPHA )\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ) * morphTargetInfluences[ i ];\n\t\t#elif defined( USE_COLOR )\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ).rgb * morphTargetInfluences[ i ];\n\t\t#endif\n\t}\n#endif"; - // we use the index array to access the correct indices +var morphnormal_vertex = "#ifdef USE_MORPHNORMALS\n\tobjectNormal *= morphTargetBaseInfluence;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\tif ( morphTargetInfluences[ i ] != 0.0 ) objectNormal += getMorph( gl_VertexID, i, 1 ).xyz * morphTargetInfluences[ i ];\n\t}\n#endif"; - const a = indexArray[ y ][ x ]; - const b = indexArray[ y + 1 ][ x ]; - const c = indexArray[ y + 1 ][ x + 1 ]; - const d = indexArray[ y ][ x + 1 ]; +var morphtarget_pars_vertex = "#ifdef USE_MORPHTARGETS\n\t#ifndef USE_INSTANCING_MORPH\n\t\tuniform float morphTargetBaseInfluence;\n\t\tuniform float morphTargetInfluences[ MORPHTARGETS_COUNT ];\n\t#endif\n\tuniform sampler2DArray morphTargetsTexture;\n\tuniform ivec2 morphTargetsTextureSize;\n\tvec4 getMorph( const in int vertexIndex, const in int morphTargetIndex, const in int offset ) {\n\t\tint texelIndex = vertexIndex * MORPHTARGETS_TEXTURE_STRIDE + offset;\n\t\tint y = texelIndex / morphTargetsTextureSize.x;\n\t\tint x = texelIndex - y * morphTargetsTextureSize.x;\n\t\tivec3 morphUV = ivec3( x, y, morphTargetIndex );\n\t\treturn texelFetch( morphTargetsTexture, morphUV, 0 );\n\t}\n#endif"; - // faces +var morphtarget_vertex = "#ifdef USE_MORPHTARGETS\n\ttransformed *= morphTargetBaseInfluence;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\tif ( morphTargetInfluences[ i ] != 0.0 ) transformed += getMorph( gl_VertexID, i, 0 ).xyz * morphTargetInfluences[ i ];\n\t}\n#endif"; - indices.push( a, b, d ); - indices.push( b, c, d ); +var normal_fragment_begin = "float faceDirection = gl_FrontFacing ? 1.0 : - 1.0;\n#ifdef FLAT_SHADED\n\tvec3 fdx = dFdx( vViewPosition );\n\tvec3 fdy = dFdy( vViewPosition );\n\tvec3 normal = normalize( cross( fdx, fdy ) );\n#else\n\tvec3 normal = normalize( vNormal );\n\t#ifdef DOUBLE_SIDED\n\t\tnormal *= faceDirection;\n\t#endif\n#endif\n#if defined( USE_NORMALMAP_TANGENTSPACE ) || defined( USE_CLEARCOAT_NORMALMAP ) || defined( USE_ANISOTROPY )\n\t#ifdef USE_TANGENT\n\t\tmat3 tbn = mat3( normalize( vTangent ), normalize( vBitangent ), normal );\n\t#else\n\t\tmat3 tbn = getTangentFrame( - vViewPosition, normal,\n\t\t#if defined( USE_NORMALMAP )\n\t\t\tvNormalMapUv\n\t\t#elif defined( USE_CLEARCOAT_NORMALMAP )\n\t\t\tvClearcoatNormalMapUv\n\t\t#else\n\t\t\tvUv\n\t\t#endif\n\t\t);\n\t#endif\n\t#if defined( DOUBLE_SIDED ) && ! defined( FLAT_SHADED )\n\t\ttbn[0] *= faceDirection;\n\t\ttbn[1] *= faceDirection;\n\t#endif\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\t#ifdef USE_TANGENT\n\t\tmat3 tbn2 = mat3( normalize( vTangent ), normalize( vBitangent ), normal );\n\t#else\n\t\tmat3 tbn2 = getTangentFrame( - vViewPosition, normal, vClearcoatNormalMapUv );\n\t#endif\n\t#if defined( DOUBLE_SIDED ) && ! defined( FLAT_SHADED )\n\t\ttbn2[0] *= faceDirection;\n\t\ttbn2[1] *= faceDirection;\n\t#endif\n#endif\nvec3 nonPerturbedNormal = normal;"; - // update group counter +var normal_fragment_maps = "#ifdef USE_NORMALMAP_OBJECTSPACE\n\tnormal = texture2D( normalMap, vNormalMapUv ).xyz * 2.0 - 1.0;\n\t#ifdef FLIP_SIDED\n\t\tnormal = - normal;\n\t#endif\n\t#ifdef DOUBLE_SIDED\n\t\tnormal = normal * faceDirection;\n\t#endif\n\tnormal = normalize( normalMatrix * normal );\n#elif defined( USE_NORMALMAP_TANGENTSPACE )\n\tvec3 mapN = texture2D( normalMap, vNormalMapUv ).xyz * 2.0 - 1.0;\n\tmapN.xy *= normalScale;\n\tnormal = normalize( tbn * mapN );\n#elif defined( USE_BUMPMAP )\n\tnormal = perturbNormalArb( - vViewPosition, normal, dHdxy_fwd(), faceDirection );\n#endif"; - groupCount += 6; +var normal_pars_fragment = "#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif"; - } +var normal_pars_vertex = "#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif"; - } +var normal_vertex = "#ifndef FLAT_SHADED\n\tvNormal = normalize( transformedNormal );\n\t#ifdef USE_TANGENT\n\t\tvTangent = normalize( transformedTangent );\n\t\tvBitangent = normalize( cross( vNormal, vTangent ) * tangent.w );\n\t#endif\n#endif"; - // add a group to the geometry. this will ensure multi material support +var normalmap_pars_fragment = "#ifdef USE_NORMALMAP\n\tuniform sampler2D normalMap;\n\tuniform vec2 normalScale;\n#endif\n#ifdef USE_NORMALMAP_OBJECTSPACE\n\tuniform mat3 normalMatrix;\n#endif\n#if ! defined ( USE_TANGENT ) && ( defined ( USE_NORMALMAP_TANGENTSPACE ) || defined ( USE_CLEARCOAT_NORMALMAP ) || defined( USE_ANISOTROPY ) )\n\tmat3 getTangentFrame( vec3 eye_pos, vec3 surf_norm, vec2 uv ) {\n\t\tvec3 q0 = dFdx( eye_pos.xyz );\n\t\tvec3 q1 = dFdy( eye_pos.xyz );\n\t\tvec2 st0 = dFdx( uv.st );\n\t\tvec2 st1 = dFdy( uv.st );\n\t\tvec3 N = surf_norm;\n\t\tvec3 q1perp = cross( q1, N );\n\t\tvec3 q0perp = cross( N, q0 );\n\t\tvec3 T = q1perp * st0.x + q0perp * st1.x;\n\t\tvec3 B = q1perp * st0.y + q0perp * st1.y;\n\t\tfloat det = max( dot( T, T ), dot( B, B ) );\n\t\tfloat scale = ( det == 0.0 ) ? 0.0 : inversesqrt( det );\n\t\treturn mat3( T * scale, B * scale, N );\n\t}\n#endif"; - scope.addGroup( groupStart, groupCount, 0 ); +var clearcoat_normal_fragment_begin = "#ifdef USE_CLEARCOAT\n\tvec3 clearcoatNormal = nonPerturbedNormal;\n#endif"; - // calculate new start value for groups +var clearcoat_normal_fragment_maps = "#ifdef USE_CLEARCOAT_NORMALMAP\n\tvec3 clearcoatMapN = texture2D( clearcoatNormalMap, vClearcoatNormalMapUv ).xyz * 2.0 - 1.0;\n\tclearcoatMapN.xy *= clearcoatNormalScale;\n\tclearcoatNormal = normalize( tbn2 * clearcoatMapN );\n#endif"; - groupStart += groupCount; +var clearcoat_pars_fragment = "#ifdef USE_CLEARCOATMAP\n\tuniform sampler2D clearcoatMap;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tuniform sampler2D clearcoatNormalMap;\n\tuniform vec2 clearcoatNormalScale;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tuniform sampler2D clearcoatRoughnessMap;\n#endif"; - } +var iridescence_pars_fragment = "#ifdef USE_IRIDESCENCEMAP\n\tuniform sampler2D iridescenceMap;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tuniform sampler2D iridescenceThicknessMap;\n#endif"; - function generateCap( top ) { +var opaque_fragment = "#ifdef OPAQUE\ndiffuseColor.a = 1.0;\n#endif\n#ifdef USE_TRANSMISSION\ndiffuseColor.a *= material.transmissionAlpha;\n#endif\ngl_FragColor = vec4( outgoingLight, diffuseColor.a );"; - // save the index of the first center vertex - const centerIndexStart = index; +var packing = "vec3 packNormalToRGB( const in vec3 normal ) {\n\treturn normalize( normal ) * 0.5 + 0.5;\n}\nvec3 unpackRGBToNormal( const in vec3 rgb ) {\n\treturn 2.0 * rgb.xyz - 1.0;\n}\nconst float PackUpscale = 256. / 255.;const float UnpackDownscale = 255. / 256.;const float ShiftRight8 = 1. / 256.;\nconst float Inv255 = 1. / 255.;\nconst vec4 PackFactors = vec4( 1.0, 256.0, 256.0 * 256.0, 256.0 * 256.0 * 256.0 );\nconst vec2 UnpackFactors2 = vec2( UnpackDownscale, 1.0 / PackFactors.g );\nconst vec3 UnpackFactors3 = vec3( UnpackDownscale / PackFactors.rg, 1.0 / PackFactors.b );\nconst vec4 UnpackFactors4 = vec4( UnpackDownscale / PackFactors.rgb, 1.0 / PackFactors.a );\nvec4 packDepthToRGBA( const in float v ) {\n\tif( v <= 0.0 )\n\t\treturn vec4( 0., 0., 0., 0. );\n\tif( v >= 1.0 )\n\t\treturn vec4( 1., 1., 1., 1. );\n\tfloat vuf;\n\tfloat af = modf( v * PackFactors.a, vuf );\n\tfloat bf = modf( vuf * ShiftRight8, vuf );\n\tfloat gf = modf( vuf * ShiftRight8, vuf );\n\treturn vec4( vuf * Inv255, gf * PackUpscale, bf * PackUpscale, af );\n}\nvec3 packDepthToRGB( const in float v ) {\n\tif( v <= 0.0 )\n\t\treturn vec3( 0., 0., 0. );\n\tif( v >= 1.0 )\n\t\treturn vec3( 1., 1., 1. );\n\tfloat vuf;\n\tfloat bf = modf( v * PackFactors.b, vuf );\n\tfloat gf = modf( vuf * ShiftRight8, vuf );\n\treturn vec3( vuf * Inv255, gf * PackUpscale, bf );\n}\nvec2 packDepthToRG( const in float v ) {\n\tif( v <= 0.0 )\n\t\treturn vec2( 0., 0. );\n\tif( v >= 1.0 )\n\t\treturn vec2( 1., 1. );\n\tfloat vuf;\n\tfloat gf = modf( v * 256., vuf );\n\treturn vec2( vuf * Inv255, gf );\n}\nfloat unpackRGBAToDepth( const in vec4 v ) {\n\treturn dot( v, UnpackFactors4 );\n}\nfloat unpackRGBToDepth( const in vec3 v ) {\n\treturn dot( v, UnpackFactors3 );\n}\nfloat unpackRGToDepth( const in vec2 v ) {\n\treturn v.r * UnpackFactors2.r + v.g * UnpackFactors2.g;\n}\nvec4 pack2HalfToRGBA( const in vec2 v ) {\n\tvec4 r = vec4( v.x, fract( v.x * 255.0 ), v.y, fract( v.y * 255.0 ) );\n\treturn vec4( r.x - r.y / 255.0, r.y, r.z - r.w / 255.0, r.w );\n}\nvec2 unpackRGBATo2Half( const in vec4 v ) {\n\treturn vec2( v.x + ( v.y / 255.0 ), v.z + ( v.w / 255.0 ) );\n}\nfloat viewZToOrthographicDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( viewZ + near ) / ( near - far );\n}\nfloat orthographicDepthToViewZ( const in float depth, const in float near, const in float far ) {\n\treturn depth * ( near - far ) - near;\n}\nfloat viewZToPerspectiveDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( ( near + viewZ ) * far ) / ( ( far - near ) * viewZ );\n}\nfloat perspectiveDepthToViewZ( const in float depth, const in float near, const in float far ) {\n\treturn ( near * far ) / ( ( far - near ) * depth - far );\n}"; - const uv = new Vector2(); - const vertex = new Vector3(); +var premultiplied_alpha_fragment = "#ifdef PREMULTIPLIED_ALPHA\n\tgl_FragColor.rgb *= gl_FragColor.a;\n#endif"; - let groupCount = 0; +var project_vertex = "vec4 mvPosition = vec4( transformed, 1.0 );\n#ifdef USE_BATCHING\n\tmvPosition = batchingMatrix * mvPosition;\n#endif\n#ifdef USE_INSTANCING\n\tmvPosition = instanceMatrix * mvPosition;\n#endif\nmvPosition = modelViewMatrix * mvPosition;\ngl_Position = projectionMatrix * mvPosition;"; - const radius = ( top === true ) ? radiusTop : radiusBottom; - const sign = ( top === true ) ? 1 : - 1; +var dithering_fragment = "#ifdef DITHERING\n\tgl_FragColor.rgb = dithering( gl_FragColor.rgb );\n#endif"; - // first we generate the center vertex data of the cap. - // because the geometry needs one set of uvs per face, - // we must generate a center vertex per face/segment +var dithering_pars_fragment = "#ifdef DITHERING\n\tvec3 dithering( vec3 color ) {\n\t\tfloat grid_position = rand( gl_FragCoord.xy );\n\t\tvec3 dither_shift_RGB = vec3( 0.25 / 255.0, -0.25 / 255.0, 0.25 / 255.0 );\n\t\tdither_shift_RGB = mix( 2.0 * dither_shift_RGB, -2.0 * dither_shift_RGB, grid_position );\n\t\treturn color + dither_shift_RGB;\n\t}\n#endif"; - for ( let x = 1; x <= radialSegments; x ++ ) { +var roughnessmap_fragment = "float roughnessFactor = roughness;\n#ifdef USE_ROUGHNESSMAP\n\tvec4 texelRoughness = texture2D( roughnessMap, vRoughnessMapUv );\n\troughnessFactor *= texelRoughness.g;\n#endif"; - // vertex +var roughnessmap_pars_fragment = "#ifdef USE_ROUGHNESSMAP\n\tuniform sampler2D roughnessMap;\n#endif"; - vertices.push( 0, halfHeight * sign, 0 ); +var shadowmap_pars_fragment = "#if NUM_SPOT_LIGHT_COORDS > 0\n\tvarying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ];\n#endif\n#if NUM_SPOT_LIGHT_MAPS > 0\n\tuniform sampler2D spotLightMap[ NUM_SPOT_LIGHT_MAPS ];\n#endif\n#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D directionalShadowMap[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D spotShadowMap[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D pointShadowMap[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n\tfloat texture2DCompare( sampler2D depths, vec2 uv, float compare ) {\n\t\treturn step( compare, unpackRGBAToDepth( texture2D( depths, uv ) ) );\n\t}\n\tvec2 texture2DDistribution( sampler2D shadow, vec2 uv ) {\n\t\treturn unpackRGBATo2Half( texture2D( shadow, uv ) );\n\t}\n\tfloat VSMShadow (sampler2D shadow, vec2 uv, float compare ){\n\t\tfloat occlusion = 1.0;\n\t\tvec2 distribution = texture2DDistribution( shadow, uv );\n\t\tfloat hard_shadow = step( compare , distribution.x );\n\t\tif (hard_shadow != 1.0 ) {\n\t\t\tfloat distance = compare - distribution.x ;\n\t\t\tfloat variance = max( 0.00000, distribution.y * distribution.y );\n\t\t\tfloat softness_probability = variance / (variance + distance * distance );\t\t\tsoftness_probability = clamp( ( softness_probability - 0.3 ) / ( 0.95 - 0.3 ), 0.0, 1.0 );\t\t\tocclusion = clamp( max( hard_shadow, softness_probability ), 0.0, 1.0 );\n\t\t}\n\t\treturn occlusion;\n\t}\n\tfloat getShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowIntensity, float shadowBias, float shadowRadius, vec4 shadowCoord ) {\n\t\tfloat shadow = 1.0;\n\t\tshadowCoord.xyz /= shadowCoord.w;\n\t\tshadowCoord.z += shadowBias;\n\t\tbool inFrustum = shadowCoord.x >= 0.0 && shadowCoord.x <= 1.0 && shadowCoord.y >= 0.0 && shadowCoord.y <= 1.0;\n\t\tbool frustumTest = inFrustum && shadowCoord.z <= 1.0;\n\t\tif ( frustumTest ) {\n\t\t#if defined( SHADOWMAP_TYPE_PCF )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx0 = - texelSize.x * shadowRadius;\n\t\t\tfloat dy0 = - texelSize.y * shadowRadius;\n\t\t\tfloat dx1 = + texelSize.x * shadowRadius;\n\t\t\tfloat dy1 = + texelSize.y * shadowRadius;\n\t\t\tfloat dx2 = dx0 / 2.0;\n\t\t\tfloat dy2 = dy0 / 2.0;\n\t\t\tfloat dx3 = dx1 / 2.0;\n\t\t\tfloat dy3 = dy1 / 2.0;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy1 ), shadowCoord.z )\n\t\t\t) * ( 1.0 / 17.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_PCF_SOFT )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx = texelSize.x;\n\t\t\tfloat dy = texelSize.y;\n\t\t\tvec2 uv = shadowCoord.xy;\n\t\t\tvec2 f = fract( uv * shadowMapSize + 0.5 );\n\t\t\tuv -= f * texelSize;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, uv, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( dx, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( 0.0, dy ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + texelSize, shadowCoord.z ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, dy ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( 0.0, -dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 0.0, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( mix( texture2DCompare( shadowMap, uv + vec2( -dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t f.x ),\n\t\t\t\t\t mix( texture2DCompare( shadowMap, uv + vec2( -dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t f.x ),\n\t\t\t\t\t f.y )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_VSM )\n\t\t\tshadow = VSMShadow( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#else\n\t\t\tshadow = texture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#endif\n\t\t}\n\t\treturn mix( 1.0, shadow, shadowIntensity );\n\t}\n\tvec2 cubeToUV( vec3 v, float texelSizeY ) {\n\t\tvec3 absV = abs( v );\n\t\tfloat scaleToCube = 1.0 / max( absV.x, max( absV.y, absV.z ) );\n\t\tabsV *= scaleToCube;\n\t\tv *= scaleToCube * ( 1.0 - 2.0 * texelSizeY );\n\t\tvec2 planar = v.xy;\n\t\tfloat almostATexel = 1.5 * texelSizeY;\n\t\tfloat almostOne = 1.0 - almostATexel;\n\t\tif ( absV.z >= almostOne ) {\n\t\t\tif ( v.z > 0.0 )\n\t\t\t\tplanar.x = 4.0 - v.x;\n\t\t} else if ( absV.x >= almostOne ) {\n\t\t\tfloat signX = sign( v.x );\n\t\t\tplanar.x = v.z * signX + 2.0 * signX;\n\t\t} else if ( absV.y >= almostOne ) {\n\t\t\tfloat signY = sign( v.y );\n\t\t\tplanar.x = v.x + 2.0 * signY + 2.0;\n\t\t\tplanar.y = v.z * signY - 2.0;\n\t\t}\n\t\treturn vec2( 0.125, 0.25 ) * planar + vec2( 0.375, 0.75 );\n\t}\n\tfloat getPointShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowIntensity, float shadowBias, float shadowRadius, vec4 shadowCoord, float shadowCameraNear, float shadowCameraFar ) {\n\t\tfloat shadow = 1.0;\n\t\tvec3 lightToPosition = shadowCoord.xyz;\n\t\t\n\t\tfloat lightToPositionLength = length( lightToPosition );\n\t\tif ( lightToPositionLength - shadowCameraFar <= 0.0 && lightToPositionLength - shadowCameraNear >= 0.0 ) {\n\t\t\tfloat dp = ( lightToPositionLength - shadowCameraNear ) / ( shadowCameraFar - shadowCameraNear );\t\t\tdp += shadowBias;\n\t\t\tvec3 bd3D = normalize( lightToPosition );\n\t\t\tvec2 texelSize = vec2( 1.0 ) / ( shadowMapSize * vec2( 4.0, 2.0 ) );\n\t\t\t#if defined( SHADOWMAP_TYPE_PCF ) || defined( SHADOWMAP_TYPE_PCF_SOFT ) || defined( SHADOWMAP_TYPE_VSM )\n\t\t\t\tvec2 offset = vec2( - 1, 1 ) * shadowRadius * texelSize.y;\n\t\t\t\tshadow = (\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyy, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyy, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyx, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyx, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxy, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxy, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxx, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxx, texelSize.y ), dp )\n\t\t\t\t) * ( 1.0 / 9.0 );\n\t\t\t#else\n\t\t\t\tshadow = texture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp );\n\t\t\t#endif\n\t\t}\n\t\treturn mix( 1.0, shadow, shadowIntensity );\n\t}\n#endif"; - // normal +var shadowmap_pars_vertex = "#if NUM_SPOT_LIGHT_COORDS > 0\n\tuniform mat4 spotLightMatrix[ NUM_SPOT_LIGHT_COORDS ];\n\tvarying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ];\n#endif\n#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform mat4 directionalShadowMatrix[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform mat4 pointShadowMatrix[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n#endif"; - normals.push( 0, sign, 0 ); +var shadowmap_vertex = "#if ( defined( USE_SHADOWMAP ) && ( NUM_DIR_LIGHT_SHADOWS > 0 || NUM_POINT_LIGHT_SHADOWS > 0 ) ) || ( NUM_SPOT_LIGHT_COORDS > 0 )\n\tvec3 shadowWorldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\tvec4 shadowWorldPosition;\n#endif\n#if defined( USE_SHADOWMAP )\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * directionalLightShadows[ i ].shadowNormalBias, 0 );\n\t\t\tvDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * shadowWorldPosition;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * pointLightShadows[ i ].shadowNormalBias, 0 );\n\t\t\tvPointShadowCoord[ i ] = pointShadowMatrix[ i ] * shadowWorldPosition;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n#endif\n#if NUM_SPOT_LIGHT_COORDS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_COORDS; i ++ ) {\n\t\tshadowWorldPosition = worldPosition;\n\t\t#if ( defined( USE_SHADOWMAP ) && UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\t\tshadowWorldPosition.xyz += shadowWorldNormal * spotLightShadows[ i ].shadowNormalBias;\n\t\t#endif\n\t\tvSpotLightCoord[ i ] = spotLightMatrix[ i ] * shadowWorldPosition;\n\t}\n\t#pragma unroll_loop_end\n#endif"; - // uv +var shadowmask_pars_fragment = "float getShadowMask() {\n\tfloat shadow = 1.0;\n\t#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\tdirectionalLight = directionalLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowIntensity, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_SHADOWS; i ++ ) {\n\t\tspotLight = spotLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowIntensity, spotLight.shadowBias, spotLight.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\tpointLight = pointLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowIntensity, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#endif\n\treturn shadow;\n}"; - uvs.push( 0.5, 0.5 ); +var skinbase_vertex = "#ifdef USE_SKINNING\n\tmat4 boneMatX = getBoneMatrix( skinIndex.x );\n\tmat4 boneMatY = getBoneMatrix( skinIndex.y );\n\tmat4 boneMatZ = getBoneMatrix( skinIndex.z );\n\tmat4 boneMatW = getBoneMatrix( skinIndex.w );\n#endif"; - // increase index +var skinning_pars_vertex = "#ifdef USE_SKINNING\n\tuniform mat4 bindMatrix;\n\tuniform mat4 bindMatrixInverse;\n\tuniform highp sampler2D boneTexture;\n\tmat4 getBoneMatrix( const in float i ) {\n\t\tint size = textureSize( boneTexture, 0 ).x;\n\t\tint j = int( i ) * 4;\n\t\tint x = j % size;\n\t\tint y = j / size;\n\t\tvec4 v1 = texelFetch( boneTexture, ivec2( x, y ), 0 );\n\t\tvec4 v2 = texelFetch( boneTexture, ivec2( x + 1, y ), 0 );\n\t\tvec4 v3 = texelFetch( boneTexture, ivec2( x + 2, y ), 0 );\n\t\tvec4 v4 = texelFetch( boneTexture, ivec2( x + 3, y ), 0 );\n\t\treturn mat4( v1, v2, v3, v4 );\n\t}\n#endif"; - index ++; +var skinning_vertex = "#ifdef USE_SKINNING\n\tvec4 skinVertex = bindMatrix * vec4( transformed, 1.0 );\n\tvec4 skinned = vec4( 0.0 );\n\tskinned += boneMatX * skinVertex * skinWeight.x;\n\tskinned += boneMatY * skinVertex * skinWeight.y;\n\tskinned += boneMatZ * skinVertex * skinWeight.z;\n\tskinned += boneMatW * skinVertex * skinWeight.w;\n\ttransformed = ( bindMatrixInverse * skinned ).xyz;\n#endif"; - } +var skinnormal_vertex = "#ifdef USE_SKINNING\n\tmat4 skinMatrix = mat4( 0.0 );\n\tskinMatrix += skinWeight.x * boneMatX;\n\tskinMatrix += skinWeight.y * boneMatY;\n\tskinMatrix += skinWeight.z * boneMatZ;\n\tskinMatrix += skinWeight.w * boneMatW;\n\tskinMatrix = bindMatrixInverse * skinMatrix * bindMatrix;\n\tobjectNormal = vec4( skinMatrix * vec4( objectNormal, 0.0 ) ).xyz;\n\t#ifdef USE_TANGENT\n\t\tobjectTangent = vec4( skinMatrix * vec4( objectTangent, 0.0 ) ).xyz;\n\t#endif\n#endif"; - // save the index of the last center vertex - const centerIndexEnd = index; +var specularmap_fragment = "float specularStrength;\n#ifdef USE_SPECULARMAP\n\tvec4 texelSpecular = texture2D( specularMap, vSpecularMapUv );\n\tspecularStrength = texelSpecular.r;\n#else\n\tspecularStrength = 1.0;\n#endif"; - // now we generate the surrounding vertices, normals and uvs +var specularmap_pars_fragment = "#ifdef USE_SPECULARMAP\n\tuniform sampler2D specularMap;\n#endif"; - for ( let x = 0; x <= radialSegments; x ++ ) { +var tonemapping_fragment = "#if defined( TONE_MAPPING )\n\tgl_FragColor.rgb = toneMapping( gl_FragColor.rgb );\n#endif"; - const u = x / radialSegments; - const theta = u * thetaLength + thetaStart; +var tonemapping_pars_fragment = "#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\nuniform float toneMappingExposure;\nvec3 LinearToneMapping( vec3 color ) {\n\treturn saturate( toneMappingExposure * color );\n}\nvec3 ReinhardToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\treturn saturate( color / ( vec3( 1.0 ) + color ) );\n}\nvec3 CineonToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\tcolor = max( vec3( 0.0 ), color - 0.004 );\n\treturn pow( ( color * ( 6.2 * color + 0.5 ) ) / ( color * ( 6.2 * color + 1.7 ) + 0.06 ), vec3( 2.2 ) );\n}\nvec3 RRTAndODTFit( vec3 v ) {\n\tvec3 a = v * ( v + 0.0245786 ) - 0.000090537;\n\tvec3 b = v * ( 0.983729 * v + 0.4329510 ) + 0.238081;\n\treturn a / b;\n}\nvec3 ACESFilmicToneMapping( vec3 color ) {\n\tconst mat3 ACESInputMat = mat3(\n\t\tvec3( 0.59719, 0.07600, 0.02840 ),\t\tvec3( 0.35458, 0.90834, 0.13383 ),\n\t\tvec3( 0.04823, 0.01566, 0.83777 )\n\t);\n\tconst mat3 ACESOutputMat = mat3(\n\t\tvec3( 1.60475, -0.10208, -0.00327 ),\t\tvec3( -0.53108, 1.10813, -0.07276 ),\n\t\tvec3( -0.07367, -0.00605, 1.07602 )\n\t);\n\tcolor *= toneMappingExposure / 0.6;\n\tcolor = ACESInputMat * color;\n\tcolor = RRTAndODTFit( color );\n\tcolor = ACESOutputMat * color;\n\treturn saturate( color );\n}\nconst mat3 LINEAR_REC2020_TO_LINEAR_SRGB = mat3(\n\tvec3( 1.6605, - 0.1246, - 0.0182 ),\n\tvec3( - 0.5876, 1.1329, - 0.1006 ),\n\tvec3( - 0.0728, - 0.0083, 1.1187 )\n);\nconst mat3 LINEAR_SRGB_TO_LINEAR_REC2020 = mat3(\n\tvec3( 0.6274, 0.0691, 0.0164 ),\n\tvec3( 0.3293, 0.9195, 0.0880 ),\n\tvec3( 0.0433, 0.0113, 0.8956 )\n);\nvec3 agxDefaultContrastApprox( vec3 x ) {\n\tvec3 x2 = x * x;\n\tvec3 x4 = x2 * x2;\n\treturn + 15.5 * x4 * x2\n\t\t- 40.14 * x4 * x\n\t\t+ 31.96 * x4\n\t\t- 6.868 * x2 * x\n\t\t+ 0.4298 * x2\n\t\t+ 0.1191 * x\n\t\t- 0.00232;\n}\nvec3 AgXToneMapping( vec3 color ) {\n\tconst mat3 AgXInsetMatrix = mat3(\n\t\tvec3( 0.856627153315983, 0.137318972929847, 0.11189821299995 ),\n\t\tvec3( 0.0951212405381588, 0.761241990602591, 0.0767994186031903 ),\n\t\tvec3( 0.0482516061458583, 0.101439036467562, 0.811302368396859 )\n\t);\n\tconst mat3 AgXOutsetMatrix = mat3(\n\t\tvec3( 1.1271005818144368, - 0.1413297634984383, - 0.14132976349843826 ),\n\t\tvec3( - 0.11060664309660323, 1.157823702216272, - 0.11060664309660294 ),\n\t\tvec3( - 0.016493938717834573, - 0.016493938717834257, 1.2519364065950405 )\n\t);\n\tconst float AgxMinEv = - 12.47393;\tconst float AgxMaxEv = 4.026069;\n\tcolor *= toneMappingExposure;\n\tcolor = LINEAR_SRGB_TO_LINEAR_REC2020 * color;\n\tcolor = AgXInsetMatrix * color;\n\tcolor = max( color, 1e-10 );\tcolor = log2( color );\n\tcolor = ( color - AgxMinEv ) / ( AgxMaxEv - AgxMinEv );\n\tcolor = clamp( color, 0.0, 1.0 );\n\tcolor = agxDefaultContrastApprox( color );\n\tcolor = AgXOutsetMatrix * color;\n\tcolor = pow( max( vec3( 0.0 ), color ), vec3( 2.2 ) );\n\tcolor = LINEAR_REC2020_TO_LINEAR_SRGB * color;\n\tcolor = clamp( color, 0.0, 1.0 );\n\treturn color;\n}\nvec3 NeutralToneMapping( vec3 color ) {\n\tconst float StartCompression = 0.8 - 0.04;\n\tconst float Desaturation = 0.15;\n\tcolor *= toneMappingExposure;\n\tfloat x = min( color.r, min( color.g, color.b ) );\n\tfloat offset = x < 0.08 ? x - 6.25 * x * x : 0.04;\n\tcolor -= offset;\n\tfloat peak = max( color.r, max( color.g, color.b ) );\n\tif ( peak < StartCompression ) return color;\n\tfloat d = 1. - StartCompression;\n\tfloat newPeak = 1. - d * d / ( peak + d - StartCompression );\n\tcolor *= newPeak / peak;\n\tfloat g = 1. - 1. / ( Desaturation * ( peak - newPeak ) + 1. );\n\treturn mix( color, vec3( newPeak ), g );\n}\nvec3 CustomToneMapping( vec3 color ) { return color; }"; - const cosTheta = Math.cos( theta ); - const sinTheta = Math.sin( theta ); +var transmission_fragment = "#ifdef USE_TRANSMISSION\n\tmaterial.transmission = transmission;\n\tmaterial.transmissionAlpha = 1.0;\n\tmaterial.thickness = thickness;\n\tmaterial.attenuationDistance = attenuationDistance;\n\tmaterial.attenuationColor = attenuationColor;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tmaterial.transmission *= texture2D( transmissionMap, vTransmissionMapUv ).r;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tmaterial.thickness *= texture2D( thicknessMap, vThicknessMapUv ).g;\n\t#endif\n\tvec3 pos = vWorldPosition;\n\tvec3 v = normalize( cameraPosition - pos );\n\tvec3 n = inverseTransformDirection( normal, viewMatrix );\n\tvec4 transmitted = getIBLVolumeRefraction(\n\t\tn, v, material.roughness, material.diffuseColor, material.specularColor, material.specularF90,\n\t\tpos, modelMatrix, viewMatrix, projectionMatrix, material.dispersion, material.ior, material.thickness,\n\t\tmaterial.attenuationColor, material.attenuationDistance );\n\tmaterial.transmissionAlpha = mix( material.transmissionAlpha, transmitted.a, material.transmission );\n\ttotalDiffuse = mix( totalDiffuse, transmitted.rgb, material.transmission );\n#endif"; - // vertex +var transmission_pars_fragment = "#ifdef USE_TRANSMISSION\n\tuniform float transmission;\n\tuniform float thickness;\n\tuniform float attenuationDistance;\n\tuniform vec3 attenuationColor;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tuniform sampler2D transmissionMap;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tuniform sampler2D thicknessMap;\n\t#endif\n\tuniform vec2 transmissionSamplerSize;\n\tuniform sampler2D transmissionSamplerMap;\n\tuniform mat4 modelMatrix;\n\tuniform mat4 projectionMatrix;\n\tvarying vec3 vWorldPosition;\n\tfloat w0( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * ( a * ( - a + 3.0 ) - 3.0 ) + 1.0 );\n\t}\n\tfloat w1( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * a * ( 3.0 * a - 6.0 ) + 4.0 );\n\t}\n\tfloat w2( float a ){\n\t\treturn ( 1.0 / 6.0 ) * ( a * ( a * ( - 3.0 * a + 3.0 ) + 3.0 ) + 1.0 );\n\t}\n\tfloat w3( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * a * a );\n\t}\n\tfloat g0( float a ) {\n\t\treturn w0( a ) + w1( a );\n\t}\n\tfloat g1( float a ) {\n\t\treturn w2( a ) + w3( a );\n\t}\n\tfloat h0( float a ) {\n\t\treturn - 1.0 + w1( a ) / ( w0( a ) + w1( a ) );\n\t}\n\tfloat h1( float a ) {\n\t\treturn 1.0 + w3( a ) / ( w2( a ) + w3( a ) );\n\t}\n\tvec4 bicubic( sampler2D tex, vec2 uv, vec4 texelSize, float lod ) {\n\t\tuv = uv * texelSize.zw + 0.5;\n\t\tvec2 iuv = floor( uv );\n\t\tvec2 fuv = fract( uv );\n\t\tfloat g0x = g0( fuv.x );\n\t\tfloat g1x = g1( fuv.x );\n\t\tfloat h0x = h0( fuv.x );\n\t\tfloat h1x = h1( fuv.x );\n\t\tfloat h0y = h0( fuv.y );\n\t\tfloat h1y = h1( fuv.y );\n\t\tvec2 p0 = ( vec2( iuv.x + h0x, iuv.y + h0y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p1 = ( vec2( iuv.x + h1x, iuv.y + h0y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p2 = ( vec2( iuv.x + h0x, iuv.y + h1y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p3 = ( vec2( iuv.x + h1x, iuv.y + h1y ) - 0.5 ) * texelSize.xy;\n\t\treturn g0( fuv.y ) * ( g0x * textureLod( tex, p0, lod ) + g1x * textureLod( tex, p1, lod ) ) +\n\t\t\tg1( fuv.y ) * ( g0x * textureLod( tex, p2, lod ) + g1x * textureLod( tex, p3, lod ) );\n\t}\n\tvec4 textureBicubic( sampler2D sampler, vec2 uv, float lod ) {\n\t\tvec2 fLodSize = vec2( textureSize( sampler, int( lod ) ) );\n\t\tvec2 cLodSize = vec2( textureSize( sampler, int( lod + 1.0 ) ) );\n\t\tvec2 fLodSizeInv = 1.0 / fLodSize;\n\t\tvec2 cLodSizeInv = 1.0 / cLodSize;\n\t\tvec4 fSample = bicubic( sampler, uv, vec4( fLodSizeInv, fLodSize ), floor( lod ) );\n\t\tvec4 cSample = bicubic( sampler, uv, vec4( cLodSizeInv, cLodSize ), ceil( lod ) );\n\t\treturn mix( fSample, cSample, fract( lod ) );\n\t}\n\tvec3 getVolumeTransmissionRay( const in vec3 n, const in vec3 v, const in float thickness, const in float ior, const in mat4 modelMatrix ) {\n\t\tvec3 refractionVector = refract( - v, normalize( n ), 1.0 / ior );\n\t\tvec3 modelScale;\n\t\tmodelScale.x = length( vec3( modelMatrix[ 0 ].xyz ) );\n\t\tmodelScale.y = length( vec3( modelMatrix[ 1 ].xyz ) );\n\t\tmodelScale.z = length( vec3( modelMatrix[ 2 ].xyz ) );\n\t\treturn normalize( refractionVector ) * thickness * modelScale;\n\t}\n\tfloat applyIorToRoughness( const in float roughness, const in float ior ) {\n\t\treturn roughness * clamp( ior * 2.0 - 2.0, 0.0, 1.0 );\n\t}\n\tvec4 getTransmissionSample( const in vec2 fragCoord, const in float roughness, const in float ior ) {\n\t\tfloat lod = log2( transmissionSamplerSize.x ) * applyIorToRoughness( roughness, ior );\n\t\treturn textureBicubic( transmissionSamplerMap, fragCoord.xy, lod );\n\t}\n\tvec3 volumeAttenuation( const in float transmissionDistance, const in vec3 attenuationColor, const in float attenuationDistance ) {\n\t\tif ( isinf( attenuationDistance ) ) {\n\t\t\treturn vec3( 1.0 );\n\t\t} else {\n\t\t\tvec3 attenuationCoefficient = -log( attenuationColor ) / attenuationDistance;\n\t\t\tvec3 transmittance = exp( - attenuationCoefficient * transmissionDistance );\t\t\treturn transmittance;\n\t\t}\n\t}\n\tvec4 getIBLVolumeRefraction( const in vec3 n, const in vec3 v, const in float roughness, const in vec3 diffuseColor,\n\t\tconst in vec3 specularColor, const in float specularF90, const in vec3 position, const in mat4 modelMatrix,\n\t\tconst in mat4 viewMatrix, const in mat4 projMatrix, const in float dispersion, const in float ior, const in float thickness,\n\t\tconst in vec3 attenuationColor, const in float attenuationDistance ) {\n\t\tvec4 transmittedLight;\n\t\tvec3 transmittance;\n\t\t#ifdef USE_DISPERSION\n\t\t\tfloat halfSpread = ( ior - 1.0 ) * 0.025 * dispersion;\n\t\t\tvec3 iors = vec3( ior - halfSpread, ior, ior + halfSpread );\n\t\t\tfor ( int i = 0; i < 3; i ++ ) {\n\t\t\t\tvec3 transmissionRay = getVolumeTransmissionRay( n, v, thickness, iors[ i ], modelMatrix );\n\t\t\t\tvec3 refractedRayExit = position + transmissionRay;\n\t\t\t\tvec4 ndcPos = projMatrix * viewMatrix * vec4( refractedRayExit, 1.0 );\n\t\t\t\tvec2 refractionCoords = ndcPos.xy / ndcPos.w;\n\t\t\t\trefractionCoords += 1.0;\n\t\t\t\trefractionCoords /= 2.0;\n\t\t\t\tvec4 transmissionSample = getTransmissionSample( refractionCoords, roughness, iors[ i ] );\n\t\t\t\ttransmittedLight[ i ] = transmissionSample[ i ];\n\t\t\t\ttransmittedLight.a += transmissionSample.a;\n\t\t\t\ttransmittance[ i ] = diffuseColor[ i ] * volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance )[ i ];\n\t\t\t}\n\t\t\ttransmittedLight.a /= 3.0;\n\t\t#else\n\t\t\tvec3 transmissionRay = getVolumeTransmissionRay( n, v, thickness, ior, modelMatrix );\n\t\t\tvec3 refractedRayExit = position + transmissionRay;\n\t\t\tvec4 ndcPos = projMatrix * viewMatrix * vec4( refractedRayExit, 1.0 );\n\t\t\tvec2 refractionCoords = ndcPos.xy / ndcPos.w;\n\t\t\trefractionCoords += 1.0;\n\t\t\trefractionCoords /= 2.0;\n\t\t\ttransmittedLight = getTransmissionSample( refractionCoords, roughness, ior );\n\t\t\ttransmittance = diffuseColor * volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance );\n\t\t#endif\n\t\tvec3 attenuatedColor = transmittance * transmittedLight.rgb;\n\t\tvec3 F = EnvironmentBRDF( n, v, specularColor, specularF90, roughness );\n\t\tfloat transmittanceFactor = ( transmittance.r + transmittance.g + transmittance.b ) / 3.0;\n\t\treturn vec4( ( 1.0 - F ) * attenuatedColor, 1.0 - ( 1.0 - transmittedLight.a ) * transmittanceFactor );\n\t}\n#endif"; - vertex.x = radius * sinTheta; - vertex.y = halfHeight * sign; - vertex.z = radius * cosTheta; - vertices.push( vertex.x, vertex.y, vertex.z ); +var uv_pars_fragment = "#if defined( USE_UV ) || defined( USE_ANISOTROPY )\n\tvarying vec2 vUv;\n#endif\n#ifdef USE_MAP\n\tvarying vec2 vMapUv;\n#endif\n#ifdef USE_ALPHAMAP\n\tvarying vec2 vAlphaMapUv;\n#endif\n#ifdef USE_LIGHTMAP\n\tvarying vec2 vLightMapUv;\n#endif\n#ifdef USE_AOMAP\n\tvarying vec2 vAoMapUv;\n#endif\n#ifdef USE_BUMPMAP\n\tvarying vec2 vBumpMapUv;\n#endif\n#ifdef USE_NORMALMAP\n\tvarying vec2 vNormalMapUv;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tvarying vec2 vEmissiveMapUv;\n#endif\n#ifdef USE_METALNESSMAP\n\tvarying vec2 vMetalnessMapUv;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tvarying vec2 vRoughnessMapUv;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tvarying vec2 vAnisotropyMapUv;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tvarying vec2 vClearcoatMapUv;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tvarying vec2 vClearcoatNormalMapUv;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tvarying vec2 vClearcoatRoughnessMapUv;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tvarying vec2 vIridescenceMapUv;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tvarying vec2 vIridescenceThicknessMapUv;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tvarying vec2 vSheenColorMapUv;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tvarying vec2 vSheenRoughnessMapUv;\n#endif\n#ifdef USE_SPECULARMAP\n\tvarying vec2 vSpecularMapUv;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tvarying vec2 vSpecularColorMapUv;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tvarying vec2 vSpecularIntensityMapUv;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tuniform mat3 transmissionMapTransform;\n\tvarying vec2 vTransmissionMapUv;\n#endif\n#ifdef USE_THICKNESSMAP\n\tuniform mat3 thicknessMapTransform;\n\tvarying vec2 vThicknessMapUv;\n#endif"; - // normal +var uv_pars_vertex = "#if defined( USE_UV ) || defined( USE_ANISOTROPY )\n\tvarying vec2 vUv;\n#endif\n#ifdef USE_MAP\n\tuniform mat3 mapTransform;\n\tvarying vec2 vMapUv;\n#endif\n#ifdef USE_ALPHAMAP\n\tuniform mat3 alphaMapTransform;\n\tvarying vec2 vAlphaMapUv;\n#endif\n#ifdef USE_LIGHTMAP\n\tuniform mat3 lightMapTransform;\n\tvarying vec2 vLightMapUv;\n#endif\n#ifdef USE_AOMAP\n\tuniform mat3 aoMapTransform;\n\tvarying vec2 vAoMapUv;\n#endif\n#ifdef USE_BUMPMAP\n\tuniform mat3 bumpMapTransform;\n\tvarying vec2 vBumpMapUv;\n#endif\n#ifdef USE_NORMALMAP\n\tuniform mat3 normalMapTransform;\n\tvarying vec2 vNormalMapUv;\n#endif\n#ifdef USE_DISPLACEMENTMAP\n\tuniform mat3 displacementMapTransform;\n\tvarying vec2 vDisplacementMapUv;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tuniform mat3 emissiveMapTransform;\n\tvarying vec2 vEmissiveMapUv;\n#endif\n#ifdef USE_METALNESSMAP\n\tuniform mat3 metalnessMapTransform;\n\tvarying vec2 vMetalnessMapUv;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tuniform mat3 roughnessMapTransform;\n\tvarying vec2 vRoughnessMapUv;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tuniform mat3 anisotropyMapTransform;\n\tvarying vec2 vAnisotropyMapUv;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tuniform mat3 clearcoatMapTransform;\n\tvarying vec2 vClearcoatMapUv;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tuniform mat3 clearcoatNormalMapTransform;\n\tvarying vec2 vClearcoatNormalMapUv;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tuniform mat3 clearcoatRoughnessMapTransform;\n\tvarying vec2 vClearcoatRoughnessMapUv;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tuniform mat3 sheenColorMapTransform;\n\tvarying vec2 vSheenColorMapUv;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tuniform mat3 sheenRoughnessMapTransform;\n\tvarying vec2 vSheenRoughnessMapUv;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tuniform mat3 iridescenceMapTransform;\n\tvarying vec2 vIridescenceMapUv;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tuniform mat3 iridescenceThicknessMapTransform;\n\tvarying vec2 vIridescenceThicknessMapUv;\n#endif\n#ifdef USE_SPECULARMAP\n\tuniform mat3 specularMapTransform;\n\tvarying vec2 vSpecularMapUv;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tuniform mat3 specularColorMapTransform;\n\tvarying vec2 vSpecularColorMapUv;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tuniform mat3 specularIntensityMapTransform;\n\tvarying vec2 vSpecularIntensityMapUv;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tuniform mat3 transmissionMapTransform;\n\tvarying vec2 vTransmissionMapUv;\n#endif\n#ifdef USE_THICKNESSMAP\n\tuniform mat3 thicknessMapTransform;\n\tvarying vec2 vThicknessMapUv;\n#endif"; - normals.push( 0, sign, 0 ); +var uv_vertex = "#if defined( USE_UV ) || defined( USE_ANISOTROPY )\n\tvUv = vec3( uv, 1 ).xy;\n#endif\n#ifdef USE_MAP\n\tvMapUv = ( mapTransform * vec3( MAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ALPHAMAP\n\tvAlphaMapUv = ( alphaMapTransform * vec3( ALPHAMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_LIGHTMAP\n\tvLightMapUv = ( lightMapTransform * vec3( LIGHTMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_AOMAP\n\tvAoMapUv = ( aoMapTransform * vec3( AOMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_BUMPMAP\n\tvBumpMapUv = ( bumpMapTransform * vec3( BUMPMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_NORMALMAP\n\tvNormalMapUv = ( normalMapTransform * vec3( NORMALMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_DISPLACEMENTMAP\n\tvDisplacementMapUv = ( displacementMapTransform * vec3( DISPLACEMENTMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tvEmissiveMapUv = ( emissiveMapTransform * vec3( EMISSIVEMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_METALNESSMAP\n\tvMetalnessMapUv = ( metalnessMapTransform * vec3( METALNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tvRoughnessMapUv = ( roughnessMapTransform * vec3( ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tvAnisotropyMapUv = ( anisotropyMapTransform * vec3( ANISOTROPYMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tvClearcoatMapUv = ( clearcoatMapTransform * vec3( CLEARCOATMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tvClearcoatNormalMapUv = ( clearcoatNormalMapTransform * vec3( CLEARCOAT_NORMALMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tvClearcoatRoughnessMapUv = ( clearcoatRoughnessMapTransform * vec3( CLEARCOAT_ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tvIridescenceMapUv = ( iridescenceMapTransform * vec3( IRIDESCENCEMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tvIridescenceThicknessMapUv = ( iridescenceThicknessMapTransform * vec3( IRIDESCENCE_THICKNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tvSheenColorMapUv = ( sheenColorMapTransform * vec3( SHEEN_COLORMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tvSheenRoughnessMapUv = ( sheenRoughnessMapTransform * vec3( SHEEN_ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULARMAP\n\tvSpecularMapUv = ( specularMapTransform * vec3( SPECULARMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tvSpecularColorMapUv = ( specularColorMapTransform * vec3( SPECULAR_COLORMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tvSpecularIntensityMapUv = ( specularIntensityMapTransform * vec3( SPECULAR_INTENSITYMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tvTransmissionMapUv = ( transmissionMapTransform * vec3( TRANSMISSIONMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_THICKNESSMAP\n\tvThicknessMapUv = ( thicknessMapTransform * vec3( THICKNESSMAP_UV, 1 ) ).xy;\n#endif"; - // uv +var worldpos_vertex = "#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined ( USE_SHADOWMAP ) || defined ( USE_TRANSMISSION ) || NUM_SPOT_LIGHT_COORDS > 0\n\tvec4 worldPosition = vec4( transformed, 1.0 );\n\t#ifdef USE_BATCHING\n\t\tworldPosition = batchingMatrix * worldPosition;\n\t#endif\n\t#ifdef USE_INSTANCING\n\t\tworldPosition = instanceMatrix * worldPosition;\n\t#endif\n\tworldPosition = modelMatrix * worldPosition;\n#endif"; - uv.x = ( cosTheta * 0.5 ) + 0.5; - uv.y = ( sinTheta * 0.5 * sign ) + 0.5; - uvs.push( uv.x, uv.y ); +const vertex$h = "varying vec2 vUv;\nuniform mat3 uvTransform;\nvoid main() {\n\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n\tgl_Position = vec4( position.xy, 1.0, 1.0 );\n}"; - // increase index +const fragment$h = "uniform sampler2D t2D;\nuniform float backgroundIntensity;\nvarying vec2 vUv;\nvoid main() {\n\tvec4 texColor = texture2D( t2D, vUv );\n\t#ifdef DECODE_VIDEO_TEXTURE\n\t\ttexColor = vec4( mix( pow( texColor.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), texColor.rgb * 0.0773993808, vec3( lessThanEqual( texColor.rgb, vec3( 0.04045 ) ) ) ), texColor.w );\n\t#endif\n\ttexColor.rgb *= backgroundIntensity;\n\tgl_FragColor = texColor;\n\t#include \n\t#include \n}"; - index ++; +const vertex$g = "varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n\tgl_Position.z = gl_Position.w;\n}"; - } +const fragment$g = "#ifdef ENVMAP_TYPE_CUBE\n\tuniform samplerCube envMap;\n#elif defined( ENVMAP_TYPE_CUBE_UV )\n\tuniform sampler2D envMap;\n#endif\nuniform float flipEnvMap;\nuniform float backgroundBlurriness;\nuniform float backgroundIntensity;\nuniform mat3 backgroundRotation;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 texColor = textureCube( envMap, backgroundRotation * vec3( flipEnvMap * vWorldDirection.x, vWorldDirection.yz ) );\n\t#elif defined( ENVMAP_TYPE_CUBE_UV )\n\t\tvec4 texColor = textureCubeUV( envMap, backgroundRotation * vWorldDirection, backgroundBlurriness );\n\t#else\n\t\tvec4 texColor = vec4( 0.0, 0.0, 0.0, 1.0 );\n\t#endif\n\ttexColor.rgb *= backgroundIntensity;\n\tgl_FragColor = texColor;\n\t#include \n\t#include \n}"; - // generate indices +const vertex$f = "varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n\tgl_Position.z = gl_Position.w;\n}"; - for ( let x = 0; x < radialSegments; x ++ ) { +const fragment$f = "uniform samplerCube tCube;\nuniform float tFlip;\nuniform float opacity;\nvarying vec3 vWorldDirection;\nvoid main() {\n\tvec4 texColor = textureCube( tCube, vec3( tFlip * vWorldDirection.x, vWorldDirection.yz ) );\n\tgl_FragColor = texColor;\n\tgl_FragColor.a *= opacity;\n\t#include \n\t#include \n}"; - const c = centerIndexStart + x; - const i = centerIndexEnd + x; +const vertex$e = "#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvHighPrecisionZW = gl_Position.zw;\n}"; - if ( top === true ) { +const fragment$e = "#if DEPTH_PACKING == 3200\n\tuniform float opacity;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include \n\t#if DEPTH_PACKING == 3200\n\t\tdiffuseColor.a = opacity;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tfloat fragCoordZ = 0.5 * vHighPrecisionZW[0] / vHighPrecisionZW[1] + 0.5;\n\t#if DEPTH_PACKING == 3200\n\t\tgl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity );\n\t#elif DEPTH_PACKING == 3201\n\t\tgl_FragColor = packDepthToRGBA( fragCoordZ );\n\t#elif DEPTH_PACKING == 3202\n\t\tgl_FragColor = vec4( packDepthToRGB( fragCoordZ ), 1.0 );\n\t#elif DEPTH_PACKING == 3203\n\t\tgl_FragColor = vec4( packDepthToRG( fragCoordZ ), 0.0, 1.0 );\n\t#endif\n}"; - // face top +const vertex$d = "#define DISTANCE\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvWorldPosition = worldPosition.xyz;\n}"; - indices.push( i, i + 1, c ); +const fragment$d = "#define DISTANCE\nuniform vec3 referencePosition;\nuniform float nearDistance;\nuniform float farDistance;\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main () {\n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tfloat dist = length( vWorldPosition - referencePosition );\n\tdist = ( dist - nearDistance ) / ( farDistance - nearDistance );\n\tdist = saturate( dist );\n\tgl_FragColor = packDepthToRGBA( dist );\n}"; - } else { +const vertex$c = "varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n}"; - // face bottom +const fragment$c = "uniform sampler2D tEquirect;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvec3 direction = normalize( vWorldDirection );\n\tvec2 sampleUV = equirectUv( direction );\n\tgl_FragColor = texture2D( tEquirect, sampleUV );\n\t#include \n\t#include \n}"; - indices.push( i + 1, i, c ); +const vertex$b = "uniform float scale;\nattribute float lineDistance;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvLineDistance = scale * lineDistance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - } +const fragment$b = "uniform vec3 diffuse;\nuniform float opacity;\nuniform float dashSize;\nuniform float totalSize;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tif ( mod( vLineDistance, totalSize ) > dashSize ) {\n\t\tdiscard;\n\t}\n\tvec3 outgoingLight = vec3( 0.0 );\n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - groupCount += 3; +const vertex$a = "#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#if defined ( USE_ENVMAP ) || defined ( USE_SKINNING )\n\t\t#include \n\t\t#include \n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - } +const fragment$a = "uniform vec3 diffuse;\nuniform float opacity;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel = texture2D( lightMap, vLightMapUv );\n\t\treflectedLight.indirectDiffuse += lightMapTexel.rgb * lightMapIntensity * RECIPROCAL_PI;\n\t#else\n\t\treflectedLight.indirectDiffuse += vec3( 1.0 );\n\t#endif\n\t#include \n\treflectedLight.indirectDiffuse *= diffuseColor.rgb;\n\tvec3 outgoingLight = reflectedLight.indirectDiffuse;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - // add a group to the geometry. this will ensure multi material support +const vertex$9 = "#define LAMBERT\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n\t#include \n}"; - scope.addGroup( groupStart, groupCount, top === true ? 1 : 2 ); +const fragment$9 = "#define LAMBERT\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - // calculate new start value for groups +const vertex$8 = "#define MATCAP\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n}"; - groupStart += groupCount; +const fragment$8 = "#define MATCAP\nuniform vec3 diffuse;\nuniform float opacity;\nuniform sampler2D matcap;\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 viewDir = normalize( vViewPosition );\n\tvec3 x = normalize( vec3( viewDir.z, 0.0, - viewDir.x ) );\n\tvec3 y = cross( viewDir, x );\n\tvec2 uv = vec2( dot( x, normal ), dot( y, normal ) ) * 0.495 + 0.5;\n\t#ifdef USE_MATCAP\n\t\tvec4 matcapColor = texture2D( matcap, uv );\n\t#else\n\t\tvec4 matcapColor = vec4( vec3( mix( 0.2, 0.8, uv.y ) ), 1.0 );\n\t#endif\n\tvec3 outgoingLight = diffuseColor.rgb * matcapColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - } +const vertex$7 = "#define NORMAL\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvarying vec3 vViewPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvViewPosition = - mvPosition.xyz;\n#endif\n}"; - } +const fragment$7 = "#define NORMAL\nuniform float opacity;\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvarying vec3 vViewPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( 0.0, 0.0, 0.0, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\tgl_FragColor = vec4( packNormalToRGB( normal ), diffuseColor.a );\n\t#ifdef OPAQUE\n\t\tgl_FragColor.a = 1.0;\n\t#endif\n}"; - copy( source ) { +const vertex$6 = "#define PHONG\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n\t#include \n}"; - super.copy( source ); +const fragment$6 = "#define PHONG\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform vec3 specular;\nuniform float shininess;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - this.parameters = Object.assign( {}, source.parameters ); +const vertex$5 = "#define STANDARD\nvarying vec3 vViewPosition;\n#ifdef USE_TRANSMISSION\n\tvarying vec3 vWorldPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n#ifdef USE_TRANSMISSION\n\tvWorldPosition = worldPosition.xyz;\n#endif\n}"; - return this; +const fragment$5 = "#define STANDARD\n#ifdef PHYSICAL\n\t#define IOR\n\t#define USE_SPECULAR\n#endif\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float roughness;\nuniform float metalness;\nuniform float opacity;\n#ifdef IOR\n\tuniform float ior;\n#endif\n#ifdef USE_SPECULAR\n\tuniform float specularIntensity;\n\tuniform vec3 specularColor;\n\t#ifdef USE_SPECULAR_COLORMAP\n\t\tuniform sampler2D specularColorMap;\n\t#endif\n\t#ifdef USE_SPECULAR_INTENSITYMAP\n\t\tuniform sampler2D specularIntensityMap;\n\t#endif\n#endif\n#ifdef USE_CLEARCOAT\n\tuniform float clearcoat;\n\tuniform float clearcoatRoughness;\n#endif\n#ifdef USE_DISPERSION\n\tuniform float dispersion;\n#endif\n#ifdef USE_IRIDESCENCE\n\tuniform float iridescence;\n\tuniform float iridescenceIOR;\n\tuniform float iridescenceThicknessMinimum;\n\tuniform float iridescenceThicknessMaximum;\n#endif\n#ifdef USE_SHEEN\n\tuniform vec3 sheenColor;\n\tuniform float sheenRoughness;\n\t#ifdef USE_SHEEN_COLORMAP\n\t\tuniform sampler2D sheenColorMap;\n\t#endif\n\t#ifdef USE_SHEEN_ROUGHNESSMAP\n\t\tuniform sampler2D sheenRoughnessMap;\n\t#endif\n#endif\n#ifdef USE_ANISOTROPY\n\tuniform vec2 anisotropyVector;\n\t#ifdef USE_ANISOTROPYMAP\n\t\tuniform sampler2D anisotropyMap;\n\t#endif\n#endif\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 totalDiffuse = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse;\n\tvec3 totalSpecular = reflectedLight.directSpecular + reflectedLight.indirectSpecular;\n\t#include \n\tvec3 outgoingLight = totalDiffuse + totalSpecular + totalEmissiveRadiance;\n\t#ifdef USE_SHEEN\n\t\tfloat sheenEnergyComp = 1.0 - 0.157 * max3( material.sheenColor );\n\t\toutgoingLight = outgoingLight * sheenEnergyComp + sheenSpecularDirect + sheenSpecularIndirect;\n\t#endif\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNVcc = saturate( dot( geometryClearcoatNormal, geometryViewDir ) );\n\t\tvec3 Fcc = F_Schlick( material.clearcoatF0, material.clearcoatF90, dotNVcc );\n\t\toutgoingLight = outgoingLight * ( 1.0 - material.clearcoat * Fcc ) + ( clearcoatSpecularDirect + clearcoatSpecularIndirect ) * material.clearcoat;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - } +const vertex$4 = "#define TOON\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n}"; - static fromJSON( data ) { +const fragment$4 = "#define TOON\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - return new CylinderGeometry( data.radiusTop, data.radiusBottom, data.height, data.radialSegments, data.heightSegments, data.openEnded, data.thetaStart, data.thetaLength ); +const vertex$3 = "uniform float size;\nuniform float scale;\n#include \n#include \n#include \n#include \n#include \n#include \n#ifdef USE_POINTS_UV\n\tvarying vec2 vUv;\n\tuniform mat3 uvTransform;\n#endif\nvoid main() {\n\t#ifdef USE_POINTS_UV\n\t\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tgl_PointSize = size;\n\t#ifdef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) gl_PointSize *= ( scale / - mvPosition.z );\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n}"; - } +const fragment$3 = "uniform vec3 diffuse;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tvec3 outgoingLight = vec3( 0.0 );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; -} +const vertex$2 = "#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; -class ConeGeometry extends CylinderGeometry { +const fragment$2 = "uniform vec3 color;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tgl_FragColor = vec4( color, opacity * ( 1.0 - getShadowMask() ) );\n\t#include \n\t#include \n\t#include \n}"; - constructor( radius = 1, height = 1, radialSegments = 32, heightSegments = 1, openEnded = false, thetaStart = 0, thetaLength = Math.PI * 2 ) { +const vertex$1 = "uniform float rotation;\nuniform vec2 center;\n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 mvPosition = modelViewMatrix[ 3 ];\n\tvec2 scale = vec2( length( modelMatrix[ 0 ].xyz ), length( modelMatrix[ 1 ].xyz ) );\n\t#ifndef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) scale *= - mvPosition.z;\n\t#endif\n\tvec2 alignedPosition = ( position.xy - ( center - vec2( 0.5 ) ) ) * scale;\n\tvec2 rotatedPosition;\n\trotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;\n\trotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;\n\tmvPosition.xy += rotatedPosition;\n\tgl_Position = projectionMatrix * mvPosition;\n\t#include \n\t#include \n\t#include \n}"; - super( 0, radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ); +const fragment$1 = "uniform vec3 diffuse;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tvec3 outgoingLight = vec3( 0.0 );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n}"; - this.type = 'ConeGeometry'; +const ShaderChunk = { + alphahash_fragment: alphahash_fragment, + alphahash_pars_fragment: alphahash_pars_fragment, + alphamap_fragment: alphamap_fragment, + alphamap_pars_fragment: alphamap_pars_fragment, + alphatest_fragment: alphatest_fragment, + alphatest_pars_fragment: alphatest_pars_fragment, + aomap_fragment: aomap_fragment, + aomap_pars_fragment: aomap_pars_fragment, + batching_pars_vertex: batching_pars_vertex, + batching_vertex: batching_vertex, + begin_vertex: begin_vertex, + beginnormal_vertex: beginnormal_vertex, + bsdfs: bsdfs, + iridescence_fragment: iridescence_fragment, + bumpmap_pars_fragment: bumpmap_pars_fragment, + clipping_planes_fragment: clipping_planes_fragment, + clipping_planes_pars_fragment: clipping_planes_pars_fragment, + clipping_planes_pars_vertex: clipping_planes_pars_vertex, + clipping_planes_vertex: clipping_planes_vertex, + color_fragment: color_fragment, + color_pars_fragment: color_pars_fragment, + color_pars_vertex: color_pars_vertex, + color_vertex: color_vertex, + common: common, + cube_uv_reflection_fragment: cube_uv_reflection_fragment, + defaultnormal_vertex: defaultnormal_vertex, + displacementmap_pars_vertex: displacementmap_pars_vertex, + displacementmap_vertex: displacementmap_vertex, + emissivemap_fragment: emissivemap_fragment, + emissivemap_pars_fragment: emissivemap_pars_fragment, + colorspace_fragment: colorspace_fragment, + colorspace_pars_fragment: colorspace_pars_fragment, + envmap_fragment: envmap_fragment, + envmap_common_pars_fragment: envmap_common_pars_fragment, + envmap_pars_fragment: envmap_pars_fragment, + envmap_pars_vertex: envmap_pars_vertex, + envmap_physical_pars_fragment: envmap_physical_pars_fragment, + envmap_vertex: envmap_vertex, + fog_vertex: fog_vertex, + fog_pars_vertex: fog_pars_vertex, + fog_fragment: fog_fragment, + fog_pars_fragment: fog_pars_fragment, + gradientmap_pars_fragment: gradientmap_pars_fragment, + lightmap_pars_fragment: lightmap_pars_fragment, + lights_lambert_fragment: lights_lambert_fragment, + lights_lambert_pars_fragment: lights_lambert_pars_fragment, + lights_pars_begin: lights_pars_begin, + lights_toon_fragment: lights_toon_fragment, + lights_toon_pars_fragment: lights_toon_pars_fragment, + lights_phong_fragment: lights_phong_fragment, + lights_phong_pars_fragment: lights_phong_pars_fragment, + lights_physical_fragment: lights_physical_fragment, + lights_physical_pars_fragment: lights_physical_pars_fragment, + lights_fragment_begin: lights_fragment_begin, + lights_fragment_maps: lights_fragment_maps, + lights_fragment_end: lights_fragment_end, + logdepthbuf_fragment: logdepthbuf_fragment, + logdepthbuf_pars_fragment: logdepthbuf_pars_fragment, + logdepthbuf_pars_vertex: logdepthbuf_pars_vertex, + logdepthbuf_vertex: logdepthbuf_vertex, + map_fragment: map_fragment, + map_pars_fragment: map_pars_fragment, + map_particle_fragment: map_particle_fragment, + map_particle_pars_fragment: map_particle_pars_fragment, + metalnessmap_fragment: metalnessmap_fragment, + metalnessmap_pars_fragment: metalnessmap_pars_fragment, + morphinstance_vertex: morphinstance_vertex, + morphcolor_vertex: morphcolor_vertex, + morphnormal_vertex: morphnormal_vertex, + morphtarget_pars_vertex: morphtarget_pars_vertex, + morphtarget_vertex: morphtarget_vertex, + normal_fragment_begin: normal_fragment_begin, + normal_fragment_maps: normal_fragment_maps, + normal_pars_fragment: normal_pars_fragment, + normal_pars_vertex: normal_pars_vertex, + normal_vertex: normal_vertex, + normalmap_pars_fragment: normalmap_pars_fragment, + clearcoat_normal_fragment_begin: clearcoat_normal_fragment_begin, + clearcoat_normal_fragment_maps: clearcoat_normal_fragment_maps, + clearcoat_pars_fragment: clearcoat_pars_fragment, + iridescence_pars_fragment: iridescence_pars_fragment, + opaque_fragment: opaque_fragment, + packing: packing, + premultiplied_alpha_fragment: premultiplied_alpha_fragment, + project_vertex: project_vertex, + dithering_fragment: dithering_fragment, + dithering_pars_fragment: dithering_pars_fragment, + roughnessmap_fragment: roughnessmap_fragment, + roughnessmap_pars_fragment: roughnessmap_pars_fragment, + shadowmap_pars_fragment: shadowmap_pars_fragment, + shadowmap_pars_vertex: shadowmap_pars_vertex, + shadowmap_vertex: shadowmap_vertex, + shadowmask_pars_fragment: shadowmask_pars_fragment, + skinbase_vertex: skinbase_vertex, + skinning_pars_vertex: skinning_pars_vertex, + skinning_vertex: skinning_vertex, + skinnormal_vertex: skinnormal_vertex, + specularmap_fragment: specularmap_fragment, + specularmap_pars_fragment: specularmap_pars_fragment, + tonemapping_fragment: tonemapping_fragment, + tonemapping_pars_fragment: tonemapping_pars_fragment, + transmission_fragment: transmission_fragment, + transmission_pars_fragment: transmission_pars_fragment, + uv_pars_fragment: uv_pars_fragment, + uv_pars_vertex: uv_pars_vertex, + uv_vertex: uv_vertex, + worldpos_vertex: worldpos_vertex, - this.parameters = { - radius: radius, - height: height, - radialSegments: radialSegments, - heightSegments: heightSegments, - openEnded: openEnded, - thetaStart: thetaStart, - thetaLength: thetaLength - }; + background_vert: vertex$h, + background_frag: fragment$h, + backgroundCube_vert: vertex$g, + backgroundCube_frag: fragment$g, + cube_vert: vertex$f, + cube_frag: fragment$f, + depth_vert: vertex$e, + depth_frag: fragment$e, + distanceRGBA_vert: vertex$d, + distanceRGBA_frag: fragment$d, + equirect_vert: vertex$c, + equirect_frag: fragment$c, + linedashed_vert: vertex$b, + linedashed_frag: fragment$b, + meshbasic_vert: vertex$a, + meshbasic_frag: fragment$a, + meshlambert_vert: vertex$9, + meshlambert_frag: fragment$9, + meshmatcap_vert: vertex$8, + meshmatcap_frag: fragment$8, + meshnormal_vert: vertex$7, + meshnormal_frag: fragment$7, + meshphong_vert: vertex$6, + meshphong_frag: fragment$6, + meshphysical_vert: vertex$5, + meshphysical_frag: fragment$5, + meshtoon_vert: vertex$4, + meshtoon_frag: fragment$4, + points_vert: vertex$3, + points_frag: fragment$3, + shadow_vert: vertex$2, + shadow_frag: fragment$2, + sprite_vert: vertex$1, + sprite_frag: fragment$1 +}; - } +// Uniforms library for shared webgl shaders +const UniformsLib = { - static fromJSON( data ) { + common: { - return new ConeGeometry( data.radius, data.height, data.radialSegments, data.heightSegments, data.openEnded, data.thetaStart, data.thetaLength ); + diffuse: { value: /*@__PURE__*/ new Color( 0xffffff ) }, + opacity: { value: 1.0 }, - } + map: { value: null }, + mapTransform: { value: /*@__PURE__*/ new Matrix3() }, -} + alphaMap: { value: null }, + alphaMapTransform: { value: /*@__PURE__*/ new Matrix3() }, -class PolyhedronGeometry extends BufferGeometry { + alphaTest: { value: 0 } - constructor( vertices = [], indices = [], radius = 1, detail = 0 ) { + }, - super(); + specularmap: { - this.type = 'PolyhedronGeometry'; + specularMap: { value: null }, + specularMapTransform: { value: /*@__PURE__*/ new Matrix3() } - this.parameters = { - vertices: vertices, - indices: indices, - radius: radius, - detail: detail - }; + }, - // default buffer data + envmap: { - const vertexBuffer = []; - const uvBuffer = []; + envMap: { value: null }, + envMapRotation: { value: /*@__PURE__*/ new Matrix3() }, + flipEnvMap: { value: -1 }, + reflectivity: { value: 1.0 }, // basic, lambert, phong + ior: { value: 1.5 }, // physical + refractionRatio: { value: 0.98 }, // basic, lambert, phong - // the subdivision creates the vertex buffer data + }, - subdivide( detail ); + aomap: { - // all vertices should lie on a conceptual sphere with a given radius + aoMap: { value: null }, + aoMapIntensity: { value: 1 }, + aoMapTransform: { value: /*@__PURE__*/ new Matrix3() } - applyRadius( radius ); + }, - // finally, create the uv data + lightmap: { - generateUVs(); + lightMap: { value: null }, + lightMapIntensity: { value: 1 }, + lightMapTransform: { value: /*@__PURE__*/ new Matrix3() } - // build non-indexed geometry + }, - this.setAttribute( 'position', new Float32BufferAttribute( vertexBuffer, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( vertexBuffer.slice(), 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvBuffer, 2 ) ); + bumpmap: { - if ( detail === 0 ) { + bumpMap: { value: null }, + bumpMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + bumpScale: { value: 1 } - this.computeVertexNormals(); // flat normals + }, - } else { + normalmap: { - this.normalizeNormals(); // smooth normals + normalMap: { value: null }, + normalMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + normalScale: { value: /*@__PURE__*/ new Vector2( 1, 1 ) } - } + }, - // helper functions + displacementmap: { - function subdivide( detail ) { + displacementMap: { value: null }, + displacementMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + displacementScale: { value: 1 }, + displacementBias: { value: 0 } - const a = new Vector3(); - const b = new Vector3(); - const c = new Vector3(); + }, - // iterate over all faces and apply a subdivision with the given detail value + emissivemap: { - for ( let i = 0; i < indices.length; i += 3 ) { + emissiveMap: { value: null }, + emissiveMapTransform: { value: /*@__PURE__*/ new Matrix3() } - // get the vertices of the face + }, - getVertexByIndex( indices[ i + 0 ], a ); - getVertexByIndex( indices[ i + 1 ], b ); - getVertexByIndex( indices[ i + 2 ], c ); + metalnessmap: { - // perform subdivision + metalnessMap: { value: null }, + metalnessMapTransform: { value: /*@__PURE__*/ new Matrix3() } - subdivideFace( a, b, c, detail ); + }, - } + roughnessmap: { - } + roughnessMap: { value: null }, + roughnessMapTransform: { value: /*@__PURE__*/ new Matrix3() } - function subdivideFace( a, b, c, detail ) { + }, - const cols = detail + 1; + gradientmap: { - // we use this multidimensional array as a data structure for creating the subdivision + gradientMap: { value: null } - const v = []; + }, - // construct all of the vertices for this subdivision + fog: { - for ( let i = 0; i <= cols; i ++ ) { + fogDensity: { value: 0.00025 }, + fogNear: { value: 1 }, + fogFar: { value: 2000 }, + fogColor: { value: /*@__PURE__*/ new Color( 0xffffff ) } - v[ i ] = []; + }, - const aj = a.clone().lerp( c, i / cols ); - const bj = b.clone().lerp( c, i / cols ); + lights: { - const rows = cols - i; + ambientLightColor: { value: [] }, - for ( let j = 0; j <= rows; j ++ ) { + lightProbe: { value: [] }, - if ( j === 0 && i === cols ) { + directionalLights: { value: [], properties: { + direction: {}, + color: {} + } }, - v[ i ][ j ] = aj; + directionalLightShadows: { value: [], properties: { + shadowIntensity: 1, + shadowBias: {}, + shadowNormalBias: {}, + shadowRadius: {}, + shadowMapSize: {} + } }, - } else { + directionalShadowMap: { value: [] }, + directionalShadowMatrix: { value: [] }, - v[ i ][ j ] = aj.clone().lerp( bj, j / rows ); + spotLights: { value: [], properties: { + color: {}, + position: {}, + direction: {}, + distance: {}, + coneCos: {}, + penumbraCos: {}, + decay: {} + } }, - } + spotLightShadows: { value: [], properties: { + shadowIntensity: 1, + shadowBias: {}, + shadowNormalBias: {}, + shadowRadius: {}, + shadowMapSize: {} + } }, - } + spotLightMap: { value: [] }, + spotShadowMap: { value: [] }, + spotLightMatrix: { value: [] }, - } + pointLights: { value: [], properties: { + color: {}, + position: {}, + decay: {}, + distance: {} + } }, - // construct all of the faces + pointLightShadows: { value: [], properties: { + shadowIntensity: 1, + shadowBias: {}, + shadowNormalBias: {}, + shadowRadius: {}, + shadowMapSize: {}, + shadowCameraNear: {}, + shadowCameraFar: {} + } }, - for ( let i = 0; i < cols; i ++ ) { + pointShadowMap: { value: [] }, + pointShadowMatrix: { value: [] }, - for ( let j = 0; j < 2 * ( cols - i ) - 1; j ++ ) { + hemisphereLights: { value: [], properties: { + direction: {}, + skyColor: {}, + groundColor: {} + } }, - const k = Math.floor( j / 2 ); + // TODO (abelnation): RectAreaLight BRDF data needs to be moved from example to main src + rectAreaLights: { value: [], properties: { + color: {}, + position: {}, + width: {}, + height: {} + } }, - if ( j % 2 === 0 ) { + ltc_1: { value: null }, + ltc_2: { value: null } - pushVertex( v[ i ][ k + 1 ] ); - pushVertex( v[ i + 1 ][ k ] ); - pushVertex( v[ i ][ k ] ); + }, - } else { + points: { - pushVertex( v[ i ][ k + 1 ] ); - pushVertex( v[ i + 1 ][ k + 1 ] ); - pushVertex( v[ i + 1 ][ k ] ); + diffuse: { value: /*@__PURE__*/ new Color( 0xffffff ) }, + opacity: { value: 1.0 }, + size: { value: 1.0 }, + scale: { value: 1.0 }, + map: { value: null }, + alphaMap: { value: null }, + alphaMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + alphaTest: { value: 0 }, + uvTransform: { value: /*@__PURE__*/ new Matrix3() } - } + }, - } + sprite: { - } + diffuse: { value: /*@__PURE__*/ new Color( 0xffffff ) }, + opacity: { value: 1.0 }, + center: { value: /*@__PURE__*/ new Vector2( 0.5, 0.5 ) }, + rotation: { value: 0.0 }, + map: { value: null }, + mapTransform: { value: /*@__PURE__*/ new Matrix3() }, + alphaMap: { value: null }, + alphaMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + alphaTest: { value: 0 } - } + } - function applyRadius( radius ) { +}; - const vertex = new Vector3(); +const ShaderLib = { - // iterate over the entire buffer and apply the radius to each vertex + basic: { - for ( let i = 0; i < vertexBuffer.length; i += 3 ) { + uniforms: /*@__PURE__*/ mergeUniforms( [ + UniformsLib.common, + UniformsLib.specularmap, + UniformsLib.envmap, + UniformsLib.aomap, + UniformsLib.lightmap, + UniformsLib.fog + ] ), - vertex.x = vertexBuffer[ i + 0 ]; - vertex.y = vertexBuffer[ i + 1 ]; - vertex.z = vertexBuffer[ i + 2 ]; + vertexShader: ShaderChunk.meshbasic_vert, + fragmentShader: ShaderChunk.meshbasic_frag - vertex.normalize().multiplyScalar( radius ); + }, - vertexBuffer[ i + 0 ] = vertex.x; - vertexBuffer[ i + 1 ] = vertex.y; - vertexBuffer[ i + 2 ] = vertex.z; + lambert: { + uniforms: /*@__PURE__*/ mergeUniforms( [ + UniformsLib.common, + UniformsLib.specularmap, + UniformsLib.envmap, + UniformsLib.aomap, + UniformsLib.lightmap, + UniformsLib.emissivemap, + UniformsLib.bumpmap, + UniformsLib.normalmap, + UniformsLib.displacementmap, + UniformsLib.fog, + UniformsLib.lights, + { + emissive: { value: /*@__PURE__*/ new Color( 0x000000 ) } } + ] ), - } - - function generateUVs() { - - const vertex = new Vector3(); - - for ( let i = 0; i < vertexBuffer.length; i += 3 ) { + vertexShader: ShaderChunk.meshlambert_vert, + fragmentShader: ShaderChunk.meshlambert_frag - vertex.x = vertexBuffer[ i + 0 ]; - vertex.y = vertexBuffer[ i + 1 ]; - vertex.z = vertexBuffer[ i + 2 ]; + }, - const u = azimuth( vertex ) / 2 / Math.PI + 0.5; - const v = inclination( vertex ) / Math.PI + 0.5; - uvBuffer.push( u, 1 - v ); + phong: { + uniforms: /*@__PURE__*/ mergeUniforms( [ + UniformsLib.common, + UniformsLib.specularmap, + UniformsLib.envmap, + UniformsLib.aomap, + UniformsLib.lightmap, + UniformsLib.emissivemap, + UniformsLib.bumpmap, + UniformsLib.normalmap, + UniformsLib.displacementmap, + UniformsLib.fog, + UniformsLib.lights, + { + emissive: { value: /*@__PURE__*/ new Color( 0x000000 ) }, + specular: { value: /*@__PURE__*/ new Color( 0x111111 ) }, + shininess: { value: 30 } } + ] ), - correctUVs(); - - correctSeam(); - - } - - function correctSeam() { - - // handle case when face straddles the seam, see #3269 - - for ( let i = 0; i < uvBuffer.length; i += 6 ) { - - // uv data of a single face - - const x0 = uvBuffer[ i + 0 ]; - const x1 = uvBuffer[ i + 2 ]; - const x2 = uvBuffer[ i + 4 ]; - - const max = Math.max( x0, x1, x2 ); - const min = Math.min( x0, x1, x2 ); - - // 0.9 is somewhat arbitrary - - if ( max > 0.9 && min < 0.1 ) { + vertexShader: ShaderChunk.meshphong_vert, + fragmentShader: ShaderChunk.meshphong_frag - if ( x0 < 0.2 ) uvBuffer[ i + 0 ] += 1; - if ( x1 < 0.2 ) uvBuffer[ i + 2 ] += 1; - if ( x2 < 0.2 ) uvBuffer[ i + 4 ] += 1; + }, - } + standard: { + uniforms: /*@__PURE__*/ mergeUniforms( [ + UniformsLib.common, + UniformsLib.envmap, + UniformsLib.aomap, + UniformsLib.lightmap, + UniformsLib.emissivemap, + UniformsLib.bumpmap, + UniformsLib.normalmap, + UniformsLib.displacementmap, + UniformsLib.roughnessmap, + UniformsLib.metalnessmap, + UniformsLib.fog, + UniformsLib.lights, + { + emissive: { value: /*@__PURE__*/ new Color( 0x000000 ) }, + roughness: { value: 1.0 }, + metalness: { value: 0.0 }, + envMapIntensity: { value: 1 } } + ] ), - } - - function pushVertex( vertex ) { - - vertexBuffer.push( vertex.x, vertex.y, vertex.z ); - - } - - function getVertexByIndex( index, vertex ) { - - const stride = index * 3; - - vertex.x = vertices[ stride + 0 ]; - vertex.y = vertices[ stride + 1 ]; - vertex.z = vertices[ stride + 2 ]; - - } - - function correctUVs() { - - const a = new Vector3(); - const b = new Vector3(); - const c = new Vector3(); - - const centroid = new Vector3(); - - const uvA = new Vector2(); - const uvB = new Vector2(); - const uvC = new Vector2(); - - for ( let i = 0, j = 0; i < vertexBuffer.length; i += 9, j += 6 ) { - - a.set( vertexBuffer[ i + 0 ], vertexBuffer[ i + 1 ], vertexBuffer[ i + 2 ] ); - b.set( vertexBuffer[ i + 3 ], vertexBuffer[ i + 4 ], vertexBuffer[ i + 5 ] ); - c.set( vertexBuffer[ i + 6 ], vertexBuffer[ i + 7 ], vertexBuffer[ i + 8 ] ); - - uvA.set( uvBuffer[ j + 0 ], uvBuffer[ j + 1 ] ); - uvB.set( uvBuffer[ j + 2 ], uvBuffer[ j + 3 ] ); - uvC.set( uvBuffer[ j + 4 ], uvBuffer[ j + 5 ] ); - - centroid.copy( a ).add( b ).add( c ).divideScalar( 3 ); + vertexShader: ShaderChunk.meshphysical_vert, + fragmentShader: ShaderChunk.meshphysical_frag - const azi = azimuth( centroid ); + }, - correctUV( uvA, j + 0, a, azi ); - correctUV( uvB, j + 2, b, azi ); - correctUV( uvC, j + 4, c, azi ); + toon: { + uniforms: /*@__PURE__*/ mergeUniforms( [ + UniformsLib.common, + UniformsLib.aomap, + UniformsLib.lightmap, + UniformsLib.emissivemap, + UniformsLib.bumpmap, + UniformsLib.normalmap, + UniformsLib.displacementmap, + UniformsLib.gradientmap, + UniformsLib.fog, + UniformsLib.lights, + { + emissive: { value: /*@__PURE__*/ new Color( 0x000000 ) } } + ] ), - } - - function correctUV( uv, stride, vector, azimuth ) { - - if ( ( azimuth < 0 ) && ( uv.x === 1 ) ) { - - uvBuffer[ stride ] = uv.x - 1; - - } + vertexShader: ShaderChunk.meshtoon_vert, + fragmentShader: ShaderChunk.meshtoon_frag - if ( ( vector.x === 0 ) && ( vector.z === 0 ) ) { + }, - uvBuffer[ stride ] = azimuth / 2 / Math.PI + 0.5; + matcap: { + uniforms: /*@__PURE__*/ mergeUniforms( [ + UniformsLib.common, + UniformsLib.bumpmap, + UniformsLib.normalmap, + UniformsLib.displacementmap, + UniformsLib.fog, + { + matcap: { value: null } } + ] ), - } - - // Angle around the Y axis, counter-clockwise when looking from above. - - function azimuth( vector ) { - - return Math.atan2( vector.z, - vector.x ); - - } - - - // Angle above the XZ plane. - - function inclination( vector ) { - - return Math.atan2( - vector.y, Math.sqrt( ( vector.x * vector.x ) + ( vector.z * vector.z ) ) ); - - } - - } - - copy( source ) { + vertexShader: ShaderChunk.meshmatcap_vert, + fragmentShader: ShaderChunk.meshmatcap_frag - super.copy( source ); + }, - this.parameters = Object.assign( {}, source.parameters ); + points: { - return this; + uniforms: /*@__PURE__*/ mergeUniforms( [ + UniformsLib.points, + UniformsLib.fog + ] ), - } + vertexShader: ShaderChunk.points_vert, + fragmentShader: ShaderChunk.points_frag - static fromJSON( data ) { + }, - return new PolyhedronGeometry( data.vertices, data.indices, data.radius, data.details ); + dashed: { - } + uniforms: /*@__PURE__*/ mergeUniforms( [ + UniformsLib.common, + UniformsLib.fog, + { + scale: { value: 1 }, + dashSize: { value: 1 }, + totalSize: { value: 2 } + } + ] ), -} + vertexShader: ShaderChunk.linedashed_vert, + fragmentShader: ShaderChunk.linedashed_frag -class DodecahedronGeometry extends PolyhedronGeometry { + }, - constructor( radius = 1, detail = 0 ) { + depth: { - const t = ( 1 + Math.sqrt( 5 ) ) / 2; - const r = 1 / t; + uniforms: /*@__PURE__*/ mergeUniforms( [ + UniformsLib.common, + UniformsLib.displacementmap + ] ), - const vertices = [ + vertexShader: ShaderChunk.depth_vert, + fragmentShader: ShaderChunk.depth_frag - // (±1, ±1, ±1) - - 1, - 1, - 1, - 1, - 1, 1, - - 1, 1, - 1, - 1, 1, 1, - 1, - 1, - 1, 1, - 1, 1, - 1, 1, - 1, 1, 1, 1, + }, - // (0, ±1/φ, ±φ) - 0, - r, - t, 0, - r, t, - 0, r, - t, 0, r, t, + normal: { - // (±1/φ, ±φ, 0) - - r, - t, 0, - r, t, 0, - r, - t, 0, r, t, 0, + uniforms: /*@__PURE__*/ mergeUniforms( [ + UniformsLib.common, + UniformsLib.bumpmap, + UniformsLib.normalmap, + UniformsLib.displacementmap, + { + opacity: { value: 1.0 } + } + ] ), - // (±φ, 0, ±1/φ) - - t, 0, - r, t, 0, - r, - - t, 0, r, t, 0, r - ]; + vertexShader: ShaderChunk.meshnormal_vert, + fragmentShader: ShaderChunk.meshnormal_frag - const indices = [ - 3, 11, 7, 3, 7, 15, 3, 15, 13, - 7, 19, 17, 7, 17, 6, 7, 6, 15, - 17, 4, 8, 17, 8, 10, 17, 10, 6, - 8, 0, 16, 8, 16, 2, 8, 2, 10, - 0, 12, 1, 0, 1, 18, 0, 18, 16, - 6, 10, 2, 6, 2, 13, 6, 13, 15, - 2, 16, 18, 2, 18, 3, 2, 3, 13, - 18, 1, 9, 18, 9, 11, 18, 11, 3, - 4, 14, 12, 4, 12, 0, 4, 0, 8, - 11, 9, 5, 11, 5, 19, 11, 19, 7, - 19, 5, 14, 19, 14, 4, 19, 4, 17, - 1, 12, 14, 1, 14, 5, 1, 5, 9 - ]; + }, - super( vertices, indices, radius, detail ); + sprite: { - this.type = 'DodecahedronGeometry'; + uniforms: /*@__PURE__*/ mergeUniforms( [ + UniformsLib.sprite, + UniformsLib.fog + ] ), - this.parameters = { - radius: radius, - detail: detail - }; + vertexShader: ShaderChunk.sprite_vert, + fragmentShader: ShaderChunk.sprite_frag - } + }, - static fromJSON( data ) { + background: { - return new DodecahedronGeometry( data.radius, data.detail ); + uniforms: { + uvTransform: { value: /*@__PURE__*/ new Matrix3() }, + t2D: { value: null }, + backgroundIntensity: { value: 1 } + }, - } + vertexShader: ShaderChunk.background_vert, + fragmentShader: ShaderChunk.background_frag -} + }, -const _v0 = /*@__PURE__*/ new Vector3(); -const _v1$1 = /*@__PURE__*/ new Vector3(); -const _normal = /*@__PURE__*/ new Vector3(); -const _triangle = /*@__PURE__*/ new Triangle(); + backgroundCube: { -class EdgesGeometry extends BufferGeometry { + uniforms: { + envMap: { value: null }, + flipEnvMap: { value: -1 }, + backgroundBlurriness: { value: 0 }, + backgroundIntensity: { value: 1 }, + backgroundRotation: { value: /*@__PURE__*/ new Matrix3() } + }, - constructor( geometry = null, thresholdAngle = 1 ) { + vertexShader: ShaderChunk.backgroundCube_vert, + fragmentShader: ShaderChunk.backgroundCube_frag - super(); + }, - this.type = 'EdgesGeometry'; + cube: { - this.parameters = { - geometry: geometry, - thresholdAngle: thresholdAngle - }; + uniforms: { + tCube: { value: null }, + tFlip: { value: -1 }, + opacity: { value: 1.0 } + }, - if ( geometry !== null ) { + vertexShader: ShaderChunk.cube_vert, + fragmentShader: ShaderChunk.cube_frag - const precisionPoints = 4; - const precision = Math.pow( 10, precisionPoints ); - const thresholdDot = Math.cos( DEG2RAD * thresholdAngle ); + }, - const indexAttr = geometry.getIndex(); - const positionAttr = geometry.getAttribute( 'position' ); - const indexCount = indexAttr ? indexAttr.count : positionAttr.count; + equirect: { - const indexArr = [ 0, 0, 0 ]; - const vertKeys = [ 'a', 'b', 'c' ]; - const hashes = new Array( 3 ); + uniforms: { + tEquirect: { value: null }, + }, - const edgeData = {}; - const vertices = []; - for ( let i = 0; i < indexCount; i += 3 ) { + vertexShader: ShaderChunk.equirect_vert, + fragmentShader: ShaderChunk.equirect_frag - if ( indexAttr ) { + }, - indexArr[ 0 ] = indexAttr.getX( i ); - indexArr[ 1 ] = indexAttr.getX( i + 1 ); - indexArr[ 2 ] = indexAttr.getX( i + 2 ); + distanceRGBA: { - } else { + uniforms: /*@__PURE__*/ mergeUniforms( [ + UniformsLib.common, + UniformsLib.displacementmap, + { + referencePosition: { value: /*@__PURE__*/ new Vector3() }, + nearDistance: { value: 1 }, + farDistance: { value: 1000 } + } + ] ), - indexArr[ 0 ] = i; - indexArr[ 1 ] = i + 1; - indexArr[ 2 ] = i + 2; + vertexShader: ShaderChunk.distanceRGBA_vert, + fragmentShader: ShaderChunk.distanceRGBA_frag - } + }, - const { a, b, c } = _triangle; - a.fromBufferAttribute( positionAttr, indexArr[ 0 ] ); - b.fromBufferAttribute( positionAttr, indexArr[ 1 ] ); - c.fromBufferAttribute( positionAttr, indexArr[ 2 ] ); - _triangle.getNormal( _normal ); + shadow: { - // create hashes for the edge from the vertices - hashes[ 0 ] = `${ Math.round( a.x * precision ) },${ Math.round( a.y * precision ) },${ Math.round( a.z * precision ) }`; - hashes[ 1 ] = `${ Math.round( b.x * precision ) },${ Math.round( b.y * precision ) },${ Math.round( b.z * precision ) }`; - hashes[ 2 ] = `${ Math.round( c.x * precision ) },${ Math.round( c.y * precision ) },${ Math.round( c.z * precision ) }`; + uniforms: /*@__PURE__*/ mergeUniforms( [ + UniformsLib.lights, + UniformsLib.fog, + { + color: { value: /*@__PURE__*/ new Color( 0x00000 ) }, + opacity: { value: 1.0 } + }, + ] ), - // skip degenerate triangles - if ( hashes[ 0 ] === hashes[ 1 ] || hashes[ 1 ] === hashes[ 2 ] || hashes[ 2 ] === hashes[ 0 ] ) { + vertexShader: ShaderChunk.shadow_vert, + fragmentShader: ShaderChunk.shadow_frag - continue; + } - } +}; - // iterate over every edge - for ( let j = 0; j < 3; j ++ ) { +ShaderLib.physical = { - // get the first and next vertex making up the edge - const jNext = ( j + 1 ) % 3; - const vecHash0 = hashes[ j ]; - const vecHash1 = hashes[ jNext ]; - const v0 = _triangle[ vertKeys[ j ] ]; - const v1 = _triangle[ vertKeys[ jNext ] ]; + uniforms: /*@__PURE__*/ mergeUniforms( [ + ShaderLib.standard.uniforms, + { + clearcoat: { value: 0 }, + clearcoatMap: { value: null }, + clearcoatMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + clearcoatNormalMap: { value: null }, + clearcoatNormalMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + clearcoatNormalScale: { value: /*@__PURE__*/ new Vector2( 1, 1 ) }, + clearcoatRoughness: { value: 0 }, + clearcoatRoughnessMap: { value: null }, + clearcoatRoughnessMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + dispersion: { value: 0 }, + iridescence: { value: 0 }, + iridescenceMap: { value: null }, + iridescenceMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + iridescenceIOR: { value: 1.3 }, + iridescenceThicknessMinimum: { value: 100 }, + iridescenceThicknessMaximum: { value: 400 }, + iridescenceThicknessMap: { value: null }, + iridescenceThicknessMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + sheen: { value: 0 }, + sheenColor: { value: /*@__PURE__*/ new Color( 0x000000 ) }, + sheenColorMap: { value: null }, + sheenColorMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + sheenRoughness: { value: 1 }, + sheenRoughnessMap: { value: null }, + sheenRoughnessMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + transmission: { value: 0 }, + transmissionMap: { value: null }, + transmissionMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + transmissionSamplerSize: { value: /*@__PURE__*/ new Vector2() }, + transmissionSamplerMap: { value: null }, + thickness: { value: 0 }, + thicknessMap: { value: null }, + thicknessMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + attenuationDistance: { value: 0 }, + attenuationColor: { value: /*@__PURE__*/ new Color( 0x000000 ) }, + specularColor: { value: /*@__PURE__*/ new Color( 1, 1, 1 ) }, + specularColorMap: { value: null }, + specularColorMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + specularIntensity: { value: 1 }, + specularIntensityMap: { value: null }, + specularIntensityMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + anisotropyVector: { value: /*@__PURE__*/ new Vector2() }, + anisotropyMap: { value: null }, + anisotropyMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + } + ] ), - const hash = `${ vecHash0 }_${ vecHash1 }`; - const reverseHash = `${ vecHash1 }_${ vecHash0 }`; + vertexShader: ShaderChunk.meshphysical_vert, + fragmentShader: ShaderChunk.meshphysical_frag - if ( reverseHash in edgeData && edgeData[ reverseHash ] ) { +}; - // if we found a sibling edge add it into the vertex array if - // it meets the angle threshold and delete the edge from the map. - if ( _normal.dot( edgeData[ reverseHash ].normal ) <= thresholdDot ) { +const _rgb = { r: 0, b: 0, g: 0 }; +const _e1$1 = /*@__PURE__*/ new Euler(); +const _m1$1 = /*@__PURE__*/ new Matrix4(); - vertices.push( v0.x, v0.y, v0.z ); - vertices.push( v1.x, v1.y, v1.z ); +function WebGLBackground( renderer, cubemaps, cubeuvmaps, state, objects, alpha, premultipliedAlpha ) { - } + const clearColor = new Color( 0x000000 ); + let clearAlpha = alpha === true ? 0 : 1; - edgeData[ reverseHash ] = null; + let planeMesh; + let boxMesh; - } else if ( ! ( hash in edgeData ) ) { + let currentBackground = null; + let currentBackgroundVersion = 0; + let currentTonemapping = null; - // if we've already got an edge here then skip adding a new one - edgeData[ hash ] = { + function getBackground( scene ) { - index0: indexArr[ j ], - index1: indexArr[ jNext ], - normal: _normal.clone(), + let background = scene.isScene === true ? scene.background : null; - }; + if ( background && background.isTexture ) { - } + const usePMREM = scene.backgroundBlurriness > 0; // use PMREM if the user wants to blur the background + background = ( usePMREM ? cubeuvmaps : cubemaps ).get( background ); - } + } - } + return background; - // iterate over all remaining, unmatched edges and add them to the vertex array - for ( const key in edgeData ) { + } - if ( edgeData[ key ] ) { + function render( scene ) { - const { index0, index1 } = edgeData[ key ]; - _v0.fromBufferAttribute( positionAttr, index0 ); - _v1$1.fromBufferAttribute( positionAttr, index1 ); + let forceClear = false; + const background = getBackground( scene ); - vertices.push( _v0.x, _v0.y, _v0.z ); - vertices.push( _v1$1.x, _v1$1.y, _v1$1.z ); + if ( background === null ) { - } + setClear( clearColor, clearAlpha ); - } + } else if ( background && background.isColor ) { - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + setClear( background, 1 ); + forceClear = true; } - } - - copy( source ) { - - super.copy( source ); + const environmentBlendMode = renderer.xr.getEnvironmentBlendMode(); - this.parameters = Object.assign( {}, source.parameters ); + if ( environmentBlendMode === 'additive' ) { - return this; + state.buffers.color.setClear( 0, 0, 0, 1, premultipliedAlpha ); - } + } else if ( environmentBlendMode === 'alpha-blend' ) { -} + state.buffers.color.setClear( 0, 0, 0, 0, premultipliedAlpha ); -class Shape extends Path { + } - constructor( points ) { + if ( renderer.autoClear || forceClear ) { - super( points ); + // buffers might not be writable which is required to ensure a correct clear - this.uuid = generateUUID(); + state.buffers.depth.setTest( true ); + state.buffers.depth.setMask( true ); + state.buffers.color.setMask( true ); - this.type = 'Shape'; + renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil ); - this.holes = []; + } } - getPointsHoles( divisions ) { + function addToRenderList( renderList, scene ) { - const holesPts = []; + const background = getBackground( scene ); - for ( let i = 0, l = this.holes.length; i < l; i ++ ) { + if ( background && ( background.isCubeTexture || background.mapping === CubeUVReflectionMapping ) ) { - holesPts[ i ] = this.holes[ i ].getPoints( divisions ); + if ( boxMesh === undefined ) { - } + boxMesh = new Mesh( + new BoxGeometry( 1, 1, 1 ), + new ShaderMaterial( { + name: 'BackgroundCubeMaterial', + uniforms: cloneUniforms( ShaderLib.backgroundCube.uniforms ), + vertexShader: ShaderLib.backgroundCube.vertexShader, + fragmentShader: ShaderLib.backgroundCube.fragmentShader, + side: BackSide, + depthTest: false, + depthWrite: false, + fog: false, + allowOverride: false + } ) + ); - return holesPts; + boxMesh.geometry.deleteAttribute( 'normal' ); + boxMesh.geometry.deleteAttribute( 'uv' ); - } + boxMesh.onBeforeRender = function ( renderer, scene, camera ) { - // get points of shape and holes (keypoints based on segments parameter) + this.matrixWorld.copyPosition( camera.matrixWorld ); - extractPoints( divisions ) { + }; - return { + // add "envMap" material property so the renderer can evaluate it like for built-in materials + Object.defineProperty( boxMesh.material, 'envMap', { - shape: this.getPoints( divisions ), - holes: this.getPointsHoles( divisions ) + get: function () { - }; + return this.uniforms.envMap.value; - } + } - copy( source ) { + } ); - super.copy( source ); + objects.update( boxMesh ); - this.holes = []; + } - for ( let i = 0, l = source.holes.length; i < l; i ++ ) { + _e1$1.copy( scene.backgroundRotation ); - const hole = source.holes[ i ]; + // accommodate left-handed frame + _e1$1.x *= -1; _e1$1.y *= -1; _e1$1.z *= -1; - this.holes.push( hole.clone() ); + if ( background.isCubeTexture && background.isRenderTargetTexture === false ) { - } + // environment maps which are not cube render targets or PMREMs follow a different convention + _e1$1.y *= -1; + _e1$1.z *= -1; - return this; + } - } + boxMesh.material.uniforms.envMap.value = background; + boxMesh.material.uniforms.flipEnvMap.value = ( background.isCubeTexture && background.isRenderTargetTexture === false ) ? -1 : 1; + boxMesh.material.uniforms.backgroundBlurriness.value = scene.backgroundBlurriness; + boxMesh.material.uniforms.backgroundIntensity.value = scene.backgroundIntensity; + boxMesh.material.uniforms.backgroundRotation.value.setFromMatrix4( _m1$1.makeRotationFromEuler( _e1$1 ) ); + boxMesh.material.toneMapped = ColorManagement.getTransfer( background.colorSpace ) !== SRGBTransfer; - toJSON() { + if ( currentBackground !== background || + currentBackgroundVersion !== background.version || + currentTonemapping !== renderer.toneMapping ) { - const data = super.toJSON(); + boxMesh.material.needsUpdate = true; - data.uuid = this.uuid; - data.holes = []; + currentBackground = background; + currentBackgroundVersion = background.version; + currentTonemapping = renderer.toneMapping; - for ( let i = 0, l = this.holes.length; i < l; i ++ ) { + } - const hole = this.holes[ i ]; - data.holes.push( hole.toJSON() ); + boxMesh.layers.enableAll(); - } + // push to the pre-sorted opaque render list + renderList.unshift( boxMesh, boxMesh.geometry, boxMesh.material, 0, 0, null ); - return data; + } else if ( background && background.isTexture ) { - } + if ( planeMesh === undefined ) { - fromJSON( json ) { + planeMesh = new Mesh( + new PlaneGeometry( 2, 2 ), + new ShaderMaterial( { + name: 'BackgroundMaterial', + uniforms: cloneUniforms( ShaderLib.background.uniforms ), + vertexShader: ShaderLib.background.vertexShader, + fragmentShader: ShaderLib.background.fragmentShader, + side: FrontSide, + depthTest: false, + depthWrite: false, + fog: false, + allowOverride: false + } ) + ); - super.fromJSON( json ); + planeMesh.geometry.deleteAttribute( 'normal' ); - this.uuid = json.uuid; - this.holes = []; + // add "map" material property so the renderer can evaluate it like for built-in materials + Object.defineProperty( planeMesh.material, 'map', { - for ( let i = 0, l = json.holes.length; i < l; i ++ ) { + get: function () { - const hole = json.holes[ i ]; - this.holes.push( new Path().fromJSON( hole ) ); + return this.uniforms.t2D.value; - } + } - return this; + } ); - } + objects.update( planeMesh ); -} + } -/** - * Port from https://github.com/mapbox/earcut (v2.2.4) - */ + planeMesh.material.uniforms.t2D.value = background; + planeMesh.material.uniforms.backgroundIntensity.value = scene.backgroundIntensity; + planeMesh.material.toneMapped = ColorManagement.getTransfer( background.colorSpace ) !== SRGBTransfer; -const Earcut = { + if ( background.matrixAutoUpdate === true ) { - triangulate: function ( data, holeIndices, dim = 2 ) { + background.updateMatrix(); - const hasHoles = holeIndices && holeIndices.length; - const outerLen = hasHoles ? holeIndices[ 0 ] * dim : data.length; - let outerNode = linkedList( data, 0, outerLen, dim, true ); - const triangles = []; + } - if ( ! outerNode || outerNode.next === outerNode.prev ) return triangles; + planeMesh.material.uniforms.uvTransform.value.copy( background.matrix ); - let minX, minY, maxX, maxY, x, y, invSize; + if ( currentBackground !== background || + currentBackgroundVersion !== background.version || + currentTonemapping !== renderer.toneMapping ) { - if ( hasHoles ) outerNode = eliminateHoles( data, holeIndices, outerNode, dim ); + planeMesh.material.needsUpdate = true; - // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox - if ( data.length > 80 * dim ) { + currentBackground = background; + currentBackgroundVersion = background.version; + currentTonemapping = renderer.toneMapping; - minX = maxX = data[ 0 ]; - minY = maxY = data[ 1 ]; + } - for ( let i = dim; i < outerLen; i += dim ) { + planeMesh.layers.enableAll(); - x = data[ i ]; - y = data[ i + 1 ]; - if ( x < minX ) minX = x; - if ( y < minY ) minY = y; - if ( x > maxX ) maxX = x; - if ( y > maxY ) maxY = y; + // push to the pre-sorted opaque render list + renderList.unshift( planeMesh, planeMesh.geometry, planeMesh.material, 0, 0, null ); - } + } - // minX, minY and invSize are later used to transform coords into integers for z-order calculation - invSize = Math.max( maxX - minX, maxY - minY ); - invSize = invSize !== 0 ? 32767 / invSize : 0; + } - } + function setClear( color, alpha ) { - earcutLinked( outerNode, triangles, dim, minX, minY, invSize, 0 ); + color.getRGB( _rgb, getUnlitUniformColorSpace( renderer ) ); - return triangles; + state.buffers.color.setClear( _rgb.r, _rgb.g, _rgb.b, alpha, premultipliedAlpha ); } -}; - -// create a circular doubly linked list from polygon points in the specified winding order -function linkedList( data, start, end, dim, clockwise ) { + function dispose() { - let i, last; + if ( boxMesh !== undefined ) { - if ( clockwise === ( signedArea( data, start, end, dim ) > 0 ) ) { + boxMesh.geometry.dispose(); + boxMesh.material.dispose(); - for ( i = start; i < end; i += dim ) last = insertNode( i, data[ i ], data[ i + 1 ], last ); + boxMesh = undefined; - } else { + } - for ( i = end - dim; i >= start; i -= dim ) last = insertNode( i, data[ i ], data[ i + 1 ], last ); + if ( planeMesh !== undefined ) { - } + planeMesh.geometry.dispose(); + planeMesh.material.dispose(); - if ( last && equals( last, last.next ) ) { + planeMesh = undefined; - removeNode( last ); - last = last.next; + } } - return last; - -} - -// eliminate colinear or duplicate points -function filterPoints( start, end ) { + return { - if ( ! start ) return start; - if ( ! end ) end = start; + getClearColor: function () { - let p = start, - again; - do { + return clearColor; - again = false; + }, + setClearColor: function ( color, alpha = 1 ) { - if ( ! p.steiner && ( equals( p, p.next ) || area( p.prev, p, p.next ) === 0 ) ) { + clearColor.set( color ); + clearAlpha = alpha; + setClear( clearColor, clearAlpha ); - removeNode( p ); - p = end = p.prev; - if ( p === p.next ) break; - again = true; + }, + getClearAlpha: function () { - } else { + return clearAlpha; - p = p.next; + }, + setClearAlpha: function ( alpha ) { - } + clearAlpha = alpha; + setClear( clearColor, clearAlpha ); - } while ( again || p !== end ); + }, + render: render, + addToRenderList: addToRenderList, + dispose: dispose - return end; + }; } -// main ear slicing loop which triangulates a polygon (given as a linked list) -function earcutLinked( ear, triangles, dim, minX, minY, invSize, pass ) { - - if ( ! ear ) return; +function WebGLBindingStates( gl, attributes ) { - // interlink polygon nodes in z-order - if ( ! pass && invSize ) indexCurve( ear, minX, minY, invSize ); - - let stop = ear, - prev, next; + const maxVertexAttributes = gl.getParameter( gl.MAX_VERTEX_ATTRIBS ); - // iterate through ears, slicing them one by one - while ( ear.prev !== ear.next ) { + const bindingStates = {}; - prev = ear.prev; - next = ear.next; + const defaultState = createBindingState( null ); + let currentState = defaultState; + let forceUpdate = false; - if ( invSize ? isEarHashed( ear, minX, minY, invSize ) : isEar( ear ) ) { + function setup( object, material, program, geometry, index ) { - // cut off the triangle - triangles.push( prev.i / dim | 0 ); - triangles.push( ear.i / dim | 0 ); - triangles.push( next.i / dim | 0 ); + let updateBuffers = false; - removeNode( ear ); + const state = getBindingState( geometry, program, material ); - // skipping the next vertex leads to less sliver triangles - ear = next.next; - stop = next.next; + if ( currentState !== state ) { - continue; + currentState = state; + bindVertexArrayObject( currentState.object ); } - ear = next; + updateBuffers = needsUpdate( object, geometry, program, index ); - // if we looped through the whole remaining polygon and can't find any more ears - if ( ear === stop ) { + if ( updateBuffers ) saveCache( object, geometry, program, index ); - // try filtering points and slicing again - if ( ! pass ) { + if ( index !== null ) { - earcutLinked( filterPoints( ear ), triangles, dim, minX, minY, invSize, 1 ); + attributes.update( index, gl.ELEMENT_ARRAY_BUFFER ); - // if this didn't work, try curing all small self-intersections locally + } - } else if ( pass === 1 ) { + if ( updateBuffers || forceUpdate ) { - ear = cureLocalIntersections( filterPoints( ear ), triangles, dim ); - earcutLinked( ear, triangles, dim, minX, minY, invSize, 2 ); + forceUpdate = false; - // as a last resort, try splitting the remaining polygon into two + setupVertexAttributes( object, material, program, geometry ); - } else if ( pass === 2 ) { + if ( index !== null ) { - splitEarcut( ear, triangles, dim, minX, minY, invSize ); + gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, attributes.get( index ).buffer ); } - break; - } } -} - -// check whether a polygon node forms a valid ear with adjacent nodes -function isEar( ear ) { - - const a = ear.prev, - b = ear, - c = ear.next; - - if ( area( a, b, c ) >= 0 ) return false; // reflex, can't be an ear + function createVertexArrayObject() { - // now make sure we don't have other points inside the potential ear - const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y; + return gl.createVertexArray(); - // triangle bbox; min & max are calculated like this for speed - const x0 = ax < bx ? ( ax < cx ? ax : cx ) : ( bx < cx ? bx : cx ), - y0 = ay < by ? ( ay < cy ? ay : cy ) : ( by < cy ? by : cy ), - x1 = ax > bx ? ( ax > cx ? ax : cx ) : ( bx > cx ? bx : cx ), - y1 = ay > by ? ( ay > cy ? ay : cy ) : ( by > cy ? by : cy ); + } - let p = c.next; - while ( p !== a ) { + function bindVertexArrayObject( vao ) { - if ( p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && - pointInTriangle( ax, ay, bx, by, cx, cy, p.x, p.y ) && - area( p.prev, p, p.next ) >= 0 ) return false; - p = p.next; + return gl.bindVertexArray( vao ); } - return true; + function deleteVertexArrayObject( vao ) { -} + return gl.deleteVertexArray( vao ); -function isEarHashed( ear, minX, minY, invSize ) { + } - const a = ear.prev, - b = ear, - c = ear.next; + function getBindingState( geometry, program, material ) { - if ( area( a, b, c ) >= 0 ) return false; // reflex, can't be an ear + const wireframe = ( material.wireframe === true ); - const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y; + let programMap = bindingStates[ geometry.id ]; - // triangle bbox; min & max are calculated like this for speed - const x0 = ax < bx ? ( ax < cx ? ax : cx ) : ( bx < cx ? bx : cx ), - y0 = ay < by ? ( ay < cy ? ay : cy ) : ( by < cy ? by : cy ), - x1 = ax > bx ? ( ax > cx ? ax : cx ) : ( bx > cx ? bx : cx ), - y1 = ay > by ? ( ay > cy ? ay : cy ) : ( by > cy ? by : cy ); + if ( programMap === undefined ) { - // z-order range for the current triangle bbox; - const minZ = zOrder( x0, y0, minX, minY, invSize ), - maxZ = zOrder( x1, y1, minX, minY, invSize ); + programMap = {}; + bindingStates[ geometry.id ] = programMap; - let p = ear.prevZ, - n = ear.nextZ; + } - // look for points inside the triangle in both directions - while ( p && p.z >= minZ && n && n.z <= maxZ ) { + let stateMap = programMap[ program.id ]; - if ( p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c && - pointInTriangle( ax, ay, bx, by, cx, cy, p.x, p.y ) && area( p.prev, p, p.next ) >= 0 ) return false; - p = p.prevZ; + if ( stateMap === undefined ) { - if ( n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c && - pointInTriangle( ax, ay, bx, by, cx, cy, n.x, n.y ) && area( n.prev, n, n.next ) >= 0 ) return false; - n = n.nextZ; + stateMap = {}; + programMap[ program.id ] = stateMap; - } + } - // look for remaining points in decreasing z-order - while ( p && p.z >= minZ ) { + let state = stateMap[ wireframe ]; - if ( p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c && - pointInTriangle( ax, ay, bx, by, cx, cy, p.x, p.y ) && area( p.prev, p, p.next ) >= 0 ) return false; - p = p.prevZ; + if ( state === undefined ) { - } + state = createBindingState( createVertexArrayObject() ); + stateMap[ wireframe ] = state; - // look for remaining points in increasing z-order - while ( n && n.z <= maxZ ) { + } - if ( n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c && - pointInTriangle( ax, ay, bx, by, cx, cy, n.x, n.y ) && area( n.prev, n, n.next ) >= 0 ) return false; - n = n.nextZ; + return state; } - return true; - -} - -// go through all polygon nodes and cure small local self-intersections -function cureLocalIntersections( start, triangles, dim ) { - - let p = start; - do { - - const a = p.prev, - b = p.next.next; - - if ( ! equals( a, b ) && intersects( a, p, p.next, b ) && locallyInside( a, b ) && locallyInside( b, a ) ) { + function createBindingState( vao ) { - triangles.push( a.i / dim | 0 ); - triangles.push( p.i / dim | 0 ); - triangles.push( b.i / dim | 0 ); + const newAttributes = []; + const enabledAttributes = []; + const attributeDivisors = []; - // remove two nodes involved - removeNode( p ); - removeNode( p.next ); + for ( let i = 0; i < maxVertexAttributes; i ++ ) { - p = start = b; + newAttributes[ i ] = 0; + enabledAttributes[ i ] = 0; + attributeDivisors[ i ] = 0; } - p = p.next; + return { - } while ( p !== start ); + // for backward compatibility on non-VAO support browser + geometry: null, + program: null, + wireframe: false, - return filterPoints( p ); + newAttributes: newAttributes, + enabledAttributes: enabledAttributes, + attributeDivisors: attributeDivisors, + object: vao, + attributes: {}, + index: null -} + }; -// try splitting polygon into two and triangulate them independently -function splitEarcut( start, triangles, dim, minX, minY, invSize ) { + } - // look for a valid diagonal that divides the polygon into two - let a = start; - do { + function needsUpdate( object, geometry, program, index ) { - let b = a.next.next; - while ( b !== a.prev ) { + const cachedAttributes = currentState.attributes; + const geometryAttributes = geometry.attributes; - if ( a.i !== b.i && isValidDiagonal( a, b ) ) { + let attributesNum = 0; - // split the polygon in two by the diagonal - let c = splitPolygon( a, b ); + const programAttributes = program.getAttributes(); - // filter colinear points around the cuts - a = filterPoints( a, a.next ); - c = filterPoints( c, c.next ); + for ( const name in programAttributes ) { - // run earcut on each half - earcutLinked( a, triangles, dim, minX, minY, invSize, 0 ); - earcutLinked( c, triangles, dim, minX, minY, invSize, 0 ); - return; + const programAttribute = programAttributes[ name ]; - } + if ( programAttribute.location >= 0 ) { - b = b.next; + const cachedAttribute = cachedAttributes[ name ]; + let geometryAttribute = geometryAttributes[ name ]; - } + if ( geometryAttribute === undefined ) { - a = a.next; + if ( name === 'instanceMatrix' && object.instanceMatrix ) geometryAttribute = object.instanceMatrix; + if ( name === 'instanceColor' && object.instanceColor ) geometryAttribute = object.instanceColor; - } while ( a !== start ); + } -} + if ( cachedAttribute === undefined ) return true; -// link every hole into the outer loop, producing a single-ring polygon without holes -function eliminateHoles( data, holeIndices, outerNode, dim ) { + if ( cachedAttribute.attribute !== geometryAttribute ) return true; - const queue = []; - let i, len, start, end, list; + if ( geometryAttribute && cachedAttribute.data !== geometryAttribute.data ) return true; - for ( i = 0, len = holeIndices.length; i < len; i ++ ) { + attributesNum ++; - start = holeIndices[ i ] * dim; - end = i < len - 1 ? holeIndices[ i + 1 ] * dim : data.length; - list = linkedList( data, start, end, dim, false ); - if ( list === list.next ) list.steiner = true; - queue.push( getLeftmost( list ) ); + } - } + } - queue.sort( compareX ); + if ( currentState.attributesNum !== attributesNum ) return true; - // process holes from left to right - for ( i = 0; i < queue.length; i ++ ) { + if ( currentState.index !== index ) return true; - outerNode = eliminateHole( queue[ i ], outerNode ); + return false; } - return outerNode; - -} - -function compareX( a, b ) { - - return a.x - b.x; + function saveCache( object, geometry, program, index ) { -} + const cache = {}; + const attributes = geometry.attributes; + let attributesNum = 0; -// find a bridge between vertices that connects hole with an outer ring and link it -function eliminateHole( hole, outerNode ) { + const programAttributes = program.getAttributes(); - const bridge = findHoleBridge( hole, outerNode ); - if ( ! bridge ) { + for ( const name in programAttributes ) { - return outerNode; + const programAttribute = programAttributes[ name ]; - } + if ( programAttribute.location >= 0 ) { - const bridgeReverse = splitPolygon( bridge, hole ); + let attribute = attributes[ name ]; - // filter collinear points around the cuts - filterPoints( bridgeReverse, bridgeReverse.next ); - return filterPoints( bridge, bridge.next ); + if ( attribute === undefined ) { -} + if ( name === 'instanceMatrix' && object.instanceMatrix ) attribute = object.instanceMatrix; + if ( name === 'instanceColor' && object.instanceColor ) attribute = object.instanceColor; -// David Eberly's algorithm for finding a bridge between hole and outer polygon -function findHoleBridge( hole, outerNode ) { + } - let p = outerNode, - qx = - Infinity, - m; + const data = {}; + data.attribute = attribute; - const hx = hole.x, hy = hole.y; + if ( attribute && attribute.data ) { - // find a segment intersected by a ray from the hole's leftmost point to the left; - // segment's endpoint with lesser x will be potential connection point - do { + data.data = attribute.data; - if ( hy <= p.y && hy >= p.next.y && p.next.y !== p.y ) { + } - const x = p.x + ( hy - p.y ) * ( p.next.x - p.x ) / ( p.next.y - p.y ); - if ( x <= hx && x > qx ) { + cache[ name ] = data; - qx = x; - m = p.x < p.next.x ? p : p.next; - if ( x === hx ) return m; // hole touches outer segment; pick leftmost endpoint + attributesNum ++; } } - p = p.next; - - } while ( p !== outerNode ); - - if ( ! m ) return null; - - // look for points inside the triangle of hole point, segment intersection and endpoint; - // if there are no points found, we have a valid connection; - // otherwise choose the point of the minimum angle with the ray as connection point - - const stop = m, - mx = m.x, - my = m.y; - let tanMin = Infinity, tan; - - p = m; + currentState.attributes = cache; + currentState.attributesNum = attributesNum; - do { + currentState.index = index; - if ( hx >= p.x && p.x >= mx && hx !== p.x && - pointInTriangle( hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y ) ) { + } - tan = Math.abs( hy - p.y ) / ( hx - p.x ); // tangential + function initAttributes() { - if ( locallyInside( p, hole ) && ( tan < tanMin || ( tan === tanMin && ( p.x > m.x || ( p.x === m.x && sectorContainsSector( m, p ) ) ) ) ) ) { + const newAttributes = currentState.newAttributes; - m = p; - tanMin = tan; + for ( let i = 0, il = newAttributes.length; i < il; i ++ ) { - } + newAttributes[ i ] = 0; } - p = p.next; - - } while ( p !== stop ); - - return m; + } -} + function enableAttribute( attribute ) { -// whether sector in vertex m contains sector in vertex p in the same coordinates -function sectorContainsSector( m, p ) { + enableAttributeAndDivisor( attribute, 0 ); - return area( m.prev, m, p.prev ) < 0 && area( p.next, m, m.next ) < 0; + } -} + function enableAttributeAndDivisor( attribute, meshPerAttribute ) { -// interlink polygon nodes in z-order -function indexCurve( start, minX, minY, invSize ) { + const newAttributes = currentState.newAttributes; + const enabledAttributes = currentState.enabledAttributes; + const attributeDivisors = currentState.attributeDivisors; - let p = start; - do { + newAttributes[ attribute ] = 1; - if ( p.z === 0 ) p.z = zOrder( p.x, p.y, minX, minY, invSize ); - p.prevZ = p.prev; - p.nextZ = p.next; - p = p.next; + if ( enabledAttributes[ attribute ] === 0 ) { - } while ( p !== start ); + gl.enableVertexAttribArray( attribute ); + enabledAttributes[ attribute ] = 1; - p.prevZ.nextZ = null; - p.prevZ = null; + } - sortLinked( p ); + if ( attributeDivisors[ attribute ] !== meshPerAttribute ) { -} + gl.vertexAttribDivisor( attribute, meshPerAttribute ); + attributeDivisors[ attribute ] = meshPerAttribute; -// Simon Tatham's linked list merge sort algorithm -// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html -function sortLinked( list ) { + } - let i, p, q, e, tail, numMerges, pSize, qSize, - inSize = 1; + } - do { + function disableUnusedAttributes() { - p = list; - list = null; - tail = null; - numMerges = 0; + const newAttributes = currentState.newAttributes; + const enabledAttributes = currentState.enabledAttributes; - while ( p ) { + for ( let i = 0, il = enabledAttributes.length; i < il; i ++ ) { - numMerges ++; - q = p; - pSize = 0; - for ( i = 0; i < inSize; i ++ ) { + if ( enabledAttributes[ i ] !== newAttributes[ i ] ) { - pSize ++; - q = q.nextZ; - if ( ! q ) break; + gl.disableVertexAttribArray( i ); + enabledAttributes[ i ] = 0; } - qSize = inSize; - - while ( pSize > 0 || ( qSize > 0 && q ) ) { - - if ( pSize !== 0 && ( qSize === 0 || ! q || p.z <= q.z ) ) { - - e = p; - p = p.nextZ; - pSize --; - - } else { + } - e = q; - q = q.nextZ; - qSize --; + } - } + function vertexAttribPointer( index, size, type, normalized, stride, offset, integer ) { - if ( tail ) tail.nextZ = e; - else list = e; + if ( integer === true ) { - e.prevZ = tail; - tail = e; + gl.vertexAttribIPointer( index, size, type, stride, offset ); - } + } else { - p = q; + gl.vertexAttribPointer( index, size, type, normalized, stride, offset ); } - tail.nextZ = null; - inSize *= 2; - - } while ( numMerges > 1 ); + } - return list; + function setupVertexAttributes( object, material, program, geometry ) { -} + initAttributes(); -// z-order of a point given coords and inverse of the longer side of data bbox -function zOrder( x, y, minX, minY, invSize ) { + const geometryAttributes = geometry.attributes; - // coords are transformed into non-negative 15-bit integer range - x = ( x - minX ) * invSize | 0; - y = ( y - minY ) * invSize | 0; + const programAttributes = program.getAttributes(); - x = ( x | ( x << 8 ) ) & 0x00FF00FF; - x = ( x | ( x << 4 ) ) & 0x0F0F0F0F; - x = ( x | ( x << 2 ) ) & 0x33333333; - x = ( x | ( x << 1 ) ) & 0x55555555; + const materialDefaultAttributeValues = material.defaultAttributeValues; - y = ( y | ( y << 8 ) ) & 0x00FF00FF; - y = ( y | ( y << 4 ) ) & 0x0F0F0F0F; - y = ( y | ( y << 2 ) ) & 0x33333333; - y = ( y | ( y << 1 ) ) & 0x55555555; + for ( const name in programAttributes ) { - return x | ( y << 1 ); + const programAttribute = programAttributes[ name ]; -} + if ( programAttribute.location >= 0 ) { -// find the leftmost node of a polygon ring -function getLeftmost( start ) { + let geometryAttribute = geometryAttributes[ name ]; - let p = start, - leftmost = start; - do { + if ( geometryAttribute === undefined ) { - if ( p.x < leftmost.x || ( p.x === leftmost.x && p.y < leftmost.y ) ) leftmost = p; - p = p.next; + if ( name === 'instanceMatrix' && object.instanceMatrix ) geometryAttribute = object.instanceMatrix; + if ( name === 'instanceColor' && object.instanceColor ) geometryAttribute = object.instanceColor; - } while ( p !== start ); + } - return leftmost; + if ( geometryAttribute !== undefined ) { -} + const normalized = geometryAttribute.normalized; + const size = geometryAttribute.itemSize; -// check if a point lies within a convex triangle -function pointInTriangle( ax, ay, bx, by, cx, cy, px, py ) { + const attribute = attributes.get( geometryAttribute ); - return ( cx - px ) * ( ay - py ) >= ( ax - px ) * ( cy - py ) && - ( ax - px ) * ( by - py ) >= ( bx - px ) * ( ay - py ) && - ( bx - px ) * ( cy - py ) >= ( cx - px ) * ( by - py ); + // TODO Attribute may not be available on context restore -} + if ( attribute === undefined ) continue; -// check if a diagonal between two polygon nodes is valid (lies in polygon interior) -function isValidDiagonal( a, b ) { + const buffer = attribute.buffer; + const type = attribute.type; + const bytesPerElement = attribute.bytesPerElement; - return a.next.i !== b.i && a.prev.i !== b.i && ! intersectsPolygon( a, b ) && // dones't intersect other edges - ( locallyInside( a, b ) && locallyInside( b, a ) && middleInside( a, b ) && // locally visible - ( area( a.prev, a, b.prev ) || area( a, b.prev, b ) ) || // does not create opposite-facing sectors - equals( a, b ) && area( a.prev, a, a.next ) > 0 && area( b.prev, b, b.next ) > 0 ); // special zero-length case + // check for integer attributes -} + const integer = ( type === gl.INT || type === gl.UNSIGNED_INT || geometryAttribute.gpuType === IntType ); -// signed area of a triangle -function area( p, q, r ) { + if ( geometryAttribute.isInterleavedBufferAttribute ) { - return ( q.y - p.y ) * ( r.x - q.x ) - ( q.x - p.x ) * ( r.y - q.y ); + const data = geometryAttribute.data; + const stride = data.stride; + const offset = geometryAttribute.offset; -} + if ( data.isInstancedInterleavedBuffer ) { -// check if two points are equal -function equals( p1, p2 ) { + for ( let i = 0; i < programAttribute.locationSize; i ++ ) { - return p1.x === p2.x && p1.y === p2.y; + enableAttributeAndDivisor( programAttribute.location + i, data.meshPerAttribute ); -} + } -// check if two segments intersect -function intersects( p1, q1, p2, q2 ) { + if ( object.isInstancedMesh !== true && geometry._maxInstanceCount === undefined ) { - const o1 = sign( area( p1, q1, p2 ) ); - const o2 = sign( area( p1, q1, q2 ) ); - const o3 = sign( area( p2, q2, p1 ) ); - const o4 = sign( area( p2, q2, q1 ) ); + geometry._maxInstanceCount = data.meshPerAttribute * data.count; - if ( o1 !== o2 && o3 !== o4 ) return true; // general case + } - if ( o1 === 0 && onSegment( p1, p2, q1 ) ) return true; // p1, q1 and p2 are collinear and p2 lies on p1q1 - if ( o2 === 0 && onSegment( p1, q2, q1 ) ) return true; // p1, q1 and q2 are collinear and q2 lies on p1q1 - if ( o3 === 0 && onSegment( p2, p1, q2 ) ) return true; // p2, q2 and p1 are collinear and p1 lies on p2q2 - if ( o4 === 0 && onSegment( p2, q1, q2 ) ) return true; // p2, q2 and q1 are collinear and q1 lies on p2q2 + } else { - return false; + for ( let i = 0; i < programAttribute.locationSize; i ++ ) { -} + enableAttribute( programAttribute.location + i ); -// for collinear points p, q, r, check if point q lies on segment pr -function onSegment( p, q, r ) { + } - return q.x <= Math.max( p.x, r.x ) && q.x >= Math.min( p.x, r.x ) && q.y <= Math.max( p.y, r.y ) && q.y >= Math.min( p.y, r.y ); + } -} + gl.bindBuffer( gl.ARRAY_BUFFER, buffer ); -function sign( num ) { + for ( let i = 0; i < programAttribute.locationSize; i ++ ) { - return num > 0 ? 1 : num < 0 ? - 1 : 0; + vertexAttribPointer( + programAttribute.location + i, + size / programAttribute.locationSize, + type, + normalized, + stride * bytesPerElement, + ( offset + ( size / programAttribute.locationSize ) * i ) * bytesPerElement, + integer + ); -} + } -// check if a polygon diagonal intersects any polygon segments -function intersectsPolygon( a, b ) { + } else { - let p = a; - do { + if ( geometryAttribute.isInstancedBufferAttribute ) { - if ( p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i && - intersects( p, p.next, a, b ) ) return true; - p = p.next; + for ( let i = 0; i < programAttribute.locationSize; i ++ ) { - } while ( p !== a ); + enableAttributeAndDivisor( programAttribute.location + i, geometryAttribute.meshPerAttribute ); - return false; + } -} + if ( object.isInstancedMesh !== true && geometry._maxInstanceCount === undefined ) { -// check if a polygon diagonal is locally inside the polygon -function locallyInside( a, b ) { + geometry._maxInstanceCount = geometryAttribute.meshPerAttribute * geometryAttribute.count; - return area( a.prev, a, a.next ) < 0 ? - area( a, b, a.next ) >= 0 && area( a, a.prev, b ) >= 0 : - area( a, b, a.prev ) < 0 || area( a, a.next, b ) < 0; + } -} + } else { -// check if the middle point of a polygon diagonal is inside the polygon -function middleInside( a, b ) { + for ( let i = 0; i < programAttribute.locationSize; i ++ ) { - let p = a, - inside = false; - const px = ( a.x + b.x ) / 2, - py = ( a.y + b.y ) / 2; - do { + enableAttribute( programAttribute.location + i ); - if ( ( ( p.y > py ) !== ( p.next.y > py ) ) && p.next.y !== p.y && - ( px < ( p.next.x - p.x ) * ( py - p.y ) / ( p.next.y - p.y ) + p.x ) ) - inside = ! inside; - p = p.next; + } - } while ( p !== a ); + } - return inside; + gl.bindBuffer( gl.ARRAY_BUFFER, buffer ); -} + for ( let i = 0; i < programAttribute.locationSize; i ++ ) { -// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two; -// if one belongs to the outer ring and another to a hole, it merges it into a single ring -function splitPolygon( a, b ) { + vertexAttribPointer( + programAttribute.location + i, + size / programAttribute.locationSize, + type, + normalized, + size * bytesPerElement, + ( size / programAttribute.locationSize ) * i * bytesPerElement, + integer + ); - const a2 = new Node( a.i, a.x, a.y ), - b2 = new Node( b.i, b.x, b.y ), - an = a.next, - bp = b.prev; + } - a.next = b; - b.prev = a; + } - a2.next = an; - an.prev = a2; + } else if ( materialDefaultAttributeValues !== undefined ) { - b2.next = a2; - a2.prev = b2; + const value = materialDefaultAttributeValues[ name ]; - bp.next = b2; - b2.prev = bp; + if ( value !== undefined ) { - return b2; + switch ( value.length ) { -} + case 2: + gl.vertexAttrib2fv( programAttribute.location, value ); + break; -// create a node and optionally link it with previous one (in a circular doubly linked list) -function insertNode( i, x, y, last ) { + case 3: + gl.vertexAttrib3fv( programAttribute.location, value ); + break; - const p = new Node( i, x, y ); + case 4: + gl.vertexAttrib4fv( programAttribute.location, value ); + break; - if ( ! last ) { + default: + gl.vertexAttrib1fv( programAttribute.location, value ); - p.prev = p; - p.next = p; + } - } else { + } - p.next = last.next; - p.prev = last; - last.next.prev = p; - last.next = p; + } - } + } - return p; + } -} + disableUnusedAttributes(); -function removeNode( p ) { + } - p.next.prev = p.prev; - p.prev.next = p.next; + function dispose() { - if ( p.prevZ ) p.prevZ.nextZ = p.nextZ; - if ( p.nextZ ) p.nextZ.prevZ = p.prevZ; + reset(); -} + for ( const geometryId in bindingStates ) { -function Node( i, x, y ) { + const programMap = bindingStates[ geometryId ]; - // vertex index in coordinates array - this.i = i; + for ( const programId in programMap ) { - // vertex coordinates - this.x = x; - this.y = y; + const stateMap = programMap[ programId ]; - // previous and next vertex nodes in a polygon ring - this.prev = null; - this.next = null; + for ( const wireframe in stateMap ) { - // z-order curve value - this.z = 0; + deleteVertexArrayObject( stateMap[ wireframe ].object ); - // previous and next nodes in z-order - this.prevZ = null; - this.nextZ = null; + delete stateMap[ wireframe ]; - // indicates whether this is a steiner point - this.steiner = false; + } -} + delete programMap[ programId ]; -function signedArea( data, start, end, dim ) { + } - let sum = 0; - for ( let i = start, j = end - dim; i < end; i += dim ) { + delete bindingStates[ geometryId ]; - sum += ( data[ j ] - data[ i ] ) * ( data[ i + 1 ] + data[ j + 1 ] ); - j = i; + } } - return sum; - -} + function releaseStatesOfGeometry( geometry ) { -class ShapeUtils { + if ( bindingStates[ geometry.id ] === undefined ) return; - // calculate area of the contour polygon + const programMap = bindingStates[ geometry.id ]; - static area( contour ) { + for ( const programId in programMap ) { - const n = contour.length; - let a = 0.0; + const stateMap = programMap[ programId ]; - for ( let p = n - 1, q = 0; q < n; p = q ++ ) { + for ( const wireframe in stateMap ) { - a += contour[ p ].x * contour[ q ].y - contour[ q ].x * contour[ p ].y; + deleteVertexArrayObject( stateMap[ wireframe ].object ); - } + delete stateMap[ wireframe ]; - return a * 0.5; + } - } + delete programMap[ programId ]; - static isClockWise( pts ) { + } - return ShapeUtils.area( pts ) < 0; + delete bindingStates[ geometry.id ]; } - static triangulateShape( contour, holes ) { - - const vertices = []; // flat array of vertices like [ x0,y0, x1,y1, x2,y2, ... ] - const holeIndices = []; // array of hole indices - const faces = []; // final array of vertex indices like [ [ a,b,d ], [ b,c,d ] ] - - removeDupEndPts( contour ); - addContour( vertices, contour ); - - // - - let holeIndex = contour.length; + function releaseStatesOfProgram( program ) { - holes.forEach( removeDupEndPts ); + for ( const geometryId in bindingStates ) { - for ( let i = 0; i < holes.length; i ++ ) { + const programMap = bindingStates[ geometryId ]; - holeIndices.push( holeIndex ); - holeIndex += holes[ i ].length; - addContour( vertices, holes[ i ] ); + if ( programMap[ program.id ] === undefined ) continue; - } + const stateMap = programMap[ program.id ]; - // + for ( const wireframe in stateMap ) { - const triangles = Earcut.triangulate( vertices, holeIndices ); + deleteVertexArrayObject( stateMap[ wireframe ].object ); - // + delete stateMap[ wireframe ]; - for ( let i = 0; i < triangles.length; i += 3 ) { + } - faces.push( triangles.slice( i, i + 3 ) ); + delete programMap[ program.id ]; } - return faces; - } -} - -function removeDupEndPts( points ) { + function reset() { - const l = points.length; + resetDefaultState(); + forceUpdate = true; - if ( l > 2 && points[ l - 1 ].equals( points[ 0 ] ) ) { + if ( currentState === defaultState ) return; - points.pop(); + currentState = defaultState; + bindVertexArrayObject( currentState.object ); } -} - -function addContour( vertices, contour ) { + // for backward-compatibility - for ( let i = 0; i < contour.length; i ++ ) { + function resetDefaultState() { - vertices.push( contour[ i ].x ); - vertices.push( contour[ i ].y ); + defaultState.geometry = null; + defaultState.program = null; + defaultState.wireframe = false; } -} - -/** - * Creates extruded geometry from a path shape. - * - * parameters = { - * - * curveSegments: , // number of points on the curves - * steps: , // number of points for z-side extrusions / used for subdividing segments of extrude spline too - * depth: , // Depth to extrude the shape - * - * bevelEnabled: , // turn on bevel - * bevelThickness: , // how deep into the original shape bevel goes - * bevelSize: , // how far from shape outline (including bevelOffset) is bevel - * bevelOffset: , // how far from shape outline does bevel start - * bevelSegments: , // number of bevel layers - * - * extrudePath: // curve to extrude shape along - * - * UVGenerator: // object that provides UV generator functions - * - * } - */ - + return { -class ExtrudeGeometry extends BufferGeometry { + setup: setup, + reset: reset, + resetDefaultState: resetDefaultState, + dispose: dispose, + releaseStatesOfGeometry: releaseStatesOfGeometry, + releaseStatesOfProgram: releaseStatesOfProgram, - constructor( shapes = new Shape( [ new Vector2( 0.5, 0.5 ), new Vector2( - 0.5, 0.5 ), new Vector2( - 0.5, - 0.5 ), new Vector2( 0.5, - 0.5 ) ] ), options = {} ) { + initAttributes: initAttributes, + enableAttribute: enableAttribute, + disableUnusedAttributes: disableUnusedAttributes - super(); + }; - this.type = 'ExtrudeGeometry'; +} - this.parameters = { - shapes: shapes, - options: options - }; +function WebGLBufferRenderer( gl, extensions, info ) { - shapes = Array.isArray( shapes ) ? shapes : [ shapes ]; + let mode; - const scope = this; + function setMode( value ) { - const verticesArray = []; - const uvArray = []; + mode = value; - for ( let i = 0, l = shapes.length; i < l; i ++ ) { + } - const shape = shapes[ i ]; - addShape( shape ); + function render( start, count ) { - } + gl.drawArrays( mode, start, count ); - // build geometry + info.update( count, mode, 1 ); - this.setAttribute( 'position', new Float32BufferAttribute( verticesArray, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvArray, 2 ) ); + } - this.computeVertexNormals(); + function renderInstances( start, count, primcount ) { - // functions + if ( primcount === 0 ) return; - function addShape( shape ) { + gl.drawArraysInstanced( mode, start, count, primcount ); - const placeholder = []; + info.update( count, mode, primcount ); - // options + } - const curveSegments = options.curveSegments !== undefined ? options.curveSegments : 12; - const steps = options.steps !== undefined ? options.steps : 1; - const depth = options.depth !== undefined ? options.depth : 1; + function renderMultiDraw( starts, counts, drawCount ) { - let bevelEnabled = options.bevelEnabled !== undefined ? options.bevelEnabled : true; - let bevelThickness = options.bevelThickness !== undefined ? options.bevelThickness : 0.2; - let bevelSize = options.bevelSize !== undefined ? options.bevelSize : bevelThickness - 0.1; - let bevelOffset = options.bevelOffset !== undefined ? options.bevelOffset : 0; - let bevelSegments = options.bevelSegments !== undefined ? options.bevelSegments : 3; + if ( drawCount === 0 ) return; - const extrudePath = options.extrudePath; + const extension = extensions.get( 'WEBGL_multi_draw' ); + extension.multiDrawArraysWEBGL( mode, starts, 0, counts, 0, drawCount ); - const uvgen = options.UVGenerator !== undefined ? options.UVGenerator : WorldUVGenerator; + let elementCount = 0; + for ( let i = 0; i < drawCount; i ++ ) { - // + elementCount += counts[ i ]; - let extrudePts, extrudeByPath = false; - let splineTube, binormal, normal, position2; + } - if ( extrudePath ) { + info.update( elementCount, mode, 1 ); - extrudePts = extrudePath.getSpacedPoints( steps ); + } - extrudeByPath = true; - bevelEnabled = false; // bevels not supported for path extrusion + function renderMultiDrawInstances( starts, counts, drawCount, primcount ) { - // SETUP TNB variables + if ( drawCount === 0 ) return; - // TODO1 - have a .isClosed in spline? + const extension = extensions.get( 'WEBGL_multi_draw' ); - splineTube = extrudePath.computeFrenetFrames( steps, false ); + if ( extension === null ) { - // console.log(splineTube, 'splineTube', splineTube.normals.length, 'steps', steps, 'extrudePts', extrudePts.length); + for ( let i = 0; i < starts.length; i ++ ) { - binormal = new Vector3(); - normal = new Vector3(); - position2 = new Vector3(); + renderInstances( starts[ i ], counts[ i ], primcount[ i ] ); } - // Safeguards if bevels are not enabled + } else { - if ( ! bevelEnabled ) { + extension.multiDrawArraysInstancedWEBGL( mode, starts, 0, counts, 0, primcount, 0, drawCount ); - bevelSegments = 0; - bevelThickness = 0; - bevelSize = 0; - bevelOffset = 0; + let elementCount = 0; + for ( let i = 0; i < drawCount; i ++ ) { + + elementCount += counts[ i ] * primcount[ i ]; } - // Variables initialization + info.update( elementCount, mode, 1 ); - const shapePoints = shape.extractPoints( curveSegments ); + } - let vertices = shapePoints.shape; - const holes = shapePoints.holes; + } - const reverse = ! ShapeUtils.isClockWise( vertices ); + // - if ( reverse ) { + this.setMode = setMode; + this.render = render; + this.renderInstances = renderInstances; + this.renderMultiDraw = renderMultiDraw; + this.renderMultiDrawInstances = renderMultiDrawInstances; - vertices = vertices.reverse(); +} - // Maybe we should also check if holes are in the opposite direction, just to be safe ... +function WebGLCapabilities( gl, extensions, parameters, utils ) { - for ( let h = 0, hl = holes.length; h < hl; h ++ ) { + let maxAnisotropy; - const ahole = holes[ h ]; + function getMaxAnisotropy() { - if ( ShapeUtils.isClockWise( ahole ) ) { + if ( maxAnisotropy !== undefined ) return maxAnisotropy; - holes[ h ] = ahole.reverse(); + if ( extensions.has( 'EXT_texture_filter_anisotropic' ) === true ) { - } + const extension = extensions.get( 'EXT_texture_filter_anisotropic' ); - } + maxAnisotropy = gl.getParameter( extension.MAX_TEXTURE_MAX_ANISOTROPY_EXT ); - } + } else { + maxAnisotropy = 0; - const faces = ShapeUtils.triangulateShape( vertices, holes ); + } - /* Vertices */ + return maxAnisotropy; - const contour = vertices; // vertices has all points but contour has only points of circumference + } - for ( let h = 0, hl = holes.length; h < hl; h ++ ) { + function textureFormatReadable( textureFormat ) { - const ahole = holes[ h ]; + if ( textureFormat !== RGBAFormat && utils.convert( textureFormat ) !== gl.getParameter( gl.IMPLEMENTATION_COLOR_READ_FORMAT ) ) { - vertices = vertices.concat( ahole ); + return false; - } + } + return true; - function scalePt2( pt, vec, size ) { + } - if ( ! vec ) console.error( 'THREE.ExtrudeGeometry: vec does not exist' ); + function textureTypeReadable( textureType ) { - return pt.clone().addScaledVector( vec, size ); + const halfFloatSupportedByExt = ( textureType === HalfFloatType ) && ( extensions.has( 'EXT_color_buffer_half_float' ) || extensions.has( 'EXT_color_buffer_float' ) ); - } + if ( textureType !== UnsignedByteType && utils.convert( textureType ) !== gl.getParameter( gl.IMPLEMENTATION_COLOR_READ_TYPE ) && // Edge and Chrome Mac < 52 (#9513) + textureType !== FloatType && ! halfFloatSupportedByExt ) { - const vlen = vertices.length, flen = faces.length; + return false; + } - // Find directions for point movement + return true; + } - function getBevelVec( inPt, inPrev, inNext ) { + function getMaxPrecision( precision ) { - // computes for inPt the corresponding point inPt' on a new contour - // shifted by 1 unit (length of normalized vector) to the left - // if we walk along contour clockwise, this new contour is outside the old one - // - // inPt' is the intersection of the two lines parallel to the two - // adjacent edges of inPt at a distance of 1 unit on the left side. + if ( precision === 'highp' ) { - let v_trans_x, v_trans_y, shrink_by; // resulting translation vector for inPt + if ( gl.getShaderPrecisionFormat( gl.VERTEX_SHADER, gl.HIGH_FLOAT ).precision > 0 && + gl.getShaderPrecisionFormat( gl.FRAGMENT_SHADER, gl.HIGH_FLOAT ).precision > 0 ) { - // good reading for geometry algorithms (here: line-line intersection) - // http://geomalgorithms.com/a05-_intersect-1.html + return 'highp'; - const v_prev_x = inPt.x - inPrev.x, - v_prev_y = inPt.y - inPrev.y; - const v_next_x = inNext.x - inPt.x, - v_next_y = inNext.y - inPt.y; + } - const v_prev_lensq = ( v_prev_x * v_prev_x + v_prev_y * v_prev_y ); + precision = 'mediump'; - // check for collinear edges - const collinear0 = ( v_prev_x * v_next_y - v_prev_y * v_next_x ); + } - if ( Math.abs( collinear0 ) > Number.EPSILON ) { + if ( precision === 'mediump' ) { - // not collinear + if ( gl.getShaderPrecisionFormat( gl.VERTEX_SHADER, gl.MEDIUM_FLOAT ).precision > 0 && + gl.getShaderPrecisionFormat( gl.FRAGMENT_SHADER, gl.MEDIUM_FLOAT ).precision > 0 ) { - // length of vectors for normalizing + return 'mediump'; - const v_prev_len = Math.sqrt( v_prev_lensq ); - const v_next_len = Math.sqrt( v_next_x * v_next_x + v_next_y * v_next_y ); + } - // shift adjacent points by unit vectors to the left + } - const ptPrevShift_x = ( inPrev.x - v_prev_y / v_prev_len ); - const ptPrevShift_y = ( inPrev.y + v_prev_x / v_prev_len ); + return 'lowp'; - const ptNextShift_x = ( inNext.x - v_next_y / v_next_len ); - const ptNextShift_y = ( inNext.y + v_next_x / v_next_len ); + } - // scaling factor for v_prev to intersection point + let precision = parameters.precision !== undefined ? parameters.precision : 'highp'; + const maxPrecision = getMaxPrecision( precision ); - const sf = ( ( ptNextShift_x - ptPrevShift_x ) * v_next_y - - ( ptNextShift_y - ptPrevShift_y ) * v_next_x ) / - ( v_prev_x * v_next_y - v_prev_y * v_next_x ); + if ( maxPrecision !== precision ) { - // vector from inPt to intersection point + console.warn( 'THREE.WebGLRenderer:', precision, 'not supported, using', maxPrecision, 'instead.' ); + precision = maxPrecision; - v_trans_x = ( ptPrevShift_x + v_prev_x * sf - inPt.x ); - v_trans_y = ( ptPrevShift_y + v_prev_y * sf - inPt.y ); + } - // Don't normalize!, otherwise sharp corners become ugly - // but prevent crazy spikes - const v_trans_lensq = ( v_trans_x * v_trans_x + v_trans_y * v_trans_y ); - if ( v_trans_lensq <= 2 ) { + const logarithmicDepthBuffer = parameters.logarithmicDepthBuffer === true; + const reverseDepthBuffer = parameters.reverseDepthBuffer === true && extensions.has( 'EXT_clip_control' ); - return new Vector2( v_trans_x, v_trans_y ); + const maxTextures = gl.getParameter( gl.MAX_TEXTURE_IMAGE_UNITS ); + const maxVertexTextures = gl.getParameter( gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS ); + const maxTextureSize = gl.getParameter( gl.MAX_TEXTURE_SIZE ); + const maxCubemapSize = gl.getParameter( gl.MAX_CUBE_MAP_TEXTURE_SIZE ); - } else { + const maxAttributes = gl.getParameter( gl.MAX_VERTEX_ATTRIBS ); + const maxVertexUniforms = gl.getParameter( gl.MAX_VERTEX_UNIFORM_VECTORS ); + const maxVaryings = gl.getParameter( gl.MAX_VARYING_VECTORS ); + const maxFragmentUniforms = gl.getParameter( gl.MAX_FRAGMENT_UNIFORM_VECTORS ); - shrink_by = Math.sqrt( v_trans_lensq / 2 ); + const vertexTextures = maxVertexTextures > 0; - } + const maxSamples = gl.getParameter( gl.MAX_SAMPLES ); - } else { + return { - // handle special case of collinear edges + isWebGL2: true, // keeping this for backwards compatibility - let direction_eq = false; // assumes: opposite + getMaxAnisotropy: getMaxAnisotropy, + getMaxPrecision: getMaxPrecision, - if ( v_prev_x > Number.EPSILON ) { + textureFormatReadable: textureFormatReadable, + textureTypeReadable: textureTypeReadable, - if ( v_next_x > Number.EPSILON ) { + precision: precision, + logarithmicDepthBuffer: logarithmicDepthBuffer, + reverseDepthBuffer: reverseDepthBuffer, - direction_eq = true; + maxTextures: maxTextures, + maxVertexTextures: maxVertexTextures, + maxTextureSize: maxTextureSize, + maxCubemapSize: maxCubemapSize, - } + maxAttributes: maxAttributes, + maxVertexUniforms: maxVertexUniforms, + maxVaryings: maxVaryings, + maxFragmentUniforms: maxFragmentUniforms, - } else { + vertexTextures: vertexTextures, - if ( v_prev_x < - Number.EPSILON ) { + maxSamples: maxSamples - if ( v_next_x < - Number.EPSILON ) { + }; - direction_eq = true; +} - } +function WebGLClipping( properties ) { - } else { + const scope = this; - if ( Math.sign( v_prev_y ) === Math.sign( v_next_y ) ) { + let globalState = null, + numGlobalPlanes = 0, + localClippingEnabled = false, + renderingShadows = false; - direction_eq = true; + const plane = new Plane(), + viewNormalMatrix = new Matrix3(), - } + uniform = { value: null, needsUpdate: false }; - } + this.uniform = uniform; + this.numPlanes = 0; + this.numIntersection = 0; - } + this.init = function ( planes, enableLocalClipping ) { - if ( direction_eq ) { + const enabled = + planes.length !== 0 || + enableLocalClipping || + // enable state of previous frame - the clipping code has to + // run another frame in order to reset the state: + numGlobalPlanes !== 0 || + localClippingEnabled; - // console.log("Warning: lines are a straight sequence"); - v_trans_x = - v_prev_y; - v_trans_y = v_prev_x; - shrink_by = Math.sqrt( v_prev_lensq ); + localClippingEnabled = enableLocalClipping; - } else { + numGlobalPlanes = planes.length; - // console.log("Warning: lines are a straight spike"); - v_trans_x = v_prev_x; - v_trans_y = v_prev_y; - shrink_by = Math.sqrt( v_prev_lensq / 2 ); + return enabled; - } + }; - } + this.beginShadows = function () { - return new Vector2( v_trans_x / shrink_by, v_trans_y / shrink_by ); + renderingShadows = true; + projectPlanes( null ); - } + }; + this.endShadows = function () { - const contourMovements = []; + renderingShadows = false; - for ( let i = 0, il = contour.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) { + }; - if ( j === il ) j = 0; - if ( k === il ) k = 0; + this.setGlobalState = function ( planes, camera ) { - // (j)---(i)---(k) - // console.log('i,j,k', i, j , k) + globalState = projectPlanes( planes, camera, 0 ); - contourMovements[ i ] = getBevelVec( contour[ i ], contour[ j ], contour[ k ] ); + }; - } + this.setState = function ( material, camera, useCache ) { - const holesMovements = []; - let oneHoleMovements, verticesMovements = contourMovements.concat(); + const planes = material.clippingPlanes, + clipIntersection = material.clipIntersection, + clipShadows = material.clipShadows; - for ( let h = 0, hl = holes.length; h < hl; h ++ ) { + const materialProperties = properties.get( material ); - const ahole = holes[ h ]; + if ( ! localClippingEnabled || planes === null || planes.length === 0 || renderingShadows && ! clipShadows ) { - oneHoleMovements = []; + // there's no local clipping - for ( let i = 0, il = ahole.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) { + if ( renderingShadows ) { - if ( j === il ) j = 0; - if ( k === il ) k = 0; + // there's no global clipping - // (j)---(i)---(k) - oneHoleMovements[ i ] = getBevelVec( ahole[ i ], ahole[ j ], ahole[ k ] ); + projectPlanes( null ); - } + } else { - holesMovements.push( oneHoleMovements ); - verticesMovements = verticesMovements.concat( oneHoleMovements ); + resetGlobalState(); } + } else { + + const nGlobal = renderingShadows ? 0 : numGlobalPlanes, + lGlobal = nGlobal * 4; - // Loop bevelSegments, 1 for the front, 1 for the back + let dstArray = materialProperties.clippingState || null; - for ( let b = 0; b < bevelSegments; b ++ ) { + uniform.value = dstArray; // ensure unique state - //for ( b = bevelSegments; b > 0; b -- ) { + dstArray = projectPlanes( planes, camera, lGlobal, useCache ); - const t = b / bevelSegments; - const z = bevelThickness * Math.cos( t * Math.PI / 2 ); - const bs = bevelSize * Math.sin( t * Math.PI / 2 ) + bevelOffset; + for ( let i = 0; i !== lGlobal; ++ i ) { - // contract shape + dstArray[ i ] = globalState[ i ]; - for ( let i = 0, il = contour.length; i < il; i ++ ) { + } - const vert = scalePt2( contour[ i ], contourMovements[ i ], bs ); + materialProperties.clippingState = dstArray; + this.numIntersection = clipIntersection ? this.numPlanes : 0; + this.numPlanes += nGlobal; - v( vert.x, vert.y, - z ); + } - } - // expand holes + }; - for ( let h = 0, hl = holes.length; h < hl; h ++ ) { + function resetGlobalState() { - const ahole = holes[ h ]; - oneHoleMovements = holesMovements[ h ]; + if ( uniform.value !== globalState ) { - for ( let i = 0, il = ahole.length; i < il; i ++ ) { + uniform.value = globalState; + uniform.needsUpdate = numGlobalPlanes > 0; - const vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs ); + } - v( vert.x, vert.y, - z ); + scope.numPlanes = numGlobalPlanes; + scope.numIntersection = 0; - } + } - } + function projectPlanes( planes, camera, dstOffset, skipTransform ) { - } + const nPlanes = planes !== null ? planes.length : 0; + let dstArray = null; - const bs = bevelSize + bevelOffset; + if ( nPlanes !== 0 ) { - // Back facing vertices + dstArray = uniform.value; - for ( let i = 0; i < vlen; i ++ ) { + if ( skipTransform !== true || dstArray === null ) { - const vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ]; + const flatSize = dstOffset + nPlanes * 4, + viewMatrix = camera.matrixWorldInverse; - if ( ! extrudeByPath ) { + viewNormalMatrix.getNormalMatrix( viewMatrix ); - v( vert.x, vert.y, 0 ); + if ( dstArray === null || dstArray.length < flatSize ) { - } else { + dstArray = new Float32Array( flatSize ); - // v( vert.x, vert.y + extrudePts[ 0 ].y, extrudePts[ 0 ].x ); + } - normal.copy( splineTube.normals[ 0 ] ).multiplyScalar( vert.x ); - binormal.copy( splineTube.binormals[ 0 ] ).multiplyScalar( vert.y ); + for ( let i = 0, i4 = dstOffset; i !== nPlanes; ++ i, i4 += 4 ) { - position2.copy( extrudePts[ 0 ] ).add( normal ).add( binormal ); + plane.copy( planes[ i ] ).applyMatrix4( viewMatrix, viewNormalMatrix ); - v( position2.x, position2.y, position2.z ); + plane.normal.toArray( dstArray, i4 ); + dstArray[ i4 + 3 ] = plane.constant; } } - // Add stepped vertices... - // Including front facing vertices - - for ( let s = 1; s <= steps; s ++ ) { + uniform.value = dstArray; + uniform.needsUpdate = true; - for ( let i = 0; i < vlen; i ++ ) { + } - const vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ]; + scope.numPlanes = nPlanes; + scope.numIntersection = 0; - if ( ! extrudeByPath ) { + return dstArray; - v( vert.x, vert.y, depth / steps * s ); + } - } else { +} - // v( vert.x, vert.y + extrudePts[ s - 1 ].y, extrudePts[ s - 1 ].x ); +function WebGLCubeMaps( renderer ) { - normal.copy( splineTube.normals[ s ] ).multiplyScalar( vert.x ); - binormal.copy( splineTube.binormals[ s ] ).multiplyScalar( vert.y ); + let cubemaps = new WeakMap(); - position2.copy( extrudePts[ s ] ).add( normal ).add( binormal ); + function mapTextureMapping( texture, mapping ) { - v( position2.x, position2.y, position2.z ); + if ( mapping === EquirectangularReflectionMapping ) { - } + texture.mapping = CubeReflectionMapping; - } + } else if ( mapping === EquirectangularRefractionMapping ) { - } + texture.mapping = CubeRefractionMapping; + } - // Add bevel segments planes + return texture; - //for ( b = 1; b <= bevelSegments; b ++ ) { - for ( let b = bevelSegments - 1; b >= 0; b -- ) { + } - const t = b / bevelSegments; - const z = bevelThickness * Math.cos( t * Math.PI / 2 ); - const bs = bevelSize * Math.sin( t * Math.PI / 2 ) + bevelOffset; + function get( texture ) { - // contract shape + if ( texture && texture.isTexture ) { - for ( let i = 0, il = contour.length; i < il; i ++ ) { + const mapping = texture.mapping; - const vert = scalePt2( contour[ i ], contourMovements[ i ], bs ); - v( vert.x, vert.y, depth + z ); + if ( mapping === EquirectangularReflectionMapping || mapping === EquirectangularRefractionMapping ) { - } + if ( cubemaps.has( texture ) ) { - // expand holes + const cubemap = cubemaps.get( texture ).texture; + return mapTextureMapping( cubemap, texture.mapping ); - for ( let h = 0, hl = holes.length; h < hl; h ++ ) { + } else { - const ahole = holes[ h ]; - oneHoleMovements = holesMovements[ h ]; + const image = texture.image; - for ( let i = 0, il = ahole.length; i < il; i ++ ) { + if ( image && image.height > 0 ) { - const vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs ); + const renderTarget = new WebGLCubeRenderTarget( image.height ); + renderTarget.fromEquirectangularTexture( renderer, texture ); + cubemaps.set( texture, renderTarget ); - if ( ! extrudeByPath ) { + texture.addEventListener( 'dispose', onTextureDispose ); - v( vert.x, vert.y, depth + z ); + return mapTextureMapping( renderTarget.texture, texture.mapping ); - } else { + } else { - v( vert.x, vert.y + extrudePts[ steps - 1 ].y, extrudePts[ steps - 1 ].x + z ); + // image not yet ready. try the conversion next frame - } + return null; } @@ -37202,14194 +60778,15520 @@ class ExtrudeGeometry extends BufferGeometry { } - /* Faces */ + } - // Top and bottom faces + return texture; - buildLidFaces(); + } - // Sides faces + function onTextureDispose( event ) { - buildSideFaces(); + const texture = event.target; + texture.removeEventListener( 'dispose', onTextureDispose ); - ///// Internal functions + const cubemap = cubemaps.get( texture ); - function buildLidFaces() { + if ( cubemap !== undefined ) { - const start = verticesArray.length / 3; + cubemaps.delete( texture ); + cubemap.dispose(); - if ( bevelEnabled ) { + } - let layer = 0; // steps + 1 - let offset = vlen * layer; + } - // Bottom faces + function dispose() { - for ( let i = 0; i < flen; i ++ ) { + cubemaps = new WeakMap(); - const face = faces[ i ]; - f3( face[ 2 ] + offset, face[ 1 ] + offset, face[ 0 ] + offset ); + } - } + return { + get: get, + dispose: dispose + }; - layer = steps + bevelSegments * 2; - offset = vlen * layer; +} - // Top faces +const LOD_MIN = 4; - for ( let i = 0; i < flen; i ++ ) { +// The standard deviations (radians) associated with the extra mips. These are +// chosen to approximate a Trowbridge-Reitz distribution function times the +// geometric shadowing function. These sigma values squared must match the +// variance #defines in cube_uv_reflection_fragment.glsl.js. +const EXTRA_LOD_SIGMA = [ 0.125, 0.215, 0.35, 0.446, 0.526, 0.582 ]; - const face = faces[ i ]; - f3( face[ 0 ] + offset, face[ 1 ] + offset, face[ 2 ] + offset ); +// The maximum length of the blur for loop. Smaller sigmas will use fewer +// samples and exit early, but not recompile the shader. +const MAX_SAMPLES = 20; - } +const _flatCamera = /*@__PURE__*/ new OrthographicCamera(); +const _clearColor = /*@__PURE__*/ new Color(); +let _oldTarget = null; +let _oldActiveCubeFace = 0; +let _oldActiveMipmapLevel = 0; +let _oldXrEnabled = false; - } else { +// Golden Ratio +const PHI = ( 1 + Math.sqrt( 5 ) ) / 2; +const INV_PHI = 1 / PHI; - // Bottom faces +// Vertices of a dodecahedron (except the opposites, which represent the +// same axis), used as axis directions evenly spread on a sphere. +const _axisDirections = [ + /*@__PURE__*/ new Vector3( - PHI, INV_PHI, 0 ), + /*@__PURE__*/ new Vector3( PHI, INV_PHI, 0 ), + /*@__PURE__*/ new Vector3( - INV_PHI, 0, PHI ), + /*@__PURE__*/ new Vector3( INV_PHI, 0, PHI ), + /*@__PURE__*/ new Vector3( 0, PHI, - INV_PHI ), + /*@__PURE__*/ new Vector3( 0, PHI, INV_PHI ), + /*@__PURE__*/ new Vector3( -1, 1, -1 ), + /*@__PURE__*/ new Vector3( 1, 1, -1 ), + /*@__PURE__*/ new Vector3( -1, 1, 1 ), + /*@__PURE__*/ new Vector3( 1, 1, 1 ) ]; - for ( let i = 0; i < flen; i ++ ) { +const _origin = /*@__PURE__*/ new Vector3(); - const face = faces[ i ]; - f3( face[ 2 ], face[ 1 ], face[ 0 ] ); +/** + * This class generates a Prefiltered, Mipmapped Radiance Environment Map + * (PMREM) from a cubeMap environment texture. This allows different levels of + * blur to be quickly accessed based on material roughness. It is packed into a + * special CubeUV format that allows us to perform custom interpolation so that + * we can support nonlinear formats such as RGBE. Unlike a traditional mipmap + * chain, it only goes down to the LOD_MIN level (above), and then creates extra + * even more filtered 'mips' at the same LOD_MIN resolution, associated with + * higher roughness levels. In this way we maintain resolution to smoothly + * interpolate diffuse lighting while limiting sampling computation. + * + * Paper: Fast, Accurate Image-Based Lighting: + * {@link https://drive.google.com/file/d/15y8r_UpKlU9SvV4ILb0C3qCPecS8pvLz/view} +*/ +class PMREMGenerator { - } + /** + * Constructs a new PMREM generator. + * + * @param {WebGLRenderer} renderer - The renderer. + */ + constructor( renderer ) { - // Top faces + this._renderer = renderer; + this._pingPongRenderTarget = null; - for ( let i = 0; i < flen; i ++ ) { + this._lodMax = 0; + this._cubeSize = 0; + this._lodPlanes = []; + this._sizeLods = []; + this._sigmas = []; - const face = faces[ i ]; - f3( face[ 0 ] + vlen * steps, face[ 1 ] + vlen * steps, face[ 2 ] + vlen * steps ); + this._blurMaterial = null; + this._cubemapMaterial = null; + this._equirectMaterial = null; - } + this._compileMaterial( this._blurMaterial ); + + } + + /** + * Generates a PMREM from a supplied Scene, which can be faster than using an + * image if networking bandwidth is low. Optional sigma specifies a blur radius + * in radians to be applied to the scene before PMREM generation. Optional near + * and far planes ensure the scene is rendered in its entirety. + * + * @param {Scene} scene - The scene to be captured. + * @param {number} [sigma=0] - The blur radius in radians. + * @param {number} [near=0.1] - The near plane distance. + * @param {number} [far=100] - The far plane distance. + * @param {Object} [options={}] - The configuration options. + * @param {number} [options.size=256] - The texture size of the PMREM. + * @param {Vector3} [options.renderTarget=origin] - The position of the internal cube camera that renders the scene. + * @return {WebGLRenderTarget} The resulting PMREM. + */ + fromScene( scene, sigma = 0, near = 0.1, far = 100, options = {} ) { + + const { + size = 256, + position = _origin, + } = options; + + _oldTarget = this._renderer.getRenderTarget(); + _oldActiveCubeFace = this._renderer.getActiveCubeFace(); + _oldActiveMipmapLevel = this._renderer.getActiveMipmapLevel(); + _oldXrEnabled = this._renderer.xr.enabled; - } + this._renderer.xr.enabled = false; - scope.addGroup( start, verticesArray.length / 3 - start, 0 ); + this._setSize( size ); - } + const cubeUVRenderTarget = this._allocateTargets(); + cubeUVRenderTarget.depthBuffer = true; - // Create faces for the z-sides of the shape + this._sceneToCubeUV( scene, near, far, cubeUVRenderTarget, position ); - function buildSideFaces() { + if ( sigma > 0 ) { - const start = verticesArray.length / 3; - let layeroffset = 0; - sidewalls( contour, layeroffset ); - layeroffset += contour.length; + this._blur( cubeUVRenderTarget, 0, 0, sigma ); - for ( let h = 0, hl = holes.length; h < hl; h ++ ) { + } - const ahole = holes[ h ]; - sidewalls( ahole, layeroffset ); + this._applyPMREM( cubeUVRenderTarget ); + this._cleanup( cubeUVRenderTarget ); - //, true - layeroffset += ahole.length; + return cubeUVRenderTarget; - } + } + /** + * Generates a PMREM from an equirectangular texture, which can be either LDR + * or HDR. The ideal input image size is 1k (1024 x 512), + * as this matches best with the 256 x 256 cubemap output. + * + * @param {Texture} equirectangular - The equirectangular texture to be converted. + * @param {?WebGLRenderTarget} [renderTarget=null] - The render target to use. + * @return {WebGLRenderTarget} The resulting PMREM. + */ + fromEquirectangular( equirectangular, renderTarget = null ) { - scope.addGroup( start, verticesArray.length / 3 - start, 1 ); + return this._fromTexture( equirectangular, renderTarget ); + } - } + /** + * Generates a PMREM from an cubemap texture, which can be either LDR + * or HDR. The ideal input cube size is 256 x 256, + * as this matches best with the 256 x 256 cubemap output. + * + * @param {Texture} cubemap - The cubemap texture to be converted. + * @param {?WebGLRenderTarget} [renderTarget=null] - The render target to use. + * @return {WebGLRenderTarget} The resulting PMREM. + */ + fromCubemap( cubemap, renderTarget = null ) { - function sidewalls( contour, layeroffset ) { + return this._fromTexture( cubemap, renderTarget ); - let i = contour.length; + } - while ( -- i >= 0 ) { + /** + * Pre-compiles the cubemap shader. You can get faster start-up by invoking this method during + * your texture's network fetch for increased concurrency. + */ + compileCubemapShader() { - const j = i; - let k = i - 1; - if ( k < 0 ) k = contour.length - 1; + if ( this._cubemapMaterial === null ) { - //console.log('b', i,j, i-1, k,vertices.length); + this._cubemapMaterial = _getCubemapMaterial(); + this._compileMaterial( this._cubemapMaterial ); - for ( let s = 0, sl = ( steps + bevelSegments * 2 ); s < sl; s ++ ) { + } - const slen1 = vlen * s; - const slen2 = vlen * ( s + 1 ); + } - const a = layeroffset + j + slen1, - b = layeroffset + k + slen1, - c = layeroffset + k + slen2, - d = layeroffset + j + slen2; + /** + * Pre-compiles the equirectangular shader. You can get faster start-up by invoking this method during + * your texture's network fetch for increased concurrency. + */ + compileEquirectangularShader() { - f4( a, b, c, d ); + if ( this._equirectMaterial === null ) { - } + this._equirectMaterial = _getEquirectMaterial(); + this._compileMaterial( this._equirectMaterial ); - } + } - } + } - function v( x, y, z ) { + /** + * Disposes of the PMREMGenerator's internal memory. Note that PMREMGenerator is a static class, + * so you should not need more than one PMREMGenerator object. If you do, calling dispose() on + * one of them will cause any others to also become unusable. + */ + dispose() { - placeholder.push( x ); - placeholder.push( y ); - placeholder.push( z ); + this._dispose(); - } + if ( this._cubemapMaterial !== null ) this._cubemapMaterial.dispose(); + if ( this._equirectMaterial !== null ) this._equirectMaterial.dispose(); + } - function f3( a, b, c ) { + // private interface - addVertex( a ); - addVertex( b ); - addVertex( c ); + _setSize( cubeSize ) { - const nextIndex = verticesArray.length / 3; - const uvs = uvgen.generateTopUV( scope, verticesArray, nextIndex - 3, nextIndex - 2, nextIndex - 1 ); + this._lodMax = Math.floor( Math.log2( cubeSize ) ); + this._cubeSize = Math.pow( 2, this._lodMax ); - addUV( uvs[ 0 ] ); - addUV( uvs[ 1 ] ); - addUV( uvs[ 2 ] ); + } - } + _dispose() { - function f4( a, b, c, d ) { + if ( this._blurMaterial !== null ) this._blurMaterial.dispose(); - addVertex( a ); - addVertex( b ); - addVertex( d ); + if ( this._pingPongRenderTarget !== null ) this._pingPongRenderTarget.dispose(); - addVertex( b ); - addVertex( c ); - addVertex( d ); + for ( let i = 0; i < this._lodPlanes.length; i ++ ) { + this._lodPlanes[ i ].dispose(); - const nextIndex = verticesArray.length / 3; - const uvs = uvgen.generateSideWallUV( scope, verticesArray, nextIndex - 6, nextIndex - 3, nextIndex - 2, nextIndex - 1 ); + } - addUV( uvs[ 0 ] ); - addUV( uvs[ 1 ] ); - addUV( uvs[ 3 ] ); + } - addUV( uvs[ 1 ] ); - addUV( uvs[ 2 ] ); - addUV( uvs[ 3 ] ); + _cleanup( outputTarget ) { - } + this._renderer.setRenderTarget( _oldTarget, _oldActiveCubeFace, _oldActiveMipmapLevel ); + this._renderer.xr.enabled = _oldXrEnabled; - function addVertex( index ) { + outputTarget.scissorTest = false; + _setViewport( outputTarget, 0, 0, outputTarget.width, outputTarget.height ); - verticesArray.push( placeholder[ index * 3 + 0 ] ); - verticesArray.push( placeholder[ index * 3 + 1 ] ); - verticesArray.push( placeholder[ index * 3 + 2 ] ); + } - } + _fromTexture( texture, renderTarget ) { + if ( texture.mapping === CubeReflectionMapping || texture.mapping === CubeRefractionMapping ) { - function addUV( vector2 ) { + this._setSize( texture.image.length === 0 ? 16 : ( texture.image[ 0 ].width || texture.image[ 0 ].image.width ) ); - uvArray.push( vector2.x ); - uvArray.push( vector2.y ); + } else { // Equirectangular - } + this._setSize( texture.image.width / 4 ); } - } - - copy( source ) { + _oldTarget = this._renderer.getRenderTarget(); + _oldActiveCubeFace = this._renderer.getActiveCubeFace(); + _oldActiveMipmapLevel = this._renderer.getActiveMipmapLevel(); + _oldXrEnabled = this._renderer.xr.enabled; - super.copy( source ); + this._renderer.xr.enabled = false; - this.parameters = Object.assign( {}, source.parameters ); + const cubeUVRenderTarget = renderTarget || this._allocateTargets(); + this._textureToCubeUV( texture, cubeUVRenderTarget ); + this._applyPMREM( cubeUVRenderTarget ); + this._cleanup( cubeUVRenderTarget ); - return this; + return cubeUVRenderTarget; } - toJSON() { + _allocateTargets() { - const data = super.toJSON(); + const width = 3 * Math.max( this._cubeSize, 16 * 7 ); + const height = 4 * this._cubeSize; - const shapes = this.parameters.shapes; - const options = this.parameters.options; + const params = { + magFilter: LinearFilter, + minFilter: LinearFilter, + generateMipmaps: false, + type: HalfFloatType, + format: RGBAFormat, + colorSpace: LinearSRGBColorSpace, + depthBuffer: false + }; - return toJSON$1( shapes, options, data ); + const cubeUVRenderTarget = _createRenderTarget( width, height, params ); - } + if ( this._pingPongRenderTarget === null || this._pingPongRenderTarget.width !== width || this._pingPongRenderTarget.height !== height ) { - static fromJSON( data, shapes ) { + if ( this._pingPongRenderTarget !== null ) { - const geometryShapes = []; + this._dispose(); - for ( let j = 0, jl = data.shapes.length; j < jl; j ++ ) { + } - const shape = shapes[ data.shapes[ j ] ]; + this._pingPongRenderTarget = _createRenderTarget( width, height, params ); - geometryShapes.push( shape ); + const { _lodMax } = this; + ( { sizeLods: this._sizeLods, lodPlanes: this._lodPlanes, sigmas: this._sigmas } = _createPlanes( _lodMax ) ); - } + this._blurMaterial = _getBlurShader( _lodMax, width, height ); - const extrudePath = data.options.extrudePath; + } - if ( extrudePath !== undefined ) { + return cubeUVRenderTarget; - data.options.extrudePath = new Curves[ extrudePath.type ]().fromJSON( extrudePath ); + } - } + _compileMaterial( material ) { - return new ExtrudeGeometry( geometryShapes, data.options ); + const tmpMesh = new Mesh( this._lodPlanes[ 0 ], material ); + this._renderer.compile( tmpMesh, _flatCamera ); } -} + _sceneToCubeUV( scene, near, far, cubeUVRenderTarget, position ) { -const WorldUVGenerator = { + const fov = 90; + const aspect = 1; + const cubeCamera = new PerspectiveCamera( fov, aspect, near, far ); + const upSign = [ 1, -1, 1, 1, 1, 1 ]; + const forwardSign = [ 1, 1, 1, -1, -1, -1 ]; + const renderer = this._renderer; - generateTopUV: function ( geometry, vertices, indexA, indexB, indexC ) { + const originalAutoClear = renderer.autoClear; + const toneMapping = renderer.toneMapping; + renderer.getClearColor( _clearColor ); - const a_x = vertices[ indexA * 3 ]; - const a_y = vertices[ indexA * 3 + 1 ]; - const b_x = vertices[ indexB * 3 ]; - const b_y = vertices[ indexB * 3 + 1 ]; - const c_x = vertices[ indexC * 3 ]; - const c_y = vertices[ indexC * 3 + 1 ]; + renderer.toneMapping = NoToneMapping; + renderer.autoClear = false; - return [ - new Vector2( a_x, a_y ), - new Vector2( b_x, b_y ), - new Vector2( c_x, c_y ) - ]; + const backgroundMaterial = new MeshBasicMaterial( { + name: 'PMREM.Background', + side: BackSide, + depthWrite: false, + depthTest: false, + } ); - }, + const backgroundBox = new Mesh( new BoxGeometry(), backgroundMaterial ); - generateSideWallUV: function ( geometry, vertices, indexA, indexB, indexC, indexD ) { + let useSolidColor = false; + const background = scene.background; - const a_x = vertices[ indexA * 3 ]; - const a_y = vertices[ indexA * 3 + 1 ]; - const a_z = vertices[ indexA * 3 + 2 ]; - const b_x = vertices[ indexB * 3 ]; - const b_y = vertices[ indexB * 3 + 1 ]; - const b_z = vertices[ indexB * 3 + 2 ]; - const c_x = vertices[ indexC * 3 ]; - const c_y = vertices[ indexC * 3 + 1 ]; - const c_z = vertices[ indexC * 3 + 2 ]; - const d_x = vertices[ indexD * 3 ]; - const d_y = vertices[ indexD * 3 + 1 ]; - const d_z = vertices[ indexD * 3 + 2 ]; + if ( background ) { - if ( Math.abs( a_y - b_y ) < Math.abs( a_x - b_x ) ) { + if ( background.isColor ) { - return [ - new Vector2( a_x, 1 - a_z ), - new Vector2( b_x, 1 - b_z ), - new Vector2( c_x, 1 - c_z ), - new Vector2( d_x, 1 - d_z ) - ]; + backgroundMaterial.color.copy( background ); + scene.background = null; + useSolidColor = true; + + } } else { - return [ - new Vector2( a_y, 1 - a_z ), - new Vector2( b_y, 1 - b_z ), - new Vector2( c_y, 1 - c_z ), - new Vector2( d_y, 1 - d_z ) - ]; + backgroundMaterial.color.copy( _clearColor ); + useSolidColor = true; } - } - -}; - -function toJSON$1( shapes, options, data ) { + for ( let i = 0; i < 6; i ++ ) { - data.shapes = []; + const col = i % 3; - if ( Array.isArray( shapes ) ) { + if ( col === 0 ) { - for ( let i = 0, l = shapes.length; i < l; i ++ ) { + cubeCamera.up.set( 0, upSign[ i ], 0 ); + cubeCamera.position.set( position.x, position.y, position.z ); + cubeCamera.lookAt( position.x + forwardSign[ i ], position.y, position.z ); - const shape = shapes[ i ]; + } else if ( col === 1 ) { - data.shapes.push( shape.uuid ); + cubeCamera.up.set( 0, 0, upSign[ i ] ); + cubeCamera.position.set( position.x, position.y, position.z ); + cubeCamera.lookAt( position.x, position.y + forwardSign[ i ], position.z ); - } - } else { + } else { - data.shapes.push( shapes.uuid ); + cubeCamera.up.set( 0, upSign[ i ], 0 ); + cubeCamera.position.set( position.x, position.y, position.z ); + cubeCamera.lookAt( position.x, position.y, position.z + forwardSign[ i ] ); - } + } - data.options = Object.assign( {}, options ); + const size = this._cubeSize; - if ( options.extrudePath !== undefined ) data.options.extrudePath = options.extrudePath.toJSON(); + _setViewport( cubeUVRenderTarget, col * size, i > 2 ? size : 0, size, size ); - return data; + renderer.setRenderTarget( cubeUVRenderTarget ); -} + if ( useSolidColor ) { -class IcosahedronGeometry extends PolyhedronGeometry { + renderer.render( backgroundBox, cubeCamera ); - constructor( radius = 1, detail = 0 ) { + } - const t = ( 1 + Math.sqrt( 5 ) ) / 2; + renderer.render( scene, cubeCamera ); - const vertices = [ - - 1, t, 0, 1, t, 0, - 1, - t, 0, 1, - t, 0, - 0, - 1, t, 0, 1, t, 0, - 1, - t, 0, 1, - t, - t, 0, - 1, t, 0, 1, - t, 0, - 1, - t, 0, 1 - ]; + } - const indices = [ - 0, 11, 5, 0, 5, 1, 0, 1, 7, 0, 7, 10, 0, 10, 11, - 1, 5, 9, 5, 11, 4, 11, 10, 2, 10, 7, 6, 7, 1, 8, - 3, 9, 4, 3, 4, 2, 3, 2, 6, 3, 6, 8, 3, 8, 9, - 4, 9, 5, 2, 4, 11, 6, 2, 10, 8, 6, 7, 9, 8, 1 - ]; + backgroundBox.geometry.dispose(); + backgroundBox.material.dispose(); - super( vertices, indices, radius, detail ); + renderer.toneMapping = toneMapping; + renderer.autoClear = originalAutoClear; + scene.background = background; - this.type = 'IcosahedronGeometry'; + } - this.parameters = { - radius: radius, - detail: detail - }; + _textureToCubeUV( texture, cubeUVRenderTarget ) { - } + const renderer = this._renderer; - static fromJSON( data ) { + const isCubeTexture = ( texture.mapping === CubeReflectionMapping || texture.mapping === CubeRefractionMapping ); - return new IcosahedronGeometry( data.radius, data.detail ); + if ( isCubeTexture ) { - } + if ( this._cubemapMaterial === null ) { -} + this._cubemapMaterial = _getCubemapMaterial(); -class OctahedronGeometry extends PolyhedronGeometry { + } - constructor( radius = 1, detail = 0 ) { + this._cubemapMaterial.uniforms.flipEnvMap.value = ( texture.isRenderTargetTexture === false ) ? -1 : 1; - const vertices = [ - 1, 0, 0, - 1, 0, 0, 0, 1, 0, - 0, - 1, 0, 0, 0, 1, 0, 0, - 1 - ]; + } else { - const indices = [ - 0, 2, 4, 0, 4, 3, 0, 3, 5, - 0, 5, 2, 1, 2, 5, 1, 5, 3, - 1, 3, 4, 1, 4, 2 - ]; + if ( this._equirectMaterial === null ) { - super( vertices, indices, radius, detail ); + this._equirectMaterial = _getEquirectMaterial(); - this.type = 'OctahedronGeometry'; + } - this.parameters = { - radius: radius, - detail: detail - }; + } - } + const material = isCubeTexture ? this._cubemapMaterial : this._equirectMaterial; + const mesh = new Mesh( this._lodPlanes[ 0 ], material ); - static fromJSON( data ) { + const uniforms = material.uniforms; - return new OctahedronGeometry( data.radius, data.detail ); + uniforms[ 'envMap' ].value = texture; - } + const size = this._cubeSize; -} + _setViewport( cubeUVRenderTarget, 0, 0, 3 * size, 2 * size ); -class RingGeometry extends BufferGeometry { + renderer.setRenderTarget( cubeUVRenderTarget ); + renderer.render( mesh, _flatCamera ); - constructor( innerRadius = 0.5, outerRadius = 1, thetaSegments = 32, phiSegments = 1, thetaStart = 0, thetaLength = Math.PI * 2 ) { + } - super(); + _applyPMREM( cubeUVRenderTarget ) { - this.type = 'RingGeometry'; + const renderer = this._renderer; + const autoClear = renderer.autoClear; + renderer.autoClear = false; + const n = this._lodPlanes.length; - this.parameters = { - innerRadius: innerRadius, - outerRadius: outerRadius, - thetaSegments: thetaSegments, - phiSegments: phiSegments, - thetaStart: thetaStart, - thetaLength: thetaLength - }; + for ( let i = 1; i < n; i ++ ) { - thetaSegments = Math.max( 3, thetaSegments ); - phiSegments = Math.max( 1, phiSegments ); + const sigma = Math.sqrt( this._sigmas[ i ] * this._sigmas[ i ] - this._sigmas[ i - 1 ] * this._sigmas[ i - 1 ] ); - // buffers + const poleAxis = _axisDirections[ ( n - i - 1 ) % _axisDirections.length ]; - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; + this._blur( cubeUVRenderTarget, i - 1, i, sigma, poleAxis ); - // some helper variables + } - let radius = innerRadius; - const radiusStep = ( ( outerRadius - innerRadius ) / phiSegments ); - const vertex = new Vector3(); - const uv = new Vector2(); + renderer.autoClear = autoClear; - // generate vertices, normals and uvs + } - for ( let j = 0; j <= phiSegments; j ++ ) { + /** + * This is a two-pass Gaussian blur for a cubemap. Normally this is done + * vertically and horizontally, but this breaks down on a cube. Here we apply + * the blur latitudinally (around the poles), and then longitudinally (towards + * the poles) to approximate the orthogonally-separable blur. It is least + * accurate at the poles, but still does a decent job. + * + * @private + * @param {WebGLRenderTarget} cubeUVRenderTarget + * @param {number} lodIn + * @param {number} lodOut + * @param {number} sigma + * @param {Vector3} [poleAxis] + */ + _blur( cubeUVRenderTarget, lodIn, lodOut, sigma, poleAxis ) { - for ( let i = 0; i <= thetaSegments; i ++ ) { + const pingPongRenderTarget = this._pingPongRenderTarget; - // values are generate from the inside of the ring to the outside + this._halfBlur( + cubeUVRenderTarget, + pingPongRenderTarget, + lodIn, + lodOut, + sigma, + 'latitudinal', + poleAxis ); - const segment = thetaStart + i / thetaSegments * thetaLength; + this._halfBlur( + pingPongRenderTarget, + cubeUVRenderTarget, + lodOut, + lodOut, + sigma, + 'longitudinal', + poleAxis ); - // vertex + } - vertex.x = radius * Math.cos( segment ); - vertex.y = radius * Math.sin( segment ); + _halfBlur( targetIn, targetOut, lodIn, lodOut, sigmaRadians, direction, poleAxis ) { - vertices.push( vertex.x, vertex.y, vertex.z ); + const renderer = this._renderer; + const blurMaterial = this._blurMaterial; - // normal + if ( direction !== 'latitudinal' && direction !== 'longitudinal' ) { - normals.push( 0, 0, 1 ); + console.error( + 'blur direction must be either latitudinal or longitudinal!' ); - // uv + } - uv.x = ( vertex.x / outerRadius + 1 ) / 2; - uv.y = ( vertex.y / outerRadius + 1 ) / 2; + // Number of standard deviations at which to cut off the discrete approximation. + const STANDARD_DEVIATIONS = 3; - uvs.push( uv.x, uv.y ); + const blurMesh = new Mesh( this._lodPlanes[ lodOut ], blurMaterial ); + const blurUniforms = blurMaterial.uniforms; - } + const pixels = this._sizeLods[ lodIn ] - 1; + const radiansPerPixel = isFinite( sigmaRadians ) ? Math.PI / ( 2 * pixels ) : 2 * Math.PI / ( 2 * MAX_SAMPLES - 1 ); + const sigmaPixels = sigmaRadians / radiansPerPixel; + const samples = isFinite( sigmaRadians ) ? 1 + Math.floor( STANDARD_DEVIATIONS * sigmaPixels ) : MAX_SAMPLES; - // increase the radius for next row of vertices + if ( samples > MAX_SAMPLES ) { - radius += radiusStep; + console.warn( `sigmaRadians, ${ + sigmaRadians}, is too large and will clip, as it requested ${ + samples} samples when the maximum is set to ${MAX_SAMPLES}` ); } - // indices - - for ( let j = 0; j < phiSegments; j ++ ) { + const weights = []; + let sum = 0; - const thetaSegmentLevel = j * ( thetaSegments + 1 ); + for ( let i = 0; i < MAX_SAMPLES; ++ i ) { - for ( let i = 0; i < thetaSegments; i ++ ) { + const x = i / sigmaPixels; + const weight = Math.exp( - x * x / 2 ); + weights.push( weight ); - const segment = i + thetaSegmentLevel; + if ( i === 0 ) { - const a = segment; - const b = segment + thetaSegments + 1; - const c = segment + thetaSegments + 2; - const d = segment + 1; + sum += weight; - // faces + } else if ( i < samples ) { - indices.push( a, b, d ); - indices.push( b, c, d ); + sum += 2 * weight; } } - // build geometry + for ( let i = 0; i < weights.length; i ++ ) { - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + weights[ i ] = weights[ i ] / sum; - } + } - copy( source ) { + blurUniforms[ 'envMap' ].value = targetIn.texture; + blurUniforms[ 'samples' ].value = samples; + blurUniforms[ 'weights' ].value = weights; + blurUniforms[ 'latitudinal' ].value = direction === 'latitudinal'; - super.copy( source ); + if ( poleAxis ) { - this.parameters = Object.assign( {}, source.parameters ); + blurUniforms[ 'poleAxis' ].value = poleAxis; - return this; + } - } + const { _lodMax } = this; + blurUniforms[ 'dTheta' ].value = radiansPerPixel; + blurUniforms[ 'mipInt' ].value = _lodMax - lodIn; - static fromJSON( data ) { + const outputSize = this._sizeLods[ lodOut ]; + const x = 3 * outputSize * ( lodOut > _lodMax - LOD_MIN ? lodOut - _lodMax + LOD_MIN : 0 ); + const y = 4 * ( this._cubeSize - outputSize ); - return new RingGeometry( data.innerRadius, data.outerRadius, data.thetaSegments, data.phiSegments, data.thetaStart, data.thetaLength ); + _setViewport( targetOut, x, y, 3 * outputSize, 2 * outputSize ); + renderer.setRenderTarget( targetOut ); + renderer.render( blurMesh, _flatCamera ); } } -class ShapeGeometry extends BufferGeometry { - constructor( shapes = new Shape( [ new Vector2( 0, 0.5 ), new Vector2( - 0.5, - 0.5 ), new Vector2( 0.5, - 0.5 ) ] ), curveSegments = 12 ) { - super(); +function _createPlanes( lodMax ) { - this.type = 'ShapeGeometry'; + const lodPlanes = []; + const sizeLods = []; + const sigmas = []; - this.parameters = { - shapes: shapes, - curveSegments: curveSegments - }; + let lod = lodMax; - // buffers + const totalLods = lodMax - LOD_MIN + 1 + EXTRA_LOD_SIGMA.length; - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; + for ( let i = 0; i < totalLods; i ++ ) { - // helper variables + const sizeLod = Math.pow( 2, lod ); + sizeLods.push( sizeLod ); + let sigma = 1.0 / sizeLod; - let groupStart = 0; - let groupCount = 0; + if ( i > lodMax - LOD_MIN ) { - // allow single and array values for "shapes" parameter + sigma = EXTRA_LOD_SIGMA[ i - lodMax + LOD_MIN - 1 ]; - if ( Array.isArray( shapes ) === false ) { + } else if ( i === 0 ) { - addShape( shapes ); + sigma = 0; - } else { + } - for ( let i = 0; i < shapes.length; i ++ ) { + sigmas.push( sigma ); - addShape( shapes[ i ] ); + const texelSize = 1.0 / ( sizeLod - 2 ); + const min = - texelSize; + const max = 1 + texelSize; + const uv1 = [ min, min, max, min, max, max, min, min, max, max, min, max ]; - this.addGroup( groupStart, groupCount, i ); // enables MultiMaterial support + const cubeFaces = 6; + const vertices = 6; + const positionSize = 3; + const uvSize = 2; + const faceIndexSize = 1; - groupStart += groupCount; - groupCount = 0; + const position = new Float32Array( positionSize * vertices * cubeFaces ); + const uv = new Float32Array( uvSize * vertices * cubeFaces ); + const faceIndex = new Float32Array( faceIndexSize * vertices * cubeFaces ); - } + for ( let face = 0; face < cubeFaces; face ++ ) { + + const x = ( face % 3 ) * 2 / 3 - 1; + const y = face > 2 ? 0 : -1; + const coordinates = [ + x, y, 0, + x + 2 / 3, y, 0, + x + 2 / 3, y + 1, 0, + x, y, 0, + x + 2 / 3, y + 1, 0, + x, y + 1, 0 + ]; + position.set( coordinates, positionSize * vertices * face ); + uv.set( uv1, uvSize * vertices * face ); + const fill = [ face, face, face, face, face, face ]; + faceIndex.set( fill, faceIndexSize * vertices * face ); } - // build geometry + const planes = new BufferGeometry(); + planes.setAttribute( 'position', new BufferAttribute( position, positionSize ) ); + planes.setAttribute( 'uv', new BufferAttribute( uv, uvSize ) ); + planes.setAttribute( 'faceIndex', new BufferAttribute( faceIndex, faceIndexSize ) ); + lodPlanes.push( planes ); - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + if ( lod > LOD_MIN ) { + lod --; - // helper functions + } - function addShape( shape ) { + } - const indexOffset = vertices.length / 3; - const points = shape.extractPoints( curveSegments ); + return { lodPlanes, sizeLods, sigmas }; - let shapeVertices = points.shape; - const shapeHoles = points.holes; +} - // check direction of vertices +function _createRenderTarget( width, height, params ) { - if ( ShapeUtils.isClockWise( shapeVertices ) === false ) { + const cubeUVRenderTarget = new WebGLRenderTarget( width, height, params ); + cubeUVRenderTarget.texture.mapping = CubeUVReflectionMapping; + cubeUVRenderTarget.texture.name = 'PMREM.cubeUv'; + cubeUVRenderTarget.scissorTest = true; + return cubeUVRenderTarget; - shapeVertices = shapeVertices.reverse(); +} - } +function _setViewport( target, x, y, width, height ) { - for ( let i = 0, l = shapeHoles.length; i < l; i ++ ) { + target.viewport.set( x, y, width, height ); + target.scissor.set( x, y, width, height ); - const shapeHole = shapeHoles[ i ]; +} - if ( ShapeUtils.isClockWise( shapeHole ) === true ) { +function _getBlurShader( lodMax, width, height ) { - shapeHoles[ i ] = shapeHole.reverse(); + const weights = new Float32Array( MAX_SAMPLES ); + const poleAxis = new Vector3( 0, 1, 0 ); + const shaderMaterial = new ShaderMaterial( { - } + name: 'SphericalGaussianBlur', - } + defines: { + 'n': MAX_SAMPLES, + 'CUBEUV_TEXEL_WIDTH': 1.0 / width, + 'CUBEUV_TEXEL_HEIGHT': 1.0 / height, + 'CUBEUV_MAX_MIP': `${lodMax}.0`, + }, - const faces = ShapeUtils.triangulateShape( shapeVertices, shapeHoles ); + uniforms: { + 'envMap': { value: null }, + 'samples': { value: 1 }, + 'weights': { value: weights }, + 'latitudinal': { value: false }, + 'dTheta': { value: 0 }, + 'mipInt': { value: 0 }, + 'poleAxis': { value: poleAxis } + }, - // join vertices of inner and outer paths to a single array + vertexShader: _getCommonVertexShader(), - for ( let i = 0, l = shapeHoles.length; i < l; i ++ ) { + fragmentShader: /* glsl */` - const shapeHole = shapeHoles[ i ]; - shapeVertices = shapeVertices.concat( shapeHole ); + precision mediump float; + precision mediump int; - } + varying vec3 vOutputDirection; - // vertices, normals, uvs + uniform sampler2D envMap; + uniform int samples; + uniform float weights[ n ]; + uniform bool latitudinal; + uniform float dTheta; + uniform float mipInt; + uniform vec3 poleAxis; - for ( let i = 0, l = shapeVertices.length; i < l; i ++ ) { + #define ENVMAP_TYPE_CUBE_UV + #include - const vertex = shapeVertices[ i ]; + vec3 getSample( float theta, vec3 axis ) { - vertices.push( vertex.x, vertex.y, 0 ); - normals.push( 0, 0, 1 ); - uvs.push( vertex.x, vertex.y ); // world uvs + float cosTheta = cos( theta ); + // Rodrigues' axis-angle rotation + vec3 sampleDirection = vOutputDirection * cosTheta + + cross( axis, vOutputDirection ) * sin( theta ) + + axis * dot( axis, vOutputDirection ) * ( 1.0 - cosTheta ); + + return bilinearCubeUV( envMap, sampleDirection, mipInt ); } - // indices + void main() { - for ( let i = 0, l = faces.length; i < l; i ++ ) { + vec3 axis = latitudinal ? poleAxis : cross( poleAxis, vOutputDirection ); - const face = faces[ i ]; + if ( all( equal( axis, vec3( 0.0 ) ) ) ) { - const a = face[ 0 ] + indexOffset; - const b = face[ 1 ] + indexOffset; - const c = face[ 2 ] + indexOffset; + axis = vec3( vOutputDirection.z, 0.0, - vOutputDirection.x ); - indices.push( a, b, c ); - groupCount += 3; + } - } + axis = normalize( axis ); - } + gl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 ); + gl_FragColor.rgb += weights[ 0 ] * getSample( 0.0, axis ); - } + for ( int i = 1; i < n; i++ ) { - copy( source ) { + if ( i >= samples ) { - super.copy( source ); + break; - this.parameters = Object.assign( {}, source.parameters ); + } - return this; + float theta = dTheta * float( i ); + gl_FragColor.rgb += weights[ i ] * getSample( -1.0 * theta, axis ); + gl_FragColor.rgb += weights[ i ] * getSample( theta, axis ); - } + } - toJSON() { + } + `, - const data = super.toJSON(); + blending: NoBlending, + depthTest: false, + depthWrite: false - const shapes = this.parameters.shapes; + } ); - return toJSON( shapes, data ); + return shaderMaterial; - } +} - static fromJSON( data, shapes ) { +function _getEquirectMaterial() { - const geometryShapes = []; + return new ShaderMaterial( { - for ( let j = 0, jl = data.shapes.length; j < jl; j ++ ) { + name: 'EquirectangularToCubeUV', - const shape = shapes[ data.shapes[ j ] ]; + uniforms: { + 'envMap': { value: null } + }, - geometryShapes.push( shape ); + vertexShader: _getCommonVertexShader(), - } + fragmentShader: /* glsl */` - return new ShapeGeometry( geometryShapes, data.curveSegments ); + precision mediump float; + precision mediump int; - } + varying vec3 vOutputDirection; -} + uniform sampler2D envMap; -function toJSON( shapes, data ) { + #include - data.shapes = []; + void main() { - if ( Array.isArray( shapes ) ) { + vec3 outputDirection = normalize( vOutputDirection ); + vec2 uv = equirectUv( outputDirection ); - for ( let i = 0, l = shapes.length; i < l; i ++ ) { + gl_FragColor = vec4( texture2D ( envMap, uv ).rgb, 1.0 ); - const shape = shapes[ i ]; + } + `, - data.shapes.push( shape.uuid ); + blending: NoBlending, + depthTest: false, + depthWrite: false - } + } ); - } else { +} - data.shapes.push( shapes.uuid ); +function _getCubemapMaterial() { - } + return new ShaderMaterial( { - return data; + name: 'CubemapToCubeUV', -} + uniforms: { + 'envMap': { value: null }, + 'flipEnvMap': { value: -1 } + }, -class SphereGeometry extends BufferGeometry { + vertexShader: _getCommonVertexShader(), - constructor( radius = 1, widthSegments = 32, heightSegments = 16, phiStart = 0, phiLength = Math.PI * 2, thetaStart = 0, thetaLength = Math.PI ) { + fragmentShader: /* glsl */` - super(); + precision mediump float; + precision mediump int; - this.type = 'SphereGeometry'; + uniform float flipEnvMap; - this.parameters = { - radius: radius, - widthSegments: widthSegments, - heightSegments: heightSegments, - phiStart: phiStart, - phiLength: phiLength, - thetaStart: thetaStart, - thetaLength: thetaLength - }; + varying vec3 vOutputDirection; - widthSegments = Math.max( 3, Math.floor( widthSegments ) ); - heightSegments = Math.max( 2, Math.floor( heightSegments ) ); + uniform samplerCube envMap; - const thetaEnd = Math.min( thetaStart + thetaLength, Math.PI ); + void main() { - let index = 0; - const grid = []; + gl_FragColor = textureCube( envMap, vec3( flipEnvMap * vOutputDirection.x, vOutputDirection.yz ) ); - const vertex = new Vector3(); - const normal = new Vector3(); + } + `, - // buffers + blending: NoBlending, + depthTest: false, + depthWrite: false - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; + } ); - // generate vertices, normals and uvs +} - for ( let iy = 0; iy <= heightSegments; iy ++ ) { +function _getCommonVertexShader() { - const verticesRow = []; + return /* glsl */` - const v = iy / heightSegments; + precision mediump float; + precision mediump int; - // special case for the poles + attribute float faceIndex; - let uOffset = 0; + varying vec3 vOutputDirection; - if ( iy === 0 && thetaStart === 0 ) { + // RH coordinate system; PMREM face-indexing convention + vec3 getDirection( vec2 uv, float face ) { - uOffset = 0.5 / widthSegments; + uv = 2.0 * uv - 1.0; - } else if ( iy === heightSegments && thetaEnd === Math.PI ) { + vec3 direction = vec3( uv, 1.0 ); - uOffset = - 0.5 / widthSegments; + if ( face == 0.0 ) { - } + direction = direction.zyx; // ( 1, v, u ) pos x - for ( let ix = 0; ix <= widthSegments; ix ++ ) { + } else if ( face == 1.0 ) { - const u = ix / widthSegments; + direction = direction.xzy; + direction.xz *= -1.0; // ( -u, 1, -v ) pos y - // vertex + } else if ( face == 2.0 ) { - vertex.x = - radius * Math.cos( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength ); - vertex.y = radius * Math.cos( thetaStart + v * thetaLength ); - vertex.z = radius * Math.sin( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength ); + direction.x *= -1.0; // ( -u, v, 1 ) pos z - vertices.push( vertex.x, vertex.y, vertex.z ); + } else if ( face == 3.0 ) { - // normal + direction = direction.zyx; + direction.xz *= -1.0; // ( -1, v, -u ) neg x - normal.copy( vertex ).normalize(); - normals.push( normal.x, normal.y, normal.z ); + } else if ( face == 4.0 ) { - // uv + direction = direction.xzy; + direction.xy *= -1.0; // ( -u, -1, v ) neg y - uvs.push( u + uOffset, 1 - v ); + } else if ( face == 5.0 ) { - verticesRow.push( index ++ ); + direction.z *= -1.0; // ( u, v, -1 ) neg z } - grid.push( verticesRow ); + return direction; } - // indices + void main() { - for ( let iy = 0; iy < heightSegments; iy ++ ) { + vOutputDirection = getDirection( uv, faceIndex ); + gl_Position = vec4( position, 1.0 ); - for ( let ix = 0; ix < widthSegments; ix ++ ) { + } + `; - const a = grid[ iy ][ ix + 1 ]; - const b = grid[ iy ][ ix ]; - const c = grid[ iy + 1 ][ ix ]; - const d = grid[ iy + 1 ][ ix + 1 ]; +} - if ( iy !== 0 || thetaStart > 0 ) indices.push( a, b, d ); - if ( iy !== heightSegments - 1 || thetaEnd < Math.PI ) indices.push( b, c, d ); +function WebGLCubeUVMaps( renderer ) { - } + let cubeUVmaps = new WeakMap(); - } + let pmremGenerator = null; - // build geometry + function get( texture ) { - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + if ( texture && texture.isTexture ) { - } + const mapping = texture.mapping; - copy( source ) { + const isEquirectMap = ( mapping === EquirectangularReflectionMapping || mapping === EquirectangularRefractionMapping ); + const isCubeMap = ( mapping === CubeReflectionMapping || mapping === CubeRefractionMapping ); - super.copy( source ); + // equirect/cube map to cubeUV conversion - this.parameters = Object.assign( {}, source.parameters ); + if ( isEquirectMap || isCubeMap ) { - return this; + let renderTarget = cubeUVmaps.get( texture ); - } + const currentPMREMVersion = renderTarget !== undefined ? renderTarget.texture.pmremVersion : 0; - static fromJSON( data ) { + if ( texture.isRenderTargetTexture && texture.pmremVersion !== currentPMREMVersion ) { - return new SphereGeometry( data.radius, data.widthSegments, data.heightSegments, data.phiStart, data.phiLength, data.thetaStart, data.thetaLength ); + if ( pmremGenerator === null ) pmremGenerator = new PMREMGenerator( renderer ); - } + renderTarget = isEquirectMap ? pmremGenerator.fromEquirectangular( texture, renderTarget ) : pmremGenerator.fromCubemap( texture, renderTarget ); + renderTarget.texture.pmremVersion = texture.pmremVersion; -} + cubeUVmaps.set( texture, renderTarget ); -class TetrahedronGeometry extends PolyhedronGeometry { + return renderTarget.texture; - constructor( radius = 1, detail = 0 ) { + } else { - const vertices = [ - 1, 1, 1, - 1, - 1, 1, - 1, 1, - 1, 1, - 1, - 1 - ]; + if ( renderTarget !== undefined ) { - const indices = [ - 2, 1, 0, 0, 3, 2, 1, 3, 0, 2, 3, 1 - ]; + return renderTarget.texture; - super( vertices, indices, radius, detail ); + } else { - this.type = 'TetrahedronGeometry'; + const image = texture.image; - this.parameters = { - radius: radius, - detail: detail - }; + if ( ( isEquirectMap && image && image.height > 0 ) || ( isCubeMap && image && isCubeTextureComplete( image ) ) ) { - } + if ( pmremGenerator === null ) pmremGenerator = new PMREMGenerator( renderer ); - static fromJSON( data ) { + renderTarget = isEquirectMap ? pmremGenerator.fromEquirectangular( texture ) : pmremGenerator.fromCubemap( texture ); + renderTarget.texture.pmremVersion = texture.pmremVersion; - return new TetrahedronGeometry( data.radius, data.detail ); + cubeUVmaps.set( texture, renderTarget ); - } + texture.addEventListener( 'dispose', onTextureDispose ); -} + return renderTarget.texture; -class TorusGeometry extends BufferGeometry { + } else { - constructor( radius = 1, tube = 0.4, radialSegments = 12, tubularSegments = 48, arc = Math.PI * 2 ) { + // image not yet ready. try the conversion next frame - super(); + return null; - this.type = 'TorusGeometry'; + } - this.parameters = { - radius: radius, - tube: tube, - radialSegments: radialSegments, - tubularSegments: tubularSegments, - arc: arc - }; + } - radialSegments = Math.floor( radialSegments ); - tubularSegments = Math.floor( tubularSegments ); + } - // buffers + } - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; + } - // helper variables + return texture; - const center = new Vector3(); - const vertex = new Vector3(); - const normal = new Vector3(); + } - // generate vertices, normals and uvs + function isCubeTextureComplete( image ) { - for ( let j = 0; j <= radialSegments; j ++ ) { + let count = 0; + const length = 6; - for ( let i = 0; i <= tubularSegments; i ++ ) { + for ( let i = 0; i < length; i ++ ) { - const u = i / tubularSegments * arc; - const v = j / radialSegments * Math.PI * 2; + if ( image[ i ] !== undefined ) count ++; - // vertex + } - vertex.x = ( radius + tube * Math.cos( v ) ) * Math.cos( u ); - vertex.y = ( radius + tube * Math.cos( v ) ) * Math.sin( u ); - vertex.z = tube * Math.sin( v ); + return count === length; - vertices.push( vertex.x, vertex.y, vertex.z ); - // normal + } - center.x = radius * Math.cos( u ); - center.y = radius * Math.sin( u ); - normal.subVectors( vertex, center ).normalize(); + function onTextureDispose( event ) { + + const texture = event.target; + + texture.removeEventListener( 'dispose', onTextureDispose ); + + const cubemapUV = cubeUVmaps.get( texture ); + + if ( cubemapUV !== undefined ) { + + cubeUVmaps.delete( texture ); + cubemapUV.dispose(); + + } - normals.push( normal.x, normal.y, normal.z ); + } - // uv + function dispose() { - uvs.push( i / tubularSegments ); - uvs.push( j / radialSegments ); + cubeUVmaps = new WeakMap(); - } + if ( pmremGenerator !== null ) { + + pmremGenerator.dispose(); + pmremGenerator = null; } - // generate indices + } - for ( let j = 1; j <= radialSegments; j ++ ) { + return { + get: get, + dispose: dispose + }; - for ( let i = 1; i <= tubularSegments; i ++ ) { +} - // indices +function WebGLExtensions( gl ) { - const a = ( tubularSegments + 1 ) * j + i - 1; - const b = ( tubularSegments + 1 ) * ( j - 1 ) + i - 1; - const c = ( tubularSegments + 1 ) * ( j - 1 ) + i; - const d = ( tubularSegments + 1 ) * j + i; + const extensions = {}; - // faces + function getExtension( name ) { - indices.push( a, b, d ); - indices.push( b, c, d ); + if ( extensions[ name ] !== undefined ) { - } + return extensions[ name ]; } - // build geometry + let extension; - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + switch ( name ) { - } + case 'WEBGL_depth_texture': + extension = gl.getExtension( 'WEBGL_depth_texture' ) || gl.getExtension( 'MOZ_WEBGL_depth_texture' ) || gl.getExtension( 'WEBKIT_WEBGL_depth_texture' ); + break; - copy( source ) { + case 'EXT_texture_filter_anisotropic': + extension = gl.getExtension( 'EXT_texture_filter_anisotropic' ) || gl.getExtension( 'MOZ_EXT_texture_filter_anisotropic' ) || gl.getExtension( 'WEBKIT_EXT_texture_filter_anisotropic' ); + break; - super.copy( source ); + case 'WEBGL_compressed_texture_s3tc': + extension = gl.getExtension( 'WEBGL_compressed_texture_s3tc' ) || gl.getExtension( 'MOZ_WEBGL_compressed_texture_s3tc' ) || gl.getExtension( 'WEBKIT_WEBGL_compressed_texture_s3tc' ); + break; - this.parameters = Object.assign( {}, source.parameters ); + case 'WEBGL_compressed_texture_pvrtc': + extension = gl.getExtension( 'WEBGL_compressed_texture_pvrtc' ) || gl.getExtension( 'WEBKIT_WEBGL_compressed_texture_pvrtc' ); + break; - return this; + default: + extension = gl.getExtension( name ); - } + } - static fromJSON( data ) { + extensions[ name ] = extension; - return new TorusGeometry( data.radius, data.tube, data.radialSegments, data.tubularSegments, data.arc ); + return extension; } -} + return { -class TorusKnotGeometry extends BufferGeometry { + has: function ( name ) { - constructor( radius = 1, tube = 0.4, tubularSegments = 64, radialSegments = 8, p = 2, q = 3 ) { + return getExtension( name ) !== null; - super(); + }, - this.type = 'TorusKnotGeometry'; + init: function () { - this.parameters = { - radius: radius, - tube: tube, - tubularSegments: tubularSegments, - radialSegments: radialSegments, - p: p, - q: q - }; + getExtension( 'EXT_color_buffer_float' ); + getExtension( 'WEBGL_clip_cull_distance' ); + getExtension( 'OES_texture_float_linear' ); + getExtension( 'EXT_color_buffer_half_float' ); + getExtension( 'WEBGL_multisampled_render_to_texture' ); + getExtension( 'WEBGL_render_shared_exponent' ); - tubularSegments = Math.floor( tubularSegments ); - radialSegments = Math.floor( radialSegments ); + }, - // buffers + get: function ( name ) { - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; + const extension = getExtension( name ); - // helper variables + if ( extension === null ) { - const vertex = new Vector3(); - const normal = new Vector3(); + warnOnce( 'THREE.WebGLRenderer: ' + name + ' extension not supported.' ); - const P1 = new Vector3(); - const P2 = new Vector3(); + } - const B = new Vector3(); - const T = new Vector3(); - const N = new Vector3(); + return extension; - // generate vertices, normals and uvs + } - for ( let i = 0; i <= tubularSegments; ++ i ) { + }; - // the radian "u" is used to calculate the position on the torus curve of the current tubular segment +} - const u = i / tubularSegments * p * Math.PI * 2; +function WebGLGeometries( gl, attributes, info, bindingStates ) { - // now we calculate two points. P1 is our current position on the curve, P2 is a little farther ahead. - // these points are used to create a special "coordinate space", which is necessary to calculate the correct vertex positions + const geometries = {}; + const wireframeAttributes = new WeakMap(); - calculatePositionOnCurve( u, p, q, radius, P1 ); - calculatePositionOnCurve( u + 0.01, p, q, radius, P2 ); + function onGeometryDispose( event ) { - // calculate orthonormal basis + const geometry = event.target; - T.subVectors( P2, P1 ); - N.addVectors( P2, P1 ); - B.crossVectors( T, N ); - N.crossVectors( B, T ); + if ( geometry.index !== null ) { - // normalize B, N. T can be ignored, we don't use it + attributes.remove( geometry.index ); - B.normalize(); - N.normalize(); + } - for ( let j = 0; j <= radialSegments; ++ j ) { + for ( const name in geometry.attributes ) { - // now calculate the vertices. they are nothing more than an extrusion of the torus curve. - // because we extrude a shape in the xy-plane, there is no need to calculate a z-value. + attributes.remove( geometry.attributes[ name ] ); - const v = j / radialSegments * Math.PI * 2; - const cx = - tube * Math.cos( v ); - const cy = tube * Math.sin( v ); + } - // now calculate the final vertex position. - // first we orient the extrusion with our basis vectors, then we add it to the current position on the curve + geometry.removeEventListener( 'dispose', onGeometryDispose ); - vertex.x = P1.x + ( cx * N.x + cy * B.x ); - vertex.y = P1.y + ( cx * N.y + cy * B.y ); - vertex.z = P1.z + ( cx * N.z + cy * B.z ); + delete geometries[ geometry.id ]; - vertices.push( vertex.x, vertex.y, vertex.z ); + const attribute = wireframeAttributes.get( geometry ); - // normal (P1 is always the center/origin of the extrusion, thus we can use it to calculate the normal) + if ( attribute ) { - normal.subVectors( vertex, P1 ).normalize(); + attributes.remove( attribute ); + wireframeAttributes.delete( geometry ); - normals.push( normal.x, normal.y, normal.z ); + } - // uv + bindingStates.releaseStatesOfGeometry( geometry ); - uvs.push( i / tubularSegments ); - uvs.push( j / radialSegments ); + if ( geometry.isInstancedBufferGeometry === true ) { - } + delete geometry._maxInstanceCount; } - // generate indices + // - for ( let j = 1; j <= tubularSegments; j ++ ) { + info.memory.geometries --; - for ( let i = 1; i <= radialSegments; i ++ ) { + } - // indices + function get( object, geometry ) { - const a = ( radialSegments + 1 ) * ( j - 1 ) + ( i - 1 ); - const b = ( radialSegments + 1 ) * j + ( i - 1 ); - const c = ( radialSegments + 1 ) * j + i; - const d = ( radialSegments + 1 ) * ( j - 1 ) + i; + if ( geometries[ geometry.id ] === true ) return geometry; - // faces + geometry.addEventListener( 'dispose', onGeometryDispose ); - indices.push( a, b, d ); - indices.push( b, c, d ); + geometries[ geometry.id ] = true; - } + info.memory.geometries ++; - } + return geometry; - // build geometry + } - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + function update( geometry ) { - // this function calculates the current position on the torus curve + const geometryAttributes = geometry.attributes; - function calculatePositionOnCurve( u, p, q, radius, position ) { + // Updating index buffer in VAO now. See WebGLBindingStates. - const cu = Math.cos( u ); - const su = Math.sin( u ); - const quOverP = q / p * u; - const cs = Math.cos( quOverP ); + for ( const name in geometryAttributes ) { - position.x = radius * ( 2 + cs ) * 0.5 * cu; - position.y = radius * ( 2 + cs ) * su * 0.5; - position.z = radius * Math.sin( quOverP ) * 0.5; + attributes.update( geometryAttributes[ name ], gl.ARRAY_BUFFER ); } } - copy( source ) { - - super.copy( source ); + function updateWireframeAttribute( geometry ) { - this.parameters = Object.assign( {}, source.parameters ); + const indices = []; - return this; + const geometryIndex = geometry.index; + const geometryPosition = geometry.attributes.position; + let version = 0; - } + if ( geometryIndex !== null ) { - static fromJSON( data ) { + const array = geometryIndex.array; + version = geometryIndex.version; - return new TorusKnotGeometry( data.radius, data.tube, data.tubularSegments, data.radialSegments, data.p, data.q ); + for ( let i = 0, l = array.length; i < l; i += 3 ) { - } + const a = array[ i + 0 ]; + const b = array[ i + 1 ]; + const c = array[ i + 2 ]; -} + indices.push( a, b, b, c, c, a ); -class TubeGeometry extends BufferGeometry { + } - constructor( path = new QuadraticBezierCurve3( new Vector3( - 1, - 1, 0 ), new Vector3( - 1, 1, 0 ), new Vector3( 1, 1, 0 ) ), tubularSegments = 64, radius = 1, radialSegments = 8, closed = false ) { + } else if ( geometryPosition !== undefined ) { - super(); + const array = geometryPosition.array; + version = geometryPosition.version; - this.type = 'TubeGeometry'; + for ( let i = 0, l = ( array.length / 3 ) - 1; i < l; i += 3 ) { - this.parameters = { - path: path, - tubularSegments: tubularSegments, - radius: radius, - radialSegments: radialSegments, - closed: closed - }; + const a = i + 0; + const b = i + 1; + const c = i + 2; - const frames = path.computeFrenetFrames( tubularSegments, closed ); + indices.push( a, b, b, c, c, a ); - // expose internals + } - this.tangents = frames.tangents; - this.normals = frames.normals; - this.binormals = frames.binormals; + } else { - // helper variables + return; - const vertex = new Vector3(); - const normal = new Vector3(); - const uv = new Vector2(); - let P = new Vector3(); + } - // buffer + const attribute = new ( arrayNeedsUint32( indices ) ? Uint32BufferAttribute : Uint16BufferAttribute )( indices, 1 ); + attribute.version = version; - const vertices = []; - const normals = []; - const uvs = []; - const indices = []; + // Updating index buffer in VAO now. See WebGLBindingStates - // create buffer data + // - generateBufferData(); + const previousAttribute = wireframeAttributes.get( geometry ); - // build geometry + if ( previousAttribute ) attributes.remove( previousAttribute ); - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + // - // functions + wireframeAttributes.set( geometry, attribute ); - function generateBufferData() { + } - for ( let i = 0; i < tubularSegments; i ++ ) { + function getWireframeAttribute( geometry ) { - generateSegment( i ); + const currentAttribute = wireframeAttributes.get( geometry ); - } + if ( currentAttribute ) { - // if the geometry is not closed, generate the last row of vertices and normals - // at the regular position on the given path - // - // if the geometry is closed, duplicate the first row of vertices and normals (uvs will differ) + const geometryIndex = geometry.index; - generateSegment( ( closed === false ) ? tubularSegments : 0 ); + if ( geometryIndex !== null ) { - // uvs are generated in a separate function. - // this makes it easy compute correct values for closed geometries + // if the attribute is obsolete, create a new one - generateUVs(); + if ( currentAttribute.version < geometryIndex.version ) { - // finally create faces + updateWireframeAttribute( geometry ); - generateIndices(); + } - } + } - function generateSegment( i ) { + } else { - // we use getPointAt to sample evenly distributed points from the given path + updateWireframeAttribute( geometry ); - P = path.getPointAt( i / tubularSegments, P ); + } - // retrieve corresponding normal and binormal + return wireframeAttributes.get( geometry ); - const N = frames.normals[ i ]; - const B = frames.binormals[ i ]; + } - // generate normals and vertices for the current segment + return { - for ( let j = 0; j <= radialSegments; j ++ ) { + get: get, + update: update, - const v = j / radialSegments * Math.PI * 2; + getWireframeAttribute: getWireframeAttribute - const sin = Math.sin( v ); - const cos = - Math.cos( v ); + }; - // normal +} - normal.x = ( cos * N.x + sin * B.x ); - normal.y = ( cos * N.y + sin * B.y ); - normal.z = ( cos * N.z + sin * B.z ); - normal.normalize(); +function WebGLIndexedBufferRenderer( gl, extensions, info ) { - normals.push( normal.x, normal.y, normal.z ); + let mode; - // vertex + function setMode( value ) { - vertex.x = P.x + radius * normal.x; - vertex.y = P.y + radius * normal.y; - vertex.z = P.z + radius * normal.z; + mode = value; - vertices.push( vertex.x, vertex.y, vertex.z ); + } - } + let type, bytesPerElement; - } + function setIndex( value ) { - function generateIndices() { + type = value.type; + bytesPerElement = value.bytesPerElement; - for ( let j = 1; j <= tubularSegments; j ++ ) { + } - for ( let i = 1; i <= radialSegments; i ++ ) { + function render( start, count ) { - const a = ( radialSegments + 1 ) * ( j - 1 ) + ( i - 1 ); - const b = ( radialSegments + 1 ) * j + ( i - 1 ); - const c = ( radialSegments + 1 ) * j + i; - const d = ( radialSegments + 1 ) * ( j - 1 ) + i; + gl.drawElements( mode, count, type, start * bytesPerElement ); - // faces + info.update( count, mode, 1 ); - indices.push( a, b, d ); - indices.push( b, c, d ); + } - } + function renderInstances( start, count, primcount ) { - } + if ( primcount === 0 ) return; - } + gl.drawElementsInstanced( mode, count, type, start * bytesPerElement, primcount ); - function generateUVs() { + info.update( count, mode, primcount ); - for ( let i = 0; i <= tubularSegments; i ++ ) { + } - for ( let j = 0; j <= radialSegments; j ++ ) { + function renderMultiDraw( starts, counts, drawCount ) { - uv.x = i / tubularSegments; - uv.y = j / radialSegments; + if ( drawCount === 0 ) return; - uvs.push( uv.x, uv.y ); + const extension = extensions.get( 'WEBGL_multi_draw' ); + extension.multiDrawElementsWEBGL( mode, counts, 0, type, starts, 0, drawCount ); - } + let elementCount = 0; + for ( let i = 0; i < drawCount; i ++ ) { - } + elementCount += counts[ i ]; } + info.update( elementCount, mode, 1 ); + + } - copy( source ) { + function renderMultiDrawInstances( starts, counts, drawCount, primcount ) { - super.copy( source ); + if ( drawCount === 0 ) return; - this.parameters = Object.assign( {}, source.parameters ); + const extension = extensions.get( 'WEBGL_multi_draw' ); - return this; + if ( extension === null ) { - } + for ( let i = 0; i < starts.length; i ++ ) { - toJSON() { + renderInstances( starts[ i ] / bytesPerElement, counts[ i ], primcount[ i ] ); - const data = super.toJSON(); + } - data.path = this.parameters.path.toJSON(); + } else { - return data; + extension.multiDrawElementsInstancedWEBGL( mode, counts, 0, type, starts, 0, primcount, 0, drawCount ); - } + let elementCount = 0; + for ( let i = 0; i < drawCount; i ++ ) { - static fromJSON( data ) { + elementCount += counts[ i ] * primcount[ i ]; - // This only works for built-in curves (e.g. CatmullRomCurve3). - // User defined curves or instances of CurvePath will not be deserialized. - return new TubeGeometry( - new Curves[ data.path.type ]().fromJSON( data.path ), - data.tubularSegments, - data.radius, - data.radialSegments, - data.closed - ); + } - } + info.update( elementCount, mode, 1 ); -} + } -class WireframeGeometry extends BufferGeometry { + } - constructor( geometry = null ) { + // - super(); + this.setMode = setMode; + this.setIndex = setIndex; + this.render = render; + this.renderInstances = renderInstances; + this.renderMultiDraw = renderMultiDraw; + this.renderMultiDrawInstances = renderMultiDrawInstances; - this.type = 'WireframeGeometry'; +} - this.parameters = { - geometry: geometry - }; +function WebGLInfo( gl ) { - if ( geometry !== null ) { + const memory = { + geometries: 0, + textures: 0 + }; - // buffer + const render = { + frame: 0, + calls: 0, + triangles: 0, + points: 0, + lines: 0 + }; - const vertices = []; - const edges = new Set(); + function update( count, mode, instanceCount ) { - // helper variables + render.calls ++; - const start = new Vector3(); - const end = new Vector3(); + switch ( mode ) { - if ( geometry.index !== null ) { + case gl.TRIANGLES: + render.triangles += instanceCount * ( count / 3 ); + break; - // indexed BufferGeometry + case gl.LINES: + render.lines += instanceCount * ( count / 2 ); + break; - const position = geometry.attributes.position; - const indices = geometry.index; - let groups = geometry.groups; + case gl.LINE_STRIP: + render.lines += instanceCount * ( count - 1 ); + break; - if ( groups.length === 0 ) { + case gl.LINE_LOOP: + render.lines += instanceCount * count; + break; - groups = [ { start: 0, count: indices.count, materialIndex: 0 } ]; + case gl.POINTS: + render.points += instanceCount * count; + break; - } + default: + console.error( 'THREE.WebGLInfo: Unknown draw mode:', mode ); + break; - // create a data structure that contains all edges without duplicates + } - for ( let o = 0, ol = groups.length; o < ol; ++ o ) { + } - const group = groups[ o ]; + function reset() { - const groupStart = group.start; - const groupCount = group.count; + render.calls = 0; + render.triangles = 0; + render.points = 0; + render.lines = 0; - for ( let i = groupStart, l = ( groupStart + groupCount ); i < l; i += 3 ) { + } - for ( let j = 0; j < 3; j ++ ) { + return { + memory: memory, + render: render, + programs: null, + autoReset: true, + reset: reset, + update: update + }; - const index1 = indices.getX( i + j ); - const index2 = indices.getX( i + ( j + 1 ) % 3 ); +} - start.fromBufferAttribute( position, index1 ); - end.fromBufferAttribute( position, index2 ); +function WebGLMorphtargets( gl, capabilities, textures ) { - if ( isUniqueEdge( start, end, edges ) === true ) { + const morphTextures = new WeakMap(); + const morph = new Vector4(); - vertices.push( start.x, start.y, start.z ); - vertices.push( end.x, end.y, end.z ); + function update( object, geometry, program ) { - } + const objectInfluences = object.morphTargetInfluences; - } + // the following encodes morph targets into an array of data textures. Each layer represents a single morph target. - } + const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color; + const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0; - } + let entry = morphTextures.get( geometry ); - } else { + if ( entry === undefined || entry.count !== morphTargetsCount ) { - // non-indexed BufferGeometry + if ( entry !== undefined ) entry.texture.dispose(); - const position = geometry.attributes.position; + const hasMorphPosition = geometry.morphAttributes.position !== undefined; + const hasMorphNormals = geometry.morphAttributes.normal !== undefined; + const hasMorphColors = geometry.morphAttributes.color !== undefined; - for ( let i = 0, l = ( position.count / 3 ); i < l; i ++ ) { + const morphTargets = geometry.morphAttributes.position || []; + const morphNormals = geometry.morphAttributes.normal || []; + const morphColors = geometry.morphAttributes.color || []; - for ( let j = 0; j < 3; j ++ ) { + let vertexDataCount = 0; - // three edges per triangle, an edge is represented as (index1, index2) - // e.g. the first triangle has the following edges: (0,1),(1,2),(2,0) + if ( hasMorphPosition === true ) vertexDataCount = 1; + if ( hasMorphNormals === true ) vertexDataCount = 2; + if ( hasMorphColors === true ) vertexDataCount = 3; - const index1 = 3 * i + j; - const index2 = 3 * i + ( ( j + 1 ) % 3 ); + let width = geometry.attributes.position.count * vertexDataCount; + let height = 1; - start.fromBufferAttribute( position, index1 ); - end.fromBufferAttribute( position, index2 ); + if ( width > capabilities.maxTextureSize ) { - if ( isUniqueEdge( start, end, edges ) === true ) { + height = Math.ceil( width / capabilities.maxTextureSize ); + width = capabilities.maxTextureSize; - vertices.push( start.x, start.y, start.z ); - vertices.push( end.x, end.y, end.z ); + } - } + const buffer = new Float32Array( width * height * 4 * morphTargetsCount ); - } + const texture = new DataArrayTexture( buffer, width, height, morphTargetsCount ); + texture.type = FloatType; + texture.needsUpdate = true; - } + // fill buffer - } + const vertexDataStride = vertexDataCount * 4; - // build geometry + for ( let i = 0; i < morphTargetsCount; i ++ ) { - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + const morphTarget = morphTargets[ i ]; + const morphNormal = morphNormals[ i ]; + const morphColor = morphColors[ i ]; - } + const offset = width * height * 4 * i; - } + for ( let j = 0; j < morphTarget.count; j ++ ) { - copy( source ) { + const stride = j * vertexDataStride; - super.copy( source ); + if ( hasMorphPosition === true ) { - this.parameters = Object.assign( {}, source.parameters ); + morph.fromBufferAttribute( morphTarget, j ); - return this; + buffer[ offset + stride + 0 ] = morph.x; + buffer[ offset + stride + 1 ] = morph.y; + buffer[ offset + stride + 2 ] = morph.z; + buffer[ offset + stride + 3 ] = 0; - } + } -} + if ( hasMorphNormals === true ) { -function isUniqueEdge( start, end, edges ) { + morph.fromBufferAttribute( morphNormal, j ); - const hash1 = `${start.x},${start.y},${start.z}-${end.x},${end.y},${end.z}`; - const hash2 = `${end.x},${end.y},${end.z}-${start.x},${start.y},${start.z}`; // coincident edge + buffer[ offset + stride + 4 ] = morph.x; + buffer[ offset + stride + 5 ] = morph.y; + buffer[ offset + stride + 6 ] = morph.z; + buffer[ offset + stride + 7 ] = 0; - if ( edges.has( hash1 ) === true || edges.has( hash2 ) === true ) { + } - return false; + if ( hasMorphColors === true ) { - } else { + morph.fromBufferAttribute( morphColor, j ); - edges.add( hash1 ); - edges.add( hash2 ); - return true; + buffer[ offset + stride + 8 ] = morph.x; + buffer[ offset + stride + 9 ] = morph.y; + buffer[ offset + stride + 10 ] = morph.z; + buffer[ offset + stride + 11 ] = ( morphColor.itemSize === 4 ) ? morph.w : 1; - } + } -} + } -var Geometries = /*#__PURE__*/Object.freeze({ - __proto__: null, - BoxGeometry: BoxGeometry, - CapsuleGeometry: CapsuleGeometry, - CircleGeometry: CircleGeometry, - ConeGeometry: ConeGeometry, - CylinderGeometry: CylinderGeometry, - DodecahedronGeometry: DodecahedronGeometry, - EdgesGeometry: EdgesGeometry, - ExtrudeGeometry: ExtrudeGeometry, - IcosahedronGeometry: IcosahedronGeometry, - LatheGeometry: LatheGeometry, - OctahedronGeometry: OctahedronGeometry, - PlaneGeometry: PlaneGeometry, - PolyhedronGeometry: PolyhedronGeometry, - RingGeometry: RingGeometry, - ShapeGeometry: ShapeGeometry, - SphereGeometry: SphereGeometry, - TetrahedronGeometry: TetrahedronGeometry, - TorusGeometry: TorusGeometry, - TorusKnotGeometry: TorusKnotGeometry, - TubeGeometry: TubeGeometry, - WireframeGeometry: WireframeGeometry -}); + } -class ShadowMaterial extends Material { + entry = { + count: morphTargetsCount, + texture: texture, + size: new Vector2( width, height ) + }; - constructor( parameters ) { + morphTextures.set( geometry, entry ); - super(); + function disposeTexture() { - this.isShadowMaterial = true; + texture.dispose(); - this.type = 'ShadowMaterial'; + morphTextures.delete( geometry ); - this.color = new Color( 0x000000 ); - this.transparent = true; + geometry.removeEventListener( 'dispose', disposeTexture ); - this.fog = true; + } - this.setValues( parameters ); + geometry.addEventListener( 'dispose', disposeTexture ); - } + } - copy( source ) { + // + if ( object.isInstancedMesh === true && object.morphTexture !== null ) { - super.copy( source ); + program.getUniforms().setValue( gl, 'morphTexture', object.morphTexture, textures ); - this.color.copy( source.color ); + } else { - this.fog = source.fog; + let morphInfluencesSum = 0; - return this; + for ( let i = 0; i < objectInfluences.length; i ++ ) { - } + morphInfluencesSum += objectInfluences[ i ]; -} + } -class RawShaderMaterial extends ShaderMaterial { + const morphBaseInfluence = geometry.morphTargetsRelative ? 1 : 1 - morphInfluencesSum; - constructor( parameters ) { - super( parameters ); + program.getUniforms().setValue( gl, 'morphTargetBaseInfluence', morphBaseInfluence ); + program.getUniforms().setValue( gl, 'morphTargetInfluences', objectInfluences ); - this.isRawShaderMaterial = true; + } - this.type = 'RawShaderMaterial'; + program.getUniforms().setValue( gl, 'morphTargetsTexture', entry.texture, textures ); + program.getUniforms().setValue( gl, 'morphTargetsTextureSize', entry.size ); } -} + return { -class MeshStandardMaterial extends Material { + update: update - constructor( parameters ) { + }; - super(); +} - this.isMeshStandardMaterial = true; +function WebGLObjects( gl, geometries, attributes, info ) { - this.defines = { 'STANDARD': '' }; + let updateMap = new WeakMap(); - this.type = 'MeshStandardMaterial'; + function update( object ) { - this.color = new Color( 0xffffff ); // diffuse - this.roughness = 1.0; - this.metalness = 0.0; + const frame = info.render.frame; - this.map = null; + const geometry = object.geometry; + const buffergeometry = geometries.get( object, geometry ); - this.lightMap = null; - this.lightMapIntensity = 1.0; + // Update once per frame - this.aoMap = null; - this.aoMapIntensity = 1.0; + if ( updateMap.get( buffergeometry ) !== frame ) { - this.emissive = new Color( 0x000000 ); - this.emissiveIntensity = 1.0; - this.emissiveMap = null; + geometries.update( buffergeometry ); - this.bumpMap = null; - this.bumpScale = 1; + updateMap.set( buffergeometry, frame ); - this.normalMap = null; - this.normalMapType = TangentSpaceNormalMap; - this.normalScale = new Vector2( 1, 1 ); + } - this.displacementMap = null; - this.displacementScale = 1; - this.displacementBias = 0; + if ( object.isInstancedMesh ) { - this.roughnessMap = null; + if ( object.hasEventListener( 'dispose', onInstancedMeshDispose ) === false ) { - this.metalnessMap = null; + object.addEventListener( 'dispose', onInstancedMeshDispose ); - this.alphaMap = null; + } - this.envMap = null; - this.envMapIntensity = 1.0; + if ( updateMap.get( object ) !== frame ) { - this.wireframe = false; - this.wireframeLinewidth = 1; - this.wireframeLinecap = 'round'; - this.wireframeLinejoin = 'round'; + attributes.update( object.instanceMatrix, gl.ARRAY_BUFFER ); - this.flatShading = false; + if ( object.instanceColor !== null ) { - this.fog = true; + attributes.update( object.instanceColor, gl.ARRAY_BUFFER ); - this.setValues( parameters ); + } - } + updateMap.set( object, frame ); - copy( source ) { + } - super.copy( source ); + } - this.defines = { 'STANDARD': '' }; + if ( object.isSkinnedMesh ) { - this.color.copy( source.color ); - this.roughness = source.roughness; - this.metalness = source.metalness; + const skeleton = object.skeleton; - this.map = source.map; + if ( updateMap.get( skeleton ) !== frame ) { - this.lightMap = source.lightMap; - this.lightMapIntensity = source.lightMapIntensity; + skeleton.update(); - this.aoMap = source.aoMap; - this.aoMapIntensity = source.aoMapIntensity; + updateMap.set( skeleton, frame ); - this.emissive.copy( source.emissive ); - this.emissiveMap = source.emissiveMap; - this.emissiveIntensity = source.emissiveIntensity; + } - this.bumpMap = source.bumpMap; - this.bumpScale = source.bumpScale; + } - this.normalMap = source.normalMap; - this.normalMapType = source.normalMapType; - this.normalScale.copy( source.normalScale ); + return buffergeometry; - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; + } - this.roughnessMap = source.roughnessMap; + function dispose() { - this.metalnessMap = source.metalnessMap; + updateMap = new WeakMap(); - this.alphaMap = source.alphaMap; + } - this.envMap = source.envMap; - this.envMapIntensity = source.envMapIntensity; + function onInstancedMeshDispose( event ) { - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; - this.wireframeLinecap = source.wireframeLinecap; - this.wireframeLinejoin = source.wireframeLinejoin; + const instancedMesh = event.target; - this.flatShading = source.flatShading; + instancedMesh.removeEventListener( 'dispose', onInstancedMeshDispose ); - this.fog = source.fog; + attributes.remove( instancedMesh.instanceMatrix ); - return this; + if ( instancedMesh.instanceColor !== null ) attributes.remove( instancedMesh.instanceColor ); } -} + return { -class MeshPhysicalMaterial extends MeshStandardMaterial { + update: update, + dispose: dispose - constructor( parameters ) { + }; - super(); +} - this.isMeshPhysicalMaterial = true; +/** + * Uniforms of a program. + * Those form a tree structure with a special top-level container for the root, + * which you get by calling 'new WebGLUniforms( gl, program )'. + * + * + * Properties of inner nodes including the top-level container: + * + * .seq - array of nested uniforms + * .map - nested uniforms by name + * + * + * Methods of all nodes except the top-level container: + * + * .setValue( gl, value, [textures] ) + * + * uploads a uniform value(s) + * the 'textures' parameter is needed for sampler uniforms + * + * + * Static methods of the top-level container (textures factorizations): + * + * .upload( gl, seq, values, textures ) + * + * sets uniforms in 'seq' to 'values[id].value' + * + * .seqWithValue( seq, values ) : filteredSeq + * + * filters 'seq' entries with corresponding entry in values + * + * + * Methods of the top-level container (textures factorizations): + * + * .setValue( gl, name, value, textures ) + * + * sets uniform with name 'name' to 'value' + * + * .setOptional( gl, obj, prop ) + * + * like .set for an optional property of the object + * + */ - this.defines = { - 'STANDARD': '', - 'PHYSICAL': '' +const emptyTexture = /*@__PURE__*/ new Texture(); - }; +const emptyShadowTexture = /*@__PURE__*/ new DepthTexture( 1, 1 ); - this.type = 'MeshPhysicalMaterial'; +const emptyArrayTexture = /*@__PURE__*/ new DataArrayTexture(); +const empty3dTexture = /*@__PURE__*/ new Data3DTexture(); +const emptyCubeTexture = /*@__PURE__*/ new CubeTexture(); - this.anisotropyRotation = 0; - this.anisotropyMap = null; +// --- Utilities --- - this.clearcoatMap = null; - this.clearcoatRoughness = 0.0; - this.clearcoatRoughnessMap = null; - this.clearcoatNormalScale = new Vector2( 1, 1 ); - this.clearcoatNormalMap = null; +// Array Caches (provide typed arrays for temporary by size) - this.ior = 1.5; +const arrayCacheF32 = []; +const arrayCacheI32 = []; - Object.defineProperty( this, 'reflectivity', { - get: function () { +// Float32Array caches used for uploading Matrix uniforms - return ( clamp( 2.5 * ( this.ior - 1 ) / ( this.ior + 1 ), 0, 1 ) ); +const mat4array = new Float32Array( 16 ); +const mat3array = new Float32Array( 9 ); +const mat2array = new Float32Array( 4 ); - }, - set: function ( reflectivity ) { +// Flattening for arrays of vectors and matrices - this.ior = ( 1 + 0.4 * reflectivity ) / ( 1 - 0.4 * reflectivity ); +function flatten( array, nBlocks, blockSize ) { - } - } ); + const firstElem = array[ 0 ]; - this.iridescenceMap = null; - this.iridescenceIOR = 1.3; - this.iridescenceThicknessRange = [ 100, 400 ]; - this.iridescenceThicknessMap = null; + if ( firstElem <= 0 || firstElem > 0 ) return array; + // unoptimized: ! isNaN( firstElem ) + // see http://jacksondunstan.com/articles/983 - this.sheenColor = new Color( 0x000000 ); - this.sheenColorMap = null; - this.sheenRoughness = 1.0; - this.sheenRoughnessMap = null; + const n = nBlocks * blockSize; + let r = arrayCacheF32[ n ]; - this.transmissionMap = null; + if ( r === undefined ) { - this.thickness = 0; - this.thicknessMap = null; - this.attenuationDistance = Infinity; - this.attenuationColor = new Color( 1, 1, 1 ); + r = new Float32Array( n ); + arrayCacheF32[ n ] = r; - this.specularIntensity = 1.0; - this.specularIntensityMap = null; - this.specularColor = new Color( 1, 1, 1 ); - this.specularColorMap = null; + } - this._anisotropy = 0; - this._clearcoat = 0; - this._iridescence = 0; - this._sheen = 0.0; - this._transmission = 0; + if ( nBlocks !== 0 ) { - this.setValues( parameters ); + firstElem.toArray( r, 0 ); - } + for ( let i = 1, offset = 0; i !== nBlocks; ++ i ) { - get anisotropy() { + offset += blockSize; + array[ i ].toArray( r, offset ); - return this._anisotropy; + } } - set anisotropy( value ) { + return r; - if ( this._anisotropy > 0 !== value > 0 ) { +} - this.version ++; +function arraysEqual( a, b ) { - } + if ( a.length !== b.length ) return false; - this._anisotropy = value; + for ( let i = 0, l = a.length; i < l; i ++ ) { + + if ( a[ i ] !== b[ i ] ) return false; } - get clearcoat() { + return true; - return this._clearcoat; +} + +function copyArray( a, b ) { + + for ( let i = 0, l = b.length; i < l; i ++ ) { + + a[ i ] = b[ i ]; } - set clearcoat( value ) { +} - if ( this._clearcoat > 0 !== value > 0 ) { +// Texture unit allocation - this.version ++; +function allocTexUnits( textures, n ) { - } + let r = arrayCacheI32[ n ]; - this._clearcoat = value; + if ( r === undefined ) { + + r = new Int32Array( n ); + arrayCacheI32[ n ] = r; } - get iridescence() { + for ( let i = 0; i !== n; ++ i ) { - return this._iridescence; + r[ i ] = textures.allocateTextureUnit(); } - set iridescence( value ) { + return r; - if ( this._iridescence > 0 !== value > 0 ) { +} - this.version ++; +// --- Setters --- - } +// Note: Defining these methods externally, because they come in a bunch +// and this way their names minify. - this._iridescence = value; +// Single scalar - } +function setValueV1f( gl, v ) { - get sheen() { + const cache = this.cache; - return this._sheen; + if ( cache[ 0 ] === v ) return; - } + gl.uniform1f( this.addr, v ); - set sheen( value ) { + cache[ 0 ] = v; - if ( this._sheen > 0 !== value > 0 ) { +} - this.version ++; +// Single float vector (from flat array or THREE.VectorN) - } +function setValueV2f( gl, v ) { - this._sheen = value; + const cache = this.cache; - } + if ( v.x !== undefined ) { - get transmission() { + if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y ) { - return this._transmission; + gl.uniform2f( this.addr, v.x, v.y ); - } + cache[ 0 ] = v.x; + cache[ 1 ] = v.y; - set transmission( value ) { + } - if ( this._transmission > 0 !== value > 0 ) { + } else { - this.version ++; + if ( arraysEqual( cache, v ) ) return; - } + gl.uniform2fv( this.addr, v ); - this._transmission = value; + copyArray( cache, v ); } - copy( source ) { +} - super.copy( source ); +function setValueV3f( gl, v ) { - this.defines = { + const cache = this.cache; - 'STANDARD': '', - 'PHYSICAL': '' + if ( v.x !== undefined ) { - }; + if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z ) { - this.anisotropy = source.anisotropy; - this.anisotropyRotation = source.anisotropyRotation; - this.anisotropyMap = source.anisotropyMap; + gl.uniform3f( this.addr, v.x, v.y, v.z ); - this.clearcoat = source.clearcoat; - this.clearcoatMap = source.clearcoatMap; - this.clearcoatRoughness = source.clearcoatRoughness; - this.clearcoatRoughnessMap = source.clearcoatRoughnessMap; - this.clearcoatNormalMap = source.clearcoatNormalMap; - this.clearcoatNormalScale.copy( source.clearcoatNormalScale ); + cache[ 0 ] = v.x; + cache[ 1 ] = v.y; + cache[ 2 ] = v.z; - this.ior = source.ior; + } - this.iridescence = source.iridescence; - this.iridescenceMap = source.iridescenceMap; - this.iridescenceIOR = source.iridescenceIOR; - this.iridescenceThicknessRange = [ ...source.iridescenceThicknessRange ]; - this.iridescenceThicknessMap = source.iridescenceThicknessMap; + } else if ( v.r !== undefined ) { - this.sheen = source.sheen; - this.sheenColor.copy( source.sheenColor ); - this.sheenColorMap = source.sheenColorMap; - this.sheenRoughness = source.sheenRoughness; - this.sheenRoughnessMap = source.sheenRoughnessMap; + if ( cache[ 0 ] !== v.r || cache[ 1 ] !== v.g || cache[ 2 ] !== v.b ) { - this.transmission = source.transmission; - this.transmissionMap = source.transmissionMap; + gl.uniform3f( this.addr, v.r, v.g, v.b ); - this.thickness = source.thickness; - this.thicknessMap = source.thicknessMap; - this.attenuationDistance = source.attenuationDistance; - this.attenuationColor.copy( source.attenuationColor ); + cache[ 0 ] = v.r; + cache[ 1 ] = v.g; + cache[ 2 ] = v.b; - this.specularIntensity = source.specularIntensity; - this.specularIntensityMap = source.specularIntensityMap; - this.specularColor.copy( source.specularColor ); - this.specularColorMap = source.specularColorMap; + } - return this; + } else { + + if ( arraysEqual( cache, v ) ) return; + + gl.uniform3fv( this.addr, v ); + + copyArray( cache, v ); } } -class MeshPhongMaterial extends Material { +function setValueV4f( gl, v ) { - constructor( parameters ) { + const cache = this.cache; - super(); + if ( v.x !== undefined ) { - this.isMeshPhongMaterial = true; + if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z || cache[ 3 ] !== v.w ) { - this.type = 'MeshPhongMaterial'; + gl.uniform4f( this.addr, v.x, v.y, v.z, v.w ); - this.color = new Color( 0xffffff ); // diffuse - this.specular = new Color( 0x111111 ); - this.shininess = 30; + cache[ 0 ] = v.x; + cache[ 1 ] = v.y; + cache[ 2 ] = v.z; + cache[ 3 ] = v.w; - this.map = null; + } - this.lightMap = null; - this.lightMapIntensity = 1.0; + } else { - this.aoMap = null; - this.aoMapIntensity = 1.0; + if ( arraysEqual( cache, v ) ) return; - this.emissive = new Color( 0x000000 ); - this.emissiveIntensity = 1.0; - this.emissiveMap = null; + gl.uniform4fv( this.addr, v ); - this.bumpMap = null; - this.bumpScale = 1; + copyArray( cache, v ); - this.normalMap = null; - this.normalMapType = TangentSpaceNormalMap; - this.normalScale = new Vector2( 1, 1 ); + } - this.displacementMap = null; - this.displacementScale = 1; - this.displacementBias = 0; +} - this.specularMap = null; +// Single matrix (from flat array or THREE.MatrixN) - this.alphaMap = null; +function setValueM2( gl, v ) { - this.envMap = null; - this.combine = MultiplyOperation; - this.reflectivity = 1; - this.refractionRatio = 0.98; + const cache = this.cache; + const elements = v.elements; - this.wireframe = false; - this.wireframeLinewidth = 1; - this.wireframeLinecap = 'round'; - this.wireframeLinejoin = 'round'; + if ( elements === undefined ) { - this.flatShading = false; + if ( arraysEqual( cache, v ) ) return; - this.fog = true; + gl.uniformMatrix2fv( this.addr, false, v ); - this.setValues( parameters ); + copyArray( cache, v ); - } + } else { - copy( source ) { + if ( arraysEqual( cache, elements ) ) return; - super.copy( source ); + mat2array.set( elements ); - this.color.copy( source.color ); - this.specular.copy( source.specular ); - this.shininess = source.shininess; + gl.uniformMatrix2fv( this.addr, false, mat2array ); - this.map = source.map; + copyArray( cache, elements ); - this.lightMap = source.lightMap; - this.lightMapIntensity = source.lightMapIntensity; + } - this.aoMap = source.aoMap; - this.aoMapIntensity = source.aoMapIntensity; +} - this.emissive.copy( source.emissive ); - this.emissiveMap = source.emissiveMap; - this.emissiveIntensity = source.emissiveIntensity; +function setValueM3( gl, v ) { - this.bumpMap = source.bumpMap; - this.bumpScale = source.bumpScale; + const cache = this.cache; + const elements = v.elements; - this.normalMap = source.normalMap; - this.normalMapType = source.normalMapType; - this.normalScale.copy( source.normalScale ); + if ( elements === undefined ) { - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; + if ( arraysEqual( cache, v ) ) return; - this.specularMap = source.specularMap; + gl.uniformMatrix3fv( this.addr, false, v ); - this.alphaMap = source.alphaMap; + copyArray( cache, v ); - this.envMap = source.envMap; - this.combine = source.combine; - this.reflectivity = source.reflectivity; - this.refractionRatio = source.refractionRatio; + } else { - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; - this.wireframeLinecap = source.wireframeLinecap; - this.wireframeLinejoin = source.wireframeLinejoin; + if ( arraysEqual( cache, elements ) ) return; - this.flatShading = source.flatShading; + mat3array.set( elements ); - this.fog = source.fog; + gl.uniformMatrix3fv( this.addr, false, mat3array ); - return this; + copyArray( cache, elements ); } } -class MeshToonMaterial extends Material { - - constructor( parameters ) { +function setValueM4( gl, v ) { - super(); + const cache = this.cache; + const elements = v.elements; - this.isMeshToonMaterial = true; + if ( elements === undefined ) { - this.defines = { 'TOON': '' }; + if ( arraysEqual( cache, v ) ) return; - this.type = 'MeshToonMaterial'; + gl.uniformMatrix4fv( this.addr, false, v ); - this.color = new Color( 0xffffff ); + copyArray( cache, v ); - this.map = null; - this.gradientMap = null; + } else { - this.lightMap = null; - this.lightMapIntensity = 1.0; + if ( arraysEqual( cache, elements ) ) return; - this.aoMap = null; - this.aoMapIntensity = 1.0; + mat4array.set( elements ); - this.emissive = new Color( 0x000000 ); - this.emissiveIntensity = 1.0; - this.emissiveMap = null; + gl.uniformMatrix4fv( this.addr, false, mat4array ); - this.bumpMap = null; - this.bumpScale = 1; + copyArray( cache, elements ); - this.normalMap = null; - this.normalMapType = TangentSpaceNormalMap; - this.normalScale = new Vector2( 1, 1 ); + } - this.displacementMap = null; - this.displacementScale = 1; - this.displacementBias = 0; +} - this.alphaMap = null; +// Single integer / boolean - this.wireframe = false; - this.wireframeLinewidth = 1; - this.wireframeLinecap = 'round'; - this.wireframeLinejoin = 'round'; +function setValueV1i( gl, v ) { - this.fog = true; + const cache = this.cache; - this.setValues( parameters ); + if ( cache[ 0 ] === v ) return; - } + gl.uniform1i( this.addr, v ); - copy( source ) { + cache[ 0 ] = v; - super.copy( source ); +} - this.color.copy( source.color ); +// Single integer / boolean vector (from flat array or THREE.VectorN) - this.map = source.map; - this.gradientMap = source.gradientMap; +function setValueV2i( gl, v ) { - this.lightMap = source.lightMap; - this.lightMapIntensity = source.lightMapIntensity; + const cache = this.cache; - this.aoMap = source.aoMap; - this.aoMapIntensity = source.aoMapIntensity; + if ( v.x !== undefined ) { - this.emissive.copy( source.emissive ); - this.emissiveMap = source.emissiveMap; - this.emissiveIntensity = source.emissiveIntensity; + if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y ) { - this.bumpMap = source.bumpMap; - this.bumpScale = source.bumpScale; + gl.uniform2i( this.addr, v.x, v.y ); - this.normalMap = source.normalMap; - this.normalMapType = source.normalMapType; - this.normalScale.copy( source.normalScale ); + cache[ 0 ] = v.x; + cache[ 1 ] = v.y; - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; + } - this.alphaMap = source.alphaMap; + } else { - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; - this.wireframeLinecap = source.wireframeLinecap; - this.wireframeLinejoin = source.wireframeLinejoin; + if ( arraysEqual( cache, v ) ) return; - this.fog = source.fog; + gl.uniform2iv( this.addr, v ); - return this; + copyArray( cache, v ); } } -class MeshNormalMaterial extends Material { +function setValueV3i( gl, v ) { - constructor( parameters ) { + const cache = this.cache; - super(); + if ( v.x !== undefined ) { - this.isMeshNormalMaterial = true; + if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z ) { - this.type = 'MeshNormalMaterial'; + gl.uniform3i( this.addr, v.x, v.y, v.z ); - this.bumpMap = null; - this.bumpScale = 1; + cache[ 0 ] = v.x; + cache[ 1 ] = v.y; + cache[ 2 ] = v.z; - this.normalMap = null; - this.normalMapType = TangentSpaceNormalMap; - this.normalScale = new Vector2( 1, 1 ); + } - this.displacementMap = null; - this.displacementScale = 1; - this.displacementBias = 0; + } else { - this.wireframe = false; - this.wireframeLinewidth = 1; + if ( arraysEqual( cache, v ) ) return; - this.flatShading = false; + gl.uniform3iv( this.addr, v ); - this.setValues( parameters ); + copyArray( cache, v ); } - copy( source ) { - - super.copy( source ); - - this.bumpMap = source.bumpMap; - this.bumpScale = source.bumpScale; +} - this.normalMap = source.normalMap; - this.normalMapType = source.normalMapType; - this.normalScale.copy( source.normalScale ); +function setValueV4i( gl, v ) { - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; + const cache = this.cache; - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; + if ( v.x !== undefined ) { - this.flatShading = source.flatShading; + if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z || cache[ 3 ] !== v.w ) { - return this; + gl.uniform4i( this.addr, v.x, v.y, v.z, v.w ); - } + cache[ 0 ] = v.x; + cache[ 1 ] = v.y; + cache[ 2 ] = v.z; + cache[ 3 ] = v.w; -} + } -class MeshLambertMaterial extends Material { + } else { - constructor( parameters ) { + if ( arraysEqual( cache, v ) ) return; - super(); + gl.uniform4iv( this.addr, v ); - this.isMeshLambertMaterial = true; + copyArray( cache, v ); - this.type = 'MeshLambertMaterial'; + } - this.color = new Color( 0xffffff ); // diffuse +} - this.map = null; +// Single unsigned integer - this.lightMap = null; - this.lightMapIntensity = 1.0; +function setValueV1ui( gl, v ) { - this.aoMap = null; - this.aoMapIntensity = 1.0; + const cache = this.cache; - this.emissive = new Color( 0x000000 ); - this.emissiveIntensity = 1.0; - this.emissiveMap = null; + if ( cache[ 0 ] === v ) return; - this.bumpMap = null; - this.bumpScale = 1; + gl.uniform1ui( this.addr, v ); - this.normalMap = null; - this.normalMapType = TangentSpaceNormalMap; - this.normalScale = new Vector2( 1, 1 ); + cache[ 0 ] = v; - this.displacementMap = null; - this.displacementScale = 1; - this.displacementBias = 0; +} - this.specularMap = null; +// Single unsigned integer vector (from flat array or THREE.VectorN) - this.alphaMap = null; +function setValueV2ui( gl, v ) { - this.envMap = null; - this.combine = MultiplyOperation; - this.reflectivity = 1; - this.refractionRatio = 0.98; + const cache = this.cache; - this.wireframe = false; - this.wireframeLinewidth = 1; - this.wireframeLinecap = 'round'; - this.wireframeLinejoin = 'round'; + if ( v.x !== undefined ) { - this.flatShading = false; + if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y ) { - this.fog = true; + gl.uniform2ui( this.addr, v.x, v.y ); - this.setValues( parameters ); + cache[ 0 ] = v.x; + cache[ 1 ] = v.y; - } + } - copy( source ) { + } else { - super.copy( source ); + if ( arraysEqual( cache, v ) ) return; - this.color.copy( source.color ); + gl.uniform2uiv( this.addr, v ); - this.map = source.map; + copyArray( cache, v ); - this.lightMap = source.lightMap; - this.lightMapIntensity = source.lightMapIntensity; + } - this.aoMap = source.aoMap; - this.aoMapIntensity = source.aoMapIntensity; +} - this.emissive.copy( source.emissive ); - this.emissiveMap = source.emissiveMap; - this.emissiveIntensity = source.emissiveIntensity; +function setValueV3ui( gl, v ) { - this.bumpMap = source.bumpMap; - this.bumpScale = source.bumpScale; + const cache = this.cache; - this.normalMap = source.normalMap; - this.normalMapType = source.normalMapType; - this.normalScale.copy( source.normalScale ); + if ( v.x !== undefined ) { - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; + if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z ) { - this.specularMap = source.specularMap; + gl.uniform3ui( this.addr, v.x, v.y, v.z ); - this.alphaMap = source.alphaMap; + cache[ 0 ] = v.x; + cache[ 1 ] = v.y; + cache[ 2 ] = v.z; - this.envMap = source.envMap; - this.combine = source.combine; - this.reflectivity = source.reflectivity; - this.refractionRatio = source.refractionRatio; + } - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; - this.wireframeLinecap = source.wireframeLinecap; - this.wireframeLinejoin = source.wireframeLinejoin; + } else { - this.flatShading = source.flatShading; + if ( arraysEqual( cache, v ) ) return; - this.fog = source.fog; + gl.uniform3uiv( this.addr, v ); - return this; + copyArray( cache, v ); } } -class MeshMatcapMaterial extends Material { +function setValueV4ui( gl, v ) { - constructor( parameters ) { + const cache = this.cache; - super(); + if ( v.x !== undefined ) { - this.isMeshMatcapMaterial = true; + if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z || cache[ 3 ] !== v.w ) { - this.defines = { 'MATCAP': '' }; + gl.uniform4ui( this.addr, v.x, v.y, v.z, v.w ); - this.type = 'MeshMatcapMaterial'; + cache[ 0 ] = v.x; + cache[ 1 ] = v.y; + cache[ 2 ] = v.z; + cache[ 3 ] = v.w; - this.color = new Color( 0xffffff ); // diffuse + } - this.matcap = null; + } else { - this.map = null; + if ( arraysEqual( cache, v ) ) return; - this.bumpMap = null; - this.bumpScale = 1; + gl.uniform4uiv( this.addr, v ); - this.normalMap = null; - this.normalMapType = TangentSpaceNormalMap; - this.normalScale = new Vector2( 1, 1 ); + copyArray( cache, v ); - this.displacementMap = null; - this.displacementScale = 1; - this.displacementBias = 0; + } - this.alphaMap = null; +} - this.flatShading = false; - this.fog = true; +// Single texture (2D / Cube) - this.setValues( parameters ); +function setValueT1( gl, v, textures ) { - } + const cache = this.cache; + const unit = textures.allocateTextureUnit(); + if ( cache[ 0 ] !== unit ) { - copy( source ) { + gl.uniform1i( this.addr, unit ); + cache[ 0 ] = unit; - super.copy( source ); + } - this.defines = { 'MATCAP': '' }; + let emptyTexture2D; - this.color.copy( source.color ); + if ( this.type === gl.SAMPLER_2D_SHADOW ) { - this.matcap = source.matcap; + emptyShadowTexture.compareFunction = LessEqualCompare; // #28670 + emptyTexture2D = emptyShadowTexture; - this.map = source.map; + } else { - this.bumpMap = source.bumpMap; - this.bumpScale = source.bumpScale; + emptyTexture2D = emptyTexture; - this.normalMap = source.normalMap; - this.normalMapType = source.normalMapType; - this.normalScale.copy( source.normalScale ); + } - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; + textures.setTexture2D( v || emptyTexture2D, unit ); - this.alphaMap = source.alphaMap; +} - this.flatShading = source.flatShading; +function setValueT3D1( gl, v, textures ) { - this.fog = source.fog; + const cache = this.cache; + const unit = textures.allocateTextureUnit(); - return this; + if ( cache[ 0 ] !== unit ) { + + gl.uniform1i( this.addr, unit ); + cache[ 0 ] = unit; } -} + textures.setTexture3D( v || empty3dTexture, unit ); -class LineDashedMaterial extends LineBasicMaterial { +} - constructor( parameters ) { +function setValueT6( gl, v, textures ) { - super(); + const cache = this.cache; + const unit = textures.allocateTextureUnit(); - this.isLineDashedMaterial = true; + if ( cache[ 0 ] !== unit ) { - this.type = 'LineDashedMaterial'; + gl.uniform1i( this.addr, unit ); + cache[ 0 ] = unit; - this.scale = 1; - this.dashSize = 3; - this.gapSize = 1; + } - this.setValues( parameters ); + textures.setTextureCube( v || emptyCubeTexture, unit ); - } +} - copy( source ) { +function setValueT2DArray1( gl, v, textures ) { - super.copy( source ); + const cache = this.cache; + const unit = textures.allocateTextureUnit(); - this.scale = source.scale; - this.dashSize = source.dashSize; - this.gapSize = source.gapSize; + if ( cache[ 0 ] !== unit ) { - return this; + gl.uniform1i( this.addr, unit ); + cache[ 0 ] = unit; } + textures.setTexture2DArray( v || emptyArrayTexture, unit ); + } -// same as Array.prototype.slice, but also works on typed arrays -function arraySlice( array, from, to ) { +// Helper to pick the right setter for the singular case + +function getSingularSetter( type ) { - if ( isTypedArray( array ) ) { + switch ( type ) { - // in ios9 array.subarray(from, undefined) will return empty array - // but array.subarray(from) or array.subarray(from, len) is correct - return new array.constructor( array.subarray( from, to !== undefined ? to : array.length ) ); + case 0x1406: return setValueV1f; // FLOAT + case 0x8b50: return setValueV2f; // _VEC2 + case 0x8b51: return setValueV3f; // _VEC3 + case 0x8b52: return setValueV4f; // _VEC4 - } + case 0x8b5a: return setValueM2; // _MAT2 + case 0x8b5b: return setValueM3; // _MAT3 + case 0x8b5c: return setValueM4; // _MAT4 - return array.slice( from, to ); + case 0x1404: case 0x8b56: return setValueV1i; // INT, BOOL + case 0x8b53: case 0x8b57: return setValueV2i; // _VEC2 + case 0x8b54: case 0x8b58: return setValueV3i; // _VEC3 + case 0x8b55: case 0x8b59: return setValueV4i; // _VEC4 -} + case 0x1405: return setValueV1ui; // UINT + case 0x8dc6: return setValueV2ui; // _VEC2 + case 0x8dc7: return setValueV3ui; // _VEC3 + case 0x8dc8: return setValueV4ui; // _VEC4 -// converts an array to a specific type -function convertArray( array, type, forceClone ) { + case 0x8b5e: // SAMPLER_2D + case 0x8d66: // SAMPLER_EXTERNAL_OES + case 0x8dca: // INT_SAMPLER_2D + case 0x8dd2: // UNSIGNED_INT_SAMPLER_2D + case 0x8b62: // SAMPLER_2D_SHADOW + return setValueT1; - if ( ! array || // let 'undefined' and 'null' pass - ! forceClone && array.constructor === type ) return array; + case 0x8b5f: // SAMPLER_3D + case 0x8dcb: // INT_SAMPLER_3D + case 0x8dd3: // UNSIGNED_INT_SAMPLER_3D + return setValueT3D1; - if ( typeof type.BYTES_PER_ELEMENT === 'number' ) { + case 0x8b60: // SAMPLER_CUBE + case 0x8dcc: // INT_SAMPLER_CUBE + case 0x8dd4: // UNSIGNED_INT_SAMPLER_CUBE + case 0x8dc5: // SAMPLER_CUBE_SHADOW + return setValueT6; - return new type( array ); // create typed array + case 0x8dc1: // SAMPLER_2D_ARRAY + case 0x8dcf: // INT_SAMPLER_2D_ARRAY + case 0x8dd7: // UNSIGNED_INT_SAMPLER_2D_ARRAY + case 0x8dc4: // SAMPLER_2D_ARRAY_SHADOW + return setValueT2DArray1; } - return Array.prototype.slice.call( array ); // create Array - } -function isTypedArray( object ) { - - return ArrayBuffer.isView( object ) && - ! ( object instanceof DataView ); -} +// Array of scalars -// returns an array by which times and values can be sorted -function getKeyframeOrder( times ) { +function setValueV1fArray( gl, v ) { - function compareTime( i, j ) { + gl.uniform1fv( this.addr, v ); - return times[ i ] - times[ j ]; +} - } +// Array of vectors (from flat array or array of THREE.VectorN) - const n = times.length; - const result = new Array( n ); - for ( let i = 0; i !== n; ++ i ) result[ i ] = i; +function setValueV2fArray( gl, v ) { - result.sort( compareTime ); + const data = flatten( v, this.size, 2 ); - return result; + gl.uniform2fv( this.addr, data ); } -// uses the array previously returned by 'getKeyframeOrder' to sort data -function sortedArray( values, stride, order ) { - - const nValues = values.length; - const result = new values.constructor( nValues ); - - for ( let i = 0, dstOffset = 0; dstOffset !== nValues; ++ i ) { +function setValueV3fArray( gl, v ) { - const srcOffset = order[ i ] * stride; + const data = flatten( v, this.size, 3 ); - for ( let j = 0; j !== stride; ++ j ) { + gl.uniform3fv( this.addr, data ); - result[ dstOffset ++ ] = values[ srcOffset + j ]; +} - } +function setValueV4fArray( gl, v ) { - } + const data = flatten( v, this.size, 4 ); - return result; + gl.uniform4fv( this.addr, data ); } -// function for parsing AOS keyframe formats -function flattenJSON( jsonKeys, times, values, valuePropertyName ) { +// Array of matrices (from flat array or array of THREE.MatrixN) - let i = 1, key = jsonKeys[ 0 ]; +function setValueM2Array( gl, v ) { - while ( key !== undefined && key[ valuePropertyName ] === undefined ) { + const data = flatten( v, this.size, 4 ); - key = jsonKeys[ i ++ ]; + gl.uniformMatrix2fv( this.addr, false, data ); - } +} - if ( key === undefined ) return; // no data +function setValueM3Array( gl, v ) { - let value = key[ valuePropertyName ]; - if ( value === undefined ) return; // no data + const data = flatten( v, this.size, 9 ); - if ( Array.isArray( value ) ) { + gl.uniformMatrix3fv( this.addr, false, data ); - do { +} - value = key[ valuePropertyName ]; +function setValueM4Array( gl, v ) { - if ( value !== undefined ) { + const data = flatten( v, this.size, 16 ); - times.push( key.time ); - values.push.apply( values, value ); // push all elements + gl.uniformMatrix4fv( this.addr, false, data ); - } +} - key = jsonKeys[ i ++ ]; +// Array of integer / boolean - } while ( key !== undefined ); +function setValueV1iArray( gl, v ) { - } else if ( value.toArray !== undefined ) { + gl.uniform1iv( this.addr, v ); - // ...assume THREE.Math-ish +} - do { +// Array of integer / boolean vectors (from flat array) - value = key[ valuePropertyName ]; +function setValueV2iArray( gl, v ) { - if ( value !== undefined ) { + gl.uniform2iv( this.addr, v ); - times.push( key.time ); - value.toArray( values, values.length ); +} - } +function setValueV3iArray( gl, v ) { - key = jsonKeys[ i ++ ]; + gl.uniform3iv( this.addr, v ); - } while ( key !== undefined ); +} - } else { +function setValueV4iArray( gl, v ) { - // otherwise push as-is + gl.uniform4iv( this.addr, v ); - do { +} - value = key[ valuePropertyName ]; +// Array of unsigned integer - if ( value !== undefined ) { +function setValueV1uiArray( gl, v ) { - times.push( key.time ); - values.push( value ); + gl.uniform1uiv( this.addr, v ); - } +} - key = jsonKeys[ i ++ ]; +// Array of unsigned integer vectors (from flat array) - } while ( key !== undefined ); +function setValueV2uiArray( gl, v ) { - } + gl.uniform2uiv( this.addr, v ); } -function subclip( sourceClip, name, startFrame, endFrame, fps = 30 ) { +function setValueV3uiArray( gl, v ) { - const clip = sourceClip.clone(); + gl.uniform3uiv( this.addr, v ); - clip.name = name; +} - const tracks = []; +function setValueV4uiArray( gl, v ) { - for ( let i = 0; i < clip.tracks.length; ++ i ) { + gl.uniform4uiv( this.addr, v ); - const track = clip.tracks[ i ]; - const valueSize = track.getValueSize(); +} - const times = []; - const values = []; - for ( let j = 0; j < track.times.length; ++ j ) { +// Array of textures (2D / 3D / Cube / 2DArray) - const frame = track.times[ j ] * fps; +function setValueT1Array( gl, v, textures ) { - if ( frame < startFrame || frame >= endFrame ) continue; + const cache = this.cache; - times.push( track.times[ j ] ); + const n = v.length; - for ( let k = 0; k < valueSize; ++ k ) { + const units = allocTexUnits( textures, n ); - values.push( track.values[ j * valueSize + k ] ); + if ( ! arraysEqual( cache, units ) ) { - } + gl.uniform1iv( this.addr, units ); - } + copyArray( cache, units ); - if ( times.length === 0 ) continue; + } - track.times = convertArray( times, track.times.constructor ); - track.values = convertArray( values, track.values.constructor ); + for ( let i = 0; i !== n; ++ i ) { - tracks.push( track ); + textures.setTexture2D( v[ i ] || emptyTexture, units[ i ] ); } - clip.tracks = tracks; +} - // find minimum .times value across all tracks in the trimmed clip +function setValueT3DArray( gl, v, textures ) { - let minStartTime = Infinity; + const cache = this.cache; - for ( let i = 0; i < clip.tracks.length; ++ i ) { + const n = v.length; - if ( minStartTime > clip.tracks[ i ].times[ 0 ] ) { + const units = allocTexUnits( textures, n ); - minStartTime = clip.tracks[ i ].times[ 0 ]; + if ( ! arraysEqual( cache, units ) ) { - } + gl.uniform1iv( this.addr, units ); - } + copyArray( cache, units ); - // shift all tracks such that clip begins at t=0 + } - for ( let i = 0; i < clip.tracks.length; ++ i ) { + for ( let i = 0; i !== n; ++ i ) { - clip.tracks[ i ].shift( - 1 * minStartTime ); + textures.setTexture3D( v[ i ] || empty3dTexture, units[ i ] ); } - clip.resetDuration(); - - return clip; - } -function makeClipAdditive( targetClip, referenceFrame = 0, referenceClip = targetClip, fps = 30 ) { - - if ( fps <= 0 ) fps = 30; - - const numTracks = referenceClip.tracks.length; - const referenceTime = referenceFrame / fps; - - // Make each track's values relative to the values at the reference frame - for ( let i = 0; i < numTracks; ++ i ) { - - const referenceTrack = referenceClip.tracks[ i ]; - const referenceTrackType = referenceTrack.ValueTypeName; - - // Skip this track if it's non-numeric - if ( referenceTrackType === 'bool' || referenceTrackType === 'string' ) continue; +function setValueT6Array( gl, v, textures ) { - // Find the track in the target clip whose name and type matches the reference track - const targetTrack = targetClip.tracks.find( function ( track ) { + const cache = this.cache; - return track.name === referenceTrack.name - && track.ValueTypeName === referenceTrackType; + const n = v.length; - } ); + const units = allocTexUnits( textures, n ); - if ( targetTrack === undefined ) continue; + if ( ! arraysEqual( cache, units ) ) { - let referenceOffset = 0; - const referenceValueSize = referenceTrack.getValueSize(); + gl.uniform1iv( this.addr, units ); - if ( referenceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) { + copyArray( cache, units ); - referenceOffset = referenceValueSize / 3; + } - } + for ( let i = 0; i !== n; ++ i ) { - let targetOffset = 0; - const targetValueSize = targetTrack.getValueSize(); + textures.setTextureCube( v[ i ] || emptyCubeTexture, units[ i ] ); - if ( targetTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) { + } - targetOffset = targetValueSize / 3; +} - } +function setValueT2DArrayArray( gl, v, textures ) { - const lastIndex = referenceTrack.times.length - 1; - let referenceValue; + const cache = this.cache; - // Find the value to subtract out of the track - if ( referenceTime <= referenceTrack.times[ 0 ] ) { + const n = v.length; - // Reference frame is earlier than the first keyframe, so just use the first keyframe - const startIndex = referenceOffset; - const endIndex = referenceValueSize - referenceOffset; - referenceValue = arraySlice( referenceTrack.values, startIndex, endIndex ); + const units = allocTexUnits( textures, n ); - } else if ( referenceTime >= referenceTrack.times[ lastIndex ] ) { + if ( ! arraysEqual( cache, units ) ) { - // Reference frame is after the last keyframe, so just use the last keyframe - const startIndex = lastIndex * referenceValueSize + referenceOffset; - const endIndex = startIndex + referenceValueSize - referenceOffset; - referenceValue = arraySlice( referenceTrack.values, startIndex, endIndex ); + gl.uniform1iv( this.addr, units ); - } else { + copyArray( cache, units ); - // Interpolate to the reference value - const interpolant = referenceTrack.createInterpolant(); - const startIndex = referenceOffset; - const endIndex = referenceValueSize - referenceOffset; - interpolant.evaluate( referenceTime ); - referenceValue = arraySlice( interpolant.resultBuffer, startIndex, endIndex ); + } - } + for ( let i = 0; i !== n; ++ i ) { - // Conjugate the quaternion - if ( referenceTrackType === 'quaternion' ) { + textures.setTexture2DArray( v[ i ] || emptyArrayTexture, units[ i ] ); - const referenceQuat = new Quaternion().fromArray( referenceValue ).normalize().conjugate(); - referenceQuat.toArray( referenceValue ); + } - } +} - // Subtract the reference value from all of the track values - const numTimes = targetTrack.times.length; - for ( let j = 0; j < numTimes; ++ j ) { +// Helper to pick the right setter for a pure (bottom-level) array - const valueStart = j * targetValueSize + targetOffset; +function getPureArraySetter( type ) { - if ( referenceTrackType === 'quaternion' ) { + switch ( type ) { - // Multiply the conjugate for quaternion track types - Quaternion.multiplyQuaternionsFlat( - targetTrack.values, - valueStart, - referenceValue, - 0, - targetTrack.values, - valueStart - ); + case 0x1406: return setValueV1fArray; // FLOAT + case 0x8b50: return setValueV2fArray; // _VEC2 + case 0x8b51: return setValueV3fArray; // _VEC3 + case 0x8b52: return setValueV4fArray; // _VEC4 - } else { + case 0x8b5a: return setValueM2Array; // _MAT2 + case 0x8b5b: return setValueM3Array; // _MAT3 + case 0x8b5c: return setValueM4Array; // _MAT4 - const valueEnd = targetValueSize - targetOffset * 2; + case 0x1404: case 0x8b56: return setValueV1iArray; // INT, BOOL + case 0x8b53: case 0x8b57: return setValueV2iArray; // _VEC2 + case 0x8b54: case 0x8b58: return setValueV3iArray; // _VEC3 + case 0x8b55: case 0x8b59: return setValueV4iArray; // _VEC4 - // Subtract each value for all other numeric track types - for ( let k = 0; k < valueEnd; ++ k ) { + case 0x1405: return setValueV1uiArray; // UINT + case 0x8dc6: return setValueV2uiArray; // _VEC2 + case 0x8dc7: return setValueV3uiArray; // _VEC3 + case 0x8dc8: return setValueV4uiArray; // _VEC4 - targetTrack.values[ valueStart + k ] -= referenceValue[ k ]; + case 0x8b5e: // SAMPLER_2D + case 0x8d66: // SAMPLER_EXTERNAL_OES + case 0x8dca: // INT_SAMPLER_2D + case 0x8dd2: // UNSIGNED_INT_SAMPLER_2D + case 0x8b62: // SAMPLER_2D_SHADOW + return setValueT1Array; - } + case 0x8b5f: // SAMPLER_3D + case 0x8dcb: // INT_SAMPLER_3D + case 0x8dd3: // UNSIGNED_INT_SAMPLER_3D + return setValueT3DArray; - } + case 0x8b60: // SAMPLER_CUBE + case 0x8dcc: // INT_SAMPLER_CUBE + case 0x8dd4: // UNSIGNED_INT_SAMPLER_CUBE + case 0x8dc5: // SAMPLER_CUBE_SHADOW + return setValueT6Array; - } + case 0x8dc1: // SAMPLER_2D_ARRAY + case 0x8dcf: // INT_SAMPLER_2D_ARRAY + case 0x8dd7: // UNSIGNED_INT_SAMPLER_2D_ARRAY + case 0x8dc4: // SAMPLER_2D_ARRAY_SHADOW + return setValueT2DArrayArray; } - targetClip.blendMode = AdditiveAnimationBlendMode; - - return targetClip; - } -const AnimationUtils = { - arraySlice: arraySlice, - convertArray: convertArray, - isTypedArray: isTypedArray, - getKeyframeOrder: getKeyframeOrder, - sortedArray: sortedArray, - flattenJSON: flattenJSON, - subclip: subclip, - makeClipAdditive: makeClipAdditive -}; - -/** - * Abstract base class of interpolants over parametric samples. - * - * The parameter domain is one dimensional, typically the time or a path - * along a curve defined by the data. - * - * The sample values can have any dimensionality and derived classes may - * apply special interpretations to the data. - * - * This class provides the interval seek in a Template Method, deferring - * the actual interpolation to derived classes. - * - * Time complexity is O(1) for linear access crossing at most two points - * and O(log N) for random access, where N is the number of positions. - * - * References: - * - * http://www.oodesign.com/template-method-pattern.html - * - */ - -class Interpolant { +// --- Uniform Classes --- - constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { +class SingleUniform { - this.parameterPositions = parameterPositions; - this._cachedIndex = 0; + constructor( id, activeInfo, addr ) { - this.resultBuffer = resultBuffer !== undefined ? - resultBuffer : new sampleValues.constructor( sampleSize ); - this.sampleValues = sampleValues; - this.valueSize = sampleSize; + this.id = id; + this.addr = addr; + this.cache = []; + this.type = activeInfo.type; + this.setValue = getSingularSetter( activeInfo.type ); - this.settings = null; - this.DefaultSettings_ = {}; + // this.path = activeInfo.name; // DEBUG } - evaluate( t ) { - - const pp = this.parameterPositions; - let i1 = this._cachedIndex, - t1 = pp[ i1 ], - t0 = pp[ i1 - 1 ]; - - validate_interval: { - - seek: { - - let right; - - linear_scan: { - - //- See http://jsperf.com/comparison-to-undefined/3 - //- slower code: - //- - //- if ( t >= t1 || t1 === undefined ) { - forward_scan: if ( ! ( t < t1 ) ) { +} - for ( let giveUpAt = i1 + 2; ; ) { +class PureArrayUniform { - if ( t1 === undefined ) { + constructor( id, activeInfo, addr ) { - if ( t < t0 ) break forward_scan; + this.id = id; + this.addr = addr; + this.cache = []; + this.type = activeInfo.type; + this.size = activeInfo.size; + this.setValue = getPureArraySetter( activeInfo.type ); - // after end + // this.path = activeInfo.name; // DEBUG - i1 = pp.length; - this._cachedIndex = i1; - return this.copySampleValue_( i1 - 1 ); + } - } +} - if ( i1 === giveUpAt ) break; // this loop +class StructuredUniform { - t0 = t1; - t1 = pp[ ++ i1 ]; + constructor( id ) { - if ( t < t1 ) { + this.id = id; - // we have arrived at the sought interval - break seek; + this.seq = []; + this.map = {}; - } + } - } + setValue( gl, value, textures ) { - // prepare binary search on the right side of the index - right = pp.length; - break linear_scan; + const seq = this.seq; - } + for ( let i = 0, n = seq.length; i !== n; ++ i ) { - //- slower code: - //- if ( t < t0 || t0 === undefined ) { - if ( ! ( t >= t0 ) ) { + const u = seq[ i ]; + u.setValue( gl, value[ u.id ], textures ); - // looping? + } - const t1global = pp[ 1 ]; + } - if ( t < t1global ) { +} - i1 = 2; // + 1, using the scan for the details - t0 = t1global; +// --- Top-level --- - } +// Parser - builds up the property tree from the path strings - // linear reverse scan +const RePathPart = /(\w+)(\])?(\[|\.)?/g; - for ( let giveUpAt = i1 - 2; ; ) { +// extracts +// - the identifier (member name or array index) +// - followed by an optional right bracket (found when array index) +// - followed by an optional left bracket or dot (type of subscript) +// +// Note: These portions can be read in a non-overlapping fashion and +// allow straightforward parsing of the hierarchy that WebGL encodes +// in the uniform names. - if ( t0 === undefined ) { +function addUniform( container, uniformObject ) { - // before start + container.seq.push( uniformObject ); + container.map[ uniformObject.id ] = uniformObject; - this._cachedIndex = 0; - return this.copySampleValue_( 0 ); +} - } +function parseUniform( activeInfo, addr, container ) { - if ( i1 === giveUpAt ) break; // this loop + const path = activeInfo.name, + pathLength = path.length; - t1 = t0; - t0 = pp[ -- i1 - 1 ]; + // reset RegExp object, because of the early exit of a previous run + RePathPart.lastIndex = 0; - if ( t >= t0 ) { + while ( true ) { - // we have arrived at the sought interval - break seek; + const match = RePathPart.exec( path ), + matchEnd = RePathPart.lastIndex; - } + let id = match[ 1 ]; + const idIsIndex = match[ 2 ] === ']', + subscript = match[ 3 ]; - } + if ( idIsIndex ) id = id | 0; // convert to integer - // prepare binary search on the left side of the index - right = i1; - i1 = 0; - break linear_scan; + if ( subscript === undefined || subscript === '[' && matchEnd + 2 === pathLength ) { - } + // bare name or "pure" bottom-level array "[0]" suffix - // the interval is valid + addUniform( container, subscript === undefined ? + new SingleUniform( id, activeInfo, addr ) : + new PureArrayUniform( id, activeInfo, addr ) ); - break validate_interval; + break; - } // linear scan + } else { - // binary search + // step into inner node / create it in case it doesn't exist - while ( i1 < right ) { + const map = container.map; + let next = map[ id ]; - const mid = ( i1 + right ) >>> 1; + if ( next === undefined ) { - if ( t < pp[ mid ] ) { + next = new StructuredUniform( id ); + addUniform( container, next ); - right = mid; + } - } else { + container = next; - i1 = mid + 1; + } - } + } - } +} - t1 = pp[ i1 ]; - t0 = pp[ i1 - 1 ]; +// Root Container - // check boundary cases, again +class WebGLUniforms { - if ( t0 === undefined ) { + constructor( gl, program ) { - this._cachedIndex = 0; - return this.copySampleValue_( 0 ); + this.seq = []; + this.map = {}; - } + const n = gl.getProgramParameter( program, gl.ACTIVE_UNIFORMS ); - if ( t1 === undefined ) { + for ( let i = 0; i < n; ++ i ) { - i1 = pp.length; - this._cachedIndex = i1; - return this.copySampleValue_( i1 - 1 ); + const info = gl.getActiveUniform( program, i ), + addr = gl.getUniformLocation( program, info.name ); - } + parseUniform( info, addr, this ); - } // seek + } - this._cachedIndex = i1; + } - this.intervalChanged_( i1, t0, t1 ); + setValue( gl, name, value, textures ) { - } // validate_interval + const u = this.map[ name ]; - return this.interpolate_( i1, t0, t, t1 ); + if ( u !== undefined ) u.setValue( gl, value, textures ); } - getSettings_() { + setOptional( gl, object, name ) { - return this.settings || this.DefaultSettings_; + const v = object[ name ]; + + if ( v !== undefined ) this.setValue( gl, name, v ); } - copySampleValue_( index ) { + static upload( gl, seq, values, textures ) { - // copies a sample value to the result buffer + for ( let i = 0, n = seq.length; i !== n; ++ i ) { - const result = this.resultBuffer, - values = this.sampleValues, - stride = this.valueSize, - offset = index * stride; + const u = seq[ i ], + v = values[ u.id ]; - for ( let i = 0; i !== stride; ++ i ) { + if ( v.needsUpdate !== false ) { - result[ i ] = values[ offset + i ]; + // note: always updating when .needsUpdate is undefined + u.setValue( gl, v.value, textures ); - } + } - return result; + } } - // Template methods for derived classes: + static seqWithValue( seq, values ) { - interpolate_( /* i1, t0, t, t1 */ ) { + const r = []; - throw new Error( 'call to abstract method' ); - // implementations shall return this.resultBuffer + for ( let i = 0, n = seq.length; i !== n; ++ i ) { - } + const u = seq[ i ]; + if ( u.id in values ) r.push( u ); - intervalChanged_( /* i1, t0, t1 */ ) { + } - // empty + return r; } } -/** - * Fast and simple cubic spline interpolant. - * - * It was derived from a Hermitian construction setting the first derivative - * at each sample position to the linear slope between neighboring positions - * over their parameter interval. - */ - -class CubicInterpolant extends Interpolant { +function WebGLShader( gl, type, string ) { - constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { + const shader = gl.createShader( type ); - super( parameterPositions, sampleValues, sampleSize, resultBuffer ); + gl.shaderSource( shader, string ); + gl.compileShader( shader ); - this._weightPrev = - 0; - this._offsetPrev = - 0; - this._weightNext = - 0; - this._offsetNext = - 0; + return shader; - this.DefaultSettings_ = { +} - endingStart: ZeroCurvatureEnding, - endingEnd: ZeroCurvatureEnding +// From https://www.khronos.org/registry/webgl/extensions/KHR_parallel_shader_compile/ +const COMPLETION_STATUS_KHR = 0x91B1; - }; +let programIdCount = 0; - } +function handleSource( string, errorLine ) { - intervalChanged_( i1, t0, t1 ) { + const lines = string.split( '\n' ); + const lines2 = []; - const pp = this.parameterPositions; - let iPrev = i1 - 2, - iNext = i1 + 1, + const from = Math.max( errorLine - 6, 0 ); + const to = Math.min( errorLine + 6, lines.length ); - tPrev = pp[ iPrev ], - tNext = pp[ iNext ]; + for ( let i = from; i < to; i ++ ) { - if ( tPrev === undefined ) { + const line = i + 1; + lines2.push( `${line === errorLine ? '>' : ' '} ${line}: ${lines[ i ]}` ); - switch ( this.getSettings_().endingStart ) { + } - case ZeroSlopeEnding: + return lines2.join( '\n' ); - // f'(t0) = 0 - iPrev = i1; - tPrev = 2 * t0 - t1; +} - break; +const _m0 = /*@__PURE__*/ new Matrix3(); - case WrapAroundEnding: +function getEncodingComponents( colorSpace ) { - // use the other end of the curve - iPrev = pp.length - 2; - tPrev = t0 + pp[ iPrev ] - pp[ iPrev + 1 ]; + ColorManagement._getMatrix( _m0, ColorManagement.workingColorSpace, colorSpace ); - break; + const encodingMatrix = `mat3( ${ _m0.elements.map( ( v ) => v.toFixed( 4 ) ) } )`; - default: // ZeroCurvatureEnding + switch ( ColorManagement.getTransfer( colorSpace ) ) { - // f''(t0) = 0 a.k.a. Natural Spline - iPrev = i1; - tPrev = t1; + case LinearTransfer: + return [ encodingMatrix, 'LinearTransferOETF' ]; - } + case SRGBTransfer: + return [ encodingMatrix, 'sRGBTransferOETF' ]; - } + default: + console.warn( 'THREE.WebGLProgram: Unsupported color space: ', colorSpace ); + return [ encodingMatrix, 'LinearTransferOETF' ]; - if ( tNext === undefined ) { + } - switch ( this.getSettings_().endingEnd ) { +} - case ZeroSlopeEnding: +function getShaderErrors( gl, shader, type ) { - // f'(tN) = 0 - iNext = i1; - tNext = 2 * t1 - t0; + const status = gl.getShaderParameter( shader, gl.COMPILE_STATUS ); + const errors = gl.getShaderInfoLog( shader ).trim(); - break; + if ( status && errors === '' ) return ''; - case WrapAroundEnding: + const errorMatches = /ERROR: 0:(\d+)/.exec( errors ); + if ( errorMatches ) { - // use the other end of the curve - iNext = 1; - tNext = t1 + pp[ 1 ] - pp[ 0 ]; + // --enable-privileged-webgl-extension + // console.log( '**' + type + '**', gl.getExtension( 'WEBGL_debug_shaders' ).getTranslatedShaderSource( shader ) ); - break; + const errorLine = parseInt( errorMatches[ 1 ] ); + return type.toUpperCase() + '\n\n' + errors + '\n\n' + handleSource( gl.getShaderSource( shader ), errorLine ); - default: // ZeroCurvatureEnding + } else { - // f''(tN) = 0, a.k.a. Natural Spline - iNext = i1 - 1; - tNext = t0; + return errors; - } + } - } +} - const halfDt = ( t1 - t0 ) * 0.5, - stride = this.valueSize; +function getTexelEncodingFunction( functionName, colorSpace ) { - this._weightPrev = halfDt / ( t0 - tPrev ); - this._weightNext = halfDt / ( tNext - t1 ); - this._offsetPrev = iPrev * stride; - this._offsetNext = iNext * stride; + const components = getEncodingComponents( colorSpace ); - } + return [ - interpolate_( i1, t0, t, t1 ) { + `vec4 ${functionName}( vec4 value ) {`, - const result = this.resultBuffer, - values = this.sampleValues, - stride = this.valueSize, + ` return ${components[ 1 ]}( vec4( value.rgb * ${components[ 0 ]}, value.a ) );`, - o1 = i1 * stride, o0 = o1 - stride, - oP = this._offsetPrev, oN = this._offsetNext, - wP = this._weightPrev, wN = this._weightNext, + '}', - p = ( t - t0 ) / ( t1 - t0 ), - pp = p * p, - ppp = pp * p; + ].join( '\n' ); - // evaluate polynomials +} - const sP = - wP * ppp + 2 * wP * pp - wP * p; - const s0 = ( 1 + wP ) * ppp + ( - 1.5 - 2 * wP ) * pp + ( - 0.5 + wP ) * p + 1; - const s1 = ( - 1 - wN ) * ppp + ( 1.5 + wN ) * pp + 0.5 * p; - const sN = wN * ppp - wN * pp; +function getToneMappingFunction( functionName, toneMapping ) { - // combine data linearly + let toneMappingName; - for ( let i = 0; i !== stride; ++ i ) { + switch ( toneMapping ) { - result[ i ] = - sP * values[ oP + i ] + - s0 * values[ o0 + i ] + - s1 * values[ o1 + i ] + - sN * values[ oN + i ]; + case LinearToneMapping: + toneMappingName = 'Linear'; + break; - } + case ReinhardToneMapping: + toneMappingName = 'Reinhard'; + break; - return result; + case CineonToneMapping: + toneMappingName = 'Cineon'; + break; - } + case ACESFilmicToneMapping: + toneMappingName = 'ACESFilmic'; + break; -} + case AgXToneMapping: + toneMappingName = 'AgX'; + break; -class LinearInterpolant extends Interpolant { + case NeutralToneMapping: + toneMappingName = 'Neutral'; + break; - constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { + case CustomToneMapping: + toneMappingName = 'Custom'; + break; - super( parameterPositions, sampleValues, sampleSize, resultBuffer ); + default: + console.warn( 'THREE.WebGLProgram: Unsupported toneMapping:', toneMapping ); + toneMappingName = 'Linear'; } - interpolate_( i1, t0, t, t1 ) { - - const result = this.resultBuffer, - values = this.sampleValues, - stride = this.valueSize, - - offset1 = i1 * stride, - offset0 = offset1 - stride, + return 'vec3 ' + functionName + '( vec3 color ) { return ' + toneMappingName + 'ToneMapping( color ); }'; - weight1 = ( t - t0 ) / ( t1 - t0 ), - weight0 = 1 - weight1; +} - for ( let i = 0; i !== stride; ++ i ) { +const _v0 = /*@__PURE__*/ new Vector3(); - result[ i ] = - values[ offset0 + i ] * weight0 + - values[ offset1 + i ] * weight1; +function getLuminanceFunction() { - } + ColorManagement.getLuminanceCoefficients( _v0 ); - return result; + const r = _v0.x.toFixed( 4 ); + const g = _v0.y.toFixed( 4 ); + const b = _v0.z.toFixed( 4 ); - } + return [ -} + 'float luminance( const in vec3 rgb ) {', -/** - * - * Interpolant that evaluates to the sample value at the position preceding - * the parameter. - */ + ` const vec3 weights = vec3( ${ r }, ${ g }, ${ b } );`, -class DiscreteInterpolant extends Interpolant { + ' return dot( weights, rgb );', - constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { + '}' - super( parameterPositions, sampleValues, sampleSize, resultBuffer ); + ].join( '\n' ); - } +} - interpolate_( i1 /*, t0, t, t1 */ ) { +function generateVertexExtensions( parameters ) { - return this.copySampleValue_( i1 - 1 ); + const chunks = [ + parameters.extensionClipCullDistance ? '#extension GL_ANGLE_clip_cull_distance : require' : '', + parameters.extensionMultiDraw ? '#extension GL_ANGLE_multi_draw : require' : '', + ]; - } + return chunks.filter( filterEmptyLine ).join( '\n' ); } -class KeyframeTrack { +function generateDefines( defines ) { - constructor( name, times, values, interpolation ) { + const chunks = []; - if ( name === undefined ) throw new Error( 'THREE.KeyframeTrack: track name is undefined' ); - if ( times === undefined || times.length === 0 ) throw new Error( 'THREE.KeyframeTrack: no keyframes in track named ' + name ); + for ( const name in defines ) { - this.name = name; + const value = defines[ name ]; - this.times = convertArray( times, this.TimeBufferType ); - this.values = convertArray( values, this.ValueBufferType ); + if ( value === false ) continue; - this.setInterpolation( interpolation || this.DefaultInterpolation ); + chunks.push( '#define ' + name + ' ' + value ); } - // Serialization (in static context, because of constructor invocation - // and automatic invocation of .toJSON): - - static toJSON( track ) { - - const trackType = track.constructor; + return chunks.join( '\n' ); - let json; +} - // derived classes can define a static toJSON method - if ( trackType.toJSON !== this.toJSON ) { +function fetchAttributeLocations( gl, program ) { - json = trackType.toJSON( track ); + const attributes = {}; - } else { + const n = gl.getProgramParameter( program, gl.ACTIVE_ATTRIBUTES ); - // by default, we assume the data can be serialized as-is - json = { + for ( let i = 0; i < n; i ++ ) { - 'name': track.name, - 'times': convertArray( track.times, Array ), - 'values': convertArray( track.values, Array ) + const info = gl.getActiveAttrib( program, i ); + const name = info.name; - }; + let locationSize = 1; + if ( info.type === gl.FLOAT_MAT2 ) locationSize = 2; + if ( info.type === gl.FLOAT_MAT3 ) locationSize = 3; + if ( info.type === gl.FLOAT_MAT4 ) locationSize = 4; - const interpolation = track.getInterpolation(); + // console.log( 'THREE.WebGLProgram: ACTIVE VERTEX ATTRIBUTE:', name, i ); - if ( interpolation !== track.DefaultInterpolation ) { + attributes[ name ] = { + type: info.type, + location: gl.getAttribLocation( program, name ), + locationSize: locationSize + }; - json.interpolation = interpolation; + } - } + return attributes; - } +} - json.type = track.ValueTypeName; // mandatory +function filterEmptyLine( string ) { - return json; + return string !== ''; - } +} - InterpolantFactoryMethodDiscrete( result ) { +function replaceLightNums( string, parameters ) { - return new DiscreteInterpolant( this.times, this.values, this.getValueSize(), result ); + const numSpotLightCoords = parameters.numSpotLightShadows + parameters.numSpotLightMaps - parameters.numSpotLightShadowsWithMaps; - } + return string + .replace( /NUM_DIR_LIGHTS/g, parameters.numDirLights ) + .replace( /NUM_SPOT_LIGHTS/g, parameters.numSpotLights ) + .replace( /NUM_SPOT_LIGHT_MAPS/g, parameters.numSpotLightMaps ) + .replace( /NUM_SPOT_LIGHT_COORDS/g, numSpotLightCoords ) + .replace( /NUM_RECT_AREA_LIGHTS/g, parameters.numRectAreaLights ) + .replace( /NUM_POINT_LIGHTS/g, parameters.numPointLights ) + .replace( /NUM_HEMI_LIGHTS/g, parameters.numHemiLights ) + .replace( /NUM_DIR_LIGHT_SHADOWS/g, parameters.numDirLightShadows ) + .replace( /NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS/g, parameters.numSpotLightShadowsWithMaps ) + .replace( /NUM_SPOT_LIGHT_SHADOWS/g, parameters.numSpotLightShadows ) + .replace( /NUM_POINT_LIGHT_SHADOWS/g, parameters.numPointLightShadows ); - InterpolantFactoryMethodLinear( result ) { +} - return new LinearInterpolant( this.times, this.values, this.getValueSize(), result ); +function replaceClippingPlaneNums( string, parameters ) { - } + return string + .replace( /NUM_CLIPPING_PLANES/g, parameters.numClippingPlanes ) + .replace( /UNION_CLIPPING_PLANES/g, ( parameters.numClippingPlanes - parameters.numClipIntersection ) ); - InterpolantFactoryMethodSmooth( result ) { +} - return new CubicInterpolant( this.times, this.values, this.getValueSize(), result ); +// Resolve Includes - } +const includePattern = /^[ \t]*#include +<([\w\d./]+)>/gm; - setInterpolation( interpolation ) { +function resolveIncludes( string ) { - let factoryMethod; + return string.replace( includePattern, includeReplacer ); - switch ( interpolation ) { +} - case InterpolateDiscrete: +const shaderChunkMap = new Map(); - factoryMethod = this.InterpolantFactoryMethodDiscrete; +function includeReplacer( match, include ) { - break; + let string = ShaderChunk[ include ]; - case InterpolateLinear: + if ( string === undefined ) { - factoryMethod = this.InterpolantFactoryMethodLinear; + const newInclude = shaderChunkMap.get( include ); - break; + if ( newInclude !== undefined ) { - case InterpolateSmooth: + string = ShaderChunk[ newInclude ]; + console.warn( 'THREE.WebGLRenderer: Shader chunk "%s" has been deprecated. Use "%s" instead.', include, newInclude ); - factoryMethod = this.InterpolantFactoryMethodSmooth; + } else { - break; + throw new Error( 'Can not resolve #include <' + include + '>' ); } - if ( factoryMethod === undefined ) { - - const message = 'unsupported interpolation for ' + - this.ValueTypeName + ' keyframe track named ' + this.name; + } - if ( this.createInterpolant === undefined ) { + return resolveIncludes( string ); - // fall back to default, unless the default itself is messed up - if ( interpolation !== this.DefaultInterpolation ) { +} - this.setInterpolation( this.DefaultInterpolation ); +// Unroll Loops - } else { +const unrollLoopPattern = /#pragma unroll_loop_start\s+for\s*\(\s*int\s+i\s*=\s*(\d+)\s*;\s*i\s*<\s*(\d+)\s*;\s*i\s*\+\+\s*\)\s*{([\s\S]+?)}\s+#pragma unroll_loop_end/g; - throw new Error( message ); // fatal, in this case +function unrollLoops( string ) { - } + return string.replace( unrollLoopPattern, loopReplacer ); - } +} - console.warn( 'THREE.KeyframeTrack:', message ); - return this; +function loopReplacer( match, start, end, snippet ) { - } + let string = ''; - this.createInterpolant = factoryMethod; + for ( let i = parseInt( start ); i < parseInt( end ); i ++ ) { - return this; + string += snippet + .replace( /\[\s*i\s*\]/g, '[ ' + i + ' ]' ) + .replace( /UNROLLED_LOOP_INDEX/g, i ); } - getInterpolation() { - - switch ( this.createInterpolant ) { + return string; - case this.InterpolantFactoryMethodDiscrete: +} - return InterpolateDiscrete; +// - case this.InterpolantFactoryMethodLinear: +function generatePrecision( parameters ) { - return InterpolateLinear; + let precisionstring = `precision ${parameters.precision} float; + precision ${parameters.precision} int; + precision ${parameters.precision} sampler2D; + precision ${parameters.precision} samplerCube; + precision ${parameters.precision} sampler3D; + precision ${parameters.precision} sampler2DArray; + precision ${parameters.precision} sampler2DShadow; + precision ${parameters.precision} samplerCubeShadow; + precision ${parameters.precision} sampler2DArrayShadow; + precision ${parameters.precision} isampler2D; + precision ${parameters.precision} isampler3D; + precision ${parameters.precision} isamplerCube; + precision ${parameters.precision} isampler2DArray; + precision ${parameters.precision} usampler2D; + precision ${parameters.precision} usampler3D; + precision ${parameters.precision} usamplerCube; + precision ${parameters.precision} usampler2DArray; + `; - case this.InterpolantFactoryMethodSmooth: + if ( parameters.precision === 'highp' ) { - return InterpolateSmooth; + precisionstring += '\n#define HIGH_PRECISION'; - } + } else if ( parameters.precision === 'mediump' ) { - } + precisionstring += '\n#define MEDIUM_PRECISION'; - getValueSize() { + } else if ( parameters.precision === 'lowp' ) { - return this.values.length / this.times.length; + precisionstring += '\n#define LOW_PRECISION'; } - // move all keyframes either forwards or backwards in time - shift( timeOffset ) { + return precisionstring; - if ( timeOffset !== 0.0 ) { +} - const times = this.times; +function generateShadowMapTypeDefine( parameters ) { - for ( let i = 0, n = times.length; i !== n; ++ i ) { + let shadowMapTypeDefine = 'SHADOWMAP_TYPE_BASIC'; - times[ i ] += timeOffset; + if ( parameters.shadowMapType === PCFShadowMap ) { - } + shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF'; - } + } else if ( parameters.shadowMapType === PCFSoftShadowMap ) { - return this; + shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF_SOFT'; + + } else if ( parameters.shadowMapType === VSMShadowMap ) { + + shadowMapTypeDefine = 'SHADOWMAP_TYPE_VSM'; } - // scale all keyframe times by a factor (useful for frame <-> seconds conversions) - scale( timeScale ) { + return shadowMapTypeDefine; - if ( timeScale !== 1.0 ) { +} - const times = this.times; +function generateEnvMapTypeDefine( parameters ) { - for ( let i = 0, n = times.length; i !== n; ++ i ) { + let envMapTypeDefine = 'ENVMAP_TYPE_CUBE'; - times[ i ] *= timeScale; + if ( parameters.envMap ) { - } + switch ( parameters.envMapMode ) { - } + case CubeReflectionMapping: + case CubeRefractionMapping: + envMapTypeDefine = 'ENVMAP_TYPE_CUBE'; + break; - return this; + case CubeUVReflectionMapping: + envMapTypeDefine = 'ENVMAP_TYPE_CUBE_UV'; + break; + + } } - // removes keyframes before and after animation without changing any values within the range [startTime, endTime]. - // IMPORTANT: We do not shift around keys to the start of the track time, because for interpolated keys this will change their values - trim( startTime, endTime ) { + return envMapTypeDefine; - const times = this.times, - nKeys = times.length; +} - let from = 0, - to = nKeys - 1; +function generateEnvMapModeDefine( parameters ) { - while ( from !== nKeys && times[ from ] < startTime ) { + let envMapModeDefine = 'ENVMAP_MODE_REFLECTION'; - ++ from; + if ( parameters.envMap ) { - } + switch ( parameters.envMapMode ) { - while ( to !== - 1 && times[ to ] > endTime ) { + case CubeRefractionMapping: - -- to; + envMapModeDefine = 'ENVMAP_MODE_REFRACTION'; + break; } - ++ to; // inclusive -> exclusive bound + } - if ( from !== 0 || to !== nKeys ) { + return envMapModeDefine; - // empty tracks are forbidden, so keep at least one keyframe - if ( from >= to ) { +} - to = Math.max( to, 1 ); - from = to - 1; +function generateEnvMapBlendingDefine( parameters ) { - } + let envMapBlendingDefine = 'ENVMAP_BLENDING_NONE'; - const stride = this.getValueSize(); - this.times = arraySlice( times, from, to ); - this.values = arraySlice( this.values, from * stride, to * stride ); + if ( parameters.envMap ) { - } + switch ( parameters.combine ) { - return this; + case MultiplyOperation: + envMapBlendingDefine = 'ENVMAP_BLENDING_MULTIPLY'; + break; - } + case MixOperation: + envMapBlendingDefine = 'ENVMAP_BLENDING_MIX'; + break; - // ensure we do not get a GarbageInGarbageOut situation, make sure tracks are at least minimally viable - validate() { + case AddOperation: + envMapBlendingDefine = 'ENVMAP_BLENDING_ADD'; + break; - let valid = true; + } - const valueSize = this.getValueSize(); - if ( valueSize - Math.floor( valueSize ) !== 0 ) { + } - console.error( 'THREE.KeyframeTrack: Invalid value size in track.', this ); - valid = false; + return envMapBlendingDefine; - } +} - const times = this.times, - values = this.values, +function generateCubeUVSize( parameters ) { - nKeys = times.length; + const imageHeight = parameters.envMapCubeUVHeight; - if ( nKeys === 0 ) { + if ( imageHeight === null ) return null; - console.error( 'THREE.KeyframeTrack: Track is empty.', this ); - valid = false; + const maxMip = Math.log2( imageHeight ) - 2; - } + const texelHeight = 1.0 / imageHeight; - let prevTime = null; + const texelWidth = 1.0 / ( 3 * Math.max( Math.pow( 2, maxMip ), 7 * 16 ) ); - for ( let i = 0; i !== nKeys; i ++ ) { + return { texelWidth, texelHeight, maxMip }; - const currTime = times[ i ]; +} - if ( typeof currTime === 'number' && isNaN( currTime ) ) { +function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) { - console.error( 'THREE.KeyframeTrack: Time is not a valid number.', this, i, currTime ); - valid = false; - break; + // TODO Send this event to Three.js DevTools + // console.log( 'WebGLProgram', cacheKey ); - } + const gl = renderer.getContext(); - if ( prevTime !== null && prevTime > currTime ) { + const defines = parameters.defines; - console.error( 'THREE.KeyframeTrack: Out of order keys.', this, i, currTime, prevTime ); - valid = false; - break; + let vertexShader = parameters.vertexShader; + let fragmentShader = parameters.fragmentShader; - } + const shadowMapTypeDefine = generateShadowMapTypeDefine( parameters ); + const envMapTypeDefine = generateEnvMapTypeDefine( parameters ); + const envMapModeDefine = generateEnvMapModeDefine( parameters ); + const envMapBlendingDefine = generateEnvMapBlendingDefine( parameters ); + const envMapCubeUVSize = generateCubeUVSize( parameters ); - prevTime = currTime; + const customVertexExtensions = generateVertexExtensions( parameters ); - } + const customDefines = generateDefines( defines ); - if ( values !== undefined ) { + const program = gl.createProgram(); - if ( isTypedArray( values ) ) { + let prefixVertex, prefixFragment; + let versionString = parameters.glslVersion ? '#version ' + parameters.glslVersion + '\n' : ''; - for ( let i = 0, n = values.length; i !== n; ++ i ) { + if ( parameters.isRawShaderMaterial ) { - const value = values[ i ]; + prefixVertex = [ - if ( isNaN( value ) ) { + '#define SHADER_TYPE ' + parameters.shaderType, + '#define SHADER_NAME ' + parameters.shaderName, - console.error( 'THREE.KeyframeTrack: Value is not a valid number.', this, i, value ); - valid = false; - break; + customDefines - } + ].filter( filterEmptyLine ).join( '\n' ); - } + if ( prefixVertex.length > 0 ) { - } + prefixVertex += '\n'; } - return valid; - - } + prefixFragment = [ - // removes equivalent sequential keys as common in morph target sequences - // (0,0,0,0,1,1,1,0,0,0,0,0,0,0) --> (0,0,1,1,0,0) - optimize() { + '#define SHADER_TYPE ' + parameters.shaderType, + '#define SHADER_NAME ' + parameters.shaderName, - // times or values may be shared with other tracks, so overwriting is unsafe - const times = arraySlice( this.times ), - values = arraySlice( this.values ), - stride = this.getValueSize(), + customDefines - smoothInterpolation = this.getInterpolation() === InterpolateSmooth, + ].filter( filterEmptyLine ).join( '\n' ); - lastIndex = times.length - 1; + if ( prefixFragment.length > 0 ) { - let writeIndex = 1; + prefixFragment += '\n'; - for ( let i = 1; i < lastIndex; ++ i ) { + } - let keep = false; + } else { - const time = times[ i ]; - const timeNext = times[ i + 1 ]; + prefixVertex = [ - // remove adjacent keyframes scheduled at the same time + generatePrecision( parameters ), - if ( time !== timeNext && ( i !== 1 || time !== times[ 0 ] ) ) { + '#define SHADER_TYPE ' + parameters.shaderType, + '#define SHADER_NAME ' + parameters.shaderName, - if ( ! smoothInterpolation ) { + customDefines, - // remove unnecessary keyframes same as their neighbors + parameters.extensionClipCullDistance ? '#define USE_CLIP_DISTANCE' : '', + parameters.batching ? '#define USE_BATCHING' : '', + parameters.batchingColor ? '#define USE_BATCHING_COLOR' : '', + parameters.instancing ? '#define USE_INSTANCING' : '', + parameters.instancingColor ? '#define USE_INSTANCING_COLOR' : '', + parameters.instancingMorph ? '#define USE_INSTANCING_MORPH' : '', - const offset = i * stride, - offsetP = offset - stride, - offsetN = offset + stride; + parameters.useFog && parameters.fog ? '#define USE_FOG' : '', + parameters.useFog && parameters.fogExp2 ? '#define FOG_EXP2' : '', - for ( let j = 0; j !== stride; ++ j ) { + parameters.map ? '#define USE_MAP' : '', + parameters.envMap ? '#define USE_ENVMAP' : '', + parameters.envMap ? '#define ' + envMapModeDefine : '', + parameters.lightMap ? '#define USE_LIGHTMAP' : '', + parameters.aoMap ? '#define USE_AOMAP' : '', + parameters.bumpMap ? '#define USE_BUMPMAP' : '', + parameters.normalMap ? '#define USE_NORMALMAP' : '', + parameters.normalMapObjectSpace ? '#define USE_NORMALMAP_OBJECTSPACE' : '', + parameters.normalMapTangentSpace ? '#define USE_NORMALMAP_TANGENTSPACE' : '', + parameters.displacementMap ? '#define USE_DISPLACEMENTMAP' : '', + parameters.emissiveMap ? '#define USE_EMISSIVEMAP' : '', - const value = values[ offset + j ]; + parameters.anisotropy ? '#define USE_ANISOTROPY' : '', + parameters.anisotropyMap ? '#define USE_ANISOTROPYMAP' : '', - if ( value !== values[ offsetP + j ] || - value !== values[ offsetN + j ] ) { + parameters.clearcoatMap ? '#define USE_CLEARCOATMAP' : '', + parameters.clearcoatRoughnessMap ? '#define USE_CLEARCOAT_ROUGHNESSMAP' : '', + parameters.clearcoatNormalMap ? '#define USE_CLEARCOAT_NORMALMAP' : '', - keep = true; - break; + parameters.iridescenceMap ? '#define USE_IRIDESCENCEMAP' : '', + parameters.iridescenceThicknessMap ? '#define USE_IRIDESCENCE_THICKNESSMAP' : '', - } + parameters.specularMap ? '#define USE_SPECULARMAP' : '', + parameters.specularColorMap ? '#define USE_SPECULAR_COLORMAP' : '', + parameters.specularIntensityMap ? '#define USE_SPECULAR_INTENSITYMAP' : '', - } + parameters.roughnessMap ? '#define USE_ROUGHNESSMAP' : '', + parameters.metalnessMap ? '#define USE_METALNESSMAP' : '', + parameters.alphaMap ? '#define USE_ALPHAMAP' : '', + parameters.alphaHash ? '#define USE_ALPHAHASH' : '', - } else { + parameters.transmission ? '#define USE_TRANSMISSION' : '', + parameters.transmissionMap ? '#define USE_TRANSMISSIONMAP' : '', + parameters.thicknessMap ? '#define USE_THICKNESSMAP' : '', - keep = true; + parameters.sheenColorMap ? '#define USE_SHEEN_COLORMAP' : '', + parameters.sheenRoughnessMap ? '#define USE_SHEEN_ROUGHNESSMAP' : '', - } + // - } + parameters.mapUv ? '#define MAP_UV ' + parameters.mapUv : '', + parameters.alphaMapUv ? '#define ALPHAMAP_UV ' + parameters.alphaMapUv : '', + parameters.lightMapUv ? '#define LIGHTMAP_UV ' + parameters.lightMapUv : '', + parameters.aoMapUv ? '#define AOMAP_UV ' + parameters.aoMapUv : '', + parameters.emissiveMapUv ? '#define EMISSIVEMAP_UV ' + parameters.emissiveMapUv : '', + parameters.bumpMapUv ? '#define BUMPMAP_UV ' + parameters.bumpMapUv : '', + parameters.normalMapUv ? '#define NORMALMAP_UV ' + parameters.normalMapUv : '', + parameters.displacementMapUv ? '#define DISPLACEMENTMAP_UV ' + parameters.displacementMapUv : '', - // in-place compaction + parameters.metalnessMapUv ? '#define METALNESSMAP_UV ' + parameters.metalnessMapUv : '', + parameters.roughnessMapUv ? '#define ROUGHNESSMAP_UV ' + parameters.roughnessMapUv : '', - if ( keep ) { + parameters.anisotropyMapUv ? '#define ANISOTROPYMAP_UV ' + parameters.anisotropyMapUv : '', - if ( i !== writeIndex ) { + parameters.clearcoatMapUv ? '#define CLEARCOATMAP_UV ' + parameters.clearcoatMapUv : '', + parameters.clearcoatNormalMapUv ? '#define CLEARCOAT_NORMALMAP_UV ' + parameters.clearcoatNormalMapUv : '', + parameters.clearcoatRoughnessMapUv ? '#define CLEARCOAT_ROUGHNESSMAP_UV ' + parameters.clearcoatRoughnessMapUv : '', - times[ writeIndex ] = times[ i ]; + parameters.iridescenceMapUv ? '#define IRIDESCENCEMAP_UV ' + parameters.iridescenceMapUv : '', + parameters.iridescenceThicknessMapUv ? '#define IRIDESCENCE_THICKNESSMAP_UV ' + parameters.iridescenceThicknessMapUv : '', - const readOffset = i * stride, - writeOffset = writeIndex * stride; + parameters.sheenColorMapUv ? '#define SHEEN_COLORMAP_UV ' + parameters.sheenColorMapUv : '', + parameters.sheenRoughnessMapUv ? '#define SHEEN_ROUGHNESSMAP_UV ' + parameters.sheenRoughnessMapUv : '', - for ( let j = 0; j !== stride; ++ j ) { + parameters.specularMapUv ? '#define SPECULARMAP_UV ' + parameters.specularMapUv : '', + parameters.specularColorMapUv ? '#define SPECULAR_COLORMAP_UV ' + parameters.specularColorMapUv : '', + parameters.specularIntensityMapUv ? '#define SPECULAR_INTENSITYMAP_UV ' + parameters.specularIntensityMapUv : '', - values[ writeOffset + j ] = values[ readOffset + j ]; + parameters.transmissionMapUv ? '#define TRANSMISSIONMAP_UV ' + parameters.transmissionMapUv : '', + parameters.thicknessMapUv ? '#define THICKNESSMAP_UV ' + parameters.thicknessMapUv : '', - } + // - } + parameters.vertexTangents && parameters.flatShading === false ? '#define USE_TANGENT' : '', + parameters.vertexColors ? '#define USE_COLOR' : '', + parameters.vertexAlphas ? '#define USE_COLOR_ALPHA' : '', + parameters.vertexUv1s ? '#define USE_UV1' : '', + parameters.vertexUv2s ? '#define USE_UV2' : '', + parameters.vertexUv3s ? '#define USE_UV3' : '', - ++ writeIndex; + parameters.pointsUvs ? '#define USE_POINTS_UV' : '', - } + parameters.flatShading ? '#define FLAT_SHADED' : '', - } + parameters.skinning ? '#define USE_SKINNING' : '', - // flush last keyframe (compaction looks ahead) + parameters.morphTargets ? '#define USE_MORPHTARGETS' : '', + parameters.morphNormals && parameters.flatShading === false ? '#define USE_MORPHNORMALS' : '', + ( parameters.morphColors ) ? '#define USE_MORPHCOLORS' : '', + ( parameters.morphTargetsCount > 0 ) ? '#define MORPHTARGETS_TEXTURE_STRIDE ' + parameters.morphTextureStride : '', + ( parameters.morphTargetsCount > 0 ) ? '#define MORPHTARGETS_COUNT ' + parameters.morphTargetsCount : '', + parameters.doubleSided ? '#define DOUBLE_SIDED' : '', + parameters.flipSided ? '#define FLIP_SIDED' : '', - if ( lastIndex > 0 ) { + parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '', + parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '', - times[ writeIndex ] = times[ lastIndex ]; + parameters.sizeAttenuation ? '#define USE_SIZEATTENUATION' : '', - for ( let readOffset = lastIndex * stride, writeOffset = writeIndex * stride, j = 0; j !== stride; ++ j ) { + parameters.numLightProbes > 0 ? '#define USE_LIGHT_PROBES' : '', - values[ writeOffset + j ] = values[ readOffset + j ]; + parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '', + parameters.reverseDepthBuffer ? '#define USE_REVERSEDEPTHBUF' : '', - } + 'uniform mat4 modelMatrix;', + 'uniform mat4 modelViewMatrix;', + 'uniform mat4 projectionMatrix;', + 'uniform mat4 viewMatrix;', + 'uniform mat3 normalMatrix;', + 'uniform vec3 cameraPosition;', + 'uniform bool isOrthographic;', - ++ writeIndex; + '#ifdef USE_INSTANCING', - } + ' attribute mat4 instanceMatrix;', - if ( writeIndex !== times.length ) { + '#endif', - this.times = arraySlice( times, 0, writeIndex ); - this.values = arraySlice( values, 0, writeIndex * stride ); + '#ifdef USE_INSTANCING_COLOR', - } else { + ' attribute vec3 instanceColor;', - this.times = times; - this.values = values; + '#endif', - } + '#ifdef USE_INSTANCING_MORPH', - return this; + ' uniform sampler2D morphTexture;', - } + '#endif', - clone() { + 'attribute vec3 position;', + 'attribute vec3 normal;', + 'attribute vec2 uv;', - const times = arraySlice( this.times, 0 ); - const values = arraySlice( this.values, 0 ); + '#ifdef USE_UV1', - const TypedKeyframeTrack = this.constructor; - const track = new TypedKeyframeTrack( this.name, times, values ); + ' attribute vec2 uv1;', - // Interpolant argument to constructor is not saved, so copy the factory method directly. - track.createInterpolant = this.createInterpolant; + '#endif', - return track; + '#ifdef USE_UV2', - } + ' attribute vec2 uv2;', -} + '#endif', -KeyframeTrack.prototype.TimeBufferType = Float32Array; -KeyframeTrack.prototype.ValueBufferType = Float32Array; -KeyframeTrack.prototype.DefaultInterpolation = InterpolateLinear; + '#ifdef USE_UV3', -/** - * A Track of Boolean keyframe values. - */ -class BooleanKeyframeTrack extends KeyframeTrack {} + ' attribute vec2 uv3;', -BooleanKeyframeTrack.prototype.ValueTypeName = 'bool'; -BooleanKeyframeTrack.prototype.ValueBufferType = Array; -BooleanKeyframeTrack.prototype.DefaultInterpolation = InterpolateDiscrete; -BooleanKeyframeTrack.prototype.InterpolantFactoryMethodLinear = undefined; -BooleanKeyframeTrack.prototype.InterpolantFactoryMethodSmooth = undefined; + '#endif', -/** - * A Track of keyframe values that represent color. - */ -class ColorKeyframeTrack extends KeyframeTrack {} + '#ifdef USE_TANGENT', -ColorKeyframeTrack.prototype.ValueTypeName = 'color'; + ' attribute vec4 tangent;', -/** - * A Track of numeric keyframe values. - */ -class NumberKeyframeTrack extends KeyframeTrack {} + '#endif', -NumberKeyframeTrack.prototype.ValueTypeName = 'number'; + '#if defined( USE_COLOR_ALPHA )', -/** - * Spherical linear unit quaternion interpolant. - */ + ' attribute vec4 color;', -class QuaternionLinearInterpolant extends Interpolant { + '#elif defined( USE_COLOR )', - constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { + ' attribute vec3 color;', - super( parameterPositions, sampleValues, sampleSize, resultBuffer ); + '#endif', - } + '#ifdef USE_SKINNING', - interpolate_( i1, t0, t, t1 ) { + ' attribute vec4 skinIndex;', + ' attribute vec4 skinWeight;', - const result = this.resultBuffer, - values = this.sampleValues, - stride = this.valueSize, + '#endif', - alpha = ( t - t0 ) / ( t1 - t0 ); + '\n' - let offset = i1 * stride; + ].filter( filterEmptyLine ).join( '\n' ); - for ( let end = offset + stride; offset !== end; offset += 4 ) { + prefixFragment = [ - Quaternion.slerpFlat( result, 0, values, offset - stride, values, offset, alpha ); + generatePrecision( parameters ), - } + '#define SHADER_TYPE ' + parameters.shaderType, + '#define SHADER_NAME ' + parameters.shaderName, - return result; + customDefines, - } + parameters.useFog && parameters.fog ? '#define USE_FOG' : '', + parameters.useFog && parameters.fogExp2 ? '#define FOG_EXP2' : '', -} + parameters.alphaToCoverage ? '#define ALPHA_TO_COVERAGE' : '', + parameters.map ? '#define USE_MAP' : '', + parameters.matcap ? '#define USE_MATCAP' : '', + parameters.envMap ? '#define USE_ENVMAP' : '', + parameters.envMap ? '#define ' + envMapTypeDefine : '', + parameters.envMap ? '#define ' + envMapModeDefine : '', + parameters.envMap ? '#define ' + envMapBlendingDefine : '', + envMapCubeUVSize ? '#define CUBEUV_TEXEL_WIDTH ' + envMapCubeUVSize.texelWidth : '', + envMapCubeUVSize ? '#define CUBEUV_TEXEL_HEIGHT ' + envMapCubeUVSize.texelHeight : '', + envMapCubeUVSize ? '#define CUBEUV_MAX_MIP ' + envMapCubeUVSize.maxMip + '.0' : '', + parameters.lightMap ? '#define USE_LIGHTMAP' : '', + parameters.aoMap ? '#define USE_AOMAP' : '', + parameters.bumpMap ? '#define USE_BUMPMAP' : '', + parameters.normalMap ? '#define USE_NORMALMAP' : '', + parameters.normalMapObjectSpace ? '#define USE_NORMALMAP_OBJECTSPACE' : '', + parameters.normalMapTangentSpace ? '#define USE_NORMALMAP_TANGENTSPACE' : '', + parameters.emissiveMap ? '#define USE_EMISSIVEMAP' : '', -/** - * A Track of quaternion keyframe values. - */ -class QuaternionKeyframeTrack extends KeyframeTrack { + parameters.anisotropy ? '#define USE_ANISOTROPY' : '', + parameters.anisotropyMap ? '#define USE_ANISOTROPYMAP' : '', - InterpolantFactoryMethodLinear( result ) { + parameters.clearcoat ? '#define USE_CLEARCOAT' : '', + parameters.clearcoatMap ? '#define USE_CLEARCOATMAP' : '', + parameters.clearcoatRoughnessMap ? '#define USE_CLEARCOAT_ROUGHNESSMAP' : '', + parameters.clearcoatNormalMap ? '#define USE_CLEARCOAT_NORMALMAP' : '', - return new QuaternionLinearInterpolant( this.times, this.values, this.getValueSize(), result ); + parameters.dispersion ? '#define USE_DISPERSION' : '', - } + parameters.iridescence ? '#define USE_IRIDESCENCE' : '', + parameters.iridescenceMap ? '#define USE_IRIDESCENCEMAP' : '', + parameters.iridescenceThicknessMap ? '#define USE_IRIDESCENCE_THICKNESSMAP' : '', -} + parameters.specularMap ? '#define USE_SPECULARMAP' : '', + parameters.specularColorMap ? '#define USE_SPECULAR_COLORMAP' : '', + parameters.specularIntensityMap ? '#define USE_SPECULAR_INTENSITYMAP' : '', -QuaternionKeyframeTrack.prototype.ValueTypeName = 'quaternion'; -// ValueBufferType is inherited -QuaternionKeyframeTrack.prototype.DefaultInterpolation = InterpolateLinear; -QuaternionKeyframeTrack.prototype.InterpolantFactoryMethodSmooth = undefined; + parameters.roughnessMap ? '#define USE_ROUGHNESSMAP' : '', + parameters.metalnessMap ? '#define USE_METALNESSMAP' : '', -/** - * A Track that interpolates Strings - */ -class StringKeyframeTrack extends KeyframeTrack {} + parameters.alphaMap ? '#define USE_ALPHAMAP' : '', + parameters.alphaTest ? '#define USE_ALPHATEST' : '', + parameters.alphaHash ? '#define USE_ALPHAHASH' : '', -StringKeyframeTrack.prototype.ValueTypeName = 'string'; -StringKeyframeTrack.prototype.ValueBufferType = Array; -StringKeyframeTrack.prototype.DefaultInterpolation = InterpolateDiscrete; -StringKeyframeTrack.prototype.InterpolantFactoryMethodLinear = undefined; -StringKeyframeTrack.prototype.InterpolantFactoryMethodSmooth = undefined; + parameters.sheen ? '#define USE_SHEEN' : '', + parameters.sheenColorMap ? '#define USE_SHEEN_COLORMAP' : '', + parameters.sheenRoughnessMap ? '#define USE_SHEEN_ROUGHNESSMAP' : '', -/** - * A Track of vectored keyframe values. - */ -class VectorKeyframeTrack extends KeyframeTrack {} + parameters.transmission ? '#define USE_TRANSMISSION' : '', + parameters.transmissionMap ? '#define USE_TRANSMISSIONMAP' : '', + parameters.thicknessMap ? '#define USE_THICKNESSMAP' : '', -VectorKeyframeTrack.prototype.ValueTypeName = 'vector'; + parameters.vertexTangents && parameters.flatShading === false ? '#define USE_TANGENT' : '', + parameters.vertexColors || parameters.instancingColor || parameters.batchingColor ? '#define USE_COLOR' : '', + parameters.vertexAlphas ? '#define USE_COLOR_ALPHA' : '', + parameters.vertexUv1s ? '#define USE_UV1' : '', + parameters.vertexUv2s ? '#define USE_UV2' : '', + parameters.vertexUv3s ? '#define USE_UV3' : '', -class AnimationClip { + parameters.pointsUvs ? '#define USE_POINTS_UV' : '', - constructor( name, duration = - 1, tracks, blendMode = NormalAnimationBlendMode ) { + parameters.gradientMap ? '#define USE_GRADIENTMAP' : '', - this.name = name; - this.tracks = tracks; - this.duration = duration; - this.blendMode = blendMode; + parameters.flatShading ? '#define FLAT_SHADED' : '', - this.uuid = generateUUID(); + parameters.doubleSided ? '#define DOUBLE_SIDED' : '', + parameters.flipSided ? '#define FLIP_SIDED' : '', - // this means it should figure out its duration by scanning the tracks - if ( this.duration < 0 ) { + parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '', + parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '', - this.resetDuration(); + parameters.premultipliedAlpha ? '#define PREMULTIPLIED_ALPHA' : '', - } + parameters.numLightProbes > 0 ? '#define USE_LIGHT_PROBES' : '', - } + parameters.decodeVideoTexture ? '#define DECODE_VIDEO_TEXTURE' : '', + parameters.decodeVideoTextureEmissive ? '#define DECODE_VIDEO_TEXTURE_EMISSIVE' : '', + parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '', + parameters.reverseDepthBuffer ? '#define USE_REVERSEDEPTHBUF' : '', - static parse( json ) { + 'uniform mat4 viewMatrix;', + 'uniform vec3 cameraPosition;', + 'uniform bool isOrthographic;', - const tracks = [], - jsonTracks = json.tracks, - frameTime = 1.0 / ( json.fps || 1.0 ); + ( parameters.toneMapping !== NoToneMapping ) ? '#define TONE_MAPPING' : '', + ( parameters.toneMapping !== NoToneMapping ) ? ShaderChunk[ 'tonemapping_pars_fragment' ] : '', // this code is required here because it is used by the toneMapping() function defined below + ( parameters.toneMapping !== NoToneMapping ) ? getToneMappingFunction( 'toneMapping', parameters.toneMapping ) : '', - for ( let i = 0, n = jsonTracks.length; i !== n; ++ i ) { + parameters.dithering ? '#define DITHERING' : '', + parameters.opaque ? '#define OPAQUE' : '', - tracks.push( parseKeyframeTrack( jsonTracks[ i ] ).scale( frameTime ) ); + ShaderChunk[ 'colorspace_pars_fragment' ], // this code is required here because it is used by the various encoding/decoding function defined below + getTexelEncodingFunction( 'linearToOutputTexel', parameters.outputColorSpace ), + getLuminanceFunction(), - } + parameters.useDepthPacking ? '#define DEPTH_PACKING ' + parameters.depthPacking : '', - const clip = new this( json.name, json.duration, tracks, json.blendMode ); - clip.uuid = json.uuid; + '\n' - return clip; + ].filter( filterEmptyLine ).join( '\n' ); } - static toJSON( clip ) { - - const tracks = [], - clipTracks = clip.tracks; + vertexShader = resolveIncludes( vertexShader ); + vertexShader = replaceLightNums( vertexShader, parameters ); + vertexShader = replaceClippingPlaneNums( vertexShader, parameters ); - const json = { + fragmentShader = resolveIncludes( fragmentShader ); + fragmentShader = replaceLightNums( fragmentShader, parameters ); + fragmentShader = replaceClippingPlaneNums( fragmentShader, parameters ); - 'name': clip.name, - 'duration': clip.duration, - 'tracks': tracks, - 'uuid': clip.uuid, - 'blendMode': clip.blendMode + vertexShader = unrollLoops( vertexShader ); + fragmentShader = unrollLoops( fragmentShader ); - }; + if ( parameters.isRawShaderMaterial !== true ) { - for ( let i = 0, n = clipTracks.length; i !== n; ++ i ) { + // GLSL 3.0 conversion for built-in materials and ShaderMaterial - tracks.push( KeyframeTrack.toJSON( clipTracks[ i ] ) ); + versionString = '#version 300 es\n'; - } + prefixVertex = [ + customVertexExtensions, + '#define attribute in', + '#define varying out', + '#define texture2D texture' + ].join( '\n' ) + '\n' + prefixVertex; - return json; + prefixFragment = [ + '#define varying in', + ( parameters.glslVersion === GLSL3 ) ? '' : 'layout(location = 0) out highp vec4 pc_fragColor;', + ( parameters.glslVersion === GLSL3 ) ? '' : '#define gl_FragColor pc_fragColor', + '#define gl_FragDepthEXT gl_FragDepth', + '#define texture2D texture', + '#define textureCube texture', + '#define texture2DProj textureProj', + '#define texture2DLodEXT textureLod', + '#define texture2DProjLodEXT textureProjLod', + '#define textureCubeLodEXT textureLod', + '#define texture2DGradEXT textureGrad', + '#define texture2DProjGradEXT textureProjGrad', + '#define textureCubeGradEXT textureGrad' + ].join( '\n' ) + '\n' + prefixFragment; } - static CreateFromMorphTargetSequence( name, morphTargetSequence, fps, noLoop ) { + const vertexGlsl = versionString + prefixVertex + vertexShader; + const fragmentGlsl = versionString + prefixFragment + fragmentShader; - const numMorphTargets = morphTargetSequence.length; - const tracks = []; + // console.log( '*VERTEX*', vertexGlsl ); + // console.log( '*FRAGMENT*', fragmentGlsl ); - for ( let i = 0; i < numMorphTargets; i ++ ) { + const glVertexShader = WebGLShader( gl, gl.VERTEX_SHADER, vertexGlsl ); + const glFragmentShader = WebGLShader( gl, gl.FRAGMENT_SHADER, fragmentGlsl ); - let times = []; - let values = []; + gl.attachShader( program, glVertexShader ); + gl.attachShader( program, glFragmentShader ); - times.push( - ( i + numMorphTargets - 1 ) % numMorphTargets, - i, - ( i + 1 ) % numMorphTargets ); + // Force a particular attribute to index 0. - values.push( 0, 1, 0 ); + if ( parameters.index0AttributeName !== undefined ) { - const order = getKeyframeOrder( times ); - times = sortedArray( times, 1, order ); - values = sortedArray( values, 1, order ); + gl.bindAttribLocation( program, 0, parameters.index0AttributeName ); - // if there is a key at the first frame, duplicate it as the - // last frame as well for perfect loop. - if ( ! noLoop && times[ 0 ] === 0 ) { + } else if ( parameters.morphTargets === true ) { - times.push( numMorphTargets ); - values.push( values[ 0 ] ); + // programs with morphTargets displace position out of attribute 0 + gl.bindAttribLocation( program, 0, 'position' ); - } + } - tracks.push( - new NumberKeyframeTrack( - '.morphTargetInfluences[' + morphTargetSequence[ i ].name + ']', - times, values - ).scale( 1.0 / fps ) ); + gl.linkProgram( program ); - } + function onFirstUse( self ) { - return new this( name, - 1, tracks ); + // check for link errors + if ( renderer.debug.checkShaderErrors ) { - } + const programLog = gl.getProgramInfoLog( program ).trim(); + const vertexLog = gl.getShaderInfoLog( glVertexShader ).trim(); + const fragmentLog = gl.getShaderInfoLog( glFragmentShader ).trim(); - static findByName( objectOrClipArray, name ) { + let runnable = true; + let haveDiagnostics = true; - let clipArray = objectOrClipArray; + if ( gl.getProgramParameter( program, gl.LINK_STATUS ) === false ) { - if ( ! Array.isArray( objectOrClipArray ) ) { + runnable = false; - const o = objectOrClipArray; - clipArray = o.geometry && o.geometry.animations || o.animations; + if ( typeof renderer.debug.onShaderError === 'function' ) { - } + renderer.debug.onShaderError( gl, program, glVertexShader, glFragmentShader ); - for ( let i = 0; i < clipArray.length; i ++ ) { + } else { - if ( clipArray[ i ].name === name ) { + // default error reporting - return clipArray[ i ]; + const vertexErrors = getShaderErrors( gl, glVertexShader, 'vertex' ); + const fragmentErrors = getShaderErrors( gl, glFragmentShader, 'fragment' ); - } + console.error( + 'THREE.WebGLProgram: Shader Error ' + gl.getError() + ' - ' + + 'VALIDATE_STATUS ' + gl.getProgramParameter( program, gl.VALIDATE_STATUS ) + '\n\n' + + 'Material Name: ' + self.name + '\n' + + 'Material Type: ' + self.type + '\n\n' + + 'Program Info Log: ' + programLog + '\n' + + vertexErrors + '\n' + + fragmentErrors + ); - } + } + + } else if ( programLog !== '' ) { + + console.warn( 'THREE.WebGLProgram: Program Info Log:', programLog ); - return null; + } else if ( vertexLog === '' || fragmentLog === '' ) { - } + haveDiagnostics = false; - static CreateClipsFromMorphTargetSequences( morphTargets, fps, noLoop ) { + } - const animationToMorphTargets = {}; + if ( haveDiagnostics ) { - // tested with https://regex101.com/ on trick sequences - // such flamingo_flyA_003, flamingo_run1_003, crdeath0059 - const pattern = /^([\w-]*?)([\d]+)$/; + self.diagnostics = { - // sort morph target names into animation groups based - // patterns like Walk_001, Walk_002, Run_001, Run_002 - for ( let i = 0, il = morphTargets.length; i < il; i ++ ) { + runnable: runnable, - const morphTarget = morphTargets[ i ]; - const parts = morphTarget.name.match( pattern ); + programLog: programLog, - if ( parts && parts.length > 1 ) { + vertexShader: { - const name = parts[ 1 ]; + log: vertexLog, + prefix: prefixVertex - let animationMorphTargets = animationToMorphTargets[ name ]; + }, - if ( ! animationMorphTargets ) { + fragmentShader: { - animationToMorphTargets[ name ] = animationMorphTargets = []; + log: fragmentLog, + prefix: prefixFragment - } + } - animationMorphTargets.push( morphTarget ); + }; } } - const clips = []; - - for ( const name in animationToMorphTargets ) { + // Clean up - clips.push( this.CreateFromMorphTargetSequence( name, animationToMorphTargets[ name ], fps, noLoop ) ); + // Crashes in iOS9 and iOS10. #18402 + // gl.detachShader( program, glVertexShader ); + // gl.detachShader( program, glFragmentShader ); - } + gl.deleteShader( glVertexShader ); + gl.deleteShader( glFragmentShader ); - return clips; + cachedUniforms = new WebGLUniforms( gl, program ); + cachedAttributes = fetchAttributeLocations( gl, program ); } - // parse the animation.hierarchy format - static parseAnimation( animation, bones ) { + // set up caching for uniform locations - if ( ! animation ) { + let cachedUniforms; - console.error( 'THREE.AnimationClip: No animation in JSONLoader data.' ); - return null; + this.getUniforms = function () { - } + if ( cachedUniforms === undefined ) { - const addNonemptyTrack = function ( trackType, trackName, animationKeys, propertyName, destTracks ) { + // Populates cachedUniforms and cachedAttributes + onFirstUse( this ); - // only return track if there are actually keys. - if ( animationKeys.length !== 0 ) { + } - const times = []; - const values = []; + return cachedUniforms; - flattenJSON( animationKeys, times, values, propertyName ); + }; - // empty keys are filtered out, so check again - if ( times.length !== 0 ) { + // set up caching for attribute locations - destTracks.push( new trackType( trackName, times, values ) ); + let cachedAttributes; - } + this.getAttributes = function () { - } + if ( cachedAttributes === undefined ) { - }; + // Populates cachedAttributes and cachedUniforms + onFirstUse( this ); - const tracks = []; + } - const clipName = animation.name || 'default'; - const fps = animation.fps || 30; - const blendMode = animation.blendMode; + return cachedAttributes; - // automatic length determination in AnimationClip. - let duration = animation.length || - 1; + }; - const hierarchyTracks = animation.hierarchy || []; + // indicate when the program is ready to be used. if the KHR_parallel_shader_compile extension isn't supported, + // flag the program as ready immediately. It may cause a stall when it's first used. - for ( let h = 0; h < hierarchyTracks.length; h ++ ) { + let programReady = ( parameters.rendererExtensionParallelShaderCompile === false ); - const animationKeys = hierarchyTracks[ h ].keys; + this.isReady = function () { - // skip empty tracks - if ( ! animationKeys || animationKeys.length === 0 ) continue; + if ( programReady === false ) { - // process morph targets - if ( animationKeys[ 0 ].morphTargets ) { + programReady = gl.getProgramParameter( program, COMPLETION_STATUS_KHR ); - // figure out all morph targets used in this track - const morphTargetNames = {}; + } - let k; + return programReady; - for ( k = 0; k < animationKeys.length; k ++ ) { + }; - if ( animationKeys[ k ].morphTargets ) { + // free resource - for ( let m = 0; m < animationKeys[ k ].morphTargets.length; m ++ ) { + this.destroy = function () { - morphTargetNames[ animationKeys[ k ].morphTargets[ m ] ] = - 1; + bindingStates.releaseStatesOfProgram( this ); - } + gl.deleteProgram( program ); + this.program = undefined; - } + }; - } + // - // create a track for each morph target with all zero - // morphTargetInfluences except for the keys in which - // the morphTarget is named. - for ( const morphTargetName in morphTargetNames ) { + this.type = parameters.shaderType; + this.name = parameters.shaderName; + this.id = programIdCount ++; + this.cacheKey = cacheKey; + this.usedTimes = 1; + this.program = program; + this.vertexShader = glVertexShader; + this.fragmentShader = glFragmentShader; - const times = []; - const values = []; + return this; - for ( let m = 0; m !== animationKeys[ k ].morphTargets.length; ++ m ) { +} - const animationKey = animationKeys[ k ]; +let _id = 0; - times.push( animationKey.time ); - values.push( ( animationKey.morphTarget === morphTargetName ) ? 1 : 0 ); +class WebGLShaderCache { - } + constructor() { - tracks.push( new NumberKeyframeTrack( '.morphTargetInfluence[' + morphTargetName + ']', times, values ) ); + this.shaderCache = new Map(); + this.materialCache = new Map(); - } + } - duration = morphTargetNames.length * fps; + update( material ) { - } else { + const vertexShader = material.vertexShader; + const fragmentShader = material.fragmentShader; - // ...assume skeletal animation + const vertexShaderStage = this._getShaderStage( vertexShader ); + const fragmentShaderStage = this._getShaderStage( fragmentShader ); - const boneName = '.bones[' + bones[ h ].name + ']'; + const materialShaders = this._getShaderCacheForMaterial( material ); - addNonemptyTrack( - VectorKeyframeTrack, boneName + '.position', - animationKeys, 'pos', tracks ); + if ( materialShaders.has( vertexShaderStage ) === false ) { - addNonemptyTrack( - QuaternionKeyframeTrack, boneName + '.quaternion', - animationKeys, 'rot', tracks ); + materialShaders.add( vertexShaderStage ); + vertexShaderStage.usedTimes ++; - addNonemptyTrack( - VectorKeyframeTrack, boneName + '.scale', - animationKeys, 'scl', tracks ); + } - } + if ( materialShaders.has( fragmentShaderStage ) === false ) { + + materialShaders.add( fragmentShaderStage ); + fragmentShaderStage.usedTimes ++; } - if ( tracks.length === 0 ) { + return this; - return null; + } + + remove( material ) { + + const materialShaders = this.materialCache.get( material ); + + for ( const shaderStage of materialShaders ) { + + shaderStage.usedTimes --; + + if ( shaderStage.usedTimes === 0 ) this.shaderCache.delete( shaderStage.code ); } - const clip = new this( clipName, duration, tracks, blendMode ); + this.materialCache.delete( material ); - return clip; + return this; } - resetDuration() { + getVertexShaderID( material ) { - const tracks = this.tracks; - let duration = 0; + return this._getShaderStage( material.vertexShader ).id; - for ( let i = 0, n = tracks.length; i !== n; ++ i ) { + } - const track = this.tracks[ i ]; + getFragmentShaderID( material ) { - duration = Math.max( duration, track.times[ track.times.length - 1 ] ); + return this._getShaderStage( material.fragmentShader ).id; - } + } - this.duration = duration; + dispose() { - return this; + this.shaderCache.clear(); + this.materialCache.clear(); } - trim() { + _getShaderCacheForMaterial( material ) { - for ( let i = 0; i < this.tracks.length; i ++ ) { + const cache = this.materialCache; + let set = cache.get( material ); - this.tracks[ i ].trim( 0, this.duration ); + if ( set === undefined ) { + + set = new Set(); + cache.set( material, set ); } - return this; + return set; } - validate() { + _getShaderStage( code ) { - let valid = true; + const cache = this.shaderCache; + let stage = cache.get( code ); - for ( let i = 0; i < this.tracks.length; i ++ ) { + if ( stage === undefined ) { - valid = valid && this.tracks[ i ].validate(); + stage = new WebGLShaderStage( code ); + cache.set( code, stage ); } - return valid; + return stage; } - optimize() { +} - for ( let i = 0; i < this.tracks.length; i ++ ) { +class WebGLShaderStage { - this.tracks[ i ].optimize(); + constructor( code ) { - } + this.id = _id ++; - return this; + this.code = code; + this.usedTimes = 0; } - clone() { +} - const tracks = []; +function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities, bindingStates, clipping ) { - for ( let i = 0; i < this.tracks.length; i ++ ) { + const _programLayers = new Layers(); + const _customShaders = new WebGLShaderCache(); + const _activeChannels = new Set(); + const programs = []; - tracks.push( this.tracks[ i ].clone() ); + const logarithmicDepthBuffer = capabilities.logarithmicDepthBuffer; + const SUPPORTS_VERTEX_TEXTURES = capabilities.vertexTextures; - } + let precision = capabilities.precision; - return new this.constructor( this.name, this.duration, tracks, this.blendMode ); + const shaderIDs = { + MeshDepthMaterial: 'depth', + MeshDistanceMaterial: 'distanceRGBA', + MeshNormalMaterial: 'normal', + MeshBasicMaterial: 'basic', + MeshLambertMaterial: 'lambert', + MeshPhongMaterial: 'phong', + MeshToonMaterial: 'toon', + MeshStandardMaterial: 'physical', + MeshPhysicalMaterial: 'physical', + MeshMatcapMaterial: 'matcap', + LineBasicMaterial: 'basic', + LineDashedMaterial: 'dashed', + PointsMaterial: 'points', + ShadowMaterial: 'shadow', + SpriteMaterial: 'sprite' + }; - } + function getChannel( value ) { - toJSON() { + _activeChannels.add( value ); - return this.constructor.toJSON( this ); + if ( value === 0 ) return 'uv'; + + return `uv${ value }`; } -} + function getParameters( material, lights, shadows, scene, object ) { -function getTrackTypeForValueTypeName( typeName ) { + const fog = scene.fog; + const geometry = object.geometry; + const environment = material.isMeshStandardMaterial ? scene.environment : null; - switch ( typeName.toLowerCase() ) { + const envMap = ( material.isMeshStandardMaterial ? cubeuvmaps : cubemaps ).get( material.envMap || environment ); + const envMapCubeUVHeight = ( !! envMap ) && ( envMap.mapping === CubeUVReflectionMapping ) ? envMap.image.height : null; - case 'scalar': - case 'double': - case 'float': - case 'number': - case 'integer': + const shaderID = shaderIDs[ material.type ]; - return NumberKeyframeTrack; + // heuristics to create shader parameters according to lights in the scene + // (not to blow over maxLights budget) - case 'vector': - case 'vector2': - case 'vector3': - case 'vector4': + if ( material.precision !== null ) { - return VectorKeyframeTrack; + precision = capabilities.getMaxPrecision( material.precision ); - case 'color': + if ( precision !== material.precision ) { - return ColorKeyframeTrack; + console.warn( 'THREE.WebGLProgram.getParameters:', material.precision, 'not supported, using', precision, 'instead.' ); - case 'quaternion': + } - return QuaternionKeyframeTrack; + } - case 'bool': - case 'boolean': + // - return BooleanKeyframeTrack; + const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color; + const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0; - case 'string': + let morphTextureStride = 0; - return StringKeyframeTrack; + if ( geometry.morphAttributes.position !== undefined ) morphTextureStride = 1; + if ( geometry.morphAttributes.normal !== undefined ) morphTextureStride = 2; + if ( geometry.morphAttributes.color !== undefined ) morphTextureStride = 3; - } + // - throw new Error( 'THREE.KeyframeTrack: Unsupported typeName: ' + typeName ); + let vertexShader, fragmentShader; + let customVertexShaderID, customFragmentShaderID; -} + if ( shaderID ) { -function parseKeyframeTrack( json ) { + const shader = ShaderLib[ shaderID ]; - if ( json.type === undefined ) { + vertexShader = shader.vertexShader; + fragmentShader = shader.fragmentShader; - throw new Error( 'THREE.KeyframeTrack: track type undefined, can not parse' ); + } else { - } + vertexShader = material.vertexShader; + fragmentShader = material.fragmentShader; - const trackType = getTrackTypeForValueTypeName( json.type ); + _customShaders.update( material ); - if ( json.times === undefined ) { + customVertexShaderID = _customShaders.getVertexShaderID( material ); + customFragmentShaderID = _customShaders.getFragmentShaderID( material ); - const times = [], values = []; + } - flattenJSON( json.keys, times, values, 'value' ); + const currentRenderTarget = renderer.getRenderTarget(); + const reverseDepthBuffer = renderer.state.buffers.depth.getReversed(); - json.times = times; - json.values = values; + const IS_INSTANCEDMESH = object.isInstancedMesh === true; + const IS_BATCHEDMESH = object.isBatchedMesh === true; - } + const HAS_MAP = !! material.map; + const HAS_MATCAP = !! material.matcap; + const HAS_ENVMAP = !! envMap; + const HAS_AOMAP = !! material.aoMap; + const HAS_LIGHTMAP = !! material.lightMap; + const HAS_BUMPMAP = !! material.bumpMap; + const HAS_NORMALMAP = !! material.normalMap; + const HAS_DISPLACEMENTMAP = !! material.displacementMap; + const HAS_EMISSIVEMAP = !! material.emissiveMap; - // derived classes can define a static parse method - if ( trackType.parse !== undefined ) { + const HAS_METALNESSMAP = !! material.metalnessMap; + const HAS_ROUGHNESSMAP = !! material.roughnessMap; - return trackType.parse( json ); + const HAS_ANISOTROPY = material.anisotropy > 0; + const HAS_CLEARCOAT = material.clearcoat > 0; + const HAS_DISPERSION = material.dispersion > 0; + const HAS_IRIDESCENCE = material.iridescence > 0; + const HAS_SHEEN = material.sheen > 0; + const HAS_TRANSMISSION = material.transmission > 0; - } else { + const HAS_ANISOTROPYMAP = HAS_ANISOTROPY && !! material.anisotropyMap; - // by default, we assume a constructor compatible with the base - return new trackType( json.name, json.times, json.values, json.interpolation ); + const HAS_CLEARCOATMAP = HAS_CLEARCOAT && !! material.clearcoatMap; + const HAS_CLEARCOAT_NORMALMAP = HAS_CLEARCOAT && !! material.clearcoatNormalMap; + const HAS_CLEARCOAT_ROUGHNESSMAP = HAS_CLEARCOAT && !! material.clearcoatRoughnessMap; - } + const HAS_IRIDESCENCEMAP = HAS_IRIDESCENCE && !! material.iridescenceMap; + const HAS_IRIDESCENCE_THICKNESSMAP = HAS_IRIDESCENCE && !! material.iridescenceThicknessMap; -} + const HAS_SHEEN_COLORMAP = HAS_SHEEN && !! material.sheenColorMap; + const HAS_SHEEN_ROUGHNESSMAP = HAS_SHEEN && !! material.sheenRoughnessMap; -const Cache = { + const HAS_SPECULARMAP = !! material.specularMap; + const HAS_SPECULAR_COLORMAP = !! material.specularColorMap; + const HAS_SPECULAR_INTENSITYMAP = !! material.specularIntensityMap; - enabled: false, + const HAS_TRANSMISSIONMAP = HAS_TRANSMISSION && !! material.transmissionMap; + const HAS_THICKNESSMAP = HAS_TRANSMISSION && !! material.thicknessMap; - files: {}, + const HAS_GRADIENTMAP = !! material.gradientMap; - add: function ( key, file ) { + const HAS_ALPHAMAP = !! material.alphaMap; - if ( this.enabled === false ) return; + const HAS_ALPHATEST = material.alphaTest > 0; - // console.log( 'THREE.Cache', 'Adding key:', key ); + const HAS_ALPHAHASH = !! material.alphaHash; - this.files[ key ] = file; + const HAS_EXTENSIONS = !! material.extensions; - }, + let toneMapping = NoToneMapping; - get: function ( key ) { + if ( material.toneMapped ) { - if ( this.enabled === false ) return; + if ( currentRenderTarget === null || currentRenderTarget.isXRRenderTarget === true ) { - // console.log( 'THREE.Cache', 'Checking key:', key ); + toneMapping = renderer.toneMapping; - return this.files[ key ]; + } - }, + } - remove: function ( key ) { + const parameters = { - delete this.files[ key ]; + shaderID: shaderID, + shaderType: material.type, + shaderName: material.name, - }, + vertexShader: vertexShader, + fragmentShader: fragmentShader, + defines: material.defines, - clear: function () { + customVertexShaderID: customVertexShaderID, + customFragmentShaderID: customFragmentShaderID, - this.files = {}; + isRawShaderMaterial: material.isRawShaderMaterial === true, + glslVersion: material.glslVersion, - } + precision: precision, -}; + batching: IS_BATCHEDMESH, + batchingColor: IS_BATCHEDMESH && object._colorsTexture !== null, + instancing: IS_INSTANCEDMESH, + instancingColor: IS_INSTANCEDMESH && object.instanceColor !== null, + instancingMorph: IS_INSTANCEDMESH && object.morphTexture !== null, -class LoadingManager { + supportsVertexTextures: SUPPORTS_VERTEX_TEXTURES, + outputColorSpace: ( currentRenderTarget === null ) ? renderer.outputColorSpace : ( currentRenderTarget.isXRRenderTarget === true ? currentRenderTarget.texture.colorSpace : LinearSRGBColorSpace ), + alphaToCoverage: !! material.alphaToCoverage, - constructor( onLoad, onProgress, onError ) { + map: HAS_MAP, + matcap: HAS_MATCAP, + envMap: HAS_ENVMAP, + envMapMode: HAS_ENVMAP && envMap.mapping, + envMapCubeUVHeight: envMapCubeUVHeight, + aoMap: HAS_AOMAP, + lightMap: HAS_LIGHTMAP, + bumpMap: HAS_BUMPMAP, + normalMap: HAS_NORMALMAP, + displacementMap: SUPPORTS_VERTEX_TEXTURES && HAS_DISPLACEMENTMAP, + emissiveMap: HAS_EMISSIVEMAP, - const scope = this; + normalMapObjectSpace: HAS_NORMALMAP && material.normalMapType === ObjectSpaceNormalMap, + normalMapTangentSpace: HAS_NORMALMAP && material.normalMapType === TangentSpaceNormalMap, - let isLoading = false; - let itemsLoaded = 0; - let itemsTotal = 0; - let urlModifier = undefined; - const handlers = []; + metalnessMap: HAS_METALNESSMAP, + roughnessMap: HAS_ROUGHNESSMAP, - // Refer to #5689 for the reason why we don't set .onStart - // in the constructor + anisotropy: HAS_ANISOTROPY, + anisotropyMap: HAS_ANISOTROPYMAP, - this.onStart = undefined; - this.onLoad = onLoad; - this.onProgress = onProgress; - this.onError = onError; + clearcoat: HAS_CLEARCOAT, + clearcoatMap: HAS_CLEARCOATMAP, + clearcoatNormalMap: HAS_CLEARCOAT_NORMALMAP, + clearcoatRoughnessMap: HAS_CLEARCOAT_ROUGHNESSMAP, - this.itemStart = function ( url ) { + dispersion: HAS_DISPERSION, - itemsTotal ++; + iridescence: HAS_IRIDESCENCE, + iridescenceMap: HAS_IRIDESCENCEMAP, + iridescenceThicknessMap: HAS_IRIDESCENCE_THICKNESSMAP, - if ( isLoading === false ) { + sheen: HAS_SHEEN, + sheenColorMap: HAS_SHEEN_COLORMAP, + sheenRoughnessMap: HAS_SHEEN_ROUGHNESSMAP, - if ( scope.onStart !== undefined ) { + specularMap: HAS_SPECULARMAP, + specularColorMap: HAS_SPECULAR_COLORMAP, + specularIntensityMap: HAS_SPECULAR_INTENSITYMAP, - scope.onStart( url, itemsLoaded, itemsTotal ); + transmission: HAS_TRANSMISSION, + transmissionMap: HAS_TRANSMISSIONMAP, + thicknessMap: HAS_THICKNESSMAP, - } + gradientMap: HAS_GRADIENTMAP, - } + opaque: material.transparent === false && material.blending === NormalBlending && material.alphaToCoverage === false, - isLoading = true; + alphaMap: HAS_ALPHAMAP, + alphaTest: HAS_ALPHATEST, + alphaHash: HAS_ALPHAHASH, - }; + combine: material.combine, - this.itemEnd = function ( url ) { + // - itemsLoaded ++; + mapUv: HAS_MAP && getChannel( material.map.channel ), + aoMapUv: HAS_AOMAP && getChannel( material.aoMap.channel ), + lightMapUv: HAS_LIGHTMAP && getChannel( material.lightMap.channel ), + bumpMapUv: HAS_BUMPMAP && getChannel( material.bumpMap.channel ), + normalMapUv: HAS_NORMALMAP && getChannel( material.normalMap.channel ), + displacementMapUv: HAS_DISPLACEMENTMAP && getChannel( material.displacementMap.channel ), + emissiveMapUv: HAS_EMISSIVEMAP && getChannel( material.emissiveMap.channel ), - if ( scope.onProgress !== undefined ) { + metalnessMapUv: HAS_METALNESSMAP && getChannel( material.metalnessMap.channel ), + roughnessMapUv: HAS_ROUGHNESSMAP && getChannel( material.roughnessMap.channel ), - scope.onProgress( url, itemsLoaded, itemsTotal ); + anisotropyMapUv: HAS_ANISOTROPYMAP && getChannel( material.anisotropyMap.channel ), - } + clearcoatMapUv: HAS_CLEARCOATMAP && getChannel( material.clearcoatMap.channel ), + clearcoatNormalMapUv: HAS_CLEARCOAT_NORMALMAP && getChannel( material.clearcoatNormalMap.channel ), + clearcoatRoughnessMapUv: HAS_CLEARCOAT_ROUGHNESSMAP && getChannel( material.clearcoatRoughnessMap.channel ), - if ( itemsLoaded === itemsTotal ) { + iridescenceMapUv: HAS_IRIDESCENCEMAP && getChannel( material.iridescenceMap.channel ), + iridescenceThicknessMapUv: HAS_IRIDESCENCE_THICKNESSMAP && getChannel( material.iridescenceThicknessMap.channel ), - isLoading = false; + sheenColorMapUv: HAS_SHEEN_COLORMAP && getChannel( material.sheenColorMap.channel ), + sheenRoughnessMapUv: HAS_SHEEN_ROUGHNESSMAP && getChannel( material.sheenRoughnessMap.channel ), - if ( scope.onLoad !== undefined ) { + specularMapUv: HAS_SPECULARMAP && getChannel( material.specularMap.channel ), + specularColorMapUv: HAS_SPECULAR_COLORMAP && getChannel( material.specularColorMap.channel ), + specularIntensityMapUv: HAS_SPECULAR_INTENSITYMAP && getChannel( material.specularIntensityMap.channel ), - scope.onLoad(); + transmissionMapUv: HAS_TRANSMISSIONMAP && getChannel( material.transmissionMap.channel ), + thicknessMapUv: HAS_THICKNESSMAP && getChannel( material.thicknessMap.channel ), - } + alphaMapUv: HAS_ALPHAMAP && getChannel( material.alphaMap.channel ), - } + // - }; + vertexTangents: !! geometry.attributes.tangent && ( HAS_NORMALMAP || HAS_ANISOTROPY ), + vertexColors: material.vertexColors, + vertexAlphas: material.vertexColors === true && !! geometry.attributes.color && geometry.attributes.color.itemSize === 4, + + pointsUvs: object.isPoints === true && !! geometry.attributes.uv && ( HAS_MAP || HAS_ALPHAMAP ), + + fog: !! fog, + useFog: material.fog === true, + fogExp2: ( !! fog && fog.isFogExp2 ), + + flatShading: ( material.flatShading === true && material.wireframe === false ), - this.itemError = function ( url ) { + sizeAttenuation: material.sizeAttenuation === true, + logarithmicDepthBuffer: logarithmicDepthBuffer, + reverseDepthBuffer: reverseDepthBuffer, - if ( scope.onError !== undefined ) { + skinning: object.isSkinnedMesh === true, - scope.onError( url ); + morphTargets: geometry.morphAttributes.position !== undefined, + morphNormals: geometry.morphAttributes.normal !== undefined, + morphColors: geometry.morphAttributes.color !== undefined, + morphTargetsCount: morphTargetsCount, + morphTextureStride: morphTextureStride, - } + numDirLights: lights.directional.length, + numPointLights: lights.point.length, + numSpotLights: lights.spot.length, + numSpotLightMaps: lights.spotLightMap.length, + numRectAreaLights: lights.rectArea.length, + numHemiLights: lights.hemi.length, - }; + numDirLightShadows: lights.directionalShadowMap.length, + numPointLightShadows: lights.pointShadowMap.length, + numSpotLightShadows: lights.spotShadowMap.length, + numSpotLightShadowsWithMaps: lights.numSpotLightShadowsWithMaps, - this.resolveURL = function ( url ) { + numLightProbes: lights.numLightProbes, - if ( urlModifier ) { + numClippingPlanes: clipping.numPlanes, + numClipIntersection: clipping.numIntersection, - return urlModifier( url ); + dithering: material.dithering, - } + shadowMapEnabled: renderer.shadowMap.enabled && shadows.length > 0, + shadowMapType: renderer.shadowMap.type, - return url; + toneMapping: toneMapping, - }; + decodeVideoTexture: HAS_MAP && ( material.map.isVideoTexture === true ) && ( ColorManagement.getTransfer( material.map.colorSpace ) === SRGBTransfer ), + decodeVideoTextureEmissive: HAS_EMISSIVEMAP && ( material.emissiveMap.isVideoTexture === true ) && ( ColorManagement.getTransfer( material.emissiveMap.colorSpace ) === SRGBTransfer ), - this.setURLModifier = function ( transform ) { + premultipliedAlpha: material.premultipliedAlpha, - urlModifier = transform; + doubleSided: material.side === DoubleSide, + flipSided: material.side === BackSide, - return this; + useDepthPacking: material.depthPacking >= 0, + depthPacking: material.depthPacking || 0, - }; + index0AttributeName: material.index0AttributeName, - this.addHandler = function ( regex, loader ) { + extensionClipCullDistance: HAS_EXTENSIONS && material.extensions.clipCullDistance === true && extensions.has( 'WEBGL_clip_cull_distance' ), + extensionMultiDraw: ( HAS_EXTENSIONS && material.extensions.multiDraw === true || IS_BATCHEDMESH ) && extensions.has( 'WEBGL_multi_draw' ), - handlers.push( regex, loader ); + rendererExtensionParallelShaderCompile: extensions.has( 'KHR_parallel_shader_compile' ), - return this; + customProgramCacheKey: material.customProgramCacheKey() }; - this.removeHandler = function ( regex ) { + // the usage of getChannel() determines the active texture channels for this shader - const index = handlers.indexOf( regex ); + parameters.vertexUv1s = _activeChannels.has( 1 ); + parameters.vertexUv2s = _activeChannels.has( 2 ); + parameters.vertexUv3s = _activeChannels.has( 3 ); - if ( index !== - 1 ) { + _activeChannels.clear(); - handlers.splice( index, 2 ); + return parameters; - } + } - return this; + function getProgramCacheKey( parameters ) { - }; + const array = []; - this.getHandler = function ( file ) { + if ( parameters.shaderID ) { - for ( let i = 0, l = handlers.length; i < l; i += 2 ) { + array.push( parameters.shaderID ); - const regex = handlers[ i ]; - const loader = handlers[ i + 1 ]; + } else { - if ( regex.global ) regex.lastIndex = 0; // see #17920 + array.push( parameters.customVertexShaderID ); + array.push( parameters.customFragmentShaderID ); - if ( regex.test( file ) ) { + } - return loader; + if ( parameters.defines !== undefined ) { - } + for ( const name in parameters.defines ) { + + array.push( name ); + array.push( parameters.defines[ name ] ); } - return null; + } - }; + if ( parameters.isRawShaderMaterial === false ) { - } + getProgramCacheKeyParameters( array, parameters ); + getProgramCacheKeyBooleans( array, parameters ); + array.push( renderer.outputColorSpace ); -} + } -const DefaultLoadingManager = /*@__PURE__*/ new LoadingManager(); + array.push( parameters.customProgramCacheKey ); -class Loader { + return array.join(); - constructor( manager ) { + } - this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager; + function getProgramCacheKeyParameters( array, parameters ) { - this.crossOrigin = 'anonymous'; - this.withCredentials = false; - this.path = ''; - this.resourcePath = ''; - this.requestHeader = {}; + array.push( parameters.precision ); + array.push( parameters.outputColorSpace ); + array.push( parameters.envMapMode ); + array.push( parameters.envMapCubeUVHeight ); + array.push( parameters.mapUv ); + array.push( parameters.alphaMapUv ); + array.push( parameters.lightMapUv ); + array.push( parameters.aoMapUv ); + array.push( parameters.bumpMapUv ); + array.push( parameters.normalMapUv ); + array.push( parameters.displacementMapUv ); + array.push( parameters.emissiveMapUv ); + array.push( parameters.metalnessMapUv ); + array.push( parameters.roughnessMapUv ); + array.push( parameters.anisotropyMapUv ); + array.push( parameters.clearcoatMapUv ); + array.push( parameters.clearcoatNormalMapUv ); + array.push( parameters.clearcoatRoughnessMapUv ); + array.push( parameters.iridescenceMapUv ); + array.push( parameters.iridescenceThicknessMapUv ); + array.push( parameters.sheenColorMapUv ); + array.push( parameters.sheenRoughnessMapUv ); + array.push( parameters.specularMapUv ); + array.push( parameters.specularColorMapUv ); + array.push( parameters.specularIntensityMapUv ); + array.push( parameters.transmissionMapUv ); + array.push( parameters.thicknessMapUv ); + array.push( parameters.combine ); + array.push( parameters.fogExp2 ); + array.push( parameters.sizeAttenuation ); + array.push( parameters.morphTargetsCount ); + array.push( parameters.morphAttributeCount ); + array.push( parameters.numDirLights ); + array.push( parameters.numPointLights ); + array.push( parameters.numSpotLights ); + array.push( parameters.numSpotLightMaps ); + array.push( parameters.numHemiLights ); + array.push( parameters.numRectAreaLights ); + array.push( parameters.numDirLightShadows ); + array.push( parameters.numPointLightShadows ); + array.push( parameters.numSpotLightShadows ); + array.push( parameters.numSpotLightShadowsWithMaps ); + array.push( parameters.numLightProbes ); + array.push( parameters.shadowMapType ); + array.push( parameters.toneMapping ); + array.push( parameters.numClippingPlanes ); + array.push( parameters.numClipIntersection ); + array.push( parameters.depthPacking ); } - load( /* url, onLoad, onProgress, onError */ ) {} + function getProgramCacheKeyBooleans( array, parameters ) { - loadAsync( url, onProgress ) { + _programLayers.disableAll(); - const scope = this; + if ( parameters.supportsVertexTextures ) + _programLayers.enable( 0 ); + if ( parameters.instancing ) + _programLayers.enable( 1 ); + if ( parameters.instancingColor ) + _programLayers.enable( 2 ); + if ( parameters.instancingMorph ) + _programLayers.enable( 3 ); + if ( parameters.matcap ) + _programLayers.enable( 4 ); + if ( parameters.envMap ) + _programLayers.enable( 5 ); + if ( parameters.normalMapObjectSpace ) + _programLayers.enable( 6 ); + if ( parameters.normalMapTangentSpace ) + _programLayers.enable( 7 ); + if ( parameters.clearcoat ) + _programLayers.enable( 8 ); + if ( parameters.iridescence ) + _programLayers.enable( 9 ); + if ( parameters.alphaTest ) + _programLayers.enable( 10 ); + if ( parameters.vertexColors ) + _programLayers.enable( 11 ); + if ( parameters.vertexAlphas ) + _programLayers.enable( 12 ); + if ( parameters.vertexUv1s ) + _programLayers.enable( 13 ); + if ( parameters.vertexUv2s ) + _programLayers.enable( 14 ); + if ( parameters.vertexUv3s ) + _programLayers.enable( 15 ); + if ( parameters.vertexTangents ) + _programLayers.enable( 16 ); + if ( parameters.anisotropy ) + _programLayers.enable( 17 ); + if ( parameters.alphaHash ) + _programLayers.enable( 18 ); + if ( parameters.batching ) + _programLayers.enable( 19 ); + if ( parameters.dispersion ) + _programLayers.enable( 20 ); + if ( parameters.batchingColor ) + _programLayers.enable( 21 ); + if ( parameters.gradientMap ) + _programLayers.enable( 22 ); - return new Promise( function ( resolve, reject ) { + array.push( _programLayers.mask ); + _programLayers.disableAll(); - scope.load( url, resolve, onProgress, reject ); + if ( parameters.fog ) + _programLayers.enable( 0 ); + if ( parameters.useFog ) + _programLayers.enable( 1 ); + if ( parameters.flatShading ) + _programLayers.enable( 2 ); + if ( parameters.logarithmicDepthBuffer ) + _programLayers.enable( 3 ); + if ( parameters.reverseDepthBuffer ) + _programLayers.enable( 4 ); + if ( parameters.skinning ) + _programLayers.enable( 5 ); + if ( parameters.morphTargets ) + _programLayers.enable( 6 ); + if ( parameters.morphNormals ) + _programLayers.enable( 7 ); + if ( parameters.morphColors ) + _programLayers.enable( 8 ); + if ( parameters.premultipliedAlpha ) + _programLayers.enable( 9 ); + if ( parameters.shadowMapEnabled ) + _programLayers.enable( 10 ); + if ( parameters.doubleSided ) + _programLayers.enable( 11 ); + if ( parameters.flipSided ) + _programLayers.enable( 12 ); + if ( parameters.useDepthPacking ) + _programLayers.enable( 13 ); + if ( parameters.dithering ) + _programLayers.enable( 14 ); + if ( parameters.transmission ) + _programLayers.enable( 15 ); + if ( parameters.sheen ) + _programLayers.enable( 16 ); + if ( parameters.opaque ) + _programLayers.enable( 17 ); + if ( parameters.pointsUvs ) + _programLayers.enable( 18 ); + if ( parameters.decodeVideoTexture ) + _programLayers.enable( 19 ); + if ( parameters.decodeVideoTextureEmissive ) + _programLayers.enable( 20 ); + if ( parameters.alphaToCoverage ) + _programLayers.enable( 21 ); - } ); + array.push( _programLayers.mask ); } - parse( /* data */ ) {} - - setCrossOrigin( crossOrigin ) { + function getUniforms( material ) { - this.crossOrigin = crossOrigin; - return this; + const shaderID = shaderIDs[ material.type ]; + let uniforms; - } + if ( shaderID ) { - setWithCredentials( value ) { + const shader = ShaderLib[ shaderID ]; + uniforms = UniformsUtils.clone( shader.uniforms ); - this.withCredentials = value; - return this; + } else { - } + uniforms = material.uniforms; - setPath( path ) { + } - this.path = path; - return this; + return uniforms; } - setResourcePath( resourcePath ) { - - this.resourcePath = resourcePath; - return this; + function acquireProgram( parameters, cacheKey ) { - } + let program; - setRequestHeader( requestHeader ) { + // Check if code has been already compiled + for ( let p = 0, pl = programs.length; p < pl; p ++ ) { - this.requestHeader = requestHeader; - return this; + const preexistingProgram = programs[ p ]; - } + if ( preexistingProgram.cacheKey === cacheKey ) { -} + program = preexistingProgram; + ++ program.usedTimes; -const loading = {}; + break; -class HttpError extends Error { + } - constructor( message, response ) { + } - super( message ); - this.response = response; + if ( program === undefined ) { - } + program = new WebGLProgram( renderer, cacheKey, parameters, bindingStates ); + programs.push( program ); -} + } -class FileLoader extends Loader { + return program; - constructor( manager ) { + } - super( manager ); + function releaseProgram( program ) { - } + if ( -- program.usedTimes === 0 ) { - load( url, onLoad, onProgress, onError ) { + // Remove from unordered set + const i = programs.indexOf( program ); + programs[ i ] = programs[ programs.length - 1 ]; + programs.pop(); - if ( url === undefined ) url = ''; + // Free WebGL resources + program.destroy(); - if ( this.path !== undefined ) url = this.path + url; + } - url = this.manager.resolveURL( url ); + } - const cached = Cache.get( url ); + function releaseShaderCache( material ) { - if ( cached !== undefined ) { + _customShaders.remove( material ); - this.manager.itemStart( url ); + } - setTimeout( () => { + function dispose() { - if ( onLoad ) onLoad( cached ); + _customShaders.dispose(); - this.manager.itemEnd( url ); + } - }, 0 ); + return { + getParameters: getParameters, + getProgramCacheKey: getProgramCacheKey, + getUniforms: getUniforms, + acquireProgram: acquireProgram, + releaseProgram: releaseProgram, + releaseShaderCache: releaseShaderCache, + // Exposed for resource monitoring & error feedback via renderer.info: + programs: programs, + dispose: dispose + }; - return cached; +} - } +function WebGLProperties() { - // Check if request is duplicate + let properties = new WeakMap(); - if ( loading[ url ] !== undefined ) { + function has( object ) { - loading[ url ].push( { + return properties.has( object ); - onLoad: onLoad, - onProgress: onProgress, - onError: onError + } - } ); + function get( object ) { - return; + let map = properties.get( object ); - } + if ( map === undefined ) { - // Initialise array for duplicate requests - loading[ url ] = []; + map = {}; + properties.set( object, map ); - loading[ url ].push( { - onLoad: onLoad, - onProgress: onProgress, - onError: onError, - } ); + } - // create request - const req = new Request( url, { - headers: new Headers( this.requestHeader ), - credentials: this.withCredentials ? 'include' : 'same-origin', - // An abort controller could be added within a future PR - } ); + return map; - // record states ( avoid data race ) - const mimeType = this.mimeType; - const responseType = this.responseType; + } - // start the fetch - fetch( req ) - .then( response => { + function remove( object ) { - if ( response.status === 200 || response.status === 0 ) { + properties.delete( object ); - // Some browsers return HTTP Status 0 when using non-http protocol - // e.g. 'file://' or 'data://'. Handle as success. + } - if ( response.status === 0 ) { + function update( object, key, value ) { - console.warn( 'THREE.FileLoader: HTTP Status 0 received.' ); + properties.get( object )[ key ] = value; - } + } - // Workaround: Checking if response.body === undefined for Alipay browser #23548 + function dispose() { - if ( typeof ReadableStream === 'undefined' || response.body === undefined || response.body.getReader === undefined ) { + properties = new WeakMap(); - return response; + } - } + return { + has: has, + get: get, + remove: remove, + update: update, + dispose: dispose + }; - const callbacks = loading[ url ]; - const reader = response.body.getReader(); +} - // Nginx needs X-File-Size check - // https://serverfault.com/questions/482875/why-does-nginx-remove-content-length-header-for-chunked-content - const contentLength = response.headers.get( 'Content-Length' ) || response.headers.get( 'X-File-Size' ); - const total = contentLength ? parseInt( contentLength ) : 0; - const lengthComputable = total !== 0; - let loaded = 0; +function painterSortStable( a, b ) { - // periodically read data into the new stream tracking while download progress - const stream = new ReadableStream( { - start( controller ) { + if ( a.groupOrder !== b.groupOrder ) { - readData(); + return a.groupOrder - b.groupOrder; - function readData() { + } else if ( a.renderOrder !== b.renderOrder ) { - reader.read().then( ( { done, value } ) => { + return a.renderOrder - b.renderOrder; - if ( done ) { + } else if ( a.material.id !== b.material.id ) { - controller.close(); + return a.material.id - b.material.id; - } else { + } else if ( a.z !== b.z ) { - loaded += value.byteLength; + return a.z - b.z; - const event = new ProgressEvent( 'progress', { lengthComputable, loaded, total } ); - for ( let i = 0, il = callbacks.length; i < il; i ++ ) { + } else { - const callback = callbacks[ i ]; - if ( callback.onProgress ) callback.onProgress( event ); + return a.id - b.id; - } + } - controller.enqueue( value ); - readData(); +} - } +function reversePainterSortStable( a, b ) { - } ); + if ( a.groupOrder !== b.groupOrder ) { - } + return a.groupOrder - b.groupOrder; - } + } else if ( a.renderOrder !== b.renderOrder ) { - } ); + return a.renderOrder - b.renderOrder; - return new Response( stream ); + } else if ( a.z !== b.z ) { - } else { + return b.z - a.z; - throw new HttpError( `fetch for "${response.url}" responded with ${response.status}: ${response.statusText}`, response ); + } else { - } + return a.id - b.id; - } ) - .then( response => { + } - switch ( responseType ) { +} - case 'arraybuffer': - return response.arrayBuffer(); +function WebGLRenderList() { - case 'blob': + const renderItems = []; + let renderItemsIndex = 0; - return response.blob(); + const opaque = []; + const transmissive = []; + const transparent = []; - case 'document': + function init() { - return response.text() - .then( text => { + renderItemsIndex = 0; - const parser = new DOMParser(); - return parser.parseFromString( text, mimeType ); + opaque.length = 0; + transmissive.length = 0; + transparent.length = 0; - } ); + } - case 'json': + function getNextRenderItem( object, geometry, material, groupOrder, z, group ) { - return response.json(); + let renderItem = renderItems[ renderItemsIndex ]; - default: + if ( renderItem === undefined ) { - if ( mimeType === undefined ) { + renderItem = { + id: object.id, + object: object, + geometry: geometry, + material: material, + groupOrder: groupOrder, + renderOrder: object.renderOrder, + z: z, + group: group + }; - return response.text(); + renderItems[ renderItemsIndex ] = renderItem; - } else { + } else { - // sniff encoding - const re = /charset="?([^;"\s]*)"?/i; - const exec = re.exec( mimeType ); - const label = exec && exec[ 1 ] ? exec[ 1 ].toLowerCase() : undefined; - const decoder = new TextDecoder( label ); - return response.arrayBuffer().then( ab => decoder.decode( ab ) ); + renderItem.id = object.id; + renderItem.object = object; + renderItem.geometry = geometry; + renderItem.material = material; + renderItem.groupOrder = groupOrder; + renderItem.renderOrder = object.renderOrder; + renderItem.z = z; + renderItem.group = group; - } + } - } + renderItemsIndex ++; - } ) - .then( data => { + return renderItem; - // Add to cache only on HTTP success, so that we do not cache - // error response bodies as proper responses to requests. - Cache.add( url, data ); + } - const callbacks = loading[ url ]; - delete loading[ url ]; + function push( object, geometry, material, groupOrder, z, group ) { - for ( let i = 0, il = callbacks.length; i < il; i ++ ) { + const renderItem = getNextRenderItem( object, geometry, material, groupOrder, z, group ); - const callback = callbacks[ i ]; - if ( callback.onLoad ) callback.onLoad( data ); + if ( material.transmission > 0.0 ) { - } + transmissive.push( renderItem ); - } ) - .catch( err => { + } else if ( material.transparent === true ) { - // Abort errors and other errors are handled the same + transparent.push( renderItem ); - const callbacks = loading[ url ]; + } else { - if ( callbacks === undefined ) { + opaque.push( renderItem ); - // When onLoad was called and url was deleted in `loading` - this.manager.itemError( url ); - throw err; + } - } + } - delete loading[ url ]; + function unshift( object, geometry, material, groupOrder, z, group ) { - for ( let i = 0, il = callbacks.length; i < il; i ++ ) { + const renderItem = getNextRenderItem( object, geometry, material, groupOrder, z, group ); - const callback = callbacks[ i ]; - if ( callback.onError ) callback.onError( err ); + if ( material.transmission > 0.0 ) { - } + transmissive.unshift( renderItem ); - this.manager.itemError( url ); + } else if ( material.transparent === true ) { - } ) - .finally( () => { + transparent.unshift( renderItem ); - this.manager.itemEnd( url ); + } else { - } ); + opaque.unshift( renderItem ); - this.manager.itemStart( url ); + } } - setResponseType( value ) { + function sort( customOpaqueSort, customTransparentSort ) { - this.responseType = value; - return this; + if ( opaque.length > 1 ) opaque.sort( customOpaqueSort || painterSortStable ); + if ( transmissive.length > 1 ) transmissive.sort( customTransparentSort || reversePainterSortStable ); + if ( transparent.length > 1 ) transparent.sort( customTransparentSort || reversePainterSortStable ); } - setMimeType( value ) { + function finish() { - this.mimeType = value; - return this; + // Clear references from inactive renderItems in the list - } + for ( let i = renderItemsIndex, il = renderItems.length; i < il; i ++ ) { -} + const renderItem = renderItems[ i ]; -class AnimationLoader extends Loader { + if ( renderItem.id === null ) break; - constructor( manager ) { + renderItem.id = null; + renderItem.object = null; + renderItem.geometry = null; + renderItem.material = null; + renderItem.group = null; - super( manager ); + } } - load( url, onLoad, onProgress, onError ) { + return { - const scope = this; + opaque: opaque, + transmissive: transmissive, + transparent: transparent, - const loader = new FileLoader( this.manager ); - loader.setPath( this.path ); - loader.setRequestHeader( this.requestHeader ); - loader.setWithCredentials( this.withCredentials ); - loader.load( url, function ( text ) { + init: init, + push: push, + unshift: unshift, + finish: finish, - try { + sort: sort + }; - onLoad( scope.parse( JSON.parse( text ) ) ); +} - } catch ( e ) { +function WebGLRenderLists() { - if ( onError ) { + let lists = new WeakMap(); - onError( e ); + function get( scene, renderCallDepth ) { - } else { + const listArray = lists.get( scene ); + let list; - console.error( e ); + if ( listArray === undefined ) { - } + list = new WebGLRenderList(); + lists.set( scene, [ list ] ); - scope.manager.itemError( url ); + } else { - } + if ( renderCallDepth >= listArray.length ) { - }, onProgress, onError ); + list = new WebGLRenderList(); + listArray.push( list ); - } + } else { - parse( json ) { + list = listArray[ renderCallDepth ]; - const animations = []; + } - for ( let i = 0; i < json.length; i ++ ) { + } - const clip = AnimationClip.parse( json[ i ] ); + return list; - animations.push( clip ); + } - } + function dispose() { - return animations; + lists = new WeakMap(); } + return { + get: get, + dispose: dispose + }; + } -/** - * Abstract Base class to block based textures loader (dds, pvr, ...) - * - * Sub classes have to implement the parse() method which will be used in load(). - */ +function UniformsCache() { -class CompressedTextureLoader extends Loader { + const lights = {}; - constructor( manager ) { + return { - super( manager ); + get: function ( light ) { - } + if ( lights[ light.id ] !== undefined ) { - load( url, onLoad, onProgress, onError ) { + return lights[ light.id ]; - const scope = this; + } - const images = []; + let uniforms; - const texture = new CompressedTexture(); + switch ( light.type ) { - const loader = new FileLoader( this.manager ); - loader.setPath( this.path ); - loader.setResponseType( 'arraybuffer' ); - loader.setRequestHeader( this.requestHeader ); - loader.setWithCredentials( scope.withCredentials ); + case 'DirectionalLight': + uniforms = { + direction: new Vector3(), + color: new Color() + }; + break; - let loaded = 0; + case 'SpotLight': + uniforms = { + position: new Vector3(), + direction: new Vector3(), + color: new Color(), + distance: 0, + coneCos: 0, + penumbraCos: 0, + decay: 0 + }; + break; - function loadTexture( i ) { + case 'PointLight': + uniforms = { + position: new Vector3(), + color: new Color(), + distance: 0, + decay: 0 + }; + break; - loader.load( url[ i ], function ( buffer ) { + case 'HemisphereLight': + uniforms = { + direction: new Vector3(), + skyColor: new Color(), + groundColor: new Color() + }; + break; - const texDatas = scope.parse( buffer, true ); + case 'RectAreaLight': + uniforms = { + color: new Color(), + position: new Vector3(), + halfWidth: new Vector3(), + halfHeight: new Vector3() + }; + break; - images[ i ] = { - width: texDatas.width, - height: texDatas.height, - format: texDatas.format, - mipmaps: texDatas.mipmaps - }; + } - loaded += 1; + lights[ light.id ] = uniforms; - if ( loaded === 6 ) { + return uniforms; - if ( texDatas.mipmapCount === 1 ) texture.minFilter = LinearFilter; + } - texture.image = images; - texture.format = texDatas.format; - texture.needsUpdate = true; + }; - if ( onLoad ) onLoad( texture ); +} - } +function ShadowUniformsCache() { - }, onProgress, onError ); + const lights = {}; - } + return { - if ( Array.isArray( url ) ) { + get: function ( light ) { - for ( let i = 0, il = url.length; i < il; ++ i ) { + if ( lights[ light.id ] !== undefined ) { - loadTexture( i ); + return lights[ light.id ]; } - } else { - - // compressed cubemap texture stored in a single DDS file + let uniforms; - loader.load( url, function ( buffer ) { + switch ( light.type ) { - const texDatas = scope.parse( buffer, true ); + case 'DirectionalLight': + uniforms = { + shadowIntensity: 1, + shadowBias: 0, + shadowNormalBias: 0, + shadowRadius: 1, + shadowMapSize: new Vector2() + }; + break; - if ( texDatas.isCubemap ) { + case 'SpotLight': + uniforms = { + shadowIntensity: 1, + shadowBias: 0, + shadowNormalBias: 0, + shadowRadius: 1, + shadowMapSize: new Vector2() + }; + break; - const faces = texDatas.mipmaps.length / texDatas.mipmapCount; + case 'PointLight': + uniforms = { + shadowIntensity: 1, + shadowBias: 0, + shadowNormalBias: 0, + shadowRadius: 1, + shadowMapSize: new Vector2(), + shadowCameraNear: 1, + shadowCameraFar: 1000 + }; + break; - for ( let f = 0; f < faces; f ++ ) { + // TODO (abelnation): set RectAreaLight shadow uniforms - images[ f ] = { mipmaps: [] }; + } - for ( let i = 0; i < texDatas.mipmapCount; i ++ ) { + lights[ light.id ] = uniforms; - images[ f ].mipmaps.push( texDatas.mipmaps[ f * texDatas.mipmapCount + i ] ); - images[ f ].format = texDatas.format; - images[ f ].width = texDatas.width; - images[ f ].height = texDatas.height; + return uniforms; - } + } - } + }; - texture.image = images; +} - } else { - texture.image.width = texDatas.width; - texture.image.height = texDatas.height; - texture.mipmaps = texDatas.mipmaps; - } +let nextVersion = 0; - if ( texDatas.mipmapCount === 1 ) { +function shadowCastingAndTexturingLightsFirst( lightA, lightB ) { - texture.minFilter = LinearFilter; + return ( lightB.castShadow ? 2 : 0 ) - ( lightA.castShadow ? 2 : 0 ) + ( lightB.map ? 1 : 0 ) - ( lightA.map ? 1 : 0 ); - } +} - texture.format = texDatas.format; - texture.needsUpdate = true; +function WebGLLights( extensions ) { - if ( onLoad ) onLoad( texture ); + const cache = new UniformsCache(); - }, onProgress, onError ); + const shadowCache = ShadowUniformsCache(); - } + const state = { - return texture; + version: 0, - } + hash: { + directionalLength: -1, + pointLength: -1, + spotLength: -1, + rectAreaLength: -1, + hemiLength: -1, + + numDirectionalShadows: -1, + numPointShadows: -1, + numSpotShadows: -1, + numSpotMaps: -1, + + numLightProbes: -1 + }, -} + ambient: [ 0, 0, 0 ], + probe: [], + directional: [], + directionalShadow: [], + directionalShadowMap: [], + directionalShadowMatrix: [], + spot: [], + spotLightMap: [], + spotShadow: [], + spotShadowMap: [], + spotLightMatrix: [], + rectArea: [], + rectAreaLTC1: null, + rectAreaLTC2: null, + point: [], + pointShadow: [], + pointShadowMap: [], + pointShadowMatrix: [], + hemi: [], + numSpotLightShadowsWithMaps: 0, + numLightProbes: 0 -class ImageLoader extends Loader { + }; - constructor( manager ) { + for ( let i = 0; i < 9; i ++ ) state.probe.push( new Vector3() ); - super( manager ); + const vector3 = new Vector3(); + const matrix4 = new Matrix4(); + const matrix42 = new Matrix4(); - } + function setup( lights ) { - load( url, onLoad, onProgress, onError ) { + let r = 0, g = 0, b = 0; - if ( this.path !== undefined ) url = this.path + url; + for ( let i = 0; i < 9; i ++ ) state.probe[ i ].set( 0, 0, 0 ); - url = this.manager.resolveURL( url ); + let directionalLength = 0; + let pointLength = 0; + let spotLength = 0; + let rectAreaLength = 0; + let hemiLength = 0; - const scope = this; + let numDirectionalShadows = 0; + let numPointShadows = 0; + let numSpotShadows = 0; + let numSpotMaps = 0; + let numSpotShadowsWithMaps = 0; - const cached = Cache.get( url ); + let numLightProbes = 0; - if ( cached !== undefined ) { + // ordering : [shadow casting + map texturing, map texturing, shadow casting, none ] + lights.sort( shadowCastingAndTexturingLightsFirst ); - scope.manager.itemStart( url ); + for ( let i = 0, l = lights.length; i < l; i ++ ) { - setTimeout( function () { + const light = lights[ i ]; - if ( onLoad ) onLoad( cached ); + const color = light.color; + const intensity = light.intensity; + const distance = light.distance; - scope.manager.itemEnd( url ); + const shadowMap = ( light.shadow && light.shadow.map ) ? light.shadow.map.texture : null; - }, 0 ); + if ( light.isAmbientLight ) { - return cached; + r += color.r * intensity; + g += color.g * intensity; + b += color.b * intensity; - } + } else if ( light.isLightProbe ) { - const image = createElementNS( 'img' ); + for ( let j = 0; j < 9; j ++ ) { - function onImageLoad() { + state.probe[ j ].addScaledVector( light.sh.coefficients[ j ], intensity ); - removeEventListeners(); + } - Cache.add( url, this ); + numLightProbes ++; - if ( onLoad ) onLoad( this ); + } else if ( light.isDirectionalLight ) { - scope.manager.itemEnd( url ); + const uniforms = cache.get( light ); - } + uniforms.color.copy( light.color ).multiplyScalar( light.intensity ); - function onImageError( event ) { + if ( light.castShadow ) { - removeEventListeners(); + const shadow = light.shadow; - if ( onError ) onError( event ); + const shadowUniforms = shadowCache.get( light ); - scope.manager.itemError( url ); - scope.manager.itemEnd( url ); + shadowUniforms.shadowIntensity = shadow.intensity; + shadowUniforms.shadowBias = shadow.bias; + shadowUniforms.shadowNormalBias = shadow.normalBias; + shadowUniforms.shadowRadius = shadow.radius; + shadowUniforms.shadowMapSize = shadow.mapSize; - } + state.directionalShadow[ directionalLength ] = shadowUniforms; + state.directionalShadowMap[ directionalLength ] = shadowMap; + state.directionalShadowMatrix[ directionalLength ] = light.shadow.matrix; - function removeEventListeners() { + numDirectionalShadows ++; - image.removeEventListener( 'load', onImageLoad, false ); - image.removeEventListener( 'error', onImageError, false ); + } - } + state.directional[ directionalLength ] = uniforms; - image.addEventListener( 'load', onImageLoad, false ); - image.addEventListener( 'error', onImageError, false ); + directionalLength ++; - if ( url.slice( 0, 5 ) !== 'data:' ) { + } else if ( light.isSpotLight ) { - if ( this.crossOrigin !== undefined ) image.crossOrigin = this.crossOrigin; + const uniforms = cache.get( light ); - } + uniforms.position.setFromMatrixPosition( light.matrixWorld ); - scope.manager.itemStart( url ); + uniforms.color.copy( color ).multiplyScalar( intensity ); + uniforms.distance = distance; - image.src = url; + uniforms.coneCos = Math.cos( light.angle ); + uniforms.penumbraCos = Math.cos( light.angle * ( 1 - light.penumbra ) ); + uniforms.decay = light.decay; - return image; + state.spot[ spotLength ] = uniforms; - } + const shadow = light.shadow; -} + if ( light.map ) { -class CubeTextureLoader extends Loader { + state.spotLightMap[ numSpotMaps ] = light.map; + numSpotMaps ++; - constructor( manager ) { + // make sure the lightMatrix is up to date + // TODO : do it if required only + shadow.updateMatrices( light ); - super( manager ); + if ( light.castShadow ) numSpotShadowsWithMaps ++; - } + } - load( urls, onLoad, onProgress, onError ) { + state.spotLightMatrix[ spotLength ] = shadow.matrix; - const texture = new CubeTexture(); + if ( light.castShadow ) { - const loader = new ImageLoader( this.manager ); - loader.setCrossOrigin( this.crossOrigin ); - loader.setPath( this.path ); + const shadowUniforms = shadowCache.get( light ); - let loaded = 0; + shadowUniforms.shadowIntensity = shadow.intensity; + shadowUniforms.shadowBias = shadow.bias; + shadowUniforms.shadowNormalBias = shadow.normalBias; + shadowUniforms.shadowRadius = shadow.radius; + shadowUniforms.shadowMapSize = shadow.mapSize; - function loadTexture( i ) { + state.spotShadow[ spotLength ] = shadowUniforms; + state.spotShadowMap[ spotLength ] = shadowMap; - loader.load( urls[ i ], function ( image ) { + numSpotShadows ++; - texture.images[ i ] = image; + } - loaded ++; + spotLength ++; - if ( loaded === 6 ) { + } else if ( light.isRectAreaLight ) { - texture.needsUpdate = true; + const uniforms = cache.get( light ); - if ( onLoad ) onLoad( texture ); + uniforms.color.copy( color ).multiplyScalar( intensity ); - } + uniforms.halfWidth.set( light.width * 0.5, 0.0, 0.0 ); + uniforms.halfHeight.set( 0.0, light.height * 0.5, 0.0 ); - }, undefined, onError ); + state.rectArea[ rectAreaLength ] = uniforms; - } + rectAreaLength ++; - for ( let i = 0; i < urls.length; ++ i ) { + } else if ( light.isPointLight ) { - loadTexture( i ); + const uniforms = cache.get( light ); - } + uniforms.color.copy( light.color ).multiplyScalar( light.intensity ); + uniforms.distance = light.distance; + uniforms.decay = light.decay; - return texture; + if ( light.castShadow ) { - } + const shadow = light.shadow; -} + const shadowUniforms = shadowCache.get( light ); -/** - * Abstract Base class to load generic binary textures formats (rgbe, hdr, ...) - * - * Sub classes have to implement the parse() method which will be used in load(). - */ + shadowUniforms.shadowIntensity = shadow.intensity; + shadowUniforms.shadowBias = shadow.bias; + shadowUniforms.shadowNormalBias = shadow.normalBias; + shadowUniforms.shadowRadius = shadow.radius; + shadowUniforms.shadowMapSize = shadow.mapSize; + shadowUniforms.shadowCameraNear = shadow.camera.near; + shadowUniforms.shadowCameraFar = shadow.camera.far; -class DataTextureLoader extends Loader { + state.pointShadow[ pointLength ] = shadowUniforms; + state.pointShadowMap[ pointLength ] = shadowMap; + state.pointShadowMatrix[ pointLength ] = light.shadow.matrix; - constructor( manager ) { + numPointShadows ++; - super( manager ); + } - } + state.point[ pointLength ] = uniforms; - load( url, onLoad, onProgress, onError ) { + pointLength ++; - const scope = this; + } else if ( light.isHemisphereLight ) { - const texture = new DataTexture(); + const uniforms = cache.get( light ); - const loader = new FileLoader( this.manager ); - loader.setResponseType( 'arraybuffer' ); - loader.setRequestHeader( this.requestHeader ); - loader.setPath( this.path ); - loader.setWithCredentials( scope.withCredentials ); - loader.load( url, function ( buffer ) { + uniforms.skyColor.copy( light.color ).multiplyScalar( intensity ); + uniforms.groundColor.copy( light.groundColor ).multiplyScalar( intensity ); - const texData = scope.parse( buffer ); + state.hemi[ hemiLength ] = uniforms; - if ( ! texData ) return; + hemiLength ++; - if ( texData.image !== undefined ) { + } - texture.image = texData.image; + } - } else if ( texData.data !== undefined ) { + if ( rectAreaLength > 0 ) { - texture.image.width = texData.width; - texture.image.height = texData.height; - texture.image.data = texData.data; + if ( extensions.has( 'OES_texture_float_linear' ) === true ) { - } + state.rectAreaLTC1 = UniformsLib.LTC_FLOAT_1; + state.rectAreaLTC2 = UniformsLib.LTC_FLOAT_2; - texture.wrapS = texData.wrapS !== undefined ? texData.wrapS : ClampToEdgeWrapping; - texture.wrapT = texData.wrapT !== undefined ? texData.wrapT : ClampToEdgeWrapping; + } else { - texture.magFilter = texData.magFilter !== undefined ? texData.magFilter : LinearFilter; - texture.minFilter = texData.minFilter !== undefined ? texData.minFilter : LinearFilter; + state.rectAreaLTC1 = UniformsLib.LTC_HALF_1; + state.rectAreaLTC2 = UniformsLib.LTC_HALF_2; - texture.anisotropy = texData.anisotropy !== undefined ? texData.anisotropy : 1; + } - if ( texData.colorSpace !== undefined ) { + } - texture.colorSpace = texData.colorSpace; + state.ambient[ 0 ] = r; + state.ambient[ 1 ] = g; + state.ambient[ 2 ] = b; - } else if ( texData.encoding !== undefined ) { // @deprecated, r152 + const hash = state.hash; - texture.encoding = texData.encoding; + if ( hash.directionalLength !== directionalLength || + hash.pointLength !== pointLength || + hash.spotLength !== spotLength || + hash.rectAreaLength !== rectAreaLength || + hash.hemiLength !== hemiLength || + hash.numDirectionalShadows !== numDirectionalShadows || + hash.numPointShadows !== numPointShadows || + hash.numSpotShadows !== numSpotShadows || + hash.numSpotMaps !== numSpotMaps || + hash.numLightProbes !== numLightProbes ) { - } + state.directional.length = directionalLength; + state.spot.length = spotLength; + state.rectArea.length = rectAreaLength; + state.point.length = pointLength; + state.hemi.length = hemiLength; - if ( texData.flipY !== undefined ) { + state.directionalShadow.length = numDirectionalShadows; + state.directionalShadowMap.length = numDirectionalShadows; + state.pointShadow.length = numPointShadows; + state.pointShadowMap.length = numPointShadows; + state.spotShadow.length = numSpotShadows; + state.spotShadowMap.length = numSpotShadows; + state.directionalShadowMatrix.length = numDirectionalShadows; + state.pointShadowMatrix.length = numPointShadows; + state.spotLightMatrix.length = numSpotShadows + numSpotMaps - numSpotShadowsWithMaps; + state.spotLightMap.length = numSpotMaps; + state.numSpotLightShadowsWithMaps = numSpotShadowsWithMaps; + state.numLightProbes = numLightProbes; - texture.flipY = texData.flipY; + hash.directionalLength = directionalLength; + hash.pointLength = pointLength; + hash.spotLength = spotLength; + hash.rectAreaLength = rectAreaLength; + hash.hemiLength = hemiLength; - } + hash.numDirectionalShadows = numDirectionalShadows; + hash.numPointShadows = numPointShadows; + hash.numSpotShadows = numSpotShadows; + hash.numSpotMaps = numSpotMaps; - if ( texData.format !== undefined ) { + hash.numLightProbes = numLightProbes; - texture.format = texData.format; + state.version = nextVersion ++; - } + } - if ( texData.type !== undefined ) { + } - texture.type = texData.type; + function setupView( lights, camera ) { - } + let directionalLength = 0; + let pointLength = 0; + let spotLength = 0; + let rectAreaLength = 0; + let hemiLength = 0; - if ( texData.mipmaps !== undefined ) { + const viewMatrix = camera.matrixWorldInverse; - texture.mipmaps = texData.mipmaps; - texture.minFilter = LinearMipmapLinearFilter; // presumably... + for ( let i = 0, l = lights.length; i < l; i ++ ) { - } + const light = lights[ i ]; - if ( texData.mipmapCount === 1 ) { + if ( light.isDirectionalLight ) { - texture.minFilter = LinearFilter; + const uniforms = state.directional[ directionalLength ]; - } + uniforms.direction.setFromMatrixPosition( light.matrixWorld ); + vector3.setFromMatrixPosition( light.target.matrixWorld ); + uniforms.direction.sub( vector3 ); + uniforms.direction.transformDirection( viewMatrix ); - if ( texData.generateMipmaps !== undefined ) { + directionalLength ++; - texture.generateMipmaps = texData.generateMipmaps; + } else if ( light.isSpotLight ) { - } + const uniforms = state.spot[ spotLength ]; - texture.needsUpdate = true; + uniforms.position.setFromMatrixPosition( light.matrixWorld ); + uniforms.position.applyMatrix4( viewMatrix ); - if ( onLoad ) onLoad( texture, texData ); + uniforms.direction.setFromMatrixPosition( light.matrixWorld ); + vector3.setFromMatrixPosition( light.target.matrixWorld ); + uniforms.direction.sub( vector3 ); + uniforms.direction.transformDirection( viewMatrix ); - }, onProgress, onError ); + spotLength ++; + } else if ( light.isRectAreaLight ) { - return texture; + const uniforms = state.rectArea[ rectAreaLength ]; - } + uniforms.position.setFromMatrixPosition( light.matrixWorld ); + uniforms.position.applyMatrix4( viewMatrix ); -} + // extract local rotation of light to derive width/height half vectors + matrix42.identity(); + matrix4.copy( light.matrixWorld ); + matrix4.premultiply( viewMatrix ); + matrix42.extractRotation( matrix4 ); -class TextureLoader extends Loader { + uniforms.halfWidth.set( light.width * 0.5, 0.0, 0.0 ); + uniforms.halfHeight.set( 0.0, light.height * 0.5, 0.0 ); - constructor( manager ) { + uniforms.halfWidth.applyMatrix4( matrix42 ); + uniforms.halfHeight.applyMatrix4( matrix42 ); - super( manager ); + rectAreaLength ++; - } + } else if ( light.isPointLight ) { - load( url, onLoad, onProgress, onError ) { + const uniforms = state.point[ pointLength ]; - const texture = new Texture(); + uniforms.position.setFromMatrixPosition( light.matrixWorld ); + uniforms.position.applyMatrix4( viewMatrix ); - const loader = new ImageLoader( this.manager ); - loader.setCrossOrigin( this.crossOrigin ); - loader.setPath( this.path ); + pointLength ++; - loader.load( url, function ( image ) { + } else if ( light.isHemisphereLight ) { - texture.image = image; - texture.needsUpdate = true; + const uniforms = state.hemi[ hemiLength ]; - if ( onLoad !== undefined ) { + uniforms.direction.setFromMatrixPosition( light.matrixWorld ); + uniforms.direction.transformDirection( viewMatrix ); - onLoad( texture ); + hemiLength ++; } - }, onProgress, onError ); - - return texture; + } } + return { + setup: setup, + setupView: setupView, + state: state + }; + } -class Light extends Object3D { +function WebGLRenderState( extensions ) { - constructor( color, intensity = 1 ) { + const lights = new WebGLLights( extensions ); - super(); + const lightsArray = []; + const shadowsArray = []; - this.isLight = true; + function init( camera ) { - this.type = 'Light'; + state.camera = camera; - this.color = new Color( color ); - this.intensity = intensity; + lightsArray.length = 0; + shadowsArray.length = 0; } - dispose() { + function pushLight( light ) { - // Empty here in base class; some subclasses override. + lightsArray.push( light ); } - copy( source, recursive ) { + function pushShadow( shadowLight ) { - super.copy( source, recursive ); + shadowsArray.push( shadowLight ); - this.color.copy( source.color ); - this.intensity = source.intensity; + } - return this; + function setupLights() { + + lights.setup( lightsArray ); } - toJSON( meta ) { + function setupLightsView( camera ) { - const data = super.toJSON( meta ); + lights.setupView( lightsArray, camera ); - data.object.color = this.color.getHex(); - data.object.intensity = this.intensity; + } - if ( this.groundColor !== undefined ) data.object.groundColor = this.groundColor.getHex(); + const state = { + lightsArray: lightsArray, + shadowsArray: shadowsArray, - if ( this.distance !== undefined ) data.object.distance = this.distance; - if ( this.angle !== undefined ) data.object.angle = this.angle; - if ( this.decay !== undefined ) data.object.decay = this.decay; - if ( this.penumbra !== undefined ) data.object.penumbra = this.penumbra; + camera: null, - if ( this.shadow !== undefined ) data.object.shadow = this.shadow.toJSON(); + lights: lights, - return data; + transmissionRenderTarget: {} + }; - } + return { + init: init, + state: state, + setupLights: setupLights, + setupLightsView: setupLightsView, + + pushLight: pushLight, + pushShadow: pushShadow + }; } -class HemisphereLight extends Light { +function WebGLRenderStates( extensions ) { - constructor( skyColor, groundColor, intensity ) { + let renderStates = new WeakMap(); - super( skyColor, intensity ); + function get( scene, renderCallDepth = 0 ) { - this.isHemisphereLight = true; + const renderStateArray = renderStates.get( scene ); + let renderState; - this.type = 'HemisphereLight'; + if ( renderStateArray === undefined ) { - this.position.copy( Object3D.DEFAULT_UP ); - this.updateMatrix(); + renderState = new WebGLRenderState( extensions ); + renderStates.set( scene, [ renderState ] ); - this.groundColor = new Color( groundColor ); + } else { - } + if ( renderCallDepth >= renderStateArray.length ) { - copy( source, recursive ) { + renderState = new WebGLRenderState( extensions ); + renderStateArray.push( renderState ); - super.copy( source, recursive ); + } else { - this.groundColor.copy( source.groundColor ); + renderState = renderStateArray[ renderCallDepth ]; - return this; + } + + } + + return renderState; + + } + + function dispose() { + + renderStates = new WeakMap(); } + return { + get: get, + dispose: dispose + }; + } -const _projScreenMatrix$1 = /*@__PURE__*/ new Matrix4(); -const _lightPositionWorld$1 = /*@__PURE__*/ new Vector3(); -const _lookTarget$1 = /*@__PURE__*/ new Vector3(); +const vertex = "void main() {\n\tgl_Position = vec4( position, 1.0 );\n}"; -class LightShadow { +const fragment = "uniform sampler2D shadow_pass;\nuniform vec2 resolution;\nuniform float radius;\n#include \nvoid main() {\n\tconst float samples = float( VSM_SAMPLES );\n\tfloat mean = 0.0;\n\tfloat squared_mean = 0.0;\n\tfloat uvStride = samples <= 1.0 ? 0.0 : 2.0 / ( samples - 1.0 );\n\tfloat uvStart = samples <= 1.0 ? 0.0 : - 1.0;\n\tfor ( float i = 0.0; i < samples; i ++ ) {\n\t\tfloat uvOffset = uvStart + i * uvStride;\n\t\t#ifdef HORIZONTAL_PASS\n\t\t\tvec2 distribution = unpackRGBATo2Half( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( uvOffset, 0.0 ) * radius ) / resolution ) );\n\t\t\tmean += distribution.x;\n\t\t\tsquared_mean += distribution.y * distribution.y + distribution.x * distribution.x;\n\t\t#else\n\t\t\tfloat depth = unpackRGBAToDepth( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( 0.0, uvOffset ) * radius ) / resolution ) );\n\t\t\tmean += depth;\n\t\t\tsquared_mean += depth * depth;\n\t\t#endif\n\t}\n\tmean = mean / samples;\n\tsquared_mean = squared_mean / samples;\n\tfloat std_dev = sqrt( squared_mean - mean * mean );\n\tgl_FragColor = pack2HalfToRGBA( vec2( mean, std_dev ) );\n}"; - constructor( camera ) { +function WebGLShadowMap( renderer, objects, capabilities ) { - this.camera = camera; + let _frustum = new Frustum(); - this.bias = 0; - this.normalBias = 0; - this.radius = 1; - this.blurSamples = 8; + const _shadowMapSize = new Vector2(), + _viewportSize = new Vector2(), - this.mapSize = new Vector2( 512, 512 ); + _viewport = new Vector4(), - this.map = null; - this.mapPass = null; - this.matrix = new Matrix4(); + _depthMaterial = new MeshDepthMaterial( { depthPacking: RGBADepthPacking } ), + _distanceMaterial = new MeshDistanceMaterial(), - this.autoUpdate = true; - this.needsUpdate = false; + _materialCache = {}, - this._frustum = new Frustum(); - this._frameExtents = new Vector2( 1, 1 ); + _maxTextureSize = capabilities.maxTextureSize; - this._viewportCount = 1; + const shadowSide = { [ FrontSide ]: BackSide, [ BackSide ]: FrontSide, [ DoubleSide ]: DoubleSide }; - this._viewports = [ + const shadowMaterialVertical = new ShaderMaterial( { + defines: { + VSM_SAMPLES: 8 + }, + uniforms: { + shadow_pass: { value: null }, + resolution: { value: new Vector2() }, + radius: { value: 4.0 } + }, - new Vector4( 0, 0, 1, 1 ) + vertexShader: vertex, + fragmentShader: fragment - ]; + } ); - } + const shadowMaterialHorizontal = shadowMaterialVertical.clone(); + shadowMaterialHorizontal.defines.HORIZONTAL_PASS = 1; - getViewportCount() { + const fullScreenTri = new BufferGeometry(); + fullScreenTri.setAttribute( + 'position', + new BufferAttribute( + new Float32Array( [ -1, -1, 0.5, 3, -1, 0.5, -1, 3, 0.5 ] ), + 3 + ) + ); - return this._viewportCount; + const fullScreenMesh = new Mesh( fullScreenTri, shadowMaterialVertical ); - } + const scope = this; - getFrustum() { + this.enabled = false; - return this._frustum; + this.autoUpdate = true; + this.needsUpdate = false; - } + this.type = PCFShadowMap; + let _previousType = this.type; - updateMatrices( light ) { + this.render = function ( lights, scene, camera ) { - const shadowCamera = this.camera; - const shadowMatrix = this.matrix; + if ( scope.enabled === false ) return; + if ( scope.autoUpdate === false && scope.needsUpdate === false ) return; - _lightPositionWorld$1.setFromMatrixPosition( light.matrixWorld ); - shadowCamera.position.copy( _lightPositionWorld$1 ); + if ( lights.length === 0 ) return; - _lookTarget$1.setFromMatrixPosition( light.target.matrixWorld ); - shadowCamera.lookAt( _lookTarget$1 ); - shadowCamera.updateMatrixWorld(); + const currentRenderTarget = renderer.getRenderTarget(); + const activeCubeFace = renderer.getActiveCubeFace(); + const activeMipmapLevel = renderer.getActiveMipmapLevel(); - _projScreenMatrix$1.multiplyMatrices( shadowCamera.projectionMatrix, shadowCamera.matrixWorldInverse ); - this._frustum.setFromProjectionMatrix( _projScreenMatrix$1 ); + const _state = renderer.state; - shadowMatrix.set( - 0.5, 0.0, 0.0, 0.5, - 0.0, 0.5, 0.0, 0.5, - 0.0, 0.0, 0.5, 0.5, - 0.0, 0.0, 0.0, 1.0 - ); + // Set GL state for depth map. + _state.setBlending( NoBlending ); + _state.buffers.color.setClear( 1, 1, 1, 1 ); + _state.buffers.depth.setTest( true ); + _state.setScissorTest( false ); - shadowMatrix.multiply( _projScreenMatrix$1 ); + // check for shadow map type changes - } + const toVSM = ( _previousType !== VSMShadowMap && this.type === VSMShadowMap ); + const fromVSM = ( _previousType === VSMShadowMap && this.type !== VSMShadowMap ); - getViewport( viewportIndex ) { + // render depth map - return this._viewports[ viewportIndex ]; + for ( let i = 0, il = lights.length; i < il; i ++ ) { - } + const light = lights[ i ]; + const shadow = light.shadow; - getFrameExtents() { + if ( shadow === undefined ) { - return this._frameExtents; + console.warn( 'THREE.WebGLShadowMap:', light, 'has no shadow.' ); + continue; - } + } - dispose() { + if ( shadow.autoUpdate === false && shadow.needsUpdate === false ) continue; - if ( this.map ) { + _shadowMapSize.copy( shadow.mapSize ); - this.map.dispose(); + const shadowFrameExtents = shadow.getFrameExtents(); - } + _shadowMapSize.multiply( shadowFrameExtents ); - if ( this.mapPass ) { + _viewportSize.copy( shadow.mapSize ); - this.mapPass.dispose(); + if ( _shadowMapSize.x > _maxTextureSize || _shadowMapSize.y > _maxTextureSize ) { - } + if ( _shadowMapSize.x > _maxTextureSize ) { - } + _viewportSize.x = Math.floor( _maxTextureSize / shadowFrameExtents.x ); + _shadowMapSize.x = _viewportSize.x * shadowFrameExtents.x; + shadow.mapSize.x = _viewportSize.x; - copy( source ) { + } - this.camera = source.camera.clone(); + if ( _shadowMapSize.y > _maxTextureSize ) { - this.bias = source.bias; - this.radius = source.radius; + _viewportSize.y = Math.floor( _maxTextureSize / shadowFrameExtents.y ); + _shadowMapSize.y = _viewportSize.y * shadowFrameExtents.y; + shadow.mapSize.y = _viewportSize.y; - this.mapSize.copy( source.mapSize ); + } - return this; + } - } + if ( shadow.map === null || toVSM === true || fromVSM === true ) { - clone() { + const pars = ( this.type !== VSMShadowMap ) ? { minFilter: NearestFilter, magFilter: NearestFilter } : {}; - return new this.constructor().copy( this ); + if ( shadow.map !== null ) { - } + shadow.map.dispose(); - toJSON() { + } - const object = {}; + shadow.map = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y, pars ); + shadow.map.texture.name = light.name + '.shadowMap'; - if ( this.bias !== 0 ) object.bias = this.bias; - if ( this.normalBias !== 0 ) object.normalBias = this.normalBias; - if ( this.radius !== 1 ) object.radius = this.radius; - if ( this.mapSize.x !== 512 || this.mapSize.y !== 512 ) object.mapSize = this.mapSize.toArray(); + shadow.camera.updateProjectionMatrix(); - object.camera = this.camera.toJSON( false ).object; - delete object.camera.matrix; + } - return object; + renderer.setRenderTarget( shadow.map ); + renderer.clear(); - } + const viewportCount = shadow.getViewportCount(); -} + for ( let vp = 0; vp < viewportCount; vp ++ ) { -class SpotLightShadow extends LightShadow { + const viewport = shadow.getViewport( vp ); - constructor() { + _viewport.set( + _viewportSize.x * viewport.x, + _viewportSize.y * viewport.y, + _viewportSize.x * viewport.z, + _viewportSize.y * viewport.w + ); - super( new PerspectiveCamera( 50, 1, 0.5, 500 ) ); + _state.viewport( _viewport ); - this.isSpotLightShadow = true; + shadow.updateMatrices( light, vp ); - this.focus = 1; + _frustum = shadow.getFrustum(); - } + renderObject( scene, camera, shadow.camera, light, this.type ); - updateMatrices( light ) { + } - const camera = this.camera; + // do blur pass for VSM - const fov = RAD2DEG * 2 * light.angle * this.focus; - const aspect = this.mapSize.width / this.mapSize.height; - const far = light.distance || camera.far; + if ( shadow.isPointLightShadow !== true && this.type === VSMShadowMap ) { - if ( fov !== camera.fov || aspect !== camera.aspect || far !== camera.far ) { + VSMPass( shadow, camera ); - camera.fov = fov; - camera.aspect = aspect; - camera.far = far; - camera.updateProjectionMatrix(); + } - } + shadow.needsUpdate = false; - super.updateMatrices( light ); + } - } + _previousType = this.type; - copy( source ) { + scope.needsUpdate = false; - super.copy( source ); + renderer.setRenderTarget( currentRenderTarget, activeCubeFace, activeMipmapLevel ); - this.focus = source.focus; + }; - return this; + function VSMPass( shadow, camera ) { - } + const geometry = objects.update( fullScreenMesh ); -} + if ( shadowMaterialVertical.defines.VSM_SAMPLES !== shadow.blurSamples ) { -class SpotLight extends Light { + shadowMaterialVertical.defines.VSM_SAMPLES = shadow.blurSamples; + shadowMaterialHorizontal.defines.VSM_SAMPLES = shadow.blurSamples; - constructor( color, intensity, distance = 0, angle = Math.PI / 3, penumbra = 0, decay = 2 ) { + shadowMaterialVertical.needsUpdate = true; + shadowMaterialHorizontal.needsUpdate = true; - super( color, intensity ); + } - this.isSpotLight = true; + if ( shadow.mapPass === null ) { - this.type = 'SpotLight'; + shadow.mapPass = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y ); - this.position.copy( Object3D.DEFAULT_UP ); - this.updateMatrix(); + } - this.target = new Object3D(); + // vertical pass - this.distance = distance; - this.angle = angle; - this.penumbra = penumbra; - this.decay = decay; + shadowMaterialVertical.uniforms.shadow_pass.value = shadow.map.texture; + shadowMaterialVertical.uniforms.resolution.value = shadow.mapSize; + shadowMaterialVertical.uniforms.radius.value = shadow.radius; + renderer.setRenderTarget( shadow.mapPass ); + renderer.clear(); + renderer.renderBufferDirect( camera, null, geometry, shadowMaterialVertical, fullScreenMesh, null ); - this.map = null; + // horizontal pass - this.shadow = new SpotLightShadow(); + shadowMaterialHorizontal.uniforms.shadow_pass.value = shadow.mapPass.texture; + shadowMaterialHorizontal.uniforms.resolution.value = shadow.mapSize; + shadowMaterialHorizontal.uniforms.radius.value = shadow.radius; + renderer.setRenderTarget( shadow.map ); + renderer.clear(); + renderer.renderBufferDirect( camera, null, geometry, shadowMaterialHorizontal, fullScreenMesh, null ); } - get power() { - - // compute the light's luminous power (in lumens) from its intensity (in candela) - // by convention for a spotlight, luminous power (lm) = π * luminous intensity (cd) - return this.intensity * Math.PI; + function getDepthMaterial( object, material, light, type ) { - } + let result = null; - set power( power ) { + const customMaterial = ( light.isPointLight === true ) ? object.customDistanceMaterial : object.customDepthMaterial; - // set the light's intensity (in candela) from the desired luminous power (in lumens) - this.intensity = power / Math.PI; + if ( customMaterial !== undefined ) { - } + result = customMaterial; - dispose() { + } else { - this.shadow.dispose(); + result = ( light.isPointLight === true ) ? _distanceMaterial : _depthMaterial; - } + if ( ( renderer.localClippingEnabled && material.clipShadows === true && Array.isArray( material.clippingPlanes ) && material.clippingPlanes.length !== 0 ) || + ( material.displacementMap && material.displacementScale !== 0 ) || + ( material.alphaMap && material.alphaTest > 0 ) || + ( material.map && material.alphaTest > 0 ) || + ( material.alphaToCoverage === true ) ) { - copy( source, recursive ) { + // in this case we need a unique material instance reflecting the + // appropriate state - super.copy( source, recursive ); + const keyA = result.uuid, keyB = material.uuid; - this.distance = source.distance; - this.angle = source.angle; - this.penumbra = source.penumbra; - this.decay = source.decay; + let materialsForVariant = _materialCache[ keyA ]; - this.target = source.target.clone(); + if ( materialsForVariant === undefined ) { - this.shadow = source.shadow.clone(); + materialsForVariant = {}; + _materialCache[ keyA ] = materialsForVariant; - return this; + } - } + let cachedMaterial = materialsForVariant[ keyB ]; -} + if ( cachedMaterial === undefined ) { -const _projScreenMatrix = /*@__PURE__*/ new Matrix4(); -const _lightPositionWorld = /*@__PURE__*/ new Vector3(); -const _lookTarget = /*@__PURE__*/ new Vector3(); + cachedMaterial = result.clone(); + materialsForVariant[ keyB ] = cachedMaterial; + material.addEventListener( 'dispose', onMaterialDispose ); -class PointLightShadow extends LightShadow { + } - constructor() { + result = cachedMaterial; - super( new PerspectiveCamera( 90, 1, 0.5, 500 ) ); + } - this.isPointLightShadow = true; + } - this._frameExtents = new Vector2( 4, 2 ); + result.visible = material.visible; + result.wireframe = material.wireframe; - this._viewportCount = 6; + if ( type === VSMShadowMap ) { - this._viewports = [ - // These viewports map a cube-map onto a 2D texture with the - // following orientation: - // - // xzXZ - // y Y - // - // X - Positive x direction - // x - Negative x direction - // Y - Positive y direction - // y - Negative y direction - // Z - Positive z direction - // z - Negative z direction + result.side = ( material.shadowSide !== null ) ? material.shadowSide : material.side; - // positive X - new Vector4( 2, 1, 1, 1 ), - // negative X - new Vector4( 0, 1, 1, 1 ), - // positive Z - new Vector4( 3, 1, 1, 1 ), - // negative Z - new Vector4( 1, 1, 1, 1 ), - // positive Y - new Vector4( 3, 0, 1, 1 ), - // negative Y - new Vector4( 1, 0, 1, 1 ) - ]; + } else { - this._cubeDirections = [ - new Vector3( 1, 0, 0 ), new Vector3( - 1, 0, 0 ), new Vector3( 0, 0, 1 ), - new Vector3( 0, 0, - 1 ), new Vector3( 0, 1, 0 ), new Vector3( 0, - 1, 0 ) - ]; + result.side = ( material.shadowSide !== null ) ? material.shadowSide : shadowSide[ material.side ]; - this._cubeUps = [ - new Vector3( 0, 1, 0 ), new Vector3( 0, 1, 0 ), new Vector3( 0, 1, 0 ), - new Vector3( 0, 1, 0 ), new Vector3( 0, 0, 1 ), new Vector3( 0, 0, - 1 ) - ]; + } - } + result.alphaMap = material.alphaMap; + result.alphaTest = ( material.alphaToCoverage === true ) ? 0.5 : material.alphaTest; // approximate alphaToCoverage by using a fixed alphaTest value + result.map = material.map; - updateMatrices( light, viewportIndex = 0 ) { + result.clipShadows = material.clipShadows; + result.clippingPlanes = material.clippingPlanes; + result.clipIntersection = material.clipIntersection; - const camera = this.camera; - const shadowMatrix = this.matrix; + result.displacementMap = material.displacementMap; + result.displacementScale = material.displacementScale; + result.displacementBias = material.displacementBias; - const far = light.distance || camera.far; + result.wireframeLinewidth = material.wireframeLinewidth; + result.linewidth = material.linewidth; - if ( far !== camera.far ) { + if ( light.isPointLight === true && result.isMeshDistanceMaterial === true ) { - camera.far = far; - camera.updateProjectionMatrix(); + const materialProperties = renderer.properties.get( result ); + materialProperties.light = light; } - _lightPositionWorld.setFromMatrixPosition( light.matrixWorld ); - camera.position.copy( _lightPositionWorld ); + return result; - _lookTarget.copy( camera.position ); - _lookTarget.add( this._cubeDirections[ viewportIndex ] ); - camera.up.copy( this._cubeUps[ viewportIndex ] ); - camera.lookAt( _lookTarget ); - camera.updateMatrixWorld(); + } - shadowMatrix.makeTranslation( - _lightPositionWorld.x, - _lightPositionWorld.y, - _lightPositionWorld.z ); + function renderObject( object, camera, shadowCamera, light, type ) { - _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); - this._frustum.setFromProjectionMatrix( _projScreenMatrix ); + if ( object.visible === false ) return; - } + const visible = object.layers.test( camera.layers ); -} + if ( visible && ( object.isMesh || object.isLine || object.isPoints ) ) { -class PointLight extends Light { + if ( ( object.castShadow || ( object.receiveShadow && type === VSMShadowMap ) ) && ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) ) { - constructor( color, intensity, distance = 0, decay = 2 ) { + object.modelViewMatrix.multiplyMatrices( shadowCamera.matrixWorldInverse, object.matrixWorld ); - super( color, intensity ); + const geometry = objects.update( object ); + const material = object.material; - this.isPointLight = true; + if ( Array.isArray( material ) ) { - this.type = 'PointLight'; + const groups = geometry.groups; - this.distance = distance; - this.decay = decay; + for ( let k = 0, kl = groups.length; k < kl; k ++ ) { - this.shadow = new PointLightShadow(); + const group = groups[ k ]; + const groupMaterial = material[ group.materialIndex ]; - } + if ( groupMaterial && groupMaterial.visible ) { - get power() { + const depthMaterial = getDepthMaterial( object, groupMaterial, light, type ); - // compute the light's luminous power (in lumens) from its intensity (in candela) - // for an isotropic light source, luminous power (lm) = 4 π luminous intensity (cd) - return this.intensity * 4 * Math.PI; + object.onBeforeShadow( renderer, object, camera, shadowCamera, geometry, depthMaterial, group ); - } + renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, group ); - set power( power ) { + object.onAfterShadow( renderer, object, camera, shadowCamera, geometry, depthMaterial, group ); - // set the light's intensity (in candela) from the desired luminous power (in lumens) - this.intensity = power / ( 4 * Math.PI ); + } - } + } - dispose() { + } else if ( material.visible ) { - this.shadow.dispose(); + const depthMaterial = getDepthMaterial( object, material, light, type ); - } + object.onBeforeShadow( renderer, object, camera, shadowCamera, geometry, depthMaterial, null ); - copy( source, recursive ) { + renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, null ); - super.copy( source, recursive ); + object.onAfterShadow( renderer, object, camera, shadowCamera, geometry, depthMaterial, null ); - this.distance = source.distance; - this.decay = source.decay; + } - this.shadow = source.shadow.clone(); + } - return this; + } - } + const children = object.children; -} + for ( let i = 0, l = children.length; i < l; i ++ ) { -class DirectionalLightShadow extends LightShadow { + renderObject( children[ i ], camera, shadowCamera, light, type ); - constructor() { + } - super( new OrthographicCamera( - 5, 5, 5, - 5, 0.5, 500 ) ); + } - this.isDirectionalLightShadow = true; + function onMaterialDispose( event ) { - } + const material = event.target; -} + material.removeEventListener( 'dispose', onMaterialDispose ); -class DirectionalLight extends Light { + // make sure to remove the unique distance/depth materials used for shadow map rendering - constructor( color, intensity ) { + for ( const id in _materialCache ) { - super( color, intensity ); + const cache = _materialCache[ id ]; - this.isDirectionalLight = true; + const uuid = event.target.uuid; - this.type = 'DirectionalLight'; + if ( uuid in cache ) { - this.position.copy( Object3D.DEFAULT_UP ); - this.updateMatrix(); + const shadowMaterial = cache[ uuid ]; + shadowMaterial.dispose(); + delete cache[ uuid ]; - this.target = new Object3D(); + } - this.shadow = new DirectionalLightShadow(); + } } - dispose() { +} - this.shadow.dispose(); +const reversedFuncs = { + [ NeverDepth ]: AlwaysDepth, + [ LessDepth ]: GreaterDepth, + [ EqualDepth ]: NotEqualDepth, + [ LessEqualDepth ]: GreaterEqualDepth, - } + [ AlwaysDepth ]: NeverDepth, + [ GreaterDepth ]: LessDepth, + [ NotEqualDepth ]: EqualDepth, + [ GreaterEqualDepth ]: LessEqualDepth, +}; - copy( source ) { +function WebGLState( gl, extensions ) { - super.copy( source ); + function ColorBuffer() { - this.target = source.target.clone(); - this.shadow = source.shadow.clone(); + let locked = false; - return this; + const color = new Vector4(); + let currentColorMask = null; + const currentColorClear = new Vector4( 0, 0, 0, 0 ); - } + return { -} + setMask: function ( colorMask ) { -class AmbientLight extends Light { + if ( currentColorMask !== colorMask && ! locked ) { - constructor( color, intensity ) { + gl.colorMask( colorMask, colorMask, colorMask, colorMask ); + currentColorMask = colorMask; - super( color, intensity ); + } - this.isAmbientLight = true; + }, - this.type = 'AmbientLight'; + setLocked: function ( lock ) { - } + locked = lock; -} + }, -class RectAreaLight extends Light { + setClear: function ( r, g, b, a, premultipliedAlpha ) { - constructor( color, intensity, width = 10, height = 10 ) { + if ( premultipliedAlpha === true ) { - super( color, intensity ); + r *= a; g *= a; b *= a; - this.isRectAreaLight = true; + } - this.type = 'RectAreaLight'; + color.set( r, g, b, a ); - this.width = width; - this.height = height; + if ( currentColorClear.equals( color ) === false ) { - } + gl.clearColor( r, g, b, a ); + currentColorClear.copy( color ); - get power() { + } - // compute the light's luminous power (in lumens) from its intensity (in nits) - return this.intensity * this.width * this.height * Math.PI; + }, - } + reset: function () { - set power( power ) { + locked = false; - // set the light's intensity (in nits) from the desired luminous power (in lumens) - this.intensity = power / ( this.width * this.height * Math.PI ); + currentColorMask = null; + currentColorClear.set( -1, 0, 0, 0 ); // set to invalid state - } + } - copy( source ) { + }; - super.copy( source ); + } - this.width = source.width; - this.height = source.height; + function DepthBuffer() { - return this; + let locked = false; - } + let currentReversed = false; + let currentDepthMask = null; + let currentDepthFunc = null; + let currentDepthClear = null; - toJSON( meta ) { + return { - const data = super.toJSON( meta ); + setReversed: function ( reversed ) { - data.object.width = this.width; - data.object.height = this.height; + if ( currentReversed !== reversed ) { - return data; + const ext = extensions.get( 'EXT_clip_control' ); - } + if ( reversed ) { -} + ext.clipControlEXT( ext.LOWER_LEFT_EXT, ext.ZERO_TO_ONE_EXT ); -/** - * Primary reference: - * https://graphics.stanford.edu/papers/envmap/envmap.pdf - * - * Secondary reference: - * https://www.ppsloan.org/publications/StupidSH36.pdf - */ + } else { -// 3-band SH defined by 9 coefficients + ext.clipControlEXT( ext.LOWER_LEFT_EXT, ext.NEGATIVE_ONE_TO_ONE_EXT ); -class SphericalHarmonics3 { + } - constructor() { + currentReversed = reversed; - this.isSphericalHarmonics3 = true; + const oldDepth = currentDepthClear; + currentDepthClear = null; + this.setClear( oldDepth ); - this.coefficients = []; + } - for ( let i = 0; i < 9; i ++ ) { + }, - this.coefficients.push( new Vector3() ); + getReversed: function () { - } + return currentReversed; - } + }, - set( coefficients ) { + setTest: function ( depthTest ) { - for ( let i = 0; i < 9; i ++ ) { + if ( depthTest ) { - this.coefficients[ i ].copy( coefficients[ i ] ); + enable( gl.DEPTH_TEST ); - } + } else { - return this; + disable( gl.DEPTH_TEST ); - } + } - zero() { + }, - for ( let i = 0; i < 9; i ++ ) { + setMask: function ( depthMask ) { - this.coefficients[ i ].set( 0, 0, 0 ); + if ( currentDepthMask !== depthMask && ! locked ) { - } + gl.depthMask( depthMask ); + currentDepthMask = depthMask; - return this; + } - } + }, - // get the radiance in the direction of the normal - // target is a Vector3 - getAt( normal, target ) { + setFunc: function ( depthFunc ) { - // normal is assumed to be unit length + if ( currentReversed ) depthFunc = reversedFuncs[ depthFunc ]; - const x = normal.x, y = normal.y, z = normal.z; + if ( currentDepthFunc !== depthFunc ) { - const coeff = this.coefficients; + switch ( depthFunc ) { - // band 0 - target.copy( coeff[ 0 ] ).multiplyScalar( 0.282095 ); + case NeverDepth: - // band 1 - target.addScaledVector( coeff[ 1 ], 0.488603 * y ); - target.addScaledVector( coeff[ 2 ], 0.488603 * z ); - target.addScaledVector( coeff[ 3 ], 0.488603 * x ); + gl.depthFunc( gl.NEVER ); + break; - // band 2 - target.addScaledVector( coeff[ 4 ], 1.092548 * ( x * y ) ); - target.addScaledVector( coeff[ 5 ], 1.092548 * ( y * z ) ); - target.addScaledVector( coeff[ 6 ], 0.315392 * ( 3.0 * z * z - 1.0 ) ); - target.addScaledVector( coeff[ 7 ], 1.092548 * ( x * z ) ); - target.addScaledVector( coeff[ 8 ], 0.546274 * ( x * x - y * y ) ); + case AlwaysDepth: - return target; + gl.depthFunc( gl.ALWAYS ); + break; - } + case LessDepth: - // get the irradiance (radiance convolved with cosine lobe) in the direction of the normal - // target is a Vector3 - // https://graphics.stanford.edu/papers/envmap/envmap.pdf - getIrradianceAt( normal, target ) { + gl.depthFunc( gl.LESS ); + break; - // normal is assumed to be unit length + case LessEqualDepth: - const x = normal.x, y = normal.y, z = normal.z; + gl.depthFunc( gl.LEQUAL ); + break; - const coeff = this.coefficients; + case EqualDepth: - // band 0 - target.copy( coeff[ 0 ] ).multiplyScalar( 0.886227 ); // π * 0.282095 + gl.depthFunc( gl.EQUAL ); + break; - // band 1 - target.addScaledVector( coeff[ 1 ], 2.0 * 0.511664 * y ); // ( 2 * π / 3 ) * 0.488603 - target.addScaledVector( coeff[ 2 ], 2.0 * 0.511664 * z ); - target.addScaledVector( coeff[ 3 ], 2.0 * 0.511664 * x ); + case GreaterEqualDepth: - // band 2 - target.addScaledVector( coeff[ 4 ], 2.0 * 0.429043 * x * y ); // ( π / 4 ) * 1.092548 - target.addScaledVector( coeff[ 5 ], 2.0 * 0.429043 * y * z ); - target.addScaledVector( coeff[ 6 ], 0.743125 * z * z - 0.247708 ); // ( π / 4 ) * 0.315392 * 3 - target.addScaledVector( coeff[ 7 ], 2.0 * 0.429043 * x * z ); - target.addScaledVector( coeff[ 8 ], 0.429043 * ( x * x - y * y ) ); // ( π / 4 ) * 0.546274 + gl.depthFunc( gl.GEQUAL ); + break; - return target; + case GreaterDepth: - } + gl.depthFunc( gl.GREATER ); + break; - add( sh ) { + case NotEqualDepth: - for ( let i = 0; i < 9; i ++ ) { + gl.depthFunc( gl.NOTEQUAL ); + break; - this.coefficients[ i ].add( sh.coefficients[ i ] ); + default: - } + gl.depthFunc( gl.LEQUAL ); - return this; + } - } + currentDepthFunc = depthFunc; - addScaledSH( sh, s ) { + } - for ( let i = 0; i < 9; i ++ ) { + }, - this.coefficients[ i ].addScaledVector( sh.coefficients[ i ], s ); + setLocked: function ( lock ) { - } + locked = lock; - return this; + }, - } + setClear: function ( depth ) { - scale( s ) { + if ( currentDepthClear !== depth ) { - for ( let i = 0; i < 9; i ++ ) { + if ( currentReversed ) { - this.coefficients[ i ].multiplyScalar( s ); + depth = 1 - depth; - } + } - return this; + gl.clearDepth( depth ); + currentDepthClear = depth; - } + } - lerp( sh, alpha ) { + }, - for ( let i = 0; i < 9; i ++ ) { + reset: function () { - this.coefficients[ i ].lerp( sh.coefficients[ i ], alpha ); + locked = false; - } + currentDepthMask = null; + currentDepthFunc = null; + currentDepthClear = null; + currentReversed = false; - return this; + } + + }; } - equals( sh ) { + function StencilBuffer() { - for ( let i = 0; i < 9; i ++ ) { + let locked = false; - if ( ! this.coefficients[ i ].equals( sh.coefficients[ i ] ) ) { + let currentStencilMask = null; + let currentStencilFunc = null; + let currentStencilRef = null; + let currentStencilFuncMask = null; + let currentStencilFail = null; + let currentStencilZFail = null; + let currentStencilZPass = null; + let currentStencilClear = null; - return false; + return { - } + setTest: function ( stencilTest ) { - } + if ( ! locked ) { - return true; + if ( stencilTest ) { - } + enable( gl.STENCIL_TEST ); - copy( sh ) { + } else { - return this.set( sh.coefficients ); + disable( gl.STENCIL_TEST ); - } + } - clone() { + } - return new this.constructor().copy( this ); + }, - } + setMask: function ( stencilMask ) { - fromArray( array, offset = 0 ) { + if ( currentStencilMask !== stencilMask && ! locked ) { - const coefficients = this.coefficients; + gl.stencilMask( stencilMask ); + currentStencilMask = stencilMask; - for ( let i = 0; i < 9; i ++ ) { + } - coefficients[ i ].fromArray( array, offset + ( i * 3 ) ); + }, - } + setFunc: function ( stencilFunc, stencilRef, stencilMask ) { - return this; + if ( currentStencilFunc !== stencilFunc || + currentStencilRef !== stencilRef || + currentStencilFuncMask !== stencilMask ) { - } + gl.stencilFunc( stencilFunc, stencilRef, stencilMask ); - toArray( array = [], offset = 0 ) { + currentStencilFunc = stencilFunc; + currentStencilRef = stencilRef; + currentStencilFuncMask = stencilMask; - const coefficients = this.coefficients; + } - for ( let i = 0; i < 9; i ++ ) { + }, - coefficients[ i ].toArray( array, offset + ( i * 3 ) ); + setOp: function ( stencilFail, stencilZFail, stencilZPass ) { - } + if ( currentStencilFail !== stencilFail || + currentStencilZFail !== stencilZFail || + currentStencilZPass !== stencilZPass ) { - return array; + gl.stencilOp( stencilFail, stencilZFail, stencilZPass ); - } + currentStencilFail = stencilFail; + currentStencilZFail = stencilZFail; + currentStencilZPass = stencilZPass; - // evaluate the basis functions - // shBasis is an Array[ 9 ] - static getBasisAt( normal, shBasis ) { + } - // normal is assumed to be unit length + }, - const x = normal.x, y = normal.y, z = normal.z; + setLocked: function ( lock ) { - // band 0 - shBasis[ 0 ] = 0.282095; + locked = lock; - // band 1 - shBasis[ 1 ] = 0.488603 * y; - shBasis[ 2 ] = 0.488603 * z; - shBasis[ 3 ] = 0.488603 * x; + }, - // band 2 - shBasis[ 4 ] = 1.092548 * x * y; - shBasis[ 5 ] = 1.092548 * y * z; - shBasis[ 6 ] = 0.315392 * ( 3 * z * z - 1 ); - shBasis[ 7 ] = 1.092548 * x * z; - shBasis[ 8 ] = 0.546274 * ( x * x - y * y ); + setClear: function ( stencil ) { - } + if ( currentStencilClear !== stencil ) { -} + gl.clearStencil( stencil ); + currentStencilClear = stencil; -class LightProbe extends Light { + } - constructor( sh = new SphericalHarmonics3(), intensity = 1 ) { + }, - super( undefined, intensity ); + reset: function () { - this.isLightProbe = true; + locked = false; - this.sh = sh; + currentStencilMask = null; + currentStencilFunc = null; + currentStencilRef = null; + currentStencilFuncMask = null; + currentStencilFail = null; + currentStencilZFail = null; + currentStencilZPass = null; + currentStencilClear = null; - } + } - copy( source ) { + }; - super.copy( source ); + } - this.sh.copy( source.sh ); + // - return this; + const colorBuffer = new ColorBuffer(); + const depthBuffer = new DepthBuffer(); + const stencilBuffer = new StencilBuffer(); - } + const uboBindings = new WeakMap(); + const uboProgramMap = new WeakMap(); - fromJSON( json ) { + let enabledCapabilities = {}; - this.intensity = json.intensity; // TODO: Move this bit to Light.fromJSON(); - this.sh.fromArray( json.sh ); + let currentBoundFramebuffers = {}; + let currentDrawbuffers = new WeakMap(); + let defaultDrawbuffers = []; - return this; + let currentProgram = null; - } + let currentBlendingEnabled = false; + let currentBlending = null; + let currentBlendEquation = null; + let currentBlendSrc = null; + let currentBlendDst = null; + let currentBlendEquationAlpha = null; + let currentBlendSrcAlpha = null; + let currentBlendDstAlpha = null; + let currentBlendColor = new Color( 0, 0, 0 ); + let currentBlendAlpha = 0; + let currentPremultipledAlpha = false; - toJSON( meta ) { + let currentFlipSided = null; + let currentCullFace = null; - const data = super.toJSON( meta ); + let currentLineWidth = null; - data.object.sh = this.sh.toArray(); + let currentPolygonOffsetFactor = null; + let currentPolygonOffsetUnits = null; - return data; + const maxTextures = gl.getParameter( gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS ); - } + let lineWidthAvailable = false; + let version = 0; + const glVersion = gl.getParameter( gl.VERSION ); -} + if ( glVersion.indexOf( 'WebGL' ) !== -1 ) { -class MaterialLoader extends Loader { + version = parseFloat( /^WebGL (\d)/.exec( glVersion )[ 1 ] ); + lineWidthAvailable = ( version >= 1.0 ); - constructor( manager ) { + } else if ( glVersion.indexOf( 'OpenGL ES' ) !== -1 ) { - super( manager ); - this.textures = {}; + version = parseFloat( /^OpenGL ES (\d)/.exec( glVersion )[ 1 ] ); + lineWidthAvailable = ( version >= 2.0 ); } - load( url, onLoad, onProgress, onError ) { - - const scope = this; + let currentTextureSlot = null; + let currentBoundTextures = {}; - const loader = new FileLoader( scope.manager ); - loader.setPath( scope.path ); - loader.setRequestHeader( scope.requestHeader ); - loader.setWithCredentials( scope.withCredentials ); - loader.load( url, function ( text ) { + const scissorParam = gl.getParameter( gl.SCISSOR_BOX ); + const viewportParam = gl.getParameter( gl.VIEWPORT ); - try { + const currentScissor = new Vector4().fromArray( scissorParam ); + const currentViewport = new Vector4().fromArray( viewportParam ); - onLoad( scope.parse( JSON.parse( text ) ) ); + function createTexture( type, target, count, dimensions ) { - } catch ( e ) { + const data = new Uint8Array( 4 ); // 4 is required to match default unpack alignment of 4. + const texture = gl.createTexture(); - if ( onError ) { + gl.bindTexture( type, texture ); + gl.texParameteri( type, gl.TEXTURE_MIN_FILTER, gl.NEAREST ); + gl.texParameteri( type, gl.TEXTURE_MAG_FILTER, gl.NEAREST ); - onError( e ); + for ( let i = 0; i < count; i ++ ) { - } else { + if ( type === gl.TEXTURE_3D || type === gl.TEXTURE_2D_ARRAY ) { - console.error( e ); + gl.texImage3D( target, 0, gl.RGBA, 1, 1, dimensions, 0, gl.RGBA, gl.UNSIGNED_BYTE, data ); - } + } else { - scope.manager.itemError( url ); + gl.texImage2D( target + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, data ); } - }, onProgress, onError ); + } + + return texture; } - parse( json ) { + const emptyTextures = {}; + emptyTextures[ gl.TEXTURE_2D ] = createTexture( gl.TEXTURE_2D, gl.TEXTURE_2D, 1 ); + emptyTextures[ gl.TEXTURE_CUBE_MAP ] = createTexture( gl.TEXTURE_CUBE_MAP, gl.TEXTURE_CUBE_MAP_POSITIVE_X, 6 ); + emptyTextures[ gl.TEXTURE_2D_ARRAY ] = createTexture( gl.TEXTURE_2D_ARRAY, gl.TEXTURE_2D_ARRAY, 1, 1 ); + emptyTextures[ gl.TEXTURE_3D ] = createTexture( gl.TEXTURE_3D, gl.TEXTURE_3D, 1, 1 ); - const textures = this.textures; + // init - function getTexture( name ) { + colorBuffer.setClear( 0, 0, 0, 1 ); + depthBuffer.setClear( 1 ); + stencilBuffer.setClear( 0 ); - if ( textures[ name ] === undefined ) { + enable( gl.DEPTH_TEST ); + depthBuffer.setFunc( LessEqualDepth ); - console.warn( 'THREE.MaterialLoader: Undefined texture', name ); + setFlipSided( false ); + setCullFace( CullFaceBack ); + enable( gl.CULL_FACE ); - } + setBlending( NoBlending ); - return textures[ name ]; + // - } + function enable( id ) { - const material = MaterialLoader.createMaterialFromType( json.type ); + if ( enabledCapabilities[ id ] !== true ) { - if ( json.uuid !== undefined ) material.uuid = json.uuid; - if ( json.name !== undefined ) material.name = json.name; - if ( json.color !== undefined && material.color !== undefined ) material.color.setHex( json.color ); - if ( json.roughness !== undefined ) material.roughness = json.roughness; - if ( json.metalness !== undefined ) material.metalness = json.metalness; - if ( json.sheen !== undefined ) material.sheen = json.sheen; - if ( json.sheenColor !== undefined ) material.sheenColor = new Color().setHex( json.sheenColor ); - if ( json.sheenRoughness !== undefined ) material.sheenRoughness = json.sheenRoughness; - if ( json.emissive !== undefined && material.emissive !== undefined ) material.emissive.setHex( json.emissive ); - if ( json.specular !== undefined && material.specular !== undefined ) material.specular.setHex( json.specular ); - if ( json.specularIntensity !== undefined ) material.specularIntensity = json.specularIntensity; - if ( json.specularColor !== undefined && material.specularColor !== undefined ) material.specularColor.setHex( json.specularColor ); - if ( json.shininess !== undefined ) material.shininess = json.shininess; - if ( json.clearcoat !== undefined ) material.clearcoat = json.clearcoat; - if ( json.clearcoatRoughness !== undefined ) material.clearcoatRoughness = json.clearcoatRoughness; - if ( json.iridescence !== undefined ) material.iridescence = json.iridescence; - if ( json.iridescenceIOR !== undefined ) material.iridescenceIOR = json.iridescenceIOR; - if ( json.iridescenceThicknessRange !== undefined ) material.iridescenceThicknessRange = json.iridescenceThicknessRange; - if ( json.transmission !== undefined ) material.transmission = json.transmission; - if ( json.thickness !== undefined ) material.thickness = json.thickness; - if ( json.attenuationDistance !== undefined ) material.attenuationDistance = json.attenuationDistance; - if ( json.attenuationColor !== undefined && material.attenuationColor !== undefined ) material.attenuationColor.setHex( json.attenuationColor ); - if ( json.anisotropy !== undefined ) material.anisotropy = json.anisotropy; - if ( json.anisotropyRotation !== undefined ) material.anisotropyRotation = json.anisotropyRotation; - if ( json.fog !== undefined ) material.fog = json.fog; - if ( json.flatShading !== undefined ) material.flatShading = json.flatShading; - if ( json.blending !== undefined ) material.blending = json.blending; - if ( json.combine !== undefined ) material.combine = json.combine; - if ( json.side !== undefined ) material.side = json.side; - if ( json.shadowSide !== undefined ) material.shadowSide = json.shadowSide; - if ( json.opacity !== undefined ) material.opacity = json.opacity; - if ( json.transparent !== undefined ) material.transparent = json.transparent; - if ( json.alphaTest !== undefined ) material.alphaTest = json.alphaTest; - if ( json.depthTest !== undefined ) material.depthTest = json.depthTest; - if ( json.depthWrite !== undefined ) material.depthWrite = json.depthWrite; - if ( json.colorWrite !== undefined ) material.colorWrite = json.colorWrite; + gl.enable( id ); + enabledCapabilities[ id ] = true; - if ( json.stencilWrite !== undefined ) material.stencilWrite = json.stencilWrite; - if ( json.stencilWriteMask !== undefined ) material.stencilWriteMask = json.stencilWriteMask; - if ( json.stencilFunc !== undefined ) material.stencilFunc = json.stencilFunc; - if ( json.stencilRef !== undefined ) material.stencilRef = json.stencilRef; - if ( json.stencilFuncMask !== undefined ) material.stencilFuncMask = json.stencilFuncMask; - if ( json.stencilFail !== undefined ) material.stencilFail = json.stencilFail; - if ( json.stencilZFail !== undefined ) material.stencilZFail = json.stencilZFail; - if ( json.stencilZPass !== undefined ) material.stencilZPass = json.stencilZPass; + } - if ( json.wireframe !== undefined ) material.wireframe = json.wireframe; - if ( json.wireframeLinewidth !== undefined ) material.wireframeLinewidth = json.wireframeLinewidth; - if ( json.wireframeLinecap !== undefined ) material.wireframeLinecap = json.wireframeLinecap; - if ( json.wireframeLinejoin !== undefined ) material.wireframeLinejoin = json.wireframeLinejoin; + } - if ( json.rotation !== undefined ) material.rotation = json.rotation; + function disable( id ) { - if ( json.linewidth !== 1 ) material.linewidth = json.linewidth; - if ( json.dashSize !== undefined ) material.dashSize = json.dashSize; - if ( json.gapSize !== undefined ) material.gapSize = json.gapSize; - if ( json.scale !== undefined ) material.scale = json.scale; + if ( enabledCapabilities[ id ] !== false ) { - if ( json.polygonOffset !== undefined ) material.polygonOffset = json.polygonOffset; - if ( json.polygonOffsetFactor !== undefined ) material.polygonOffsetFactor = json.polygonOffsetFactor; - if ( json.polygonOffsetUnits !== undefined ) material.polygonOffsetUnits = json.polygonOffsetUnits; + gl.disable( id ); + enabledCapabilities[ id ] = false; - if ( json.dithering !== undefined ) material.dithering = json.dithering; + } - if ( json.alphaToCoverage !== undefined ) material.alphaToCoverage = json.alphaToCoverage; - if ( json.premultipliedAlpha !== undefined ) material.premultipliedAlpha = json.premultipliedAlpha; - if ( json.forceSinglePass !== undefined ) material.forceSinglePass = json.forceSinglePass; + } - if ( json.visible !== undefined ) material.visible = json.visible; + function bindFramebuffer( target, framebuffer ) { - if ( json.toneMapped !== undefined ) material.toneMapped = json.toneMapped; + if ( currentBoundFramebuffers[ target ] !== framebuffer ) { - if ( json.userData !== undefined ) material.userData = json.userData; + gl.bindFramebuffer( target, framebuffer ); - if ( json.vertexColors !== undefined ) { + currentBoundFramebuffers[ target ] = framebuffer; - if ( typeof json.vertexColors === 'number' ) { + // gl.DRAW_FRAMEBUFFER is equivalent to gl.FRAMEBUFFER - material.vertexColors = ( json.vertexColors > 0 ) ? true : false; + if ( target === gl.DRAW_FRAMEBUFFER ) { - } else { + currentBoundFramebuffers[ gl.FRAMEBUFFER ] = framebuffer; - material.vertexColors = json.vertexColors; + } + + if ( target === gl.FRAMEBUFFER ) { + + currentBoundFramebuffers[ gl.DRAW_FRAMEBUFFER ] = framebuffer; } + return true; + } - // Shader Material + return false; - if ( json.uniforms !== undefined ) { + } - for ( const name in json.uniforms ) { + function drawBuffers( renderTarget, framebuffer ) { - const uniform = json.uniforms[ name ]; + let drawBuffers = defaultDrawbuffers; - material.uniforms[ name ] = {}; + let needsUpdate = false; - switch ( uniform.type ) { + if ( renderTarget ) { - case 't': - material.uniforms[ name ].value = getTexture( uniform.value ); - break; + drawBuffers = currentDrawbuffers.get( framebuffer ); - case 'c': - material.uniforms[ name ].value = new Color().setHex( uniform.value ); - break; + if ( drawBuffers === undefined ) { - case 'v2': - material.uniforms[ name ].value = new Vector2().fromArray( uniform.value ); - break; + drawBuffers = []; + currentDrawbuffers.set( framebuffer, drawBuffers ); - case 'v3': - material.uniforms[ name ].value = new Vector3().fromArray( uniform.value ); - break; + } - case 'v4': - material.uniforms[ name ].value = new Vector4().fromArray( uniform.value ); - break; + const textures = renderTarget.textures; - case 'm3': - material.uniforms[ name ].value = new Matrix3().fromArray( uniform.value ); - break; + if ( drawBuffers.length !== textures.length || drawBuffers[ 0 ] !== gl.COLOR_ATTACHMENT0 ) { - case 'm4': - material.uniforms[ name ].value = new Matrix4().fromArray( uniform.value ); - break; + for ( let i = 0, il = textures.length; i < il; i ++ ) { - default: - material.uniforms[ name ].value = uniform.value; + drawBuffers[ i ] = gl.COLOR_ATTACHMENT0 + i; } - } + drawBuffers.length = textures.length; - } + needsUpdate = true; - if ( json.defines !== undefined ) material.defines = json.defines; - if ( json.vertexShader !== undefined ) material.vertexShader = json.vertexShader; - if ( json.fragmentShader !== undefined ) material.fragmentShader = json.fragmentShader; - if ( json.glslVersion !== undefined ) material.glslVersion = json.glslVersion; + } - if ( json.extensions !== undefined ) { + } else { - for ( const key in json.extensions ) { + if ( drawBuffers[ 0 ] !== gl.BACK ) { - material.extensions[ key ] = json.extensions[ key ]; + drawBuffers[ 0 ] = gl.BACK; + + needsUpdate = true; } } - if ( json.lights !== undefined ) material.lights = json.lights; - if ( json.clipping !== undefined ) material.clipping = json.clipping; - - // for PointsMaterial - - if ( json.size !== undefined ) material.size = json.size; - if ( json.sizeAttenuation !== undefined ) material.sizeAttenuation = json.sizeAttenuation; + if ( needsUpdate ) { - // maps + gl.drawBuffers( drawBuffers ); - if ( json.map !== undefined ) material.map = getTexture( json.map ); - if ( json.matcap !== undefined ) material.matcap = getTexture( json.matcap ); + } - if ( json.alphaMap !== undefined ) material.alphaMap = getTexture( json.alphaMap ); + } - if ( json.bumpMap !== undefined ) material.bumpMap = getTexture( json.bumpMap ); - if ( json.bumpScale !== undefined ) material.bumpScale = json.bumpScale; + function useProgram( program ) { - if ( json.normalMap !== undefined ) material.normalMap = getTexture( json.normalMap ); - if ( json.normalMapType !== undefined ) material.normalMapType = json.normalMapType; - if ( json.normalScale !== undefined ) { + if ( currentProgram !== program ) { - let normalScale = json.normalScale; + gl.useProgram( program ); - if ( Array.isArray( normalScale ) === false ) { + currentProgram = program; - // Blender exporter used to export a scalar. See #7459 + return true; - normalScale = [ normalScale, normalScale ]; + } - } + return false; - material.normalScale = new Vector2().fromArray( normalScale ); + } - } + const equationToGL = { + [ AddEquation ]: gl.FUNC_ADD, + [ SubtractEquation ]: gl.FUNC_SUBTRACT, + [ ReverseSubtractEquation ]: gl.FUNC_REVERSE_SUBTRACT + }; - if ( json.displacementMap !== undefined ) material.displacementMap = getTexture( json.displacementMap ); - if ( json.displacementScale !== undefined ) material.displacementScale = json.displacementScale; - if ( json.displacementBias !== undefined ) material.displacementBias = json.displacementBias; + equationToGL[ MinEquation ] = gl.MIN; + equationToGL[ MaxEquation ] = gl.MAX; - if ( json.roughnessMap !== undefined ) material.roughnessMap = getTexture( json.roughnessMap ); - if ( json.metalnessMap !== undefined ) material.metalnessMap = getTexture( json.metalnessMap ); + const factorToGL = { + [ ZeroFactor ]: gl.ZERO, + [ OneFactor ]: gl.ONE, + [ SrcColorFactor ]: gl.SRC_COLOR, + [ SrcAlphaFactor ]: gl.SRC_ALPHA, + [ SrcAlphaSaturateFactor ]: gl.SRC_ALPHA_SATURATE, + [ DstColorFactor ]: gl.DST_COLOR, + [ DstAlphaFactor ]: gl.DST_ALPHA, + [ OneMinusSrcColorFactor ]: gl.ONE_MINUS_SRC_COLOR, + [ OneMinusSrcAlphaFactor ]: gl.ONE_MINUS_SRC_ALPHA, + [ OneMinusDstColorFactor ]: gl.ONE_MINUS_DST_COLOR, + [ OneMinusDstAlphaFactor ]: gl.ONE_MINUS_DST_ALPHA, + [ ConstantColorFactor ]: gl.CONSTANT_COLOR, + [ OneMinusConstantColorFactor ]: gl.ONE_MINUS_CONSTANT_COLOR, + [ ConstantAlphaFactor ]: gl.CONSTANT_ALPHA, + [ OneMinusConstantAlphaFactor ]: gl.ONE_MINUS_CONSTANT_ALPHA + }; - if ( json.emissiveMap !== undefined ) material.emissiveMap = getTexture( json.emissiveMap ); - if ( json.emissiveIntensity !== undefined ) material.emissiveIntensity = json.emissiveIntensity; + function setBlending( blending, blendEquation, blendSrc, blendDst, blendEquationAlpha, blendSrcAlpha, blendDstAlpha, blendColor, blendAlpha, premultipliedAlpha ) { - if ( json.specularMap !== undefined ) material.specularMap = getTexture( json.specularMap ); - if ( json.specularIntensityMap !== undefined ) material.specularIntensityMap = getTexture( json.specularIntensityMap ); - if ( json.specularColorMap !== undefined ) material.specularColorMap = getTexture( json.specularColorMap ); + if ( blending === NoBlending ) { - if ( json.envMap !== undefined ) material.envMap = getTexture( json.envMap ); - if ( json.envMapIntensity !== undefined ) material.envMapIntensity = json.envMapIntensity; + if ( currentBlendingEnabled === true ) { - if ( json.reflectivity !== undefined ) material.reflectivity = json.reflectivity; - if ( json.refractionRatio !== undefined ) material.refractionRatio = json.refractionRatio; + disable( gl.BLEND ); + currentBlendingEnabled = false; - if ( json.lightMap !== undefined ) material.lightMap = getTexture( json.lightMap ); - if ( json.lightMapIntensity !== undefined ) material.lightMapIntensity = json.lightMapIntensity; + } - if ( json.aoMap !== undefined ) material.aoMap = getTexture( json.aoMap ); - if ( json.aoMapIntensity !== undefined ) material.aoMapIntensity = json.aoMapIntensity; + return; - if ( json.gradientMap !== undefined ) material.gradientMap = getTexture( json.gradientMap ); + } - if ( json.clearcoatMap !== undefined ) material.clearcoatMap = getTexture( json.clearcoatMap ); - if ( json.clearcoatRoughnessMap !== undefined ) material.clearcoatRoughnessMap = getTexture( json.clearcoatRoughnessMap ); - if ( json.clearcoatNormalMap !== undefined ) material.clearcoatNormalMap = getTexture( json.clearcoatNormalMap ); - if ( json.clearcoatNormalScale !== undefined ) material.clearcoatNormalScale = new Vector2().fromArray( json.clearcoatNormalScale ); + if ( currentBlendingEnabled === false ) { - if ( json.iridescenceMap !== undefined ) material.iridescenceMap = getTexture( json.iridescenceMap ); - if ( json.iridescenceThicknessMap !== undefined ) material.iridescenceThicknessMap = getTexture( json.iridescenceThicknessMap ); + enable( gl.BLEND ); + currentBlendingEnabled = true; - if ( json.transmissionMap !== undefined ) material.transmissionMap = getTexture( json.transmissionMap ); - if ( json.thicknessMap !== undefined ) material.thicknessMap = getTexture( json.thicknessMap ); + } - if ( json.anisotropyMap !== undefined ) material.anisotropyMap = getTexture( json.anisotropyMap ); + if ( blending !== CustomBlending ) { - if ( json.sheenColorMap !== undefined ) material.sheenColorMap = getTexture( json.sheenColorMap ); - if ( json.sheenRoughnessMap !== undefined ) material.sheenRoughnessMap = getTexture( json.sheenRoughnessMap ); + if ( blending !== currentBlending || premultipliedAlpha !== currentPremultipledAlpha ) { - return material; + if ( currentBlendEquation !== AddEquation || currentBlendEquationAlpha !== AddEquation ) { - } + gl.blendEquation( gl.FUNC_ADD ); - setTextures( value ) { + currentBlendEquation = AddEquation; + currentBlendEquationAlpha = AddEquation; - this.textures = value; - return this; + } - } + if ( premultipliedAlpha ) { - static createMaterialFromType( type ) { + switch ( blending ) { - const materialLib = { - ShadowMaterial, - SpriteMaterial, - RawShaderMaterial, - ShaderMaterial, - PointsMaterial, - MeshPhysicalMaterial, - MeshStandardMaterial, - MeshPhongMaterial, - MeshToonMaterial, - MeshNormalMaterial, - MeshLambertMaterial, - MeshDepthMaterial, - MeshDistanceMaterial, - MeshBasicMaterial, - MeshMatcapMaterial, - LineDashedMaterial, - LineBasicMaterial, - Material - }; + case NormalBlending: + gl.blendFuncSeparate( gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA ); + break; - return new materialLib[ type ](); + case AdditiveBlending: + gl.blendFunc( gl.ONE, gl.ONE ); + break; - } + case SubtractiveBlending: + gl.blendFuncSeparate( gl.ZERO, gl.ONE_MINUS_SRC_COLOR, gl.ZERO, gl.ONE ); + break; -} + case MultiplyBlending: + gl.blendFuncSeparate( gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA, gl.ZERO, gl.ONE ); + break; -class LoaderUtils { + default: + console.error( 'THREE.WebGLState: Invalid blending: ', blending ); + break; - static decodeText( array ) { + } - if ( typeof TextDecoder !== 'undefined' ) { + } else { - return new TextDecoder().decode( array ); + switch ( blending ) { - } + case NormalBlending: + gl.blendFuncSeparate( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA ); + break; - // Avoid the String.fromCharCode.apply(null, array) shortcut, which - // throws a "maximum call stack size exceeded" error for large arrays. + case AdditiveBlending: + gl.blendFuncSeparate( gl.SRC_ALPHA, gl.ONE, gl.ONE, gl.ONE ); + break; - let s = ''; + case SubtractiveBlending: + console.error( 'THREE.WebGLState: SubtractiveBlending requires material.premultipliedAlpha = true' ); + break; - for ( let i = 0, il = array.length; i < il; i ++ ) { + case MultiplyBlending: + console.error( 'THREE.WebGLState: MultiplyBlending requires material.premultipliedAlpha = true' ); + break; - // Implicitly assumes little-endian. - s += String.fromCharCode( array[ i ] ); + default: + console.error( 'THREE.WebGLState: Invalid blending: ', blending ); + break; - } + } - try { + } - // merges multi-byte utf-8 characters. + currentBlendSrc = null; + currentBlendDst = null; + currentBlendSrcAlpha = null; + currentBlendDstAlpha = null; + currentBlendColor.set( 0, 0, 0 ); + currentBlendAlpha = 0; - return decodeURIComponent( escape( s ) ); + currentBlending = blending; + currentPremultipledAlpha = premultipliedAlpha; - } catch ( e ) { // see #16358 + } - return s; + return; } - } - - static extractUrlBase( url ) { + // custom blending - const index = url.lastIndexOf( '/' ); + blendEquationAlpha = blendEquationAlpha || blendEquation; + blendSrcAlpha = blendSrcAlpha || blendSrc; + blendDstAlpha = blendDstAlpha || blendDst; - if ( index === - 1 ) return './'; + if ( blendEquation !== currentBlendEquation || blendEquationAlpha !== currentBlendEquationAlpha ) { - return url.slice( 0, index + 1 ); + gl.blendEquationSeparate( equationToGL[ blendEquation ], equationToGL[ blendEquationAlpha ] ); - } + currentBlendEquation = blendEquation; + currentBlendEquationAlpha = blendEquationAlpha; - static resolveURL( url, path ) { + } - // Invalid URL - if ( typeof url !== 'string' || url === '' ) return ''; + if ( blendSrc !== currentBlendSrc || blendDst !== currentBlendDst || blendSrcAlpha !== currentBlendSrcAlpha || blendDstAlpha !== currentBlendDstAlpha ) { - // Host Relative URL - if ( /^https?:\/\//i.test( path ) && /^\//.test( url ) ) { + gl.blendFuncSeparate( factorToGL[ blendSrc ], factorToGL[ blendDst ], factorToGL[ blendSrcAlpha ], factorToGL[ blendDstAlpha ] ); - path = path.replace( /(^https?:\/\/[^\/]+).*/i, '$1' ); + currentBlendSrc = blendSrc; + currentBlendDst = blendDst; + currentBlendSrcAlpha = blendSrcAlpha; + currentBlendDstAlpha = blendDstAlpha; } - // Absolute URL http://,https://,// - if ( /^(https?:)?\/\//i.test( url ) ) return url; + if ( blendColor.equals( currentBlendColor ) === false || blendAlpha !== currentBlendAlpha ) { - // Data URI - if ( /^data:.*,.*$/i.test( url ) ) return url; + gl.blendColor( blendColor.r, blendColor.g, blendColor.b, blendAlpha ); - // Blob URL - if ( /^blob:.*$/i.test( url ) ) return url; + currentBlendColor.copy( blendColor ); + currentBlendAlpha = blendAlpha; - // Relative URL - return path + url; + } + + currentBlending = blending; + currentPremultipledAlpha = false; } -} + function setMaterial( material, frontFaceCW ) { -class InstancedBufferGeometry extends BufferGeometry { + material.side === DoubleSide + ? disable( gl.CULL_FACE ) + : enable( gl.CULL_FACE ); - constructor() { + let flipSided = ( material.side === BackSide ); + if ( frontFaceCW ) flipSided = ! flipSided; - super(); + setFlipSided( flipSided ); - this.isInstancedBufferGeometry = true; + ( material.blending === NormalBlending && material.transparent === false ) + ? setBlending( NoBlending ) + : setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst, material.blendEquationAlpha, material.blendSrcAlpha, material.blendDstAlpha, material.blendColor, material.blendAlpha, material.premultipliedAlpha ); - this.type = 'InstancedBufferGeometry'; - this.instanceCount = Infinity; + depthBuffer.setFunc( material.depthFunc ); + depthBuffer.setTest( material.depthTest ); + depthBuffer.setMask( material.depthWrite ); + colorBuffer.setMask( material.colorWrite ); - } + const stencilWrite = material.stencilWrite; + stencilBuffer.setTest( stencilWrite ); + if ( stencilWrite ) { - copy( source ) { + stencilBuffer.setMask( material.stencilWriteMask ); + stencilBuffer.setFunc( material.stencilFunc, material.stencilRef, material.stencilFuncMask ); + stencilBuffer.setOp( material.stencilFail, material.stencilZFail, material.stencilZPass ); - super.copy( source ); + } - this.instanceCount = source.instanceCount; + setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits ); - return this; + material.alphaToCoverage === true + ? enable( gl.SAMPLE_ALPHA_TO_COVERAGE ) + : disable( gl.SAMPLE_ALPHA_TO_COVERAGE ); } - toJSON() { + // - const data = super.toJSON(); + function setFlipSided( flipSided ) { - data.instanceCount = this.instanceCount; + if ( currentFlipSided !== flipSided ) { - data.isInstancedBufferGeometry = true; + if ( flipSided ) { - return data; + gl.frontFace( gl.CW ); - } + } else { -} + gl.frontFace( gl.CCW ); -class BufferGeometryLoader extends Loader { + } - constructor( manager ) { + currentFlipSided = flipSided; - super( manager ); + } } - load( url, onLoad, onProgress, onError ) { + function setCullFace( cullFace ) { - const scope = this; + if ( cullFace !== CullFaceNone ) { - const loader = new FileLoader( scope.manager ); - loader.setPath( scope.path ); - loader.setRequestHeader( scope.requestHeader ); - loader.setWithCredentials( scope.withCredentials ); - loader.load( url, function ( text ) { + enable( gl.CULL_FACE ); - try { + if ( cullFace !== currentCullFace ) { - onLoad( scope.parse( JSON.parse( text ) ) ); + if ( cullFace === CullFaceBack ) { - } catch ( e ) { + gl.cullFace( gl.BACK ); - if ( onError ) { + } else if ( cullFace === CullFaceFront ) { - onError( e ); + gl.cullFace( gl.FRONT ); } else { - console.error( e ); + gl.cullFace( gl.FRONT_AND_BACK ); } - scope.manager.itemError( url ); - } - }, onProgress, onError ); - - } - - parse( json ) { + } else { - const interleavedBufferMap = {}; - const arrayBufferMap = {}; + disable( gl.CULL_FACE ); - function getInterleavedBuffer( json, uuid ) { + } - if ( interleavedBufferMap[ uuid ] !== undefined ) return interleavedBufferMap[ uuid ]; + currentCullFace = cullFace; - const interleavedBuffers = json.interleavedBuffers; - const interleavedBuffer = interleavedBuffers[ uuid ]; + } - const buffer = getArrayBuffer( json, interleavedBuffer.buffer ); + function setLineWidth( width ) { - const array = getTypedArray( interleavedBuffer.type, buffer ); - const ib = new InterleavedBuffer( array, interleavedBuffer.stride ); - ib.uuid = interleavedBuffer.uuid; + if ( width !== currentLineWidth ) { - interleavedBufferMap[ uuid ] = ib; + if ( lineWidthAvailable ) gl.lineWidth( width ); - return ib; + currentLineWidth = width; } - function getArrayBuffer( json, uuid ) { - - if ( arrayBufferMap[ uuid ] !== undefined ) return arrayBufferMap[ uuid ]; + } - const arrayBuffers = json.arrayBuffers; - const arrayBuffer = arrayBuffers[ uuid ]; + function setPolygonOffset( polygonOffset, factor, units ) { - const ab = new Uint32Array( arrayBuffer ).buffer; + if ( polygonOffset ) { - arrayBufferMap[ uuid ] = ab; + enable( gl.POLYGON_OFFSET_FILL ); - return ab; + if ( currentPolygonOffsetFactor !== factor || currentPolygonOffsetUnits !== units ) { - } + gl.polygonOffset( factor, units ); - const geometry = json.isInstancedBufferGeometry ? new InstancedBufferGeometry() : new BufferGeometry(); + currentPolygonOffsetFactor = factor; + currentPolygonOffsetUnits = units; - const index = json.data.index; + } - if ( index !== undefined ) { + } else { - const typedArray = getTypedArray( index.type, index.array ); - geometry.setIndex( new BufferAttribute( typedArray, 1 ) ); + disable( gl.POLYGON_OFFSET_FILL ); } - const attributes = json.data.attributes; + } - for ( const key in attributes ) { + function setScissorTest( scissorTest ) { - const attribute = attributes[ key ]; - let bufferAttribute; + if ( scissorTest ) { - if ( attribute.isInterleavedBufferAttribute ) { + enable( gl.SCISSOR_TEST ); - const interleavedBuffer = getInterleavedBuffer( json.data, attribute.data ); - bufferAttribute = new InterleavedBufferAttribute( interleavedBuffer, attribute.itemSize, attribute.offset, attribute.normalized ); + } else { - } else { + disable( gl.SCISSOR_TEST ); - const typedArray = getTypedArray( attribute.type, attribute.array ); - const bufferAttributeConstr = attribute.isInstancedBufferAttribute ? InstancedBufferAttribute : BufferAttribute; - bufferAttribute = new bufferAttributeConstr( typedArray, attribute.itemSize, attribute.normalized ); + } - } + } - if ( attribute.name !== undefined ) bufferAttribute.name = attribute.name; - if ( attribute.usage !== undefined ) bufferAttribute.setUsage( attribute.usage ); + // texture - if ( attribute.updateRange !== undefined ) { + function activeTexture( webglSlot ) { - bufferAttribute.updateRange.offset = attribute.updateRange.offset; - bufferAttribute.updateRange.count = attribute.updateRange.count; + if ( webglSlot === undefined ) webglSlot = gl.TEXTURE0 + maxTextures - 1; - } + if ( currentTextureSlot !== webglSlot ) { - geometry.setAttribute( key, bufferAttribute ); + gl.activeTexture( webglSlot ); + currentTextureSlot = webglSlot; } - const morphAttributes = json.data.morphAttributes; + } - if ( morphAttributes ) { + function bindTexture( webglType, webglTexture, webglSlot ) { - for ( const key in morphAttributes ) { + if ( webglSlot === undefined ) { - const attributeArray = morphAttributes[ key ]; + if ( currentTextureSlot === null ) { - const array = []; + webglSlot = gl.TEXTURE0 + maxTextures - 1; - for ( let i = 0, il = attributeArray.length; i < il; i ++ ) { + } else { - const attribute = attributeArray[ i ]; - let bufferAttribute; + webglSlot = currentTextureSlot; - if ( attribute.isInterleavedBufferAttribute ) { + } - const interleavedBuffer = getInterleavedBuffer( json.data, attribute.data ); - bufferAttribute = new InterleavedBufferAttribute( interleavedBuffer, attribute.itemSize, attribute.offset, attribute.normalized ); + } - } else { + let boundTexture = currentBoundTextures[ webglSlot ]; - const typedArray = getTypedArray( attribute.type, attribute.array ); - bufferAttribute = new BufferAttribute( typedArray, attribute.itemSize, attribute.normalized ); + if ( boundTexture === undefined ) { - } + boundTexture = { type: undefined, texture: undefined }; + currentBoundTextures[ webglSlot ] = boundTexture; - if ( attribute.name !== undefined ) bufferAttribute.name = attribute.name; - array.push( bufferAttribute ); + } - } + if ( boundTexture.type !== webglType || boundTexture.texture !== webglTexture ) { - geometry.morphAttributes[ key ] = array; + if ( currentTextureSlot !== webglSlot ) { + + gl.activeTexture( webglSlot ); + currentTextureSlot = webglSlot; } + gl.bindTexture( webglType, webglTexture || emptyTextures[ webglType ] ); + + boundTexture.type = webglType; + boundTexture.texture = webglTexture; + } - const morphTargetsRelative = json.data.morphTargetsRelative; + } - if ( morphTargetsRelative ) { + function unbindTexture() { - geometry.morphTargetsRelative = true; + const boundTexture = currentBoundTextures[ currentTextureSlot ]; + + if ( boundTexture !== undefined && boundTexture.type !== undefined ) { + + gl.bindTexture( boundTexture.type, null ); + + boundTexture.type = undefined; + boundTexture.texture = undefined; } - const groups = json.data.groups || json.data.drawcalls || json.data.offsets; + } - if ( groups !== undefined ) { + function compressedTexImage2D() { - for ( let i = 0, n = groups.length; i !== n; ++ i ) { + try { - const group = groups[ i ]; + gl.compressedTexImage2D( ...arguments ); - geometry.addGroup( group.start, group.count, group.materialIndex ); + } catch ( error ) { - } + console.error( 'THREE.WebGLState:', error ); } - const boundingSphere = json.data.boundingSphere; - - if ( boundingSphere !== undefined ) { + } - const center = new Vector3(); + function compressedTexImage3D() { - if ( boundingSphere.center !== undefined ) { + try { - center.fromArray( boundingSphere.center ); + gl.compressedTexImage3D( ...arguments ); - } + } catch ( error ) { - geometry.boundingSphere = new Sphere( center, boundingSphere.radius ); + console.error( 'THREE.WebGLState:', error ); } - if ( json.name ) geometry.name = json.name; - if ( json.userData ) geometry.userData = json.userData; + } - return geometry; + function texSubImage2D() { - } + try { -} + gl.texSubImage2D( ...arguments ); -class ObjectLoader extends Loader { + } catch ( error ) { - constructor( manager ) { + console.error( 'THREE.WebGLState:', error ); - super( manager ); + } } - load( url, onLoad, onProgress, onError ) { + function texSubImage3D() { - const scope = this; + try { - const path = ( this.path === '' ) ? LoaderUtils.extractUrlBase( url ) : this.path; - this.resourcePath = this.resourcePath || path; + gl.texSubImage3D( ...arguments ); - const loader = new FileLoader( this.manager ); - loader.setPath( this.path ); - loader.setRequestHeader( this.requestHeader ); - loader.setWithCredentials( this.withCredentials ); - loader.load( url, function ( text ) { + } catch ( error ) { - let json = null; + console.error( 'THREE.WebGLState:', error ); - try { + } - json = JSON.parse( text ); + } - } catch ( error ) { + function compressedTexSubImage2D() { - if ( onError !== undefined ) onError( error ); + try { - console.error( 'THREE:ObjectLoader: Can\'t parse ' + url + '.', error.message ); + gl.compressedTexSubImage2D( ...arguments ); - return; + } catch ( error ) { - } + console.error( 'THREE.WebGLState:', error ); - const metadata = json.metadata; + } - if ( metadata === undefined || metadata.type === undefined || metadata.type.toLowerCase() === 'geometry' ) { + } - if ( onError !== undefined ) onError( new Error( 'THREE.ObjectLoader: Can\'t load ' + url ) ); + function compressedTexSubImage3D() { - console.error( 'THREE.ObjectLoader: Can\'t load ' + url ); - return; + try { - } + gl.compressedTexSubImage3D( ...arguments ); - scope.parse( json, onLoad ); + } catch ( error ) { - }, onProgress, onError ); + console.error( 'THREE.WebGLState:', error ); + + } } - async loadAsync( url, onProgress ) { + function texStorage2D() { - const scope = this; + try { - const path = ( this.path === '' ) ? LoaderUtils.extractUrlBase( url ) : this.path; - this.resourcePath = this.resourcePath || path; + gl.texStorage2D( ...arguments ); - const loader = new FileLoader( this.manager ); - loader.setPath( this.path ); - loader.setRequestHeader( this.requestHeader ); - loader.setWithCredentials( this.withCredentials ); + } catch ( error ) { - const text = await loader.loadAsync( url, onProgress ); + console.error( 'THREE.WebGLState:', error ); - const json = JSON.parse( text ); + } - const metadata = json.metadata; + } - if ( metadata === undefined || metadata.type === undefined || metadata.type.toLowerCase() === 'geometry' ) { + function texStorage3D() { - throw new Error( 'THREE.ObjectLoader: Can\'t load ' + url ); + try { - } + gl.texStorage3D( ...arguments ); - return await scope.parseAsync( json ); + } catch ( error ) { + + console.error( 'THREE.WebGLState:', error ); + + } } - parse( json, onLoad ) { + function texImage2D() { - const animations = this.parseAnimations( json.animations ); - const shapes = this.parseShapes( json.shapes ); - const geometries = this.parseGeometries( json.geometries, shapes ); + try { - const images = this.parseImages( json.images, function () { + gl.texImage2D( ...arguments ); - if ( onLoad !== undefined ) onLoad( object ); + } catch ( error ) { - } ); + console.error( 'THREE.WebGLState:', error ); - const textures = this.parseTextures( json.textures, images ); - const materials = this.parseMaterials( json.materials, textures ); + } - const object = this.parseObject( json.object, geometries, materials, textures, animations ); - const skeletons = this.parseSkeletons( json.skeletons, object ); + } - this.bindSkeletons( object, skeletons ); + function texImage3D() { - // + try { - if ( onLoad !== undefined ) { + gl.texImage3D( ...arguments ); - let hasImages = false; + } catch ( error ) { - for ( const uuid in images ) { + console.error( 'THREE.WebGLState:', error ); - if ( images[ uuid ].data instanceof HTMLImageElement ) { + } - hasImages = true; - break; + } - } + // - } + function scissor( scissor ) { - if ( hasImages === false ) onLoad( object ); + if ( currentScissor.equals( scissor ) === false ) { - } + gl.scissor( scissor.x, scissor.y, scissor.z, scissor.w ); + currentScissor.copy( scissor ); - return object; + } } - async parseAsync( json ) { + function viewport( viewport ) { - const animations = this.parseAnimations( json.animations ); - const shapes = this.parseShapes( json.shapes ); - const geometries = this.parseGeometries( json.geometries, shapes ); + if ( currentViewport.equals( viewport ) === false ) { - const images = await this.parseImagesAsync( json.images ); + gl.viewport( viewport.x, viewport.y, viewport.z, viewport.w ); + currentViewport.copy( viewport ); - const textures = this.parseTextures( json.textures, images ); - const materials = this.parseMaterials( json.materials, textures ); + } - const object = this.parseObject( json.object, geometries, materials, textures, animations ); - const skeletons = this.parseSkeletons( json.skeletons, object ); + } - this.bindSkeletons( object, skeletons ); + function updateUBOMapping( uniformsGroup, program ) { - return object; + let mapping = uboProgramMap.get( program ); - } + if ( mapping === undefined ) { - parseShapes( json ) { + mapping = new WeakMap(); - const shapes = {}; + uboProgramMap.set( program, mapping ); - if ( json !== undefined ) { + } - for ( let i = 0, l = json.length; i < l; i ++ ) { + let blockIndex = mapping.get( uniformsGroup ); - const shape = new Shape().fromJSON( json[ i ] ); + if ( blockIndex === undefined ) { - shapes[ shape.uuid ] = shape; + blockIndex = gl.getUniformBlockIndex( program, uniformsGroup.name ); - } + mapping.set( uniformsGroup, blockIndex ); } - return shapes; - } - parseSkeletons( json, object ) { - - const skeletons = {}; - const bones = {}; - - // generate bone lookup table + function uniformBlockBinding( uniformsGroup, program ) { - object.traverse( function ( child ) { + const mapping = uboProgramMap.get( program ); + const blockIndex = mapping.get( uniformsGroup ); - if ( child.isBone ) bones[ child.uuid ] = child; + if ( uboBindings.get( program ) !== blockIndex ) { - } ); + // bind shader specific block index to global block point + gl.uniformBlockBinding( program, blockIndex, uniformsGroup.__bindingPointIndex ); - // create skeletons + uboBindings.set( program, blockIndex ); - if ( json !== undefined ) { + } - for ( let i = 0, l = json.length; i < l; i ++ ) { + } - const skeleton = new Skeleton().fromJSON( json[ i ], bones ); + // - skeletons[ skeleton.uuid ] = skeleton; + function reset() { - } + // reset state - } + gl.disable( gl.BLEND ); + gl.disable( gl.CULL_FACE ); + gl.disable( gl.DEPTH_TEST ); + gl.disable( gl.POLYGON_OFFSET_FILL ); + gl.disable( gl.SCISSOR_TEST ); + gl.disable( gl.STENCIL_TEST ); + gl.disable( gl.SAMPLE_ALPHA_TO_COVERAGE ); - return skeletons; + gl.blendEquation( gl.FUNC_ADD ); + gl.blendFunc( gl.ONE, gl.ZERO ); + gl.blendFuncSeparate( gl.ONE, gl.ZERO, gl.ONE, gl.ZERO ); + gl.blendColor( 0, 0, 0, 0 ); - } + gl.colorMask( true, true, true, true ); + gl.clearColor( 0, 0, 0, 0 ); - parseGeometries( json, shapes ) { + gl.depthMask( true ); + gl.depthFunc( gl.LESS ); - const geometries = {}; + depthBuffer.setReversed( false ); - if ( json !== undefined ) { + gl.clearDepth( 1 ); - const bufferGeometryLoader = new BufferGeometryLoader(); + gl.stencilMask( 0xffffffff ); + gl.stencilFunc( gl.ALWAYS, 0, 0xffffffff ); + gl.stencilOp( gl.KEEP, gl.KEEP, gl.KEEP ); + gl.clearStencil( 0 ); - for ( let i = 0, l = json.length; i < l; i ++ ) { + gl.cullFace( gl.BACK ); + gl.frontFace( gl.CCW ); - let geometry; - const data = json[ i ]; + gl.polygonOffset( 0, 0 ); - switch ( data.type ) { + gl.activeTexture( gl.TEXTURE0 ); - case 'BufferGeometry': - case 'InstancedBufferGeometry': + gl.bindFramebuffer( gl.FRAMEBUFFER, null ); + gl.bindFramebuffer( gl.DRAW_FRAMEBUFFER, null ); + gl.bindFramebuffer( gl.READ_FRAMEBUFFER, null ); - geometry = bufferGeometryLoader.parse( data ); - break; + gl.useProgram( null ); - default: + gl.lineWidth( 1 ); - if ( data.type in Geometries ) { + gl.scissor( 0, 0, gl.canvas.width, gl.canvas.height ); + gl.viewport( 0, 0, gl.canvas.width, gl.canvas.height ); - geometry = Geometries[ data.type ].fromJSON( data, shapes ); + // reset internals - } else { + enabledCapabilities = {}; - console.warn( `THREE.ObjectLoader: Unsupported geometry type "${ data.type }"` ); + currentTextureSlot = null; + currentBoundTextures = {}; - } + currentBoundFramebuffers = {}; + currentDrawbuffers = new WeakMap(); + defaultDrawbuffers = []; - } + currentProgram = null; - geometry.uuid = data.uuid; + currentBlendingEnabled = false; + currentBlending = null; + currentBlendEquation = null; + currentBlendSrc = null; + currentBlendDst = null; + currentBlendEquationAlpha = null; + currentBlendSrcAlpha = null; + currentBlendDstAlpha = null; + currentBlendColor = new Color( 0, 0, 0 ); + currentBlendAlpha = 0; + currentPremultipledAlpha = false; - if ( data.name !== undefined ) geometry.name = data.name; - if ( data.userData !== undefined ) geometry.userData = data.userData; + currentFlipSided = null; + currentCullFace = null; - geometries[ data.uuid ] = geometry; + currentLineWidth = null; - } + currentPolygonOffsetFactor = null; + currentPolygonOffsetUnits = null; - } + currentScissor.set( 0, 0, gl.canvas.width, gl.canvas.height ); + currentViewport.set( 0, 0, gl.canvas.width, gl.canvas.height ); - return geometries; + colorBuffer.reset(); + depthBuffer.reset(); + stencilBuffer.reset(); } - parseMaterials( json, textures ) { + return { - const cache = {}; // MultiMaterial - const materials = {}; + buffers: { + color: colorBuffer, + depth: depthBuffer, + stencil: stencilBuffer + }, - if ( json !== undefined ) { + enable: enable, + disable: disable, - const loader = new MaterialLoader(); - loader.setTextures( textures ); + bindFramebuffer: bindFramebuffer, + drawBuffers: drawBuffers, - for ( let i = 0, l = json.length; i < l; i ++ ) { + useProgram: useProgram, - const data = json[ i ]; + setBlending: setBlending, + setMaterial: setMaterial, - if ( cache[ data.uuid ] === undefined ) { + setFlipSided: setFlipSided, + setCullFace: setCullFace, - cache[ data.uuid ] = loader.parse( data ); + setLineWidth: setLineWidth, + setPolygonOffset: setPolygonOffset, - } + setScissorTest: setScissorTest, - materials[ data.uuid ] = cache[ data.uuid ]; + activeTexture: activeTexture, + bindTexture: bindTexture, + unbindTexture: unbindTexture, + compressedTexImage2D: compressedTexImage2D, + compressedTexImage3D: compressedTexImage3D, + texImage2D: texImage2D, + texImage3D: texImage3D, - } + updateUBOMapping: updateUBOMapping, + uniformBlockBinding: uniformBlockBinding, - } + texStorage2D: texStorage2D, + texStorage3D: texStorage3D, + texSubImage2D: texSubImage2D, + texSubImage3D: texSubImage3D, + compressedTexSubImage2D: compressedTexSubImage2D, + compressedTexSubImage3D: compressedTexSubImage3D, - return materials; + scissor: scissor, + viewport: viewport, - } + reset: reset - parseAnimations( json ) { + }; - const animations = {}; +} - if ( json !== undefined ) { +function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, info ) { - for ( let i = 0; i < json.length; i ++ ) { + const multisampledRTTExt = extensions.has( 'WEBGL_multisampled_render_to_texture' ) ? extensions.get( 'WEBGL_multisampled_render_to_texture' ) : null; + const supportsInvalidateFramebuffer = typeof navigator === 'undefined' ? false : /OculusBrowser/g.test( navigator.userAgent ); - const data = json[ i ]; + const _imageDimensions = new Vector2(); + const _videoTextures = new WeakMap(); + let _canvas; - const clip = AnimationClip.parse( data ); + const _sources = new WeakMap(); // maps WebglTexture objects to instances of Source - animations[ clip.uuid ] = clip; + // cordova iOS (as of 5.0) still uses UIWebView, which provides OffscreenCanvas, + // also OffscreenCanvas.getContext("webgl"), but not OffscreenCanvas.getContext("2d")! + // Some implementations may only implement OffscreenCanvas partially (e.g. lacking 2d). - } + let useOffscreenCanvas = false; - } + try { - return animations; + useOffscreenCanvas = typeof OffscreenCanvas !== 'undefined' + // eslint-disable-next-line compat/compat + && ( new OffscreenCanvas( 1, 1 ).getContext( '2d' ) ) !== null; - } + } catch ( err ) { - parseImages( json, onLoad ) { + // Ignore any errors - const scope = this; - const images = {}; + } - let loader; + function createCanvas( width, height ) { - function loadImage( url ) { + // Use OffscreenCanvas when available. Specially needed in web workers - scope.manager.itemStart( url ); + return useOffscreenCanvas ? + // eslint-disable-next-line compat/compat + new OffscreenCanvas( width, height ) : createElementNS( 'canvas' ); - return loader.load( url, function () { + } - scope.manager.itemEnd( url ); + function resizeImage( image, needsNewCanvas, maxSize ) { - }, undefined, function () { + let scale = 1; - scope.manager.itemError( url ); - scope.manager.itemEnd( url ); + const dimensions = getDimensions( image ); - } ); + // handle case if texture exceeds max size - } + if ( dimensions.width > maxSize || dimensions.height > maxSize ) { - function deserializeImage( image ) { + scale = maxSize / Math.max( dimensions.width, dimensions.height ); - if ( typeof image === 'string' ) { + } - const url = image; + // only perform resize if necessary - const path = /^(\/\/)|([a-z]+:(\/\/)?)/i.test( url ) ? url : scope.resourcePath + url; + if ( scale < 1 ) { - return loadImage( path ); + // only perform resize for certain image types - } else { + if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || + ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) || + ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) || + ( typeof VideoFrame !== 'undefined' && image instanceof VideoFrame ) ) { - if ( image.data ) { + const width = Math.floor( scale * dimensions.width ); + const height = Math.floor( scale * dimensions.height ); - return { - data: getTypedArray( image.type, image.data ), - width: image.width, - height: image.height - }; + if ( _canvas === undefined ) _canvas = createCanvas( width, height ); - } else { + // cube textures can't reuse the same canvas - return null; + const canvas = needsNewCanvas ? createCanvas( width, height ) : _canvas; - } + canvas.width = width; + canvas.height = height; - } + const context = canvas.getContext( '2d' ); + context.drawImage( image, 0, 0, width, height ); - } + console.warn( 'THREE.WebGLRenderer: Texture has been resized from (' + dimensions.width + 'x' + dimensions.height + ') to (' + width + 'x' + height + ').' ); - if ( json !== undefined && json.length > 0 ) { + return canvas; - const manager = new LoadingManager( onLoad ); + } else { - loader = new ImageLoader( manager ); - loader.setCrossOrigin( this.crossOrigin ); + if ( 'data' in image ) { - for ( let i = 0, il = json.length; i < il; i ++ ) { + console.warn( 'THREE.WebGLRenderer: Image in DataTexture is too big (' + dimensions.width + 'x' + dimensions.height + ').' ); - const image = json[ i ]; - const url = image.url; + } - if ( Array.isArray( url ) ) { + return image; - // load array of images e.g CubeTexture + } - const imageArray = []; + } - for ( let j = 0, jl = url.length; j < jl; j ++ ) { + return image; - const currentUrl = url[ j ]; + } - const deserializedImage = deserializeImage( currentUrl ); + function textureNeedsGenerateMipmaps( texture ) { - if ( deserializedImage !== null ) { + return texture.generateMipmaps; - if ( deserializedImage instanceof HTMLImageElement ) { + } - imageArray.push( deserializedImage ); + function generateMipmap( target ) { - } else { + _gl.generateMipmap( target ); - // special case: handle array of data textures for cube textures + } - imageArray.push( new DataTexture( deserializedImage.data, deserializedImage.width, deserializedImage.height ) ); + function getTargetType( texture ) { - } + if ( texture.isWebGLCubeRenderTarget ) return _gl.TEXTURE_CUBE_MAP; + if ( texture.isWebGL3DRenderTarget ) return _gl.TEXTURE_3D; + if ( texture.isWebGLArrayRenderTarget || texture.isCompressedArrayTexture ) return _gl.TEXTURE_2D_ARRAY; + return _gl.TEXTURE_2D; - } + } - } + function getInternalFormat( internalFormatName, glFormat, glType, colorSpace, forceLinearTransfer = false ) { - images[ image.uuid ] = new Source( imageArray ); + if ( internalFormatName !== null ) { - } else { + if ( _gl[ internalFormatName ] !== undefined ) return _gl[ internalFormatName ]; - // load single image + console.warn( 'THREE.WebGLRenderer: Attempt to use non-existing WebGL internal format \'' + internalFormatName + '\'' ); - const deserializedImage = deserializeImage( image.url ); - images[ image.uuid ] = new Source( deserializedImage ); + } + let internalFormat = glFormat; - } + if ( glFormat === _gl.RED ) { - } + if ( glType === _gl.FLOAT ) internalFormat = _gl.R32F; + if ( glType === _gl.HALF_FLOAT ) internalFormat = _gl.R16F; + if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = _gl.R8; } - return images; + if ( glFormat === _gl.RED_INTEGER ) { - } + if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = _gl.R8UI; + if ( glType === _gl.UNSIGNED_SHORT ) internalFormat = _gl.R16UI; + if ( glType === _gl.UNSIGNED_INT ) internalFormat = _gl.R32UI; + if ( glType === _gl.BYTE ) internalFormat = _gl.R8I; + if ( glType === _gl.SHORT ) internalFormat = _gl.R16I; + if ( glType === _gl.INT ) internalFormat = _gl.R32I; - async parseImagesAsync( json ) { + } - const scope = this; - const images = {}; + if ( glFormat === _gl.RG ) { - let loader; + if ( glType === _gl.FLOAT ) internalFormat = _gl.RG32F; + if ( glType === _gl.HALF_FLOAT ) internalFormat = _gl.RG16F; + if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = _gl.RG8; - async function deserializeImage( image ) { + } - if ( typeof image === 'string' ) { + if ( glFormat === _gl.RG_INTEGER ) { - const url = image; + if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = _gl.RG8UI; + if ( glType === _gl.UNSIGNED_SHORT ) internalFormat = _gl.RG16UI; + if ( glType === _gl.UNSIGNED_INT ) internalFormat = _gl.RG32UI; + if ( glType === _gl.BYTE ) internalFormat = _gl.RG8I; + if ( glType === _gl.SHORT ) internalFormat = _gl.RG16I; + if ( glType === _gl.INT ) internalFormat = _gl.RG32I; - const path = /^(\/\/)|([a-z]+:(\/\/)?)/i.test( url ) ? url : scope.resourcePath + url; + } - return await loader.loadAsync( path ); + if ( glFormat === _gl.RGB_INTEGER ) { - } else { + if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = _gl.RGB8UI; + if ( glType === _gl.UNSIGNED_SHORT ) internalFormat = _gl.RGB16UI; + if ( glType === _gl.UNSIGNED_INT ) internalFormat = _gl.RGB32UI; + if ( glType === _gl.BYTE ) internalFormat = _gl.RGB8I; + if ( glType === _gl.SHORT ) internalFormat = _gl.RGB16I; + if ( glType === _gl.INT ) internalFormat = _gl.RGB32I; - if ( image.data ) { + } - return { - data: getTypedArray( image.type, image.data ), - width: image.width, - height: image.height - }; + if ( glFormat === _gl.RGBA_INTEGER ) { - } else { + if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = _gl.RGBA8UI; + if ( glType === _gl.UNSIGNED_SHORT ) internalFormat = _gl.RGBA16UI; + if ( glType === _gl.UNSIGNED_INT ) internalFormat = _gl.RGBA32UI; + if ( glType === _gl.BYTE ) internalFormat = _gl.RGBA8I; + if ( glType === _gl.SHORT ) internalFormat = _gl.RGBA16I; + if ( glType === _gl.INT ) internalFormat = _gl.RGBA32I; - return null; + } - } + if ( glFormat === _gl.RGB ) { - } + if ( glType === _gl.UNSIGNED_INT_5_9_9_9_REV ) internalFormat = _gl.RGB9_E5; } - if ( json !== undefined && json.length > 0 ) { + if ( glFormat === _gl.RGBA ) { - loader = new ImageLoader( this.manager ); - loader.setCrossOrigin( this.crossOrigin ); + const transfer = forceLinearTransfer ? LinearTransfer : ColorManagement.getTransfer( colorSpace ); - for ( let i = 0, il = json.length; i < il; i ++ ) { + if ( glType === _gl.FLOAT ) internalFormat = _gl.RGBA32F; + if ( glType === _gl.HALF_FLOAT ) internalFormat = _gl.RGBA16F; + if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = ( transfer === SRGBTransfer ) ? _gl.SRGB8_ALPHA8 : _gl.RGBA8; + if ( glType === _gl.UNSIGNED_SHORT_4_4_4_4 ) internalFormat = _gl.RGBA4; + if ( glType === _gl.UNSIGNED_SHORT_5_5_5_1 ) internalFormat = _gl.RGB5_A1; - const image = json[ i ]; - const url = image.url; + } - if ( Array.isArray( url ) ) { + if ( internalFormat === _gl.R16F || internalFormat === _gl.R32F || + internalFormat === _gl.RG16F || internalFormat === _gl.RG32F || + internalFormat === _gl.RGBA16F || internalFormat === _gl.RGBA32F ) { - // load array of images e.g CubeTexture + extensions.get( 'EXT_color_buffer_float' ); - const imageArray = []; + } - for ( let j = 0, jl = url.length; j < jl; j ++ ) { + return internalFormat; - const currentUrl = url[ j ]; + } - const deserializedImage = await deserializeImage( currentUrl ); + function getInternalDepthFormat( useStencil, depthType ) { - if ( deserializedImage !== null ) { + let glInternalFormat; + if ( useStencil ) { - if ( deserializedImage instanceof HTMLImageElement ) { + if ( depthType === null || depthType === UnsignedIntType || depthType === UnsignedInt248Type ) { - imageArray.push( deserializedImage ); + glInternalFormat = _gl.DEPTH24_STENCIL8; - } else { + } else if ( depthType === FloatType ) { - // special case: handle array of data textures for cube textures + glInternalFormat = _gl.DEPTH32F_STENCIL8; - imageArray.push( new DataTexture( deserializedImage.data, deserializedImage.width, deserializedImage.height ) ); + } else if ( depthType === UnsignedShortType ) { - } + glInternalFormat = _gl.DEPTH24_STENCIL8; + console.warn( 'DepthTexture: 16 bit depth attachment is not supported with stencil. Using 24-bit attachment.' ); - } + } - } + } else { - images[ image.uuid ] = new Source( imageArray ); + if ( depthType === null || depthType === UnsignedIntType || depthType === UnsignedInt248Type ) { - } else { + glInternalFormat = _gl.DEPTH_COMPONENT24; - // load single image + } else if ( depthType === FloatType ) { - const deserializedImage = await deserializeImage( image.url ); - images[ image.uuid ] = new Source( deserializedImage ); + glInternalFormat = _gl.DEPTH_COMPONENT32F; - } + } else if ( depthType === UnsignedShortType ) { + + glInternalFormat = _gl.DEPTH_COMPONENT16; } } - return images; + return glInternalFormat; } - parseTextures( json, images ) { - - function parseConstant( value, type ) { + function getMipLevels( texture, image ) { - if ( typeof value === 'number' ) return value; + if ( textureNeedsGenerateMipmaps( texture ) === true || ( texture.isFramebufferTexture && texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter ) ) { - console.warn( 'THREE.ObjectLoader.parseTexture: Constant should be in numeric form.', value ); + return Math.log2( Math.max( image.width, image.height ) ) + 1; - return type[ value ]; + } else if ( texture.mipmaps !== undefined && texture.mipmaps.length > 0 ) { - } + // user-defined mipmaps - const textures = {}; + return texture.mipmaps.length; - if ( json !== undefined ) { + } else if ( texture.isCompressedTexture && Array.isArray( texture.image ) ) { - for ( let i = 0, l = json.length; i < l; i ++ ) { + return image.mipmaps.length; - const data = json[ i ]; + } else { - if ( data.image === undefined ) { + // texture without mipmaps (only base level) - console.warn( 'THREE.ObjectLoader: No "image" specified for', data.uuid ); + return 1; - } + } - if ( images[ data.image ] === undefined ) { + } - console.warn( 'THREE.ObjectLoader: Undefined image', data.image ); + // - } + function onTextureDispose( event ) { - const source = images[ data.image ]; - const image = source.data; + const texture = event.target; - let texture; + texture.removeEventListener( 'dispose', onTextureDispose ); - if ( Array.isArray( image ) ) { + deallocateTexture( texture ); - texture = new CubeTexture(); + if ( texture.isVideoTexture ) { - if ( image.length === 6 ) texture.needsUpdate = true; + _videoTextures.delete( texture ); - } else { + } - if ( image && image.data ) { + } - texture = new DataTexture(); + function onRenderTargetDispose( event ) { - } else { + const renderTarget = event.target; - texture = new Texture(); + renderTarget.removeEventListener( 'dispose', onRenderTargetDispose ); - } + deallocateRenderTarget( renderTarget ); - if ( image ) texture.needsUpdate = true; // textures can have undefined image data + } - } + // - texture.source = source; + function deallocateTexture( texture ) { - texture.uuid = data.uuid; + const textureProperties = properties.get( texture ); - if ( data.name !== undefined ) texture.name = data.name; + if ( textureProperties.__webglInit === undefined ) return; - if ( data.mapping !== undefined ) texture.mapping = parseConstant( data.mapping, TEXTURE_MAPPING ); - if ( data.channel !== undefined ) texture.channel = data.channel; + // check if it's necessary to remove the WebGLTexture object - if ( data.offset !== undefined ) texture.offset.fromArray( data.offset ); - if ( data.repeat !== undefined ) texture.repeat.fromArray( data.repeat ); - if ( data.center !== undefined ) texture.center.fromArray( data.center ); - if ( data.rotation !== undefined ) texture.rotation = data.rotation; + const source = texture.source; + const webglTextures = _sources.get( source ); - if ( data.wrap !== undefined ) { + if ( webglTextures ) { - texture.wrapS = parseConstant( data.wrap[ 0 ], TEXTURE_WRAPPING ); - texture.wrapT = parseConstant( data.wrap[ 1 ], TEXTURE_WRAPPING ); + const webglTexture = webglTextures[ textureProperties.__cacheKey ]; + webglTexture.usedTimes --; - } + // the WebGLTexture object is not used anymore, remove it - if ( data.format !== undefined ) texture.format = data.format; - if ( data.internalFormat !== undefined ) texture.internalFormat = data.internalFormat; - if ( data.type !== undefined ) texture.type = data.type; - if ( data.colorSpace !== undefined ) texture.colorSpace = data.colorSpace; - if ( data.encoding !== undefined ) texture.encoding = data.encoding; // @deprecated, r152 + if ( webglTexture.usedTimes === 0 ) { - if ( data.minFilter !== undefined ) texture.minFilter = parseConstant( data.minFilter, TEXTURE_FILTER ); - if ( data.magFilter !== undefined ) texture.magFilter = parseConstant( data.magFilter, TEXTURE_FILTER ); - if ( data.anisotropy !== undefined ) texture.anisotropy = data.anisotropy; + deleteTexture( texture ); - if ( data.flipY !== undefined ) texture.flipY = data.flipY; + } - if ( data.generateMipmaps !== undefined ) texture.generateMipmaps = data.generateMipmaps; - if ( data.premultiplyAlpha !== undefined ) texture.premultiplyAlpha = data.premultiplyAlpha; - if ( data.unpackAlignment !== undefined ) texture.unpackAlignment = data.unpackAlignment; - if ( data.compareFunction !== undefined ) texture.compareFunction = data.compareFunction; + // remove the weak map entry if no WebGLTexture uses the source anymore - if ( data.userData !== undefined ) texture.userData = data.userData; + if ( Object.keys( webglTextures ).length === 0 ) { - textures[ data.uuid ] = texture; + _sources.delete( source ); } } - return textures; + properties.remove( texture ); } - parseObject( data, geometries, materials, textures, animations ) { - - let object; + function deleteTexture( texture ) { - function getGeometry( name ) { + const textureProperties = properties.get( texture ); + _gl.deleteTexture( textureProperties.__webglTexture ); - if ( geometries[ name ] === undefined ) { + const source = texture.source; + const webglTextures = _sources.get( source ); + delete webglTextures[ textureProperties.__cacheKey ]; - console.warn( 'THREE.ObjectLoader: Undefined geometry', name ); + info.memory.textures --; - } + } - return geometries[ name ]; + function deallocateRenderTarget( renderTarget ) { - } + const renderTargetProperties = properties.get( renderTarget ); - function getMaterial( name ) { + if ( renderTarget.depthTexture ) { - if ( name === undefined ) return undefined; + renderTarget.depthTexture.dispose(); - if ( Array.isArray( name ) ) { + properties.remove( renderTarget.depthTexture ); - const array = []; + } - for ( let i = 0, l = name.length; i < l; i ++ ) { + if ( renderTarget.isWebGLCubeRenderTarget ) { - const uuid = name[ i ]; + for ( let i = 0; i < 6; i ++ ) { - if ( materials[ uuid ] === undefined ) { + if ( Array.isArray( renderTargetProperties.__webglFramebuffer[ i ] ) ) { - console.warn( 'THREE.ObjectLoader: Undefined material', uuid ); + for ( let level = 0; level < renderTargetProperties.__webglFramebuffer[ i ].length; level ++ ) _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer[ i ][ level ] ); - } + } else { - array.push( materials[ uuid ] ); + _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer[ i ] ); } - return array; + if ( renderTargetProperties.__webglDepthbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthbuffer[ i ] ); } - if ( materials[ name ] === undefined ) { - - console.warn( 'THREE.ObjectLoader: Undefined material', name ); + } else { - } + if ( Array.isArray( renderTargetProperties.__webglFramebuffer ) ) { - return materials[ name ]; + for ( let level = 0; level < renderTargetProperties.__webglFramebuffer.length; level ++ ) _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer[ level ] ); - } + } else { - function getTexture( uuid ) { + _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer ); - if ( textures[ uuid ] === undefined ) { + } - console.warn( 'THREE.ObjectLoader: Undefined texture', uuid ); + if ( renderTargetProperties.__webglDepthbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthbuffer ); + if ( renderTargetProperties.__webglMultisampledFramebuffer ) _gl.deleteFramebuffer( renderTargetProperties.__webglMultisampledFramebuffer ); - } + if ( renderTargetProperties.__webglColorRenderbuffer ) { - return textures[ uuid ]; + for ( let i = 0; i < renderTargetProperties.__webglColorRenderbuffer.length; i ++ ) { - } + if ( renderTargetProperties.__webglColorRenderbuffer[ i ] ) _gl.deleteRenderbuffer( renderTargetProperties.__webglColorRenderbuffer[ i ] ); - let geometry, material; + } - switch ( data.type ) { + } - case 'Scene': + if ( renderTargetProperties.__webglDepthRenderbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthRenderbuffer ); - object = new Scene(); + } - if ( data.background !== undefined ) { + const textures = renderTarget.textures; - if ( Number.isInteger( data.background ) ) { + for ( let i = 0, il = textures.length; i < il; i ++ ) { - object.background = new Color( data.background ); + const attachmentProperties = properties.get( textures[ i ] ); - } else { + if ( attachmentProperties.__webglTexture ) { - object.background = getTexture( data.background ); + _gl.deleteTexture( attachmentProperties.__webglTexture ); - } + info.memory.textures --; - } + } - if ( data.environment !== undefined ) { + properties.remove( textures[ i ] ); - object.environment = getTexture( data.environment ); + } - } + properties.remove( renderTarget ); - if ( data.fog !== undefined ) { + } - if ( data.fog.type === 'Fog' ) { + // - object.fog = new Fog( data.fog.color, data.fog.near, data.fog.far ); + let textureUnits = 0; - } else if ( data.fog.type === 'FogExp2' ) { + function resetTextureUnits() { - object.fog = new FogExp2( data.fog.color, data.fog.density ); + textureUnits = 0; - } + } - } + function allocateTextureUnit() { - if ( data.backgroundBlurriness !== undefined ) object.backgroundBlurriness = data.backgroundBlurriness; - if ( data.backgroundIntensity !== undefined ) object.backgroundIntensity = data.backgroundIntensity; + const textureUnit = textureUnits; - break; + if ( textureUnit >= capabilities.maxTextures ) { - case 'PerspectiveCamera': + console.warn( 'THREE.WebGLTextures: Trying to use ' + textureUnit + ' texture units while this GPU supports only ' + capabilities.maxTextures ); - object = new PerspectiveCamera( data.fov, data.aspect, data.near, data.far ); + } - if ( data.focus !== undefined ) object.focus = data.focus; - if ( data.zoom !== undefined ) object.zoom = data.zoom; - if ( data.filmGauge !== undefined ) object.filmGauge = data.filmGauge; - if ( data.filmOffset !== undefined ) object.filmOffset = data.filmOffset; - if ( data.view !== undefined ) object.view = Object.assign( {}, data.view ); + textureUnits += 1; - break; + return textureUnit; - case 'OrthographicCamera': + } - object = new OrthographicCamera( data.left, data.right, data.top, data.bottom, data.near, data.far ); + function getTextureCacheKey( texture ) { - if ( data.zoom !== undefined ) object.zoom = data.zoom; - if ( data.view !== undefined ) object.view = Object.assign( {}, data.view ); + const array = []; - break; + array.push( texture.wrapS ); + array.push( texture.wrapT ); + array.push( texture.wrapR || 0 ); + array.push( texture.magFilter ); + array.push( texture.minFilter ); + array.push( texture.anisotropy ); + array.push( texture.internalFormat ); + array.push( texture.format ); + array.push( texture.type ); + array.push( texture.generateMipmaps ); + array.push( texture.premultiplyAlpha ); + array.push( texture.flipY ); + array.push( texture.unpackAlignment ); + array.push( texture.colorSpace ); - case 'AmbientLight': + return array.join(); - object = new AmbientLight( data.color, data.intensity ); + } - break; + // - case 'DirectionalLight': + function setTexture2D( texture, slot ) { - object = new DirectionalLight( data.color, data.intensity ); + const textureProperties = properties.get( texture ); - break; + if ( texture.isVideoTexture ) updateVideoTexture( texture ); - case 'PointLight': + if ( texture.isRenderTargetTexture === false && texture.version > 0 && textureProperties.__version !== texture.version ) { - object = new PointLight( data.color, data.intensity, data.distance, data.decay ); + const image = texture.image; - break; + if ( image === null ) { - case 'RectAreaLight': + console.warn( 'THREE.WebGLRenderer: Texture marked for update but no image data found.' ); - object = new RectAreaLight( data.color, data.intensity, data.width, data.height ); + } else if ( image.complete === false ) { - break; + console.warn( 'THREE.WebGLRenderer: Texture marked for update but image is incomplete' ); - case 'SpotLight': + } else { - object = new SpotLight( data.color, data.intensity, data.distance, data.angle, data.penumbra, data.decay ); + uploadTexture( textureProperties, texture, slot ); + return; - break; + } - case 'HemisphereLight': + } - object = new HemisphereLight( data.color, data.groundColor, data.intensity ); + state.bindTexture( _gl.TEXTURE_2D, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); - break; + } - case 'LightProbe': + function setTexture2DArray( texture, slot ) { - object = new LightProbe().fromJSON( data ); + const textureProperties = properties.get( texture ); - break; + if ( texture.version > 0 && textureProperties.__version !== texture.version ) { - case 'SkinnedMesh': + uploadTexture( textureProperties, texture, slot ); + return; - geometry = getGeometry( data.geometry ); - material = getMaterial( data.material ); + } - object = new SkinnedMesh( geometry, material ); + state.bindTexture( _gl.TEXTURE_2D_ARRAY, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); - if ( data.bindMode !== undefined ) object.bindMode = data.bindMode; - if ( data.bindMatrix !== undefined ) object.bindMatrix.fromArray( data.bindMatrix ); - if ( data.skeleton !== undefined ) object.skeleton = data.skeleton; + } - break; + function setTexture3D( texture, slot ) { - case 'Mesh': + const textureProperties = properties.get( texture ); - geometry = getGeometry( data.geometry ); - material = getMaterial( data.material ); + if ( texture.version > 0 && textureProperties.__version !== texture.version ) { - object = new Mesh( geometry, material ); + uploadTexture( textureProperties, texture, slot ); + return; - break; + } - case 'InstancedMesh': + state.bindTexture( _gl.TEXTURE_3D, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); - geometry = getGeometry( data.geometry ); - material = getMaterial( data.material ); - const count = data.count; - const instanceMatrix = data.instanceMatrix; - const instanceColor = data.instanceColor; + } - object = new InstancedMesh( geometry, material, count ); - object.instanceMatrix = new InstancedBufferAttribute( new Float32Array( instanceMatrix.array ), 16 ); - if ( instanceColor !== undefined ) object.instanceColor = new InstancedBufferAttribute( new Float32Array( instanceColor.array ), instanceColor.itemSize ); + function setTextureCube( texture, slot ) { - break; + const textureProperties = properties.get( texture ); - case 'LOD': + if ( texture.version > 0 && textureProperties.__version !== texture.version ) { - object = new LOD(); + uploadCubeTexture( textureProperties, texture, slot ); + return; - break; + } - case 'Line': + state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); - object = new Line( getGeometry( data.geometry ), getMaterial( data.material ) ); + } - break; + const wrappingToGL = { + [ RepeatWrapping ]: _gl.REPEAT, + [ ClampToEdgeWrapping ]: _gl.CLAMP_TO_EDGE, + [ MirroredRepeatWrapping ]: _gl.MIRRORED_REPEAT + }; - case 'LineLoop': + const filterToGL = { + [ NearestFilter ]: _gl.NEAREST, + [ NearestMipmapNearestFilter ]: _gl.NEAREST_MIPMAP_NEAREST, + [ NearestMipmapLinearFilter ]: _gl.NEAREST_MIPMAP_LINEAR, - object = new LineLoop( getGeometry( data.geometry ), getMaterial( data.material ) ); + [ LinearFilter ]: _gl.LINEAR, + [ LinearMipmapNearestFilter ]: _gl.LINEAR_MIPMAP_NEAREST, + [ LinearMipmapLinearFilter ]: _gl.LINEAR_MIPMAP_LINEAR + }; - break; + const compareToGL = { + [ NeverCompare ]: _gl.NEVER, + [ AlwaysCompare ]: _gl.ALWAYS, + [ LessCompare ]: _gl.LESS, + [ LessEqualCompare ]: _gl.LEQUAL, + [ EqualCompare ]: _gl.EQUAL, + [ GreaterEqualCompare ]: _gl.GEQUAL, + [ GreaterCompare ]: _gl.GREATER, + [ NotEqualCompare ]: _gl.NOTEQUAL + }; - case 'LineSegments': + function setTextureParameters( textureType, texture ) { - object = new LineSegments( getGeometry( data.geometry ), getMaterial( data.material ) ); + if ( texture.type === FloatType && extensions.has( 'OES_texture_float_linear' ) === false && + ( texture.magFilter === LinearFilter || texture.magFilter === LinearMipmapNearestFilter || texture.magFilter === NearestMipmapLinearFilter || texture.magFilter === LinearMipmapLinearFilter || + texture.minFilter === LinearFilter || texture.minFilter === LinearMipmapNearestFilter || texture.minFilter === NearestMipmapLinearFilter || texture.minFilter === LinearMipmapLinearFilter ) ) { - break; + console.warn( 'THREE.WebGLRenderer: Unable to use linear filtering with floating point textures. OES_texture_float_linear not supported on this device.' ); - case 'PointCloud': - case 'Points': + } - object = new Points( getGeometry( data.geometry ), getMaterial( data.material ) ); + _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_S, wrappingToGL[ texture.wrapS ] ); + _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_T, wrappingToGL[ texture.wrapT ] ); - break; + if ( textureType === _gl.TEXTURE_3D || textureType === _gl.TEXTURE_2D_ARRAY ) { - case 'Sprite': + _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_R, wrappingToGL[ texture.wrapR ] ); - object = new Sprite( getMaterial( data.material ) ); + } - break; + _gl.texParameteri( textureType, _gl.TEXTURE_MAG_FILTER, filterToGL[ texture.magFilter ] ); + _gl.texParameteri( textureType, _gl.TEXTURE_MIN_FILTER, filterToGL[ texture.minFilter ] ); - case 'Group': + if ( texture.compareFunction ) { - object = new Group(); + _gl.texParameteri( textureType, _gl.TEXTURE_COMPARE_MODE, _gl.COMPARE_REF_TO_TEXTURE ); + _gl.texParameteri( textureType, _gl.TEXTURE_COMPARE_FUNC, compareToGL[ texture.compareFunction ] ); - break; + } - case 'Bone': + if ( extensions.has( 'EXT_texture_filter_anisotropic' ) === true ) { - object = new Bone(); + if ( texture.magFilter === NearestFilter ) return; + if ( texture.minFilter !== NearestMipmapLinearFilter && texture.minFilter !== LinearMipmapLinearFilter ) return; + if ( texture.type === FloatType && extensions.has( 'OES_texture_float_linear' ) === false ) return; // verify extension - break; + if ( texture.anisotropy > 1 || properties.get( texture ).__currentAnisotropy ) { - default: + const extension = extensions.get( 'EXT_texture_filter_anisotropic' ); + _gl.texParameterf( textureType, extension.TEXTURE_MAX_ANISOTROPY_EXT, Math.min( texture.anisotropy, capabilities.getMaxAnisotropy() ) ); + properties.get( texture ).__currentAnisotropy = texture.anisotropy; - object = new Object3D(); + } } - object.uuid = data.uuid; - - if ( data.name !== undefined ) object.name = data.name; + } - if ( data.matrix !== undefined ) { + function initTexture( textureProperties, texture ) { - object.matrix.fromArray( data.matrix ); + let forceUpload = false; - if ( data.matrixAutoUpdate !== undefined ) object.matrixAutoUpdate = data.matrixAutoUpdate; - if ( object.matrixAutoUpdate ) object.matrix.decompose( object.position, object.quaternion, object.scale ); + if ( textureProperties.__webglInit === undefined ) { - } else { + textureProperties.__webglInit = true; - if ( data.position !== undefined ) object.position.fromArray( data.position ); - if ( data.rotation !== undefined ) object.rotation.fromArray( data.rotation ); - if ( data.quaternion !== undefined ) object.quaternion.fromArray( data.quaternion ); - if ( data.scale !== undefined ) object.scale.fromArray( data.scale ); + texture.addEventListener( 'dispose', onTextureDispose ); } - if ( data.up !== undefined ) object.up.fromArray( data.up ); + // create Source <-> WebGLTextures mapping if necessary - if ( data.castShadow !== undefined ) object.castShadow = data.castShadow; - if ( data.receiveShadow !== undefined ) object.receiveShadow = data.receiveShadow; + const source = texture.source; + let webglTextures = _sources.get( source ); - if ( data.shadow ) { + if ( webglTextures === undefined ) { - if ( data.shadow.bias !== undefined ) object.shadow.bias = data.shadow.bias; - if ( data.shadow.normalBias !== undefined ) object.shadow.normalBias = data.shadow.normalBias; - if ( data.shadow.radius !== undefined ) object.shadow.radius = data.shadow.radius; - if ( data.shadow.mapSize !== undefined ) object.shadow.mapSize.fromArray( data.shadow.mapSize ); - if ( data.shadow.camera !== undefined ) object.shadow.camera = this.parseObject( data.shadow.camera ); + webglTextures = {}; + _sources.set( source, webglTextures ); } - if ( data.visible !== undefined ) object.visible = data.visible; - if ( data.frustumCulled !== undefined ) object.frustumCulled = data.frustumCulled; - if ( data.renderOrder !== undefined ) object.renderOrder = data.renderOrder; - if ( data.userData !== undefined ) object.userData = data.userData; - if ( data.layers !== undefined ) object.layers.mask = data.layers; - - if ( data.children !== undefined ) { - - const children = data.children; + // check if there is already a WebGLTexture object for the given texture parameters - for ( let i = 0; i < children.length; i ++ ) { + const textureCacheKey = getTextureCacheKey( texture ); - object.add( this.parseObject( children[ i ], geometries, materials, textures, animations ) ); + if ( textureCacheKey !== textureProperties.__cacheKey ) { - } + // if not, create a new instance of WebGLTexture - } + if ( webglTextures[ textureCacheKey ] === undefined ) { - if ( data.animations !== undefined ) { + // create new entry - const objectAnimations = data.animations; + webglTextures[ textureCacheKey ] = { + texture: _gl.createTexture(), + usedTimes: 0 + }; - for ( let i = 0; i < objectAnimations.length; i ++ ) { + info.memory.textures ++; - const uuid = objectAnimations[ i ]; + // when a new instance of WebGLTexture was created, a texture upload is required + // even if the image contents are identical - object.animations.push( animations[ uuid ] ); + forceUpload = true; } - } - - if ( data.type === 'LOD' ) { + webglTextures[ textureCacheKey ].usedTimes ++; - if ( data.autoUpdate !== undefined ) object.autoUpdate = data.autoUpdate; + // every time the texture cache key changes, it's necessary to check if an instance of + // WebGLTexture can be deleted in order to avoid a memory leak. - const levels = data.levels; + const webglTexture = webglTextures[ textureProperties.__cacheKey ]; - for ( let l = 0; l < levels.length; l ++ ) { + if ( webglTexture !== undefined ) { - const level = levels[ l ]; - const child = object.getObjectByProperty( 'uuid', level.object ); + webglTextures[ textureProperties.__cacheKey ].usedTimes --; - if ( child !== undefined ) { + if ( webglTexture.usedTimes === 0 ) { - object.addLevel( child, level.distance, level.hysteresis ); + deleteTexture( texture ); } } + // store references to cache key and WebGLTexture object + + textureProperties.__cacheKey = textureCacheKey; + textureProperties.__webglTexture = webglTextures[ textureCacheKey ].texture; + } - return object; + return forceUpload; } - bindSkeletons( object, skeletons ) { + function getRow( index, rowLength, componentStride ) { - if ( Object.keys( skeletons ).length === 0 ) return; + return Math.floor( Math.floor( index / componentStride ) / rowLength ); - object.traverse( function ( child ) { + } - if ( child.isSkinnedMesh === true && child.skeleton !== undefined ) { + function updateTexture( texture, image, glFormat, glType ) { - const skeleton = skeletons[ child.skeleton ]; + const componentStride = 4; // only RGBA supported - if ( skeleton === undefined ) { + const updateRanges = texture.updateRanges; - console.warn( 'THREE.ObjectLoader: No skeleton found with UUID:', child.skeleton ); + if ( updateRanges.length === 0 ) { + + state.texSubImage2D( _gl.TEXTURE_2D, 0, 0, 0, image.width, image.height, glFormat, glType, image.data ); + + } else { + + // Before applying update ranges, we merge any adjacent / overlapping + // ranges to reduce load on `gl.texSubImage2D`. Empirically, this has led + // to performance improvements for applications which make heavy use of + // update ranges. Likely due to GPU command overhead. + // + // Note that to reduce garbage collection between frames, we merge the + // update ranges in-place. This is safe because this method will clear the + // update ranges once updated. + + updateRanges.sort( ( a, b ) => a.start - b.start ); + + // To merge the update ranges in-place, we work from left to right in the + // existing updateRanges array, merging ranges. This may result in a final + // array which is smaller than the original. This index tracks the last + // index representing a merged range, any data after this index can be + // trimmed once the merge algorithm is completed. + let mergeIndex = 0; + + for ( let i = 1; i < updateRanges.length; i ++ ) { + + const previousRange = updateRanges[ mergeIndex ]; + const range = updateRanges[ i ]; + + // Only merge if in the same row and overlapping/adjacent + const previousEnd = previousRange.start + previousRange.count; + const currentRow = getRow( range.start, image.width, componentStride ); + const previousRow = getRow( previousRange.start, image.width, componentStride ); + + // We add one here to merge adjacent ranges. This is safe because ranges + // operate over positive integers. + if ( + range.start <= previousEnd + 1 && + currentRow === previousRow && + getRow( range.start + range.count - 1, image.width, componentStride ) === currentRow // ensure range doesn't spill + ) { + + previousRange.count = Math.max( + previousRange.count, + range.start + range.count - previousRange.start + ); } else { - child.bind( skeleton, child.bindMatrix ); + ++ mergeIndex; + updateRanges[ mergeIndex ] = range; } + } - } ); + // Trim the array to only contain the merged ranges. + updateRanges.length = mergeIndex + 1; - } + const currentUnpackRowLen = _gl.getParameter( _gl.UNPACK_ROW_LENGTH ); + const currentUnpackSkipPixels = _gl.getParameter( _gl.UNPACK_SKIP_PIXELS ); + const currentUnpackSkipRows = _gl.getParameter( _gl.UNPACK_SKIP_ROWS ); -} + _gl.pixelStorei( _gl.UNPACK_ROW_LENGTH, image.width ); -const TEXTURE_MAPPING = { - UVMapping: UVMapping, - CubeReflectionMapping: CubeReflectionMapping, - CubeRefractionMapping: CubeRefractionMapping, - EquirectangularReflectionMapping: EquirectangularReflectionMapping, - EquirectangularRefractionMapping: EquirectangularRefractionMapping, - CubeUVReflectionMapping: CubeUVReflectionMapping -}; + for ( let i = 0, l = updateRanges.length; i < l; i ++ ) { -const TEXTURE_WRAPPING = { - RepeatWrapping: RepeatWrapping, - ClampToEdgeWrapping: ClampToEdgeWrapping, - MirroredRepeatWrapping: MirroredRepeatWrapping -}; + const range = updateRanges[ i ]; -const TEXTURE_FILTER = { - NearestFilter: NearestFilter, - NearestMipmapNearestFilter: NearestMipmapNearestFilter, - NearestMipmapLinearFilter: NearestMipmapLinearFilter, - LinearFilter: LinearFilter, - LinearMipmapNearestFilter: LinearMipmapNearestFilter, - LinearMipmapLinearFilter: LinearMipmapLinearFilter -}; + const pixelStart = Math.floor( range.start / componentStride ); + const pixelCount = Math.ceil( range.count / componentStride ); -class ImageBitmapLoader extends Loader { + const x = pixelStart % image.width; + const y = Math.floor( pixelStart / image.width ); - constructor( manager ) { + // Assumes update ranges refer to contiguous memory + const width = pixelCount; + const height = 1; - super( manager ); + _gl.pixelStorei( _gl.UNPACK_SKIP_PIXELS, x ); + _gl.pixelStorei( _gl.UNPACK_SKIP_ROWS, y ); - this.isImageBitmapLoader = true; + state.texSubImage2D( _gl.TEXTURE_2D, 0, x, y, width, height, glFormat, glType, image.data ); - if ( typeof createImageBitmap === 'undefined' ) { + } - console.warn( 'THREE.ImageBitmapLoader: createImageBitmap() not supported.' ); + texture.clearUpdateRanges(); + + _gl.pixelStorei( _gl.UNPACK_ROW_LENGTH, currentUnpackRowLen ); + _gl.pixelStorei( _gl.UNPACK_SKIP_PIXELS, currentUnpackSkipPixels ); + _gl.pixelStorei( _gl.UNPACK_SKIP_ROWS, currentUnpackSkipRows ); } - if ( typeof fetch === 'undefined' ) { + } - console.warn( 'THREE.ImageBitmapLoader: fetch() not supported.' ); + function uploadTexture( textureProperties, texture, slot ) { - } + let textureType = _gl.TEXTURE_2D; - this.options = { premultiplyAlpha: 'none' }; + if ( texture.isDataArrayTexture || texture.isCompressedArrayTexture ) textureType = _gl.TEXTURE_2D_ARRAY; + if ( texture.isData3DTexture ) textureType = _gl.TEXTURE_3D; - } + const forceUpload = initTexture( textureProperties, texture ); + const source = texture.source; - setOptions( options ) { + state.bindTexture( textureType, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); - this.options = options; + const sourceProperties = properties.get( source ); - return this; + if ( source.version !== sourceProperties.__version || forceUpload === true ) { - } + state.activeTexture( _gl.TEXTURE0 + slot ); - load( url, onLoad, onProgress, onError ) { + const workingPrimaries = ColorManagement.getPrimaries( ColorManagement.workingColorSpace ); + const texturePrimaries = texture.colorSpace === NoColorSpace ? null : ColorManagement.getPrimaries( texture.colorSpace ); + const unpackConversion = texture.colorSpace === NoColorSpace || workingPrimaries === texturePrimaries ? _gl.NONE : _gl.BROWSER_DEFAULT_WEBGL; - if ( url === undefined ) url = ''; + _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY ); + _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha ); + _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, texture.unpackAlignment ); + _gl.pixelStorei( _gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, unpackConversion ); - if ( this.path !== undefined ) url = this.path + url; + let image = resizeImage( texture.image, false, capabilities.maxTextureSize ); + image = verifyColorSpace( texture, image ); - url = this.manager.resolveURL( url ); + const glFormat = utils.convert( texture.format, texture.colorSpace ); - const scope = this; + const glType = utils.convert( texture.type ); + let glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace, texture.isVideoTexture ); - const cached = Cache.get( url ); + setTextureParameters( textureType, texture ); - if ( cached !== undefined ) { + let mipmap; + const mipmaps = texture.mipmaps; - scope.manager.itemStart( url ); + const useTexStorage = ( texture.isVideoTexture !== true ); + const allocateMemory = ( sourceProperties.__version === undefined ) || ( forceUpload === true ); + const dataReady = source.dataReady; + const levels = getMipLevels( texture, image ); - setTimeout( function () { + if ( texture.isDepthTexture ) { - if ( onLoad ) onLoad( cached ); + glInternalFormat = getInternalDepthFormat( texture.format === DepthStencilFormat, texture.type ); - scope.manager.itemEnd( url ); + // - }, 0 ); + if ( allocateMemory ) { - return cached; + if ( useTexStorage ) { - } + state.texStorage2D( _gl.TEXTURE_2D, 1, glInternalFormat, image.width, image.height ); - const fetchOptions = {}; - fetchOptions.credentials = ( this.crossOrigin === 'anonymous' ) ? 'same-origin' : 'include'; - fetchOptions.headers = this.requestHeader; + } else { - fetch( url, fetchOptions ).then( function ( res ) { + state.texImage2D( _gl.TEXTURE_2D, 0, glInternalFormat, image.width, image.height, 0, glFormat, glType, null ); - return res.blob(); + } - } ).then( function ( blob ) { + } - return createImageBitmap( blob, Object.assign( scope.options, { colorSpaceConversion: 'none' } ) ); + } else if ( texture.isDataTexture ) { - } ).then( function ( imageBitmap ) { + // use manually created mipmaps if available + // if there are no manual mipmaps + // set 0 level mipmap and then use GL to generate other mipmap levels - Cache.add( url, imageBitmap ); + if ( mipmaps.length > 0 ) { - if ( onLoad ) onLoad( imageBitmap ); + if ( useTexStorage && allocateMemory ) { - scope.manager.itemEnd( url ); + state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, mipmaps[ 0 ].width, mipmaps[ 0 ].height ); - } ).catch( function ( e ) { + } - if ( onError ) onError( e ); + for ( let i = 0, il = mipmaps.length; i < il; i ++ ) { - scope.manager.itemError( url ); - scope.manager.itemEnd( url ); + mipmap = mipmaps[ i ]; - } ); + if ( useTexStorage ) { - scope.manager.itemStart( url ); + if ( dataReady ) { - } + state.texSubImage2D( _gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, glType, mipmap.data ); -} + } -let _context; + } else { -class AudioContext { + state.texImage2D( _gl.TEXTURE_2D, i, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); - static getContext() { + } - if ( _context === undefined ) { + } - _context = new ( window.AudioContext || window.webkitAudioContext )(); + texture.generateMipmaps = false; - } + } else { - return _context; + if ( useTexStorage ) { - } + if ( allocateMemory ) { - static setContext( value ) { + state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, image.width, image.height ); - _context = value; + } - } + if ( dataReady ) { -} + updateTexture( texture, image, glFormat, glType ); -class AudioLoader extends Loader { + } - constructor( manager ) { + } else { - super( manager ); + state.texImage2D( _gl.TEXTURE_2D, 0, glInternalFormat, image.width, image.height, 0, glFormat, glType, image.data ); - } + } - load( url, onLoad, onProgress, onError ) { + } - const scope = this; + } else if ( texture.isCompressedTexture ) { - const loader = new FileLoader( this.manager ); - loader.setResponseType( 'arraybuffer' ); - loader.setPath( this.path ); - loader.setRequestHeader( this.requestHeader ); - loader.setWithCredentials( this.withCredentials ); - loader.load( url, function ( buffer ) { + if ( texture.isCompressedArrayTexture ) { - try { + if ( useTexStorage && allocateMemory ) { - // Create a copy of the buffer. The `decodeAudioData` method - // detaches the buffer when complete, preventing reuse. - const bufferCopy = buffer.slice( 0 ); + state.texStorage3D( _gl.TEXTURE_2D_ARRAY, levels, glInternalFormat, mipmaps[ 0 ].width, mipmaps[ 0 ].height, image.depth ); - const context = AudioContext.getContext(); - context.decodeAudioData( bufferCopy, function ( audioBuffer ) { + } - onLoad( audioBuffer ); + for ( let i = 0, il = mipmaps.length; i < il; i ++ ) { - }, handleError ); + mipmap = mipmaps[ i ]; - } catch ( e ) { + if ( texture.format !== RGBAFormat ) { - handleError( e ); + if ( glFormat !== null ) { - } + if ( useTexStorage ) { - }, onProgress, onError ); + if ( dataReady ) { - function handleError( e ) { + if ( texture.layerUpdates.size > 0 ) { - if ( onError ) { + const layerByteLength = getByteLength( mipmap.width, mipmap.height, texture.format, texture.type ); - onError( e ); + for ( const layerIndex of texture.layerUpdates ) { - } else { + const layerData = mipmap.data.subarray( + layerIndex * layerByteLength / mipmap.data.BYTES_PER_ELEMENT, + ( layerIndex + 1 ) * layerByteLength / mipmap.data.BYTES_PER_ELEMENT + ); + state.compressedTexSubImage3D( _gl.TEXTURE_2D_ARRAY, i, 0, 0, layerIndex, mipmap.width, mipmap.height, 1, glFormat, layerData ); - console.error( e ); + } - } + texture.clearLayerUpdates(); - scope.manager.itemError( url ); + } else { - } + state.compressedTexSubImage3D( _gl.TEXTURE_2D_ARRAY, i, 0, 0, 0, mipmap.width, mipmap.height, image.depth, glFormat, mipmap.data ); - } + } -} + } -class HemisphereLightProbe extends LightProbe { + } else { - constructor( skyColor, groundColor, intensity = 1 ) { + state.compressedTexImage3D( _gl.TEXTURE_2D_ARRAY, i, glInternalFormat, mipmap.width, mipmap.height, image.depth, 0, mipmap.data, 0, 0 ); - super( undefined, intensity ); + } - this.isHemisphereLightProbe = true; + } else { - const color1 = new Color().set( skyColor ); - const color2 = new Color().set( groundColor ); + console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()' ); - const sky = new Vector3( color1.r, color1.g, color1.b ); - const ground = new Vector3( color2.r, color2.g, color2.b ); + } - // without extra factor of PI in the shader, should = 1 / Math.sqrt( Math.PI ); - const c0 = Math.sqrt( Math.PI ); - const c1 = c0 * Math.sqrt( 0.75 ); + } else { - this.sh.coefficients[ 0 ].copy( sky ).add( ground ).multiplyScalar( c0 ); - this.sh.coefficients[ 1 ].copy( sky ).sub( ground ).multiplyScalar( c1 ); + if ( useTexStorage ) { - } + if ( dataReady ) { -} + state.texSubImage3D( _gl.TEXTURE_2D_ARRAY, i, 0, 0, 0, mipmap.width, mipmap.height, image.depth, glFormat, glType, mipmap.data ); -class AmbientLightProbe extends LightProbe { + } - constructor( color, intensity = 1 ) { + } else { - super( undefined, intensity ); + state.texImage3D( _gl.TEXTURE_2D_ARRAY, i, glInternalFormat, mipmap.width, mipmap.height, image.depth, 0, glFormat, glType, mipmap.data ); - this.isAmbientLightProbe = true; + } - const color1 = new Color().set( color ); + } - // without extra factor of PI in the shader, would be 2 / Math.sqrt( Math.PI ); - this.sh.coefficients[ 0 ].set( color1.r, color1.g, color1.b ).multiplyScalar( 2 * Math.sqrt( Math.PI ) ); + } - } + } else { -} + if ( useTexStorage && allocateMemory ) { -const _eyeRight = /*@__PURE__*/ new Matrix4(); -const _eyeLeft = /*@__PURE__*/ new Matrix4(); -const _projectionMatrix = /*@__PURE__*/ new Matrix4(); + state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, mipmaps[ 0 ].width, mipmaps[ 0 ].height ); -class StereoCamera { + } - constructor() { + for ( let i = 0, il = mipmaps.length; i < il; i ++ ) { - this.type = 'StereoCamera'; + mipmap = mipmaps[ i ]; - this.aspect = 1; + if ( texture.format !== RGBAFormat ) { - this.eyeSep = 0.064; + if ( glFormat !== null ) { - this.cameraL = new PerspectiveCamera(); - this.cameraL.layers.enable( 1 ); - this.cameraL.matrixAutoUpdate = false; + if ( useTexStorage ) { - this.cameraR = new PerspectiveCamera(); - this.cameraR.layers.enable( 2 ); - this.cameraR.matrixAutoUpdate = false; + if ( dataReady ) { - this._cache = { - focus: null, - fov: null, - aspect: null, - near: null, - far: null, - zoom: null, - eyeSep: null - }; + state.compressedTexSubImage2D( _gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, mipmap.data ); - } + } - update( camera ) { + } else { - const cache = this._cache; + state.compressedTexImage2D( _gl.TEXTURE_2D, i, glInternalFormat, mipmap.width, mipmap.height, 0, mipmap.data ); - const needsUpdate = cache.focus !== camera.focus || cache.fov !== camera.fov || - cache.aspect !== camera.aspect * this.aspect || cache.near !== camera.near || - cache.far !== camera.far || cache.zoom !== camera.zoom || cache.eyeSep !== this.eyeSep; + } - if ( needsUpdate ) { + } else { - cache.focus = camera.focus; - cache.fov = camera.fov; - cache.aspect = camera.aspect * this.aspect; - cache.near = camera.near; - cache.far = camera.far; - cache.zoom = camera.zoom; - cache.eyeSep = this.eyeSep; + console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()' ); - // Off-axis stereoscopic effect based on - // http://paulbourke.net/stereographics/stereorender/ + } - _projectionMatrix.copy( camera.projectionMatrix ); - const eyeSepHalf = cache.eyeSep / 2; - const eyeSepOnProjection = eyeSepHalf * cache.near / cache.focus; - const ymax = ( cache.near * Math.tan( DEG2RAD * cache.fov * 0.5 ) ) / cache.zoom; - let xmin, xmax; + } else { - // translate xOffset + if ( useTexStorage ) { + + if ( dataReady ) { - _eyeLeft.elements[ 12 ] = - eyeSepHalf; - _eyeRight.elements[ 12 ] = eyeSepHalf; + state.texSubImage2D( _gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, glType, mipmap.data ); - // for left eye + } - xmin = - ymax * cache.aspect + eyeSepOnProjection; - xmax = ymax * cache.aspect + eyeSepOnProjection; + } else { - _projectionMatrix.elements[ 0 ] = 2 * cache.near / ( xmax - xmin ); - _projectionMatrix.elements[ 8 ] = ( xmax + xmin ) / ( xmax - xmin ); + state.texImage2D( _gl.TEXTURE_2D, i, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); - this.cameraL.projectionMatrix.copy( _projectionMatrix ); + } - // for right eye + } - xmin = - ymax * cache.aspect - eyeSepOnProjection; - xmax = ymax * cache.aspect - eyeSepOnProjection; + } - _projectionMatrix.elements[ 0 ] = 2 * cache.near / ( xmax - xmin ); - _projectionMatrix.elements[ 8 ] = ( xmax + xmin ) / ( xmax - xmin ); + } - this.cameraR.projectionMatrix.copy( _projectionMatrix ); + } else if ( texture.isDataArrayTexture ) { - } + if ( useTexStorage ) { - this.cameraL.matrixWorld.copy( camera.matrixWorld ).multiply( _eyeLeft ); - this.cameraR.matrixWorld.copy( camera.matrixWorld ).multiply( _eyeRight ); + if ( allocateMemory ) { - } + state.texStorage3D( _gl.TEXTURE_2D_ARRAY, levels, glInternalFormat, image.width, image.height, image.depth ); -} + } -class Clock { + if ( dataReady ) { - constructor( autoStart = true ) { + if ( texture.layerUpdates.size > 0 ) { - this.autoStart = autoStart; + const layerByteLength = getByteLength( image.width, image.height, texture.format, texture.type ); - this.startTime = 0; - this.oldTime = 0; - this.elapsedTime = 0; + for ( const layerIndex of texture.layerUpdates ) { - this.running = false; + const layerData = image.data.subarray( + layerIndex * layerByteLength / image.data.BYTES_PER_ELEMENT, + ( layerIndex + 1 ) * layerByteLength / image.data.BYTES_PER_ELEMENT + ); + state.texSubImage3D( _gl.TEXTURE_2D_ARRAY, 0, 0, 0, layerIndex, image.width, image.height, 1, glFormat, glType, layerData ); - } + } - start() { + texture.clearLayerUpdates(); - this.startTime = now(); + } else { - this.oldTime = this.startTime; - this.elapsedTime = 0; - this.running = true; + state.texSubImage3D( _gl.TEXTURE_2D_ARRAY, 0, 0, 0, 0, image.width, image.height, image.depth, glFormat, glType, image.data ); - } + } - stop() { + } - this.getElapsedTime(); - this.running = false; - this.autoStart = false; + } else { - } + state.texImage3D( _gl.TEXTURE_2D_ARRAY, 0, glInternalFormat, image.width, image.height, image.depth, 0, glFormat, glType, image.data ); - getElapsedTime() { + } - this.getDelta(); - return this.elapsedTime; + } else if ( texture.isData3DTexture ) { - } + if ( useTexStorage ) { - getDelta() { + if ( allocateMemory ) { - let diff = 0; + state.texStorage3D( _gl.TEXTURE_3D, levels, glInternalFormat, image.width, image.height, image.depth ); - if ( this.autoStart && ! this.running ) { + } - this.start(); - return 0; + if ( dataReady ) { - } + state.texSubImage3D( _gl.TEXTURE_3D, 0, 0, 0, 0, image.width, image.height, image.depth, glFormat, glType, image.data ); - if ( this.running ) { + } - const newTime = now(); + } else { - diff = ( newTime - this.oldTime ) / 1000; - this.oldTime = newTime; + state.texImage3D( _gl.TEXTURE_3D, 0, glInternalFormat, image.width, image.height, image.depth, 0, glFormat, glType, image.data ); - this.elapsedTime += diff; + } - } + } else if ( texture.isFramebufferTexture ) { - return diff; + if ( allocateMemory ) { - } + if ( useTexStorage ) { -} + state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, image.width, image.height ); -function now() { + } else { - return ( typeof performance === 'undefined' ? Date : performance ).now(); // see #10732 + let width = image.width, height = image.height; -} + for ( let i = 0; i < levels; i ++ ) { -const _position$1 = /*@__PURE__*/ new Vector3(); -const _quaternion$1 = /*@__PURE__*/ new Quaternion(); -const _scale$1 = /*@__PURE__*/ new Vector3(); -const _orientation$1 = /*@__PURE__*/ new Vector3(); + state.texImage2D( _gl.TEXTURE_2D, i, glInternalFormat, width, height, 0, glFormat, glType, null ); -class AudioListener extends Object3D { + width >>= 1; + height >>= 1; - constructor() { + } - super(); + } - this.type = 'AudioListener'; + } - this.context = AudioContext.getContext(); + } else { - this.gain = this.context.createGain(); - this.gain.connect( this.context.destination ); + // regular Texture (image, video, canvas) - this.filter = null; + // use manually created mipmaps if available + // if there are no manual mipmaps + // set 0 level mipmap and then use GL to generate other mipmap levels - this.timeDelta = 0; + if ( mipmaps.length > 0 ) { - // private + if ( useTexStorage && allocateMemory ) { - this._clock = new Clock(); + const dimensions = getDimensions( mipmaps[ 0 ] ); - } + state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, dimensions.width, dimensions.height ); - getInput() { + } - return this.gain; + for ( let i = 0, il = mipmaps.length; i < il; i ++ ) { - } + mipmap = mipmaps[ i ]; - removeFilter() { + if ( useTexStorage ) { - if ( this.filter !== null ) { + if ( dataReady ) { - this.gain.disconnect( this.filter ); - this.filter.disconnect( this.context.destination ); - this.gain.connect( this.context.destination ); - this.filter = null; + state.texSubImage2D( _gl.TEXTURE_2D, i, 0, 0, glFormat, glType, mipmap ); - } + } - return this; + } else { - } + state.texImage2D( _gl.TEXTURE_2D, i, glInternalFormat, glFormat, glType, mipmap ); - getFilter() { + } - return this.filter; + } - } + texture.generateMipmaps = false; - setFilter( value ) { + } else { - if ( this.filter !== null ) { + if ( useTexStorage ) { - this.gain.disconnect( this.filter ); - this.filter.disconnect( this.context.destination ); + if ( allocateMemory ) { - } else { + const dimensions = getDimensions( image ); - this.gain.disconnect( this.context.destination ); + state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, dimensions.width, dimensions.height ); - } + } - this.filter = value; - this.gain.connect( this.filter ); - this.filter.connect( this.context.destination ); + if ( dataReady ) { - return this; + state.texSubImage2D( _gl.TEXTURE_2D, 0, 0, 0, glFormat, glType, image ); - } + } - getMasterVolume() { + } else { - return this.gain.gain.value; + state.texImage2D( _gl.TEXTURE_2D, 0, glInternalFormat, glFormat, glType, image ); - } + } - setMasterVolume( value ) { + } - this.gain.gain.setTargetAtTime( value, this.context.currentTime, 0.01 ); + } - return this; + if ( textureNeedsGenerateMipmaps( texture ) ) { - } + generateMipmap( textureType ); - updateMatrixWorld( force ) { + } - super.updateMatrixWorld( force ); + sourceProperties.__version = source.version; - const listener = this.context.listener; - const up = this.up; + if ( texture.onUpdate ) texture.onUpdate( texture ); - this.timeDelta = this._clock.getDelta(); + } - this.matrixWorld.decompose( _position$1, _quaternion$1, _scale$1 ); + textureProperties.__version = texture.version; - _orientation$1.set( 0, 0, - 1 ).applyQuaternion( _quaternion$1 ); + } - if ( listener.positionX ) { + function uploadCubeTexture( textureProperties, texture, slot ) { - // code path for Chrome (see #14393) + if ( texture.image.length !== 6 ) return; - const endTime = this.context.currentTime + this.timeDelta; + const forceUpload = initTexture( textureProperties, texture ); + const source = texture.source; - listener.positionX.linearRampToValueAtTime( _position$1.x, endTime ); - listener.positionY.linearRampToValueAtTime( _position$1.y, endTime ); - listener.positionZ.linearRampToValueAtTime( _position$1.z, endTime ); - listener.forwardX.linearRampToValueAtTime( _orientation$1.x, endTime ); - listener.forwardY.linearRampToValueAtTime( _orientation$1.y, endTime ); - listener.forwardZ.linearRampToValueAtTime( _orientation$1.z, endTime ); - listener.upX.linearRampToValueAtTime( up.x, endTime ); - listener.upY.linearRampToValueAtTime( up.y, endTime ); - listener.upZ.linearRampToValueAtTime( up.z, endTime ); + state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); - } else { + const sourceProperties = properties.get( source ); - listener.setPosition( _position$1.x, _position$1.y, _position$1.z ); - listener.setOrientation( _orientation$1.x, _orientation$1.y, _orientation$1.z, up.x, up.y, up.z ); + if ( source.version !== sourceProperties.__version || forceUpload === true ) { - } + state.activeTexture( _gl.TEXTURE0 + slot ); - } + const workingPrimaries = ColorManagement.getPrimaries( ColorManagement.workingColorSpace ); + const texturePrimaries = texture.colorSpace === NoColorSpace ? null : ColorManagement.getPrimaries( texture.colorSpace ); + const unpackConversion = texture.colorSpace === NoColorSpace || workingPrimaries === texturePrimaries ? _gl.NONE : _gl.BROWSER_DEFAULT_WEBGL; -} + _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY ); + _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha ); + _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, texture.unpackAlignment ); + _gl.pixelStorei( _gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, unpackConversion ); -class Audio extends Object3D { + const isCompressed = ( texture.isCompressedTexture || texture.image[ 0 ].isCompressedTexture ); + const isDataTexture = ( texture.image[ 0 ] && texture.image[ 0 ].isDataTexture ); - constructor( listener ) { + const cubeImage = []; - super(); + for ( let i = 0; i < 6; i ++ ) { - this.type = 'Audio'; + if ( ! isCompressed && ! isDataTexture ) { - this.listener = listener; - this.context = listener.context; + cubeImage[ i ] = resizeImage( texture.image[ i ], true, capabilities.maxCubemapSize ); - this.gain = this.context.createGain(); - this.gain.connect( listener.getInput() ); + } else { - this.autoplay = false; + cubeImage[ i ] = isDataTexture ? texture.image[ i ].image : texture.image[ i ]; - this.buffer = null; - this.detune = 0; - this.loop = false; - this.loopStart = 0; - this.loopEnd = 0; - this.offset = 0; - this.duration = undefined; - this.playbackRate = 1; - this.isPlaying = false; - this.hasPlaybackControl = true; - this.source = null; - this.sourceType = 'empty'; + } - this._startedAt = 0; - this._progress = 0; - this._connected = false; + cubeImage[ i ] = verifyColorSpace( texture, cubeImage[ i ] ); - this.filters = []; + } - } + const image = cubeImage[ 0 ], + glFormat = utils.convert( texture.format, texture.colorSpace ), + glType = utils.convert( texture.type ), + glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace ); - getOutput() { + const useTexStorage = ( texture.isVideoTexture !== true ); + const allocateMemory = ( sourceProperties.__version === undefined ) || ( forceUpload === true ); + const dataReady = source.dataReady; + let levels = getMipLevels( texture, image ); - return this.gain; + setTextureParameters( _gl.TEXTURE_CUBE_MAP, texture ); - } + let mipmaps; - setNodeSource( audioNode ) { + if ( isCompressed ) { - this.hasPlaybackControl = false; - this.sourceType = 'audioNode'; - this.source = audioNode; - this.connect(); + if ( useTexStorage && allocateMemory ) { - return this; + state.texStorage2D( _gl.TEXTURE_CUBE_MAP, levels, glInternalFormat, image.width, image.height ); - } + } - setMediaElementSource( mediaElement ) { + for ( let i = 0; i < 6; i ++ ) { - this.hasPlaybackControl = false; - this.sourceType = 'mediaNode'; - this.source = this.context.createMediaElementSource( mediaElement ); - this.connect(); + mipmaps = cubeImage[ i ].mipmaps; - return this; + for ( let j = 0; j < mipmaps.length; j ++ ) { - } + const mipmap = mipmaps[ j ]; - setMediaStreamSource( mediaStream ) { + if ( texture.format !== RGBAFormat ) { - this.hasPlaybackControl = false; - this.sourceType = 'mediaStreamNode'; - this.source = this.context.createMediaStreamSource( mediaStream ); - this.connect(); + if ( glFormat !== null ) { - return this; + if ( useTexStorage ) { - } + if ( dataReady ) { - setBuffer( audioBuffer ) { + state.compressedTexSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, 0, 0, mipmap.width, mipmap.height, glFormat, mipmap.data ); - this.buffer = audioBuffer; - this.sourceType = 'buffer'; + } - if ( this.autoplay ) this.play(); + } else { - return this; + state.compressedTexImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glInternalFormat, mipmap.width, mipmap.height, 0, mipmap.data ); - } + } - play( delay = 0 ) { + } else { - if ( this.isPlaying === true ) { + console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .setTextureCube()' ); - console.warn( 'THREE.Audio: Audio is already playing.' ); - return; + } - } + } else { - if ( this.hasPlaybackControl === false ) { + if ( useTexStorage ) { - console.warn( 'THREE.Audio: this Audio has no playback control.' ); - return; + if ( dataReady ) { - } + state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, 0, 0, mipmap.width, mipmap.height, glFormat, glType, mipmap.data ); - this._startedAt = this.context.currentTime + delay; + } - const source = this.context.createBufferSource(); - source.buffer = this.buffer; - source.loop = this.loop; - source.loopStart = this.loopStart; - source.loopEnd = this.loopEnd; - source.onended = this.onEnded.bind( this ); - source.start( this._startedAt, this._progress + this.offset, this.duration ); + } else { - this.isPlaying = true; + state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); - this.source = source; + } - this.setDetune( this.detune ); - this.setPlaybackRate( this.playbackRate ); + } - return this.connect(); + } - } + } - pause() { + } else { - if ( this.hasPlaybackControl === false ) { + mipmaps = texture.mipmaps; - console.warn( 'THREE.Audio: this Audio has no playback control.' ); - return; + if ( useTexStorage && allocateMemory ) { - } + // TODO: Uniformly handle mipmap definitions + // Normal textures and compressed cube textures define base level + mips with their mipmap array + // Uncompressed cube textures use their mipmap array only for mips (no base level) - if ( this.isPlaying === true ) { + if ( mipmaps.length > 0 ) levels ++; - // update current progress + const dimensions = getDimensions( cubeImage[ 0 ] ); - this._progress += Math.max( this.context.currentTime - this._startedAt, 0 ) * this.playbackRate; + state.texStorage2D( _gl.TEXTURE_CUBE_MAP, levels, glInternalFormat, dimensions.width, dimensions.height ); - if ( this.loop === true ) { + } - // ensure _progress does not exceed duration with looped audios + for ( let i = 0; i < 6; i ++ ) { - this._progress = this._progress % ( this.duration || this.buffer.duration ); + if ( isDataTexture ) { - } + if ( useTexStorage ) { - this.source.stop(); - this.source.onended = null; + if ( dataReady ) { - this.isPlaying = false; + state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, 0, 0, cubeImage[ i ].width, cubeImage[ i ].height, glFormat, glType, cubeImage[ i ].data ); - } + } - return this; + } else { - } + state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, cubeImage[ i ].width, cubeImage[ i ].height, 0, glFormat, glType, cubeImage[ i ].data ); - stop() { + } - if ( this.hasPlaybackControl === false ) { + for ( let j = 0; j < mipmaps.length; j ++ ) { - console.warn( 'THREE.Audio: this Audio has no playback control.' ); - return; + const mipmap = mipmaps[ j ]; + const mipmapImage = mipmap.image[ i ].image; - } + if ( useTexStorage ) { - this._progress = 0; + if ( dataReady ) { - if ( this.source !== null ) { + state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, 0, 0, mipmapImage.width, mipmapImage.height, glFormat, glType, mipmapImage.data ); - this.source.stop(); - this.source.onended = null; + } - } + } else { - this.isPlaying = false; + state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, glInternalFormat, mipmapImage.width, mipmapImage.height, 0, glFormat, glType, mipmapImage.data ); - return this; + } - } + } - connect() { + } else { - if ( this.filters.length > 0 ) { + if ( useTexStorage ) { - this.source.connect( this.filters[ 0 ] ); + if ( dataReady ) { - for ( let i = 1, l = this.filters.length; i < l; i ++ ) { + state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, 0, 0, glFormat, glType, cubeImage[ i ] ); - this.filters[ i - 1 ].connect( this.filters[ i ] ); + } - } + } else { - this.filters[ this.filters.length - 1 ].connect( this.getOutput() ); + state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, glFormat, glType, cubeImage[ i ] ); - } else { + } - this.source.connect( this.getOutput() ); + for ( let j = 0; j < mipmaps.length; j ++ ) { - } + const mipmap = mipmaps[ j ]; - this._connected = true; + if ( useTexStorage ) { - return this; + if ( dataReady ) { - } + state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, 0, 0, glFormat, glType, mipmap.image[ i ] ); - disconnect() { + } - if ( this.filters.length > 0 ) { + } else { - this.source.disconnect( this.filters[ 0 ] ); + state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, glInternalFormat, glFormat, glType, mipmap.image[ i ] ); - for ( let i = 1, l = this.filters.length; i < l; i ++ ) { + } - this.filters[ i - 1 ].disconnect( this.filters[ i ] ); + } - } + } - this.filters[ this.filters.length - 1 ].disconnect( this.getOutput() ); + } - } else { + } - this.source.disconnect( this.getOutput() ); + if ( textureNeedsGenerateMipmaps( texture ) ) { - } + // We assume images for cube map have the same size. + generateMipmap( _gl.TEXTURE_CUBE_MAP ); - this._connected = false; + } - return this; + sourceProperties.__version = source.version; - } + if ( texture.onUpdate ) texture.onUpdate( texture ); - getFilters() { + } - return this.filters; + textureProperties.__version = texture.version; } - setFilters( value ) { + // Render targets - if ( ! value ) value = []; + // Setup storage for target texture and bind it to correct framebuffer + function setupFrameBufferTexture( framebuffer, renderTarget, texture, attachment, textureTarget, level ) { - if ( this._connected === true ) { + const glFormat = utils.convert( texture.format, texture.colorSpace ); + const glType = utils.convert( texture.type ); + const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace ); + const renderTargetProperties = properties.get( renderTarget ); + const textureProperties = properties.get( texture ); - this.disconnect(); - this.filters = value.slice(); - this.connect(); + textureProperties.__renderTarget = renderTarget; - } else { + if ( ! renderTargetProperties.__hasExternalTextures ) { - this.filters = value.slice(); + const width = Math.max( 1, renderTarget.width >> level ); + const height = Math.max( 1, renderTarget.height >> level ); - } + if ( textureTarget === _gl.TEXTURE_3D || textureTarget === _gl.TEXTURE_2D_ARRAY ) { - return this; + state.texImage3D( textureTarget, level, glInternalFormat, width, height, renderTarget.depth, 0, glFormat, glType, null ); - } + } else { - setDetune( value ) { + state.texImage2D( textureTarget, level, glInternalFormat, width, height, 0, glFormat, glType, null ); - this.detune = value; + } - if ( this.source.detune === undefined ) return; // only set detune when available + } - if ( this.isPlaying === true ) { + state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); - this.source.detune.setTargetAtTime( this.detune, this.context.currentTime, 0.01 ); + if ( useMultisampledRTT( renderTarget ) ) { - } + multisampledRTTExt.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, attachment, textureTarget, textureProperties.__webglTexture, 0, getRenderTargetSamples( renderTarget ) ); - return this; + } else if ( textureTarget === _gl.TEXTURE_2D || ( textureTarget >= _gl.TEXTURE_CUBE_MAP_POSITIVE_X && textureTarget <= _gl.TEXTURE_CUBE_MAP_NEGATIVE_Z ) ) { // see #24753 - } + _gl.framebufferTexture2D( _gl.FRAMEBUFFER, attachment, textureTarget, textureProperties.__webglTexture, level ); - getDetune() { + } - return this.detune; + state.bindFramebuffer( _gl.FRAMEBUFFER, null ); } - getFilter() { + // Setup storage for internal depth/stencil buffers and bind to correct framebuffer + function setupRenderBufferStorage( renderbuffer, renderTarget, isMultisample ) { - return this.getFilters()[ 0 ]; + _gl.bindRenderbuffer( _gl.RENDERBUFFER, renderbuffer ); - } + if ( renderTarget.depthBuffer ) { - setFilter( filter ) { + // retrieve the depth attachment types + const depthTexture = renderTarget.depthTexture; + const depthType = depthTexture && depthTexture.isDepthTexture ? depthTexture.type : null; + const glInternalFormat = getInternalDepthFormat( renderTarget.stencilBuffer, depthType ); + const glAttachmentType = renderTarget.stencilBuffer ? _gl.DEPTH_STENCIL_ATTACHMENT : _gl.DEPTH_ATTACHMENT; - return this.setFilters( filter ? [ filter ] : [] ); + // set up the attachment + const samples = getRenderTargetSamples( renderTarget ); + const isUseMultisampledRTT = useMultisampledRTT( renderTarget ); + if ( isUseMultisampledRTT ) { - } + multisampledRTTExt.renderbufferStorageMultisampleEXT( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); - setPlaybackRate( value ) { + } else if ( isMultisample ) { - if ( this.hasPlaybackControl === false ) { + _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); - console.warn( 'THREE.Audio: this Audio has no playback control.' ); - return; + } else { - } + _gl.renderbufferStorage( _gl.RENDERBUFFER, glInternalFormat, renderTarget.width, renderTarget.height ); - this.playbackRate = value; + } - if ( this.isPlaying === true ) { + _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, glAttachmentType, _gl.RENDERBUFFER, renderbuffer ); - this.source.playbackRate.setTargetAtTime( this.playbackRate, this.context.currentTime, 0.01 ); + } else { - } + const textures = renderTarget.textures; - return this; + for ( let i = 0; i < textures.length; i ++ ) { - } + const texture = textures[ i ]; - getPlaybackRate() { + const glFormat = utils.convert( texture.format, texture.colorSpace ); + const glType = utils.convert( texture.type ); + const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace ); + const samples = getRenderTargetSamples( renderTarget ); - return this.playbackRate; + if ( isMultisample && useMultisampledRTT( renderTarget ) === false ) { - } + _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); - onEnded() { + } else if ( useMultisampledRTT( renderTarget ) ) { - this.isPlaying = false; + multisampledRTTExt.renderbufferStorageMultisampleEXT( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); - } + } else { - getLoop() { + _gl.renderbufferStorage( _gl.RENDERBUFFER, glInternalFormat, renderTarget.width, renderTarget.height ); - if ( this.hasPlaybackControl === false ) { + } - console.warn( 'THREE.Audio: this Audio has no playback control.' ); - return false; + } } - return this.loop; + _gl.bindRenderbuffer( _gl.RENDERBUFFER, null ); } - setLoop( value ) { + // Setup resources for a Depth Texture for a FBO (needs an extension) + function setupDepthTexture( framebuffer, renderTarget ) { - if ( this.hasPlaybackControl === false ) { + const isCube = ( renderTarget && renderTarget.isWebGLCubeRenderTarget ); + if ( isCube ) throw new Error( 'Depth Texture with cube render targets is not supported' ); - console.warn( 'THREE.Audio: this Audio has no playback control.' ); - return; + state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); + + if ( ! ( renderTarget.depthTexture && renderTarget.depthTexture.isDepthTexture ) ) { + + throw new Error( 'renderTarget.depthTexture must be an instance of THREE.DepthTexture' ); } - this.loop = value; + const textureProperties = properties.get( renderTarget.depthTexture ); + textureProperties.__renderTarget = renderTarget; - if ( this.isPlaying === true ) { + // upload an empty depth texture with framebuffer size + if ( ! textureProperties.__webglTexture || + renderTarget.depthTexture.image.width !== renderTarget.width || + renderTarget.depthTexture.image.height !== renderTarget.height ) { - this.source.loop = this.loop; + renderTarget.depthTexture.image.width = renderTarget.width; + renderTarget.depthTexture.image.height = renderTarget.height; + renderTarget.depthTexture.needsUpdate = true; } - return this; - - } + setTexture2D( renderTarget.depthTexture, 0 ); - setLoopStart( value ) { + const webglDepthTexture = textureProperties.__webglTexture; + const samples = getRenderTargetSamples( renderTarget ); - this.loopStart = value; + if ( renderTarget.depthTexture.format === DepthFormat ) { - return this; + if ( useMultisampledRTT( renderTarget ) ) { - } + multisampledRTTExt.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0, samples ); - setLoopEnd( value ) { + } else { - this.loopEnd = value; + _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0 ); - return this; + } - } + } else if ( renderTarget.depthTexture.format === DepthStencilFormat ) { - getVolume() { + if ( useMultisampledRTT( renderTarget ) ) { - return this.gain.gain.value; + multisampledRTTExt.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0, samples ); - } + } else { - setVolume( value ) { + _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0 ); - this.gain.gain.setTargetAtTime( value, this.context.currentTime, 0.01 ); + } - return this; + } else { - } + throw new Error( 'Unknown depthTexture format' ); -} + } -const _position = /*@__PURE__*/ new Vector3(); -const _quaternion = /*@__PURE__*/ new Quaternion(); -const _scale = /*@__PURE__*/ new Vector3(); -const _orientation = /*@__PURE__*/ new Vector3(); + } -class PositionalAudio extends Audio { + // Setup GL resources for a non-texture depth buffer + function setupDepthRenderbuffer( renderTarget ) { - constructor( listener ) { + const renderTargetProperties = properties.get( renderTarget ); + const isCube = ( renderTarget.isWebGLCubeRenderTarget === true ); - super( listener ); + // if the bound depth texture has changed + if ( renderTargetProperties.__boundDepthTexture !== renderTarget.depthTexture ) { - this.panner = this.context.createPanner(); - this.panner.panningModel = 'HRTF'; - this.panner.connect( this.gain ); + // fire the dispose event to get rid of stored state associated with the previously bound depth buffer + const depthTexture = renderTarget.depthTexture; + if ( renderTargetProperties.__depthDisposeCallback ) { - } + renderTargetProperties.__depthDisposeCallback(); - disconnect() { + } - super.disconnect(); + // set up dispose listeners to track when the currently attached buffer is implicitly unbound + if ( depthTexture ) { - this.panner.disconnect( this.gain ); + const disposeEvent = () => { - } + delete renderTargetProperties.__boundDepthTexture; + delete renderTargetProperties.__depthDisposeCallback; + depthTexture.removeEventListener( 'dispose', disposeEvent ); - getOutput() { + }; - return this.panner; + depthTexture.addEventListener( 'dispose', disposeEvent ); + renderTargetProperties.__depthDisposeCallback = disposeEvent; - } + } - getRefDistance() { + renderTargetProperties.__boundDepthTexture = depthTexture; - return this.panner.refDistance; + } - } + if ( renderTarget.depthTexture && ! renderTargetProperties.__autoAllocateDepthBuffer ) { - setRefDistance( value ) { + if ( isCube ) throw new Error( 'target.depthTexture not supported in Cube render targets' ); - this.panner.refDistance = value; + const mipmaps = renderTarget.texture.mipmaps; - return this; + if ( mipmaps && mipmaps.length > 0 ) { - } + setupDepthTexture( renderTargetProperties.__webglFramebuffer[ 0 ], renderTarget ); - getRolloffFactor() { + } else { - return this.panner.rolloffFactor; + setupDepthTexture( renderTargetProperties.__webglFramebuffer, renderTarget ); - } + } - setRolloffFactor( value ) { + } else { - this.panner.rolloffFactor = value; + if ( isCube ) { - return this; + renderTargetProperties.__webglDepthbuffer = []; - } + for ( let i = 0; i < 6; i ++ ) { - getDistanceModel() { + state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer[ i ] ); - return this.panner.distanceModel; + if ( renderTargetProperties.__webglDepthbuffer[ i ] === undefined ) { - } + renderTargetProperties.__webglDepthbuffer[ i ] = _gl.createRenderbuffer(); + setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer[ i ], renderTarget, false ); - setDistanceModel( value ) { + } else { - this.panner.distanceModel = value; + // attach buffer if it's been created already + const glAttachmentType = renderTarget.stencilBuffer ? _gl.DEPTH_STENCIL_ATTACHMENT : _gl.DEPTH_ATTACHMENT; + const renderbuffer = renderTargetProperties.__webglDepthbuffer[ i ]; + _gl.bindRenderbuffer( _gl.RENDERBUFFER, renderbuffer ); + _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, glAttachmentType, _gl.RENDERBUFFER, renderbuffer ); - return this; + } - } + } - getMaxDistance() { + } else { - return this.panner.maxDistance; + const mipmaps = renderTarget.texture.mipmaps; - } + if ( mipmaps && mipmaps.length > 0 ) { - setMaxDistance( value ) { + state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer[ 0 ] ); - this.panner.maxDistance = value; + } else { - return this; + state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); - } + } - setDirectionalCone( coneInnerAngle, coneOuterAngle, coneOuterGain ) { + if ( renderTargetProperties.__webglDepthbuffer === undefined ) { - this.panner.coneInnerAngle = coneInnerAngle; - this.panner.coneOuterAngle = coneOuterAngle; - this.panner.coneOuterGain = coneOuterGain; + renderTargetProperties.__webglDepthbuffer = _gl.createRenderbuffer(); + setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer, renderTarget, false ); - return this; + } else { - } + // attach buffer if it's been created already + const glAttachmentType = renderTarget.stencilBuffer ? _gl.DEPTH_STENCIL_ATTACHMENT : _gl.DEPTH_ATTACHMENT; + const renderbuffer = renderTargetProperties.__webglDepthbuffer; + _gl.bindRenderbuffer( _gl.RENDERBUFFER, renderbuffer ); + _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, glAttachmentType, _gl.RENDERBUFFER, renderbuffer ); - updateMatrixWorld( force ) { + } - super.updateMatrixWorld( force ); + } - if ( this.hasPlaybackControl === true && this.isPlaying === false ) return; + } - this.matrixWorld.decompose( _position, _quaternion, _scale ); + state.bindFramebuffer( _gl.FRAMEBUFFER, null ); - _orientation.set( 0, 0, 1 ).applyQuaternion( _quaternion ); + } - const panner = this.panner; + // rebind framebuffer with external textures + function rebindTextures( renderTarget, colorTexture, depthTexture ) { - if ( panner.positionX ) { + const renderTargetProperties = properties.get( renderTarget ); - // code path for Chrome and Firefox (see #14393) + if ( colorTexture !== undefined ) { - const endTime = this.context.currentTime + this.listener.timeDelta; + setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, renderTarget.texture, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D, 0 ); - panner.positionX.linearRampToValueAtTime( _position.x, endTime ); - panner.positionY.linearRampToValueAtTime( _position.y, endTime ); - panner.positionZ.linearRampToValueAtTime( _position.z, endTime ); - panner.orientationX.linearRampToValueAtTime( _orientation.x, endTime ); - panner.orientationY.linearRampToValueAtTime( _orientation.y, endTime ); - panner.orientationZ.linearRampToValueAtTime( _orientation.z, endTime ); + } - } else { + if ( depthTexture !== undefined ) { - panner.setPosition( _position.x, _position.y, _position.z ); - panner.setOrientation( _orientation.x, _orientation.y, _orientation.z ); + setupDepthRenderbuffer( renderTarget ); } } -} + // Set up GL resources for the render target + function setupRenderTarget( renderTarget ) { -class AudioAnalyser { + const texture = renderTarget.texture; - constructor( audio, fftSize = 2048 ) { + const renderTargetProperties = properties.get( renderTarget ); + const textureProperties = properties.get( texture ); - this.analyser = audio.context.createAnalyser(); - this.analyser.fftSize = fftSize; + renderTarget.addEventListener( 'dispose', onRenderTargetDispose ); - this.data = new Uint8Array( this.analyser.frequencyBinCount ); + const textures = renderTarget.textures; - audio.getOutput().connect( this.analyser ); + const isCube = ( renderTarget.isWebGLCubeRenderTarget === true ); + const isMultipleRenderTargets = ( textures.length > 1 ); - } + if ( ! isMultipleRenderTargets ) { + if ( textureProperties.__webglTexture === undefined ) { - getFrequencyData() { + textureProperties.__webglTexture = _gl.createTexture(); - this.analyser.getByteFrequencyData( this.data ); + } - return this.data; + textureProperties.__version = texture.version; + info.memory.textures ++; - } + } - getAverageFrequency() { + // Setup framebuffer - let value = 0; - const data = this.getFrequencyData(); + if ( isCube ) { - for ( let i = 0; i < data.length; i ++ ) { + renderTargetProperties.__webglFramebuffer = []; - value += data[ i ]; + for ( let i = 0; i < 6; i ++ ) { - } + if ( texture.mipmaps && texture.mipmaps.length > 0 ) { - return value / data.length; + renderTargetProperties.__webglFramebuffer[ i ] = []; - } + for ( let level = 0; level < texture.mipmaps.length; level ++ ) { -} + renderTargetProperties.__webglFramebuffer[ i ][ level ] = _gl.createFramebuffer(); -class PropertyMixer { + } - constructor( binding, typeName, valueSize ) { + } else { - this.binding = binding; - this.valueSize = valueSize; + renderTargetProperties.__webglFramebuffer[ i ] = _gl.createFramebuffer(); - let mixFunction, - mixFunctionAdditive, - setIdentity; + } - // buffer layout: [ incoming | accu0 | accu1 | orig | addAccu | (optional work) ] - // - // interpolators can use .buffer as their .result - // the data then goes to 'incoming' - // - // 'accu0' and 'accu1' are used frame-interleaved for - // the cumulative result and are compared to detect - // changes - // - // 'orig' stores the original state of the property - // - // 'add' is used for additive cumulative results - // - // 'work' is optional and is only present for quaternion types. It is used - // to store intermediate quaternion multiplication results + } - switch ( typeName ) { + } else { - case 'quaternion': - mixFunction = this._slerp; - mixFunctionAdditive = this._slerpAdditive; - setIdentity = this._setAdditiveIdentityQuaternion; + if ( texture.mipmaps && texture.mipmaps.length > 0 ) { - this.buffer = new Float64Array( valueSize * 6 ); - this._workIndex = 5; - break; + renderTargetProperties.__webglFramebuffer = []; - case 'string': - case 'bool': - mixFunction = this._select; + for ( let level = 0; level < texture.mipmaps.length; level ++ ) { - // Use the regular mix function and for additive on these types, - // additive is not relevant for non-numeric types - mixFunctionAdditive = this._select; + renderTargetProperties.__webglFramebuffer[ level ] = _gl.createFramebuffer(); - setIdentity = this._setAdditiveIdentityOther; + } - this.buffer = new Array( valueSize * 5 ); - break; + } else { - default: - mixFunction = this._lerp; - mixFunctionAdditive = this._lerpAdditive; - setIdentity = this._setAdditiveIdentityNumeric; + renderTargetProperties.__webglFramebuffer = _gl.createFramebuffer(); - this.buffer = new Float64Array( valueSize * 5 ); + } - } + if ( isMultipleRenderTargets ) { - this._mixBufferRegion = mixFunction; - this._mixBufferRegionAdditive = mixFunctionAdditive; - this._setIdentity = setIdentity; - this._origIndex = 3; - this._addIndex = 4; + for ( let i = 0, il = textures.length; i < il; i ++ ) { - this.cumulativeWeight = 0; - this.cumulativeWeightAdditive = 0; + const attachmentProperties = properties.get( textures[ i ] ); - this.useCount = 0; - this.referenceCount = 0; + if ( attachmentProperties.__webglTexture === undefined ) { - } + attachmentProperties.__webglTexture = _gl.createTexture(); - // accumulate data in the 'incoming' region into 'accu' - accumulate( accuIndex, weight ) { + info.memory.textures ++; - // note: happily accumulating nothing when weight = 0, the caller knows - // the weight and shouldn't have made the call in the first place + } - const buffer = this.buffer, - stride = this.valueSize, - offset = accuIndex * stride + stride; + } - let currentWeight = this.cumulativeWeight; + } - if ( currentWeight === 0 ) { + if ( ( renderTarget.samples > 0 ) && useMultisampledRTT( renderTarget ) === false ) { - // accuN := incoming * weight + renderTargetProperties.__webglMultisampledFramebuffer = _gl.createFramebuffer(); + renderTargetProperties.__webglColorRenderbuffer = []; - for ( let i = 0; i !== stride; ++ i ) { + state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); - buffer[ offset + i ] = buffer[ i ]; + for ( let i = 0; i < textures.length; i ++ ) { - } + const texture = textures[ i ]; + renderTargetProperties.__webglColorRenderbuffer[ i ] = _gl.createRenderbuffer(); - currentWeight = weight; + _gl.bindRenderbuffer( _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] ); - } else { + const glFormat = utils.convert( texture.format, texture.colorSpace ); + const glType = utils.convert( texture.type ); + const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace, renderTarget.isXRRenderTarget === true ); + const samples = getRenderTargetSamples( renderTarget ); + _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); - // accuN := accuN + incoming * weight + _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] ); - currentWeight += weight; - const mix = weight / currentWeight; - this._mixBufferRegion( buffer, offset, 0, mix, stride ); + } - } + _gl.bindRenderbuffer( _gl.RENDERBUFFER, null ); - this.cumulativeWeight = currentWeight; + if ( renderTarget.depthBuffer ) { - } + renderTargetProperties.__webglDepthRenderbuffer = _gl.createRenderbuffer(); + setupRenderBufferStorage( renderTargetProperties.__webglDepthRenderbuffer, renderTarget, true ); - // accumulate data in the 'incoming' region into 'add' - accumulateAdditive( weight ) { + } - const buffer = this.buffer, - stride = this.valueSize, - offset = stride * this._addIndex; + state.bindFramebuffer( _gl.FRAMEBUFFER, null ); - if ( this.cumulativeWeightAdditive === 0 ) { + } - // add = identity + } - this._setIdentity(); + // Setup color buffer - } + if ( isCube ) { - // add := add + incoming * weight + state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__webglTexture ); + setTextureParameters( _gl.TEXTURE_CUBE_MAP, texture ); - this._mixBufferRegionAdditive( buffer, offset, 0, weight, stride ); - this.cumulativeWeightAdditive += weight; + for ( let i = 0; i < 6; i ++ ) { - } + if ( texture.mipmaps && texture.mipmaps.length > 0 ) { - // apply the state of 'accu' to the binding when accus differ - apply( accuIndex ) { + for ( let level = 0; level < texture.mipmaps.length; level ++ ) { - const stride = this.valueSize, - buffer = this.buffer, - offset = accuIndex * stride + stride, + setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer[ i ][ level ], renderTarget, texture, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, level ); - weight = this.cumulativeWeight, - weightAdditive = this.cumulativeWeightAdditive, + } - binding = this.binding; + } else { - this.cumulativeWeight = 0; - this.cumulativeWeightAdditive = 0; + setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer[ i ], renderTarget, texture, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0 ); - if ( weight < 1 ) { + } - // accuN := accuN + original * ( 1 - cumulativeWeight ) + } - const originalValueOffset = stride * this._origIndex; + if ( textureNeedsGenerateMipmaps( texture ) ) { - this._mixBufferRegion( - buffer, offset, originalValueOffset, 1 - weight, stride ); + generateMipmap( _gl.TEXTURE_CUBE_MAP ); - } + } - if ( weightAdditive > 0 ) { + state.unbindTexture(); - // accuN := accuN + additive accuN + } else if ( isMultipleRenderTargets ) { - this._mixBufferRegionAdditive( buffer, offset, this._addIndex * stride, 1, stride ); + for ( let i = 0, il = textures.length; i < il; i ++ ) { - } + const attachment = textures[ i ]; + const attachmentProperties = properties.get( attachment ); - for ( let i = stride, e = stride + stride; i !== e; ++ i ) { + state.bindTexture( _gl.TEXTURE_2D, attachmentProperties.__webglTexture ); + setTextureParameters( _gl.TEXTURE_2D, attachment ); + setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, attachment, _gl.COLOR_ATTACHMENT0 + i, _gl.TEXTURE_2D, 0 ); - if ( buffer[ i ] !== buffer[ i + stride ] ) { + if ( textureNeedsGenerateMipmaps( attachment ) ) { - // value has changed -> update scene graph + generateMipmap( _gl.TEXTURE_2D ); - binding.setValue( buffer, offset ); - break; + } } - } + state.unbindTexture(); - } + } else { - // remember the state of the bound property and copy it to both accus - saveOriginalState() { + let glTextureType = _gl.TEXTURE_2D; - const binding = this.binding; + if ( renderTarget.isWebGL3DRenderTarget || renderTarget.isWebGLArrayRenderTarget ) { - const buffer = this.buffer, - stride = this.valueSize, + glTextureType = renderTarget.isWebGL3DRenderTarget ? _gl.TEXTURE_3D : _gl.TEXTURE_2D_ARRAY; - originalValueOffset = stride * this._origIndex; + } - binding.getValue( buffer, originalValueOffset ); + state.bindTexture( glTextureType, textureProperties.__webglTexture ); + setTextureParameters( glTextureType, texture ); - // accu[0..1] := orig -- initially detect changes against the original - for ( let i = stride, e = originalValueOffset; i !== e; ++ i ) { + if ( texture.mipmaps && texture.mipmaps.length > 0 ) { - buffer[ i ] = buffer[ originalValueOffset + ( i % stride ) ]; + for ( let level = 0; level < texture.mipmaps.length; level ++ ) { - } + setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer[ level ], renderTarget, texture, _gl.COLOR_ATTACHMENT0, glTextureType, level ); - // Add to identity for additive - this._setIdentity(); + } - this.cumulativeWeight = 0; - this.cumulativeWeightAdditive = 0; + } else { - } + setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, texture, _gl.COLOR_ATTACHMENT0, glTextureType, 0 ); - // apply the state previously taken via 'saveOriginalState' to the binding - restoreOriginalState() { + } - const originalValueOffset = this.valueSize * 3; - this.binding.setValue( this.buffer, originalValueOffset ); + if ( textureNeedsGenerateMipmaps( texture ) ) { - } + generateMipmap( glTextureType ); - _setAdditiveIdentityNumeric() { + } - const startIndex = this._addIndex * this.valueSize; - const endIndex = startIndex + this.valueSize; + state.unbindTexture(); - for ( let i = startIndex; i < endIndex; i ++ ) { + } - this.buffer[ i ] = 0; + // Setup depth and stencil buffers + + if ( renderTarget.depthBuffer ) { + + setupDepthRenderbuffer( renderTarget ); } } - _setAdditiveIdentityQuaternion() { + function updateRenderTargetMipmap( renderTarget ) { - this._setAdditiveIdentityNumeric(); - this.buffer[ this._addIndex * this.valueSize + 3 ] = 1; + const textures = renderTarget.textures; - } + for ( let i = 0, il = textures.length; i < il; i ++ ) { - _setAdditiveIdentityOther() { + const texture = textures[ i ]; - const startIndex = this._origIndex * this.valueSize; - const targetIndex = this._addIndex * this.valueSize; + if ( textureNeedsGenerateMipmaps( texture ) ) { - for ( let i = 0; i < this.valueSize; i ++ ) { + const targetType = getTargetType( renderTarget ); + const webglTexture = properties.get( texture ).__webglTexture; - this.buffer[ targetIndex + i ] = this.buffer[ startIndex + i ]; + state.bindTexture( targetType, webglTexture ); + generateMipmap( targetType ); + state.unbindTexture(); + + } } } + const invalidationArrayRead = []; + const invalidationArrayDraw = []; - // mix functions + function updateMultisampleRenderTarget( renderTarget ) { - _select( buffer, dstOffset, srcOffset, t, stride ) { + if ( renderTarget.samples > 0 ) { - if ( t >= 0.5 ) { + if ( useMultisampledRTT( renderTarget ) === false ) { - for ( let i = 0; i !== stride; ++ i ) { + const textures = renderTarget.textures; + const width = renderTarget.width; + const height = renderTarget.height; + let mask = _gl.COLOR_BUFFER_BIT; + const depthStyle = renderTarget.stencilBuffer ? _gl.DEPTH_STENCIL_ATTACHMENT : _gl.DEPTH_ATTACHMENT; + const renderTargetProperties = properties.get( renderTarget ); + const isMultipleRenderTargets = ( textures.length > 1 ); - buffer[ dstOffset + i ] = buffer[ srcOffset + i ]; + // If MRT we need to remove FBO attachments + if ( isMultipleRenderTargets ) { - } + for ( let i = 0; i < textures.length; i ++ ) { - } + state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); + _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.RENDERBUFFER, null ); - } + state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); + _gl.framebufferTexture2D( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.TEXTURE_2D, null, 0 ); - _slerp( buffer, dstOffset, srcOffset, t ) { + } - Quaternion.slerpFlat( buffer, dstOffset, buffer, dstOffset, buffer, srcOffset, t ); + } - } + state.bindFramebuffer( _gl.READ_FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); - _slerpAdditive( buffer, dstOffset, srcOffset, t, stride ) { + const mipmaps = renderTarget.texture.mipmaps; - const workOffset = this._workIndex * stride; + if ( mipmaps && mipmaps.length > 0 ) { - // Store result in intermediate buffer offset - Quaternion.multiplyQuaternionsFlat( buffer, workOffset, buffer, dstOffset, buffer, srcOffset ); + state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, renderTargetProperties.__webglFramebuffer[ 0 ] ); - // Slerp to the intermediate result - Quaternion.slerpFlat( buffer, dstOffset, buffer, dstOffset, buffer, workOffset, t ); + } else { - } + state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); - _lerp( buffer, dstOffset, srcOffset, t, stride ) { + } - const s = 1 - t; + for ( let i = 0; i < textures.length; i ++ ) { - for ( let i = 0; i !== stride; ++ i ) { + if ( renderTarget.resolveDepthBuffer ) { - const j = dstOffset + i; + if ( renderTarget.depthBuffer ) mask |= _gl.DEPTH_BUFFER_BIT; - buffer[ j ] = buffer[ j ] * s + buffer[ srcOffset + i ] * t; + // resolving stencil is slow with a D3D backend. disable it for all transmission render targets (see #27799) - } + if ( renderTarget.stencilBuffer && renderTarget.resolveStencilBuffer ) mask |= _gl.STENCIL_BUFFER_BIT; - } + } - _lerpAdditive( buffer, dstOffset, srcOffset, t, stride ) { + if ( isMultipleRenderTargets ) { - for ( let i = 0; i !== stride; ++ i ) { + _gl.framebufferRenderbuffer( _gl.READ_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] ); - const j = dstOffset + i; + const webglTexture = properties.get( textures[ i ] ).__webglTexture; + _gl.framebufferTexture2D( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D, webglTexture, 0 ); - buffer[ j ] = buffer[ j ] + buffer[ srcOffset + i ] * t; + } - } + _gl.blitFramebuffer( 0, 0, width, height, 0, 0, width, height, mask, _gl.NEAREST ); - } + if ( supportsInvalidateFramebuffer === true ) { -} + invalidationArrayRead.length = 0; + invalidationArrayDraw.length = 0; -// Characters [].:/ are reserved for track binding syntax. -const _RESERVED_CHARS_RE = '\\[\\]\\.:\\/'; -const _reservedRe = new RegExp( '[' + _RESERVED_CHARS_RE + ']', 'g' ); + invalidationArrayRead.push( _gl.COLOR_ATTACHMENT0 + i ); -// Attempts to allow node names from any language. ES5's `\w` regexp matches -// only latin characters, and the unicode \p{L} is not yet supported. So -// instead, we exclude reserved characters and match everything else. -const _wordChar = '[^' + _RESERVED_CHARS_RE + ']'; -const _wordCharOrDot = '[^' + _RESERVED_CHARS_RE.replace( '\\.', '' ) + ']'; + if ( renderTarget.depthBuffer && renderTarget.resolveDepthBuffer === false ) { -// Parent directories, delimited by '/' or ':'. Currently unused, but must -// be matched to parse the rest of the track name. -const _directoryRe = /*@__PURE__*/ /((?:WC+[\/:])*)/.source.replace( 'WC', _wordChar ); + invalidationArrayRead.push( depthStyle ); + invalidationArrayDraw.push( depthStyle ); -// Target node. May contain word characters (a-zA-Z0-9_) and '.' or '-'. -const _nodeRe = /*@__PURE__*/ /(WCOD+)?/.source.replace( 'WCOD', _wordCharOrDot ); + _gl.invalidateFramebuffer( _gl.DRAW_FRAMEBUFFER, invalidationArrayDraw ); -// Object on target node, and accessor. May not contain reserved -// characters. Accessor may contain any character except closing bracket. -const _objectRe = /*@__PURE__*/ /(?:\.(WC+)(?:\[(.+)\])?)?/.source.replace( 'WC', _wordChar ); + } -// Property and accessor. May not contain reserved characters. Accessor may -// contain any non-bracket characters. -const _propertyRe = /*@__PURE__*/ /\.(WC+)(?:\[(.+)\])?/.source.replace( 'WC', _wordChar ); + _gl.invalidateFramebuffer( _gl.READ_FRAMEBUFFER, invalidationArrayRead ); -const _trackRe = new RegExp( '' - + '^' - + _directoryRe - + _nodeRe - + _objectRe - + _propertyRe - + '$' -); + } + + } -const _supportedObjectNames = [ 'material', 'materials', 'bones', 'map' ]; + state.bindFramebuffer( _gl.READ_FRAMEBUFFER, null ); + state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, null ); -class Composite { + // If MRT since pre-blit we removed the FBO we need to reconstruct the attachments + if ( isMultipleRenderTargets ) { - constructor( targetGroup, path, optionalParsedPath ) { + for ( let i = 0; i < textures.length; i ++ ) { - const parsedPath = optionalParsedPath || PropertyBinding.parseTrackName( path ); + state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); + _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] ); - this._targetGroup = targetGroup; - this._bindings = targetGroup.subscribe_( path, parsedPath ); + const webglTexture = properties.get( textures[ i ] ).__webglTexture; - } + state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); + _gl.framebufferTexture2D( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.TEXTURE_2D, webglTexture, 0 ); - getValue( array, offset ) { + } - this.bind(); // bind all binding + } - const firstValidIndex = this._targetGroup.nCachedObjects_, - binding = this._bindings[ firstValidIndex ]; + state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); - // and only call .getValue on the first - if ( binding !== undefined ) binding.getValue( array, offset ); + } else { - } + if ( renderTarget.depthBuffer && renderTarget.resolveDepthBuffer === false && supportsInvalidateFramebuffer ) { - setValue( array, offset ) { + const depthStyle = renderTarget.stencilBuffer ? _gl.DEPTH_STENCIL_ATTACHMENT : _gl.DEPTH_ATTACHMENT; - const bindings = this._bindings; + _gl.invalidateFramebuffer( _gl.DRAW_FRAMEBUFFER, [ depthStyle ] ); - for ( let i = this._targetGroup.nCachedObjects_, n = bindings.length; i !== n; ++ i ) { + } - bindings[ i ].setValue( array, offset ); + } } } - bind() { + function getRenderTargetSamples( renderTarget ) { - const bindings = this._bindings; + return Math.min( capabilities.maxSamples, renderTarget.samples ); - for ( let i = this._targetGroup.nCachedObjects_, n = bindings.length; i !== n; ++ i ) { + } - bindings[ i ].bind(); + function useMultisampledRTT( renderTarget ) { - } + const renderTargetProperties = properties.get( renderTarget ); + + return renderTarget.samples > 0 && extensions.has( 'WEBGL_multisampled_render_to_texture' ) === true && renderTargetProperties.__useRenderToTexture !== false; } - unbind() { + function updateVideoTexture( texture ) { - const bindings = this._bindings; + const frame = info.render.frame; - for ( let i = this._targetGroup.nCachedObjects_, n = bindings.length; i !== n; ++ i ) { + // Check the last frame we updated the VideoTexture - bindings[ i ].unbind(); + if ( _videoTextures.get( texture ) !== frame ) { + + _videoTextures.set( texture, frame ); + texture.update(); } } -} - -// Note: This class uses a State pattern on a per-method basis: -// 'bind' sets 'this.getValue' / 'setValue' and shadows the -// prototype version of these methods with one that represents -// the bound state. When the property is not found, the methods -// become no-ops. -class PropertyBinding { + function verifyColorSpace( texture, image ) { - constructor( rootNode, path, parsedPath ) { + const colorSpace = texture.colorSpace; + const format = texture.format; + const type = texture.type; - this.path = path; - this.parsedPath = parsedPath || PropertyBinding.parseTrackName( path ); + if ( texture.isCompressedTexture === true || texture.isVideoTexture === true ) return image; - this.node = PropertyBinding.findNode( rootNode, this.parsedPath.nodeName ); + if ( colorSpace !== LinearSRGBColorSpace && colorSpace !== NoColorSpace ) { - this.rootNode = rootNode; + // sRGB - // initial state of these methods that calls 'bind' - this.getValue = this._getValue_unbound; - this.setValue = this._setValue_unbound; + if ( ColorManagement.getTransfer( colorSpace ) === SRGBTransfer ) { - } + // in WebGL 2 uncompressed textures can only be sRGB encoded if they have the RGBA8 format + if ( format !== RGBAFormat || type !== UnsignedByteType ) { - static create( root, path, parsedPath ) { + console.warn( 'THREE.WebGLTextures: sRGB encoded textures have to use RGBAFormat and UnsignedByteType.' ); - if ( ! ( root && root.isAnimationObjectGroup ) ) { + } - return new PropertyBinding( root, path, parsedPath ); + } else { - } else { + console.error( 'THREE.WebGLTextures: Unsupported texture color space:', colorSpace ); - return new PropertyBinding.Composite( root, path, parsedPath ); + } } + return image; + } - /** - * Replaces spaces with underscores and removes unsupported characters from - * node names, to ensure compatibility with parseTrackName(). - * - * @param {string} name Node name to be sanitized. - * @return {string} - */ - static sanitizeNodeName( name ) { + function getDimensions( image ) { - return name.replace( /\s/g, '_' ).replace( _reservedRe, '' ); + if ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) { - } + // if intrinsic data are not available, fallback to width/height - static parseTrackName( trackName ) { + _imageDimensions.width = image.naturalWidth || image.width; + _imageDimensions.height = image.naturalHeight || image.height; - const matches = _trackRe.exec( trackName ); + } else if ( typeof VideoFrame !== 'undefined' && image instanceof VideoFrame ) { - if ( matches === null ) { + _imageDimensions.width = image.displayWidth; + _imageDimensions.height = image.displayHeight; - throw new Error( 'PropertyBinding: Cannot parse trackName: ' + trackName ); + } else { + + _imageDimensions.width = image.width; + _imageDimensions.height = image.height; } - const results = { - // directoryName: matches[ 1 ], // (tschw) currently unused - nodeName: matches[ 2 ], - objectName: matches[ 3 ], - objectIndex: matches[ 4 ], - propertyName: matches[ 5 ], // required - propertyIndex: matches[ 6 ] - }; + return _imageDimensions; - const lastDot = results.nodeName && results.nodeName.lastIndexOf( '.' ); + } - if ( lastDot !== undefined && lastDot !== - 1 ) { + // - const objectName = results.nodeName.substring( lastDot + 1 ); + this.allocateTextureUnit = allocateTextureUnit; + this.resetTextureUnits = resetTextureUnits; - // Object names must be checked against an allowlist. Otherwise, there - // is no way to parse 'foo.bar.baz': 'baz' must be a property, but - // 'bar' could be the objectName, or part of a nodeName (which can - // include '.' characters). - if ( _supportedObjectNames.indexOf( objectName ) !== - 1 ) { + this.setTexture2D = setTexture2D; + this.setTexture2DArray = setTexture2DArray; + this.setTexture3D = setTexture3D; + this.setTextureCube = setTextureCube; + this.rebindTextures = rebindTextures; + this.setupRenderTarget = setupRenderTarget; + this.updateRenderTargetMipmap = updateRenderTargetMipmap; + this.updateMultisampleRenderTarget = updateMultisampleRenderTarget; + this.setupDepthRenderbuffer = setupDepthRenderbuffer; + this.setupFrameBufferTexture = setupFrameBufferTexture; + this.useMultisampledRTT = useMultisampledRTT; - results.nodeName = results.nodeName.substring( 0, lastDot ); - results.objectName = objectName; +} - } +function WebGLUtils( gl, extensions ) { - } + function convert( p, colorSpace = NoColorSpace ) { - if ( results.propertyName === null || results.propertyName.length === 0 ) { + let extension; - throw new Error( 'PropertyBinding: can not parse propertyName from trackName: ' + trackName ); + const transfer = ColorManagement.getTransfer( colorSpace ); - } + if ( p === UnsignedByteType ) return gl.UNSIGNED_BYTE; + if ( p === UnsignedShort4444Type ) return gl.UNSIGNED_SHORT_4_4_4_4; + if ( p === UnsignedShort5551Type ) return gl.UNSIGNED_SHORT_5_5_5_1; + if ( p === UnsignedInt5999Type ) return gl.UNSIGNED_INT_5_9_9_9_REV; - return results; + if ( p === ByteType ) return gl.BYTE; + if ( p === ShortType ) return gl.SHORT; + if ( p === UnsignedShortType ) return gl.UNSIGNED_SHORT; + if ( p === IntType ) return gl.INT; + if ( p === UnsignedIntType ) return gl.UNSIGNED_INT; + if ( p === FloatType ) return gl.FLOAT; + if ( p === HalfFloatType ) return gl.HALF_FLOAT; - } + if ( p === AlphaFormat ) return gl.ALPHA; + if ( p === RGBFormat ) return gl.RGB; + if ( p === RGBAFormat ) return gl.RGBA; + if ( p === DepthFormat ) return gl.DEPTH_COMPONENT; + if ( p === DepthStencilFormat ) return gl.DEPTH_STENCIL; - static findNode( root, nodeName ) { + // WebGL2 formats. - if ( nodeName === undefined || nodeName === '' || nodeName === '.' || nodeName === - 1 || nodeName === root.name || nodeName === root.uuid ) { + if ( p === RedFormat ) return gl.RED; + if ( p === RedIntegerFormat ) return gl.RED_INTEGER; + if ( p === RGFormat ) return gl.RG; + if ( p === RGIntegerFormat ) return gl.RG_INTEGER; + if ( p === RGBAIntegerFormat ) return gl.RGBA_INTEGER; - return root; + // S3TC - } + if ( p === RGB_S3TC_DXT1_Format || p === RGBA_S3TC_DXT1_Format || p === RGBA_S3TC_DXT3_Format || p === RGBA_S3TC_DXT5_Format ) { - // search into skeleton bones. - if ( root.skeleton ) { + if ( transfer === SRGBTransfer ) { - const bone = root.skeleton.getBoneByName( nodeName ); + extension = extensions.get( 'WEBGL_compressed_texture_s3tc_srgb' ); - if ( bone !== undefined ) { + if ( extension !== null ) { - return bone; + if ( p === RGB_S3TC_DXT1_Format ) return extension.COMPRESSED_SRGB_S3TC_DXT1_EXT; + if ( p === RGBA_S3TC_DXT1_Format ) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT; + if ( p === RGBA_S3TC_DXT3_Format ) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT; + if ( p === RGBA_S3TC_DXT5_Format ) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT; - } + } else { - } + return null; - // search into node subtree. - if ( root.children ) { + } - const searchNodeSubtree = function ( children ) { + } else { - for ( let i = 0; i < children.length; i ++ ) { + extension = extensions.get( 'WEBGL_compressed_texture_s3tc' ); - const childNode = children[ i ]; + if ( extension !== null ) { - if ( childNode.name === nodeName || childNode.uuid === nodeName ) { + if ( p === RGB_S3TC_DXT1_Format ) return extension.COMPRESSED_RGB_S3TC_DXT1_EXT; + if ( p === RGBA_S3TC_DXT1_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT1_EXT; + if ( p === RGBA_S3TC_DXT3_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT3_EXT; + if ( p === RGBA_S3TC_DXT5_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT5_EXT; - return childNode; + } else { - } + return null; - const result = searchNodeSubtree( childNode.children ); + } - if ( result ) return result; + } - } + } - return null; + // PVRTC - }; + if ( p === RGB_PVRTC_4BPPV1_Format || p === RGB_PVRTC_2BPPV1_Format || p === RGBA_PVRTC_4BPPV1_Format || p === RGBA_PVRTC_2BPPV1_Format ) { - const subTreeNode = searchNodeSubtree( root.children ); + extension = extensions.get( 'WEBGL_compressed_texture_pvrtc' ); - if ( subTreeNode ) { + if ( extension !== null ) { - return subTreeNode; + if ( p === RGB_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_4BPPV1_IMG; + if ( p === RGB_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_2BPPV1_IMG; + if ( p === RGBA_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; + if ( p === RGBA_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG; + + } else { + + return null; } } - return null; + // ETC - } + if ( p === RGB_ETC1_Format || p === RGB_ETC2_Format || p === RGBA_ETC2_EAC_Format ) { - // these are used to "bind" a nonexistent property - _getValue_unavailable() {} - _setValue_unavailable() {} + extension = extensions.get( 'WEBGL_compressed_texture_etc' ); - // Getters + if ( extension !== null ) { - _getValue_direct( buffer, offset ) { + if ( p === RGB_ETC1_Format || p === RGB_ETC2_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ETC2 : extension.COMPRESSED_RGB8_ETC2; + if ( p === RGBA_ETC2_EAC_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ETC2_EAC : extension.COMPRESSED_RGBA8_ETC2_EAC; - buffer[ offset ] = this.targetObject[ this.propertyName ]; + } else { - } + return null; - _getValue_array( buffer, offset ) { + } - const source = this.resolvedProperty; + } - for ( let i = 0, n = source.length; i !== n; ++ i ) { + // ASTC - buffer[ offset ++ ] = source[ i ]; + if ( p === RGBA_ASTC_4x4_Format || p === RGBA_ASTC_5x4_Format || p === RGBA_ASTC_5x5_Format || + p === RGBA_ASTC_6x5_Format || p === RGBA_ASTC_6x6_Format || p === RGBA_ASTC_8x5_Format || + p === RGBA_ASTC_8x6_Format || p === RGBA_ASTC_8x8_Format || p === RGBA_ASTC_10x5_Format || + p === RGBA_ASTC_10x6_Format || p === RGBA_ASTC_10x8_Format || p === RGBA_ASTC_10x10_Format || + p === RGBA_ASTC_12x10_Format || p === RGBA_ASTC_12x12_Format ) { - } + extension = extensions.get( 'WEBGL_compressed_texture_astc' ); - } + if ( extension !== null ) { - _getValue_arrayElement( buffer, offset ) { + if ( p === RGBA_ASTC_4x4_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR : extension.COMPRESSED_RGBA_ASTC_4x4_KHR; + if ( p === RGBA_ASTC_5x4_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR : extension.COMPRESSED_RGBA_ASTC_5x4_KHR; + if ( p === RGBA_ASTC_5x5_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR : extension.COMPRESSED_RGBA_ASTC_5x5_KHR; + if ( p === RGBA_ASTC_6x5_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR : extension.COMPRESSED_RGBA_ASTC_6x5_KHR; + if ( p === RGBA_ASTC_6x6_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR : extension.COMPRESSED_RGBA_ASTC_6x6_KHR; + if ( p === RGBA_ASTC_8x5_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR : extension.COMPRESSED_RGBA_ASTC_8x5_KHR; + if ( p === RGBA_ASTC_8x6_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR : extension.COMPRESSED_RGBA_ASTC_8x6_KHR; + if ( p === RGBA_ASTC_8x8_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR : extension.COMPRESSED_RGBA_ASTC_8x8_KHR; + if ( p === RGBA_ASTC_10x5_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR : extension.COMPRESSED_RGBA_ASTC_10x5_KHR; + if ( p === RGBA_ASTC_10x6_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR : extension.COMPRESSED_RGBA_ASTC_10x6_KHR; + if ( p === RGBA_ASTC_10x8_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR : extension.COMPRESSED_RGBA_ASTC_10x8_KHR; + if ( p === RGBA_ASTC_10x10_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR : extension.COMPRESSED_RGBA_ASTC_10x10_KHR; + if ( p === RGBA_ASTC_12x10_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR : extension.COMPRESSED_RGBA_ASTC_12x10_KHR; + if ( p === RGBA_ASTC_12x12_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR : extension.COMPRESSED_RGBA_ASTC_12x12_KHR; - buffer[ offset ] = this.resolvedProperty[ this.propertyIndex ]; + } else { - } + return null; - _getValue_toArray( buffer, offset ) { + } - this.resolvedProperty.toArray( buffer, offset ); + } - } + // BPTC - // Direct + if ( p === RGBA_BPTC_Format || p === RGB_BPTC_SIGNED_Format || p === RGB_BPTC_UNSIGNED_Format ) { - _setValue_direct( buffer, offset ) { + extension = extensions.get( 'EXT_texture_compression_bptc' ); - this.targetObject[ this.propertyName ] = buffer[ offset ]; + if ( extension !== null ) { - } + if ( p === RGBA_BPTC_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT : extension.COMPRESSED_RGBA_BPTC_UNORM_EXT; + if ( p === RGB_BPTC_SIGNED_Format ) return extension.COMPRESSED_RGB_BPTC_SIGNED_FLOAT_EXT; + if ( p === RGB_BPTC_UNSIGNED_Format ) return extension.COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_EXT; - _setValue_direct_setNeedsUpdate( buffer, offset ) { + } else { - this.targetObject[ this.propertyName ] = buffer[ offset ]; - this.targetObject.needsUpdate = true; + return null; - } + } - _setValue_direct_setMatrixWorldNeedsUpdate( buffer, offset ) { + } - this.targetObject[ this.propertyName ] = buffer[ offset ]; - this.targetObject.matrixWorldNeedsUpdate = true; + // RGTC - } + if ( p === RED_RGTC1_Format || p === SIGNED_RED_RGTC1_Format || p === RED_GREEN_RGTC2_Format || p === SIGNED_RED_GREEN_RGTC2_Format ) { - // EntireArray + extension = extensions.get( 'EXT_texture_compression_rgtc' ); - _setValue_array( buffer, offset ) { + if ( extension !== null ) { - const dest = this.resolvedProperty; + if ( p === RGBA_BPTC_Format ) return extension.COMPRESSED_RED_RGTC1_EXT; + if ( p === SIGNED_RED_RGTC1_Format ) return extension.COMPRESSED_SIGNED_RED_RGTC1_EXT; + if ( p === RED_GREEN_RGTC2_Format ) return extension.COMPRESSED_RED_GREEN_RGTC2_EXT; + if ( p === SIGNED_RED_GREEN_RGTC2_Format ) return extension.COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT; - for ( let i = 0, n = dest.length; i !== n; ++ i ) { + } else { - dest[ i ] = buffer[ offset ++ ]; + return null; + + } } - } + // - _setValue_array_setNeedsUpdate( buffer, offset ) { + if ( p === UnsignedInt248Type ) return gl.UNSIGNED_INT_24_8; - const dest = this.resolvedProperty; + // if "p" can't be resolved, assume the user defines a WebGL constant as a string (fallback/workaround for packed RGB formats) - for ( let i = 0, n = dest.length; i !== n; ++ i ) { + return ( gl[ p ] !== undefined ) ? gl[ p ] : null; - dest[ i ] = buffer[ offset ++ ]; + } - } + return { convert: convert }; - this.targetObject.needsUpdate = true; +} - } +const _occlusion_vertex = ` +void main() { - _setValue_array_setMatrixWorldNeedsUpdate( buffer, offset ) { + gl_Position = vec4( position, 1.0 ); - const dest = this.resolvedProperty; +}`; - for ( let i = 0, n = dest.length; i !== n; ++ i ) { +const _occlusion_fragment = ` +uniform sampler2DArray depthColor; +uniform float depthWidth; +uniform float depthHeight; - dest[ i ] = buffer[ offset ++ ]; +void main() { - } + vec2 coord = vec2( gl_FragCoord.x / depthWidth, gl_FragCoord.y / depthHeight ); - this.targetObject.matrixWorldNeedsUpdate = true; + if ( coord.x >= 1.0 ) { + + gl_FragDepth = texture( depthColor, vec3( coord.x - 1.0, coord.y, 1 ) ).r; + + } else { + + gl_FragDepth = texture( depthColor, vec3( coord.x, coord.y, 0 ) ).r; } - // ArrayElement +}`; - _setValue_arrayElement( buffer, offset ) { +/** + * A XR module that manages the access to the Depth Sensing API. + */ +class WebXRDepthSensing { - this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ]; + /** + * Constructs a new depth sensing module. + */ + constructor() { - } + /** + * A texture representing the depth of the user's environment. + * + * @type {?Texture} + */ + this.texture = null; - _setValue_arrayElement_setNeedsUpdate( buffer, offset ) { + /** + * A plane mesh for visualizing the depth texture. + * + * @type {?Mesh} + */ + this.mesh = null; - this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ]; - this.targetObject.needsUpdate = true; + /** + * The depth near value. + * + * @type {number} + */ + this.depthNear = 0; + + /** + * The depth near far. + * + * @type {number} + */ + this.depthFar = 0; } - _setValue_arrayElement_setMatrixWorldNeedsUpdate( buffer, offset ) { + /** + * Inits the depth sensing module + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {XRWebGLDepthInformation} depthData - The XR depth data. + * @param {XRRenderState} renderState - The XR render state. + */ + init( renderer, depthData, renderState ) { - this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ]; - this.targetObject.matrixWorldNeedsUpdate = true; + if ( this.texture === null ) { - } + const texture = new Texture(); - // HasToFromArray + const texProps = renderer.properties.get( texture ); + texProps.__webglTexture = depthData.texture; - _setValue_fromArray( buffer, offset ) { + if ( ( depthData.depthNear !== renderState.depthNear ) || ( depthData.depthFar !== renderState.depthFar ) ) { - this.resolvedProperty.fromArray( buffer, offset ); + this.depthNear = depthData.depthNear; + this.depthFar = depthData.depthFar; - } + } - _setValue_fromArray_setNeedsUpdate( buffer, offset ) { + this.texture = texture; - this.resolvedProperty.fromArray( buffer, offset ); - this.targetObject.needsUpdate = true; + } } - _setValue_fromArray_setMatrixWorldNeedsUpdate( buffer, offset ) { + /** + * Returns a plane mesh that visualizes the depth texture. + * + * @param {ArrayCamera} cameraXR - The XR camera. + * @return {?Mesh} The plane mesh. + */ + getMesh( cameraXR ) { - this.resolvedProperty.fromArray( buffer, offset ); - this.targetObject.matrixWorldNeedsUpdate = true; + if ( this.texture !== null ) { - } + if ( this.mesh === null ) { - _getValue_unbound( targetArray, offset ) { + const viewport = cameraXR.cameras[ 0 ].viewport; + const material = new ShaderMaterial( { + vertexShader: _occlusion_vertex, + fragmentShader: _occlusion_fragment, + uniforms: { + depthColor: { value: this.texture }, + depthWidth: { value: viewport.z }, + depthHeight: { value: viewport.w } + } + } ); - this.bind(); - this.getValue( targetArray, offset ); + this.mesh = new Mesh( new PlaneGeometry( 20, 20 ), material ); - } + } - _setValue_unbound( sourceArray, offset ) { + } - this.bind(); - this.setValue( sourceArray, offset ); + return this.mesh; } - // create getter / setter pair for a property in the scene graph - bind() { + /** + * Resets the module + */ + reset() { - let targetObject = this.node; - const parsedPath = this.parsedPath; + this.texture = null; + this.mesh = null; - const objectName = parsedPath.objectName; - const propertyName = parsedPath.propertyName; - let propertyIndex = parsedPath.propertyIndex; + } - if ( ! targetObject ) { + /** + * Returns a texture representing the depth of the user's environment. + * + * @return {?Texture} The depth texture. + */ + getDepthTexture() { - targetObject = PropertyBinding.findNode( this.rootNode, parsedPath.nodeName ); + return this.texture; - this.node = targetObject; + } - } +} - // set fail state so we can just 'return' on error - this.getValue = this._getValue_unavailable; - this.setValue = this._setValue_unavailable; +/** + * This class represents an abstraction of the WebXR Device API and is + * internally used by {@link WebGLRenderer}. `WebXRManager` also provides a public + * interface that allows users to enable/disable XR and perform XR related + * tasks like for instance retrieving controllers. + * + * @augments EventDispatcher + * @hideconstructor + */ +class WebXRManager extends EventDispatcher { - // ensure there is a value node - if ( ! targetObject ) { + /** + * Constructs a new WebGL renderer. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGL2RenderingContext} gl - The rendering context. + */ + constructor( renderer, gl ) { - console.error( 'THREE.PropertyBinding: Trying to update node for track: ' + this.path + ' but it wasn\'t found.' ); - return; + super(); - } + const scope = this; - if ( objectName ) { + let session = null; - let objectIndex = parsedPath.objectIndex; + let framebufferScaleFactor = 1.0; - // special cases were we need to reach deeper into the hierarchy to get the face materials.... - switch ( objectName ) { + let referenceSpace = null; + let referenceSpaceType = 'local-floor'; + // Set default foveation to maximum. + let foveation = 1.0; + let customReferenceSpace = null; - case 'materials': + let pose = null; + let glBinding = null; + let glProjLayer = null; + let glBaseLayer = null; + let xrFrame = null; - if ( ! targetObject.material ) { + const depthSensing = new WebXRDepthSensing(); + const attributes = gl.getContextAttributes(); - console.error( 'THREE.PropertyBinding: Can not bind to material as node does not have a material.', this ); - return; + let initialRenderTarget = null; + let newRenderTarget = null; - } + const controllers = []; + const controllerInputSources = []; - if ( ! targetObject.material.materials ) { + const currentSize = new Vector2(); + let currentPixelRatio = null; - console.error( 'THREE.PropertyBinding: Can not bind to material.materials as node.material does not have a materials array.', this ); - return; + // - } + const cameraL = new PerspectiveCamera(); + cameraL.viewport = new Vector4(); - targetObject = targetObject.material.materials; + const cameraR = new PerspectiveCamera(); + cameraR.viewport = new Vector4(); - break; + const cameras = [ cameraL, cameraR ]; - case 'bones': + const cameraXR = new ArrayCamera(); - if ( ! targetObject.skeleton ) { + let _currentDepthNear = null; + let _currentDepthFar = null; - console.error( 'THREE.PropertyBinding: Can not bind to bones as node does not have a skeleton.', this ); - return; + // - } + /** + * Whether the manager's XR camera should be automatically updated or not. + * + * @type {boolean} + * @default true + */ + this.cameraAutoUpdate = true; - // potential future optimization: skip this if propertyIndex is already an integer - // and convert the integer string to a true integer. + /** + * This flag notifies the renderer to be ready for XR rendering. Set it to `true` + * if you are going to use XR in your app. + * + * @type {boolean} + * @default false + */ + this.enabled = false; - targetObject = targetObject.skeleton.bones; + /** + * Whether XR presentation is active or not. + * + * @type {boolean} + * @readonly + * @default false + */ + this.isPresenting = false; - // support resolving morphTarget names into indices. - for ( let i = 0; i < targetObject.length; i ++ ) { + /** + * Returns a group representing the `target ray` space of the XR controller. + * Use this space for visualizing 3D objects that support the user in pointing + * tasks like UI interaction. + * + * @param {number} index - The index of the controller. + * @return {Group} A group representing the `target ray` space. + */ + this.getController = function ( index ) { - if ( targetObject[ i ].name === objectIndex ) { + let controller = controllers[ index ]; - objectIndex = i; - break; + if ( controller === undefined ) { - } + controller = new WebXRController(); + controllers[ index ] = controller; - } + } - break; + return controller.getTargetRaySpace(); - case 'map': + }; - if ( 'map' in targetObject ) { + /** + * Returns a group representing the `grip` space of the XR controller. + * Use this space for visualizing 3D objects that support the user in pointing + * tasks like UI interaction. + * + * Note: If you want to show something in the user's hand AND offer a + * pointing ray at the same time, you'll want to attached the handheld object + * to the group returned by `getControllerGrip()` and the ray to the + * group returned by `getController()`. The idea is to have two + * different groups in two different coordinate spaces for the same WebXR + * controller. + * + * @param {number} index - The index of the controller. + * @return {Group} A group representing the `grip` space. + */ + this.getControllerGrip = function ( index ) { - targetObject = targetObject.map; - break; + let controller = controllers[ index ]; - } + if ( controller === undefined ) { - if ( ! targetObject.material ) { + controller = new WebXRController(); + controllers[ index ] = controller; - console.error( 'THREE.PropertyBinding: Can not bind to material as node does not have a material.', this ); - return; + } - } + return controller.getGripSpace(); - if ( ! targetObject.material.map ) { + }; - console.error( 'THREE.PropertyBinding: Can not bind to material.map as node.material does not have a map.', this ); - return; + /** + * Returns a group representing the `hand` space of the XR controller. + * Use this space for visualizing 3D objects that support the user in pointing + * tasks like UI interaction. + * + * @param {number} index - The index of the controller. + * @return {Group} A group representing the `hand` space. + */ + this.getHand = function ( index ) { - } + let controller = controllers[ index ]; - targetObject = targetObject.material.map; - break; + if ( controller === undefined ) { - default: + controller = new WebXRController(); + controllers[ index ] = controller; - if ( targetObject[ objectName ] === undefined ) { + } - console.error( 'THREE.PropertyBinding: Can not bind to objectName of node undefined.', this ); - return; + return controller.getHandSpace(); - } + }; - targetObject = targetObject[ objectName ]; + // - } + function onSessionEvent( event ) { + const controllerIndex = controllerInputSources.indexOf( event.inputSource ); - if ( objectIndex !== undefined ) { + if ( controllerIndex === -1 ) { - if ( targetObject[ objectIndex ] === undefined ) { + return; - console.error( 'THREE.PropertyBinding: Trying to bind to objectIndex of objectName, but is undefined.', this, targetObject ); - return; + } - } + const controller = controllers[ controllerIndex ]; - targetObject = targetObject[ objectIndex ]; + if ( controller !== undefined ) { + + controller.update( event.inputSource, event.frame, customReferenceSpace || referenceSpace ); + controller.dispatchEvent( { type: event.type, data: event.inputSource } ); } } - // resolve property - const nodeProperty = targetObject[ propertyName ]; + function onSessionEnd() { - if ( nodeProperty === undefined ) { + session.removeEventListener( 'select', onSessionEvent ); + session.removeEventListener( 'selectstart', onSessionEvent ); + session.removeEventListener( 'selectend', onSessionEvent ); + session.removeEventListener( 'squeeze', onSessionEvent ); + session.removeEventListener( 'squeezestart', onSessionEvent ); + session.removeEventListener( 'squeezeend', onSessionEvent ); + session.removeEventListener( 'end', onSessionEnd ); + session.removeEventListener( 'inputsourceschange', onInputSourcesChange ); - const nodeName = parsedPath.nodeName; + for ( let i = 0; i < controllers.length; i ++ ) { - console.error( 'THREE.PropertyBinding: Trying to update property for track: ' + nodeName + - '.' + propertyName + ' but it wasn\'t found.', targetObject ); - return; + const inputSource = controllerInputSources[ i ]; - } + if ( inputSource === null ) continue; - // determine versioning scheme - let versioning = this.Versioning.None; + controllerInputSources[ i ] = null; - this.targetObject = targetObject; + controllers[ i ].disconnect( inputSource ); - if ( targetObject.needsUpdate !== undefined ) { // material + } - versioning = this.Versioning.NeedsUpdate; + _currentDepthNear = null; + _currentDepthFar = null; - } else if ( targetObject.matrixWorldNeedsUpdate !== undefined ) { // node transform + depthSensing.reset(); - versioning = this.Versioning.MatrixWorldNeedsUpdate; + // restore framebuffer/rendering state - } + renderer.setRenderTarget( initialRenderTarget ); - // determine how the property gets bound - let bindingType = this.BindingType.Direct; + glBaseLayer = null; + glProjLayer = null; + glBinding = null; + session = null; + newRenderTarget = null; - if ( propertyIndex !== undefined ) { + // - // access a sub element of the property array (only primitives are supported right now) + animation.stop(); - if ( propertyName === 'morphTargetInfluences' ) { + scope.isPresenting = false; - // potential optimization, skip this if propertyIndex is already an integer, and convert the integer string to a true integer. + renderer.setPixelRatio( currentPixelRatio ); + renderer.setSize( currentSize.width, currentSize.height, false ); - // support resolving morphTarget names into indices. - if ( ! targetObject.geometry ) { + scope.dispatchEvent( { type: 'sessionend' } ); - console.error( 'THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.', this ); - return; + } - } + /** + * Sets the framebuffer scale factor. + * + * This method can not be used during a XR session. + * + * @param {number} value - The framebuffer scale factor. + */ + this.setFramebufferScaleFactor = function ( value ) { - if ( ! targetObject.geometry.morphAttributes ) { + framebufferScaleFactor = value; - console.error( 'THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.morphAttributes.', this ); - return; + if ( scope.isPresenting === true ) { - } + console.warn( 'THREE.WebXRManager: Cannot change framebuffer scale while presenting.' ); - if ( targetObject.morphTargetDictionary[ propertyIndex ] !== undefined ) { + } - propertyIndex = targetObject.morphTargetDictionary[ propertyIndex ]; + }; - } + /** + * Sets the reference space type. Can be used to configure a spatial relationship with the user's physical + * environment. Depending on how the user moves in 3D space, setting an appropriate reference space can + * improve tracking. Default is `local-floor`. Valid values can be found here + * https://developer.mozilla.org/en-US/docs/Web/API/XRReferenceSpace#reference_space_types. + * + * This method can not be used during a XR session. + * + * @param {string} value - The reference space type. + */ + this.setReferenceSpaceType = function ( value ) { - } + referenceSpaceType = value; - bindingType = this.BindingType.ArrayElement; + if ( scope.isPresenting === true ) { - this.resolvedProperty = nodeProperty; - this.propertyIndex = propertyIndex; + console.warn( 'THREE.WebXRManager: Cannot change reference space type while presenting.' ); - } else if ( nodeProperty.fromArray !== undefined && nodeProperty.toArray !== undefined ) { + } - // must use copy for Object3D.Euler/Quaternion + }; - bindingType = this.BindingType.HasFromToArray; + /** + * Returns the XR reference space. + * + * @return {XRReferenceSpace} The XR reference space. + */ + this.getReferenceSpace = function () { - this.resolvedProperty = nodeProperty; + return customReferenceSpace || referenceSpace; - } else if ( Array.isArray( nodeProperty ) ) { + }; - bindingType = this.BindingType.EntireArray; + /** + * Sets a custom XR reference space. + * + * @param {XRReferenceSpace} space - The XR reference space. + */ + this.setReferenceSpace = function ( space ) { - this.resolvedProperty = nodeProperty; + customReferenceSpace = space; - } else { + }; - this.propertyName = propertyName; + /** + * Returns the current base layer. + * + * @return {?(XRWebGLLayer|XRProjectionLayer)} The XR base layer. + */ + this.getBaseLayer = function () { - } + return glProjLayer !== null ? glProjLayer : glBaseLayer; - // select getter / setter - this.getValue = this.GetterByBindingType[ bindingType ]; - this.setValue = this.SetterByBindingTypeAndVersioning[ bindingType ][ versioning ]; + }; - } + /** + * Returns the current XR binding. + * + * @return {?XRWebGLBinding} The XR binding. + */ + this.getBinding = function () { - unbind() { + return glBinding; - this.node = null; + }; - // back to the prototype version of getValue / setValue - // note: avoiding to mutate the shape of 'this' via 'delete' - this.getValue = this._getValue_unbound; - this.setValue = this._setValue_unbound; + /** + * Returns the current XR frame. + * + * @return {?XRFrame} The XR frame. Returns `null` when used outside a XR session. + */ + this.getFrame = function () { - } + return xrFrame; -} + }; -PropertyBinding.Composite = Composite; + /** + * Returns the current XR session. + * + * @return {?XRSession} The XR session. Returns `null` when used outside a XR session. + */ + this.getSession = function () { -PropertyBinding.prototype.BindingType = { - Direct: 0, - EntireArray: 1, - ArrayElement: 2, - HasFromToArray: 3 -}; + return session; -PropertyBinding.prototype.Versioning = { - None: 0, - NeedsUpdate: 1, - MatrixWorldNeedsUpdate: 2 -}; + }; -PropertyBinding.prototype.GetterByBindingType = [ + /** + * After a XR session has been requested usually with one of the `*Button` modules, it + * is injected into the renderer with this method. This method triggers the start of + * the actual XR rendering. + * + * @async + * @param {XRSession} value - The XR session to set. + * @return {Promise} A Promise that resolves when the session has been set. + */ + this.setSession = async function ( value ) { - PropertyBinding.prototype._getValue_direct, - PropertyBinding.prototype._getValue_array, - PropertyBinding.prototype._getValue_arrayElement, - PropertyBinding.prototype._getValue_toArray, + session = value; -]; + if ( session !== null ) { -PropertyBinding.prototype.SetterByBindingTypeAndVersioning = [ + initialRenderTarget = renderer.getRenderTarget(); - [ - // Direct - PropertyBinding.prototype._setValue_direct, - PropertyBinding.prototype._setValue_direct_setNeedsUpdate, - PropertyBinding.prototype._setValue_direct_setMatrixWorldNeedsUpdate, + session.addEventListener( 'select', onSessionEvent ); + session.addEventListener( 'selectstart', onSessionEvent ); + session.addEventListener( 'selectend', onSessionEvent ); + session.addEventListener( 'squeeze', onSessionEvent ); + session.addEventListener( 'squeezestart', onSessionEvent ); + session.addEventListener( 'squeezeend', onSessionEvent ); + session.addEventListener( 'end', onSessionEnd ); + session.addEventListener( 'inputsourceschange', onInputSourcesChange ); - ], [ + if ( attributes.xrCompatible !== true ) { - // EntireArray + await gl.makeXRCompatible(); - PropertyBinding.prototype._setValue_array, - PropertyBinding.prototype._setValue_array_setNeedsUpdate, - PropertyBinding.prototype._setValue_array_setMatrixWorldNeedsUpdate, + } - ], [ + currentPixelRatio = renderer.getPixelRatio(); + renderer.getSize( currentSize ); - // ArrayElement - PropertyBinding.prototype._setValue_arrayElement, - PropertyBinding.prototype._setValue_arrayElement_setNeedsUpdate, - PropertyBinding.prototype._setValue_arrayElement_setMatrixWorldNeedsUpdate, + // Check that the browser implements the necessary APIs to use an + // XRProjectionLayer rather than an XRWebGLLayer + const useLayers = typeof XRWebGLBinding !== 'undefined' && 'createProjectionLayer' in XRWebGLBinding.prototype; - ], [ + if ( ! useLayers ) { - // HasToFromArray - PropertyBinding.prototype._setValue_fromArray, - PropertyBinding.prototype._setValue_fromArray_setNeedsUpdate, - PropertyBinding.prototype._setValue_fromArray_setMatrixWorldNeedsUpdate, + const layerInit = { + antialias: attributes.antialias, + alpha: true, + depth: attributes.depth, + stencil: attributes.stencil, + framebufferScaleFactor: framebufferScaleFactor + }; - ] + glBaseLayer = new XRWebGLLayer( session, gl, layerInit ); -]; + session.updateRenderState( { baseLayer: glBaseLayer } ); -/** - * - * A group of objects that receives a shared animation state. - * - * Usage: - * - * - Add objects you would otherwise pass as 'root' to the - * constructor or the .clipAction method of AnimationMixer. - * - * - Instead pass this object as 'root'. - * - * - You can also add and remove objects later when the mixer - * is running. - * - * Note: - * - * Objects of this class appear as one object to the mixer, - * so cache control of the individual objects must be done - * on the group. - * - * Limitation: - * - * - The animated properties must be compatible among the - * all objects in the group. - * - * - A single property can either be controlled through a - * target group or directly, but not both. - */ + renderer.setPixelRatio( 1 ); + renderer.setSize( glBaseLayer.framebufferWidth, glBaseLayer.framebufferHeight, false ); -class AnimationObjectGroup { + newRenderTarget = new WebGLRenderTarget( + glBaseLayer.framebufferWidth, + glBaseLayer.framebufferHeight, + { + format: RGBAFormat, + type: UnsignedByteType, + colorSpace: renderer.outputColorSpace, + stencilBuffer: attributes.stencil, + resolveDepthBuffer: ( glBaseLayer.ignoreDepthValues === false ), + resolveStencilBuffer: ( glBaseLayer.ignoreDepthValues === false ) - constructor() { + } + ); - this.isAnimationObjectGroup = true; + } else { - this.uuid = generateUUID(); + let depthFormat = null; + let depthType = null; + let glDepthFormat = null; - // cached objects followed by the active ones - this._objects = Array.prototype.slice.call( arguments ); + if ( attributes.depth ) { - this.nCachedObjects_ = 0; // threshold - // note: read by PropertyBinding.Composite + glDepthFormat = attributes.stencil ? gl.DEPTH24_STENCIL8 : gl.DEPTH_COMPONENT24; + depthFormat = attributes.stencil ? DepthStencilFormat : DepthFormat; + depthType = attributes.stencil ? UnsignedInt248Type : UnsignedIntType; - const indices = {}; - this._indicesByUUID = indices; // for bookkeeping + } - for ( let i = 0, n = arguments.length; i !== n; ++ i ) { + const projectionlayerInit = { + colorFormat: gl.RGBA8, + depthFormat: glDepthFormat, + scaleFactor: framebufferScaleFactor + }; - indices[ arguments[ i ].uuid ] = i; + glBinding = new XRWebGLBinding( session, gl ); - } + glProjLayer = glBinding.createProjectionLayer( projectionlayerInit ); - this._paths = []; // inside: string - this._parsedPaths = []; // inside: { we don't care, here } - this._bindings = []; // inside: Array< PropertyBinding > - this._bindingsIndicesByPath = {}; // inside: indices in these arrays + session.updateRenderState( { layers: [ glProjLayer ] } ); - const scope = this; + renderer.setPixelRatio( 1 ); + renderer.setSize( glProjLayer.textureWidth, glProjLayer.textureHeight, false ); - this.stats = { + newRenderTarget = new WebGLRenderTarget( + glProjLayer.textureWidth, + glProjLayer.textureHeight, + { + format: RGBAFormat, + type: UnsignedByteType, + depthTexture: new DepthTexture( glProjLayer.textureWidth, glProjLayer.textureHeight, depthType, undefined, undefined, undefined, undefined, undefined, undefined, depthFormat ), + stencilBuffer: attributes.stencil, + colorSpace: renderer.outputColorSpace, + samples: attributes.antialias ? 4 : 0, + resolveDepthBuffer: ( glProjLayer.ignoreDepthValues === false ), + resolveStencilBuffer: ( glProjLayer.ignoreDepthValues === false ) + } ); - objects: { - get total() { + } - return scope._objects.length; + newRenderTarget.isXRRenderTarget = true; // TODO Remove this when possible, see #23278 - }, - get inUse() { + this.setFoveation( foveation ); - return this.total - scope.nCachedObjects_; + customReferenceSpace = null; + referenceSpace = await session.requestReferenceSpace( referenceSpaceType ); - } - }, - get bindingsPerObject() { + animation.setContext( session ); + animation.start(); - return scope._bindings.length; + scope.isPresenting = true; + + scope.dispatchEvent( { type: 'sessionstart' } ); } }; - } + /** + * Returns the environment blend mode from the current XR session. + * + * @return {'opaque'|'additive'|'alpha-blend'|undefined} The environment blend mode. Returns `undefined` when used outside of a XR session. + */ + this.getEnvironmentBlendMode = function () { - add() { + if ( session !== null ) { - const objects = this._objects, - indicesByUUID = this._indicesByUUID, - paths = this._paths, - parsedPaths = this._parsedPaths, - bindings = this._bindings, - nBindings = bindings.length; + return session.environmentBlendMode; - let knownObject = undefined, - nObjects = objects.length, - nCachedObjects = this.nCachedObjects_; + } - for ( let i = 0, n = arguments.length; i !== n; ++ i ) { + }; - const object = arguments[ i ], - uuid = object.uuid; - let index = indicesByUUID[ uuid ]; + /** + * Returns the current depth texture computed via depth sensing. + * + * @return {?Texture} The depth texture. + */ + this.getDepthTexture = function () { - if ( index === undefined ) { + return depthSensing.getDepthTexture(); - // unknown object -> add it to the ACTIVE region + }; - index = nObjects ++; - indicesByUUID[ uuid ] = index; - objects.push( object ); + function onInputSourcesChange( event ) { - // accounting is done, now do the same for all bindings + // Notify disconnected - for ( let j = 0, m = nBindings; j !== m; ++ j ) { + for ( let i = 0; i < event.removed.length; i ++ ) { - bindings[ j ].push( new PropertyBinding( object, paths[ j ], parsedPaths[ j ] ) ); + const inputSource = event.removed[ i ]; + const index = controllerInputSources.indexOf( inputSource ); - } + if ( index >= 0 ) { - } else if ( index < nCachedObjects ) { + controllerInputSources[ index ] = null; + controllers[ index ].disconnect( inputSource ); - knownObject = objects[ index ]; + } - // move existing object to the ACTIVE region + } - const firstActiveIndex = -- nCachedObjects, - lastCachedObject = objects[ firstActiveIndex ]; + // Notify connected - indicesByUUID[ lastCachedObject.uuid ] = index; - objects[ index ] = lastCachedObject; + for ( let i = 0; i < event.added.length; i ++ ) { - indicesByUUID[ uuid ] = firstActiveIndex; - objects[ firstActiveIndex ] = object; + const inputSource = event.added[ i ]; - // accounting is done, now do the same for all bindings + let controllerIndex = controllerInputSources.indexOf( inputSource ); - for ( let j = 0, m = nBindings; j !== m; ++ j ) { + if ( controllerIndex === -1 ) { - const bindingsForPath = bindings[ j ], - lastCached = bindingsForPath[ firstActiveIndex ]; + // Assign input source a controller that currently has no input source - let binding = bindingsForPath[ index ]; + for ( let i = 0; i < controllers.length; i ++ ) { - bindingsForPath[ index ] = lastCached; + if ( i >= controllerInputSources.length ) { - if ( binding === undefined ) { + controllerInputSources.push( inputSource ); + controllerIndex = i; + break; - // since we do not bother to create new bindings - // for objects that are cached, the binding may - // or may not exist + } else if ( controllerInputSources[ i ] === null ) { - binding = new PropertyBinding( object, paths[ j ], parsedPaths[ j ] ); + controllerInputSources[ i ] = inputSource; + controllerIndex = i; + break; + + } } - bindingsForPath[ firstActiveIndex ] = binding; + // If all controllers do currently receive input we ignore new ones + + if ( controllerIndex === -1 ) break; } - } else if ( objects[ index ] !== knownObject ) { + const controller = controllers[ controllerIndex ]; - console.error( 'THREE.AnimationObjectGroup: Different objects with the same UUID ' + - 'detected. Clean the caches or recreate your infrastructure when reloading scenes.' ); + if ( controller ) { - } // else the object is already where we want it to be + controller.connect( inputSource ); - } // for arguments + } - this.nCachedObjects_ = nCachedObjects; + } - } + } - remove() { + // - const objects = this._objects, - indicesByUUID = this._indicesByUUID, - bindings = this._bindings, - nBindings = bindings.length; + const cameraLPos = new Vector3(); + const cameraRPos = new Vector3(); - let nCachedObjects = this.nCachedObjects_; + /** + * Assumes 2 cameras that are parallel and share an X-axis, and that + * the cameras' projection and world matrices have already been set. + * And that near and far planes are identical for both cameras. + * Visualization of this technique: https://computergraphics.stackexchange.com/a/4765 + * + * @param {ArrayCamera} camera - The camera to update. + * @param {PerspectiveCamera} cameraL - The left camera. + * @param {PerspectiveCamera} cameraR - The right camera. + */ + function setProjectionFromUnion( camera, cameraL, cameraR ) { - for ( let i = 0, n = arguments.length; i !== n; ++ i ) { + cameraLPos.setFromMatrixPosition( cameraL.matrixWorld ); + cameraRPos.setFromMatrixPosition( cameraR.matrixWorld ); - const object = arguments[ i ], - uuid = object.uuid, - index = indicesByUUID[ uuid ]; + const ipd = cameraLPos.distanceTo( cameraRPos ); - if ( index !== undefined && index >= nCachedObjects ) { + const projL = cameraL.projectionMatrix.elements; + const projR = cameraR.projectionMatrix.elements; - // move existing object into the CACHED region + // VR systems will have identical far and near planes, and + // most likely identical top and bottom frustum extents. + // Use the left camera for these values. + const near = projL[ 14 ] / ( projL[ 10 ] - 1 ); + const far = projL[ 14 ] / ( projL[ 10 ] + 1 ); + const topFov = ( projL[ 9 ] + 1 ) / projL[ 5 ]; + const bottomFov = ( projL[ 9 ] - 1 ) / projL[ 5 ]; - const lastCachedIndex = nCachedObjects ++, - firstActiveObject = objects[ lastCachedIndex ]; + const leftFov = ( projL[ 8 ] - 1 ) / projL[ 0 ]; + const rightFov = ( projR[ 8 ] + 1 ) / projR[ 0 ]; + const left = near * leftFov; + const right = near * rightFov; - indicesByUUID[ firstActiveObject.uuid ] = index; - objects[ index ] = firstActiveObject; + // Calculate the new camera's position offset from the + // left camera. xOffset should be roughly half `ipd`. + const zOffset = ipd / ( - leftFov + rightFov ); + const xOffset = zOffset * - leftFov; - indicesByUUID[ uuid ] = lastCachedIndex; - objects[ lastCachedIndex ] = object; + // TODO: Better way to apply this offset? + cameraL.matrixWorld.decompose( camera.position, camera.quaternion, camera.scale ); + camera.translateX( xOffset ); + camera.translateZ( zOffset ); + camera.matrixWorld.compose( camera.position, camera.quaternion, camera.scale ); + camera.matrixWorldInverse.copy( camera.matrixWorld ).invert(); - // accounting is done, now do the same for all bindings + // Check if the projection uses an infinite far plane. + if ( projL[ 10 ] === -1 ) { - for ( let j = 0, m = nBindings; j !== m; ++ j ) { + // Use the projection matrix from the left eye. + // The camera offset is sufficient to include the view volumes + // of both eyes (assuming symmetric projections). + camera.projectionMatrix.copy( cameraL.projectionMatrix ); + camera.projectionMatrixInverse.copy( cameraL.projectionMatrixInverse ); - const bindingsForPath = bindings[ j ], - firstActive = bindingsForPath[ lastCachedIndex ], - binding = bindingsForPath[ index ]; + } else { - bindingsForPath[ index ] = firstActive; - bindingsForPath[ lastCachedIndex ] = binding; + // Find the union of the frustum values of the cameras and scale + // the values so that the near plane's position does not change in world space, + // although must now be relative to the new union camera. + const near2 = near + zOffset; + const far2 = far + zOffset; + const left2 = left - xOffset; + const right2 = right + ( ipd - xOffset ); + const top2 = topFov * far / far2 * near2; + const bottom2 = bottomFov * far / far2 * near2; - } + camera.projectionMatrix.makePerspective( left2, right2, top2, bottom2, near2, far2 ); + camera.projectionMatrixInverse.copy( camera.projectionMatrix ).invert(); } - } // for arguments - - this.nCachedObjects_ = nCachedObjects; + } - } + function updateCamera( camera, parent ) { - // remove & forget - uncache() { + if ( parent === null ) { - const objects = this._objects, - indicesByUUID = this._indicesByUUID, - bindings = this._bindings, - nBindings = bindings.length; + camera.matrixWorld.copy( camera.matrix ); - let nCachedObjects = this.nCachedObjects_, - nObjects = objects.length; + } else { - for ( let i = 0, n = arguments.length; i !== n; ++ i ) { + camera.matrixWorld.multiplyMatrices( parent.matrixWorld, camera.matrix ); - const object = arguments[ i ], - uuid = object.uuid, - index = indicesByUUID[ uuid ]; + } - if ( index !== undefined ) { + camera.matrixWorldInverse.copy( camera.matrixWorld ).invert(); - delete indicesByUUID[ uuid ]; + } - if ( index < nCachedObjects ) { + /** + * Updates the state of the XR camera. Use this method on app level if you + * set cameraAutoUpdate` to `false`. The method requires the non-XR + * camera of the scene as a parameter. The passed in camera's transformation + * is automatically adjusted to the position of the XR camera when calling + * this method. + * + * @param {Camera} camera - The camera. + */ + this.updateCamera = function ( camera ) { - // object is cached, shrink the CACHED region + if ( session === null ) return; - const firstActiveIndex = -- nCachedObjects, - lastCachedObject = objects[ firstActiveIndex ], - lastIndex = -- nObjects, - lastObject = objects[ lastIndex ]; + let depthNear = camera.near; + let depthFar = camera.far; - // last cached object takes this object's place - indicesByUUID[ lastCachedObject.uuid ] = index; - objects[ index ] = lastCachedObject; + if ( depthSensing.texture !== null ) { - // last object goes to the activated slot and pop - indicesByUUID[ lastObject.uuid ] = firstActiveIndex; - objects[ firstActiveIndex ] = lastObject; - objects.pop(); + if ( depthSensing.depthNear > 0 ) depthNear = depthSensing.depthNear; + if ( depthSensing.depthFar > 0 ) depthFar = depthSensing.depthFar; - // accounting is done, now do the same for all bindings + } - for ( let j = 0, m = nBindings; j !== m; ++ j ) { + cameraXR.near = cameraR.near = cameraL.near = depthNear; + cameraXR.far = cameraR.far = cameraL.far = depthFar; - const bindingsForPath = bindings[ j ], - lastCached = bindingsForPath[ firstActiveIndex ], - last = bindingsForPath[ lastIndex ]; + if ( _currentDepthNear !== cameraXR.near || _currentDepthFar !== cameraXR.far ) { - bindingsForPath[ index ] = lastCached; - bindingsForPath[ firstActiveIndex ] = last; - bindingsForPath.pop(); + // Note that the new renderState won't apply until the next frame. See #18320 - } + session.updateRenderState( { + depthNear: cameraXR.near, + depthFar: cameraXR.far + } ); - } else { + _currentDepthNear = cameraXR.near; + _currentDepthFar = cameraXR.far; - // object is active, just swap with the last and pop + } - const lastIndex = -- nObjects, - lastObject = objects[ lastIndex ]; + cameraL.layers.mask = camera.layers.mask | 0b010; + cameraR.layers.mask = camera.layers.mask | 0b100; + cameraXR.layers.mask = cameraL.layers.mask | cameraR.layers.mask; - if ( lastIndex > 0 ) { + const parent = camera.parent; + const cameras = cameraXR.cameras; - indicesByUUID[ lastObject.uuid ] = index; + updateCamera( cameraXR, parent ); - } + for ( let i = 0; i < cameras.length; i ++ ) { - objects[ index ] = lastObject; - objects.pop(); + updateCamera( cameras[ i ], parent ); - // accounting is done, now do the same for all bindings + } - for ( let j = 0, m = nBindings; j !== m; ++ j ) { + // update projection matrix for proper view frustum culling - const bindingsForPath = bindings[ j ]; + if ( cameras.length === 2 ) { - bindingsForPath[ index ] = bindingsForPath[ lastIndex ]; - bindingsForPath.pop(); + setProjectionFromUnion( cameraXR, cameraL, cameraR ); - } + } else { - } // cached or active + // assume single camera setup (AR) - } // if object is known + cameraXR.projectionMatrix.copy( cameraL.projectionMatrix ); - } // for arguments + } - this.nCachedObjects_ = nCachedObjects; + // update user camera and its children - } + updateUserCamera( camera, cameraXR, parent ); - // Internal interface used by befriended PropertyBinding.Composite: + }; - subscribe_( path, parsedPath ) { + function updateUserCamera( camera, cameraXR, parent ) { - // returns an array of bindings for the given path that is changed - // according to the contained objects in the group + if ( parent === null ) { - const indicesByPath = this._bindingsIndicesByPath; - let index = indicesByPath[ path ]; - const bindings = this._bindings; + camera.matrix.copy( cameraXR.matrixWorld ); - if ( index !== undefined ) return bindings[ index ]; + } else { - const paths = this._paths, - parsedPaths = this._parsedPaths, - objects = this._objects, - nObjects = objects.length, - nCachedObjects = this.nCachedObjects_, - bindingsForPath = new Array( nObjects ); + camera.matrix.copy( parent.matrixWorld ); + camera.matrix.invert(); + camera.matrix.multiply( cameraXR.matrixWorld ); - index = bindings.length; + } - indicesByPath[ path ] = index; + camera.matrix.decompose( camera.position, camera.quaternion, camera.scale ); + camera.updateMatrixWorld( true ); - paths.push( path ); - parsedPaths.push( parsedPath ); - bindings.push( bindingsForPath ); + camera.projectionMatrix.copy( cameraXR.projectionMatrix ); + camera.projectionMatrixInverse.copy( cameraXR.projectionMatrixInverse ); - for ( let i = nCachedObjects, n = objects.length; i !== n; ++ i ) { + if ( camera.isPerspectiveCamera ) { - const object = objects[ i ]; - bindingsForPath[ i ] = new PropertyBinding( object, path, parsedPath ); + camera.fov = RAD2DEG * 2 * Math.atan( 1 / camera.projectionMatrix.elements[ 5 ] ); + camera.zoom = 1; + + } } - return bindingsForPath; + /** + * Returns an instance of {@link ArrayCamera} which represents the XR camera + * of the active XR session. For each view it holds a separate camera object. + * + * The camera's `fov` is currently not used and does not reflect the fov of + * the XR camera. If you need the fov on app level, you have to compute in + * manually from the XR camera's projection matrices. + * + * @return {ArrayCamera} The XR camera. + */ + this.getCamera = function () { - } + return cameraXR; - unsubscribe_( path ) { + }; - // tells the group to forget about a property path and no longer - // update the array previously obtained with 'subscribe_' + /** + * Returns the amount of foveation used by the XR compositor for the projection layer. + * + * @return {number} The amount of foveation. + */ + this.getFoveation = function () { - const indicesByPath = this._bindingsIndicesByPath, - index = indicesByPath[ path ]; + if ( glProjLayer === null && glBaseLayer === null ) { - if ( index !== undefined ) { + return undefined; - const paths = this._paths, - parsedPaths = this._parsedPaths, - bindings = this._bindings, - lastBindingsIndex = bindings.length - 1, - lastBindings = bindings[ lastBindingsIndex ], - lastBindingsPath = path[ lastBindingsIndex ]; + } - indicesByPath[ lastBindingsPath ] = index; + return foveation; - bindings[ index ] = lastBindings; - bindings.pop(); + }; - parsedPaths[ index ] = parsedPaths[ lastBindingsIndex ]; - parsedPaths.pop(); + /** + * Sets the foveation value. + * + * @param {number} value - A number in the range `[0,1]` where `0` means no foveation (full resolution) + * and `1` means maximum foveation (the edges render at lower resolution). + */ + this.setFoveation = function ( value ) { - paths[ index ] = paths[ lastBindingsIndex ]; - paths.pop(); + // 0 = no foveation = full resolution + // 1 = maximum foveation = the edges render at lower resolution - } + foveation = value; - } + if ( glProjLayer !== null ) { -} + glProjLayer.fixedFoveation = value; -class AnimationAction { + } - constructor( mixer, clip, localRoot = null, blendMode = clip.blendMode ) { + if ( glBaseLayer !== null && glBaseLayer.fixedFoveation !== undefined ) { - this._mixer = mixer; - this._clip = clip; - this._localRoot = localRoot; - this.blendMode = blendMode; + glBaseLayer.fixedFoveation = value; - const tracks = clip.tracks, - nTracks = tracks.length, - interpolants = new Array( nTracks ); + } - const interpolantSettings = { - endingStart: ZeroCurvatureEnding, - endingEnd: ZeroCurvatureEnding }; - for ( let i = 0; i !== nTracks; ++ i ) { + /** + * Returns `true` if depth sensing is supported. + * + * @return {boolean} Whether depth sensing is supported or not. + */ + this.hasDepthSensing = function () { - const interpolant = tracks[ i ].createInterpolant( null ); - interpolants[ i ] = interpolant; - interpolant.settings = interpolantSettings; + return depthSensing.texture !== null; - } + }; - this._interpolantSettings = interpolantSettings; + /** + * Returns the depth sensing mesh. + * + * @return {Mesh} The depth sensing mesh. + */ + this.getDepthSensingMesh = function () { - this._interpolants = interpolants; // bound by the mixer + return depthSensing.getMesh( cameraXR ); - // inside: PropertyMixer (managed by the mixer) - this._propertyBindings = new Array( nTracks ); + }; - this._cacheIndex = null; // for the memory manager - this._byClipCacheIndex = null; // for the memory manager + // Animation Loop - this._timeScaleInterpolant = null; - this._weightInterpolant = null; + let onAnimationFrameCallback = null; - this.loop = LoopRepeat; - this._loopCount = - 1; + function onAnimationFrame( time, frame ) { - // global mixer time when the action is to be started - // it's set back to 'null' upon start of the action - this._startTime = null; + pose = frame.getViewerPose( customReferenceSpace || referenceSpace ); + xrFrame = frame; - // scaled local time of the action - // gets clamped or wrapped to 0..clip.duration according to loop - this.time = 0; + if ( pose !== null ) { - this.timeScale = 1; - this._effectiveTimeScale = 1; + const views = pose.views; - this.weight = 1; - this._effectiveWeight = 1; + if ( glBaseLayer !== null ) { - this.repetitions = Infinity; // no. of repetitions when looping + renderer.setRenderTargetFramebuffer( newRenderTarget, glBaseLayer.framebuffer ); + renderer.setRenderTarget( newRenderTarget ); - this.paused = false; // true -> zero effective time scale - this.enabled = true; // false -> zero effective weight + } - this.clampWhenFinished = false;// keep feeding the last frame? + let cameraXRNeedsUpdate = false; - this.zeroSlopeAtStart = true;// for smooth interpolation w/o separate - this.zeroSlopeAtEnd = true;// clips for start, loop and end + // check if it's necessary to rebuild cameraXR's camera list - } + if ( views.length !== cameraXR.cameras.length ) { - // State & Scheduling + cameraXR.cameras.length = 0; + cameraXRNeedsUpdate = true; - play() { + } - this._mixer._activateAction( this ); + for ( let i = 0; i < views.length; i ++ ) { - return this; + const view = views[ i ]; - } + let viewport = null; - stop() { + if ( glBaseLayer !== null ) { - this._mixer._deactivateAction( this ); + viewport = glBaseLayer.getViewport( view ); - return this.reset(); + } else { - } + const glSubImage = glBinding.getViewSubImage( glProjLayer, view ); + viewport = glSubImage.viewport; - reset() { + // For side-by-side projection, we only produce a single texture for both eyes. + if ( i === 0 ) { - this.paused = false; - this.enabled = true; + renderer.setRenderTargetTextures( + newRenderTarget, + glSubImage.colorTexture, + glSubImage.depthStencilTexture ); - this.time = 0; // restart clip - this._loopCount = - 1;// forget previous loops - this._startTime = null;// forget scheduling + renderer.setRenderTarget( newRenderTarget ); - return this.stopFading().stopWarping(); + } - } + } - isRunning() { + let camera = cameras[ i ]; - return this.enabled && ! this.paused && this.timeScale !== 0 && - this._startTime === null && this._mixer._isActiveAction( this ); + if ( camera === undefined ) { - } + camera = new PerspectiveCamera(); + camera.layers.enable( i ); + camera.viewport = new Vector4(); + cameras[ i ] = camera; - // return true when play has been called - isScheduled() { + } - return this._mixer._isActiveAction( this ); + camera.matrix.fromArray( view.transform.matrix ); + camera.matrix.decompose( camera.position, camera.quaternion, camera.scale ); + camera.projectionMatrix.fromArray( view.projectionMatrix ); + camera.projectionMatrixInverse.copy( camera.projectionMatrix ).invert(); + camera.viewport.set( viewport.x, viewport.y, viewport.width, viewport.height ); - } + if ( i === 0 ) { - startAt( time ) { + cameraXR.matrix.copy( camera.matrix ); + cameraXR.matrix.decompose( cameraXR.position, cameraXR.quaternion, cameraXR.scale ); - this._startTime = time; + } - return this; + if ( cameraXRNeedsUpdate === true ) { - } + cameraXR.cameras.push( camera ); - setLoop( mode, repetitions ) { + } - this.loop = mode; - this.repetitions = repetitions; + } - return this; + // - } + const enabledFeatures = session.enabledFeatures; + const gpuDepthSensingEnabled = enabledFeatures && + enabledFeatures.includes( 'depth-sensing' ) && + session.depthUsage == 'gpu-optimized'; - // Weight + if ( gpuDepthSensingEnabled && glBinding ) { - // set the weight stopping any scheduled fading - // although .enabled = false yields an effective weight of zero, this - // method does *not* change .enabled, because it would be confusing - setEffectiveWeight( weight ) { + const depthData = glBinding.getDepthInformation( views[ 0 ] ); - this.weight = weight; + if ( depthData && depthData.isValid && depthData.texture ) { - // note: same logic as when updated at runtime - this._effectiveWeight = this.enabled ? weight : 0; + depthSensing.init( renderer, depthData, session.renderState ); - return this.stopFading(); + } - } + } - // return the weight considering fading and .enabled - getEffectiveWeight() { + } - return this._effectiveWeight; + // - } + for ( let i = 0; i < controllers.length; i ++ ) { - fadeIn( duration ) { + const inputSource = controllerInputSources[ i ]; + const controller = controllers[ i ]; - return this._scheduleFading( duration, 0, 1 ); + if ( inputSource !== null && controller !== undefined ) { - } + controller.update( inputSource, frame, customReferenceSpace || referenceSpace ); - fadeOut( duration ) { + } - return this._scheduleFading( duration, 1, 0 ); + } - } + if ( onAnimationFrameCallback ) onAnimationFrameCallback( time, frame ); - crossFadeFrom( fadeOutAction, duration, warp ) { + if ( frame.detectedPlanes ) { - fadeOutAction.fadeOut( duration ); - this.fadeIn( duration ); + scope.dispatchEvent( { type: 'planesdetected', data: frame } ); - if ( warp ) { + } - const fadeInDuration = this._clip.duration, - fadeOutDuration = fadeOutAction._clip.duration, + xrFrame = null; - startEndRatio = fadeOutDuration / fadeInDuration, - endStartRatio = fadeInDuration / fadeOutDuration; + } - fadeOutAction.warp( 1.0, startEndRatio, duration ); - this.warp( endStartRatio, 1.0, duration ); + const animation = new WebGLAnimation(); - } + animation.setAnimationLoop( onAnimationFrame ); - return this; + this.setAnimationLoop = function ( callback ) { - } + onAnimationFrameCallback = callback; - crossFadeTo( fadeInAction, duration, warp ) { + }; - return fadeInAction.crossFadeFrom( this, duration, warp ); + this.dispose = function () {}; } - stopFading() { +} - const weightInterpolant = this._weightInterpolant; +const _e1 = /*@__PURE__*/ new Euler(); +const _m1 = /*@__PURE__*/ new Matrix4(); - if ( weightInterpolant !== null ) { +function WebGLMaterials( renderer, properties ) { - this._weightInterpolant = null; - this._mixer._takeBackControlInterpolant( weightInterpolant ); + function refreshTransformUniform( map, uniform ) { + + if ( map.matrixAutoUpdate === true ) { + + map.updateMatrix(); } - return this; + uniform.value.copy( map.matrix ); } - // Time Scale Control + function refreshFogUniforms( uniforms, fog ) { - // set the time scale stopping any scheduled warping - // although .paused = true yields an effective time scale of zero, this - // method does *not* change .paused, because it would be confusing - setEffectiveTimeScale( timeScale ) { + fog.color.getRGB( uniforms.fogColor.value, getUnlitUniformColorSpace( renderer ) ); - this.timeScale = timeScale; - this._effectiveTimeScale = this.paused ? 0 : timeScale; + if ( fog.isFog ) { - return this.stopWarping(); + uniforms.fogNear.value = fog.near; + uniforms.fogFar.value = fog.far; - } + } else if ( fog.isFogExp2 ) { - // return the time scale considering warping and .paused - getEffectiveTimeScale() { + uniforms.fogDensity.value = fog.density; - return this._effectiveTimeScale; + } } - setDuration( duration ) { + function refreshMaterialUniforms( uniforms, material, pixelRatio, height, transmissionRenderTarget ) { - this.timeScale = this._clip.duration / duration; + if ( material.isMeshBasicMaterial ) { - return this.stopWarping(); + refreshUniformsCommon( uniforms, material ); - } + } else if ( material.isMeshLambertMaterial ) { - syncWith( action ) { + refreshUniformsCommon( uniforms, material ); - this.time = action.time; - this.timeScale = action.timeScale; + } else if ( material.isMeshToonMaterial ) { - return this.stopWarping(); + refreshUniformsCommon( uniforms, material ); + refreshUniformsToon( uniforms, material ); - } + } else if ( material.isMeshPhongMaterial ) { - halt( duration ) { + refreshUniformsCommon( uniforms, material ); + refreshUniformsPhong( uniforms, material ); - return this.warp( this._effectiveTimeScale, 0, duration ); + } else if ( material.isMeshStandardMaterial ) { - } + refreshUniformsCommon( uniforms, material ); + refreshUniformsStandard( uniforms, material ); - warp( startTimeScale, endTimeScale, duration ) { + if ( material.isMeshPhysicalMaterial ) { - const mixer = this._mixer, - now = mixer.time, - timeScale = this.timeScale; + refreshUniformsPhysical( uniforms, material, transmissionRenderTarget ); - let interpolant = this._timeScaleInterpolant; + } - if ( interpolant === null ) { + } else if ( material.isMeshMatcapMaterial ) { - interpolant = mixer._lendControlInterpolant(); - this._timeScaleInterpolant = interpolant; + refreshUniformsCommon( uniforms, material ); + refreshUniformsMatcap( uniforms, material ); - } + } else if ( material.isMeshDepthMaterial ) { - const times = interpolant.parameterPositions, - values = interpolant.sampleValues; + refreshUniformsCommon( uniforms, material ); - times[ 0 ] = now; - times[ 1 ] = now + duration; + } else if ( material.isMeshDistanceMaterial ) { - values[ 0 ] = startTimeScale / timeScale; - values[ 1 ] = endTimeScale / timeScale; + refreshUniformsCommon( uniforms, material ); + refreshUniformsDistance( uniforms, material ); - return this; + } else if ( material.isMeshNormalMaterial ) { - } + refreshUniformsCommon( uniforms, material ); - stopWarping() { + } else if ( material.isLineBasicMaterial ) { - const timeScaleInterpolant = this._timeScaleInterpolant; + refreshUniformsLine( uniforms, material ); - if ( timeScaleInterpolant !== null ) { + if ( material.isLineDashedMaterial ) { - this._timeScaleInterpolant = null; - this._mixer._takeBackControlInterpolant( timeScaleInterpolant ); + refreshUniformsDash( uniforms, material ); - } + } - return this; + } else if ( material.isPointsMaterial ) { - } + refreshUniformsPoints( uniforms, material, pixelRatio, height ); - // Object Accessors + } else if ( material.isSpriteMaterial ) { - getMixer() { + refreshUniformsSprites( uniforms, material ); - return this._mixer; + } else if ( material.isShadowMaterial ) { - } + uniforms.color.value.copy( material.color ); + uniforms.opacity.value = material.opacity; - getClip() { + } else if ( material.isShaderMaterial ) { - return this._clip; + material.uniformsNeedUpdate = false; // #15581 + + } } - getRoot() { + function refreshUniformsCommon( uniforms, material ) { - return this._localRoot || this._mixer._root; + uniforms.opacity.value = material.opacity; - } + if ( material.color ) { - // Interna + uniforms.diffuse.value.copy( material.color ); - _update( time, deltaTime, timeDirection, accuIndex ) { + } - // called by the mixer + if ( material.emissive ) { - if ( ! this.enabled ) { + uniforms.emissive.value.copy( material.emissive ).multiplyScalar( material.emissiveIntensity ); - // call ._updateWeight() to update ._effectiveWeight + } - this._updateWeight( time ); - return; + if ( material.map ) { + + uniforms.map.value = material.map; + + refreshTransformUniform( material.map, uniforms.mapTransform ); } - const startTime = this._startTime; + if ( material.alphaMap ) { - if ( startTime !== null ) { + uniforms.alphaMap.value = material.alphaMap; - // check for scheduled start of action + refreshTransformUniform( material.alphaMap, uniforms.alphaMapTransform ); - const timeRunning = ( time - startTime ) * timeDirection; - if ( timeRunning < 0 || timeDirection === 0 ) { + } - deltaTime = 0; + if ( material.bumpMap ) { - } else { + uniforms.bumpMap.value = material.bumpMap; + refreshTransformUniform( material.bumpMap, uniforms.bumpMapTransform ); - this._startTime = null; // unschedule - deltaTime = timeDirection * timeRunning; + uniforms.bumpScale.value = material.bumpScale; + + if ( material.side === BackSide ) { + + uniforms.bumpScale.value *= -1; } } - // apply time scale and advance time + if ( material.normalMap ) { - deltaTime *= this._updateTimeScale( time ); - const clipTime = this._updateTime( deltaTime ); + uniforms.normalMap.value = material.normalMap; - // note: _updateTime may disable the action resulting in - // an effective weight of 0 + refreshTransformUniform( material.normalMap, uniforms.normalMapTransform ); - const weight = this._updateWeight( time ); + uniforms.normalScale.value.copy( material.normalScale ); - if ( weight > 0 ) { + if ( material.side === BackSide ) { - const interpolants = this._interpolants; - const propertyMixers = this._propertyBindings; + uniforms.normalScale.value.negate(); - switch ( this.blendMode ) { + } - case AdditiveAnimationBlendMode: + } + + if ( material.displacementMap ) { + + uniforms.displacementMap.value = material.displacementMap; + + refreshTransformUniform( material.displacementMap, uniforms.displacementMapTransform ); + + uniforms.displacementScale.value = material.displacementScale; + uniforms.displacementBias.value = material.displacementBias; + + } + + if ( material.emissiveMap ) { - for ( let j = 0, m = interpolants.length; j !== m; ++ j ) { + uniforms.emissiveMap.value = material.emissiveMap; - interpolants[ j ].evaluate( clipTime ); - propertyMixers[ j ].accumulateAdditive( weight ); + refreshTransformUniform( material.emissiveMap, uniforms.emissiveMapTransform ); - } + } - break; + if ( material.specularMap ) { - case NormalAnimationBlendMode: - default: + uniforms.specularMap.value = material.specularMap; - for ( let j = 0, m = interpolants.length; j !== m; ++ j ) { + refreshTransformUniform( material.specularMap, uniforms.specularMapTransform ); - interpolants[ j ].evaluate( clipTime ); - propertyMixers[ j ].accumulate( accuIndex, weight ); + } - } + if ( material.alphaTest > 0 ) { - } + uniforms.alphaTest.value = material.alphaTest; } - } + const materialProperties = properties.get( material ); - _updateWeight( time ) { + const envMap = materialProperties.envMap; + const envMapRotation = materialProperties.envMapRotation; - let weight = 0; + if ( envMap ) { - if ( this.enabled ) { + uniforms.envMap.value = envMap; - weight = this.weight; - const interpolant = this._weightInterpolant; + _e1.copy( envMapRotation ); - if ( interpolant !== null ) { + // accommodate left-handed frame + _e1.x *= -1; _e1.y *= -1; _e1.z *= -1; - const interpolantValue = interpolant.evaluate( time )[ 0 ]; + if ( envMap.isCubeTexture && envMap.isRenderTargetTexture === false ) { - weight *= interpolantValue; + // environment maps which are not cube render targets or PMREMs follow a different convention + _e1.y *= -1; + _e1.z *= -1; - if ( time > interpolant.parameterPositions[ 1 ] ) { + } - this.stopFading(); + uniforms.envMapRotation.value.setFromMatrix4( _m1.makeRotationFromEuler( _e1 ) ); - if ( interpolantValue === 0 ) { + uniforms.flipEnvMap.value = ( envMap.isCubeTexture && envMap.isRenderTargetTexture === false ) ? -1 : 1; - // faded out, disable - this.enabled = false; + uniforms.reflectivity.value = material.reflectivity; + uniforms.ior.value = material.ior; + uniforms.refractionRatio.value = material.refractionRatio; - } + } - } + if ( material.lightMap ) { - } + uniforms.lightMap.value = material.lightMap; + uniforms.lightMapIntensity.value = material.lightMapIntensity; + + refreshTransformUniform( material.lightMap, uniforms.lightMapTransform ); } - this._effectiveWeight = weight; - return weight; + if ( material.aoMap ) { + + uniforms.aoMap.value = material.aoMap; + uniforms.aoMapIntensity.value = material.aoMapIntensity; + + refreshTransformUniform( material.aoMap, uniforms.aoMapTransform ); + + } } - _updateTimeScale( time ) { + function refreshUniformsLine( uniforms, material ) { - let timeScale = 0; + uniforms.diffuse.value.copy( material.color ); + uniforms.opacity.value = material.opacity; - if ( ! this.paused ) { + if ( material.map ) { - timeScale = this.timeScale; + uniforms.map.value = material.map; - const interpolant = this._timeScaleInterpolant; + refreshTransformUniform( material.map, uniforms.mapTransform ); - if ( interpolant !== null ) { + } - const interpolantValue = interpolant.evaluate( time )[ 0 ]; + } - timeScale *= interpolantValue; + function refreshUniformsDash( uniforms, material ) { - if ( time > interpolant.parameterPositions[ 1 ] ) { + uniforms.dashSize.value = material.dashSize; + uniforms.totalSize.value = material.dashSize + material.gapSize; + uniforms.scale.value = material.scale; - this.stopWarping(); + } - if ( timeScale === 0 ) { + function refreshUniformsPoints( uniforms, material, pixelRatio, height ) { - // motion has halted, pause - this.paused = true; + uniforms.diffuse.value.copy( material.color ); + uniforms.opacity.value = material.opacity; + uniforms.size.value = material.size * pixelRatio; + uniforms.scale.value = height * 0.5; - } else { + if ( material.map ) { - // warp done - apply final time scale - this.timeScale = timeScale; + uniforms.map.value = material.map; - } + refreshTransformUniform( material.map, uniforms.uvTransform ); - } + } - } + if ( material.alphaMap ) { + + uniforms.alphaMap.value = material.alphaMap; + + refreshTransformUniform( material.alphaMap, uniforms.alphaMapTransform ); } - this._effectiveTimeScale = timeScale; - return timeScale; + if ( material.alphaTest > 0 ) { - } + uniforms.alphaTest.value = material.alphaTest; - _updateTime( deltaTime ) { + } - const duration = this._clip.duration; - const loop = this.loop; + } - let time = this.time + deltaTime; - let loopCount = this._loopCount; + function refreshUniformsSprites( uniforms, material ) { - const pingPong = ( loop === LoopPingPong ); + uniforms.diffuse.value.copy( material.color ); + uniforms.opacity.value = material.opacity; + uniforms.rotation.value = material.rotation; - if ( deltaTime === 0 ) { + if ( material.map ) { - if ( loopCount === - 1 ) return time; + uniforms.map.value = material.map; - return ( pingPong && ( loopCount & 1 ) === 1 ) ? duration - time : time; + refreshTransformUniform( material.map, uniforms.mapTransform ); } - if ( loop === LoopOnce ) { + if ( material.alphaMap ) { - if ( loopCount === - 1 ) { + uniforms.alphaMap.value = material.alphaMap; - // just started + refreshTransformUniform( material.alphaMap, uniforms.alphaMapTransform ); - this._loopCount = 0; - this._setEndings( true, true, false ); + } - } + if ( material.alphaTest > 0 ) { - handle_stop: { + uniforms.alphaTest.value = material.alphaTest; - if ( time >= duration ) { + } - time = duration; + } - } else if ( time < 0 ) { + function refreshUniformsPhong( uniforms, material ) { - time = 0; + uniforms.specular.value.copy( material.specular ); + uniforms.shininess.value = Math.max( material.shininess, 1e-4 ); // to prevent pow( 0.0, 0.0 ) - } else { + } - this.time = time; + function refreshUniformsToon( uniforms, material ) { - break handle_stop; + if ( material.gradientMap ) { - } + uniforms.gradientMap.value = material.gradientMap; - if ( this.clampWhenFinished ) this.paused = true; - else this.enabled = false; + } - this.time = time; + } - this._mixer.dispatchEvent( { - type: 'finished', action: this, - direction: deltaTime < 0 ? - 1 : 1 - } ); + function refreshUniformsStandard( uniforms, material ) { - } + uniforms.metalness.value = material.metalness; - } else { // repetitive Repeat or PingPong + if ( material.metalnessMap ) { - if ( loopCount === - 1 ) { + uniforms.metalnessMap.value = material.metalnessMap; - // just started + refreshTransformUniform( material.metalnessMap, uniforms.metalnessMapTransform ); - if ( deltaTime >= 0 ) { + } - loopCount = 0; + uniforms.roughness.value = material.roughness; - this._setEndings( true, this.repetitions === 0, pingPong ); + if ( material.roughnessMap ) { - } else { + uniforms.roughnessMap.value = material.roughnessMap; - // when looping in reverse direction, the initial - // transition through zero counts as a repetition, - // so leave loopCount at -1 + refreshTransformUniform( material.roughnessMap, uniforms.roughnessMapTransform ); - this._setEndings( this.repetitions === 0, true, pingPong ); + } - } + if ( material.envMap ) { - } + //uniforms.envMap.value = material.envMap; // part of uniforms common - if ( time >= duration || time < 0 ) { + uniforms.envMapIntensity.value = material.envMapIntensity; - // wrap around + } - const loopDelta = Math.floor( time / duration ); // signed - time -= duration * loopDelta; + } - loopCount += Math.abs( loopDelta ); + function refreshUniformsPhysical( uniforms, material, transmissionRenderTarget ) { - const pending = this.repetitions - loopCount; + uniforms.ior.value = material.ior; // also part of uniforms common - if ( pending <= 0 ) { + if ( material.sheen > 0 ) { - // have to stop (switch state, clamp time, fire event) + uniforms.sheenColor.value.copy( material.sheenColor ).multiplyScalar( material.sheen ); - if ( this.clampWhenFinished ) this.paused = true; - else this.enabled = false; + uniforms.sheenRoughness.value = material.sheenRoughness; - time = deltaTime > 0 ? duration : 0; + if ( material.sheenColorMap ) { - this.time = time; + uniforms.sheenColorMap.value = material.sheenColorMap; - this._mixer.dispatchEvent( { - type: 'finished', action: this, - direction: deltaTime > 0 ? 1 : - 1 - } ); + refreshTransformUniform( material.sheenColorMap, uniforms.sheenColorMapTransform ); - } else { + } - // keep running + if ( material.sheenRoughnessMap ) { - if ( pending === 1 ) { + uniforms.sheenRoughnessMap.value = material.sheenRoughnessMap; - // entering the last round + refreshTransformUniform( material.sheenRoughnessMap, uniforms.sheenRoughnessMapTransform ); - const atStart = deltaTime < 0; - this._setEndings( atStart, ! atStart, pingPong ); + } - } else { + } - this._setEndings( false, false, pingPong ); + if ( material.clearcoat > 0 ) { - } + uniforms.clearcoat.value = material.clearcoat; + uniforms.clearcoatRoughness.value = material.clearcoatRoughness; - this._loopCount = loopCount; + if ( material.clearcoatMap ) { - this.time = time; + uniforms.clearcoatMap.value = material.clearcoatMap; - this._mixer.dispatchEvent( { - type: 'loop', action: this, loopDelta: loopDelta - } ); + refreshTransformUniform( material.clearcoatMap, uniforms.clearcoatMapTransform ); - } + } - } else { + if ( material.clearcoatRoughnessMap ) { - this.time = time; + uniforms.clearcoatRoughnessMap.value = material.clearcoatRoughnessMap; + + refreshTransformUniform( material.clearcoatRoughnessMap, uniforms.clearcoatRoughnessMapTransform ); } - if ( pingPong && ( loopCount & 1 ) === 1 ) { + if ( material.clearcoatNormalMap ) { - // invert time for the "pong round" + uniforms.clearcoatNormalMap.value = material.clearcoatNormalMap; - return duration - time; + refreshTransformUniform( material.clearcoatNormalMap, uniforms.clearcoatNormalMapTransform ); - } + uniforms.clearcoatNormalScale.value.copy( material.clearcoatNormalScale ); - } + if ( material.side === BackSide ) { - return time; + uniforms.clearcoatNormalScale.value.negate(); - } + } - _setEndings( atStart, atEnd, pingPong ) { + } - const settings = this._interpolantSettings; + } - if ( pingPong ) { + if ( material.dispersion > 0 ) { - settings.endingStart = ZeroSlopeEnding; - settings.endingEnd = ZeroSlopeEnding; + uniforms.dispersion.value = material.dispersion; - } else { + } - // assuming for LoopOnce atStart == atEnd == true + if ( material.iridescence > 0 ) { - if ( atStart ) { + uniforms.iridescence.value = material.iridescence; + uniforms.iridescenceIOR.value = material.iridescenceIOR; + uniforms.iridescenceThicknessMinimum.value = material.iridescenceThicknessRange[ 0 ]; + uniforms.iridescenceThicknessMaximum.value = material.iridescenceThicknessRange[ 1 ]; - settings.endingStart = this.zeroSlopeAtStart ? ZeroSlopeEnding : ZeroCurvatureEnding; + if ( material.iridescenceMap ) { - } else { + uniforms.iridescenceMap.value = material.iridescenceMap; - settings.endingStart = WrapAroundEnding; + refreshTransformUniform( material.iridescenceMap, uniforms.iridescenceMapTransform ); } - if ( atEnd ) { - - settings.endingEnd = this.zeroSlopeAtEnd ? ZeroSlopeEnding : ZeroCurvatureEnding; + if ( material.iridescenceThicknessMap ) { - } else { + uniforms.iridescenceThicknessMap.value = material.iridescenceThicknessMap; - settings.endingEnd = WrapAroundEnding; + refreshTransformUniform( material.iridescenceThicknessMap, uniforms.iridescenceThicknessMapTransform ); } } - } + if ( material.transmission > 0 ) { - _scheduleFading( duration, weightNow, weightThen ) { + uniforms.transmission.value = material.transmission; + uniforms.transmissionSamplerMap.value = transmissionRenderTarget.texture; + uniforms.transmissionSamplerSize.value.set( transmissionRenderTarget.width, transmissionRenderTarget.height ); - const mixer = this._mixer, now = mixer.time; - let interpolant = this._weightInterpolant; + if ( material.transmissionMap ) { - if ( interpolant === null ) { + uniforms.transmissionMap.value = material.transmissionMap; - interpolant = mixer._lendControlInterpolant(); - this._weightInterpolant = interpolant; + refreshTransformUniform( material.transmissionMap, uniforms.transmissionMapTransform ); - } + } - const times = interpolant.parameterPositions, - values = interpolant.sampleValues; + uniforms.thickness.value = material.thickness; - times[ 0 ] = now; - values[ 0 ] = weightNow; - times[ 1 ] = now + duration; - values[ 1 ] = weightThen; + if ( material.thicknessMap ) { - return this; + uniforms.thicknessMap.value = material.thicknessMap; - } + refreshTransformUniform( material.thicknessMap, uniforms.thicknessMapTransform ); -} + } -const _controlInterpolantsResultBuffer = new Float32Array( 1 ); + uniforms.attenuationDistance.value = material.attenuationDistance; + uniforms.attenuationColor.value.copy( material.attenuationColor ); + } -class AnimationMixer extends EventDispatcher { + if ( material.anisotropy > 0 ) { - constructor( root ) { + uniforms.anisotropyVector.value.set( material.anisotropy * Math.cos( material.anisotropyRotation ), material.anisotropy * Math.sin( material.anisotropyRotation ) ); - super(); + if ( material.anisotropyMap ) { - this._root = root; - this._initMemoryManager(); - this._accuIndex = 0; - this.time = 0; - this.timeScale = 1.0; + uniforms.anisotropyMap.value = material.anisotropyMap; - } + refreshTransformUniform( material.anisotropyMap, uniforms.anisotropyMapTransform ); - _bindAction( action, prototypeAction ) { + } - const root = action._localRoot || this._root, - tracks = action._clip.tracks, - nTracks = tracks.length, - bindings = action._propertyBindings, - interpolants = action._interpolants, - rootUuid = root.uuid, - bindingsByRoot = this._bindingsByRootAndName; + } - let bindingsByName = bindingsByRoot[ rootUuid ]; + uniforms.specularIntensity.value = material.specularIntensity; + uniforms.specularColor.value.copy( material.specularColor ); - if ( bindingsByName === undefined ) { + if ( material.specularColorMap ) { - bindingsByName = {}; - bindingsByRoot[ rootUuid ] = bindingsByName; + uniforms.specularColorMap.value = material.specularColorMap; + + refreshTransformUniform( material.specularColorMap, uniforms.specularColorMapTransform ); } - for ( let i = 0; i !== nTracks; ++ i ) { + if ( material.specularIntensityMap ) { - const track = tracks[ i ], - trackName = track.name; + uniforms.specularIntensityMap.value = material.specularIntensityMap; - let binding = bindingsByName[ trackName ]; + refreshTransformUniform( material.specularIntensityMap, uniforms.specularIntensityMapTransform ); - if ( binding !== undefined ) { + } - ++ binding.referenceCount; - bindings[ i ] = binding; + } - } else { + function refreshUniformsMatcap( uniforms, material ) { - binding = bindings[ i ]; + if ( material.matcap ) { - if ( binding !== undefined ) { + uniforms.matcap.value = material.matcap; - // existing binding, make sure the cache knows + } - if ( binding._cacheIndex === null ) { + } - ++ binding.referenceCount; - this._addInactiveBinding( binding, rootUuid, trackName ); + function refreshUniformsDistance( uniforms, material ) { - } + const light = properties.get( material ).light; - continue; + uniforms.referencePosition.value.setFromMatrixPosition( light.matrixWorld ); + uniforms.nearDistance.value = light.shadow.camera.near; + uniforms.farDistance.value = light.shadow.camera.far; - } + } - const path = prototypeAction && prototypeAction. - _propertyBindings[ i ].binding.parsedPath; + return { + refreshFogUniforms: refreshFogUniforms, + refreshMaterialUniforms: refreshMaterialUniforms + }; - binding = new PropertyMixer( - PropertyBinding.create( root, trackName, path ), - track.ValueTypeName, track.getValueSize() ); +} - ++ binding.referenceCount; - this._addInactiveBinding( binding, rootUuid, trackName ); +function WebGLUniformsGroups( gl, info, capabilities, state ) { - bindings[ i ] = binding; + let buffers = {}; + let updateList = {}; + let allocatedBindingPoints = []; - } + const maxBindingPoints = gl.getParameter( gl.MAX_UNIFORM_BUFFER_BINDINGS ); // binding points are global whereas block indices are per shader program - interpolants[ i ].resultBuffer = binding.buffer; + function bind( uniformsGroup, program ) { - } + const webglProgram = program.program; + state.uniformBlockBinding( uniformsGroup, webglProgram ); } - _activateAction( action ) { - - if ( ! this._isActiveAction( action ) ) { - - if ( action._cacheIndex === null ) { + function update( uniformsGroup, program ) { - // this action has been forgotten by the cache, but the user - // appears to be still using it -> rebind + let buffer = buffers[ uniformsGroup.id ]; - const rootUuid = ( action._localRoot || this._root ).uuid, - clipUuid = action._clip.uuid, - actionsForClip = this._actionsByClip[ clipUuid ]; + if ( buffer === undefined ) { - this._bindAction( action, - actionsForClip && actionsForClip.knownActions[ 0 ] ); + prepareUniformsGroup( uniformsGroup ); - this._addInactiveAction( action, clipUuid, rootUuid ); + buffer = createBuffer( uniformsGroup ); + buffers[ uniformsGroup.id ] = buffer; - } + uniformsGroup.addEventListener( 'dispose', onUniformsGroupsDispose ); - const bindings = action._propertyBindings; + } - // increment reference counts / sort out state - for ( let i = 0, n = bindings.length; i !== n; ++ i ) { + // ensure to update the binding points/block indices mapping for this program - const binding = bindings[ i ]; + const webglProgram = program.program; + state.updateUBOMapping( uniformsGroup, webglProgram ); - if ( binding.useCount ++ === 0 ) { + // update UBO once per frame - this._lendBinding( binding ); - binding.saveOriginalState(); + const frame = info.render.frame; - } + if ( updateList[ uniformsGroup.id ] !== frame ) { - } + updateBufferData( uniformsGroup ); - this._lendAction( action ); + updateList[ uniformsGroup.id ] = frame; } } - _deactivateAction( action ) { + function createBuffer( uniformsGroup ) { - if ( this._isActiveAction( action ) ) { + // the setup of an UBO is independent of a particular shader program but global - const bindings = action._propertyBindings; + const bindingPointIndex = allocateBindingPointIndex(); + uniformsGroup.__bindingPointIndex = bindingPointIndex; - // decrement reference counts / sort out state - for ( let i = 0, n = bindings.length; i !== n; ++ i ) { + const buffer = gl.createBuffer(); + const size = uniformsGroup.__size; + const usage = uniformsGroup.usage; - const binding = bindings[ i ]; + gl.bindBuffer( gl.UNIFORM_BUFFER, buffer ); + gl.bufferData( gl.UNIFORM_BUFFER, size, usage ); + gl.bindBuffer( gl.UNIFORM_BUFFER, null ); + gl.bindBufferBase( gl.UNIFORM_BUFFER, bindingPointIndex, buffer ); - if ( -- binding.useCount === 0 ) { + return buffer; - binding.restoreOriginalState(); - this._takeBackBinding( binding ); + } - } + function allocateBindingPointIndex() { - } + for ( let i = 0; i < maxBindingPoints; i ++ ) { - this._takeBackAction( action ); + if ( allocatedBindingPoints.indexOf( i ) === -1 ) { + + allocatedBindingPoints.push( i ); + return i; + + } } + console.error( 'THREE.WebGLRenderer: Maximum number of simultaneously usable uniforms groups reached.' ); + + return 0; + } - // Memory manager + function updateBufferData( uniformsGroup ) { - _initMemoryManager() { + const buffer = buffers[ uniformsGroup.id ]; + const uniforms = uniformsGroup.uniforms; + const cache = uniformsGroup.__cache; - this._actions = []; // 'nActiveActions' followed by inactive ones - this._nActiveActions = 0; + gl.bindBuffer( gl.UNIFORM_BUFFER, buffer ); - this._actionsByClip = {}; - // inside: - // { - // knownActions: Array< AnimationAction > - used as prototypes - // actionByRoot: AnimationAction - lookup - // } + for ( let i = 0, il = uniforms.length; i < il; i ++ ) { + const uniformArray = Array.isArray( uniforms[ i ] ) ? uniforms[ i ] : [ uniforms[ i ] ]; - this._bindings = []; // 'nActiveBindings' followed by inactive ones - this._nActiveBindings = 0; + for ( let j = 0, jl = uniformArray.length; j < jl; j ++ ) { + + const uniform = uniformArray[ j ]; - this._bindingsByRootAndName = {}; // inside: Map< name, PropertyMixer > + if ( hasUniformChanged( uniform, i, j, cache ) === true ) { + const offset = uniform.__offset; - this._controlInterpolants = []; // same game as above - this._nActiveControlInterpolants = 0; + const values = Array.isArray( uniform.value ) ? uniform.value : [ uniform.value ]; - const scope = this; + let arrayOffset = 0; - this.stats = { + for ( let k = 0; k < values.length; k ++ ) { - actions: { - get total() { + const value = values[ k ]; - return scope._actions.length; + const info = getUniformSize( value ); - }, - get inUse() { + // TODO add integer and struct support + if ( typeof value === 'number' || typeof value === 'boolean' ) { - return scope._nActiveActions; + uniform.__data[ 0 ] = value; + gl.bufferSubData( gl.UNIFORM_BUFFER, offset + arrayOffset, uniform.__data ); - } - }, - bindings: { - get total() { + } else if ( value.isMatrix3 ) { - return scope._bindings.length; + // manually converting 3x3 to 3x4 - }, - get inUse() { + uniform.__data[ 0 ] = value.elements[ 0 ]; + uniform.__data[ 1 ] = value.elements[ 1 ]; + uniform.__data[ 2 ] = value.elements[ 2 ]; + uniform.__data[ 3 ] = 0; + uniform.__data[ 4 ] = value.elements[ 3 ]; + uniform.__data[ 5 ] = value.elements[ 4 ]; + uniform.__data[ 6 ] = value.elements[ 5 ]; + uniform.__data[ 7 ] = 0; + uniform.__data[ 8 ] = value.elements[ 6 ]; + uniform.__data[ 9 ] = value.elements[ 7 ]; + uniform.__data[ 10 ] = value.elements[ 8 ]; + uniform.__data[ 11 ] = 0; - return scope._nActiveBindings; + } else { - } - }, - controlInterpolants: { - get total() { + value.toArray( uniform.__data, arrayOffset ); - return scope._controlInterpolants.length; + arrayOffset += info.storage / Float32Array.BYTES_PER_ELEMENT; - }, - get inUse() { + } - return scope._nActiveControlInterpolants; + } + + gl.bufferSubData( gl.UNIFORM_BUFFER, offset, uniform.__data ); } + } - }; + } + + gl.bindBuffer( gl.UNIFORM_BUFFER, null ); } - // Memory management for AnimationAction objects + function hasUniformChanged( uniform, index, indexArray, cache ) { - _isActiveAction( action ) { + const value = uniform.value; + const indexString = index + '_' + indexArray; - const index = action._cacheIndex; - return index !== null && index < this._nActiveActions; + if ( cache[ indexString ] === undefined ) { - } + // cache entry does not exist so far - _addInactiveAction( action, clipUuid, rootUuid ) { + if ( typeof value === 'number' || typeof value === 'boolean' ) { - const actions = this._actions, - actionsByClip = this._actionsByClip; + cache[ indexString ] = value; - let actionsForClip = actionsByClip[ clipUuid ]; + } else { - if ( actionsForClip === undefined ) { + cache[ indexString ] = value.clone(); - actionsForClip = { + } - knownActions: [ action ], - actionByRoot: {} + return true; - }; + } else { - action._byClipCacheIndex = 0; + const cachedObject = cache[ indexString ]; - actionsByClip[ clipUuid ] = actionsForClip; + // compare current value with cached entry - } else { + if ( typeof value === 'number' || typeof value === 'boolean' ) { - const knownActions = actionsForClip.knownActions; + if ( cachedObject !== value ) { - action._byClipCacheIndex = knownActions.length; - knownActions.push( action ); + cache[ indexString ] = value; + return true; - } + } - action._cacheIndex = actions.length; - actions.push( action ); + } else { - actionsForClip.actionByRoot[ rootUuid ] = action; + if ( cachedObject.equals( value ) === false ) { - } + cachedObject.copy( value ); + return true; - _removeInactiveAction( action ) { + } - const actions = this._actions, - lastInactiveAction = actions[ actions.length - 1 ], - cacheIndex = action._cacheIndex; + } - lastInactiveAction._cacheIndex = cacheIndex; - actions[ cacheIndex ] = lastInactiveAction; - actions.pop(); + } - action._cacheIndex = null; + return false; + } - const clipUuid = action._clip.uuid, - actionsByClip = this._actionsByClip, - actionsForClip = actionsByClip[ clipUuid ], - knownActionsForClip = actionsForClip.knownActions, + function prepareUniformsGroup( uniformsGroup ) { - lastKnownAction = - knownActionsForClip[ knownActionsForClip.length - 1 ], + // determine total buffer size according to the STD140 layout + // Hint: STD140 is the only supported layout in WebGL 2 - byClipCacheIndex = action._byClipCacheIndex; + const uniforms = uniformsGroup.uniforms; - lastKnownAction._byClipCacheIndex = byClipCacheIndex; - knownActionsForClip[ byClipCacheIndex ] = lastKnownAction; - knownActionsForClip.pop(); + let offset = 0; // global buffer offset in bytes + const chunkSize = 16; // size of a chunk in bytes - action._byClipCacheIndex = null; + for ( let i = 0, l = uniforms.length; i < l; i ++ ) { + const uniformArray = Array.isArray( uniforms[ i ] ) ? uniforms[ i ] : [ uniforms[ i ] ]; - const actionByRoot = actionsForClip.actionByRoot, - rootUuid = ( action._localRoot || this._root ).uuid; + for ( let j = 0, jl = uniformArray.length; j < jl; j ++ ) { - delete actionByRoot[ rootUuid ]; + const uniform = uniformArray[ j ]; - if ( knownActionsForClip.length === 0 ) { + const values = Array.isArray( uniform.value ) ? uniform.value : [ uniform.value ]; - delete actionsByClip[ clipUuid ]; + for ( let k = 0, kl = values.length; k < kl; k ++ ) { - } + const value = values[ k ]; - this._removeInactiveBindingsForAction( action ); + const info = getUniformSize( value ); - } + const chunkOffset = offset % chunkSize; // offset in the current chunk + const chunkPadding = chunkOffset % info.boundary; // required padding to match boundary + const chunkStart = chunkOffset + chunkPadding; // the start position in the current chunk for the data - _removeInactiveBindingsForAction( action ) { + offset += chunkPadding; - const bindings = action._propertyBindings; + // Check for chunk overflow + if ( chunkStart !== 0 && ( chunkSize - chunkStart ) < info.storage ) { - for ( let i = 0, n = bindings.length; i !== n; ++ i ) { + // Add padding and adjust offset + offset += ( chunkSize - chunkStart ); - const binding = bindings[ i ]; + } - if ( -- binding.referenceCount === 0 ) { + // the following two properties will be used for partial buffer updates + uniform.__data = new Float32Array( info.storage / Float32Array.BYTES_PER_ELEMENT ); + uniform.__offset = offset; - this._removeInactiveBinding( binding ); + // Update the global offset + offset += info.storage; + + } } } - } - - _lendAction( action ) { - - // [ active actions | inactive actions ] - // [ active actions >| inactive actions ] - // s a - // <-swap-> - // a s + // ensure correct final padding - const actions = this._actions, - prevIndex = action._cacheIndex, + const chunkOffset = offset % chunkSize; - lastActiveIndex = this._nActiveActions ++, + if ( chunkOffset > 0 ) offset += ( chunkSize - chunkOffset ); - firstInactiveAction = actions[ lastActiveIndex ]; + // - action._cacheIndex = lastActiveIndex; - actions[ lastActiveIndex ] = action; + uniformsGroup.__size = offset; + uniformsGroup.__cache = {}; - firstInactiveAction._cacheIndex = prevIndex; - actions[ prevIndex ] = firstInactiveAction; + return this; } - _takeBackAction( action ) { + function getUniformSize( value ) { - // [ active actions | inactive actions ] - // [ active actions |< inactive actions ] - // a s - // <-swap-> - // s a + const info = { + boundary: 0, // bytes + storage: 0 // bytes + }; - const actions = this._actions, - prevIndex = action._cacheIndex, + // determine sizes according to STD140 - firstInactiveIndex = -- this._nActiveActions, + if ( typeof value === 'number' || typeof value === 'boolean' ) { - lastActiveAction = actions[ firstInactiveIndex ]; + // float/int/bool - action._cacheIndex = firstInactiveIndex; - actions[ firstInactiveIndex ] = action; + info.boundary = 4; + info.storage = 4; - lastActiveAction._cacheIndex = prevIndex; - actions[ prevIndex ] = lastActiveAction; + } else if ( value.isVector2 ) { - } + // vec2 - // Memory management for PropertyMixer objects + info.boundary = 8; + info.storage = 8; - _addInactiveBinding( binding, rootUuid, trackName ) { + } else if ( value.isVector3 || value.isColor ) { - const bindingsByRoot = this._bindingsByRootAndName, - bindings = this._bindings; + // vec3 - let bindingByName = bindingsByRoot[ rootUuid ]; + info.boundary = 16; + info.storage = 12; // evil: vec3 must start on a 16-byte boundary but it only consumes 12 bytes - if ( bindingByName === undefined ) { + } else if ( value.isVector4 ) { - bindingByName = {}; - bindingsByRoot[ rootUuid ] = bindingByName; + // vec4 - } + info.boundary = 16; + info.storage = 16; - bindingByName[ trackName ] = binding; + } else if ( value.isMatrix3 ) { - binding._cacheIndex = bindings.length; - bindings.push( binding ); + // mat3 (in STD140 a 3x3 matrix is represented as 3x4) - } + info.boundary = 48; + info.storage = 48; - _removeInactiveBinding( binding ) { + } else if ( value.isMatrix4 ) { - const bindings = this._bindings, - propBinding = binding.binding, - rootUuid = propBinding.rootNode.uuid, - trackName = propBinding.path, - bindingsByRoot = this._bindingsByRootAndName, - bindingByName = bindingsByRoot[ rootUuid ], + // mat4 - lastInactiveBinding = bindings[ bindings.length - 1 ], - cacheIndex = binding._cacheIndex; + info.boundary = 64; + info.storage = 64; - lastInactiveBinding._cacheIndex = cacheIndex; - bindings[ cacheIndex ] = lastInactiveBinding; - bindings.pop(); + } else if ( value.isTexture ) { - delete bindingByName[ trackName ]; + console.warn( 'THREE.WebGLRenderer: Texture samplers can not be part of an uniforms group.' ); - if ( Object.keys( bindingByName ).length === 0 ) { + } else { - delete bindingsByRoot[ rootUuid ]; + console.warn( 'THREE.WebGLRenderer: Unsupported uniform value type.', value ); } + return info; + } - _lendBinding( binding ) { + function onUniformsGroupsDispose( event ) { - const bindings = this._bindings, - prevIndex = binding._cacheIndex, + const uniformsGroup = event.target; - lastActiveIndex = this._nActiveBindings ++, + uniformsGroup.removeEventListener( 'dispose', onUniformsGroupsDispose ); - firstInactiveBinding = bindings[ lastActiveIndex ]; + const index = allocatedBindingPoints.indexOf( uniformsGroup.__bindingPointIndex ); + allocatedBindingPoints.splice( index, 1 ); - binding._cacheIndex = lastActiveIndex; - bindings[ lastActiveIndex ] = binding; + gl.deleteBuffer( buffers[ uniformsGroup.id ] ); - firstInactiveBinding._cacheIndex = prevIndex; - bindings[ prevIndex ] = firstInactiveBinding; + delete buffers[ uniformsGroup.id ]; + delete updateList[ uniformsGroup.id ]; } - _takeBackBinding( binding ) { - - const bindings = this._bindings, - prevIndex = binding._cacheIndex, + function dispose() { - firstInactiveIndex = -- this._nActiveBindings, + for ( const id in buffers ) { - lastActiveBinding = bindings[ firstInactiveIndex ]; + gl.deleteBuffer( buffers[ id ] ); - binding._cacheIndex = firstInactiveIndex; - bindings[ firstInactiveIndex ] = binding; + } - lastActiveBinding._cacheIndex = prevIndex; - bindings[ prevIndex ] = lastActiveBinding; + allocatedBindingPoints = []; + buffers = {}; + updateList = {}; } + return { - // Memory management of Interpolants for weight and time scale + bind: bind, + update: update, - _lendControlInterpolant() { + dispose: dispose - const interpolants = this._controlInterpolants, - lastActiveIndex = this._nActiveControlInterpolants ++; + }; - let interpolant = interpolants[ lastActiveIndex ]; +} - if ( interpolant === undefined ) { +/** + * This renderer uses WebGL 2 to display scenes. + * + * WebGL 1 is not supported since `r163`. + */ +class WebGLRenderer { - interpolant = new LinearInterpolant( - new Float32Array( 2 ), new Float32Array( 2 ), - 1, _controlInterpolantsResultBuffer ); + /** + * Constructs a new WebGL renderer. + * + * @param {WebGLRenderer~Options} [parameters] - The configuration parameter. + */ + constructor( parameters = {} ) { - interpolant.__cacheIndex = lastActiveIndex; - interpolants[ lastActiveIndex ] = interpolant; + const { + canvas = createCanvasElement(), + context = null, + depth = true, + stencil = false, + alpha = false, + antialias = false, + premultipliedAlpha = true, + preserveDrawingBuffer = false, + powerPreference = 'default', + failIfMajorPerformanceCaveat = false, + reverseDepthBuffer = false, + } = parameters; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isWebGLRenderer = true; + + let _alpha; + + if ( context !== null ) { + + if ( typeof WebGLRenderingContext !== 'undefined' && context instanceof WebGLRenderingContext ) { + + throw new Error( 'THREE.WebGLRenderer: WebGL 1 is not supported since r163.' ); + + } + + _alpha = context.getContextAttributes().alpha; + + } else { + + _alpha = alpha; } - return interpolant; + const uintClearColor = new Uint32Array( 4 ); + const intClearColor = new Int32Array( 4 ); - } + let currentRenderList = null; + let currentRenderState = null; - _takeBackControlInterpolant( interpolant ) { + // render() can be called from within a callback triggered by another render. + // We track this so that the nested render call gets its list and state isolated from the parent render call. - const interpolants = this._controlInterpolants, - prevIndex = interpolant.__cacheIndex, + const renderListStack = []; + const renderStateStack = []; - firstInactiveIndex = -- this._nActiveControlInterpolants, + // public properties - lastActiveInterpolant = interpolants[ firstInactiveIndex ]; + /** + * A canvas where the renderer draws its output.This is automatically created by the renderer + * in the constructor (if not provided already); you just need to add it to your page like so: + * ```js + * document.body.appendChild( renderer.domElement ); + * ``` + * + * @type {DOMElement} + */ + this.domElement = canvas; - interpolant.__cacheIndex = firstInactiveIndex; - interpolants[ firstInactiveIndex ] = interpolant; + /** + * A object with debug configuration settings. + * + * - `checkShaderErrors`: If it is `true`, defines whether material shader programs are + * checked for errors during compilation and linkage process. It may be useful to disable + * this check in production for performance gain. It is strongly recommended to keep these + * checks enabled during development. If the shader does not compile and link - it will not + * work and associated material will not render. + * - `onShaderError(gl, program, glVertexShader,glFragmentShader)`: A callback function that + * can be used for custom error reporting. The callback receives the WebGL context, an instance + * of WebGLProgram as well two instances of WebGLShader representing the vertex and fragment shader. + * Assigning a custom function disables the default error reporting. + * + * @type {Object} + */ + this.debug = { - lastActiveInterpolant.__cacheIndex = prevIndex; - interpolants[ prevIndex ] = lastActiveInterpolant; + /** + * Enables error checking and reporting when shader programs are being compiled. + * @type {boolean} + */ + checkShaderErrors: true, + /** + * Callback for custom error reporting. + * @type {?Function} + */ + onShaderError: null + }; - } + // clearing - // return an action for a clip optionally using a custom root target - // object (this method allocates a lot of dynamic memory in case a - // previously unknown clip/root combination is specified) - clipAction( clip, optionalRoot, blendMode ) { + /** + * Whether the renderer should automatically clear its output before rendering a frame or not. + * + * @type {boolean} + * @default true + */ + this.autoClear = true; - const root = optionalRoot || this._root, - rootUuid = root.uuid; + /** + * If {@link WebGLRenderer#autoClear} set to `true`, whether the renderer should clear + * the color buffer or not. + * + * @type {boolean} + * @default true + */ + this.autoClearColor = true; - let clipObject = typeof clip === 'string' ? AnimationClip.findByName( root, clip ) : clip; + /** + * If {@link WebGLRenderer#autoClear} set to `true`, whether the renderer should clear + * the depth buffer or not. + * + * @type {boolean} + * @default true + */ + this.autoClearDepth = true; - const clipUuid = clipObject !== null ? clipObject.uuid : clip; + /** + * If {@link WebGLRenderer#autoClear} set to `true`, whether the renderer should clear + * the stencil buffer or not. + * + * @type {boolean} + * @default true + */ + this.autoClearStencil = true; - const actionsForClip = this._actionsByClip[ clipUuid ]; - let prototypeAction = null; + // scene graph - if ( blendMode === undefined ) { + /** + * Whether the renderer should sort objects or not. + * + * Note: Sorting is used to attempt to properly render objects that have some + * degree of transparency. By definition, sorting objects may not work in all + * cases. Depending on the needs of application, it may be necessary to turn + * off sorting and use other methods to deal with transparency rendering e.g. + * manually determining each object's rendering order. + * + * @type {boolean} + * @default true + */ + this.sortObjects = true; - if ( clipObject !== null ) { + // user-defined clipping - blendMode = clipObject.blendMode; + /** + * User-defined clipping planes specified in world space. These planes apply globally. + * Points in space whose dot product with the plane is negative are cut away. + * + * @type {Array} + */ + this.clippingPlanes = []; - } else { + /** + * Whether the renderer respects object-level clipping planes or not. + * + * @type {boolean} + * @default false + */ + this.localClippingEnabled = false; - blendMode = NormalAnimationBlendMode; + // tone mapping - } + /** + * The tone mapping technique of the renderer. + * + * @type {(NoToneMapping|LinearToneMapping|ReinhardToneMapping|CineonToneMapping|ACESFilmicToneMapping|CustomToneMapping|AgXToneMapping|NeutralToneMapping)} + * @default NoToneMapping + */ + this.toneMapping = NoToneMapping; - } + /** + * Exposure level of tone mapping. + * + * @type {number} + * @default 1 + */ + this.toneMappingExposure = 1.0; - if ( actionsForClip !== undefined ) { + // transmission - const existingAction = actionsForClip.actionByRoot[ rootUuid ]; + /** + * The normalized resolution scale for the transmission render target, measured in percentage + * of viewport dimensions. Lowering this value can result in significant performance improvements + * when using {@link MeshPhysicalMaterial#transmission}. + * + * @type {number} + * @default 1 + */ + this.transmissionResolutionScale = 1.0; - if ( existingAction !== undefined && existingAction.blendMode === blendMode ) { + // internal properties - return existingAction; + const _this = this; - } + let _isContextLost = false; - // we know the clip, so we don't have to parse all - // the bindings again but can just copy - prototypeAction = actionsForClip.knownActions[ 0 ]; + // internal state cache - // also, take the clip from the prototype action - if ( clipObject === null ) - clipObject = prototypeAction._clip; + this._outputColorSpace = SRGBColorSpace; - } + let _currentActiveCubeFace = 0; + let _currentActiveMipmapLevel = 0; + let _currentRenderTarget = null; + let _currentMaterialId = -1; - // clip must be known when specified via string - if ( clipObject === null ) return null; + let _currentCamera = null; - // allocate all resources required to run it - const newAction = new AnimationAction( this, clipObject, optionalRoot, blendMode ); + const _currentViewport = new Vector4(); + const _currentScissor = new Vector4(); + let _currentScissorTest = null; - this._bindAction( newAction, prototypeAction ); + const _currentClearColor = new Color( 0x000000 ); + let _currentClearAlpha = 0; - // and make the action known to the memory manager - this._addInactiveAction( newAction, clipUuid, rootUuid ); + // - return newAction; + let _width = canvas.width; + let _height = canvas.height; - } + let _pixelRatio = 1; + let _opaqueSort = null; + let _transparentSort = null; - // get an existing action - existingAction( clip, optionalRoot ) { + const _viewport = new Vector4( 0, 0, _width, _height ); + const _scissor = new Vector4( 0, 0, _width, _height ); + let _scissorTest = false; - const root = optionalRoot || this._root, - rootUuid = root.uuid, + // frustum - clipObject = typeof clip === 'string' ? - AnimationClip.findByName( root, clip ) : clip, + const _frustum = new Frustum(); - clipUuid = clipObject ? clipObject.uuid : clip, + // clipping - actionsForClip = this._actionsByClip[ clipUuid ]; + let _clippingEnabled = false; + let _localClippingEnabled = false; - if ( actionsForClip !== undefined ) { + // camera matrices cache - return actionsForClip.actionByRoot[ rootUuid ] || null; + const _currentProjectionMatrix = new Matrix4(); + const _projScreenMatrix = new Matrix4(); - } + const _vector3 = new Vector3(); - return null; + const _vector4 = new Vector4(); - } + const _emptyScene = { background: null, fog: null, environment: null, overrideMaterial: null, isScene: true }; - // deactivates all previously scheduled actions - stopAllAction() { + let _renderBackground = false; - const actions = this._actions, - nActions = this._nActiveActions; + function getTargetPixelRatio() { - for ( let i = nActions - 1; i >= 0; -- i ) { + return _currentRenderTarget === null ? _pixelRatio : 1; - actions[ i ].stop(); + } + + // initialize + + let _gl = context; + + function getContext( contextName, contextAttributes ) { + + return canvas.getContext( contextName, contextAttributes ); } - return this; + try { - } + const contextAttributes = { + alpha: true, + depth, + stencil, + antialias, + premultipliedAlpha, + preserveDrawingBuffer, + powerPreference, + failIfMajorPerformanceCaveat, + }; - // advance the time and update apply the animation - update( deltaTime ) { + // OffscreenCanvas does not have setAttribute, see #22811 + if ( 'setAttribute' in canvas ) canvas.setAttribute( 'data-engine', `three.js r${REVISION}` ); - deltaTime *= this.timeScale; + // event listeners must be registered before WebGL context is created, see #12753 + canvas.addEventListener( 'webglcontextlost', onContextLost, false ); + canvas.addEventListener( 'webglcontextrestored', onContextRestore, false ); + canvas.addEventListener( 'webglcontextcreationerror', onContextCreationError, false ); - const actions = this._actions, - nActions = this._nActiveActions, + if ( _gl === null ) { - time = this.time += deltaTime, - timeDirection = Math.sign( deltaTime ), + const contextName = 'webgl2'; - accuIndex = this._accuIndex ^= 1; + _gl = getContext( contextName, contextAttributes ); - // run active actions + if ( _gl === null ) { - for ( let i = 0; i !== nActions; ++ i ) { + if ( getContext( contextName ) ) { - const action = actions[ i ]; + throw new Error( 'Error creating WebGL context with your selected attributes.' ); - action._update( time, deltaTime, timeDirection, accuIndex ); + } else { - } + throw new Error( 'Error creating WebGL context.' ); + + } - // update scene graph + } - const bindings = this._bindings, - nBindings = this._nActiveBindings; + } - for ( let i = 0; i !== nBindings; ++ i ) { + } catch ( error ) { - bindings[ i ].apply( accuIndex ); + console.error( 'THREE.WebGLRenderer: ' + error.message ); + throw error; } - return this; + let extensions, capabilities, state, info; + let properties, textures, cubemaps, cubeuvmaps, attributes, geometries, objects; + let programCache, materials, renderLists, renderStates, clipping, shadowMap; - } + let background, morphtargets, bufferRenderer, indexedBufferRenderer; - // Allows you to seek to a specific time in an animation. - setTime( timeInSeconds ) { + let utils, bindingStates, uniformsGroups; - this.time = 0; // Zero out time attribute for AnimationMixer object; - for ( let i = 0; i < this._actions.length; i ++ ) { + function initGLContext() { - this._actions[ i ].time = 0; // Zero out time attribute for all associated AnimationAction objects. + extensions = new WebGLExtensions( _gl ); + extensions.init(); - } + utils = new WebGLUtils( _gl, extensions ); - return this.update( timeInSeconds ); // Update used to set exact time. Returns "this" AnimationMixer object. + capabilities = new WebGLCapabilities( _gl, extensions, parameters, utils ); - } + state = new WebGLState( _gl, extensions ); - // return this mixer's root target object - getRoot() { + if ( capabilities.reverseDepthBuffer && reverseDepthBuffer ) { - return this._root; + state.buffers.depth.setReversed( true ); - } + } - // free all resources specific to a particular clip - uncacheClip( clip ) { + info = new WebGLInfo( _gl ); + properties = new WebGLProperties(); + textures = new WebGLTextures( _gl, extensions, state, properties, capabilities, utils, info ); + cubemaps = new WebGLCubeMaps( _this ); + cubeuvmaps = new WebGLCubeUVMaps( _this ); + attributes = new WebGLAttributes( _gl ); + bindingStates = new WebGLBindingStates( _gl, attributes ); + geometries = new WebGLGeometries( _gl, attributes, info, bindingStates ); + objects = new WebGLObjects( _gl, geometries, attributes, info ); + morphtargets = new WebGLMorphtargets( _gl, capabilities, textures ); + clipping = new WebGLClipping( properties ); + programCache = new WebGLPrograms( _this, cubemaps, cubeuvmaps, extensions, capabilities, bindingStates, clipping ); + materials = new WebGLMaterials( _this, properties ); + renderLists = new WebGLRenderLists(); + renderStates = new WebGLRenderStates( extensions ); + background = new WebGLBackground( _this, cubemaps, cubeuvmaps, state, objects, _alpha, premultipliedAlpha ); + shadowMap = new WebGLShadowMap( _this, objects, capabilities ); + uniformsGroups = new WebGLUniformsGroups( _gl, info, capabilities, state ); - const actions = this._actions, - clipUuid = clip.uuid, - actionsByClip = this._actionsByClip, - actionsForClip = actionsByClip[ clipUuid ]; + bufferRenderer = new WebGLBufferRenderer( _gl, extensions, info ); + indexedBufferRenderer = new WebGLIndexedBufferRenderer( _gl, extensions, info ); - if ( actionsForClip !== undefined ) { + info.programs = programCache.programs; - // note: just calling _removeInactiveAction would mess up the - // iteration state and also require updating the state we can - // just throw away + /** + * Holds details about the capabilities of the current rendering context. + * + * @name WebGLRenderer#capabilities + * @type {WebGLRenderer~Capabilities} + */ + _this.capabilities = capabilities; - const actionsToRemove = actionsForClip.knownActions; + /** + * Provides methods for retrieving and testing WebGL extensions. + * + * - `get(extensionName:string)`: Used to check whether a WebGL extension is supported + * and return the extension object if available. + * - `has(extensionName:string)`: returns `true` if the extension is supported. + * + * @name WebGLRenderer#extensions + * @type {Object} + */ + _this.extensions = extensions; - for ( let i = 0, n = actionsToRemove.length; i !== n; ++ i ) { + /** + * Used to track properties of other objects like native WebGL objects. + * + * @name WebGLRenderer#properties + * @type {Object} + */ + _this.properties = properties; - const action = actionsToRemove[ i ]; + /** + * Manages the render lists of the renderer. + * + * @name WebGLRenderer#renderLists + * @type {Object} + */ + _this.renderLists = renderLists; - this._deactivateAction( action ); - const cacheIndex = action._cacheIndex, - lastInactiveAction = actions[ actions.length - 1 ]; - action._cacheIndex = null; - action._byClipCacheIndex = null; + /** + * Interface for managing shadows. + * + * @name WebGLRenderer#shadowMap + * @type {WebGLRenderer~ShadowMap} + */ + _this.shadowMap = shadowMap; - lastInactiveAction._cacheIndex = cacheIndex; - actions[ cacheIndex ] = lastInactiveAction; - actions.pop(); + /** + * Interface for managing the WebGL state. + * + * @name WebGLRenderer#state + * @type {Object} + */ + _this.state = state; - this._removeInactiveBindingsForAction( action ); + /** + * Holds a series of statistical information about the GPU memory + * and the rendering process. Useful for debugging and monitoring. + * + * By default these data are reset at each render call but when having + * multiple render passes per frame (e.g. when using post processing) it can + * be preferred to reset with a custom pattern. First, set `autoReset` to + * `false`. + * ```js + * renderer.info.autoReset = false; + * ``` + * Call `reset()` whenever you have finished to render a single frame. + * ```js + * renderer.info.reset(); + * ``` + * + * @name WebGLRenderer#info + * @type {WebGLRenderer~Info} + */ + _this.info = info; - } + } - delete actionsByClip[ clipUuid ]; + initGLContext(); - } + // xr - } + const xr = new WebXRManager( _this, _gl ); - // free all resources specific to a particular root target object - uncacheRoot( root ) { + /** + * A reference to the XR manager. + * + * @type {WebXRManager} + */ + this.xr = xr; - const rootUuid = root.uuid, - actionsByClip = this._actionsByClip; + /** + * Returns the rendering context. + * + * @return {WebGL2RenderingContext} The rendering context. + */ + this.getContext = function () { - for ( const clipUuid in actionsByClip ) { + return _gl; - const actionByRoot = actionsByClip[ clipUuid ].actionByRoot, - action = actionByRoot[ rootUuid ]; + }; - if ( action !== undefined ) { + /** + * Returns the rendering context attributes. + * + * @return {WebGLContextAttributes} The rendering context attributes. + */ + this.getContextAttributes = function () { - this._deactivateAction( action ); - this._removeInactiveAction( action ); + return _gl.getContextAttributes(); - } + }; - } + /** + * Simulates a loss of the WebGL context. This requires support for the `WEBGL_lose_context` extension. + */ + this.forceContextLoss = function () { - const bindingsByRoot = this._bindingsByRootAndName, - bindingByName = bindingsByRoot[ rootUuid ]; + const extension = extensions.get( 'WEBGL_lose_context' ); + if ( extension ) extension.loseContext(); - if ( bindingByName !== undefined ) { + }; - for ( const trackName in bindingByName ) { + /** + * Simulates a restore of the WebGL context. This requires support for the `WEBGL_lose_context` extension. + */ + this.forceContextRestore = function () { - const binding = bindingByName[ trackName ]; - binding.restoreOriginalState(); - this._removeInactiveBinding( binding ); + const extension = extensions.get( 'WEBGL_lose_context' ); + if ( extension ) extension.restoreContext(); - } + }; - } + /** + * Returns the pixel ratio. + * + * @return {number} The pixel ratio. + */ + this.getPixelRatio = function () { - } + return _pixelRatio; - // remove a targeted clip from the cache - uncacheAction( clip, optionalRoot ) { + }; - const action = this.existingAction( clip, optionalRoot ); + /** + * Sets the given pixel ratio and resizes the canvas if necessary. + * + * @param {number} value - The pixel ratio. + */ + this.setPixelRatio = function ( value ) { - if ( action !== null ) { + if ( value === undefined ) return; - this._deactivateAction( action ); - this._removeInactiveAction( action ); + _pixelRatio = value; - } + this.setSize( _width, _height, false ); - } + }; -} + /** + * Returns the renderer's size in logical pixels. This method does not honor the pixel ratio. + * + * @param {Vector2} target - The method writes the result in this target object. + * @return {Vector2} The renderer's size in logical pixels. + */ + this.getSize = function ( target ) { -class Uniform { + return target.set( _width, _height ); - constructor( value ) { + }; - this.value = value; + /** + * Resizes the output canvas to (width, height) with device pixel ratio taken + * into account, and also sets the viewport to fit that size, starting in (0, + * 0). Setting `updateStyle` to false prevents any style changes to the output canvas. + * + * @param {number} width - The width in logical pixels. + * @param {number} height - The height in logical pixels. + * @param {boolean} [updateStyle=true] - Whether to update the `style` attribute of the canvas or not. + */ + this.setSize = function ( width, height, updateStyle = true ) { - } + if ( xr.isPresenting ) { - clone() { + console.warn( 'THREE.WebGLRenderer: Can\'t change size while VR device is presenting.' ); + return; - return new Uniform( this.value.clone === undefined ? this.value : this.value.clone() ); + } - } + _width = width; + _height = height; -} + canvas.width = Math.floor( width * _pixelRatio ); + canvas.height = Math.floor( height * _pixelRatio ); -let id = 0; + if ( updateStyle === true ) { -class UniformsGroup extends EventDispatcher { + canvas.style.width = width + 'px'; + canvas.style.height = height + 'px'; - constructor() { + } - super(); + this.setViewport( 0, 0, width, height ); - this.isUniformsGroup = true; + }; - Object.defineProperty( this, 'id', { value: id ++ } ); + /** + * Returns the drawing buffer size in physical pixels. This method honors the pixel ratio. + * + * @param {Vector2} target - The method writes the result in this target object. + * @return {Vector2} The drawing buffer size. + */ + this.getDrawingBufferSize = function ( target ) { - this.name = ''; + return target.set( _width * _pixelRatio, _height * _pixelRatio ).floor(); - this.usage = StaticDrawUsage; - this.uniforms = []; + }; - } + /** + * This method allows to define the drawing buffer size by specifying + * width, height and pixel ratio all at once. The size of the drawing + * buffer is computed with this formula: + * ```js + * size.x = width * pixelRatio; + * size.y = height * pixelRatio; + * ``` + * + * @param {number} width - The width in logical pixels. + * @param {number} height - The height in logical pixels. + * @param {number} pixelRatio - The pixel ratio. + */ + this.setDrawingBufferSize = function ( width, height, pixelRatio ) { - add( uniform ) { + _width = width; + _height = height; - this.uniforms.push( uniform ); + _pixelRatio = pixelRatio; - return this; + canvas.width = Math.floor( width * pixelRatio ); + canvas.height = Math.floor( height * pixelRatio ); - } + this.setViewport( 0, 0, width, height ); - remove( uniform ) { + }; - const index = this.uniforms.indexOf( uniform ); + /** + * Returns the current viewport definition. + * + * @param {Vector2} target - The method writes the result in this target object. + * @return {Vector2} The current viewport definition. + */ + this.getCurrentViewport = function ( target ) { - if ( index !== - 1 ) this.uniforms.splice( index, 1 ); + return target.copy( _currentViewport ); - return this; + }; - } + /** + * Returns the viewport definition. + * + * @param {Vector4} target - The method writes the result in this target object. + * @return {Vector4} The viewport definition. + */ + this.getViewport = function ( target ) { - setName( name ) { + return target.copy( _viewport ); - this.name = name; + }; - return this; + /** + * Sets the viewport to render from `(x, y)` to `(x + width, y + height)`. + * + * @param {number | Vector4} x - The horizontal coordinate for the lower left corner of the viewport origin in logical pixel unit. + * Or alternatively a four-component vector specifying all the parameters of the viewport. + * @param {number} y - The vertical coordinate for the lower left corner of the viewport origin in logical pixel unit. + * @param {number} width - The width of the viewport in logical pixel unit. + * @param {number} height - The height of the viewport in logical pixel unit. + */ + this.setViewport = function ( x, y, width, height ) { - } + if ( x.isVector4 ) { - setUsage( value ) { + _viewport.set( x.x, x.y, x.z, x.w ); - this.usage = value; + } else { - return this; + _viewport.set( x, y, width, height ); - } + } - dispose() { + state.viewport( _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ).round() ); - this.dispatchEvent( { type: 'dispose' } ); + }; - return this; + /** + * Returns the scissor region. + * + * @param {Vector4} target - The method writes the result in this target object. + * @return {Vector4} The scissor region. + */ + this.getScissor = function ( target ) { - } + return target.copy( _scissor ); - copy( source ) { + }; - this.name = source.name; - this.usage = source.usage; + /** + * Sets the scissor region to render from `(x, y)` to `(x + width, y + height)`. + * + * @param {number | Vector4} x - The horizontal coordinate for the lower left corner of the scissor region origin in logical pixel unit. + * Or alternatively a four-component vector specifying all the parameters of the scissor region. + * @param {number} y - The vertical coordinate for the lower left corner of the scissor region origin in logical pixel unit. + * @param {number} width - The width of the scissor region in logical pixel unit. + * @param {number} height - The height of the scissor region in logical pixel unit. + */ + this.setScissor = function ( x, y, width, height ) { - const uniformsSource = source.uniforms; + if ( x.isVector4 ) { - this.uniforms.length = 0; + _scissor.set( x.x, x.y, x.z, x.w ); - for ( let i = 0, l = uniformsSource.length; i < l; i ++ ) { + } else { - this.uniforms.push( uniformsSource[ i ].clone() ); + _scissor.set( x, y, width, height ); - } + } - return this; + state.scissor( _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ).round() ); - } + }; - clone() { + /** + * Returns `true` if the scissor test is enabled. + * + * @return {boolean} Whether the scissor test is enabled or not. + */ + this.getScissorTest = function () { - return new this.constructor().copy( this ); + return _scissorTest; - } + }; -} + /** + * Enable or disable the scissor test. When this is enabled, only the pixels + * within the defined scissor area will be affected by further renderer + * actions. + * + * @param {boolean} boolean - Whether the scissor test is enabled or not. + */ + this.setScissorTest = function ( boolean ) { -class InstancedInterleavedBuffer extends InterleavedBuffer { + state.setScissorTest( _scissorTest = boolean ); - constructor( array, stride, meshPerAttribute = 1 ) { + }; - super( array, stride ); + /** + * Sets a custom opaque sort function for the render lists. Pass `null` + * to use the default `painterSortStable` function. + * + * @param {?Function} method - The opaque sort function. + */ + this.setOpaqueSort = function ( method ) { - this.isInstancedInterleavedBuffer = true; + _opaqueSort = method; - this.meshPerAttribute = meshPerAttribute; + }; - } + /** + * Sets a custom transparent sort function for the render lists. Pass `null` + * to use the default `reversePainterSortStable` function. + * + * @param {?Function} method - The opaque sort function. + */ + this.setTransparentSort = function ( method ) { - copy( source ) { + _transparentSort = method; - super.copy( source ); + }; - this.meshPerAttribute = source.meshPerAttribute; + // Clearing - return this; + /** + * Returns the clear color. + * + * @param {Color} target - The method writes the result in this target object. + * @return {Color} The clear color. + */ + this.getClearColor = function ( target ) { - } + return target.copy( background.getClearColor() ); - clone( data ) { + }; - const ib = super.clone( data ); + /** + * Sets the clear color and alpha. + * + * @param {Color} color - The clear color. + * @param {number} [alpha=1] - The clear alpha. + */ + this.setClearColor = function () { - ib.meshPerAttribute = this.meshPerAttribute; + background.setClearColor( ...arguments ); - return ib; + }; - } + /** + * Returns the clear alpha. Ranges within `[0,1]`. + * + * @return {number} The clear alpha. + */ + this.getClearAlpha = function () { - toJSON( data ) { + return background.getClearAlpha(); - const json = super.toJSON( data ); + }; - json.isInstancedInterleavedBuffer = true; - json.meshPerAttribute = this.meshPerAttribute; + /** + * Sets the clear alpha. + * + * @param {number} alpha - The clear alpha. + */ + this.setClearAlpha = function () { - return json; + background.setClearAlpha( ...arguments ); - } + }; -} + /** + * Tells the renderer to clear its color, depth or stencil drawing buffer(s). + * This method initializes the buffers to the current clear color values. + * + * @param {boolean} [color=true] - Whether the color buffer should be cleared or not. + * @param {boolean} [depth=true] - Whether the depth buffer should be cleared or not. + * @param {boolean} [stencil=true] - Whether the stencil buffer should be cleared or not. + */ + this.clear = function ( color = true, depth = true, stencil = true ) { -class GLBufferAttribute { + let bits = 0; - constructor( buffer, type, itemSize, elementSize, count ) { + if ( color ) { - this.isGLBufferAttribute = true; + // check if we're trying to clear an integer target + let isIntegerFormat = false; + if ( _currentRenderTarget !== null ) { - this.name = ''; + const targetFormat = _currentRenderTarget.texture.format; + isIntegerFormat = targetFormat === RGBAIntegerFormat || + targetFormat === RGIntegerFormat || + targetFormat === RedIntegerFormat; - this.buffer = buffer; - this.type = type; - this.itemSize = itemSize; - this.elementSize = elementSize; - this.count = count; + } - this.version = 0; + // use the appropriate clear functions to clear the target if it's a signed + // or unsigned integer target + if ( isIntegerFormat ) { - } + const targetType = _currentRenderTarget.texture.type; + const isUnsignedType = targetType === UnsignedByteType || + targetType === UnsignedIntType || + targetType === UnsignedShortType || + targetType === UnsignedInt248Type || + targetType === UnsignedShort4444Type || + targetType === UnsignedShort5551Type; - set needsUpdate( value ) { + const clearColor = background.getClearColor(); + const a = background.getClearAlpha(); + const r = clearColor.r; + const g = clearColor.g; + const b = clearColor.b; - if ( value === true ) this.version ++; + if ( isUnsignedType ) { - } + uintClearColor[ 0 ] = r; + uintClearColor[ 1 ] = g; + uintClearColor[ 2 ] = b; + uintClearColor[ 3 ] = a; + _gl.clearBufferuiv( _gl.COLOR, 0, uintClearColor ); - setBuffer( buffer ) { + } else { - this.buffer = buffer; + intClearColor[ 0 ] = r; + intClearColor[ 1 ] = g; + intClearColor[ 2 ] = b; + intClearColor[ 3 ] = a; + _gl.clearBufferiv( _gl.COLOR, 0, intClearColor ); - return this; + } - } + } else { - setType( type, elementSize ) { + bits |= _gl.COLOR_BUFFER_BIT; - this.type = type; - this.elementSize = elementSize; + } - return this; + } - } + if ( depth ) { - setItemSize( itemSize ) { + bits |= _gl.DEPTH_BUFFER_BIT; - this.itemSize = itemSize; + } - return this; + if ( stencil ) { - } + bits |= _gl.STENCIL_BUFFER_BIT; + this.state.buffers.stencil.setMask( 0xffffffff ); - setCount( count ) { + } - this.count = count; + _gl.clear( bits ); - return this; + }; - } + /** + * Clears the color buffer. Equivalent to calling `renderer.clear( true, false, false )`. + */ + this.clearColor = function () { -} + this.clear( true, false, false ); -class Raycaster { + }; - constructor( origin, direction, near = 0, far = Infinity ) { + /** + * Clears the depth buffer. Equivalent to calling `renderer.clear( false, true, false )`. + */ + this.clearDepth = function () { - this.ray = new Ray( origin, direction ); - // direction is assumed to be normalized (for accurate distance calculations) + this.clear( false, true, false ); - this.near = near; - this.far = far; - this.camera = null; - this.layers = new Layers(); + }; + + /** + * Clears the stencil buffer. Equivalent to calling `renderer.clear( false, false, true )`. + */ + this.clearStencil = function () { + + this.clear( false, false, true ); - this.params = { - Mesh: {}, - Line: { threshold: 1 }, - LOD: {}, - Points: { threshold: 1 }, - Sprite: {} }; - } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + this.dispose = function () { - set( origin, direction ) { + canvas.removeEventListener( 'webglcontextlost', onContextLost, false ); + canvas.removeEventListener( 'webglcontextrestored', onContextRestore, false ); + canvas.removeEventListener( 'webglcontextcreationerror', onContextCreationError, false ); - // direction is assumed to be normalized (for accurate distance calculations) + background.dispose(); + renderLists.dispose(); + renderStates.dispose(); + properties.dispose(); + cubemaps.dispose(); + cubeuvmaps.dispose(); + objects.dispose(); + bindingStates.dispose(); + uniformsGroups.dispose(); + programCache.dispose(); - this.ray.set( origin, direction ); + xr.dispose(); - } + xr.removeEventListener( 'sessionstart', onXRSessionStart ); + xr.removeEventListener( 'sessionend', onXRSessionEnd ); - setFromCamera( coords, camera ) { + animation.stop(); - if ( camera.isPerspectiveCamera ) { + }; - this.ray.origin.setFromMatrixPosition( camera.matrixWorld ); - this.ray.direction.set( coords.x, coords.y, 0.5 ).unproject( camera ).sub( this.ray.origin ).normalize(); - this.camera = camera; + // Events - } else if ( camera.isOrthographicCamera ) { + function onContextLost( event ) { - this.ray.origin.set( coords.x, coords.y, ( camera.near + camera.far ) / ( camera.near - camera.far ) ).unproject( camera ); // set origin in plane of camera - this.ray.direction.set( 0, 0, - 1 ).transformDirection( camera.matrixWorld ); - this.camera = camera; + event.preventDefault(); - } else { + console.log( 'THREE.WebGLRenderer: Context Lost.' ); - console.error( 'THREE.Raycaster: Unsupported camera type: ' + camera.type ); + _isContextLost = true; } - } + function onContextRestore( /* event */ ) { - intersectObject( object, recursive = true, intersects = [] ) { + console.log( 'THREE.WebGLRenderer: Context Restored.' ); - intersectObject( object, this, intersects, recursive ); + _isContextLost = false; - intersects.sort( ascSort ); + const infoAutoReset = info.autoReset; + const shadowMapEnabled = shadowMap.enabled; + const shadowMapAutoUpdate = shadowMap.autoUpdate; + const shadowMapNeedsUpdate = shadowMap.needsUpdate; + const shadowMapType = shadowMap.type; - return intersects; + initGLContext(); - } + info.autoReset = infoAutoReset; + shadowMap.enabled = shadowMapEnabled; + shadowMap.autoUpdate = shadowMapAutoUpdate; + shadowMap.needsUpdate = shadowMapNeedsUpdate; + shadowMap.type = shadowMapType; - intersectObjects( objects, recursive = true, intersects = [] ) { + } - for ( let i = 0, l = objects.length; i < l; i ++ ) { + function onContextCreationError( event ) { - intersectObject( objects[ i ], this, intersects, recursive ); + console.error( 'THREE.WebGLRenderer: A WebGL context could not be created. Reason: ', event.statusMessage ); } - intersects.sort( ascSort ); - - return intersects; + function onMaterialDispose( event ) { - } + const material = event.target; -} + material.removeEventListener( 'dispose', onMaterialDispose ); -function ascSort( a, b ) { + deallocateMaterial( material ); - return a.distance - b.distance; + } -} + // Buffer deallocation -function intersectObject( object, raycaster, intersects, recursive ) { + function deallocateMaterial( material ) { - if ( object.layers.test( raycaster.layers ) ) { + releaseMaterialProgramReferences( material ); - object.raycast( raycaster, intersects ); + properties.remove( material ); - } + } - if ( recursive === true ) { - const children = object.children; + function releaseMaterialProgramReferences( material ) { - for ( let i = 0, l = children.length; i < l; i ++ ) { + const programs = properties.get( material ).programs; - intersectObject( children[ i ], raycaster, intersects, true ); + if ( programs !== undefined ) { - } + programs.forEach( function ( program ) { - } + programCache.releaseProgram( program ); -} + } ); -/** - * Ref: https://en.wikipedia.org/wiki/Spherical_coordinate_system - * - * The polar angle (phi) is measured from the positive y-axis. The positive y-axis is up. - * The azimuthal angle (theta) is measured from the positive z-axis. - */ + if ( material.isShaderMaterial ) { + programCache.releaseShaderCache( material ); -class Spherical { + } - constructor( radius = 1, phi = 0, theta = 0 ) { + } - this.radius = radius; - this.phi = phi; // polar angle - this.theta = theta; // azimuthal angle + } - return this; + // Buffer rendering - } + this.renderBufferDirect = function ( camera, scene, geometry, material, object, group ) { - set( radius, phi, theta ) { + if ( scene === null ) scene = _emptyScene; // renderBufferDirect second parameter used to be fog (could be null) - this.radius = radius; - this.phi = phi; - this.theta = theta; + const frontFaceCW = ( object.isMesh && object.matrixWorld.determinant() < 0 ); - return this; + const program = setProgram( camera, scene, geometry, material, object ); - } + state.setMaterial( material, frontFaceCW ); - copy( other ) { + // - this.radius = other.radius; - this.phi = other.phi; - this.theta = other.theta; + let index = geometry.index; + let rangeFactor = 1; - return this; + if ( material.wireframe === true ) { - } + index = geometries.getWireframeAttribute( geometry ); - // restrict phi to be between EPS and PI-EPS - makeSafe() { + if ( index === undefined ) return; - const EPS = 0.000001; - this.phi = Math.max( EPS, Math.min( Math.PI - EPS, this.phi ) ); + rangeFactor = 2; - return this; + } - } + // - setFromVector3( v ) { + const drawRange = geometry.drawRange; + const position = geometry.attributes.position; - return this.setFromCartesianCoords( v.x, v.y, v.z ); + let drawStart = drawRange.start * rangeFactor; + let drawEnd = ( drawRange.start + drawRange.count ) * rangeFactor; - } + if ( group !== null ) { - setFromCartesianCoords( x, y, z ) { + drawStart = Math.max( drawStart, group.start * rangeFactor ); + drawEnd = Math.min( drawEnd, ( group.start + group.count ) * rangeFactor ); - this.radius = Math.sqrt( x * x + y * y + z * z ); + } - if ( this.radius === 0 ) { + if ( index !== null ) { - this.theta = 0; - this.phi = 0; + drawStart = Math.max( drawStart, 0 ); + drawEnd = Math.min( drawEnd, index.count ); - } else { + } else if ( position !== undefined && position !== null ) { - this.theta = Math.atan2( x, z ); - this.phi = Math.acos( clamp( y / this.radius, - 1, 1 ) ); + drawStart = Math.max( drawStart, 0 ); + drawEnd = Math.min( drawEnd, position.count ); - } + } - return this; + const drawCount = drawEnd - drawStart; - } + if ( drawCount < 0 || drawCount === Infinity ) return; - clone() { + // - return new this.constructor().copy( this ); + bindingStates.setup( object, material, program, geometry, index ); - } + let attribute; + let renderer = bufferRenderer; -} + if ( index !== null ) { -/** - * Ref: https://en.wikipedia.org/wiki/Cylindrical_coordinate_system - */ + attribute = attributes.get( index ); -class Cylindrical { + renderer = indexedBufferRenderer; + renderer.setIndex( attribute ); - constructor( radius = 1, theta = 0, y = 0 ) { + } - this.radius = radius; // distance from the origin to a point in the x-z plane - this.theta = theta; // counterclockwise angle in the x-z plane measured in radians from the positive z-axis - this.y = y; // height above the x-z plane + // - return this; + if ( object.isMesh ) { - } + if ( material.wireframe === true ) { - set( radius, theta, y ) { + state.setLineWidth( material.wireframeLinewidth * getTargetPixelRatio() ); + renderer.setMode( _gl.LINES ); - this.radius = radius; - this.theta = theta; - this.y = y; + } else { - return this; + renderer.setMode( _gl.TRIANGLES ); - } + } - copy( other ) { + } else if ( object.isLine ) { - this.radius = other.radius; - this.theta = other.theta; - this.y = other.y; + let lineWidth = material.linewidth; - return this; + if ( lineWidth === undefined ) lineWidth = 1; // Not using Line*Material - } + state.setLineWidth( lineWidth * getTargetPixelRatio() ); - setFromVector3( v ) { + if ( object.isLineSegments ) { - return this.setFromCartesianCoords( v.x, v.y, v.z ); + renderer.setMode( _gl.LINES ); - } + } else if ( object.isLineLoop ) { - setFromCartesianCoords( x, y, z ) { + renderer.setMode( _gl.LINE_LOOP ); - this.radius = Math.sqrt( x * x + z * z ); - this.theta = Math.atan2( x, z ); - this.y = y; + } else { - return this; + renderer.setMode( _gl.LINE_STRIP ); - } + } - clone() { + } else if ( object.isPoints ) { - return new this.constructor().copy( this ); + renderer.setMode( _gl.POINTS ); - } + } else if ( object.isSprite ) { -} + renderer.setMode( _gl.TRIANGLES ); -const _vector$4 = /*@__PURE__*/ new Vector2(); + } -class Box2 { + if ( object.isBatchedMesh ) { - constructor( min = new Vector2( + Infinity, + Infinity ), max = new Vector2( - Infinity, - Infinity ) ) { + if ( object._multiDrawInstances !== null ) { - this.isBox2 = true; + // @deprecated, r174 + warnOnce( 'THREE.WebGLRenderer: renderMultiDrawInstances has been deprecated and will be removed in r184. Append to renderMultiDraw arguments and use indirection.' ); + renderer.renderMultiDrawInstances( object._multiDrawStarts, object._multiDrawCounts, object._multiDrawCount, object._multiDrawInstances ); - this.min = min; - this.max = max; + } else { - } + if ( ! extensions.get( 'WEBGL_multi_draw' ) ) { - set( min, max ) { + const starts = object._multiDrawStarts; + const counts = object._multiDrawCounts; + const drawCount = object._multiDrawCount; + const bytesPerElement = index ? attributes.get( index ).bytesPerElement : 1; + const uniforms = properties.get( material ).currentProgram.getUniforms(); + for ( let i = 0; i < drawCount; i ++ ) { - this.min.copy( min ); - this.max.copy( max ); + uniforms.setValue( _gl, '_gl_DrawID', i ); + renderer.render( starts[ i ] / bytesPerElement, counts[ i ] ); - return this; + } - } + } else { - setFromPoints( points ) { + renderer.renderMultiDraw( object._multiDrawStarts, object._multiDrawCounts, object._multiDrawCount ); - this.makeEmpty(); + } - for ( let i = 0, il = points.length; i < il; i ++ ) { + } - this.expandByPoint( points[ i ] ); + } else if ( object.isInstancedMesh ) { - } + renderer.renderInstances( drawStart, drawCount, object.count ); - return this; + } else if ( geometry.isInstancedBufferGeometry ) { - } + const maxInstanceCount = geometry._maxInstanceCount !== undefined ? geometry._maxInstanceCount : Infinity; + const instanceCount = Math.min( geometry.instanceCount, maxInstanceCount ); - setFromCenterAndSize( center, size ) { + renderer.renderInstances( drawStart, drawCount, instanceCount ); - const halfSize = _vector$4.copy( size ).multiplyScalar( 0.5 ); - this.min.copy( center ).sub( halfSize ); - this.max.copy( center ).add( halfSize ); + } else { - return this; + renderer.render( drawStart, drawCount ); - } + } - clone() { + }; - return new this.constructor().copy( this ); + // Compile - } + function prepareMaterial( material, scene, object ) { - copy( box ) { + if ( material.transparent === true && material.side === DoubleSide && material.forceSinglePass === false ) { - this.min.copy( box.min ); - this.max.copy( box.max ); + material.side = BackSide; + material.needsUpdate = true; + getProgram( material, scene, object ); - return this; + material.side = FrontSide; + material.needsUpdate = true; + getProgram( material, scene, object ); - } + material.side = DoubleSide; - makeEmpty() { + } else { - this.min.x = this.min.y = + Infinity; - this.max.x = this.max.y = - Infinity; + getProgram( material, scene, object ); - return this; + } - } + } - isEmpty() { + /** + * Compiles all materials in the scene with the camera. This is useful to precompile shaders + * before the first rendering. If you want to add a 3D object to an existing scene, use the third + * optional parameter for applying the target scene. + * + * Note that the (target) scene's lighting and environment must be configured before calling this method. + * + * @param {Object3D} scene - The scene or another type of 3D object to precompile. + * @param {Camera} camera - The camera. + * @param {?Scene} [targetScene=null] - The target scene. + * @return {Set} The precompiled materials. + */ + this.compile = function ( scene, camera, targetScene = null ) { - // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes + if ( targetScene === null ) targetScene = scene; - return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y ); + currentRenderState = renderStates.get( targetScene ); + currentRenderState.init( camera ); - } + renderStateStack.push( currentRenderState ); - getCenter( target ) { + // gather lights from both the target scene and the new object that will be added to the scene. - return this.isEmpty() ? target.set( 0, 0 ) : target.addVectors( this.min, this.max ).multiplyScalar( 0.5 ); + targetScene.traverseVisible( function ( object ) { - } + if ( object.isLight && object.layers.test( camera.layers ) ) { - getSize( target ) { + currentRenderState.pushLight( object ); - return this.isEmpty() ? target.set( 0, 0 ) : target.subVectors( this.max, this.min ); + if ( object.castShadow ) { - } + currentRenderState.pushShadow( object ); - expandByPoint( point ) { + } - this.min.min( point ); - this.max.max( point ); + } - return this; + } ); - } + if ( scene !== targetScene ) { - expandByVector( vector ) { + scene.traverseVisible( function ( object ) { - this.min.sub( vector ); - this.max.add( vector ); + if ( object.isLight && object.layers.test( camera.layers ) ) { - return this; + currentRenderState.pushLight( object ); - } + if ( object.castShadow ) { - expandByScalar( scalar ) { + currentRenderState.pushShadow( object ); - this.min.addScalar( - scalar ); - this.max.addScalar( scalar ); + } - return this; + } - } + } ); - containsPoint( point ) { + } - return point.x < this.min.x || point.x > this.max.x || - point.y < this.min.y || point.y > this.max.y ? false : true; + currentRenderState.setupLights(); - } + // Only initialize materials in the new scene, not the targetScene. - containsBox( box ) { + const materials = new Set(); - return this.min.x <= box.min.x && box.max.x <= this.max.x && - this.min.y <= box.min.y && box.max.y <= this.max.y; + scene.traverse( function ( object ) { - } + if ( ! ( object.isMesh || object.isPoints || object.isLine || object.isSprite ) ) { - getParameter( point, target ) { + return; - // This can potentially have a divide by zero if the box - // has a size dimension of 0. + } - return target.set( - ( point.x - this.min.x ) / ( this.max.x - this.min.x ), - ( point.y - this.min.y ) / ( this.max.y - this.min.y ) - ); + const material = object.material; - } + if ( material ) { - intersectsBox( box ) { + if ( Array.isArray( material ) ) { - // using 4 splitting planes to rule out intersections + for ( let i = 0; i < material.length; i ++ ) { - return box.max.x < this.min.x || box.min.x > this.max.x || - box.max.y < this.min.y || box.min.y > this.max.y ? false : true; + const material2 = material[ i ]; - } + prepareMaterial( material2, targetScene, object ); + materials.add( material2 ); - clampPoint( point, target ) { + } - return target.copy( point ).clamp( this.min, this.max ); + } else { - } + prepareMaterial( material, targetScene, object ); + materials.add( material ); - distanceToPoint( point ) { + } - return this.clampPoint( point, _vector$4 ).distanceTo( point ); + } - } + } ); - intersect( box ) { + currentRenderState = renderStateStack.pop(); - this.min.max( box.min ); - this.max.min( box.max ); + return materials; - if ( this.isEmpty() ) this.makeEmpty(); + }; - return this; + // compileAsync - } + /** + * Asynchronous version of {@link WebGLRenderer#compile}. + * + * This method makes use of the `KHR_parallel_shader_compile` WebGL extension. Hence, + * it is recommended to use this version of `compile()` whenever possible. + * + * @async + * @param {Object3D} scene - The scene or another type of 3D object to precompile. + * @param {Camera} camera - The camera. + * @param {?Scene} [targetScene=null] - The target scene. + * @return {Promise} A Promise that resolves when the given scene can be rendered without unnecessary stalling due to shader compilation. + */ + this.compileAsync = function ( scene, camera, targetScene = null ) { - union( box ) { + const materials = this.compile( scene, camera, targetScene ); - this.min.min( box.min ); - this.max.max( box.max ); + // Wait for all the materials in the new object to indicate that they're + // ready to be used before resolving the promise. - return this; + return new Promise( ( resolve ) => { - } + function checkMaterialsReady() { - translate( offset ) { + materials.forEach( function ( material ) { - this.min.add( offset ); - this.max.add( offset ); + const materialProperties = properties.get( material ); + const program = materialProperties.currentProgram; - return this; + if ( program.isReady() ) { - } + // remove any programs that report they're ready to use from the list + materials.delete( material ); - equals( box ) { + } - return box.min.equals( this.min ) && box.max.equals( this.max ); + } ); - } + // once the list of compiling materials is empty, call the callback -} + if ( materials.size === 0 ) { -const _startP = /*@__PURE__*/ new Vector3(); -const _startEnd = /*@__PURE__*/ new Vector3(); + resolve( scene ); + return; -class Line3 { + } - constructor( start = new Vector3(), end = new Vector3() ) { + // if some materials are still not ready, wait a bit and check again - this.start = start; - this.end = end; + setTimeout( checkMaterialsReady, 10 ); - } + } - set( start, end ) { + if ( extensions.get( 'KHR_parallel_shader_compile' ) !== null ) { - this.start.copy( start ); - this.end.copy( end ); + // If we can check the compilation status of the materials without + // blocking then do so right away. - return this; + checkMaterialsReady(); - } + } else { - copy( line ) { + // Otherwise start by waiting a bit to give the materials we just + // initialized a chance to finish. - this.start.copy( line.start ); - this.end.copy( line.end ); + setTimeout( checkMaterialsReady, 10 ); - return this; + } - } + } ); - getCenter( target ) { + }; - return target.addVectors( this.start, this.end ).multiplyScalar( 0.5 ); + // Animation Loop - } + let onAnimationFrameCallback = null; - delta( target ) { + function onAnimationFrame( time ) { - return target.subVectors( this.end, this.start ); + if ( onAnimationFrameCallback ) onAnimationFrameCallback( time ); - } + } - distanceSq() { + function onXRSessionStart() { - return this.start.distanceToSquared( this.end ); + animation.stop(); - } + } - distance() { + function onXRSessionEnd() { - return this.start.distanceTo( this.end ); + animation.start(); - } + } - at( t, target ) { + const animation = new WebGLAnimation(); + animation.setAnimationLoop( onAnimationFrame ); - return this.delta( target ).multiplyScalar( t ).add( this.start ); + if ( typeof self !== 'undefined' ) animation.setContext( self ); - } + this.setAnimationLoop = function ( callback ) { - closestPointToPointParameter( point, clampToLine ) { + onAnimationFrameCallback = callback; + xr.setAnimationLoop( callback ); - _startP.subVectors( point, this.start ); - _startEnd.subVectors( this.end, this.start ); + ( callback === null ) ? animation.stop() : animation.start(); - const startEnd2 = _startEnd.dot( _startEnd ); - const startEnd_startP = _startEnd.dot( _startP ); + }; - let t = startEnd_startP / startEnd2; + xr.addEventListener( 'sessionstart', onXRSessionStart ); + xr.addEventListener( 'sessionend', onXRSessionEnd ); - if ( clampToLine ) { + // Rendering - t = clamp( t, 0, 1 ); + /** + * Renders the given scene (or other type of 3D object) using the given camera. + * + * The render is done to a previously specified render target set by calling {@link WebGLRenderer#setRenderTarget} + * or to the canvas as usual. + * + * By default render buffers are cleared before rendering but you can prevent + * this by setting the property `autoClear` to `false`. If you want to prevent + * only certain buffers being cleared you can `autoClearColor`, `autoClearDepth` + * or `autoClearStencil` to `false`. To force a clear, use {@link WebGLRenderer#clear}. + * + * @param {Object3D} scene - The scene to render. + * @param {Camera} camera - The camera. + */ + this.render = function ( scene, camera ) { - } + if ( camera !== undefined && camera.isCamera !== true ) { - return t; + console.error( 'THREE.WebGLRenderer.render: camera is not an instance of THREE.Camera.' ); + return; - } + } - closestPointToPoint( point, clampToLine, target ) { + if ( _isContextLost === true ) return; - const t = this.closestPointToPointParameter( point, clampToLine ); + // update scene graph - return this.delta( target ).multiplyScalar( t ).add( this.start ); + if ( scene.matrixWorldAutoUpdate === true ) scene.updateMatrixWorld(); - } + // update camera matrices and frustum - applyMatrix4( matrix ) { + if ( camera.parent === null && camera.matrixWorldAutoUpdate === true ) camera.updateMatrixWorld(); - this.start.applyMatrix4( matrix ); - this.end.applyMatrix4( matrix ); + if ( xr.enabled === true && xr.isPresenting === true ) { - return this; + if ( xr.cameraAutoUpdate === true ) xr.updateCamera( camera ); - } + camera = xr.getCamera(); // use XR camera for rendering - equals( line ) { + } - return line.start.equals( this.start ) && line.end.equals( this.end ); + // + if ( scene.isScene === true ) scene.onBeforeRender( _this, scene, camera, _currentRenderTarget ); - } + currentRenderState = renderStates.get( scene, renderStateStack.length ); + currentRenderState.init( camera ); - clone() { + renderStateStack.push( currentRenderState ); - return new this.constructor().copy( this ); + _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); + _frustum.setFromProjectionMatrix( _projScreenMatrix ); - } + _localClippingEnabled = this.localClippingEnabled; + _clippingEnabled = clipping.init( this.clippingPlanes, _localClippingEnabled ); -} + currentRenderList = renderLists.get( scene, renderListStack.length ); + currentRenderList.init(); -const _vector$3 = /*@__PURE__*/ new Vector3(); + renderListStack.push( currentRenderList ); -class SpotLightHelper extends Object3D { + if ( xr.enabled === true && xr.isPresenting === true ) { - constructor( light, color ) { + const depthSensingMesh = _this.xr.getDepthSensingMesh(); - super(); + if ( depthSensingMesh !== null ) { - this.light = light; + projectObject( depthSensingMesh, camera, - Infinity, _this.sortObjects ); - this.matrix = light.matrixWorld; - this.matrixAutoUpdate = false; + } - this.color = color; + } - this.type = 'SpotLightHelper'; + projectObject( scene, camera, 0, _this.sortObjects ); - const geometry = new BufferGeometry(); + currentRenderList.finish(); - const positions = [ - 0, 0, 0, 0, 0, 1, - 0, 0, 0, 1, 0, 1, - 0, 0, 0, - 1, 0, 1, - 0, 0, 0, 0, 1, 1, - 0, 0, 0, 0, - 1, 1 - ]; + if ( _this.sortObjects === true ) { - for ( let i = 0, j = 1, l = 32; i < l; i ++, j ++ ) { + currentRenderList.sort( _opaqueSort, _transparentSort ); - const p1 = ( i / l ) * Math.PI * 2; - const p2 = ( j / l ) * Math.PI * 2; + } - positions.push( - Math.cos( p1 ), Math.sin( p1 ), 1, - Math.cos( p2 ), Math.sin( p2 ), 1 - ); + _renderBackground = xr.enabled === false || xr.isPresenting === false || xr.hasDepthSensing() === false; + if ( _renderBackground ) { - } + background.addToRenderList( currentRenderList, scene ); - geometry.setAttribute( 'position', new Float32BufferAttribute( positions, 3 ) ); + } - const material = new LineBasicMaterial( { fog: false, toneMapped: false } ); + // - this.cone = new LineSegments( geometry, material ); - this.add( this.cone ); + this.info.render.frame ++; - this.update(); + if ( _clippingEnabled === true ) clipping.beginShadows(); - } + const shadowsArray = currentRenderState.state.shadowsArray; - dispose() { + shadowMap.render( shadowsArray, scene, camera ); - this.cone.geometry.dispose(); - this.cone.material.dispose(); + if ( _clippingEnabled === true ) clipping.endShadows(); - } + // - update() { + if ( this.info.autoReset === true ) this.info.reset(); - this.light.updateWorldMatrix( true, false ); - this.light.target.updateWorldMatrix( true, false ); + // render scene - const coneLength = this.light.distance ? this.light.distance : 1000; - const coneWidth = coneLength * Math.tan( this.light.angle ); + const opaqueObjects = currentRenderList.opaque; + const transmissiveObjects = currentRenderList.transmissive; - this.cone.scale.set( coneWidth, coneWidth, coneLength ); + currentRenderState.setupLights(); - _vector$3.setFromMatrixPosition( this.light.target.matrixWorld ); + if ( camera.isArrayCamera ) { - this.cone.lookAt( _vector$3 ); + const cameras = camera.cameras; - if ( this.color !== undefined ) { + if ( transmissiveObjects.length > 0 ) { - this.cone.material.color.set( this.color ); + for ( let i = 0, l = cameras.length; i < l; i ++ ) { - } else { + const camera2 = cameras[ i ]; - this.cone.material.color.copy( this.light.color ); + renderTransmissionPass( opaqueObjects, transmissiveObjects, scene, camera2 ); - } + } - } + } -} + if ( _renderBackground ) background.render( scene ); -const _vector$2 = /*@__PURE__*/ new Vector3(); -const _boneMatrix = /*@__PURE__*/ new Matrix4(); -const _matrixWorldInv = /*@__PURE__*/ new Matrix4(); + for ( let i = 0, l = cameras.length; i < l; i ++ ) { + const camera2 = cameras[ i ]; -class SkeletonHelper extends LineSegments { + renderScene( currentRenderList, scene, camera2, camera2.viewport ); - constructor( object ) { + } - const bones = getBoneList( object ); + } else { - const geometry = new BufferGeometry(); + if ( transmissiveObjects.length > 0 ) renderTransmissionPass( opaqueObjects, transmissiveObjects, scene, camera ); - const vertices = []; - const colors = []; + if ( _renderBackground ) background.render( scene ); - const color1 = new Color( 0, 0, 1 ); - const color2 = new Color( 0, 1, 0 ); + renderScene( currentRenderList, scene, camera ); - for ( let i = 0; i < bones.length; i ++ ) { + } - const bone = bones[ i ]; + // - if ( bone.parent && bone.parent.isBone ) { + if ( _currentRenderTarget !== null && _currentActiveMipmapLevel === 0 ) { - vertices.push( 0, 0, 0 ); - vertices.push( 0, 0, 0 ); - colors.push( color1.r, color1.g, color1.b ); - colors.push( color2.r, color2.g, color2.b ); + // resolve multisample renderbuffers to a single-sample texture if necessary - } + textures.updateMultisampleRenderTarget( _currentRenderTarget ); - } + // Generate mipmap if we're using any kind of mipmap filtering - geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - geometry.setAttribute( 'color', new Float32BufferAttribute( colors, 3 ) ); + textures.updateRenderTargetMipmap( _currentRenderTarget ); - const material = new LineBasicMaterial( { vertexColors: true, depthTest: false, depthWrite: false, toneMapped: false, transparent: true } ); + } - super( geometry, material ); + // - this.isSkeletonHelper = true; + if ( scene.isScene === true ) scene.onAfterRender( _this, scene, camera ); - this.type = 'SkeletonHelper'; + // _gl.finish(); - this.root = object; - this.bones = bones; + bindingStates.resetDefaultState(); + _currentMaterialId = -1; + _currentCamera = null; - this.matrix = object.matrixWorld; - this.matrixAutoUpdate = false; + renderStateStack.pop(); - } + if ( renderStateStack.length > 0 ) { - updateMatrixWorld( force ) { + currentRenderState = renderStateStack[ renderStateStack.length - 1 ]; - const bones = this.bones; + if ( _clippingEnabled === true ) clipping.setGlobalState( _this.clippingPlanes, currentRenderState.state.camera ); - const geometry = this.geometry; - const position = geometry.getAttribute( 'position' ); + } else { - _matrixWorldInv.copy( this.root.matrixWorld ).invert(); + currentRenderState = null; - for ( let i = 0, j = 0; i < bones.length; i ++ ) { + } - const bone = bones[ i ]; + renderListStack.pop(); - if ( bone.parent && bone.parent.isBone ) { + if ( renderListStack.length > 0 ) { - _boneMatrix.multiplyMatrices( _matrixWorldInv, bone.matrixWorld ); - _vector$2.setFromMatrixPosition( _boneMatrix ); - position.setXYZ( j, _vector$2.x, _vector$2.y, _vector$2.z ); + currentRenderList = renderListStack[ renderListStack.length - 1 ]; - _boneMatrix.multiplyMatrices( _matrixWorldInv, bone.parent.matrixWorld ); - _vector$2.setFromMatrixPosition( _boneMatrix ); - position.setXYZ( j + 1, _vector$2.x, _vector$2.y, _vector$2.z ); + } else { - j += 2; + currentRenderList = null; } - } - - geometry.getAttribute( 'position' ).needsUpdate = true; + }; - super.updateMatrixWorld( force ); + function projectObject( object, camera, groupOrder, sortObjects ) { - } + if ( object.visible === false ) return; - dispose() { + const visible = object.layers.test( camera.layers ); - this.geometry.dispose(); - this.material.dispose(); + if ( visible ) { - } + if ( object.isGroup ) { -} + groupOrder = object.renderOrder; + } else if ( object.isLOD ) { -function getBoneList( object ) { + if ( object.autoUpdate === true ) object.update( camera ); - const boneList = []; + } else if ( object.isLight ) { - if ( object.isBone === true ) { + currentRenderState.pushLight( object ); - boneList.push( object ); + if ( object.castShadow ) { - } + currentRenderState.pushShadow( object ); - for ( let i = 0; i < object.children.length; i ++ ) { + } - boneList.push.apply( boneList, getBoneList( object.children[ i ] ) ); + } else if ( object.isSprite ) { - } + if ( ! object.frustumCulled || _frustum.intersectsSprite( object ) ) { - return boneList; + if ( sortObjects ) { -} + _vector4.setFromMatrixPosition( object.matrixWorld ) + .applyMatrix4( _projScreenMatrix ); -class PointLightHelper extends Mesh { + } - constructor( light, sphereSize, color ) { + const geometry = objects.update( object ); + const material = object.material; - const geometry = new SphereGeometry( sphereSize, 4, 2 ); - const material = new MeshBasicMaterial( { wireframe: true, fog: false, toneMapped: false } ); + if ( material.visible ) { - super( geometry, material ); + currentRenderList.push( object, geometry, material, groupOrder, _vector4.z, null ); - this.light = light; + } - this.color = color; + } - this.type = 'PointLightHelper'; + } else if ( object.isMesh || object.isLine || object.isPoints ) { - this.matrix = this.light.matrixWorld; - this.matrixAutoUpdate = false; + if ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) { - this.update(); + const geometry = objects.update( object ); + const material = object.material; + if ( sortObjects ) { - /* - // TODO: delete this comment? - const distanceGeometry = new THREE.IcosahedronGeometry( 1, 2 ); - const distanceMaterial = new THREE.MeshBasicMaterial( { color: hexColor, fog: false, wireframe: true, opacity: 0.1, transparent: true } ); + if ( object.boundingSphere !== undefined ) { - this.lightSphere = new THREE.Mesh( bulbGeometry, bulbMaterial ); - this.lightDistance = new THREE.Mesh( distanceGeometry, distanceMaterial ); + if ( object.boundingSphere === null ) object.computeBoundingSphere(); + _vector4.copy( object.boundingSphere.center ); - const d = light.distance; + } else { - if ( d === 0.0 ) { + if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); + _vector4.copy( geometry.boundingSphere.center ); - this.lightDistance.visible = false; + } - } else { + _vector4 + .applyMatrix4( object.matrixWorld ) + .applyMatrix4( _projScreenMatrix ); - this.lightDistance.scale.set( d, d, d ); + } - } + if ( Array.isArray( material ) ) { - this.add( this.lightDistance ); - */ + const groups = geometry.groups; - } + for ( let i = 0, l = groups.length; i < l; i ++ ) { - dispose() { + const group = groups[ i ]; + const groupMaterial = material[ group.materialIndex ]; - this.geometry.dispose(); - this.material.dispose(); + if ( groupMaterial && groupMaterial.visible ) { - } + currentRenderList.push( object, geometry, groupMaterial, groupOrder, _vector4.z, group ); - update() { + } - this.light.updateWorldMatrix( true, false ); + } - if ( this.color !== undefined ) { + } else if ( material.visible ) { - this.material.color.set( this.color ); + currentRenderList.push( object, geometry, material, groupOrder, _vector4.z, null ); - } else { + } - this.material.color.copy( this.light.color ); + } - } + } - /* - const d = this.light.distance; + } - if ( d === 0.0 ) { + const children = object.children; - this.lightDistance.visible = false; + for ( let i = 0, l = children.length; i < l; i ++ ) { - } else { + projectObject( children[ i ], camera, groupOrder, sortObjects ); - this.lightDistance.visible = true; - this.lightDistance.scale.set( d, d, d ); + } } - */ - } + function renderScene( currentRenderList, scene, camera, viewport ) { -} + const opaqueObjects = currentRenderList.opaque; + const transmissiveObjects = currentRenderList.transmissive; + const transparentObjects = currentRenderList.transparent; -const _vector$1 = /*@__PURE__*/ new Vector3(); -const _color1 = /*@__PURE__*/ new Color(); -const _color2 = /*@__PURE__*/ new Color(); + currentRenderState.setupLightsView( camera ); -class HemisphereLightHelper extends Object3D { + if ( _clippingEnabled === true ) clipping.setGlobalState( _this.clippingPlanes, camera ); - constructor( light, size, color ) { + if ( viewport ) state.viewport( _currentViewport.copy( viewport ) ); - super(); + if ( opaqueObjects.length > 0 ) renderObjects( opaqueObjects, scene, camera ); + if ( transmissiveObjects.length > 0 ) renderObjects( transmissiveObjects, scene, camera ); + if ( transparentObjects.length > 0 ) renderObjects( transparentObjects, scene, camera ); - this.light = light; + // Ensure depth buffer writing is enabled so it can be cleared on next render - this.matrix = light.matrixWorld; - this.matrixAutoUpdate = false; + state.buffers.depth.setTest( true ); + state.buffers.depth.setMask( true ); + state.buffers.color.setMask( true ); - this.color = color; + state.setPolygonOffset( false ); - this.type = 'HemisphereLightHelper'; + } - const geometry = new OctahedronGeometry( size ); - geometry.rotateY( Math.PI * 0.5 ); + function renderTransmissionPass( opaqueObjects, transmissiveObjects, scene, camera ) { - this.material = new MeshBasicMaterial( { wireframe: true, fog: false, toneMapped: false } ); - if ( this.color === undefined ) this.material.vertexColors = true; + const overrideMaterial = scene.isScene === true ? scene.overrideMaterial : null; - const position = geometry.getAttribute( 'position' ); - const colors = new Float32Array( position.count * 3 ); + if ( overrideMaterial !== null ) { - geometry.setAttribute( 'color', new BufferAttribute( colors, 3 ) ); + return; - this.add( new Mesh( geometry, this.material ) ); + } - this.update(); + if ( currentRenderState.state.transmissionRenderTarget[ camera.id ] === undefined ) { - } + currentRenderState.state.transmissionRenderTarget[ camera.id ] = new WebGLRenderTarget( 1, 1, { + generateMipmaps: true, + type: ( extensions.has( 'EXT_color_buffer_half_float' ) || extensions.has( 'EXT_color_buffer_float' ) ) ? HalfFloatType : UnsignedByteType, + minFilter: LinearMipmapLinearFilter, + samples: 4, + stencilBuffer: stencil, + resolveDepthBuffer: false, + resolveStencilBuffer: false, + colorSpace: ColorManagement.workingColorSpace, + } ); - dispose() { + // debug - this.children[ 0 ].geometry.dispose(); - this.children[ 0 ].material.dispose(); + /* + const geometry = new PlaneGeometry(); + const material = new MeshBasicMaterial( { map: _transmissionRenderTarget.texture } ); - } + const mesh = new Mesh( geometry, material ); + scene.add( mesh ); + */ - update() { + } - const mesh = this.children[ 0 ]; + const transmissionRenderTarget = currentRenderState.state.transmissionRenderTarget[ camera.id ]; - if ( this.color !== undefined ) { + const activeViewport = camera.viewport || _currentViewport; + transmissionRenderTarget.setSize( activeViewport.z * _this.transmissionResolutionScale, activeViewport.w * _this.transmissionResolutionScale ); - this.material.color.set( this.color ); + // - } else { + const currentRenderTarget = _this.getRenderTarget(); + const currentActiveCubeFace = _this.getActiveCubeFace(); + const currentActiveMipmapLevel = _this.getActiveMipmapLevel(); - const colors = mesh.geometry.getAttribute( 'color' ); + _this.setRenderTarget( transmissionRenderTarget ); - _color1.copy( this.light.color ); - _color2.copy( this.light.groundColor ); + _this.getClearColor( _currentClearColor ); + _currentClearAlpha = _this.getClearAlpha(); + if ( _currentClearAlpha < 1 ) _this.setClearColor( 0xffffff, 0.5 ); - for ( let i = 0, l = colors.count; i < l; i ++ ) { + _this.clear(); - const color = ( i < ( l / 2 ) ) ? _color1 : _color2; + if ( _renderBackground ) background.render( scene ); - colors.setXYZ( i, color.r, color.g, color.b ); + // Turn off the features which can affect the frag color for opaque objects pass. + // Otherwise they are applied twice in opaque objects pass and transmission objects pass. + const currentToneMapping = _this.toneMapping; + _this.toneMapping = NoToneMapping; - } + // Remove viewport from camera to avoid nested render calls resetting viewport to it (e.g Reflector). + // Transmission render pass requires viewport to match the transmissionRenderTarget. + const currentCameraViewport = camera.viewport; + if ( camera.viewport !== undefined ) camera.viewport = undefined; - colors.needsUpdate = true; + currentRenderState.setupLightsView( camera ); - } + if ( _clippingEnabled === true ) clipping.setGlobalState( _this.clippingPlanes, camera ); - this.light.updateWorldMatrix( true, false ); + renderObjects( opaqueObjects, scene, camera ); - mesh.lookAt( _vector$1.setFromMatrixPosition( this.light.matrixWorld ).negate() ); + textures.updateMultisampleRenderTarget( transmissionRenderTarget ); + textures.updateRenderTargetMipmap( transmissionRenderTarget ); - } + if ( extensions.has( 'WEBGL_multisampled_render_to_texture' ) === false ) { // see #28131 -} + let renderTargetNeedsUpdate = false; -class GridHelper extends LineSegments { + for ( let i = 0, l = transmissiveObjects.length; i < l; i ++ ) { - constructor( size = 10, divisions = 10, color1 = 0x444444, color2 = 0x888888 ) { + const renderItem = transmissiveObjects[ i ]; - color1 = new Color( color1 ); - color2 = new Color( color2 ); + const object = renderItem.object; + const geometry = renderItem.geometry; + const material = renderItem.material; + const group = renderItem.group; - const center = divisions / 2; - const step = size / divisions; - const halfSize = size / 2; + if ( material.side === DoubleSide && object.layers.test( camera.layers ) ) { - const vertices = [], colors = []; + const currentSide = material.side; - for ( let i = 0, j = 0, k = - halfSize; i <= divisions; i ++, k += step ) { + material.side = BackSide; + material.needsUpdate = true; - vertices.push( - halfSize, 0, k, halfSize, 0, k ); - vertices.push( k, 0, - halfSize, k, 0, halfSize ); + renderObject( object, scene, camera, geometry, material, group ); - const color = i === center ? color1 : color2; + material.side = currentSide; + material.needsUpdate = true; - color.toArray( colors, j ); j += 3; - color.toArray( colors, j ); j += 3; - color.toArray( colors, j ); j += 3; - color.toArray( colors, j ); j += 3; + renderTargetNeedsUpdate = true; - } + } - const geometry = new BufferGeometry(); - geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - geometry.setAttribute( 'color', new Float32BufferAttribute( colors, 3 ) ); + } - const material = new LineBasicMaterial( { vertexColors: true, toneMapped: false } ); + if ( renderTargetNeedsUpdate === true ) { - super( geometry, material ); + textures.updateMultisampleRenderTarget( transmissionRenderTarget ); + textures.updateRenderTargetMipmap( transmissionRenderTarget ); - this.type = 'GridHelper'; + } - } + } - dispose() { + _this.setRenderTarget( currentRenderTarget, currentActiveCubeFace, currentActiveMipmapLevel ); - this.geometry.dispose(); - this.material.dispose(); + _this.setClearColor( _currentClearColor, _currentClearAlpha ); - } + if ( currentCameraViewport !== undefined ) camera.viewport = currentCameraViewport; -} + _this.toneMapping = currentToneMapping; -class PolarGridHelper extends LineSegments { + } - constructor( radius = 10, sectors = 16, rings = 8, divisions = 64, color1 = 0x444444, color2 = 0x888888 ) { + function renderObjects( renderList, scene, camera ) { - color1 = new Color( color1 ); - color2 = new Color( color2 ); + const overrideMaterial = scene.isScene === true ? scene.overrideMaterial : null; - const vertices = []; - const colors = []; + for ( let i = 0, l = renderList.length; i < l; i ++ ) { - // create the sectors + const renderItem = renderList[ i ]; - if ( sectors > 1 ) { + const object = renderItem.object; + const geometry = renderItem.geometry; + const group = renderItem.group; + let material = renderItem.material; - for ( let i = 0; i < sectors; i ++ ) { + if ( material.allowOverride === true && overrideMaterial !== null ) { - const v = ( i / sectors ) * ( Math.PI * 2 ); + material = overrideMaterial; - const x = Math.sin( v ) * radius; - const z = Math.cos( v ) * radius; + } - vertices.push( 0, 0, 0 ); - vertices.push( x, 0, z ); + if ( object.layers.test( camera.layers ) ) { - const color = ( i & 1 ) ? color1 : color2; + renderObject( object, scene, camera, geometry, material, group ); - colors.push( color.r, color.g, color.b ); - colors.push( color.r, color.g, color.b ); + } } } - // create the rings + function renderObject( object, scene, camera, geometry, material, group ) { - for ( let i = 0; i < rings; i ++ ) { + object.onBeforeRender( _this, scene, camera, geometry, material, group ); - const color = ( i & 1 ) ? color1 : color2; + object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld ); + object.normalMatrix.getNormalMatrix( object.modelViewMatrix ); - const r = radius - ( radius / rings * i ); + material.onBeforeRender( _this, scene, camera, geometry, object, group ); - for ( let j = 0; j < divisions; j ++ ) { + if ( material.transparent === true && material.side === DoubleSide && material.forceSinglePass === false ) { - // first vertex + material.side = BackSide; + material.needsUpdate = true; + _this.renderBufferDirect( camera, scene, geometry, material, object, group ); - let v = ( j / divisions ) * ( Math.PI * 2 ); + material.side = FrontSide; + material.needsUpdate = true; + _this.renderBufferDirect( camera, scene, geometry, material, object, group ); - let x = Math.sin( v ) * r; - let z = Math.cos( v ) * r; + material.side = DoubleSide; - vertices.push( x, 0, z ); - colors.push( color.r, color.g, color.b ); + } else { - // second vertex + _this.renderBufferDirect( camera, scene, geometry, material, object, group ); - v = ( ( j + 1 ) / divisions ) * ( Math.PI * 2 ); + } - x = Math.sin( v ) * r; - z = Math.cos( v ) * r; + object.onAfterRender( _this, scene, camera, geometry, material, group ); - vertices.push( x, 0, z ); - colors.push( color.r, color.g, color.b ); + } - } + function getProgram( material, scene, object ) { - } + if ( scene.isScene !== true ) scene = _emptyScene; // scene could be a Mesh, Line, Points, ... - const geometry = new BufferGeometry(); - geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - geometry.setAttribute( 'color', new Float32BufferAttribute( colors, 3 ) ); + const materialProperties = properties.get( material ); - const material = new LineBasicMaterial( { vertexColors: true, toneMapped: false } ); + const lights = currentRenderState.state.lights; + const shadowsArray = currentRenderState.state.shadowsArray; - super( geometry, material ); + const lightsStateVersion = lights.state.version; - this.type = 'PolarGridHelper'; + const parameters = programCache.getParameters( material, lights.state, shadowsArray, scene, object ); + const programCacheKey = programCache.getProgramCacheKey( parameters ); - } + let programs = materialProperties.programs; - dispose() { + // always update environment and fog - changing these trigger an getProgram call, but it's possible that the program doesn't change - this.geometry.dispose(); - this.material.dispose(); + materialProperties.environment = material.isMeshStandardMaterial ? scene.environment : null; + materialProperties.fog = scene.fog; + materialProperties.envMap = ( material.isMeshStandardMaterial ? cubeuvmaps : cubemaps ).get( material.envMap || materialProperties.environment ); + materialProperties.envMapRotation = ( materialProperties.environment !== null && material.envMap === null ) ? scene.environmentRotation : material.envMapRotation; - } + if ( programs === undefined ) { -} + // new material -const _v1 = /*@__PURE__*/ new Vector3(); -const _v2 = /*@__PURE__*/ new Vector3(); -const _v3 = /*@__PURE__*/ new Vector3(); + material.addEventListener( 'dispose', onMaterialDispose ); -class DirectionalLightHelper extends Object3D { + programs = new Map(); + materialProperties.programs = programs; - constructor( light, size, color ) { + } - super(); + let program = programs.get( programCacheKey ); - this.light = light; + if ( program !== undefined ) { - this.matrix = light.matrixWorld; - this.matrixAutoUpdate = false; + // early out if program and light state is identical - this.color = color; + if ( materialProperties.currentProgram === program && materialProperties.lightsStateVersion === lightsStateVersion ) { - this.type = 'DirectionalLightHelper'; + updateCommonMaterialProperties( material, parameters ); - if ( size === undefined ) size = 1; + return program; - let geometry = new BufferGeometry(); - geometry.setAttribute( 'position', new Float32BufferAttribute( [ - - size, size, 0, - size, size, 0, - size, - size, 0, - - size, - size, 0, - - size, size, 0 - ], 3 ) ); + } - const material = new LineBasicMaterial( { fog: false, toneMapped: false } ); + } else { - this.lightPlane = new Line( geometry, material ); - this.add( this.lightPlane ); + parameters.uniforms = programCache.getUniforms( material ); - geometry = new BufferGeometry(); - geometry.setAttribute( 'position', new Float32BufferAttribute( [ 0, 0, 0, 0, 0, 1 ], 3 ) ); + material.onBeforeCompile( parameters, _this ); - this.targetLine = new Line( geometry, material ); - this.add( this.targetLine ); + program = programCache.acquireProgram( parameters, programCacheKey ); + programs.set( programCacheKey, program ); - this.update(); + materialProperties.uniforms = parameters.uniforms; - } + } - dispose() { + const uniforms = materialProperties.uniforms; - this.lightPlane.geometry.dispose(); - this.lightPlane.material.dispose(); - this.targetLine.geometry.dispose(); - this.targetLine.material.dispose(); + if ( ( ! material.isShaderMaterial && ! material.isRawShaderMaterial ) || material.clipping === true ) { - } + uniforms.clippingPlanes = clipping.uniform; - update() { + } - this.light.updateWorldMatrix( true, false ); - this.light.target.updateWorldMatrix( true, false ); + updateCommonMaterialProperties( material, parameters ); - _v1.setFromMatrixPosition( this.light.matrixWorld ); - _v2.setFromMatrixPosition( this.light.target.matrixWorld ); - _v3.subVectors( _v2, _v1 ); + // store the light setup it was created for - this.lightPlane.lookAt( _v2 ); + materialProperties.needsLights = materialNeedsLights( material ); + materialProperties.lightsStateVersion = lightsStateVersion; - if ( this.color !== undefined ) { + if ( materialProperties.needsLights ) { + + // wire up the material to this renderer's lighting state + + uniforms.ambientLightColor.value = lights.state.ambient; + uniforms.lightProbe.value = lights.state.probe; + uniforms.directionalLights.value = lights.state.directional; + uniforms.directionalLightShadows.value = lights.state.directionalShadow; + uniforms.spotLights.value = lights.state.spot; + uniforms.spotLightShadows.value = lights.state.spotShadow; + uniforms.rectAreaLights.value = lights.state.rectArea; + uniforms.ltc_1.value = lights.state.rectAreaLTC1; + uniforms.ltc_2.value = lights.state.rectAreaLTC2; + uniforms.pointLights.value = lights.state.point; + uniforms.pointLightShadows.value = lights.state.pointShadow; + uniforms.hemisphereLights.value = lights.state.hemi; + + uniforms.directionalShadowMap.value = lights.state.directionalShadowMap; + uniforms.directionalShadowMatrix.value = lights.state.directionalShadowMatrix; + uniforms.spotShadowMap.value = lights.state.spotShadowMap; + uniforms.spotLightMatrix.value = lights.state.spotLightMatrix; + uniforms.spotLightMap.value = lights.state.spotLightMap; + uniforms.pointShadowMap.value = lights.state.pointShadowMap; + uniforms.pointShadowMatrix.value = lights.state.pointShadowMatrix; + // TODO (abelnation): add area lights shadow info to uniforms - this.lightPlane.material.color.set( this.color ); - this.targetLine.material.color.set( this.color ); + } - } else { + materialProperties.currentProgram = program; + materialProperties.uniformsList = null; - this.lightPlane.material.color.copy( this.light.color ); - this.targetLine.material.color.copy( this.light.color ); + return program; } - this.targetLine.lookAt( _v2 ); - this.targetLine.scale.z = _v3.length(); + function getUniformList( materialProperties ) { - } + if ( materialProperties.uniformsList === null ) { -} + const progUniforms = materialProperties.currentProgram.getUniforms(); + materialProperties.uniformsList = WebGLUniforms.seqWithValue( progUniforms.seq, materialProperties.uniforms ); -const _vector = /*@__PURE__*/ new Vector3(); -const _camera = /*@__PURE__*/ new Camera(); + } -/** - * - shows frustum, line of sight and up of the camera - * - suitable for fast updates - * - based on frustum visualization in lightgl.js shadowmap example - * https://github.com/evanw/lightgl.js/blob/master/tests/shadowmap.html - */ + return materialProperties.uniformsList; -class CameraHelper extends LineSegments { + } - constructor( camera ) { + function updateCommonMaterialProperties( material, parameters ) { - const geometry = new BufferGeometry(); - const material = new LineBasicMaterial( { color: 0xffffff, vertexColors: true, toneMapped: false } ); + const materialProperties = properties.get( material ); - const vertices = []; - const colors = []; + materialProperties.outputColorSpace = parameters.outputColorSpace; + materialProperties.batching = parameters.batching; + materialProperties.batchingColor = parameters.batchingColor; + materialProperties.instancing = parameters.instancing; + materialProperties.instancingColor = parameters.instancingColor; + materialProperties.instancingMorph = parameters.instancingMorph; + materialProperties.skinning = parameters.skinning; + materialProperties.morphTargets = parameters.morphTargets; + materialProperties.morphNormals = parameters.morphNormals; + materialProperties.morphColors = parameters.morphColors; + materialProperties.morphTargetsCount = parameters.morphTargetsCount; + materialProperties.numClippingPlanes = parameters.numClippingPlanes; + materialProperties.numIntersection = parameters.numClipIntersection; + materialProperties.vertexAlphas = parameters.vertexAlphas; + materialProperties.vertexTangents = parameters.vertexTangents; + materialProperties.toneMapping = parameters.toneMapping; - const pointMap = {}; + } - // near + function setProgram( camera, scene, geometry, material, object ) { - addLine( 'n1', 'n2' ); - addLine( 'n2', 'n4' ); - addLine( 'n4', 'n3' ); - addLine( 'n3', 'n1' ); + if ( scene.isScene !== true ) scene = _emptyScene; // scene could be a Mesh, Line, Points, ... - // far + textures.resetTextureUnits(); - addLine( 'f1', 'f2' ); - addLine( 'f2', 'f4' ); - addLine( 'f4', 'f3' ); - addLine( 'f3', 'f1' ); + const fog = scene.fog; + const environment = material.isMeshStandardMaterial ? scene.environment : null; + const colorSpace = ( _currentRenderTarget === null ) ? _this.outputColorSpace : ( _currentRenderTarget.isXRRenderTarget === true ? _currentRenderTarget.texture.colorSpace : LinearSRGBColorSpace ); + const envMap = ( material.isMeshStandardMaterial ? cubeuvmaps : cubemaps ).get( material.envMap || environment ); + const vertexAlphas = material.vertexColors === true && !! geometry.attributes.color && geometry.attributes.color.itemSize === 4; + const vertexTangents = !! geometry.attributes.tangent && ( !! material.normalMap || material.anisotropy > 0 ); + const morphTargets = !! geometry.morphAttributes.position; + const morphNormals = !! geometry.morphAttributes.normal; + const morphColors = !! geometry.morphAttributes.color; - // sides + let toneMapping = NoToneMapping; - addLine( 'n1', 'f1' ); - addLine( 'n2', 'f2' ); - addLine( 'n3', 'f3' ); - addLine( 'n4', 'f4' ); + if ( material.toneMapped ) { - // cone + if ( _currentRenderTarget === null || _currentRenderTarget.isXRRenderTarget === true ) { - addLine( 'p', 'n1' ); - addLine( 'p', 'n2' ); - addLine( 'p', 'n3' ); - addLine( 'p', 'n4' ); + toneMapping = _this.toneMapping; - // up + } - addLine( 'u1', 'u2' ); - addLine( 'u2', 'u3' ); - addLine( 'u3', 'u1' ); + } - // target + const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color; + const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0; - addLine( 'c', 't' ); - addLine( 'p', 'c' ); + const materialProperties = properties.get( material ); + const lights = currentRenderState.state.lights; - // cross + if ( _clippingEnabled === true ) { - addLine( 'cn1', 'cn2' ); - addLine( 'cn3', 'cn4' ); + if ( _localClippingEnabled === true || camera !== _currentCamera ) { - addLine( 'cf1', 'cf2' ); - addLine( 'cf3', 'cf4' ); + const useCache = + camera === _currentCamera && + material.id === _currentMaterialId; - function addLine( a, b ) { + // we might want to call this function with some ClippingGroup + // object instead of the material, once it becomes feasible + // (#8465, #8379) + clipping.setState( material, camera, useCache ); - addPoint( a ); - addPoint( b ); + } - } + } - function addPoint( id ) { + // - vertices.push( 0, 0, 0 ); - colors.push( 0, 0, 0 ); + let needsProgramChange = false; - if ( pointMap[ id ] === undefined ) { + if ( material.version === materialProperties.__version ) { - pointMap[ id ] = []; + if ( materialProperties.needsLights && ( materialProperties.lightsStateVersion !== lights.state.version ) ) { - } + needsProgramChange = true; - pointMap[ id ].push( ( vertices.length / 3 ) - 1 ); + } else if ( materialProperties.outputColorSpace !== colorSpace ) { - } + needsProgramChange = true; - geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - geometry.setAttribute( 'color', new Float32BufferAttribute( colors, 3 ) ); + } else if ( object.isBatchedMesh && materialProperties.batching === false ) { - super( geometry, material ); + needsProgramChange = true; - this.type = 'CameraHelper'; + } else if ( ! object.isBatchedMesh && materialProperties.batching === true ) { - this.camera = camera; - if ( this.camera.updateProjectionMatrix ) this.camera.updateProjectionMatrix(); + needsProgramChange = true; - this.matrix = camera.matrixWorld; - this.matrixAutoUpdate = false; + } else if ( object.isBatchedMesh && materialProperties.batchingColor === true && object.colorTexture === null ) { - this.pointMap = pointMap; + needsProgramChange = true; - this.update(); + } else if ( object.isBatchedMesh && materialProperties.batchingColor === false && object.colorTexture !== null ) { - // colors + needsProgramChange = true; - const colorFrustum = new Color( 0xffaa00 ); - const colorCone = new Color( 0xff0000 ); - const colorUp = new Color( 0x00aaff ); - const colorTarget = new Color( 0xffffff ); - const colorCross = new Color( 0x333333 ); + } else if ( object.isInstancedMesh && materialProperties.instancing === false ) { - this.setColors( colorFrustum, colorCone, colorUp, colorTarget, colorCross ); + needsProgramChange = true; - } + } else if ( ! object.isInstancedMesh && materialProperties.instancing === true ) { - setColors( frustum, cone, up, target, cross ) { + needsProgramChange = true; - const geometry = this.geometry; + } else if ( object.isSkinnedMesh && materialProperties.skinning === false ) { - const colorAttribute = geometry.getAttribute( 'color' ); + needsProgramChange = true; - // near + } else if ( ! object.isSkinnedMesh && materialProperties.skinning === true ) { - colorAttribute.setXYZ( 0, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 1, frustum.r, frustum.g, frustum.b ); // n1, n2 - colorAttribute.setXYZ( 2, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 3, frustum.r, frustum.g, frustum.b ); // n2, n4 - colorAttribute.setXYZ( 4, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 5, frustum.r, frustum.g, frustum.b ); // n4, n3 - colorAttribute.setXYZ( 6, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 7, frustum.r, frustum.g, frustum.b ); // n3, n1 + needsProgramChange = true; - // far + } else if ( object.isInstancedMesh && materialProperties.instancingColor === true && object.instanceColor === null ) { - colorAttribute.setXYZ( 8, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 9, frustum.r, frustum.g, frustum.b ); // f1, f2 - colorAttribute.setXYZ( 10, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 11, frustum.r, frustum.g, frustum.b ); // f2, f4 - colorAttribute.setXYZ( 12, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 13, frustum.r, frustum.g, frustum.b ); // f4, f3 - colorAttribute.setXYZ( 14, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 15, frustum.r, frustum.g, frustum.b ); // f3, f1 + needsProgramChange = true; - // sides + } else if ( object.isInstancedMesh && materialProperties.instancingColor === false && object.instanceColor !== null ) { - colorAttribute.setXYZ( 16, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 17, frustum.r, frustum.g, frustum.b ); // n1, f1 - colorAttribute.setXYZ( 18, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 19, frustum.r, frustum.g, frustum.b ); // n2, f2 - colorAttribute.setXYZ( 20, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 21, frustum.r, frustum.g, frustum.b ); // n3, f3 - colorAttribute.setXYZ( 22, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 23, frustum.r, frustum.g, frustum.b ); // n4, f4 + needsProgramChange = true; - // cone + } else if ( object.isInstancedMesh && materialProperties.instancingMorph === true && object.morphTexture === null ) { - colorAttribute.setXYZ( 24, cone.r, cone.g, cone.b ); colorAttribute.setXYZ( 25, cone.r, cone.g, cone.b ); // p, n1 - colorAttribute.setXYZ( 26, cone.r, cone.g, cone.b ); colorAttribute.setXYZ( 27, cone.r, cone.g, cone.b ); // p, n2 - colorAttribute.setXYZ( 28, cone.r, cone.g, cone.b ); colorAttribute.setXYZ( 29, cone.r, cone.g, cone.b ); // p, n3 - colorAttribute.setXYZ( 30, cone.r, cone.g, cone.b ); colorAttribute.setXYZ( 31, cone.r, cone.g, cone.b ); // p, n4 + needsProgramChange = true; - // up + } else if ( object.isInstancedMesh && materialProperties.instancingMorph === false && object.morphTexture !== null ) { - colorAttribute.setXYZ( 32, up.r, up.g, up.b ); colorAttribute.setXYZ( 33, up.r, up.g, up.b ); // u1, u2 - colorAttribute.setXYZ( 34, up.r, up.g, up.b ); colorAttribute.setXYZ( 35, up.r, up.g, up.b ); // u2, u3 - colorAttribute.setXYZ( 36, up.r, up.g, up.b ); colorAttribute.setXYZ( 37, up.r, up.g, up.b ); // u3, u1 + needsProgramChange = true; - // target + } else if ( materialProperties.envMap !== envMap ) { - colorAttribute.setXYZ( 38, target.r, target.g, target.b ); colorAttribute.setXYZ( 39, target.r, target.g, target.b ); // c, t - colorAttribute.setXYZ( 40, cross.r, cross.g, cross.b ); colorAttribute.setXYZ( 41, cross.r, cross.g, cross.b ); // p, c + needsProgramChange = true; - // cross + } else if ( material.fog === true && materialProperties.fog !== fog ) { - colorAttribute.setXYZ( 42, cross.r, cross.g, cross.b ); colorAttribute.setXYZ( 43, cross.r, cross.g, cross.b ); // cn1, cn2 - colorAttribute.setXYZ( 44, cross.r, cross.g, cross.b ); colorAttribute.setXYZ( 45, cross.r, cross.g, cross.b ); // cn3, cn4 + needsProgramChange = true; - colorAttribute.setXYZ( 46, cross.r, cross.g, cross.b ); colorAttribute.setXYZ( 47, cross.r, cross.g, cross.b ); // cf1, cf2 - colorAttribute.setXYZ( 48, cross.r, cross.g, cross.b ); colorAttribute.setXYZ( 49, cross.r, cross.g, cross.b ); // cf3, cf4 + } else if ( materialProperties.numClippingPlanes !== undefined && + ( materialProperties.numClippingPlanes !== clipping.numPlanes || + materialProperties.numIntersection !== clipping.numIntersection ) ) { - colorAttribute.needsUpdate = true; + needsProgramChange = true; - } + } else if ( materialProperties.vertexAlphas !== vertexAlphas ) { - update() { + needsProgramChange = true; - const geometry = this.geometry; - const pointMap = this.pointMap; + } else if ( materialProperties.vertexTangents !== vertexTangents ) { - const w = 1, h = 1; + needsProgramChange = true; - // we need just camera projection matrix inverse - // world matrix must be identity + } else if ( materialProperties.morphTargets !== morphTargets ) { - _camera.projectionMatrixInverse.copy( this.camera.projectionMatrixInverse ); + needsProgramChange = true; - // center / target + } else if ( materialProperties.morphNormals !== morphNormals ) { - setPoint( 'c', pointMap, geometry, _camera, 0, 0, - 1 ); - setPoint( 't', pointMap, geometry, _camera, 0, 0, 1 ); + needsProgramChange = true; - // near + } else if ( materialProperties.morphColors !== morphColors ) { - setPoint( 'n1', pointMap, geometry, _camera, - w, - h, - 1 ); - setPoint( 'n2', pointMap, geometry, _camera, w, - h, - 1 ); - setPoint( 'n3', pointMap, geometry, _camera, - w, h, - 1 ); - setPoint( 'n4', pointMap, geometry, _camera, w, h, - 1 ); + needsProgramChange = true; - // far + } else if ( materialProperties.toneMapping !== toneMapping ) { - setPoint( 'f1', pointMap, geometry, _camera, - w, - h, 1 ); - setPoint( 'f2', pointMap, geometry, _camera, w, - h, 1 ); - setPoint( 'f3', pointMap, geometry, _camera, - w, h, 1 ); - setPoint( 'f4', pointMap, geometry, _camera, w, h, 1 ); + needsProgramChange = true; - // up + } else if ( materialProperties.morphTargetsCount !== morphTargetsCount ) { - setPoint( 'u1', pointMap, geometry, _camera, w * 0.7, h * 1.1, - 1 ); - setPoint( 'u2', pointMap, geometry, _camera, - w * 0.7, h * 1.1, - 1 ); - setPoint( 'u3', pointMap, geometry, _camera, 0, h * 2, - 1 ); + needsProgramChange = true; - // cross + } - setPoint( 'cf1', pointMap, geometry, _camera, - w, 0, 1 ); - setPoint( 'cf2', pointMap, geometry, _camera, w, 0, 1 ); - setPoint( 'cf3', pointMap, geometry, _camera, 0, - h, 1 ); - setPoint( 'cf4', pointMap, geometry, _camera, 0, h, 1 ); + } else { - setPoint( 'cn1', pointMap, geometry, _camera, - w, 0, - 1 ); - setPoint( 'cn2', pointMap, geometry, _camera, w, 0, - 1 ); - setPoint( 'cn3', pointMap, geometry, _camera, 0, - h, - 1 ); - setPoint( 'cn4', pointMap, geometry, _camera, 0, h, - 1 ); + needsProgramChange = true; + materialProperties.__version = material.version; - geometry.getAttribute( 'position' ).needsUpdate = true; + } - } + // - dispose() { + let program = materialProperties.currentProgram; - this.geometry.dispose(); - this.material.dispose(); + if ( needsProgramChange === true ) { - } + program = getProgram( material, scene, object ); -} + } + let refreshProgram = false; + let refreshMaterial = false; + let refreshLights = false; -function setPoint( point, pointMap, geometry, camera, x, y, z ) { + const p_uniforms = program.getUniforms(), + m_uniforms = materialProperties.uniforms; - _vector.set( x, y, z ).unproject( camera ); + if ( state.useProgram( program.program ) ) { - const points = pointMap[ point ]; + refreshProgram = true; + refreshMaterial = true; + refreshLights = true; - if ( points !== undefined ) { + } - const position = geometry.getAttribute( 'position' ); + if ( material.id !== _currentMaterialId ) { - for ( let i = 0, l = points.length; i < l; i ++ ) { + _currentMaterialId = material.id; - position.setXYZ( points[ i ], _vector.x, _vector.y, _vector.z ); + refreshMaterial = true; - } + } - } + if ( refreshProgram || _currentCamera !== camera ) { -} + // common camera uniforms -const _box = /*@__PURE__*/ new Box3(); + const reverseDepthBuffer = state.buffers.depth.getReversed(); -class BoxHelper extends LineSegments { + if ( reverseDepthBuffer ) { - constructor( object, color = 0xffff00 ) { + _currentProjectionMatrix.copy( camera.projectionMatrix ); - const indices = new Uint16Array( [ 0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7 ] ); - const positions = new Float32Array( 8 * 3 ); + toNormalizedProjectionMatrix( _currentProjectionMatrix ); + toReversedProjectionMatrix( _currentProjectionMatrix ); - const geometry = new BufferGeometry(); - geometry.setIndex( new BufferAttribute( indices, 1 ) ); - geometry.setAttribute( 'position', new BufferAttribute( positions, 3 ) ); + p_uniforms.setValue( _gl, 'projectionMatrix', _currentProjectionMatrix ); - super( geometry, new LineBasicMaterial( { color: color, toneMapped: false } ) ); + } else { - this.object = object; - this.type = 'BoxHelper'; + p_uniforms.setValue( _gl, 'projectionMatrix', camera.projectionMatrix ); - this.matrixAutoUpdate = false; + } - this.update(); + p_uniforms.setValue( _gl, 'viewMatrix', camera.matrixWorldInverse ); - } + const uCamPos = p_uniforms.map.cameraPosition; - update( object ) { + if ( uCamPos !== undefined ) { - if ( object !== undefined ) { + uCamPos.setValue( _gl, _vector3.setFromMatrixPosition( camera.matrixWorld ) ); - console.warn( 'THREE.BoxHelper: .update() has no longer arguments.' ); + } - } + if ( capabilities.logarithmicDepthBuffer ) { - if ( this.object !== undefined ) { + p_uniforms.setValue( _gl, 'logDepthBufFC', + 2.0 / ( Math.log( camera.far + 1.0 ) / Math.LN2 ) ); - _box.setFromObject( this.object ); + } - } + // consider moving isOrthographic to UniformLib and WebGLMaterials, see https://github.com/mrdoob/three.js/pull/26467#issuecomment-1645185067 - if ( _box.isEmpty() ) return; + if ( material.isMeshPhongMaterial || + material.isMeshToonMaterial || + material.isMeshLambertMaterial || + material.isMeshBasicMaterial || + material.isMeshStandardMaterial || + material.isShaderMaterial ) { - const min = _box.min; - const max = _box.max; + p_uniforms.setValue( _gl, 'isOrthographic', camera.isOrthographicCamera === true ); - /* - 5____4 - 1/___0/| - | 6__|_7 - 2/___3/ + } - 0: max.x, max.y, max.z - 1: min.x, max.y, max.z - 2: min.x, min.y, max.z - 3: max.x, min.y, max.z - 4: max.x, max.y, min.z - 5: min.x, max.y, min.z - 6: min.x, min.y, min.z - 7: max.x, min.y, min.z - */ + if ( _currentCamera !== camera ) { - const position = this.geometry.attributes.position; - const array = position.array; + _currentCamera = camera; - array[ 0 ] = max.x; array[ 1 ] = max.y; array[ 2 ] = max.z; - array[ 3 ] = min.x; array[ 4 ] = max.y; array[ 5 ] = max.z; - array[ 6 ] = min.x; array[ 7 ] = min.y; array[ 8 ] = max.z; - array[ 9 ] = max.x; array[ 10 ] = min.y; array[ 11 ] = max.z; - array[ 12 ] = max.x; array[ 13 ] = max.y; array[ 14 ] = min.z; - array[ 15 ] = min.x; array[ 16 ] = max.y; array[ 17 ] = min.z; - array[ 18 ] = min.x; array[ 19 ] = min.y; array[ 20 ] = min.z; - array[ 21 ] = max.x; array[ 22 ] = min.y; array[ 23 ] = min.z; + // lighting uniforms depend on the camera so enforce an update + // now, in case this material supports lights - or later, when + // the next material that does gets activated: - position.needsUpdate = true; + refreshMaterial = true; // set to true on material change + refreshLights = true; // remains set until update done - this.geometry.computeBoundingSphere(); + } - } + } - setFromObject( object ) { + // skinning and morph target uniforms must be set even if material didn't change + // auto-setting of texture unit for bone and morph texture must go before other textures + // otherwise textures used for skinning and morphing can take over texture units reserved for other material textures - this.object = object; - this.update(); + if ( object.isSkinnedMesh ) { - return this; + p_uniforms.setOptional( _gl, object, 'bindMatrix' ); + p_uniforms.setOptional( _gl, object, 'bindMatrixInverse' ); - } + const skeleton = object.skeleton; - copy( source, recursive ) { + if ( skeleton ) { - super.copy( source, recursive ); + if ( skeleton.boneTexture === null ) skeleton.computeBoneTexture(); - this.object = source.object; + p_uniforms.setValue( _gl, 'boneTexture', skeleton.boneTexture, textures ); - return this; + } - } + } - dispose() { + if ( object.isBatchedMesh ) { - this.geometry.dispose(); - this.material.dispose(); + p_uniforms.setOptional( _gl, object, 'batchingTexture' ); + p_uniforms.setValue( _gl, 'batchingTexture', object._matricesTexture, textures ); - } + p_uniforms.setOptional( _gl, object, 'batchingIdTexture' ); + p_uniforms.setValue( _gl, 'batchingIdTexture', object._indirectTexture, textures ); -} + p_uniforms.setOptional( _gl, object, 'batchingColorTexture' ); + if ( object._colorsTexture !== null ) { -class Box3Helper extends LineSegments { + p_uniforms.setValue( _gl, 'batchingColorTexture', object._colorsTexture, textures ); - constructor( box, color = 0xffff00 ) { + } - const indices = new Uint16Array( [ 0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7 ] ); + } - const positions = [ 1, 1, 1, - 1, 1, 1, - 1, - 1, 1, 1, - 1, 1, 1, 1, - 1, - 1, 1, - 1, - 1, - 1, - 1, 1, - 1, - 1 ]; + const morphAttributes = geometry.morphAttributes; - const geometry = new BufferGeometry(); + if ( morphAttributes.position !== undefined || morphAttributes.normal !== undefined || ( morphAttributes.color !== undefined ) ) { - geometry.setIndex( new BufferAttribute( indices, 1 ) ); + morphtargets.update( object, geometry, program ); - geometry.setAttribute( 'position', new Float32BufferAttribute( positions, 3 ) ); + } - super( geometry, new LineBasicMaterial( { color: color, toneMapped: false } ) ); + if ( refreshMaterial || materialProperties.receiveShadow !== object.receiveShadow ) { - this.box = box; + materialProperties.receiveShadow = object.receiveShadow; + p_uniforms.setValue( _gl, 'receiveShadow', object.receiveShadow ); - this.type = 'Box3Helper'; + } - this.geometry.computeBoundingSphere(); + // https://github.com/mrdoob/three.js/pull/24467#issuecomment-1209031512 - } + if ( material.isMeshGouraudMaterial && material.envMap !== null ) { - updateMatrixWorld( force ) { + m_uniforms.envMap.value = envMap; - const box = this.box; + m_uniforms.flipEnvMap.value = ( envMap.isCubeTexture && envMap.isRenderTargetTexture === false ) ? -1 : 1; - if ( box.isEmpty() ) return; + } - box.getCenter( this.position ); + if ( material.isMeshStandardMaterial && material.envMap === null && scene.environment !== null ) { - box.getSize( this.scale ); + m_uniforms.envMapIntensity.value = scene.environmentIntensity; - this.scale.multiplyScalar( 0.5 ); + } - super.updateMatrixWorld( force ); + if ( refreshMaterial ) { - } + p_uniforms.setValue( _gl, 'toneMappingExposure', _this.toneMappingExposure ); - dispose() { + if ( materialProperties.needsLights ) { - this.geometry.dispose(); - this.material.dispose(); + // the current material requires lighting info - } + // note: all lighting uniforms are always set correctly + // they simply reference the renderer's state for their + // values + // + // use the current material's .needsUpdate flags to set + // the GL state when required -} + markUniformsLightsNeedsUpdate( m_uniforms, refreshLights ); -class PlaneHelper extends Line { + } - constructor( plane, size = 1, hex = 0xffff00 ) { + // refresh uniforms common to several materials - const color = hex; + if ( fog && material.fog === true ) { - const positions = [ 1, - 1, 0, - 1, 1, 0, - 1, - 1, 0, 1, 1, 0, - 1, 1, 0, - 1, - 1, 0, 1, - 1, 0, 1, 1, 0 ]; + materials.refreshFogUniforms( m_uniforms, fog ); - const geometry = new BufferGeometry(); - geometry.setAttribute( 'position', new Float32BufferAttribute( positions, 3 ) ); - geometry.computeBoundingSphere(); + } - super( geometry, new LineBasicMaterial( { color: color, toneMapped: false } ) ); + materials.refreshMaterialUniforms( m_uniforms, material, _pixelRatio, _height, currentRenderState.state.transmissionRenderTarget[ camera.id ] ); - this.type = 'PlaneHelper'; + WebGLUniforms.upload( _gl, getUniformList( materialProperties ), m_uniforms, textures ); - this.plane = plane; + } - this.size = size; + if ( material.isShaderMaterial && material.uniformsNeedUpdate === true ) { - const positions2 = [ 1, 1, 0, - 1, 1, 0, - 1, - 1, 0, 1, 1, 0, - 1, - 1, 0, 1, - 1, 0 ]; + WebGLUniforms.upload( _gl, getUniformList( materialProperties ), m_uniforms, textures ); + material.uniformsNeedUpdate = false; - const geometry2 = new BufferGeometry(); - geometry2.setAttribute( 'position', new Float32BufferAttribute( positions2, 3 ) ); - geometry2.computeBoundingSphere(); + } - this.add( new Mesh( geometry2, new MeshBasicMaterial( { color: color, opacity: 0.2, transparent: true, depthWrite: false, toneMapped: false } ) ) ); + if ( material.isSpriteMaterial ) { - } + p_uniforms.setValue( _gl, 'center', object.center ); - updateMatrixWorld( force ) { + } - this.position.set( 0, 0, 0 ); + // common matrices - this.scale.set( 0.5 * this.size, 0.5 * this.size, 1 ); + p_uniforms.setValue( _gl, 'modelViewMatrix', object.modelViewMatrix ); + p_uniforms.setValue( _gl, 'normalMatrix', object.normalMatrix ); + p_uniforms.setValue( _gl, 'modelMatrix', object.matrixWorld ); - this.lookAt( this.plane.normal ); + // UBOs - this.translateZ( - this.plane.constant ); + if ( material.isShaderMaterial || material.isRawShaderMaterial ) { - super.updateMatrixWorld( force ); + const groups = material.uniformsGroups; - } + for ( let i = 0, l = groups.length; i < l; i ++ ) { - dispose() { + const group = groups[ i ]; - this.geometry.dispose(); - this.material.dispose(); - this.children[ 0 ].geometry.dispose(); - this.children[ 0 ].material.dispose(); + uniformsGroups.update( group, program ); + uniformsGroups.bind( group, program ); - } + } -} + } -const _axis = /*@__PURE__*/ new Vector3(); -let _lineGeometry, _coneGeometry; + return program; -class ArrowHelper extends Object3D { + } - // dir is assumed to be normalized + // If uniforms are marked as clean, they don't need to be loaded to the GPU. - constructor( dir = new Vector3( 0, 0, 1 ), origin = new Vector3( 0, 0, 0 ), length = 1, color = 0xffff00, headLength = length * 0.2, headWidth = headLength * 0.2 ) { + function markUniformsLightsNeedsUpdate( uniforms, value ) { - super(); + uniforms.ambientLightColor.needsUpdate = value; + uniforms.lightProbe.needsUpdate = value; - this.type = 'ArrowHelper'; + uniforms.directionalLights.needsUpdate = value; + uniforms.directionalLightShadows.needsUpdate = value; + uniforms.pointLights.needsUpdate = value; + uniforms.pointLightShadows.needsUpdate = value; + uniforms.spotLights.needsUpdate = value; + uniforms.spotLightShadows.needsUpdate = value; + uniforms.rectAreaLights.needsUpdate = value; + uniforms.hemisphereLights.needsUpdate = value; - if ( _lineGeometry === undefined ) { + } - _lineGeometry = new BufferGeometry(); - _lineGeometry.setAttribute( 'position', new Float32BufferAttribute( [ 0, 0, 0, 0, 1, 0 ], 3 ) ); + function materialNeedsLights( material ) { - _coneGeometry = new CylinderGeometry( 0, 0.5, 1, 5, 1 ); - _coneGeometry.translate( 0, - 0.5, 0 ); + return material.isMeshLambertMaterial || material.isMeshToonMaterial || material.isMeshPhongMaterial || + material.isMeshStandardMaterial || material.isShadowMaterial || + ( material.isShaderMaterial && material.lights === true ); } - this.position.copy( origin ); - - this.line = new Line( _lineGeometry, new LineBasicMaterial( { color: color, toneMapped: false } ) ); - this.line.matrixAutoUpdate = false; - this.add( this.line ); - - this.cone = new Mesh( _coneGeometry, new MeshBasicMaterial( { color: color, toneMapped: false } ) ); - this.cone.matrixAutoUpdate = false; - this.add( this.cone ); + /** + * Returns the active cube face. + * + * @return {number} The active cube face. + */ + this.getActiveCubeFace = function () { - this.setDirection( dir ); - this.setLength( length, headLength, headWidth ); + return _currentActiveCubeFace; - } + }; - setDirection( dir ) { + /** + * Returns the active mipmap level. + * + * @return {number} The active mipmap level. + */ + this.getActiveMipmapLevel = function () { - // dir is assumed to be normalized + return _currentActiveMipmapLevel; - if ( dir.y > 0.99999 ) { + }; - this.quaternion.set( 0, 0, 0, 1 ); + /** + * Returns the active render target. + * + * @return {?WebGLRenderTarget} The active render target. Returns `null` if no render target + * is currently set. + */ + this.getRenderTarget = function () { - } else if ( dir.y < - 0.99999 ) { + return _currentRenderTarget; - this.quaternion.set( 1, 0, 0, 0 ); + }; - } else { + this.setRenderTargetTextures = function ( renderTarget, colorTexture, depthTexture ) { - _axis.set( dir.z, 0, - dir.x ).normalize(); + const renderTargetProperties = properties.get( renderTarget ); - const radians = Math.acos( dir.y ); + renderTargetProperties.__autoAllocateDepthBuffer = renderTarget.resolveDepthBuffer === false; + if ( renderTargetProperties.__autoAllocateDepthBuffer === false ) { - this.quaternion.setFromAxisAngle( _axis, radians ); + // The multisample_render_to_texture extension doesn't work properly if there + // are midframe flushes and an external depth buffer. Disable use of the extension. + renderTargetProperties.__useRenderToTexture = false; - } + } - } + properties.get( renderTarget.texture ).__webglTexture = colorTexture; + properties.get( renderTarget.depthTexture ).__webglTexture = renderTargetProperties.__autoAllocateDepthBuffer ? undefined : depthTexture; - setLength( length, headLength = length * 0.2, headWidth = headLength * 0.2 ) { + renderTargetProperties.__hasExternalTextures = true; - this.line.scale.set( 1, Math.max( 0.0001, length - headLength ), 1 ); // see #17458 - this.line.updateMatrix(); + }; - this.cone.scale.set( headWidth, headLength, headWidth ); - this.cone.position.y = length; - this.cone.updateMatrix(); + this.setRenderTargetFramebuffer = function ( renderTarget, defaultFramebuffer ) { - } + const renderTargetProperties = properties.get( renderTarget ); + renderTargetProperties.__webglFramebuffer = defaultFramebuffer; + renderTargetProperties.__useDefaultFramebuffer = defaultFramebuffer === undefined; - setColor( color ) { + }; - this.line.material.color.set( color ); - this.cone.material.color.set( color ); + const _scratchFrameBuffer = _gl.createFramebuffer(); - } + /** + * Sets the active rendertarget. + * + * @param {?WebGLRenderTarget} renderTarget - The render target to set. When `null` is given, + * the canvas is set as the active render target instead. + * @param {number} [activeCubeFace=0] - The active cube face when using a cube render target. + * Indicates the z layer to render in to when using 3D or array render targets. + * @param {number} [activeMipmapLevel=0] - The active mipmap level. + */ + this.setRenderTarget = function ( renderTarget, activeCubeFace = 0, activeMipmapLevel = 0 ) { - copy( source ) { + _currentRenderTarget = renderTarget; + _currentActiveCubeFace = activeCubeFace; + _currentActiveMipmapLevel = activeMipmapLevel; - super.copy( source, false ); + let useDefaultFramebuffer = true; + let framebuffer = null; + let isCube = false; + let isRenderTarget3D = false; - this.line.copy( source.line ); - this.cone.copy( source.cone ); + if ( renderTarget ) { - return this; + const renderTargetProperties = properties.get( renderTarget ); - } + if ( renderTargetProperties.__useDefaultFramebuffer !== undefined ) { - dispose() { + // We need to make sure to rebind the framebuffer. + state.bindFramebuffer( _gl.FRAMEBUFFER, null ); + useDefaultFramebuffer = false; - this.line.geometry.dispose(); - this.line.material.dispose(); - this.cone.geometry.dispose(); - this.cone.material.dispose(); + } else if ( renderTargetProperties.__webglFramebuffer === undefined ) { - } + textures.setupRenderTarget( renderTarget ); -} + } else if ( renderTargetProperties.__hasExternalTextures ) { -class AxesHelper extends LineSegments { + // Color and depth texture must be rebound in order for the swapchain to update. + textures.rebindTextures( renderTarget, properties.get( renderTarget.texture ).__webglTexture, properties.get( renderTarget.depthTexture ).__webglTexture ); - constructor( size = 1 ) { + } else if ( renderTarget.depthBuffer ) { - const vertices = [ - 0, 0, 0, size, 0, 0, - 0, 0, 0, 0, size, 0, - 0, 0, 0, 0, 0, size - ]; + // check if the depth texture is already bound to the frame buffer and that it's been initialized + const depthTexture = renderTarget.depthTexture; + if ( renderTargetProperties.__boundDepthTexture !== depthTexture ) { - const colors = [ - 1, 0, 0, 1, 0.6, 0, - 0, 1, 0, 0.6, 1, 0, - 0, 0, 1, 0, 0.6, 1 - ]; + // check if the depth texture is compatible + if ( + depthTexture !== null && + properties.has( depthTexture ) && + ( renderTarget.width !== depthTexture.image.width || renderTarget.height !== depthTexture.image.height ) + ) { - const geometry = new BufferGeometry(); - geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - geometry.setAttribute( 'color', new Float32BufferAttribute( colors, 3 ) ); + throw new Error( 'WebGLRenderTarget: Attached DepthTexture is initialized to the incorrect size.' ); - const material = new LineBasicMaterial( { vertexColors: true, toneMapped: false } ); + } - super( geometry, material ); + // Swap the depth buffer to the currently attached one + textures.setupDepthRenderbuffer( renderTarget ); - this.type = 'AxesHelper'; + } - } + } - setColors( xAxisColor, yAxisColor, zAxisColor ) { + const texture = renderTarget.texture; - const color = new Color(); - const array = this.geometry.attributes.color.array; + if ( texture.isData3DTexture || texture.isDataArrayTexture || texture.isCompressedArrayTexture ) { - color.set( xAxisColor ); - color.toArray( array, 0 ); - color.toArray( array, 3 ); + isRenderTarget3D = true; - color.set( yAxisColor ); - color.toArray( array, 6 ); - color.toArray( array, 9 ); + } - color.set( zAxisColor ); - color.toArray( array, 12 ); - color.toArray( array, 15 ); + const __webglFramebuffer = properties.get( renderTarget ).__webglFramebuffer; - this.geometry.attributes.color.needsUpdate = true; + if ( renderTarget.isWebGLCubeRenderTarget ) { - return this; + if ( Array.isArray( __webglFramebuffer[ activeCubeFace ] ) ) { - } + framebuffer = __webglFramebuffer[ activeCubeFace ][ activeMipmapLevel ]; - dispose() { + } else { - this.geometry.dispose(); - this.material.dispose(); + framebuffer = __webglFramebuffer[ activeCubeFace ]; - } + } -} + isCube = true; -class ShapePath { + } else if ( ( renderTarget.samples > 0 ) && textures.useMultisampledRTT( renderTarget ) === false ) { - constructor() { + framebuffer = properties.get( renderTarget ).__webglMultisampledFramebuffer; - this.type = 'ShapePath'; + } else { - this.color = new Color(); + if ( Array.isArray( __webglFramebuffer ) ) { - this.subPaths = []; - this.currentPath = null; + framebuffer = __webglFramebuffer[ activeMipmapLevel ]; - } + } else { - moveTo( x, y ) { + framebuffer = __webglFramebuffer; - this.currentPath = new Path(); - this.subPaths.push( this.currentPath ); - this.currentPath.moveTo( x, y ); + } - return this; + } - } + _currentViewport.copy( renderTarget.viewport ); + _currentScissor.copy( renderTarget.scissor ); + _currentScissorTest = renderTarget.scissorTest; - lineTo( x, y ) { + } else { - this.currentPath.lineTo( x, y ); + _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ).floor(); + _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ).floor(); + _currentScissorTest = _scissorTest; - return this; + } - } + // Use a scratch frame buffer if rendering to a mip level to avoid depth buffers + // being bound that are different sizes. + if ( activeMipmapLevel !== 0 ) { - quadraticCurveTo( aCPx, aCPy, aX, aY ) { + framebuffer = _scratchFrameBuffer; - this.currentPath.quadraticCurveTo( aCPx, aCPy, aX, aY ); + } - return this; + const framebufferBound = state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); - } + if ( framebufferBound && useDefaultFramebuffer ) { - bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) { + state.drawBuffers( renderTarget, framebuffer ); - this.currentPath.bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ); + } - return this; + state.viewport( _currentViewport ); + state.scissor( _currentScissor ); + state.setScissorTest( _currentScissorTest ); - } + if ( isCube ) { - splineThru( pts ) { + const textureProperties = properties.get( renderTarget.texture ); + _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + activeCubeFace, textureProperties.__webglTexture, activeMipmapLevel ); - this.currentPath.splineThru( pts ); + } else if ( isRenderTarget3D ) { - return this; + const textureProperties = properties.get( renderTarget.texture ); + const layer = activeCubeFace; + _gl.framebufferTextureLayer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, textureProperties.__webglTexture, activeMipmapLevel, layer ); - } + } else if ( renderTarget !== null && activeMipmapLevel !== 0 ) { - toShapes( isCCW ) { + // Only bind the frame buffer if we are using a scratch frame buffer to render to a mipmap. + // If we rebind the texture when using a multi sample buffer then an error about inconsistent samples will be thrown. + const textureProperties = properties.get( renderTarget.texture ); + _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D, textureProperties.__webglTexture, activeMipmapLevel ); - function toShapesNoHoles( inSubpaths ) { + } - const shapes = []; + _currentMaterialId = -1; // reset current material to ensure correct uniform bindings - for ( let i = 0, l = inSubpaths.length; i < l; i ++ ) { + }; - const tmpPath = inSubpaths[ i ]; + /** + * Reads the pixel data from the given render target into the given buffer. + * + * @param {WebGLRenderTarget} renderTarget - The render target to read from. + * @param {number} x - The `x` coordinate of the copy region's origin. + * @param {number} y - The `y` coordinate of the copy region's origin. + * @param {number} width - The width of the copy region. + * @param {number} height - The height of the copy region. + * @param {TypedArray} buffer - The result buffer. + * @param {number} [activeCubeFaceIndex] - The active cube face index. + * @param {number} [textureIndex=0] - The texture index of an MRT render target. + */ + this.readRenderTargetPixels = function ( renderTarget, x, y, width, height, buffer, activeCubeFaceIndex, textureIndex = 0 ) { - const tmpShape = new Shape(); - tmpShape.curves = tmpPath.curves; + if ( ! ( renderTarget && renderTarget.isWebGLRenderTarget ) ) { - shapes.push( tmpShape ); + console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.' ); + return; } - return shapes; + let framebuffer = properties.get( renderTarget ).__webglFramebuffer; - } + if ( renderTarget.isWebGLCubeRenderTarget && activeCubeFaceIndex !== undefined ) { - function isPointInsidePolygon( inPt, inPolygon ) { + framebuffer = framebuffer[ activeCubeFaceIndex ]; - const polyLen = inPolygon.length; + } - // inPt on polygon contour => immediate success or - // toggling of inside/outside at every single! intersection point of an edge - // with the horizontal line through inPt, left of inPt - // not counting lowerY endpoints of edges and whole edges on that line - let inside = false; - for ( let p = polyLen - 1, q = 0; q < polyLen; p = q ++ ) { + if ( framebuffer ) { - let edgeLowPt = inPolygon[ p ]; - let edgeHighPt = inPolygon[ q ]; + state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); - let edgeDx = edgeHighPt.x - edgeLowPt.x; - let edgeDy = edgeHighPt.y - edgeLowPt.y; + try { - if ( Math.abs( edgeDy ) > Number.EPSILON ) { + const texture = renderTarget.textures[ textureIndex ]; + const textureFormat = texture.format; + const textureType = texture.type; - // not parallel - if ( edgeDy < 0 ) { + if ( ! capabilities.textureFormatReadable( textureFormat ) ) { - edgeLowPt = inPolygon[ q ]; edgeDx = - edgeDx; - edgeHighPt = inPolygon[ p ]; edgeDy = - edgeDy; + console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in RGBA or implementation defined format.' ); + return; } - if ( ( inPt.y < edgeLowPt.y ) || ( inPt.y > edgeHighPt.y ) ) continue; + if ( ! capabilities.textureTypeReadable( textureType ) ) { - if ( inPt.y === edgeLowPt.y ) { + console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in UnsignedByteType or implementation defined type.' ); + return; - if ( inPt.x === edgeLowPt.x ) return true; // inPt is on contour ? - // continue; // no intersection or edgeLowPt => doesn't count !!! + } - } else { + // the following if statement ensures valid read requests (no out-of-bounds pixels, see #8604) - const perpEdge = edgeDy * ( inPt.x - edgeLowPt.x ) - edgeDx * ( inPt.y - edgeLowPt.y ); - if ( perpEdge === 0 ) return true; // inPt is on contour ? - if ( perpEdge < 0 ) continue; - inside = ! inside; // true intersection left of inPt + if ( ( x >= 0 && x <= ( renderTarget.width - width ) ) && ( y >= 0 && y <= ( renderTarget.height - height ) ) ) { + + // when using MRT, select the correct color buffer for the subsequent read command + + if ( renderTarget.textures.length > 1 ) _gl.readBuffer( _gl.COLOR_ATTACHMENT0 + textureIndex ); + + _gl.readPixels( x, y, width, height, utils.convert( textureFormat ), utils.convert( textureType ), buffer ); } - } else { + } finally { - // parallel or collinear - if ( inPt.y !== edgeLowPt.y ) continue; // parallel - // edge lies on the same horizontal line as inPt - if ( ( ( edgeHighPt.x <= inPt.x ) && ( inPt.x <= edgeLowPt.x ) ) || - ( ( edgeLowPt.x <= inPt.x ) && ( inPt.x <= edgeHighPt.x ) ) ) return true; // inPt: Point on contour ! - // continue; + // restore framebuffer of current render target if necessary + + const framebuffer = ( _currentRenderTarget !== null ) ? properties.get( _currentRenderTarget ).__webglFramebuffer : null; + state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); } } - return inside; - - } + }; - const isClockWise = ShapeUtils.isClockWise; + /** + * Asynchronous, non-blocking version of {@link WebGLRenderer#readRenderTargetPixels}. + * + * It is recommended to use this version of `readRenderTargetPixels()` whenever possible. + * + * @async + * @param {WebGLRenderTarget} renderTarget - The render target to read from. + * @param {number} x - The `x` coordinate of the copy region's origin. + * @param {number} y - The `y` coordinate of the copy region's origin. + * @param {number} width - The width of the copy region. + * @param {number} height - The height of the copy region. + * @param {TypedArray} buffer - The result buffer. + * @param {number} [activeCubeFaceIndex] - The active cube face index. + * @param {number} [textureIndex=0] - The texture index of an MRT render target. + * @return {Promise} A Promise that resolves when the read has been finished. The resolve provides the read data as a typed array. + */ + this.readRenderTargetPixelsAsync = async function ( renderTarget, x, y, width, height, buffer, activeCubeFaceIndex, textureIndex = 0 ) { - const subPaths = this.subPaths; - if ( subPaths.length === 0 ) return []; + if ( ! ( renderTarget && renderTarget.isWebGLRenderTarget ) ) { - let solid, tmpPath, tmpShape; - const shapes = []; + throw new Error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.' ); - if ( subPaths.length === 1 ) { + } - tmpPath = subPaths[ 0 ]; - tmpShape = new Shape(); - tmpShape.curves = tmpPath.curves; - shapes.push( tmpShape ); - return shapes; + let framebuffer = properties.get( renderTarget ).__webglFramebuffer; + if ( renderTarget.isWebGLCubeRenderTarget && activeCubeFaceIndex !== undefined ) { - } + framebuffer = framebuffer[ activeCubeFaceIndex ]; - let holesFirst = ! isClockWise( subPaths[ 0 ].getPoints() ); - holesFirst = isCCW ? ! holesFirst : holesFirst; + } - // console.log("Holes first", holesFirst); + if ( framebuffer ) { - const betterShapeHoles = []; - const newShapes = []; - let newShapeHoles = []; - let mainIdx = 0; - let tmpPoints; + // the following if statement ensures valid read requests (no out-of-bounds pixels, see #8604) + if ( ( x >= 0 && x <= ( renderTarget.width - width ) ) && ( y >= 0 && y <= ( renderTarget.height - height ) ) ) { - newShapes[ mainIdx ] = undefined; - newShapeHoles[ mainIdx ] = []; + // set the active frame buffer to the one we want to read + state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); - for ( let i = 0, l = subPaths.length; i < l; i ++ ) { + const texture = renderTarget.textures[ textureIndex ]; + const textureFormat = texture.format; + const textureType = texture.type; - tmpPath = subPaths[ i ]; - tmpPoints = tmpPath.getPoints(); - solid = isClockWise( tmpPoints ); - solid = isCCW ? ! solid : solid; + if ( ! capabilities.textureFormatReadable( textureFormat ) ) { - if ( solid ) { + throw new Error( 'THREE.WebGLRenderer.readRenderTargetPixelsAsync: renderTarget is not in RGBA or implementation defined format.' ); - if ( ( ! holesFirst ) && ( newShapes[ mainIdx ] ) ) mainIdx ++; + } - newShapes[ mainIdx ] = { s: new Shape(), p: tmpPoints }; - newShapes[ mainIdx ].s.curves = tmpPath.curves; + if ( ! capabilities.textureTypeReadable( textureType ) ) { - if ( holesFirst ) mainIdx ++; - newShapeHoles[ mainIdx ] = []; + throw new Error( 'THREE.WebGLRenderer.readRenderTargetPixelsAsync: renderTarget is not in UnsignedByteType or implementation defined type.' ); - //console.log('cw', i); + } - } else { + const glBuffer = _gl.createBuffer(); + _gl.bindBuffer( _gl.PIXEL_PACK_BUFFER, glBuffer ); + _gl.bufferData( _gl.PIXEL_PACK_BUFFER, buffer.byteLength, _gl.STREAM_READ ); - newShapeHoles[ mainIdx ].push( { h: tmpPath, p: tmpPoints[ 0 ] } ); + // when using MRT, select the corect color buffer for the subsequent read command - //console.log('ccw', i); + if ( renderTarget.textures.length > 1 ) _gl.readBuffer( _gl.COLOR_ATTACHMENT0 + textureIndex ); - } + _gl.readPixels( x, y, width, height, utils.convert( textureFormat ), utils.convert( textureType ), 0 ); - } + // reset the frame buffer to the currently set buffer before waiting + const currFramebuffer = _currentRenderTarget !== null ? properties.get( _currentRenderTarget ).__webglFramebuffer : null; + state.bindFramebuffer( _gl.FRAMEBUFFER, currFramebuffer ); - // only Holes? -> probably all Shapes with wrong orientation - if ( ! newShapes[ 0 ] ) return toShapesNoHoles( subPaths ); + // check if the commands have finished every 8 ms + const sync = _gl.fenceSync( _gl.SYNC_GPU_COMMANDS_COMPLETE, 0 ); + _gl.flush(); - if ( newShapes.length > 1 ) { + await probeAsync( _gl, sync, 4 ); - let ambiguous = false; - let toChange = 0; + // read the data and delete the buffer + _gl.bindBuffer( _gl.PIXEL_PACK_BUFFER, glBuffer ); + _gl.getBufferSubData( _gl.PIXEL_PACK_BUFFER, 0, buffer ); + _gl.deleteBuffer( glBuffer ); + _gl.deleteSync( sync ); - for ( let sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) { + return buffer; - betterShapeHoles[ sIdx ] = []; + } else { - } + throw new Error( 'THREE.WebGLRenderer.readRenderTargetPixelsAsync: requested read bounds are out of range.' ); - for ( let sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) { + } - const sho = newShapeHoles[ sIdx ]; + } - for ( let hIdx = 0; hIdx < sho.length; hIdx ++ ) { + }; - const ho = sho[ hIdx ]; - let hole_unassigned = true; + /** + * Copies pixels from the current bound framebuffer into the given texture. + * + * @param {FramebufferTexture} texture - The texture. + * @param {?Vector2} [position=null] - The start position of the copy operation. + * @param {number} [level=0] - The mip level. The default represents the base mip. + */ + this.copyFramebufferToTexture = function ( texture, position = null, level = 0 ) { - for ( let s2Idx = 0; s2Idx < newShapes.length; s2Idx ++ ) { + const levelScale = Math.pow( 2, - level ); + const width = Math.floor( texture.image.width * levelScale ); + const height = Math.floor( texture.image.height * levelScale ); - if ( isPointInsidePolygon( ho.p, newShapes[ s2Idx ].p ) ) { + const x = position !== null ? position.x : 0; + const y = position !== null ? position.y : 0; - if ( sIdx !== s2Idx ) toChange ++; + textures.setTexture2D( texture, 0 ); - if ( hole_unassigned ) { + _gl.copyTexSubImage2D( _gl.TEXTURE_2D, level, 0, 0, x, y, width, height ); - hole_unassigned = false; - betterShapeHoles[ s2Idx ].push( ho ); + state.unbindTexture(); - } else { + }; - ambiguous = true; + const _srcFramebuffer = _gl.createFramebuffer(); + const _dstFramebuffer = _gl.createFramebuffer(); - } + /** + * Copies data of the given source texture into a destination texture. + * + * When using render target textures as `srcTexture` and `dstTexture`, you must make sure both render targets are initialized + * {@link WebGLRenderer#initRenderTarget}. + * + * @param {Texture} srcTexture - The source texture. + * @param {Texture} dstTexture - The destination texture. + * @param {?(Box2|Box3)} [srcRegion=null] - A bounding box which describes the source region. Can be two or three-dimensional. + * @param {?(Vector2|Vector3)} [dstPosition=null] - A vector that represents the origin of the destination region. Can be two or three-dimensional. + * @param {number} [srcLevel=0] - The source mipmap level to copy. + * @param {?number} [dstLevel=null] - The destination mipmap level. + */ + this.copyTextureToTexture = function ( srcTexture, dstTexture, srcRegion = null, dstPosition = null, srcLevel = 0, dstLevel = null ) { - } + // support the previous signature with just a single dst mipmap level + if ( dstLevel === null ) { - } + if ( srcLevel !== 0 ) { - if ( hole_unassigned ) { + // @deprecated, r171 + warnOnce( 'WebGLRenderer: copyTextureToTexture function signature has changed to support src and dst mipmap levels.' ); + dstLevel = srcLevel; + srcLevel = 0; - betterShapeHoles[ sIdx ].push( ho ); + } else { - } + dstLevel = 0; } } - if ( toChange > 0 && ambiguous === false ) { + // gather the necessary dimensions to copy + let width, height, depth, minX, minY, minZ; + let dstX, dstY, dstZ; + const image = srcTexture.isCompressedTexture ? srcTexture.mipmaps[ dstLevel ] : srcTexture.image; + if ( srcRegion !== null ) { - newShapeHoles = betterShapeHoles; + width = srcRegion.max.x - srcRegion.min.x; + height = srcRegion.max.y - srcRegion.min.y; + depth = srcRegion.isBox3 ? srcRegion.max.z - srcRegion.min.z : 1; + minX = srcRegion.min.x; + minY = srcRegion.min.y; + minZ = srcRegion.isBox3 ? srcRegion.min.z : 0; - } + } else { - } + const levelScale = Math.pow( 2, - srcLevel ); + width = Math.floor( image.width * levelScale ); + height = Math.floor( image.height * levelScale ); + if ( srcTexture.isDataArrayTexture ) { - let tmpHoles; + depth = image.depth; - for ( let i = 0, il = newShapes.length; i < il; i ++ ) { + } else if ( srcTexture.isData3DTexture ) { - tmpShape = newShapes[ i ].s; - shapes.push( tmpShape ); - tmpHoles = newShapeHoles[ i ]; + depth = Math.floor( image.depth * levelScale ); - for ( let j = 0, jl = tmpHoles.length; j < jl; j ++ ) { + } else { - tmpShape.holes.push( tmpHoles[ j ].h ); + depth = 1; + + } + + minX = 0; + minY = 0; + minZ = 0; } - } + if ( dstPosition !== null ) { - //console.log("shape", shapes); + dstX = dstPosition.x; + dstY = dstPosition.y; + dstZ = dstPosition.z; - return shapes; + } else { - } + dstX = 0; + dstY = 0; + dstZ = 0; -} + } -class BoxBufferGeometry extends BoxGeometry { // @deprecated, r144 + // Set up the destination target + const glFormat = utils.convert( dstTexture.format ); + const glType = utils.convert( dstTexture.type ); + let glTarget; - constructor( width, height, depth, widthSegments, heightSegments, depthSegments ) { + if ( dstTexture.isData3DTexture ) { - console.warn( 'THREE.BoxBufferGeometry has been renamed to THREE.BoxGeometry.' ); - super( width, height, depth, widthSegments, heightSegments, depthSegments ); + textures.setTexture3D( dstTexture, 0 ); + glTarget = _gl.TEXTURE_3D; + } else if ( dstTexture.isDataArrayTexture || dstTexture.isCompressedArrayTexture ) { - } + textures.setTexture2DArray( dstTexture, 0 ); + glTarget = _gl.TEXTURE_2D_ARRAY; -} + } else { -class CapsuleBufferGeometry extends CapsuleGeometry { // @deprecated, r144 + textures.setTexture2D( dstTexture, 0 ); + glTarget = _gl.TEXTURE_2D; - constructor( radius, length, capSegments, radialSegments ) { + } - console.warn( 'THREE.CapsuleBufferGeometry has been renamed to THREE.CapsuleGeometry.' ); - super( radius, length, capSegments, radialSegments ); + _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, dstTexture.flipY ); + _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, dstTexture.premultiplyAlpha ); + _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, dstTexture.unpackAlignment ); - } + // used for copying data from cpu + const currentUnpackRowLen = _gl.getParameter( _gl.UNPACK_ROW_LENGTH ); + const currentUnpackImageHeight = _gl.getParameter( _gl.UNPACK_IMAGE_HEIGHT ); + const currentUnpackSkipPixels = _gl.getParameter( _gl.UNPACK_SKIP_PIXELS ); + const currentUnpackSkipRows = _gl.getParameter( _gl.UNPACK_SKIP_ROWS ); + const currentUnpackSkipImages = _gl.getParameter( _gl.UNPACK_SKIP_IMAGES ); -} + _gl.pixelStorei( _gl.UNPACK_ROW_LENGTH, image.width ); + _gl.pixelStorei( _gl.UNPACK_IMAGE_HEIGHT, image.height ); + _gl.pixelStorei( _gl.UNPACK_SKIP_PIXELS, minX ); + _gl.pixelStorei( _gl.UNPACK_SKIP_ROWS, minY ); + _gl.pixelStorei( _gl.UNPACK_SKIP_IMAGES, minZ ); -class CircleBufferGeometry extends CircleGeometry { // @deprecated, r144 + // set up the src texture + const isSrc3D = srcTexture.isDataArrayTexture || srcTexture.isData3DTexture; + const isDst3D = dstTexture.isDataArrayTexture || dstTexture.isData3DTexture; + if ( srcTexture.isDepthTexture ) { - constructor( radius, segments, thetaStart, thetaLength ) { + const srcTextureProperties = properties.get( srcTexture ); + const dstTextureProperties = properties.get( dstTexture ); + const srcRenderTargetProperties = properties.get( srcTextureProperties.__renderTarget ); + const dstRenderTargetProperties = properties.get( dstTextureProperties.__renderTarget ); + state.bindFramebuffer( _gl.READ_FRAMEBUFFER, srcRenderTargetProperties.__webglFramebuffer ); + state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, dstRenderTargetProperties.__webglFramebuffer ); - console.warn( 'THREE.CircleBufferGeometry has been renamed to THREE.CircleGeometry.' ); - super( radius, segments, thetaStart, thetaLength ); + for ( let i = 0; i < depth; i ++ ) { - } + // if the source or destination are a 3d target then a layer needs to be bound + if ( isSrc3D ) { -} + _gl.framebufferTextureLayer( _gl.READ_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, properties.get( srcTexture ).__webglTexture, srcLevel, minZ + i ); + _gl.framebufferTextureLayer( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, properties.get( dstTexture ).__webglTexture, dstLevel, dstZ + i ); -class ConeBufferGeometry extends ConeGeometry { // @deprecated, r144 + } - constructor( radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ) { + _gl.blitFramebuffer( minX, minY, width, height, dstX, dstY, width, height, _gl.DEPTH_BUFFER_BIT, _gl.NEAREST ); - console.warn( 'THREE.ConeBufferGeometry has been renamed to THREE.ConeGeometry.' ); - super( radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ); + } - } + state.bindFramebuffer( _gl.READ_FRAMEBUFFER, null ); + state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, null ); -} + } else if ( srcLevel !== 0 || srcTexture.isRenderTargetTexture || properties.has( srcTexture ) ) { -class CylinderBufferGeometry extends CylinderGeometry { // @deprecated, r144 + // get the appropriate frame buffers + const srcTextureProperties = properties.get( srcTexture ); + const dstTextureProperties = properties.get( dstTexture ); - constructor( radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ) { + // bind the frame buffer targets + state.bindFramebuffer( _gl.READ_FRAMEBUFFER, _srcFramebuffer ); + state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, _dstFramebuffer ); - console.warn( 'THREE.CylinderBufferGeometry has been renamed to THREE.CylinderGeometry.' ); - super( radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ); + for ( let i = 0; i < depth; i ++ ) { - } + // assign the correct layers and mip maps to the frame buffers + if ( isSrc3D ) { -} + _gl.framebufferTextureLayer( _gl.READ_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, srcTextureProperties.__webglTexture, srcLevel, minZ + i ); -class DodecahedronBufferGeometry extends DodecahedronGeometry { // @deprecated, r144 + } else { - constructor( radius, detail ) { + _gl.framebufferTexture2D( _gl.READ_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D, srcTextureProperties.__webglTexture, srcLevel ); - console.warn( 'THREE.DodecahedronBufferGeometry has been renamed to THREE.DodecahedronGeometry.' ); - super( radius, detail ); + } - } + if ( isDst3D ) { -} + _gl.framebufferTextureLayer( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, dstTextureProperties.__webglTexture, dstLevel, dstZ + i ); -class ExtrudeBufferGeometry extends ExtrudeGeometry { // @deprecated, r144 + } else { - constructor( shapes, options ) { + _gl.framebufferTexture2D( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D, dstTextureProperties.__webglTexture, dstLevel ); - console.warn( 'THREE.ExtrudeBufferGeometry has been renamed to THREE.ExtrudeGeometry.' ); - super( shapes, options ); + } - } + // copy the data using the fastest function that can achieve the copy + if ( srcLevel !== 0 ) { -} + _gl.blitFramebuffer( minX, minY, width, height, dstX, dstY, width, height, _gl.COLOR_BUFFER_BIT, _gl.NEAREST ); -class IcosahedronBufferGeometry extends IcosahedronGeometry { // @deprecated, r144 + } else if ( isDst3D ) { - constructor( radius, detail ) { + _gl.copyTexSubImage3D( glTarget, dstLevel, dstX, dstY, dstZ + i, minX, minY, width, height ); - console.warn( 'THREE.IcosahedronBufferGeometry has been renamed to THREE.IcosahedronGeometry.' ); - super( radius, detail ); + } else { - } + _gl.copyTexSubImage2D( glTarget, dstLevel, dstX, dstY, minX, minY, width, height ); -} + } -class LatheBufferGeometry extends LatheGeometry { // @deprecated, r144 + } - constructor( points, segments, phiStart, phiLength ) { + // unbind read, draw buffers + state.bindFramebuffer( _gl.READ_FRAMEBUFFER, null ); + state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, null ); - console.warn( 'THREE.LatheBufferGeometry has been renamed to THREE.LatheGeometry.' ); - super( points, segments, phiStart, phiLength ); + } else { - } + if ( isDst3D ) { -} + // copy data into the 3d texture + if ( srcTexture.isDataTexture || srcTexture.isData3DTexture ) { -class OctahedronBufferGeometry extends OctahedronGeometry { // @deprecated, r144 + _gl.texSubImage3D( glTarget, dstLevel, dstX, dstY, dstZ, width, height, depth, glFormat, glType, image.data ); - constructor( radius, detail ) { + } else if ( dstTexture.isCompressedArrayTexture ) { - console.warn( 'THREE.OctahedronBufferGeometry has been renamed to THREE.OctahedronGeometry.' ); - super( radius, detail ); + _gl.compressedTexSubImage3D( glTarget, dstLevel, dstX, dstY, dstZ, width, height, depth, glFormat, image.data ); - } + } else { -} + _gl.texSubImage3D( glTarget, dstLevel, dstX, dstY, dstZ, width, height, depth, glFormat, glType, image ); -class PlaneBufferGeometry extends PlaneGeometry { // @deprecated, r144 + } - constructor( width, height, widthSegments, heightSegments ) { + } else { - console.warn( 'THREE.PlaneBufferGeometry has been renamed to THREE.PlaneGeometry.' ); - super( width, height, widthSegments, heightSegments ); + // copy data into the 2d texture + if ( srcTexture.isDataTexture ) { - } + _gl.texSubImage2D( _gl.TEXTURE_2D, dstLevel, dstX, dstY, width, height, glFormat, glType, image.data ); -} + } else if ( srcTexture.isCompressedTexture ) { -class PolyhedronBufferGeometry extends PolyhedronGeometry { // @deprecated, r144 + _gl.compressedTexSubImage2D( _gl.TEXTURE_2D, dstLevel, dstX, dstY, image.width, image.height, glFormat, image.data ); - constructor( vertices, indices, radius, detail ) { + } else { - console.warn( 'THREE.PolyhedronBufferGeometry has been renamed to THREE.PolyhedronGeometry.' ); - super( vertices, indices, radius, detail ); + _gl.texSubImage2D( _gl.TEXTURE_2D, dstLevel, dstX, dstY, width, height, glFormat, glType, image ); - } + } -} + } + + } -class RingBufferGeometry extends RingGeometry { // @deprecated, r144 + // reset values + _gl.pixelStorei( _gl.UNPACK_ROW_LENGTH, currentUnpackRowLen ); + _gl.pixelStorei( _gl.UNPACK_IMAGE_HEIGHT, currentUnpackImageHeight ); + _gl.pixelStorei( _gl.UNPACK_SKIP_PIXELS, currentUnpackSkipPixels ); + _gl.pixelStorei( _gl.UNPACK_SKIP_ROWS, currentUnpackSkipRows ); + _gl.pixelStorei( _gl.UNPACK_SKIP_IMAGES, currentUnpackSkipImages ); - constructor( innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength ) { + // Generate mipmaps only when copying level 0 + if ( dstLevel === 0 && dstTexture.generateMipmaps ) { - console.warn( 'THREE.RingBufferGeometry has been renamed to THREE.RingGeometry.' ); - super( innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength ); + _gl.generateMipmap( glTarget ); - } + } -} + state.unbindTexture(); -class ShapeBufferGeometry extends ShapeGeometry { // @deprecated, r144 + }; - constructor( shapes, curveSegments ) { + this.copyTextureToTexture3D = function ( srcTexture, dstTexture, srcRegion = null, dstPosition = null, level = 0 ) { - console.warn( 'THREE.ShapeBufferGeometry has been renamed to THREE.ShapeGeometry.' ); - super( shapes, curveSegments ); + // @deprecated, r170 + warnOnce( 'WebGLRenderer: copyTextureToTexture3D function has been deprecated. Use "copyTextureToTexture" instead.' ); - } + return this.copyTextureToTexture( srcTexture, dstTexture, srcRegion, dstPosition, level ); -} + }; -class SphereBufferGeometry extends SphereGeometry { // @deprecated, r144 + /** + * Initializes the given WebGLRenderTarget memory. Useful for initializing a render target so data + * can be copied into it using {@link WebGLRenderer#copyTextureToTexture} before it has been + * rendered to. + * + * @param {WebGLRenderTarget} target - The render target. + */ + this.initRenderTarget = function ( target ) { - constructor( radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength ) { + if ( properties.get( target ).__webglFramebuffer === undefined ) { - console.warn( 'THREE.SphereBufferGeometry has been renamed to THREE.SphereGeometry.' ); - super( radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength ); + textures.setupRenderTarget( target ); - } + } -} + }; + + /** + * Initializes the given texture. Useful for preloading a texture rather than waiting until first + * render (which can cause noticeable lags due to decode and GPU upload overhead). + * + * @param {Texture} texture - The texture. + */ + this.initTexture = function ( texture ) { -class TetrahedronBufferGeometry extends TetrahedronGeometry { // @deprecated, r144 + if ( texture.isCubeTexture ) { - constructor( radius, detail ) { + textures.setTextureCube( texture, 0 ); - console.warn( 'THREE.TetrahedronBufferGeometry has been renamed to THREE.TetrahedronGeometry.' ); - super( radius, detail ); + } else if ( texture.isData3DTexture ) { - } + textures.setTexture3D( texture, 0 ); -} + } else if ( texture.isDataArrayTexture || texture.isCompressedArrayTexture ) { -class TorusBufferGeometry extends TorusGeometry { // @deprecated, r144 + textures.setTexture2DArray( texture, 0 ); - constructor( radius, tube, radialSegments, tubularSegments, arc ) { + } else { - console.warn( 'THREE.TorusBufferGeometry has been renamed to THREE.TorusGeometry.' ); - super( radius, tube, radialSegments, tubularSegments, arc ); + textures.setTexture2D( texture, 0 ); - } + } -} + state.unbindTexture(); -class TorusKnotBufferGeometry extends TorusKnotGeometry { // @deprecated, r144 + }; - constructor( radius, tube, tubularSegments, radialSegments, p, q ) { + /** + * Can be used to reset the internal WebGL state. This method is mostly + * relevant for applications which share a single WebGL context across + * multiple WebGL libraries. + */ + this.resetState = function () { - console.warn( 'THREE.TorusKnotBufferGeometry has been renamed to THREE.TorusKnotGeometry.' ); - super( radius, tube, tubularSegments, radialSegments, p, q ); + _currentActiveCubeFace = 0; + _currentActiveMipmapLevel = 0; + _currentRenderTarget = null; - } + state.reset(); + bindingStates.reset(); -} + }; -class TubeBufferGeometry extends TubeGeometry { // @deprecated, r144 + if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) { - constructor( path, tubularSegments, radius, radialSegments, closed ) { + __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'observe', { detail: this } ) ); - console.warn( 'THREE.TubeBufferGeometry has been renamed to THREE.TubeGeometry.' ); - super( path, tubularSegments, radius, radialSegments, closed ); + } } -} + /** + * Defines the coordinate system of the renderer. + * + * In `WebGLRenderer`, the value is always `WebGLCoordinateSystem`. + * + * @type {WebGLCoordinateSystem|WebGPUCoordinateSystem} + * @default WebGLCoordinateSystem + * @readonly + */ + get coordinateSystem() { -if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) { + return WebGLCoordinateSystem; - __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'register', { detail: { - revision: REVISION, - } } ) ); + } -} + /** + * Defines the output color space of the renderer. + * + * @type {SRGBColorSpace|LinearSRGBColorSpace} + * @default SRGBColorSpace + */ + get outputColorSpace() { -if ( typeof window !== 'undefined' ) { + return this._outputColorSpace; - if ( window.__THREE__ ) { + } - console.warn( 'WARNING: Multiple instances of Three.js being imported.' ); + set outputColorSpace( colorSpace ) { - } else { + this._outputColorSpace = colorSpace; - window.__THREE__ = REVISION; + const gl = this.getContext(); + gl.drawingBufferColorSpace = ColorManagement._getDrawingBufferColorSpace( colorSpace ); + gl.unpackColorSpace = ColorManagement._getUnpackColorSpace(); } @@ -51400,12 +76302,12 @@ exports.AddEquation = AddEquation; exports.AddOperation = AddOperation; exports.AdditiveAnimationBlendMode = AdditiveAnimationBlendMode; exports.AdditiveBlending = AdditiveBlending; +exports.AgXToneMapping = AgXToneMapping; exports.AlphaFormat = AlphaFormat; exports.AlwaysCompare = AlwaysCompare; exports.AlwaysDepth = AlwaysDepth; exports.AlwaysStencilFunc = AlwaysStencilFunc; exports.AmbientLight = AmbientLight; -exports.AmbientLightProbe = AmbientLightProbe; exports.AnimationAction = AnimationAction; exports.AnimationClip = AnimationClip; exports.AnimationLoader = AnimationLoader; @@ -51415,6 +76317,7 @@ exports.AnimationUtils = AnimationUtils; exports.ArcCurve = ArcCurve; exports.ArrayCamera = ArrayCamera; exports.ArrowHelper = ArrowHelper; +exports.AttachedBindMode = AttachedBindMode; exports.Audio = Audio; exports.AudioAnalyser = AudioAnalyser; exports.AudioContext = AudioContext; @@ -51424,12 +76327,12 @@ exports.AxesHelper = AxesHelper; exports.BackSide = BackSide; exports.BasicDepthPacking = BasicDepthPacking; exports.BasicShadowMap = BasicShadowMap; +exports.BatchedMesh = BatchedMesh; exports.Bone = Bone; exports.BooleanKeyframeTrack = BooleanKeyframeTrack; exports.Box2 = Box2; exports.Box3 = Box3; exports.Box3Helper = Box3Helper; -exports.BoxBufferGeometry = BoxBufferGeometry; exports.BoxGeometry = BoxGeometry; exports.BoxHelper = BoxHelper; exports.BufferAttribute = BufferAttribute; @@ -51440,11 +76343,9 @@ exports.Cache = Cache; exports.Camera = Camera; exports.CameraHelper = CameraHelper; exports.CanvasTexture = CanvasTexture; -exports.CapsuleBufferGeometry = CapsuleBufferGeometry; exports.CapsuleGeometry = CapsuleGeometry; exports.CatmullRomCurve3 = CatmullRomCurve3; exports.CineonToneMapping = CineonToneMapping; -exports.CircleBufferGeometry = CircleBufferGeometry; exports.CircleGeometry = CircleGeometry; exports.ClampToEdgeWrapping = ClampToEdgeWrapping; exports.Clock = Clock; @@ -51452,10 +76353,13 @@ exports.Color = Color; exports.ColorKeyframeTrack = ColorKeyframeTrack; exports.ColorManagement = ColorManagement; exports.CompressedArrayTexture = CompressedArrayTexture; +exports.CompressedCubeTexture = CompressedCubeTexture; exports.CompressedTexture = CompressedTexture; exports.CompressedTextureLoader = CompressedTextureLoader; -exports.ConeBufferGeometry = ConeBufferGeometry; exports.ConeGeometry = ConeGeometry; +exports.ConstantAlphaFactor = ConstantAlphaFactor; +exports.ConstantColorFactor = ConstantColorFactor; +exports.Controls = Controls; exports.CubeCamera = CubeCamera; exports.CubeReflectionMapping = CubeReflectionMapping; exports.CubeRefractionMapping = CubeRefractionMapping; @@ -51473,7 +76377,6 @@ exports.Curve = Curve; exports.CurvePath = CurvePath; exports.CustomBlending = CustomBlending; exports.CustomToneMapping = CustomToneMapping; -exports.CylinderBufferGeometry = CylinderBufferGeometry; exports.CylinderGeometry = CylinderGeometry; exports.Cylindrical = Cylindrical; exports.Data3DTexture = Data3DTexture; @@ -51487,11 +76390,10 @@ exports.DefaultLoadingManager = DefaultLoadingManager; exports.DepthFormat = DepthFormat; exports.DepthStencilFormat = DepthStencilFormat; exports.DepthTexture = DepthTexture; +exports.DetachedBindMode = DetachedBindMode; exports.DirectionalLight = DirectionalLight; exports.DirectionalLightHelper = DirectionalLightHelper; exports.DiscreteInterpolant = DiscreteInterpolant; -exports.DisplayP3ColorSpace = DisplayP3ColorSpace; -exports.DodecahedronBufferGeometry = DodecahedronBufferGeometry; exports.DodecahedronGeometry = DodecahedronGeometry; exports.DoubleSide = DoubleSide; exports.DstAlphaFactor = DstAlphaFactor; @@ -51508,18 +76410,17 @@ exports.EquirectangularReflectionMapping = EquirectangularReflectionMapping; exports.EquirectangularRefractionMapping = EquirectangularRefractionMapping; exports.Euler = Euler; exports.EventDispatcher = EventDispatcher; -exports.ExtrudeBufferGeometry = ExtrudeBufferGeometry; exports.ExtrudeGeometry = ExtrudeGeometry; exports.FileLoader = FileLoader; exports.Float16BufferAttribute = Float16BufferAttribute; exports.Float32BufferAttribute = Float32BufferAttribute; -exports.Float64BufferAttribute = Float64BufferAttribute; exports.FloatType = FloatType; exports.Fog = Fog; exports.FogExp2 = FogExp2; exports.FramebufferTexture = FramebufferTexture; exports.FrontSide = FrontSide; exports.Frustum = Frustum; +exports.FrustumArray = FrustumArray; exports.GLBufferAttribute = GLBufferAttribute; exports.GLSL1 = GLSL1; exports.GLSL3 = GLSL3; @@ -51534,8 +76435,6 @@ exports.Group = Group; exports.HalfFloatType = HalfFloatType; exports.HemisphereLight = HemisphereLight; exports.HemisphereLightHelper = HemisphereLightHelper; -exports.HemisphereLightProbe = HemisphereLightProbe; -exports.IcosahedronBufferGeometry = IcosahedronBufferGeometry; exports.IcosahedronGeometry = IcosahedronGeometry; exports.ImageBitmapLoader = ImageBitmapLoader; exports.ImageLoader = ImageLoader; @@ -51556,11 +76455,12 @@ exports.Interpolant = Interpolant; exports.InterpolateDiscrete = InterpolateDiscrete; exports.InterpolateLinear = InterpolateLinear; exports.InterpolateSmooth = InterpolateSmooth; +exports.InterpolationSamplingMode = InterpolationSamplingMode; +exports.InterpolationSamplingType = InterpolationSamplingType; exports.InvertStencilOp = InvertStencilOp; exports.KeepStencilOp = KeepStencilOp; exports.KeyframeTrack = KeyframeTrack; exports.LOD = LOD; -exports.LatheBufferGeometry = LatheBufferGeometry; exports.LatheGeometry = LatheGeometry; exports.Layers = Layers; exports.LessCompare = LessCompare; @@ -51579,7 +76479,6 @@ exports.LineCurve3 = LineCurve3; exports.LineDashedMaterial = LineDashedMaterial; exports.LineLoop = LineLoop; exports.LineSegments = LineSegments; -exports.LinearEncoding = LinearEncoding; exports.LinearFilter = LinearFilter; exports.LinearInterpolant = LinearInterpolant; exports.LinearMipMapLinearFilter = LinearMipMapLinearFilter; @@ -51588,18 +76487,18 @@ exports.LinearMipmapLinearFilter = LinearMipmapLinearFilter; exports.LinearMipmapNearestFilter = LinearMipmapNearestFilter; exports.LinearSRGBColorSpace = LinearSRGBColorSpace; exports.LinearToneMapping = LinearToneMapping; +exports.LinearTransfer = LinearTransfer; exports.Loader = Loader; exports.LoaderUtils = LoaderUtils; exports.LoadingManager = LoadingManager; exports.LoopOnce = LoopOnce; exports.LoopPingPong = LoopPingPong; exports.LoopRepeat = LoopRepeat; -exports.LuminanceAlphaFormat = LuminanceAlphaFormat; -exports.LuminanceFormat = LuminanceFormat; exports.MOUSE = MOUSE; exports.Material = Material; exports.MaterialLoader = MaterialLoader; exports.MathUtils = MathUtils; +exports.Matrix2 = Matrix2; exports.Matrix3 = Matrix3; exports.Matrix4 = Matrix4; exports.MaxEquation = MaxEquation; @@ -51624,6 +76523,7 @@ exports.NearestMipMapLinearFilter = NearestMipMapLinearFilter; exports.NearestMipMapNearestFilter = NearestMipMapNearestFilter; exports.NearestMipmapLinearFilter = NearestMipmapLinearFilter; exports.NearestMipmapNearestFilter = NearestMipmapNearestFilter; +exports.NeutralToneMapping = NeutralToneMapping; exports.NeverCompare = NeverCompare; exports.NeverDepth = NeverDepth; exports.NeverStencilFunc = NeverStencilFunc; @@ -51639,9 +76539,10 @@ exports.NumberKeyframeTrack = NumberKeyframeTrack; exports.Object3D = Object3D; exports.ObjectLoader = ObjectLoader; exports.ObjectSpaceNormalMap = ObjectSpaceNormalMap; -exports.OctahedronBufferGeometry = OctahedronBufferGeometry; exports.OctahedronGeometry = OctahedronGeometry; exports.OneFactor = OneFactor; +exports.OneMinusConstantAlphaFactor = OneMinusConstantAlphaFactor; +exports.OneMinusConstantColorFactor = OneMinusConstantColorFactor; exports.OneMinusDstAlphaFactor = OneMinusDstAlphaFactor; exports.OneMinusDstColorFactor = OneMinusDstColorFactor; exports.OneMinusSrcAlphaFactor = OneMinusSrcAlphaFactor; @@ -51653,7 +76554,6 @@ exports.PMREMGenerator = PMREMGenerator; exports.Path = Path; exports.PerspectiveCamera = PerspectiveCamera; exports.Plane = Plane; -exports.PlaneBufferGeometry = PlaneBufferGeometry; exports.PlaneGeometry = PlaneGeometry; exports.PlaneHelper = PlaneHelper; exports.PointLight = PointLight; @@ -51661,7 +76561,6 @@ exports.PointLightHelper = PointLightHelper; exports.Points = Points; exports.PointsMaterial = PointsMaterial; exports.PolarGridHelper = PolarGridHelper; -exports.PolyhedronBufferGeometry = PolyhedronBufferGeometry; exports.PolyhedronGeometry = PolyhedronGeometry; exports.PositionalAudio = PositionalAudio; exports.PropertyBinding = PropertyBinding; @@ -51698,11 +76597,17 @@ exports.RGBA_PVRTC_4BPPV1_Format = RGBA_PVRTC_4BPPV1_Format; exports.RGBA_S3TC_DXT1_Format = RGBA_S3TC_DXT1_Format; exports.RGBA_S3TC_DXT3_Format = RGBA_S3TC_DXT3_Format; exports.RGBA_S3TC_DXT5_Format = RGBA_S3TC_DXT5_Format; +exports.RGBDepthPacking = RGBDepthPacking; +exports.RGBFormat = RGBFormat; +exports.RGBIntegerFormat = RGBIntegerFormat; +exports.RGB_BPTC_SIGNED_Format = RGB_BPTC_SIGNED_Format; +exports.RGB_BPTC_UNSIGNED_Format = RGB_BPTC_UNSIGNED_Format; exports.RGB_ETC1_Format = RGB_ETC1_Format; exports.RGB_ETC2_Format = RGB_ETC2_Format; exports.RGB_PVRTC_2BPPV1_Format = RGB_PVRTC_2BPPV1_Format; exports.RGB_PVRTC_4BPPV1_Format = RGB_PVRTC_4BPPV1_Format; exports.RGB_S3TC_DXT1_Format = RGB_S3TC_DXT1_Format; +exports.RGDepthPacking = RGDepthPacking; exports.RGFormat = RGFormat; exports.RGIntegerFormat = RGIntegerFormat; exports.RawShaderMaterial = RawShaderMaterial; @@ -51712,21 +76617,22 @@ exports.RectAreaLight = RectAreaLight; exports.RedFormat = RedFormat; exports.RedIntegerFormat = RedIntegerFormat; exports.ReinhardToneMapping = ReinhardToneMapping; +exports.RenderTarget = RenderTarget; +exports.RenderTarget3D = RenderTarget3D; exports.RepeatWrapping = RepeatWrapping; exports.ReplaceStencilOp = ReplaceStencilOp; exports.ReverseSubtractEquation = ReverseSubtractEquation; -exports.RingBufferGeometry = RingBufferGeometry; exports.RingGeometry = RingGeometry; exports.SIGNED_RED_GREEN_RGTC2_Format = SIGNED_RED_GREEN_RGTC2_Format; exports.SIGNED_RED_RGTC1_Format = SIGNED_RED_RGTC1_Format; exports.SRGBColorSpace = SRGBColorSpace; +exports.SRGBTransfer = SRGBTransfer; exports.Scene = Scene; exports.ShaderChunk = ShaderChunk; exports.ShaderLib = ShaderLib; exports.ShaderMaterial = ShaderMaterial; exports.ShadowMaterial = ShadowMaterial; exports.Shape = Shape; -exports.ShapeBufferGeometry = ShapeBufferGeometry; exports.ShapeGeometry = ShapeGeometry; exports.ShapePath = ShapePath; exports.ShapeUtils = ShapeUtils; @@ -51736,7 +76642,6 @@ exports.SkeletonHelper = SkeletonHelper; exports.SkinnedMesh = SkinnedMesh; exports.Source = Source; exports.Sphere = Sphere; -exports.SphereBufferGeometry = SphereBufferGeometry; exports.SphereGeometry = SphereGeometry; exports.Spherical = Spherical; exports.SphericalHarmonics3 = SphericalHarmonics3; @@ -51760,21 +76665,18 @@ exports.SubtractEquation = SubtractEquation; exports.SubtractiveBlending = SubtractiveBlending; exports.TOUCH = TOUCH; exports.TangentSpaceNormalMap = TangentSpaceNormalMap; -exports.TetrahedronBufferGeometry = TetrahedronBufferGeometry; exports.TetrahedronGeometry = TetrahedronGeometry; exports.Texture = Texture; exports.TextureLoader = TextureLoader; -exports.TorusBufferGeometry = TorusBufferGeometry; +exports.TextureUtils = TextureUtils; +exports.TimestampQuery = TimestampQuery; exports.TorusGeometry = TorusGeometry; -exports.TorusKnotBufferGeometry = TorusKnotBufferGeometry; exports.TorusKnotGeometry = TorusKnotGeometry; exports.Triangle = Triangle; exports.TriangleFanDrawMode = TriangleFanDrawMode; exports.TriangleStripDrawMode = TriangleStripDrawMode; exports.TrianglesDrawMode = TrianglesDrawMode; -exports.TubeBufferGeometry = TubeBufferGeometry; exports.TubeGeometry = TubeGeometry; -exports.TwoPassDoubleSide = TwoPassDoubleSide; exports.UVMapping = UVMapping; exports.Uint16BufferAttribute = Uint16BufferAttribute; exports.Uint32BufferAttribute = Uint32BufferAttribute; @@ -51786,6 +76688,7 @@ exports.UniformsLib = UniformsLib; exports.UniformsUtils = UniformsUtils; exports.UnsignedByteType = UnsignedByteType; exports.UnsignedInt248Type = UnsignedInt248Type; +exports.UnsignedInt5999Type = UnsignedInt5999Type; exports.UnsignedIntType = UnsignedIntType; exports.UnsignedShort4444Type = UnsignedShort4444Type; exports.UnsignedShort5551Type = UnsignedShort5551Type; @@ -51795,20 +76698,21 @@ exports.Vector2 = Vector2; exports.Vector3 = Vector3; exports.Vector4 = Vector4; exports.VectorKeyframeTrack = VectorKeyframeTrack; +exports.VideoFrameTexture = VideoFrameTexture; exports.VideoTexture = VideoTexture; -exports.WebGL1Renderer = WebGL1Renderer; exports.WebGL3DRenderTarget = WebGL3DRenderTarget; exports.WebGLArrayRenderTarget = WebGLArrayRenderTarget; +exports.WebGLCoordinateSystem = WebGLCoordinateSystem; exports.WebGLCubeRenderTarget = WebGLCubeRenderTarget; -exports.WebGLMultipleRenderTargets = WebGLMultipleRenderTargets; exports.WebGLRenderTarget = WebGLRenderTarget; exports.WebGLRenderer = WebGLRenderer; exports.WebGLUtils = WebGLUtils; +exports.WebGPUCoordinateSystem = WebGPUCoordinateSystem; +exports.WebXRController = WebXRController; exports.WireframeGeometry = WireframeGeometry; exports.WrapAroundEnding = WrapAroundEnding; exports.ZeroCurvatureEnding = ZeroCurvatureEnding; exports.ZeroFactor = ZeroFactor; exports.ZeroSlopeEnding = ZeroSlopeEnding; exports.ZeroStencilOp = ZeroStencilOp; -exports._SRGBAFormat = _SRGBAFormat; -exports.sRGBEncoding = sRGBEncoding; +exports.createCanvasElement = createCanvasElement; diff --git a/build/three.core.js b/build/three.core.js new file mode 100644 index 00000000000000..84a6dd16a32964 --- /dev/null +++ b/build/three.core.js @@ -0,0 +1,58157 @@ +/** + * @license + * Copyright 2010-2025 Three.js Authors + * SPDX-License-Identifier: MIT + */ +const REVISION = '178'; + +/** + * Represents mouse buttons and interaction types in context of controls. + * + * @type {ConstantsMouse} + * @constant + */ +const MOUSE = { LEFT: 0, MIDDLE: 1, RIGHT: 2, ROTATE: 0, DOLLY: 1, PAN: 2 }; + +/** + * Represents touch interaction types in context of controls. + * + * @type {ConstantsTouch} + * @constant + */ +const TOUCH = { ROTATE: 0, PAN: 1, DOLLY_PAN: 2, DOLLY_ROTATE: 3 }; + +/** + * Disables face culling. + * + * @type {number} + * @constant + */ +const CullFaceNone = 0; + +/** + * Culls back faces. + * + * @type {number} + * @constant + */ +const CullFaceBack = 1; + +/** + * Culls front faces. + * + * @type {number} + * @constant + */ +const CullFaceFront = 2; + +/** + * Culls both front and back faces. + * + * @type {number} + * @constant + */ +const CullFaceFrontBack = 3; + +/** + * Gives unfiltered shadow maps - fastest, but lowest quality. + * + * @type {number} + * @constant + */ +const BasicShadowMap = 0; + +/** + * Filters shadow maps using the Percentage-Closer Filtering (PCF) algorithm. + * + * @type {number} + * @constant + */ +const PCFShadowMap = 1; + +/** + * Filters shadow maps using the Percentage-Closer Filtering (PCF) algorithm with + * better soft shadows especially when using low-resolution shadow maps. + * + * @type {number} + * @constant + */ +const PCFSoftShadowMap = 2; + +/** + * Filters shadow maps using the Variance Shadow Map (VSM) algorithm. + * When using VSMShadowMap all shadow receivers will also cast shadows. + * + * @type {number} + * @constant + */ +const VSMShadowMap = 3; + +/** + * Only front faces are rendered. + * + * @type {number} + * @constant + */ +const FrontSide = 0; + +/** + * Only back faces are rendered. + * + * @type {number} + * @constant + */ +const BackSide = 1; + +/** + * Both front and back faces are rendered. + * + * @type {number} + * @constant + */ +const DoubleSide = 2; + +/** + * No blending is performed which effectively disables + * alpha transparency. + * + * @type {number} + * @constant + */ +const NoBlending = 0; + +/** + * The default blending. + * + * @type {number} + * @constant + */ +const NormalBlending = 1; + +/** + * Represents additive blending. + * + * @type {number} + * @constant + */ +const AdditiveBlending = 2; + +/** + * Represents subtractive blending. + * + * @type {number} + * @constant + */ +const SubtractiveBlending = 3; + +/** + * Represents multiply blending. + * + * @type {number} + * @constant + */ +const MultiplyBlending = 4; + +/** + * Represents custom blending. + * + * @type {number} + * @constant + */ +const CustomBlending = 5; + +/** + * A `source + destination` blending equation. + * + * @type {number} + * @constant + */ +const AddEquation = 100; + +/** + * A `source - destination` blending equation. + * + * @type {number} + * @constant + */ +const SubtractEquation = 101; + +/** + * A `destination - source` blending equation. + * + * @type {number} + * @constant + */ +const ReverseSubtractEquation = 102; + +/** + * A blend equation that uses the minimum of source and destination. + * + * @type {number} + * @constant + */ +const MinEquation = 103; + +/** + * A blend equation that uses the maximum of source and destination. + * + * @type {number} + * @constant + */ +const MaxEquation = 104; + +/** + * Multiplies all colors by `0`. + * + * @type {number} + * @constant + */ +const ZeroFactor = 200; + +/** + * Multiplies all colors by `1`. + * + * @type {number} + * @constant + */ +const OneFactor = 201; + +/** + * Multiplies all colors by the source colors. + * + * @type {number} + * @constant + */ +const SrcColorFactor = 202; + +/** + * Multiplies all colors by `1` minus each source color. + * + * @type {number} + * @constant + */ +const OneMinusSrcColorFactor = 203; + +/** + * Multiplies all colors by the source alpha value. + * + * @type {number} + * @constant + */ +const SrcAlphaFactor = 204; + +/** + * Multiplies all colors by 1 minus the source alpha value. + * + * @type {number} + * @constant + */ +const OneMinusSrcAlphaFactor = 205; + +/** + * Multiplies all colors by the destination alpha value. + * + * @type {number} + * @constant + */ +const DstAlphaFactor = 206; + +/** + * Multiplies all colors by `1` minus the destination alpha value. + * + * @type {number} + * @constant + */ +const OneMinusDstAlphaFactor = 207; + +/** + * Multiplies all colors by the destination color. + * + * @type {number} + * @constant + */ +const DstColorFactor = 208; + +/** + * Multiplies all colors by `1` minus each destination color. + * + * @type {number} + * @constant + */ +const OneMinusDstColorFactor = 209; + +/** + * Multiplies the RGB colors by the smaller of either the source alpha + * value or the value of `1` minus the destination alpha value. The alpha + * value is multiplied by `1`. + * + * @type {number} + * @constant + */ +const SrcAlphaSaturateFactor = 210; + +/** + * Multiplies all colors by a constant color. + * + * @type {number} + * @constant + */ +const ConstantColorFactor = 211; + +/** + * Multiplies all colors by `1` minus a constant color. + * + * @type {number} + * @constant + */ +const OneMinusConstantColorFactor = 212; + +/** + * Multiplies all colors by a constant alpha value. + * + * @type {number} + * @constant + */ +const ConstantAlphaFactor = 213; + +/** + * Multiplies all colors by 1 minus a constant alpha value. + * + * @type {number} + * @constant + */ +const OneMinusConstantAlphaFactor = 214; + +/** + * Never pass. + * + * @type {number} + * @constant + */ +const NeverDepth = 0; + +/** + * Always pass. + * + * @type {number} + * @constant + */ +const AlwaysDepth = 1; + +/** + * Pass if the incoming value is less than the depth buffer value. + * + * @type {number} + * @constant + */ +const LessDepth = 2; + +/** + * Pass if the incoming value is less than or equal to the depth buffer value. + * + * @type {number} + * @constant + */ +const LessEqualDepth = 3; + +/** + * Pass if the incoming value equals the depth buffer value. + * + * @type {number} + * @constant + */ +const EqualDepth = 4; + +/** + * Pass if the incoming value is greater than or equal to the depth buffer value. + * + * @type {number} + * @constant + */ +const GreaterEqualDepth = 5; + +/** + * Pass if the incoming value is greater than the depth buffer value. + * + * @type {number} + * @constant + */ +const GreaterDepth = 6; + +/** + * Pass if the incoming value is not equal to the depth buffer value. + * + * @type {number} + * @constant + */ +const NotEqualDepth = 7; + +/** + * Multiplies the environment map color with the surface color. + * + * @type {number} + * @constant + */ +const MultiplyOperation = 0; + +/** + * Uses reflectivity to blend between the two colors. + * + * @type {number} + * @constant + */ +const MixOperation = 1; + +/** + * Adds the two colors. + * + * @type {number} + * @constant + */ +const AddOperation = 2; + +/** + * No tone mapping is applied. + * + * @type {number} + * @constant + */ +const NoToneMapping = 0; + +/** + * Linear tone mapping. + * + * @type {number} + * @constant + */ +const LinearToneMapping = 1; + +/** + * Reinhard tone mapping. + * + * @type {number} + * @constant + */ +const ReinhardToneMapping = 2; + +/** + * Cineon tone mapping. + * + * @type {number} + * @constant + */ +const CineonToneMapping = 3; + +/** + * ACES Filmic tone mapping. + * + * @type {number} + * @constant + */ +const ACESFilmicToneMapping = 4; + +/** + * Custom tone mapping. + * + * Expects a custom implementation by modifying shader code of the material's fragment shader. + * + * @type {number} + * @constant + */ +const CustomToneMapping = 5; + +/** + * AgX tone mapping. + * + * @type {number} + * @constant + */ +const AgXToneMapping = 6; + +/** + * Neutral tone mapping. + * + * Implementation based on the Khronos 3D Commerce Group standard tone mapping. + * + * @type {number} + * @constant + */ +const NeutralToneMapping = 7; + +/** + * The skinned mesh shares the same world space as the skeleton. + * + * @type {string} + * @constant + */ +const AttachedBindMode = 'attached'; + +/** + * The skinned mesh does not share the same world space as the skeleton. + * This is useful when a skeleton is shared across multiple skinned meshes. + * + * @type {string} + * @constant + */ +const DetachedBindMode = 'detached'; + +/** + * Maps textures using the geometry's UV coordinates. + * + * @type {number} + * @constant + */ +const UVMapping = 300; + +/** + * Reflection mapping for cube textures. + * + * @type {number} + * @constant + */ +const CubeReflectionMapping = 301; + +/** + * Refraction mapping for cube textures. + * + * @type {number} + * @constant + */ +const CubeRefractionMapping = 302; + +/** + * Reflection mapping for equirectangular textures. + * + * @type {number} + * @constant + */ +const EquirectangularReflectionMapping = 303; + +/** + * Refraction mapping for equirectangular textures. + * + * @type {number} + * @constant + */ +const EquirectangularRefractionMapping = 304; + +/** + * Reflection mapping for PMREM textures. + * + * @type {number} + * @constant + */ +const CubeUVReflectionMapping = 306; + +/** + * The texture will simply repeat to infinity. + * + * @type {number} + * @constant + */ +const RepeatWrapping = 1000; + +/** + * The last pixel of the texture stretches to the edge of the mesh. + * + * @type {number} + * @constant + */ +const ClampToEdgeWrapping = 1001; + +/** + * The texture will repeats to infinity, mirroring on each repeat. + * + * @type {number} + * @constant + */ +const MirroredRepeatWrapping = 1002; + +/** + * Returns the value of the texture element that is nearest (in Manhattan distance) + * to the specified texture coordinates. + * + * @type {number} + * @constant + */ +const NearestFilter = 1003; + +/** + * Chooses the mipmap that most closely matches the size of the pixel being textured + * and uses the `NearestFilter` criterion (the texel nearest to the center of the pixel) + * to produce a texture value. + * + * @type {number} + * @constant + */ +const NearestMipmapNearestFilter = 1004; +const NearestMipMapNearestFilter = 1004; // legacy + +/** + * Chooses the two mipmaps that most closely match the size of the pixel being textured and + * uses the `NearestFilter` criterion to produce a texture value from each mipmap. + * The final texture value is a weighted average of those two values. + * + * @type {number} + * @constant + */ +const NearestMipmapLinearFilter = 1005; +const NearestMipMapLinearFilter = 1005; // legacy + +/** + * Returns the weighted average of the four texture elements that are closest to the specified + * texture coordinates, and can include items wrapped or repeated from other parts of a texture, + * depending on the values of `wrapS` and `wrapT`, and on the exact mapping. + * + * @type {number} + * @constant + */ +const LinearFilter = 1006; + +/** + * Chooses the mipmap that most closely matches the size of the pixel being textured and uses + * the `LinearFilter` criterion (a weighted average of the four texels that are closest to the + * center of the pixel) to produce a texture value. + * + * @type {number} + * @constant + */ +const LinearMipmapNearestFilter = 1007; +const LinearMipMapNearestFilter = 1007; // legacy + +/** + * Chooses the two mipmaps that most closely match the size of the pixel being textured and uses + * the `LinearFilter` criterion to produce a texture value from each mipmap. The final texture value + * is a weighted average of those two values. + * + * @type {number} + * @constant + */ +const LinearMipmapLinearFilter = 1008; +const LinearMipMapLinearFilter = 1008; // legacy + +/** + * An unsigned byte data type for textures. + * + * @type {number} + * @constant + */ +const UnsignedByteType = 1009; + +/** + * A byte data type for textures. + * + * @type {number} + * @constant + */ +const ByteType = 1010; + +/** + * A short data type for textures. + * + * @type {number} + * @constant + */ +const ShortType = 1011; + +/** + * An unsigned short data type for textures. + * + * @type {number} + * @constant + */ +const UnsignedShortType = 1012; + +/** + * An int data type for textures. + * + * @type {number} + * @constant + */ +const IntType = 1013; + +/** + * An unsigned int data type for textures. + * + * @type {number} + * @constant + */ +const UnsignedIntType = 1014; + +/** + * A float data type for textures. + * + * @type {number} + * @constant + */ +const FloatType = 1015; + +/** + * A half float data type for textures. + * + * @type {number} + * @constant + */ +const HalfFloatType = 1016; + +/** + * An unsigned short 4_4_4_4 (packed) data type for textures. + * + * @type {number} + * @constant + */ +const UnsignedShort4444Type = 1017; + +/** + * An unsigned short 5_5_5_1 (packed) data type for textures. + * + * @type {number} + * @constant + */ +const UnsignedShort5551Type = 1018; + +/** + * An unsigned int 24_8 data type for textures. + * + * @type {number} + * @constant + */ +const UnsignedInt248Type = 1020; + +/** + * An unsigned int 5_9_9_9 (packed) data type for textures. + * + * @type {number} + * @constant + */ +const UnsignedInt5999Type = 35902; + +/** + * Discards the red, green and blue components and reads just the alpha component. + * + * @type {number} + * @constant + */ +const AlphaFormat = 1021; + +/** + * Discards the alpha component and reads the red, green and blue component. + * + * @type {number} + * @constant + */ +const RGBFormat = 1022; + +/** + * Reads the red, green, blue and alpha components. + * + * @type {number} + * @constant + */ +const RGBAFormat = 1023; + +/** + * Reads each element as a single depth value, converts it to floating point, and clamps to the range `[0,1]`. + * + * @type {number} + * @constant + */ +const DepthFormat = 1026; + +/** + * Reads each element is a pair of depth and stencil values. The depth component of the pair is interpreted as + * in `DepthFormat`. The stencil component is interpreted based on the depth + stencil internal format. + * + * @type {number} + * @constant + */ +const DepthStencilFormat = 1027; + +/** + * Discards the green, blue and alpha components and reads just the red component. + * + * @type {number} + * @constant + */ +const RedFormat = 1028; + +/** + * Discards the green, blue and alpha components and reads just the red component. The texels are read as integers instead of floating point. + * + * @type {number} + * @constant + */ +const RedIntegerFormat = 1029; + +/** + * Discards the alpha, and blue components and reads the red, and green components. + * + * @type {number} + * @constant + */ +const RGFormat = 1030; + +/** + * Discards the alpha, and blue components and reads the red, and green components. The texels are read as integers instead of floating point. + * + * @type {number} + * @constant + */ +const RGIntegerFormat = 1031; + +/** + * Discards the alpha component and reads the red, green and blue component. The texels are read as integers instead of floating point. + * + * @type {number} + * @constant + */ +const RGBIntegerFormat = 1032; + +/** + * Reads the red, green, blue and alpha components. The texels are read as integers instead of floating point. + * + * @type {number} + * @constant + */ +const RGBAIntegerFormat = 1033; + +/** + * A DXT1-compressed image in an RGB image format. + * + * @type {number} + * @constant + */ +const RGB_S3TC_DXT1_Format = 33776; + +/** + * A DXT1-compressed image in an RGB image format with a simple on/off alpha value. + * + * @type {number} + * @constant + */ +const RGBA_S3TC_DXT1_Format = 33777; + +/** + * A DXT3-compressed image in an RGBA image format. Compared to a 32-bit RGBA texture, it offers 4:1 compression. + * + * @type {number} + * @constant + */ +const RGBA_S3TC_DXT3_Format = 33778; + +/** + * A DXT5-compressed image in an RGBA image format. It also provides a 4:1 compression, but differs to the DXT3 + * compression in how the alpha compression is done. + * + * @type {number} + * @constant + */ +const RGBA_S3TC_DXT5_Format = 33779; + +/** + * PVRTC RGB compression in 4-bit mode. One block for each 4×4 pixels. + * + * @type {number} + * @constant + */ +const RGB_PVRTC_4BPPV1_Format = 35840; + +/** + * PVRTC RGB compression in 2-bit mode. One block for each 8×4 pixels. + * + * @type {number} + * @constant + */ +const RGB_PVRTC_2BPPV1_Format = 35841; + +/** + * PVRTC RGBA compression in 4-bit mode. One block for each 4×4 pixels. + * + * @type {number} + * @constant + */ +const RGBA_PVRTC_4BPPV1_Format = 35842; + +/** + * PVRTC RGBA compression in 2-bit mode. One block for each 8×4 pixels. + * + * @type {number} + * @constant + */ +const RGBA_PVRTC_2BPPV1_Format = 35843; + +/** + * ETC1 RGB format. + * + * @type {number} + * @constant + */ +const RGB_ETC1_Format = 36196; + +/** + * ETC2 RGB format. + * + * @type {number} + * @constant + */ +const RGB_ETC2_Format = 37492; + +/** + * ETC2 RGBA format. + * + * @type {number} + * @constant + */ +const RGBA_ETC2_EAC_Format = 37496; + +/** + * ASTC RGBA 4x4 format. + * + * @type {number} + * @constant + */ +const RGBA_ASTC_4x4_Format = 37808; + +/** + * ASTC RGBA 5x4 format. + * + * @type {number} + * @constant + */ +const RGBA_ASTC_5x4_Format = 37809; + +/** + * ASTC RGBA 5x5 format. + * + * @type {number} + * @constant + */ +const RGBA_ASTC_5x5_Format = 37810; + +/** + * ASTC RGBA 6x5 format. + * + * @type {number} + * @constant + */ +const RGBA_ASTC_6x5_Format = 37811; + +/** + * ASTC RGBA 6x6 format. + * + * @type {number} + * @constant + */ +const RGBA_ASTC_6x6_Format = 37812; + +/** + * ASTC RGBA 8x5 format. + * + * @type {number} + * @constant + */ +const RGBA_ASTC_8x5_Format = 37813; + +/** + * ASTC RGBA 8x6 format. + * + * @type {number} + * @constant + */ +const RGBA_ASTC_8x6_Format = 37814; + +/** + * ASTC RGBA 8x8 format. + * + * @type {number} + * @constant + */ +const RGBA_ASTC_8x8_Format = 37815; + +/** + * ASTC RGBA 10x5 format. + * + * @type {number} + * @constant + */ +const RGBA_ASTC_10x5_Format = 37816; + +/** + * ASTC RGBA 10x6 format. + * + * @type {number} + * @constant + */ +const RGBA_ASTC_10x6_Format = 37817; + +/** + * ASTC RGBA 10x8 format. + * + * @type {number} + * @constant + */ +const RGBA_ASTC_10x8_Format = 37818; + +/** + * ASTC RGBA 10x10 format. + * + * @type {number} + * @constant + */ +const RGBA_ASTC_10x10_Format = 37819; + +/** + * ASTC RGBA 12x10 format. + * + * @type {number} + * @constant + */ +const RGBA_ASTC_12x10_Format = 37820; + +/** + * ASTC RGBA 12x12 format. + * + * @type {number} + * @constant + */ +const RGBA_ASTC_12x12_Format = 37821; + +/** + * BPTC RGBA format. + * + * @type {number} + * @constant + */ +const RGBA_BPTC_Format = 36492; + +/** + * BPTC Signed RGB format. + * + * @type {number} + * @constant + */ +const RGB_BPTC_SIGNED_Format = 36494; + +/** + * BPTC Unsigned RGB format. + * + * @type {number} + * @constant + */ +const RGB_BPTC_UNSIGNED_Format = 36495; + +/** + * RGTC1 Red format. + * + * @type {number} + * @constant + */ +const RED_RGTC1_Format = 36283; + +/** + * RGTC1 Signed Red format. + * + * @type {number} + * @constant + */ +const SIGNED_RED_RGTC1_Format = 36284; + +/** + * RGTC2 Red Green format. + * + * @type {number} + * @constant + */ +const RED_GREEN_RGTC2_Format = 36285; + +/** + * RGTC2 Signed Red Green format. + * + * @type {number} + * @constant + */ +const SIGNED_RED_GREEN_RGTC2_Format = 36286; + +/** + * Animations are played once. + * + * @type {number} + * @constant + */ +const LoopOnce = 2200; + +/** + * Animations are played with a chosen number of repetitions, each time jumping from + * the end of the clip directly to its beginning. + * + * @type {number} + * @constant + */ +const LoopRepeat = 2201; + +/** + * Animations are played with a chosen number of repetitions, alternately playing forward + * and backward. + * + * @type {number} + * @constant + */ +const LoopPingPong = 2202; + +/** + * Discrete interpolation mode for keyframe tracks. + * + * @type {number} + * @constant + */ +const InterpolateDiscrete = 2300; + +/** + * Linear interpolation mode for keyframe tracks. + * + * @type {number} + * @constant + */ +const InterpolateLinear = 2301; + +/** + * Smooth interpolation mode for keyframe tracks. + * + * @type {number} + * @constant + */ +const InterpolateSmooth = 2302; + +/** + * Zero curvature ending for animations. + * + * @type {number} + * @constant + */ +const ZeroCurvatureEnding = 2400; + +/** + * Zero slope ending for animations. + * + * @type {number} + * @constant + */ +const ZeroSlopeEnding = 2401; + +/** + * Wrap around ending for animations. + * + * @type {number} + * @constant + */ +const WrapAroundEnding = 2402; + +/** + * Default animation blend mode. + * + * @type {number} + * @constant + */ +const NormalAnimationBlendMode = 2500; + +/** + * Additive animation blend mode. Can be used to layer motions on top of + * each other to build complex performances from smaller re-usable assets. + * + * @type {number} + * @constant + */ +const AdditiveAnimationBlendMode = 2501; + +/** + * For every three vertices draw a single triangle. + * + * @type {number} + * @constant + */ +const TrianglesDrawMode = 0; + +/** + * For each vertex draw a triangle from the last three vertices. + * + * @type {number} + * @constant + */ +const TriangleStripDrawMode = 1; + +/** + * For each vertex draw a triangle from the first vertex and the last two vertices. + * + * @type {number} + * @constant + */ +const TriangleFanDrawMode = 2; + +/** + * Basic depth packing. + * + * @type {number} + * @constant + */ +const BasicDepthPacking = 3200; + +/** + * A depth value is packed into 32 bit RGBA. + * + * @type {number} + * @constant + */ +const RGBADepthPacking = 3201; + +/** + * A depth value is packed into 24 bit RGB. + * + * @type {number} + * @constant + */ +const RGBDepthPacking = 3202; + +/** + * A depth value is packed into 16 bit RG. + * + * @type {number} + * @constant + */ +const RGDepthPacking = 3203; + +/** + * Normal information is relative to the underlying surface. + * + * @type {number} + * @constant + */ +const TangentSpaceNormalMap = 0; + +/** + * Normal information is relative to the object orientation. + * + * @type {number} + * @constant + */ +const ObjectSpaceNormalMap = 1; + +// Color space string identifiers, matching CSS Color Module Level 4 and WebGPU names where available. + +/** + * No color space. + * + * @type {string} + * @constant + */ +const NoColorSpace = ''; + +/** + * sRGB color space. + * + * @type {string} + * @constant + */ +const SRGBColorSpace = 'srgb'; + +/** + * sRGB-linear color space. + * + * @type {string} + * @constant + */ +const LinearSRGBColorSpace = 'srgb-linear'; + +/** + * Linear transfer function. + * + * @type {string} + * @constant + */ +const LinearTransfer = 'linear'; + +/** + * sRGB transfer function. + * + * @type {string} + * @constant + */ +const SRGBTransfer = 'srgb'; + +/** + * Sets the stencil buffer value to `0`. + * + * @type {number} + * @constant + */ +const ZeroStencilOp = 0; + +/** + * Keeps the current value. + * + * @type {number} + * @constant + */ +const KeepStencilOp = 7680; + +/** + * Sets the stencil buffer value to the specified reference value. + * + * @type {number} + * @constant + */ +const ReplaceStencilOp = 7681; + +/** + * Increments the current stencil buffer value. Clamps to the maximum representable unsigned value. + * + * @type {number} + * @constant + */ +const IncrementStencilOp = 7682; + +/** + * Decrements the current stencil buffer value. Clamps to `0`. + * + * @type {number} + * @constant + */ +const DecrementStencilOp = 7683; + +/** + * Increments the current stencil buffer value. Wraps stencil buffer value to zero when incrementing + * the maximum representable unsigned value. + * + * @type {number} + * @constant + */ +const IncrementWrapStencilOp = 34055; + +/** + * Decrements the current stencil buffer value. Wraps stencil buffer value to the maximum representable + * unsigned value when decrementing a stencil buffer value of `0`. + * + * @type {number} + * @constant + */ +const DecrementWrapStencilOp = 34056; + +/** + * Inverts the current stencil buffer value bitwise. + * + * @type {number} + * @constant + */ +const InvertStencilOp = 5386; + +/** + * Will never return true. + * + * @type {number} + * @constant + */ +const NeverStencilFunc = 512; + +/** + * Will return true if the stencil reference value is less than the current stencil value. + * + * @type {number} + * @constant + */ +const LessStencilFunc = 513; + +/** + * Will return true if the stencil reference value is equal to the current stencil value. + * + * @type {number} + * @constant + */ +const EqualStencilFunc = 514; + +/** + * Will return true if the stencil reference value is less than or equal to the current stencil value. + * + * @type {number} + * @constant + */ +const LessEqualStencilFunc = 515; + +/** + * Will return true if the stencil reference value is greater than the current stencil value. + * + * @type {number} + * @constant + */ +const GreaterStencilFunc = 516; + +/** + * Will return true if the stencil reference value is not equal to the current stencil value. + * + * @type {number} + * @constant + */ +const NotEqualStencilFunc = 517; + +/** + * Will return true if the stencil reference value is greater than or equal to the current stencil value. + * + * @type {number} + * @constant + */ +const GreaterEqualStencilFunc = 518; + +/** + * Will always return true. + * + * @type {number} + * @constant + */ +const AlwaysStencilFunc = 519; + +/** + * Never pass. + * + * @type {number} + * @constant + */ +const NeverCompare = 512; + +/** + * Pass if the incoming value is less than the texture value. + * + * @type {number} + * @constant + */ +const LessCompare = 513; + +/** + * Pass if the incoming value equals the texture value. + * + * @type {number} + * @constant + */ +const EqualCompare = 514; + +/** + * Pass if the incoming value is less than or equal to the texture value. + * + * @type {number} + * @constant + */ +const LessEqualCompare = 515; + +/** + * Pass if the incoming value is greater than the texture value. + * + * @type {number} + * @constant + */ +const GreaterCompare = 516; + +/** + * Pass if the incoming value is not equal to the texture value. + * + * @type {number} + * @constant + */ +const NotEqualCompare = 517; + +/** + * Pass if the incoming value is greater than or equal to the texture value. + * + * @type {number} + * @constant + */ +const GreaterEqualCompare = 518; + +/** + * Always pass. + * + * @type {number} + * @constant + */ +const AlwaysCompare = 519; + +/** + * The contents are intended to be specified once by the application, and used many + * times as the source for drawing and image specification commands. + * + * @type {number} + * @constant + */ +const StaticDrawUsage = 35044; + +/** + * The contents are intended to be respecified repeatedly by the application, and + * used many times as the source for drawing and image specification commands. + * + * @type {number} + * @constant + */ +const DynamicDrawUsage = 35048; + +/** + * The contents are intended to be specified once by the application, and used at most + * a few times as the source for drawing and image specification commands. + * + * @type {number} + * @constant + */ +const StreamDrawUsage = 35040; + +/** + * The contents are intended to be specified once by reading data from the 3D API, and queried + * many times by the application. + * + * @type {number} + * @constant + */ +const StaticReadUsage = 35045; + +/** + * The contents are intended to be respecified repeatedly by reading data from the 3D API, and queried + * many times by the application. + * + * @type {number} + * @constant + */ +const DynamicReadUsage = 35049; + +/** + * The contents are intended to be specified once by reading data from the 3D API, and queried at most + * a few times by the application + * + * @type {number} + * @constant + */ +const StreamReadUsage = 35041; + +/** + * The contents are intended to be specified once by reading data from the 3D API, and used many times as + * the source for WebGL drawing and image specification commands. + * + * @type {number} + * @constant + */ +const StaticCopyUsage = 35046; + +/** + * The contents are intended to be respecified repeatedly by reading data from the 3D API, and used many times + * as the source for WebGL drawing and image specification commands. + * + * @type {number} + * @constant + */ +const DynamicCopyUsage = 35050; + +/** + * The contents are intended to be specified once by reading data from the 3D API, and used at most a few times + * as the source for WebGL drawing and image specification commands. + * + * @type {number} + * @constant + */ +const StreamCopyUsage = 35042; + +/** + * GLSL 1 shader code. + * + * @type {string} + * @constant + */ +const GLSL1 = '100'; + +/** + * GLSL 3 shader code. + * + * @type {string} + * @constant + */ +const GLSL3 = '300 es'; + +/** + * WebGL coordinate system. + * + * @type {number} + * @constant + */ +const WebGLCoordinateSystem = 2000; + +/** + * WebGPU coordinate system. + * + * @type {number} + * @constant + */ +const WebGPUCoordinateSystem = 2001; + +/** + * Represents the different timestamp query types. + * + * @type {ConstantsTimestampQuery} + * @constant + */ +const TimestampQuery = { + COMPUTE: 'compute', + RENDER: 'render' +}; + +/** + * Represents mouse buttons and interaction types in context of controls. + * + * @type {ConstantsInterpolationSamplingType} + * @constant + */ +const InterpolationSamplingType = { + PERSPECTIVE: 'perspective', + LINEAR: 'linear', + FLAT: 'flat' +}; + +/** + * Represents the different interpolation sampling modes. + * + * @type {ConstantsInterpolationSamplingMode} + * @constant + */ +const InterpolationSamplingMode = { + NORMAL: 'normal', + CENTROID: 'centroid', + SAMPLE: 'sample', + FIRST: 'first', + EITHER: 'either' +}; + +/** + * This type represents mouse buttons and interaction types in context of controls. + * + * @typedef {Object} ConstantsMouse + * @property {number} MIDDLE - The left mouse button. + * @property {number} LEFT - The middle mouse button. + * @property {number} RIGHT - The right mouse button. + * @property {number} ROTATE - A rotate interaction. + * @property {number} DOLLY - A dolly interaction. + * @property {number} PAN - A pan interaction. + **/ + +/** + * This type represents touch interaction types in context of controls. + * + * @typedef {Object} ConstantsTouch + * @property {number} ROTATE - A rotate interaction. + * @property {number} PAN - A pan interaction. + * @property {number} DOLLY_PAN - The dolly-pan interaction. + * @property {number} DOLLY_ROTATE - A dolly-rotate interaction. + **/ + +/** + * This type represents the different timestamp query types. + * + * @typedef {Object} ConstantsTimestampQuery + * @property {string} COMPUTE - A `compute` timestamp query. + * @property {string} RENDER - A `render` timestamp query. + **/ + +/** + * Represents the different interpolation sampling types. + * + * @typedef {Object} ConstantsInterpolationSamplingType + * @property {string} PERSPECTIVE - Perspective-correct interpolation. + * @property {string} LINEAR - Linear interpolation. + * @property {string} FLAT - Flat interpolation. + */ + +/** + * Represents the different interpolation sampling modes. + * + * @typedef {Object} ConstantsInterpolationSamplingMode + * @property {string} NORMAL - Normal sampling mode. + * @property {string} CENTROID - Centroid sampling mode. + * @property {string} SAMPLE - Sample-specific sampling mode. + * @property {string} FLAT_FIRST - Flat interpolation using the first vertex. + * @property {string} FLAT_EITHER - Flat interpolation using either vertex. + */ + +/** + * This modules allows to dispatch event objects on custom JavaScript objects. + * + * Main repository: [eventdispatcher.js]{@link https://github.com/mrdoob/eventdispatcher.js/} + * + * Code Example: + * ```js + * class Car extends EventDispatcher { + * start() { + * this.dispatchEvent( { type: 'start', message: 'vroom vroom!' } ); + * } + *}; + * + * // Using events with the custom object + * const car = new Car(); + * car.addEventListener( 'start', function ( event ) { + * alert( event.message ); + * } ); + * + * car.start(); + * ``` + */ +class EventDispatcher { + + /** + * Adds the given event listener to the given event type. + * + * @param {string} type - The type of event to listen to. + * @param {Function} listener - The function that gets called when the event is fired. + */ + addEventListener( type, listener ) { + + if ( this._listeners === undefined ) this._listeners = {}; + + const listeners = this._listeners; + + if ( listeners[ type ] === undefined ) { + + listeners[ type ] = []; + + } + + if ( listeners[ type ].indexOf( listener ) === -1 ) { + + listeners[ type ].push( listener ); + + } + + } + + /** + * Returns `true` if the given event listener has been added to the given event type. + * + * @param {string} type - The type of event. + * @param {Function} listener - The listener to check. + * @return {boolean} Whether the given event listener has been added to the given event type. + */ + hasEventListener( type, listener ) { + + const listeners = this._listeners; + + if ( listeners === undefined ) return false; + + return listeners[ type ] !== undefined && listeners[ type ].indexOf( listener ) !== -1; + + } + + /** + * Removes the given event listener from the given event type. + * + * @param {string} type - The type of event. + * @param {Function} listener - The listener to remove. + */ + removeEventListener( type, listener ) { + + const listeners = this._listeners; + + if ( listeners === undefined ) return; + + const listenerArray = listeners[ type ]; + + if ( listenerArray !== undefined ) { + + const index = listenerArray.indexOf( listener ); + + if ( index !== -1 ) { + + listenerArray.splice( index, 1 ); + + } + + } + + } + + /** + * Dispatches an event object. + * + * @param {Object} event - The event that gets fired. + */ + dispatchEvent( event ) { + + const listeners = this._listeners; + + if ( listeners === undefined ) return; + + const listenerArray = listeners[ event.type ]; + + if ( listenerArray !== undefined ) { + + event.target = this; + + // Make a copy, in case listeners are removed while iterating. + const array = listenerArray.slice( 0 ); + + for ( let i = 0, l = array.length; i < l; i ++ ) { + + array[ i ].call( this, event ); + + } + + event.target = null; + + } + + } + +} + +const _lut = [ '00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '0a', '0b', '0c', '0d', '0e', '0f', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '1a', '1b', '1c', '1d', '1e', '1f', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '2a', '2b', '2c', '2d', '2e', '2f', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '3a', '3b', '3c', '3d', '3e', '3f', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '4a', '4b', '4c', '4d', '4e', '4f', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '5a', '5b', '5c', '5d', '5e', '5f', '60', '61', '62', '63', '64', '65', '66', '67', '68', '69', '6a', '6b', '6c', '6d', '6e', '6f', '70', '71', '72', '73', '74', '75', '76', '77', '78', '79', '7a', '7b', '7c', '7d', '7e', '7f', '80', '81', '82', '83', '84', '85', '86', '87', '88', '89', '8a', '8b', '8c', '8d', '8e', '8f', '90', '91', '92', '93', '94', '95', '96', '97', '98', '99', '9a', '9b', '9c', '9d', '9e', '9f', 'a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8', 'a9', 'aa', 'ab', 'ac', 'ad', 'ae', 'af', 'b0', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'b7', 'b8', 'b9', 'ba', 'bb', 'bc', 'bd', 'be', 'bf', 'c0', 'c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7', 'c8', 'c9', 'ca', 'cb', 'cc', 'cd', 'ce', 'cf', 'd0', 'd1', 'd2', 'd3', 'd4', 'd5', 'd6', 'd7', 'd8', 'd9', 'da', 'db', 'dc', 'dd', 'de', 'df', 'e0', 'e1', 'e2', 'e3', 'e4', 'e5', 'e6', 'e7', 'e8', 'e9', 'ea', 'eb', 'ec', 'ed', 'ee', 'ef', 'f0', 'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'fa', 'fb', 'fc', 'fd', 'fe', 'ff' ]; + +let _seed = 1234567; + + +const DEG2RAD = Math.PI / 180; +const RAD2DEG = 180 / Math.PI; + +/** + * Generate a [UUID]{@link https://en.wikipedia.org/wiki/Universally_unique_identifier} + * (universally unique identifier). + * + * @return {string} The UUID. + */ +function generateUUID() { + + // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/21963136#21963136 + + const d0 = Math.random() * 0xffffffff | 0; + const d1 = Math.random() * 0xffffffff | 0; + const d2 = Math.random() * 0xffffffff | 0; + const d3 = Math.random() * 0xffffffff | 0; + const uuid = _lut[ d0 & 0xff ] + _lut[ d0 >> 8 & 0xff ] + _lut[ d0 >> 16 & 0xff ] + _lut[ d0 >> 24 & 0xff ] + '-' + + _lut[ d1 & 0xff ] + _lut[ d1 >> 8 & 0xff ] + '-' + _lut[ d1 >> 16 & 0x0f | 0x40 ] + _lut[ d1 >> 24 & 0xff ] + '-' + + _lut[ d2 & 0x3f | 0x80 ] + _lut[ d2 >> 8 & 0xff ] + '-' + _lut[ d2 >> 16 & 0xff ] + _lut[ d2 >> 24 & 0xff ] + + _lut[ d3 & 0xff ] + _lut[ d3 >> 8 & 0xff ] + _lut[ d3 >> 16 & 0xff ] + _lut[ d3 >> 24 & 0xff ]; + + // .toLowerCase() here flattens concatenated strings to save heap memory space. + return uuid.toLowerCase(); + +} + +/** + * Clamps the given value between min and max. + * + * @param {number} value - The value to clamp. + * @param {number} min - The min value. + * @param {number} max - The max value. + * @return {number} The clamped value. + */ +function clamp( value, min, max ) { + + return Math.max( min, Math.min( max, value ) ); + +} + +/** + * Computes the Euclidean modulo of the given parameters that + * is `( ( n % m ) + m ) % m`. + * + * @param {number} n - The first parameter. + * @param {number} m - The second parameter. + * @return {number} The Euclidean modulo. + */ +function euclideanModulo( n, m ) { + + // https://en.wikipedia.org/wiki/Modulo_operation + + return ( ( n % m ) + m ) % m; + +} + +/** + * Performs a linear mapping from range `` to range `` + * for the given value. + * + * @param {number} x - The value to be mapped. + * @param {number} a1 - Minimum value for range A. + * @param {number} a2 - Maximum value for range A. + * @param {number} b1 - Minimum value for range B. + * @param {number} b2 - Maximum value for range B. + * @return {number} The mapped value. + */ +function mapLinear( x, a1, a2, b1, b2 ) { + + return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 ); + +} + +/** + * Returns the percentage in the closed interval `[0, 1]` of the given value + * between the start and end point. + * + * @param {number} x - The start point + * @param {number} y - The end point. + * @param {number} value - A value between start and end. + * @return {number} The interpolation factor. + */ +function inverseLerp( x, y, value ) { + + // https://www.gamedev.net/tutorials/programming/general-and-gameplay-programming/inverse-lerp-a-super-useful-yet-often-overlooked-function-r5230/ + + if ( x !== y ) { + + return ( value - x ) / ( y - x ); + + } else { + + return 0; + + } + +} + +/** + * Returns a value linearly interpolated from two known points based on the given interval - + * `t = 0` will return `x` and `t = 1` will return `y`. + * + * @param {number} x - The start point + * @param {number} y - The end point. + * @param {number} t - The interpolation factor in the closed interval `[0, 1]`. + * @return {number} The interpolated value. + */ +function lerp( x, y, t ) { + + return ( 1 - t ) * x + t * y; + +} + +/** + * Smoothly interpolate a number from `x` to `y` in a spring-like manner using a delta + * time to maintain frame rate independent movement. For details, see + * [Frame rate independent damping using lerp]{@link http://www.rorydriscoll.com/2016/03/07/frame-rate-independent-damping-using-lerp/}. + * + * @param {number} x - The current point. + * @param {number} y - The target point. + * @param {number} lambda - A higher lambda value will make the movement more sudden, + * and a lower value will make the movement more gradual. + * @param {number} dt - Delta time in seconds. + * @return {number} The interpolated value. + */ +function damp( x, y, lambda, dt ) { + + return lerp( x, y, 1 - Math.exp( - lambda * dt ) ); + +} + +/** + * Returns a value that alternates between `0` and the given `length` parameter. + * + * @param {number} x - The value to pingpong. + * @param {number} [length=1] - The positive value the function will pingpong to. + * @return {number} The alternated value. + */ +function pingpong( x, length = 1 ) { + + // https://www.desmos.com/calculator/vcsjnyz7x4 + + return length - Math.abs( euclideanModulo( x, length * 2 ) - length ); + +} + +/** + * Returns a value in the range `[0,1]` that represents the percentage that `x` has + * moved between `min` and `max`, but smoothed or slowed down the closer `x` is to + * the `min` and `max`. + * + * See [Smoothstep]{@link http://en.wikipedia.org/wiki/Smoothstep} for more details. + * + * @param {number} x - The value to evaluate based on its position between min and max. + * @param {number} min - The min value. Any x value below min will be `0`. + * @param {number} max - The max value. Any x value above max will be `1`. + * @return {number} The alternated value. + */ +function smoothstep( x, min, max ) { + + if ( x <= min ) return 0; + if ( x >= max ) return 1; + + x = ( x - min ) / ( max - min ); + + return x * x * ( 3 - 2 * x ); + +} + +/** + * A [variation on smoothstep]{@link https://en.wikipedia.org/wiki/Smoothstep#Variations} + * that has zero 1st and 2nd order derivatives at x=0 and x=1. + * + * @param {number} x - The value to evaluate based on its position between min and max. + * @param {number} min - The min value. Any x value below min will be `0`. + * @param {number} max - The max value. Any x value above max will be `1`. + * @return {number} The alternated value. + */ +function smootherstep( x, min, max ) { + + if ( x <= min ) return 0; + if ( x >= max ) return 1; + + x = ( x - min ) / ( max - min ); + + return x * x * x * ( x * ( x * 6 - 15 ) + 10 ); + +} + +/** + * Returns a random integer from `` interval. + * + * @param {number} low - The lower value boundary. + * @param {number} high - The upper value boundary + * @return {number} A random integer. + */ +function randInt( low, high ) { + + return low + Math.floor( Math.random() * ( high - low + 1 ) ); + +} + +/** + * Returns a random float from `` interval. + * + * @param {number} low - The lower value boundary. + * @param {number} high - The upper value boundary + * @return {number} A random float. + */ +function randFloat( low, high ) { + + return low + Math.random() * ( high - low ); + +} + +/** + * Returns a random integer from `<-range/2, range/2>` interval. + * + * @param {number} range - Defines the value range. + * @return {number} A random float. + */ +function randFloatSpread( range ) { + + return range * ( 0.5 - Math.random() ); + +} + +/** + * Returns a deterministic pseudo-random float in the interval `[0, 1]`. + * + * @param {number} [s] - The integer seed. + * @return {number} A random float. + */ +function seededRandom( s ) { + + if ( s !== undefined ) _seed = s; + + // Mulberry32 generator + + let t = _seed += 0x6D2B79F5; + + t = Math.imul( t ^ t >>> 15, t | 1 ); + + t ^= t + Math.imul( t ^ t >>> 7, t | 61 ); + + return ( ( t ^ t >>> 14 ) >>> 0 ) / 4294967296; + +} + +/** + * Converts degrees to radians. + * + * @param {number} degrees - A value in degrees. + * @return {number} The converted value in radians. + */ +function degToRad( degrees ) { + + return degrees * DEG2RAD; + +} + +/** + * Converts radians to degrees. + * + * @param {number} radians - A value in radians. + * @return {number} The converted value in degrees. + */ +function radToDeg( radians ) { + + return radians * RAD2DEG; + +} + +/** + * Returns `true` if the given number is a power of two. + * + * @param {number} value - The value to check. + * @return {boolean} Whether the given number is a power of two or not. + */ +function isPowerOfTwo( value ) { + + return ( value & ( value - 1 ) ) === 0 && value !== 0; + +} + +/** + * Returns the smallest power of two that is greater than or equal to the given number. + * + * @param {number} value - The value to find a POT for. + * @return {number} The smallest power of two that is greater than or equal to the given number. + */ +function ceilPowerOfTwo( value ) { + + return Math.pow( 2, Math.ceil( Math.log( value ) / Math.LN2 ) ); + +} + +/** + * Returns the largest power of two that is less than or equal to the given number. + * + * @param {number} value - The value to find a POT for. + * @return {number} The largest power of two that is less than or equal to the given number. + */ +function floorPowerOfTwo( value ) { + + return Math.pow( 2, Math.floor( Math.log( value ) / Math.LN2 ) ); + +} + +/** + * Sets the given quaternion from the [Intrinsic Proper Euler Angles]{@link https://en.wikipedia.org/wiki/Euler_angles} + * defined by the given angles and order. + * + * Rotations are applied to the axes in the order specified by order: + * rotation by angle `a` is applied first, then by angle `b`, then by angle `c`. + * + * @param {Quaternion} q - The quaternion to set. + * @param {number} a - The rotation applied to the first axis, in radians. + * @param {number} b - The rotation applied to the second axis, in radians. + * @param {number} c - The rotation applied to the third axis, in radians. + * @param {('XYX'|'XZX'|'YXY'|'YZY'|'ZXZ'|'ZYZ')} order - A string specifying the axes order. + */ +function setQuaternionFromProperEuler( q, a, b, c, order ) { + + const cos = Math.cos; + const sin = Math.sin; + + const c2 = cos( b / 2 ); + const s2 = sin( b / 2 ); + + const c13 = cos( ( a + c ) / 2 ); + const s13 = sin( ( a + c ) / 2 ); + + const c1_3 = cos( ( a - c ) / 2 ); + const s1_3 = sin( ( a - c ) / 2 ); + + const c3_1 = cos( ( c - a ) / 2 ); + const s3_1 = sin( ( c - a ) / 2 ); + + switch ( order ) { + + case 'XYX': + q.set( c2 * s13, s2 * c1_3, s2 * s1_3, c2 * c13 ); + break; + + case 'YZY': + q.set( s2 * s1_3, c2 * s13, s2 * c1_3, c2 * c13 ); + break; + + case 'ZXZ': + q.set( s2 * c1_3, s2 * s1_3, c2 * s13, c2 * c13 ); + break; + + case 'XZX': + q.set( c2 * s13, s2 * s3_1, s2 * c3_1, c2 * c13 ); + break; + + case 'YXY': + q.set( s2 * c3_1, c2 * s13, s2 * s3_1, c2 * c13 ); + break; + + case 'ZYZ': + q.set( s2 * s3_1, s2 * c3_1, c2 * s13, c2 * c13 ); + break; + + default: + console.warn( 'THREE.MathUtils: .setQuaternionFromProperEuler() encountered an unknown order: ' + order ); + + } + +} + +/** + * Denormalizes the given value according to the given typed array. + * + * @param {number} value - The value to denormalize. + * @param {TypedArray} array - The typed array that defines the data type of the value. + * @return {number} The denormalize (float) value in the range `[0,1]`. + */ +function denormalize( value, array ) { + + switch ( array.constructor ) { + + case Float32Array: + + return value; + + case Uint32Array: + + return value / 4294967295.0; + + case Uint16Array: + + return value / 65535.0; + + case Uint8Array: + + return value / 255.0; + + case Int32Array: + + return Math.max( value / 2147483647.0, -1 ); + + case Int16Array: + + return Math.max( value / 32767.0, -1 ); + + case Int8Array: + + return Math.max( value / 127.0, -1 ); + + default: + + throw new Error( 'Invalid component type.' ); + + } + +} + +/** + * Normalizes the given value according to the given typed array. + * + * @param {number} value - The float value in the range `[0,1]` to normalize. + * @param {TypedArray} array - The typed array that defines the data type of the value. + * @return {number} The normalize value. + */ +function normalize( value, array ) { + + switch ( array.constructor ) { + + case Float32Array: + + return value; + + case Uint32Array: + + return Math.round( value * 4294967295.0 ); + + case Uint16Array: + + return Math.round( value * 65535.0 ); + + case Uint8Array: + + return Math.round( value * 255.0 ); + + case Int32Array: + + return Math.round( value * 2147483647.0 ); + + case Int16Array: + + return Math.round( value * 32767.0 ); + + case Int8Array: + + return Math.round( value * 127.0 ); + + default: + + throw new Error( 'Invalid component type.' ); + + } + +} + +/** + * @class + * @classdesc A collection of math utility functions. + * @hideconstructor + */ +const MathUtils = { + DEG2RAD: DEG2RAD, + RAD2DEG: RAD2DEG, + /** + * Generate a [UUID]{@link https://en.wikipedia.org/wiki/Universally_unique_identifier} + * (universally unique identifier). + * + * @static + * @method + * @return {string} The UUID. + */ + generateUUID: generateUUID, + /** + * Clamps the given value between min and max. + * + * @static + * @method + * @param {number} value - The value to clamp. + * @param {number} min - The min value. + * @param {number} max - The max value. + * @return {number} The clamped value. + */ + clamp: clamp, + /** + * Computes the Euclidean modulo of the given parameters that + * is `( ( n % m ) + m ) % m`. + * + * @static + * @method + * @param {number} n - The first parameter. + * @param {number} m - The second parameter. + * @return {number} The Euclidean modulo. + */ + euclideanModulo: euclideanModulo, + /** + * Performs a linear mapping from range `` to range `` + * for the given value. + * + * @static + * @method + * @param {number} x - The value to be mapped. + * @param {number} a1 - Minimum value for range A. + * @param {number} a2 - Maximum value for range A. + * @param {number} b1 - Minimum value for range B. + * @param {number} b2 - Maximum value for range B. + * @return {number} The mapped value. + */ + mapLinear: mapLinear, + /** + * Returns the percentage in the closed interval `[0, 1]` of the given value + * between the start and end point. + * + * @static + * @method + * @param {number} x - The start point + * @param {number} y - The end point. + * @param {number} value - A value between start and end. + * @return {number} The interpolation factor. + */ + inverseLerp: inverseLerp, + /** + * Returns a value linearly interpolated from two known points based on the given interval - + * `t = 0` will return `x` and `t = 1` will return `y`. + * + * @static + * @method + * @param {number} x - The start point + * @param {number} y - The end point. + * @param {number} t - The interpolation factor in the closed interval `[0, 1]`. + * @return {number} The interpolated value. + */ + lerp: lerp, + /** + * Smoothly interpolate a number from `x` to `y` in a spring-like manner using a delta + * time to maintain frame rate independent movement. For details, see + * [Frame rate independent damping using lerp]{@link http://www.rorydriscoll.com/2016/03/07/frame-rate-independent-damping-using-lerp/}. + * + * @static + * @method + * @param {number} x - The current point. + * @param {number} y - The target point. + * @param {number} lambda - A higher lambda value will make the movement more sudden, + * and a lower value will make the movement more gradual. + * @param {number} dt - Delta time in seconds. + * @return {number} The interpolated value. + */ + damp: damp, + /** + * Returns a value that alternates between `0` and the given `length` parameter. + * + * @static + * @method + * @param {number} x - The value to pingpong. + * @param {number} [length=1] - The positive value the function will pingpong to. + * @return {number} The alternated value. + */ + pingpong: pingpong, + /** + * Returns a value in the range `[0,1]` that represents the percentage that `x` has + * moved between `min` and `max`, but smoothed or slowed down the closer `x` is to + * the `min` and `max`. + * + * See [Smoothstep]{@link http://en.wikipedia.org/wiki/Smoothstep} for more details. + * + * @static + * @method + * @param {number} x - The value to evaluate based on its position between min and max. + * @param {number} min - The min value. Any x value below min will be `0`. + * @param {number} max - The max value. Any x value above max will be `1`. + * @return {number} The alternated value. + */ + smoothstep: smoothstep, + /** + * A [variation on smoothstep]{@link https://en.wikipedia.org/wiki/Smoothstep#Variations} + * that has zero 1st and 2nd order derivatives at x=0 and x=1. + * + * @static + * @method + * @param {number} x - The value to evaluate based on its position between min and max. + * @param {number} min - The min value. Any x value below min will be `0`. + * @param {number} max - The max value. Any x value above max will be `1`. + * @return {number} The alternated value. + */ + smootherstep: smootherstep, + /** + * Returns a random integer from `` interval. + * + * @static + * @method + * @param {number} low - The lower value boundary. + * @param {number} high - The upper value boundary + * @return {number} A random integer. + */ + randInt: randInt, + /** + * Returns a random float from `` interval. + * + * @static + * @method + * @param {number} low - The lower value boundary. + * @param {number} high - The upper value boundary + * @return {number} A random float. + */ + randFloat: randFloat, + /** + * Returns a random integer from `<-range/2, range/2>` interval. + * + * @static + * @method + * @param {number} range - Defines the value range. + * @return {number} A random float. + */ + randFloatSpread: randFloatSpread, + /** + * Returns a deterministic pseudo-random float in the interval `[0, 1]`. + * + * @static + * @method + * @param {number} [s] - The integer seed. + * @return {number} A random float. + */ + seededRandom: seededRandom, + /** + * Converts degrees to radians. + * + * @static + * @method + * @param {number} degrees - A value in degrees. + * @return {number} The converted value in radians. + */ + degToRad: degToRad, + /** + * Converts radians to degrees. + * + * @static + * @method + * @param {number} radians - A value in radians. + * @return {number} The converted value in degrees. + */ + radToDeg: radToDeg, + /** + * Returns `true` if the given number is a power of two. + * + * @static + * @method + * @param {number} value - The value to check. + * @return {boolean} Whether the given number is a power of two or not. + */ + isPowerOfTwo: isPowerOfTwo, + /** + * Returns the smallest power of two that is greater than or equal to the given number. + * + * @static + * @method + * @param {number} value - The value to find a POT for. + * @return {number} The smallest power of two that is greater than or equal to the given number. + */ + ceilPowerOfTwo: ceilPowerOfTwo, + /** + * Returns the largest power of two that is less than or equal to the given number. + * + * @static + * @method + * @param {number} value - The value to find a POT for. + * @return {number} The largest power of two that is less than or equal to the given number. + */ + floorPowerOfTwo: floorPowerOfTwo, + /** + * Sets the given quaternion from the [Intrinsic Proper Euler Angles]{@link https://en.wikipedia.org/wiki/Euler_angles} + * defined by the given angles and order. + * + * Rotations are applied to the axes in the order specified by order: + * rotation by angle `a` is applied first, then by angle `b`, then by angle `c`. + * + * @static + * @method + * @param {Quaternion} q - The quaternion to set. + * @param {number} a - The rotation applied to the first axis, in radians. + * @param {number} b - The rotation applied to the second axis, in radians. + * @param {number} c - The rotation applied to the third axis, in radians. + * @param {('XYX'|'XZX'|'YXY'|'YZY'|'ZXZ'|'ZYZ')} order - A string specifying the axes order. + */ + setQuaternionFromProperEuler: setQuaternionFromProperEuler, + /** + * Normalizes the given value according to the given typed array. + * + * @static + * @method + * @param {number} value - The float value in the range `[0,1]` to normalize. + * @param {TypedArray} array - The typed array that defines the data type of the value. + * @return {number} The normalize value. + */ + normalize: normalize, + /** + * Denormalizes the given value according to the given typed array. + * + * @static + * @method + * @param {number} value - The value to denormalize. + * @param {TypedArray} array - The typed array that defines the data type of the value. + * @return {number} The denormalize (float) value in the range `[0,1]`. + */ + denormalize: denormalize +}; + +/** + * Class representing a 2D vector. A 2D vector is an ordered pair of numbers + * (labeled x and y), which can be used to represent a number of things, such as: + * + * - A point in 2D space (i.e. a position on a plane). + * - A direction and length across a plane. In three.js the length will + * always be the Euclidean distance(straight-line distance) from `(0, 0)` to `(x, y)` + * and the direction is also measured from `(0, 0)` towards `(x, y)`. + * - Any arbitrary ordered pair of numbers. + * + * There are other things a 2D vector can be used to represent, such as + * momentum vectors, complex numbers and so on, however these are the most + * common uses in three.js. + * + * Iterating through a vector instance will yield its components `(x, y)` in + * the corresponding order. + * ```js + * const a = new THREE.Vector2( 0, 1 ); + * + * //no arguments; will be initialised to (0, 0) + * const b = new THREE.Vector2( ); + * + * const d = a.distanceTo( b ); + * ``` + */ +class Vector2 { + + /** + * Constructs a new 2D vector. + * + * @param {number} [x=0] - The x value of this vector. + * @param {number} [y=0] - The y value of this vector. + */ + constructor( x = 0, y = 0 ) { + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + Vector2.prototype.isVector2 = true; + + /** + * The x value of this vector. + * + * @type {number} + */ + this.x = x; + + /** + * The y value of this vector. + * + * @type {number} + */ + this.y = y; + + } + + /** + * Alias for {@link Vector2#x}. + * + * @type {number} + */ + get width() { + + return this.x; + + } + + set width( value ) { + + this.x = value; + + } + + /** + * Alias for {@link Vector2#y}. + * + * @type {number} + */ + get height() { + + return this.y; + + } + + set height( value ) { + + this.y = value; + + } + + /** + * Sets the vector components. + * + * @param {number} x - The value of the x component. + * @param {number} y - The value of the y component. + * @return {Vector2} A reference to this vector. + */ + set( x, y ) { + + this.x = x; + this.y = y; + + return this; + + } + + /** + * Sets the vector components to the same value. + * + * @param {number} scalar - The value to set for all vector components. + * @return {Vector2} A reference to this vector. + */ + setScalar( scalar ) { + + this.x = scalar; + this.y = scalar; + + return this; + + } + + /** + * Sets the vector's x component to the given value + * + * @param {number} x - The value to set. + * @return {Vector2} A reference to this vector. + */ + setX( x ) { + + this.x = x; + + return this; + + } + + /** + * Sets the vector's y component to the given value + * + * @param {number} y - The value to set. + * @return {Vector2} A reference to this vector. + */ + setY( y ) { + + this.y = y; + + return this; + + } + + /** + * Allows to set a vector component with an index. + * + * @param {number} index - The component index. `0` equals to x, `1` equals to y. + * @param {number} value - The value to set. + * @return {Vector2} A reference to this vector. + */ + setComponent( index, value ) { + + switch ( index ) { + + case 0: this.x = value; break; + case 1: this.y = value; break; + default: throw new Error( 'index is out of range: ' + index ); + + } + + return this; + + } + + /** + * Returns the value of the vector component which matches the given index. + * + * @param {number} index - The component index. `0` equals to x, `1` equals to y. + * @return {number} A vector component value. + */ + getComponent( index ) { + + switch ( index ) { + + case 0: return this.x; + case 1: return this.y; + default: throw new Error( 'index is out of range: ' + index ); + + } + + } + + /** + * Returns a new vector with copied values from this instance. + * + * @return {Vector2} A clone of this instance. + */ + clone() { + + return new this.constructor( this.x, this.y ); + + } + + /** + * Copies the values of the given vector to this instance. + * + * @param {Vector2} v - The vector to copy. + * @return {Vector2} A reference to this vector. + */ + copy( v ) { + + this.x = v.x; + this.y = v.y; + + return this; + + } + + /** + * Adds the given vector to this instance. + * + * @param {Vector2} v - The vector to add. + * @return {Vector2} A reference to this vector. + */ + add( v ) { + + this.x += v.x; + this.y += v.y; + + return this; + + } + + /** + * Adds the given scalar value to all components of this instance. + * + * @param {number} s - The scalar to add. + * @return {Vector2} A reference to this vector. + */ + addScalar( s ) { + + this.x += s; + this.y += s; + + return this; + + } + + /** + * Adds the given vectors and stores the result in this instance. + * + * @param {Vector2} a - The first vector. + * @param {Vector2} b - The second vector. + * @return {Vector2} A reference to this vector. + */ + addVectors( a, b ) { + + this.x = a.x + b.x; + this.y = a.y + b.y; + + return this; + + } + + /** + * Adds the given vector scaled by the given factor to this instance. + * + * @param {Vector2} v - The vector. + * @param {number} s - The factor that scales `v`. + * @return {Vector2} A reference to this vector. + */ + addScaledVector( v, s ) { + + this.x += v.x * s; + this.y += v.y * s; + + return this; + + } + + /** + * Subtracts the given vector from this instance. + * + * @param {Vector2} v - The vector to subtract. + * @return {Vector2} A reference to this vector. + */ + sub( v ) { + + this.x -= v.x; + this.y -= v.y; + + return this; + + } + + /** + * Subtracts the given scalar value from all components of this instance. + * + * @param {number} s - The scalar to subtract. + * @return {Vector2} A reference to this vector. + */ + subScalar( s ) { + + this.x -= s; + this.y -= s; + + return this; + + } + + /** + * Subtracts the given vectors and stores the result in this instance. + * + * @param {Vector2} a - The first vector. + * @param {Vector2} b - The second vector. + * @return {Vector2} A reference to this vector. + */ + subVectors( a, b ) { + + this.x = a.x - b.x; + this.y = a.y - b.y; + + return this; + + } + + /** + * Multiplies the given vector with this instance. + * + * @param {Vector2} v - The vector to multiply. + * @return {Vector2} A reference to this vector. + */ + multiply( v ) { + + this.x *= v.x; + this.y *= v.y; + + return this; + + } + + /** + * Multiplies the given scalar value with all components of this instance. + * + * @param {number} scalar - The scalar to multiply. + * @return {Vector2} A reference to this vector. + */ + multiplyScalar( scalar ) { + + this.x *= scalar; + this.y *= scalar; + + return this; + + } + + /** + * Divides this instance by the given vector. + * + * @param {Vector2} v - The vector to divide. + * @return {Vector2} A reference to this vector. + */ + divide( v ) { + + this.x /= v.x; + this.y /= v.y; + + return this; + + } + + /** + * Divides this vector by the given scalar. + * + * @param {number} scalar - The scalar to divide. + * @return {Vector2} A reference to this vector. + */ + divideScalar( scalar ) { + + return this.multiplyScalar( 1 / scalar ); + + } + + /** + * Multiplies this vector (with an implicit 1 as the 3rd component) by + * the given 3x3 matrix. + * + * @param {Matrix3} m - The matrix to apply. + * @return {Vector2} A reference to this vector. + */ + applyMatrix3( m ) { + + const x = this.x, y = this.y; + const e = m.elements; + + this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ]; + this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ]; + + return this; + + } + + /** + * If this vector's x or y value is greater than the given vector's x or y + * value, replace that value with the corresponding min value. + * + * @param {Vector2} v - The vector. + * @return {Vector2} A reference to this vector. + */ + min( v ) { + + this.x = Math.min( this.x, v.x ); + this.y = Math.min( this.y, v.y ); + + return this; + + } + + /** + * If this vector's x or y value is less than the given vector's x or y + * value, replace that value with the corresponding max value. + * + * @param {Vector2} v - The vector. + * @return {Vector2} A reference to this vector. + */ + max( v ) { + + this.x = Math.max( this.x, v.x ); + this.y = Math.max( this.y, v.y ); + + return this; + + } + + /** + * If this vector's x or y value is greater than the max vector's x or y + * value, it is replaced by the corresponding value. + * If this vector's x or y value is less than the min vector's x or y value, + * it is replaced by the corresponding value. + * + * @param {Vector2} min - The minimum x and y values. + * @param {Vector2} max - The maximum x and y values in the desired range. + * @return {Vector2} A reference to this vector. + */ + clamp( min, max ) { + + // assumes min < max, componentwise + + this.x = clamp( this.x, min.x, max.x ); + this.y = clamp( this.y, min.y, max.y ); + + return this; + + } + + /** + * If this vector's x or y values are greater than the max value, they are + * replaced by the max value. + * If this vector's x or y values are less than the min value, they are + * replaced by the min value. + * + * @param {number} minVal - The minimum value the components will be clamped to. + * @param {number} maxVal - The maximum value the components will be clamped to. + * @return {Vector2} A reference to this vector. + */ + clampScalar( minVal, maxVal ) { + + this.x = clamp( this.x, minVal, maxVal ); + this.y = clamp( this.y, minVal, maxVal ); + + return this; + + } + + /** + * If this vector's length is greater than the max value, it is replaced by + * the max value. + * If this vector's length is less than the min value, it is replaced by the + * min value. + * + * @param {number} min - The minimum value the vector length will be clamped to. + * @param {number} max - The maximum value the vector length will be clamped to. + * @return {Vector2} A reference to this vector. + */ + clampLength( min, max ) { + + const length = this.length(); + + return this.divideScalar( length || 1 ).multiplyScalar( clamp( length, min, max ) ); + + } + + /** + * The components of this vector are rounded down to the nearest integer value. + * + * @return {Vector2} A reference to this vector. + */ + floor() { + + this.x = Math.floor( this.x ); + this.y = Math.floor( this.y ); + + return this; + + } + + /** + * The components of this vector are rounded up to the nearest integer value. + * + * @return {Vector2} A reference to this vector. + */ + ceil() { + + this.x = Math.ceil( this.x ); + this.y = Math.ceil( this.y ); + + return this; + + } + + /** + * The components of this vector are rounded to the nearest integer value + * + * @return {Vector2} A reference to this vector. + */ + round() { + + this.x = Math.round( this.x ); + this.y = Math.round( this.y ); + + return this; + + } + + /** + * The components of this vector are rounded towards zero (up if negative, + * down if positive) to an integer value. + * + * @return {Vector2} A reference to this vector. + */ + roundToZero() { + + this.x = Math.trunc( this.x ); + this.y = Math.trunc( this.y ); + + return this; + + } + + /** + * Inverts this vector - i.e. sets x = -x and y = -y. + * + * @return {Vector2} A reference to this vector. + */ + negate() { + + this.x = - this.x; + this.y = - this.y; + + return this; + + } + + /** + * Calculates the dot product of the given vector with this instance. + * + * @param {Vector2} v - The vector to compute the dot product with. + * @return {number} The result of the dot product. + */ + dot( v ) { + + return this.x * v.x + this.y * v.y; + + } + + /** + * Calculates the cross product of the given vector with this instance. + * + * @param {Vector2} v - The vector to compute the cross product with. + * @return {number} The result of the cross product. + */ + cross( v ) { + + return this.x * v.y - this.y * v.x; + + } + + /** + * Computes the square of the Euclidean length (straight-line length) from + * (0, 0) to (x, y). If you are comparing the lengths of vectors, you should + * compare the length squared instead as it is slightly more efficient to calculate. + * + * @return {number} The square length of this vector. + */ + lengthSq() { + + return this.x * this.x + this.y * this.y; + + } + + /** + * Computes the Euclidean length (straight-line length) from (0, 0) to (x, y). + * + * @return {number} The length of this vector. + */ + length() { + + return Math.sqrt( this.x * this.x + this.y * this.y ); + + } + + /** + * Computes the Manhattan length of this vector. + * + * @return {number} The length of this vector. + */ + manhattanLength() { + + return Math.abs( this.x ) + Math.abs( this.y ); + + } + + /** + * Converts this vector to a unit vector - that is, sets it equal to a vector + * with the same direction as this one, but with a vector length of `1`. + * + * @return {Vector2} A reference to this vector. + */ + normalize() { + + return this.divideScalar( this.length() || 1 ); + + } + + /** + * Computes the angle in radians of this vector with respect to the positive x-axis. + * + * @return {number} The angle in radians. + */ + angle() { + + const angle = Math.atan2( - this.y, - this.x ) + Math.PI; + + return angle; + + } + + /** + * Returns the angle between the given vector and this instance in radians. + * + * @param {Vector2} v - The vector to compute the angle with. + * @return {number} The angle in radians. + */ + angleTo( v ) { + + const denominator = Math.sqrt( this.lengthSq() * v.lengthSq() ); + + if ( denominator === 0 ) return Math.PI / 2; + + const theta = this.dot( v ) / denominator; + + // clamp, to handle numerical problems + + return Math.acos( clamp( theta, -1, 1 ) ); + + } + + /** + * Computes the distance from the given vector to this instance. + * + * @param {Vector2} v - The vector to compute the distance to. + * @return {number} The distance. + */ + distanceTo( v ) { + + return Math.sqrt( this.distanceToSquared( v ) ); + + } + + /** + * Computes the squared distance from the given vector to this instance. + * If you are just comparing the distance with another distance, you should compare + * the distance squared instead as it is slightly more efficient to calculate. + * + * @param {Vector2} v - The vector to compute the squared distance to. + * @return {number} The squared distance. + */ + distanceToSquared( v ) { + + const dx = this.x - v.x, dy = this.y - v.y; + return dx * dx + dy * dy; + + } + + /** + * Computes the Manhattan distance from the given vector to this instance. + * + * @param {Vector2} v - The vector to compute the Manhattan distance to. + * @return {number} The Manhattan distance. + */ + manhattanDistanceTo( v ) { + + return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y ); + + } + + /** + * Sets this vector to a vector with the same direction as this one, but + * with the specified length. + * + * @param {number} length - The new length of this vector. + * @return {Vector2} A reference to this vector. + */ + setLength( length ) { + + return this.normalize().multiplyScalar( length ); + + } + + /** + * Linearly interpolates between the given vector and this instance, where + * alpha is the percent distance along the line - alpha = 0 will be this + * vector, and alpha = 1 will be the given one. + * + * @param {Vector2} v - The vector to interpolate towards. + * @param {number} alpha - The interpolation factor, typically in the closed interval `[0, 1]`. + * @return {Vector2} A reference to this vector. + */ + lerp( v, alpha ) { + + this.x += ( v.x - this.x ) * alpha; + this.y += ( v.y - this.y ) * alpha; + + return this; + + } + + /** + * Linearly interpolates between the given vectors, where alpha is the percent + * distance along the line - alpha = 0 will be first vector, and alpha = 1 will + * be the second one. The result is stored in this instance. + * + * @param {Vector2} v1 - The first vector. + * @param {Vector2} v2 - The second vector. + * @param {number} alpha - The interpolation factor, typically in the closed interval `[0, 1]`. + * @return {Vector2} A reference to this vector. + */ + lerpVectors( v1, v2, alpha ) { + + this.x = v1.x + ( v2.x - v1.x ) * alpha; + this.y = v1.y + ( v2.y - v1.y ) * alpha; + + return this; + + } + + /** + * Returns `true` if this vector is equal with the given one. + * + * @param {Vector2} v - The vector to test for equality. + * @return {boolean} Whether this vector is equal with the given one. + */ + equals( v ) { + + return ( ( v.x === this.x ) && ( v.y === this.y ) ); + + } + + /** + * Sets this vector's x value to be `array[ offset ]` and y + * value to be `array[ offset + 1 ]`. + * + * @param {Array} array - An array holding the vector component values. + * @param {number} [offset=0] - The offset into the array. + * @return {Vector2} A reference to this vector. + */ + fromArray( array, offset = 0 ) { + + this.x = array[ offset ]; + this.y = array[ offset + 1 ]; + + return this; + + } + + /** + * Writes the components of this vector to the given array. If no array is provided, + * the method returns a new instance. + * + * @param {Array} [array=[]] - The target array holding the vector components. + * @param {number} [offset=0] - Index of the first element in the array. + * @return {Array} The vector components. + */ + toArray( array = [], offset = 0 ) { + + array[ offset ] = this.x; + array[ offset + 1 ] = this.y; + + return array; + + } + + /** + * Sets the components of this vector from the given buffer attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute holding vector data. + * @param {number} index - The index into the attribute. + * @return {Vector2} A reference to this vector. + */ + fromBufferAttribute( attribute, index ) { + + this.x = attribute.getX( index ); + this.y = attribute.getY( index ); + + return this; + + } + + /** + * Rotates this vector around the given center by the given angle. + * + * @param {Vector2} center - The point around which to rotate. + * @param {number} angle - The angle to rotate, in radians. + * @return {Vector2} A reference to this vector. + */ + rotateAround( center, angle ) { + + const c = Math.cos( angle ), s = Math.sin( angle ); + + const x = this.x - center.x; + const y = this.y - center.y; + + this.x = x * c - y * s + center.x; + this.y = x * s + y * c + center.y; + + return this; + + } + + /** + * Sets each component of this vector to a pseudo-random value between `0` and + * `1`, excluding `1`. + * + * @return {Vector2} A reference to this vector. + */ + random() { + + this.x = Math.random(); + this.y = Math.random(); + + return this; + + } + + *[ Symbol.iterator ]() { + + yield this.x; + yield this.y; + + } + +} + +/** + * Class for representing a Quaternion. Quaternions are used in three.js to represent rotations. + * + * Iterating through a vector instance will yield its components `(x, y, z, w)` in + * the corresponding order. + * + * Note that three.js expects Quaternions to be normalized. + * ```js + * const quaternion = new THREE.Quaternion(); + * quaternion.setFromAxisAngle( new THREE.Vector3( 0, 1, 0 ), Math.PI / 2 ); + * + * const vector = new THREE.Vector3( 1, 0, 0 ); + * vector.applyQuaternion( quaternion ); + * ``` + */ +class Quaternion { + + /** + * Constructs a new quaternion. + * + * @param {number} [x=0] - The x value of this quaternion. + * @param {number} [y=0] - The y value of this quaternion. + * @param {number} [z=0] - The z value of this quaternion. + * @param {number} [w=1] - The w value of this quaternion. + */ + constructor( x = 0, y = 0, z = 0, w = 1 ) { + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isQuaternion = true; + + this._x = x; + this._y = y; + this._z = z; + this._w = w; + + } + + /** + * Interpolates between two quaternions via SLERP. This implementation assumes the + * quaternion data are managed in flat arrays. + * + * @param {Array} dst - The destination array. + * @param {number} dstOffset - An offset into the destination array. + * @param {Array} src0 - The source array of the first quaternion. + * @param {number} srcOffset0 - An offset into the first source array. + * @param {Array} src1 - The source array of the second quaternion. + * @param {number} srcOffset1 - An offset into the second source array. + * @param {number} t - The interpolation factor in the range `[0,1]`. + * @see {@link Quaternion#slerp} + */ + static slerpFlat( dst, dstOffset, src0, srcOffset0, src1, srcOffset1, t ) { + + // fuzz-free, array-based Quaternion SLERP operation + + let x0 = src0[ srcOffset0 + 0 ], + y0 = src0[ srcOffset0 + 1 ], + z0 = src0[ srcOffset0 + 2 ], + w0 = src0[ srcOffset0 + 3 ]; + + const x1 = src1[ srcOffset1 + 0 ], + y1 = src1[ srcOffset1 + 1 ], + z1 = src1[ srcOffset1 + 2 ], + w1 = src1[ srcOffset1 + 3 ]; + + if ( t === 0 ) { + + dst[ dstOffset + 0 ] = x0; + dst[ dstOffset + 1 ] = y0; + dst[ dstOffset + 2 ] = z0; + dst[ dstOffset + 3 ] = w0; + return; + + } + + if ( t === 1 ) { + + dst[ dstOffset + 0 ] = x1; + dst[ dstOffset + 1 ] = y1; + dst[ dstOffset + 2 ] = z1; + dst[ dstOffset + 3 ] = w1; + return; + + } + + if ( w0 !== w1 || x0 !== x1 || y0 !== y1 || z0 !== z1 ) { + + let s = 1 - t; + const cos = x0 * x1 + y0 * y1 + z0 * z1 + w0 * w1, + dir = ( cos >= 0 ? 1 : -1 ), + sqrSin = 1 - cos * cos; + + // Skip the Slerp for tiny steps to avoid numeric problems: + if ( sqrSin > Number.EPSILON ) { + + const sin = Math.sqrt( sqrSin ), + len = Math.atan2( sin, cos * dir ); + + s = Math.sin( s * len ) / sin; + t = Math.sin( t * len ) / sin; + + } + + const tDir = t * dir; + + x0 = x0 * s + x1 * tDir; + y0 = y0 * s + y1 * tDir; + z0 = z0 * s + z1 * tDir; + w0 = w0 * s + w1 * tDir; + + // Normalize in case we just did a lerp: + if ( s === 1 - t ) { + + const f = 1 / Math.sqrt( x0 * x0 + y0 * y0 + z0 * z0 + w0 * w0 ); + + x0 *= f; + y0 *= f; + z0 *= f; + w0 *= f; + + } + + } + + dst[ dstOffset ] = x0; + dst[ dstOffset + 1 ] = y0; + dst[ dstOffset + 2 ] = z0; + dst[ dstOffset + 3 ] = w0; + + } + + /** + * Multiplies two quaternions. This implementation assumes the quaternion data are managed + * in flat arrays. + * + * @param {Array} dst - The destination array. + * @param {number} dstOffset - An offset into the destination array. + * @param {Array} src0 - The source array of the first quaternion. + * @param {number} srcOffset0 - An offset into the first source array. + * @param {Array} src1 - The source array of the second quaternion. + * @param {number} srcOffset1 - An offset into the second source array. + * @return {Array} The destination array. + * @see {@link Quaternion#multiplyQuaternions}. + */ + static multiplyQuaternionsFlat( dst, dstOffset, src0, srcOffset0, src1, srcOffset1 ) { + + const x0 = src0[ srcOffset0 ]; + const y0 = src0[ srcOffset0 + 1 ]; + const z0 = src0[ srcOffset0 + 2 ]; + const w0 = src0[ srcOffset0 + 3 ]; + + const x1 = src1[ srcOffset1 ]; + const y1 = src1[ srcOffset1 + 1 ]; + const z1 = src1[ srcOffset1 + 2 ]; + const w1 = src1[ srcOffset1 + 3 ]; + + dst[ dstOffset ] = x0 * w1 + w0 * x1 + y0 * z1 - z0 * y1; + dst[ dstOffset + 1 ] = y0 * w1 + w0 * y1 + z0 * x1 - x0 * z1; + dst[ dstOffset + 2 ] = z0 * w1 + w0 * z1 + x0 * y1 - y0 * x1; + dst[ dstOffset + 3 ] = w0 * w1 - x0 * x1 - y0 * y1 - z0 * z1; + + return dst; + + } + + /** + * The x value of this quaternion. + * + * @type {number} + * @default 0 + */ + get x() { + + return this._x; + + } + + set x( value ) { + + this._x = value; + this._onChangeCallback(); + + } + + /** + * The y value of this quaternion. + * + * @type {number} + * @default 0 + */ + get y() { + + return this._y; + + } + + set y( value ) { + + this._y = value; + this._onChangeCallback(); + + } + + /** + * The z value of this quaternion. + * + * @type {number} + * @default 0 + */ + get z() { + + return this._z; + + } + + set z( value ) { + + this._z = value; + this._onChangeCallback(); + + } + + /** + * The w value of this quaternion. + * + * @type {number} + * @default 1 + */ + get w() { + + return this._w; + + } + + set w( value ) { + + this._w = value; + this._onChangeCallback(); + + } + + /** + * Sets the quaternion components. + * + * @param {number} x - The x value of this quaternion. + * @param {number} y - The y value of this quaternion. + * @param {number} z - The z value of this quaternion. + * @param {number} w - The w value of this quaternion. + * @return {Quaternion} A reference to this quaternion. + */ + set( x, y, z, w ) { + + this._x = x; + this._y = y; + this._z = z; + this._w = w; + + this._onChangeCallback(); + + return this; + + } + + /** + * Returns a new quaternion with copied values from this instance. + * + * @return {Quaternion} A clone of this instance. + */ + clone() { + + return new this.constructor( this._x, this._y, this._z, this._w ); + + } + + /** + * Copies the values of the given quaternion to this instance. + * + * @param {Quaternion} quaternion - The quaternion to copy. + * @return {Quaternion} A reference to this quaternion. + */ + copy( quaternion ) { + + this._x = quaternion.x; + this._y = quaternion.y; + this._z = quaternion.z; + this._w = quaternion.w; + + this._onChangeCallback(); + + return this; + + } + + /** + * Sets this quaternion from the rotation specified by the given + * Euler angles. + * + * @param {Euler} euler - The Euler angles. + * @param {boolean} [update=true] - Whether the internal `onChange` callback should be executed or not. + * @return {Quaternion} A reference to this quaternion. + */ + setFromEuler( euler, update = true ) { + + const x = euler._x, y = euler._y, z = euler._z, order = euler._order; + + // http://www.mathworks.com/matlabcentral/fileexchange/ + // 20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/ + // content/SpinCalc.m + + const cos = Math.cos; + const sin = Math.sin; + + const c1 = cos( x / 2 ); + const c2 = cos( y / 2 ); + const c3 = cos( z / 2 ); + + const s1 = sin( x / 2 ); + const s2 = sin( y / 2 ); + const s3 = sin( z / 2 ); + + switch ( order ) { + + case 'XYZ': + this._x = s1 * c2 * c3 + c1 * s2 * s3; + this._y = c1 * s2 * c3 - s1 * c2 * s3; + this._z = c1 * c2 * s3 + s1 * s2 * c3; + this._w = c1 * c2 * c3 - s1 * s2 * s3; + break; + + case 'YXZ': + this._x = s1 * c2 * c3 + c1 * s2 * s3; + this._y = c1 * s2 * c3 - s1 * c2 * s3; + this._z = c1 * c2 * s3 - s1 * s2 * c3; + this._w = c1 * c2 * c3 + s1 * s2 * s3; + break; + + case 'ZXY': + this._x = s1 * c2 * c3 - c1 * s2 * s3; + this._y = c1 * s2 * c3 + s1 * c2 * s3; + this._z = c1 * c2 * s3 + s1 * s2 * c3; + this._w = c1 * c2 * c3 - s1 * s2 * s3; + break; + + case 'ZYX': + this._x = s1 * c2 * c3 - c1 * s2 * s3; + this._y = c1 * s2 * c3 + s1 * c2 * s3; + this._z = c1 * c2 * s3 - s1 * s2 * c3; + this._w = c1 * c2 * c3 + s1 * s2 * s3; + break; + + case 'YZX': + this._x = s1 * c2 * c3 + c1 * s2 * s3; + this._y = c1 * s2 * c3 + s1 * c2 * s3; + this._z = c1 * c2 * s3 - s1 * s2 * c3; + this._w = c1 * c2 * c3 - s1 * s2 * s3; + break; + + case 'XZY': + this._x = s1 * c2 * c3 - c1 * s2 * s3; + this._y = c1 * s2 * c3 - s1 * c2 * s3; + this._z = c1 * c2 * s3 + s1 * s2 * c3; + this._w = c1 * c2 * c3 + s1 * s2 * s3; + break; + + default: + console.warn( 'THREE.Quaternion: .setFromEuler() encountered an unknown order: ' + order ); + + } + + if ( update === true ) this._onChangeCallback(); + + return this; + + } + + /** + * Sets this quaternion from the given axis and angle. + * + * @param {Vector3} axis - The normalized axis. + * @param {number} angle - The angle in radians. + * @return {Quaternion} A reference to this quaternion. + */ + setFromAxisAngle( axis, angle ) { + + // http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm + + const halfAngle = angle / 2, s = Math.sin( halfAngle ); + + this._x = axis.x * s; + this._y = axis.y * s; + this._z = axis.z * s; + this._w = Math.cos( halfAngle ); + + this._onChangeCallback(); + + return this; + + } + + /** + * Sets this quaternion from the given rotation matrix. + * + * @param {Matrix4} m - A 4x4 matrix of which the upper 3x3 of matrix is a pure rotation matrix (i.e. unscaled). + * @return {Quaternion} A reference to this quaternion. + */ + setFromRotationMatrix( m ) { + + // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm + + // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) + + const te = m.elements, + + m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ], + m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ], + m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ], + + trace = m11 + m22 + m33; + + if ( trace > 0 ) { + + const s = 0.5 / Math.sqrt( trace + 1.0 ); + + this._w = 0.25 / s; + this._x = ( m32 - m23 ) * s; + this._y = ( m13 - m31 ) * s; + this._z = ( m21 - m12 ) * s; + + } else if ( m11 > m22 && m11 > m33 ) { + + const s = 2.0 * Math.sqrt( 1.0 + m11 - m22 - m33 ); + + this._w = ( m32 - m23 ) / s; + this._x = 0.25 * s; + this._y = ( m12 + m21 ) / s; + this._z = ( m13 + m31 ) / s; + + } else if ( m22 > m33 ) { + + const s = 2.0 * Math.sqrt( 1.0 + m22 - m11 - m33 ); + + this._w = ( m13 - m31 ) / s; + this._x = ( m12 + m21 ) / s; + this._y = 0.25 * s; + this._z = ( m23 + m32 ) / s; + + } else { + + const s = 2.0 * Math.sqrt( 1.0 + m33 - m11 - m22 ); + + this._w = ( m21 - m12 ) / s; + this._x = ( m13 + m31 ) / s; + this._y = ( m23 + m32 ) / s; + this._z = 0.25 * s; + + } + + this._onChangeCallback(); + + return this; + + } + + /** + * Sets this quaternion to the rotation required to rotate the direction vector + * `vFrom` to the direction vector `vTo`. + * + * @param {Vector3} vFrom - The first (normalized) direction vector. + * @param {Vector3} vTo - The second (normalized) direction vector. + * @return {Quaternion} A reference to this quaternion. + */ + setFromUnitVectors( vFrom, vTo ) { + + // assumes direction vectors vFrom and vTo are normalized + + let r = vFrom.dot( vTo ) + 1; + + if ( r < 1e-8 ) { // the epsilon value has been discussed in #31286 + + // vFrom and vTo point in opposite directions + + r = 0; + + if ( Math.abs( vFrom.x ) > Math.abs( vFrom.z ) ) { + + this._x = - vFrom.y; + this._y = vFrom.x; + this._z = 0; + this._w = r; + + } else { + + this._x = 0; + this._y = - vFrom.z; + this._z = vFrom.y; + this._w = r; + + } + + } else { + + // crossVectors( vFrom, vTo ); // inlined to avoid cyclic dependency on Vector3 + + this._x = vFrom.y * vTo.z - vFrom.z * vTo.y; + this._y = vFrom.z * vTo.x - vFrom.x * vTo.z; + this._z = vFrom.x * vTo.y - vFrom.y * vTo.x; + this._w = r; + + } + + return this.normalize(); + + } + + /** + * Returns the angle between this quaternion and the given one in radians. + * + * @param {Quaternion} q - The quaternion to compute the angle with. + * @return {number} The angle in radians. + */ + angleTo( q ) { + + return 2 * Math.acos( Math.abs( clamp( this.dot( q ), -1, 1 ) ) ); + + } + + /** + * Rotates this quaternion by a given angular step to the given quaternion. + * The method ensures that the final quaternion will not overshoot `q`. + * + * @param {Quaternion} q - The target quaternion. + * @param {number} step - The angular step in radians. + * @return {Quaternion} A reference to this quaternion. + */ + rotateTowards( q, step ) { + + const angle = this.angleTo( q ); + + if ( angle === 0 ) return this; + + const t = Math.min( 1, step / angle ); + + this.slerp( q, t ); + + return this; + + } + + /** + * Sets this quaternion to the identity quaternion; that is, to the + * quaternion that represents "no rotation". + * + * @return {Quaternion} A reference to this quaternion. + */ + identity() { + + return this.set( 0, 0, 0, 1 ); + + } + + /** + * Inverts this quaternion via {@link Quaternion#conjugate}. The + * quaternion is assumed to have unit length. + * + * @return {Quaternion} A reference to this quaternion. + */ + invert() { + + return this.conjugate(); + + } + + /** + * Returns the rotational conjugate of this quaternion. The conjugate of a + * quaternion represents the same rotation in the opposite direction about + * the rotational axis. + * + * @return {Quaternion} A reference to this quaternion. + */ + conjugate() { + + this._x *= -1; + this._y *= -1; + this._z *= -1; + + this._onChangeCallback(); + + return this; + + } + + /** + * Calculates the dot product of this quaternion and the given one. + * + * @param {Quaternion} v - The quaternion to compute the dot product with. + * @return {number} The result of the dot product. + */ + dot( v ) { + + return this._x * v._x + this._y * v._y + this._z * v._z + this._w * v._w; + + } + + /** + * Computes the squared Euclidean length (straight-line length) of this quaternion, + * considered as a 4 dimensional vector. This can be useful if you are comparing the + * lengths of two quaternions, as this is a slightly more efficient calculation than + * {@link Quaternion#length}. + * + * @return {number} The squared Euclidean length. + */ + lengthSq() { + + return this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w; + + } + + /** + * Computes the Euclidean length (straight-line length) of this quaternion, + * considered as a 4 dimensional vector. + * + * @return {number} The Euclidean length. + */ + length() { + + return Math.sqrt( this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w ); + + } + + /** + * Normalizes this quaternion - that is, calculated the quaternion that performs + * the same rotation as this one, but has a length equal to `1`. + * + * @return {Quaternion} A reference to this quaternion. + */ + normalize() { + + let l = this.length(); + + if ( l === 0 ) { + + this._x = 0; + this._y = 0; + this._z = 0; + this._w = 1; + + } else { + + l = 1 / l; + + this._x = this._x * l; + this._y = this._y * l; + this._z = this._z * l; + this._w = this._w * l; + + } + + this._onChangeCallback(); + + return this; + + } + + /** + * Multiplies this quaternion by the given one. + * + * @param {Quaternion} q - The quaternion. + * @return {Quaternion} A reference to this quaternion. + */ + multiply( q ) { + + return this.multiplyQuaternions( this, q ); + + } + + /** + * Pre-multiplies this quaternion by the given one. + * + * @param {Quaternion} q - The quaternion. + * @return {Quaternion} A reference to this quaternion. + */ + premultiply( q ) { + + return this.multiplyQuaternions( q, this ); + + } + + /** + * Multiplies the given quaternions and stores the result in this instance. + * + * @param {Quaternion} a - The first quaternion. + * @param {Quaternion} b - The second quaternion. + * @return {Quaternion} A reference to this quaternion. + */ + multiplyQuaternions( a, b ) { + + // from http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/code/index.htm + + const qax = a._x, qay = a._y, qaz = a._z, qaw = a._w; + const qbx = b._x, qby = b._y, qbz = b._z, qbw = b._w; + + this._x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby; + this._y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz; + this._z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx; + this._w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz; + + this._onChangeCallback(); + + return this; + + } + + /** + * Performs a spherical linear interpolation between quaternions. + * + * @param {Quaternion} qb - The target quaternion. + * @param {number} t - The interpolation factor in the closed interval `[0, 1]`. + * @return {Quaternion} A reference to this quaternion. + */ + slerp( qb, t ) { + + if ( t === 0 ) return this; + if ( t === 1 ) return this.copy( qb ); + + const x = this._x, y = this._y, z = this._z, w = this._w; + + // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/ + + let cosHalfTheta = w * qb._w + x * qb._x + y * qb._y + z * qb._z; + + if ( cosHalfTheta < 0 ) { + + this._w = - qb._w; + this._x = - qb._x; + this._y = - qb._y; + this._z = - qb._z; + + cosHalfTheta = - cosHalfTheta; + + } else { + + this.copy( qb ); + + } + + if ( cosHalfTheta >= 1.0 ) { + + this._w = w; + this._x = x; + this._y = y; + this._z = z; + + return this; + + } + + const sqrSinHalfTheta = 1.0 - cosHalfTheta * cosHalfTheta; + + if ( sqrSinHalfTheta <= Number.EPSILON ) { + + const s = 1 - t; + this._w = s * w + t * this._w; + this._x = s * x + t * this._x; + this._y = s * y + t * this._y; + this._z = s * z + t * this._z; + + this.normalize(); // normalize calls _onChangeCallback() + + return this; + + } + + const sinHalfTheta = Math.sqrt( sqrSinHalfTheta ); + const halfTheta = Math.atan2( sinHalfTheta, cosHalfTheta ); + const ratioA = Math.sin( ( 1 - t ) * halfTheta ) / sinHalfTheta, + ratioB = Math.sin( t * halfTheta ) / sinHalfTheta; + + this._w = ( w * ratioA + this._w * ratioB ); + this._x = ( x * ratioA + this._x * ratioB ); + this._y = ( y * ratioA + this._y * ratioB ); + this._z = ( z * ratioA + this._z * ratioB ); + + this._onChangeCallback(); + + return this; + + } + + /** + * Performs a spherical linear interpolation between the given quaternions + * and stores the result in this quaternion. + * + * @param {Quaternion} qa - The source quaternion. + * @param {Quaternion} qb - The target quaternion. + * @param {number} t - The interpolation factor in the closed interval `[0, 1]`. + * @return {Quaternion} A reference to this quaternion. + */ + slerpQuaternions( qa, qb, t ) { + + return this.copy( qa ).slerp( qb, t ); + + } + + /** + * Sets this quaternion to a uniformly random, normalized quaternion. + * + * @return {Quaternion} A reference to this quaternion. + */ + random() { + + // Ken Shoemake + // Uniform random rotations + // D. Kirk, editor, Graphics Gems III, pages 124-132. Academic Press, New York, 1992. + + const theta1 = 2 * Math.PI * Math.random(); + const theta2 = 2 * Math.PI * Math.random(); + + const x0 = Math.random(); + const r1 = Math.sqrt( 1 - x0 ); + const r2 = Math.sqrt( x0 ); + + return this.set( + r1 * Math.sin( theta1 ), + r1 * Math.cos( theta1 ), + r2 * Math.sin( theta2 ), + r2 * Math.cos( theta2 ), + ); + + } + + /** + * Returns `true` if this quaternion is equal with the given one. + * + * @param {Quaternion} quaternion - The quaternion to test for equality. + * @return {boolean} Whether this quaternion is equal with the given one. + */ + equals( quaternion ) { + + return ( quaternion._x === this._x ) && ( quaternion._y === this._y ) && ( quaternion._z === this._z ) && ( quaternion._w === this._w ); + + } + + /** + * Sets this quaternion's components from the given array. + * + * @param {Array} array - An array holding the quaternion component values. + * @param {number} [offset=0] - The offset into the array. + * @return {Quaternion} A reference to this quaternion. + */ + fromArray( array, offset = 0 ) { + + this._x = array[ offset ]; + this._y = array[ offset + 1 ]; + this._z = array[ offset + 2 ]; + this._w = array[ offset + 3 ]; + + this._onChangeCallback(); + + return this; + + } + + /** + * Writes the components of this quaternion to the given array. If no array is provided, + * the method returns a new instance. + * + * @param {Array} [array=[]] - The target array holding the quaternion components. + * @param {number} [offset=0] - Index of the first element in the array. + * @return {Array} The quaternion components. + */ + toArray( array = [], offset = 0 ) { + + array[ offset ] = this._x; + array[ offset + 1 ] = this._y; + array[ offset + 2 ] = this._z; + array[ offset + 3 ] = this._w; + + return array; + + } + + /** + * Sets the components of this quaternion from the given buffer attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute holding quaternion data. + * @param {number} index - The index into the attribute. + * @return {Quaternion} A reference to this quaternion. + */ + fromBufferAttribute( attribute, index ) { + + this._x = attribute.getX( index ); + this._y = attribute.getY( index ); + this._z = attribute.getZ( index ); + this._w = attribute.getW( index ); + + this._onChangeCallback(); + + return this; + + } + + /** + * This methods defines the serialization result of this class. Returns the + * numerical elements of this quaternion in an array of format `[x, y, z, w]`. + * + * @return {Array} The serialized quaternion. + */ + toJSON() { + + return this.toArray(); + + } + + _onChange( callback ) { + + this._onChangeCallback = callback; + + return this; + + } + + _onChangeCallback() {} + + *[ Symbol.iterator ]() { + + yield this._x; + yield this._y; + yield this._z; + yield this._w; + + } + +} + +/** + * Class representing a 3D vector. A 3D vector is an ordered triplet of numbers + * (labeled x, y and z), which can be used to represent a number of things, such as: + * + * - A point in 3D space. + * - A direction and length in 3D space. In three.js the length will + * always be the Euclidean distance(straight-line distance) from `(0, 0, 0)` to `(x, y, z)` + * and the direction is also measured from `(0, 0, 0)` towards `(x, y, z)`. + * - Any arbitrary ordered triplet of numbers. + * + * There are other things a 3D vector can be used to represent, such as + * momentum vectors and so on, however these are the most + * common uses in three.js. + * + * Iterating through a vector instance will yield its components `(x, y, z)` in + * the corresponding order. + * ```js + * const a = new THREE.Vector3( 0, 1, 0 ); + * + * //no arguments; will be initialised to (0, 0, 0) + * const b = new THREE.Vector3( ); + * + * const d = a.distanceTo( b ); + * ``` + */ +class Vector3 { + + /** + * Constructs a new 3D vector. + * + * @param {number} [x=0] - The x value of this vector. + * @param {number} [y=0] - The y value of this vector. + * @param {number} [z=0] - The z value of this vector. + */ + constructor( x = 0, y = 0, z = 0 ) { + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + Vector3.prototype.isVector3 = true; + + /** + * The x value of this vector. + * + * @type {number} + */ + this.x = x; + + /** + * The y value of this vector. + * + * @type {number} + */ + this.y = y; + + /** + * The z value of this vector. + * + * @type {number} + */ + this.z = z; + + } + + /** + * Sets the vector components. + * + * @param {number} x - The value of the x component. + * @param {number} y - The value of the y component. + * @param {number} z - The value of the z component. + * @return {Vector3} A reference to this vector. + */ + set( x, y, z ) { + + if ( z === undefined ) z = this.z; // sprite.scale.set(x,y) + + this.x = x; + this.y = y; + this.z = z; + + return this; + + } + + /** + * Sets the vector components to the same value. + * + * @param {number} scalar - The value to set for all vector components. + * @return {Vector3} A reference to this vector. + */ + setScalar( scalar ) { + + this.x = scalar; + this.y = scalar; + this.z = scalar; + + return this; + + } + + /** + * Sets the vector's x component to the given value + * + * @param {number} x - The value to set. + * @return {Vector3} A reference to this vector. + */ + setX( x ) { + + this.x = x; + + return this; + + } + + /** + * Sets the vector's y component to the given value + * + * @param {number} y - The value to set. + * @return {Vector3} A reference to this vector. + */ + setY( y ) { + + this.y = y; + + return this; + + } + + /** + * Sets the vector's z component to the given value + * + * @param {number} z - The value to set. + * @return {Vector3} A reference to this vector. + */ + setZ( z ) { + + this.z = z; + + return this; + + } + + /** + * Allows to set a vector component with an index. + * + * @param {number} index - The component index. `0` equals to x, `1` equals to y, `2` equals to z. + * @param {number} value - The value to set. + * @return {Vector3} A reference to this vector. + */ + setComponent( index, value ) { + + switch ( index ) { + + case 0: this.x = value; break; + case 1: this.y = value; break; + case 2: this.z = value; break; + default: throw new Error( 'index is out of range: ' + index ); + + } + + return this; + + } + + /** + * Returns the value of the vector component which matches the given index. + * + * @param {number} index - The component index. `0` equals to x, `1` equals to y, `2` equals to z. + * @return {number} A vector component value. + */ + getComponent( index ) { + + switch ( index ) { + + case 0: return this.x; + case 1: return this.y; + case 2: return this.z; + default: throw new Error( 'index is out of range: ' + index ); + + } + + } + + /** + * Returns a new vector with copied values from this instance. + * + * @return {Vector3} A clone of this instance. + */ + clone() { + + return new this.constructor( this.x, this.y, this.z ); + + } + + /** + * Copies the values of the given vector to this instance. + * + * @param {Vector3} v - The vector to copy. + * @return {Vector3} A reference to this vector. + */ + copy( v ) { + + this.x = v.x; + this.y = v.y; + this.z = v.z; + + return this; + + } + + /** + * Adds the given vector to this instance. + * + * @param {Vector3} v - The vector to add. + * @return {Vector3} A reference to this vector. + */ + add( v ) { + + this.x += v.x; + this.y += v.y; + this.z += v.z; + + return this; + + } + + /** + * Adds the given scalar value to all components of this instance. + * + * @param {number} s - The scalar to add. + * @return {Vector3} A reference to this vector. + */ + addScalar( s ) { + + this.x += s; + this.y += s; + this.z += s; + + return this; + + } + + /** + * Adds the given vectors and stores the result in this instance. + * + * @param {Vector3} a - The first vector. + * @param {Vector3} b - The second vector. + * @return {Vector3} A reference to this vector. + */ + addVectors( a, b ) { + + this.x = a.x + b.x; + this.y = a.y + b.y; + this.z = a.z + b.z; + + return this; + + } + + /** + * Adds the given vector scaled by the given factor to this instance. + * + * @param {Vector3|Vector4} v - The vector. + * @param {number} s - The factor that scales `v`. + * @return {Vector3} A reference to this vector. + */ + addScaledVector( v, s ) { + + this.x += v.x * s; + this.y += v.y * s; + this.z += v.z * s; + + return this; + + } + + /** + * Subtracts the given vector from this instance. + * + * @param {Vector3} v - The vector to subtract. + * @return {Vector3} A reference to this vector. + */ + sub( v ) { + + this.x -= v.x; + this.y -= v.y; + this.z -= v.z; + + return this; + + } + + /** + * Subtracts the given scalar value from all components of this instance. + * + * @param {number} s - The scalar to subtract. + * @return {Vector3} A reference to this vector. + */ + subScalar( s ) { + + this.x -= s; + this.y -= s; + this.z -= s; + + return this; + + } + + /** + * Subtracts the given vectors and stores the result in this instance. + * + * @param {Vector3} a - The first vector. + * @param {Vector3} b - The second vector. + * @return {Vector3} A reference to this vector. + */ + subVectors( a, b ) { + + this.x = a.x - b.x; + this.y = a.y - b.y; + this.z = a.z - b.z; + + return this; + + } + + /** + * Multiplies the given vector with this instance. + * + * @param {Vector3} v - The vector to multiply. + * @return {Vector3} A reference to this vector. + */ + multiply( v ) { + + this.x *= v.x; + this.y *= v.y; + this.z *= v.z; + + return this; + + } + + /** + * Multiplies the given scalar value with all components of this instance. + * + * @param {number} scalar - The scalar to multiply. + * @return {Vector3} A reference to this vector. + */ + multiplyScalar( scalar ) { + + this.x *= scalar; + this.y *= scalar; + this.z *= scalar; + + return this; + + } + + /** + * Multiplies the given vectors and stores the result in this instance. + * + * @param {Vector3} a - The first vector. + * @param {Vector3} b - The second vector. + * @return {Vector3} A reference to this vector. + */ + multiplyVectors( a, b ) { + + this.x = a.x * b.x; + this.y = a.y * b.y; + this.z = a.z * b.z; + + return this; + + } + + /** + * Applies the given Euler rotation to this vector. + * + * @param {Euler} euler - The Euler angles. + * @return {Vector3} A reference to this vector. + */ + applyEuler( euler ) { + + return this.applyQuaternion( _quaternion$4.setFromEuler( euler ) ); + + } + + /** + * Applies a rotation specified by an axis and an angle to this vector. + * + * @param {Vector3} axis - A normalized vector representing the rotation axis. + * @param {number} angle - The angle in radians. + * @return {Vector3} A reference to this vector. + */ + applyAxisAngle( axis, angle ) { + + return this.applyQuaternion( _quaternion$4.setFromAxisAngle( axis, angle ) ); + + } + + /** + * Multiplies this vector with the given 3x3 matrix. + * + * @param {Matrix3} m - The 3x3 matrix. + * @return {Vector3} A reference to this vector. + */ + applyMatrix3( m ) { + + const x = this.x, y = this.y, z = this.z; + const e = m.elements; + + this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ] * z; + this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ] * z; + this.z = e[ 2 ] * x + e[ 5 ] * y + e[ 8 ] * z; + + return this; + + } + + /** + * Multiplies this vector by the given normal matrix and normalizes + * the result. + * + * @param {Matrix3} m - The normal matrix. + * @return {Vector3} A reference to this vector. + */ + applyNormalMatrix( m ) { + + return this.applyMatrix3( m ).normalize(); + + } + + /** + * Multiplies this vector (with an implicit 1 in the 4th dimension) by m, and + * divides by perspective. + * + * @param {Matrix4} m - The matrix to apply. + * @return {Vector3} A reference to this vector. + */ + applyMatrix4( m ) { + + const x = this.x, y = this.y, z = this.z; + const e = m.elements; + + const w = 1 / ( e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] ); + + this.x = ( e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] ) * w; + this.y = ( e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] ) * w; + this.z = ( e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] ) * w; + + return this; + + } + + /** + * Applies the given Quaternion to this vector. + * + * @param {Quaternion} q - The Quaternion. + * @return {Vector3} A reference to this vector. + */ + applyQuaternion( q ) { + + // quaternion q is assumed to have unit length + + const vx = this.x, vy = this.y, vz = this.z; + const qx = q.x, qy = q.y, qz = q.z, qw = q.w; + + // t = 2 * cross( q.xyz, v ); + const tx = 2 * ( qy * vz - qz * vy ); + const ty = 2 * ( qz * vx - qx * vz ); + const tz = 2 * ( qx * vy - qy * vx ); + + // v + q.w * t + cross( q.xyz, t ); + this.x = vx + qw * tx + qy * tz - qz * ty; + this.y = vy + qw * ty + qz * tx - qx * tz; + this.z = vz + qw * tz + qx * ty - qy * tx; + + return this; + + } + + /** + * Projects this vector from world space into the camera's normalized + * device coordinate (NDC) space. + * + * @param {Camera} camera - The camera. + * @return {Vector3} A reference to this vector. + */ + project( camera ) { + + return this.applyMatrix4( camera.matrixWorldInverse ).applyMatrix4( camera.projectionMatrix ); + + } + + /** + * Unprojects this vector from the camera's normalized device coordinate (NDC) + * space into world space. + * + * @param {Camera} camera - The camera. + * @return {Vector3} A reference to this vector. + */ + unproject( camera ) { + + return this.applyMatrix4( camera.projectionMatrixInverse ).applyMatrix4( camera.matrixWorld ); + + } + + /** + * Transforms the direction of this vector by a matrix (the upper left 3 x 3 + * subset of the given 4x4 matrix and then normalizes the result. + * + * @param {Matrix4} m - The matrix. + * @return {Vector3} A reference to this vector. + */ + transformDirection( m ) { + + // input: THREE.Matrix4 affine matrix + // vector interpreted as a direction + + const x = this.x, y = this.y, z = this.z; + const e = m.elements; + + this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z; + this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z; + this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z; + + return this.normalize(); + + } + + /** + * Divides this instance by the given vector. + * + * @param {Vector3} v - The vector to divide. + * @return {Vector3} A reference to this vector. + */ + divide( v ) { + + this.x /= v.x; + this.y /= v.y; + this.z /= v.z; + + return this; + + } + + /** + * Divides this vector by the given scalar. + * + * @param {number} scalar - The scalar to divide. + * @return {Vector3} A reference to this vector. + */ + divideScalar( scalar ) { + + return this.multiplyScalar( 1 / scalar ); + + } + + /** + * If this vector's x, y or z value is greater than the given vector's x, y or z + * value, replace that value with the corresponding min value. + * + * @param {Vector3} v - The vector. + * @return {Vector3} A reference to this vector. + */ + min( v ) { + + this.x = Math.min( this.x, v.x ); + this.y = Math.min( this.y, v.y ); + this.z = Math.min( this.z, v.z ); + + return this; + + } + + /** + * If this vector's x, y or z value is less than the given vector's x, y or z + * value, replace that value with the corresponding max value. + * + * @param {Vector3} v - The vector. + * @return {Vector3} A reference to this vector. + */ + max( v ) { + + this.x = Math.max( this.x, v.x ); + this.y = Math.max( this.y, v.y ); + this.z = Math.max( this.z, v.z ); + + return this; + + } + + /** + * If this vector's x, y or z value is greater than the max vector's x, y or z + * value, it is replaced by the corresponding value. + * If this vector's x, y or z value is less than the min vector's x, y or z value, + * it is replaced by the corresponding value. + * + * @param {Vector3} min - The minimum x, y and z values. + * @param {Vector3} max - The maximum x, y and z values in the desired range. + * @return {Vector3} A reference to this vector. + */ + clamp( min, max ) { + + // assumes min < max, componentwise + + this.x = clamp( this.x, min.x, max.x ); + this.y = clamp( this.y, min.y, max.y ); + this.z = clamp( this.z, min.z, max.z ); + + return this; + + } + + /** + * If this vector's x, y or z values are greater than the max value, they are + * replaced by the max value. + * If this vector's x, y or z values are less than the min value, they are + * replaced by the min value. + * + * @param {number} minVal - The minimum value the components will be clamped to. + * @param {number} maxVal - The maximum value the components will be clamped to. + * @return {Vector3} A reference to this vector. + */ + clampScalar( minVal, maxVal ) { + + this.x = clamp( this.x, minVal, maxVal ); + this.y = clamp( this.y, minVal, maxVal ); + this.z = clamp( this.z, minVal, maxVal ); + + return this; + + } + + /** + * If this vector's length is greater than the max value, it is replaced by + * the max value. + * If this vector's length is less than the min value, it is replaced by the + * min value. + * + * @param {number} min - The minimum value the vector length will be clamped to. + * @param {number} max - The maximum value the vector length will be clamped to. + * @return {Vector3} A reference to this vector. + */ + clampLength( min, max ) { + + const length = this.length(); + + return this.divideScalar( length || 1 ).multiplyScalar( clamp( length, min, max ) ); + + } + + /** + * The components of this vector are rounded down to the nearest integer value. + * + * @return {Vector3} A reference to this vector. + */ + floor() { + + this.x = Math.floor( this.x ); + this.y = Math.floor( this.y ); + this.z = Math.floor( this.z ); + + return this; + + } + + /** + * The components of this vector are rounded up to the nearest integer value. + * + * @return {Vector3} A reference to this vector. + */ + ceil() { + + this.x = Math.ceil( this.x ); + this.y = Math.ceil( this.y ); + this.z = Math.ceil( this.z ); + + return this; + + } + + /** + * The components of this vector are rounded to the nearest integer value + * + * @return {Vector3} A reference to this vector. + */ + round() { + + this.x = Math.round( this.x ); + this.y = Math.round( this.y ); + this.z = Math.round( this.z ); + + return this; + + } + + /** + * The components of this vector are rounded towards zero (up if negative, + * down if positive) to an integer value. + * + * @return {Vector3} A reference to this vector. + */ + roundToZero() { + + this.x = Math.trunc( this.x ); + this.y = Math.trunc( this.y ); + this.z = Math.trunc( this.z ); + + return this; + + } + + /** + * Inverts this vector - i.e. sets x = -x, y = -y and z = -z. + * + * @return {Vector3} A reference to this vector. + */ + negate() { + + this.x = - this.x; + this.y = - this.y; + this.z = - this.z; + + return this; + + } + + /** + * Calculates the dot product of the given vector with this instance. + * + * @param {Vector3} v - The vector to compute the dot product with. + * @return {number} The result of the dot product. + */ + dot( v ) { + + return this.x * v.x + this.y * v.y + this.z * v.z; + + } + + // TODO lengthSquared? + + /** + * Computes the square of the Euclidean length (straight-line length) from + * (0, 0, 0) to (x, y, z). If you are comparing the lengths of vectors, you should + * compare the length squared instead as it is slightly more efficient to calculate. + * + * @return {number} The square length of this vector. + */ + lengthSq() { + + return this.x * this.x + this.y * this.y + this.z * this.z; + + } + + /** + * Computes the Euclidean length (straight-line length) from (0, 0, 0) to (x, y, z). + * + * @return {number} The length of this vector. + */ + length() { + + return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z ); + + } + + /** + * Computes the Manhattan length of this vector. + * + * @return {number} The length of this vector. + */ + manhattanLength() { + + return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ); + + } + + /** + * Converts this vector to a unit vector - that is, sets it equal to a vector + * with the same direction as this one, but with a vector length of `1`. + * + * @return {Vector3} A reference to this vector. + */ + normalize() { + + return this.divideScalar( this.length() || 1 ); + + } + + /** + * Sets this vector to a vector with the same direction as this one, but + * with the specified length. + * + * @param {number} length - The new length of this vector. + * @return {Vector3} A reference to this vector. + */ + setLength( length ) { + + return this.normalize().multiplyScalar( length ); + + } + + /** + * Linearly interpolates between the given vector and this instance, where + * alpha is the percent distance along the line - alpha = 0 will be this + * vector, and alpha = 1 will be the given one. + * + * @param {Vector3} v - The vector to interpolate towards. + * @param {number} alpha - The interpolation factor, typically in the closed interval `[0, 1]`. + * @return {Vector3} A reference to this vector. + */ + lerp( v, alpha ) { + + this.x += ( v.x - this.x ) * alpha; + this.y += ( v.y - this.y ) * alpha; + this.z += ( v.z - this.z ) * alpha; + + return this; + + } + + /** + * Linearly interpolates between the given vectors, where alpha is the percent + * distance along the line - alpha = 0 will be first vector, and alpha = 1 will + * be the second one. The result is stored in this instance. + * + * @param {Vector3} v1 - The first vector. + * @param {Vector3} v2 - The second vector. + * @param {number} alpha - The interpolation factor, typically in the closed interval `[0, 1]`. + * @return {Vector3} A reference to this vector. + */ + lerpVectors( v1, v2, alpha ) { + + this.x = v1.x + ( v2.x - v1.x ) * alpha; + this.y = v1.y + ( v2.y - v1.y ) * alpha; + this.z = v1.z + ( v2.z - v1.z ) * alpha; + + return this; + + } + + /** + * Calculates the cross product of the given vector with this instance. + * + * @param {Vector3} v - The vector to compute the cross product with. + * @return {Vector3} The result of the cross product. + */ + cross( v ) { + + return this.crossVectors( this, v ); + + } + + /** + * Calculates the cross product of the given vectors and stores the result + * in this instance. + * + * @param {Vector3} a - The first vector. + * @param {Vector3} b - The second vector. + * @return {Vector3} A reference to this vector. + */ + crossVectors( a, b ) { + + const ax = a.x, ay = a.y, az = a.z; + const bx = b.x, by = b.y, bz = b.z; + + this.x = ay * bz - az * by; + this.y = az * bx - ax * bz; + this.z = ax * by - ay * bx; + + return this; + + } + + /** + * Projects this vector onto the given one. + * + * @param {Vector3} v - The vector to project to. + * @return {Vector3} A reference to this vector. + */ + projectOnVector( v ) { + + const denominator = v.lengthSq(); + + if ( denominator === 0 ) return this.set( 0, 0, 0 ); + + const scalar = v.dot( this ) / denominator; + + return this.copy( v ).multiplyScalar( scalar ); + + } + + /** + * Projects this vector onto a plane by subtracting this + * vector projected onto the plane's normal from this vector. + * + * @param {Vector3} planeNormal - The plane normal. + * @return {Vector3} A reference to this vector. + */ + projectOnPlane( planeNormal ) { + + _vector$c.copy( this ).projectOnVector( planeNormal ); + + return this.sub( _vector$c ); + + } + + /** + * Reflects this vector off a plane orthogonal to the given normal vector. + * + * @param {Vector3} normal - The (normalized) normal vector. + * @return {Vector3} A reference to this vector. + */ + reflect( normal ) { + + return this.sub( _vector$c.copy( normal ).multiplyScalar( 2 * this.dot( normal ) ) ); + + } + /** + * Returns the angle between the given vector and this instance in radians. + * + * @param {Vector3} v - The vector to compute the angle with. + * @return {number} The angle in radians. + */ + angleTo( v ) { + + const denominator = Math.sqrt( this.lengthSq() * v.lengthSq() ); + + if ( denominator === 0 ) return Math.PI / 2; + + const theta = this.dot( v ) / denominator; + + // clamp, to handle numerical problems + + return Math.acos( clamp( theta, -1, 1 ) ); + + } + + /** + * Computes the distance from the given vector to this instance. + * + * @param {Vector3} v - The vector to compute the distance to. + * @return {number} The distance. + */ + distanceTo( v ) { + + return Math.sqrt( this.distanceToSquared( v ) ); + + } + + /** + * Computes the squared distance from the given vector to this instance. + * If you are just comparing the distance with another distance, you should compare + * the distance squared instead as it is slightly more efficient to calculate. + * + * @param {Vector3} v - The vector to compute the squared distance to. + * @return {number} The squared distance. + */ + distanceToSquared( v ) { + + const dx = this.x - v.x, dy = this.y - v.y, dz = this.z - v.z; + + return dx * dx + dy * dy + dz * dz; + + } + + /** + * Computes the Manhattan distance from the given vector to this instance. + * + * @param {Vector3} v - The vector to compute the Manhattan distance to. + * @return {number} The Manhattan distance. + */ + manhattanDistanceTo( v ) { + + return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y ) + Math.abs( this.z - v.z ); + + } + + /** + * Sets the vector components from the given spherical coordinates. + * + * @param {Spherical} s - The spherical coordinates. + * @return {Vector3} A reference to this vector. + */ + setFromSpherical( s ) { + + return this.setFromSphericalCoords( s.radius, s.phi, s.theta ); + + } + + /** + * Sets the vector components from the given spherical coordinates. + * + * @param {number} radius - The radius. + * @param {number} phi - The phi angle in radians. + * @param {number} theta - The theta angle in radians. + * @return {Vector3} A reference to this vector. + */ + setFromSphericalCoords( radius, phi, theta ) { + + const sinPhiRadius = Math.sin( phi ) * radius; + + this.x = sinPhiRadius * Math.sin( theta ); + this.y = Math.cos( phi ) * radius; + this.z = sinPhiRadius * Math.cos( theta ); + + return this; + + } + + /** + * Sets the vector components from the given cylindrical coordinates. + * + * @param {Cylindrical} c - The cylindrical coordinates. + * @return {Vector3} A reference to this vector. + */ + setFromCylindrical( c ) { + + return this.setFromCylindricalCoords( c.radius, c.theta, c.y ); + + } + + /** + * Sets the vector components from the given cylindrical coordinates. + * + * @param {number} radius - The radius. + * @param {number} theta - The theta angle in radians. + * @param {number} y - The y value. + * @return {Vector3} A reference to this vector. + */ + setFromCylindricalCoords( radius, theta, y ) { + + this.x = radius * Math.sin( theta ); + this.y = y; + this.z = radius * Math.cos( theta ); + + return this; + + } + + /** + * Sets the vector components to the position elements of the + * given transformation matrix. + * + * @param {Matrix4} m - The 4x4 matrix. + * @return {Vector3} A reference to this vector. + */ + setFromMatrixPosition( m ) { + + const e = m.elements; + + this.x = e[ 12 ]; + this.y = e[ 13 ]; + this.z = e[ 14 ]; + + return this; + + } + + /** + * Sets the vector components to the scale elements of the + * given transformation matrix. + * + * @param {Matrix4} m - The 4x4 matrix. + * @return {Vector3} A reference to this vector. + */ + setFromMatrixScale( m ) { + + const sx = this.setFromMatrixColumn( m, 0 ).length(); + const sy = this.setFromMatrixColumn( m, 1 ).length(); + const sz = this.setFromMatrixColumn( m, 2 ).length(); + + this.x = sx; + this.y = sy; + this.z = sz; + + return this; + + } + + /** + * Sets the vector components from the specified matrix column. + * + * @param {Matrix4} m - The 4x4 matrix. + * @param {number} index - The column index. + * @return {Vector3} A reference to this vector. + */ + setFromMatrixColumn( m, index ) { + + return this.fromArray( m.elements, index * 4 ); + + } + + /** + * Sets the vector components from the specified matrix column. + * + * @param {Matrix3} m - The 3x3 matrix. + * @param {number} index - The column index. + * @return {Vector3} A reference to this vector. + */ + setFromMatrix3Column( m, index ) { + + return this.fromArray( m.elements, index * 3 ); + + } + + /** + * Sets the vector components from the given Euler angles. + * + * @param {Euler} e - The Euler angles to set. + * @return {Vector3} A reference to this vector. + */ + setFromEuler( e ) { + + this.x = e._x; + this.y = e._y; + this.z = e._z; + + return this; + + } + + /** + * Sets the vector components from the RGB components of the + * given color. + * + * @param {Color} c - The color to set. + * @return {Vector3} A reference to this vector. + */ + setFromColor( c ) { + + this.x = c.r; + this.y = c.g; + this.z = c.b; + + return this; + + } + + /** + * Returns `true` if this vector is equal with the given one. + * + * @param {Vector3} v - The vector to test for equality. + * @return {boolean} Whether this vector is equal with the given one. + */ + equals( v ) { + + return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) ); + + } + + /** + * Sets this vector's x value to be `array[ offset ]`, y value to be `array[ offset + 1 ]` + * and z value to be `array[ offset + 2 ]`. + * + * @param {Array} array - An array holding the vector component values. + * @param {number} [offset=0] - The offset into the array. + * @return {Vector3} A reference to this vector. + */ + fromArray( array, offset = 0 ) { + + this.x = array[ offset ]; + this.y = array[ offset + 1 ]; + this.z = array[ offset + 2 ]; + + return this; + + } + + /** + * Writes the components of this vector to the given array. If no array is provided, + * the method returns a new instance. + * + * @param {Array} [array=[]] - The target array holding the vector components. + * @param {number} [offset=0] - Index of the first element in the array. + * @return {Array} The vector components. + */ + toArray( array = [], offset = 0 ) { + + array[ offset ] = this.x; + array[ offset + 1 ] = this.y; + array[ offset + 2 ] = this.z; + + return array; + + } + + /** + * Sets the components of this vector from the given buffer attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute holding vector data. + * @param {number} index - The index into the attribute. + * @return {Vector3} A reference to this vector. + */ + fromBufferAttribute( attribute, index ) { + + this.x = attribute.getX( index ); + this.y = attribute.getY( index ); + this.z = attribute.getZ( index ); + + return this; + + } + + /** + * Sets each component of this vector to a pseudo-random value between `0` and + * `1`, excluding `1`. + * + * @return {Vector3} A reference to this vector. + */ + random() { + + this.x = Math.random(); + this.y = Math.random(); + this.z = Math.random(); + + return this; + + } + + /** + * Sets this vector to a uniformly random point on a unit sphere. + * + * @return {Vector3} A reference to this vector. + */ + randomDirection() { + + // https://mathworld.wolfram.com/SpherePointPicking.html + + const theta = Math.random() * Math.PI * 2; + const u = Math.random() * 2 - 1; + const c = Math.sqrt( 1 - u * u ); + + this.x = c * Math.cos( theta ); + this.y = u; + this.z = c * Math.sin( theta ); + + return this; + + } + + *[ Symbol.iterator ]() { + + yield this.x; + yield this.y; + yield this.z; + + } + +} + +const _vector$c = /*@__PURE__*/ new Vector3(); +const _quaternion$4 = /*@__PURE__*/ new Quaternion(); + +/** + * Represents a 3x3 matrix. + * + * A Note on Row-Major and Column-Major Ordering: + * + * The constructor and {@link Matrix3#set} method take arguments in + * [row-major]{@link https://en.wikipedia.org/wiki/Row-_and_column-major_order#Column-major_order} + * order, while internally they are stored in the {@link Matrix3#elements} array in column-major order. + * This means that calling: + * ```js + * const m = new THREE.Matrix(); + * m.set( 11, 12, 13, + * 21, 22, 23, + * 31, 32, 33 ); + * ``` + * will result in the elements array containing: + * ```js + * m.elements = [ 11, 21, 31, + * 12, 22, 32, + * 13, 23, 33 ]; + * ``` + * and internally all calculations are performed using column-major ordering. + * However, as the actual ordering makes no difference mathematically and + * most people are used to thinking about matrices in row-major order, the + * three.js documentation shows matrices in row-major order. Just bear in + * mind that if you are reading the source code, you'll have to take the + * transpose of any matrices outlined here to make sense of the calculations. + */ +class Matrix3 { + + /** + * Constructs a new 3x3 matrix. The arguments are supposed to be + * in row-major order. If no arguments are provided, the constructor + * initializes the matrix as an identity matrix. + * + * @param {number} [n11] - 1-1 matrix element. + * @param {number} [n12] - 1-2 matrix element. + * @param {number} [n13] - 1-3 matrix element. + * @param {number} [n21] - 2-1 matrix element. + * @param {number} [n22] - 2-2 matrix element. + * @param {number} [n23] - 2-3 matrix element. + * @param {number} [n31] - 3-1 matrix element. + * @param {number} [n32] - 3-2 matrix element. + * @param {number} [n33] - 3-3 matrix element. + */ + constructor( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) { + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + Matrix3.prototype.isMatrix3 = true; + + /** + * A column-major list of matrix values. + * + * @type {Array} + */ + this.elements = [ + + 1, 0, 0, + 0, 1, 0, + 0, 0, 1 + + ]; + + if ( n11 !== undefined ) { + + this.set( n11, n12, n13, n21, n22, n23, n31, n32, n33 ); + + } + + } + + /** + * Sets the elements of the matrix.The arguments are supposed to be + * in row-major order. + * + * @param {number} [n11] - 1-1 matrix element. + * @param {number} [n12] - 1-2 matrix element. + * @param {number} [n13] - 1-3 matrix element. + * @param {number} [n21] - 2-1 matrix element. + * @param {number} [n22] - 2-2 matrix element. + * @param {number} [n23] - 2-3 matrix element. + * @param {number} [n31] - 3-1 matrix element. + * @param {number} [n32] - 3-2 matrix element. + * @param {number} [n33] - 3-3 matrix element. + * @return {Matrix3} A reference to this matrix. + */ + set( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) { + + const te = this.elements; + + te[ 0 ] = n11; te[ 1 ] = n21; te[ 2 ] = n31; + te[ 3 ] = n12; te[ 4 ] = n22; te[ 5 ] = n32; + te[ 6 ] = n13; te[ 7 ] = n23; te[ 8 ] = n33; + + return this; + + } + + /** + * Sets this matrix to the 3x3 identity matrix. + * + * @return {Matrix3} A reference to this matrix. + */ + identity() { + + this.set( + + 1, 0, 0, + 0, 1, 0, + 0, 0, 1 + + ); + + return this; + + } + + /** + * Copies the values of the given matrix to this instance. + * + * @param {Matrix3} m - The matrix to copy. + * @return {Matrix3} A reference to this matrix. + */ + copy( m ) { + + const te = this.elements; + const me = m.elements; + + te[ 0 ] = me[ 0 ]; te[ 1 ] = me[ 1 ]; te[ 2 ] = me[ 2 ]; + te[ 3 ] = me[ 3 ]; te[ 4 ] = me[ 4 ]; te[ 5 ] = me[ 5 ]; + te[ 6 ] = me[ 6 ]; te[ 7 ] = me[ 7 ]; te[ 8 ] = me[ 8 ]; + + return this; + + } + + /** + * Extracts the basis of this matrix into the three axis vectors provided. + * + * @param {Vector3} xAxis - The basis's x axis. + * @param {Vector3} yAxis - The basis's y axis. + * @param {Vector3} zAxis - The basis's z axis. + * @return {Matrix3} A reference to this matrix. + */ + extractBasis( xAxis, yAxis, zAxis ) { + + xAxis.setFromMatrix3Column( this, 0 ); + yAxis.setFromMatrix3Column( this, 1 ); + zAxis.setFromMatrix3Column( this, 2 ); + + return this; + + } + + /** + * Set this matrix to the upper 3x3 matrix of the given 4x4 matrix. + * + * @param {Matrix4} m - The 4x4 matrix. + * @return {Matrix3} A reference to this matrix. + */ + setFromMatrix4( m ) { + + const me = m.elements; + + this.set( + + me[ 0 ], me[ 4 ], me[ 8 ], + me[ 1 ], me[ 5 ], me[ 9 ], + me[ 2 ], me[ 6 ], me[ 10 ] + + ); + + return this; + + } + + /** + * Post-multiplies this matrix by the given 3x3 matrix. + * + * @param {Matrix3} m - The matrix to multiply with. + * @return {Matrix3} A reference to this matrix. + */ + multiply( m ) { + + return this.multiplyMatrices( this, m ); + + } + + /** + * Pre-multiplies this matrix by the given 3x3 matrix. + * + * @param {Matrix3} m - The matrix to multiply with. + * @return {Matrix3} A reference to this matrix. + */ + premultiply( m ) { + + return this.multiplyMatrices( m, this ); + + } + + /** + * Multiples the given 3x3 matrices and stores the result + * in this matrix. + * + * @param {Matrix3} a - The first matrix. + * @param {Matrix3} b - The second matrix. + * @return {Matrix3} A reference to this matrix. + */ + multiplyMatrices( a, b ) { + + const ae = a.elements; + const be = b.elements; + const te = this.elements; + + const a11 = ae[ 0 ], a12 = ae[ 3 ], a13 = ae[ 6 ]; + const a21 = ae[ 1 ], a22 = ae[ 4 ], a23 = ae[ 7 ]; + const a31 = ae[ 2 ], a32 = ae[ 5 ], a33 = ae[ 8 ]; + + const b11 = be[ 0 ], b12 = be[ 3 ], b13 = be[ 6 ]; + const b21 = be[ 1 ], b22 = be[ 4 ], b23 = be[ 7 ]; + const b31 = be[ 2 ], b32 = be[ 5 ], b33 = be[ 8 ]; + + te[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31; + te[ 3 ] = a11 * b12 + a12 * b22 + a13 * b32; + te[ 6 ] = a11 * b13 + a12 * b23 + a13 * b33; + + te[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31; + te[ 4 ] = a21 * b12 + a22 * b22 + a23 * b32; + te[ 7 ] = a21 * b13 + a22 * b23 + a23 * b33; + + te[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31; + te[ 5 ] = a31 * b12 + a32 * b22 + a33 * b32; + te[ 8 ] = a31 * b13 + a32 * b23 + a33 * b33; + + return this; + + } + + /** + * Multiplies every component of the matrix by the given scalar. + * + * @param {number} s - The scalar. + * @return {Matrix3} A reference to this matrix. + */ + multiplyScalar( s ) { + + const te = this.elements; + + te[ 0 ] *= s; te[ 3 ] *= s; te[ 6 ] *= s; + te[ 1 ] *= s; te[ 4 ] *= s; te[ 7 ] *= s; + te[ 2 ] *= s; te[ 5 ] *= s; te[ 8 ] *= s; + + return this; + + } + + /** + * Computes and returns the determinant of this matrix. + * + * @return {number} The determinant. + */ + determinant() { + + const te = this.elements; + + const a = te[ 0 ], b = te[ 1 ], c = te[ 2 ], + d = te[ 3 ], e = te[ 4 ], f = te[ 5 ], + g = te[ 6 ], h = te[ 7 ], i = te[ 8 ]; + + return a * e * i - a * f * h - b * d * i + b * f * g + c * d * h - c * e * g; + + } + + /** + * Inverts this matrix, using the [analytic method]{@link https://en.wikipedia.org/wiki/Invertible_matrix#Analytic_solution}. + * You can not invert with a determinant of zero. If you attempt this, the method produces + * a zero matrix instead. + * + * @return {Matrix3} A reference to this matrix. + */ + invert() { + + const te = this.elements, + + n11 = te[ 0 ], n21 = te[ 1 ], n31 = te[ 2 ], + n12 = te[ 3 ], n22 = te[ 4 ], n32 = te[ 5 ], + n13 = te[ 6 ], n23 = te[ 7 ], n33 = te[ 8 ], + + t11 = n33 * n22 - n32 * n23, + t12 = n32 * n13 - n33 * n12, + t13 = n23 * n12 - n22 * n13, + + det = n11 * t11 + n21 * t12 + n31 * t13; + + if ( det === 0 ) return this.set( 0, 0, 0, 0, 0, 0, 0, 0, 0 ); + + const detInv = 1 / det; + + te[ 0 ] = t11 * detInv; + te[ 1 ] = ( n31 * n23 - n33 * n21 ) * detInv; + te[ 2 ] = ( n32 * n21 - n31 * n22 ) * detInv; + + te[ 3 ] = t12 * detInv; + te[ 4 ] = ( n33 * n11 - n31 * n13 ) * detInv; + te[ 5 ] = ( n31 * n12 - n32 * n11 ) * detInv; + + te[ 6 ] = t13 * detInv; + te[ 7 ] = ( n21 * n13 - n23 * n11 ) * detInv; + te[ 8 ] = ( n22 * n11 - n21 * n12 ) * detInv; + + return this; + + } + + /** + * Transposes this matrix in place. + * + * @return {Matrix3} A reference to this matrix. + */ + transpose() { + + let tmp; + const m = this.elements; + + tmp = m[ 1 ]; m[ 1 ] = m[ 3 ]; m[ 3 ] = tmp; + tmp = m[ 2 ]; m[ 2 ] = m[ 6 ]; m[ 6 ] = tmp; + tmp = m[ 5 ]; m[ 5 ] = m[ 7 ]; m[ 7 ] = tmp; + + return this; + + } + + /** + * Computes the normal matrix which is the inverse transpose of the upper + * left 3x3 portion of the given 4x4 matrix. + * + * @param {Matrix4} matrix4 - The 4x4 matrix. + * @return {Matrix3} A reference to this matrix. + */ + getNormalMatrix( matrix4 ) { + + return this.setFromMatrix4( matrix4 ).invert().transpose(); + + } + + /** + * Transposes this matrix into the supplied array, and returns itself unchanged. + * + * @param {Array} r - An array to store the transposed matrix elements. + * @return {Matrix3} A reference to this matrix. + */ + transposeIntoArray( r ) { + + const m = this.elements; + + r[ 0 ] = m[ 0 ]; + r[ 1 ] = m[ 3 ]; + r[ 2 ] = m[ 6 ]; + r[ 3 ] = m[ 1 ]; + r[ 4 ] = m[ 4 ]; + r[ 5 ] = m[ 7 ]; + r[ 6 ] = m[ 2 ]; + r[ 7 ] = m[ 5 ]; + r[ 8 ] = m[ 8 ]; + + return this; + + } + + /** + * Sets the UV transform matrix from offset, repeat, rotation, and center. + * + * @param {number} tx - Offset x. + * @param {number} ty - Offset y. + * @param {number} sx - Repeat x. + * @param {number} sy - Repeat y. + * @param {number} rotation - Rotation, in radians. Positive values rotate counterclockwise. + * @param {number} cx - Center x of rotation. + * @param {number} cy - Center y of rotation + * @return {Matrix3} A reference to this matrix. + */ + setUvTransform( tx, ty, sx, sy, rotation, cx, cy ) { + + const c = Math.cos( rotation ); + const s = Math.sin( rotation ); + + this.set( + sx * c, sx * s, - sx * ( c * cx + s * cy ) + cx + tx, + - sy * s, sy * c, - sy * ( - s * cx + c * cy ) + cy + ty, + 0, 0, 1 + ); + + return this; + + } + + /** + * Scales this matrix with the given scalar values. + * + * @param {number} sx - The amount to scale in the X axis. + * @param {number} sy - The amount to scale in the Y axis. + * @return {Matrix3} A reference to this matrix. + */ + scale( sx, sy ) { + + this.premultiply( _m3.makeScale( sx, sy ) ); + + return this; + + } + + /** + * Rotates this matrix by the given angle. + * + * @param {number} theta - The rotation in radians. + * @return {Matrix3} A reference to this matrix. + */ + rotate( theta ) { + + this.premultiply( _m3.makeRotation( - theta ) ); + + return this; + + } + + /** + * Translates this matrix by the given scalar values. + * + * @param {number} tx - The amount to translate in the X axis. + * @param {number} ty - The amount to translate in the Y axis. + * @return {Matrix3} A reference to this matrix. + */ + translate( tx, ty ) { + + this.premultiply( _m3.makeTranslation( tx, ty ) ); + + return this; + + } + + // for 2D Transforms + + /** + * Sets this matrix as a 2D translation transform. + * + * @param {number|Vector2} x - The amount to translate in the X axis or alternatively a translation vector. + * @param {number} y - The amount to translate in the Y axis. + * @return {Matrix3} A reference to this matrix. + */ + makeTranslation( x, y ) { + + if ( x.isVector2 ) { + + this.set( + + 1, 0, x.x, + 0, 1, x.y, + 0, 0, 1 + + ); + + } else { + + this.set( + + 1, 0, x, + 0, 1, y, + 0, 0, 1 + + ); + + } + + return this; + + } + + /** + * Sets this matrix as a 2D rotational transformation. + * + * @param {number} theta - The rotation in radians. + * @return {Matrix3} A reference to this matrix. + */ + makeRotation( theta ) { + + // counterclockwise + + const c = Math.cos( theta ); + const s = Math.sin( theta ); + + this.set( + + c, - s, 0, + s, c, 0, + 0, 0, 1 + + ); + + return this; + + } + + /** + * Sets this matrix as a 2D scale transform. + * + * @param {number} x - The amount to scale in the X axis. + * @param {number} y - The amount to scale in the Y axis. + * @return {Matrix3} A reference to this matrix. + */ + makeScale( x, y ) { + + this.set( + + x, 0, 0, + 0, y, 0, + 0, 0, 1 + + ); + + return this; + + } + + /** + * Returns `true` if this matrix is equal with the given one. + * + * @param {Matrix3} matrix - The matrix to test for equality. + * @return {boolean} Whether this matrix is equal with the given one. + */ + equals( matrix ) { + + const te = this.elements; + const me = matrix.elements; + + for ( let i = 0; i < 9; i ++ ) { + + if ( te[ i ] !== me[ i ] ) return false; + + } + + return true; + + } + + /** + * Sets the elements of the matrix from the given array. + * + * @param {Array} array - The matrix elements in column-major order. + * @param {number} [offset=0] - Index of the first element in the array. + * @return {Matrix3} A reference to this matrix. + */ + fromArray( array, offset = 0 ) { + + for ( let i = 0; i < 9; i ++ ) { + + this.elements[ i ] = array[ i + offset ]; + + } + + return this; + + } + + /** + * Writes the elements of this matrix to the given array. If no array is provided, + * the method returns a new instance. + * + * @param {Array} [array=[]] - The target array holding the matrix elements in column-major order. + * @param {number} [offset=0] - Index of the first element in the array. + * @return {Array} The matrix elements in column-major order. + */ + toArray( array = [], offset = 0 ) { + + const te = this.elements; + + array[ offset ] = te[ 0 ]; + array[ offset + 1 ] = te[ 1 ]; + array[ offset + 2 ] = te[ 2 ]; + + array[ offset + 3 ] = te[ 3 ]; + array[ offset + 4 ] = te[ 4 ]; + array[ offset + 5 ] = te[ 5 ]; + + array[ offset + 6 ] = te[ 6 ]; + array[ offset + 7 ] = te[ 7 ]; + array[ offset + 8 ] = te[ 8 ]; + + return array; + + } + + /** + * Returns a matrix with copied values from this instance. + * + * @return {Matrix3} A clone of this instance. + */ + clone() { + + return new this.constructor().fromArray( this.elements ); + + } + +} + +const _m3 = /*@__PURE__*/ new Matrix3(); + +function arrayNeedsUint32( array ) { + + // assumes larger values usually on last + + for ( let i = array.length - 1; i >= 0; -- i ) { + + if ( array[ i ] >= 65535 ) return true; // account for PRIMITIVE_RESTART_FIXED_INDEX, #24565 + + } + + return false; + +} + +const TYPED_ARRAYS = { + Int8Array: Int8Array, + Uint8Array: Uint8Array, + Uint8ClampedArray: Uint8ClampedArray, + Int16Array: Int16Array, + Uint16Array: Uint16Array, + Int32Array: Int32Array, + Uint32Array: Uint32Array, + Float32Array: Float32Array, + Float64Array: Float64Array +}; + +function getTypedArray( type, buffer ) { + + return new TYPED_ARRAYS[ type ]( buffer ); + +} + +function createElementNS( name ) { + + return document.createElementNS( 'http://www.w3.org/1999/xhtml', name ); + +} + +function createCanvasElement() { + + const canvas = createElementNS( 'canvas' ); + canvas.style.display = 'block'; + return canvas; + +} + +const _cache = {}; + +function warnOnce( message ) { + + if ( message in _cache ) return; + + _cache[ message ] = true; + + console.warn( message ); + +} + +function probeAsync( gl, sync, interval ) { + + return new Promise( function ( resolve, reject ) { + + function probe() { + + switch ( gl.clientWaitSync( sync, gl.SYNC_FLUSH_COMMANDS_BIT, 0 ) ) { + + case gl.WAIT_FAILED: + reject(); + break; + + case gl.TIMEOUT_EXPIRED: + setTimeout( probe, interval ); + break; + + default: + resolve(); + + } + + } + + setTimeout( probe, interval ); + + } ); + +} + +function toNormalizedProjectionMatrix( projectionMatrix ) { + + const m = projectionMatrix.elements; + + // Convert [-1, 1] to [0, 1] projection matrix + m[ 2 ] = 0.5 * m[ 2 ] + 0.5 * m[ 3 ]; + m[ 6 ] = 0.5 * m[ 6 ] + 0.5 * m[ 7 ]; + m[ 10 ] = 0.5 * m[ 10 ] + 0.5 * m[ 11 ]; + m[ 14 ] = 0.5 * m[ 14 ] + 0.5 * m[ 15 ]; + +} + +function toReversedProjectionMatrix( projectionMatrix ) { + + const m = projectionMatrix.elements; + const isPerspectiveMatrix = m[ 11 ] === -1; + + // Reverse [0, 1] projection matrix + if ( isPerspectiveMatrix ) { + + m[ 10 ] = - m[ 10 ] - 1; + m[ 14 ] = - m[ 14 ]; + + } else { + + m[ 10 ] = - m[ 10 ]; + m[ 14 ] = - m[ 14 ] + 1; + + } + +} + +const LINEAR_REC709_TO_XYZ = /*@__PURE__*/ new Matrix3().set( + 0.4123908, 0.3575843, 0.1804808, + 0.2126390, 0.7151687, 0.0721923, + 0.0193308, 0.1191948, 0.9505322 +); + +const XYZ_TO_LINEAR_REC709 = /*@__PURE__*/ new Matrix3().set( + 3.2409699, -1.5373832, -0.4986108, + -0.9692436, 1.8759675, 0.0415551, + 0.0556301, -0.203977, 1.0569715 +); + +function createColorManagement() { + + const ColorManagement = { + + enabled: true, + + workingColorSpace: LinearSRGBColorSpace, + + /** + * Implementations of supported color spaces. + * + * Required: + * - primaries: chromaticity coordinates [ rx ry gx gy bx by ] + * - whitePoint: reference white [ x y ] + * - transfer: transfer function (pre-defined) + * - toXYZ: Matrix3 RGB to XYZ transform + * - fromXYZ: Matrix3 XYZ to RGB transform + * - luminanceCoefficients: RGB luminance coefficients + * + * Optional: + * - outputColorSpaceConfig: { drawingBufferColorSpace: ColorSpace } + * - workingColorSpaceConfig: { unpackColorSpace: ColorSpace } + * + * Reference: + * - https://www.russellcottrell.com/photo/matrixCalculator.htm + */ + spaces: {}, + + convert: function ( color, sourceColorSpace, targetColorSpace ) { + + if ( this.enabled === false || sourceColorSpace === targetColorSpace || ! sourceColorSpace || ! targetColorSpace ) { + + return color; + + } + + if ( this.spaces[ sourceColorSpace ].transfer === SRGBTransfer ) { + + color.r = SRGBToLinear( color.r ); + color.g = SRGBToLinear( color.g ); + color.b = SRGBToLinear( color.b ); + + } + + if ( this.spaces[ sourceColorSpace ].primaries !== this.spaces[ targetColorSpace ].primaries ) { + + color.applyMatrix3( this.spaces[ sourceColorSpace ].toXYZ ); + color.applyMatrix3( this.spaces[ targetColorSpace ].fromXYZ ); + + } + + if ( this.spaces[ targetColorSpace ].transfer === SRGBTransfer ) { + + color.r = LinearToSRGB( color.r ); + color.g = LinearToSRGB( color.g ); + color.b = LinearToSRGB( color.b ); + + } + + return color; + + }, + + workingToColorSpace: function ( color, targetColorSpace ) { + + return this.convert( color, this.workingColorSpace, targetColorSpace ); + + }, + + colorSpaceToWorking: function ( color, sourceColorSpace ) { + + return this.convert( color, sourceColorSpace, this.workingColorSpace ); + + }, + + getPrimaries: function ( colorSpace ) { + + return this.spaces[ colorSpace ].primaries; + + }, + + getTransfer: function ( colorSpace ) { + + if ( colorSpace === NoColorSpace ) return LinearTransfer; + + return this.spaces[ colorSpace ].transfer; + + }, + + getLuminanceCoefficients: function ( target, colorSpace = this.workingColorSpace ) { + + return target.fromArray( this.spaces[ colorSpace ].luminanceCoefficients ); + + }, + + define: function ( colorSpaces ) { + + Object.assign( this.spaces, colorSpaces ); + + }, + + // Internal APIs + + _getMatrix: function ( targetMatrix, sourceColorSpace, targetColorSpace ) { + + return targetMatrix + .copy( this.spaces[ sourceColorSpace ].toXYZ ) + .multiply( this.spaces[ targetColorSpace ].fromXYZ ); + + }, + + _getDrawingBufferColorSpace: function ( colorSpace ) { + + return this.spaces[ colorSpace ].outputColorSpaceConfig.drawingBufferColorSpace; + + }, + + _getUnpackColorSpace: function ( colorSpace = this.workingColorSpace ) { + + return this.spaces[ colorSpace ].workingColorSpaceConfig.unpackColorSpace; + + }, + + // Deprecated + + fromWorkingColorSpace: function ( color, targetColorSpace ) { + + warnOnce( 'THREE.ColorManagement: .fromWorkingColorSpace() has been renamed to .workingToColorSpace().' ); // @deprecated, r177 + + return ColorManagement.workingToColorSpace( color, targetColorSpace ); + + }, + + toWorkingColorSpace: function ( color, sourceColorSpace ) { + + warnOnce( 'THREE.ColorManagement: .toWorkingColorSpace() has been renamed to .colorSpaceToWorking().' ); // @deprecated, r177 + + return ColorManagement.colorSpaceToWorking( color, sourceColorSpace ); + + }, + + }; + + /****************************************************************************** + * sRGB definitions + */ + + const REC709_PRIMARIES = [ 0.640, 0.330, 0.300, 0.600, 0.150, 0.060 ]; + const REC709_LUMINANCE_COEFFICIENTS = [ 0.2126, 0.7152, 0.0722 ]; + const D65 = [ 0.3127, 0.3290 ]; + + ColorManagement.define( { + + [ LinearSRGBColorSpace ]: { + primaries: REC709_PRIMARIES, + whitePoint: D65, + transfer: LinearTransfer, + toXYZ: LINEAR_REC709_TO_XYZ, + fromXYZ: XYZ_TO_LINEAR_REC709, + luminanceCoefficients: REC709_LUMINANCE_COEFFICIENTS, + workingColorSpaceConfig: { unpackColorSpace: SRGBColorSpace }, + outputColorSpaceConfig: { drawingBufferColorSpace: SRGBColorSpace } + }, + + [ SRGBColorSpace ]: { + primaries: REC709_PRIMARIES, + whitePoint: D65, + transfer: SRGBTransfer, + toXYZ: LINEAR_REC709_TO_XYZ, + fromXYZ: XYZ_TO_LINEAR_REC709, + luminanceCoefficients: REC709_LUMINANCE_COEFFICIENTS, + outputColorSpaceConfig: { drawingBufferColorSpace: SRGBColorSpace } + }, + + } ); + + return ColorManagement; + +} + +const ColorManagement = /*@__PURE__*/ createColorManagement(); + +function SRGBToLinear( c ) { + + return ( c < 0.04045 ) ? c * 0.0773993808 : Math.pow( c * 0.9478672986 + 0.0521327014, 2.4 ); + +} + +function LinearToSRGB( c ) { + + return ( c < 0.0031308 ) ? c * 12.92 : 1.055 * ( Math.pow( c, 0.41666 ) ) - 0.055; + +} + +let _canvas; + +/** + * A class containing utility functions for images. + * + * @hideconstructor + */ +class ImageUtils { + + /** + * Returns a data URI containing a representation of the given image. + * + * @param {(HTMLImageElement|HTMLCanvasElement)} image - The image object. + * @param {string} [type='image/png'] - Indicates the image format. + * @return {string} The data URI. + */ + static getDataURL( image, type = 'image/png' ) { + + if ( /^data:/i.test( image.src ) ) { + + return image.src; + + } + + if ( typeof HTMLCanvasElement === 'undefined' ) { + + return image.src; + + } + + let canvas; + + if ( image instanceof HTMLCanvasElement ) { + + canvas = image; + + } else { + + if ( _canvas === undefined ) _canvas = createElementNS( 'canvas' ); + + _canvas.width = image.width; + _canvas.height = image.height; + + const context = _canvas.getContext( '2d' ); + + if ( image instanceof ImageData ) { + + context.putImageData( image, 0, 0 ); + + } else { + + context.drawImage( image, 0, 0, image.width, image.height ); + + } + + canvas = _canvas; + + } + + return canvas.toDataURL( type ); + + } + + /** + * Converts the given sRGB image data to linear color space. + * + * @param {(HTMLImageElement|HTMLCanvasElement|ImageBitmap|Object)} image - The image object. + * @return {HTMLCanvasElement|Object} The converted image. + */ + static sRGBToLinear( image ) { + + if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || + ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) || + ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) { + + const canvas = createElementNS( 'canvas' ); + + canvas.width = image.width; + canvas.height = image.height; + + const context = canvas.getContext( '2d' ); + context.drawImage( image, 0, 0, image.width, image.height ); + + const imageData = context.getImageData( 0, 0, image.width, image.height ); + const data = imageData.data; + + for ( let i = 0; i < data.length; i ++ ) { + + data[ i ] = SRGBToLinear( data[ i ] / 255 ) * 255; + + } + + context.putImageData( imageData, 0, 0 ); + + return canvas; + + } else if ( image.data ) { + + const data = image.data.slice( 0 ); + + for ( let i = 0; i < data.length; i ++ ) { + + if ( data instanceof Uint8Array || data instanceof Uint8ClampedArray ) { + + data[ i ] = Math.floor( SRGBToLinear( data[ i ] / 255 ) * 255 ); + + } else { + + // assuming float + + data[ i ] = SRGBToLinear( data[ i ] ); + + } + + } + + return { + data: data, + width: image.width, + height: image.height + }; + + } else { + + console.warn( 'THREE.ImageUtils.sRGBToLinear(): Unsupported image type. No color space conversion applied.' ); + return image; + + } + + } + +} + +let _sourceId = 0; + +/** + * Represents the data source of a texture. + * + * The main purpose of this class is to decouple the data definition from the texture + * definition so the same data can be used with multiple texture instances. + */ +class Source { + + /** + * Constructs a new video texture. + * + * @param {any} [data=null] - The data definition of a texture. + */ + constructor( data = null ) { + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSource = true; + + /** + * The ID of the source. + * + * @name Source#id + * @type {number} + * @readonly + */ + Object.defineProperty( this, 'id', { value: _sourceId ++ } ); + + /** + * The UUID of the source. + * + * @type {string} + * @readonly + */ + this.uuid = generateUUID(); + + /** + * The data definition of a texture. + * + * @type {any} + */ + this.data = data; + + /** + * This property is only relevant when {@link Source#needsUpdate} is set to `true` and + * provides more control on how texture data should be processed. When `dataReady` is set + * to `false`, the engine performs the memory allocation (if necessary) but does not transfer + * the data into the GPU memory. + * + * @type {boolean} + * @default true + */ + this.dataReady = true; + + /** + * This starts at `0` and counts how many times {@link Source#needsUpdate} is set to `true`. + * + * @type {number} + * @readonly + * @default 0 + */ + this.version = 0; + + } + + getSize( target ) { + + const data = this.data; + + if ( data instanceof HTMLVideoElement ) { + + target.set( data.videoWidth, data.videoHeight ); + + } else if ( data !== null ) { + + target.set( data.width, data.height, data.depth || 0 ); + + } else { + + target.set( 0, 0, 0 ); + + } + + return target; + + } + + /** + * When the property is set to `true`, the engine allocates the memory + * for the texture (if necessary) and triggers the actual texture upload + * to the GPU next time the source is used. + * + * @type {boolean} + * @default false + * @param {boolean} value + */ + set needsUpdate( value ) { + + if ( value === true ) this.version ++; + + } + + /** + * Serializes the source into JSON. + * + * @param {?(Object|string)} meta - An optional value holding meta information about the serialization. + * @return {Object} A JSON object representing the serialized source. + * @see {@link ObjectLoader#parse} + */ + toJSON( meta ) { + + const isRootObject = ( meta === undefined || typeof meta === 'string' ); + + if ( ! isRootObject && meta.images[ this.uuid ] !== undefined ) { + + return meta.images[ this.uuid ]; + + } + + const output = { + uuid: this.uuid, + url: '' + }; + + const data = this.data; + + if ( data !== null ) { + + let url; + + if ( Array.isArray( data ) ) { + + // cube texture + + url = []; + + for ( let i = 0, l = data.length; i < l; i ++ ) { + + if ( data[ i ].isDataTexture ) { + + url.push( serializeImage( data[ i ].image ) ); + + } else { + + url.push( serializeImage( data[ i ] ) ); + + } + + } + + } else { + + // texture + + url = serializeImage( data ); + + } + + output.url = url; + + } + + if ( ! isRootObject ) { + + meta.images[ this.uuid ] = output; + + } + + return output; + + } + +} + +function serializeImage( image ) { + + if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || + ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) || + ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) { + + // default images + + return ImageUtils.getDataURL( image ); + + } else { + + if ( image.data ) { + + // images of DataTexture + + return { + data: Array.from( image.data ), + width: image.width, + height: image.height, + type: image.data.constructor.name + }; + + } else { + + console.warn( 'THREE.Texture: Unable to serialize Texture.' ); + return {}; + + } + + } + +} + +let _textureId = 0; + +const _tempVec3 = /*@__PURE__*/ new Vector3(); + +/** + * Base class for all textures. + * + * Note: After the initial use of a texture, its dimensions, format, and type + * cannot be changed. Instead, call {@link Texture#dispose} on the texture and instantiate a new one. + * + * @augments EventDispatcher + */ +class Texture extends EventDispatcher { + + /** + * Constructs a new texture. + * + * @param {?Object} [image=Texture.DEFAULT_IMAGE] - The image holding the texture data. + * @param {number} [mapping=Texture.DEFAULT_MAPPING] - The texture mapping. + * @param {number} [wrapS=ClampToEdgeWrapping] - The wrapS value. + * @param {number} [wrapT=ClampToEdgeWrapping] - The wrapT value. + * @param {number} [magFilter=LinearFilter] - The mag filter value. + * @param {number} [minFilter=LinearMipmapLinearFilter] - The min filter value. + * @param {number} [format=RGBAFormat] - The texture format. + * @param {number} [type=UnsignedByteType] - The texture type. + * @param {number} [anisotropy=Texture.DEFAULT_ANISOTROPY] - The anisotropy value. + * @param {string} [colorSpace=NoColorSpace] - The color space. + */ + constructor( image = Texture.DEFAULT_IMAGE, mapping = Texture.DEFAULT_MAPPING, wrapS = ClampToEdgeWrapping, wrapT = ClampToEdgeWrapping, magFilter = LinearFilter, minFilter = LinearMipmapLinearFilter, format = RGBAFormat, type = UnsignedByteType, anisotropy = Texture.DEFAULT_ANISOTROPY, colorSpace = NoColorSpace ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isTexture = true; + + /** + * The ID of the texture. + * + * @name Texture#id + * @type {number} + * @readonly + */ + Object.defineProperty( this, 'id', { value: _textureId ++ } ); + + /** + * The UUID of the material. + * + * @type {string} + * @readonly + */ + this.uuid = generateUUID(); + + /** + * The name of the material. + * + * @type {string} + */ + this.name = ''; + + /** + * The data definition of a texture. A reference to the data source can be + * shared across textures. This is often useful in context of spritesheets + * where multiple textures render the same data but with different texture + * transformations. + * + * @type {Source} + */ + this.source = new Source( image ); + + /** + * An array holding user-defined mipmaps. + * + * @type {Array} + */ + this.mipmaps = []; + + /** + * How the texture is applied to the object. The value `UVMapping` + * is the default, where texture or uv coordinates are used to apply the map. + * + * @type {(UVMapping|CubeReflectionMapping|CubeRefractionMapping|EquirectangularReflectionMapping|EquirectangularRefractionMapping|CubeUVReflectionMapping)} + * @default UVMapping + */ + this.mapping = mapping; + + /** + * Lets you select the uv attribute to map the texture to. `0` for `uv`, + * `1` for `uv1`, `2` for `uv2` and `3` for `uv3`. + * + * @type {number} + * @default 0 + */ + this.channel = 0; + + /** + * This defines how the texture is wrapped horizontally and corresponds to + * *U* in UV mapping. + * + * @type {(RepeatWrapping|ClampToEdgeWrapping|MirroredRepeatWrapping)} + * @default ClampToEdgeWrapping + */ + this.wrapS = wrapS; + + /** + * This defines how the texture is wrapped horizontally and corresponds to + * *V* in UV mapping. + * + * @type {(RepeatWrapping|ClampToEdgeWrapping|MirroredRepeatWrapping)} + * @default ClampToEdgeWrapping + */ + this.wrapT = wrapT; + + /** + * How the texture is sampled when a texel covers more than one pixel. + * + * @type {(NearestFilter|NearestMipmapNearestFilter|NearestMipmapLinearFilter|LinearFilter|LinearMipmapNearestFilter|LinearMipmapLinearFilter)} + * @default LinearFilter + */ + this.magFilter = magFilter; + + /** + * How the texture is sampled when a texel covers less than one pixel. + * + * @type {(NearestFilter|NearestMipmapNearestFilter|NearestMipmapLinearFilter|LinearFilter|LinearMipmapNearestFilter|LinearMipmapLinearFilter)} + * @default LinearMipmapLinearFilter + */ + this.minFilter = minFilter; + + /** + * The number of samples taken along the axis through the pixel that has the + * highest density of texels. By default, this value is `1`. A higher value + * gives a less blurry result than a basic mipmap, at the cost of more + * texture samples being used. + * + * @type {number} + * @default 0 + */ + this.anisotropy = anisotropy; + + /** + * The format of the texture. + * + * @type {number} + * @default RGBAFormat + */ + this.format = format; + + /** + * The default internal format is derived from {@link Texture#format} and {@link Texture#type} and + * defines how the texture data is going to be stored on the GPU. + * + * This property allows to overwrite the default format. + * + * @type {?string} + * @default null + */ + this.internalFormat = null; + + /** + * The data type of the texture. + * + * @type {number} + * @default UnsignedByteType + */ + this.type = type; + + /** + * How much a single repetition of the texture is offset from the beginning, + * in each direction U and V. Typical range is `0.0` to `1.0`. + * + * @type {Vector2} + * @default (0,0) + */ + this.offset = new Vector2( 0, 0 ); + + /** + * How many times the texture is repeated across the surface, in each + * direction U and V. If repeat is set greater than `1` in either direction, + * the corresponding wrap parameter should also be set to `RepeatWrapping` + * or `MirroredRepeatWrapping` to achieve the desired tiling effect. + * + * @type {Vector2} + * @default (1,1) + */ + this.repeat = new Vector2( 1, 1 ); + + /** + * The point around which rotation occurs. A value of `(0.5, 0.5)` corresponds + * to the center of the texture. Default is `(0, 0)`, the lower left. + * + * @type {Vector2} + * @default (0,0) + */ + this.center = new Vector2( 0, 0 ); + + /** + * How much the texture is rotated around the center point, in radians. + * Positive values are counter-clockwise. + * + * @type {number} + * @default 0 + */ + this.rotation = 0; + + /** + * Whether to update the texture's uv-transformation {@link Texture#matrix} + * from the properties {@link Texture#offset}, {@link Texture#repeat}, + * {@link Texture#rotation}, and {@link Texture#center}. + * + * Set this to `false` if you are specifying the uv-transform matrix directly. + * + * @type {boolean} + * @default true + */ + this.matrixAutoUpdate = true; + + /** + * The uv-transformation matrix of the texture. + * + * @type {Matrix3} + */ + this.matrix = new Matrix3(); + + /** + * Whether to generate mipmaps (if possible) for a texture. + * + * Set this to `false` if you are creating mipmaps manually. + * + * @type {boolean} + * @default true + */ + this.generateMipmaps = true; + + /** + * If set to `true`, the alpha channel, if present, is multiplied into the + * color channels when the texture is uploaded to the GPU. + * + * Note that this property has no effect when using `ImageBitmap`. You need to + * configure premultiply alpha on bitmap creation instead. + * + * @type {boolean} + * @default false + */ + this.premultiplyAlpha = false; + + /** + * If set to `true`, the texture is flipped along the vertical axis when + * uploaded to the GPU. + * + * Note that this property has no effect when using `ImageBitmap`. You need to + * configure the flip on bitmap creation instead. + * + * @type {boolean} + * @default true + */ + this.flipY = true; + + /** + * Specifies the alignment requirements for the start of each pixel row in memory. + * The allowable values are `1` (byte-alignment), `2` (rows aligned to even-numbered bytes), + * `4` (word-alignment), and `8` (rows start on double-word boundaries). + * + * @type {number} + * @default 4 + */ + this.unpackAlignment = 4; // valid values: 1, 2, 4, 8 (see http://www.khronos.org/opengles/sdk/docs/man/xhtml/glPixelStorei.xml) + + /** + * Textures containing color data should be annotated with `SRGBColorSpace` or `LinearSRGBColorSpace`. + * + * @type {string} + * @default NoColorSpace + */ + this.colorSpace = colorSpace; + + /** + * An object that can be used to store custom data about the texture. It + * should not hold references to functions as these will not be cloned. + * + * @type {Object} + */ + this.userData = {}; + + /** + * This can be used to only update a subregion or specific rows of the texture (for example, just the + * first 3 rows). Use the `addUpdateRange()` function to add ranges to this array. + * + * @type {Array} + */ + this.updateRanges = []; + + /** + * This starts at `0` and counts how many times {@link Texture#needsUpdate} is set to `true`. + * + * @type {number} + * @readonly + * @default 0 + */ + this.version = 0; + + /** + * A callback function, called when the texture is updated (e.g., when + * {@link Texture#needsUpdate} has been set to true and then the texture is used). + * + * @type {?Function} + * @default null + */ + this.onUpdate = null; + + /** + * An optional back reference to the textures render target. + * + * @type {?(RenderTarget|WebGLRenderTarget)} + * @default null + */ + this.renderTarget = null; + + /** + * Indicates whether a texture belongs to a render target or not. + * + * @type {boolean} + * @readonly + * @default false + */ + this.isRenderTargetTexture = false; + + /** + * Indicates if a texture should be handled like a texture array. + * + * @type {boolean} + * @readonly + * @default false + */ + this.isArrayTexture = image && image.depth && image.depth > 1 ? true : false; + + /** + * Indicates whether this texture should be processed by `PMREMGenerator` or not + * (only relevant for render target textures). + * + * @type {number} + * @readonly + * @default 0 + */ + this.pmremVersion = 0; + + } + + /** + * The width of the texture in pixels. + */ + get width() { + + return this.source.getSize( _tempVec3 ).x; + + } + + /** + * The height of the texture in pixels. + */ + get height() { + + return this.source.getSize( _tempVec3 ).y; + + } + + /** + * The depth of the texture in pixels. + */ + get depth() { + + return this.source.getSize( _tempVec3 ).z; + + } + + /** + * The image object holding the texture data. + * + * @type {?Object} + */ + get image() { + + return this.source.data; + + } + + set image( value = null ) { + + this.source.data = value; + + } + + /** + * Updates the texture transformation matrix from the from the properties {@link Texture#offset}, + * {@link Texture#repeat}, {@link Texture#rotation}, and {@link Texture#center}. + */ + updateMatrix() { + + this.matrix.setUvTransform( this.offset.x, this.offset.y, this.repeat.x, this.repeat.y, this.rotation, this.center.x, this.center.y ); + + } + + /** + * Adds a range of data in the data texture to be updated on the GPU. + * + * @param {number} start - Position at which to start update. + * @param {number} count - The number of components to update. + */ + addUpdateRange( start, count ) { + + this.updateRanges.push( { start, count } ); + + } + + /** + * Clears the update ranges. + */ + clearUpdateRanges() { + + this.updateRanges.length = 0; + + } + + /** + * Returns a new texture with copied values from this instance. + * + * @return {Texture} A clone of this instance. + */ + clone() { + + return new this.constructor().copy( this ); + + } + + /** + * Copies the values of the given texture to this instance. + * + * @param {Texture} source - The texture to copy. + * @return {Texture} A reference to this instance. + */ + copy( source ) { + + this.name = source.name; + + this.source = source.source; + this.mipmaps = source.mipmaps.slice( 0 ); + + this.mapping = source.mapping; + this.channel = source.channel; + + this.wrapS = source.wrapS; + this.wrapT = source.wrapT; + + this.magFilter = source.magFilter; + this.minFilter = source.minFilter; + + this.anisotropy = source.anisotropy; + + this.format = source.format; + this.internalFormat = source.internalFormat; + this.type = source.type; + + this.offset.copy( source.offset ); + this.repeat.copy( source.repeat ); + this.center.copy( source.center ); + this.rotation = source.rotation; + + this.matrixAutoUpdate = source.matrixAutoUpdate; + this.matrix.copy( source.matrix ); + + this.generateMipmaps = source.generateMipmaps; + this.premultiplyAlpha = source.premultiplyAlpha; + this.flipY = source.flipY; + this.unpackAlignment = source.unpackAlignment; + this.colorSpace = source.colorSpace; + + this.renderTarget = source.renderTarget; + this.isRenderTargetTexture = source.isRenderTargetTexture; + this.isArrayTexture = source.isArrayTexture; + + this.userData = JSON.parse( JSON.stringify( source.userData ) ); + + this.needsUpdate = true; + + return this; + + } + + /** + * Sets this texture's properties based on `values`. + * @param {Object} values - A container with texture parameters. + */ + setValues( values ) { + + for ( const key in values ) { + + const newValue = values[ key ]; + + if ( newValue === undefined ) { + + console.warn( `THREE.Texture.setValues(): parameter '${ key }' has value of undefined.` ); + continue; + + } + + const currentValue = this[ key ]; + + if ( currentValue === undefined ) { + + console.warn( `THREE.Texture.setValues(): property '${ key }' does not exist.` ); + continue; + + } + + if ( ( currentValue && newValue ) && ( currentValue.isVector2 && newValue.isVector2 ) ) { + + currentValue.copy( newValue ); + + } else if ( ( currentValue && newValue ) && ( currentValue.isVector3 && newValue.isVector3 ) ) { + + currentValue.copy( newValue ); + + } else if ( ( currentValue && newValue ) && ( currentValue.isMatrix3 && newValue.isMatrix3 ) ) { + + currentValue.copy( newValue ); + + } else { + + this[ key ] = newValue; + + } + + } + + } + + /** + * Serializes the texture into JSON. + * + * @param {?(Object|string)} meta - An optional value holding meta information about the serialization. + * @return {Object} A JSON object representing the serialized texture. + * @see {@link ObjectLoader#parse} + */ + toJSON( meta ) { + + const isRootObject = ( meta === undefined || typeof meta === 'string' ); + + if ( ! isRootObject && meta.textures[ this.uuid ] !== undefined ) { + + return meta.textures[ this.uuid ]; + + } + + const output = { + + metadata: { + version: 4.7, + type: 'Texture', + generator: 'Texture.toJSON' + }, + + uuid: this.uuid, + name: this.name, + + image: this.source.toJSON( meta ).uuid, + + mapping: this.mapping, + channel: this.channel, + + repeat: [ this.repeat.x, this.repeat.y ], + offset: [ this.offset.x, this.offset.y ], + center: [ this.center.x, this.center.y ], + rotation: this.rotation, + + wrap: [ this.wrapS, this.wrapT ], + + format: this.format, + internalFormat: this.internalFormat, + type: this.type, + colorSpace: this.colorSpace, + + minFilter: this.minFilter, + magFilter: this.magFilter, + anisotropy: this.anisotropy, + + flipY: this.flipY, + + generateMipmaps: this.generateMipmaps, + premultiplyAlpha: this.premultiplyAlpha, + unpackAlignment: this.unpackAlignment + + }; + + if ( Object.keys( this.userData ).length > 0 ) output.userData = this.userData; + + if ( ! isRootObject ) { + + meta.textures[ this.uuid ] = output; + + } + + return output; + + } + + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + * + * @fires Texture#dispose + */ + dispose() { + + /** + * Fires when the texture has been disposed of. + * + * @event Texture#dispose + * @type {Object} + */ + this.dispatchEvent( { type: 'dispose' } ); + + } + + /** + * Transforms the given uv vector with the textures uv transformation matrix. + * + * @param {Vector2} uv - The uv vector. + * @return {Vector2} The transformed uv vector. + */ + transformUv( uv ) { + + if ( this.mapping !== UVMapping ) return uv; + + uv.applyMatrix3( this.matrix ); + + if ( uv.x < 0 || uv.x > 1 ) { + + switch ( this.wrapS ) { + + case RepeatWrapping: + + uv.x = uv.x - Math.floor( uv.x ); + break; + + case ClampToEdgeWrapping: + + uv.x = uv.x < 0 ? 0 : 1; + break; + + case MirroredRepeatWrapping: + + if ( Math.abs( Math.floor( uv.x ) % 2 ) === 1 ) { + + uv.x = Math.ceil( uv.x ) - uv.x; + + } else { + + uv.x = uv.x - Math.floor( uv.x ); + + } + + break; + + } + + } + + if ( uv.y < 0 || uv.y > 1 ) { + + switch ( this.wrapT ) { + + case RepeatWrapping: + + uv.y = uv.y - Math.floor( uv.y ); + break; + + case ClampToEdgeWrapping: + + uv.y = uv.y < 0 ? 0 : 1; + break; + + case MirroredRepeatWrapping: + + if ( Math.abs( Math.floor( uv.y ) % 2 ) === 1 ) { + + uv.y = Math.ceil( uv.y ) - uv.y; + + } else { + + uv.y = uv.y - Math.floor( uv.y ); + + } + + break; + + } + + } + + if ( this.flipY ) { + + uv.y = 1 - uv.y; + + } + + return uv; + + } + + /** + * Setting this property to `true` indicates the engine the texture + * must be updated in the next render. This triggers a texture upload + * to the GPU and ensures correct texture parameter configuration. + * + * @type {boolean} + * @default false + * @param {boolean} value + */ + set needsUpdate( value ) { + + if ( value === true ) { + + this.version ++; + this.source.needsUpdate = true; + + } + + } + + /** + * Setting this property to `true` indicates the engine the PMREM + * must be regenerated. + * + * @type {boolean} + * @default false + * @param {boolean} value + */ + set needsPMREMUpdate( value ) { + + if ( value === true ) { + + this.pmremVersion ++; + + } + + } + +} + +/** + * The default image for all textures. + * + * @static + * @type {?Image} + * @default null + */ +Texture.DEFAULT_IMAGE = null; + +/** + * The default mapping for all textures. + * + * @static + * @type {number} + * @default UVMapping + */ +Texture.DEFAULT_MAPPING = UVMapping; + +/** + * The default anisotropy value for all textures. + * + * @static + * @type {number} + * @default 1 + */ +Texture.DEFAULT_ANISOTROPY = 1; + +/** + * Class representing a 4D vector. A 4D vector is an ordered quadruplet of numbers + * (labeled x, y, z and w), which can be used to represent a number of things, such as: + * + * - A point in 4D space. + * - A direction and length in 4D space. In three.js the length will + * always be the Euclidean distance(straight-line distance) from `(0, 0, 0, 0)` to `(x, y, z, w)` + * and the direction is also measured from `(0, 0, 0, 0)` towards `(x, y, z, w)`. + * - Any arbitrary ordered quadruplet of numbers. + * + * There are other things a 4D vector can be used to represent, however these + * are the most common uses in *three.js*. + * + * Iterating through a vector instance will yield its components `(x, y, z, w)` in + * the corresponding order. + * ```js + * const a = new THREE.Vector4( 0, 1, 0, 0 ); + * + * //no arguments; will be initialised to (0, 0, 0, 1) + * const b = new THREE.Vector4( ); + * + * const d = a.dot( b ); + * ``` + */ +class Vector4 { + + /** + * Constructs a new 4D vector. + * + * @param {number} [x=0] - The x value of this vector. + * @param {number} [y=0] - The y value of this vector. + * @param {number} [z=0] - The z value of this vector. + * @param {number} [w=1] - The w value of this vector. + */ + constructor( x = 0, y = 0, z = 0, w = 1 ) { + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + Vector4.prototype.isVector4 = true; + + /** + * The x value of this vector. + * + * @type {number} + */ + this.x = x; + + /** + * The y value of this vector. + * + * @type {number} + */ + this.y = y; + + /** + * The z value of this vector. + * + * @type {number} + */ + this.z = z; + + /** + * The w value of this vector. + * + * @type {number} + */ + this.w = w; + + } + + /** + * Alias for {@link Vector4#z}. + * + * @type {number} + */ + get width() { + + return this.z; + + } + + set width( value ) { + + this.z = value; + + } + + /** + * Alias for {@link Vector4#w}. + * + * @type {number} + */ + get height() { + + return this.w; + + } + + set height( value ) { + + this.w = value; + + } + + /** + * Sets the vector components. + * + * @param {number} x - The value of the x component. + * @param {number} y - The value of the y component. + * @param {number} z - The value of the z component. + * @param {number} w - The value of the w component. + * @return {Vector4} A reference to this vector. + */ + set( x, y, z, w ) { + + this.x = x; + this.y = y; + this.z = z; + this.w = w; + + return this; + + } + + /** + * Sets the vector components to the same value. + * + * @param {number} scalar - The value to set for all vector components. + * @return {Vector4} A reference to this vector. + */ + setScalar( scalar ) { + + this.x = scalar; + this.y = scalar; + this.z = scalar; + this.w = scalar; + + return this; + + } + + /** + * Sets the vector's x component to the given value + * + * @param {number} x - The value to set. + * @return {Vector4} A reference to this vector. + */ + setX( x ) { + + this.x = x; + + return this; + + } + + /** + * Sets the vector's y component to the given value + * + * @param {number} y - The value to set. + * @return {Vector4} A reference to this vector. + */ + setY( y ) { + + this.y = y; + + return this; + + } + + /** + * Sets the vector's z component to the given value + * + * @param {number} z - The value to set. + * @return {Vector4} A reference to this vector. + */ + setZ( z ) { + + this.z = z; + + return this; + + } + + /** + * Sets the vector's w component to the given value + * + * @param {number} w - The value to set. + * @return {Vector4} A reference to this vector. + */ + setW( w ) { + + this.w = w; + + return this; + + } + + /** + * Allows to set a vector component with an index. + * + * @param {number} index - The component index. `0` equals to x, `1` equals to y, + * `2` equals to z, `3` equals to w. + * @param {number} value - The value to set. + * @return {Vector4} A reference to this vector. + */ + setComponent( index, value ) { + + switch ( index ) { + + case 0: this.x = value; break; + case 1: this.y = value; break; + case 2: this.z = value; break; + case 3: this.w = value; break; + default: throw new Error( 'index is out of range: ' + index ); + + } + + return this; + + } + + /** + * Returns the value of the vector component which matches the given index. + * + * @param {number} index - The component index. `0` equals to x, `1` equals to y, + * `2` equals to z, `3` equals to w. + * @return {number} A vector component value. + */ + getComponent( index ) { + + switch ( index ) { + + case 0: return this.x; + case 1: return this.y; + case 2: return this.z; + case 3: return this.w; + default: throw new Error( 'index is out of range: ' + index ); + + } + + } + + /** + * Returns a new vector with copied values from this instance. + * + * @return {Vector4} A clone of this instance. + */ + clone() { + + return new this.constructor( this.x, this.y, this.z, this.w ); + + } + + /** + * Copies the values of the given vector to this instance. + * + * @param {Vector3|Vector4} v - The vector to copy. + * @return {Vector4} A reference to this vector. + */ + copy( v ) { + + this.x = v.x; + this.y = v.y; + this.z = v.z; + this.w = ( v.w !== undefined ) ? v.w : 1; + + return this; + + } + + /** + * Adds the given vector to this instance. + * + * @param {Vector4} v - The vector to add. + * @return {Vector4} A reference to this vector. + */ + add( v ) { + + this.x += v.x; + this.y += v.y; + this.z += v.z; + this.w += v.w; + + return this; + + } + + /** + * Adds the given scalar value to all components of this instance. + * + * @param {number} s - The scalar to add. + * @return {Vector4} A reference to this vector. + */ + addScalar( s ) { + + this.x += s; + this.y += s; + this.z += s; + this.w += s; + + return this; + + } + + /** + * Adds the given vectors and stores the result in this instance. + * + * @param {Vector4} a - The first vector. + * @param {Vector4} b - The second vector. + * @return {Vector4} A reference to this vector. + */ + addVectors( a, b ) { + + this.x = a.x + b.x; + this.y = a.y + b.y; + this.z = a.z + b.z; + this.w = a.w + b.w; + + return this; + + } + + /** + * Adds the given vector scaled by the given factor to this instance. + * + * @param {Vector4} v - The vector. + * @param {number} s - The factor that scales `v`. + * @return {Vector4} A reference to this vector. + */ + addScaledVector( v, s ) { + + this.x += v.x * s; + this.y += v.y * s; + this.z += v.z * s; + this.w += v.w * s; + + return this; + + } + + /** + * Subtracts the given vector from this instance. + * + * @param {Vector4} v - The vector to subtract. + * @return {Vector4} A reference to this vector. + */ + sub( v ) { + + this.x -= v.x; + this.y -= v.y; + this.z -= v.z; + this.w -= v.w; + + return this; + + } + + /** + * Subtracts the given scalar value from all components of this instance. + * + * @param {number} s - The scalar to subtract. + * @return {Vector4} A reference to this vector. + */ + subScalar( s ) { + + this.x -= s; + this.y -= s; + this.z -= s; + this.w -= s; + + return this; + + } + + /** + * Subtracts the given vectors and stores the result in this instance. + * + * @param {Vector4} a - The first vector. + * @param {Vector4} b - The second vector. + * @return {Vector4} A reference to this vector. + */ + subVectors( a, b ) { + + this.x = a.x - b.x; + this.y = a.y - b.y; + this.z = a.z - b.z; + this.w = a.w - b.w; + + return this; + + } + + /** + * Multiplies the given vector with this instance. + * + * @param {Vector4} v - The vector to multiply. + * @return {Vector4} A reference to this vector. + */ + multiply( v ) { + + this.x *= v.x; + this.y *= v.y; + this.z *= v.z; + this.w *= v.w; + + return this; + + } + + /** + * Multiplies the given scalar value with all components of this instance. + * + * @param {number} scalar - The scalar to multiply. + * @return {Vector4} A reference to this vector. + */ + multiplyScalar( scalar ) { + + this.x *= scalar; + this.y *= scalar; + this.z *= scalar; + this.w *= scalar; + + return this; + + } + + /** + * Multiplies this vector with the given 4x4 matrix. + * + * @param {Matrix4} m - The 4x4 matrix. + * @return {Vector4} A reference to this vector. + */ + applyMatrix4( m ) { + + const x = this.x, y = this.y, z = this.z, w = this.w; + const e = m.elements; + + this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] * w; + this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] * w; + this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] * w; + this.w = e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] * w; + + return this; + + } + + /** + * Divides this instance by the given vector. + * + * @param {Vector4} v - The vector to divide. + * @return {Vector4} A reference to this vector. + */ + divide( v ) { + + this.x /= v.x; + this.y /= v.y; + this.z /= v.z; + this.w /= v.w; + + return this; + + } + + /** + * Divides this vector by the given scalar. + * + * @param {number} scalar - The scalar to divide. + * @return {Vector4} A reference to this vector. + */ + divideScalar( scalar ) { + + return this.multiplyScalar( 1 / scalar ); + + } + + /** + * Sets the x, y and z components of this + * vector to the quaternion's axis and w to the angle. + * + * @param {Quaternion} q - The Quaternion to set. + * @return {Vector4} A reference to this vector. + */ + setAxisAngleFromQuaternion( q ) { + + // http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToAngle/index.htm + + // q is assumed to be normalized + + this.w = 2 * Math.acos( q.w ); + + const s = Math.sqrt( 1 - q.w * q.w ); + + if ( s < 0.0001 ) { + + this.x = 1; + this.y = 0; + this.z = 0; + + } else { + + this.x = q.x / s; + this.y = q.y / s; + this.z = q.z / s; + + } + + return this; + + } + + /** + * Sets the x, y and z components of this + * vector to the axis of rotation and w to the angle. + * + * @param {Matrix4} m - A 4x4 matrix of which the upper left 3x3 matrix is a pure rotation matrix. + * @return {Vector4} A reference to this vector. + */ + setAxisAngleFromRotationMatrix( m ) { + + // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToAngle/index.htm + + // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) + + let angle, x, y, z; // variables for result + const epsilon = 0.01, // margin to allow for rounding errors + epsilon2 = 0.1, // margin to distinguish between 0 and 180 degrees + + te = m.elements, + + m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ], + m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ], + m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ]; + + if ( ( Math.abs( m12 - m21 ) < epsilon ) && + ( Math.abs( m13 - m31 ) < epsilon ) && + ( Math.abs( m23 - m32 ) < epsilon ) ) { + + // singularity found + // first check for identity matrix which must have +1 for all terms + // in leading diagonal and zero in other terms + + if ( ( Math.abs( m12 + m21 ) < epsilon2 ) && + ( Math.abs( m13 + m31 ) < epsilon2 ) && + ( Math.abs( m23 + m32 ) < epsilon2 ) && + ( Math.abs( m11 + m22 + m33 - 3 ) < epsilon2 ) ) { + + // this singularity is identity matrix so angle = 0 + + this.set( 1, 0, 0, 0 ); + + return this; // zero angle, arbitrary axis + + } + + // otherwise this singularity is angle = 180 + + angle = Math.PI; + + const xx = ( m11 + 1 ) / 2; + const yy = ( m22 + 1 ) / 2; + const zz = ( m33 + 1 ) / 2; + const xy = ( m12 + m21 ) / 4; + const xz = ( m13 + m31 ) / 4; + const yz = ( m23 + m32 ) / 4; + + if ( ( xx > yy ) && ( xx > zz ) ) { + + // m11 is the largest diagonal term + + if ( xx < epsilon ) { + + x = 0; + y = 0.707106781; + z = 0.707106781; + + } else { + + x = Math.sqrt( xx ); + y = xy / x; + z = xz / x; + + } + + } else if ( yy > zz ) { + + // m22 is the largest diagonal term + + if ( yy < epsilon ) { + + x = 0.707106781; + y = 0; + z = 0.707106781; + + } else { + + y = Math.sqrt( yy ); + x = xy / y; + z = yz / y; + + } + + } else { + + // m33 is the largest diagonal term so base result on this + + if ( zz < epsilon ) { + + x = 0.707106781; + y = 0.707106781; + z = 0; + + } else { + + z = Math.sqrt( zz ); + x = xz / z; + y = yz / z; + + } + + } + + this.set( x, y, z, angle ); + + return this; // return 180 deg rotation + + } + + // as we have reached here there are no singularities so we can handle normally + + let s = Math.sqrt( ( m32 - m23 ) * ( m32 - m23 ) + + ( m13 - m31 ) * ( m13 - m31 ) + + ( m21 - m12 ) * ( m21 - m12 ) ); // used to normalize + + if ( Math.abs( s ) < 0.001 ) s = 1; + + // prevent divide by zero, should not happen if matrix is orthogonal and should be + // caught by singularity test above, but I've left it in just in case + + this.x = ( m32 - m23 ) / s; + this.y = ( m13 - m31 ) / s; + this.z = ( m21 - m12 ) / s; + this.w = Math.acos( ( m11 + m22 + m33 - 1 ) / 2 ); + + return this; + + } + + /** + * Sets the vector components to the position elements of the + * given transformation matrix. + * + * @param {Matrix4} m - The 4x4 matrix. + * @return {Vector4} A reference to this vector. + */ + setFromMatrixPosition( m ) { + + const e = m.elements; + + this.x = e[ 12 ]; + this.y = e[ 13 ]; + this.z = e[ 14 ]; + this.w = e[ 15 ]; + + return this; + + } + + /** + * If this vector's x, y, z or w value is greater than the given vector's x, y, z or w + * value, replace that value with the corresponding min value. + * + * @param {Vector4} v - The vector. + * @return {Vector4} A reference to this vector. + */ + min( v ) { + + this.x = Math.min( this.x, v.x ); + this.y = Math.min( this.y, v.y ); + this.z = Math.min( this.z, v.z ); + this.w = Math.min( this.w, v.w ); + + return this; + + } + + /** + * If this vector's x, y, z or w value is less than the given vector's x, y, z or w + * value, replace that value with the corresponding max value. + * + * @param {Vector4} v - The vector. + * @return {Vector4} A reference to this vector. + */ + max( v ) { + + this.x = Math.max( this.x, v.x ); + this.y = Math.max( this.y, v.y ); + this.z = Math.max( this.z, v.z ); + this.w = Math.max( this.w, v.w ); + + return this; + + } + + /** + * If this vector's x, y, z or w value is greater than the max vector's x, y, z or w + * value, it is replaced by the corresponding value. + * If this vector's x, y, z or w value is less than the min vector's x, y, z or w value, + * it is replaced by the corresponding value. + * + * @param {Vector4} min - The minimum x, y and z values. + * @param {Vector4} max - The maximum x, y and z values in the desired range. + * @return {Vector4} A reference to this vector. + */ + clamp( min, max ) { + + // assumes min < max, componentwise + + this.x = clamp( this.x, min.x, max.x ); + this.y = clamp( this.y, min.y, max.y ); + this.z = clamp( this.z, min.z, max.z ); + this.w = clamp( this.w, min.w, max.w ); + + return this; + + } + + /** + * If this vector's x, y, z or w values are greater than the max value, they are + * replaced by the max value. + * If this vector's x, y, z or w values are less than the min value, they are + * replaced by the min value. + * + * @param {number} minVal - The minimum value the components will be clamped to. + * @param {number} maxVal - The maximum value the components will be clamped to. + * @return {Vector4} A reference to this vector. + */ + clampScalar( minVal, maxVal ) { + + this.x = clamp( this.x, minVal, maxVal ); + this.y = clamp( this.y, minVal, maxVal ); + this.z = clamp( this.z, minVal, maxVal ); + this.w = clamp( this.w, minVal, maxVal ); + + return this; + + } + + /** + * If this vector's length is greater than the max value, it is replaced by + * the max value. + * If this vector's length is less than the min value, it is replaced by the + * min value. + * + * @param {number} min - The minimum value the vector length will be clamped to. + * @param {number} max - The maximum value the vector length will be clamped to. + * @return {Vector4} A reference to this vector. + */ + clampLength( min, max ) { + + const length = this.length(); + + return this.divideScalar( length || 1 ).multiplyScalar( clamp( length, min, max ) ); + + } + + /** + * The components of this vector are rounded down to the nearest integer value. + * + * @return {Vector4} A reference to this vector. + */ + floor() { + + this.x = Math.floor( this.x ); + this.y = Math.floor( this.y ); + this.z = Math.floor( this.z ); + this.w = Math.floor( this.w ); + + return this; + + } + + /** + * The components of this vector are rounded up to the nearest integer value. + * + * @return {Vector4} A reference to this vector. + */ + ceil() { + + this.x = Math.ceil( this.x ); + this.y = Math.ceil( this.y ); + this.z = Math.ceil( this.z ); + this.w = Math.ceil( this.w ); + + return this; + + } + + /** + * The components of this vector are rounded to the nearest integer value + * + * @return {Vector4} A reference to this vector. + */ + round() { + + this.x = Math.round( this.x ); + this.y = Math.round( this.y ); + this.z = Math.round( this.z ); + this.w = Math.round( this.w ); + + return this; + + } + + /** + * The components of this vector are rounded towards zero (up if negative, + * down if positive) to an integer value. + * + * @return {Vector4} A reference to this vector. + */ + roundToZero() { + + this.x = Math.trunc( this.x ); + this.y = Math.trunc( this.y ); + this.z = Math.trunc( this.z ); + this.w = Math.trunc( this.w ); + + return this; + + } + + /** + * Inverts this vector - i.e. sets x = -x, y = -y, z = -z, w = -w. + * + * @return {Vector4} A reference to this vector. + */ + negate() { + + this.x = - this.x; + this.y = - this.y; + this.z = - this.z; + this.w = - this.w; + + return this; + + } + + /** + * Calculates the dot product of the given vector with this instance. + * + * @param {Vector4} v - The vector to compute the dot product with. + * @return {number} The result of the dot product. + */ + dot( v ) { + + return this.x * v.x + this.y * v.y + this.z * v.z + this.w * v.w; + + } + + /** + * Computes the square of the Euclidean length (straight-line length) from + * (0, 0, 0, 0) to (x, y, z, w). If you are comparing the lengths of vectors, you should + * compare the length squared instead as it is slightly more efficient to calculate. + * + * @return {number} The square length of this vector. + */ + lengthSq() { + + return this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w; + + } + + /** + * Computes the Euclidean length (straight-line length) from (0, 0, 0, 0) to (x, y, z, w). + * + * @return {number} The length of this vector. + */ + length() { + + return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w ); + + } + + /** + * Computes the Manhattan length of this vector. + * + * @return {number} The length of this vector. + */ + manhattanLength() { + + return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ) + Math.abs( this.w ); + + } + + /** + * Converts this vector to a unit vector - that is, sets it equal to a vector + * with the same direction as this one, but with a vector length of `1`. + * + * @return {Vector4} A reference to this vector. + */ + normalize() { + + return this.divideScalar( this.length() || 1 ); + + } + + /** + * Sets this vector to a vector with the same direction as this one, but + * with the specified length. + * + * @param {number} length - The new length of this vector. + * @return {Vector4} A reference to this vector. + */ + setLength( length ) { + + return this.normalize().multiplyScalar( length ); + + } + + /** + * Linearly interpolates between the given vector and this instance, where + * alpha is the percent distance along the line - alpha = 0 will be this + * vector, and alpha = 1 will be the given one. + * + * @param {Vector4} v - The vector to interpolate towards. + * @param {number} alpha - The interpolation factor, typically in the closed interval `[0, 1]`. + * @return {Vector4} A reference to this vector. + */ + lerp( v, alpha ) { + + this.x += ( v.x - this.x ) * alpha; + this.y += ( v.y - this.y ) * alpha; + this.z += ( v.z - this.z ) * alpha; + this.w += ( v.w - this.w ) * alpha; + + return this; + + } + + /** + * Linearly interpolates between the given vectors, where alpha is the percent + * distance along the line - alpha = 0 will be first vector, and alpha = 1 will + * be the second one. The result is stored in this instance. + * + * @param {Vector4} v1 - The first vector. + * @param {Vector4} v2 - The second vector. + * @param {number} alpha - The interpolation factor, typically in the closed interval `[0, 1]`. + * @return {Vector4} A reference to this vector. + */ + lerpVectors( v1, v2, alpha ) { + + this.x = v1.x + ( v2.x - v1.x ) * alpha; + this.y = v1.y + ( v2.y - v1.y ) * alpha; + this.z = v1.z + ( v2.z - v1.z ) * alpha; + this.w = v1.w + ( v2.w - v1.w ) * alpha; + + return this; + + } + + /** + * Returns `true` if this vector is equal with the given one. + * + * @param {Vector4} v - The vector to test for equality. + * @return {boolean} Whether this vector is equal with the given one. + */ + equals( v ) { + + return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) && ( v.w === this.w ) ); + + } + + /** + * Sets this vector's x value to be `array[ offset ]`, y value to be `array[ offset + 1 ]`, + * z value to be `array[ offset + 2 ]`, w value to be `array[ offset + 3 ]`. + * + * @param {Array} array - An array holding the vector component values. + * @param {number} [offset=0] - The offset into the array. + * @return {Vector4} A reference to this vector. + */ + fromArray( array, offset = 0 ) { + + this.x = array[ offset ]; + this.y = array[ offset + 1 ]; + this.z = array[ offset + 2 ]; + this.w = array[ offset + 3 ]; + + return this; + + } + + /** + * Writes the components of this vector to the given array. If no array is provided, + * the method returns a new instance. + * + * @param {Array} [array=[]] - The target array holding the vector components. + * @param {number} [offset=0] - Index of the first element in the array. + * @return {Array} The vector components. + */ + toArray( array = [], offset = 0 ) { + + array[ offset ] = this.x; + array[ offset + 1 ] = this.y; + array[ offset + 2 ] = this.z; + array[ offset + 3 ] = this.w; + + return array; + + } + + /** + * Sets the components of this vector from the given buffer attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute holding vector data. + * @param {number} index - The index into the attribute. + * @return {Vector4} A reference to this vector. + */ + fromBufferAttribute( attribute, index ) { + + this.x = attribute.getX( index ); + this.y = attribute.getY( index ); + this.z = attribute.getZ( index ); + this.w = attribute.getW( index ); + + return this; + + } + + /** + * Sets each component of this vector to a pseudo-random value between `0` and + * `1`, excluding `1`. + * + * @return {Vector4} A reference to this vector. + */ + random() { + + this.x = Math.random(); + this.y = Math.random(); + this.z = Math.random(); + this.w = Math.random(); + + return this; + + } + + *[ Symbol.iterator ]() { + + yield this.x; + yield this.y; + yield this.z; + yield this.w; + + } + +} + +/** + * A render target is a buffer where the video card draws pixels for a scene + * that is being rendered in the background. It is used in different effects, + * such as applying postprocessing to a rendered image before displaying it + * on the screen. + * + * @augments EventDispatcher + */ +class RenderTarget extends EventDispatcher { + + /** + * Render target options. + * + * @typedef {Object} RenderTarget~Options + * @property {boolean} [generateMipmaps=false] - Whether to generate mipmaps or not. + * @property {number} [magFilter=LinearFilter] - The mag filter. + * @property {number} [minFilter=LinearFilter] - The min filter. + * @property {number} [format=RGBAFormat] - The texture format. + * @property {number} [type=UnsignedByteType] - The texture type. + * @property {?string} [internalFormat=null] - The texture's internal format. + * @property {number} [wrapS=ClampToEdgeWrapping] - The texture's uv wrapping mode. + * @property {number} [wrapT=ClampToEdgeWrapping] - The texture's uv wrapping mode. + * @property {number} [anisotropy=1] - The texture's anisotropy value. + * @property {string} [colorSpace=NoColorSpace] - The texture's color space. + * @property {boolean} [depthBuffer=true] - Whether to allocate a depth buffer or not. + * @property {boolean} [stencilBuffer=false] - Whether to allocate a stencil buffer or not. + * @property {boolean} [resolveDepthBuffer=true] - Whether to resolve the depth buffer or not. + * @property {boolean} [resolveStencilBuffer=true] - Whether to resolve the stencil buffer or not. + * @property {?Texture} [depthTexture=null] - Reference to a depth texture. + * @property {number} [samples=0] - The MSAA samples count. + * @property {number} [count=1] - Defines the number of color attachments . Must be at least `1`. + * @property {number} [depth=1] - The texture depth. + * @property {boolean} [multiview=false] - Whether this target is used for multiview rendering. + */ + + /** + * Constructs a new render target. + * + * @param {number} [width=1] - The width of the render target. + * @param {number} [height=1] - The height of the render target. + * @param {RenderTarget~Options} [options] - The configuration object. + */ + constructor( width = 1, height = 1, options = {} ) { + + super(); + + options = Object.assign( { + generateMipmaps: false, + internalFormat: null, + minFilter: LinearFilter, + depthBuffer: true, + stencilBuffer: false, + resolveDepthBuffer: true, + resolveStencilBuffer: true, + depthTexture: null, + samples: 0, + count: 1, + depth: 1, + multiview: false + }, options ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isRenderTarget = true; + + /** + * The width of the render target. + * + * @type {number} + * @default 1 + */ + this.width = width; + + /** + * The height of the render target. + * + * @type {number} + * @default 1 + */ + this.height = height; + + /** + * The depth of the render target. + * + * @type {number} + * @default 1 + */ + this.depth = options.depth; + + /** + * A rectangular area inside the render target's viewport. Fragments that are + * outside the area will be discarded. + * + * @type {Vector4} + * @default (0,0,width,height) + */ + this.scissor = new Vector4( 0, 0, width, height ); + + /** + * Indicates whether the scissor test should be enabled when rendering into + * this render target or not. + * + * @type {boolean} + * @default false + */ + this.scissorTest = false; + + /** + * A rectangular area representing the render target's viewport. + * + * @type {Vector4} + * @default (0,0,width,height) + */ + this.viewport = new Vector4( 0, 0, width, height ); + + const image = { width: width, height: height, depth: options.depth }; + + const texture = new Texture( image ); + + /** + * An array of textures. Each color attachment is represented as a separate texture. + * Has at least a single entry for the default color attachment. + * + * @type {Array} + */ + this.textures = []; + + const count = options.count; + for ( let i = 0; i < count; i ++ ) { + + this.textures[ i ] = texture.clone(); + this.textures[ i ].isRenderTargetTexture = true; + this.textures[ i ].renderTarget = this; + + } + + this._setTextureOptions( options ); + + /** + * Whether to allocate a depth buffer or not. + * + * @type {boolean} + * @default true + */ + this.depthBuffer = options.depthBuffer; + + /** + * Whether to allocate a stencil buffer or not. + * + * @type {boolean} + * @default false + */ + this.stencilBuffer = options.stencilBuffer; + + /** + * Whether to resolve the depth buffer or not. + * + * @type {boolean} + * @default true + */ + this.resolveDepthBuffer = options.resolveDepthBuffer; + + /** + * Whether to resolve the stencil buffer or not. + * + * @type {boolean} + * @default true + */ + this.resolveStencilBuffer = options.resolveStencilBuffer; + + this._depthTexture = null; + this.depthTexture = options.depthTexture; + + /** + * The number of MSAA samples. + * + * A value of `0` disables MSAA. + * + * @type {number} + * @default 0 + */ + this.samples = options.samples; + + /** + * Whether to this target is used in multiview rendering. + * + * @type {boolean} + * @default false + */ + this.multiview = options.multiview; + + } + + _setTextureOptions( options = {} ) { + + const values = { + minFilter: LinearFilter, + generateMipmaps: false, + flipY: false, + internalFormat: null + }; + + if ( options.mapping !== undefined ) values.mapping = options.mapping; + if ( options.wrapS !== undefined ) values.wrapS = options.wrapS; + if ( options.wrapT !== undefined ) values.wrapT = options.wrapT; + if ( options.wrapR !== undefined ) values.wrapR = options.wrapR; + if ( options.magFilter !== undefined ) values.magFilter = options.magFilter; + if ( options.minFilter !== undefined ) values.minFilter = options.minFilter; + if ( options.format !== undefined ) values.format = options.format; + if ( options.type !== undefined ) values.type = options.type; + if ( options.anisotropy !== undefined ) values.anisotropy = options.anisotropy; + if ( options.colorSpace !== undefined ) values.colorSpace = options.colorSpace; + if ( options.flipY !== undefined ) values.flipY = options.flipY; + if ( options.generateMipmaps !== undefined ) values.generateMipmaps = options.generateMipmaps; + if ( options.internalFormat !== undefined ) values.internalFormat = options.internalFormat; + + for ( let i = 0; i < this.textures.length; i ++ ) { + + const texture = this.textures[ i ]; + texture.setValues( values ); + + } + + } + + /** + * The texture representing the default color attachment. + * + * @type {Texture} + */ + get texture() { + + return this.textures[ 0 ]; + + } + + set texture( value ) { + + this.textures[ 0 ] = value; + + } + + set depthTexture( current ) { + + if ( this._depthTexture !== null ) this._depthTexture.renderTarget = null; + if ( current !== null ) current.renderTarget = this; + + this._depthTexture = current; + + } + + /** + * Instead of saving the depth in a renderbuffer, a texture + * can be used instead which is useful for further processing + * e.g. in context of post-processing. + * + * @type {?DepthTexture} + * @default null + */ + get depthTexture() { + + return this._depthTexture; + + } + + /** + * Sets the size of this render target. + * + * @param {number} width - The width. + * @param {number} height - The height. + * @param {number} [depth=1] - The depth. + */ + setSize( width, height, depth = 1 ) { + + if ( this.width !== width || this.height !== height || this.depth !== depth ) { + + this.width = width; + this.height = height; + this.depth = depth; + + for ( let i = 0, il = this.textures.length; i < il; i ++ ) { + + this.textures[ i ].image.width = width; + this.textures[ i ].image.height = height; + this.textures[ i ].image.depth = depth; + this.textures[ i ].isArrayTexture = this.textures[ i ].image.depth > 1; + + } + + this.dispose(); + + } + + this.viewport.set( 0, 0, width, height ); + this.scissor.set( 0, 0, width, height ); + + } + + /** + * Returns a new render target with copied values from this instance. + * + * @return {RenderTarget} A clone of this instance. + */ + clone() { + + return new this.constructor().copy( this ); + + } + + /** + * Copies the settings of the given render target. This is a structural copy so + * no resources are shared between render targets after the copy. That includes + * all MRT textures and the depth texture. + * + * @param {RenderTarget} source - The render target to copy. + * @return {RenderTarget} A reference to this instance. + */ + copy( source ) { + + this.width = source.width; + this.height = source.height; + this.depth = source.depth; + + this.scissor.copy( source.scissor ); + this.scissorTest = source.scissorTest; + + this.viewport.copy( source.viewport ); + + this.textures.length = 0; + + for ( let i = 0, il = source.textures.length; i < il; i ++ ) { + + this.textures[ i ] = source.textures[ i ].clone(); + this.textures[ i ].isRenderTargetTexture = true; + this.textures[ i ].renderTarget = this; + + // ensure image object is not shared, see #20328 + + const image = Object.assign( {}, source.textures[ i ].image ); + this.textures[ i ].source = new Source( image ); + + } + + this.depthBuffer = source.depthBuffer; + this.stencilBuffer = source.stencilBuffer; + + this.resolveDepthBuffer = source.resolveDepthBuffer; + this.resolveStencilBuffer = source.resolveStencilBuffer; + + if ( source.depthTexture !== null ) this.depthTexture = source.depthTexture.clone(); + + this.samples = source.samples; + + return this; + + } + + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + * + * @fires RenderTarget#dispose + */ + dispose() { + + this.dispatchEvent( { type: 'dispose' } ); + + } + +} + +/** + * A render target used in context of {@link WebGLRenderer}. + * + * @augments RenderTarget + */ +class WebGLRenderTarget extends RenderTarget { + + /** + * Constructs a new 3D render target. + * + * @param {number} [width=1] - The width of the render target. + * @param {number} [height=1] - The height of the render target. + * @param {RenderTarget~Options} [options] - The configuration object. + */ + constructor( width = 1, height = 1, options = {} ) { + + super( width, height, options ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isWebGLRenderTarget = true; + + } + +} + +/** + * Creates an array of textures directly from raw buffer data. + * + * @augments Texture + */ +class DataArrayTexture extends Texture { + + /** + * Constructs a new data array texture. + * + * @param {?TypedArray} [data=null] - The buffer data. + * @param {number} [width=1] - The width of the texture. + * @param {number} [height=1] - The height of the texture. + * @param {number} [depth=1] - The depth of the texture. + */ + constructor( data = null, width = 1, height = 1, depth = 1 ) { + + super( null ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isDataArrayTexture = true; + + /** + * The image definition of a data texture. + * + * @type {{data:TypedArray,width:number,height:number,depth:number}} + */ + this.image = { data, width, height, depth }; + + /** + * How the texture is sampled when a texel covers more than one pixel. + * + * Overwritten and set to `NearestFilter` by default. + * + * @type {(NearestFilter|NearestMipmapNearestFilter|NearestMipmapLinearFilter|LinearFilter|LinearMipmapNearestFilter|LinearMipmapLinearFilter)} + * @default NearestFilter + */ + this.magFilter = NearestFilter; + + /** + * How the texture is sampled when a texel covers less than one pixel. + * + * Overwritten and set to `NearestFilter` by default. + * + * @type {(NearestFilter|NearestMipmapNearestFilter|NearestMipmapLinearFilter|LinearFilter|LinearMipmapNearestFilter|LinearMipmapLinearFilter)} + * @default NearestFilter + */ + this.minFilter = NearestFilter; + + /** + * This defines how the texture is wrapped in the depth and corresponds to + * *W* in UVW mapping. + * + * @type {(RepeatWrapping|ClampToEdgeWrapping|MirroredRepeatWrapping)} + * @default ClampToEdgeWrapping + */ + this.wrapR = ClampToEdgeWrapping; + + /** + * Whether to generate mipmaps (if possible) for a texture. + * + * Overwritten and set to `false` by default. + * + * @type {boolean} + * @default false + */ + this.generateMipmaps = false; + + /** + * If set to `true`, the texture is flipped along the vertical axis when + * uploaded to the GPU. + * + * Overwritten and set to `false` by default. + * + * @type {boolean} + * @default false + */ + this.flipY = false; + + /** + * Specifies the alignment requirements for the start of each pixel row in memory. + * + * Overwritten and set to `1` by default. + * + * @type {boolean} + * @default 1 + */ + this.unpackAlignment = 1; + + /** + * A set of all layers which need to be updated in the texture. + * + * @type {Set} + */ + this.layerUpdates = new Set(); + + } + + /** + * Describes that a specific layer of the texture needs to be updated. + * Normally when {@link Texture#needsUpdate} is set to `true`, the + * entire data texture array is sent to the GPU. Marking specific + * layers will only transmit subsets of all mipmaps associated with a + * specific depth in the array which is often much more performant. + * + * @param {number} layerIndex - The layer index that should be updated. + */ + addLayerUpdate( layerIndex ) { + + this.layerUpdates.add( layerIndex ); + + } + + /** + * Resets the layer updates registry. + */ + clearLayerUpdates() { + + this.layerUpdates.clear(); + + } + +} + +/** + * An array render target used in context of {@link WebGLRenderer}. + * + * @augments WebGLRenderTarget + */ +class WebGLArrayRenderTarget extends WebGLRenderTarget { + + /** + * Constructs a new array render target. + * + * @param {number} [width=1] - The width of the render target. + * @param {number} [height=1] - The height of the render target. + * @param {number} [depth=1] - The height of the render target. + * @param {RenderTarget~Options} [options] - The configuration object. + */ + constructor( width = 1, height = 1, depth = 1, options = {} ) { + + super( width, height, options ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isWebGLArrayRenderTarget = true; + + this.depth = depth; + + /** + * Overwritten with a different texture type. + * + * @type {DataArrayTexture} + */ + this.texture = new DataArrayTexture( null, width, height, depth ); + this._setTextureOptions( options ); + + this.texture.isRenderTargetTexture = true; + + } + +} + +/** + * Creates a three-dimensional texture from raw data, with parameters to + * divide it into width, height, and depth. + * + * @augments Texture + */ +class Data3DTexture extends Texture { + + /** + * Constructs a new data array texture. + * + * @param {?TypedArray} [data=null] - The buffer data. + * @param {number} [width=1] - The width of the texture. + * @param {number} [height=1] - The height of the texture. + * @param {number} [depth=1] - The depth of the texture. + */ + constructor( data = null, width = 1, height = 1, depth = 1 ) { + + // We're going to add .setXXX() methods for setting properties later. + // Users can still set in Data3DTexture directly. + // + // const texture = new THREE.Data3DTexture( data, width, height, depth ); + // texture.anisotropy = 16; + // + // See #14839 + + super( null ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isData3DTexture = true; + + /** + * The image definition of a data texture. + * + * @type {{data:TypedArray,width:number,height:number,depth:number}} + */ + this.image = { data, width, height, depth }; + + /** + * How the texture is sampled when a texel covers more than one pixel. + * + * Overwritten and set to `NearestFilter` by default. + * + * @type {(NearestFilter|NearestMipmapNearestFilter|NearestMipmapLinearFilter|LinearFilter|LinearMipmapNearestFilter|LinearMipmapLinearFilter)} + * @default NearestFilter + */ + this.magFilter = NearestFilter; + + /** + * How the texture is sampled when a texel covers less than one pixel. + * + * Overwritten and set to `NearestFilter` by default. + * + * @type {(NearestFilter|NearestMipmapNearestFilter|NearestMipmapLinearFilter|LinearFilter|LinearMipmapNearestFilter|LinearMipmapLinearFilter)} + * @default NearestFilter + */ + this.minFilter = NearestFilter; + + /** + * This defines how the texture is wrapped in the depth and corresponds to + * *W* in UVW mapping. + * + * @type {(RepeatWrapping|ClampToEdgeWrapping|MirroredRepeatWrapping)} + * @default ClampToEdgeWrapping + */ + this.wrapR = ClampToEdgeWrapping; + + /** + * Whether to generate mipmaps (if possible) for a texture. + * + * Overwritten and set to `false` by default. + * + * @type {boolean} + * @default false + */ + this.generateMipmaps = false; + + /** + * If set to `true`, the texture is flipped along the vertical axis when + * uploaded to the GPU. + * + * Overwritten and set to `false` by default. + * + * @type {boolean} + * @default false + */ + this.flipY = false; + + /** + * Specifies the alignment requirements for the start of each pixel row in memory. + * + * Overwritten and set to `1` by default. + * + * @type {boolean} + * @default 1 + */ + this.unpackAlignment = 1; + + } + +} + +/** + * A 3D render target used in context of {@link WebGLRenderer}. + * + * @augments WebGLRenderTarget + */ +class WebGL3DRenderTarget extends WebGLRenderTarget { + + /** + * Constructs a new 3D render target. + * + * @param {number} [width=1] - The width of the render target. + * @param {number} [height=1] - The height of the render target. + * @param {number} [depth=1] - The height of the render target. + * @param {RenderTarget~Options} [options] - The configuration object. + */ + constructor( width = 1, height = 1, depth = 1, options = {} ) { + + super( width, height, options ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isWebGL3DRenderTarget = true; + + this.depth = depth; + + /** + * Overwritten with a different texture type. + * + * @type {Data3DTexture} + */ + this.texture = new Data3DTexture( null, width, height, depth ); + this._setTextureOptions( options ); + + this.texture.isRenderTargetTexture = true; + + } + +} + +/** + * Represents an axis-aligned bounding box (AABB) in 3D space. + */ +class Box3 { + + /** + * Constructs a new bounding box. + * + * @param {Vector3} [min=(Infinity,Infinity,Infinity)] - A vector representing the lower boundary of the box. + * @param {Vector3} [max=(-Infinity,-Infinity,-Infinity)] - A vector representing the upper boundary of the box. + */ + constructor( min = new Vector3( + Infinity, + Infinity, + Infinity ), max = new Vector3( - Infinity, - Infinity, - Infinity ) ) { + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isBox3 = true; + + /** + * The lower boundary of the box. + * + * @type {Vector3} + */ + this.min = min; + + /** + * The upper boundary of the box. + * + * @type {Vector3} + */ + this.max = max; + + } + + /** + * Sets the lower and upper boundaries of this box. + * Please note that this method only copies the values from the given objects. + * + * @param {Vector3} min - The lower boundary of the box. + * @param {Vector3} max - The upper boundary of the box. + * @return {Box3} A reference to this bounding box. + */ + set( min, max ) { + + this.min.copy( min ); + this.max.copy( max ); + + return this; + + } + + /** + * Sets the upper and lower bounds of this box so it encloses the position data + * in the given array. + * + * @param {Array} array - An array holding 3D position data. + * @return {Box3} A reference to this bounding box. + */ + setFromArray( array ) { + + this.makeEmpty(); + + for ( let i = 0, il = array.length; i < il; i += 3 ) { + + this.expandByPoint( _vector$b.fromArray( array, i ) ); + + } + + return this; + + } + + /** + * Sets the upper and lower bounds of this box so it encloses the position data + * in the given buffer attribute. + * + * @param {BufferAttribute} attribute - A buffer attribute holding 3D position data. + * @return {Box3} A reference to this bounding box. + */ + setFromBufferAttribute( attribute ) { + + this.makeEmpty(); + + for ( let i = 0, il = attribute.count; i < il; i ++ ) { + + this.expandByPoint( _vector$b.fromBufferAttribute( attribute, i ) ); + + } + + return this; + + } + + /** + * Sets the upper and lower bounds of this box so it encloses the position data + * in the given array. + * + * @param {Array} points - An array holding 3D position data as instances of {@link Vector3}. + * @return {Box3} A reference to this bounding box. + */ + setFromPoints( points ) { + + this.makeEmpty(); + + for ( let i = 0, il = points.length; i < il; i ++ ) { + + this.expandByPoint( points[ i ] ); + + } + + return this; + + } + + /** + * Centers this box on the given center vector and sets this box's width, height and + * depth to the given size values. + * + * @param {Vector3} center - The center of the box. + * @param {Vector3} size - The x, y and z dimensions of the box. + * @return {Box3} A reference to this bounding box. + */ + setFromCenterAndSize( center, size ) { + + const halfSize = _vector$b.copy( size ).multiplyScalar( 0.5 ); + + this.min.copy( center ).sub( halfSize ); + this.max.copy( center ).add( halfSize ); + + return this; + + } + + /** + * Computes the world-axis-aligned bounding box for the given 3D object + * (including its children), accounting for the object's, and children's, + * world transforms. The function may result in a larger box than strictly necessary. + * + * @param {Object3D} object - The 3D object to compute the bounding box for. + * @param {boolean} [precise=false] - If set to `true`, the method computes the smallest + * world-axis-aligned bounding box at the expense of more computation. + * @return {Box3} A reference to this bounding box. + */ + setFromObject( object, precise = false ) { + + this.makeEmpty(); + + return this.expandByObject( object, precise ); + + } + + /** + * Returns a new box with copied values from this instance. + * + * @return {Box3} A clone of this instance. + */ + clone() { + + return new this.constructor().copy( this ); + + } + + /** + * Copies the values of the given box to this instance. + * + * @param {Box3} box - The box to copy. + * @return {Box3} A reference to this bounding box. + */ + copy( box ) { + + this.min.copy( box.min ); + this.max.copy( box.max ); + + return this; + + } + + /** + * Makes this box empty which means in encloses a zero space in 3D. + * + * @return {Box3} A reference to this bounding box. + */ + makeEmpty() { + + this.min.x = this.min.y = this.min.z = + Infinity; + this.max.x = this.max.y = this.max.z = - Infinity; + + return this; + + } + + /** + * Returns true if this box includes zero points within its bounds. + * Note that a box with equal lower and upper bounds still includes one + * point, the one both bounds share. + * + * @return {boolean} Whether this box is empty or not. + */ + isEmpty() { + + // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes + + return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y ) || ( this.max.z < this.min.z ); + + } + + /** + * Returns the center point of this box. + * + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The center point. + */ + getCenter( target ) { + + return this.isEmpty() ? target.set( 0, 0, 0 ) : target.addVectors( this.min, this.max ).multiplyScalar( 0.5 ); + + } + + /** + * Returns the dimensions of this box. + * + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The size. + */ + getSize( target ) { + + return this.isEmpty() ? target.set( 0, 0, 0 ) : target.subVectors( this.max, this.min ); + + } + + /** + * Expands the boundaries of this box to include the given point. + * + * @param {Vector3} point - The point that should be included by the bounding box. + * @return {Box3} A reference to this bounding box. + */ + expandByPoint( point ) { + + this.min.min( point ); + this.max.max( point ); + + return this; + + } + + /** + * Expands this box equilaterally by the given vector. The width of this + * box will be expanded by the x component of the vector in both + * directions. The height of this box will be expanded by the y component of + * the vector in both directions. The depth of this box will be + * expanded by the z component of the vector in both directions. + * + * @param {Vector3} vector - The vector that should expand the bounding box. + * @return {Box3} A reference to this bounding box. + */ + expandByVector( vector ) { + + this.min.sub( vector ); + this.max.add( vector ); + + return this; + + } + + /** + * Expands each dimension of the box by the given scalar. If negative, the + * dimensions of the box will be contracted. + * + * @param {number} scalar - The scalar value that should expand the bounding box. + * @return {Box3} A reference to this bounding box. + */ + expandByScalar( scalar ) { + + this.min.addScalar( - scalar ); + this.max.addScalar( scalar ); + + return this; + + } + + /** + * Expands the boundaries of this box to include the given 3D object and + * its children, accounting for the object's, and children's, world + * transforms. The function may result in a larger box than strictly + * necessary (unless the precise parameter is set to true). + * + * @param {Object3D} object - The 3D object that should expand the bounding box. + * @param {boolean} precise - If set to `true`, the method expands the bounding box + * as little as necessary at the expense of more computation. + * @return {Box3} A reference to this bounding box. + */ + expandByObject( object, precise = false ) { + + // Computes the world-axis-aligned bounding box of an object (including its children), + // accounting for both the object's, and children's, world transforms + + object.updateWorldMatrix( false, false ); + + const geometry = object.geometry; + + if ( geometry !== undefined ) { + + const positionAttribute = geometry.getAttribute( 'position' ); + + // precise AABB computation based on vertex data requires at least a position attribute. + // instancing isn't supported so far and uses the normal (conservative) code path. + + if ( precise === true && positionAttribute !== undefined && object.isInstancedMesh !== true ) { + + for ( let i = 0, l = positionAttribute.count; i < l; i ++ ) { + + if ( object.isMesh === true ) { + + object.getVertexPosition( i, _vector$b ); + + } else { + + _vector$b.fromBufferAttribute( positionAttribute, i ); + + } + + _vector$b.applyMatrix4( object.matrixWorld ); + this.expandByPoint( _vector$b ); + + } + + } else { + + if ( object.boundingBox !== undefined ) { + + // object-level bounding box + + if ( object.boundingBox === null ) { + + object.computeBoundingBox(); + + } + + _box$4.copy( object.boundingBox ); + + + } else { + + // geometry-level bounding box + + if ( geometry.boundingBox === null ) { + + geometry.computeBoundingBox(); + + } + + _box$4.copy( geometry.boundingBox ); + + } + + _box$4.applyMatrix4( object.matrixWorld ); + + this.union( _box$4 ); + + } + + } + + const children = object.children; + + for ( let i = 0, l = children.length; i < l; i ++ ) { + + this.expandByObject( children[ i ], precise ); + + } + + return this; + + } + + /** + * Returns `true` if the given point lies within or on the boundaries of this box. + * + * @param {Vector3} point - The point to test. + * @return {boolean} Whether the bounding box contains the given point or not. + */ + containsPoint( point ) { + + return point.x >= this.min.x && point.x <= this.max.x && + point.y >= this.min.y && point.y <= this.max.y && + point.z >= this.min.z && point.z <= this.max.z; + + } + + /** + * Returns `true` if this bounding box includes the entirety of the given bounding box. + * If this box and the given one are identical, this function also returns `true`. + * + * @param {Box3} box - The bounding box to test. + * @return {boolean} Whether the bounding box contains the given bounding box or not. + */ + containsBox( box ) { + + return this.min.x <= box.min.x && box.max.x <= this.max.x && + this.min.y <= box.min.y && box.max.y <= this.max.y && + this.min.z <= box.min.z && box.max.z <= this.max.z; + + } + + /** + * Returns a point as a proportion of this box's width, height and depth. + * + * @param {Vector3} point - A point in 3D space. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} A point as a proportion of this box's width, height and depth. + */ + getParameter( point, target ) { + + // This can potentially have a divide by zero if the box + // has a size dimension of 0. + + return target.set( + ( point.x - this.min.x ) / ( this.max.x - this.min.x ), + ( point.y - this.min.y ) / ( this.max.y - this.min.y ), + ( point.z - this.min.z ) / ( this.max.z - this.min.z ) + ); + + } + + /** + * Returns `true` if the given bounding box intersects with this bounding box. + * + * @param {Box3} box - The bounding box to test. + * @return {boolean} Whether the given bounding box intersects with this bounding box. + */ + intersectsBox( box ) { + + // using 6 splitting planes to rule out intersections. + return box.max.x >= this.min.x && box.min.x <= this.max.x && + box.max.y >= this.min.y && box.min.y <= this.max.y && + box.max.z >= this.min.z && box.min.z <= this.max.z; + + } + + /** + * Returns `true` if the given bounding sphere intersects with this bounding box. + * + * @param {Sphere} sphere - The bounding sphere to test. + * @return {boolean} Whether the given bounding sphere intersects with this bounding box. + */ + intersectsSphere( sphere ) { + + // Find the point on the AABB closest to the sphere center. + this.clampPoint( sphere.center, _vector$b ); + + // If that point is inside the sphere, the AABB and sphere intersect. + return _vector$b.distanceToSquared( sphere.center ) <= ( sphere.radius * sphere.radius ); + + } + + /** + * Returns `true` if the given plane intersects with this bounding box. + * + * @param {Plane} plane - The plane to test. + * @return {boolean} Whether the given plane intersects with this bounding box. + */ + intersectsPlane( plane ) { + + // We compute the minimum and maximum dot product values. If those values + // are on the same side (back or front) of the plane, then there is no intersection. + + let min, max; + + if ( plane.normal.x > 0 ) { + + min = plane.normal.x * this.min.x; + max = plane.normal.x * this.max.x; + + } else { + + min = plane.normal.x * this.max.x; + max = plane.normal.x * this.min.x; + + } + + if ( plane.normal.y > 0 ) { + + min += plane.normal.y * this.min.y; + max += plane.normal.y * this.max.y; + + } else { + + min += plane.normal.y * this.max.y; + max += plane.normal.y * this.min.y; + + } + + if ( plane.normal.z > 0 ) { + + min += plane.normal.z * this.min.z; + max += plane.normal.z * this.max.z; + + } else { + + min += plane.normal.z * this.max.z; + max += plane.normal.z * this.min.z; + + } + + return ( min <= - plane.constant && max >= - plane.constant ); + + } + + /** + * Returns `true` if the given triangle intersects with this bounding box. + * + * @param {Triangle} triangle - The triangle to test. + * @return {boolean} Whether the given triangle intersects with this bounding box. + */ + intersectsTriangle( triangle ) { + + if ( this.isEmpty() ) { + + return false; + + } + + // compute box center and extents + this.getCenter( _center ); + _extents.subVectors( this.max, _center ); + + // translate triangle to aabb origin + _v0$2.subVectors( triangle.a, _center ); + _v1$7.subVectors( triangle.b, _center ); + _v2$4.subVectors( triangle.c, _center ); + + // compute edge vectors for triangle + _f0.subVectors( _v1$7, _v0$2 ); + _f1.subVectors( _v2$4, _v1$7 ); + _f2.subVectors( _v0$2, _v2$4 ); + + // test against axes that are given by cross product combinations of the edges of the triangle and the edges of the aabb + // make an axis testing of each of the 3 sides of the aabb against each of the 3 sides of the triangle = 9 axis of separation + // axis_ij = u_i x f_j (u0, u1, u2 = face normals of aabb = x,y,z axes vectors since aabb is axis aligned) + let axes = [ + 0, - _f0.z, _f0.y, 0, - _f1.z, _f1.y, 0, - _f2.z, _f2.y, + _f0.z, 0, - _f0.x, _f1.z, 0, - _f1.x, _f2.z, 0, - _f2.x, + - _f0.y, _f0.x, 0, - _f1.y, _f1.x, 0, - _f2.y, _f2.x, 0 + ]; + if ( ! satForAxes( axes, _v0$2, _v1$7, _v2$4, _extents ) ) { + + return false; + + } + + // test 3 face normals from the aabb + axes = [ 1, 0, 0, 0, 1, 0, 0, 0, 1 ]; + if ( ! satForAxes( axes, _v0$2, _v1$7, _v2$4, _extents ) ) { + + return false; + + } + + // finally testing the face normal of the triangle + // use already existing triangle edge vectors here + _triangleNormal.crossVectors( _f0, _f1 ); + axes = [ _triangleNormal.x, _triangleNormal.y, _triangleNormal.z ]; + + return satForAxes( axes, _v0$2, _v1$7, _v2$4, _extents ); + + } + + /** + * Clamps the given point within the bounds of this box. + * + * @param {Vector3} point - The point to clamp. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The clamped point. + */ + clampPoint( point, target ) { + + return target.copy( point ).clamp( this.min, this.max ); + + } + + /** + * Returns the euclidean distance from any edge of this box to the specified point. If + * the given point lies inside of this box, the distance will be `0`. + * + * @param {Vector3} point - The point to compute the distance to. + * @return {number} The euclidean distance. + */ + distanceToPoint( point ) { + + return this.clampPoint( point, _vector$b ).distanceTo( point ); + + } + + /** + * Returns a bounding sphere that encloses this bounding box. + * + * @param {Sphere} target - The target sphere that is used to store the method's result. + * @return {Sphere} The bounding sphere that encloses this bounding box. + */ + getBoundingSphere( target ) { + + if ( this.isEmpty() ) { + + target.makeEmpty(); + + } else { + + this.getCenter( target.center ); + + target.radius = this.getSize( _vector$b ).length() * 0.5; + + } + + return target; + + } + + /** + * Computes the intersection of this bounding box and the given one, setting the upper + * bound of this box to the lesser of the two boxes' upper bounds and the + * lower bound of this box to the greater of the two boxes' lower bounds. If + * there's no overlap, makes this box empty. + * + * @param {Box3} box - The bounding box to intersect with. + * @return {Box3} A reference to this bounding box. + */ + intersect( box ) { + + this.min.max( box.min ); + this.max.min( box.max ); + + // ensure that if there is no overlap, the result is fully empty, not slightly empty with non-inf/+inf values that will cause subsequence intersects to erroneously return valid values. + if ( this.isEmpty() ) this.makeEmpty(); + + return this; + + } + + /** + * Computes the union of this box and another and the given one, setting the upper + * bound of this box to the greater of the two boxes' upper bounds and the + * lower bound of this box to the lesser of the two boxes' lower bounds. + * + * @param {Box3} box - The bounding box that will be unioned with this instance. + * @return {Box3} A reference to this bounding box. + */ + union( box ) { + + this.min.min( box.min ); + this.max.max( box.max ); + + return this; + + } + + /** + * Transforms this bounding box by the given 4x4 transformation matrix. + * + * @param {Matrix4} matrix - The transformation matrix. + * @return {Box3} A reference to this bounding box. + */ + applyMatrix4( matrix ) { + + // transform of empty box is an empty box. + if ( this.isEmpty() ) return this; + + // NOTE: I am using a binary pattern to specify all 2^3 combinations below + _points[ 0 ].set( this.min.x, this.min.y, this.min.z ).applyMatrix4( matrix ); // 000 + _points[ 1 ].set( this.min.x, this.min.y, this.max.z ).applyMatrix4( matrix ); // 001 + _points[ 2 ].set( this.min.x, this.max.y, this.min.z ).applyMatrix4( matrix ); // 010 + _points[ 3 ].set( this.min.x, this.max.y, this.max.z ).applyMatrix4( matrix ); // 011 + _points[ 4 ].set( this.max.x, this.min.y, this.min.z ).applyMatrix4( matrix ); // 100 + _points[ 5 ].set( this.max.x, this.min.y, this.max.z ).applyMatrix4( matrix ); // 101 + _points[ 6 ].set( this.max.x, this.max.y, this.min.z ).applyMatrix4( matrix ); // 110 + _points[ 7 ].set( this.max.x, this.max.y, this.max.z ).applyMatrix4( matrix ); // 111 + + this.setFromPoints( _points ); + + return this; + + } + + /** + * Adds the given offset to both the upper and lower bounds of this bounding box, + * effectively moving it in 3D space. + * + * @param {Vector3} offset - The offset that should be used to translate the bounding box. + * @return {Box3} A reference to this bounding box. + */ + translate( offset ) { + + this.min.add( offset ); + this.max.add( offset ); + + return this; + + } + + /** + * Returns `true` if this bounding box is equal with the given one. + * + * @param {Box3} box - The box to test for equality. + * @return {boolean} Whether this bounding box is equal with the given one. + */ + equals( box ) { + + return box.min.equals( this.min ) && box.max.equals( this.max ); + + } + + /** + * Returns a serialized structure of the bounding box. + * + * @return {Object} Serialized structure with fields representing the object state. + */ + toJSON() { + + return { + min: this.min.toArray(), + max: this.max.toArray() + }; + + } + + /** + * Returns a serialized structure of the bounding box. + * + * @param {Object} json - The serialized json to set the box from. + * @return {Box3} A reference to this bounding box. + */ + fromJSON( json ) { + + this.min.fromArray( json.min ); + this.max.fromArray( json.max ); + return this; + + } + +} + +const _points = [ + /*@__PURE__*/ new Vector3(), + /*@__PURE__*/ new Vector3(), + /*@__PURE__*/ new Vector3(), + /*@__PURE__*/ new Vector3(), + /*@__PURE__*/ new Vector3(), + /*@__PURE__*/ new Vector3(), + /*@__PURE__*/ new Vector3(), + /*@__PURE__*/ new Vector3() +]; + +const _vector$b = /*@__PURE__*/ new Vector3(); + +const _box$4 = /*@__PURE__*/ new Box3(); + +// triangle centered vertices + +const _v0$2 = /*@__PURE__*/ new Vector3(); +const _v1$7 = /*@__PURE__*/ new Vector3(); +const _v2$4 = /*@__PURE__*/ new Vector3(); + +// triangle edge vectors + +const _f0 = /*@__PURE__*/ new Vector3(); +const _f1 = /*@__PURE__*/ new Vector3(); +const _f2 = /*@__PURE__*/ new Vector3(); + +const _center = /*@__PURE__*/ new Vector3(); +const _extents = /*@__PURE__*/ new Vector3(); +const _triangleNormal = /*@__PURE__*/ new Vector3(); +const _testAxis = /*@__PURE__*/ new Vector3(); + +function satForAxes( axes, v0, v1, v2, extents ) { + + for ( let i = 0, j = axes.length - 3; i <= j; i += 3 ) { + + _testAxis.fromArray( axes, i ); + // project the aabb onto the separating axis + const r = extents.x * Math.abs( _testAxis.x ) + extents.y * Math.abs( _testAxis.y ) + extents.z * Math.abs( _testAxis.z ); + // project all 3 vertices of the triangle onto the separating axis + const p0 = v0.dot( _testAxis ); + const p1 = v1.dot( _testAxis ); + const p2 = v2.dot( _testAxis ); + // actual test, basically see if either of the most extreme of the triangle points intersects r + if ( Math.max( - Math.max( p0, p1, p2 ), Math.min( p0, p1, p2 ) ) > r ) { + + // points of the projected triangle are outside the projected half-length of the aabb + // the axis is separating and we can exit + return false; + + } + + } + + return true; + +} + +const _box$3 = /*@__PURE__*/ new Box3(); +const _v1$6 = /*@__PURE__*/ new Vector3(); +const _v2$3 = /*@__PURE__*/ new Vector3(); + +/** + * An analytical 3D sphere defined by a center and radius. This class is mainly + * used as a Bounding Sphere for 3D objects. + */ +class Sphere { + + /** + * Constructs a new sphere. + * + * @param {Vector3} [center=(0,0,0)] - The center of the sphere + * @param {number} [radius=-1] - The radius of the sphere. + */ + constructor( center = new Vector3(), radius = -1 ) { + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSphere = true; + + /** + * The center of the sphere + * + * @type {Vector3} + */ + this.center = center; + + /** + * The radius of the sphere. + * + * @type {number} + */ + this.radius = radius; + + } + + /** + * Sets the sphere's components by copying the given values. + * + * @param {Vector3} center - The center. + * @param {number} radius - The radius. + * @return {Sphere} A reference to this sphere. + */ + set( center, radius ) { + + this.center.copy( center ); + this.radius = radius; + + return this; + + } + + /** + * Computes the minimum bounding sphere for list of points. + * If the optional center point is given, it is used as the sphere's + * center. Otherwise, the center of the axis-aligned bounding box + * encompassing the points is calculated. + * + * @param {Array} points - A list of points in 3D space. + * @param {Vector3} [optionalCenter] - The center of the sphere. + * @return {Sphere} A reference to this sphere. + */ + setFromPoints( points, optionalCenter ) { + + const center = this.center; + + if ( optionalCenter !== undefined ) { + + center.copy( optionalCenter ); + + } else { + + _box$3.setFromPoints( points ).getCenter( center ); + + } + + let maxRadiusSq = 0; + + for ( let i = 0, il = points.length; i < il; i ++ ) { + + maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( points[ i ] ) ); + + } + + this.radius = Math.sqrt( maxRadiusSq ); + + return this; + + } + + /** + * Copies the values of the given sphere to this instance. + * + * @param {Sphere} sphere - The sphere to copy. + * @return {Sphere} A reference to this sphere. + */ + copy( sphere ) { + + this.center.copy( sphere.center ); + this.radius = sphere.radius; + + return this; + + } + + /** + * Returns `true` if the sphere is empty (the radius set to a negative number). + * + * Spheres with a radius of `0` contain only their center point and are not + * considered to be empty. + * + * @return {boolean} Whether this sphere is empty or not. + */ + isEmpty() { + + return ( this.radius < 0 ); + + } + + /** + * Makes this sphere empty which means in encloses a zero space in 3D. + * + * @return {Sphere} A reference to this sphere. + */ + makeEmpty() { + + this.center.set( 0, 0, 0 ); + this.radius = -1; + + return this; + + } + + /** + * Returns `true` if this sphere contains the given point inclusive of + * the surface of the sphere. + * + * @param {Vector3} point - The point to check. + * @return {boolean} Whether this sphere contains the given point or not. + */ + containsPoint( point ) { + + return ( point.distanceToSquared( this.center ) <= ( this.radius * this.radius ) ); + + } + + /** + * Returns the closest distance from the boundary of the sphere to the + * given point. If the sphere contains the point, the distance will + * be negative. + * + * @param {Vector3} point - The point to compute the distance to. + * @return {number} The distance to the point. + */ + distanceToPoint( point ) { + + return ( point.distanceTo( this.center ) - this.radius ); + + } + + /** + * Returns `true` if this sphere intersects with the given one. + * + * @param {Sphere} sphere - The sphere to test. + * @return {boolean} Whether this sphere intersects with the given one or not. + */ + intersectsSphere( sphere ) { + + const radiusSum = this.radius + sphere.radius; + + return sphere.center.distanceToSquared( this.center ) <= ( radiusSum * radiusSum ); + + } + + /** + * Returns `true` if this sphere intersects with the given box. + * + * @param {Box3} box - The box to test. + * @return {boolean} Whether this sphere intersects with the given box or not. + */ + intersectsBox( box ) { + + return box.intersectsSphere( this ); + + } + + /** + * Returns `true` if this sphere intersects with the given plane. + * + * @param {Plane} plane - The plane to test. + * @return {boolean} Whether this sphere intersects with the given plane or not. + */ + intersectsPlane( plane ) { + + return Math.abs( plane.distanceToPoint( this.center ) ) <= this.radius; + + } + + /** + * Clamps a point within the sphere. If the point is outside the sphere, it + * will clamp it to the closest point on the edge of the sphere. Points + * already inside the sphere will not be affected. + * + * @param {Vector3} point - The plane to clamp. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The clamped point. + */ + clampPoint( point, target ) { + + const deltaLengthSq = this.center.distanceToSquared( point ); + + target.copy( point ); + + if ( deltaLengthSq > ( this.radius * this.radius ) ) { + + target.sub( this.center ).normalize(); + target.multiplyScalar( this.radius ).add( this.center ); + + } + + return target; + + } + + /** + * Returns a bounding box that encloses this sphere. + * + * @param {Box3} target - The target box that is used to store the method's result. + * @return {Box3} The bounding box that encloses this sphere. + */ + getBoundingBox( target ) { + + if ( this.isEmpty() ) { + + // Empty sphere produces empty bounding box + target.makeEmpty(); + return target; + + } + + target.set( this.center, this.center ); + target.expandByScalar( this.radius ); + + return target; + + } + + /** + * Transforms this sphere with the given 4x4 transformation matrix. + * + * @param {Matrix4} matrix - The transformation matrix. + * @return {Sphere} A reference to this sphere. + */ + applyMatrix4( matrix ) { + + this.center.applyMatrix4( matrix ); + this.radius = this.radius * matrix.getMaxScaleOnAxis(); + + return this; + + } + + /** + * Translates the sphere's center by the given offset. + * + * @param {Vector3} offset - The offset. + * @return {Sphere} A reference to this sphere. + */ + translate( offset ) { + + this.center.add( offset ); + + return this; + + } + + /** + * Expands the boundaries of this sphere to include the given point. + * + * @param {Vector3} point - The point to include. + * @return {Sphere} A reference to this sphere. + */ + expandByPoint( point ) { + + if ( this.isEmpty() ) { + + this.center.copy( point ); + + this.radius = 0; + + return this; + + } + + _v1$6.subVectors( point, this.center ); + + const lengthSq = _v1$6.lengthSq(); + + if ( lengthSq > ( this.radius * this.radius ) ) { + + // calculate the minimal sphere + + const length = Math.sqrt( lengthSq ); + + const delta = ( length - this.radius ) * 0.5; + + this.center.addScaledVector( _v1$6, delta / length ); + + this.radius += delta; + + } + + return this; + + } + + /** + * Expands this sphere to enclose both the original sphere and the given sphere. + * + * @param {Sphere} sphere - The sphere to include. + * @return {Sphere} A reference to this sphere. + */ + union( sphere ) { + + if ( sphere.isEmpty() ) { + + return this; + + } + + if ( this.isEmpty() ) { + + this.copy( sphere ); + + return this; + + } + + if ( this.center.equals( sphere.center ) === true ) { + + this.radius = Math.max( this.radius, sphere.radius ); + + } else { + + _v2$3.subVectors( sphere.center, this.center ).setLength( sphere.radius ); + + this.expandByPoint( _v1$6.copy( sphere.center ).add( _v2$3 ) ); + + this.expandByPoint( _v1$6.copy( sphere.center ).sub( _v2$3 ) ); + + } + + return this; + + } + + /** + * Returns `true` if this sphere is equal with the given one. + * + * @param {Sphere} sphere - The sphere to test for equality. + * @return {boolean} Whether this bounding sphere is equal with the given one. + */ + equals( sphere ) { + + return sphere.center.equals( this.center ) && ( sphere.radius === this.radius ); + + } + + /** + * Returns a new sphere with copied values from this instance. + * + * @return {Sphere} A clone of this instance. + */ + clone() { + + return new this.constructor().copy( this ); + + } + + /** + * Returns a serialized structure of the bounding sphere. + * + * @return {Object} Serialized structure with fields representing the object state. + */ + toJSON() { + + return { + radius: this.radius, + center: this.center.toArray() + }; + + } + + /** + * Returns a serialized structure of the bounding sphere. + * + * @param {Object} json - The serialized json to set the sphere from. + * @return {Box3} A reference to this bounding sphere. + */ + fromJSON( json ) { + + this.radius = json.radius; + this.center.fromArray( json.center ); + return this; + + } + +} + +const _vector$a = /*@__PURE__*/ new Vector3(); +const _segCenter = /*@__PURE__*/ new Vector3(); +const _segDir = /*@__PURE__*/ new Vector3(); +const _diff = /*@__PURE__*/ new Vector3(); + +const _edge1 = /*@__PURE__*/ new Vector3(); +const _edge2 = /*@__PURE__*/ new Vector3(); +const _normal$1 = /*@__PURE__*/ new Vector3(); + +/** + * A ray that emits from an origin in a certain direction. The class is used by + * {@link Raycaster} to assist with raycasting. Raycasting is used for + * mouse picking (working out what objects in the 3D space the mouse is over) + * amongst other things. + */ +class Ray { + + /** + * Constructs a new ray. + * + * @param {Vector3} [origin=(0,0,0)] - The origin of the ray. + * @param {Vector3} [direction=(0,0,-1)] - The (normalized) direction of the ray. + */ + constructor( origin = new Vector3(), direction = new Vector3( 0, 0, -1 ) ) { + + /** + * The origin of the ray. + * + * @type {Vector3} + */ + this.origin = origin; + + /** + * The (normalized) direction of the ray. + * + * @type {Vector3} + */ + this.direction = direction; + + } + + /** + * Sets the ray's components by copying the given values. + * + * @param {Vector3} origin - The origin. + * @param {Vector3} direction - The direction. + * @return {Ray} A reference to this ray. + */ + set( origin, direction ) { + + this.origin.copy( origin ); + this.direction.copy( direction ); + + return this; + + } + + /** + * Copies the values of the given ray to this instance. + * + * @param {Ray} ray - The ray to copy. + * @return {Ray} A reference to this ray. + */ + copy( ray ) { + + this.origin.copy( ray.origin ); + this.direction.copy( ray.direction ); + + return this; + + } + + /** + * Returns a vector that is located at a given distance along this ray. + * + * @param {number} t - The distance along the ray to retrieve a position for. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} A position on the ray. + */ + at( t, target ) { + + return target.copy( this.origin ).addScaledVector( this.direction, t ); + + } + + /** + * Adjusts the direction of the ray to point at the given vector in world space. + * + * @param {Vector3} v - The target position. + * @return {Ray} A reference to this ray. + */ + lookAt( v ) { + + this.direction.copy( v ).sub( this.origin ).normalize(); + + return this; + + } + + /** + * Shift the origin of this ray along its direction by the given distance. + * + * @param {number} t - The distance along the ray to interpolate. + * @return {Ray} A reference to this ray. + */ + recast( t ) { + + this.origin.copy( this.at( t, _vector$a ) ); + + return this; + + } + + /** + * Returns the point along this ray that is closest to the given point. + * + * @param {Vector3} point - A point in 3D space to get the closet location on the ray for. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The closest point on this ray. + */ + closestPointToPoint( point, target ) { + + target.subVectors( point, this.origin ); + + const directionDistance = target.dot( this.direction ); + + if ( directionDistance < 0 ) { + + return target.copy( this.origin ); + + } + + return target.copy( this.origin ).addScaledVector( this.direction, directionDistance ); + + } + + /** + * Returns the distance of the closest approach between this ray and the given point. + * + * @param {Vector3} point - A point in 3D space to compute the distance to. + * @return {number} The distance. + */ + distanceToPoint( point ) { + + return Math.sqrt( this.distanceSqToPoint( point ) ); + + } + + /** + * Returns the squared distance of the closest approach between this ray and the given point. + * + * @param {Vector3} point - A point in 3D space to compute the distance to. + * @return {number} The squared distance. + */ + distanceSqToPoint( point ) { + + const directionDistance = _vector$a.subVectors( point, this.origin ).dot( this.direction ); + + // point behind the ray + + if ( directionDistance < 0 ) { + + return this.origin.distanceToSquared( point ); + + } + + _vector$a.copy( this.origin ).addScaledVector( this.direction, directionDistance ); + + return _vector$a.distanceToSquared( point ); + + } + + /** + * Returns the squared distance between this ray and the given line segment. + * + * @param {Vector3} v0 - The start point of the line segment. + * @param {Vector3} v1 - The end point of the line segment. + * @param {Vector3} [optionalPointOnRay] - When provided, it receives the point on this ray that is closest to the segment. + * @param {Vector3} [optionalPointOnSegment] - When provided, it receives the point on the line segment that is closest to this ray. + * @return {number} The squared distance. + */ + distanceSqToSegment( v0, v1, optionalPointOnRay, optionalPointOnSegment ) { + + // from https://github.com/pmjoniak/GeometricTools/blob/master/GTEngine/Include/Mathematics/GteDistRaySegment.h + // It returns the min distance between the ray and the segment + // defined by v0 and v1 + // It can also set two optional targets : + // - The closest point on the ray + // - The closest point on the segment + + _segCenter.copy( v0 ).add( v1 ).multiplyScalar( 0.5 ); + _segDir.copy( v1 ).sub( v0 ).normalize(); + _diff.copy( this.origin ).sub( _segCenter ); + + const segExtent = v0.distanceTo( v1 ) * 0.5; + const a01 = - this.direction.dot( _segDir ); + const b0 = _diff.dot( this.direction ); + const b1 = - _diff.dot( _segDir ); + const c = _diff.lengthSq(); + const det = Math.abs( 1 - a01 * a01 ); + let s0, s1, sqrDist, extDet; + + if ( det > 0 ) { + + // The ray and segment are not parallel. + + s0 = a01 * b1 - b0; + s1 = a01 * b0 - b1; + extDet = segExtent * det; + + if ( s0 >= 0 ) { + + if ( s1 >= - extDet ) { + + if ( s1 <= extDet ) { + + // region 0 + // Minimum at interior points of ray and segment. + + const invDet = 1 / det; + s0 *= invDet; + s1 *= invDet; + sqrDist = s0 * ( s0 + a01 * s1 + 2 * b0 ) + s1 * ( a01 * s0 + s1 + 2 * b1 ) + c; + + } else { + + // region 1 + + s1 = segExtent; + s0 = Math.max( 0, - ( a01 * s1 + b0 ) ); + sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; + + } + + } else { + + // region 5 + + s1 = - segExtent; + s0 = Math.max( 0, - ( a01 * s1 + b0 ) ); + sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; + + } + + } else { + + if ( s1 <= - extDet ) { + + // region 4 + + s0 = Math.max( 0, - ( - a01 * segExtent + b0 ) ); + s1 = ( s0 > 0 ) ? - segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent ); + sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; + + } else if ( s1 <= extDet ) { + + // region 3 + + s0 = 0; + s1 = Math.min( Math.max( - segExtent, - b1 ), segExtent ); + sqrDist = s1 * ( s1 + 2 * b1 ) + c; + + } else { + + // region 2 + + s0 = Math.max( 0, - ( a01 * segExtent + b0 ) ); + s1 = ( s0 > 0 ) ? segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent ); + sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; + + } + + } + + } else { + + // Ray and segment are parallel. + + s1 = ( a01 > 0 ) ? - segExtent : segExtent; + s0 = Math.max( 0, - ( a01 * s1 + b0 ) ); + sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; + + } + + if ( optionalPointOnRay ) { + + optionalPointOnRay.copy( this.origin ).addScaledVector( this.direction, s0 ); + + } + + if ( optionalPointOnSegment ) { + + optionalPointOnSegment.copy( _segCenter ).addScaledVector( _segDir, s1 ); + + } + + return sqrDist; + + } + + /** + * Intersects this ray with the given sphere, returning the intersection + * point or `null` if there is no intersection. + * + * @param {Sphere} sphere - The sphere to intersect. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {?Vector3} The intersection point. + */ + intersectSphere( sphere, target ) { + + _vector$a.subVectors( sphere.center, this.origin ); + const tca = _vector$a.dot( this.direction ); + const d2 = _vector$a.dot( _vector$a ) - tca * tca; + const radius2 = sphere.radius * sphere.radius; + + if ( d2 > radius2 ) return null; + + const thc = Math.sqrt( radius2 - d2 ); + + // t0 = first intersect point - entrance on front of sphere + const t0 = tca - thc; + + // t1 = second intersect point - exit point on back of sphere + const t1 = tca + thc; + + // test to see if t1 is behind the ray - if so, return null + if ( t1 < 0 ) return null; + + // test to see if t0 is behind the ray: + // if it is, the ray is inside the sphere, so return the second exit point scaled by t1, + // in order to always return an intersect point that is in front of the ray. + if ( t0 < 0 ) return this.at( t1, target ); + + // else t0 is in front of the ray, so return the first collision point scaled by t0 + return this.at( t0, target ); + + } + + /** + * Returns `true` if this ray intersects with the given sphere. + * + * @param {Sphere} sphere - The sphere to intersect. + * @return {boolean} Whether this ray intersects with the given sphere or not. + */ + intersectsSphere( sphere ) { + + if ( sphere.radius < 0 ) return false; // handle empty spheres, see #31187 + + return this.distanceSqToPoint( sphere.center ) <= ( sphere.radius * sphere.radius ); + + } + + /** + * Computes the distance from the ray's origin to the given plane. Returns `null` if the ray + * does not intersect with the plane. + * + * @param {Plane} plane - The plane to compute the distance to. + * @return {?number} Whether this ray intersects with the given sphere or not. + */ + distanceToPlane( plane ) { + + const denominator = plane.normal.dot( this.direction ); + + if ( denominator === 0 ) { + + // line is coplanar, return origin + if ( plane.distanceToPoint( this.origin ) === 0 ) { + + return 0; + + } + + // Null is preferable to undefined since undefined means.... it is undefined + + return null; + + } + + const t = - ( this.origin.dot( plane.normal ) + plane.constant ) / denominator; + + // Return if the ray never intersects the plane + + return t >= 0 ? t : null; + + } + + /** + * Intersects this ray with the given plane, returning the intersection + * point or `null` if there is no intersection. + * + * @param {Plane} plane - The plane to intersect. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {?Vector3} The intersection point. + */ + intersectPlane( plane, target ) { + + const t = this.distanceToPlane( plane ); + + if ( t === null ) { + + return null; + + } + + return this.at( t, target ); + + } + + /** + * Returns `true` if this ray intersects with the given plane. + * + * @param {Plane} plane - The plane to intersect. + * @return {boolean} Whether this ray intersects with the given plane or not. + */ + intersectsPlane( plane ) { + + // check if the ray lies on the plane first + + const distToPoint = plane.distanceToPoint( this.origin ); + + if ( distToPoint === 0 ) { + + return true; + + } + + const denominator = plane.normal.dot( this.direction ); + + if ( denominator * distToPoint < 0 ) { + + return true; + + } + + // ray origin is behind the plane (and is pointing behind it) + + return false; + + } + + /** + * Intersects this ray with the given bounding box, returning the intersection + * point or `null` if there is no intersection. + * + * @param {Box3} box - The box to intersect. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {?Vector3} The intersection point. + */ + intersectBox( box, target ) { + + let tmin, tmax, tymin, tymax, tzmin, tzmax; + + const invdirx = 1 / this.direction.x, + invdiry = 1 / this.direction.y, + invdirz = 1 / this.direction.z; + + const origin = this.origin; + + if ( invdirx >= 0 ) { + + tmin = ( box.min.x - origin.x ) * invdirx; + tmax = ( box.max.x - origin.x ) * invdirx; + + } else { + + tmin = ( box.max.x - origin.x ) * invdirx; + tmax = ( box.min.x - origin.x ) * invdirx; + + } + + if ( invdiry >= 0 ) { + + tymin = ( box.min.y - origin.y ) * invdiry; + tymax = ( box.max.y - origin.y ) * invdiry; + + } else { + + tymin = ( box.max.y - origin.y ) * invdiry; + tymax = ( box.min.y - origin.y ) * invdiry; + + } + + if ( ( tmin > tymax ) || ( tymin > tmax ) ) return null; + + if ( tymin > tmin || isNaN( tmin ) ) tmin = tymin; + + if ( tymax < tmax || isNaN( tmax ) ) tmax = tymax; + + if ( invdirz >= 0 ) { + + tzmin = ( box.min.z - origin.z ) * invdirz; + tzmax = ( box.max.z - origin.z ) * invdirz; + + } else { + + tzmin = ( box.max.z - origin.z ) * invdirz; + tzmax = ( box.min.z - origin.z ) * invdirz; + + } + + if ( ( tmin > tzmax ) || ( tzmin > tmax ) ) return null; + + if ( tzmin > tmin || tmin !== tmin ) tmin = tzmin; + + if ( tzmax < tmax || tmax !== tmax ) tmax = tzmax; + + //return point closest to the ray (positive side) + + if ( tmax < 0 ) return null; + + return this.at( tmin >= 0 ? tmin : tmax, target ); + + } + + /** + * Returns `true` if this ray intersects with the given box. + * + * @param {Box3} box - The box to intersect. + * @return {boolean} Whether this ray intersects with the given box or not. + */ + intersectsBox( box ) { + + return this.intersectBox( box, _vector$a ) !== null; + + } + + /** + * Intersects this ray with the given triangle, returning the intersection + * point or `null` if there is no intersection. + * + * @param {Vector3} a - The first vertex of the triangle. + * @param {Vector3} b - The second vertex of the triangle. + * @param {Vector3} c - The third vertex of the triangle. + * @param {boolean} backfaceCulling - Whether to use backface culling or not. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {?Vector3} The intersection point. + */ + intersectTriangle( a, b, c, backfaceCulling, target ) { + + // Compute the offset origin, edges, and normal. + + // from https://github.com/pmjoniak/GeometricTools/blob/master/GTEngine/Include/Mathematics/GteIntrRay3Triangle3.h + + _edge1.subVectors( b, a ); + _edge2.subVectors( c, a ); + _normal$1.crossVectors( _edge1, _edge2 ); + + // Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction, + // E1 = kEdge1, E2 = kEdge2, N = Cross(E1,E2)) by + // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2)) + // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q)) + // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N) + let DdN = this.direction.dot( _normal$1 ); + let sign; + + if ( DdN > 0 ) { + + if ( backfaceCulling ) return null; + sign = 1; + + } else if ( DdN < 0 ) { + + sign = -1; + DdN = - DdN; + + } else { + + return null; + + } + + _diff.subVectors( this.origin, a ); + const DdQxE2 = sign * this.direction.dot( _edge2.crossVectors( _diff, _edge2 ) ); + + // b1 < 0, no intersection + if ( DdQxE2 < 0 ) { + + return null; + + } + + const DdE1xQ = sign * this.direction.dot( _edge1.cross( _diff ) ); + + // b2 < 0, no intersection + if ( DdE1xQ < 0 ) { + + return null; + + } + + // b1+b2 > 1, no intersection + if ( DdQxE2 + DdE1xQ > DdN ) { + + return null; + + } + + // Line intersects triangle, check if ray does. + const QdN = - sign * _diff.dot( _normal$1 ); + + // t < 0, no intersection + if ( QdN < 0 ) { + + return null; + + } + + // Ray intersects triangle. + return this.at( QdN / DdN, target ); + + } + + /** + * Transforms this ray with the given 4x4 transformation matrix. + * + * @param {Matrix4} matrix4 - The transformation matrix. + * @return {Ray} A reference to this ray. + */ + applyMatrix4( matrix4 ) { + + this.origin.applyMatrix4( matrix4 ); + this.direction.transformDirection( matrix4 ); + + return this; + + } + + /** + * Returns `true` if this ray is equal with the given one. + * + * @param {Ray} ray - The ray to test for equality. + * @return {boolean} Whether this ray is equal with the given one. + */ + equals( ray ) { + + return ray.origin.equals( this.origin ) && ray.direction.equals( this.direction ); + + } + + /** + * Returns a new ray with copied values from this instance. + * + * @return {Ray} A clone of this instance. + */ + clone() { + + return new this.constructor().copy( this ); + + } + +} + +/** + * Represents a 4x4 matrix. + * + * The most common use of a 4x4 matrix in 3D computer graphics is as a transformation matrix. + * For an introduction to transformation matrices as used in WebGL, check out [this tutorial]{@link https://www.opengl-tutorial.org/beginners-tutorials/tutorial-3-matrices} + * + * This allows a 3D vector representing a point in 3D space to undergo + * transformations such as translation, rotation, shear, scale, reflection, + * orthogonal or perspective projection and so on, by being multiplied by the + * matrix. This is known as `applying` the matrix to the vector. + * + * A Note on Row-Major and Column-Major Ordering: + * + * The constructor and {@link Matrix3#set} method take arguments in + * [row-major]{@link https://en.wikipedia.org/wiki/Row-_and_column-major_order#Column-major_order} + * order, while internally they are stored in the {@link Matrix3#elements} array in column-major order. + * This means that calling: + * ```js + * const m = new THREE.Matrix4(); + * m.set( 11, 12, 13, 14, + * 21, 22, 23, 24, + * 31, 32, 33, 34, + * 41, 42, 43, 44 ); + * ``` + * will result in the elements array containing: + * ```js + * m.elements = [ 11, 21, 31, 41, + * 12, 22, 32, 42, + * 13, 23, 33, 43, + * 14, 24, 34, 44 ]; + * ``` + * and internally all calculations are performed using column-major ordering. + * However, as the actual ordering makes no difference mathematically and + * most people are used to thinking about matrices in row-major order, the + * three.js documentation shows matrices in row-major order. Just bear in + * mind that if you are reading the source code, you'll have to take the + * transpose of any matrices outlined here to make sense of the calculations. + */ +class Matrix4 { + + /** + * Constructs a new 4x4 matrix. The arguments are supposed to be + * in row-major order. If no arguments are provided, the constructor + * initializes the matrix as an identity matrix. + * + * @param {number} [n11] - 1-1 matrix element. + * @param {number} [n12] - 1-2 matrix element. + * @param {number} [n13] - 1-3 matrix element. + * @param {number} [n14] - 1-4 matrix element. + * @param {number} [n21] - 2-1 matrix element. + * @param {number} [n22] - 2-2 matrix element. + * @param {number} [n23] - 2-3 matrix element. + * @param {number} [n24] - 2-4 matrix element. + * @param {number} [n31] - 3-1 matrix element. + * @param {number} [n32] - 3-2 matrix element. + * @param {number} [n33] - 3-3 matrix element. + * @param {number} [n34] - 3-4 matrix element. + * @param {number} [n41] - 4-1 matrix element. + * @param {number} [n42] - 4-2 matrix element. + * @param {number} [n43] - 4-3 matrix element. + * @param {number} [n44] - 4-4 matrix element. + */ + constructor( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ) { + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + Matrix4.prototype.isMatrix4 = true; + + /** + * A column-major list of matrix values. + * + * @type {Array} + */ + this.elements = [ + + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + + ]; + + if ( n11 !== undefined ) { + + this.set( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ); + + } + + } + + /** + * Sets the elements of the matrix.The arguments are supposed to be + * in row-major order. + * + * @param {number} [n11] - 1-1 matrix element. + * @param {number} [n12] - 1-2 matrix element. + * @param {number} [n13] - 1-3 matrix element. + * @param {number} [n14] - 1-4 matrix element. + * @param {number} [n21] - 2-1 matrix element. + * @param {number} [n22] - 2-2 matrix element. + * @param {number} [n23] - 2-3 matrix element. + * @param {number} [n24] - 2-4 matrix element. + * @param {number} [n31] - 3-1 matrix element. + * @param {number} [n32] - 3-2 matrix element. + * @param {number} [n33] - 3-3 matrix element. + * @param {number} [n34] - 3-4 matrix element. + * @param {number} [n41] - 4-1 matrix element. + * @param {number} [n42] - 4-2 matrix element. + * @param {number} [n43] - 4-3 matrix element. + * @param {number} [n44] - 4-4 matrix element. + * @return {Matrix4} A reference to this matrix. + */ + set( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ) { + + const te = this.elements; + + te[ 0 ] = n11; te[ 4 ] = n12; te[ 8 ] = n13; te[ 12 ] = n14; + te[ 1 ] = n21; te[ 5 ] = n22; te[ 9 ] = n23; te[ 13 ] = n24; + te[ 2 ] = n31; te[ 6 ] = n32; te[ 10 ] = n33; te[ 14 ] = n34; + te[ 3 ] = n41; te[ 7 ] = n42; te[ 11 ] = n43; te[ 15 ] = n44; + + return this; + + } + + /** + * Sets this matrix to the 4x4 identity matrix. + * + * @return {Matrix4} A reference to this matrix. + */ + identity() { + + this.set( + + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + + ); + + return this; + + } + + /** + * Returns a matrix with copied values from this instance. + * + * @return {Matrix4} A clone of this instance. + */ + clone() { + + return new Matrix4().fromArray( this.elements ); + + } + + /** + * Copies the values of the given matrix to this instance. + * + * @param {Matrix4} m - The matrix to copy. + * @return {Matrix4} A reference to this matrix. + */ + copy( m ) { + + const te = this.elements; + const me = m.elements; + + te[ 0 ] = me[ 0 ]; te[ 1 ] = me[ 1 ]; te[ 2 ] = me[ 2 ]; te[ 3 ] = me[ 3 ]; + te[ 4 ] = me[ 4 ]; te[ 5 ] = me[ 5 ]; te[ 6 ] = me[ 6 ]; te[ 7 ] = me[ 7 ]; + te[ 8 ] = me[ 8 ]; te[ 9 ] = me[ 9 ]; te[ 10 ] = me[ 10 ]; te[ 11 ] = me[ 11 ]; + te[ 12 ] = me[ 12 ]; te[ 13 ] = me[ 13 ]; te[ 14 ] = me[ 14 ]; te[ 15 ] = me[ 15 ]; + + return this; + + } + + /** + * Copies the translation component of the given matrix + * into this matrix's translation component. + * + * @param {Matrix4} m - The matrix to copy the translation component. + * @return {Matrix4} A reference to this matrix. + */ + copyPosition( m ) { + + const te = this.elements, me = m.elements; + + te[ 12 ] = me[ 12 ]; + te[ 13 ] = me[ 13 ]; + te[ 14 ] = me[ 14 ]; + + return this; + + } + + /** + * Set the upper 3x3 elements of this matrix to the values of given 3x3 matrix. + * + * @param {Matrix3} m - The 3x3 matrix. + * @return {Matrix4} A reference to this matrix. + */ + setFromMatrix3( m ) { + + const me = m.elements; + + this.set( + + me[ 0 ], me[ 3 ], me[ 6 ], 0, + me[ 1 ], me[ 4 ], me[ 7 ], 0, + me[ 2 ], me[ 5 ], me[ 8 ], 0, + 0, 0, 0, 1 + + ); + + return this; + + } + + /** + * Extracts the basis of this matrix into the three axis vectors provided. + * + * @param {Vector3} xAxis - The basis's x axis. + * @param {Vector3} yAxis - The basis's y axis. + * @param {Vector3} zAxis - The basis's z axis. + * @return {Matrix4} A reference to this matrix. + */ + extractBasis( xAxis, yAxis, zAxis ) { + + xAxis.setFromMatrixColumn( this, 0 ); + yAxis.setFromMatrixColumn( this, 1 ); + zAxis.setFromMatrixColumn( this, 2 ); + + return this; + + } + + /** + * Sets the given basis vectors to this matrix. + * + * @param {Vector3} xAxis - The basis's x axis. + * @param {Vector3} yAxis - The basis's y axis. + * @param {Vector3} zAxis - The basis's z axis. + * @return {Matrix4} A reference to this matrix. + */ + makeBasis( xAxis, yAxis, zAxis ) { + + this.set( + xAxis.x, yAxis.x, zAxis.x, 0, + xAxis.y, yAxis.y, zAxis.y, 0, + xAxis.z, yAxis.z, zAxis.z, 0, + 0, 0, 0, 1 + ); + + return this; + + } + + /** + * Extracts the rotation component of the given matrix + * into this matrix's rotation component. + * + * Note: This method does not support reflection matrices. + * + * @param {Matrix4} m - The matrix. + * @return {Matrix4} A reference to this matrix. + */ + extractRotation( m ) { + + const te = this.elements; + const me = m.elements; + + const scaleX = 1 / _v1$5.setFromMatrixColumn( m, 0 ).length(); + const scaleY = 1 / _v1$5.setFromMatrixColumn( m, 1 ).length(); + const scaleZ = 1 / _v1$5.setFromMatrixColumn( m, 2 ).length(); + + te[ 0 ] = me[ 0 ] * scaleX; + te[ 1 ] = me[ 1 ] * scaleX; + te[ 2 ] = me[ 2 ] * scaleX; + te[ 3 ] = 0; + + te[ 4 ] = me[ 4 ] * scaleY; + te[ 5 ] = me[ 5 ] * scaleY; + te[ 6 ] = me[ 6 ] * scaleY; + te[ 7 ] = 0; + + te[ 8 ] = me[ 8 ] * scaleZ; + te[ 9 ] = me[ 9 ] * scaleZ; + te[ 10 ] = me[ 10 ] * scaleZ; + te[ 11 ] = 0; + + te[ 12 ] = 0; + te[ 13 ] = 0; + te[ 14 ] = 0; + te[ 15 ] = 1; + + return this; + + } + + /** + * Sets the rotation component (the upper left 3x3 matrix) of this matrix to + * the rotation specified by the given Euler angles. The rest of + * the matrix is set to the identity. Depending on the {@link Euler#order}, + * there are six possible outcomes. See [this page]{@link https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix} + * for a complete list. + * + * @param {Euler} euler - The Euler angles. + * @return {Matrix4} A reference to this matrix. + */ + makeRotationFromEuler( euler ) { + + const te = this.elements; + + const x = euler.x, y = euler.y, z = euler.z; + const a = Math.cos( x ), b = Math.sin( x ); + const c = Math.cos( y ), d = Math.sin( y ); + const e = Math.cos( z ), f = Math.sin( z ); + + if ( euler.order === 'XYZ' ) { + + const ae = a * e, af = a * f, be = b * e, bf = b * f; + + te[ 0 ] = c * e; + te[ 4 ] = - c * f; + te[ 8 ] = d; + + te[ 1 ] = af + be * d; + te[ 5 ] = ae - bf * d; + te[ 9 ] = - b * c; + + te[ 2 ] = bf - ae * d; + te[ 6 ] = be + af * d; + te[ 10 ] = a * c; + + } else if ( euler.order === 'YXZ' ) { + + const ce = c * e, cf = c * f, de = d * e, df = d * f; + + te[ 0 ] = ce + df * b; + te[ 4 ] = de * b - cf; + te[ 8 ] = a * d; + + te[ 1 ] = a * f; + te[ 5 ] = a * e; + te[ 9 ] = - b; + + te[ 2 ] = cf * b - de; + te[ 6 ] = df + ce * b; + te[ 10 ] = a * c; + + } else if ( euler.order === 'ZXY' ) { + + const ce = c * e, cf = c * f, de = d * e, df = d * f; + + te[ 0 ] = ce - df * b; + te[ 4 ] = - a * f; + te[ 8 ] = de + cf * b; + + te[ 1 ] = cf + de * b; + te[ 5 ] = a * e; + te[ 9 ] = df - ce * b; + + te[ 2 ] = - a * d; + te[ 6 ] = b; + te[ 10 ] = a * c; + + } else if ( euler.order === 'ZYX' ) { + + const ae = a * e, af = a * f, be = b * e, bf = b * f; + + te[ 0 ] = c * e; + te[ 4 ] = be * d - af; + te[ 8 ] = ae * d + bf; + + te[ 1 ] = c * f; + te[ 5 ] = bf * d + ae; + te[ 9 ] = af * d - be; + + te[ 2 ] = - d; + te[ 6 ] = b * c; + te[ 10 ] = a * c; + + } else if ( euler.order === 'YZX' ) { + + const ac = a * c, ad = a * d, bc = b * c, bd = b * d; + + te[ 0 ] = c * e; + te[ 4 ] = bd - ac * f; + te[ 8 ] = bc * f + ad; + + te[ 1 ] = f; + te[ 5 ] = a * e; + te[ 9 ] = - b * e; + + te[ 2 ] = - d * e; + te[ 6 ] = ad * f + bc; + te[ 10 ] = ac - bd * f; + + } else if ( euler.order === 'XZY' ) { + + const ac = a * c, ad = a * d, bc = b * c, bd = b * d; + + te[ 0 ] = c * e; + te[ 4 ] = - f; + te[ 8 ] = d * e; + + te[ 1 ] = ac * f + bd; + te[ 5 ] = a * e; + te[ 9 ] = ad * f - bc; + + te[ 2 ] = bc * f - ad; + te[ 6 ] = b * e; + te[ 10 ] = bd * f + ac; + + } + + // bottom row + te[ 3 ] = 0; + te[ 7 ] = 0; + te[ 11 ] = 0; + + // last column + te[ 12 ] = 0; + te[ 13 ] = 0; + te[ 14 ] = 0; + te[ 15 ] = 1; + + return this; + + } + + /** + * Sets the rotation component of this matrix to the rotation specified by + * the given Quaternion as outlined [here]{@link https://en.wikipedia.org/wiki/Rotation_matrix#Quaternion} + * The rest of the matrix is set to the identity. + * + * @param {Quaternion} q - The Quaternion. + * @return {Matrix4} A reference to this matrix. + */ + makeRotationFromQuaternion( q ) { + + return this.compose( _zero, q, _one ); + + } + + /** + * Sets the rotation component of the transformation matrix, looking from `eye` towards + * `target`, and oriented by the up-direction. + * + * @param {Vector3} eye - The eye vector. + * @param {Vector3} target - The target vector. + * @param {Vector3} up - The up vector. + * @return {Matrix4} A reference to this matrix. + */ + lookAt( eye, target, up ) { + + const te = this.elements; + + _z.subVectors( eye, target ); + + if ( _z.lengthSq() === 0 ) { + + // eye and target are in the same position + + _z.z = 1; + + } + + _z.normalize(); + _x.crossVectors( up, _z ); + + if ( _x.lengthSq() === 0 ) { + + // up and z are parallel + + if ( Math.abs( up.z ) === 1 ) { + + _z.x += 0.0001; + + } else { + + _z.z += 0.0001; + + } + + _z.normalize(); + _x.crossVectors( up, _z ); + + } + + _x.normalize(); + _y.crossVectors( _z, _x ); + + te[ 0 ] = _x.x; te[ 4 ] = _y.x; te[ 8 ] = _z.x; + te[ 1 ] = _x.y; te[ 5 ] = _y.y; te[ 9 ] = _z.y; + te[ 2 ] = _x.z; te[ 6 ] = _y.z; te[ 10 ] = _z.z; + + return this; + + } + + /** + * Post-multiplies this matrix by the given 4x4 matrix. + * + * @param {Matrix4} m - The matrix to multiply with. + * @return {Matrix4} A reference to this matrix. + */ + multiply( m ) { + + return this.multiplyMatrices( this, m ); + + } + + /** + * Pre-multiplies this matrix by the given 4x4 matrix. + * + * @param {Matrix4} m - The matrix to multiply with. + * @return {Matrix4} A reference to this matrix. + */ + premultiply( m ) { + + return this.multiplyMatrices( m, this ); + + } + + /** + * Multiples the given 4x4 matrices and stores the result + * in this matrix. + * + * @param {Matrix4} a - The first matrix. + * @param {Matrix4} b - The second matrix. + * @return {Matrix4} A reference to this matrix. + */ + multiplyMatrices( a, b ) { + + const ae = a.elements; + const be = b.elements; + const te = this.elements; + + const a11 = ae[ 0 ], a12 = ae[ 4 ], a13 = ae[ 8 ], a14 = ae[ 12 ]; + const a21 = ae[ 1 ], a22 = ae[ 5 ], a23 = ae[ 9 ], a24 = ae[ 13 ]; + const a31 = ae[ 2 ], a32 = ae[ 6 ], a33 = ae[ 10 ], a34 = ae[ 14 ]; + const a41 = ae[ 3 ], a42 = ae[ 7 ], a43 = ae[ 11 ], a44 = ae[ 15 ]; + + const b11 = be[ 0 ], b12 = be[ 4 ], b13 = be[ 8 ], b14 = be[ 12 ]; + const b21 = be[ 1 ], b22 = be[ 5 ], b23 = be[ 9 ], b24 = be[ 13 ]; + const b31 = be[ 2 ], b32 = be[ 6 ], b33 = be[ 10 ], b34 = be[ 14 ]; + const b41 = be[ 3 ], b42 = be[ 7 ], b43 = be[ 11 ], b44 = be[ 15 ]; + + te[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41; + te[ 4 ] = a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42; + te[ 8 ] = a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43; + te[ 12 ] = a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44; + + te[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41; + te[ 5 ] = a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42; + te[ 9 ] = a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43; + te[ 13 ] = a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44; + + te[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41; + te[ 6 ] = a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42; + te[ 10 ] = a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43; + te[ 14 ] = a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44; + + te[ 3 ] = a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41; + te[ 7 ] = a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42; + te[ 11 ] = a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43; + te[ 15 ] = a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44; + + return this; + + } + + /** + * Multiplies every component of the matrix by the given scalar. + * + * @param {number} s - The scalar. + * @return {Matrix4} A reference to this matrix. + */ + multiplyScalar( s ) { + + const te = this.elements; + + te[ 0 ] *= s; te[ 4 ] *= s; te[ 8 ] *= s; te[ 12 ] *= s; + te[ 1 ] *= s; te[ 5 ] *= s; te[ 9 ] *= s; te[ 13 ] *= s; + te[ 2 ] *= s; te[ 6 ] *= s; te[ 10 ] *= s; te[ 14 ] *= s; + te[ 3 ] *= s; te[ 7 ] *= s; te[ 11 ] *= s; te[ 15 ] *= s; + + return this; + + } + + /** + * Computes and returns the determinant of this matrix. + * + * Based on the method outlined [here]{@link http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.html}. + * + * @return {number} The determinant. + */ + determinant() { + + const te = this.elements; + + const n11 = te[ 0 ], n12 = te[ 4 ], n13 = te[ 8 ], n14 = te[ 12 ]; + const n21 = te[ 1 ], n22 = te[ 5 ], n23 = te[ 9 ], n24 = te[ 13 ]; + const n31 = te[ 2 ], n32 = te[ 6 ], n33 = te[ 10 ], n34 = te[ 14 ]; + const n41 = te[ 3 ], n42 = te[ 7 ], n43 = te[ 11 ], n44 = te[ 15 ]; + + //TODO: make this more efficient + + return ( + n41 * ( + + n14 * n23 * n32 + - n13 * n24 * n32 + - n14 * n22 * n33 + + n12 * n24 * n33 + + n13 * n22 * n34 + - n12 * n23 * n34 + ) + + n42 * ( + + n11 * n23 * n34 + - n11 * n24 * n33 + + n14 * n21 * n33 + - n13 * n21 * n34 + + n13 * n24 * n31 + - n14 * n23 * n31 + ) + + n43 * ( + + n11 * n24 * n32 + - n11 * n22 * n34 + - n14 * n21 * n32 + + n12 * n21 * n34 + + n14 * n22 * n31 + - n12 * n24 * n31 + ) + + n44 * ( + - n13 * n22 * n31 + - n11 * n23 * n32 + + n11 * n22 * n33 + + n13 * n21 * n32 + - n12 * n21 * n33 + + n12 * n23 * n31 + ) + + ); + + } + + /** + * Transposes this matrix in place. + * + * @return {Matrix4} A reference to this matrix. + */ + transpose() { + + const te = this.elements; + let tmp; + + tmp = te[ 1 ]; te[ 1 ] = te[ 4 ]; te[ 4 ] = tmp; + tmp = te[ 2 ]; te[ 2 ] = te[ 8 ]; te[ 8 ] = tmp; + tmp = te[ 6 ]; te[ 6 ] = te[ 9 ]; te[ 9 ] = tmp; + + tmp = te[ 3 ]; te[ 3 ] = te[ 12 ]; te[ 12 ] = tmp; + tmp = te[ 7 ]; te[ 7 ] = te[ 13 ]; te[ 13 ] = tmp; + tmp = te[ 11 ]; te[ 11 ] = te[ 14 ]; te[ 14 ] = tmp; + + return this; + + } + + /** + * Sets the position component for this matrix from the given vector, + * without affecting the rest of the matrix. + * + * @param {number|Vector3} x - The x component of the vector or alternatively the vector object. + * @param {number} y - The y component of the vector. + * @param {number} z - The z component of the vector. + * @return {Matrix4} A reference to this matrix. + */ + setPosition( x, y, z ) { + + const te = this.elements; + + if ( x.isVector3 ) { + + te[ 12 ] = x.x; + te[ 13 ] = x.y; + te[ 14 ] = x.z; + + } else { + + te[ 12 ] = x; + te[ 13 ] = y; + te[ 14 ] = z; + + } + + return this; + + } + + /** + * Inverts this matrix, using the [analytic method]{@link https://en.wikipedia.org/wiki/Invertible_matrix#Analytic_solution}. + * You can not invert with a determinant of zero. If you attempt this, the method produces + * a zero matrix instead. + * + * @return {Matrix4} A reference to this matrix. + */ + invert() { + + // based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm + const te = this.elements, + + n11 = te[ 0 ], n21 = te[ 1 ], n31 = te[ 2 ], n41 = te[ 3 ], + n12 = te[ 4 ], n22 = te[ 5 ], n32 = te[ 6 ], n42 = te[ 7 ], + n13 = te[ 8 ], n23 = te[ 9 ], n33 = te[ 10 ], n43 = te[ 11 ], + n14 = te[ 12 ], n24 = te[ 13 ], n34 = te[ 14 ], n44 = te[ 15 ], + + t11 = n23 * n34 * n42 - n24 * n33 * n42 + n24 * n32 * n43 - n22 * n34 * n43 - n23 * n32 * n44 + n22 * n33 * n44, + t12 = n14 * n33 * n42 - n13 * n34 * n42 - n14 * n32 * n43 + n12 * n34 * n43 + n13 * n32 * n44 - n12 * n33 * n44, + t13 = n13 * n24 * n42 - n14 * n23 * n42 + n14 * n22 * n43 - n12 * n24 * n43 - n13 * n22 * n44 + n12 * n23 * n44, + t14 = n14 * n23 * n32 - n13 * n24 * n32 - n14 * n22 * n33 + n12 * n24 * n33 + n13 * n22 * n34 - n12 * n23 * n34; + + const det = n11 * t11 + n21 * t12 + n31 * t13 + n41 * t14; + + if ( det === 0 ) return this.set( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ); + + const detInv = 1 / det; + + te[ 0 ] = t11 * detInv; + te[ 1 ] = ( n24 * n33 * n41 - n23 * n34 * n41 - n24 * n31 * n43 + n21 * n34 * n43 + n23 * n31 * n44 - n21 * n33 * n44 ) * detInv; + te[ 2 ] = ( n22 * n34 * n41 - n24 * n32 * n41 + n24 * n31 * n42 - n21 * n34 * n42 - n22 * n31 * n44 + n21 * n32 * n44 ) * detInv; + te[ 3 ] = ( n23 * n32 * n41 - n22 * n33 * n41 - n23 * n31 * n42 + n21 * n33 * n42 + n22 * n31 * n43 - n21 * n32 * n43 ) * detInv; + + te[ 4 ] = t12 * detInv; + te[ 5 ] = ( n13 * n34 * n41 - n14 * n33 * n41 + n14 * n31 * n43 - n11 * n34 * n43 - n13 * n31 * n44 + n11 * n33 * n44 ) * detInv; + te[ 6 ] = ( n14 * n32 * n41 - n12 * n34 * n41 - n14 * n31 * n42 + n11 * n34 * n42 + n12 * n31 * n44 - n11 * n32 * n44 ) * detInv; + te[ 7 ] = ( n12 * n33 * n41 - n13 * n32 * n41 + n13 * n31 * n42 - n11 * n33 * n42 - n12 * n31 * n43 + n11 * n32 * n43 ) * detInv; + + te[ 8 ] = t13 * detInv; + te[ 9 ] = ( n14 * n23 * n41 - n13 * n24 * n41 - n14 * n21 * n43 + n11 * n24 * n43 + n13 * n21 * n44 - n11 * n23 * n44 ) * detInv; + te[ 10 ] = ( n12 * n24 * n41 - n14 * n22 * n41 + n14 * n21 * n42 - n11 * n24 * n42 - n12 * n21 * n44 + n11 * n22 * n44 ) * detInv; + te[ 11 ] = ( n13 * n22 * n41 - n12 * n23 * n41 - n13 * n21 * n42 + n11 * n23 * n42 + n12 * n21 * n43 - n11 * n22 * n43 ) * detInv; + + te[ 12 ] = t14 * detInv; + te[ 13 ] = ( n13 * n24 * n31 - n14 * n23 * n31 + n14 * n21 * n33 - n11 * n24 * n33 - n13 * n21 * n34 + n11 * n23 * n34 ) * detInv; + te[ 14 ] = ( n14 * n22 * n31 - n12 * n24 * n31 - n14 * n21 * n32 + n11 * n24 * n32 + n12 * n21 * n34 - n11 * n22 * n34 ) * detInv; + te[ 15 ] = ( n12 * n23 * n31 - n13 * n22 * n31 + n13 * n21 * n32 - n11 * n23 * n32 - n12 * n21 * n33 + n11 * n22 * n33 ) * detInv; + + return this; + + } + + /** + * Multiplies the columns of this matrix by the given vector. + * + * @param {Vector3} v - The scale vector. + * @return {Matrix4} A reference to this matrix. + */ + scale( v ) { + + const te = this.elements; + const x = v.x, y = v.y, z = v.z; + + te[ 0 ] *= x; te[ 4 ] *= y; te[ 8 ] *= z; + te[ 1 ] *= x; te[ 5 ] *= y; te[ 9 ] *= z; + te[ 2 ] *= x; te[ 6 ] *= y; te[ 10 ] *= z; + te[ 3 ] *= x; te[ 7 ] *= y; te[ 11 ] *= z; + + return this; + + } + + /** + * Gets the maximum scale value of the three axes. + * + * @return {number} The maximum scale. + */ + getMaxScaleOnAxis() { + + const te = this.elements; + + const scaleXSq = te[ 0 ] * te[ 0 ] + te[ 1 ] * te[ 1 ] + te[ 2 ] * te[ 2 ]; + const scaleYSq = te[ 4 ] * te[ 4 ] + te[ 5 ] * te[ 5 ] + te[ 6 ] * te[ 6 ]; + const scaleZSq = te[ 8 ] * te[ 8 ] + te[ 9 ] * te[ 9 ] + te[ 10 ] * te[ 10 ]; + + return Math.sqrt( Math.max( scaleXSq, scaleYSq, scaleZSq ) ); + + } + + /** + * Sets this matrix as a translation transform from the given vector. + * + * @param {number|Vector3} x - The amount to translate in the X axis or alternatively a translation vector. + * @param {number} y - The amount to translate in the Y axis. + * @param {number} z - The amount to translate in the z axis. + * @return {Matrix4} A reference to this matrix. + */ + makeTranslation( x, y, z ) { + + if ( x.isVector3 ) { + + this.set( + + 1, 0, 0, x.x, + 0, 1, 0, x.y, + 0, 0, 1, x.z, + 0, 0, 0, 1 + + ); + + } else { + + this.set( + + 1, 0, 0, x, + 0, 1, 0, y, + 0, 0, 1, z, + 0, 0, 0, 1 + + ); + + } + + return this; + + } + + /** + * Sets this matrix as a rotational transformation around the X axis by + * the given angle. + * + * @param {number} theta - The rotation in radians. + * @return {Matrix4} A reference to this matrix. + */ + makeRotationX( theta ) { + + const c = Math.cos( theta ), s = Math.sin( theta ); + + this.set( + + 1, 0, 0, 0, + 0, c, - s, 0, + 0, s, c, 0, + 0, 0, 0, 1 + + ); + + return this; + + } + + /** + * Sets this matrix as a rotational transformation around the Y axis by + * the given angle. + * + * @param {number} theta - The rotation in radians. + * @return {Matrix4} A reference to this matrix. + */ + makeRotationY( theta ) { + + const c = Math.cos( theta ), s = Math.sin( theta ); + + this.set( + + c, 0, s, 0, + 0, 1, 0, 0, + - s, 0, c, 0, + 0, 0, 0, 1 + + ); + + return this; + + } + + /** + * Sets this matrix as a rotational transformation around the Z axis by + * the given angle. + * + * @param {number} theta - The rotation in radians. + * @return {Matrix4} A reference to this matrix. + */ + makeRotationZ( theta ) { + + const c = Math.cos( theta ), s = Math.sin( theta ); + + this.set( + + c, - s, 0, 0, + s, c, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + + ); + + return this; + + } + + /** + * Sets this matrix as a rotational transformation around the given axis by + * the given angle. + * + * This is a somewhat controversial but mathematically sound alternative to + * rotating via Quaternions. See the discussion [here]{@link https://www.gamedev.net/articles/programming/math-and-physics/do-we-really-need-quaternions-r1199}. + * + * @param {Vector3} axis - The normalized rotation axis. + * @param {number} angle - The rotation in radians. + * @return {Matrix4} A reference to this matrix. + */ + makeRotationAxis( axis, angle ) { + + // Based on http://www.gamedev.net/reference/articles/article1199.asp + + const c = Math.cos( angle ); + const s = Math.sin( angle ); + const t = 1 - c; + const x = axis.x, y = axis.y, z = axis.z; + const tx = t * x, ty = t * y; + + this.set( + + tx * x + c, tx * y - s * z, tx * z + s * y, 0, + tx * y + s * z, ty * y + c, ty * z - s * x, 0, + tx * z - s * y, ty * z + s * x, t * z * z + c, 0, + 0, 0, 0, 1 + + ); + + return this; + + } + + /** + * Sets this matrix as a scale transformation. + * + * @param {number} x - The amount to scale in the X axis. + * @param {number} y - The amount to scale in the Y axis. + * @param {number} z - The amount to scale in the Z axis. + * @return {Matrix4} A reference to this matrix. + */ + makeScale( x, y, z ) { + + this.set( + + x, 0, 0, 0, + 0, y, 0, 0, + 0, 0, z, 0, + 0, 0, 0, 1 + + ); + + return this; + + } + + /** + * Sets this matrix as a shear transformation. + * + * @param {number} xy - The amount to shear X by Y. + * @param {number} xz - The amount to shear X by Z. + * @param {number} yx - The amount to shear Y by X. + * @param {number} yz - The amount to shear Y by Z. + * @param {number} zx - The amount to shear Z by X. + * @param {number} zy - The amount to shear Z by Y. + * @return {Matrix4} A reference to this matrix. + */ + makeShear( xy, xz, yx, yz, zx, zy ) { + + this.set( + + 1, yx, zx, 0, + xy, 1, zy, 0, + xz, yz, 1, 0, + 0, 0, 0, 1 + + ); + + return this; + + } + + /** + * Sets this matrix to the transformation composed of the given position, + * rotation (Quaternion) and scale. + * + * @param {Vector3} position - The position vector. + * @param {Quaternion} quaternion - The rotation as a Quaternion. + * @param {Vector3} scale - The scale vector. + * @return {Matrix4} A reference to this matrix. + */ + compose( position, quaternion, scale ) { + + const te = this.elements; + + const x = quaternion._x, y = quaternion._y, z = quaternion._z, w = quaternion._w; + const x2 = x + x, y2 = y + y, z2 = z + z; + const xx = x * x2, xy = x * y2, xz = x * z2; + const yy = y * y2, yz = y * z2, zz = z * z2; + const wx = w * x2, wy = w * y2, wz = w * z2; + + const sx = scale.x, sy = scale.y, sz = scale.z; + + te[ 0 ] = ( 1 - ( yy + zz ) ) * sx; + te[ 1 ] = ( xy + wz ) * sx; + te[ 2 ] = ( xz - wy ) * sx; + te[ 3 ] = 0; + + te[ 4 ] = ( xy - wz ) * sy; + te[ 5 ] = ( 1 - ( xx + zz ) ) * sy; + te[ 6 ] = ( yz + wx ) * sy; + te[ 7 ] = 0; + + te[ 8 ] = ( xz + wy ) * sz; + te[ 9 ] = ( yz - wx ) * sz; + te[ 10 ] = ( 1 - ( xx + yy ) ) * sz; + te[ 11 ] = 0; + + te[ 12 ] = position.x; + te[ 13 ] = position.y; + te[ 14 ] = position.z; + te[ 15 ] = 1; + + return this; + + } + + /** + * Decomposes this matrix into its position, rotation and scale components + * and provides the result in the given objects. + * + * Note: Not all matrices are decomposable in this way. For example, if an + * object has a non-uniformly scaled parent, then the object's world matrix + * may not be decomposable, and this method may not be appropriate. + * + * @param {Vector3} position - The position vector. + * @param {Quaternion} quaternion - The rotation as a Quaternion. + * @param {Vector3} scale - The scale vector. + * @return {Matrix4} A reference to this matrix. + */ + decompose( position, quaternion, scale ) { + + const te = this.elements; + + let sx = _v1$5.set( te[ 0 ], te[ 1 ], te[ 2 ] ).length(); + const sy = _v1$5.set( te[ 4 ], te[ 5 ], te[ 6 ] ).length(); + const sz = _v1$5.set( te[ 8 ], te[ 9 ], te[ 10 ] ).length(); + + // if determine is negative, we need to invert one scale + const det = this.determinant(); + if ( det < 0 ) sx = - sx; + + position.x = te[ 12 ]; + position.y = te[ 13 ]; + position.z = te[ 14 ]; + + // scale the rotation part + _m1$2.copy( this ); + + const invSX = 1 / sx; + const invSY = 1 / sy; + const invSZ = 1 / sz; + + _m1$2.elements[ 0 ] *= invSX; + _m1$2.elements[ 1 ] *= invSX; + _m1$2.elements[ 2 ] *= invSX; + + _m1$2.elements[ 4 ] *= invSY; + _m1$2.elements[ 5 ] *= invSY; + _m1$2.elements[ 6 ] *= invSY; + + _m1$2.elements[ 8 ] *= invSZ; + _m1$2.elements[ 9 ] *= invSZ; + _m1$2.elements[ 10 ] *= invSZ; + + quaternion.setFromRotationMatrix( _m1$2 ); + + scale.x = sx; + scale.y = sy; + scale.z = sz; + + return this; + + } + + /** + * Creates a perspective projection matrix. This is used internally by + * {@link PerspectiveCamera#updateProjectionMatrix}. + + * @param {number} left - Left boundary of the viewing frustum at the near plane. + * @param {number} right - Right boundary of the viewing frustum at the near plane. + * @param {number} top - Top boundary of the viewing frustum at the near plane. + * @param {number} bottom - Bottom boundary of the viewing frustum at the near plane. + * @param {number} near - The distance from the camera to the near plane. + * @param {number} far - The distance from the camera to the far plane. + * @param {(WebGLCoordinateSystem|WebGPUCoordinateSystem)} [coordinateSystem=WebGLCoordinateSystem] - The coordinate system. + * @return {Matrix4} A reference to this matrix. + */ + makePerspective( left, right, top, bottom, near, far, coordinateSystem = WebGLCoordinateSystem ) { + + const te = this.elements; + const x = 2 * near / ( right - left ); + const y = 2 * near / ( top - bottom ); + + const a = ( right + left ) / ( right - left ); + const b = ( top + bottom ) / ( top - bottom ); + + let c, d; + + if ( coordinateSystem === WebGLCoordinateSystem ) { + + c = - ( far + near ) / ( far - near ); + d = ( -2 * far * near ) / ( far - near ); + + } else if ( coordinateSystem === WebGPUCoordinateSystem ) { + + c = - far / ( far - near ); + d = ( - far * near ) / ( far - near ); + + } else { + + throw new Error( 'THREE.Matrix4.makePerspective(): Invalid coordinate system: ' + coordinateSystem ); + + } + + te[ 0 ] = x; te[ 4 ] = 0; te[ 8 ] = a; te[ 12 ] = 0; + te[ 1 ] = 0; te[ 5 ] = y; te[ 9 ] = b; te[ 13 ] = 0; + te[ 2 ] = 0; te[ 6 ] = 0; te[ 10 ] = c; te[ 14 ] = d; + te[ 3 ] = 0; te[ 7 ] = 0; te[ 11 ] = -1; te[ 15 ] = 0; + + return this; + + } + + /** + * Creates a orthographic projection matrix. This is used internally by + * {@link OrthographicCamera#updateProjectionMatrix}. + + * @param {number} left - Left boundary of the viewing frustum at the near plane. + * @param {number} right - Right boundary of the viewing frustum at the near plane. + * @param {number} top - Top boundary of the viewing frustum at the near plane. + * @param {number} bottom - Bottom boundary of the viewing frustum at the near plane. + * @param {number} near - The distance from the camera to the near plane. + * @param {number} far - The distance from the camera to the far plane. + * @param {(WebGLCoordinateSystem|WebGPUCoordinateSystem)} [coordinateSystem=WebGLCoordinateSystem] - The coordinate system. + * @return {Matrix4} A reference to this matrix. + */ + makeOrthographic( left, right, top, bottom, near, far, coordinateSystem = WebGLCoordinateSystem ) { + + const te = this.elements; + const w = 1.0 / ( right - left ); + const h = 1.0 / ( top - bottom ); + const p = 1.0 / ( far - near ); + + const x = ( right + left ) * w; + const y = ( top + bottom ) * h; + + let z, zInv; + + if ( coordinateSystem === WebGLCoordinateSystem ) { + + z = ( far + near ) * p; + zInv = -2 * p; + + } else if ( coordinateSystem === WebGPUCoordinateSystem ) { + + z = near * p; + zInv = -1 * p; + + } else { + + throw new Error( 'THREE.Matrix4.makeOrthographic(): Invalid coordinate system: ' + coordinateSystem ); + + } + + te[ 0 ] = 2 * w; te[ 4 ] = 0; te[ 8 ] = 0; te[ 12 ] = - x; + te[ 1 ] = 0; te[ 5 ] = 2 * h; te[ 9 ] = 0; te[ 13 ] = - y; + te[ 2 ] = 0; te[ 6 ] = 0; te[ 10 ] = zInv; te[ 14 ] = - z; + te[ 3 ] = 0; te[ 7 ] = 0; te[ 11 ] = 0; te[ 15 ] = 1; + + return this; + + } + + /** + * Returns `true` if this matrix is equal with the given one. + * + * @param {Matrix4} matrix - The matrix to test for equality. + * @return {boolean} Whether this matrix is equal with the given one. + */ + equals( matrix ) { + + const te = this.elements; + const me = matrix.elements; + + for ( let i = 0; i < 16; i ++ ) { + + if ( te[ i ] !== me[ i ] ) return false; + + } + + return true; + + } + + /** + * Sets the elements of the matrix from the given array. + * + * @param {Array} array - The matrix elements in column-major order. + * @param {number} [offset=0] - Index of the first element in the array. + * @return {Matrix4} A reference to this matrix. + */ + fromArray( array, offset = 0 ) { + + for ( let i = 0; i < 16; i ++ ) { + + this.elements[ i ] = array[ i + offset ]; + + } + + return this; + + } + + /** + * Writes the elements of this matrix to the given array. If no array is provided, + * the method returns a new instance. + * + * @param {Array} [array=[]] - The target array holding the matrix elements in column-major order. + * @param {number} [offset=0] - Index of the first element in the array. + * @return {Array} The matrix elements in column-major order. + */ + toArray( array = [], offset = 0 ) { + + const te = this.elements; + + array[ offset ] = te[ 0 ]; + array[ offset + 1 ] = te[ 1 ]; + array[ offset + 2 ] = te[ 2 ]; + array[ offset + 3 ] = te[ 3 ]; + + array[ offset + 4 ] = te[ 4 ]; + array[ offset + 5 ] = te[ 5 ]; + array[ offset + 6 ] = te[ 6 ]; + array[ offset + 7 ] = te[ 7 ]; + + array[ offset + 8 ] = te[ 8 ]; + array[ offset + 9 ] = te[ 9 ]; + array[ offset + 10 ] = te[ 10 ]; + array[ offset + 11 ] = te[ 11 ]; + + array[ offset + 12 ] = te[ 12 ]; + array[ offset + 13 ] = te[ 13 ]; + array[ offset + 14 ] = te[ 14 ]; + array[ offset + 15 ] = te[ 15 ]; + + return array; + + } + +} + +const _v1$5 = /*@__PURE__*/ new Vector3(); +const _m1$2 = /*@__PURE__*/ new Matrix4(); +const _zero = /*@__PURE__*/ new Vector3( 0, 0, 0 ); +const _one = /*@__PURE__*/ new Vector3( 1, 1, 1 ); +const _x = /*@__PURE__*/ new Vector3(); +const _y = /*@__PURE__*/ new Vector3(); +const _z = /*@__PURE__*/ new Vector3(); + +const _matrix$2 = /*@__PURE__*/ new Matrix4(); +const _quaternion$3 = /*@__PURE__*/ new Quaternion(); + +/** + * A class representing Euler angles. + * + * Euler angles describe a rotational transformation by rotating an object on + * its various axes in specified amounts per axis, and a specified axis + * order. + * + * Iterating through an instance will yield its components (x, y, z, + * order) in the corresponding order. + * + * ```js + * const a = new THREE.Euler( 0, 1, 1.57, 'XYZ' ); + * const b = new THREE.Vector3( 1, 0, 1 ); + * b.applyEuler(a); + * ``` + */ +class Euler { + + /** + * Constructs a new euler instance. + * + * @param {number} [x=0] - The angle of the x axis in radians. + * @param {number} [y=0] - The angle of the y axis in radians. + * @param {number} [z=0] - The angle of the z axis in radians. + * @param {string} [order=Euler.DEFAULT_ORDER] - A string representing the order that the rotations are applied. + */ + constructor( x = 0, y = 0, z = 0, order = Euler.DEFAULT_ORDER ) { + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isEuler = true; + + this._x = x; + this._y = y; + this._z = z; + this._order = order; + + } + + /** + * The angle of the x axis in radians. + * + * @type {number} + * @default 0 + */ + get x() { + + return this._x; + + } + + set x( value ) { + + this._x = value; + this._onChangeCallback(); + + } + + /** + * The angle of the y axis in radians. + * + * @type {number} + * @default 0 + */ + get y() { + + return this._y; + + } + + set y( value ) { + + this._y = value; + this._onChangeCallback(); + + } + + /** + * The angle of the z axis in radians. + * + * @type {number} + * @default 0 + */ + get z() { + + return this._z; + + } + + set z( value ) { + + this._z = value; + this._onChangeCallback(); + + } + + /** + * A string representing the order that the rotations are applied. + * + * @type {string} + * @default 'XYZ' + */ + get order() { + + return this._order; + + } + + set order( value ) { + + this._order = value; + this._onChangeCallback(); + + } + + /** + * Sets the Euler components. + * + * @param {number} x - The angle of the x axis in radians. + * @param {number} y - The angle of the y axis in radians. + * @param {number} z - The angle of the z axis in radians. + * @param {string} [order] - A string representing the order that the rotations are applied. + * @return {Euler} A reference to this Euler instance. + */ + set( x, y, z, order = this._order ) { + + this._x = x; + this._y = y; + this._z = z; + this._order = order; + + this._onChangeCallback(); + + return this; + + } + + /** + * Returns a new Euler instance with copied values from this instance. + * + * @return {Euler} A clone of this instance. + */ + clone() { + + return new this.constructor( this._x, this._y, this._z, this._order ); + + } + + /** + * Copies the values of the given Euler instance to this instance. + * + * @param {Euler} euler - The Euler instance to copy. + * @return {Euler} A reference to this Euler instance. + */ + copy( euler ) { + + this._x = euler._x; + this._y = euler._y; + this._z = euler._z; + this._order = euler._order; + + this._onChangeCallback(); + + return this; + + } + + /** + * Sets the angles of this Euler instance from a pure rotation matrix. + * + * @param {Matrix4} m - A 4x4 matrix of which the upper 3x3 of matrix is a pure rotation matrix (i.e. unscaled). + * @param {string} [order] - A string representing the order that the rotations are applied. + * @param {boolean} [update=true] - Whether the internal `onChange` callback should be executed or not. + * @return {Euler} A reference to this Euler instance. + */ + setFromRotationMatrix( m, order = this._order, update = true ) { + + const te = m.elements; + const m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ]; + const m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ]; + const m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ]; + + switch ( order ) { + + case 'XYZ': + + this._y = Math.asin( clamp( m13, -1, 1 ) ); + + if ( Math.abs( m13 ) < 0.9999999 ) { + + this._x = Math.atan2( - m23, m33 ); + this._z = Math.atan2( - m12, m11 ); + + } else { + + this._x = Math.atan2( m32, m22 ); + this._z = 0; + + } + + break; + + case 'YXZ': + + this._x = Math.asin( - clamp( m23, -1, 1 ) ); + + if ( Math.abs( m23 ) < 0.9999999 ) { + + this._y = Math.atan2( m13, m33 ); + this._z = Math.atan2( m21, m22 ); + + } else { + + this._y = Math.atan2( - m31, m11 ); + this._z = 0; + + } + + break; + + case 'ZXY': + + this._x = Math.asin( clamp( m32, -1, 1 ) ); + + if ( Math.abs( m32 ) < 0.9999999 ) { + + this._y = Math.atan2( - m31, m33 ); + this._z = Math.atan2( - m12, m22 ); + + } else { + + this._y = 0; + this._z = Math.atan2( m21, m11 ); + + } + + break; + + case 'ZYX': + + this._y = Math.asin( - clamp( m31, -1, 1 ) ); + + if ( Math.abs( m31 ) < 0.9999999 ) { + + this._x = Math.atan2( m32, m33 ); + this._z = Math.atan2( m21, m11 ); + + } else { + + this._x = 0; + this._z = Math.atan2( - m12, m22 ); + + } + + break; + + case 'YZX': + + this._z = Math.asin( clamp( m21, -1, 1 ) ); + + if ( Math.abs( m21 ) < 0.9999999 ) { + + this._x = Math.atan2( - m23, m22 ); + this._y = Math.atan2( - m31, m11 ); + + } else { + + this._x = 0; + this._y = Math.atan2( m13, m33 ); + + } + + break; + + case 'XZY': + + this._z = Math.asin( - clamp( m12, -1, 1 ) ); + + if ( Math.abs( m12 ) < 0.9999999 ) { + + this._x = Math.atan2( m32, m22 ); + this._y = Math.atan2( m13, m11 ); + + } else { + + this._x = Math.atan2( - m23, m33 ); + this._y = 0; + + } + + break; + + default: + + console.warn( 'THREE.Euler: .setFromRotationMatrix() encountered an unknown order: ' + order ); + + } + + this._order = order; + + if ( update === true ) this._onChangeCallback(); + + return this; + + } + + /** + * Sets the angles of this Euler instance from a normalized quaternion. + * + * @param {Quaternion} q - A normalized Quaternion. + * @param {string} [order] - A string representing the order that the rotations are applied. + * @param {boolean} [update=true] - Whether the internal `onChange` callback should be executed or not. + * @return {Euler} A reference to this Euler instance. + */ + setFromQuaternion( q, order, update ) { + + _matrix$2.makeRotationFromQuaternion( q ); + + return this.setFromRotationMatrix( _matrix$2, order, update ); + + } + + /** + * Sets the angles of this Euler instance from the given vector. + * + * @param {Vector3} v - The vector. + * @param {string} [order] - A string representing the order that the rotations are applied. + * @return {Euler} A reference to this Euler instance. + */ + setFromVector3( v, order = this._order ) { + + return this.set( v.x, v.y, v.z, order ); + + } + + /** + * Resets the euler angle with a new order by creating a quaternion from this + * euler angle and then setting this euler angle with the quaternion and the + * new order. + * + * Warning: This discards revolution information. + * + * @param {string} [newOrder] - A string representing the new order that the rotations are applied. + * @return {Euler} A reference to this Euler instance. + */ + reorder( newOrder ) { + + _quaternion$3.setFromEuler( this ); + + return this.setFromQuaternion( _quaternion$3, newOrder ); + + } + + /** + * Returns `true` if this Euler instance is equal with the given one. + * + * @param {Euler} euler - The Euler instance to test for equality. + * @return {boolean} Whether this Euler instance is equal with the given one. + */ + equals( euler ) { + + return ( euler._x === this._x ) && ( euler._y === this._y ) && ( euler._z === this._z ) && ( euler._order === this._order ); + + } + + /** + * Sets this Euler instance's components to values from the given array. The first three + * entries of the array are assign to the x,y and z components. An optional fourth entry + * defines the Euler order. + * + * @param {Array} array - An array holding the Euler component values. + * @return {Euler} A reference to this Euler instance. + */ + fromArray( array ) { + + this._x = array[ 0 ]; + this._y = array[ 1 ]; + this._z = array[ 2 ]; + if ( array[ 3 ] !== undefined ) this._order = array[ 3 ]; + + this._onChangeCallback(); + + return this; + + } + + /** + * Writes the components of this Euler instance to the given array. If no array is provided, + * the method returns a new instance. + * + * @param {Array} [array=[]] - The target array holding the Euler components. + * @param {number} [offset=0] - Index of the first element in the array. + * @return {Array} The Euler components. + */ + toArray( array = [], offset = 0 ) { + + array[ offset ] = this._x; + array[ offset + 1 ] = this._y; + array[ offset + 2 ] = this._z; + array[ offset + 3 ] = this._order; + + return array; + + } + + _onChange( callback ) { + + this._onChangeCallback = callback; + + return this; + + } + + _onChangeCallback() {} + + *[ Symbol.iterator ]() { + + yield this._x; + yield this._y; + yield this._z; + yield this._order; + + } + +} + +/** + * The default Euler angle order. + * + * @static + * @type {string} + * @default 'XYZ' + */ +Euler.DEFAULT_ORDER = 'XYZ'; + +/** + * A layers object assigns an 3D object to 1 or more of 32 + * layers numbered `0` to `31` - internally the layers are stored as a + * bit mask], and by default all 3D objects are a member of layer `0`. + * + * This can be used to control visibility - an object must share a layer with + * a camera to be visible when that camera's view is + * rendered. + * + * All classes that inherit from {@link Object3D} have an `layers` property which + * is an instance of this class. + */ +class Layers { + + /** + * Constructs a new layers instance, with membership + * initially set to layer `0`. + */ + constructor() { + + /** + * A bit mask storing which of the 32 layers this layers object is currently + * a member of. + * + * @type {number} + */ + this.mask = 1 | 0; + + } + + /** + * Sets membership to the given layer, and remove membership all other layers. + * + * @param {number} layer - The layer to set. + */ + set( layer ) { + + this.mask = ( 1 << layer | 0 ) >>> 0; + + } + + /** + * Adds membership of the given layer. + * + * @param {number} layer - The layer to enable. + */ + enable( layer ) { + + this.mask |= 1 << layer | 0; + + } + + /** + * Adds membership to all layers. + */ + enableAll() { + + this.mask = 0xffffffff | 0; + + } + + /** + * Toggles the membership of the given layer. + * + * @param {number} layer - The layer to toggle. + */ + toggle( layer ) { + + this.mask ^= 1 << layer | 0; + + } + + /** + * Removes membership of the given layer. + * + * @param {number} layer - The layer to enable. + */ + disable( layer ) { + + this.mask &= ~ ( 1 << layer | 0 ); + + } + + /** + * Removes the membership from all layers. + */ + disableAll() { + + this.mask = 0; + + } + + /** + * Returns `true` if this and the given layers object have at least one + * layer in common. + * + * @param {Layers} layers - The layers to test. + * @return {boolean } Whether this and the given layers object have at least one layer in common or not. + */ + test( layers ) { + + return ( this.mask & layers.mask ) !== 0; + + } + + /** + * Returns `true` if the given layer is enabled. + * + * @param {number} layer - The layer to test. + * @return {boolean } Whether the given layer is enabled or not. + */ + isEnabled( layer ) { + + return ( this.mask & ( 1 << layer | 0 ) ) !== 0; + + } + +} + +let _object3DId = 0; + +const _v1$4 = /*@__PURE__*/ new Vector3(); +const _q1 = /*@__PURE__*/ new Quaternion(); +const _m1$1 = /*@__PURE__*/ new Matrix4(); +const _target = /*@__PURE__*/ new Vector3(); + +const _position$3 = /*@__PURE__*/ new Vector3(); +const _scale$2 = /*@__PURE__*/ new Vector3(); +const _quaternion$2 = /*@__PURE__*/ new Quaternion(); + +const _xAxis = /*@__PURE__*/ new Vector3( 1, 0, 0 ); +const _yAxis = /*@__PURE__*/ new Vector3( 0, 1, 0 ); +const _zAxis = /*@__PURE__*/ new Vector3( 0, 0, 1 ); + +/** + * Fires when the object has been added to its parent object. + * + * @event Object3D#added + * @type {Object} + */ +const _addedEvent = { type: 'added' }; + +/** + * Fires when the object has been removed from its parent object. + * + * @event Object3D#removed + * @type {Object} + */ +const _removedEvent = { type: 'removed' }; + +/** + * Fires when a new child object has been added. + * + * @event Object3D#childadded + * @type {Object} + */ +const _childaddedEvent = { type: 'childadded', child: null }; + +/** + * Fires when a child object has been removed. + * + * @event Object3D#childremoved + * @type {Object} + */ +const _childremovedEvent = { type: 'childremoved', child: null }; + +/** + * This is the base class for most objects in three.js and provides a set of + * properties and methods for manipulating objects in 3D space. + * + * @augments EventDispatcher + */ +class Object3D extends EventDispatcher { + + /** + * Constructs a new 3D object. + */ + constructor() { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isObject3D = true; + + /** + * The ID of the 3D object. + * + * @name Object3D#id + * @type {number} + * @readonly + */ + Object.defineProperty( this, 'id', { value: _object3DId ++ } ); + + /** + * The UUID of the 3D object. + * + * @type {string} + * @readonly + */ + this.uuid = generateUUID(); + + /** + * The name of the 3D object. + * + * @type {string} + */ + this.name = ''; + + /** + * The type property is used for detecting the object type + * in context of serialization/deserialization. + * + * @type {string} + * @readonly + */ + this.type = 'Object3D'; + + /** + * A reference to the parent object. + * + * @type {?Object3D} + * @default null + */ + this.parent = null; + + /** + * An array holding the child 3D objects of this instance. + * + * @type {Array} + */ + this.children = []; + + /** + * Defines the `up` direction of the 3D object which influences + * the orientation via methods like {@link Object3D#lookAt}. + * + * The default values for all 3D objects is defined by `Object3D.DEFAULT_UP`. + * + * @type {Vector3} + */ + this.up = Object3D.DEFAULT_UP.clone(); + + const position = new Vector3(); + const rotation = new Euler(); + const quaternion = new Quaternion(); + const scale = new Vector3( 1, 1, 1 ); + + function onRotationChange() { + + quaternion.setFromEuler( rotation, false ); + + } + + function onQuaternionChange() { + + rotation.setFromQuaternion( quaternion, undefined, false ); + + } + + rotation._onChange( onRotationChange ); + quaternion._onChange( onQuaternionChange ); + + Object.defineProperties( this, { + /** + * Represents the object's local position. + * + * @name Object3D#position + * @type {Vector3} + * @default (0,0,0) + */ + position: { + configurable: true, + enumerable: true, + value: position + }, + /** + * Represents the object's local rotation as Euler angles, in radians. + * + * @name Object3D#rotation + * @type {Euler} + * @default (0,0,0) + */ + rotation: { + configurable: true, + enumerable: true, + value: rotation + }, + /** + * Represents the object's local rotation as Quaternions. + * + * @name Object3D#quaternion + * @type {Quaternion} + */ + quaternion: { + configurable: true, + enumerable: true, + value: quaternion + }, + /** + * Represents the object's local scale. + * + * @name Object3D#scale + * @type {Vector3} + * @default (1,1,1) + */ + scale: { + configurable: true, + enumerable: true, + value: scale + }, + /** + * Represents the object's model-view matrix. + * + * @name Object3D#modelViewMatrix + * @type {Matrix4} + */ + modelViewMatrix: { + value: new Matrix4() + }, + /** + * Represents the object's normal matrix. + * + * @name Object3D#normalMatrix + * @type {Matrix3} + */ + normalMatrix: { + value: new Matrix3() + } + } ); + + /** + * Represents the object's transformation matrix in local space. + * + * @type {Matrix4} + */ + this.matrix = new Matrix4(); + + /** + * Represents the object's transformation matrix in world space. + * If the 3D object has no parent, then it's identical to the local transformation matrix + * + * @type {Matrix4} + */ + this.matrixWorld = new Matrix4(); + + /** + * When set to `true`, the engine automatically computes the local matrix from position, + * rotation and scale every frame. + * + * The default values for all 3D objects is defined by `Object3D.DEFAULT_MATRIX_AUTO_UPDATE`. + * + * @type {boolean} + * @default true + */ + this.matrixAutoUpdate = Object3D.DEFAULT_MATRIX_AUTO_UPDATE; + + /** + * When set to `true`, the engine automatically computes the world matrix from the current local + * matrix and the object's transformation hierarchy. + * + * The default values for all 3D objects is defined by `Object3D.DEFAULT_MATRIX_WORLD_AUTO_UPDATE`. + * + * @type {boolean} + * @default true + */ + this.matrixWorldAutoUpdate = Object3D.DEFAULT_MATRIX_WORLD_AUTO_UPDATE; // checked by the renderer + + /** + * When set to `true`, it calculates the world matrix in that frame and resets this property + * to `false`. + * + * @type {boolean} + * @default false + */ + this.matrixWorldNeedsUpdate = false; + + /** + * The layer membership of the 3D object. The 3D object is only visible if it has + * at least one layer in common with the camera in use. This property can also be + * used to filter out unwanted objects in ray-intersection tests when using {@link Raycaster}. + * + * @type {Layers} + */ + this.layers = new Layers(); + + /** + * When set to `true`, the 3D object gets rendered. + * + * @type {boolean} + * @default true + */ + this.visible = true; + + /** + * When set to `true`, the 3D object gets rendered into shadow maps. + * + * @type {boolean} + * @default false + */ + this.castShadow = false; + + /** + * When set to `true`, the 3D object is affected by shadows in the scene. + * + * @type {boolean} + * @default false + */ + this.receiveShadow = false; + + /** + * When set to `true`, the 3D object is honored by view frustum culling. + * + * @type {boolean} + * @default true + */ + this.frustumCulled = true; + + /** + * This value allows the default rendering order of scene graph objects to be + * overridden although opaque and transparent objects remain sorted independently. + * When this property is set for an instance of {@link Group},all descendants + * objects will be sorted and rendered together. Sorting is from lowest to highest + * render order. + * + * @type {number} + * @default 0 + */ + this.renderOrder = 0; + + /** + * An array holding the animation clips of the 3D object. + * + * @type {Array} + */ + this.animations = []; + + /** + * Custom depth material to be used when rendering to the depth map. Can only be used + * in context of meshes. When shadow-casting with a {@link DirectionalLight} or {@link SpotLight}, + * if you are modifying vertex positions in the vertex shader you must specify a custom depth + * material for proper shadows. + * + * Only relevant in context of {@link WebGLRenderer}. + * + * @type {(Material|undefined)} + * @default undefined + */ + this.customDepthMaterial = undefined; + + /** + * Same as {@link Object3D#customDepthMaterial}, but used with {@link PointLight}. + * + * Only relevant in context of {@link WebGLRenderer}. + * + * @type {(Material|undefined)} + * @default undefined + */ + this.customDistanceMaterial = undefined; + + /** + * An object that can be used to store custom data about the 3D object. It + * should not hold references to functions as these will not be cloned. + * + * @type {Object} + */ + this.userData = {}; + + } + + /** + * A callback that is executed immediately before a 3D object is rendered to a shadow map. + * + * @param {Renderer|WebGLRenderer} renderer - The renderer. + * @param {Object3D} object - The 3D object. + * @param {Camera} camera - The camera that is used to render the scene. + * @param {Camera} shadowCamera - The shadow camera. + * @param {BufferGeometry} geometry - The 3D object's geometry. + * @param {Material} depthMaterial - The depth material. + * @param {Object} group - The geometry group data. + */ + onBeforeShadow( /* renderer, object, camera, shadowCamera, geometry, depthMaterial, group */ ) {} + + /** + * A callback that is executed immediately after a 3D object is rendered to a shadow map. + * + * @param {Renderer|WebGLRenderer} renderer - The renderer. + * @param {Object3D} object - The 3D object. + * @param {Camera} camera - The camera that is used to render the scene. + * @param {Camera} shadowCamera - The shadow camera. + * @param {BufferGeometry} geometry - The 3D object's geometry. + * @param {Material} depthMaterial - The depth material. + * @param {Object} group - The geometry group data. + */ + onAfterShadow( /* renderer, object, camera, shadowCamera, geometry, depthMaterial, group */ ) {} + + /** + * A callback that is executed immediately before a 3D object is rendered. + * + * @param {Renderer|WebGLRenderer} renderer - The renderer. + * @param {Object3D} object - The 3D object. + * @param {Camera} camera - The camera that is used to render the scene. + * @param {BufferGeometry} geometry - The 3D object's geometry. + * @param {Material} material - The 3D object's material. + * @param {Object} group - The geometry group data. + */ + onBeforeRender( /* renderer, scene, camera, geometry, material, group */ ) {} + + /** + * A callback that is executed immediately after a 3D object is rendered. + * + * @param {Renderer|WebGLRenderer} renderer - The renderer. + * @param {Object3D} object - The 3D object. + * @param {Camera} camera - The camera that is used to render the scene. + * @param {BufferGeometry} geometry - The 3D object's geometry. + * @param {Material} material - The 3D object's material. + * @param {Object} group - The geometry group data. + */ + onAfterRender( /* renderer, scene, camera, geometry, material, group */ ) {} + + /** + * Applies the given transformation matrix to the object and updates the object's position, + * rotation and scale. + * + * @param {Matrix4} matrix - The transformation matrix. + */ + applyMatrix4( matrix ) { + + if ( this.matrixAutoUpdate ) this.updateMatrix(); + + this.matrix.premultiply( matrix ); + + this.matrix.decompose( this.position, this.quaternion, this.scale ); + + } + + /** + * Applies a rotation represented by given the quaternion to the 3D object. + * + * @param {Quaternion} q - The quaternion. + * @return {Object3D} A reference to this instance. + */ + applyQuaternion( q ) { + + this.quaternion.premultiply( q ); + + return this; + + } + + /** + * Sets the given rotation represented as an axis/angle couple to the 3D object. + * + * @param {Vector3} axis - The (normalized) axis vector. + * @param {number} angle - The angle in radians. + */ + setRotationFromAxisAngle( axis, angle ) { + + // assumes axis is normalized + + this.quaternion.setFromAxisAngle( axis, angle ); + + } + + /** + * Sets the given rotation represented as Euler angles to the 3D object. + * + * @param {Euler} euler - The Euler angles. + */ + setRotationFromEuler( euler ) { + + this.quaternion.setFromEuler( euler, true ); + + } + + /** + * Sets the given rotation represented as rotation matrix to the 3D object. + * + * @param {Matrix4} m - Although a 4x4 matrix is expected, the upper 3x3 portion must be + * a pure rotation matrix (i.e, unscaled). + */ + setRotationFromMatrix( m ) { + + // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) + + this.quaternion.setFromRotationMatrix( m ); + + } + + /** + * Sets the given rotation represented as a Quaternion to the 3D object. + * + * @param {Quaternion} q - The Quaternion + */ + setRotationFromQuaternion( q ) { + + // assumes q is normalized + + this.quaternion.copy( q ); + + } + + /** + * Rotates the 3D object along an axis in local space. + * + * @param {Vector3} axis - The (normalized) axis vector. + * @param {number} angle - The angle in radians. + * @return {Object3D} A reference to this instance. + */ + rotateOnAxis( axis, angle ) { + + // rotate object on axis in object space + // axis is assumed to be normalized + + _q1.setFromAxisAngle( axis, angle ); + + this.quaternion.multiply( _q1 ); + + return this; + + } + + /** + * Rotates the 3D object along an axis in world space. + * + * @param {Vector3} axis - The (normalized) axis vector. + * @param {number} angle - The angle in radians. + * @return {Object3D} A reference to this instance. + */ + rotateOnWorldAxis( axis, angle ) { + + // rotate object on axis in world space + // axis is assumed to be normalized + // method assumes no rotated parent + + _q1.setFromAxisAngle( axis, angle ); + + this.quaternion.premultiply( _q1 ); + + return this; + + } + + /** + * Rotates the 3D object around its X axis in local space. + * + * @param {number} angle - The angle in radians. + * @return {Object3D} A reference to this instance. + */ + rotateX( angle ) { + + return this.rotateOnAxis( _xAxis, angle ); + + } + + /** + * Rotates the 3D object around its Y axis in local space. + * + * @param {number} angle - The angle in radians. + * @return {Object3D} A reference to this instance. + */ + rotateY( angle ) { + + return this.rotateOnAxis( _yAxis, angle ); + + } + + /** + * Rotates the 3D object around its Z axis in local space. + * + * @param {number} angle - The angle in radians. + * @return {Object3D} A reference to this instance. + */ + rotateZ( angle ) { + + return this.rotateOnAxis( _zAxis, angle ); + + } + + /** + * Translate the 3D object by a distance along the given axis in local space. + * + * @param {Vector3} axis - The (normalized) axis vector. + * @param {number} distance - The distance in world units. + * @return {Object3D} A reference to this instance. + */ + translateOnAxis( axis, distance ) { + + // translate object by distance along axis in object space + // axis is assumed to be normalized + + _v1$4.copy( axis ).applyQuaternion( this.quaternion ); + + this.position.add( _v1$4.multiplyScalar( distance ) ); + + return this; + + } + + /** + * Translate the 3D object by a distance along its X-axis in local space. + * + * @param {number} distance - The distance in world units. + * @return {Object3D} A reference to this instance. + */ + translateX( distance ) { + + return this.translateOnAxis( _xAxis, distance ); + + } + + /** + * Translate the 3D object by a distance along its Y-axis in local space. + * + * @param {number} distance - The distance in world units. + * @return {Object3D} A reference to this instance. + */ + translateY( distance ) { + + return this.translateOnAxis( _yAxis, distance ); + + } + + /** + * Translate the 3D object by a distance along its Z-axis in local space. + * + * @param {number} distance - The distance in world units. + * @return {Object3D} A reference to this instance. + */ + translateZ( distance ) { + + return this.translateOnAxis( _zAxis, distance ); + + } + + /** + * Converts the given vector from this 3D object's local space to world space. + * + * @param {Vector3} vector - The vector to convert. + * @return {Vector3} The converted vector. + */ + localToWorld( vector ) { + + this.updateWorldMatrix( true, false ); + + return vector.applyMatrix4( this.matrixWorld ); + + } + + /** + * Converts the given vector from this 3D object's word space to local space. + * + * @param {Vector3} vector - The vector to convert. + * @return {Vector3} The converted vector. + */ + worldToLocal( vector ) { + + this.updateWorldMatrix( true, false ); + + return vector.applyMatrix4( _m1$1.copy( this.matrixWorld ).invert() ); + + } + + /** + * Rotates the object to face a point in world space. + * + * This method does not support objects having non-uniformly-scaled parent(s). + * + * @param {number|Vector3} x - The x coordinate in world space. Alternatively, a vector representing a position in world space + * @param {number} [y] - The y coordinate in world space. + * @param {number} [z] - The z coordinate in world space. + */ + lookAt( x, y, z ) { + + // This method does not support objects having non-uniformly-scaled parent(s) + + if ( x.isVector3 ) { + + _target.copy( x ); + + } else { + + _target.set( x, y, z ); + + } + + const parent = this.parent; + + this.updateWorldMatrix( true, false ); + + _position$3.setFromMatrixPosition( this.matrixWorld ); + + if ( this.isCamera || this.isLight ) { + + _m1$1.lookAt( _position$3, _target, this.up ); + + } else { + + _m1$1.lookAt( _target, _position$3, this.up ); + + } + + this.quaternion.setFromRotationMatrix( _m1$1 ); + + if ( parent ) { + + _m1$1.extractRotation( parent.matrixWorld ); + _q1.setFromRotationMatrix( _m1$1 ); + this.quaternion.premultiply( _q1.invert() ); + + } + + } + + /** + * Adds the given 3D object as a child to this 3D object. An arbitrary number of + * objects may be added. Any current parent on an object passed in here will be + * removed, since an object can have at most one parent. + * + * @fires Object3D#added + * @fires Object3D#childadded + * @param {Object3D} object - The 3D object to add. + * @return {Object3D} A reference to this instance. + */ + add( object ) { + + if ( arguments.length > 1 ) { + + for ( let i = 0; i < arguments.length; i ++ ) { + + this.add( arguments[ i ] ); + + } + + return this; + + } + + if ( object === this ) { + + console.error( 'THREE.Object3D.add: object can\'t be added as a child of itself.', object ); + return this; + + } + + if ( object && object.isObject3D ) { + + object.removeFromParent(); + object.parent = this; + this.children.push( object ); + + object.dispatchEvent( _addedEvent ); + + _childaddedEvent.child = object; + this.dispatchEvent( _childaddedEvent ); + _childaddedEvent.child = null; + + } else { + + console.error( 'THREE.Object3D.add: object not an instance of THREE.Object3D.', object ); + + } + + return this; + + } + + /** + * Removes the given 3D object as child from this 3D object. + * An arbitrary number of objects may be removed. + * + * @fires Object3D#removed + * @fires Object3D#childremoved + * @param {Object3D} object - The 3D object to remove. + * @return {Object3D} A reference to this instance. + */ + remove( object ) { + + if ( arguments.length > 1 ) { + + for ( let i = 0; i < arguments.length; i ++ ) { + + this.remove( arguments[ i ] ); + + } + + return this; + + } + + const index = this.children.indexOf( object ); + + if ( index !== -1 ) { + + object.parent = null; + this.children.splice( index, 1 ); + + object.dispatchEvent( _removedEvent ); + + _childremovedEvent.child = object; + this.dispatchEvent( _childremovedEvent ); + _childremovedEvent.child = null; + + } + + return this; + + } + + /** + * Removes this 3D object from its current parent. + * + * @fires Object3D#removed + * @fires Object3D#childremoved + * @return {Object3D} A reference to this instance. + */ + removeFromParent() { + + const parent = this.parent; + + if ( parent !== null ) { + + parent.remove( this ); + + } + + return this; + + } + + /** + * Removes all child objects. + * + * @fires Object3D#removed + * @fires Object3D#childremoved + * @return {Object3D} A reference to this instance. + */ + clear() { + + return this.remove( ... this.children ); + + } + + /** + * Adds the given 3D object as a child of this 3D object, while maintaining the object's world + * transform. This method does not support scene graphs having non-uniformly-scaled nodes(s). + * + * @fires Object3D#added + * @fires Object3D#childadded + * @param {Object3D} object - The 3D object to attach. + * @return {Object3D} A reference to this instance. + */ + attach( object ) { + + // adds object as a child of this, while maintaining the object's world transform + + // Note: This method does not support scene graphs having non-uniformly-scaled nodes(s) + + this.updateWorldMatrix( true, false ); + + _m1$1.copy( this.matrixWorld ).invert(); + + if ( object.parent !== null ) { + + object.parent.updateWorldMatrix( true, false ); + + _m1$1.multiply( object.parent.matrixWorld ); + + } + + object.applyMatrix4( _m1$1 ); + + object.removeFromParent(); + object.parent = this; + this.children.push( object ); + + object.updateWorldMatrix( false, true ); + + object.dispatchEvent( _addedEvent ); + + _childaddedEvent.child = object; + this.dispatchEvent( _childaddedEvent ); + _childaddedEvent.child = null; + + return this; + + } + + /** + * Searches through the 3D object and its children, starting with the 3D object + * itself, and returns the first with a matching ID. + * + * @param {number} id - The id. + * @return {Object3D|undefined} The found 3D object. Returns `undefined` if no 3D object has been found. + */ + getObjectById( id ) { + + return this.getObjectByProperty( 'id', id ); + + } + + /** + * Searches through the 3D object and its children, starting with the 3D object + * itself, and returns the first with a matching name. + * + * @param {string} name - The name. + * @return {Object3D|undefined} The found 3D object. Returns `undefined` if no 3D object has been found. + */ + getObjectByName( name ) { + + return this.getObjectByProperty( 'name', name ); + + } + + /** + * Searches through the 3D object and its children, starting with the 3D object + * itself, and returns the first with a matching property value. + * + * @param {string} name - The name of the property. + * @param {any} value - The value. + * @return {Object3D|undefined} The found 3D object. Returns `undefined` if no 3D object has been found. + */ + getObjectByProperty( name, value ) { + + if ( this[ name ] === value ) return this; + + for ( let i = 0, l = this.children.length; i < l; i ++ ) { + + const child = this.children[ i ]; + const object = child.getObjectByProperty( name, value ); + + if ( object !== undefined ) { + + return object; + + } + + } + + return undefined; + + } + + /** + * Searches through the 3D object and its children, starting with the 3D object + * itself, and returns all 3D objects with a matching property value. + * + * @param {string} name - The name of the property. + * @param {any} value - The value. + * @param {Array} result - The method stores the result in this array. + * @return {Array} The found 3D objects. + */ + getObjectsByProperty( name, value, result = [] ) { + + if ( this[ name ] === value ) result.push( this ); + + const children = this.children; + + for ( let i = 0, l = children.length; i < l; i ++ ) { + + children[ i ].getObjectsByProperty( name, value, result ); + + } + + return result; + + } + + /** + * Returns a vector representing the position of the 3D object in world space. + * + * @param {Vector3} target - The target vector the result is stored to. + * @return {Vector3} The 3D object's position in world space. + */ + getWorldPosition( target ) { + + this.updateWorldMatrix( true, false ); + + return target.setFromMatrixPosition( this.matrixWorld ); + + } + + /** + * Returns a Quaternion representing the position of the 3D object in world space. + * + * @param {Quaternion} target - The target Quaternion the result is stored to. + * @return {Quaternion} The 3D object's rotation in world space. + */ + getWorldQuaternion( target ) { + + this.updateWorldMatrix( true, false ); + + this.matrixWorld.decompose( _position$3, target, _scale$2 ); + + return target; + + } + + /** + * Returns a vector representing the scale of the 3D object in world space. + * + * @param {Vector3} target - The target vector the result is stored to. + * @return {Vector3} The 3D object's scale in world space. + */ + getWorldScale( target ) { + + this.updateWorldMatrix( true, false ); + + this.matrixWorld.decompose( _position$3, _quaternion$2, target ); + + return target; + + } + + /** + * Returns a vector representing the ("look") direction of the 3D object in world space. + * + * @param {Vector3} target - The target vector the result is stored to. + * @return {Vector3} The 3D object's direction in world space. + */ + getWorldDirection( target ) { + + this.updateWorldMatrix( true, false ); + + const e = this.matrixWorld.elements; + + return target.set( e[ 8 ], e[ 9 ], e[ 10 ] ).normalize(); + + } + + /** + * Abstract method to get intersections between a casted ray and this + * 3D object. Renderable 3D objects such as {@link Mesh}, {@link Line} or {@link Points} + * implement this method in order to use raycasting. + * + * @abstract + * @param {Raycaster} raycaster - The raycaster. + * @param {Array} intersects - An array holding the result of the method. + */ + raycast( /* raycaster, intersects */ ) {} + + /** + * Executes the callback on this 3D object and all descendants. + * + * Note: Modifying the scene graph inside the callback is discouraged. + * + * @param {Function} callback - A callback function that allows to process the current 3D object. + */ + traverse( callback ) { + + callback( this ); + + const children = this.children; + + for ( let i = 0, l = children.length; i < l; i ++ ) { + + children[ i ].traverse( callback ); + + } + + } + + /** + * Like {@link Object3D#traverse}, but the callback will only be executed for visible 3D objects. + * Descendants of invisible 3D objects are not traversed. + * + * Note: Modifying the scene graph inside the callback is discouraged. + * + * @param {Function} callback - A callback function that allows to process the current 3D object. + */ + traverseVisible( callback ) { + + if ( this.visible === false ) return; + + callback( this ); + + const children = this.children; + + for ( let i = 0, l = children.length; i < l; i ++ ) { + + children[ i ].traverseVisible( callback ); + + } + + } + + /** + * Like {@link Object3D#traverse}, but the callback will only be executed for all ancestors. + * + * Note: Modifying the scene graph inside the callback is discouraged. + * + * @param {Function} callback - A callback function that allows to process the current 3D object. + */ + traverseAncestors( callback ) { + + const parent = this.parent; + + if ( parent !== null ) { + + callback( parent ); + + parent.traverseAncestors( callback ); + + } + + } + + /** + * Updates the transformation matrix in local space by computing it from the current + * position, rotation and scale values. + */ + updateMatrix() { + + this.matrix.compose( this.position, this.quaternion, this.scale ); + + this.matrixWorldNeedsUpdate = true; + + } + + /** + * Updates the transformation matrix in world space of this 3D objects and its descendants. + * + * To ensure correct results, this method also recomputes the 3D object's transformation matrix in + * local space. The computation of the local and world matrix can be controlled with the + * {@link Object3D#matrixAutoUpdate} and {@link Object3D#matrixWorldAutoUpdate} flags which are both + * `true` by default. Set these flags to `false` if you need more control over the update matrix process. + * + * @param {boolean} [force=false] - When set to `true`, a recomputation of world matrices is forced even + * when {@link Object3D#matrixWorldAutoUpdate} is set to `false`. + */ + updateMatrixWorld( force ) { + + if ( this.matrixAutoUpdate ) this.updateMatrix(); + + if ( this.matrixWorldNeedsUpdate || force ) { + + if ( this.matrixWorldAutoUpdate === true ) { + + if ( this.parent === null ) { + + this.matrixWorld.copy( this.matrix ); + + } else { + + this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix ); + + } + + } + + this.matrixWorldNeedsUpdate = false; + + force = true; + + } + + // make sure descendants are updated if required + + const children = this.children; + + for ( let i = 0, l = children.length; i < l; i ++ ) { + + const child = children[ i ]; + + child.updateMatrixWorld( force ); + + } + + } + + /** + * An alternative version of {@link Object3D#updateMatrixWorld} with more control over the + * update of ancestor and descendant nodes. + * + * @param {boolean} [updateParents=false] Whether ancestor nodes should be updated or not. + * @param {boolean} [updateChildren=false] Whether descendant nodes should be updated or not. + */ + updateWorldMatrix( updateParents, updateChildren ) { + + const parent = this.parent; + + if ( updateParents === true && parent !== null ) { + + parent.updateWorldMatrix( true, false ); + + } + + if ( this.matrixAutoUpdate ) this.updateMatrix(); + + if ( this.matrixWorldAutoUpdate === true ) { + + if ( this.parent === null ) { + + this.matrixWorld.copy( this.matrix ); + + } else { + + this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix ); + + } + + } + + // make sure descendants are updated + + if ( updateChildren === true ) { + + const children = this.children; + + for ( let i = 0, l = children.length; i < l; i ++ ) { + + const child = children[ i ]; + + child.updateWorldMatrix( false, true ); + + } + + } + + } + + /** + * Serializes the 3D object into JSON. + * + * @param {?(Object|string)} meta - An optional value holding meta information about the serialization. + * @return {Object} A JSON object representing the serialized 3D object. + * @see {@link ObjectLoader#parse} + */ + toJSON( meta ) { + + // meta is a string when called from JSON.stringify + const isRootObject = ( meta === undefined || typeof meta === 'string' ); + + const output = {}; + + // meta is a hash used to collect geometries, materials. + // not providing it implies that this is the root object + // being serialized. + if ( isRootObject ) { + + // initialize meta obj + meta = { + geometries: {}, + materials: {}, + textures: {}, + images: {}, + shapes: {}, + skeletons: {}, + animations: {}, + nodes: {} + }; + + output.metadata = { + version: 4.7, + type: 'Object', + generator: 'Object3D.toJSON' + }; + + } + + // standard Object3D serialization + + const object = {}; + + object.uuid = this.uuid; + object.type = this.type; + + if ( this.name !== '' ) object.name = this.name; + if ( this.castShadow === true ) object.castShadow = true; + if ( this.receiveShadow === true ) object.receiveShadow = true; + if ( this.visible === false ) object.visible = false; + if ( this.frustumCulled === false ) object.frustumCulled = false; + if ( this.renderOrder !== 0 ) object.renderOrder = this.renderOrder; + if ( Object.keys( this.userData ).length > 0 ) object.userData = this.userData; + + object.layers = this.layers.mask; + object.matrix = this.matrix.toArray(); + object.up = this.up.toArray(); + + if ( this.matrixAutoUpdate === false ) object.matrixAutoUpdate = false; + + // object specific properties + + if ( this.isInstancedMesh ) { + + object.type = 'InstancedMesh'; + object.count = this.count; + object.instanceMatrix = this.instanceMatrix.toJSON(); + if ( this.instanceColor !== null ) object.instanceColor = this.instanceColor.toJSON(); + + } + + if ( this.isBatchedMesh ) { + + object.type = 'BatchedMesh'; + object.perObjectFrustumCulled = this.perObjectFrustumCulled; + object.sortObjects = this.sortObjects; + + object.drawRanges = this._drawRanges; + object.reservedRanges = this._reservedRanges; + + object.geometryInfo = this._geometryInfo.map( info => ( { + ...info, + boundingBox: info.boundingBox ? info.boundingBox.toJSON() : undefined, + boundingSphere: info.boundingSphere ? info.boundingSphere.toJSON() : undefined + } ) ); + object.instanceInfo = this._instanceInfo.map( info => ( { ...info } ) ); + + object.availableInstanceIds = this._availableInstanceIds.slice(); + object.availableGeometryIds = this._availableGeometryIds.slice(); + + object.nextIndexStart = this._nextIndexStart; + object.nextVertexStart = this._nextVertexStart; + object.geometryCount = this._geometryCount; + + object.maxInstanceCount = this._maxInstanceCount; + object.maxVertexCount = this._maxVertexCount; + object.maxIndexCount = this._maxIndexCount; + + object.geometryInitialized = this._geometryInitialized; + + object.matricesTexture = this._matricesTexture.toJSON( meta ); + + object.indirectTexture = this._indirectTexture.toJSON( meta ); + + if ( this._colorsTexture !== null ) { + + object.colorsTexture = this._colorsTexture.toJSON( meta ); + + } + + if ( this.boundingSphere !== null ) { + + object.boundingSphere = this.boundingSphere.toJSON(); + + } + + if ( this.boundingBox !== null ) { + + object.boundingBox = this.boundingBox.toJSON(); + + } + + } + + // + + function serialize( library, element ) { + + if ( library[ element.uuid ] === undefined ) { + + library[ element.uuid ] = element.toJSON( meta ); + + } + + return element.uuid; + + } + + if ( this.isScene ) { + + if ( this.background ) { + + if ( this.background.isColor ) { + + object.background = this.background.toJSON(); + + } else if ( this.background.isTexture ) { + + object.background = this.background.toJSON( meta ).uuid; + + } + + } + + if ( this.environment && this.environment.isTexture && this.environment.isRenderTargetTexture !== true ) { + + object.environment = this.environment.toJSON( meta ).uuid; + + } + + } else if ( this.isMesh || this.isLine || this.isPoints ) { + + object.geometry = serialize( meta.geometries, this.geometry ); + + const parameters = this.geometry.parameters; + + if ( parameters !== undefined && parameters.shapes !== undefined ) { + + const shapes = parameters.shapes; + + if ( Array.isArray( shapes ) ) { + + for ( let i = 0, l = shapes.length; i < l; i ++ ) { + + const shape = shapes[ i ]; + + serialize( meta.shapes, shape ); + + } + + } else { + + serialize( meta.shapes, shapes ); + + } + + } + + } + + if ( this.isSkinnedMesh ) { + + object.bindMode = this.bindMode; + object.bindMatrix = this.bindMatrix.toArray(); + + if ( this.skeleton !== undefined ) { + + serialize( meta.skeletons, this.skeleton ); + + object.skeleton = this.skeleton.uuid; + + } + + } + + if ( this.material !== undefined ) { + + if ( Array.isArray( this.material ) ) { + + const uuids = []; + + for ( let i = 0, l = this.material.length; i < l; i ++ ) { + + uuids.push( serialize( meta.materials, this.material[ i ] ) ); + + } + + object.material = uuids; + + } else { + + object.material = serialize( meta.materials, this.material ); + + } + + } + + // + + if ( this.children.length > 0 ) { + + object.children = []; + + for ( let i = 0; i < this.children.length; i ++ ) { + + object.children.push( this.children[ i ].toJSON( meta ).object ); + + } + + } + + // + + if ( this.animations.length > 0 ) { + + object.animations = []; + + for ( let i = 0; i < this.animations.length; i ++ ) { + + const animation = this.animations[ i ]; + + object.animations.push( serialize( meta.animations, animation ) ); + + } + + } + + if ( isRootObject ) { + + const geometries = extractFromCache( meta.geometries ); + const materials = extractFromCache( meta.materials ); + const textures = extractFromCache( meta.textures ); + const images = extractFromCache( meta.images ); + const shapes = extractFromCache( meta.shapes ); + const skeletons = extractFromCache( meta.skeletons ); + const animations = extractFromCache( meta.animations ); + const nodes = extractFromCache( meta.nodes ); + + if ( geometries.length > 0 ) output.geometries = geometries; + if ( materials.length > 0 ) output.materials = materials; + if ( textures.length > 0 ) output.textures = textures; + if ( images.length > 0 ) output.images = images; + if ( shapes.length > 0 ) output.shapes = shapes; + if ( skeletons.length > 0 ) output.skeletons = skeletons; + if ( animations.length > 0 ) output.animations = animations; + if ( nodes.length > 0 ) output.nodes = nodes; + + } + + output.object = object; + + return output; + + // extract data from the cache hash + // remove metadata on each item + // and return as array + function extractFromCache( cache ) { + + const values = []; + for ( const key in cache ) { + + const data = cache[ key ]; + delete data.metadata; + values.push( data ); + + } + + return values; + + } + + } + + /** + * Returns a new 3D object with copied values from this instance. + * + * @param {boolean} [recursive=true] - When set to `true`, descendants of the 3D object are also cloned. + * @return {Object3D} A clone of this instance. + */ + clone( recursive ) { + + return new this.constructor().copy( this, recursive ); + + } + + /** + * Copies the values of the given 3D object to this instance. + * + * @param {Object3D} source - The 3D object to copy. + * @param {boolean} [recursive=true] - When set to `true`, descendants of the 3D object are cloned. + * @return {Object3D} A reference to this instance. + */ + copy( source, recursive = true ) { + + this.name = source.name; + + this.up.copy( source.up ); + + this.position.copy( source.position ); + this.rotation.order = source.rotation.order; + this.quaternion.copy( source.quaternion ); + this.scale.copy( source.scale ); + + this.matrix.copy( source.matrix ); + this.matrixWorld.copy( source.matrixWorld ); + + this.matrixAutoUpdate = source.matrixAutoUpdate; + + this.matrixWorldAutoUpdate = source.matrixWorldAutoUpdate; + this.matrixWorldNeedsUpdate = source.matrixWorldNeedsUpdate; + + this.layers.mask = source.layers.mask; + this.visible = source.visible; + + this.castShadow = source.castShadow; + this.receiveShadow = source.receiveShadow; + + this.frustumCulled = source.frustumCulled; + this.renderOrder = source.renderOrder; + + this.animations = source.animations.slice(); + + this.userData = JSON.parse( JSON.stringify( source.userData ) ); + + if ( recursive === true ) { + + for ( let i = 0; i < source.children.length; i ++ ) { + + const child = source.children[ i ]; + this.add( child.clone() ); + + } + + } + + return this; + + } + +} + +/** + * The default up direction for objects, also used as the default + * position for {@link DirectionalLight} and {@link HemisphereLight}. + * + * @static + * @type {Vector3} + * @default (0,1,0) + */ +Object3D.DEFAULT_UP = /*@__PURE__*/ new Vector3( 0, 1, 0 ); + +/** + * The default setting for {@link Object3D#matrixAutoUpdate} for + * newly created 3D objects. + * + * @static + * @type {boolean} + * @default true + */ +Object3D.DEFAULT_MATRIX_AUTO_UPDATE = true; + +/** + * The default setting for {@link Object3D#matrixWorldAutoUpdate} for + * newly created 3D objects. + * + * @static + * @type {boolean} + * @default true + */ +Object3D.DEFAULT_MATRIX_WORLD_AUTO_UPDATE = true; + +const _v0$1 = /*@__PURE__*/ new Vector3(); +const _v1$3 = /*@__PURE__*/ new Vector3(); +const _v2$2 = /*@__PURE__*/ new Vector3(); +const _v3$2 = /*@__PURE__*/ new Vector3(); + +const _vab = /*@__PURE__*/ new Vector3(); +const _vac = /*@__PURE__*/ new Vector3(); +const _vbc = /*@__PURE__*/ new Vector3(); +const _vap = /*@__PURE__*/ new Vector3(); +const _vbp = /*@__PURE__*/ new Vector3(); +const _vcp = /*@__PURE__*/ new Vector3(); + +const _v40 = /*@__PURE__*/ new Vector4(); +const _v41 = /*@__PURE__*/ new Vector4(); +const _v42 = /*@__PURE__*/ new Vector4(); + +/** + * A geometric triangle as defined by three vectors representing its three corners. + */ +class Triangle { + + /** + * Constructs a new triangle. + * + * @param {Vector3} [a=(0,0,0)] - The first corner of the triangle. + * @param {Vector3} [b=(0,0,0)] - The second corner of the triangle. + * @param {Vector3} [c=(0,0,0)] - The third corner of the triangle. + */ + constructor( a = new Vector3(), b = new Vector3(), c = new Vector3() ) { + + /** + * The first corner of the triangle. + * + * @type {Vector3} + */ + this.a = a; + + /** + * The second corner of the triangle. + * + * @type {Vector3} + */ + this.b = b; + + /** + * The third corner of the triangle. + * + * @type {Vector3} + */ + this.c = c; + + } + + /** + * Computes the normal vector of a triangle. + * + * @param {Vector3} a - The first corner of the triangle. + * @param {Vector3} b - The second corner of the triangle. + * @param {Vector3} c - The third corner of the triangle. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The triangle's normal. + */ + static getNormal( a, b, c, target ) { + + target.subVectors( c, b ); + _v0$1.subVectors( a, b ); + target.cross( _v0$1 ); + + const targetLengthSq = target.lengthSq(); + if ( targetLengthSq > 0 ) { + + return target.multiplyScalar( 1 / Math.sqrt( targetLengthSq ) ); + + } + + return target.set( 0, 0, 0 ); + + } + + /** + * Computes a barycentric coordinates from the given vector. + * Returns `null` if the triangle is degenerate. + * + * @param {Vector3} point - A point in 3D space. + * @param {Vector3} a - The first corner of the triangle. + * @param {Vector3} b - The second corner of the triangle. + * @param {Vector3} c - The third corner of the triangle. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {?Vector3} The barycentric coordinates for the given point + */ + static getBarycoord( point, a, b, c, target ) { + + // based on: http://www.blackpawn.com/texts/pointinpoly/default.html + + _v0$1.subVectors( c, a ); + _v1$3.subVectors( b, a ); + _v2$2.subVectors( point, a ); + + const dot00 = _v0$1.dot( _v0$1 ); + const dot01 = _v0$1.dot( _v1$3 ); + const dot02 = _v0$1.dot( _v2$2 ); + const dot11 = _v1$3.dot( _v1$3 ); + const dot12 = _v1$3.dot( _v2$2 ); + + const denom = ( dot00 * dot11 - dot01 * dot01 ); + + // collinear or singular triangle + if ( denom === 0 ) { + + target.set( 0, 0, 0 ); + return null; + + } + + const invDenom = 1 / denom; + const u = ( dot11 * dot02 - dot01 * dot12 ) * invDenom; + const v = ( dot00 * dot12 - dot01 * dot02 ) * invDenom; + + // barycentric coordinates must always sum to 1 + return target.set( 1 - u - v, v, u ); + + } + + /** + * Returns `true` if the given point, when projected onto the plane of the + * triangle, lies within the triangle. + * + * @param {Vector3} point - The point in 3D space to test. + * @param {Vector3} a - The first corner of the triangle. + * @param {Vector3} b - The second corner of the triangle. + * @param {Vector3} c - The third corner of the triangle. + * @return {boolean} Whether the given point, when projected onto the plane of the + * triangle, lies within the triangle or not. + */ + static containsPoint( point, a, b, c ) { + + // if the triangle is degenerate then we can't contain a point + if ( this.getBarycoord( point, a, b, c, _v3$2 ) === null ) { + + return false; + + } + + return ( _v3$2.x >= 0 ) && ( _v3$2.y >= 0 ) && ( ( _v3$2.x + _v3$2.y ) <= 1 ); + + } + + /** + * Computes the value barycentrically interpolated for the given point on the + * triangle. Returns `null` if the triangle is degenerate. + * + * @param {Vector3} point - Position of interpolated point. + * @param {Vector3} p1 - The first corner of the triangle. + * @param {Vector3} p2 - The second corner of the triangle. + * @param {Vector3} p3 - The third corner of the triangle. + * @param {Vector3} v1 - Value to interpolate of first vertex. + * @param {Vector3} v2 - Value to interpolate of second vertex. + * @param {Vector3} v3 - Value to interpolate of third vertex. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {?Vector3} The interpolated value. + */ + static getInterpolation( point, p1, p2, p3, v1, v2, v3, target ) { + + if ( this.getBarycoord( point, p1, p2, p3, _v3$2 ) === null ) { + + target.x = 0; + target.y = 0; + if ( 'z' in target ) target.z = 0; + if ( 'w' in target ) target.w = 0; + return null; + + } + + target.setScalar( 0 ); + target.addScaledVector( v1, _v3$2.x ); + target.addScaledVector( v2, _v3$2.y ); + target.addScaledVector( v3, _v3$2.z ); + + return target; + + } + + /** + * Computes the value barycentrically interpolated for the given attribute and indices. + * + * @param {BufferAttribute} attr - The attribute to interpolate. + * @param {number} i1 - Index of first vertex. + * @param {number} i2 - Index of second vertex. + * @param {number} i3 - Index of third vertex. + * @param {Vector3} barycoord - The barycoordinate value to use to interpolate. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The interpolated attribute value. + */ + static getInterpolatedAttribute( attr, i1, i2, i3, barycoord, target ) { + + _v40.setScalar( 0 ); + _v41.setScalar( 0 ); + _v42.setScalar( 0 ); + + _v40.fromBufferAttribute( attr, i1 ); + _v41.fromBufferAttribute( attr, i2 ); + _v42.fromBufferAttribute( attr, i3 ); + + target.setScalar( 0 ); + target.addScaledVector( _v40, barycoord.x ); + target.addScaledVector( _v41, barycoord.y ); + target.addScaledVector( _v42, barycoord.z ); + + return target; + + } + + /** + * Returns `true` if the triangle is oriented towards the given direction. + * + * @param {Vector3} a - The first corner of the triangle. + * @param {Vector3} b - The second corner of the triangle. + * @param {Vector3} c - The third corner of the triangle. + * @param {Vector3} direction - The (normalized) direction vector. + * @return {boolean} Whether the triangle is oriented towards the given direction or not. + */ + static isFrontFacing( a, b, c, direction ) { + + _v0$1.subVectors( c, b ); + _v1$3.subVectors( a, b ); + + // strictly front facing + return ( _v0$1.cross( _v1$3 ).dot( direction ) < 0 ) ? true : false; + + } + + /** + * Sets the triangle's vertices by copying the given values. + * + * @param {Vector3} a - The first corner of the triangle. + * @param {Vector3} b - The second corner of the triangle. + * @param {Vector3} c - The third corner of the triangle. + * @return {Triangle} A reference to this triangle. + */ + set( a, b, c ) { + + this.a.copy( a ); + this.b.copy( b ); + this.c.copy( c ); + + return this; + + } + + /** + * Sets the triangle's vertices by copying the given array values. + * + * @param {Array} points - An array with 3D points. + * @param {number} i0 - The array index representing the first corner of the triangle. + * @param {number} i1 - The array index representing the second corner of the triangle. + * @param {number} i2 - The array index representing the third corner of the triangle. + * @return {Triangle} A reference to this triangle. + */ + setFromPointsAndIndices( points, i0, i1, i2 ) { + + this.a.copy( points[ i0 ] ); + this.b.copy( points[ i1 ] ); + this.c.copy( points[ i2 ] ); + + return this; + + } + + /** + * Sets the triangle's vertices by copying the given attribute values. + * + * @param {BufferAttribute} attribute - A buffer attribute with 3D points data. + * @param {number} i0 - The attribute index representing the first corner of the triangle. + * @param {number} i1 - The attribute index representing the second corner of the triangle. + * @param {number} i2 - The attribute index representing the third corner of the triangle. + * @return {Triangle} A reference to this triangle. + */ + setFromAttributeAndIndices( attribute, i0, i1, i2 ) { + + this.a.fromBufferAttribute( attribute, i0 ); + this.b.fromBufferAttribute( attribute, i1 ); + this.c.fromBufferAttribute( attribute, i2 ); + + return this; + + } + + /** + * Returns a new triangle with copied values from this instance. + * + * @return {Triangle} A clone of this instance. + */ + clone() { + + return new this.constructor().copy( this ); + + } + + /** + * Copies the values of the given triangle to this instance. + * + * @param {Triangle} triangle - The triangle to copy. + * @return {Triangle} A reference to this triangle. + */ + copy( triangle ) { + + this.a.copy( triangle.a ); + this.b.copy( triangle.b ); + this.c.copy( triangle.c ); + + return this; + + } + + /** + * Computes the area of the triangle. + * + * @return {number} The triangle's area. + */ + getArea() { + + _v0$1.subVectors( this.c, this.b ); + _v1$3.subVectors( this.a, this.b ); + + return _v0$1.cross( _v1$3 ).length() * 0.5; + + } + + /** + * Computes the midpoint of the triangle. + * + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The triangle's midpoint. + */ + getMidpoint( target ) { + + return target.addVectors( this.a, this.b ).add( this.c ).multiplyScalar( 1 / 3 ); + + } + + /** + * Computes the normal of the triangle. + * + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The triangle's normal. + */ + getNormal( target ) { + + return Triangle.getNormal( this.a, this.b, this.c, target ); + + } + + /** + * Computes a plane the triangle lies within. + * + * @param {Plane} target - The target vector that is used to store the method's result. + * @return {Plane} The plane the triangle lies within. + */ + getPlane( target ) { + + return target.setFromCoplanarPoints( this.a, this.b, this.c ); + + } + + /** + * Computes a barycentric coordinates from the given vector. + * Returns `null` if the triangle is degenerate. + * + * @param {Vector3} point - A point in 3D space. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {?Vector3} The barycentric coordinates for the given point + */ + getBarycoord( point, target ) { + + return Triangle.getBarycoord( point, this.a, this.b, this.c, target ); + + } + + /** + * Computes the value barycentrically interpolated for the given point on the + * triangle. Returns `null` if the triangle is degenerate. + * + * @param {Vector3} point - Position of interpolated point. + * @param {Vector3} v1 - Value to interpolate of first vertex. + * @param {Vector3} v2 - Value to interpolate of second vertex. + * @param {Vector3} v3 - Value to interpolate of third vertex. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {?Vector3} The interpolated value. + */ + getInterpolation( point, v1, v2, v3, target ) { + + return Triangle.getInterpolation( point, this.a, this.b, this.c, v1, v2, v3, target ); + + } + + /** + * Returns `true` if the given point, when projected onto the plane of the + * triangle, lies within the triangle. + * + * @param {Vector3} point - The point in 3D space to test. + * @return {boolean} Whether the given point, when projected onto the plane of the + * triangle, lies within the triangle or not. + */ + containsPoint( point ) { + + return Triangle.containsPoint( point, this.a, this.b, this.c ); + + } + + /** + * Returns `true` if the triangle is oriented towards the given direction. + * + * @param {Vector3} direction - The (normalized) direction vector. + * @return {boolean} Whether the triangle is oriented towards the given direction or not. + */ + isFrontFacing( direction ) { + + return Triangle.isFrontFacing( this.a, this.b, this.c, direction ); + + } + + /** + * Returns `true` if this triangle intersects with the given box. + * + * @param {Box3} box - The box to intersect. + * @return {boolean} Whether this triangle intersects with the given box or not. + */ + intersectsBox( box ) { + + return box.intersectsTriangle( this ); + + } + + /** + * Returns the closest point on the triangle to the given point. + * + * @param {Vector3} p - The point to compute the closest point for. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The closest point on the triangle. + */ + closestPointToPoint( p, target ) { + + const a = this.a, b = this.b, c = this.c; + let v, w; + + // algorithm thanks to Real-Time Collision Detection by Christer Ericson, + // published by Morgan Kaufmann Publishers, (c) 2005 Elsevier Inc., + // under the accompanying license; see chapter 5.1.5 for detailed explanation. + // basically, we're distinguishing which of the voronoi regions of the triangle + // the point lies in with the minimum amount of redundant computation. + + _vab.subVectors( b, a ); + _vac.subVectors( c, a ); + _vap.subVectors( p, a ); + const d1 = _vab.dot( _vap ); + const d2 = _vac.dot( _vap ); + if ( d1 <= 0 && d2 <= 0 ) { + + // vertex region of A; barycentric coords (1, 0, 0) + return target.copy( a ); + + } + + _vbp.subVectors( p, b ); + const d3 = _vab.dot( _vbp ); + const d4 = _vac.dot( _vbp ); + if ( d3 >= 0 && d4 <= d3 ) { + + // vertex region of B; barycentric coords (0, 1, 0) + return target.copy( b ); + + } + + const vc = d1 * d4 - d3 * d2; + if ( vc <= 0 && d1 >= 0 && d3 <= 0 ) { + + v = d1 / ( d1 - d3 ); + // edge region of AB; barycentric coords (1-v, v, 0) + return target.copy( a ).addScaledVector( _vab, v ); + + } + + _vcp.subVectors( p, c ); + const d5 = _vab.dot( _vcp ); + const d6 = _vac.dot( _vcp ); + if ( d6 >= 0 && d5 <= d6 ) { + + // vertex region of C; barycentric coords (0, 0, 1) + return target.copy( c ); + + } + + const vb = d5 * d2 - d1 * d6; + if ( vb <= 0 && d2 >= 0 && d6 <= 0 ) { + + w = d2 / ( d2 - d6 ); + // edge region of AC; barycentric coords (1-w, 0, w) + return target.copy( a ).addScaledVector( _vac, w ); + + } + + const va = d3 * d6 - d5 * d4; + if ( va <= 0 && ( d4 - d3 ) >= 0 && ( d5 - d6 ) >= 0 ) { + + _vbc.subVectors( c, b ); + w = ( d4 - d3 ) / ( ( d4 - d3 ) + ( d5 - d6 ) ); + // edge region of BC; barycentric coords (0, 1-w, w) + return target.copy( b ).addScaledVector( _vbc, w ); // edge region of BC + + } + + // face region + const denom = 1 / ( va + vb + vc ); + // u = va * denom + v = vb * denom; + w = vc * denom; + + return target.copy( a ).addScaledVector( _vab, v ).addScaledVector( _vac, w ); + + } + + /** + * Returns `true` if this triangle is equal with the given one. + * + * @param {Triangle} triangle - The triangle to test for equality. + * @return {boolean} Whether this triangle is equal with the given one. + */ + equals( triangle ) { + + return triangle.a.equals( this.a ) && triangle.b.equals( this.b ) && triangle.c.equals( this.c ); + + } + +} + +const _colorKeywords = { 'aliceblue': 0xF0F8FF, 'antiquewhite': 0xFAEBD7, 'aqua': 0x00FFFF, 'aquamarine': 0x7FFFD4, 'azure': 0xF0FFFF, + 'beige': 0xF5F5DC, 'bisque': 0xFFE4C4, 'black': 0x000000, 'blanchedalmond': 0xFFEBCD, 'blue': 0x0000FF, 'blueviolet': 0x8A2BE2, + 'brown': 0xA52A2A, 'burlywood': 0xDEB887, 'cadetblue': 0x5F9EA0, 'chartreuse': 0x7FFF00, 'chocolate': 0xD2691E, 'coral': 0xFF7F50, + 'cornflowerblue': 0x6495ED, 'cornsilk': 0xFFF8DC, 'crimson': 0xDC143C, 'cyan': 0x00FFFF, 'darkblue': 0x00008B, 'darkcyan': 0x008B8B, + 'darkgoldenrod': 0xB8860B, 'darkgray': 0xA9A9A9, 'darkgreen': 0x006400, 'darkgrey': 0xA9A9A9, 'darkkhaki': 0xBDB76B, 'darkmagenta': 0x8B008B, + 'darkolivegreen': 0x556B2F, 'darkorange': 0xFF8C00, 'darkorchid': 0x9932CC, 'darkred': 0x8B0000, 'darksalmon': 0xE9967A, 'darkseagreen': 0x8FBC8F, + 'darkslateblue': 0x483D8B, 'darkslategray': 0x2F4F4F, 'darkslategrey': 0x2F4F4F, 'darkturquoise': 0x00CED1, 'darkviolet': 0x9400D3, + 'deeppink': 0xFF1493, 'deepskyblue': 0x00BFFF, 'dimgray': 0x696969, 'dimgrey': 0x696969, 'dodgerblue': 0x1E90FF, 'firebrick': 0xB22222, + 'floralwhite': 0xFFFAF0, 'forestgreen': 0x228B22, 'fuchsia': 0xFF00FF, 'gainsboro': 0xDCDCDC, 'ghostwhite': 0xF8F8FF, 'gold': 0xFFD700, + 'goldenrod': 0xDAA520, 'gray': 0x808080, 'green': 0x008000, 'greenyellow': 0xADFF2F, 'grey': 0x808080, 'honeydew': 0xF0FFF0, 'hotpink': 0xFF69B4, + 'indianred': 0xCD5C5C, 'indigo': 0x4B0082, 'ivory': 0xFFFFF0, 'khaki': 0xF0E68C, 'lavender': 0xE6E6FA, 'lavenderblush': 0xFFF0F5, 'lawngreen': 0x7CFC00, + 'lemonchiffon': 0xFFFACD, 'lightblue': 0xADD8E6, 'lightcoral': 0xF08080, 'lightcyan': 0xE0FFFF, 'lightgoldenrodyellow': 0xFAFAD2, 'lightgray': 0xD3D3D3, + 'lightgreen': 0x90EE90, 'lightgrey': 0xD3D3D3, 'lightpink': 0xFFB6C1, 'lightsalmon': 0xFFA07A, 'lightseagreen': 0x20B2AA, 'lightskyblue': 0x87CEFA, + 'lightslategray': 0x778899, 'lightslategrey': 0x778899, 'lightsteelblue': 0xB0C4DE, 'lightyellow': 0xFFFFE0, 'lime': 0x00FF00, 'limegreen': 0x32CD32, + 'linen': 0xFAF0E6, 'magenta': 0xFF00FF, 'maroon': 0x800000, 'mediumaquamarine': 0x66CDAA, 'mediumblue': 0x0000CD, 'mediumorchid': 0xBA55D3, + 'mediumpurple': 0x9370DB, 'mediumseagreen': 0x3CB371, 'mediumslateblue': 0x7B68EE, 'mediumspringgreen': 0x00FA9A, 'mediumturquoise': 0x48D1CC, + 'mediumvioletred': 0xC71585, 'midnightblue': 0x191970, 'mintcream': 0xF5FFFA, 'mistyrose': 0xFFE4E1, 'moccasin': 0xFFE4B5, 'navajowhite': 0xFFDEAD, + 'navy': 0x000080, 'oldlace': 0xFDF5E6, 'olive': 0x808000, 'olivedrab': 0x6B8E23, 'orange': 0xFFA500, 'orangered': 0xFF4500, 'orchid': 0xDA70D6, + 'palegoldenrod': 0xEEE8AA, 'palegreen': 0x98FB98, 'paleturquoise': 0xAFEEEE, 'palevioletred': 0xDB7093, 'papayawhip': 0xFFEFD5, 'peachpuff': 0xFFDAB9, + 'peru': 0xCD853F, 'pink': 0xFFC0CB, 'plum': 0xDDA0DD, 'powderblue': 0xB0E0E6, 'purple': 0x800080, 'rebeccapurple': 0x663399, 'red': 0xFF0000, 'rosybrown': 0xBC8F8F, + 'royalblue': 0x4169E1, 'saddlebrown': 0x8B4513, 'salmon': 0xFA8072, 'sandybrown': 0xF4A460, 'seagreen': 0x2E8B57, 'seashell': 0xFFF5EE, + 'sienna': 0xA0522D, 'silver': 0xC0C0C0, 'skyblue': 0x87CEEB, 'slateblue': 0x6A5ACD, 'slategray': 0x708090, 'slategrey': 0x708090, 'snow': 0xFFFAFA, + 'springgreen': 0x00FF7F, 'steelblue': 0x4682B4, 'tan': 0xD2B48C, 'teal': 0x008080, 'thistle': 0xD8BFD8, 'tomato': 0xFF6347, 'turquoise': 0x40E0D0, + 'violet': 0xEE82EE, 'wheat': 0xF5DEB3, 'white': 0xFFFFFF, 'whitesmoke': 0xF5F5F5, 'yellow': 0xFFFF00, 'yellowgreen': 0x9ACD32 }; + +const _hslA = { h: 0, s: 0, l: 0 }; +const _hslB = { h: 0, s: 0, l: 0 }; + +function hue2rgb( p, q, t ) { + + if ( t < 0 ) t += 1; + if ( t > 1 ) t -= 1; + if ( t < 1 / 6 ) return p + ( q - p ) * 6 * t; + if ( t < 1 / 2 ) return q; + if ( t < 2 / 3 ) return p + ( q - p ) * 6 * ( 2 / 3 - t ); + return p; + +} + +/** + * A Color instance is represented by RGB components in the linear working + * color space, which defaults to `LinearSRGBColorSpace`. Inputs + * conventionally using `SRGBColorSpace` (such as hexadecimals and CSS + * strings) are converted to the working color space automatically. + * + * ```js + * // converted automatically from SRGBColorSpace to LinearSRGBColorSpace + * const color = new THREE.Color().setHex( 0x112233 ); + * ``` + * Source color spaces may be specified explicitly, to ensure correct conversions. + * ```js + * // assumed already LinearSRGBColorSpace; no conversion + * const color = new THREE.Color().setRGB( 0.5, 0.5, 0.5 ); + * + * // converted explicitly from SRGBColorSpace to LinearSRGBColorSpace + * const color = new THREE.Color().setRGB( 0.5, 0.5, 0.5, SRGBColorSpace ); + * ``` + * If THREE.ColorManagement is disabled, no conversions occur. For details, + * see Color management. Iterating through a Color instance will yield + * its components (r, g, b) in the corresponding order. A Color can be initialised + * in any of the following ways: + * ```js + * //empty constructor - will default white + * const color1 = new THREE.Color(); + * + * //Hexadecimal color (recommended) + * const color2 = new THREE.Color( 0xff0000 ); + * + * //RGB string + * const color3 = new THREE.Color("rgb(255, 0, 0)"); + * const color4 = new THREE.Color("rgb(100%, 0%, 0%)"); + * + * //X11 color name - all 140 color names are supported. + * //Note the lack of CamelCase in the name + * const color5 = new THREE.Color( 'skyblue' ); + * //HSL string + * const color6 = new THREE.Color("hsl(0, 100%, 50%)"); + * + * //Separate RGB values between 0 and 1 + * const color7 = new THREE.Color( 1, 0, 0 ); + * ``` + */ +class Color { + + /** + * Constructs a new color. + * + * Note that standard method of specifying color in three.js is with a hexadecimal triplet, + * and that method is used throughout the rest of the documentation. + * + * @param {(number|string|Color)} [r] - The red component of the color. If `g` and `b` are + * not provided, it can be hexadecimal triplet, a CSS-style string or another `Color` instance. + * @param {number} [g] - The green component. + * @param {number} [b] - The blue component. + */ + constructor( r, g, b ) { + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isColor = true; + + /** + * The red component. + * + * @type {number} + * @default 1 + */ + this.r = 1; + + /** + * The green component. + * + * @type {number} + * @default 1 + */ + this.g = 1; + + /** + * The blue component. + * + * @type {number} + * @default 1 + */ + this.b = 1; + + return this.set( r, g, b ); + + } + + /** + * Sets the colors's components from the given values. + * + * @param {(number|string|Color)} [r] - The red component of the color. If `g` and `b` are + * not provided, it can be hexadecimal triplet, a CSS-style string or another `Color` instance. + * @param {number} [g] - The green component. + * @param {number} [b] - The blue component. + * @return {Color} A reference to this color. + */ + set( r, g, b ) { + + if ( g === undefined && b === undefined ) { + + // r is THREE.Color, hex or string + + const value = r; + + if ( value && value.isColor ) { + + this.copy( value ); + + } else if ( typeof value === 'number' ) { + + this.setHex( value ); + + } else if ( typeof value === 'string' ) { + + this.setStyle( value ); + + } + + } else { + + this.setRGB( r, g, b ); + + } + + return this; + + } + + /** + * Sets the colors's components to the given scalar value. + * + * @param {number} scalar - The scalar value. + * @return {Color} A reference to this color. + */ + setScalar( scalar ) { + + this.r = scalar; + this.g = scalar; + this.b = scalar; + + return this; + + } + + /** + * Sets this color from a hexadecimal value. + * + * @param {number} hex - The hexadecimal value. + * @param {string} [colorSpace=SRGBColorSpace] - The color space. + * @return {Color} A reference to this color. + */ + setHex( hex, colorSpace = SRGBColorSpace ) { + + hex = Math.floor( hex ); + + this.r = ( hex >> 16 & 255 ) / 255; + this.g = ( hex >> 8 & 255 ) / 255; + this.b = ( hex & 255 ) / 255; + + ColorManagement.colorSpaceToWorking( this, colorSpace ); + + return this; + + } + + /** + * Sets this color from RGB values. + * + * @param {number} r - Red channel value between `0.0` and `1.0`. + * @param {number} g - Green channel value between `0.0` and `1.0`. + * @param {number} b - Blue channel value between `0.0` and `1.0`. + * @param {string} [colorSpace=ColorManagement.workingColorSpace] - The color space. + * @return {Color} A reference to this color. + */ + setRGB( r, g, b, colorSpace = ColorManagement.workingColorSpace ) { + + this.r = r; + this.g = g; + this.b = b; + + ColorManagement.colorSpaceToWorking( this, colorSpace ); + + return this; + + } + + /** + * Sets this color from RGB values. + * + * @param {number} h - Hue value between `0.0` and `1.0`. + * @param {number} s - Saturation value between `0.0` and `1.0`. + * @param {number} l - Lightness value between `0.0` and `1.0`. + * @param {string} [colorSpace=ColorManagement.workingColorSpace] - The color space. + * @return {Color} A reference to this color. + */ + setHSL( h, s, l, colorSpace = ColorManagement.workingColorSpace ) { + + // h,s,l ranges are in 0.0 - 1.0 + h = euclideanModulo( h, 1 ); + s = clamp( s, 0, 1 ); + l = clamp( l, 0, 1 ); + + if ( s === 0 ) { + + this.r = this.g = this.b = l; + + } else { + + const p = l <= 0.5 ? l * ( 1 + s ) : l + s - ( l * s ); + const q = ( 2 * l ) - p; + + this.r = hue2rgb( q, p, h + 1 / 3 ); + this.g = hue2rgb( q, p, h ); + this.b = hue2rgb( q, p, h - 1 / 3 ); + + } + + ColorManagement.colorSpaceToWorking( this, colorSpace ); + + return this; + + } + + /** + * Sets this color from a CSS-style string. For example, `rgb(250, 0,0)`, + * `rgb(100%, 0%, 0%)`, `hsl(0, 100%, 50%)`, `#ff0000`, `#f00`, or `red` ( or + * any [X11 color name]{@link https://en.wikipedia.org/wiki/X11_color_names#Color_name_chart} - + * all 140 color names are supported). + * + * @param {string} style - Color as a CSS-style string. + * @param {string} [colorSpace=SRGBColorSpace] - The color space. + * @return {Color} A reference to this color. + */ + setStyle( style, colorSpace = SRGBColorSpace ) { + + function handleAlpha( string ) { + + if ( string === undefined ) return; + + if ( parseFloat( string ) < 1 ) { + + console.warn( 'THREE.Color: Alpha component of ' + style + ' will be ignored.' ); + + } + + } + + + let m; + + if ( m = /^(\w+)\(([^\)]*)\)/.exec( style ) ) { + + // rgb / hsl + + let color; + const name = m[ 1 ]; + const components = m[ 2 ]; + + switch ( name ) { + + case 'rgb': + case 'rgba': + + if ( color = /^\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) { + + // rgb(255,0,0) rgba(255,0,0,0.5) + + handleAlpha( color[ 4 ] ); + + return this.setRGB( + Math.min( 255, parseInt( color[ 1 ], 10 ) ) / 255, + Math.min( 255, parseInt( color[ 2 ], 10 ) ) / 255, + Math.min( 255, parseInt( color[ 3 ], 10 ) ) / 255, + colorSpace + ); + + } + + if ( color = /^\s*(\d+)\%\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) { + + // rgb(100%,0%,0%) rgba(100%,0%,0%,0.5) + + handleAlpha( color[ 4 ] ); + + return this.setRGB( + Math.min( 100, parseInt( color[ 1 ], 10 ) ) / 100, + Math.min( 100, parseInt( color[ 2 ], 10 ) ) / 100, + Math.min( 100, parseInt( color[ 3 ], 10 ) ) / 100, + colorSpace + ); + + } + + break; + + case 'hsl': + case 'hsla': + + if ( color = /^\s*(\d*\.?\d+)\s*,\s*(\d*\.?\d+)\%\s*,\s*(\d*\.?\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) { + + // hsl(120,50%,50%) hsla(120,50%,50%,0.5) + + handleAlpha( color[ 4 ] ); + + return this.setHSL( + parseFloat( color[ 1 ] ) / 360, + parseFloat( color[ 2 ] ) / 100, + parseFloat( color[ 3 ] ) / 100, + colorSpace + ); + + } + + break; + + default: + + console.warn( 'THREE.Color: Unknown color model ' + style ); + + } + + } else if ( m = /^\#([A-Fa-f\d]+)$/.exec( style ) ) { + + // hex color + + const hex = m[ 1 ]; + const size = hex.length; + + if ( size === 3 ) { + + // #ff0 + return this.setRGB( + parseInt( hex.charAt( 0 ), 16 ) / 15, + parseInt( hex.charAt( 1 ), 16 ) / 15, + parseInt( hex.charAt( 2 ), 16 ) / 15, + colorSpace + ); + + } else if ( size === 6 ) { + + // #ff0000 + return this.setHex( parseInt( hex, 16 ), colorSpace ); + + } else { + + console.warn( 'THREE.Color: Invalid hex color ' + style ); + + } + + } else if ( style && style.length > 0 ) { + + return this.setColorName( style, colorSpace ); + + } + + return this; + + } + + /** + * Sets this color from a color name. Faster than {@link Color#setStyle} if + * you don't need the other CSS-style formats. + * + * For convenience, the list of names is exposed in `Color.NAMES` as a hash. + * ```js + * Color.NAMES.aliceblue // returns 0xF0F8FF + * ``` + * + * @param {string} style - The color name. + * @param {string} [colorSpace=SRGBColorSpace] - The color space. + * @return {Color} A reference to this color. + */ + setColorName( style, colorSpace = SRGBColorSpace ) { + + // color keywords + const hex = _colorKeywords[ style.toLowerCase() ]; + + if ( hex !== undefined ) { + + // red + this.setHex( hex, colorSpace ); + + } else { + + // unknown color + console.warn( 'THREE.Color: Unknown color ' + style ); + + } + + return this; + + } + + /** + * Returns a new color with copied values from this instance. + * + * @return {Color} A clone of this instance. + */ + clone() { + + return new this.constructor( this.r, this.g, this.b ); + + } + + /** + * Copies the values of the given color to this instance. + * + * @param {Color} color - The color to copy. + * @return {Color} A reference to this color. + */ + copy( color ) { + + this.r = color.r; + this.g = color.g; + this.b = color.b; + + return this; + + } + + /** + * Copies the given color into this color, and then converts this color from + * `SRGBColorSpace` to `LinearSRGBColorSpace`. + * + * @param {Color} color - The color to copy/convert. + * @return {Color} A reference to this color. + */ + copySRGBToLinear( color ) { + + this.r = SRGBToLinear( color.r ); + this.g = SRGBToLinear( color.g ); + this.b = SRGBToLinear( color.b ); + + return this; + + } + + /** + * Copies the given color into this color, and then converts this color from + * `LinearSRGBColorSpace` to `SRGBColorSpace`. + * + * @param {Color} color - The color to copy/convert. + * @return {Color} A reference to this color. + */ + copyLinearToSRGB( color ) { + + this.r = LinearToSRGB( color.r ); + this.g = LinearToSRGB( color.g ); + this.b = LinearToSRGB( color.b ); + + return this; + + } + + /** + * Converts this color from `SRGBColorSpace` to `LinearSRGBColorSpace`. + * + * @return {Color} A reference to this color. + */ + convertSRGBToLinear() { + + this.copySRGBToLinear( this ); + + return this; + + } + + /** + * Converts this color from `LinearSRGBColorSpace` to `SRGBColorSpace`. + * + * @return {Color} A reference to this color. + */ + convertLinearToSRGB() { + + this.copyLinearToSRGB( this ); + + return this; + + } + + /** + * Returns the hexadecimal value of this color. + * + * @param {string} [colorSpace=SRGBColorSpace] - The color space. + * @return {number} The hexadecimal value. + */ + getHex( colorSpace = SRGBColorSpace ) { + + ColorManagement.workingToColorSpace( _color.copy( this ), colorSpace ); + + return Math.round( clamp( _color.r * 255, 0, 255 ) ) * 65536 + Math.round( clamp( _color.g * 255, 0, 255 ) ) * 256 + Math.round( clamp( _color.b * 255, 0, 255 ) ); + + } + + /** + * Returns the hexadecimal value of this color as a string (for example, 'FFFFFF'). + * + * @param {string} [colorSpace=SRGBColorSpace] - The color space. + * @return {string} The hexadecimal value as a string. + */ + getHexString( colorSpace = SRGBColorSpace ) { + + return ( '000000' + this.getHex( colorSpace ).toString( 16 ) ).slice( -6 ); + + } + + /** + * Converts the colors RGB values into the HSL format and stores them into the + * given target object. + * + * @param {{h:number,s:number,l:number}} target - The target object that is used to store the method's result. + * @param {string} [colorSpace=ColorManagement.workingColorSpace] - The color space. + * @return {{h:number,s:number,l:number}} The HSL representation of this color. + */ + getHSL( target, colorSpace = ColorManagement.workingColorSpace ) { + + // h,s,l ranges are in 0.0 - 1.0 + + ColorManagement.workingToColorSpace( _color.copy( this ), colorSpace ); + + const r = _color.r, g = _color.g, b = _color.b; + + const max = Math.max( r, g, b ); + const min = Math.min( r, g, b ); + + let hue, saturation; + const lightness = ( min + max ) / 2.0; + + if ( min === max ) { + + hue = 0; + saturation = 0; + + } else { + + const delta = max - min; + + saturation = lightness <= 0.5 ? delta / ( max + min ) : delta / ( 2 - max - min ); + + switch ( max ) { + + case r: hue = ( g - b ) / delta + ( g < b ? 6 : 0 ); break; + case g: hue = ( b - r ) / delta + 2; break; + case b: hue = ( r - g ) / delta + 4; break; + + } + + hue /= 6; + + } + + target.h = hue; + target.s = saturation; + target.l = lightness; + + return target; + + } + + /** + * Returns the RGB values of this color and stores them into the given target object. + * + * @param {Color} target - The target color that is used to store the method's result. + * @param {string} [colorSpace=ColorManagement.workingColorSpace] - The color space. + * @return {Color} The RGB representation of this color. + */ + getRGB( target, colorSpace = ColorManagement.workingColorSpace ) { + + ColorManagement.workingToColorSpace( _color.copy( this ), colorSpace ); + + target.r = _color.r; + target.g = _color.g; + target.b = _color.b; + + return target; + + } + + /** + * Returns the value of this color as a CSS style string. Example: `rgb(255,0,0)`. + * + * @param {string} [colorSpace=SRGBColorSpace] - The color space. + * @return {string} The CSS representation of this color. + */ + getStyle( colorSpace = SRGBColorSpace ) { + + ColorManagement.workingToColorSpace( _color.copy( this ), colorSpace ); + + const r = _color.r, g = _color.g, b = _color.b; + + if ( colorSpace !== SRGBColorSpace ) { + + // Requires CSS Color Module Level 4 (https://www.w3.org/TR/css-color-4/). + return `color(${ colorSpace } ${ r.toFixed( 3 ) } ${ g.toFixed( 3 ) } ${ b.toFixed( 3 ) })`; + + } + + return `rgb(${ Math.round( r * 255 ) },${ Math.round( g * 255 ) },${ Math.round( b * 255 ) })`; + + } + + /** + * Adds the given HSL values to this color's values. + * Internally, this converts the color's RGB values to HSL, adds HSL + * and then converts the color back to RGB. + * + * @param {number} h - Hue value between `0.0` and `1.0`. + * @param {number} s - Saturation value between `0.0` and `1.0`. + * @param {number} l - Lightness value between `0.0` and `1.0`. + * @return {Color} A reference to this color. + */ + offsetHSL( h, s, l ) { + + this.getHSL( _hslA ); + + return this.setHSL( _hslA.h + h, _hslA.s + s, _hslA.l + l ); + + } + + /** + * Adds the RGB values of the given color to the RGB values of this color. + * + * @param {Color} color - The color to add. + * @return {Color} A reference to this color. + */ + add( color ) { + + this.r += color.r; + this.g += color.g; + this.b += color.b; + + return this; + + } + + /** + * Adds the RGB values of the given colors and stores the result in this instance. + * + * @param {Color} color1 - The first color. + * @param {Color} color2 - The second color. + * @return {Color} A reference to this color. + */ + addColors( color1, color2 ) { + + this.r = color1.r + color2.r; + this.g = color1.g + color2.g; + this.b = color1.b + color2.b; + + return this; + + } + + /** + * Adds the given scalar value to the RGB values of this color. + * + * @param {number} s - The scalar to add. + * @return {Color} A reference to this color. + */ + addScalar( s ) { + + this.r += s; + this.g += s; + this.b += s; + + return this; + + } + + /** + * Subtracts the RGB values of the given color from the RGB values of this color. + * + * @param {Color} color - The color to subtract. + * @return {Color} A reference to this color. + */ + sub( color ) { + + this.r = Math.max( 0, this.r - color.r ); + this.g = Math.max( 0, this.g - color.g ); + this.b = Math.max( 0, this.b - color.b ); + + return this; + + } + + /** + * Multiplies the RGB values of the given color with the RGB values of this color. + * + * @param {Color} color - The color to multiply. + * @return {Color} A reference to this color. + */ + multiply( color ) { + + this.r *= color.r; + this.g *= color.g; + this.b *= color.b; + + return this; + + } + + /** + * Multiplies the given scalar value with the RGB values of this color. + * + * @param {number} s - The scalar to multiply. + * @return {Color} A reference to this color. + */ + multiplyScalar( s ) { + + this.r *= s; + this.g *= s; + this.b *= s; + + return this; + + } + + /** + * Linearly interpolates this color's RGB values toward the RGB values of the + * given color. The alpha argument can be thought of as the ratio between + * the two colors, where `0.0` is this color and `1.0` is the first argument. + * + * @param {Color} color - The color to converge on. + * @param {number} alpha - The interpolation factor in the closed interval `[0,1]`. + * @return {Color} A reference to this color. + */ + lerp( color, alpha ) { + + this.r += ( color.r - this.r ) * alpha; + this.g += ( color.g - this.g ) * alpha; + this.b += ( color.b - this.b ) * alpha; + + return this; + + } + + /** + * Linearly interpolates between the given colors and stores the result in this instance. + * The alpha argument can be thought of as the ratio between the two colors, where `0.0` + * is the first and `1.0` is the second color. + * + * @param {Color} color1 - The first color. + * @param {Color} color2 - The second color. + * @param {number} alpha - The interpolation factor in the closed interval `[0,1]`. + * @return {Color} A reference to this color. + */ + lerpColors( color1, color2, alpha ) { + + this.r = color1.r + ( color2.r - color1.r ) * alpha; + this.g = color1.g + ( color2.g - color1.g ) * alpha; + this.b = color1.b + ( color2.b - color1.b ) * alpha; + + return this; + + } + + /** + * Linearly interpolates this color's HSL values toward the HSL values of the + * given color. It differs from {@link Color#lerp} by not interpolating straight + * from one color to the other, but instead going through all the hues in between + * those two colors. The alpha argument can be thought of as the ratio between + * the two colors, where 0.0 is this color and 1.0 is the first argument. + * + * @param {Color} color - The color to converge on. + * @param {number} alpha - The interpolation factor in the closed interval `[0,1]`. + * @return {Color} A reference to this color. + */ + lerpHSL( color, alpha ) { + + this.getHSL( _hslA ); + color.getHSL( _hslB ); + + const h = lerp( _hslA.h, _hslB.h, alpha ); + const s = lerp( _hslA.s, _hslB.s, alpha ); + const l = lerp( _hslA.l, _hslB.l, alpha ); + + this.setHSL( h, s, l ); + + return this; + + } + + /** + * Sets the color's RGB components from the given 3D vector. + * + * @param {Vector3} v - The vector to set. + * @return {Color} A reference to this color. + */ + setFromVector3( v ) { + + this.r = v.x; + this.g = v.y; + this.b = v.z; + + return this; + + } + + /** + * Transforms this color with the given 3x3 matrix. + * + * @param {Matrix3} m - The matrix. + * @return {Color} A reference to this color. + */ + applyMatrix3( m ) { + + const r = this.r, g = this.g, b = this.b; + const e = m.elements; + + this.r = e[ 0 ] * r + e[ 3 ] * g + e[ 6 ] * b; + this.g = e[ 1 ] * r + e[ 4 ] * g + e[ 7 ] * b; + this.b = e[ 2 ] * r + e[ 5 ] * g + e[ 8 ] * b; + + return this; + + } + + /** + * Returns `true` if this color is equal with the given one. + * + * @param {Color} c - The color to test for equality. + * @return {boolean} Whether this bounding color is equal with the given one. + */ + equals( c ) { + + return ( c.r === this.r ) && ( c.g === this.g ) && ( c.b === this.b ); + + } + + /** + * Sets this color's RGB components from the given array. + * + * @param {Array} array - An array holding the RGB values. + * @param {number} [offset=0] - The offset into the array. + * @return {Color} A reference to this color. + */ + fromArray( array, offset = 0 ) { + + this.r = array[ offset ]; + this.g = array[ offset + 1 ]; + this.b = array[ offset + 2 ]; + + return this; + + } + + /** + * Writes the RGB components of this color to the given array. If no array is provided, + * the method returns a new instance. + * + * @param {Array} [array=[]] - The target array holding the color components. + * @param {number} [offset=0] - Index of the first element in the array. + * @return {Array} The color components. + */ + toArray( array = [], offset = 0 ) { + + array[ offset ] = this.r; + array[ offset + 1 ] = this.g; + array[ offset + 2 ] = this.b; + + return array; + + } + + /** + * Sets the components of this color from the given buffer attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute holding color data. + * @param {number} index - The index into the attribute. + * @return {Color} A reference to this color. + */ + fromBufferAttribute( attribute, index ) { + + this.r = attribute.getX( index ); + this.g = attribute.getY( index ); + this.b = attribute.getZ( index ); + + return this; + + } + + /** + * This methods defines the serialization result of this class. Returns the color + * as a hexadecimal value. + * + * @return {number} The hexadecimal value. + */ + toJSON() { + + return this.getHex(); + + } + + *[ Symbol.iterator ]() { + + yield this.r; + yield this.g; + yield this.b; + + } + +} + +const _color = /*@__PURE__*/ new Color(); + +/** + * A dictionary with X11 color names. + * + * Note that multiple words such as Dark Orange become the string 'darkorange'. + * + * @static + * @type {Object} + */ +Color.NAMES = _colorKeywords; + +let _materialId = 0; + +/** + * Abstract base class for materials. + * + * Materials define the appearance of renderable 3D objects. + * + * @abstract + * @augments EventDispatcher + */ +class Material extends EventDispatcher { + + /** + * Constructs a new material. + */ + constructor() { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMaterial = true; + + /** + * The ID of the material. + * + * @name Material#id + * @type {number} + * @readonly + */ + Object.defineProperty( this, 'id', { value: _materialId ++ } ); + + /** + * The UUID of the material. + * + * @type {string} + * @readonly + */ + this.uuid = generateUUID(); + + /** + * The name of the material. + * + * @type {string} + */ + this.name = ''; + + /** + * The type property is used for detecting the object type + * in context of serialization/deserialization. + * + * @type {string} + * @readonly + */ + this.type = 'Material'; + + /** + * Defines the blending type of the material. + * + * It must be set to `CustomBlending` if custom blending properties like + * {@link Material#blendSrc}, {@link Material#blendDst} or {@link Material#blendEquation} + * should have any effect. + * + * @type {(NoBlending|NormalBlending|AdditiveBlending|SubtractiveBlending|MultiplyBlending|CustomBlending)} + * @default NormalBlending + */ + this.blending = NormalBlending; + + /** + * Defines which side of faces will be rendered - front, back or both. + * + * @type {(FrontSide|BackSide|DoubleSide)} + * @default FrontSide + */ + this.side = FrontSide; + + /** + * If set to `true`, vertex colors should be used. + * + * The engine supports RGB and RGBA vertex colors depending on whether a three (RGB) or + * four (RGBA) component color buffer attribute is used. + * + * @type {boolean} + * @default false + */ + this.vertexColors = false; + + /** + * Defines how transparent the material is. + * A value of `0.0` indicates fully transparent, `1.0` is fully opaque. + * + * If the {@link Material#transparent} is not set to `true`, + * the material will remain fully opaque and this value will only affect its color. + * + * @type {number} + * @default 1 + */ + this.opacity = 1; + + /** + * Defines whether this material is transparent. This has an effect on + * rendering as transparent objects need special treatment and are rendered + * after non-transparent objects. + * + * When set to true, the extent to which the material is transparent is + * controlled by {@link Material#opacity}. + * + * @type {boolean} + * @default false + */ + this.transparent = false; + + /** + * Enables alpha hashed transparency, an alternative to {@link Material#transparent} or + * {@link Material#alphaTest}. The material will not be rendered if opacity is lower than + * a random threshold. Randomization introduces some grain or noise, but approximates alpha + * blending without the associated problems of sorting. Using TAA can reduce the resulting noise. + * + * @type {boolean} + * @default false + */ + this.alphaHash = false; + + /** + * Defines the blending source factor. + * + * @type {(ZeroFactor|OneFactor|SrcColorFactor|OneMinusSrcColorFactor|SrcAlphaFactor|OneMinusSrcAlphaFactor|DstAlphaFactor|OneMinusDstAlphaFactor|DstColorFactor|OneMinusDstColorFactor|SrcAlphaSaturateFactor|ConstantColorFactor|OneMinusConstantColorFactor|ConstantAlphaFactor|OneMinusConstantAlphaFactor)} + * @default SrcAlphaFactor + */ + this.blendSrc = SrcAlphaFactor; + + /** + * Defines the blending destination factor. + * + * @type {(ZeroFactor|OneFactor|SrcColorFactor|OneMinusSrcColorFactor|SrcAlphaFactor|OneMinusSrcAlphaFactor|DstAlphaFactor|OneMinusDstAlphaFactor|DstColorFactor|OneMinusDstColorFactor|SrcAlphaSaturateFactor|ConstantColorFactor|OneMinusConstantColorFactor|ConstantAlphaFactor|OneMinusConstantAlphaFactor)} + * @default OneMinusSrcAlphaFactor + */ + this.blendDst = OneMinusSrcAlphaFactor; + + /** + * Defines the blending equation. + * + * @type {(AddEquation|SubtractEquation|ReverseSubtractEquation|MinEquation|MaxEquation)} + * @default AddEquation + */ + this.blendEquation = AddEquation; + + /** + * Defines the blending source alpha factor. + * + * @type {?(ZeroFactor|OneFactor|SrcColorFactor|OneMinusSrcColorFactor|SrcAlphaFactor|OneMinusSrcAlphaFactor|DstAlphaFactor|OneMinusDstAlphaFactor|DstColorFactor|OneMinusDstColorFactor|SrcAlphaSaturateFactor|ConstantColorFactor|OneMinusConstantColorFactor|ConstantAlphaFactor|OneMinusConstantAlphaFactor)} + * @default null + */ + this.blendSrcAlpha = null; + + /** + * Defines the blending destination alpha factor. + * + * @type {?(ZeroFactor|OneFactor|SrcColorFactor|OneMinusSrcColorFactor|SrcAlphaFactor|OneMinusSrcAlphaFactor|DstAlphaFactor|OneMinusDstAlphaFactor|DstColorFactor|OneMinusDstColorFactor|SrcAlphaSaturateFactor|ConstantColorFactor|OneMinusConstantColorFactor|ConstantAlphaFactor|OneMinusConstantAlphaFactor)} + * @default null + */ + this.blendDstAlpha = null; + + /** + * Defines the blending equation of the alpha channel. + * + * @type {?(AddEquation|SubtractEquation|ReverseSubtractEquation|MinEquation|MaxEquation)} + * @default null + */ + this.blendEquationAlpha = null; + + /** + * Represents the RGB values of the constant blend color. + * + * This property has only an effect when using custom blending with `ConstantColor` or `OneMinusConstantColor`. + * + * @type {Color} + * @default (0,0,0) + */ + this.blendColor = new Color( 0, 0, 0 ); + + /** + * Represents the alpha value of the constant blend color. + * + * This property has only an effect when using custom blending with `ConstantAlpha` or `OneMinusConstantAlpha`. + * + * @type {number} + * @default 0 + */ + this.blendAlpha = 0; + + /** + * Defines the depth function. + * + * @type {(NeverDepth|AlwaysDepth|LessDepth|LessEqualDepth|EqualDepth|GreaterEqualDepth|GreaterDepth|NotEqualDepth)} + * @default LessEqualDepth + */ + this.depthFunc = LessEqualDepth; + + /** + * Whether to have depth test enabled when rendering this material. + * When the depth test is disabled, the depth write will also be implicitly disabled. + * + * @type {boolean} + * @default true + */ + this.depthTest = true; + + /** + * Whether rendering this material has any effect on the depth buffer. + * + * When drawing 2D overlays it can be useful to disable the depth writing in + * order to layer several things together without creating z-index artifacts. + * + * @type {boolean} + * @default true + */ + this.depthWrite = true; + + /** + * The bit mask to use when writing to the stencil buffer. + * + * @type {number} + * @default 0xff + */ + this.stencilWriteMask = 0xff; + + /** + * The stencil comparison function to use. + * + * @type {NeverStencilFunc|LessStencilFunc|EqualStencilFunc|LessEqualStencilFunc|GreaterStencilFunc|NotEqualStencilFunc|GreaterEqualStencilFunc|AlwaysStencilFunc} + * @default AlwaysStencilFunc + */ + this.stencilFunc = AlwaysStencilFunc; + + /** + * The value to use when performing stencil comparisons or stencil operations. + * + * @type {number} + * @default 0 + */ + this.stencilRef = 0; + + /** + * The bit mask to use when comparing against the stencil buffer. + * + * @type {number} + * @default 0xff + */ + this.stencilFuncMask = 0xff; + + /** + * Which stencil operation to perform when the comparison function returns `false`. + * + * @type {ZeroStencilOp|KeepStencilOp|ReplaceStencilOp|IncrementStencilOp|DecrementStencilOp|IncrementWrapStencilOp|DecrementWrapStencilOp|InvertStencilOp} + * @default KeepStencilOp + */ + this.stencilFail = KeepStencilOp; + + /** + * Which stencil operation to perform when the comparison function returns + * `true` but the depth test fails. + * + * @type {ZeroStencilOp|KeepStencilOp|ReplaceStencilOp|IncrementStencilOp|DecrementStencilOp|IncrementWrapStencilOp|DecrementWrapStencilOp|InvertStencilOp} + * @default KeepStencilOp + */ + this.stencilZFail = KeepStencilOp; + + /** + * Which stencil operation to perform when the comparison function returns + * `true` and the depth test passes. + * + * @type {ZeroStencilOp|KeepStencilOp|ReplaceStencilOp|IncrementStencilOp|DecrementStencilOp|IncrementWrapStencilOp|DecrementWrapStencilOp|InvertStencilOp} + * @default KeepStencilOp + */ + this.stencilZPass = KeepStencilOp; + + /** + * Whether stencil operations are performed against the stencil buffer. In + * order to perform writes or comparisons against the stencil buffer this + * value must be `true`. + * + * @type {boolean} + * @default false + */ + this.stencilWrite = false; + + /** + * User-defined clipping planes specified as THREE.Plane objects in world + * space. These planes apply to the objects this material is attached to. + * Points in space whose signed distance to the plane is negative are clipped + * (not rendered). This requires {@link WebGLRenderer#localClippingEnabled} to + * be `true`. + * + * @type {?Array} + * @default null + */ + this.clippingPlanes = null; + + /** + * Changes the behavior of clipping planes so that only their intersection is + * clipped, rather than their union. + * + * @type {boolean} + * @default false + */ + this.clipIntersection = false; + + /** + * Defines whether to clip shadows according to the clipping planes specified + * on this material. + * + * @type {boolean} + * @default false + */ + this.clipShadows = false; + + /** + * Defines which side of faces cast shadows. If `null`, the side casting shadows + * is determined as follows: + * + * - When {@link Material#side} is set to `FrontSide`, the back side cast shadows. + * - When {@link Material#side} is set to `BackSide`, the front side cast shadows. + * - When {@link Material#side} is set to `DoubleSide`, both sides cast shadows. + * + * @type {?(FrontSide|BackSide|DoubleSide)} + * @default null + */ + this.shadowSide = null; + + /** + * Whether to render the material's color. + * + * This can be used in conjunction with {@link Object3D#renderOder} to create invisible + * objects that occlude other objects. + * + * @type {boolean} + * @default true + */ + this.colorWrite = true; + + /** + * Override the renderer's default precision for this material. + * + * @type {?('highp'|'mediump'|'lowp')} + * @default null + */ + this.precision = null; + + /** + * Whether to use polygon offset or not. When enabled, each fragment's depth value will + * be offset after it is interpolated from the depth values of the appropriate vertices. + * The offset is added before the depth test is performed and before the value is written + * into the depth buffer. + * + * Can be useful for rendering hidden-line images, for applying decals to surfaces, and for + * rendering solids with highlighted edges. + * + * @type {boolean} + * @default false + */ + this.polygonOffset = false; + + /** + * Specifies a scale factor that is used to create a variable depth offset for each polygon. + * + * @type {number} + * @default 0 + */ + this.polygonOffsetFactor = 0; + + /** + * Is multiplied by an implementation-specific value to create a constant depth offset. + * + * @type {number} + * @default 0 + */ + this.polygonOffsetUnits = 0; + + /** + * Whether to apply dithering to the color to remove the appearance of banding. + * + * @type {boolean} + * @default false + */ + this.dithering = false; + + /** + * Whether alpha to coverage should be enabled or not. Can only be used with MSAA-enabled contexts + * (meaning when the renderer was created with *antialias* parameter set to `true`). Enabling this + * will smooth aliasing on clip plane edges and alphaTest-clipped edges. + * + * @type {boolean} + * @default false + */ + this.alphaToCoverage = false; + + /** + * Whether to premultiply the alpha (transparency) value. + * + * @type {boolean} + * @default false + */ + this.premultipliedAlpha = false; + + /** + * Whether double-sided, transparent objects should be rendered with a single pass or not. + * + * The engine renders double-sided, transparent objects with two draw calls (back faces first, + * then front faces) to mitigate transparency artifacts. There are scenarios however where this + * approach produces no quality gains but still doubles draw calls e.g. when rendering flat + * vegetation like grass sprites. In these cases, set the `forceSinglePass` flag to `true` to + * disable the two pass rendering to avoid performance issues. + * + * @type {boolean} + * @default false + */ + this.forceSinglePass = false; + + /** + * Whether it's possible to override the material with {@link Scene#overrideMaterial} or not. + * + * @type {boolean} + * @default true + */ + this.allowOverride = true; + + /** + * Defines whether 3D objects using this material are visible. + * + * @type {boolean} + * @default true + */ + this.visible = true; + + /** + * Defines whether this material is tone mapped according to the renderer's tone mapping setting. + * + * It is ignored when rendering to a render target or using post processing or when using + * `WebGPURenderer`. In all these cases, all materials are honored by tone mapping. + * + * @type {boolean} + * @default true + */ + this.toneMapped = true; + + /** + * An object that can be used to store custom data about the Material. It + * should not hold references to functions as these will not be cloned. + * + * @type {Object} + */ + this.userData = {}; + + /** + * This starts at `0` and counts how many times {@link Material#needsUpdate} is set to `true`. + * + * @type {number} + * @readonly + * @default 0 + */ + this.version = 0; + + this._alphaTest = 0; + + } + + /** + * Sets the alpha value to be used when running an alpha test. The material + * will not be rendered if the opacity is lower than this value. + * + * @type {number} + * @readonly + * @default 0 + */ + get alphaTest() { + + return this._alphaTest; + + } + + set alphaTest( value ) { + + if ( this._alphaTest > 0 !== value > 0 ) { + + this.version ++; + + } + + this._alphaTest = value; + + } + + /** + * An optional callback that is executed immediately before the material is used to render a 3D object. + * + * This method can only be used when rendering with {@link WebGLRenderer}. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {Scene} scene - The scene. + * @param {Camera} camera - The camera that is used to render the scene. + * @param {BufferGeometry} geometry - The 3D object's geometry. + * @param {Object3D} object - The 3D object. + * @param {Object} group - The geometry group data. + */ + onBeforeRender( /* renderer, scene, camera, geometry, object, group */ ) {} + + /** + * An optional callback that is executed immediately before the shader + * program is compiled. This function is called with the shader source code + * as a parameter. Useful for the modification of built-in materials. + * + * This method can only be used when rendering with {@link WebGLRenderer}. The + * recommended approach when customizing materials is to use `WebGPURenderer` with the new + * Node Material system and [TSL]{@link https://github.com/mrdoob/three.js/wiki/Three.js-Shading-Language}. + * + * @param {{vertexShader:string,fragmentShader:string,uniforms:Object}} shaderobject - The object holds the uniforms and the vertex and fragment shader source. + * @param {WebGLRenderer} renderer - A reference to the renderer. + */ + onBeforeCompile( /* shaderobject, renderer */ ) {} + + /** + * In case {@link Material#onBeforeCompile} is used, this callback can be used to identify + * values of settings used in `onBeforeCompile()`, so three.js can reuse a cached + * shader or recompile the shader for this material as needed. + * + * This method can only be used when rendering with {@link WebGLRenderer}. + * + * @return {string} The custom program cache key. + */ + customProgramCacheKey() { + + return this.onBeforeCompile.toString(); + + } + + /** + * This method can be used to set default values from parameter objects. + * It is a generic implementation so it can be used with different types + * of materials. + * + * @param {Object} [values] - The material values to set. + */ + setValues( values ) { + + if ( values === undefined ) return; + + for ( const key in values ) { + + const newValue = values[ key ]; + + if ( newValue === undefined ) { + + console.warn( `THREE.Material: parameter '${ key }' has value of undefined.` ); + continue; + + } + + const currentValue = this[ key ]; + + if ( currentValue === undefined ) { + + console.warn( `THREE.Material: '${ key }' is not a property of THREE.${ this.type }.` ); + continue; + + } + + if ( currentValue && currentValue.isColor ) { + + currentValue.set( newValue ); + + } else if ( ( currentValue && currentValue.isVector3 ) && ( newValue && newValue.isVector3 ) ) { + + currentValue.copy( newValue ); + + } else { + + this[ key ] = newValue; + + } + + } + + } + + /** + * Serializes the material into JSON. + * + * @param {?(Object|string)} meta - An optional value holding meta information about the serialization. + * @return {Object} A JSON object representing the serialized material. + * @see {@link ObjectLoader#parse} + */ + toJSON( meta ) { + + const isRootObject = ( meta === undefined || typeof meta === 'string' ); + + if ( isRootObject ) { + + meta = { + textures: {}, + images: {} + }; + + } + + const data = { + metadata: { + version: 4.7, + type: 'Material', + generator: 'Material.toJSON' + } + }; + + // standard Material serialization + data.uuid = this.uuid; + data.type = this.type; + + if ( this.name !== '' ) data.name = this.name; + + if ( this.color && this.color.isColor ) data.color = this.color.getHex(); + + if ( this.roughness !== undefined ) data.roughness = this.roughness; + if ( this.metalness !== undefined ) data.metalness = this.metalness; + + if ( this.sheen !== undefined ) data.sheen = this.sheen; + if ( this.sheenColor && this.sheenColor.isColor ) data.sheenColor = this.sheenColor.getHex(); + if ( this.sheenRoughness !== undefined ) data.sheenRoughness = this.sheenRoughness; + if ( this.emissive && this.emissive.isColor ) data.emissive = this.emissive.getHex(); + if ( this.emissiveIntensity !== undefined && this.emissiveIntensity !== 1 ) data.emissiveIntensity = this.emissiveIntensity; + + if ( this.specular && this.specular.isColor ) data.specular = this.specular.getHex(); + if ( this.specularIntensity !== undefined ) data.specularIntensity = this.specularIntensity; + if ( this.specularColor && this.specularColor.isColor ) data.specularColor = this.specularColor.getHex(); + if ( this.shininess !== undefined ) data.shininess = this.shininess; + if ( this.clearcoat !== undefined ) data.clearcoat = this.clearcoat; + if ( this.clearcoatRoughness !== undefined ) data.clearcoatRoughness = this.clearcoatRoughness; + + if ( this.clearcoatMap && this.clearcoatMap.isTexture ) { + + data.clearcoatMap = this.clearcoatMap.toJSON( meta ).uuid; + + } + + if ( this.clearcoatRoughnessMap && this.clearcoatRoughnessMap.isTexture ) { + + data.clearcoatRoughnessMap = this.clearcoatRoughnessMap.toJSON( meta ).uuid; + + } + + if ( this.clearcoatNormalMap && this.clearcoatNormalMap.isTexture ) { + + data.clearcoatNormalMap = this.clearcoatNormalMap.toJSON( meta ).uuid; + data.clearcoatNormalScale = this.clearcoatNormalScale.toArray(); + + } + + if ( this.dispersion !== undefined ) data.dispersion = this.dispersion; + + if ( this.iridescence !== undefined ) data.iridescence = this.iridescence; + if ( this.iridescenceIOR !== undefined ) data.iridescenceIOR = this.iridescenceIOR; + if ( this.iridescenceThicknessRange !== undefined ) data.iridescenceThicknessRange = this.iridescenceThicknessRange; + + if ( this.iridescenceMap && this.iridescenceMap.isTexture ) { + + data.iridescenceMap = this.iridescenceMap.toJSON( meta ).uuid; + + } + + if ( this.iridescenceThicknessMap && this.iridescenceThicknessMap.isTexture ) { + + data.iridescenceThicknessMap = this.iridescenceThicknessMap.toJSON( meta ).uuid; + + } + + if ( this.anisotropy !== undefined ) data.anisotropy = this.anisotropy; + if ( this.anisotropyRotation !== undefined ) data.anisotropyRotation = this.anisotropyRotation; + + if ( this.anisotropyMap && this.anisotropyMap.isTexture ) { + + data.anisotropyMap = this.anisotropyMap.toJSON( meta ).uuid; + + } + + if ( this.map && this.map.isTexture ) data.map = this.map.toJSON( meta ).uuid; + if ( this.matcap && this.matcap.isTexture ) data.matcap = this.matcap.toJSON( meta ).uuid; + if ( this.alphaMap && this.alphaMap.isTexture ) data.alphaMap = this.alphaMap.toJSON( meta ).uuid; + + if ( this.lightMap && this.lightMap.isTexture ) { + + data.lightMap = this.lightMap.toJSON( meta ).uuid; + data.lightMapIntensity = this.lightMapIntensity; + + } + + if ( this.aoMap && this.aoMap.isTexture ) { + + data.aoMap = this.aoMap.toJSON( meta ).uuid; + data.aoMapIntensity = this.aoMapIntensity; + + } + + if ( this.bumpMap && this.bumpMap.isTexture ) { + + data.bumpMap = this.bumpMap.toJSON( meta ).uuid; + data.bumpScale = this.bumpScale; + + } + + if ( this.normalMap && this.normalMap.isTexture ) { + + data.normalMap = this.normalMap.toJSON( meta ).uuid; + data.normalMapType = this.normalMapType; + data.normalScale = this.normalScale.toArray(); + + } + + if ( this.displacementMap && this.displacementMap.isTexture ) { + + data.displacementMap = this.displacementMap.toJSON( meta ).uuid; + data.displacementScale = this.displacementScale; + data.displacementBias = this.displacementBias; + + } + + if ( this.roughnessMap && this.roughnessMap.isTexture ) data.roughnessMap = this.roughnessMap.toJSON( meta ).uuid; + if ( this.metalnessMap && this.metalnessMap.isTexture ) data.metalnessMap = this.metalnessMap.toJSON( meta ).uuid; + + if ( this.emissiveMap && this.emissiveMap.isTexture ) data.emissiveMap = this.emissiveMap.toJSON( meta ).uuid; + if ( this.specularMap && this.specularMap.isTexture ) data.specularMap = this.specularMap.toJSON( meta ).uuid; + if ( this.specularIntensityMap && this.specularIntensityMap.isTexture ) data.specularIntensityMap = this.specularIntensityMap.toJSON( meta ).uuid; + if ( this.specularColorMap && this.specularColorMap.isTexture ) data.specularColorMap = this.specularColorMap.toJSON( meta ).uuid; + + if ( this.envMap && this.envMap.isTexture ) { + + data.envMap = this.envMap.toJSON( meta ).uuid; + + if ( this.combine !== undefined ) data.combine = this.combine; + + } + + if ( this.envMapRotation !== undefined ) data.envMapRotation = this.envMapRotation.toArray(); + if ( this.envMapIntensity !== undefined ) data.envMapIntensity = this.envMapIntensity; + if ( this.reflectivity !== undefined ) data.reflectivity = this.reflectivity; + if ( this.refractionRatio !== undefined ) data.refractionRatio = this.refractionRatio; + + if ( this.gradientMap && this.gradientMap.isTexture ) { + + data.gradientMap = this.gradientMap.toJSON( meta ).uuid; + + } + + if ( this.transmission !== undefined ) data.transmission = this.transmission; + if ( this.transmissionMap && this.transmissionMap.isTexture ) data.transmissionMap = this.transmissionMap.toJSON( meta ).uuid; + if ( this.thickness !== undefined ) data.thickness = this.thickness; + if ( this.thicknessMap && this.thicknessMap.isTexture ) data.thicknessMap = this.thicknessMap.toJSON( meta ).uuid; + if ( this.attenuationDistance !== undefined && this.attenuationDistance !== Infinity ) data.attenuationDistance = this.attenuationDistance; + if ( this.attenuationColor !== undefined ) data.attenuationColor = this.attenuationColor.getHex(); + + if ( this.size !== undefined ) data.size = this.size; + if ( this.shadowSide !== null ) data.shadowSide = this.shadowSide; + if ( this.sizeAttenuation !== undefined ) data.sizeAttenuation = this.sizeAttenuation; + + if ( this.blending !== NormalBlending ) data.blending = this.blending; + if ( this.side !== FrontSide ) data.side = this.side; + if ( this.vertexColors === true ) data.vertexColors = true; + + if ( this.opacity < 1 ) data.opacity = this.opacity; + if ( this.transparent === true ) data.transparent = true; + + if ( this.blendSrc !== SrcAlphaFactor ) data.blendSrc = this.blendSrc; + if ( this.blendDst !== OneMinusSrcAlphaFactor ) data.blendDst = this.blendDst; + if ( this.blendEquation !== AddEquation ) data.blendEquation = this.blendEquation; + if ( this.blendSrcAlpha !== null ) data.blendSrcAlpha = this.blendSrcAlpha; + if ( this.blendDstAlpha !== null ) data.blendDstAlpha = this.blendDstAlpha; + if ( this.blendEquationAlpha !== null ) data.blendEquationAlpha = this.blendEquationAlpha; + if ( this.blendColor && this.blendColor.isColor ) data.blendColor = this.blendColor.getHex(); + if ( this.blendAlpha !== 0 ) data.blendAlpha = this.blendAlpha; + + if ( this.depthFunc !== LessEqualDepth ) data.depthFunc = this.depthFunc; + if ( this.depthTest === false ) data.depthTest = this.depthTest; + if ( this.depthWrite === false ) data.depthWrite = this.depthWrite; + if ( this.colorWrite === false ) data.colorWrite = this.colorWrite; + + if ( this.stencilWriteMask !== 0xff ) data.stencilWriteMask = this.stencilWriteMask; + if ( this.stencilFunc !== AlwaysStencilFunc ) data.stencilFunc = this.stencilFunc; + if ( this.stencilRef !== 0 ) data.stencilRef = this.stencilRef; + if ( this.stencilFuncMask !== 0xff ) data.stencilFuncMask = this.stencilFuncMask; + if ( this.stencilFail !== KeepStencilOp ) data.stencilFail = this.stencilFail; + if ( this.stencilZFail !== KeepStencilOp ) data.stencilZFail = this.stencilZFail; + if ( this.stencilZPass !== KeepStencilOp ) data.stencilZPass = this.stencilZPass; + if ( this.stencilWrite === true ) data.stencilWrite = this.stencilWrite; + + // rotation (SpriteMaterial) + if ( this.rotation !== undefined && this.rotation !== 0 ) data.rotation = this.rotation; + + if ( this.polygonOffset === true ) data.polygonOffset = true; + if ( this.polygonOffsetFactor !== 0 ) data.polygonOffsetFactor = this.polygonOffsetFactor; + if ( this.polygonOffsetUnits !== 0 ) data.polygonOffsetUnits = this.polygonOffsetUnits; + + if ( this.linewidth !== undefined && this.linewidth !== 1 ) data.linewidth = this.linewidth; + if ( this.dashSize !== undefined ) data.dashSize = this.dashSize; + if ( this.gapSize !== undefined ) data.gapSize = this.gapSize; + if ( this.scale !== undefined ) data.scale = this.scale; + + if ( this.dithering === true ) data.dithering = true; + + if ( this.alphaTest > 0 ) data.alphaTest = this.alphaTest; + if ( this.alphaHash === true ) data.alphaHash = true; + if ( this.alphaToCoverage === true ) data.alphaToCoverage = true; + if ( this.premultipliedAlpha === true ) data.premultipliedAlpha = true; + if ( this.forceSinglePass === true ) data.forceSinglePass = true; + + if ( this.wireframe === true ) data.wireframe = true; + if ( this.wireframeLinewidth > 1 ) data.wireframeLinewidth = this.wireframeLinewidth; + if ( this.wireframeLinecap !== 'round' ) data.wireframeLinecap = this.wireframeLinecap; + if ( this.wireframeLinejoin !== 'round' ) data.wireframeLinejoin = this.wireframeLinejoin; + + if ( this.flatShading === true ) data.flatShading = true; + + if ( this.visible === false ) data.visible = false; + + if ( this.toneMapped === false ) data.toneMapped = false; + + if ( this.fog === false ) data.fog = false; + + if ( Object.keys( this.userData ).length > 0 ) data.userData = this.userData; + + // TODO: Copied from Object3D.toJSON + + function extractFromCache( cache ) { + + const values = []; + + for ( const key in cache ) { + + const data = cache[ key ]; + delete data.metadata; + values.push( data ); + + } + + return values; + + } + + if ( isRootObject ) { + + const textures = extractFromCache( meta.textures ); + const images = extractFromCache( meta.images ); + + if ( textures.length > 0 ) data.textures = textures; + if ( images.length > 0 ) data.images = images; + + } + + return data; + + } + + /** + * Returns a new material with copied values from this instance. + * + * @return {Material} A clone of this instance. + */ + clone() { + + return new this.constructor().copy( this ); + + } + + /** + * Copies the values of the given material to this instance. + * + * @param {Material} source - The material to copy. + * @return {Material} A reference to this instance. + */ + copy( source ) { + + this.name = source.name; + + this.blending = source.blending; + this.side = source.side; + this.vertexColors = source.vertexColors; + + this.opacity = source.opacity; + this.transparent = source.transparent; + + this.blendSrc = source.blendSrc; + this.blendDst = source.blendDst; + this.blendEquation = source.blendEquation; + this.blendSrcAlpha = source.blendSrcAlpha; + this.blendDstAlpha = source.blendDstAlpha; + this.blendEquationAlpha = source.blendEquationAlpha; + this.blendColor.copy( source.blendColor ); + this.blendAlpha = source.blendAlpha; + + this.depthFunc = source.depthFunc; + this.depthTest = source.depthTest; + this.depthWrite = source.depthWrite; + + this.stencilWriteMask = source.stencilWriteMask; + this.stencilFunc = source.stencilFunc; + this.stencilRef = source.stencilRef; + this.stencilFuncMask = source.stencilFuncMask; + this.stencilFail = source.stencilFail; + this.stencilZFail = source.stencilZFail; + this.stencilZPass = source.stencilZPass; + this.stencilWrite = source.stencilWrite; + + const srcPlanes = source.clippingPlanes; + let dstPlanes = null; + + if ( srcPlanes !== null ) { + + const n = srcPlanes.length; + dstPlanes = new Array( n ); + + for ( let i = 0; i !== n; ++ i ) { + + dstPlanes[ i ] = srcPlanes[ i ].clone(); + + } + + } + + this.clippingPlanes = dstPlanes; + this.clipIntersection = source.clipIntersection; + this.clipShadows = source.clipShadows; + + this.shadowSide = source.shadowSide; + + this.colorWrite = source.colorWrite; + + this.precision = source.precision; + + this.polygonOffset = source.polygonOffset; + this.polygonOffsetFactor = source.polygonOffsetFactor; + this.polygonOffsetUnits = source.polygonOffsetUnits; + + this.dithering = source.dithering; + + this.alphaTest = source.alphaTest; + this.alphaHash = source.alphaHash; + this.alphaToCoverage = source.alphaToCoverage; + this.premultipliedAlpha = source.premultipliedAlpha; + this.forceSinglePass = source.forceSinglePass; + + this.visible = source.visible; + + this.toneMapped = source.toneMapped; + + this.userData = JSON.parse( JSON.stringify( source.userData ) ); + + return this; + + } + + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + * + * @fires Material#dispose + */ + dispose() { + + /** + * Fires when the material has been disposed of. + * + * @event Material#dispose + * @type {Object} + */ + this.dispatchEvent( { type: 'dispose' } ); + + } + + /** + * Setting this property to `true` indicates the engine the material + * needs to be recompiled. + * + * @type {boolean} + * @default false + * @param {boolean} value + */ + set needsUpdate( value ) { + + if ( value === true ) this.version ++; + + } + +} + +/** + * A material for drawing geometries in a simple shaded (flat or wireframe) way. + * + * This material is not affected by lights. + * + * @augments Material + */ +class MeshBasicMaterial extends Material { + + /** + * Constructs a new mesh basic material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshBasicMaterial = true; + + this.type = 'MeshBasicMaterial'; + + /** + * Color of the material. + * + * @type {Color} + * @default (1,1,1) + */ + this.color = new Color( 0xffffff ); // emissive + + /** + * The color map. May optionally include an alpha channel, typically combined + * with {@link Material#transparent} or {@link Material#alphaTest}. The texture map + * color is modulated by the diffuse `color`. + * + * @type {?Texture} + * @default null + */ + this.map = null; + + /** + * The light map. Requires a second set of UVs. + * + * @type {?Texture} + * @default null + */ + this.lightMap = null; + + /** + * Intensity of the baked light. + * + * @type {number} + * @default 1 + */ + this.lightMapIntensity = 1.0; + + /** + * The red channel of this texture is used as the ambient occlusion map. + * Requires a second set of UVs. + * + * @type {?Texture} + * @default null + */ + this.aoMap = null; + + /** + * Intensity of the ambient occlusion effect. Range is `[0,1]`, where `0` + * disables ambient occlusion. Where intensity is `1` and the AO map's + * red channel is also `1`, ambient light is fully occluded on a surface. + * + * @type {number} + * @default 1 + */ + this.aoMapIntensity = 1.0; + + /** + * Specular map used by the material. + * + * @type {?Texture} + * @default null + */ + this.specularMap = null; + + /** + * The alpha map is a grayscale texture that controls the opacity across the + * surface (black: fully transparent; white: fully opaque). + * + * Only the color of the texture is used, ignoring the alpha channel if one + * exists. For RGB and RGBA textures, the renderer will use the green channel + * when sampling this texture due to the extra bit of precision provided for + * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and + * luminance/alpha textures will also still work as expected. + * + * @type {?Texture} + * @default null + */ + this.alphaMap = null; + + /** + * The environment map. + * + * @type {?Texture} + * @default null + */ + this.envMap = null; + + /** + * The rotation of the environment map in radians. + * + * @type {Euler} + * @default (0,0,0) + */ + this.envMapRotation = new Euler(); + + /** + * How to combine the result of the surface's color with the environment map, if any. + * + * When set to `MixOperation`, the {@link MeshBasicMaterial#reflectivity} is used to + * blend between the two colors. + * + * @type {(MultiplyOperation|MixOperation|AddOperation)} + * @default MultiplyOperation + */ + this.combine = MultiplyOperation; + + /** + * How much the environment map affects the surface. + * The valid range is between `0` (no reflections) and `1` (full reflections). + * + * @type {number} + * @default 1 + */ + this.reflectivity = 1; + + /** + * The index of refraction (IOR) of air (approximately 1) divided by the + * index of refraction of the material. It is used with environment mapping + * modes {@link CubeRefractionMapping} and {@link EquirectangularRefractionMapping}. + * The refraction ratio should not exceed `1`. + * + * @type {number} + * @default 0.98 + */ + this.refractionRatio = 0.98; + + /** + * Renders the geometry as a wireframe. + * + * @type {boolean} + * @default false + */ + this.wireframe = false; + + /** + * Controls the thickness of the wireframe. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {number} + * @default 1 + */ + this.wireframeLinewidth = 1; + + /** + * Defines appearance of wireframe ends. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ + this.wireframeLinecap = 'round'; + + /** + * Defines appearance of wireframe joints. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ + this.wireframeLinejoin = 'round'; + + /** + * Whether the material is affected by fog or not. + * + * @type {boolean} + * @default true + */ + this.fog = true; + + this.setValues( parameters ); + + } + + copy( source ) { + + super.copy( source ); + + this.color.copy( source.color ); + + this.map = source.map; + + this.lightMap = source.lightMap; + this.lightMapIntensity = source.lightMapIntensity; + + this.aoMap = source.aoMap; + this.aoMapIntensity = source.aoMapIntensity; + + this.specularMap = source.specularMap; + + this.alphaMap = source.alphaMap; + + this.envMap = source.envMap; + this.envMapRotation.copy( source.envMapRotation ); + this.combine = source.combine; + this.reflectivity = source.reflectivity; + this.refractionRatio = source.refractionRatio; + + this.wireframe = source.wireframe; + this.wireframeLinewidth = source.wireframeLinewidth; + this.wireframeLinecap = source.wireframeLinecap; + this.wireframeLinejoin = source.wireframeLinejoin; + + this.fog = source.fog; + + return this; + + } + +} + +// Fast Half Float Conversions, http://www.fox-toolkit.org/ftp/fasthalffloatconversion.pdf + +const _tables = /*@__PURE__*/ _generateTables(); + +function _generateTables() { + + // float32 to float16 helpers + + const buffer = new ArrayBuffer( 4 ); + const floatView = new Float32Array( buffer ); + const uint32View = new Uint32Array( buffer ); + + const baseTable = new Uint32Array( 512 ); + const shiftTable = new Uint32Array( 512 ); + + for ( let i = 0; i < 256; ++ i ) { + + const e = i - 127; + + // very small number (0, -0) + + if ( e < -27 ) { + + baseTable[ i ] = 0x0000; + baseTable[ i | 0x100 ] = 0x8000; + shiftTable[ i ] = 24; + shiftTable[ i | 0x100 ] = 24; + + // small number (denorm) + + } else if ( e < -14 ) { + + baseTable[ i ] = 0x0400 >> ( - e - 14 ); + baseTable[ i | 0x100 ] = ( 0x0400 >> ( - e - 14 ) ) | 0x8000; + shiftTable[ i ] = - e - 1; + shiftTable[ i | 0x100 ] = - e - 1; + + // normal number + + } else if ( e <= 15 ) { + + baseTable[ i ] = ( e + 15 ) << 10; + baseTable[ i | 0x100 ] = ( ( e + 15 ) << 10 ) | 0x8000; + shiftTable[ i ] = 13; + shiftTable[ i | 0x100 ] = 13; + + // large number (Infinity, -Infinity) + + } else if ( e < 128 ) { + + baseTable[ i ] = 0x7c00; + baseTable[ i | 0x100 ] = 0xfc00; + shiftTable[ i ] = 24; + shiftTable[ i | 0x100 ] = 24; + + // stay (NaN, Infinity, -Infinity) + + } else { + + baseTable[ i ] = 0x7c00; + baseTable[ i | 0x100 ] = 0xfc00; + shiftTable[ i ] = 13; + shiftTable[ i | 0x100 ] = 13; + + } + + } + + // float16 to float32 helpers + + const mantissaTable = new Uint32Array( 2048 ); + const exponentTable = new Uint32Array( 64 ); + const offsetTable = new Uint32Array( 64 ); + + for ( let i = 1; i < 1024; ++ i ) { + + let m = i << 13; // zero pad mantissa bits + let e = 0; // zero exponent + + // normalized + while ( ( m & 0x00800000 ) === 0 ) { + + m <<= 1; + e -= 0x00800000; // decrement exponent + + } + + m &= -8388609; // clear leading 1 bit + e += 0x38800000; // adjust bias + + mantissaTable[ i ] = m | e; + + } + + for ( let i = 1024; i < 2048; ++ i ) { + + mantissaTable[ i ] = 0x38000000 + ( ( i - 1024 ) << 13 ); + + } + + for ( let i = 1; i < 31; ++ i ) { + + exponentTable[ i ] = i << 23; + + } + + exponentTable[ 31 ] = 0x47800000; + exponentTable[ 32 ] = 0x80000000; + + for ( let i = 33; i < 63; ++ i ) { + + exponentTable[ i ] = 0x80000000 + ( ( i - 32 ) << 23 ); + + } + + exponentTable[ 63 ] = 0xc7800000; + + for ( let i = 1; i < 64; ++ i ) { + + if ( i !== 32 ) { + + offsetTable[ i ] = 1024; + + } + + } + + return { + floatView: floatView, + uint32View: uint32View, + baseTable: baseTable, + shiftTable: shiftTable, + mantissaTable: mantissaTable, + exponentTable: exponentTable, + offsetTable: offsetTable + }; + +} + +/** + * Returns a half precision floating point value (FP16) from the given single + * precision floating point value (FP32). + * + * @param {number} val - A single precision floating point value. + * @return {number} The FP16 value. + */ +function toHalfFloat( val ) { + + if ( Math.abs( val ) > 65504 ) console.warn( 'THREE.DataUtils.toHalfFloat(): Value out of range.' ); + + val = clamp( val, -65504, 65504 ); + + _tables.floatView[ 0 ] = val; + const f = _tables.uint32View[ 0 ]; + const e = ( f >> 23 ) & 0x1ff; + return _tables.baseTable[ e ] + ( ( f & 0x007fffff ) >> _tables.shiftTable[ e ] ); + +} + +/** + * Returns a single precision floating point value (FP32) from the given half + * precision floating point value (FP16). + * + * @param {number} val - A half precision floating point value. + * @return {number} The FP32 value. + */ +function fromHalfFloat( val ) { + + const m = val >> 10; + _tables.uint32View[ 0 ] = _tables.mantissaTable[ _tables.offsetTable[ m ] + ( val & 0x3ff ) ] + _tables.exponentTable[ m ]; + return _tables.floatView[ 0 ]; + +} + +/** + * A class containing utility functions for data. + * + * @hideconstructor + */ +class DataUtils { + + /** + * Returns a half precision floating point value (FP16) from the given single + * precision floating point value (FP32). + * + * @param {number} val - A single precision floating point value. + * @return {number} The FP16 value. + */ + static toHalfFloat( val ) { + + return toHalfFloat( val ); + + } + + /** + * Returns a single precision floating point value (FP32) from the given half + * precision floating point value (FP16). + * + * @param {number} val - A half precision floating point value. + * @return {number} The FP32 value. + */ + static fromHalfFloat( val ) { + + return fromHalfFloat( val ); + + } + +} + +const _vector$9 = /*@__PURE__*/ new Vector3(); +const _vector2$1 = /*@__PURE__*/ new Vector2(); + +let _id$2 = 0; + +/** + * This class stores data for an attribute (such as vertex positions, face + * indices, normals, colors, UVs, and any custom attributes ) associated with + * a geometry, which allows for more efficient passing of data to the GPU. + * + * When working with vector-like data, the `fromBufferAttribute( attribute, index )` + * helper methods on vector and color class might be helpful. E.g. {@link Vector3#fromBufferAttribute}. + */ +class BufferAttribute { + + /** + * Constructs a new buffer attribute. + * + * @param {TypedArray} array - The array holding the attribute data. + * @param {number} itemSize - The item size. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + */ + constructor( array, itemSize, normalized = false ) { + + if ( Array.isArray( array ) ) { + + throw new TypeError( 'THREE.BufferAttribute: array should be a Typed Array.' ); + + } + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isBufferAttribute = true; + + /** + * The ID of the buffer attribute. + * + * @name BufferAttribute#id + * @type {number} + * @readonly + */ + Object.defineProperty( this, 'id', { value: _id$2 ++ } ); + + /** + * The name of the buffer attribute. + * + * @type {string} + */ + this.name = ''; + + /** + * The array holding the attribute data. It should have `itemSize * numVertices` + * elements, where `numVertices` is the number of vertices in the associated geometry. + * + * @type {TypedArray} + */ + this.array = array; + + /** + * The number of values of the array that should be associated with a particular vertex. + * For instance, if this attribute is storing a 3-component vector (such as a position, + * normal, or color), then the value should be `3`. + * + * @type {number} + */ + this.itemSize = itemSize; + + /** + * Represents the number of items this buffer attribute stores. It is internally computed + * by dividing the `array` length by the `itemSize`. + * + * @type {number} + * @readonly + */ + this.count = array !== undefined ? array.length / itemSize : 0; + + /** + * Applies to integer data only. Indicates how the underlying data in the buffer maps to + * the values in the GLSL code. For instance, if `array` is an instance of `UInt16Array`, + * and `normalized` is `true`, the values `0 - +65535` in the array data will be mapped to + * `0.0f - +1.0f` in the GLSL attribute. If `normalized` is `false`, the values will be converted + * to floats unmodified, i.e. `65535` becomes `65535.0f`. + * + * @type {boolean} + */ + this.normalized = normalized; + + /** + * Defines the intended usage pattern of the data store for optimization purposes. + * + * Note: After the initial use of a buffer, its usage cannot be changed. Instead, + * instantiate a new one and set the desired usage before the next render. + * + * @type {(StaticDrawUsage|DynamicDrawUsage|StreamDrawUsage|StaticReadUsage|DynamicReadUsage|StreamReadUsage|StaticCopyUsage|DynamicCopyUsage|StreamCopyUsage)} + * @default StaticDrawUsage + */ + this.usage = StaticDrawUsage; + + /** + * This can be used to only update some components of stored vectors (for example, just the + * component related to color). Use the `addUpdateRange()` function to add ranges to this array. + * + * @type {Array} + */ + this.updateRanges = []; + + /** + * Configures the bound GPU type for use in shaders. + * + * Note: this only has an effect for integer arrays and is not configurable for float arrays. + * For lower precision float types, use `Float16BufferAttribute`. + * + * @type {(FloatType|IntType)} + * @default FloatType + */ + this.gpuType = FloatType; + + /** + * A version number, incremented every time the `needsUpdate` is set to `true`. + * + * @type {number} + */ + this.version = 0; + + } + + /** + * A callback function that is executed after the renderer has transferred the attribute + * array data to the GPU. + */ + onUploadCallback() {} + + /** + * Flag to indicate that this attribute has changed and should be re-sent to + * the GPU. Set this to `true` when you modify the value of the array. + * + * @type {number} + * @default false + * @param {boolean} value + */ + set needsUpdate( value ) { + + if ( value === true ) this.version ++; + + } + + /** + * Sets the usage of this buffer attribute. + * + * @param {(StaticDrawUsage|DynamicDrawUsage|StreamDrawUsage|StaticReadUsage|DynamicReadUsage|StreamReadUsage|StaticCopyUsage|DynamicCopyUsage|StreamCopyUsage)} value - The usage to set. + * @return {BufferAttribute} A reference to this buffer attribute. + */ + setUsage( value ) { + + this.usage = value; + + return this; + + } + + /** + * Adds a range of data in the data array to be updated on the GPU. + * + * @param {number} start - Position at which to start update. + * @param {number} count - The number of components to update. + */ + addUpdateRange( start, count ) { + + this.updateRanges.push( { start, count } ); + + } + + /** + * Clears the update ranges. + */ + clearUpdateRanges() { + + this.updateRanges.length = 0; + + } + + /** + * Copies the values of the given buffer attribute to this instance. + * + * @param {BufferAttribute} source - The buffer attribute to copy. + * @return {BufferAttribute} A reference to this instance. + */ + copy( source ) { + + this.name = source.name; + this.array = new source.array.constructor( source.array ); + this.itemSize = source.itemSize; + this.count = source.count; + this.normalized = source.normalized; + + this.usage = source.usage; + this.gpuType = source.gpuType; + + return this; + + } + + /** + * Copies a vector from the given buffer attribute to this one. The start + * and destination position in the attribute buffers are represented by the + * given indices. + * + * @param {number} index1 - The destination index into this buffer attribute. + * @param {BufferAttribute} attribute - The buffer attribute to copy from. + * @param {number} index2 - The source index into the given buffer attribute. + * @return {BufferAttribute} A reference to this instance. + */ + copyAt( index1, attribute, index2 ) { + + index1 *= this.itemSize; + index2 *= attribute.itemSize; + + for ( let i = 0, l = this.itemSize; i < l; i ++ ) { + + this.array[ index1 + i ] = attribute.array[ index2 + i ]; + + } + + return this; + + } + + /** + * Copies the given array data into this buffer attribute. + * + * @param {(TypedArray|Array)} array - The array to copy. + * @return {BufferAttribute} A reference to this instance. + */ + copyArray( array ) { + + this.array.set( array ); + + return this; + + } + + /** + * Applies the given 3x3 matrix to the given attribute. Works with + * item size `2` and `3`. + * + * @param {Matrix3} m - The matrix to apply. + * @return {BufferAttribute} A reference to this instance. + */ + applyMatrix3( m ) { + + if ( this.itemSize === 2 ) { + + for ( let i = 0, l = this.count; i < l; i ++ ) { + + _vector2$1.fromBufferAttribute( this, i ); + _vector2$1.applyMatrix3( m ); + + this.setXY( i, _vector2$1.x, _vector2$1.y ); + + } + + } else if ( this.itemSize === 3 ) { + + for ( let i = 0, l = this.count; i < l; i ++ ) { + + _vector$9.fromBufferAttribute( this, i ); + _vector$9.applyMatrix3( m ); + + this.setXYZ( i, _vector$9.x, _vector$9.y, _vector$9.z ); + + } + + } + + return this; + + } + + /** + * Applies the given 4x4 matrix to the given attribute. Only works with + * item size `3`. + * + * @param {Matrix4} m - The matrix to apply. + * @return {BufferAttribute} A reference to this instance. + */ + applyMatrix4( m ) { + + for ( let i = 0, l = this.count; i < l; i ++ ) { + + _vector$9.fromBufferAttribute( this, i ); + + _vector$9.applyMatrix4( m ); + + this.setXYZ( i, _vector$9.x, _vector$9.y, _vector$9.z ); + + } + + return this; + + } + + /** + * Applies the given 3x3 normal matrix to the given attribute. Only works with + * item size `3`. + * + * @param {Matrix3} m - The normal matrix to apply. + * @return {BufferAttribute} A reference to this instance. + */ + applyNormalMatrix( m ) { + + for ( let i = 0, l = this.count; i < l; i ++ ) { + + _vector$9.fromBufferAttribute( this, i ); + + _vector$9.applyNormalMatrix( m ); + + this.setXYZ( i, _vector$9.x, _vector$9.y, _vector$9.z ); + + } + + return this; + + } + + /** + * Applies the given 4x4 matrix to the given attribute. Only works with + * item size `3` and with direction vectors. + * + * @param {Matrix4} m - The matrix to apply. + * @return {BufferAttribute} A reference to this instance. + */ + transformDirection( m ) { + + for ( let i = 0, l = this.count; i < l; i ++ ) { + + _vector$9.fromBufferAttribute( this, i ); + + _vector$9.transformDirection( m ); + + this.setXYZ( i, _vector$9.x, _vector$9.y, _vector$9.z ); + + } + + return this; + + } + + /** + * Sets the given array data in the buffer attribute. + * + * @param {(TypedArray|Array)} value - The array data to set. + * @param {number} [offset=0] - The offset in this buffer attribute's array. + * @return {BufferAttribute} A reference to this instance. + */ + set( value, offset = 0 ) { + + // Matching BufferAttribute constructor, do not normalize the array. + this.array.set( value, offset ); + + return this; + + } + + /** + * Returns the given component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} component - The component index. + * @return {number} The returned value. + */ + getComponent( index, component ) { + + let value = this.array[ index * this.itemSize + component ]; + + if ( this.normalized ) value = denormalize( value, this.array ); + + return value; + + } + + /** + * Sets the given value to the given component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} component - The component index. + * @param {number} value - The value to set. + * @return {BufferAttribute} A reference to this instance. + */ + setComponent( index, component, value ) { + + if ( this.normalized ) value = normalize( value, this.array ); + + this.array[ index * this.itemSize + component ] = value; + + return this; + + } + + /** + * Returns the x component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @return {number} The x component. + */ + getX( index ) { + + let x = this.array[ index * this.itemSize ]; + + if ( this.normalized ) x = denormalize( x, this.array ); + + return x; + + } + + /** + * Sets the x component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} x - The value to set. + * @return {BufferAttribute} A reference to this instance. + */ + setX( index, x ) { + + if ( this.normalized ) x = normalize( x, this.array ); + + this.array[ index * this.itemSize ] = x; + + return this; + + } + + /** + * Returns the y component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @return {number} The y component. + */ + getY( index ) { + + let y = this.array[ index * this.itemSize + 1 ]; + + if ( this.normalized ) y = denormalize( y, this.array ); + + return y; + + } + + /** + * Sets the y component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} y - The value to set. + * @return {BufferAttribute} A reference to this instance. + */ + setY( index, y ) { + + if ( this.normalized ) y = normalize( y, this.array ); + + this.array[ index * this.itemSize + 1 ] = y; + + return this; + + } + + /** + * Returns the z component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @return {number} The z component. + */ + getZ( index ) { + + let z = this.array[ index * this.itemSize + 2 ]; + + if ( this.normalized ) z = denormalize( z, this.array ); + + return z; + + } + + /** + * Sets the z component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} z - The value to set. + * @return {BufferAttribute} A reference to this instance. + */ + setZ( index, z ) { + + if ( this.normalized ) z = normalize( z, this.array ); + + this.array[ index * this.itemSize + 2 ] = z; + + return this; + + } + + /** + * Returns the w component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @return {number} The w component. + */ + getW( index ) { + + let w = this.array[ index * this.itemSize + 3 ]; + + if ( this.normalized ) w = denormalize( w, this.array ); + + return w; + + } + + /** + * Sets the w component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} w - The value to set. + * @return {BufferAttribute} A reference to this instance. + */ + setW( index, w ) { + + if ( this.normalized ) w = normalize( w, this.array ); + + this.array[ index * this.itemSize + 3 ] = w; + + return this; + + } + + /** + * Sets the x and y component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} x - The value for the x component to set. + * @param {number} y - The value for the y component to set. + * @return {BufferAttribute} A reference to this instance. + */ + setXY( index, x, y ) { + + index *= this.itemSize; + + if ( this.normalized ) { + + x = normalize( x, this.array ); + y = normalize( y, this.array ); + + } + + this.array[ index + 0 ] = x; + this.array[ index + 1 ] = y; + + return this; + + } + + /** + * Sets the x, y and z component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} x - The value for the x component to set. + * @param {number} y - The value for the y component to set. + * @param {number} z - The value for the z component to set. + * @return {BufferAttribute} A reference to this instance. + */ + setXYZ( index, x, y, z ) { + + index *= this.itemSize; + + if ( this.normalized ) { + + x = normalize( x, this.array ); + y = normalize( y, this.array ); + z = normalize( z, this.array ); + + } + + this.array[ index + 0 ] = x; + this.array[ index + 1 ] = y; + this.array[ index + 2 ] = z; + + return this; + + } + + /** + * Sets the x, y, z and w component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} x - The value for the x component to set. + * @param {number} y - The value for the y component to set. + * @param {number} z - The value for the z component to set. + * @param {number} w - The value for the w component to set. + * @return {BufferAttribute} A reference to this instance. + */ + setXYZW( index, x, y, z, w ) { + + index *= this.itemSize; + + if ( this.normalized ) { + + x = normalize( x, this.array ); + y = normalize( y, this.array ); + z = normalize( z, this.array ); + w = normalize( w, this.array ); + + } + + this.array[ index + 0 ] = x; + this.array[ index + 1 ] = y; + this.array[ index + 2 ] = z; + this.array[ index + 3 ] = w; + + return this; + + } + + /** + * Sets the given callback function that is executed after the Renderer has transferred + * the attribute array data to the GPU. Can be used to perform clean-up operations after + * the upload when attribute data are not needed anymore on the CPU side. + * + * @param {Function} callback - The `onUpload()` callback. + * @return {BufferAttribute} A reference to this instance. + */ + onUpload( callback ) { + + this.onUploadCallback = callback; + + return this; + + } + + /** + * Returns a new buffer attribute with copied values from this instance. + * + * @return {BufferAttribute} A clone of this instance. + */ + clone() { + + return new this.constructor( this.array, this.itemSize ).copy( this ); + + } + + /** + * Serializes the buffer attribute into JSON. + * + * @return {Object} A JSON object representing the serialized buffer attribute. + */ + toJSON() { + + const data = { + itemSize: this.itemSize, + type: this.array.constructor.name, + array: Array.from( this.array ), + normalized: this.normalized + }; + + if ( this.name !== '' ) data.name = this.name; + if ( this.usage !== StaticDrawUsage ) data.usage = this.usage; + + return data; + + } + +} + +/** + * Convenient class that can be used when creating a `Int8` buffer attribute with + * a plain `Array` instance. + * + * @augments BufferAttribute + */ +class Int8BufferAttribute extends BufferAttribute { + + /** + * Constructs a new buffer attribute. + * + * @param {(Array|Int8Array)} array - The array holding the attribute data. + * @param {number} itemSize - The item size. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + */ + constructor( array, itemSize, normalized ) { + + super( new Int8Array( array ), itemSize, normalized ); + + } + +} + +/** + * Convenient class that can be used when creating a `UInt8` buffer attribute with + * a plain `Array` instance. + * + * @augments BufferAttribute + */ +class Uint8BufferAttribute extends BufferAttribute { + + /** + * Constructs a new buffer attribute. + * + * @param {(Array|Uint8Array)} array - The array holding the attribute data. + * @param {number} itemSize - The item size. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + */ + constructor( array, itemSize, normalized ) { + + super( new Uint8Array( array ), itemSize, normalized ); + + } + +} + +/** + * Convenient class that can be used when creating a `UInt8Clamped` buffer attribute with + * a plain `Array` instance. + * + * @augments BufferAttribute + */ +class Uint8ClampedBufferAttribute extends BufferAttribute { + + /** + * Constructs a new buffer attribute. + * + * @param {(Array|Uint8ClampedArray)} array - The array holding the attribute data. + * @param {number} itemSize - The item size. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + */ + constructor( array, itemSize, normalized ) { + + super( new Uint8ClampedArray( array ), itemSize, normalized ); + + } + +} + +/** + * Convenient class that can be used when creating a `Int16` buffer attribute with + * a plain `Array` instance. + * + * @augments BufferAttribute + */ +class Int16BufferAttribute extends BufferAttribute { + + /** + * Constructs a new buffer attribute. + * + * @param {(Array|Int16Array)} array - The array holding the attribute data. + * @param {number} itemSize - The item size. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + */ + constructor( array, itemSize, normalized ) { + + super( new Int16Array( array ), itemSize, normalized ); + + } + +} + +/** + * Convenient class that can be used when creating a `UInt16` buffer attribute with + * a plain `Array` instance. + * + * @augments BufferAttribute + */ +class Uint16BufferAttribute extends BufferAttribute { + + /** + * Constructs a new buffer attribute. + * + * @param {(Array|Uint16Array)} array - The array holding the attribute data. + * @param {number} itemSize - The item size. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + */ + constructor( array, itemSize, normalized ) { + + super( new Uint16Array( array ), itemSize, normalized ); + + } + +} + +/** + * Convenient class that can be used when creating a `Int32` buffer attribute with + * a plain `Array` instance. + * + * @augments BufferAttribute + */ +class Int32BufferAttribute extends BufferAttribute { + + /** + * Constructs a new buffer attribute. + * + * @param {(Array|Int32Array)} array - The array holding the attribute data. + * @param {number} itemSize - The item size. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + */ + constructor( array, itemSize, normalized ) { + + super( new Int32Array( array ), itemSize, normalized ); + + } + +} + +/** + * Convenient class that can be used when creating a `UInt32` buffer attribute with + * a plain `Array` instance. + * + * @augments BufferAttribute + */ +class Uint32BufferAttribute extends BufferAttribute { + + /** + * Constructs a new buffer attribute. + * + * @param {(Array|Uint32Array)} array - The array holding the attribute data. + * @param {number} itemSize - The item size. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + */ + constructor( array, itemSize, normalized ) { + + super( new Uint32Array( array ), itemSize, normalized ); + + } + +} + +/** + * Convenient class that can be used when creating a `Float16` buffer attribute with + * a plain `Array` instance. + * + * This class automatically converts to and from FP16 via `Uint16Array` since `Float16Array` + * browser support is still problematic. + * + * @augments BufferAttribute + */ +class Float16BufferAttribute extends BufferAttribute { + + /** + * Constructs a new buffer attribute. + * + * @param {(Array|Uint16Array)} array - The array holding the attribute data. + * @param {number} itemSize - The item size. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + */ + constructor( array, itemSize, normalized ) { + + super( new Uint16Array( array ), itemSize, normalized ); + + this.isFloat16BufferAttribute = true; + + } + + getX( index ) { + + let x = fromHalfFloat( this.array[ index * this.itemSize ] ); + + if ( this.normalized ) x = denormalize( x, this.array ); + + return x; + + } + + setX( index, x ) { + + if ( this.normalized ) x = normalize( x, this.array ); + + this.array[ index * this.itemSize ] = toHalfFloat( x ); + + return this; + + } + + getY( index ) { + + let y = fromHalfFloat( this.array[ index * this.itemSize + 1 ] ); + + if ( this.normalized ) y = denormalize( y, this.array ); + + return y; + + } + + setY( index, y ) { + + if ( this.normalized ) y = normalize( y, this.array ); + + this.array[ index * this.itemSize + 1 ] = toHalfFloat( y ); + + return this; + + } + + getZ( index ) { + + let z = fromHalfFloat( this.array[ index * this.itemSize + 2 ] ); + + if ( this.normalized ) z = denormalize( z, this.array ); + + return z; + + } + + setZ( index, z ) { + + if ( this.normalized ) z = normalize( z, this.array ); + + this.array[ index * this.itemSize + 2 ] = toHalfFloat( z ); + + return this; + + } + + getW( index ) { + + let w = fromHalfFloat( this.array[ index * this.itemSize + 3 ] ); + + if ( this.normalized ) w = denormalize( w, this.array ); + + return w; + + } + + setW( index, w ) { + + if ( this.normalized ) w = normalize( w, this.array ); + + this.array[ index * this.itemSize + 3 ] = toHalfFloat( w ); + + return this; + + } + + setXY( index, x, y ) { + + index *= this.itemSize; + + if ( this.normalized ) { + + x = normalize( x, this.array ); + y = normalize( y, this.array ); + + } + + this.array[ index + 0 ] = toHalfFloat( x ); + this.array[ index + 1 ] = toHalfFloat( y ); + + return this; + + } + + setXYZ( index, x, y, z ) { + + index *= this.itemSize; + + if ( this.normalized ) { + + x = normalize( x, this.array ); + y = normalize( y, this.array ); + z = normalize( z, this.array ); + + } + + this.array[ index + 0 ] = toHalfFloat( x ); + this.array[ index + 1 ] = toHalfFloat( y ); + this.array[ index + 2 ] = toHalfFloat( z ); + + return this; + + } + + setXYZW( index, x, y, z, w ) { + + index *= this.itemSize; + + if ( this.normalized ) { + + x = normalize( x, this.array ); + y = normalize( y, this.array ); + z = normalize( z, this.array ); + w = normalize( w, this.array ); + + } + + this.array[ index + 0 ] = toHalfFloat( x ); + this.array[ index + 1 ] = toHalfFloat( y ); + this.array[ index + 2 ] = toHalfFloat( z ); + this.array[ index + 3 ] = toHalfFloat( w ); + + return this; + + } + +} + +/** + * Convenient class that can be used when creating a `Float32` buffer attribute with + * a plain `Array` instance. + * + * @augments BufferAttribute + */ +class Float32BufferAttribute extends BufferAttribute { + + /** + * Constructs a new buffer attribute. + * + * @param {(Array|Float32Array)} array - The array holding the attribute data. + * @param {number} itemSize - The item size. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + */ + constructor( array, itemSize, normalized ) { + + super( new Float32Array( array ), itemSize, normalized ); + + } + +} + +let _id$1 = 0; + +const _m1 = /*@__PURE__*/ new Matrix4(); +const _obj = /*@__PURE__*/ new Object3D(); +const _offset = /*@__PURE__*/ new Vector3(); +const _box$2 = /*@__PURE__*/ new Box3(); +const _boxMorphTargets = /*@__PURE__*/ new Box3(); +const _vector$8 = /*@__PURE__*/ new Vector3(); + +/** + * A representation of mesh, line, or point geometry. Includes vertex + * positions, face indices, normals, colors, UVs, and custom attributes + * within buffers, reducing the cost of passing all this data to the GPU. + * + * ```js + * const geometry = new THREE.BufferGeometry(); + * // create a simple square shape. We duplicate the top left and bottom right + * // vertices because each vertex needs to appear once per triangle. + * const vertices = new Float32Array( [ + * -1.0, -1.0, 1.0, // v0 + * 1.0, -1.0, 1.0, // v1 + * 1.0, 1.0, 1.0, // v2 + * + * 1.0, 1.0, 1.0, // v3 + * -1.0, 1.0, 1.0, // v4 + * -1.0, -1.0, 1.0 // v5 + * ] ); + * // itemSize = 3 because there are 3 values (components) per vertex + * geometry.setAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) ); + * const material = new THREE.MeshBasicMaterial( { color: 0xff0000 } ); + * const mesh = new THREE.Mesh( geometry, material ); + * ``` + * + * @augments EventDispatcher + */ +class BufferGeometry extends EventDispatcher { + + /** + * Constructs a new geometry. + */ + constructor() { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isBufferGeometry = true; + + /** + * The ID of the geometry. + * + * @name BufferGeometry#id + * @type {number} + * @readonly + */ + Object.defineProperty( this, 'id', { value: _id$1 ++ } ); + + /** + * The UUID of the geometry. + * + * @type {string} + * @readonly + */ + this.uuid = generateUUID(); + + /** + * The name of the geometry. + * + * @type {string} + */ + this.name = ''; + this.type = 'BufferGeometry'; + + /** + * Allows for vertices to be re-used across multiple triangles; this is + * called using "indexed triangles". Each triangle is associated with the + * indices of three vertices. This attribute therefore stores the index of + * each vertex for each triangular face. If this attribute is not set, the + * renderer assumes that each three contiguous positions represent a single triangle. + * + * @type {?BufferAttribute} + * @default null + */ + this.index = null; + + /** + * A (storage) buffer attribute which was generated with a compute shader and + * now defines indirect draw calls. + * + * Can only be used with {@link WebGPURenderer} and a WebGPU backend. + * + * @type {?BufferAttribute} + * @default null + */ + this.indirect = null; + + /** + * This dictionary has as id the name of the attribute to be set and as value + * the buffer attribute to set it to. Rather than accessing this property directly, + * use `setAttribute()` and `getAttribute()` to access attributes of this geometry. + * + * @type {Object} + */ + this.attributes = {}; + + /** + * This dictionary holds the morph targets of the geometry. + * + * Note: Once the geometry has been rendered, the morph attribute data cannot + * be changed. You will have to call `dispose()?, and create a new geometry instance. + * + * @type {Object} + */ + this.morphAttributes = {}; + + /** + * Used to control the morph target behavior; when set to `true`, the morph + * target data is treated as relative offsets, rather than as absolute + * positions/normals. + * + * @type {boolean} + * @default false + */ + this.morphTargetsRelative = false; + + /** + * Split the geometry into groups, each of which will be rendered in a + * separate draw call. This allows an array of materials to be used with the geometry. + * + * Use `addGroup()` and `clearGroups()` to edit groups, rather than modifying this array directly. + * + * Every vertex and index must belong to exactly one group — groups must not share vertices or + * indices, and must not leave vertices or indices unused. + * + * @type {Array} + */ + this.groups = []; + + /** + * Bounding box for the geometry which can be calculated with `computeBoundingBox()`. + * + * @type {Box3} + * @default null + */ + this.boundingBox = null; + + /** + * Bounding sphere for the geometry which can be calculated with `computeBoundingSphere()`. + * + * @type {Sphere} + * @default null + */ + this.boundingSphere = null; + + /** + * Determines the part of the geometry to render. This should not be set directly, + * instead use `setDrawRange()`. + * + * @type {{start:number,count:number}} + */ + this.drawRange = { start: 0, count: Infinity }; + + /** + * An object that can be used to store custom data about the geometry. + * It should not hold references to functions as these will not be cloned. + * + * @type {Object} + */ + this.userData = {}; + + } + + /** + * Returns the index of this geometry. + * + * @return {?BufferAttribute} The index. Returns `null` if no index is defined. + */ + getIndex() { + + return this.index; + + } + + /** + * Sets the given index to this geometry. + * + * @param {Array|BufferAttribute} index - The index to set. + * @return {BufferGeometry} A reference to this instance. + */ + setIndex( index ) { + + if ( Array.isArray( index ) ) { + + this.index = new ( arrayNeedsUint32( index ) ? Uint32BufferAttribute : Uint16BufferAttribute )( index, 1 ); + + } else { + + this.index = index; + + } + + return this; + + } + + /** + * Sets the given indirect attribute to this geometry. + * + * @param {BufferAttribute} indirect - The attribute holding indirect draw calls. + * @return {BufferGeometry} A reference to this instance. + */ + setIndirect( indirect ) { + + this.indirect = indirect; + + return this; + + } + + /** + * Returns the indirect attribute of this geometry. + * + * @return {?BufferAttribute} The indirect attribute. Returns `null` if no indirect attribute is defined. + */ + getIndirect() { + + return this.indirect; + + } + + /** + * Returns the buffer attribute for the given name. + * + * @param {string} name - The attribute name. + * @return {BufferAttribute|InterleavedBufferAttribute|undefined} The buffer attribute. + * Returns `undefined` if not attribute has been found. + */ + getAttribute( name ) { + + return this.attributes[ name ]; + + } + + /** + * Sets the given attribute for the given name. + * + * @param {string} name - The attribute name. + * @param {BufferAttribute|InterleavedBufferAttribute} attribute - The attribute to set. + * @return {BufferGeometry} A reference to this instance. + */ + setAttribute( name, attribute ) { + + this.attributes[ name ] = attribute; + + return this; + + } + + /** + * Deletes the attribute for the given name. + * + * @param {string} name - The attribute name to delete. + * @return {BufferGeometry} A reference to this instance. + */ + deleteAttribute( name ) { + + delete this.attributes[ name ]; + + return this; + + } + + /** + * Returns `true` if this geometry has an attribute for the given name. + * + * @param {string} name - The attribute name. + * @return {boolean} Whether this geometry has an attribute for the given name or not. + */ + hasAttribute( name ) { + + return this.attributes[ name ] !== undefined; + + } + + /** + * Adds a group to this geometry. + * + * @param {number} start - The first element in this draw call. That is the first + * vertex for non-indexed geometry, otherwise the first triangle index. + * @param {number} count - Specifies how many vertices (or indices) are part of this group. + * @param {number} [materialIndex=0] - The material array index to use. + */ + addGroup( start, count, materialIndex = 0 ) { + + this.groups.push( { + + start: start, + count: count, + materialIndex: materialIndex + + } ); + + } + + /** + * Clears all groups. + */ + clearGroups() { + + this.groups = []; + + } + + /** + * Sets the draw range for this geometry. + * + * @param {number} start - The first vertex for non-indexed geometry, otherwise the first triangle index. + * @param {number} count - For non-indexed BufferGeometry, `count` is the number of vertices to render. + * For indexed BufferGeometry, `count` is the number of indices to render. + */ + setDrawRange( start, count ) { + + this.drawRange.start = start; + this.drawRange.count = count; + + } + + /** + * Applies the given 4x4 transformation matrix to the geometry. + * + * @param {Matrix4} matrix - The matrix to apply. + * @return {BufferGeometry} A reference to this instance. + */ + applyMatrix4( matrix ) { + + const position = this.attributes.position; + + if ( position !== undefined ) { + + position.applyMatrix4( matrix ); + + position.needsUpdate = true; + + } + + const normal = this.attributes.normal; + + if ( normal !== undefined ) { + + const normalMatrix = new Matrix3().getNormalMatrix( matrix ); + + normal.applyNormalMatrix( normalMatrix ); + + normal.needsUpdate = true; + + } + + const tangent = this.attributes.tangent; + + if ( tangent !== undefined ) { + + tangent.transformDirection( matrix ); + + tangent.needsUpdate = true; + + } + + if ( this.boundingBox !== null ) { + + this.computeBoundingBox(); + + } + + if ( this.boundingSphere !== null ) { + + this.computeBoundingSphere(); + + } + + return this; + + } + + /** + * Applies the rotation represented by the Quaternion to the geometry. + * + * @param {Quaternion} q - The Quaternion to apply. + * @return {BufferGeometry} A reference to this instance. + */ + applyQuaternion( q ) { + + _m1.makeRotationFromQuaternion( q ); + + this.applyMatrix4( _m1 ); + + return this; + + } + + /** + * Rotates the geometry about the X axis. This is typically done as a one time + * operation, and not during a loop. Use {@link Object3D#rotation} for typical + * real-time mesh rotation. + * + * @param {number} angle - The angle in radians. + * @return {BufferGeometry} A reference to this instance. + */ + rotateX( angle ) { + + // rotate geometry around world x-axis + + _m1.makeRotationX( angle ); + + this.applyMatrix4( _m1 ); + + return this; + + } + + /** + * Rotates the geometry about the Y axis. This is typically done as a one time + * operation, and not during a loop. Use {@link Object3D#rotation} for typical + * real-time mesh rotation. + * + * @param {number} angle - The angle in radians. + * @return {BufferGeometry} A reference to this instance. + */ + rotateY( angle ) { + + // rotate geometry around world y-axis + + _m1.makeRotationY( angle ); + + this.applyMatrix4( _m1 ); + + return this; + + } + + /** + * Rotates the geometry about the Z axis. This is typically done as a one time + * operation, and not during a loop. Use {@link Object3D#rotation} for typical + * real-time mesh rotation. + * + * @param {number} angle - The angle in radians. + * @return {BufferGeometry} A reference to this instance. + */ + rotateZ( angle ) { + + // rotate geometry around world z-axis + + _m1.makeRotationZ( angle ); + + this.applyMatrix4( _m1 ); + + return this; + + } + + /** + * Translates the geometry. This is typically done as a one time + * operation, and not during a loop. Use {@link Object3D#position} for typical + * real-time mesh rotation. + * + * @param {number} x - The x offset. + * @param {number} y - The y offset. + * @param {number} z - The z offset. + * @return {BufferGeometry} A reference to this instance. + */ + translate( x, y, z ) { + + // translate geometry + + _m1.makeTranslation( x, y, z ); + + this.applyMatrix4( _m1 ); + + return this; + + } + + /** + * Scales the geometry. This is typically done as a one time + * operation, and not during a loop. Use {@link Object3D#scale} for typical + * real-time mesh rotation. + * + * @param {number} x - The x scale. + * @param {number} y - The y scale. + * @param {number} z - The z scale. + * @return {BufferGeometry} A reference to this instance. + */ + scale( x, y, z ) { + + // scale geometry + + _m1.makeScale( x, y, z ); + + this.applyMatrix4( _m1 ); + + return this; + + } + + /** + * Rotates the geometry to face a point in 3D space. This is typically done as a one time + * operation, and not during a loop. Use {@link Object3D#lookAt} for typical + * real-time mesh rotation. + * + * @param {Vector3} vector - The target point. + * @return {BufferGeometry} A reference to this instance. + */ + lookAt( vector ) { + + _obj.lookAt( vector ); + + _obj.updateMatrix(); + + this.applyMatrix4( _obj.matrix ); + + return this; + + } + + /** + * Center the geometry based on its bounding box. + * + * @return {BufferGeometry} A reference to this instance. + */ + center() { + + this.computeBoundingBox(); + + this.boundingBox.getCenter( _offset ).negate(); + + this.translate( _offset.x, _offset.y, _offset.z ); + + return this; + + } + + /** + * Defines a geometry by creating a `position` attribute based on the given array of points. The array + * can hold 2D or 3D vectors. When using two-dimensional data, the `z` coordinate for all vertices is + * set to `0`. + * + * If the method is used with an existing `position` attribute, the vertex data are overwritten with the + * data from the array. The length of the array must match the vertex count. + * + * @param {Array|Array} points - The points. + * @return {BufferGeometry} A reference to this instance. + */ + setFromPoints( points ) { + + const positionAttribute = this.getAttribute( 'position' ); + + if ( positionAttribute === undefined ) { + + const position = []; + + for ( let i = 0, l = points.length; i < l; i ++ ) { + + const point = points[ i ]; + position.push( point.x, point.y, point.z || 0 ); + + } + + this.setAttribute( 'position', new Float32BufferAttribute( position, 3 ) ); + + } else { + + const l = Math.min( points.length, positionAttribute.count ); // make sure data do not exceed buffer size + + for ( let i = 0; i < l; i ++ ) { + + const point = points[ i ]; + positionAttribute.setXYZ( i, point.x, point.y, point.z || 0 ); + + } + + if ( points.length > positionAttribute.count ) { + + console.warn( 'THREE.BufferGeometry: Buffer size too small for points data. Use .dispose() and create a new geometry.' ); + + } + + positionAttribute.needsUpdate = true; + + } + + return this; + + } + + /** + * Computes the bounding box of the geometry, and updates the `boundingBox` member. + * The bounding box is not computed by the engine; it must be computed by your app. + * You may need to recompute the bounding box if the geometry vertices are modified. + */ + computeBoundingBox() { + + if ( this.boundingBox === null ) { + + this.boundingBox = new Box3(); + + } + + const position = this.attributes.position; + const morphAttributesPosition = this.morphAttributes.position; + + if ( position && position.isGLBufferAttribute ) { + + console.error( 'THREE.BufferGeometry.computeBoundingBox(): GLBufferAttribute requires a manual bounding box.', this ); + + this.boundingBox.set( + new Vector3( - Infinity, - Infinity, - Infinity ), + new Vector3( + Infinity, + Infinity, + Infinity ) + ); + + return; + + } + + if ( position !== undefined ) { + + this.boundingBox.setFromBufferAttribute( position ); + + // process morph attributes if present + + if ( morphAttributesPosition ) { + + for ( let i = 0, il = morphAttributesPosition.length; i < il; i ++ ) { + + const morphAttribute = morphAttributesPosition[ i ]; + _box$2.setFromBufferAttribute( morphAttribute ); + + if ( this.morphTargetsRelative ) { + + _vector$8.addVectors( this.boundingBox.min, _box$2.min ); + this.boundingBox.expandByPoint( _vector$8 ); + + _vector$8.addVectors( this.boundingBox.max, _box$2.max ); + this.boundingBox.expandByPoint( _vector$8 ); + + } else { + + this.boundingBox.expandByPoint( _box$2.min ); + this.boundingBox.expandByPoint( _box$2.max ); + + } + + } + + } + + } else { + + this.boundingBox.makeEmpty(); + + } + + if ( isNaN( this.boundingBox.min.x ) || isNaN( this.boundingBox.min.y ) || isNaN( this.boundingBox.min.z ) ) { + + console.error( 'THREE.BufferGeometry.computeBoundingBox(): Computed min/max have NaN values. The "position" attribute is likely to have NaN values.', this ); + + } + + } + + /** + * Computes the bounding sphere of the geometry, and updates the `boundingSphere` member. + * The engine automatically computes the bounding sphere when it is needed, e.g., for ray casting or view frustum culling. + * You may need to recompute the bounding sphere if the geometry vertices are modified. + */ + computeBoundingSphere() { + + if ( this.boundingSphere === null ) { + + this.boundingSphere = new Sphere(); + + } + + const position = this.attributes.position; + const morphAttributesPosition = this.morphAttributes.position; + + if ( position && position.isGLBufferAttribute ) { + + console.error( 'THREE.BufferGeometry.computeBoundingSphere(): GLBufferAttribute requires a manual bounding sphere.', this ); + + this.boundingSphere.set( new Vector3(), Infinity ); + + return; + + } + + if ( position ) { + + // first, find the center of the bounding sphere + + const center = this.boundingSphere.center; + + _box$2.setFromBufferAttribute( position ); + + // process morph attributes if present + + if ( morphAttributesPosition ) { + + for ( let i = 0, il = morphAttributesPosition.length; i < il; i ++ ) { + + const morphAttribute = morphAttributesPosition[ i ]; + _boxMorphTargets.setFromBufferAttribute( morphAttribute ); + + if ( this.morphTargetsRelative ) { + + _vector$8.addVectors( _box$2.min, _boxMorphTargets.min ); + _box$2.expandByPoint( _vector$8 ); + + _vector$8.addVectors( _box$2.max, _boxMorphTargets.max ); + _box$2.expandByPoint( _vector$8 ); + + } else { + + _box$2.expandByPoint( _boxMorphTargets.min ); + _box$2.expandByPoint( _boxMorphTargets.max ); + + } + + } + + } + + _box$2.getCenter( center ); + + // second, try to find a boundingSphere with a radius smaller than the + // boundingSphere of the boundingBox: sqrt(3) smaller in the best case + + let maxRadiusSq = 0; + + for ( let i = 0, il = position.count; i < il; i ++ ) { + + _vector$8.fromBufferAttribute( position, i ); + + maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( _vector$8 ) ); + + } + + // process morph attributes if present + + if ( morphAttributesPosition ) { + + for ( let i = 0, il = morphAttributesPosition.length; i < il; i ++ ) { + + const morphAttribute = morphAttributesPosition[ i ]; + const morphTargetsRelative = this.morphTargetsRelative; + + for ( let j = 0, jl = morphAttribute.count; j < jl; j ++ ) { + + _vector$8.fromBufferAttribute( morphAttribute, j ); + + if ( morphTargetsRelative ) { + + _offset.fromBufferAttribute( position, j ); + _vector$8.add( _offset ); + + } + + maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( _vector$8 ) ); + + } + + } + + } + + this.boundingSphere.radius = Math.sqrt( maxRadiusSq ); + + if ( isNaN( this.boundingSphere.radius ) ) { + + console.error( 'THREE.BufferGeometry.computeBoundingSphere(): Computed radius is NaN. The "position" attribute is likely to have NaN values.', this ); + + } + + } + + } + + /** + * Calculates and adds a tangent attribute to this geometry. + * + * The computation is only supported for indexed geometries and if position, normal, and uv attributes + * are defined. When using a tangent space normal map, prefer the MikkTSpace algorithm provided by + * {@link BufferGeometryUtils#computeMikkTSpaceTangents} instead. + */ + computeTangents() { + + const index = this.index; + const attributes = this.attributes; + + // based on http://www.terathon.com/code/tangent.html + // (per vertex tangents) + + if ( index === null || + attributes.position === undefined || + attributes.normal === undefined || + attributes.uv === undefined ) { + + console.error( 'THREE.BufferGeometry: .computeTangents() failed. Missing required attributes (index, position, normal or uv)' ); + return; + + } + + const positionAttribute = attributes.position; + const normalAttribute = attributes.normal; + const uvAttribute = attributes.uv; + + if ( this.hasAttribute( 'tangent' ) === false ) { + + this.setAttribute( 'tangent', new BufferAttribute( new Float32Array( 4 * positionAttribute.count ), 4 ) ); + + } + + const tangentAttribute = this.getAttribute( 'tangent' ); + + const tan1 = [], tan2 = []; + + for ( let i = 0; i < positionAttribute.count; i ++ ) { + + tan1[ i ] = new Vector3(); + tan2[ i ] = new Vector3(); + + } + + const vA = new Vector3(), + vB = new Vector3(), + vC = new Vector3(), + + uvA = new Vector2(), + uvB = new Vector2(), + uvC = new Vector2(), + + sdir = new Vector3(), + tdir = new Vector3(); + + function handleTriangle( a, b, c ) { + + vA.fromBufferAttribute( positionAttribute, a ); + vB.fromBufferAttribute( positionAttribute, b ); + vC.fromBufferAttribute( positionAttribute, c ); + + uvA.fromBufferAttribute( uvAttribute, a ); + uvB.fromBufferAttribute( uvAttribute, b ); + uvC.fromBufferAttribute( uvAttribute, c ); + + vB.sub( vA ); + vC.sub( vA ); + + uvB.sub( uvA ); + uvC.sub( uvA ); + + const r = 1.0 / ( uvB.x * uvC.y - uvC.x * uvB.y ); + + // silently ignore degenerate uv triangles having coincident or colinear vertices + + if ( ! isFinite( r ) ) return; + + sdir.copy( vB ).multiplyScalar( uvC.y ).addScaledVector( vC, - uvB.y ).multiplyScalar( r ); + tdir.copy( vC ).multiplyScalar( uvB.x ).addScaledVector( vB, - uvC.x ).multiplyScalar( r ); + + tan1[ a ].add( sdir ); + tan1[ b ].add( sdir ); + tan1[ c ].add( sdir ); + + tan2[ a ].add( tdir ); + tan2[ b ].add( tdir ); + tan2[ c ].add( tdir ); + + } + + let groups = this.groups; + + if ( groups.length === 0 ) { + + groups = [ { + start: 0, + count: index.count + } ]; + + } + + for ( let i = 0, il = groups.length; i < il; ++ i ) { + + const group = groups[ i ]; + + const start = group.start; + const count = group.count; + + for ( let j = start, jl = start + count; j < jl; j += 3 ) { + + handleTriangle( + index.getX( j + 0 ), + index.getX( j + 1 ), + index.getX( j + 2 ) + ); + + } + + } + + const tmp = new Vector3(), tmp2 = new Vector3(); + const n = new Vector3(), n2 = new Vector3(); + + function handleVertex( v ) { + + n.fromBufferAttribute( normalAttribute, v ); + n2.copy( n ); + + const t = tan1[ v ]; + + // Gram-Schmidt orthogonalize + + tmp.copy( t ); + tmp.sub( n.multiplyScalar( n.dot( t ) ) ).normalize(); + + // Calculate handedness + + tmp2.crossVectors( n2, t ); + const test = tmp2.dot( tan2[ v ] ); + const w = ( test < 0.0 ) ? -1 : 1.0; + + tangentAttribute.setXYZW( v, tmp.x, tmp.y, tmp.z, w ); + + } + + for ( let i = 0, il = groups.length; i < il; ++ i ) { + + const group = groups[ i ]; + + const start = group.start; + const count = group.count; + + for ( let j = start, jl = start + count; j < jl; j += 3 ) { + + handleVertex( index.getX( j + 0 ) ); + handleVertex( index.getX( j + 1 ) ); + handleVertex( index.getX( j + 2 ) ); + + } + + } + + } + + /** + * Computes vertex normals for the given vertex data. For indexed geometries, the method sets + * each vertex normal to be the average of the face normals of the faces that share that vertex. + * For non-indexed geometries, vertices are not shared, and the method sets each vertex normal + * to be the same as the face normal. + */ + computeVertexNormals() { + + const index = this.index; + const positionAttribute = this.getAttribute( 'position' ); + + if ( positionAttribute !== undefined ) { + + let normalAttribute = this.getAttribute( 'normal' ); + + if ( normalAttribute === undefined ) { + + normalAttribute = new BufferAttribute( new Float32Array( positionAttribute.count * 3 ), 3 ); + this.setAttribute( 'normal', normalAttribute ); + + } else { + + // reset existing normals to zero + + for ( let i = 0, il = normalAttribute.count; i < il; i ++ ) { + + normalAttribute.setXYZ( i, 0, 0, 0 ); + + } + + } + + const pA = new Vector3(), pB = new Vector3(), pC = new Vector3(); + const nA = new Vector3(), nB = new Vector3(), nC = new Vector3(); + const cb = new Vector3(), ab = new Vector3(); + + // indexed elements + + if ( index ) { + + for ( let i = 0, il = index.count; i < il; i += 3 ) { + + const vA = index.getX( i + 0 ); + const vB = index.getX( i + 1 ); + const vC = index.getX( i + 2 ); + + pA.fromBufferAttribute( positionAttribute, vA ); + pB.fromBufferAttribute( positionAttribute, vB ); + pC.fromBufferAttribute( positionAttribute, vC ); + + cb.subVectors( pC, pB ); + ab.subVectors( pA, pB ); + cb.cross( ab ); + + nA.fromBufferAttribute( normalAttribute, vA ); + nB.fromBufferAttribute( normalAttribute, vB ); + nC.fromBufferAttribute( normalAttribute, vC ); + + nA.add( cb ); + nB.add( cb ); + nC.add( cb ); + + normalAttribute.setXYZ( vA, nA.x, nA.y, nA.z ); + normalAttribute.setXYZ( vB, nB.x, nB.y, nB.z ); + normalAttribute.setXYZ( vC, nC.x, nC.y, nC.z ); + + } + + } else { + + // non-indexed elements (unconnected triangle soup) + + for ( let i = 0, il = positionAttribute.count; i < il; i += 3 ) { + + pA.fromBufferAttribute( positionAttribute, i + 0 ); + pB.fromBufferAttribute( positionAttribute, i + 1 ); + pC.fromBufferAttribute( positionAttribute, i + 2 ); + + cb.subVectors( pC, pB ); + ab.subVectors( pA, pB ); + cb.cross( ab ); + + normalAttribute.setXYZ( i + 0, cb.x, cb.y, cb.z ); + normalAttribute.setXYZ( i + 1, cb.x, cb.y, cb.z ); + normalAttribute.setXYZ( i + 2, cb.x, cb.y, cb.z ); + + } + + } + + this.normalizeNormals(); + + normalAttribute.needsUpdate = true; + + } + + } + + /** + * Ensures every normal vector in a geometry will have a magnitude of `1`. This will + * correct lighting on the geometry surfaces. + */ + normalizeNormals() { + + const normals = this.attributes.normal; + + for ( let i = 0, il = normals.count; i < il; i ++ ) { + + _vector$8.fromBufferAttribute( normals, i ); + + _vector$8.normalize(); + + normals.setXYZ( i, _vector$8.x, _vector$8.y, _vector$8.z ); + + } + + } + + /** + * Return a new non-index version of this indexed geometry. If the geometry + * is already non-indexed, the method is a NOOP. + * + * @return {BufferGeometry} The non-indexed version of this indexed geometry. + */ + toNonIndexed() { + + function convertBufferAttribute( attribute, indices ) { + + const array = attribute.array; + const itemSize = attribute.itemSize; + const normalized = attribute.normalized; + + const array2 = new array.constructor( indices.length * itemSize ); + + let index = 0, index2 = 0; + + for ( let i = 0, l = indices.length; i < l; i ++ ) { + + if ( attribute.isInterleavedBufferAttribute ) { + + index = indices[ i ] * attribute.data.stride + attribute.offset; + + } else { + + index = indices[ i ] * itemSize; + + } + + for ( let j = 0; j < itemSize; j ++ ) { + + array2[ index2 ++ ] = array[ index ++ ]; + + } + + } + + return new BufferAttribute( array2, itemSize, normalized ); + + } + + // + + if ( this.index === null ) { + + console.warn( 'THREE.BufferGeometry.toNonIndexed(): BufferGeometry is already non-indexed.' ); + return this; + + } + + const geometry2 = new BufferGeometry(); + + const indices = this.index.array; + const attributes = this.attributes; + + // attributes + + for ( const name in attributes ) { + + const attribute = attributes[ name ]; + + const newAttribute = convertBufferAttribute( attribute, indices ); + + geometry2.setAttribute( name, newAttribute ); + + } + + // morph attributes + + const morphAttributes = this.morphAttributes; + + for ( const name in morphAttributes ) { + + const morphArray = []; + const morphAttribute = morphAttributes[ name ]; // morphAttribute: array of Float32BufferAttributes + + for ( let i = 0, il = morphAttribute.length; i < il; i ++ ) { + + const attribute = morphAttribute[ i ]; + + const newAttribute = convertBufferAttribute( attribute, indices ); + + morphArray.push( newAttribute ); + + } + + geometry2.morphAttributes[ name ] = morphArray; + + } + + geometry2.morphTargetsRelative = this.morphTargetsRelative; + + // groups + + const groups = this.groups; + + for ( let i = 0, l = groups.length; i < l; i ++ ) { + + const group = groups[ i ]; + geometry2.addGroup( group.start, group.count, group.materialIndex ); + + } + + return geometry2; + + } + + /** + * Serializes the geometry into JSON. + * + * @return {Object} A JSON object representing the serialized geometry. + */ + toJSON() { + + const data = { + metadata: { + version: 4.7, + type: 'BufferGeometry', + generator: 'BufferGeometry.toJSON' + } + }; + + // standard BufferGeometry serialization + + data.uuid = this.uuid; + data.type = this.type; + if ( this.name !== '' ) data.name = this.name; + if ( Object.keys( this.userData ).length > 0 ) data.userData = this.userData; + + if ( this.parameters !== undefined ) { + + const parameters = this.parameters; + + for ( const key in parameters ) { + + if ( parameters[ key ] !== undefined ) data[ key ] = parameters[ key ]; + + } + + return data; + + } + + // for simplicity the code assumes attributes are not shared across geometries, see #15811 + + data.data = { attributes: {} }; + + const index = this.index; + + if ( index !== null ) { + + data.data.index = { + type: index.array.constructor.name, + array: Array.prototype.slice.call( index.array ) + }; + + } + + const attributes = this.attributes; + + for ( const key in attributes ) { + + const attribute = attributes[ key ]; + + data.data.attributes[ key ] = attribute.toJSON( data.data ); + + } + + const morphAttributes = {}; + let hasMorphAttributes = false; + + for ( const key in this.morphAttributes ) { + + const attributeArray = this.morphAttributes[ key ]; + + const array = []; + + for ( let i = 0, il = attributeArray.length; i < il; i ++ ) { + + const attribute = attributeArray[ i ]; + + array.push( attribute.toJSON( data.data ) ); + + } + + if ( array.length > 0 ) { + + morphAttributes[ key ] = array; + + hasMorphAttributes = true; + + } + + } + + if ( hasMorphAttributes ) { + + data.data.morphAttributes = morphAttributes; + data.data.morphTargetsRelative = this.morphTargetsRelative; + + } + + const groups = this.groups; + + if ( groups.length > 0 ) { + + data.data.groups = JSON.parse( JSON.stringify( groups ) ); + + } + + const boundingSphere = this.boundingSphere; + + if ( boundingSphere !== null ) { + + data.data.boundingSphere = boundingSphere.toJSON(); + + } + + return data; + + } + + /** + * Returns a new geometry with copied values from this instance. + * + * @return {BufferGeometry} A clone of this instance. + */ + clone() { + + return new this.constructor().copy( this ); + + } + + /** + * Copies the values of the given geometry to this instance. + * + * @param {BufferGeometry} source - The geometry to copy. + * @return {BufferGeometry} A reference to this instance. + */ + copy( source ) { + + // reset + + this.index = null; + this.attributes = {}; + this.morphAttributes = {}; + this.groups = []; + this.boundingBox = null; + this.boundingSphere = null; + + // used for storing cloned, shared data + + const data = {}; + + // name + + this.name = source.name; + + // index + + const index = source.index; + + if ( index !== null ) { + + this.setIndex( index.clone() ); + + } + + // attributes + + const attributes = source.attributes; + + for ( const name in attributes ) { + + const attribute = attributes[ name ]; + this.setAttribute( name, attribute.clone( data ) ); + + } + + // morph attributes + + const morphAttributes = source.morphAttributes; + + for ( const name in morphAttributes ) { + + const array = []; + const morphAttribute = morphAttributes[ name ]; // morphAttribute: array of Float32BufferAttributes + + for ( let i = 0, l = morphAttribute.length; i < l; i ++ ) { + + array.push( morphAttribute[ i ].clone( data ) ); + + } + + this.morphAttributes[ name ] = array; + + } + + this.morphTargetsRelative = source.morphTargetsRelative; + + // groups + + const groups = source.groups; + + for ( let i = 0, l = groups.length; i < l; i ++ ) { + + const group = groups[ i ]; + this.addGroup( group.start, group.count, group.materialIndex ); + + } + + // bounding box + + const boundingBox = source.boundingBox; + + if ( boundingBox !== null ) { + + this.boundingBox = boundingBox.clone(); + + } + + // bounding sphere + + const boundingSphere = source.boundingSphere; + + if ( boundingSphere !== null ) { + + this.boundingSphere = boundingSphere.clone(); + + } + + // draw range + + this.drawRange.start = source.drawRange.start; + this.drawRange.count = source.drawRange.count; + + // user data + + this.userData = source.userData; + + return this; + + } + + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + * + * @fires BufferGeometry#dispose + */ + dispose() { + + this.dispatchEvent( { type: 'dispose' } ); + + } + +} + +const _inverseMatrix$3 = /*@__PURE__*/ new Matrix4(); +const _ray$3 = /*@__PURE__*/ new Ray(); +const _sphere$6 = /*@__PURE__*/ new Sphere(); +const _sphereHitAt = /*@__PURE__*/ new Vector3(); + +const _vA$1 = /*@__PURE__*/ new Vector3(); +const _vB$1 = /*@__PURE__*/ new Vector3(); +const _vC$1 = /*@__PURE__*/ new Vector3(); + +const _tempA = /*@__PURE__*/ new Vector3(); +const _morphA = /*@__PURE__*/ new Vector3(); + +const _intersectionPoint = /*@__PURE__*/ new Vector3(); +const _intersectionPointWorld = /*@__PURE__*/ new Vector3(); + +/** + * Class representing triangular polygon mesh based objects. + * + * ```js + * const geometry = new THREE.BoxGeometry( 1, 1, 1 ); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + * const mesh = new THREE.Mesh( geometry, material ); + * scene.add( mesh ); + * ``` + * + * @augments Object3D + */ +class Mesh extends Object3D { + + /** + * Constructs a new mesh. + * + * @param {BufferGeometry} [geometry] - The mesh geometry. + * @param {Material|Array} [material] - The mesh material. + */ + constructor( geometry = new BufferGeometry(), material = new MeshBasicMaterial() ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMesh = true; + + this.type = 'Mesh'; + + /** + * The mesh geometry. + * + * @type {BufferGeometry} + */ + this.geometry = geometry; + + /** + * The mesh material. + * + * @type {Material|Array} + * @default MeshBasicMaterial + */ + this.material = material; + + /** + * A dictionary representing the morph targets in the geometry. The key is the + * morph targets name, the value its attribute index. This member is `undefined` + * by default and only set when morph targets are detected in the geometry. + * + * @type {Object|undefined} + * @default undefined + */ + this.morphTargetDictionary = undefined; + + /** + * An array of weights typically in the range `[0,1]` that specify how much of the morph + * is applied. This member is `undefined` by default and only set when morph targets are + * detected in the geometry. + * + * @type {Array|undefined} + * @default undefined + */ + this.morphTargetInfluences = undefined; + + /** + * The number of instances of this mesh. + * Can only be used with {@link WebGPURenderer}. + * + * @type {number} + * @default 1 + */ + this.count = 1; + + this.updateMorphTargets(); + + } + + copy( source, recursive ) { + + super.copy( source, recursive ); + + if ( source.morphTargetInfluences !== undefined ) { + + this.morphTargetInfluences = source.morphTargetInfluences.slice(); + + } + + if ( source.morphTargetDictionary !== undefined ) { + + this.morphTargetDictionary = Object.assign( {}, source.morphTargetDictionary ); + + } + + this.material = Array.isArray( source.material ) ? source.material.slice() : source.material; + this.geometry = source.geometry; + + return this; + + } + + /** + * Sets the values of {@link Mesh#morphTargetDictionary} and {@link Mesh#morphTargetInfluences} + * to make sure existing morph targets can influence this 3D object. + */ + updateMorphTargets() { + + const geometry = this.geometry; + + const morphAttributes = geometry.morphAttributes; + const keys = Object.keys( morphAttributes ); + + if ( keys.length > 0 ) { + + const morphAttribute = morphAttributes[ keys[ 0 ] ]; + + if ( morphAttribute !== undefined ) { + + this.morphTargetInfluences = []; + this.morphTargetDictionary = {}; + + for ( let m = 0, ml = morphAttribute.length; m < ml; m ++ ) { + + const name = morphAttribute[ m ].name || String( m ); + + this.morphTargetInfluences.push( 0 ); + this.morphTargetDictionary[ name ] = m; + + } + + } + + } + + } + + /** + * Returns the local-space position of the vertex at the given index, taking into + * account the current animation state of both morph targets and skinning. + * + * @param {number} index - The vertex index. + * @param {Vector3} target - The target object that is used to store the method's result. + * @return {Vector3} The vertex position in local space. + */ + getVertexPosition( index, target ) { + + const geometry = this.geometry; + const position = geometry.attributes.position; + const morphPosition = geometry.morphAttributes.position; + const morphTargetsRelative = geometry.morphTargetsRelative; + + target.fromBufferAttribute( position, index ); + + const morphInfluences = this.morphTargetInfluences; + + if ( morphPosition && morphInfluences ) { + + _morphA.set( 0, 0, 0 ); + + for ( let i = 0, il = morphPosition.length; i < il; i ++ ) { + + const influence = morphInfluences[ i ]; + const morphAttribute = morphPosition[ i ]; + + if ( influence === 0 ) continue; + + _tempA.fromBufferAttribute( morphAttribute, index ); + + if ( morphTargetsRelative ) { + + _morphA.addScaledVector( _tempA, influence ); + + } else { + + _morphA.addScaledVector( _tempA.sub( target ), influence ); + + } + + } + + target.add( _morphA ); + + } + + return target; + + } + + /** + * Computes intersection points between a casted ray and this line. + * + * @param {Raycaster} raycaster - The raycaster. + * @param {Array} intersects - The target array that holds the intersection points. + */ + raycast( raycaster, intersects ) { + + const geometry = this.geometry; + const material = this.material; + const matrixWorld = this.matrixWorld; + + if ( material === undefined ) return; + + // test with bounding sphere in world space + + if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); + + _sphere$6.copy( geometry.boundingSphere ); + _sphere$6.applyMatrix4( matrixWorld ); + + // check distance from ray origin to bounding sphere + + _ray$3.copy( raycaster.ray ).recast( raycaster.near ); + + if ( _sphere$6.containsPoint( _ray$3.origin ) === false ) { + + if ( _ray$3.intersectSphere( _sphere$6, _sphereHitAt ) === null ) return; + + if ( _ray$3.origin.distanceToSquared( _sphereHitAt ) > ( raycaster.far - raycaster.near ) ** 2 ) return; + + } + + // convert ray to local space of mesh + + _inverseMatrix$3.copy( matrixWorld ).invert(); + _ray$3.copy( raycaster.ray ).applyMatrix4( _inverseMatrix$3 ); + + // test with bounding box in local space + + if ( geometry.boundingBox !== null ) { + + if ( _ray$3.intersectsBox( geometry.boundingBox ) === false ) return; + + } + + // test for intersections with geometry + + this._computeIntersections( raycaster, intersects, _ray$3 ); + + } + + _computeIntersections( raycaster, intersects, rayLocalSpace ) { + + let intersection; + + const geometry = this.geometry; + const material = this.material; + + const index = geometry.index; + const position = geometry.attributes.position; + const uv = geometry.attributes.uv; + const uv1 = geometry.attributes.uv1; + const normal = geometry.attributes.normal; + const groups = geometry.groups; + const drawRange = geometry.drawRange; + + if ( index !== null ) { + + // indexed buffer geometry + + if ( Array.isArray( material ) ) { + + for ( let i = 0, il = groups.length; i < il; i ++ ) { + + const group = groups[ i ]; + const groupMaterial = material[ group.materialIndex ]; + + const start = Math.max( group.start, drawRange.start ); + const end = Math.min( index.count, Math.min( ( group.start + group.count ), ( drawRange.start + drawRange.count ) ) ); + + for ( let j = start, jl = end; j < jl; j += 3 ) { + + const a = index.getX( j ); + const b = index.getX( j + 1 ); + const c = index.getX( j + 2 ); + + intersection = checkGeometryIntersection( this, groupMaterial, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c ); + + if ( intersection ) { + + intersection.faceIndex = Math.floor( j / 3 ); // triangle number in indexed buffer semantics + intersection.face.materialIndex = group.materialIndex; + intersects.push( intersection ); + + } + + } + + } + + } else { + + const start = Math.max( 0, drawRange.start ); + const end = Math.min( index.count, ( drawRange.start + drawRange.count ) ); + + for ( let i = start, il = end; i < il; i += 3 ) { + + const a = index.getX( i ); + const b = index.getX( i + 1 ); + const c = index.getX( i + 2 ); + + intersection = checkGeometryIntersection( this, material, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c ); + + if ( intersection ) { + + intersection.faceIndex = Math.floor( i / 3 ); // triangle number in indexed buffer semantics + intersects.push( intersection ); + + } + + } + + } + + } else if ( position !== undefined ) { + + // non-indexed buffer geometry + + if ( Array.isArray( material ) ) { + + for ( let i = 0, il = groups.length; i < il; i ++ ) { + + const group = groups[ i ]; + const groupMaterial = material[ group.materialIndex ]; + + const start = Math.max( group.start, drawRange.start ); + const end = Math.min( position.count, Math.min( ( group.start + group.count ), ( drawRange.start + drawRange.count ) ) ); + + for ( let j = start, jl = end; j < jl; j += 3 ) { + + const a = j; + const b = j + 1; + const c = j + 2; + + intersection = checkGeometryIntersection( this, groupMaterial, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c ); + + if ( intersection ) { + + intersection.faceIndex = Math.floor( j / 3 ); // triangle number in non-indexed buffer semantics + intersection.face.materialIndex = group.materialIndex; + intersects.push( intersection ); + + } + + } + + } + + } else { + + const start = Math.max( 0, drawRange.start ); + const end = Math.min( position.count, ( drawRange.start + drawRange.count ) ); + + for ( let i = start, il = end; i < il; i += 3 ) { + + const a = i; + const b = i + 1; + const c = i + 2; + + intersection = checkGeometryIntersection( this, material, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c ); + + if ( intersection ) { + + intersection.faceIndex = Math.floor( i / 3 ); // triangle number in non-indexed buffer semantics + intersects.push( intersection ); + + } + + } + + } + + } + + } + +} + +function checkIntersection$1( object, material, raycaster, ray, pA, pB, pC, point ) { + + let intersect; + + if ( material.side === BackSide ) { + + intersect = ray.intersectTriangle( pC, pB, pA, true, point ); + + } else { + + intersect = ray.intersectTriangle( pA, pB, pC, ( material.side === FrontSide ), point ); + + } + + if ( intersect === null ) return null; + + _intersectionPointWorld.copy( point ); + _intersectionPointWorld.applyMatrix4( object.matrixWorld ); + + const distance = raycaster.ray.origin.distanceTo( _intersectionPointWorld ); + + if ( distance < raycaster.near || distance > raycaster.far ) return null; + + return { + distance: distance, + point: _intersectionPointWorld.clone(), + object: object + }; + +} + +function checkGeometryIntersection( object, material, raycaster, ray, uv, uv1, normal, a, b, c ) { + + object.getVertexPosition( a, _vA$1 ); + object.getVertexPosition( b, _vB$1 ); + object.getVertexPosition( c, _vC$1 ); + + const intersection = checkIntersection$1( object, material, raycaster, ray, _vA$1, _vB$1, _vC$1, _intersectionPoint ); + + if ( intersection ) { + + const barycoord = new Vector3(); + Triangle.getBarycoord( _intersectionPoint, _vA$1, _vB$1, _vC$1, barycoord ); + + if ( uv ) { + + intersection.uv = Triangle.getInterpolatedAttribute( uv, a, b, c, barycoord, new Vector2() ); + + } + + if ( uv1 ) { + + intersection.uv1 = Triangle.getInterpolatedAttribute( uv1, a, b, c, barycoord, new Vector2() ); + + } + + if ( normal ) { + + intersection.normal = Triangle.getInterpolatedAttribute( normal, a, b, c, barycoord, new Vector3() ); + + if ( intersection.normal.dot( ray.direction ) > 0 ) { + + intersection.normal.multiplyScalar( -1 ); + + } + + } + + const face = { + a: a, + b: b, + c: c, + normal: new Vector3(), + materialIndex: 0 + }; + + Triangle.getNormal( _vA$1, _vB$1, _vC$1, face.normal ); + + intersection.face = face; + intersection.barycoord = barycoord; + + } + + return intersection; + +} + +/** + * A geometry class for a rectangular cuboid with a given width, height, and depth. + * On creation, the cuboid is centred on the origin, with each edge parallel to one + * of the axes. + * + * ```js + * const geometry = new THREE.BoxGeometry( 1, 1, 1 ); + * const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); + * const cube = new THREE.Mesh( geometry, material ); + * scene.add( cube ); + * ``` + * + * @augments BufferGeometry + */ +class BoxGeometry extends BufferGeometry { + + /** + * Constructs a new box geometry. + * + * @param {number} [width=1] - The width. That is, the length of the edges parallel to the X axis. + * @param {number} [height=1] - The height. That is, the length of the edges parallel to the Y axis. + * @param {number} [depth=1] - The depth. That is, the length of the edges parallel to the Z axis. + * @param {number} [widthSegments=1] - Number of segmented rectangular faces along the width of the sides. + * @param {number} [heightSegments=1] - Number of segmented rectangular faces along the height of the sides. + * @param {number} [depthSegments=1] - Number of segmented rectangular faces along the depth of the sides. + */ + constructor( width = 1, height = 1, depth = 1, widthSegments = 1, heightSegments = 1, depthSegments = 1 ) { + + super(); + + this.type = 'BoxGeometry'; + + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + width: width, + height: height, + depth: depth, + widthSegments: widthSegments, + heightSegments: heightSegments, + depthSegments: depthSegments + }; + + const scope = this; + + // segments + + widthSegments = Math.floor( widthSegments ); + heightSegments = Math.floor( heightSegments ); + depthSegments = Math.floor( depthSegments ); + + // buffers + + const indices = []; + const vertices = []; + const normals = []; + const uvs = []; + + // helper variables + + let numberOfVertices = 0; + let groupStart = 0; + + // build each side of the box geometry + + buildPlane( 'z', 'y', 'x', -1, -1, depth, height, width, depthSegments, heightSegments, 0 ); // px + buildPlane( 'z', 'y', 'x', 1, -1, depth, height, - width, depthSegments, heightSegments, 1 ); // nx + buildPlane( 'x', 'z', 'y', 1, 1, width, depth, height, widthSegments, depthSegments, 2 ); // py + buildPlane( 'x', 'z', 'y', 1, -1, width, depth, - height, widthSegments, depthSegments, 3 ); // ny + buildPlane( 'x', 'y', 'z', 1, -1, width, height, depth, widthSegments, heightSegments, 4 ); // pz + buildPlane( 'x', 'y', 'z', -1, -1, width, height, - depth, widthSegments, heightSegments, 5 ); // nz + + // build geometry + + this.setIndex( indices ); + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + + function buildPlane( u, v, w, udir, vdir, width, height, depth, gridX, gridY, materialIndex ) { + + const segmentWidth = width / gridX; + const segmentHeight = height / gridY; + + const widthHalf = width / 2; + const heightHalf = height / 2; + const depthHalf = depth / 2; + + const gridX1 = gridX + 1; + const gridY1 = gridY + 1; + + let vertexCounter = 0; + let groupCount = 0; + + const vector = new Vector3(); + + // generate vertices, normals and uvs + + for ( let iy = 0; iy < gridY1; iy ++ ) { + + const y = iy * segmentHeight - heightHalf; + + for ( let ix = 0; ix < gridX1; ix ++ ) { + + const x = ix * segmentWidth - widthHalf; + + // set values to correct vector component + + vector[ u ] = x * udir; + vector[ v ] = y * vdir; + vector[ w ] = depthHalf; + + // now apply vector to vertex buffer + + vertices.push( vector.x, vector.y, vector.z ); + + // set values to correct vector component + + vector[ u ] = 0; + vector[ v ] = 0; + vector[ w ] = depth > 0 ? 1 : -1; + + // now apply vector to normal buffer + + normals.push( vector.x, vector.y, vector.z ); + + // uvs + + uvs.push( ix / gridX ); + uvs.push( 1 - ( iy / gridY ) ); + + // counters + + vertexCounter += 1; + + } + + } + + // indices + + // 1. you need three indices to draw a single face + // 2. a single segment consists of two faces + // 3. so we need to generate six (2*3) indices per segment + + for ( let iy = 0; iy < gridY; iy ++ ) { + + for ( let ix = 0; ix < gridX; ix ++ ) { + + const a = numberOfVertices + ix + gridX1 * iy; + const b = numberOfVertices + ix + gridX1 * ( iy + 1 ); + const c = numberOfVertices + ( ix + 1 ) + gridX1 * ( iy + 1 ); + const d = numberOfVertices + ( ix + 1 ) + gridX1 * iy; + + // faces + + indices.push( a, b, d ); + indices.push( b, c, d ); + + // increase counter + + groupCount += 6; + + } + + } + + // add a group to the geometry. this will ensure multi material support + + scope.addGroup( groupStart, groupCount, materialIndex ); + + // calculate new start value for groups + + groupStart += groupCount; + + // update total number of vertices + + numberOfVertices += vertexCounter; + + } + + } + + copy( source ) { + + super.copy( source ); + + this.parameters = Object.assign( {}, source.parameters ); + + return this; + + } + + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {BoxGeometry} A new instance. + */ + static fromJSON( data ) { + + return new BoxGeometry( data.width, data.height, data.depth, data.widthSegments, data.heightSegments, data.depthSegments ); + + } + +} + +// Uniform Utilities + +function cloneUniforms( src ) { + + const dst = {}; + + for ( const u in src ) { + + dst[ u ] = {}; + + for ( const p in src[ u ] ) { + + const property = src[ u ][ p ]; + + if ( property && ( property.isColor || + property.isMatrix3 || property.isMatrix4 || + property.isVector2 || property.isVector3 || property.isVector4 || + property.isTexture || property.isQuaternion ) ) { + + if ( property.isRenderTargetTexture ) { + + console.warn( 'UniformsUtils: Textures of render targets cannot be cloned via cloneUniforms() or mergeUniforms().' ); + dst[ u ][ p ] = null; + + } else { + + dst[ u ][ p ] = property.clone(); + + } + + } else if ( Array.isArray( property ) ) { + + dst[ u ][ p ] = property.slice(); + + } else { + + dst[ u ][ p ] = property; + + } + + } + + } + + return dst; + +} + +function mergeUniforms( uniforms ) { + + const merged = {}; + + for ( let u = 0; u < uniforms.length; u ++ ) { + + const tmp = cloneUniforms( uniforms[ u ] ); + + for ( const p in tmp ) { + + merged[ p ] = tmp[ p ]; + + } + + } + + return merged; + +} + +function cloneUniformsGroups( src ) { + + const dst = []; + + for ( let u = 0; u < src.length; u ++ ) { + + dst.push( src[ u ].clone() ); + + } + + return dst; + +} + +function getUnlitUniformColorSpace( renderer ) { + + const currentRenderTarget = renderer.getRenderTarget(); + + if ( currentRenderTarget === null ) { + + // https://github.com/mrdoob/three.js/pull/23937#issuecomment-1111067398 + return renderer.outputColorSpace; + + } + + // https://github.com/mrdoob/three.js/issues/27868 + if ( currentRenderTarget.isXRRenderTarget === true ) { + + return currentRenderTarget.texture.colorSpace; + + } + + return ColorManagement.workingColorSpace; + +} + +// Legacy + +const UniformsUtils = { clone: cloneUniforms, merge: mergeUniforms }; + +var default_vertex = "void main() {\n\tgl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n}"; + +var default_fragment = "void main() {\n\tgl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0 );\n}"; + +/** + * A material rendered with custom shaders. A shader is a small program written in GLSL. + * that runs on the GPU. You may want to use a custom shader if you need to implement an + * effect not included with any of the built-in materials. + * + * There are the following notes to bear in mind when using a `ShaderMaterial`: + * + * - `ShaderMaterial` can only be used with {@link WebGLRenderer}. + * - Built in attributes and uniforms are passed to the shaders along with your code. If + * you don't want that, use {@link RawShaderMaterial} instead. + * - You can use the directive `#pragma unroll_loop_start` and `#pragma unroll_loop_end` + * in order to unroll a `for` loop in GLSL by the shader preprocessor. The directive has + * to be placed right above the loop. The loop formatting has to correspond to a defined standard. + * - The loop has to be [normalized]{@link https://en.wikipedia.org/wiki/Normalized_loop}. + * - The loop variable has to be *i*. + * - The value `UNROLLED_LOOP_INDEX` will be replaced with the explicitly + * value of *i* for the given iteration and can be used in preprocessor + * statements. + * + * ```js + * const material = new THREE.ShaderMaterial( { + * uniforms: { + * time: { value: 1.0 }, + * resolution: { value: new THREE.Vector2() } + * }, + * vertexShader: document.getElementById( 'vertexShader' ).textContent, + * fragmentShader: document.getElementById( 'fragmentShader' ).textContent + * } ); + * ``` + * + * @augments Material + */ +class ShaderMaterial extends Material { + + /** + * Constructs a new shader material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isShaderMaterial = true; + + this.type = 'ShaderMaterial'; + + /** + * Defines custom constants using `#define` directives within the GLSL code + * for both the vertex shader and the fragment shader; each key/value pair + * yields another directive. + * ```js + * defines: { + * FOO: 15, + * BAR: true + * } + * ``` + * Yields the lines: + * ``` + * #define FOO 15 + * #define BAR true + * ``` + * + * @type {Object} + */ + this.defines = {}; + + /** + * An object of the form: + * ```js + * { + * "uniform1": { value: 1.0 }, + * "uniform2": { value: 2 } + * } + * ``` + * specifying the uniforms to be passed to the shader code; keys are uniform + * names, values are definitions of the form + * ``` + * { + * value: 1.0 + * } + * ``` + * where `value` is the value of the uniform. Names must match the name of + * the uniform, as defined in the GLSL code. Note that uniforms are refreshed + * on every frame, so updating the value of the uniform will immediately + * update the value available to the GLSL code. + * + * @type {Object} + */ + this.uniforms = {}; + + /** + * An array holding uniforms groups for configuring UBOs. + * + * @type {Array} + */ + this.uniformsGroups = []; + + /** + * Vertex shader GLSL code. This is the actual code for the shader. + * + * @type {string} + */ + this.vertexShader = default_vertex; + + /** + * Fragment shader GLSL code. This is the actual code for the shader. + * + * @type {string} + */ + this.fragmentShader = default_fragment; + + /** + * Controls line thickness or lines. + * + * WebGL and WebGPU ignore this setting and always render line primitives with a + * width of one pixel. + * + * @type {number} + * @default 1 + */ + this.linewidth = 1; + + /** + * Renders the geometry as a wireframe. + * + * @type {boolean} + * @default false + */ + this.wireframe = false; + + /** + * Controls the thickness of the wireframe. + * + * WebGL and WebGPU ignore this property and always render + * 1 pixel wide lines. + * + * @type {number} + * @default 1 + */ + this.wireframeLinewidth = 1; + + /** + * Define whether the material color is affected by global fog settings; `true` + * to pass fog uniforms to the shader. + * + * @type {boolean} + * @default false + */ + this.fog = false; + + /** + * Defines whether this material uses lighting; `true` to pass uniform data + * related to lighting to this shader. + * + * @type {boolean} + * @default false + */ + this.lights = false; + + /** + * Defines whether this material supports clipping; `true` to let the renderer + * pass the clippingPlanes uniform. + * + * @type {boolean} + * @default false + */ + this.clipping = false; + + /** + * Overwritten and set to `true` by default. + * + * @type {boolean} + * @default true + */ + this.forceSinglePass = true; + + /** + * This object allows to enable certain WebGL 2 extensions. + * + * - clipCullDistance: set to `true` to use vertex shader clipping + * - multiDraw: set to `true` to use vertex shader multi_draw / enable gl_DrawID + * + * @type {{clipCullDistance:false,multiDraw:false}} + */ + this.extensions = { + clipCullDistance: false, // set to use vertex shader clipping + multiDraw: false // set to use vertex shader multi_draw / enable gl_DrawID + }; + + /** + * When the rendered geometry doesn't include these attributes but the + * material does, these default values will be passed to the shaders. This + * avoids errors when buffer data is missing. + * + * - color: [ 1, 1, 1 ] + * - uv: [ 0, 0 ] + * - uv1: [ 0, 0 ] + * + * @type {Object} + */ + this.defaultAttributeValues = { + 'color': [ 1, 1, 1 ], + 'uv': [ 0, 0 ], + 'uv1': [ 0, 0 ] + }; + + /** + * If set, this calls [gl.bindAttribLocation]{@link https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/bindAttribLocation} + * to bind a generic vertex index to an attribute variable. + * + * @type {string|undefined} + * @default undefined + */ + this.index0AttributeName = undefined; + + /** + * Can be used to force a uniform update while changing uniforms in + * {@link Object3D#onBeforeRender}. + * + * @type {boolean} + * @default false + */ + this.uniformsNeedUpdate = false; + + /** + * Defines the GLSL version of custom shader code. + * + * @type {?(GLSL1|GLSL3)} + * @default null + */ + this.glslVersion = null; + + if ( parameters !== undefined ) { + + this.setValues( parameters ); + + } + + } + + copy( source ) { + + super.copy( source ); + + this.fragmentShader = source.fragmentShader; + this.vertexShader = source.vertexShader; + + this.uniforms = cloneUniforms( source.uniforms ); + this.uniformsGroups = cloneUniformsGroups( source.uniformsGroups ); + + this.defines = Object.assign( {}, source.defines ); + + this.wireframe = source.wireframe; + this.wireframeLinewidth = source.wireframeLinewidth; + + this.fog = source.fog; + this.lights = source.lights; + this.clipping = source.clipping; + + this.extensions = Object.assign( {}, source.extensions ); + + this.glslVersion = source.glslVersion; + + return this; + + } + + toJSON( meta ) { + + const data = super.toJSON( meta ); + + data.glslVersion = this.glslVersion; + data.uniforms = {}; + + for ( const name in this.uniforms ) { + + const uniform = this.uniforms[ name ]; + const value = uniform.value; + + if ( value && value.isTexture ) { + + data.uniforms[ name ] = { + type: 't', + value: value.toJSON( meta ).uuid + }; + + } else if ( value && value.isColor ) { + + data.uniforms[ name ] = { + type: 'c', + value: value.getHex() + }; + + } else if ( value && value.isVector2 ) { + + data.uniforms[ name ] = { + type: 'v2', + value: value.toArray() + }; + + } else if ( value && value.isVector3 ) { + + data.uniforms[ name ] = { + type: 'v3', + value: value.toArray() + }; + + } else if ( value && value.isVector4 ) { + + data.uniforms[ name ] = { + type: 'v4', + value: value.toArray() + }; + + } else if ( value && value.isMatrix3 ) { + + data.uniforms[ name ] = { + type: 'm3', + value: value.toArray() + }; + + } else if ( value && value.isMatrix4 ) { + + data.uniforms[ name ] = { + type: 'm4', + value: value.toArray() + }; + + } else { + + data.uniforms[ name ] = { + value: value + }; + + // note: the array variants v2v, v3v, v4v, m4v and tv are not supported so far + + } + + } + + if ( Object.keys( this.defines ).length > 0 ) data.defines = this.defines; + + data.vertexShader = this.vertexShader; + data.fragmentShader = this.fragmentShader; + + data.lights = this.lights; + data.clipping = this.clipping; + + const extensions = {}; + + for ( const key in this.extensions ) { + + if ( this.extensions[ key ] === true ) extensions[ key ] = true; + + } + + if ( Object.keys( extensions ).length > 0 ) data.extensions = extensions; + + return data; + + } + +} + +/** + * Abstract base class for cameras. This class should always be inherited + * when you build a new camera. + * + * @abstract + * @augments Object3D + */ +class Camera extends Object3D { + + /** + * Constructs a new camera. + */ + constructor() { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isCamera = true; + + this.type = 'Camera'; + + /** + * The inverse of the camera's world matrix. + * + * @type {Matrix4} + */ + this.matrixWorldInverse = new Matrix4(); + + /** + * The camera's projection matrix. + * + * @type {Matrix4} + */ + this.projectionMatrix = new Matrix4(); + + /** + * The inverse of the camera's projection matrix. + * + * @type {Matrix4} + */ + this.projectionMatrixInverse = new Matrix4(); + + /** + * The coordinate system in which the camera is used. + * + * @type {(WebGLCoordinateSystem|WebGPUCoordinateSystem)} + */ + this.coordinateSystem = WebGLCoordinateSystem; + + } + + copy( source, recursive ) { + + super.copy( source, recursive ); + + this.matrixWorldInverse.copy( source.matrixWorldInverse ); + + this.projectionMatrix.copy( source.projectionMatrix ); + this.projectionMatrixInverse.copy( source.projectionMatrixInverse ); + + this.coordinateSystem = source.coordinateSystem; + + return this; + + } + + /** + * Returns a vector representing the ("look") direction of the 3D object in world space. + * + * This method is overwritten since cameras have a different forward vector compared to other + * 3D objects. A camera looks down its local, negative z-axis by default. + * + * @param {Vector3} target - The target vector the result is stored to. + * @return {Vector3} The 3D object's direction in world space. + */ + getWorldDirection( target ) { + + return super.getWorldDirection( target ).negate(); + + } + + updateMatrixWorld( force ) { + + super.updateMatrixWorld( force ); + + this.matrixWorldInverse.copy( this.matrixWorld ).invert(); + + } + + updateWorldMatrix( updateParents, updateChildren ) { + + super.updateWorldMatrix( updateParents, updateChildren ); + + this.matrixWorldInverse.copy( this.matrixWorld ).invert(); + + } + + clone() { + + return new this.constructor().copy( this ); + + } + +} + +const _v3$1 = /*@__PURE__*/ new Vector3(); +const _minTarget = /*@__PURE__*/ new Vector2(); +const _maxTarget = /*@__PURE__*/ new Vector2(); + +/** + * Camera that uses [perspective projection]{@link https://en.wikipedia.org/wiki/Perspective_(graphical)}. + * + * This projection mode is designed to mimic the way the human eye sees. It + * is the most common projection mode used for rendering a 3D scene. + * + * ```js + * const camera = new THREE.PerspectiveCamera( 45, width / height, 1, 1000 ); + * scene.add( camera ); + * ``` + * + * @augments Camera + */ +class PerspectiveCamera extends Camera { + + /** + * Constructs a new perspective camera. + * + * @param {number} [fov=50] - The vertical field of view. + * @param {number} [aspect=1] - The aspect ratio. + * @param {number} [near=0.1] - The camera's near plane. + * @param {number} [far=2000] - The camera's far plane. + */ + constructor( fov = 50, aspect = 1, near = 0.1, far = 2000 ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isPerspectiveCamera = true; + + this.type = 'PerspectiveCamera'; + + /** + * The vertical field of view, from bottom to top of view, + * in degrees. + * + * @type {number} + * @default 50 + */ + this.fov = fov; + + /** + * The zoom factor of the camera. + * + * @type {number} + * @default 1 + */ + this.zoom = 1; + + /** + * The camera's near plane. The valid range is greater than `0` + * and less than the current value of {@link PerspectiveCamera#far}. + * + * Note that, unlike for the {@link OrthographicCamera}, `0` is not a + * valid value for a perspective camera's near plane. + * + * @type {number} + * @default 0.1 + */ + this.near = near; + + /** + * The camera's far plane. Must be greater than the + * current value of {@link PerspectiveCamera#near}. + * + * @type {number} + * @default 2000 + */ + this.far = far; + + /** + * Object distance used for stereoscopy and depth-of-field effects. This + * parameter does not influence the projection matrix unless a + * {@link StereoCamera} is being used. + * + * @type {number} + * @default 10 + */ + this.focus = 10; + + /** + * The aspect ratio, usually the canvas width / canvas height. + * + * @type {number} + * @default 1 + */ + this.aspect = aspect; + + /** + * Represents the frustum window specification. This property should not be edited + * directly but via {@link PerspectiveCamera#setViewOffset} and {@link PerspectiveCamera#clearViewOffset}. + * + * @type {?Object} + * @default null + */ + this.view = null; + + /** + * Film size used for the larger axis. Default is `35` (millimeters). This + * parameter does not influence the projection matrix unless {@link PerspectiveCamera#filmOffset} + * is set to a nonzero value. + * + * @type {number} + * @default 35 + */ + this.filmGauge = 35; + + /** + * Horizontal off-center offset in the same unit as {@link PerspectiveCamera#filmGauge}. + * + * @type {number} + * @default 0 + */ + this.filmOffset = 0; + + this.updateProjectionMatrix(); + + } + + copy( source, recursive ) { + + super.copy( source, recursive ); + + this.fov = source.fov; + this.zoom = source.zoom; + + this.near = source.near; + this.far = source.far; + this.focus = source.focus; + + this.aspect = source.aspect; + this.view = source.view === null ? null : Object.assign( {}, source.view ); + + this.filmGauge = source.filmGauge; + this.filmOffset = source.filmOffset; + + return this; + + } + + /** + * Sets the FOV by focal length in respect to the current {@link PerspectiveCamera#filmGauge}. + * + * The default film gauge is 35, so that the focal length can be specified for + * a 35mm (full frame) camera. + * + * @param {number} focalLength - Values for focal length and film gauge must have the same unit. + */ + setFocalLength( focalLength ) { + + /** see {@link http://www.bobatkins.com/photography/technical/field_of_view.html} */ + const vExtentSlope = 0.5 * this.getFilmHeight() / focalLength; + + this.fov = RAD2DEG * 2 * Math.atan( vExtentSlope ); + this.updateProjectionMatrix(); + + } + + /** + * Returns the focal length from the current {@link PerspectiveCamera#fov} and + * {@link PerspectiveCamera#filmGauge}. + * + * @return {number} The computed focal length. + */ + getFocalLength() { + + const vExtentSlope = Math.tan( DEG2RAD * 0.5 * this.fov ); + + return 0.5 * this.getFilmHeight() / vExtentSlope; + + } + + /** + * Returns the current vertical field of view angle in degrees considering {@link PerspectiveCamera#zoom}. + * + * @return {number} The effective FOV. + */ + getEffectiveFOV() { + + return RAD2DEG * 2 * Math.atan( + Math.tan( DEG2RAD * 0.5 * this.fov ) / this.zoom ); + + } + + /** + * Returns the width of the image on the film. If {@link PerspectiveCamera#aspect} is greater than or + * equal to one (landscape format), the result equals {@link PerspectiveCamera#filmGauge}. + * + * @return {number} The film width. + */ + getFilmWidth() { + + // film not completely covered in portrait format (aspect < 1) + return this.filmGauge * Math.min( this.aspect, 1 ); + + } + + /** + * Returns the height of the image on the film. If {@link PerspectiveCamera#aspect} is greater than or + * equal to one (landscape format), the result equals {@link PerspectiveCamera#filmGauge}. + * + * @return {number} The film width. + */ + getFilmHeight() { + + // film not completely covered in landscape format (aspect > 1) + return this.filmGauge / Math.max( this.aspect, 1 ); + + } + + /** + * Computes the 2D bounds of the camera's viewable rectangle at a given distance along the viewing direction. + * Sets `minTarget` and `maxTarget` to the coordinates of the lower-left and upper-right corners of the view rectangle. + * + * @param {number} distance - The viewing distance. + * @param {Vector2} minTarget - The lower-left corner of the view rectangle is written into this vector. + * @param {Vector2} maxTarget - The upper-right corner of the view rectangle is written into this vector. + */ + getViewBounds( distance, minTarget, maxTarget ) { + + _v3$1.set( -1, -1, 0.5 ).applyMatrix4( this.projectionMatrixInverse ); + + minTarget.set( _v3$1.x, _v3$1.y ).multiplyScalar( - distance / _v3$1.z ); + + _v3$1.set( 1, 1, 0.5 ).applyMatrix4( this.projectionMatrixInverse ); + + maxTarget.set( _v3$1.x, _v3$1.y ).multiplyScalar( - distance / _v3$1.z ); + + } + + /** + * Computes the width and height of the camera's viewable rectangle at a given distance along the viewing direction. + * + * @param {number} distance - The viewing distance. + * @param {Vector2} target - The target vector that is used to store result where x is width and y is height. + * @returns {Vector2} The view size. + */ + getViewSize( distance, target ) { + + this.getViewBounds( distance, _minTarget, _maxTarget ); + + return target.subVectors( _maxTarget, _minTarget ); + + } + + /** + * Sets an offset in a larger frustum. This is useful for multi-window or + * multi-monitor/multi-machine setups. + * + * For example, if you have 3x2 monitors and each monitor is 1920x1080 and + * the monitors are in grid like this + *``` + * +---+---+---+ + * | A | B | C | + * +---+---+---+ + * | D | E | F | + * +---+---+---+ + *``` + * then for each monitor you would call it like this: + *```js + * const w = 1920; + * const h = 1080; + * const fullWidth = w * 3; + * const fullHeight = h * 2; + * + * // --A-- + * camera.setViewOffset( fullWidth, fullHeight, w * 0, h * 0, w, h ); + * // --B-- + * camera.setViewOffset( fullWidth, fullHeight, w * 1, h * 0, w, h ); + * // --C-- + * camera.setViewOffset( fullWidth, fullHeight, w * 2, h * 0, w, h ); + * // --D-- + * camera.setViewOffset( fullWidth, fullHeight, w * 0, h * 1, w, h ); + * // --E-- + * camera.setViewOffset( fullWidth, fullHeight, w * 1, h * 1, w, h ); + * // --F-- + * camera.setViewOffset( fullWidth, fullHeight, w * 2, h * 1, w, h ); + * ``` + * + * Note there is no reason monitors have to be the same size or in a grid. + * + * @param {number} fullWidth - The full width of multiview setup. + * @param {number} fullHeight - The full height of multiview setup. + * @param {number} x - The horizontal offset of the subcamera. + * @param {number} y - The vertical offset of the subcamera. + * @param {number} width - The width of subcamera. + * @param {number} height - The height of subcamera. + */ + setViewOffset( fullWidth, fullHeight, x, y, width, height ) { + + this.aspect = fullWidth / fullHeight; + + if ( this.view === null ) { + + this.view = { + enabled: true, + fullWidth: 1, + fullHeight: 1, + offsetX: 0, + offsetY: 0, + width: 1, + height: 1 + }; + + } + + this.view.enabled = true; + this.view.fullWidth = fullWidth; + this.view.fullHeight = fullHeight; + this.view.offsetX = x; + this.view.offsetY = y; + this.view.width = width; + this.view.height = height; + + this.updateProjectionMatrix(); + + } + + /** + * Removes the view offset from the projection matrix. + */ + clearViewOffset() { + + if ( this.view !== null ) { + + this.view.enabled = false; + + } + + this.updateProjectionMatrix(); + + } + + /** + * Updates the camera's projection matrix. Must be called after any change of + * camera properties. + */ + updateProjectionMatrix() { + + const near = this.near; + let top = near * Math.tan( DEG2RAD * 0.5 * this.fov ) / this.zoom; + let height = 2 * top; + let width = this.aspect * height; + let left = -0.5 * width; + const view = this.view; + + if ( this.view !== null && this.view.enabled ) { + + const fullWidth = view.fullWidth, + fullHeight = view.fullHeight; + + left += view.offsetX * width / fullWidth; + top -= view.offsetY * height / fullHeight; + width *= view.width / fullWidth; + height *= view.height / fullHeight; + + } + + const skew = this.filmOffset; + if ( skew !== 0 ) left += near * skew / this.getFilmWidth(); + + this.projectionMatrix.makePerspective( left, left + width, top, top - height, near, this.far, this.coordinateSystem ); + + this.projectionMatrixInverse.copy( this.projectionMatrix ).invert(); + + } + + toJSON( meta ) { + + const data = super.toJSON( meta ); + + data.object.fov = this.fov; + data.object.zoom = this.zoom; + + data.object.near = this.near; + data.object.far = this.far; + data.object.focus = this.focus; + + data.object.aspect = this.aspect; + + if ( this.view !== null ) data.object.view = Object.assign( {}, this.view ); + + data.object.filmGauge = this.filmGauge; + data.object.filmOffset = this.filmOffset; + + return data; + + } + +} + +const fov = -90; // negative fov is not an error +const aspect = 1; + +/** + * A special type of camera that is positioned in 3D space to render its surroundings into a + * cube render target. The render target can then be used as an environment map for rendering + * realtime reflections in your scene. + * + * ```js + * // Create cube render target + * const cubeRenderTarget = new THREE.WebGLCubeRenderTarget( 256, { generateMipmaps: true, minFilter: THREE.LinearMipmapLinearFilter } ); + * + * // Create cube camera + * const cubeCamera = new THREE.CubeCamera( 1, 100000, cubeRenderTarget ); + * scene.add( cubeCamera ); + * + * // Create car + * const chromeMaterial = new THREE.MeshLambertMaterial( { color: 0xffffff, envMap: cubeRenderTarget.texture } ); + * const car = new THREE.Mesh( carGeometry, chromeMaterial ); + * scene.add( car ); + * + * // Update the render target cube + * car.visible = false; + * cubeCamera.position.copy( car.position ); + * cubeCamera.update( renderer, scene ); + * + * // Render the scene + * car.visible = true; + * renderer.render( scene, camera ); + * ``` + * + * @augments Object3D + */ +class CubeCamera extends Object3D { + + /** + * Constructs a new cube camera. + * + * @param {number} near - The camera's near plane. + * @param {number} far - The camera's far plane. + * @param {WebGLCubeRenderTarget} renderTarget - The cube render target. + */ + constructor( near, far, renderTarget ) { + + super(); + + this.type = 'CubeCamera'; + + /** + * A reference to the cube render target. + * + * @type {WebGLCubeRenderTarget} + */ + this.renderTarget = renderTarget; + + /** + * The current active coordinate system. + * + * @type {?(WebGLCoordinateSystem|WebGPUCoordinateSystem)} + * @default null + */ + this.coordinateSystem = null; + + /** + * The current active mipmap level + * + * @type {number} + * @default 0 + */ + this.activeMipmapLevel = 0; + + const cameraPX = new PerspectiveCamera( fov, aspect, near, far ); + cameraPX.layers = this.layers; + this.add( cameraPX ); + + const cameraNX = new PerspectiveCamera( fov, aspect, near, far ); + cameraNX.layers = this.layers; + this.add( cameraNX ); + + const cameraPY = new PerspectiveCamera( fov, aspect, near, far ); + cameraPY.layers = this.layers; + this.add( cameraPY ); + + const cameraNY = new PerspectiveCamera( fov, aspect, near, far ); + cameraNY.layers = this.layers; + this.add( cameraNY ); + + const cameraPZ = new PerspectiveCamera( fov, aspect, near, far ); + cameraPZ.layers = this.layers; + this.add( cameraPZ ); + + const cameraNZ = new PerspectiveCamera( fov, aspect, near, far ); + cameraNZ.layers = this.layers; + this.add( cameraNZ ); + + } + + /** + * Must be called when the coordinate system of the cube camera is changed. + */ + updateCoordinateSystem() { + + const coordinateSystem = this.coordinateSystem; + + const cameras = this.children.concat(); + + const [ cameraPX, cameraNX, cameraPY, cameraNY, cameraPZ, cameraNZ ] = cameras; + + for ( const camera of cameras ) this.remove( camera ); + + if ( coordinateSystem === WebGLCoordinateSystem ) { + + cameraPX.up.set( 0, 1, 0 ); + cameraPX.lookAt( 1, 0, 0 ); + + cameraNX.up.set( 0, 1, 0 ); + cameraNX.lookAt( -1, 0, 0 ); + + cameraPY.up.set( 0, 0, -1 ); + cameraPY.lookAt( 0, 1, 0 ); + + cameraNY.up.set( 0, 0, 1 ); + cameraNY.lookAt( 0, -1, 0 ); + + cameraPZ.up.set( 0, 1, 0 ); + cameraPZ.lookAt( 0, 0, 1 ); + + cameraNZ.up.set( 0, 1, 0 ); + cameraNZ.lookAt( 0, 0, -1 ); + + } else if ( coordinateSystem === WebGPUCoordinateSystem ) { + + cameraPX.up.set( 0, -1, 0 ); + cameraPX.lookAt( -1, 0, 0 ); + + cameraNX.up.set( 0, -1, 0 ); + cameraNX.lookAt( 1, 0, 0 ); + + cameraPY.up.set( 0, 0, 1 ); + cameraPY.lookAt( 0, 1, 0 ); + + cameraNY.up.set( 0, 0, -1 ); + cameraNY.lookAt( 0, -1, 0 ); + + cameraPZ.up.set( 0, -1, 0 ); + cameraPZ.lookAt( 0, 0, 1 ); + + cameraNZ.up.set( 0, -1, 0 ); + cameraNZ.lookAt( 0, 0, -1 ); + + } else { + + throw new Error( 'THREE.CubeCamera.updateCoordinateSystem(): Invalid coordinate system: ' + coordinateSystem ); + + } + + for ( const camera of cameras ) { + + this.add( camera ); + + camera.updateMatrixWorld(); + + } + + } + + /** + * Calling this method will render the given scene with the given renderer + * into the cube render target of the camera. + * + * @param {(Renderer|WebGLRenderer)} renderer - The renderer. + * @param {Scene} scene - The scene to render. + */ + update( renderer, scene ) { + + if ( this.parent === null ) this.updateMatrixWorld(); + + const { renderTarget, activeMipmapLevel } = this; + + if ( this.coordinateSystem !== renderer.coordinateSystem ) { + + this.coordinateSystem = renderer.coordinateSystem; + + this.updateCoordinateSystem(); + + } + + const [ cameraPX, cameraNX, cameraPY, cameraNY, cameraPZ, cameraNZ ] = this.children; + + const currentRenderTarget = renderer.getRenderTarget(); + const currentActiveCubeFace = renderer.getActiveCubeFace(); + const currentActiveMipmapLevel = renderer.getActiveMipmapLevel(); + + const currentXrEnabled = renderer.xr.enabled; + + renderer.xr.enabled = false; + + const generateMipmaps = renderTarget.texture.generateMipmaps; + + renderTarget.texture.generateMipmaps = false; + + renderer.setRenderTarget( renderTarget, 0, activeMipmapLevel ); + renderer.render( scene, cameraPX ); + + renderer.setRenderTarget( renderTarget, 1, activeMipmapLevel ); + renderer.render( scene, cameraNX ); + + renderer.setRenderTarget( renderTarget, 2, activeMipmapLevel ); + renderer.render( scene, cameraPY ); + + renderer.setRenderTarget( renderTarget, 3, activeMipmapLevel ); + renderer.render( scene, cameraNY ); + + renderer.setRenderTarget( renderTarget, 4, activeMipmapLevel ); + renderer.render( scene, cameraPZ ); + + // mipmaps are generated during the last call of render() + // at this point, all sides of the cube render target are defined + + renderTarget.texture.generateMipmaps = generateMipmaps; + + renderer.setRenderTarget( renderTarget, 5, activeMipmapLevel ); + renderer.render( scene, cameraNZ ); + + renderer.setRenderTarget( currentRenderTarget, currentActiveCubeFace, currentActiveMipmapLevel ); + + renderer.xr.enabled = currentXrEnabled; + + renderTarget.texture.needsPMREMUpdate = true; + + } + +} + +/** + * Creates a cube texture made up of six images. + * + * ```js + * const loader = new THREE.CubeTextureLoader(); + * loader.setPath( 'textures/cube/pisa/' ); + * + * const textureCube = loader.load( [ + * 'px.png', 'nx.png', 'py.png', 'ny.png', 'pz.png', 'nz.png' + * ] ); + * + * const material = new THREE.MeshBasicMaterial( { color: 0xffffff, envMap: textureCube } ); + * ``` + * + * @augments Texture + */ +class CubeTexture extends Texture { + + /** + * Constructs a new cube texture. + * + * @param {Array} [images=[]] - An array holding a image for each side of a cube. + * @param {number} [mapping=CubeReflectionMapping] - The texture mapping. + * @param {number} [wrapS=ClampToEdgeWrapping] - The wrapS value. + * @param {number} [wrapT=ClampToEdgeWrapping] - The wrapT value. + * @param {number} [magFilter=LinearFilter] - The mag filter value. + * @param {number} [minFilter=LinearMipmapLinearFilter] - The min filter value. + * @param {number} [format=RGBAFormat] - The texture format. + * @param {number} [type=UnsignedByteType] - The texture type. + * @param {number} [anisotropy=Texture.DEFAULT_ANISOTROPY] - The anisotropy value. + * @param {string} [colorSpace=NoColorSpace] - The color space value. + */ + constructor( images = [], mapping = CubeReflectionMapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, colorSpace ) { + + super( images, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, colorSpace ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isCubeTexture = true; + + /** + * If set to `true`, the texture is flipped along the vertical axis when + * uploaded to the GPU. + * + * Overwritten and set to `false` by default. + * + * @type {boolean} + * @default false + */ + this.flipY = false; + + } + + /** + * Alias for {@link CubeTexture#image}. + * + * @type {Array} + */ + get images() { + + return this.image; + + } + + set images( value ) { + + this.image = value; + + } + +} + +/** + * A cube render target used in context of {@link WebGLRenderer}. + * + * @augments WebGLRenderTarget + */ +class WebGLCubeRenderTarget extends WebGLRenderTarget { + + /** + * Constructs a new cube render target. + * + * @param {number} [size=1] - The size of the render target. + * @param {RenderTarget~Options} [options] - The configuration object. + */ + constructor( size = 1, options = {} ) { + + super( size, size, options ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isWebGLCubeRenderTarget = true; + + const image = { width: size, height: size, depth: 1 }; + const images = [ image, image, image, image, image, image ]; + + /** + * Overwritten with a different texture type. + * + * @type {DataArrayTexture} + */ + this.texture = new CubeTexture( images ); + this._setTextureOptions( options ); + + // By convention -- likely based on the RenderMan spec from the 1990's -- cube maps are specified by WebGL (and three.js) + // in a coordinate system in which positive-x is to the right when looking up the positive-z axis -- in other words, + // in a left-handed coordinate system. By continuing this convention, preexisting cube maps continued to render correctly. + + // three.js uses a right-handed coordinate system. So environment maps used in three.js appear to have px and nx swapped + // and the flag isRenderTargetTexture controls this conversion. The flip is not required when using WebGLCubeRenderTarget.texture + // as a cube texture (this is detected when isRenderTargetTexture is set to true for cube textures). + + this.texture.isRenderTargetTexture = true; + + } + + /** + * Converts the given equirectangular texture to a cube map. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {Texture} texture - The equirectangular texture. + * @return {WebGLCubeRenderTarget} A reference to this cube render target. + */ + fromEquirectangularTexture( renderer, texture ) { + + this.texture.type = texture.type; + this.texture.colorSpace = texture.colorSpace; + + this.texture.generateMipmaps = texture.generateMipmaps; + this.texture.minFilter = texture.minFilter; + this.texture.magFilter = texture.magFilter; + + const shader = { + + uniforms: { + tEquirect: { value: null }, + }, + + vertexShader: /* glsl */` + + varying vec3 vWorldDirection; + + vec3 transformDirection( in vec3 dir, in mat4 matrix ) { + + return normalize( ( matrix * vec4( dir, 0.0 ) ).xyz ); + + } + + void main() { + + vWorldDirection = transformDirection( position, modelMatrix ); + + #include + #include + + } + `, + + fragmentShader: /* glsl */` + + uniform sampler2D tEquirect; + + varying vec3 vWorldDirection; + + #include + + void main() { + + vec3 direction = normalize( vWorldDirection ); + + vec2 sampleUV = equirectUv( direction ); + + gl_FragColor = texture2D( tEquirect, sampleUV ); + + } + ` + }; + + const geometry = new BoxGeometry( 5, 5, 5 ); + + const material = new ShaderMaterial( { + + name: 'CubemapFromEquirect', + + uniforms: cloneUniforms( shader.uniforms ), + vertexShader: shader.vertexShader, + fragmentShader: shader.fragmentShader, + side: BackSide, + blending: NoBlending + + } ); + + material.uniforms.tEquirect.value = texture; + + const mesh = new Mesh( geometry, material ); + + const currentMinFilter = texture.minFilter; + + // Avoid blurred poles + if ( texture.minFilter === LinearMipmapLinearFilter ) texture.minFilter = LinearFilter; + + const camera = new CubeCamera( 1, 10, this ); + camera.update( renderer, mesh ); + + texture.minFilter = currentMinFilter; + + mesh.geometry.dispose(); + mesh.material.dispose(); + + return this; + + } + + /** + * Clears this cube render target. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {boolean} [color=true] - Whether the color buffer should be cleared or not. + * @param {boolean} [depth=true] - Whether the depth buffer should be cleared or not. + * @param {boolean} [stencil=true] - Whether the stencil buffer should be cleared or not. + */ + clear( renderer, color = true, depth = true, stencil = true ) { + + const currentRenderTarget = renderer.getRenderTarget(); + + for ( let i = 0; i < 6; i ++ ) { + + renderer.setRenderTarget( this, i ); + + renderer.clear( color, depth, stencil ); + + } + + renderer.setRenderTarget( currentRenderTarget ); + + } + +} + +/** + * This is almost identical to an {@link Object3D}. Its purpose is to + * make working with groups of objects syntactically clearer. + * + * ```js + * // Create a group and add the two cubes. + * // These cubes can now be rotated / scaled etc as a group. + * const group = new THREE.Group(); + * + * group.add( meshA ); + * group.add( meshB ); + * + * scene.add( group ); + * ``` + * + * @augments Object3D + */ +class Group extends Object3D { + + constructor() { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isGroup = true; + + this.type = 'Group'; + + } + +} + +const _moveEvent = { type: 'move' }; + +/** + * Class for representing a XR controller with its + * different coordinate systems. + * + * @private + */ +class WebXRController { + + /** + * Constructs a new XR controller. + */ + constructor() { + + /** + * A group representing the target ray space + * of the XR controller. + * + * @private + * @type {?Group} + * @default null + */ + this._targetRay = null; + + /** + * A group representing the grip space + * of the XR controller. + * + * @private + * @type {?Group} + * @default null + */ + this._grip = null; + + /** + * A group representing the hand space + * of the XR controller. + * + * @private + * @type {?Group} + * @default null + */ + this._hand = null; + + } + + /** + * Returns a group representing the hand space of the XR controller. + * + * @return {Group} A group representing the hand space of the XR controller. + */ + getHandSpace() { + + if ( this._hand === null ) { + + this._hand = new Group(); + this._hand.matrixAutoUpdate = false; + this._hand.visible = false; + + this._hand.joints = {}; + this._hand.inputState = { pinching: false }; + + } + + return this._hand; + + } + + /** + * Returns a group representing the target ray space of the XR controller. + * + * @return {Group} A group representing the target ray space of the XR controller. + */ + getTargetRaySpace() { + + if ( this._targetRay === null ) { + + this._targetRay = new Group(); + this._targetRay.matrixAutoUpdate = false; + this._targetRay.visible = false; + this._targetRay.hasLinearVelocity = false; + this._targetRay.linearVelocity = new Vector3(); + this._targetRay.hasAngularVelocity = false; + this._targetRay.angularVelocity = new Vector3(); + + } + + return this._targetRay; + + } + + /** + * Returns a group representing the grip space of the XR controller. + * + * @return {Group} A group representing the grip space of the XR controller. + */ + getGripSpace() { + + if ( this._grip === null ) { + + this._grip = new Group(); + this._grip.matrixAutoUpdate = false; + this._grip.visible = false; + this._grip.hasLinearVelocity = false; + this._grip.linearVelocity = new Vector3(); + this._grip.hasAngularVelocity = false; + this._grip.angularVelocity = new Vector3(); + + } + + return this._grip; + + } + + /** + * Dispatches the given event to the groups representing + * the different coordinate spaces of the XR controller. + * + * @param {Object} event - The event to dispatch. + * @return {WebXRController} A reference to this instance. + */ + dispatchEvent( event ) { + + if ( this._targetRay !== null ) { + + this._targetRay.dispatchEvent( event ); + + } + + if ( this._grip !== null ) { + + this._grip.dispatchEvent( event ); + + } + + if ( this._hand !== null ) { + + this._hand.dispatchEvent( event ); + + } + + return this; + + } + + /** + * Connects the controller with the given XR input source. + * + * @param {XRInputSource} inputSource - The input source. + * @return {WebXRController} A reference to this instance. + */ + connect( inputSource ) { + + if ( inputSource && inputSource.hand ) { + + const hand = this._hand; + + if ( hand ) { + + for ( const inputjoint of inputSource.hand.values() ) { + + // Initialize hand with joints when connected + this._getHandJoint( hand, inputjoint ); + + } + + } + + } + + this.dispatchEvent( { type: 'connected', data: inputSource } ); + + return this; + + } + + /** + * Disconnects the controller from the given XR input source. + * + * @param {XRInputSource} inputSource - The input source. + * @return {WebXRController} A reference to this instance. + */ + disconnect( inputSource ) { + + this.dispatchEvent( { type: 'disconnected', data: inputSource } ); + + if ( this._targetRay !== null ) { + + this._targetRay.visible = false; + + } + + if ( this._grip !== null ) { + + this._grip.visible = false; + + } + + if ( this._hand !== null ) { + + this._hand.visible = false; + + } + + return this; + + } + + /** + * Updates the controller with the given input source, XR frame and reference space. + * This updates the transformations of the groups that represent the different + * coordinate systems of the controller. + * + * @param {XRInputSource} inputSource - The input source. + * @param {XRFrame} frame - The XR frame. + * @param {XRReferenceSpace} referenceSpace - The reference space. + * @return {WebXRController} A reference to this instance. + */ + update( inputSource, frame, referenceSpace ) { + + let inputPose = null; + let gripPose = null; + let handPose = null; + + const targetRay = this._targetRay; + const grip = this._grip; + const hand = this._hand; + + if ( inputSource && frame.session.visibilityState !== 'visible-blurred' ) { + + if ( hand && inputSource.hand ) { + + handPose = true; + + for ( const inputjoint of inputSource.hand.values() ) { + + // Update the joints groups with the XRJoint poses + const jointPose = frame.getJointPose( inputjoint, referenceSpace ); + + // The transform of this joint will be updated with the joint pose on each frame + const joint = this._getHandJoint( hand, inputjoint ); + + if ( jointPose !== null ) { + + joint.matrix.fromArray( jointPose.transform.matrix ); + joint.matrix.decompose( joint.position, joint.rotation, joint.scale ); + joint.matrixWorldNeedsUpdate = true; + joint.jointRadius = jointPose.radius; + + } + + joint.visible = jointPose !== null; + + } + + // Custom events + + // Check pinchz + const indexTip = hand.joints[ 'index-finger-tip' ]; + const thumbTip = hand.joints[ 'thumb-tip' ]; + const distance = indexTip.position.distanceTo( thumbTip.position ); + + const distanceToPinch = 0.02; + const threshold = 0.005; + + if ( hand.inputState.pinching && distance > distanceToPinch + threshold ) { + + hand.inputState.pinching = false; + this.dispatchEvent( { + type: 'pinchend', + handedness: inputSource.handedness, + target: this + } ); + + } else if ( ! hand.inputState.pinching && distance <= distanceToPinch - threshold ) { + + hand.inputState.pinching = true; + this.dispatchEvent( { + type: 'pinchstart', + handedness: inputSource.handedness, + target: this + } ); + + } + + } else { + + if ( grip !== null && inputSource.gripSpace ) { + + gripPose = frame.getPose( inputSource.gripSpace, referenceSpace ); + + if ( gripPose !== null ) { + + grip.matrix.fromArray( gripPose.transform.matrix ); + grip.matrix.decompose( grip.position, grip.rotation, grip.scale ); + grip.matrixWorldNeedsUpdate = true; + + if ( gripPose.linearVelocity ) { + + grip.hasLinearVelocity = true; + grip.linearVelocity.copy( gripPose.linearVelocity ); + + } else { + + grip.hasLinearVelocity = false; + + } + + if ( gripPose.angularVelocity ) { + + grip.hasAngularVelocity = true; + grip.angularVelocity.copy( gripPose.angularVelocity ); + + } else { + + grip.hasAngularVelocity = false; + + } + + } + + } + + } + + if ( targetRay !== null ) { + + inputPose = frame.getPose( inputSource.targetRaySpace, referenceSpace ); + + // Some runtimes (namely Vive Cosmos with Vive OpenXR Runtime) have only grip space and ray space is equal to it + if ( inputPose === null && gripPose !== null ) { + + inputPose = gripPose; + + } + + if ( inputPose !== null ) { + + targetRay.matrix.fromArray( inputPose.transform.matrix ); + targetRay.matrix.decompose( targetRay.position, targetRay.rotation, targetRay.scale ); + targetRay.matrixWorldNeedsUpdate = true; + + if ( inputPose.linearVelocity ) { + + targetRay.hasLinearVelocity = true; + targetRay.linearVelocity.copy( inputPose.linearVelocity ); + + } else { + + targetRay.hasLinearVelocity = false; + + } + + if ( inputPose.angularVelocity ) { + + targetRay.hasAngularVelocity = true; + targetRay.angularVelocity.copy( inputPose.angularVelocity ); + + } else { + + targetRay.hasAngularVelocity = false; + + } + + this.dispatchEvent( _moveEvent ); + + } + + } + + + } + + if ( targetRay !== null ) { + + targetRay.visible = ( inputPose !== null ); + + } + + if ( grip !== null ) { + + grip.visible = ( gripPose !== null ); + + } + + if ( hand !== null ) { + + hand.visible = ( handPose !== null ); + + } + + return this; + + } + + /** + * Returns a group representing the hand joint for the given input joint. + * + * @private + * @param {Group} hand - The group representing the hand space. + * @param {XRJointSpace} inputjoint - The hand joint data. + * @return {Group} A group representing the hand joint for the given input joint. + */ + _getHandJoint( hand, inputjoint ) { + + if ( hand.joints[ inputjoint.jointName ] === undefined ) { + + const joint = new Group(); + joint.matrixAutoUpdate = false; + joint.visible = false; + hand.joints[ inputjoint.jointName ] = joint; + + hand.add( joint ); + + } + + return hand.joints[ inputjoint.jointName ]; + + } + +} + +/** + * This class can be used to define an exponential squared fog, + * which gives a clear view near the camera and a faster than exponentially + * densening fog farther from the camera. + * + * ```js + * const scene = new THREE.Scene(); + * scene.fog = new THREE.FogExp2( 0xcccccc, 0.002 ); + * ``` + */ +class FogExp2 { + + /** + * Constructs a new fog. + * + * @param {number|Color} color - The fog's color. + * @param {number} [density=0.00025] - Defines how fast the fog will grow dense. + */ + constructor( color, density = 0.00025 ) { + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isFogExp2 = true; + + /** + * The name of the fog. + * + * @type {string} + */ + this.name = ''; + + /** + * The fog's color. + * + * @type {Color} + */ + this.color = new Color( color ); + + /** + * Defines how fast the fog will grow dense. + * + * @type {number} + * @default 0.00025 + */ + this.density = density; + + } + + /** + * Returns a new fog with copied values from this instance. + * + * @return {FogExp2} A clone of this instance. + */ + clone() { + + return new FogExp2( this.color, this.density ); + + } + + /** + * Serializes the fog into JSON. + * + * @param {?(Object|string)} meta - An optional value holding meta information about the serialization. + * @return {Object} A JSON object representing the serialized fog + */ + toJSON( /* meta */ ) { + + return { + type: 'FogExp2', + name: this.name, + color: this.color.getHex(), + density: this.density + }; + + } + +} + +/** + * This class can be used to define a linear fog that grows linearly denser + * with the distance. + * + * ```js + * const scene = new THREE.Scene(); + * scene.fog = new THREE.Fog( 0xcccccc, 10, 15 ); + * ``` + */ +class Fog { + + /** + * Constructs a new fog. + * + * @param {number|Color} color - The fog's color. + * @param {number} [near=1] - The minimum distance to start applying fog. + * @param {number} [far=1000] - The maximum distance at which fog stops being calculated and applied. + */ + constructor( color, near = 1, far = 1000 ) { + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isFog = true; + + /** + * The name of the fog. + * + * @type {string} + */ + this.name = ''; + + /** + * The fog's color. + * + * @type {Color} + */ + this.color = new Color( color ); + + /** + * The minimum distance to start applying fog. Objects that are less than + * `near` units from the active camera won't be affected by fog. + * + * @type {number} + * @default 1 + */ + this.near = near; + + /** + * The maximum distance at which fog stops being calculated and applied. + * Objects that are more than `far` units away from the active camera won't + * be affected by fog. + * + * @type {number} + * @default 1000 + */ + this.far = far; + + } + + /** + * Returns a new fog with copied values from this instance. + * + * @return {Fog} A clone of this instance. + */ + clone() { + + return new Fog( this.color, this.near, this.far ); + + } + + /** + * Serializes the fog into JSON. + * + * @param {?(Object|string)} meta - An optional value holding meta information about the serialization. + * @return {Object} A JSON object representing the serialized fog + */ + toJSON( /* meta */ ) { + + return { + type: 'Fog', + name: this.name, + color: this.color.getHex(), + near: this.near, + far: this.far + }; + + } + +} + +/** + * Scenes allow you to set up what is to be rendered and where by three.js. + * This is where you place 3D objects like meshes, lines or lights. + * + * @augments Object3D + */ +class Scene extends Object3D { + + /** + * Constructs a new scene. + */ + constructor() { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isScene = true; + + this.type = 'Scene'; + + /** + * Defines the background of the scene. Valid inputs are: + * + * - A color for defining a uniform colored background. + * - A texture for defining a (flat) textured background. + * - Cube textures or equirectangular textures for defining a skybox. + * + * @type {?(Color|Texture)} + * @default null + */ + this.background = null; + + /** + * Sets the environment map for all physical materials in the scene. However, + * it's not possible to overwrite an existing texture assigned to the `envMap` + * material property. + * + * @type {?Texture} + * @default null + */ + this.environment = null; + + /** + * A fog instance defining the type of fog that affects everything + * rendered in the scene. + * + * @type {?(Fog|FogExp2)} + * @default null + */ + this.fog = null; + + /** + * Sets the blurriness of the background. Only influences environment maps + * assigned to {@link Scene#background}. Valid input is a float between `0` + * and `1`. + * + * @type {number} + * @default 0 + */ + this.backgroundBlurriness = 0; + + /** + * Attenuates the color of the background. Only applies to background textures. + * + * @type {number} + * @default 1 + */ + this.backgroundIntensity = 1; + + /** + * The rotation of the background in radians. Only influences environment maps + * assigned to {@link Scene#background}. + * + * @type {Euler} + * @default (0,0,0) + */ + this.backgroundRotation = new Euler(); + + /** + * Attenuates the color of the environment. Only influences environment maps + * assigned to {@link Scene#environment}. + * + * @type {number} + * @default 1 + */ + this.environmentIntensity = 1; + + /** + * The rotation of the environment map in radians. Only influences physical materials + * in the scene when {@link Scene#environment} is used. + * + * @type {Euler} + * @default (0,0,0) + */ + this.environmentRotation = new Euler(); + + /** + * Forces everything in the scene to be rendered with the defined material. It is possible + * to exclude materials from override by setting {@link Material#allowOverride} to `false`. + * + * @type {?Material} + * @default null + */ + this.overrideMaterial = null; + + if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) { + + __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'observe', { detail: this } ) ); + + } + + } + + copy( source, recursive ) { + + super.copy( source, recursive ); + + if ( source.background !== null ) this.background = source.background.clone(); + if ( source.environment !== null ) this.environment = source.environment.clone(); + if ( source.fog !== null ) this.fog = source.fog.clone(); + + this.backgroundBlurriness = source.backgroundBlurriness; + this.backgroundIntensity = source.backgroundIntensity; + this.backgroundRotation.copy( source.backgroundRotation ); + + this.environmentIntensity = source.environmentIntensity; + this.environmentRotation.copy( source.environmentRotation ); + + if ( source.overrideMaterial !== null ) this.overrideMaterial = source.overrideMaterial.clone(); + + this.matrixAutoUpdate = source.matrixAutoUpdate; + + return this; + + } + + toJSON( meta ) { + + const data = super.toJSON( meta ); + + if ( this.fog !== null ) data.object.fog = this.fog.toJSON(); + + if ( this.backgroundBlurriness > 0 ) data.object.backgroundBlurriness = this.backgroundBlurriness; + if ( this.backgroundIntensity !== 1 ) data.object.backgroundIntensity = this.backgroundIntensity; + data.object.backgroundRotation = this.backgroundRotation.toArray(); + + if ( this.environmentIntensity !== 1 ) data.object.environmentIntensity = this.environmentIntensity; + data.object.environmentRotation = this.environmentRotation.toArray(); + + return data; + + } + +} + +/** + * "Interleaved" means that multiple attributes, possibly of different types, + * (e.g., position, normal, uv, color) are packed into a single array buffer. + * + * An introduction into interleaved arrays can be found here: [Interleaved array basics]{@link https://blog.tojicode.com/2011/05/interleaved-array-basics.html} + */ +class InterleavedBuffer { + + /** + * Constructs a new interleaved buffer. + * + * @param {TypedArray} array - A typed array with a shared buffer storing attribute data. + * @param {number} stride - The number of typed-array elements per vertex. + */ + constructor( array, stride ) { + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isInterleavedBuffer = true; + + /** + * A typed array with a shared buffer storing attribute data. + * + * @type {TypedArray} + */ + this.array = array; + + /** + * The number of typed-array elements per vertex. + * + * @type {number} + */ + this.stride = stride; + + /** + * The total number of elements in the array + * + * @type {number} + * @readonly + */ + this.count = array !== undefined ? array.length / stride : 0; + + /** + * Defines the intended usage pattern of the data store for optimization purposes. + * + * Note: After the initial use of a buffer, its usage cannot be changed. Instead, + * instantiate a new one and set the desired usage before the next render. + * + * @type {(StaticDrawUsage|DynamicDrawUsage|StreamDrawUsage|StaticReadUsage|DynamicReadUsage|StreamReadUsage|StaticCopyUsage|DynamicCopyUsage|StreamCopyUsage)} + * @default StaticDrawUsage + */ + this.usage = StaticDrawUsage; + + /** + * This can be used to only update some components of stored vectors (for example, just the + * component related to color). Use the `addUpdateRange()` function to add ranges to this array. + * + * @type {Array} + */ + this.updateRanges = []; + + /** + * A version number, incremented every time the `needsUpdate` is set to `true`. + * + * @type {number} + */ + this.version = 0; + + /** + * The UUID of the interleaved buffer. + * + * @type {string} + * @readonly + */ + this.uuid = generateUUID(); + + } + + /** + * A callback function that is executed after the renderer has transferred the attribute array + * data to the GPU. + */ + onUploadCallback() {} + + /** + * Flag to indicate that this attribute has changed and should be re-sent to + * the GPU. Set this to `true` when you modify the value of the array. + * + * @type {number} + * @default false + * @param {boolean} value + */ + set needsUpdate( value ) { + + if ( value === true ) this.version ++; + + } + + /** + * Sets the usage of this interleaved buffer. + * + * @param {(StaticDrawUsage|DynamicDrawUsage|StreamDrawUsage|StaticReadUsage|DynamicReadUsage|StreamReadUsage|StaticCopyUsage|DynamicCopyUsage|StreamCopyUsage)} value - The usage to set. + * @return {InterleavedBuffer} A reference to this interleaved buffer. + */ + setUsage( value ) { + + this.usage = value; + + return this; + + } + + /** + * Adds a range of data in the data array to be updated on the GPU. + * + * @param {number} start - Position at which to start update. + * @param {number} count - The number of components to update. + */ + addUpdateRange( start, count ) { + + this.updateRanges.push( { start, count } ); + + } + + /** + * Clears the update ranges. + */ + clearUpdateRanges() { + + this.updateRanges.length = 0; + + } + + /** + * Copies the values of the given interleaved buffer to this instance. + * + * @param {InterleavedBuffer} source - The interleaved buffer to copy. + * @return {InterleavedBuffer} A reference to this instance. + */ + copy( source ) { + + this.array = new source.array.constructor( source.array ); + this.count = source.count; + this.stride = source.stride; + this.usage = source.usage; + + return this; + + } + + /** + * Copies a vector from the given interleaved buffer to this one. The start + * and destination position in the attribute buffers are represented by the + * given indices. + * + * @param {number} index1 - The destination index into this interleaved buffer. + * @param {InterleavedBuffer} interleavedBuffer - The interleaved buffer to copy from. + * @param {number} index2 - The source index into the given interleaved buffer. + * @return {InterleavedBuffer} A reference to this instance. + */ + copyAt( index1, interleavedBuffer, index2 ) { + + index1 *= this.stride; + index2 *= interleavedBuffer.stride; + + for ( let i = 0, l = this.stride; i < l; i ++ ) { + + this.array[ index1 + i ] = interleavedBuffer.array[ index2 + i ]; + + } + + return this; + + } + + /** + * Sets the given array data in the interleaved buffer. + * + * @param {(TypedArray|Array)} value - The array data to set. + * @param {number} [offset=0] - The offset in this interleaved buffer's array. + * @return {InterleavedBuffer} A reference to this instance. + */ + set( value, offset = 0 ) { + + this.array.set( value, offset ); + + return this; + + } + + /** + * Returns a new interleaved buffer with copied values from this instance. + * + * @param {Object} [data] - An object with shared array buffers that allows to retain shared structures. + * @return {InterleavedBuffer} A clone of this instance. + */ + clone( data ) { + + if ( data.arrayBuffers === undefined ) { + + data.arrayBuffers = {}; + + } + + if ( this.array.buffer._uuid === undefined ) { + + this.array.buffer._uuid = generateUUID(); + + } + + if ( data.arrayBuffers[ this.array.buffer._uuid ] === undefined ) { + + data.arrayBuffers[ this.array.buffer._uuid ] = this.array.slice( 0 ).buffer; + + } + + const array = new this.array.constructor( data.arrayBuffers[ this.array.buffer._uuid ] ); + + const ib = new this.constructor( array, this.stride ); + ib.setUsage( this.usage ); + + return ib; + + } + + /** + * Sets the given callback function that is executed after the Renderer has transferred + * the array data to the GPU. Can be used to perform clean-up operations after + * the upload when data are not needed anymore on the CPU side. + * + * @param {Function} callback - The `onUpload()` callback. + * @return {InterleavedBuffer} A reference to this instance. + */ + onUpload( callback ) { + + this.onUploadCallback = callback; + + return this; + + } + + /** + * Serializes the interleaved buffer into JSON. + * + * @param {Object} [data] - An optional value holding meta information about the serialization. + * @return {Object} A JSON object representing the serialized interleaved buffer. + */ + toJSON( data ) { + + if ( data.arrayBuffers === undefined ) { + + data.arrayBuffers = {}; + + } + + // generate UUID for array buffer if necessary + + if ( this.array.buffer._uuid === undefined ) { + + this.array.buffer._uuid = generateUUID(); + + } + + if ( data.arrayBuffers[ this.array.buffer._uuid ] === undefined ) { + + data.arrayBuffers[ this.array.buffer._uuid ] = Array.from( new Uint32Array( this.array.buffer ) ); + + } + + // + + return { + uuid: this.uuid, + buffer: this.array.buffer._uuid, + type: this.array.constructor.name, + stride: this.stride + }; + + } + +} + +const _vector$7 = /*@__PURE__*/ new Vector3(); + +/** + * An alternative version of a buffer attribute with interleaved data. Interleaved + * attributes share a common interleaved data storage ({@link InterleavedBuffer}) and refer with + * different offsets into the buffer. + */ +class InterleavedBufferAttribute { + + /** + * Constructs a new interleaved buffer attribute. + * + * @param {InterleavedBuffer} interleavedBuffer - The buffer holding the interleaved data. + * @param {number} itemSize - The item size. + * @param {number} offset - The attribute offset into the buffer. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + */ + constructor( interleavedBuffer, itemSize, offset, normalized = false ) { + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isInterleavedBufferAttribute = true; + + /** + * The name of the buffer attribute. + * + * @type {string} + */ + this.name = ''; + + /** + * The buffer holding the interleaved data. + * + * @type {InterleavedBuffer} + */ + this.data = interleavedBuffer; + + /** + * The item size, see {@link BufferAttribute#itemSize}. + * + * @type {number} + */ + this.itemSize = itemSize; + + /** + * The attribute offset into the buffer. + * + * @type {number} + */ + this.offset = offset; + + /** + * Whether the data are normalized or not, see {@link BufferAttribute#normalized} + * + * @type {InterleavedBuffer} + */ + this.normalized = normalized; + + } + + /** + * The item count of this buffer attribute. + * + * @type {number} + * @readonly + */ + get count() { + + return this.data.count; + + } + + /** + * The array holding the interleaved buffer attribute data. + * + * @type {TypedArray} + */ + get array() { + + return this.data.array; + + } + + /** + * Flag to indicate that this attribute has changed and should be re-sent to + * the GPU. Set this to `true` when you modify the value of the array. + * + * @type {number} + * @default false + * @param {boolean} value + */ + set needsUpdate( value ) { + + this.data.needsUpdate = value; + + } + + /** + * Applies the given 4x4 matrix to the given attribute. Only works with + * item size `3`. + * + * @param {Matrix4} m - The matrix to apply. + * @return {InterleavedBufferAttribute} A reference to this instance. + */ + applyMatrix4( m ) { + + for ( let i = 0, l = this.data.count; i < l; i ++ ) { + + _vector$7.fromBufferAttribute( this, i ); + + _vector$7.applyMatrix4( m ); + + this.setXYZ( i, _vector$7.x, _vector$7.y, _vector$7.z ); + + } + + return this; + + } + + /** + * Applies the given 3x3 normal matrix to the given attribute. Only works with + * item size `3`. + * + * @param {Matrix3} m - The normal matrix to apply. + * @return {InterleavedBufferAttribute} A reference to this instance. + */ + applyNormalMatrix( m ) { + + for ( let i = 0, l = this.count; i < l; i ++ ) { + + _vector$7.fromBufferAttribute( this, i ); + + _vector$7.applyNormalMatrix( m ); + + this.setXYZ( i, _vector$7.x, _vector$7.y, _vector$7.z ); + + } + + return this; + + } + + /** + * Applies the given 4x4 matrix to the given attribute. Only works with + * item size `3` and with direction vectors. + * + * @param {Matrix4} m - The matrix to apply. + * @return {InterleavedBufferAttribute} A reference to this instance. + */ + transformDirection( m ) { + + for ( let i = 0, l = this.count; i < l; i ++ ) { + + _vector$7.fromBufferAttribute( this, i ); + + _vector$7.transformDirection( m ); + + this.setXYZ( i, _vector$7.x, _vector$7.y, _vector$7.z ); + + } + + return this; + + } + + /** + * Returns the given component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} component - The component index. + * @return {number} The returned value. + */ + getComponent( index, component ) { + + let value = this.array[ index * this.data.stride + this.offset + component ]; + + if ( this.normalized ) value = denormalize( value, this.array ); + + return value; + + } + + /** + * Sets the given value to the given component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} component - The component index. + * @param {number} value - The value to set. + * @return {InterleavedBufferAttribute} A reference to this instance. + */ + setComponent( index, component, value ) { + + if ( this.normalized ) value = normalize( value, this.array ); + + this.data.array[ index * this.data.stride + this.offset + component ] = value; + + return this; + + } + + /** + * Sets the x component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} x - The value to set. + * @return {InterleavedBufferAttribute} A reference to this instance. + */ + setX( index, x ) { + + if ( this.normalized ) x = normalize( x, this.array ); + + this.data.array[ index * this.data.stride + this.offset ] = x; + + return this; + + } + + /** + * Sets the y component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} y - The value to set. + * @return {InterleavedBufferAttribute} A reference to this instance. + */ + setY( index, y ) { + + if ( this.normalized ) y = normalize( y, this.array ); + + this.data.array[ index * this.data.stride + this.offset + 1 ] = y; + + return this; + + } + + /** + * Sets the z component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} z - The value to set. + * @return {InterleavedBufferAttribute} A reference to this instance. + */ + setZ( index, z ) { + + if ( this.normalized ) z = normalize( z, this.array ); + + this.data.array[ index * this.data.stride + this.offset + 2 ] = z; + + return this; + + } + + /** + * Sets the w component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} w - The value to set. + * @return {InterleavedBufferAttribute} A reference to this instance. + */ + setW( index, w ) { + + if ( this.normalized ) w = normalize( w, this.array ); + + this.data.array[ index * this.data.stride + this.offset + 3 ] = w; + + return this; + + } + + /** + * Returns the x component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @return {number} The x component. + */ + getX( index ) { + + let x = this.data.array[ index * this.data.stride + this.offset ]; + + if ( this.normalized ) x = denormalize( x, this.array ); + + return x; + + } + + /** + * Returns the y component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @return {number} The y component. + */ + getY( index ) { + + let y = this.data.array[ index * this.data.stride + this.offset + 1 ]; + + if ( this.normalized ) y = denormalize( y, this.array ); + + return y; + + } + + /** + * Returns the z component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @return {number} The z component. + */ + getZ( index ) { + + let z = this.data.array[ index * this.data.stride + this.offset + 2 ]; + + if ( this.normalized ) z = denormalize( z, this.array ); + + return z; + + } + + /** + * Returns the w component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @return {number} The w component. + */ + getW( index ) { + + let w = this.data.array[ index * this.data.stride + this.offset + 3 ]; + + if ( this.normalized ) w = denormalize( w, this.array ); + + return w; + + } + + /** + * Sets the x and y component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} x - The value for the x component to set. + * @param {number} y - The value for the y component to set. + * @return {InterleavedBufferAttribute} A reference to this instance. + */ + setXY( index, x, y ) { + + index = index * this.data.stride + this.offset; + + if ( this.normalized ) { + + x = normalize( x, this.array ); + y = normalize( y, this.array ); + + } + + this.data.array[ index + 0 ] = x; + this.data.array[ index + 1 ] = y; + + return this; + + } + + /** + * Sets the x, y and z component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} x - The value for the x component to set. + * @param {number} y - The value for the y component to set. + * @param {number} z - The value for the z component to set. + * @return {InterleavedBufferAttribute} A reference to this instance. + */ + setXYZ( index, x, y, z ) { + + index = index * this.data.stride + this.offset; + + if ( this.normalized ) { + + x = normalize( x, this.array ); + y = normalize( y, this.array ); + z = normalize( z, this.array ); + + } + + this.data.array[ index + 0 ] = x; + this.data.array[ index + 1 ] = y; + this.data.array[ index + 2 ] = z; + + return this; + + } + + /** + * Sets the x, y, z and w component of the vector at the given index. + * + * @param {number} index - The index into the buffer attribute. + * @param {number} x - The value for the x component to set. + * @param {number} y - The value for the y component to set. + * @param {number} z - The value for the z component to set. + * @param {number} w - The value for the w component to set. + * @return {InterleavedBufferAttribute} A reference to this instance. + */ + setXYZW( index, x, y, z, w ) { + + index = index * this.data.stride + this.offset; + + if ( this.normalized ) { + + x = normalize( x, this.array ); + y = normalize( y, this.array ); + z = normalize( z, this.array ); + w = normalize( w, this.array ); + + } + + this.data.array[ index + 0 ] = x; + this.data.array[ index + 1 ] = y; + this.data.array[ index + 2 ] = z; + this.data.array[ index + 3 ] = w; + + return this; + + } + + /** + * Returns a new buffer attribute with copied values from this instance. + * + * If no parameter is provided, cloning an interleaved buffer attribute will de-interleave buffer data. + * + * @param {Object} [data] - An object with interleaved buffers that allows to retain the interleaved property. + * @return {BufferAttribute|InterleavedBufferAttribute} A clone of this instance. + */ + clone( data ) { + + if ( data === undefined ) { + + console.log( 'THREE.InterleavedBufferAttribute.clone(): Cloning an interleaved buffer attribute will de-interleave buffer data.' ); + + const array = []; + + for ( let i = 0; i < this.count; i ++ ) { + + const index = i * this.data.stride + this.offset; + + for ( let j = 0; j < this.itemSize; j ++ ) { + + array.push( this.data.array[ index + j ] ); + + } + + } + + return new BufferAttribute( new this.array.constructor( array ), this.itemSize, this.normalized ); + + } else { + + if ( data.interleavedBuffers === undefined ) { + + data.interleavedBuffers = {}; + + } + + if ( data.interleavedBuffers[ this.data.uuid ] === undefined ) { + + data.interleavedBuffers[ this.data.uuid ] = this.data.clone( data ); + + } + + return new InterleavedBufferAttribute( data.interleavedBuffers[ this.data.uuid ], this.itemSize, this.offset, this.normalized ); + + } + + } + + /** + * Serializes the buffer attribute into JSON. + * + * If no parameter is provided, cloning an interleaved buffer attribute will de-interleave buffer data. + * + * @param {Object} [data] - An optional value holding meta information about the serialization. + * @return {Object} A JSON object representing the serialized buffer attribute. + */ + toJSON( data ) { + + if ( data === undefined ) { + + console.log( 'THREE.InterleavedBufferAttribute.toJSON(): Serializing an interleaved buffer attribute will de-interleave buffer data.' ); + + const array = []; + + for ( let i = 0; i < this.count; i ++ ) { + + const index = i * this.data.stride + this.offset; + + for ( let j = 0; j < this.itemSize; j ++ ) { + + array.push( this.data.array[ index + j ] ); + + } + + } + + // de-interleave data and save it as an ordinary buffer attribute for now + + return { + itemSize: this.itemSize, + type: this.array.constructor.name, + array: array, + normalized: this.normalized + }; + + } else { + + // save as true interleaved attribute + + if ( data.interleavedBuffers === undefined ) { + + data.interleavedBuffers = {}; + + } + + if ( data.interleavedBuffers[ this.data.uuid ] === undefined ) { + + data.interleavedBuffers[ this.data.uuid ] = this.data.toJSON( data ); + + } + + return { + isInterleavedBufferAttribute: true, + itemSize: this.itemSize, + data: this.data.uuid, + offset: this.offset, + normalized: this.normalized + }; + + } + + } + +} + +/** + * A material for rendering instances of {@link Sprite}. + * + * ```js + * const map = new THREE.TextureLoader().load( 'textures/sprite.png' ); + * const material = new THREE.SpriteMaterial( { map: map, color: 0xffffff } ); + * + * const sprite = new THREE.Sprite( material ); + * sprite.scale.set(200, 200, 1) + * scene.add( sprite ); + * ``` + * + * @augments Material + */ +class SpriteMaterial extends Material { + + /** + * Constructs a new sprite material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSpriteMaterial = true; + + this.type = 'SpriteMaterial'; + + /** + * Color of the material. + * + * @type {Color} + * @default (1,1,1) + */ + this.color = new Color( 0xffffff ); + + /** + * The color map. May optionally include an alpha channel, typically combined + * with {@link Material#transparent} or {@link Material#alphaTest}. The texture map + * color is modulated by the diffuse `color`. + * + * @type {?Texture} + * @default null + */ + this.map = null; + + /** + * The alpha map is a grayscale texture that controls the opacity across the + * surface (black: fully transparent; white: fully opaque). + * + * Only the color of the texture is used, ignoring the alpha channel if one + * exists. For RGB and RGBA textures, the renderer will use the green channel + * when sampling this texture due to the extra bit of precision provided for + * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and + * luminance/alpha textures will also still work as expected. + * + * @type {?Texture} + * @default null + */ + this.alphaMap = null; + + /** + * The rotation of the sprite in radians. + * + * @type {number} + * @default 0 + */ + this.rotation = 0; + + /** + * Specifies whether size of the sprite is attenuated by the camera depth (perspective camera only). + * + * @type {boolean} + * @default true + */ + this.sizeAttenuation = true; + + /** + * Overwritten since sprite materials are transparent + * by default. + * + * @type {boolean} + * @default true + */ + this.transparent = true; + + /** + * Whether the material is affected by fog or not. + * + * @type {boolean} + * @default true + */ + this.fog = true; + + this.setValues( parameters ); + + } + + copy( source ) { + + super.copy( source ); + + this.color.copy( source.color ); + + this.map = source.map; + + this.alphaMap = source.alphaMap; + + this.rotation = source.rotation; + + this.sizeAttenuation = source.sizeAttenuation; + + this.fog = source.fog; + + return this; + + } + +} + +let _geometry; + +const _intersectPoint = /*@__PURE__*/ new Vector3(); +const _worldScale = /*@__PURE__*/ new Vector3(); +const _mvPosition = /*@__PURE__*/ new Vector3(); + +const _alignedPosition = /*@__PURE__*/ new Vector2(); +const _rotatedPosition = /*@__PURE__*/ new Vector2(); +const _viewWorldMatrix = /*@__PURE__*/ new Matrix4(); + +const _vA = /*@__PURE__*/ new Vector3(); +const _vB = /*@__PURE__*/ new Vector3(); +const _vC = /*@__PURE__*/ new Vector3(); + +const _uvA = /*@__PURE__*/ new Vector2(); +const _uvB = /*@__PURE__*/ new Vector2(); +const _uvC = /*@__PURE__*/ new Vector2(); + +/** + * A sprite is a plane that always faces towards the camera, generally with a + * partially transparent texture applied. + * + * Sprites do not cast shadows, setting {@link Object3D#castShadow} to `true` will + * have no effect. + * + * ```js + * const map = new THREE.TextureLoader().load( 'sprite.png' ); + * const material = new THREE.SpriteMaterial( { map: map } ); + * + * const sprite = new THREE.Sprite( material ); + * scene.add( sprite ); + * ``` + * + * @augments Object3D + */ +class Sprite extends Object3D { + + /** + * Constructs a new sprite. + * + * @param {SpriteMaterial} [material] - The sprite material. + */ + constructor( material = new SpriteMaterial() ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSprite = true; + + this.type = 'Sprite'; + + if ( _geometry === undefined ) { + + _geometry = new BufferGeometry(); + + const float32Array = new Float32Array( [ + -0.5, -0.5, 0, 0, 0, + 0.5, -0.5, 0, 1, 0, + 0.5, 0.5, 0, 1, 1, + -0.5, 0.5, 0, 0, 1 + ] ); + + const interleavedBuffer = new InterleavedBuffer( float32Array, 5 ); + + _geometry.setIndex( [ 0, 1, 2, 0, 2, 3 ] ); + _geometry.setAttribute( 'position', new InterleavedBufferAttribute( interleavedBuffer, 3, 0, false ) ); + _geometry.setAttribute( 'uv', new InterleavedBufferAttribute( interleavedBuffer, 2, 3, false ) ); + + } + + /** + * The sprite geometry. + * + * @type {BufferGeometry} + */ + this.geometry = _geometry; + + /** + * The sprite material. + * + * @type {SpriteMaterial} + */ + this.material = material; + + /** + * The sprite's anchor point, and the point around which the sprite rotates. + * A value of `(0.5, 0.5)` corresponds to the midpoint of the sprite. A value + * of `(0, 0)` corresponds to the lower left corner of the sprite. + * + * @type {Vector2} + * @default (0.5,0.5) + */ + this.center = new Vector2( 0.5, 0.5 ); + + /** + * The number of instances of this sprite. + * Can only be used with {@link WebGPURenderer}. + * + * @type {number} + * @default 1 + */ + this.count = 1; + + } + + /** + * Computes intersection points between a casted ray and this sprite. + * + * @param {Raycaster} raycaster - The raycaster. + * @param {Array} intersects - The target array that holds the intersection points. + */ + raycast( raycaster, intersects ) { + + if ( raycaster.camera === null ) { + + console.error( 'THREE.Sprite: "Raycaster.camera" needs to be set in order to raycast against sprites.' ); + + } + + _worldScale.setFromMatrixScale( this.matrixWorld ); + + _viewWorldMatrix.copy( raycaster.camera.matrixWorld ); + this.modelViewMatrix.multiplyMatrices( raycaster.camera.matrixWorldInverse, this.matrixWorld ); + + _mvPosition.setFromMatrixPosition( this.modelViewMatrix ); + + if ( raycaster.camera.isPerspectiveCamera && this.material.sizeAttenuation === false ) { + + _worldScale.multiplyScalar( - _mvPosition.z ); + + } + + const rotation = this.material.rotation; + let sin, cos; + + if ( rotation !== 0 ) { + + cos = Math.cos( rotation ); + sin = Math.sin( rotation ); + + } + + const center = this.center; + + transformVertex( _vA.set( -0.5, -0.5, 0 ), _mvPosition, center, _worldScale, sin, cos ); + transformVertex( _vB.set( 0.5, -0.5, 0 ), _mvPosition, center, _worldScale, sin, cos ); + transformVertex( _vC.set( 0.5, 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos ); + + _uvA.set( 0, 0 ); + _uvB.set( 1, 0 ); + _uvC.set( 1, 1 ); + + // check first triangle + let intersect = raycaster.ray.intersectTriangle( _vA, _vB, _vC, false, _intersectPoint ); + + if ( intersect === null ) { + + // check second triangle + transformVertex( _vB.set( -0.5, 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos ); + _uvB.set( 0, 1 ); + + intersect = raycaster.ray.intersectTriangle( _vA, _vC, _vB, false, _intersectPoint ); + if ( intersect === null ) { + + return; + + } + + } + + const distance = raycaster.ray.origin.distanceTo( _intersectPoint ); + + if ( distance < raycaster.near || distance > raycaster.far ) return; + + intersects.push( { + + distance: distance, + point: _intersectPoint.clone(), + uv: Triangle.getInterpolation( _intersectPoint, _vA, _vB, _vC, _uvA, _uvB, _uvC, new Vector2() ), + face: null, + object: this + + } ); + + } + + copy( source, recursive ) { + + super.copy( source, recursive ); + + if ( source.center !== undefined ) this.center.copy( source.center ); + + this.material = source.material; + + return this; + + } + +} + +function transformVertex( vertexPosition, mvPosition, center, scale, sin, cos ) { + + // compute position in camera space + _alignedPosition.subVectors( vertexPosition, center ).addScalar( 0.5 ).multiply( scale ); + + // to check if rotation is not zero + if ( sin !== undefined ) { + + _rotatedPosition.x = ( cos * _alignedPosition.x ) - ( sin * _alignedPosition.y ); + _rotatedPosition.y = ( sin * _alignedPosition.x ) + ( cos * _alignedPosition.y ); + + } else { + + _rotatedPosition.copy( _alignedPosition ); + + } + + + vertexPosition.copy( mvPosition ); + vertexPosition.x += _rotatedPosition.x; + vertexPosition.y += _rotatedPosition.y; + + // transform to world space + vertexPosition.applyMatrix4( _viewWorldMatrix ); + +} + +const _v1$2 = /*@__PURE__*/ new Vector3(); +const _v2$1 = /*@__PURE__*/ new Vector3(); + +/** + * A component for providing a basic Level of Detail (LOD) mechanism. + * + * Every LOD level is associated with an object, and rendering can be switched + * between them at the distances specified. Typically you would create, say, + * three meshes, one for far away (low detail), one for mid range (medium + * detail) and one for close up (high detail). + * + * ```js + * const lod = new THREE.LOD(); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + * + * //Create spheres with 3 levels of detail and create new LOD levels for them + * for( let i = 0; i < 3; i++ ) { + * + * const geometry = new THREE.IcosahedronGeometry( 10, 3 - i ); + * const mesh = new THREE.Mesh( geometry, material ); + * lod.addLevel( mesh, i * 75 ); + * + * } + * + * scene.add( lod ); + * ``` + * + * @augments Object3D + */ +class LOD extends Object3D { + + /** + * Constructs a new LOD. + */ + constructor() { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLOD = true; + + /** + * The current LOD index. + * + * @private + * @type {number} + * @default 0 + */ + this._currentLevel = 0; + + this.type = 'LOD'; + + Object.defineProperties( this, { + /** + * This array holds the LOD levels. + * + * @name LOD#levels + * @type {Array<{object:Object3D,distance:number,hysteresis:number}>} + */ + levels: { + enumerable: true, + value: [] + } + } ); + + /** + * Whether the LOD object is updated automatically by the renderer per frame + * or not. If set to `false`, you have to call {@link LOD#update} in the + * render loop by yourself. + * + * @type {boolean} + * @default true + */ + this.autoUpdate = true; + + } + + copy( source ) { + + super.copy( source, false ); + + const levels = source.levels; + + for ( let i = 0, l = levels.length; i < l; i ++ ) { + + const level = levels[ i ]; + + this.addLevel( level.object.clone(), level.distance, level.hysteresis ); + + } + + this.autoUpdate = source.autoUpdate; + + return this; + + } + + /** + * Adds a mesh that will display at a certain distance and greater. Typically + * the further away the distance, the lower the detail on the mesh. + * + * @param {Object3D} object - The 3D object to display at this level. + * @param {number} [distance=0] - The distance at which to display this level of detail. + * @param {number} [hysteresis=0] - Threshold used to avoid flickering at LOD boundaries, as a fraction of distance. + * @return {LOD} A reference to this instance. + */ + addLevel( object, distance = 0, hysteresis = 0 ) { + + distance = Math.abs( distance ); + + const levels = this.levels; + + let l; + + for ( l = 0; l < levels.length; l ++ ) { + + if ( distance < levels[ l ].distance ) { + + break; + + } + + } + + levels.splice( l, 0, { distance: distance, hysteresis: hysteresis, object: object } ); + + this.add( object ); + + return this; + + } + + /** + * Removes an existing level, based on the distance from the camera. + * Returns `true` when the level has been removed. Otherwise `false`. + * + * @param {number} distance - Distance of the level to remove. + * @return {boolean} Whether the level has been removed or not. + */ + removeLevel( distance ) { + + const levels = this.levels; + + for ( let i = 0; i < levels.length; i ++ ) { + + if ( levels[ i ].distance === distance ) { + + const removedElements = levels.splice( i, 1 ); + this.remove( removedElements[ 0 ].object ); + + return true; + + } + + } + + return false; + + } + + /** + * Returns the currently active LOD level index. + * + * @return {number} The current active LOD level index. + */ + getCurrentLevel() { + + return this._currentLevel; + + } + + /** + * Returns a reference to the first 3D object that is greater than + * the given distance. + * + * @param {number} distance - The LOD distance. + * @return {Object3D|null} The found 3D object. `null` if no 3D object has been found. + */ + getObjectForDistance( distance ) { + + const levels = this.levels; + + if ( levels.length > 0 ) { + + let i, l; + + for ( i = 1, l = levels.length; i < l; i ++ ) { + + let levelDistance = levels[ i ].distance; + + if ( levels[ i ].object.visible ) { + + levelDistance -= levelDistance * levels[ i ].hysteresis; + + } + + if ( distance < levelDistance ) { + + break; + + } + + } + + return levels[ i - 1 ].object; + + } + + return null; + + } + + /** + * Computes intersection points between a casted ray and this LOD. + * + * @param {Raycaster} raycaster - The raycaster. + * @param {Array} intersects - The target array that holds the intersection points. + */ + raycast( raycaster, intersects ) { + + const levels = this.levels; + + if ( levels.length > 0 ) { + + _v1$2.setFromMatrixPosition( this.matrixWorld ); + + const distance = raycaster.ray.origin.distanceTo( _v1$2 ); + + this.getObjectForDistance( distance ).raycast( raycaster, intersects ); + + } + + } + + /** + * Updates the LOD by computing which LOD level should be visible according + * to the current distance of the given camera. + * + * @param {Camera} camera - The camera the scene is rendered with. + */ + update( camera ) { + + const levels = this.levels; + + if ( levels.length > 1 ) { + + _v1$2.setFromMatrixPosition( camera.matrixWorld ); + _v2$1.setFromMatrixPosition( this.matrixWorld ); + + const distance = _v1$2.distanceTo( _v2$1 ) / camera.zoom; + + levels[ 0 ].object.visible = true; + + let i, l; + + for ( i = 1, l = levels.length; i < l; i ++ ) { + + let levelDistance = levels[ i ].distance; + + if ( levels[ i ].object.visible ) { + + levelDistance -= levelDistance * levels[ i ].hysteresis; + + } + + if ( distance >= levelDistance ) { + + levels[ i - 1 ].object.visible = false; + levels[ i ].object.visible = true; + + } else { + + break; + + } + + } + + this._currentLevel = i - 1; + + for ( ; i < l; i ++ ) { + + levels[ i ].object.visible = false; + + } + + } + + } + + toJSON( meta ) { + + const data = super.toJSON( meta ); + + if ( this.autoUpdate === false ) data.object.autoUpdate = false; + + data.object.levels = []; + + const levels = this.levels; + + for ( let i = 0, l = levels.length; i < l; i ++ ) { + + const level = levels[ i ]; + + data.object.levels.push( { + object: level.object.uuid, + distance: level.distance, + hysteresis: level.hysteresis + } ); + + } + + return data; + + } + +} + +const _basePosition = /*@__PURE__*/ new Vector3(); + +const _skinIndex = /*@__PURE__*/ new Vector4(); +const _skinWeight = /*@__PURE__*/ new Vector4(); + +const _vector3 = /*@__PURE__*/ new Vector3(); +const _matrix4 = /*@__PURE__*/ new Matrix4(); +const _vertex = /*@__PURE__*/ new Vector3(); + +const _sphere$5 = /*@__PURE__*/ new Sphere(); +const _inverseMatrix$2 = /*@__PURE__*/ new Matrix4(); +const _ray$2 = /*@__PURE__*/ new Ray(); + +/** + * A mesh that has a {@link Skeleton} that can then be used to animate the + * vertices of the geometry with skinning/skeleton animation. + * + * Next to a valid skeleton, the skinned mesh requires skin indices and weights + * as buffer attributes in its geometry. These attribute define which bones affect a single + * vertex to a certain extend. + * + * Typically skinned meshes are not created manually but loaders like {@link GLTFLoader} + * or {@link FBXLoader } import respective models. + * + * @augments Mesh + */ +class SkinnedMesh extends Mesh { + + /** + * Constructs a new skinned mesh. + * + * @param {BufferGeometry} [geometry] - The mesh geometry. + * @param {Material|Array} [material] - The mesh material. + */ + constructor( geometry, material ) { + + super( geometry, material ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSkinnedMesh = true; + + this.type = 'SkinnedMesh'; + + /** + * `AttachedBindMode` means the skinned mesh shares the same world space as the skeleton. + * This is not true when using `DetachedBindMode` which is useful when sharing a skeleton + * across multiple skinned meshes. + * + * @type {(AttachedBindMode|DetachedBindMode)} + * @default AttachedBindMode + */ + this.bindMode = AttachedBindMode; + + /** + * The base matrix that is used for the bound bone transforms. + * + * @type {Matrix4} + */ + this.bindMatrix = new Matrix4(); + + /** + * The base matrix that is used for resetting the bound bone transforms. + * + * @type {Matrix4} + */ + this.bindMatrixInverse = new Matrix4(); + + /** + * The bounding box of the skinned mesh. Can be computed via {@link SkinnedMesh#computeBoundingBox}. + * + * @type {?Box3} + * @default null + */ + this.boundingBox = null; + + /** + * The bounding sphere of the skinned mesh. Can be computed via {@link SkinnedMesh#computeBoundingSphere}. + * + * @type {?Sphere} + * @default null + */ + this.boundingSphere = null; + + } + + /** + * Computes the bounding box of the skinned mesh, and updates {@link SkinnedMesh#boundingBox}. + * The bounding box is not automatically computed by the engine; this method must be called by your app. + * If the skinned mesh is animated, the bounding box should be recomputed per frame in order to reflect + * the current animation state. + */ + computeBoundingBox() { + + const geometry = this.geometry; + + if ( this.boundingBox === null ) { + + this.boundingBox = new Box3(); + + } + + this.boundingBox.makeEmpty(); + + const positionAttribute = geometry.getAttribute( 'position' ); + + for ( let i = 0; i < positionAttribute.count; i ++ ) { + + this.getVertexPosition( i, _vertex ); + this.boundingBox.expandByPoint( _vertex ); + + } + + } + + /** + * Computes the bounding sphere of the skinned mesh, and updates {@link SkinnedMesh#boundingSphere}. + * The bounding sphere is automatically computed by the engine once when it is needed, e.g., for ray casting + * and view frustum culling. If the skinned mesh is animated, the bounding sphere should be recomputed + * per frame in order to reflect the current animation state. + */ + computeBoundingSphere() { + + const geometry = this.geometry; + + if ( this.boundingSphere === null ) { + + this.boundingSphere = new Sphere(); + + } + + this.boundingSphere.makeEmpty(); + + const positionAttribute = geometry.getAttribute( 'position' ); + + for ( let i = 0; i < positionAttribute.count; i ++ ) { + + this.getVertexPosition( i, _vertex ); + this.boundingSphere.expandByPoint( _vertex ); + + } + + } + + copy( source, recursive ) { + + super.copy( source, recursive ); + + this.bindMode = source.bindMode; + this.bindMatrix.copy( source.bindMatrix ); + this.bindMatrixInverse.copy( source.bindMatrixInverse ); + + this.skeleton = source.skeleton; + + if ( source.boundingBox !== null ) this.boundingBox = source.boundingBox.clone(); + if ( source.boundingSphere !== null ) this.boundingSphere = source.boundingSphere.clone(); + + return this; + + } + + raycast( raycaster, intersects ) { + + const material = this.material; + const matrixWorld = this.matrixWorld; + + if ( material === undefined ) return; + + // test with bounding sphere in world space + + if ( this.boundingSphere === null ) this.computeBoundingSphere(); + + _sphere$5.copy( this.boundingSphere ); + _sphere$5.applyMatrix4( matrixWorld ); + + if ( raycaster.ray.intersectsSphere( _sphere$5 ) === false ) return; + + // convert ray to local space of skinned mesh + + _inverseMatrix$2.copy( matrixWorld ).invert(); + _ray$2.copy( raycaster.ray ).applyMatrix4( _inverseMatrix$2 ); + + // test with bounding box in local space + + if ( this.boundingBox !== null ) { + + if ( _ray$2.intersectsBox( this.boundingBox ) === false ) return; + + } + + // test for intersections with geometry + + this._computeIntersections( raycaster, intersects, _ray$2 ); + + } + + getVertexPosition( index, target ) { + + super.getVertexPosition( index, target ); + + this.applyBoneTransform( index, target ); + + return target; + + } + + /** + * Binds the given skeleton to the skinned mesh. + * + * @param {Skeleton} skeleton - The skeleton to bind. + * @param {Matrix4} [bindMatrix] - The bind matrix. If no bind matrix is provided, + * the skinned mesh's world matrix will be used instead. + */ + bind( skeleton, bindMatrix ) { + + this.skeleton = skeleton; + + if ( bindMatrix === undefined ) { + + this.updateMatrixWorld( true ); + + this.skeleton.calculateInverses(); + + bindMatrix = this.matrixWorld; + + } + + this.bindMatrix.copy( bindMatrix ); + this.bindMatrixInverse.copy( bindMatrix ).invert(); + + } + + /** + * This method sets the skinned mesh in the rest pose). + */ + pose() { + + this.skeleton.pose(); + + } + + /** + * Normalizes the skin weights which are defined as a buffer attribute + * in the skinned mesh's geometry. + */ + normalizeSkinWeights() { + + const vector = new Vector4(); + + const skinWeight = this.geometry.attributes.skinWeight; + + for ( let i = 0, l = skinWeight.count; i < l; i ++ ) { + + vector.fromBufferAttribute( skinWeight, i ); + + const scale = 1.0 / vector.manhattanLength(); + + if ( scale !== Infinity ) { + + vector.multiplyScalar( scale ); + + } else { + + vector.set( 1, 0, 0, 0 ); // do something reasonable + + } + + skinWeight.setXYZW( i, vector.x, vector.y, vector.z, vector.w ); + + } + + } + + updateMatrixWorld( force ) { + + super.updateMatrixWorld( force ); + + if ( this.bindMode === AttachedBindMode ) { + + this.bindMatrixInverse.copy( this.matrixWorld ).invert(); + + } else if ( this.bindMode === DetachedBindMode ) { + + this.bindMatrixInverse.copy( this.bindMatrix ).invert(); + + } else { + + console.warn( 'THREE.SkinnedMesh: Unrecognized bindMode: ' + this.bindMode ); + + } + + } + + /** + * Applies the bone transform associated with the given index to the given + * vertex position. Returns the updated vector. + * + * @param {number} index - The vertex index. + * @param {Vector3} target - The target object that is used to store the method's result. + * the skinned mesh's world matrix will be used instead. + * @return {Vector3} The updated vertex position. + */ + applyBoneTransform( index, target ) { + + const skeleton = this.skeleton; + const geometry = this.geometry; + + _skinIndex.fromBufferAttribute( geometry.attributes.skinIndex, index ); + _skinWeight.fromBufferAttribute( geometry.attributes.skinWeight, index ); + + _basePosition.copy( target ).applyMatrix4( this.bindMatrix ); + + target.set( 0, 0, 0 ); + + for ( let i = 0; i < 4; i ++ ) { + + const weight = _skinWeight.getComponent( i ); + + if ( weight !== 0 ) { + + const boneIndex = _skinIndex.getComponent( i ); + + _matrix4.multiplyMatrices( skeleton.bones[ boneIndex ].matrixWorld, skeleton.boneInverses[ boneIndex ] ); + + target.addScaledVector( _vector3.copy( _basePosition ).applyMatrix4( _matrix4 ), weight ); + + } + + } + + return target.applyMatrix4( this.bindMatrixInverse ); + + } + +} + +/** + * A bone which is part of a {@link Skeleton}. The skeleton in turn is used by + * the {@link SkinnedMesh}. + * + * ```js + * const root = new THREE.Bone(); + * const child = new THREE.Bone(); + * + * root.add( child ); + * child.position.y = 5; + * ``` + * + * @augments Object3D + */ +class Bone extends Object3D { + + /** + * Constructs a new bone. + */ + constructor() { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isBone = true; + + this.type = 'Bone'; + + } + +} + +/** + * Creates a texture directly from raw buffer data. + * + * The interpretation of the data depends on type and format: If the type is + * `UnsignedByteType`, a `Uint8Array` will be useful for addressing the + * texel data. If the format is `RGBAFormat`, data needs four values for + * one texel; Red, Green, Blue and Alpha (typically the opacity). + * + * @augments Texture + */ +class DataTexture extends Texture { + + /** + * Constructs a new data texture. + * + * @param {?TypedArray} [data=null] - The buffer data. + * @param {number} [width=1] - The width of the texture. + * @param {number} [height=1] - The height of the texture. + * @param {number} [format=RGBAFormat] - The texture format. + * @param {number} [type=UnsignedByteType] - The texture type. + * @param {number} [mapping=Texture.DEFAULT_MAPPING] - The texture mapping. + * @param {number} [wrapS=ClampToEdgeWrapping] - The wrapS value. + * @param {number} [wrapT=ClampToEdgeWrapping] - The wrapT value. + * @param {number} [magFilter=NearestFilter] - The mag filter value. + * @param {number} [minFilter=NearestFilter] - The min filter value. + * @param {number} [anisotropy=Texture.DEFAULT_ANISOTROPY] - The anisotropy value. + * @param {string} [colorSpace=NoColorSpace] - The color space. + */ + constructor( data = null, width = 1, height = 1, format, type, mapping, wrapS, wrapT, magFilter = NearestFilter, minFilter = NearestFilter, anisotropy, colorSpace ) { + + super( null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, colorSpace ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isDataTexture = true; + + /** + * The image definition of a data texture. + * + * @type {{data:TypedArray,width:number,height:number}} + */ + this.image = { data: data, width: width, height: height }; + + /** + * Whether to generate mipmaps (if possible) for a texture. + * + * Overwritten and set to `false` by default. + * + * @type {boolean} + * @default false + */ + this.generateMipmaps = false; + + /** + * If set to `true`, the texture is flipped along the vertical axis when + * uploaded to the GPU. + * + * Overwritten and set to `false` by default. + * + * @type {boolean} + * @default false + */ + this.flipY = false; + + /** + * Specifies the alignment requirements for the start of each pixel row in memory. + * + * Overwritten and set to `1` by default. + * + * @type {boolean} + * @default 1 + */ + this.unpackAlignment = 1; + + } + +} + +const _offsetMatrix = /*@__PURE__*/ new Matrix4(); +const _identityMatrix = /*@__PURE__*/ new Matrix4(); + +/** + * Class for representing the armatures in `three.js`. The skeleton + * is defined by a hierarchy of bones. + * + * ```js + * const bones = []; + * + * const shoulder = new THREE.Bone(); + * const elbow = new THREE.Bone(); + * const hand = new THREE.Bone(); + * + * shoulder.add( elbow ); + * elbow.add( hand ); + * + * bones.push( shoulder , elbow, hand); + * + * shoulder.position.y = -5; + * elbow.position.y = 0; + * hand.position.y = 5; + * + * const armSkeleton = new THREE.Skeleton( bones ); + * ``` + */ +class Skeleton { + + /** + * Constructs a new skeleton. + * + * @param {Array} [bones] - An array of bones. + * @param {Array} [boneInverses] - An array of bone inverse matrices. + * If not provided, these matrices will be computed automatically via {@link Skeleton#calculateInverses}. + */ + constructor( bones = [], boneInverses = [] ) { + + this.uuid = generateUUID(); + + /** + * An array of bones defining the skeleton. + * + * @type {Array} + */ + this.bones = bones.slice( 0 ); + + /** + * An array of bone inverse matrices. + * + * @type {Array} + */ + this.boneInverses = boneInverses; + + /** + * An array buffer holding the bone data. + * Input data for {@link Skeleton#boneTexture}. + * + * @type {?Float32Array} + * @default null + */ + this.boneMatrices = null; + + /** + * A texture holding the bone data for use + * in the vertex shader. + * + * @type {?DataTexture} + * @default null + */ + this.boneTexture = null; + + this.init(); + + } + + /** + * Initializes the skeleton. This method gets automatically called by the constructor + * but depending on how the skeleton is created it might be necessary to call this method + * manually. + */ + init() { + + const bones = this.bones; + const boneInverses = this.boneInverses; + + this.boneMatrices = new Float32Array( bones.length * 16 ); + + // calculate inverse bone matrices if necessary + + if ( boneInverses.length === 0 ) { + + this.calculateInverses(); + + } else { + + // handle special case + + if ( bones.length !== boneInverses.length ) { + + console.warn( 'THREE.Skeleton: Number of inverse bone matrices does not match amount of bones.' ); + + this.boneInverses = []; + + for ( let i = 0, il = this.bones.length; i < il; i ++ ) { + + this.boneInverses.push( new Matrix4() ); + + } + + } + + } + + } + + /** + * Computes the bone inverse matrices. This method resets {@link Skeleton#boneInverses} + * and fills it with new matrices. + */ + calculateInverses() { + + this.boneInverses.length = 0; + + for ( let i = 0, il = this.bones.length; i < il; i ++ ) { + + const inverse = new Matrix4(); + + if ( this.bones[ i ] ) { + + inverse.copy( this.bones[ i ].matrixWorld ).invert(); + + } + + this.boneInverses.push( inverse ); + + } + + } + + /** + * Resets the skeleton to the base pose. + */ + pose() { + + // recover the bind-time world matrices + + for ( let i = 0, il = this.bones.length; i < il; i ++ ) { + + const bone = this.bones[ i ]; + + if ( bone ) { + + bone.matrixWorld.copy( this.boneInverses[ i ] ).invert(); + + } + + } + + // compute the local matrices, positions, rotations and scales + + for ( let i = 0, il = this.bones.length; i < il; i ++ ) { + + const bone = this.bones[ i ]; + + if ( bone ) { + + if ( bone.parent && bone.parent.isBone ) { + + bone.matrix.copy( bone.parent.matrixWorld ).invert(); + bone.matrix.multiply( bone.matrixWorld ); + + } else { + + bone.matrix.copy( bone.matrixWorld ); + + } + + bone.matrix.decompose( bone.position, bone.quaternion, bone.scale ); + + } + + } + + } + + /** + * Resets the skeleton to the base pose. + */ + update() { + + const bones = this.bones; + const boneInverses = this.boneInverses; + const boneMatrices = this.boneMatrices; + const boneTexture = this.boneTexture; + + // flatten bone matrices to array + + for ( let i = 0, il = bones.length; i < il; i ++ ) { + + // compute the offset between the current and the original transform + + const matrix = bones[ i ] ? bones[ i ].matrixWorld : _identityMatrix; + + _offsetMatrix.multiplyMatrices( matrix, boneInverses[ i ] ); + _offsetMatrix.toArray( boneMatrices, i * 16 ); + + } + + if ( boneTexture !== null ) { + + boneTexture.needsUpdate = true; + + } + + } + + /** + * Returns a new skeleton with copied values from this instance. + * + * @return {Skeleton} A clone of this instance. + */ + clone() { + + return new Skeleton( this.bones, this.boneInverses ); + + } + + /** + * Computes a data texture for passing bone data to the vertex shader. + * + * @return {Skeleton} A reference of this instance. + */ + computeBoneTexture() { + + // layout (1 matrix = 4 pixels) + // RGBA RGBA RGBA RGBA (=> column1, column2, column3, column4) + // with 8x8 pixel texture max 16 bones * 4 pixels = (8 * 8) + // 16x16 pixel texture max 64 bones * 4 pixels = (16 * 16) + // 32x32 pixel texture max 256 bones * 4 pixels = (32 * 32) + // 64x64 pixel texture max 1024 bones * 4 pixels = (64 * 64) + + let size = Math.sqrt( this.bones.length * 4 ); // 4 pixels needed for 1 matrix + size = Math.ceil( size / 4 ) * 4; + size = Math.max( size, 4 ); + + const boneMatrices = new Float32Array( size * size * 4 ); // 4 floats per RGBA pixel + boneMatrices.set( this.boneMatrices ); // copy current values + + const boneTexture = new DataTexture( boneMatrices, size, size, RGBAFormat, FloatType ); + boneTexture.needsUpdate = true; + + this.boneMatrices = boneMatrices; + this.boneTexture = boneTexture; + + return this; + + } + + /** + * Searches through the skeleton's bone array and returns the first with a + * matching name. + * + * @param {string} name - The name of the bone. + * @return {Bone|undefined} The found bone. `undefined` if no bone has been found. + */ + getBoneByName( name ) { + + for ( let i = 0, il = this.bones.length; i < il; i ++ ) { + + const bone = this.bones[ i ]; + + if ( bone.name === name ) { + + return bone; + + } + + } + + return undefined; + + } + + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose( ) { + + if ( this.boneTexture !== null ) { + + this.boneTexture.dispose(); + + this.boneTexture = null; + + } + + } + + /** + * Setups the skeleton by the given JSON and bones. + * + * @param {Object} json - The skeleton as serialized JSON. + * @param {Object} bones - An array of bones. + * @return {Skeleton} A reference of this instance. + */ + fromJSON( json, bones ) { + + this.uuid = json.uuid; + + for ( let i = 0, l = json.bones.length; i < l; i ++ ) { + + const uuid = json.bones[ i ]; + let bone = bones[ uuid ]; + + if ( bone === undefined ) { + + console.warn( 'THREE.Skeleton: No bone found with UUID:', uuid ); + bone = new Bone(); + + } + + this.bones.push( bone ); + this.boneInverses.push( new Matrix4().fromArray( json.boneInverses[ i ] ) ); + + } + + this.init(); + + return this; + + } + + /** + * Serializes the skeleton into JSON. + * + * @return {Object} A JSON object representing the serialized skeleton. + * @see {@link ObjectLoader#parse} + */ + toJSON() { + + const data = { + metadata: { + version: 4.7, + type: 'Skeleton', + generator: 'Skeleton.toJSON' + }, + bones: [], + boneInverses: [] + }; + + data.uuid = this.uuid; + + const bones = this.bones; + const boneInverses = this.boneInverses; + + for ( let i = 0, l = bones.length; i < l; i ++ ) { + + const bone = bones[ i ]; + data.bones.push( bone.uuid ); + + const boneInverse = boneInverses[ i ]; + data.boneInverses.push( boneInverse.toArray() ); + + } + + return data; + + } + +} + +/** + * An instanced version of a buffer attribute. + * + * @augments BufferAttribute + */ +class InstancedBufferAttribute extends BufferAttribute { + + /** + * Constructs a new instanced buffer attribute. + * + * @param {TypedArray} array - The array holding the attribute data. + * @param {number} itemSize - The item size. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + * @param {number} [meshPerAttribute=1] - How often a value of this buffer attribute should be repeated. + */ + constructor( array, itemSize, normalized, meshPerAttribute = 1 ) { + + super( array, itemSize, normalized ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isInstancedBufferAttribute = true; + + /** + * Defines how often a value of this buffer attribute should be repeated. A + * value of one means that each value of the instanced attribute is used for + * a single instance. A value of two means that each value is used for two + * consecutive instances (and so on). + * + * @type {number} + * @default 1 + */ + this.meshPerAttribute = meshPerAttribute; + + } + + copy( source ) { + + super.copy( source ); + + this.meshPerAttribute = source.meshPerAttribute; + + return this; + + } + + toJSON() { + + const data = super.toJSON(); + + data.meshPerAttribute = this.meshPerAttribute; + + data.isInstancedBufferAttribute = true; + + return data; + + } + +} + +const _instanceLocalMatrix = /*@__PURE__*/ new Matrix4(); +const _instanceWorldMatrix = /*@__PURE__*/ new Matrix4(); + +const _instanceIntersects = []; + +const _box3 = /*@__PURE__*/ new Box3(); +const _identity = /*@__PURE__*/ new Matrix4(); +const _mesh$1 = /*@__PURE__*/ new Mesh(); +const _sphere$4 = /*@__PURE__*/ new Sphere(); + +/** + * A special version of a mesh with instanced rendering support. Use + * this class if you have to render a large number of objects with the same + * geometry and material(s) but with different world transformations. The usage + * of 'InstancedMesh' will help you to reduce the number of draw calls and thus + * improve the overall rendering performance in your application. + * + * @augments Mesh + */ +class InstancedMesh extends Mesh { + + /** + * Constructs a new instanced mesh. + * + * @param {BufferGeometry} [geometry] - The mesh geometry. + * @param {Material|Array} [material] - The mesh material. + * @param {number} count - The number of instances. + */ + constructor( geometry, material, count ) { + + super( geometry, material ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isInstancedMesh = true; + + /** + * Represents the local transformation of all instances. You have to set its + * {@link BufferAttribute#needsUpdate} flag to true if you modify instanced data + * via {@link InstancedMesh#setMatrixAt}. + * + * @type {InstancedBufferAttribute} + */ + this.instanceMatrix = new InstancedBufferAttribute( new Float32Array( count * 16 ), 16 ); + + /** + * Represents the color of all instances. You have to set its + * {@link BufferAttribute#needsUpdate} flag to true if you modify instanced data + * via {@link InstancedMesh#setColorAt}. + * + * @type {?InstancedBufferAttribute} + * @default null + */ + this.instanceColor = null; + + /** + * Represents the morph target weights of all instances. You have to set its + * {@link Texture#needsUpdate} flag to true if you modify instanced data + * via {@link InstancedMesh#setMorphAt}. + * + * @type {?DataTexture} + * @default null + */ + this.morphTexture = null; + + /** + * The number of instances. + * + * @type {number} + */ + this.count = count; + + /** + * The bounding box of the instanced mesh. Can be computed via {@link InstancedMesh#computeBoundingBox}. + * + * @type {?Box3} + * @default null + */ + this.boundingBox = null; + + /** + * The bounding sphere of the instanced mesh. Can be computed via {@link InstancedMesh#computeBoundingSphere}. + * + * @type {?Sphere} + * @default null + */ + this.boundingSphere = null; + + for ( let i = 0; i < count; i ++ ) { + + this.setMatrixAt( i, _identity ); + + } + + } + + /** + * Computes the bounding box of the instanced mesh, and updates {@link InstancedMesh#boundingBox}. + * The bounding box is not automatically computed by the engine; this method must be called by your app. + * You may need to recompute the bounding box if an instance is transformed via {@link InstancedMesh#setMatrixAt}. + */ + computeBoundingBox() { + + const geometry = this.geometry; + const count = this.count; + + if ( this.boundingBox === null ) { + + this.boundingBox = new Box3(); + + } + + if ( geometry.boundingBox === null ) { + + geometry.computeBoundingBox(); + + } + + this.boundingBox.makeEmpty(); + + for ( let i = 0; i < count; i ++ ) { + + this.getMatrixAt( i, _instanceLocalMatrix ); + + _box3.copy( geometry.boundingBox ).applyMatrix4( _instanceLocalMatrix ); + + this.boundingBox.union( _box3 ); + + } + + } + + /** + * Computes the bounding sphere of the instanced mesh, and updates {@link InstancedMesh#boundingSphere} + * The engine automatically computes the bounding sphere when it is needed, e.g., for ray casting or view frustum culling. + * You may need to recompute the bounding sphere if an instance is transformed via {@link InstancedMesh#setMatrixAt}. + */ + computeBoundingSphere() { + + const geometry = this.geometry; + const count = this.count; + + if ( this.boundingSphere === null ) { + + this.boundingSphere = new Sphere(); + + } + + if ( geometry.boundingSphere === null ) { + + geometry.computeBoundingSphere(); + + } + + this.boundingSphere.makeEmpty(); + + for ( let i = 0; i < count; i ++ ) { + + this.getMatrixAt( i, _instanceLocalMatrix ); + + _sphere$4.copy( geometry.boundingSphere ).applyMatrix4( _instanceLocalMatrix ); + + this.boundingSphere.union( _sphere$4 ); + + } + + } + + copy( source, recursive ) { + + super.copy( source, recursive ); + + this.instanceMatrix.copy( source.instanceMatrix ); + + if ( source.morphTexture !== null ) this.morphTexture = source.morphTexture.clone(); + if ( source.instanceColor !== null ) this.instanceColor = source.instanceColor.clone(); + + this.count = source.count; + + if ( source.boundingBox !== null ) this.boundingBox = source.boundingBox.clone(); + if ( source.boundingSphere !== null ) this.boundingSphere = source.boundingSphere.clone(); + + return this; + + } + + /** + * Gets the color of the defined instance. + * + * @param {number} index - The instance index. + * @param {Color} color - The target object that is used to store the method's result. + */ + getColorAt( index, color ) { + + color.fromArray( this.instanceColor.array, index * 3 ); + + } + + /** + * Gets the local transformation matrix of the defined instance. + * + * @param {number} index - The instance index. + * @param {Matrix4} matrix - The target object that is used to store the method's result. + */ + getMatrixAt( index, matrix ) { + + matrix.fromArray( this.instanceMatrix.array, index * 16 ); + + } + + /** + * Gets the morph target weights of the defined instance. + * + * @param {number} index - The instance index. + * @param {Mesh} object - The target object that is used to store the method's result. + */ + getMorphAt( index, object ) { + + const objectInfluences = object.morphTargetInfluences; + + const array = this.morphTexture.source.data.data; + + const len = objectInfluences.length + 1; // All influences + the baseInfluenceSum + + const dataIndex = index * len + 1; // Skip the baseInfluenceSum at the beginning + + for ( let i = 0; i < objectInfluences.length; i ++ ) { + + objectInfluences[ i ] = array[ dataIndex + i ]; + + } + + } + + raycast( raycaster, intersects ) { + + const matrixWorld = this.matrixWorld; + const raycastTimes = this.count; + + _mesh$1.geometry = this.geometry; + _mesh$1.material = this.material; + + if ( _mesh$1.material === undefined ) return; + + // test with bounding sphere first + + if ( this.boundingSphere === null ) this.computeBoundingSphere(); + + _sphere$4.copy( this.boundingSphere ); + _sphere$4.applyMatrix4( matrixWorld ); + + if ( raycaster.ray.intersectsSphere( _sphere$4 ) === false ) return; + + // now test each instance + + for ( let instanceId = 0; instanceId < raycastTimes; instanceId ++ ) { + + // calculate the world matrix for each instance + + this.getMatrixAt( instanceId, _instanceLocalMatrix ); + + _instanceWorldMatrix.multiplyMatrices( matrixWorld, _instanceLocalMatrix ); + + // the mesh represents this single instance + + _mesh$1.matrixWorld = _instanceWorldMatrix; + + _mesh$1.raycast( raycaster, _instanceIntersects ); + + // process the result of raycast + + for ( let i = 0, l = _instanceIntersects.length; i < l; i ++ ) { + + const intersect = _instanceIntersects[ i ]; + intersect.instanceId = instanceId; + intersect.object = this; + intersects.push( intersect ); + + } + + _instanceIntersects.length = 0; + + } + + } + + /** + * Sets the given color to the defined instance. Make sure you set the `needsUpdate` flag of + * {@link InstancedMesh#instanceColor} to `true` after updating all the colors. + * + * @param {number} index - The instance index. + * @param {Color} color - The instance color. + */ + setColorAt( index, color ) { + + if ( this.instanceColor === null ) { + + this.instanceColor = new InstancedBufferAttribute( new Float32Array( this.instanceMatrix.count * 3 ).fill( 1 ), 3 ); + + } + + color.toArray( this.instanceColor.array, index * 3 ); + + } + + /** + * Sets the given local transformation matrix to the defined instance. Make sure you set the `needsUpdate` flag of + * {@link InstancedMesh#instanceMatrix} to `true` after updating all the colors. + * + * @param {number} index - The instance index. + * @param {Matrix4} matrix - The local transformation. + */ + setMatrixAt( index, matrix ) { + + matrix.toArray( this.instanceMatrix.array, index * 16 ); + + } + + /** + * Sets the morph target weights to the defined instance. Make sure you set the `needsUpdate` flag of + * {@link InstancedMesh#morphTexture} to `true` after updating all the influences. + * + * @param {number} index - The instance index. + * @param {Mesh} object - A mesh which `morphTargetInfluences` property containing the morph target weights + * of a single instance. + */ + setMorphAt( index, object ) { + + const objectInfluences = object.morphTargetInfluences; + + const len = objectInfluences.length + 1; // morphBaseInfluence + all influences + + if ( this.morphTexture === null ) { + + this.morphTexture = new DataTexture( new Float32Array( len * this.count ), len, this.count, RedFormat, FloatType ); + + } + + const array = this.morphTexture.source.data.data; + + let morphInfluencesSum = 0; + + for ( let i = 0; i < objectInfluences.length; i ++ ) { + + morphInfluencesSum += objectInfluences[ i ]; + + } + + const morphBaseInfluence = this.geometry.morphTargetsRelative ? 1 : 1 - morphInfluencesSum; + + const dataIndex = len * index; + + array[ dataIndex ] = morphBaseInfluence; + + array.set( objectInfluences, dataIndex + 1 ); + + } + + updateMorphTargets() { + + } + + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose() { + + this.dispatchEvent( { type: 'dispose' } ); + + if ( this.morphTexture !== null ) { + + this.morphTexture.dispose(); + this.morphTexture = null; + + } + + } + +} + +const _vector1 = /*@__PURE__*/ new Vector3(); +const _vector2 = /*@__PURE__*/ new Vector3(); +const _normalMatrix = /*@__PURE__*/ new Matrix3(); + +/** + * A two dimensional surface that extends infinitely in 3D space, represented + * in [Hessian normal form]{@link http://mathworld.wolfram.com/HessianNormalForm.html} + * by a unit length normal vector and a constant. + */ +class Plane { + + /** + * Constructs a new plane. + * + * @param {Vector3} [normal=(1,0,0)] - A unit length vector defining the normal of the plane. + * @param {number} [constant=0] - The signed distance from the origin to the plane. + */ + constructor( normal = new Vector3( 1, 0, 0 ), constant = 0 ) { + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isPlane = true; + + /** + * A unit length vector defining the normal of the plane. + * + * @type {Vector3} + */ + this.normal = normal; + + /** + * The signed distance from the origin to the plane. + * + * @type {number} + * @default 0 + */ + this.constant = constant; + + } + + /** + * Sets the plane components by copying the given values. + * + * @param {Vector3} normal - The normal. + * @param {number} constant - The constant. + * @return {Plane} A reference to this plane. + */ + set( normal, constant ) { + + this.normal.copy( normal ); + this.constant = constant; + + return this; + + } + + /** + * Sets the plane components by defining `x`, `y`, `z` as the + * plane normal and `w` as the constant. + * + * @param {number} x - The value for the normal's x component. + * @param {number} y - The value for the normal's y component. + * @param {number} z - The value for the normal's z component. + * @param {number} w - The constant value. + * @return {Plane} A reference to this plane. + */ + setComponents( x, y, z, w ) { + + this.normal.set( x, y, z ); + this.constant = w; + + return this; + + } + + /** + * Sets the plane from the given normal and coplanar point (that is a point + * that lies onto the plane). + * + * @param {Vector3} normal - The normal. + * @param {Vector3} point - A coplanar point. + * @return {Plane} A reference to this plane. + */ + setFromNormalAndCoplanarPoint( normal, point ) { + + this.normal.copy( normal ); + this.constant = - point.dot( this.normal ); + + return this; + + } + + /** + * Sets the plane from three coplanar points. The winding order is + * assumed to be counter-clockwise, and determines the direction of + * the plane normal. + * + * @param {Vector3} a - The first coplanar point. + * @param {Vector3} b - The second coplanar point. + * @param {Vector3} c - The third coplanar point. + * @return {Plane} A reference to this plane. + */ + setFromCoplanarPoints( a, b, c ) { + + const normal = _vector1.subVectors( c, b ).cross( _vector2.subVectors( a, b ) ).normalize(); + + // Q: should an error be thrown if normal is zero (e.g. degenerate plane)? + + this.setFromNormalAndCoplanarPoint( normal, a ); + + return this; + + } + + /** + * Copies the values of the given plane to this instance. + * + * @param {Plane} plane - The plane to copy. + * @return {Plane} A reference to this plane. + */ + copy( plane ) { + + this.normal.copy( plane.normal ); + this.constant = plane.constant; + + return this; + + } + + /** + * Normalizes the plane normal and adjusts the constant accordingly. + * + * @return {Plane} A reference to this plane. + */ + normalize() { + + // Note: will lead to a divide by zero if the plane is invalid. + + const inverseNormalLength = 1.0 / this.normal.length(); + this.normal.multiplyScalar( inverseNormalLength ); + this.constant *= inverseNormalLength; + + return this; + + } + + /** + * Negates both the plane normal and the constant. + * + * @return {Plane} A reference to this plane. + */ + negate() { + + this.constant *= -1; + this.normal.negate(); + + return this; + + } + + /** + * Returns the signed distance from the given point to this plane. + * + * @param {Vector3} point - The point to compute the distance for. + * @return {number} The signed distance. + */ + distanceToPoint( point ) { + + return this.normal.dot( point ) + this.constant; + + } + + /** + * Returns the signed distance from the given sphere to this plane. + * + * @param {Sphere} sphere - The sphere to compute the distance for. + * @return {number} The signed distance. + */ + distanceToSphere( sphere ) { + + return this.distanceToPoint( sphere.center ) - sphere.radius; + + } + + /** + * Projects a the given point onto the plane. + * + * @param {Vector3} point - The point to project. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The projected point on the plane. + */ + projectPoint( point, target ) { + + return target.copy( point ).addScaledVector( this.normal, - this.distanceToPoint( point ) ); + + } + + /** + * Returns the intersection point of the passed line and the plane. Returns + * `null` if the line does not intersect. Returns the line's starting point if + * the line is coplanar with the plane. + * + * @param {Line3} line - The line to compute the intersection for. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {?Vector3} The intersection point. + */ + intersectLine( line, target ) { + + const direction = line.delta( _vector1 ); + + const denominator = this.normal.dot( direction ); + + if ( denominator === 0 ) { + + // line is coplanar, return origin + if ( this.distanceToPoint( line.start ) === 0 ) { + + return target.copy( line.start ); + + } + + // Unsure if this is the correct method to handle this case. + return null; + + } + + const t = - ( line.start.dot( this.normal ) + this.constant ) / denominator; + + if ( t < 0 || t > 1 ) { + + return null; + + } + + return target.copy( line.start ).addScaledVector( direction, t ); + + } + + /** + * Returns `true` if the given line segment intersects with (passes through) the plane. + * + * @param {Line3} line - The line to test. + * @return {boolean} Whether the given line segment intersects with the plane or not. + */ + intersectsLine( line ) { + + // Note: this tests if a line intersects the plane, not whether it (or its end-points) are coplanar with it. + + const startSign = this.distanceToPoint( line.start ); + const endSign = this.distanceToPoint( line.end ); + + return ( startSign < 0 && endSign > 0 ) || ( endSign < 0 && startSign > 0 ); + + } + + /** + * Returns `true` if the given bounding box intersects with the plane. + * + * @param {Box3} box - The bounding box to test. + * @return {boolean} Whether the given bounding box intersects with the plane or not. + */ + intersectsBox( box ) { + + return box.intersectsPlane( this ); + + } + + /** + * Returns `true` if the given bounding sphere intersects with the plane. + * + * @param {Sphere} sphere - The bounding sphere to test. + * @return {boolean} Whether the given bounding sphere intersects with the plane or not. + */ + intersectsSphere( sphere ) { + + return sphere.intersectsPlane( this ); + + } + + /** + * Returns a coplanar vector to the plane, by calculating the + * projection of the normal at the origin onto the plane. + * + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The coplanar point. + */ + coplanarPoint( target ) { + + return target.copy( this.normal ).multiplyScalar( - this.constant ); + + } + + /** + * Apply a 4x4 matrix to the plane. The matrix must be an affine, homogeneous transform. + * + * The optional normal matrix can be pre-computed like so: + * ```js + * const optionalNormalMatrix = new THREE.Matrix3().getNormalMatrix( matrix ); + * ``` + * + * @param {Matrix4} matrix - The transformation matrix. + * @param {Matrix4} [optionalNormalMatrix] - A pre-computed normal matrix. + * @return {Plane} A reference to this plane. + */ + applyMatrix4( matrix, optionalNormalMatrix ) { + + const normalMatrix = optionalNormalMatrix || _normalMatrix.getNormalMatrix( matrix ); + + const referencePoint = this.coplanarPoint( _vector1 ).applyMatrix4( matrix ); + + const normal = this.normal.applyMatrix3( normalMatrix ).normalize(); + + this.constant = - referencePoint.dot( normal ); + + return this; + + } + + /** + * Translates the plane by the distance defined by the given offset vector. + * Note that this only affects the plane constant and will not affect the normal vector. + * + * @param {Vector3} offset - The offset vector. + * @return {Plane} A reference to this plane. + */ + translate( offset ) { + + this.constant -= offset.dot( this.normal ); + + return this; + + } + + /** + * Returns `true` if this plane is equal with the given one. + * + * @param {Plane} plane - The plane to test for equality. + * @return {boolean} Whether this plane is equal with the given one. + */ + equals( plane ) { + + return plane.normal.equals( this.normal ) && ( plane.constant === this.constant ); + + } + + /** + * Returns a new plane with copied values from this instance. + * + * @return {Plane} A clone of this instance. + */ + clone() { + + return new this.constructor().copy( this ); + + } + +} + +const _sphere$3 = /*@__PURE__*/ new Sphere(); +const _defaultSpriteCenter = /*@__PURE__*/ new Vector2( 0.5, 0.5 ); +const _vector$6 = /*@__PURE__*/ new Vector3(); + +/** + * Frustums are used to determine what is inside the camera's field of view. + * They help speed up the rendering process - objects which lie outside a camera's + * frustum can safely be excluded from rendering. + * + * This class is mainly intended for use internally by a renderer. + */ +class Frustum { + + /** + * Constructs a new frustum. + * + * @param {Plane} [p0] - The first plane that encloses the frustum. + * @param {Plane} [p1] - The second plane that encloses the frustum. + * @param {Plane} [p2] - The third plane that encloses the frustum. + * @param {Plane} [p3] - The fourth plane that encloses the frustum. + * @param {Plane} [p4] - The fifth plane that encloses the frustum. + * @param {Plane} [p5] - The sixth plane that encloses the frustum. + */ + constructor( p0 = new Plane(), p1 = new Plane(), p2 = new Plane(), p3 = new Plane(), p4 = new Plane(), p5 = new Plane() ) { + + /** + * This array holds the planes that enclose the frustum. + * + * @type {Array} + */ + this.planes = [ p0, p1, p2, p3, p4, p5 ]; + + } + + /** + * Sets the frustum planes by copying the given planes. + * + * @param {Plane} [p0] - The first plane that encloses the frustum. + * @param {Plane} [p1] - The second plane that encloses the frustum. + * @param {Plane} [p2] - The third plane that encloses the frustum. + * @param {Plane} [p3] - The fourth plane that encloses the frustum. + * @param {Plane} [p4] - The fifth plane that encloses the frustum. + * @param {Plane} [p5] - The sixth plane that encloses the frustum. + * @return {Frustum} A reference to this frustum. + */ + set( p0, p1, p2, p3, p4, p5 ) { + + const planes = this.planes; + + planes[ 0 ].copy( p0 ); + planes[ 1 ].copy( p1 ); + planes[ 2 ].copy( p2 ); + planes[ 3 ].copy( p3 ); + planes[ 4 ].copy( p4 ); + planes[ 5 ].copy( p5 ); + + return this; + + } + + /** + * Copies the values of the given frustum to this instance. + * + * @param {Frustum} frustum - The frustum to copy. + * @return {Frustum} A reference to this frustum. + */ + copy( frustum ) { + + const planes = this.planes; + + for ( let i = 0; i < 6; i ++ ) { + + planes[ i ].copy( frustum.planes[ i ] ); + + } + + return this; + + } + + /** + * Sets the frustum planes from the given projection matrix. + * + * @param {Matrix4} m - The projection matrix. + * @param {(WebGLCoordinateSystem|WebGPUCoordinateSystem)} coordinateSystem - The coordinate system. + * @return {Frustum} A reference to this frustum. + */ + setFromProjectionMatrix( m, coordinateSystem = WebGLCoordinateSystem ) { + + const planes = this.planes; + const me = m.elements; + const me0 = me[ 0 ], me1 = me[ 1 ], me2 = me[ 2 ], me3 = me[ 3 ]; + const me4 = me[ 4 ], me5 = me[ 5 ], me6 = me[ 6 ], me7 = me[ 7 ]; + const me8 = me[ 8 ], me9 = me[ 9 ], me10 = me[ 10 ], me11 = me[ 11 ]; + const me12 = me[ 12 ], me13 = me[ 13 ], me14 = me[ 14 ], me15 = me[ 15 ]; + + planes[ 0 ].setComponents( me3 - me0, me7 - me4, me11 - me8, me15 - me12 ).normalize(); + planes[ 1 ].setComponents( me3 + me0, me7 + me4, me11 + me8, me15 + me12 ).normalize(); + planes[ 2 ].setComponents( me3 + me1, me7 + me5, me11 + me9, me15 + me13 ).normalize(); + planes[ 3 ].setComponents( me3 - me1, me7 - me5, me11 - me9, me15 - me13 ).normalize(); + planes[ 4 ].setComponents( me3 - me2, me7 - me6, me11 - me10, me15 - me14 ).normalize(); + + if ( coordinateSystem === WebGLCoordinateSystem ) { + + planes[ 5 ].setComponents( me3 + me2, me7 + me6, me11 + me10, me15 + me14 ).normalize(); + + } else if ( coordinateSystem === WebGPUCoordinateSystem ) { + + planes[ 5 ].setComponents( me2, me6, me10, me14 ).normalize(); + + } else { + + throw new Error( 'THREE.Frustum.setFromProjectionMatrix(): Invalid coordinate system: ' + coordinateSystem ); + + } + + return this; + + } + + /** + * Returns `true` if the 3D object's bounding sphere is intersecting this frustum. + * + * Note that the 3D object must have a geometry so that the bounding sphere can be calculated. + * + * @param {Object3D} object - The 3D object to test. + * @return {boolean} Whether the 3D object's bounding sphere is intersecting this frustum or not. + */ + intersectsObject( object ) { + + if ( object.boundingSphere !== undefined ) { + + if ( object.boundingSphere === null ) object.computeBoundingSphere(); + + _sphere$3.copy( object.boundingSphere ).applyMatrix4( object.matrixWorld ); + + } else { + + const geometry = object.geometry; + + if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); + + _sphere$3.copy( geometry.boundingSphere ).applyMatrix4( object.matrixWorld ); + + } + + return this.intersectsSphere( _sphere$3 ); + + } + + /** + * Returns `true` if the given sprite is intersecting this frustum. + * + * @param {Sprite} sprite - The sprite to test. + * @return {boolean} Whether the sprite is intersecting this frustum or not. + */ + intersectsSprite( sprite ) { + + _sphere$3.center.set( 0, 0, 0 ); + + const offset = _defaultSpriteCenter.distanceTo( sprite.center ); + + _sphere$3.radius = 0.7071067811865476 + offset; + _sphere$3.applyMatrix4( sprite.matrixWorld ); + + return this.intersectsSphere( _sphere$3 ); + + } + + /** + * Returns `true` if the given bounding sphere is intersecting this frustum. + * + * @param {Sphere} sphere - The bounding sphere to test. + * @return {boolean} Whether the bounding sphere is intersecting this frustum or not. + */ + intersectsSphere( sphere ) { + + const planes = this.planes; + const center = sphere.center; + const negRadius = - sphere.radius; + + for ( let i = 0; i < 6; i ++ ) { + + const distance = planes[ i ].distanceToPoint( center ); + + if ( distance < negRadius ) { + + return false; + + } + + } + + return true; + + } + + /** + * Returns `true` if the given bounding box is intersecting this frustum. + * + * @param {Box3} box - The bounding box to test. + * @return {boolean} Whether the bounding box is intersecting this frustum or not. + */ + intersectsBox( box ) { + + const planes = this.planes; + + for ( let i = 0; i < 6; i ++ ) { + + const plane = planes[ i ]; + + // corner at max distance + + _vector$6.x = plane.normal.x > 0 ? box.max.x : box.min.x; + _vector$6.y = plane.normal.y > 0 ? box.max.y : box.min.y; + _vector$6.z = plane.normal.z > 0 ? box.max.z : box.min.z; + + if ( plane.distanceToPoint( _vector$6 ) < 0 ) { + + return false; + + } + + } + + return true; + + } + + /** + * Returns `true` if the given point lies within the frustum. + * + * @param {Vector3} point - The point to test. + * @return {boolean} Whether the point lies within this frustum or not. + */ + containsPoint( point ) { + + const planes = this.planes; + + for ( let i = 0; i < 6; i ++ ) { + + if ( planes[ i ].distanceToPoint( point ) < 0 ) { + + return false; + + } + + } + + return true; + + } + + /** + * Returns a new frustum with copied values from this instance. + * + * @return {Frustum} A clone of this instance. + */ + clone() { + + return new this.constructor().copy( this ); + + } + +} + +const _projScreenMatrix$2 = /*@__PURE__*/ new Matrix4(); +const _frustum$1 = /*@__PURE__*/ new Frustum(); + +/** + * FrustumArray is used to determine if an object is visible in at least one camera + * from an array of cameras. This is particularly useful for multi-view renderers. +*/ +class FrustumArray { + + /** + * Constructs a new frustum array. + * + */ + constructor() { + + /** + * The coordinate system to use. + * + * @type {WebGLCoordinateSystem|WebGPUCoordinateSystem} + * @default WebGLCoordinateSystem + */ + this.coordinateSystem = WebGLCoordinateSystem; + + } + + /** + * Returns `true` if the 3D object's bounding sphere is intersecting any frustum + * from the camera array. + * + * @param {Object3D} object - The 3D object to test. + * @param {Object} cameraArray - An object with a cameras property containing an array of cameras. + * @return {boolean} Whether the 3D object is visible in any camera. + */ + intersectsObject( object, cameraArray ) { + + if ( ! cameraArray.isArrayCamera || cameraArray.cameras.length === 0 ) { + + return false; + + } + + for ( let i = 0; i < cameraArray.cameras.length; i ++ ) { + + const camera = cameraArray.cameras[ i ]; + + _projScreenMatrix$2.multiplyMatrices( + camera.projectionMatrix, + camera.matrixWorldInverse + ); + + _frustum$1.setFromProjectionMatrix( + _projScreenMatrix$2, + this.coordinateSystem + ); + + if ( _frustum$1.intersectsObject( object ) ) { + + return true; // Object is visible in at least one camera + + } + + } + + return false; // Not visible in any camera + + } + + /** + * Returns `true` if the given sprite is intersecting any frustum + * from the camera array. + * + * @param {Sprite} sprite - The sprite to test. + * @param {Object} cameraArray - An object with a cameras property containing an array of cameras. + * @return {boolean} Whether the sprite is visible in any camera. + */ + intersectsSprite( sprite, cameraArray ) { + + if ( ! cameraArray || ! cameraArray.cameras || cameraArray.cameras.length === 0 ) { + + return false; + + } + + for ( let i = 0; i < cameraArray.cameras.length; i ++ ) { + + const camera = cameraArray.cameras[ i ]; + + _projScreenMatrix$2.multiplyMatrices( + camera.projectionMatrix, + camera.matrixWorldInverse + ); + + _frustum$1.setFromProjectionMatrix( + _projScreenMatrix$2, + this.coordinateSystem + ); + + if ( _frustum$1.intersectsSprite( sprite ) ) { + + return true; // Sprite is visible in at least one camera + + } + + } + + return false; // Not visible in any camera + + } + + /** + * Returns `true` if the given bounding sphere is intersecting any frustum + * from the camera array. + * + * @param {Sphere} sphere - The bounding sphere to test. + * @param {Object} cameraArray - An object with a cameras property containing an array of cameras. + * @return {boolean} Whether the sphere is visible in any camera. + */ + intersectsSphere( sphere, cameraArray ) { + + if ( ! cameraArray || ! cameraArray.cameras || cameraArray.cameras.length === 0 ) { + + return false; + + } + + for ( let i = 0; i < cameraArray.cameras.length; i ++ ) { + + const camera = cameraArray.cameras[ i ]; + + _projScreenMatrix$2.multiplyMatrices( + camera.projectionMatrix, + camera.matrixWorldInverse + ); + + _frustum$1.setFromProjectionMatrix( + _projScreenMatrix$2, + this.coordinateSystem + ); + + if ( _frustum$1.intersectsSphere( sphere ) ) { + + return true; // Sphere is visible in at least one camera + + } + + } + + return false; // Not visible in any camera + + } + + /** + * Returns `true` if the given bounding box is intersecting any frustum + * from the camera array. + * + * @param {Box3} box - The bounding box to test. + * @param {Object} cameraArray - An object with a cameras property containing an array of cameras. + * @return {boolean} Whether the box is visible in any camera. + */ + intersectsBox( box, cameraArray ) { + + if ( ! cameraArray || ! cameraArray.cameras || cameraArray.cameras.length === 0 ) { + + return false; + + } + + for ( let i = 0; i < cameraArray.cameras.length; i ++ ) { + + const camera = cameraArray.cameras[ i ]; + + _projScreenMatrix$2.multiplyMatrices( + camera.projectionMatrix, + camera.matrixWorldInverse + ); + + _frustum$1.setFromProjectionMatrix( + _projScreenMatrix$2, + this.coordinateSystem + ); + + if ( _frustum$1.intersectsBox( box ) ) { + + return true; // Box is visible in at least one camera + + } + + } + + return false; // Not visible in any camera + + } + + /** + * Returns `true` if the given point lies within any frustum + * from the camera array. + * + * @param {Vector3} point - The point to test. + * @param {Object} cameraArray - An object with a cameras property containing an array of cameras. + * @return {boolean} Whether the point is visible in any camera. + */ + containsPoint( point, cameraArray ) { + + if ( ! cameraArray || ! cameraArray.cameras || cameraArray.cameras.length === 0 ) { + + return false; + + } + + for ( let i = 0; i < cameraArray.cameras.length; i ++ ) { + + const camera = cameraArray.cameras[ i ]; + + _projScreenMatrix$2.multiplyMatrices( + camera.projectionMatrix, + camera.matrixWorldInverse + ); + + _frustum$1.setFromProjectionMatrix( + _projScreenMatrix$2, + this.coordinateSystem + ); + + if ( _frustum$1.containsPoint( point ) ) { + + return true; // Point is visible in at least one camera + + } + + } + + return false; // Not visible in any camera + + } + + /** + * Returns a new frustum array with copied values from this instance. + * + * @return {FrustumArray} A clone of this instance. + */ + clone() { + + return new FrustumArray(); + + } + +} + +function ascIdSort( a, b ) { + + return a - b; + +} + +function sortOpaque( a, b ) { + + return a.z - b.z; + +} + +function sortTransparent( a, b ) { + + return b.z - a.z; + +} + +class MultiDrawRenderList { + + constructor() { + + this.index = 0; + this.pool = []; + this.list = []; + + } + + push( start, count, z, index ) { + + const pool = this.pool; + const list = this.list; + if ( this.index >= pool.length ) { + + pool.push( { + + start: -1, + count: -1, + z: -1, + index: -1, + + } ); + + } + + const item = pool[ this.index ]; + list.push( item ); + this.index ++; + + item.start = start; + item.count = count; + item.z = z; + item.index = index; + + } + + reset() { + + this.list.length = 0; + this.index = 0; + + } + +} + +const _matrix$1 = /*@__PURE__*/ new Matrix4(); +const _whiteColor = /*@__PURE__*/ new Color( 1, 1, 1 ); +const _frustum = /*@__PURE__*/ new Frustum(); +const _frustumArray = /*@__PURE__*/ new FrustumArray(); +const _box$1 = /*@__PURE__*/ new Box3(); +const _sphere$2 = /*@__PURE__*/ new Sphere(); +const _vector$5 = /*@__PURE__*/ new Vector3(); +const _forward$1 = /*@__PURE__*/ new Vector3(); +const _temp = /*@__PURE__*/ new Vector3(); +const _renderList = /*@__PURE__*/ new MultiDrawRenderList(); +const _mesh = /*@__PURE__*/ new Mesh(); +const _batchIntersects = []; + +// copies data from attribute "src" into "target" starting at "targetOffset" +function copyAttributeData( src, target, targetOffset = 0 ) { + + const itemSize = target.itemSize; + if ( src.isInterleavedBufferAttribute || src.array.constructor !== target.array.constructor ) { + + // use the component getters and setters if the array data cannot + // be copied directly + const vertexCount = src.count; + for ( let i = 0; i < vertexCount; i ++ ) { + + for ( let c = 0; c < itemSize; c ++ ) { + + target.setComponent( i + targetOffset, c, src.getComponent( i, c ) ); + + } + + } + + } else { + + // faster copy approach using typed array set function + target.array.set( src.array, targetOffset * itemSize ); + + } + + target.needsUpdate = true; + +} + +// safely copies array contents to a potentially smaller array +function copyArrayContents( src, target ) { + + if ( src.constructor !== target.constructor ) { + + // if arrays are of a different type (eg due to index size increasing) then data must be per-element copied + const len = Math.min( src.length, target.length ); + for ( let i = 0; i < len; i ++ ) { + + target[ i ] = src[ i ]; + + } + + } else { + + // if the arrays use the same data layout we can use a fast block copy + const len = Math.min( src.length, target.length ); + target.set( new src.constructor( src.buffer, 0, len ) ); + + } + +} + +/** + * A special version of a mesh with multi draw batch rendering support. Use + * this class if you have to render a large number of objects with the same + * material but with different geometries or world transformations. The usage of + * `BatchedMesh` will help you to reduce the number of draw calls and thus improve the overall + * rendering performance in your application. + * + * ```js + * const box = new THREE.BoxGeometry( 1, 1, 1 ); + * const sphere = new THREE.SphereGeometry( 1, 12, 12 ); + * const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); + * + * // initialize and add geometries into the batched mesh + * const batchedMesh = new BatchedMesh( 10, 5000, 10000, material ); + * const boxGeometryId = batchedMesh.addGeometry( box ); + * const sphereGeometryId = batchedMesh.addGeometry( sphere ); + * + * // create instances of those geometries + * const boxInstancedId1 = batchedMesh.addInstance( boxGeometryId ); + * const boxInstancedId2 = batchedMesh.addInstance( boxGeometryId ); + * + * const sphereInstancedId1 = batchedMesh.addInstance( sphereGeometryId ); + * const sphereInstancedId2 = batchedMesh.addInstance( sphereGeometryId ); + * + * // position the geometries + * batchedMesh.setMatrixAt( boxInstancedId1, boxMatrix1 ); + * batchedMesh.setMatrixAt( boxInstancedId2, boxMatrix2 ); + * + * batchedMesh.setMatrixAt( sphereInstancedId1, sphereMatrix1 ); + * batchedMesh.setMatrixAt( sphereInstancedId2, sphereMatrix2 ); + * + * scene.add( batchedMesh ); + * ``` + * + * @augments Mesh + */ +class BatchedMesh extends Mesh { + + /** + * Constructs a new batched mesh. + * + * @param {number} maxInstanceCount - The maximum number of individual instances planned to be added and rendered. + * @param {number} maxVertexCount - The maximum number of vertices to be used by all unique geometries. + * @param {number} [maxIndexCount=maxVertexCount*2] - The maximum number of indices to be used by all unique geometries + * @param {Material|Array} [material] - The mesh material. + */ + constructor( maxInstanceCount, maxVertexCount, maxIndexCount = maxVertexCount * 2, material ) { + + super( new BufferGeometry(), material ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isBatchedMesh = true; + + /** + * When set ot `true`, the individual objects of a batch are frustum culled. + * + * @type {boolean} + * @default true + */ + this.perObjectFrustumCulled = true; + + /** + * When set to `true`, the individual objects of a batch are sorted to improve overdraw-related artifacts. + * If the material is marked as "transparent" objects are rendered back to front and if not then they are + * rendered front to back. + * + * @type {boolean} + * @default true + */ + this.sortObjects = true; + + /** + * The bounding box of the batched mesh. Can be computed via {@link BatchedMesh#computeBoundingBox}. + * + * @type {?Box3} + * @default null + */ + this.boundingBox = null; + + /** + * The bounding sphere of the batched mesh. Can be computed via {@link BatchedMesh#computeBoundingSphere}. + * + * @type {?Sphere} + * @default null + */ + this.boundingSphere = null; + + /** + * Takes a sort a function that is run before render. The function takes a list of instances to + * sort and a camera. The objects in the list include a "z" field to perform a depth-ordered + * sort with. + * + * @type {?Function} + * @default null + */ + this.customSort = null; + + // stores visible, active, and geometry id per instance and reserved buffer ranges for geometries + this._instanceInfo = []; + this._geometryInfo = []; + + // instance, geometry ids that have been set as inactive, and are available to be overwritten + this._availableInstanceIds = []; + this._availableGeometryIds = []; + + // used to track where the next point is that geometry should be inserted + this._nextIndexStart = 0; + this._nextVertexStart = 0; + this._geometryCount = 0; + + // flags + this._visibilityChanged = true; + this._geometryInitialized = false; + + // cached user options + this._maxInstanceCount = maxInstanceCount; + this._maxVertexCount = maxVertexCount; + this._maxIndexCount = maxIndexCount; + + // buffers for multi draw + this._multiDrawCounts = new Int32Array( maxInstanceCount ); + this._multiDrawStarts = new Int32Array( maxInstanceCount ); + this._multiDrawCount = 0; + this._multiDrawInstances = null; + + // Local matrix per geometry by using data texture + this._matricesTexture = null; + this._indirectTexture = null; + this._colorsTexture = null; + + this._initMatricesTexture(); + this._initIndirectTexture(); + + } + + /** + * The maximum number of individual instances that can be stored in the batch. + * + * @type {number} + * @readonly + */ + get maxInstanceCount() { + + return this._maxInstanceCount; + + } + + /** + * The instance count. + * + * @type {number} + * @readonly + */ + get instanceCount() { + + return this._instanceInfo.length - this._availableInstanceIds.length; + + } + + /** + * The number of unused vertices. + * + * @type {number} + * @readonly + */ + get unusedVertexCount() { + + return this._maxVertexCount - this._nextVertexStart; + + } + + /** + * The number of unused indices. + * + * @type {number} + * @readonly + */ + get unusedIndexCount() { + + return this._maxIndexCount - this._nextIndexStart; + + } + + _initMatricesTexture() { + + // layout (1 matrix = 4 pixels) + // RGBA RGBA RGBA RGBA (=> column1, column2, column3, column4) + // with 8x8 pixel texture max 16 matrices * 4 pixels = (8 * 8) + // 16x16 pixel texture max 64 matrices * 4 pixels = (16 * 16) + // 32x32 pixel texture max 256 matrices * 4 pixels = (32 * 32) + // 64x64 pixel texture max 1024 matrices * 4 pixels = (64 * 64) + + let size = Math.sqrt( this._maxInstanceCount * 4 ); // 4 pixels needed for 1 matrix + size = Math.ceil( size / 4 ) * 4; + size = Math.max( size, 4 ); + + const matricesArray = new Float32Array( size * size * 4 ); // 4 floats per RGBA pixel + const matricesTexture = new DataTexture( matricesArray, size, size, RGBAFormat, FloatType ); + + this._matricesTexture = matricesTexture; + + } + + _initIndirectTexture() { + + let size = Math.sqrt( this._maxInstanceCount ); + size = Math.ceil( size ); + + const indirectArray = new Uint32Array( size * size ); + const indirectTexture = new DataTexture( indirectArray, size, size, RedIntegerFormat, UnsignedIntType ); + + this._indirectTexture = indirectTexture; + + } + + _initColorsTexture() { + + let size = Math.sqrt( this._maxInstanceCount ); + size = Math.ceil( size ); + + // 4 floats per RGBA pixel initialized to white + const colorsArray = new Float32Array( size * size * 4 ).fill( 1 ); + const colorsTexture = new DataTexture( colorsArray, size, size, RGBAFormat, FloatType ); + colorsTexture.colorSpace = ColorManagement.workingColorSpace; + + this._colorsTexture = colorsTexture; + + } + + _initializeGeometry( reference ) { + + const geometry = this.geometry; + const maxVertexCount = this._maxVertexCount; + const maxIndexCount = this._maxIndexCount; + if ( this._geometryInitialized === false ) { + + for ( const attributeName in reference.attributes ) { + + const srcAttribute = reference.getAttribute( attributeName ); + const { array, itemSize, normalized } = srcAttribute; + + const dstArray = new array.constructor( maxVertexCount * itemSize ); + const dstAttribute = new BufferAttribute( dstArray, itemSize, normalized ); + + geometry.setAttribute( attributeName, dstAttribute ); + + } + + if ( reference.getIndex() !== null ) { + + // Reserve last u16 index for primitive restart. + const indexArray = maxVertexCount > 65535 + ? new Uint32Array( maxIndexCount ) + : new Uint16Array( maxIndexCount ); + + geometry.setIndex( new BufferAttribute( indexArray, 1 ) ); + + } + + this._geometryInitialized = true; + + } + + } + + // Make sure the geometry is compatible with the existing combined geometry attributes + _validateGeometry( geometry ) { + + // check to ensure the geometries are using consistent attributes and indices + const batchGeometry = this.geometry; + if ( Boolean( geometry.getIndex() ) !== Boolean( batchGeometry.getIndex() ) ) { + + throw new Error( 'THREE.BatchedMesh: All geometries must consistently have "index".' ); + + } + + for ( const attributeName in batchGeometry.attributes ) { + + if ( ! geometry.hasAttribute( attributeName ) ) { + + throw new Error( `THREE.BatchedMesh: Added geometry missing "${ attributeName }". All geometries must have consistent attributes.` ); + + } + + const srcAttribute = geometry.getAttribute( attributeName ); + const dstAttribute = batchGeometry.getAttribute( attributeName ); + if ( srcAttribute.itemSize !== dstAttribute.itemSize || srcAttribute.normalized !== dstAttribute.normalized ) { + + throw new Error( 'THREE.BatchedMesh: All attributes must have a consistent itemSize and normalized value.' ); + + } + + } + + } + + /** + * Validates the instance defined by the given ID. + * + * @param {number} instanceId - The instance to validate. + */ + validateInstanceId( instanceId ) { + + const instanceInfo = this._instanceInfo; + if ( instanceId < 0 || instanceId >= instanceInfo.length || instanceInfo[ instanceId ].active === false ) { + + throw new Error( `THREE.BatchedMesh: Invalid instanceId ${instanceId}. Instance is either out of range or has been deleted.` ); + + } + + } + + /** + * Validates the geometry defined by the given ID. + * + * @param {number} geometryId - The geometry to validate. + */ + validateGeometryId( geometryId ) { + + const geometryInfoList = this._geometryInfo; + if ( geometryId < 0 || geometryId >= geometryInfoList.length || geometryInfoList[ geometryId ].active === false ) { + + throw new Error( `THREE.BatchedMesh: Invalid geometryId ${geometryId}. Geometry is either out of range or has been deleted.` ); + + } + + } + + /** + * Takes a sort a function that is run before render. The function takes a list of instances to + * sort and a camera. The objects in the list include a "z" field to perform a depth-ordered sort with. + * + * @param {Function} func - The custom sort function. + * @return {BatchedMesh} A reference to this batched mesh. + */ + setCustomSort( func ) { + + this.customSort = func; + return this; + + } + + /** + * Computes the bounding box, updating {@link BatchedMesh#boundingBox}. + * Bounding boxes aren't computed by default. They need to be explicitly computed, + * otherwise they are `null`. + */ + computeBoundingBox() { + + if ( this.boundingBox === null ) { + + this.boundingBox = new Box3(); + + } + + const boundingBox = this.boundingBox; + const instanceInfo = this._instanceInfo; + + boundingBox.makeEmpty(); + for ( let i = 0, l = instanceInfo.length; i < l; i ++ ) { + + if ( instanceInfo[ i ].active === false ) continue; + + const geometryId = instanceInfo[ i ].geometryIndex; + this.getMatrixAt( i, _matrix$1 ); + this.getBoundingBoxAt( geometryId, _box$1 ).applyMatrix4( _matrix$1 ); + boundingBox.union( _box$1 ); + + } + + } + + /** + * Computes the bounding sphere, updating {@link BatchedMesh#boundingSphere}. + * Bounding spheres aren't computed by default. They need to be explicitly computed, + * otherwise they are `null`. + */ + computeBoundingSphere() { + + if ( this.boundingSphere === null ) { + + this.boundingSphere = new Sphere(); + + } + + const boundingSphere = this.boundingSphere; + const instanceInfo = this._instanceInfo; + + boundingSphere.makeEmpty(); + for ( let i = 0, l = instanceInfo.length; i < l; i ++ ) { + + if ( instanceInfo[ i ].active === false ) continue; + + const geometryId = instanceInfo[ i ].geometryIndex; + this.getMatrixAt( i, _matrix$1 ); + this.getBoundingSphereAt( geometryId, _sphere$2 ).applyMatrix4( _matrix$1 ); + boundingSphere.union( _sphere$2 ); + + } + + } + + /** + * Adds a new instance to the batch using the geometry of the given ID and returns + * a new id referring to the new instance to be used by other functions. + * + * @param {number} geometryId - The ID of a previously added geometry via {@link BatchedMesh#addGeometry}. + * @return {number} The instance ID. + */ + addInstance( geometryId ) { + + const atCapacity = this._instanceInfo.length >= this.maxInstanceCount; + + // ensure we're not over geometry + if ( atCapacity && this._availableInstanceIds.length === 0 ) { + + throw new Error( 'THREE.BatchedMesh: Maximum item count reached.' ); + + } + + const instanceInfo = { + visible: true, + active: true, + geometryIndex: geometryId, + }; + + let drawId = null; + + // Prioritize using previously freed instance ids + if ( this._availableInstanceIds.length > 0 ) { + + this._availableInstanceIds.sort( ascIdSort ); + + drawId = this._availableInstanceIds.shift(); + this._instanceInfo[ drawId ] = instanceInfo; + + } else { + + drawId = this._instanceInfo.length; + this._instanceInfo.push( instanceInfo ); + + } + + const matricesTexture = this._matricesTexture; + _matrix$1.identity().toArray( matricesTexture.image.data, drawId * 16 ); + matricesTexture.needsUpdate = true; + + const colorsTexture = this._colorsTexture; + if ( colorsTexture ) { + + _whiteColor.toArray( colorsTexture.image.data, drawId * 4 ); + colorsTexture.needsUpdate = true; + + } + + this._visibilityChanged = true; + return drawId; + + } + + /** + * Adds the given geometry to the batch and returns the associated + * geometry id referring to it to be used in other functions. + * + * @param {BufferGeometry} geometry - The geometry to add. + * @param {number} [reservedVertexCount=-1] - Optional parameter specifying the amount of + * vertex buffer space to reserve for the added geometry. This is necessary if it is planned + * to set a new geometry at this index at a later time that is larger than the original geometry. + * Defaults to the length of the given geometry vertex buffer. + * @param {number} [reservedIndexCount=-1] - Optional parameter specifying the amount of index + * buffer space to reserve for the added geometry. This is necessary if it is planned to set a + * new geometry at this index at a later time that is larger than the original geometry. Defaults to + * the length of the given geometry index buffer. + * @return {number} The geometry ID. + */ + addGeometry( geometry, reservedVertexCount = -1, reservedIndexCount = -1 ) { + + this._initializeGeometry( geometry ); + + this._validateGeometry( geometry ); + + const geometryInfo = { + // geometry information + vertexStart: -1, + vertexCount: -1, + reservedVertexCount: -1, + + indexStart: -1, + indexCount: -1, + reservedIndexCount: -1, + + // draw range information + start: -1, + count: -1, + + // state + boundingBox: null, + boundingSphere: null, + active: true, + }; + + const geometryInfoList = this._geometryInfo; + geometryInfo.vertexStart = this._nextVertexStart; + geometryInfo.reservedVertexCount = reservedVertexCount === -1 ? geometry.getAttribute( 'position' ).count : reservedVertexCount; + + const index = geometry.getIndex(); + const hasIndex = index !== null; + if ( hasIndex ) { + + geometryInfo.indexStart = this._nextIndexStart; + geometryInfo.reservedIndexCount = reservedIndexCount === -1 ? index.count : reservedIndexCount; + + } + + if ( + geometryInfo.indexStart !== -1 && + geometryInfo.indexStart + geometryInfo.reservedIndexCount > this._maxIndexCount || + geometryInfo.vertexStart + geometryInfo.reservedVertexCount > this._maxVertexCount + ) { + + throw new Error( 'THREE.BatchedMesh: Reserved space request exceeds the maximum buffer size.' ); + + } + + // update id + let geometryId; + if ( this._availableGeometryIds.length > 0 ) { + + this._availableGeometryIds.sort( ascIdSort ); + + geometryId = this._availableGeometryIds.shift(); + geometryInfoList[ geometryId ] = geometryInfo; + + + } else { + + geometryId = this._geometryCount; + this._geometryCount ++; + geometryInfoList.push( geometryInfo ); + + } + + // update the geometry + this.setGeometryAt( geometryId, geometry ); + + // increment the next geometry position + this._nextIndexStart = geometryInfo.indexStart + geometryInfo.reservedIndexCount; + this._nextVertexStart = geometryInfo.vertexStart + geometryInfo.reservedVertexCount; + + return geometryId; + + } + + /** + * Replaces the geometry at the given ID with the provided geometry. Throws an error if there + * is not enough space reserved for geometry. Calling this will change all instances that are + * rendering that geometry. + * + * @param {number} geometryId - The ID of the geometry that should be replaced with the given geometry. + * @param {BufferGeometry} geometry - The new geometry. + * @return {number} The geometry ID. + */ + setGeometryAt( geometryId, geometry ) { + + if ( geometryId >= this._geometryCount ) { + + throw new Error( 'THREE.BatchedMesh: Maximum geometry count reached.' ); + + } + + this._validateGeometry( geometry ); + + const batchGeometry = this.geometry; + const hasIndex = batchGeometry.getIndex() !== null; + const dstIndex = batchGeometry.getIndex(); + const srcIndex = geometry.getIndex(); + const geometryInfo = this._geometryInfo[ geometryId ]; + if ( + hasIndex && + srcIndex.count > geometryInfo.reservedIndexCount || + geometry.attributes.position.count > geometryInfo.reservedVertexCount + ) { + + throw new Error( 'THREE.BatchedMesh: Reserved space not large enough for provided geometry.' ); + + } + + // copy geometry buffer data over + const vertexStart = geometryInfo.vertexStart; + const reservedVertexCount = geometryInfo.reservedVertexCount; + geometryInfo.vertexCount = geometry.getAttribute( 'position' ).count; + + for ( const attributeName in batchGeometry.attributes ) { + + // copy attribute data + const srcAttribute = geometry.getAttribute( attributeName ); + const dstAttribute = batchGeometry.getAttribute( attributeName ); + copyAttributeData( srcAttribute, dstAttribute, vertexStart ); + + // fill the rest in with zeroes + const itemSize = srcAttribute.itemSize; + for ( let i = srcAttribute.count, l = reservedVertexCount; i < l; i ++ ) { + + const index = vertexStart + i; + for ( let c = 0; c < itemSize; c ++ ) { + + dstAttribute.setComponent( index, c, 0 ); + + } + + } + + dstAttribute.needsUpdate = true; + dstAttribute.addUpdateRange( vertexStart * itemSize, reservedVertexCount * itemSize ); + + } + + // copy index + if ( hasIndex ) { + + const indexStart = geometryInfo.indexStart; + const reservedIndexCount = geometryInfo.reservedIndexCount; + geometryInfo.indexCount = geometry.getIndex().count; + + // copy index data over + for ( let i = 0; i < srcIndex.count; i ++ ) { + + dstIndex.setX( indexStart + i, vertexStart + srcIndex.getX( i ) ); + + } + + // fill the rest in with zeroes + for ( let i = srcIndex.count, l = reservedIndexCount; i < l; i ++ ) { + + dstIndex.setX( indexStart + i, vertexStart ); + + } + + dstIndex.needsUpdate = true; + dstIndex.addUpdateRange( indexStart, geometryInfo.reservedIndexCount ); + + } + + // update the draw range + geometryInfo.start = hasIndex ? geometryInfo.indexStart : geometryInfo.vertexStart; + geometryInfo.count = hasIndex ? geometryInfo.indexCount : geometryInfo.vertexCount; + + // store the bounding boxes + geometryInfo.boundingBox = null; + if ( geometry.boundingBox !== null ) { + + geometryInfo.boundingBox = geometry.boundingBox.clone(); + + } + + geometryInfo.boundingSphere = null; + if ( geometry.boundingSphere !== null ) { + + geometryInfo.boundingSphere = geometry.boundingSphere.clone(); + + } + + this._visibilityChanged = true; + return geometryId; + + } + + /** + * Deletes the geometry defined by the given ID from this batch. Any instances referencing + * this geometry will also be removed as a side effect. + * + * @param {number} geometryId - The ID of the geometry to remove from the batch. + * @return {BatchedMesh} A reference to this batched mesh. + */ + deleteGeometry( geometryId ) { + + const geometryInfoList = this._geometryInfo; + if ( geometryId >= geometryInfoList.length || geometryInfoList[ geometryId ].active === false ) { + + return this; + + } + + // delete any instances associated with this geometry + const instanceInfo = this._instanceInfo; + for ( let i = 0, l = instanceInfo.length; i < l; i ++ ) { + + if ( instanceInfo[ i ].active && instanceInfo[ i ].geometryIndex === geometryId ) { + + this.deleteInstance( i ); + + } + + } + + geometryInfoList[ geometryId ].active = false; + this._availableGeometryIds.push( geometryId ); + this._visibilityChanged = true; + + return this; + + } + + /** + * Deletes an existing instance from the batch using the given ID. + * + * @param {number} instanceId - The ID of the instance to remove from the batch. + * @return {BatchedMesh} A reference to this batched mesh. + */ + deleteInstance( instanceId ) { + + this.validateInstanceId( instanceId ); + + this._instanceInfo[ instanceId ].active = false; + this._availableInstanceIds.push( instanceId ); + this._visibilityChanged = true; + + return this; + + } + + /** + * Repacks the sub geometries in [name] to remove any unused space remaining from + * previously deleted geometry, freeing up space to add new geometry. + * + * @param {number} instanceId - The ID of the instance to remove from the batch. + * @return {BatchedMesh} A reference to this batched mesh. + */ + optimize() { + + // track the next indices to copy data to + let nextVertexStart = 0; + let nextIndexStart = 0; + + // Iterate over all geometry ranges in order sorted from earliest in the geometry buffer to latest + // in the geometry buffer. Because draw range objects can be reused there is no guarantee of their order. + const geometryInfoList = this._geometryInfo; + const indices = geometryInfoList + .map( ( e, i ) => i ) + .sort( ( a, b ) => { + + return geometryInfoList[ a ].vertexStart - geometryInfoList[ b ].vertexStart; + + } ); + + const geometry = this.geometry; + for ( let i = 0, l = geometryInfoList.length; i < l; i ++ ) { + + // if a geometry range is inactive then don't copy anything + const index = indices[ i ]; + const geometryInfo = geometryInfoList[ index ]; + if ( geometryInfo.active === false ) { + + continue; + + } + + // if a geometry contains an index buffer then shift it, as well + if ( geometry.index !== null ) { + + if ( geometryInfo.indexStart !== nextIndexStart ) { + + const { indexStart, vertexStart, reservedIndexCount } = geometryInfo; + const index = geometry.index; + const array = index.array; + + // shift the index pointers based on how the vertex data will shift + // adjusting the index must happen first so the original vertex start value is available + const elementDelta = nextVertexStart - vertexStart; + for ( let j = indexStart; j < indexStart + reservedIndexCount; j ++ ) { + + array[ j ] = array[ j ] + elementDelta; + + } + + index.array.copyWithin( nextIndexStart, indexStart, indexStart + reservedIndexCount ); + index.addUpdateRange( nextIndexStart, reservedIndexCount ); + + geometryInfo.indexStart = nextIndexStart; + + } + + nextIndexStart += geometryInfo.reservedIndexCount; + + } + + // if a geometry needs to be moved then copy attribute data to overwrite unused space + if ( geometryInfo.vertexStart !== nextVertexStart ) { + + const { vertexStart, reservedVertexCount } = geometryInfo; + const attributes = geometry.attributes; + for ( const key in attributes ) { + + const attribute = attributes[ key ]; + const { array, itemSize } = attribute; + array.copyWithin( nextVertexStart * itemSize, vertexStart * itemSize, ( vertexStart + reservedVertexCount ) * itemSize ); + attribute.addUpdateRange( nextVertexStart * itemSize, reservedVertexCount * itemSize ); + + } + + geometryInfo.vertexStart = nextVertexStart; + + } + + nextVertexStart += geometryInfo.reservedVertexCount; + geometryInfo.start = geometry.index ? geometryInfo.indexStart : geometryInfo.vertexStart; + + // step the next geometry points to the shifted position + this._nextIndexStart = geometry.index ? geometryInfo.indexStart + geometryInfo.reservedIndexCount : 0; + this._nextVertexStart = geometryInfo.vertexStart + geometryInfo.reservedVertexCount; + + } + + return this; + + } + + /** + * Returns the bounding box for the given geometry. + * + * @param {number} geometryId - The ID of the geometry to return the bounding box for. + * @param {Box3} target - The target object that is used to store the method's result. + * @return {Box3|null} The geometry's bounding box. Returns `null` if no geometry has been found for the given ID. + */ + getBoundingBoxAt( geometryId, target ) { + + if ( geometryId >= this._geometryCount ) { + + return null; + + } + + // compute bounding box + const geometry = this.geometry; + const geometryInfo = this._geometryInfo[ geometryId ]; + if ( geometryInfo.boundingBox === null ) { + + const box = new Box3(); + const index = geometry.index; + const position = geometry.attributes.position; + for ( let i = geometryInfo.start, l = geometryInfo.start + geometryInfo.count; i < l; i ++ ) { + + let iv = i; + if ( index ) { + + iv = index.getX( iv ); + + } + + box.expandByPoint( _vector$5.fromBufferAttribute( position, iv ) ); + + } + + geometryInfo.boundingBox = box; + + } + + target.copy( geometryInfo.boundingBox ); + return target; + + } + + /** + * Returns the bounding sphere for the given geometry. + * + * @param {number} geometryId - The ID of the geometry to return the bounding sphere for. + * @param {Sphere} target - The target object that is used to store the method's result. + * @return {Sphere|null} The geometry's bounding sphere. Returns `null` if no geometry has been found for the given ID. + */ + getBoundingSphereAt( geometryId, target ) { + + if ( geometryId >= this._geometryCount ) { + + return null; + + } + + // compute bounding sphere + const geometry = this.geometry; + const geometryInfo = this._geometryInfo[ geometryId ]; + if ( geometryInfo.boundingSphere === null ) { + + const sphere = new Sphere(); + this.getBoundingBoxAt( geometryId, _box$1 ); + _box$1.getCenter( sphere.center ); + + const index = geometry.index; + const position = geometry.attributes.position; + + let maxRadiusSq = 0; + for ( let i = geometryInfo.start, l = geometryInfo.start + geometryInfo.count; i < l; i ++ ) { + + let iv = i; + if ( index ) { + + iv = index.getX( iv ); + + } + + _vector$5.fromBufferAttribute( position, iv ); + maxRadiusSq = Math.max( maxRadiusSq, sphere.center.distanceToSquared( _vector$5 ) ); + + } + + sphere.radius = Math.sqrt( maxRadiusSq ); + geometryInfo.boundingSphere = sphere; + + } + + target.copy( geometryInfo.boundingSphere ); + return target; + + } + + /** + * Sets the given local transformation matrix to the defined instance. + * Negatively scaled matrices are not supported. + * + * @param {number} instanceId - The ID of an instance to set the matrix of. + * @param {Matrix4} matrix - A 4x4 matrix representing the local transformation of a single instance. + * @return {BatchedMesh} A reference to this batched mesh. + */ + setMatrixAt( instanceId, matrix ) { + + this.validateInstanceId( instanceId ); + + const matricesTexture = this._matricesTexture; + const matricesArray = this._matricesTexture.image.data; + matrix.toArray( matricesArray, instanceId * 16 ); + matricesTexture.needsUpdate = true; + + return this; + + } + + /** + * Returns the local transformation matrix of the defined instance. + * + * @param {number} instanceId - The ID of an instance to get the matrix of. + * @param {Matrix4} matrix - The target object that is used to store the method's result. + * @return {Matrix4} The instance's local transformation matrix. + */ + getMatrixAt( instanceId, matrix ) { + + this.validateInstanceId( instanceId ); + return matrix.fromArray( this._matricesTexture.image.data, instanceId * 16 ); + + } + + /** + * Sets the given color to the defined instance. + * + * @param {number} instanceId - The ID of an instance to set the color of. + * @param {Color} color - The color to set the instance to. + * @return {BatchedMesh} A reference to this batched mesh. + */ + setColorAt( instanceId, color ) { + + this.validateInstanceId( instanceId ); + + if ( this._colorsTexture === null ) { + + this._initColorsTexture(); + + } + + color.toArray( this._colorsTexture.image.data, instanceId * 4 ); + this._colorsTexture.needsUpdate = true; + + return this; + + } + + /** + * Returns the color of the defined instance. + * + * @param {number} instanceId - The ID of an instance to get the color of. + * @param {Color} color - The target object that is used to store the method's result. + * @return {Color} The instance's color. + */ + getColorAt( instanceId, color ) { + + this.validateInstanceId( instanceId ); + return color.fromArray( this._colorsTexture.image.data, instanceId * 4 ); + + } + + /** + * Sets the visibility of the instance. + * + * @param {number} instanceId - The id of the instance to set the visibility of. + * @param {boolean} visible - Whether the instance is visible or not. + * @return {BatchedMesh} A reference to this batched mesh. + */ + setVisibleAt( instanceId, visible ) { + + this.validateInstanceId( instanceId ); + + if ( this._instanceInfo[ instanceId ].visible === visible ) { + + return this; + + } + + this._instanceInfo[ instanceId ].visible = visible; + this._visibilityChanged = true; + + return this; + + } + + /** + * Returns the visibility state of the defined instance. + * + * @param {number} instanceId - The ID of an instance to get the visibility state of. + * @return {boolean} Whether the instance is visible or not. + */ + getVisibleAt( instanceId ) { + + this.validateInstanceId( instanceId ); + + return this._instanceInfo[ instanceId ].visible; + + } + + /** + * Sets the geometry ID of the instance at the given index. + * + * @param {number} instanceId - The ID of the instance to set the geometry ID of. + * @param {number} geometryId - The geometry ID to be use by the instance. + * @return {BatchedMesh} A reference to this batched mesh. + */ + setGeometryIdAt( instanceId, geometryId ) { + + this.validateInstanceId( instanceId ); + this.validateGeometryId( geometryId ); + + this._instanceInfo[ instanceId ].geometryIndex = geometryId; + + return this; + + } + + /** + * Returns the geometry ID of the defined instance. + * + * @param {number} instanceId - The ID of an instance to get the geometry ID of. + * @return {number} The instance's geometry ID. + */ + getGeometryIdAt( instanceId ) { + + this.validateInstanceId( instanceId ); + + return this._instanceInfo[ instanceId ].geometryIndex; + + } + + /** + * Get the range representing the subset of triangles related to the attached geometry, + * indicating the starting offset and count, or `null` if invalid. + * + * @param {number} geometryId - The id of the geometry to get the range of. + * @param {Object} [target] - The target object that is used to store the method's result. + * @return {{ + * vertexStart:number,vertexCount:number,reservedVertexCount:number, + * indexStart:number,indexCount:number,reservedIndexCount:number, + * start:number,count:number + * }} The result object with range data. + */ + getGeometryRangeAt( geometryId, target = {} ) { + + this.validateGeometryId( geometryId ); + + const geometryInfo = this._geometryInfo[ geometryId ]; + target.vertexStart = geometryInfo.vertexStart; + target.vertexCount = geometryInfo.vertexCount; + target.reservedVertexCount = geometryInfo.reservedVertexCount; + + target.indexStart = geometryInfo.indexStart; + target.indexCount = geometryInfo.indexCount; + target.reservedIndexCount = geometryInfo.reservedIndexCount; + + target.start = geometryInfo.start; + target.count = geometryInfo.count; + + return target; + + } + + /** + * Resizes the necessary buffers to support the provided number of instances. + * If the provided arguments shrink the number of instances but there are not enough + * unused Ids at the end of the list then an error is thrown. + * + * @param {number} maxInstanceCount - The max number of individual instances that can be added and rendered by the batch. + */ + setInstanceCount( maxInstanceCount ) { + + // shrink the available instances as much as possible + const availableInstanceIds = this._availableInstanceIds; + const instanceInfo = this._instanceInfo; + availableInstanceIds.sort( ascIdSort ); + while ( availableInstanceIds[ availableInstanceIds.length - 1 ] === instanceInfo.length ) { + + instanceInfo.pop(); + availableInstanceIds.pop(); + + } + + // throw an error if it can't be shrunk to the desired size + if ( maxInstanceCount < instanceInfo.length ) { + + throw new Error( `BatchedMesh: Instance ids outside the range ${ maxInstanceCount } are being used. Cannot shrink instance count.` ); + + } + + // copy the multi draw counts + const multiDrawCounts = new Int32Array( maxInstanceCount ); + const multiDrawStarts = new Int32Array( maxInstanceCount ); + copyArrayContents( this._multiDrawCounts, multiDrawCounts ); + copyArrayContents( this._multiDrawStarts, multiDrawStarts ); + + this._multiDrawCounts = multiDrawCounts; + this._multiDrawStarts = multiDrawStarts; + this._maxInstanceCount = maxInstanceCount; + + // update texture data for instance sampling + const indirectTexture = this._indirectTexture; + const matricesTexture = this._matricesTexture; + const colorsTexture = this._colorsTexture; + + indirectTexture.dispose(); + this._initIndirectTexture(); + copyArrayContents( indirectTexture.image.data, this._indirectTexture.image.data ); + + matricesTexture.dispose(); + this._initMatricesTexture(); + copyArrayContents( matricesTexture.image.data, this._matricesTexture.image.data ); + + if ( colorsTexture ) { + + colorsTexture.dispose(); + this._initColorsTexture(); + copyArrayContents( colorsTexture.image.data, this._colorsTexture.image.data ); + + } + + } + + /** + * Resizes the available space in the batch's vertex and index buffer attributes to the provided sizes. + * If the provided arguments shrink the geometry buffers but there is not enough unused space at the + * end of the geometry attributes then an error is thrown. + * + * @param {number} maxVertexCount - The maximum number of vertices to be used by all unique geometries to resize to. + * @param {number} maxIndexCount - The maximum number of indices to be used by all unique geometries to resize to. + */ + setGeometrySize( maxVertexCount, maxIndexCount ) { + + // Check if we can shrink to the requested vertex attribute size + const validRanges = [ ...this._geometryInfo ].filter( info => info.active ); + const requiredVertexLength = Math.max( ...validRanges.map( range => range.vertexStart + range.reservedVertexCount ) ); + if ( requiredVertexLength > maxVertexCount ) { + + throw new Error( `BatchedMesh: Geometry vertex values are being used outside the range ${ maxIndexCount }. Cannot shrink further.` ); + + } + + // Check if we can shrink to the requested index attribute size + if ( this.geometry.index ) { + + const requiredIndexLength = Math.max( ...validRanges.map( range => range.indexStart + range.reservedIndexCount ) ); + if ( requiredIndexLength > maxIndexCount ) { + + throw new Error( `BatchedMesh: Geometry index values are being used outside the range ${ maxIndexCount }. Cannot shrink further.` ); + + } + + } + + // + + // dispose of the previous geometry + const oldGeometry = this.geometry; + oldGeometry.dispose(); + + // recreate the geometry needed based on the previous variant + this._maxVertexCount = maxVertexCount; + this._maxIndexCount = maxIndexCount; + + if ( this._geometryInitialized ) { + + this._geometryInitialized = false; + this.geometry = new BufferGeometry(); + this._initializeGeometry( oldGeometry ); + + } + + // copy data from the previous geometry + const geometry = this.geometry; + if ( oldGeometry.index ) { + + copyArrayContents( oldGeometry.index.array, geometry.index.array ); + + } + + for ( const key in oldGeometry.attributes ) { + + copyArrayContents( oldGeometry.attributes[ key ].array, geometry.attributes[ key ].array ); + + } + + } + + raycast( raycaster, intersects ) { + + const instanceInfo = this._instanceInfo; + const geometryInfoList = this._geometryInfo; + const matrixWorld = this.matrixWorld; + const batchGeometry = this.geometry; + + // iterate over each geometry + _mesh.material = this.material; + _mesh.geometry.index = batchGeometry.index; + _mesh.geometry.attributes = batchGeometry.attributes; + if ( _mesh.geometry.boundingBox === null ) { + + _mesh.geometry.boundingBox = new Box3(); + + } + + if ( _mesh.geometry.boundingSphere === null ) { + + _mesh.geometry.boundingSphere = new Sphere(); + + } + + for ( let i = 0, l = instanceInfo.length; i < l; i ++ ) { + + if ( ! instanceInfo[ i ].visible || ! instanceInfo[ i ].active ) { + + continue; + + } + + const geometryId = instanceInfo[ i ].geometryIndex; + const geometryInfo = geometryInfoList[ geometryId ]; + _mesh.geometry.setDrawRange( geometryInfo.start, geometryInfo.count ); + + // get the intersects + this.getMatrixAt( i, _mesh.matrixWorld ).premultiply( matrixWorld ); + this.getBoundingBoxAt( geometryId, _mesh.geometry.boundingBox ); + this.getBoundingSphereAt( geometryId, _mesh.geometry.boundingSphere ); + _mesh.raycast( raycaster, _batchIntersects ); + + // add batch id to the intersects + for ( let j = 0, l = _batchIntersects.length; j < l; j ++ ) { + + const intersect = _batchIntersects[ j ]; + intersect.object = this; + intersect.batchId = i; + intersects.push( intersect ); + + } + + _batchIntersects.length = 0; + + } + + _mesh.material = null; + _mesh.geometry.index = null; + _mesh.geometry.attributes = {}; + _mesh.geometry.setDrawRange( 0, Infinity ); + + } + + copy( source ) { + + super.copy( source ); + + this.geometry = source.geometry.clone(); + this.perObjectFrustumCulled = source.perObjectFrustumCulled; + this.sortObjects = source.sortObjects; + this.boundingBox = source.boundingBox !== null ? source.boundingBox.clone() : null; + this.boundingSphere = source.boundingSphere !== null ? source.boundingSphere.clone() : null; + + this._geometryInfo = source._geometryInfo.map( info => ( { + ...info, + + boundingBox: info.boundingBox !== null ? info.boundingBox.clone() : null, + boundingSphere: info.boundingSphere !== null ? info.boundingSphere.clone() : null, + } ) ); + this._instanceInfo = source._instanceInfo.map( info => ( { ...info } ) ); + + this._availableInstanceIds = source._availableInstanceIds.slice(); + this._availableGeometryIds = source._availableGeometryIds.slice(); + + this._nextIndexStart = source._nextIndexStart; + this._nextVertexStart = source._nextVertexStart; + this._geometryCount = source._geometryCount; + + this._maxInstanceCount = source._maxInstanceCount; + this._maxVertexCount = source._maxVertexCount; + this._maxIndexCount = source._maxIndexCount; + + this._geometryInitialized = source._geometryInitialized; + this._multiDrawCounts = source._multiDrawCounts.slice(); + this._multiDrawStarts = source._multiDrawStarts.slice(); + + this._indirectTexture = source._indirectTexture.clone(); + this._indirectTexture.image.data = this._indirectTexture.image.data.slice(); + + this._matricesTexture = source._matricesTexture.clone(); + this._matricesTexture.image.data = this._matricesTexture.image.data.slice(); + + if ( this._colorsTexture !== null ) { + + this._colorsTexture = source._colorsTexture.clone(); + this._colorsTexture.image.data = this._colorsTexture.image.data.slice(); + + } + + return this; + + } + + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose() { + + // Assuming the geometry is not shared with other meshes + this.geometry.dispose(); + + this._matricesTexture.dispose(); + this._matricesTexture = null; + + this._indirectTexture.dispose(); + this._indirectTexture = null; + + if ( this._colorsTexture !== null ) { + + this._colorsTexture.dispose(); + this._colorsTexture = null; + + } + + } + + onBeforeRender( renderer, scene, camera, geometry, material/*, _group*/ ) { + + // if visibility has not changed and frustum culling and object sorting is not required + // then skip iterating over all items + if ( ! this._visibilityChanged && ! this.perObjectFrustumCulled && ! this.sortObjects ) { + + return; + + } + + // the indexed version of the multi draw function requires specifying the start + // offset in bytes. + const index = geometry.getIndex(); + const bytesPerElement = index === null ? 1 : index.array.BYTES_PER_ELEMENT; + + const instanceInfo = this._instanceInfo; + const multiDrawStarts = this._multiDrawStarts; + const multiDrawCounts = this._multiDrawCounts; + const geometryInfoList = this._geometryInfo; + const perObjectFrustumCulled = this.perObjectFrustumCulled; + const indirectTexture = this._indirectTexture; + const indirectArray = indirectTexture.image.data; + + const frustum = camera.isArrayCamera ? _frustumArray : _frustum; + // prepare the frustum in the local frame + if ( perObjectFrustumCulled && ! camera.isArrayCamera ) { + + _matrix$1 + .multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ) + .multiply( this.matrixWorld ); + _frustum.setFromProjectionMatrix( + _matrix$1, + renderer.coordinateSystem + ); + + } + + let multiDrawCount = 0; + if ( this.sortObjects ) { + + // get the camera position in the local frame + _matrix$1.copy( this.matrixWorld ).invert(); + _vector$5.setFromMatrixPosition( camera.matrixWorld ).applyMatrix4( _matrix$1 ); + _forward$1.set( 0, 0, -1 ).transformDirection( camera.matrixWorld ).transformDirection( _matrix$1 ); + + for ( let i = 0, l = instanceInfo.length; i < l; i ++ ) { + + if ( instanceInfo[ i ].visible && instanceInfo[ i ].active ) { + + const geometryId = instanceInfo[ i ].geometryIndex; + + // get the bounds in world space + this.getMatrixAt( i, _matrix$1 ); + this.getBoundingSphereAt( geometryId, _sphere$2 ).applyMatrix4( _matrix$1 ); + + // determine whether the batched geometry is within the frustum + let culled = false; + if ( perObjectFrustumCulled ) { + + culled = ! frustum.intersectsSphere( _sphere$2, camera ); + + } + + if ( ! culled ) { + + // get the distance from camera used for sorting + const geometryInfo = geometryInfoList[ geometryId ]; + const z = _temp.subVectors( _sphere$2.center, _vector$5 ).dot( _forward$1 ); + _renderList.push( geometryInfo.start, geometryInfo.count, z, i ); + + } + + } + + } + + // Sort the draw ranges and prep for rendering + const list = _renderList.list; + const customSort = this.customSort; + if ( customSort === null ) { + + list.sort( material.transparent ? sortTransparent : sortOpaque ); + + } else { + + customSort.call( this, list, camera ); + + } + + for ( let i = 0, l = list.length; i < l; i ++ ) { + + const item = list[ i ]; + multiDrawStarts[ multiDrawCount ] = item.start * bytesPerElement; + multiDrawCounts[ multiDrawCount ] = item.count; + indirectArray[ multiDrawCount ] = item.index; + multiDrawCount ++; + + } + + _renderList.reset(); + + } else { + + for ( let i = 0, l = instanceInfo.length; i < l; i ++ ) { + + if ( instanceInfo[ i ].visible && instanceInfo[ i ].active ) { + + const geometryId = instanceInfo[ i ].geometryIndex; + + // determine whether the batched geometry is within the frustum + let culled = false; + if ( perObjectFrustumCulled ) { + + // get the bounds in world space + this.getMatrixAt( i, _matrix$1 ); + this.getBoundingSphereAt( geometryId, _sphere$2 ).applyMatrix4( _matrix$1 ); + culled = ! frustum.intersectsSphere( _sphere$2, camera ); + + } + + if ( ! culled ) { + + const geometryInfo = geometryInfoList[ geometryId ]; + multiDrawStarts[ multiDrawCount ] = geometryInfo.start * bytesPerElement; + multiDrawCounts[ multiDrawCount ] = geometryInfo.count; + indirectArray[ multiDrawCount ] = i; + multiDrawCount ++; + + } + + } + + } + + } + + indirectTexture.needsUpdate = true; + this._multiDrawCount = multiDrawCount; + this._visibilityChanged = false; + + } + + onBeforeShadow( renderer, object, camera, shadowCamera, geometry, depthMaterial/* , group */ ) { + + this.onBeforeRender( renderer, null, shadowCamera, geometry, depthMaterial ); + + } + +} + +/** + * A material for rendering line primitives. + * + * Materials define the appearance of renderable 3D objects. + * + * ```js + * const material = new THREE.LineBasicMaterial( { color: 0xffffff } ); + * ``` + * + * @augments Material + */ +class LineBasicMaterial extends Material { + + /** + * Constructs a new line basic material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLineBasicMaterial = true; + + this.type = 'LineBasicMaterial'; + + /** + * Color of the material. + * + * @type {Color} + * @default (1,1,1) + */ + this.color = new Color( 0xffffff ); + + /** + * Sets the color of the lines using data from a texture. The texture map + * color is modulated by the diffuse `color`. + * + * @type {?Texture} + * @default null + */ + this.map = null; + + /** + * Controls line thickness or lines. + * + * Can only be used with {@link SVGRenderer}. WebGL and WebGPU + * ignore this setting and always render line primitives with a + * width of one pixel. + * + * @type {number} + * @default 1 + */ + this.linewidth = 1; + + /** + * Defines appearance of line ends. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('butt'|'round'|'square')} + * @default 'round' + */ + this.linecap = 'round'; + + /** + * Defines appearance of line joints. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ + this.linejoin = 'round'; + + /** + * Whether the material is affected by fog or not. + * + * @type {boolean} + * @default true + */ + this.fog = true; + + this.setValues( parameters ); + + } + + copy( source ) { + + super.copy( source ); + + this.color.copy( source.color ); + + this.map = source.map; + + this.linewidth = source.linewidth; + this.linecap = source.linecap; + this.linejoin = source.linejoin; + + this.fog = source.fog; + + return this; + + } + +} + +const _vStart = /*@__PURE__*/ new Vector3(); +const _vEnd = /*@__PURE__*/ new Vector3(); + +const _inverseMatrix$1 = /*@__PURE__*/ new Matrix4(); +const _ray$1 = /*@__PURE__*/ new Ray(); +const _sphere$1 = /*@__PURE__*/ new Sphere(); + +const _intersectPointOnRay = /*@__PURE__*/ new Vector3(); +const _intersectPointOnSegment = /*@__PURE__*/ new Vector3(); + +/** + * A continuous line. The line are rendered by connecting consecutive + * vertices with straight lines. + * + * ```js + * const material = new THREE.LineBasicMaterial( { color: 0x0000ff } ); + * + * const points = []; + * points.push( new THREE.Vector3( - 10, 0, 0 ) ); + * points.push( new THREE.Vector3( 0, 10, 0 ) ); + * points.push( new THREE.Vector3( 10, 0, 0 ) ); + * + * const geometry = new THREE.BufferGeometry().setFromPoints( points ); + * + * const line = new THREE.Line( geometry, material ); + * scene.add( line ); + * ``` + * + * @augments Object3D + */ +class Line extends Object3D { + + /** + * Constructs a new line. + * + * @param {BufferGeometry} [geometry] - The line geometry. + * @param {Material|Array} [material] - The line material. + */ + constructor( geometry = new BufferGeometry(), material = new LineBasicMaterial() ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLine = true; + + this.type = 'Line'; + + /** + * The line geometry. + * + * @type {BufferGeometry} + */ + this.geometry = geometry; + + /** + * The line material. + * + * @type {Material|Array} + * @default LineBasicMaterial + */ + this.material = material; + + /** + * A dictionary representing the morph targets in the geometry. The key is the + * morph targets name, the value its attribute index. This member is `undefined` + * by default and only set when morph targets are detected in the geometry. + * + * @type {Object|undefined} + * @default undefined + */ + this.morphTargetDictionary = undefined; + + /** + * An array of weights typically in the range `[0,1]` that specify how much of the morph + * is applied. This member is `undefined` by default and only set when morph targets are + * detected in the geometry. + * + * @type {Array|undefined} + * @default undefined + */ + this.morphTargetInfluences = undefined; + + this.updateMorphTargets(); + + } + + copy( source, recursive ) { + + super.copy( source, recursive ); + + this.material = Array.isArray( source.material ) ? source.material.slice() : source.material; + this.geometry = source.geometry; + + return this; + + } + + /** + * Computes an array of distance values which are necessary for rendering dashed lines. + * For each vertex in the geometry, the method calculates the cumulative length from the + * current point to the very beginning of the line. + * + * @return {Line} A reference to this line. + */ + computeLineDistances() { + + const geometry = this.geometry; + + // we assume non-indexed geometry + + if ( geometry.index === null ) { + + const positionAttribute = geometry.attributes.position; + const lineDistances = [ 0 ]; + + for ( let i = 1, l = positionAttribute.count; i < l; i ++ ) { + + _vStart.fromBufferAttribute( positionAttribute, i - 1 ); + _vEnd.fromBufferAttribute( positionAttribute, i ); + + lineDistances[ i ] = lineDistances[ i - 1 ]; + lineDistances[ i ] += _vStart.distanceTo( _vEnd ); + + } + + geometry.setAttribute( 'lineDistance', new Float32BufferAttribute( lineDistances, 1 ) ); + + } else { + + console.warn( 'THREE.Line.computeLineDistances(): Computation only possible with non-indexed BufferGeometry.' ); + + } + + return this; + + } + + /** + * Computes intersection points between a casted ray and this line. + * + * @param {Raycaster} raycaster - The raycaster. + * @param {Array} intersects - The target array that holds the intersection points. + */ + raycast( raycaster, intersects ) { + + const geometry = this.geometry; + const matrixWorld = this.matrixWorld; + const threshold = raycaster.params.Line.threshold; + const drawRange = geometry.drawRange; + + // Checking boundingSphere distance to ray + + if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); + + _sphere$1.copy( geometry.boundingSphere ); + _sphere$1.applyMatrix4( matrixWorld ); + _sphere$1.radius += threshold; + + if ( raycaster.ray.intersectsSphere( _sphere$1 ) === false ) return; + + // + + _inverseMatrix$1.copy( matrixWorld ).invert(); + _ray$1.copy( raycaster.ray ).applyMatrix4( _inverseMatrix$1 ); + + const localThreshold = threshold / ( ( this.scale.x + this.scale.y + this.scale.z ) / 3 ); + const localThresholdSq = localThreshold * localThreshold; + + const step = this.isLineSegments ? 2 : 1; + + const index = geometry.index; + const attributes = geometry.attributes; + const positionAttribute = attributes.position; + + if ( index !== null ) { + + const start = Math.max( 0, drawRange.start ); + const end = Math.min( index.count, ( drawRange.start + drawRange.count ) ); + + for ( let i = start, l = end - 1; i < l; i += step ) { + + const a = index.getX( i ); + const b = index.getX( i + 1 ); + + const intersect = checkIntersection( this, raycaster, _ray$1, localThresholdSq, a, b, i ); + + if ( intersect ) { + + intersects.push( intersect ); + + } + + } + + if ( this.isLineLoop ) { + + const a = index.getX( end - 1 ); + const b = index.getX( start ); + + const intersect = checkIntersection( this, raycaster, _ray$1, localThresholdSq, a, b, end - 1 ); + + if ( intersect ) { + + intersects.push( intersect ); + + } + + } + + } else { + + const start = Math.max( 0, drawRange.start ); + const end = Math.min( positionAttribute.count, ( drawRange.start + drawRange.count ) ); + + for ( let i = start, l = end - 1; i < l; i += step ) { + + const intersect = checkIntersection( this, raycaster, _ray$1, localThresholdSq, i, i + 1, i ); + + if ( intersect ) { + + intersects.push( intersect ); + + } + + } + + if ( this.isLineLoop ) { + + const intersect = checkIntersection( this, raycaster, _ray$1, localThresholdSq, end - 1, start, end - 1 ); + + if ( intersect ) { + + intersects.push( intersect ); + + } + + } + + } + + } + + /** + * Sets the values of {@link Line#morphTargetDictionary} and {@link Line#morphTargetInfluences} + * to make sure existing morph targets can influence this 3D object. + */ + updateMorphTargets() { + + const geometry = this.geometry; + + const morphAttributes = geometry.morphAttributes; + const keys = Object.keys( morphAttributes ); + + if ( keys.length > 0 ) { + + const morphAttribute = morphAttributes[ keys[ 0 ] ]; + + if ( morphAttribute !== undefined ) { + + this.morphTargetInfluences = []; + this.morphTargetDictionary = {}; + + for ( let m = 0, ml = morphAttribute.length; m < ml; m ++ ) { + + const name = morphAttribute[ m ].name || String( m ); + + this.morphTargetInfluences.push( 0 ); + this.morphTargetDictionary[ name ] = m; + + } + + } + + } + + } + +} + +function checkIntersection( object, raycaster, ray, thresholdSq, a, b, i ) { + + const positionAttribute = object.geometry.attributes.position; + + _vStart.fromBufferAttribute( positionAttribute, a ); + _vEnd.fromBufferAttribute( positionAttribute, b ); + + const distSq = ray.distanceSqToSegment( _vStart, _vEnd, _intersectPointOnRay, _intersectPointOnSegment ); + + if ( distSq > thresholdSq ) return; + + _intersectPointOnRay.applyMatrix4( object.matrixWorld ); // Move back to world space for distance calculation + + const distance = raycaster.ray.origin.distanceTo( _intersectPointOnRay ); + + if ( distance < raycaster.near || distance > raycaster.far ) return; + + return { + + distance: distance, + // What do we want? intersection point on the ray or on the segment?? + // point: raycaster.ray.at( distance ), + point: _intersectPointOnSegment.clone().applyMatrix4( object.matrixWorld ), + index: i, + face: null, + faceIndex: null, + barycoord: null, + object: object + + }; + +} + +const _start = /*@__PURE__*/ new Vector3(); +const _end = /*@__PURE__*/ new Vector3(); + +/** + * A series of lines drawn between pairs of vertices. + * + * @augments Line + */ +class LineSegments extends Line { + + /** + * Constructs a new line segments. + * + * @param {BufferGeometry} [geometry] - The line geometry. + * @param {Material|Array} [material] - The line material. + */ + constructor( geometry, material ) { + + super( geometry, material ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLineSegments = true; + + this.type = 'LineSegments'; + + } + + computeLineDistances() { + + const geometry = this.geometry; + + // we assume non-indexed geometry + + if ( geometry.index === null ) { + + const positionAttribute = geometry.attributes.position; + const lineDistances = []; + + for ( let i = 0, l = positionAttribute.count; i < l; i += 2 ) { + + _start.fromBufferAttribute( positionAttribute, i ); + _end.fromBufferAttribute( positionAttribute, i + 1 ); + + lineDistances[ i ] = ( i === 0 ) ? 0 : lineDistances[ i - 1 ]; + lineDistances[ i + 1 ] = lineDistances[ i ] + _start.distanceTo( _end ); + + } + + geometry.setAttribute( 'lineDistance', new Float32BufferAttribute( lineDistances, 1 ) ); + + } else { + + console.warn( 'THREE.LineSegments.computeLineDistances(): Computation only possible with non-indexed BufferGeometry.' ); + + } + + return this; + + } + +} + +/** + * A continuous line. This is nearly the same as {@link Line} the only difference + * is that the last vertex is connected with the first vertex in order to close + * the line to form a loop. + * + * @augments Line + */ +class LineLoop extends Line { + + /** + * Constructs a new line loop. + * + * @param {BufferGeometry} [geometry] - The line geometry. + * @param {Material|Array} [material] - The line material. + */ + constructor( geometry, material ) { + + super( geometry, material ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLineLoop = true; + + this.type = 'LineLoop'; + + } + +} + +/** + * A material for rendering point primitives. + * + * Materials define the appearance of renderable 3D objects. + * + * ```js + * const vertices = []; + * + * for ( let i = 0; i < 10000; i ++ ) { + * const x = THREE.MathUtils.randFloatSpread( 2000 ); + * const y = THREE.MathUtils.randFloatSpread( 2000 ); + * const z = THREE.MathUtils.randFloatSpread( 2000 ); + * + * vertices.push( x, y, z ); + * } + * + * const geometry = new THREE.BufferGeometry(); + * geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) ); + * const material = new THREE.PointsMaterial( { color: 0x888888 } ); + * const points = new THREE.Points( geometry, material ); + * scene.add( points ); + * ``` + * + * @augments Material + */ +class PointsMaterial extends Material { + + /** + * Constructs a new points material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isPointsMaterial = true; + + this.type = 'PointsMaterial'; + + /** + * Color of the material. + * + * @type {Color} + * @default (1,1,1) + */ + this.color = new Color( 0xffffff ); + + /** + * The color map. May optionally include an alpha channel, typically combined + * with {@link Material#transparent} or {@link Material#alphaTest}. The texture map + * color is modulated by the diffuse `color`. + * + * @type {?Texture} + * @default null + */ + this.map = null; + + /** + * The alpha map is a grayscale texture that controls the opacity across the + * surface (black: fully transparent; white: fully opaque). + * + * Only the color of the texture is used, ignoring the alpha channel if one + * exists. For RGB and RGBA textures, the renderer will use the green channel + * when sampling this texture due to the extra bit of precision provided for + * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and + * luminance/alpha textures will also still work as expected. + * + * @type {?Texture} + * @default null + */ + this.alphaMap = null; + + /** + * Defines the size of the points in pixels. + * + * Might be capped if the value exceeds hardware dependent parameters like [gl.ALIASED_POINT_SIZE_RANGE]{@link https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/getParamete}. + * + * @type {number} + * @default 1 + */ + this.size = 1; + + /** + * Specifies whether size of individual points is attenuated by the camera depth (perspective camera only). + * + * @type {boolean} + * @default true + */ + this.sizeAttenuation = true; + + /** + * Whether the material is affected by fog or not. + * + * @type {boolean} + * @default true + */ + this.fog = true; + + this.setValues( parameters ); + + } + + copy( source ) { + + super.copy( source ); + + this.color.copy( source.color ); + + this.map = source.map; + + this.alphaMap = source.alphaMap; + + this.size = source.size; + this.sizeAttenuation = source.sizeAttenuation; + + this.fog = source.fog; + + return this; + + } + +} + +const _inverseMatrix = /*@__PURE__*/ new Matrix4(); +const _ray = /*@__PURE__*/ new Ray(); +const _sphere = /*@__PURE__*/ new Sphere(); +const _position$2 = /*@__PURE__*/ new Vector3(); + +/** + * A class for displaying points or point clouds. + * + * @augments Object3D + */ +class Points extends Object3D { + + /** + * Constructs a new point cloud. + * + * @param {BufferGeometry} [geometry] - The points geometry. + * @param {Material|Array} [material] - The points material. + */ + constructor( geometry = new BufferGeometry(), material = new PointsMaterial() ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isPoints = true; + + this.type = 'Points'; + + /** + * The points geometry. + * + * @type {BufferGeometry} + */ + this.geometry = geometry; + + /** + * The line material. + * + * @type {Material|Array} + * @default PointsMaterial + */ + this.material = material; + + /** + * A dictionary representing the morph targets in the geometry. The key is the + * morph targets name, the value its attribute index. This member is `undefined` + * by default and only set when morph targets are detected in the geometry. + * + * @type {Object|undefined} + * @default undefined + */ + this.morphTargetDictionary = undefined; + + /** + * An array of weights typically in the range `[0,1]` that specify how much of the morph + * is applied. This member is `undefined` by default and only set when morph targets are + * detected in the geometry. + * + * @type {Array|undefined} + * @default undefined + */ + this.morphTargetInfluences = undefined; + + this.updateMorphTargets(); + + } + + copy( source, recursive ) { + + super.copy( source, recursive ); + + this.material = Array.isArray( source.material ) ? source.material.slice() : source.material; + this.geometry = source.geometry; + + return this; + + } + + /** + * Computes intersection points between a casted ray and this point cloud. + * + * @param {Raycaster} raycaster - The raycaster. + * @param {Array} intersects - The target array that holds the intersection points. + */ + raycast( raycaster, intersects ) { + + const geometry = this.geometry; + const matrixWorld = this.matrixWorld; + const threshold = raycaster.params.Points.threshold; + const drawRange = geometry.drawRange; + + // Checking boundingSphere distance to ray + + if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); + + _sphere.copy( geometry.boundingSphere ); + _sphere.applyMatrix4( matrixWorld ); + _sphere.radius += threshold; + + if ( raycaster.ray.intersectsSphere( _sphere ) === false ) return; + + // + + _inverseMatrix.copy( matrixWorld ).invert(); + _ray.copy( raycaster.ray ).applyMatrix4( _inverseMatrix ); + + const localThreshold = threshold / ( ( this.scale.x + this.scale.y + this.scale.z ) / 3 ); + const localThresholdSq = localThreshold * localThreshold; + + const index = geometry.index; + const attributes = geometry.attributes; + const positionAttribute = attributes.position; + + if ( index !== null ) { + + const start = Math.max( 0, drawRange.start ); + const end = Math.min( index.count, ( drawRange.start + drawRange.count ) ); + + for ( let i = start, il = end; i < il; i ++ ) { + + const a = index.getX( i ); + + _position$2.fromBufferAttribute( positionAttribute, a ); + + testPoint( _position$2, a, localThresholdSq, matrixWorld, raycaster, intersects, this ); + + } + + } else { + + const start = Math.max( 0, drawRange.start ); + const end = Math.min( positionAttribute.count, ( drawRange.start + drawRange.count ) ); + + for ( let i = start, l = end; i < l; i ++ ) { + + _position$2.fromBufferAttribute( positionAttribute, i ); + + testPoint( _position$2, i, localThresholdSq, matrixWorld, raycaster, intersects, this ); + + } + + } + + } + + /** + * Sets the values of {@link Points#morphTargetDictionary} and {@link Points#morphTargetInfluences} + * to make sure existing morph targets can influence this 3D object. + */ + updateMorphTargets() { + + const geometry = this.geometry; + + const morphAttributes = geometry.morphAttributes; + const keys = Object.keys( morphAttributes ); + + if ( keys.length > 0 ) { + + const morphAttribute = morphAttributes[ keys[ 0 ] ]; + + if ( morphAttribute !== undefined ) { + + this.morphTargetInfluences = []; + this.morphTargetDictionary = {}; + + for ( let m = 0, ml = morphAttribute.length; m < ml; m ++ ) { + + const name = morphAttribute[ m ].name || String( m ); + + this.morphTargetInfluences.push( 0 ); + this.morphTargetDictionary[ name ] = m; + + } + + } + + } + + } + +} + +function testPoint( point, index, localThresholdSq, matrixWorld, raycaster, intersects, object ) { + + const rayPointDistanceSq = _ray.distanceSqToPoint( point ); + + if ( rayPointDistanceSq < localThresholdSq ) { + + const intersectPoint = new Vector3(); + + _ray.closestPointToPoint( point, intersectPoint ); + intersectPoint.applyMatrix4( matrixWorld ); + + const distance = raycaster.ray.origin.distanceTo( intersectPoint ); + + if ( distance < raycaster.near || distance > raycaster.far ) return; + + intersects.push( { + + distance: distance, + distanceToRay: Math.sqrt( rayPointDistanceSq ), + point: intersectPoint, + index: index, + face: null, + faceIndex: null, + barycoord: null, + object: object + + } ); + + } + +} + +/** + * A texture for use with a video. + * + * ```js + * // assuming you have created a HTML video element with id="video" + * const video = document.getElementById( 'video' ); + * const texture = new THREE.VideoTexture( video ); + * ``` + * + * Note: After the initial use of a texture, its dimensions, format, and type + * cannot be changed. Instead, call {@link Texture#dispose} on the texture and instantiate a new one. + * + * @augments Texture + */ +class VideoTexture extends Texture { + + /** + * Constructs a new video texture. + * + * @param {HTMLVideoElement} video - The video element to use as a data source for the texture. + * @param {number} [mapping=Texture.DEFAULT_MAPPING] - The texture mapping. + * @param {number} [wrapS=ClampToEdgeWrapping] - The wrapS value. + * @param {number} [wrapT=ClampToEdgeWrapping] - The wrapT value. + * @param {number} [magFilter=LinearFilter] - The mag filter value. + * @param {number} [minFilter=LinearFilter] - The min filter value. + * @param {number} [format=RGBAFormat] - The texture format. + * @param {number} [type=UnsignedByteType] - The texture type. + * @param {number} [anisotropy=Texture.DEFAULT_ANISOTROPY] - The anisotropy value. + */ + constructor( video, mapping, wrapS, wrapT, magFilter = LinearFilter, minFilter = LinearFilter, format, type, anisotropy ) { + + super( video, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isVideoTexture = true; + + /** + * Whether to generate mipmaps (if possible) for a texture. + * + * Overwritten and set to `false` by default. + * + * @type {boolean} + * @default false + */ + this.generateMipmaps = false; + + const scope = this; + + function updateVideo() { + + scope.needsUpdate = true; + video.requestVideoFrameCallback( updateVideo ); + + } + + if ( 'requestVideoFrameCallback' in video ) { + + video.requestVideoFrameCallback( updateVideo ); + + } + + } + + clone() { + + return new this.constructor( this.image ).copy( this ); + + } + + /** + * This method is called automatically by the renderer and sets {@link Texture#needsUpdate} + * to `true` every time a new frame is available. + * + * Only relevant if `requestVideoFrameCallback` is not supported in the browser. + */ + update() { + + const video = this.image; + const hasVideoFrameCallback = 'requestVideoFrameCallback' in video; + + if ( hasVideoFrameCallback === false && video.readyState >= video.HAVE_CURRENT_DATA ) { + + this.needsUpdate = true; + + } + + } + +} + +/** + * This class can be used as an alternative way to define video data. Instead of using + * an instance of `HTMLVideoElement` like with `VideoTexture`, `VideoFrameTexture` expects each frame is + * defined manually via {@link VideoFrameTexture#setFrame}. A typical use case for this module is when + * video frames are decoded with the WebCodecs API. + * + * ```js + * const texture = new THREE.VideoFrameTexture(); + * texture.setFrame( frame ); + * ``` + * + * @augments VideoTexture + */ +class VideoFrameTexture extends VideoTexture { + + /** + * Constructs a new video frame texture. + * + * @param {number} [mapping=Texture.DEFAULT_MAPPING] - The texture mapping. + * @param {number} [wrapS=ClampToEdgeWrapping] - The wrapS value. + * @param {number} [wrapT=ClampToEdgeWrapping] - The wrapT value. + * @param {number} [magFilter=LinearFilter] - The mag filter value. + * @param {number} [minFilter=LinearFilter] - The min filter value. + * @param {number} [format=RGBAFormat] - The texture format. + * @param {number} [type=UnsignedByteType] - The texture type. + * @param {number} [anisotropy=Texture.DEFAULT_ANISOTROPY] - The anisotropy value. + */ + constructor( mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) { + + super( {}, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isVideoFrameTexture = true; + + } + + /** + * This method overwritten with an empty implementation since + * this type of texture is updated via `setFrame()`. + */ + update() {} + + clone() { + + return new this.constructor().copy( this ); // restoring Texture.clone() + + } + + /** + * Sets the current frame of the video. This will automatically update the texture + * so the data can be used for rendering. + * + * @param {VideoFrame} frame - The video frame. + */ + setFrame( frame ) { + + this.image = frame; + this.needsUpdate = true; + + } + +} + +/** + * This class can only be used in combination with `copyFramebufferToTexture()` methods + * of renderers. It extracts the contents of the current bound framebuffer and provides it + * as a texture for further usage. + * + * ```js + * const pixelRatio = window.devicePixelRatio; + * const textureSize = 128 * pixelRatio; + * + * const frameTexture = new FramebufferTexture( textureSize, textureSize ); + * + * // calculate start position for copying part of the frame data + * const vector = new Vector2(); + * vector.x = ( window.innerWidth * pixelRatio / 2 ) - ( textureSize / 2 ); + * vector.y = ( window.innerHeight * pixelRatio / 2 ) - ( textureSize / 2 ); + * + * renderer.render( scene, camera ); + * + * // copy part of the rendered frame into the framebuffer texture + * renderer.copyFramebufferToTexture( frameTexture, vector ); + * ``` + * + * @augments Texture + */ +class FramebufferTexture extends Texture { + + /** + * Constructs a new framebuffer texture. + * + * @param {number} width - The width of the texture. + * @param {number} height - The height of the texture. + */ + constructor( width, height ) { + + super( { width, height } ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isFramebufferTexture = true; + + /** + * How the texture is sampled when a texel covers more than one pixel. + * + * Overwritten and set to `NearestFilter` by default to disable filtering. + * + * @type {(NearestFilter|NearestMipmapNearestFilter|NearestMipmapLinearFilter|LinearFilter|LinearMipmapNearestFilter|LinearMipmapLinearFilter)} + * @default NearestFilter + */ + this.magFilter = NearestFilter; + + /** + * How the texture is sampled when a texel covers less than one pixel. + * + * Overwritten and set to `NearestFilter` by default to disable filtering. + * + * @type {(NearestFilter|NearestMipmapNearestFilter|NearestMipmapLinearFilter|LinearFilter|LinearMipmapNearestFilter|LinearMipmapLinearFilter)} + * @default NearestFilter + */ + this.minFilter = NearestFilter; + + /** + * Whether to generate mipmaps (if possible) for a texture. + * + * Overwritten and set to `false` by default. + * + * @type {boolean} + * @default false + */ + this.generateMipmaps = false; + + this.needsUpdate = true; + + } + +} + +/** + * Creates a texture based on data in compressed form. + * + * These texture are usually loaded with {@link CompressedTextureLoader}. + * + * @augments Texture + */ +class CompressedTexture extends Texture { + + /** + * Constructs a new compressed texture. + * + * @param {Array} mipmaps - This array holds for all mipmaps (including the bases mip) + * the data and dimensions. + * @param {number} width - The width of the texture. + * @param {number} height - The height of the texture. + * @param {number} [format=RGBAFormat] - The texture format. + * @param {number} [type=UnsignedByteType] - The texture type. + * @param {number} [mapping=Texture.DEFAULT_MAPPING] - The texture mapping. + * @param {number} [wrapS=ClampToEdgeWrapping] - The wrapS value. + * @param {number} [wrapT=ClampToEdgeWrapping] - The wrapT value. + * @param {number} [magFilter=LinearFilter] - The mag filter value. + * @param {number} [minFilter=LinearMipmapLinearFilter] - The min filter value. + * @param {number} [anisotropy=Texture.DEFAULT_ANISOTROPY] - The anisotropy value. + * @param {string} [colorSpace=NoColorSpace] - The color space. + */ + constructor( mipmaps, width, height, format, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, colorSpace ) { + + super( null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, colorSpace ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isCompressedTexture = true; + + /** + * The image property of a compressed texture just defines its dimensions. + * + * @type {{width:number,height:number}} + */ + this.image = { width: width, height: height }; + + /** + * This array holds for all mipmaps (including the bases mip) the data and dimensions. + * + * @type {Array} + */ + this.mipmaps = mipmaps; + + /** + * If set to `true`, the texture is flipped along the vertical axis when + * uploaded to the GPU. + * + * Overwritten and set to `false` by default since it is not possible to + * flip compressed textures. + * + * @type {boolean} + * @default false + * @readonly + */ + this.flipY = false; + + /** + * Whether to generate mipmaps (if possible) for a texture. + * + * Overwritten and set to `false` by default since it is not + * possible to generate mipmaps for compressed data. Mipmaps + * must be embedded in the compressed texture file. + * + * @type {boolean} + * @default false + * @readonly + */ + this.generateMipmaps = false; + + } + +} + +/** + * Creates a texture 2D array based on data in compressed form. + * + * These texture are usually loaded with {@link CompressedTextureLoader}. + * + * @augments CompressedTexture + */ +class CompressedArrayTexture extends CompressedTexture { + + /** + * Constructs a new compressed array texture. + * + * @param {Array} mipmaps - This array holds for all mipmaps (including the bases mip) + * the data and dimensions. + * @param {number} width - The width of the texture. + * @param {number} height - The height of the texture. + * @param {number} depth - The depth of the texture. + * @param {number} [format=RGBAFormat] - The min filter value. + * @param {number} [type=UnsignedByteType] - The min filter value. + */ + constructor( mipmaps, width, height, depth, format, type ) { + + super( mipmaps, width, height, format, type ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isCompressedArrayTexture = true; + + /** + * The image property of a compressed texture just defines its dimensions. + * + * @name CompressedArrayTexture#image + * @type {{width:number,height:number,depth:number}} + */ + this.image.depth = depth; + + /** + * This defines how the texture is wrapped in the depth and corresponds to + * *W* in UVW mapping. + * + * @type {(RepeatWrapping|ClampToEdgeWrapping|MirroredRepeatWrapping)} + * @default ClampToEdgeWrapping + */ + this.wrapR = ClampToEdgeWrapping; + + /** + * A set of all layers which need to be updated in the texture. + * + * @type {Set} + */ + this.layerUpdates = new Set(); + + } + + /** + * Describes that a specific layer of the texture needs to be updated. + * Normally when {@link Texture#needsUpdate} is set to `true`, the + * entire compressed texture array is sent to the GPU. Marking specific + * layers will only transmit subsets of all mipmaps associated with a + * specific depth in the array which is often much more performant. + * + * @param {number} layerIndex - The layer index that should be updated. + */ + addLayerUpdate( layerIndex ) { + + this.layerUpdates.add( layerIndex ); + + } + + /** + * Resets the layer updates registry. + */ + clearLayerUpdates() { + + this.layerUpdates.clear(); + + } + +} + +/** + * Creates a cube texture based on data in compressed form. + * + * These texture are usually loaded with {@link CompressedTextureLoader}. + * + * @augments CompressedTexture + */ +class CompressedCubeTexture extends CompressedTexture { + + /** + * Constructs a new compressed texture. + * + * @param {Array} images - An array of compressed textures. + * @param {number} [format=RGBAFormat] - The texture format. + * @param {number} [type=UnsignedByteType] - The texture type. + */ + constructor( images, format, type ) { + + super( undefined, images[ 0 ].width, images[ 0 ].height, format, type, CubeReflectionMapping ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isCompressedCubeTexture = true; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isCubeTexture = true; + + this.image = images; + + } + +} + +/** + * Creates a texture from a canvas element. + * + * This is almost the same as the base texture class, except that it sets {@link Texture#needsUpdate} + * to `true` immediately since a canvas can directly be used for rendering. + * + * @augments Texture + */ +class CanvasTexture extends Texture { + + /** + * Constructs a new texture. + * + * @param {HTMLCanvasElement} [canvas] - The HTML canvas element. + * @param {number} [mapping=Texture.DEFAULT_MAPPING] - The texture mapping. + * @param {number} [wrapS=ClampToEdgeWrapping] - The wrapS value. + * @param {number} [wrapT=ClampToEdgeWrapping] - The wrapT value. + * @param {number} [magFilter=LinearFilter] - The mag filter value. + * @param {number} [minFilter=LinearMipmapLinearFilter] - The min filter value. + * @param {number} [format=RGBAFormat] - The texture format. + * @param {number} [type=UnsignedByteType] - The texture type. + * @param {number} [anisotropy=Texture.DEFAULT_ANISOTROPY] - The anisotropy value. + */ + constructor( canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) { + + super( canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isCanvasTexture = true; + + this.needsUpdate = true; + + } + +} + +/** + * This class can be used to automatically save the depth information of a + * rendering into a texture. + * + * @augments Texture + */ +class DepthTexture extends Texture { + + /** + * Constructs a new depth texture. + * + * @param {number} width - The width of the texture. + * @param {number} height - The height of the texture. + * @param {number} [type=UnsignedIntType] - The texture type. + * @param {number} [mapping=Texture.DEFAULT_MAPPING] - The texture mapping. + * @param {number} [wrapS=ClampToEdgeWrapping] - The wrapS value. + * @param {number} [wrapT=ClampToEdgeWrapping] - The wrapT value. + * @param {number} [magFilter=LinearFilter] - The mag filter value. + * @param {number} [minFilter=LinearFilter] - The min filter value. + * @param {number} [anisotropy=Texture.DEFAULT_ANISOTROPY] - The anisotropy value. + * @param {number} [format=DepthFormat] - The texture format. + * @param {number} [depth=1] - The depth of the texture. + */ + constructor( width, height, type = UnsignedIntType, mapping, wrapS, wrapT, magFilter = NearestFilter, minFilter = NearestFilter, anisotropy, format = DepthFormat, depth = 1 ) { + + if ( format !== DepthFormat && format !== DepthStencilFormat ) { + + throw new Error( 'DepthTexture format must be either THREE.DepthFormat or THREE.DepthStencilFormat' ); + + } + + const image = { width: width, height: height, depth: depth }; + + super( image, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isDepthTexture = true; + + /** + * If set to `true`, the texture is flipped along the vertical axis when + * uploaded to the GPU. + * + * Overwritten and set to `false` by default. + * + * @type {boolean} + * @default false + */ + this.flipY = false; + + /** + * Whether to generate mipmaps (if possible) for a texture. + * + * Overwritten and set to `false` by default. + * + * @type {boolean} + * @default false + */ + this.generateMipmaps = false; + + /** + * Code corresponding to the depth compare function. + * + * @type {?(NeverCompare|LessCompare|EqualCompare|LessEqualCompare|GreaterCompare|NotEqualCompare|GreaterEqualCompare|AlwaysCompare)} + * @default null + */ + this.compareFunction = null; + + } + + + copy( source ) { + + super.copy( source ); + + this.source = new Source( Object.assign( {}, source.image ) ); // see #30540 + this.compareFunction = source.compareFunction; + + return this; + + } + + toJSON( meta ) { + + const data = super.toJSON( meta ); + + if ( this.compareFunction !== null ) data.compareFunction = this.compareFunction; + + return data; + + } + +} + +/** + * A geometry class for representing a capsule. + * + * ```js + * const geometry = new THREE.CapsuleGeometry( 1, 1, 4, 8, 1 ); + * const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); + * const capsule = new THREE.Mesh( geometry, material ); + * scene.add( capsule ); + * ``` + * + * @augments BufferGeometry + */ +class CapsuleGeometry extends BufferGeometry { + + /** + * Constructs a new capsule geometry. + * + * @param {number} [radius=1] - Radius of the capsule. + * @param {number} [height=1] - Height of the middle section. + * @param {number} [capSegments=4] - Number of curve segments used to build each cap. + * @param {number} [radialSegments=8] - Number of segmented faces around the circumference of the capsule. Must be an integer >= 3. + * @param {number} [heightSegments=1] - Number of rows of faces along the height of the middle section. Must be an integer >= 1. + */ + constructor( radius = 1, height = 1, capSegments = 4, radialSegments = 8, heightSegments = 1 ) { + + super(); + + this.type = 'CapsuleGeometry'; + + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + radius: radius, + height: height, + capSegments: capSegments, + radialSegments: radialSegments, + heightSegments: heightSegments, + }; + + height = Math.max( 0, height ); + capSegments = Math.max( 1, Math.floor( capSegments ) ); + radialSegments = Math.max( 3, Math.floor( radialSegments ) ); + heightSegments = Math.max( 1, Math.floor( heightSegments ) ); + + // buffers + + const indices = []; + const vertices = []; + const normals = []; + const uvs = []; + + // helper variables + + const halfHeight = height / 2; + const capArcLength = ( Math.PI / 2 ) * radius; + const cylinderPartLength = height; + const totalArcLength = 2 * capArcLength + cylinderPartLength; + + const numVerticalSegments = capSegments * 2 + heightSegments; + const verticesPerRow = radialSegments + 1; + + const normal = new Vector3(); + const vertex = new Vector3(); + + // generate vertices, normals, and uvs + + for ( let iy = 0; iy <= numVerticalSegments; iy ++ ) { + + let currentArcLength = 0; + let profileY = 0; + let profileRadius = 0; + let normalYComponent = 0; + + if ( iy <= capSegments ) { + + // bottom cap + const segmentProgress = iy / capSegments; + const angle = ( segmentProgress * Math.PI ) / 2; + profileY = - halfHeight - radius * Math.cos( angle ); + profileRadius = radius * Math.sin( angle ); + normalYComponent = - radius * Math.cos( angle ); + currentArcLength = segmentProgress * capArcLength; + + } else if ( iy <= capSegments + heightSegments ) { + + // middle section + const segmentProgress = ( iy - capSegments ) / heightSegments; + profileY = - halfHeight + segmentProgress * height; + profileRadius = radius; + normalYComponent = 0; + currentArcLength = capArcLength + segmentProgress * cylinderPartLength; + + } else { + + // top cap + const segmentProgress = + ( iy - capSegments - heightSegments ) / capSegments; + const angle = ( segmentProgress * Math.PI ) / 2; + profileY = halfHeight + radius * Math.sin( angle ); + profileRadius = radius * Math.cos( angle ); + normalYComponent = radius * Math.sin( angle ); + currentArcLength = + capArcLength + cylinderPartLength + segmentProgress * capArcLength; + + } + + const v = Math.max( 0, Math.min( 1, currentArcLength / totalArcLength ) ); + + + // special case for the poles + + let uOffset = 0; + + if ( iy === 0 ) { + + uOffset = 0.5 / radialSegments; + + } else if ( iy === numVerticalSegments ) { + + uOffset = -0.5 / radialSegments; + + } + + for ( let ix = 0; ix <= radialSegments; ix ++ ) { + + const u = ix / radialSegments; + const theta = u * Math.PI * 2; + + const sinTheta = Math.sin( theta ); + const cosTheta = Math.cos( theta ); + + // vertex + + vertex.x = - profileRadius * cosTheta; + vertex.y = profileY; + vertex.z = profileRadius * sinTheta; + vertices.push( vertex.x, vertex.y, vertex.z ); + + // normal + + normal.set( + - profileRadius * cosTheta, + normalYComponent, + profileRadius * sinTheta + ); + normal.normalize(); + normals.push( normal.x, normal.y, normal.z ); + + // uv + + uvs.push( u + uOffset, v ); + + } + + if ( iy > 0 ) { + + const prevIndexRow = ( iy - 1 ) * verticesPerRow; + for ( let ix = 0; ix < radialSegments; ix ++ ) { + + const i1 = prevIndexRow + ix; + const i2 = prevIndexRow + ix + 1; + const i3 = iy * verticesPerRow + ix; + const i4 = iy * verticesPerRow + ix + 1; + + indices.push( i1, i2, i3 ); + indices.push( i2, i4, i3 ); + + } + + } + + } + + // build geometry + + this.setIndex( indices ); + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + + } + + copy( source ) { + + super.copy( source ); + + this.parameters = Object.assign( {}, source.parameters ); + + return this; + + } + + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {CapsuleGeometry} A new instance. + */ + static fromJSON( data ) { + + return new CapsuleGeometry( data.radius, data.height, data.capSegments, data.radialSegments, data.heightSegments ); + + } + +} + +/** + * A simple shape of Euclidean geometry. It is constructed from a + * number of triangular segments that are oriented around a central point and + * extend as far out as a given radius. It is built counter-clockwise from a + * start angle and a given central angle. It can also be used to create + * regular polygons, where the number of segments determines the number of + * sides. + * + * ```js + * const geometry = new THREE.CircleGeometry( 5, 32 ); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + * const circle = new THREE.Mesh( geometry, material ); + * scene.add( circle ) + * ``` + * + * @augments BufferGeometry + */ +class CircleGeometry extends BufferGeometry { + + /** + * Constructs a new circle geometry. + * + * @param {number} [radius=1] - Radius of the circle. + * @param {number} [segments=32] - Number of segments (triangles), minimum = `3`. + * @param {number} [thetaStart=0] - Start angle for first segment in radians. + * @param {number} [thetaLength=Math.PI*2] - The central angle, often called theta, + * of the circular sector in radians. The default value results in a complete circle. + */ + constructor( radius = 1, segments = 32, thetaStart = 0, thetaLength = Math.PI * 2 ) { + + super(); + + this.type = 'CircleGeometry'; + + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + radius: radius, + segments: segments, + thetaStart: thetaStart, + thetaLength: thetaLength + }; + + segments = Math.max( 3, segments ); + + // buffers + + const indices = []; + const vertices = []; + const normals = []; + const uvs = []; + + // helper variables + + const vertex = new Vector3(); + const uv = new Vector2(); + + // center point + + vertices.push( 0, 0, 0 ); + normals.push( 0, 0, 1 ); + uvs.push( 0.5, 0.5 ); + + for ( let s = 0, i = 3; s <= segments; s ++, i += 3 ) { + + const segment = thetaStart + s / segments * thetaLength; + + // vertex + + vertex.x = radius * Math.cos( segment ); + vertex.y = radius * Math.sin( segment ); + + vertices.push( vertex.x, vertex.y, vertex.z ); + + // normal + + normals.push( 0, 0, 1 ); + + // uvs + + uv.x = ( vertices[ i ] / radius + 1 ) / 2; + uv.y = ( vertices[ i + 1 ] / radius + 1 ) / 2; + + uvs.push( uv.x, uv.y ); + + } + + // indices + + for ( let i = 1; i <= segments; i ++ ) { + + indices.push( i, i + 1, 0 ); + + } + + // build geometry + + this.setIndex( indices ); + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + + } + + copy( source ) { + + super.copy( source ); + + this.parameters = Object.assign( {}, source.parameters ); + + return this; + + } + + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {CircleGeometry} A new instance. + */ + static fromJSON( data ) { + + return new CircleGeometry( data.radius, data.segments, data.thetaStart, data.thetaLength ); + + } + +} + +/** + * A geometry class for representing a cylinder. + * + * ```js + * const geometry = new THREE.CylinderGeometry( 5, 5, 20, 32 ); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + * const cylinder = new THREE.Mesh( geometry, material ); + * scene.add( cylinder ); + * ``` + * + * @augments BufferGeometry + */ +class CylinderGeometry extends BufferGeometry { + + /** + * Constructs a new cylinder geometry. + * + * @param {number} [radiusTop=1] - Radius of the cylinder at the top. + * @param {number} [radiusBottom=1] - Radius of the cylinder at the bottom. + * @param {number} [height=1] - Height of the cylinder. + * @param {number} [radialSegments=32] - Number of segmented faces around the circumference of the cylinder. + * @param {number} [heightSegments=1] - Number of rows of faces along the height of the cylinder. + * @param {boolean} [openEnded=false] - Whether the base of the cylinder is open or capped. + * @param {number} [thetaStart=0] - Start angle for first segment, in radians. + * @param {number} [thetaLength=Math.PI*2] - The central angle, often called theta, of the circular sector, in radians. + * The default value results in a complete cylinder. + */ + constructor( radiusTop = 1, radiusBottom = 1, height = 1, radialSegments = 32, heightSegments = 1, openEnded = false, thetaStart = 0, thetaLength = Math.PI * 2 ) { + + super(); + + this.type = 'CylinderGeometry'; + + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + radiusTop: radiusTop, + radiusBottom: radiusBottom, + height: height, + radialSegments: radialSegments, + heightSegments: heightSegments, + openEnded: openEnded, + thetaStart: thetaStart, + thetaLength: thetaLength + }; + + const scope = this; + + radialSegments = Math.floor( radialSegments ); + heightSegments = Math.floor( heightSegments ); + + // buffers + + const indices = []; + const vertices = []; + const normals = []; + const uvs = []; + + // helper variables + + let index = 0; + const indexArray = []; + const halfHeight = height / 2; + let groupStart = 0; + + // generate geometry + + generateTorso(); + + if ( openEnded === false ) { + + if ( radiusTop > 0 ) generateCap( true ); + if ( radiusBottom > 0 ) generateCap( false ); + + } + + // build geometry + + this.setIndex( indices ); + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + + function generateTorso() { + + const normal = new Vector3(); + const vertex = new Vector3(); + + let groupCount = 0; + + // this will be used to calculate the normal + const slope = ( radiusBottom - radiusTop ) / height; + + // generate vertices, normals and uvs + + for ( let y = 0; y <= heightSegments; y ++ ) { + + const indexRow = []; + + const v = y / heightSegments; + + // calculate the radius of the current row + + const radius = v * ( radiusBottom - radiusTop ) + radiusTop; + + for ( let x = 0; x <= radialSegments; x ++ ) { + + const u = x / radialSegments; + + const theta = u * thetaLength + thetaStart; + + const sinTheta = Math.sin( theta ); + const cosTheta = Math.cos( theta ); + + // vertex + + vertex.x = radius * sinTheta; + vertex.y = - v * height + halfHeight; + vertex.z = radius * cosTheta; + vertices.push( vertex.x, vertex.y, vertex.z ); + + // normal + + normal.set( sinTheta, slope, cosTheta ).normalize(); + normals.push( normal.x, normal.y, normal.z ); + + // uv + + uvs.push( u, 1 - v ); + + // save index of vertex in respective row + + indexRow.push( index ++ ); + + } + + // now save vertices of the row in our index array + + indexArray.push( indexRow ); + + } + + // generate indices + + for ( let x = 0; x < radialSegments; x ++ ) { + + for ( let y = 0; y < heightSegments; y ++ ) { + + // we use the index array to access the correct indices + + const a = indexArray[ y ][ x ]; + const b = indexArray[ y + 1 ][ x ]; + const c = indexArray[ y + 1 ][ x + 1 ]; + const d = indexArray[ y ][ x + 1 ]; + + // faces + + if ( radiusTop > 0 || y !== 0 ) { + + indices.push( a, b, d ); + groupCount += 3; + + } + + if ( radiusBottom > 0 || y !== heightSegments - 1 ) { + + indices.push( b, c, d ); + groupCount += 3; + + } + + } + + } + + // add a group to the geometry. this will ensure multi material support + + scope.addGroup( groupStart, groupCount, 0 ); + + // calculate new start value for groups + + groupStart += groupCount; + + } + + function generateCap( top ) { + + // save the index of the first center vertex + const centerIndexStart = index; + + const uv = new Vector2(); + const vertex = new Vector3(); + + let groupCount = 0; + + const radius = ( top === true ) ? radiusTop : radiusBottom; + const sign = ( top === true ) ? 1 : -1; + + // first we generate the center vertex data of the cap. + // because the geometry needs one set of uvs per face, + // we must generate a center vertex per face/segment + + for ( let x = 1; x <= radialSegments; x ++ ) { + + // vertex + + vertices.push( 0, halfHeight * sign, 0 ); + + // normal + + normals.push( 0, sign, 0 ); + + // uv + + uvs.push( 0.5, 0.5 ); + + // increase index + + index ++; + + } + + // save the index of the last center vertex + const centerIndexEnd = index; + + // now we generate the surrounding vertices, normals and uvs + + for ( let x = 0; x <= radialSegments; x ++ ) { + + const u = x / radialSegments; + const theta = u * thetaLength + thetaStart; + + const cosTheta = Math.cos( theta ); + const sinTheta = Math.sin( theta ); + + // vertex + + vertex.x = radius * sinTheta; + vertex.y = halfHeight * sign; + vertex.z = radius * cosTheta; + vertices.push( vertex.x, vertex.y, vertex.z ); + + // normal + + normals.push( 0, sign, 0 ); + + // uv + + uv.x = ( cosTheta * 0.5 ) + 0.5; + uv.y = ( sinTheta * 0.5 * sign ) + 0.5; + uvs.push( uv.x, uv.y ); + + // increase index + + index ++; + + } + + // generate indices + + for ( let x = 0; x < radialSegments; x ++ ) { + + const c = centerIndexStart + x; + const i = centerIndexEnd + x; + + if ( top === true ) { + + // face top + + indices.push( i, i + 1, c ); + + } else { + + // face bottom + + indices.push( i + 1, i, c ); + + } + + groupCount += 3; + + } + + // add a group to the geometry. this will ensure multi material support + + scope.addGroup( groupStart, groupCount, top === true ? 1 : 2 ); + + // calculate new start value for groups + + groupStart += groupCount; + + } + + } + + copy( source ) { + + super.copy( source ); + + this.parameters = Object.assign( {}, source.parameters ); + + return this; + + } + + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {CylinderGeometry} A new instance. + */ + static fromJSON( data ) { + + return new CylinderGeometry( data.radiusTop, data.radiusBottom, data.height, data.radialSegments, data.heightSegments, data.openEnded, data.thetaStart, data.thetaLength ); + + } + +} + +/** + * A geometry class for representing a cone. + * + * ```js + * const geometry = new THREE.ConeGeometry( 5, 20, 32 ); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + * const cone = new THREE.Mesh(geometry, material ); + * scene.add( cone ); + * ``` + * + * @augments CylinderGeometry + */ +class ConeGeometry extends CylinderGeometry { + + /** + * Constructs a new cone geometry. + * + * @param {number} [radius=1] - Radius of the cone base. + * @param {number} [height=1] - Height of the cone. + * @param {number} [radialSegments=32] - Number of segmented faces around the circumference of the cone. + * @param {number} [heightSegments=1] - Number of rows of faces along the height of the cone. + * @param {boolean} [openEnded=false] - Whether the base of the cone is open or capped. + * @param {number} [thetaStart=0] - Start angle for first segment, in radians. + * @param {number} [thetaLength=Math.PI*2] - The central angle, often called theta, of the circular sector, in radians. + * The default value results in a complete cone. + */ + constructor( radius = 1, height = 1, radialSegments = 32, heightSegments = 1, openEnded = false, thetaStart = 0, thetaLength = Math.PI * 2 ) { + + super( 0, radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ); + + this.type = 'ConeGeometry'; + + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + radius: radius, + height: height, + radialSegments: radialSegments, + heightSegments: heightSegments, + openEnded: openEnded, + thetaStart: thetaStart, + thetaLength: thetaLength + }; + + } + + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {ConeGeometry} A new instance. + */ + static fromJSON( data ) { + + return new ConeGeometry( data.radius, data.height, data.radialSegments, data.heightSegments, data.openEnded, data.thetaStart, data.thetaLength ); + + } + +} + +/** + * A polyhedron is a solid in three dimensions with flat faces. This class + * will take an array of vertices, project them onto a sphere, and then + * divide them up to the desired level of detail. + * + * @augments BufferGeometry + */ +class PolyhedronGeometry extends BufferGeometry { + + /** + * Constructs a new polyhedron geometry. + * + * @param {Array} [vertices] - A flat array of vertices describing the base shape. + * @param {Array} [indices] - A flat array of indices describing the base shape. + * @param {number} [radius=1] - The radius of the shape. + * @param {number} [detail=0] - How many levels to subdivide the geometry. The more detail, the smoother the shape. + */ + constructor( vertices = [], indices = [], radius = 1, detail = 0 ) { + + super(); + + this.type = 'PolyhedronGeometry'; + + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + vertices: vertices, + indices: indices, + radius: radius, + detail: detail + }; + + // default buffer data + + const vertexBuffer = []; + const uvBuffer = []; + + // the subdivision creates the vertex buffer data + + subdivide( detail ); + + // all vertices should lie on a conceptual sphere with a given radius + + applyRadius( radius ); + + // finally, create the uv data + + generateUVs(); + + // build non-indexed geometry + + this.setAttribute( 'position', new Float32BufferAttribute( vertexBuffer, 3 ) ); + this.setAttribute( 'normal', new Float32BufferAttribute( vertexBuffer.slice(), 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvBuffer, 2 ) ); + + if ( detail === 0 ) { + + this.computeVertexNormals(); // flat normals + + } else { + + this.normalizeNormals(); // smooth normals + + } + + // helper functions + + function subdivide( detail ) { + + const a = new Vector3(); + const b = new Vector3(); + const c = new Vector3(); + + // iterate over all faces and apply a subdivision with the given detail value + + for ( let i = 0; i < indices.length; i += 3 ) { + + // get the vertices of the face + + getVertexByIndex( indices[ i + 0 ], a ); + getVertexByIndex( indices[ i + 1 ], b ); + getVertexByIndex( indices[ i + 2 ], c ); + + // perform subdivision + + subdivideFace( a, b, c, detail ); + + } + + } + + function subdivideFace( a, b, c, detail ) { + + const cols = detail + 1; + + // we use this multidimensional array as a data structure for creating the subdivision + + const v = []; + + // construct all of the vertices for this subdivision + + for ( let i = 0; i <= cols; i ++ ) { + + v[ i ] = []; + + const aj = a.clone().lerp( c, i / cols ); + const bj = b.clone().lerp( c, i / cols ); + + const rows = cols - i; + + for ( let j = 0; j <= rows; j ++ ) { + + if ( j === 0 && i === cols ) { + + v[ i ][ j ] = aj; + + } else { + + v[ i ][ j ] = aj.clone().lerp( bj, j / rows ); + + } + + } + + } + + // construct all of the faces + + for ( let i = 0; i < cols; i ++ ) { + + for ( let j = 0; j < 2 * ( cols - i ) - 1; j ++ ) { + + const k = Math.floor( j / 2 ); + + if ( j % 2 === 0 ) { + + pushVertex( v[ i ][ k + 1 ] ); + pushVertex( v[ i + 1 ][ k ] ); + pushVertex( v[ i ][ k ] ); + + } else { + + pushVertex( v[ i ][ k + 1 ] ); + pushVertex( v[ i + 1 ][ k + 1 ] ); + pushVertex( v[ i + 1 ][ k ] ); + + } + + } + + } + + } + + function applyRadius( radius ) { + + const vertex = new Vector3(); + + // iterate over the entire buffer and apply the radius to each vertex + + for ( let i = 0; i < vertexBuffer.length; i += 3 ) { + + vertex.x = vertexBuffer[ i + 0 ]; + vertex.y = vertexBuffer[ i + 1 ]; + vertex.z = vertexBuffer[ i + 2 ]; + + vertex.normalize().multiplyScalar( radius ); + + vertexBuffer[ i + 0 ] = vertex.x; + vertexBuffer[ i + 1 ] = vertex.y; + vertexBuffer[ i + 2 ] = vertex.z; + + } + + } + + function generateUVs() { + + const vertex = new Vector3(); + + for ( let i = 0; i < vertexBuffer.length; i += 3 ) { + + vertex.x = vertexBuffer[ i + 0 ]; + vertex.y = vertexBuffer[ i + 1 ]; + vertex.z = vertexBuffer[ i + 2 ]; + + const u = azimuth( vertex ) / 2 / Math.PI + 0.5; + const v = inclination( vertex ) / Math.PI + 0.5; + uvBuffer.push( u, 1 - v ); + + } + + correctUVs(); + + correctSeam(); + + } + + function correctSeam() { + + // handle case when face straddles the seam, see #3269 + + for ( let i = 0; i < uvBuffer.length; i += 6 ) { + + // uv data of a single face + + const x0 = uvBuffer[ i + 0 ]; + const x1 = uvBuffer[ i + 2 ]; + const x2 = uvBuffer[ i + 4 ]; + + const max = Math.max( x0, x1, x2 ); + const min = Math.min( x0, x1, x2 ); + + // 0.9 is somewhat arbitrary + + if ( max > 0.9 && min < 0.1 ) { + + if ( x0 < 0.2 ) uvBuffer[ i + 0 ] += 1; + if ( x1 < 0.2 ) uvBuffer[ i + 2 ] += 1; + if ( x2 < 0.2 ) uvBuffer[ i + 4 ] += 1; + + } + + } + + } + + function pushVertex( vertex ) { + + vertexBuffer.push( vertex.x, vertex.y, vertex.z ); + + } + + function getVertexByIndex( index, vertex ) { + + const stride = index * 3; + + vertex.x = vertices[ stride + 0 ]; + vertex.y = vertices[ stride + 1 ]; + vertex.z = vertices[ stride + 2 ]; + + } + + function correctUVs() { + + const a = new Vector3(); + const b = new Vector3(); + const c = new Vector3(); + + const centroid = new Vector3(); + + const uvA = new Vector2(); + const uvB = new Vector2(); + const uvC = new Vector2(); + + for ( let i = 0, j = 0; i < vertexBuffer.length; i += 9, j += 6 ) { + + a.set( vertexBuffer[ i + 0 ], vertexBuffer[ i + 1 ], vertexBuffer[ i + 2 ] ); + b.set( vertexBuffer[ i + 3 ], vertexBuffer[ i + 4 ], vertexBuffer[ i + 5 ] ); + c.set( vertexBuffer[ i + 6 ], vertexBuffer[ i + 7 ], vertexBuffer[ i + 8 ] ); + + uvA.set( uvBuffer[ j + 0 ], uvBuffer[ j + 1 ] ); + uvB.set( uvBuffer[ j + 2 ], uvBuffer[ j + 3 ] ); + uvC.set( uvBuffer[ j + 4 ], uvBuffer[ j + 5 ] ); + + centroid.copy( a ).add( b ).add( c ).divideScalar( 3 ); + + const azi = azimuth( centroid ); + + correctUV( uvA, j + 0, a, azi ); + correctUV( uvB, j + 2, b, azi ); + correctUV( uvC, j + 4, c, azi ); + + } + + } + + function correctUV( uv, stride, vector, azimuth ) { + + if ( ( azimuth < 0 ) && ( uv.x === 1 ) ) { + + uvBuffer[ stride ] = uv.x - 1; + + } + + if ( ( vector.x === 0 ) && ( vector.z === 0 ) ) { + + uvBuffer[ stride ] = azimuth / 2 / Math.PI + 0.5; + + } + + } + + // Angle around the Y axis, counter-clockwise when looking from above. + + function azimuth( vector ) { + + return Math.atan2( vector.z, - vector.x ); + + } + + + // Angle above the XZ plane. + + function inclination( vector ) { + + return Math.atan2( - vector.y, Math.sqrt( ( vector.x * vector.x ) + ( vector.z * vector.z ) ) ); + + } + + } + + copy( source ) { + + super.copy( source ); + + this.parameters = Object.assign( {}, source.parameters ); + + return this; + + } + + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {PolyhedronGeometry} A new instance. + */ + static fromJSON( data ) { + + return new PolyhedronGeometry( data.vertices, data.indices, data.radius, data.details ); + + } + +} + +/** + * A geometry class for representing a dodecahedron. + * + * ```js + * const geometry = new THREE.DodecahedronGeometry(); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + * const dodecahedron = new THREE.Mesh( geometry, material ); + * scene.add( dodecahedron ); + * ``` + * + * @augments PolyhedronGeometry + */ +class DodecahedronGeometry extends PolyhedronGeometry { + + /** + * Constructs a new dodecahedron geometry. + * + * @param {number} [radius=1] - Radius of the dodecahedron. + * @param {number} [detail=0] - Setting this to a value greater than `0` adds vertices making it no longer a dodecahedron. + */ + constructor( radius = 1, detail = 0 ) { + + const t = ( 1 + Math.sqrt( 5 ) ) / 2; + const r = 1 / t; + + const vertices = [ + + // (±1, ±1, ±1) + -1, -1, -1, -1, -1, 1, + -1, 1, -1, -1, 1, 1, + 1, -1, -1, 1, -1, 1, + 1, 1, -1, 1, 1, 1, + + // (0, ±1/φ, ±φ) + 0, - r, - t, 0, - r, t, + 0, r, - t, 0, r, t, + + // (±1/φ, ±φ, 0) + - r, - t, 0, - r, t, 0, + r, - t, 0, r, t, 0, + + // (±φ, 0, ±1/φ) + - t, 0, - r, t, 0, - r, + - t, 0, r, t, 0, r + ]; + + const indices = [ + 3, 11, 7, 3, 7, 15, 3, 15, 13, + 7, 19, 17, 7, 17, 6, 7, 6, 15, + 17, 4, 8, 17, 8, 10, 17, 10, 6, + 8, 0, 16, 8, 16, 2, 8, 2, 10, + 0, 12, 1, 0, 1, 18, 0, 18, 16, + 6, 10, 2, 6, 2, 13, 6, 13, 15, + 2, 16, 18, 2, 18, 3, 2, 3, 13, + 18, 1, 9, 18, 9, 11, 18, 11, 3, + 4, 14, 12, 4, 12, 0, 4, 0, 8, + 11, 9, 5, 11, 5, 19, 11, 19, 7, + 19, 5, 14, 19, 14, 4, 19, 4, 17, + 1, 12, 14, 1, 14, 5, 1, 5, 9 + ]; + + super( vertices, indices, radius, detail ); + + this.type = 'DodecahedronGeometry'; + + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + radius: radius, + detail: detail + }; + + } + + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {DodecahedronGeometry} A new instance. + */ + static fromJSON( data ) { + + return new DodecahedronGeometry( data.radius, data.detail ); + + } + +} + +const _v0 = /*@__PURE__*/ new Vector3(); +const _v1$1 = /*@__PURE__*/ new Vector3(); +const _normal = /*@__PURE__*/ new Vector3(); +const _triangle = /*@__PURE__*/ new Triangle(); + +/** + * Can be used as a helper object to view the edges of a geometry. + * + * ```js + * const geometry = new THREE.BoxGeometry(); + * const edges = new THREE.EdgesGeometry( geometry ); + * const line = new THREE.LineSegments( edges ); + * scene.add( line ); + * ``` + * + * Note: It is not yet possible to serialize/deserialize instances of this class. + * + * @augments BufferGeometry + */ +class EdgesGeometry extends BufferGeometry { + + /** + * Constructs a new edges geometry. + * + * @param {?BufferGeometry} [geometry=null] - The geometry. + * @param {number} [thresholdAngle=1] - An edge is only rendered if the angle (in degrees) + * between the face normals of the adjoining faces exceeds this value. + */ + constructor( geometry = null, thresholdAngle = 1 ) { + + super(); + + this.type = 'EdgesGeometry'; + + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + geometry: geometry, + thresholdAngle: thresholdAngle + }; + + if ( geometry !== null ) { + + const precisionPoints = 4; + const precision = Math.pow( 10, precisionPoints ); + const thresholdDot = Math.cos( DEG2RAD * thresholdAngle ); + + const indexAttr = geometry.getIndex(); + const positionAttr = geometry.getAttribute( 'position' ); + const indexCount = indexAttr ? indexAttr.count : positionAttr.count; + + const indexArr = [ 0, 0, 0 ]; + const vertKeys = [ 'a', 'b', 'c' ]; + const hashes = new Array( 3 ); + + const edgeData = {}; + const vertices = []; + for ( let i = 0; i < indexCount; i += 3 ) { + + if ( indexAttr ) { + + indexArr[ 0 ] = indexAttr.getX( i ); + indexArr[ 1 ] = indexAttr.getX( i + 1 ); + indexArr[ 2 ] = indexAttr.getX( i + 2 ); + + } else { + + indexArr[ 0 ] = i; + indexArr[ 1 ] = i + 1; + indexArr[ 2 ] = i + 2; + + } + + const { a, b, c } = _triangle; + a.fromBufferAttribute( positionAttr, indexArr[ 0 ] ); + b.fromBufferAttribute( positionAttr, indexArr[ 1 ] ); + c.fromBufferAttribute( positionAttr, indexArr[ 2 ] ); + _triangle.getNormal( _normal ); + + // create hashes for the edge from the vertices + hashes[ 0 ] = `${ Math.round( a.x * precision ) },${ Math.round( a.y * precision ) },${ Math.round( a.z * precision ) }`; + hashes[ 1 ] = `${ Math.round( b.x * precision ) },${ Math.round( b.y * precision ) },${ Math.round( b.z * precision ) }`; + hashes[ 2 ] = `${ Math.round( c.x * precision ) },${ Math.round( c.y * precision ) },${ Math.round( c.z * precision ) }`; + + // skip degenerate triangles + if ( hashes[ 0 ] === hashes[ 1 ] || hashes[ 1 ] === hashes[ 2 ] || hashes[ 2 ] === hashes[ 0 ] ) { + + continue; + + } + + // iterate over every edge + for ( let j = 0; j < 3; j ++ ) { + + // get the first and next vertex making up the edge + const jNext = ( j + 1 ) % 3; + const vecHash0 = hashes[ j ]; + const vecHash1 = hashes[ jNext ]; + const v0 = _triangle[ vertKeys[ j ] ]; + const v1 = _triangle[ vertKeys[ jNext ] ]; + + const hash = `${ vecHash0 }_${ vecHash1 }`; + const reverseHash = `${ vecHash1 }_${ vecHash0 }`; + + if ( reverseHash in edgeData && edgeData[ reverseHash ] ) { + + // if we found a sibling edge add it into the vertex array if + // it meets the angle threshold and delete the edge from the map. + if ( _normal.dot( edgeData[ reverseHash ].normal ) <= thresholdDot ) { + + vertices.push( v0.x, v0.y, v0.z ); + vertices.push( v1.x, v1.y, v1.z ); + + } + + edgeData[ reverseHash ] = null; + + } else if ( ! ( hash in edgeData ) ) { + + // if we've already got an edge here then skip adding a new one + edgeData[ hash ] = { + + index0: indexArr[ j ], + index1: indexArr[ jNext ], + normal: _normal.clone(), + + }; + + } + + } + + } + + // iterate over all remaining, unmatched edges and add them to the vertex array + for ( const key in edgeData ) { + + if ( edgeData[ key ] ) { + + const { index0, index1 } = edgeData[ key ]; + _v0.fromBufferAttribute( positionAttr, index0 ); + _v1$1.fromBufferAttribute( positionAttr, index1 ); + + vertices.push( _v0.x, _v0.y, _v0.z ); + vertices.push( _v1$1.x, _v1$1.y, _v1$1.z ); + + } + + } + + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + + } + + } + + copy( source ) { + + super.copy( source ); + + this.parameters = Object.assign( {}, source.parameters ); + + return this; + + } + +} + +/** + * An abstract base class for creating an analytic curve object that contains methods + * for interpolation. + * + * @abstract + */ +class Curve { + + /** + * Constructs a new curve. + */ + constructor() { + + /** + * The type property is used for detecting the object type + * in context of serialization/deserialization. + * + * @type {string} + * @readonly + */ + this.type = 'Curve'; + + /** + * This value determines the amount of divisions when calculating the + * cumulative segment lengths of a curve via {@link Curve#getLengths}. To ensure + * precision when using methods like {@link Curve#getSpacedPoints}, it is + * recommended to increase the value of this property if the curve is very large. + * + * @type {number} + * @default 200 + */ + this.arcLengthDivisions = 200; + + /** + * Must be set to `true` if the curve parameters have changed. + * + * @type {boolean} + * @default false + */ + this.needsUpdate = false; + + /** + * An internal cache that holds precomputed curve length values. + * + * @private + * @type {?Array} + * @default null + */ + this.cacheArcLengths = null; + + } + + /** + * This method returns a vector in 2D or 3D space (depending on the curve definition) + * for the given interpolation factor. + * + * @abstract + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {(Vector2|Vector3)} [optionalTarget] - The optional target vector the result is written to. + * @return {(Vector2|Vector3)} The position on the curve. It can be a 2D or 3D vector depending on the curve definition. + */ + getPoint( /* t, optionalTarget */ ) { + + console.warn( 'THREE.Curve: .getPoint() not implemented.' ); + + } + + /** + * This method returns a vector in 2D or 3D space (depending on the curve definition) + * for the given interpolation factor. Unlike {@link Curve#getPoint}, this method honors the length + * of the curve which equidistant samples. + * + * @param {number} u - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {(Vector2|Vector3)} [optionalTarget] - The optional target vector the result is written to. + * @return {(Vector2|Vector3)} The position on the curve. It can be a 2D or 3D vector depending on the curve definition. + */ + getPointAt( u, optionalTarget ) { + + const t = this.getUtoTmapping( u ); + return this.getPoint( t, optionalTarget ); + + } + + /** + * This method samples the curve via {@link Curve#getPoint} and returns an array of points representing + * the curve shape. + * + * @param {number} [divisions=5] - The number of divisions. + * @return {Array<(Vector2|Vector3)>} An array holding the sampled curve values. The number of points is `divisions + 1`. + */ + getPoints( divisions = 5 ) { + + const points = []; + + for ( let d = 0; d <= divisions; d ++ ) { + + points.push( this.getPoint( d / divisions ) ); + + } + + return points; + + } + + // Get sequence of points using getPointAt( u ) + + /** + * This method samples the curve via {@link Curve#getPointAt} and returns an array of points representing + * the curve shape. Unlike {@link Curve#getPoints}, this method returns equi-spaced points across the entire + * curve. + * + * @param {number} [divisions=5] - The number of divisions. + * @return {Array<(Vector2|Vector3)>} An array holding the sampled curve values. The number of points is `divisions + 1`. + */ + getSpacedPoints( divisions = 5 ) { + + const points = []; + + for ( let d = 0; d <= divisions; d ++ ) { + + points.push( this.getPointAt( d / divisions ) ); + + } + + return points; + + } + + /** + * Returns the total arc length of the curve. + * + * @return {number} The length of the curve. + */ + getLength() { + + const lengths = this.getLengths(); + return lengths[ lengths.length - 1 ]; + + } + + /** + * Returns an array of cumulative segment lengths of the curve. + * + * @param {number} [divisions=this.arcLengthDivisions] - The number of divisions. + * @return {Array} An array holding the cumulative segment lengths. + */ + getLengths( divisions = this.arcLengthDivisions ) { + + if ( this.cacheArcLengths && + ( this.cacheArcLengths.length === divisions + 1 ) && + ! this.needsUpdate ) { + + return this.cacheArcLengths; + + } + + this.needsUpdate = false; + + const cache = []; + let current, last = this.getPoint( 0 ); + let sum = 0; + + cache.push( 0 ); + + for ( let p = 1; p <= divisions; p ++ ) { + + current = this.getPoint( p / divisions ); + sum += current.distanceTo( last ); + cache.push( sum ); + last = current; + + } + + this.cacheArcLengths = cache; + + return cache; // { sums: cache, sum: sum }; Sum is in the last element. + + } + + /** + * Update the cumulative segment distance cache. The method must be called + * every time curve parameters are changed. If an updated curve is part of a + * composed curve like {@link CurvePath}, this method must be called on the + * composed curve, too. + */ + updateArcLengths() { + + this.needsUpdate = true; + this.getLengths(); + + } + + /** + * Given an interpolation factor in the range `[0,1]`, this method returns an updated + * interpolation factor in the same range that can be ued to sample equidistant points + * from a curve. + * + * @param {number} u - The interpolation factor. + * @param {?number} distance - An optional distance on the curve. + * @return {number} The updated interpolation factor. + */ + getUtoTmapping( u, distance = null ) { + + const arcLengths = this.getLengths(); + + let i = 0; + const il = arcLengths.length; + + let targetArcLength; // The targeted u distance value to get + + if ( distance ) { + + targetArcLength = distance; + + } else { + + targetArcLength = u * arcLengths[ il - 1 ]; + + } + + // binary search for the index with largest value smaller than target u distance + + let low = 0, high = il - 1, comparison; + + while ( low <= high ) { + + i = Math.floor( low + ( high - low ) / 2 ); // less likely to overflow, though probably not issue here, JS doesn't really have integers, all numbers are floats + + comparison = arcLengths[ i ] - targetArcLength; + + if ( comparison < 0 ) { + + low = i + 1; + + } else if ( comparison > 0 ) { + + high = i - 1; + + } else { + + high = i; + break; + + // DONE + + } + + } + + i = high; + + if ( arcLengths[ i ] === targetArcLength ) { + + return i / ( il - 1 ); + + } + + // we could get finer grain at lengths, or use simple interpolation between two points + + const lengthBefore = arcLengths[ i ]; + const lengthAfter = arcLengths[ i + 1 ]; + + const segmentLength = lengthAfter - lengthBefore; + + // determine where we are between the 'before' and 'after' points + + const segmentFraction = ( targetArcLength - lengthBefore ) / segmentLength; + + // add that fractional amount to t + + const t = ( i + segmentFraction ) / ( il - 1 ); + + return t; + + } + + /** + * Returns a unit vector tangent for the given interpolation factor. + * If the derived curve does not implement its tangent derivation, + * two points a small delta apart will be used to find its gradient + * which seems to give a reasonable approximation. + * + * @param {number} t - The interpolation factor. + * @param {(Vector2|Vector3)} [optionalTarget] - The optional target vector the result is written to. + * @return {(Vector2|Vector3)} The tangent vector. + */ + getTangent( t, optionalTarget ) { + + const delta = 0.0001; + let t1 = t - delta; + let t2 = t + delta; + + // Capping in case of danger + + if ( t1 < 0 ) t1 = 0; + if ( t2 > 1 ) t2 = 1; + + const pt1 = this.getPoint( t1 ); + const pt2 = this.getPoint( t2 ); + + const tangent = optionalTarget || ( ( pt1.isVector2 ) ? new Vector2() : new Vector3() ); + + tangent.copy( pt2 ).sub( pt1 ).normalize(); + + return tangent; + + } + + /** + * Same as {@link Curve#getTangent} but with equidistant samples. + * + * @param {number} u - The interpolation factor. + * @param {(Vector2|Vector3)} [optionalTarget] - The optional target vector the result is written to. + * @return {(Vector2|Vector3)} The tangent vector. + * @see {@link Curve#getPointAt} + */ + getTangentAt( u, optionalTarget ) { + + const t = this.getUtoTmapping( u ); + return this.getTangent( t, optionalTarget ); + + } + + /** + * Generates the Frenet Frames. Requires a curve definition in 3D space. Used + * in geometries like {@link TubeGeometry} or {@link ExtrudeGeometry}. + * + * @param {number} segments - The number of segments. + * @param {boolean} [closed=false] - Whether the curve is closed or not. + * @return {{tangents: Array, normals: Array, binormals: Array}} The Frenet Frames. + */ + computeFrenetFrames( segments, closed = false ) { + + // see http://www.cs.indiana.edu/pub/techreports/TR425.pdf + + const normal = new Vector3(); + + const tangents = []; + const normals = []; + const binormals = []; + + const vec = new Vector3(); + const mat = new Matrix4(); + + // compute the tangent vectors for each segment on the curve + + for ( let i = 0; i <= segments; i ++ ) { + + const u = i / segments; + + tangents[ i ] = this.getTangentAt( u, new Vector3() ); + + } + + // select an initial normal vector perpendicular to the first tangent vector, + // and in the direction of the minimum tangent xyz component + + normals[ 0 ] = new Vector3(); + binormals[ 0 ] = new Vector3(); + let min = Number.MAX_VALUE; + const tx = Math.abs( tangents[ 0 ].x ); + const ty = Math.abs( tangents[ 0 ].y ); + const tz = Math.abs( tangents[ 0 ].z ); + + if ( tx <= min ) { + + min = tx; + normal.set( 1, 0, 0 ); + + } + + if ( ty <= min ) { + + min = ty; + normal.set( 0, 1, 0 ); + + } + + if ( tz <= min ) { + + normal.set( 0, 0, 1 ); + + } + + vec.crossVectors( tangents[ 0 ], normal ).normalize(); + + normals[ 0 ].crossVectors( tangents[ 0 ], vec ); + binormals[ 0 ].crossVectors( tangents[ 0 ], normals[ 0 ] ); + + + // compute the slowly-varying normal and binormal vectors for each segment on the curve + + for ( let i = 1; i <= segments; i ++ ) { + + normals[ i ] = normals[ i - 1 ].clone(); + + binormals[ i ] = binormals[ i - 1 ].clone(); + + vec.crossVectors( tangents[ i - 1 ], tangents[ i ] ); + + if ( vec.length() > Number.EPSILON ) { + + vec.normalize(); + + const theta = Math.acos( clamp( tangents[ i - 1 ].dot( tangents[ i ] ), -1, 1 ) ); // clamp for floating pt errors + + normals[ i ].applyMatrix4( mat.makeRotationAxis( vec, theta ) ); + + } + + binormals[ i ].crossVectors( tangents[ i ], normals[ i ] ); + + } + + // if the curve is closed, postprocess the vectors so the first and last normal vectors are the same + + if ( closed === true ) { + + let theta = Math.acos( clamp( normals[ 0 ].dot( normals[ segments ] ), -1, 1 ) ); + theta /= segments; + + if ( tangents[ 0 ].dot( vec.crossVectors( normals[ 0 ], normals[ segments ] ) ) > 0 ) { + + theta = - theta; + + } + + for ( let i = 1; i <= segments; i ++ ) { + + // twist a little... + normals[ i ].applyMatrix4( mat.makeRotationAxis( tangents[ i ], theta * i ) ); + binormals[ i ].crossVectors( tangents[ i ], normals[ i ] ); + + } + + } + + return { + tangents: tangents, + normals: normals, + binormals: binormals + }; + + } + + /** + * Returns a new curve with copied values from this instance. + * + * @return {Curve} A clone of this instance. + */ + clone() { + + return new this.constructor().copy( this ); + + } + + /** + * Copies the values of the given curve to this instance. + * + * @param {Curve} source - The curve to copy. + * @return {Curve} A reference to this curve. + */ + copy( source ) { + + this.arcLengthDivisions = source.arcLengthDivisions; + + return this; + + } + + /** + * Serializes the curve into JSON. + * + * @return {Object} A JSON object representing the serialized curve. + * @see {@link ObjectLoader#parse} + */ + toJSON() { + + const data = { + metadata: { + version: 4.7, + type: 'Curve', + generator: 'Curve.toJSON' + } + }; + + data.arcLengthDivisions = this.arcLengthDivisions; + data.type = this.type; + + return data; + + } + + /** + * Deserializes the curve from the given JSON. + * + * @param {Object} json - The JSON holding the serialized curve. + * @return {Curve} A reference to this curve. + */ + fromJSON( json ) { + + this.arcLengthDivisions = json.arcLengthDivisions; + + return this; + + } + +} + +/** + * A curve representing an ellipse. + * + * ```js + * const curve = new THREE.EllipseCurve( + * 0, 0, + * 10, 10, + * 0, 2 * Math.PI, + * false, + * 0 + * ); + * + * const points = curve.getPoints( 50 ); + * const geometry = new THREE.BufferGeometry().setFromPoints( points ); + * + * const material = new THREE.LineBasicMaterial( { color: 0xff0000 } ); + * + * // Create the final object to add to the scene + * const ellipse = new THREE.Line( geometry, material ); + * ``` + * + * @augments Curve + */ +class EllipseCurve extends Curve { + + /** + * Constructs a new ellipse curve. + * + * @param {number} [aX=0] - The X center of the ellipse. + * @param {number} [aY=0] - The Y center of the ellipse. + * @param {number} [xRadius=1] - The radius of the ellipse in the x direction. + * @param {number} [yRadius=1] - The radius of the ellipse in the y direction. + * @param {number} [aStartAngle=0] - The start angle of the curve in radians starting from the positive X axis. + * @param {number} [aEndAngle=Math.PI*2] - The end angle of the curve in radians starting from the positive X axis. + * @param {boolean} [aClockwise=false] - Whether the ellipse is drawn clockwise or not. + * @param {number} [aRotation=0] - The rotation angle of the ellipse in radians, counterclockwise from the positive X axis. + */ + constructor( aX = 0, aY = 0, xRadius = 1, yRadius = 1, aStartAngle = 0, aEndAngle = Math.PI * 2, aClockwise = false, aRotation = 0 ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isEllipseCurve = true; + + this.type = 'EllipseCurve'; + + /** + * The X center of the ellipse. + * + * @type {number} + * @default 0 + */ + this.aX = aX; + + /** + * The Y center of the ellipse. + * + * @type {number} + * @default 0 + */ + this.aY = aY; + + /** + * The radius of the ellipse in the x direction. + * Setting the this value equal to the {@link EllipseCurve#yRadius} will result in a circle. + * + * @type {number} + * @default 1 + */ + this.xRadius = xRadius; + + /** + * The radius of the ellipse in the y direction. + * Setting the this value equal to the {@link EllipseCurve#xRadius} will result in a circle. + * + * @type {number} + * @default 1 + */ + this.yRadius = yRadius; + + /** + * The start angle of the curve in radians starting from the positive X axis. + * + * @type {number} + * @default 0 + */ + this.aStartAngle = aStartAngle; + + /** + * The end angle of the curve in radians starting from the positive X axis. + * + * @type {number} + * @default Math.PI*2 + */ + this.aEndAngle = aEndAngle; + + /** + * Whether the ellipse is drawn clockwise or not. + * + * @type {boolean} + * @default false + */ + this.aClockwise = aClockwise; + + /** + * The rotation angle of the ellipse in radians, counterclockwise from the positive X axis. + * + * @type {number} + * @default 0 + */ + this.aRotation = aRotation; + + } + + /** + * Returns a point on the curve. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector2} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector2} The position on the curve. + */ + getPoint( t, optionalTarget = new Vector2() ) { + + const point = optionalTarget; + + const twoPi = Math.PI * 2; + let deltaAngle = this.aEndAngle - this.aStartAngle; + const samePoints = Math.abs( deltaAngle ) < Number.EPSILON; + + // ensures that deltaAngle is 0 .. 2 PI + while ( deltaAngle < 0 ) deltaAngle += twoPi; + while ( deltaAngle > twoPi ) deltaAngle -= twoPi; + + if ( deltaAngle < Number.EPSILON ) { + + if ( samePoints ) { + + deltaAngle = 0; + + } else { + + deltaAngle = twoPi; + + } + + } + + if ( this.aClockwise === true && ! samePoints ) { + + if ( deltaAngle === twoPi ) { + + deltaAngle = - twoPi; + + } else { + + deltaAngle = deltaAngle - twoPi; + + } + + } + + const angle = this.aStartAngle + t * deltaAngle; + let x = this.aX + this.xRadius * Math.cos( angle ); + let y = this.aY + this.yRadius * Math.sin( angle ); + + if ( this.aRotation !== 0 ) { + + const cos = Math.cos( this.aRotation ); + const sin = Math.sin( this.aRotation ); + + const tx = x - this.aX; + const ty = y - this.aY; + + // Rotate the point about the center of the ellipse. + x = tx * cos - ty * sin + this.aX; + y = tx * sin + ty * cos + this.aY; + + } + + return point.set( x, y ); + + } + + copy( source ) { + + super.copy( source ); + + this.aX = source.aX; + this.aY = source.aY; + + this.xRadius = source.xRadius; + this.yRadius = source.yRadius; + + this.aStartAngle = source.aStartAngle; + this.aEndAngle = source.aEndAngle; + + this.aClockwise = source.aClockwise; + + this.aRotation = source.aRotation; + + return this; + + } + + toJSON() { + + const data = super.toJSON(); + + data.aX = this.aX; + data.aY = this.aY; + + data.xRadius = this.xRadius; + data.yRadius = this.yRadius; + + data.aStartAngle = this.aStartAngle; + data.aEndAngle = this.aEndAngle; + + data.aClockwise = this.aClockwise; + + data.aRotation = this.aRotation; + + return data; + + } + + fromJSON( json ) { + + super.fromJSON( json ); + + this.aX = json.aX; + this.aY = json.aY; + + this.xRadius = json.xRadius; + this.yRadius = json.yRadius; + + this.aStartAngle = json.aStartAngle; + this.aEndAngle = json.aEndAngle; + + this.aClockwise = json.aClockwise; + + this.aRotation = json.aRotation; + + return this; + + } + +} + +/** + * A curve representing an arc. + * + * @augments EllipseCurve + */ +class ArcCurve extends EllipseCurve { + + /** + * Constructs a new arc curve. + * + * @param {number} [aX=0] - The X center of the ellipse. + * @param {number} [aY=0] - The Y center of the ellipse. + * @param {number} [aRadius=1] - The radius of the ellipse in the x direction. + * @param {number} [aStartAngle=0] - The start angle of the curve in radians starting from the positive X axis. + * @param {number} [aEndAngle=Math.PI*2] - The end angle of the curve in radians starting from the positive X axis. + * @param {boolean} [aClockwise=false] - Whether the ellipse is drawn clockwise or not. + */ + constructor( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) { + + super( aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isArcCurve = true; + + this.type = 'ArcCurve'; + + } + +} + +function CubicPoly() { + + /** + * Centripetal CatmullRom Curve - which is useful for avoiding + * cusps and self-intersections in non-uniform catmull rom curves. + * http://www.cemyuksel.com/research/catmullrom_param/catmullrom.pdf + * + * curve.type accepts centripetal(default), chordal and catmullrom + * curve.tension is used for catmullrom which defaults to 0.5 + */ + + /* + Based on an optimized c++ solution in + - http://stackoverflow.com/questions/9489736/catmull-rom-curve-with-no-cusps-and-no-self-intersections/ + - http://ideone.com/NoEbVM + + This CubicPoly class could be used for reusing some variables and calculations, + but for three.js curve use, it could be possible inlined and flatten into a single function call + which can be placed in CurveUtils. + */ + + let c0 = 0, c1 = 0, c2 = 0, c3 = 0; + + /* + * Compute coefficients for a cubic polynomial + * p(s) = c0 + c1*s + c2*s^2 + c3*s^3 + * such that + * p(0) = x0, p(1) = x1 + * and + * p'(0) = t0, p'(1) = t1. + */ + function init( x0, x1, t0, t1 ) { + + c0 = x0; + c1 = t0; + c2 = -3 * x0 + 3 * x1 - 2 * t0 - t1; + c3 = 2 * x0 - 2 * x1 + t0 + t1; + + } + + return { + + initCatmullRom: function ( x0, x1, x2, x3, tension ) { + + init( x1, x2, tension * ( x2 - x0 ), tension * ( x3 - x1 ) ); + + }, + + initNonuniformCatmullRom: function ( x0, x1, x2, x3, dt0, dt1, dt2 ) { + + // compute tangents when parameterized in [t1,t2] + let t1 = ( x1 - x0 ) / dt0 - ( x2 - x0 ) / ( dt0 + dt1 ) + ( x2 - x1 ) / dt1; + let t2 = ( x2 - x1 ) / dt1 - ( x3 - x1 ) / ( dt1 + dt2 ) + ( x3 - x2 ) / dt2; + + // rescale tangents for parametrization in [0,1] + t1 *= dt1; + t2 *= dt1; + + init( x1, x2, t1, t2 ); + + }, + + calc: function ( t ) { + + const t2 = t * t; + const t3 = t2 * t; + return c0 + c1 * t + c2 * t2 + c3 * t3; + + } + + }; + +} + +// + +const tmp = /*@__PURE__*/ new Vector3(); +const px = /*@__PURE__*/ new CubicPoly(); +const py = /*@__PURE__*/ new CubicPoly(); +const pz = /*@__PURE__*/ new CubicPoly(); + +/** + * A curve representing a Catmull-Rom spline. + * + * ```js + * //Create a closed wavey loop + * const curve = new THREE.CatmullRomCurve3( [ + * new THREE.Vector3( -10, 0, 10 ), + * new THREE.Vector3( -5, 5, 5 ), + * new THREE.Vector3( 0, 0, 0 ), + * new THREE.Vector3( 5, -5, 5 ), + * new THREE.Vector3( 10, 0, 10 ) + * ] ); + * + * const points = curve.getPoints( 50 ); + * const geometry = new THREE.BufferGeometry().setFromPoints( points ); + * + * const material = new THREE.LineBasicMaterial( { color: 0xff0000 } ); + * + * // Create the final object to add to the scene + * const curveObject = new THREE.Line( geometry, material ); + * ``` + * + * @augments Curve + */ +class CatmullRomCurve3 extends Curve { + + /** + * Constructs a new Catmull-Rom curve. + * + * @param {Array} [points] - An array of 3D points defining the curve. + * @param {boolean} [closed=false] - Whether the curve is closed or not. + * @param {('centripetal'|'chordal'|'catmullrom')} [curveType='centripetal'] - The curve type. + * @param {number} [tension=0.5] - Tension of the curve. + */ + constructor( points = [], closed = false, curveType = 'centripetal', tension = 0.5 ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isCatmullRomCurve3 = true; + + this.type = 'CatmullRomCurve3'; + + /** + * An array of 3D points defining the curve. + * + * @type {Array} + */ + this.points = points; + + /** + * Whether the curve is closed or not. + * + * @type {boolean} + * @default false + */ + this.closed = closed; + + /** + * The curve type. + * + * @type {('centripetal'|'chordal'|'catmullrom')} + * @default 'centripetal' + */ + this.curveType = curveType; + + /** + * Tension of the curve. + * + * @type {number} + * @default 0.5 + */ + this.tension = tension; + + } + + /** + * Returns a point on the curve. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector3} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector3} The position on the curve. + */ + getPoint( t, optionalTarget = new Vector3() ) { + + const point = optionalTarget; + + const points = this.points; + const l = points.length; + + const p = ( l - ( this.closed ? 0 : 1 ) ) * t; + let intPoint = Math.floor( p ); + let weight = p - intPoint; + + if ( this.closed ) { + + intPoint += intPoint > 0 ? 0 : ( Math.floor( Math.abs( intPoint ) / l ) + 1 ) * l; + + } else if ( weight === 0 && intPoint === l - 1 ) { + + intPoint = l - 2; + weight = 1; + + } + + let p0, p3; // 4 points (p1 & p2 defined below) + + if ( this.closed || intPoint > 0 ) { + + p0 = points[ ( intPoint - 1 ) % l ]; + + } else { + + // extrapolate first point + tmp.subVectors( points[ 0 ], points[ 1 ] ).add( points[ 0 ] ); + p0 = tmp; + + } + + const p1 = points[ intPoint % l ]; + const p2 = points[ ( intPoint + 1 ) % l ]; + + if ( this.closed || intPoint + 2 < l ) { + + p3 = points[ ( intPoint + 2 ) % l ]; + + } else { + + // extrapolate last point + tmp.subVectors( points[ l - 1 ], points[ l - 2 ] ).add( points[ l - 1 ] ); + p3 = tmp; + + } + + if ( this.curveType === 'centripetal' || this.curveType === 'chordal' ) { + + // init Centripetal / Chordal Catmull-Rom + const pow = this.curveType === 'chordal' ? 0.5 : 0.25; + let dt0 = Math.pow( p0.distanceToSquared( p1 ), pow ); + let dt1 = Math.pow( p1.distanceToSquared( p2 ), pow ); + let dt2 = Math.pow( p2.distanceToSquared( p3 ), pow ); + + // safety check for repeated points + if ( dt1 < 1e-4 ) dt1 = 1.0; + if ( dt0 < 1e-4 ) dt0 = dt1; + if ( dt2 < 1e-4 ) dt2 = dt1; + + px.initNonuniformCatmullRom( p0.x, p1.x, p2.x, p3.x, dt0, dt1, dt2 ); + py.initNonuniformCatmullRom( p0.y, p1.y, p2.y, p3.y, dt0, dt1, dt2 ); + pz.initNonuniformCatmullRom( p0.z, p1.z, p2.z, p3.z, dt0, dt1, dt2 ); + + } else if ( this.curveType === 'catmullrom' ) { + + px.initCatmullRom( p0.x, p1.x, p2.x, p3.x, this.tension ); + py.initCatmullRom( p0.y, p1.y, p2.y, p3.y, this.tension ); + pz.initCatmullRom( p0.z, p1.z, p2.z, p3.z, this.tension ); + + } + + point.set( + px.calc( weight ), + py.calc( weight ), + pz.calc( weight ) + ); + + return point; + + } + + copy( source ) { + + super.copy( source ); + + this.points = []; + + for ( let i = 0, l = source.points.length; i < l; i ++ ) { + + const point = source.points[ i ]; + + this.points.push( point.clone() ); + + } + + this.closed = source.closed; + this.curveType = source.curveType; + this.tension = source.tension; + + return this; + + } + + toJSON() { + + const data = super.toJSON(); + + data.points = []; + + for ( let i = 0, l = this.points.length; i < l; i ++ ) { + + const point = this.points[ i ]; + data.points.push( point.toArray() ); + + } + + data.closed = this.closed; + data.curveType = this.curveType; + data.tension = this.tension; + + return data; + + } + + fromJSON( json ) { + + super.fromJSON( json ); + + this.points = []; + + for ( let i = 0, l = json.points.length; i < l; i ++ ) { + + const point = json.points[ i ]; + this.points.push( new Vector3().fromArray( point ) ); + + } + + this.closed = json.closed; + this.curveType = json.curveType; + this.tension = json.tension; + + return this; + + } + +} + +// Bezier Curves formulas obtained from: https://en.wikipedia.org/wiki/B%C3%A9zier_curve + +/** + * Computes a point on a Catmull-Rom spline. + * + * @param {number} t - The interpolation factor. + * @param {number} p0 - The first control point. + * @param {number} p1 - The second control point. + * @param {number} p2 - The third control point. + * @param {number} p3 - The fourth control point. + * @return {number} The calculated point on a Catmull-Rom spline. + */ +function CatmullRom( t, p0, p1, p2, p3 ) { + + const v0 = ( p2 - p0 ) * 0.5; + const v1 = ( p3 - p1 ) * 0.5; + const t2 = t * t; + const t3 = t * t2; + return ( 2 * p1 - 2 * p2 + v0 + v1 ) * t3 + ( -3 * p1 + 3 * p2 - 2 * v0 - v1 ) * t2 + v0 * t + p1; + +} + +// + +function QuadraticBezierP0( t, p ) { + + const k = 1 - t; + return k * k * p; + +} + +function QuadraticBezierP1( t, p ) { + + return 2 * ( 1 - t ) * t * p; + +} + +function QuadraticBezierP2( t, p ) { + + return t * t * p; + +} + +/** + * Computes a point on a Quadratic Bezier curve. + * + * @param {number} t - The interpolation factor. + * @param {number} p0 - The first control point. + * @param {number} p1 - The second control point. + * @param {number} p2 - The third control point. + * @return {number} The calculated point on a Quadratic Bezier curve. + */ +function QuadraticBezier( t, p0, p1, p2 ) { + + return QuadraticBezierP0( t, p0 ) + QuadraticBezierP1( t, p1 ) + + QuadraticBezierP2( t, p2 ); + +} + +// + +function CubicBezierP0( t, p ) { + + const k = 1 - t; + return k * k * k * p; + +} + +function CubicBezierP1( t, p ) { + + const k = 1 - t; + return 3 * k * k * t * p; + +} + +function CubicBezierP2( t, p ) { + + return 3 * ( 1 - t ) * t * t * p; + +} + +function CubicBezierP3( t, p ) { + + return t * t * t * p; + +} + +/** + * Computes a point on a Cubic Bezier curve. + * + * @param {number} t - The interpolation factor. + * @param {number} p0 - The first control point. + * @param {number} p1 - The second control point. + * @param {number} p2 - The third control point. + * @param {number} p3 - The fourth control point. + * @return {number} The calculated point on a Cubic Bezier curve. + */ +function CubicBezier( t, p0, p1, p2, p3 ) { + + return CubicBezierP0( t, p0 ) + CubicBezierP1( t, p1 ) + CubicBezierP2( t, p2 ) + + CubicBezierP3( t, p3 ); + +} + +/** + * A curve representing a 2D Cubic Bezier curve. + * + * ```js + * const curve = new THREE.CubicBezierCurve( + * new THREE.Vector2( - 0, 0 ), + * new THREE.Vector2( - 5, 15 ), + * new THREE.Vector2( 20, 15 ), + * new THREE.Vector2( 10, 0 ) + * ); + * + * const points = curve.getPoints( 50 ); + * const geometry = new THREE.BufferGeometry().setFromPoints( points ); + * + * const material = new THREE.LineBasicMaterial( { color: 0xff0000 } ); + * + * // Create the final object to add to the scene + * const curveObject = new THREE.Line( geometry, material ); + * ``` + * + * @augments Curve + */ +class CubicBezierCurve extends Curve { + + /** + * Constructs a new Cubic Bezier curve. + * + * @param {Vector2} [v0] - The start point. + * @param {Vector2} [v1] - The first control point. + * @param {Vector2} [v2] - The second control point. + * @param {Vector2} [v3] - The end point. + */ + constructor( v0 = new Vector2(), v1 = new Vector2(), v2 = new Vector2(), v3 = new Vector2() ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isCubicBezierCurve = true; + + this.type = 'CubicBezierCurve'; + + /** + * The start point. + * + * @type {Vector2} + */ + this.v0 = v0; + + /** + * The first control point. + * + * @type {Vector2} + */ + this.v1 = v1; + + /** + * The second control point. + * + * @type {Vector2} + */ + this.v2 = v2; + + /** + * The end point. + * + * @type {Vector2} + */ + this.v3 = v3; + + } + + /** + * Returns a point on the curve. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector2} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector2} The position on the curve. + */ + getPoint( t, optionalTarget = new Vector2() ) { + + const point = optionalTarget; + + const v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3; + + point.set( + CubicBezier( t, v0.x, v1.x, v2.x, v3.x ), + CubicBezier( t, v0.y, v1.y, v2.y, v3.y ) + ); + + return point; + + } + + copy( source ) { + + super.copy( source ); + + this.v0.copy( source.v0 ); + this.v1.copy( source.v1 ); + this.v2.copy( source.v2 ); + this.v3.copy( source.v3 ); + + return this; + + } + + toJSON() { + + const data = super.toJSON(); + + data.v0 = this.v0.toArray(); + data.v1 = this.v1.toArray(); + data.v2 = this.v2.toArray(); + data.v3 = this.v3.toArray(); + + return data; + + } + + fromJSON( json ) { + + super.fromJSON( json ); + + this.v0.fromArray( json.v0 ); + this.v1.fromArray( json.v1 ); + this.v2.fromArray( json.v2 ); + this.v3.fromArray( json.v3 ); + + return this; + + } + +} + +/** + * A curve representing a 3D Cubic Bezier curve. + * + * @augments Curve + */ +class CubicBezierCurve3 extends Curve { + + /** + * Constructs a new Cubic Bezier curve. + * + * @param {Vector3} [v0] - The start point. + * @param {Vector3} [v1] - The first control point. + * @param {Vector3} [v2] - The second control point. + * @param {Vector3} [v3] - The end point. + */ + constructor( v0 = new Vector3(), v1 = new Vector3(), v2 = new Vector3(), v3 = new Vector3() ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isCubicBezierCurve3 = true; + + this.type = 'CubicBezierCurve3'; + + /** + * The start point. + * + * @type {Vector3} + */ + this.v0 = v0; + + /** + * The first control point. + * + * @type {Vector3} + */ + this.v1 = v1; + + /** + * The second control point. + * + * @type {Vector3} + */ + this.v2 = v2; + + /** + * The end point. + * + * @type {Vector3} + */ + this.v3 = v3; + + } + + /** + * Returns a point on the curve. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector3} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector3} The position on the curve. + */ + getPoint( t, optionalTarget = new Vector3() ) { + + const point = optionalTarget; + + const v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3; + + point.set( + CubicBezier( t, v0.x, v1.x, v2.x, v3.x ), + CubicBezier( t, v0.y, v1.y, v2.y, v3.y ), + CubicBezier( t, v0.z, v1.z, v2.z, v3.z ) + ); + + return point; + + } + + copy( source ) { + + super.copy( source ); + + this.v0.copy( source.v0 ); + this.v1.copy( source.v1 ); + this.v2.copy( source.v2 ); + this.v3.copy( source.v3 ); + + return this; + + } + + toJSON() { + + const data = super.toJSON(); + + data.v0 = this.v0.toArray(); + data.v1 = this.v1.toArray(); + data.v2 = this.v2.toArray(); + data.v3 = this.v3.toArray(); + + return data; + + } + + fromJSON( json ) { + + super.fromJSON( json ); + + this.v0.fromArray( json.v0 ); + this.v1.fromArray( json.v1 ); + this.v2.fromArray( json.v2 ); + this.v3.fromArray( json.v3 ); + + return this; + + } + +} + +/** + * A curve representing a 2D line segment. + * + * @augments Curve + */ +class LineCurve extends Curve { + + /** + * Constructs a new line curve. + * + * @param {Vector2} [v1] - The start point. + * @param {Vector2} [v2] - The end point. + */ + constructor( v1 = new Vector2(), v2 = new Vector2() ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLineCurve = true; + + this.type = 'LineCurve'; + + /** + * The start point. + * + * @type {Vector2} + */ + this.v1 = v1; + + /** + * The end point. + * + * @type {Vector2} + */ + this.v2 = v2; + + } + + /** + * Returns a point on the line. + * + * @param {number} t - A interpolation factor representing a position on the line. Must be in the range `[0,1]`. + * @param {Vector2} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector2} The position on the line. + */ + getPoint( t, optionalTarget = new Vector2() ) { + + const point = optionalTarget; + + if ( t === 1 ) { + + point.copy( this.v2 ); + + } else { + + point.copy( this.v2 ).sub( this.v1 ); + point.multiplyScalar( t ).add( this.v1 ); + + } + + return point; + + } + + // Line curve is linear, so we can overwrite default getPointAt + getPointAt( u, optionalTarget ) { + + return this.getPoint( u, optionalTarget ); + + } + + getTangent( t, optionalTarget = new Vector2() ) { + + return optionalTarget.subVectors( this.v2, this.v1 ).normalize(); + + } + + getTangentAt( u, optionalTarget ) { + + return this.getTangent( u, optionalTarget ); + + } + + copy( source ) { + + super.copy( source ); + + this.v1.copy( source.v1 ); + this.v2.copy( source.v2 ); + + return this; + + } + + toJSON() { + + const data = super.toJSON(); + + data.v1 = this.v1.toArray(); + data.v2 = this.v2.toArray(); + + return data; + + } + + fromJSON( json ) { + + super.fromJSON( json ); + + this.v1.fromArray( json.v1 ); + this.v2.fromArray( json.v2 ); + + return this; + + } + +} + +/** + * A curve representing a 3D line segment. + * + * @augments Curve + */ +class LineCurve3 extends Curve { + + /** + * Constructs a new line curve. + * + * @param {Vector3} [v1] - The start point. + * @param {Vector3} [v2] - The end point. + */ + constructor( v1 = new Vector3(), v2 = new Vector3() ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLineCurve3 = true; + + this.type = 'LineCurve3'; + + /** + * The start point. + * + * @type {Vector3} + */ + this.v1 = v1; + + /** + * The end point. + * + * @type {Vector2} + */ + this.v2 = v2; + + } + + /** + * Returns a point on the line. + * + * @param {number} t - A interpolation factor representing a position on the line. Must be in the range `[0,1]`. + * @param {Vector3} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector3} The position on the line. + */ + getPoint( t, optionalTarget = new Vector3() ) { + + const point = optionalTarget; + + if ( t === 1 ) { + + point.copy( this.v2 ); + + } else { + + point.copy( this.v2 ).sub( this.v1 ); + point.multiplyScalar( t ).add( this.v1 ); + + } + + return point; + + } + + // Line curve is linear, so we can overwrite default getPointAt + getPointAt( u, optionalTarget ) { + + return this.getPoint( u, optionalTarget ); + + } + + getTangent( t, optionalTarget = new Vector3() ) { + + return optionalTarget.subVectors( this.v2, this.v1 ).normalize(); + + } + + getTangentAt( u, optionalTarget ) { + + return this.getTangent( u, optionalTarget ); + + } + + copy( source ) { + + super.copy( source ); + + this.v1.copy( source.v1 ); + this.v2.copy( source.v2 ); + + return this; + + } + + toJSON() { + + const data = super.toJSON(); + + data.v1 = this.v1.toArray(); + data.v2 = this.v2.toArray(); + + return data; + + } + + fromJSON( json ) { + + super.fromJSON( json ); + + this.v1.fromArray( json.v1 ); + this.v2.fromArray( json.v2 ); + + return this; + + } + +} + +/** + * A curve representing a 2D Quadratic Bezier curve. + * + * ```js + * const curve = new THREE.QuadraticBezierCurve( + * new THREE.Vector2( - 10, 0 ), + * new THREE.Vector2( 20, 15 ), + * new THREE.Vector2( 10, 0 ) + * ) + * + * const points = curve.getPoints( 50 ); + * const geometry = new THREE.BufferGeometry().setFromPoints( points ); + * + * const material = new THREE.LineBasicMaterial( { color: 0xff0000 } ); + * + * // Create the final object to add to the scene + * const curveObject = new THREE.Line( geometry, material ); + * ``` + * + * @augments Curve + */ +class QuadraticBezierCurve extends Curve { + + /** + * Constructs a new Quadratic Bezier curve. + * + * @param {Vector2} [v0] - The start point. + * @param {Vector2} [v1] - The control point. + * @param {Vector2} [v2] - The end point. + */ + constructor( v0 = new Vector2(), v1 = new Vector2(), v2 = new Vector2() ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isQuadraticBezierCurve = true; + + this.type = 'QuadraticBezierCurve'; + + /** + * The start point. + * + * @type {Vector2} + */ + this.v0 = v0; + + /** + * The control point. + * + * @type {Vector2} + */ + this.v1 = v1; + + /** + * The end point. + * + * @type {Vector2} + */ + this.v2 = v2; + + } + + /** + * Returns a point on the curve. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector2} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector2} The position on the curve. + */ + getPoint( t, optionalTarget = new Vector2() ) { + + const point = optionalTarget; + + const v0 = this.v0, v1 = this.v1, v2 = this.v2; + + point.set( + QuadraticBezier( t, v0.x, v1.x, v2.x ), + QuadraticBezier( t, v0.y, v1.y, v2.y ) + ); + + return point; + + } + + copy( source ) { + + super.copy( source ); + + this.v0.copy( source.v0 ); + this.v1.copy( source.v1 ); + this.v2.copy( source.v2 ); + + return this; + + } + + toJSON() { + + const data = super.toJSON(); + + data.v0 = this.v0.toArray(); + data.v1 = this.v1.toArray(); + data.v2 = this.v2.toArray(); + + return data; + + } + + fromJSON( json ) { + + super.fromJSON( json ); + + this.v0.fromArray( json.v0 ); + this.v1.fromArray( json.v1 ); + this.v2.fromArray( json.v2 ); + + return this; + + } + +} + +/** + * A curve representing a 3D Quadratic Bezier curve. + * + * @augments Curve + */ +class QuadraticBezierCurve3 extends Curve { + + /** + * Constructs a new Quadratic Bezier curve. + * + * @param {Vector3} [v0] - The start point. + * @param {Vector3} [v1] - The control point. + * @param {Vector3} [v2] - The end point. + */ + constructor( v0 = new Vector3(), v1 = new Vector3(), v2 = new Vector3() ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isQuadraticBezierCurve3 = true; + + this.type = 'QuadraticBezierCurve3'; + + /** + * The start point. + * + * @type {Vector3} + */ + this.v0 = v0; + + /** + * The control point. + * + * @type {Vector3} + */ + this.v1 = v1; + + /** + * The end point. + * + * @type {Vector3} + */ + this.v2 = v2; + + } + + /** + * Returns a point on the curve. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector3} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector3} The position on the curve. + */ + getPoint( t, optionalTarget = new Vector3() ) { + + const point = optionalTarget; + + const v0 = this.v0, v1 = this.v1, v2 = this.v2; + + point.set( + QuadraticBezier( t, v0.x, v1.x, v2.x ), + QuadraticBezier( t, v0.y, v1.y, v2.y ), + QuadraticBezier( t, v0.z, v1.z, v2.z ) + ); + + return point; + + } + + copy( source ) { + + super.copy( source ); + + this.v0.copy( source.v0 ); + this.v1.copy( source.v1 ); + this.v2.copy( source.v2 ); + + return this; + + } + + toJSON() { + + const data = super.toJSON(); + + data.v0 = this.v0.toArray(); + data.v1 = this.v1.toArray(); + data.v2 = this.v2.toArray(); + + return data; + + } + + fromJSON( json ) { + + super.fromJSON( json ); + + this.v0.fromArray( json.v0 ); + this.v1.fromArray( json.v1 ); + this.v2.fromArray( json.v2 ); + + return this; + + } + +} + +/** + * A curve representing a 2D spline curve. + * + * ```js + * // Create a sine-like wave + * const curve = new THREE.SplineCurve( [ + * new THREE.Vector2( -10, 0 ), + * new THREE.Vector2( -5, 5 ), + * new THREE.Vector2( 0, 0 ), + * new THREE.Vector2( 5, -5 ), + * new THREE.Vector2( 10, 0 ) + * ] ); + * + * const points = curve.getPoints( 50 ); + * const geometry = new THREE.BufferGeometry().setFromPoints( points ); + * + * const material = new THREE.LineBasicMaterial( { color: 0xff0000 } ); + * + * // Create the final object to add to the scene + * const splineObject = new THREE.Line( geometry, material ); + * ``` + * + * @augments Curve + */ +class SplineCurve extends Curve { + + /** + * Constructs a new 2D spline curve. + * + * @param {Array} [points] - An array of 2D points defining the curve. + */ + constructor( points = [] ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSplineCurve = true; + + this.type = 'SplineCurve'; + + /** + * An array of 2D points defining the curve. + * + * @type {Array} + */ + this.points = points; + + } + + /** + * Returns a point on the curve. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector2} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector2} The position on the curve. + */ + getPoint( t, optionalTarget = new Vector2() ) { + + const point = optionalTarget; + + const points = this.points; + const p = ( points.length - 1 ) * t; + + const intPoint = Math.floor( p ); + const weight = p - intPoint; + + const p0 = points[ intPoint === 0 ? intPoint : intPoint - 1 ]; + const p1 = points[ intPoint ]; + const p2 = points[ intPoint > points.length - 2 ? points.length - 1 : intPoint + 1 ]; + const p3 = points[ intPoint > points.length - 3 ? points.length - 1 : intPoint + 2 ]; + + point.set( + CatmullRom( weight, p0.x, p1.x, p2.x, p3.x ), + CatmullRom( weight, p0.y, p1.y, p2.y, p3.y ) + ); + + return point; + + } + + copy( source ) { + + super.copy( source ); + + this.points = []; + + for ( let i = 0, l = source.points.length; i < l; i ++ ) { + + const point = source.points[ i ]; + + this.points.push( point.clone() ); + + } + + return this; + + } + + toJSON() { + + const data = super.toJSON(); + + data.points = []; + + for ( let i = 0, l = this.points.length; i < l; i ++ ) { + + const point = this.points[ i ]; + data.points.push( point.toArray() ); + + } + + return data; + + } + + fromJSON( json ) { + + super.fromJSON( json ); + + this.points = []; + + for ( let i = 0, l = json.points.length; i < l; i ++ ) { + + const point = json.points[ i ]; + this.points.push( new Vector2().fromArray( point ) ); + + } + + return this; + + } + +} + +var Curves = /*#__PURE__*/Object.freeze({ + __proto__: null, + ArcCurve: ArcCurve, + CatmullRomCurve3: CatmullRomCurve3, + CubicBezierCurve: CubicBezierCurve, + CubicBezierCurve3: CubicBezierCurve3, + EllipseCurve: EllipseCurve, + LineCurve: LineCurve, + LineCurve3: LineCurve3, + QuadraticBezierCurve: QuadraticBezierCurve, + QuadraticBezierCurve3: QuadraticBezierCurve3, + SplineCurve: SplineCurve +}); + +/** + * A base class extending {@link Curve}. `CurvePath` is simply an + * array of connected curves, but retains the API of a curve. + * + * @augments Curve + */ +class CurvePath extends Curve { + + /** + * Constructs a new curve path. + */ + constructor() { + + super(); + + this.type = 'CurvePath'; + + /** + * An array of curves defining the + * path. + * + * @type {Array} + */ + this.curves = []; + + /** + * Whether the path should automatically be closed + * by a line curve. + * + * @type {boolean} + * @default false + */ + this.autoClose = false; + + } + + /** + * Adds a curve to this curve path. + * + * @param {Curve} curve - The curve to add. + */ + add( curve ) { + + this.curves.push( curve ); + + } + + /** + * Adds a line curve to close the path. + * + * @return {CurvePath} A reference to this curve path. + */ + closePath() { + + // Add a line curve if start and end of lines are not connected + const startPoint = this.curves[ 0 ].getPoint( 0 ); + const endPoint = this.curves[ this.curves.length - 1 ].getPoint( 1 ); + + if ( ! startPoint.equals( endPoint ) ) { + + const lineType = ( startPoint.isVector2 === true ) ? 'LineCurve' : 'LineCurve3'; + this.curves.push( new Curves[ lineType ]( endPoint, startPoint ) ); + + } + + return this; + + } + + /** + * This method returns a vector in 2D or 3D space (depending on the curve definitions) + * for the given interpolation factor. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {(Vector2|Vector3)} [optionalTarget] - The optional target vector the result is written to. + * @return {?(Vector2|Vector3)} The position on the curve. It can be a 2D or 3D vector depending on the curve definition. + */ + getPoint( t, optionalTarget ) { + + // To get accurate point with reference to + // entire path distance at time t, + // following has to be done: + + // 1. Length of each sub path have to be known + // 2. Locate and identify type of curve + // 3. Get t for the curve + // 4. Return curve.getPointAt(t') + + const d = t * this.getLength(); + const curveLengths = this.getCurveLengths(); + let i = 0; + + // To think about boundaries points. + + while ( i < curveLengths.length ) { + + if ( curveLengths[ i ] >= d ) { + + const diff = curveLengths[ i ] - d; + const curve = this.curves[ i ]; + + const segmentLength = curve.getLength(); + const u = segmentLength === 0 ? 0 : 1 - diff / segmentLength; + + return curve.getPointAt( u, optionalTarget ); + + } + + i ++; + + } + + return null; + + // loop where sum != 0, sum > d , sum+1 } The curve lengths. + */ + getCurveLengths() { + + // Compute lengths and cache them + // We cannot overwrite getLengths() because UtoT mapping uses it. + // We use cache values if curves and cache array are same length + + if ( this.cacheLengths && this.cacheLengths.length === this.curves.length ) { + + return this.cacheLengths; + + } + + // Get length of sub-curve + // Push sums into cached array + + const lengths = []; + let sums = 0; + + for ( let i = 0, l = this.curves.length; i < l; i ++ ) { + + sums += this.curves[ i ].getLength(); + lengths.push( sums ); + + } + + this.cacheLengths = lengths; + + return lengths; + + } + + getSpacedPoints( divisions = 40 ) { + + const points = []; + + for ( let i = 0; i <= divisions; i ++ ) { + + points.push( this.getPoint( i / divisions ) ); + + } + + if ( this.autoClose ) { + + points.push( points[ 0 ] ); + + } + + return points; + + } + + getPoints( divisions = 12 ) { + + const points = []; + let last; + + for ( let i = 0, curves = this.curves; i < curves.length; i ++ ) { + + const curve = curves[ i ]; + const resolution = curve.isEllipseCurve ? divisions * 2 + : ( curve.isLineCurve || curve.isLineCurve3 ) ? 1 + : curve.isSplineCurve ? divisions * curve.points.length + : divisions; + + const pts = curve.getPoints( resolution ); + + for ( let j = 0; j < pts.length; j ++ ) { + + const point = pts[ j ]; + + if ( last && last.equals( point ) ) continue; // ensures no consecutive points are duplicates + + points.push( point ); + last = point; + + } + + } + + if ( this.autoClose && points.length > 1 && ! points[ points.length - 1 ].equals( points[ 0 ] ) ) { + + points.push( points[ 0 ] ); + + } + + return points; + + } + + copy( source ) { + + super.copy( source ); + + this.curves = []; + + for ( let i = 0, l = source.curves.length; i < l; i ++ ) { + + const curve = source.curves[ i ]; + + this.curves.push( curve.clone() ); + + } + + this.autoClose = source.autoClose; + + return this; + + } + + toJSON() { + + const data = super.toJSON(); + + data.autoClose = this.autoClose; + data.curves = []; + + for ( let i = 0, l = this.curves.length; i < l; i ++ ) { + + const curve = this.curves[ i ]; + data.curves.push( curve.toJSON() ); + + } + + return data; + + } + + fromJSON( json ) { + + super.fromJSON( json ); + + this.autoClose = json.autoClose; + this.curves = []; + + for ( let i = 0, l = json.curves.length; i < l; i ++ ) { + + const curve = json.curves[ i ]; + this.curves.push( new Curves[ curve.type ]().fromJSON( curve ) ); + + } + + return this; + + } + +} + +/** + * A 2D path representation. The class provides methods for creating paths + * and contours of 2D shapes similar to the 2D Canvas API. + * + * ```js + * const path = new THREE.Path(); + * + * path.lineTo( 0, 0.8 ); + * path.quadraticCurveTo( 0, 1, 0.2, 1 ); + * path.lineTo( 1, 1 ); + * + * const points = path.getPoints(); + * + * const geometry = new THREE.BufferGeometry().setFromPoints( points ); + * const material = new THREE.LineBasicMaterial( { color: 0xffffff } ); + * + * const line = new THREE.Line( geometry, material ); + * scene.add( line ); + * ``` + * + * @augments CurvePath + */ +class Path extends CurvePath { + + /** + * Constructs a new path. + * + * @param {Array} [points] - An array of 2D points defining the path. + */ + constructor( points ) { + + super(); + + this.type = 'Path'; + + /** + * The current offset of the path. Any new curve added will start here. + * + * @type {Vector2} + */ + this.currentPoint = new Vector2(); + + if ( points ) { + + this.setFromPoints( points ); + + } + + } + + /** + * Creates a path from the given list of points. The points are added + * to the path as instances of {@link LineCurve}. + * + * @param {Array} points - An array of 2D points. + * @return {Path} A reference to this path. + */ + setFromPoints( points ) { + + this.moveTo( points[ 0 ].x, points[ 0 ].y ); + + for ( let i = 1, l = points.length; i < l; i ++ ) { + + this.lineTo( points[ i ].x, points[ i ].y ); + + } + + return this; + + } + + /** + * Moves {@link Path#currentPoint} to the given point. + * + * @param {number} x - The x coordinate. + * @param {number} y - The y coordinate. + * @return {Path} A reference to this path. + */ + moveTo( x, y ) { + + this.currentPoint.set( x, y ); // TODO consider referencing vectors instead of copying? + + return this; + + } + + /** + * Adds an instance of {@link LineCurve} to the path by connecting + * the current point with the given one. + * + * @param {number} x - The x coordinate of the end point. + * @param {number} y - The y coordinate of the end point. + * @return {Path} A reference to this path. + */ + lineTo( x, y ) { + + const curve = new LineCurve( this.currentPoint.clone(), new Vector2( x, y ) ); + this.curves.push( curve ); + + this.currentPoint.set( x, y ); + + return this; + + } + + /** + * Adds an instance of {@link QuadraticBezierCurve} to the path by connecting + * the current point with the given one. + * + * @param {number} aCPx - The x coordinate of the control point. + * @param {number} aCPy - The y coordinate of the control point. + * @param {number} aX - The x coordinate of the end point. + * @param {number} aY - The y coordinate of the end point. + * @return {Path} A reference to this path. + */ + quadraticCurveTo( aCPx, aCPy, aX, aY ) { + + const curve = new QuadraticBezierCurve( + this.currentPoint.clone(), + new Vector2( aCPx, aCPy ), + new Vector2( aX, aY ) + ); + + this.curves.push( curve ); + + this.currentPoint.set( aX, aY ); + + return this; + + } + + /** + * Adds an instance of {@link CubicBezierCurve} to the path by connecting + * the current point with the given one. + * + * @param {number} aCP1x - The x coordinate of the first control point. + * @param {number} aCP1y - The y coordinate of the first control point. + * @param {number} aCP2x - The x coordinate of the second control point. + * @param {number} aCP2y - The y coordinate of the second control point. + * @param {number} aX - The x coordinate of the end point. + * @param {number} aY - The y coordinate of the end point. + * @return {Path} A reference to this path. + */ + bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) { + + const curve = new CubicBezierCurve( + this.currentPoint.clone(), + new Vector2( aCP1x, aCP1y ), + new Vector2( aCP2x, aCP2y ), + new Vector2( aX, aY ) + ); + + this.curves.push( curve ); + + this.currentPoint.set( aX, aY ); + + return this; + + } + + /** + * Adds an instance of {@link SplineCurve} to the path by connecting + * the current point with the given list of points. + * + * @param {Array} pts - An array of points in 2D space. + * @return {Path} A reference to this path. + */ + splineThru( pts ) { + + const npts = [ this.currentPoint.clone() ].concat( pts ); + + const curve = new SplineCurve( npts ); + this.curves.push( curve ); + + this.currentPoint.copy( pts[ pts.length - 1 ] ); + + return this; + + } + + /** + * Adds an arc as an instance of {@link EllipseCurve} to the path, positioned relative + * to the current point. + * + * @param {number} [aX=0] - The x coordinate of the center of the arc offsetted from the previous curve. + * @param {number} [aY=0] - The y coordinate of the center of the arc offsetted from the previous curve. + * @param {number} [aRadius=1] - The radius of the arc. + * @param {number} [aStartAngle=0] - The start angle in radians. + * @param {number} [aEndAngle=Math.PI*2] - The end angle in radians. + * @param {boolean} [aClockwise=false] - Whether to sweep the arc clockwise or not. + * @return {Path} A reference to this path. + */ + arc( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) { + + const x0 = this.currentPoint.x; + const y0 = this.currentPoint.y; + + this.absarc( aX + x0, aY + y0, aRadius, + aStartAngle, aEndAngle, aClockwise ); + + return this; + + } + + /** + * Adds an absolutely positioned arc as an instance of {@link EllipseCurve} to the path. + * + * @param {number} [aX=0] - The x coordinate of the center of the arc. + * @param {number} [aY=0] - The y coordinate of the center of the arc. + * @param {number} [aRadius=1] - The radius of the arc. + * @param {number} [aStartAngle=0] - The start angle in radians. + * @param {number} [aEndAngle=Math.PI*2] - The end angle in radians. + * @param {boolean} [aClockwise=false] - Whether to sweep the arc clockwise or not. + * @return {Path} A reference to this path. + */ + absarc( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) { + + this.absellipse( aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise ); + + return this; + + } + + /** + * Adds an ellipse as an instance of {@link EllipseCurve} to the path, positioned relative + * to the current point + * + * @param {number} [aX=0] - The x coordinate of the center of the ellipse offsetted from the previous curve. + * @param {number} [aY=0] - The y coordinate of the center of the ellipse offsetted from the previous curve. + * @param {number} [xRadius=1] - The radius of the ellipse in the x axis. + * @param {number} [yRadius=1] - The radius of the ellipse in the y axis. + * @param {number} [aStartAngle=0] - The start angle in radians. + * @param {number} [aEndAngle=Math.PI*2] - The end angle in radians. + * @param {boolean} [aClockwise=false] - Whether to sweep the ellipse clockwise or not. + * @param {number} [aRotation=0] - The rotation angle of the ellipse in radians, counterclockwise from the positive X axis. + * @return {Path} A reference to this path. + */ + ellipse( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) { + + const x0 = this.currentPoint.x; + const y0 = this.currentPoint.y; + + this.absellipse( aX + x0, aY + y0, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ); + + return this; + + } + + /** + * Adds an absolutely positioned ellipse as an instance of {@link EllipseCurve} to the path. + * + * @param {number} [aX=0] - The x coordinate of the absolute center of the ellipse. + * @param {number} [aY=0] - The y coordinate of the absolute center of the ellipse. + * @param {number} [xRadius=1] - The radius of the ellipse in the x axis. + * @param {number} [yRadius=1] - The radius of the ellipse in the y axis. + * @param {number} [aStartAngle=0] - The start angle in radians. + * @param {number} [aEndAngle=Math.PI*2] - The end angle in radians. + * @param {boolean} [aClockwise=false] - Whether to sweep the ellipse clockwise or not. + * @param {number} [aRotation=0] - The rotation angle of the ellipse in radians, counterclockwise from the positive X axis. + * @return {Path} A reference to this path. + */ + absellipse( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) { + + const curve = new EllipseCurve( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ); + + if ( this.curves.length > 0 ) { + + // if a previous curve is present, attempt to join + const firstPoint = curve.getPoint( 0 ); + + if ( ! firstPoint.equals( this.currentPoint ) ) { + + this.lineTo( firstPoint.x, firstPoint.y ); + + } + + } + + this.curves.push( curve ); + + const lastPoint = curve.getPoint( 1 ); + this.currentPoint.copy( lastPoint ); + + return this; + + } + + copy( source ) { + + super.copy( source ); + + this.currentPoint.copy( source.currentPoint ); + + return this; + + } + + toJSON() { + + const data = super.toJSON(); + + data.currentPoint = this.currentPoint.toArray(); + + return data; + + } + + fromJSON( json ) { + + super.fromJSON( json ); + + this.currentPoint.fromArray( json.currentPoint ); + + return this; + + } + +} + +/** + * Defines an arbitrary 2d shape plane using paths with optional holes. It + * can be used with {@link ExtrudeGeometry}, {@link ShapeGeometry}, to get + * points, or to get triangulated faces. + * + * ```js + * const heartShape = new THREE.Shape(); + * + * heartShape.moveTo( 25, 25 ); + * heartShape.bezierCurveTo( 25, 25, 20, 0, 0, 0 ); + * heartShape.bezierCurveTo( - 30, 0, - 30, 35, - 30, 35 ); + * heartShape.bezierCurveTo( - 30, 55, - 10, 77, 25, 95 ); + * heartShape.bezierCurveTo( 60, 77, 80, 55, 80, 35 ); + * heartShape.bezierCurveTo( 80, 35, 80, 0, 50, 0 ); + * heartShape.bezierCurveTo( 35, 0, 25, 25, 25, 25 ); + * + * const extrudeSettings = { + * depth: 8, + * bevelEnabled: true, + * bevelSegments: 2, + * steps: 2, + * bevelSize: 1, + * bevelThickness: 1 + * }; + * + * const geometry = new THREE.ExtrudeGeometry( heartShape, extrudeSettings ); + * const mesh = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial() ); + * ``` + * + * @augments Path + */ +class Shape extends Path { + + /** + * Constructs a new shape. + * + * @param {Array} [points] - An array of 2D points defining the shape. + */ + constructor( points ) { + + super( points ); + + /** + * The UUID of the shape. + * + * @type {string} + * @readonly + */ + this.uuid = generateUUID(); + + this.type = 'Shape'; + + /** + * Defines the holes in the shape. Hole definitions must use the + * opposite winding order (CW/CCW) than the outer shape. + * + * @type {Array} + * @readonly + */ + this.holes = []; + + } + + /** + * Returns an array representing each contour of the holes + * as a list of 2D points. + * + * @param {number} divisions - The fineness of the result. + * @return {Array>} The holes as a series of 2D points. + */ + getPointsHoles( divisions ) { + + const holesPts = []; + + for ( let i = 0, l = this.holes.length; i < l; i ++ ) { + + holesPts[ i ] = this.holes[ i ].getPoints( divisions ); + + } + + return holesPts; + + } + + // get points of shape and holes (keypoints based on segments parameter) + + /** + * Returns an object that holds contour data for the shape and its holes as + * arrays of 2D points. + * + * @param {number} divisions - The fineness of the result. + * @return {{shape:Array,holes:Array>}} An object with contour data. + */ + extractPoints( divisions ) { + + return { + + shape: this.getPoints( divisions ), + holes: this.getPointsHoles( divisions ) + + }; + + } + + copy( source ) { + + super.copy( source ); + + this.holes = []; + + for ( let i = 0, l = source.holes.length; i < l; i ++ ) { + + const hole = source.holes[ i ]; + + this.holes.push( hole.clone() ); + + } + + return this; + + } + + toJSON() { + + const data = super.toJSON(); + + data.uuid = this.uuid; + data.holes = []; + + for ( let i = 0, l = this.holes.length; i < l; i ++ ) { + + const hole = this.holes[ i ]; + data.holes.push( hole.toJSON() ); + + } + + return data; + + } + + fromJSON( json ) { + + super.fromJSON( json ); + + this.uuid = json.uuid; + this.holes = []; + + for ( let i = 0, l = json.holes.length; i < l; i ++ ) { + + const hole = json.holes[ i ]; + this.holes.push( new Path().fromJSON( hole ) ); + + } + + return this; + + } + +} + +/* eslint-disable */ +// copy of mapbox/earcut version 3.0.1 +// https://github.com/mapbox/earcut/tree/v3.0.1 + +function earcut(data, holeIndices, dim = 2) { + + const hasHoles = holeIndices && holeIndices.length; + const outerLen = hasHoles ? holeIndices[0] * dim : data.length; + let outerNode = linkedList(data, 0, outerLen, dim, true); + const triangles = []; + + if (!outerNode || outerNode.next === outerNode.prev) return triangles; + + let minX, minY, invSize; + + if (hasHoles) outerNode = eliminateHoles(data, holeIndices, outerNode, dim); + + // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox + if (data.length > 80 * dim) { + minX = Infinity; + minY = Infinity; + let maxX = -Infinity; + let maxY = -Infinity; + + for (let i = dim; i < outerLen; i += dim) { + const x = data[i]; + const y = data[i + 1]; + if (x < minX) minX = x; + if (y < minY) minY = y; + if (x > maxX) maxX = x; + if (y > maxY) maxY = y; + } + + // minX, minY and invSize are later used to transform coords into integers for z-order calculation + invSize = Math.max(maxX - minX, maxY - minY); + invSize = invSize !== 0 ? 32767 / invSize : 0; + } + + earcutLinked(outerNode, triangles, dim, minX, minY, invSize, 0); + + return triangles; +} + +// create a circular doubly linked list from polygon points in the specified winding order +function linkedList(data, start, end, dim, clockwise) { + let last; + + if (clockwise === (signedArea(data, start, end, dim) > 0)) { + for (let i = start; i < end; i += dim) last = insertNode(i / dim | 0, data[i], data[i + 1], last); + } else { + for (let i = end - dim; i >= start; i -= dim) last = insertNode(i / dim | 0, data[i], data[i + 1], last); + } + + if (last && equals(last, last.next)) { + removeNode(last); + last = last.next; + } + + return last; +} + +// eliminate colinear or duplicate points +function filterPoints(start, end) { + if (!start) return start; + if (!end) end = start; + + let p = start, + again; + do { + again = false; + + if (!p.steiner && (equals(p, p.next) || area(p.prev, p, p.next) === 0)) { + removeNode(p); + p = end = p.prev; + if (p === p.next) break; + again = true; + + } else { + p = p.next; + } + } while (again || p !== end); + + return end; +} + +// main ear slicing loop which triangulates a polygon (given as a linked list) +function earcutLinked(ear, triangles, dim, minX, minY, invSize, pass) { + if (!ear) return; + + // interlink polygon nodes in z-order + if (!pass && invSize) indexCurve(ear, minX, minY, invSize); + + let stop = ear; + + // iterate through ears, slicing them one by one + while (ear.prev !== ear.next) { + const prev = ear.prev; + const next = ear.next; + + if (invSize ? isEarHashed(ear, minX, minY, invSize) : isEar(ear)) { + triangles.push(prev.i, ear.i, next.i); // cut off the triangle + + removeNode(ear); + + // skipping the next vertex leads to less sliver triangles + ear = next.next; + stop = next.next; + + continue; + } + + ear = next; + + // if we looped through the whole remaining polygon and can't find any more ears + if (ear === stop) { + // try filtering points and slicing again + if (!pass) { + earcutLinked(filterPoints(ear), triangles, dim, minX, minY, invSize, 1); + + // if this didn't work, try curing all small self-intersections locally + } else if (pass === 1) { + ear = cureLocalIntersections(filterPoints(ear), triangles); + earcutLinked(ear, triangles, dim, minX, minY, invSize, 2); + + // as a last resort, try splitting the remaining polygon into two + } else if (pass === 2) { + splitEarcut(ear, triangles, dim, minX, minY, invSize); + } + + break; + } + } +} + +// check whether a polygon node forms a valid ear with adjacent nodes +function isEar(ear) { + const a = ear.prev, + b = ear, + c = ear.next; + + if (area(a, b, c) >= 0) return false; // reflex, can't be an ear + + // now make sure we don't have other points inside the potential ear + const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y; + + // triangle bbox + const x0 = Math.min(ax, bx, cx), + y0 = Math.min(ay, by, cy), + x1 = Math.max(ax, bx, cx), + y1 = Math.max(ay, by, cy); + + let p = c.next; + while (p !== a) { + if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && + pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, p.x, p.y) && + area(p.prev, p, p.next) >= 0) return false; + p = p.next; + } + + return true; +} + +function isEarHashed(ear, minX, minY, invSize) { + const a = ear.prev, + b = ear, + c = ear.next; + + if (area(a, b, c) >= 0) return false; // reflex, can't be an ear + + const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y; + + // triangle bbox + const x0 = Math.min(ax, bx, cx), + y0 = Math.min(ay, by, cy), + x1 = Math.max(ax, bx, cx), + y1 = Math.max(ay, by, cy); + + // z-order range for the current triangle bbox; + const minZ = zOrder(x0, y0, minX, minY, invSize), + maxZ = zOrder(x1, y1, minX, minY, invSize); + + let p = ear.prevZ, + n = ear.nextZ; + + // look for points inside the triangle in both directions + while (p && p.z >= minZ && n && n.z <= maxZ) { + if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c && + pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false; + p = p.prevZ; + + if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c && + pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false; + n = n.nextZ; + } + + // look for remaining points in decreasing z-order + while (p && p.z >= minZ) { + if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c && + pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false; + p = p.prevZ; + } + + // look for remaining points in increasing z-order + while (n && n.z <= maxZ) { + if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c && + pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false; + n = n.nextZ; + } + + return true; +} + +// go through all polygon nodes and cure small local self-intersections +function cureLocalIntersections(start, triangles) { + let p = start; + do { + const a = p.prev, + b = p.next.next; + + if (!equals(a, b) && intersects(a, p, p.next, b) && locallyInside(a, b) && locallyInside(b, a)) { + + triangles.push(a.i, p.i, b.i); + + // remove two nodes involved + removeNode(p); + removeNode(p.next); + + p = start = b; + } + p = p.next; + } while (p !== start); + + return filterPoints(p); +} + +// try splitting polygon into two and triangulate them independently +function splitEarcut(start, triangles, dim, minX, minY, invSize) { + // look for a valid diagonal that divides the polygon into two + let a = start; + do { + let b = a.next.next; + while (b !== a.prev) { + if (a.i !== b.i && isValidDiagonal(a, b)) { + // split the polygon in two by the diagonal + let c = splitPolygon(a, b); + + // filter colinear points around the cuts + a = filterPoints(a, a.next); + c = filterPoints(c, c.next); + + // run earcut on each half + earcutLinked(a, triangles, dim, minX, minY, invSize, 0); + earcutLinked(c, triangles, dim, minX, minY, invSize, 0); + return; + } + b = b.next; + } + a = a.next; + } while (a !== start); +} + +// link every hole into the outer loop, producing a single-ring polygon without holes +function eliminateHoles(data, holeIndices, outerNode, dim) { + const queue = []; + + for (let i = 0, len = holeIndices.length; i < len; i++) { + const start = holeIndices[i] * dim; + const end = i < len - 1 ? holeIndices[i + 1] * dim : data.length; + const list = linkedList(data, start, end, dim, false); + if (list === list.next) list.steiner = true; + queue.push(getLeftmost(list)); + } + + queue.sort(compareXYSlope); + + // process holes from left to right + for (let i = 0; i < queue.length; i++) { + outerNode = eliminateHole(queue[i], outerNode); + } + + return outerNode; +} + +function compareXYSlope(a, b) { + let result = a.x - b.x; + // when the left-most point of 2 holes meet at a vertex, sort the holes counterclockwise so that when we find + // the bridge to the outer shell is always the point that they meet at. + if (result === 0) { + result = a.y - b.y; + if (result === 0) { + const aSlope = (a.next.y - a.y) / (a.next.x - a.x); + const bSlope = (b.next.y - b.y) / (b.next.x - b.x); + result = aSlope - bSlope; + } + } + return result; +} + +// find a bridge between vertices that connects hole with an outer ring and and link it +function eliminateHole(hole, outerNode) { + const bridge = findHoleBridge(hole, outerNode); + if (!bridge) { + return outerNode; + } + + const bridgeReverse = splitPolygon(bridge, hole); + + // filter collinear points around the cuts + filterPoints(bridgeReverse, bridgeReverse.next); + return filterPoints(bridge, bridge.next); +} + +// David Eberly's algorithm for finding a bridge between hole and outer polygon +function findHoleBridge(hole, outerNode) { + let p = outerNode; + const hx = hole.x; + const hy = hole.y; + let qx = -Infinity; + let m; + + // find a segment intersected by a ray from the hole's leftmost point to the left; + // segment's endpoint with lesser x will be potential connection point + // unless they intersect at a vertex, then choose the vertex + if (equals(hole, p)) return p; + do { + if (equals(hole, p.next)) return p.next; + else if (hy <= p.y && hy >= p.next.y && p.next.y !== p.y) { + const x = p.x + (hy - p.y) * (p.next.x - p.x) / (p.next.y - p.y); + if (x <= hx && x > qx) { + qx = x; + m = p.x < p.next.x ? p : p.next; + if (x === hx) return m; // hole touches outer segment; pick leftmost endpoint + } + } + p = p.next; + } while (p !== outerNode); + + if (!m) return null; + + // look for points inside the triangle of hole point, segment intersection and endpoint; + // if there are no points found, we have a valid connection; + // otherwise choose the point of the minimum angle with the ray as connection point + + const stop = m; + const mx = m.x; + const my = m.y; + let tanMin = Infinity; + + p = m; + + do { + if (hx >= p.x && p.x >= mx && hx !== p.x && + pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y)) { + + const tan = Math.abs(hy - p.y) / (hx - p.x); // tangential + + if (locallyInside(p, hole) && + (tan < tanMin || (tan === tanMin && (p.x > m.x || (p.x === m.x && sectorContainsSector(m, p)))))) { + m = p; + tanMin = tan; + } + } + + p = p.next; + } while (p !== stop); + + return m; +} + +// whether sector in vertex m contains sector in vertex p in the same coordinates +function sectorContainsSector(m, p) { + return area(m.prev, m, p.prev) < 0 && area(p.next, m, m.next) < 0; +} + +// interlink polygon nodes in z-order +function indexCurve(start, minX, minY, invSize) { + let p = start; + do { + if (p.z === 0) p.z = zOrder(p.x, p.y, minX, minY, invSize); + p.prevZ = p.prev; + p.nextZ = p.next; + p = p.next; + } while (p !== start); + + p.prevZ.nextZ = null; + p.prevZ = null; + + sortLinked(p); +} + +// Simon Tatham's linked list merge sort algorithm +// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html +function sortLinked(list) { + let numMerges; + let inSize = 1; + + do { + let p = list; + let e; + list = null; + let tail = null; + numMerges = 0; + + while (p) { + numMerges++; + let q = p; + let pSize = 0; + for (let i = 0; i < inSize; i++) { + pSize++; + q = q.nextZ; + if (!q) break; + } + let qSize = inSize; + + while (pSize > 0 || (qSize > 0 && q)) { + + if (pSize !== 0 && (qSize === 0 || !q || p.z <= q.z)) { + e = p; + p = p.nextZ; + pSize--; + } else { + e = q; + q = q.nextZ; + qSize--; + } + + if (tail) tail.nextZ = e; + else list = e; + + e.prevZ = tail; + tail = e; + } + + p = q; + } + + tail.nextZ = null; + inSize *= 2; + + } while (numMerges > 1); + + return list; +} + +// z-order of a point given coords and inverse of the longer side of data bbox +function zOrder(x, y, minX, minY, invSize) { + // coords are transformed into non-negative 15-bit integer range + x = (x - minX) * invSize | 0; + y = (y - minY) * invSize | 0; + + x = (x | (x << 8)) & 0x00FF00FF; + x = (x | (x << 4)) & 0x0F0F0F0F; + x = (x | (x << 2)) & 0x33333333; + x = (x | (x << 1)) & 0x55555555; + + y = (y | (y << 8)) & 0x00FF00FF; + y = (y | (y << 4)) & 0x0F0F0F0F; + y = (y | (y << 2)) & 0x33333333; + y = (y | (y << 1)) & 0x55555555; + + return x | (y << 1); +} + +// find the leftmost node of a polygon ring +function getLeftmost(start) { + let p = start, + leftmost = start; + do { + if (p.x < leftmost.x || (p.x === leftmost.x && p.y < leftmost.y)) leftmost = p; + p = p.next; + } while (p !== start); + + return leftmost; +} + +// check if a point lies within a convex triangle +function pointInTriangle(ax, ay, bx, by, cx, cy, px, py) { + return (cx - px) * (ay - py) >= (ax - px) * (cy - py) && + (ax - px) * (by - py) >= (bx - px) * (ay - py) && + (bx - px) * (cy - py) >= (cx - px) * (by - py); +} + +// check if a point lies within a convex triangle but false if its equal to the first point of the triangle +function pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, px, py) { + return !(ax === px && ay === py) && pointInTriangle(ax, ay, bx, by, cx, cy, px, py); +} + +// check if a diagonal between two polygon nodes is valid (lies in polygon interior) +function isValidDiagonal(a, b) { + return a.next.i !== b.i && a.prev.i !== b.i && !intersectsPolygon(a, b) && // dones't intersect other edges + (locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b) && // locally visible + (area(a.prev, a, b.prev) || area(a, b.prev, b)) || // does not create opposite-facing sectors + equals(a, b) && area(a.prev, a, a.next) > 0 && area(b.prev, b, b.next) > 0); // special zero-length case +} + +// signed area of a triangle +function area(p, q, r) { + return (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y); +} + +// check if two points are equal +function equals(p1, p2) { + return p1.x === p2.x && p1.y === p2.y; +} + +// check if two segments intersect +function intersects(p1, q1, p2, q2) { + const o1 = sign(area(p1, q1, p2)); + const o2 = sign(area(p1, q1, q2)); + const o3 = sign(area(p2, q2, p1)); + const o4 = sign(area(p2, q2, q1)); + + if (o1 !== o2 && o3 !== o4) return true; // general case + + if (o1 === 0 && onSegment(p1, p2, q1)) return true; // p1, q1 and p2 are collinear and p2 lies on p1q1 + if (o2 === 0 && onSegment(p1, q2, q1)) return true; // p1, q1 and q2 are collinear and q2 lies on p1q1 + if (o3 === 0 && onSegment(p2, p1, q2)) return true; // p2, q2 and p1 are collinear and p1 lies on p2q2 + if (o4 === 0 && onSegment(p2, q1, q2)) return true; // p2, q2 and q1 are collinear and q1 lies on p2q2 + + return false; +} + +// for collinear points p, q, r, check if point q lies on segment pr +function onSegment(p, q, r) { + return q.x <= Math.max(p.x, r.x) && q.x >= Math.min(p.x, r.x) && q.y <= Math.max(p.y, r.y) && q.y >= Math.min(p.y, r.y); +} + +function sign(num) { + return num > 0 ? 1 : num < 0 ? -1 : 0; +} + +// check if a polygon diagonal intersects any polygon segments +function intersectsPolygon(a, b) { + let p = a; + do { + if (p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i && + intersects(p, p.next, a, b)) return true; + p = p.next; + } while (p !== a); + + return false; +} + +// check if a polygon diagonal is locally inside the polygon +function locallyInside(a, b) { + return area(a.prev, a, a.next) < 0 ? + area(a, b, a.next) >= 0 && area(a, a.prev, b) >= 0 : + area(a, b, a.prev) < 0 || area(a, a.next, b) < 0; +} + +// check if the middle point of a polygon diagonal is inside the polygon +function middleInside(a, b) { + let p = a; + let inside = false; + const px = (a.x + b.x) / 2; + const py = (a.y + b.y) / 2; + do { + if (((p.y > py) !== (p.next.y > py)) && p.next.y !== p.y && + (px < (p.next.x - p.x) * (py - p.y) / (p.next.y - p.y) + p.x)) + inside = !inside; + p = p.next; + } while (p !== a); + + return inside; +} + +// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two; +// if one belongs to the outer ring and another to a hole, it merges it into a single ring +function splitPolygon(a, b) { + const a2 = createNode(a.i, a.x, a.y), + b2 = createNode(b.i, b.x, b.y), + an = a.next, + bp = b.prev; + + a.next = b; + b.prev = a; + + a2.next = an; + an.prev = a2; + + b2.next = a2; + a2.prev = b2; + + bp.next = b2; + b2.prev = bp; + + return b2; +} + +// create a node and optionally link it with previous one (in a circular doubly linked list) +function insertNode(i, x, y, last) { + const p = createNode(i, x, y); + + if (!last) { + p.prev = p; + p.next = p; + + } else { + p.next = last.next; + p.prev = last; + last.next.prev = p; + last.next = p; + } + return p; +} + +function removeNode(p) { + p.next.prev = p.prev; + p.prev.next = p.next; + + if (p.prevZ) p.prevZ.nextZ = p.nextZ; + if (p.nextZ) p.nextZ.prevZ = p.prevZ; +} + +function createNode(i, x, y) { + return { + i, // vertex index in coordinates array + x, y, // vertex coordinates + prev: null, // previous and next vertex nodes in a polygon ring + next: null, + z: 0, // z-order curve value + prevZ: null, // previous and next nodes in z-order + nextZ: null, + steiner: false // indicates whether this is a steiner point + }; +} + +function signedArea(data, start, end, dim) { + let sum = 0; + for (let i = start, j = end - dim; i < end; i += dim) { + sum += (data[j] - data[i]) * (data[i + 1] + data[j + 1]); + j = i; + } + return sum; +} + +class Earcut { + + /** + * Triangulates the given shape definition by returning an array of triangles. + * + * @param {Array} data - An array with 2D points. + * @param {Array} holeIndices - An array with indices defining holes. + * @param {number} [dim=2] - The number of coordinates per vertex in the input array. + * @return {Array} An array representing the triangulated faces. Each face is defined by three consecutive numbers + * representing vertex indices. + */ + static triangulate( data, holeIndices, dim = 2 ) { + + return earcut( data, holeIndices, dim ); + + } + +} + +/** + * A class containing utility functions for shapes. + * + * @hideconstructor + */ +class ShapeUtils { + + /** + * Calculate area of a ( 2D ) contour polygon. + * + * @param {Array} contour - An array of 2D points. + * @return {number} The area. + */ + static area( contour ) { + + const n = contour.length; + let a = 0.0; + + for ( let p = n - 1, q = 0; q < n; p = q ++ ) { + + a += contour[ p ].x * contour[ q ].y - contour[ q ].x * contour[ p ].y; + + } + + return a * 0.5; + + } + + /** + * Returns `true` if the given contour uses a clockwise winding order. + * + * @param {Array} pts - An array of 2D points defining a polygon. + * @return {boolean} Whether the given contour uses a clockwise winding order or not. + */ + static isClockWise( pts ) { + + return ShapeUtils.area( pts ) < 0; + + } + + /** + * Triangulates the given shape definition. + * + * @param {Array} contour - An array of 2D points defining the contour. + * @param {Array>} holes - An array that holds arrays of 2D points defining the holes. + * @return {Array>} An array that holds for each face definition an array with three indices. + */ + static triangulateShape( contour, holes ) { + + const vertices = []; // flat array of vertices like [ x0,y0, x1,y1, x2,y2, ... ] + const holeIndices = []; // array of hole indices + const faces = []; // final array of vertex indices like [ [ a,b,d ], [ b,c,d ] ] + + removeDupEndPts( contour ); + addContour( vertices, contour ); + + // + + let holeIndex = contour.length; + + holes.forEach( removeDupEndPts ); + + for ( let i = 0; i < holes.length; i ++ ) { + + holeIndices.push( holeIndex ); + holeIndex += holes[ i ].length; + addContour( vertices, holes[ i ] ); + + } + + // + + const triangles = Earcut.triangulate( vertices, holeIndices ); + + // + + for ( let i = 0; i < triangles.length; i += 3 ) { + + faces.push( triangles.slice( i, i + 3 ) ); + + } + + return faces; + + } + +} + +function removeDupEndPts( points ) { + + const l = points.length; + + if ( l > 2 && points[ l - 1 ].equals( points[ 0 ] ) ) { + + points.pop(); + + } + +} + +function addContour( vertices, contour ) { + + for ( let i = 0; i < contour.length; i ++ ) { + + vertices.push( contour[ i ].x ); + vertices.push( contour[ i ].y ); + + } + +} + +/** + * Creates extruded geometry from a path shape. + * + * ```js + * const length = 12, width = 8; + * + * const shape = new THREE.Shape(); + * shape.moveTo( 0,0 ); + * shape.lineTo( 0, width ); + * shape.lineTo( length, width ); + * shape.lineTo( length, 0 ); + * shape.lineTo( 0, 0 ); + * + * const geometry = new THREE.ExtrudeGeometry( shape ); + * const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); + * const mesh = new THREE.Mesh( geometry, material ) ; + * scene.add( mesh ); + * ``` + * + * @augments BufferGeometry + */ +class ExtrudeGeometry extends BufferGeometry { + + /** + * Constructs a new extrude geometry. + * + * @param {Shape|Array} [shapes] - A shape or an array of shapes. + * @param {ExtrudeGeometry~Options} [options] - The extrude settings. + */ + constructor( shapes = new Shape( [ new Vector2( 0.5, 0.5 ), new Vector2( -0.5, 0.5 ), new Vector2( -0.5, -0.5 ), new Vector2( 0.5, -0.5 ) ] ), options = {} ) { + + super(); + + this.type = 'ExtrudeGeometry'; + + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + shapes: shapes, + options: options + }; + + shapes = Array.isArray( shapes ) ? shapes : [ shapes ]; + + const scope = this; + + const verticesArray = []; + const uvArray = []; + + for ( let i = 0, l = shapes.length; i < l; i ++ ) { + + const shape = shapes[ i ]; + addShape( shape ); + + } + + // build geometry + + this.setAttribute( 'position', new Float32BufferAttribute( verticesArray, 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvArray, 2 ) ); + + this.computeVertexNormals(); + + // functions + + function addShape( shape ) { + + const placeholder = []; + + // options + + const curveSegments = options.curveSegments !== undefined ? options.curveSegments : 12; + const steps = options.steps !== undefined ? options.steps : 1; + const depth = options.depth !== undefined ? options.depth : 1; + + let bevelEnabled = options.bevelEnabled !== undefined ? options.bevelEnabled : true; + let bevelThickness = options.bevelThickness !== undefined ? options.bevelThickness : 0.2; + let bevelSize = options.bevelSize !== undefined ? options.bevelSize : bevelThickness - 0.1; + let bevelOffset = options.bevelOffset !== undefined ? options.bevelOffset : 0; + let bevelSegments = options.bevelSegments !== undefined ? options.bevelSegments : 3; + + const extrudePath = options.extrudePath; + + const uvgen = options.UVGenerator !== undefined ? options.UVGenerator : WorldUVGenerator; + + // + + let extrudePts, extrudeByPath = false; + let splineTube, binormal, normal, position2; + + if ( extrudePath ) { + + extrudePts = extrudePath.getSpacedPoints( steps ); + + extrudeByPath = true; + bevelEnabled = false; // bevels not supported for path extrusion + + // SETUP TNB variables + + // TODO1 - have a .isClosed in spline? + + splineTube = extrudePath.computeFrenetFrames( steps, false ); + + // console.log(splineTube, 'splineTube', splineTube.normals.length, 'steps', steps, 'extrudePts', extrudePts.length); + + binormal = new Vector3(); + normal = new Vector3(); + position2 = new Vector3(); + + } + + // Safeguards if bevels are not enabled + + if ( ! bevelEnabled ) { + + bevelSegments = 0; + bevelThickness = 0; + bevelSize = 0; + bevelOffset = 0; + + } + + // Variables initialization + + const shapePoints = shape.extractPoints( curveSegments ); + + let vertices = shapePoints.shape; + const holes = shapePoints.holes; + + const reverse = ! ShapeUtils.isClockWise( vertices ); + + if ( reverse ) { + + vertices = vertices.reverse(); + + // Maybe we should also check if holes are in the opposite direction, just to be safe ... + + for ( let h = 0, hl = holes.length; h < hl; h ++ ) { + + const ahole = holes[ h ]; + + if ( ShapeUtils.isClockWise( ahole ) ) { + + holes[ h ] = ahole.reverse(); + + } + + } + + } + + /**Merges index-adjacent points that are within a threshold distance of each other. Array is modified in-place. Threshold distance is empirical, and scaled based on the magnitude of point coordinates. + * @param {Array} points + */ + function mergeOverlappingPoints( points ) { + + const THRESHOLD = 1e-10; + const THRESHOLD_SQ = THRESHOLD * THRESHOLD; + let prevPos = points[ 0 ]; + for ( let i = 1; i <= points.length; i ++ ) { + + const currentIndex = i % points.length; + const currentPos = points[ currentIndex ]; + const dx = currentPos.x - prevPos.x; + const dy = currentPos.y - prevPos.y; + const distSq = dx * dx + dy * dy; + + const scalingFactorSqrt = Math.max( + Math.abs( currentPos.x ), + Math.abs( currentPos.y ), + Math.abs( prevPos.x ), + Math.abs( prevPos.y ) + ); + const thresholdSqScaled = THRESHOLD_SQ * scalingFactorSqrt * scalingFactorSqrt; + if ( distSq <= thresholdSqScaled ) { + + points.splice( currentIndex, 1 ); + i --; + continue; + + } + + prevPos = currentPos; + + } + + } + + mergeOverlappingPoints( vertices ); + holes.forEach( mergeOverlappingPoints ); + + const numHoles = holes.length; + + /* Vertices */ + + const contour = vertices; // vertices has all points but contour has only points of circumference + + for ( let h = 0; h < numHoles; h ++ ) { + + const ahole = holes[ h ]; + + vertices = vertices.concat( ahole ); + + } + + + function scalePt2( pt, vec, size ) { + + if ( ! vec ) console.error( 'THREE.ExtrudeGeometry: vec does not exist' ); + + return pt.clone().addScaledVector( vec, size ); + + } + + const vlen = vertices.length; + + + // Find directions for point movement + + + function getBevelVec( inPt, inPrev, inNext ) { + + // computes for inPt the corresponding point inPt' on a new contour + // shifted by 1 unit (length of normalized vector) to the left + // if we walk along contour clockwise, this new contour is outside the old one + // + // inPt' is the intersection of the two lines parallel to the two + // adjacent edges of inPt at a distance of 1 unit on the left side. + + let v_trans_x, v_trans_y, shrink_by; // resulting translation vector for inPt + + // good reading for geometry algorithms (here: line-line intersection) + // http://geomalgorithms.com/a05-_intersect-1.html + + const v_prev_x = inPt.x - inPrev.x, + v_prev_y = inPt.y - inPrev.y; + const v_next_x = inNext.x - inPt.x, + v_next_y = inNext.y - inPt.y; + + const v_prev_lensq = ( v_prev_x * v_prev_x + v_prev_y * v_prev_y ); + + // check for collinear edges + const collinear0 = ( v_prev_x * v_next_y - v_prev_y * v_next_x ); + + if ( Math.abs( collinear0 ) > Number.EPSILON ) { + + // not collinear + + // length of vectors for normalizing + + const v_prev_len = Math.sqrt( v_prev_lensq ); + const v_next_len = Math.sqrt( v_next_x * v_next_x + v_next_y * v_next_y ); + + // shift adjacent points by unit vectors to the left + + const ptPrevShift_x = ( inPrev.x - v_prev_y / v_prev_len ); + const ptPrevShift_y = ( inPrev.y + v_prev_x / v_prev_len ); + + const ptNextShift_x = ( inNext.x - v_next_y / v_next_len ); + const ptNextShift_y = ( inNext.y + v_next_x / v_next_len ); + + // scaling factor for v_prev to intersection point + + const sf = ( ( ptNextShift_x - ptPrevShift_x ) * v_next_y - + ( ptNextShift_y - ptPrevShift_y ) * v_next_x ) / + ( v_prev_x * v_next_y - v_prev_y * v_next_x ); + + // vector from inPt to intersection point + + v_trans_x = ( ptPrevShift_x + v_prev_x * sf - inPt.x ); + v_trans_y = ( ptPrevShift_y + v_prev_y * sf - inPt.y ); + + // Don't normalize!, otherwise sharp corners become ugly + // but prevent crazy spikes + const v_trans_lensq = ( v_trans_x * v_trans_x + v_trans_y * v_trans_y ); + if ( v_trans_lensq <= 2 ) { + + return new Vector2( v_trans_x, v_trans_y ); + + } else { + + shrink_by = Math.sqrt( v_trans_lensq / 2 ); + + } + + } else { + + // handle special case of collinear edges + + let direction_eq = false; // assumes: opposite + + if ( v_prev_x > Number.EPSILON ) { + + if ( v_next_x > Number.EPSILON ) { + + direction_eq = true; + + } + + } else { + + if ( v_prev_x < - Number.EPSILON ) { + + if ( v_next_x < - Number.EPSILON ) { + + direction_eq = true; + + } + + } else { + + if ( Math.sign( v_prev_y ) === Math.sign( v_next_y ) ) { + + direction_eq = true; + + } + + } + + } + + if ( direction_eq ) { + + // console.log("Warning: lines are a straight sequence"); + v_trans_x = - v_prev_y; + v_trans_y = v_prev_x; + shrink_by = Math.sqrt( v_prev_lensq ); + + } else { + + // console.log("Warning: lines are a straight spike"); + v_trans_x = v_prev_x; + v_trans_y = v_prev_y; + shrink_by = Math.sqrt( v_prev_lensq / 2 ); + + } + + } + + return new Vector2( v_trans_x / shrink_by, v_trans_y / shrink_by ); + + } + + + const contourMovements = []; + + for ( let i = 0, il = contour.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) { + + if ( j === il ) j = 0; + if ( k === il ) k = 0; + + // (j)---(i)---(k) + // console.log('i,j,k', i, j , k) + + contourMovements[ i ] = getBevelVec( contour[ i ], contour[ j ], contour[ k ] ); + + } + + const holesMovements = []; + let oneHoleMovements, verticesMovements = contourMovements.concat(); + + for ( let h = 0, hl = numHoles; h < hl; h ++ ) { + + const ahole = holes[ h ]; + + oneHoleMovements = []; + + for ( let i = 0, il = ahole.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) { + + if ( j === il ) j = 0; + if ( k === il ) k = 0; + + // (j)---(i)---(k) + oneHoleMovements[ i ] = getBevelVec( ahole[ i ], ahole[ j ], ahole[ k ] ); + + } + + holesMovements.push( oneHoleMovements ); + verticesMovements = verticesMovements.concat( oneHoleMovements ); + + } + + let faces; + + if ( bevelSegments === 0 ) { + + faces = ShapeUtils.triangulateShape( contour, holes ); + + } else { + + const contractedContourVertices = []; + const expandedHoleVertices = []; + + // Loop bevelSegments, 1 for the front, 1 for the back + + for ( let b = 0; b < bevelSegments; b ++ ) { + + //for ( b = bevelSegments; b > 0; b -- ) { + + const t = b / bevelSegments; + const z = bevelThickness * Math.cos( t * Math.PI / 2 ); + const bs = bevelSize * Math.sin( t * Math.PI / 2 ) + bevelOffset; + + // contract shape + + for ( let i = 0, il = contour.length; i < il; i ++ ) { + + const vert = scalePt2( contour[ i ], contourMovements[ i ], bs ); + + v( vert.x, vert.y, - z ); + if ( t === 0 ) contractedContourVertices.push( vert ); + + } + + // expand holes + + for ( let h = 0, hl = numHoles; h < hl; h ++ ) { + + const ahole = holes[ h ]; + oneHoleMovements = holesMovements[ h ]; + const oneHoleVertices = []; + for ( let i = 0, il = ahole.length; i < il; i ++ ) { + + const vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs ); + + v( vert.x, vert.y, - z ); + if ( t === 0 ) oneHoleVertices.push( vert ); + + } + + if ( t === 0 ) expandedHoleVertices.push( oneHoleVertices ); + + } + + } + + faces = ShapeUtils.triangulateShape( contractedContourVertices, expandedHoleVertices ); + + } + + const flen = faces.length; + + const bs = bevelSize + bevelOffset; + + // Back facing vertices + + for ( let i = 0; i < vlen; i ++ ) { + + const vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ]; + + if ( ! extrudeByPath ) { + + v( vert.x, vert.y, 0 ); + + } else { + + // v( vert.x, vert.y + extrudePts[ 0 ].y, extrudePts[ 0 ].x ); + + normal.copy( splineTube.normals[ 0 ] ).multiplyScalar( vert.x ); + binormal.copy( splineTube.binormals[ 0 ] ).multiplyScalar( vert.y ); + + position2.copy( extrudePts[ 0 ] ).add( normal ).add( binormal ); + + v( position2.x, position2.y, position2.z ); + + } + + } + + // Add stepped vertices... + // Including front facing vertices + + for ( let s = 1; s <= steps; s ++ ) { + + for ( let i = 0; i < vlen; i ++ ) { + + const vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ]; + + if ( ! extrudeByPath ) { + + v( vert.x, vert.y, depth / steps * s ); + + } else { + + // v( vert.x, vert.y + extrudePts[ s - 1 ].y, extrudePts[ s - 1 ].x ); + + normal.copy( splineTube.normals[ s ] ).multiplyScalar( vert.x ); + binormal.copy( splineTube.binormals[ s ] ).multiplyScalar( vert.y ); + + position2.copy( extrudePts[ s ] ).add( normal ).add( binormal ); + + v( position2.x, position2.y, position2.z ); + + } + + } + + } + + + // Add bevel segments planes + + //for ( b = 1; b <= bevelSegments; b ++ ) { + for ( let b = bevelSegments - 1; b >= 0; b -- ) { + + const t = b / bevelSegments; + const z = bevelThickness * Math.cos( t * Math.PI / 2 ); + const bs = bevelSize * Math.sin( t * Math.PI / 2 ) + bevelOffset; + + // contract shape + + for ( let i = 0, il = contour.length; i < il; i ++ ) { + + const vert = scalePt2( contour[ i ], contourMovements[ i ], bs ); + v( vert.x, vert.y, depth + z ); + + } + + // expand holes + + for ( let h = 0, hl = holes.length; h < hl; h ++ ) { + + const ahole = holes[ h ]; + oneHoleMovements = holesMovements[ h ]; + + for ( let i = 0, il = ahole.length; i < il; i ++ ) { + + const vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs ); + + if ( ! extrudeByPath ) { + + v( vert.x, vert.y, depth + z ); + + } else { + + v( vert.x, vert.y + extrudePts[ steps - 1 ].y, extrudePts[ steps - 1 ].x + z ); + + } + + } + + } + + } + + /* Faces */ + + // Top and bottom faces + + buildLidFaces(); + + // Sides faces + + buildSideFaces(); + + + ///// Internal functions + + function buildLidFaces() { + + const start = verticesArray.length / 3; + + if ( bevelEnabled ) { + + let layer = 0; // steps + 1 + let offset = vlen * layer; + + // Bottom faces + + for ( let i = 0; i < flen; i ++ ) { + + const face = faces[ i ]; + f3( face[ 2 ] + offset, face[ 1 ] + offset, face[ 0 ] + offset ); + + } + + layer = steps + bevelSegments * 2; + offset = vlen * layer; + + // Top faces + + for ( let i = 0; i < flen; i ++ ) { + + const face = faces[ i ]; + f3( face[ 0 ] + offset, face[ 1 ] + offset, face[ 2 ] + offset ); + + } + + } else { + + // Bottom faces + + for ( let i = 0; i < flen; i ++ ) { + + const face = faces[ i ]; + f3( face[ 2 ], face[ 1 ], face[ 0 ] ); + + } + + // Top faces + + for ( let i = 0; i < flen; i ++ ) { + + const face = faces[ i ]; + f3( face[ 0 ] + vlen * steps, face[ 1 ] + vlen * steps, face[ 2 ] + vlen * steps ); + + } + + } + + scope.addGroup( start, verticesArray.length / 3 - start, 0 ); + + } + + // Create faces for the z-sides of the shape + + function buildSideFaces() { + + const start = verticesArray.length / 3; + let layeroffset = 0; + sidewalls( contour, layeroffset ); + layeroffset += contour.length; + + for ( let h = 0, hl = holes.length; h < hl; h ++ ) { + + const ahole = holes[ h ]; + sidewalls( ahole, layeroffset ); + + //, true + layeroffset += ahole.length; + + } + + + scope.addGroup( start, verticesArray.length / 3 - start, 1 ); + + + } + + function sidewalls( contour, layeroffset ) { + + let i = contour.length; + + while ( -- i >= 0 ) { + + const j = i; + let k = i - 1; + if ( k < 0 ) k = contour.length - 1; + + //console.log('b', i,j, i-1, k,vertices.length); + + for ( let s = 0, sl = ( steps + bevelSegments * 2 ); s < sl; s ++ ) { + + const slen1 = vlen * s; + const slen2 = vlen * ( s + 1 ); + + const a = layeroffset + j + slen1, + b = layeroffset + k + slen1, + c = layeroffset + k + slen2, + d = layeroffset + j + slen2; + + f4( a, b, c, d ); + + } + + } + + } + + function v( x, y, z ) { + + placeholder.push( x ); + placeholder.push( y ); + placeholder.push( z ); + + } + + + function f3( a, b, c ) { + + addVertex( a ); + addVertex( b ); + addVertex( c ); + + const nextIndex = verticesArray.length / 3; + const uvs = uvgen.generateTopUV( scope, verticesArray, nextIndex - 3, nextIndex - 2, nextIndex - 1 ); + + addUV( uvs[ 0 ] ); + addUV( uvs[ 1 ] ); + addUV( uvs[ 2 ] ); + + } + + function f4( a, b, c, d ) { + + addVertex( a ); + addVertex( b ); + addVertex( d ); + + addVertex( b ); + addVertex( c ); + addVertex( d ); + + + const nextIndex = verticesArray.length / 3; + const uvs = uvgen.generateSideWallUV( scope, verticesArray, nextIndex - 6, nextIndex - 3, nextIndex - 2, nextIndex - 1 ); + + addUV( uvs[ 0 ] ); + addUV( uvs[ 1 ] ); + addUV( uvs[ 3 ] ); + + addUV( uvs[ 1 ] ); + addUV( uvs[ 2 ] ); + addUV( uvs[ 3 ] ); + + } + + function addVertex( index ) { + + verticesArray.push( placeholder[ index * 3 + 0 ] ); + verticesArray.push( placeholder[ index * 3 + 1 ] ); + verticesArray.push( placeholder[ index * 3 + 2 ] ); + + } + + + function addUV( vector2 ) { + + uvArray.push( vector2.x ); + uvArray.push( vector2.y ); + + } + + } + + } + + copy( source ) { + + super.copy( source ); + + this.parameters = Object.assign( {}, source.parameters ); + + return this; + + } + + toJSON() { + + const data = super.toJSON(); + + const shapes = this.parameters.shapes; + const options = this.parameters.options; + + return toJSON$1( shapes, options, data ); + + } + + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @param {Array} shapes - An array of shapes. + * @return {ExtrudeGeometry} A new instance. + */ + static fromJSON( data, shapes ) { + + const geometryShapes = []; + + for ( let j = 0, jl = data.shapes.length; j < jl; j ++ ) { + + const shape = shapes[ data.shapes[ j ] ]; + + geometryShapes.push( shape ); + + } + + const extrudePath = data.options.extrudePath; + + if ( extrudePath !== undefined ) { + + data.options.extrudePath = new Curves[ extrudePath.type ]().fromJSON( extrudePath ); + + } + + return new ExtrudeGeometry( geometryShapes, data.options ); + + } + +} + +const WorldUVGenerator = { + + generateTopUV: function ( geometry, vertices, indexA, indexB, indexC ) { + + const a_x = vertices[ indexA * 3 ]; + const a_y = vertices[ indexA * 3 + 1 ]; + const b_x = vertices[ indexB * 3 ]; + const b_y = vertices[ indexB * 3 + 1 ]; + const c_x = vertices[ indexC * 3 ]; + const c_y = vertices[ indexC * 3 + 1 ]; + + return [ + new Vector2( a_x, a_y ), + new Vector2( b_x, b_y ), + new Vector2( c_x, c_y ) + ]; + + }, + + generateSideWallUV: function ( geometry, vertices, indexA, indexB, indexC, indexD ) { + + const a_x = vertices[ indexA * 3 ]; + const a_y = vertices[ indexA * 3 + 1 ]; + const a_z = vertices[ indexA * 3 + 2 ]; + const b_x = vertices[ indexB * 3 ]; + const b_y = vertices[ indexB * 3 + 1 ]; + const b_z = vertices[ indexB * 3 + 2 ]; + const c_x = vertices[ indexC * 3 ]; + const c_y = vertices[ indexC * 3 + 1 ]; + const c_z = vertices[ indexC * 3 + 2 ]; + const d_x = vertices[ indexD * 3 ]; + const d_y = vertices[ indexD * 3 + 1 ]; + const d_z = vertices[ indexD * 3 + 2 ]; + + if ( Math.abs( a_y - b_y ) < Math.abs( a_x - b_x ) ) { + + return [ + new Vector2( a_x, 1 - a_z ), + new Vector2( b_x, 1 - b_z ), + new Vector2( c_x, 1 - c_z ), + new Vector2( d_x, 1 - d_z ) + ]; + + } else { + + return [ + new Vector2( a_y, 1 - a_z ), + new Vector2( b_y, 1 - b_z ), + new Vector2( c_y, 1 - c_z ), + new Vector2( d_y, 1 - d_z ) + ]; + + } + + } + +}; + +function toJSON$1( shapes, options, data ) { + + data.shapes = []; + + if ( Array.isArray( shapes ) ) { + + for ( let i = 0, l = shapes.length; i < l; i ++ ) { + + const shape = shapes[ i ]; + + data.shapes.push( shape.uuid ); + + } + + } else { + + data.shapes.push( shapes.uuid ); + + } + + data.options = Object.assign( {}, options ); + + if ( options.extrudePath !== undefined ) data.options.extrudePath = options.extrudePath.toJSON(); + + return data; + +} + +/** + * A geometry class for representing an icosahedron. + * + * ```js + * const geometry = new THREE.IcosahedronGeometry(); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + * const icosahedron = new THREE.Mesh( geometry, material ); + * scene.add( icosahedron ); + * ``` + * + * @augments PolyhedronGeometry + */ +class IcosahedronGeometry extends PolyhedronGeometry { + + /** + * Constructs a new icosahedron geometry. + * + * @param {number} [radius=1] - Radius of the icosahedron. + * @param {number} [detail=0] - Setting this to a value greater than `0` adds vertices making it no longer a icosahedron. + */ + constructor( radius = 1, detail = 0 ) { + + const t = ( 1 + Math.sqrt( 5 ) ) / 2; + + const vertices = [ + -1, t, 0, 1, t, 0, -1, - t, 0, 1, - t, 0, + 0, -1, t, 0, 1, t, 0, -1, - t, 0, 1, - t, + t, 0, -1, t, 0, 1, - t, 0, -1, - t, 0, 1 + ]; + + const indices = [ + 0, 11, 5, 0, 5, 1, 0, 1, 7, 0, 7, 10, 0, 10, 11, + 1, 5, 9, 5, 11, 4, 11, 10, 2, 10, 7, 6, 7, 1, 8, + 3, 9, 4, 3, 4, 2, 3, 2, 6, 3, 6, 8, 3, 8, 9, + 4, 9, 5, 2, 4, 11, 6, 2, 10, 8, 6, 7, 9, 8, 1 + ]; + + super( vertices, indices, radius, detail ); + + this.type = 'IcosahedronGeometry'; + + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + radius: radius, + detail: detail + }; + + } + + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {IcosahedronGeometry} A new instance. + */ + static fromJSON( data ) { + + return new IcosahedronGeometry( data.radius, data.detail ); + + } + +} + +/** + * Creates meshes with axial symmetry like vases. The lathe rotates around the Y axis. + * + * ```js + * const points = []; + * for ( let i = 0; i < 10; i ++ ) { + * points.push( new THREE.Vector2( Math.sin( i * 0.2 ) * 10 + 5, ( i - 5 ) * 2 ) ); + * } + * const geometry = new THREE.LatheGeometry( points ); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + * const lathe = new THREE.Mesh( geometry, material ); + * scene.add( lathe ); + * ``` + * + * @augments BufferGeometry + */ +class LatheGeometry extends BufferGeometry { + + /** + * Constructs a new lathe geometry. + * + * @param {Array} [points] - An array of points in 2D space. The x-coordinate of each point + * must be greater than zero. + * @param {number} [segments=12] - The number of circumference segments to generate. + * @param {number} [phiStart=0] - The starting angle in radians. + * @param {number} [phiLength=Math.PI*2] - The radian (0 to 2PI) range of the lathed section 2PI is a + * closed lathe, less than 2PI is a portion. + */ + constructor( points = [ new Vector2( 0, -0.5 ), new Vector2( 0.5, 0 ), new Vector2( 0, 0.5 ) ], segments = 12, phiStart = 0, phiLength = Math.PI * 2 ) { + + super(); + + this.type = 'LatheGeometry'; + + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + points: points, + segments: segments, + phiStart: phiStart, + phiLength: phiLength + }; + + segments = Math.floor( segments ); + + // clamp phiLength so it's in range of [ 0, 2PI ] + + phiLength = clamp( phiLength, 0, Math.PI * 2 ); + + // buffers + + const indices = []; + const vertices = []; + const uvs = []; + const initNormals = []; + const normals = []; + + // helper variables + + const inverseSegments = 1.0 / segments; + const vertex = new Vector3(); + const uv = new Vector2(); + const normal = new Vector3(); + const curNormal = new Vector3(); + const prevNormal = new Vector3(); + let dx = 0; + let dy = 0; + + // pre-compute normals for initial "meridian" + + for ( let j = 0; j <= ( points.length - 1 ); j ++ ) { + + switch ( j ) { + + case 0: // special handling for 1st vertex on path + + dx = points[ j + 1 ].x - points[ j ].x; + dy = points[ j + 1 ].y - points[ j ].y; + + normal.x = dy * 1.0; + normal.y = - dx; + normal.z = dy * 0.0; + + prevNormal.copy( normal ); + + normal.normalize(); + + initNormals.push( normal.x, normal.y, normal.z ); + + break; + + case ( points.length - 1 ): // special handling for last Vertex on path + + initNormals.push( prevNormal.x, prevNormal.y, prevNormal.z ); + + break; + + default: // default handling for all vertices in between + + dx = points[ j + 1 ].x - points[ j ].x; + dy = points[ j + 1 ].y - points[ j ].y; + + normal.x = dy * 1.0; + normal.y = - dx; + normal.z = dy * 0.0; + + curNormal.copy( normal ); + + normal.x += prevNormal.x; + normal.y += prevNormal.y; + normal.z += prevNormal.z; + + normal.normalize(); + + initNormals.push( normal.x, normal.y, normal.z ); + + prevNormal.copy( curNormal ); + + } + + } + + // generate vertices, uvs and normals + + for ( let i = 0; i <= segments; i ++ ) { + + const phi = phiStart + i * inverseSegments * phiLength; + + const sin = Math.sin( phi ); + const cos = Math.cos( phi ); + + for ( let j = 0; j <= ( points.length - 1 ); j ++ ) { + + // vertex + + vertex.x = points[ j ].x * sin; + vertex.y = points[ j ].y; + vertex.z = points[ j ].x * cos; + + vertices.push( vertex.x, vertex.y, vertex.z ); + + // uv + + uv.x = i / segments; + uv.y = j / ( points.length - 1 ); + + uvs.push( uv.x, uv.y ); + + // normal + + const x = initNormals[ 3 * j + 0 ] * sin; + const y = initNormals[ 3 * j + 1 ]; + const z = initNormals[ 3 * j + 0 ] * cos; + + normals.push( x, y, z ); + + } + + } + + // indices + + for ( let i = 0; i < segments; i ++ ) { + + for ( let j = 0; j < ( points.length - 1 ); j ++ ) { + + const base = j + i * points.length; + + const a = base; + const b = base + points.length; + const c = base + points.length + 1; + const d = base + 1; + + // faces + + indices.push( a, b, d ); + indices.push( c, d, b ); + + } + + } + + // build geometry + + this.setIndex( indices ); + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + + } + + copy( source ) { + + super.copy( source ); + + this.parameters = Object.assign( {}, source.parameters ); + + return this; + + } + + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {LatheGeometry} A new instance. + */ + static fromJSON( data ) { + + return new LatheGeometry( data.points, data.segments, data.phiStart, data.phiLength ); + + } + +} + +/** + * A geometry class for representing an octahedron. + * + * ```js + * const geometry = new THREE.OctahedronGeometry(); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + * const octahedron = new THREE.Mesh( geometry, material ); + * scene.add( octahedron ); + * ``` + * + * @augments PolyhedronGeometry + */ +class OctahedronGeometry extends PolyhedronGeometry { + + /** + * Constructs a new octahedron geometry. + * + * @param {number} [radius=1] - Radius of the octahedron. + * @param {number} [detail=0] - Setting this to a value greater than `0` adds vertices making it no longer a octahedron. + */ + constructor( radius = 1, detail = 0 ) { + + const vertices = [ + 1, 0, 0, -1, 0, 0, 0, 1, 0, + 0, -1, 0, 0, 0, 1, 0, 0, -1 + ]; + + const indices = [ + 0, 2, 4, 0, 4, 3, 0, 3, 5, + 0, 5, 2, 1, 2, 5, 1, 5, 3, + 1, 3, 4, 1, 4, 2 + ]; + + super( vertices, indices, radius, detail ); + + this.type = 'OctahedronGeometry'; + + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + radius: radius, + detail: detail + }; + + } + + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {OctahedronGeometry} A new instance. + */ + static fromJSON( data ) { + + return new OctahedronGeometry( data.radius, data.detail ); + + } + +} + +/** + * A geometry class for representing a plane. + * + * ```js + * const geometry = new THREE.PlaneGeometry( 1, 1 ); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00, side: THREE.DoubleSide } ); + * const plane = new THREE.Mesh( geometry, material ); + * scene.add( plane ); + * ``` + * + * @augments BufferGeometry + */ +class PlaneGeometry extends BufferGeometry { + + /** + * Constructs a new plane geometry. + * + * @param {number} [width=1] - The width along the X axis. + * @param {number} [height=1] - The height along the Y axis + * @param {number} [widthSegments=1] - The number of segments along the X axis. + * @param {number} [heightSegments=1] - The number of segments along the Y axis. + */ + constructor( width = 1, height = 1, widthSegments = 1, heightSegments = 1 ) { + + super(); + + this.type = 'PlaneGeometry'; + + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + width: width, + height: height, + widthSegments: widthSegments, + heightSegments: heightSegments + }; + + const width_half = width / 2; + const height_half = height / 2; + + const gridX = Math.floor( widthSegments ); + const gridY = Math.floor( heightSegments ); + + const gridX1 = gridX + 1; + const gridY1 = gridY + 1; + + const segment_width = width / gridX; + const segment_height = height / gridY; + + // + + const indices = []; + const vertices = []; + const normals = []; + const uvs = []; + + for ( let iy = 0; iy < gridY1; iy ++ ) { + + const y = iy * segment_height - height_half; + + for ( let ix = 0; ix < gridX1; ix ++ ) { + + const x = ix * segment_width - width_half; + + vertices.push( x, - y, 0 ); + + normals.push( 0, 0, 1 ); + + uvs.push( ix / gridX ); + uvs.push( 1 - ( iy / gridY ) ); + + } + + } + + for ( let iy = 0; iy < gridY; iy ++ ) { + + for ( let ix = 0; ix < gridX; ix ++ ) { + + const a = ix + gridX1 * iy; + const b = ix + gridX1 * ( iy + 1 ); + const c = ( ix + 1 ) + gridX1 * ( iy + 1 ); + const d = ( ix + 1 ) + gridX1 * iy; + + indices.push( a, b, d ); + indices.push( b, c, d ); + + } + + } + + this.setIndex( indices ); + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + + } + + copy( source ) { + + super.copy( source ); + + this.parameters = Object.assign( {}, source.parameters ); + + return this; + + } + + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {PlaneGeometry} A new instance. + */ + static fromJSON( data ) { + + return new PlaneGeometry( data.width, data.height, data.widthSegments, data.heightSegments ); + + } + +} + +/** + * A class for generating a two-dimensional ring geometry. + * + * ```js + * const geometry = new THREE.RingGeometry( 1, 5, 32 ); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00, side: THREE.DoubleSide } ); + * const mesh = new THREE.Mesh( geometry, material ); + * scene.add( mesh ); + * ``` + * + * @augments BufferGeometry + */ +class RingGeometry extends BufferGeometry { + + /** + * Constructs a new ring geometry. + * + * @param {number} [innerRadius=0.5] - The inner radius of the ring. + * @param {number} [outerRadius=1] - The outer radius of the ring. + * @param {number} [thetaSegments=32] - Number of segments. A higher number means the ring will be more round. Minimum is `3`. + * @param {number} [phiSegments=1] - Number of segments per ring segment. Minimum is `1`. + * @param {number} [thetaStart=0] - Starting angle in radians. + * @param {number} [thetaLength=Math.PI*2] - Central angle in radians. + */ + constructor( innerRadius = 0.5, outerRadius = 1, thetaSegments = 32, phiSegments = 1, thetaStart = 0, thetaLength = Math.PI * 2 ) { + + super(); + + this.type = 'RingGeometry'; + + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + innerRadius: innerRadius, + outerRadius: outerRadius, + thetaSegments: thetaSegments, + phiSegments: phiSegments, + thetaStart: thetaStart, + thetaLength: thetaLength + }; + + thetaSegments = Math.max( 3, thetaSegments ); + phiSegments = Math.max( 1, phiSegments ); + + // buffers + + const indices = []; + const vertices = []; + const normals = []; + const uvs = []; + + // some helper variables + + let radius = innerRadius; + const radiusStep = ( ( outerRadius - innerRadius ) / phiSegments ); + const vertex = new Vector3(); + const uv = new Vector2(); + + // generate vertices, normals and uvs + + for ( let j = 0; j <= phiSegments; j ++ ) { + + for ( let i = 0; i <= thetaSegments; i ++ ) { + + // values are generate from the inside of the ring to the outside + + const segment = thetaStart + i / thetaSegments * thetaLength; + + // vertex + + vertex.x = radius * Math.cos( segment ); + vertex.y = radius * Math.sin( segment ); + + vertices.push( vertex.x, vertex.y, vertex.z ); + + // normal + + normals.push( 0, 0, 1 ); + + // uv + + uv.x = ( vertex.x / outerRadius + 1 ) / 2; + uv.y = ( vertex.y / outerRadius + 1 ) / 2; + + uvs.push( uv.x, uv.y ); + + } + + // increase the radius for next row of vertices + + radius += radiusStep; + + } + + // indices + + for ( let j = 0; j < phiSegments; j ++ ) { + + const thetaSegmentLevel = j * ( thetaSegments + 1 ); + + for ( let i = 0; i < thetaSegments; i ++ ) { + + const segment = i + thetaSegmentLevel; + + const a = segment; + const b = segment + thetaSegments + 1; + const c = segment + thetaSegments + 2; + const d = segment + 1; + + // faces + + indices.push( a, b, d ); + indices.push( b, c, d ); + + } + + } + + // build geometry + + this.setIndex( indices ); + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + + } + + copy( source ) { + + super.copy( source ); + + this.parameters = Object.assign( {}, source.parameters ); + + return this; + + } + + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {RingGeometry} A new instance. + */ + static fromJSON( data ) { + + return new RingGeometry( data.innerRadius, data.outerRadius, data.thetaSegments, data.phiSegments, data.thetaStart, data.thetaLength ); + + } + +} + +/** + * Creates an one-sided polygonal geometry from one or more path shapes. + * + * ```js + * const arcShape = new THREE.Shape() + * .moveTo( 5, 1 ) + * .absarc( 1, 1, 4, 0, Math.PI * 2, false ); + * + * const geometry = new THREE.ShapeGeometry( arcShape ); + * const material = new THREE.MeshBasicMaterial( { color: 0x00ff00, side: THREE.DoubleSide } ); + * const mesh = new THREE.Mesh( geometry, material ) ; + * scene.add( mesh ); + * ``` + * + * @augments BufferGeometry + */ +class ShapeGeometry extends BufferGeometry { + + /** + * Constructs a new shape geometry. + * + * @param {Shape|Array} [shapes] - A shape or an array of shapes. + * @param {number} [curveSegments=12] - Number of segments per shape. + */ + constructor( shapes = new Shape( [ new Vector2( 0, 0.5 ), new Vector2( -0.5, -0.5 ), new Vector2( 0.5, -0.5 ) ] ), curveSegments = 12 ) { + + super(); + + this.type = 'ShapeGeometry'; + + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + shapes: shapes, + curveSegments: curveSegments + }; + + // buffers + + const indices = []; + const vertices = []; + const normals = []; + const uvs = []; + + // helper variables + + let groupStart = 0; + let groupCount = 0; + + // allow single and array values for "shapes" parameter + + if ( Array.isArray( shapes ) === false ) { + + addShape( shapes ); + + } else { + + for ( let i = 0; i < shapes.length; i ++ ) { + + addShape( shapes[ i ] ); + + this.addGroup( groupStart, groupCount, i ); // enables MultiMaterial support + + groupStart += groupCount; + groupCount = 0; + + } + + } + + // build geometry + + this.setIndex( indices ); + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + + + // helper functions + + function addShape( shape ) { + + const indexOffset = vertices.length / 3; + const points = shape.extractPoints( curveSegments ); + + let shapeVertices = points.shape; + const shapeHoles = points.holes; + + // check direction of vertices + + if ( ShapeUtils.isClockWise( shapeVertices ) === false ) { + + shapeVertices = shapeVertices.reverse(); + + } + + for ( let i = 0, l = shapeHoles.length; i < l; i ++ ) { + + const shapeHole = shapeHoles[ i ]; + + if ( ShapeUtils.isClockWise( shapeHole ) === true ) { + + shapeHoles[ i ] = shapeHole.reverse(); + + } + + } + + const faces = ShapeUtils.triangulateShape( shapeVertices, shapeHoles ); + + // join vertices of inner and outer paths to a single array + + for ( let i = 0, l = shapeHoles.length; i < l; i ++ ) { + + const shapeHole = shapeHoles[ i ]; + shapeVertices = shapeVertices.concat( shapeHole ); + + } + + // vertices, normals, uvs + + for ( let i = 0, l = shapeVertices.length; i < l; i ++ ) { + + const vertex = shapeVertices[ i ]; + + vertices.push( vertex.x, vertex.y, 0 ); + normals.push( 0, 0, 1 ); + uvs.push( vertex.x, vertex.y ); // world uvs + + } + + // indices + + for ( let i = 0, l = faces.length; i < l; i ++ ) { + + const face = faces[ i ]; + + const a = face[ 0 ] + indexOffset; + const b = face[ 1 ] + indexOffset; + const c = face[ 2 ] + indexOffset; + + indices.push( a, b, c ); + groupCount += 3; + + } + + } + + } + + copy( source ) { + + super.copy( source ); + + this.parameters = Object.assign( {}, source.parameters ); + + return this; + + } + + toJSON() { + + const data = super.toJSON(); + + const shapes = this.parameters.shapes; + + return toJSON( shapes, data ); + + } + + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @param {Array} shapes - An array of shapes. + * @return {ShapeGeometry} A new instance. + */ + static fromJSON( data, shapes ) { + + const geometryShapes = []; + + for ( let j = 0, jl = data.shapes.length; j < jl; j ++ ) { + + const shape = shapes[ data.shapes[ j ] ]; + + geometryShapes.push( shape ); + + } + + return new ShapeGeometry( geometryShapes, data.curveSegments ); + + } + +} + +function toJSON( shapes, data ) { + + data.shapes = []; + + if ( Array.isArray( shapes ) ) { + + for ( let i = 0, l = shapes.length; i < l; i ++ ) { + + const shape = shapes[ i ]; + + data.shapes.push( shape.uuid ); + + } + + } else { + + data.shapes.push( shapes.uuid ); + + } + + return data; + +} + +/** + * A class for generating a sphere geometry. + * + * ```js + * const geometry = new THREE.SphereGeometry( 15, 32, 16 ); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + * const sphere = new THREE.Mesh( geometry, material ); + * scene.add( sphere ); + * ``` + * + * @augments BufferGeometry + */ +class SphereGeometry extends BufferGeometry { + + /** + * Constructs a new sphere geometry. + * + * @param {number} [radius=1] - The sphere radius. + * @param {number} [widthSegments=32] - The number of horizontal segments. Minimum value is `3`. + * @param {number} [heightSegments=16] - The number of vertical segments. Minimum value is `2`. + * @param {number} [phiStart=0] - The horizontal starting angle in radians. + * @param {number} [phiLength=Math.PI*2] - The horizontal sweep angle size. + * @param {number} [thetaStart=0] - The vertical starting angle in radians. + * @param {number} [thetaLength=Math.PI] - The vertical sweep angle size. + */ + constructor( radius = 1, widthSegments = 32, heightSegments = 16, phiStart = 0, phiLength = Math.PI * 2, thetaStart = 0, thetaLength = Math.PI ) { + + super(); + + this.type = 'SphereGeometry'; + + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + radius: radius, + widthSegments: widthSegments, + heightSegments: heightSegments, + phiStart: phiStart, + phiLength: phiLength, + thetaStart: thetaStart, + thetaLength: thetaLength + }; + + widthSegments = Math.max( 3, Math.floor( widthSegments ) ); + heightSegments = Math.max( 2, Math.floor( heightSegments ) ); + + const thetaEnd = Math.min( thetaStart + thetaLength, Math.PI ); + + let index = 0; + const grid = []; + + const vertex = new Vector3(); + const normal = new Vector3(); + + // buffers + + const indices = []; + const vertices = []; + const normals = []; + const uvs = []; + + // generate vertices, normals and uvs + + for ( let iy = 0; iy <= heightSegments; iy ++ ) { + + const verticesRow = []; + + const v = iy / heightSegments; + + // special case for the poles + + let uOffset = 0; + + if ( iy === 0 && thetaStart === 0 ) { + + uOffset = 0.5 / widthSegments; + + } else if ( iy === heightSegments && thetaEnd === Math.PI ) { + + uOffset = -0.5 / widthSegments; + + } + + for ( let ix = 0; ix <= widthSegments; ix ++ ) { + + const u = ix / widthSegments; + + // vertex + + vertex.x = - radius * Math.cos( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength ); + vertex.y = radius * Math.cos( thetaStart + v * thetaLength ); + vertex.z = radius * Math.sin( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength ); + + vertices.push( vertex.x, vertex.y, vertex.z ); + + // normal + + normal.copy( vertex ).normalize(); + normals.push( normal.x, normal.y, normal.z ); + + // uv + + uvs.push( u + uOffset, 1 - v ); + + verticesRow.push( index ++ ); + + } + + grid.push( verticesRow ); + + } + + // indices + + for ( let iy = 0; iy < heightSegments; iy ++ ) { + + for ( let ix = 0; ix < widthSegments; ix ++ ) { + + const a = grid[ iy ][ ix + 1 ]; + const b = grid[ iy ][ ix ]; + const c = grid[ iy + 1 ][ ix ]; + const d = grid[ iy + 1 ][ ix + 1 ]; + + if ( iy !== 0 || thetaStart > 0 ) indices.push( a, b, d ); + if ( iy !== heightSegments - 1 || thetaEnd < Math.PI ) indices.push( b, c, d ); + + } + + } + + // build geometry + + this.setIndex( indices ); + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + + } + + copy( source ) { + + super.copy( source ); + + this.parameters = Object.assign( {}, source.parameters ); + + return this; + + } + + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {SphereGeometry} A new instance. + */ + static fromJSON( data ) { + + return new SphereGeometry( data.radius, data.widthSegments, data.heightSegments, data.phiStart, data.phiLength, data.thetaStart, data.thetaLength ); + + } + +} + +/** + * A geometry class for representing an tetrahedron. + * + * ```js + * const geometry = new THREE.TetrahedronGeometry(); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + * const tetrahedron = new THREE.Mesh( geometry, material ); + * scene.add( tetrahedron ); + * ``` + * + * @augments PolyhedronGeometry + */ +class TetrahedronGeometry extends PolyhedronGeometry { + + /** + * Constructs a new tetrahedron geometry. + * + * @param {number} [radius=1] - Radius of the tetrahedron. + * @param {number} [detail=0] - Setting this to a value greater than `0` adds vertices making it no longer a tetrahedron. + */ + constructor( radius = 1, detail = 0 ) { + + const vertices = [ + 1, 1, 1, -1, -1, 1, -1, 1, -1, 1, -1, -1 + ]; + + const indices = [ + 2, 1, 0, 0, 3, 2, 1, 3, 0, 2, 3, 1 + ]; + + super( vertices, indices, radius, detail ); + + this.type = 'TetrahedronGeometry'; + + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + radius: radius, + detail: detail + }; + + } + + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {TetrahedronGeometry} A new instance. + */ + static fromJSON( data ) { + + return new TetrahedronGeometry( data.radius, data.detail ); + + } + +} + +/** + * A geometry class for representing an torus. + * + * ```js + * const geometry = new THREE.TorusGeometry( 10, 3, 16, 100 ); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + * const torus = new THREE.Mesh( geometry, material ); + * scene.add( torus ); + * ``` + * + * @augments BufferGeometry + */ +class TorusGeometry extends BufferGeometry { + + /** + * Constructs a new torus geometry. + * + * @param {number} [radius=1] - Radius of the torus, from the center of the torus to the center of the tube. + * @param {number} [tube=0.4] - Radius of the tube. Must be smaller than `radius`. + * @param {number} [radialSegments=12] - The number of radial segments. + * @param {number} [tubularSegments=48] - The number of tubular segments. + * @param {number} [arc=Math.PI*2] - Central angle in radians. + */ + constructor( radius = 1, tube = 0.4, radialSegments = 12, tubularSegments = 48, arc = Math.PI * 2 ) { + + super(); + + this.type = 'TorusGeometry'; + + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + radius: radius, + tube: tube, + radialSegments: radialSegments, + tubularSegments: tubularSegments, + arc: arc + }; + + radialSegments = Math.floor( radialSegments ); + tubularSegments = Math.floor( tubularSegments ); + + // buffers + + const indices = []; + const vertices = []; + const normals = []; + const uvs = []; + + // helper variables + + const center = new Vector3(); + const vertex = new Vector3(); + const normal = new Vector3(); + + // generate vertices, normals and uvs + + for ( let j = 0; j <= radialSegments; j ++ ) { + + for ( let i = 0; i <= tubularSegments; i ++ ) { + + const u = i / tubularSegments * arc; + const v = j / radialSegments * Math.PI * 2; + + // vertex + + vertex.x = ( radius + tube * Math.cos( v ) ) * Math.cos( u ); + vertex.y = ( radius + tube * Math.cos( v ) ) * Math.sin( u ); + vertex.z = tube * Math.sin( v ); + + vertices.push( vertex.x, vertex.y, vertex.z ); + + // normal + + center.x = radius * Math.cos( u ); + center.y = radius * Math.sin( u ); + normal.subVectors( vertex, center ).normalize(); + + normals.push( normal.x, normal.y, normal.z ); + + // uv + + uvs.push( i / tubularSegments ); + uvs.push( j / radialSegments ); + + } + + } + + // generate indices + + for ( let j = 1; j <= radialSegments; j ++ ) { + + for ( let i = 1; i <= tubularSegments; i ++ ) { + + // indices + + const a = ( tubularSegments + 1 ) * j + i - 1; + const b = ( tubularSegments + 1 ) * ( j - 1 ) + i - 1; + const c = ( tubularSegments + 1 ) * ( j - 1 ) + i; + const d = ( tubularSegments + 1 ) * j + i; + + // faces + + indices.push( a, b, d ); + indices.push( b, c, d ); + + } + + } + + // build geometry + + this.setIndex( indices ); + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + + } + + copy( source ) { + + super.copy( source ); + + this.parameters = Object.assign( {}, source.parameters ); + + return this; + + } + + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {TorusGeometry} A new instance. + */ + static fromJSON( data ) { + + return new TorusGeometry( data.radius, data.tube, data.radialSegments, data.tubularSegments, data.arc ); + + } + +} + +/** + * Creates a torus knot, the particular shape of which is defined by a pair + * of coprime integers, p and q. If p and q are not coprime, the result will + * be a torus link. + * + * ```js + * const geometry = new THREE.TorusKnotGeometry( 10, 3, 100, 16 ); + * const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + * const torusKnot = new THREE.Mesh( geometry, material ); + * scene.add( torusKnot ); + * ``` + * + * @augments BufferGeometry + */ +class TorusKnotGeometry extends BufferGeometry { + + /** + * Constructs a new torus knot geometry. + * + * @param {number} [radius=1] - Radius of the torus knot. + * @param {number} [tube=0.4] - Radius of the tube. + * @param {number} [tubularSegments=64] - The number of tubular segments. + * @param {number} [radialSegments=8] - The number of radial segments. + * @param {number} [p=2] - This value determines, how many times the geometry winds around its axis of rotational symmetry. + * @param {number} [q=3] - This value determines, how many times the geometry winds around a circle in the interior of the torus. + */ + constructor( radius = 1, tube = 0.4, tubularSegments = 64, radialSegments = 8, p = 2, q = 3 ) { + + super(); + + this.type = 'TorusKnotGeometry'; + + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + radius: radius, + tube: tube, + tubularSegments: tubularSegments, + radialSegments: radialSegments, + p: p, + q: q + }; + + tubularSegments = Math.floor( tubularSegments ); + radialSegments = Math.floor( radialSegments ); + + // buffers + + const indices = []; + const vertices = []; + const normals = []; + const uvs = []; + + // helper variables + + const vertex = new Vector3(); + const normal = new Vector3(); + + const P1 = new Vector3(); + const P2 = new Vector3(); + + const B = new Vector3(); + const T = new Vector3(); + const N = new Vector3(); + + // generate vertices, normals and uvs + + for ( let i = 0; i <= tubularSegments; ++ i ) { + + // the radian "u" is used to calculate the position on the torus curve of the current tubular segment + + const u = i / tubularSegments * p * Math.PI * 2; + + // now we calculate two points. P1 is our current position on the curve, P2 is a little farther ahead. + // these points are used to create a special "coordinate space", which is necessary to calculate the correct vertex positions + + calculatePositionOnCurve( u, p, q, radius, P1 ); + calculatePositionOnCurve( u + 0.01, p, q, radius, P2 ); + + // calculate orthonormal basis + + T.subVectors( P2, P1 ); + N.addVectors( P2, P1 ); + B.crossVectors( T, N ); + N.crossVectors( B, T ); + + // normalize B, N. T can be ignored, we don't use it + + B.normalize(); + N.normalize(); + + for ( let j = 0; j <= radialSegments; ++ j ) { + + // now calculate the vertices. they are nothing more than an extrusion of the torus curve. + // because we extrude a shape in the xy-plane, there is no need to calculate a z-value. + + const v = j / radialSegments * Math.PI * 2; + const cx = - tube * Math.cos( v ); + const cy = tube * Math.sin( v ); + + // now calculate the final vertex position. + // first we orient the extrusion with our basis vectors, then we add it to the current position on the curve + + vertex.x = P1.x + ( cx * N.x + cy * B.x ); + vertex.y = P1.y + ( cx * N.y + cy * B.y ); + vertex.z = P1.z + ( cx * N.z + cy * B.z ); + + vertices.push( vertex.x, vertex.y, vertex.z ); + + // normal (P1 is always the center/origin of the extrusion, thus we can use it to calculate the normal) + + normal.subVectors( vertex, P1 ).normalize(); + + normals.push( normal.x, normal.y, normal.z ); + + // uv + + uvs.push( i / tubularSegments ); + uvs.push( j / radialSegments ); + + } + + } + + // generate indices + + for ( let j = 1; j <= tubularSegments; j ++ ) { + + for ( let i = 1; i <= radialSegments; i ++ ) { + + // indices + + const a = ( radialSegments + 1 ) * ( j - 1 ) + ( i - 1 ); + const b = ( radialSegments + 1 ) * j + ( i - 1 ); + const c = ( radialSegments + 1 ) * j + i; + const d = ( radialSegments + 1 ) * ( j - 1 ) + i; + + // faces + + indices.push( a, b, d ); + indices.push( b, c, d ); + + } + + } + + // build geometry + + this.setIndex( indices ); + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + + // this function calculates the current position on the torus curve + + function calculatePositionOnCurve( u, p, q, radius, position ) { + + const cu = Math.cos( u ); + const su = Math.sin( u ); + const quOverP = q / p * u; + const cs = Math.cos( quOverP ); + + position.x = radius * ( 2 + cs ) * 0.5 * cu; + position.y = radius * ( 2 + cs ) * su * 0.5; + position.z = radius * Math.sin( quOverP ) * 0.5; + + } + + } + + copy( source ) { + + super.copy( source ); + + this.parameters = Object.assign( {}, source.parameters ); + + return this; + + } + + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {TorusKnotGeometry} A new instance. + */ + static fromJSON( data ) { + + return new TorusKnotGeometry( data.radius, data.tube, data.tubularSegments, data.radialSegments, data.p, data.q ); + + } + +} + +/** + * Creates a tube that extrudes along a 3D curve. + * + * ```js + * class CustomSinCurve extends THREE.Curve { + * + * getPoint( t, optionalTarget = new THREE.Vector3() ) { + * + * const tx = t * 3 - 1.5; + * const ty = Math.sin( 2 * Math.PI * t ); + * const tz = 0; + * + * return optionalTarget.set( tx, ty, tz ); + * } + * + * } + * + * const path = new CustomSinCurve( 10 ); + * const geometry = new THREE.TubeGeometry( path, 20, 2, 8, false ); + * const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); + * const mesh = new THREE.Mesh( geometry, material ); + * scene.add( mesh ); + * ``` + * + * @augments BufferGeometry + */ +class TubeGeometry extends BufferGeometry { + + /** + * Constructs a new tube geometry. + * + * @param {Curve} [path=QuadraticBezierCurve3] - A 3D curve defining the path of the tube. + * @param {number} [tubularSegments=64] - The number of segments that make up the tube. + * @param {number} [radius=1] -The radius of the tube. + * @param {number} [radialSegments=8] - The number of segments that make up the cross-section. + * @param {boolean} [closed=false] - Whether the tube is closed or not. + */ + constructor( path = new QuadraticBezierCurve3( new Vector3( -1, -1, 0 ), new Vector3( -1, 1, 0 ), new Vector3( 1, 1, 0 ) ), tubularSegments = 64, radius = 1, radialSegments = 8, closed = false ) { + + super(); + + this.type = 'TubeGeometry'; + + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + path: path, + tubularSegments: tubularSegments, + radius: radius, + radialSegments: radialSegments, + closed: closed + }; + + const frames = path.computeFrenetFrames( tubularSegments, closed ); + + // expose internals + + this.tangents = frames.tangents; + this.normals = frames.normals; + this.binormals = frames.binormals; + + // helper variables + + const vertex = new Vector3(); + const normal = new Vector3(); + const uv = new Vector2(); + let P = new Vector3(); + + // buffer + + const vertices = []; + const normals = []; + const uvs = []; + const indices = []; + + // create buffer data + + generateBufferData(); + + // build geometry + + this.setIndex( indices ); + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + + // functions + + function generateBufferData() { + + for ( let i = 0; i < tubularSegments; i ++ ) { + + generateSegment( i ); + + } + + // if the geometry is not closed, generate the last row of vertices and normals + // at the regular position on the given path + // + // if the geometry is closed, duplicate the first row of vertices and normals (uvs will differ) + + generateSegment( ( closed === false ) ? tubularSegments : 0 ); + + // uvs are generated in a separate function. + // this makes it easy compute correct values for closed geometries + + generateUVs(); + + // finally create faces + + generateIndices(); + + } + + function generateSegment( i ) { + + // we use getPointAt to sample evenly distributed points from the given path + + P = path.getPointAt( i / tubularSegments, P ); + + // retrieve corresponding normal and binormal + + const N = frames.normals[ i ]; + const B = frames.binormals[ i ]; + + // generate normals and vertices for the current segment + + for ( let j = 0; j <= radialSegments; j ++ ) { + + const v = j / radialSegments * Math.PI * 2; + + const sin = Math.sin( v ); + const cos = - Math.cos( v ); + + // normal + + normal.x = ( cos * N.x + sin * B.x ); + normal.y = ( cos * N.y + sin * B.y ); + normal.z = ( cos * N.z + sin * B.z ); + normal.normalize(); + + normals.push( normal.x, normal.y, normal.z ); + + // vertex + + vertex.x = P.x + radius * normal.x; + vertex.y = P.y + radius * normal.y; + vertex.z = P.z + radius * normal.z; + + vertices.push( vertex.x, vertex.y, vertex.z ); + + } + + } + + function generateIndices() { + + for ( let j = 1; j <= tubularSegments; j ++ ) { + + for ( let i = 1; i <= radialSegments; i ++ ) { + + const a = ( radialSegments + 1 ) * ( j - 1 ) + ( i - 1 ); + const b = ( radialSegments + 1 ) * j + ( i - 1 ); + const c = ( radialSegments + 1 ) * j + i; + const d = ( radialSegments + 1 ) * ( j - 1 ) + i; + + // faces + + indices.push( a, b, d ); + indices.push( b, c, d ); + + } + + } + + } + + function generateUVs() { + + for ( let i = 0; i <= tubularSegments; i ++ ) { + + for ( let j = 0; j <= radialSegments; j ++ ) { + + uv.x = i / tubularSegments; + uv.y = j / radialSegments; + + uvs.push( uv.x, uv.y ); + + } + + } + + } + + } + + copy( source ) { + + super.copy( source ); + + this.parameters = Object.assign( {}, source.parameters ); + + return this; + + } + + toJSON() { + + const data = super.toJSON(); + + data.path = this.parameters.path.toJSON(); + + return data; + + } + + /** + * Factory method for creating an instance of this class from the given + * JSON object. + * + * @param {Object} data - A JSON object representing the serialized geometry. + * @return {TubeGeometry} A new instance. + */ + static fromJSON( data ) { + + // This only works for built-in curves (e.g. CatmullRomCurve3). + // User defined curves or instances of CurvePath will not be deserialized. + return new TubeGeometry( + new Curves[ data.path.type ]().fromJSON( data.path ), + data.tubularSegments, + data.radius, + data.radialSegments, + data.closed + ); + + } + +} + +/** + * Can be used as a helper object to visualize a geometry as a wireframe. + * + * ```js + * const geometry = new THREE.SphereGeometry(); + * + * const wireframe = new THREE.WireframeGeometry( geometry ); + * + * const line = new THREE.LineSegments( wireframe ); + * line.material.depthWrite = false; + * line.material.opacity = 0.25; + * line.material.transparent = true; + * + * scene.add( line ); + * ``` + * + * Note: It is not yet possible to serialize/deserialize instances of this class. + * + * @augments BufferGeometry + */ +class WireframeGeometry extends BufferGeometry { + + /** + * Constructs a new wireframe geometry. + * + * @param {?BufferGeometry} [geometry=null] - The geometry. + */ + constructor( geometry = null ) { + + super(); + + this.type = 'WireframeGeometry'; + + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ + this.parameters = { + geometry: geometry + }; + + if ( geometry !== null ) { + + // buffer + + const vertices = []; + const edges = new Set(); + + // helper variables + + const start = new Vector3(); + const end = new Vector3(); + + if ( geometry.index !== null ) { + + // indexed BufferGeometry + + const position = geometry.attributes.position; + const indices = geometry.index; + let groups = geometry.groups; + + if ( groups.length === 0 ) { + + groups = [ { start: 0, count: indices.count, materialIndex: 0 } ]; + + } + + // create a data structure that contains all edges without duplicates + + for ( let o = 0, ol = groups.length; o < ol; ++ o ) { + + const group = groups[ o ]; + + const groupStart = group.start; + const groupCount = group.count; + + for ( let i = groupStart, l = ( groupStart + groupCount ); i < l; i += 3 ) { + + for ( let j = 0; j < 3; j ++ ) { + + const index1 = indices.getX( i + j ); + const index2 = indices.getX( i + ( j + 1 ) % 3 ); + + start.fromBufferAttribute( position, index1 ); + end.fromBufferAttribute( position, index2 ); + + if ( isUniqueEdge( start, end, edges ) === true ) { + + vertices.push( start.x, start.y, start.z ); + vertices.push( end.x, end.y, end.z ); + + } + + } + + } + + } + + } else { + + // non-indexed BufferGeometry + + const position = geometry.attributes.position; + + for ( let i = 0, l = ( position.count / 3 ); i < l; i ++ ) { + + for ( let j = 0; j < 3; j ++ ) { + + // three edges per triangle, an edge is represented as (index1, index2) + // e.g. the first triangle has the following edges: (0,1),(1,2),(2,0) + + const index1 = 3 * i + j; + const index2 = 3 * i + ( ( j + 1 ) % 3 ); + + start.fromBufferAttribute( position, index1 ); + end.fromBufferAttribute( position, index2 ); + + if ( isUniqueEdge( start, end, edges ) === true ) { + + vertices.push( start.x, start.y, start.z ); + vertices.push( end.x, end.y, end.z ); + + } + + } + + } + + } + + // build geometry + + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + + } + + } + + copy( source ) { + + super.copy( source ); + + this.parameters = Object.assign( {}, source.parameters ); + + return this; + + } + +} + +function isUniqueEdge( start, end, edges ) { + + const hash1 = `${start.x},${start.y},${start.z}-${end.x},${end.y},${end.z}`; + const hash2 = `${end.x},${end.y},${end.z}-${start.x},${start.y},${start.z}`; // coincident edge + + if ( edges.has( hash1 ) === true || edges.has( hash2 ) === true ) { + + return false; + + } else { + + edges.add( hash1 ); + edges.add( hash2 ); + return true; + + } + +} + +var Geometries = /*#__PURE__*/Object.freeze({ + __proto__: null, + BoxGeometry: BoxGeometry, + CapsuleGeometry: CapsuleGeometry, + CircleGeometry: CircleGeometry, + ConeGeometry: ConeGeometry, + CylinderGeometry: CylinderGeometry, + DodecahedronGeometry: DodecahedronGeometry, + EdgesGeometry: EdgesGeometry, + ExtrudeGeometry: ExtrudeGeometry, + IcosahedronGeometry: IcosahedronGeometry, + LatheGeometry: LatheGeometry, + OctahedronGeometry: OctahedronGeometry, + PlaneGeometry: PlaneGeometry, + PolyhedronGeometry: PolyhedronGeometry, + RingGeometry: RingGeometry, + ShapeGeometry: ShapeGeometry, + SphereGeometry: SphereGeometry, + TetrahedronGeometry: TetrahedronGeometry, + TorusGeometry: TorusGeometry, + TorusKnotGeometry: TorusKnotGeometry, + TubeGeometry: TubeGeometry, + WireframeGeometry: WireframeGeometry +}); + +/** + * This material can receive shadows, but otherwise is completely transparent. + * + * ```js + * const geometry = new THREE.PlaneGeometry( 2000, 2000 ); + * geometry.rotateX( - Math.PI / 2 ); + * + * const material = new THREE.ShadowMaterial(); + * material.opacity = 0.2; + * + * const plane = new THREE.Mesh( geometry, material ); + * plane.position.y = -200; + * plane.receiveShadow = true; + * scene.add( plane ); + * ``` + * + * @augments Material + */ +class ShadowMaterial extends Material { + + /** + * Constructs a new shadow material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isShadowMaterial = true; + + this.type = 'ShadowMaterial'; + + /** + * Color of the material. + * + * @type {Color} + * @default (0,0,0) + */ + this.color = new Color( 0x000000 ); + + /** + * Overwritten since shadow materials are transparent + * by default. + * + * @type {boolean} + * @default true + */ + this.transparent = true; + + /** + * Whether the material is affected by fog or not. + * + * @type {boolean} + * @default true + */ + this.fog = true; + + this.setValues( parameters ); + + } + + copy( source ) { + + super.copy( source ); + + this.color.copy( source.color ); + + this.fog = source.fog; + + return this; + + } + +} + +/** + * This class works just like {@link ShaderMaterial}, except that definitions + * of built-in uniforms and attributes are not automatically prepended to the + * GLSL shader code. + * + * `RawShaderMaterial` can only be used with {@link WebGLRenderer}. + * + * @augments ShaderMaterial + */ +class RawShaderMaterial extends ShaderMaterial { + + /** + * Constructs a new raw shader material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { + + super( parameters ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isRawShaderMaterial = true; + + this.type = 'RawShaderMaterial'; + + } + +} + +/** + * A standard physically based material, using Metallic-Roughness workflow. + * + * Physically based rendering (PBR) has recently become the standard in many + * 3D applications, such as [Unity]{@link https://blogs.unity3d.com/2014/10/29/physically-based-shading-in-unity-5-a-primer/}, + * [Unreal]{@link https://docs.unrealengine.com/latest/INT/Engine/Rendering/Materials/PhysicallyBased/} and + * [3D Studio Max]{@link http://area.autodesk.com/blogs/the-3ds-max-blog/what039s-new-for-rendering-in-3ds-max-2017}. + * + * This approach differs from older approaches in that instead of using + * approximations for the way in which light interacts with a surface, a + * physically correct model is used. The idea is that, instead of tweaking + * materials to look good under specific lighting, a material can be created + * that will react 'correctly' under all lighting scenarios. + * + * In practice this gives a more accurate and realistic looking result than + * the {@link MeshLambertMaterial} or {@link MeshPhongMaterial}, at the cost of + * being somewhat more computationally expensive. `MeshStandardMaterial` uses per-fragment + * shading. + * + * Note that for best results you should always specify an environment map when using this material. + * + * For a non-technical introduction to the concept of PBR and how to set up a + * PBR material, check out these articles by the people at [marmoset]{@link https://www.marmoset.co}: + * + * - [Basic Theory of Physically Based Rendering]{@link https://www.marmoset.co/posts/basic-theory-of-physically-based-rendering/} + * - [Physically Based Rendering and You Can Too]{@link https://www.marmoset.co/posts/physically-based-rendering-and-you-can-too/} + * + * Technical details of the approach used in three.js (and most other PBR systems) can be found is this + * [paper from Disney]{@link https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf} + * (pdf), by Brent Burley. + * + * @augments Material + */ +class MeshStandardMaterial extends Material { + + /** + * Constructs a new mesh standard material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshStandardMaterial = true; + + this.type = 'MeshStandardMaterial'; + + this.defines = { 'STANDARD': '' }; + + /** + * Color of the material. + * + * @type {Color} + * @default (1,1,1) + */ + this.color = new Color( 0xffffff ); // diffuse + + /** + * How rough the material appears. `0.0` means a smooth mirror reflection, `1.0` + * means fully diffuse. If `roughnessMap` is also provided, + * both values are multiplied. + * + * @type {number} + * @default 1 + */ + this.roughness = 1.0; + + /** + * How much the material is like a metal. Non-metallic materials such as wood + * or stone use `0.0`, metallic use `1.0`, with nothing (usually) in between. + * A value between `0.0` and `1.0` could be used for a rusty metal look. + * If `metalnessMap` is also provided, both values are multiplied. + * + * @type {number} + * @default 0 + */ + this.metalness = 0.0; + + /** + * The color map. May optionally include an alpha channel, typically combined + * with {@link Material#transparent} or {@link Material#alphaTest}. The texture map + * color is modulated by the diffuse `color`. + * + * @type {?Texture} + * @default null + */ + this.map = null; + + /** + * The light map. Requires a second set of UVs. + * + * @type {?Texture} + * @default null + */ + this.lightMap = null; + + /** + * Intensity of the baked light. + * + * @type {number} + * @default 1 + */ + this.lightMapIntensity = 1.0; + + /** + * The red channel of this texture is used as the ambient occlusion map. + * Requires a second set of UVs. + * + * @type {?Texture} + * @default null + */ + this.aoMap = null; + + /** + * Intensity of the ambient occlusion effect. Range is `[0,1]`, where `0` + * disables ambient occlusion. Where intensity is `1` and the AO map's + * red channel is also `1`, ambient light is fully occluded on a surface. + * + * @type {number} + * @default 1 + */ + this.aoMapIntensity = 1.0; + + /** + * Emissive (light) color of the material, essentially a solid color + * unaffected by other lighting. + * + * @type {Color} + * @default (0,0,0) + */ + this.emissive = new Color( 0x000000 ); + + /** + * Intensity of the emissive light. Modulates the emissive color. + * + * @type {number} + * @default 1 + */ + this.emissiveIntensity = 1.0; + + /** + * Set emissive (glow) map. The emissive map color is modulated by the + * emissive color and the emissive intensity. If you have an emissive map, + * be sure to set the emissive color to something other than black. + * + * @type {?Texture} + * @default null + */ + this.emissiveMap = null; + + /** + * The texture to create a bump map. The black and white values map to the + * perceived depth in relation to the lights. Bump doesn't actually affect + * the geometry of the object, only the lighting. If a normal map is defined + * this will be ignored. + * + * @type {?Texture} + * @default null + */ + this.bumpMap = null; + + /** + * How much the bump map affects the material. Typical range is `[0,1]`. + * + * @type {number} + * @default 1 + */ + this.bumpScale = 1; + + /** + * The texture to create a normal map. The RGB values affect the surface + * normal for each pixel fragment and change the way the color is lit. Normal + * maps do not change the actual shape of the surface, only the lighting. In + * case the material has a normal map authored using the left handed + * convention, the `y` component of `normalScale` should be negated to compensate + * for the different handedness. + * + * @type {?Texture} + * @default null + */ + this.normalMap = null; + + /** + * The type of normal map. + * + * @type {(TangentSpaceNormalMap|ObjectSpaceNormalMap)} + * @default TangentSpaceNormalMap + */ + this.normalMapType = TangentSpaceNormalMap; + + /** + * How much the normal map affects the material. Typical value range is `[0,1]`. + * + * @type {Vector2} + * @default (1,1) + */ + this.normalScale = new Vector2( 1, 1 ); + + /** + * The displacement map affects the position of the mesh's vertices. Unlike + * other maps which only affect the light and shade of the material the + * displaced vertices can cast shadows, block other objects, and otherwise + * act as real geometry. The displacement texture is an image where the value + * of each pixel (white being the highest) is mapped against, and + * repositions, the vertices of the mesh. + * + * @type {?Texture} + * @default null + */ + this.displacementMap = null; + + /** + * How much the displacement map affects the mesh (where black is no + * displacement, and white is maximum displacement). Without a displacement + * map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementScale = 1; + + /** + * The offset of the displacement map's values on the mesh's vertices. + * The bias is added to the scaled sample of the displacement map. + * Without a displacement map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementBias = 0; + + /** + * The green channel of this texture is used to alter the roughness of the + * material. + * + * @type {?Texture} + * @default null + */ + this.roughnessMap = null; + + /** + * The blue channel of this texture is used to alter the metalness of the + * material. + * + * @type {?Texture} + * @default null + */ + this.metalnessMap = null; + + /** + * The alpha map is a grayscale texture that controls the opacity across the + * surface (black: fully transparent; white: fully opaque). + * + * Only the color of the texture is used, ignoring the alpha channel if one + * exists. For RGB and RGBA textures, the renderer will use the green channel + * when sampling this texture due to the extra bit of precision provided for + * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and + * luminance/alpha textures will also still work as expected. + * + * @type {?Texture} + * @default null + */ + this.alphaMap = null; + + /** + * The environment map. To ensure a physically correct rendering, environment maps + * are internally pre-processed with {@link PMREMGenerator}. + * + * @type {?Texture} + * @default null + */ + this.envMap = null; + + /** + * The rotation of the environment map in radians. + * + * @type {Euler} + * @default (0,0,0) + */ + this.envMapRotation = new Euler(); + + /** + * Scales the effect of the environment map by multiplying its color. + * + * @type {number} + * @default 1 + */ + this.envMapIntensity = 1.0; + + /** + * Renders the geometry as a wireframe. + * + * @type {boolean} + * @default false + */ + this.wireframe = false; + + /** + * Controls the thickness of the wireframe. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {number} + * @default 1 + */ + this.wireframeLinewidth = 1; + + /** + * Defines appearance of wireframe ends. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ + this.wireframeLinecap = 'round'; + + /** + * Defines appearance of wireframe joints. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ + this.wireframeLinejoin = 'round'; + + /** + * Whether the material is rendered with flat shading or not. + * + * @type {boolean} + * @default false + */ + this.flatShading = false; + + /** + * Whether the material is affected by fog or not. + * + * @type {boolean} + * @default true + */ + this.fog = true; + + this.setValues( parameters ); + + } + + copy( source ) { + + super.copy( source ); + + this.defines = { 'STANDARD': '' }; + + this.color.copy( source.color ); + this.roughness = source.roughness; + this.metalness = source.metalness; + + this.map = source.map; + + this.lightMap = source.lightMap; + this.lightMapIntensity = source.lightMapIntensity; + + this.aoMap = source.aoMap; + this.aoMapIntensity = source.aoMapIntensity; + + this.emissive.copy( source.emissive ); + this.emissiveMap = source.emissiveMap; + this.emissiveIntensity = source.emissiveIntensity; + + this.bumpMap = source.bumpMap; + this.bumpScale = source.bumpScale; + + this.normalMap = source.normalMap; + this.normalMapType = source.normalMapType; + this.normalScale.copy( source.normalScale ); + + this.displacementMap = source.displacementMap; + this.displacementScale = source.displacementScale; + this.displacementBias = source.displacementBias; + + this.roughnessMap = source.roughnessMap; + + this.metalnessMap = source.metalnessMap; + + this.alphaMap = source.alphaMap; + + this.envMap = source.envMap; + this.envMapRotation.copy( source.envMapRotation ); + this.envMapIntensity = source.envMapIntensity; + + this.wireframe = source.wireframe; + this.wireframeLinewidth = source.wireframeLinewidth; + this.wireframeLinecap = source.wireframeLinecap; + this.wireframeLinejoin = source.wireframeLinejoin; + + this.flatShading = source.flatShading; + + this.fog = source.fog; + + return this; + + } + +} + +/** + * An extension of the {@link MeshStandardMaterial}, providing more advanced + * physically-based rendering properties: + * + * - Anisotropy: Ability to represent the anisotropic property of materials + * as observable with brushed metals. + * - Clearcoat: Some materials — like car paints, carbon fiber, and wet surfaces — require + * a clear, reflective layer on top of another layer that may be irregular or rough. + * Clearcoat approximates this effect, without the need for a separate transparent surface. + * - Iridescence: Allows to render the effect where hue varies depending on the viewing + * angle and illumination angle. This can be seen on soap bubbles, oil films, or on the + * wings of many insects. + * - Physically-based transparency: One limitation of {@link Material#opacity} is that highly + * transparent materials are less reflective. Physically-based transmission provides a more + * realistic option for thin, transparent surfaces like glass. + * - Advanced reflectivity: More flexible reflectivity for non-metallic materials. + * - Sheen: Can be used for representing cloth and fabric materials. + * + * As a result of these complex shading features, `MeshPhysicalMaterial` has a + * higher performance cost, per pixel, than other three.js materials. Most + * effects are disabled by default, and add cost as they are enabled. For + * best results, always specify an environment map when using this material. + * + * @augments MeshStandardMaterial + */ +class MeshPhysicalMaterial extends MeshStandardMaterial { + + /** + * Constructs a new mesh physical material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshPhysicalMaterial = true; + + this.defines = { + + 'STANDARD': '', + 'PHYSICAL': '' + + }; + + this.type = 'MeshPhysicalMaterial'; + + /** + * The rotation of the anisotropy in tangent, bitangent space, measured in radians + * counter-clockwise from the tangent. When `anisotropyMap` is present, this + * property provides additional rotation to the vectors in the texture. + * + * @type {number} + * @default 1 + */ + this.anisotropyRotation = 0; + + /** + * Red and green channels represent the anisotropy direction in `[-1, 1]` tangent, + * bitangent space, to be rotated by `anisotropyRotation`. The blue channel + * contains strength as `[0, 1]` to be multiplied by `anisotropy`. + * + * @type {?Texture} + * @default null + */ + this.anisotropyMap = null; + + /** + * The red channel of this texture is multiplied against `clearcoat`, + * for per-pixel control over a coating's intensity. + * + * @type {?Texture} + * @default null + */ + this.clearcoatMap = null; + + /** + * Roughness of the clear coat layer, from `0.0` to `1.0`. + * + * @type {number} + * @default 0 + */ + this.clearcoatRoughness = 0.0; + + /** + * The green channel of this texture is multiplied against + * `clearcoatRoughness`, for per-pixel control over a coating's roughness. + * + * @type {?Texture} + * @default null + */ + this.clearcoatRoughnessMap = null; + + /** + * How much `clearcoatNormalMap` affects the clear coat layer, from + * `(0,0)` to `(1,1)`. + * + * @type {Vector2} + * @default (1,1) + */ + this.clearcoatNormalScale = new Vector2( 1, 1 ); + + /** + * Can be used to enable independent normals for the clear coat layer. + * + * @type {?Texture} + * @default null + */ + this.clearcoatNormalMap = null; + + /** + * Index-of-refraction for non-metallic materials, from `1.0` to `2.333`. + * + * @type {number} + * @default 1.5 + */ + this.ior = 1.5; + + /** + * Degree of reflectivity, from `0.0` to `1.0`. Default is `0.5`, which + * corresponds to an index-of-refraction of `1.5`. + * + * This models the reflectivity of non-metallic materials. It has no effect + * when `metalness` is `1.0` + * + * @name MeshPhysicalMaterial#reflectivity + * @type {number} + * @default 0.5 + */ + Object.defineProperty( this, 'reflectivity', { + get: function () { + + return ( clamp( 2.5 * ( this.ior - 1 ) / ( this.ior + 1 ), 0, 1 ) ); + + }, + set: function ( reflectivity ) { + + this.ior = ( 1 + 0.4 * reflectivity ) / ( 1 - 0.4 * reflectivity ); + + } + } ); + + /** + * The red channel of this texture is multiplied against `iridescence`, for per-pixel + * control over iridescence. + * + * @type {?Texture} + * @default null + */ + this.iridescenceMap = null; + + /** + * Strength of the iridescence RGB color shift effect, represented by an index-of-refraction. + * Between `1.0` to `2.333`. + * + * @type {number} + * @default 1.3 + */ + this.iridescenceIOR = 1.3; + + /** + *Array of exactly 2 elements, specifying minimum and maximum thickness of the iridescence layer. + Thickness of iridescence layer has an equivalent effect of the one `thickness` has on `ior`. + * + * @type {Array} + * @default [100,400] + */ + this.iridescenceThicknessRange = [ 100, 400 ]; + + /** + * A texture that defines the thickness of the iridescence layer, stored in the green channel. + * Minimum and maximum values of thickness are defined by `iridescenceThicknessRange` array: + * - `0.0` in the green channel will result in thickness equal to first element of the array. + * - `1.0` in the green channel will result in thickness equal to second element of the array. + * - Values in-between will linearly interpolate between the elements of the array. + * + * @type {?Texture} + * @default null + */ + this.iridescenceThicknessMap = null; + + /** + * The sheen tint. + * + * @type {Color} + * @default (0,0,0) + */ + this.sheenColor = new Color( 0x000000 ); + + /** + * The RGB channels of this texture are multiplied against `sheenColor`, for per-pixel control + * over sheen tint. + * + * @type {?Texture} + * @default null + */ + this.sheenColorMap = null; + + /** + * Roughness of the sheen layer, from `0.0` to `1.0`. + * + * @type {number} + * @default 1 + */ + this.sheenRoughness = 1.0; + + /** + * The alpha channel of this texture is multiplied against `sheenRoughness`, for per-pixel control + * over sheen roughness. + * + * @type {?Texture} + * @default null + */ + this.sheenRoughnessMap = null; + + /** + * The red channel of this texture is multiplied against `transmission`, for per-pixel control over + * optical transparency. + * + * @type {?Texture} + * @default null + */ + this.transmissionMap = null; + + /** + * The thickness of the volume beneath the surface. The value is given in the + * coordinate space of the mesh. If the value is `0` the material is + * thin-walled. Otherwise the material is a volume boundary. + * + * @type {number} + * @default 0 + */ + this.thickness = 0; + + /** + * A texture that defines the thickness, stored in the green channel. This will + * be multiplied by `thickness`. + * + * @type {?Texture} + * @default null + */ + this.thicknessMap = null; + + /** + * Density of the medium given as the average distance that light travels in + * the medium before interacting with a particle. The value is given in world + * space units, and must be greater than zero. + * + * @type {number} + * @default Infinity + */ + this.attenuationDistance = Infinity; + + /** + * The color that white light turns into due to absorption when reaching the + * attenuation distance. + * + * @type {Color} + * @default (1,1,1) + */ + this.attenuationColor = new Color( 1, 1, 1 ); + + /** + * A float that scales the amount of specular reflection for non-metals only. + * When set to zero, the model is effectively Lambertian. From `0.0` to `1.0`. + * + * @type {number} + * @default 1 + */ + this.specularIntensity = 1.0; + + /** + * The alpha channel of this texture is multiplied against `specularIntensity`, + * for per-pixel control over specular intensity. + * + * @type {?Texture} + * @default null + */ + this.specularIntensityMap = null; + + /** + * Tints the specular reflection at normal incidence for non-metals only. + * + * @type {Color} + * @default (1,1,1) + */ + this.specularColor = new Color( 1, 1, 1 ); + + /** + * The RGB channels of this texture are multiplied against `specularColor`, + * for per-pixel control over specular color. + * + * @type {?Texture} + * @default null + */ + this.specularColorMap = null; + + this._anisotropy = 0; + this._clearcoat = 0; + this._dispersion = 0; + this._iridescence = 0; + this._sheen = 0.0; + this._transmission = 0; + + this.setValues( parameters ); + + } + + /** + * The anisotropy strength. + * + * @type {number} + * @default 0 + */ + get anisotropy() { + + return this._anisotropy; + + } + + set anisotropy( value ) { + + if ( this._anisotropy > 0 !== value > 0 ) { + + this.version ++; + + } + + this._anisotropy = value; + + } + + /** + * Represents the intensity of the clear coat layer, from `0.0` to `1.0`. Use + * clear coat related properties to enable multilayer materials that have a + * thin translucent layer over the base layer. + * + * @type {number} + * @default 0 + */ + get clearcoat() { + + return this._clearcoat; + + } + + set clearcoat( value ) { + + if ( this._clearcoat > 0 !== value > 0 ) { + + this.version ++; + + } + + this._clearcoat = value; + + } + /** + * The intensity of the iridescence layer, simulating RGB color shift based on the angle between + * the surface and the viewer, from `0.0` to `1.0`. + * + * @type {number} + * @default 0 + */ + get iridescence() { + + return this._iridescence; + + } + + set iridescence( value ) { + + if ( this._iridescence > 0 !== value > 0 ) { + + this.version ++; + + } + + this._iridescence = value; + + } + + /** + * Defines the strength of the angular separation of colors (chromatic aberration) transmitting + * through a relatively clear volume. Any value zero or larger is valid, the typical range of + * realistic values is `[0, 1]`. This property can be only be used with transmissive objects. + * + * @type {number} + * @default 0 + */ + get dispersion() { + + return this._dispersion; + + } + + set dispersion( value ) { + + if ( this._dispersion > 0 !== value > 0 ) { + + this.version ++; + + } + + this._dispersion = value; + + } + + /** + * The intensity of the sheen layer, from `0.0` to `1.0`. + * + * @type {number} + * @default 0 + */ + get sheen() { + + return this._sheen; + + } + + set sheen( value ) { + + if ( this._sheen > 0 !== value > 0 ) { + + this.version ++; + + } + + this._sheen = value; + + } + + /** + * Degree of transmission (or optical transparency), from `0.0` to `1.0`. + * + * Thin, transparent or semitransparent, plastic or glass materials remain + * largely reflective even if they are fully transmissive. The transmission + * property can be used to model these materials. + * + * When transmission is non-zero, `opacity` should be set to `1`. + * + * @type {number} + * @default 0 + */ + get transmission() { + + return this._transmission; + + } + + set transmission( value ) { + + if ( this._transmission > 0 !== value > 0 ) { + + this.version ++; + + } + + this._transmission = value; + + } + + copy( source ) { + + super.copy( source ); + + this.defines = { + + 'STANDARD': '', + 'PHYSICAL': '' + + }; + + this.anisotropy = source.anisotropy; + this.anisotropyRotation = source.anisotropyRotation; + this.anisotropyMap = source.anisotropyMap; + + this.clearcoat = source.clearcoat; + this.clearcoatMap = source.clearcoatMap; + this.clearcoatRoughness = source.clearcoatRoughness; + this.clearcoatRoughnessMap = source.clearcoatRoughnessMap; + this.clearcoatNormalMap = source.clearcoatNormalMap; + this.clearcoatNormalScale.copy( source.clearcoatNormalScale ); + + this.dispersion = source.dispersion; + this.ior = source.ior; + + this.iridescence = source.iridescence; + this.iridescenceMap = source.iridescenceMap; + this.iridescenceIOR = source.iridescenceIOR; + this.iridescenceThicknessRange = [ ...source.iridescenceThicknessRange ]; + this.iridescenceThicknessMap = source.iridescenceThicknessMap; + + this.sheen = source.sheen; + this.sheenColor.copy( source.sheenColor ); + this.sheenColorMap = source.sheenColorMap; + this.sheenRoughness = source.sheenRoughness; + this.sheenRoughnessMap = source.sheenRoughnessMap; + + this.transmission = source.transmission; + this.transmissionMap = source.transmissionMap; + + this.thickness = source.thickness; + this.thicknessMap = source.thicknessMap; + this.attenuationDistance = source.attenuationDistance; + this.attenuationColor.copy( source.attenuationColor ); + + this.specularIntensity = source.specularIntensity; + this.specularIntensityMap = source.specularIntensityMap; + this.specularColor.copy( source.specularColor ); + this.specularColorMap = source.specularColorMap; + + return this; + + } + +} + +/** + * A material for shiny surfaces with specular highlights. + * + * The material uses a non-physically based [Blinn-Phong]{@link https://en.wikipedia.org/wiki/Blinn-Phong_shading_model} + * model for calculating reflectance. Unlike the Lambertian model used in the + * {@link MeshLambertMaterial} this can simulate shiny surfaces with specular + * highlights (such as varnished wood). `MeshPhongMaterial` uses per-fragment shading. + * + * Performance will generally be greater when using this material over the + * {@link MeshStandardMaterial} or {@link MeshPhysicalMaterial}, at the cost of + * some graphical accuracy. + * + * @augments Material + */ +class MeshPhongMaterial extends Material { + + /** + * Constructs a new mesh phong material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshPhongMaterial = true; + + this.type = 'MeshPhongMaterial'; + + /** + * Color of the material. + * + * @type {Color} + * @default (1,1,1) + */ + this.color = new Color( 0xffffff ); // diffuse + + /** + * Specular color of the material. The default color is set to `0x111111` (very dark grey) + * + * This defines how shiny the material is and the color of its shine. + * + * @type {Color} + */ + this.specular = new Color( 0x111111 ); + + /** + * How shiny the specular highlight is; a higher value gives a sharper highlight. + * + * @type {number} + * @default 30 + */ + this.shininess = 30; + + /** + * The color map. May optionally include an alpha channel, typically combined + * with {@link Material#transparent} or {@link Material#alphaTest}. The texture map + * color is modulated by the diffuse `color`. + * + * @type {?Texture} + * @default null + */ + this.map = null; + + /** + * The light map. Requires a second set of UVs. + * + * @type {?Texture} + * @default null + */ + this.lightMap = null; + + /** + * Intensity of the baked light. + * + * @type {number} + * @default 1 + */ + this.lightMapIntensity = 1.0; + + /** + * The red channel of this texture is used as the ambient occlusion map. + * Requires a second set of UVs. + * + * @type {?Texture} + * @default null + */ + this.aoMap = null; + + /** + * Intensity of the ambient occlusion effect. Range is `[0,1]`, where `0` + * disables ambient occlusion. Where intensity is `1` and the AO map's + * red channel is also `1`, ambient light is fully occluded on a surface. + * + * @type {number} + * @default 1 + */ + this.aoMapIntensity = 1.0; + + /** + * Emissive (light) color of the material, essentially a solid color + * unaffected by other lighting. + * + * @type {Color} + * @default (0,0,0) + */ + this.emissive = new Color( 0x000000 ); + + /** + * Intensity of the emissive light. Modulates the emissive color. + * + * @type {number} + * @default 1 + */ + this.emissiveIntensity = 1.0; + + /** + * Set emissive (glow) map. The emissive map color is modulated by the + * emissive color and the emissive intensity. If you have an emissive map, + * be sure to set the emissive color to something other than black. + * + * @type {?Texture} + * @default null + */ + this.emissiveMap = null; + + /** + * The texture to create a bump map. The black and white values map to the + * perceived depth in relation to the lights. Bump doesn't actually affect + * the geometry of the object, only the lighting. If a normal map is defined + * this will be ignored. + * + * @type {?Texture} + * @default null + */ + this.bumpMap = null; + + /** + * How much the bump map affects the material. Typical range is `[0,1]`. + * + * @type {number} + * @default 1 + */ + this.bumpScale = 1; + + /** + * The texture to create a normal map. The RGB values affect the surface + * normal for each pixel fragment and change the way the color is lit. Normal + * maps do not change the actual shape of the surface, only the lighting. In + * case the material has a normal map authored using the left handed + * convention, the `y` component of `normalScale` should be negated to compensate + * for the different handedness. + * + * @type {?Texture} + * @default null + */ + this.normalMap = null; + + /** + * The type of normal map. + * + * @type {(TangentSpaceNormalMap|ObjectSpaceNormalMap)} + * @default TangentSpaceNormalMap + */ + this.normalMapType = TangentSpaceNormalMap; + + /** + * How much the normal map affects the material. Typical value range is `[0,1]`. + * + * @type {Vector2} + * @default (1,1) + */ + this.normalScale = new Vector2( 1, 1 ); + + /** + * The displacement map affects the position of the mesh's vertices. Unlike + * other maps which only affect the light and shade of the material the + * displaced vertices can cast shadows, block other objects, and otherwise + * act as real geometry. The displacement texture is an image where the value + * of each pixel (white being the highest) is mapped against, and + * repositions, the vertices of the mesh. + * + * @type {?Texture} + * @default null + */ + this.displacementMap = null; + + /** + * How much the displacement map affects the mesh (where black is no + * displacement, and white is maximum displacement). Without a displacement + * map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementScale = 1; + + /** + * The offset of the displacement map's values on the mesh's vertices. + * The bias is added to the scaled sample of the displacement map. + * Without a displacement map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementBias = 0; + + /** + * The specular map value affects both how much the specular surface + * highlight contributes and how much of the environment map affects the + * surface. + * + * @type {?Texture} + * @default null + */ + this.specularMap = null; + + /** + * The alpha map is a grayscale texture that controls the opacity across the + * surface (black: fully transparent; white: fully opaque). + * + * Only the color of the texture is used, ignoring the alpha channel if one + * exists. For RGB and RGBA textures, the renderer will use the green channel + * when sampling this texture due to the extra bit of precision provided for + * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and + * luminance/alpha textures will also still work as expected. + * + * @type {?Texture} + * @default null + */ + this.alphaMap = null; + + /** + * The environment map. + * + * @type {?Texture} + * @default null + */ + this.envMap = null; + + /** + * The rotation of the environment map in radians. + * + * @type {Euler} + * @default (0,0,0) + */ + this.envMapRotation = new Euler(); + + /** + * How to combine the result of the surface's color with the environment map, if any. + * + * When set to `MixOperation`, the {@link MeshBasicMaterial#reflectivity} is used to + * blend between the two colors. + * + * @type {(MultiplyOperation|MixOperation|AddOperation)} + * @default MultiplyOperation + */ + this.combine = MultiplyOperation; + + /** + * How much the environment map affects the surface. + * The valid range is between `0` (no reflections) and `1` (full reflections). + * + * @type {number} + * @default 1 + */ + this.reflectivity = 1; + + /** + * The index of refraction (IOR) of air (approximately 1) divided by the + * index of refraction of the material. It is used with environment mapping + * modes {@link CubeRefractionMapping} and {@link EquirectangularRefractionMapping}. + * The refraction ratio should not exceed `1`. + * + * @type {number} + * @default 0.98 + */ + this.refractionRatio = 0.98; + + /** + * Renders the geometry as a wireframe. + * + * @type {boolean} + * @default false + */ + this.wireframe = false; + + /** + * Controls the thickness of the wireframe. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {number} + * @default 1 + */ + this.wireframeLinewidth = 1; + + /** + * Defines appearance of wireframe ends. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ + this.wireframeLinecap = 'round'; + + /** + * Defines appearance of wireframe joints. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ + this.wireframeLinejoin = 'round'; + + /** + * Whether the material is rendered with flat shading or not. + * + * @type {boolean} + * @default false + */ + this.flatShading = false; + + /** + * Whether the material is affected by fog or not. + * + * @type {boolean} + * @default true + */ + this.fog = true; + + this.setValues( parameters ); + + } + + copy( source ) { + + super.copy( source ); + + this.color.copy( source.color ); + this.specular.copy( source.specular ); + this.shininess = source.shininess; + + this.map = source.map; + + this.lightMap = source.lightMap; + this.lightMapIntensity = source.lightMapIntensity; + + this.aoMap = source.aoMap; + this.aoMapIntensity = source.aoMapIntensity; + + this.emissive.copy( source.emissive ); + this.emissiveMap = source.emissiveMap; + this.emissiveIntensity = source.emissiveIntensity; + + this.bumpMap = source.bumpMap; + this.bumpScale = source.bumpScale; + + this.normalMap = source.normalMap; + this.normalMapType = source.normalMapType; + this.normalScale.copy( source.normalScale ); + + this.displacementMap = source.displacementMap; + this.displacementScale = source.displacementScale; + this.displacementBias = source.displacementBias; + + this.specularMap = source.specularMap; + + this.alphaMap = source.alphaMap; + + this.envMap = source.envMap; + this.envMapRotation.copy( source.envMapRotation ); + this.combine = source.combine; + this.reflectivity = source.reflectivity; + this.refractionRatio = source.refractionRatio; + + this.wireframe = source.wireframe; + this.wireframeLinewidth = source.wireframeLinewidth; + this.wireframeLinecap = source.wireframeLinecap; + this.wireframeLinejoin = source.wireframeLinejoin; + + this.flatShading = source.flatShading; + + this.fog = source.fog; + + return this; + + } + +} + +/** + * A material implementing toon shading. + * + * @augments Material + */ +class MeshToonMaterial extends Material { + + /** + * Constructs a new mesh toon material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshToonMaterial = true; + + this.defines = { 'TOON': '' }; + + this.type = 'MeshToonMaterial'; + + /** + * Color of the material. + * + * @type {Color} + * @default (1,1,1) + */ + this.color = new Color( 0xffffff ); + + /** + * The color map. May optionally include an alpha channel, typically combined + * with {@link Material#transparent} or {@link Material#alphaTest}. The texture map + * color is modulated by the diffuse `color`. + * + * @type {?Texture} + * @default null + */ + this.map = null; + + /** + * Gradient map for toon shading. It's required to set + * {@link Texture#minFilter} and {@link Texture#magFilter} to {@linkNearestFilter} + * when using this type of texture. + * + * @type {?Texture} + * @default null + */ + this.gradientMap = null; + + /** + * The light map. Requires a second set of UVs. + * + * @type {?Texture} + * @default null + */ + this.lightMap = null; + + /** + * Intensity of the baked light. + * + * @type {number} + * @default 1 + */ + this.lightMapIntensity = 1.0; + + /** + * The red channel of this texture is used as the ambient occlusion map. + * Requires a second set of UVs. + * + * @type {?Texture} + * @default null + */ + this.aoMap = null; + + /** + * Intensity of the ambient occlusion effect. Range is `[0,1]`, where `0` + * disables ambient occlusion. Where intensity is `1` and the AO map's + * red channel is also `1`, ambient light is fully occluded on a surface. + * + * @type {number} + * @default 1 + */ + this.aoMapIntensity = 1.0; + + /** + * Emissive (light) color of the material, essentially a solid color + * unaffected by other lighting. + * + * @type {Color} + * @default (0,0,0) + */ + this.emissive = new Color( 0x000000 ); + + /** + * Intensity of the emissive light. Modulates the emissive color. + * + * @type {number} + * @default 1 + */ + this.emissiveIntensity = 1.0; + + /** + * Set emissive (glow) map. The emissive map color is modulated by the + * emissive color and the emissive intensity. If you have an emissive map, + * be sure to set the emissive color to something other than black. + * + * @type {?Texture} + * @default null + */ + this.emissiveMap = null; + + /** + * The texture to create a bump map. The black and white values map to the + * perceived depth in relation to the lights. Bump doesn't actually affect + * the geometry of the object, only the lighting. If a normal map is defined + * this will be ignored. + * + * @type {?Texture} + * @default null + */ + this.bumpMap = null; + + /** + * How much the bump map affects the material. Typical range is `[0,1]`. + * + * @type {number} + * @default 1 + */ + this.bumpScale = 1; + + /** + * The texture to create a normal map. The RGB values affect the surface + * normal for each pixel fragment and change the way the color is lit. Normal + * maps do not change the actual shape of the surface, only the lighting. In + * case the material has a normal map authored using the left handed + * convention, the `y` component of `normalScale` should be negated to compensate + * for the different handedness. + * + * @type {?Texture} + * @default null + */ + this.normalMap = null; + + /** + * The type of normal map. + * + * @type {(TangentSpaceNormalMap|ObjectSpaceNormalMap)} + * @default TangentSpaceNormalMap + */ + this.normalMapType = TangentSpaceNormalMap; + + /** + * How much the normal map affects the material. Typical value range is `[0,1]`. + * + * @type {Vector2} + * @default (1,1) + */ + this.normalScale = new Vector2( 1, 1 ); + + /** + * The displacement map affects the position of the mesh's vertices. Unlike + * other maps which only affect the light and shade of the material the + * displaced vertices can cast shadows, block other objects, and otherwise + * act as real geometry. The displacement texture is an image where the value + * of each pixel (white being the highest) is mapped against, and + * repositions, the vertices of the mesh. + * + * @type {?Texture} + * @default null + */ + this.displacementMap = null; + + /** + * How much the displacement map affects the mesh (where black is no + * displacement, and white is maximum displacement). Without a displacement + * map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementScale = 1; + + /** + * The offset of the displacement map's values on the mesh's vertices. + * The bias is added to the scaled sample of the displacement map. + * Without a displacement map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementBias = 0; + + /** + * The alpha map is a grayscale texture that controls the opacity across the + * surface (black: fully transparent; white: fully opaque). + * + * Only the color of the texture is used, ignoring the alpha channel if one + * exists. For RGB and RGBA textures, the renderer will use the green channel + * when sampling this texture due to the extra bit of precision provided for + * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and + * luminance/alpha textures will also still work as expected. + * + * @type {?Texture} + * @default null + */ + this.alphaMap = null; + + /** + * Renders the geometry as a wireframe. + * + * @type {boolean} + * @default false + */ + this.wireframe = false; + + /** + * Controls the thickness of the wireframe. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {number} + * @default 1 + */ + this.wireframeLinewidth = 1; + + /** + * Defines appearance of wireframe ends. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ + this.wireframeLinecap = 'round'; + + /** + * Defines appearance of wireframe joints. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ + this.wireframeLinejoin = 'round'; + + /** + * Whether the material is affected by fog or not. + * + * @type {boolean} + * @default true + */ + this.fog = true; + + this.setValues( parameters ); + + } + + copy( source ) { + + super.copy( source ); + + this.color.copy( source.color ); + + this.map = source.map; + this.gradientMap = source.gradientMap; + + this.lightMap = source.lightMap; + this.lightMapIntensity = source.lightMapIntensity; + + this.aoMap = source.aoMap; + this.aoMapIntensity = source.aoMapIntensity; + + this.emissive.copy( source.emissive ); + this.emissiveMap = source.emissiveMap; + this.emissiveIntensity = source.emissiveIntensity; + + this.bumpMap = source.bumpMap; + this.bumpScale = source.bumpScale; + + this.normalMap = source.normalMap; + this.normalMapType = source.normalMapType; + this.normalScale.copy( source.normalScale ); + + this.displacementMap = source.displacementMap; + this.displacementScale = source.displacementScale; + this.displacementBias = source.displacementBias; + + this.alphaMap = source.alphaMap; + + this.wireframe = source.wireframe; + this.wireframeLinewidth = source.wireframeLinewidth; + this.wireframeLinecap = source.wireframeLinecap; + this.wireframeLinejoin = source.wireframeLinejoin; + + this.fog = source.fog; + + return this; + + } + +} + +/** + * A material that maps the normal vectors to RGB colors. + * + * @augments Material + */ +class MeshNormalMaterial extends Material { + + /** + * Constructs a new mesh normal material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshNormalMaterial = true; + + this.type = 'MeshNormalMaterial'; + + /** + * The texture to create a bump map. The black and white values map to the + * perceived depth in relation to the lights. Bump doesn't actually affect + * the geometry of the object, only the lighting. If a normal map is defined + * this will be ignored. + * + * @type {?Texture} + * @default null + */ + this.bumpMap = null; + + /** + * How much the bump map affects the material. Typical range is `[0,1]`. + * + * @type {number} + * @default 1 + */ + this.bumpScale = 1; + + /** + * The texture to create a normal map. The RGB values affect the surface + * normal for each pixel fragment and change the way the color is lit. Normal + * maps do not change the actual shape of the surface, only the lighting. In + * case the material has a normal map authored using the left handed + * convention, the `y` component of `normalScale` should be negated to compensate + * for the different handedness. + * + * @type {?Texture} + * @default null + */ + this.normalMap = null; + + /** + * The type of normal map. + * + * @type {(TangentSpaceNormalMap|ObjectSpaceNormalMap)} + * @default TangentSpaceNormalMap + */ + this.normalMapType = TangentSpaceNormalMap; + + /** + * How much the normal map affects the material. Typical value range is `[0,1]`. + * + * @type {Vector2} + * @default (1,1) + */ + this.normalScale = new Vector2( 1, 1 ); + + /** + * The displacement map affects the position of the mesh's vertices. Unlike + * other maps which only affect the light and shade of the material the + * displaced vertices can cast shadows, block other objects, and otherwise + * act as real geometry. The displacement texture is an image where the value + * of each pixel (white being the highest) is mapped against, and + * repositions, the vertices of the mesh. + * + * @type {?Texture} + * @default null + */ + this.displacementMap = null; + + /** + * How much the displacement map affects the mesh (where black is no + * displacement, and white is maximum displacement). Without a displacement + * map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementScale = 1; + + /** + * The offset of the displacement map's values on the mesh's vertices. + * The bias is added to the scaled sample of the displacement map. + * Without a displacement map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementBias = 0; + + /** + * Renders the geometry as a wireframe. + * + * @type {boolean} + * @default false + */ + this.wireframe = false; + + /** + * Controls the thickness of the wireframe. + * + * WebGL and WebGPU ignore this property and always render + * 1 pixel wide lines. + * + * @type {number} + * @default 1 + */ + this.wireframeLinewidth = 1; + + /** + * Whether the material is rendered with flat shading or not. + * + * @type {boolean} + * @default false + */ + this.flatShading = false; + + this.setValues( parameters ); + + } + + copy( source ) { + + super.copy( source ); + + this.bumpMap = source.bumpMap; + this.bumpScale = source.bumpScale; + + this.normalMap = source.normalMap; + this.normalMapType = source.normalMapType; + this.normalScale.copy( source.normalScale ); + + this.displacementMap = source.displacementMap; + this.displacementScale = source.displacementScale; + this.displacementBias = source.displacementBias; + + this.wireframe = source.wireframe; + this.wireframeLinewidth = source.wireframeLinewidth; + + this.flatShading = source.flatShading; + + return this; + + } + +} + +/** + * A material for non-shiny surfaces, without specular highlights. + * + * The material uses a non-physically based [Lambertian]{@link https://en.wikipedia.org/wiki/Lambertian_reflectance} + * model for calculating reflectance. This can simulate some surfaces (such + * as untreated wood or stone) well, but cannot simulate shiny surfaces with + * specular highlights (such as varnished wood). `MeshLambertMaterial` uses per-fragment + * shading. + * + * Due to the simplicity of the reflectance and illumination models, + * performance will be greater when using this material over the + * {@link MeshPhongMaterial}, {@link MeshStandardMaterial} or + * {@link MeshPhysicalMaterial}, at the cost of some graphical accuracy. + * + * @augments Material + */ +class MeshLambertMaterial extends Material { + + /** + * Constructs a new mesh lambert material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshLambertMaterial = true; + + this.type = 'MeshLambertMaterial'; + + /** + * Color of the material. + * + * @type {Color} + * @default (1,1,1) + */ + this.color = new Color( 0xffffff ); // diffuse + + /** + * The color map. May optionally include an alpha channel, typically combined + * with {@link Material#transparent} or {@link Material#alphaTest}. The texture map + * color is modulated by the diffuse `color`. + * + * @type {?Texture} + * @default null + */ + this.map = null; + + /** + * The light map. Requires a second set of UVs. + * + * @type {?Texture} + * @default null + */ + this.lightMap = null; + + /** + * Intensity of the baked light. + * + * @type {number} + * @default 1 + */ + this.lightMapIntensity = 1.0; + + /** + * The red channel of this texture is used as the ambient occlusion map. + * Requires a second set of UVs. + * + * @type {?Texture} + * @default null + */ + this.aoMap = null; + + /** + * Intensity of the ambient occlusion effect. Range is `[0,1]`, where `0` + * disables ambient occlusion. Where intensity is `1` and the AO map's + * red channel is also `1`, ambient light is fully occluded on a surface. + * + * @type {number} + * @default 1 + */ + this.aoMapIntensity = 1.0; + + /** + * Emissive (light) color of the material, essentially a solid color + * unaffected by other lighting. + * + * @type {Color} + * @default (0,0,0) + */ + this.emissive = new Color( 0x000000 ); + + /** + * Intensity of the emissive light. Modulates the emissive color. + * + * @type {number} + * @default 1 + */ + this.emissiveIntensity = 1.0; + + /** + * Set emissive (glow) map. The emissive map color is modulated by the + * emissive color and the emissive intensity. If you have an emissive map, + * be sure to set the emissive color to something other than black. + * + * @type {?Texture} + * @default null + */ + this.emissiveMap = null; + + /** + * The texture to create a bump map. The black and white values map to the + * perceived depth in relation to the lights. Bump doesn't actually affect + * the geometry of the object, only the lighting. If a normal map is defined + * this will be ignored. + * + * @type {?Texture} + * @default null + */ + this.bumpMap = null; + + /** + * How much the bump map affects the material. Typical range is `[0,1]`. + * + * @type {number} + * @default 1 + */ + this.bumpScale = 1; + + /** + * The texture to create a normal map. The RGB values affect the surface + * normal for each pixel fragment and change the way the color is lit. Normal + * maps do not change the actual shape of the surface, only the lighting. In + * case the material has a normal map authored using the left handed + * convention, the `y` component of `normalScale` should be negated to compensate + * for the different handedness. + * + * @type {?Texture} + * @default null + */ + this.normalMap = null; + + /** + * The type of normal map. + * + * @type {(TangentSpaceNormalMap|ObjectSpaceNormalMap)} + * @default TangentSpaceNormalMap + */ + this.normalMapType = TangentSpaceNormalMap; + + /** + * How much the normal map affects the material. Typical value range is `[0,1]`. + * + * @type {Vector2} + * @default (1,1) + */ + this.normalScale = new Vector2( 1, 1 ); + + /** + * The displacement map affects the position of the mesh's vertices. Unlike + * other maps which only affect the light and shade of the material the + * displaced vertices can cast shadows, block other objects, and otherwise + * act as real geometry. The displacement texture is an image where the value + * of each pixel (white being the highest) is mapped against, and + * repositions, the vertices of the mesh. + * + * @type {?Texture} + * @default null + */ + this.displacementMap = null; + + /** + * How much the displacement map affects the mesh (where black is no + * displacement, and white is maximum displacement). Without a displacement + * map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementScale = 1; + + /** + * The offset of the displacement map's values on the mesh's vertices. + * The bias is added to the scaled sample of the displacement map. + * Without a displacement map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementBias = 0; + + /** + * Specular map used by the material. + * + * @type {?Texture} + * @default null + */ + this.specularMap = null; + + /** + * The alpha map is a grayscale texture that controls the opacity across the + * surface (black: fully transparent; white: fully opaque). + * + * Only the color of the texture is used, ignoring the alpha channel if one + * exists. For RGB and RGBA textures, the renderer will use the green channel + * when sampling this texture due to the extra bit of precision provided for + * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and + * luminance/alpha textures will also still work as expected. + * + * @type {?Texture} + * @default null + */ + this.alphaMap = null; + + /** + * The environment map. + * + * @type {?Texture} + * @default null + */ + this.envMap = null; + + /** + * The rotation of the environment map in radians. + * + * @type {Euler} + * @default (0,0,0) + */ + this.envMapRotation = new Euler(); + + /** + * How to combine the result of the surface's color with the environment map, if any. + * + * When set to `MixOperation`, the {@link MeshBasicMaterial#reflectivity} is used to + * blend between the two colors. + * + * @type {(MultiplyOperation|MixOperation|AddOperation)} + * @default MultiplyOperation + */ + this.combine = MultiplyOperation; + + /** + * How much the environment map affects the surface. + * The valid range is between `0` (no reflections) and `1` (full reflections). + * + * @type {number} + * @default 1 + */ + this.reflectivity = 1; + + /** + * The index of refraction (IOR) of air (approximately 1) divided by the + * index of refraction of the material. It is used with environment mapping + * modes {@link CubeRefractionMapping} and {@link EquirectangularRefractionMapping}. + * The refraction ratio should not exceed `1`. + * + * @type {number} + * @default 0.98 + */ + this.refractionRatio = 0.98; + + /** + * Renders the geometry as a wireframe. + * + * @type {boolean} + * @default false + */ + this.wireframe = false; + + /** + * Controls the thickness of the wireframe. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {number} + * @default 1 + */ + this.wireframeLinewidth = 1; + + /** + * Defines appearance of wireframe ends. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ + this.wireframeLinecap = 'round'; + + /** + * Defines appearance of wireframe joints. + * + * Can only be used with {@link SVGRenderer}. + * + * @type {('round'|'bevel'|'miter')} + * @default 'round' + */ + this.wireframeLinejoin = 'round'; + + /** + * Whether the material is rendered with flat shading or not. + * + * @type {boolean} + * @default false + */ + this.flatShading = false; + + /** + * Whether the material is affected by fog or not. + * + * @type {boolean} + * @default true + */ + this.fog = true; + + this.setValues( parameters ); + + } + + copy( source ) { + + super.copy( source ); + + this.color.copy( source.color ); + + this.map = source.map; + + this.lightMap = source.lightMap; + this.lightMapIntensity = source.lightMapIntensity; + + this.aoMap = source.aoMap; + this.aoMapIntensity = source.aoMapIntensity; + + this.emissive.copy( source.emissive ); + this.emissiveMap = source.emissiveMap; + this.emissiveIntensity = source.emissiveIntensity; + + this.bumpMap = source.bumpMap; + this.bumpScale = source.bumpScale; + + this.normalMap = source.normalMap; + this.normalMapType = source.normalMapType; + this.normalScale.copy( source.normalScale ); + + this.displacementMap = source.displacementMap; + this.displacementScale = source.displacementScale; + this.displacementBias = source.displacementBias; + + this.specularMap = source.specularMap; + + this.alphaMap = source.alphaMap; + + this.envMap = source.envMap; + this.envMapRotation.copy( source.envMapRotation ); + this.combine = source.combine; + this.reflectivity = source.reflectivity; + this.refractionRatio = source.refractionRatio; + + this.wireframe = source.wireframe; + this.wireframeLinewidth = source.wireframeLinewidth; + this.wireframeLinecap = source.wireframeLinecap; + this.wireframeLinejoin = source.wireframeLinejoin; + + this.flatShading = source.flatShading; + + this.fog = source.fog; + + return this; + + } + +} + +/** + * A material for drawing geometry by depth. Depth is based off of the camera + * near and far plane. White is nearest, black is farthest. + * + * @augments Material + */ +class MeshDepthMaterial extends Material { + + /** + * Constructs a new mesh depth material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshDepthMaterial = true; + + this.type = 'MeshDepthMaterial'; + + /** + * Type for depth packing. + * + * @type {(BasicDepthPacking|RGBADepthPacking|RGBDepthPacking|RGDepthPacking)} + * @default BasicDepthPacking + */ + this.depthPacking = BasicDepthPacking; + + /** + * The color map. May optionally include an alpha channel, typically combined + * with {@link Material#transparent} or {@link Material#alphaTest}. + * + * @type {?Texture} + * @default null + */ + this.map = null; + + /** + * The alpha map is a grayscale texture that controls the opacity across the + * surface (black: fully transparent; white: fully opaque). + * + * Only the color of the texture is used, ignoring the alpha channel if one + * exists. For RGB and RGBA textures, the renderer will use the green channel + * when sampling this texture due to the extra bit of precision provided for + * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and + * luminance/alpha textures will also still work as expected. + * + * @type {?Texture} + * @default null + */ + this.alphaMap = null; + + /** + * The displacement map affects the position of the mesh's vertices. Unlike + * other maps which only affect the light and shade of the material the + * displaced vertices can cast shadows, block other objects, and otherwise + * act as real geometry. The displacement texture is an image where the value + * of each pixel (white being the highest) is mapped against, and + * repositions, the vertices of the mesh. + * + * @type {?Texture} + * @default null + */ + this.displacementMap = null; + + /** + * How much the displacement map affects the mesh (where black is no + * displacement, and white is maximum displacement). Without a displacement + * map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementScale = 1; + + /** + * The offset of the displacement map's values on the mesh's vertices. + * The bias is added to the scaled sample of the displacement map. + * Without a displacement map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementBias = 0; + + /** + * Renders the geometry as a wireframe. + * + * @type {boolean} + * @default false + */ + this.wireframe = false; + + /** + * Controls the thickness of the wireframe. + * + * WebGL and WebGPU ignore this property and always render + * 1 pixel wide lines. + * + * @type {number} + * @default 1 + */ + this.wireframeLinewidth = 1; + + this.setValues( parameters ); + + } + + copy( source ) { + + super.copy( source ); + + this.depthPacking = source.depthPacking; + + this.map = source.map; + + this.alphaMap = source.alphaMap; + + this.displacementMap = source.displacementMap; + this.displacementScale = source.displacementScale; + this.displacementBias = source.displacementBias; + + this.wireframe = source.wireframe; + this.wireframeLinewidth = source.wireframeLinewidth; + + return this; + + } + +} + +/** + * A material used internally for implementing shadow mapping with + * point lights. + * + * Can also be used to customize the shadow casting of an object by assigning + * an instance of `MeshDistanceMaterial` to {@link Object3D#customDistanceMaterial}. + * The following examples demonstrates this approach in order to ensure + * transparent parts of objects do no cast shadows. + * + * @augments Material + */ +class MeshDistanceMaterial extends Material { + + /** + * Constructs a new mesh distance material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshDistanceMaterial = true; + + this.type = 'MeshDistanceMaterial'; + + /** + * The color map. May optionally include an alpha channel, typically combined + * with {@link Material#transparent} or {@link Material#alphaTest}. + * + * @type {?Texture} + * @default null + */ + this.map = null; + + /** + * The alpha map is a grayscale texture that controls the opacity across the + * surface (black: fully transparent; white: fully opaque). + * + * Only the color of the texture is used, ignoring the alpha channel if one + * exists. For RGB and RGBA textures, the renderer will use the green channel + * when sampling this texture due to the extra bit of precision provided for + * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and + * luminance/alpha textures will also still work as expected. + * + * @type {?Texture} + * @default null + */ + this.alphaMap = null; + + /** + * The displacement map affects the position of the mesh's vertices. Unlike + * other maps which only affect the light and shade of the material the + * displaced vertices can cast shadows, block other objects, and otherwise + * act as real geometry. The displacement texture is an image where the value + * of each pixel (white being the highest) is mapped against, and + * repositions, the vertices of the mesh. + * + * @type {?Texture} + * @default null + */ + this.displacementMap = null; + + /** + * How much the displacement map affects the mesh (where black is no + * displacement, and white is maximum displacement). Without a displacement + * map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementScale = 1; + + /** + * The offset of the displacement map's values on the mesh's vertices. + * The bias is added to the scaled sample of the displacement map. + * Without a displacement map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementBias = 0; + + this.setValues( parameters ); + + } + + copy( source ) { + + super.copy( source ); + + this.map = source.map; + + this.alphaMap = source.alphaMap; + + this.displacementMap = source.displacementMap; + this.displacementScale = source.displacementScale; + this.displacementBias = source.displacementBias; + + return this; + + } + +} + +/** + * This material is defined by a MatCap (or Lit Sphere) texture, which encodes the + * material color and shading. + * + * `MeshMatcapMaterial` does not respond to lights since the matcap image file encodes + * baked lighting. It will cast a shadow onto an object that receives shadows + * (and shadow clipping works), but it will not self-shadow or receive + * shadows. + * + * @augments Material + */ +class MeshMatcapMaterial extends Material { + + /** + * Constructs a new mesh matcap material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshMatcapMaterial = true; + + this.defines = { 'MATCAP': '' }; + + this.type = 'MeshMatcapMaterial'; + + /** + * Color of the material. + * + * @type {Color} + * @default (1,1,1) + */ + this.color = new Color( 0xffffff ); // diffuse + + /** + * The matcap map. + * + * @type {?Texture} + * @default null + */ + this.matcap = null; + + /** + * The color map. May optionally include an alpha channel, typically combined + * with {@link Material#transparent} or {@link Material#alphaTest}. The texture map + * color is modulated by the diffuse `color`. + * + * @type {?Texture} + * @default null + */ + this.map = null; + + /** + * The texture to create a bump map. The black and white values map to the + * perceived depth in relation to the lights. Bump doesn't actually affect + * the geometry of the object, only the lighting. If a normal map is defined + * this will be ignored. + * + * @type {?Texture} + * @default null + */ + this.bumpMap = null; + + /** + * How much the bump map affects the material. Typical range is `[0,1]`. + * + * @type {number} + * @default 1 + */ + this.bumpScale = 1; + + /** + * The texture to create a normal map. The RGB values affect the surface + * normal for each pixel fragment and change the way the color is lit. Normal + * maps do not change the actual shape of the surface, only the lighting. In + * case the material has a normal map authored using the left handed + * convention, the `y` component of `normalScale` should be negated to compensate + * for the different handedness. + * + * @type {?Texture} + * @default null + */ + this.normalMap = null; + + /** + * The type of normal map. + * + * @type {(TangentSpaceNormalMap|ObjectSpaceNormalMap)} + * @default TangentSpaceNormalMap + */ + this.normalMapType = TangentSpaceNormalMap; + + /** + * How much the normal map affects the material. Typical value range is `[0,1]`. + * + * @type {Vector2} + * @default (1,1) + */ + this.normalScale = new Vector2( 1, 1 ); + + /** + * The displacement map affects the position of the mesh's vertices. Unlike + * other maps which only affect the light and shade of the material the + * displaced vertices can cast shadows, block other objects, and otherwise + * act as real geometry. The displacement texture is an image where the value + * of each pixel (white being the highest) is mapped against, and + * repositions, the vertices of the mesh. + * + * @type {?Texture} + * @default null + */ + this.displacementMap = null; + + /** + * How much the displacement map affects the mesh (where black is no + * displacement, and white is maximum displacement). Without a displacement + * map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementScale = 1; + + /** + * The offset of the displacement map's values on the mesh's vertices. + * The bias is added to the scaled sample of the displacement map. + * Without a displacement map set, this value is not applied. + * + * @type {number} + * @default 0 + */ + this.displacementBias = 0; + + /** + * The alpha map is a grayscale texture that controls the opacity across the + * surface (black: fully transparent; white: fully opaque). + * + * Only the color of the texture is used, ignoring the alpha channel if one + * exists. For RGB and RGBA textures, the renderer will use the green channel + * when sampling this texture due to the extra bit of precision provided for + * green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and + * luminance/alpha textures will also still work as expected. + * + * @type {?Texture} + * @default null + */ + this.alphaMap = null; + + /** + * Whether the material is rendered with flat shading or not. + * + * @type {boolean} + * @default false + */ + this.flatShading = false; + + /** + * Whether the material is affected by fog or not. + * + * @type {boolean} + * @default true + */ + this.fog = true; + + this.setValues( parameters ); + + } + + + copy( source ) { + + super.copy( source ); + + this.defines = { 'MATCAP': '' }; + + this.color.copy( source.color ); + + this.matcap = source.matcap; + + this.map = source.map; + + this.bumpMap = source.bumpMap; + this.bumpScale = source.bumpScale; + + this.normalMap = source.normalMap; + this.normalMapType = source.normalMapType; + this.normalScale.copy( source.normalScale ); + + this.displacementMap = source.displacementMap; + this.displacementScale = source.displacementScale; + this.displacementBias = source.displacementBias; + + this.alphaMap = source.alphaMap; + + this.flatShading = source.flatShading; + + this.fog = source.fog; + + return this; + + } + +} + +/** + * A material for rendering line primitives. + * + * Materials define the appearance of renderable 3D objects. + * + * ```js + * const material = new THREE.LineDashedMaterial( { + * color: 0xffffff, + * scale: 1, + * dashSize: 3, + * gapSize: 1, + * } ); + * ``` + * + * @augments LineBasicMaterial + */ +class LineDashedMaterial extends LineBasicMaterial { + + /** + * Constructs a new line dashed material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLineDashedMaterial = true; + this.type = 'LineDashedMaterial'; + + /** + * The scale of the dashed part of a line. + * + * @type {number} + * @default 1 + */ + this.scale = 1; + + /** + * The size of the dash. This is both the gap with the stroke. + * + * @type {number} + * @default 3 + */ + this.dashSize = 3; + + /** + * The size of the gap. + * + * @type {number} + * @default 1 + */ + this.gapSize = 1; + + this.setValues( parameters ); + + } + + copy( source ) { + + super.copy( source ); + + this.scale = source.scale; + this.dashSize = source.dashSize; + this.gapSize = source.gapSize; + + return this; + + } + +} + +/** + * Converts an array to a specific type. + * + * @param {TypedArray|Array} array - The array to convert. + * @param {TypedArray.constructor} type - The constructor of a typed array that defines the new type. + * @return {TypedArray} The converted array. + */ +function convertArray( array, type ) { + + if ( ! array || array.constructor === type ) return array; + + if ( typeof type.BYTES_PER_ELEMENT === 'number' ) { + + return new type( array ); // create typed array + + } + + return Array.prototype.slice.call( array ); // create Array + +} + +/** + * Returns `true` if the given object is a typed array. + * + * @param {any} object - The object to check. + * @return {boolean} Whether the given object is a typed array. + */ +function isTypedArray( object ) { + + return ArrayBuffer.isView( object ) && ! ( object instanceof DataView ); + +} + +/** + * Returns an array by which times and values can be sorted. + * + * @param {Array} times - The keyframe time values. + * @return {Array} The array. + */ +function getKeyframeOrder( times ) { + + function compareTime( i, j ) { + + return times[ i ] - times[ j ]; + + } + + const n = times.length; + const result = new Array( n ); + for ( let i = 0; i !== n; ++ i ) result[ i ] = i; + + result.sort( compareTime ); + + return result; + +} + +/** + * Sorts the given array by the previously computed order via `getKeyframeOrder()`. + * + * @param {Array} values - The values to sort. + * @param {number} stride - The stride. + * @param {Array} order - The sort order. + * @return {Array} The sorted values. + */ +function sortedArray( values, stride, order ) { + + const nValues = values.length; + const result = new values.constructor( nValues ); + + for ( let i = 0, dstOffset = 0; dstOffset !== nValues; ++ i ) { + + const srcOffset = order[ i ] * stride; + + for ( let j = 0; j !== stride; ++ j ) { + + result[ dstOffset ++ ] = values[ srcOffset + j ]; + + } + + } + + return result; + +} + +/** + * Used for parsing AOS keyframe formats. + * + * @param {Array} jsonKeys - A list of JSON keyframes. + * @param {Array} times - This array will be filled with keyframe times by this function. + * @param {Array} values - This array will be filled with keyframe values by this function. + * @param {string} valuePropertyName - The name of the property to use. + */ +function flattenJSON( jsonKeys, times, values, valuePropertyName ) { + + let i = 1, key = jsonKeys[ 0 ]; + + while ( key !== undefined && key[ valuePropertyName ] === undefined ) { + + key = jsonKeys[ i ++ ]; + + } + + if ( key === undefined ) return; // no data + + let value = key[ valuePropertyName ]; + if ( value === undefined ) return; // no data + + if ( Array.isArray( value ) ) { + + do { + + value = key[ valuePropertyName ]; + + if ( value !== undefined ) { + + times.push( key.time ); + values.push( ...value ); // push all elements + + } + + key = jsonKeys[ i ++ ]; + + } while ( key !== undefined ); + + } else if ( value.toArray !== undefined ) { + + // ...assume THREE.Math-ish + + do { + + value = key[ valuePropertyName ]; + + if ( value !== undefined ) { + + times.push( key.time ); + value.toArray( values, values.length ); + + } + + key = jsonKeys[ i ++ ]; + + } while ( key !== undefined ); + + } else { + + // otherwise push as-is + + do { + + value = key[ valuePropertyName ]; + + if ( value !== undefined ) { + + times.push( key.time ); + values.push( value ); + + } + + key = jsonKeys[ i ++ ]; + + } while ( key !== undefined ); + + } + +} + +/** + * Creates a new clip, containing only the segment of the original clip between the given frames. + * + * @param {AnimationClip} sourceClip - The values to sort. + * @param {string} name - The name of the clip. + * @param {number} startFrame - The start frame. + * @param {number} endFrame - The end frame. + * @param {number} [fps=30] - The FPS. + * @return {AnimationClip} The new sub clip. + */ +function subclip( sourceClip, name, startFrame, endFrame, fps = 30 ) { + + const clip = sourceClip.clone(); + + clip.name = name; + + const tracks = []; + + for ( let i = 0; i < clip.tracks.length; ++ i ) { + + const track = clip.tracks[ i ]; + const valueSize = track.getValueSize(); + + const times = []; + const values = []; + + for ( let j = 0; j < track.times.length; ++ j ) { + + const frame = track.times[ j ] * fps; + + if ( frame < startFrame || frame >= endFrame ) continue; + + times.push( track.times[ j ] ); + + for ( let k = 0; k < valueSize; ++ k ) { + + values.push( track.values[ j * valueSize + k ] ); + + } + + } + + if ( times.length === 0 ) continue; + + track.times = convertArray( times, track.times.constructor ); + track.values = convertArray( values, track.values.constructor ); + + tracks.push( track ); + + } + + clip.tracks = tracks; + + // find minimum .times value across all tracks in the trimmed clip + + let minStartTime = Infinity; + + for ( let i = 0; i < clip.tracks.length; ++ i ) { + + if ( minStartTime > clip.tracks[ i ].times[ 0 ] ) { + + minStartTime = clip.tracks[ i ].times[ 0 ]; + + } + + } + + // shift all tracks such that clip begins at t=0 + + for ( let i = 0; i < clip.tracks.length; ++ i ) { + + clip.tracks[ i ].shift( -1 * minStartTime ); + + } + + clip.resetDuration(); + + return clip; + +} + +/** + * Converts the keyframes of the given animation clip to an additive format. + * + * @param {AnimationClip} targetClip - The clip to make additive. + * @param {number} [referenceFrame=0] - The reference frame. + * @param {AnimationClip} [referenceClip=targetClip] - The reference clip. + * @param {number} [fps=30] - The FPS. + * @return {AnimationClip} The updated clip which is now additive. + */ +function makeClipAdditive( targetClip, referenceFrame = 0, referenceClip = targetClip, fps = 30 ) { + + if ( fps <= 0 ) fps = 30; + + const numTracks = referenceClip.tracks.length; + const referenceTime = referenceFrame / fps; + + // Make each track's values relative to the values at the reference frame + for ( let i = 0; i < numTracks; ++ i ) { + + const referenceTrack = referenceClip.tracks[ i ]; + const referenceTrackType = referenceTrack.ValueTypeName; + + // Skip this track if it's non-numeric + if ( referenceTrackType === 'bool' || referenceTrackType === 'string' ) continue; + + // Find the track in the target clip whose name and type matches the reference track + const targetTrack = targetClip.tracks.find( function ( track ) { + + return track.name === referenceTrack.name + && track.ValueTypeName === referenceTrackType; + + } ); + + if ( targetTrack === undefined ) continue; + + let referenceOffset = 0; + const referenceValueSize = referenceTrack.getValueSize(); + + if ( referenceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) { + + referenceOffset = referenceValueSize / 3; + + } + + let targetOffset = 0; + const targetValueSize = targetTrack.getValueSize(); + + if ( targetTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) { + + targetOffset = targetValueSize / 3; + + } + + const lastIndex = referenceTrack.times.length - 1; + let referenceValue; + + // Find the value to subtract out of the track + if ( referenceTime <= referenceTrack.times[ 0 ] ) { + + // Reference frame is earlier than the first keyframe, so just use the first keyframe + const startIndex = referenceOffset; + const endIndex = referenceValueSize - referenceOffset; + referenceValue = referenceTrack.values.slice( startIndex, endIndex ); + + } else if ( referenceTime >= referenceTrack.times[ lastIndex ] ) { + + // Reference frame is after the last keyframe, so just use the last keyframe + const startIndex = lastIndex * referenceValueSize + referenceOffset; + const endIndex = startIndex + referenceValueSize - referenceOffset; + referenceValue = referenceTrack.values.slice( startIndex, endIndex ); + + } else { + + // Interpolate to the reference value + const interpolant = referenceTrack.createInterpolant(); + const startIndex = referenceOffset; + const endIndex = referenceValueSize - referenceOffset; + interpolant.evaluate( referenceTime ); + referenceValue = interpolant.resultBuffer.slice( startIndex, endIndex ); + + } + + // Conjugate the quaternion + if ( referenceTrackType === 'quaternion' ) { + + const referenceQuat = new Quaternion().fromArray( referenceValue ).normalize().conjugate(); + referenceQuat.toArray( referenceValue ); + + } + + // Subtract the reference value from all of the track values + + const numTimes = targetTrack.times.length; + for ( let j = 0; j < numTimes; ++ j ) { + + const valueStart = j * targetValueSize + targetOffset; + + if ( referenceTrackType === 'quaternion' ) { + + // Multiply the conjugate for quaternion track types + Quaternion.multiplyQuaternionsFlat( + targetTrack.values, + valueStart, + referenceValue, + 0, + targetTrack.values, + valueStart + ); + + } else { + + const valueEnd = targetValueSize - targetOffset * 2; + + // Subtract each value for all other numeric track types + for ( let k = 0; k < valueEnd; ++ k ) { + + targetTrack.values[ valueStart + k ] -= referenceValue[ k ]; + + } + + } + + } + + } + + targetClip.blendMode = AdditiveAnimationBlendMode; + + return targetClip; + +} + +/** + * A class with various methods to assist with animations. + * + * @hideconstructor + */ +class AnimationUtils { + + /** + * Converts an array to a specific type + * + * @static + * @param {TypedArray|Array} array - The array to convert. + * @param {TypedArray.constructor} type - The constructor of a type array. + * @return {TypedArray} The converted array + */ + static convertArray( array, type ) { + + return convertArray( array, type ); + + } + + /** + * Returns `true` if the given object is a typed array. + * + * @static + * @param {any} object - The object to check. + * @return {boolean} Whether the given object is a typed array. + */ + static isTypedArray( object ) { + + return isTypedArray( object ); + + } + + /** + * Returns an array by which times and values can be sorted. + * + * @static + * @param {Array} times - The keyframe time values. + * @return {Array} The array. + */ + static getKeyframeOrder( times ) { + + return getKeyframeOrder( times ); + + } + + /** + * Sorts the given array by the previously computed order via `getKeyframeOrder()`. + * + * @static + * @param {Array} values - The values to sort. + * @param {number} stride - The stride. + * @param {Array} order - The sort order. + * @return {Array} The sorted values. + */ + static sortedArray( values, stride, order ) { + + return sortedArray( values, stride, order ); + + } + + /** + * Used for parsing AOS keyframe formats. + * + * @static + * @param {Array} jsonKeys - A list of JSON keyframes. + * @param {Array} times - This array will be filled with keyframe times by this method. + * @param {Array} values - This array will be filled with keyframe values by this method. + * @param {string} valuePropertyName - The name of the property to use. + */ + static flattenJSON( jsonKeys, times, values, valuePropertyName ) { + + flattenJSON( jsonKeys, times, values, valuePropertyName ); + + } + + /** + * Creates a new clip, containing only the segment of the original clip between the given frames. + * + * @static + * @param {AnimationClip} sourceClip - The values to sort. + * @param {string} name - The name of the clip. + * @param {number} startFrame - The start frame. + * @param {number} endFrame - The end frame. + * @param {number} [fps=30] - The FPS. + * @return {AnimationClip} The new sub clip. + */ + static subclip( sourceClip, name, startFrame, endFrame, fps = 30 ) { + + return subclip( sourceClip, name, startFrame, endFrame, fps ); + + } + + /** + * Converts the keyframes of the given animation clip to an additive format. + * + * @static + * @param {AnimationClip} targetClip - The clip to make additive. + * @param {number} [referenceFrame=0] - The reference frame. + * @param {AnimationClip} [referenceClip=targetClip] - The reference clip. + * @param {number} [fps=30] - The FPS. + * @return {AnimationClip} The updated clip which is now additive. + */ + static makeClipAdditive( targetClip, referenceFrame = 0, referenceClip = targetClip, fps = 30 ) { + + return makeClipAdditive( targetClip, referenceFrame, referenceClip, fps ); + + } + +} + +/** + * Abstract base class of interpolants over parametric samples. + * + * The parameter domain is one dimensional, typically the time or a path + * along a curve defined by the data. + * + * The sample values can have any dimensionality and derived classes may + * apply special interpretations to the data. + * + * This class provides the interval seek in a Template Method, deferring + * the actual interpolation to derived classes. + * + * Time complexity is O(1) for linear access crossing at most two points + * and O(log N) for random access, where N is the number of positions. + * + * References: {@link http://www.oodesign.com/template-method-pattern.html} + * + * @abstract + */ +class Interpolant { + + /** + * Constructs a new interpolant. + * + * @param {TypedArray} parameterPositions - The parameter positions hold the interpolation factors. + * @param {TypedArray} sampleValues - The sample values. + * @param {number} sampleSize - The sample size + * @param {TypedArray} [resultBuffer] - The result buffer. + */ + constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { + + /** + * The parameter positions. + * + * @type {TypedArray} + */ + this.parameterPositions = parameterPositions; + + /** + * A cache index. + * + * @private + * @type {number} + * @default 0 + */ + this._cachedIndex = 0; + + /** + * The result buffer. + * + * @type {TypedArray} + */ + this.resultBuffer = resultBuffer !== undefined ? resultBuffer : new sampleValues.constructor( sampleSize ); + + /** + * The sample values. + * + * @type {TypedArray} + */ + this.sampleValues = sampleValues; + + /** + * The value size. + * + * @type {TypedArray} + */ + this.valueSize = sampleSize; + + /** + * The interpolation settings. + * + * @type {?Object} + * @default null + */ + this.settings = null; + + /** + * The default settings object. + * + * @type {Object} + */ + this.DefaultSettings_ = {}; + + } + + /** + * Evaluate the interpolant at position `t`. + * + * @param {number} t - The interpolation factor. + * @return {TypedArray} The result buffer. + */ + evaluate( t ) { + + const pp = this.parameterPositions; + let i1 = this._cachedIndex, + t1 = pp[ i1 ], + t0 = pp[ i1 - 1 ]; + + validate_interval: { + + seek: { + + let right; + + linear_scan: { + + //- See http://jsperf.com/comparison-to-undefined/3 + //- slower code: + //- + //- if ( t >= t1 || t1 === undefined ) { + forward_scan: if ( ! ( t < t1 ) ) { + + for ( let giveUpAt = i1 + 2; ; ) { + + if ( t1 === undefined ) { + + if ( t < t0 ) break forward_scan; + + // after end + + i1 = pp.length; + this._cachedIndex = i1; + return this.copySampleValue_( i1 - 1 ); + + } + + if ( i1 === giveUpAt ) break; // this loop + + t0 = t1; + t1 = pp[ ++ i1 ]; + + if ( t < t1 ) { + + // we have arrived at the sought interval + break seek; + + } + + } + + // prepare binary search on the right side of the index + right = pp.length; + break linear_scan; + + } + + //- slower code: + //- if ( t < t0 || t0 === undefined ) { + if ( ! ( t >= t0 ) ) { + + // looping? + + const t1global = pp[ 1 ]; + + if ( t < t1global ) { + + i1 = 2; // + 1, using the scan for the details + t0 = t1global; + + } + + // linear reverse scan + + for ( let giveUpAt = i1 - 2; ; ) { + + if ( t0 === undefined ) { + + // before start + + this._cachedIndex = 0; + return this.copySampleValue_( 0 ); + + } + + if ( i1 === giveUpAt ) break; // this loop + + t1 = t0; + t0 = pp[ -- i1 - 1 ]; + + if ( t >= t0 ) { + + // we have arrived at the sought interval + break seek; + + } + + } + + // prepare binary search on the left side of the index + right = i1; + i1 = 0; + break linear_scan; + + } + + // the interval is valid + + break validate_interval; + + } // linear scan + + // binary search + + while ( i1 < right ) { + + const mid = ( i1 + right ) >>> 1; + + if ( t < pp[ mid ] ) { + + right = mid; + + } else { + + i1 = mid + 1; + + } + + } + + t1 = pp[ i1 ]; + t0 = pp[ i1 - 1 ]; + + // check boundary cases, again + + if ( t0 === undefined ) { + + this._cachedIndex = 0; + return this.copySampleValue_( 0 ); + + } + + if ( t1 === undefined ) { + + i1 = pp.length; + this._cachedIndex = i1; + return this.copySampleValue_( i1 - 1 ); + + } + + } // seek + + this._cachedIndex = i1; + + this.intervalChanged_( i1, t0, t1 ); + + } // validate_interval + + return this.interpolate_( i1, t0, t, t1 ); + + } + + /** + * Returns the interpolation settings. + * + * @return {Object} The interpolation settings. + */ + getSettings_() { + + return this.settings || this.DefaultSettings_; + + } + + /** + * Copies a sample value to the result buffer. + * + * @param {number} index - An index into the sample value buffer. + * @return {TypedArray} The result buffer. + */ + copySampleValue_( index ) { + + // copies a sample value to the result buffer + + const result = this.resultBuffer, + values = this.sampleValues, + stride = this.valueSize, + offset = index * stride; + + for ( let i = 0; i !== stride; ++ i ) { + + result[ i ] = values[ offset + i ]; + + } + + return result; + + } + + /** + * Copies a sample value to the result buffer. + * + * @abstract + * @param {number} i1 - An index into the sample value buffer. + * @param {number} t0 - The previous interpolation factor. + * @param {number} t - The current interpolation factor. + * @param {number} t1 - The next interpolation factor. + * @return {TypedArray} The result buffer. + */ + interpolate_( /* i1, t0, t, t1 */ ) { + + throw new Error( 'call to abstract method' ); + // implementations shall return this.resultBuffer + + } + + /** + * Optional method that is executed when the interval has changed. + * + * @param {number} i1 - An index into the sample value buffer. + * @param {number} t0 - The previous interpolation factor. + * @param {number} t - The current interpolation factor. + */ + intervalChanged_( /* i1, t0, t1 */ ) { + + // empty + + } + +} + +/** + * Fast and simple cubic spline interpolant. + * + * It was derived from a Hermitian construction setting the first derivative + * at each sample position to the linear slope between neighboring positions + * over their parameter interval. + * + * @augments Interpolant + */ +class CubicInterpolant extends Interpolant { + + /** + * Constructs a new cubic interpolant. + * + * @param {TypedArray} parameterPositions - The parameter positions hold the interpolation factors. + * @param {TypedArray} sampleValues - The sample values. + * @param {number} sampleSize - The sample size + * @param {TypedArray} [resultBuffer] - The result buffer. + */ + constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { + + super( parameterPositions, sampleValues, sampleSize, resultBuffer ); + + this._weightPrev = -0; + this._offsetPrev = -0; + this._weightNext = -0; + this._offsetNext = -0; + + this.DefaultSettings_ = { + + endingStart: ZeroCurvatureEnding, + endingEnd: ZeroCurvatureEnding + + }; + + } + + intervalChanged_( i1, t0, t1 ) { + + const pp = this.parameterPositions; + let iPrev = i1 - 2, + iNext = i1 + 1, + + tPrev = pp[ iPrev ], + tNext = pp[ iNext ]; + + if ( tPrev === undefined ) { + + switch ( this.getSettings_().endingStart ) { + + case ZeroSlopeEnding: + + // f'(t0) = 0 + iPrev = i1; + tPrev = 2 * t0 - t1; + + break; + + case WrapAroundEnding: + + // use the other end of the curve + iPrev = pp.length - 2; + tPrev = t0 + pp[ iPrev ] - pp[ iPrev + 1 ]; + + break; + + default: // ZeroCurvatureEnding + + // f''(t0) = 0 a.k.a. Natural Spline + iPrev = i1; + tPrev = t1; + + } + + } + + if ( tNext === undefined ) { + + switch ( this.getSettings_().endingEnd ) { + + case ZeroSlopeEnding: + + // f'(tN) = 0 + iNext = i1; + tNext = 2 * t1 - t0; + + break; + + case WrapAroundEnding: + + // use the other end of the curve + iNext = 1; + tNext = t1 + pp[ 1 ] - pp[ 0 ]; + + break; + + default: // ZeroCurvatureEnding + + // f''(tN) = 0, a.k.a. Natural Spline + iNext = i1 - 1; + tNext = t0; + + } + + } + + const halfDt = ( t1 - t0 ) * 0.5, + stride = this.valueSize; + + this._weightPrev = halfDt / ( t0 - tPrev ); + this._weightNext = halfDt / ( tNext - t1 ); + this._offsetPrev = iPrev * stride; + this._offsetNext = iNext * stride; + + } + + interpolate_( i1, t0, t, t1 ) { + + const result = this.resultBuffer, + values = this.sampleValues, + stride = this.valueSize, + + o1 = i1 * stride, o0 = o1 - stride, + oP = this._offsetPrev, oN = this._offsetNext, + wP = this._weightPrev, wN = this._weightNext, + + p = ( t - t0 ) / ( t1 - t0 ), + pp = p * p, + ppp = pp * p; + + // evaluate polynomials + + const sP = - wP * ppp + 2 * wP * pp - wP * p; + const s0 = ( 1 + wP ) * ppp + ( -1.5 - 2 * wP ) * pp + ( -0.5 + wP ) * p + 1; + const s1 = ( -1 - wN ) * ppp + ( 1.5 + wN ) * pp + 0.5 * p; + const sN = wN * ppp - wN * pp; + + // combine data linearly + + for ( let i = 0; i !== stride; ++ i ) { + + result[ i ] = + sP * values[ oP + i ] + + s0 * values[ o0 + i ] + + s1 * values[ o1 + i ] + + sN * values[ oN + i ]; + + } + + return result; + + } + +} + +/** + * A basic linear interpolant. + * + * @augments Interpolant + */ +class LinearInterpolant extends Interpolant { + + /** + * Constructs a new linear interpolant. + * + * @param {TypedArray} parameterPositions - The parameter positions hold the interpolation factors. + * @param {TypedArray} sampleValues - The sample values. + * @param {number} sampleSize - The sample size + * @param {TypedArray} [resultBuffer] - The result buffer. + */ + constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { + + super( parameterPositions, sampleValues, sampleSize, resultBuffer ); + + } + + interpolate_( i1, t0, t, t1 ) { + + const result = this.resultBuffer, + values = this.sampleValues, + stride = this.valueSize, + + offset1 = i1 * stride, + offset0 = offset1 - stride, + + weight1 = ( t - t0 ) / ( t1 - t0 ), + weight0 = 1 - weight1; + + for ( let i = 0; i !== stride; ++ i ) { + + result[ i ] = + values[ offset0 + i ] * weight0 + + values[ offset1 + i ] * weight1; + + } + + return result; + + } + +} + +/** + * Interpolant that evaluates to the sample value at the position preceding + * the parameter. + * + * @augments Interpolant + */ +class DiscreteInterpolant extends Interpolant { + + /** + * Constructs a new discrete interpolant. + * + * @param {TypedArray} parameterPositions - The parameter positions hold the interpolation factors. + * @param {TypedArray} sampleValues - The sample values. + * @param {number} sampleSize - The sample size + * @param {TypedArray} [resultBuffer] - The result buffer. + */ + constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { + + super( parameterPositions, sampleValues, sampleSize, resultBuffer ); + + } + + interpolate_( i1 /*, t0, t, t1 */ ) { + + return this.copySampleValue_( i1 - 1 ); + + } + +} + +/** + * Represents s a timed sequence of keyframes, which are composed of lists of + * times and related values, and which are used to animate a specific property + * of an object. + */ +class KeyframeTrack { + + /** + * Constructs a new keyframe track. + * + * @param {string} name - The keyframe track's name. + * @param {Array} times - A list of keyframe times. + * @param {Array} values - A list of keyframe values. + * @param {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth)} [interpolation] - The interpolation type. + */ + constructor( name, times, values, interpolation ) { + + if ( name === undefined ) throw new Error( 'THREE.KeyframeTrack: track name is undefined' ); + if ( times === undefined || times.length === 0 ) throw new Error( 'THREE.KeyframeTrack: no keyframes in track named ' + name ); + + /** + * The track's name can refer to morph targets or bones or + * possibly other values within an animated object. See {@link PropertyBinding#parseTrackName} + * for the forms of strings that can be parsed for property binding. + * + * @type {string} + */ + this.name = name; + + /** + * The keyframe times. + * + * @type {Float32Array} + */ + this.times = convertArray( times, this.TimeBufferType ); + + /** + * The keyframe values. + * + * @type {Float32Array} + */ + this.values = convertArray( values, this.ValueBufferType ); + + this.setInterpolation( interpolation || this.DefaultInterpolation ); + + } + + /** + * Converts the keyframe track to JSON. + * + * @static + * @param {KeyframeTrack} track - The keyframe track to serialize. + * @return {Object} The serialized keyframe track as JSON. + */ + static toJSON( track ) { + + const trackType = track.constructor; + + let json; + + // derived classes can define a static toJSON method + if ( trackType.toJSON !== this.toJSON ) { + + json = trackType.toJSON( track ); + + } else { + + // by default, we assume the data can be serialized as-is + json = { + + 'name': track.name, + 'times': convertArray( track.times, Array ), + 'values': convertArray( track.values, Array ) + + }; + + const interpolation = track.getInterpolation(); + + if ( interpolation !== track.DefaultInterpolation ) { + + json.interpolation = interpolation; + + } + + } + + json.type = track.ValueTypeName; // mandatory + + return json; + + } + + /** + * Factory method for creating a new discrete interpolant. + * + * @static + * @param {TypedArray} [result] - The result buffer. + * @return {DiscreteInterpolant} The new interpolant. + */ + InterpolantFactoryMethodDiscrete( result ) { + + return new DiscreteInterpolant( this.times, this.values, this.getValueSize(), result ); + + } + + /** + * Factory method for creating a new linear interpolant. + * + * @static + * @param {TypedArray} [result] - The result buffer. + * @return {LinearInterpolant} The new interpolant. + */ + InterpolantFactoryMethodLinear( result ) { + + return new LinearInterpolant( this.times, this.values, this.getValueSize(), result ); + + } + + /** + * Factory method for creating a new smooth interpolant. + * + * @static + * @param {TypedArray} [result] - The result buffer. + * @return {CubicInterpolant} The new interpolant. + */ + InterpolantFactoryMethodSmooth( result ) { + + return new CubicInterpolant( this.times, this.values, this.getValueSize(), result ); + + } + + /** + * Defines the interpolation factor method for this keyframe track. + * + * @param {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth)} interpolation - The interpolation type. + * @return {KeyframeTrack} A reference to this keyframe track. + */ + setInterpolation( interpolation ) { + + let factoryMethod; + + switch ( interpolation ) { + + case InterpolateDiscrete: + + factoryMethod = this.InterpolantFactoryMethodDiscrete; + + break; + + case InterpolateLinear: + + factoryMethod = this.InterpolantFactoryMethodLinear; + + break; + + case InterpolateSmooth: + + factoryMethod = this.InterpolantFactoryMethodSmooth; + + break; + + } + + if ( factoryMethod === undefined ) { + + const message = 'unsupported interpolation for ' + + this.ValueTypeName + ' keyframe track named ' + this.name; + + if ( this.createInterpolant === undefined ) { + + // fall back to default, unless the default itself is messed up + if ( interpolation !== this.DefaultInterpolation ) { + + this.setInterpolation( this.DefaultInterpolation ); + + } else { + + throw new Error( message ); // fatal, in this case + + } + + } + + console.warn( 'THREE.KeyframeTrack:', message ); + return this; + + } + + this.createInterpolant = factoryMethod; + + return this; + + } + + /** + * Returns the current interpolation type. + * + * @return {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth)} The interpolation type. + */ + getInterpolation() { + + switch ( this.createInterpolant ) { + + case this.InterpolantFactoryMethodDiscrete: + + return InterpolateDiscrete; + + case this.InterpolantFactoryMethodLinear: + + return InterpolateLinear; + + case this.InterpolantFactoryMethodSmooth: + + return InterpolateSmooth; + + } + + } + + /** + * Returns the value size. + * + * @return {number} The value size. + */ + getValueSize() { + + return this.values.length / this.times.length; + + } + + /** + * Moves all keyframes either forward or backward in time. + * + * @param {number} timeOffset - The offset to move the time values. + * @return {KeyframeTrack} A reference to this keyframe track. + */ + shift( timeOffset ) { + + if ( timeOffset !== 0.0 ) { + + const times = this.times; + + for ( let i = 0, n = times.length; i !== n; ++ i ) { + + times[ i ] += timeOffset; + + } + + } + + return this; + + } + + /** + * Scale all keyframe times by a factor (useful for frame - seconds conversions). + * + * @param {number} timeScale - The time scale. + * @return {KeyframeTrack} A reference to this keyframe track. + */ + scale( timeScale ) { + + if ( timeScale !== 1.0 ) { + + const times = this.times; + + for ( let i = 0, n = times.length; i !== n; ++ i ) { + + times[ i ] *= timeScale; + + } + + } + + return this; + + } + + /** + * Removes keyframes before and after animation without changing any values within the defined time range. + * + * Note: The method does not shift around keys to the start of the track time, because for interpolated + * keys this will change their values + * + * @param {number} startTime - The start time. + * @param {number} endTime - The end time. + * @return {KeyframeTrack} A reference to this keyframe track. + */ + trim( startTime, endTime ) { + + const times = this.times, + nKeys = times.length; + + let from = 0, + to = nKeys - 1; + + while ( from !== nKeys && times[ from ] < startTime ) { + + ++ from; + + } + + while ( to !== -1 && times[ to ] > endTime ) { + + -- to; + + } + + ++ to; // inclusive -> exclusive bound + + if ( from !== 0 || to !== nKeys ) { + + // empty tracks are forbidden, so keep at least one keyframe + if ( from >= to ) { + + to = Math.max( to, 1 ); + from = to - 1; + + } + + const stride = this.getValueSize(); + this.times = times.slice( from, to ); + this.values = this.values.slice( from * stride, to * stride ); + + } + + return this; + + } + + /** + * Performs minimal validation on the keyframe track. Returns `true` if the values + * are valid. + * + * @return {boolean} Whether the keyframes are valid or not. + */ + validate() { + + let valid = true; + + const valueSize = this.getValueSize(); + if ( valueSize - Math.floor( valueSize ) !== 0 ) { + + console.error( 'THREE.KeyframeTrack: Invalid value size in track.', this ); + valid = false; + + } + + const times = this.times, + values = this.values, + + nKeys = times.length; + + if ( nKeys === 0 ) { + + console.error( 'THREE.KeyframeTrack: Track is empty.', this ); + valid = false; + + } + + let prevTime = null; + + for ( let i = 0; i !== nKeys; i ++ ) { + + const currTime = times[ i ]; + + if ( typeof currTime === 'number' && isNaN( currTime ) ) { + + console.error( 'THREE.KeyframeTrack: Time is not a valid number.', this, i, currTime ); + valid = false; + break; + + } + + if ( prevTime !== null && prevTime > currTime ) { + + console.error( 'THREE.KeyframeTrack: Out of order keys.', this, i, currTime, prevTime ); + valid = false; + break; + + } + + prevTime = currTime; + + } + + if ( values !== undefined ) { + + if ( isTypedArray( values ) ) { + + for ( let i = 0, n = values.length; i !== n; ++ i ) { + + const value = values[ i ]; + + if ( isNaN( value ) ) { + + console.error( 'THREE.KeyframeTrack: Value is not a valid number.', this, i, value ); + valid = false; + break; + + } + + } + + } + + } + + return valid; + + } + + /** + * Optimizes this keyframe track by removing equivalent sequential keys (which are + * common in morph target sequences). + * + * @return {AnimationClip} A reference to this animation clip. + */ + optimize() { + + // (0,0,0,0,1,1,1,0,0,0,0,0,0,0) --> (0,0,1,1,0,0) + + // times or values may be shared with other tracks, so overwriting is unsafe + const times = this.times.slice(), + values = this.values.slice(), + stride = this.getValueSize(), + + smoothInterpolation = this.getInterpolation() === InterpolateSmooth, + + lastIndex = times.length - 1; + + let writeIndex = 1; + + for ( let i = 1; i < lastIndex; ++ i ) { + + let keep = false; + + const time = times[ i ]; + const timeNext = times[ i + 1 ]; + + // remove adjacent keyframes scheduled at the same time + + if ( time !== timeNext && ( i !== 1 || time !== times[ 0 ] ) ) { + + if ( ! smoothInterpolation ) { + + // remove unnecessary keyframes same as their neighbors + + const offset = i * stride, + offsetP = offset - stride, + offsetN = offset + stride; + + for ( let j = 0; j !== stride; ++ j ) { + + const value = values[ offset + j ]; + + if ( value !== values[ offsetP + j ] || + value !== values[ offsetN + j ] ) { + + keep = true; + break; + + } + + } + + } else { + + keep = true; + + } + + } + + // in-place compaction + + if ( keep ) { + + if ( i !== writeIndex ) { + + times[ writeIndex ] = times[ i ]; + + const readOffset = i * stride, + writeOffset = writeIndex * stride; + + for ( let j = 0; j !== stride; ++ j ) { + + values[ writeOffset + j ] = values[ readOffset + j ]; + + } + + } + + ++ writeIndex; + + } + + } + + // flush last keyframe (compaction looks ahead) + + if ( lastIndex > 0 ) { + + times[ writeIndex ] = times[ lastIndex ]; + + for ( let readOffset = lastIndex * stride, writeOffset = writeIndex * stride, j = 0; j !== stride; ++ j ) { + + values[ writeOffset + j ] = values[ readOffset + j ]; + + } + + ++ writeIndex; + + } + + if ( writeIndex !== times.length ) { + + this.times = times.slice( 0, writeIndex ); + this.values = values.slice( 0, writeIndex * stride ); + + } else { + + this.times = times; + this.values = values; + + } + + return this; + + } + + /** + * Returns a new keyframe track with copied values from this instance. + * + * @return {KeyframeTrack} A clone of this instance. + */ + clone() { + + const times = this.times.slice(); + const values = this.values.slice(); + + const TypedKeyframeTrack = this.constructor; + const track = new TypedKeyframeTrack( this.name, times, values ); + + // Interpolant argument to constructor is not saved, so copy the factory method directly. + track.createInterpolant = this.createInterpolant; + + return track; + + } + +} + +/** + * The value type name. + * + * @type {String} + * @default '' + */ +KeyframeTrack.prototype.ValueTypeName = ''; + +/** + * The time buffer type of this keyframe track. + * + * @type {TypedArray|Array} + * @default Float32Array.constructor + */ +KeyframeTrack.prototype.TimeBufferType = Float32Array; + +/** + * The value buffer type of this keyframe track. + * + * @type {TypedArray|Array} + * @default Float32Array.constructor + */ +KeyframeTrack.prototype.ValueBufferType = Float32Array; + +/** + * The default interpolation type of this keyframe track. + * + * @type {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth)} + * @default InterpolateLinear + */ +KeyframeTrack.prototype.DefaultInterpolation = InterpolateLinear; + +/** + * A track for boolean keyframe values. + * + * @augments KeyframeTrack + */ +class BooleanKeyframeTrack extends KeyframeTrack { + + /** + * Constructs a new boolean keyframe track. + * + * This keyframe track type has no `interpolation` parameter because the + * interpolation is always discrete. + * + * @param {string} name - The keyframe track's name. + * @param {Array} times - A list of keyframe times. + * @param {Array} values - A list of keyframe values. + */ + constructor( name, times, values ) { + + super( name, times, values ); + + } + +} + +/** + * The value type name. + * + * @type {String} + * @default 'bool' + */ +BooleanKeyframeTrack.prototype.ValueTypeName = 'bool'; + +/** + * The value buffer type of this keyframe track. + * + * @type {TypedArray|Array} + * @default Array.constructor + */ +BooleanKeyframeTrack.prototype.ValueBufferType = Array; + +/** + * The default interpolation type of this keyframe track. + * + * @type {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth)} + * @default InterpolateDiscrete + */ +BooleanKeyframeTrack.prototype.DefaultInterpolation = InterpolateDiscrete; +BooleanKeyframeTrack.prototype.InterpolantFactoryMethodLinear = undefined; +BooleanKeyframeTrack.prototype.InterpolantFactoryMethodSmooth = undefined; + +/** + * A track for color keyframe values. + * + * @augments KeyframeTrack + */ +class ColorKeyframeTrack extends KeyframeTrack { + + /** + * Constructs a new color keyframe track. + * + * @param {string} name - The keyframe track's name. + * @param {Array} times - A list of keyframe times. + * @param {Array} values - A list of keyframe values. + * @param {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth)} [interpolation] - The interpolation type. + */ + constructor( name, times, values, interpolation ) { + + super( name, times, values, interpolation ); + + } + +} + +/** + * The value type name. + * + * @type {String} + * @default 'color' + */ +ColorKeyframeTrack.prototype.ValueTypeName = 'color'; + +/** + * A track for numeric keyframe values. + * + * @augments KeyframeTrack + */ +class NumberKeyframeTrack extends KeyframeTrack { + + /** + * Constructs a new number keyframe track. + * + * @param {string} name - The keyframe track's name. + * @param {Array} times - A list of keyframe times. + * @param {Array} values - A list of keyframe values. + * @param {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth)} [interpolation] - The interpolation type. + */ + constructor( name, times, values, interpolation ) { + + super( name, times, values, interpolation ); + + } + +} + +/** + * The value type name. + * + * @type {String} + * @default 'number' + */ +NumberKeyframeTrack.prototype.ValueTypeName = 'number'; + +/** + * Spherical linear unit quaternion interpolant. + * + * @augments Interpolant + */ +class QuaternionLinearInterpolant extends Interpolant { + + /** + * Constructs a new SLERP interpolant. + * + * @param {TypedArray} parameterPositions - The parameter positions hold the interpolation factors. + * @param {TypedArray} sampleValues - The sample values. + * @param {number} sampleSize - The sample size + * @param {TypedArray} [resultBuffer] - The result buffer. + */ + constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { + + super( parameterPositions, sampleValues, sampleSize, resultBuffer ); + + } + + interpolate_( i1, t0, t, t1 ) { + + const result = this.resultBuffer, + values = this.sampleValues, + stride = this.valueSize, + + alpha = ( t - t0 ) / ( t1 - t0 ); + + let offset = i1 * stride; + + for ( let end = offset + stride; offset !== end; offset += 4 ) { + + Quaternion.slerpFlat( result, 0, values, offset - stride, values, offset, alpha ); + + } + + return result; + + } + +} + +/** + * A track for Quaternion keyframe values. + * + * @augments KeyframeTrack + */ +class QuaternionKeyframeTrack extends KeyframeTrack { + + /** + * Constructs a new Quaternion keyframe track. + * + * @param {string} name - The keyframe track's name. + * @param {Array} times - A list of keyframe times. + * @param {Array} values - A list of keyframe values. + * @param {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth)} [interpolation] - The interpolation type. + */ + constructor( name, times, values, interpolation ) { + + super( name, times, values, interpolation ); + + } + + /** + * Overwritten so the method returns Quaternion based interpolant. + * + * @static + * @param {TypedArray} [result] - The result buffer. + * @return {QuaternionLinearInterpolant} The new interpolant. + */ + InterpolantFactoryMethodLinear( result ) { + + return new QuaternionLinearInterpolant( this.times, this.values, this.getValueSize(), result ); + + } + +} + +/** + * The value type name. + * + * @type {String} + * @default 'quaternion' + */ +QuaternionKeyframeTrack.prototype.ValueTypeName = 'quaternion'; +// ValueBufferType is inherited +// DefaultInterpolation is inherited; +QuaternionKeyframeTrack.prototype.InterpolantFactoryMethodSmooth = undefined; + +/** + * A track for string keyframe values. + * + * @augments KeyframeTrack + */ +class StringKeyframeTrack extends KeyframeTrack { + + /** + * Constructs a new string keyframe track. + * + * This keyframe track type has no `interpolation` parameter because the + * interpolation is always discrete. + * + * @param {string} name - The keyframe track's name. + * @param {Array} times - A list of keyframe times. + * @param {Array} values - A list of keyframe values. + */ + constructor( name, times, values ) { + + super( name, times, values ); + + } + +} + +/** + * The value type name. + * + * @type {String} + * @default 'string' + */ +StringKeyframeTrack.prototype.ValueTypeName = 'string'; + +/** + * The value buffer type of this keyframe track. + * + * @type {TypedArray|Array} + * @default Array.constructor + */ +StringKeyframeTrack.prototype.ValueBufferType = Array; + +/** + * The default interpolation type of this keyframe track. + * + * @type {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth)} + * @default InterpolateDiscrete + */ +StringKeyframeTrack.prototype.DefaultInterpolation = InterpolateDiscrete; +StringKeyframeTrack.prototype.InterpolantFactoryMethodLinear = undefined; +StringKeyframeTrack.prototype.InterpolantFactoryMethodSmooth = undefined; + +/** + * A track for vector keyframe values. + * + * @augments KeyframeTrack + */ +class VectorKeyframeTrack extends KeyframeTrack { + + /** + * Constructs a new vector keyframe track. + * + * @param {string} name - The keyframe track's name. + * @param {Array} times - A list of keyframe times. + * @param {Array} values - A list of keyframe values. + * @param {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth)} [interpolation] - The interpolation type. + */ + constructor( name, times, values, interpolation ) { + + super( name, times, values, interpolation ); + + } + +} + +/** + * The value type name. + * + * @type {String} + * @default 'vector' + */ +VectorKeyframeTrack.prototype.ValueTypeName = 'vector'; + +/** + * A reusable set of keyframe tracks which represent an animation. + */ +class AnimationClip { + + /** + * Constructs a new animation clip. + * + * Note: Instead of instantiating an AnimationClip directly with the constructor, you can + * use the static interface of this class for creating clips. In most cases though, animation clips + * will automatically be created by loaders when importing animated 3D assets. + * + * @param {string} [name=''] - The clip's name. + * @param {number} [duration=-1] - The clip's duration in seconds. If a negative value is passed, + * the duration will be calculated from the passed keyframes. + * @param {Array} tracks - An array of keyframe tracks. + * @param {(NormalAnimationBlendMode|AdditiveAnimationBlendMode)} [blendMode=NormalAnimationBlendMode] - Defines how the animation + * is blended/combined when two or more animations are simultaneously played. + */ + constructor( name = '', duration = -1, tracks = [], blendMode = NormalAnimationBlendMode ) { + + /** + * The clip's name. + * + * @type {string} + */ + this.name = name; + + /** + * An array of keyframe tracks. + * + * @type {Array} + */ + this.tracks = tracks; + + /** + * The clip's duration in seconds. + * + * @type {number} + */ + this.duration = duration; + + /** + * Defines how the animation is blended/combined when two or more animations + * are simultaneously played. + * + * @type {(NormalAnimationBlendMode|AdditiveAnimationBlendMode)} + */ + this.blendMode = blendMode; + + /** + * The UUID of the animation clip. + * + * @type {string} + * @readonly + */ + this.uuid = generateUUID(); + + // this means it should figure out its duration by scanning the tracks + if ( this.duration < 0 ) { + + this.resetDuration(); + + } + + } + + /** + * Factory method for creating an animation clip from the given JSON. + * + * @static + * @param {Object} json - The serialized animation clip. + * @return {AnimationClip} The new animation clip. + */ + static parse( json ) { + + const tracks = [], + jsonTracks = json.tracks, + frameTime = 1.0 / ( json.fps || 1.0 ); + + for ( let i = 0, n = jsonTracks.length; i !== n; ++ i ) { + + tracks.push( parseKeyframeTrack( jsonTracks[ i ] ).scale( frameTime ) ); + + } + + const clip = new this( json.name, json.duration, tracks, json.blendMode ); + clip.uuid = json.uuid; + + return clip; + + } + + /** + * Serializes the given animation clip into JSON. + * + * @static + * @param {AnimationClip} clip - The animation clip to serialize. + * @return {Object} The JSON object. + */ + static toJSON( clip ) { + + const tracks = [], + clipTracks = clip.tracks; + + const json = { + + 'name': clip.name, + 'duration': clip.duration, + 'tracks': tracks, + 'uuid': clip.uuid, + 'blendMode': clip.blendMode + + }; + + for ( let i = 0, n = clipTracks.length; i !== n; ++ i ) { + + tracks.push( KeyframeTrack.toJSON( clipTracks[ i ] ) ); + + } + + return json; + + } + + /** + * Returns a new animation clip from the passed morph targets array of a + * geometry, taking a name and the number of frames per second. + * + * Note: The fps parameter is required, but the animation speed can be + * overridden via {@link AnimationAction#setDuration}. + * + * @static + * @param {string} name - The name of the animation clip. + * @param {Array} morphTargetSequence - A sequence of morph targets. + * @param {number} fps - The Frames-Per-Second value. + * @param {boolean} noLoop - Whether the clip should be no loop or not. + * @return {AnimationClip} The new animation clip. + */ + static CreateFromMorphTargetSequence( name, morphTargetSequence, fps, noLoop ) { + + const numMorphTargets = morphTargetSequence.length; + const tracks = []; + + for ( let i = 0; i < numMorphTargets; i ++ ) { + + let times = []; + let values = []; + + times.push( + ( i + numMorphTargets - 1 ) % numMorphTargets, + i, + ( i + 1 ) % numMorphTargets ); + + values.push( 0, 1, 0 ); + + const order = getKeyframeOrder( times ); + times = sortedArray( times, 1, order ); + values = sortedArray( values, 1, order ); + + // if there is a key at the first frame, duplicate it as the + // last frame as well for perfect loop. + if ( ! noLoop && times[ 0 ] === 0 ) { + + times.push( numMorphTargets ); + values.push( values[ 0 ] ); + + } + + tracks.push( + new NumberKeyframeTrack( + '.morphTargetInfluences[' + morphTargetSequence[ i ].name + ']', + times, values + ).scale( 1.0 / fps ) ); + + } + + return new this( name, -1, tracks ); + + } + + /** + * Searches for an animation clip by name, taking as its first parameter + * either an array of clips, or a mesh or geometry that contains an + * array named "animations" property. + * + * @static + * @param {(Array|Object3D)} objectOrClipArray - The array or object to search through. + * @param {string} name - The name to search for. + * @return {?AnimationClip} The found animation clip. Returns `null` if no clip has been found. + */ + static findByName( objectOrClipArray, name ) { + + let clipArray = objectOrClipArray; + + if ( ! Array.isArray( objectOrClipArray ) ) { + + const o = objectOrClipArray; + clipArray = o.geometry && o.geometry.animations || o.animations; + + } + + for ( let i = 0; i < clipArray.length; i ++ ) { + + if ( clipArray[ i ].name === name ) { + + return clipArray[ i ]; + + } + + } + + return null; + + } + + /** + * Returns an array of new AnimationClips created from the morph target + * sequences of a geometry, trying to sort morph target names into + * animation-group-based patterns like "Walk_001, Walk_002, Run_001, Run_002...". + * + * See {@link MD2Loader#parse} as an example for how the method should be used. + * + * @static + * @param {Array} morphTargets - A sequence of morph targets. + * @param {number} fps - The Frames-Per-Second value. + * @param {boolean} noLoop - Whether the clip should be no loop or not. + * @return {Array} An array of new animation clips. + */ + static CreateClipsFromMorphTargetSequences( morphTargets, fps, noLoop ) { + + const animationToMorphTargets = {}; + + // tested with https://regex101.com/ on trick sequences + // such flamingo_flyA_003, flamingo_run1_003, crdeath0059 + const pattern = /^([\w-]*?)([\d]+)$/; + + // sort morph target names into animation groups based + // patterns like Walk_001, Walk_002, Run_001, Run_002 + for ( let i = 0, il = morphTargets.length; i < il; i ++ ) { + + const morphTarget = morphTargets[ i ]; + const parts = morphTarget.name.match( pattern ); + + if ( parts && parts.length > 1 ) { + + const name = parts[ 1 ]; + + let animationMorphTargets = animationToMorphTargets[ name ]; + + if ( ! animationMorphTargets ) { + + animationToMorphTargets[ name ] = animationMorphTargets = []; + + } + + animationMorphTargets.push( morphTarget ); + + } + + } + + const clips = []; + + for ( const name in animationToMorphTargets ) { + + clips.push( this.CreateFromMorphTargetSequence( name, animationToMorphTargets[ name ], fps, noLoop ) ); + + } + + return clips; + + } + + /** + * Parses the `animation.hierarchy` format and returns a new animation clip. + * + * @static + * @deprecated since r175. + * @param {Object} animation - A serialized animation clip as JSON. + * @param {Array} bones - An array of bones. + * @return {?AnimationClip} The new animation clip. + */ + static parseAnimation( animation, bones ) { + + console.warn( 'THREE.AnimationClip: parseAnimation() is deprecated and will be removed with r185' ); + + if ( ! animation ) { + + console.error( 'THREE.AnimationClip: No animation in JSONLoader data.' ); + return null; + + } + + const addNonemptyTrack = function ( trackType, trackName, animationKeys, propertyName, destTracks ) { + + // only return track if there are actually keys. + if ( animationKeys.length !== 0 ) { + + const times = []; + const values = []; + + flattenJSON( animationKeys, times, values, propertyName ); + + // empty keys are filtered out, so check again + if ( times.length !== 0 ) { + + destTracks.push( new trackType( trackName, times, values ) ); + + } + + } + + }; + + const tracks = []; + + const clipName = animation.name || 'default'; + const fps = animation.fps || 30; + const blendMode = animation.blendMode; + + // automatic length determination in AnimationClip. + let duration = animation.length || -1; + + const hierarchyTracks = animation.hierarchy || []; + + for ( let h = 0; h < hierarchyTracks.length; h ++ ) { + + const animationKeys = hierarchyTracks[ h ].keys; + + // skip empty tracks + if ( ! animationKeys || animationKeys.length === 0 ) continue; + + // process morph targets + if ( animationKeys[ 0 ].morphTargets ) { + + // figure out all morph targets used in this track + const morphTargetNames = {}; + + let k; + + for ( k = 0; k < animationKeys.length; k ++ ) { + + if ( animationKeys[ k ].morphTargets ) { + + for ( let m = 0; m < animationKeys[ k ].morphTargets.length; m ++ ) { + + morphTargetNames[ animationKeys[ k ].morphTargets[ m ] ] = -1; + + } + + } + + } + + // create a track for each morph target with all zero + // morphTargetInfluences except for the keys in which + // the morphTarget is named. + for ( const morphTargetName in morphTargetNames ) { + + const times = []; + const values = []; + + for ( let m = 0; m !== animationKeys[ k ].morphTargets.length; ++ m ) { + + const animationKey = animationKeys[ k ]; + + times.push( animationKey.time ); + values.push( ( animationKey.morphTarget === morphTargetName ) ? 1 : 0 ); + + } + + tracks.push( new NumberKeyframeTrack( '.morphTargetInfluence[' + morphTargetName + ']', times, values ) ); + + } + + duration = morphTargetNames.length * fps; + + } else { + + // ...assume skeletal animation + + const boneName = '.bones[' + bones[ h ].name + ']'; + + addNonemptyTrack( + VectorKeyframeTrack, boneName + '.position', + animationKeys, 'pos', tracks ); + + addNonemptyTrack( + QuaternionKeyframeTrack, boneName + '.quaternion', + animationKeys, 'rot', tracks ); + + addNonemptyTrack( + VectorKeyframeTrack, boneName + '.scale', + animationKeys, 'scl', tracks ); + + } + + } + + if ( tracks.length === 0 ) { + + return null; + + } + + const clip = new this( clipName, duration, tracks, blendMode ); + + return clip; + + } + + /** + * Sets the duration of this clip to the duration of its longest keyframe track. + * + * @return {AnimationClip} A reference to this animation clip. + */ + resetDuration() { + + const tracks = this.tracks; + let duration = 0; + + for ( let i = 0, n = tracks.length; i !== n; ++ i ) { + + const track = this.tracks[ i ]; + + duration = Math.max( duration, track.times[ track.times.length - 1 ] ); + + } + + this.duration = duration; + + return this; + + } + + /** + * Trims all tracks to the clip's duration. + * + * @return {AnimationClip} A reference to this animation clip. + */ + trim() { + + for ( let i = 0; i < this.tracks.length; i ++ ) { + + this.tracks[ i ].trim( 0, this.duration ); + + } + + return this; + + } + + /** + * Performs minimal validation on each track in the clip. Returns `true` if all + * tracks are valid. + * + * @return {boolean} Whether the clip's keyframes are valid or not. + */ + validate() { + + let valid = true; + + for ( let i = 0; i < this.tracks.length; i ++ ) { + + valid = valid && this.tracks[ i ].validate(); + + } + + return valid; + + } + + /** + * Optimizes each track by removing equivalent sequential keys (which are + * common in morph target sequences). + * + * @return {AnimationClip} A reference to this animation clip. + */ + optimize() { + + for ( let i = 0; i < this.tracks.length; i ++ ) { + + this.tracks[ i ].optimize(); + + } + + return this; + + } + + /** + * Returns a new animation clip with copied values from this instance. + * + * @return {AnimationClip} A clone of this instance. + */ + clone() { + + const tracks = []; + + for ( let i = 0; i < this.tracks.length; i ++ ) { + + tracks.push( this.tracks[ i ].clone() ); + + } + + return new this.constructor( this.name, this.duration, tracks, this.blendMode ); + + } + + /** + * Serializes this animation clip into JSON. + * + * @return {Object} The JSON object. + */ + toJSON() { + + return this.constructor.toJSON( this ); + + } + +} + +function getTrackTypeForValueTypeName( typeName ) { + + switch ( typeName.toLowerCase() ) { + + case 'scalar': + case 'double': + case 'float': + case 'number': + case 'integer': + + return NumberKeyframeTrack; + + case 'vector': + case 'vector2': + case 'vector3': + case 'vector4': + + return VectorKeyframeTrack; + + case 'color': + + return ColorKeyframeTrack; + + case 'quaternion': + + return QuaternionKeyframeTrack; + + case 'bool': + case 'boolean': + + return BooleanKeyframeTrack; + + case 'string': + + return StringKeyframeTrack; + + } + + throw new Error( 'THREE.KeyframeTrack: Unsupported typeName: ' + typeName ); + +} + +function parseKeyframeTrack( json ) { + + if ( json.type === undefined ) { + + throw new Error( 'THREE.KeyframeTrack: track type undefined, can not parse' ); + + } + + const trackType = getTrackTypeForValueTypeName( json.type ); + + if ( json.times === undefined ) { + + const times = [], values = []; + + flattenJSON( json.keys, times, values, 'value' ); + + json.times = times; + json.values = values; + + } + + // derived classes can define a static parse method + if ( trackType.parse !== undefined ) { + + return trackType.parse( json ); + + } else { + + // by default, we assume a constructor compatible with the base + return new trackType( json.name, json.times, json.values, json.interpolation ); + + } + +} + +/** + * @class + * @classdesc A simple caching system, used internally by {@link FileLoader}. + * To enable caching across all loaders that use {@link FileLoader}, add `THREE.Cache.enabled = true.` once in your app. + * @hideconstructor + */ +const Cache = { + + /** + * Whether caching is enabled or not. + * + * @static + * @type {boolean} + * @default false + */ + enabled: false, + + /** + * A dictionary that holds cached files. + * + * @static + * @type {Object} + */ + files: {}, + + /** + * Adds a cache entry with a key to reference the file. If this key already + * holds a file, it is overwritten. + * + * @static + * @param {string} key - The key to reference the cached file. + * @param {Object} file - The file to be cached. + */ + add: function ( key, file ) { + + if ( this.enabled === false ) return; + + // console.log( 'THREE.Cache', 'Adding key:', key ); + + this.files[ key ] = file; + + }, + + /** + * Gets the cached value for the given key. + * + * @static + * @param {string} key - The key to reference the cached file. + * @return {Object|undefined} The cached file. If the key does not exist `undefined` is returned. + */ + get: function ( key ) { + + if ( this.enabled === false ) return; + + // console.log( 'THREE.Cache', 'Checking key:', key ); + + return this.files[ key ]; + + }, + + /** + * Removes the cached file associated with the given key. + * + * @static + * @param {string} key - The key to reference the cached file. + */ + remove: function ( key ) { + + delete this.files[ key ]; + + }, + + /** + * Remove all values from the cache. + * + * @static + */ + clear: function () { + + this.files = {}; + + } + +}; + +/** + * Handles and keeps track of loaded and pending data. A default global + * instance of this class is created and used by loaders if not supplied + * manually. + * + * In general that should be sufficient, however there are times when it can + * be useful to have separate loaders - for example if you want to show + * separate loading bars for objects and textures. + * + * ```js + * const manager = new THREE.LoadingManager(); + * manager.onLoad = () => console.log( 'Loading complete!' ); + * + * const loader1 = new OBJLoader( manager ); + * const loader2 = new ColladaLoader( manager ); + * ``` + */ +class LoadingManager { + + /** + * Constructs a new loading manager. + * + * @param {Function} [onLoad] - Executes when all items have been loaded. + * @param {Function} [onProgress] - Executes when single items have been loaded. + * @param {Function} [onError] - Executes when an error occurs. + */ + constructor( onLoad, onProgress, onError ) { + + const scope = this; + + let isLoading = false; + let itemsLoaded = 0; + let itemsTotal = 0; + let urlModifier = undefined; + const handlers = []; + + // Refer to #5689 for the reason why we don't set .onStart + // in the constructor + + /** + * Executes when an item starts loading. + * + * @type {Function|undefined} + * @default undefined + */ + this.onStart = undefined; + + /** + * Executes when all items have been loaded. + * + * @type {Function|undefined} + * @default undefined + */ + this.onLoad = onLoad; + + /** + * Executes when single items have been loaded. + * + * @type {Function|undefined} + * @default undefined + */ + this.onProgress = onProgress; + + /** + * Executes when an error occurs. + * + * @type {Function|undefined} + * @default undefined + */ + this.onError = onError; + + /** + * This should be called by any loader using the manager when the loader + * starts loading an item. + * + * @param {string} url - The URL to load. + */ + this.itemStart = function ( url ) { + + itemsTotal ++; + + if ( isLoading === false ) { + + if ( scope.onStart !== undefined ) { + + scope.onStart( url, itemsLoaded, itemsTotal ); + + } + + } + + isLoading = true; + + }; + + /** + * This should be called by any loader using the manager when the loader + * ended loading an item. + * + * @param {string} url - The URL of the loaded item. + */ + this.itemEnd = function ( url ) { + + itemsLoaded ++; + + if ( scope.onProgress !== undefined ) { + + scope.onProgress( url, itemsLoaded, itemsTotal ); + + } + + if ( itemsLoaded === itemsTotal ) { + + isLoading = false; + + if ( scope.onLoad !== undefined ) { + + scope.onLoad(); + + } + + } + + }; + + /** + * This should be called by any loader using the manager when the loader + * encounters an error when loading an item. + * + * @param {string} url - The URL of the item that produces an error. + */ + this.itemError = function ( url ) { + + if ( scope.onError !== undefined ) { + + scope.onError( url ); + + } + + }; + + /** + * Given a URL, uses the URL modifier callback (if any) and returns a + * resolved URL. If no URL modifier is set, returns the original URL. + * + * @param {string} url - The URL to load. + * @return {string} The resolved URL. + */ + this.resolveURL = function ( url ) { + + if ( urlModifier ) { + + return urlModifier( url ); + + } + + return url; + + }; + + /** + * If provided, the callback will be passed each resource URL before a + * request is sent. The callback may return the original URL, or a new URL to + * override loading behavior. This behavior can be used to load assets from + * .ZIP files, drag-and-drop APIs, and Data URIs. + * + * ```js + * const blobs = {'fish.gltf': blob1, 'diffuse.png': blob2, 'normal.png': blob3}; + * + * const manager = new THREE.LoadingManager(); + * + * // Initialize loading manager with URL callback. + * const objectURLs = []; + * manager.setURLModifier( ( url ) => { + * + * url = URL.createObjectURL( blobs[ url ] ); + * objectURLs.push( url ); + * return url; + * + * } ); + * + * // Load as usual, then revoke the blob URLs. + * const loader = new GLTFLoader( manager ); + * loader.load( 'fish.gltf', (gltf) => { + * + * scene.add( gltf.scene ); + * objectURLs.forEach( ( url ) => URL.revokeObjectURL( url ) ); + * + * } ); + * ``` + * + * @param {function(string):string} transform - URL modifier callback. Called with an URL and must return a resolved URL. + * @return {LoadingManager} A reference to this loading manager. + */ + this.setURLModifier = function ( transform ) { + + urlModifier = transform; + + return this; + + }; + + /** + * Registers a loader with the given regular expression. Can be used to + * define what loader should be used in order to load specific files. A + * typical use case is to overwrite the default loader for textures. + * + * ```js + * // add handler for TGA textures + * manager.addHandler( /\.tga$/i, new TGALoader() ); + * ``` + * + * @param {string} regex - A regular expression. + * @param {Loader} loader - A loader that should handle matched cases. + * @return {LoadingManager} A reference to this loading manager. + */ + this.addHandler = function ( regex, loader ) { + + handlers.push( regex, loader ); + + return this; + + }; + + /** + * Removes the loader for the given regular expression. + * + * @param {string} regex - A regular expression. + * @return {LoadingManager} A reference to this loading manager. + */ + this.removeHandler = function ( regex ) { + + const index = handlers.indexOf( regex ); + + if ( index !== -1 ) { + + handlers.splice( index, 2 ); + + } + + return this; + + }; + + /** + * Can be used to retrieve the registered loader for the given file path. + * + * @param {string} file - The file path. + * @return {?Loader} The registered loader. Returns `null` if no loader was found. + */ + this.getHandler = function ( file ) { + + for ( let i = 0, l = handlers.length; i < l; i += 2 ) { + + const regex = handlers[ i ]; + const loader = handlers[ i + 1 ]; + + if ( regex.global ) regex.lastIndex = 0; // see #17920 + + if ( regex.test( file ) ) { + + return loader; + + } + + } + + return null; + + }; + + } + +} + +/** + * The global default loading manager. + * + * @constant + * @type {LoadingManager} + */ +const DefaultLoadingManager = /*@__PURE__*/ new LoadingManager(); + +/** + * Abstract base class for loaders. + * + * @abstract + */ +class Loader { + + /** + * Constructs a new loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ + constructor( manager ) { + + /** + * The loading manager. + * + * @type {LoadingManager} + * @default DefaultLoadingManager + */ + this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager; + + /** + * The crossOrigin string to implement CORS for loading the url from a + * different domain that allows CORS. + * + * @type {string} + * @default 'anonymous' + */ + this.crossOrigin = 'anonymous'; + + /** + * Whether the XMLHttpRequest uses credentials. + * + * @type {boolean} + * @default false + */ + this.withCredentials = false; + + /** + * The base path from which the asset will be loaded. + * + * @type {string} + */ + this.path = ''; + + /** + * The base path from which additional resources like textures will be loaded. + * + * @type {string} + */ + this.resourcePath = ''; + + /** + * The [request header]{@link https://developer.mozilla.org/en-US/docs/Glossary/Request_header} + * used in HTTP request. + * + * @type {Object} + */ + this.requestHeader = {}; + + } + + /** + * This method needs to be implemented by all concrete loaders. It holds the + * logic for loading assets from the backend. + * + * @param {string} url - The path/URL of the file to be loaded. + * @param {Function} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} [onProgress] - Executed while the loading is in progress. + * @param {onErrorCallback} [onError] - Executed when errors occur. + */ + load( /* url, onLoad, onProgress, onError */ ) {} + + /** + * A async version of {@link Loader#load}. + * + * @param {string} url - The path/URL of the file to be loaded. + * @param {onProgressCallback} [onProgress] - Executed while the loading is in progress. + * @return {Promise} A Promise that resolves when the asset has been loaded. + */ + loadAsync( url, onProgress ) { + + const scope = this; + + return new Promise( function ( resolve, reject ) { + + scope.load( url, resolve, onProgress, reject ); + + } ); + + } + + /** + * This method needs to be implemented by all concrete loaders. It holds the + * logic for parsing the asset into three.js entities. + * + * @param {any} data - The data to parse. + */ + parse( /* data */ ) {} + + /** + * Sets the `crossOrigin` String to implement CORS for loading the URL + * from a different domain that allows CORS. + * + * @param {string} crossOrigin - The `crossOrigin` value. + * @return {Loader} A reference to this instance. + */ + setCrossOrigin( crossOrigin ) { + + this.crossOrigin = crossOrigin; + return this; + + } + + /** + * Whether the XMLHttpRequest uses credentials such as cookies, authorization + * headers or TLS client certificates, see [XMLHttpRequest.withCredentials]{@link https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials}. + * + * Note: This setting has no effect if you are loading files locally or from the same domain. + * + * @param {boolean} value - The `withCredentials` value. + * @return {Loader} A reference to this instance. + */ + setWithCredentials( value ) { + + this.withCredentials = value; + return this; + + } + + /** + * Sets the base path for the asset. + * + * @param {string} path - The base path. + * @return {Loader} A reference to this instance. + */ + setPath( path ) { + + this.path = path; + return this; + + } + + /** + * Sets the base path for dependent resources like textures. + * + * @param {string} resourcePath - The resource path. + * @return {Loader} A reference to this instance. + */ + setResourcePath( resourcePath ) { + + this.resourcePath = resourcePath; + return this; + + } + + /** + * Sets the given request header. + * + * @param {Object} requestHeader - A [request header]{@link https://developer.mozilla.org/en-US/docs/Glossary/Request_header} + * for configuring the HTTP request. + * @return {Loader} A reference to this instance. + */ + setRequestHeader( requestHeader ) { + + this.requestHeader = requestHeader; + return this; + + } + +} + +/** + * Callback for onProgress in loaders. + * + * @callback onProgressCallback + * @param {ProgressEvent} event - An instance of `ProgressEvent` that represents the current loading status. + */ + +/** + * Callback for onError in loaders. + * + * @callback onErrorCallback + * @param {Error} error - The error which occurred during the loading process. + */ + +/** + * The default material name that is used by loaders + * when creating materials for loaded 3D objects. + * + * Note: Not all loaders might honor this setting. + * + * @static + * @type {string} + * @default '__DEFAULT' + */ +Loader.DEFAULT_MATERIAL_NAME = '__DEFAULT'; + +const loading = {}; + +class HttpError extends Error { + + constructor( message, response ) { + + super( message ); + this.response = response; + + } + +} + +/** + * A low level class for loading resources with the Fetch API, used internally by + * most loaders. It can also be used directly to load any file type that does + * not have a loader. + * + * This loader supports caching. If you want to use it, add `THREE.Cache.enabled = true;` + * once to your application. + * + * ```js + * const loader = new THREE.FileLoader(); + * const data = await loader.loadAsync( 'example.txt' ); + * ``` + * + * @augments Loader + */ +class FileLoader extends Loader { + + /** + * Constructs a new file loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ + constructor( manager ) { + + super( manager ); + + /** + * The expected mime type. + * + * @type {string} + */ + this.mimeType = ''; + + /** + * The expected response type. + * + * @type {('arraybuffer'|'blob'|'document'|'json'|'')} + * @default '' + */ + this.responseType = ''; + + } + + /** + * Starts loading from the given URL and pass the loaded response to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(any)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} [onProgress] - Executed while the loading is in progress. + * @param {onErrorCallback} [onError] - Executed when errors occur. + * @return {any|undefined} The cached resource if available. + */ + load( url, onLoad, onProgress, onError ) { + + if ( url === undefined ) url = ''; + + if ( this.path !== undefined ) url = this.path + url; + + url = this.manager.resolveURL( url ); + + const cached = Cache.get( `file:${url}` ); + + if ( cached !== undefined ) { + + this.manager.itemStart( url ); + + setTimeout( () => { + + if ( onLoad ) onLoad( cached ); + + this.manager.itemEnd( url ); + + }, 0 ); + + return cached; + + } + + // Check if request is duplicate + + if ( loading[ url ] !== undefined ) { + + loading[ url ].push( { + + onLoad: onLoad, + onProgress: onProgress, + onError: onError + + } ); + + return; + + } + + // Initialise array for duplicate requests + loading[ url ] = []; + + loading[ url ].push( { + onLoad: onLoad, + onProgress: onProgress, + onError: onError, + } ); + + // create request + const req = new Request( url, { + headers: new Headers( this.requestHeader ), + credentials: this.withCredentials ? 'include' : 'same-origin', + // An abort controller could be added within a future PR + } ); + + // record states ( avoid data race ) + const mimeType = this.mimeType; + const responseType = this.responseType; + + // start the fetch + fetch( req ) + .then( response => { + + if ( response.status === 200 || response.status === 0 ) { + + // Some browsers return HTTP Status 0 when using non-http protocol + // e.g. 'file://' or 'data://'. Handle as success. + + if ( response.status === 0 ) { + + console.warn( 'THREE.FileLoader: HTTP Status 0 received.' ); + + } + + // Workaround: Checking if response.body === undefined for Alipay browser #23548 + + if ( typeof ReadableStream === 'undefined' || response.body === undefined || response.body.getReader === undefined ) { + + return response; + + } + + const callbacks = loading[ url ]; + const reader = response.body.getReader(); + + // Nginx needs X-File-Size check + // https://serverfault.com/questions/482875/why-does-nginx-remove-content-length-header-for-chunked-content + const contentLength = response.headers.get( 'X-File-Size' ) || response.headers.get( 'Content-Length' ); + const total = contentLength ? parseInt( contentLength ) : 0; + const lengthComputable = total !== 0; + let loaded = 0; + + // periodically read data into the new stream tracking while download progress + const stream = new ReadableStream( { + start( controller ) { + + readData(); + + function readData() { + + reader.read().then( ( { done, value } ) => { + + if ( done ) { + + controller.close(); + + } else { + + loaded += value.byteLength; + + const event = new ProgressEvent( 'progress', { lengthComputable, loaded, total } ); + for ( let i = 0, il = callbacks.length; i < il; i ++ ) { + + const callback = callbacks[ i ]; + if ( callback.onProgress ) callback.onProgress( event ); + + } + + controller.enqueue( value ); + readData(); + + } + + }, ( e ) => { + + controller.error( e ); + + } ); + + } + + } + + } ); + + return new Response( stream ); + + } else { + + throw new HttpError( `fetch for "${response.url}" responded with ${response.status}: ${response.statusText}`, response ); + + } + + } ) + .then( response => { + + switch ( responseType ) { + + case 'arraybuffer': + + return response.arrayBuffer(); + + case 'blob': + + return response.blob(); + + case 'document': + + return response.text() + .then( text => { + + const parser = new DOMParser(); + return parser.parseFromString( text, mimeType ); + + } ); + + case 'json': + + return response.json(); + + default: + + if ( mimeType === '' ) { + + return response.text(); + + } else { + + // sniff encoding + const re = /charset="?([^;"\s]*)"?/i; + const exec = re.exec( mimeType ); + const label = exec && exec[ 1 ] ? exec[ 1 ].toLowerCase() : undefined; + const decoder = new TextDecoder( label ); + return response.arrayBuffer().then( ab => decoder.decode( ab ) ); + + } + + } + + } ) + .then( data => { + + // Add to cache only on HTTP success, so that we do not cache + // error response bodies as proper responses to requests. + Cache.add( `file:${url}`, data ); + + const callbacks = loading[ url ]; + delete loading[ url ]; + + for ( let i = 0, il = callbacks.length; i < il; i ++ ) { + + const callback = callbacks[ i ]; + if ( callback.onLoad ) callback.onLoad( data ); + + } + + } ) + .catch( err => { + + // Abort errors and other errors are handled the same + + const callbacks = loading[ url ]; + + if ( callbacks === undefined ) { + + // When onLoad was called and url was deleted in `loading` + this.manager.itemError( url ); + throw err; + + } + + delete loading[ url ]; + + for ( let i = 0, il = callbacks.length; i < il; i ++ ) { + + const callback = callbacks[ i ]; + if ( callback.onError ) callback.onError( err ); + + } + + this.manager.itemError( url ); + + } ) + .finally( () => { + + this.manager.itemEnd( url ); + + } ); + + this.manager.itemStart( url ); + + } + + /** + * Sets the expected response type. + * + * @param {('arraybuffer'|'blob'|'document'|'json'|'')} value - The response type. + * @return {FileLoader} A reference to this file loader. + */ + setResponseType( value ) { + + this.responseType = value; + return this; + + } + + /** + * Sets the expected mime type of the loaded file. + * + * @param {string} value - The mime type. + * @return {FileLoader} A reference to this file loader. + */ + setMimeType( value ) { + + this.mimeType = value; + return this; + + } + +} + +/** + * Class for loading animation clips in the JSON format. The files are internally + * loaded via {@link FileLoader}. + * + * ```js + * const loader = new THREE.AnimationLoader(); + * const animations = await loader.loadAsync( 'animations/animation.js' ); + * ``` + * + * @augments Loader + */ +class AnimationLoader extends Loader { + + /** + * Constructs a new animation loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ + constructor( manager ) { + + super( manager ); + + } + + /** + * Starts loading from the given URL and pass the loaded animations as an array + * holding instances of {@link AnimationClip} to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(Array)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const loader = new FileLoader( this.manager ); + loader.setPath( this.path ); + loader.setRequestHeader( this.requestHeader ); + loader.setWithCredentials( this.withCredentials ); + loader.load( url, function ( text ) { + + try { + + onLoad( scope.parse( JSON.parse( text ) ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + /** + * Parses the given JSON object and returns an array of animation clips. + * + * @param {Object} json - The serialized animation clips. + * @return {Array} The parsed animation clips. + */ + parse( json ) { + + const animations = []; + + for ( let i = 0; i < json.length; i ++ ) { + + const clip = AnimationClip.parse( json[ i ] ); + + animations.push( clip ); + + } + + return animations; + + } + +} + +/** + * Abstract base class for loading compressed texture formats S3TC, ASTC or ETC. + * Textures are internally loaded via {@link FileLoader}. + * + * Derived classes have to implement the `parse()` method which holds the parsing + * for the respective format. + * + * @abstract + * @augments Loader + */ +class CompressedTextureLoader extends Loader { + + /** + * Constructs a new compressed texture loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ + constructor( manager ) { + + super( manager ); + + } + + /** + * Starts loading from the given URL and passes the loaded compressed texture + * to the `onLoad()` callback. The method also returns a new texture object which can + * directly be used for material creation. If you do it this way, the texture + * may pop up in your scene once the respective loading process is finished. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(CompressedTexture)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + * @return {CompressedTexture} The compressed texture. + */ + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const images = []; + + const texture = new CompressedTexture(); + + const loader = new FileLoader( this.manager ); + loader.setPath( this.path ); + loader.setResponseType( 'arraybuffer' ); + loader.setRequestHeader( this.requestHeader ); + loader.setWithCredentials( scope.withCredentials ); + + let loaded = 0; + + function loadTexture( i ) { + + loader.load( url[ i ], function ( buffer ) { + + const texDatas = scope.parse( buffer, true ); + + images[ i ] = { + width: texDatas.width, + height: texDatas.height, + format: texDatas.format, + mipmaps: texDatas.mipmaps + }; + + loaded += 1; + + if ( loaded === 6 ) { + + if ( texDatas.mipmapCount === 1 ) texture.minFilter = LinearFilter; + + texture.image = images; + texture.format = texDatas.format; + texture.needsUpdate = true; + + if ( onLoad ) onLoad( texture ); + + } + + }, onProgress, onError ); + + } + + if ( Array.isArray( url ) ) { + + for ( let i = 0, il = url.length; i < il; ++ i ) { + + loadTexture( i ); + + } + + } else { + + // compressed cubemap texture stored in a single DDS file + + loader.load( url, function ( buffer ) { + + const texDatas = scope.parse( buffer, true ); + + if ( texDatas.isCubemap ) { + + const faces = texDatas.mipmaps.length / texDatas.mipmapCount; + + for ( let f = 0; f < faces; f ++ ) { + + images[ f ] = { mipmaps: [] }; + + for ( let i = 0; i < texDatas.mipmapCount; i ++ ) { + + images[ f ].mipmaps.push( texDatas.mipmaps[ f * texDatas.mipmapCount + i ] ); + images[ f ].format = texDatas.format; + images[ f ].width = texDatas.width; + images[ f ].height = texDatas.height; + + } + + } + + texture.image = images; + + } else { + + texture.image.width = texDatas.width; + texture.image.height = texDatas.height; + texture.mipmaps = texDatas.mipmaps; + + } + + if ( texDatas.mipmapCount === 1 ) { + + texture.minFilter = LinearFilter; + + } + + texture.format = texDatas.format; + texture.needsUpdate = true; + + if ( onLoad ) onLoad( texture ); + + }, onProgress, onError ); + + } + + return texture; + + } + +} + +const _loading = new WeakMap(); + +/** + * A loader for loading images. The class loads images with the HTML `Image` API. + * + * ```js + * const loader = new THREE.ImageLoader(); + * const image = await loader.loadAsync( 'image.png' ); + * ``` + * Please note that `ImageLoader` has dropped support for progress + * events in `r84`. For an `ImageLoader` that supports progress events, see + * [this thread]{@link https://github.com/mrdoob/three.js/issues/10439#issuecomment-275785639}. + * + * @augments Loader + */ +class ImageLoader extends Loader { + + /** + * Constructs a new image loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ + constructor( manager ) { + + super( manager ); + + } + + /** + * Starts loading from the given URL and passes the loaded image + * to the `onLoad()` callback. The method also returns a new `Image` object which can + * directly be used for texture creation. If you do it this way, the texture + * may pop up in your scene once the respective loading process is finished. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(Image)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Unsupported in this loader. + * @param {onErrorCallback} onError - Executed when errors occur. + * @return {Image} The image. + */ + load( url, onLoad, onProgress, onError ) { + + if ( this.path !== undefined ) url = this.path + url; + + url = this.manager.resolveURL( url ); + + const scope = this; + + const cached = Cache.get( `image:${url}` ); + + if ( cached !== undefined ) { + + if ( cached.complete === true ) { + + scope.manager.itemStart( url ); + + setTimeout( function () { + + if ( onLoad ) onLoad( cached ); + + scope.manager.itemEnd( url ); + + }, 0 ); + + } else { + + let arr = _loading.get( cached ); + + if ( arr === undefined ) { + + arr = []; + _loading.set( cached, arr ); + + } + + arr.push( { onLoad, onError } ); + + } + + return cached; + + } + + const image = createElementNS( 'img' ); + + function onImageLoad() { + + removeEventListeners(); + + if ( onLoad ) onLoad( this ); + + // + + const callbacks = _loading.get( this ) || []; + + for ( let i = 0; i < callbacks.length; i ++ ) { + + const callback = callbacks[ i ]; + if ( callback.onLoad ) callback.onLoad( this ); + + } + + _loading.delete( this ); + + scope.manager.itemEnd( url ); + + } + + function onImageError( event ) { + + removeEventListeners(); + + if ( onError ) onError( event ); + + Cache.remove( `image:${url}` ); + + // + + const callbacks = _loading.get( this ) || []; + + for ( let i = 0; i < callbacks.length; i ++ ) { + + const callback = callbacks[ i ]; + if ( callback.onError ) callback.onError( event ); + + } + + _loading.delete( this ); + + + scope.manager.itemError( url ); + scope.manager.itemEnd( url ); + + } + + function removeEventListeners() { + + image.removeEventListener( 'load', onImageLoad, false ); + image.removeEventListener( 'error', onImageError, false ); + + } + + image.addEventListener( 'load', onImageLoad, false ); + image.addEventListener( 'error', onImageError, false ); + + if ( url.slice( 0, 5 ) !== 'data:' ) { + + if ( this.crossOrigin !== undefined ) image.crossOrigin = this.crossOrigin; + + } + + Cache.add( `image:${url}`, image ); + scope.manager.itemStart( url ); + + image.src = url; + + return image; + + } + +} + +/** + * Class for loading cube textures. Images are internally loaded via {@link ImageLoader}. + * + * The loader returns an instance of {@link CubeTexture} and expects the cube map to + * be defined as six separate images representing the sides of a cube. Other cube map definitions + * like vertical and horizontal cross, column and row layouts are not supported. + * + * Note that, by convention, cube maps are specified in a coordinate system + * in which positive-x is to the right when looking up the positive-z axis -- + * in other words, using a left-handed coordinate system. Since three.js uses + * a right-handed coordinate system, environment maps used in three.js will + * have pos-x and neg-x swapped. + * + * The loaded cube texture is in sRGB color space. Meaning {@link Texture#colorSpace} + * is set to `SRGBColorSpace` by default. + * + * ```js + * const loader = new THREE.CubeTextureLoader().setPath( 'textures/cubeMaps/' ); + * const cubeTexture = await loader.loadAsync( [ + * 'px.png', 'nx.png', 'py.png', 'ny.png', 'pz.png', 'nz.png' + * ] ); + * scene.background = cubeTexture; + * ``` + * + * @augments Loader + */ +class CubeTextureLoader extends Loader { + + /** + * Constructs a new cube texture loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ + constructor( manager ) { + + super( manager ); + + } + + /** + * Starts loading from the given URL and pass the fully loaded cube texture + * to the `onLoad()` callback. The method also returns a new cube texture object which can + * directly be used for material creation. If you do it this way, the cube texture + * may pop up in your scene once the respective loading process is finished. + * + * @param {Array} urls - Array of 6 URLs to images, one for each side of the + * cube texture. The urls should be specified in the following order: pos-x, + * neg-x, pos-y, neg-y, pos-z, neg-z. An array of data URIs are allowed as well. + * @param {function(CubeTexture)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Unsupported in this loader. + * @param {onErrorCallback} onError - Executed when errors occur. + * @return {CubeTexture} The cube texture. + */ + load( urls, onLoad, onProgress, onError ) { + + const texture = new CubeTexture(); + texture.colorSpace = SRGBColorSpace; + + const loader = new ImageLoader( this.manager ); + loader.setCrossOrigin( this.crossOrigin ); + loader.setPath( this.path ); + + let loaded = 0; + + function loadTexture( i ) { + + loader.load( urls[ i ], function ( image ) { + + texture.images[ i ] = image; + + loaded ++; + + if ( loaded === 6 ) { + + texture.needsUpdate = true; + + if ( onLoad ) onLoad( texture ); + + } + + }, undefined, onError ); + + } + + for ( let i = 0; i < urls.length; ++ i ) { + + loadTexture( i ); + + } + + return texture; + + } + +} + +/** + * Abstract base class for loading binary texture formats RGBE, EXR or TGA. + * Textures are internally loaded via {@link FileLoader}. + * + * Derived classes have to implement the `parse()` method which holds the parsing + * for the respective format. + * + * @abstract + * @augments Loader + */ +class DataTextureLoader extends Loader { + + /** + * Constructs a new data texture loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ + constructor( manager ) { + + super( manager ); + + } + + /** + * Starts loading from the given URL and passes the loaded data texture + * to the `onLoad()` callback. The method also returns a new texture object which can + * directly be used for material creation. If you do it this way, the texture + * may pop up in your scene once the respective loading process is finished. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(DataTexture)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + * @return {DataTexture} The data texture. + */ + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const texture = new DataTexture(); + + const loader = new FileLoader( this.manager ); + loader.setResponseType( 'arraybuffer' ); + loader.setRequestHeader( this.requestHeader ); + loader.setPath( this.path ); + loader.setWithCredentials( scope.withCredentials ); + loader.load( url, function ( buffer ) { + + let texData; + + try { + + texData = scope.parse( buffer ); + + } catch ( error ) { + + if ( onError !== undefined ) { + + onError( error ); + + } else { + + console.error( error ); + return; + + } + + } + + if ( texData.image !== undefined ) { + + texture.image = texData.image; + + } else if ( texData.data !== undefined ) { + + texture.image.width = texData.width; + texture.image.height = texData.height; + texture.image.data = texData.data; + + } + + texture.wrapS = texData.wrapS !== undefined ? texData.wrapS : ClampToEdgeWrapping; + texture.wrapT = texData.wrapT !== undefined ? texData.wrapT : ClampToEdgeWrapping; + + texture.magFilter = texData.magFilter !== undefined ? texData.magFilter : LinearFilter; + texture.minFilter = texData.minFilter !== undefined ? texData.minFilter : LinearFilter; + + texture.anisotropy = texData.anisotropy !== undefined ? texData.anisotropy : 1; + + if ( texData.colorSpace !== undefined ) { + + texture.colorSpace = texData.colorSpace; + + } + + if ( texData.flipY !== undefined ) { + + texture.flipY = texData.flipY; + + } + + if ( texData.format !== undefined ) { + + texture.format = texData.format; + + } + + if ( texData.type !== undefined ) { + + texture.type = texData.type; + + } + + if ( texData.mipmaps !== undefined ) { + + texture.mipmaps = texData.mipmaps; + texture.minFilter = LinearMipmapLinearFilter; // presumably... + + } + + if ( texData.mipmapCount === 1 ) { + + texture.minFilter = LinearFilter; + + } + + if ( texData.generateMipmaps !== undefined ) { + + texture.generateMipmaps = texData.generateMipmaps; + + } + + texture.needsUpdate = true; + + if ( onLoad ) onLoad( texture, texData ); + + }, onProgress, onError ); + + + return texture; + + } + +} + +/** + * Class for loading textures. Images are internally + * loaded via {@link ImageLoader}. + * + * ```js + * const loader = new THREE.TextureLoader(); + * const texture = await loader.loadAsync( 'textures/land_ocean_ice_cloud_2048.jpg' ); + * + * const material = new THREE.MeshBasicMaterial( { map:texture } ); + * ``` + * Please note that `TextureLoader` has dropped support for progress + * events in `r84`. For a `TextureLoader` that supports progress events, see + * [this thread]{@link https://github.com/mrdoob/three.js/issues/10439#issuecomment-293260145}. + * + * @augments Loader + */ +class TextureLoader extends Loader { + + /** + * Constructs a new texture loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ + constructor( manager ) { + + super( manager ); + + } + + /** + * Starts loading from the given URL and pass the fully loaded texture + * to the `onLoad()` callback. The method also returns a new texture object which can + * directly be used for material creation. If you do it this way, the texture + * may pop up in your scene once the respective loading process is finished. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(Texture)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Unsupported in this loader. + * @param {onErrorCallback} onError - Executed when errors occur. + * @return {Texture} The texture. + */ + load( url, onLoad, onProgress, onError ) { + + const texture = new Texture(); + + const loader = new ImageLoader( this.manager ); + loader.setCrossOrigin( this.crossOrigin ); + loader.setPath( this.path ); + + loader.load( url, function ( image ) { + + texture.image = image; + texture.needsUpdate = true; + + if ( onLoad !== undefined ) { + + onLoad( texture ); + + } + + }, onProgress, onError ); + + return texture; + + } + +} + +/** + * Abstract base class for lights - all other light types inherit the + * properties and methods described here. + * + * @abstract + * @augments Object3D + */ +class Light extends Object3D { + + /** + * Constructs a new light. + * + * @param {(number|Color|string)} [color=0xffffff] - The light's color. + * @param {number} [intensity=1] - The light's strength/intensity. + */ + constructor( color, intensity = 1 ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLight = true; + + this.type = 'Light'; + + /** + * The light's color. + * + * @type {Color} + */ + this.color = new Color( color ); + + /** + * The light's intensity. + * + * @type {number} + * @default 1 + */ + this.intensity = intensity; + + } + + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose() { + + // Empty here in base class; some subclasses override. + + } + + copy( source, recursive ) { + + super.copy( source, recursive ); + + this.color.copy( source.color ); + this.intensity = source.intensity; + + return this; + + } + + toJSON( meta ) { + + const data = super.toJSON( meta ); + + data.object.color = this.color.getHex(); + data.object.intensity = this.intensity; + + if ( this.groundColor !== undefined ) data.object.groundColor = this.groundColor.getHex(); + + if ( this.distance !== undefined ) data.object.distance = this.distance; + if ( this.angle !== undefined ) data.object.angle = this.angle; + if ( this.decay !== undefined ) data.object.decay = this.decay; + if ( this.penumbra !== undefined ) data.object.penumbra = this.penumbra; + + if ( this.shadow !== undefined ) data.object.shadow = this.shadow.toJSON(); + if ( this.target !== undefined ) data.object.target = this.target.uuid; + + return data; + + } + +} + +/** + * A light source positioned directly above the scene, with color fading from + * the sky color to the ground color. + * + * This light cannot be used to cast shadows. + * + * ```js + * const light = new THREE.HemisphereLight( 0xffffbb, 0x080820, 1 ); + * scene.add( light ); + * ``` + * + * @augments Light + */ +class HemisphereLight extends Light { + + /** + * Constructs a new hemisphere light. + * + * @param {(number|Color|string)} [skyColor=0xffffff] - The light's sky color. + * @param {(number|Color|string)} [groundColor=0xffffff] - The light's ground color. + * @param {number} [intensity=1] - The light's strength/intensity. + */ + constructor( skyColor, groundColor, intensity ) { + + super( skyColor, intensity ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isHemisphereLight = true; + + this.type = 'HemisphereLight'; + + this.position.copy( Object3D.DEFAULT_UP ); + this.updateMatrix(); + + /** + * The light's ground color. + * + * @type {Color} + */ + this.groundColor = new Color( groundColor ); + + } + + copy( source, recursive ) { + + super.copy( source, recursive ); + + this.groundColor.copy( source.groundColor ); + + return this; + + } + +} + +const _projScreenMatrix$1 = /*@__PURE__*/ new Matrix4(); +const _lightPositionWorld$1 = /*@__PURE__*/ new Vector3(); +const _lookTarget$1 = /*@__PURE__*/ new Vector3(); + +/** + * Abstract base class for light shadow classes. These classes + * represent the shadow configuration for different light types. + * + * @abstract + */ +class LightShadow { + + /** + * Constructs a new light shadow. + * + * @param {Camera} camera - The light's view of the world. + */ + constructor( camera ) { + + /** + * The light's view of the world. + * + * @type {Camera} + */ + this.camera = camera; + + /** + * The intensity of the shadow. The default is `1`. + * Valid values are in the range `[0, 1]`. + * + * @type {number} + * @default 1 + */ + this.intensity = 1; + + /** + * Shadow map bias, how much to add or subtract from the normalized depth + * when deciding whether a surface is in shadow. + * + * The default is `0`. Very tiny adjustments here (in the order of `0.0001`) + * may help reduce artifacts in shadows. + * + * @type {number} + * @default 0 + */ + this.bias = 0; + + /** + * Defines how much the position used to query the shadow map is offset along + * the object normal. The default is `0`. Increasing this value can be used to + * reduce shadow acne especially in large scenes where light shines onto + * geometry at a shallow angle. The cost is that shadows may appear distorted. + * + * @type {number} + * @default 0 + */ + this.normalBias = 0; + + /** + * Setting this to values greater than 1 will blur the edges of the shadow. + * High values will cause unwanted banding effects in the shadows - a greater + * map size will allow for a higher value to be used here before these effects + * become visible. + * + * The property has no effect when the shadow map type is `PCFSoftShadowMap` and + * and it is recommended to increase softness by decreasing the shadow map size instead. + * + * The property has no effect when the shadow map type is `BasicShadowMap`. + * + * @type {number} + * @default 1 + */ + this.radius = 1; + + /** + * The amount of samples to use when blurring a VSM shadow map. + * + * @type {number} + * @default 8 + */ + this.blurSamples = 8; + + /** + * Defines the width and height of the shadow map. Higher values give better quality + * shadows at the cost of computation time. Values must be powers of two. + * + * @type {Vector2} + * @default (512,512) + */ + this.mapSize = new Vector2( 512, 512 ); + + /** + * The type of shadow texture. The default is `UnsignedByteType`. + * + * @type {number} + * @default UnsignedByteType + */ + this.mapType = UnsignedByteType; + + /** + * The depth map generated using the internal camera; a location beyond a + * pixel's depth is in shadow. Computed internally during rendering. + * + * @type {?RenderTarget} + * @default null + */ + this.map = null; + + /** + * The distribution map generated using the internal camera; an occlusion is + * calculated based on the distribution of depths. Computed internally during + * rendering. + * + * @type {?RenderTarget} + * @default null + */ + this.mapPass = null; + + /** + * Model to shadow camera space, to compute location and depth in shadow map. + * This is computed internally during rendering. + * + * @type {Matrix4} + */ + this.matrix = new Matrix4(); + + /** + * Enables automatic updates of the light's shadow. If you do not require dynamic + * lighting / shadows, you may set this to `false`. + * + * @type {boolean} + * @default true + */ + this.autoUpdate = true; + + /** + * When set to `true`, shadow maps will be updated in the next `render` call. + * If you have set {@link LightShadow#autoUpdate} to `false`, you will need to + * set this property to `true` and then make a render call to update the light's shadow. + * + * @type {boolean} + * @default false + */ + this.needsUpdate = false; + + this._frustum = new Frustum(); + this._frameExtents = new Vector2( 1, 1 ); + + this._viewportCount = 1; + + this._viewports = [ + + new Vector4( 0, 0, 1, 1 ) + + ]; + + } + + /** + * Used internally by the renderer to get the number of viewports that need + * to be rendered for this shadow. + * + * @return {number} The viewport count. + */ + getViewportCount() { + + return this._viewportCount; + + } + + /** + * Gets the shadow cameras frustum. Used internally by the renderer to cull objects. + * + * @return {Frustum} The shadow camera frustum. + */ + getFrustum() { + + return this._frustum; + + } + + /** + * Update the matrices for the camera and shadow, used internally by the renderer. + * + * @param {Light} light - The light for which the shadow is being rendered. + */ + updateMatrices( light ) { + + const shadowCamera = this.camera; + const shadowMatrix = this.matrix; + + _lightPositionWorld$1.setFromMatrixPosition( light.matrixWorld ); + shadowCamera.position.copy( _lightPositionWorld$1 ); + + _lookTarget$1.setFromMatrixPosition( light.target.matrixWorld ); + shadowCamera.lookAt( _lookTarget$1 ); + shadowCamera.updateMatrixWorld(); + + _projScreenMatrix$1.multiplyMatrices( shadowCamera.projectionMatrix, shadowCamera.matrixWorldInverse ); + this._frustum.setFromProjectionMatrix( _projScreenMatrix$1 ); + + shadowMatrix.set( + 0.5, 0.0, 0.0, 0.5, + 0.0, 0.5, 0.0, 0.5, + 0.0, 0.0, 0.5, 0.5, + 0.0, 0.0, 0.0, 1.0 + ); + + shadowMatrix.multiply( _projScreenMatrix$1 ); + + } + + /** + * Returns a viewport definition for the given viewport index. + * + * @param {number} viewportIndex - The viewport index. + * @return {Vector4} The viewport. + */ + getViewport( viewportIndex ) { + + return this._viewports[ viewportIndex ]; + + } + + /** + * Returns the frame extends. + * + * @return {Vector2} The frame extends. + */ + getFrameExtents() { + + return this._frameExtents; + + } + + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose() { + + if ( this.map ) { + + this.map.dispose(); + + } + + if ( this.mapPass ) { + + this.mapPass.dispose(); + + } + + } + + /** + * Copies the values of the given light shadow instance to this instance. + * + * @param {LightShadow} source - The light shadow to copy. + * @return {LightShadow} A reference to this light shadow instance. + */ + copy( source ) { + + this.camera = source.camera.clone(); + + this.intensity = source.intensity; + + this.bias = source.bias; + this.radius = source.radius; + + this.autoUpdate = source.autoUpdate; + this.needsUpdate = source.needsUpdate; + this.normalBias = source.normalBias; + this.blurSamples = source.blurSamples; + + this.mapSize.copy( source.mapSize ); + + return this; + + } + + /** + * Returns a new light shadow instance with copied values from this instance. + * + * @return {LightShadow} A clone of this instance. + */ + clone() { + + return new this.constructor().copy( this ); + + } + + /** + * Serializes the light shadow into JSON. + * + * @return {Object} A JSON object representing the serialized light shadow. + * @see {@link ObjectLoader#parse} + */ + toJSON() { + + const object = {}; + + if ( this.intensity !== 1 ) object.intensity = this.intensity; + if ( this.bias !== 0 ) object.bias = this.bias; + if ( this.normalBias !== 0 ) object.normalBias = this.normalBias; + if ( this.radius !== 1 ) object.radius = this.radius; + if ( this.mapSize.x !== 512 || this.mapSize.y !== 512 ) object.mapSize = this.mapSize.toArray(); + + object.camera = this.camera.toJSON( false ).object; + delete object.camera.matrix; + + return object; + + } + +} + +/** + * Represents the shadow configuration of directional lights. + * + * @augments LightShadow + */ +class SpotLightShadow extends LightShadow { + + /** + * Constructs a new spot light shadow. + */ + constructor() { + + super( new PerspectiveCamera( 50, 1, 0.5, 500 ) ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSpotLightShadow = true; + + /** + * Used to focus the shadow camera. The camera's field of view is set as a + * percentage of the spotlight's field-of-view. Range is `[0, 1]`. + * + * @type {number} + * @default 1 + */ + this.focus = 1; + + /** + * Texture aspect ratio. + * + * @type {number} + * @default 1 + */ + this.aspect = 1; + + } + + updateMatrices( light ) { + + const camera = this.camera; + + const fov = RAD2DEG * 2 * light.angle * this.focus; + const aspect = ( this.mapSize.width / this.mapSize.height ) * this.aspect; + const far = light.distance || camera.far; + + if ( fov !== camera.fov || aspect !== camera.aspect || far !== camera.far ) { + + camera.fov = fov; + camera.aspect = aspect; + camera.far = far; + camera.updateProjectionMatrix(); + + } + + super.updateMatrices( light ); + + } + + copy( source ) { + + super.copy( source ); + + this.focus = source.focus; + + return this; + + } + +} + +/** + * This light gets emitted from a single point in one direction, along a cone + * that increases in size the further from the light it gets. + * + * This light can cast shadows - see the {@link SpotLightShadow} for details. + * + * ```js + * // white spotlight shining from the side, modulated by a texture + * const spotLight = new THREE.SpotLight( 0xffffff ); + * spotLight.position.set( 100, 1000, 100 ); + * spotLight.map = new THREE.TextureLoader().load( url ); + * + * spotLight.castShadow = true; + * spotLight.shadow.mapSize.width = 1024; + * spotLight.shadow.mapSize.height = 1024; + * spotLight.shadow.camera.near = 500; + * spotLight.shadow.camera.far = 4000; + * spotLight.shadow.camera.fov = 30;s + * ``` + * + * @augments Light + */ +class SpotLight extends Light { + + /** + * Constructs a new spot light. + * + * @param {(number|Color|string)} [color=0xffffff] - The light's color. + * @param {number} [intensity=1] - The light's strength/intensity measured in candela (cd). + * @param {number} [distance=0] - Maximum range of the light. `0` means no limit. + * @param {number} [angle=Math.PI/3] - Maximum angle of light dispersion from its direction whose upper bound is `Math.PI/2`. + * @param {number} [penumbra=0] - Percent of the spotlight cone that is attenuated due to penumbra. Value range is `[0,1]`. + * @param {number} [decay=2] - The amount the light dims along the distance of the light. + */ + constructor( color, intensity, distance = 0, angle = Math.PI / 3, penumbra = 0, decay = 2 ) { + + super( color, intensity ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSpotLight = true; + + this.type = 'SpotLight'; + + this.position.copy( Object3D.DEFAULT_UP ); + this.updateMatrix(); + + /** + * The spot light points from its position to the + * target's position. + * + * For the target's position to be changed to anything other + * than the default, it must be added to the scene. + * + * It is also possible to set the target to be another 3D object + * in the scene. The light will now track the target object. + * + * @type {Object3D} + */ + this.target = new Object3D(); + + /** + * Maximum range of the light. `0` means no limit. + * + * @type {number} + * @default 0 + */ + this.distance = distance; + + /** + * Maximum angle of light dispersion from its direction whose upper bound is `Math.PI/2`. + * + * @type {number} + * @default Math.PI/3 + */ + this.angle = angle; + + /** + * Percent of the spotlight cone that is attenuated due to penumbra. + * Value range is `[0,1]`. + * + * @type {number} + * @default 0 + */ + this.penumbra = penumbra; + + /** + * The amount the light dims along the distance of the light. In context of + * physically-correct rendering the default value should not be changed. + * + * @type {number} + * @default 2 + */ + this.decay = decay; + + /** + * A texture used to modulate the color of the light. The spot light + * color is mixed with the RGB value of this texture, with a ratio + * corresponding to its alpha value. The cookie-like masking effect is + * reproduced using pixel values (0, 0, 0, 1-cookie_value). + * + * *Warning*: This property is disabled if {@link Object3D#castShadow} is set to `false`. + * + * @type {?Texture} + * @default null + */ + this.map = null; + + /** + * This property holds the light's shadow configuration. + * + * @type {SpotLightShadow} + */ + this.shadow = new SpotLightShadow(); + + } + + /** + * The light's power. Power is the luminous power of the light measured in lumens (lm). + * Changing the power will also change the light's intensity. + * + * @type {number} + */ + get power() { + + // compute the light's luminous power (in lumens) from its intensity (in candela) + // by convention for a spotlight, luminous power (lm) = π * luminous intensity (cd) + return this.intensity * Math.PI; + + } + + set power( power ) { + + // set the light's intensity (in candela) from the desired luminous power (in lumens) + this.intensity = power / Math.PI; + + } + + dispose() { + + this.shadow.dispose(); + + } + + copy( source, recursive ) { + + super.copy( source, recursive ); + + this.distance = source.distance; + this.angle = source.angle; + this.penumbra = source.penumbra; + this.decay = source.decay; + + this.target = source.target.clone(); + + this.shadow = source.shadow.clone(); + + return this; + + } + +} + +const _projScreenMatrix = /*@__PURE__*/ new Matrix4(); +const _lightPositionWorld = /*@__PURE__*/ new Vector3(); +const _lookTarget = /*@__PURE__*/ new Vector3(); + +/** + * Represents the shadow configuration of point lights. + * + * @augments LightShadow + */ +class PointLightShadow extends LightShadow { + + /** + * Constructs a new point light shadow. + */ + constructor() { + + super( new PerspectiveCamera( 90, 1, 0.5, 500 ) ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isPointLightShadow = true; + + this._frameExtents = new Vector2( 4, 2 ); + + this._viewportCount = 6; + + this._viewports = [ + // These viewports map a cube-map onto a 2D texture with the + // following orientation: + // + // xzXZ + // y Y + // + // X - Positive x direction + // x - Negative x direction + // Y - Positive y direction + // y - Negative y direction + // Z - Positive z direction + // z - Negative z direction + + // positive X + new Vector4( 2, 1, 1, 1 ), + // negative X + new Vector4( 0, 1, 1, 1 ), + // positive Z + new Vector4( 3, 1, 1, 1 ), + // negative Z + new Vector4( 1, 1, 1, 1 ), + // positive Y + new Vector4( 3, 0, 1, 1 ), + // negative Y + new Vector4( 1, 0, 1, 1 ) + ]; + + this._cubeDirections = [ + new Vector3( 1, 0, 0 ), new Vector3( -1, 0, 0 ), new Vector3( 0, 0, 1 ), + new Vector3( 0, 0, -1 ), new Vector3( 0, 1, 0 ), new Vector3( 0, -1, 0 ) + ]; + + this._cubeUps = [ + new Vector3( 0, 1, 0 ), new Vector3( 0, 1, 0 ), new Vector3( 0, 1, 0 ), + new Vector3( 0, 1, 0 ), new Vector3( 0, 0, 1 ), new Vector3( 0, 0, -1 ) + ]; + + } + + /** + * Update the matrices for the camera and shadow, used internally by the renderer. + * + * @param {Light} light - The light for which the shadow is being rendered. + * @param {number} [viewportIndex=0] - The viewport index. + */ + updateMatrices( light, viewportIndex = 0 ) { + + const camera = this.camera; + const shadowMatrix = this.matrix; + + const far = light.distance || camera.far; + + if ( far !== camera.far ) { + + camera.far = far; + camera.updateProjectionMatrix(); + + } + + _lightPositionWorld.setFromMatrixPosition( light.matrixWorld ); + camera.position.copy( _lightPositionWorld ); + + _lookTarget.copy( camera.position ); + _lookTarget.add( this._cubeDirections[ viewportIndex ] ); + camera.up.copy( this._cubeUps[ viewportIndex ] ); + camera.lookAt( _lookTarget ); + camera.updateMatrixWorld(); + + shadowMatrix.makeTranslation( - _lightPositionWorld.x, - _lightPositionWorld.y, - _lightPositionWorld.z ); + + _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); + this._frustum.setFromProjectionMatrix( _projScreenMatrix ); + + } + +} + +/** + * A light that gets emitted from a single point in all directions. A common + * use case for this is to replicate the light emitted from a bare + * lightbulb. + * + * This light can cast shadows - see the {@link PointLightShadow} for details. + * + * ```js + * const light = new THREE.PointLight( 0xff0000, 1, 100 ); + * light.position.set( 50, 50, 50 ); + * scene.add( light ); + * ``` + * + * @augments Light + */ +class PointLight extends Light { + + /** + * Constructs a new point light. + * + * @param {(number|Color|string)} [color=0xffffff] - The light's color. + * @param {number} [intensity=1] - The light's strength/intensity measured in candela (cd). + * @param {number} [distance=0] - Maximum range of the light. `0` means no limit. + * @param {number} [decay=2] - The amount the light dims along the distance of the light. + */ + constructor( color, intensity, distance = 0, decay = 2 ) { + + super( color, intensity ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isPointLight = true; + + this.type = 'PointLight'; + + /** + * When distance is zero, light will attenuate according to inverse-square + * law to infinite distance. When distance is non-zero, light will attenuate + * according to inverse-square law until near the distance cutoff, where it + * will then attenuate quickly and smoothly to 0. Inherently, cutoffs are not + * physically correct. + * + * @type {number} + * @default 0 + */ + this.distance = distance; + + /** + * The amount the light dims along the distance of the light. In context of + * physically-correct rendering the default value should not be changed. + * + * @type {number} + * @default 2 + */ + this.decay = decay; + + /** + * This property holds the light's shadow configuration. + * + * @type {PointLightShadow} + */ + this.shadow = new PointLightShadow(); + + } + + /** + * The light's power. Power is the luminous power of the light measured in lumens (lm). + * Changing the power will also change the light's intensity. + * + * @type {number} + */ + get power() { + + // compute the light's luminous power (in lumens) from its intensity (in candela) + // for an isotropic light source, luminous power (lm) = 4 π luminous intensity (cd) + return this.intensity * 4 * Math.PI; + + } + + set power( power ) { + + // set the light's intensity (in candela) from the desired luminous power (in lumens) + this.intensity = power / ( 4 * Math.PI ); + + } + + dispose() { + + this.shadow.dispose(); + + } + + copy( source, recursive ) { + + super.copy( source, recursive ); + + this.distance = source.distance; + this.decay = source.decay; + + this.shadow = source.shadow.clone(); + + return this; + + } + +} + +/** + * Camera that uses [orthographic projection]{@link https://en.wikipedia.org/wiki/Orthographic_projection}. + * + * In this projection mode, an object's size in the rendered image stays + * constant regardless of its distance from the camera. This can be useful + * for rendering 2D scenes and UI elements, amongst other things. + * + * ```js + * const camera = new THREE.OrthographicCamera( width / - 2, width / 2, height / 2, height / - 2, 1, 1000 ); + * scene.add( camera ); + * ``` + * + * @augments Camera + */ +class OrthographicCamera extends Camera { + + /** + * Constructs a new orthographic camera. + * + * @param {number} [left=-1] - The left plane of the camera's frustum. + * @param {number} [right=1] - The right plane of the camera's frustum. + * @param {number} [top=1] - The top plane of the camera's frustum. + * @param {number} [bottom=-1] - The bottom plane of the camera's frustum. + * @param {number} [near=0.1] - The camera's near plane. + * @param {number} [far=2000] - The camera's far plane. + */ + constructor( left = -1, right = 1, top = 1, bottom = -1, near = 0.1, far = 2000 ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isOrthographicCamera = true; + + this.type = 'OrthographicCamera'; + + /** + * The zoom factor of the camera. + * + * @type {number} + * @default 1 + */ + this.zoom = 1; + + /** + * Represents the frustum window specification. This property should not be edited + * directly but via {@link PerspectiveCamera#setViewOffset} and {@link PerspectiveCamera#clearViewOffset}. + * + * @type {?Object} + * @default null + */ + this.view = null; + + /** + * The left plane of the camera's frustum. + * + * @type {number} + * @default -1 + */ + this.left = left; + + /** + * The right plane of the camera's frustum. + * + * @type {number} + * @default 1 + */ + this.right = right; + + /** + * The top plane of the camera's frustum. + * + * @type {number} + * @default 1 + */ + this.top = top; + + /** + * The bottom plane of the camera's frustum. + * + * @type {number} + * @default -1 + */ + this.bottom = bottom; + + /** + * The camera's near plane. The valid range is greater than `0` + * and less than the current value of {@link OrthographicCamera#far}. + * + * Note that, unlike for the {@link PerspectiveCamera}, `0` is a + * valid value for an orthographic camera's near plane. + * + * @type {number} + * @default 0.1 + */ + this.near = near; + + /** + * The camera's far plane. Must be greater than the + * current value of {@link OrthographicCamera#near}. + * + * @type {number} + * @default 2000 + */ + this.far = far; + + this.updateProjectionMatrix(); + + } + + copy( source, recursive ) { + + super.copy( source, recursive ); + + this.left = source.left; + this.right = source.right; + this.top = source.top; + this.bottom = source.bottom; + this.near = source.near; + this.far = source.far; + + this.zoom = source.zoom; + this.view = source.view === null ? null : Object.assign( {}, source.view ); + + return this; + + } + + /** + * Sets an offset in a larger frustum. This is useful for multi-window or + * multi-monitor/multi-machine setups. + * + * @param {number} fullWidth - The full width of multiview setup. + * @param {number} fullHeight - The full height of multiview setup. + * @param {number} x - The horizontal offset of the subcamera. + * @param {number} y - The vertical offset of the subcamera. + * @param {number} width - The width of subcamera. + * @param {number} height - The height of subcamera. + * @see {@link PerspectiveCamera#setViewOffset} + */ + setViewOffset( fullWidth, fullHeight, x, y, width, height ) { + + if ( this.view === null ) { + + this.view = { + enabled: true, + fullWidth: 1, + fullHeight: 1, + offsetX: 0, + offsetY: 0, + width: 1, + height: 1 + }; + + } + + this.view.enabled = true; + this.view.fullWidth = fullWidth; + this.view.fullHeight = fullHeight; + this.view.offsetX = x; + this.view.offsetY = y; + this.view.width = width; + this.view.height = height; + + this.updateProjectionMatrix(); + + } + + /** + * Removes the view offset from the projection matrix. + */ + clearViewOffset() { + + if ( this.view !== null ) { + + this.view.enabled = false; + + } + + this.updateProjectionMatrix(); + + } + + /** + * Updates the camera's projection matrix. Must be called after any change of + * camera properties. + */ + updateProjectionMatrix() { + + const dx = ( this.right - this.left ) / ( 2 * this.zoom ); + const dy = ( this.top - this.bottom ) / ( 2 * this.zoom ); + const cx = ( this.right + this.left ) / 2; + const cy = ( this.top + this.bottom ) / 2; + + let left = cx - dx; + let right = cx + dx; + let top = cy + dy; + let bottom = cy - dy; + + if ( this.view !== null && this.view.enabled ) { + + const scaleW = ( this.right - this.left ) / this.view.fullWidth / this.zoom; + const scaleH = ( this.top - this.bottom ) / this.view.fullHeight / this.zoom; + + left += scaleW * this.view.offsetX; + right = left + scaleW * this.view.width; + top -= scaleH * this.view.offsetY; + bottom = top - scaleH * this.view.height; + + } + + this.projectionMatrix.makeOrthographic( left, right, top, bottom, this.near, this.far, this.coordinateSystem ); + + this.projectionMatrixInverse.copy( this.projectionMatrix ).invert(); + + } + + toJSON( meta ) { + + const data = super.toJSON( meta ); + + data.object.zoom = this.zoom; + data.object.left = this.left; + data.object.right = this.right; + data.object.top = this.top; + data.object.bottom = this.bottom; + data.object.near = this.near; + data.object.far = this.far; + + if ( this.view !== null ) data.object.view = Object.assign( {}, this.view ); + + return data; + + } + +} + +/** + * Represents the shadow configuration of directional lights. + * + * @augments LightShadow + */ +class DirectionalLightShadow extends LightShadow { + + /** + * Constructs a new directional light shadow. + */ + constructor() { + + super( new OrthographicCamera( -5, 5, 5, -5, 0.5, 500 ) ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isDirectionalLightShadow = true; + + } + +} + +/** + * A light that gets emitted in a specific direction. This light will behave + * as though it is infinitely far away and the rays produced from it are all + * parallel. The common use case for this is to simulate daylight; the sun is + * far enough away that its position can be considered to be infinite, and + * all light rays coming from it are parallel. + * + * A common point of confusion for directional lights is that setting the + * rotation has no effect. This is because three.js's DirectionalLight is the + * equivalent to what is often called a 'Target Direct Light' in other + * applications. + * + * This means that its direction is calculated as pointing from the light's + * {@link Object3D#position} to the {@link DirectionalLight#target} position + * (as opposed to a 'Free Direct Light' that just has a rotation + * component). + * + * This light can cast shadows - see the {@link DirectionalLightShadow} for details. + * + * ```js + * // White directional light at half intensity shining from the top. + * const directionalLight = new THREE.DirectionalLight( 0xffffff, 0.5 ); + * scene.add( directionalLight ); + * ``` + * + * @augments Light + */ +class DirectionalLight extends Light { + + /** + * Constructs a new directional light. + * + * @param {(number|Color|string)} [color=0xffffff] - The light's color. + * @param {number} [intensity=1] - The light's strength/intensity. + */ + constructor( color, intensity ) { + + super( color, intensity ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isDirectionalLight = true; + + this.type = 'DirectionalLight'; + + this.position.copy( Object3D.DEFAULT_UP ); + this.updateMatrix(); + + /** + * The directional light points from its position to the + * target's position. + * + * For the target's position to be changed to anything other + * than the default, it must be added to the scene. + * + * It is also possible to set the target to be another 3D object + * in the scene. The light will now track the target object. + * + * @type {Object3D} + */ + this.target = new Object3D(); + + /** + * This property holds the light's shadow configuration. + * + * @type {DirectionalLightShadow} + */ + this.shadow = new DirectionalLightShadow(); + + } + + dispose() { + + this.shadow.dispose(); + + } + + copy( source ) { + + super.copy( source ); + + this.target = source.target.clone(); + this.shadow = source.shadow.clone(); + + return this; + + } + +} + +/** + * This light globally illuminates all objects in the scene equally. + * + * It cannot be used to cast shadows as it does not have a direction. + * + * ```js + * const light = new THREE.AmbientLight( 0x404040 ); // soft white light + * scene.add( light ); + * ``` + * + * @augments Light + */ +class AmbientLight extends Light { + + /** + * Constructs a new ambient light. + * + * @param {(number|Color|string)} [color=0xffffff] - The light's color. + * @param {number} [intensity=1] - The light's strength/intensity. + */ + constructor( color, intensity ) { + + super( color, intensity ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isAmbientLight = true; + + this.type = 'AmbientLight'; + + } + +} + +/** + * This class emits light uniformly across the face a rectangular plane. + * This light type can be used to simulate light sources such as bright + * windows or strip lighting. + * + * Important Notes: + * + * - There is no shadow support. + * - Only PBR materials are supported. + * - You have to include `RectAreaLightUniformsLib` (`WebGLRenderer`) or `RectAreaLightTexturesLib` (`WebGPURenderer`) + * into your app and init the uniforms/textures. + * + * ```js + * RectAreaLightUniformsLib.init(); // only relevant for WebGLRenderer + * THREE.RectAreaLightNode.setLTC( RectAreaLightTexturesLib.init() ); // only relevant for WebGPURenderer + * + * const intensity = 1; const width = 10; const height = 10; + * const rectLight = new THREE.RectAreaLight( 0xffffff, intensity, width, height ); + * rectLight.position.set( 5, 5, 0 ); + * rectLight.lookAt( 0, 0, 0 ); + * scene.add( rectLight ) + * ``` + * + * @augments Light + */ +class RectAreaLight extends Light { + + /** + * Constructs a new area light. + * + * @param {(number|Color|string)} [color=0xffffff] - The light's color. + * @param {number} [intensity=1] - The light's strength/intensity. + * @param {number} [width=10] - The width of the light. + * @param {number} [height=10] - The height of the light. + */ + constructor( color, intensity, width = 10, height = 10 ) { + + super( color, intensity ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isRectAreaLight = true; + + this.type = 'RectAreaLight'; + + /** + * The width of the light. + * + * @type {number} + * @default 10 + */ + this.width = width; + + /** + * The height of the light. + * + * @type {number} + * @default 10 + */ + this.height = height; + + } + + /** + * The light's power. Power is the luminous power of the light measured in lumens (lm). + * Changing the power will also change the light's intensity. + * + * @type {number} + */ + get power() { + + // compute the light's luminous power (in lumens) from its intensity (in nits) + return this.intensity * this.width * this.height * Math.PI; + + } + + set power( power ) { + + // set the light's intensity (in nits) from the desired luminous power (in lumens) + this.intensity = power / ( this.width * this.height * Math.PI ); + + } + + copy( source ) { + + super.copy( source ); + + this.width = source.width; + this.height = source.height; + + return this; + + } + + toJSON( meta ) { + + const data = super.toJSON( meta ); + + data.object.width = this.width; + data.object.height = this.height; + + return data; + + } + +} + +/** + * Represents a third-order spherical harmonics (SH). Light probes use this class + * to encode lighting information. + * + * - Primary reference: {@link https://graphics.stanford.edu/papers/envmap/envmap.pdf} + * - Secondary reference: {@link https://www.ppsloan.org/publications/StupidSH36.pdf} + */ +class SphericalHarmonics3 { + + /** + * Constructs a new spherical harmonics. + */ + constructor() { + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSphericalHarmonics3 = true; + + /** + * An array holding the (9) SH coefficients. + * + * @type {Array} + */ + this.coefficients = []; + + for ( let i = 0; i < 9; i ++ ) { + + this.coefficients.push( new Vector3() ); + + } + + } + + /** + * Sets the given SH coefficients to this instance by copying + * the values. + * + * @param {Array} coefficients - The SH coefficients. + * @return {SphericalHarmonics3} A reference to this spherical harmonics. + */ + set( coefficients ) { + + for ( let i = 0; i < 9; i ++ ) { + + this.coefficients[ i ].copy( coefficients[ i ] ); + + } + + return this; + + } + + /** + * Sets all SH coefficients to `0`. + * + * @return {SphericalHarmonics3} A reference to this spherical harmonics. + */ + zero() { + + for ( let i = 0; i < 9; i ++ ) { + + this.coefficients[ i ].set( 0, 0, 0 ); + + } + + return this; + + } + + /** + * Returns the radiance in the direction of the given normal. + * + * @param {Vector3} normal - The normal vector (assumed to be unit length) + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The radiance. + */ + getAt( normal, target ) { + + // normal is assumed to be unit length + + const x = normal.x, y = normal.y, z = normal.z; + + const coeff = this.coefficients; + + // band 0 + target.copy( coeff[ 0 ] ).multiplyScalar( 0.282095 ); + + // band 1 + target.addScaledVector( coeff[ 1 ], 0.488603 * y ); + target.addScaledVector( coeff[ 2 ], 0.488603 * z ); + target.addScaledVector( coeff[ 3 ], 0.488603 * x ); + + // band 2 + target.addScaledVector( coeff[ 4 ], 1.092548 * ( x * y ) ); + target.addScaledVector( coeff[ 5 ], 1.092548 * ( y * z ) ); + target.addScaledVector( coeff[ 6 ], 0.315392 * ( 3.0 * z * z - 1.0 ) ); + target.addScaledVector( coeff[ 7 ], 1.092548 * ( x * z ) ); + target.addScaledVector( coeff[ 8 ], 0.546274 * ( x * x - y * y ) ); + + return target; + + } + + /** + * Returns the irradiance (radiance convolved with cosine lobe) in the + * direction of the given normal. + * + * @param {Vector3} normal - The normal vector (assumed to be unit length) + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The irradiance. + */ + getIrradianceAt( normal, target ) { + + // normal is assumed to be unit length + + const x = normal.x, y = normal.y, z = normal.z; + + const coeff = this.coefficients; + + // band 0 + target.copy( coeff[ 0 ] ).multiplyScalar( 0.886227 ); // π * 0.282095 + + // band 1 + target.addScaledVector( coeff[ 1 ], 2.0 * 0.511664 * y ); // ( 2 * π / 3 ) * 0.488603 + target.addScaledVector( coeff[ 2 ], 2.0 * 0.511664 * z ); + target.addScaledVector( coeff[ 3 ], 2.0 * 0.511664 * x ); + + // band 2 + target.addScaledVector( coeff[ 4 ], 2.0 * 0.429043 * x * y ); // ( π / 4 ) * 1.092548 + target.addScaledVector( coeff[ 5 ], 2.0 * 0.429043 * y * z ); + target.addScaledVector( coeff[ 6 ], 0.743125 * z * z - 0.247708 ); // ( π / 4 ) * 0.315392 * 3 + target.addScaledVector( coeff[ 7 ], 2.0 * 0.429043 * x * z ); + target.addScaledVector( coeff[ 8 ], 0.429043 * ( x * x - y * y ) ); // ( π / 4 ) * 0.546274 + + return target; + + } + + /** + * Adds the given SH to this instance. + * + * @param {SphericalHarmonics3} sh - The SH to add. + * @return {SphericalHarmonics3} A reference to this spherical harmonics. + */ + add( sh ) { + + for ( let i = 0; i < 9; i ++ ) { + + this.coefficients[ i ].add( sh.coefficients[ i ] ); + + } + + return this; + + } + + /** + * A convenience method for performing {@link SphericalHarmonics3#add} and + * {@link SphericalHarmonics3#scale} at once. + * + * @param {SphericalHarmonics3} sh - The SH to add. + * @param {number} s - The scale factor. + * @return {SphericalHarmonics3} A reference to this spherical harmonics. + */ + addScaledSH( sh, s ) { + + for ( let i = 0; i < 9; i ++ ) { + + this.coefficients[ i ].addScaledVector( sh.coefficients[ i ], s ); + + } + + return this; + + } + + /** + * Scales this SH by the given scale factor. + * + * @param {number} s - The scale factor. + * @return {SphericalHarmonics3} A reference to this spherical harmonics. + */ + scale( s ) { + + for ( let i = 0; i < 9; i ++ ) { + + this.coefficients[ i ].multiplyScalar( s ); + + } + + return this; + + } + + /** + * Linear interpolates between the given SH and this instance by the given + * alpha factor. + * + * @param {SphericalHarmonics3} sh - The SH to interpolate with. + * @param {number} alpha - The alpha factor. + * @return {SphericalHarmonics3} A reference to this spherical harmonics. + */ + lerp( sh, alpha ) { + + for ( let i = 0; i < 9; i ++ ) { + + this.coefficients[ i ].lerp( sh.coefficients[ i ], alpha ); + + } + + return this; + + } + + /** + * Returns `true` if this spherical harmonics is equal with the given one. + * + * @param {SphericalHarmonics3} sh - The spherical harmonics to test for equality. + * @return {boolean} Whether this spherical harmonics is equal with the given one. + */ + equals( sh ) { + + for ( let i = 0; i < 9; i ++ ) { + + if ( ! this.coefficients[ i ].equals( sh.coefficients[ i ] ) ) { + + return false; + + } + + } + + return true; + + } + + /** + * Copies the values of the given spherical harmonics to this instance. + * + * @param {SphericalHarmonics3} sh - The spherical harmonics to copy. + * @return {SphericalHarmonics3} A reference to this spherical harmonics. + */ + copy( sh ) { + + return this.set( sh.coefficients ); + + } + + /** + * Returns a new spherical harmonics with copied values from this instance. + * + * @return {SphericalHarmonics3} A clone of this instance. + */ + clone() { + + return new this.constructor().copy( this ); + + } + + /** + * Sets the SH coefficients of this instance from the given array. + * + * @param {Array} array - An array holding the SH coefficients. + * @param {number} [offset=0] - The array offset where to start copying. + * @return {SphericalHarmonics3} A clone of this instance. + */ + fromArray( array, offset = 0 ) { + + const coefficients = this.coefficients; + + for ( let i = 0; i < 9; i ++ ) { + + coefficients[ i ].fromArray( array, offset + ( i * 3 ) ); + + } + + return this; + + } + + /** + * Returns an array with the SH coefficients, or copies them into the provided + * array. The coefficients are represented as numbers. + * + * @param {Array} [array=[]] - The target array. + * @param {number} [offset=0] - The array offset where to start copying. + * @return {Array} An array with flat SH coefficients. + */ + toArray( array = [], offset = 0 ) { + + const coefficients = this.coefficients; + + for ( let i = 0; i < 9; i ++ ) { + + coefficients[ i ].toArray( array, offset + ( i * 3 ) ); + + } + + return array; + + } + + /** + * Computes the SH basis for the given normal vector. + * + * @param {Vector3} normal - The normal. + * @param {Array} shBasis - The target array holding the SH basis. + */ + static getBasisAt( normal, shBasis ) { + + // normal is assumed to be unit length + + const x = normal.x, y = normal.y, z = normal.z; + + // band 0 + shBasis[ 0 ] = 0.282095; + + // band 1 + shBasis[ 1 ] = 0.488603 * y; + shBasis[ 2 ] = 0.488603 * z; + shBasis[ 3 ] = 0.488603 * x; + + // band 2 + shBasis[ 4 ] = 1.092548 * x * y; + shBasis[ 5 ] = 1.092548 * y * z; + shBasis[ 6 ] = 0.315392 * ( 3 * z * z - 1 ); + shBasis[ 7 ] = 1.092548 * x * z; + shBasis[ 8 ] = 0.546274 * ( x * x - y * y ); + + } + +} + +/** + * Light probes are an alternative way of adding light to a 3D scene. Unlike + * classical light sources (e.g. directional, point or spot lights), light + * probes do not emit light. Instead they store information about light + * passing through 3D space. During rendering, the light that hits a 3D + * object is approximated by using the data from the light probe. + * + * Light probes are usually created from (radiance) environment maps. The + * class {@link LightProbeGenerator} can be used to create light probes from + * cube textures or render targets. However, light estimation data could also + * be provided in other forms e.g. by WebXR. This enables the rendering of + * augmented reality content that reacts to real world lighting. + * + * The current probe implementation in three.js supports so-called diffuse + * light probes. This type of light probe is functionally equivalent to an + * irradiance environment map. + * + * @augments Light + */ +class LightProbe extends Light { + + /** + * Constructs a new light probe. + * + * @param {SphericalHarmonics3} sh - The spherical harmonics which represents encoded lighting information. + * @param {number} [intensity=1] - The light's strength/intensity. + */ + constructor( sh = new SphericalHarmonics3(), intensity = 1 ) { + + super( undefined, intensity ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLightProbe = true; + + /** + * A light probe uses spherical harmonics to encode lighting information. + * + * @type {SphericalHarmonics3} + */ + this.sh = sh; + + } + + copy( source ) { + + super.copy( source ); + + this.sh.copy( source.sh ); + + return this; + + } + + /** + * Deserializes the light prove from the given JSON. + * + * @param {Object} json - The JSON holding the serialized light probe. + * @return {LightProbe} A reference to this light probe. + */ + fromJSON( json ) { + + this.intensity = json.intensity; // TODO: Move this bit to Light.fromJSON(); + this.sh.fromArray( json.sh ); + + return this; + + } + + toJSON( meta ) { + + const data = super.toJSON( meta ); + + data.object.sh = this.sh.toArray(); + + return data; + + } + +} + +/** + * Class for loading geometries. The files are internally + * loaded via {@link FileLoader}. + * + * ```js + * const loader = new THREE.MaterialLoader(); + * const material = await loader.loadAsync( 'material.json' ); + * ``` + * This loader does not support node materials. Use {@link NodeMaterialLoader} instead. + * + * @augments Loader + */ +class MaterialLoader extends Loader { + + /** + * Constructs a new material loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ + constructor( manager ) { + + super( manager ); + + /** + * A dictionary holding textures used by the material. + * + * @type {Object} + */ + this.textures = {}; + + } + + /** + * Starts loading from the given URL and pass the loaded material to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(Material)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const loader = new FileLoader( scope.manager ); + loader.setPath( scope.path ); + loader.setRequestHeader( scope.requestHeader ); + loader.setWithCredentials( scope.withCredentials ); + loader.load( url, function ( text ) { + + try { + + onLoad( scope.parse( JSON.parse( text ) ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + /** + * Parses the given JSON object and returns a material. + * + * @param {Object} json - The serialized material. + * @return {Material} The parsed material. + */ + parse( json ) { + + const textures = this.textures; + + function getTexture( name ) { + + if ( textures[ name ] === undefined ) { + + console.warn( 'THREE.MaterialLoader: Undefined texture', name ); + + } + + return textures[ name ]; + + } + + const material = this.createMaterialFromType( json.type ); + + if ( json.uuid !== undefined ) material.uuid = json.uuid; + if ( json.name !== undefined ) material.name = json.name; + if ( json.color !== undefined && material.color !== undefined ) material.color.setHex( json.color ); + if ( json.roughness !== undefined ) material.roughness = json.roughness; + if ( json.metalness !== undefined ) material.metalness = json.metalness; + if ( json.sheen !== undefined ) material.sheen = json.sheen; + if ( json.sheenColor !== undefined ) material.sheenColor = new Color().setHex( json.sheenColor ); + if ( json.sheenRoughness !== undefined ) material.sheenRoughness = json.sheenRoughness; + if ( json.emissive !== undefined && material.emissive !== undefined ) material.emissive.setHex( json.emissive ); + if ( json.specular !== undefined && material.specular !== undefined ) material.specular.setHex( json.specular ); + if ( json.specularIntensity !== undefined ) material.specularIntensity = json.specularIntensity; + if ( json.specularColor !== undefined && material.specularColor !== undefined ) material.specularColor.setHex( json.specularColor ); + if ( json.shininess !== undefined ) material.shininess = json.shininess; + if ( json.clearcoat !== undefined ) material.clearcoat = json.clearcoat; + if ( json.clearcoatRoughness !== undefined ) material.clearcoatRoughness = json.clearcoatRoughness; + if ( json.dispersion !== undefined ) material.dispersion = json.dispersion; + if ( json.iridescence !== undefined ) material.iridescence = json.iridescence; + if ( json.iridescenceIOR !== undefined ) material.iridescenceIOR = json.iridescenceIOR; + if ( json.iridescenceThicknessRange !== undefined ) material.iridescenceThicknessRange = json.iridescenceThicknessRange; + if ( json.transmission !== undefined ) material.transmission = json.transmission; + if ( json.thickness !== undefined ) material.thickness = json.thickness; + if ( json.attenuationDistance !== undefined ) material.attenuationDistance = json.attenuationDistance; + if ( json.attenuationColor !== undefined && material.attenuationColor !== undefined ) material.attenuationColor.setHex( json.attenuationColor ); + if ( json.anisotropy !== undefined ) material.anisotropy = json.anisotropy; + if ( json.anisotropyRotation !== undefined ) material.anisotropyRotation = json.anisotropyRotation; + if ( json.fog !== undefined ) material.fog = json.fog; + if ( json.flatShading !== undefined ) material.flatShading = json.flatShading; + if ( json.blending !== undefined ) material.blending = json.blending; + if ( json.combine !== undefined ) material.combine = json.combine; + if ( json.side !== undefined ) material.side = json.side; + if ( json.shadowSide !== undefined ) material.shadowSide = json.shadowSide; + if ( json.opacity !== undefined ) material.opacity = json.opacity; + if ( json.transparent !== undefined ) material.transparent = json.transparent; + if ( json.alphaTest !== undefined ) material.alphaTest = json.alphaTest; + if ( json.alphaHash !== undefined ) material.alphaHash = json.alphaHash; + if ( json.depthFunc !== undefined ) material.depthFunc = json.depthFunc; + if ( json.depthTest !== undefined ) material.depthTest = json.depthTest; + if ( json.depthWrite !== undefined ) material.depthWrite = json.depthWrite; + if ( json.colorWrite !== undefined ) material.colorWrite = json.colorWrite; + if ( json.blendSrc !== undefined ) material.blendSrc = json.blendSrc; + if ( json.blendDst !== undefined ) material.blendDst = json.blendDst; + if ( json.blendEquation !== undefined ) material.blendEquation = json.blendEquation; + if ( json.blendSrcAlpha !== undefined ) material.blendSrcAlpha = json.blendSrcAlpha; + if ( json.blendDstAlpha !== undefined ) material.blendDstAlpha = json.blendDstAlpha; + if ( json.blendEquationAlpha !== undefined ) material.blendEquationAlpha = json.blendEquationAlpha; + if ( json.blendColor !== undefined && material.blendColor !== undefined ) material.blendColor.setHex( json.blendColor ); + if ( json.blendAlpha !== undefined ) material.blendAlpha = json.blendAlpha; + if ( json.stencilWriteMask !== undefined ) material.stencilWriteMask = json.stencilWriteMask; + if ( json.stencilFunc !== undefined ) material.stencilFunc = json.stencilFunc; + if ( json.stencilRef !== undefined ) material.stencilRef = json.stencilRef; + if ( json.stencilFuncMask !== undefined ) material.stencilFuncMask = json.stencilFuncMask; + if ( json.stencilFail !== undefined ) material.stencilFail = json.stencilFail; + if ( json.stencilZFail !== undefined ) material.stencilZFail = json.stencilZFail; + if ( json.stencilZPass !== undefined ) material.stencilZPass = json.stencilZPass; + if ( json.stencilWrite !== undefined ) material.stencilWrite = json.stencilWrite; + + if ( json.wireframe !== undefined ) material.wireframe = json.wireframe; + if ( json.wireframeLinewidth !== undefined ) material.wireframeLinewidth = json.wireframeLinewidth; + if ( json.wireframeLinecap !== undefined ) material.wireframeLinecap = json.wireframeLinecap; + if ( json.wireframeLinejoin !== undefined ) material.wireframeLinejoin = json.wireframeLinejoin; + + if ( json.rotation !== undefined ) material.rotation = json.rotation; + + if ( json.linewidth !== undefined ) material.linewidth = json.linewidth; + if ( json.dashSize !== undefined ) material.dashSize = json.dashSize; + if ( json.gapSize !== undefined ) material.gapSize = json.gapSize; + if ( json.scale !== undefined ) material.scale = json.scale; + + if ( json.polygonOffset !== undefined ) material.polygonOffset = json.polygonOffset; + if ( json.polygonOffsetFactor !== undefined ) material.polygonOffsetFactor = json.polygonOffsetFactor; + if ( json.polygonOffsetUnits !== undefined ) material.polygonOffsetUnits = json.polygonOffsetUnits; + + if ( json.dithering !== undefined ) material.dithering = json.dithering; + + if ( json.alphaToCoverage !== undefined ) material.alphaToCoverage = json.alphaToCoverage; + if ( json.premultipliedAlpha !== undefined ) material.premultipliedAlpha = json.premultipliedAlpha; + if ( json.forceSinglePass !== undefined ) material.forceSinglePass = json.forceSinglePass; + + if ( json.visible !== undefined ) material.visible = json.visible; + + if ( json.toneMapped !== undefined ) material.toneMapped = json.toneMapped; + + if ( json.userData !== undefined ) material.userData = json.userData; + + if ( json.vertexColors !== undefined ) { + + if ( typeof json.vertexColors === 'number' ) { + + material.vertexColors = ( json.vertexColors > 0 ) ? true : false; + + } else { + + material.vertexColors = json.vertexColors; + + } + + } + + // Shader Material + + if ( json.uniforms !== undefined ) { + + for ( const name in json.uniforms ) { + + const uniform = json.uniforms[ name ]; + + material.uniforms[ name ] = {}; + + switch ( uniform.type ) { + + case 't': + material.uniforms[ name ].value = getTexture( uniform.value ); + break; + + case 'c': + material.uniforms[ name ].value = new Color().setHex( uniform.value ); + break; + + case 'v2': + material.uniforms[ name ].value = new Vector2().fromArray( uniform.value ); + break; + + case 'v3': + material.uniforms[ name ].value = new Vector3().fromArray( uniform.value ); + break; + + case 'v4': + material.uniforms[ name ].value = new Vector4().fromArray( uniform.value ); + break; + + case 'm3': + material.uniforms[ name ].value = new Matrix3().fromArray( uniform.value ); + break; + + case 'm4': + material.uniforms[ name ].value = new Matrix4().fromArray( uniform.value ); + break; + + default: + material.uniforms[ name ].value = uniform.value; + + } + + } + + } + + if ( json.defines !== undefined ) material.defines = json.defines; + if ( json.vertexShader !== undefined ) material.vertexShader = json.vertexShader; + if ( json.fragmentShader !== undefined ) material.fragmentShader = json.fragmentShader; + if ( json.glslVersion !== undefined ) material.glslVersion = json.glslVersion; + + if ( json.extensions !== undefined ) { + + for ( const key in json.extensions ) { + + material.extensions[ key ] = json.extensions[ key ]; + + } + + } + + if ( json.lights !== undefined ) material.lights = json.lights; + if ( json.clipping !== undefined ) material.clipping = json.clipping; + + // for PointsMaterial + + if ( json.size !== undefined ) material.size = json.size; + if ( json.sizeAttenuation !== undefined ) material.sizeAttenuation = json.sizeAttenuation; + + // maps + + if ( json.map !== undefined ) material.map = getTexture( json.map ); + if ( json.matcap !== undefined ) material.matcap = getTexture( json.matcap ); + + if ( json.alphaMap !== undefined ) material.alphaMap = getTexture( json.alphaMap ); + + if ( json.bumpMap !== undefined ) material.bumpMap = getTexture( json.bumpMap ); + if ( json.bumpScale !== undefined ) material.bumpScale = json.bumpScale; + + if ( json.normalMap !== undefined ) material.normalMap = getTexture( json.normalMap ); + if ( json.normalMapType !== undefined ) material.normalMapType = json.normalMapType; + if ( json.normalScale !== undefined ) { + + let normalScale = json.normalScale; + + if ( Array.isArray( normalScale ) === false ) { + + // Blender exporter used to export a scalar. See #7459 + + normalScale = [ normalScale, normalScale ]; + + } + + material.normalScale = new Vector2().fromArray( normalScale ); + + } + + if ( json.displacementMap !== undefined ) material.displacementMap = getTexture( json.displacementMap ); + if ( json.displacementScale !== undefined ) material.displacementScale = json.displacementScale; + if ( json.displacementBias !== undefined ) material.displacementBias = json.displacementBias; + + if ( json.roughnessMap !== undefined ) material.roughnessMap = getTexture( json.roughnessMap ); + if ( json.metalnessMap !== undefined ) material.metalnessMap = getTexture( json.metalnessMap ); + + if ( json.emissiveMap !== undefined ) material.emissiveMap = getTexture( json.emissiveMap ); + if ( json.emissiveIntensity !== undefined ) material.emissiveIntensity = json.emissiveIntensity; + + if ( json.specularMap !== undefined ) material.specularMap = getTexture( json.specularMap ); + if ( json.specularIntensityMap !== undefined ) material.specularIntensityMap = getTexture( json.specularIntensityMap ); + if ( json.specularColorMap !== undefined ) material.specularColorMap = getTexture( json.specularColorMap ); + + if ( json.envMap !== undefined ) material.envMap = getTexture( json.envMap ); + if ( json.envMapRotation !== undefined ) material.envMapRotation.fromArray( json.envMapRotation ); + if ( json.envMapIntensity !== undefined ) material.envMapIntensity = json.envMapIntensity; + + if ( json.reflectivity !== undefined ) material.reflectivity = json.reflectivity; + if ( json.refractionRatio !== undefined ) material.refractionRatio = json.refractionRatio; + + if ( json.lightMap !== undefined ) material.lightMap = getTexture( json.lightMap ); + if ( json.lightMapIntensity !== undefined ) material.lightMapIntensity = json.lightMapIntensity; + + if ( json.aoMap !== undefined ) material.aoMap = getTexture( json.aoMap ); + if ( json.aoMapIntensity !== undefined ) material.aoMapIntensity = json.aoMapIntensity; + + if ( json.gradientMap !== undefined ) material.gradientMap = getTexture( json.gradientMap ); + + if ( json.clearcoatMap !== undefined ) material.clearcoatMap = getTexture( json.clearcoatMap ); + if ( json.clearcoatRoughnessMap !== undefined ) material.clearcoatRoughnessMap = getTexture( json.clearcoatRoughnessMap ); + if ( json.clearcoatNormalMap !== undefined ) material.clearcoatNormalMap = getTexture( json.clearcoatNormalMap ); + if ( json.clearcoatNormalScale !== undefined ) material.clearcoatNormalScale = new Vector2().fromArray( json.clearcoatNormalScale ); + + if ( json.iridescenceMap !== undefined ) material.iridescenceMap = getTexture( json.iridescenceMap ); + if ( json.iridescenceThicknessMap !== undefined ) material.iridescenceThicknessMap = getTexture( json.iridescenceThicknessMap ); + + if ( json.transmissionMap !== undefined ) material.transmissionMap = getTexture( json.transmissionMap ); + if ( json.thicknessMap !== undefined ) material.thicknessMap = getTexture( json.thicknessMap ); + + if ( json.anisotropyMap !== undefined ) material.anisotropyMap = getTexture( json.anisotropyMap ); + + if ( json.sheenColorMap !== undefined ) material.sheenColorMap = getTexture( json.sheenColorMap ); + if ( json.sheenRoughnessMap !== undefined ) material.sheenRoughnessMap = getTexture( json.sheenRoughnessMap ); + + return material; + + } + + /** + * Textures are not embedded in the material JSON so they have + * to be injected before the loading process starts. + * + * @param {Object} value - A dictionary holding textures for material properties. + * @return {MaterialLoader} A reference to this material loader. + */ + setTextures( value ) { + + this.textures = value; + return this; + + } + + /** + * Creates a material for the given type. + * + * @param {string} type - The material type. + * @return {Material} The new material. + */ + createMaterialFromType( type ) { + + return MaterialLoader.createMaterialFromType( type ); + + } + + /** + * Creates a material for the given type. + * + * @static + * @param {string} type - The material type. + * @return {Material} The new material. + */ + static createMaterialFromType( type ) { + + const materialLib = { + ShadowMaterial, + SpriteMaterial, + RawShaderMaterial, + ShaderMaterial, + PointsMaterial, + MeshPhysicalMaterial, + MeshStandardMaterial, + MeshPhongMaterial, + MeshToonMaterial, + MeshNormalMaterial, + MeshLambertMaterial, + MeshDepthMaterial, + MeshDistanceMaterial, + MeshBasicMaterial, + MeshMatcapMaterial, + LineDashedMaterial, + LineBasicMaterial, + Material + }; + + return new materialLib[ type ](); + + } + +} + +/** + * A class with loader utility functions. + */ +class LoaderUtils { + + /** + * Extracts the base URL from the given URL. + * + * @param {string} url -The URL to extract the base URL from. + * @return {string} The extracted base URL. + */ + static extractUrlBase( url ) { + + const index = url.lastIndexOf( '/' ); + + if ( index === -1 ) return './'; + + return url.slice( 0, index + 1 ); + + } + + /** + * Resolves relative URLs against the given path. Absolute paths, data urls, + * and blob URLs will be returned as is. Invalid URLs will return an empty + * string. + * + * @param {string} url -The URL to resolve. + * @param {string} path - The base path for relative URLs to be resolved against. + * @return {string} The resolved URL. + */ + static resolveURL( url, path ) { + + // Invalid URL + if ( typeof url !== 'string' || url === '' ) return ''; + + // Host Relative URL + if ( /^https?:\/\//i.test( path ) && /^\//.test( url ) ) { + + path = path.replace( /(^https?:\/\/[^\/]+).*/i, '$1' ); + + } + + // Absolute URL http://,https://,// + if ( /^(https?:)?\/\//i.test( url ) ) return url; + + // Data URI + if ( /^data:.*,.*$/i.test( url ) ) return url; + + // Blob URL + if ( /^blob:.*$/i.test( url ) ) return url; + + // Relative URL + return path + url; + + } + +} + +/** + * An instanced version of a geometry. + */ +class InstancedBufferGeometry extends BufferGeometry { + + /** + * Constructs a new instanced buffer geometry. + */ + constructor() { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isInstancedBufferGeometry = true; + + this.type = 'InstancedBufferGeometry'; + + /** + * The instance count. + * + * @type {number} + * @default Infinity + */ + this.instanceCount = Infinity; + + } + + copy( source ) { + + super.copy( source ); + + this.instanceCount = source.instanceCount; + + return this; + + } + + toJSON() { + + const data = super.toJSON(); + + data.instanceCount = this.instanceCount; + + data.isInstancedBufferGeometry = true; + + return data; + + } + +} + +/** + * Class for loading geometries. The files are internally + * loaded via {@link FileLoader}. + * + * ```js + * const loader = new THREE.BufferGeometryLoader(); + * const geometry = await loader.loadAsync( 'models/json/pressure.json' ); + * + * const material = new THREE.MeshBasicMaterial( { color: 0xF5F5F5 } ); + * const object = new THREE.Mesh( geometry, material ); + * scene.add( object ); + * ``` + * + * @augments Loader + */ +class BufferGeometryLoader extends Loader { + + /** + * Constructs a new geometry loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ + constructor( manager ) { + + super( manager ); + + } + + /** + * Starts loading from the given URL and pass the loaded geometry to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(BufferGeometry)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const loader = new FileLoader( scope.manager ); + loader.setPath( scope.path ); + loader.setRequestHeader( scope.requestHeader ); + loader.setWithCredentials( scope.withCredentials ); + loader.load( url, function ( text ) { + + try { + + onLoad( scope.parse( JSON.parse( text ) ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + /** + * Parses the given JSON object and returns a geometry. + * + * @param {Object} json - The serialized geometry. + * @return {BufferGeometry} The parsed geometry. + */ + parse( json ) { + + const interleavedBufferMap = {}; + const arrayBufferMap = {}; + + function getInterleavedBuffer( json, uuid ) { + + if ( interleavedBufferMap[ uuid ] !== undefined ) return interleavedBufferMap[ uuid ]; + + const interleavedBuffers = json.interleavedBuffers; + const interleavedBuffer = interleavedBuffers[ uuid ]; + + const buffer = getArrayBuffer( json, interleavedBuffer.buffer ); + + const array = getTypedArray( interleavedBuffer.type, buffer ); + const ib = new InterleavedBuffer( array, interleavedBuffer.stride ); + ib.uuid = interleavedBuffer.uuid; + + interleavedBufferMap[ uuid ] = ib; + + return ib; + + } + + function getArrayBuffer( json, uuid ) { + + if ( arrayBufferMap[ uuid ] !== undefined ) return arrayBufferMap[ uuid ]; + + const arrayBuffers = json.arrayBuffers; + const arrayBuffer = arrayBuffers[ uuid ]; + + const ab = new Uint32Array( arrayBuffer ).buffer; + + arrayBufferMap[ uuid ] = ab; + + return ab; + + } + + const geometry = json.isInstancedBufferGeometry ? new InstancedBufferGeometry() : new BufferGeometry(); + + const index = json.data.index; + + if ( index !== undefined ) { + + const typedArray = getTypedArray( index.type, index.array ); + geometry.setIndex( new BufferAttribute( typedArray, 1 ) ); + + } + + const attributes = json.data.attributes; + + for ( const key in attributes ) { + + const attribute = attributes[ key ]; + let bufferAttribute; + + if ( attribute.isInterleavedBufferAttribute ) { + + const interleavedBuffer = getInterleavedBuffer( json.data, attribute.data ); + bufferAttribute = new InterleavedBufferAttribute( interleavedBuffer, attribute.itemSize, attribute.offset, attribute.normalized ); + + } else { + + const typedArray = getTypedArray( attribute.type, attribute.array ); + const bufferAttributeConstr = attribute.isInstancedBufferAttribute ? InstancedBufferAttribute : BufferAttribute; + bufferAttribute = new bufferAttributeConstr( typedArray, attribute.itemSize, attribute.normalized ); + + } + + if ( attribute.name !== undefined ) bufferAttribute.name = attribute.name; + if ( attribute.usage !== undefined ) bufferAttribute.setUsage( attribute.usage ); + + geometry.setAttribute( key, bufferAttribute ); + + } + + const morphAttributes = json.data.morphAttributes; + + if ( morphAttributes ) { + + for ( const key in morphAttributes ) { + + const attributeArray = morphAttributes[ key ]; + + const array = []; + + for ( let i = 0, il = attributeArray.length; i < il; i ++ ) { + + const attribute = attributeArray[ i ]; + let bufferAttribute; + + if ( attribute.isInterleavedBufferAttribute ) { + + const interleavedBuffer = getInterleavedBuffer( json.data, attribute.data ); + bufferAttribute = new InterleavedBufferAttribute( interleavedBuffer, attribute.itemSize, attribute.offset, attribute.normalized ); + + } else { + + const typedArray = getTypedArray( attribute.type, attribute.array ); + bufferAttribute = new BufferAttribute( typedArray, attribute.itemSize, attribute.normalized ); + + } + + if ( attribute.name !== undefined ) bufferAttribute.name = attribute.name; + array.push( bufferAttribute ); + + } + + geometry.morphAttributes[ key ] = array; + + } + + } + + const morphTargetsRelative = json.data.morphTargetsRelative; + + if ( morphTargetsRelative ) { + + geometry.morphTargetsRelative = true; + + } + + const groups = json.data.groups || json.data.drawcalls || json.data.offsets; + + if ( groups !== undefined ) { + + for ( let i = 0, n = groups.length; i !== n; ++ i ) { + + const group = groups[ i ]; + + geometry.addGroup( group.start, group.count, group.materialIndex ); + + } + + } + + const boundingSphere = json.data.boundingSphere; + + if ( boundingSphere !== undefined ) { + + geometry.boundingSphere = new Sphere().fromJSON( boundingSphere ); + + } + + if ( json.name ) geometry.name = json.name; + if ( json.userData ) geometry.userData = json.userData; + + return geometry; + + } + +} + +/** + * A loader for loading a JSON resource in the [JSON Object/Scene format]{@link https://github.com/mrdoob/three.js/wiki/JSON-Object-Scene-format-4}. + * The files are internally loaded via {@link FileLoader}. + * + * ```js + * const loader = new THREE.ObjectLoader(); + * const obj = await loader.loadAsync( 'models/json/example.json' ); + * scene.add( obj ); + * + * // Alternatively, to parse a previously loaded JSON structure + * const object = await loader.parseAsync( a_json_object ); + * scene.add( object ); + * ``` + * + * @augments Loader + */ +class ObjectLoader extends Loader { + + /** + * Constructs a new object loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ + constructor( manager ) { + + super( manager ); + + } + + /** + * Starts loading from the given URL and pass the loaded 3D object to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(Object3D)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const path = ( this.path === '' ) ? LoaderUtils.extractUrlBase( url ) : this.path; + this.resourcePath = this.resourcePath || path; + + const loader = new FileLoader( this.manager ); + loader.setPath( this.path ); + loader.setRequestHeader( this.requestHeader ); + loader.setWithCredentials( this.withCredentials ); + loader.load( url, function ( text ) { + + let json = null; + + try { + + json = JSON.parse( text ); + + } catch ( error ) { + + if ( onError !== undefined ) onError( error ); + + console.error( 'THREE:ObjectLoader: Can\'t parse ' + url + '.', error.message ); + + return; + + } + + const metadata = json.metadata; + + if ( metadata === undefined || metadata.type === undefined || metadata.type.toLowerCase() === 'geometry' ) { + + if ( onError !== undefined ) onError( new Error( 'THREE.ObjectLoader: Can\'t load ' + url ) ); + + console.error( 'THREE.ObjectLoader: Can\'t load ' + url ); + return; + + } + + scope.parse( json, onLoad ); + + }, onProgress, onError ); + + } + + /** + * Async version of {@link ObjectLoader#load}. + * + * @async + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @return {Promise} A Promise that resolves with the loaded 3D object. + */ + async loadAsync( url, onProgress ) { + + const scope = this; + + const path = ( this.path === '' ) ? LoaderUtils.extractUrlBase( url ) : this.path; + this.resourcePath = this.resourcePath || path; + + const loader = new FileLoader( this.manager ); + loader.setPath( this.path ); + loader.setRequestHeader( this.requestHeader ); + loader.setWithCredentials( this.withCredentials ); + + const text = await loader.loadAsync( url, onProgress ); + + const json = JSON.parse( text ); + + const metadata = json.metadata; + + if ( metadata === undefined || metadata.type === undefined || metadata.type.toLowerCase() === 'geometry' ) { + + throw new Error( 'THREE.ObjectLoader: Can\'t load ' + url ); + + } + + return await scope.parseAsync( json ); + + } + + /** + * Parses the given JSON. This is used internally by {@link ObjectLoader#load} + * but can also be used directly to parse a previously loaded JSON structure. + * + * @param {Object} json - The serialized 3D object. + * @param {onLoad} onLoad - Executed when all resources (e.g. textures) have been fully loaded. + * @return {Object3D} The parsed 3D object. + */ + parse( json, onLoad ) { + + const animations = this.parseAnimations( json.animations ); + const shapes = this.parseShapes( json.shapes ); + const geometries = this.parseGeometries( json.geometries, shapes ); + + const images = this.parseImages( json.images, function () { + + if ( onLoad !== undefined ) onLoad( object ); + + } ); + + const textures = this.parseTextures( json.textures, images ); + const materials = this.parseMaterials( json.materials, textures ); + + const object = this.parseObject( json.object, geometries, materials, textures, animations ); + const skeletons = this.parseSkeletons( json.skeletons, object ); + + this.bindSkeletons( object, skeletons ); + this.bindLightTargets( object ); + + // + + if ( onLoad !== undefined ) { + + let hasImages = false; + + for ( const uuid in images ) { + + if ( images[ uuid ].data instanceof HTMLImageElement ) { + + hasImages = true; + break; + + } + + } + + if ( hasImages === false ) onLoad( object ); + + } + + return object; + + } + + /** + * Async version of {@link ObjectLoader#parse}. + * + * @param {Object} json - The serialized 3D object. + * @return {Promise} A Promise that resolves with the parsed 3D object. + */ + async parseAsync( json ) { + + const animations = this.parseAnimations( json.animations ); + const shapes = this.parseShapes( json.shapes ); + const geometries = this.parseGeometries( json.geometries, shapes ); + + const images = await this.parseImagesAsync( json.images ); + + const textures = this.parseTextures( json.textures, images ); + const materials = this.parseMaterials( json.materials, textures ); + + const object = this.parseObject( json.object, geometries, materials, textures, animations ); + const skeletons = this.parseSkeletons( json.skeletons, object ); + + this.bindSkeletons( object, skeletons ); + this.bindLightTargets( object ); + + return object; + + } + + // internals + + parseShapes( json ) { + + const shapes = {}; + + if ( json !== undefined ) { + + for ( let i = 0, l = json.length; i < l; i ++ ) { + + const shape = new Shape().fromJSON( json[ i ] ); + + shapes[ shape.uuid ] = shape; + + } + + } + + return shapes; + + } + + parseSkeletons( json, object ) { + + const skeletons = {}; + const bones = {}; + + // generate bone lookup table + + object.traverse( function ( child ) { + + if ( child.isBone ) bones[ child.uuid ] = child; + + } ); + + // create skeletons + + if ( json !== undefined ) { + + for ( let i = 0, l = json.length; i < l; i ++ ) { + + const skeleton = new Skeleton().fromJSON( json[ i ], bones ); + + skeletons[ skeleton.uuid ] = skeleton; + + } + + } + + return skeletons; + + } + + parseGeometries( json, shapes ) { + + const geometries = {}; + + if ( json !== undefined ) { + + const bufferGeometryLoader = new BufferGeometryLoader(); + + for ( let i = 0, l = json.length; i < l; i ++ ) { + + let geometry; + const data = json[ i ]; + + switch ( data.type ) { + + case 'BufferGeometry': + case 'InstancedBufferGeometry': + + geometry = bufferGeometryLoader.parse( data ); + break; + + default: + + if ( data.type in Geometries ) { + + geometry = Geometries[ data.type ].fromJSON( data, shapes ); + + } else { + + console.warn( `THREE.ObjectLoader: Unsupported geometry type "${ data.type }"` ); + + } + + } + + geometry.uuid = data.uuid; + + if ( data.name !== undefined ) geometry.name = data.name; + if ( data.userData !== undefined ) geometry.userData = data.userData; + + geometries[ data.uuid ] = geometry; + + } + + } + + return geometries; + + } + + parseMaterials( json, textures ) { + + const cache = {}; // MultiMaterial + const materials = {}; + + if ( json !== undefined ) { + + const loader = new MaterialLoader(); + loader.setTextures( textures ); + + for ( let i = 0, l = json.length; i < l; i ++ ) { + + const data = json[ i ]; + + if ( cache[ data.uuid ] === undefined ) { + + cache[ data.uuid ] = loader.parse( data ); + + } + + materials[ data.uuid ] = cache[ data.uuid ]; + + } + + } + + return materials; + + } + + parseAnimations( json ) { + + const animations = {}; + + if ( json !== undefined ) { + + for ( let i = 0; i < json.length; i ++ ) { + + const data = json[ i ]; + + const clip = AnimationClip.parse( data ); + + animations[ clip.uuid ] = clip; + + } + + } + + return animations; + + } + + parseImages( json, onLoad ) { + + const scope = this; + const images = {}; + + let loader; + + function loadImage( url ) { + + scope.manager.itemStart( url ); + + return loader.load( url, function () { + + scope.manager.itemEnd( url ); + + }, undefined, function () { + + scope.manager.itemError( url ); + scope.manager.itemEnd( url ); + + } ); + + } + + function deserializeImage( image ) { + + if ( typeof image === 'string' ) { + + const url = image; + + const path = /^(\/\/)|([a-z]+:(\/\/)?)/i.test( url ) ? url : scope.resourcePath + url; + + return loadImage( path ); + + } else { + + if ( image.data ) { + + return { + data: getTypedArray( image.type, image.data ), + width: image.width, + height: image.height + }; + + } else { + + return null; + + } + + } + + } + + if ( json !== undefined && json.length > 0 ) { + + const manager = new LoadingManager( onLoad ); + + loader = new ImageLoader( manager ); + loader.setCrossOrigin( this.crossOrigin ); + + for ( let i = 0, il = json.length; i < il; i ++ ) { + + const image = json[ i ]; + const url = image.url; + + if ( Array.isArray( url ) ) { + + // load array of images e.g CubeTexture + + const imageArray = []; + + for ( let j = 0, jl = url.length; j < jl; j ++ ) { + + const currentUrl = url[ j ]; + + const deserializedImage = deserializeImage( currentUrl ); + + if ( deserializedImage !== null ) { + + if ( deserializedImage instanceof HTMLImageElement ) { + + imageArray.push( deserializedImage ); + + } else { + + // special case: handle array of data textures for cube textures + + imageArray.push( new DataTexture( deserializedImage.data, deserializedImage.width, deserializedImage.height ) ); + + } + + } + + } + + images[ image.uuid ] = new Source( imageArray ); + + } else { + + // load single image + + const deserializedImage = deserializeImage( image.url ); + images[ image.uuid ] = new Source( deserializedImage ); + + + } + + } + + } + + return images; + + } + + async parseImagesAsync( json ) { + + const scope = this; + const images = {}; + + let loader; + + async function deserializeImage( image ) { + + if ( typeof image === 'string' ) { + + const url = image; + + const path = /^(\/\/)|([a-z]+:(\/\/)?)/i.test( url ) ? url : scope.resourcePath + url; + + return await loader.loadAsync( path ); + + } else { + + if ( image.data ) { + + return { + data: getTypedArray( image.type, image.data ), + width: image.width, + height: image.height + }; + + } else { + + return null; + + } + + } + + } + + if ( json !== undefined && json.length > 0 ) { + + loader = new ImageLoader( this.manager ); + loader.setCrossOrigin( this.crossOrigin ); + + for ( let i = 0, il = json.length; i < il; i ++ ) { + + const image = json[ i ]; + const url = image.url; + + if ( Array.isArray( url ) ) { + + // load array of images e.g CubeTexture + + const imageArray = []; + + for ( let j = 0, jl = url.length; j < jl; j ++ ) { + + const currentUrl = url[ j ]; + + const deserializedImage = await deserializeImage( currentUrl ); + + if ( deserializedImage !== null ) { + + if ( deserializedImage instanceof HTMLImageElement ) { + + imageArray.push( deserializedImage ); + + } else { + + // special case: handle array of data textures for cube textures + + imageArray.push( new DataTexture( deserializedImage.data, deserializedImage.width, deserializedImage.height ) ); + + } + + } + + } + + images[ image.uuid ] = new Source( imageArray ); + + } else { + + // load single image + + const deserializedImage = await deserializeImage( image.url ); + images[ image.uuid ] = new Source( deserializedImage ); + + } + + } + + } + + return images; + + } + + parseTextures( json, images ) { + + function parseConstant( value, type ) { + + if ( typeof value === 'number' ) return value; + + console.warn( 'THREE.ObjectLoader.parseTexture: Constant should be in numeric form.', value ); + + return type[ value ]; + + } + + const textures = {}; + + if ( json !== undefined ) { + + for ( let i = 0, l = json.length; i < l; i ++ ) { + + const data = json[ i ]; + + if ( data.image === undefined ) { + + console.warn( 'THREE.ObjectLoader: No "image" specified for', data.uuid ); + + } + + if ( images[ data.image ] === undefined ) { + + console.warn( 'THREE.ObjectLoader: Undefined image', data.image ); + + } + + const source = images[ data.image ]; + const image = source.data; + + let texture; + + if ( Array.isArray( image ) ) { + + texture = new CubeTexture(); + + if ( image.length === 6 ) texture.needsUpdate = true; + + } else { + + if ( image && image.data ) { + + texture = new DataTexture(); + + } else { + + texture = new Texture(); + + } + + if ( image ) texture.needsUpdate = true; // textures can have undefined image data + + } + + texture.source = source; + + texture.uuid = data.uuid; + + if ( data.name !== undefined ) texture.name = data.name; + + if ( data.mapping !== undefined ) texture.mapping = parseConstant( data.mapping, TEXTURE_MAPPING ); + if ( data.channel !== undefined ) texture.channel = data.channel; + + if ( data.offset !== undefined ) texture.offset.fromArray( data.offset ); + if ( data.repeat !== undefined ) texture.repeat.fromArray( data.repeat ); + if ( data.center !== undefined ) texture.center.fromArray( data.center ); + if ( data.rotation !== undefined ) texture.rotation = data.rotation; + + if ( data.wrap !== undefined ) { + + texture.wrapS = parseConstant( data.wrap[ 0 ], TEXTURE_WRAPPING ); + texture.wrapT = parseConstant( data.wrap[ 1 ], TEXTURE_WRAPPING ); + + } + + if ( data.format !== undefined ) texture.format = data.format; + if ( data.internalFormat !== undefined ) texture.internalFormat = data.internalFormat; + if ( data.type !== undefined ) texture.type = data.type; + if ( data.colorSpace !== undefined ) texture.colorSpace = data.colorSpace; + + if ( data.minFilter !== undefined ) texture.minFilter = parseConstant( data.minFilter, TEXTURE_FILTER ); + if ( data.magFilter !== undefined ) texture.magFilter = parseConstant( data.magFilter, TEXTURE_FILTER ); + if ( data.anisotropy !== undefined ) texture.anisotropy = data.anisotropy; + + if ( data.flipY !== undefined ) texture.flipY = data.flipY; + + if ( data.generateMipmaps !== undefined ) texture.generateMipmaps = data.generateMipmaps; + if ( data.premultiplyAlpha !== undefined ) texture.premultiplyAlpha = data.premultiplyAlpha; + if ( data.unpackAlignment !== undefined ) texture.unpackAlignment = data.unpackAlignment; + if ( data.compareFunction !== undefined ) texture.compareFunction = data.compareFunction; + + if ( data.userData !== undefined ) texture.userData = data.userData; + + textures[ data.uuid ] = texture; + + } + + } + + return textures; + + } + + parseObject( data, geometries, materials, textures, animations ) { + + let object; + + function getGeometry( name ) { + + if ( geometries[ name ] === undefined ) { + + console.warn( 'THREE.ObjectLoader: Undefined geometry', name ); + + } + + return geometries[ name ]; + + } + + function getMaterial( name ) { + + if ( name === undefined ) return undefined; + + if ( Array.isArray( name ) ) { + + const array = []; + + for ( let i = 0, l = name.length; i < l; i ++ ) { + + const uuid = name[ i ]; + + if ( materials[ uuid ] === undefined ) { + + console.warn( 'THREE.ObjectLoader: Undefined material', uuid ); + + } + + array.push( materials[ uuid ] ); + + } + + return array; + + } + + if ( materials[ name ] === undefined ) { + + console.warn( 'THREE.ObjectLoader: Undefined material', name ); + + } + + return materials[ name ]; + + } + + function getTexture( uuid ) { + + if ( textures[ uuid ] === undefined ) { + + console.warn( 'THREE.ObjectLoader: Undefined texture', uuid ); + + } + + return textures[ uuid ]; + + } + + let geometry, material; + + switch ( data.type ) { + + case 'Scene': + + object = new Scene(); + + if ( data.background !== undefined ) { + + if ( Number.isInteger( data.background ) ) { + + object.background = new Color( data.background ); + + } else { + + object.background = getTexture( data.background ); + + } + + } + + if ( data.environment !== undefined ) { + + object.environment = getTexture( data.environment ); + + } + + if ( data.fog !== undefined ) { + + if ( data.fog.type === 'Fog' ) { + + object.fog = new Fog( data.fog.color, data.fog.near, data.fog.far ); + + } else if ( data.fog.type === 'FogExp2' ) { + + object.fog = new FogExp2( data.fog.color, data.fog.density ); + + } + + if ( data.fog.name !== '' ) { + + object.fog.name = data.fog.name; + + } + + } + + if ( data.backgroundBlurriness !== undefined ) object.backgroundBlurriness = data.backgroundBlurriness; + if ( data.backgroundIntensity !== undefined ) object.backgroundIntensity = data.backgroundIntensity; + if ( data.backgroundRotation !== undefined ) object.backgroundRotation.fromArray( data.backgroundRotation ); + + if ( data.environmentIntensity !== undefined ) object.environmentIntensity = data.environmentIntensity; + if ( data.environmentRotation !== undefined ) object.environmentRotation.fromArray( data.environmentRotation ); + + break; + + case 'PerspectiveCamera': + + object = new PerspectiveCamera( data.fov, data.aspect, data.near, data.far ); + + if ( data.focus !== undefined ) object.focus = data.focus; + if ( data.zoom !== undefined ) object.zoom = data.zoom; + if ( data.filmGauge !== undefined ) object.filmGauge = data.filmGauge; + if ( data.filmOffset !== undefined ) object.filmOffset = data.filmOffset; + if ( data.view !== undefined ) object.view = Object.assign( {}, data.view ); + + break; + + case 'OrthographicCamera': + + object = new OrthographicCamera( data.left, data.right, data.top, data.bottom, data.near, data.far ); + + if ( data.zoom !== undefined ) object.zoom = data.zoom; + if ( data.view !== undefined ) object.view = Object.assign( {}, data.view ); + + break; + + case 'AmbientLight': + + object = new AmbientLight( data.color, data.intensity ); + + break; + + case 'DirectionalLight': + + object = new DirectionalLight( data.color, data.intensity ); + object.target = data.target || ''; + + break; + + case 'PointLight': + + object = new PointLight( data.color, data.intensity, data.distance, data.decay ); + + break; + + case 'RectAreaLight': + + object = new RectAreaLight( data.color, data.intensity, data.width, data.height ); + + break; + + case 'SpotLight': + + object = new SpotLight( data.color, data.intensity, data.distance, data.angle, data.penumbra, data.decay ); + object.target = data.target || ''; + + break; + + case 'HemisphereLight': + + object = new HemisphereLight( data.color, data.groundColor, data.intensity ); + + break; + + case 'LightProbe': + + object = new LightProbe().fromJSON( data ); + + break; + + case 'SkinnedMesh': + + geometry = getGeometry( data.geometry ); + material = getMaterial( data.material ); + + object = new SkinnedMesh( geometry, material ); + + if ( data.bindMode !== undefined ) object.bindMode = data.bindMode; + if ( data.bindMatrix !== undefined ) object.bindMatrix.fromArray( data.bindMatrix ); + if ( data.skeleton !== undefined ) object.skeleton = data.skeleton; + + break; + + case 'Mesh': + + geometry = getGeometry( data.geometry ); + material = getMaterial( data.material ); + + object = new Mesh( geometry, material ); + + break; + + case 'InstancedMesh': + + geometry = getGeometry( data.geometry ); + material = getMaterial( data.material ); + const count = data.count; + const instanceMatrix = data.instanceMatrix; + const instanceColor = data.instanceColor; + + object = new InstancedMesh( geometry, material, count ); + object.instanceMatrix = new InstancedBufferAttribute( new Float32Array( instanceMatrix.array ), 16 ); + if ( instanceColor !== undefined ) object.instanceColor = new InstancedBufferAttribute( new Float32Array( instanceColor.array ), instanceColor.itemSize ); + + break; + + case 'BatchedMesh': + + geometry = getGeometry( data.geometry ); + material = getMaterial( data.material ); + + object = new BatchedMesh( data.maxInstanceCount, data.maxVertexCount, data.maxIndexCount, material ); + object.geometry = geometry; + object.perObjectFrustumCulled = data.perObjectFrustumCulled; + object.sortObjects = data.sortObjects; + + object._drawRanges = data.drawRanges; + object._reservedRanges = data.reservedRanges; + + object._geometryInfo = data.geometryInfo.map( info => { + + let box = null; + let sphere = null; + if ( info.boundingBox !== undefined ) { + + box = new Box3().fromJSON( info.boundingBox ); + + } + + if ( info.boundingSphere !== undefined ) { + + sphere = new Sphere().fromJSON( info.boundingSphere ); + + } + + return { + ...info, + boundingBox: box, + boundingSphere: sphere + }; + + } ); + object._instanceInfo = data.instanceInfo; + + object._availableInstanceIds = data._availableInstanceIds; + object._availableGeometryIds = data._availableGeometryIds; + + object._nextIndexStart = data.nextIndexStart; + object._nextVertexStart = data.nextVertexStart; + object._geometryCount = data.geometryCount; + + object._maxInstanceCount = data.maxInstanceCount; + object._maxVertexCount = data.maxVertexCount; + object._maxIndexCount = data.maxIndexCount; + + object._geometryInitialized = data.geometryInitialized; + + object._matricesTexture = getTexture( data.matricesTexture.uuid ); + + object._indirectTexture = getTexture( data.indirectTexture.uuid ); + + if ( data.colorsTexture !== undefined ) { + + object._colorsTexture = getTexture( data.colorsTexture.uuid ); + + } + + if ( data.boundingSphere !== undefined ) { + + object.boundingSphere = new Sphere().fromJSON( data.boundingSphere ); + + } + + if ( data.boundingBox !== undefined ) { + + object.boundingBox = new Box3().fromJSON( data.boundingBox ); + + } + + break; + + case 'LOD': + + object = new LOD(); + + break; + + case 'Line': + + object = new Line( getGeometry( data.geometry ), getMaterial( data.material ) ); + + break; + + case 'LineLoop': + + object = new LineLoop( getGeometry( data.geometry ), getMaterial( data.material ) ); + + break; + + case 'LineSegments': + + object = new LineSegments( getGeometry( data.geometry ), getMaterial( data.material ) ); + + break; + + case 'PointCloud': + case 'Points': + + object = new Points( getGeometry( data.geometry ), getMaterial( data.material ) ); + + break; + + case 'Sprite': + + object = new Sprite( getMaterial( data.material ) ); + + break; + + case 'Group': + + object = new Group(); + + break; + + case 'Bone': + + object = new Bone(); + + break; + + default: + + object = new Object3D(); + + } + + object.uuid = data.uuid; + + if ( data.name !== undefined ) object.name = data.name; + + if ( data.matrix !== undefined ) { + + object.matrix.fromArray( data.matrix ); + + if ( data.matrixAutoUpdate !== undefined ) object.matrixAutoUpdate = data.matrixAutoUpdate; + if ( object.matrixAutoUpdate ) object.matrix.decompose( object.position, object.quaternion, object.scale ); + + } else { + + if ( data.position !== undefined ) object.position.fromArray( data.position ); + if ( data.rotation !== undefined ) object.rotation.fromArray( data.rotation ); + if ( data.quaternion !== undefined ) object.quaternion.fromArray( data.quaternion ); + if ( data.scale !== undefined ) object.scale.fromArray( data.scale ); + + } + + if ( data.up !== undefined ) object.up.fromArray( data.up ); + + if ( data.castShadow !== undefined ) object.castShadow = data.castShadow; + if ( data.receiveShadow !== undefined ) object.receiveShadow = data.receiveShadow; + + if ( data.shadow ) { + + if ( data.shadow.intensity !== undefined ) object.shadow.intensity = data.shadow.intensity; + if ( data.shadow.bias !== undefined ) object.shadow.bias = data.shadow.bias; + if ( data.shadow.normalBias !== undefined ) object.shadow.normalBias = data.shadow.normalBias; + if ( data.shadow.radius !== undefined ) object.shadow.radius = data.shadow.radius; + if ( data.shadow.mapSize !== undefined ) object.shadow.mapSize.fromArray( data.shadow.mapSize ); + if ( data.shadow.camera !== undefined ) object.shadow.camera = this.parseObject( data.shadow.camera ); + + } + + if ( data.visible !== undefined ) object.visible = data.visible; + if ( data.frustumCulled !== undefined ) object.frustumCulled = data.frustumCulled; + if ( data.renderOrder !== undefined ) object.renderOrder = data.renderOrder; + if ( data.userData !== undefined ) object.userData = data.userData; + if ( data.layers !== undefined ) object.layers.mask = data.layers; + + if ( data.children !== undefined ) { + + const children = data.children; + + for ( let i = 0; i < children.length; i ++ ) { + + object.add( this.parseObject( children[ i ], geometries, materials, textures, animations ) ); + + } + + } + + if ( data.animations !== undefined ) { + + const objectAnimations = data.animations; + + for ( let i = 0; i < objectAnimations.length; i ++ ) { + + const uuid = objectAnimations[ i ]; + + object.animations.push( animations[ uuid ] ); + + } + + } + + if ( data.type === 'LOD' ) { + + if ( data.autoUpdate !== undefined ) object.autoUpdate = data.autoUpdate; + + const levels = data.levels; + + for ( let l = 0; l < levels.length; l ++ ) { + + const level = levels[ l ]; + const child = object.getObjectByProperty( 'uuid', level.object ); + + if ( child !== undefined ) { + + object.addLevel( child, level.distance, level.hysteresis ); + + } + + } + + } + + return object; + + } + + bindSkeletons( object, skeletons ) { + + if ( Object.keys( skeletons ).length === 0 ) return; + + object.traverse( function ( child ) { + + if ( child.isSkinnedMesh === true && child.skeleton !== undefined ) { + + const skeleton = skeletons[ child.skeleton ]; + + if ( skeleton === undefined ) { + + console.warn( 'THREE.ObjectLoader: No skeleton found with UUID:', child.skeleton ); + + } else { + + child.bind( skeleton, child.bindMatrix ); + + } + + } + + } ); + + } + + bindLightTargets( object ) { + + object.traverse( function ( child ) { + + if ( child.isDirectionalLight || child.isSpotLight ) { + + const uuid = child.target; + + const target = object.getObjectByProperty( 'uuid', uuid ); + + if ( target !== undefined ) { + + child.target = target; + + } else { + + child.target = new Object3D(); + + } + + } + + } ); + + } + +} + +const TEXTURE_MAPPING = { + UVMapping: UVMapping, + CubeReflectionMapping: CubeReflectionMapping, + CubeRefractionMapping: CubeRefractionMapping, + EquirectangularReflectionMapping: EquirectangularReflectionMapping, + EquirectangularRefractionMapping: EquirectangularRefractionMapping, + CubeUVReflectionMapping: CubeUVReflectionMapping +}; + +const TEXTURE_WRAPPING = { + RepeatWrapping: RepeatWrapping, + ClampToEdgeWrapping: ClampToEdgeWrapping, + MirroredRepeatWrapping: MirroredRepeatWrapping +}; + +const TEXTURE_FILTER = { + NearestFilter: NearestFilter, + NearestMipmapNearestFilter: NearestMipmapNearestFilter, + NearestMipmapLinearFilter: NearestMipmapLinearFilter, + LinearFilter: LinearFilter, + LinearMipmapNearestFilter: LinearMipmapNearestFilter, + LinearMipmapLinearFilter: LinearMipmapLinearFilter +}; + +const _errorMap = new WeakMap(); + +/** + * A loader for loading images as an [ImageBitmap]{@link https://developer.mozilla.org/en-US/docs/Web/API/ImageBitmap}. + * An `ImageBitmap` provides an asynchronous and resource efficient pathway to prepare + * textures for rendering. + * + * Note that {@link Texture#flipY} and {@link Texture#premultiplyAlpha} are ignored with image bitmaps. + * They needs these configuration on bitmap creation unlike regular images need them on uploading to GPU. + * + * You need to set the equivalent options via {@link ImageBitmapLoader#setOptions} instead. + * + * Also note that unlike {@link FileLoader}, this loader avoids multiple concurrent requests to the same URL only if `Cache` is enabled. + * + * ```js + * const loader = new THREE.ImageBitmapLoader(); + * loader.setOptions( { imageOrientation: 'flipY' } ); // set options if needed + * const imageBitmap = await loader.loadAsync( 'image.png' ); + * + * const texture = new THREE.Texture( imageBitmap ); + * texture.needsUpdate = true; + * ``` + * + * @augments Loader + */ +class ImageBitmapLoader extends Loader { + + /** + * Constructs a new image bitmap loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ + constructor( manager ) { + + super( manager ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isImageBitmapLoader = true; + + if ( typeof createImageBitmap === 'undefined' ) { + + console.warn( 'THREE.ImageBitmapLoader: createImageBitmap() not supported.' ); + + } + + if ( typeof fetch === 'undefined' ) { + + console.warn( 'THREE.ImageBitmapLoader: fetch() not supported.' ); + + } + + /** + * Represents the loader options. + * + * @type {Object} + * @default {premultiplyAlpha:'none'} + */ + this.options = { premultiplyAlpha: 'none' }; + + } + + /** + * Sets the given loader options. The structure of the object must match the `options` parameter of + * [createImageBitmap]{@link https://developer.mozilla.org/en-US/docs/Web/API/Window/createImageBitmap}. + * + * @param {Object} options - The loader options to set. + * @return {ImageBitmapLoader} A reference to this image bitmap loader. + */ + setOptions( options ) { + + this.options = options; + + return this; + + } + + /** + * Starts loading from the given URL and pass the loaded image bitmap to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(ImageBitmap)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Unsupported in this loader. + * @param {onErrorCallback} onError - Executed when errors occur. + * @return {ImageBitmap|undefined} The image bitmap. + */ + load( url, onLoad, onProgress, onError ) { + + if ( url === undefined ) url = ''; + + if ( this.path !== undefined ) url = this.path + url; + + url = this.manager.resolveURL( url ); + + const scope = this; + + const cached = Cache.get( `image-bitmap:${url}` ); + + if ( cached !== undefined ) { + + scope.manager.itemStart( url ); + + // If cached is a promise, wait for it to resolve + if ( cached.then ) { + + cached.then( imageBitmap => { + + // check if there is an error for the cached promise + + if ( _errorMap.has( cached ) === true ) { + + if ( onError ) onError( _errorMap.get( cached ) ); + + scope.manager.itemError( url ); + scope.manager.itemEnd( url ); + + } else { + + if ( onLoad ) onLoad( imageBitmap ); + + scope.manager.itemEnd( url ); + + return imageBitmap; + + } + + } ); + + return; + + } + + // If cached is not a promise (i.e., it's already an imageBitmap) + setTimeout( function () { + + if ( onLoad ) onLoad( cached ); + + scope.manager.itemEnd( url ); + + }, 0 ); + + return cached; + + } + + const fetchOptions = {}; + fetchOptions.credentials = ( this.crossOrigin === 'anonymous' ) ? 'same-origin' : 'include'; + fetchOptions.headers = this.requestHeader; + + const promise = fetch( url, fetchOptions ).then( function ( res ) { + + return res.blob(); + + } ).then( function ( blob ) { + + return createImageBitmap( blob, Object.assign( scope.options, { colorSpaceConversion: 'none' } ) ); + + } ).then( function ( imageBitmap ) { + + Cache.add( `image-bitmap:${url}`, imageBitmap ); + + if ( onLoad ) onLoad( imageBitmap ); + + scope.manager.itemEnd( url ); + + return imageBitmap; + + } ).catch( function ( e ) { + + if ( onError ) onError( e ); + + _errorMap.set( promise, e ); + + Cache.remove( `image-bitmap:${url}` ); + + scope.manager.itemError( url ); + scope.manager.itemEnd( url ); + + } ); + + Cache.add( `image-bitmap:${url}`, promise ); + scope.manager.itemStart( url ); + + } + +} + +let _context; + +/** + * Manages the global audio context in the engine. + * + * @hideconstructor + */ +class AudioContext { + + /** + * Returns the global native audio context. + * + * @return {AudioContext} The native audio context. + */ + static getContext() { + + if ( _context === undefined ) { + + _context = new ( window.AudioContext || window.webkitAudioContext )(); + + } + + return _context; + + } + + /** + * Allows to set the global native audio context from outside. + * + * @param {AudioContext} value - The native context to set. + */ + static setContext( value ) { + + _context = value; + + } + +} + +/** + * Class for loading audio buffers. Audios are internally + * loaded via {@link FileLoader}. + * + * ```js + * const audioListener = new THREE.AudioListener(); + * const ambientSound = new THREE.Audio( audioListener ); + * + * const loader = new THREE.AudioLoader(); + * const audioBuffer = await loader.loadAsync( 'audio/ambient_ocean.ogg' ); + * + * ambientSound.setBuffer( audioBuffer ); + * ambientSound.play(); + * ``` + * + * @augments Loader + */ +class AudioLoader extends Loader { + + /** + * Constructs a new audio loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ + constructor( manager ) { + + super( manager ); + + } + + /** + * Starts loading from the given URL and passes the loaded audio buffer + * to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(AudioBuffer)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const loader = new FileLoader( this.manager ); + loader.setResponseType( 'arraybuffer' ); + loader.setPath( this.path ); + loader.setRequestHeader( this.requestHeader ); + loader.setWithCredentials( this.withCredentials ); + loader.load( url, function ( buffer ) { + + try { + + // Create a copy of the buffer. The `decodeAudioData` method + // detaches the buffer when complete, preventing reuse. + const bufferCopy = buffer.slice( 0 ); + + const context = AudioContext.getContext(); + context.decodeAudioData( bufferCopy, function ( audioBuffer ) { + + onLoad( audioBuffer ); + + } ).catch( handleError ); + + } catch ( e ) { + + handleError( e ); + + } + + }, onProgress, onError ); + + function handleError( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + } + +} + +const _eyeRight = /*@__PURE__*/ new Matrix4(); +const _eyeLeft = /*@__PURE__*/ new Matrix4(); +const _projectionMatrix = /*@__PURE__*/ new Matrix4(); + +/** + * A special type of camera that uses two perspective cameras with + * stereoscopic projection. Can be used for rendering stereo effects + * like [3D Anaglyph]{@link https://en.wikipedia.org/wiki/Anaglyph_3D} or + * [Parallax Barrier]{@link https://en.wikipedia.org/wiki/parallax_barrier}. + */ +class StereoCamera { + + /** + * Constructs a new stereo camera. + */ + constructor() { + + /** + * The type property is used for detecting the object type + * in context of serialization/deserialization. + * + * @type {string} + * @readonly + */ + this.type = 'StereoCamera'; + + /** + * The aspect. + * + * @type {number} + * @default 1 + */ + this.aspect = 1; + + /** + * The eye separation which represents the distance + * between the left and right camera. + * + * @type {number} + * @default 0.064 + */ + this.eyeSep = 0.064; + + /** + * The camera representing the left eye. This is added to layer `1` so objects to be + * rendered by the left camera must also be added to this layer. + * + * @type {PerspectiveCamera} + */ + this.cameraL = new PerspectiveCamera(); + this.cameraL.layers.enable( 1 ); + this.cameraL.matrixAutoUpdate = false; + + /** + * The camera representing the right eye. This is added to layer `2` so objects to be + * rendered by the right camera must also be added to this layer. + * + * @type {PerspectiveCamera} + */ + this.cameraR = new PerspectiveCamera(); + this.cameraR.layers.enable( 2 ); + this.cameraR.matrixAutoUpdate = false; + + this._cache = { + focus: null, + fov: null, + aspect: null, + near: null, + far: null, + zoom: null, + eyeSep: null + }; + + } + + /** + * Updates the stereo camera based on the given perspective camera. + * + * @param {PerspectiveCamera} camera - The perspective camera. + */ + update( camera ) { + + const cache = this._cache; + + const needsUpdate = cache.focus !== camera.focus || cache.fov !== camera.fov || + cache.aspect !== camera.aspect * this.aspect || cache.near !== camera.near || + cache.far !== camera.far || cache.zoom !== camera.zoom || cache.eyeSep !== this.eyeSep; + + if ( needsUpdate ) { + + cache.focus = camera.focus; + cache.fov = camera.fov; + cache.aspect = camera.aspect * this.aspect; + cache.near = camera.near; + cache.far = camera.far; + cache.zoom = camera.zoom; + cache.eyeSep = this.eyeSep; + + // Off-axis stereoscopic effect based on + // http://paulbourke.net/stereographics/stereorender/ + + _projectionMatrix.copy( camera.projectionMatrix ); + const eyeSepHalf = cache.eyeSep / 2; + const eyeSepOnProjection = eyeSepHalf * cache.near / cache.focus; + const ymax = ( cache.near * Math.tan( DEG2RAD * cache.fov * 0.5 ) ) / cache.zoom; + let xmin, xmax; + + // translate xOffset + + _eyeLeft.elements[ 12 ] = - eyeSepHalf; + _eyeRight.elements[ 12 ] = eyeSepHalf; + + // for left eye + + xmin = - ymax * cache.aspect + eyeSepOnProjection; + xmax = ymax * cache.aspect + eyeSepOnProjection; + + _projectionMatrix.elements[ 0 ] = 2 * cache.near / ( xmax - xmin ); + _projectionMatrix.elements[ 8 ] = ( xmax + xmin ) / ( xmax - xmin ); + + this.cameraL.projectionMatrix.copy( _projectionMatrix ); + + // for right eye + + xmin = - ymax * cache.aspect - eyeSepOnProjection; + xmax = ymax * cache.aspect - eyeSepOnProjection; + + _projectionMatrix.elements[ 0 ] = 2 * cache.near / ( xmax - xmin ); + _projectionMatrix.elements[ 8 ] = ( xmax + xmin ) / ( xmax - xmin ); + + this.cameraR.projectionMatrix.copy( _projectionMatrix ); + + } + + this.cameraL.matrixWorld.copy( camera.matrixWorld ).multiply( _eyeLeft ); + this.cameraR.matrixWorld.copy( camera.matrixWorld ).multiply( _eyeRight ); + + } + +} + +/** + * This type of camera can be used in order to efficiently render a scene with a + * predefined set of cameras. This is an important performance aspect for + * rendering VR scenes. + * + * An instance of `ArrayCamera` always has an array of sub cameras. It's mandatory + * to define for each sub camera the `viewport` property which determines the + * part of the viewport that is rendered with this camera. + * + * @augments PerspectiveCamera + */ +class ArrayCamera extends PerspectiveCamera { + + /** + * Constructs a new array camera. + * + * @param {Array} [array=[]] - An array of perspective sub cameras. + */ + constructor( array = [] ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isArrayCamera = true; + + /** + * Whether this camera is used with multiview rendering or not. + * + * @type {boolean} + * @readonly + * @default false + */ + this.isMultiViewCamera = false; + + /** + * An array of perspective sub cameras. + * + * @type {Array} + */ + this.cameras = array; + + } + +} + +/** + * Class for keeping track of time. + */ +class Clock { + + /** + * Constructs a new clock. + * + * @param {boolean} [autoStart=true] - Whether to automatically start the clock when + * `getDelta()` is called for the first time. + */ + constructor( autoStart = true ) { + + /** + * If set to `true`, the clock starts automatically when `getDelta()` is called + * for the first time. + * + * @type {boolean} + * @default true + */ + this.autoStart = autoStart; + + /** + * Holds the time at which the clock's `start()` method was last called. + * + * @type {number} + * @default 0 + */ + this.startTime = 0; + + /** + * Holds the time at which the clock's `start()`, `getElapsedTime()` or + * `getDelta()` methods were last called. + * + * @type {number} + * @default 0 + */ + this.oldTime = 0; + + /** + * Keeps track of the total time that the clock has been running. + * + * @type {number} + * @default 0 + */ + this.elapsedTime = 0; + + /** + * Whether the clock is running or not. + * + * @type {boolean} + * @default true + */ + this.running = false; + + } + + /** + * Starts the clock. When `autoStart` is set to `true`, the method is automatically + * called by the class. + */ + start() { + + this.startTime = performance.now(); + + this.oldTime = this.startTime; + this.elapsedTime = 0; + this.running = true; + + } + + /** + * Stops the clock. + */ + stop() { + + this.getElapsedTime(); + this.running = false; + this.autoStart = false; + + } + + /** + * Returns the elapsed time in seconds. + * + * @return {number} The elapsed time. + */ + getElapsedTime() { + + this.getDelta(); + return this.elapsedTime; + + } + + /** + * Returns the delta time in seconds. + * + * @return {number} The delta time. + */ + getDelta() { + + let diff = 0; + + if ( this.autoStart && ! this.running ) { + + this.start(); + return 0; + + } + + if ( this.running ) { + + const newTime = performance.now(); + + diff = ( newTime - this.oldTime ) / 1000; + this.oldTime = newTime; + + this.elapsedTime += diff; + + } + + return diff; + + } + +} + +const _position$1 = /*@__PURE__*/ new Vector3(); +const _quaternion$1 = /*@__PURE__*/ new Quaternion(); +const _scale$1 = /*@__PURE__*/ new Vector3(); + +const _forward = /*@__PURE__*/ new Vector3(); +const _up = /*@__PURE__*/ new Vector3(); + +/** + * The class represents a virtual listener of the all positional and non-positional audio effects + * in the scene. A three.js application usually creates a single listener. It is a mandatory + * constructor parameter for audios entities like {@link Audio} and {@link PositionalAudio}. + * + * In most cases, the listener object is a child of the camera. So the 3D transformation of the + * camera represents the 3D transformation of the listener. + * + * @augments Object3D + */ +class AudioListener extends Object3D { + + /** + * Constructs a new audio listener. + */ + constructor() { + + super(); + + this.type = 'AudioListener'; + + /** + * The native audio context. + * + * @type {AudioContext} + * @readonly + */ + this.context = AudioContext.getContext(); + + /** + * The gain node used for volume control. + * + * @type {GainNode} + * @readonly + */ + this.gain = this.context.createGain(); + this.gain.connect( this.context.destination ); + + /** + * An optional filter. + * + * Defined via {@link AudioListener#setFilter}. + * + * @type {?AudioNode} + * @default null + * @readonly + */ + this.filter = null; + + /** + * Time delta values required for `linearRampToValueAtTime()` usage. + * + * @type {number} + * @default 0 + * @readonly + */ + this.timeDelta = 0; + + // private + + this._clock = new Clock(); + + } + + /** + * Returns the listener's input node. + * + * This method is used by other audio nodes to connect to this listener. + * + * @return {GainNode} The input node. + */ + getInput() { + + return this.gain; + + } + + /** + * Removes the current filter from this listener. + * + * @return {AudioListener} A reference to this listener. + */ + removeFilter() { + + if ( this.filter !== null ) { + + this.gain.disconnect( this.filter ); + this.filter.disconnect( this.context.destination ); + this.gain.connect( this.context.destination ); + this.filter = null; + + } + + return this; + + } + + /** + * Returns the current set filter. + * + * @return {?AudioNode} The filter. + */ + getFilter() { + + return this.filter; + + } + + /** + * Sets the given filter to this listener. + * + * @param {AudioNode} value - The filter to set. + * @return {AudioListener} A reference to this listener. + */ + setFilter( value ) { + + if ( this.filter !== null ) { + + this.gain.disconnect( this.filter ); + this.filter.disconnect( this.context.destination ); + + } else { + + this.gain.disconnect( this.context.destination ); + + } + + this.filter = value; + this.gain.connect( this.filter ); + this.filter.connect( this.context.destination ); + + return this; + + } + + /** + * Returns the applications master volume. + * + * @return {number} The master volume. + */ + getMasterVolume() { + + return this.gain.gain.value; + + } + + /** + * Sets the applications master volume. This volume setting affects + * all audio nodes in the scene. + * + * @param {number} value - The master volume to set. + * @return {AudioListener} A reference to this listener. + */ + setMasterVolume( value ) { + + this.gain.gain.setTargetAtTime( value, this.context.currentTime, 0.01 ); + + return this; + + } + + updateMatrixWorld( force ) { + + super.updateMatrixWorld( force ); + + const listener = this.context.listener; + + this.timeDelta = this._clock.getDelta(); + + this.matrixWorld.decompose( _position$1, _quaternion$1, _scale$1 ); + + // the initial forward and up directions must be orthogonal + _forward.set( 0, 0, -1 ).applyQuaternion( _quaternion$1 ); + _up.set( 0, 1, 0 ).applyQuaternion( _quaternion$1 ); + + if ( listener.positionX ) { + + // code path for Chrome (see #14393) + + const endTime = this.context.currentTime + this.timeDelta; + + listener.positionX.linearRampToValueAtTime( _position$1.x, endTime ); + listener.positionY.linearRampToValueAtTime( _position$1.y, endTime ); + listener.positionZ.linearRampToValueAtTime( _position$1.z, endTime ); + listener.forwardX.linearRampToValueAtTime( _forward.x, endTime ); + listener.forwardY.linearRampToValueAtTime( _forward.y, endTime ); + listener.forwardZ.linearRampToValueAtTime( _forward.z, endTime ); + listener.upX.linearRampToValueAtTime( _up.x, endTime ); + listener.upY.linearRampToValueAtTime( _up.y, endTime ); + listener.upZ.linearRampToValueAtTime( _up.z, endTime ); + + } else { + + listener.setPosition( _position$1.x, _position$1.y, _position$1.z ); + listener.setOrientation( _forward.x, _forward.y, _forward.z, _up.x, _up.y, _up.z ); + + } + + } + +} + +/** + * Represents a non-positional ( global ) audio object. + * + * This and related audio modules make use of the [Web Audio API]{@link https://www.w3.org/TR/webaudio-1.1/}. + * + * ```js + * // create an AudioListener and add it to the camera + * const listener = new THREE.AudioListener(); + * camera.add( listener ); + * + * // create a global audio source + * const sound = new THREE.Audio( listener ); + * + * // load a sound and set it as the Audio object's buffer + * const audioLoader = new THREE.AudioLoader(); + * audioLoader.load( 'sounds/ambient.ogg', function( buffer ) { + * sound.setBuffer( buffer ); + * sound.setLoop( true ); + * sound.setVolume( 0.5 ); + * sound.play(); + * }); + * ``` + * + * @augments Object3D + */ +class Audio extends Object3D { + + /** + * Constructs a new audio. + * + * @param {AudioListener} listener - The global audio listener. + */ + constructor( listener ) { + + super(); + + this.type = 'Audio'; + + /** + * The global audio listener. + * + * @type {AudioListener} + * @readonly + */ + this.listener = listener; + + /** + * The audio context. + * + * @type {AudioContext} + * @readonly + */ + this.context = listener.context; + + /** + * The gain node used for volume control. + * + * @type {GainNode} + * @readonly + */ + this.gain = this.context.createGain(); + this.gain.connect( listener.getInput() ); + + /** + * Whether to start playback automatically or not. + * + * @type {boolean} + * @default false + */ + this.autoplay = false; + + /** + * A reference to an audio buffer. + * + * Defined via {@link Audio#setBuffer}. + * + * @type {?AudioBuffer} + * @default null + * @readonly + */ + this.buffer = null; + + /** + * Modify pitch, measured in cents. +/- 100 is a semitone. + * +/- 1200 is an octave. + * + * Defined via {@link Audio#setDetune}. + * + * @type {number} + * @default 0 + * @readonly + */ + this.detune = 0; + + /** + * Whether the audio should loop or not. + * + * Defined via {@link Audio#setLoop}. + * + * @type {boolean} + * @default false + * @readonly + */ + this.loop = false; + + /** + * Defines where in the audio buffer the replay should + * start, in seconds. + * + * @type {number} + * @default 0 + */ + this.loopStart = 0; + + /** + * Defines where in the audio buffer the replay should + * stop, in seconds. + * + * @type {number} + * @default 0 + */ + this.loopEnd = 0; + + /** + * An offset to the time within the audio buffer the playback + * should begin, in seconds. + * + * @type {number} + * @default 0 + */ + this.offset = 0; + + /** + * Overrides the default duration of the audio. + * + * @type {undefined|number} + * @default undefined + */ + this.duration = undefined; + + /** + * The playback speed. + * + * Defined via {@link Audio#setPlaybackRate}. + * + * @type {number} + * @readonly + * @default 1 + */ + this.playbackRate = 1; + + /** + * Indicates whether the audio is playing or not. + * + * This flag will be automatically set when using {@link Audio#play}, + * {@link Audio#pause}, {@link Audio#stop}. + * + * @type {boolean} + * @readonly + * @default false + */ + this.isPlaying = false; + + /** + * Indicates whether the audio playback can be controlled + * with method like {@link Audio#play} or {@link Audio#pause}. + * + * This flag will be automatically set when audio sources are + * defined. + * + * @type {boolean} + * @readonly + * @default true + */ + this.hasPlaybackControl = true; + + /** + * Holds a reference to the current audio source. + * + * The property is automatically by one of the `set*()` methods. + * + * @type {?AudioNode} + * @readonly + * @default null + */ + this.source = null; + + /** + * Defines the source type. + * + * The property is automatically by one of the `set*()` methods. + * + * @type {('empty'|'audioNode'|'mediaNode'|'mediaStreamNode'|'buffer')} + * @readonly + * @default 'empty' + */ + this.sourceType = 'empty'; + + this._startedAt = 0; + this._progress = 0; + this._connected = false; + + /** + * Can be used to apply a variety of low-order filters to create + * more complex sound effects e.g. via `BiquadFilterNode`. + * + * The property is automatically set by {@link Audio#setFilters}. + * + * @type {Array} + * @readonly + */ + this.filters = []; + + } + + /** + * Returns the output audio node. + * + * @return {GainNode} The output node. + */ + getOutput() { + + return this.gain; + + } + + /** + * Sets the given audio node as the source of this instance. + * + * {@link Audio#sourceType} is set to `audioNode` and {@link Audio#hasPlaybackControl} to `false`. + * + * @param {AudioNode} audioNode - The audio node like an instance of `OscillatorNode`. + * @return {Audio} A reference to this instance. + */ + setNodeSource( audioNode ) { + + this.hasPlaybackControl = false; + this.sourceType = 'audioNode'; + this.source = audioNode; + this.connect(); + + return this; + + } + + /** + * Sets the given media element as the source of this instance. + * + * {@link Audio#sourceType} is set to `mediaNode` and {@link Audio#hasPlaybackControl} to `false`. + * + * @param {HTMLMediaElement} mediaElement - The media element. + * @return {Audio} A reference to this instance. + */ + setMediaElementSource( mediaElement ) { + + this.hasPlaybackControl = false; + this.sourceType = 'mediaNode'; + this.source = this.context.createMediaElementSource( mediaElement ); + this.connect(); + + return this; + + } + + /** + * Sets the given media stream as the source of this instance. + * + * {@link Audio#sourceType} is set to `mediaStreamNode` and {@link Audio#hasPlaybackControl} to `false`. + * + * @param {MediaStream} mediaStream - The media stream. + * @return {Audio} A reference to this instance. + */ + setMediaStreamSource( mediaStream ) { + + this.hasPlaybackControl = false; + this.sourceType = 'mediaStreamNode'; + this.source = this.context.createMediaStreamSource( mediaStream ); + this.connect(); + + return this; + + } + + /** + * Sets the given audio buffer as the source of this instance. + * + * {@link Audio#sourceType} is set to `buffer` and {@link Audio#hasPlaybackControl} to `true`. + * + * @param {AudioBuffer} audioBuffer - The audio buffer. + * @return {Audio} A reference to this instance. + */ + setBuffer( audioBuffer ) { + + this.buffer = audioBuffer; + this.sourceType = 'buffer'; + + if ( this.autoplay ) this.play(); + + return this; + + } + + /** + * Starts the playback of the audio. + * + * Can only be used with compatible audio sources that allow playback control. + * + * @param {number} [delay=0] - The delay, in seconds, at which the audio should start playing. + * @return {Audio|undefined} A reference to this instance. + */ + play( delay = 0 ) { + + if ( this.isPlaying === true ) { + + console.warn( 'THREE.Audio: Audio is already playing.' ); + return; + + } + + if ( this.hasPlaybackControl === false ) { + + console.warn( 'THREE.Audio: this Audio has no playback control.' ); + return; + + } + + this._startedAt = this.context.currentTime + delay; + + const source = this.context.createBufferSource(); + source.buffer = this.buffer; + source.loop = this.loop; + source.loopStart = this.loopStart; + source.loopEnd = this.loopEnd; + source.onended = this.onEnded.bind( this ); + source.start( this._startedAt, this._progress + this.offset, this.duration ); + + this.isPlaying = true; + + this.source = source; + + this.setDetune( this.detune ); + this.setPlaybackRate( this.playbackRate ); + + return this.connect(); + + } + + /** + * Pauses the playback of the audio. + * + * Can only be used with compatible audio sources that allow playback control. + * + * @return {Audio|undefined} A reference to this instance. + */ + pause() { + + if ( this.hasPlaybackControl === false ) { + + console.warn( 'THREE.Audio: this Audio has no playback control.' ); + return; + + } + + if ( this.isPlaying === true ) { + + // update current progress + + this._progress += Math.max( this.context.currentTime - this._startedAt, 0 ) * this.playbackRate; + + if ( this.loop === true ) { + + // ensure _progress does not exceed duration with looped audios + + this._progress = this._progress % ( this.duration || this.buffer.duration ); + + } + + this.source.stop(); + this.source.onended = null; + + this.isPlaying = false; + + } + + return this; + + } + + /** + * Stops the playback of the audio. + * + * Can only be used with compatible audio sources that allow playback control. + * + * @param {number} [delay=0] - The delay, in seconds, at which the audio should stop playing. + * @return {Audio|undefined} A reference to this instance. + */ + stop( delay = 0 ) { + + if ( this.hasPlaybackControl === false ) { + + console.warn( 'THREE.Audio: this Audio has no playback control.' ); + return; + + } + + this._progress = 0; + + if ( this.source !== null ) { + + this.source.stop( this.context.currentTime + delay ); + this.source.onended = null; + + } + + this.isPlaying = false; + + return this; + + } + + /** + * Connects to the audio source. This is used internally on + * initialisation and when setting / removing filters. + * + * @return {Audio} A reference to this instance. + */ + connect() { + + if ( this.filters.length > 0 ) { + + this.source.connect( this.filters[ 0 ] ); + + for ( let i = 1, l = this.filters.length; i < l; i ++ ) { + + this.filters[ i - 1 ].connect( this.filters[ i ] ); + + } + + this.filters[ this.filters.length - 1 ].connect( this.getOutput() ); + + } else { + + this.source.connect( this.getOutput() ); + + } + + this._connected = true; + + return this; + + } + + /** + * Disconnects to the audio source. This is used internally on + * initialisation and when setting / removing filters. + * + * @return {Audio|undefined} A reference to this instance. + */ + disconnect() { + + if ( this._connected === false ) { + + return; + + } + + if ( this.filters.length > 0 ) { + + this.source.disconnect( this.filters[ 0 ] ); + + for ( let i = 1, l = this.filters.length; i < l; i ++ ) { + + this.filters[ i - 1 ].disconnect( this.filters[ i ] ); + + } + + this.filters[ this.filters.length - 1 ].disconnect( this.getOutput() ); + + } else { + + this.source.disconnect( this.getOutput() ); + + } + + this._connected = false; + + return this; + + } + + /** + * Returns the current set filters. + * + * @return {Array} The list of filters. + */ + getFilters() { + + return this.filters; + + } + + /** + * Sets an array of filters and connects them with the audio source. + * + * @param {Array} [value] - A list of filters. + * @return {Audio} A reference to this instance. + */ + setFilters( value ) { + + if ( ! value ) value = []; + + if ( this._connected === true ) { + + this.disconnect(); + this.filters = value.slice(); + this.connect(); + + } else { + + this.filters = value.slice(); + + } + + return this; + + } + + /** + * Defines the detuning of oscillation in cents. + * + * @param {number} value - The detuning of oscillation in cents. + * @return {Audio} A reference to this instance. + */ + setDetune( value ) { + + this.detune = value; + + if ( this.isPlaying === true && this.source.detune !== undefined ) { + + this.source.detune.setTargetAtTime( this.detune, this.context.currentTime, 0.01 ); + + } + + return this; + + } + + /** + * Returns the detuning of oscillation in cents. + * + * @return {number} The detuning of oscillation in cents. + */ + getDetune() { + + return this.detune; + + } + + /** + * Returns the first filter in the list of filters. + * + * @return {AudioNode|undefined} The first filter in the list of filters. + */ + getFilter() { + + return this.getFilters()[ 0 ]; + + } + + /** + * Applies a single filter node to the audio. + * + * @param {AudioNode} [filter] - The filter to set. + * @return {Audio} A reference to this instance. + */ + setFilter( filter ) { + + return this.setFilters( filter ? [ filter ] : [] ); + + } + + /** + * Sets the playback rate. + * + * Can only be used with compatible audio sources that allow playback control. + * + * @param {number} [value] - The playback rate to set. + * @return {Audio|undefined} A reference to this instance. + */ + setPlaybackRate( value ) { + + if ( this.hasPlaybackControl === false ) { + + console.warn( 'THREE.Audio: this Audio has no playback control.' ); + return; + + } + + this.playbackRate = value; + + if ( this.isPlaying === true ) { + + this.source.playbackRate.setTargetAtTime( this.playbackRate, this.context.currentTime, 0.01 ); + + } + + return this; + + } + + /** + * Returns the current playback rate. + + * @return {number} The playback rate. + */ + getPlaybackRate() { + + return this.playbackRate; + + } + + /** + * Automatically called when playback finished. + */ + onEnded() { + + this.isPlaying = false; + this._progress = 0; + + } + + /** + * Returns the loop flag. + * + * Can only be used with compatible audio sources that allow playback control. + * + * @return {boolean} Whether the audio should loop or not. + */ + getLoop() { + + if ( this.hasPlaybackControl === false ) { + + console.warn( 'THREE.Audio: this Audio has no playback control.' ); + return false; + + } + + return this.loop; + + } + + /** + * Sets the loop flag. + * + * Can only be used with compatible audio sources that allow playback control. + * + * @param {boolean} value - Whether the audio should loop or not. + * @return {Audio|undefined} A reference to this instance. + */ + setLoop( value ) { + + if ( this.hasPlaybackControl === false ) { + + console.warn( 'THREE.Audio: this Audio has no playback control.' ); + return; + + } + + this.loop = value; + + if ( this.isPlaying === true ) { + + this.source.loop = this.loop; + + } + + return this; + + } + + /** + * Sets the loop start value which defines where in the audio buffer the replay should + * start, in seconds. + * + * @param {number} value - The loop start value. + * @return {Audio} A reference to this instance. + */ + setLoopStart( value ) { + + this.loopStart = value; + + return this; + + } + + /** + * Sets the loop end value which defines where in the audio buffer the replay should + * stop, in seconds. + * + * @param {number} value - The loop end value. + * @return {Audio} A reference to this instance. + */ + setLoopEnd( value ) { + + this.loopEnd = value; + + return this; + + } + + /** + * Returns the volume. + * + * @return {number} The volume. + */ + getVolume() { + + return this.gain.gain.value; + + } + + /** + * Sets the volume. + * + * @param {number} value - The volume to set. + * @return {Audio} A reference to this instance. + */ + setVolume( value ) { + + this.gain.gain.setTargetAtTime( value, this.context.currentTime, 0.01 ); + + return this; + + } + + copy( source, recursive ) { + + super.copy( source, recursive ); + + if ( source.sourceType !== 'buffer' ) { + + console.warn( 'THREE.Audio: Audio source type cannot be copied.' ); + + return this; + + } + + this.autoplay = source.autoplay; + + this.buffer = source.buffer; + this.detune = source.detune; + this.loop = source.loop; + this.loopStart = source.loopStart; + this.loopEnd = source.loopEnd; + this.offset = source.offset; + this.duration = source.duration; + this.playbackRate = source.playbackRate; + this.hasPlaybackControl = source.hasPlaybackControl; + this.sourceType = source.sourceType; + + this.filters = source.filters.slice(); + + return this; + + } + + clone( recursive ) { + + return new this.constructor( this.listener ).copy( this, recursive ); + + } + +} + +const _position = /*@__PURE__*/ new Vector3(); +const _quaternion = /*@__PURE__*/ new Quaternion(); +const _scale = /*@__PURE__*/ new Vector3(); +const _orientation = /*@__PURE__*/ new Vector3(); + +/** + * Represents a positional audio object. + * + * ```js + * // create an AudioListener and add it to the camera + * const listener = new THREE.AudioListener(); + * camera.add( listener ); + * + * // create the PositionalAudio object (passing in the listener) + * const sound = new THREE.PositionalAudio( listener ); + * + * // load a sound and set it as the PositionalAudio object's buffer + * const audioLoader = new THREE.AudioLoader(); + * audioLoader.load( 'sounds/song.ogg', function( buffer ) { + * sound.setBuffer( buffer ); + * sound.setRefDistance( 20 ); + * sound.play(); + * }); + * + * // create an object for the sound to play from + * const sphere = new THREE.SphereGeometry( 20, 32, 16 ); + * const material = new THREE.MeshPhongMaterial( { color: 0xff2200 } ); + * const mesh = new THREE.Mesh( sphere, material ); + * scene.add( mesh ); + * + * // finally add the sound to the mesh + * mesh.add( sound ); + * + * @augments Audio + */ +class PositionalAudio extends Audio { + + /** + * Constructs a positional audio. + * + * @param {AudioListener} listener - The global audio listener. + */ + constructor( listener ) { + + super( listener ); + + /** + * The panner node represents the location, direction, and behavior of an audio + * source in 3D space. + * + * @type {PannerNode} + * @readonly + */ + this.panner = this.context.createPanner(); + this.panner.panningModel = 'HRTF'; + this.panner.connect( this.gain ); + + } + + connect() { + + super.connect(); + + this.panner.connect( this.gain ); + + return this; + + } + + disconnect() { + + super.disconnect(); + + this.panner.disconnect( this.gain ); + + return this; + + } + + getOutput() { + + return this.panner; + + } + + /** + * Returns the current reference distance. + * + * @return {number} The reference distance. + */ + getRefDistance() { + + return this.panner.refDistance; + + } + + /** + * Defines the reference distance for reducing volume as the audio source moves + * further from the listener – i.e. the distance at which the volume reduction + * starts taking effect. + * + * @param {number} value - The reference distance to set. + * @return {PositionalAudio} A reference to this instance. + */ + setRefDistance( value ) { + + this.panner.refDistance = value; + + return this; + + } + + /** + * Returns the current rolloff factor. + * + * @return {number} The rolloff factor. + */ + getRolloffFactor() { + + return this.panner.rolloffFactor; + + } + + /** + * Defines how quickly the volume is reduced as the source moves away from the listener. + * + * @param {number} value - The rolloff factor. + * @return {PositionalAudio} A reference to this instance. + */ + setRolloffFactor( value ) { + + this.panner.rolloffFactor = value; + + return this; + + } + + /** + * Returns the current distance model. + * + * @return {('linear'|'inverse'|'exponential')} The distance model. + */ + getDistanceModel() { + + return this.panner.distanceModel; + + } + + /** + * Defines which algorithm to use to reduce the volume of the audio source + * as it moves away from the listener. + * + * Read [the spec]{@link https://www.w3.org/TR/webaudio-1.1/#enumdef-distancemodeltype} + * for more details. + * + * @param {('linear'|'inverse'|'exponential')} value - The distance model to set. + * @return {PositionalAudio} A reference to this instance. + */ + setDistanceModel( value ) { + + this.panner.distanceModel = value; + + return this; + + } + + /** + * Returns the current max distance. + * + * @return {number} The max distance. + */ + getMaxDistance() { + + return this.panner.maxDistance; + + } + + /** + * Defines the maximum distance between the audio source and the listener, + * after which the volume is not reduced any further. + * + * This value is used only by the `linear` distance model. + * + * @param {number} value - The max distance. + * @return {PositionalAudio} A reference to this instance. + */ + setMaxDistance( value ) { + + this.panner.maxDistance = value; + + return this; + + } + + /** + * Sets the directional cone in which the audio can be listened. + * + * @param {number} coneInnerAngle - An angle, in degrees, of a cone inside of which there will be no volume reduction. + * @param {number} coneOuterAngle - An angle, in degrees, of a cone outside of which the volume will be reduced by a constant value, defined by the `coneOuterGain` parameter. + * @param {number} coneOuterGain - The amount of volume reduction outside the cone defined by the `coneOuterAngle`. When set to `0`, no sound can be heard. + * @return {PositionalAudio} A reference to this instance. + */ + setDirectionalCone( coneInnerAngle, coneOuterAngle, coneOuterGain ) { + + this.panner.coneInnerAngle = coneInnerAngle; + this.panner.coneOuterAngle = coneOuterAngle; + this.panner.coneOuterGain = coneOuterGain; + + return this; + + } + + updateMatrixWorld( force ) { + + super.updateMatrixWorld( force ); + + if ( this.hasPlaybackControl === true && this.isPlaying === false ) return; + + this.matrixWorld.decompose( _position, _quaternion, _scale ); + + _orientation.set( 0, 0, 1 ).applyQuaternion( _quaternion ); + + const panner = this.panner; + + if ( panner.positionX ) { + + // code path for Chrome and Firefox (see #14393) + + const endTime = this.context.currentTime + this.listener.timeDelta; + + panner.positionX.linearRampToValueAtTime( _position.x, endTime ); + panner.positionY.linearRampToValueAtTime( _position.y, endTime ); + panner.positionZ.linearRampToValueAtTime( _position.z, endTime ); + panner.orientationX.linearRampToValueAtTime( _orientation.x, endTime ); + panner.orientationY.linearRampToValueAtTime( _orientation.y, endTime ); + panner.orientationZ.linearRampToValueAtTime( _orientation.z, endTime ); + + } else { + + panner.setPosition( _position.x, _position.y, _position.z ); + panner.setOrientation( _orientation.x, _orientation.y, _orientation.z ); + + } + + } + +} + +/** + * This class can be used to analyse audio data. + * + * ```js + * // create an AudioListener and add it to the camera + * const listener = new THREE.AudioListener(); + * camera.add( listener ); + * + * // create an Audio source + * const sound = new THREE.Audio( listener ); + * + * // load a sound and set it as the Audio object's buffer + * const audioLoader = new THREE.AudioLoader(); + * audioLoader.load( 'sounds/ambient.ogg', function( buffer ) { + * sound.setBuffer( buffer ); + * sound.setLoop(true); + * sound.setVolume(0.5); + * sound.play(); + * }); + * + * // create an AudioAnalyser, passing in the sound and desired fftSize + * const analyser = new THREE.AudioAnalyser( sound, 32 ); + * + * // get the average frequency of the sound + * const data = analyser.getAverageFrequency(); + * ``` + */ +class AudioAnalyser { + + /** + * Constructs a new audio analyzer. + * + * @param {Audio} audio - The audio to analyze. + * @param {number} [fftSize=2048] - The window size in samples that is used when performing a Fast Fourier Transform (FFT) to get frequency domain data. + */ + constructor( audio, fftSize = 2048 ) { + + /** + * The global audio listener. + * + * @type {AnalyserNode} + */ + this.analyser = audio.context.createAnalyser(); + this.analyser.fftSize = fftSize; + + /** + * Holds the analyzed data. + * + * @type {Uint8Array} + */ + this.data = new Uint8Array( this.analyser.frequencyBinCount ); + + audio.getOutput().connect( this.analyser ); + + } + + /** + * Returns an array with frequency data of the audio. + * + * Each item in the array represents the decibel value for a specific frequency. + * The frequencies are spread linearly from 0 to 1/2 of the sample rate. + * For example, for 48000 sample rate, the last item of the array will represent + * the decibel value for 24000 Hz. + * + * @return {Uint8Array} The frequency data. + */ + getFrequencyData() { + + this.analyser.getByteFrequencyData( this.data ); + + return this.data; + + } + + /** + * Returns the average of the frequencies returned by {@link AudioAnalyser#getFrequencyData}. + * + * @return {number} The average frequency. + */ + getAverageFrequency() { + + let value = 0; + const data = this.getFrequencyData(); + + for ( let i = 0; i < data.length; i ++ ) { + + value += data[ i ]; + + } + + return value / data.length; + + } + +} + +/** + * Buffered scene graph property that allows weighted accumulation; used internally. + */ +class PropertyMixer { + + /** + * Constructs a new property mixer. + * + * @param {PropertyBinding} binding - The property binding. + * @param {string} typeName - The keyframe track type name. + * @param {number} valueSize - The keyframe track value size. + */ + constructor( binding, typeName, valueSize ) { + + /** + * The property binding. + * + * @type {PropertyBinding} + */ + this.binding = binding; + + /** + * The keyframe track value size. + * + * @type {number} + */ + this.valueSize = valueSize; + + let mixFunction, + mixFunctionAdditive, + setIdentity; + + // buffer layout: [ incoming | accu0 | accu1 | orig | addAccu | (optional work) ] + // + // interpolators can use .buffer as their .result + // the data then goes to 'incoming' + // + // 'accu0' and 'accu1' are used frame-interleaved for + // the cumulative result and are compared to detect + // changes + // + // 'orig' stores the original state of the property + // + // 'add' is used for additive cumulative results + // + // 'work' is optional and is only present for quaternion types. It is used + // to store intermediate quaternion multiplication results + + switch ( typeName ) { + + case 'quaternion': + mixFunction = this._slerp; + mixFunctionAdditive = this._slerpAdditive; + setIdentity = this._setAdditiveIdentityQuaternion; + + this.buffer = new Float64Array( valueSize * 6 ); + this._workIndex = 5; + break; + + case 'string': + case 'bool': + mixFunction = this._select; + + // Use the regular mix function and for additive on these types, + // additive is not relevant for non-numeric types + mixFunctionAdditive = this._select; + + setIdentity = this._setAdditiveIdentityOther; + + this.buffer = new Array( valueSize * 5 ); + break; + + default: + mixFunction = this._lerp; + mixFunctionAdditive = this._lerpAdditive; + setIdentity = this._setAdditiveIdentityNumeric; + + this.buffer = new Float64Array( valueSize * 5 ); + + } + + this._mixBufferRegion = mixFunction; + this._mixBufferRegionAdditive = mixFunctionAdditive; + this._setIdentity = setIdentity; + this._origIndex = 3; + this._addIndex = 4; + + /** + * TODO + * + * @type {number} + * @default 0 + */ + this.cumulativeWeight = 0; + + /** + * TODO + * + * @type {number} + * @default 0 + */ + this.cumulativeWeightAdditive = 0; + + /** + * TODO + * + * @type {number} + * @default 0 + */ + this.useCount = 0; + + /** + * TODO + * + * @type {number} + * @default 0 + */ + this.referenceCount = 0; + + } + + /** + * Accumulates data in the `incoming` region into `accu`. + * + * @param {number} accuIndex - The accumulation index. + * @param {number} weight - The weight. + */ + accumulate( accuIndex, weight ) { + + // note: happily accumulating nothing when weight = 0, the caller knows + // the weight and shouldn't have made the call in the first place + + const buffer = this.buffer, + stride = this.valueSize, + offset = accuIndex * stride + stride; + + let currentWeight = this.cumulativeWeight; + + if ( currentWeight === 0 ) { + + // accuN := incoming * weight + + for ( let i = 0; i !== stride; ++ i ) { + + buffer[ offset + i ] = buffer[ i ]; + + } + + currentWeight = weight; + + } else { + + // accuN := accuN + incoming * weight + + currentWeight += weight; + const mix = weight / currentWeight; + this._mixBufferRegion( buffer, offset, 0, mix, stride ); + + } + + this.cumulativeWeight = currentWeight; + + } + + /** + * Accumulates data in the `incoming` region into `add`. + * + * @param {number} weight - The weight. + */ + accumulateAdditive( weight ) { + + const buffer = this.buffer, + stride = this.valueSize, + offset = stride * this._addIndex; + + if ( this.cumulativeWeightAdditive === 0 ) { + + // add = identity + + this._setIdentity(); + + } + + // add := add + incoming * weight + + this._mixBufferRegionAdditive( buffer, offset, 0, weight, stride ); + this.cumulativeWeightAdditive += weight; + + } + + /** + * Applies the state of `accu` to the binding when accus differ. + * + * @param {number} accuIndex - The accumulation index. + */ + apply( accuIndex ) { + + const stride = this.valueSize, + buffer = this.buffer, + offset = accuIndex * stride + stride, + + weight = this.cumulativeWeight, + weightAdditive = this.cumulativeWeightAdditive, + + binding = this.binding; + + this.cumulativeWeight = 0; + this.cumulativeWeightAdditive = 0; + + if ( weight < 1 ) { + + // accuN := accuN + original * ( 1 - cumulativeWeight ) + + const originalValueOffset = stride * this._origIndex; + + this._mixBufferRegion( + buffer, offset, originalValueOffset, 1 - weight, stride ); + + } + + if ( weightAdditive > 0 ) { + + // accuN := accuN + additive accuN + + this._mixBufferRegionAdditive( buffer, offset, this._addIndex * stride, 1, stride ); + + } + + for ( let i = stride, e = stride + stride; i !== e; ++ i ) { + + if ( buffer[ i ] !== buffer[ i + stride ] ) { + + // value has changed -> update scene graph + + binding.setValue( buffer, offset ); + break; + + } + + } + + } + + + /** + * Remembers the state of the bound property and copy it to both accus. + */ + saveOriginalState() { + + const binding = this.binding; + + const buffer = this.buffer, + stride = this.valueSize, + + originalValueOffset = stride * this._origIndex; + + binding.getValue( buffer, originalValueOffset ); + + // accu[0..1] := orig -- initially detect changes against the original + for ( let i = stride, e = originalValueOffset; i !== e; ++ i ) { + + buffer[ i ] = buffer[ originalValueOffset + ( i % stride ) ]; + + } + + // Add to identity for additive + this._setIdentity(); + + this.cumulativeWeight = 0; + this.cumulativeWeightAdditive = 0; + + } + + /** + * Applies the state previously taken via {@link PropertyMixer#saveOriginalState} to the binding. + */ + restoreOriginalState() { + + const originalValueOffset = this.valueSize * 3; + this.binding.setValue( this.buffer, originalValueOffset ); + + } + + // internals + + _setAdditiveIdentityNumeric() { + + const startIndex = this._addIndex * this.valueSize; + const endIndex = startIndex + this.valueSize; + + for ( let i = startIndex; i < endIndex; i ++ ) { + + this.buffer[ i ] = 0; + + } + + } + + _setAdditiveIdentityQuaternion() { + + this._setAdditiveIdentityNumeric(); + this.buffer[ this._addIndex * this.valueSize + 3 ] = 1; + + } + + _setAdditiveIdentityOther() { + + const startIndex = this._origIndex * this.valueSize; + const targetIndex = this._addIndex * this.valueSize; + + for ( let i = 0; i < this.valueSize; i ++ ) { + + this.buffer[ targetIndex + i ] = this.buffer[ startIndex + i ]; + + } + + } + + + // mix functions + + _select( buffer, dstOffset, srcOffset, t, stride ) { + + if ( t >= 0.5 ) { + + for ( let i = 0; i !== stride; ++ i ) { + + buffer[ dstOffset + i ] = buffer[ srcOffset + i ]; + + } + + } + + } + + _slerp( buffer, dstOffset, srcOffset, t ) { + + Quaternion.slerpFlat( buffer, dstOffset, buffer, dstOffset, buffer, srcOffset, t ); + + } + + _slerpAdditive( buffer, dstOffset, srcOffset, t, stride ) { + + const workOffset = this._workIndex * stride; + + // Store result in intermediate buffer offset + Quaternion.multiplyQuaternionsFlat( buffer, workOffset, buffer, dstOffset, buffer, srcOffset ); + + // Slerp to the intermediate result + Quaternion.slerpFlat( buffer, dstOffset, buffer, dstOffset, buffer, workOffset, t ); + + } + + _lerp( buffer, dstOffset, srcOffset, t, stride ) { + + const s = 1 - t; + + for ( let i = 0; i !== stride; ++ i ) { + + const j = dstOffset + i; + + buffer[ j ] = buffer[ j ] * s + buffer[ srcOffset + i ] * t; + + } + + } + + _lerpAdditive( buffer, dstOffset, srcOffset, t, stride ) { + + for ( let i = 0; i !== stride; ++ i ) { + + const j = dstOffset + i; + + buffer[ j ] = buffer[ j ] + buffer[ srcOffset + i ] * t; + + } + + } + +} + +// Characters [].:/ are reserved for track binding syntax. +const _RESERVED_CHARS_RE = '\\[\\]\\.:\\/'; +const _reservedRe = new RegExp( '[' + _RESERVED_CHARS_RE + ']', 'g' ); + +// Attempts to allow node names from any language. ES5's `\w` regexp matches +// only latin characters, and the unicode \p{L} is not yet supported. So +// instead, we exclude reserved characters and match everything else. +const _wordChar = '[^' + _RESERVED_CHARS_RE + ']'; +const _wordCharOrDot = '[^' + _RESERVED_CHARS_RE.replace( '\\.', '' ) + ']'; + +// Parent directories, delimited by '/' or ':'. Currently unused, but must +// be matched to parse the rest of the track name. +const _directoryRe = /*@__PURE__*/ /((?:WC+[\/:])*)/.source.replace( 'WC', _wordChar ); + +// Target node. May contain word characters (a-zA-Z0-9_) and '.' or '-'. +const _nodeRe = /*@__PURE__*/ /(WCOD+)?/.source.replace( 'WCOD', _wordCharOrDot ); + +// Object on target node, and accessor. May not contain reserved +// characters. Accessor may contain any character except closing bracket. +const _objectRe = /*@__PURE__*/ /(?:\.(WC+)(?:\[(.+)\])?)?/.source.replace( 'WC', _wordChar ); + +// Property and accessor. May not contain reserved characters. Accessor may +// contain any non-bracket characters. +const _propertyRe = /*@__PURE__*/ /\.(WC+)(?:\[(.+)\])?/.source.replace( 'WC', _wordChar ); + +const _trackRe = new RegExp( '' + + '^' + + _directoryRe + + _nodeRe + + _objectRe + + _propertyRe + + '$' +); + +const _supportedObjectNames = [ 'material', 'materials', 'bones', 'map' ]; + +class Composite { + + constructor( targetGroup, path, optionalParsedPath ) { + + const parsedPath = optionalParsedPath || PropertyBinding.parseTrackName( path ); + + this._targetGroup = targetGroup; + this._bindings = targetGroup.subscribe_( path, parsedPath ); + + } + + getValue( array, offset ) { + + this.bind(); // bind all binding + + const firstValidIndex = this._targetGroup.nCachedObjects_, + binding = this._bindings[ firstValidIndex ]; + + // and only call .getValue on the first + if ( binding !== undefined ) binding.getValue( array, offset ); + + } + + setValue( array, offset ) { + + const bindings = this._bindings; + + for ( let i = this._targetGroup.nCachedObjects_, n = bindings.length; i !== n; ++ i ) { + + bindings[ i ].setValue( array, offset ); + + } + + } + + bind() { + + const bindings = this._bindings; + + for ( let i = this._targetGroup.nCachedObjects_, n = bindings.length; i !== n; ++ i ) { + + bindings[ i ].bind(); + + } + + } + + unbind() { + + const bindings = this._bindings; + + for ( let i = this._targetGroup.nCachedObjects_, n = bindings.length; i !== n; ++ i ) { + + bindings[ i ].unbind(); + + } + + } + +} + +// Note: This class uses a State pattern on a per-method basis: +// 'bind' sets 'this.getValue' / 'setValue' and shadows the +// prototype version of these methods with one that represents +// the bound state. When the property is not found, the methods +// become no-ops. + + +/** + * This holds a reference to a real property in the scene graph; used internally. + */ +class PropertyBinding { + + /** + * Constructs a new property binding. + * + * @param {Object} rootNode - The root node. + * @param {string} path - The path. + * @param {?Object} [parsedPath] - The parsed path. + */ + constructor( rootNode, path, parsedPath ) { + + /** + * The object path to the animated property. + * + * @type {string} + */ + this.path = path; + + /** + * An object holding information about the path. + * + * @type {Object} + */ + this.parsedPath = parsedPath || PropertyBinding.parseTrackName( path ); + + /** + * The object owns the animated property. + * + * @type {?Object} + */ + this.node = PropertyBinding.findNode( rootNode, this.parsedPath.nodeName ); + + /** + * The root node. + * + * @type {Object3D|Skeleton} + */ + this.rootNode = rootNode; + + // initial state of these methods that calls 'bind' + this.getValue = this._getValue_unbound; + this.setValue = this._setValue_unbound; + + } + + + /** + * Factory method for creating a property binding from the given parameters. + * + * @static + * @param {Object} root - The root node. + * @param {string} path - The path. + * @param {?Object} [parsedPath] - The parsed path. + * @return {PropertyBinding|Composite} The created property binding or composite. + */ + static create( root, path, parsedPath ) { + + if ( ! ( root && root.isAnimationObjectGroup ) ) { + + return new PropertyBinding( root, path, parsedPath ); + + } else { + + return new PropertyBinding.Composite( root, path, parsedPath ); + + } + + } + + /** + * Replaces spaces with underscores and removes unsupported characters from + * node names, to ensure compatibility with parseTrackName(). + * + * @param {string} name - Node name to be sanitized. + * @return {string} The sanitized node name. + */ + static sanitizeNodeName( name ) { + + return name.replace( /\s/g, '_' ).replace( _reservedRe, '' ); + + } + + /** + * Parses the given track name (an object path to an animated property) and + * returns an object with information about the path. Matches strings in the following forms: + * + * - nodeName.property + * - nodeName.property[accessor] + * - nodeName.material.property[accessor] + * - uuid.property[accessor] + * - uuid.objectName[objectIndex].propertyName[propertyIndex] + * - parentName/nodeName.property + * - parentName/parentName/nodeName.property[index] + * - .bone[Armature.DEF_cog].position + * - scene:helium_balloon_model:helium_balloon_model.position + * + * @static + * @param {string} trackName - The track name to parse. + * @return {Object} The parsed track name as an object. + */ + static parseTrackName( trackName ) { + + const matches = _trackRe.exec( trackName ); + + if ( matches === null ) { + + throw new Error( 'PropertyBinding: Cannot parse trackName: ' + trackName ); + + } + + const results = { + // directoryName: matches[ 1 ], // (tschw) currently unused + nodeName: matches[ 2 ], + objectName: matches[ 3 ], + objectIndex: matches[ 4 ], + propertyName: matches[ 5 ], // required + propertyIndex: matches[ 6 ] + }; + + const lastDot = results.nodeName && results.nodeName.lastIndexOf( '.' ); + + if ( lastDot !== undefined && lastDot !== -1 ) { + + const objectName = results.nodeName.substring( lastDot + 1 ); + + // Object names must be checked against an allowlist. Otherwise, there + // is no way to parse 'foo.bar.baz': 'baz' must be a property, but + // 'bar' could be the objectName, or part of a nodeName (which can + // include '.' characters). + if ( _supportedObjectNames.indexOf( objectName ) !== -1 ) { + + results.nodeName = results.nodeName.substring( 0, lastDot ); + results.objectName = objectName; + + } + + } + + if ( results.propertyName === null || results.propertyName.length === 0 ) { + + throw new Error( 'PropertyBinding: can not parse propertyName from trackName: ' + trackName ); + + } + + return results; + + } + + /** + * Searches for a node in the hierarchy of the given root object by the given + * node name. + * + * @static + * @param {Object} root - The root object. + * @param {string|number} nodeName - The name of the node. + * @return {?Object} The found node. Returns `null` if no object was found. + */ + static findNode( root, nodeName ) { + + if ( nodeName === undefined || nodeName === '' || nodeName === '.' || nodeName === -1 || nodeName === root.name || nodeName === root.uuid ) { + + return root; + + } + + // search into skeleton bones. + if ( root.skeleton ) { + + const bone = root.skeleton.getBoneByName( nodeName ); + + if ( bone !== undefined ) { + + return bone; + + } + + } + + // search into node subtree. + if ( root.children ) { + + const searchNodeSubtree = function ( children ) { + + for ( let i = 0; i < children.length; i ++ ) { + + const childNode = children[ i ]; + + if ( childNode.name === nodeName || childNode.uuid === nodeName ) { + + return childNode; + + } + + const result = searchNodeSubtree( childNode.children ); + + if ( result ) return result; + + } + + return null; + + }; + + const subTreeNode = searchNodeSubtree( root.children ); + + if ( subTreeNode ) { + + return subTreeNode; + + } + + } + + return null; + + } + + // these are used to "bind" a nonexistent property + _getValue_unavailable() {} + _setValue_unavailable() {} + + // Getters + + _getValue_direct( buffer, offset ) { + + buffer[ offset ] = this.targetObject[ this.propertyName ]; + + } + + _getValue_array( buffer, offset ) { + + const source = this.resolvedProperty; + + for ( let i = 0, n = source.length; i !== n; ++ i ) { + + buffer[ offset ++ ] = source[ i ]; + + } + + } + + _getValue_arrayElement( buffer, offset ) { + + buffer[ offset ] = this.resolvedProperty[ this.propertyIndex ]; + + } + + _getValue_toArray( buffer, offset ) { + + this.resolvedProperty.toArray( buffer, offset ); + + } + + // Direct + + _setValue_direct( buffer, offset ) { + + this.targetObject[ this.propertyName ] = buffer[ offset ]; + + } + + _setValue_direct_setNeedsUpdate( buffer, offset ) { + + this.targetObject[ this.propertyName ] = buffer[ offset ]; + this.targetObject.needsUpdate = true; + + } + + _setValue_direct_setMatrixWorldNeedsUpdate( buffer, offset ) { + + this.targetObject[ this.propertyName ] = buffer[ offset ]; + this.targetObject.matrixWorldNeedsUpdate = true; + + } + + // EntireArray + + _setValue_array( buffer, offset ) { + + const dest = this.resolvedProperty; + + for ( let i = 0, n = dest.length; i !== n; ++ i ) { + + dest[ i ] = buffer[ offset ++ ]; + + } + + } + + _setValue_array_setNeedsUpdate( buffer, offset ) { + + const dest = this.resolvedProperty; + + for ( let i = 0, n = dest.length; i !== n; ++ i ) { + + dest[ i ] = buffer[ offset ++ ]; + + } + + this.targetObject.needsUpdate = true; + + } + + _setValue_array_setMatrixWorldNeedsUpdate( buffer, offset ) { + + const dest = this.resolvedProperty; + + for ( let i = 0, n = dest.length; i !== n; ++ i ) { + + dest[ i ] = buffer[ offset ++ ]; + + } + + this.targetObject.matrixWorldNeedsUpdate = true; + + } + + // ArrayElement + + _setValue_arrayElement( buffer, offset ) { + + this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ]; + + } + + _setValue_arrayElement_setNeedsUpdate( buffer, offset ) { + + this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ]; + this.targetObject.needsUpdate = true; + + } + + _setValue_arrayElement_setMatrixWorldNeedsUpdate( buffer, offset ) { + + this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ]; + this.targetObject.matrixWorldNeedsUpdate = true; + + } + + // HasToFromArray + + _setValue_fromArray( buffer, offset ) { + + this.resolvedProperty.fromArray( buffer, offset ); + + } + + _setValue_fromArray_setNeedsUpdate( buffer, offset ) { + + this.resolvedProperty.fromArray( buffer, offset ); + this.targetObject.needsUpdate = true; + + } + + _setValue_fromArray_setMatrixWorldNeedsUpdate( buffer, offset ) { + + this.resolvedProperty.fromArray( buffer, offset ); + this.targetObject.matrixWorldNeedsUpdate = true; + + } + + _getValue_unbound( targetArray, offset ) { + + this.bind(); + this.getValue( targetArray, offset ); + + } + + _setValue_unbound( sourceArray, offset ) { + + this.bind(); + this.setValue( sourceArray, offset ); + + } + + /** + * Creates a getter / setter pair for the property tracked by this binding. + */ + bind() { + + let targetObject = this.node; + const parsedPath = this.parsedPath; + + const objectName = parsedPath.objectName; + const propertyName = parsedPath.propertyName; + let propertyIndex = parsedPath.propertyIndex; + + if ( ! targetObject ) { + + targetObject = PropertyBinding.findNode( this.rootNode, parsedPath.nodeName ); + + this.node = targetObject; + + } + + // set fail state so we can just 'return' on error + this.getValue = this._getValue_unavailable; + this.setValue = this._setValue_unavailable; + + // ensure there is a value node + if ( ! targetObject ) { + + console.warn( 'THREE.PropertyBinding: No target node found for track: ' + this.path + '.' ); + return; + + } + + if ( objectName ) { + + let objectIndex = parsedPath.objectIndex; + + // special cases were we need to reach deeper into the hierarchy to get the face materials.... + switch ( objectName ) { + + case 'materials': + + if ( ! targetObject.material ) { + + console.error( 'THREE.PropertyBinding: Can not bind to material as node does not have a material.', this ); + return; + + } + + if ( ! targetObject.material.materials ) { + + console.error( 'THREE.PropertyBinding: Can not bind to material.materials as node.material does not have a materials array.', this ); + return; + + } + + targetObject = targetObject.material.materials; + + break; + + case 'bones': + + if ( ! targetObject.skeleton ) { + + console.error( 'THREE.PropertyBinding: Can not bind to bones as node does not have a skeleton.', this ); + return; + + } + + // potential future optimization: skip this if propertyIndex is already an integer + // and convert the integer string to a true integer. + + targetObject = targetObject.skeleton.bones; + + // support resolving morphTarget names into indices. + for ( let i = 0; i < targetObject.length; i ++ ) { + + if ( targetObject[ i ].name === objectIndex ) { + + objectIndex = i; + break; + + } + + } + + break; + + case 'map': + + if ( 'map' in targetObject ) { + + targetObject = targetObject.map; + break; + + } + + if ( ! targetObject.material ) { + + console.error( 'THREE.PropertyBinding: Can not bind to material as node does not have a material.', this ); + return; + + } + + if ( ! targetObject.material.map ) { + + console.error( 'THREE.PropertyBinding: Can not bind to material.map as node.material does not have a map.', this ); + return; + + } + + targetObject = targetObject.material.map; + break; + + default: + + if ( targetObject[ objectName ] === undefined ) { + + console.error( 'THREE.PropertyBinding: Can not bind to objectName of node undefined.', this ); + return; + + } + + targetObject = targetObject[ objectName ]; + + } + + + if ( objectIndex !== undefined ) { + + if ( targetObject[ objectIndex ] === undefined ) { + + console.error( 'THREE.PropertyBinding: Trying to bind to objectIndex of objectName, but is undefined.', this, targetObject ); + return; + + } + + targetObject = targetObject[ objectIndex ]; + + } + + } + + // resolve property + const nodeProperty = targetObject[ propertyName ]; + + if ( nodeProperty === undefined ) { + + const nodeName = parsedPath.nodeName; + + console.error( 'THREE.PropertyBinding: Trying to update property for track: ' + nodeName + + '.' + propertyName + ' but it wasn\'t found.', targetObject ); + return; + + } + + // determine versioning scheme + let versioning = this.Versioning.None; + + this.targetObject = targetObject; + + if ( targetObject.isMaterial === true ) { + + versioning = this.Versioning.NeedsUpdate; + + } else if ( targetObject.isObject3D === true ) { + + versioning = this.Versioning.MatrixWorldNeedsUpdate; + + } + + // determine how the property gets bound + let bindingType = this.BindingType.Direct; + + if ( propertyIndex !== undefined ) { + + // access a sub element of the property array (only primitives are supported right now) + + if ( propertyName === 'morphTargetInfluences' ) { + + // potential optimization, skip this if propertyIndex is already an integer, and convert the integer string to a true integer. + + // support resolving morphTarget names into indices. + if ( ! targetObject.geometry ) { + + console.error( 'THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.', this ); + return; + + } + + if ( ! targetObject.geometry.morphAttributes ) { + + console.error( 'THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.morphAttributes.', this ); + return; + + } + + if ( targetObject.morphTargetDictionary[ propertyIndex ] !== undefined ) { + + propertyIndex = targetObject.morphTargetDictionary[ propertyIndex ]; + + } + + } + + bindingType = this.BindingType.ArrayElement; + + this.resolvedProperty = nodeProperty; + this.propertyIndex = propertyIndex; + + } else if ( nodeProperty.fromArray !== undefined && nodeProperty.toArray !== undefined ) { + + // must use copy for Object3D.Euler/Quaternion + + bindingType = this.BindingType.HasFromToArray; + + this.resolvedProperty = nodeProperty; + + } else if ( Array.isArray( nodeProperty ) ) { + + bindingType = this.BindingType.EntireArray; + + this.resolvedProperty = nodeProperty; + + } else { + + this.propertyName = propertyName; + + } + + // select getter / setter + this.getValue = this.GetterByBindingType[ bindingType ]; + this.setValue = this.SetterByBindingTypeAndVersioning[ bindingType ][ versioning ]; + + } + + /** + * Unbinds the property. + */ + unbind() { + + this.node = null; + + // back to the prototype version of getValue / setValue + // note: avoiding to mutate the shape of 'this' via 'delete' + this.getValue = this._getValue_unbound; + this.setValue = this._setValue_unbound; + + } + +} + +PropertyBinding.Composite = Composite; + +PropertyBinding.prototype.BindingType = { + Direct: 0, + EntireArray: 1, + ArrayElement: 2, + HasFromToArray: 3 +}; + +PropertyBinding.prototype.Versioning = { + None: 0, + NeedsUpdate: 1, + MatrixWorldNeedsUpdate: 2 +}; + +PropertyBinding.prototype.GetterByBindingType = [ + + PropertyBinding.prototype._getValue_direct, + PropertyBinding.prototype._getValue_array, + PropertyBinding.prototype._getValue_arrayElement, + PropertyBinding.prototype._getValue_toArray, + +]; + +PropertyBinding.prototype.SetterByBindingTypeAndVersioning = [ + + [ + // Direct + PropertyBinding.prototype._setValue_direct, + PropertyBinding.prototype._setValue_direct_setNeedsUpdate, + PropertyBinding.prototype._setValue_direct_setMatrixWorldNeedsUpdate, + + ], [ + + // EntireArray + + PropertyBinding.prototype._setValue_array, + PropertyBinding.prototype._setValue_array_setNeedsUpdate, + PropertyBinding.prototype._setValue_array_setMatrixWorldNeedsUpdate, + + ], [ + + // ArrayElement + PropertyBinding.prototype._setValue_arrayElement, + PropertyBinding.prototype._setValue_arrayElement_setNeedsUpdate, + PropertyBinding.prototype._setValue_arrayElement_setMatrixWorldNeedsUpdate, + + ], [ + + // HasToFromArray + PropertyBinding.prototype._setValue_fromArray, + PropertyBinding.prototype._setValue_fromArray_setNeedsUpdate, + PropertyBinding.prototype._setValue_fromArray_setMatrixWorldNeedsUpdate, + + ] + +]; + +/** + * A group of objects that receives a shared animation state. + * + * Usage: + * + * - Add objects you would otherwise pass as 'root' to the + * constructor or the .clipAction method of AnimationMixer. + * - Instead pass this object as 'root'. + * - You can also add and remove objects later when the mixer is running. + * + * Note: + * + * - Objects of this class appear as one object to the mixer, + * so cache control of the individual objects must be done on the group. + * + * Limitation: + * + * - The animated properties must be compatible among the all objects in the group. + * - A single property can either be controlled through a target group or directly, but not both. + */ +class AnimationObjectGroup { + + /** + * Constructs a new animation group. + * + * @param {...Object3D} arguments - An arbitrary number of 3D objects that share the same animation state. + */ + constructor() { + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isAnimationObjectGroup = true; + + /** + * The UUID of the 3D object. + * + * @type {string} + * @readonly + */ + this.uuid = generateUUID(); + + // cached objects followed by the active ones + this._objects = Array.prototype.slice.call( arguments ); + + this.nCachedObjects_ = 0; // threshold + // note: read by PropertyBinding.Composite + + const indices = {}; + this._indicesByUUID = indices; // for bookkeeping + + for ( let i = 0, n = arguments.length; i !== n; ++ i ) { + + indices[ arguments[ i ].uuid ] = i; + + } + + this._paths = []; // inside: string + this._parsedPaths = []; // inside: { we don't care, here } + this._bindings = []; // inside: Array< PropertyBinding > + this._bindingsIndicesByPath = {}; // inside: indices in these arrays + + const scope = this; + + this.stats = { + + objects: { + get total() { + + return scope._objects.length; + + }, + get inUse() { + + return this.total - scope.nCachedObjects_; + + } + }, + get bindingsPerObject() { + + return scope._bindings.length; + + } + + }; + + } + + /** + * Adds an arbitrary number of objects to this animation group. + * + * @param {...Object3D} arguments - The 3D objects to add. + */ + add() { + + const objects = this._objects, + indicesByUUID = this._indicesByUUID, + paths = this._paths, + parsedPaths = this._parsedPaths, + bindings = this._bindings, + nBindings = bindings.length; + + let knownObject = undefined, + nObjects = objects.length, + nCachedObjects = this.nCachedObjects_; + + for ( let i = 0, n = arguments.length; i !== n; ++ i ) { + + const object = arguments[ i ], + uuid = object.uuid; + let index = indicesByUUID[ uuid ]; + + if ( index === undefined ) { + + // unknown object -> add it to the ACTIVE region + + index = nObjects ++; + indicesByUUID[ uuid ] = index; + objects.push( object ); + + // accounting is done, now do the same for all bindings + + for ( let j = 0, m = nBindings; j !== m; ++ j ) { + + bindings[ j ].push( new PropertyBinding( object, paths[ j ], parsedPaths[ j ] ) ); + + } + + } else if ( index < nCachedObjects ) { + + knownObject = objects[ index ]; + + // move existing object to the ACTIVE region + + const firstActiveIndex = -- nCachedObjects, + lastCachedObject = objects[ firstActiveIndex ]; + + indicesByUUID[ lastCachedObject.uuid ] = index; + objects[ index ] = lastCachedObject; + + indicesByUUID[ uuid ] = firstActiveIndex; + objects[ firstActiveIndex ] = object; + + // accounting is done, now do the same for all bindings + + for ( let j = 0, m = nBindings; j !== m; ++ j ) { + + const bindingsForPath = bindings[ j ], + lastCached = bindingsForPath[ firstActiveIndex ]; + + let binding = bindingsForPath[ index ]; + + bindingsForPath[ index ] = lastCached; + + if ( binding === undefined ) { + + // since we do not bother to create new bindings + // for objects that are cached, the binding may + // or may not exist + + binding = new PropertyBinding( object, paths[ j ], parsedPaths[ j ] ); + + } + + bindingsForPath[ firstActiveIndex ] = binding; + + } + + } else if ( objects[ index ] !== knownObject ) { + + console.error( 'THREE.AnimationObjectGroup: Different objects with the same UUID ' + + 'detected. Clean the caches or recreate your infrastructure when reloading scenes.' ); + + } // else the object is already where we want it to be + + } // for arguments + + this.nCachedObjects_ = nCachedObjects; + + } + + /** + * Removes an arbitrary number of objects to this animation group + * + * @param {...Object3D} arguments - The 3D objects to remove. + */ + remove() { + + const objects = this._objects, + indicesByUUID = this._indicesByUUID, + bindings = this._bindings, + nBindings = bindings.length; + + let nCachedObjects = this.nCachedObjects_; + + for ( let i = 0, n = arguments.length; i !== n; ++ i ) { + + const object = arguments[ i ], + uuid = object.uuid, + index = indicesByUUID[ uuid ]; + + if ( index !== undefined && index >= nCachedObjects ) { + + // move existing object into the CACHED region + + const lastCachedIndex = nCachedObjects ++, + firstActiveObject = objects[ lastCachedIndex ]; + + indicesByUUID[ firstActiveObject.uuid ] = index; + objects[ index ] = firstActiveObject; + + indicesByUUID[ uuid ] = lastCachedIndex; + objects[ lastCachedIndex ] = object; + + // accounting is done, now do the same for all bindings + + for ( let j = 0, m = nBindings; j !== m; ++ j ) { + + const bindingsForPath = bindings[ j ], + firstActive = bindingsForPath[ lastCachedIndex ], + binding = bindingsForPath[ index ]; + + bindingsForPath[ index ] = firstActive; + bindingsForPath[ lastCachedIndex ] = binding; + + } + + } + + } // for arguments + + this.nCachedObjects_ = nCachedObjects; + + } + + /** + * Deallocates all memory resources for the passed 3D objects of this animation group. + * + * @param {...Object3D} arguments - The 3D objects to uncache. + */ + uncache() { + + const objects = this._objects, + indicesByUUID = this._indicesByUUID, + bindings = this._bindings, + nBindings = bindings.length; + + let nCachedObjects = this.nCachedObjects_, + nObjects = objects.length; + + for ( let i = 0, n = arguments.length; i !== n; ++ i ) { + + const object = arguments[ i ], + uuid = object.uuid, + index = indicesByUUID[ uuid ]; + + if ( index !== undefined ) { + + delete indicesByUUID[ uuid ]; + + if ( index < nCachedObjects ) { + + // object is cached, shrink the CACHED region + + const firstActiveIndex = -- nCachedObjects, + lastCachedObject = objects[ firstActiveIndex ], + lastIndex = -- nObjects, + lastObject = objects[ lastIndex ]; + + // last cached object takes this object's place + indicesByUUID[ lastCachedObject.uuid ] = index; + objects[ index ] = lastCachedObject; + + // last object goes to the activated slot and pop + indicesByUUID[ lastObject.uuid ] = firstActiveIndex; + objects[ firstActiveIndex ] = lastObject; + objects.pop(); + + // accounting is done, now do the same for all bindings + + for ( let j = 0, m = nBindings; j !== m; ++ j ) { + + const bindingsForPath = bindings[ j ], + lastCached = bindingsForPath[ firstActiveIndex ], + last = bindingsForPath[ lastIndex ]; + + bindingsForPath[ index ] = lastCached; + bindingsForPath[ firstActiveIndex ] = last; + bindingsForPath.pop(); + + } + + } else { + + // object is active, just swap with the last and pop + + const lastIndex = -- nObjects, + lastObject = objects[ lastIndex ]; + + if ( lastIndex > 0 ) { + + indicesByUUID[ lastObject.uuid ] = index; + + } + + objects[ index ] = lastObject; + objects.pop(); + + // accounting is done, now do the same for all bindings + + for ( let j = 0, m = nBindings; j !== m; ++ j ) { + + const bindingsForPath = bindings[ j ]; + + bindingsForPath[ index ] = bindingsForPath[ lastIndex ]; + bindingsForPath.pop(); + + } + + } // cached or active + + } // if object is known + + } // for arguments + + this.nCachedObjects_ = nCachedObjects; + + } + + // Internal interface used by befriended PropertyBinding.Composite: + + subscribe_( path, parsedPath ) { + + // returns an array of bindings for the given path that is changed + // according to the contained objects in the group + + const indicesByPath = this._bindingsIndicesByPath; + let index = indicesByPath[ path ]; + const bindings = this._bindings; + + if ( index !== undefined ) return bindings[ index ]; + + const paths = this._paths, + parsedPaths = this._parsedPaths, + objects = this._objects, + nObjects = objects.length, + nCachedObjects = this.nCachedObjects_, + bindingsForPath = new Array( nObjects ); + + index = bindings.length; + + indicesByPath[ path ] = index; + + paths.push( path ); + parsedPaths.push( parsedPath ); + bindings.push( bindingsForPath ); + + for ( let i = nCachedObjects, n = objects.length; i !== n; ++ i ) { + + const object = objects[ i ]; + bindingsForPath[ i ] = new PropertyBinding( object, path, parsedPath ); + + } + + return bindingsForPath; + + } + + unsubscribe_( path ) { + + // tells the group to forget about a property path and no longer + // update the array previously obtained with 'subscribe_' + + const indicesByPath = this._bindingsIndicesByPath, + index = indicesByPath[ path ]; + + if ( index !== undefined ) { + + const paths = this._paths, + parsedPaths = this._parsedPaths, + bindings = this._bindings, + lastBindingsIndex = bindings.length - 1, + lastBindings = bindings[ lastBindingsIndex ], + lastBindingsPath = path[ lastBindingsIndex ]; + + indicesByPath[ lastBindingsPath ] = index; + + bindings[ index ] = lastBindings; + bindings.pop(); + + parsedPaths[ index ] = parsedPaths[ lastBindingsIndex ]; + parsedPaths.pop(); + + paths[ index ] = paths[ lastBindingsIndex ]; + paths.pop(); + + } + + } + +} + +/** + * An instance of `AnimationAction` schedules the playback of an animation which is + * stored in {@link AnimationClip}. + */ +class AnimationAction { + + /** + * Constructs a new animation action. + * + * @param {AnimationMixer} mixer - The mixer that is controlled by this action. + * @param {AnimationClip} clip - The animation clip that holds the actual keyframes. + * @param {?Object3D} [localRoot=null] - The root object on which this action is performed. + * @param {(NormalAnimationBlendMode|AdditiveAnimationBlendMode)} [blendMode] - The blend mode. + */ + constructor( mixer, clip, localRoot = null, blendMode = clip.blendMode ) { + + this._mixer = mixer; + this._clip = clip; + this._localRoot = localRoot; + + /** + * Defines how the animation is blended/combined when two or more animations + * are simultaneously played. + * + * @type {(NormalAnimationBlendMode|AdditiveAnimationBlendMode)} + */ + this.blendMode = blendMode; + + const tracks = clip.tracks, + nTracks = tracks.length, + interpolants = new Array( nTracks ); + + const interpolantSettings = { + endingStart: ZeroCurvatureEnding, + endingEnd: ZeroCurvatureEnding + }; + + for ( let i = 0; i !== nTracks; ++ i ) { + + const interpolant = tracks[ i ].createInterpolant( null ); + interpolants[ i ] = interpolant; + interpolant.settings = interpolantSettings; + + } + + this._interpolantSettings = interpolantSettings; + + this._interpolants = interpolants; // bound by the mixer + + // inside: PropertyMixer (managed by the mixer) + this._propertyBindings = new Array( nTracks ); + + this._cacheIndex = null; // for the memory manager + this._byClipCacheIndex = null; // for the memory manager + + this._timeScaleInterpolant = null; + this._weightInterpolant = null; + + /** + * The loop mode, set via {@link AnimationAction#setLoop}. + * + * @type {(LoopRepeat|LoopOnce|LoopPingPong)} + * @default LoopRepeat + */ + this.loop = LoopRepeat; + this._loopCount = -1; + + // global mixer time when the action is to be started + // it's set back to 'null' upon start of the action + this._startTime = null; + + /** + * The local time of this action (in seconds, starting with `0`). + * + * The value gets clamped or wrapped to `[0,clip.duration]` (according to the + * loop state). + * + * @type {number} + * @default Infinity + */ + this.time = 0; + + /** + * Scaling factor for the {@link AnimationAction#time}. A value of `0` causes the + * animation to pause. Negative values cause the animation to play backwards. + * + * @type {number} + * @default 1 + */ + this.timeScale = 1; + this._effectiveTimeScale = 1; + + /** + * The degree of influence of this action (in the interval `[0, 1]`). Values + * between `0` (no impact) and `1` (full impact) can be used to blend between + * several actions. + * + * @type {number} + * @default 1 + */ + this.weight = 1; + this._effectiveWeight = 1; + + /** + * The number of repetitions of the performed clip over the course of this action. + * Can be set via {@link AnimationAction#setLoop}. + * + * Setting this number has no effect if {@link AnimationAction#loop} is set to + * `THREE:LoopOnce`. + * + * @type {number} + * @default Infinity + */ + this.repetitions = Infinity; + + /** + * If set to `true`, the playback of the action is paused. + * + * @type {boolean} + * @default false + */ + this.paused = false; + + /** + * If set to `false`, the action is disabled so it has no impact. + * + * When the action is re-enabled, the animation continues from its current + * time (setting `enabled` to `false` doesn't reset the action). + * + * @type {boolean} + * @default true + */ + this.enabled = true; + + /** + * If set to true the animation will automatically be paused on its last frame. + * + * If set to false, {@link AnimationAction#enabled} will automatically be switched + * to `false` when the last loop of the action has finished, so that this action has + * no further impact. + * + * Note: This member has no impact if the action is interrupted (it + * has only an effect if its last loop has really finished). + * + * @type {boolean} + * @default false + */ + this.clampWhenFinished = false; + + /** + * Enables smooth interpolation without separate clips for start, loop and end. + * + * @type {boolean} + * @default true + */ + this.zeroSlopeAtStart = true; + + /** + * Enables smooth interpolation without separate clips for start, loop and end. + * + * @type {boolean} + * @default true + */ + this.zeroSlopeAtEnd = true; + + } + + /** + * Starts the playback of the animation. + * + * @return {AnimationAction} A reference to this animation action. + */ + play() { + + this._mixer._activateAction( this ); + + return this; + + } + + /** + * Stops the playback of the animation. + * + * @return {AnimationAction} A reference to this animation action. + */ + stop() { + + this._mixer._deactivateAction( this ); + + return this.reset(); + + } + + /** + * Resets the playback of the animation. + * + * @return {AnimationAction} A reference to this animation action. + */ + reset() { + + this.paused = false; + this.enabled = true; + + this.time = 0; // restart clip + this._loopCount = -1;// forget previous loops + this._startTime = null;// forget scheduling + + return this.stopFading().stopWarping(); + + } + + /** + * Returns `true` if the animation is running. + * + * @return {boolean} Whether the animation is running or not. + */ + isRunning() { + + return this.enabled && ! this.paused && this.timeScale !== 0 && + this._startTime === null && this._mixer._isActiveAction( this ); + + } + + /** + * Returns `true` when {@link AnimationAction#play} has been called. + * + * @return {boolean} Whether the animation is scheduled or not. + */ + isScheduled() { + + return this._mixer._isActiveAction( this ); + + } + + /** + * Defines the time when the animation should start. + * + * @param {number} time - The start time in seconds. + * @return {AnimationAction} A reference to this animation action. + */ + startAt( time ) { + + this._startTime = time; + + return this; + + } + + /** + * Configures the loop settings for this action. + * + * @param {(LoopRepeat|LoopOnce|LoopPingPong)} mode - The loop mode. + * @param {number} repetitions - The number of repetitions. + * @return {AnimationAction} A reference to this animation action. + */ + setLoop( mode, repetitions ) { + + this.loop = mode; + this.repetitions = repetitions; + + return this; + + } + + /** + * Sets the effective weight of this action. + * + * An action has no effect and thus an effective weight of zero when the + * action is disabled. + * + * @param {number} weight - The weight to set. + * @return {AnimationAction} A reference to this animation action. + */ + setEffectiveWeight( weight ) { + + this.weight = weight; + + // note: same logic as when updated at runtime + this._effectiveWeight = this.enabled ? weight : 0; + + return this.stopFading(); + + } + + /** + * Returns the effective weight of this action. + * + * @return {number} The effective weight. + */ + getEffectiveWeight() { + + return this._effectiveWeight; + + } + + /** + * Fades the animation in by increasing its weight gradually from `0` to `1`, + * within the passed time interval. + * + * @param {number} duration - The duration of the fade. + * @return {AnimationAction} A reference to this animation action. + */ + fadeIn( duration ) { + + return this._scheduleFading( duration, 0, 1 ); + + } + + /** + * Fades the animation out by decreasing its weight gradually from `1` to `0`, + * within the passed time interval. + * + * @param {number} duration - The duration of the fade. + * @return {AnimationAction} A reference to this animation action. + */ + fadeOut( duration ) { + + return this._scheduleFading( duration, 1, 0 ); + + } + + /** + * Causes this action to fade in and the given action to fade out, + * within the passed time interval. + * + * @param {AnimationAction} fadeOutAction - The animation action to fade out. + * @param {number} duration - The duration of the fade. + * @param {boolean} [warp=false] - Whether warping should be used or not. + * @return {AnimationAction} A reference to this animation action. + */ + crossFadeFrom( fadeOutAction, duration, warp = false ) { + + fadeOutAction.fadeOut( duration ); + this.fadeIn( duration ); + + if ( warp === true ) { + + const fadeInDuration = this._clip.duration, + fadeOutDuration = fadeOutAction._clip.duration, + + startEndRatio = fadeOutDuration / fadeInDuration, + endStartRatio = fadeInDuration / fadeOutDuration; + + fadeOutAction.warp( 1.0, startEndRatio, duration ); + this.warp( endStartRatio, 1.0, duration ); + + } + + return this; + + } + + /** + * Causes this action to fade out and the given action to fade in, + * within the passed time interval. + * + * @param {AnimationAction} fadeInAction - The animation action to fade in. + * @param {number} duration - The duration of the fade. + * @param {boolean} [warp=false] - Whether warping should be used or not. + * @return {AnimationAction} A reference to this animation action. + */ + crossFadeTo( fadeInAction, duration, warp = false ) { + + return fadeInAction.crossFadeFrom( this, duration, warp ); + + } + + /** + * Stops any fading which is applied to this action. + * + * @return {AnimationAction} A reference to this animation action. + */ + stopFading() { + + const weightInterpolant = this._weightInterpolant; + + if ( weightInterpolant !== null ) { + + this._weightInterpolant = null; + this._mixer._takeBackControlInterpolant( weightInterpolant ); + + } + + return this; + + } + + /** + * Sets the effective time scale of this action. + * + * An action has no effect and thus an effective time scale of zero when the + * action is paused. + * + * @param {number} timeScale - The time scale to set. + * @return {AnimationAction} A reference to this animation action. + */ + setEffectiveTimeScale( timeScale ) { + + this.timeScale = timeScale; + this._effectiveTimeScale = this.paused ? 0 : timeScale; + + return this.stopWarping(); + + } + + /** + * Returns the effective time scale of this action. + * + * @return {number} The effective time scale. + */ + getEffectiveTimeScale() { + + return this._effectiveTimeScale; + + } + + /** + * Sets the duration for a single loop of this action. + * + * @param {number} duration - The duration to set. + * @return {AnimationAction} A reference to this animation action. + */ + setDuration( duration ) { + + this.timeScale = this._clip.duration / duration; + + return this.stopWarping(); + + } + + /** + * Synchronizes this action with the passed other action. + * + * @param {AnimationAction} action - The action to sync with. + * @return {AnimationAction} A reference to this animation action. + */ + syncWith( action ) { + + this.time = action.time; + this.timeScale = action.timeScale; + + return this.stopWarping(); + + } + + /** + * Decelerates this animation's speed to `0` within the passed time interval. + * + * @param {number} duration - The duration. + * @return {AnimationAction} A reference to this animation action. + */ + halt( duration ) { + + return this.warp( this._effectiveTimeScale, 0, duration ); + + } + + /** + * Changes the playback speed, within the passed time interval, by modifying + * {@link AnimationAction#timeScale} gradually from `startTimeScale` to + * `endTimeScale`. + * + * @param {number} startTimeScale - The start time scale. + * @param {number} endTimeScale - The end time scale. + * @param {number} duration - The duration. + * @return {AnimationAction} A reference to this animation action. + */ + warp( startTimeScale, endTimeScale, duration ) { + + const mixer = this._mixer, + now = mixer.time, + timeScale = this.timeScale; + + let interpolant = this._timeScaleInterpolant; + + if ( interpolant === null ) { + + interpolant = mixer._lendControlInterpolant(); + this._timeScaleInterpolant = interpolant; + + } + + const times = interpolant.parameterPositions, + values = interpolant.sampleValues; + + times[ 0 ] = now; + times[ 1 ] = now + duration; + + values[ 0 ] = startTimeScale / timeScale; + values[ 1 ] = endTimeScale / timeScale; + + return this; + + } + + /** + * Stops any scheduled warping which is applied to this action. + * + * @return {AnimationAction} A reference to this animation action. + */ + stopWarping() { + + const timeScaleInterpolant = this._timeScaleInterpolant; + + if ( timeScaleInterpolant !== null ) { + + this._timeScaleInterpolant = null; + this._mixer._takeBackControlInterpolant( timeScaleInterpolant ); + + } + + return this; + + } + + /** + * Returns the animation mixer of this animation action. + * + * @return {AnimationMixer} The animation mixer. + */ + getMixer() { + + return this._mixer; + + } + + /** + * Returns the animation clip of this animation action. + * + * @return {AnimationClip} The animation clip. + */ + getClip() { + + return this._clip; + + } + + /** + * Returns the root object of this animation action. + * + * @return {Object3D} The root object. + */ + getRoot() { + + return this._localRoot || this._mixer._root; + + } + + // Interna + + _update( time, deltaTime, timeDirection, accuIndex ) { + + // called by the mixer + + if ( ! this.enabled ) { + + // call ._updateWeight() to update ._effectiveWeight + + this._updateWeight( time ); + return; + + } + + const startTime = this._startTime; + + if ( startTime !== null ) { + + // check for scheduled start of action + + const timeRunning = ( time - startTime ) * timeDirection; + if ( timeRunning < 0 || timeDirection === 0 ) { + + deltaTime = 0; + + } else { + + + this._startTime = null; // unschedule + deltaTime = timeDirection * timeRunning; + + } + + } + + // apply time scale and advance time + + deltaTime *= this._updateTimeScale( time ); + const clipTime = this._updateTime( deltaTime ); + + // note: _updateTime may disable the action resulting in + // an effective weight of 0 + + const weight = this._updateWeight( time ); + + if ( weight > 0 ) { + + const interpolants = this._interpolants; + const propertyMixers = this._propertyBindings; + + switch ( this.blendMode ) { + + case AdditiveAnimationBlendMode: + + for ( let j = 0, m = interpolants.length; j !== m; ++ j ) { + + interpolants[ j ].evaluate( clipTime ); + propertyMixers[ j ].accumulateAdditive( weight ); + + } + + break; + + case NormalAnimationBlendMode: + default: + + for ( let j = 0, m = interpolants.length; j !== m; ++ j ) { + + interpolants[ j ].evaluate( clipTime ); + propertyMixers[ j ].accumulate( accuIndex, weight ); + + } + + } + + } + + } + + _updateWeight( time ) { + + let weight = 0; + + if ( this.enabled ) { + + weight = this.weight; + const interpolant = this._weightInterpolant; + + if ( interpolant !== null ) { + + const interpolantValue = interpolant.evaluate( time )[ 0 ]; + + weight *= interpolantValue; + + if ( time > interpolant.parameterPositions[ 1 ] ) { + + this.stopFading(); + + if ( interpolantValue === 0 ) { + + // faded out, disable + this.enabled = false; + + } + + } + + } + + } + + this._effectiveWeight = weight; + return weight; + + } + + _updateTimeScale( time ) { + + let timeScale = 0; + + if ( ! this.paused ) { + + timeScale = this.timeScale; + + const interpolant = this._timeScaleInterpolant; + + if ( interpolant !== null ) { + + const interpolantValue = interpolant.evaluate( time )[ 0 ]; + + timeScale *= interpolantValue; + + if ( time > interpolant.parameterPositions[ 1 ] ) { + + this.stopWarping(); + + if ( timeScale === 0 ) { + + // motion has halted, pause + this.paused = true; + + } else { + + // warp done - apply final time scale + this.timeScale = timeScale; + + } + + } + + } + + } + + this._effectiveTimeScale = timeScale; + return timeScale; + + } + + _updateTime( deltaTime ) { + + const duration = this._clip.duration; + const loop = this.loop; + + let time = this.time + deltaTime; + let loopCount = this._loopCount; + + const pingPong = ( loop === LoopPingPong ); + + if ( deltaTime === 0 ) { + + if ( loopCount === -1 ) return time; + + return ( pingPong && ( loopCount & 1 ) === 1 ) ? duration - time : time; + + } + + if ( loop === LoopOnce ) { + + if ( loopCount === -1 ) { + + // just started + + this._loopCount = 0; + this._setEndings( true, true, false ); + + } + + handle_stop: { + + if ( time >= duration ) { + + time = duration; + + } else if ( time < 0 ) { + + time = 0; + + } else { + + this.time = time; + + break handle_stop; + + } + + if ( this.clampWhenFinished ) this.paused = true; + else this.enabled = false; + + this.time = time; + + this._mixer.dispatchEvent( { + type: 'finished', action: this, + direction: deltaTime < 0 ? -1 : 1 + } ); + + } + + } else { // repetitive Repeat or PingPong + + if ( loopCount === -1 ) { + + // just started + + if ( deltaTime >= 0 ) { + + loopCount = 0; + + this._setEndings( true, this.repetitions === 0, pingPong ); + + } else { + + // when looping in reverse direction, the initial + // transition through zero counts as a repetition, + // so leave loopCount at -1 + + this._setEndings( this.repetitions === 0, true, pingPong ); + + } + + } + + if ( time >= duration || time < 0 ) { + + // wrap around + + const loopDelta = Math.floor( time / duration ); // signed + time -= duration * loopDelta; + + loopCount += Math.abs( loopDelta ); + + const pending = this.repetitions - loopCount; + + if ( pending <= 0 ) { + + // have to stop (switch state, clamp time, fire event) + + if ( this.clampWhenFinished ) this.paused = true; + else this.enabled = false; + + time = deltaTime > 0 ? duration : 0; + + this.time = time; + + this._mixer.dispatchEvent( { + type: 'finished', action: this, + direction: deltaTime > 0 ? 1 : -1 + } ); + + } else { + + // keep running + + if ( pending === 1 ) { + + // entering the last round + + const atStart = deltaTime < 0; + this._setEndings( atStart, ! atStart, pingPong ); + + } else { + + this._setEndings( false, false, pingPong ); + + } + + this._loopCount = loopCount; + + this.time = time; + + this._mixer.dispatchEvent( { + type: 'loop', action: this, loopDelta: loopDelta + } ); + + } + + } else { + + this.time = time; + + } + + if ( pingPong && ( loopCount & 1 ) === 1 ) { + + // invert time for the "pong round" + + return duration - time; + + } + + } + + return time; + + } + + _setEndings( atStart, atEnd, pingPong ) { + + const settings = this._interpolantSettings; + + if ( pingPong ) { + + settings.endingStart = ZeroSlopeEnding; + settings.endingEnd = ZeroSlopeEnding; + + } else { + + // assuming for LoopOnce atStart == atEnd == true + + if ( atStart ) { + + settings.endingStart = this.zeroSlopeAtStart ? ZeroSlopeEnding : ZeroCurvatureEnding; + + } else { + + settings.endingStart = WrapAroundEnding; + + } + + if ( atEnd ) { + + settings.endingEnd = this.zeroSlopeAtEnd ? ZeroSlopeEnding : ZeroCurvatureEnding; + + } else { + + settings.endingEnd = WrapAroundEnding; + + } + + } + + } + + _scheduleFading( duration, weightNow, weightThen ) { + + const mixer = this._mixer, now = mixer.time; + let interpolant = this._weightInterpolant; + + if ( interpolant === null ) { + + interpolant = mixer._lendControlInterpolant(); + this._weightInterpolant = interpolant; + + } + + const times = interpolant.parameterPositions, + values = interpolant.sampleValues; + + times[ 0 ] = now; + values[ 0 ] = weightNow; + times[ 1 ] = now + duration; + values[ 1 ] = weightThen; + + return this; + + } + +} + +const _controlInterpolantsResultBuffer = new Float32Array( 1 ); + +/** + * `AnimationMixer` is a player for animations on a particular object in + * the scene. When multiple objects in the scene are animated independently, + * one `AnimationMixer` may be used for each object. + */ +class AnimationMixer extends EventDispatcher { + + /** + * Constructs a new animation mixer. + * + * @param {Object3D} root - The object whose animations shall be played by this mixer. + */ + constructor( root ) { + + super(); + + this._root = root; + this._initMemoryManager(); + this._accuIndex = 0; + + /** + * The global mixer time (in seconds; starting with `0` on the mixer's creation). + * + * @type {number} + * @default 0 + */ + this.time = 0; + + /** + * A scaling factor for the global time. + * + * Note: Setting this member to `0` and later back to `1` is a + * possibility to pause/unpause all actions that are controlled by this + * mixer. + * + * @type {number} + * @default 1 + */ + this.timeScale = 1.0; + + } + + _bindAction( action, prototypeAction ) { + + const root = action._localRoot || this._root, + tracks = action._clip.tracks, + nTracks = tracks.length, + bindings = action._propertyBindings, + interpolants = action._interpolants, + rootUuid = root.uuid, + bindingsByRoot = this._bindingsByRootAndName; + + let bindingsByName = bindingsByRoot[ rootUuid ]; + + if ( bindingsByName === undefined ) { + + bindingsByName = {}; + bindingsByRoot[ rootUuid ] = bindingsByName; + + } + + for ( let i = 0; i !== nTracks; ++ i ) { + + const track = tracks[ i ], + trackName = track.name; + + let binding = bindingsByName[ trackName ]; + + if ( binding !== undefined ) { + + ++ binding.referenceCount; + bindings[ i ] = binding; + + } else { + + binding = bindings[ i ]; + + if ( binding !== undefined ) { + + // existing binding, make sure the cache knows + + if ( binding._cacheIndex === null ) { + + ++ binding.referenceCount; + this._addInactiveBinding( binding, rootUuid, trackName ); + + } + + continue; + + } + + const path = prototypeAction && prototypeAction. + _propertyBindings[ i ].binding.parsedPath; + + binding = new PropertyMixer( + PropertyBinding.create( root, trackName, path ), + track.ValueTypeName, track.getValueSize() ); + + ++ binding.referenceCount; + this._addInactiveBinding( binding, rootUuid, trackName ); + + bindings[ i ] = binding; + + } + + interpolants[ i ].resultBuffer = binding.buffer; + + } + + } + + _activateAction( action ) { + + if ( ! this._isActiveAction( action ) ) { + + if ( action._cacheIndex === null ) { + + // this action has been forgotten by the cache, but the user + // appears to be still using it -> rebind + + const rootUuid = ( action._localRoot || this._root ).uuid, + clipUuid = action._clip.uuid, + actionsForClip = this._actionsByClip[ clipUuid ]; + + this._bindAction( action, + actionsForClip && actionsForClip.knownActions[ 0 ] ); + + this._addInactiveAction( action, clipUuid, rootUuid ); + + } + + const bindings = action._propertyBindings; + + // increment reference counts / sort out state + for ( let i = 0, n = bindings.length; i !== n; ++ i ) { + + const binding = bindings[ i ]; + + if ( binding.useCount ++ === 0 ) { + + this._lendBinding( binding ); + binding.saveOriginalState(); + + } + + } + + this._lendAction( action ); + + } + + } + + _deactivateAction( action ) { + + if ( this._isActiveAction( action ) ) { + + const bindings = action._propertyBindings; + + // decrement reference counts / sort out state + for ( let i = 0, n = bindings.length; i !== n; ++ i ) { + + const binding = bindings[ i ]; + + if ( -- binding.useCount === 0 ) { + + binding.restoreOriginalState(); + this._takeBackBinding( binding ); + + } + + } + + this._takeBackAction( action ); + + } + + } + + // Memory manager + + _initMemoryManager() { + + this._actions = []; // 'nActiveActions' followed by inactive ones + this._nActiveActions = 0; + + this._actionsByClip = {}; + // inside: + // { + // knownActions: Array< AnimationAction > - used as prototypes + // actionByRoot: AnimationAction - lookup + // } + + + this._bindings = []; // 'nActiveBindings' followed by inactive ones + this._nActiveBindings = 0; + + this._bindingsByRootAndName = {}; // inside: Map< name, PropertyMixer > + + + this._controlInterpolants = []; // same game as above + this._nActiveControlInterpolants = 0; + + const scope = this; + + this.stats = { + + actions: { + get total() { + + return scope._actions.length; + + }, + get inUse() { + + return scope._nActiveActions; + + } + }, + bindings: { + get total() { + + return scope._bindings.length; + + }, + get inUse() { + + return scope._nActiveBindings; + + } + }, + controlInterpolants: { + get total() { + + return scope._controlInterpolants.length; + + }, + get inUse() { + + return scope._nActiveControlInterpolants; + + } + } + + }; + + } + + // Memory management for AnimationAction objects + + _isActiveAction( action ) { + + const index = action._cacheIndex; + return index !== null && index < this._nActiveActions; + + } + + _addInactiveAction( action, clipUuid, rootUuid ) { + + const actions = this._actions, + actionsByClip = this._actionsByClip; + + let actionsForClip = actionsByClip[ clipUuid ]; + + if ( actionsForClip === undefined ) { + + actionsForClip = { + + knownActions: [ action ], + actionByRoot: {} + + }; + + action._byClipCacheIndex = 0; + + actionsByClip[ clipUuid ] = actionsForClip; + + } else { + + const knownActions = actionsForClip.knownActions; + + action._byClipCacheIndex = knownActions.length; + knownActions.push( action ); + + } + + action._cacheIndex = actions.length; + actions.push( action ); + + actionsForClip.actionByRoot[ rootUuid ] = action; + + } + + _removeInactiveAction( action ) { + + const actions = this._actions, + lastInactiveAction = actions[ actions.length - 1 ], + cacheIndex = action._cacheIndex; + + lastInactiveAction._cacheIndex = cacheIndex; + actions[ cacheIndex ] = lastInactiveAction; + actions.pop(); + + action._cacheIndex = null; + + + const clipUuid = action._clip.uuid, + actionsByClip = this._actionsByClip, + actionsForClip = actionsByClip[ clipUuid ], + knownActionsForClip = actionsForClip.knownActions, + + lastKnownAction = + knownActionsForClip[ knownActionsForClip.length - 1 ], + + byClipCacheIndex = action._byClipCacheIndex; + + lastKnownAction._byClipCacheIndex = byClipCacheIndex; + knownActionsForClip[ byClipCacheIndex ] = lastKnownAction; + knownActionsForClip.pop(); + + action._byClipCacheIndex = null; + + + const actionByRoot = actionsForClip.actionByRoot, + rootUuid = ( action._localRoot || this._root ).uuid; + + delete actionByRoot[ rootUuid ]; + + if ( knownActionsForClip.length === 0 ) { + + delete actionsByClip[ clipUuid ]; + + } + + this._removeInactiveBindingsForAction( action ); + + } + + _removeInactiveBindingsForAction( action ) { + + const bindings = action._propertyBindings; + + for ( let i = 0, n = bindings.length; i !== n; ++ i ) { + + const binding = bindings[ i ]; + + if ( -- binding.referenceCount === 0 ) { + + this._removeInactiveBinding( binding ); + + } + + } + + } + + _lendAction( action ) { + + // [ active actions | inactive actions ] + // [ active actions >| inactive actions ] + // s a + // <-swap-> + // a s + + const actions = this._actions, + prevIndex = action._cacheIndex, + + lastActiveIndex = this._nActiveActions ++, + + firstInactiveAction = actions[ lastActiveIndex ]; + + action._cacheIndex = lastActiveIndex; + actions[ lastActiveIndex ] = action; + + firstInactiveAction._cacheIndex = prevIndex; + actions[ prevIndex ] = firstInactiveAction; + + } + + _takeBackAction( action ) { + + // [ active actions | inactive actions ] + // [ active actions |< inactive actions ] + // a s + // <-swap-> + // s a + + const actions = this._actions, + prevIndex = action._cacheIndex, + + firstInactiveIndex = -- this._nActiveActions, + + lastActiveAction = actions[ firstInactiveIndex ]; + + action._cacheIndex = firstInactiveIndex; + actions[ firstInactiveIndex ] = action; + + lastActiveAction._cacheIndex = prevIndex; + actions[ prevIndex ] = lastActiveAction; + + } + + // Memory management for PropertyMixer objects + + _addInactiveBinding( binding, rootUuid, trackName ) { + + const bindingsByRoot = this._bindingsByRootAndName, + bindings = this._bindings; + + let bindingByName = bindingsByRoot[ rootUuid ]; + + if ( bindingByName === undefined ) { + + bindingByName = {}; + bindingsByRoot[ rootUuid ] = bindingByName; + + } + + bindingByName[ trackName ] = binding; + + binding._cacheIndex = bindings.length; + bindings.push( binding ); + + } + + _removeInactiveBinding( binding ) { + + const bindings = this._bindings, + propBinding = binding.binding, + rootUuid = propBinding.rootNode.uuid, + trackName = propBinding.path, + bindingsByRoot = this._bindingsByRootAndName, + bindingByName = bindingsByRoot[ rootUuid ], + + lastInactiveBinding = bindings[ bindings.length - 1 ], + cacheIndex = binding._cacheIndex; + + lastInactiveBinding._cacheIndex = cacheIndex; + bindings[ cacheIndex ] = lastInactiveBinding; + bindings.pop(); + + delete bindingByName[ trackName ]; + + if ( Object.keys( bindingByName ).length === 0 ) { + + delete bindingsByRoot[ rootUuid ]; + + } + + } + + _lendBinding( binding ) { + + const bindings = this._bindings, + prevIndex = binding._cacheIndex, + + lastActiveIndex = this._nActiveBindings ++, + + firstInactiveBinding = bindings[ lastActiveIndex ]; + + binding._cacheIndex = lastActiveIndex; + bindings[ lastActiveIndex ] = binding; + + firstInactiveBinding._cacheIndex = prevIndex; + bindings[ prevIndex ] = firstInactiveBinding; + + } + + _takeBackBinding( binding ) { + + const bindings = this._bindings, + prevIndex = binding._cacheIndex, + + firstInactiveIndex = -- this._nActiveBindings, + + lastActiveBinding = bindings[ firstInactiveIndex ]; + + binding._cacheIndex = firstInactiveIndex; + bindings[ firstInactiveIndex ] = binding; + + lastActiveBinding._cacheIndex = prevIndex; + bindings[ prevIndex ] = lastActiveBinding; + + } + + + // Memory management of Interpolants for weight and time scale + + _lendControlInterpolant() { + + const interpolants = this._controlInterpolants, + lastActiveIndex = this._nActiveControlInterpolants ++; + + let interpolant = interpolants[ lastActiveIndex ]; + + if ( interpolant === undefined ) { + + interpolant = new LinearInterpolant( + new Float32Array( 2 ), new Float32Array( 2 ), + 1, _controlInterpolantsResultBuffer ); + + interpolant.__cacheIndex = lastActiveIndex; + interpolants[ lastActiveIndex ] = interpolant; + + } + + return interpolant; + + } + + _takeBackControlInterpolant( interpolant ) { + + const interpolants = this._controlInterpolants, + prevIndex = interpolant.__cacheIndex, + + firstInactiveIndex = -- this._nActiveControlInterpolants, + + lastActiveInterpolant = interpolants[ firstInactiveIndex ]; + + interpolant.__cacheIndex = firstInactiveIndex; + interpolants[ firstInactiveIndex ] = interpolant; + + lastActiveInterpolant.__cacheIndex = prevIndex; + interpolants[ prevIndex ] = lastActiveInterpolant; + + } + + /** + * Returns an instance of {@link AnimationAction} for the passed clip. + * + * If an action fitting the clip and root parameters doesn't yet exist, it + * will be created by this method. Calling this method several times with the + * same clip and root parameters always returns the same action. + * + * @param {AnimationClip|string} clip - An animation clip or alternatively the name of the animation clip. + * @param {Object3D} [optionalRoot] - An alternative root object. + * @param {(NormalAnimationBlendMode|AdditiveAnimationBlendMode)} [blendMode] - The blend mode. + * @return {?AnimationAction} The animation action. + */ + clipAction( clip, optionalRoot, blendMode ) { + + const root = optionalRoot || this._root, + rootUuid = root.uuid; + + let clipObject = typeof clip === 'string' ? AnimationClip.findByName( root, clip ) : clip; + + const clipUuid = clipObject !== null ? clipObject.uuid : clip; + + const actionsForClip = this._actionsByClip[ clipUuid ]; + let prototypeAction = null; + + if ( blendMode === undefined ) { + + if ( clipObject !== null ) { + + blendMode = clipObject.blendMode; + + } else { + + blendMode = NormalAnimationBlendMode; + + } + + } + + if ( actionsForClip !== undefined ) { + + const existingAction = actionsForClip.actionByRoot[ rootUuid ]; + + if ( existingAction !== undefined && existingAction.blendMode === blendMode ) { + + return existingAction; + + } + + // we know the clip, so we don't have to parse all + // the bindings again but can just copy + prototypeAction = actionsForClip.knownActions[ 0 ]; + + // also, take the clip from the prototype action + if ( clipObject === null ) + clipObject = prototypeAction._clip; + + } + + // clip must be known when specified via string + if ( clipObject === null ) return null; + + // allocate all resources required to run it + const newAction = new AnimationAction( this, clipObject, optionalRoot, blendMode ); + + this._bindAction( newAction, prototypeAction ); + + // and make the action known to the memory manager + this._addInactiveAction( newAction, clipUuid, rootUuid ); + + return newAction; + + } + + /** + * Returns an existing animation action for the passed clip. + * + * @param {AnimationClip|string} clip - An animation clip or alternatively the name of the animation clip. + * @param {Object3D} [optionalRoot] - An alternative root object. + * @return {?AnimationAction} The animation action. Returns `null` if no action was found. + */ + existingAction( clip, optionalRoot ) { + + const root = optionalRoot || this._root, + rootUuid = root.uuid, + + clipObject = typeof clip === 'string' ? + AnimationClip.findByName( root, clip ) : clip, + + clipUuid = clipObject ? clipObject.uuid : clip, + + actionsForClip = this._actionsByClip[ clipUuid ]; + + if ( actionsForClip !== undefined ) { + + return actionsForClip.actionByRoot[ rootUuid ] || null; + + } + + return null; + + } + + /** + * Deactivates all previously scheduled actions on this mixer. + * + * @return {AnimationMixer} A reference to thi animation mixer. + */ + stopAllAction() { + + const actions = this._actions, + nActions = this._nActiveActions; + + for ( let i = nActions - 1; i >= 0; -- i ) { + + actions[ i ].stop(); + + } + + return this; + + } + + /** + * Advances the global mixer time and updates the animation. + * + * This is usually done in the render loop by passing the delta + * time from {@link Clock} or {@link Timer}. + * + * @param {number} deltaTime - The delta time in seconds. + * @return {AnimationMixer} A reference to thi animation mixer. + */ + update( deltaTime ) { + + deltaTime *= this.timeScale; + + const actions = this._actions, + nActions = this._nActiveActions, + + time = this.time += deltaTime, + timeDirection = Math.sign( deltaTime ), + + accuIndex = this._accuIndex ^= 1; + + // run active actions + + for ( let i = 0; i !== nActions; ++ i ) { + + const action = actions[ i ]; + + action._update( time, deltaTime, timeDirection, accuIndex ); + + } + + // update scene graph + + const bindings = this._bindings, + nBindings = this._nActiveBindings; + + for ( let i = 0; i !== nBindings; ++ i ) { + + bindings[ i ].apply( accuIndex ); + + } + + return this; + + } + + /** + * Sets the global mixer to a specific time and updates the animation accordingly. + * + * This is useful when you need to jump to an exact time in an animation. The + * input parameter will be scaled by {@link AnimationMixer#timeScale} + * + * @param {number} time - The time to set in seconds. + * @return {AnimationMixer} A reference to thi animation mixer. + */ + setTime( time ) { + + this.time = 0; // Zero out time attribute for AnimationMixer object; + for ( let i = 0; i < this._actions.length; i ++ ) { + + this._actions[ i ].time = 0; // Zero out time attribute for all associated AnimationAction objects. + + } + + return this.update( time ); // Update used to set exact time. Returns "this" AnimationMixer object. + + } + + /** + * Returns this mixer's root object. + * + * @return {Object3D} The mixer's root object. + */ + getRoot() { + + return this._root; + + } + + /** + * Deallocates all memory resources for a clip. Before using this method make + * sure to call {@link AnimationAction#stop} for all related actions. + * + * @param {AnimationClip} clip - The clip to uncache. + */ + uncacheClip( clip ) { + + const actions = this._actions, + clipUuid = clip.uuid, + actionsByClip = this._actionsByClip, + actionsForClip = actionsByClip[ clipUuid ]; + + if ( actionsForClip !== undefined ) { + + // note: just calling _removeInactiveAction would mess up the + // iteration state and also require updating the state we can + // just throw away + + const actionsToRemove = actionsForClip.knownActions; + + for ( let i = 0, n = actionsToRemove.length; i !== n; ++ i ) { + + const action = actionsToRemove[ i ]; + + this._deactivateAction( action ); + + const cacheIndex = action._cacheIndex, + lastInactiveAction = actions[ actions.length - 1 ]; + + action._cacheIndex = null; + action._byClipCacheIndex = null; + + lastInactiveAction._cacheIndex = cacheIndex; + actions[ cacheIndex ] = lastInactiveAction; + actions.pop(); + + this._removeInactiveBindingsForAction( action ); + + } + + delete actionsByClip[ clipUuid ]; + + } + + } + + /** + * Deallocates all memory resources for a root object. Before using this + * method make sure to call {@link AnimationAction#stop} for all related + * actions or alternatively {@link AnimationMixer#stopAllAction} when the + * mixer operates on a single root. + * + * @param {Object3D} root - The root object to uncache. + */ + uncacheRoot( root ) { + + const rootUuid = root.uuid, + actionsByClip = this._actionsByClip; + + for ( const clipUuid in actionsByClip ) { + + const actionByRoot = actionsByClip[ clipUuid ].actionByRoot, + action = actionByRoot[ rootUuid ]; + + if ( action !== undefined ) { + + this._deactivateAction( action ); + this._removeInactiveAction( action ); + + } + + } + + const bindingsByRoot = this._bindingsByRootAndName, + bindingByName = bindingsByRoot[ rootUuid ]; + + if ( bindingByName !== undefined ) { + + for ( const trackName in bindingByName ) { + + const binding = bindingByName[ trackName ]; + binding.restoreOriginalState(); + this._removeInactiveBinding( binding ); + + } + + } + + } + + /** + * Deallocates all memory resources for an action. The action is identified by the + * given clip and an optional root object. Before using this method make + * sure to call {@link AnimationAction#stop} to deactivate the action. + * + * @param {AnimationClip|string} clip - An animation clip or alternatively the name of the animation clip. + * @param {Object3D} [optionalRoot] - An alternative root object. + */ + uncacheAction( clip, optionalRoot ) { + + const action = this.existingAction( clip, optionalRoot ); + + if ( action !== null ) { + + this._deactivateAction( action ); + this._removeInactiveAction( action ); + + } + + } + +} + +/** + * Represents a 3D render target. + * + * @augments RenderTarget + */ +class RenderTarget3D extends RenderTarget { + + /** + * Constructs a new 3D render target. + * + * @param {number} [width=1] - The width of the render target. + * @param {number} [height=1] - The height of the render target. + * @param {number} [depth=1] - The height of the render target. + * @param {RenderTarget~Options} [options] - The configuration object. + */ + constructor( width = 1, height = 1, depth = 1, options = {} ) { + + super( width, height, options ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isRenderTarget3D = true; + + this.depth = depth; + + /** + * Overwritten with a different texture type. + * + * @type {Data3DTexture} + */ + this.texture = new Data3DTexture( null, width, height, depth ); + this._setTextureOptions( options ); + + this.texture.isRenderTargetTexture = true; + + } + +} + +/** + * Represents a uniform which is a global shader variable. They are passed to shader programs. + * + * When declaring a uniform of a {@link ShaderMaterial}, it is declared by value or by object. + * ```js + * uniforms: { + * time: { value: 1.0 }, + * resolution: new Uniform( new Vector2() ) + * }; + * ``` + * Since this class can only be used in context of {@link ShaderMaterial}, it is only supported + * in {@link WebGLRenderer}. + */ +class Uniform { + + /** + * Constructs a new uniform. + * + * @param {any} value - The uniform value. + */ + constructor( value ) { + + /** + * The uniform value. + * + * @type {any} + */ + this.value = value; + + } + + /** + * Returns a new uniform with copied values from this instance. + * If the value has a `clone()` method, the value is cloned as well. + * + * @return {Uniform} A clone of this instance. + */ + clone() { + + return new Uniform( this.value.clone === undefined ? this.value : this.value.clone() ); + + } + +} + +let _id = 0; + +/** + * A class for managing multiple uniforms in a single group. The renderer will process + * such a definition as a single UBO. + * + * Since this class can only be used in context of {@link ShaderMaterial}, it is only supported + * in {@link WebGLRenderer}. + * + * @augments EventDispatcher + */ +class UniformsGroup extends EventDispatcher { + + /** + * Constructs a new uniforms group. + */ + constructor() { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isUniformsGroup = true; + + /** + * The ID of the 3D object. + * + * @name UniformsGroup#id + * @type {number} + * @readonly + */ + Object.defineProperty( this, 'id', { value: _id ++ } ); + + /** + * The name of the uniforms group. + * + * @type {string} + */ + this.name = ''; + + /** + * The buffer usage. + * + * @type {(StaticDrawUsage|DynamicDrawUsage|StreamDrawUsage|StaticReadUsage|DynamicReadUsage|StreamReadUsage|StaticCopyUsage|DynamicCopyUsage|StreamCopyUsage)} + * @default StaticDrawUsage + */ + this.usage = StaticDrawUsage; + + /** + * An array holding the uniforms. + * + * @type {Array} + */ + this.uniforms = []; + + } + + /** + * Adds the given uniform to this uniforms group. + * + * @param {Uniform} uniform - The uniform to add. + * @return {UniformsGroup} A reference to this uniforms group. + */ + add( uniform ) { + + this.uniforms.push( uniform ); + + return this; + + } + + /** + * Removes the given uniform from this uniforms group. + * + * @param {Uniform} uniform - The uniform to remove. + * @return {UniformsGroup} A reference to this uniforms group. + */ + remove( uniform ) { + + const index = this.uniforms.indexOf( uniform ); + + if ( index !== -1 ) this.uniforms.splice( index, 1 ); + + return this; + + } + + /** + * Sets the name of this uniforms group. + * + * @param {string} name - The name to set. + * @return {UniformsGroup} A reference to this uniforms group. + */ + setName( name ) { + + this.name = name; + + return this; + + } + + /** + * Sets the usage of this uniforms group. + * + * @param {(StaticDrawUsage|DynamicDrawUsage|StreamDrawUsage|StaticReadUsage|DynamicReadUsage|StreamReadUsage|StaticCopyUsage|DynamicCopyUsage|StreamCopyUsage)} value - The usage to set. + * @return {UniformsGroup} A reference to this uniforms group. + */ + setUsage( value ) { + + this.usage = value; + + return this; + + } + + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + * + * @fires Texture#dispose + */ + dispose() { + + this.dispatchEvent( { type: 'dispose' } ); + + } + + /** + * Copies the values of the given uniforms group to this instance. + * + * @param {UniformsGroup} source - The uniforms group to copy. + * @return {UniformsGroup} A reference to this uniforms group. + */ + copy( source ) { + + this.name = source.name; + this.usage = source.usage; + + const uniformsSource = source.uniforms; + + this.uniforms.length = 0; + + for ( let i = 0, l = uniformsSource.length; i < l; i ++ ) { + + const uniforms = Array.isArray( uniformsSource[ i ] ) ? uniformsSource[ i ] : [ uniformsSource[ i ] ]; + + for ( let j = 0; j < uniforms.length; j ++ ) { + + this.uniforms.push( uniforms[ j ].clone() ); + + } + + } + + return this; + + } + + /** + * Returns a new uniforms group with copied values from this instance. + * + * @return {UniformsGroup} A clone of this instance. + */ + clone() { + + return new this.constructor().copy( this ); + + } + +} + +/** + * An instanced version of an interleaved buffer. + * + * @augments InterleavedBuffer + */ +class InstancedInterleavedBuffer extends InterleavedBuffer { + + /** + * Constructs a new instanced interleaved buffer. + * + * @param {TypedArray} array - A typed array with a shared buffer storing attribute data. + * @param {number} stride - The number of typed-array elements per vertex. + * @param {number} [meshPerAttribute=1] - Defines how often a value of this interleaved buffer should be repeated. + */ + constructor( array, stride, meshPerAttribute = 1 ) { + + super( array, stride ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isInstancedInterleavedBuffer = true; + + /** + * Defines how often a value of this buffer attribute should be repeated, + * see {@link InstancedBufferAttribute#meshPerAttribute}. + * + * @type {number} + * @default 1 + */ + this.meshPerAttribute = meshPerAttribute; + + } + + copy( source ) { + + super.copy( source ); + + this.meshPerAttribute = source.meshPerAttribute; + + return this; + + } + + clone( data ) { + + const ib = super.clone( data ); + + ib.meshPerAttribute = this.meshPerAttribute; + + return ib; + + } + + toJSON( data ) { + + const json = super.toJSON( data ); + + json.isInstancedInterleavedBuffer = true; + json.meshPerAttribute = this.meshPerAttribute; + + return json; + + } + +} + +/** + * An alternative version of a buffer attribute with more control over the VBO. + * + * The renderer does not construct a VBO for this kind of attribute. Instead, it uses + * whatever VBO is passed in constructor and can later be altered via the `buffer` property. + * + * The most common use case for this class is when some kind of GPGPU calculation interferes + * or even produces the VBOs in question. + * + * Notice that this class can only be used with {@link WebGLRenderer}. + */ +class GLBufferAttribute { + + /** + * Constructs a new GL buffer attribute. + * + * @param {WebGLBuffer} buffer - The native WebGL buffer. + * @param {number} type - The native data type (e.g. `gl.FLOAT`). + * @param {number} itemSize - The item size. + * @param {number} elementSize - The corresponding size (in bytes) for the given `type` parameter. + * @param {number} count - The expected number of vertices in VBO. + * @param {boolean} [normalized=false] - Whether the data are normalized or not. + */ + constructor( buffer, type, itemSize, elementSize, count, normalized = false ) { + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isGLBufferAttribute = true; + + /** + * The name of the buffer attribute. + * + * @type {string} + */ + this.name = ''; + + /** + * The native WebGL buffer. + * + * @type {WebGLBuffer} + */ + this.buffer = buffer; + + /** + * The native data type. + * + * @type {number} + */ + this.type = type; + + /** + * The item size, see {@link BufferAttribute#itemSize}. + * + * @type {number} + */ + this.itemSize = itemSize; + + /** + * The corresponding size (in bytes) for the given `type` parameter. + * + * @type {number} + */ + this.elementSize = elementSize; + + /** + * The expected number of vertices in VBO. + * + * @type {number} + */ + this.count = count; + + /** + * Applies to integer data only. Indicates how the underlying data in the buffer maps to + * the values in the GLSL code. For instance, if `buffer` contains data of `gl.UNSIGNED_SHORT`, + * and `normalized` is `true`, the values `0 - +65535` in the buffer data will be mapped to + * `0.0f - +1.0f` in the GLSL attribute. If `normalized` is `false`, the values will be converted + * to floats unmodified, i.e. `65535` becomes `65535.0f`. + * + * @type {boolean} + */ + this.normalized = normalized; + + /** + * A version number, incremented every time the `needsUpdate` is set to `true`. + * + * @type {number} + */ + this.version = 0; + + } + + /** + * Flag to indicate that this attribute has changed and should be re-sent to + * the GPU. Set this to `true` when you modify the value of the array. + * + * @type {number} + * @default false + * @param {boolean} value + */ + set needsUpdate( value ) { + + if ( value === true ) this.version ++; + + } + + /** + * Sets the given native WebGL buffer. + * + * @param {WebGLBuffer} buffer - The buffer to set. + * @return {BufferAttribute} A reference to this instance. + */ + setBuffer( buffer ) { + + this.buffer = buffer; + + return this; + + } + + /** + * Sets the given native data type and element size. + * + * @param {number} type - The native data type (e.g. `gl.FLOAT`). + * @param {number} elementSize - The corresponding size (in bytes) for the given `type` parameter. + * @return {BufferAttribute} A reference to this instance. + */ + setType( type, elementSize ) { + + this.type = type; + this.elementSize = elementSize; + + return this; + + } + + /** + * Sets the item size. + * + * @param {number} itemSize - The item size. + * @return {BufferAttribute} A reference to this instance. + */ + setItemSize( itemSize ) { + + this.itemSize = itemSize; + + return this; + + } + + /** + * Sets the count (the expected number of vertices in VBO). + * + * @param {number} count - The count. + * @return {BufferAttribute} A reference to this instance. + */ + setCount( count ) { + + this.count = count; + + return this; + + } + +} + +const _matrix = /*@__PURE__*/ new Matrix4(); + +/** + * This class is designed to assist with raycasting. Raycasting is used for + * mouse picking (working out what objects in the 3d space the mouse is over) + * amongst other things. + */ +class Raycaster { + + /** + * Constructs a new raycaster. + * + * @param {Vector3} origin - The origin vector where the ray casts from. + * @param {Vector3} direction - The (normalized) direction vector that gives direction to the ray. + * @param {number} [near=0] - All results returned are further away than near. Near can't be negative. + * @param {number} [far=Infinity] - All results returned are closer than far. Far can't be lower than near. + */ + constructor( origin, direction, near = 0, far = Infinity ) { + + /** + * The ray used for raycasting. + * + * @type {Ray} + */ + this.ray = new Ray( origin, direction ); + + /** + * All results returned are further away than near. Near can't be negative. + * + * @type {number} + * @default 0 + */ + this.near = near; + + /** + * All results returned are further away than near. Near can't be negative. + * + * @type {number} + * @default Infinity + */ + this.far = far; + + /** + * The camera to use when raycasting against view-dependent objects such as + * billboarded objects like sprites. This field can be set manually or + * is set when calling `setFromCamera()`. + * + * @type {?Camera} + * @default null + */ + this.camera = null; + + /** + * Allows to selectively ignore 3D objects when performing intersection tests. + * The following code example ensures that only 3D objects on layer `1` will be + * honored by raycaster. + * ```js + * raycaster.layers.set( 1 ); + * object.layers.enable( 1 ); + * ``` + * + * @type {Layers} + */ + this.layers = new Layers(); + + + /** + * A parameter object that configures the raycasting. It has the structure: + * + * ``` + * { + * Mesh: {}, + * Line: { threshold: 1 }, + * LOD: {}, + * Points: { threshold: 1 }, + * Sprite: {} + * } + * ``` + * Where `threshold` is the precision of the raycaster when intersecting objects, in world units. + * + * @type {Object} + */ + this.params = { + Mesh: {}, + Line: { threshold: 1 }, + LOD: {}, + Points: { threshold: 1 }, + Sprite: {} + }; + + } + + /** + * Updates the ray with a new origin and direction by copying the values from the arguments. + * + * @param {Vector3} origin - The origin vector where the ray casts from. + * @param {Vector3} direction - The (normalized) direction vector that gives direction to the ray. + */ + set( origin, direction ) { + + // direction is assumed to be normalized (for accurate distance calculations) + + this.ray.set( origin, direction ); + + } + + /** + * Uses the given coordinates and camera to compute a new origin and direction for the internal ray. + * + * @param {Vector2} coords - 2D coordinates of the mouse, in normalized device coordinates (NDC). + * X and Y components should be between `-1` and `1`. + * @param {Camera} camera - The camera from which the ray should originate. + */ + setFromCamera( coords, camera ) { + + if ( camera.isPerspectiveCamera ) { + + this.ray.origin.setFromMatrixPosition( camera.matrixWorld ); + this.ray.direction.set( coords.x, coords.y, 0.5 ).unproject( camera ).sub( this.ray.origin ).normalize(); + this.camera = camera; + + } else if ( camera.isOrthographicCamera ) { + + this.ray.origin.set( coords.x, coords.y, ( camera.near + camera.far ) / ( camera.near - camera.far ) ).unproject( camera ); // set origin in plane of camera + this.ray.direction.set( 0, 0, -1 ).transformDirection( camera.matrixWorld ); + this.camera = camera; + + } else { + + console.error( 'THREE.Raycaster: Unsupported camera type: ' + camera.type ); + + } + + } + + /** + * Uses the given WebXR controller to compute a new origin and direction for the internal ray. + * + * @param {WebXRController} controller - The controller to copy the position and direction from. + * @return {Raycaster} A reference to this raycaster. + */ + setFromXRController( controller ) { + + _matrix.identity().extractRotation( controller.matrixWorld ); + + this.ray.origin.setFromMatrixPosition( controller.matrixWorld ); + this.ray.direction.set( 0, 0, -1 ).applyMatrix4( _matrix ); + + return this; + + } + + /** + * The intersection point of a raycaster intersection test. + * @typedef {Object} Raycaster~Intersection + * @property {number} distance - The distance from the ray's origin to the intersection point. + * @property {number} distanceToRay - Some 3D objects e.g. {@link Points} provide the distance of the + * intersection to the nearest point on the ray. For other objects it will be `undefined`. + * @property {Vector3} point - The intersection point, in world coordinates. + * @property {Object} face - The face that has been intersected. + * @property {number} faceIndex - The face index. + * @property {Object3D} object - The 3D object that has been intersected. + * @property {Vector2} uv - U,V coordinates at point of intersection. + * @property {Vector2} uv1 - Second set of U,V coordinates at point of intersection. + * @property {Vector3} uv1 - Interpolated normal vector at point of intersection. + * @property {number} instanceId - The index number of the instance where the ray + * intersects the {@link InstancedMesh}. + */ + + /** + * Checks all intersection between the ray and the object with or without the + * descendants. Intersections are returned sorted by distance, closest first. + * + * `Raycaster` delegates to the `raycast()` method of the passed 3D object, when + * evaluating whether the ray intersects the object or not. This allows meshes to respond + * differently to ray casting than lines or points. + * + * Note that for meshes, faces must be pointed towards the origin of the ray in order + * to be detected; intersections of the ray passing through the back of a face will not + * be detected. To raycast against both faces of an object, you'll want to set {@link Material#side} + * to `THREE.DoubleSide`. + * + * @param {Object3D} object - The 3D object to check for intersection with the ray. + * @param {boolean} [recursive=true] - If set to `true`, it also checks all descendants. + * Otherwise it only checks intersection with the object. + * @param {Array} [intersects=[]] The target array that holds the result of the method. + * @return {Array} An array holding the intersection points. + */ + intersectObject( object, recursive = true, intersects = [] ) { + + intersect( object, this, intersects, recursive ); + + intersects.sort( ascSort ); + + return intersects; + + } + + /** + * Checks all intersection between the ray and the objects with or without + * the descendants. Intersections are returned sorted by distance, closest first. + * + * @param {Array} objects - The 3D objects to check for intersection with the ray. + * @param {boolean} [recursive=true] - If set to `true`, it also checks all descendants. + * Otherwise it only checks intersection with the object. + * @param {Array} [intersects=[]] The target array that holds the result of the method. + * @return {Array} An array holding the intersection points. + */ + intersectObjects( objects, recursive = true, intersects = [] ) { + + for ( let i = 0, l = objects.length; i < l; i ++ ) { + + intersect( objects[ i ], this, intersects, recursive ); + + } + + intersects.sort( ascSort ); + + return intersects; + + } + +} + +function ascSort( a, b ) { + + return a.distance - b.distance; + +} + +function intersect( object, raycaster, intersects, recursive ) { + + let propagate = true; + + if ( object.layers.test( raycaster.layers ) ) { + + const result = object.raycast( raycaster, intersects ); + + if ( result === false ) propagate = false; + + } + + if ( propagate === true && recursive === true ) { + + const children = object.children; + + for ( let i = 0, l = children.length; i < l; i ++ ) { + + intersect( children[ i ], raycaster, intersects, true ); + + } + + } + +} + +/** + * This class can be used to represent points in 3D space as + * [Spherical coordinates]{@link https://en.wikipedia.org/wiki/Spherical_coordinate_system}. + */ +class Spherical { + + /** + * Constructs a new spherical. + * + * @param {number} [radius=1] - The radius, or the Euclidean distance (straight-line distance) from the point to the origin. + * @param {number} [phi=0] - The polar angle in radians from the y (up) axis. + * @param {number} [theta=0] - The equator/azimuthal angle in radians around the y (up) axis. + */ + constructor( radius = 1, phi = 0, theta = 0 ) { + + /** + * The radius, or the Euclidean distance (straight-line distance) from the point to the origin. + * + * @type {number} + * @default 1 + */ + this.radius = radius; + + /** + * The polar angle in radians from the y (up) axis. + * + * @type {number} + * @default 0 + */ + this.phi = phi; + + /** + * The equator/azimuthal angle in radians around the y (up) axis. + * + * @type {number} + * @default 0 + */ + this.theta = theta; + + } + + /** + * Sets the spherical components by copying the given values. + * + * @param {number} radius - The radius. + * @param {number} phi - The polar angle. + * @param {number} theta - The azimuthal angle. + * @return {Spherical} A reference to this spherical. + */ + set( radius, phi, theta ) { + + this.radius = radius; + this.phi = phi; + this.theta = theta; + + return this; + + } + + /** + * Copies the values of the given spherical to this instance. + * + * @param {Spherical} other - The spherical to copy. + * @return {Spherical} A reference to this spherical. + */ + copy( other ) { + + this.radius = other.radius; + this.phi = other.phi; + this.theta = other.theta; + + return this; + + } + + /** + * Restricts the polar angle [page:.phi phi] to be between `0.000001` and pi - + * `0.000001`. + * + * @return {Spherical} A reference to this spherical. + */ + makeSafe() { + + const EPS = 0.000001; + this.phi = clamp( this.phi, EPS, Math.PI - EPS ); + + return this; + + } + + /** + * Sets the spherical components from the given vector which is assumed to hold + * Cartesian coordinates. + * + * @param {Vector3} v - The vector to set. + * @return {Spherical} A reference to this spherical. + */ + setFromVector3( v ) { + + return this.setFromCartesianCoords( v.x, v.y, v.z ); + + } + + /** + * Sets the spherical components from the given Cartesian coordinates. + * + * @param {number} x - The x value. + * @param {number} y - The x value. + * @param {number} z - The x value. + * @return {Spherical} A reference to this spherical. + */ + setFromCartesianCoords( x, y, z ) { + + this.radius = Math.sqrt( x * x + y * y + z * z ); + + if ( this.radius === 0 ) { + + this.theta = 0; + this.phi = 0; + + } else { + + this.theta = Math.atan2( x, z ); + this.phi = Math.acos( clamp( y / this.radius, -1, 1 ) ); + + } + + return this; + + } + + /** + * Returns a new spherical with copied values from this instance. + * + * @return {Spherical} A clone of this instance. + */ + clone() { + + return new this.constructor().copy( this ); + + } + +} + +/** + * This class can be used to represent points in 3D space as + * [Cylindrical coordinates]{@link https://en.wikipedia.org/wiki/Cylindrical_coordinate_system}. + */ +class Cylindrical { + + /** + * Constructs a new cylindrical. + * + * @param {number} [radius=1] - The distance from the origin to a point in the x-z plane. + * @param {number} [theta=0] - A counterclockwise angle in the x-z plane measured in radians from the positive z-axis. + * @param {number} [y=0] - The height above the x-z plane. + */ + constructor( radius = 1, theta = 0, y = 0 ) { + + /** + * The distance from the origin to a point in the x-z plane. + * + * @type {number} + * @default 1 + */ + this.radius = radius; + + /** + * A counterclockwise angle in the x-z plane measured in radians from the positive z-axis. + * + * @type {number} + * @default 0 + */ + this.theta = theta; + + /** + * The height above the x-z plane. + * + * @type {number} + * @default 0 + */ + this.y = y; + + } + + /** + * Sets the cylindrical components by copying the given values. + * + * @param {number} radius - The radius. + * @param {number} theta - The theta angle. + * @param {number} y - The height value. + * @return {Cylindrical} A reference to this cylindrical. + */ + set( radius, theta, y ) { + + this.radius = radius; + this.theta = theta; + this.y = y; + + return this; + + } + + /** + * Copies the values of the given cylindrical to this instance. + * + * @param {Cylindrical} other - The cylindrical to copy. + * @return {Cylindrical} A reference to this cylindrical. + */ + copy( other ) { + + this.radius = other.radius; + this.theta = other.theta; + this.y = other.y; + + return this; + + } + + /** + * Sets the cylindrical components from the given vector which is assumed to hold + * Cartesian coordinates. + * + * @param {Vector3} v - The vector to set. + * @return {Cylindrical} A reference to this cylindrical. + */ + setFromVector3( v ) { + + return this.setFromCartesianCoords( v.x, v.y, v.z ); + + } + + /** + * Sets the cylindrical components from the given Cartesian coordinates. + * + * @param {number} x - The x value. + * @param {number} y - The x value. + * @param {number} z - The x value. + * @return {Cylindrical} A reference to this cylindrical. + */ + setFromCartesianCoords( x, y, z ) { + + this.radius = Math.sqrt( x * x + z * z ); + this.theta = Math.atan2( x, z ); + this.y = y; + + return this; + + } + + /** + * Returns a new cylindrical with copied values from this instance. + * + * @return {Cylindrical} A clone of this instance. + */ + clone() { + + return new this.constructor().copy( this ); + + } + +} + +/** + * Represents a 2x2 matrix. + * + * A Note on Row-Major and Column-Major Ordering: + * + * The constructor and {@link Matrix2#set} method take arguments in + * [row-major]{@link https://en.wikipedia.org/wiki/Row-_and_column-major_order#Column-major_order} + * order, while internally they are stored in the {@link Matrix2#elements} array in column-major order. + * This means that calling: + * ```js + * const m = new THREE.Matrix2(); + * m.set( 11, 12, + * 21, 22 ); + * ``` + * will result in the elements array containing: + * ```js + * m.elements = [ 11, 21, + * 12, 22 ]; + * ``` + * and internally all calculations are performed using column-major ordering. + * However, as the actual ordering makes no difference mathematically and + * most people are used to thinking about matrices in row-major order, the + * three.js documentation shows matrices in row-major order. Just bear in + * mind that if you are reading the source code, you'll have to take the + * transpose of any matrices outlined here to make sense of the calculations. + */ +class Matrix2 { + + /** + * Constructs a new 2x2 matrix. The arguments are supposed to be + * in row-major order. If no arguments are provided, the constructor + * initializes the matrix as an identity matrix. + * + * @param {number} [n11] - 1-1 matrix element. + * @param {number} [n12] - 1-2 matrix element. + * @param {number} [n21] - 2-1 matrix element. + * @param {number} [n22] - 2-2 matrix element. + */ + constructor( n11, n12, n21, n22 ) { + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + Matrix2.prototype.isMatrix2 = true; + + /** + * A column-major list of matrix values. + * + * @type {Array} + */ + this.elements = [ + 1, 0, + 0, 1, + ]; + + if ( n11 !== undefined ) { + + this.set( n11, n12, n21, n22 ); + + } + + } + + /** + * Sets this matrix to the 2x2 identity matrix. + * + * @return {Matrix2} A reference to this matrix. + */ + identity() { + + this.set( + 1, 0, + 0, 1, + ); + + return this; + + } + + /** + * Sets the elements of the matrix from the given array. + * + * @param {Array} array - The matrix elements in column-major order. + * @param {number} [offset=0] - Index of the first element in the array. + * @return {Matrix2} A reference to this matrix. + */ + fromArray( array, offset = 0 ) { + + for ( let i = 0; i < 4; i ++ ) { + + this.elements[ i ] = array[ i + offset ]; + + } + + return this; + + } + + /** + * Sets the elements of the matrix.The arguments are supposed to be + * in row-major order. + * + * @param {number} n11 - 1-1 matrix element. + * @param {number} n12 - 1-2 matrix element. + * @param {number} n21 - 2-1 matrix element. + * @param {number} n22 - 2-2 matrix element. + * @return {Matrix2} A reference to this matrix. + */ + set( n11, n12, n21, n22 ) { + + const te = this.elements; + + te[ 0 ] = n11; te[ 2 ] = n12; + te[ 1 ] = n21; te[ 3 ] = n22; + + return this; + + } + +} + +const _vector$4 = /*@__PURE__*/ new Vector2(); + +/** + * Represents an axis-aligned bounding box (AABB) in 2D space. + */ +class Box2 { + + /** + * Constructs a new bounding box. + * + * @param {Vector2} [min=(Infinity,Infinity)] - A vector representing the lower boundary of the box. + * @param {Vector2} [max=(-Infinity,-Infinity)] - A vector representing the upper boundary of the box. + */ + constructor( min = new Vector2( + Infinity, + Infinity ), max = new Vector2( - Infinity, - Infinity ) ) { + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isBox2 = true; + + /** + * The lower boundary of the box. + * + * @type {Vector2} + */ + this.min = min; + + /** + * The upper boundary of the box. + * + * @type {Vector2} + */ + this.max = max; + + } + + /** + * Sets the lower and upper boundaries of this box. + * Please note that this method only copies the values from the given objects. + * + * @param {Vector2} min - The lower boundary of the box. + * @param {Vector2} max - The upper boundary of the box. + * @return {Box2} A reference to this bounding box. + */ + set( min, max ) { + + this.min.copy( min ); + this.max.copy( max ); + + return this; + + } + + /** + * Sets the upper and lower bounds of this box so it encloses the position data + * in the given array. + * + * @param {Array} points - An array holding 2D position data as instances of {@link Vector2}. + * @return {Box2} A reference to this bounding box. + */ + setFromPoints( points ) { + + this.makeEmpty(); + + for ( let i = 0, il = points.length; i < il; i ++ ) { + + this.expandByPoint( points[ i ] ); + + } + + return this; + + } + + /** + * Centers this box on the given center vector and sets this box's width, height and + * depth to the given size values. + * + * @param {Vector2} center - The center of the box. + * @param {Vector2} size - The x and y dimensions of the box. + * @return {Box2} A reference to this bounding box. + */ + setFromCenterAndSize( center, size ) { + + const halfSize = _vector$4.copy( size ).multiplyScalar( 0.5 ); + this.min.copy( center ).sub( halfSize ); + this.max.copy( center ).add( halfSize ); + + return this; + + } + + /** + * Returns a new box with copied values from this instance. + * + * @return {Box2} A clone of this instance. + */ + clone() { + + return new this.constructor().copy( this ); + + } + + /** + * Copies the values of the given box to this instance. + * + * @param {Box2} box - The box to copy. + * @return {Box2} A reference to this bounding box. + */ + copy( box ) { + + this.min.copy( box.min ); + this.max.copy( box.max ); + + return this; + + } + + /** + * Makes this box empty which means in encloses a zero space in 2D. + * + * @return {Box2} A reference to this bounding box. + */ + makeEmpty() { + + this.min.x = this.min.y = + Infinity; + this.max.x = this.max.y = - Infinity; + + return this; + + } + + /** + * Returns true if this box includes zero points within its bounds. + * Note that a box with equal lower and upper bounds still includes one + * point, the one both bounds share. + * + * @return {boolean} Whether this box is empty or not. + */ + isEmpty() { + + // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes + + return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y ); + + } + + /** + * Returns the center point of this box. + * + * @param {Vector2} target - The target vector that is used to store the method's result. + * @return {Vector2} The center point. + */ + getCenter( target ) { + + return this.isEmpty() ? target.set( 0, 0 ) : target.addVectors( this.min, this.max ).multiplyScalar( 0.5 ); + + } + + /** + * Returns the dimensions of this box. + * + * @param {Vector2} target - The target vector that is used to store the method's result. + * @return {Vector2} The size. + */ + getSize( target ) { + + return this.isEmpty() ? target.set( 0, 0 ) : target.subVectors( this.max, this.min ); + + } + + /** + * Expands the boundaries of this box to include the given point. + * + * @param {Vector2} point - The point that should be included by the bounding box. + * @return {Box2} A reference to this bounding box. + */ + expandByPoint( point ) { + + this.min.min( point ); + this.max.max( point ); + + return this; + + } + + /** + * Expands this box equilaterally by the given vector. The width of this + * box will be expanded by the x component of the vector in both + * directions. The height of this box will be expanded by the y component of + * the vector in both directions. + * + * @param {Vector2} vector - The vector that should expand the bounding box. + * @return {Box2} A reference to this bounding box. + */ + expandByVector( vector ) { + + this.min.sub( vector ); + this.max.add( vector ); + + return this; + + } + + /** + * Expands each dimension of the box by the given scalar. If negative, the + * dimensions of the box will be contracted. + * + * @param {number} scalar - The scalar value that should expand the bounding box. + * @return {Box2} A reference to this bounding box. + */ + expandByScalar( scalar ) { + + this.min.addScalar( - scalar ); + this.max.addScalar( scalar ); + + return this; + + } + + /** + * Returns `true` if the given point lies within or on the boundaries of this box. + * + * @param {Vector2} point - The point to test. + * @return {boolean} Whether the bounding box contains the given point or not. + */ + containsPoint( point ) { + + return point.x >= this.min.x && point.x <= this.max.x && + point.y >= this.min.y && point.y <= this.max.y; + + } + + /** + * Returns `true` if this bounding box includes the entirety of the given bounding box. + * If this box and the given one are identical, this function also returns `true`. + * + * @param {Box2} box - The bounding box to test. + * @return {boolean} Whether the bounding box contains the given bounding box or not. + */ + containsBox( box ) { + + return this.min.x <= box.min.x && box.max.x <= this.max.x && + this.min.y <= box.min.y && box.max.y <= this.max.y; + + } + + /** + * Returns a point as a proportion of this box's width and height. + * + * @param {Vector2} point - A point in 2D space. + * @param {Vector2} target - The target vector that is used to store the method's result. + * @return {Vector2} A point as a proportion of this box's width and height. + */ + getParameter( point, target ) { + + // This can potentially have a divide by zero if the box + // has a size dimension of 0. + + return target.set( + ( point.x - this.min.x ) / ( this.max.x - this.min.x ), + ( point.y - this.min.y ) / ( this.max.y - this.min.y ) + ); + + } + + /** + * Returns `true` if the given bounding box intersects with this bounding box. + * + * @param {Box2} box - The bounding box to test. + * @return {boolean} Whether the given bounding box intersects with this bounding box. + */ + intersectsBox( box ) { + + // using 4 splitting planes to rule out intersections + + return box.max.x >= this.min.x && box.min.x <= this.max.x && + box.max.y >= this.min.y && box.min.y <= this.max.y; + + } + + /** + * Clamps the given point within the bounds of this box. + * + * @param {Vector2} point - The point to clamp. + * @param {Vector2} target - The target vector that is used to store the method's result. + * @return {Vector2} The clamped point. + */ + clampPoint( point, target ) { + + return target.copy( point ).clamp( this.min, this.max ); + + } + + /** + * Returns the euclidean distance from any edge of this box to the specified point. If + * the given point lies inside of this box, the distance will be `0`. + * + * @param {Vector2} point - The point to compute the distance to. + * @return {number} The euclidean distance. + */ + distanceToPoint( point ) { + + return this.clampPoint( point, _vector$4 ).distanceTo( point ); + + } + + /** + * Computes the intersection of this bounding box and the given one, setting the upper + * bound of this box to the lesser of the two boxes' upper bounds and the + * lower bound of this box to the greater of the two boxes' lower bounds. If + * there's no overlap, makes this box empty. + * + * @param {Box2} box - The bounding box to intersect with. + * @return {Box2} A reference to this bounding box. + */ + intersect( box ) { + + this.min.max( box.min ); + this.max.min( box.max ); + + if ( this.isEmpty() ) this.makeEmpty(); + + return this; + + } + + /** + * Computes the union of this box and another and the given one, setting the upper + * bound of this box to the greater of the two boxes' upper bounds and the + * lower bound of this box to the lesser of the two boxes' lower bounds. + * + * @param {Box2} box - The bounding box that will be unioned with this instance. + * @return {Box2} A reference to this bounding box. + */ + union( box ) { + + this.min.min( box.min ); + this.max.max( box.max ); + + return this; + + } + + /** + * Adds the given offset to both the upper and lower bounds of this bounding box, + * effectively moving it in 2D space. + * + * @param {Vector2} offset - The offset that should be used to translate the bounding box. + * @return {Box2} A reference to this bounding box. + */ + translate( offset ) { + + this.min.add( offset ); + this.max.add( offset ); + + return this; + + } + + /** + * Returns `true` if this bounding box is equal with the given one. + * + * @param {Box2} box - The box to test for equality. + * @return {boolean} Whether this bounding box is equal with the given one. + */ + equals( box ) { + + return box.min.equals( this.min ) && box.max.equals( this.max ); + + } + +} + +const _startP = /*@__PURE__*/ new Vector3(); +const _startEnd = /*@__PURE__*/ new Vector3(); + +/** + * An analytical line segment in 3D space represented by a start and end point. + */ +class Line3 { + + /** + * Constructs a new line segment. + * + * @param {Vector3} [start=(0,0,0)] - Start of the line segment. + * @param {Vector3} [end=(0,0,0)] - End of the line segment. + */ + constructor( start = new Vector3(), end = new Vector3() ) { + + /** + * Start of the line segment. + * + * @type {Vector3} + */ + this.start = start; + + /** + * End of the line segment. + * + * @type {Vector3} + */ + this.end = end; + + } + + /** + * Sets the start and end values by copying the given vectors. + * + * @param {Vector3} start - The start point. + * @param {Vector3} end - The end point. + * @return {Line3} A reference to this line segment. + */ + set( start, end ) { + + this.start.copy( start ); + this.end.copy( end ); + + return this; + + } + + /** + * Copies the values of the given line segment to this instance. + * + * @param {Line3} line - The line segment to copy. + * @return {Line3} A reference to this line segment. + */ + copy( line ) { + + this.start.copy( line.start ); + this.end.copy( line.end ); + + return this; + + } + + /** + * Returns the center of the line segment. + * + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The center point. + */ + getCenter( target ) { + + return target.addVectors( this.start, this.end ).multiplyScalar( 0.5 ); + + } + + /** + * Returns the delta vector of the line segment's start and end point. + * + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The delta vector. + */ + delta( target ) { + + return target.subVectors( this.end, this.start ); + + } + + /** + * Returns the squared Euclidean distance between the line' start and end point. + * + * @return {number} The squared Euclidean distance. + */ + distanceSq() { + + return this.start.distanceToSquared( this.end ); + + } + + /** + * Returns the Euclidean distance between the line' start and end point. + * + * @return {number} The Euclidean distance. + */ + distance() { + + return this.start.distanceTo( this.end ); + + } + + /** + * Returns a vector at a certain position along the line segment. + * + * @param {number} t - A value between `[0,1]` to represent a position along the line segment. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The delta vector. + */ + at( t, target ) { + + return this.delta( target ).multiplyScalar( t ).add( this.start ); + + } + + /** + * Returns a point parameter based on the closest point as projected on the line segment. + * + * @param {Vector3} point - The point for which to return a point parameter. + * @param {boolean} clampToLine - Whether to clamp the result to the range `[0,1]` or not. + * @return {number} The point parameter. + */ + closestPointToPointParameter( point, clampToLine ) { + + _startP.subVectors( point, this.start ); + _startEnd.subVectors( this.end, this.start ); + + const startEnd2 = _startEnd.dot( _startEnd ); + const startEnd_startP = _startEnd.dot( _startP ); + + let t = startEnd_startP / startEnd2; + + if ( clampToLine ) { + + t = clamp( t, 0, 1 ); + + } + + return t; + + } + + /** + * Returns the closets point on the line for a given point. + * + * @param {Vector3} point - The point to compute the closest point on the line for. + * @param {boolean} clampToLine - Whether to clamp the result to the range `[0,1]` or not. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The closest point on the line. + */ + closestPointToPoint( point, clampToLine, target ) { + + const t = this.closestPointToPointParameter( point, clampToLine ); + + return this.delta( target ).multiplyScalar( t ).add( this.start ); + + } + + /** + * Applies a 4x4 transformation matrix to this line segment. + * + * @param {Matrix4} matrix - The transformation matrix. + * @return {Line3} A reference to this line segment. + */ + applyMatrix4( matrix ) { + + this.start.applyMatrix4( matrix ); + this.end.applyMatrix4( matrix ); + + return this; + + } + + /** + * Returns `true` if this line segment is equal with the given one. + * + * @param {Line3} line - The line segment to test for equality. + * @return {boolean} Whether this line segment is equal with the given one. + */ + equals( line ) { + + return line.start.equals( this.start ) && line.end.equals( this.end ); + + } + + /** + * Returns a new line segment with copied values from this instance. + * + * @return {Line3} A clone of this instance. + */ + clone() { + + return new this.constructor().copy( this ); + + } + +} + +const _vector$3 = /*@__PURE__*/ new Vector3(); + +/** + * This displays a cone shaped helper object for a {@link SpotLight}. + * + * ```js + * const spotLight = new THREE.SpotLight( 0xffffff ); + * spotLight.position.set( 10, 10, 10 ); + * scene.add( spotLight ); + * + * const spotLightHelper = new THREE.SpotLightHelper( spotLight ); + * scene.add( spotLightHelper ); + * ``` + * + * @augments Object3D + */ +class SpotLightHelper extends Object3D { + + /** + * Constructs a new spot light helper. + * + * @param {HemisphereLight} light - The light to be visualized. + * @param {number|Color|string} [color] - The helper's color. If not set, the helper will take + * the color of the light. + */ + constructor( light, color ) { + + super(); + + /** + * The light being visualized. + * + * @type {SpotLight} + */ + this.light = light; + + this.matrixAutoUpdate = false; + + /** + * The color parameter passed in the constructor. + * If not set, the helper will take the color of the light. + * + * @type {number|Color|string} + */ + this.color = color; + + this.type = 'SpotLightHelper'; + + const geometry = new BufferGeometry(); + + const positions = [ + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 1, 0, 1, + 0, 0, 0, -1, 0, 1, + 0, 0, 0, 0, 1, 1, + 0, 0, 0, 0, -1, 1 + ]; + + for ( let i = 0, j = 1, l = 32; i < l; i ++, j ++ ) { + + const p1 = ( i / l ) * Math.PI * 2; + const p2 = ( j / l ) * Math.PI * 2; + + positions.push( + Math.cos( p1 ), Math.sin( p1 ), 1, + Math.cos( p2 ), Math.sin( p2 ), 1 + ); + + } + + geometry.setAttribute( 'position', new Float32BufferAttribute( positions, 3 ) ); + + const material = new LineBasicMaterial( { fog: false, toneMapped: false } ); + + this.cone = new LineSegments( geometry, material ); + this.add( this.cone ); + + this.update(); + + } + + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose() { + + this.cone.geometry.dispose(); + this.cone.material.dispose(); + + } + + /** + * Updates the helper to match the position and direction of the + * light being visualized. + */ + update() { + + this.light.updateWorldMatrix( true, false ); + this.light.target.updateWorldMatrix( true, false ); + + // update the local matrix based on the parent and light target transforms + if ( this.parent ) { + + this.parent.updateWorldMatrix( true ); + + this.matrix + .copy( this.parent.matrixWorld ) + .invert() + .multiply( this.light.matrixWorld ); + + } else { + + this.matrix.copy( this.light.matrixWorld ); + + } + + this.matrixWorld.copy( this.light.matrixWorld ); + + const coneLength = this.light.distance ? this.light.distance : 1000; + const coneWidth = coneLength * Math.tan( this.light.angle ); + + this.cone.scale.set( coneWidth, coneWidth, coneLength ); + + _vector$3.setFromMatrixPosition( this.light.target.matrixWorld ); + + this.cone.lookAt( _vector$3 ); + + if ( this.color !== undefined ) { + + this.cone.material.color.set( this.color ); + + } else { + + this.cone.material.color.copy( this.light.color ); + + } + + } + +} + +const _vector$2 = /*@__PURE__*/ new Vector3(); +const _boneMatrix = /*@__PURE__*/ new Matrix4(); +const _matrixWorldInv = /*@__PURE__*/ new Matrix4(); + +/** + * A helper object to assist with visualizing a {@link Skeleton}. + * + * ```js + * const helper = new THREE.SkeletonHelper( skinnedMesh ); + * scene.add( helper ); + * ``` + * + * @augments LineSegments + */ +class SkeletonHelper extends LineSegments { + + /** + * Constructs a new hemisphere light helper. + * + * @param {Object3D} object - Usually an instance of {@link SkinnedMesh}. However, any 3D object + * can be used if it represents a hierarchy of bones (see {@link Bone}). + */ + constructor( object ) { + + const bones = getBoneList( object ); + + const geometry = new BufferGeometry(); + + const vertices = []; + const colors = []; + + const color1 = new Color( 0, 0, 1 ); + const color2 = new Color( 0, 1, 0 ); + + for ( let i = 0; i < bones.length; i ++ ) { + + const bone = bones[ i ]; + + if ( bone.parent && bone.parent.isBone ) { + + vertices.push( 0, 0, 0 ); + vertices.push( 0, 0, 0 ); + colors.push( color1.r, color1.g, color1.b ); + colors.push( color2.r, color2.g, color2.b ); + + } + + } + + geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + geometry.setAttribute( 'color', new Float32BufferAttribute( colors, 3 ) ); + + const material = new LineBasicMaterial( { vertexColors: true, depthTest: false, depthWrite: false, toneMapped: false, transparent: true } ); + + super( geometry, material ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSkeletonHelper = true; + + this.type = 'SkeletonHelper'; + + /** + * The object being visualized. + * + * @type {Object3D} + */ + this.root = object; + + /** + * The list of bones that the helper visualizes. + * + * @type {Array} + */ + this.bones = bones; + + this.matrix = object.matrixWorld; + this.matrixAutoUpdate = false; + + } + + updateMatrixWorld( force ) { + + const bones = this.bones; + + const geometry = this.geometry; + const position = geometry.getAttribute( 'position' ); + + _matrixWorldInv.copy( this.root.matrixWorld ).invert(); + + for ( let i = 0, j = 0; i < bones.length; i ++ ) { + + const bone = bones[ i ]; + + if ( bone.parent && bone.parent.isBone ) { + + _boneMatrix.multiplyMatrices( _matrixWorldInv, bone.matrixWorld ); + _vector$2.setFromMatrixPosition( _boneMatrix ); + position.setXYZ( j, _vector$2.x, _vector$2.y, _vector$2.z ); + + _boneMatrix.multiplyMatrices( _matrixWorldInv, bone.parent.matrixWorld ); + _vector$2.setFromMatrixPosition( _boneMatrix ); + position.setXYZ( j + 1, _vector$2.x, _vector$2.y, _vector$2.z ); + + j += 2; + + } + + } + + geometry.getAttribute( 'position' ).needsUpdate = true; + + super.updateMatrixWorld( force ); + + } + + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose() { + + this.geometry.dispose(); + this.material.dispose(); + + } + +} + + +function getBoneList( object ) { + + const boneList = []; + + if ( object.isBone === true ) { + + boneList.push( object ); + + } + + for ( let i = 0; i < object.children.length; i ++ ) { + + boneList.push( ...getBoneList( object.children[ i ] ) ); + + } + + return boneList; + +} + +/** + * This displays a helper object consisting of a spherical mesh for + * visualizing an instance of {@link PointLight}. + * + * ```js + * const pointLight = new THREE.PointLight( 0xff0000, 1, 100 ); + * pointLight.position.set( 10, 10, 10 ); + * scene.add( pointLight ); + * + * const sphereSize = 1; + * const pointLightHelper = new THREE.PointLightHelper( pointLight, sphereSize ); + * scene.add( pointLightHelper ); + * ``` + * + * @augments Mesh + */ +class PointLightHelper extends Mesh { + + /** + * Constructs a new point light helper. + * + * @param {PointLight} light - The light to be visualized. + * @param {number} [sphereSize=1] - The size of the sphere helper. + * @param {number|Color|string} [color] - The helper's color. If not set, the helper will take + * the color of the light. + */ + constructor( light, sphereSize, color ) { + + const geometry = new SphereGeometry( sphereSize, 4, 2 ); + const material = new MeshBasicMaterial( { wireframe: true, fog: false, toneMapped: false } ); + + super( geometry, material ); + + /** + * The light being visualized. + * + * @type {HemisphereLight} + */ + this.light = light; + + /** + * The color parameter passed in the constructor. + * If not set, the helper will take the color of the light. + * + * @type {number|Color|string} + */ + this.color = color; + + this.type = 'PointLightHelper'; + + this.matrix = this.light.matrixWorld; + this.matrixAutoUpdate = false; + + this.update(); + + + /* + // TODO: delete this comment? + const distanceGeometry = new THREE.IcosahedronGeometry( 1, 2 ); + const distanceMaterial = new THREE.MeshBasicMaterial( { color: hexColor, fog: false, wireframe: true, opacity: 0.1, transparent: true } ); + + this.lightSphere = new THREE.Mesh( bulbGeometry, bulbMaterial ); + this.lightDistance = new THREE.Mesh( distanceGeometry, distanceMaterial ); + + const d = light.distance; + + if ( d === 0.0 ) { + + this.lightDistance.visible = false; + + } else { + + this.lightDistance.scale.set( d, d, d ); + + } + + this.add( this.lightDistance ); + */ + + } + + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose() { + + this.geometry.dispose(); + this.material.dispose(); + + } + + /** + * Updates the helper to match the position of the + * light being visualized. + */ + update() { + + this.light.updateWorldMatrix( true, false ); + + if ( this.color !== undefined ) { + + this.material.color.set( this.color ); + + } else { + + this.material.color.copy( this.light.color ); + + } + + /* + const d = this.light.distance; + + if ( d === 0.0 ) { + + this.lightDistance.visible = false; + + } else { + + this.lightDistance.visible = true; + this.lightDistance.scale.set( d, d, d ); + + } + */ + + } + +} + +const _vector$1 = /*@__PURE__*/ new Vector3(); +const _color1 = /*@__PURE__*/ new Color(); +const _color2 = /*@__PURE__*/ new Color(); + +/** + * Creates a visual aid consisting of a spherical mesh for a + * given {@link HemisphereLight}. + * + * ```js + * const light = new THREE.HemisphereLight( 0xffffbb, 0x080820, 1 ); + * const helper = new THREE.HemisphereLightHelper( light, 5 ); + * scene.add( helper ); + * ``` + * + * @augments Object3D + */ +class HemisphereLightHelper extends Object3D { + + /** + * Constructs a new hemisphere light helper. + * + * @param {HemisphereLight} light - The light to be visualized. + * @param {number} [size=1] - The size of the mesh used to visualize the light. + * @param {number|Color|string} [color] - The helper's color. If not set, the helper will take + * the color of the light. + */ + constructor( light, size, color ) { + + super(); + + /** + * The light being visualized. + * + * @type {HemisphereLight} + */ + this.light = light; + + this.matrix = light.matrixWorld; + this.matrixAutoUpdate = false; + + /** + * The color parameter passed in the constructor. + * If not set, the helper will take the color of the light. + * + * @type {number|Color|string} + */ + this.color = color; + + this.type = 'HemisphereLightHelper'; + + const geometry = new OctahedronGeometry( size ); + geometry.rotateY( Math.PI * 0.5 ); + + this.material = new MeshBasicMaterial( { wireframe: true, fog: false, toneMapped: false } ); + if ( this.color === undefined ) this.material.vertexColors = true; + + const position = geometry.getAttribute( 'position' ); + const colors = new Float32Array( position.count * 3 ); + + geometry.setAttribute( 'color', new BufferAttribute( colors, 3 ) ); + + this.add( new Mesh( geometry, this.material ) ); + + this.update(); + + } + + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose() { + + this.children[ 0 ].geometry.dispose(); + this.children[ 0 ].material.dispose(); + + } + + /** + * Updates the helper to match the position and direction of the + * light being visualized. + */ + update() { + + const mesh = this.children[ 0 ]; + + if ( this.color !== undefined ) { + + this.material.color.set( this.color ); + + } else { + + const colors = mesh.geometry.getAttribute( 'color' ); + + _color1.copy( this.light.color ); + _color2.copy( this.light.groundColor ); + + for ( let i = 0, l = colors.count; i < l; i ++ ) { + + const color = ( i < ( l / 2 ) ) ? _color1 : _color2; + + colors.setXYZ( i, color.r, color.g, color.b ); + + } + + colors.needsUpdate = true; + + } + + this.light.updateWorldMatrix( true, false ); + + mesh.lookAt( _vector$1.setFromMatrixPosition( this.light.matrixWorld ).negate() ); + + } + +} + +/** + * The helper is an object to define grids. Grids are two-dimensional + * arrays of lines. + * + * ```js + * const size = 10; + * const divisions = 10; + * + * const gridHelper = new THREE.GridHelper( size, divisions ); + * scene.add( gridHelper ); + * ``` + * + * @augments LineSegments + */ +class GridHelper extends LineSegments { + + /** + * Constructs a new grid helper. + * + * @param {number} [size=10] - The size of the grid. + * @param {number} [divisions=10] - The number of divisions across the grid. + * @param {number|Color|string} [color1=0x444444] - The color of the center line. + * @param {number|Color|string} [color2=0x888888] - The color of the lines of the grid. + */ + constructor( size = 10, divisions = 10, color1 = 0x444444, color2 = 0x888888 ) { + + color1 = new Color( color1 ); + color2 = new Color( color2 ); + + const center = divisions / 2; + const step = size / divisions; + const halfSize = size / 2; + + const vertices = [], colors = []; + + for ( let i = 0, j = 0, k = - halfSize; i <= divisions; i ++, k += step ) { + + vertices.push( - halfSize, 0, k, halfSize, 0, k ); + vertices.push( k, 0, - halfSize, k, 0, halfSize ); + + const color = i === center ? color1 : color2; + + color.toArray( colors, j ); j += 3; + color.toArray( colors, j ); j += 3; + color.toArray( colors, j ); j += 3; + color.toArray( colors, j ); j += 3; + + } + + const geometry = new BufferGeometry(); + geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + geometry.setAttribute( 'color', new Float32BufferAttribute( colors, 3 ) ); + + const material = new LineBasicMaterial( { vertexColors: true, toneMapped: false } ); + + super( geometry, material ); + + this.type = 'GridHelper'; + + } + + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose() { + + this.geometry.dispose(); + this.material.dispose(); + + } + +} + +/** + * This helper is an object to define polar grids. Grids are + * two-dimensional arrays of lines. + * + * ```js + * const radius = 10; + * const sectors = 16; + * const rings = 8; + * const divisions = 64; + * + * const helper = new THREE.PolarGridHelper( radius, sectors, rings, divisions ); + * scene.add( helper ); + * ``` + * + * @augments LineSegments + */ +class PolarGridHelper extends LineSegments { + + /** + * Constructs a new polar grid helper. + * + * @param {number} [radius=10] - The radius of the polar grid. This can be any positive number. + * @param {number} [sectors=16] - The number of sectors the grid will be divided into. This can be any positive integer. + * @param {number} [rings=16] - The number of rings. This can be any positive integer. + * @param {number} [divisions=64] - The number of line segments used for each circle. This can be any positive integer. + * @param {number|Color|string} [color1=0x444444] - The first color used for grid elements. + * @param {number|Color|string} [color2=0x888888] - The second color used for grid elements. + */ + constructor( radius = 10, sectors = 16, rings = 8, divisions = 64, color1 = 0x444444, color2 = 0x888888 ) { + + color1 = new Color( color1 ); + color2 = new Color( color2 ); + + const vertices = []; + const colors = []; + + // create the sectors + + if ( sectors > 1 ) { + + for ( let i = 0; i < sectors; i ++ ) { + + const v = ( i / sectors ) * ( Math.PI * 2 ); + + const x = Math.sin( v ) * radius; + const z = Math.cos( v ) * radius; + + vertices.push( 0, 0, 0 ); + vertices.push( x, 0, z ); + + const color = ( i & 1 ) ? color1 : color2; + + colors.push( color.r, color.g, color.b ); + colors.push( color.r, color.g, color.b ); + + } + + } + + // create the rings + + for ( let i = 0; i < rings; i ++ ) { + + const color = ( i & 1 ) ? color1 : color2; + + const r = radius - ( radius / rings * i ); + + for ( let j = 0; j < divisions; j ++ ) { + + // first vertex + + let v = ( j / divisions ) * ( Math.PI * 2 ); + + let x = Math.sin( v ) * r; + let z = Math.cos( v ) * r; + + vertices.push( x, 0, z ); + colors.push( color.r, color.g, color.b ); + + // second vertex + + v = ( ( j + 1 ) / divisions ) * ( Math.PI * 2 ); + + x = Math.sin( v ) * r; + z = Math.cos( v ) * r; + + vertices.push( x, 0, z ); + colors.push( color.r, color.g, color.b ); + + } + + } + + const geometry = new BufferGeometry(); + geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + geometry.setAttribute( 'color', new Float32BufferAttribute( colors, 3 ) ); + + const material = new LineBasicMaterial( { vertexColors: true, toneMapped: false } ); + + super( geometry, material ); + + this.type = 'PolarGridHelper'; + + } + + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose() { + + this.geometry.dispose(); + this.material.dispose(); + + } + +} + +const _v1 = /*@__PURE__*/ new Vector3(); +const _v2 = /*@__PURE__*/ new Vector3(); +const _v3 = /*@__PURE__*/ new Vector3(); + +/** + * Helper object to assist with visualizing a {@link DirectionalLight}'s + * effect on the scene. This consists of plane and a line representing the + * light's position and direction. + * + * ```js + * const light = new THREE.DirectionalLight( 0xFFFFFF ); + * scene.add( light ); + * + * const helper = new THREE.DirectionalLightHelper( light, 5 ); + * scene.add( helper ); + * ``` + * + * @augments Object3D + */ +class DirectionalLightHelper extends Object3D { + + /** + * Constructs a new directional light helper. + * + * @param {DirectionalLight} light - The light to be visualized. + * @param {number} [size=1] - The dimensions of the plane. + * @param {number|Color|string} [color] - The helper's color. If not set, the helper will take + * the color of the light. + */ + constructor( light, size, color ) { + + super(); + + /** + * The light being visualized. + * + * @type {DirectionalLight} + */ + this.light = light; + + this.matrix = light.matrixWorld; + this.matrixAutoUpdate = false; + + /** + * The color parameter passed in the constructor. + * If not set, the helper will take the color of the light. + * + * @type {number|Color|string} + */ + this.color = color; + + this.type = 'DirectionalLightHelper'; + + if ( size === undefined ) size = 1; + + let geometry = new BufferGeometry(); + geometry.setAttribute( 'position', new Float32BufferAttribute( [ + - size, size, 0, + size, size, 0, + size, - size, 0, + - size, - size, 0, + - size, size, 0 + ], 3 ) ); + + const material = new LineBasicMaterial( { fog: false, toneMapped: false } ); + + /** + * Contains the line showing the location of the directional light. + * + * @type {Line} + */ + this.lightPlane = new Line( geometry, material ); + this.add( this.lightPlane ); + + geometry = new BufferGeometry(); + geometry.setAttribute( 'position', new Float32BufferAttribute( [ 0, 0, 0, 0, 0, 1 ], 3 ) ); + + /** + * Represents the target line of the directional light. + * + * @type {Line} + */ + this.targetLine = new Line( geometry, material ); + this.add( this.targetLine ); + + this.update(); + + } + + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose() { + + this.lightPlane.geometry.dispose(); + this.lightPlane.material.dispose(); + this.targetLine.geometry.dispose(); + this.targetLine.material.dispose(); + + } + + /** + * Updates the helper to match the position and direction of the + * light being visualized. + */ + update() { + + this.light.updateWorldMatrix( true, false ); + this.light.target.updateWorldMatrix( true, false ); + + _v1.setFromMatrixPosition( this.light.matrixWorld ); + _v2.setFromMatrixPosition( this.light.target.matrixWorld ); + _v3.subVectors( _v2, _v1 ); + + this.lightPlane.lookAt( _v2 ); + + if ( this.color !== undefined ) { + + this.lightPlane.material.color.set( this.color ); + this.targetLine.material.color.set( this.color ); + + } else { + + this.lightPlane.material.color.copy( this.light.color ); + this.targetLine.material.color.copy( this.light.color ); + + } + + this.targetLine.lookAt( _v2 ); + this.targetLine.scale.z = _v3.length(); + + } + +} + +const _vector = /*@__PURE__*/ new Vector3(); +const _camera = /*@__PURE__*/ new Camera(); + +/** + * This helps with visualizing what a camera contains in its frustum. It + * visualizes the frustum of a camera using a line segments. + * + * Based on frustum visualization in [lightgl.js shadowmap example]{@link https://github.com/evanw/lightgl.js/blob/master/tests/shadowmap.html}. + * + * `CameraHelper` must be a child of the scene. + * + * ```js + * const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); + * const helper = new THREE.CameraHelper( camera ); + * scene.add( helper ); + * ``` + * + * @augments LineSegments + */ +class CameraHelper extends LineSegments { + + /** + * Constructs a new arrow helper. + * + * @param {Camera} camera - The camera to visualize. + */ + constructor( camera ) { + + const geometry = new BufferGeometry(); + const material = new LineBasicMaterial( { color: 0xffffff, vertexColors: true, toneMapped: false } ); + + const vertices = []; + const colors = []; + + const pointMap = {}; + + // near + + addLine( 'n1', 'n2' ); + addLine( 'n2', 'n4' ); + addLine( 'n4', 'n3' ); + addLine( 'n3', 'n1' ); + + // far + + addLine( 'f1', 'f2' ); + addLine( 'f2', 'f4' ); + addLine( 'f4', 'f3' ); + addLine( 'f3', 'f1' ); + + // sides + + addLine( 'n1', 'f1' ); + addLine( 'n2', 'f2' ); + addLine( 'n3', 'f3' ); + addLine( 'n4', 'f4' ); + + // cone + + addLine( 'p', 'n1' ); + addLine( 'p', 'n2' ); + addLine( 'p', 'n3' ); + addLine( 'p', 'n4' ); + + // up + + addLine( 'u1', 'u2' ); + addLine( 'u2', 'u3' ); + addLine( 'u3', 'u1' ); + + // target + + addLine( 'c', 't' ); + addLine( 'p', 'c' ); + + // cross + + addLine( 'cn1', 'cn2' ); + addLine( 'cn3', 'cn4' ); + + addLine( 'cf1', 'cf2' ); + addLine( 'cf3', 'cf4' ); + + function addLine( a, b ) { + + addPoint( a ); + addPoint( b ); + + } + + function addPoint( id ) { + + vertices.push( 0, 0, 0 ); + colors.push( 0, 0, 0 ); + + if ( pointMap[ id ] === undefined ) { + + pointMap[ id ] = []; + + } + + pointMap[ id ].push( ( vertices.length / 3 ) - 1 ); + + } + + geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + geometry.setAttribute( 'color', new Float32BufferAttribute( colors, 3 ) ); + + super( geometry, material ); + + this.type = 'CameraHelper'; + + /** + * The camera being visualized. + * + * @type {Camera} + */ + this.camera = camera; + if ( this.camera.updateProjectionMatrix ) this.camera.updateProjectionMatrix(); + + this.matrix = camera.matrixWorld; + this.matrixAutoUpdate = false; + + /** + * This contains the points used to visualize the camera. + * + * @type {Object>} + */ + this.pointMap = pointMap; + + this.update(); + + // colors + + const colorFrustum = new Color( 0xffaa00 ); + const colorCone = new Color( 0xff0000 ); + const colorUp = new Color( 0x00aaff ); + const colorTarget = new Color( 0xffffff ); + const colorCross = new Color( 0x333333 ); + + this.setColors( colorFrustum, colorCone, colorUp, colorTarget, colorCross ); + + } + + /** + * Defines the colors of the helper. + * + * @param {Color} frustum - The frustum line color. + * @param {Color} cone - The cone line color. + * @param {Color} up - The up line color. + * @param {Color} target - The target line color. + * @param {Color} cross - The cross line color. + */ + setColors( frustum, cone, up, target, cross ) { + + const geometry = this.geometry; + + const colorAttribute = geometry.getAttribute( 'color' ); + + // near + + colorAttribute.setXYZ( 0, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 1, frustum.r, frustum.g, frustum.b ); // n1, n2 + colorAttribute.setXYZ( 2, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 3, frustum.r, frustum.g, frustum.b ); // n2, n4 + colorAttribute.setXYZ( 4, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 5, frustum.r, frustum.g, frustum.b ); // n4, n3 + colorAttribute.setXYZ( 6, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 7, frustum.r, frustum.g, frustum.b ); // n3, n1 + + // far + + colorAttribute.setXYZ( 8, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 9, frustum.r, frustum.g, frustum.b ); // f1, f2 + colorAttribute.setXYZ( 10, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 11, frustum.r, frustum.g, frustum.b ); // f2, f4 + colorAttribute.setXYZ( 12, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 13, frustum.r, frustum.g, frustum.b ); // f4, f3 + colorAttribute.setXYZ( 14, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 15, frustum.r, frustum.g, frustum.b ); // f3, f1 + + // sides + + colorAttribute.setXYZ( 16, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 17, frustum.r, frustum.g, frustum.b ); // n1, f1 + colorAttribute.setXYZ( 18, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 19, frustum.r, frustum.g, frustum.b ); // n2, f2 + colorAttribute.setXYZ( 20, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 21, frustum.r, frustum.g, frustum.b ); // n3, f3 + colorAttribute.setXYZ( 22, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 23, frustum.r, frustum.g, frustum.b ); // n4, f4 + + // cone + + colorAttribute.setXYZ( 24, cone.r, cone.g, cone.b ); colorAttribute.setXYZ( 25, cone.r, cone.g, cone.b ); // p, n1 + colorAttribute.setXYZ( 26, cone.r, cone.g, cone.b ); colorAttribute.setXYZ( 27, cone.r, cone.g, cone.b ); // p, n2 + colorAttribute.setXYZ( 28, cone.r, cone.g, cone.b ); colorAttribute.setXYZ( 29, cone.r, cone.g, cone.b ); // p, n3 + colorAttribute.setXYZ( 30, cone.r, cone.g, cone.b ); colorAttribute.setXYZ( 31, cone.r, cone.g, cone.b ); // p, n4 + + // up + + colorAttribute.setXYZ( 32, up.r, up.g, up.b ); colorAttribute.setXYZ( 33, up.r, up.g, up.b ); // u1, u2 + colorAttribute.setXYZ( 34, up.r, up.g, up.b ); colorAttribute.setXYZ( 35, up.r, up.g, up.b ); // u2, u3 + colorAttribute.setXYZ( 36, up.r, up.g, up.b ); colorAttribute.setXYZ( 37, up.r, up.g, up.b ); // u3, u1 + + // target + + colorAttribute.setXYZ( 38, target.r, target.g, target.b ); colorAttribute.setXYZ( 39, target.r, target.g, target.b ); // c, t + colorAttribute.setXYZ( 40, cross.r, cross.g, cross.b ); colorAttribute.setXYZ( 41, cross.r, cross.g, cross.b ); // p, c + + // cross + + colorAttribute.setXYZ( 42, cross.r, cross.g, cross.b ); colorAttribute.setXYZ( 43, cross.r, cross.g, cross.b ); // cn1, cn2 + colorAttribute.setXYZ( 44, cross.r, cross.g, cross.b ); colorAttribute.setXYZ( 45, cross.r, cross.g, cross.b ); // cn3, cn4 + + colorAttribute.setXYZ( 46, cross.r, cross.g, cross.b ); colorAttribute.setXYZ( 47, cross.r, cross.g, cross.b ); // cf1, cf2 + colorAttribute.setXYZ( 48, cross.r, cross.g, cross.b ); colorAttribute.setXYZ( 49, cross.r, cross.g, cross.b ); // cf3, cf4 + + colorAttribute.needsUpdate = true; + + } + + /** + * Updates the helper based on the projection matrix of the camera. + */ + update() { + + const geometry = this.geometry; + const pointMap = this.pointMap; + + const w = 1, h = 1; + + // we need just camera projection matrix inverse + // world matrix must be identity + + _camera.projectionMatrixInverse.copy( this.camera.projectionMatrixInverse ); + + // Adjust z values based on coordinate system + const nearZ = this.camera.coordinateSystem === WebGLCoordinateSystem ? -1 : 0; + + // center / target + setPoint( 'c', pointMap, geometry, _camera, 0, 0, nearZ ); + setPoint( 't', pointMap, geometry, _camera, 0, 0, 1 ); + + // near + + setPoint( 'n1', pointMap, geometry, _camera, - w, - h, nearZ ); + setPoint( 'n2', pointMap, geometry, _camera, w, - h, nearZ ); + setPoint( 'n3', pointMap, geometry, _camera, - w, h, nearZ ); + setPoint( 'n4', pointMap, geometry, _camera, w, h, nearZ ); + + // far + + setPoint( 'f1', pointMap, geometry, _camera, - w, - h, 1 ); + setPoint( 'f2', pointMap, geometry, _camera, w, - h, 1 ); + setPoint( 'f3', pointMap, geometry, _camera, - w, h, 1 ); + setPoint( 'f4', pointMap, geometry, _camera, w, h, 1 ); + + // up + + setPoint( 'u1', pointMap, geometry, _camera, w * 0.7, h * 1.1, nearZ ); + setPoint( 'u2', pointMap, geometry, _camera, - w * 0.7, h * 1.1, nearZ ); + setPoint( 'u3', pointMap, geometry, _camera, 0, h * 2, nearZ ); + + // cross + + setPoint( 'cf1', pointMap, geometry, _camera, - w, 0, 1 ); + setPoint( 'cf2', pointMap, geometry, _camera, w, 0, 1 ); + setPoint( 'cf3', pointMap, geometry, _camera, 0, - h, 1 ); + setPoint( 'cf4', pointMap, geometry, _camera, 0, h, 1 ); + + setPoint( 'cn1', pointMap, geometry, _camera, - w, 0, nearZ ); + setPoint( 'cn2', pointMap, geometry, _camera, w, 0, nearZ ); + setPoint( 'cn3', pointMap, geometry, _camera, 0, - h, nearZ ); + setPoint( 'cn4', pointMap, geometry, _camera, 0, h, nearZ ); + + geometry.getAttribute( 'position' ).needsUpdate = true; + + } + + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose() { + + this.geometry.dispose(); + this.material.dispose(); + + } + +} + + +function setPoint( point, pointMap, geometry, camera, x, y, z ) { + + _vector.set( x, y, z ).unproject( camera ); + + const points = pointMap[ point ]; + + if ( points !== undefined ) { + + const position = geometry.getAttribute( 'position' ); + + for ( let i = 0, l = points.length; i < l; i ++ ) { + + position.setXYZ( points[ i ], _vector.x, _vector.y, _vector.z ); + + } + + } + +} + +const _box = /*@__PURE__*/ new Box3(); + +/** + * Helper object to graphically show the world-axis-aligned bounding box + * around an object. The actual bounding box is handled with {@link Box3}, + * this is just a visual helper for debugging. It can be automatically + * resized with {@link BoxHelper#update} when the object it's created from + * is transformed. Note that the object must have a geometry for this to work, + * so it won't work with sprites. + * + * ```js + * const sphere = new THREE.SphereGeometry(); + * const object = new THREE.Mesh( sphere, new THREE.MeshBasicMaterial( 0xff0000 ) ); + * const box = new THREE.BoxHelper( object, 0xffff00 ); + * scene.add( box ); + * ``` + * + * @augments LineSegments + */ +class BoxHelper extends LineSegments { + + /** + * Constructs a new box helper. + * + * @param {Object3D} [object] - The 3D object to show the world-axis-aligned bounding box. + * @param {number|Color|string} [color=0xffff00] - The box's color. + */ + constructor( object, color = 0xffff00 ) { + + const indices = new Uint16Array( [ 0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7 ] ); + const positions = new Float32Array( 8 * 3 ); + + const geometry = new BufferGeometry(); + geometry.setIndex( new BufferAttribute( indices, 1 ) ); + geometry.setAttribute( 'position', new BufferAttribute( positions, 3 ) ); + + super( geometry, new LineBasicMaterial( { color: color, toneMapped: false } ) ); + + /** + * The 3D object being visualized. + * + * @type {Object3D} + */ + this.object = object; + this.type = 'BoxHelper'; + + this.matrixAutoUpdate = false; + + this.update(); + + } + + /** + * Updates the helper's geometry to match the dimensions of the object, + * including any children. + */ + update() { + + if ( this.object !== undefined ) { + + _box.setFromObject( this.object ); + + } + + if ( _box.isEmpty() ) return; + + const min = _box.min; + const max = _box.max; + + /* + 5____4 + 1/___0/| + | 6__|_7 + 2/___3/ + + 0: max.x, max.y, max.z + 1: min.x, max.y, max.z + 2: min.x, min.y, max.z + 3: max.x, min.y, max.z + 4: max.x, max.y, min.z + 5: min.x, max.y, min.z + 6: min.x, min.y, min.z + 7: max.x, min.y, min.z + */ + + const position = this.geometry.attributes.position; + const array = position.array; + + array[ 0 ] = max.x; array[ 1 ] = max.y; array[ 2 ] = max.z; + array[ 3 ] = min.x; array[ 4 ] = max.y; array[ 5 ] = max.z; + array[ 6 ] = min.x; array[ 7 ] = min.y; array[ 8 ] = max.z; + array[ 9 ] = max.x; array[ 10 ] = min.y; array[ 11 ] = max.z; + array[ 12 ] = max.x; array[ 13 ] = max.y; array[ 14 ] = min.z; + array[ 15 ] = min.x; array[ 16 ] = max.y; array[ 17 ] = min.z; + array[ 18 ] = min.x; array[ 19 ] = min.y; array[ 20 ] = min.z; + array[ 21 ] = max.x; array[ 22 ] = min.y; array[ 23 ] = min.z; + + position.needsUpdate = true; + + this.geometry.computeBoundingSphere(); + + } + + /** + * Updates the wireframe box for the passed object. + * + * @param {Object3D} object - The 3D object to create the helper for. + * @return {BoxHelper} A reference to this instance. + */ + setFromObject( object ) { + + this.object = object; + this.update(); + + return this; + + } + + copy( source, recursive ) { + + super.copy( source, recursive ); + + this.object = source.object; + + return this; + + } + + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose() { + + this.geometry.dispose(); + this.material.dispose(); + + } + +} + +/** + * A helper object to visualize an instance of {@link Box3}. + * + * ```js + * const box = new THREE.Box3(); + * box.setFromCenterAndSize( new THREE.Vector3( 1, 1, 1 ), new THREE.Vector3( 2, 1, 3 ) ); + * + * const helper = new THREE.Box3Helper( box, 0xffff00 ); + * scene.add( helper ) + * ``` + * + * @augments LineSegments + */ +class Box3Helper extends LineSegments { + + /** + * Constructs a new box3 helper. + * + * @param {Box3} box - The box to visualize. + * @param {number|Color|string} [color=0xffff00] - The box's color. + */ + constructor( box, color = 0xffff00 ) { + + const indices = new Uint16Array( [ 0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7 ] ); + + const positions = [ 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, 1, 1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1 ]; + + const geometry = new BufferGeometry(); + + geometry.setIndex( new BufferAttribute( indices, 1 ) ); + + geometry.setAttribute( 'position', new Float32BufferAttribute( positions, 3 ) ); + + super( geometry, new LineBasicMaterial( { color: color, toneMapped: false } ) ); + + /** + * The box being visualized. + * + * @type {Box3} + */ + this.box = box; + + this.type = 'Box3Helper'; + + this.geometry.computeBoundingSphere(); + + } + + updateMatrixWorld( force ) { + + const box = this.box; + + if ( box.isEmpty() ) return; + + box.getCenter( this.position ); + + box.getSize( this.scale ); + + this.scale.multiplyScalar( 0.5 ); + + super.updateMatrixWorld( force ); + + } + + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose() { + + this.geometry.dispose(); + this.material.dispose(); + + } + +} + +/** + * A helper object to visualize an instance of {@link Plane}. + * + * ```js + * const plane = new THREE.Plane( new THREE.Vector3( 1, 1, 0.2 ), 3 ); + * const helper = new THREE.PlaneHelper( plane, 1, 0xffff00 ); + * scene.add( helper ); + * ``` + * + * @augments Line + */ +class PlaneHelper extends Line { + + /** + * Constructs a new plane helper. + * + * @param {Plane} plane - The plane to be visualized. + * @param {number} [size=1] - The side length of plane helper. + * @param {number|Color|string} [hex=0xffff00] - The helper's color. + */ + constructor( plane, size = 1, hex = 0xffff00 ) { + + const color = hex; + + const positions = [ 1, -1, 0, -1, 1, 0, -1, -1, 0, 1, 1, 0, -1, 1, 0, -1, -1, 0, 1, -1, 0, 1, 1, 0 ]; + + const geometry = new BufferGeometry(); + geometry.setAttribute( 'position', new Float32BufferAttribute( positions, 3 ) ); + geometry.computeBoundingSphere(); + + super( geometry, new LineBasicMaterial( { color: color, toneMapped: false } ) ); + + this.type = 'PlaneHelper'; + + /** + * The plane being visualized. + * + * @type {Plane} + */ + this.plane = plane; + + /** + * The side length of plane helper. + * + * @type {number} + * @default 1 + */ + this.size = size; + + const positions2 = [ 1, 1, 0, -1, 1, 0, -1, -1, 0, 1, 1, 0, -1, -1, 0, 1, -1, 0 ]; + + const geometry2 = new BufferGeometry(); + geometry2.setAttribute( 'position', new Float32BufferAttribute( positions2, 3 ) ); + geometry2.computeBoundingSphere(); + + this.add( new Mesh( geometry2, new MeshBasicMaterial( { color: color, opacity: 0.2, transparent: true, depthWrite: false, toneMapped: false } ) ) ); + + } + + updateMatrixWorld( force ) { + + this.position.set( 0, 0, 0 ); + + this.scale.set( 0.5 * this.size, 0.5 * this.size, 1 ); + + this.lookAt( this.plane.normal ); + + this.translateZ( - this.plane.constant ); + + super.updateMatrixWorld( force ); + + } + + /** + * Updates the helper to match the position and direction of the + * light being visualized. + */ + dispose() { + + this.geometry.dispose(); + this.material.dispose(); + this.children[ 0 ].geometry.dispose(); + this.children[ 0 ].material.dispose(); + + } + +} + +const _axis = /*@__PURE__*/ new Vector3(); +let _lineGeometry, _coneGeometry; + +/** + * An 3D arrow object for visualizing directions. + * + * ```js + * const dir = new THREE.Vector3( 1, 2, 0 ); + * + * //normalize the direction vector (convert to vector of length 1) + * dir.normalize(); + * + * const origin = new THREE.Vector3( 0, 0, 0 ); + * const length = 1; + * const hex = 0xffff00; + * + * const arrowHelper = new THREE.ArrowHelper( dir, origin, length, hex ); + * scene.add( arrowHelper ); + * ``` + * + * @augments Object3D + */ +class ArrowHelper extends Object3D { + + /** + * Constructs a new arrow helper. + * + * @param {Vector3} [dir=(0, 0, 1)] - The (normalized) direction vector. + * @param {Vector3} [origin=(0, 0, 0)] - Point at which the arrow starts. + * @param {number} [length=1] - Length of the arrow in world units. + * @param {(number|Color|string)} [color=0xffff00] - Color of the arrow. + * @param {number} [headLength=length*0.2] - The length of the head of the arrow. + * @param {number} [headWidth=headLength*0.2] - The width of the head of the arrow. + */ + constructor( dir = new Vector3( 0, 0, 1 ), origin = new Vector3( 0, 0, 0 ), length = 1, color = 0xffff00, headLength = length * 0.2, headWidth = headLength * 0.2 ) { + + super(); + + this.type = 'ArrowHelper'; + + if ( _lineGeometry === undefined ) { + + _lineGeometry = new BufferGeometry(); + _lineGeometry.setAttribute( 'position', new Float32BufferAttribute( [ 0, 0, 0, 0, 1, 0 ], 3 ) ); + + _coneGeometry = new ConeGeometry( 0.5, 1, 5, 1 ); + _coneGeometry.translate( 0, -0.5, 0 ); + + } + + this.position.copy( origin ); + + /** + * The line part of the arrow helper. + * + * @type {Line} + */ + this.line = new Line( _lineGeometry, new LineBasicMaterial( { color: color, toneMapped: false } ) ); + this.line.matrixAutoUpdate = false; + this.add( this.line ); + + /** + * The cone part of the arrow helper. + * + * @type {Mesh} + */ + this.cone = new Mesh( _coneGeometry, new MeshBasicMaterial( { color: color, toneMapped: false } ) ); + this.cone.matrixAutoUpdate = false; + this.add( this.cone ); + + this.setDirection( dir ); + this.setLength( length, headLength, headWidth ); + + } + + /** + * Sets the direction of the helper. + * + * @param {Vector3} dir - The normalized direction vector. + */ + setDirection( dir ) { + + // dir is assumed to be normalized + + if ( dir.y > 0.99999 ) { + + this.quaternion.set( 0, 0, 0, 1 ); + + } else if ( dir.y < -0.99999 ) { + + this.quaternion.set( 1, 0, 0, 0 ); + + } else { + + _axis.set( dir.z, 0, - dir.x ).normalize(); + + const radians = Math.acos( dir.y ); + + this.quaternion.setFromAxisAngle( _axis, radians ); + + } + + } + + /** + * Sets the length of the helper. + * + * @param {number} length - Length of the arrow in world units. + * @param {number} [headLength=length*0.2] - The length of the head of the arrow. + * @param {number} [headWidth=headLength*0.2] - The width of the head of the arrow. + */ + setLength( length, headLength = length * 0.2, headWidth = headLength * 0.2 ) { + + this.line.scale.set( 1, Math.max( 0.0001, length - headLength ), 1 ); // see #17458 + this.line.updateMatrix(); + + this.cone.scale.set( headWidth, headLength, headWidth ); + this.cone.position.y = length; + this.cone.updateMatrix(); + + } + + /** + * Sets the color of the helper. + * + * @param {number|Color|string} color - The color to set. + */ + setColor( color ) { + + this.line.material.color.set( color ); + this.cone.material.color.set( color ); + + } + + copy( source ) { + + super.copy( source, false ); + + this.line.copy( source.line ); + this.cone.copy( source.cone ); + + return this; + + } + + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose() { + + this.line.geometry.dispose(); + this.line.material.dispose(); + this.cone.geometry.dispose(); + this.cone.material.dispose(); + + } + +} + +/** + * An axis object to visualize the 3 axes in a simple way. + * The X axis is red. The Y axis is green. The Z axis is blue. + * + * ```js + * const axesHelper = new THREE.AxesHelper( 5 ); + * scene.add( axesHelper ); + * ``` + * + * @augments LineSegments + */ +class AxesHelper extends LineSegments { + + /** + * Constructs a new axes helper. + * + * @param {number} [size=1] - Size of the lines representing the axes. + */ + constructor( size = 1 ) { + + const vertices = [ + 0, 0, 0, size, 0, 0, + 0, 0, 0, 0, size, 0, + 0, 0, 0, 0, 0, size + ]; + + const colors = [ + 1, 0, 0, 1, 0.6, 0, + 0, 1, 0, 0.6, 1, 0, + 0, 0, 1, 0, 0.6, 1 + ]; + + const geometry = new BufferGeometry(); + geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + geometry.setAttribute( 'color', new Float32BufferAttribute( colors, 3 ) ); + + const material = new LineBasicMaterial( { vertexColors: true, toneMapped: false } ); + + super( geometry, material ); + + this.type = 'AxesHelper'; + + } + + /** + * Defines the colors of the axes helper. + * + * @param {number|Color|string} xAxisColor - The color for the x axis. + * @param {number|Color|string} yAxisColor - The color for the y axis. + * @param {number|Color|string} zAxisColor - The color for the z axis. + * @return {AxesHelper} A reference to this axes helper. + */ + setColors( xAxisColor, yAxisColor, zAxisColor ) { + + const color = new Color(); + const array = this.geometry.attributes.color.array; + + color.set( xAxisColor ); + color.toArray( array, 0 ); + color.toArray( array, 3 ); + + color.set( yAxisColor ); + color.toArray( array, 6 ); + color.toArray( array, 9 ); + + color.set( zAxisColor ); + color.toArray( array, 12 ); + color.toArray( array, 15 ); + + this.geometry.attributes.color.needsUpdate = true; + + return this; + + } + + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose() { + + this.geometry.dispose(); + this.material.dispose(); + + } + +} + +/** + * This class is used to convert a series of paths to an array of + * shapes. It is specifically used in context of fonts and SVG. + */ +class ShapePath { + + /** + * Constructs a new shape path. + */ + constructor() { + + this.type = 'ShapePath'; + + /** + * The color of the shape. + * + * @type {Color} + */ + this.color = new Color(); + + /** + * The paths that have been generated for this shape. + * + * @type {Array} + * @default null + */ + this.subPaths = []; + + /** + * The current path that is being generated. + * + * @type {?Path} + * @default null + */ + this.currentPath = null; + + } + + /** + * Creates a new path and moves it current point to the given one. + * + * @param {number} x - The x coordinate. + * @param {number} y - The y coordinate. + * @return {ShapePath} A reference to this shape path. + */ + moveTo( x, y ) { + + this.currentPath = new Path(); + this.subPaths.push( this.currentPath ); + this.currentPath.moveTo( x, y ); + + return this; + + } + + /** + * Adds an instance of {@link LineCurve} to the path by connecting + * the current point with the given one. + * + * @param {number} x - The x coordinate of the end point. + * @param {number} y - The y coordinate of the end point. + * @return {ShapePath} A reference to this shape path. + */ + lineTo( x, y ) { + + this.currentPath.lineTo( x, y ); + + return this; + + } + + /** + * Adds an instance of {@link QuadraticBezierCurve} to the path by connecting + * the current point with the given one. + * + * @param {number} aCPx - The x coordinate of the control point. + * @param {number} aCPy - The y coordinate of the control point. + * @param {number} aX - The x coordinate of the end point. + * @param {number} aY - The y coordinate of the end point. + * @return {ShapePath} A reference to this shape path. + */ + quadraticCurveTo( aCPx, aCPy, aX, aY ) { + + this.currentPath.quadraticCurveTo( aCPx, aCPy, aX, aY ); + + return this; + + } + + /** + * Adds an instance of {@link CubicBezierCurve} to the path by connecting + * the current point with the given one. + * + * @param {number} aCP1x - The x coordinate of the first control point. + * @param {number} aCP1y - The y coordinate of the first control point. + * @param {number} aCP2x - The x coordinate of the second control point. + * @param {number} aCP2y - The y coordinate of the second control point. + * @param {number} aX - The x coordinate of the end point. + * @param {number} aY - The y coordinate of the end point. + * @return {ShapePath} A reference to this shape path. + */ + bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) { + + this.currentPath.bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ); + + return this; + + } + + /** + * Adds an instance of {@link SplineCurve} to the path by connecting + * the current point with the given list of points. + * + * @param {Array} pts - An array of points in 2D space. + * @return {ShapePath} A reference to this shape path. + */ + splineThru( pts ) { + + this.currentPath.splineThru( pts ); + + return this; + + } + + /** + * Converts the paths into an array of shapes. + * + * @param {boolean} isCCW - By default solid shapes are defined clockwise (CW) and holes are defined counterclockwise (CCW). + * If this flag is set to `true`, then those are flipped. + * @return {Array} An array of shapes. + */ + toShapes( isCCW ) { + + function toShapesNoHoles( inSubpaths ) { + + const shapes = []; + + for ( let i = 0, l = inSubpaths.length; i < l; i ++ ) { + + const tmpPath = inSubpaths[ i ]; + + const tmpShape = new Shape(); + tmpShape.curves = tmpPath.curves; + + shapes.push( tmpShape ); + + } + + return shapes; + + } + + function isPointInsidePolygon( inPt, inPolygon ) { + + const polyLen = inPolygon.length; + + // inPt on polygon contour => immediate success or + // toggling of inside/outside at every single! intersection point of an edge + // with the horizontal line through inPt, left of inPt + // not counting lowerY endpoints of edges and whole edges on that line + let inside = false; + for ( let p = polyLen - 1, q = 0; q < polyLen; p = q ++ ) { + + let edgeLowPt = inPolygon[ p ]; + let edgeHighPt = inPolygon[ q ]; + + let edgeDx = edgeHighPt.x - edgeLowPt.x; + let edgeDy = edgeHighPt.y - edgeLowPt.y; + + if ( Math.abs( edgeDy ) > Number.EPSILON ) { + + // not parallel + if ( edgeDy < 0 ) { + + edgeLowPt = inPolygon[ q ]; edgeDx = - edgeDx; + edgeHighPt = inPolygon[ p ]; edgeDy = - edgeDy; + + } + + if ( ( inPt.y < edgeLowPt.y ) || ( inPt.y > edgeHighPt.y ) ) continue; + + if ( inPt.y === edgeLowPt.y ) { + + if ( inPt.x === edgeLowPt.x ) return true; // inPt is on contour ? + // continue; // no intersection or edgeLowPt => doesn't count !!! + + } else { + + const perpEdge = edgeDy * ( inPt.x - edgeLowPt.x ) - edgeDx * ( inPt.y - edgeLowPt.y ); + if ( perpEdge === 0 ) return true; // inPt is on contour ? + if ( perpEdge < 0 ) continue; + inside = ! inside; // true intersection left of inPt + + } + + } else { + + // parallel or collinear + if ( inPt.y !== edgeLowPt.y ) continue; // parallel + // edge lies on the same horizontal line as inPt + if ( ( ( edgeHighPt.x <= inPt.x ) && ( inPt.x <= edgeLowPt.x ) ) || + ( ( edgeLowPt.x <= inPt.x ) && ( inPt.x <= edgeHighPt.x ) ) ) return true; // inPt: Point on contour ! + // continue; + + } + + } + + return inside; + + } + + const isClockWise = ShapeUtils.isClockWise; + + const subPaths = this.subPaths; + if ( subPaths.length === 0 ) return []; + + let solid, tmpPath, tmpShape; + const shapes = []; + + if ( subPaths.length === 1 ) { + + tmpPath = subPaths[ 0 ]; + tmpShape = new Shape(); + tmpShape.curves = tmpPath.curves; + shapes.push( tmpShape ); + return shapes; + + } + + let holesFirst = ! isClockWise( subPaths[ 0 ].getPoints() ); + holesFirst = isCCW ? ! holesFirst : holesFirst; + + // console.log("Holes first", holesFirst); + + const betterShapeHoles = []; + const newShapes = []; + let newShapeHoles = []; + let mainIdx = 0; + let tmpPoints; + + newShapes[ mainIdx ] = undefined; + newShapeHoles[ mainIdx ] = []; + + for ( let i = 0, l = subPaths.length; i < l; i ++ ) { + + tmpPath = subPaths[ i ]; + tmpPoints = tmpPath.getPoints(); + solid = isClockWise( tmpPoints ); + solid = isCCW ? ! solid : solid; + + if ( solid ) { + + if ( ( ! holesFirst ) && ( newShapes[ mainIdx ] ) ) mainIdx ++; + + newShapes[ mainIdx ] = { s: new Shape(), p: tmpPoints }; + newShapes[ mainIdx ].s.curves = tmpPath.curves; + + if ( holesFirst ) mainIdx ++; + newShapeHoles[ mainIdx ] = []; + + //console.log('cw', i); + + } else { + + newShapeHoles[ mainIdx ].push( { h: tmpPath, p: tmpPoints[ 0 ] } ); + + //console.log('ccw', i); + + } + + } + + // only Holes? -> probably all Shapes with wrong orientation + if ( ! newShapes[ 0 ] ) return toShapesNoHoles( subPaths ); + + + if ( newShapes.length > 1 ) { + + let ambiguous = false; + let toChange = 0; + + for ( let sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) { + + betterShapeHoles[ sIdx ] = []; + + } + + for ( let sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) { + + const sho = newShapeHoles[ sIdx ]; + + for ( let hIdx = 0; hIdx < sho.length; hIdx ++ ) { + + const ho = sho[ hIdx ]; + let hole_unassigned = true; + + for ( let s2Idx = 0; s2Idx < newShapes.length; s2Idx ++ ) { + + if ( isPointInsidePolygon( ho.p, newShapes[ s2Idx ].p ) ) { + + if ( sIdx !== s2Idx ) toChange ++; + + if ( hole_unassigned ) { + + hole_unassigned = false; + betterShapeHoles[ s2Idx ].push( ho ); + + } else { + + ambiguous = true; + + } + + } + + } + + if ( hole_unassigned ) { + + betterShapeHoles[ sIdx ].push( ho ); + + } + + } + + } + + if ( toChange > 0 && ambiguous === false ) { + + newShapeHoles = betterShapeHoles; + + } + + } + + let tmpHoles; + + for ( let i = 0, il = newShapes.length; i < il; i ++ ) { + + tmpShape = newShapes[ i ].s; + shapes.push( tmpShape ); + tmpHoles = newShapeHoles[ i ]; + + for ( let j = 0, jl = tmpHoles.length; j < jl; j ++ ) { + + tmpShape.holes.push( tmpHoles[ j ].h ); + + } + + } + + //console.log("shape", shapes); + + return shapes; + + } + +} + +/** + * Abstract base class for controls. + * + * @abstract + * @augments EventDispatcher + */ +class Controls extends EventDispatcher { + + /** + * Constructs a new controls instance. + * + * @param {Object3D} object - The object that is managed by the controls. + * @param {?HTMLDOMElement} domElement - The HTML element used for event listeners. + */ + constructor( object, domElement = null ) { + + super(); + + /** + * The object that is managed by the controls. + * + * @type {Object3D} + */ + this.object = object; + + /** + * The HTML element used for event listeners. + * + * @type {?HTMLDOMElement} + * @default null + */ + this.domElement = domElement; + + /** + * Whether the controls responds to user input or not. + * + * @type {boolean} + * @default true + */ + this.enabled = true; + + /** + * The internal state of the controls. + * + * @type {number} + * @default -1 + */ + this.state = -1; + + /** + * This object defines the keyboard input of the controls. + * + * @type {Object} + */ + this.keys = {}; + + /** + * This object defines what type of actions are assigned to the available mouse buttons. + * It depends on the control implementation what kind of mouse buttons and actions are supported. + * + * @type {{LEFT: ?number, MIDDLE: ?number, RIGHT: ?number}} + */ + this.mouseButtons = { LEFT: null, MIDDLE: null, RIGHT: null }; + + /** + * This object defines what type of actions are assigned to what kind of touch interaction. + * It depends on the control implementation what kind of touch interaction and actions are supported. + * + * @type {{ONE: ?number, TWO: ?number}} + */ + this.touches = { ONE: null, TWO: null }; + + } + + /** + * Connects the controls to the DOM. This method has so called "side effects" since + * it adds the module's event listeners to the DOM. + * + * @param {HTMLDOMElement} element - The DOM element to connect to. + */ + connect( element ) { + + if ( element === undefined ) { + + console.warn( 'THREE.Controls: connect() now requires an element.' ); // @deprecated, the warning can be removed with r185 + return; + + } + + if ( this.domElement !== null ) this.disconnect(); + + this.domElement = element; + + } + + /** + * Disconnects the controls from the DOM. + */ + disconnect() {} + + /** + * Call this method if you no longer want use to the controls. It frees all internal + * resources and removes all event listeners. + */ + dispose() {} + + /** + * Controls should implement this method if they have to update their internal state + * per simulation step. + * + * @param {number} [delta] - The time delta in seconds. + */ + update( /* delta */ ) {} + +} + +/** + * Scales the texture as large as possible within its surface without cropping + * or stretching the texture. The method preserves the original aspect ratio of + * the texture. Akin to CSS `object-fit: contain` + * + * @param {Texture} texture - The texture. + * @param {number} aspect - The texture's aspect ratio. + * @return {Texture} The updated texture. + */ +function contain( texture, aspect ) { + + const imageAspect = ( texture.image && texture.image.width ) ? texture.image.width / texture.image.height : 1; + + if ( imageAspect > aspect ) { + + texture.repeat.x = 1; + texture.repeat.y = imageAspect / aspect; + + texture.offset.x = 0; + texture.offset.y = ( 1 - texture.repeat.y ) / 2; + + } else { + + texture.repeat.x = aspect / imageAspect; + texture.repeat.y = 1; + + texture.offset.x = ( 1 - texture.repeat.x ) / 2; + texture.offset.y = 0; + + } + + return texture; + +} + +/** + * Scales the texture to the smallest possible size to fill the surface, leaving + * no empty space. The method preserves the original aspect ratio of the texture. + * Akin to CSS `object-fit: cover`. + * + * @param {Texture} texture - The texture. + * @param {number} aspect - The texture's aspect ratio. + * @return {Texture} The updated texture. + */ +function cover( texture, aspect ) { + + const imageAspect = ( texture.image && texture.image.width ) ? texture.image.width / texture.image.height : 1; + + if ( imageAspect > aspect ) { + + texture.repeat.x = aspect / imageAspect; + texture.repeat.y = 1; + + texture.offset.x = ( 1 - texture.repeat.x ) / 2; + texture.offset.y = 0; + + } else { + + texture.repeat.x = 1; + texture.repeat.y = imageAspect / aspect; + + texture.offset.x = 0; + texture.offset.y = ( 1 - texture.repeat.y ) / 2; + + } + + return texture; + +} + +/** + * Configures the texture to the default transformation. Akin to CSS `object-fit: fill`. + * + * @param {Texture} texture - The texture. + * @return {Texture} The updated texture. + */ +function fill( texture ) { + + texture.repeat.x = 1; + texture.repeat.y = 1; + + texture.offset.x = 0; + texture.offset.y = 0; + + return texture; + +} + +/** + * Determines how many bytes must be used to represent the texture. + * + * @param {number} width - The width of the texture. + * @param {number} height - The height of the texture. + * @param {number} format - The texture's format. + * @param {number} type - The texture's type. + * @return {number} The byte length. + */ +function getByteLength( width, height, format, type ) { + + const typeByteLength = getTextureTypeByteLength( type ); + + switch ( format ) { + + // https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glTexImage2D.xhtml + case AlphaFormat: + return width * height; + case RedFormat: + return ( ( width * height ) / typeByteLength.components ) * typeByteLength.byteLength; + case RedIntegerFormat: + return ( ( width * height ) / typeByteLength.components ) * typeByteLength.byteLength; + case RGFormat: + return ( ( width * height * 2 ) / typeByteLength.components ) * typeByteLength.byteLength; + case RGIntegerFormat: + return ( ( width * height * 2 ) / typeByteLength.components ) * typeByteLength.byteLength; + case RGBFormat: + return ( ( width * height * 3 ) / typeByteLength.components ) * typeByteLength.byteLength; + case RGBAFormat: + return ( ( width * height * 4 ) / typeByteLength.components ) * typeByteLength.byteLength; + case RGBAIntegerFormat: + return ( ( width * height * 4 ) / typeByteLength.components ) * typeByteLength.byteLength; + + // https://registry.khronos.org/webgl/extensions/WEBGL_compressed_texture_s3tc_srgb/ + case RGB_S3TC_DXT1_Format: + case RGBA_S3TC_DXT1_Format: + return Math.floor( ( width + 3 ) / 4 ) * Math.floor( ( height + 3 ) / 4 ) * 8; + case RGBA_S3TC_DXT3_Format: + case RGBA_S3TC_DXT5_Format: + return Math.floor( ( width + 3 ) / 4 ) * Math.floor( ( height + 3 ) / 4 ) * 16; + + // https://registry.khronos.org/webgl/extensions/WEBGL_compressed_texture_pvrtc/ + case RGB_PVRTC_2BPPV1_Format: + case RGBA_PVRTC_2BPPV1_Format: + return ( Math.max( width, 16 ) * Math.max( height, 8 ) ) / 4; + case RGB_PVRTC_4BPPV1_Format: + case RGBA_PVRTC_4BPPV1_Format: + return ( Math.max( width, 8 ) * Math.max( height, 8 ) ) / 2; + + // https://registry.khronos.org/webgl/extensions/WEBGL_compressed_texture_etc/ + case RGB_ETC1_Format: + case RGB_ETC2_Format: + return Math.floor( ( width + 3 ) / 4 ) * Math.floor( ( height + 3 ) / 4 ) * 8; + case RGBA_ETC2_EAC_Format: + return Math.floor( ( width + 3 ) / 4 ) * Math.floor( ( height + 3 ) / 4 ) * 16; + + // https://registry.khronos.org/webgl/extensions/WEBGL_compressed_texture_astc/ + case RGBA_ASTC_4x4_Format: + return Math.floor( ( width + 3 ) / 4 ) * Math.floor( ( height + 3 ) / 4 ) * 16; + case RGBA_ASTC_5x4_Format: + return Math.floor( ( width + 4 ) / 5 ) * Math.floor( ( height + 3 ) / 4 ) * 16; + case RGBA_ASTC_5x5_Format: + return Math.floor( ( width + 4 ) / 5 ) * Math.floor( ( height + 4 ) / 5 ) * 16; + case RGBA_ASTC_6x5_Format: + return Math.floor( ( width + 5 ) / 6 ) * Math.floor( ( height + 4 ) / 5 ) * 16; + case RGBA_ASTC_6x6_Format: + return Math.floor( ( width + 5 ) / 6 ) * Math.floor( ( height + 5 ) / 6 ) * 16; + case RGBA_ASTC_8x5_Format: + return Math.floor( ( width + 7 ) / 8 ) * Math.floor( ( height + 4 ) / 5 ) * 16; + case RGBA_ASTC_8x6_Format: + return Math.floor( ( width + 7 ) / 8 ) * Math.floor( ( height + 5 ) / 6 ) * 16; + case RGBA_ASTC_8x8_Format: + return Math.floor( ( width + 7 ) / 8 ) * Math.floor( ( height + 7 ) / 8 ) * 16; + case RGBA_ASTC_10x5_Format: + return Math.floor( ( width + 9 ) / 10 ) * Math.floor( ( height + 4 ) / 5 ) * 16; + case RGBA_ASTC_10x6_Format: + return Math.floor( ( width + 9 ) / 10 ) * Math.floor( ( height + 5 ) / 6 ) * 16; + case RGBA_ASTC_10x8_Format: + return Math.floor( ( width + 9 ) / 10 ) * Math.floor( ( height + 7 ) / 8 ) * 16; + case RGBA_ASTC_10x10_Format: + return Math.floor( ( width + 9 ) / 10 ) * Math.floor( ( height + 9 ) / 10 ) * 16; + case RGBA_ASTC_12x10_Format: + return Math.floor( ( width + 11 ) / 12 ) * Math.floor( ( height + 9 ) / 10 ) * 16; + case RGBA_ASTC_12x12_Format: + return Math.floor( ( width + 11 ) / 12 ) * Math.floor( ( height + 11 ) / 12 ) * 16; + + // https://registry.khronos.org/webgl/extensions/EXT_texture_compression_bptc/ + case RGBA_BPTC_Format: + case RGB_BPTC_SIGNED_Format: + case RGB_BPTC_UNSIGNED_Format: + return Math.ceil( width / 4 ) * Math.ceil( height / 4 ) * 16; + + // https://registry.khronos.org/webgl/extensions/EXT_texture_compression_rgtc/ + case RED_RGTC1_Format: + case SIGNED_RED_RGTC1_Format: + return Math.ceil( width / 4 ) * Math.ceil( height / 4 ) * 8; + case RED_GREEN_RGTC2_Format: + case SIGNED_RED_GREEN_RGTC2_Format: + return Math.ceil( width / 4 ) * Math.ceil( height / 4 ) * 16; + + } + + throw new Error( + `Unable to determine texture byte length for ${format} format.`, + ); + +} + +function getTextureTypeByteLength( type ) { + + switch ( type ) { + + case UnsignedByteType: + case ByteType: + return { byteLength: 1, components: 1 }; + case UnsignedShortType: + case ShortType: + case HalfFloatType: + return { byteLength: 2, components: 1 }; + case UnsignedShort4444Type: + case UnsignedShort5551Type: + return { byteLength: 2, components: 4 }; + case UnsignedIntType: + case IntType: + case FloatType: + return { byteLength: 4, components: 1 }; + case UnsignedInt5999Type: + return { byteLength: 4, components: 3 }; + + } + + throw new Error( `Unknown texture type ${type}.` ); + +} + +/** + * A class containing utility functions for textures. + * + * @hideconstructor + */ +class TextureUtils { + + /** + * Scales the texture as large as possible within its surface without cropping + * or stretching the texture. The method preserves the original aspect ratio of + * the texture. Akin to CSS `object-fit: contain` + * + * @param {Texture} texture - The texture. + * @param {number} aspect - The texture's aspect ratio. + * @return {Texture} The updated texture. + */ + static contain( texture, aspect ) { + + return contain( texture, aspect ); + + } + + /** + * Scales the texture to the smallest possible size to fill the surface, leaving + * no empty space. The method preserves the original aspect ratio of the texture. + * Akin to CSS `object-fit: cover`. + * + * @param {Texture} texture - The texture. + * @param {number} aspect - The texture's aspect ratio. + * @return {Texture} The updated texture. + */ + static cover( texture, aspect ) { + + return cover( texture, aspect ); + + } + + /** + * Configures the texture to the default transformation. Akin to CSS `object-fit: fill`. + * + * @param {Texture} texture - The texture. + * @return {Texture} The updated texture. + */ + static fill( texture ) { + + return fill( texture ); + + } + + /** + * Determines how many bytes must be used to represent the texture. + * + * @param {number} width - The width of the texture. + * @param {number} height - The height of the texture. + * @param {number} format - The texture's format. + * @param {number} type - The texture's type. + * @return {number} The byte length. + */ + static getByteLength( width, height, format, type ) { + + return getByteLength( width, height, format, type ); + + } + +} + +if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) { + + __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'register', { detail: { + revision: REVISION, + } } ) ); + +} + +if ( typeof window !== 'undefined' ) { + + if ( window.__THREE__ ) { + + console.warn( 'WARNING: Multiple instances of Three.js being imported.' ); + + } else { + + window.__THREE__ = REVISION; + + } + +} + +export { ACESFilmicToneMapping, AddEquation, AddOperation, AdditiveAnimationBlendMode, AdditiveBlending, AgXToneMapping, AlphaFormat, AlwaysCompare, AlwaysDepth, AlwaysStencilFunc, AmbientLight, AnimationAction, AnimationClip, AnimationLoader, AnimationMixer, AnimationObjectGroup, AnimationUtils, ArcCurve, ArrayCamera, ArrowHelper, AttachedBindMode, Audio, AudioAnalyser, AudioContext, AudioListener, AudioLoader, AxesHelper, BackSide, BasicDepthPacking, BasicShadowMap, BatchedMesh, Bone, BooleanKeyframeTrack, Box2, Box3, Box3Helper, BoxGeometry, BoxHelper, BufferAttribute, BufferGeometry, BufferGeometryLoader, ByteType, Cache, Camera, CameraHelper, CanvasTexture, CapsuleGeometry, CatmullRomCurve3, CineonToneMapping, CircleGeometry, ClampToEdgeWrapping, Clock, Color, ColorKeyframeTrack, ColorManagement, CompressedArrayTexture, CompressedCubeTexture, CompressedTexture, CompressedTextureLoader, ConeGeometry, ConstantAlphaFactor, ConstantColorFactor, Controls, CubeCamera, CubeReflectionMapping, CubeRefractionMapping, CubeTexture, CubeTextureLoader, CubeUVReflectionMapping, CubicBezierCurve, CubicBezierCurve3, CubicInterpolant, CullFaceBack, CullFaceFront, CullFaceFrontBack, CullFaceNone, Curve, CurvePath, CustomBlending, CustomToneMapping, CylinderGeometry, Cylindrical, Data3DTexture, DataArrayTexture, DataTexture, DataTextureLoader, DataUtils, DecrementStencilOp, DecrementWrapStencilOp, DefaultLoadingManager, DepthFormat, DepthStencilFormat, DepthTexture, DetachedBindMode, DirectionalLight, DirectionalLightHelper, DiscreteInterpolant, DodecahedronGeometry, DoubleSide, DstAlphaFactor, DstColorFactor, DynamicCopyUsage, DynamicDrawUsage, DynamicReadUsage, EdgesGeometry, EllipseCurve, EqualCompare, EqualDepth, EqualStencilFunc, EquirectangularReflectionMapping, EquirectangularRefractionMapping, Euler, EventDispatcher, ExtrudeGeometry, FileLoader, Float16BufferAttribute, Float32BufferAttribute, FloatType, Fog, FogExp2, FramebufferTexture, FrontSide, Frustum, FrustumArray, GLBufferAttribute, GLSL1, GLSL3, GreaterCompare, GreaterDepth, GreaterEqualCompare, GreaterEqualDepth, GreaterEqualStencilFunc, GreaterStencilFunc, GridHelper, Group, HalfFloatType, HemisphereLight, HemisphereLightHelper, IcosahedronGeometry, ImageBitmapLoader, ImageLoader, ImageUtils, IncrementStencilOp, IncrementWrapStencilOp, InstancedBufferAttribute, InstancedBufferGeometry, InstancedInterleavedBuffer, InstancedMesh, Int16BufferAttribute, Int32BufferAttribute, Int8BufferAttribute, IntType, InterleavedBuffer, InterleavedBufferAttribute, Interpolant, InterpolateDiscrete, InterpolateLinear, InterpolateSmooth, InterpolationSamplingMode, InterpolationSamplingType, InvertStencilOp, KeepStencilOp, KeyframeTrack, LOD, LatheGeometry, Layers, LessCompare, LessDepth, LessEqualCompare, LessEqualDepth, LessEqualStencilFunc, LessStencilFunc, Light, LightProbe, Line, Line3, LineBasicMaterial, LineCurve, LineCurve3, LineDashedMaterial, LineLoop, LineSegments, LinearFilter, LinearInterpolant, LinearMipMapLinearFilter, LinearMipMapNearestFilter, LinearMipmapLinearFilter, LinearMipmapNearestFilter, LinearSRGBColorSpace, LinearToneMapping, LinearTransfer, Loader, LoaderUtils, LoadingManager, LoopOnce, LoopPingPong, LoopRepeat, MOUSE, Material, MaterialLoader, MathUtils, Matrix2, Matrix3, Matrix4, MaxEquation, Mesh, MeshBasicMaterial, MeshDepthMaterial, MeshDistanceMaterial, MeshLambertMaterial, MeshMatcapMaterial, MeshNormalMaterial, MeshPhongMaterial, MeshPhysicalMaterial, MeshStandardMaterial, MeshToonMaterial, MinEquation, MirroredRepeatWrapping, MixOperation, MultiplyBlending, MultiplyOperation, NearestFilter, NearestMipMapLinearFilter, NearestMipMapNearestFilter, NearestMipmapLinearFilter, NearestMipmapNearestFilter, NeutralToneMapping, NeverCompare, NeverDepth, NeverStencilFunc, NoBlending, NoColorSpace, NoToneMapping, NormalAnimationBlendMode, NormalBlending, NotEqualCompare, NotEqualDepth, NotEqualStencilFunc, NumberKeyframeTrack, Object3D, ObjectLoader, ObjectSpaceNormalMap, OctahedronGeometry, OneFactor, OneMinusConstantAlphaFactor, OneMinusConstantColorFactor, OneMinusDstAlphaFactor, OneMinusDstColorFactor, OneMinusSrcAlphaFactor, OneMinusSrcColorFactor, OrthographicCamera, PCFShadowMap, PCFSoftShadowMap, Path, PerspectiveCamera, Plane, PlaneGeometry, PlaneHelper, PointLight, PointLightHelper, Points, PointsMaterial, PolarGridHelper, PolyhedronGeometry, PositionalAudio, PropertyBinding, PropertyMixer, QuadraticBezierCurve, QuadraticBezierCurve3, Quaternion, QuaternionKeyframeTrack, QuaternionLinearInterpolant, RAD2DEG, RED_GREEN_RGTC2_Format, RED_RGTC1_Format, REVISION, RGBADepthPacking, RGBAFormat, RGBAIntegerFormat, RGBA_ASTC_10x10_Format, RGBA_ASTC_10x5_Format, RGBA_ASTC_10x6_Format, RGBA_ASTC_10x8_Format, RGBA_ASTC_12x10_Format, RGBA_ASTC_12x12_Format, RGBA_ASTC_4x4_Format, RGBA_ASTC_5x4_Format, RGBA_ASTC_5x5_Format, RGBA_ASTC_6x5_Format, RGBA_ASTC_6x6_Format, RGBA_ASTC_8x5_Format, RGBA_ASTC_8x6_Format, RGBA_ASTC_8x8_Format, RGBA_BPTC_Format, RGBA_ETC2_EAC_Format, RGBA_PVRTC_2BPPV1_Format, RGBA_PVRTC_4BPPV1_Format, RGBA_S3TC_DXT1_Format, RGBA_S3TC_DXT3_Format, RGBA_S3TC_DXT5_Format, RGBDepthPacking, RGBFormat, RGBIntegerFormat, RGB_BPTC_SIGNED_Format, RGB_BPTC_UNSIGNED_Format, RGB_ETC1_Format, RGB_ETC2_Format, RGB_PVRTC_2BPPV1_Format, RGB_PVRTC_4BPPV1_Format, RGB_S3TC_DXT1_Format, RGDepthPacking, RGFormat, RGIntegerFormat, RawShaderMaterial, Ray, Raycaster, RectAreaLight, RedFormat, RedIntegerFormat, ReinhardToneMapping, RenderTarget, RenderTarget3D, RepeatWrapping, ReplaceStencilOp, ReverseSubtractEquation, RingGeometry, SIGNED_RED_GREEN_RGTC2_Format, SIGNED_RED_RGTC1_Format, SRGBColorSpace, SRGBTransfer, Scene, ShaderMaterial, ShadowMaterial, Shape, ShapeGeometry, ShapePath, ShapeUtils, ShortType, Skeleton, SkeletonHelper, SkinnedMesh, Source, Sphere, SphereGeometry, Spherical, SphericalHarmonics3, SplineCurve, SpotLight, SpotLightHelper, Sprite, SpriteMaterial, SrcAlphaFactor, SrcAlphaSaturateFactor, SrcColorFactor, StaticCopyUsage, StaticDrawUsage, StaticReadUsage, StereoCamera, StreamCopyUsage, StreamDrawUsage, StreamReadUsage, StringKeyframeTrack, SubtractEquation, SubtractiveBlending, TOUCH, TangentSpaceNormalMap, TetrahedronGeometry, Texture, TextureLoader, TextureUtils, TimestampQuery, TorusGeometry, TorusKnotGeometry, Triangle, TriangleFanDrawMode, TriangleStripDrawMode, TrianglesDrawMode, TubeGeometry, UVMapping, Uint16BufferAttribute, Uint32BufferAttribute, Uint8BufferAttribute, Uint8ClampedBufferAttribute, Uniform, UniformsGroup, UniformsUtils, UnsignedByteType, UnsignedInt248Type, UnsignedInt5999Type, UnsignedIntType, UnsignedShort4444Type, UnsignedShort5551Type, UnsignedShortType, VSMShadowMap, Vector2, Vector3, Vector4, VectorKeyframeTrack, VideoFrameTexture, VideoTexture, WebGL3DRenderTarget, WebGLArrayRenderTarget, WebGLCoordinateSystem, WebGLCubeRenderTarget, WebGLRenderTarget, WebGPUCoordinateSystem, WebXRController, WireframeGeometry, WrapAroundEnding, ZeroCurvatureEnding, ZeroFactor, ZeroSlopeEnding, ZeroStencilOp, arrayNeedsUint32, cloneUniforms, createCanvasElement, createElementNS, getByteLength, getUnlitUniformColorSpace, mergeUniforms, probeAsync, toNormalizedProjectionMatrix, toReversedProjectionMatrix, warnOnce }; diff --git a/build/three.core.min.js b/build/three.core.min.js new file mode 100644 index 00000000000000..bf1d3aa1819372 --- /dev/null +++ b/build/three.core.min.js @@ -0,0 +1,6 @@ +/** + * @license + * Copyright 2010-2025 Three.js Authors + * SPDX-License-Identifier: MIT + */ +const t="178",e={LEFT:0,MIDDLE:1,RIGHT:2,ROTATE:0,DOLLY:1,PAN:2},i={ROTATE:0,PAN:1,DOLLY_PAN:2,DOLLY_ROTATE:3},s=0,r=1,n=2,a=3,o=0,h=1,l=2,c=3,u=0,d=1,p=2,m=0,y=1,g=2,f=3,x=4,b=5,v=100,w=101,M=102,S=103,_=104,A=200,T=201,z=202,I=203,C=204,B=205,k=206,E=207,R=208,P=209,O=210,N=211,F=212,V=213,L=214,j=0,W=1,U=2,D=3,H=4,q=5,J=6,X=7,Y=0,Z=1,G=2,$=0,Q=1,K=2,tt=3,et=4,it=5,st=6,rt=7,nt="attached",at="detached",ot=300,ht=301,lt=302,ct=303,ut=304,dt=306,pt=1e3,mt=1001,yt=1002,gt=1003,ft=1004,xt=1004,bt=1005,vt=1005,wt=1006,Mt=1007,St=1007,_t=1008,At=1008,Tt=1009,zt=1010,It=1011,Ct=1012,Bt=1013,kt=1014,Et=1015,Rt=1016,Pt=1017,Ot=1018,Nt=1020,Ft=35902,Vt=1021,Lt=1022,jt=1023,Wt=1026,Ut=1027,Dt=1028,Ht=1029,qt=1030,Jt=1031,Xt=1032,Yt=1033,Zt=33776,Gt=33777,$t=33778,Qt=33779,Kt=35840,te=35841,ee=35842,ie=35843,se=36196,re=37492,ne=37496,ae=37808,oe=37809,he=37810,le=37811,ce=37812,ue=37813,de=37814,pe=37815,me=37816,ye=37817,ge=37818,fe=37819,xe=37820,be=37821,ve=36492,we=36494,Me=36495,Se=36283,_e=36284,Ae=36285,Te=36286,ze=2200,Ie=2201,Ce=2202,Be=2300,ke=2301,Ee=2302,Re=2400,Pe=2401,Oe=2402,Ne=2500,Fe=2501,Ve=0,Le=1,je=2,We=3200,Ue=3201,De=3202,He=3203,qe=0,Je=1,Xe="",Ye="srgb",Ze="srgb-linear",Ge="linear",$e="srgb",Qe=0,Ke=7680,ti=7681,ei=7682,ii=7683,si=34055,ri=34056,ni=5386,ai=512,oi=513,hi=514,li=515,ci=516,ui=517,di=518,pi=519,mi=512,yi=513,gi=514,fi=515,xi=516,bi=517,vi=518,wi=519,Mi=35044,Si=35048,_i=35040,Ai=35045,Ti=35049,zi=35041,Ii=35046,Ci=35050,Bi=35042,ki="100",Ei="300 es",Ri=2e3,Pi=2001,Oi={COMPUTE:"compute",RENDER:"render"},Ni={PERSPECTIVE:"perspective",LINEAR:"linear",FLAT:"flat"},Fi={NORMAL:"normal",CENTROID:"centroid",SAMPLE:"sample",FIRST:"first",EITHER:"either"};class Vi{addEventListener(t,e){void 0===this._listeners&&(this._listeners={});const i=this._listeners;void 0===i[t]&&(i[t]=[]),-1===i[t].indexOf(e)&&i[t].push(e)}hasEventListener(t,e){const i=this._listeners;return void 0!==i&&(void 0!==i[t]&&-1!==i[t].indexOf(e))}removeEventListener(t,e){const i=this._listeners;if(void 0===i)return;const s=i[t];if(void 0!==s){const t=s.indexOf(e);-1!==t&&s.splice(t,1)}}dispatchEvent(t){const e=this._listeners;if(void 0===e)return;const i=e[t.type];if(void 0!==i){t.target=this;const e=i.slice(0);for(let i=0,s=e.length;i>8&255]+Li[t>>16&255]+Li[t>>24&255]+"-"+Li[255&e]+Li[e>>8&255]+"-"+Li[e>>16&15|64]+Li[e>>24&255]+"-"+Li[63&i|128]+Li[i>>8&255]+"-"+Li[i>>16&255]+Li[i>>24&255]+Li[255&s]+Li[s>>8&255]+Li[s>>16&255]+Li[s>>24&255]).toLowerCase()}function Hi(t,e,i){return Math.max(e,Math.min(i,t))}function qi(t,e){return(t%e+e)%e}function Ji(t,e,i){return(1-i)*t+i*e}function Xi(t,e){switch(e.constructor){case Float32Array:return t;case Uint32Array:return t/4294967295;case Uint16Array:return t/65535;case Uint8Array:return t/255;case Int32Array:return Math.max(t/2147483647,-1);case Int16Array:return Math.max(t/32767,-1);case Int8Array:return Math.max(t/127,-1);default:throw new Error("Invalid component type.")}}function Yi(t,e){switch(e.constructor){case Float32Array:return t;case Uint32Array:return Math.round(4294967295*t);case Uint16Array:return Math.round(65535*t);case Uint8Array:return Math.round(255*t);case Int32Array:return Math.round(2147483647*t);case Int16Array:return Math.round(32767*t);case Int8Array:return Math.round(127*t);default:throw new Error("Invalid component type.")}}const Zi={DEG2RAD:Wi,RAD2DEG:Ui,generateUUID:Di,clamp:Hi,euclideanModulo:qi,mapLinear:function(t,e,i,s,r){return s+(t-e)*(r-s)/(i-e)},inverseLerp:function(t,e,i){return t!==e?(i-t)/(e-t):0},lerp:Ji,damp:function(t,e,i,s){return Ji(t,e,1-Math.exp(-i*s))},pingpong:function(t,e=1){return e-Math.abs(qi(t,2*e)-e)},smoothstep:function(t,e,i){return t<=e?0:t>=i?1:(t=(t-e)/(i-e))*t*(3-2*t)},smootherstep:function(t,e,i){return t<=e?0:t>=i?1:(t=(t-e)/(i-e))*t*t*(t*(6*t-15)+10)},randInt:function(t,e){return t+Math.floor(Math.random()*(e-t+1))},randFloat:function(t,e){return t+Math.random()*(e-t)},randFloatSpread:function(t){return t*(.5-Math.random())},seededRandom:function(t){void 0!==t&&(ji=t);let e=ji+=1831565813;return e=Math.imul(e^e>>>15,1|e),e^=e+Math.imul(e^e>>>7,61|e),((e^e>>>14)>>>0)/4294967296},degToRad:function(t){return t*Wi},radToDeg:function(t){return t*Ui},isPowerOfTwo:function(t){return!(t&t-1)&&0!==t},ceilPowerOfTwo:function(t){return Math.pow(2,Math.ceil(Math.log(t)/Math.LN2))},floorPowerOfTwo:function(t){return Math.pow(2,Math.floor(Math.log(t)/Math.LN2))},setQuaternionFromProperEuler:function(t,e,i,s,r){const n=Math.cos,a=Math.sin,o=n(i/2),h=a(i/2),l=n((e+s)/2),c=a((e+s)/2),u=n((e-s)/2),d=a((e-s)/2),p=n((s-e)/2),m=a((s-e)/2);switch(r){case"XYX":t.set(o*c,h*u,h*d,o*l);break;case"YZY":t.set(h*d,o*c,h*u,o*l);break;case"ZXZ":t.set(h*u,h*d,o*c,o*l);break;case"XZX":t.set(o*c,h*m,h*p,o*l);break;case"YXY":t.set(h*p,o*c,h*m,o*l);break;case"ZYZ":t.set(h*m,h*p,o*c,o*l);break;default:console.warn("THREE.MathUtils: .setQuaternionFromProperEuler() encountered an unknown order: "+r)}},normalize:Yi,denormalize:Xi};class Gi{constructor(t=0,e=0){Gi.prototype.isVector2=!0,this.x=t,this.y=e}get width(){return this.x}set width(t){this.x=t}get height(){return this.y}set height(t){this.y=t}set(t,e){return this.x=t,this.y=e,this}setScalar(t){return this.x=t,this.y=t,this}setX(t){return this.x=t,this}setY(t){return this.y=t,this}setComponent(t,e){switch(t){case 0:this.x=e;break;case 1:this.y=e;break;default:throw new Error("index is out of range: "+t)}return this}getComponent(t){switch(t){case 0:return this.x;case 1:return this.y;default:throw new Error("index is out of range: "+t)}}clone(){return new this.constructor(this.x,this.y)}copy(t){return this.x=t.x,this.y=t.y,this}add(t){return this.x+=t.x,this.y+=t.y,this}addScalar(t){return this.x+=t,this.y+=t,this}addVectors(t,e){return this.x=t.x+e.x,this.y=t.y+e.y,this}addScaledVector(t,e){return this.x+=t.x*e,this.y+=t.y*e,this}sub(t){return this.x-=t.x,this.y-=t.y,this}subScalar(t){return this.x-=t,this.y-=t,this}subVectors(t,e){return this.x=t.x-e.x,this.y=t.y-e.y,this}multiply(t){return this.x*=t.x,this.y*=t.y,this}multiplyScalar(t){return this.x*=t,this.y*=t,this}divide(t){return this.x/=t.x,this.y/=t.y,this}divideScalar(t){return this.multiplyScalar(1/t)}applyMatrix3(t){const e=this.x,i=this.y,s=t.elements;return this.x=s[0]*e+s[3]*i+s[6],this.y=s[1]*e+s[4]*i+s[7],this}min(t){return this.x=Math.min(this.x,t.x),this.y=Math.min(this.y,t.y),this}max(t){return this.x=Math.max(this.x,t.x),this.y=Math.max(this.y,t.y),this}clamp(t,e){return this.x=Hi(this.x,t.x,e.x),this.y=Hi(this.y,t.y,e.y),this}clampScalar(t,e){return this.x=Hi(this.x,t,e),this.y=Hi(this.y,t,e),this}clampLength(t,e){const i=this.length();return this.divideScalar(i||1).multiplyScalar(Hi(i,t,e))}floor(){return this.x=Math.floor(this.x),this.y=Math.floor(this.y),this}ceil(){return this.x=Math.ceil(this.x),this.y=Math.ceil(this.y),this}round(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this}roundToZero(){return this.x=Math.trunc(this.x),this.y=Math.trunc(this.y),this}negate(){return this.x=-this.x,this.y=-this.y,this}dot(t){return this.x*t.x+this.y*t.y}cross(t){return this.x*t.y-this.y*t.x}lengthSq(){return this.x*this.x+this.y*this.y}length(){return Math.sqrt(this.x*this.x+this.y*this.y)}manhattanLength(){return Math.abs(this.x)+Math.abs(this.y)}normalize(){return this.divideScalar(this.length()||1)}angle(){return Math.atan2(-this.y,-this.x)+Math.PI}angleTo(t){const e=Math.sqrt(this.lengthSq()*t.lengthSq());if(0===e)return Math.PI/2;const i=this.dot(t)/e;return Math.acos(Hi(i,-1,1))}distanceTo(t){return Math.sqrt(this.distanceToSquared(t))}distanceToSquared(t){const e=this.x-t.x,i=this.y-t.y;return e*e+i*i}manhattanDistanceTo(t){return Math.abs(this.x-t.x)+Math.abs(this.y-t.y)}setLength(t){return this.normalize().multiplyScalar(t)}lerp(t,e){return this.x+=(t.x-this.x)*e,this.y+=(t.y-this.y)*e,this}lerpVectors(t,e,i){return this.x=t.x+(e.x-t.x)*i,this.y=t.y+(e.y-t.y)*i,this}equals(t){return t.x===this.x&&t.y===this.y}fromArray(t,e=0){return this.x=t[e],this.y=t[e+1],this}toArray(t=[],e=0){return t[e]=this.x,t[e+1]=this.y,t}fromBufferAttribute(t,e){return this.x=t.getX(e),this.y=t.getY(e),this}rotateAround(t,e){const i=Math.cos(e),s=Math.sin(e),r=this.x-t.x,n=this.y-t.y;return this.x=r*i-n*s+t.x,this.y=r*s+n*i+t.y,this}random(){return this.x=Math.random(),this.y=Math.random(),this}*[Symbol.iterator](){yield this.x,yield this.y}}class $i{constructor(t=0,e=0,i=0,s=1){this.isQuaternion=!0,this._x=t,this._y=e,this._z=i,this._w=s}static slerpFlat(t,e,i,s,r,n,a){let o=i[s+0],h=i[s+1],l=i[s+2],c=i[s+3];const u=r[n+0],d=r[n+1],p=r[n+2],m=r[n+3];if(0===a)return t[e+0]=o,t[e+1]=h,t[e+2]=l,void(t[e+3]=c);if(1===a)return t[e+0]=u,t[e+1]=d,t[e+2]=p,void(t[e+3]=m);if(c!==m||o!==u||h!==d||l!==p){let t=1-a;const e=o*u+h*d+l*p+c*m,i=e>=0?1:-1,s=1-e*e;if(s>Number.EPSILON){const r=Math.sqrt(s),n=Math.atan2(r,e*i);t=Math.sin(t*n)/r,a=Math.sin(a*n)/r}const r=a*i;if(o=o*t+u*r,h=h*t+d*r,l=l*t+p*r,c=c*t+m*r,t===1-a){const t=1/Math.sqrt(o*o+h*h+l*l+c*c);o*=t,h*=t,l*=t,c*=t}}t[e]=o,t[e+1]=h,t[e+2]=l,t[e+3]=c}static multiplyQuaternionsFlat(t,e,i,s,r,n){const a=i[s],o=i[s+1],h=i[s+2],l=i[s+3],c=r[n],u=r[n+1],d=r[n+2],p=r[n+3];return t[e]=a*p+l*c+o*d-h*u,t[e+1]=o*p+l*u+h*c-a*d,t[e+2]=h*p+l*d+a*u-o*c,t[e+3]=l*p-a*c-o*u-h*d,t}get x(){return this._x}set x(t){this._x=t,this._onChangeCallback()}get y(){return this._y}set y(t){this._y=t,this._onChangeCallback()}get z(){return this._z}set z(t){this._z=t,this._onChangeCallback()}get w(){return this._w}set w(t){this._w=t,this._onChangeCallback()}set(t,e,i,s){return this._x=t,this._y=e,this._z=i,this._w=s,this._onChangeCallback(),this}clone(){return new this.constructor(this._x,this._y,this._z,this._w)}copy(t){return this._x=t.x,this._y=t.y,this._z=t.z,this._w=t.w,this._onChangeCallback(),this}setFromEuler(t,e=!0){const i=t._x,s=t._y,r=t._z,n=t._order,a=Math.cos,o=Math.sin,h=a(i/2),l=a(s/2),c=a(r/2),u=o(i/2),d=o(s/2),p=o(r/2);switch(n){case"XYZ":this._x=u*l*c+h*d*p,this._y=h*d*c-u*l*p,this._z=h*l*p+u*d*c,this._w=h*l*c-u*d*p;break;case"YXZ":this._x=u*l*c+h*d*p,this._y=h*d*c-u*l*p,this._z=h*l*p-u*d*c,this._w=h*l*c+u*d*p;break;case"ZXY":this._x=u*l*c-h*d*p,this._y=h*d*c+u*l*p,this._z=h*l*p+u*d*c,this._w=h*l*c-u*d*p;break;case"ZYX":this._x=u*l*c-h*d*p,this._y=h*d*c+u*l*p,this._z=h*l*p-u*d*c,this._w=h*l*c+u*d*p;break;case"YZX":this._x=u*l*c+h*d*p,this._y=h*d*c+u*l*p,this._z=h*l*p-u*d*c,this._w=h*l*c-u*d*p;break;case"XZY":this._x=u*l*c-h*d*p,this._y=h*d*c-u*l*p,this._z=h*l*p+u*d*c,this._w=h*l*c+u*d*p;break;default:console.warn("THREE.Quaternion: .setFromEuler() encountered an unknown order: "+n)}return!0===e&&this._onChangeCallback(),this}setFromAxisAngle(t,e){const i=e/2,s=Math.sin(i);return this._x=t.x*s,this._y=t.y*s,this._z=t.z*s,this._w=Math.cos(i),this._onChangeCallback(),this}setFromRotationMatrix(t){const e=t.elements,i=e[0],s=e[4],r=e[8],n=e[1],a=e[5],o=e[9],h=e[2],l=e[6],c=e[10],u=i+a+c;if(u>0){const t=.5/Math.sqrt(u+1);this._w=.25/t,this._x=(l-o)*t,this._y=(r-h)*t,this._z=(n-s)*t}else if(i>a&&i>c){const t=2*Math.sqrt(1+i-a-c);this._w=(l-o)/t,this._x=.25*t,this._y=(s+n)/t,this._z=(r+h)/t}else if(a>c){const t=2*Math.sqrt(1+a-i-c);this._w=(r-h)/t,this._x=(s+n)/t,this._y=.25*t,this._z=(o+l)/t}else{const t=2*Math.sqrt(1+c-i-a);this._w=(n-s)/t,this._x=(r+h)/t,this._y=(o+l)/t,this._z=.25*t}return this._onChangeCallback(),this}setFromUnitVectors(t,e){let i=t.dot(e)+1;return i<1e-8?(i=0,Math.abs(t.x)>Math.abs(t.z)?(this._x=-t.y,this._y=t.x,this._z=0,this._w=i):(this._x=0,this._y=-t.z,this._z=t.y,this._w=i)):(this._x=t.y*e.z-t.z*e.y,this._y=t.z*e.x-t.x*e.z,this._z=t.x*e.y-t.y*e.x,this._w=i),this.normalize()}angleTo(t){return 2*Math.acos(Math.abs(Hi(this.dot(t),-1,1)))}rotateTowards(t,e){const i=this.angleTo(t);if(0===i)return this;const s=Math.min(1,e/i);return this.slerp(t,s),this}identity(){return this.set(0,0,0,1)}invert(){return this.conjugate()}conjugate(){return this._x*=-1,this._y*=-1,this._z*=-1,this._onChangeCallback(),this}dot(t){return this._x*t._x+this._y*t._y+this._z*t._z+this._w*t._w}lengthSq(){return this._x*this._x+this._y*this._y+this._z*this._z+this._w*this._w}length(){return Math.sqrt(this._x*this._x+this._y*this._y+this._z*this._z+this._w*this._w)}normalize(){let t=this.length();return 0===t?(this._x=0,this._y=0,this._z=0,this._w=1):(t=1/t,this._x=this._x*t,this._y=this._y*t,this._z=this._z*t,this._w=this._w*t),this._onChangeCallback(),this}multiply(t){return this.multiplyQuaternions(this,t)}premultiply(t){return this.multiplyQuaternions(t,this)}multiplyQuaternions(t,e){const i=t._x,s=t._y,r=t._z,n=t._w,a=e._x,o=e._y,h=e._z,l=e._w;return this._x=i*l+n*a+s*h-r*o,this._y=s*l+n*o+r*a-i*h,this._z=r*l+n*h+i*o-s*a,this._w=n*l-i*a-s*o-r*h,this._onChangeCallback(),this}slerp(t,e){if(0===e)return this;if(1===e)return this.copy(t);const i=this._x,s=this._y,r=this._z,n=this._w;let a=n*t._w+i*t._x+s*t._y+r*t._z;if(a<0?(this._w=-t._w,this._x=-t._x,this._y=-t._y,this._z=-t._z,a=-a):this.copy(t),a>=1)return this._w=n,this._x=i,this._y=s,this._z=r,this;const o=1-a*a;if(o<=Number.EPSILON){const t=1-e;return this._w=t*n+e*this._w,this._x=t*i+e*this._x,this._y=t*s+e*this._y,this._z=t*r+e*this._z,this.normalize(),this}const h=Math.sqrt(o),l=Math.atan2(h,a),c=Math.sin((1-e)*l)/h,u=Math.sin(e*l)/h;return this._w=n*c+this._w*u,this._x=i*c+this._x*u,this._y=s*c+this._y*u,this._z=r*c+this._z*u,this._onChangeCallback(),this}slerpQuaternions(t,e,i){return this.copy(t).slerp(e,i)}random(){const t=2*Math.PI*Math.random(),e=2*Math.PI*Math.random(),i=Math.random(),s=Math.sqrt(1-i),r=Math.sqrt(i);return this.set(s*Math.sin(t),s*Math.cos(t),r*Math.sin(e),r*Math.cos(e))}equals(t){return t._x===this._x&&t._y===this._y&&t._z===this._z&&t._w===this._w}fromArray(t,e=0){return this._x=t[e],this._y=t[e+1],this._z=t[e+2],this._w=t[e+3],this._onChangeCallback(),this}toArray(t=[],e=0){return t[e]=this._x,t[e+1]=this._y,t[e+2]=this._z,t[e+3]=this._w,t}fromBufferAttribute(t,e){return this._x=t.getX(e),this._y=t.getY(e),this._z=t.getZ(e),this._w=t.getW(e),this._onChangeCallback(),this}toJSON(){return this.toArray()}_onChange(t){return this._onChangeCallback=t,this}_onChangeCallback(){}*[Symbol.iterator](){yield this._x,yield this._y,yield this._z,yield this._w}}class Qi{constructor(t=0,e=0,i=0){Qi.prototype.isVector3=!0,this.x=t,this.y=e,this.z=i}set(t,e,i){return void 0===i&&(i=this.z),this.x=t,this.y=e,this.z=i,this}setScalar(t){return this.x=t,this.y=t,this.z=t,this}setX(t){return this.x=t,this}setY(t){return this.y=t,this}setZ(t){return this.z=t,this}setComponent(t,e){switch(t){case 0:this.x=e;break;case 1:this.y=e;break;case 2:this.z=e;break;default:throw new Error("index is out of range: "+t)}return this}getComponent(t){switch(t){case 0:return this.x;case 1:return this.y;case 2:return this.z;default:throw new Error("index is out of range: "+t)}}clone(){return new this.constructor(this.x,this.y,this.z)}copy(t){return this.x=t.x,this.y=t.y,this.z=t.z,this}add(t){return this.x+=t.x,this.y+=t.y,this.z+=t.z,this}addScalar(t){return this.x+=t,this.y+=t,this.z+=t,this}addVectors(t,e){return this.x=t.x+e.x,this.y=t.y+e.y,this.z=t.z+e.z,this}addScaledVector(t,e){return this.x+=t.x*e,this.y+=t.y*e,this.z+=t.z*e,this}sub(t){return this.x-=t.x,this.y-=t.y,this.z-=t.z,this}subScalar(t){return this.x-=t,this.y-=t,this.z-=t,this}subVectors(t,e){return this.x=t.x-e.x,this.y=t.y-e.y,this.z=t.z-e.z,this}multiply(t){return this.x*=t.x,this.y*=t.y,this.z*=t.z,this}multiplyScalar(t){return this.x*=t,this.y*=t,this.z*=t,this}multiplyVectors(t,e){return this.x=t.x*e.x,this.y=t.y*e.y,this.z=t.z*e.z,this}applyEuler(t){return this.applyQuaternion(ts.setFromEuler(t))}applyAxisAngle(t,e){return this.applyQuaternion(ts.setFromAxisAngle(t,e))}applyMatrix3(t){const e=this.x,i=this.y,s=this.z,r=t.elements;return this.x=r[0]*e+r[3]*i+r[6]*s,this.y=r[1]*e+r[4]*i+r[7]*s,this.z=r[2]*e+r[5]*i+r[8]*s,this}applyNormalMatrix(t){return this.applyMatrix3(t).normalize()}applyMatrix4(t){const e=this.x,i=this.y,s=this.z,r=t.elements,n=1/(r[3]*e+r[7]*i+r[11]*s+r[15]);return this.x=(r[0]*e+r[4]*i+r[8]*s+r[12])*n,this.y=(r[1]*e+r[5]*i+r[9]*s+r[13])*n,this.z=(r[2]*e+r[6]*i+r[10]*s+r[14])*n,this}applyQuaternion(t){const e=this.x,i=this.y,s=this.z,r=t.x,n=t.y,a=t.z,o=t.w,h=2*(n*s-a*i),l=2*(a*e-r*s),c=2*(r*i-n*e);return this.x=e+o*h+n*c-a*l,this.y=i+o*l+a*h-r*c,this.z=s+o*c+r*l-n*h,this}project(t){return this.applyMatrix4(t.matrixWorldInverse).applyMatrix4(t.projectionMatrix)}unproject(t){return this.applyMatrix4(t.projectionMatrixInverse).applyMatrix4(t.matrixWorld)}transformDirection(t){const e=this.x,i=this.y,s=this.z,r=t.elements;return this.x=r[0]*e+r[4]*i+r[8]*s,this.y=r[1]*e+r[5]*i+r[9]*s,this.z=r[2]*e+r[6]*i+r[10]*s,this.normalize()}divide(t){return this.x/=t.x,this.y/=t.y,this.z/=t.z,this}divideScalar(t){return this.multiplyScalar(1/t)}min(t){return this.x=Math.min(this.x,t.x),this.y=Math.min(this.y,t.y),this.z=Math.min(this.z,t.z),this}max(t){return this.x=Math.max(this.x,t.x),this.y=Math.max(this.y,t.y),this.z=Math.max(this.z,t.z),this}clamp(t,e){return this.x=Hi(this.x,t.x,e.x),this.y=Hi(this.y,t.y,e.y),this.z=Hi(this.z,t.z,e.z),this}clampScalar(t,e){return this.x=Hi(this.x,t,e),this.y=Hi(this.y,t,e),this.z=Hi(this.z,t,e),this}clampLength(t,e){const i=this.length();return this.divideScalar(i||1).multiplyScalar(Hi(i,t,e))}floor(){return this.x=Math.floor(this.x),this.y=Math.floor(this.y),this.z=Math.floor(this.z),this}ceil(){return this.x=Math.ceil(this.x),this.y=Math.ceil(this.y),this.z=Math.ceil(this.z),this}round(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this.z=Math.round(this.z),this}roundToZero(){return this.x=Math.trunc(this.x),this.y=Math.trunc(this.y),this.z=Math.trunc(this.z),this}negate(){return this.x=-this.x,this.y=-this.y,this.z=-this.z,this}dot(t){return this.x*t.x+this.y*t.y+this.z*t.z}lengthSq(){return this.x*this.x+this.y*this.y+this.z*this.z}length(){return Math.sqrt(this.x*this.x+this.y*this.y+this.z*this.z)}manhattanLength(){return Math.abs(this.x)+Math.abs(this.y)+Math.abs(this.z)}normalize(){return this.divideScalar(this.length()||1)}setLength(t){return this.normalize().multiplyScalar(t)}lerp(t,e){return this.x+=(t.x-this.x)*e,this.y+=(t.y-this.y)*e,this.z+=(t.z-this.z)*e,this}lerpVectors(t,e,i){return this.x=t.x+(e.x-t.x)*i,this.y=t.y+(e.y-t.y)*i,this.z=t.z+(e.z-t.z)*i,this}cross(t){return this.crossVectors(this,t)}crossVectors(t,e){const i=t.x,s=t.y,r=t.z,n=e.x,a=e.y,o=e.z;return this.x=s*o-r*a,this.y=r*n-i*o,this.z=i*a-s*n,this}projectOnVector(t){const e=t.lengthSq();if(0===e)return this.set(0,0,0);const i=t.dot(this)/e;return this.copy(t).multiplyScalar(i)}projectOnPlane(t){return Ki.copy(this).projectOnVector(t),this.sub(Ki)}reflect(t){return this.sub(Ki.copy(t).multiplyScalar(2*this.dot(t)))}angleTo(t){const e=Math.sqrt(this.lengthSq()*t.lengthSq());if(0===e)return Math.PI/2;const i=this.dot(t)/e;return Math.acos(Hi(i,-1,1))}distanceTo(t){return Math.sqrt(this.distanceToSquared(t))}distanceToSquared(t){const e=this.x-t.x,i=this.y-t.y,s=this.z-t.z;return e*e+i*i+s*s}manhattanDistanceTo(t){return Math.abs(this.x-t.x)+Math.abs(this.y-t.y)+Math.abs(this.z-t.z)}setFromSpherical(t){return this.setFromSphericalCoords(t.radius,t.phi,t.theta)}setFromSphericalCoords(t,e,i){const s=Math.sin(e)*t;return this.x=s*Math.sin(i),this.y=Math.cos(e)*t,this.z=s*Math.cos(i),this}setFromCylindrical(t){return this.setFromCylindricalCoords(t.radius,t.theta,t.y)}setFromCylindricalCoords(t,e,i){return this.x=t*Math.sin(e),this.y=i,this.z=t*Math.cos(e),this}setFromMatrixPosition(t){const e=t.elements;return this.x=e[12],this.y=e[13],this.z=e[14],this}setFromMatrixScale(t){const e=this.setFromMatrixColumn(t,0).length(),i=this.setFromMatrixColumn(t,1).length(),s=this.setFromMatrixColumn(t,2).length();return this.x=e,this.y=i,this.z=s,this}setFromMatrixColumn(t,e){return this.fromArray(t.elements,4*e)}setFromMatrix3Column(t,e){return this.fromArray(t.elements,3*e)}setFromEuler(t){return this.x=t._x,this.y=t._y,this.z=t._z,this}setFromColor(t){return this.x=t.r,this.y=t.g,this.z=t.b,this}equals(t){return t.x===this.x&&t.y===this.y&&t.z===this.z}fromArray(t,e=0){return this.x=t[e],this.y=t[e+1],this.z=t[e+2],this}toArray(t=[],e=0){return t[e]=this.x,t[e+1]=this.y,t[e+2]=this.z,t}fromBufferAttribute(t,e){return this.x=t.getX(e),this.y=t.getY(e),this.z=t.getZ(e),this}random(){return this.x=Math.random(),this.y=Math.random(),this.z=Math.random(),this}randomDirection(){const t=Math.random()*Math.PI*2,e=2*Math.random()-1,i=Math.sqrt(1-e*e);return this.x=i*Math.cos(t),this.y=e,this.z=i*Math.sin(t),this}*[Symbol.iterator](){yield this.x,yield this.y,yield this.z}}const Ki=new Qi,ts=new $i;class es{constructor(t,e,i,s,r,n,a,o,h){es.prototype.isMatrix3=!0,this.elements=[1,0,0,0,1,0,0,0,1],void 0!==t&&this.set(t,e,i,s,r,n,a,o,h)}set(t,e,i,s,r,n,a,o,h){const l=this.elements;return l[0]=t,l[1]=s,l[2]=a,l[3]=e,l[4]=r,l[5]=o,l[6]=i,l[7]=n,l[8]=h,this}identity(){return this.set(1,0,0,0,1,0,0,0,1),this}copy(t){const e=this.elements,i=t.elements;return e[0]=i[0],e[1]=i[1],e[2]=i[2],e[3]=i[3],e[4]=i[4],e[5]=i[5],e[6]=i[6],e[7]=i[7],e[8]=i[8],this}extractBasis(t,e,i){return t.setFromMatrix3Column(this,0),e.setFromMatrix3Column(this,1),i.setFromMatrix3Column(this,2),this}setFromMatrix4(t){const e=t.elements;return this.set(e[0],e[4],e[8],e[1],e[5],e[9],e[2],e[6],e[10]),this}multiply(t){return this.multiplyMatrices(this,t)}premultiply(t){return this.multiplyMatrices(t,this)}multiplyMatrices(t,e){const i=t.elements,s=e.elements,r=this.elements,n=i[0],a=i[3],o=i[6],h=i[1],l=i[4],c=i[7],u=i[2],d=i[5],p=i[8],m=s[0],y=s[3],g=s[6],f=s[1],x=s[4],b=s[7],v=s[2],w=s[5],M=s[8];return r[0]=n*m+a*f+o*v,r[3]=n*y+a*x+o*w,r[6]=n*g+a*b+o*M,r[1]=h*m+l*f+c*v,r[4]=h*y+l*x+c*w,r[7]=h*g+l*b+c*M,r[2]=u*m+d*f+p*v,r[5]=u*y+d*x+p*w,r[8]=u*g+d*b+p*M,this}multiplyScalar(t){const e=this.elements;return e[0]*=t,e[3]*=t,e[6]*=t,e[1]*=t,e[4]*=t,e[7]*=t,e[2]*=t,e[5]*=t,e[8]*=t,this}determinant(){const t=this.elements,e=t[0],i=t[1],s=t[2],r=t[3],n=t[4],a=t[5],o=t[6],h=t[7],l=t[8];return e*n*l-e*a*h-i*r*l+i*a*o+s*r*h-s*n*o}invert(){const t=this.elements,e=t[0],i=t[1],s=t[2],r=t[3],n=t[4],a=t[5],o=t[6],h=t[7],l=t[8],c=l*n-a*h,u=a*o-l*r,d=h*r-n*o,p=e*c+i*u+s*d;if(0===p)return this.set(0,0,0,0,0,0,0,0,0);const m=1/p;return t[0]=c*m,t[1]=(s*h-l*i)*m,t[2]=(a*i-s*n)*m,t[3]=u*m,t[4]=(l*e-s*o)*m,t[5]=(s*r-a*e)*m,t[6]=d*m,t[7]=(i*o-h*e)*m,t[8]=(n*e-i*r)*m,this}transpose(){let t;const e=this.elements;return t=e[1],e[1]=e[3],e[3]=t,t=e[2],e[2]=e[6],e[6]=t,t=e[5],e[5]=e[7],e[7]=t,this}getNormalMatrix(t){return this.setFromMatrix4(t).invert().transpose()}transposeIntoArray(t){const e=this.elements;return t[0]=e[0],t[1]=e[3],t[2]=e[6],t[3]=e[1],t[4]=e[4],t[5]=e[7],t[6]=e[2],t[7]=e[5],t[8]=e[8],this}setUvTransform(t,e,i,s,r,n,a){const o=Math.cos(r),h=Math.sin(r);return this.set(i*o,i*h,-i*(o*n+h*a)+n+t,-s*h,s*o,-s*(-h*n+o*a)+a+e,0,0,1),this}scale(t,e){return this.premultiply(is.makeScale(t,e)),this}rotate(t){return this.premultiply(is.makeRotation(-t)),this}translate(t,e){return this.premultiply(is.makeTranslation(t,e)),this}makeTranslation(t,e){return t.isVector2?this.set(1,0,t.x,0,1,t.y,0,0,1):this.set(1,0,t,0,1,e,0,0,1),this}makeRotation(t){const e=Math.cos(t),i=Math.sin(t);return this.set(e,-i,0,i,e,0,0,0,1),this}makeScale(t,e){return this.set(t,0,0,0,e,0,0,0,1),this}equals(t){const e=this.elements,i=t.elements;for(let t=0;t<9;t++)if(e[t]!==i[t])return!1;return!0}fromArray(t,e=0){for(let i=0;i<9;i++)this.elements[i]=t[i+e];return this}toArray(t=[],e=0){const i=this.elements;return t[e]=i[0],t[e+1]=i[1],t[e+2]=i[2],t[e+3]=i[3],t[e+4]=i[4],t[e+5]=i[5],t[e+6]=i[6],t[e+7]=i[7],t[e+8]=i[8],t}clone(){return(new this.constructor).fromArray(this.elements)}}const is=new es;function ss(t){for(let e=t.length-1;e>=0;--e)if(t[e]>=65535)return!0;return!1}const rs={Int8Array:Int8Array,Uint8Array:Uint8Array,Uint8ClampedArray:Uint8ClampedArray,Int16Array:Int16Array,Uint16Array:Uint16Array,Int32Array:Int32Array,Uint32Array:Uint32Array,Float32Array:Float32Array,Float64Array:Float64Array};function ns(t,e){return new rs[t](e)}function as(t){return document.createElementNS("http://www.w3.org/1999/xhtml",t)}function os(){const t=as("canvas");return t.style.display="block",t}const hs={};function ls(t){t in hs||(hs[t]=!0,console.warn(t))}function cs(t,e,i){return new Promise(function(s,r){setTimeout(function n(){switch(t.clientWaitSync(e,t.SYNC_FLUSH_COMMANDS_BIT,0)){case t.WAIT_FAILED:r();break;case t.TIMEOUT_EXPIRED:setTimeout(n,i);break;default:s()}},i)})}function us(t){const e=t.elements;e[2]=.5*e[2]+.5*e[3],e[6]=.5*e[6]+.5*e[7],e[10]=.5*e[10]+.5*e[11],e[14]=.5*e[14]+.5*e[15]}function ds(t){const e=t.elements;-1===e[11]?(e[10]=-e[10]-1,e[14]=-e[14]):(e[10]=-e[10],e[14]=1-e[14])}const ps=(new es).set(.4123908,.3575843,.1804808,.212639,.7151687,.0721923,.0193308,.1191948,.9505322),ms=(new es).set(3.2409699,-1.5373832,-.4986108,-.9692436,1.8759675,.0415551,.0556301,-.203977,1.0569715);function ys(){const t={enabled:!0,workingColorSpace:Ze,spaces:{},convert:function(t,e,i){return!1!==this.enabled&&e!==i&&e&&i?(this.spaces[e].transfer===$e&&(t.r=fs(t.r),t.g=fs(t.g),t.b=fs(t.b)),this.spaces[e].primaries!==this.spaces[i].primaries&&(t.applyMatrix3(this.spaces[e].toXYZ),t.applyMatrix3(this.spaces[i].fromXYZ)),this.spaces[i].transfer===$e&&(t.r=xs(t.r),t.g=xs(t.g),t.b=xs(t.b)),t):t},workingToColorSpace:function(t,e){return this.convert(t,this.workingColorSpace,e)},colorSpaceToWorking:function(t,e){return this.convert(t,e,this.workingColorSpace)},getPrimaries:function(t){return this.spaces[t].primaries},getTransfer:function(t){return""===t?Ge:this.spaces[t].transfer},getLuminanceCoefficients:function(t,e=this.workingColorSpace){return t.fromArray(this.spaces[e].luminanceCoefficients)},define:function(t){Object.assign(this.spaces,t)},_getMatrix:function(t,e,i){return t.copy(this.spaces[e].toXYZ).multiply(this.spaces[i].fromXYZ)},_getDrawingBufferColorSpace:function(t){return this.spaces[t].outputColorSpaceConfig.drawingBufferColorSpace},_getUnpackColorSpace:function(t=this.workingColorSpace){return this.spaces[t].workingColorSpaceConfig.unpackColorSpace},fromWorkingColorSpace:function(e,i){return ls("THREE.ColorManagement: .fromWorkingColorSpace() has been renamed to .workingToColorSpace()."),t.workingToColorSpace(e,i)},toWorkingColorSpace:function(e,i){return ls("THREE.ColorManagement: .toWorkingColorSpace() has been renamed to .colorSpaceToWorking()."),t.colorSpaceToWorking(e,i)}},e=[.64,.33,.3,.6,.15,.06],i=[.2126,.7152,.0722],s=[.3127,.329];return t.define({[Ze]:{primaries:e,whitePoint:s,transfer:Ge,toXYZ:ps,fromXYZ:ms,luminanceCoefficients:i,workingColorSpaceConfig:{unpackColorSpace:Ye},outputColorSpaceConfig:{drawingBufferColorSpace:Ye}},[Ye]:{primaries:e,whitePoint:s,transfer:$e,toXYZ:ps,fromXYZ:ms,luminanceCoefficients:i,outputColorSpaceConfig:{drawingBufferColorSpace:Ye}}}),t}const gs=ys();function fs(t){return t<.04045?.0773993808*t:Math.pow(.9478672986*t+.0521327014,2.4)}function xs(t){return t<.0031308?12.92*t:1.055*Math.pow(t,.41666)-.055}let bs;class vs{static getDataURL(t,e="image/png"){if(/^data:/i.test(t.src))return t.src;if("undefined"==typeof HTMLCanvasElement)return t.src;let i;if(t instanceof HTMLCanvasElement)i=t;else{void 0===bs&&(bs=as("canvas")),bs.width=t.width,bs.height=t.height;const e=bs.getContext("2d");t instanceof ImageData?e.putImageData(t,0,0):e.drawImage(t,0,0,t.width,t.height),i=bs}return i.toDataURL(e)}static sRGBToLinear(t){if("undefined"!=typeof HTMLImageElement&&t instanceof HTMLImageElement||"undefined"!=typeof HTMLCanvasElement&&t instanceof HTMLCanvasElement||"undefined"!=typeof ImageBitmap&&t instanceof ImageBitmap){const e=as("canvas");e.width=t.width,e.height=t.height;const i=e.getContext("2d");i.drawImage(t,0,0,t.width,t.height);const s=i.getImageData(0,0,t.width,t.height),r=s.data;for(let t=0;t1),this.pmremVersion=0}get width(){return this.source.getSize(As).x}get height(){return this.source.getSize(As).y}get depth(){return this.source.getSize(As).z}get image(){return this.source.data}set image(t=null){this.source.data=t}updateMatrix(){this.matrix.setUvTransform(this.offset.x,this.offset.y,this.repeat.x,this.repeat.y,this.rotation,this.center.x,this.center.y)}addUpdateRange(t,e){this.updateRanges.push({start:t,count:e})}clearUpdateRanges(){this.updateRanges.length=0}clone(){return(new this.constructor).copy(this)}copy(t){return this.name=t.name,this.source=t.source,this.mipmaps=t.mipmaps.slice(0),this.mapping=t.mapping,this.channel=t.channel,this.wrapS=t.wrapS,this.wrapT=t.wrapT,this.magFilter=t.magFilter,this.minFilter=t.minFilter,this.anisotropy=t.anisotropy,this.format=t.format,this.internalFormat=t.internalFormat,this.type=t.type,this.offset.copy(t.offset),this.repeat.copy(t.repeat),this.center.copy(t.center),this.rotation=t.rotation,this.matrixAutoUpdate=t.matrixAutoUpdate,this.matrix.copy(t.matrix),this.generateMipmaps=t.generateMipmaps,this.premultiplyAlpha=t.premultiplyAlpha,this.flipY=t.flipY,this.unpackAlignment=t.unpackAlignment,this.colorSpace=t.colorSpace,this.renderTarget=t.renderTarget,this.isRenderTargetTexture=t.isRenderTargetTexture,this.isArrayTexture=t.isArrayTexture,this.userData=JSON.parse(JSON.stringify(t.userData)),this.needsUpdate=!0,this}setValues(t){for(const e in t){const i=t[e];if(void 0===i){console.warn(`THREE.Texture.setValues(): parameter '${e}' has value of undefined.`);continue}const s=this[e];void 0!==s?s&&i&&s.isVector2&&i.isVector2||s&&i&&s.isVector3&&i.isVector3||s&&i&&s.isMatrix3&&i.isMatrix3?s.copy(i):this[e]=i:console.warn(`THREE.Texture.setValues(): property '${e}' does not exist.`)}}toJSON(t){const e=void 0===t||"string"==typeof t;if(!e&&void 0!==t.textures[this.uuid])return t.textures[this.uuid];const i={metadata:{version:4.7,type:"Texture",generator:"Texture.toJSON"},uuid:this.uuid,name:this.name,image:this.source.toJSON(t).uuid,mapping:this.mapping,channel:this.channel,repeat:[this.repeat.x,this.repeat.y],offset:[this.offset.x,this.offset.y],center:[this.center.x,this.center.y],rotation:this.rotation,wrap:[this.wrapS,this.wrapT],format:this.format,internalFormat:this.internalFormat,type:this.type,colorSpace:this.colorSpace,minFilter:this.minFilter,magFilter:this.magFilter,anisotropy:this.anisotropy,flipY:this.flipY,generateMipmaps:this.generateMipmaps,premultiplyAlpha:this.premultiplyAlpha,unpackAlignment:this.unpackAlignment};return Object.keys(this.userData).length>0&&(i.userData=this.userData),e||(t.textures[this.uuid]=i),i}dispose(){this.dispatchEvent({type:"dispose"})}transformUv(t){if(this.mapping!==ot)return t;if(t.applyMatrix3(this.matrix),t.x<0||t.x>1)switch(this.wrapS){case pt:t.x=t.x-Math.floor(t.x);break;case mt:t.x=t.x<0?0:1;break;case yt:1===Math.abs(Math.floor(t.x)%2)?t.x=Math.ceil(t.x)-t.x:t.x=t.x-Math.floor(t.x)}if(t.y<0||t.y>1)switch(this.wrapT){case pt:t.y=t.y-Math.floor(t.y);break;case mt:t.y=t.y<0?0:1;break;case yt:1===Math.abs(Math.floor(t.y)%2)?t.y=Math.ceil(t.y)-t.y:t.y=t.y-Math.floor(t.y)}return this.flipY&&(t.y=1-t.y),t}set needsUpdate(t){!0===t&&(this.version++,this.source.needsUpdate=!0)}set needsPMREMUpdate(t){!0===t&&this.pmremVersion++}}Ts.DEFAULT_IMAGE=null,Ts.DEFAULT_MAPPING=ot,Ts.DEFAULT_ANISOTROPY=1;class zs{constructor(t=0,e=0,i=0,s=1){zs.prototype.isVector4=!0,this.x=t,this.y=e,this.z=i,this.w=s}get width(){return this.z}set width(t){this.z=t}get height(){return this.w}set height(t){this.w=t}set(t,e,i,s){return this.x=t,this.y=e,this.z=i,this.w=s,this}setScalar(t){return this.x=t,this.y=t,this.z=t,this.w=t,this}setX(t){return this.x=t,this}setY(t){return this.y=t,this}setZ(t){return this.z=t,this}setW(t){return this.w=t,this}setComponent(t,e){switch(t){case 0:this.x=e;break;case 1:this.y=e;break;case 2:this.z=e;break;case 3:this.w=e;break;default:throw new Error("index is out of range: "+t)}return this}getComponent(t){switch(t){case 0:return this.x;case 1:return this.y;case 2:return this.z;case 3:return this.w;default:throw new Error("index is out of range: "+t)}}clone(){return new this.constructor(this.x,this.y,this.z,this.w)}copy(t){return this.x=t.x,this.y=t.y,this.z=t.z,this.w=void 0!==t.w?t.w:1,this}add(t){return this.x+=t.x,this.y+=t.y,this.z+=t.z,this.w+=t.w,this}addScalar(t){return this.x+=t,this.y+=t,this.z+=t,this.w+=t,this}addVectors(t,e){return this.x=t.x+e.x,this.y=t.y+e.y,this.z=t.z+e.z,this.w=t.w+e.w,this}addScaledVector(t,e){return this.x+=t.x*e,this.y+=t.y*e,this.z+=t.z*e,this.w+=t.w*e,this}sub(t){return this.x-=t.x,this.y-=t.y,this.z-=t.z,this.w-=t.w,this}subScalar(t){return this.x-=t,this.y-=t,this.z-=t,this.w-=t,this}subVectors(t,e){return this.x=t.x-e.x,this.y=t.y-e.y,this.z=t.z-e.z,this.w=t.w-e.w,this}multiply(t){return this.x*=t.x,this.y*=t.y,this.z*=t.z,this.w*=t.w,this}multiplyScalar(t){return this.x*=t,this.y*=t,this.z*=t,this.w*=t,this}applyMatrix4(t){const e=this.x,i=this.y,s=this.z,r=this.w,n=t.elements;return this.x=n[0]*e+n[4]*i+n[8]*s+n[12]*r,this.y=n[1]*e+n[5]*i+n[9]*s+n[13]*r,this.z=n[2]*e+n[6]*i+n[10]*s+n[14]*r,this.w=n[3]*e+n[7]*i+n[11]*s+n[15]*r,this}divide(t){return this.x/=t.x,this.y/=t.y,this.z/=t.z,this.w/=t.w,this}divideScalar(t){return this.multiplyScalar(1/t)}setAxisAngleFromQuaternion(t){this.w=2*Math.acos(t.w);const e=Math.sqrt(1-t.w*t.w);return e<1e-4?(this.x=1,this.y=0,this.z=0):(this.x=t.x/e,this.y=t.y/e,this.z=t.z/e),this}setAxisAngleFromRotationMatrix(t){let e,i,s,r;const n=.01,a=.1,o=t.elements,h=o[0],l=o[4],c=o[8],u=o[1],d=o[5],p=o[9],m=o[2],y=o[6],g=o[10];if(Math.abs(l-u)o&&t>f?tf?o1;this.dispose()}this.viewport.set(0,0,t,e),this.scissor.set(0,0,t,e)}clone(){return(new this.constructor).copy(this)}copy(t){this.width=t.width,this.height=t.height,this.depth=t.depth,this.scissor.copy(t.scissor),this.scissorTest=t.scissorTest,this.viewport.copy(t.viewport),this.textures.length=0;for(let e=0,i=t.textures.length;e=this.min.x&&t.x<=this.max.x&&t.y>=this.min.y&&t.y<=this.max.y&&t.z>=this.min.z&&t.z<=this.max.z}containsBox(t){return this.min.x<=t.min.x&&t.max.x<=this.max.x&&this.min.y<=t.min.y&&t.max.y<=this.max.y&&this.min.z<=t.min.z&&t.max.z<=this.max.z}getParameter(t,e){return e.set((t.x-this.min.x)/(this.max.x-this.min.x),(t.y-this.min.y)/(this.max.y-this.min.y),(t.z-this.min.z)/(this.max.z-this.min.z))}intersectsBox(t){return t.max.x>=this.min.x&&t.min.x<=this.max.x&&t.max.y>=this.min.y&&t.min.y<=this.max.y&&t.max.z>=this.min.z&&t.min.z<=this.max.z}intersectsSphere(t){return this.clampPoint(t.center,Ns),Ns.distanceToSquared(t.center)<=t.radius*t.radius}intersectsPlane(t){let e,i;return t.normal.x>0?(e=t.normal.x*this.min.x,i=t.normal.x*this.max.x):(e=t.normal.x*this.max.x,i=t.normal.x*this.min.x),t.normal.y>0?(e+=t.normal.y*this.min.y,i+=t.normal.y*this.max.y):(e+=t.normal.y*this.max.y,i+=t.normal.y*this.min.y),t.normal.z>0?(e+=t.normal.z*this.min.z,i+=t.normal.z*this.max.z):(e+=t.normal.z*this.max.z,i+=t.normal.z*this.min.z),e<=-t.constant&&i>=-t.constant}intersectsTriangle(t){if(this.isEmpty())return!1;this.getCenter(Hs),qs.subVectors(this.max,Hs),Vs.subVectors(t.a,Hs),Ls.subVectors(t.b,Hs),js.subVectors(t.c,Hs),Ws.subVectors(Ls,Vs),Us.subVectors(js,Ls),Ds.subVectors(Vs,js);let e=[0,-Ws.z,Ws.y,0,-Us.z,Us.y,0,-Ds.z,Ds.y,Ws.z,0,-Ws.x,Us.z,0,-Us.x,Ds.z,0,-Ds.x,-Ws.y,Ws.x,0,-Us.y,Us.x,0,-Ds.y,Ds.x,0];return!!Ys(e,Vs,Ls,js,qs)&&(e=[1,0,0,0,1,0,0,0,1],!!Ys(e,Vs,Ls,js,qs)&&(Js.crossVectors(Ws,Us),e=[Js.x,Js.y,Js.z],Ys(e,Vs,Ls,js,qs)))}clampPoint(t,e){return e.copy(t).clamp(this.min,this.max)}distanceToPoint(t){return this.clampPoint(t,Ns).distanceTo(t)}getBoundingSphere(t){return this.isEmpty()?t.makeEmpty():(this.getCenter(t.center),t.radius=.5*this.getSize(Ns).length()),t}intersect(t){return this.min.max(t.min),this.max.min(t.max),this.isEmpty()&&this.makeEmpty(),this}union(t){return this.min.min(t.min),this.max.max(t.max),this}applyMatrix4(t){return this.isEmpty()||(Os[0].set(this.min.x,this.min.y,this.min.z).applyMatrix4(t),Os[1].set(this.min.x,this.min.y,this.max.z).applyMatrix4(t),Os[2].set(this.min.x,this.max.y,this.min.z).applyMatrix4(t),Os[3].set(this.min.x,this.max.y,this.max.z).applyMatrix4(t),Os[4].set(this.max.x,this.min.y,this.min.z).applyMatrix4(t),Os[5].set(this.max.x,this.min.y,this.max.z).applyMatrix4(t),Os[6].set(this.max.x,this.max.y,this.min.z).applyMatrix4(t),Os[7].set(this.max.x,this.max.y,this.max.z).applyMatrix4(t),this.setFromPoints(Os)),this}translate(t){return this.min.add(t),this.max.add(t),this}equals(t){return t.min.equals(this.min)&&t.max.equals(this.max)}toJSON(){return{min:this.min.toArray(),max:this.max.toArray()}}fromJSON(t){return this.min.fromArray(t.min),this.max.fromArray(t.max),this}}const Os=[new Qi,new Qi,new Qi,new Qi,new Qi,new Qi,new Qi,new Qi],Ns=new Qi,Fs=new Ps,Vs=new Qi,Ls=new Qi,js=new Qi,Ws=new Qi,Us=new Qi,Ds=new Qi,Hs=new Qi,qs=new Qi,Js=new Qi,Xs=new Qi;function Ys(t,e,i,s,r){for(let n=0,a=t.length-3;n<=a;n+=3){Xs.fromArray(t,n);const a=r.x*Math.abs(Xs.x)+r.y*Math.abs(Xs.y)+r.z*Math.abs(Xs.z),o=e.dot(Xs),h=i.dot(Xs),l=s.dot(Xs);if(Math.max(-Math.max(o,h,l),Math.min(o,h,l))>a)return!1}return!0}const Zs=new Ps,Gs=new Qi,$s=new Qi;class Qs{constructor(t=new Qi,e=-1){this.isSphere=!0,this.center=t,this.radius=e}set(t,e){return this.center.copy(t),this.radius=e,this}setFromPoints(t,e){const i=this.center;void 0!==e?i.copy(e):Zs.setFromPoints(t).getCenter(i);let s=0;for(let e=0,r=t.length;ethis.radius*this.radius&&(e.sub(this.center).normalize(),e.multiplyScalar(this.radius).add(this.center)),e}getBoundingBox(t){return this.isEmpty()?(t.makeEmpty(),t):(t.set(this.center,this.center),t.expandByScalar(this.radius),t)}applyMatrix4(t){return this.center.applyMatrix4(t),this.radius=this.radius*t.getMaxScaleOnAxis(),this}translate(t){return this.center.add(t),this}expandByPoint(t){if(this.isEmpty())return this.center.copy(t),this.radius=0,this;Gs.subVectors(t,this.center);const e=Gs.lengthSq();if(e>this.radius*this.radius){const t=Math.sqrt(e),i=.5*(t-this.radius);this.center.addScaledVector(Gs,i/t),this.radius+=i}return this}union(t){return t.isEmpty()?this:this.isEmpty()?(this.copy(t),this):(!0===this.center.equals(t.center)?this.radius=Math.max(this.radius,t.radius):($s.subVectors(t.center,this.center).setLength(t.radius),this.expandByPoint(Gs.copy(t.center).add($s)),this.expandByPoint(Gs.copy(t.center).sub($s))),this)}equals(t){return t.center.equals(this.center)&&t.radius===this.radius}clone(){return(new this.constructor).copy(this)}toJSON(){return{radius:this.radius,center:this.center.toArray()}}fromJSON(t){return this.radius=t.radius,this.center.fromArray(t.center),this}}const Ks=new Qi,tr=new Qi,er=new Qi,ir=new Qi,sr=new Qi,rr=new Qi,nr=new Qi;class ar{constructor(t=new Qi,e=new Qi(0,0,-1)){this.origin=t,this.direction=e}set(t,e){return this.origin.copy(t),this.direction.copy(e),this}copy(t){return this.origin.copy(t.origin),this.direction.copy(t.direction),this}at(t,e){return e.copy(this.origin).addScaledVector(this.direction,t)}lookAt(t){return this.direction.copy(t).sub(this.origin).normalize(),this}recast(t){return this.origin.copy(this.at(t,Ks)),this}closestPointToPoint(t,e){e.subVectors(t,this.origin);const i=e.dot(this.direction);return i<0?e.copy(this.origin):e.copy(this.origin).addScaledVector(this.direction,i)}distanceToPoint(t){return Math.sqrt(this.distanceSqToPoint(t))}distanceSqToPoint(t){const e=Ks.subVectors(t,this.origin).dot(this.direction);return e<0?this.origin.distanceToSquared(t):(Ks.copy(this.origin).addScaledVector(this.direction,e),Ks.distanceToSquared(t))}distanceSqToSegment(t,e,i,s){tr.copy(t).add(e).multiplyScalar(.5),er.copy(e).sub(t).normalize(),ir.copy(this.origin).sub(tr);const r=.5*t.distanceTo(e),n=-this.direction.dot(er),a=ir.dot(this.direction),o=-ir.dot(er),h=ir.lengthSq(),l=Math.abs(1-n*n);let c,u,d,p;if(l>0)if(c=n*o-a,u=n*a-o,p=r*l,c>=0)if(u>=-p)if(u<=p){const t=1/l;c*=t,u*=t,d=c*(c+n*u+2*a)+u*(n*c+u+2*o)+h}else u=r,c=Math.max(0,-(n*u+a)),d=-c*c+u*(u+2*o)+h;else u=-r,c=Math.max(0,-(n*u+a)),d=-c*c+u*(u+2*o)+h;else u<=-p?(c=Math.max(0,-(-n*r+a)),u=c>0?-r:Math.min(Math.max(-r,-o),r),d=-c*c+u*(u+2*o)+h):u<=p?(c=0,u=Math.min(Math.max(-r,-o),r),d=u*(u+2*o)+h):(c=Math.max(0,-(n*r+a)),u=c>0?r:Math.min(Math.max(-r,-o),r),d=-c*c+u*(u+2*o)+h);else u=n>0?-r:r,c=Math.max(0,-(n*u+a)),d=-c*c+u*(u+2*o)+h;return i&&i.copy(this.origin).addScaledVector(this.direction,c),s&&s.copy(tr).addScaledVector(er,u),d}intersectSphere(t,e){Ks.subVectors(t.center,this.origin);const i=Ks.dot(this.direction),s=Ks.dot(Ks)-i*i,r=t.radius*t.radius;if(s>r)return null;const n=Math.sqrt(r-s),a=i-n,o=i+n;return o<0?null:a<0?this.at(o,e):this.at(a,e)}intersectsSphere(t){return!(t.radius<0)&&this.distanceSqToPoint(t.center)<=t.radius*t.radius}distanceToPlane(t){const e=t.normal.dot(this.direction);if(0===e)return 0===t.distanceToPoint(this.origin)?0:null;const i=-(this.origin.dot(t.normal)+t.constant)/e;return i>=0?i:null}intersectPlane(t,e){const i=this.distanceToPlane(t);return null===i?null:this.at(i,e)}intersectsPlane(t){const e=t.distanceToPoint(this.origin);if(0===e)return!0;return t.normal.dot(this.direction)*e<0}intersectBox(t,e){let i,s,r,n,a,o;const h=1/this.direction.x,l=1/this.direction.y,c=1/this.direction.z,u=this.origin;return h>=0?(i=(t.min.x-u.x)*h,s=(t.max.x-u.x)*h):(i=(t.max.x-u.x)*h,s=(t.min.x-u.x)*h),l>=0?(r=(t.min.y-u.y)*l,n=(t.max.y-u.y)*l):(r=(t.max.y-u.y)*l,n=(t.min.y-u.y)*l),i>n||r>s?null:((r>i||isNaN(i))&&(i=r),(n=0?(a=(t.min.z-u.z)*c,o=(t.max.z-u.z)*c):(a=(t.max.z-u.z)*c,o=(t.min.z-u.z)*c),i>o||a>s?null:((a>i||i!=i)&&(i=a),(o=0?i:s,e)))}intersectsBox(t){return null!==this.intersectBox(t,Ks)}intersectTriangle(t,e,i,s,r){sr.subVectors(e,t),rr.subVectors(i,t),nr.crossVectors(sr,rr);let n,a=this.direction.dot(nr);if(a>0){if(s)return null;n=1}else{if(!(a<0))return null;n=-1,a=-a}ir.subVectors(this.origin,t);const o=n*this.direction.dot(rr.crossVectors(ir,rr));if(o<0)return null;const h=n*this.direction.dot(sr.cross(ir));if(h<0)return null;if(o+h>a)return null;const l=-n*ir.dot(nr);return l<0?null:this.at(l/a,r)}applyMatrix4(t){return this.origin.applyMatrix4(t),this.direction.transformDirection(t),this}equals(t){return t.origin.equals(this.origin)&&t.direction.equals(this.direction)}clone(){return(new this.constructor).copy(this)}}class or{constructor(t,e,i,s,r,n,a,o,h,l,c,u,d,p,m,y){or.prototype.isMatrix4=!0,this.elements=[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],void 0!==t&&this.set(t,e,i,s,r,n,a,o,h,l,c,u,d,p,m,y)}set(t,e,i,s,r,n,a,o,h,l,c,u,d,p,m,y){const g=this.elements;return g[0]=t,g[4]=e,g[8]=i,g[12]=s,g[1]=r,g[5]=n,g[9]=a,g[13]=o,g[2]=h,g[6]=l,g[10]=c,g[14]=u,g[3]=d,g[7]=p,g[11]=m,g[15]=y,this}identity(){return this.set(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1),this}clone(){return(new or).fromArray(this.elements)}copy(t){const e=this.elements,i=t.elements;return e[0]=i[0],e[1]=i[1],e[2]=i[2],e[3]=i[3],e[4]=i[4],e[5]=i[5],e[6]=i[6],e[7]=i[7],e[8]=i[8],e[9]=i[9],e[10]=i[10],e[11]=i[11],e[12]=i[12],e[13]=i[13],e[14]=i[14],e[15]=i[15],this}copyPosition(t){const e=this.elements,i=t.elements;return e[12]=i[12],e[13]=i[13],e[14]=i[14],this}setFromMatrix3(t){const e=t.elements;return this.set(e[0],e[3],e[6],0,e[1],e[4],e[7],0,e[2],e[5],e[8],0,0,0,0,1),this}extractBasis(t,e,i){return t.setFromMatrixColumn(this,0),e.setFromMatrixColumn(this,1),i.setFromMatrixColumn(this,2),this}makeBasis(t,e,i){return this.set(t.x,e.x,i.x,0,t.y,e.y,i.y,0,t.z,e.z,i.z,0,0,0,0,1),this}extractRotation(t){const e=this.elements,i=t.elements,s=1/hr.setFromMatrixColumn(t,0).length(),r=1/hr.setFromMatrixColumn(t,1).length(),n=1/hr.setFromMatrixColumn(t,2).length();return e[0]=i[0]*s,e[1]=i[1]*s,e[2]=i[2]*s,e[3]=0,e[4]=i[4]*r,e[5]=i[5]*r,e[6]=i[6]*r,e[7]=0,e[8]=i[8]*n,e[9]=i[9]*n,e[10]=i[10]*n,e[11]=0,e[12]=0,e[13]=0,e[14]=0,e[15]=1,this}makeRotationFromEuler(t){const e=this.elements,i=t.x,s=t.y,r=t.z,n=Math.cos(i),a=Math.sin(i),o=Math.cos(s),h=Math.sin(s),l=Math.cos(r),c=Math.sin(r);if("XYZ"===t.order){const t=n*l,i=n*c,s=a*l,r=a*c;e[0]=o*l,e[4]=-o*c,e[8]=h,e[1]=i+s*h,e[5]=t-r*h,e[9]=-a*o,e[2]=r-t*h,e[6]=s+i*h,e[10]=n*o}else if("YXZ"===t.order){const t=o*l,i=o*c,s=h*l,r=h*c;e[0]=t+r*a,e[4]=s*a-i,e[8]=n*h,e[1]=n*c,e[5]=n*l,e[9]=-a,e[2]=i*a-s,e[6]=r+t*a,e[10]=n*o}else if("ZXY"===t.order){const t=o*l,i=o*c,s=h*l,r=h*c;e[0]=t-r*a,e[4]=-n*c,e[8]=s+i*a,e[1]=i+s*a,e[5]=n*l,e[9]=r-t*a,e[2]=-n*h,e[6]=a,e[10]=n*o}else if("ZYX"===t.order){const t=n*l,i=n*c,s=a*l,r=a*c;e[0]=o*l,e[4]=s*h-i,e[8]=t*h+r,e[1]=o*c,e[5]=r*h+t,e[9]=i*h-s,e[2]=-h,e[6]=a*o,e[10]=n*o}else if("YZX"===t.order){const t=n*o,i=n*h,s=a*o,r=a*h;e[0]=o*l,e[4]=r-t*c,e[8]=s*c+i,e[1]=c,e[5]=n*l,e[9]=-a*l,e[2]=-h*l,e[6]=i*c+s,e[10]=t-r*c}else if("XZY"===t.order){const t=n*o,i=n*h,s=a*o,r=a*h;e[0]=o*l,e[4]=-c,e[8]=h*l,e[1]=t*c+r,e[5]=n*l,e[9]=i*c-s,e[2]=s*c-i,e[6]=a*l,e[10]=r*c+t}return e[3]=0,e[7]=0,e[11]=0,e[12]=0,e[13]=0,e[14]=0,e[15]=1,this}makeRotationFromQuaternion(t){return this.compose(cr,t,ur)}lookAt(t,e,i){const s=this.elements;return mr.subVectors(t,e),0===mr.lengthSq()&&(mr.z=1),mr.normalize(),dr.crossVectors(i,mr),0===dr.lengthSq()&&(1===Math.abs(i.z)?mr.x+=1e-4:mr.z+=1e-4,mr.normalize(),dr.crossVectors(i,mr)),dr.normalize(),pr.crossVectors(mr,dr),s[0]=dr.x,s[4]=pr.x,s[8]=mr.x,s[1]=dr.y,s[5]=pr.y,s[9]=mr.y,s[2]=dr.z,s[6]=pr.z,s[10]=mr.z,this}multiply(t){return this.multiplyMatrices(this,t)}premultiply(t){return this.multiplyMatrices(t,this)}multiplyMatrices(t,e){const i=t.elements,s=e.elements,r=this.elements,n=i[0],a=i[4],o=i[8],h=i[12],l=i[1],c=i[5],u=i[9],d=i[13],p=i[2],m=i[6],y=i[10],g=i[14],f=i[3],x=i[7],b=i[11],v=i[15],w=s[0],M=s[4],S=s[8],_=s[12],A=s[1],T=s[5],z=s[9],I=s[13],C=s[2],B=s[6],k=s[10],E=s[14],R=s[3],P=s[7],O=s[11],N=s[15];return r[0]=n*w+a*A+o*C+h*R,r[4]=n*M+a*T+o*B+h*P,r[8]=n*S+a*z+o*k+h*O,r[12]=n*_+a*I+o*E+h*N,r[1]=l*w+c*A+u*C+d*R,r[5]=l*M+c*T+u*B+d*P,r[9]=l*S+c*z+u*k+d*O,r[13]=l*_+c*I+u*E+d*N,r[2]=p*w+m*A+y*C+g*R,r[6]=p*M+m*T+y*B+g*P,r[10]=p*S+m*z+y*k+g*O,r[14]=p*_+m*I+y*E+g*N,r[3]=f*w+x*A+b*C+v*R,r[7]=f*M+x*T+b*B+v*P,r[11]=f*S+x*z+b*k+v*O,r[15]=f*_+x*I+b*E+v*N,this}multiplyScalar(t){const e=this.elements;return e[0]*=t,e[4]*=t,e[8]*=t,e[12]*=t,e[1]*=t,e[5]*=t,e[9]*=t,e[13]*=t,e[2]*=t,e[6]*=t,e[10]*=t,e[14]*=t,e[3]*=t,e[7]*=t,e[11]*=t,e[15]*=t,this}determinant(){const t=this.elements,e=t[0],i=t[4],s=t[8],r=t[12],n=t[1],a=t[5],o=t[9],h=t[13],l=t[2],c=t[6],u=t[10],d=t[14];return t[3]*(+r*o*c-s*h*c-r*a*u+i*h*u+s*a*d-i*o*d)+t[7]*(+e*o*d-e*h*u+r*n*u-s*n*d+s*h*l-r*o*l)+t[11]*(+e*h*c-e*a*d-r*n*c+i*n*d+r*a*l-i*h*l)+t[15]*(-s*a*l-e*o*c+e*a*u+s*n*c-i*n*u+i*o*l)}transpose(){const t=this.elements;let e;return e=t[1],t[1]=t[4],t[4]=e,e=t[2],t[2]=t[8],t[8]=e,e=t[6],t[6]=t[9],t[9]=e,e=t[3],t[3]=t[12],t[12]=e,e=t[7],t[7]=t[13],t[13]=e,e=t[11],t[11]=t[14],t[14]=e,this}setPosition(t,e,i){const s=this.elements;return t.isVector3?(s[12]=t.x,s[13]=t.y,s[14]=t.z):(s[12]=t,s[13]=e,s[14]=i),this}invert(){const t=this.elements,e=t[0],i=t[1],s=t[2],r=t[3],n=t[4],a=t[5],o=t[6],h=t[7],l=t[8],c=t[9],u=t[10],d=t[11],p=t[12],m=t[13],y=t[14],g=t[15],f=c*y*h-m*u*h+m*o*d-a*y*d-c*o*g+a*u*g,x=p*u*h-l*y*h-p*o*d+n*y*d+l*o*g-n*u*g,b=l*m*h-p*c*h+p*a*d-n*m*d-l*a*g+n*c*g,v=p*c*o-l*m*o-p*a*u+n*m*u+l*a*y-n*c*y,w=e*f+i*x+s*b+r*v;if(0===w)return this.set(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);const M=1/w;return t[0]=f*M,t[1]=(m*u*r-c*y*r-m*s*d+i*y*d+c*s*g-i*u*g)*M,t[2]=(a*y*r-m*o*r+m*s*h-i*y*h-a*s*g+i*o*g)*M,t[3]=(c*o*r-a*u*r-c*s*h+i*u*h+a*s*d-i*o*d)*M,t[4]=x*M,t[5]=(l*y*r-p*u*r+p*s*d-e*y*d-l*s*g+e*u*g)*M,t[6]=(p*o*r-n*y*r-p*s*h+e*y*h+n*s*g-e*o*g)*M,t[7]=(n*u*r-l*o*r+l*s*h-e*u*h-n*s*d+e*o*d)*M,t[8]=b*M,t[9]=(p*c*r-l*m*r-p*i*d+e*m*d+l*i*g-e*c*g)*M,t[10]=(n*m*r-p*a*r+p*i*h-e*m*h-n*i*g+e*a*g)*M,t[11]=(l*a*r-n*c*r-l*i*h+e*c*h+n*i*d-e*a*d)*M,t[12]=v*M,t[13]=(l*m*s-p*c*s+p*i*u-e*m*u-l*i*y+e*c*y)*M,t[14]=(p*a*s-n*m*s-p*i*o+e*m*o+n*i*y-e*a*y)*M,t[15]=(n*c*s-l*a*s+l*i*o-e*c*o-n*i*u+e*a*u)*M,this}scale(t){const e=this.elements,i=t.x,s=t.y,r=t.z;return e[0]*=i,e[4]*=s,e[8]*=r,e[1]*=i,e[5]*=s,e[9]*=r,e[2]*=i,e[6]*=s,e[10]*=r,e[3]*=i,e[7]*=s,e[11]*=r,this}getMaxScaleOnAxis(){const t=this.elements,e=t[0]*t[0]+t[1]*t[1]+t[2]*t[2],i=t[4]*t[4]+t[5]*t[5]+t[6]*t[6],s=t[8]*t[8]+t[9]*t[9]+t[10]*t[10];return Math.sqrt(Math.max(e,i,s))}makeTranslation(t,e,i){return t.isVector3?this.set(1,0,0,t.x,0,1,0,t.y,0,0,1,t.z,0,0,0,1):this.set(1,0,0,t,0,1,0,e,0,0,1,i,0,0,0,1),this}makeRotationX(t){const e=Math.cos(t),i=Math.sin(t);return this.set(1,0,0,0,0,e,-i,0,0,i,e,0,0,0,0,1),this}makeRotationY(t){const e=Math.cos(t),i=Math.sin(t);return this.set(e,0,i,0,0,1,0,0,-i,0,e,0,0,0,0,1),this}makeRotationZ(t){const e=Math.cos(t),i=Math.sin(t);return this.set(e,-i,0,0,i,e,0,0,0,0,1,0,0,0,0,1),this}makeRotationAxis(t,e){const i=Math.cos(e),s=Math.sin(e),r=1-i,n=t.x,a=t.y,o=t.z,h=r*n,l=r*a;return this.set(h*n+i,h*a-s*o,h*o+s*a,0,h*a+s*o,l*a+i,l*o-s*n,0,h*o-s*a,l*o+s*n,r*o*o+i,0,0,0,0,1),this}makeScale(t,e,i){return this.set(t,0,0,0,0,e,0,0,0,0,i,0,0,0,0,1),this}makeShear(t,e,i,s,r,n){return this.set(1,i,r,0,t,1,n,0,e,s,1,0,0,0,0,1),this}compose(t,e,i){const s=this.elements,r=e._x,n=e._y,a=e._z,o=e._w,h=r+r,l=n+n,c=a+a,u=r*h,d=r*l,p=r*c,m=n*l,y=n*c,g=a*c,f=o*h,x=o*l,b=o*c,v=i.x,w=i.y,M=i.z;return s[0]=(1-(m+g))*v,s[1]=(d+b)*v,s[2]=(p-x)*v,s[3]=0,s[4]=(d-b)*w,s[5]=(1-(u+g))*w,s[6]=(y+f)*w,s[7]=0,s[8]=(p+x)*M,s[9]=(y-f)*M,s[10]=(1-(u+m))*M,s[11]=0,s[12]=t.x,s[13]=t.y,s[14]=t.z,s[15]=1,this}decompose(t,e,i){const s=this.elements;let r=hr.set(s[0],s[1],s[2]).length();const n=hr.set(s[4],s[5],s[6]).length(),a=hr.set(s[8],s[9],s[10]).length();this.determinant()<0&&(r=-r),t.x=s[12],t.y=s[13],t.z=s[14],lr.copy(this);const o=1/r,h=1/n,l=1/a;return lr.elements[0]*=o,lr.elements[1]*=o,lr.elements[2]*=o,lr.elements[4]*=h,lr.elements[5]*=h,lr.elements[6]*=h,lr.elements[8]*=l,lr.elements[9]*=l,lr.elements[10]*=l,e.setFromRotationMatrix(lr),i.x=r,i.y=n,i.z=a,this}makePerspective(t,e,i,s,r,n,a=2e3){const o=this.elements,h=2*r/(e-t),l=2*r/(i-s),c=(e+t)/(e-t),u=(i+s)/(i-s);let d,p;if(a===Ri)d=-(n+r)/(n-r),p=-2*n*r/(n-r);else{if(a!==Pi)throw new Error("THREE.Matrix4.makePerspective(): Invalid coordinate system: "+a);d=-n/(n-r),p=-n*r/(n-r)}return o[0]=h,o[4]=0,o[8]=c,o[12]=0,o[1]=0,o[5]=l,o[9]=u,o[13]=0,o[2]=0,o[6]=0,o[10]=d,o[14]=p,o[3]=0,o[7]=0,o[11]=-1,o[15]=0,this}makeOrthographic(t,e,i,s,r,n,a=2e3){const o=this.elements,h=1/(e-t),l=1/(i-s),c=1/(n-r),u=(e+t)*h,d=(i+s)*l;let p,m;if(a===Ri)p=(n+r)*c,m=-2*c;else{if(a!==Pi)throw new Error("THREE.Matrix4.makeOrthographic(): Invalid coordinate system: "+a);p=r*c,m=-1*c}return o[0]=2*h,o[4]=0,o[8]=0,o[12]=-u,o[1]=0,o[5]=2*l,o[9]=0,o[13]=-d,o[2]=0,o[6]=0,o[10]=m,o[14]=-p,o[3]=0,o[7]=0,o[11]=0,o[15]=1,this}equals(t){const e=this.elements,i=t.elements;for(let t=0;t<16;t++)if(e[t]!==i[t])return!1;return!0}fromArray(t,e=0){for(let i=0;i<16;i++)this.elements[i]=t[i+e];return this}toArray(t=[],e=0){const i=this.elements;return t[e]=i[0],t[e+1]=i[1],t[e+2]=i[2],t[e+3]=i[3],t[e+4]=i[4],t[e+5]=i[5],t[e+6]=i[6],t[e+7]=i[7],t[e+8]=i[8],t[e+9]=i[9],t[e+10]=i[10],t[e+11]=i[11],t[e+12]=i[12],t[e+13]=i[13],t[e+14]=i[14],t[e+15]=i[15],t}}const hr=new Qi,lr=new or,cr=new Qi(0,0,0),ur=new Qi(1,1,1),dr=new Qi,pr=new Qi,mr=new Qi,yr=new or,gr=new $i;class fr{constructor(t=0,e=0,i=0,s=fr.DEFAULT_ORDER){this.isEuler=!0,this._x=t,this._y=e,this._z=i,this._order=s}get x(){return this._x}set x(t){this._x=t,this._onChangeCallback()}get y(){return this._y}set y(t){this._y=t,this._onChangeCallback()}get z(){return this._z}set z(t){this._z=t,this._onChangeCallback()}get order(){return this._order}set order(t){this._order=t,this._onChangeCallback()}set(t,e,i,s=this._order){return this._x=t,this._y=e,this._z=i,this._order=s,this._onChangeCallback(),this}clone(){return new this.constructor(this._x,this._y,this._z,this._order)}copy(t){return this._x=t._x,this._y=t._y,this._z=t._z,this._order=t._order,this._onChangeCallback(),this}setFromRotationMatrix(t,e=this._order,i=!0){const s=t.elements,r=s[0],n=s[4],a=s[8],o=s[1],h=s[5],l=s[9],c=s[2],u=s[6],d=s[10];switch(e){case"XYZ":this._y=Math.asin(Hi(a,-1,1)),Math.abs(a)<.9999999?(this._x=Math.atan2(-l,d),this._z=Math.atan2(-n,r)):(this._x=Math.atan2(u,h),this._z=0);break;case"YXZ":this._x=Math.asin(-Hi(l,-1,1)),Math.abs(l)<.9999999?(this._y=Math.atan2(a,d),this._z=Math.atan2(o,h)):(this._y=Math.atan2(-c,r),this._z=0);break;case"ZXY":this._x=Math.asin(Hi(u,-1,1)),Math.abs(u)<.9999999?(this._y=Math.atan2(-c,d),this._z=Math.atan2(-n,h)):(this._y=0,this._z=Math.atan2(o,r));break;case"ZYX":this._y=Math.asin(-Hi(c,-1,1)),Math.abs(c)<.9999999?(this._x=Math.atan2(u,d),this._z=Math.atan2(o,r)):(this._x=0,this._z=Math.atan2(-n,h));break;case"YZX":this._z=Math.asin(Hi(o,-1,1)),Math.abs(o)<.9999999?(this._x=Math.atan2(-l,h),this._y=Math.atan2(-c,r)):(this._x=0,this._y=Math.atan2(a,d));break;case"XZY":this._z=Math.asin(-Hi(n,-1,1)),Math.abs(n)<.9999999?(this._x=Math.atan2(u,h),this._y=Math.atan2(a,r)):(this._x=Math.atan2(-l,d),this._y=0);break;default:console.warn("THREE.Euler: .setFromRotationMatrix() encountered an unknown order: "+e)}return this._order=e,!0===i&&this._onChangeCallback(),this}setFromQuaternion(t,e,i){return yr.makeRotationFromQuaternion(t),this.setFromRotationMatrix(yr,e,i)}setFromVector3(t,e=this._order){return this.set(t.x,t.y,t.z,e)}reorder(t){return gr.setFromEuler(this),this.setFromQuaternion(gr,t)}equals(t){return t._x===this._x&&t._y===this._y&&t._z===this._z&&t._order===this._order}fromArray(t){return this._x=t[0],this._y=t[1],this._z=t[2],void 0!==t[3]&&(this._order=t[3]),this._onChangeCallback(),this}toArray(t=[],e=0){return t[e]=this._x,t[e+1]=this._y,t[e+2]=this._z,t[e+3]=this._order,t}_onChange(t){return this._onChangeCallback=t,this}_onChangeCallback(){}*[Symbol.iterator](){yield this._x,yield this._y,yield this._z,yield this._order}}fr.DEFAULT_ORDER="XYZ";class xr{constructor(){this.mask=1}set(t){this.mask=1<>>0}enable(t){this.mask|=1<1){for(let t=0;t1){for(let t=0;t0&&(s.userData=this.userData),s.layers=this.layers.mask,s.matrix=this.matrix.toArray(),s.up=this.up.toArray(),!1===this.matrixAutoUpdate&&(s.matrixAutoUpdate=!1),this.isInstancedMesh&&(s.type="InstancedMesh",s.count=this.count,s.instanceMatrix=this.instanceMatrix.toJSON(),null!==this.instanceColor&&(s.instanceColor=this.instanceColor.toJSON())),this.isBatchedMesh&&(s.type="BatchedMesh",s.perObjectFrustumCulled=this.perObjectFrustumCulled,s.sortObjects=this.sortObjects,s.drawRanges=this._drawRanges,s.reservedRanges=this._reservedRanges,s.geometryInfo=this._geometryInfo.map(t=>({...t,boundingBox:t.boundingBox?t.boundingBox.toJSON():void 0,boundingSphere:t.boundingSphere?t.boundingSphere.toJSON():void 0})),s.instanceInfo=this._instanceInfo.map(t=>({...t})),s.availableInstanceIds=this._availableInstanceIds.slice(),s.availableGeometryIds=this._availableGeometryIds.slice(),s.nextIndexStart=this._nextIndexStart,s.nextVertexStart=this._nextVertexStart,s.geometryCount=this._geometryCount,s.maxInstanceCount=this._maxInstanceCount,s.maxVertexCount=this._maxVertexCount,s.maxIndexCount=this._maxIndexCount,s.geometryInitialized=this._geometryInitialized,s.matricesTexture=this._matricesTexture.toJSON(t),s.indirectTexture=this._indirectTexture.toJSON(t),null!==this._colorsTexture&&(s.colorsTexture=this._colorsTexture.toJSON(t)),null!==this.boundingSphere&&(s.boundingSphere=this.boundingSphere.toJSON()),null!==this.boundingBox&&(s.boundingBox=this.boundingBox.toJSON())),this.isScene)this.background&&(this.background.isColor?s.background=this.background.toJSON():this.background.isTexture&&(s.background=this.background.toJSON(t).uuid)),this.environment&&this.environment.isTexture&&!0!==this.environment.isRenderTargetTexture&&(s.environment=this.environment.toJSON(t).uuid);else if(this.isMesh||this.isLine||this.isPoints){s.geometry=r(t.geometries,this.geometry);const e=this.geometry.parameters;if(void 0!==e&&void 0!==e.shapes){const i=e.shapes;if(Array.isArray(i))for(let e=0,s=i.length;e0){s.children=[];for(let e=0;e0){s.animations=[];for(let e=0;e0&&(i.geometries=e),s.length>0&&(i.materials=s),r.length>0&&(i.textures=r),a.length>0&&(i.images=a),o.length>0&&(i.shapes=o),h.length>0&&(i.skeletons=h),l.length>0&&(i.animations=l),c.length>0&&(i.nodes=c)}return i.object=s,i;function n(t){const e=[];for(const i in t){const s=t[i];delete s.metadata,e.push(s)}return e}}clone(t){return(new this.constructor).copy(this,t)}copy(t,e=!0){if(this.name=t.name,this.up.copy(t.up),this.position.copy(t.position),this.rotation.order=t.rotation.order,this.quaternion.copy(t.quaternion),this.scale.copy(t.scale),this.matrix.copy(t.matrix),this.matrixWorld.copy(t.matrixWorld),this.matrixAutoUpdate=t.matrixAutoUpdate,this.matrixWorldAutoUpdate=t.matrixWorldAutoUpdate,this.matrixWorldNeedsUpdate=t.matrixWorldNeedsUpdate,this.layers.mask=t.layers.mask,this.visible=t.visible,this.castShadow=t.castShadow,this.receiveShadow=t.receiveShadow,this.frustumCulled=t.frustumCulled,this.renderOrder=t.renderOrder,this.animations=t.animations.slice(),this.userData=JSON.parse(JSON.stringify(t.userData)),!0===e)for(let e=0;e0?s.multiplyScalar(1/Math.sqrt(r)):s.set(0,0,0)}static getBarycoord(t,e,i,s,r){Or.subVectors(s,e),Nr.subVectors(i,e),Fr.subVectors(t,e);const n=Or.dot(Or),a=Or.dot(Nr),o=Or.dot(Fr),h=Nr.dot(Nr),l=Nr.dot(Fr),c=n*h-a*a;if(0===c)return r.set(0,0,0),null;const u=1/c,d=(h*o-a*l)*u,p=(n*l-a*o)*u;return r.set(1-d-p,p,d)}static containsPoint(t,e,i,s){return null!==this.getBarycoord(t,e,i,s,Vr)&&(Vr.x>=0&&Vr.y>=0&&Vr.x+Vr.y<=1)}static getInterpolation(t,e,i,s,r,n,a,o){return null===this.getBarycoord(t,e,i,s,Vr)?(o.x=0,o.y=0,"z"in o&&(o.z=0),"w"in o&&(o.w=0),null):(o.setScalar(0),o.addScaledVector(r,Vr.x),o.addScaledVector(n,Vr.y),o.addScaledVector(a,Vr.z),o)}static getInterpolatedAttribute(t,e,i,s,r,n){return qr.setScalar(0),Jr.setScalar(0),Xr.setScalar(0),qr.fromBufferAttribute(t,e),Jr.fromBufferAttribute(t,i),Xr.fromBufferAttribute(t,s),n.setScalar(0),n.addScaledVector(qr,r.x),n.addScaledVector(Jr,r.y),n.addScaledVector(Xr,r.z),n}static isFrontFacing(t,e,i,s){return Or.subVectors(i,e),Nr.subVectors(t,e),Or.cross(Nr).dot(s)<0}set(t,e,i){return this.a.copy(t),this.b.copy(e),this.c.copy(i),this}setFromPointsAndIndices(t,e,i,s){return this.a.copy(t[e]),this.b.copy(t[i]),this.c.copy(t[s]),this}setFromAttributeAndIndices(t,e,i,s){return this.a.fromBufferAttribute(t,e),this.b.fromBufferAttribute(t,i),this.c.fromBufferAttribute(t,s),this}clone(){return(new this.constructor).copy(this)}copy(t){return this.a.copy(t.a),this.b.copy(t.b),this.c.copy(t.c),this}getArea(){return Or.subVectors(this.c,this.b),Nr.subVectors(this.a,this.b),.5*Or.cross(Nr).length()}getMidpoint(t){return t.addVectors(this.a,this.b).add(this.c).multiplyScalar(1/3)}getNormal(t){return Yr.getNormal(this.a,this.b,this.c,t)}getPlane(t){return t.setFromCoplanarPoints(this.a,this.b,this.c)}getBarycoord(t,e){return Yr.getBarycoord(t,this.a,this.b,this.c,e)}getInterpolation(t,e,i,s,r){return Yr.getInterpolation(t,this.a,this.b,this.c,e,i,s,r)}containsPoint(t){return Yr.containsPoint(t,this.a,this.b,this.c)}isFrontFacing(t){return Yr.isFrontFacing(this.a,this.b,this.c,t)}intersectsBox(t){return t.intersectsTriangle(this)}closestPointToPoint(t,e){const i=this.a,s=this.b,r=this.c;let n,a;Lr.subVectors(s,i),jr.subVectors(r,i),Ur.subVectors(t,i);const o=Lr.dot(Ur),h=jr.dot(Ur);if(o<=0&&h<=0)return e.copy(i);Dr.subVectors(t,s);const l=Lr.dot(Dr),c=jr.dot(Dr);if(l>=0&&c<=l)return e.copy(s);const u=o*c-l*h;if(u<=0&&o>=0&&l<=0)return n=o/(o-l),e.copy(i).addScaledVector(Lr,n);Hr.subVectors(t,r);const d=Lr.dot(Hr),p=jr.dot(Hr);if(p>=0&&d<=p)return e.copy(r);const m=d*h-o*p;if(m<=0&&h>=0&&p<=0)return a=h/(h-p),e.copy(i).addScaledVector(jr,a);const y=l*p-d*c;if(y<=0&&c-l>=0&&d-p>=0)return Wr.subVectors(r,s),a=(c-l)/(c-l+(d-p)),e.copy(s).addScaledVector(Wr,a);const g=1/(y+m+u);return n=m*g,a=u*g,e.copy(i).addScaledVector(Lr,n).addScaledVector(jr,a)}equals(t){return t.a.equals(this.a)&&t.b.equals(this.b)&&t.c.equals(this.c)}}const Zr={aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074},Gr={h:0,s:0,l:0},$r={h:0,s:0,l:0};function Qr(t,e,i){return i<0&&(i+=1),i>1&&(i-=1),i<1/6?t+6*(e-t)*i:i<.5?e:i<2/3?t+6*(e-t)*(2/3-i):t}class Kr{constructor(t,e,i){return this.isColor=!0,this.r=1,this.g=1,this.b=1,this.set(t,e,i)}set(t,e,i){if(void 0===e&&void 0===i){const e=t;e&&e.isColor?this.copy(e):"number"==typeof e?this.setHex(e):"string"==typeof e&&this.setStyle(e)}else this.setRGB(t,e,i);return this}setScalar(t){return this.r=t,this.g=t,this.b=t,this}setHex(t,e=Ye){return t=Math.floor(t),this.r=(t>>16&255)/255,this.g=(t>>8&255)/255,this.b=(255&t)/255,gs.colorSpaceToWorking(this,e),this}setRGB(t,e,i,s=gs.workingColorSpace){return this.r=t,this.g=e,this.b=i,gs.colorSpaceToWorking(this,s),this}setHSL(t,e,i,s=gs.workingColorSpace){if(t=qi(t,1),e=Hi(e,0,1),i=Hi(i,0,1),0===e)this.r=this.g=this.b=i;else{const s=i<=.5?i*(1+e):i+e-i*e,r=2*i-s;this.r=Qr(r,s,t+1/3),this.g=Qr(r,s,t),this.b=Qr(r,s,t-1/3)}return gs.colorSpaceToWorking(this,s),this}setStyle(t,e=Ye){function i(e){void 0!==e&&parseFloat(e)<1&&console.warn("THREE.Color: Alpha component of "+t+" will be ignored.")}let s;if(s=/^(\w+)\(([^\)]*)\)/.exec(t)){let r;const n=s[1],a=s[2];switch(n){case"rgb":case"rgba":if(r=/^\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(a))return i(r[4]),this.setRGB(Math.min(255,parseInt(r[1],10))/255,Math.min(255,parseInt(r[2],10))/255,Math.min(255,parseInt(r[3],10))/255,e);if(r=/^\s*(\d+)\%\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(a))return i(r[4]),this.setRGB(Math.min(100,parseInt(r[1],10))/100,Math.min(100,parseInt(r[2],10))/100,Math.min(100,parseInt(r[3],10))/100,e);break;case"hsl":case"hsla":if(r=/^\s*(\d*\.?\d+)\s*,\s*(\d*\.?\d+)\%\s*,\s*(\d*\.?\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(a))return i(r[4]),this.setHSL(parseFloat(r[1])/360,parseFloat(r[2])/100,parseFloat(r[3])/100,e);break;default:console.warn("THREE.Color: Unknown color model "+t)}}else if(s=/^\#([A-Fa-f\d]+)$/.exec(t)){const i=s[1],r=i.length;if(3===r)return this.setRGB(parseInt(i.charAt(0),16)/15,parseInt(i.charAt(1),16)/15,parseInt(i.charAt(2),16)/15,e);if(6===r)return this.setHex(parseInt(i,16),e);console.warn("THREE.Color: Invalid hex color "+t)}else if(t&&t.length>0)return this.setColorName(t,e);return this}setColorName(t,e=Ye){const i=Zr[t.toLowerCase()];return void 0!==i?this.setHex(i,e):console.warn("THREE.Color: Unknown color "+t),this}clone(){return new this.constructor(this.r,this.g,this.b)}copy(t){return this.r=t.r,this.g=t.g,this.b=t.b,this}copySRGBToLinear(t){return this.r=fs(t.r),this.g=fs(t.g),this.b=fs(t.b),this}copyLinearToSRGB(t){return this.r=xs(t.r),this.g=xs(t.g),this.b=xs(t.b),this}convertSRGBToLinear(){return this.copySRGBToLinear(this),this}convertLinearToSRGB(){return this.copyLinearToSRGB(this),this}getHex(t=Ye){return gs.workingToColorSpace(tn.copy(this),t),65536*Math.round(Hi(255*tn.r,0,255))+256*Math.round(Hi(255*tn.g,0,255))+Math.round(Hi(255*tn.b,0,255))}getHexString(t=Ye){return("000000"+this.getHex(t).toString(16)).slice(-6)}getHSL(t,e=gs.workingColorSpace){gs.workingToColorSpace(tn.copy(this),e);const i=tn.r,s=tn.g,r=tn.b,n=Math.max(i,s,r),a=Math.min(i,s,r);let o,h;const l=(a+n)/2;if(a===n)o=0,h=0;else{const t=n-a;switch(h=l<=.5?t/(n+a):t/(2-n-a),n){case i:o=(s-r)/t+(s0!=t>0&&this.version++,this._alphaTest=t}onBeforeRender(){}onBeforeCompile(){}customProgramCacheKey(){return this.onBeforeCompile.toString()}setValues(t){if(void 0!==t)for(const e in t){const i=t[e];if(void 0===i){console.warn(`THREE.Material: parameter '${e}' has value of undefined.`);continue}const s=this[e];void 0!==s?s&&s.isColor?s.set(i):s&&s.isVector3&&i&&i.isVector3?s.copy(i):this[e]=i:console.warn(`THREE.Material: '${e}' is not a property of THREE.${this.type}.`)}}toJSON(t){const e=void 0===t||"string"==typeof t;e&&(t={textures:{},images:{}});const i={metadata:{version:4.7,type:"Material",generator:"Material.toJSON"}};function s(t){const e=[];for(const i in t){const s=t[i];delete s.metadata,e.push(s)}return e}if(i.uuid=this.uuid,i.type=this.type,""!==this.name&&(i.name=this.name),this.color&&this.color.isColor&&(i.color=this.color.getHex()),void 0!==this.roughness&&(i.roughness=this.roughness),void 0!==this.metalness&&(i.metalness=this.metalness),void 0!==this.sheen&&(i.sheen=this.sheen),this.sheenColor&&this.sheenColor.isColor&&(i.sheenColor=this.sheenColor.getHex()),void 0!==this.sheenRoughness&&(i.sheenRoughness=this.sheenRoughness),this.emissive&&this.emissive.isColor&&(i.emissive=this.emissive.getHex()),void 0!==this.emissiveIntensity&&1!==this.emissiveIntensity&&(i.emissiveIntensity=this.emissiveIntensity),this.specular&&this.specular.isColor&&(i.specular=this.specular.getHex()),void 0!==this.specularIntensity&&(i.specularIntensity=this.specularIntensity),this.specularColor&&this.specularColor.isColor&&(i.specularColor=this.specularColor.getHex()),void 0!==this.shininess&&(i.shininess=this.shininess),void 0!==this.clearcoat&&(i.clearcoat=this.clearcoat),void 0!==this.clearcoatRoughness&&(i.clearcoatRoughness=this.clearcoatRoughness),this.clearcoatMap&&this.clearcoatMap.isTexture&&(i.clearcoatMap=this.clearcoatMap.toJSON(t).uuid),this.clearcoatRoughnessMap&&this.clearcoatRoughnessMap.isTexture&&(i.clearcoatRoughnessMap=this.clearcoatRoughnessMap.toJSON(t).uuid),this.clearcoatNormalMap&&this.clearcoatNormalMap.isTexture&&(i.clearcoatNormalMap=this.clearcoatNormalMap.toJSON(t).uuid,i.clearcoatNormalScale=this.clearcoatNormalScale.toArray()),void 0!==this.dispersion&&(i.dispersion=this.dispersion),void 0!==this.iridescence&&(i.iridescence=this.iridescence),void 0!==this.iridescenceIOR&&(i.iridescenceIOR=this.iridescenceIOR),void 0!==this.iridescenceThicknessRange&&(i.iridescenceThicknessRange=this.iridescenceThicknessRange),this.iridescenceMap&&this.iridescenceMap.isTexture&&(i.iridescenceMap=this.iridescenceMap.toJSON(t).uuid),this.iridescenceThicknessMap&&this.iridescenceThicknessMap.isTexture&&(i.iridescenceThicknessMap=this.iridescenceThicknessMap.toJSON(t).uuid),void 0!==this.anisotropy&&(i.anisotropy=this.anisotropy),void 0!==this.anisotropyRotation&&(i.anisotropyRotation=this.anisotropyRotation),this.anisotropyMap&&this.anisotropyMap.isTexture&&(i.anisotropyMap=this.anisotropyMap.toJSON(t).uuid),this.map&&this.map.isTexture&&(i.map=this.map.toJSON(t).uuid),this.matcap&&this.matcap.isTexture&&(i.matcap=this.matcap.toJSON(t).uuid),this.alphaMap&&this.alphaMap.isTexture&&(i.alphaMap=this.alphaMap.toJSON(t).uuid),this.lightMap&&this.lightMap.isTexture&&(i.lightMap=this.lightMap.toJSON(t).uuid,i.lightMapIntensity=this.lightMapIntensity),this.aoMap&&this.aoMap.isTexture&&(i.aoMap=this.aoMap.toJSON(t).uuid,i.aoMapIntensity=this.aoMapIntensity),this.bumpMap&&this.bumpMap.isTexture&&(i.bumpMap=this.bumpMap.toJSON(t).uuid,i.bumpScale=this.bumpScale),this.normalMap&&this.normalMap.isTexture&&(i.normalMap=this.normalMap.toJSON(t).uuid,i.normalMapType=this.normalMapType,i.normalScale=this.normalScale.toArray()),this.displacementMap&&this.displacementMap.isTexture&&(i.displacementMap=this.displacementMap.toJSON(t).uuid,i.displacementScale=this.displacementScale,i.displacementBias=this.displacementBias),this.roughnessMap&&this.roughnessMap.isTexture&&(i.roughnessMap=this.roughnessMap.toJSON(t).uuid),this.metalnessMap&&this.metalnessMap.isTexture&&(i.metalnessMap=this.metalnessMap.toJSON(t).uuid),this.emissiveMap&&this.emissiveMap.isTexture&&(i.emissiveMap=this.emissiveMap.toJSON(t).uuid),this.specularMap&&this.specularMap.isTexture&&(i.specularMap=this.specularMap.toJSON(t).uuid),this.specularIntensityMap&&this.specularIntensityMap.isTexture&&(i.specularIntensityMap=this.specularIntensityMap.toJSON(t).uuid),this.specularColorMap&&this.specularColorMap.isTexture&&(i.specularColorMap=this.specularColorMap.toJSON(t).uuid),this.envMap&&this.envMap.isTexture&&(i.envMap=this.envMap.toJSON(t).uuid,void 0!==this.combine&&(i.combine=this.combine)),void 0!==this.envMapRotation&&(i.envMapRotation=this.envMapRotation.toArray()),void 0!==this.envMapIntensity&&(i.envMapIntensity=this.envMapIntensity),void 0!==this.reflectivity&&(i.reflectivity=this.reflectivity),void 0!==this.refractionRatio&&(i.refractionRatio=this.refractionRatio),this.gradientMap&&this.gradientMap.isTexture&&(i.gradientMap=this.gradientMap.toJSON(t).uuid),void 0!==this.transmission&&(i.transmission=this.transmission),this.transmissionMap&&this.transmissionMap.isTexture&&(i.transmissionMap=this.transmissionMap.toJSON(t).uuid),void 0!==this.thickness&&(i.thickness=this.thickness),this.thicknessMap&&this.thicknessMap.isTexture&&(i.thicknessMap=this.thicknessMap.toJSON(t).uuid),void 0!==this.attenuationDistance&&this.attenuationDistance!==1/0&&(i.attenuationDistance=this.attenuationDistance),void 0!==this.attenuationColor&&(i.attenuationColor=this.attenuationColor.getHex()),void 0!==this.size&&(i.size=this.size),null!==this.shadowSide&&(i.shadowSide=this.shadowSide),void 0!==this.sizeAttenuation&&(i.sizeAttenuation=this.sizeAttenuation),1!==this.blending&&(i.blending=this.blending),0!==this.side&&(i.side=this.side),!0===this.vertexColors&&(i.vertexColors=!0),this.opacity<1&&(i.opacity=this.opacity),!0===this.transparent&&(i.transparent=!0),204!==this.blendSrc&&(i.blendSrc=this.blendSrc),205!==this.blendDst&&(i.blendDst=this.blendDst),100!==this.blendEquation&&(i.blendEquation=this.blendEquation),null!==this.blendSrcAlpha&&(i.blendSrcAlpha=this.blendSrcAlpha),null!==this.blendDstAlpha&&(i.blendDstAlpha=this.blendDstAlpha),null!==this.blendEquationAlpha&&(i.blendEquationAlpha=this.blendEquationAlpha),this.blendColor&&this.blendColor.isColor&&(i.blendColor=this.blendColor.getHex()),0!==this.blendAlpha&&(i.blendAlpha=this.blendAlpha),3!==this.depthFunc&&(i.depthFunc=this.depthFunc),!1===this.depthTest&&(i.depthTest=this.depthTest),!1===this.depthWrite&&(i.depthWrite=this.depthWrite),!1===this.colorWrite&&(i.colorWrite=this.colorWrite),255!==this.stencilWriteMask&&(i.stencilWriteMask=this.stencilWriteMask),519!==this.stencilFunc&&(i.stencilFunc=this.stencilFunc),0!==this.stencilRef&&(i.stencilRef=this.stencilRef),255!==this.stencilFuncMask&&(i.stencilFuncMask=this.stencilFuncMask),this.stencilFail!==Ke&&(i.stencilFail=this.stencilFail),this.stencilZFail!==Ke&&(i.stencilZFail=this.stencilZFail),this.stencilZPass!==Ke&&(i.stencilZPass=this.stencilZPass),!0===this.stencilWrite&&(i.stencilWrite=this.stencilWrite),void 0!==this.rotation&&0!==this.rotation&&(i.rotation=this.rotation),!0===this.polygonOffset&&(i.polygonOffset=!0),0!==this.polygonOffsetFactor&&(i.polygonOffsetFactor=this.polygonOffsetFactor),0!==this.polygonOffsetUnits&&(i.polygonOffsetUnits=this.polygonOffsetUnits),void 0!==this.linewidth&&1!==this.linewidth&&(i.linewidth=this.linewidth),void 0!==this.dashSize&&(i.dashSize=this.dashSize),void 0!==this.gapSize&&(i.gapSize=this.gapSize),void 0!==this.scale&&(i.scale=this.scale),!0===this.dithering&&(i.dithering=!0),this.alphaTest>0&&(i.alphaTest=this.alphaTest),!0===this.alphaHash&&(i.alphaHash=!0),!0===this.alphaToCoverage&&(i.alphaToCoverage=!0),!0===this.premultipliedAlpha&&(i.premultipliedAlpha=!0),!0===this.forceSinglePass&&(i.forceSinglePass=!0),!0===this.wireframe&&(i.wireframe=!0),this.wireframeLinewidth>1&&(i.wireframeLinewidth=this.wireframeLinewidth),"round"!==this.wireframeLinecap&&(i.wireframeLinecap=this.wireframeLinecap),"round"!==this.wireframeLinejoin&&(i.wireframeLinejoin=this.wireframeLinejoin),!0===this.flatShading&&(i.flatShading=!0),!1===this.visible&&(i.visible=!1),!1===this.toneMapped&&(i.toneMapped=!1),!1===this.fog&&(i.fog=!1),Object.keys(this.userData).length>0&&(i.userData=this.userData),e){const e=s(t.textures),r=s(t.images);e.length>0&&(i.textures=e),r.length>0&&(i.images=r)}return i}clone(){return(new this.constructor).copy(this)}copy(t){this.name=t.name,this.blending=t.blending,this.side=t.side,this.vertexColors=t.vertexColors,this.opacity=t.opacity,this.transparent=t.transparent,this.blendSrc=t.blendSrc,this.blendDst=t.blendDst,this.blendEquation=t.blendEquation,this.blendSrcAlpha=t.blendSrcAlpha,this.blendDstAlpha=t.blendDstAlpha,this.blendEquationAlpha=t.blendEquationAlpha,this.blendColor.copy(t.blendColor),this.blendAlpha=t.blendAlpha,this.depthFunc=t.depthFunc,this.depthTest=t.depthTest,this.depthWrite=t.depthWrite,this.stencilWriteMask=t.stencilWriteMask,this.stencilFunc=t.stencilFunc,this.stencilRef=t.stencilRef,this.stencilFuncMask=t.stencilFuncMask,this.stencilFail=t.stencilFail,this.stencilZFail=t.stencilZFail,this.stencilZPass=t.stencilZPass,this.stencilWrite=t.stencilWrite;const e=t.clippingPlanes;let i=null;if(null!==e){const t=e.length;i=new Array(t);for(let s=0;s!==t;++s)i[s]=e[s].clone()}return this.clippingPlanes=i,this.clipIntersection=t.clipIntersection,this.clipShadows=t.clipShadows,this.shadowSide=t.shadowSide,this.colorWrite=t.colorWrite,this.precision=t.precision,this.polygonOffset=t.polygonOffset,this.polygonOffsetFactor=t.polygonOffsetFactor,this.polygonOffsetUnits=t.polygonOffsetUnits,this.dithering=t.dithering,this.alphaTest=t.alphaTest,this.alphaHash=t.alphaHash,this.alphaToCoverage=t.alphaToCoverage,this.premultipliedAlpha=t.premultipliedAlpha,this.forceSinglePass=t.forceSinglePass,this.visible=t.visible,this.toneMapped=t.toneMapped,this.userData=JSON.parse(JSON.stringify(t.userData)),this}dispose(){this.dispatchEvent({type:"dispose"})}set needsUpdate(t){!0===t&&this.version++}}class rn extends sn{constructor(t){super(),this.isMeshBasicMaterial=!0,this.type="MeshBasicMaterial",this.color=new Kr(16777215),this.map=null,this.lightMap=null,this.lightMapIntensity=1,this.aoMap=null,this.aoMapIntensity=1,this.specularMap=null,this.alphaMap=null,this.envMap=null,this.envMapRotation=new fr,this.combine=0,this.reflectivity=1,this.refractionRatio=.98,this.wireframe=!1,this.wireframeLinewidth=1,this.wireframeLinecap="round",this.wireframeLinejoin="round",this.fog=!0,this.setValues(t)}copy(t){return super.copy(t),this.color.copy(t.color),this.map=t.map,this.lightMap=t.lightMap,this.lightMapIntensity=t.lightMapIntensity,this.aoMap=t.aoMap,this.aoMapIntensity=t.aoMapIntensity,this.specularMap=t.specularMap,this.alphaMap=t.alphaMap,this.envMap=t.envMap,this.envMapRotation.copy(t.envMapRotation),this.combine=t.combine,this.reflectivity=t.reflectivity,this.refractionRatio=t.refractionRatio,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.wireframeLinecap=t.wireframeLinecap,this.wireframeLinejoin=t.wireframeLinejoin,this.fog=t.fog,this}}const nn=an();function an(){const t=new ArrayBuffer(4),e=new Float32Array(t),i=new Uint32Array(t),s=new Uint32Array(512),r=new Uint32Array(512);for(let t=0;t<256;++t){const e=t-127;e<-27?(s[t]=0,s[256|t]=32768,r[t]=24,r[256|t]=24):e<-14?(s[t]=1024>>-e-14,s[256|t]=1024>>-e-14|32768,r[t]=-e-1,r[256|t]=-e-1):e<=15?(s[t]=e+15<<10,s[256|t]=e+15<<10|32768,r[t]=13,r[256|t]=13):e<128?(s[t]=31744,s[256|t]=64512,r[t]=24,r[256|t]=24):(s[t]=31744,s[256|t]=64512,r[t]=13,r[256|t]=13)}const n=new Uint32Array(2048),a=new Uint32Array(64),o=new Uint32Array(64);for(let t=1;t<1024;++t){let e=t<<13,i=0;for(;!(8388608&e);)e<<=1,i-=8388608;e&=-8388609,i+=947912704,n[t]=e|i}for(let t=1024;t<2048;++t)n[t]=939524096+(t-1024<<13);for(let t=1;t<31;++t)a[t]=t<<23;a[31]=1199570944,a[32]=2147483648;for(let t=33;t<63;++t)a[t]=2147483648+(t-32<<23);a[63]=3347054592;for(let t=1;t<64;++t)32!==t&&(o[t]=1024);return{floatView:e,uint32View:i,baseTable:s,shiftTable:r,mantissaTable:n,exponentTable:a,offsetTable:o}}function on(t){Math.abs(t)>65504&&console.warn("THREE.DataUtils.toHalfFloat(): Value out of range."),t=Hi(t,-65504,65504),nn.floatView[0]=t;const e=nn.uint32View[0],i=e>>23&511;return nn.baseTable[i]+((8388607&e)>>nn.shiftTable[i])}function hn(t){const e=t>>10;return nn.uint32View[0]=nn.mantissaTable[nn.offsetTable[e]+(1023&t)]+nn.exponentTable[e],nn.floatView[0]}class ln{static toHalfFloat(t){return on(t)}static fromHalfFloat(t){return hn(t)}}const cn=new Qi,un=new Gi;let dn=0;class pn{constructor(t,e,i=!1){if(Array.isArray(t))throw new TypeError("THREE.BufferAttribute: array should be a Typed Array.");this.isBufferAttribute=!0,Object.defineProperty(this,"id",{value:dn++}),this.name="",this.array=t,this.itemSize=e,this.count=void 0!==t?t.length/e:0,this.normalized=i,this.usage=Mi,this.updateRanges=[],this.gpuType=Et,this.version=0}onUploadCallback(){}set needsUpdate(t){!0===t&&this.version++}setUsage(t){return this.usage=t,this}addUpdateRange(t,e){this.updateRanges.push({start:t,count:e})}clearUpdateRanges(){this.updateRanges.length=0}copy(t){return this.name=t.name,this.array=new t.array.constructor(t.array),this.itemSize=t.itemSize,this.count=t.count,this.normalized=t.normalized,this.usage=t.usage,this.gpuType=t.gpuType,this}copyAt(t,e,i){t*=this.itemSize,i*=e.itemSize;for(let s=0,r=this.itemSize;se.count&&console.warn("THREE.BufferGeometry: Buffer size too small for points data. Use .dispose() and create a new geometry."),e.needsUpdate=!0}return this}computeBoundingBox(){null===this.boundingBox&&(this.boundingBox=new Ps);const t=this.attributes.position,e=this.morphAttributes.position;if(t&&t.isGLBufferAttribute)return console.error("THREE.BufferGeometry.computeBoundingBox(): GLBufferAttribute requires a manual bounding box.",this),void this.boundingBox.set(new Qi(-1/0,-1/0,-1/0),new Qi(1/0,1/0,1/0));if(void 0!==t){if(this.boundingBox.setFromBufferAttribute(t),e)for(let t=0,i=e.length;t0&&(t.userData=this.userData),void 0!==this.parameters){const e=this.parameters;for(const i in e)void 0!==e[i]&&(t[i]=e[i]);return t}t.data={attributes:{}};const e=this.index;null!==e&&(t.data.index={type:e.array.constructor.name,array:Array.prototype.slice.call(e.array)});const i=this.attributes;for(const e in i){const s=i[e];t.data.attributes[e]=s.toJSON(t.data)}const s={};let r=!1;for(const e in this.morphAttributes){const i=this.morphAttributes[e],n=[];for(let e=0,s=i.length;e0&&(s[e]=n,r=!0)}r&&(t.data.morphAttributes=s,t.data.morphTargetsRelative=this.morphTargetsRelative);const n=this.groups;n.length>0&&(t.data.groups=JSON.parse(JSON.stringify(n)));const a=this.boundingSphere;return null!==a&&(t.data.boundingSphere=a.toJSON()),t}clone(){return(new this.constructor).copy(this)}copy(t){this.index=null,this.attributes={},this.morphAttributes={},this.groups=[],this.boundingBox=null,this.boundingSphere=null;const e={};this.name=t.name;const i=t.index;null!==i&&this.setIndex(i.clone());const s=t.attributes;for(const t in s){const i=s[t];this.setAttribute(t,i.clone(e))}const r=t.morphAttributes;for(const t in r){const i=[],s=r[t];for(let t=0,r=s.length;t0){const i=t[e[0]];if(void 0!==i){this.morphTargetInfluences=[],this.morphTargetDictionary={};for(let t=0,e=i.length;t(t.far-t.near)**2)return}kn.copy(r).invert(),En.copy(t.ray).applyMatrix4(kn),null!==i.boundingBox&&!1===En.intersectsBox(i.boundingBox)||this._computeIntersections(t,e,En)}}_computeIntersections(t,e,i){let s;const r=this.geometry,n=this.material,a=r.index,o=r.attributes.position,h=r.attributes.uv,l=r.attributes.uv1,c=r.attributes.normal,u=r.groups,d=r.drawRange;if(null!==a)if(Array.isArray(n))for(let r=0,o=u.length;ri.far?null:{distance:l,point:Wn.clone(),object:t}}(t,e,i,s,On,Nn,Fn,jn);if(c){const t=new Qi;Yr.getBarycoord(jn,On,Nn,Fn,t),r&&(c.uv=Yr.getInterpolatedAttribute(r,o,h,l,t,new Gi)),n&&(c.uv1=Yr.getInterpolatedAttribute(n,o,h,l,t,new Gi)),a&&(c.normal=Yr.getInterpolatedAttribute(a,o,h,l,t,new Qi),c.normal.dot(s.direction)>0&&c.normal.multiplyScalar(-1));const e={a:o,b:h,c:l,normal:new Qi,materialIndex:0};Yr.getNormal(On,Nn,Fn,e.normal),c.face=e,c.barycoord=t}return c}class Hn extends Bn{constructor(t=1,e=1,i=1,s=1,r=1,n=1){super(),this.type="BoxGeometry",this.parameters={width:t,height:e,depth:i,widthSegments:s,heightSegments:r,depthSegments:n};const a=this;s=Math.floor(s),r=Math.floor(r),n=Math.floor(n);const o=[],h=[],l=[],c=[];let u=0,d=0;function p(t,e,i,s,r,n,p,m,y,g,f){const x=n/y,b=p/g,v=n/2,w=p/2,M=m/2,S=y+1,_=g+1;let A=0,T=0;const z=new Qi;for(let n=0;n<_;n++){const a=n*b-w;for(let o=0;o0?1:-1,l.push(z.x,z.y,z.z),c.push(o/y),c.push(1-n/g),A+=1}}for(let t=0;t0&&(e.defines=this.defines),e.vertexShader=this.vertexShader,e.fragmentShader=this.fragmentShader,e.lights=this.lights,e.clipping=this.clipping;const i={};for(const t in this.extensions)!0===this.extensions[t]&&(i[t]=!0);return Object.keys(i).length>0&&(e.extensions=i),e}}class Gn extends Pr{constructor(){super(),this.isCamera=!0,this.type="Camera",this.matrixWorldInverse=new or,this.projectionMatrix=new or,this.projectionMatrixInverse=new or,this.coordinateSystem=Ri}copy(t,e){return super.copy(t,e),this.matrixWorldInverse.copy(t.matrixWorldInverse),this.projectionMatrix.copy(t.projectionMatrix),this.projectionMatrixInverse.copy(t.projectionMatrixInverse),this.coordinateSystem=t.coordinateSystem,this}getWorldDirection(t){return super.getWorldDirection(t).negate()}updateMatrixWorld(t){super.updateMatrixWorld(t),this.matrixWorldInverse.copy(this.matrixWorld).invert()}updateWorldMatrix(t,e){super.updateWorldMatrix(t,e),this.matrixWorldInverse.copy(this.matrixWorld).invert()}clone(){return(new this.constructor).copy(this)}}const $n=new Qi,Qn=new Gi,Kn=new Gi;class ta extends Gn{constructor(t=50,e=1,i=.1,s=2e3){super(),this.isPerspectiveCamera=!0,this.type="PerspectiveCamera",this.fov=t,this.zoom=1,this.near=i,this.far=s,this.focus=10,this.aspect=e,this.view=null,this.filmGauge=35,this.filmOffset=0,this.updateProjectionMatrix()}copy(t,e){return super.copy(t,e),this.fov=t.fov,this.zoom=t.zoom,this.near=t.near,this.far=t.far,this.focus=t.focus,this.aspect=t.aspect,this.view=null===t.view?null:Object.assign({},t.view),this.filmGauge=t.filmGauge,this.filmOffset=t.filmOffset,this}setFocalLength(t){const e=.5*this.getFilmHeight()/t;this.fov=2*Ui*Math.atan(e),this.updateProjectionMatrix()}getFocalLength(){const t=Math.tan(.5*Wi*this.fov);return.5*this.getFilmHeight()/t}getEffectiveFOV(){return 2*Ui*Math.atan(Math.tan(.5*Wi*this.fov)/this.zoom)}getFilmWidth(){return this.filmGauge*Math.min(this.aspect,1)}getFilmHeight(){return this.filmGauge/Math.max(this.aspect,1)}getViewBounds(t,e,i){$n.set(-1,-1,.5).applyMatrix4(this.projectionMatrixInverse),e.set($n.x,$n.y).multiplyScalar(-t/$n.z),$n.set(1,1,.5).applyMatrix4(this.projectionMatrixInverse),i.set($n.x,$n.y).multiplyScalar(-t/$n.z)}getViewSize(t,e){return this.getViewBounds(t,Qn,Kn),e.subVectors(Kn,Qn)}setViewOffset(t,e,i,s,r,n){this.aspect=t/e,null===this.view&&(this.view={enabled:!0,fullWidth:1,fullHeight:1,offsetX:0,offsetY:0,width:1,height:1}),this.view.enabled=!0,this.view.fullWidth=t,this.view.fullHeight=e,this.view.offsetX=i,this.view.offsetY=s,this.view.width=r,this.view.height=n,this.updateProjectionMatrix()}clearViewOffset(){null!==this.view&&(this.view.enabled=!1),this.updateProjectionMatrix()}updateProjectionMatrix(){const t=this.near;let e=t*Math.tan(.5*Wi*this.fov)/this.zoom,i=2*e,s=this.aspect*i,r=-.5*s;const n=this.view;if(null!==this.view&&this.view.enabled){const t=n.fullWidth,a=n.fullHeight;r+=n.offsetX*s/t,e-=n.offsetY*i/a,s*=n.width/t,i*=n.height/a}const a=this.filmOffset;0!==a&&(r+=t*a/this.getFilmWidth()),this.projectionMatrix.makePerspective(r,r+s,e,e-i,t,this.far,this.coordinateSystem),this.projectionMatrixInverse.copy(this.projectionMatrix).invert()}toJSON(t){const e=super.toJSON(t);return e.object.fov=this.fov,e.object.zoom=this.zoom,e.object.near=this.near,e.object.far=this.far,e.object.focus=this.focus,e.object.aspect=this.aspect,null!==this.view&&(e.object.view=Object.assign({},this.view)),e.object.filmGauge=this.filmGauge,e.object.filmOffset=this.filmOffset,e}}const ea=-90;class ia extends Pr{constructor(t,e,i){super(),this.type="CubeCamera",this.renderTarget=i,this.coordinateSystem=null,this.activeMipmapLevel=0;const s=new ta(ea,1,t,e);s.layers=this.layers,this.add(s);const r=new ta(ea,1,t,e);r.layers=this.layers,this.add(r);const n=new ta(ea,1,t,e);n.layers=this.layers,this.add(n);const a=new ta(ea,1,t,e);a.layers=this.layers,this.add(a);const o=new ta(ea,1,t,e);o.layers=this.layers,this.add(o);const h=new ta(ea,1,t,e);h.layers=this.layers,this.add(h)}updateCoordinateSystem(){const t=this.coordinateSystem,e=this.children.concat(),[i,s,r,n,a,o]=e;for(const t of e)this.remove(t);if(t===Ri)i.up.set(0,1,0),i.lookAt(1,0,0),s.up.set(0,1,0),s.lookAt(-1,0,0),r.up.set(0,0,-1),r.lookAt(0,1,0),n.up.set(0,0,1),n.lookAt(0,-1,0),a.up.set(0,1,0),a.lookAt(0,0,1),o.up.set(0,1,0),o.lookAt(0,0,-1);else{if(t!==Pi)throw new Error("THREE.CubeCamera.updateCoordinateSystem(): Invalid coordinate system: "+t);i.up.set(0,-1,0),i.lookAt(-1,0,0),s.up.set(0,-1,0),s.lookAt(1,0,0),r.up.set(0,0,1),r.lookAt(0,1,0),n.up.set(0,0,-1),n.lookAt(0,-1,0),a.up.set(0,-1,0),a.lookAt(0,0,1),o.up.set(0,-1,0),o.lookAt(0,0,-1)}for(const t of e)this.add(t),t.updateMatrixWorld()}update(t,e){null===this.parent&&this.updateMatrixWorld();const{renderTarget:i,activeMipmapLevel:s}=this;this.coordinateSystem!==t.coordinateSystem&&(this.coordinateSystem=t.coordinateSystem,this.updateCoordinateSystem());const[r,n,a,o,h,l]=this.children,c=t.getRenderTarget(),u=t.getActiveCubeFace(),d=t.getActiveMipmapLevel(),p=t.xr.enabled;t.xr.enabled=!1;const m=i.texture.generateMipmaps;i.texture.generateMipmaps=!1,t.setRenderTarget(i,0,s),t.render(e,r),t.setRenderTarget(i,1,s),t.render(e,n),t.setRenderTarget(i,2,s),t.render(e,a),t.setRenderTarget(i,3,s),t.render(e,o),t.setRenderTarget(i,4,s),t.render(e,h),i.texture.generateMipmaps=m,t.setRenderTarget(i,5,s),t.render(e,l),t.setRenderTarget(c,u,d),t.xr.enabled=p,i.texture.needsPMREMUpdate=!0}}class sa extends Ts{constructor(t=[],e=301,i,s,r,n,a,o,h,l){super(t,e,i,s,r,n,a,o,h,l),this.isCubeTexture=!0,this.flipY=!1}get images(){return this.image}set images(t){this.image=t}}class ra extends Cs{constructor(t=1,e={}){super(t,t,e),this.isWebGLCubeRenderTarget=!0;const i={width:t,height:t,depth:1},s=[i,i,i,i,i,i];this.texture=new sa(s),this._setTextureOptions(e),this.texture.isRenderTargetTexture=!0}fromEquirectangularTexture(t,e){this.texture.type=e.type,this.texture.colorSpace=e.colorSpace,this.texture.generateMipmaps=e.generateMipmaps,this.texture.minFilter=e.minFilter,this.texture.magFilter=e.magFilter;const i={uniforms:{tEquirect:{value:null}},vertexShader:"\n\n\t\t\t\tvarying vec3 vWorldDirection;\n\n\t\t\t\tvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n\n\t\t\t\t\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n\n\t\t\t\t}\n\n\t\t\t\tvoid main() {\n\n\t\t\t\t\tvWorldDirection = transformDirection( position, modelMatrix );\n\n\t\t\t\t\t#include \n\t\t\t\t\t#include \n\n\t\t\t\t}\n\t\t\t",fragmentShader:"\n\n\t\t\t\tuniform sampler2D tEquirect;\n\n\t\t\t\tvarying vec3 vWorldDirection;\n\n\t\t\t\t#include \n\n\t\t\t\tvoid main() {\n\n\t\t\t\t\tvec3 direction = normalize( vWorldDirection );\n\n\t\t\t\t\tvec2 sampleUV = equirectUv( direction );\n\n\t\t\t\t\tgl_FragColor = texture2D( tEquirect, sampleUV );\n\n\t\t\t\t}\n\t\t\t"},s=new Hn(5,5,5),r=new Zn({name:"CubemapFromEquirect",uniforms:qn(i.uniforms),vertexShader:i.vertexShader,fragmentShader:i.fragmentShader,side:1,blending:0});r.uniforms.tEquirect.value=e;const n=new Un(s,r),a=e.minFilter;e.minFilter===_t&&(e.minFilter=wt);return new ia(1,10,this).update(t,n),e.minFilter=a,n.geometry.dispose(),n.material.dispose(),this}clear(t,e=!0,i=!0,s=!0){const r=t.getRenderTarget();for(let r=0;r<6;r++)t.setRenderTarget(this,r),t.clear(e,i,s);t.setRenderTarget(r)}}class na extends Pr{constructor(){super(),this.isGroup=!0,this.type="Group"}}const aa={type:"move"};class oa{constructor(){this._targetRay=null,this._grip=null,this._hand=null}getHandSpace(){return null===this._hand&&(this._hand=new na,this._hand.matrixAutoUpdate=!1,this._hand.visible=!1,this._hand.joints={},this._hand.inputState={pinching:!1}),this._hand}getTargetRaySpace(){return null===this._targetRay&&(this._targetRay=new na,this._targetRay.matrixAutoUpdate=!1,this._targetRay.visible=!1,this._targetRay.hasLinearVelocity=!1,this._targetRay.linearVelocity=new Qi,this._targetRay.hasAngularVelocity=!1,this._targetRay.angularVelocity=new Qi),this._targetRay}getGripSpace(){return null===this._grip&&(this._grip=new na,this._grip.matrixAutoUpdate=!1,this._grip.visible=!1,this._grip.hasLinearVelocity=!1,this._grip.linearVelocity=new Qi,this._grip.hasAngularVelocity=!1,this._grip.angularVelocity=new Qi),this._grip}dispatchEvent(t){return null!==this._targetRay&&this._targetRay.dispatchEvent(t),null!==this._grip&&this._grip.dispatchEvent(t),null!==this._hand&&this._hand.dispatchEvent(t),this}connect(t){if(t&&t.hand){const e=this._hand;if(e)for(const i of t.hand.values())this._getHandJoint(e,i)}return this.dispatchEvent({type:"connected",data:t}),this}disconnect(t){return this.dispatchEvent({type:"disconnected",data:t}),null!==this._targetRay&&(this._targetRay.visible=!1),null!==this._grip&&(this._grip.visible=!1),null!==this._hand&&(this._hand.visible=!1),this}update(t,e,i){let s=null,r=null,n=null;const a=this._targetRay,o=this._grip,h=this._hand;if(t&&"visible-blurred"!==e.session.visibilityState){if(h&&t.hand){n=!0;for(const s of t.hand.values()){const t=e.getJointPose(s,i),r=this._getHandJoint(h,s);null!==t&&(r.matrix.fromArray(t.transform.matrix),r.matrix.decompose(r.position,r.rotation,r.scale),r.matrixWorldNeedsUpdate=!0,r.jointRadius=t.radius),r.visible=null!==t}const s=h.joints["index-finger-tip"],r=h.joints["thumb-tip"],a=s.position.distanceTo(r.position),o=.02,l=.005;h.inputState.pinching&&a>o+l?(h.inputState.pinching=!1,this.dispatchEvent({type:"pinchend",handedness:t.handedness,target:this})):!h.inputState.pinching&&a<=o-l&&(h.inputState.pinching=!0,this.dispatchEvent({type:"pinchstart",handedness:t.handedness,target:this}))}else null!==o&&t.gripSpace&&(r=e.getPose(t.gripSpace,i),null!==r&&(o.matrix.fromArray(r.transform.matrix),o.matrix.decompose(o.position,o.rotation,o.scale),o.matrixWorldNeedsUpdate=!0,r.linearVelocity?(o.hasLinearVelocity=!0,o.linearVelocity.copy(r.linearVelocity)):o.hasLinearVelocity=!1,r.angularVelocity?(o.hasAngularVelocity=!0,o.angularVelocity.copy(r.angularVelocity)):o.hasAngularVelocity=!1));null!==a&&(s=e.getPose(t.targetRaySpace,i),null===s&&null!==r&&(s=r),null!==s&&(a.matrix.fromArray(s.transform.matrix),a.matrix.decompose(a.position,a.rotation,a.scale),a.matrixWorldNeedsUpdate=!0,s.linearVelocity?(a.hasLinearVelocity=!0,a.linearVelocity.copy(s.linearVelocity)):a.hasLinearVelocity=!1,s.angularVelocity?(a.hasAngularVelocity=!0,a.angularVelocity.copy(s.angularVelocity)):a.hasAngularVelocity=!1,this.dispatchEvent(aa)))}return null!==a&&(a.visible=null!==s),null!==o&&(o.visible=null!==r),null!==h&&(h.visible=null!==n),this}_getHandJoint(t,e){if(void 0===t.joints[e.jointName]){const i=new na;i.matrixAutoUpdate=!1,i.visible=!1,t.joints[e.jointName]=i,t.add(i)}return t.joints[e.jointName]}}class ha{constructor(t,e=25e-5){this.isFogExp2=!0,this.name="",this.color=new Kr(t),this.density=e}clone(){return new ha(this.color,this.density)}toJSON(){return{type:"FogExp2",name:this.name,color:this.color.getHex(),density:this.density}}}class la{constructor(t,e=1,i=1e3){this.isFog=!0,this.name="",this.color=new Kr(t),this.near=e,this.far=i}clone(){return new la(this.color,this.near,this.far)}toJSON(){return{type:"Fog",name:this.name,color:this.color.getHex(),near:this.near,far:this.far}}}class ca extends Pr{constructor(){super(),this.isScene=!0,this.type="Scene",this.background=null,this.environment=null,this.fog=null,this.backgroundBlurriness=0,this.backgroundIntensity=1,this.backgroundRotation=new fr,this.environmentIntensity=1,this.environmentRotation=new fr,this.overrideMaterial=null,"undefined"!=typeof __THREE_DEVTOOLS__&&__THREE_DEVTOOLS__.dispatchEvent(new CustomEvent("observe",{detail:this}))}copy(t,e){return super.copy(t,e),null!==t.background&&(this.background=t.background.clone()),null!==t.environment&&(this.environment=t.environment.clone()),null!==t.fog&&(this.fog=t.fog.clone()),this.backgroundBlurriness=t.backgroundBlurriness,this.backgroundIntensity=t.backgroundIntensity,this.backgroundRotation.copy(t.backgroundRotation),this.environmentIntensity=t.environmentIntensity,this.environmentRotation.copy(t.environmentRotation),null!==t.overrideMaterial&&(this.overrideMaterial=t.overrideMaterial.clone()),this.matrixAutoUpdate=t.matrixAutoUpdate,this}toJSON(t){const e=super.toJSON(t);return null!==this.fog&&(e.object.fog=this.fog.toJSON()),this.backgroundBlurriness>0&&(e.object.backgroundBlurriness=this.backgroundBlurriness),1!==this.backgroundIntensity&&(e.object.backgroundIntensity=this.backgroundIntensity),e.object.backgroundRotation=this.backgroundRotation.toArray(),1!==this.environmentIntensity&&(e.object.environmentIntensity=this.environmentIntensity),e.object.environmentRotation=this.environmentRotation.toArray(),e}}class ua{constructor(t,e){this.isInterleavedBuffer=!0,this.array=t,this.stride=e,this.count=void 0!==t?t.length/e:0,this.usage=Mi,this.updateRanges=[],this.version=0,this.uuid=Di()}onUploadCallback(){}set needsUpdate(t){!0===t&&this.version++}setUsage(t){return this.usage=t,this}addUpdateRange(t,e){this.updateRanges.push({start:t,count:e})}clearUpdateRanges(){this.updateRanges.length=0}copy(t){return this.array=new t.array.constructor(t.array),this.count=t.count,this.stride=t.stride,this.usage=t.usage,this}copyAt(t,e,i){t*=this.stride,i*=e.stride;for(let s=0,r=this.stride;st.far||e.push({distance:o,point:ga.clone(),uv:Yr.getInterpolation(ga,Ma,Sa,_a,Aa,Ta,za,new Gi),face:null,object:this})}copy(t,e){return super.copy(t,e),void 0!==t.center&&this.center.copy(t.center),this.material=t.material,this}}function Ca(t,e,i,s,r,n){ba.subVectors(t,i).addScalar(.5).multiply(s),void 0!==r?(va.x=n*ba.x-r*ba.y,va.y=r*ba.x+n*ba.y):va.copy(ba),t.copy(e),t.x+=va.x,t.y+=va.y,t.applyMatrix4(wa)}const Ba=new Qi,ka=new Qi;class Ea extends Pr{constructor(){super(),this.isLOD=!0,this._currentLevel=0,this.type="LOD",Object.defineProperties(this,{levels:{enumerable:!0,value:[]}}),this.autoUpdate=!0}copy(t){super.copy(t,!1);const e=t.levels;for(let t=0,i=e.length;t0){let i,s;for(i=1,s=e.length;i0){Ba.setFromMatrixPosition(this.matrixWorld);const i=t.ray.origin.distanceTo(Ba);this.getObjectForDistance(i).raycast(t,e)}}update(t){const e=this.levels;if(e.length>1){Ba.setFromMatrixPosition(t.matrixWorld),ka.setFromMatrixPosition(this.matrixWorld);const i=Ba.distanceTo(ka)/t.zoom;let s,r;for(e[0].object.visible=!0,s=1,r=e.length;s=t))break;e[s-1].object.visible=!1,e[s].object.visible=!0}for(this._currentLevel=s-1;s1?null:e.copy(t.start).addScaledVector(i,r)}intersectsLine(t){const e=this.distanceToPoint(t.start),i=this.distanceToPoint(t.end);return e<0&&i>0||i<0&&e>0}intersectsBox(t){return t.intersectsPlane(this)}intersectsSphere(t){return t.intersectsPlane(this)}coplanarPoint(t){return t.copy(this.normal).multiplyScalar(-this.constant)}applyMatrix4(t,e){const i=e||no.getNormalMatrix(t),s=this.coplanarPoint(so).applyMatrix4(t),r=this.normal.applyMatrix3(i).normalize();return this.constant=-s.dot(r),this}translate(t){return this.constant-=t.dot(this.normal),this}equals(t){return t.normal.equals(this.normal)&&t.constant===this.constant}clone(){return(new this.constructor).copy(this)}}const oo=new Qs,ho=new Gi(.5,.5),lo=new Qi;class co{constructor(t=new ao,e=new ao,i=new ao,s=new ao,r=new ao,n=new ao){this.planes=[t,e,i,s,r,n]}set(t,e,i,s,r,n){const a=this.planes;return a[0].copy(t),a[1].copy(e),a[2].copy(i),a[3].copy(s),a[4].copy(r),a[5].copy(n),this}copy(t){const e=this.planes;for(let i=0;i<6;i++)e[i].copy(t.planes[i]);return this}setFromProjectionMatrix(t,e=2e3){const i=this.planes,s=t.elements,r=s[0],n=s[1],a=s[2],o=s[3],h=s[4],l=s[5],c=s[6],u=s[7],d=s[8],p=s[9],m=s[10],y=s[11],g=s[12],f=s[13],x=s[14],b=s[15];if(i[0].setComponents(o-r,u-h,y-d,b-g).normalize(),i[1].setComponents(o+r,u+h,y+d,b+g).normalize(),i[2].setComponents(o+n,u+l,y+p,b+f).normalize(),i[3].setComponents(o-n,u-l,y-p,b-f).normalize(),i[4].setComponents(o-a,u-c,y-m,b-x).normalize(),e===Ri)i[5].setComponents(o+a,u+c,y+m,b+x).normalize();else{if(e!==Pi)throw new Error("THREE.Frustum.setFromProjectionMatrix(): Invalid coordinate system: "+e);i[5].setComponents(a,c,m,x).normalize()}return this}intersectsObject(t){if(void 0!==t.boundingSphere)null===t.boundingSphere&&t.computeBoundingSphere(),oo.copy(t.boundingSphere).applyMatrix4(t.matrixWorld);else{const e=t.geometry;null===e.boundingSphere&&e.computeBoundingSphere(),oo.copy(e.boundingSphere).applyMatrix4(t.matrixWorld)}return this.intersectsSphere(oo)}intersectsSprite(t){oo.center.set(0,0,0);const e=ho.distanceTo(t.center);return oo.radius=.7071067811865476+e,oo.applyMatrix4(t.matrixWorld),this.intersectsSphere(oo)}intersectsSphere(t){const e=this.planes,i=t.center,s=-t.radius;for(let t=0;t<6;t++){if(e[t].distanceToPoint(i)0?t.max.x:t.min.x,lo.y=s.normal.y>0?t.max.y:t.min.y,lo.z=s.normal.z>0?t.max.z:t.min.z,s.distanceToPoint(lo)<0)return!1}return!0}containsPoint(t){const e=this.planes;for(let i=0;i<6;i++)if(e[i].distanceToPoint(t)<0)return!1;return!0}clone(){return(new this.constructor).copy(this)}}const uo=new or,po=new co;class mo{constructor(){this.coordinateSystem=Ri}intersectsObject(t,e){if(!e.isArrayCamera||0===e.cameras.length)return!1;for(let i=0;i=r.length&&r.push({start:-1,count:-1,z:-1,index:-1});const a=r[this.index];n.push(a),this.index++,a.start=t,a.count=e,a.z=i,a.index=s}reset(){this.list.length=0,this.index=0}}const bo=new or,vo=new Kr(1,1,1),wo=new co,Mo=new mo,So=new Ps,_o=new Qs,Ao=new Qi,To=new Qi,zo=new Qi,Io=new xo,Co=new Un,Bo=[];function ko(t,e,i=0){const s=e.itemSize;if(t.isInterleavedBufferAttribute||t.array.constructor!==e.array.constructor){const r=t.count;for(let n=0;n65535?new Uint32Array(s):new Uint16Array(s);e.setIndex(new pn(t,1))}this._geometryInitialized=!0}}_validateGeometry(t){const e=this.geometry;if(Boolean(t.getIndex())!==Boolean(e.getIndex()))throw new Error('THREE.BatchedMesh: All geometries must consistently have "index".');for(const i in e.attributes){if(!t.hasAttribute(i))throw new Error(`THREE.BatchedMesh: Added geometry missing "${i}". All geometries must have consistent attributes.`);const s=t.getAttribute(i),r=e.getAttribute(i);if(s.itemSize!==r.itemSize||s.normalized!==r.normalized)throw new Error("THREE.BatchedMesh: All attributes must have a consistent itemSize and normalized value.")}}validateInstanceId(t){const e=this._instanceInfo;if(t<0||t>=e.length||!1===e[t].active)throw new Error(`THREE.BatchedMesh: Invalid instanceId ${t}. Instance is either out of range or has been deleted.`)}validateGeometryId(t){const e=this._geometryInfo;if(t<0||t>=e.length||!1===e[t].active)throw new Error(`THREE.BatchedMesh: Invalid geometryId ${t}. Geometry is either out of range or has been deleted.`)}setCustomSort(t){return this.customSort=t,this}computeBoundingBox(){null===this.boundingBox&&(this.boundingBox=new Ps);const t=this.boundingBox,e=this._instanceInfo;t.makeEmpty();for(let i=0,s=e.length;i=this.maxInstanceCount&&0===this._availableInstanceIds.length)throw new Error("THREE.BatchedMesh: Maximum item count reached.");const e={visible:!0,active:!0,geometryIndex:t};let i=null;this._availableInstanceIds.length>0?(this._availableInstanceIds.sort(yo),i=this._availableInstanceIds.shift(),this._instanceInfo[i]=e):(i=this._instanceInfo.length,this._instanceInfo.push(e));const s=this._matricesTexture;bo.identity().toArray(s.image.data,16*i),s.needsUpdate=!0;const r=this._colorsTexture;return r&&(vo.toArray(r.image.data,4*i),r.needsUpdate=!0),this._visibilityChanged=!0,i}addGeometry(t,e=-1,i=-1){this._initializeGeometry(t),this._validateGeometry(t);const s={vertexStart:-1,vertexCount:-1,reservedVertexCount:-1,indexStart:-1,indexCount:-1,reservedIndexCount:-1,start:-1,count:-1,boundingBox:null,boundingSphere:null,active:!0},r=this._geometryInfo;s.vertexStart=this._nextVertexStart,s.reservedVertexCount=-1===e?t.getAttribute("position").count:e;const n=t.getIndex();if(null!==n&&(s.indexStart=this._nextIndexStart,s.reservedIndexCount=-1===i?n.count:i),-1!==s.indexStart&&s.indexStart+s.reservedIndexCount>this._maxIndexCount||s.vertexStart+s.reservedVertexCount>this._maxVertexCount)throw new Error("THREE.BatchedMesh: Reserved space request exceeds the maximum buffer size.");let a;return this._availableGeometryIds.length>0?(this._availableGeometryIds.sort(yo),a=this._availableGeometryIds.shift(),r[a]=s):(a=this._geometryCount,this._geometryCount++,r.push(s)),this.setGeometryAt(a,t),this._nextIndexStart=s.indexStart+s.reservedIndexCount,this._nextVertexStart=s.vertexStart+s.reservedVertexCount,a}setGeometryAt(t,e){if(t>=this._geometryCount)throw new Error("THREE.BatchedMesh: Maximum geometry count reached.");this._validateGeometry(e);const i=this.geometry,s=null!==i.getIndex(),r=i.getIndex(),n=e.getIndex(),a=this._geometryInfo[t];if(s&&n.count>a.reservedIndexCount||e.attributes.position.count>a.reservedVertexCount)throw new Error("THREE.BatchedMesh: Reserved space not large enough for provided geometry.");const o=a.vertexStart,h=a.reservedVertexCount;a.vertexCount=e.getAttribute("position").count;for(const t in i.attributes){const s=e.getAttribute(t),r=i.getAttribute(t);ko(s,r,o);const n=s.itemSize;for(let t=s.count,e=h;t=e.length||!1===e[t].active)return this;const i=this._instanceInfo;for(let e=0,s=i.length;ee).sort((t,e)=>i[t].vertexStart-i[e].vertexStart),r=this.geometry;for(let n=0,a=i.length;n=this._geometryCount)return null;const i=this.geometry,s=this._geometryInfo[t];if(null===s.boundingBox){const t=new Ps,e=i.index,r=i.attributes.position;for(let i=s.start,n=s.start+s.count;i=this._geometryCount)return null;const i=this.geometry,s=this._geometryInfo[t];if(null===s.boundingSphere){const e=new Qs;this.getBoundingBoxAt(t,So),So.getCenter(e.center);const r=i.index,n=i.attributes.position;let a=0;for(let t=s.start,i=s.start+s.count;tt.active);if(Math.max(...i.map(t=>t.vertexStart+t.reservedVertexCount))>t)throw new Error(`BatchedMesh: Geometry vertex values are being used outside the range ${e}. Cannot shrink further.`);if(this.geometry.index){if(Math.max(...i.map(t=>t.indexStart+t.reservedIndexCount))>e)throw new Error(`BatchedMesh: Geometry index values are being used outside the range ${e}. Cannot shrink further.`)}const s=this.geometry;s.dispose(),this._maxVertexCount=t,this._maxIndexCount=e,this._geometryInitialized&&(this._geometryInitialized=!1,this.geometry=new Bn,this._initializeGeometry(s));const r=this.geometry;s.index&&Eo(s.index.array,r.index.array);for(const t in s.attributes)Eo(s.attributes[t].array,r.attributes[t].array)}raycast(t,e){const i=this._instanceInfo,s=this._geometryInfo,r=this.matrixWorld,n=this.geometry;Co.material=this.material,Co.geometry.index=n.index,Co.geometry.attributes=n.attributes,null===Co.geometry.boundingBox&&(Co.geometry.boundingBox=new Ps),null===Co.geometry.boundingSphere&&(Co.geometry.boundingSphere=new Qs);for(let n=0,a=i.length;n({...t,boundingBox:null!==t.boundingBox?t.boundingBox.clone():null,boundingSphere:null!==t.boundingSphere?t.boundingSphere.clone():null})),this._instanceInfo=t._instanceInfo.map(t=>({...t})),this._availableInstanceIds=t._availableInstanceIds.slice(),this._availableGeometryIds=t._availableGeometryIds.slice(),this._nextIndexStart=t._nextIndexStart,this._nextVertexStart=t._nextVertexStart,this._geometryCount=t._geometryCount,this._maxInstanceCount=t._maxInstanceCount,this._maxVertexCount=t._maxVertexCount,this._maxIndexCount=t._maxIndexCount,this._geometryInitialized=t._geometryInitialized,this._multiDrawCounts=t._multiDrawCounts.slice(),this._multiDrawStarts=t._multiDrawStarts.slice(),this._indirectTexture=t._indirectTexture.clone(),this._indirectTexture.image.data=this._indirectTexture.image.data.slice(),this._matricesTexture=t._matricesTexture.clone(),this._matricesTexture.image.data=this._matricesTexture.image.data.slice(),null!==this._colorsTexture&&(this._colorsTexture=t._colorsTexture.clone(),this._colorsTexture.image.data=this._colorsTexture.image.data.slice()),this}dispose(){this.geometry.dispose(),this._matricesTexture.dispose(),this._matricesTexture=null,this._indirectTexture.dispose(),this._indirectTexture=null,null!==this._colorsTexture&&(this._colorsTexture.dispose(),this._colorsTexture=null)}onBeforeRender(t,e,i,s,r){if(!this._visibilityChanged&&!this.perObjectFrustumCulled&&!this.sortObjects)return;const n=s.getIndex(),a=null===n?1:n.array.BYTES_PER_ELEMENT,o=this._instanceInfo,h=this._multiDrawStarts,l=this._multiDrawCounts,c=this._geometryInfo,u=this.perObjectFrustumCulled,d=this._indirectTexture,p=d.image.data,m=i.isArrayCamera?Mo:wo;u&&!i.isArrayCamera&&(bo.multiplyMatrices(i.projectionMatrix,i.matrixWorldInverse).multiply(this.matrixWorld),wo.setFromProjectionMatrix(bo,t.coordinateSystem));let y=0;if(this.sortObjects){bo.copy(this.matrixWorld).invert(),Ao.setFromMatrixPosition(i.matrixWorld).applyMatrix4(bo),To.set(0,0,-1).transformDirection(i.matrixWorld).transformDirection(bo);for(let t=0,e=o.length;t0){const i=t[e[0]];if(void 0!==i){this.morphTargetInfluences=[],this.morphTargetDictionary={};for(let t=0,e=i.length;ts)return;jo.applyMatrix4(t.matrixWorld);const h=e.ray.origin.distanceTo(jo);return he.far?void 0:{distance:h,point:Wo.clone().applyMatrix4(t.matrixWorld),index:a,face:null,faceIndex:null,barycoord:null,object:t}}const Ho=new Qi,qo=new Qi;class Jo extends Uo{constructor(t,e){super(t,e),this.isLineSegments=!0,this.type="LineSegments"}computeLineDistances(){const t=this.geometry;if(null===t.index){const e=t.attributes.position,i=[];for(let t=0,s=e.count;t0){const i=t[e[0]];if(void 0!==i){this.morphTargetInfluences=[],this.morphTargetDictionary={};for(let t=0,e=i.length;tr.far)return;n.push({distance:h,distanceToRay:Math.sqrt(o),point:i,index:e,face:null,faceIndex:null,barycoord:null,object:a})}}class eh extends Ts{constructor(t,e,i,s,r=1006,n=1006,a,o,h){super(t,e,i,s,r,n,a,o,h),this.isVideoTexture=!0,this.generateMipmaps=!1;const l=this;"requestVideoFrameCallback"in t&&t.requestVideoFrameCallback(function e(){l.needsUpdate=!0,t.requestVideoFrameCallback(e)})}clone(){return new this.constructor(this.image).copy(this)}update(){const t=this.image;!1==="requestVideoFrameCallback"in t&&t.readyState>=t.HAVE_CURRENT_DATA&&(this.needsUpdate=!0)}}class ih extends eh{constructor(t,e,i,s,r,n,a,o){super({},t,e,i,s,r,n,a,o),this.isVideoFrameTexture=!0}update(){}clone(){return(new this.constructor).copy(this)}setFrame(t){this.image=t,this.needsUpdate=!0}}class sh extends Ts{constructor(t,e){super({width:t,height:e}),this.isFramebufferTexture=!0,this.magFilter=gt,this.minFilter=gt,this.generateMipmaps=!1,this.needsUpdate=!0}}class rh extends Ts{constructor(t,e,i,s,r,n,a,o,h,l,c,u){super(null,n,a,o,h,l,s,r,c,u),this.isCompressedTexture=!0,this.image={width:e,height:i},this.mipmaps=t,this.flipY=!1,this.generateMipmaps=!1}}class nh extends rh{constructor(t,e,i,s,r,n){super(t,e,i,r,n),this.isCompressedArrayTexture=!0,this.image.depth=s,this.wrapR=mt,this.layerUpdates=new Set}addLayerUpdate(t){this.layerUpdates.add(t)}clearLayerUpdates(){this.layerUpdates.clear()}}class ah extends rh{constructor(t,e,i){super(void 0,t[0].width,t[0].height,e,i,ht),this.isCompressedCubeTexture=!0,this.isCubeTexture=!0,this.image=t}}class oh extends Ts{constructor(t,e,i,s,r,n,a,o,h){super(t,e,i,s,r,n,a,o,h),this.isCanvasTexture=!0,this.needsUpdate=!0}}class hh extends Ts{constructor(t,e,i=1014,s,r,n,a=1003,o=1003,h,l=1026,c=1){if(l!==Wt&&1027!==l)throw new Error("DepthTexture format must be either THREE.DepthFormat or THREE.DepthStencilFormat");super({width:t,height:e,depth:c},s,r,n,a,o,l,i,h),this.isDepthTexture=!0,this.flipY=!1,this.generateMipmaps=!1,this.compareFunction=null}copy(t){return super.copy(t),this.source=new Ms(Object.assign({},t.image)),this.compareFunction=t.compareFunction,this}toJSON(t){const e=super.toJSON(t);return null!==this.compareFunction&&(e.compareFunction=this.compareFunction),e}}class lh extends Bn{constructor(t=1,e=1,i=4,s=8,r=1){super(),this.type="CapsuleGeometry",this.parameters={radius:t,height:e,capSegments:i,radialSegments:s,heightSegments:r},e=Math.max(0,e),i=Math.max(1,Math.floor(i)),s=Math.max(3,Math.floor(s)),r=Math.max(1,Math.floor(r));const n=[],a=[],o=[],h=[],l=e/2,c=Math.PI/2*t,u=e,d=2*c+u,p=2*i+r,m=s+1,y=new Qi,g=new Qi;for(let f=0;f<=p;f++){let x=0,b=0,v=0,w=0;if(f<=i){const e=f/i,s=e*Math.PI/2;b=-l-t*Math.cos(s),v=t*Math.sin(s),w=-t*Math.cos(s),x=e*c}else if(f<=i+r){const s=(f-i)/r;b=s*e-l,v=t,w=0,x=c+s*u}else{const e=(f-i-r)/i,s=e*Math.PI/2;b=l+t*Math.sin(s),v=t*Math.cos(s),w=t*Math.sin(s),x=c+u+e*c}const M=Math.max(0,Math.min(1,x/d));let S=0;0===f?S=.5/s:f===p&&(S=-.5/s);for(let t=0;t<=s;t++){const e=t/s,i=e*Math.PI*2,r=Math.sin(i),n=Math.cos(i);g.x=-v*n,g.y=b,g.z=v*r,a.push(g.x,g.y,g.z),y.set(-v*n,w,v*r),y.normalize(),o.push(y.x,y.y,y.z),h.push(e+S,M)}if(f>0){const t=(f-1)*m;for(let e=0;e0||0!==s)&&(l.push(n,a,h),x+=3),(e>0||s!==r-1)&&(l.push(a,o,h),x+=3)}h.addGroup(g,x,0),g+=x}(),!1===n&&(t>0&&f(!0),e>0&&f(!1)),this.setIndex(l),this.setAttribute("position",new Mn(c,3)),this.setAttribute("normal",new Mn(u,3)),this.setAttribute("uv",new Mn(d,2))}copy(t){return super.copy(t),this.parameters=Object.assign({},t.parameters),this}static fromJSON(t){return new uh(t.radiusTop,t.radiusBottom,t.height,t.radialSegments,t.heightSegments,t.openEnded,t.thetaStart,t.thetaLength)}}class dh extends uh{constructor(t=1,e=1,i=32,s=1,r=!1,n=0,a=2*Math.PI){super(0,t,e,i,s,r,n,a),this.type="ConeGeometry",this.parameters={radius:t,height:e,radialSegments:i,heightSegments:s,openEnded:r,thetaStart:n,thetaLength:a}}static fromJSON(t){return new dh(t.radius,t.height,t.radialSegments,t.heightSegments,t.openEnded,t.thetaStart,t.thetaLength)}}class ph extends Bn{constructor(t=[],e=[],i=1,s=0){super(),this.type="PolyhedronGeometry",this.parameters={vertices:t,indices:e,radius:i,detail:s};const r=[],n=[];function a(t,e,i,s){const r=s+1,n=[];for(let s=0;s<=r;s++){n[s]=[];const a=t.clone().lerp(i,s/r),o=e.clone().lerp(i,s/r),h=r-s;for(let t=0;t<=h;t++)n[s][t]=0===t&&s===r?a:a.clone().lerp(o,t/h)}for(let t=0;t.9&&a<.1&&(e<.2&&(n[t+0]+=1),i<.2&&(n[t+2]+=1),s<.2&&(n[t+4]+=1))}}()}(),this.setAttribute("position",new Mn(r,3)),this.setAttribute("normal",new Mn(r.slice(),3)),this.setAttribute("uv",new Mn(n,2)),0===s?this.computeVertexNormals():this.normalizeNormals()}copy(t){return super.copy(t),this.parameters=Object.assign({},t.parameters),this}static fromJSON(t){return new ph(t.vertices,t.indices,t.radius,t.details)}}class mh extends ph{constructor(t=1,e=0){const i=(1+Math.sqrt(5))/2,s=1/i;super([-1,-1,-1,-1,-1,1,-1,1,-1,-1,1,1,1,-1,-1,1,-1,1,1,1,-1,1,1,1,0,-s,-i,0,-s,i,0,s,-i,0,s,i,-s,-i,0,-s,i,0,s,-i,0,s,i,0,-i,0,-s,i,0,-s,-i,0,s,i,0,s],[3,11,7,3,7,15,3,15,13,7,19,17,7,17,6,7,6,15,17,4,8,17,8,10,17,10,6,8,0,16,8,16,2,8,2,10,0,12,1,0,1,18,0,18,16,6,10,2,6,2,13,6,13,15,2,16,18,2,18,3,2,3,13,18,1,9,18,9,11,18,11,3,4,14,12,4,12,0,4,0,8,11,9,5,11,5,19,11,19,7,19,5,14,19,14,4,19,4,17,1,12,14,1,14,5,1,5,9],t,e),this.type="DodecahedronGeometry",this.parameters={radius:t,detail:e}}static fromJSON(t){return new mh(t.radius,t.detail)}}const yh=new Qi,gh=new Qi,fh=new Qi,xh=new Yr;class bh extends Bn{constructor(t=null,e=1){if(super(),this.type="EdgesGeometry",this.parameters={geometry:t,thresholdAngle:e},null!==t){const i=4,s=Math.pow(10,i),r=Math.cos(Wi*e),n=t.getIndex(),a=t.getAttribute("position"),o=n?n.count:a.count,h=[0,0,0],l=["a","b","c"],c=new Array(3),u={},d=[];for(let t=0;t0)){h=s;break}h=s-1}if(s=h,i[s]===n)return s/(r-1);const l=i[s];return(s+(n-l)/(i[s+1]-l))/(r-1)}getTangent(t,e){const i=1e-4;let s=t-i,r=t+i;s<0&&(s=0),r>1&&(r=1);const n=this.getPoint(s),a=this.getPoint(r),o=e||(n.isVector2?new Gi:new Qi);return o.copy(a).sub(n).normalize(),o}getTangentAt(t,e){const i=this.getUtoTmapping(t);return this.getTangent(i,e)}computeFrenetFrames(t,e=!1){const i=new Qi,s=[],r=[],n=[],a=new Qi,o=new or;for(let e=0;e<=t;e++){const i=e/t;s[e]=this.getTangentAt(i,new Qi)}r[0]=new Qi,n[0]=new Qi;let h=Number.MAX_VALUE;const l=Math.abs(s[0].x),c=Math.abs(s[0].y),u=Math.abs(s[0].z);l<=h&&(h=l,i.set(1,0,0)),c<=h&&(h=c,i.set(0,1,0)),u<=h&&i.set(0,0,1),a.crossVectors(s[0],i).normalize(),r[0].crossVectors(s[0],a),n[0].crossVectors(s[0],r[0]);for(let e=1;e<=t;e++){if(r[e]=r[e-1].clone(),n[e]=n[e-1].clone(),a.crossVectors(s[e-1],s[e]),a.length()>Number.EPSILON){a.normalize();const t=Math.acos(Hi(s[e-1].dot(s[e]),-1,1));r[e].applyMatrix4(o.makeRotationAxis(a,t))}n[e].crossVectors(s[e],r[e])}if(!0===e){let e=Math.acos(Hi(r[0].dot(r[t]),-1,1));e/=t,s[0].dot(a.crossVectors(r[0],r[t]))>0&&(e=-e);for(let i=1;i<=t;i++)r[i].applyMatrix4(o.makeRotationAxis(s[i],e*i)),n[i].crossVectors(s[i],r[i])}return{tangents:s,normals:r,binormals:n}}clone(){return(new this.constructor).copy(this)}copy(t){return this.arcLengthDivisions=t.arcLengthDivisions,this}toJSON(){const t={metadata:{version:4.7,type:"Curve",generator:"Curve.toJSON"}};return t.arcLengthDivisions=this.arcLengthDivisions,t.type=this.type,t}fromJSON(t){return this.arcLengthDivisions=t.arcLengthDivisions,this}}class wh extends vh{constructor(t=0,e=0,i=1,s=1,r=0,n=2*Math.PI,a=!1,o=0){super(),this.isEllipseCurve=!0,this.type="EllipseCurve",this.aX=t,this.aY=e,this.xRadius=i,this.yRadius=s,this.aStartAngle=r,this.aEndAngle=n,this.aClockwise=a,this.aRotation=o}getPoint(t,e=new Gi){const i=e,s=2*Math.PI;let r=this.aEndAngle-this.aStartAngle;const n=Math.abs(r)s;)r-=s;r0?0:(Math.floor(Math.abs(h)/r)+1)*r:0===l&&h===r-1&&(h=r-2,l=1),this.closed||h>0?a=s[(h-1)%r]:(_h.subVectors(s[0],s[1]).add(s[0]),a=_h);const c=s[h%r],u=s[(h+1)%r];if(this.closed||h+2s.length-2?s.length-1:n+1],c=s[n>s.length-3?s.length-1:n+2];return i.set(Ch(a,o.x,h.x,l.x,c.x),Ch(a,o.y,h.y,l.y,c.y)),i}copy(t){super.copy(t),this.points=[];for(let e=0,i=t.points.length;e=i){const t=s[r]-i,n=this.curves[r],a=n.getLength(),o=0===a?0:1-t/a;return n.getPointAt(o,e)}r++}return null}getLength(){const t=this.getCurveLengths();return t[t.length-1]}updateArcLengths(){this.needsUpdate=!0,this.cacheLengths=null,this.getCurveLengths()}getCurveLengths(){if(this.cacheLengths&&this.cacheLengths.length===this.curves.length)return this.cacheLengths;const t=[];let e=0;for(let i=0,s=this.curves.length;i1&&!e[e.length-1].equals(e[0])&&e.push(e[0]),e}copy(t){super.copy(t),this.curves=[];for(let e=0,i=t.curves.length;e0){const t=h.getPoint(0);t.equals(this.currentPoint)||this.lineTo(t.x,t.y)}this.curves.push(h);const l=h.getPoint(1);return this.currentPoint.copy(l),this}copy(t){return super.copy(t),this.currentPoint.copy(t.currentPoint),this}toJSON(){const t=super.toJSON();return t.currentPoint=this.currentPoint.toArray(),t}fromJSON(t){return super.fromJSON(t),this.currentPoint.fromArray(t.currentPoint),this}}class Uh extends Wh{constructor(t){super(t),this.uuid=Di(),this.type="Shape",this.holes=[]}getPointsHoles(t){const e=[];for(let i=0,s=this.holes.length;i80*i){o=1/0,h=1/0;let e=-1/0,s=-1/0;for(let n=i;ne&&(e=i),r>s&&(s=r)}l=Math.max(e-o,s-h),l=0!==l?32767/l:0}return Jh(n,a,i,o,h,l,0),a}function Hh(t,e,i,s,r){let n;if(r===function(t,e,i,s){let r=0;for(let n=e,a=i-s;n0)for(let r=e;r=e;r-=s)n=dl(r/s|0,t[r],t[r+1],n);return n&&al(n,n.next)&&(pl(n),n=n.next),n}function qh(t,e){if(!t)return t;e||(e=t);let i,s=t;do{if(i=!1,s.steiner||!al(s,s.next)&&0!==nl(s.prev,s,s.next))s=s.next;else{if(pl(s),s=e=s.prev,s===s.next)break;i=!0}}while(i||s!==e);return e}function Jh(t,e,i,s,r,n,a){if(!t)return;!a&&n&&function(t,e,i,s){let r=t;do{0===r.z&&(r.z=tl(r.x,r.y,e,i,s)),r.prevZ=r.prev,r.nextZ=r.next,r=r.next}while(r!==t);r.prevZ.nextZ=null,r.prevZ=null,function(t){let e,i=1;do{let s,r=t;t=null;let n=null;for(e=0;r;){e++;let a=r,o=0;for(let t=0;t0||h>0&&a;)0!==o&&(0===h||!a||r.z<=a.z)?(s=r,r=r.nextZ,o--):(s=a,a=a.nextZ,h--),n?n.nextZ=s:t=s,s.prevZ=n,n=s;r=a}n.nextZ=null,i*=2}while(e>1)}(r)}(t,s,r,n);let o=t;for(;t.prev!==t.next;){const h=t.prev,l=t.next;if(n?Yh(t,s,r,n):Xh(t))e.push(h.i,t.i,l.i),pl(t),t=l.next,o=l.next;else if((t=l)===o){a?1===a?Jh(t=Zh(qh(t),e),e,i,s,r,n,2):2===a&&Gh(t,e,i,s,r,n):Jh(qh(t),e,i,s,r,n,1);break}}}function Xh(t){const e=t.prev,i=t,s=t.next;if(nl(e,i,s)>=0)return!1;const r=e.x,n=i.x,a=s.x,o=e.y,h=i.y,l=s.y,c=Math.min(r,n,a),u=Math.min(o,h,l),d=Math.max(r,n,a),p=Math.max(o,h,l);let m=s.next;for(;m!==e;){if(m.x>=c&&m.x<=d&&m.y>=u&&m.y<=p&&sl(r,o,n,h,a,l,m.x,m.y)&&nl(m.prev,m,m.next)>=0)return!1;m=m.next}return!0}function Yh(t,e,i,s){const r=t.prev,n=t,a=t.next;if(nl(r,n,a)>=0)return!1;const o=r.x,h=n.x,l=a.x,c=r.y,u=n.y,d=a.y,p=Math.min(o,h,l),m=Math.min(c,u,d),y=Math.max(o,h,l),g=Math.max(c,u,d),f=tl(p,m,e,i,s),x=tl(y,g,e,i,s);let b=t.prevZ,v=t.nextZ;for(;b&&b.z>=f&&v&&v.z<=x;){if(b.x>=p&&b.x<=y&&b.y>=m&&b.y<=g&&b!==r&&b!==a&&sl(o,c,h,u,l,d,b.x,b.y)&&nl(b.prev,b,b.next)>=0)return!1;if(b=b.prevZ,v.x>=p&&v.x<=y&&v.y>=m&&v.y<=g&&v!==r&&v!==a&&sl(o,c,h,u,l,d,v.x,v.y)&&nl(v.prev,v,v.next)>=0)return!1;v=v.nextZ}for(;b&&b.z>=f;){if(b.x>=p&&b.x<=y&&b.y>=m&&b.y<=g&&b!==r&&b!==a&&sl(o,c,h,u,l,d,b.x,b.y)&&nl(b.prev,b,b.next)>=0)return!1;b=b.prevZ}for(;v&&v.z<=x;){if(v.x>=p&&v.x<=y&&v.y>=m&&v.y<=g&&v!==r&&v!==a&&sl(o,c,h,u,l,d,v.x,v.y)&&nl(v.prev,v,v.next)>=0)return!1;v=v.nextZ}return!0}function Zh(t,e){let i=t;do{const s=i.prev,r=i.next.next;!al(s,r)&&ol(s,i,i.next,r)&&cl(s,r)&&cl(r,s)&&(e.push(s.i,i.i,r.i),pl(i),pl(i.next),i=t=r),i=i.next}while(i!==t);return qh(i)}function Gh(t,e,i,s,r,n){let a=t;do{let t=a.next.next;for(;t!==a.prev;){if(a.i!==t.i&&rl(a,t)){let o=ul(a,t);return a=qh(a,a.next),o=qh(o,o.next),Jh(a,e,i,s,r,n,0),void Jh(o,e,i,s,r,n,0)}t=t.next}a=a.next}while(a!==t)}function $h(t,e){let i=t.x-e.x;if(0===i&&(i=t.y-e.y,0===i)){i=(t.next.y-t.y)/(t.next.x-t.x)-(e.next.y-e.y)/(e.next.x-e.x)}return i}function Qh(t,e){const i=function(t,e){let i=e;const s=t.x,r=t.y;let n,a=-1/0;if(al(t,i))return i;do{if(al(t,i.next))return i.next;if(r<=i.y&&r>=i.next.y&&i.next.y!==i.y){const t=i.x+(r-i.y)*(i.next.x-i.x)/(i.next.y-i.y);if(t<=s&&t>a&&(a=t,n=i.x=i.x&&i.x>=h&&s!==i.x&&il(rn.x||i.x===n.x&&Kh(n,i)))&&(n=i,c=e)}i=i.next}while(i!==o);return n}(t,e);if(!i)return e;const s=ul(i,t);return qh(s,s.next),qh(i,i.next)}function Kh(t,e){return nl(t.prev,t,e.prev)<0&&nl(e.next,t,t.next)<0}function tl(t,e,i,s,r){return(t=1431655765&((t=858993459&((t=252645135&((t=16711935&((t=(t-i)*r|0)|t<<8))|t<<4))|t<<2))|t<<1))|(e=1431655765&((e=858993459&((e=252645135&((e=16711935&((e=(e-s)*r|0)|e<<8))|e<<4))|e<<2))|e<<1))<<1}function el(t){let e=t,i=t;do{(e.x=(t-a)*(n-o)&&(t-a)*(s-o)>=(i-a)*(e-o)&&(i-a)*(n-o)>=(r-a)*(s-o)}function sl(t,e,i,s,r,n,a,o){return!(t===a&&e===o)&&il(t,e,i,s,r,n,a,o)}function rl(t,e){return t.next.i!==e.i&&t.prev.i!==e.i&&!function(t,e){let i=t;do{if(i.i!==t.i&&i.next.i!==t.i&&i.i!==e.i&&i.next.i!==e.i&&ol(i,i.next,t,e))return!0;i=i.next}while(i!==t);return!1}(t,e)&&(cl(t,e)&&cl(e,t)&&function(t,e){let i=t,s=!1;const r=(t.x+e.x)/2,n=(t.y+e.y)/2;do{i.y>n!=i.next.y>n&&i.next.y!==i.y&&r<(i.next.x-i.x)*(n-i.y)/(i.next.y-i.y)+i.x&&(s=!s),i=i.next}while(i!==t);return s}(t,e)&&(nl(t.prev,t,e.prev)||nl(t,e.prev,e))||al(t,e)&&nl(t.prev,t,t.next)>0&&nl(e.prev,e,e.next)>0)}function nl(t,e,i){return(e.y-t.y)*(i.x-e.x)-(e.x-t.x)*(i.y-e.y)}function al(t,e){return t.x===e.x&&t.y===e.y}function ol(t,e,i,s){const r=ll(nl(t,e,i)),n=ll(nl(t,e,s)),a=ll(nl(i,s,t)),o=ll(nl(i,s,e));return r!==n&&a!==o||(!(0!==r||!hl(t,i,e))||(!(0!==n||!hl(t,s,e))||(!(0!==a||!hl(i,t,s))||!(0!==o||!hl(i,e,s)))))}function hl(t,e,i){return e.x<=Math.max(t.x,i.x)&&e.x>=Math.min(t.x,i.x)&&e.y<=Math.max(t.y,i.y)&&e.y>=Math.min(t.y,i.y)}function ll(t){return t>0?1:t<0?-1:0}function cl(t,e){return nl(t.prev,t,t.next)<0?nl(t,e,t.next)>=0&&nl(t,t.prev,e)>=0:nl(t,e,t.prev)<0||nl(t,t.next,e)<0}function ul(t,e){const i=ml(t.i,t.x,t.y),s=ml(e.i,e.x,e.y),r=t.next,n=e.prev;return t.next=e,e.prev=t,i.next=r,r.prev=i,s.next=i,i.prev=s,n.next=s,s.prev=n,s}function dl(t,e,i,s){const r=ml(t,e,i);return s?(r.next=s.next,r.prev=s,s.next.prev=r,s.next=r):(r.prev=r,r.next=r),r}function pl(t){t.next.prev=t.prev,t.prev.next=t.next,t.prevZ&&(t.prevZ.nextZ=t.nextZ),t.nextZ&&(t.nextZ.prevZ=t.prevZ)}function ml(t,e,i){return{i:t,x:e,y:i,prev:null,next:null,z:0,prevZ:null,nextZ:null,steiner:!1}}class yl{static triangulate(t,e,i=2){return Dh(t,e,i)}}class gl{static area(t){const e=t.length;let i=0;for(let s=e-1,r=0;r2&&t[e-1].equals(t[0])&&t.pop()}function xl(t,e){for(let i=0;iNumber.EPSILON){const u=Math.sqrt(c),d=Math.sqrt(h*h+l*l),p=e.x-o/u,m=e.y+a/u,y=((i.x-l/d-p)*l-(i.y+h/d-m)*h)/(a*l-o*h);s=p+a*y-t.x,r=m+o*y-t.y;const g=s*s+r*r;if(g<=2)return new Gi(s,r);n=Math.sqrt(g/2)}else{let t=!1;a>Number.EPSILON?h>Number.EPSILON&&(t=!0):a<-Number.EPSILON?h<-Number.EPSILON&&(t=!0):Math.sign(o)===Math.sign(l)&&(t=!0),t?(s=-o,r=a,n=Math.sqrt(c)):(s=a,r=o,n=Math.sqrt(c/2))}return new Gi(s/n,r/n)}const k=[];for(let t=0,e=z.length,i=e-1,s=t+1;t=0;t--){const e=t/p,i=c*Math.cos(e*Math.PI/2),s=u*Math.sin(e*Math.PI/2)+d;for(let t=0,e=z.length;t=0;){const s=i;let r=i-1;r<0&&(r=t.length-1);for(let t=0,i=o+2*p;t0)&&d.push(e,r,h),(t!==i-1||o0!=t>0&&this.version++,this._anisotropy=t}get clearcoat(){return this._clearcoat}set clearcoat(t){this._clearcoat>0!=t>0&&this.version++,this._clearcoat=t}get iridescence(){return this._iridescence}set iridescence(t){this._iridescence>0!=t>0&&this.version++,this._iridescence=t}get dispersion(){return this._dispersion}set dispersion(t){this._dispersion>0!=t>0&&this.version++,this._dispersion=t}get sheen(){return this._sheen}set sheen(t){this._sheen>0!=t>0&&this.version++,this._sheen=t}get transmission(){return this._transmission}set transmission(t){this._transmission>0!=t>0&&this.version++,this._transmission=t}copy(t){return super.copy(t),this.defines={STANDARD:"",PHYSICAL:""},this.anisotropy=t.anisotropy,this.anisotropyRotation=t.anisotropyRotation,this.anisotropyMap=t.anisotropyMap,this.clearcoat=t.clearcoat,this.clearcoatMap=t.clearcoatMap,this.clearcoatRoughness=t.clearcoatRoughness,this.clearcoatRoughnessMap=t.clearcoatRoughnessMap,this.clearcoatNormalMap=t.clearcoatNormalMap,this.clearcoatNormalScale.copy(t.clearcoatNormalScale),this.dispersion=t.dispersion,this.ior=t.ior,this.iridescence=t.iridescence,this.iridescenceMap=t.iridescenceMap,this.iridescenceIOR=t.iridescenceIOR,this.iridescenceThicknessRange=[...t.iridescenceThicknessRange],this.iridescenceThicknessMap=t.iridescenceThicknessMap,this.sheen=t.sheen,this.sheenColor.copy(t.sheenColor),this.sheenColorMap=t.sheenColorMap,this.sheenRoughness=t.sheenRoughness,this.sheenRoughnessMap=t.sheenRoughnessMap,this.transmission=t.transmission,this.transmissionMap=t.transmissionMap,this.thickness=t.thickness,this.thicknessMap=t.thicknessMap,this.attenuationDistance=t.attenuationDistance,this.attenuationColor.copy(t.attenuationColor),this.specularIntensity=t.specularIntensity,this.specularIntensityMap=t.specularIntensityMap,this.specularColor.copy(t.specularColor),this.specularColorMap=t.specularColorMap,this}}class Ll extends sn{constructor(t){super(),this.isMeshPhongMaterial=!0,this.type="MeshPhongMaterial",this.color=new Kr(16777215),this.specular=new Kr(1118481),this.shininess=30,this.map=null,this.lightMap=null,this.lightMapIntensity=1,this.aoMap=null,this.aoMapIntensity=1,this.emissive=new Kr(0),this.emissiveIntensity=1,this.emissiveMap=null,this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new Gi(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.specularMap=null,this.alphaMap=null,this.envMap=null,this.envMapRotation=new fr,this.combine=0,this.reflectivity=1,this.refractionRatio=.98,this.wireframe=!1,this.wireframeLinewidth=1,this.wireframeLinecap="round",this.wireframeLinejoin="round",this.flatShading=!1,this.fog=!0,this.setValues(t)}copy(t){return super.copy(t),this.color.copy(t.color),this.specular.copy(t.specular),this.shininess=t.shininess,this.map=t.map,this.lightMap=t.lightMap,this.lightMapIntensity=t.lightMapIntensity,this.aoMap=t.aoMap,this.aoMapIntensity=t.aoMapIntensity,this.emissive.copy(t.emissive),this.emissiveMap=t.emissiveMap,this.emissiveIntensity=t.emissiveIntensity,this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.specularMap=t.specularMap,this.alphaMap=t.alphaMap,this.envMap=t.envMap,this.envMapRotation.copy(t.envMapRotation),this.combine=t.combine,this.reflectivity=t.reflectivity,this.refractionRatio=t.refractionRatio,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.wireframeLinecap=t.wireframeLinecap,this.wireframeLinejoin=t.wireframeLinejoin,this.flatShading=t.flatShading,this.fog=t.fog,this}}class jl extends sn{constructor(t){super(),this.isMeshToonMaterial=!0,this.defines={TOON:""},this.type="MeshToonMaterial",this.color=new Kr(16777215),this.map=null,this.gradientMap=null,this.lightMap=null,this.lightMapIntensity=1,this.aoMap=null,this.aoMapIntensity=1,this.emissive=new Kr(0),this.emissiveIntensity=1,this.emissiveMap=null,this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new Gi(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.alphaMap=null,this.wireframe=!1,this.wireframeLinewidth=1,this.wireframeLinecap="round",this.wireframeLinejoin="round",this.fog=!0,this.setValues(t)}copy(t){return super.copy(t),this.color.copy(t.color),this.map=t.map,this.gradientMap=t.gradientMap,this.lightMap=t.lightMap,this.lightMapIntensity=t.lightMapIntensity,this.aoMap=t.aoMap,this.aoMapIntensity=t.aoMapIntensity,this.emissive.copy(t.emissive),this.emissiveMap=t.emissiveMap,this.emissiveIntensity=t.emissiveIntensity,this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.alphaMap=t.alphaMap,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.wireframeLinecap=t.wireframeLinecap,this.wireframeLinejoin=t.wireframeLinejoin,this.fog=t.fog,this}}class Wl extends sn{constructor(t){super(),this.isMeshNormalMaterial=!0,this.type="MeshNormalMaterial",this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new Gi(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.wireframe=!1,this.wireframeLinewidth=1,this.flatShading=!1,this.setValues(t)}copy(t){return super.copy(t),this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.flatShading=t.flatShading,this}}class Ul extends sn{constructor(t){super(),this.isMeshLambertMaterial=!0,this.type="MeshLambertMaterial",this.color=new Kr(16777215),this.map=null,this.lightMap=null,this.lightMapIntensity=1,this.aoMap=null,this.aoMapIntensity=1,this.emissive=new Kr(0),this.emissiveIntensity=1,this.emissiveMap=null,this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new Gi(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.specularMap=null,this.alphaMap=null,this.envMap=null,this.envMapRotation=new fr,this.combine=0,this.reflectivity=1,this.refractionRatio=.98,this.wireframe=!1,this.wireframeLinewidth=1,this.wireframeLinecap="round",this.wireframeLinejoin="round",this.flatShading=!1,this.fog=!0,this.setValues(t)}copy(t){return super.copy(t),this.color.copy(t.color),this.map=t.map,this.lightMap=t.lightMap,this.lightMapIntensity=t.lightMapIntensity,this.aoMap=t.aoMap,this.aoMapIntensity=t.aoMapIntensity,this.emissive.copy(t.emissive),this.emissiveMap=t.emissiveMap,this.emissiveIntensity=t.emissiveIntensity,this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.specularMap=t.specularMap,this.alphaMap=t.alphaMap,this.envMap=t.envMap,this.envMapRotation.copy(t.envMapRotation),this.combine=t.combine,this.reflectivity=t.reflectivity,this.refractionRatio=t.refractionRatio,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.wireframeLinecap=t.wireframeLinecap,this.wireframeLinejoin=t.wireframeLinejoin,this.flatShading=t.flatShading,this.fog=t.fog,this}}class Dl extends sn{constructor(t){super(),this.isMeshDepthMaterial=!0,this.type="MeshDepthMaterial",this.depthPacking=3200,this.map=null,this.alphaMap=null,this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.wireframe=!1,this.wireframeLinewidth=1,this.setValues(t)}copy(t){return super.copy(t),this.depthPacking=t.depthPacking,this.map=t.map,this.alphaMap=t.alphaMap,this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this}}class Hl extends sn{constructor(t){super(),this.isMeshDistanceMaterial=!0,this.type="MeshDistanceMaterial",this.map=null,this.alphaMap=null,this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.setValues(t)}copy(t){return super.copy(t),this.map=t.map,this.alphaMap=t.alphaMap,this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this}}class ql extends sn{constructor(t){super(),this.isMeshMatcapMaterial=!0,this.defines={MATCAP:""},this.type="MeshMatcapMaterial",this.color=new Kr(16777215),this.matcap=null,this.map=null,this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new Gi(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.alphaMap=null,this.flatShading=!1,this.fog=!0,this.setValues(t)}copy(t){return super.copy(t),this.defines={MATCAP:""},this.color.copy(t.color),this.matcap=t.matcap,this.map=t.map,this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.alphaMap=t.alphaMap,this.flatShading=t.flatShading,this.fog=t.fog,this}}class Jl extends Po{constructor(t){super(),this.isLineDashedMaterial=!0,this.type="LineDashedMaterial",this.scale=1,this.dashSize=3,this.gapSize=1,this.setValues(t)}copy(t){return super.copy(t),this.scale=t.scale,this.dashSize=t.dashSize,this.gapSize=t.gapSize,this}}function Xl(t,e){return t&&t.constructor!==e?"number"==typeof e.BYTES_PER_ELEMENT?new e(t):Array.prototype.slice.call(t):t}function Yl(t){return ArrayBuffer.isView(t)&&!(t instanceof DataView)}function Zl(t){const e=t.length,i=new Array(e);for(let t=0;t!==e;++t)i[t]=t;return i.sort(function(e,i){return t[e]-t[i]}),i}function Gl(t,e,i){const s=t.length,r=new t.constructor(s);for(let n=0,a=0;a!==s;++n){const s=i[n]*e;for(let i=0;i!==e;++i)r[a++]=t[s+i]}return r}function $l(t,e,i,s){let r=1,n=t[0];for(;void 0!==n&&void 0===n[s];)n=t[r++];if(void 0===n)return;let a=n[s];if(void 0!==a)if(Array.isArray(a))do{a=n[s],void 0!==a&&(e.push(n.time),i.push(...a)),n=t[r++]}while(void 0!==n);else if(void 0!==a.toArray)do{a=n[s],void 0!==a&&(e.push(n.time),a.toArray(i,i.length)),n=t[r++]}while(void 0!==n);else do{a=n[s],void 0!==a&&(e.push(n.time),i.push(a)),n=t[r++]}while(void 0!==n)}class Ql{static convertArray(t,e){return Xl(t,e)}static isTypedArray(t){return Yl(t)}static getKeyframeOrder(t){return Zl(t)}static sortedArray(t,e,i){return Gl(t,e,i)}static flattenJSON(t,e,i,s){$l(t,e,i,s)}static subclip(t,e,i,s,r=30){return function(t,e,i,s,r=30){const n=t.clone();n.name=e;const a=[];for(let t=0;t=s)){h.push(e.times[t]);for(let i=0;in.tracks[t].times[0]&&(o=n.tracks[t].times[0]);for(let t=0;t=s.times[u]){const t=u*h+o,e=t+h-o;d=s.values.slice(t,e)}else{const t=s.createInterpolant(),e=o,i=h-o;t.evaluate(n),d=t.resultBuffer.slice(e,i)}"quaternion"===r&&(new $i).fromArray(d).normalize().conjugate().toArray(d);const p=a.times.length;for(let t=0;t=r)){const a=e[1];t=r)break e}n=i,i=0;break i}break t}for(;i>>1;te;)--n;if(++n,0!==r||n!==s){r>=n&&(n=Math.max(n,1),r=n-1);const t=this.getValueSize();this.times=i.slice(r,n),this.values=this.values.slice(r*t,n*t)}return this}validate(){let t=!0;const e=this.getValueSize();e-Math.floor(e)!==0&&(console.error("THREE.KeyframeTrack: Invalid value size in track.",this),t=!1);const i=this.times,s=this.values,r=i.length;0===r&&(console.error("THREE.KeyframeTrack: Track is empty.",this),t=!1);let n=null;for(let e=0;e!==r;e++){const s=i[e];if("number"==typeof s&&isNaN(s)){console.error("THREE.KeyframeTrack: Time is not a valid number.",this,e,s),t=!1;break}if(null!==n&&n>s){console.error("THREE.KeyframeTrack: Out of order keys.",this,e,s,n),t=!1;break}n=s}if(void 0!==s&&Yl(s))for(let e=0,i=s.length;e!==i;++e){const i=s[e];if(isNaN(i)){console.error("THREE.KeyframeTrack: Value is not a valid number.",this,e,i),t=!1;break}}return t}optimize(){const t=this.times.slice(),e=this.values.slice(),i=this.getValueSize(),s=this.getInterpolation()===Ee,r=t.length-1;let n=1;for(let a=1;a0){t[n]=t[r];for(let t=r*i,s=n*i,a=0;a!==i;++a)e[s+a]=e[t+a];++n}return n!==t.length?(this.times=t.slice(0,n),this.values=e.slice(0,n*i)):(this.times=t,this.values=e),this}clone(){const t=this.times.slice(),e=this.values.slice(),i=new(0,this.constructor)(this.name,t,e);return i.createInterpolant=this.createInterpolant,i}}sc.prototype.ValueTypeName="",sc.prototype.TimeBufferType=Float32Array,sc.prototype.ValueBufferType=Float32Array,sc.prototype.DefaultInterpolation=ke;class rc extends sc{constructor(t,e,i){super(t,e,i)}}rc.prototype.ValueTypeName="bool",rc.prototype.ValueBufferType=Array,rc.prototype.DefaultInterpolation=Be,rc.prototype.InterpolantFactoryMethodLinear=void 0,rc.prototype.InterpolantFactoryMethodSmooth=void 0;class nc extends sc{constructor(t,e,i,s){super(t,e,i,s)}}nc.prototype.ValueTypeName="color";class ac extends sc{constructor(t,e,i,s){super(t,e,i,s)}}ac.prototype.ValueTypeName="number";class oc extends Kl{constructor(t,e,i,s){super(t,e,i,s)}interpolate_(t,e,i,s){const r=this.resultBuffer,n=this.sampleValues,a=this.valueSize,o=(i-e)/(s-e);let h=t*a;for(let t=h+a;h!==t;h+=4)$i.slerpFlat(r,0,n,h-a,n,h,o);return r}}class hc extends sc{constructor(t,e,i,s){super(t,e,i,s)}InterpolantFactoryMethodLinear(t){return new oc(this.times,this.values,this.getValueSize(),t)}}hc.prototype.ValueTypeName="quaternion",hc.prototype.InterpolantFactoryMethodSmooth=void 0;class lc extends sc{constructor(t,e,i){super(t,e,i)}}lc.prototype.ValueTypeName="string",lc.prototype.ValueBufferType=Array,lc.prototype.DefaultInterpolation=Be,lc.prototype.InterpolantFactoryMethodLinear=void 0,lc.prototype.InterpolantFactoryMethodSmooth=void 0;class cc extends sc{constructor(t,e,i,s){super(t,e,i,s)}}cc.prototype.ValueTypeName="vector";class uc{constructor(t="",e=-1,i=[],s=2500){this.name=t,this.tracks=i,this.duration=e,this.blendMode=s,this.uuid=Di(),this.duration<0&&this.resetDuration()}static parse(t){const e=[],i=t.tracks,s=1/(t.fps||1);for(let t=0,r=i.length;t!==r;++t)e.push(dc(i[t]).scale(s));const r=new this(t.name,t.duration,e,t.blendMode);return r.uuid=t.uuid,r}static toJSON(t){const e=[],i=t.tracks,s={name:t.name,duration:t.duration,tracks:e,uuid:t.uuid,blendMode:t.blendMode};for(let t=0,s=i.length;t!==s;++t)e.push(sc.toJSON(i[t]));return s}static CreateFromMorphTargetSequence(t,e,i,s){const r=e.length,n=[];for(let t=0;t1){const t=n[1];let e=s[t];e||(s[t]=e=[]),e.push(i)}}const n=[];for(const t in s)n.push(this.CreateFromMorphTargetSequence(t,s[t],e,i));return n}static parseAnimation(t,e){if(console.warn("THREE.AnimationClip: parseAnimation() is deprecated and will be removed with r185"),!t)return console.error("THREE.AnimationClip: No animation in JSONLoader data."),null;const i=function(t,e,i,s,r){if(0!==i.length){const n=[],a=[];$l(i,n,a,s),0!==n.length&&r.push(new t(e,n,a))}},s=[],r=t.name||"default",n=t.fps||30,a=t.blendMode;let o=t.length||-1;const h=t.hierarchy||[];for(let t=0;t{e&&e(r),this.manager.itemEnd(t)},0),r;if(void 0!==fc[t])return void fc[t].push({onLoad:e,onProgress:i,onError:s});fc[t]=[],fc[t].push({onLoad:e,onProgress:i,onError:s});const n=new Request(t,{headers:new Headers(this.requestHeader),credentials:this.withCredentials?"include":"same-origin"}),a=this.mimeType,o=this.responseType;fetch(n).then(e=>{if(200===e.status||0===e.status){if(0===e.status&&console.warn("THREE.FileLoader: HTTP Status 0 received."),"undefined"==typeof ReadableStream||void 0===e.body||void 0===e.body.getReader)return e;const i=fc[t],s=e.body.getReader(),r=e.headers.get("X-File-Size")||e.headers.get("Content-Length"),n=r?parseInt(r):0,a=0!==n;let o=0;const h=new ReadableStream({start(t){!function e(){s.read().then(({done:s,value:r})=>{if(s)t.close();else{o+=r.byteLength;const s=new ProgressEvent("progress",{lengthComputable:a,loaded:o,total:n});for(let t=0,e=i.length;t{t.error(e)})}()}});return new Response(h)}throw new xc(`fetch for "${e.url}" responded with ${e.status}: ${e.statusText}`,e)}).then(t=>{switch(o){case"arraybuffer":return t.arrayBuffer();case"blob":return t.blob();case"document":return t.text().then(t=>(new DOMParser).parseFromString(t,a));case"json":return t.json();default:if(""===a)return t.text();{const e=/charset="?([^;"\s]*)"?/i.exec(a),i=e&&e[1]?e[1].toLowerCase():void 0,s=new TextDecoder(i);return t.arrayBuffer().then(t=>s.decode(t))}}}).then(e=>{pc.add(`file:${t}`,e);const i=fc[t];delete fc[t];for(let t=0,s=i.length;t{const i=fc[t];if(void 0===i)throw this.manager.itemError(t),e;delete fc[t];for(let t=0,s=i.length;t{this.manager.itemEnd(t)}),this.manager.itemStart(t)}setResponseType(t){return this.responseType=t,this}setMimeType(t){return this.mimeType=t,this}}class vc extends gc{constructor(t){super(t)}load(t,e,i,s){const r=this,n=new bc(this.manager);n.setPath(this.path),n.setRequestHeader(this.requestHeader),n.setWithCredentials(this.withCredentials),n.load(t,function(i){try{e(r.parse(JSON.parse(i)))}catch(e){s?s(e):console.error(e),r.manager.itemError(t)}},i,s)}parse(t){const e=[];for(let i=0;i0:s.vertexColors=t.vertexColors),void 0!==t.uniforms)for(const e in t.uniforms){const r=t.uniforms[e];switch(s.uniforms[e]={},r.type){case"t":s.uniforms[e].value=i(r.value);break;case"c":s.uniforms[e].value=(new Kr).setHex(r.value);break;case"v2":s.uniforms[e].value=(new Gi).fromArray(r.value);break;case"v3":s.uniforms[e].value=(new Qi).fromArray(r.value);break;case"v4":s.uniforms[e].value=(new zs).fromArray(r.value);break;case"m3":s.uniforms[e].value=(new es).fromArray(r.value);break;case"m4":s.uniforms[e].value=(new or).fromArray(r.value);break;default:s.uniforms[e].value=r.value}}if(void 0!==t.defines&&(s.defines=t.defines),void 0!==t.vertexShader&&(s.vertexShader=t.vertexShader),void 0!==t.fragmentShader&&(s.fragmentShader=t.fragmentShader),void 0!==t.glslVersion&&(s.glslVersion=t.glslVersion),void 0!==t.extensions)for(const e in t.extensions)s.extensions[e]=t.extensions[e];if(void 0!==t.lights&&(s.lights=t.lights),void 0!==t.clipping&&(s.clipping=t.clipping),void 0!==t.size&&(s.size=t.size),void 0!==t.sizeAttenuation&&(s.sizeAttenuation=t.sizeAttenuation),void 0!==t.map&&(s.map=i(t.map)),void 0!==t.matcap&&(s.matcap=i(t.matcap)),void 0!==t.alphaMap&&(s.alphaMap=i(t.alphaMap)),void 0!==t.bumpMap&&(s.bumpMap=i(t.bumpMap)),void 0!==t.bumpScale&&(s.bumpScale=t.bumpScale),void 0!==t.normalMap&&(s.normalMap=i(t.normalMap)),void 0!==t.normalMapType&&(s.normalMapType=t.normalMapType),void 0!==t.normalScale){let e=t.normalScale;!1===Array.isArray(e)&&(e=[e,e]),s.normalScale=(new Gi).fromArray(e)}return void 0!==t.displacementMap&&(s.displacementMap=i(t.displacementMap)),void 0!==t.displacementScale&&(s.displacementScale=t.displacementScale),void 0!==t.displacementBias&&(s.displacementBias=t.displacementBias),void 0!==t.roughnessMap&&(s.roughnessMap=i(t.roughnessMap)),void 0!==t.metalnessMap&&(s.metalnessMap=i(t.metalnessMap)),void 0!==t.emissiveMap&&(s.emissiveMap=i(t.emissiveMap)),void 0!==t.emissiveIntensity&&(s.emissiveIntensity=t.emissiveIntensity),void 0!==t.specularMap&&(s.specularMap=i(t.specularMap)),void 0!==t.specularIntensityMap&&(s.specularIntensityMap=i(t.specularIntensityMap)),void 0!==t.specularColorMap&&(s.specularColorMap=i(t.specularColorMap)),void 0!==t.envMap&&(s.envMap=i(t.envMap)),void 0!==t.envMapRotation&&s.envMapRotation.fromArray(t.envMapRotation),void 0!==t.envMapIntensity&&(s.envMapIntensity=t.envMapIntensity),void 0!==t.reflectivity&&(s.reflectivity=t.reflectivity),void 0!==t.refractionRatio&&(s.refractionRatio=t.refractionRatio),void 0!==t.lightMap&&(s.lightMap=i(t.lightMap)),void 0!==t.lightMapIntensity&&(s.lightMapIntensity=t.lightMapIntensity),void 0!==t.aoMap&&(s.aoMap=i(t.aoMap)),void 0!==t.aoMapIntensity&&(s.aoMapIntensity=t.aoMapIntensity),void 0!==t.gradientMap&&(s.gradientMap=i(t.gradientMap)),void 0!==t.clearcoatMap&&(s.clearcoatMap=i(t.clearcoatMap)),void 0!==t.clearcoatRoughnessMap&&(s.clearcoatRoughnessMap=i(t.clearcoatRoughnessMap)),void 0!==t.clearcoatNormalMap&&(s.clearcoatNormalMap=i(t.clearcoatNormalMap)),void 0!==t.clearcoatNormalScale&&(s.clearcoatNormalScale=(new Gi).fromArray(t.clearcoatNormalScale)),void 0!==t.iridescenceMap&&(s.iridescenceMap=i(t.iridescenceMap)),void 0!==t.iridescenceThicknessMap&&(s.iridescenceThicknessMap=i(t.iridescenceThicknessMap)),void 0!==t.transmissionMap&&(s.transmissionMap=i(t.transmissionMap)),void 0!==t.thicknessMap&&(s.thicknessMap=i(t.thicknessMap)),void 0!==t.anisotropyMap&&(s.anisotropyMap=i(t.anisotropyMap)),void 0!==t.sheenColorMap&&(s.sheenColorMap=i(t.sheenColorMap)),void 0!==t.sheenRoughnessMap&&(s.sheenRoughnessMap=i(t.sheenRoughnessMap)),s}setTextures(t){return this.textures=t,this}createMaterialFromType(t){return Xc.createMaterialFromType(t)}static createMaterialFromType(t){return new{ShadowMaterial:Ol,SpriteMaterial:ma,RawShaderMaterial:Nl,ShaderMaterial:Zn,PointsMaterial:Yo,MeshPhysicalMaterial:Vl,MeshStandardMaterial:Fl,MeshPhongMaterial:Ll,MeshToonMaterial:jl,MeshNormalMaterial:Wl,MeshLambertMaterial:Ul,MeshDepthMaterial:Dl,MeshDistanceMaterial:Hl,MeshBasicMaterial:rn,MeshMatcapMaterial:ql,LineDashedMaterial:Jl,LineBasicMaterial:Po,Material:sn}[t]}}class Yc{static extractUrlBase(t){const e=t.lastIndexOf("/");return-1===e?"./":t.slice(0,e+1)}static resolveURL(t,e){return"string"!=typeof t||""===t?"":(/^https?:\/\//i.test(e)&&/^\//.test(t)&&(e=e.replace(/(^https?:\/\/[^\/]+).*/i,"$1")),/^(https?:)?\/\//i.test(t)||/^data:.*,.*$/i.test(t)||/^blob:.*$/i.test(t)?t:e+t)}}class Zc extends Bn{constructor(){super(),this.isInstancedBufferGeometry=!0,this.type="InstancedBufferGeometry",this.instanceCount=1/0}copy(t){return super.copy(t),this.instanceCount=t.instanceCount,this}toJSON(){const t=super.toJSON();return t.instanceCount=this.instanceCount,t.isInstancedBufferGeometry=!0,t}}class Gc extends gc{constructor(t){super(t)}load(t,e,i,s){const r=this,n=new bc(r.manager);n.setPath(r.path),n.setRequestHeader(r.requestHeader),n.setWithCredentials(r.withCredentials),n.load(t,function(i){try{e(r.parse(JSON.parse(i)))}catch(e){s?s(e):console.error(e),r.manager.itemError(t)}},i,s)}parse(t){const e={},i={};function s(t,s){if(void 0!==e[s])return e[s];const r=t.interleavedBuffers[s],n=function(t,e){if(void 0!==i[e])return i[e];const s=t.arrayBuffers,r=s[e],n=new Uint32Array(r).buffer;return i[e]=n,n}(t,r.buffer),a=ns(r.type,n),o=new ua(a,r.stride);return o.uuid=r.uuid,e[s]=o,o}const r=t.isInstancedBufferGeometry?new Zc:new Bn,n=t.data.index;if(void 0!==n){const t=ns(n.type,n.array);r.setIndex(new pn(t,1))}const a=t.data.attributes;for(const e in a){const i=a[e];let n;if(i.isInterleavedBufferAttribute){const e=s(t.data,i.data);n=new pa(e,i.itemSize,i.offset,i.normalized)}else{const t=ns(i.type,i.array);n=new(i.isInstancedBufferAttribute?Ya:pn)(t,i.itemSize,i.normalized)}void 0!==i.name&&(n.name=i.name),void 0!==i.usage&&n.setUsage(i.usage),r.setAttribute(e,n)}const o=t.data.morphAttributes;if(o)for(const e in o){const i=o[e],n=[];for(let e=0,r=i.length;e0){const i=new mc(e);r=new Sc(i),r.setCrossOrigin(this.crossOrigin);for(let e=0,i=t.length;e0){s=new Sc(this.manager),s.setCrossOrigin(this.crossOrigin);for(let e=0,s=t.length;e{let e=null,i=null;return void 0!==t.boundingBox&&(e=(new Ps).fromJSON(t.boundingBox)),void 0!==t.boundingSphere&&(i=(new Qs).fromJSON(t.boundingSphere)),{...t,boundingBox:e,boundingSphere:i}}),n._instanceInfo=t.instanceInfo,n._availableInstanceIds=t._availableInstanceIds,n._availableGeometryIds=t._availableGeometryIds,n._nextIndexStart=t.nextIndexStart,n._nextVertexStart=t.nextVertexStart,n._geometryCount=t.geometryCount,n._maxInstanceCount=t.maxInstanceCount,n._maxVertexCount=t.maxVertexCount,n._maxIndexCount=t.maxIndexCount,n._geometryInitialized=t.geometryInitialized,n._matricesTexture=c(t.matricesTexture.uuid),n._indirectTexture=c(t.indirectTexture.uuid),void 0!==t.colorsTexture&&(n._colorsTexture=c(t.colorsTexture.uuid)),void 0!==t.boundingSphere&&(n.boundingSphere=(new Qs).fromJSON(t.boundingSphere)),void 0!==t.boundingBox&&(n.boundingBox=(new Ps).fromJSON(t.boundingBox));break;case"LOD":n=new Ea;break;case"Line":n=new Uo(h(t.geometry),l(t.material));break;case"LineLoop":n=new Xo(h(t.geometry),l(t.material));break;case"LineSegments":n=new Jo(h(t.geometry),l(t.material));break;case"PointCloud":case"Points":n=new Ko(h(t.geometry),l(t.material));break;case"Sprite":n=new Ia(l(t.material));break;case"Group":n=new na;break;case"Bone":n=new Da;break;default:n=new Pr}if(n.uuid=t.uuid,void 0!==t.name&&(n.name=t.name),void 0!==t.matrix?(n.matrix.fromArray(t.matrix),void 0!==t.matrixAutoUpdate&&(n.matrixAutoUpdate=t.matrixAutoUpdate),n.matrixAutoUpdate&&n.matrix.decompose(n.position,n.quaternion,n.scale)):(void 0!==t.position&&n.position.fromArray(t.position),void 0!==t.rotation&&n.rotation.fromArray(t.rotation),void 0!==t.quaternion&&n.quaternion.fromArray(t.quaternion),void 0!==t.scale&&n.scale.fromArray(t.scale)),void 0!==t.up&&n.up.fromArray(t.up),void 0!==t.castShadow&&(n.castShadow=t.castShadow),void 0!==t.receiveShadow&&(n.receiveShadow=t.receiveShadow),t.shadow&&(void 0!==t.shadow.intensity&&(n.shadow.intensity=t.shadow.intensity),void 0!==t.shadow.bias&&(n.shadow.bias=t.shadow.bias),void 0!==t.shadow.normalBias&&(n.shadow.normalBias=t.shadow.normalBias),void 0!==t.shadow.radius&&(n.shadow.radius=t.shadow.radius),void 0!==t.shadow.mapSize&&n.shadow.mapSize.fromArray(t.shadow.mapSize),void 0!==t.shadow.camera&&(n.shadow.camera=this.parseObject(t.shadow.camera))),void 0!==t.visible&&(n.visible=t.visible),void 0!==t.frustumCulled&&(n.frustumCulled=t.frustumCulled),void 0!==t.renderOrder&&(n.renderOrder=t.renderOrder),void 0!==t.userData&&(n.userData=t.userData),void 0!==t.layers&&(n.layers.mask=t.layers),void 0!==t.children){const a=t.children;for(let t=0;t{if(!0!==eu.has(n))return e&&e(i),r.manager.itemEnd(t),i;s&&s(eu.get(n)),r.manager.itemError(t),r.manager.itemEnd(t)}):(setTimeout(function(){e&&e(n),r.manager.itemEnd(t)},0),n);const a={};a.credentials="anonymous"===this.crossOrigin?"same-origin":"include",a.headers=this.requestHeader;const o=fetch(t,a).then(function(t){return t.blob()}).then(function(t){return createImageBitmap(t,Object.assign(r.options,{colorSpaceConversion:"none"}))}).then(function(i){return pc.add(`image-bitmap:${t}`,i),e&&e(i),r.manager.itemEnd(t),i}).catch(function(e){s&&s(e),eu.set(o,e),pc.remove(`image-bitmap:${t}`),r.manager.itemError(t),r.manager.itemEnd(t)});pc.add(`image-bitmap:${t}`,o),r.manager.itemStart(t)}}let su;class ru{static getContext(){return void 0===su&&(su=new(window.AudioContext||window.webkitAudioContext)),su}static setContext(t){su=t}}class nu extends gc{constructor(t){super(t)}load(t,e,i,s){const r=this,n=new bc(this.manager);function a(e){s?s(e):console.error(e),r.manager.itemError(t)}n.setResponseType("arraybuffer"),n.setPath(this.path),n.setRequestHeader(this.requestHeader),n.setWithCredentials(this.withCredentials),n.load(t,function(t){try{const i=t.slice(0);ru.getContext().decodeAudioData(i,function(t){e(t)}).catch(a)}catch(t){a(t)}},i,s)}}const au=new or,ou=new or,hu=new or;class lu{constructor(){this.type="StereoCamera",this.aspect=1,this.eyeSep=.064,this.cameraL=new ta,this.cameraL.layers.enable(1),this.cameraL.matrixAutoUpdate=!1,this.cameraR=new ta,this.cameraR.layers.enable(2),this.cameraR.matrixAutoUpdate=!1,this._cache={focus:null,fov:null,aspect:null,near:null,far:null,zoom:null,eyeSep:null}}update(t){const e=this._cache;if(e.focus!==t.focus||e.fov!==t.fov||e.aspect!==t.aspect*this.aspect||e.near!==t.near||e.far!==t.far||e.zoom!==t.zoom||e.eyeSep!==this.eyeSep){e.focus=t.focus,e.fov=t.fov,e.aspect=t.aspect*this.aspect,e.near=t.near,e.far=t.far,e.zoom=t.zoom,e.eyeSep=this.eyeSep,hu.copy(t.projectionMatrix);const i=e.eyeSep/2,s=i*e.near/e.focus,r=e.near*Math.tan(Wi*e.fov*.5)/e.zoom;let n,a;ou.elements[12]=-i,au.elements[12]=i,n=-r*e.aspect+s,a=r*e.aspect+s,hu.elements[0]=2*e.near/(a-n),hu.elements[8]=(a+n)/(a-n),this.cameraL.projectionMatrix.copy(hu),n=-r*e.aspect-s,a=r*e.aspect-s,hu.elements[0]=2*e.near/(a-n),hu.elements[8]=(a+n)/(a-n),this.cameraR.projectionMatrix.copy(hu)}this.cameraL.matrixWorld.copy(t.matrixWorld).multiply(ou),this.cameraR.matrixWorld.copy(t.matrixWorld).multiply(au)}}class cu extends ta{constructor(t=[]){super(),this.isArrayCamera=!0,this.isMultiViewCamera=!1,this.cameras=t}}class uu{constructor(t=!0){this.autoStart=t,this.startTime=0,this.oldTime=0,this.elapsedTime=0,this.running=!1}start(){this.startTime=performance.now(),this.oldTime=this.startTime,this.elapsedTime=0,this.running=!0}stop(){this.getElapsedTime(),this.running=!1,this.autoStart=!1}getElapsedTime(){return this.getDelta(),this.elapsedTime}getDelta(){let t=0;if(this.autoStart&&!this.running)return this.start(),0;if(this.running){const e=performance.now();t=(e-this.oldTime)/1e3,this.oldTime=e,this.elapsedTime+=t}return t}}const du=new Qi,pu=new $i,mu=new Qi,yu=new Qi,gu=new Qi;class fu extends Pr{constructor(){super(),this.type="AudioListener",this.context=ru.getContext(),this.gain=this.context.createGain(),this.gain.connect(this.context.destination),this.filter=null,this.timeDelta=0,this._clock=new uu}getInput(){return this.gain}removeFilter(){return null!==this.filter&&(this.gain.disconnect(this.filter),this.filter.disconnect(this.context.destination),this.gain.connect(this.context.destination),this.filter=null),this}getFilter(){return this.filter}setFilter(t){return null!==this.filter?(this.gain.disconnect(this.filter),this.filter.disconnect(this.context.destination)):this.gain.disconnect(this.context.destination),this.filter=t,this.gain.connect(this.filter),this.filter.connect(this.context.destination),this}getMasterVolume(){return this.gain.gain.value}setMasterVolume(t){return this.gain.gain.setTargetAtTime(t,this.context.currentTime,.01),this}updateMatrixWorld(t){super.updateMatrixWorld(t);const e=this.context.listener;if(this.timeDelta=this._clock.getDelta(),this.matrixWorld.decompose(du,pu,mu),yu.set(0,0,-1).applyQuaternion(pu),gu.set(0,1,0).applyQuaternion(pu),e.positionX){const t=this.context.currentTime+this.timeDelta;e.positionX.linearRampToValueAtTime(du.x,t),e.positionY.linearRampToValueAtTime(du.y,t),e.positionZ.linearRampToValueAtTime(du.z,t),e.forwardX.linearRampToValueAtTime(yu.x,t),e.forwardY.linearRampToValueAtTime(yu.y,t),e.forwardZ.linearRampToValueAtTime(yu.z,t),e.upX.linearRampToValueAtTime(gu.x,t),e.upY.linearRampToValueAtTime(gu.y,t),e.upZ.linearRampToValueAtTime(gu.z,t)}else e.setPosition(du.x,du.y,du.z),e.setOrientation(yu.x,yu.y,yu.z,gu.x,gu.y,gu.z)}}class xu extends Pr{constructor(t){super(),this.type="Audio",this.listener=t,this.context=t.context,this.gain=this.context.createGain(),this.gain.connect(t.getInput()),this.autoplay=!1,this.buffer=null,this.detune=0,this.loop=!1,this.loopStart=0,this.loopEnd=0,this.offset=0,this.duration=void 0,this.playbackRate=1,this.isPlaying=!1,this.hasPlaybackControl=!0,this.source=null,this.sourceType="empty",this._startedAt=0,this._progress=0,this._connected=!1,this.filters=[]}getOutput(){return this.gain}setNodeSource(t){return this.hasPlaybackControl=!1,this.sourceType="audioNode",this.source=t,this.connect(),this}setMediaElementSource(t){return this.hasPlaybackControl=!1,this.sourceType="mediaNode",this.source=this.context.createMediaElementSource(t),this.connect(),this}setMediaStreamSource(t){return this.hasPlaybackControl=!1,this.sourceType="mediaStreamNode",this.source=this.context.createMediaStreamSource(t),this.connect(),this}setBuffer(t){return this.buffer=t,this.sourceType="buffer",this.autoplay&&this.play(),this}play(t=0){if(!0===this.isPlaying)return void console.warn("THREE.Audio: Audio is already playing.");if(!1===this.hasPlaybackControl)return void console.warn("THREE.Audio: this Audio has no playback control.");this._startedAt=this.context.currentTime+t;const e=this.context.createBufferSource();return e.buffer=this.buffer,e.loop=this.loop,e.loopStart=this.loopStart,e.loopEnd=this.loopEnd,e.onended=this.onEnded.bind(this),e.start(this._startedAt,this._progress+this.offset,this.duration),this.isPlaying=!0,this.source=e,this.setDetune(this.detune),this.setPlaybackRate(this.playbackRate),this.connect()}pause(){if(!1!==this.hasPlaybackControl)return!0===this.isPlaying&&(this._progress+=Math.max(this.context.currentTime-this._startedAt,0)*this.playbackRate,!0===this.loop&&(this._progress=this._progress%(this.duration||this.buffer.duration)),this.source.stop(),this.source.onended=null,this.isPlaying=!1),this;console.warn("THREE.Audio: this Audio has no playback control.")}stop(t=0){if(!1!==this.hasPlaybackControl)return this._progress=0,null!==this.source&&(this.source.stop(this.context.currentTime+t),this.source.onended=null),this.isPlaying=!1,this;console.warn("THREE.Audio: this Audio has no playback control.")}connect(){if(this.filters.length>0){this.source.connect(this.filters[0]);for(let t=1,e=this.filters.length;t0){this.source.disconnect(this.filters[0]);for(let t=1,e=this.filters.length;t0&&this._mixBufferRegionAdditive(i,s,this._addIndex*e,1,e);for(let t=e,r=e+e;t!==r;++t)if(i[t]!==i[t+e]){a.setValue(i,s);break}}saveOriginalState(){const t=this.binding,e=this.buffer,i=this.valueSize,s=i*this._origIndex;t.getValue(e,s);for(let t=i,r=s;t!==r;++t)e[t]=e[s+t%i];this._setIdentity(),this.cumulativeWeight=0,this.cumulativeWeightAdditive=0}restoreOriginalState(){const t=3*this.valueSize;this.binding.setValue(this.buffer,t)}_setAdditiveIdentityNumeric(){const t=this._addIndex*this.valueSize,e=t+this.valueSize;for(let i=t;i=.5)for(let s=0;s!==r;++s)t[e+s]=t[i+s]}_slerp(t,e,i,s){$i.slerpFlat(t,e,t,e,t,i,s)}_slerpAdditive(t,e,i,s,r){const n=this._workIndex*r;$i.multiplyQuaternionsFlat(t,n,t,e,t,i),$i.slerpFlat(t,e,t,e,t,n,s)}_lerp(t,e,i,s,r){const n=1-s;for(let a=0;a!==r;++a){const r=e+a;t[r]=t[r]*n+t[i+a]*s}}_lerpAdditive(t,e,i,s,r){for(let n=0;n!==r;++n){const r=e+n;t[r]=t[r]+t[i+n]*s}}}const Tu="\\[\\]\\.:\\/",zu=new RegExp("["+Tu+"]","g"),Iu="[^"+Tu+"]",Cu="[^"+Tu.replace("\\.","")+"]",Bu=new RegExp("^"+/((?:WC+[\/:])*)/.source.replace("WC",Iu)+/(WCOD+)?/.source.replace("WCOD",Cu)+/(?:\.(WC+)(?:\[(.+)\])?)?/.source.replace("WC",Iu)+/\.(WC+)(?:\[(.+)\])?/.source.replace("WC",Iu)+"$"),ku=["material","materials","bones","map"];class Eu{constructor(t,e,i){this.path=e,this.parsedPath=i||Eu.parseTrackName(e),this.node=Eu.findNode(t,this.parsedPath.nodeName),this.rootNode=t,this.getValue=this._getValue_unbound,this.setValue=this._setValue_unbound}static create(t,e,i){return t&&t.isAnimationObjectGroup?new Eu.Composite(t,e,i):new Eu(t,e,i)}static sanitizeNodeName(t){return t.replace(/\s/g,"_").replace(zu,"")}static parseTrackName(t){const e=Bu.exec(t);if(null===e)throw new Error("PropertyBinding: Cannot parse trackName: "+t);const i={nodeName:e[2],objectName:e[3],objectIndex:e[4],propertyName:e[5],propertyIndex:e[6]},s=i.nodeName&&i.nodeName.lastIndexOf(".");if(void 0!==s&&-1!==s){const t=i.nodeName.substring(s+1);-1!==ku.indexOf(t)&&(i.nodeName=i.nodeName.substring(0,s),i.objectName=t)}if(null===i.propertyName||0===i.propertyName.length)throw new Error("PropertyBinding: can not parse propertyName from trackName: "+t);return i}static findNode(t,e){if(void 0===e||""===e||"."===e||-1===e||e===t.name||e===t.uuid)return t;if(t.skeleton){const i=t.skeleton.getBoneByName(e);if(void 0!==i)return i}if(t.children){const i=function(t){for(let s=0;s=r){const n=r++,l=t[n];e[l.uuid]=h,t[h]=l,e[o]=n,t[n]=a;for(let t=0,e=s;t!==e;++t){const e=i[t],s=e[n],r=e[h];e[h]=s,e[n]=r}}}this.nCachedObjects_=r}uncache(){const t=this._objects,e=this._indicesByUUID,i=this._bindings,s=i.length;let r=this.nCachedObjects_,n=t.length;for(let a=0,o=arguments.length;a!==o;++a){const o=arguments[a].uuid,h=e[o];if(void 0!==h)if(delete e[o],h0&&(e[a.uuid]=h),t[h]=a,t.pop();for(let t=0,e=s;t!==e;++t){const e=i[t];e[h]=e[r],e.pop()}}}this.nCachedObjects_=r}subscribe_(t,e){const i=this._bindingsIndicesByPath;let s=i[t];const r=this._bindings;if(void 0!==s)return r[s];const n=this._paths,a=this._parsedPaths,o=this._objects,h=o.length,l=this.nCachedObjects_,c=new Array(h);s=r.length,i[t]=s,n.push(t),a.push(e),r.push(c);for(let i=l,s=o.length;i!==s;++i){const s=o[i];c[i]=new Eu(s,t,e)}return c}unsubscribe_(t){const e=this._bindingsIndicesByPath,i=e[t];if(void 0!==i){const s=this._paths,r=this._parsedPaths,n=this._bindings,a=n.length-1,o=n[a];e[t[a]]=i,n[i]=o,n.pop(),r[i]=r[a],r.pop(),s[i]=s[a],s.pop()}}}class Pu{constructor(t,e,i=null,s=e.blendMode){this._mixer=t,this._clip=e,this._localRoot=i,this.blendMode=s;const r=e.tracks,n=r.length,a=new Array(n),o={endingStart:Re,endingEnd:Re};for(let t=0;t!==n;++t){const e=r[t].createInterpolant(null);a[t]=e,e.settings=o}this._interpolantSettings=o,this._interpolants=a,this._propertyBindings=new Array(n),this._cacheIndex=null,this._byClipCacheIndex=null,this._timeScaleInterpolant=null,this._weightInterpolant=null,this.loop=2201,this._loopCount=-1,this._startTime=null,this.time=0,this.timeScale=1,this._effectiveTimeScale=1,this.weight=1,this._effectiveWeight=1,this.repetitions=1/0,this.paused=!1,this.enabled=!0,this.clampWhenFinished=!1,this.zeroSlopeAtStart=!0,this.zeroSlopeAtEnd=!0}play(){return this._mixer._activateAction(this),this}stop(){return this._mixer._deactivateAction(this),this.reset()}reset(){return this.paused=!1,this.enabled=!0,this.time=0,this._loopCount=-1,this._startTime=null,this.stopFading().stopWarping()}isRunning(){return this.enabled&&!this.paused&&0!==this.timeScale&&null===this._startTime&&this._mixer._isActiveAction(this)}isScheduled(){return this._mixer._isActiveAction(this)}startAt(t){return this._startTime=t,this}setLoop(t,e){return this.loop=t,this.repetitions=e,this}setEffectiveWeight(t){return this.weight=t,this._effectiveWeight=this.enabled?t:0,this.stopFading()}getEffectiveWeight(){return this._effectiveWeight}fadeIn(t){return this._scheduleFading(t,0,1)}fadeOut(t){return this._scheduleFading(t,1,0)}crossFadeFrom(t,e,i=!1){if(t.fadeOut(e),this.fadeIn(e),!0===i){const i=this._clip.duration,s=t._clip.duration,r=s/i,n=i/s;t.warp(1,r,e),this.warp(n,1,e)}return this}crossFadeTo(t,e,i=!1){return t.crossFadeFrom(this,e,i)}stopFading(){const t=this._weightInterpolant;return null!==t&&(this._weightInterpolant=null,this._mixer._takeBackControlInterpolant(t)),this}setEffectiveTimeScale(t){return this.timeScale=t,this._effectiveTimeScale=this.paused?0:t,this.stopWarping()}getEffectiveTimeScale(){return this._effectiveTimeScale}setDuration(t){return this.timeScale=this._clip.duration/t,this.stopWarping()}syncWith(t){return this.time=t.time,this.timeScale=t.timeScale,this.stopWarping()}halt(t){return this.warp(this._effectiveTimeScale,0,t)}warp(t,e,i){const s=this._mixer,r=s.time,n=this.timeScale;let a=this._timeScaleInterpolant;null===a&&(a=s._lendControlInterpolant(),this._timeScaleInterpolant=a);const o=a.parameterPositions,h=a.sampleValues;return o[0]=r,o[1]=r+i,h[0]=t/n,h[1]=e/n,this}stopWarping(){const t=this._timeScaleInterpolant;return null!==t&&(this._timeScaleInterpolant=null,this._mixer._takeBackControlInterpolant(t)),this}getMixer(){return this._mixer}getClip(){return this._clip}getRoot(){return this._localRoot||this._mixer._root}_update(t,e,i,s){if(!this.enabled)return void this._updateWeight(t);const r=this._startTime;if(null!==r){const s=(t-r)*i;s<0||0===i?e=0:(this._startTime=null,e=i*s)}e*=this._updateTimeScale(t);const n=this._updateTime(e),a=this._updateWeight(t);if(a>0){const t=this._interpolants,e=this._propertyBindings;if(this.blendMode===Fe)for(let i=0,s=t.length;i!==s;++i)t[i].evaluate(n),e[i].accumulateAdditive(a);else for(let i=0,r=t.length;i!==r;++i)t[i].evaluate(n),e[i].accumulate(s,a)}}_updateWeight(t){let e=0;if(this.enabled){e=this.weight;const i=this._weightInterpolant;if(null!==i){const s=i.evaluate(t)[0];e*=s,t>i.parameterPositions[1]&&(this.stopFading(),0===s&&(this.enabled=!1))}}return this._effectiveWeight=e,e}_updateTimeScale(t){let e=0;if(!this.paused){e=this.timeScale;const i=this._timeScaleInterpolant;if(null!==i){e*=i.evaluate(t)[0],t>i.parameterPositions[1]&&(this.stopWarping(),0===e?this.paused=!0:this.timeScale=e)}}return this._effectiveTimeScale=e,e}_updateTime(t){const e=this._clip.duration,i=this.loop;let s=this.time+t,r=this._loopCount;const n=2202===i;if(0===t)return-1===r||!n||1&~r?s:e-s;if(2200===i){-1===r&&(this._loopCount=0,this._setEndings(!0,!0,!1));t:{if(s>=e)s=e;else{if(!(s<0)){this.time=s;break t}s=0}this.clampWhenFinished?this.paused=!0:this.enabled=!1,this.time=s,this._mixer.dispatchEvent({type:"finished",action:this,direction:t<0?-1:1})}}else{if(-1===r&&(t>=0?(r=0,this._setEndings(!0,0===this.repetitions,n)):this._setEndings(0===this.repetitions,!0,n)),s>=e||s<0){const i=Math.floor(s/e);s-=e*i,r+=Math.abs(i);const a=this.repetitions-r;if(a<=0)this.clampWhenFinished?this.paused=!0:this.enabled=!1,s=t>0?e:0,this.time=s,this._mixer.dispatchEvent({type:"finished",action:this,direction:t>0?1:-1});else{if(1===a){const e=t<0;this._setEndings(e,!e,n)}else this._setEndings(!1,!1,n);this._loopCount=r,this.time=s,this._mixer.dispatchEvent({type:"loop",action:this,loopDelta:i})}}else this.time=s;if(n&&!(1&~r))return e-s}return s}_setEndings(t,e,i){const s=this._interpolantSettings;i?(s.endingStart=Pe,s.endingEnd=Pe):(s.endingStart=t?this.zeroSlopeAtStart?Pe:Re:Oe,s.endingEnd=e?this.zeroSlopeAtEnd?Pe:Re:Oe)}_scheduleFading(t,e,i){const s=this._mixer,r=s.time;let n=this._weightInterpolant;null===n&&(n=s._lendControlInterpolant(),this._weightInterpolant=n);const a=n.parameterPositions,o=n.sampleValues;return a[0]=r,o[0]=e,a[1]=r+t,o[1]=i,this}}const Ou=new Float32Array(1);class Nu extends Vi{constructor(t){super(),this._root=t,this._initMemoryManager(),this._accuIndex=0,this.time=0,this.timeScale=1}_bindAction(t,e){const i=t._localRoot||this._root,s=t._clip.tracks,r=s.length,n=t._propertyBindings,a=t._interpolants,o=i.uuid,h=this._bindingsByRootAndName;let l=h[o];void 0===l&&(l={},h[o]=l);for(let t=0;t!==r;++t){const r=s[t],h=r.name;let c=l[h];if(void 0!==c)++c.referenceCount,n[t]=c;else{if(c=n[t],void 0!==c){null===c._cacheIndex&&(++c.referenceCount,this._addInactiveBinding(c,o,h));continue}const s=e&&e._propertyBindings[t].binding.parsedPath;c=new Au(Eu.create(i,h,s),r.ValueTypeName,r.getValueSize()),++c.referenceCount,this._addInactiveBinding(c,o,h),n[t]=c}a[t].resultBuffer=c.buffer}}_activateAction(t){if(!this._isActiveAction(t)){if(null===t._cacheIndex){const e=(t._localRoot||this._root).uuid,i=t._clip.uuid,s=this._actionsByClip[i];this._bindAction(t,s&&s.knownActions[0]),this._addInactiveAction(t,i,e)}const e=t._propertyBindings;for(let t=0,i=e.length;t!==i;++t){const i=e[t];0===i.useCount++&&(this._lendBinding(i),i.saveOriginalState())}this._lendAction(t)}}_deactivateAction(t){if(this._isActiveAction(t)){const e=t._propertyBindings;for(let t=0,i=e.length;t!==i;++t){const i=e[t];0===--i.useCount&&(i.restoreOriginalState(),this._takeBackBinding(i))}this._takeBackAction(t)}}_initMemoryManager(){this._actions=[],this._nActiveActions=0,this._actionsByClip={},this._bindings=[],this._nActiveBindings=0,this._bindingsByRootAndName={},this._controlInterpolants=[],this._nActiveControlInterpolants=0;const t=this;this.stats={actions:{get total(){return t._actions.length},get inUse(){return t._nActiveActions}},bindings:{get total(){return t._bindings.length},get inUse(){return t._nActiveBindings}},controlInterpolants:{get total(){return t._controlInterpolants.length},get inUse(){return t._nActiveControlInterpolants}}}}_isActiveAction(t){const e=t._cacheIndex;return null!==e&&e=0;--e)t[e].stop();return this}update(t){t*=this.timeScale;const e=this._actions,i=this._nActiveActions,s=this.time+=t,r=Math.sign(t),n=this._accuIndex^=1;for(let a=0;a!==i;++a){e[a]._update(s,t,r,n)}const a=this._bindings,o=this._nActiveBindings;for(let t=0;t!==o;++t)a[t].apply(n);return this}setTime(t){this.time=0;for(let t=0;t=this.min.x&&t.x<=this.max.x&&t.y>=this.min.y&&t.y<=this.max.y}containsBox(t){return this.min.x<=t.min.x&&t.max.x<=this.max.x&&this.min.y<=t.min.y&&t.max.y<=this.max.y}getParameter(t,e){return e.set((t.x-this.min.x)/(this.max.x-this.min.x),(t.y-this.min.y)/(this.max.y-this.min.y))}intersectsBox(t){return t.max.x>=this.min.x&&t.min.x<=this.max.x&&t.max.y>=this.min.y&&t.min.y<=this.max.y}clampPoint(t,e){return e.copy(t).clamp(this.min,this.max)}distanceToPoint(t){return this.clampPoint(t,Gu).distanceTo(t)}intersect(t){return this.min.max(t.min),this.max.min(t.max),this.isEmpty()&&this.makeEmpty(),this}union(t){return this.min.min(t.min),this.max.max(t.max),this}translate(t){return this.min.add(t),this.max.add(t),this}equals(t){return t.min.equals(this.min)&&t.max.equals(this.max)}}const Qu=new Qi,Ku=new Qi;class td{constructor(t=new Qi,e=new Qi){this.start=t,this.end=e}set(t,e){return this.start.copy(t),this.end.copy(e),this}copy(t){return this.start.copy(t.start),this.end.copy(t.end),this}getCenter(t){return t.addVectors(this.start,this.end).multiplyScalar(.5)}delta(t){return t.subVectors(this.end,this.start)}distanceSq(){return this.start.distanceToSquared(this.end)}distance(){return this.start.distanceTo(this.end)}at(t,e){return this.delta(e).multiplyScalar(t).add(this.start)}closestPointToPointParameter(t,e){Qu.subVectors(t,this.start),Ku.subVectors(this.end,this.start);const i=Ku.dot(Ku);let s=Ku.dot(Qu)/i;return e&&(s=Hi(s,0,1)),s}closestPointToPoint(t,e,i){const s=this.closestPointToPointParameter(t,e);return this.delta(i).multiplyScalar(s).add(this.start)}applyMatrix4(t){return this.start.applyMatrix4(t),this.end.applyMatrix4(t),this}equals(t){return t.start.equals(this.start)&&t.end.equals(this.end)}clone(){return(new this.constructor).copy(this)}}const ed=new Qi;class id extends Pr{constructor(t,e){super(),this.light=t,this.matrixAutoUpdate=!1,this.color=e,this.type="SpotLightHelper";const i=new Bn,s=[0,0,0,0,0,1,0,0,0,1,0,1,0,0,0,-1,0,1,0,0,0,0,1,1,0,0,0,0,-1,1];for(let t=0,e=1,i=32;t1)for(let i=0;i.99999)this.quaternion.set(0,0,0,1);else if(t.y<-.99999)this.quaternion.set(1,0,0,0);else{zd.set(t.z,0,-t.x).normalize();const e=Math.acos(t.y);this.quaternion.setFromAxisAngle(zd,e)}}setLength(t,e=.2*t,i=.2*e){this.line.scale.set(1,Math.max(1e-4,t-e),1),this.line.updateMatrix(),this.cone.scale.set(i,e,i),this.cone.position.y=t,this.cone.updateMatrix()}setColor(t){this.line.material.color.set(t),this.cone.material.color.set(t)}copy(t){return super.copy(t,!1),this.line.copy(t.line),this.cone.copy(t.cone),this}dispose(){this.line.geometry.dispose(),this.line.material.dispose(),this.cone.geometry.dispose(),this.cone.material.dispose()}}class kd extends Jo{constructor(t=1){const e=[0,0,0,t,0,0,0,0,0,0,t,0,0,0,0,0,0,t],i=new Bn;i.setAttribute("position",new Mn(e,3)),i.setAttribute("color",new Mn([1,0,0,1,.6,0,0,1,0,.6,1,0,0,0,1,0,.6,1],3));super(i,new Po({vertexColors:!0,toneMapped:!1})),this.type="AxesHelper"}setColors(t,e,i){const s=new Kr,r=this.geometry.attributes.color.array;return s.set(t),s.toArray(r,0),s.toArray(r,3),s.set(e),s.toArray(r,6),s.toArray(r,9),s.set(i),s.toArray(r,12),s.toArray(r,15),this.geometry.attributes.color.needsUpdate=!0,this}dispose(){this.geometry.dispose(),this.material.dispose()}}class Ed{constructor(){this.type="ShapePath",this.color=new Kr,this.subPaths=[],this.currentPath=null}moveTo(t,e){return this.currentPath=new Wh,this.subPaths.push(this.currentPath),this.currentPath.moveTo(t,e),this}lineTo(t,e){return this.currentPath.lineTo(t,e),this}quadraticCurveTo(t,e,i,s){return this.currentPath.quadraticCurveTo(t,e,i,s),this}bezierCurveTo(t,e,i,s,r,n){return this.currentPath.bezierCurveTo(t,e,i,s,r,n),this}splineThru(t){return this.currentPath.splineThru(t),this}toShapes(t){function e(t,e){const i=e.length;let s=!1;for(let r=i-1,n=0;nNumber.EPSILON){if(h<0&&(i=e[n],o=-o,a=e[r],h=-h),t.ya.y)continue;if(t.y===i.y){if(t.x===i.x)return!0}else{const e=h*(t.x-i.x)-o*(t.y-i.y);if(0===e)return!0;if(e<0)continue;s=!s}}else{if(t.y!==i.y)continue;if(a.x<=t.x&&t.x<=i.x||i.x<=t.x&&t.x<=a.x)return!0}}return s}const i=gl.isClockWise,s=this.subPaths;if(0===s.length)return[];let r,n,a;const o=[];if(1===s.length)return n=s[0],a=new Uh,a.curves=n.curves,o.push(a),o;let h=!i(s[0].getPoints());h=t?!h:h;const l=[],c=[];let u,d,p=[],m=0;c[m]=void 0,p[m]=[];for(let e=0,a=s.length;e1){let t=!1,i=0;for(let t=0,e=c.length;t0&&!1===t&&(p=l)}for(let t=0,e=c.length;te?(t.repeat.x=1,t.repeat.y=i/e,t.offset.x=0,t.offset.y=(1-t.repeat.y)/2):(t.repeat.x=e/i,t.repeat.y=1,t.offset.x=(1-t.repeat.x)/2,t.offset.y=0),t}(t,e)}static cover(t,e){return function(t,e){const i=t.image&&t.image.width?t.image.width/t.image.height:1;return i>e?(t.repeat.x=e/i,t.repeat.y=1,t.offset.x=(1-t.repeat.x)/2,t.offset.y=0):(t.repeat.x=1,t.repeat.y=i/e,t.offset.x=0,t.offset.y=(1-t.repeat.y)/2),t}(t,e)}static fill(t){return function(t){return t.repeat.x=1,t.repeat.y=1,t.offset.x=0,t.offset.y=0,t}(t)}static getByteLength(t,e,i,s){return Pd(t,e,i,s)}}"undefined"!=typeof __THREE_DEVTOOLS__&&__THREE_DEVTOOLS__.dispatchEvent(new CustomEvent("register",{detail:{revision:t}})),"undefined"!=typeof window&&(window.__THREE__?console.warn("WARNING: Multiple instances of Three.js being imported."):window.__THREE__=t);export{et as ACESFilmicToneMapping,v as AddEquation,G as AddOperation,Fe as AdditiveAnimationBlendMode,g as AdditiveBlending,st as AgXToneMapping,Vt as AlphaFormat,wi as AlwaysCompare,W as AlwaysDepth,pi as AlwaysStencilFunc,Dc as AmbientLight,Pu as AnimationAction,uc as AnimationClip,vc as AnimationLoader,Nu as AnimationMixer,Ru as AnimationObjectGroup,Ql as AnimationUtils,Mh as ArcCurve,cu as ArrayCamera,Bd as ArrowHelper,nt as AttachedBindMode,xu as Audio,_u as AudioAnalyser,ru as AudioContext,fu as AudioListener,nu as AudioLoader,kd as AxesHelper,d as BackSide,We as BasicDepthPacking,o as BasicShadowMap,Ro as BatchedMesh,Da as Bone,rc as BooleanKeyframeTrack,$u as Box2,Ps as Box3,Ad as Box3Helper,Hn as BoxGeometry,_d as BoxHelper,pn as BufferAttribute,Bn as BufferGeometry,Gc as BufferGeometryLoader,zt as ByteType,pc as Cache,Gn as Camera,wd as CameraHelper,oh as CanvasTexture,lh as CapsuleGeometry,Ih as CatmullRomCurve3,tt as CineonToneMapping,ch as CircleGeometry,mt as ClampToEdgeWrapping,uu as Clock,Kr as Color,nc as ColorKeyframeTrack,gs as ColorManagement,nh as CompressedArrayTexture,ah as CompressedCubeTexture,rh as CompressedTexture,wc as CompressedTextureLoader,dh as ConeGeometry,V as ConstantAlphaFactor,N as ConstantColorFactor,Rd as Controls,ia as CubeCamera,ht as CubeReflectionMapping,lt as CubeRefractionMapping,sa as CubeTexture,_c as CubeTextureLoader,dt as CubeUVReflectionMapping,Eh as CubicBezierCurve,Rh as CubicBezierCurve3,tc as CubicInterpolant,r as CullFaceBack,n as CullFaceFront,a as CullFaceFrontBack,s as CullFaceNone,vh as Curve,jh as CurvePath,b as CustomBlending,it as CustomToneMapping,uh as CylinderGeometry,Yu as Cylindrical,Es as Data3DTexture,Bs as DataArrayTexture,Ha as DataTexture,Ac as DataTextureLoader,ln as DataUtils,ii as DecrementStencilOp,ri as DecrementWrapStencilOp,yc as DefaultLoadingManager,Wt as DepthFormat,Ut as DepthStencilFormat,hh as DepthTexture,at as DetachedBindMode,Uc as DirectionalLight,xd as DirectionalLightHelper,ic as DiscreteInterpolant,mh as DodecahedronGeometry,p as DoubleSide,k as DstAlphaFactor,R as DstColorFactor,Ci as DynamicCopyUsage,Si as DynamicDrawUsage,Ti as DynamicReadUsage,bh as EdgesGeometry,wh as EllipseCurve,gi as EqualCompare,H as EqualDepth,hi as EqualStencilFunc,ct as EquirectangularReflectionMapping,ut as EquirectangularRefractionMapping,fr as Euler,Vi as EventDispatcher,bl as ExtrudeGeometry,bc as FileLoader,wn as Float16BufferAttribute,Mn as Float32BufferAttribute,Et as FloatType,la as Fog,ha as FogExp2,sh as FramebufferTexture,u as FrontSide,co as Frustum,mo as FrustumArray,Uu as GLBufferAttribute,ki as GLSL1,Ei as GLSL3,xi as GreaterCompare,J as GreaterDepth,vi as GreaterEqualCompare,q as GreaterEqualDepth,di as GreaterEqualStencilFunc,ci as GreaterStencilFunc,pd as GridHelper,na as Group,Rt as HalfFloatType,Ic as HemisphereLight,dd as HemisphereLightHelper,wl as IcosahedronGeometry,iu as ImageBitmapLoader,Sc as ImageLoader,vs as ImageUtils,ei as IncrementStencilOp,si as IncrementWrapStencilOp,Ya as InstancedBufferAttribute,Zc as InstancedBufferGeometry,Wu as InstancedInterleavedBuffer,io as InstancedMesh,fn as Int16BufferAttribute,bn as Int32BufferAttribute,mn as Int8BufferAttribute,Bt as IntType,ua as InterleavedBuffer,pa as InterleavedBufferAttribute,Kl as Interpolant,Be as InterpolateDiscrete,ke as InterpolateLinear,Ee as InterpolateSmooth,Fi as InterpolationSamplingMode,Ni as InterpolationSamplingType,ni as InvertStencilOp,Ke as KeepStencilOp,sc as KeyframeTrack,Ea as LOD,Ml as LatheGeometry,xr as Layers,yi as LessCompare,U as LessDepth,fi as LessEqualCompare,D as LessEqualDepth,li as LessEqualStencilFunc,oi as LessStencilFunc,zc as Light,Jc as LightProbe,Uo as Line,td as Line3,Po as LineBasicMaterial,Ph as LineCurve,Oh as LineCurve3,Jl as LineDashedMaterial,Xo as LineLoop,Jo as LineSegments,wt as LinearFilter,ec as LinearInterpolant,At as LinearMipMapLinearFilter,St as LinearMipMapNearestFilter,_t as LinearMipmapLinearFilter,Mt as LinearMipmapNearestFilter,Ze as LinearSRGBColorSpace,Q as LinearToneMapping,Ge as LinearTransfer,gc as Loader,Yc as LoaderUtils,mc as LoadingManager,ze as LoopOnce,Ce as LoopPingPong,Ie as LoopRepeat,e as MOUSE,sn as Material,Xc as MaterialLoader,Zi as MathUtils,Zu as Matrix2,es as Matrix3,or as Matrix4,_ as MaxEquation,Un as Mesh,rn as MeshBasicMaterial,Dl as MeshDepthMaterial,Hl as MeshDistanceMaterial,Ul as MeshLambertMaterial,ql as MeshMatcapMaterial,Wl as MeshNormalMaterial,Ll as MeshPhongMaterial,Vl as MeshPhysicalMaterial,Fl as MeshStandardMaterial,jl as MeshToonMaterial,S as MinEquation,yt as MirroredRepeatWrapping,Z as MixOperation,x as MultiplyBlending,Y as MultiplyOperation,gt as NearestFilter,vt as NearestMipMapLinearFilter,xt as NearestMipMapNearestFilter,bt as NearestMipmapLinearFilter,ft as NearestMipmapNearestFilter,rt as NeutralToneMapping,mi as NeverCompare,j as NeverDepth,ai as NeverStencilFunc,m as NoBlending,Xe as NoColorSpace,$ as NoToneMapping,Ne as NormalAnimationBlendMode,y as NormalBlending,bi as NotEqualCompare,X as NotEqualDepth,ui as NotEqualStencilFunc,ac as NumberKeyframeTrack,Pr as Object3D,$c as ObjectLoader,Je as ObjectSpaceNormalMap,Sl as OctahedronGeometry,T as OneFactor,L as OneMinusConstantAlphaFactor,F as OneMinusConstantColorFactor,E as OneMinusDstAlphaFactor,P as OneMinusDstColorFactor,B as OneMinusSrcAlphaFactor,I as OneMinusSrcColorFactor,jc as OrthographicCamera,h as PCFShadowMap,l as PCFSoftShadowMap,Wh as Path,ta as PerspectiveCamera,ao as Plane,_l as PlaneGeometry,Td as PlaneHelper,Lc as PointLight,hd as PointLightHelper,Ko as Points,Yo as PointsMaterial,md as PolarGridHelper,ph as PolyhedronGeometry,Su as PositionalAudio,Eu as PropertyBinding,Au as PropertyMixer,Nh as QuadraticBezierCurve,Fh as QuadraticBezierCurve3,$i as Quaternion,hc as QuaternionKeyframeTrack,oc as QuaternionLinearInterpolant,Ui as RAD2DEG,Ae as RED_GREEN_RGTC2_Format,Se as RED_RGTC1_Format,t as REVISION,Ue as RGBADepthPacking,jt as RGBAFormat,Yt as RGBAIntegerFormat,fe as RGBA_ASTC_10x10_Format,me as RGBA_ASTC_10x5_Format,ye as RGBA_ASTC_10x6_Format,ge as RGBA_ASTC_10x8_Format,xe as RGBA_ASTC_12x10_Format,be as RGBA_ASTC_12x12_Format,ae as RGBA_ASTC_4x4_Format,oe as RGBA_ASTC_5x4_Format,he as RGBA_ASTC_5x5_Format,le as RGBA_ASTC_6x5_Format,ce as RGBA_ASTC_6x6_Format,ue as RGBA_ASTC_8x5_Format,de as RGBA_ASTC_8x6_Format,pe as RGBA_ASTC_8x8_Format,ve as RGBA_BPTC_Format,ne as RGBA_ETC2_EAC_Format,ie as RGBA_PVRTC_2BPPV1_Format,ee as RGBA_PVRTC_4BPPV1_Format,Gt as RGBA_S3TC_DXT1_Format,$t as RGBA_S3TC_DXT3_Format,Qt as RGBA_S3TC_DXT5_Format,De as RGBDepthPacking,Lt as RGBFormat,Xt as RGBIntegerFormat,we as RGB_BPTC_SIGNED_Format,Me as RGB_BPTC_UNSIGNED_Format,se as RGB_ETC1_Format,re as RGB_ETC2_Format,te as RGB_PVRTC_2BPPV1_Format,Kt as RGB_PVRTC_4BPPV1_Format,Zt as RGB_S3TC_DXT1_Format,He as RGDepthPacking,qt as RGFormat,Jt as RGIntegerFormat,Nl as RawShaderMaterial,ar as Ray,Hu as Raycaster,Hc as RectAreaLight,Dt as RedFormat,Ht as RedIntegerFormat,K as ReinhardToneMapping,Is as RenderTarget,Fu as RenderTarget3D,pt as RepeatWrapping,ti as ReplaceStencilOp,M as ReverseSubtractEquation,Al as RingGeometry,Te as SIGNED_RED_GREEN_RGTC2_Format,_e as SIGNED_RED_RGTC1_Format,Ye as SRGBColorSpace,$e as SRGBTransfer,ca as Scene,Zn as ShaderMaterial,Ol as ShadowMaterial,Uh as Shape,Tl as ShapeGeometry,Ed as ShapePath,gl as ShapeUtils,It as ShortType,Xa as Skeleton,ad as SkeletonHelper,Ua as SkinnedMesh,Ms as Source,Qs as Sphere,zl as SphereGeometry,Xu as Spherical,qc as SphericalHarmonics3,Vh as SplineCurve,Pc as SpotLight,id as SpotLightHelper,Ia as Sprite,ma as SpriteMaterial,C as SrcAlphaFactor,O as SrcAlphaSaturateFactor,z as SrcColorFactor,Ii as StaticCopyUsage,Mi as StaticDrawUsage,Ai as StaticReadUsage,lu as StereoCamera,Bi as StreamCopyUsage,_i as StreamDrawUsage,zi as StreamReadUsage,lc as StringKeyframeTrack,w as SubtractEquation,f as SubtractiveBlending,i as TOUCH,qe as TangentSpaceNormalMap,Il as TetrahedronGeometry,Ts as Texture,Tc as TextureLoader,Od as TextureUtils,Oi as TimestampQuery,Cl as TorusGeometry,Bl as TorusKnotGeometry,Yr as Triangle,je as TriangleFanDrawMode,Le as TriangleStripDrawMode,Ve as TrianglesDrawMode,kl as TubeGeometry,ot as UVMapping,xn as Uint16BufferAttribute,vn as Uint32BufferAttribute,yn as Uint8BufferAttribute,gn as Uint8ClampedBufferAttribute,Vu as Uniform,ju as UniformsGroup,Yn as UniformsUtils,Tt as UnsignedByteType,Nt as UnsignedInt248Type,Ft as UnsignedInt5999Type,kt as UnsignedIntType,Pt as UnsignedShort4444Type,Ot as UnsignedShort5551Type,Ct as UnsignedShortType,c as VSMShadowMap,Gi as Vector2,Qi as Vector3,zs as Vector4,cc as VectorKeyframeTrack,ih as VideoFrameTexture,eh as VideoTexture,Rs as WebGL3DRenderTarget,ks as WebGLArrayRenderTarget,Ri as WebGLCoordinateSystem,ra as WebGLCubeRenderTarget,Cs as WebGLRenderTarget,Pi as WebGPUCoordinateSystem,oa as WebXRController,El as WireframeGeometry,Oe as WrapAroundEnding,Re as ZeroCurvatureEnding,A as ZeroFactor,Pe as ZeroSlopeEnding,Qe as ZeroStencilOp,ss as arrayNeedsUint32,qn as cloneUniforms,os as createCanvasElement,as as createElementNS,Pd as getByteLength,Xn as getUnlitUniformColorSpace,Jn as mergeUniforms,cs as probeAsync,us as toNormalizedProjectionMatrix,ds as toReversedProjectionMatrix,ls as warnOnce}; diff --git a/build/three.js b/build/three.js deleted file mode 100644 index 772b38a7eccbee..00000000000000 --- a/build/three.js +++ /dev/null @@ -1,51821 +0,0 @@ -console.warn( 'Scripts "build/three.js" and "build/three.min.js" are deprecated with r150+, and will be removed with r160. Please use ES Modules or alternatives: https://threejs.org/docs/index.html#manual/en/introduction/Installation' ); -/** - * @license - * Copyright 2010-2023 Three.js Authors - * SPDX-License-Identifier: MIT - */ -(function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : - typeof define === 'function' && define.amd ? define(['exports'], factory) : - (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.THREE = {})); -})(this, (function (exports) { 'use strict'; - - const REVISION = '153dev'; - - const MOUSE = { LEFT: 0, MIDDLE: 1, RIGHT: 2, ROTATE: 0, DOLLY: 1, PAN: 2 }; - const TOUCH = { ROTATE: 0, PAN: 1, DOLLY_PAN: 2, DOLLY_ROTATE: 3 }; - const CullFaceNone = 0; - const CullFaceBack = 1; - const CullFaceFront = 2; - const CullFaceFrontBack = 3; - const BasicShadowMap = 0; - const PCFShadowMap = 1; - const PCFSoftShadowMap = 2; - const VSMShadowMap = 3; - const FrontSide = 0; - const BackSide = 1; - const DoubleSide = 2; - const TwoPassDoubleSide = 2; // r149 - const NoBlending = 0; - const NormalBlending = 1; - const AdditiveBlending = 2; - const SubtractiveBlending = 3; - const MultiplyBlending = 4; - const CustomBlending = 5; - const AddEquation = 100; - const SubtractEquation = 101; - const ReverseSubtractEquation = 102; - const MinEquation = 103; - const MaxEquation = 104; - const ZeroFactor = 200; - const OneFactor = 201; - const SrcColorFactor = 202; - const OneMinusSrcColorFactor = 203; - const SrcAlphaFactor = 204; - const OneMinusSrcAlphaFactor = 205; - const DstAlphaFactor = 206; - const OneMinusDstAlphaFactor = 207; - const DstColorFactor = 208; - const OneMinusDstColorFactor = 209; - const SrcAlphaSaturateFactor = 210; - const NeverDepth = 0; - const AlwaysDepth = 1; - const LessDepth = 2; - const LessEqualDepth = 3; - const EqualDepth = 4; - const GreaterEqualDepth = 5; - const GreaterDepth = 6; - const NotEqualDepth = 7; - const MultiplyOperation = 0; - const MixOperation = 1; - const AddOperation = 2; - const NoToneMapping = 0; - const LinearToneMapping = 1; - const ReinhardToneMapping = 2; - const CineonToneMapping = 3; - const ACESFilmicToneMapping = 4; - const CustomToneMapping = 5; - - const UVMapping = 300; - const CubeReflectionMapping = 301; - const CubeRefractionMapping = 302; - const EquirectangularReflectionMapping = 303; - const EquirectangularRefractionMapping = 304; - const CubeUVReflectionMapping = 306; - const RepeatWrapping = 1000; - const ClampToEdgeWrapping = 1001; - const MirroredRepeatWrapping = 1002; - const NearestFilter = 1003; - const NearestMipmapNearestFilter = 1004; - const NearestMipMapNearestFilter = 1004; - const NearestMipmapLinearFilter = 1005; - const NearestMipMapLinearFilter = 1005; - const LinearFilter = 1006; - const LinearMipmapNearestFilter = 1007; - const LinearMipMapNearestFilter = 1007; - const LinearMipmapLinearFilter = 1008; - const LinearMipMapLinearFilter = 1008; - const UnsignedByteType = 1009; - const ByteType = 1010; - const ShortType = 1011; - const UnsignedShortType = 1012; - const IntType = 1013; - const UnsignedIntType = 1014; - const FloatType = 1015; - const HalfFloatType = 1016; - const UnsignedShort4444Type = 1017; - const UnsignedShort5551Type = 1018; - const UnsignedInt248Type = 1020; - const AlphaFormat = 1021; - const RGBAFormat = 1023; - const LuminanceFormat = 1024; - const LuminanceAlphaFormat = 1025; - const DepthFormat = 1026; - const DepthStencilFormat = 1027; - const RedFormat = 1028; - const RedIntegerFormat = 1029; - const RGFormat = 1030; - const RGIntegerFormat = 1031; - const RGBAIntegerFormat = 1033; - - const RGB_S3TC_DXT1_Format = 33776; - const RGBA_S3TC_DXT1_Format = 33777; - const RGBA_S3TC_DXT3_Format = 33778; - const RGBA_S3TC_DXT5_Format = 33779; - const RGB_PVRTC_4BPPV1_Format = 35840; - const RGB_PVRTC_2BPPV1_Format = 35841; - const RGBA_PVRTC_4BPPV1_Format = 35842; - const RGBA_PVRTC_2BPPV1_Format = 35843; - const RGB_ETC1_Format = 36196; - const RGB_ETC2_Format = 37492; - const RGBA_ETC2_EAC_Format = 37496; - const RGBA_ASTC_4x4_Format = 37808; - const RGBA_ASTC_5x4_Format = 37809; - const RGBA_ASTC_5x5_Format = 37810; - const RGBA_ASTC_6x5_Format = 37811; - const RGBA_ASTC_6x6_Format = 37812; - const RGBA_ASTC_8x5_Format = 37813; - const RGBA_ASTC_8x6_Format = 37814; - const RGBA_ASTC_8x8_Format = 37815; - const RGBA_ASTC_10x5_Format = 37816; - const RGBA_ASTC_10x6_Format = 37817; - const RGBA_ASTC_10x8_Format = 37818; - const RGBA_ASTC_10x10_Format = 37819; - const RGBA_ASTC_12x10_Format = 37820; - const RGBA_ASTC_12x12_Format = 37821; - const RGBA_BPTC_Format = 36492; - const RED_RGTC1_Format = 36283; - const SIGNED_RED_RGTC1_Format = 36284; - const RED_GREEN_RGTC2_Format = 36285; - const SIGNED_RED_GREEN_RGTC2_Format = 36286; - const LoopOnce = 2200; - const LoopRepeat = 2201; - const LoopPingPong = 2202; - const InterpolateDiscrete = 2300; - const InterpolateLinear = 2301; - const InterpolateSmooth = 2302; - const ZeroCurvatureEnding = 2400; - const ZeroSlopeEnding = 2401; - const WrapAroundEnding = 2402; - const NormalAnimationBlendMode = 2500; - const AdditiveAnimationBlendMode = 2501; - const TrianglesDrawMode = 0; - const TriangleStripDrawMode = 1; - const TriangleFanDrawMode = 2; - /** @deprecated Use LinearSRGBColorSpace or NoColorSpace in three.js r152+. */ - const LinearEncoding = 3000; - /** @deprecated Use SRGBColorSpace in three.js r152+. */ - const sRGBEncoding = 3001; - const BasicDepthPacking = 3200; - const RGBADepthPacking = 3201; - const TangentSpaceNormalMap = 0; - const ObjectSpaceNormalMap = 1; - - // Color space string identifiers, matching CSS Color Module Level 4 and WebGPU names where available. - const NoColorSpace = ''; - const SRGBColorSpace = 'srgb'; - const LinearSRGBColorSpace = 'srgb-linear'; - const DisplayP3ColorSpace = 'display-p3'; - - const ZeroStencilOp = 0; - const KeepStencilOp = 7680; - const ReplaceStencilOp = 7681; - const IncrementStencilOp = 7682; - const DecrementStencilOp = 7683; - const IncrementWrapStencilOp = 34055; - const DecrementWrapStencilOp = 34056; - const InvertStencilOp = 5386; - - const NeverStencilFunc = 512; - const LessStencilFunc = 513; - const EqualStencilFunc = 514; - const LessEqualStencilFunc = 515; - const GreaterStencilFunc = 516; - const NotEqualStencilFunc = 517; - const GreaterEqualStencilFunc = 518; - const AlwaysStencilFunc = 519; - - const NeverCompare = 512; - const LessCompare = 513; - const EqualCompare = 514; - const LessEqualCompare = 515; - const GreaterCompare = 516; - const NotEqualCompare = 517; - const GreaterEqualCompare = 518; - const AlwaysCompare = 519; - - const StaticDrawUsage = 35044; - const DynamicDrawUsage = 35048; - const StreamDrawUsage = 35040; - const StaticReadUsage = 35045; - const DynamicReadUsage = 35049; - const StreamReadUsage = 35041; - const StaticCopyUsage = 35046; - const DynamicCopyUsage = 35050; - const StreamCopyUsage = 35042; - - const GLSL1 = '100'; - const GLSL3 = '300 es'; - - const _SRGBAFormat = 1035; // fallback for WebGL 1 - - /** - * https://github.com/mrdoob/eventdispatcher.js/ - */ - - class EventDispatcher { - - addEventListener( type, listener ) { - - if ( this._listeners === undefined ) this._listeners = {}; - - const listeners = this._listeners; - - if ( listeners[ type ] === undefined ) { - - listeners[ type ] = []; - - } - - if ( listeners[ type ].indexOf( listener ) === - 1 ) { - - listeners[ type ].push( listener ); - - } - - } - - hasEventListener( type, listener ) { - - if ( this._listeners === undefined ) return false; - - const listeners = this._listeners; - - return listeners[ type ] !== undefined && listeners[ type ].indexOf( listener ) !== - 1; - - } - - removeEventListener( type, listener ) { - - if ( this._listeners === undefined ) return; - - const listeners = this._listeners; - const listenerArray = listeners[ type ]; - - if ( listenerArray !== undefined ) { - - const index = listenerArray.indexOf( listener ); - - if ( index !== - 1 ) { - - listenerArray.splice( index, 1 ); - - } - - } - - } - - dispatchEvent( event ) { - - if ( this._listeners === undefined ) return; - - const listeners = this._listeners; - const listenerArray = listeners[ event.type ]; - - if ( listenerArray !== undefined ) { - - event.target = this; - - // Make a copy, in case listeners are removed while iterating. - const array = listenerArray.slice( 0 ); - - for ( let i = 0, l = array.length; i < l; i ++ ) { - - array[ i ].call( this, event ); - - } - - event.target = null; - - } - - } - - } - - const _lut = [ '00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '0a', '0b', '0c', '0d', '0e', '0f', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '1a', '1b', '1c', '1d', '1e', '1f', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '2a', '2b', '2c', '2d', '2e', '2f', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '3a', '3b', '3c', '3d', '3e', '3f', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '4a', '4b', '4c', '4d', '4e', '4f', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '5a', '5b', '5c', '5d', '5e', '5f', '60', '61', '62', '63', '64', '65', '66', '67', '68', '69', '6a', '6b', '6c', '6d', '6e', '6f', '70', '71', '72', '73', '74', '75', '76', '77', '78', '79', '7a', '7b', '7c', '7d', '7e', '7f', '80', '81', '82', '83', '84', '85', '86', '87', '88', '89', '8a', '8b', '8c', '8d', '8e', '8f', '90', '91', '92', '93', '94', '95', '96', '97', '98', '99', '9a', '9b', '9c', '9d', '9e', '9f', 'a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8', 'a9', 'aa', 'ab', 'ac', 'ad', 'ae', 'af', 'b0', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'b7', 'b8', 'b9', 'ba', 'bb', 'bc', 'bd', 'be', 'bf', 'c0', 'c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7', 'c8', 'c9', 'ca', 'cb', 'cc', 'cd', 'ce', 'cf', 'd0', 'd1', 'd2', 'd3', 'd4', 'd5', 'd6', 'd7', 'd8', 'd9', 'da', 'db', 'dc', 'dd', 'de', 'df', 'e0', 'e1', 'e2', 'e3', 'e4', 'e5', 'e6', 'e7', 'e8', 'e9', 'ea', 'eb', 'ec', 'ed', 'ee', 'ef', 'f0', 'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'fa', 'fb', 'fc', 'fd', 'fe', 'ff' ]; - - let _seed = 1234567; - - - const DEG2RAD = Math.PI / 180; - const RAD2DEG = 180 / Math.PI; - - // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/21963136#21963136 - function generateUUID() { - - const d0 = Math.random() * 0xffffffff | 0; - const d1 = Math.random() * 0xffffffff | 0; - const d2 = Math.random() * 0xffffffff | 0; - const d3 = Math.random() * 0xffffffff | 0; - const uuid = _lut[ d0 & 0xff ] + _lut[ d0 >> 8 & 0xff ] + _lut[ d0 >> 16 & 0xff ] + _lut[ d0 >> 24 & 0xff ] + '-' + - _lut[ d1 & 0xff ] + _lut[ d1 >> 8 & 0xff ] + '-' + _lut[ d1 >> 16 & 0x0f | 0x40 ] + _lut[ d1 >> 24 & 0xff ] + '-' + - _lut[ d2 & 0x3f | 0x80 ] + _lut[ d2 >> 8 & 0xff ] + '-' + _lut[ d2 >> 16 & 0xff ] + _lut[ d2 >> 24 & 0xff ] + - _lut[ d3 & 0xff ] + _lut[ d3 >> 8 & 0xff ] + _lut[ d3 >> 16 & 0xff ] + _lut[ d3 >> 24 & 0xff ]; - - // .toLowerCase() here flattens concatenated strings to save heap memory space. - return uuid.toLowerCase(); - - } - - function clamp( value, min, max ) { - - return Math.max( min, Math.min( max, value ) ); - - } - - // compute euclidean modulo of m % n - // https://en.wikipedia.org/wiki/Modulo_operation - function euclideanModulo( n, m ) { - - return ( ( n % m ) + m ) % m; - - } - - // Linear mapping from range to range - function mapLinear( x, a1, a2, b1, b2 ) { - - return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 ); - - } - - // https://www.gamedev.net/tutorials/programming/general-and-gameplay-programming/inverse-lerp-a-super-useful-yet-often-overlooked-function-r5230/ - function inverseLerp( x, y, value ) { - - if ( x !== y ) { - - return ( value - x ) / ( y - x ); - - } else { - - return 0; - - } - - } - - // https://en.wikipedia.org/wiki/Linear_interpolation - function lerp( x, y, t ) { - - return ( 1 - t ) * x + t * y; - - } - - // http://www.rorydriscoll.com/2016/03/07/frame-rate-independent-damping-using-lerp/ - function damp( x, y, lambda, dt ) { - - return lerp( x, y, 1 - Math.exp( - lambda * dt ) ); - - } - - // https://www.desmos.com/calculator/vcsjnyz7x4 - function pingpong( x, length = 1 ) { - - return length - Math.abs( euclideanModulo( x, length * 2 ) - length ); - - } - - // http://en.wikipedia.org/wiki/Smoothstep - function smoothstep( x, min, max ) { - - if ( x <= min ) return 0; - if ( x >= max ) return 1; - - x = ( x - min ) / ( max - min ); - - return x * x * ( 3 - 2 * x ); - - } - - function smootherstep( x, min, max ) { - - if ( x <= min ) return 0; - if ( x >= max ) return 1; - - x = ( x - min ) / ( max - min ); - - return x * x * x * ( x * ( x * 6 - 15 ) + 10 ); - - } - - // Random integer from interval - function randInt( low, high ) { - - return low + Math.floor( Math.random() * ( high - low + 1 ) ); - - } - - // Random float from interval - function randFloat( low, high ) { - - return low + Math.random() * ( high - low ); - - } - - // Random float from <-range/2, range/2> interval - function randFloatSpread( range ) { - - return range * ( 0.5 - Math.random() ); - - } - - // Deterministic pseudo-random float in the interval [ 0, 1 ] - function seededRandom( s ) { - - if ( s !== undefined ) _seed = s; - - // Mulberry32 generator - - let t = _seed += 0x6D2B79F5; - - t = Math.imul( t ^ t >>> 15, t | 1 ); - - t ^= t + Math.imul( t ^ t >>> 7, t | 61 ); - - return ( ( t ^ t >>> 14 ) >>> 0 ) / 4294967296; - - } - - function degToRad( degrees ) { - - return degrees * DEG2RAD; - - } - - function radToDeg( radians ) { - - return radians * RAD2DEG; - - } - - function isPowerOfTwo( value ) { - - return ( value & ( value - 1 ) ) === 0 && value !== 0; - - } - - function ceilPowerOfTwo( value ) { - - return Math.pow( 2, Math.ceil( Math.log( value ) / Math.LN2 ) ); - - } - - function floorPowerOfTwo( value ) { - - return Math.pow( 2, Math.floor( Math.log( value ) / Math.LN2 ) ); - - } - - function setQuaternionFromProperEuler( q, a, b, c, order ) { - - // Intrinsic Proper Euler Angles - see https://en.wikipedia.org/wiki/Euler_angles - - // rotations are applied to the axes in the order specified by 'order' - // rotation by angle 'a' is applied first, then by angle 'b', then by angle 'c' - // angles are in radians - - const cos = Math.cos; - const sin = Math.sin; - - const c2 = cos( b / 2 ); - const s2 = sin( b / 2 ); - - const c13 = cos( ( a + c ) / 2 ); - const s13 = sin( ( a + c ) / 2 ); - - const c1_3 = cos( ( a - c ) / 2 ); - const s1_3 = sin( ( a - c ) / 2 ); - - const c3_1 = cos( ( c - a ) / 2 ); - const s3_1 = sin( ( c - a ) / 2 ); - - switch ( order ) { - - case 'XYX': - q.set( c2 * s13, s2 * c1_3, s2 * s1_3, c2 * c13 ); - break; - - case 'YZY': - q.set( s2 * s1_3, c2 * s13, s2 * c1_3, c2 * c13 ); - break; - - case 'ZXZ': - q.set( s2 * c1_3, s2 * s1_3, c2 * s13, c2 * c13 ); - break; - - case 'XZX': - q.set( c2 * s13, s2 * s3_1, s2 * c3_1, c2 * c13 ); - break; - - case 'YXY': - q.set( s2 * c3_1, c2 * s13, s2 * s3_1, c2 * c13 ); - break; - - case 'ZYZ': - q.set( s2 * s3_1, s2 * c3_1, c2 * s13, c2 * c13 ); - break; - - default: - console.warn( 'THREE.MathUtils: .setQuaternionFromProperEuler() encountered an unknown order: ' + order ); - - } - - } - - function denormalize( value, array ) { - - switch ( array.constructor ) { - - case Float32Array: - - return value; - - case Uint32Array: - - return value / 4294967295.0; - - case Uint16Array: - - return value / 65535.0; - - case Uint8Array: - - return value / 255.0; - - case Int32Array: - - return Math.max( value / 2147483647.0, - 1.0 ); - - case Int16Array: - - return Math.max( value / 32767.0, - 1.0 ); - - case Int8Array: - - return Math.max( value / 127.0, - 1.0 ); - - default: - - throw new Error( 'Invalid component type.' ); - - } - - } - - function normalize( value, array ) { - - switch ( array.constructor ) { - - case Float32Array: - - return value; - - case Uint32Array: - - return Math.round( value * 4294967295.0 ); - - case Uint16Array: - - return Math.round( value * 65535.0 ); - - case Uint8Array: - - return Math.round( value * 255.0 ); - - case Int32Array: - - return Math.round( value * 2147483647.0 ); - - case Int16Array: - - return Math.round( value * 32767.0 ); - - case Int8Array: - - return Math.round( value * 127.0 ); - - default: - - throw new Error( 'Invalid component type.' ); - - } - - } - - const MathUtils = { - DEG2RAD: DEG2RAD, - RAD2DEG: RAD2DEG, - generateUUID: generateUUID, - clamp: clamp, - euclideanModulo: euclideanModulo, - mapLinear: mapLinear, - inverseLerp: inverseLerp, - lerp: lerp, - damp: damp, - pingpong: pingpong, - smoothstep: smoothstep, - smootherstep: smootherstep, - randInt: randInt, - randFloat: randFloat, - randFloatSpread: randFloatSpread, - seededRandom: seededRandom, - degToRad: degToRad, - radToDeg: radToDeg, - isPowerOfTwo: isPowerOfTwo, - ceilPowerOfTwo: ceilPowerOfTwo, - floorPowerOfTwo: floorPowerOfTwo, - setQuaternionFromProperEuler: setQuaternionFromProperEuler, - normalize: normalize, - denormalize: denormalize - }; - - class Vector2 { - - constructor( x = 0, y = 0 ) { - - Vector2.prototype.isVector2 = true; - - this.x = x; - this.y = y; - - } - - get width() { - - return this.x; - - } - - set width( value ) { - - this.x = value; - - } - - get height() { - - return this.y; - - } - - set height( value ) { - - this.y = value; - - } - - set( x, y ) { - - this.x = x; - this.y = y; - - return this; - - } - - setScalar( scalar ) { - - this.x = scalar; - this.y = scalar; - - return this; - - } - - setX( x ) { - - this.x = x; - - return this; - - } - - setY( y ) { - - this.y = y; - - return this; - - } - - setComponent( index, value ) { - - switch ( index ) { - - case 0: this.x = value; break; - case 1: this.y = value; break; - default: throw new Error( 'index is out of range: ' + index ); - - } - - return this; - - } - - getComponent( index ) { - - switch ( index ) { - - case 0: return this.x; - case 1: return this.y; - default: throw new Error( 'index is out of range: ' + index ); - - } - - } - - clone() { - - return new this.constructor( this.x, this.y ); - - } - - copy( v ) { - - this.x = v.x; - this.y = v.y; - - return this; - - } - - add( v ) { - - this.x += v.x; - this.y += v.y; - - return this; - - } - - addScalar( s ) { - - this.x += s; - this.y += s; - - return this; - - } - - addVectors( a, b ) { - - this.x = a.x + b.x; - this.y = a.y + b.y; - - return this; - - } - - addScaledVector( v, s ) { - - this.x += v.x * s; - this.y += v.y * s; - - return this; - - } - - sub( v ) { - - this.x -= v.x; - this.y -= v.y; - - return this; - - } - - subScalar( s ) { - - this.x -= s; - this.y -= s; - - return this; - - } - - subVectors( a, b ) { - - this.x = a.x - b.x; - this.y = a.y - b.y; - - return this; - - } - - multiply( v ) { - - this.x *= v.x; - this.y *= v.y; - - return this; - - } - - multiplyScalar( scalar ) { - - this.x *= scalar; - this.y *= scalar; - - return this; - - } - - divide( v ) { - - this.x /= v.x; - this.y /= v.y; - - return this; - - } - - divideScalar( scalar ) { - - return this.multiplyScalar( 1 / scalar ); - - } - - applyMatrix3( m ) { - - const x = this.x, y = this.y; - const e = m.elements; - - this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ]; - this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ]; - - return this; - - } - - min( v ) { - - this.x = Math.min( this.x, v.x ); - this.y = Math.min( this.y, v.y ); - - return this; - - } - - max( v ) { - - this.x = Math.max( this.x, v.x ); - this.y = Math.max( this.y, v.y ); - - return this; - - } - - clamp( min, max ) { - - // assumes min < max, componentwise - - this.x = Math.max( min.x, Math.min( max.x, this.x ) ); - this.y = Math.max( min.y, Math.min( max.y, this.y ) ); - - return this; - - } - - clampScalar( minVal, maxVal ) { - - this.x = Math.max( minVal, Math.min( maxVal, this.x ) ); - this.y = Math.max( minVal, Math.min( maxVal, this.y ) ); - - return this; - - } - - clampLength( min, max ) { - - const length = this.length(); - - return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) ); - - } - - floor() { - - this.x = Math.floor( this.x ); - this.y = Math.floor( this.y ); - - return this; - - } - - ceil() { - - this.x = Math.ceil( this.x ); - this.y = Math.ceil( this.y ); - - return this; - - } - - round() { - - this.x = Math.round( this.x ); - this.y = Math.round( this.y ); - - return this; - - } - - roundToZero() { - - this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x ); - this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y ); - - return this; - - } - - negate() { - - this.x = - this.x; - this.y = - this.y; - - return this; - - } - - dot( v ) { - - return this.x * v.x + this.y * v.y; - - } - - cross( v ) { - - return this.x * v.y - this.y * v.x; - - } - - lengthSq() { - - return this.x * this.x + this.y * this.y; - - } - - length() { - - return Math.sqrt( this.x * this.x + this.y * this.y ); - - } - - manhattanLength() { - - return Math.abs( this.x ) + Math.abs( this.y ); - - } - - normalize() { - - return this.divideScalar( this.length() || 1 ); - - } - - angle() { - - // computes the angle in radians with respect to the positive x-axis - - const angle = Math.atan2( - this.y, - this.x ) + Math.PI; - - return angle; - - } - - angleTo( v ) { - - const denominator = Math.sqrt( this.lengthSq() * v.lengthSq() ); - - if ( denominator === 0 ) return Math.PI / 2; - - const theta = this.dot( v ) / denominator; - - // clamp, to handle numerical problems - - return Math.acos( clamp( theta, - 1, 1 ) ); - - } - - distanceTo( v ) { - - return Math.sqrt( this.distanceToSquared( v ) ); - - } - - distanceToSquared( v ) { - - const dx = this.x - v.x, dy = this.y - v.y; - return dx * dx + dy * dy; - - } - - manhattanDistanceTo( v ) { - - return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y ); - - } - - setLength( length ) { - - return this.normalize().multiplyScalar( length ); - - } - - lerp( v, alpha ) { - - this.x += ( v.x - this.x ) * alpha; - this.y += ( v.y - this.y ) * alpha; - - return this; - - } - - lerpVectors( v1, v2, alpha ) { - - this.x = v1.x + ( v2.x - v1.x ) * alpha; - this.y = v1.y + ( v2.y - v1.y ) * alpha; - - return this; - - } - - equals( v ) { - - return ( ( v.x === this.x ) && ( v.y === this.y ) ); - - } - - fromArray( array, offset = 0 ) { - - this.x = array[ offset ]; - this.y = array[ offset + 1 ]; - - return this; - - } - - toArray( array = [], offset = 0 ) { - - array[ offset ] = this.x; - array[ offset + 1 ] = this.y; - - return array; - - } - - fromBufferAttribute( attribute, index ) { - - this.x = attribute.getX( index ); - this.y = attribute.getY( index ); - - return this; - - } - - rotateAround( center, angle ) { - - const c = Math.cos( angle ), s = Math.sin( angle ); - - const x = this.x - center.x; - const y = this.y - center.y; - - this.x = x * c - y * s + center.x; - this.y = x * s + y * c + center.y; - - return this; - - } - - random() { - - this.x = Math.random(); - this.y = Math.random(); - - return this; - - } - - *[ Symbol.iterator ]() { - - yield this.x; - yield this.y; - - } - - } - - class Matrix3 { - - constructor( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) { - - Matrix3.prototype.isMatrix3 = true; - - this.elements = [ - - 1, 0, 0, - 0, 1, 0, - 0, 0, 1 - - ]; - - if ( n11 !== undefined ) { - - this.set( n11, n12, n13, n21, n22, n23, n31, n32, n33 ); - - } - - } - - set( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) { - - const te = this.elements; - - te[ 0 ] = n11; te[ 1 ] = n21; te[ 2 ] = n31; - te[ 3 ] = n12; te[ 4 ] = n22; te[ 5 ] = n32; - te[ 6 ] = n13; te[ 7 ] = n23; te[ 8 ] = n33; - - return this; - - } - - identity() { - - this.set( - - 1, 0, 0, - 0, 1, 0, - 0, 0, 1 - - ); - - return this; - - } - - copy( m ) { - - const te = this.elements; - const me = m.elements; - - te[ 0 ] = me[ 0 ]; te[ 1 ] = me[ 1 ]; te[ 2 ] = me[ 2 ]; - te[ 3 ] = me[ 3 ]; te[ 4 ] = me[ 4 ]; te[ 5 ] = me[ 5 ]; - te[ 6 ] = me[ 6 ]; te[ 7 ] = me[ 7 ]; te[ 8 ] = me[ 8 ]; - - return this; - - } - - extractBasis( xAxis, yAxis, zAxis ) { - - xAxis.setFromMatrix3Column( this, 0 ); - yAxis.setFromMatrix3Column( this, 1 ); - zAxis.setFromMatrix3Column( this, 2 ); - - return this; - - } - - setFromMatrix4( m ) { - - const me = m.elements; - - this.set( - - me[ 0 ], me[ 4 ], me[ 8 ], - me[ 1 ], me[ 5 ], me[ 9 ], - me[ 2 ], me[ 6 ], me[ 10 ] - - ); - - return this; - - } - - multiply( m ) { - - return this.multiplyMatrices( this, m ); - - } - - premultiply( m ) { - - return this.multiplyMatrices( m, this ); - - } - - multiplyMatrices( a, b ) { - - const ae = a.elements; - const be = b.elements; - const te = this.elements; - - const a11 = ae[ 0 ], a12 = ae[ 3 ], a13 = ae[ 6 ]; - const a21 = ae[ 1 ], a22 = ae[ 4 ], a23 = ae[ 7 ]; - const a31 = ae[ 2 ], a32 = ae[ 5 ], a33 = ae[ 8 ]; - - const b11 = be[ 0 ], b12 = be[ 3 ], b13 = be[ 6 ]; - const b21 = be[ 1 ], b22 = be[ 4 ], b23 = be[ 7 ]; - const b31 = be[ 2 ], b32 = be[ 5 ], b33 = be[ 8 ]; - - te[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31; - te[ 3 ] = a11 * b12 + a12 * b22 + a13 * b32; - te[ 6 ] = a11 * b13 + a12 * b23 + a13 * b33; - - te[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31; - te[ 4 ] = a21 * b12 + a22 * b22 + a23 * b32; - te[ 7 ] = a21 * b13 + a22 * b23 + a23 * b33; - - te[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31; - te[ 5 ] = a31 * b12 + a32 * b22 + a33 * b32; - te[ 8 ] = a31 * b13 + a32 * b23 + a33 * b33; - - return this; - - } - - multiplyScalar( s ) { - - const te = this.elements; - - te[ 0 ] *= s; te[ 3 ] *= s; te[ 6 ] *= s; - te[ 1 ] *= s; te[ 4 ] *= s; te[ 7 ] *= s; - te[ 2 ] *= s; te[ 5 ] *= s; te[ 8 ] *= s; - - return this; - - } - - determinant() { - - const te = this.elements; - - const a = te[ 0 ], b = te[ 1 ], c = te[ 2 ], - d = te[ 3 ], e = te[ 4 ], f = te[ 5 ], - g = te[ 6 ], h = te[ 7 ], i = te[ 8 ]; - - return a * e * i - a * f * h - b * d * i + b * f * g + c * d * h - c * e * g; - - } - - invert() { - - const te = this.elements, - - n11 = te[ 0 ], n21 = te[ 1 ], n31 = te[ 2 ], - n12 = te[ 3 ], n22 = te[ 4 ], n32 = te[ 5 ], - n13 = te[ 6 ], n23 = te[ 7 ], n33 = te[ 8 ], - - t11 = n33 * n22 - n32 * n23, - t12 = n32 * n13 - n33 * n12, - t13 = n23 * n12 - n22 * n13, - - det = n11 * t11 + n21 * t12 + n31 * t13; - - if ( det === 0 ) return this.set( 0, 0, 0, 0, 0, 0, 0, 0, 0 ); - - const detInv = 1 / det; - - te[ 0 ] = t11 * detInv; - te[ 1 ] = ( n31 * n23 - n33 * n21 ) * detInv; - te[ 2 ] = ( n32 * n21 - n31 * n22 ) * detInv; - - te[ 3 ] = t12 * detInv; - te[ 4 ] = ( n33 * n11 - n31 * n13 ) * detInv; - te[ 5 ] = ( n31 * n12 - n32 * n11 ) * detInv; - - te[ 6 ] = t13 * detInv; - te[ 7 ] = ( n21 * n13 - n23 * n11 ) * detInv; - te[ 8 ] = ( n22 * n11 - n21 * n12 ) * detInv; - - return this; - - } - - transpose() { - - let tmp; - const m = this.elements; - - tmp = m[ 1 ]; m[ 1 ] = m[ 3 ]; m[ 3 ] = tmp; - tmp = m[ 2 ]; m[ 2 ] = m[ 6 ]; m[ 6 ] = tmp; - tmp = m[ 5 ]; m[ 5 ] = m[ 7 ]; m[ 7 ] = tmp; - - return this; - - } - - getNormalMatrix( matrix4 ) { - - return this.setFromMatrix4( matrix4 ).invert().transpose(); - - } - - transposeIntoArray( r ) { - - const m = this.elements; - - r[ 0 ] = m[ 0 ]; - r[ 1 ] = m[ 3 ]; - r[ 2 ] = m[ 6 ]; - r[ 3 ] = m[ 1 ]; - r[ 4 ] = m[ 4 ]; - r[ 5 ] = m[ 7 ]; - r[ 6 ] = m[ 2 ]; - r[ 7 ] = m[ 5 ]; - r[ 8 ] = m[ 8 ]; - - return this; - - } - - setUvTransform( tx, ty, sx, sy, rotation, cx, cy ) { - - const c = Math.cos( rotation ); - const s = Math.sin( rotation ); - - this.set( - sx * c, sx * s, - sx * ( c * cx + s * cy ) + cx + tx, - - sy * s, sy * c, - sy * ( - s * cx + c * cy ) + cy + ty, - 0, 0, 1 - ); - - return this; - - } - - // - - scale( sx, sy ) { - - this.premultiply( _m3.makeScale( sx, sy ) ); - - return this; - - } - - rotate( theta ) { - - this.premultiply( _m3.makeRotation( - theta ) ); - - return this; - - } - - translate( tx, ty ) { - - this.premultiply( _m3.makeTranslation( tx, ty ) ); - - return this; - - } - - // for 2D Transforms - - makeTranslation( x, y ) { - - this.set( - - 1, 0, x, - 0, 1, y, - 0, 0, 1 - - ); - - return this; - - } - - makeRotation( theta ) { - - // counterclockwise - - const c = Math.cos( theta ); - const s = Math.sin( theta ); - - this.set( - - c, - s, 0, - s, c, 0, - 0, 0, 1 - - ); - - return this; - - } - - makeScale( x, y ) { - - this.set( - - x, 0, 0, - 0, y, 0, - 0, 0, 1 - - ); - - return this; - - } - - // - - equals( matrix ) { - - const te = this.elements; - const me = matrix.elements; - - for ( let i = 0; i < 9; i ++ ) { - - if ( te[ i ] !== me[ i ] ) return false; - - } - - return true; - - } - - fromArray( array, offset = 0 ) { - - for ( let i = 0; i < 9; i ++ ) { - - this.elements[ i ] = array[ i + offset ]; - - } - - return this; - - } - - toArray( array = [], offset = 0 ) { - - const te = this.elements; - - array[ offset ] = te[ 0 ]; - array[ offset + 1 ] = te[ 1 ]; - array[ offset + 2 ] = te[ 2 ]; - - array[ offset + 3 ] = te[ 3 ]; - array[ offset + 4 ] = te[ 4 ]; - array[ offset + 5 ] = te[ 5 ]; - - array[ offset + 6 ] = te[ 6 ]; - array[ offset + 7 ] = te[ 7 ]; - array[ offset + 8 ] = te[ 8 ]; - - return array; - - } - - clone() { - - return new this.constructor().fromArray( this.elements ); - - } - - } - - const _m3 = /*@__PURE__*/ new Matrix3(); - - function arrayNeedsUint32( array ) { - - // assumes larger values usually on last - - for ( let i = array.length - 1; i >= 0; -- i ) { - - if ( array[ i ] >= 65535 ) return true; // account for PRIMITIVE_RESTART_FIXED_INDEX, #24565 - - } - - return false; - - } - - const TYPED_ARRAYS = { - Int8Array: Int8Array, - Uint8Array: Uint8Array, - Uint8ClampedArray: Uint8ClampedArray, - Int16Array: Int16Array, - Uint16Array: Uint16Array, - Int32Array: Int32Array, - Uint32Array: Uint32Array, - Float32Array: Float32Array, - Float64Array: Float64Array - }; - - function getTypedArray( type, buffer ) { - - return new TYPED_ARRAYS[ type ]( buffer ); - - } - - function createElementNS( name ) { - - return document.createElementNS( 'http://www.w3.org/1999/xhtml', name ); - - } - - const _cache = {}; - - function warnOnce( message ) { - - if ( message in _cache ) return; - - _cache[ message ] = true; - - console.warn( message ); - - } - - function SRGBToLinear( c ) { - - return ( c < 0.04045 ) ? c * 0.0773993808 : Math.pow( c * 0.9478672986 + 0.0521327014, 2.4 ); - - } - - function LinearToSRGB( c ) { - - return ( c < 0.0031308 ) ? c * 12.92 : 1.055 * ( Math.pow( c, 0.41666 ) ) - 0.055; - - } - - /** - * Matrices converting P3 <-> Rec. 709 primaries, without gamut mapping - * or clipping. Based on W3C specifications for sRGB and Display P3, - * and ICC specifications for the D50 connection space. Values in/out - * are _linear_ sRGB and _linear_ Display P3. - * - * Note that both sRGB and Display P3 use the sRGB transfer functions. - * - * Reference: - * - http://www.russellcottrell.com/photo/matrixCalculator.htm - */ - - const LINEAR_SRGB_TO_LINEAR_DISPLAY_P3 = /*@__PURE__*/ new Matrix3().fromArray( [ - 0.8224621, 0.0331941, 0.0170827, - 0.1775380, 0.9668058, 0.0723974, - - 0.0000001, 0.0000001, 0.9105199 - ] ); - - const LINEAR_DISPLAY_P3_TO_LINEAR_SRGB = /*@__PURE__*/ new Matrix3().fromArray( [ - 1.2249401, - 0.0420569, - 0.0196376, - - 0.2249404, 1.0420571, - 0.0786361, - 0.0000001, 0.0000000, 1.0982735 - ] ); - - function DisplayP3ToLinearSRGB( color ) { - - // Display P3 uses the sRGB transfer functions - return color.convertSRGBToLinear().applyMatrix3( LINEAR_DISPLAY_P3_TO_LINEAR_SRGB ); - - } - - function LinearSRGBToDisplayP3( color ) { - - // Display P3 uses the sRGB transfer functions - return color.applyMatrix3( LINEAR_SRGB_TO_LINEAR_DISPLAY_P3 ).convertLinearToSRGB(); - - } - - // Conversions from to Linear-sRGB reference space. - const TO_LINEAR = { - [ LinearSRGBColorSpace ]: ( color ) => color, - [ SRGBColorSpace ]: ( color ) => color.convertSRGBToLinear(), - [ DisplayP3ColorSpace ]: DisplayP3ToLinearSRGB, - }; - - // Conversions to from Linear-sRGB reference space. - const FROM_LINEAR = { - [ LinearSRGBColorSpace ]: ( color ) => color, - [ SRGBColorSpace ]: ( color ) => color.convertLinearToSRGB(), - [ DisplayP3ColorSpace ]: LinearSRGBToDisplayP3, - }; - - const ColorManagement = { - - enabled: true, - - get legacyMode() { - - console.warn( 'THREE.ColorManagement: .legacyMode=false renamed to .enabled=true in r150.' ); - - return ! this.enabled; - - }, - - set legacyMode( legacyMode ) { - - console.warn( 'THREE.ColorManagement: .legacyMode=false renamed to .enabled=true in r150.' ); - - this.enabled = ! legacyMode; - - }, - - get workingColorSpace() { - - return LinearSRGBColorSpace; - - }, - - set workingColorSpace( colorSpace ) { - - console.warn( 'THREE.ColorManagement: .workingColorSpace is readonly.' ); - - }, - - convert: function ( color, sourceColorSpace, targetColorSpace ) { - - if ( this.enabled === false || sourceColorSpace === targetColorSpace || ! sourceColorSpace || ! targetColorSpace ) { - - return color; - - } - - const sourceToLinear = TO_LINEAR[ sourceColorSpace ]; - const targetFromLinear = FROM_LINEAR[ targetColorSpace ]; - - if ( sourceToLinear === undefined || targetFromLinear === undefined ) { - - throw new Error( `Unsupported color space conversion, "${ sourceColorSpace }" to "${ targetColorSpace }".` ); - - } - - return targetFromLinear( sourceToLinear( color ) ); - - }, - - fromWorkingColorSpace: function ( color, targetColorSpace ) { - - return this.convert( color, this.workingColorSpace, targetColorSpace ); - - }, - - toWorkingColorSpace: function ( color, sourceColorSpace ) { - - return this.convert( color, sourceColorSpace, this.workingColorSpace ); - - }, - - }; - - let _canvas; - - class ImageUtils { - - static getDataURL( image ) { - - if ( /^data:/i.test( image.src ) ) { - - return image.src; - - } - - if ( typeof HTMLCanvasElement === 'undefined' ) { - - return image.src; - - } - - let canvas; - - if ( image instanceof HTMLCanvasElement ) { - - canvas = image; - - } else { - - if ( _canvas === undefined ) _canvas = createElementNS( 'canvas' ); - - _canvas.width = image.width; - _canvas.height = image.height; - - const context = _canvas.getContext( '2d' ); - - if ( image instanceof ImageData ) { - - context.putImageData( image, 0, 0 ); - - } else { - - context.drawImage( image, 0, 0, image.width, image.height ); - - } - - canvas = _canvas; - - } - - if ( canvas.width > 2048 || canvas.height > 2048 ) { - - console.warn( 'THREE.ImageUtils.getDataURL: Image converted to jpg for performance reasons', image ); - - return canvas.toDataURL( 'image/jpeg', 0.6 ); - - } else { - - return canvas.toDataURL( 'image/png' ); - - } - - } - - static sRGBToLinear( image ) { - - if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || - ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) || - ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) { - - const canvas = createElementNS( 'canvas' ); - - canvas.width = image.width; - canvas.height = image.height; - - const context = canvas.getContext( '2d' ); - context.drawImage( image, 0, 0, image.width, image.height ); - - const imageData = context.getImageData( 0, 0, image.width, image.height ); - const data = imageData.data; - - for ( let i = 0; i < data.length; i ++ ) { - - data[ i ] = SRGBToLinear( data[ i ] / 255 ) * 255; - - } - - context.putImageData( imageData, 0, 0 ); - - return canvas; - - } else if ( image.data ) { - - const data = image.data.slice( 0 ); - - for ( let i = 0; i < data.length; i ++ ) { - - if ( data instanceof Uint8Array || data instanceof Uint8ClampedArray ) { - - data[ i ] = Math.floor( SRGBToLinear( data[ i ] / 255 ) * 255 ); - - } else { - - // assuming float - - data[ i ] = SRGBToLinear( data[ i ] ); - - } - - } - - return { - data: data, - width: image.width, - height: image.height - }; - - } else { - - console.warn( 'THREE.ImageUtils.sRGBToLinear(): Unsupported image type. No color space conversion applied.' ); - return image; - - } - - } - - } - - let sourceId = 0; - - class Source { - - constructor( data = null ) { - - this.isSource = true; - - Object.defineProperty( this, 'id', { value: sourceId ++ } ); - - this.uuid = generateUUID(); - - this.data = data; - - this.version = 0; - - } - - set needsUpdate( value ) { - - if ( value === true ) this.version ++; - - } - - toJSON( meta ) { - - const isRootObject = ( meta === undefined || typeof meta === 'string' ); - - if ( ! isRootObject && meta.images[ this.uuid ] !== undefined ) { - - return meta.images[ this.uuid ]; - - } - - const output = { - uuid: this.uuid, - url: '' - }; - - const data = this.data; - - if ( data !== null ) { - - let url; - - if ( Array.isArray( data ) ) { - - // cube texture - - url = []; - - for ( let i = 0, l = data.length; i < l; i ++ ) { - - if ( data[ i ].isDataTexture ) { - - url.push( serializeImage( data[ i ].image ) ); - - } else { - - url.push( serializeImage( data[ i ] ) ); - - } - - } - - } else { - - // texture - - url = serializeImage( data ); - - } - - output.url = url; - - } - - if ( ! isRootObject ) { - - meta.images[ this.uuid ] = output; - - } - - return output; - - } - - } - - function serializeImage( image ) { - - if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || - ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) || - ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) { - - // default images - - return ImageUtils.getDataURL( image ); - - } else { - - if ( image.data ) { - - // images of DataTexture - - return { - data: Array.from( image.data ), - width: image.width, - height: image.height, - type: image.data.constructor.name - }; - - } else { - - console.warn( 'THREE.Texture: Unable to serialize Texture.' ); - return {}; - - } - - } - - } - - let textureId = 0; - - class Texture extends EventDispatcher { - - constructor( image = Texture.DEFAULT_IMAGE, mapping = Texture.DEFAULT_MAPPING, wrapS = ClampToEdgeWrapping, wrapT = ClampToEdgeWrapping, magFilter = LinearFilter, minFilter = LinearMipmapLinearFilter, format = RGBAFormat, type = UnsignedByteType, anisotropy = Texture.DEFAULT_ANISOTROPY, colorSpace = NoColorSpace ) { - - super(); - - this.isTexture = true; - - Object.defineProperty( this, 'id', { value: textureId ++ } ); - - this.uuid = generateUUID(); - - this.name = ''; - - this.source = new Source( image ); - this.mipmaps = []; - - this.mapping = mapping; - this.channel = 0; - - this.wrapS = wrapS; - this.wrapT = wrapT; - - this.magFilter = magFilter; - this.minFilter = minFilter; - - this.anisotropy = anisotropy; - - this.format = format; - this.internalFormat = null; - this.type = type; - - this.offset = new Vector2( 0, 0 ); - this.repeat = new Vector2( 1, 1 ); - this.center = new Vector2( 0, 0 ); - this.rotation = 0; - - this.matrixAutoUpdate = true; - this.matrix = new Matrix3(); - - this.generateMipmaps = true; - this.premultiplyAlpha = false; - this.flipY = true; - this.unpackAlignment = 4; // valid values: 1, 2, 4, 8 (see http://www.khronos.org/opengles/sdk/docs/man/xhtml/glPixelStorei.xml) - - if ( typeof colorSpace === 'string' ) { - - this.colorSpace = colorSpace; - - } else { // @deprecated, r152 - - warnOnce( 'THREE.Texture: Property .encoding has been replaced by .colorSpace.' ); - this.colorSpace = colorSpace === sRGBEncoding ? SRGBColorSpace : NoColorSpace; - - } - - - this.userData = {}; - - this.version = 0; - this.onUpdate = null; - - this.isRenderTargetTexture = false; // indicates whether a texture belongs to a render target or not - this.needsPMREMUpdate = false; // indicates whether this texture should be processed by PMREMGenerator or not (only relevant for render target textures) - - } - - get image() { - - return this.source.data; - - } - - set image( value = null ) { - - this.source.data = value; - - } - - updateMatrix() { - - this.matrix.setUvTransform( this.offset.x, this.offset.y, this.repeat.x, this.repeat.y, this.rotation, this.center.x, this.center.y ); - - } - - clone() { - - return new this.constructor().copy( this ); - - } - - copy( source ) { - - this.name = source.name; - - this.source = source.source; - this.mipmaps = source.mipmaps.slice( 0 ); - - this.mapping = source.mapping; - this.channel = source.channel; - - this.wrapS = source.wrapS; - this.wrapT = source.wrapT; - - this.magFilter = source.magFilter; - this.minFilter = source.minFilter; - - this.anisotropy = source.anisotropy; - - this.format = source.format; - this.internalFormat = source.internalFormat; - this.type = source.type; - - this.offset.copy( source.offset ); - this.repeat.copy( source.repeat ); - this.center.copy( source.center ); - this.rotation = source.rotation; - - this.matrixAutoUpdate = source.matrixAutoUpdate; - this.matrix.copy( source.matrix ); - - this.generateMipmaps = source.generateMipmaps; - this.premultiplyAlpha = source.premultiplyAlpha; - this.flipY = source.flipY; - this.unpackAlignment = source.unpackAlignment; - this.colorSpace = source.colorSpace; - - this.userData = JSON.parse( JSON.stringify( source.userData ) ); - - this.needsUpdate = true; - - return this; - - } - - toJSON( meta ) { - - const isRootObject = ( meta === undefined || typeof meta === 'string' ); - - if ( ! isRootObject && meta.textures[ this.uuid ] !== undefined ) { - - return meta.textures[ this.uuid ]; - - } - - const output = { - - metadata: { - version: 4.6, - type: 'Texture', - generator: 'Texture.toJSON' - }, - - uuid: this.uuid, - name: this.name, - - image: this.source.toJSON( meta ).uuid, - - mapping: this.mapping, - channel: this.channel, - - repeat: [ this.repeat.x, this.repeat.y ], - offset: [ this.offset.x, this.offset.y ], - center: [ this.center.x, this.center.y ], - rotation: this.rotation, - - wrap: [ this.wrapS, this.wrapT ], - - format: this.format, - internalFormat: this.internalFormat, - type: this.type, - colorSpace: this.colorSpace, - - minFilter: this.minFilter, - magFilter: this.magFilter, - anisotropy: this.anisotropy, - - flipY: this.flipY, - - generateMipmaps: this.generateMipmaps, - premultiplyAlpha: this.premultiplyAlpha, - unpackAlignment: this.unpackAlignment - - }; - - if ( Object.keys( this.userData ).length > 0 ) output.userData = this.userData; - - if ( ! isRootObject ) { - - meta.textures[ this.uuid ] = output; - - } - - return output; - - } - - dispose() { - - this.dispatchEvent( { type: 'dispose' } ); - - } - - transformUv( uv ) { - - if ( this.mapping !== UVMapping ) return uv; - - uv.applyMatrix3( this.matrix ); - - if ( uv.x < 0 || uv.x > 1 ) { - - switch ( this.wrapS ) { - - case RepeatWrapping: - - uv.x = uv.x - Math.floor( uv.x ); - break; - - case ClampToEdgeWrapping: - - uv.x = uv.x < 0 ? 0 : 1; - break; - - case MirroredRepeatWrapping: - - if ( Math.abs( Math.floor( uv.x ) % 2 ) === 1 ) { - - uv.x = Math.ceil( uv.x ) - uv.x; - - } else { - - uv.x = uv.x - Math.floor( uv.x ); - - } - - break; - - } - - } - - if ( uv.y < 0 || uv.y > 1 ) { - - switch ( this.wrapT ) { - - case RepeatWrapping: - - uv.y = uv.y - Math.floor( uv.y ); - break; - - case ClampToEdgeWrapping: - - uv.y = uv.y < 0 ? 0 : 1; - break; - - case MirroredRepeatWrapping: - - if ( Math.abs( Math.floor( uv.y ) % 2 ) === 1 ) { - - uv.y = Math.ceil( uv.y ) - uv.y; - - } else { - - uv.y = uv.y - Math.floor( uv.y ); - - } - - break; - - } - - } - - if ( this.flipY ) { - - uv.y = 1 - uv.y; - - } - - return uv; - - } - - set needsUpdate( value ) { - - if ( value === true ) { - - this.version ++; - this.source.needsUpdate = true; - - } - - } - - get encoding() { // @deprecated, r152 - - warnOnce( 'THREE.Texture: Property .encoding has been replaced by .colorSpace.' ); - return this.colorSpace === SRGBColorSpace ? sRGBEncoding : LinearEncoding; - - } - - set encoding( encoding ) { // @deprecated, r152 - - warnOnce( 'THREE.Texture: Property .encoding has been replaced by .colorSpace.' ); - this.colorSpace = encoding === sRGBEncoding ? SRGBColorSpace : NoColorSpace; - - } - - } - - Texture.DEFAULT_IMAGE = null; - Texture.DEFAULT_MAPPING = UVMapping; - Texture.DEFAULT_ANISOTROPY = 1; - - class Vector4 { - - constructor( x = 0, y = 0, z = 0, w = 1 ) { - - Vector4.prototype.isVector4 = true; - - this.x = x; - this.y = y; - this.z = z; - this.w = w; - - } - - get width() { - - return this.z; - - } - - set width( value ) { - - this.z = value; - - } - - get height() { - - return this.w; - - } - - set height( value ) { - - this.w = value; - - } - - set( x, y, z, w ) { - - this.x = x; - this.y = y; - this.z = z; - this.w = w; - - return this; - - } - - setScalar( scalar ) { - - this.x = scalar; - this.y = scalar; - this.z = scalar; - this.w = scalar; - - return this; - - } - - setX( x ) { - - this.x = x; - - return this; - - } - - setY( y ) { - - this.y = y; - - return this; - - } - - setZ( z ) { - - this.z = z; - - return this; - - } - - setW( w ) { - - this.w = w; - - return this; - - } - - setComponent( index, value ) { - - switch ( index ) { - - case 0: this.x = value; break; - case 1: this.y = value; break; - case 2: this.z = value; break; - case 3: this.w = value; break; - default: throw new Error( 'index is out of range: ' + index ); - - } - - return this; - - } - - getComponent( index ) { - - switch ( index ) { - - case 0: return this.x; - case 1: return this.y; - case 2: return this.z; - case 3: return this.w; - default: throw new Error( 'index is out of range: ' + index ); - - } - - } - - clone() { - - return new this.constructor( this.x, this.y, this.z, this.w ); - - } - - copy( v ) { - - this.x = v.x; - this.y = v.y; - this.z = v.z; - this.w = ( v.w !== undefined ) ? v.w : 1; - - return this; - - } - - add( v ) { - - this.x += v.x; - this.y += v.y; - this.z += v.z; - this.w += v.w; - - return this; - - } - - addScalar( s ) { - - this.x += s; - this.y += s; - this.z += s; - this.w += s; - - return this; - - } - - addVectors( a, b ) { - - this.x = a.x + b.x; - this.y = a.y + b.y; - this.z = a.z + b.z; - this.w = a.w + b.w; - - return this; - - } - - addScaledVector( v, s ) { - - this.x += v.x * s; - this.y += v.y * s; - this.z += v.z * s; - this.w += v.w * s; - - return this; - - } - - sub( v ) { - - this.x -= v.x; - this.y -= v.y; - this.z -= v.z; - this.w -= v.w; - - return this; - - } - - subScalar( s ) { - - this.x -= s; - this.y -= s; - this.z -= s; - this.w -= s; - - return this; - - } - - subVectors( a, b ) { - - this.x = a.x - b.x; - this.y = a.y - b.y; - this.z = a.z - b.z; - this.w = a.w - b.w; - - return this; - - } - - multiply( v ) { - - this.x *= v.x; - this.y *= v.y; - this.z *= v.z; - this.w *= v.w; - - return this; - - } - - multiplyScalar( scalar ) { - - this.x *= scalar; - this.y *= scalar; - this.z *= scalar; - this.w *= scalar; - - return this; - - } - - applyMatrix4( m ) { - - const x = this.x, y = this.y, z = this.z, w = this.w; - const e = m.elements; - - this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] * w; - this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] * w; - this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] * w; - this.w = e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] * w; - - return this; - - } - - divideScalar( scalar ) { - - return this.multiplyScalar( 1 / scalar ); - - } - - setAxisAngleFromQuaternion( q ) { - - // http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToAngle/index.htm - - // q is assumed to be normalized - - this.w = 2 * Math.acos( q.w ); - - const s = Math.sqrt( 1 - q.w * q.w ); - - if ( s < 0.0001 ) { - - this.x = 1; - this.y = 0; - this.z = 0; - - } else { - - this.x = q.x / s; - this.y = q.y / s; - this.z = q.z / s; - - } - - return this; - - } - - setAxisAngleFromRotationMatrix( m ) { - - // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToAngle/index.htm - - // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) - - let angle, x, y, z; // variables for result - const epsilon = 0.01, // margin to allow for rounding errors - epsilon2 = 0.1, // margin to distinguish between 0 and 180 degrees - - te = m.elements, - - m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ], - m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ], - m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ]; - - if ( ( Math.abs( m12 - m21 ) < epsilon ) && - ( Math.abs( m13 - m31 ) < epsilon ) && - ( Math.abs( m23 - m32 ) < epsilon ) ) { - - // singularity found - // first check for identity matrix which must have +1 for all terms - // in leading diagonal and zero in other terms - - if ( ( Math.abs( m12 + m21 ) < epsilon2 ) && - ( Math.abs( m13 + m31 ) < epsilon2 ) && - ( Math.abs( m23 + m32 ) < epsilon2 ) && - ( Math.abs( m11 + m22 + m33 - 3 ) < epsilon2 ) ) { - - // this singularity is identity matrix so angle = 0 - - this.set( 1, 0, 0, 0 ); - - return this; // zero angle, arbitrary axis - - } - - // otherwise this singularity is angle = 180 - - angle = Math.PI; - - const xx = ( m11 + 1 ) / 2; - const yy = ( m22 + 1 ) / 2; - const zz = ( m33 + 1 ) / 2; - const xy = ( m12 + m21 ) / 4; - const xz = ( m13 + m31 ) / 4; - const yz = ( m23 + m32 ) / 4; - - if ( ( xx > yy ) && ( xx > zz ) ) { - - // m11 is the largest diagonal term - - if ( xx < epsilon ) { - - x = 0; - y = 0.707106781; - z = 0.707106781; - - } else { - - x = Math.sqrt( xx ); - y = xy / x; - z = xz / x; - - } - - } else if ( yy > zz ) { - - // m22 is the largest diagonal term - - if ( yy < epsilon ) { - - x = 0.707106781; - y = 0; - z = 0.707106781; - - } else { - - y = Math.sqrt( yy ); - x = xy / y; - z = yz / y; - - } - - } else { - - // m33 is the largest diagonal term so base result on this - - if ( zz < epsilon ) { - - x = 0.707106781; - y = 0.707106781; - z = 0; - - } else { - - z = Math.sqrt( zz ); - x = xz / z; - y = yz / z; - - } - - } - - this.set( x, y, z, angle ); - - return this; // return 180 deg rotation - - } - - // as we have reached here there are no singularities so we can handle normally - - let s = Math.sqrt( ( m32 - m23 ) * ( m32 - m23 ) + - ( m13 - m31 ) * ( m13 - m31 ) + - ( m21 - m12 ) * ( m21 - m12 ) ); // used to normalize - - if ( Math.abs( s ) < 0.001 ) s = 1; - - // prevent divide by zero, should not happen if matrix is orthogonal and should be - // caught by singularity test above, but I've left it in just in case - - this.x = ( m32 - m23 ) / s; - this.y = ( m13 - m31 ) / s; - this.z = ( m21 - m12 ) / s; - this.w = Math.acos( ( m11 + m22 + m33 - 1 ) / 2 ); - - return this; - - } - - min( v ) { - - this.x = Math.min( this.x, v.x ); - this.y = Math.min( this.y, v.y ); - this.z = Math.min( this.z, v.z ); - this.w = Math.min( this.w, v.w ); - - return this; - - } - - max( v ) { - - this.x = Math.max( this.x, v.x ); - this.y = Math.max( this.y, v.y ); - this.z = Math.max( this.z, v.z ); - this.w = Math.max( this.w, v.w ); - - return this; - - } - - clamp( min, max ) { - - // assumes min < max, componentwise - - this.x = Math.max( min.x, Math.min( max.x, this.x ) ); - this.y = Math.max( min.y, Math.min( max.y, this.y ) ); - this.z = Math.max( min.z, Math.min( max.z, this.z ) ); - this.w = Math.max( min.w, Math.min( max.w, this.w ) ); - - return this; - - } - - clampScalar( minVal, maxVal ) { - - this.x = Math.max( minVal, Math.min( maxVal, this.x ) ); - this.y = Math.max( minVal, Math.min( maxVal, this.y ) ); - this.z = Math.max( minVal, Math.min( maxVal, this.z ) ); - this.w = Math.max( minVal, Math.min( maxVal, this.w ) ); - - return this; - - } - - clampLength( min, max ) { - - const length = this.length(); - - return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) ); - - } - - floor() { - - this.x = Math.floor( this.x ); - this.y = Math.floor( this.y ); - this.z = Math.floor( this.z ); - this.w = Math.floor( this.w ); - - return this; - - } - - ceil() { - - this.x = Math.ceil( this.x ); - this.y = Math.ceil( this.y ); - this.z = Math.ceil( this.z ); - this.w = Math.ceil( this.w ); - - return this; - - } - - round() { - - this.x = Math.round( this.x ); - this.y = Math.round( this.y ); - this.z = Math.round( this.z ); - this.w = Math.round( this.w ); - - return this; - - } - - roundToZero() { - - this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x ); - this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y ); - this.z = ( this.z < 0 ) ? Math.ceil( this.z ) : Math.floor( this.z ); - this.w = ( this.w < 0 ) ? Math.ceil( this.w ) : Math.floor( this.w ); - - return this; - - } - - negate() { - - this.x = - this.x; - this.y = - this.y; - this.z = - this.z; - this.w = - this.w; - - return this; - - } - - dot( v ) { - - return this.x * v.x + this.y * v.y + this.z * v.z + this.w * v.w; - - } - - lengthSq() { - - return this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w; - - } - - length() { - - return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w ); - - } - - manhattanLength() { - - return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ) + Math.abs( this.w ); - - } - - normalize() { - - return this.divideScalar( this.length() || 1 ); - - } - - setLength( length ) { - - return this.normalize().multiplyScalar( length ); - - } - - lerp( v, alpha ) { - - this.x += ( v.x - this.x ) * alpha; - this.y += ( v.y - this.y ) * alpha; - this.z += ( v.z - this.z ) * alpha; - this.w += ( v.w - this.w ) * alpha; - - return this; - - } - - lerpVectors( v1, v2, alpha ) { - - this.x = v1.x + ( v2.x - v1.x ) * alpha; - this.y = v1.y + ( v2.y - v1.y ) * alpha; - this.z = v1.z + ( v2.z - v1.z ) * alpha; - this.w = v1.w + ( v2.w - v1.w ) * alpha; - - return this; - - } - - equals( v ) { - - return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) && ( v.w === this.w ) ); - - } - - fromArray( array, offset = 0 ) { - - this.x = array[ offset ]; - this.y = array[ offset + 1 ]; - this.z = array[ offset + 2 ]; - this.w = array[ offset + 3 ]; - - return this; - - } - - toArray( array = [], offset = 0 ) { - - array[ offset ] = this.x; - array[ offset + 1 ] = this.y; - array[ offset + 2 ] = this.z; - array[ offset + 3 ] = this.w; - - return array; - - } - - fromBufferAttribute( attribute, index ) { - - this.x = attribute.getX( index ); - this.y = attribute.getY( index ); - this.z = attribute.getZ( index ); - this.w = attribute.getW( index ); - - return this; - - } - - random() { - - this.x = Math.random(); - this.y = Math.random(); - this.z = Math.random(); - this.w = Math.random(); - - return this; - - } - - *[ Symbol.iterator ]() { - - yield this.x; - yield this.y; - yield this.z; - yield this.w; - - } - - } - - /* - In options, we can specify: - * Texture parameters for an auto-generated target texture - * depthBuffer/stencilBuffer: Booleans to indicate if we should generate these buffers - */ - class WebGLRenderTarget extends EventDispatcher { - - constructor( width = 1, height = 1, options = {} ) { - - super(); - - this.isWebGLRenderTarget = true; - - this.width = width; - this.height = height; - this.depth = 1; - - this.scissor = new Vector4( 0, 0, width, height ); - this.scissorTest = false; - - this.viewport = new Vector4( 0, 0, width, height ); - - const image = { width: width, height: height, depth: 1 }; - - if ( options.encoding !== undefined ) { - - // @deprecated, r152 - warnOnce( 'THREE.WebGLRenderTarget: option.encoding has been replaced by option.colorSpace.' ); - options.colorSpace = options.encoding === sRGBEncoding ? SRGBColorSpace : NoColorSpace; - - } - - this.texture = new Texture( image, options.mapping, options.wrapS, options.wrapT, options.magFilter, options.minFilter, options.format, options.type, options.anisotropy, options.colorSpace ); - this.texture.isRenderTargetTexture = true; - - this.texture.flipY = false; - this.texture.generateMipmaps = options.generateMipmaps !== undefined ? options.generateMipmaps : false; - this.texture.internalFormat = options.internalFormat !== undefined ? options.internalFormat : null; - this.texture.minFilter = options.minFilter !== undefined ? options.minFilter : LinearFilter; - - this.depthBuffer = options.depthBuffer !== undefined ? options.depthBuffer : true; - this.stencilBuffer = options.stencilBuffer !== undefined ? options.stencilBuffer : false; - - this.depthTexture = options.depthTexture !== undefined ? options.depthTexture : null; - - this.samples = options.samples !== undefined ? options.samples : 0; - - } - - setSize( width, height, depth = 1 ) { - - if ( this.width !== width || this.height !== height || this.depth !== depth ) { - - this.width = width; - this.height = height; - this.depth = depth; - - this.texture.image.width = width; - this.texture.image.height = height; - this.texture.image.depth = depth; - - this.dispose(); - - } - - this.viewport.set( 0, 0, width, height ); - this.scissor.set( 0, 0, width, height ); - - } - - clone() { - - return new this.constructor().copy( this ); - - } - - copy( source ) { - - this.width = source.width; - this.height = source.height; - this.depth = source.depth; - - this.scissor.copy( source.scissor ); - this.scissorTest = source.scissorTest; - - this.viewport.copy( source.viewport ); - - this.texture = source.texture.clone(); - this.texture.isRenderTargetTexture = true; - - // ensure image object is not shared, see #20328 - - const image = Object.assign( {}, source.texture.image ); - this.texture.source = new Source( image ); - - this.depthBuffer = source.depthBuffer; - this.stencilBuffer = source.stencilBuffer; - - if ( source.depthTexture !== null ) this.depthTexture = source.depthTexture.clone(); - - this.samples = source.samples; - - return this; - - } - - dispose() { - - this.dispatchEvent( { type: 'dispose' } ); - - } - - } - - class DataArrayTexture extends Texture { - - constructor( data = null, width = 1, height = 1, depth = 1 ) { - - super( null ); - - this.isDataArrayTexture = true; - - this.image = { data, width, height, depth }; - - this.magFilter = NearestFilter; - this.minFilter = NearestFilter; - - this.wrapR = ClampToEdgeWrapping; - - this.generateMipmaps = false; - this.flipY = false; - this.unpackAlignment = 1; - - } - - } - - class WebGLArrayRenderTarget extends WebGLRenderTarget { - - constructor( width = 1, height = 1, depth = 1 ) { - - super( width, height ); - - this.isWebGLArrayRenderTarget = true; - - this.depth = depth; - - this.texture = new DataArrayTexture( null, width, height, depth ); - - this.texture.isRenderTargetTexture = true; - - } - - } - - class Data3DTexture extends Texture { - - constructor( data = null, width = 1, height = 1, depth = 1 ) { - - // We're going to add .setXXX() methods for setting properties later. - // Users can still set in DataTexture3D directly. - // - // const texture = new THREE.DataTexture3D( data, width, height, depth ); - // texture.anisotropy = 16; - // - // See #14839 - - super( null ); - - this.isData3DTexture = true; - - this.image = { data, width, height, depth }; - - this.magFilter = NearestFilter; - this.minFilter = NearestFilter; - - this.wrapR = ClampToEdgeWrapping; - - this.generateMipmaps = false; - this.flipY = false; - this.unpackAlignment = 1; - - } - - } - - class WebGL3DRenderTarget extends WebGLRenderTarget { - - constructor( width = 1, height = 1, depth = 1 ) { - - super( width, height ); - - this.isWebGL3DRenderTarget = true; - - this.depth = depth; - - this.texture = new Data3DTexture( null, width, height, depth ); - - this.texture.isRenderTargetTexture = true; - - } - - } - - class WebGLMultipleRenderTargets extends WebGLRenderTarget { - - constructor( width = 1, height = 1, count = 1, options = {} ) { - - super( width, height, options ); - - this.isWebGLMultipleRenderTargets = true; - - const texture = this.texture; - - this.texture = []; - - for ( let i = 0; i < count; i ++ ) { - - this.texture[ i ] = texture.clone(); - this.texture[ i ].isRenderTargetTexture = true; - - } - - } - - setSize( width, height, depth = 1 ) { - - if ( this.width !== width || this.height !== height || this.depth !== depth ) { - - this.width = width; - this.height = height; - this.depth = depth; - - for ( let i = 0, il = this.texture.length; i < il; i ++ ) { - - this.texture[ i ].image.width = width; - this.texture[ i ].image.height = height; - this.texture[ i ].image.depth = depth; - - } - - this.dispose(); - - } - - this.viewport.set( 0, 0, width, height ); - this.scissor.set( 0, 0, width, height ); - - return this; - - } - - copy( source ) { - - this.dispose(); - - this.width = source.width; - this.height = source.height; - this.depth = source.depth; - - this.scissor.copy( source.scissor ); - this.scissorTest = source.scissorTest; - - this.viewport.copy( source.viewport ); - - this.depthBuffer = source.depthBuffer; - this.stencilBuffer = source.stencilBuffer; - - if ( source.depthTexture !== null ) this.depthTexture = source.depthTexture.clone(); - - this.texture.length = 0; - - for ( let i = 0, il = source.texture.length; i < il; i ++ ) { - - this.texture[ i ] = source.texture[ i ].clone(); - this.texture[ i ].isRenderTargetTexture = true; - - } - - return this; - - } - - } - - class Quaternion { - - constructor( x = 0, y = 0, z = 0, w = 1 ) { - - this.isQuaternion = true; - - this._x = x; - this._y = y; - this._z = z; - this._w = w; - - } - - static slerpFlat( dst, dstOffset, src0, srcOffset0, src1, srcOffset1, t ) { - - // fuzz-free, array-based Quaternion SLERP operation - - let x0 = src0[ srcOffset0 + 0 ], - y0 = src0[ srcOffset0 + 1 ], - z0 = src0[ srcOffset0 + 2 ], - w0 = src0[ srcOffset0 + 3 ]; - - const x1 = src1[ srcOffset1 + 0 ], - y1 = src1[ srcOffset1 + 1 ], - z1 = src1[ srcOffset1 + 2 ], - w1 = src1[ srcOffset1 + 3 ]; - - if ( t === 0 ) { - - dst[ dstOffset + 0 ] = x0; - dst[ dstOffset + 1 ] = y0; - dst[ dstOffset + 2 ] = z0; - dst[ dstOffset + 3 ] = w0; - return; - - } - - if ( t === 1 ) { - - dst[ dstOffset + 0 ] = x1; - dst[ dstOffset + 1 ] = y1; - dst[ dstOffset + 2 ] = z1; - dst[ dstOffset + 3 ] = w1; - return; - - } - - if ( w0 !== w1 || x0 !== x1 || y0 !== y1 || z0 !== z1 ) { - - let s = 1 - t; - const cos = x0 * x1 + y0 * y1 + z0 * z1 + w0 * w1, - dir = ( cos >= 0 ? 1 : - 1 ), - sqrSin = 1 - cos * cos; - - // Skip the Slerp for tiny steps to avoid numeric problems: - if ( sqrSin > Number.EPSILON ) { - - const sin = Math.sqrt( sqrSin ), - len = Math.atan2( sin, cos * dir ); - - s = Math.sin( s * len ) / sin; - t = Math.sin( t * len ) / sin; - - } - - const tDir = t * dir; - - x0 = x0 * s + x1 * tDir; - y0 = y0 * s + y1 * tDir; - z0 = z0 * s + z1 * tDir; - w0 = w0 * s + w1 * tDir; - - // Normalize in case we just did a lerp: - if ( s === 1 - t ) { - - const f = 1 / Math.sqrt( x0 * x0 + y0 * y0 + z0 * z0 + w0 * w0 ); - - x0 *= f; - y0 *= f; - z0 *= f; - w0 *= f; - - } - - } - - dst[ dstOffset ] = x0; - dst[ dstOffset + 1 ] = y0; - dst[ dstOffset + 2 ] = z0; - dst[ dstOffset + 3 ] = w0; - - } - - static multiplyQuaternionsFlat( dst, dstOffset, src0, srcOffset0, src1, srcOffset1 ) { - - const x0 = src0[ srcOffset0 ]; - const y0 = src0[ srcOffset0 + 1 ]; - const z0 = src0[ srcOffset0 + 2 ]; - const w0 = src0[ srcOffset0 + 3 ]; - - const x1 = src1[ srcOffset1 ]; - const y1 = src1[ srcOffset1 + 1 ]; - const z1 = src1[ srcOffset1 + 2 ]; - const w1 = src1[ srcOffset1 + 3 ]; - - dst[ dstOffset ] = x0 * w1 + w0 * x1 + y0 * z1 - z0 * y1; - dst[ dstOffset + 1 ] = y0 * w1 + w0 * y1 + z0 * x1 - x0 * z1; - dst[ dstOffset + 2 ] = z0 * w1 + w0 * z1 + x0 * y1 - y0 * x1; - dst[ dstOffset + 3 ] = w0 * w1 - x0 * x1 - y0 * y1 - z0 * z1; - - return dst; - - } - - get x() { - - return this._x; - - } - - set x( value ) { - - this._x = value; - this._onChangeCallback(); - - } - - get y() { - - return this._y; - - } - - set y( value ) { - - this._y = value; - this._onChangeCallback(); - - } - - get z() { - - return this._z; - - } - - set z( value ) { - - this._z = value; - this._onChangeCallback(); - - } - - get w() { - - return this._w; - - } - - set w( value ) { - - this._w = value; - this._onChangeCallback(); - - } - - set( x, y, z, w ) { - - this._x = x; - this._y = y; - this._z = z; - this._w = w; - - this._onChangeCallback(); - - return this; - - } - - clone() { - - return new this.constructor( this._x, this._y, this._z, this._w ); - - } - - copy( quaternion ) { - - this._x = quaternion.x; - this._y = quaternion.y; - this._z = quaternion.z; - this._w = quaternion.w; - - this._onChangeCallback(); - - return this; - - } - - setFromEuler( euler, update ) { - - const x = euler._x, y = euler._y, z = euler._z, order = euler._order; - - // http://www.mathworks.com/matlabcentral/fileexchange/ - // 20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/ - // content/SpinCalc.m - - const cos = Math.cos; - const sin = Math.sin; - - const c1 = cos( x / 2 ); - const c2 = cos( y / 2 ); - const c3 = cos( z / 2 ); - - const s1 = sin( x / 2 ); - const s2 = sin( y / 2 ); - const s3 = sin( z / 2 ); - - switch ( order ) { - - case 'XYZ': - this._x = s1 * c2 * c3 + c1 * s2 * s3; - this._y = c1 * s2 * c3 - s1 * c2 * s3; - this._z = c1 * c2 * s3 + s1 * s2 * c3; - this._w = c1 * c2 * c3 - s1 * s2 * s3; - break; - - case 'YXZ': - this._x = s1 * c2 * c3 + c1 * s2 * s3; - this._y = c1 * s2 * c3 - s1 * c2 * s3; - this._z = c1 * c2 * s3 - s1 * s2 * c3; - this._w = c1 * c2 * c3 + s1 * s2 * s3; - break; - - case 'ZXY': - this._x = s1 * c2 * c3 - c1 * s2 * s3; - this._y = c1 * s2 * c3 + s1 * c2 * s3; - this._z = c1 * c2 * s3 + s1 * s2 * c3; - this._w = c1 * c2 * c3 - s1 * s2 * s3; - break; - - case 'ZYX': - this._x = s1 * c2 * c3 - c1 * s2 * s3; - this._y = c1 * s2 * c3 + s1 * c2 * s3; - this._z = c1 * c2 * s3 - s1 * s2 * c3; - this._w = c1 * c2 * c3 + s1 * s2 * s3; - break; - - case 'YZX': - this._x = s1 * c2 * c3 + c1 * s2 * s3; - this._y = c1 * s2 * c3 + s1 * c2 * s3; - this._z = c1 * c2 * s3 - s1 * s2 * c3; - this._w = c1 * c2 * c3 - s1 * s2 * s3; - break; - - case 'XZY': - this._x = s1 * c2 * c3 - c1 * s2 * s3; - this._y = c1 * s2 * c3 - s1 * c2 * s3; - this._z = c1 * c2 * s3 + s1 * s2 * c3; - this._w = c1 * c2 * c3 + s1 * s2 * s3; - break; - - default: - console.warn( 'THREE.Quaternion: .setFromEuler() encountered an unknown order: ' + order ); - - } - - if ( update !== false ) this._onChangeCallback(); - - return this; - - } - - setFromAxisAngle( axis, angle ) { - - // http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm - - // assumes axis is normalized - - const halfAngle = angle / 2, s = Math.sin( halfAngle ); - - this._x = axis.x * s; - this._y = axis.y * s; - this._z = axis.z * s; - this._w = Math.cos( halfAngle ); - - this._onChangeCallback(); - - return this; - - } - - setFromRotationMatrix( m ) { - - // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm - - // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) - - const te = m.elements, - - m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ], - m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ], - m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ], - - trace = m11 + m22 + m33; - - if ( trace > 0 ) { - - const s = 0.5 / Math.sqrt( trace + 1.0 ); - - this._w = 0.25 / s; - this._x = ( m32 - m23 ) * s; - this._y = ( m13 - m31 ) * s; - this._z = ( m21 - m12 ) * s; - - } else if ( m11 > m22 && m11 > m33 ) { - - const s = 2.0 * Math.sqrt( 1.0 + m11 - m22 - m33 ); - - this._w = ( m32 - m23 ) / s; - this._x = 0.25 * s; - this._y = ( m12 + m21 ) / s; - this._z = ( m13 + m31 ) / s; - - } else if ( m22 > m33 ) { - - const s = 2.0 * Math.sqrt( 1.0 + m22 - m11 - m33 ); - - this._w = ( m13 - m31 ) / s; - this._x = ( m12 + m21 ) / s; - this._y = 0.25 * s; - this._z = ( m23 + m32 ) / s; - - } else { - - const s = 2.0 * Math.sqrt( 1.0 + m33 - m11 - m22 ); - - this._w = ( m21 - m12 ) / s; - this._x = ( m13 + m31 ) / s; - this._y = ( m23 + m32 ) / s; - this._z = 0.25 * s; - - } - - this._onChangeCallback(); - - return this; - - } - - setFromUnitVectors( vFrom, vTo ) { - - // assumes direction vectors vFrom and vTo are normalized - - let r = vFrom.dot( vTo ) + 1; - - if ( r < Number.EPSILON ) { - - // vFrom and vTo point in opposite directions - - r = 0; - - if ( Math.abs( vFrom.x ) > Math.abs( vFrom.z ) ) { - - this._x = - vFrom.y; - this._y = vFrom.x; - this._z = 0; - this._w = r; - - } else { - - this._x = 0; - this._y = - vFrom.z; - this._z = vFrom.y; - this._w = r; - - } - - } else { - - // crossVectors( vFrom, vTo ); // inlined to avoid cyclic dependency on Vector3 - - this._x = vFrom.y * vTo.z - vFrom.z * vTo.y; - this._y = vFrom.z * vTo.x - vFrom.x * vTo.z; - this._z = vFrom.x * vTo.y - vFrom.y * vTo.x; - this._w = r; - - } - - return this.normalize(); - - } - - angleTo( q ) { - - return 2 * Math.acos( Math.abs( clamp( this.dot( q ), - 1, 1 ) ) ); - - } - - rotateTowards( q, step ) { - - const angle = this.angleTo( q ); - - if ( angle === 0 ) return this; - - const t = Math.min( 1, step / angle ); - - this.slerp( q, t ); - - return this; - - } - - identity() { - - return this.set( 0, 0, 0, 1 ); - - } - - invert() { - - // quaternion is assumed to have unit length - - return this.conjugate(); - - } - - conjugate() { - - this._x *= - 1; - this._y *= - 1; - this._z *= - 1; - - this._onChangeCallback(); - - return this; - - } - - dot( v ) { - - return this._x * v._x + this._y * v._y + this._z * v._z + this._w * v._w; - - } - - lengthSq() { - - return this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w; - - } - - length() { - - return Math.sqrt( this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w ); - - } - - normalize() { - - let l = this.length(); - - if ( l === 0 ) { - - this._x = 0; - this._y = 0; - this._z = 0; - this._w = 1; - - } else { - - l = 1 / l; - - this._x = this._x * l; - this._y = this._y * l; - this._z = this._z * l; - this._w = this._w * l; - - } - - this._onChangeCallback(); - - return this; - - } - - multiply( q ) { - - return this.multiplyQuaternions( this, q ); - - } - - premultiply( q ) { - - return this.multiplyQuaternions( q, this ); - - } - - multiplyQuaternions( a, b ) { - - // from http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/code/index.htm - - const qax = a._x, qay = a._y, qaz = a._z, qaw = a._w; - const qbx = b._x, qby = b._y, qbz = b._z, qbw = b._w; - - this._x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby; - this._y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz; - this._z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx; - this._w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz; - - this._onChangeCallback(); - - return this; - - } - - slerp( qb, t ) { - - if ( t === 0 ) return this; - if ( t === 1 ) return this.copy( qb ); - - const x = this._x, y = this._y, z = this._z, w = this._w; - - // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/ - - let cosHalfTheta = w * qb._w + x * qb._x + y * qb._y + z * qb._z; - - if ( cosHalfTheta < 0 ) { - - this._w = - qb._w; - this._x = - qb._x; - this._y = - qb._y; - this._z = - qb._z; - - cosHalfTheta = - cosHalfTheta; - - } else { - - this.copy( qb ); - - } - - if ( cosHalfTheta >= 1.0 ) { - - this._w = w; - this._x = x; - this._y = y; - this._z = z; - - return this; - - } - - const sqrSinHalfTheta = 1.0 - cosHalfTheta * cosHalfTheta; - - if ( sqrSinHalfTheta <= Number.EPSILON ) { - - const s = 1 - t; - this._w = s * w + t * this._w; - this._x = s * x + t * this._x; - this._y = s * y + t * this._y; - this._z = s * z + t * this._z; - - this.normalize(); - this._onChangeCallback(); - - return this; - - } - - const sinHalfTheta = Math.sqrt( sqrSinHalfTheta ); - const halfTheta = Math.atan2( sinHalfTheta, cosHalfTheta ); - const ratioA = Math.sin( ( 1 - t ) * halfTheta ) / sinHalfTheta, - ratioB = Math.sin( t * halfTheta ) / sinHalfTheta; - - this._w = ( w * ratioA + this._w * ratioB ); - this._x = ( x * ratioA + this._x * ratioB ); - this._y = ( y * ratioA + this._y * ratioB ); - this._z = ( z * ratioA + this._z * ratioB ); - - this._onChangeCallback(); - - return this; - - } - - slerpQuaternions( qa, qb, t ) { - - return this.copy( qa ).slerp( qb, t ); - - } - - random() { - - // Derived from http://planning.cs.uiuc.edu/node198.html - // Note, this source uses w, x, y, z ordering, - // so we swap the order below. - - const u1 = Math.random(); - const sqrt1u1 = Math.sqrt( 1 - u1 ); - const sqrtu1 = Math.sqrt( u1 ); - - const u2 = 2 * Math.PI * Math.random(); - - const u3 = 2 * Math.PI * Math.random(); - - return this.set( - sqrt1u1 * Math.cos( u2 ), - sqrtu1 * Math.sin( u3 ), - sqrtu1 * Math.cos( u3 ), - sqrt1u1 * Math.sin( u2 ), - ); - - } - - equals( quaternion ) { - - return ( quaternion._x === this._x ) && ( quaternion._y === this._y ) && ( quaternion._z === this._z ) && ( quaternion._w === this._w ); - - } - - fromArray( array, offset = 0 ) { - - this._x = array[ offset ]; - this._y = array[ offset + 1 ]; - this._z = array[ offset + 2 ]; - this._w = array[ offset + 3 ]; - - this._onChangeCallback(); - - return this; - - } - - toArray( array = [], offset = 0 ) { - - array[ offset ] = this._x; - array[ offset + 1 ] = this._y; - array[ offset + 2 ] = this._z; - array[ offset + 3 ] = this._w; - - return array; - - } - - fromBufferAttribute( attribute, index ) { - - this._x = attribute.getX( index ); - this._y = attribute.getY( index ); - this._z = attribute.getZ( index ); - this._w = attribute.getW( index ); - - return this; - - } - - toJSON() { - - return this.toArray(); - - } - - _onChange( callback ) { - - this._onChangeCallback = callback; - - return this; - - } - - _onChangeCallback() {} - - *[ Symbol.iterator ]() { - - yield this._x; - yield this._y; - yield this._z; - yield this._w; - - } - - } - - class Vector3 { - - constructor( x = 0, y = 0, z = 0 ) { - - Vector3.prototype.isVector3 = true; - - this.x = x; - this.y = y; - this.z = z; - - } - - set( x, y, z ) { - - if ( z === undefined ) z = this.z; // sprite.scale.set(x,y) - - this.x = x; - this.y = y; - this.z = z; - - return this; - - } - - setScalar( scalar ) { - - this.x = scalar; - this.y = scalar; - this.z = scalar; - - return this; - - } - - setX( x ) { - - this.x = x; - - return this; - - } - - setY( y ) { - - this.y = y; - - return this; - - } - - setZ( z ) { - - this.z = z; - - return this; - - } - - setComponent( index, value ) { - - switch ( index ) { - - case 0: this.x = value; break; - case 1: this.y = value; break; - case 2: this.z = value; break; - default: throw new Error( 'index is out of range: ' + index ); - - } - - return this; - - } - - getComponent( index ) { - - switch ( index ) { - - case 0: return this.x; - case 1: return this.y; - case 2: return this.z; - default: throw new Error( 'index is out of range: ' + index ); - - } - - } - - clone() { - - return new this.constructor( this.x, this.y, this.z ); - - } - - copy( v ) { - - this.x = v.x; - this.y = v.y; - this.z = v.z; - - return this; - - } - - add( v ) { - - this.x += v.x; - this.y += v.y; - this.z += v.z; - - return this; - - } - - addScalar( s ) { - - this.x += s; - this.y += s; - this.z += s; - - return this; - - } - - addVectors( a, b ) { - - this.x = a.x + b.x; - this.y = a.y + b.y; - this.z = a.z + b.z; - - return this; - - } - - addScaledVector( v, s ) { - - this.x += v.x * s; - this.y += v.y * s; - this.z += v.z * s; - - return this; - - } - - sub( v ) { - - this.x -= v.x; - this.y -= v.y; - this.z -= v.z; - - return this; - - } - - subScalar( s ) { - - this.x -= s; - this.y -= s; - this.z -= s; - - return this; - - } - - subVectors( a, b ) { - - this.x = a.x - b.x; - this.y = a.y - b.y; - this.z = a.z - b.z; - - return this; - - } - - multiply( v ) { - - this.x *= v.x; - this.y *= v.y; - this.z *= v.z; - - return this; - - } - - multiplyScalar( scalar ) { - - this.x *= scalar; - this.y *= scalar; - this.z *= scalar; - - return this; - - } - - multiplyVectors( a, b ) { - - this.x = a.x * b.x; - this.y = a.y * b.y; - this.z = a.z * b.z; - - return this; - - } - - applyEuler( euler ) { - - return this.applyQuaternion( _quaternion$4.setFromEuler( euler ) ); - - } - - applyAxisAngle( axis, angle ) { - - return this.applyQuaternion( _quaternion$4.setFromAxisAngle( axis, angle ) ); - - } - - applyMatrix3( m ) { - - const x = this.x, y = this.y, z = this.z; - const e = m.elements; - - this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ] * z; - this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ] * z; - this.z = e[ 2 ] * x + e[ 5 ] * y + e[ 8 ] * z; - - return this; - - } - - applyNormalMatrix( m ) { - - return this.applyMatrix3( m ).normalize(); - - } - - applyMatrix4( m ) { - - const x = this.x, y = this.y, z = this.z; - const e = m.elements; - - const w = 1 / ( e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] ); - - this.x = ( e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] ) * w; - this.y = ( e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] ) * w; - this.z = ( e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] ) * w; - - return this; - - } - - applyQuaternion( q ) { - - const x = this.x, y = this.y, z = this.z; - const qx = q.x, qy = q.y, qz = q.z, qw = q.w; - - // calculate quat * vector - - const ix = qw * x + qy * z - qz * y; - const iy = qw * y + qz * x - qx * z; - const iz = qw * z + qx * y - qy * x; - const iw = - qx * x - qy * y - qz * z; - - // calculate result * inverse quat - - this.x = ix * qw + iw * - qx + iy * - qz - iz * - qy; - this.y = iy * qw + iw * - qy + iz * - qx - ix * - qz; - this.z = iz * qw + iw * - qz + ix * - qy - iy * - qx; - - return this; - - } - - project( camera ) { - - return this.applyMatrix4( camera.matrixWorldInverse ).applyMatrix4( camera.projectionMatrix ); - - } - - unproject( camera ) { - - return this.applyMatrix4( camera.projectionMatrixInverse ).applyMatrix4( camera.matrixWorld ); - - } - - transformDirection( m ) { - - // input: THREE.Matrix4 affine matrix - // vector interpreted as a direction - - const x = this.x, y = this.y, z = this.z; - const e = m.elements; - - this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z; - this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z; - this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z; - - return this.normalize(); - - } - - divide( v ) { - - this.x /= v.x; - this.y /= v.y; - this.z /= v.z; - - return this; - - } - - divideScalar( scalar ) { - - return this.multiplyScalar( 1 / scalar ); - - } - - min( v ) { - - this.x = Math.min( this.x, v.x ); - this.y = Math.min( this.y, v.y ); - this.z = Math.min( this.z, v.z ); - - return this; - - } - - max( v ) { - - this.x = Math.max( this.x, v.x ); - this.y = Math.max( this.y, v.y ); - this.z = Math.max( this.z, v.z ); - - return this; - - } - - clamp( min, max ) { - - // assumes min < max, componentwise - - this.x = Math.max( min.x, Math.min( max.x, this.x ) ); - this.y = Math.max( min.y, Math.min( max.y, this.y ) ); - this.z = Math.max( min.z, Math.min( max.z, this.z ) ); - - return this; - - } - - clampScalar( minVal, maxVal ) { - - this.x = Math.max( minVal, Math.min( maxVal, this.x ) ); - this.y = Math.max( minVal, Math.min( maxVal, this.y ) ); - this.z = Math.max( minVal, Math.min( maxVal, this.z ) ); - - return this; - - } - - clampLength( min, max ) { - - const length = this.length(); - - return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) ); - - } - - floor() { - - this.x = Math.floor( this.x ); - this.y = Math.floor( this.y ); - this.z = Math.floor( this.z ); - - return this; - - } - - ceil() { - - this.x = Math.ceil( this.x ); - this.y = Math.ceil( this.y ); - this.z = Math.ceil( this.z ); - - return this; - - } - - round() { - - this.x = Math.round( this.x ); - this.y = Math.round( this.y ); - this.z = Math.round( this.z ); - - return this; - - } - - roundToZero() { - - this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x ); - this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y ); - this.z = ( this.z < 0 ) ? Math.ceil( this.z ) : Math.floor( this.z ); - - return this; - - } - - negate() { - - this.x = - this.x; - this.y = - this.y; - this.z = - this.z; - - return this; - - } - - dot( v ) { - - return this.x * v.x + this.y * v.y + this.z * v.z; - - } - - // TODO lengthSquared? - - lengthSq() { - - return this.x * this.x + this.y * this.y + this.z * this.z; - - } - - length() { - - return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z ); - - } - - manhattanLength() { - - return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ); - - } - - normalize() { - - return this.divideScalar( this.length() || 1 ); - - } - - setLength( length ) { - - return this.normalize().multiplyScalar( length ); - - } - - lerp( v, alpha ) { - - this.x += ( v.x - this.x ) * alpha; - this.y += ( v.y - this.y ) * alpha; - this.z += ( v.z - this.z ) * alpha; - - return this; - - } - - lerpVectors( v1, v2, alpha ) { - - this.x = v1.x + ( v2.x - v1.x ) * alpha; - this.y = v1.y + ( v2.y - v1.y ) * alpha; - this.z = v1.z + ( v2.z - v1.z ) * alpha; - - return this; - - } - - cross( v ) { - - return this.crossVectors( this, v ); - - } - - crossVectors( a, b ) { - - const ax = a.x, ay = a.y, az = a.z; - const bx = b.x, by = b.y, bz = b.z; - - this.x = ay * bz - az * by; - this.y = az * bx - ax * bz; - this.z = ax * by - ay * bx; - - return this; - - } - - projectOnVector( v ) { - - const denominator = v.lengthSq(); - - if ( denominator === 0 ) return this.set( 0, 0, 0 ); - - const scalar = v.dot( this ) / denominator; - - return this.copy( v ).multiplyScalar( scalar ); - - } - - projectOnPlane( planeNormal ) { - - _vector$b.copy( this ).projectOnVector( planeNormal ); - - return this.sub( _vector$b ); - - } - - reflect( normal ) { - - // reflect incident vector off plane orthogonal to normal - // normal is assumed to have unit length - - return this.sub( _vector$b.copy( normal ).multiplyScalar( 2 * this.dot( normal ) ) ); - - } - - angleTo( v ) { - - const denominator = Math.sqrt( this.lengthSq() * v.lengthSq() ); - - if ( denominator === 0 ) return Math.PI / 2; - - const theta = this.dot( v ) / denominator; - - // clamp, to handle numerical problems - - return Math.acos( clamp( theta, - 1, 1 ) ); - - } - - distanceTo( v ) { - - return Math.sqrt( this.distanceToSquared( v ) ); - - } - - distanceToSquared( v ) { - - const dx = this.x - v.x, dy = this.y - v.y, dz = this.z - v.z; - - return dx * dx + dy * dy + dz * dz; - - } - - manhattanDistanceTo( v ) { - - return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y ) + Math.abs( this.z - v.z ); - - } - - setFromSpherical( s ) { - - return this.setFromSphericalCoords( s.radius, s.phi, s.theta ); - - } - - setFromSphericalCoords( radius, phi, theta ) { - - const sinPhiRadius = Math.sin( phi ) * radius; - - this.x = sinPhiRadius * Math.sin( theta ); - this.y = Math.cos( phi ) * radius; - this.z = sinPhiRadius * Math.cos( theta ); - - return this; - - } - - setFromCylindrical( c ) { - - return this.setFromCylindricalCoords( c.radius, c.theta, c.y ); - - } - - setFromCylindricalCoords( radius, theta, y ) { - - this.x = radius * Math.sin( theta ); - this.y = y; - this.z = radius * Math.cos( theta ); - - return this; - - } - - setFromMatrixPosition( m ) { - - const e = m.elements; - - this.x = e[ 12 ]; - this.y = e[ 13 ]; - this.z = e[ 14 ]; - - return this; - - } - - setFromMatrixScale( m ) { - - const sx = this.setFromMatrixColumn( m, 0 ).length(); - const sy = this.setFromMatrixColumn( m, 1 ).length(); - const sz = this.setFromMatrixColumn( m, 2 ).length(); - - this.x = sx; - this.y = sy; - this.z = sz; - - return this; - - } - - setFromMatrixColumn( m, index ) { - - return this.fromArray( m.elements, index * 4 ); - - } - - setFromMatrix3Column( m, index ) { - - return this.fromArray( m.elements, index * 3 ); - - } - - setFromEuler( e ) { - - this.x = e._x; - this.y = e._y; - this.z = e._z; - - return this; - - } - - setFromColor( c ) { - - this.x = c.r; - this.y = c.g; - this.z = c.b; - - return this; - - } - - equals( v ) { - - return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) ); - - } - - fromArray( array, offset = 0 ) { - - this.x = array[ offset ]; - this.y = array[ offset + 1 ]; - this.z = array[ offset + 2 ]; - - return this; - - } - - toArray( array = [], offset = 0 ) { - - array[ offset ] = this.x; - array[ offset + 1 ] = this.y; - array[ offset + 2 ] = this.z; - - return array; - - } - - fromBufferAttribute( attribute, index ) { - - this.x = attribute.getX( index ); - this.y = attribute.getY( index ); - this.z = attribute.getZ( index ); - - return this; - - } - - random() { - - this.x = Math.random(); - this.y = Math.random(); - this.z = Math.random(); - - return this; - - } - - randomDirection() { - - // Derived from https://mathworld.wolfram.com/SpherePointPicking.html - - const u = ( Math.random() - 0.5 ) * 2; - const t = Math.random() * Math.PI * 2; - const f = Math.sqrt( 1 - u ** 2 ); - - this.x = f * Math.cos( t ); - this.y = f * Math.sin( t ); - this.z = u; - - return this; - - } - - *[ Symbol.iterator ]() { - - yield this.x; - yield this.y; - yield this.z; - - } - - } - - const _vector$b = /*@__PURE__*/ new Vector3(); - const _quaternion$4 = /*@__PURE__*/ new Quaternion(); - - class Box3 { - - constructor( min = new Vector3( + Infinity, + Infinity, + Infinity ), max = new Vector3( - Infinity, - Infinity, - Infinity ) ) { - - this.isBox3 = true; - - this.min = min; - this.max = max; - - } - - set( min, max ) { - - this.min.copy( min ); - this.max.copy( max ); - - return this; - - } - - setFromArray( array ) { - - this.makeEmpty(); - - for ( let i = 0, il = array.length; i < il; i += 3 ) { - - this.expandByPoint( _vector$a.fromArray( array, i ) ); - - } - - return this; - - } - - setFromBufferAttribute( attribute ) { - - this.makeEmpty(); - - for ( let i = 0, il = attribute.count; i < il; i ++ ) { - - this.expandByPoint( _vector$a.fromBufferAttribute( attribute, i ) ); - - } - - return this; - - } - - setFromPoints( points ) { - - this.makeEmpty(); - - for ( let i = 0, il = points.length; i < il; i ++ ) { - - this.expandByPoint( points[ i ] ); - - } - - return this; - - } - - setFromCenterAndSize( center, size ) { - - const halfSize = _vector$a.copy( size ).multiplyScalar( 0.5 ); - - this.min.copy( center ).sub( halfSize ); - this.max.copy( center ).add( halfSize ); - - return this; - - } - - setFromObject( object, precise = false ) { - - this.makeEmpty(); - - return this.expandByObject( object, precise ); - - } - - clone() { - - return new this.constructor().copy( this ); - - } - - copy( box ) { - - this.min.copy( box.min ); - this.max.copy( box.max ); - - return this; - - } - - makeEmpty() { - - this.min.x = this.min.y = this.min.z = + Infinity; - this.max.x = this.max.y = this.max.z = - Infinity; - - return this; - - } - - isEmpty() { - - // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes - - return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y ) || ( this.max.z < this.min.z ); - - } - - getCenter( target ) { - - return this.isEmpty() ? target.set( 0, 0, 0 ) : target.addVectors( this.min, this.max ).multiplyScalar( 0.5 ); - - } - - getSize( target ) { - - return this.isEmpty() ? target.set( 0, 0, 0 ) : target.subVectors( this.max, this.min ); - - } - - expandByPoint( point ) { - - this.min.min( point ); - this.max.max( point ); - - return this; - - } - - expandByVector( vector ) { - - this.min.sub( vector ); - this.max.add( vector ); - - return this; - - } - - expandByScalar( scalar ) { - - this.min.addScalar( - scalar ); - this.max.addScalar( scalar ); - - return this; - - } - - expandByObject( object, precise = false ) { - - // Computes the world-axis-aligned bounding box of an object (including its children), - // accounting for both the object's, and children's, world transforms - - object.updateWorldMatrix( false, false ); - - if ( object.boundingBox !== undefined ) { - - if ( object.boundingBox === null ) { - - object.computeBoundingBox(); - - } - - _box$3.copy( object.boundingBox ); - _box$3.applyMatrix4( object.matrixWorld ); - - this.union( _box$3 ); - - } else { - - const geometry = object.geometry; - - if ( geometry !== undefined ) { - - if ( precise && geometry.attributes !== undefined && geometry.attributes.position !== undefined ) { - - const position = geometry.attributes.position; - for ( let i = 0, l = position.count; i < l; i ++ ) { - - _vector$a.fromBufferAttribute( position, i ).applyMatrix4( object.matrixWorld ); - this.expandByPoint( _vector$a ); - - } - - } else { - - if ( geometry.boundingBox === null ) { - - geometry.computeBoundingBox(); - - } - - _box$3.copy( geometry.boundingBox ); - _box$3.applyMatrix4( object.matrixWorld ); - - this.union( _box$3 ); - - } - - } - - } - - const children = object.children; - - for ( let i = 0, l = children.length; i < l; i ++ ) { - - this.expandByObject( children[ i ], precise ); - - } - - return this; - - } - - containsPoint( point ) { - - return point.x < this.min.x || point.x > this.max.x || - point.y < this.min.y || point.y > this.max.y || - point.z < this.min.z || point.z > this.max.z ? false : true; - - } - - containsBox( box ) { - - return this.min.x <= box.min.x && box.max.x <= this.max.x && - this.min.y <= box.min.y && box.max.y <= this.max.y && - this.min.z <= box.min.z && box.max.z <= this.max.z; - - } - - getParameter( point, target ) { - - // This can potentially have a divide by zero if the box - // has a size dimension of 0. - - return target.set( - ( point.x - this.min.x ) / ( this.max.x - this.min.x ), - ( point.y - this.min.y ) / ( this.max.y - this.min.y ), - ( point.z - this.min.z ) / ( this.max.z - this.min.z ) - ); - - } - - intersectsBox( box ) { - - // using 6 splitting planes to rule out intersections. - return box.max.x < this.min.x || box.min.x > this.max.x || - box.max.y < this.min.y || box.min.y > this.max.y || - box.max.z < this.min.z || box.min.z > this.max.z ? false : true; - - } - - intersectsSphere( sphere ) { - - // Find the point on the AABB closest to the sphere center. - this.clampPoint( sphere.center, _vector$a ); - - // If that point is inside the sphere, the AABB and sphere intersect. - return _vector$a.distanceToSquared( sphere.center ) <= ( sphere.radius * sphere.radius ); - - } - - intersectsPlane( plane ) { - - // We compute the minimum and maximum dot product values. If those values - // are on the same side (back or front) of the plane, then there is no intersection. - - let min, max; - - if ( plane.normal.x > 0 ) { - - min = plane.normal.x * this.min.x; - max = plane.normal.x * this.max.x; - - } else { - - min = plane.normal.x * this.max.x; - max = plane.normal.x * this.min.x; - - } - - if ( plane.normal.y > 0 ) { - - min += plane.normal.y * this.min.y; - max += plane.normal.y * this.max.y; - - } else { - - min += plane.normal.y * this.max.y; - max += plane.normal.y * this.min.y; - - } - - if ( plane.normal.z > 0 ) { - - min += plane.normal.z * this.min.z; - max += plane.normal.z * this.max.z; - - } else { - - min += plane.normal.z * this.max.z; - max += plane.normal.z * this.min.z; - - } - - return ( min <= - plane.constant && max >= - plane.constant ); - - } - - intersectsTriangle( triangle ) { - - if ( this.isEmpty() ) { - - return false; - - } - - // compute box center and extents - this.getCenter( _center ); - _extents.subVectors( this.max, _center ); - - // translate triangle to aabb origin - _v0$2.subVectors( triangle.a, _center ); - _v1$7.subVectors( triangle.b, _center ); - _v2$4.subVectors( triangle.c, _center ); - - // compute edge vectors for triangle - _f0.subVectors( _v1$7, _v0$2 ); - _f1.subVectors( _v2$4, _v1$7 ); - _f2.subVectors( _v0$2, _v2$4 ); - - // test against axes that are given by cross product combinations of the edges of the triangle and the edges of the aabb - // make an axis testing of each of the 3 sides of the aabb against each of the 3 sides of the triangle = 9 axis of separation - // axis_ij = u_i x f_j (u0, u1, u2 = face normals of aabb = x,y,z axes vectors since aabb is axis aligned) - let axes = [ - 0, - _f0.z, _f0.y, 0, - _f1.z, _f1.y, 0, - _f2.z, _f2.y, - _f0.z, 0, - _f0.x, _f1.z, 0, - _f1.x, _f2.z, 0, - _f2.x, - - _f0.y, _f0.x, 0, - _f1.y, _f1.x, 0, - _f2.y, _f2.x, 0 - ]; - if ( ! satForAxes( axes, _v0$2, _v1$7, _v2$4, _extents ) ) { - - return false; - - } - - // test 3 face normals from the aabb - axes = [ 1, 0, 0, 0, 1, 0, 0, 0, 1 ]; - if ( ! satForAxes( axes, _v0$2, _v1$7, _v2$4, _extents ) ) { - - return false; - - } - - // finally testing the face normal of the triangle - // use already existing triangle edge vectors here - _triangleNormal.crossVectors( _f0, _f1 ); - axes = [ _triangleNormal.x, _triangleNormal.y, _triangleNormal.z ]; - - return satForAxes( axes, _v0$2, _v1$7, _v2$4, _extents ); - - } - - clampPoint( point, target ) { - - return target.copy( point ).clamp( this.min, this.max ); - - } - - distanceToPoint( point ) { - - return this.clampPoint( point, _vector$a ).distanceTo( point ); - - } - - getBoundingSphere( target ) { - - if ( this.isEmpty() ) { - - target.makeEmpty(); - - } else { - - this.getCenter( target.center ); - - target.radius = this.getSize( _vector$a ).length() * 0.5; - - } - - return target; - - } - - intersect( box ) { - - this.min.max( box.min ); - this.max.min( box.max ); - - // ensure that if there is no overlap, the result is fully empty, not slightly empty with non-inf/+inf values that will cause subsequence intersects to erroneously return valid values. - if ( this.isEmpty() ) this.makeEmpty(); - - return this; - - } - - union( box ) { - - this.min.min( box.min ); - this.max.max( box.max ); - - return this; - - } - - applyMatrix4( matrix ) { - - // transform of empty box is an empty box. - if ( this.isEmpty() ) return this; - - // NOTE: I am using a binary pattern to specify all 2^3 combinations below - _points[ 0 ].set( this.min.x, this.min.y, this.min.z ).applyMatrix4( matrix ); // 000 - _points[ 1 ].set( this.min.x, this.min.y, this.max.z ).applyMatrix4( matrix ); // 001 - _points[ 2 ].set( this.min.x, this.max.y, this.min.z ).applyMatrix4( matrix ); // 010 - _points[ 3 ].set( this.min.x, this.max.y, this.max.z ).applyMatrix4( matrix ); // 011 - _points[ 4 ].set( this.max.x, this.min.y, this.min.z ).applyMatrix4( matrix ); // 100 - _points[ 5 ].set( this.max.x, this.min.y, this.max.z ).applyMatrix4( matrix ); // 101 - _points[ 6 ].set( this.max.x, this.max.y, this.min.z ).applyMatrix4( matrix ); // 110 - _points[ 7 ].set( this.max.x, this.max.y, this.max.z ).applyMatrix4( matrix ); // 111 - - this.setFromPoints( _points ); - - return this; - - } - - translate( offset ) { - - this.min.add( offset ); - this.max.add( offset ); - - return this; - - } - - equals( box ) { - - return box.min.equals( this.min ) && box.max.equals( this.max ); - - } - - } - - const _points = [ - /*@__PURE__*/ new Vector3(), - /*@__PURE__*/ new Vector3(), - /*@__PURE__*/ new Vector3(), - /*@__PURE__*/ new Vector3(), - /*@__PURE__*/ new Vector3(), - /*@__PURE__*/ new Vector3(), - /*@__PURE__*/ new Vector3(), - /*@__PURE__*/ new Vector3() - ]; - - const _vector$a = /*@__PURE__*/ new Vector3(); - - const _box$3 = /*@__PURE__*/ new Box3(); - - // triangle centered vertices - - const _v0$2 = /*@__PURE__*/ new Vector3(); - const _v1$7 = /*@__PURE__*/ new Vector3(); - const _v2$4 = /*@__PURE__*/ new Vector3(); - - // triangle edge vectors - - const _f0 = /*@__PURE__*/ new Vector3(); - const _f1 = /*@__PURE__*/ new Vector3(); - const _f2 = /*@__PURE__*/ new Vector3(); - - const _center = /*@__PURE__*/ new Vector3(); - const _extents = /*@__PURE__*/ new Vector3(); - const _triangleNormal = /*@__PURE__*/ new Vector3(); - const _testAxis = /*@__PURE__*/ new Vector3(); - - function satForAxes( axes, v0, v1, v2, extents ) { - - for ( let i = 0, j = axes.length - 3; i <= j; i += 3 ) { - - _testAxis.fromArray( axes, i ); - // project the aabb onto the separating axis - const r = extents.x * Math.abs( _testAxis.x ) + extents.y * Math.abs( _testAxis.y ) + extents.z * Math.abs( _testAxis.z ); - // project all 3 vertices of the triangle onto the separating axis - const p0 = v0.dot( _testAxis ); - const p1 = v1.dot( _testAxis ); - const p2 = v2.dot( _testAxis ); - // actual test, basically see if either of the most extreme of the triangle points intersects r - if ( Math.max( - Math.max( p0, p1, p2 ), Math.min( p0, p1, p2 ) ) > r ) { - - // points of the projected triangle are outside the projected half-length of the aabb - // the axis is separating and we can exit - return false; - - } - - } - - return true; - - } - - const _box$2 = /*@__PURE__*/ new Box3(); - const _v1$6 = /*@__PURE__*/ new Vector3(); - const _v2$3 = /*@__PURE__*/ new Vector3(); - - class Sphere { - - constructor( center = new Vector3(), radius = - 1 ) { - - this.center = center; - this.radius = radius; - - } - - set( center, radius ) { - - this.center.copy( center ); - this.radius = radius; - - return this; - - } - - setFromPoints( points, optionalCenter ) { - - const center = this.center; - - if ( optionalCenter !== undefined ) { - - center.copy( optionalCenter ); - - } else { - - _box$2.setFromPoints( points ).getCenter( center ); - - } - - let maxRadiusSq = 0; - - for ( let i = 0, il = points.length; i < il; i ++ ) { - - maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( points[ i ] ) ); - - } - - this.radius = Math.sqrt( maxRadiusSq ); - - return this; - - } - - copy( sphere ) { - - this.center.copy( sphere.center ); - this.radius = sphere.radius; - - return this; - - } - - isEmpty() { - - return ( this.radius < 0 ); - - } - - makeEmpty() { - - this.center.set( 0, 0, 0 ); - this.radius = - 1; - - return this; - - } - - containsPoint( point ) { - - return ( point.distanceToSquared( this.center ) <= ( this.radius * this.radius ) ); - - } - - distanceToPoint( point ) { - - return ( point.distanceTo( this.center ) - this.radius ); - - } - - intersectsSphere( sphere ) { - - const radiusSum = this.radius + sphere.radius; - - return sphere.center.distanceToSquared( this.center ) <= ( radiusSum * radiusSum ); - - } - - intersectsBox( box ) { - - return box.intersectsSphere( this ); - - } - - intersectsPlane( plane ) { - - return Math.abs( plane.distanceToPoint( this.center ) ) <= this.radius; - - } - - clampPoint( point, target ) { - - const deltaLengthSq = this.center.distanceToSquared( point ); - - target.copy( point ); - - if ( deltaLengthSq > ( this.radius * this.radius ) ) { - - target.sub( this.center ).normalize(); - target.multiplyScalar( this.radius ).add( this.center ); - - } - - return target; - - } - - getBoundingBox( target ) { - - if ( this.isEmpty() ) { - - // Empty sphere produces empty bounding box - target.makeEmpty(); - return target; - - } - - target.set( this.center, this.center ); - target.expandByScalar( this.radius ); - - return target; - - } - - applyMatrix4( matrix ) { - - this.center.applyMatrix4( matrix ); - this.radius = this.radius * matrix.getMaxScaleOnAxis(); - - return this; - - } - - translate( offset ) { - - this.center.add( offset ); - - return this; - - } - - expandByPoint( point ) { - - if ( this.isEmpty() ) { - - this.center.copy( point ); - - this.radius = 0; - - return this; - - } - - _v1$6.subVectors( point, this.center ); - - const lengthSq = _v1$6.lengthSq(); - - if ( lengthSq > ( this.radius * this.radius ) ) { - - // calculate the minimal sphere - - const length = Math.sqrt( lengthSq ); - - const delta = ( length - this.radius ) * 0.5; - - this.center.addScaledVector( _v1$6, delta / length ); - - this.radius += delta; - - } - - return this; - - } - - union( sphere ) { - - if ( sphere.isEmpty() ) { - - return this; - - } - - if ( this.isEmpty() ) { - - this.copy( sphere ); - - return this; - - } - - if ( this.center.equals( sphere.center ) === true ) { - - this.radius = Math.max( this.radius, sphere.radius ); - - } else { - - _v2$3.subVectors( sphere.center, this.center ).setLength( sphere.radius ); - - this.expandByPoint( _v1$6.copy( sphere.center ).add( _v2$3 ) ); - - this.expandByPoint( _v1$6.copy( sphere.center ).sub( _v2$3 ) ); - - } - - return this; - - } - - equals( sphere ) { - - return sphere.center.equals( this.center ) && ( sphere.radius === this.radius ); - - } - - clone() { - - return new this.constructor().copy( this ); - - } - - } - - const _vector$9 = /*@__PURE__*/ new Vector3(); - const _segCenter = /*@__PURE__*/ new Vector3(); - const _segDir = /*@__PURE__*/ new Vector3(); - const _diff = /*@__PURE__*/ new Vector3(); - - const _edge1 = /*@__PURE__*/ new Vector3(); - const _edge2 = /*@__PURE__*/ new Vector3(); - const _normal$1 = /*@__PURE__*/ new Vector3(); - - class Ray { - - constructor( origin = new Vector3(), direction = new Vector3( 0, 0, - 1 ) ) { - - this.origin = origin; - this.direction = direction; - - } - - set( origin, direction ) { - - this.origin.copy( origin ); - this.direction.copy( direction ); - - return this; - - } - - copy( ray ) { - - this.origin.copy( ray.origin ); - this.direction.copy( ray.direction ); - - return this; - - } - - at( t, target ) { - - return target.copy( this.origin ).addScaledVector( this.direction, t ); - - } - - lookAt( v ) { - - this.direction.copy( v ).sub( this.origin ).normalize(); - - return this; - - } - - recast( t ) { - - this.origin.copy( this.at( t, _vector$9 ) ); - - return this; - - } - - closestPointToPoint( point, target ) { - - target.subVectors( point, this.origin ); - - const directionDistance = target.dot( this.direction ); - - if ( directionDistance < 0 ) { - - return target.copy( this.origin ); - - } - - return target.copy( this.origin ).addScaledVector( this.direction, directionDistance ); - - } - - distanceToPoint( point ) { - - return Math.sqrt( this.distanceSqToPoint( point ) ); - - } - - distanceSqToPoint( point ) { - - const directionDistance = _vector$9.subVectors( point, this.origin ).dot( this.direction ); - - // point behind the ray - - if ( directionDistance < 0 ) { - - return this.origin.distanceToSquared( point ); - - } - - _vector$9.copy( this.origin ).addScaledVector( this.direction, directionDistance ); - - return _vector$9.distanceToSquared( point ); - - } - - distanceSqToSegment( v0, v1, optionalPointOnRay, optionalPointOnSegment ) { - - // from https://github.com/pmjoniak/GeometricTools/blob/master/GTEngine/Include/Mathematics/GteDistRaySegment.h - // It returns the min distance between the ray and the segment - // defined by v0 and v1 - // It can also set two optional targets : - // - The closest point on the ray - // - The closest point on the segment - - _segCenter.copy( v0 ).add( v1 ).multiplyScalar( 0.5 ); - _segDir.copy( v1 ).sub( v0 ).normalize(); - _diff.copy( this.origin ).sub( _segCenter ); - - const segExtent = v0.distanceTo( v1 ) * 0.5; - const a01 = - this.direction.dot( _segDir ); - const b0 = _diff.dot( this.direction ); - const b1 = - _diff.dot( _segDir ); - const c = _diff.lengthSq(); - const det = Math.abs( 1 - a01 * a01 ); - let s0, s1, sqrDist, extDet; - - if ( det > 0 ) { - - // The ray and segment are not parallel. - - s0 = a01 * b1 - b0; - s1 = a01 * b0 - b1; - extDet = segExtent * det; - - if ( s0 >= 0 ) { - - if ( s1 >= - extDet ) { - - if ( s1 <= extDet ) { - - // region 0 - // Minimum at interior points of ray and segment. - - const invDet = 1 / det; - s0 *= invDet; - s1 *= invDet; - sqrDist = s0 * ( s0 + a01 * s1 + 2 * b0 ) + s1 * ( a01 * s0 + s1 + 2 * b1 ) + c; - - } else { - - // region 1 - - s1 = segExtent; - s0 = Math.max( 0, - ( a01 * s1 + b0 ) ); - sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; - - } - - } else { - - // region 5 - - s1 = - segExtent; - s0 = Math.max( 0, - ( a01 * s1 + b0 ) ); - sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; - - } - - } else { - - if ( s1 <= - extDet ) { - - // region 4 - - s0 = Math.max( 0, - ( - a01 * segExtent + b0 ) ); - s1 = ( s0 > 0 ) ? - segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent ); - sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; - - } else if ( s1 <= extDet ) { - - // region 3 - - s0 = 0; - s1 = Math.min( Math.max( - segExtent, - b1 ), segExtent ); - sqrDist = s1 * ( s1 + 2 * b1 ) + c; - - } else { - - // region 2 - - s0 = Math.max( 0, - ( a01 * segExtent + b0 ) ); - s1 = ( s0 > 0 ) ? segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent ); - sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; - - } - - } - - } else { - - // Ray and segment are parallel. - - s1 = ( a01 > 0 ) ? - segExtent : segExtent; - s0 = Math.max( 0, - ( a01 * s1 + b0 ) ); - sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; - - } - - if ( optionalPointOnRay ) { - - optionalPointOnRay.copy( this.origin ).addScaledVector( this.direction, s0 ); - - } - - if ( optionalPointOnSegment ) { - - optionalPointOnSegment.copy( _segCenter ).addScaledVector( _segDir, s1 ); - - } - - return sqrDist; - - } - - intersectSphere( sphere, target ) { - - _vector$9.subVectors( sphere.center, this.origin ); - const tca = _vector$9.dot( this.direction ); - const d2 = _vector$9.dot( _vector$9 ) - tca * tca; - const radius2 = sphere.radius * sphere.radius; - - if ( d2 > radius2 ) return null; - - const thc = Math.sqrt( radius2 - d2 ); - - // t0 = first intersect point - entrance on front of sphere - const t0 = tca - thc; - - // t1 = second intersect point - exit point on back of sphere - const t1 = tca + thc; - - // test to see if t1 is behind the ray - if so, return null - if ( t1 < 0 ) return null; - - // test to see if t0 is behind the ray: - // if it is, the ray is inside the sphere, so return the second exit point scaled by t1, - // in order to always return an intersect point that is in front of the ray. - if ( t0 < 0 ) return this.at( t1, target ); - - // else t0 is in front of the ray, so return the first collision point scaled by t0 - return this.at( t0, target ); - - } - - intersectsSphere( sphere ) { - - return this.distanceSqToPoint( sphere.center ) <= ( sphere.radius * sphere.radius ); - - } - - distanceToPlane( plane ) { - - const denominator = plane.normal.dot( this.direction ); - - if ( denominator === 0 ) { - - // line is coplanar, return origin - if ( plane.distanceToPoint( this.origin ) === 0 ) { - - return 0; - - } - - // Null is preferable to undefined since undefined means.... it is undefined - - return null; - - } - - const t = - ( this.origin.dot( plane.normal ) + plane.constant ) / denominator; - - // Return if the ray never intersects the plane - - return t >= 0 ? t : null; - - } - - intersectPlane( plane, target ) { - - const t = this.distanceToPlane( plane ); - - if ( t === null ) { - - return null; - - } - - return this.at( t, target ); - - } - - intersectsPlane( plane ) { - - // check if the ray lies on the plane first - - const distToPoint = plane.distanceToPoint( this.origin ); - - if ( distToPoint === 0 ) { - - return true; - - } - - const denominator = plane.normal.dot( this.direction ); - - if ( denominator * distToPoint < 0 ) { - - return true; - - } - - // ray origin is behind the plane (and is pointing behind it) - - return false; - - } - - intersectBox( box, target ) { - - let tmin, tmax, tymin, tymax, tzmin, tzmax; - - const invdirx = 1 / this.direction.x, - invdiry = 1 / this.direction.y, - invdirz = 1 / this.direction.z; - - const origin = this.origin; - - if ( invdirx >= 0 ) { - - tmin = ( box.min.x - origin.x ) * invdirx; - tmax = ( box.max.x - origin.x ) * invdirx; - - } else { - - tmin = ( box.max.x - origin.x ) * invdirx; - tmax = ( box.min.x - origin.x ) * invdirx; - - } - - if ( invdiry >= 0 ) { - - tymin = ( box.min.y - origin.y ) * invdiry; - tymax = ( box.max.y - origin.y ) * invdiry; - - } else { - - tymin = ( box.max.y - origin.y ) * invdiry; - tymax = ( box.min.y - origin.y ) * invdiry; - - } - - if ( ( tmin > tymax ) || ( tymin > tmax ) ) return null; - - if ( tymin > tmin || isNaN( tmin ) ) tmin = tymin; - - if ( tymax < tmax || isNaN( tmax ) ) tmax = tymax; - - if ( invdirz >= 0 ) { - - tzmin = ( box.min.z - origin.z ) * invdirz; - tzmax = ( box.max.z - origin.z ) * invdirz; - - } else { - - tzmin = ( box.max.z - origin.z ) * invdirz; - tzmax = ( box.min.z - origin.z ) * invdirz; - - } - - if ( ( tmin > tzmax ) || ( tzmin > tmax ) ) return null; - - if ( tzmin > tmin || tmin !== tmin ) tmin = tzmin; - - if ( tzmax < tmax || tmax !== tmax ) tmax = tzmax; - - //return point closest to the ray (positive side) - - if ( tmax < 0 ) return null; - - return this.at( tmin >= 0 ? tmin : tmax, target ); - - } - - intersectsBox( box ) { - - return this.intersectBox( box, _vector$9 ) !== null; - - } - - intersectTriangle( a, b, c, backfaceCulling, target ) { - - // Compute the offset origin, edges, and normal. - - // from https://github.com/pmjoniak/GeometricTools/blob/master/GTEngine/Include/Mathematics/GteIntrRay3Triangle3.h - - _edge1.subVectors( b, a ); - _edge2.subVectors( c, a ); - _normal$1.crossVectors( _edge1, _edge2 ); - - // Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction, - // E1 = kEdge1, E2 = kEdge2, N = Cross(E1,E2)) by - // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2)) - // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q)) - // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N) - let DdN = this.direction.dot( _normal$1 ); - let sign; - - if ( DdN > 0 ) { - - if ( backfaceCulling ) return null; - sign = 1; - - } else if ( DdN < 0 ) { - - sign = - 1; - DdN = - DdN; - - } else { - - return null; - - } - - _diff.subVectors( this.origin, a ); - const DdQxE2 = sign * this.direction.dot( _edge2.crossVectors( _diff, _edge2 ) ); - - // b1 < 0, no intersection - if ( DdQxE2 < 0 ) { - - return null; - - } - - const DdE1xQ = sign * this.direction.dot( _edge1.cross( _diff ) ); - - // b2 < 0, no intersection - if ( DdE1xQ < 0 ) { - - return null; - - } - - // b1+b2 > 1, no intersection - if ( DdQxE2 + DdE1xQ > DdN ) { - - return null; - - } - - // Line intersects triangle, check if ray does. - const QdN = - sign * _diff.dot( _normal$1 ); - - // t < 0, no intersection - if ( QdN < 0 ) { - - return null; - - } - - // Ray intersects triangle. - return this.at( QdN / DdN, target ); - - } - - applyMatrix4( matrix4 ) { - - this.origin.applyMatrix4( matrix4 ); - this.direction.transformDirection( matrix4 ); - - return this; - - } - - equals( ray ) { - - return ray.origin.equals( this.origin ) && ray.direction.equals( this.direction ); - - } - - clone() { - - return new this.constructor().copy( this ); - - } - - } - - class Matrix4 { - - constructor( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ) { - - Matrix4.prototype.isMatrix4 = true; - - this.elements = [ - - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1 - - ]; - - if ( n11 !== undefined ) { - - this.set( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ); - - } - - } - - set( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ) { - - const te = this.elements; - - te[ 0 ] = n11; te[ 4 ] = n12; te[ 8 ] = n13; te[ 12 ] = n14; - te[ 1 ] = n21; te[ 5 ] = n22; te[ 9 ] = n23; te[ 13 ] = n24; - te[ 2 ] = n31; te[ 6 ] = n32; te[ 10 ] = n33; te[ 14 ] = n34; - te[ 3 ] = n41; te[ 7 ] = n42; te[ 11 ] = n43; te[ 15 ] = n44; - - return this; - - } - - identity() { - - this.set( - - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1 - - ); - - return this; - - } - - clone() { - - return new Matrix4().fromArray( this.elements ); - - } - - copy( m ) { - - const te = this.elements; - const me = m.elements; - - te[ 0 ] = me[ 0 ]; te[ 1 ] = me[ 1 ]; te[ 2 ] = me[ 2 ]; te[ 3 ] = me[ 3 ]; - te[ 4 ] = me[ 4 ]; te[ 5 ] = me[ 5 ]; te[ 6 ] = me[ 6 ]; te[ 7 ] = me[ 7 ]; - te[ 8 ] = me[ 8 ]; te[ 9 ] = me[ 9 ]; te[ 10 ] = me[ 10 ]; te[ 11 ] = me[ 11 ]; - te[ 12 ] = me[ 12 ]; te[ 13 ] = me[ 13 ]; te[ 14 ] = me[ 14 ]; te[ 15 ] = me[ 15 ]; - - return this; - - } - - copyPosition( m ) { - - const te = this.elements, me = m.elements; - - te[ 12 ] = me[ 12 ]; - te[ 13 ] = me[ 13 ]; - te[ 14 ] = me[ 14 ]; - - return this; - - } - - setFromMatrix3( m ) { - - const me = m.elements; - - this.set( - - me[ 0 ], me[ 3 ], me[ 6 ], 0, - me[ 1 ], me[ 4 ], me[ 7 ], 0, - me[ 2 ], me[ 5 ], me[ 8 ], 0, - 0, 0, 0, 1 - - ); - - return this; - - } - - extractBasis( xAxis, yAxis, zAxis ) { - - xAxis.setFromMatrixColumn( this, 0 ); - yAxis.setFromMatrixColumn( this, 1 ); - zAxis.setFromMatrixColumn( this, 2 ); - - return this; - - } - - makeBasis( xAxis, yAxis, zAxis ) { - - this.set( - xAxis.x, yAxis.x, zAxis.x, 0, - xAxis.y, yAxis.y, zAxis.y, 0, - xAxis.z, yAxis.z, zAxis.z, 0, - 0, 0, 0, 1 - ); - - return this; - - } - - extractRotation( m ) { - - // this method does not support reflection matrices - - const te = this.elements; - const me = m.elements; - - const scaleX = 1 / _v1$5.setFromMatrixColumn( m, 0 ).length(); - const scaleY = 1 / _v1$5.setFromMatrixColumn( m, 1 ).length(); - const scaleZ = 1 / _v1$5.setFromMatrixColumn( m, 2 ).length(); - - te[ 0 ] = me[ 0 ] * scaleX; - te[ 1 ] = me[ 1 ] * scaleX; - te[ 2 ] = me[ 2 ] * scaleX; - te[ 3 ] = 0; - - te[ 4 ] = me[ 4 ] * scaleY; - te[ 5 ] = me[ 5 ] * scaleY; - te[ 6 ] = me[ 6 ] * scaleY; - te[ 7 ] = 0; - - te[ 8 ] = me[ 8 ] * scaleZ; - te[ 9 ] = me[ 9 ] * scaleZ; - te[ 10 ] = me[ 10 ] * scaleZ; - te[ 11 ] = 0; - - te[ 12 ] = 0; - te[ 13 ] = 0; - te[ 14 ] = 0; - te[ 15 ] = 1; - - return this; - - } - - makeRotationFromEuler( euler ) { - - const te = this.elements; - - const x = euler.x, y = euler.y, z = euler.z; - const a = Math.cos( x ), b = Math.sin( x ); - const c = Math.cos( y ), d = Math.sin( y ); - const e = Math.cos( z ), f = Math.sin( z ); - - if ( euler.order === 'XYZ' ) { - - const ae = a * e, af = a * f, be = b * e, bf = b * f; - - te[ 0 ] = c * e; - te[ 4 ] = - c * f; - te[ 8 ] = d; - - te[ 1 ] = af + be * d; - te[ 5 ] = ae - bf * d; - te[ 9 ] = - b * c; - - te[ 2 ] = bf - ae * d; - te[ 6 ] = be + af * d; - te[ 10 ] = a * c; - - } else if ( euler.order === 'YXZ' ) { - - const ce = c * e, cf = c * f, de = d * e, df = d * f; - - te[ 0 ] = ce + df * b; - te[ 4 ] = de * b - cf; - te[ 8 ] = a * d; - - te[ 1 ] = a * f; - te[ 5 ] = a * e; - te[ 9 ] = - b; - - te[ 2 ] = cf * b - de; - te[ 6 ] = df + ce * b; - te[ 10 ] = a * c; - - } else if ( euler.order === 'ZXY' ) { - - const ce = c * e, cf = c * f, de = d * e, df = d * f; - - te[ 0 ] = ce - df * b; - te[ 4 ] = - a * f; - te[ 8 ] = de + cf * b; - - te[ 1 ] = cf + de * b; - te[ 5 ] = a * e; - te[ 9 ] = df - ce * b; - - te[ 2 ] = - a * d; - te[ 6 ] = b; - te[ 10 ] = a * c; - - } else if ( euler.order === 'ZYX' ) { - - const ae = a * e, af = a * f, be = b * e, bf = b * f; - - te[ 0 ] = c * e; - te[ 4 ] = be * d - af; - te[ 8 ] = ae * d + bf; - - te[ 1 ] = c * f; - te[ 5 ] = bf * d + ae; - te[ 9 ] = af * d - be; - - te[ 2 ] = - d; - te[ 6 ] = b * c; - te[ 10 ] = a * c; - - } else if ( euler.order === 'YZX' ) { - - const ac = a * c, ad = a * d, bc = b * c, bd = b * d; - - te[ 0 ] = c * e; - te[ 4 ] = bd - ac * f; - te[ 8 ] = bc * f + ad; - - te[ 1 ] = f; - te[ 5 ] = a * e; - te[ 9 ] = - b * e; - - te[ 2 ] = - d * e; - te[ 6 ] = ad * f + bc; - te[ 10 ] = ac - bd * f; - - } else if ( euler.order === 'XZY' ) { - - const ac = a * c, ad = a * d, bc = b * c, bd = b * d; - - te[ 0 ] = c * e; - te[ 4 ] = - f; - te[ 8 ] = d * e; - - te[ 1 ] = ac * f + bd; - te[ 5 ] = a * e; - te[ 9 ] = ad * f - bc; - - te[ 2 ] = bc * f - ad; - te[ 6 ] = b * e; - te[ 10 ] = bd * f + ac; - - } - - // bottom row - te[ 3 ] = 0; - te[ 7 ] = 0; - te[ 11 ] = 0; - - // last column - te[ 12 ] = 0; - te[ 13 ] = 0; - te[ 14 ] = 0; - te[ 15 ] = 1; - - return this; - - } - - makeRotationFromQuaternion( q ) { - - return this.compose( _zero, q, _one ); - - } - - lookAt( eye, target, up ) { - - const te = this.elements; - - _z.subVectors( eye, target ); - - if ( _z.lengthSq() === 0 ) { - - // eye and target are in the same position - - _z.z = 1; - - } - - _z.normalize(); - _x.crossVectors( up, _z ); - - if ( _x.lengthSq() === 0 ) { - - // up and z are parallel - - if ( Math.abs( up.z ) === 1 ) { - - _z.x += 0.0001; - - } else { - - _z.z += 0.0001; - - } - - _z.normalize(); - _x.crossVectors( up, _z ); - - } - - _x.normalize(); - _y.crossVectors( _z, _x ); - - te[ 0 ] = _x.x; te[ 4 ] = _y.x; te[ 8 ] = _z.x; - te[ 1 ] = _x.y; te[ 5 ] = _y.y; te[ 9 ] = _z.y; - te[ 2 ] = _x.z; te[ 6 ] = _y.z; te[ 10 ] = _z.z; - - return this; - - } - - multiply( m ) { - - return this.multiplyMatrices( this, m ); - - } - - premultiply( m ) { - - return this.multiplyMatrices( m, this ); - - } - - multiplyMatrices( a, b ) { - - const ae = a.elements; - const be = b.elements; - const te = this.elements; - - const a11 = ae[ 0 ], a12 = ae[ 4 ], a13 = ae[ 8 ], a14 = ae[ 12 ]; - const a21 = ae[ 1 ], a22 = ae[ 5 ], a23 = ae[ 9 ], a24 = ae[ 13 ]; - const a31 = ae[ 2 ], a32 = ae[ 6 ], a33 = ae[ 10 ], a34 = ae[ 14 ]; - const a41 = ae[ 3 ], a42 = ae[ 7 ], a43 = ae[ 11 ], a44 = ae[ 15 ]; - - const b11 = be[ 0 ], b12 = be[ 4 ], b13 = be[ 8 ], b14 = be[ 12 ]; - const b21 = be[ 1 ], b22 = be[ 5 ], b23 = be[ 9 ], b24 = be[ 13 ]; - const b31 = be[ 2 ], b32 = be[ 6 ], b33 = be[ 10 ], b34 = be[ 14 ]; - const b41 = be[ 3 ], b42 = be[ 7 ], b43 = be[ 11 ], b44 = be[ 15 ]; - - te[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41; - te[ 4 ] = a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42; - te[ 8 ] = a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43; - te[ 12 ] = a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44; - - te[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41; - te[ 5 ] = a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42; - te[ 9 ] = a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43; - te[ 13 ] = a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44; - - te[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41; - te[ 6 ] = a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42; - te[ 10 ] = a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43; - te[ 14 ] = a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44; - - te[ 3 ] = a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41; - te[ 7 ] = a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42; - te[ 11 ] = a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43; - te[ 15 ] = a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44; - - return this; - - } - - multiplyScalar( s ) { - - const te = this.elements; - - te[ 0 ] *= s; te[ 4 ] *= s; te[ 8 ] *= s; te[ 12 ] *= s; - te[ 1 ] *= s; te[ 5 ] *= s; te[ 9 ] *= s; te[ 13 ] *= s; - te[ 2 ] *= s; te[ 6 ] *= s; te[ 10 ] *= s; te[ 14 ] *= s; - te[ 3 ] *= s; te[ 7 ] *= s; te[ 11 ] *= s; te[ 15 ] *= s; - - return this; - - } - - determinant() { - - const te = this.elements; - - const n11 = te[ 0 ], n12 = te[ 4 ], n13 = te[ 8 ], n14 = te[ 12 ]; - const n21 = te[ 1 ], n22 = te[ 5 ], n23 = te[ 9 ], n24 = te[ 13 ]; - const n31 = te[ 2 ], n32 = te[ 6 ], n33 = te[ 10 ], n34 = te[ 14 ]; - const n41 = te[ 3 ], n42 = te[ 7 ], n43 = te[ 11 ], n44 = te[ 15 ]; - - //TODO: make this more efficient - //( based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm ) - - return ( - n41 * ( - + n14 * n23 * n32 - - n13 * n24 * n32 - - n14 * n22 * n33 - + n12 * n24 * n33 - + n13 * n22 * n34 - - n12 * n23 * n34 - ) + - n42 * ( - + n11 * n23 * n34 - - n11 * n24 * n33 - + n14 * n21 * n33 - - n13 * n21 * n34 - + n13 * n24 * n31 - - n14 * n23 * n31 - ) + - n43 * ( - + n11 * n24 * n32 - - n11 * n22 * n34 - - n14 * n21 * n32 - + n12 * n21 * n34 - + n14 * n22 * n31 - - n12 * n24 * n31 - ) + - n44 * ( - - n13 * n22 * n31 - - n11 * n23 * n32 - + n11 * n22 * n33 - + n13 * n21 * n32 - - n12 * n21 * n33 - + n12 * n23 * n31 - ) - - ); - - } - - transpose() { - - const te = this.elements; - let tmp; - - tmp = te[ 1 ]; te[ 1 ] = te[ 4 ]; te[ 4 ] = tmp; - tmp = te[ 2 ]; te[ 2 ] = te[ 8 ]; te[ 8 ] = tmp; - tmp = te[ 6 ]; te[ 6 ] = te[ 9 ]; te[ 9 ] = tmp; - - tmp = te[ 3 ]; te[ 3 ] = te[ 12 ]; te[ 12 ] = tmp; - tmp = te[ 7 ]; te[ 7 ] = te[ 13 ]; te[ 13 ] = tmp; - tmp = te[ 11 ]; te[ 11 ] = te[ 14 ]; te[ 14 ] = tmp; - - return this; - - } - - setPosition( x, y, z ) { - - const te = this.elements; - - if ( x.isVector3 ) { - - te[ 12 ] = x.x; - te[ 13 ] = x.y; - te[ 14 ] = x.z; - - } else { - - te[ 12 ] = x; - te[ 13 ] = y; - te[ 14 ] = z; - - } - - return this; - - } - - invert() { - - // based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm - const te = this.elements, - - n11 = te[ 0 ], n21 = te[ 1 ], n31 = te[ 2 ], n41 = te[ 3 ], - n12 = te[ 4 ], n22 = te[ 5 ], n32 = te[ 6 ], n42 = te[ 7 ], - n13 = te[ 8 ], n23 = te[ 9 ], n33 = te[ 10 ], n43 = te[ 11 ], - n14 = te[ 12 ], n24 = te[ 13 ], n34 = te[ 14 ], n44 = te[ 15 ], - - t11 = n23 * n34 * n42 - n24 * n33 * n42 + n24 * n32 * n43 - n22 * n34 * n43 - n23 * n32 * n44 + n22 * n33 * n44, - t12 = n14 * n33 * n42 - n13 * n34 * n42 - n14 * n32 * n43 + n12 * n34 * n43 + n13 * n32 * n44 - n12 * n33 * n44, - t13 = n13 * n24 * n42 - n14 * n23 * n42 + n14 * n22 * n43 - n12 * n24 * n43 - n13 * n22 * n44 + n12 * n23 * n44, - t14 = n14 * n23 * n32 - n13 * n24 * n32 - n14 * n22 * n33 + n12 * n24 * n33 + n13 * n22 * n34 - n12 * n23 * n34; - - const det = n11 * t11 + n21 * t12 + n31 * t13 + n41 * t14; - - if ( det === 0 ) return this.set( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ); - - const detInv = 1 / det; - - te[ 0 ] = t11 * detInv; - te[ 1 ] = ( n24 * n33 * n41 - n23 * n34 * n41 - n24 * n31 * n43 + n21 * n34 * n43 + n23 * n31 * n44 - n21 * n33 * n44 ) * detInv; - te[ 2 ] = ( n22 * n34 * n41 - n24 * n32 * n41 + n24 * n31 * n42 - n21 * n34 * n42 - n22 * n31 * n44 + n21 * n32 * n44 ) * detInv; - te[ 3 ] = ( n23 * n32 * n41 - n22 * n33 * n41 - n23 * n31 * n42 + n21 * n33 * n42 + n22 * n31 * n43 - n21 * n32 * n43 ) * detInv; - - te[ 4 ] = t12 * detInv; - te[ 5 ] = ( n13 * n34 * n41 - n14 * n33 * n41 + n14 * n31 * n43 - n11 * n34 * n43 - n13 * n31 * n44 + n11 * n33 * n44 ) * detInv; - te[ 6 ] = ( n14 * n32 * n41 - n12 * n34 * n41 - n14 * n31 * n42 + n11 * n34 * n42 + n12 * n31 * n44 - n11 * n32 * n44 ) * detInv; - te[ 7 ] = ( n12 * n33 * n41 - n13 * n32 * n41 + n13 * n31 * n42 - n11 * n33 * n42 - n12 * n31 * n43 + n11 * n32 * n43 ) * detInv; - - te[ 8 ] = t13 * detInv; - te[ 9 ] = ( n14 * n23 * n41 - n13 * n24 * n41 - n14 * n21 * n43 + n11 * n24 * n43 + n13 * n21 * n44 - n11 * n23 * n44 ) * detInv; - te[ 10 ] = ( n12 * n24 * n41 - n14 * n22 * n41 + n14 * n21 * n42 - n11 * n24 * n42 - n12 * n21 * n44 + n11 * n22 * n44 ) * detInv; - te[ 11 ] = ( n13 * n22 * n41 - n12 * n23 * n41 - n13 * n21 * n42 + n11 * n23 * n42 + n12 * n21 * n43 - n11 * n22 * n43 ) * detInv; - - te[ 12 ] = t14 * detInv; - te[ 13 ] = ( n13 * n24 * n31 - n14 * n23 * n31 + n14 * n21 * n33 - n11 * n24 * n33 - n13 * n21 * n34 + n11 * n23 * n34 ) * detInv; - te[ 14 ] = ( n14 * n22 * n31 - n12 * n24 * n31 - n14 * n21 * n32 + n11 * n24 * n32 + n12 * n21 * n34 - n11 * n22 * n34 ) * detInv; - te[ 15 ] = ( n12 * n23 * n31 - n13 * n22 * n31 + n13 * n21 * n32 - n11 * n23 * n32 - n12 * n21 * n33 + n11 * n22 * n33 ) * detInv; - - return this; - - } - - scale( v ) { - - const te = this.elements; - const x = v.x, y = v.y, z = v.z; - - te[ 0 ] *= x; te[ 4 ] *= y; te[ 8 ] *= z; - te[ 1 ] *= x; te[ 5 ] *= y; te[ 9 ] *= z; - te[ 2 ] *= x; te[ 6 ] *= y; te[ 10 ] *= z; - te[ 3 ] *= x; te[ 7 ] *= y; te[ 11 ] *= z; - - return this; - - } - - getMaxScaleOnAxis() { - - const te = this.elements; - - const scaleXSq = te[ 0 ] * te[ 0 ] + te[ 1 ] * te[ 1 ] + te[ 2 ] * te[ 2 ]; - const scaleYSq = te[ 4 ] * te[ 4 ] + te[ 5 ] * te[ 5 ] + te[ 6 ] * te[ 6 ]; - const scaleZSq = te[ 8 ] * te[ 8 ] + te[ 9 ] * te[ 9 ] + te[ 10 ] * te[ 10 ]; - - return Math.sqrt( Math.max( scaleXSq, scaleYSq, scaleZSq ) ); - - } - - makeTranslation( x, y, z ) { - - this.set( - - 1, 0, 0, x, - 0, 1, 0, y, - 0, 0, 1, z, - 0, 0, 0, 1 - - ); - - return this; - - } - - makeRotationX( theta ) { - - const c = Math.cos( theta ), s = Math.sin( theta ); - - this.set( - - 1, 0, 0, 0, - 0, c, - s, 0, - 0, s, c, 0, - 0, 0, 0, 1 - - ); - - return this; - - } - - makeRotationY( theta ) { - - const c = Math.cos( theta ), s = Math.sin( theta ); - - this.set( - - c, 0, s, 0, - 0, 1, 0, 0, - - s, 0, c, 0, - 0, 0, 0, 1 - - ); - - return this; - - } - - makeRotationZ( theta ) { - - const c = Math.cos( theta ), s = Math.sin( theta ); - - this.set( - - c, - s, 0, 0, - s, c, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1 - - ); - - return this; - - } - - makeRotationAxis( axis, angle ) { - - // Based on http://www.gamedev.net/reference/articles/article1199.asp - - const c = Math.cos( angle ); - const s = Math.sin( angle ); - const t = 1 - c; - const x = axis.x, y = axis.y, z = axis.z; - const tx = t * x, ty = t * y; - - this.set( - - tx * x + c, tx * y - s * z, tx * z + s * y, 0, - tx * y + s * z, ty * y + c, ty * z - s * x, 0, - tx * z - s * y, ty * z + s * x, t * z * z + c, 0, - 0, 0, 0, 1 - - ); - - return this; - - } - - makeScale( x, y, z ) { - - this.set( - - x, 0, 0, 0, - 0, y, 0, 0, - 0, 0, z, 0, - 0, 0, 0, 1 - - ); - - return this; - - } - - makeShear( xy, xz, yx, yz, zx, zy ) { - - this.set( - - 1, yx, zx, 0, - xy, 1, zy, 0, - xz, yz, 1, 0, - 0, 0, 0, 1 - - ); - - return this; - - } - - compose( position, quaternion, scale ) { - - const te = this.elements; - - const x = quaternion._x, y = quaternion._y, z = quaternion._z, w = quaternion._w; - const x2 = x + x, y2 = y + y, z2 = z + z; - const xx = x * x2, xy = x * y2, xz = x * z2; - const yy = y * y2, yz = y * z2, zz = z * z2; - const wx = w * x2, wy = w * y2, wz = w * z2; - - const sx = scale.x, sy = scale.y, sz = scale.z; - - te[ 0 ] = ( 1 - ( yy + zz ) ) * sx; - te[ 1 ] = ( xy + wz ) * sx; - te[ 2 ] = ( xz - wy ) * sx; - te[ 3 ] = 0; - - te[ 4 ] = ( xy - wz ) * sy; - te[ 5 ] = ( 1 - ( xx + zz ) ) * sy; - te[ 6 ] = ( yz + wx ) * sy; - te[ 7 ] = 0; - - te[ 8 ] = ( xz + wy ) * sz; - te[ 9 ] = ( yz - wx ) * sz; - te[ 10 ] = ( 1 - ( xx + yy ) ) * sz; - te[ 11 ] = 0; - - te[ 12 ] = position.x; - te[ 13 ] = position.y; - te[ 14 ] = position.z; - te[ 15 ] = 1; - - return this; - - } - - decompose( position, quaternion, scale ) { - - const te = this.elements; - - let sx = _v1$5.set( te[ 0 ], te[ 1 ], te[ 2 ] ).length(); - const sy = _v1$5.set( te[ 4 ], te[ 5 ], te[ 6 ] ).length(); - const sz = _v1$5.set( te[ 8 ], te[ 9 ], te[ 10 ] ).length(); - - // if determine is negative, we need to invert one scale - const det = this.determinant(); - if ( det < 0 ) sx = - sx; - - position.x = te[ 12 ]; - position.y = te[ 13 ]; - position.z = te[ 14 ]; - - // scale the rotation part - _m1$2.copy( this ); - - const invSX = 1 / sx; - const invSY = 1 / sy; - const invSZ = 1 / sz; - - _m1$2.elements[ 0 ] *= invSX; - _m1$2.elements[ 1 ] *= invSX; - _m1$2.elements[ 2 ] *= invSX; - - _m1$2.elements[ 4 ] *= invSY; - _m1$2.elements[ 5 ] *= invSY; - _m1$2.elements[ 6 ] *= invSY; - - _m1$2.elements[ 8 ] *= invSZ; - _m1$2.elements[ 9 ] *= invSZ; - _m1$2.elements[ 10 ] *= invSZ; - - quaternion.setFromRotationMatrix( _m1$2 ); - - scale.x = sx; - scale.y = sy; - scale.z = sz; - - return this; - - } - - makePerspective( left, right, top, bottom, near, far ) { - - const te = this.elements; - const x = 2 * near / ( right - left ); - const y = 2 * near / ( top - bottom ); - - const a = ( right + left ) / ( right - left ); - const b = ( top + bottom ) / ( top - bottom ); - const c = - ( far + near ) / ( far - near ); - const d = - 2 * far * near / ( far - near ); - - te[ 0 ] = x; te[ 4 ] = 0; te[ 8 ] = a; te[ 12 ] = 0; - te[ 1 ] = 0; te[ 5 ] = y; te[ 9 ] = b; te[ 13 ] = 0; - te[ 2 ] = 0; te[ 6 ] = 0; te[ 10 ] = c; te[ 14 ] = d; - te[ 3 ] = 0; te[ 7 ] = 0; te[ 11 ] = - 1; te[ 15 ] = 0; - - return this; - - } - - makeOrthographic( left, right, top, bottom, near, far ) { - - const te = this.elements; - const w = 1.0 / ( right - left ); - const h = 1.0 / ( top - bottom ); - const p = 1.0 / ( far - near ); - - const x = ( right + left ) * w; - const y = ( top + bottom ) * h; - const z = ( far + near ) * p; - - te[ 0 ] = 2 * w; te[ 4 ] = 0; te[ 8 ] = 0; te[ 12 ] = - x; - te[ 1 ] = 0; te[ 5 ] = 2 * h; te[ 9 ] = 0; te[ 13 ] = - y; - te[ 2 ] = 0; te[ 6 ] = 0; te[ 10 ] = - 2 * p; te[ 14 ] = - z; - te[ 3 ] = 0; te[ 7 ] = 0; te[ 11 ] = 0; te[ 15 ] = 1; - - return this; - - } - - equals( matrix ) { - - const te = this.elements; - const me = matrix.elements; - - for ( let i = 0; i < 16; i ++ ) { - - if ( te[ i ] !== me[ i ] ) return false; - - } - - return true; - - } - - fromArray( array, offset = 0 ) { - - for ( let i = 0; i < 16; i ++ ) { - - this.elements[ i ] = array[ i + offset ]; - - } - - return this; - - } - - toArray( array = [], offset = 0 ) { - - const te = this.elements; - - array[ offset ] = te[ 0 ]; - array[ offset + 1 ] = te[ 1 ]; - array[ offset + 2 ] = te[ 2 ]; - array[ offset + 3 ] = te[ 3 ]; - - array[ offset + 4 ] = te[ 4 ]; - array[ offset + 5 ] = te[ 5 ]; - array[ offset + 6 ] = te[ 6 ]; - array[ offset + 7 ] = te[ 7 ]; - - array[ offset + 8 ] = te[ 8 ]; - array[ offset + 9 ] = te[ 9 ]; - array[ offset + 10 ] = te[ 10 ]; - array[ offset + 11 ] = te[ 11 ]; - - array[ offset + 12 ] = te[ 12 ]; - array[ offset + 13 ] = te[ 13 ]; - array[ offset + 14 ] = te[ 14 ]; - array[ offset + 15 ] = te[ 15 ]; - - return array; - - } - - } - - const _v1$5 = /*@__PURE__*/ new Vector3(); - const _m1$2 = /*@__PURE__*/ new Matrix4(); - const _zero = /*@__PURE__*/ new Vector3( 0, 0, 0 ); - const _one = /*@__PURE__*/ new Vector3( 1, 1, 1 ); - const _x = /*@__PURE__*/ new Vector3(); - const _y = /*@__PURE__*/ new Vector3(); - const _z = /*@__PURE__*/ new Vector3(); - - const _matrix = /*@__PURE__*/ new Matrix4(); - const _quaternion$3 = /*@__PURE__*/ new Quaternion(); - - class Euler { - - constructor( x = 0, y = 0, z = 0, order = Euler.DEFAULT_ORDER ) { - - this.isEuler = true; - - this._x = x; - this._y = y; - this._z = z; - this._order = order; - - } - - get x() { - - return this._x; - - } - - set x( value ) { - - this._x = value; - this._onChangeCallback(); - - } - - get y() { - - return this._y; - - } - - set y( value ) { - - this._y = value; - this._onChangeCallback(); - - } - - get z() { - - return this._z; - - } - - set z( value ) { - - this._z = value; - this._onChangeCallback(); - - } - - get order() { - - return this._order; - - } - - set order( value ) { - - this._order = value; - this._onChangeCallback(); - - } - - set( x, y, z, order = this._order ) { - - this._x = x; - this._y = y; - this._z = z; - this._order = order; - - this._onChangeCallback(); - - return this; - - } - - clone() { - - return new this.constructor( this._x, this._y, this._z, this._order ); - - } - - copy( euler ) { - - this._x = euler._x; - this._y = euler._y; - this._z = euler._z; - this._order = euler._order; - - this._onChangeCallback(); - - return this; - - } - - setFromRotationMatrix( m, order = this._order, update = true ) { - - // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) - - const te = m.elements; - const m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ]; - const m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ]; - const m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ]; - - switch ( order ) { - - case 'XYZ': - - this._y = Math.asin( clamp( m13, - 1, 1 ) ); - - if ( Math.abs( m13 ) < 0.9999999 ) { - - this._x = Math.atan2( - m23, m33 ); - this._z = Math.atan2( - m12, m11 ); - - } else { - - this._x = Math.atan2( m32, m22 ); - this._z = 0; - - } - - break; - - case 'YXZ': - - this._x = Math.asin( - clamp( m23, - 1, 1 ) ); - - if ( Math.abs( m23 ) < 0.9999999 ) { - - this._y = Math.atan2( m13, m33 ); - this._z = Math.atan2( m21, m22 ); - - } else { - - this._y = Math.atan2( - m31, m11 ); - this._z = 0; - - } - - break; - - case 'ZXY': - - this._x = Math.asin( clamp( m32, - 1, 1 ) ); - - if ( Math.abs( m32 ) < 0.9999999 ) { - - this._y = Math.atan2( - m31, m33 ); - this._z = Math.atan2( - m12, m22 ); - - } else { - - this._y = 0; - this._z = Math.atan2( m21, m11 ); - - } - - break; - - case 'ZYX': - - this._y = Math.asin( - clamp( m31, - 1, 1 ) ); - - if ( Math.abs( m31 ) < 0.9999999 ) { - - this._x = Math.atan2( m32, m33 ); - this._z = Math.atan2( m21, m11 ); - - } else { - - this._x = 0; - this._z = Math.atan2( - m12, m22 ); - - } - - break; - - case 'YZX': - - this._z = Math.asin( clamp( m21, - 1, 1 ) ); - - if ( Math.abs( m21 ) < 0.9999999 ) { - - this._x = Math.atan2( - m23, m22 ); - this._y = Math.atan2( - m31, m11 ); - - } else { - - this._x = 0; - this._y = Math.atan2( m13, m33 ); - - } - - break; - - case 'XZY': - - this._z = Math.asin( - clamp( m12, - 1, 1 ) ); - - if ( Math.abs( m12 ) < 0.9999999 ) { - - this._x = Math.atan2( m32, m22 ); - this._y = Math.atan2( m13, m11 ); - - } else { - - this._x = Math.atan2( - m23, m33 ); - this._y = 0; - - } - - break; - - default: - - console.warn( 'THREE.Euler: .setFromRotationMatrix() encountered an unknown order: ' + order ); - - } - - this._order = order; - - if ( update === true ) this._onChangeCallback(); - - return this; - - } - - setFromQuaternion( q, order, update ) { - - _matrix.makeRotationFromQuaternion( q ); - - return this.setFromRotationMatrix( _matrix, order, update ); - - } - - setFromVector3( v, order = this._order ) { - - return this.set( v.x, v.y, v.z, order ); - - } - - reorder( newOrder ) { - - // WARNING: this discards revolution information -bhouston - - _quaternion$3.setFromEuler( this ); - - return this.setFromQuaternion( _quaternion$3, newOrder ); - - } - - equals( euler ) { - - return ( euler._x === this._x ) && ( euler._y === this._y ) && ( euler._z === this._z ) && ( euler._order === this._order ); - - } - - fromArray( array ) { - - this._x = array[ 0 ]; - this._y = array[ 1 ]; - this._z = array[ 2 ]; - if ( array[ 3 ] !== undefined ) this._order = array[ 3 ]; - - this._onChangeCallback(); - - return this; - - } - - toArray( array = [], offset = 0 ) { - - array[ offset ] = this._x; - array[ offset + 1 ] = this._y; - array[ offset + 2 ] = this._z; - array[ offset + 3 ] = this._order; - - return array; - - } - - _onChange( callback ) { - - this._onChangeCallback = callback; - - return this; - - } - - _onChangeCallback() {} - - *[ Symbol.iterator ]() { - - yield this._x; - yield this._y; - yield this._z; - yield this._order; - - } - - } - - Euler.DEFAULT_ORDER = 'XYZ'; - - class Layers { - - constructor() { - - this.mask = 1 | 0; - - } - - set( channel ) { - - this.mask = ( 1 << channel | 0 ) >>> 0; - - } - - enable( channel ) { - - this.mask |= 1 << channel | 0; - - } - - enableAll() { - - this.mask = 0xffffffff | 0; - - } - - toggle( channel ) { - - this.mask ^= 1 << channel | 0; - - } - - disable( channel ) { - - this.mask &= ~ ( 1 << channel | 0 ); - - } - - disableAll() { - - this.mask = 0; - - } - - test( layers ) { - - return ( this.mask & layers.mask ) !== 0; - - } - - isEnabled( channel ) { - - return ( this.mask & ( 1 << channel | 0 ) ) !== 0; - - } - - } - - let _object3DId = 0; - - const _v1$4 = /*@__PURE__*/ new Vector3(); - const _q1 = /*@__PURE__*/ new Quaternion(); - const _m1$1 = /*@__PURE__*/ new Matrix4(); - const _target = /*@__PURE__*/ new Vector3(); - - const _position$3 = /*@__PURE__*/ new Vector3(); - const _scale$2 = /*@__PURE__*/ new Vector3(); - const _quaternion$2 = /*@__PURE__*/ new Quaternion(); - - const _xAxis = /*@__PURE__*/ new Vector3( 1, 0, 0 ); - const _yAxis = /*@__PURE__*/ new Vector3( 0, 1, 0 ); - const _zAxis = /*@__PURE__*/ new Vector3( 0, 0, 1 ); - - const _addedEvent = { type: 'added' }; - const _removedEvent = { type: 'removed' }; - - class Object3D extends EventDispatcher { - - constructor() { - - super(); - - this.isObject3D = true; - - Object.defineProperty( this, 'id', { value: _object3DId ++ } ); - - this.uuid = generateUUID(); - - this.name = ''; - this.type = 'Object3D'; - - this.parent = null; - this.children = []; - - this.up = Object3D.DEFAULT_UP.clone(); - - const position = new Vector3(); - const rotation = new Euler(); - const quaternion = new Quaternion(); - const scale = new Vector3( 1, 1, 1 ); - - function onRotationChange() { - - quaternion.setFromEuler( rotation, false ); - - } - - function onQuaternionChange() { - - rotation.setFromQuaternion( quaternion, undefined, false ); - - } - - rotation._onChange( onRotationChange ); - quaternion._onChange( onQuaternionChange ); - - Object.defineProperties( this, { - position: { - configurable: true, - enumerable: true, - value: position - }, - rotation: { - configurable: true, - enumerable: true, - value: rotation - }, - quaternion: { - configurable: true, - enumerable: true, - value: quaternion - }, - scale: { - configurable: true, - enumerable: true, - value: scale - }, - modelViewMatrix: { - value: new Matrix4() - }, - normalMatrix: { - value: new Matrix3() - } - } ); - - this.matrix = new Matrix4(); - this.matrixWorld = new Matrix4(); - - this.matrixAutoUpdate = Object3D.DEFAULT_MATRIX_AUTO_UPDATE; - this.matrixWorldNeedsUpdate = false; - - this.matrixWorldAutoUpdate = Object3D.DEFAULT_MATRIX_WORLD_AUTO_UPDATE; // checked by the renderer - - this.layers = new Layers(); - this.visible = true; - - this.castShadow = false; - this.receiveShadow = false; - - this.frustumCulled = true; - this.renderOrder = 0; - - this.animations = []; - - this.userData = {}; - - } - - onBeforeRender( /* renderer, scene, camera, geometry, material, group */ ) {} - - onAfterRender( /* renderer, scene, camera, geometry, material, group */ ) {} - - applyMatrix4( matrix ) { - - if ( this.matrixAutoUpdate ) this.updateMatrix(); - - this.matrix.premultiply( matrix ); - - this.matrix.decompose( this.position, this.quaternion, this.scale ); - - } - - applyQuaternion( q ) { - - this.quaternion.premultiply( q ); - - return this; - - } - - setRotationFromAxisAngle( axis, angle ) { - - // assumes axis is normalized - - this.quaternion.setFromAxisAngle( axis, angle ); - - } - - setRotationFromEuler( euler ) { - - this.quaternion.setFromEuler( euler, true ); - - } - - setRotationFromMatrix( m ) { - - // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) - - this.quaternion.setFromRotationMatrix( m ); - - } - - setRotationFromQuaternion( q ) { - - // assumes q is normalized - - this.quaternion.copy( q ); - - } - - rotateOnAxis( axis, angle ) { - - // rotate object on axis in object space - // axis is assumed to be normalized - - _q1.setFromAxisAngle( axis, angle ); - - this.quaternion.multiply( _q1 ); - - return this; - - } - - rotateOnWorldAxis( axis, angle ) { - - // rotate object on axis in world space - // axis is assumed to be normalized - // method assumes no rotated parent - - _q1.setFromAxisAngle( axis, angle ); - - this.quaternion.premultiply( _q1 ); - - return this; - - } - - rotateX( angle ) { - - return this.rotateOnAxis( _xAxis, angle ); - - } - - rotateY( angle ) { - - return this.rotateOnAxis( _yAxis, angle ); - - } - - rotateZ( angle ) { - - return this.rotateOnAxis( _zAxis, angle ); - - } - - translateOnAxis( axis, distance ) { - - // translate object by distance along axis in object space - // axis is assumed to be normalized - - _v1$4.copy( axis ).applyQuaternion( this.quaternion ); - - this.position.add( _v1$4.multiplyScalar( distance ) ); - - return this; - - } - - translateX( distance ) { - - return this.translateOnAxis( _xAxis, distance ); - - } - - translateY( distance ) { - - return this.translateOnAxis( _yAxis, distance ); - - } - - translateZ( distance ) { - - return this.translateOnAxis( _zAxis, distance ); - - } - - localToWorld( vector ) { - - this.updateWorldMatrix( true, false ); - - return vector.applyMatrix4( this.matrixWorld ); - - } - - worldToLocal( vector ) { - - this.updateWorldMatrix( true, false ); - - return vector.applyMatrix4( _m1$1.copy( this.matrixWorld ).invert() ); - - } - - lookAt( x, y, z ) { - - // This method does not support objects having non-uniformly-scaled parent(s) - - if ( x.isVector3 ) { - - _target.copy( x ); - - } else { - - _target.set( x, y, z ); - - } - - const parent = this.parent; - - this.updateWorldMatrix( true, false ); - - _position$3.setFromMatrixPosition( this.matrixWorld ); - - if ( this.isCamera || this.isLight ) { - - _m1$1.lookAt( _position$3, _target, this.up ); - - } else { - - _m1$1.lookAt( _target, _position$3, this.up ); - - } - - this.quaternion.setFromRotationMatrix( _m1$1 ); - - if ( parent ) { - - _m1$1.extractRotation( parent.matrixWorld ); - _q1.setFromRotationMatrix( _m1$1 ); - this.quaternion.premultiply( _q1.invert() ); - - } - - } - - add( object ) { - - if ( arguments.length > 1 ) { - - for ( let i = 0; i < arguments.length; i ++ ) { - - this.add( arguments[ i ] ); - - } - - return this; - - } - - if ( object === this ) { - - console.error( 'THREE.Object3D.add: object can\'t be added as a child of itself.', object ); - return this; - - } - - if ( object && object.isObject3D ) { - - if ( object.parent !== null ) { - - object.parent.remove( object ); - - } - - object.parent = this; - this.children.push( object ); - - object.dispatchEvent( _addedEvent ); - - } else { - - console.error( 'THREE.Object3D.add: object not an instance of THREE.Object3D.', object ); - - } - - return this; - - } - - remove( object ) { - - if ( arguments.length > 1 ) { - - for ( let i = 0; i < arguments.length; i ++ ) { - - this.remove( arguments[ i ] ); - - } - - return this; - - } - - const index = this.children.indexOf( object ); - - if ( index !== - 1 ) { - - object.parent = null; - this.children.splice( index, 1 ); - - object.dispatchEvent( _removedEvent ); - - } - - return this; - - } - - removeFromParent() { - - const parent = this.parent; - - if ( parent !== null ) { - - parent.remove( this ); - - } - - return this; - - } - - clear() { - - for ( let i = 0; i < this.children.length; i ++ ) { - - const object = this.children[ i ]; - - object.parent = null; - - object.dispatchEvent( _removedEvent ); - - } - - this.children.length = 0; - - return this; - - - } - - attach( object ) { - - // adds object as a child of this, while maintaining the object's world transform - - // Note: This method does not support scene graphs having non-uniformly-scaled nodes(s) - - this.updateWorldMatrix( true, false ); - - _m1$1.copy( this.matrixWorld ).invert(); - - if ( object.parent !== null ) { - - object.parent.updateWorldMatrix( true, false ); - - _m1$1.multiply( object.parent.matrixWorld ); - - } - - object.applyMatrix4( _m1$1 ); - - this.add( object ); - - object.updateWorldMatrix( false, true ); - - return this; - - } - - getObjectById( id ) { - - return this.getObjectByProperty( 'id', id ); - - } - - getObjectByName( name ) { - - return this.getObjectByProperty( 'name', name ); - - } - - getObjectByProperty( name, value ) { - - if ( this[ name ] === value ) return this; - - for ( let i = 0, l = this.children.length; i < l; i ++ ) { - - const child = this.children[ i ]; - const object = child.getObjectByProperty( name, value ); - - if ( object !== undefined ) { - - return object; - - } - - } - - return undefined; - - } - - getObjectsByProperty( name, value ) { - - let result = []; - - if ( this[ name ] === value ) result.push( this ); - - for ( let i = 0, l = this.children.length; i < l; i ++ ) { - - const childResult = this.children[ i ].getObjectsByProperty( name, value ); - - if ( childResult.length > 0 ) { - - result = result.concat( childResult ); - - } - - } - - return result; - - } - - getWorldPosition( target ) { - - this.updateWorldMatrix( true, false ); - - return target.setFromMatrixPosition( this.matrixWorld ); - - } - - getWorldQuaternion( target ) { - - this.updateWorldMatrix( true, false ); - - this.matrixWorld.decompose( _position$3, target, _scale$2 ); - - return target; - - } - - getWorldScale( target ) { - - this.updateWorldMatrix( true, false ); - - this.matrixWorld.decompose( _position$3, _quaternion$2, target ); - - return target; - - } - - getWorldDirection( target ) { - - this.updateWorldMatrix( true, false ); - - const e = this.matrixWorld.elements; - - return target.set( e[ 8 ], e[ 9 ], e[ 10 ] ).normalize(); - - } - - raycast( /* raycaster, intersects */ ) {} - - traverse( callback ) { - - callback( this ); - - const children = this.children; - - for ( let i = 0, l = children.length; i < l; i ++ ) { - - children[ i ].traverse( callback ); - - } - - } - - traverseVisible( callback ) { - - if ( this.visible === false ) return; - - callback( this ); - - const children = this.children; - - for ( let i = 0, l = children.length; i < l; i ++ ) { - - children[ i ].traverseVisible( callback ); - - } - - } - - traverseAncestors( callback ) { - - const parent = this.parent; - - if ( parent !== null ) { - - callback( parent ); - - parent.traverseAncestors( callback ); - - } - - } - - updateMatrix() { - - this.matrix.compose( this.position, this.quaternion, this.scale ); - - this.matrixWorldNeedsUpdate = true; - - } - - updateMatrixWorld( force ) { - - if ( this.matrixAutoUpdate ) this.updateMatrix(); - - if ( this.matrixWorldNeedsUpdate || force ) { - - if ( this.parent === null ) { - - this.matrixWorld.copy( this.matrix ); - - } else { - - this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix ); - - } - - this.matrixWorldNeedsUpdate = false; - - force = true; - - } - - // update children - - const children = this.children; - - for ( let i = 0, l = children.length; i < l; i ++ ) { - - const child = children[ i ]; - - if ( child.matrixWorldAutoUpdate === true || force === true ) { - - child.updateMatrixWorld( force ); - - } - - } - - } - - updateWorldMatrix( updateParents, updateChildren ) { - - const parent = this.parent; - - if ( updateParents === true && parent !== null && parent.matrixWorldAutoUpdate === true ) { - - parent.updateWorldMatrix( true, false ); - - } - - if ( this.matrixAutoUpdate ) this.updateMatrix(); - - if ( this.parent === null ) { - - this.matrixWorld.copy( this.matrix ); - - } else { - - this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix ); - - } - - // update children - - if ( updateChildren === true ) { - - const children = this.children; - - for ( let i = 0, l = children.length; i < l; i ++ ) { - - const child = children[ i ]; - - if ( child.matrixWorldAutoUpdate === true ) { - - child.updateWorldMatrix( false, true ); - - } - - } - - } - - } - - toJSON( meta ) { - - // meta is a string when called from JSON.stringify - const isRootObject = ( meta === undefined || typeof meta === 'string' ); - - const output = {}; - - // meta is a hash used to collect geometries, materials. - // not providing it implies that this is the root object - // being serialized. - if ( isRootObject ) { - - // initialize meta obj - meta = { - geometries: {}, - materials: {}, - textures: {}, - images: {}, - shapes: {}, - skeletons: {}, - animations: {}, - nodes: {} - }; - - output.metadata = { - version: 4.6, - type: 'Object', - generator: 'Object3D.toJSON' - }; - - } - - // standard Object3D serialization - - const object = {}; - - object.uuid = this.uuid; - object.type = this.type; - - if ( this.name !== '' ) object.name = this.name; - if ( this.castShadow === true ) object.castShadow = true; - if ( this.receiveShadow === true ) object.receiveShadow = true; - if ( this.visible === false ) object.visible = false; - if ( this.frustumCulled === false ) object.frustumCulled = false; - if ( this.renderOrder !== 0 ) object.renderOrder = this.renderOrder; - if ( Object.keys( this.userData ).length > 0 ) object.userData = this.userData; - - object.layers = this.layers.mask; - object.matrix = this.matrix.toArray(); - object.up = this.up.toArray(); - - if ( this.matrixAutoUpdate === false ) object.matrixAutoUpdate = false; - - // object specific properties - - if ( this.isInstancedMesh ) { - - object.type = 'InstancedMesh'; - object.count = this.count; - object.instanceMatrix = this.instanceMatrix.toJSON(); - if ( this.instanceColor !== null ) object.instanceColor = this.instanceColor.toJSON(); - - } - - // - - function serialize( library, element ) { - - if ( library[ element.uuid ] === undefined ) { - - library[ element.uuid ] = element.toJSON( meta ); - - } - - return element.uuid; - - } - - if ( this.isScene ) { - - if ( this.background ) { - - if ( this.background.isColor ) { - - object.background = this.background.toJSON(); - - } else if ( this.background.isTexture ) { - - object.background = this.background.toJSON( meta ).uuid; - - } - - } - - if ( this.environment && this.environment.isTexture && this.environment.isRenderTargetTexture !== true ) { - - object.environment = this.environment.toJSON( meta ).uuid; - - } - - } else if ( this.isMesh || this.isLine || this.isPoints ) { - - object.geometry = serialize( meta.geometries, this.geometry ); - - const parameters = this.geometry.parameters; - - if ( parameters !== undefined && parameters.shapes !== undefined ) { - - const shapes = parameters.shapes; - - if ( Array.isArray( shapes ) ) { - - for ( let i = 0, l = shapes.length; i < l; i ++ ) { - - const shape = shapes[ i ]; - - serialize( meta.shapes, shape ); - - } - - } else { - - serialize( meta.shapes, shapes ); - - } - - } - - } - - if ( this.isSkinnedMesh ) { - - object.bindMode = this.bindMode; - object.bindMatrix = this.bindMatrix.toArray(); - - if ( this.skeleton !== undefined ) { - - serialize( meta.skeletons, this.skeleton ); - - object.skeleton = this.skeleton.uuid; - - } - - } - - if ( this.material !== undefined ) { - - if ( Array.isArray( this.material ) ) { - - const uuids = []; - - for ( let i = 0, l = this.material.length; i < l; i ++ ) { - - uuids.push( serialize( meta.materials, this.material[ i ] ) ); - - } - - object.material = uuids; - - } else { - - object.material = serialize( meta.materials, this.material ); - - } - - } - - // - - if ( this.children.length > 0 ) { - - object.children = []; - - for ( let i = 0; i < this.children.length; i ++ ) { - - object.children.push( this.children[ i ].toJSON( meta ).object ); - - } - - } - - // - - if ( this.animations.length > 0 ) { - - object.animations = []; - - for ( let i = 0; i < this.animations.length; i ++ ) { - - const animation = this.animations[ i ]; - - object.animations.push( serialize( meta.animations, animation ) ); - - } - - } - - if ( isRootObject ) { - - const geometries = extractFromCache( meta.geometries ); - const materials = extractFromCache( meta.materials ); - const textures = extractFromCache( meta.textures ); - const images = extractFromCache( meta.images ); - const shapes = extractFromCache( meta.shapes ); - const skeletons = extractFromCache( meta.skeletons ); - const animations = extractFromCache( meta.animations ); - const nodes = extractFromCache( meta.nodes ); - - if ( geometries.length > 0 ) output.geometries = geometries; - if ( materials.length > 0 ) output.materials = materials; - if ( textures.length > 0 ) output.textures = textures; - if ( images.length > 0 ) output.images = images; - if ( shapes.length > 0 ) output.shapes = shapes; - if ( skeletons.length > 0 ) output.skeletons = skeletons; - if ( animations.length > 0 ) output.animations = animations; - if ( nodes.length > 0 ) output.nodes = nodes; - - } - - output.object = object; - - return output; - - // extract data from the cache hash - // remove metadata on each item - // and return as array - function extractFromCache( cache ) { - - const values = []; - for ( const key in cache ) { - - const data = cache[ key ]; - delete data.metadata; - values.push( data ); - - } - - return values; - - } - - } - - clone( recursive ) { - - return new this.constructor().copy( this, recursive ); - - } - - copy( source, recursive = true ) { - - this.name = source.name; - - this.up.copy( source.up ); - - this.position.copy( source.position ); - this.rotation.order = source.rotation.order; - this.quaternion.copy( source.quaternion ); - this.scale.copy( source.scale ); - - this.matrix.copy( source.matrix ); - this.matrixWorld.copy( source.matrixWorld ); - - this.matrixAutoUpdate = source.matrixAutoUpdate; - this.matrixWorldNeedsUpdate = source.matrixWorldNeedsUpdate; - - this.matrixWorldAutoUpdate = source.matrixWorldAutoUpdate; - - this.layers.mask = source.layers.mask; - this.visible = source.visible; - - this.castShadow = source.castShadow; - this.receiveShadow = source.receiveShadow; - - this.frustumCulled = source.frustumCulled; - this.renderOrder = source.renderOrder; - - this.animations = source.animations; - - this.userData = JSON.parse( JSON.stringify( source.userData ) ); - - if ( recursive === true ) { - - for ( let i = 0; i < source.children.length; i ++ ) { - - const child = source.children[ i ]; - this.add( child.clone() ); - - } - - } - - return this; - - } - - } - - Object3D.DEFAULT_UP = /*@__PURE__*/ new Vector3( 0, 1, 0 ); - Object3D.DEFAULT_MATRIX_AUTO_UPDATE = true; - Object3D.DEFAULT_MATRIX_WORLD_AUTO_UPDATE = true; - - const _v0$1 = /*@__PURE__*/ new Vector3(); - const _v1$3 = /*@__PURE__*/ new Vector3(); - const _v2$2 = /*@__PURE__*/ new Vector3(); - const _v3$1 = /*@__PURE__*/ new Vector3(); - - const _vab = /*@__PURE__*/ new Vector3(); - const _vac = /*@__PURE__*/ new Vector3(); - const _vbc = /*@__PURE__*/ new Vector3(); - const _vap = /*@__PURE__*/ new Vector3(); - const _vbp = /*@__PURE__*/ new Vector3(); - const _vcp = /*@__PURE__*/ new Vector3(); - - let warnedGetUV = false; - - class Triangle { - - constructor( a = new Vector3(), b = new Vector3(), c = new Vector3() ) { - - this.a = a; - this.b = b; - this.c = c; - - } - - static getNormal( a, b, c, target ) { - - target.subVectors( c, b ); - _v0$1.subVectors( a, b ); - target.cross( _v0$1 ); - - const targetLengthSq = target.lengthSq(); - if ( targetLengthSq > 0 ) { - - return target.multiplyScalar( 1 / Math.sqrt( targetLengthSq ) ); - - } - - return target.set( 0, 0, 0 ); - - } - - // static/instance method to calculate barycentric coordinates - // based on: http://www.blackpawn.com/texts/pointinpoly/default.html - static getBarycoord( point, a, b, c, target ) { - - _v0$1.subVectors( c, a ); - _v1$3.subVectors( b, a ); - _v2$2.subVectors( point, a ); - - const dot00 = _v0$1.dot( _v0$1 ); - const dot01 = _v0$1.dot( _v1$3 ); - const dot02 = _v0$1.dot( _v2$2 ); - const dot11 = _v1$3.dot( _v1$3 ); - const dot12 = _v1$3.dot( _v2$2 ); - - const denom = ( dot00 * dot11 - dot01 * dot01 ); - - // collinear or singular triangle - if ( denom === 0 ) { - - // arbitrary location outside of triangle? - // not sure if this is the best idea, maybe should be returning undefined - return target.set( - 2, - 1, - 1 ); - - } - - const invDenom = 1 / denom; - const u = ( dot11 * dot02 - dot01 * dot12 ) * invDenom; - const v = ( dot00 * dot12 - dot01 * dot02 ) * invDenom; - - // barycentric coordinates must always sum to 1 - return target.set( 1 - u - v, v, u ); - - } - - static containsPoint( point, a, b, c ) { - - this.getBarycoord( point, a, b, c, _v3$1 ); - - return ( _v3$1.x >= 0 ) && ( _v3$1.y >= 0 ) && ( ( _v3$1.x + _v3$1.y ) <= 1 ); - - } - - static getUV( point, p1, p2, p3, uv1, uv2, uv3, target ) { // @deprecated, r151 - - if ( warnedGetUV === false ) { - - console.warn( 'THREE.Triangle.getUV() has been renamed to THREE.Triangle.getInterpolation().' ); - - warnedGetUV = true; - - } - - return this.getInterpolation( point, p1, p2, p3, uv1, uv2, uv3, target ); - - } - - static getInterpolation( point, p1, p2, p3, v1, v2, v3, target ) { - - this.getBarycoord( point, p1, p2, p3, _v3$1 ); - - target.setScalar( 0 ); - target.addScaledVector( v1, _v3$1.x ); - target.addScaledVector( v2, _v3$1.y ); - target.addScaledVector( v3, _v3$1.z ); - - return target; - - } - - static isFrontFacing( a, b, c, direction ) { - - _v0$1.subVectors( c, b ); - _v1$3.subVectors( a, b ); - - // strictly front facing - return ( _v0$1.cross( _v1$3 ).dot( direction ) < 0 ) ? true : false; - - } - - set( a, b, c ) { - - this.a.copy( a ); - this.b.copy( b ); - this.c.copy( c ); - - return this; - - } - - setFromPointsAndIndices( points, i0, i1, i2 ) { - - this.a.copy( points[ i0 ] ); - this.b.copy( points[ i1 ] ); - this.c.copy( points[ i2 ] ); - - return this; - - } - - setFromAttributeAndIndices( attribute, i0, i1, i2 ) { - - this.a.fromBufferAttribute( attribute, i0 ); - this.b.fromBufferAttribute( attribute, i1 ); - this.c.fromBufferAttribute( attribute, i2 ); - - return this; - - } - - clone() { - - return new this.constructor().copy( this ); - - } - - copy( triangle ) { - - this.a.copy( triangle.a ); - this.b.copy( triangle.b ); - this.c.copy( triangle.c ); - - return this; - - } - - getArea() { - - _v0$1.subVectors( this.c, this.b ); - _v1$3.subVectors( this.a, this.b ); - - return _v0$1.cross( _v1$3 ).length() * 0.5; - - } - - getMidpoint( target ) { - - return target.addVectors( this.a, this.b ).add( this.c ).multiplyScalar( 1 / 3 ); - - } - - getNormal( target ) { - - return Triangle.getNormal( this.a, this.b, this.c, target ); - - } - - getPlane( target ) { - - return target.setFromCoplanarPoints( this.a, this.b, this.c ); - - } - - getBarycoord( point, target ) { - - return Triangle.getBarycoord( point, this.a, this.b, this.c, target ); - - } - - getUV( point, uv1, uv2, uv3, target ) { // @deprecated, r151 - - if ( warnedGetUV === false ) { - - console.warn( 'THREE.Triangle.getUV() has been renamed to THREE.Triangle.getInterpolation().' ); - - warnedGetUV = true; - - } - - return Triangle.getInterpolation( point, this.a, this.b, this.c, uv1, uv2, uv3, target ); - - } - - getInterpolation( point, v1, v2, v3, target ) { - - return Triangle.getInterpolation( point, this.a, this.b, this.c, v1, v2, v3, target ); - - } - - containsPoint( point ) { - - return Triangle.containsPoint( point, this.a, this.b, this.c ); - - } - - isFrontFacing( direction ) { - - return Triangle.isFrontFacing( this.a, this.b, this.c, direction ); - - } - - intersectsBox( box ) { - - return box.intersectsTriangle( this ); - - } - - closestPointToPoint( p, target ) { - - const a = this.a, b = this.b, c = this.c; - let v, w; - - // algorithm thanks to Real-Time Collision Detection by Christer Ericson, - // published by Morgan Kaufmann Publishers, (c) 2005 Elsevier Inc., - // under the accompanying license; see chapter 5.1.5 for detailed explanation. - // basically, we're distinguishing which of the voronoi regions of the triangle - // the point lies in with the minimum amount of redundant computation. - - _vab.subVectors( b, a ); - _vac.subVectors( c, a ); - _vap.subVectors( p, a ); - const d1 = _vab.dot( _vap ); - const d2 = _vac.dot( _vap ); - if ( d1 <= 0 && d2 <= 0 ) { - - // vertex region of A; barycentric coords (1, 0, 0) - return target.copy( a ); - - } - - _vbp.subVectors( p, b ); - const d3 = _vab.dot( _vbp ); - const d4 = _vac.dot( _vbp ); - if ( d3 >= 0 && d4 <= d3 ) { - - // vertex region of B; barycentric coords (0, 1, 0) - return target.copy( b ); - - } - - const vc = d1 * d4 - d3 * d2; - if ( vc <= 0 && d1 >= 0 && d3 <= 0 ) { - - v = d1 / ( d1 - d3 ); - // edge region of AB; barycentric coords (1-v, v, 0) - return target.copy( a ).addScaledVector( _vab, v ); - - } - - _vcp.subVectors( p, c ); - const d5 = _vab.dot( _vcp ); - const d6 = _vac.dot( _vcp ); - if ( d6 >= 0 && d5 <= d6 ) { - - // vertex region of C; barycentric coords (0, 0, 1) - return target.copy( c ); - - } - - const vb = d5 * d2 - d1 * d6; - if ( vb <= 0 && d2 >= 0 && d6 <= 0 ) { - - w = d2 / ( d2 - d6 ); - // edge region of AC; barycentric coords (1-w, 0, w) - return target.copy( a ).addScaledVector( _vac, w ); - - } - - const va = d3 * d6 - d5 * d4; - if ( va <= 0 && ( d4 - d3 ) >= 0 && ( d5 - d6 ) >= 0 ) { - - _vbc.subVectors( c, b ); - w = ( d4 - d3 ) / ( ( d4 - d3 ) + ( d5 - d6 ) ); - // edge region of BC; barycentric coords (0, 1-w, w) - return target.copy( b ).addScaledVector( _vbc, w ); // edge region of BC - - } - - // face region - const denom = 1 / ( va + vb + vc ); - // u = va * denom - v = vb * denom; - w = vc * denom; - - return target.copy( a ).addScaledVector( _vab, v ).addScaledVector( _vac, w ); - - } - - equals( triangle ) { - - return triangle.a.equals( this.a ) && triangle.b.equals( this.b ) && triangle.c.equals( this.c ); - - } - - } - - let materialId = 0; - - class Material extends EventDispatcher { - - constructor() { - - super(); - - this.isMaterial = true; - - Object.defineProperty( this, 'id', { value: materialId ++ } ); - - this.uuid = generateUUID(); - - this.name = ''; - this.type = 'Material'; - - this.blending = NormalBlending; - this.side = FrontSide; - this.vertexColors = false; - - this.opacity = 1; - this.transparent = false; - - this.blendSrc = SrcAlphaFactor; - this.blendDst = OneMinusSrcAlphaFactor; - this.blendEquation = AddEquation; - this.blendSrcAlpha = null; - this.blendDstAlpha = null; - this.blendEquationAlpha = null; - - this.depthFunc = LessEqualDepth; - this.depthTest = true; - this.depthWrite = true; - - this.stencilWriteMask = 0xff; - this.stencilFunc = AlwaysStencilFunc; - this.stencilRef = 0; - this.stencilFuncMask = 0xff; - this.stencilFail = KeepStencilOp; - this.stencilZFail = KeepStencilOp; - this.stencilZPass = KeepStencilOp; - this.stencilWrite = false; - - this.clippingPlanes = null; - this.clipIntersection = false; - this.clipShadows = false; - - this.shadowSide = null; - - this.colorWrite = true; - - this.precision = null; // override the renderer's default precision for this material - - this.polygonOffset = false; - this.polygonOffsetFactor = 0; - this.polygonOffsetUnits = 0; - - this.dithering = false; - - this.alphaToCoverage = false; - this.premultipliedAlpha = false; - this.forceSinglePass = false; - - this.visible = true; - - this.toneMapped = true; - - this.userData = {}; - - this.version = 0; - - this._alphaTest = 0; - - } - - get alphaTest() { - - return this._alphaTest; - - } - - set alphaTest( value ) { - - if ( this._alphaTest > 0 !== value > 0 ) { - - this.version ++; - - } - - this._alphaTest = value; - - } - - onBuild( /* shaderobject, renderer */ ) {} - - onBeforeRender( /* renderer, scene, camera, geometry, object, group */ ) {} - - onBeforeCompile( /* shaderobject, renderer */ ) {} - - customProgramCacheKey() { - - return this.onBeforeCompile.toString(); - - } - - setValues( values ) { - - if ( values === undefined ) return; - - for ( const key in values ) { - - const newValue = values[ key ]; - - if ( newValue === undefined ) { - - console.warn( `THREE.Material: parameter '${ key }' has value of undefined.` ); - continue; - - } - - const currentValue = this[ key ]; - - if ( currentValue === undefined ) { - - console.warn( `THREE.Material: '${ key }' is not a property of THREE.${ this.type }.` ); - continue; - - } - - if ( currentValue && currentValue.isColor ) { - - currentValue.set( newValue ); - - } else if ( ( currentValue && currentValue.isVector3 ) && ( newValue && newValue.isVector3 ) ) { - - currentValue.copy( newValue ); - - } else { - - this[ key ] = newValue; - - } - - } - - } - - toJSON( meta ) { - - const isRootObject = ( meta === undefined || typeof meta === 'string' ); - - if ( isRootObject ) { - - meta = { - textures: {}, - images: {} - }; - - } - - const data = { - metadata: { - version: 4.6, - type: 'Material', - generator: 'Material.toJSON' - } - }; - - // standard Material serialization - data.uuid = this.uuid; - data.type = this.type; - - if ( this.name !== '' ) data.name = this.name; - - if ( this.color && this.color.isColor ) data.color = this.color.getHex(); - - if ( this.roughness !== undefined ) data.roughness = this.roughness; - if ( this.metalness !== undefined ) data.metalness = this.metalness; - - if ( this.sheen !== undefined ) data.sheen = this.sheen; - if ( this.sheenColor && this.sheenColor.isColor ) data.sheenColor = this.sheenColor.getHex(); - if ( this.sheenRoughness !== undefined ) data.sheenRoughness = this.sheenRoughness; - if ( this.emissive && this.emissive.isColor ) data.emissive = this.emissive.getHex(); - if ( this.emissiveIntensity && this.emissiveIntensity !== 1 ) data.emissiveIntensity = this.emissiveIntensity; - - if ( this.specular && this.specular.isColor ) data.specular = this.specular.getHex(); - if ( this.specularIntensity !== undefined ) data.specularIntensity = this.specularIntensity; - if ( this.specularColor && this.specularColor.isColor ) data.specularColor = this.specularColor.getHex(); - if ( this.shininess !== undefined ) data.shininess = this.shininess; - if ( this.clearcoat !== undefined ) data.clearcoat = this.clearcoat; - if ( this.clearcoatRoughness !== undefined ) data.clearcoatRoughness = this.clearcoatRoughness; - - if ( this.clearcoatMap && this.clearcoatMap.isTexture ) { - - data.clearcoatMap = this.clearcoatMap.toJSON( meta ).uuid; - - } - - if ( this.clearcoatRoughnessMap && this.clearcoatRoughnessMap.isTexture ) { - - data.clearcoatRoughnessMap = this.clearcoatRoughnessMap.toJSON( meta ).uuid; - - } - - if ( this.clearcoatNormalMap && this.clearcoatNormalMap.isTexture ) { - - data.clearcoatNormalMap = this.clearcoatNormalMap.toJSON( meta ).uuid; - data.clearcoatNormalScale = this.clearcoatNormalScale.toArray(); - - } - - if ( this.iridescence !== undefined ) data.iridescence = this.iridescence; - if ( this.iridescenceIOR !== undefined ) data.iridescenceIOR = this.iridescenceIOR; - if ( this.iridescenceThicknessRange !== undefined ) data.iridescenceThicknessRange = this.iridescenceThicknessRange; - - if ( this.iridescenceMap && this.iridescenceMap.isTexture ) { - - data.iridescenceMap = this.iridescenceMap.toJSON( meta ).uuid; - - } - - if ( this.iridescenceThicknessMap && this.iridescenceThicknessMap.isTexture ) { - - data.iridescenceThicknessMap = this.iridescenceThicknessMap.toJSON( meta ).uuid; - - } - - if ( this.anisotropy !== undefined ) data.anisotropy = this.anisotropy; - if ( this.anisotropyRotation !== undefined ) data.anisotropyRotation = this.anisotropyRotation; - - if ( this.anisotropyMap && this.anisotropyMap.isTexture ) { - - data.anisotropyMap = this.anisotropyMap.toJSON( meta ).uuid; - - } - - if ( this.map && this.map.isTexture ) data.map = this.map.toJSON( meta ).uuid; - if ( this.matcap && this.matcap.isTexture ) data.matcap = this.matcap.toJSON( meta ).uuid; - if ( this.alphaMap && this.alphaMap.isTexture ) data.alphaMap = this.alphaMap.toJSON( meta ).uuid; - - if ( this.lightMap && this.lightMap.isTexture ) { - - data.lightMap = this.lightMap.toJSON( meta ).uuid; - data.lightMapIntensity = this.lightMapIntensity; - - } - - if ( this.aoMap && this.aoMap.isTexture ) { - - data.aoMap = this.aoMap.toJSON( meta ).uuid; - data.aoMapIntensity = this.aoMapIntensity; - - } - - if ( this.bumpMap && this.bumpMap.isTexture ) { - - data.bumpMap = this.bumpMap.toJSON( meta ).uuid; - data.bumpScale = this.bumpScale; - - } - - if ( this.normalMap && this.normalMap.isTexture ) { - - data.normalMap = this.normalMap.toJSON( meta ).uuid; - data.normalMapType = this.normalMapType; - data.normalScale = this.normalScale.toArray(); - - } - - if ( this.displacementMap && this.displacementMap.isTexture ) { - - data.displacementMap = this.displacementMap.toJSON( meta ).uuid; - data.displacementScale = this.displacementScale; - data.displacementBias = this.displacementBias; - - } - - if ( this.roughnessMap && this.roughnessMap.isTexture ) data.roughnessMap = this.roughnessMap.toJSON( meta ).uuid; - if ( this.metalnessMap && this.metalnessMap.isTexture ) data.metalnessMap = this.metalnessMap.toJSON( meta ).uuid; - - if ( this.emissiveMap && this.emissiveMap.isTexture ) data.emissiveMap = this.emissiveMap.toJSON( meta ).uuid; - if ( this.specularMap && this.specularMap.isTexture ) data.specularMap = this.specularMap.toJSON( meta ).uuid; - if ( this.specularIntensityMap && this.specularIntensityMap.isTexture ) data.specularIntensityMap = this.specularIntensityMap.toJSON( meta ).uuid; - if ( this.specularColorMap && this.specularColorMap.isTexture ) data.specularColorMap = this.specularColorMap.toJSON( meta ).uuid; - - if ( this.envMap && this.envMap.isTexture ) { - - data.envMap = this.envMap.toJSON( meta ).uuid; - - if ( this.combine !== undefined ) data.combine = this.combine; - - } - - if ( this.envMapIntensity !== undefined ) data.envMapIntensity = this.envMapIntensity; - if ( this.reflectivity !== undefined ) data.reflectivity = this.reflectivity; - if ( this.refractionRatio !== undefined ) data.refractionRatio = this.refractionRatio; - - if ( this.gradientMap && this.gradientMap.isTexture ) { - - data.gradientMap = this.gradientMap.toJSON( meta ).uuid; - - } - - if ( this.transmission !== undefined ) data.transmission = this.transmission; - if ( this.transmissionMap && this.transmissionMap.isTexture ) data.transmissionMap = this.transmissionMap.toJSON( meta ).uuid; - if ( this.thickness !== undefined ) data.thickness = this.thickness; - if ( this.thicknessMap && this.thicknessMap.isTexture ) data.thicknessMap = this.thicknessMap.toJSON( meta ).uuid; - if ( this.attenuationDistance !== undefined && this.attenuationDistance !== Infinity ) data.attenuationDistance = this.attenuationDistance; - if ( this.attenuationColor !== undefined ) data.attenuationColor = this.attenuationColor.getHex(); - - if ( this.size !== undefined ) data.size = this.size; - if ( this.shadowSide !== null ) data.shadowSide = this.shadowSide; - if ( this.sizeAttenuation !== undefined ) data.sizeAttenuation = this.sizeAttenuation; - - if ( this.blending !== NormalBlending ) data.blending = this.blending; - if ( this.side !== FrontSide ) data.side = this.side; - if ( this.vertexColors ) data.vertexColors = true; - - if ( this.opacity < 1 ) data.opacity = this.opacity; - if ( this.transparent === true ) data.transparent = this.transparent; - - data.depthFunc = this.depthFunc; - data.depthTest = this.depthTest; - data.depthWrite = this.depthWrite; - data.colorWrite = this.colorWrite; - - data.stencilWrite = this.stencilWrite; - data.stencilWriteMask = this.stencilWriteMask; - data.stencilFunc = this.stencilFunc; - data.stencilRef = this.stencilRef; - data.stencilFuncMask = this.stencilFuncMask; - data.stencilFail = this.stencilFail; - data.stencilZFail = this.stencilZFail; - data.stencilZPass = this.stencilZPass; - - // rotation (SpriteMaterial) - if ( this.rotation !== undefined && this.rotation !== 0 ) data.rotation = this.rotation; - - if ( this.polygonOffset === true ) data.polygonOffset = true; - if ( this.polygonOffsetFactor !== 0 ) data.polygonOffsetFactor = this.polygonOffsetFactor; - if ( this.polygonOffsetUnits !== 0 ) data.polygonOffsetUnits = this.polygonOffsetUnits; - - if ( this.linewidth !== undefined && this.linewidth !== 1 ) data.linewidth = this.linewidth; - if ( this.dashSize !== undefined ) data.dashSize = this.dashSize; - if ( this.gapSize !== undefined ) data.gapSize = this.gapSize; - if ( this.scale !== undefined ) data.scale = this.scale; - - if ( this.dithering === true ) data.dithering = true; - - if ( this.alphaTest > 0 ) data.alphaTest = this.alphaTest; - if ( this.alphaToCoverage === true ) data.alphaToCoverage = this.alphaToCoverage; - if ( this.premultipliedAlpha === true ) data.premultipliedAlpha = this.premultipliedAlpha; - if ( this.forceSinglePass === true ) data.forceSinglePass = this.forceSinglePass; - - if ( this.wireframe === true ) data.wireframe = this.wireframe; - if ( this.wireframeLinewidth > 1 ) data.wireframeLinewidth = this.wireframeLinewidth; - if ( this.wireframeLinecap !== 'round' ) data.wireframeLinecap = this.wireframeLinecap; - if ( this.wireframeLinejoin !== 'round' ) data.wireframeLinejoin = this.wireframeLinejoin; - - if ( this.flatShading === true ) data.flatShading = this.flatShading; - - if ( this.visible === false ) data.visible = false; - - if ( this.toneMapped === false ) data.toneMapped = false; - - if ( this.fog === false ) data.fog = false; - - if ( Object.keys( this.userData ).length > 0 ) data.userData = this.userData; - - // TODO: Copied from Object3D.toJSON - - function extractFromCache( cache ) { - - const values = []; - - for ( const key in cache ) { - - const data = cache[ key ]; - delete data.metadata; - values.push( data ); - - } - - return values; - - } - - if ( isRootObject ) { - - const textures = extractFromCache( meta.textures ); - const images = extractFromCache( meta.images ); - - if ( textures.length > 0 ) data.textures = textures; - if ( images.length > 0 ) data.images = images; - - } - - return data; - - } - - clone() { - - return new this.constructor().copy( this ); - - } - - copy( source ) { - - this.name = source.name; - - this.blending = source.blending; - this.side = source.side; - this.vertexColors = source.vertexColors; - - this.opacity = source.opacity; - this.transparent = source.transparent; - - this.blendSrc = source.blendSrc; - this.blendDst = source.blendDst; - this.blendEquation = source.blendEquation; - this.blendSrcAlpha = source.blendSrcAlpha; - this.blendDstAlpha = source.blendDstAlpha; - this.blendEquationAlpha = source.blendEquationAlpha; - - this.depthFunc = source.depthFunc; - this.depthTest = source.depthTest; - this.depthWrite = source.depthWrite; - - this.stencilWriteMask = source.stencilWriteMask; - this.stencilFunc = source.stencilFunc; - this.stencilRef = source.stencilRef; - this.stencilFuncMask = source.stencilFuncMask; - this.stencilFail = source.stencilFail; - this.stencilZFail = source.stencilZFail; - this.stencilZPass = source.stencilZPass; - this.stencilWrite = source.stencilWrite; - - const srcPlanes = source.clippingPlanes; - let dstPlanes = null; - - if ( srcPlanes !== null ) { - - const n = srcPlanes.length; - dstPlanes = new Array( n ); - - for ( let i = 0; i !== n; ++ i ) { - - dstPlanes[ i ] = srcPlanes[ i ].clone(); - - } - - } - - this.clippingPlanes = dstPlanes; - this.clipIntersection = source.clipIntersection; - this.clipShadows = source.clipShadows; - - this.shadowSide = source.shadowSide; - - this.colorWrite = source.colorWrite; - - this.precision = source.precision; - - this.polygonOffset = source.polygonOffset; - this.polygonOffsetFactor = source.polygonOffsetFactor; - this.polygonOffsetUnits = source.polygonOffsetUnits; - - this.dithering = source.dithering; - - this.alphaTest = source.alphaTest; - this.alphaToCoverage = source.alphaToCoverage; - this.premultipliedAlpha = source.premultipliedAlpha; - this.forceSinglePass = source.forceSinglePass; - - this.visible = source.visible; - - this.toneMapped = source.toneMapped; - - this.userData = JSON.parse( JSON.stringify( source.userData ) ); - - return this; - - } - - dispose() { - - this.dispatchEvent( { type: 'dispose' } ); - - } - - set needsUpdate( value ) { - - if ( value === true ) this.version ++; - - } - - } - - const _colorKeywords = { 'aliceblue': 0xF0F8FF, 'antiquewhite': 0xFAEBD7, 'aqua': 0x00FFFF, 'aquamarine': 0x7FFFD4, 'azure': 0xF0FFFF, - 'beige': 0xF5F5DC, 'bisque': 0xFFE4C4, 'black': 0x000000, 'blanchedalmond': 0xFFEBCD, 'blue': 0x0000FF, 'blueviolet': 0x8A2BE2, - 'brown': 0xA52A2A, 'burlywood': 0xDEB887, 'cadetblue': 0x5F9EA0, 'chartreuse': 0x7FFF00, 'chocolate': 0xD2691E, 'coral': 0xFF7F50, - 'cornflowerblue': 0x6495ED, 'cornsilk': 0xFFF8DC, 'crimson': 0xDC143C, 'cyan': 0x00FFFF, 'darkblue': 0x00008B, 'darkcyan': 0x008B8B, - 'darkgoldenrod': 0xB8860B, 'darkgray': 0xA9A9A9, 'darkgreen': 0x006400, 'darkgrey': 0xA9A9A9, 'darkkhaki': 0xBDB76B, 'darkmagenta': 0x8B008B, - 'darkolivegreen': 0x556B2F, 'darkorange': 0xFF8C00, 'darkorchid': 0x9932CC, 'darkred': 0x8B0000, 'darksalmon': 0xE9967A, 'darkseagreen': 0x8FBC8F, - 'darkslateblue': 0x483D8B, 'darkslategray': 0x2F4F4F, 'darkslategrey': 0x2F4F4F, 'darkturquoise': 0x00CED1, 'darkviolet': 0x9400D3, - 'deeppink': 0xFF1493, 'deepskyblue': 0x00BFFF, 'dimgray': 0x696969, 'dimgrey': 0x696969, 'dodgerblue': 0x1E90FF, 'firebrick': 0xB22222, - 'floralwhite': 0xFFFAF0, 'forestgreen': 0x228B22, 'fuchsia': 0xFF00FF, 'gainsboro': 0xDCDCDC, 'ghostwhite': 0xF8F8FF, 'gold': 0xFFD700, - 'goldenrod': 0xDAA520, 'gray': 0x808080, 'green': 0x008000, 'greenyellow': 0xADFF2F, 'grey': 0x808080, 'honeydew': 0xF0FFF0, 'hotpink': 0xFF69B4, - 'indianred': 0xCD5C5C, 'indigo': 0x4B0082, 'ivory': 0xFFFFF0, 'khaki': 0xF0E68C, 'lavender': 0xE6E6FA, 'lavenderblush': 0xFFF0F5, 'lawngreen': 0x7CFC00, - 'lemonchiffon': 0xFFFACD, 'lightblue': 0xADD8E6, 'lightcoral': 0xF08080, 'lightcyan': 0xE0FFFF, 'lightgoldenrodyellow': 0xFAFAD2, 'lightgray': 0xD3D3D3, - 'lightgreen': 0x90EE90, 'lightgrey': 0xD3D3D3, 'lightpink': 0xFFB6C1, 'lightsalmon': 0xFFA07A, 'lightseagreen': 0x20B2AA, 'lightskyblue': 0x87CEFA, - 'lightslategray': 0x778899, 'lightslategrey': 0x778899, 'lightsteelblue': 0xB0C4DE, 'lightyellow': 0xFFFFE0, 'lime': 0x00FF00, 'limegreen': 0x32CD32, - 'linen': 0xFAF0E6, 'magenta': 0xFF00FF, 'maroon': 0x800000, 'mediumaquamarine': 0x66CDAA, 'mediumblue': 0x0000CD, 'mediumorchid': 0xBA55D3, - 'mediumpurple': 0x9370DB, 'mediumseagreen': 0x3CB371, 'mediumslateblue': 0x7B68EE, 'mediumspringgreen': 0x00FA9A, 'mediumturquoise': 0x48D1CC, - 'mediumvioletred': 0xC71585, 'midnightblue': 0x191970, 'mintcream': 0xF5FFFA, 'mistyrose': 0xFFE4E1, 'moccasin': 0xFFE4B5, 'navajowhite': 0xFFDEAD, - 'navy': 0x000080, 'oldlace': 0xFDF5E6, 'olive': 0x808000, 'olivedrab': 0x6B8E23, 'orange': 0xFFA500, 'orangered': 0xFF4500, 'orchid': 0xDA70D6, - 'palegoldenrod': 0xEEE8AA, 'palegreen': 0x98FB98, 'paleturquoise': 0xAFEEEE, 'palevioletred': 0xDB7093, 'papayawhip': 0xFFEFD5, 'peachpuff': 0xFFDAB9, - 'peru': 0xCD853F, 'pink': 0xFFC0CB, 'plum': 0xDDA0DD, 'powderblue': 0xB0E0E6, 'purple': 0x800080, 'rebeccapurple': 0x663399, 'red': 0xFF0000, 'rosybrown': 0xBC8F8F, - 'royalblue': 0x4169E1, 'saddlebrown': 0x8B4513, 'salmon': 0xFA8072, 'sandybrown': 0xF4A460, 'seagreen': 0x2E8B57, 'seashell': 0xFFF5EE, - 'sienna': 0xA0522D, 'silver': 0xC0C0C0, 'skyblue': 0x87CEEB, 'slateblue': 0x6A5ACD, 'slategray': 0x708090, 'slategrey': 0x708090, 'snow': 0xFFFAFA, - 'springgreen': 0x00FF7F, 'steelblue': 0x4682B4, 'tan': 0xD2B48C, 'teal': 0x008080, 'thistle': 0xD8BFD8, 'tomato': 0xFF6347, 'turquoise': 0x40E0D0, - 'violet': 0xEE82EE, 'wheat': 0xF5DEB3, 'white': 0xFFFFFF, 'whitesmoke': 0xF5F5F5, 'yellow': 0xFFFF00, 'yellowgreen': 0x9ACD32 }; - - const _hslA = { h: 0, s: 0, l: 0 }; - const _hslB = { h: 0, s: 0, l: 0 }; - - function hue2rgb( p, q, t ) { - - if ( t < 0 ) t += 1; - if ( t > 1 ) t -= 1; - if ( t < 1 / 6 ) return p + ( q - p ) * 6 * t; - if ( t < 1 / 2 ) return q; - if ( t < 2 / 3 ) return p + ( q - p ) * 6 * ( 2 / 3 - t ); - return p; - - } - - class Color { - - constructor( r, g, b ) { - - this.isColor = true; - - this.r = 1; - this.g = 1; - this.b = 1; - - return this.set( r, g, b ); - - } - - set( r, g, b ) { - - if ( g === undefined && b === undefined ) { - - // r is THREE.Color, hex or string - - const value = r; - - if ( value && value.isColor ) { - - this.copy( value ); - - } else if ( typeof value === 'number' ) { - - this.setHex( value ); - - } else if ( typeof value === 'string' ) { - - this.setStyle( value ); - - } - - } else { - - this.setRGB( r, g, b ); - - } - - return this; - - } - - setScalar( scalar ) { - - this.r = scalar; - this.g = scalar; - this.b = scalar; - - return this; - - } - - setHex( hex, colorSpace = SRGBColorSpace ) { - - hex = Math.floor( hex ); - - this.r = ( hex >> 16 & 255 ) / 255; - this.g = ( hex >> 8 & 255 ) / 255; - this.b = ( hex & 255 ) / 255; - - ColorManagement.toWorkingColorSpace( this, colorSpace ); - - return this; - - } - - setRGB( r, g, b, colorSpace = ColorManagement.workingColorSpace ) { - - this.r = r; - this.g = g; - this.b = b; - - ColorManagement.toWorkingColorSpace( this, colorSpace ); - - return this; - - } - - setHSL( h, s, l, colorSpace = ColorManagement.workingColorSpace ) { - - // h,s,l ranges are in 0.0 - 1.0 - h = euclideanModulo( h, 1 ); - s = clamp( s, 0, 1 ); - l = clamp( l, 0, 1 ); - - if ( s === 0 ) { - - this.r = this.g = this.b = l; - - } else { - - const p = l <= 0.5 ? l * ( 1 + s ) : l + s - ( l * s ); - const q = ( 2 * l ) - p; - - this.r = hue2rgb( q, p, h + 1 / 3 ); - this.g = hue2rgb( q, p, h ); - this.b = hue2rgb( q, p, h - 1 / 3 ); - - } - - ColorManagement.toWorkingColorSpace( this, colorSpace ); - - return this; - - } - - setStyle( style, colorSpace = SRGBColorSpace ) { - - function handleAlpha( string ) { - - if ( string === undefined ) return; - - if ( parseFloat( string ) < 1 ) { - - console.warn( 'THREE.Color: Alpha component of ' + style + ' will be ignored.' ); - - } - - } - - - let m; - - if ( m = /^(\w+)\(([^\)]*)\)/.exec( style ) ) { - - // rgb / hsl - - let color; - const name = m[ 1 ]; - const components = m[ 2 ]; - - switch ( name ) { - - case 'rgb': - case 'rgba': - - if ( color = /^\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) { - - // rgb(255,0,0) rgba(255,0,0,0.5) - - handleAlpha( color[ 4 ] ); - - return this.setRGB( - Math.min( 255, parseInt( color[ 1 ], 10 ) ) / 255, - Math.min( 255, parseInt( color[ 2 ], 10 ) ) / 255, - Math.min( 255, parseInt( color[ 3 ], 10 ) ) / 255, - colorSpace - ); - - } - - if ( color = /^\s*(\d+)\%\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) { - - // rgb(100%,0%,0%) rgba(100%,0%,0%,0.5) - - handleAlpha( color[ 4 ] ); - - return this.setRGB( - Math.min( 100, parseInt( color[ 1 ], 10 ) ) / 100, - Math.min( 100, parseInt( color[ 2 ], 10 ) ) / 100, - Math.min( 100, parseInt( color[ 3 ], 10 ) ) / 100, - colorSpace - ); - - } - - break; - - case 'hsl': - case 'hsla': - - if ( color = /^\s*(\d*\.?\d+)\s*,\s*(\d*\.?\d+)\%\s*,\s*(\d*\.?\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) { - - // hsl(120,50%,50%) hsla(120,50%,50%,0.5) - - handleAlpha( color[ 4 ] ); - - return this.setHSL( - parseFloat( color[ 1 ] ) / 360, - parseFloat( color[ 2 ] ) / 100, - parseFloat( color[ 3 ] ) / 100, - colorSpace - ); - - } - - break; - - default: - - console.warn( 'THREE.Color: Unknown color model ' + style ); - - } - - } else if ( m = /^\#([A-Fa-f\d]+)$/.exec( style ) ) { - - // hex color - - const hex = m[ 1 ]; - const size = hex.length; - - if ( size === 3 ) { - - // #ff0 - return this.setRGB( - parseInt( hex.charAt( 0 ), 16 ) / 15, - parseInt( hex.charAt( 1 ), 16 ) / 15, - parseInt( hex.charAt( 2 ), 16 ) / 15, - colorSpace - ); - - } else if ( size === 6 ) { - - // #ff0000 - return this.setHex( parseInt( hex, 16 ), colorSpace ); - - } else { - - console.warn( 'THREE.Color: Invalid hex color ' + style ); - - } - - } else if ( style && style.length > 0 ) { - - return this.setColorName( style, colorSpace ); - - } - - return this; - - } - - setColorName( style, colorSpace = SRGBColorSpace ) { - - // color keywords - const hex = _colorKeywords[ style.toLowerCase() ]; - - if ( hex !== undefined ) { - - // red - this.setHex( hex, colorSpace ); - - } else { - - // unknown color - console.warn( 'THREE.Color: Unknown color ' + style ); - - } - - return this; - - } - - clone() { - - return new this.constructor( this.r, this.g, this.b ); - - } - - copy( color ) { - - this.r = color.r; - this.g = color.g; - this.b = color.b; - - return this; - - } - - copySRGBToLinear( color ) { - - this.r = SRGBToLinear( color.r ); - this.g = SRGBToLinear( color.g ); - this.b = SRGBToLinear( color.b ); - - return this; - - } - - copyLinearToSRGB( color ) { - - this.r = LinearToSRGB( color.r ); - this.g = LinearToSRGB( color.g ); - this.b = LinearToSRGB( color.b ); - - return this; - - } - - convertSRGBToLinear() { - - this.copySRGBToLinear( this ); - - return this; - - } - - convertLinearToSRGB() { - - this.copyLinearToSRGB( this ); - - return this; - - } - - getHex( colorSpace = SRGBColorSpace ) { - - ColorManagement.fromWorkingColorSpace( _color.copy( this ), colorSpace ); - - return Math.round( clamp( _color.r * 255, 0, 255 ) ) * 65536 + Math.round( clamp( _color.g * 255, 0, 255 ) ) * 256 + Math.round( clamp( _color.b * 255, 0, 255 ) ); - - } - - getHexString( colorSpace = SRGBColorSpace ) { - - return ( '000000' + this.getHex( colorSpace ).toString( 16 ) ).slice( - 6 ); - - } - - getHSL( target, colorSpace = ColorManagement.workingColorSpace ) { - - // h,s,l ranges are in 0.0 - 1.0 - - ColorManagement.fromWorkingColorSpace( _color.copy( this ), colorSpace ); - - const r = _color.r, g = _color.g, b = _color.b; - - const max = Math.max( r, g, b ); - const min = Math.min( r, g, b ); - - let hue, saturation; - const lightness = ( min + max ) / 2.0; - - if ( min === max ) { - - hue = 0; - saturation = 0; - - } else { - - const delta = max - min; - - saturation = lightness <= 0.5 ? delta / ( max + min ) : delta / ( 2 - max - min ); - - switch ( max ) { - - case r: hue = ( g - b ) / delta + ( g < b ? 6 : 0 ); break; - case g: hue = ( b - r ) / delta + 2; break; - case b: hue = ( r - g ) / delta + 4; break; - - } - - hue /= 6; - - } - - target.h = hue; - target.s = saturation; - target.l = lightness; - - return target; - - } - - getRGB( target, colorSpace = ColorManagement.workingColorSpace ) { - - ColorManagement.fromWorkingColorSpace( _color.copy( this ), colorSpace ); - - target.r = _color.r; - target.g = _color.g; - target.b = _color.b; - - return target; - - } - - getStyle( colorSpace = SRGBColorSpace ) { - - ColorManagement.fromWorkingColorSpace( _color.copy( this ), colorSpace ); - - const r = _color.r, g = _color.g, b = _color.b; - - if ( colorSpace !== SRGBColorSpace ) { - - // Requires CSS Color Module Level 4 (https://www.w3.org/TR/css-color-4/). - return `color(${ colorSpace } ${ r.toFixed( 3 ) } ${ g.toFixed( 3 ) } ${ b.toFixed( 3 ) })`; - - } - - return `rgb(${ Math.round( r * 255 ) },${ Math.round( g * 255 ) },${ Math.round( b * 255 ) })`; - - } - - offsetHSL( h, s, l ) { - - this.getHSL( _hslA ); - - _hslA.h += h; _hslA.s += s; _hslA.l += l; - - this.setHSL( _hslA.h, _hslA.s, _hslA.l ); - - return this; - - } - - add( color ) { - - this.r += color.r; - this.g += color.g; - this.b += color.b; - - return this; - - } - - addColors( color1, color2 ) { - - this.r = color1.r + color2.r; - this.g = color1.g + color2.g; - this.b = color1.b + color2.b; - - return this; - - } - - addScalar( s ) { - - this.r += s; - this.g += s; - this.b += s; - - return this; - - } - - sub( color ) { - - this.r = Math.max( 0, this.r - color.r ); - this.g = Math.max( 0, this.g - color.g ); - this.b = Math.max( 0, this.b - color.b ); - - return this; - - } - - multiply( color ) { - - this.r *= color.r; - this.g *= color.g; - this.b *= color.b; - - return this; - - } - - multiplyScalar( s ) { - - this.r *= s; - this.g *= s; - this.b *= s; - - return this; - - } - - lerp( color, alpha ) { - - this.r += ( color.r - this.r ) * alpha; - this.g += ( color.g - this.g ) * alpha; - this.b += ( color.b - this.b ) * alpha; - - return this; - - } - - lerpColors( color1, color2, alpha ) { - - this.r = color1.r + ( color2.r - color1.r ) * alpha; - this.g = color1.g + ( color2.g - color1.g ) * alpha; - this.b = color1.b + ( color2.b - color1.b ) * alpha; - - return this; - - } - - lerpHSL( color, alpha ) { - - this.getHSL( _hslA ); - color.getHSL( _hslB ); - - const h = lerp( _hslA.h, _hslB.h, alpha ); - const s = lerp( _hslA.s, _hslB.s, alpha ); - const l = lerp( _hslA.l, _hslB.l, alpha ); - - this.setHSL( h, s, l ); - - return this; - - } - - setFromVector3( v ) { - - this.r = v.x; - this.g = v.y; - this.b = v.z; - - return this; - - } - - applyMatrix3( m ) { - - const r = this.r, g = this.g, b = this.b; - const e = m.elements; - - this.r = e[ 0 ] * r + e[ 3 ] * g + e[ 6 ] * b; - this.g = e[ 1 ] * r + e[ 4 ] * g + e[ 7 ] * b; - this.b = e[ 2 ] * r + e[ 5 ] * g + e[ 8 ] * b; - - return this; - - } - - equals( c ) { - - return ( c.r === this.r ) && ( c.g === this.g ) && ( c.b === this.b ); - - } - - fromArray( array, offset = 0 ) { - - this.r = array[ offset ]; - this.g = array[ offset + 1 ]; - this.b = array[ offset + 2 ]; - - return this; - - } - - toArray( array = [], offset = 0 ) { - - array[ offset ] = this.r; - array[ offset + 1 ] = this.g; - array[ offset + 2 ] = this.b; - - return array; - - } - - fromBufferAttribute( attribute, index ) { - - this.r = attribute.getX( index ); - this.g = attribute.getY( index ); - this.b = attribute.getZ( index ); - - return this; - - } - - toJSON() { - - return this.getHex(); - - } - - *[ Symbol.iterator ]() { - - yield this.r; - yield this.g; - yield this.b; - - } - - } - - const _color = /*@__PURE__*/ new Color(); - - Color.NAMES = _colorKeywords; - - class MeshBasicMaterial extends Material { - - constructor( parameters ) { - - super(); - - this.isMeshBasicMaterial = true; - - this.type = 'MeshBasicMaterial'; - - this.color = new Color( 0xffffff ); // emissive - - this.map = null; - - this.lightMap = null; - this.lightMapIntensity = 1.0; - - this.aoMap = null; - this.aoMapIntensity = 1.0; - - this.specularMap = null; - - this.alphaMap = null; - - this.envMap = null; - this.combine = MultiplyOperation; - this.reflectivity = 1; - this.refractionRatio = 0.98; - - this.wireframe = false; - this.wireframeLinewidth = 1; - this.wireframeLinecap = 'round'; - this.wireframeLinejoin = 'round'; - - this.fog = true; - - this.setValues( parameters ); - - } - - copy( source ) { - - super.copy( source ); - - this.color.copy( source.color ); - - this.map = source.map; - - this.lightMap = source.lightMap; - this.lightMapIntensity = source.lightMapIntensity; - - this.aoMap = source.aoMap; - this.aoMapIntensity = source.aoMapIntensity; - - this.specularMap = source.specularMap; - - this.alphaMap = source.alphaMap; - - this.envMap = source.envMap; - this.combine = source.combine; - this.reflectivity = source.reflectivity; - this.refractionRatio = source.refractionRatio; - - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; - this.wireframeLinecap = source.wireframeLinecap; - this.wireframeLinejoin = source.wireframeLinejoin; - - this.fog = source.fog; - - return this; - - } - - } - - // Fast Half Float Conversions, http://www.fox-toolkit.org/ftp/fasthalffloatconversion.pdf - - const _tables = /*@__PURE__*/ _generateTables(); - - function _generateTables() { - - // float32 to float16 helpers - - const buffer = new ArrayBuffer( 4 ); - const floatView = new Float32Array( buffer ); - const uint32View = new Uint32Array( buffer ); - - const baseTable = new Uint32Array( 512 ); - const shiftTable = new Uint32Array( 512 ); - - for ( let i = 0; i < 256; ++ i ) { - - const e = i - 127; - - // very small number (0, -0) - - if ( e < - 27 ) { - - baseTable[ i ] = 0x0000; - baseTable[ i | 0x100 ] = 0x8000; - shiftTable[ i ] = 24; - shiftTable[ i | 0x100 ] = 24; - - // small number (denorm) - - } else if ( e < - 14 ) { - - baseTable[ i ] = 0x0400 >> ( - e - 14 ); - baseTable[ i | 0x100 ] = ( 0x0400 >> ( - e - 14 ) ) | 0x8000; - shiftTable[ i ] = - e - 1; - shiftTable[ i | 0x100 ] = - e - 1; - - // normal number - - } else if ( e <= 15 ) { - - baseTable[ i ] = ( e + 15 ) << 10; - baseTable[ i | 0x100 ] = ( ( e + 15 ) << 10 ) | 0x8000; - shiftTable[ i ] = 13; - shiftTable[ i | 0x100 ] = 13; - - // large number (Infinity, -Infinity) - - } else if ( e < 128 ) { - - baseTable[ i ] = 0x7c00; - baseTable[ i | 0x100 ] = 0xfc00; - shiftTable[ i ] = 24; - shiftTable[ i | 0x100 ] = 24; - - // stay (NaN, Infinity, -Infinity) - - } else { - - baseTable[ i ] = 0x7c00; - baseTable[ i | 0x100 ] = 0xfc00; - shiftTable[ i ] = 13; - shiftTable[ i | 0x100 ] = 13; - - } - - } - - // float16 to float32 helpers - - const mantissaTable = new Uint32Array( 2048 ); - const exponentTable = new Uint32Array( 64 ); - const offsetTable = new Uint32Array( 64 ); - - for ( let i = 1; i < 1024; ++ i ) { - - let m = i << 13; // zero pad mantissa bits - let e = 0; // zero exponent - - // normalized - while ( ( m & 0x00800000 ) === 0 ) { - - m <<= 1; - e -= 0x00800000; // decrement exponent - - } - - m &= ~ 0x00800000; // clear leading 1 bit - e += 0x38800000; // adjust bias - - mantissaTable[ i ] = m | e; - - } - - for ( let i = 1024; i < 2048; ++ i ) { - - mantissaTable[ i ] = 0x38000000 + ( ( i - 1024 ) << 13 ); - - } - - for ( let i = 1; i < 31; ++ i ) { - - exponentTable[ i ] = i << 23; - - } - - exponentTable[ 31 ] = 0x47800000; - exponentTable[ 32 ] = 0x80000000; - - for ( let i = 33; i < 63; ++ i ) { - - exponentTable[ i ] = 0x80000000 + ( ( i - 32 ) << 23 ); - - } - - exponentTable[ 63 ] = 0xc7800000; - - for ( let i = 1; i < 64; ++ i ) { - - if ( i !== 32 ) { - - offsetTable[ i ] = 1024; - - } - - } - - return { - floatView: floatView, - uint32View: uint32View, - baseTable: baseTable, - shiftTable: shiftTable, - mantissaTable: mantissaTable, - exponentTable: exponentTable, - offsetTable: offsetTable - }; - - } - - // float32 to float16 - - function toHalfFloat( val ) { - - if ( Math.abs( val ) > 65504 ) console.warn( 'THREE.DataUtils.toHalfFloat(): Value out of range.' ); - - val = clamp( val, - 65504, 65504 ); - - _tables.floatView[ 0 ] = val; - const f = _tables.uint32View[ 0 ]; - const e = ( f >> 23 ) & 0x1ff; - return _tables.baseTable[ e ] + ( ( f & 0x007fffff ) >> _tables.shiftTable[ e ] ); - - } - - // float16 to float32 - - function fromHalfFloat( val ) { - - const m = val >> 10; - _tables.uint32View[ 0 ] = _tables.mantissaTable[ _tables.offsetTable[ m ] + ( val & 0x3ff ) ] + _tables.exponentTable[ m ]; - return _tables.floatView[ 0 ]; - - } - - const DataUtils = { - toHalfFloat: toHalfFloat, - fromHalfFloat: fromHalfFloat, - }; - - const _vector$8 = /*@__PURE__*/ new Vector3(); - const _vector2$1 = /*@__PURE__*/ new Vector2(); - - class BufferAttribute { - - constructor( array, itemSize, normalized = false ) { - - if ( Array.isArray( array ) ) { - - throw new TypeError( 'THREE.BufferAttribute: array should be a Typed Array.' ); - - } - - this.isBufferAttribute = true; - - this.name = ''; - - this.array = array; - this.itemSize = itemSize; - this.count = array !== undefined ? array.length / itemSize : 0; - this.normalized = normalized; - - this.usage = StaticDrawUsage; - this.updateRange = { offset: 0, count: - 1 }; - - this.version = 0; - - } - - onUploadCallback() {} - - set needsUpdate( value ) { - - if ( value === true ) this.version ++; - - } - - setUsage( value ) { - - this.usage = value; - - return this; - - } - - copy( source ) { - - this.name = source.name; - this.array = new source.array.constructor( source.array ); - this.itemSize = source.itemSize; - this.count = source.count; - this.normalized = source.normalized; - - this.usage = source.usage; - - return this; - - } - - copyAt( index1, attribute, index2 ) { - - index1 *= this.itemSize; - index2 *= attribute.itemSize; - - for ( let i = 0, l = this.itemSize; i < l; i ++ ) { - - this.array[ index1 + i ] = attribute.array[ index2 + i ]; - - } - - return this; - - } - - copyArray( array ) { - - this.array.set( array ); - - return this; - - } - - applyMatrix3( m ) { - - if ( this.itemSize === 2 ) { - - for ( let i = 0, l = this.count; i < l; i ++ ) { - - _vector2$1.fromBufferAttribute( this, i ); - _vector2$1.applyMatrix3( m ); - - this.setXY( i, _vector2$1.x, _vector2$1.y ); - - } - - } else if ( this.itemSize === 3 ) { - - for ( let i = 0, l = this.count; i < l; i ++ ) { - - _vector$8.fromBufferAttribute( this, i ); - _vector$8.applyMatrix3( m ); - - this.setXYZ( i, _vector$8.x, _vector$8.y, _vector$8.z ); - - } - - } - - return this; - - } - - applyMatrix4( m ) { - - for ( let i = 0, l = this.count; i < l; i ++ ) { - - _vector$8.fromBufferAttribute( this, i ); - - _vector$8.applyMatrix4( m ); - - this.setXYZ( i, _vector$8.x, _vector$8.y, _vector$8.z ); - - } - - return this; - - } - - applyNormalMatrix( m ) { - - for ( let i = 0, l = this.count; i < l; i ++ ) { - - _vector$8.fromBufferAttribute( this, i ); - - _vector$8.applyNormalMatrix( m ); - - this.setXYZ( i, _vector$8.x, _vector$8.y, _vector$8.z ); - - } - - return this; - - } - - transformDirection( m ) { - - for ( let i = 0, l = this.count; i < l; i ++ ) { - - _vector$8.fromBufferAttribute( this, i ); - - _vector$8.transformDirection( m ); - - this.setXYZ( i, _vector$8.x, _vector$8.y, _vector$8.z ); - - } - - return this; - - } - - set( value, offset = 0 ) { - - // Matching BufferAttribute constructor, do not normalize the array. - this.array.set( value, offset ); - - return this; - - } - - getX( index ) { - - let x = this.array[ index * this.itemSize ]; - - if ( this.normalized ) x = denormalize( x, this.array ); - - return x; - - } - - setX( index, x ) { - - if ( this.normalized ) x = normalize( x, this.array ); - - this.array[ index * this.itemSize ] = x; - - return this; - - } - - getY( index ) { - - let y = this.array[ index * this.itemSize + 1 ]; - - if ( this.normalized ) y = denormalize( y, this.array ); - - return y; - - } - - setY( index, y ) { - - if ( this.normalized ) y = normalize( y, this.array ); - - this.array[ index * this.itemSize + 1 ] = y; - - return this; - - } - - getZ( index ) { - - let z = this.array[ index * this.itemSize + 2 ]; - - if ( this.normalized ) z = denormalize( z, this.array ); - - return z; - - } - - setZ( index, z ) { - - if ( this.normalized ) z = normalize( z, this.array ); - - this.array[ index * this.itemSize + 2 ] = z; - - return this; - - } - - getW( index ) { - - let w = this.array[ index * this.itemSize + 3 ]; - - if ( this.normalized ) w = denormalize( w, this.array ); - - return w; - - } - - setW( index, w ) { - - if ( this.normalized ) w = normalize( w, this.array ); - - this.array[ index * this.itemSize + 3 ] = w; - - return this; - - } - - setXY( index, x, y ) { - - index *= this.itemSize; - - if ( this.normalized ) { - - x = normalize( x, this.array ); - y = normalize( y, this.array ); - - } - - this.array[ index + 0 ] = x; - this.array[ index + 1 ] = y; - - return this; - - } - - setXYZ( index, x, y, z ) { - - index *= this.itemSize; - - if ( this.normalized ) { - - x = normalize( x, this.array ); - y = normalize( y, this.array ); - z = normalize( z, this.array ); - - } - - this.array[ index + 0 ] = x; - this.array[ index + 1 ] = y; - this.array[ index + 2 ] = z; - - return this; - - } - - setXYZW( index, x, y, z, w ) { - - index *= this.itemSize; - - if ( this.normalized ) { - - x = normalize( x, this.array ); - y = normalize( y, this.array ); - z = normalize( z, this.array ); - w = normalize( w, this.array ); - - } - - this.array[ index + 0 ] = x; - this.array[ index + 1 ] = y; - this.array[ index + 2 ] = z; - this.array[ index + 3 ] = w; - - return this; - - } - - onUpload( callback ) { - - this.onUploadCallback = callback; - - return this; - - } - - clone() { - - return new this.constructor( this.array, this.itemSize ).copy( this ); - - } - - toJSON() { - - const data = { - itemSize: this.itemSize, - type: this.array.constructor.name, - array: Array.from( this.array ), - normalized: this.normalized - }; - - if ( this.name !== '' ) data.name = this.name; - if ( this.usage !== StaticDrawUsage ) data.usage = this.usage; - if ( this.updateRange.offset !== 0 || this.updateRange.count !== - 1 ) data.updateRange = this.updateRange; - - return data; - - } - - copyColorsArray() { // @deprecated, r144 - - console.error( 'THREE.BufferAttribute: copyColorsArray() was removed in r144.' ); - - } - - copyVector2sArray() { // @deprecated, r144 - - console.error( 'THREE.BufferAttribute: copyVector2sArray() was removed in r144.' ); - - } - - copyVector3sArray() { // @deprecated, r144 - - console.error( 'THREE.BufferAttribute: copyVector3sArray() was removed in r144.' ); - - } - - copyVector4sArray() { // @deprecated, r144 - - console.error( 'THREE.BufferAttribute: copyVector4sArray() was removed in r144.' ); - - } - - } - - // - - class Int8BufferAttribute extends BufferAttribute { - - constructor( array, itemSize, normalized ) { - - super( new Int8Array( array ), itemSize, normalized ); - - this.gpuType = FloatType; - - } - - copy( source ) { - - super.copy( source ); - - this.gpuType = source.gpuType; - - return this; - - } - - } - - class Uint8BufferAttribute extends BufferAttribute { - - constructor( array, itemSize, normalized ) { - - super( new Uint8Array( array ), itemSize, normalized ); - - this.gpuType = FloatType; - - } - - copy( source ) { - - super.copy( source ); - - this.gpuType = source.gpuType; - - return this; - - } - - } - - class Uint8ClampedBufferAttribute extends BufferAttribute { - - constructor( array, itemSize, normalized ) { - - super( new Uint8ClampedArray( array ), itemSize, normalized ); - - } - - } - - class Int16BufferAttribute extends BufferAttribute { - - constructor( array, itemSize, normalized ) { - - super( new Int16Array( array ), itemSize, normalized ); - - this.gpuType = FloatType; - - } - - copy( source ) { - - super.copy( source ); - - this.gpuType = source.gpuType; - - return this; - - } - - } - - class Uint16BufferAttribute extends BufferAttribute { - - constructor( array, itemSize, normalized ) { - - super( new Uint16Array( array ), itemSize, normalized ); - - this.gpuType = FloatType; - - } - - copy( source ) { - - super.copy( source ); - - this.gpuType = source.gpuType; - - return this; - - } - - } - - class Int32BufferAttribute extends BufferAttribute { - - constructor( array, itemSize, normalized ) { - - super( new Int32Array( array ), itemSize, normalized ); - - } - - } - - class Uint32BufferAttribute extends BufferAttribute { - - constructor( array, itemSize, normalized ) { - - super( new Uint32Array( array ), itemSize, normalized ); - - } - - } - - class Float16BufferAttribute extends BufferAttribute { - - constructor( array, itemSize, normalized ) { - - super( new Uint16Array( array ), itemSize, normalized ); - - this.isFloat16BufferAttribute = true; - - } - - getX( index ) { - - let x = fromHalfFloat( this.array[ index * this.itemSize ] ); - - if ( this.normalized ) x = denormalize( x, this.array ); - - return x; - - } - - setX( index, x ) { - - if ( this.normalized ) x = normalize( x, this.array ); - - this.array[ index * this.itemSize ] = toHalfFloat( x ); - - return this; - - } - - getY( index ) { - - let y = fromHalfFloat( this.array[ index * this.itemSize + 1 ] ); - - if ( this.normalized ) y = denormalize( y, this.array ); - - return y; - - } - - setY( index, y ) { - - if ( this.normalized ) y = normalize( y, this.array ); - - this.array[ index * this.itemSize + 1 ] = toHalfFloat( y ); - - return this; - - } - - getZ( index ) { - - let z = fromHalfFloat( this.array[ index * this.itemSize + 2 ] ); - - if ( this.normalized ) z = denormalize( z, this.array ); - - return z; - - } - - setZ( index, z ) { - - if ( this.normalized ) z = normalize( z, this.array ); - - this.array[ index * this.itemSize + 2 ] = toHalfFloat( z ); - - return this; - - } - - getW( index ) { - - let w = fromHalfFloat( this.array[ index * this.itemSize + 3 ] ); - - if ( this.normalized ) w = denormalize( w, this.array ); - - return w; - - } - - setW( index, w ) { - - if ( this.normalized ) w = normalize( w, this.array ); - - this.array[ index * this.itemSize + 3 ] = toHalfFloat( w ); - - return this; - - } - - setXY( index, x, y ) { - - index *= this.itemSize; - - if ( this.normalized ) { - - x = normalize( x, this.array ); - y = normalize( y, this.array ); - - } - - this.array[ index + 0 ] = toHalfFloat( x ); - this.array[ index + 1 ] = toHalfFloat( y ); - - return this; - - } - - setXYZ( index, x, y, z ) { - - index *= this.itemSize; - - if ( this.normalized ) { - - x = normalize( x, this.array ); - y = normalize( y, this.array ); - z = normalize( z, this.array ); - - } - - this.array[ index + 0 ] = toHalfFloat( x ); - this.array[ index + 1 ] = toHalfFloat( y ); - this.array[ index + 2 ] = toHalfFloat( z ); - - return this; - - } - - setXYZW( index, x, y, z, w ) { - - index *= this.itemSize; - - if ( this.normalized ) { - - x = normalize( x, this.array ); - y = normalize( y, this.array ); - z = normalize( z, this.array ); - w = normalize( w, this.array ); - - } - - this.array[ index + 0 ] = toHalfFloat( x ); - this.array[ index + 1 ] = toHalfFloat( y ); - this.array[ index + 2 ] = toHalfFloat( z ); - this.array[ index + 3 ] = toHalfFloat( w ); - - return this; - - } - - } - - - class Float32BufferAttribute extends BufferAttribute { - - constructor( array, itemSize, normalized ) { - - super( new Float32Array( array ), itemSize, normalized ); - - } - - } - - class Float64BufferAttribute extends BufferAttribute { - - constructor( array, itemSize, normalized ) { - - super( new Float64Array( array ), itemSize, normalized ); - - } - - } - - let _id$1 = 0; - - const _m1 = /*@__PURE__*/ new Matrix4(); - const _obj = /*@__PURE__*/ new Object3D(); - const _offset = /*@__PURE__*/ new Vector3(); - const _box$1 = /*@__PURE__*/ new Box3(); - const _boxMorphTargets = /*@__PURE__*/ new Box3(); - const _vector$7 = /*@__PURE__*/ new Vector3(); - - class BufferGeometry extends EventDispatcher { - - constructor() { - - super(); - - this.isBufferGeometry = true; - - Object.defineProperty( this, 'id', { value: _id$1 ++ } ); - - this.uuid = generateUUID(); - - this.name = ''; - this.type = 'BufferGeometry'; - - this.index = null; - this.attributes = {}; - - this.morphAttributes = {}; - this.morphTargetsRelative = false; - - this.groups = []; - - this.boundingBox = null; - this.boundingSphere = null; - - this.drawRange = { start: 0, count: Infinity }; - - this.userData = {}; - - } - - getIndex() { - - return this.index; - - } - - setIndex( index ) { - - if ( Array.isArray( index ) ) { - - this.index = new ( arrayNeedsUint32( index ) ? Uint32BufferAttribute : Uint16BufferAttribute )( index, 1 ); - - } else { - - this.index = index; - - } - - return this; - - } - - getAttribute( name ) { - - return this.attributes[ name ]; - - } - - setAttribute( name, attribute ) { - - this.attributes[ name ] = attribute; - - return this; - - } - - deleteAttribute( name ) { - - delete this.attributes[ name ]; - - return this; - - } - - hasAttribute( name ) { - - return this.attributes[ name ] !== undefined; - - } - - addGroup( start, count, materialIndex = 0 ) { - - this.groups.push( { - - start: start, - count: count, - materialIndex: materialIndex - - } ); - - } - - clearGroups() { - - this.groups = []; - - } - - setDrawRange( start, count ) { - - this.drawRange.start = start; - this.drawRange.count = count; - - } - - applyMatrix4( matrix ) { - - const position = this.attributes.position; - - if ( position !== undefined ) { - - position.applyMatrix4( matrix ); - - position.needsUpdate = true; - - } - - const normal = this.attributes.normal; - - if ( normal !== undefined ) { - - const normalMatrix = new Matrix3().getNormalMatrix( matrix ); - - normal.applyNormalMatrix( normalMatrix ); - - normal.needsUpdate = true; - - } - - const tangent = this.attributes.tangent; - - if ( tangent !== undefined ) { - - tangent.transformDirection( matrix ); - - tangent.needsUpdate = true; - - } - - if ( this.boundingBox !== null ) { - - this.computeBoundingBox(); - - } - - if ( this.boundingSphere !== null ) { - - this.computeBoundingSphere(); - - } - - return this; - - } - - applyQuaternion( q ) { - - _m1.makeRotationFromQuaternion( q ); - - this.applyMatrix4( _m1 ); - - return this; - - } - - rotateX( angle ) { - - // rotate geometry around world x-axis - - _m1.makeRotationX( angle ); - - this.applyMatrix4( _m1 ); - - return this; - - } - - rotateY( angle ) { - - // rotate geometry around world y-axis - - _m1.makeRotationY( angle ); - - this.applyMatrix4( _m1 ); - - return this; - - } - - rotateZ( angle ) { - - // rotate geometry around world z-axis - - _m1.makeRotationZ( angle ); - - this.applyMatrix4( _m1 ); - - return this; - - } - - translate( x, y, z ) { - - // translate geometry - - _m1.makeTranslation( x, y, z ); - - this.applyMatrix4( _m1 ); - - return this; - - } - - scale( x, y, z ) { - - // scale geometry - - _m1.makeScale( x, y, z ); - - this.applyMatrix4( _m1 ); - - return this; - - } - - lookAt( vector ) { - - _obj.lookAt( vector ); - - _obj.updateMatrix(); - - this.applyMatrix4( _obj.matrix ); - - return this; - - } - - center() { - - this.computeBoundingBox(); - - this.boundingBox.getCenter( _offset ).negate(); - - this.translate( _offset.x, _offset.y, _offset.z ); - - return this; - - } - - setFromPoints( points ) { - - const position = []; - - for ( let i = 0, l = points.length; i < l; i ++ ) { - - const point = points[ i ]; - position.push( point.x, point.y, point.z || 0 ); - - } - - this.setAttribute( 'position', new Float32BufferAttribute( position, 3 ) ); - - return this; - - } - - computeBoundingBox() { - - if ( this.boundingBox === null ) { - - this.boundingBox = new Box3(); - - } - - const position = this.attributes.position; - const morphAttributesPosition = this.morphAttributes.position; - - if ( position && position.isGLBufferAttribute ) { - - console.error( 'THREE.BufferGeometry.computeBoundingBox(): GLBufferAttribute requires a manual bounding box. Alternatively set "mesh.frustumCulled" to "false".', this ); - - this.boundingBox.set( - new Vector3( - Infinity, - Infinity, - Infinity ), - new Vector3( + Infinity, + Infinity, + Infinity ) - ); - - return; - - } - - if ( position !== undefined ) { - - this.boundingBox.setFromBufferAttribute( position ); - - // process morph attributes if present - - if ( morphAttributesPosition ) { - - for ( let i = 0, il = morphAttributesPosition.length; i < il; i ++ ) { - - const morphAttribute = morphAttributesPosition[ i ]; - _box$1.setFromBufferAttribute( morphAttribute ); - - if ( this.morphTargetsRelative ) { - - _vector$7.addVectors( this.boundingBox.min, _box$1.min ); - this.boundingBox.expandByPoint( _vector$7 ); - - _vector$7.addVectors( this.boundingBox.max, _box$1.max ); - this.boundingBox.expandByPoint( _vector$7 ); - - } else { - - this.boundingBox.expandByPoint( _box$1.min ); - this.boundingBox.expandByPoint( _box$1.max ); - - } - - } - - } - - } else { - - this.boundingBox.makeEmpty(); - - } - - if ( isNaN( this.boundingBox.min.x ) || isNaN( this.boundingBox.min.y ) || isNaN( this.boundingBox.min.z ) ) { - - console.error( 'THREE.BufferGeometry.computeBoundingBox(): Computed min/max have NaN values. The "position" attribute is likely to have NaN values.', this ); - - } - - } - - computeBoundingSphere() { - - if ( this.boundingSphere === null ) { - - this.boundingSphere = new Sphere(); - - } - - const position = this.attributes.position; - const morphAttributesPosition = this.morphAttributes.position; - - if ( position && position.isGLBufferAttribute ) { - - console.error( 'THREE.BufferGeometry.computeBoundingSphere(): GLBufferAttribute requires a manual bounding sphere. Alternatively set "mesh.frustumCulled" to "false".', this ); - - this.boundingSphere.set( new Vector3(), Infinity ); - - return; - - } - - if ( position ) { - - // first, find the center of the bounding sphere - - const center = this.boundingSphere.center; - - _box$1.setFromBufferAttribute( position ); - - // process morph attributes if present - - if ( morphAttributesPosition ) { - - for ( let i = 0, il = morphAttributesPosition.length; i < il; i ++ ) { - - const morphAttribute = morphAttributesPosition[ i ]; - _boxMorphTargets.setFromBufferAttribute( morphAttribute ); - - if ( this.morphTargetsRelative ) { - - _vector$7.addVectors( _box$1.min, _boxMorphTargets.min ); - _box$1.expandByPoint( _vector$7 ); - - _vector$7.addVectors( _box$1.max, _boxMorphTargets.max ); - _box$1.expandByPoint( _vector$7 ); - - } else { - - _box$1.expandByPoint( _boxMorphTargets.min ); - _box$1.expandByPoint( _boxMorphTargets.max ); - - } - - } - - } - - _box$1.getCenter( center ); - - // second, try to find a boundingSphere with a radius smaller than the - // boundingSphere of the boundingBox: sqrt(3) smaller in the best case - - let maxRadiusSq = 0; - - for ( let i = 0, il = position.count; i < il; i ++ ) { - - _vector$7.fromBufferAttribute( position, i ); - - maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( _vector$7 ) ); - - } - - // process morph attributes if present - - if ( morphAttributesPosition ) { - - for ( let i = 0, il = morphAttributesPosition.length; i < il; i ++ ) { - - const morphAttribute = morphAttributesPosition[ i ]; - const morphTargetsRelative = this.morphTargetsRelative; - - for ( let j = 0, jl = morphAttribute.count; j < jl; j ++ ) { - - _vector$7.fromBufferAttribute( morphAttribute, j ); - - if ( morphTargetsRelative ) { - - _offset.fromBufferAttribute( position, j ); - _vector$7.add( _offset ); - - } - - maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( _vector$7 ) ); - - } - - } - - } - - this.boundingSphere.radius = Math.sqrt( maxRadiusSq ); - - if ( isNaN( this.boundingSphere.radius ) ) { - - console.error( 'THREE.BufferGeometry.computeBoundingSphere(): Computed radius is NaN. The "position" attribute is likely to have NaN values.', this ); - - } - - } - - } - - computeTangents() { - - const index = this.index; - const attributes = this.attributes; - - // based on http://www.terathon.com/code/tangent.html - // (per vertex tangents) - - if ( index === null || - attributes.position === undefined || - attributes.normal === undefined || - attributes.uv === undefined ) { - - console.error( 'THREE.BufferGeometry: .computeTangents() failed. Missing required attributes (index, position, normal or uv)' ); - return; - - } - - const indices = index.array; - const positions = attributes.position.array; - const normals = attributes.normal.array; - const uvs = attributes.uv.array; - - const nVertices = positions.length / 3; - - if ( this.hasAttribute( 'tangent' ) === false ) { - - this.setAttribute( 'tangent', new BufferAttribute( new Float32Array( 4 * nVertices ), 4 ) ); - - } - - const tangents = this.getAttribute( 'tangent' ).array; - - const tan1 = [], tan2 = []; - - for ( let i = 0; i < nVertices; i ++ ) { - - tan1[ i ] = new Vector3(); - tan2[ i ] = new Vector3(); - - } - - const vA = new Vector3(), - vB = new Vector3(), - vC = new Vector3(), - - uvA = new Vector2(), - uvB = new Vector2(), - uvC = new Vector2(), - - sdir = new Vector3(), - tdir = new Vector3(); - - function handleTriangle( a, b, c ) { - - vA.fromArray( positions, a * 3 ); - vB.fromArray( positions, b * 3 ); - vC.fromArray( positions, c * 3 ); - - uvA.fromArray( uvs, a * 2 ); - uvB.fromArray( uvs, b * 2 ); - uvC.fromArray( uvs, c * 2 ); - - vB.sub( vA ); - vC.sub( vA ); - - uvB.sub( uvA ); - uvC.sub( uvA ); - - const r = 1.0 / ( uvB.x * uvC.y - uvC.x * uvB.y ); - - // silently ignore degenerate uv triangles having coincident or colinear vertices - - if ( ! isFinite( r ) ) return; - - sdir.copy( vB ).multiplyScalar( uvC.y ).addScaledVector( vC, - uvB.y ).multiplyScalar( r ); - tdir.copy( vC ).multiplyScalar( uvB.x ).addScaledVector( vB, - uvC.x ).multiplyScalar( r ); - - tan1[ a ].add( sdir ); - tan1[ b ].add( sdir ); - tan1[ c ].add( sdir ); - - tan2[ a ].add( tdir ); - tan2[ b ].add( tdir ); - tan2[ c ].add( tdir ); - - } - - let groups = this.groups; - - if ( groups.length === 0 ) { - - groups = [ { - start: 0, - count: indices.length - } ]; - - } - - for ( let i = 0, il = groups.length; i < il; ++ i ) { - - const group = groups[ i ]; - - const start = group.start; - const count = group.count; - - for ( let j = start, jl = start + count; j < jl; j += 3 ) { - - handleTriangle( - indices[ j + 0 ], - indices[ j + 1 ], - indices[ j + 2 ] - ); - - } - - } - - const tmp = new Vector3(), tmp2 = new Vector3(); - const n = new Vector3(), n2 = new Vector3(); - - function handleVertex( v ) { - - n.fromArray( normals, v * 3 ); - n2.copy( n ); - - const t = tan1[ v ]; - - // Gram-Schmidt orthogonalize - - tmp.copy( t ); - tmp.sub( n.multiplyScalar( n.dot( t ) ) ).normalize(); - - // Calculate handedness - - tmp2.crossVectors( n2, t ); - const test = tmp2.dot( tan2[ v ] ); - const w = ( test < 0.0 ) ? - 1.0 : 1.0; - - tangents[ v * 4 ] = tmp.x; - tangents[ v * 4 + 1 ] = tmp.y; - tangents[ v * 4 + 2 ] = tmp.z; - tangents[ v * 4 + 3 ] = w; - - } - - for ( let i = 0, il = groups.length; i < il; ++ i ) { - - const group = groups[ i ]; - - const start = group.start; - const count = group.count; - - for ( let j = start, jl = start + count; j < jl; j += 3 ) { - - handleVertex( indices[ j + 0 ] ); - handleVertex( indices[ j + 1 ] ); - handleVertex( indices[ j + 2 ] ); - - } - - } - - } - - computeVertexNormals() { - - const index = this.index; - const positionAttribute = this.getAttribute( 'position' ); - - if ( positionAttribute !== undefined ) { - - let normalAttribute = this.getAttribute( 'normal' ); - - if ( normalAttribute === undefined ) { - - normalAttribute = new BufferAttribute( new Float32Array( positionAttribute.count * 3 ), 3 ); - this.setAttribute( 'normal', normalAttribute ); - - } else { - - // reset existing normals to zero - - for ( let i = 0, il = normalAttribute.count; i < il; i ++ ) { - - normalAttribute.setXYZ( i, 0, 0, 0 ); - - } - - } - - const pA = new Vector3(), pB = new Vector3(), pC = new Vector3(); - const nA = new Vector3(), nB = new Vector3(), nC = new Vector3(); - const cb = new Vector3(), ab = new Vector3(); - - // indexed elements - - if ( index ) { - - for ( let i = 0, il = index.count; i < il; i += 3 ) { - - const vA = index.getX( i + 0 ); - const vB = index.getX( i + 1 ); - const vC = index.getX( i + 2 ); - - pA.fromBufferAttribute( positionAttribute, vA ); - pB.fromBufferAttribute( positionAttribute, vB ); - pC.fromBufferAttribute( positionAttribute, vC ); - - cb.subVectors( pC, pB ); - ab.subVectors( pA, pB ); - cb.cross( ab ); - - nA.fromBufferAttribute( normalAttribute, vA ); - nB.fromBufferAttribute( normalAttribute, vB ); - nC.fromBufferAttribute( normalAttribute, vC ); - - nA.add( cb ); - nB.add( cb ); - nC.add( cb ); - - normalAttribute.setXYZ( vA, nA.x, nA.y, nA.z ); - normalAttribute.setXYZ( vB, nB.x, nB.y, nB.z ); - normalAttribute.setXYZ( vC, nC.x, nC.y, nC.z ); - - } - - } else { - - // non-indexed elements (unconnected triangle soup) - - for ( let i = 0, il = positionAttribute.count; i < il; i += 3 ) { - - pA.fromBufferAttribute( positionAttribute, i + 0 ); - pB.fromBufferAttribute( positionAttribute, i + 1 ); - pC.fromBufferAttribute( positionAttribute, i + 2 ); - - cb.subVectors( pC, pB ); - ab.subVectors( pA, pB ); - cb.cross( ab ); - - normalAttribute.setXYZ( i + 0, cb.x, cb.y, cb.z ); - normalAttribute.setXYZ( i + 1, cb.x, cb.y, cb.z ); - normalAttribute.setXYZ( i + 2, cb.x, cb.y, cb.z ); - - } - - } - - this.normalizeNormals(); - - normalAttribute.needsUpdate = true; - - } - - } - - merge() { // @deprecated, r144 - - console.error( 'THREE.BufferGeometry.merge() has been removed. Use THREE.BufferGeometryUtils.mergeGeometries() instead.' ); - return this; - - } - - normalizeNormals() { - - const normals = this.attributes.normal; - - for ( let i = 0, il = normals.count; i < il; i ++ ) { - - _vector$7.fromBufferAttribute( normals, i ); - - _vector$7.normalize(); - - normals.setXYZ( i, _vector$7.x, _vector$7.y, _vector$7.z ); - - } - - } - - toNonIndexed() { - - function convertBufferAttribute( attribute, indices ) { - - const array = attribute.array; - const itemSize = attribute.itemSize; - const normalized = attribute.normalized; - - const array2 = new array.constructor( indices.length * itemSize ); - - let index = 0, index2 = 0; - - for ( let i = 0, l = indices.length; i < l; i ++ ) { - - if ( attribute.isInterleavedBufferAttribute ) { - - index = indices[ i ] * attribute.data.stride + attribute.offset; - - } else { - - index = indices[ i ] * itemSize; - - } - - for ( let j = 0; j < itemSize; j ++ ) { - - array2[ index2 ++ ] = array[ index ++ ]; - - } - - } - - return new BufferAttribute( array2, itemSize, normalized ); - - } - - // - - if ( this.index === null ) { - - console.warn( 'THREE.BufferGeometry.toNonIndexed(): BufferGeometry is already non-indexed.' ); - return this; - - } - - const geometry2 = new BufferGeometry(); - - const indices = this.index.array; - const attributes = this.attributes; - - // attributes - - for ( const name in attributes ) { - - const attribute = attributes[ name ]; - - const newAttribute = convertBufferAttribute( attribute, indices ); - - geometry2.setAttribute( name, newAttribute ); - - } - - // morph attributes - - const morphAttributes = this.morphAttributes; - - for ( const name in morphAttributes ) { - - const morphArray = []; - const morphAttribute = morphAttributes[ name ]; // morphAttribute: array of Float32BufferAttributes - - for ( let i = 0, il = morphAttribute.length; i < il; i ++ ) { - - const attribute = morphAttribute[ i ]; - - const newAttribute = convertBufferAttribute( attribute, indices ); - - morphArray.push( newAttribute ); - - } - - geometry2.morphAttributes[ name ] = morphArray; - - } - - geometry2.morphTargetsRelative = this.morphTargetsRelative; - - // groups - - const groups = this.groups; - - for ( let i = 0, l = groups.length; i < l; i ++ ) { - - const group = groups[ i ]; - geometry2.addGroup( group.start, group.count, group.materialIndex ); - - } - - return geometry2; - - } - - toJSON() { - - const data = { - metadata: { - version: 4.6, - type: 'BufferGeometry', - generator: 'BufferGeometry.toJSON' - } - }; - - // standard BufferGeometry serialization - - data.uuid = this.uuid; - data.type = this.type; - if ( this.name !== '' ) data.name = this.name; - if ( Object.keys( this.userData ).length > 0 ) data.userData = this.userData; - - if ( this.parameters !== undefined ) { - - const parameters = this.parameters; - - for ( const key in parameters ) { - - if ( parameters[ key ] !== undefined ) data[ key ] = parameters[ key ]; - - } - - return data; - - } - - // for simplicity the code assumes attributes are not shared across geometries, see #15811 - - data.data = { attributes: {} }; - - const index = this.index; - - if ( index !== null ) { - - data.data.index = { - type: index.array.constructor.name, - array: Array.prototype.slice.call( index.array ) - }; - - } - - const attributes = this.attributes; - - for ( const key in attributes ) { - - const attribute = attributes[ key ]; - - data.data.attributes[ key ] = attribute.toJSON( data.data ); - - } - - const morphAttributes = {}; - let hasMorphAttributes = false; - - for ( const key in this.morphAttributes ) { - - const attributeArray = this.morphAttributes[ key ]; - - const array = []; - - for ( let i = 0, il = attributeArray.length; i < il; i ++ ) { - - const attribute = attributeArray[ i ]; - - array.push( attribute.toJSON( data.data ) ); - - } - - if ( array.length > 0 ) { - - morphAttributes[ key ] = array; - - hasMorphAttributes = true; - - } - - } - - if ( hasMorphAttributes ) { - - data.data.morphAttributes = morphAttributes; - data.data.morphTargetsRelative = this.morphTargetsRelative; - - } - - const groups = this.groups; - - if ( groups.length > 0 ) { - - data.data.groups = JSON.parse( JSON.stringify( groups ) ); - - } - - const boundingSphere = this.boundingSphere; - - if ( boundingSphere !== null ) { - - data.data.boundingSphere = { - center: boundingSphere.center.toArray(), - radius: boundingSphere.radius - }; - - } - - return data; - - } - - clone() { - - return new this.constructor().copy( this ); - - } - - copy( source ) { - - // reset - - this.index = null; - this.attributes = {}; - this.morphAttributes = {}; - this.groups = []; - this.boundingBox = null; - this.boundingSphere = null; - - // used for storing cloned, shared data - - const data = {}; - - // name - - this.name = source.name; - - // index - - const index = source.index; - - if ( index !== null ) { - - this.setIndex( index.clone( data ) ); - - } - - // attributes - - const attributes = source.attributes; - - for ( const name in attributes ) { - - const attribute = attributes[ name ]; - this.setAttribute( name, attribute.clone( data ) ); - - } - - // morph attributes - - const morphAttributes = source.morphAttributes; - - for ( const name in morphAttributes ) { - - const array = []; - const morphAttribute = morphAttributes[ name ]; // morphAttribute: array of Float32BufferAttributes - - for ( let i = 0, l = morphAttribute.length; i < l; i ++ ) { - - array.push( morphAttribute[ i ].clone( data ) ); - - } - - this.morphAttributes[ name ] = array; - - } - - this.morphTargetsRelative = source.morphTargetsRelative; - - // groups - - const groups = source.groups; - - for ( let i = 0, l = groups.length; i < l; i ++ ) { - - const group = groups[ i ]; - this.addGroup( group.start, group.count, group.materialIndex ); - - } - - // bounding box - - const boundingBox = source.boundingBox; - - if ( boundingBox !== null ) { - - this.boundingBox = boundingBox.clone(); - - } - - // bounding sphere - - const boundingSphere = source.boundingSphere; - - if ( boundingSphere !== null ) { - - this.boundingSphere = boundingSphere.clone(); - - } - - // draw range - - this.drawRange.start = source.drawRange.start; - this.drawRange.count = source.drawRange.count; - - // user data - - this.userData = source.userData; - - return this; - - } - - dispose() { - - this.dispatchEvent( { type: 'dispose' } ); - - } - - } - - const _inverseMatrix$3 = /*@__PURE__*/ new Matrix4(); - const _ray$3 = /*@__PURE__*/ new Ray(); - const _sphere$5 = /*@__PURE__*/ new Sphere(); - const _sphereHitAt = /*@__PURE__*/ new Vector3(); - - const _vA$1 = /*@__PURE__*/ new Vector3(); - const _vB$1 = /*@__PURE__*/ new Vector3(); - const _vC$1 = /*@__PURE__*/ new Vector3(); - - const _tempA = /*@__PURE__*/ new Vector3(); - const _morphA = /*@__PURE__*/ new Vector3(); - - const _uvA$1 = /*@__PURE__*/ new Vector2(); - const _uvB$1 = /*@__PURE__*/ new Vector2(); - const _uvC$1 = /*@__PURE__*/ new Vector2(); - - const _normalA = /*@__PURE__*/ new Vector3(); - const _normalB = /*@__PURE__*/ new Vector3(); - const _normalC = /*@__PURE__*/ new Vector3(); - - const _intersectionPoint = /*@__PURE__*/ new Vector3(); - const _intersectionPointWorld = /*@__PURE__*/ new Vector3(); - - class Mesh extends Object3D { - - constructor( geometry = new BufferGeometry(), material = new MeshBasicMaterial() ) { - - super(); - - this.isMesh = true; - - this.type = 'Mesh'; - - this.geometry = geometry; - this.material = material; - - this.updateMorphTargets(); - - } - - copy( source, recursive ) { - - super.copy( source, recursive ); - - if ( source.morphTargetInfluences !== undefined ) { - - this.morphTargetInfluences = source.morphTargetInfluences.slice(); - - } - - if ( source.morphTargetDictionary !== undefined ) { - - this.morphTargetDictionary = Object.assign( {}, source.morphTargetDictionary ); - - } - - this.material = source.material; - this.geometry = source.geometry; - - return this; - - } - - updateMorphTargets() { - - const geometry = this.geometry; - - const morphAttributes = geometry.morphAttributes; - const keys = Object.keys( morphAttributes ); - - if ( keys.length > 0 ) { - - const morphAttribute = morphAttributes[ keys[ 0 ] ]; - - if ( morphAttribute !== undefined ) { - - this.morphTargetInfluences = []; - this.morphTargetDictionary = {}; - - for ( let m = 0, ml = morphAttribute.length; m < ml; m ++ ) { - - const name = morphAttribute[ m ].name || String( m ); - - this.morphTargetInfluences.push( 0 ); - this.morphTargetDictionary[ name ] = m; - - } - - } - - } - - } - - getVertexPosition( index, target ) { - - const geometry = this.geometry; - const position = geometry.attributes.position; - const morphPosition = geometry.morphAttributes.position; - const morphTargetsRelative = geometry.morphTargetsRelative; - - target.fromBufferAttribute( position, index ); - - const morphInfluences = this.morphTargetInfluences; - - if ( morphPosition && morphInfluences ) { - - _morphA.set( 0, 0, 0 ); - - for ( let i = 0, il = morphPosition.length; i < il; i ++ ) { - - const influence = morphInfluences[ i ]; - const morphAttribute = morphPosition[ i ]; - - if ( influence === 0 ) continue; - - _tempA.fromBufferAttribute( morphAttribute, index ); - - if ( morphTargetsRelative ) { - - _morphA.addScaledVector( _tempA, influence ); - - } else { - - _morphA.addScaledVector( _tempA.sub( target ), influence ); - - } - - } - - target.add( _morphA ); - - } - - return target; - - } - - raycast( raycaster, intersects ) { - - const geometry = this.geometry; - const material = this.material; - const matrixWorld = this.matrixWorld; - - if ( material === undefined ) return; - - // test with bounding sphere in world space - - if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); - - _sphere$5.copy( geometry.boundingSphere ); - _sphere$5.applyMatrix4( matrixWorld ); - - // check distance from ray origin to bounding sphere - - _ray$3.copy( raycaster.ray ).recast( raycaster.near ); - - if ( _sphere$5.containsPoint( _ray$3.origin ) === false ) { - - if ( _ray$3.intersectSphere( _sphere$5, _sphereHitAt ) === null ) return; - - if ( _ray$3.origin.distanceToSquared( _sphereHitAt ) > ( raycaster.far - raycaster.near ) ** 2 ) return; - - } - - // convert ray to local space of mesh - - _inverseMatrix$3.copy( matrixWorld ).invert(); - _ray$3.copy( raycaster.ray ).applyMatrix4( _inverseMatrix$3 ); - - // test with bounding box in local space - - if ( geometry.boundingBox !== null ) { - - if ( _ray$3.intersectsBox( geometry.boundingBox ) === false ) return; - - } - - // test for intersections with geometry - - this._computeIntersections( raycaster, intersects, _ray$3 ); - - } - - _computeIntersections( raycaster, intersects, rayLocalSpace ) { - - let intersection; - - const geometry = this.geometry; - const material = this.material; - - const index = geometry.index; - const position = geometry.attributes.position; - const uv = geometry.attributes.uv; - const uv1 = geometry.attributes.uv1; - const normal = geometry.attributes.normal; - const groups = geometry.groups; - const drawRange = geometry.drawRange; - - if ( index !== null ) { - - // indexed buffer geometry - - if ( Array.isArray( material ) ) { - - for ( let i = 0, il = groups.length; i < il; i ++ ) { - - const group = groups[ i ]; - const groupMaterial = material[ group.materialIndex ]; - - const start = Math.max( group.start, drawRange.start ); - const end = Math.min( index.count, Math.min( ( group.start + group.count ), ( drawRange.start + drawRange.count ) ) ); - - for ( let j = start, jl = end; j < jl; j += 3 ) { - - const a = index.getX( j ); - const b = index.getX( j + 1 ); - const c = index.getX( j + 2 ); - - intersection = checkGeometryIntersection( this, groupMaterial, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c ); - - if ( intersection ) { - - intersection.faceIndex = Math.floor( j / 3 ); // triangle number in indexed buffer semantics - intersection.face.materialIndex = group.materialIndex; - intersects.push( intersection ); - - } - - } - - } - - } else { - - const start = Math.max( 0, drawRange.start ); - const end = Math.min( index.count, ( drawRange.start + drawRange.count ) ); - - for ( let i = start, il = end; i < il; i += 3 ) { - - const a = index.getX( i ); - const b = index.getX( i + 1 ); - const c = index.getX( i + 2 ); - - intersection = checkGeometryIntersection( this, material, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c ); - - if ( intersection ) { - - intersection.faceIndex = Math.floor( i / 3 ); // triangle number in indexed buffer semantics - intersects.push( intersection ); - - } - - } - - } - - } else if ( position !== undefined ) { - - // non-indexed buffer geometry - - if ( Array.isArray( material ) ) { - - for ( let i = 0, il = groups.length; i < il; i ++ ) { - - const group = groups[ i ]; - const groupMaterial = material[ group.materialIndex ]; - - const start = Math.max( group.start, drawRange.start ); - const end = Math.min( position.count, Math.min( ( group.start + group.count ), ( drawRange.start + drawRange.count ) ) ); - - for ( let j = start, jl = end; j < jl; j += 3 ) { - - const a = j; - const b = j + 1; - const c = j + 2; - - intersection = checkGeometryIntersection( this, groupMaterial, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c ); - - if ( intersection ) { - - intersection.faceIndex = Math.floor( j / 3 ); // triangle number in non-indexed buffer semantics - intersection.face.materialIndex = group.materialIndex; - intersects.push( intersection ); - - } - - } - - } - - } else { - - const start = Math.max( 0, drawRange.start ); - const end = Math.min( position.count, ( drawRange.start + drawRange.count ) ); - - for ( let i = start, il = end; i < il; i += 3 ) { - - const a = i; - const b = i + 1; - const c = i + 2; - - intersection = checkGeometryIntersection( this, material, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c ); - - if ( intersection ) { - - intersection.faceIndex = Math.floor( i / 3 ); // triangle number in non-indexed buffer semantics - intersects.push( intersection ); - - } - - } - - } - - } - - } - - } - - function checkIntersection( object, material, raycaster, ray, pA, pB, pC, point ) { - - let intersect; - - if ( material.side === BackSide ) { - - intersect = ray.intersectTriangle( pC, pB, pA, true, point ); - - } else { - - intersect = ray.intersectTriangle( pA, pB, pC, ( material.side === FrontSide ), point ); - - } - - if ( intersect === null ) return null; - - _intersectionPointWorld.copy( point ); - _intersectionPointWorld.applyMatrix4( object.matrixWorld ); - - const distance = raycaster.ray.origin.distanceTo( _intersectionPointWorld ); - - if ( distance < raycaster.near || distance > raycaster.far ) return null; - - return { - distance: distance, - point: _intersectionPointWorld.clone(), - object: object - }; - - } - - function checkGeometryIntersection( object, material, raycaster, ray, uv, uv1, normal, a, b, c ) { - - object.getVertexPosition( a, _vA$1 ); - object.getVertexPosition( b, _vB$1 ); - object.getVertexPosition( c, _vC$1 ); - - const intersection = checkIntersection( object, material, raycaster, ray, _vA$1, _vB$1, _vC$1, _intersectionPoint ); - - if ( intersection ) { - - if ( uv ) { - - _uvA$1.fromBufferAttribute( uv, a ); - _uvB$1.fromBufferAttribute( uv, b ); - _uvC$1.fromBufferAttribute( uv, c ); - - intersection.uv = Triangle.getInterpolation( _intersectionPoint, _vA$1, _vB$1, _vC$1, _uvA$1, _uvB$1, _uvC$1, new Vector2() ); - - } - - if ( uv1 ) { - - _uvA$1.fromBufferAttribute( uv1, a ); - _uvB$1.fromBufferAttribute( uv1, b ); - _uvC$1.fromBufferAttribute( uv1, c ); - - intersection.uv1 = Triangle.getInterpolation( _intersectionPoint, _vA$1, _vB$1, _vC$1, _uvA$1, _uvB$1, _uvC$1, new Vector2() ); - intersection.uv2 = intersection.uv1; // @deprecated, r152 - - } - - if ( normal ) { - - _normalA.fromBufferAttribute( normal, a ); - _normalB.fromBufferAttribute( normal, b ); - _normalC.fromBufferAttribute( normal, c ); - - intersection.normal = Triangle.getInterpolation( _intersectionPoint, _vA$1, _vB$1, _vC$1, _normalA, _normalB, _normalC, new Vector3() ); - - if ( intersection.normal.dot( ray.direction ) > 0 ) { - - intersection.normal.multiplyScalar( - 1 ); - - } - - } - - const face = { - a: a, - b: b, - c: c, - normal: new Vector3(), - materialIndex: 0 - }; - - Triangle.getNormal( _vA$1, _vB$1, _vC$1, face.normal ); - - intersection.face = face; - - } - - return intersection; - - } - - class BoxGeometry extends BufferGeometry { - - constructor( width = 1, height = 1, depth = 1, widthSegments = 1, heightSegments = 1, depthSegments = 1 ) { - - super(); - - this.type = 'BoxGeometry'; - - this.parameters = { - width: width, - height: height, - depth: depth, - widthSegments: widthSegments, - heightSegments: heightSegments, - depthSegments: depthSegments - }; - - const scope = this; - - // segments - - widthSegments = Math.floor( widthSegments ); - heightSegments = Math.floor( heightSegments ); - depthSegments = Math.floor( depthSegments ); - - // buffers - - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; - - // helper variables - - let numberOfVertices = 0; - let groupStart = 0; - - // build each side of the box geometry - - buildPlane( 'z', 'y', 'x', - 1, - 1, depth, height, width, depthSegments, heightSegments, 0 ); // px - buildPlane( 'z', 'y', 'x', 1, - 1, depth, height, - width, depthSegments, heightSegments, 1 ); // nx - buildPlane( 'x', 'z', 'y', 1, 1, width, depth, height, widthSegments, depthSegments, 2 ); // py - buildPlane( 'x', 'z', 'y', 1, - 1, width, depth, - height, widthSegments, depthSegments, 3 ); // ny - buildPlane( 'x', 'y', 'z', 1, - 1, width, height, depth, widthSegments, heightSegments, 4 ); // pz - buildPlane( 'x', 'y', 'z', - 1, - 1, width, height, - depth, widthSegments, heightSegments, 5 ); // nz - - // build geometry - - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - - function buildPlane( u, v, w, udir, vdir, width, height, depth, gridX, gridY, materialIndex ) { - - const segmentWidth = width / gridX; - const segmentHeight = height / gridY; - - const widthHalf = width / 2; - const heightHalf = height / 2; - const depthHalf = depth / 2; - - const gridX1 = gridX + 1; - const gridY1 = gridY + 1; - - let vertexCounter = 0; - let groupCount = 0; - - const vector = new Vector3(); - - // generate vertices, normals and uvs - - for ( let iy = 0; iy < gridY1; iy ++ ) { - - const y = iy * segmentHeight - heightHalf; - - for ( let ix = 0; ix < gridX1; ix ++ ) { - - const x = ix * segmentWidth - widthHalf; - - // set values to correct vector component - - vector[ u ] = x * udir; - vector[ v ] = y * vdir; - vector[ w ] = depthHalf; - - // now apply vector to vertex buffer - - vertices.push( vector.x, vector.y, vector.z ); - - // set values to correct vector component - - vector[ u ] = 0; - vector[ v ] = 0; - vector[ w ] = depth > 0 ? 1 : - 1; - - // now apply vector to normal buffer - - normals.push( vector.x, vector.y, vector.z ); - - // uvs - - uvs.push( ix / gridX ); - uvs.push( 1 - ( iy / gridY ) ); - - // counters - - vertexCounter += 1; - - } - - } - - // indices - - // 1. you need three indices to draw a single face - // 2. a single segment consists of two faces - // 3. so we need to generate six (2*3) indices per segment - - for ( let iy = 0; iy < gridY; iy ++ ) { - - for ( let ix = 0; ix < gridX; ix ++ ) { - - const a = numberOfVertices + ix + gridX1 * iy; - const b = numberOfVertices + ix + gridX1 * ( iy + 1 ); - const c = numberOfVertices + ( ix + 1 ) + gridX1 * ( iy + 1 ); - const d = numberOfVertices + ( ix + 1 ) + gridX1 * iy; - - // faces - - indices.push( a, b, d ); - indices.push( b, c, d ); - - // increase counter - - groupCount += 6; - - } - - } - - // add a group to the geometry. this will ensure multi material support - - scope.addGroup( groupStart, groupCount, materialIndex ); - - // calculate new start value for groups - - groupStart += groupCount; - - // update total number of vertices - - numberOfVertices += vertexCounter; - - } - - } - - copy( source ) { - - super.copy( source ); - - this.parameters = Object.assign( {}, source.parameters ); - - return this; - - } - - static fromJSON( data ) { - - return new BoxGeometry( data.width, data.height, data.depth, data.widthSegments, data.heightSegments, data.depthSegments ); - - } - - } - - /** - * Uniform Utilities - */ - - function cloneUniforms( src ) { - - const dst = {}; - - for ( const u in src ) { - - dst[ u ] = {}; - - for ( const p in src[ u ] ) { - - const property = src[ u ][ p ]; - - if ( property && ( property.isColor || - property.isMatrix3 || property.isMatrix4 || - property.isVector2 || property.isVector3 || property.isVector4 || - property.isTexture || property.isQuaternion ) ) { - - if ( property.isRenderTargetTexture ) { - - console.warn( 'UniformsUtils: Textures of render targets cannot be cloned via cloneUniforms() or mergeUniforms().' ); - dst[ u ][ p ] = null; - - } else { - - dst[ u ][ p ] = property.clone(); - - } - - } else if ( Array.isArray( property ) ) { - - dst[ u ][ p ] = property.slice(); - - } else { - - dst[ u ][ p ] = property; - - } - - } - - } - - return dst; - - } - - function mergeUniforms( uniforms ) { - - const merged = {}; - - for ( let u = 0; u < uniforms.length; u ++ ) { - - const tmp = cloneUniforms( uniforms[ u ] ); - - for ( const p in tmp ) { - - merged[ p ] = tmp[ p ]; - - } - - } - - return merged; - - } - - function cloneUniformsGroups( src ) { - - const dst = []; - - for ( let u = 0; u < src.length; u ++ ) { - - dst.push( src[ u ].clone() ); - - } - - return dst; - - } - - function getUnlitUniformColorSpace( renderer ) { - - if ( renderer.getRenderTarget() === null ) { - - // https://github.com/mrdoob/three.js/pull/23937#issuecomment-1111067398 - return renderer.outputColorSpace; - - } - - return LinearSRGBColorSpace; - - } - - // Legacy - - const UniformsUtils = { clone: cloneUniforms, merge: mergeUniforms }; - - var default_vertex = "void main() {\n\tgl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n}"; - - var default_fragment = "void main() {\n\tgl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0 );\n}"; - - class ShaderMaterial extends Material { - - constructor( parameters ) { - - super(); - - this.isShaderMaterial = true; - - this.type = 'ShaderMaterial'; - - this.defines = {}; - this.uniforms = {}; - this.uniformsGroups = []; - - this.vertexShader = default_vertex; - this.fragmentShader = default_fragment; - - this.linewidth = 1; - - this.wireframe = false; - this.wireframeLinewidth = 1; - - this.fog = false; // set to use scene fog - this.lights = false; // set to use scene lights - this.clipping = false; // set to use user-defined clipping planes - - this.forceSinglePass = true; - - this.extensions = { - derivatives: false, // set to use derivatives - fragDepth: false, // set to use fragment depth values - drawBuffers: false, // set to use draw buffers - shaderTextureLOD: false // set to use shader texture LOD - }; - - // When rendered geometry doesn't include these attributes but the material does, - // use these default values in WebGL. This avoids errors when buffer data is missing. - this.defaultAttributeValues = { - 'color': [ 1, 1, 1 ], - 'uv': [ 0, 0 ], - 'uv1': [ 0, 0 ] - }; - - this.index0AttributeName = undefined; - this.uniformsNeedUpdate = false; - - this.glslVersion = null; - - if ( parameters !== undefined ) { - - this.setValues( parameters ); - - } - - } - - copy( source ) { - - super.copy( source ); - - this.fragmentShader = source.fragmentShader; - this.vertexShader = source.vertexShader; - - this.uniforms = cloneUniforms( source.uniforms ); - this.uniformsGroups = cloneUniformsGroups( source.uniformsGroups ); - - this.defines = Object.assign( {}, source.defines ); - - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; - - this.fog = source.fog; - this.lights = source.lights; - this.clipping = source.clipping; - - this.extensions = Object.assign( {}, source.extensions ); - - this.glslVersion = source.glslVersion; - - return this; - - } - - toJSON( meta ) { - - const data = super.toJSON( meta ); - - data.glslVersion = this.glslVersion; - data.uniforms = {}; - - for ( const name in this.uniforms ) { - - const uniform = this.uniforms[ name ]; - const value = uniform.value; - - if ( value && value.isTexture ) { - - data.uniforms[ name ] = { - type: 't', - value: value.toJSON( meta ).uuid - }; - - } else if ( value && value.isColor ) { - - data.uniforms[ name ] = { - type: 'c', - value: value.getHex() - }; - - } else if ( value && value.isVector2 ) { - - data.uniforms[ name ] = { - type: 'v2', - value: value.toArray() - }; - - } else if ( value && value.isVector3 ) { - - data.uniforms[ name ] = { - type: 'v3', - value: value.toArray() - }; - - } else if ( value && value.isVector4 ) { - - data.uniforms[ name ] = { - type: 'v4', - value: value.toArray() - }; - - } else if ( value && value.isMatrix3 ) { - - data.uniforms[ name ] = { - type: 'm3', - value: value.toArray() - }; - - } else if ( value && value.isMatrix4 ) { - - data.uniforms[ name ] = { - type: 'm4', - value: value.toArray() - }; - - } else { - - data.uniforms[ name ] = { - value: value - }; - - // note: the array variants v2v, v3v, v4v, m4v and tv are not supported so far - - } - - } - - if ( Object.keys( this.defines ).length > 0 ) data.defines = this.defines; - - data.vertexShader = this.vertexShader; - data.fragmentShader = this.fragmentShader; - - data.lights = this.lights; - data.clipping = this.clipping; - - const extensions = {}; - - for ( const key in this.extensions ) { - - if ( this.extensions[ key ] === true ) extensions[ key ] = true; - - } - - if ( Object.keys( extensions ).length > 0 ) data.extensions = extensions; - - return data; - - } - - } - - class Camera extends Object3D { - - constructor() { - - super(); - - this.isCamera = true; - - this.type = 'Camera'; - - this.matrixWorldInverse = new Matrix4(); - - this.projectionMatrix = new Matrix4(); - this.projectionMatrixInverse = new Matrix4(); - - } - - copy( source, recursive ) { - - super.copy( source, recursive ); - - this.matrixWorldInverse.copy( source.matrixWorldInverse ); - - this.projectionMatrix.copy( source.projectionMatrix ); - this.projectionMatrixInverse.copy( source.projectionMatrixInverse ); - - return this; - - } - - getWorldDirection( target ) { - - this.updateWorldMatrix( true, false ); - - const e = this.matrixWorld.elements; - - return target.set( - e[ 8 ], - e[ 9 ], - e[ 10 ] ).normalize(); - - } - - updateMatrixWorld( force ) { - - super.updateMatrixWorld( force ); - - this.matrixWorldInverse.copy( this.matrixWorld ).invert(); - - } - - updateWorldMatrix( updateParents, updateChildren ) { - - super.updateWorldMatrix( updateParents, updateChildren ); - - this.matrixWorldInverse.copy( this.matrixWorld ).invert(); - - } - - clone() { - - return new this.constructor().copy( this ); - - } - - } - - class PerspectiveCamera extends Camera { - - constructor( fov = 50, aspect = 1, near = 0.1, far = 2000 ) { - - super(); - - this.isPerspectiveCamera = true; - - this.type = 'PerspectiveCamera'; - - this.fov = fov; - this.zoom = 1; - - this.near = near; - this.far = far; - this.focus = 10; - - this.aspect = aspect; - this.view = null; - - this.filmGauge = 35; // width of the film (default in millimeters) - this.filmOffset = 0; // horizontal film offset (same unit as gauge) - - this.updateProjectionMatrix(); - - } - - copy( source, recursive ) { - - super.copy( source, recursive ); - - this.fov = source.fov; - this.zoom = source.zoom; - - this.near = source.near; - this.far = source.far; - this.focus = source.focus; - - this.aspect = source.aspect; - this.view = source.view === null ? null : Object.assign( {}, source.view ); - - this.filmGauge = source.filmGauge; - this.filmOffset = source.filmOffset; - - return this; - - } - - /** - * Sets the FOV by focal length in respect to the current .filmGauge. - * - * The default film gauge is 35, so that the focal length can be specified for - * a 35mm (full frame) camera. - * - * Values for focal length and film gauge must have the same unit. - */ - setFocalLength( focalLength ) { - - /** see {@link http://www.bobatkins.com/photography/technical/field_of_view.html} */ - const vExtentSlope = 0.5 * this.getFilmHeight() / focalLength; - - this.fov = RAD2DEG * 2 * Math.atan( vExtentSlope ); - this.updateProjectionMatrix(); - - } - - /** - * Calculates the focal length from the current .fov and .filmGauge. - */ - getFocalLength() { - - const vExtentSlope = Math.tan( DEG2RAD * 0.5 * this.fov ); - - return 0.5 * this.getFilmHeight() / vExtentSlope; - - } - - getEffectiveFOV() { - - return RAD2DEG * 2 * Math.atan( - Math.tan( DEG2RAD * 0.5 * this.fov ) / this.zoom ); - - } - - getFilmWidth() { - - // film not completely covered in portrait format (aspect < 1) - return this.filmGauge * Math.min( this.aspect, 1 ); - - } - - getFilmHeight() { - - // film not completely covered in landscape format (aspect > 1) - return this.filmGauge / Math.max( this.aspect, 1 ); - - } - - /** - * Sets an offset in a larger frustum. This is useful for multi-window or - * multi-monitor/multi-machine setups. - * - * For example, if you have 3x2 monitors and each monitor is 1920x1080 and - * the monitors are in grid like this - * - * +---+---+---+ - * | A | B | C | - * +---+---+---+ - * | D | E | F | - * +---+---+---+ - * - * then for each monitor you would call it like this - * - * const w = 1920; - * const h = 1080; - * const fullWidth = w * 3; - * const fullHeight = h * 2; - * - * --A-- - * camera.setViewOffset( fullWidth, fullHeight, w * 0, h * 0, w, h ); - * --B-- - * camera.setViewOffset( fullWidth, fullHeight, w * 1, h * 0, w, h ); - * --C-- - * camera.setViewOffset( fullWidth, fullHeight, w * 2, h * 0, w, h ); - * --D-- - * camera.setViewOffset( fullWidth, fullHeight, w * 0, h * 1, w, h ); - * --E-- - * camera.setViewOffset( fullWidth, fullHeight, w * 1, h * 1, w, h ); - * --F-- - * camera.setViewOffset( fullWidth, fullHeight, w * 2, h * 1, w, h ); - * - * Note there is no reason monitors have to be the same size or in a grid. - */ - setViewOffset( fullWidth, fullHeight, x, y, width, height ) { - - this.aspect = fullWidth / fullHeight; - - if ( this.view === null ) { - - this.view = { - enabled: true, - fullWidth: 1, - fullHeight: 1, - offsetX: 0, - offsetY: 0, - width: 1, - height: 1 - }; - - } - - this.view.enabled = true; - this.view.fullWidth = fullWidth; - this.view.fullHeight = fullHeight; - this.view.offsetX = x; - this.view.offsetY = y; - this.view.width = width; - this.view.height = height; - - this.updateProjectionMatrix(); - - } - - clearViewOffset() { - - if ( this.view !== null ) { - - this.view.enabled = false; - - } - - this.updateProjectionMatrix(); - - } - - updateProjectionMatrix() { - - const near = this.near; - let top = near * Math.tan( DEG2RAD * 0.5 * this.fov ) / this.zoom; - let height = 2 * top; - let width = this.aspect * height; - let left = - 0.5 * width; - const view = this.view; - - if ( this.view !== null && this.view.enabled ) { - - const fullWidth = view.fullWidth, - fullHeight = view.fullHeight; - - left += view.offsetX * width / fullWidth; - top -= view.offsetY * height / fullHeight; - width *= view.width / fullWidth; - height *= view.height / fullHeight; - - } - - const skew = this.filmOffset; - if ( skew !== 0 ) left += near * skew / this.getFilmWidth(); - - this.projectionMatrix.makePerspective( left, left + width, top, top - height, near, this.far ); - - this.projectionMatrixInverse.copy( this.projectionMatrix ).invert(); - - } - - toJSON( meta ) { - - const data = super.toJSON( meta ); - - data.object.fov = this.fov; - data.object.zoom = this.zoom; - - data.object.near = this.near; - data.object.far = this.far; - data.object.focus = this.focus; - - data.object.aspect = this.aspect; - - if ( this.view !== null ) data.object.view = Object.assign( {}, this.view ); - - data.object.filmGauge = this.filmGauge; - data.object.filmOffset = this.filmOffset; - - return data; - - } - - } - - const fov = - 90; // negative fov is not an error - const aspect = 1; - - class CubeCamera extends Object3D { - - constructor( near, far, renderTarget ) { - - super(); - - this.type = 'CubeCamera'; - - this.renderTarget = renderTarget; - - const cameraPX = new PerspectiveCamera( fov, aspect, near, far ); - cameraPX.layers = this.layers; - cameraPX.up.set( 0, 1, 0 ); - cameraPX.lookAt( 1, 0, 0 ); - this.add( cameraPX ); - - const cameraNX = new PerspectiveCamera( fov, aspect, near, far ); - cameraNX.layers = this.layers; - cameraNX.up.set( 0, 1, 0 ); - cameraNX.lookAt( - 1, 0, 0 ); - this.add( cameraNX ); - - const cameraPY = new PerspectiveCamera( fov, aspect, near, far ); - cameraPY.layers = this.layers; - cameraPY.up.set( 0, 0, - 1 ); - cameraPY.lookAt( 0, 1, 0 ); - this.add( cameraPY ); - - const cameraNY = new PerspectiveCamera( fov, aspect, near, far ); - cameraNY.layers = this.layers; - cameraNY.up.set( 0, 0, 1 ); - cameraNY.lookAt( 0, - 1, 0 ); - this.add( cameraNY ); - - const cameraPZ = new PerspectiveCamera( fov, aspect, near, far ); - cameraPZ.layers = this.layers; - cameraPZ.up.set( 0, 1, 0 ); - cameraPZ.lookAt( 0, 0, 1 ); - this.add( cameraPZ ); - - const cameraNZ = new PerspectiveCamera( fov, aspect, near, far ); - cameraNZ.layers = this.layers; - cameraNZ.up.set( 0, 1, 0 ); - cameraNZ.lookAt( 0, 0, - 1 ); - this.add( cameraNZ ); - - } - - update( renderer, scene ) { - - if ( this.parent === null ) this.updateMatrixWorld(); - - const renderTarget = this.renderTarget; - - const [ cameraPX, cameraNX, cameraPY, cameraNY, cameraPZ, cameraNZ ] = this.children; - - const currentRenderTarget = renderer.getRenderTarget(); - - const currentToneMapping = renderer.toneMapping; - const currentXrEnabled = renderer.xr.enabled; - - renderer.toneMapping = NoToneMapping; - renderer.xr.enabled = false; - - const generateMipmaps = renderTarget.texture.generateMipmaps; - - renderTarget.texture.generateMipmaps = false; - - renderer.setRenderTarget( renderTarget, 0 ); - renderer.render( scene, cameraPX ); - - renderer.setRenderTarget( renderTarget, 1 ); - renderer.render( scene, cameraNX ); - - renderer.setRenderTarget( renderTarget, 2 ); - renderer.render( scene, cameraPY ); - - renderer.setRenderTarget( renderTarget, 3 ); - renderer.render( scene, cameraNY ); - - renderer.setRenderTarget( renderTarget, 4 ); - renderer.render( scene, cameraPZ ); - - renderTarget.texture.generateMipmaps = generateMipmaps; - - renderer.setRenderTarget( renderTarget, 5 ); - renderer.render( scene, cameraNZ ); - - renderer.setRenderTarget( currentRenderTarget ); - - renderer.toneMapping = currentToneMapping; - renderer.xr.enabled = currentXrEnabled; - - renderTarget.texture.needsPMREMUpdate = true; - - } - - } - - class CubeTexture extends Texture { - - constructor( images, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, colorSpace ) { - - images = images !== undefined ? images : []; - mapping = mapping !== undefined ? mapping : CubeReflectionMapping; - - super( images, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, colorSpace ); - - this.isCubeTexture = true; - - this.flipY = false; - - } - - get images() { - - return this.image; - - } - - set images( value ) { - - this.image = value; - - } - - } - - class WebGLCubeRenderTarget extends WebGLRenderTarget { - - constructor( size = 1, options = {} ) { - - super( size, size, options ); - - this.isWebGLCubeRenderTarget = true; - - const image = { width: size, height: size, depth: 1 }; - const images = [ image, image, image, image, image, image ]; - - if ( options.encoding !== undefined ) { - - // @deprecated, r152 - warnOnce( 'THREE.WebGLCubeRenderTarget: option.encoding has been replaced by option.colorSpace.' ); - options.colorSpace = options.encoding === sRGBEncoding ? SRGBColorSpace : NoColorSpace; - - } - - this.texture = new CubeTexture( images, options.mapping, options.wrapS, options.wrapT, options.magFilter, options.minFilter, options.format, options.type, options.anisotropy, options.colorSpace ); - - // By convention -- likely based on the RenderMan spec from the 1990's -- cube maps are specified by WebGL (and three.js) - // in a coordinate system in which positive-x is to the right when looking up the positive-z axis -- in other words, - // in a left-handed coordinate system. By continuing this convention, preexisting cube maps continued to render correctly. - - // three.js uses a right-handed coordinate system. So environment maps used in three.js appear to have px and nx swapped - // and the flag isRenderTargetTexture controls this conversion. The flip is not required when using WebGLCubeRenderTarget.texture - // as a cube texture (this is detected when isRenderTargetTexture is set to true for cube textures). - - this.texture.isRenderTargetTexture = true; - - this.texture.generateMipmaps = options.generateMipmaps !== undefined ? options.generateMipmaps : false; - this.texture.minFilter = options.minFilter !== undefined ? options.minFilter : LinearFilter; - - } - - fromEquirectangularTexture( renderer, texture ) { - - this.texture.type = texture.type; - this.texture.colorSpace = texture.colorSpace; - - this.texture.generateMipmaps = texture.generateMipmaps; - this.texture.minFilter = texture.minFilter; - this.texture.magFilter = texture.magFilter; - - const shader = { - - uniforms: { - tEquirect: { value: null }, - }, - - vertexShader: /* glsl */` - - varying vec3 vWorldDirection; - - vec3 transformDirection( in vec3 dir, in mat4 matrix ) { - - return normalize( ( matrix * vec4( dir, 0.0 ) ).xyz ); - - } - - void main() { - - vWorldDirection = transformDirection( position, modelMatrix ); - - #include - #include - - } - `, - - fragmentShader: /* glsl */` - - uniform sampler2D tEquirect; - - varying vec3 vWorldDirection; - - #include - - void main() { - - vec3 direction = normalize( vWorldDirection ); - - vec2 sampleUV = equirectUv( direction ); - - gl_FragColor = texture2D( tEquirect, sampleUV ); - - } - ` - }; - - const geometry = new BoxGeometry( 5, 5, 5 ); - - const material = new ShaderMaterial( { - - name: 'CubemapFromEquirect', - - uniforms: cloneUniforms( shader.uniforms ), - vertexShader: shader.vertexShader, - fragmentShader: shader.fragmentShader, - side: BackSide, - blending: NoBlending - - } ); - - material.uniforms.tEquirect.value = texture; - - const mesh = new Mesh( geometry, material ); - - const currentMinFilter = texture.minFilter; - - // Avoid blurred poles - if ( texture.minFilter === LinearMipmapLinearFilter ) texture.minFilter = LinearFilter; - - const camera = new CubeCamera( 1, 10, this ); - camera.update( renderer, mesh ); - - texture.minFilter = currentMinFilter; - - mesh.geometry.dispose(); - mesh.material.dispose(); - - return this; - - } - - clear( renderer, color, depth, stencil ) { - - const currentRenderTarget = renderer.getRenderTarget(); - - for ( let i = 0; i < 6; i ++ ) { - - renderer.setRenderTarget( this, i ); - - renderer.clear( color, depth, stencil ); - - } - - renderer.setRenderTarget( currentRenderTarget ); - - } - - } - - const _vector1 = /*@__PURE__*/ new Vector3(); - const _vector2 = /*@__PURE__*/ new Vector3(); - const _normalMatrix = /*@__PURE__*/ new Matrix3(); - - class Plane { - - constructor( normal = new Vector3( 1, 0, 0 ), constant = 0 ) { - - this.isPlane = true; - - // normal is assumed to be normalized - - this.normal = normal; - this.constant = constant; - - } - - set( normal, constant ) { - - this.normal.copy( normal ); - this.constant = constant; - - return this; - - } - - setComponents( x, y, z, w ) { - - this.normal.set( x, y, z ); - this.constant = w; - - return this; - - } - - setFromNormalAndCoplanarPoint( normal, point ) { - - this.normal.copy( normal ); - this.constant = - point.dot( this.normal ); - - return this; - - } - - setFromCoplanarPoints( a, b, c ) { - - const normal = _vector1.subVectors( c, b ).cross( _vector2.subVectors( a, b ) ).normalize(); - - // Q: should an error be thrown if normal is zero (e.g. degenerate plane)? - - this.setFromNormalAndCoplanarPoint( normal, a ); - - return this; - - } - - copy( plane ) { - - this.normal.copy( plane.normal ); - this.constant = plane.constant; - - return this; - - } - - normalize() { - - // Note: will lead to a divide by zero if the plane is invalid. - - const inverseNormalLength = 1.0 / this.normal.length(); - this.normal.multiplyScalar( inverseNormalLength ); - this.constant *= inverseNormalLength; - - return this; - - } - - negate() { - - this.constant *= - 1; - this.normal.negate(); - - return this; - - } - - distanceToPoint( point ) { - - return this.normal.dot( point ) + this.constant; - - } - - distanceToSphere( sphere ) { - - return this.distanceToPoint( sphere.center ) - sphere.radius; - - } - - projectPoint( point, target ) { - - return target.copy( point ).addScaledVector( this.normal, - this.distanceToPoint( point ) ); - - } - - intersectLine( line, target ) { - - const direction = line.delta( _vector1 ); - - const denominator = this.normal.dot( direction ); - - if ( denominator === 0 ) { - - // line is coplanar, return origin - if ( this.distanceToPoint( line.start ) === 0 ) { - - return target.copy( line.start ); - - } - - // Unsure if this is the correct method to handle this case. - return null; - - } - - const t = - ( line.start.dot( this.normal ) + this.constant ) / denominator; - - if ( t < 0 || t > 1 ) { - - return null; - - } - - return target.copy( line.start ).addScaledVector( direction, t ); - - } - - intersectsLine( line ) { - - // Note: this tests if a line intersects the plane, not whether it (or its end-points) are coplanar with it. - - const startSign = this.distanceToPoint( line.start ); - const endSign = this.distanceToPoint( line.end ); - - return ( startSign < 0 && endSign > 0 ) || ( endSign < 0 && startSign > 0 ); - - } - - intersectsBox( box ) { - - return box.intersectsPlane( this ); - - } - - intersectsSphere( sphere ) { - - return sphere.intersectsPlane( this ); - - } - - coplanarPoint( target ) { - - return target.copy( this.normal ).multiplyScalar( - this.constant ); - - } - - applyMatrix4( matrix, optionalNormalMatrix ) { - - const normalMatrix = optionalNormalMatrix || _normalMatrix.getNormalMatrix( matrix ); - - const referencePoint = this.coplanarPoint( _vector1 ).applyMatrix4( matrix ); - - const normal = this.normal.applyMatrix3( normalMatrix ).normalize(); - - this.constant = - referencePoint.dot( normal ); - - return this; - - } - - translate( offset ) { - - this.constant -= offset.dot( this.normal ); - - return this; - - } - - equals( plane ) { - - return plane.normal.equals( this.normal ) && ( plane.constant === this.constant ); - - } - - clone() { - - return new this.constructor().copy( this ); - - } - - } - - const _sphere$4 = /*@__PURE__*/ new Sphere(); - const _vector$6 = /*@__PURE__*/ new Vector3(); - - class Frustum { - - constructor( p0 = new Plane(), p1 = new Plane(), p2 = new Plane(), p3 = new Plane(), p4 = new Plane(), p5 = new Plane() ) { - - this.planes = [ p0, p1, p2, p3, p4, p5 ]; - - } - - set( p0, p1, p2, p3, p4, p5 ) { - - const planes = this.planes; - - planes[ 0 ].copy( p0 ); - planes[ 1 ].copy( p1 ); - planes[ 2 ].copy( p2 ); - planes[ 3 ].copy( p3 ); - planes[ 4 ].copy( p4 ); - planes[ 5 ].copy( p5 ); - - return this; - - } - - copy( frustum ) { - - const planes = this.planes; - - for ( let i = 0; i < 6; i ++ ) { - - planes[ i ].copy( frustum.planes[ i ] ); - - } - - return this; - - } - - setFromProjectionMatrix( m ) { - - const planes = this.planes; - const me = m.elements; - const me0 = me[ 0 ], me1 = me[ 1 ], me2 = me[ 2 ], me3 = me[ 3 ]; - const me4 = me[ 4 ], me5 = me[ 5 ], me6 = me[ 6 ], me7 = me[ 7 ]; - const me8 = me[ 8 ], me9 = me[ 9 ], me10 = me[ 10 ], me11 = me[ 11 ]; - const me12 = me[ 12 ], me13 = me[ 13 ], me14 = me[ 14 ], me15 = me[ 15 ]; - - planes[ 0 ].setComponents( me3 - me0, me7 - me4, me11 - me8, me15 - me12 ).normalize(); - planes[ 1 ].setComponents( me3 + me0, me7 + me4, me11 + me8, me15 + me12 ).normalize(); - planes[ 2 ].setComponents( me3 + me1, me7 + me5, me11 + me9, me15 + me13 ).normalize(); - planes[ 3 ].setComponents( me3 - me1, me7 - me5, me11 - me9, me15 - me13 ).normalize(); - planes[ 4 ].setComponents( me3 - me2, me7 - me6, me11 - me10, me15 - me14 ).normalize(); - planes[ 5 ].setComponents( me3 + me2, me7 + me6, me11 + me10, me15 + me14 ).normalize(); - - return this; - - } - - intersectsObject( object ) { - - if ( object.boundingSphere !== undefined ) { - - if ( object.boundingSphere === null ) object.computeBoundingSphere(); - - _sphere$4.copy( object.boundingSphere ).applyMatrix4( object.matrixWorld ); - - } else { - - const geometry = object.geometry; - - if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); - - _sphere$4.copy( geometry.boundingSphere ).applyMatrix4( object.matrixWorld ); - - } - - return this.intersectsSphere( _sphere$4 ); - - } - - intersectsSprite( sprite ) { - - _sphere$4.center.set( 0, 0, 0 ); - _sphere$4.radius = 0.7071067811865476; - _sphere$4.applyMatrix4( sprite.matrixWorld ); - - return this.intersectsSphere( _sphere$4 ); - - } - - intersectsSphere( sphere ) { - - const planes = this.planes; - const center = sphere.center; - const negRadius = - sphere.radius; - - for ( let i = 0; i < 6; i ++ ) { - - const distance = planes[ i ].distanceToPoint( center ); - - if ( distance < negRadius ) { - - return false; - - } - - } - - return true; - - } - - intersectsBox( box ) { - - const planes = this.planes; - - for ( let i = 0; i < 6; i ++ ) { - - const plane = planes[ i ]; - - // corner at max distance - - _vector$6.x = plane.normal.x > 0 ? box.max.x : box.min.x; - _vector$6.y = plane.normal.y > 0 ? box.max.y : box.min.y; - _vector$6.z = plane.normal.z > 0 ? box.max.z : box.min.z; - - if ( plane.distanceToPoint( _vector$6 ) < 0 ) { - - return false; - - } - - } - - return true; - - } - - containsPoint( point ) { - - const planes = this.planes; - - for ( let i = 0; i < 6; i ++ ) { - - if ( planes[ i ].distanceToPoint( point ) < 0 ) { - - return false; - - } - - } - - return true; - - } - - clone() { - - return new this.constructor().copy( this ); - - } - - } - - function WebGLAnimation() { - - let context = null; - let isAnimating = false; - let animationLoop = null; - let requestId = null; - - function onAnimationFrame( time, frame ) { - - animationLoop( time, frame ); - - requestId = context.requestAnimationFrame( onAnimationFrame ); - - } - - return { - - start: function () { - - if ( isAnimating === true ) return; - if ( animationLoop === null ) return; - - requestId = context.requestAnimationFrame( onAnimationFrame ); - - isAnimating = true; - - }, - - stop: function () { - - context.cancelAnimationFrame( requestId ); - - isAnimating = false; - - }, - - setAnimationLoop: function ( callback ) { - - animationLoop = callback; - - }, - - setContext: function ( value ) { - - context = value; - - } - - }; - - } - - function WebGLAttributes( gl, capabilities ) { - - const isWebGL2 = capabilities.isWebGL2; - - const buffers = new WeakMap(); - - function createBuffer( attribute, bufferType ) { - - const array = attribute.array; - const usage = attribute.usage; - - const buffer = gl.createBuffer(); - - gl.bindBuffer( bufferType, buffer ); - gl.bufferData( bufferType, array, usage ); - - attribute.onUploadCallback(); - - let type; - - if ( array instanceof Float32Array ) { - - type = gl.FLOAT; - - } else if ( array instanceof Uint16Array ) { - - if ( attribute.isFloat16BufferAttribute ) { - - if ( isWebGL2 ) { - - type = gl.HALF_FLOAT; - - } else { - - throw new Error( 'THREE.WebGLAttributes: Usage of Float16BufferAttribute requires WebGL2.' ); - - } - - } else { - - type = gl.UNSIGNED_SHORT; - - } - - } else if ( array instanceof Int16Array ) { - - type = gl.SHORT; - - } else if ( array instanceof Uint32Array ) { - - type = gl.UNSIGNED_INT; - - } else if ( array instanceof Int32Array ) { - - type = gl.INT; - - } else if ( array instanceof Int8Array ) { - - type = gl.BYTE; - - } else if ( array instanceof Uint8Array ) { - - type = gl.UNSIGNED_BYTE; - - } else if ( array instanceof Uint8ClampedArray ) { - - type = gl.UNSIGNED_BYTE; - - } else { - - throw new Error( 'THREE.WebGLAttributes: Unsupported buffer data format: ' + array ); - - } - - return { - buffer: buffer, - type: type, - bytesPerElement: array.BYTES_PER_ELEMENT, - version: attribute.version - }; - - } - - function updateBuffer( buffer, attribute, bufferType ) { - - const array = attribute.array; - const updateRange = attribute.updateRange; - - gl.bindBuffer( bufferType, buffer ); - - if ( updateRange.count === - 1 ) { - - // Not using update ranges - - gl.bufferSubData( bufferType, 0, array ); - - } else { - - if ( isWebGL2 ) { - - gl.bufferSubData( bufferType, updateRange.offset * array.BYTES_PER_ELEMENT, - array, updateRange.offset, updateRange.count ); - - } else { - - gl.bufferSubData( bufferType, updateRange.offset * array.BYTES_PER_ELEMENT, - array.subarray( updateRange.offset, updateRange.offset + updateRange.count ) ); - - } - - updateRange.count = - 1; // reset range - - } - - attribute.onUploadCallback(); - - } - - // - - function get( attribute ) { - - if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; - - return buffers.get( attribute ); - - } - - function remove( attribute ) { - - if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; - - const data = buffers.get( attribute ); - - if ( data ) { - - gl.deleteBuffer( data.buffer ); - - buffers.delete( attribute ); - - } - - } - - function update( attribute, bufferType ) { - - if ( attribute.isGLBufferAttribute ) { - - const cached = buffers.get( attribute ); - - if ( ! cached || cached.version < attribute.version ) { - - buffers.set( attribute, { - buffer: attribute.buffer, - type: attribute.type, - bytesPerElement: attribute.elementSize, - version: attribute.version - } ); - - } - - return; - - } - - if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; - - const data = buffers.get( attribute ); - - if ( data === undefined ) { - - buffers.set( attribute, createBuffer( attribute, bufferType ) ); - - } else if ( data.version < attribute.version ) { - - updateBuffer( data.buffer, attribute, bufferType ); - - data.version = attribute.version; - - } - - } - - return { - - get: get, - remove: remove, - update: update - - }; - - } - - class PlaneGeometry extends BufferGeometry { - - constructor( width = 1, height = 1, widthSegments = 1, heightSegments = 1 ) { - - super(); - - this.type = 'PlaneGeometry'; - - this.parameters = { - width: width, - height: height, - widthSegments: widthSegments, - heightSegments: heightSegments - }; - - const width_half = width / 2; - const height_half = height / 2; - - const gridX = Math.floor( widthSegments ); - const gridY = Math.floor( heightSegments ); - - const gridX1 = gridX + 1; - const gridY1 = gridY + 1; - - const segment_width = width / gridX; - const segment_height = height / gridY; - - // - - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; - - for ( let iy = 0; iy < gridY1; iy ++ ) { - - const y = iy * segment_height - height_half; - - for ( let ix = 0; ix < gridX1; ix ++ ) { - - const x = ix * segment_width - width_half; - - vertices.push( x, - y, 0 ); - - normals.push( 0, 0, 1 ); - - uvs.push( ix / gridX ); - uvs.push( 1 - ( iy / gridY ) ); - - } - - } - - for ( let iy = 0; iy < gridY; iy ++ ) { - - for ( let ix = 0; ix < gridX; ix ++ ) { - - const a = ix + gridX1 * iy; - const b = ix + gridX1 * ( iy + 1 ); - const c = ( ix + 1 ) + gridX1 * ( iy + 1 ); - const d = ( ix + 1 ) + gridX1 * iy; - - indices.push( a, b, d ); - indices.push( b, c, d ); - - } - - } - - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - - } - - copy( source ) { - - super.copy( source ); - - this.parameters = Object.assign( {}, source.parameters ); - - return this; - - } - - static fromJSON( data ) { - - return new PlaneGeometry( data.width, data.height, data.widthSegments, data.heightSegments ); - - } - - } - - var alphamap_fragment = "#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, vAlphaMapUv ).g;\n#endif"; - - var alphamap_pars_fragment = "#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif"; - - var alphatest_fragment = "#ifdef USE_ALPHATEST\n\tif ( diffuseColor.a < alphaTest ) discard;\n#endif"; - - var alphatest_pars_fragment = "#ifdef USE_ALPHATEST\n\tuniform float alphaTest;\n#endif"; - - var aomap_fragment = "#ifdef USE_AOMAP\n\tfloat ambientOcclusion = ( texture2D( aoMap, vAoMapUv ).r - 1.0 ) * aoMapIntensity + 1.0;\n\treflectedLight.indirectDiffuse *= ambientOcclusion;\n\t#if defined( USE_ENVMAP ) && defined( STANDARD )\n\t\tfloat dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );\n\t\treflectedLight.indirectSpecular *= computeSpecularOcclusion( dotNV, ambientOcclusion, material.roughness );\n\t#endif\n#endif"; - - var aomap_pars_fragment = "#ifdef USE_AOMAP\n\tuniform sampler2D aoMap;\n\tuniform float aoMapIntensity;\n#endif"; - - var begin_vertex = "vec3 transformed = vec3( position );"; - - var beginnormal_vertex = "vec3 objectNormal = vec3( normal );\n#ifdef USE_TANGENT\n\tvec3 objectTangent = vec3( tangent.xyz );\n#endif"; - - var bsdfs = "float G_BlinnPhong_Implicit( ) {\n\treturn 0.25;\n}\nfloat D_BlinnPhong( const in float shininess, const in float dotNH ) {\n\treturn RECIPROCAL_PI * ( shininess * 0.5 + 1.0 ) * pow( dotNH, shininess );\n}\nvec3 BRDF_BlinnPhong( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in vec3 specularColor, const in float shininess ) {\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\tvec3 F = F_Schlick( specularColor, 1.0, dotVH );\n\tfloat G = G_BlinnPhong_Implicit( );\n\tfloat D = D_BlinnPhong( shininess, dotNH );\n\treturn F * ( G * D );\n} // validated"; - - var iridescence_fragment = "#ifdef USE_IRIDESCENCE\n\tconst mat3 XYZ_TO_REC709 = mat3(\n\t\t 3.2404542, -0.9692660, 0.0556434,\n\t\t-1.5371385, 1.8760108, -0.2040259,\n\t\t-0.4985314, 0.0415560, 1.0572252\n\t);\n\tvec3 Fresnel0ToIor( vec3 fresnel0 ) {\n\t\tvec3 sqrtF0 = sqrt( fresnel0 );\n\t\treturn ( vec3( 1.0 ) + sqrtF0 ) / ( vec3( 1.0 ) - sqrtF0 );\n\t}\n\tvec3 IorToFresnel0( vec3 transmittedIor, float incidentIor ) {\n\t\treturn pow2( ( transmittedIor - vec3( incidentIor ) ) / ( transmittedIor + vec3( incidentIor ) ) );\n\t}\n\tfloat IorToFresnel0( float transmittedIor, float incidentIor ) {\n\t\treturn pow2( ( transmittedIor - incidentIor ) / ( transmittedIor + incidentIor ));\n\t}\n\tvec3 evalSensitivity( float OPD, vec3 shift ) {\n\t\tfloat phase = 2.0 * PI * OPD * 1.0e-9;\n\t\tvec3 val = vec3( 5.4856e-13, 4.4201e-13, 5.2481e-13 );\n\t\tvec3 pos = vec3( 1.6810e+06, 1.7953e+06, 2.2084e+06 );\n\t\tvec3 var = vec3( 4.3278e+09, 9.3046e+09, 6.6121e+09 );\n\t\tvec3 xyz = val * sqrt( 2.0 * PI * var ) * cos( pos * phase + shift ) * exp( - pow2( phase ) * var );\n\t\txyz.x += 9.7470e-14 * sqrt( 2.0 * PI * 4.5282e+09 ) * cos( 2.2399e+06 * phase + shift[ 0 ] ) * exp( - 4.5282e+09 * pow2( phase ) );\n\t\txyz /= 1.0685e-7;\n\t\tvec3 rgb = XYZ_TO_REC709 * xyz;\n\t\treturn rgb;\n\t}\n\tvec3 evalIridescence( float outsideIOR, float eta2, float cosTheta1, float thinFilmThickness, vec3 baseF0 ) {\n\t\tvec3 I;\n\t\tfloat iridescenceIOR = mix( outsideIOR, eta2, smoothstep( 0.0, 0.03, thinFilmThickness ) );\n\t\tfloat sinTheta2Sq = pow2( outsideIOR / iridescenceIOR ) * ( 1.0 - pow2( cosTheta1 ) );\n\t\tfloat cosTheta2Sq = 1.0 - sinTheta2Sq;\n\t\tif ( cosTheta2Sq < 0.0 ) {\n\t\t\t return vec3( 1.0 );\n\t\t}\n\t\tfloat cosTheta2 = sqrt( cosTheta2Sq );\n\t\tfloat R0 = IorToFresnel0( iridescenceIOR, outsideIOR );\n\t\tfloat R12 = F_Schlick( R0, 1.0, cosTheta1 );\n\t\tfloat R21 = R12;\n\t\tfloat T121 = 1.0 - R12;\n\t\tfloat phi12 = 0.0;\n\t\tif ( iridescenceIOR < outsideIOR ) phi12 = PI;\n\t\tfloat phi21 = PI - phi12;\n\t\tvec3 baseIOR = Fresnel0ToIor( clamp( baseF0, 0.0, 0.9999 ) );\t\tvec3 R1 = IorToFresnel0( baseIOR, iridescenceIOR );\n\t\tvec3 R23 = F_Schlick( R1, 1.0, cosTheta2 );\n\t\tvec3 phi23 = vec3( 0.0 );\n\t\tif ( baseIOR[ 0 ] < iridescenceIOR ) phi23[ 0 ] = PI;\n\t\tif ( baseIOR[ 1 ] < iridescenceIOR ) phi23[ 1 ] = PI;\n\t\tif ( baseIOR[ 2 ] < iridescenceIOR ) phi23[ 2 ] = PI;\n\t\tfloat OPD = 2.0 * iridescenceIOR * thinFilmThickness * cosTheta2;\n\t\tvec3 phi = vec3( phi21 ) + phi23;\n\t\tvec3 R123 = clamp( R12 * R23, 1e-5, 0.9999 );\n\t\tvec3 r123 = sqrt( R123 );\n\t\tvec3 Rs = pow2( T121 ) * R23 / ( vec3( 1.0 ) - R123 );\n\t\tvec3 C0 = R12 + Rs;\n\t\tI = C0;\n\t\tvec3 Cm = Rs - T121;\n\t\tfor ( int m = 1; m <= 2; ++ m ) {\n\t\t\tCm *= r123;\n\t\t\tvec3 Sm = 2.0 * evalSensitivity( float( m ) * OPD, float( m ) * phi );\n\t\t\tI += Cm * Sm;\n\t\t}\n\t\treturn max( I, vec3( 0.0 ) );\n\t}\n#endif"; - - var bumpmap_pars_fragment = "#ifdef USE_BUMPMAP\n\tuniform sampler2D bumpMap;\n\tuniform float bumpScale;\n\tvec2 dHdxy_fwd() {\n\t\tvec2 dSTdx = dFdx( vBumpMapUv );\n\t\tvec2 dSTdy = dFdy( vBumpMapUv );\n\t\tfloat Hll = bumpScale * texture2D( bumpMap, vBumpMapUv ).x;\n\t\tfloat dBx = bumpScale * texture2D( bumpMap, vBumpMapUv + dSTdx ).x - Hll;\n\t\tfloat dBy = bumpScale * texture2D( bumpMap, vBumpMapUv + dSTdy ).x - Hll;\n\t\treturn vec2( dBx, dBy );\n\t}\n\tvec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy, float faceDirection ) {\n\t\tvec3 vSigmaX = dFdx( surf_pos.xyz );\n\t\tvec3 vSigmaY = dFdy( surf_pos.xyz );\n\t\tvec3 vN = surf_norm;\n\t\tvec3 R1 = cross( vSigmaY, vN );\n\t\tvec3 R2 = cross( vN, vSigmaX );\n\t\tfloat fDet = dot( vSigmaX, R1 ) * faceDirection;\n\t\tvec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );\n\t\treturn normalize( abs( fDet ) * surf_norm - vGrad );\n\t}\n#endif"; - - var clipping_planes_fragment = "#if NUM_CLIPPING_PLANES > 0\n\tvec4 plane;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) {\n\t\tplane = clippingPlanes[ i ];\n\t\tif ( dot( vClipPosition, plane.xyz ) > plane.w ) discard;\n\t}\n\t#pragma unroll_loop_end\n\t#if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES\n\t\tbool clipped = true;\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) {\n\t\t\tplane = clippingPlanes[ i ];\n\t\t\tclipped = ( dot( vClipPosition, plane.xyz ) > plane.w ) && clipped;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t\tif ( clipped ) discard;\n\t#endif\n#endif"; - - var clipping_planes_pars_fragment = "#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n\tuniform vec4 clippingPlanes[ NUM_CLIPPING_PLANES ];\n#endif"; - - var clipping_planes_pars_vertex = "#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n#endif"; - - var clipping_planes_vertex = "#if NUM_CLIPPING_PLANES > 0\n\tvClipPosition = - mvPosition.xyz;\n#endif"; - - var color_fragment = "#if defined( USE_COLOR_ALPHA )\n\tdiffuseColor *= vColor;\n#elif defined( USE_COLOR )\n\tdiffuseColor.rgb *= vColor;\n#endif"; - - var color_pars_fragment = "#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR )\n\tvarying vec3 vColor;\n#endif"; - - var color_pars_vertex = "#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR )\n\tvarying vec3 vColor;\n#endif"; - - var color_vertex = "#if defined( USE_COLOR_ALPHA )\n\tvColor = vec4( 1.0 );\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR )\n\tvColor = vec3( 1.0 );\n#endif\n#ifdef USE_COLOR\n\tvColor *= color;\n#endif\n#ifdef USE_INSTANCING_COLOR\n\tvColor.xyz *= instanceColor.xyz;\n#endif"; - - var common = "#define PI 3.141592653589793\n#define PI2 6.283185307179586\n#define PI_HALF 1.5707963267948966\n#define RECIPROCAL_PI 0.3183098861837907\n#define RECIPROCAL_PI2 0.15915494309189535\n#define EPSILON 1e-6\n#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\n#define whiteComplement( a ) ( 1.0 - saturate( a ) )\nfloat pow2( const in float x ) { return x*x; }\nvec3 pow2( const in vec3 x ) { return x*x; }\nfloat pow3( const in float x ) { return x*x*x; }\nfloat pow4( const in float x ) { float x2 = x*x; return x2*x2; }\nfloat max3( const in vec3 v ) { return max( max( v.x, v.y ), v.z ); }\nfloat average( const in vec3 v ) { return dot( v, vec3( 0.3333333 ) ); }\nhighp float rand( const in vec2 uv ) {\n\tconst highp float a = 12.9898, b = 78.233, c = 43758.5453;\n\thighp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI );\n\treturn fract( sin( sn ) * c );\n}\n#ifdef HIGH_PRECISION\n\tfloat precisionSafeLength( vec3 v ) { return length( v ); }\n#else\n\tfloat precisionSafeLength( vec3 v ) {\n\t\tfloat maxComponent = max3( abs( v ) );\n\t\treturn length( v / maxComponent ) * maxComponent;\n\t}\n#endif\nstruct IncidentLight {\n\tvec3 color;\n\tvec3 direction;\n\tbool visible;\n};\nstruct ReflectedLight {\n\tvec3 directDiffuse;\n\tvec3 directSpecular;\n\tvec3 indirectDiffuse;\n\tvec3 indirectSpecular;\n};\nstruct GeometricContext {\n\tvec3 position;\n\tvec3 normal;\n\tvec3 viewDir;\n#ifdef USE_CLEARCOAT\n\tvec3 clearcoatNormal;\n#endif\n};\nvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n}\nvec3 inverseTransformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( vec4( dir, 0.0 ) * matrix ).xyz );\n}\nmat3 transposeMat3( const in mat3 m ) {\n\tmat3 tmp;\n\ttmp[ 0 ] = vec3( m[ 0 ].x, m[ 1 ].x, m[ 2 ].x );\n\ttmp[ 1 ] = vec3( m[ 0 ].y, m[ 1 ].y, m[ 2 ].y );\n\ttmp[ 2 ] = vec3( m[ 0 ].z, m[ 1 ].z, m[ 2 ].z );\n\treturn tmp;\n}\nfloat luminance( const in vec3 rgb ) {\n\tconst vec3 weights = vec3( 0.2126729, 0.7151522, 0.0721750 );\n\treturn dot( weights, rgb );\n}\nbool isPerspectiveMatrix( mat4 m ) {\n\treturn m[ 2 ][ 3 ] == - 1.0;\n}\nvec2 equirectUv( in vec3 dir ) {\n\tfloat u = atan( dir.z, dir.x ) * RECIPROCAL_PI2 + 0.5;\n\tfloat v = asin( clamp( dir.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\n\treturn vec2( u, v );\n}\nvec3 BRDF_Lambert( const in vec3 diffuseColor ) {\n\treturn RECIPROCAL_PI * diffuseColor;\n}\nvec3 F_Schlick( const in vec3 f0, const in float f90, const in float dotVH ) {\n\tfloat fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );\n\treturn f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );\n}\nfloat F_Schlick( const in float f0, const in float f90, const in float dotVH ) {\n\tfloat fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );\n\treturn f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );\n} // validated"; - - var cube_uv_reflection_fragment = "#ifdef ENVMAP_TYPE_CUBE_UV\n\t#define cubeUV_minMipLevel 4.0\n\t#define cubeUV_minTileSize 16.0\n\tfloat getFace( vec3 direction ) {\n\t\tvec3 absDirection = abs( direction );\n\t\tfloat face = - 1.0;\n\t\tif ( absDirection.x > absDirection.z ) {\n\t\t\tif ( absDirection.x > absDirection.y )\n\t\t\t\tface = direction.x > 0.0 ? 0.0 : 3.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t} else {\n\t\t\tif ( absDirection.z > absDirection.y )\n\t\t\t\tface = direction.z > 0.0 ? 2.0 : 5.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t}\n\t\treturn face;\n\t}\n\tvec2 getUV( vec3 direction, float face ) {\n\t\tvec2 uv;\n\t\tif ( face == 0.0 ) {\n\t\t\tuv = vec2( direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 1.0 ) {\n\t\t\tuv = vec2( - direction.x, - direction.z ) / abs( direction.y );\n\t\t} else if ( face == 2.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.y ) / abs( direction.z );\n\t\t} else if ( face == 3.0 ) {\n\t\t\tuv = vec2( - direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 4.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.z ) / abs( direction.y );\n\t\t} else {\n\t\t\tuv = vec2( direction.x, direction.y ) / abs( direction.z );\n\t\t}\n\t\treturn 0.5 * ( uv + 1.0 );\n\t}\n\tvec3 bilinearCubeUV( sampler2D envMap, vec3 direction, float mipInt ) {\n\t\tfloat face = getFace( direction );\n\t\tfloat filterInt = max( cubeUV_minMipLevel - mipInt, 0.0 );\n\t\tmipInt = max( mipInt, cubeUV_minMipLevel );\n\t\tfloat faceSize = exp2( mipInt );\n\t\thighp vec2 uv = getUV( direction, face ) * ( faceSize - 2.0 ) + 1.0;\n\t\tif ( face > 2.0 ) {\n\t\t\tuv.y += faceSize;\n\t\t\tface -= 3.0;\n\t\t}\n\t\tuv.x += face * faceSize;\n\t\tuv.x += filterInt * 3.0 * cubeUV_minTileSize;\n\t\tuv.y += 4.0 * ( exp2( CUBEUV_MAX_MIP ) - faceSize );\n\t\tuv.x *= CUBEUV_TEXEL_WIDTH;\n\t\tuv.y *= CUBEUV_TEXEL_HEIGHT;\n\t\t#ifdef texture2DGradEXT\n\t\t\treturn texture2DGradEXT( envMap, uv, vec2( 0.0 ), vec2( 0.0 ) ).rgb;\n\t\t#else\n\t\t\treturn texture2D( envMap, uv ).rgb;\n\t\t#endif\n\t}\n\t#define cubeUV_r0 1.0\n\t#define cubeUV_v0 0.339\n\t#define cubeUV_m0 - 2.0\n\t#define cubeUV_r1 0.8\n\t#define cubeUV_v1 0.276\n\t#define cubeUV_m1 - 1.0\n\t#define cubeUV_r4 0.4\n\t#define cubeUV_v4 0.046\n\t#define cubeUV_m4 2.0\n\t#define cubeUV_r5 0.305\n\t#define cubeUV_v5 0.016\n\t#define cubeUV_m5 3.0\n\t#define cubeUV_r6 0.21\n\t#define cubeUV_v6 0.0038\n\t#define cubeUV_m6 4.0\n\tfloat roughnessToMip( float roughness ) {\n\t\tfloat mip = 0.0;\n\t\tif ( roughness >= cubeUV_r1 ) {\n\t\t\tmip = ( cubeUV_r0 - roughness ) * ( cubeUV_m1 - cubeUV_m0 ) / ( cubeUV_r0 - cubeUV_r1 ) + cubeUV_m0;\n\t\t} else if ( roughness >= cubeUV_r4 ) {\n\t\t\tmip = ( cubeUV_r1 - roughness ) * ( cubeUV_m4 - cubeUV_m1 ) / ( cubeUV_r1 - cubeUV_r4 ) + cubeUV_m1;\n\t\t} else if ( roughness >= cubeUV_r5 ) {\n\t\t\tmip = ( cubeUV_r4 - roughness ) * ( cubeUV_m5 - cubeUV_m4 ) / ( cubeUV_r4 - cubeUV_r5 ) + cubeUV_m4;\n\t\t} else if ( roughness >= cubeUV_r6 ) {\n\t\t\tmip = ( cubeUV_r5 - roughness ) * ( cubeUV_m6 - cubeUV_m5 ) / ( cubeUV_r5 - cubeUV_r6 ) + cubeUV_m5;\n\t\t} else {\n\t\t\tmip = - 2.0 * log2( 1.16 * roughness );\t\t}\n\t\treturn mip;\n\t}\n\tvec4 textureCubeUV( sampler2D envMap, vec3 sampleDir, float roughness ) {\n\t\tfloat mip = clamp( roughnessToMip( roughness ), cubeUV_m0, CUBEUV_MAX_MIP );\n\t\tfloat mipF = fract( mip );\n\t\tfloat mipInt = floor( mip );\n\t\tvec3 color0 = bilinearCubeUV( envMap, sampleDir, mipInt );\n\t\tif ( mipF == 0.0 ) {\n\t\t\treturn vec4( color0, 1.0 );\n\t\t} else {\n\t\t\tvec3 color1 = bilinearCubeUV( envMap, sampleDir, mipInt + 1.0 );\n\t\t\treturn vec4( mix( color0, color1, mipF ), 1.0 );\n\t\t}\n\t}\n#endif"; - - var defaultnormal_vertex = "vec3 transformedNormal = objectNormal;\n#ifdef USE_INSTANCING\n\tmat3 m = mat3( instanceMatrix );\n\ttransformedNormal /= vec3( dot( m[ 0 ], m[ 0 ] ), dot( m[ 1 ], m[ 1 ] ), dot( m[ 2 ], m[ 2 ] ) );\n\ttransformedNormal = m * transformedNormal;\n#endif\ntransformedNormal = normalMatrix * transformedNormal;\n#ifdef FLIP_SIDED\n\ttransformedNormal = - transformedNormal;\n#endif\n#ifdef USE_TANGENT\n\tvec3 transformedTangent = ( modelViewMatrix * vec4( objectTangent, 0.0 ) ).xyz;\n\t#ifdef FLIP_SIDED\n\t\ttransformedTangent = - transformedTangent;\n\t#endif\n#endif"; - - var displacementmap_pars_vertex = "#ifdef USE_DISPLACEMENTMAP\n\tuniform sampler2D displacementMap;\n\tuniform float displacementScale;\n\tuniform float displacementBias;\n#endif"; - - var displacementmap_vertex = "#ifdef USE_DISPLACEMENTMAP\n\ttransformed += normalize( objectNormal ) * ( texture2D( displacementMap, vDisplacementMapUv ).x * displacementScale + displacementBias );\n#endif"; - - var emissivemap_fragment = "#ifdef USE_EMISSIVEMAP\n\tvec4 emissiveColor = texture2D( emissiveMap, vEmissiveMapUv );\n\ttotalEmissiveRadiance *= emissiveColor.rgb;\n#endif"; - - var emissivemap_pars_fragment = "#ifdef USE_EMISSIVEMAP\n\tuniform sampler2D emissiveMap;\n#endif"; - - var encodings_fragment = "gl_FragColor = linearToOutputTexel( gl_FragColor );"; - - var encodings_pars_fragment = "vec4 LinearToLinear( in vec4 value ) {\n\treturn value;\n}\nvec4 LinearTosRGB( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a );\n}"; - - var envmap_fragment = "#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvec3 cameraToFrag;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToFrag = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToFrag = normalize( vWorldPosition - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvec3 reflectVec = reflect( cameraToFrag, worldNormal );\n\t\t#else\n\t\t\tvec3 reflectVec = refract( cameraToFrag, worldNormal, refractionRatio );\n\t\t#endif\n\t#else\n\t\tvec3 reflectVec = vReflect;\n\t#endif\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 envColor = textureCube( envMap, vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );\n\t#else\n\t\tvec4 envColor = vec4( 0.0 );\n\t#endif\n\t#ifdef ENVMAP_BLENDING_MULTIPLY\n\t\toutgoingLight = mix( outgoingLight, outgoingLight * envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_MIX )\n\t\toutgoingLight = mix( outgoingLight, envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_ADD )\n\t\toutgoingLight += envColor.xyz * specularStrength * reflectivity;\n\t#endif\n#endif"; - - var envmap_common_pars_fragment = "#ifdef USE_ENVMAP\n\tuniform float envMapIntensity;\n\tuniform float flipEnvMap;\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tuniform samplerCube envMap;\n\t#else\n\t\tuniform sampler2D envMap;\n\t#endif\n\t\n#endif"; - - var envmap_pars_fragment = "#ifdef USE_ENVMAP\n\tuniform float reflectivity;\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( LAMBERT )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\tvarying vec3 vWorldPosition;\n\t\tuniform float refractionRatio;\n\t#else\n\t\tvarying vec3 vReflect;\n\t#endif\n#endif"; - - var envmap_pars_vertex = "#ifdef USE_ENVMAP\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( LAMBERT )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\t\n\t\tvarying vec3 vWorldPosition;\n\t#else\n\t\tvarying vec3 vReflect;\n\t\tuniform float refractionRatio;\n\t#endif\n#endif"; - - var envmap_vertex = "#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvWorldPosition = worldPosition.xyz;\n\t#else\n\t\tvec3 cameraToVertex;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToVertex = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToVertex = normalize( worldPosition.xyz - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvReflect = reflect( cameraToVertex, worldNormal );\n\t\t#else\n\t\t\tvReflect = refract( cameraToVertex, worldNormal, refractionRatio );\n\t\t#endif\n\t#endif\n#endif"; - - var fog_vertex = "#ifdef USE_FOG\n\tvFogDepth = - mvPosition.z;\n#endif"; - - var fog_pars_vertex = "#ifdef USE_FOG\n\tvarying float vFogDepth;\n#endif"; - - var fog_fragment = "#ifdef USE_FOG\n\t#ifdef FOG_EXP2\n\t\tfloat fogFactor = 1.0 - exp( - fogDensity * fogDensity * vFogDepth * vFogDepth );\n\t#else\n\t\tfloat fogFactor = smoothstep( fogNear, fogFar, vFogDepth );\n\t#endif\n\tgl_FragColor.rgb = mix( gl_FragColor.rgb, fogColor, fogFactor );\n#endif"; - - var fog_pars_fragment = "#ifdef USE_FOG\n\tuniform vec3 fogColor;\n\tvarying float vFogDepth;\n\t#ifdef FOG_EXP2\n\t\tuniform float fogDensity;\n\t#else\n\t\tuniform float fogNear;\n\t\tuniform float fogFar;\n\t#endif\n#endif"; - - var gradientmap_pars_fragment = "#ifdef USE_GRADIENTMAP\n\tuniform sampler2D gradientMap;\n#endif\nvec3 getGradientIrradiance( vec3 normal, vec3 lightDirection ) {\n\tfloat dotNL = dot( normal, lightDirection );\n\tvec2 coord = vec2( dotNL * 0.5 + 0.5, 0.0 );\n\t#ifdef USE_GRADIENTMAP\n\t\treturn vec3( texture2D( gradientMap, coord ).r );\n\t#else\n\t\tvec2 fw = fwidth( coord ) * 0.5;\n\t\treturn mix( vec3( 0.7 ), vec3( 1.0 ), smoothstep( 0.7 - fw.x, 0.7 + fw.x, coord.x ) );\n\t#endif\n}"; - - var lightmap_fragment = "#ifdef USE_LIGHTMAP\n\tvec4 lightMapTexel = texture2D( lightMap, vLightMapUv );\n\tvec3 lightMapIrradiance = lightMapTexel.rgb * lightMapIntensity;\n\treflectedLight.indirectDiffuse += lightMapIrradiance;\n#endif"; - - var lightmap_pars_fragment = "#ifdef USE_LIGHTMAP\n\tuniform sampler2D lightMap;\n\tuniform float lightMapIntensity;\n#endif"; - - var lights_lambert_fragment = "LambertMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularStrength = specularStrength;"; - - var lights_lambert_pars_fragment = "varying vec3 vViewPosition;\nstruct LambertMaterial {\n\tvec3 diffuseColor;\n\tfloat specularStrength;\n};\nvoid RE_Direct_Lambert( const in IncidentLight directLight, const in GeometricContext geometry, const in LambertMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Lambert( const in vec3 irradiance, const in GeometricContext geometry, const in LambertMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Lambert\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Lambert"; - - var lights_pars_begin = "uniform bool receiveShadow;\nuniform vec3 ambientLightColor;\nuniform vec3 lightProbe[ 9 ];\nvec3 shGetIrradianceAt( in vec3 normal, in vec3 shCoefficients[ 9 ] ) {\n\tfloat x = normal.x, y = normal.y, z = normal.z;\n\tvec3 result = shCoefficients[ 0 ] * 0.886227;\n\tresult += shCoefficients[ 1 ] * 2.0 * 0.511664 * y;\n\tresult += shCoefficients[ 2 ] * 2.0 * 0.511664 * z;\n\tresult += shCoefficients[ 3 ] * 2.0 * 0.511664 * x;\n\tresult += shCoefficients[ 4 ] * 2.0 * 0.429043 * x * y;\n\tresult += shCoefficients[ 5 ] * 2.0 * 0.429043 * y * z;\n\tresult += shCoefficients[ 6 ] * ( 0.743125 * z * z - 0.247708 );\n\tresult += shCoefficients[ 7 ] * 2.0 * 0.429043 * x * z;\n\tresult += shCoefficients[ 8 ] * 0.429043 * ( x * x - y * y );\n\treturn result;\n}\nvec3 getLightProbeIrradiance( const in vec3 lightProbe[ 9 ], const in vec3 normal ) {\n\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\tvec3 irradiance = shGetIrradianceAt( worldNormal, lightProbe );\n\treturn irradiance;\n}\nvec3 getAmbientLightIrradiance( const in vec3 ambientLightColor ) {\n\tvec3 irradiance = ambientLightColor;\n\treturn irradiance;\n}\nfloat getDistanceAttenuation( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) {\n\t#if defined ( LEGACY_LIGHTS )\n\t\tif ( cutoffDistance > 0.0 && decayExponent > 0.0 ) {\n\t\t\treturn pow( saturate( - lightDistance / cutoffDistance + 1.0 ), decayExponent );\n\t\t}\n\t\treturn 1.0;\n\t#else\n\t\tfloat distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );\n\t\tif ( cutoffDistance > 0.0 ) {\n\t\t\tdistanceFalloff *= pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );\n\t\t}\n\t\treturn distanceFalloff;\n\t#endif\n}\nfloat getSpotAttenuation( const in float coneCosine, const in float penumbraCosine, const in float angleCosine ) {\n\treturn smoothstep( coneCosine, penumbraCosine, angleCosine );\n}\n#if NUM_DIR_LIGHTS > 0\n\tstruct DirectionalLight {\n\t\tvec3 direction;\n\t\tvec3 color;\n\t};\n\tuniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ];\n\tvoid getDirectionalLightInfo( const in DirectionalLight directionalLight, const in GeometricContext geometry, out IncidentLight light ) {\n\t\tlight.color = directionalLight.color;\n\t\tlight.direction = directionalLight.direction;\n\t\tlight.visible = true;\n\t}\n#endif\n#if NUM_POINT_LIGHTS > 0\n\tstruct PointLight {\n\t\tvec3 position;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t};\n\tuniform PointLight pointLights[ NUM_POINT_LIGHTS ];\n\tvoid getPointLightInfo( const in PointLight pointLight, const in GeometricContext geometry, out IncidentLight light ) {\n\t\tvec3 lVector = pointLight.position - geometry.position;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat lightDistance = length( lVector );\n\t\tlight.color = pointLight.color;\n\t\tlight.color *= getDistanceAttenuation( lightDistance, pointLight.distance, pointLight.decay );\n\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t}\n#endif\n#if NUM_SPOT_LIGHTS > 0\n\tstruct SpotLight {\n\t\tvec3 position;\n\t\tvec3 direction;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t\tfloat coneCos;\n\t\tfloat penumbraCos;\n\t};\n\tuniform SpotLight spotLights[ NUM_SPOT_LIGHTS ];\n\tvoid getSpotLightInfo( const in SpotLight spotLight, const in GeometricContext geometry, out IncidentLight light ) {\n\t\tvec3 lVector = spotLight.position - geometry.position;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat angleCos = dot( light.direction, spotLight.direction );\n\t\tfloat spotAttenuation = getSpotAttenuation( spotLight.coneCos, spotLight.penumbraCos, angleCos );\n\t\tif ( spotAttenuation > 0.0 ) {\n\t\t\tfloat lightDistance = length( lVector );\n\t\t\tlight.color = spotLight.color * spotAttenuation;\n\t\t\tlight.color *= getDistanceAttenuation( lightDistance, spotLight.distance, spotLight.decay );\n\t\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t\t} else {\n\t\t\tlight.color = vec3( 0.0 );\n\t\t\tlight.visible = false;\n\t\t}\n\t}\n#endif\n#if NUM_RECT_AREA_LIGHTS > 0\n\tstruct RectAreaLight {\n\t\tvec3 color;\n\t\tvec3 position;\n\t\tvec3 halfWidth;\n\t\tvec3 halfHeight;\n\t};\n\tuniform sampler2D ltc_1;\tuniform sampler2D ltc_2;\n\tuniform RectAreaLight rectAreaLights[ NUM_RECT_AREA_LIGHTS ];\n#endif\n#if NUM_HEMI_LIGHTS > 0\n\tstruct HemisphereLight {\n\t\tvec3 direction;\n\t\tvec3 skyColor;\n\t\tvec3 groundColor;\n\t};\n\tuniform HemisphereLight hemisphereLights[ NUM_HEMI_LIGHTS ];\n\tvec3 getHemisphereLightIrradiance( const in HemisphereLight hemiLight, const in vec3 normal ) {\n\t\tfloat dotNL = dot( normal, hemiLight.direction );\n\t\tfloat hemiDiffuseWeight = 0.5 * dotNL + 0.5;\n\t\tvec3 irradiance = mix( hemiLight.groundColor, hemiLight.skyColor, hemiDiffuseWeight );\n\t\treturn irradiance;\n\t}\n#endif"; - - var envmap_physical_pars_fragment = "#ifdef USE_ENVMAP\n\tvec3 getIBLIrradiance( const in vec3 normal ) {\n\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, worldNormal, 1.0 );\n\t\t\treturn PI * envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n\tvec3 getIBLRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness ) {\n\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\tvec3 reflectVec = reflect( - viewDir, normal );\n\t\t\treflectVec = normalize( mix( reflectVec, normal, roughness * roughness) );\n\t\t\treflectVec = inverseTransformDirection( reflectVec, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, reflectVec, roughness );\n\t\t\treturn envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n\t#ifdef USE_ANISOTROPY\n\t\tvec3 getIBLAnisotropyRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness, const in vec3 bitangent, const in float anisotropy ) {\n\t\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\t\tvec3 bentNormal = cross( bitangent, viewDir );\n\t\t\t\tbentNormal = normalize( cross( bentNormal, bitangent ) );\n\t\t\t\tbentNormal = normalize( mix( bentNormal, normal, pow2( pow2( 1.0 - anisotropy * ( 1.0 - roughness ) ) ) ) );\n\t\t\t\treturn getIBLRadiance( viewDir, bentNormal, roughness );\n\t\t\t#else\n\t\t\t\treturn vec3( 0.0 );\n\t\t\t#endif\n\t\t}\n\t#endif\n#endif"; - - var lights_toon_fragment = "ToonMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;"; - - var lights_toon_pars_fragment = "varying vec3 vViewPosition;\nstruct ToonMaterial {\n\tvec3 diffuseColor;\n};\nvoid RE_Direct_Toon( const in IncidentLight directLight, const in GeometricContext geometry, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\tvec3 irradiance = getGradientIrradiance( geometry.normal, directLight.direction ) * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Toon( const in vec3 irradiance, const in GeometricContext geometry, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Toon\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Toon"; - - var lights_phong_fragment = "BlinnPhongMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularColor = specular;\nmaterial.specularShininess = shininess;\nmaterial.specularStrength = specularStrength;"; - - var lights_phong_pars_fragment = "varying vec3 vViewPosition;\nstruct BlinnPhongMaterial {\n\tvec3 diffuseColor;\n\tvec3 specularColor;\n\tfloat specularShininess;\n\tfloat specularStrength;\n};\nvoid RE_Direct_BlinnPhong( const in IncidentLight directLight, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n\treflectedLight.directSpecular += irradiance * BRDF_BlinnPhong( directLight.direction, geometry.viewDir, geometry.normal, material.specularColor, material.specularShininess ) * material.specularStrength;\n}\nvoid RE_IndirectDiffuse_BlinnPhong( const in vec3 irradiance, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_BlinnPhong\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_BlinnPhong"; - - var lights_physical_fragment = "PhysicalMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb * ( 1.0 - metalnessFactor );\nvec3 dxy = max( abs( dFdx( geometryNormal ) ), abs( dFdy( geometryNormal ) ) );\nfloat geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z );\nmaterial.roughness = max( roughnessFactor, 0.0525 );material.roughness += geometryRoughness;\nmaterial.roughness = min( material.roughness, 1.0 );\n#ifdef IOR\n\tmaterial.ior = ior;\n\t#ifdef USE_SPECULAR\n\t\tfloat specularIntensityFactor = specularIntensity;\n\t\tvec3 specularColorFactor = specularColor;\n\t\t#ifdef USE_SPECULAR_COLORMAP\n\t\t\tspecularColorFactor *= texture2D( specularColorMap, vSpecularColorMapUv ).rgb;\n\t\t#endif\n\t\t#ifdef USE_SPECULAR_INTENSITYMAP\n\t\t\tspecularIntensityFactor *= texture2D( specularIntensityMap, vSpecularIntensityMapUv ).a;\n\t\t#endif\n\t\tmaterial.specularF90 = mix( specularIntensityFactor, 1.0, metalnessFactor );\n\t#else\n\t\tfloat specularIntensityFactor = 1.0;\n\t\tvec3 specularColorFactor = vec3( 1.0 );\n\t\tmaterial.specularF90 = 1.0;\n\t#endif\n\tmaterial.specularColor = mix( min( pow2( ( material.ior - 1.0 ) / ( material.ior + 1.0 ) ) * specularColorFactor, vec3( 1.0 ) ) * specularIntensityFactor, diffuseColor.rgb, metalnessFactor );\n#else\n\tmaterial.specularColor = mix( vec3( 0.04 ), diffuseColor.rgb, metalnessFactor );\n\tmaterial.specularF90 = 1.0;\n#endif\n#ifdef USE_CLEARCOAT\n\tmaterial.clearcoat = clearcoat;\n\tmaterial.clearcoatRoughness = clearcoatRoughness;\n\tmaterial.clearcoatF0 = vec3( 0.04 );\n\tmaterial.clearcoatF90 = 1.0;\n\t#ifdef USE_CLEARCOATMAP\n\t\tmaterial.clearcoat *= texture2D( clearcoatMap, vClearcoatMapUv ).x;\n\t#endif\n\t#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\t\tmaterial.clearcoatRoughness *= texture2D( clearcoatRoughnessMap, vClearcoatRoughnessMapUv ).y;\n\t#endif\n\tmaterial.clearcoat = saturate( material.clearcoat );\tmaterial.clearcoatRoughness = max( material.clearcoatRoughness, 0.0525 );\n\tmaterial.clearcoatRoughness += geometryRoughness;\n\tmaterial.clearcoatRoughness = min( material.clearcoatRoughness, 1.0 );\n#endif\n#ifdef USE_IRIDESCENCE\n\tmaterial.iridescence = iridescence;\n\tmaterial.iridescenceIOR = iridescenceIOR;\n\t#ifdef USE_IRIDESCENCEMAP\n\t\tmaterial.iridescence *= texture2D( iridescenceMap, vIridescenceMapUv ).r;\n\t#endif\n\t#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\t\tmaterial.iridescenceThickness = (iridescenceThicknessMaximum - iridescenceThicknessMinimum) * texture2D( iridescenceThicknessMap, vIridescenceThicknessMapUv ).g + iridescenceThicknessMinimum;\n\t#else\n\t\tmaterial.iridescenceThickness = iridescenceThicknessMaximum;\n\t#endif\n#endif\n#ifdef USE_SHEEN\n\tmaterial.sheenColor = sheenColor;\n\t#ifdef USE_SHEEN_COLORMAP\n\t\tmaterial.sheenColor *= texture2D( sheenColorMap, vSheenColorMapUv ).rgb;\n\t#endif\n\tmaterial.sheenRoughness = clamp( sheenRoughness, 0.07, 1.0 );\n\t#ifdef USE_SHEEN_ROUGHNESSMAP\n\t\tmaterial.sheenRoughness *= texture2D( sheenRoughnessMap, vSheenRoughnessMapUv ).a;\n\t#endif\n#endif\n#ifdef USE_ANISOTROPY\n\t#ifdef USE_ANISOTROPYMAP\n\t\tmat2 anisotropyMat = mat2( anisotropyVector.x, anisotropyVector.y, - anisotropyVector.y, anisotropyVector.x );\n\t\tvec3 anisotropyPolar = texture2D( anisotropyMap, vAnisotropyMapUv ).rgb;\n\t\tvec2 anisotropyV = anisotropyMat * normalize( 2.0 * anisotropyPolar.rg - vec2( 1.0 ) ) * anisotropyPolar.b;\n\t#else\n\t\tvec2 anisotropyV = anisotropyVector;\n\t#endif\n\tmaterial.anisotropy = length( anisotropyV );\n\tanisotropyV /= material.anisotropy;\n\tmaterial.anisotropy = saturate( material.anisotropy );\n\tmaterial.alphaT = mix( pow2( material.roughness ), 1.0, pow2( material.anisotropy ) );\n\tmaterial.anisotropyT = tbn[ 0 ] * anisotropyV.x - tbn[ 1 ] * anisotropyV.y;\n\tmaterial.anisotropyB = tbn[ 1 ] * anisotropyV.x + tbn[ 0 ] * anisotropyV.y;\n#endif"; - - var lights_physical_pars_fragment = "struct PhysicalMaterial {\n\tvec3 diffuseColor;\n\tfloat roughness;\n\tvec3 specularColor;\n\tfloat specularF90;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat clearcoat;\n\t\tfloat clearcoatRoughness;\n\t\tvec3 clearcoatF0;\n\t\tfloat clearcoatF90;\n\t#endif\n\t#ifdef USE_IRIDESCENCE\n\t\tfloat iridescence;\n\t\tfloat iridescenceIOR;\n\t\tfloat iridescenceThickness;\n\t\tvec3 iridescenceFresnel;\n\t\tvec3 iridescenceF0;\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tvec3 sheenColor;\n\t\tfloat sheenRoughness;\n\t#endif\n\t#ifdef IOR\n\t\tfloat ior;\n\t#endif\n\t#ifdef USE_TRANSMISSION\n\t\tfloat transmission;\n\t\tfloat transmissionAlpha;\n\t\tfloat thickness;\n\t\tfloat attenuationDistance;\n\t\tvec3 attenuationColor;\n\t#endif\n\t#ifdef USE_ANISOTROPY\n\t\tfloat anisotropy;\n\t\tfloat alphaT;\n\t\tvec3 anisotropyT;\n\t\tvec3 anisotropyB;\n\t#endif\n};\nvec3 clearcoatSpecular = vec3( 0.0 );\nvec3 sheenSpecular = vec3( 0.0 );\nvec3 Schlick_to_F0( const in vec3 f, const in float f90, const in float dotVH ) {\n float x = clamp( 1.0 - dotVH, 0.0, 1.0 );\n float x2 = x * x;\n float x5 = clamp( x * x2 * x2, 0.0, 0.9999 );\n return ( f - vec3( f90 ) * x5 ) / ( 1.0 - x5 );\n}\nfloat V_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const in float dotNV ) {\n\tfloat a2 = pow2( alpha );\n\tfloat gv = dotNL * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );\n\tfloat gl = dotNV * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );\n\treturn 0.5 / max( gv + gl, EPSILON );\n}\nfloat D_GGX( const in float alpha, const in float dotNH ) {\n\tfloat a2 = pow2( alpha );\n\tfloat denom = pow2( dotNH ) * ( a2 - 1.0 ) + 1.0;\n\treturn RECIPROCAL_PI * a2 / pow2( denom );\n}\n#ifdef USE_ANISOTROPY\n\tfloat V_GGX_SmithCorrelated_Anisotropic( const in float alphaT, const in float alphaB, const in float dotTV, const in float dotBV, const in float dotTL, const in float dotBL, const in float dotNV, const in float dotNL ) {\n\t\tfloat gv = dotNL * length( vec3( alphaT * dotTV, alphaB * dotBV, dotNV ) );\n\t\tfloat gl = dotNV * length( vec3( alphaT * dotTL, alphaB * dotBL, dotNL ) );\n\t\tfloat v = 0.5 / ( gv + gl );\n\t\treturn saturate(v);\n\t}\n\tfloat D_GGX_Anisotropic( const in float alphaT, const in float alphaB, const in float dotNH, const in float dotTH, const in float dotBH ) {\n\t\tfloat a2 = alphaT * alphaB;\n\t\thighp vec3 v = vec3( alphaB * dotTH, alphaT * dotBH, a2 * dotNH );\n\t\thighp float v2 = dot( v, v );\n\t\tfloat w2 = a2 / v2;\n\t\treturn RECIPROCAL_PI * a2 * pow2 ( w2 );\n\t}\n#endif\n#ifdef USE_CLEARCOAT\n\tvec3 BRDF_GGX_Clearcoat( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in PhysicalMaterial material) {\n\t\tvec3 f0 = material.clearcoatF0;\n\t\tfloat f90 = material.clearcoatF90;\n\t\tfloat roughness = material.clearcoatRoughness;\n\t\tfloat alpha = pow2( roughness );\n\t\tvec3 halfDir = normalize( lightDir + viewDir );\n\t\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\t\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\t\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\t\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\t\tvec3 F = F_Schlick( f0, f90, dotVH );\n\t\tfloat V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\t\tfloat D = D_GGX( alpha, dotNH );\n\t\treturn F * ( V * D );\n\t}\n#endif\nvec3 BRDF_GGX( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in PhysicalMaterial material ) {\n\tvec3 f0 = material.specularColor;\n\tfloat f90 = material.specularF90;\n\tfloat roughness = material.roughness;\n\tfloat alpha = pow2( roughness );\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\tvec3 F = F_Schlick( f0, f90, dotVH );\n\t#ifdef USE_IRIDESCENCE\n\t\tF = mix( F, material.iridescenceFresnel, material.iridescence );\n\t#endif\n\t#ifdef USE_ANISOTROPY\n\t\tfloat dotTL = dot( material.anisotropyT, lightDir );\n\t\tfloat dotTV = dot( material.anisotropyT, viewDir );\n\t\tfloat dotTH = dot( material.anisotropyT, halfDir );\n\t\tfloat dotBL = dot( material.anisotropyB, lightDir );\n\t\tfloat dotBV = dot( material.anisotropyB, viewDir );\n\t\tfloat dotBH = dot( material.anisotropyB, halfDir );\n\t\tfloat V = V_GGX_SmithCorrelated_Anisotropic( material.alphaT, alpha, dotTV, dotBV, dotTL, dotBL, dotNV, dotNL );\n\t\tfloat D = D_GGX_Anisotropic( material.alphaT, alpha, dotNH, dotTH, dotBH );\n\t#else\n\t\tfloat V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\t\tfloat D = D_GGX( alpha, dotNH );\n\t#endif\n\treturn F * ( V * D );\n}\nvec2 LTC_Uv( const in vec3 N, const in vec3 V, const in float roughness ) {\n\tconst float LUT_SIZE = 64.0;\n\tconst float LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE;\n\tconst float LUT_BIAS = 0.5 / LUT_SIZE;\n\tfloat dotNV = saturate( dot( N, V ) );\n\tvec2 uv = vec2( roughness, sqrt( 1.0 - dotNV ) );\n\tuv = uv * LUT_SCALE + LUT_BIAS;\n\treturn uv;\n}\nfloat LTC_ClippedSphereFormFactor( const in vec3 f ) {\n\tfloat l = length( f );\n\treturn max( ( l * l + f.z ) / ( l + 1.0 ), 0.0 );\n}\nvec3 LTC_EdgeVectorFormFactor( const in vec3 v1, const in vec3 v2 ) {\n\tfloat x = dot( v1, v2 );\n\tfloat y = abs( x );\n\tfloat a = 0.8543985 + ( 0.4965155 + 0.0145206 * y ) * y;\n\tfloat b = 3.4175940 + ( 4.1616724 + y ) * y;\n\tfloat v = a / b;\n\tfloat theta_sintheta = ( x > 0.0 ) ? v : 0.5 * inversesqrt( max( 1.0 - x * x, 1e-7 ) ) - v;\n\treturn cross( v1, v2 ) * theta_sintheta;\n}\nvec3 LTC_Evaluate( const in vec3 N, const in vec3 V, const in vec3 P, const in mat3 mInv, const in vec3 rectCoords[ 4 ] ) {\n\tvec3 v1 = rectCoords[ 1 ] - rectCoords[ 0 ];\n\tvec3 v2 = rectCoords[ 3 ] - rectCoords[ 0 ];\n\tvec3 lightNormal = cross( v1, v2 );\n\tif( dot( lightNormal, P - rectCoords[ 0 ] ) < 0.0 ) return vec3( 0.0 );\n\tvec3 T1, T2;\n\tT1 = normalize( V - N * dot( V, N ) );\n\tT2 = - cross( N, T1 );\n\tmat3 mat = mInv * transposeMat3( mat3( T1, T2, N ) );\n\tvec3 coords[ 4 ];\n\tcoords[ 0 ] = mat * ( rectCoords[ 0 ] - P );\n\tcoords[ 1 ] = mat * ( rectCoords[ 1 ] - P );\n\tcoords[ 2 ] = mat * ( rectCoords[ 2 ] - P );\n\tcoords[ 3 ] = mat * ( rectCoords[ 3 ] - P );\n\tcoords[ 0 ] = normalize( coords[ 0 ] );\n\tcoords[ 1 ] = normalize( coords[ 1 ] );\n\tcoords[ 2 ] = normalize( coords[ 2 ] );\n\tcoords[ 3 ] = normalize( coords[ 3 ] );\n\tvec3 vectorFormFactor = vec3( 0.0 );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 0 ], coords[ 1 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 1 ], coords[ 2 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 2 ], coords[ 3 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 3 ], coords[ 0 ] );\n\tfloat result = LTC_ClippedSphereFormFactor( vectorFormFactor );\n\treturn vec3( result );\n}\n#if defined( USE_SHEEN )\nfloat D_Charlie( float roughness, float dotNH ) {\n\tfloat alpha = pow2( roughness );\n\tfloat invAlpha = 1.0 / alpha;\n\tfloat cos2h = dotNH * dotNH;\n\tfloat sin2h = max( 1.0 - cos2h, 0.0078125 );\n\treturn ( 2.0 + invAlpha ) * pow( sin2h, invAlpha * 0.5 ) / ( 2.0 * PI );\n}\nfloat V_Neubelt( float dotNV, float dotNL ) {\n\treturn saturate( 1.0 / ( 4.0 * ( dotNL + dotNV - dotNL * dotNV ) ) );\n}\nvec3 BRDF_Sheen( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, vec3 sheenColor, const in float sheenRoughness ) {\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat D = D_Charlie( sheenRoughness, dotNH );\n\tfloat V = V_Neubelt( dotNV, dotNL );\n\treturn sheenColor * ( D * V );\n}\n#endif\nfloat IBLSheenBRDF( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat r2 = roughness * roughness;\n\tfloat a = roughness < 0.25 ? -339.2 * r2 + 161.4 * roughness - 25.9 : -8.48 * r2 + 14.3 * roughness - 9.95;\n\tfloat b = roughness < 0.25 ? 44.0 * r2 - 23.7 * roughness + 3.26 : 1.97 * r2 - 3.27 * roughness + 0.72;\n\tfloat DG = exp( a * dotNV + b ) + ( roughness < 0.25 ? 0.0 : 0.1 * ( roughness - 0.25 ) );\n\treturn saturate( DG * RECIPROCAL_PI );\n}\nvec2 DFGApprox( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tconst vec4 c0 = vec4( - 1, - 0.0275, - 0.572, 0.022 );\n\tconst vec4 c1 = vec4( 1, 0.0425, 1.04, - 0.04 );\n\tvec4 r = roughness * c0 + c1;\n\tfloat a004 = min( r.x * r.x, exp2( - 9.28 * dotNV ) ) * r.x + r.y;\n\tvec2 fab = vec2( - 1.04, 1.04 ) * a004 + r.zw;\n\treturn fab;\n}\nvec3 EnvironmentBRDF( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness ) {\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\treturn specularColor * fab.x + specularF90 * fab.y;\n}\n#ifdef USE_IRIDESCENCE\nvoid computeMultiscatteringIridescence( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float iridescence, const in vec3 iridescenceF0, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#else\nvoid computeMultiscattering( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#endif\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\t#ifdef USE_IRIDESCENCE\n\t\tvec3 Fr = mix( specularColor, iridescenceF0, iridescence );\n\t#else\n\t\tvec3 Fr = specularColor;\n\t#endif\n\tvec3 FssEss = Fr * fab.x + specularF90 * fab.y;\n\tfloat Ess = fab.x + fab.y;\n\tfloat Ems = 1.0 - Ess;\n\tvec3 Favg = Fr + ( 1.0 - Fr ) * 0.047619;\tvec3 Fms = FssEss * Favg / ( 1.0 - Ems * Favg );\n\tsingleScatter += FssEss;\n\tmultiScatter += Fms * Ems;\n}\n#if NUM_RECT_AREA_LIGHTS > 0\n\tvoid RE_Direct_RectArea_Physical( const in RectAreaLight rectAreaLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\t\tvec3 normal = geometry.normal;\n\t\tvec3 viewDir = geometry.viewDir;\n\t\tvec3 position = geometry.position;\n\t\tvec3 lightPos = rectAreaLight.position;\n\t\tvec3 halfWidth = rectAreaLight.halfWidth;\n\t\tvec3 halfHeight = rectAreaLight.halfHeight;\n\t\tvec3 lightColor = rectAreaLight.color;\n\t\tfloat roughness = material.roughness;\n\t\tvec3 rectCoords[ 4 ];\n\t\trectCoords[ 0 ] = lightPos + halfWidth - halfHeight;\t\trectCoords[ 1 ] = lightPos - halfWidth - halfHeight;\n\t\trectCoords[ 2 ] = lightPos - halfWidth + halfHeight;\n\t\trectCoords[ 3 ] = lightPos + halfWidth + halfHeight;\n\t\tvec2 uv = LTC_Uv( normal, viewDir, roughness );\n\t\tvec4 t1 = texture2D( ltc_1, uv );\n\t\tvec4 t2 = texture2D( ltc_2, uv );\n\t\tmat3 mInv = mat3(\n\t\t\tvec3( t1.x, 0, t1.y ),\n\t\t\tvec3( 0, 1, 0 ),\n\t\t\tvec3( t1.z, 0, t1.w )\n\t\t);\n\t\tvec3 fresnel = ( material.specularColor * t2.x + ( vec3( 1.0 ) - material.specularColor ) * t2.y );\n\t\treflectedLight.directSpecular += lightColor * fresnel * LTC_Evaluate( normal, viewDir, position, mInv, rectCoords );\n\t\treflectedLight.directDiffuse += lightColor * material.diffuseColor * LTC_Evaluate( normal, viewDir, position, mat3( 1.0 ), rectCoords );\n\t}\n#endif\nvoid RE_Direct_Physical( const in IncidentLight directLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNLcc = saturate( dot( geometry.clearcoatNormal, directLight.direction ) );\n\t\tvec3 ccIrradiance = dotNLcc * directLight.color;\n\t\tclearcoatSpecular += ccIrradiance * BRDF_GGX_Clearcoat( directLight.direction, geometry.viewDir, geometry.clearcoatNormal, material );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tsheenSpecular += irradiance * BRDF_Sheen( directLight.direction, geometry.viewDir, geometry.normal, material.sheenColor, material.sheenRoughness );\n\t#endif\n\treflectedLight.directSpecular += irradiance * BRDF_GGX( directLight.direction, geometry.viewDir, geometry.normal, material );\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Physical( const in vec3 irradiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 irradiance, const in vec3 clearcoatRadiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatSpecular += clearcoatRadiance * EnvironmentBRDF( geometry.clearcoatNormal, geometry.viewDir, material.clearcoatF0, material.clearcoatF90, material.clearcoatRoughness );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tsheenSpecular += irradiance * material.sheenColor * IBLSheenBRDF( geometry.normal, geometry.viewDir, material.sheenRoughness );\n\t#endif\n\tvec3 singleScattering = vec3( 0.0 );\n\tvec3 multiScattering = vec3( 0.0 );\n\tvec3 cosineWeightedIrradiance = irradiance * RECIPROCAL_PI;\n\t#ifdef USE_IRIDESCENCE\n\t\tcomputeMultiscatteringIridescence( geometry.normal, geometry.viewDir, material.specularColor, material.specularF90, material.iridescence, material.iridescenceFresnel, material.roughness, singleScattering, multiScattering );\n\t#else\n\t\tcomputeMultiscattering( geometry.normal, geometry.viewDir, material.specularColor, material.specularF90, material.roughness, singleScattering, multiScattering );\n\t#endif\n\tvec3 totalScattering = singleScattering + multiScattering;\n\tvec3 diffuse = material.diffuseColor * ( 1.0 - max( max( totalScattering.r, totalScattering.g ), totalScattering.b ) );\n\treflectedLight.indirectSpecular += radiance * singleScattering;\n\treflectedLight.indirectSpecular += multiScattering * cosineWeightedIrradiance;\n\treflectedLight.indirectDiffuse += diffuse * cosineWeightedIrradiance;\n}\n#define RE_Direct\t\t\t\tRE_Direct_Physical\n#define RE_Direct_RectArea\t\tRE_Direct_RectArea_Physical\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Physical\n#define RE_IndirectSpecular\t\tRE_IndirectSpecular_Physical\nfloat computeSpecularOcclusion( const in float dotNV, const in float ambientOcclusion, const in float roughness ) {\n\treturn saturate( pow( dotNV + ambientOcclusion, exp2( - 16.0 * roughness - 1.0 ) ) - 1.0 + ambientOcclusion );\n}"; - - var lights_fragment_begin = "\nGeometricContext geometry;\ngeometry.position = - vViewPosition;\ngeometry.normal = normal;\ngeometry.viewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( vViewPosition );\n#ifdef USE_CLEARCOAT\n\tgeometry.clearcoatNormal = clearcoatNormal;\n#endif\n#ifdef USE_IRIDESCENCE\n\tfloat dotNVi = saturate( dot( normal, geometry.viewDir ) );\n\tif ( material.iridescenceThickness == 0.0 ) {\n\t\tmaterial.iridescence = 0.0;\n\t} else {\n\t\tmaterial.iridescence = saturate( material.iridescence );\n\t}\n\tif ( material.iridescence > 0.0 ) {\n\t\tmaterial.iridescenceFresnel = evalIridescence( 1.0, material.iridescenceIOR, dotNVi, material.iridescenceThickness, material.specularColor );\n\t\tmaterial.iridescenceF0 = Schlick_to_F0( material.iridescenceFresnel, 1.0, dotNVi );\n\t}\n#endif\nIncidentLight directLight;\n#if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct )\n\tPointLight pointLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tpointLight = pointLights[ i ];\n\t\tgetPointLightInfo( pointLight, geometry, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_POINT_LIGHT_SHADOWS )\n\t\tpointLightShadow = pointLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getPointShadow( pointShadowMap[ i ], pointLightShadow.shadowMapSize, pointLightShadow.shadowBias, pointLightShadow.shadowRadius, vPointShadowCoord[ i ], pointLightShadow.shadowCameraNear, pointLightShadow.shadowCameraFar ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct )\n\tSpotLight spotLight;\n\tvec4 spotColor;\n\tvec3 spotLightCoord;\n\tbool inSpotLightMap;\n\t#if defined( USE_SHADOWMAP ) && NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tspotLight = spotLights[ i ];\n\t\tgetSpotLightInfo( spotLight, geometry, directLight );\n\t\t#if ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )\n\t\t#define SPOT_LIGHT_MAP_INDEX UNROLLED_LOOP_INDEX\n\t\t#elif ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\t#define SPOT_LIGHT_MAP_INDEX NUM_SPOT_LIGHT_MAPS\n\t\t#else\n\t\t#define SPOT_LIGHT_MAP_INDEX ( UNROLLED_LOOP_INDEX - NUM_SPOT_LIGHT_SHADOWS + NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )\n\t\t#endif\n\t\t#if ( SPOT_LIGHT_MAP_INDEX < NUM_SPOT_LIGHT_MAPS )\n\t\t\tspotLightCoord = vSpotLightCoord[ i ].xyz / vSpotLightCoord[ i ].w;\n\t\t\tinSpotLightMap = all( lessThan( abs( spotLightCoord * 2. - 1. ), vec3( 1.0 ) ) );\n\t\t\tspotColor = texture2D( spotLightMap[ SPOT_LIGHT_MAP_INDEX ], spotLightCoord.xy );\n\t\t\tdirectLight.color = inSpotLightMap ? directLight.color * spotColor.rgb : directLight.color;\n\t\t#endif\n\t\t#undef SPOT_LIGHT_MAP_INDEX\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\tspotLightShadow = spotLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( spotShadowMap[ i ], spotLightShadow.shadowMapSize, spotLightShadow.shadowBias, spotLightShadow.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct )\n\tDirectionalLight directionalLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tdirectionalLight = directionalLights[ i ];\n\t\tgetDirectionalLightInfo( directionalLight, geometry, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )\n\t\tdirectionalLightShadow = directionalLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea )\n\tRectAreaLight rectAreaLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) {\n\t\trectAreaLight = rectAreaLights[ i ];\n\t\tRE_Direct_RectArea( rectAreaLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if defined( RE_IndirectDiffuse )\n\tvec3 iblIrradiance = vec3( 0.0 );\n\tvec3 irradiance = getAmbientLightIrradiance( ambientLightColor );\n\tirradiance += getLightProbeIrradiance( lightProbe, geometry.normal );\n\t#if ( NUM_HEMI_LIGHTS > 0 )\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n\t\t\tirradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry.normal );\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n#endif\n#if defined( RE_IndirectSpecular )\n\tvec3 radiance = vec3( 0.0 );\n\tvec3 clearcoatRadiance = vec3( 0.0 );\n#endif"; - - var lights_fragment_maps = "#if defined( RE_IndirectDiffuse )\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel = texture2D( lightMap, vLightMapUv );\n\t\tvec3 lightMapIrradiance = lightMapTexel.rgb * lightMapIntensity;\n\t\tirradiance += lightMapIrradiance;\n\t#endif\n\t#if defined( USE_ENVMAP ) && defined( STANDARD ) && defined( ENVMAP_TYPE_CUBE_UV )\n\t\tiblIrradiance += getIBLIrradiance( geometry.normal );\n\t#endif\n#endif\n#if defined( USE_ENVMAP ) && defined( RE_IndirectSpecular )\n\t#ifdef USE_ANISOTROPY\n\t\tradiance += getIBLAnisotropyRadiance( geometry.viewDir, geometry.normal, material.roughness, material.anisotropyB, material.anisotropy );\n\t#else\n\t\tradiance += getIBLRadiance( geometry.viewDir, geometry.normal, material.roughness );\n\t#endif\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatRadiance += getIBLRadiance( geometry.viewDir, geometry.clearcoatNormal, material.clearcoatRoughness );\n\t#endif\n#endif"; - - var lights_fragment_end = "#if defined( RE_IndirectDiffuse )\n\tRE_IndirectDiffuse( irradiance, geometry, material, reflectedLight );\n#endif\n#if defined( RE_IndirectSpecular )\n\tRE_IndirectSpecular( radiance, iblIrradiance, clearcoatRadiance, geometry, material, reflectedLight );\n#endif"; - - var logdepthbuf_fragment = "#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tgl_FragDepthEXT = vIsPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\n#endif"; - - var logdepthbuf_pars_fragment = "#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tuniform float logDepthBufFC;\n\tvarying float vFragDepth;\n\tvarying float vIsPerspective;\n#endif"; - - var logdepthbuf_pars_vertex = "#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvarying float vFragDepth;\n\t\tvarying float vIsPerspective;\n\t#else\n\t\tuniform float logDepthBufFC;\n\t#endif\n#endif"; - - var logdepthbuf_vertex = "#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvFragDepth = 1.0 + gl_Position.w;\n\t\tvIsPerspective = float( isPerspectiveMatrix( projectionMatrix ) );\n\t#else\n\t\tif ( isPerspectiveMatrix( projectionMatrix ) ) {\n\t\t\tgl_Position.z = log2( max( EPSILON, gl_Position.w + 1.0 ) ) * logDepthBufFC - 1.0;\n\t\t\tgl_Position.z *= gl_Position.w;\n\t\t}\n\t#endif\n#endif"; - - var map_fragment = "#ifdef USE_MAP\n\tdiffuseColor *= texture2D( map, vMapUv );\n#endif"; - - var map_pars_fragment = "#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif"; - - var map_particle_fragment = "#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\t#if defined( USE_POINTS_UV )\n\t\tvec2 uv = vUv;\n\t#else\n\t\tvec2 uv = ( uvTransform * vec3( gl_PointCoord.x, 1.0 - gl_PointCoord.y, 1 ) ).xy;\n\t#endif\n#endif\n#ifdef USE_MAP\n\tdiffuseColor *= texture2D( map, uv );\n#endif\n#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, uv ).g;\n#endif"; - - var map_particle_pars_fragment = "#if defined( USE_POINTS_UV )\n\tvarying vec2 vUv;\n#else\n\t#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\t\tuniform mat3 uvTransform;\n\t#endif\n#endif\n#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif\n#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif"; - - var metalnessmap_fragment = "float metalnessFactor = metalness;\n#ifdef USE_METALNESSMAP\n\tvec4 texelMetalness = texture2D( metalnessMap, vMetalnessMapUv );\n\tmetalnessFactor *= texelMetalness.b;\n#endif"; - - var metalnessmap_pars_fragment = "#ifdef USE_METALNESSMAP\n\tuniform sampler2D metalnessMap;\n#endif"; - - var morphcolor_vertex = "#if defined( USE_MORPHCOLORS ) && defined( MORPHTARGETS_TEXTURE )\n\tvColor *= morphTargetBaseInfluence;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t#if defined( USE_COLOR_ALPHA )\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ) * morphTargetInfluences[ i ];\n\t\t#elif defined( USE_COLOR )\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ).rgb * morphTargetInfluences[ i ];\n\t\t#endif\n\t}\n#endif"; - - var morphnormal_vertex = "#ifdef USE_MORPHNORMALS\n\tobjectNormal *= morphTargetBaseInfluence;\n\t#ifdef MORPHTARGETS_TEXTURE\n\t\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) objectNormal += getMorph( gl_VertexID, i, 1 ).xyz * morphTargetInfluences[ i ];\n\t\t}\n\t#else\n\t\tobjectNormal += morphNormal0 * morphTargetInfluences[ 0 ];\n\t\tobjectNormal += morphNormal1 * morphTargetInfluences[ 1 ];\n\t\tobjectNormal += morphNormal2 * morphTargetInfluences[ 2 ];\n\t\tobjectNormal += morphNormal3 * morphTargetInfluences[ 3 ];\n\t#endif\n#endif"; - - var morphtarget_pars_vertex = "#ifdef USE_MORPHTARGETS\n\tuniform float morphTargetBaseInfluence;\n\t#ifdef MORPHTARGETS_TEXTURE\n\t\tuniform float morphTargetInfluences[ MORPHTARGETS_COUNT ];\n\t\tuniform sampler2DArray morphTargetsTexture;\n\t\tuniform ivec2 morphTargetsTextureSize;\n\t\tvec4 getMorph( const in int vertexIndex, const in int morphTargetIndex, const in int offset ) {\n\t\t\tint texelIndex = vertexIndex * MORPHTARGETS_TEXTURE_STRIDE + offset;\n\t\t\tint y = texelIndex / morphTargetsTextureSize.x;\n\t\t\tint x = texelIndex - y * morphTargetsTextureSize.x;\n\t\t\tivec3 morphUV = ivec3( x, y, morphTargetIndex );\n\t\t\treturn texelFetch( morphTargetsTexture, morphUV, 0 );\n\t\t}\n\t#else\n\t\t#ifndef USE_MORPHNORMALS\n\t\t\tuniform float morphTargetInfluences[ 8 ];\n\t\t#else\n\t\t\tuniform float morphTargetInfluences[ 4 ];\n\t\t#endif\n\t#endif\n#endif"; - - var morphtarget_vertex = "#ifdef USE_MORPHTARGETS\n\ttransformed *= morphTargetBaseInfluence;\n\t#ifdef MORPHTARGETS_TEXTURE\n\t\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) transformed += getMorph( gl_VertexID, i, 0 ).xyz * morphTargetInfluences[ i ];\n\t\t}\n\t#else\n\t\ttransformed += morphTarget0 * morphTargetInfluences[ 0 ];\n\t\ttransformed += morphTarget1 * morphTargetInfluences[ 1 ];\n\t\ttransformed += morphTarget2 * morphTargetInfluences[ 2 ];\n\t\ttransformed += morphTarget3 * morphTargetInfluences[ 3 ];\n\t\t#ifndef USE_MORPHNORMALS\n\t\t\ttransformed += morphTarget4 * morphTargetInfluences[ 4 ];\n\t\t\ttransformed += morphTarget5 * morphTargetInfluences[ 5 ];\n\t\t\ttransformed += morphTarget6 * morphTargetInfluences[ 6 ];\n\t\t\ttransformed += morphTarget7 * morphTargetInfluences[ 7 ];\n\t\t#endif\n\t#endif\n#endif"; - - var normal_fragment_begin = "float faceDirection = gl_FrontFacing ? 1.0 : - 1.0;\n#ifdef FLAT_SHADED\n\tvec3 fdx = dFdx( vViewPosition );\n\tvec3 fdy = dFdy( vViewPosition );\n\tvec3 normal = normalize( cross( fdx, fdy ) );\n#else\n\tvec3 normal = normalize( vNormal );\n\t#ifdef DOUBLE_SIDED\n\t\tnormal *= faceDirection;\n\t#endif\n#endif\n#if defined( USE_NORMALMAP_TANGENTSPACE ) || defined( USE_CLEARCOAT_NORMALMAP ) || defined( USE_ANISOTROPY )\n\t#ifdef USE_TANGENT\n\t\tmat3 tbn = mat3( normalize( vTangent ), normalize( vBitangent ), normal );\n\t#else\n\t\tmat3 tbn = getTangentFrame( - vViewPosition, normal, vNormalMapUv );\n\t#endif\n\t#if defined( DOUBLE_SIDED ) && ! defined( FLAT_SHADED )\n\t\ttbn[0] *= faceDirection;\n\t\ttbn[1] *= faceDirection;\n\t#endif\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\t#ifdef USE_TANGENT\n\t\tmat3 tbn2 = mat3( normalize( vTangent ), normalize( vBitangent ), normal );\n\t#else\n\t\tmat3 tbn2 = getTangentFrame( - vViewPosition, normal, vClearcoatNormalMapUv );\n\t#endif\n\t#if defined( DOUBLE_SIDED ) && ! defined( FLAT_SHADED )\n\t\ttbn2[0] *= faceDirection;\n\t\ttbn2[1] *= faceDirection;\n\t#endif\n#endif\nvec3 geometryNormal = normal;"; - - var normal_fragment_maps = "#ifdef USE_NORMALMAP_OBJECTSPACE\n\tnormal = texture2D( normalMap, vNormalMapUv ).xyz * 2.0 - 1.0;\n\t#ifdef FLIP_SIDED\n\t\tnormal = - normal;\n\t#endif\n\t#ifdef DOUBLE_SIDED\n\t\tnormal = normal * faceDirection;\n\t#endif\n\tnormal = normalize( normalMatrix * normal );\n#elif defined( USE_NORMALMAP_TANGENTSPACE )\n\tvec3 mapN = texture2D( normalMap, vNormalMapUv ).xyz * 2.0 - 1.0;\n\tmapN.xy *= normalScale;\n\tnormal = normalize( tbn * mapN );\n#elif defined( USE_BUMPMAP )\n\tnormal = perturbNormalArb( - vViewPosition, normal, dHdxy_fwd(), faceDirection );\n#endif"; - - var normal_pars_fragment = "#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif"; - - var normal_pars_vertex = "#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif"; - - var normal_vertex = "#ifndef FLAT_SHADED\n\tvNormal = normalize( transformedNormal );\n\t#ifdef USE_TANGENT\n\t\tvTangent = normalize( transformedTangent );\n\t\tvBitangent = normalize( cross( vNormal, vTangent ) * tangent.w );\n\t#endif\n#endif"; - - var normalmap_pars_fragment = "#ifdef USE_NORMALMAP\n\tuniform sampler2D normalMap;\n\tuniform vec2 normalScale;\n#endif\n#ifdef USE_NORMALMAP_OBJECTSPACE\n\tuniform mat3 normalMatrix;\n#endif\n#if ! defined ( USE_TANGENT ) && ( defined ( USE_NORMALMAP_TANGENTSPACE ) || defined ( USE_CLEARCOAT_NORMALMAP ) || defined( USE_ANISOTROPY ) )\n\tmat3 getTangentFrame( vec3 eye_pos, vec3 surf_norm, vec2 uv ) {\n\t\tvec3 q0 = dFdx( eye_pos.xyz );\n\t\tvec3 q1 = dFdy( eye_pos.xyz );\n\t\tvec2 st0 = dFdx( uv.st );\n\t\tvec2 st1 = dFdy( uv.st );\n\t\tvec3 N = surf_norm;\n\t\tvec3 q1perp = cross( q1, N );\n\t\tvec3 q0perp = cross( N, q0 );\n\t\tvec3 T = q1perp * st0.x + q0perp * st1.x;\n\t\tvec3 B = q1perp * st0.y + q0perp * st1.y;\n\t\tfloat det = max( dot( T, T ), dot( B, B ) );\n\t\tfloat scale = ( det == 0.0 ) ? 0.0 : inversesqrt( det );\n\t\treturn mat3( T * scale, B * scale, N );\n\t}\n#endif"; - - var clearcoat_normal_fragment_begin = "#ifdef USE_CLEARCOAT\n\tvec3 clearcoatNormal = geometryNormal;\n#endif"; - - var clearcoat_normal_fragment_maps = "#ifdef USE_CLEARCOAT_NORMALMAP\n\tvec3 clearcoatMapN = texture2D( clearcoatNormalMap, vClearcoatNormalMapUv ).xyz * 2.0 - 1.0;\n\tclearcoatMapN.xy *= clearcoatNormalScale;\n\tclearcoatNormal = normalize( tbn2 * clearcoatMapN );\n#endif"; - - var clearcoat_pars_fragment = "#ifdef USE_CLEARCOATMAP\n\tuniform sampler2D clearcoatMap;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tuniform sampler2D clearcoatNormalMap;\n\tuniform vec2 clearcoatNormalScale;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tuniform sampler2D clearcoatRoughnessMap;\n#endif"; - - var iridescence_pars_fragment = "#ifdef USE_IRIDESCENCEMAP\n\tuniform sampler2D iridescenceMap;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tuniform sampler2D iridescenceThicknessMap;\n#endif"; - - var output_fragment = "#ifdef OPAQUE\ndiffuseColor.a = 1.0;\n#endif\n#ifdef USE_TRANSMISSION\ndiffuseColor.a *= material.transmissionAlpha;\n#endif\ngl_FragColor = vec4( outgoingLight, diffuseColor.a );"; - - var packing = "vec3 packNormalToRGB( const in vec3 normal ) {\n\treturn normalize( normal ) * 0.5 + 0.5;\n}\nvec3 unpackRGBToNormal( const in vec3 rgb ) {\n\treturn 2.0 * rgb.xyz - 1.0;\n}\nconst float PackUpscale = 256. / 255.;const float UnpackDownscale = 255. / 256.;\nconst vec3 PackFactors = vec3( 256. * 256. * 256., 256. * 256., 256. );\nconst vec4 UnpackFactors = UnpackDownscale / vec4( PackFactors, 1. );\nconst float ShiftRight8 = 1. / 256.;\nvec4 packDepthToRGBA( const in float v ) {\n\tvec4 r = vec4( fract( v * PackFactors ), v );\n\tr.yzw -= r.xyz * ShiftRight8;\treturn r * PackUpscale;\n}\nfloat unpackRGBAToDepth( const in vec4 v ) {\n\treturn dot( v, UnpackFactors );\n}\nvec2 packDepthToRG( in highp float v ) {\n\treturn packDepthToRGBA( v ).yx;\n}\nfloat unpackRGToDepth( const in highp vec2 v ) {\n\treturn unpackRGBAToDepth( vec4( v.xy, 0.0, 0.0 ) );\n}\nvec4 pack2HalfToRGBA( vec2 v ) {\n\tvec4 r = vec4( v.x, fract( v.x * 255.0 ), v.y, fract( v.y * 255.0 ) );\n\treturn vec4( r.x - r.y / 255.0, r.y, r.z - r.w / 255.0, r.w );\n}\nvec2 unpackRGBATo2Half( vec4 v ) {\n\treturn vec2( v.x + ( v.y / 255.0 ), v.z + ( v.w / 255.0 ) );\n}\nfloat viewZToOrthographicDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( viewZ + near ) / ( near - far );\n}\nfloat orthographicDepthToViewZ( const in float depth, const in float near, const in float far ) {\n\treturn depth * ( near - far ) - near;\n}\nfloat viewZToPerspectiveDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( ( near + viewZ ) * far ) / ( ( far - near ) * viewZ );\n}\nfloat perspectiveDepthToViewZ( const in float depth, const in float near, const in float far ) {\n\treturn ( near * far ) / ( ( far - near ) * depth - far );\n}"; - - var premultiplied_alpha_fragment = "#ifdef PREMULTIPLIED_ALPHA\n\tgl_FragColor.rgb *= gl_FragColor.a;\n#endif"; - - var project_vertex = "vec4 mvPosition = vec4( transformed, 1.0 );\n#ifdef USE_INSTANCING\n\tmvPosition = instanceMatrix * mvPosition;\n#endif\nmvPosition = modelViewMatrix * mvPosition;\ngl_Position = projectionMatrix * mvPosition;"; - - var dithering_fragment = "#ifdef DITHERING\n\tgl_FragColor.rgb = dithering( gl_FragColor.rgb );\n#endif"; - - var dithering_pars_fragment = "#ifdef DITHERING\n\tvec3 dithering( vec3 color ) {\n\t\tfloat grid_position = rand( gl_FragCoord.xy );\n\t\tvec3 dither_shift_RGB = vec3( 0.25 / 255.0, -0.25 / 255.0, 0.25 / 255.0 );\n\t\tdither_shift_RGB = mix( 2.0 * dither_shift_RGB, -2.0 * dither_shift_RGB, grid_position );\n\t\treturn color + dither_shift_RGB;\n\t}\n#endif"; - - var roughnessmap_fragment = "float roughnessFactor = roughness;\n#ifdef USE_ROUGHNESSMAP\n\tvec4 texelRoughness = texture2D( roughnessMap, vRoughnessMapUv );\n\troughnessFactor *= texelRoughness.g;\n#endif"; - - var roughnessmap_pars_fragment = "#ifdef USE_ROUGHNESSMAP\n\tuniform sampler2D roughnessMap;\n#endif"; - - var shadowmap_pars_fragment = "#if NUM_SPOT_LIGHT_COORDS > 0\n\tvarying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ];\n#endif\n#if NUM_SPOT_LIGHT_MAPS > 0\n\tuniform sampler2D spotLightMap[ NUM_SPOT_LIGHT_MAPS ];\n#endif\n#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D directionalShadowMap[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D spotShadowMap[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D pointShadowMap[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n\tfloat texture2DCompare( sampler2D depths, vec2 uv, float compare ) {\n\t\treturn step( compare, unpackRGBAToDepth( texture2D( depths, uv ) ) );\n\t}\n\tvec2 texture2DDistribution( sampler2D shadow, vec2 uv ) {\n\t\treturn unpackRGBATo2Half( texture2D( shadow, uv ) );\n\t}\n\tfloat VSMShadow (sampler2D shadow, vec2 uv, float compare ){\n\t\tfloat occlusion = 1.0;\n\t\tvec2 distribution = texture2DDistribution( shadow, uv );\n\t\tfloat hard_shadow = step( compare , distribution.x );\n\t\tif (hard_shadow != 1.0 ) {\n\t\t\tfloat distance = compare - distribution.x ;\n\t\t\tfloat variance = max( 0.00000, distribution.y * distribution.y );\n\t\t\tfloat softness_probability = variance / (variance + distance * distance );\t\t\tsoftness_probability = clamp( ( softness_probability - 0.3 ) / ( 0.95 - 0.3 ), 0.0, 1.0 );\t\t\tocclusion = clamp( max( hard_shadow, softness_probability ), 0.0, 1.0 );\n\t\t}\n\t\treturn occlusion;\n\t}\n\tfloat getShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord ) {\n\t\tfloat shadow = 1.0;\n\t\tshadowCoord.xyz /= shadowCoord.w;\n\t\tshadowCoord.z += shadowBias;\n\t\tbool inFrustum = shadowCoord.x >= 0.0 && shadowCoord.x <= 1.0 && shadowCoord.y >= 0.0 && shadowCoord.y <= 1.0;\n\t\tbool frustumTest = inFrustum && shadowCoord.z <= 1.0;\n\t\tif ( frustumTest ) {\n\t\t#if defined( SHADOWMAP_TYPE_PCF )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx0 = - texelSize.x * shadowRadius;\n\t\t\tfloat dy0 = - texelSize.y * shadowRadius;\n\t\t\tfloat dx1 = + texelSize.x * shadowRadius;\n\t\t\tfloat dy1 = + texelSize.y * shadowRadius;\n\t\t\tfloat dx2 = dx0 / 2.0;\n\t\t\tfloat dy2 = dy0 / 2.0;\n\t\t\tfloat dx3 = dx1 / 2.0;\n\t\t\tfloat dy3 = dy1 / 2.0;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy1 ), shadowCoord.z )\n\t\t\t) * ( 1.0 / 17.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_PCF_SOFT )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx = texelSize.x;\n\t\t\tfloat dy = texelSize.y;\n\t\t\tvec2 uv = shadowCoord.xy;\n\t\t\tvec2 f = fract( uv * shadowMapSize + 0.5 );\n\t\t\tuv -= f * texelSize;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, uv, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( dx, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( 0.0, dy ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + texelSize, shadowCoord.z ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, dy ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( 0.0, -dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 0.0, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( mix( texture2DCompare( shadowMap, uv + vec2( -dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t f.x ),\n\t\t\t\t\t mix( texture2DCompare( shadowMap, uv + vec2( -dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t f.x ),\n\t\t\t\t\t f.y )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_VSM )\n\t\t\tshadow = VSMShadow( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#else\n\t\t\tshadow = texture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#endif\n\t\t}\n\t\treturn shadow;\n\t}\n\tvec2 cubeToUV( vec3 v, float texelSizeY ) {\n\t\tvec3 absV = abs( v );\n\t\tfloat scaleToCube = 1.0 / max( absV.x, max( absV.y, absV.z ) );\n\t\tabsV *= scaleToCube;\n\t\tv *= scaleToCube * ( 1.0 - 2.0 * texelSizeY );\n\t\tvec2 planar = v.xy;\n\t\tfloat almostATexel = 1.5 * texelSizeY;\n\t\tfloat almostOne = 1.0 - almostATexel;\n\t\tif ( absV.z >= almostOne ) {\n\t\t\tif ( v.z > 0.0 )\n\t\t\t\tplanar.x = 4.0 - v.x;\n\t\t} else if ( absV.x >= almostOne ) {\n\t\t\tfloat signX = sign( v.x );\n\t\t\tplanar.x = v.z * signX + 2.0 * signX;\n\t\t} else if ( absV.y >= almostOne ) {\n\t\t\tfloat signY = sign( v.y );\n\t\t\tplanar.x = v.x + 2.0 * signY + 2.0;\n\t\t\tplanar.y = v.z * signY - 2.0;\n\t\t}\n\t\treturn vec2( 0.125, 0.25 ) * planar + vec2( 0.375, 0.75 );\n\t}\n\tfloat getPointShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord, float shadowCameraNear, float shadowCameraFar ) {\n\t\tvec2 texelSize = vec2( 1.0 ) / ( shadowMapSize * vec2( 4.0, 2.0 ) );\n\t\tvec3 lightToPosition = shadowCoord.xyz;\n\t\tfloat dp = ( length( lightToPosition ) - shadowCameraNear ) / ( shadowCameraFar - shadowCameraNear );\t\tdp += shadowBias;\n\t\tvec3 bd3D = normalize( lightToPosition );\n\t\t#if defined( SHADOWMAP_TYPE_PCF ) || defined( SHADOWMAP_TYPE_PCF_SOFT ) || defined( SHADOWMAP_TYPE_VSM )\n\t\t\tvec2 offset = vec2( - 1, 1 ) * shadowRadius * texelSize.y;\n\t\t\treturn (\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxx, texelSize.y ), dp )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#else\n\t\t\treturn texture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp );\n\t\t#endif\n\t}\n#endif"; - - var shadowmap_pars_vertex = "#if NUM_SPOT_LIGHT_COORDS > 0\n\tuniform mat4 spotLightMatrix[ NUM_SPOT_LIGHT_COORDS ];\n\tvarying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ];\n#endif\n#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform mat4 directionalShadowMatrix[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform mat4 pointShadowMatrix[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n#endif"; - - var shadowmap_vertex = "#if ( defined( USE_SHADOWMAP ) && ( NUM_DIR_LIGHT_SHADOWS > 0 || NUM_POINT_LIGHT_SHADOWS > 0 ) ) || ( NUM_SPOT_LIGHT_COORDS > 0 )\n\tvec3 shadowWorldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\tvec4 shadowWorldPosition;\n#endif\n#if defined( USE_SHADOWMAP )\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * directionalLightShadows[ i ].shadowNormalBias, 0 );\n\t\t\tvDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * shadowWorldPosition;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * pointLightShadows[ i ].shadowNormalBias, 0 );\n\t\t\tvPointShadowCoord[ i ] = pointShadowMatrix[ i ] * shadowWorldPosition;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n#endif\n#if NUM_SPOT_LIGHT_COORDS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_COORDS; i ++ ) {\n\t\tshadowWorldPosition = worldPosition;\n\t\t#if ( defined( USE_SHADOWMAP ) && UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\t\tshadowWorldPosition.xyz += shadowWorldNormal * spotLightShadows[ i ].shadowNormalBias;\n\t\t#endif\n\t\tvSpotLightCoord[ i ] = spotLightMatrix[ i ] * shadowWorldPosition;\n\t}\n\t#pragma unroll_loop_end\n#endif"; - - var shadowmask_pars_fragment = "float getShadowMask() {\n\tfloat shadow = 1.0;\n\t#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\tdirectionalLight = directionalLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_SHADOWS; i ++ ) {\n\t\tspotLight = spotLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowBias, spotLight.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\tpointLight = pointLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#endif\n\treturn shadow;\n}"; - - var skinbase_vertex = "#ifdef USE_SKINNING\n\tmat4 boneMatX = getBoneMatrix( skinIndex.x );\n\tmat4 boneMatY = getBoneMatrix( skinIndex.y );\n\tmat4 boneMatZ = getBoneMatrix( skinIndex.z );\n\tmat4 boneMatW = getBoneMatrix( skinIndex.w );\n#endif"; - - var skinning_pars_vertex = "#ifdef USE_SKINNING\n\tuniform mat4 bindMatrix;\n\tuniform mat4 bindMatrixInverse;\n\tuniform highp sampler2D boneTexture;\n\tuniform int boneTextureSize;\n\tmat4 getBoneMatrix( const in float i ) {\n\t\tfloat j = i * 4.0;\n\t\tfloat x = mod( j, float( boneTextureSize ) );\n\t\tfloat y = floor( j / float( boneTextureSize ) );\n\t\tfloat dx = 1.0 / float( boneTextureSize );\n\t\tfloat dy = 1.0 / float( boneTextureSize );\n\t\ty = dy * ( y + 0.5 );\n\t\tvec4 v1 = texture2D( boneTexture, vec2( dx * ( x + 0.5 ), y ) );\n\t\tvec4 v2 = texture2D( boneTexture, vec2( dx * ( x + 1.5 ), y ) );\n\t\tvec4 v3 = texture2D( boneTexture, vec2( dx * ( x + 2.5 ), y ) );\n\t\tvec4 v4 = texture2D( boneTexture, vec2( dx * ( x + 3.5 ), y ) );\n\t\tmat4 bone = mat4( v1, v2, v3, v4 );\n\t\treturn bone;\n\t}\n#endif"; - - var skinning_vertex = "#ifdef USE_SKINNING\n\tvec4 skinVertex = bindMatrix * vec4( transformed, 1.0 );\n\tvec4 skinned = vec4( 0.0 );\n\tskinned += boneMatX * skinVertex * skinWeight.x;\n\tskinned += boneMatY * skinVertex * skinWeight.y;\n\tskinned += boneMatZ * skinVertex * skinWeight.z;\n\tskinned += boneMatW * skinVertex * skinWeight.w;\n\ttransformed = ( bindMatrixInverse * skinned ).xyz;\n#endif"; - - var skinnormal_vertex = "#ifdef USE_SKINNING\n\tmat4 skinMatrix = mat4( 0.0 );\n\tskinMatrix += skinWeight.x * boneMatX;\n\tskinMatrix += skinWeight.y * boneMatY;\n\tskinMatrix += skinWeight.z * boneMatZ;\n\tskinMatrix += skinWeight.w * boneMatW;\n\tskinMatrix = bindMatrixInverse * skinMatrix * bindMatrix;\n\tobjectNormal = vec4( skinMatrix * vec4( objectNormal, 0.0 ) ).xyz;\n\t#ifdef USE_TANGENT\n\t\tobjectTangent = vec4( skinMatrix * vec4( objectTangent, 0.0 ) ).xyz;\n\t#endif\n#endif"; - - var specularmap_fragment = "float specularStrength;\n#ifdef USE_SPECULARMAP\n\tvec4 texelSpecular = texture2D( specularMap, vSpecularMapUv );\n\tspecularStrength = texelSpecular.r;\n#else\n\tspecularStrength = 1.0;\n#endif"; - - var specularmap_pars_fragment = "#ifdef USE_SPECULARMAP\n\tuniform sampler2D specularMap;\n#endif"; - - var tonemapping_fragment = "#if defined( TONE_MAPPING )\n\tgl_FragColor.rgb = toneMapping( gl_FragColor.rgb );\n#endif"; - - var tonemapping_pars_fragment = "#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\nuniform float toneMappingExposure;\nvec3 LinearToneMapping( vec3 color ) {\n\treturn toneMappingExposure * color;\n}\nvec3 ReinhardToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\treturn saturate( color / ( vec3( 1.0 ) + color ) );\n}\nvec3 OptimizedCineonToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\tcolor = max( vec3( 0.0 ), color - 0.004 );\n\treturn pow( ( color * ( 6.2 * color + 0.5 ) ) / ( color * ( 6.2 * color + 1.7 ) + 0.06 ), vec3( 2.2 ) );\n}\nvec3 RRTAndODTFit( vec3 v ) {\n\tvec3 a = v * ( v + 0.0245786 ) - 0.000090537;\n\tvec3 b = v * ( 0.983729 * v + 0.4329510 ) + 0.238081;\n\treturn a / b;\n}\nvec3 ACESFilmicToneMapping( vec3 color ) {\n\tconst mat3 ACESInputMat = mat3(\n\t\tvec3( 0.59719, 0.07600, 0.02840 ),\t\tvec3( 0.35458, 0.90834, 0.13383 ),\n\t\tvec3( 0.04823, 0.01566, 0.83777 )\n\t);\n\tconst mat3 ACESOutputMat = mat3(\n\t\tvec3( 1.60475, -0.10208, -0.00327 ),\t\tvec3( -0.53108, 1.10813, -0.07276 ),\n\t\tvec3( -0.07367, -0.00605, 1.07602 )\n\t);\n\tcolor *= toneMappingExposure / 0.6;\n\tcolor = ACESInputMat * color;\n\tcolor = RRTAndODTFit( color );\n\tcolor = ACESOutputMat * color;\n\treturn saturate( color );\n}\nvec3 CustomToneMapping( vec3 color ) { return color; }"; - - var transmission_fragment = "#ifdef USE_TRANSMISSION\n\tmaterial.transmission = transmission;\n\tmaterial.transmissionAlpha = 1.0;\n\tmaterial.thickness = thickness;\n\tmaterial.attenuationDistance = attenuationDistance;\n\tmaterial.attenuationColor = attenuationColor;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tmaterial.transmission *= texture2D( transmissionMap, vTransmissionMapUv ).r;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tmaterial.thickness *= texture2D( thicknessMap, vThicknessMapUv ).g;\n\t#endif\n\tvec3 pos = vWorldPosition;\n\tvec3 v = normalize( cameraPosition - pos );\n\tvec3 n = inverseTransformDirection( normal, viewMatrix );\n\tvec4 transmitted = getIBLVolumeRefraction(\n\t\tn, v, material.roughness, material.diffuseColor, material.specularColor, material.specularF90,\n\t\tpos, modelMatrix, viewMatrix, projectionMatrix, material.ior, material.thickness,\n\t\tmaterial.attenuationColor, material.attenuationDistance );\n\tmaterial.transmissionAlpha = mix( material.transmissionAlpha, transmitted.a, material.transmission );\n\ttotalDiffuse = mix( totalDiffuse, transmitted.rgb, material.transmission );\n#endif"; - - var transmission_pars_fragment = "#ifdef USE_TRANSMISSION\n\tuniform float transmission;\n\tuniform float thickness;\n\tuniform float attenuationDistance;\n\tuniform vec3 attenuationColor;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tuniform sampler2D transmissionMap;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tuniform sampler2D thicknessMap;\n\t#endif\n\tuniform vec2 transmissionSamplerSize;\n\tuniform sampler2D transmissionSamplerMap;\n\tuniform mat4 modelMatrix;\n\tuniform mat4 projectionMatrix;\n\tvarying vec3 vWorldPosition;\n\tfloat w0( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * ( a * ( - a + 3.0 ) - 3.0 ) + 1.0 );\n\t}\n\tfloat w1( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * a * ( 3.0 * a - 6.0 ) + 4.0 );\n\t}\n\tfloat w2( float a ){\n\t\treturn ( 1.0 / 6.0 ) * ( a * ( a * ( - 3.0 * a + 3.0 ) + 3.0 ) + 1.0 );\n\t}\n\tfloat w3( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * a * a );\n\t}\n\tfloat g0( float a ) {\n\t\treturn w0( a ) + w1( a );\n\t}\n\tfloat g1( float a ) {\n\t\treturn w2( a ) + w3( a );\n\t}\n\tfloat h0( float a ) {\n\t\treturn - 1.0 + w1( a ) / ( w0( a ) + w1( a ) );\n\t}\n\tfloat h1( float a ) {\n\t\treturn 1.0 + w3( a ) / ( w2( a ) + w3( a ) );\n\t}\n\tvec4 bicubic( sampler2D tex, vec2 uv, vec4 texelSize, float lod ) {\n\t\tuv = uv * texelSize.zw + 0.5;\n\t\tvec2 iuv = floor( uv );\n\t\tvec2 fuv = fract( uv );\n\t\tfloat g0x = g0( fuv.x );\n\t\tfloat g1x = g1( fuv.x );\n\t\tfloat h0x = h0( fuv.x );\n\t\tfloat h1x = h1( fuv.x );\n\t\tfloat h0y = h0( fuv.y );\n\t\tfloat h1y = h1( fuv.y );\n\t\tvec2 p0 = ( vec2( iuv.x + h0x, iuv.y + h0y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p1 = ( vec2( iuv.x + h1x, iuv.y + h0y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p2 = ( vec2( iuv.x + h0x, iuv.y + h1y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p3 = ( vec2( iuv.x + h1x, iuv.y + h1y ) - 0.5 ) * texelSize.xy;\n\t\treturn g0( fuv.y ) * ( g0x * textureLod( tex, p0, lod ) + g1x * textureLod( tex, p1, lod ) ) +\n\t\t\tg1( fuv.y ) * ( g0x * textureLod( tex, p2, lod ) + g1x * textureLod( tex, p3, lod ) );\n\t}\n\tvec4 textureBicubic( sampler2D sampler, vec2 uv, float lod ) {\n\t\tvec2 fLodSize = vec2( textureSize( sampler, int( lod ) ) );\n\t\tvec2 cLodSize = vec2( textureSize( sampler, int( lod + 1.0 ) ) );\n\t\tvec2 fLodSizeInv = 1.0 / fLodSize;\n\t\tvec2 cLodSizeInv = 1.0 / cLodSize;\n\t\tvec4 fSample = bicubic( sampler, uv, vec4( fLodSizeInv, fLodSize ), floor( lod ) );\n\t\tvec4 cSample = bicubic( sampler, uv, vec4( cLodSizeInv, cLodSize ), ceil( lod ) );\n\t\treturn mix( fSample, cSample, fract( lod ) );\n\t}\n\tvec3 getVolumeTransmissionRay( const in vec3 n, const in vec3 v, const in float thickness, const in float ior, const in mat4 modelMatrix ) {\n\t\tvec3 refractionVector = refract( - v, normalize( n ), 1.0 / ior );\n\t\tvec3 modelScale;\n\t\tmodelScale.x = length( vec3( modelMatrix[ 0 ].xyz ) );\n\t\tmodelScale.y = length( vec3( modelMatrix[ 1 ].xyz ) );\n\t\tmodelScale.z = length( vec3( modelMatrix[ 2 ].xyz ) );\n\t\treturn normalize( refractionVector ) * thickness * modelScale;\n\t}\n\tfloat applyIorToRoughness( const in float roughness, const in float ior ) {\n\t\treturn roughness * clamp( ior * 2.0 - 2.0, 0.0, 1.0 );\n\t}\n\tvec4 getTransmissionSample( const in vec2 fragCoord, const in float roughness, const in float ior ) {\n\t\tfloat lod = log2( transmissionSamplerSize.x ) * applyIorToRoughness( roughness, ior );\n\t\treturn textureBicubic( transmissionSamplerMap, fragCoord.xy, lod );\n\t}\n\tvec3 volumeAttenuation( const in float transmissionDistance, const in vec3 attenuationColor, const in float attenuationDistance ) {\n\t\tif ( isinf( attenuationDistance ) ) {\n\t\t\treturn vec3( 1.0 );\n\t\t} else {\n\t\t\tvec3 attenuationCoefficient = -log( attenuationColor ) / attenuationDistance;\n\t\t\tvec3 transmittance = exp( - attenuationCoefficient * transmissionDistance );\t\t\treturn transmittance;\n\t\t}\n\t}\n\tvec4 getIBLVolumeRefraction( const in vec3 n, const in vec3 v, const in float roughness, const in vec3 diffuseColor,\n\t\tconst in vec3 specularColor, const in float specularF90, const in vec3 position, const in mat4 modelMatrix,\n\t\tconst in mat4 viewMatrix, const in mat4 projMatrix, const in float ior, const in float thickness,\n\t\tconst in vec3 attenuationColor, const in float attenuationDistance ) {\n\t\tvec3 transmissionRay = getVolumeTransmissionRay( n, v, thickness, ior, modelMatrix );\n\t\tvec3 refractedRayExit = position + transmissionRay;\n\t\tvec4 ndcPos = projMatrix * viewMatrix * vec4( refractedRayExit, 1.0 );\n\t\tvec2 refractionCoords = ndcPos.xy / ndcPos.w;\n\t\trefractionCoords += 1.0;\n\t\trefractionCoords /= 2.0;\n\t\tvec4 transmittedLight = getTransmissionSample( refractionCoords, roughness, ior );\n\t\tvec3 transmittance = diffuseColor * volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance );\n\t\tvec3 attenuatedColor = transmittance * transmittedLight.rgb;\n\t\tvec3 F = EnvironmentBRDF( n, v, specularColor, specularF90, roughness );\n\t\tfloat transmittanceFactor = ( transmittance.r + transmittance.g + transmittance.b ) / 3.0;\n\t\treturn vec4( ( 1.0 - F ) * attenuatedColor, 1.0 - ( 1.0 - transmittedLight.a ) * transmittanceFactor );\n\t}\n#endif"; - - var uv_pars_fragment = "#ifdef USE_UV\n\tvarying vec2 vUv;\n#endif\n#ifdef USE_MAP\n\tvarying vec2 vMapUv;\n#endif\n#ifdef USE_ALPHAMAP\n\tvarying vec2 vAlphaMapUv;\n#endif\n#ifdef USE_LIGHTMAP\n\tvarying vec2 vLightMapUv;\n#endif\n#ifdef USE_AOMAP\n\tvarying vec2 vAoMapUv;\n#endif\n#ifdef USE_BUMPMAP\n\tvarying vec2 vBumpMapUv;\n#endif\n#ifdef USE_NORMALMAP\n\tvarying vec2 vNormalMapUv;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tvarying vec2 vEmissiveMapUv;\n#endif\n#ifdef USE_METALNESSMAP\n\tvarying vec2 vMetalnessMapUv;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tvarying vec2 vRoughnessMapUv;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tvarying vec2 vAnisotropyMapUv;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tvarying vec2 vClearcoatMapUv;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tvarying vec2 vClearcoatNormalMapUv;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tvarying vec2 vClearcoatRoughnessMapUv;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tvarying vec2 vIridescenceMapUv;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tvarying vec2 vIridescenceThicknessMapUv;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tvarying vec2 vSheenColorMapUv;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tvarying vec2 vSheenRoughnessMapUv;\n#endif\n#ifdef USE_SPECULARMAP\n\tvarying vec2 vSpecularMapUv;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tvarying vec2 vSpecularColorMapUv;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tvarying vec2 vSpecularIntensityMapUv;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tuniform mat3 transmissionMapTransform;\n\tvarying vec2 vTransmissionMapUv;\n#endif\n#ifdef USE_THICKNESSMAP\n\tuniform mat3 thicknessMapTransform;\n\tvarying vec2 vThicknessMapUv;\n#endif"; - - var uv_pars_vertex = "#ifdef USE_UV\n\tvarying vec2 vUv;\n#endif\n#ifdef USE_MAP\n\tuniform mat3 mapTransform;\n\tvarying vec2 vMapUv;\n#endif\n#ifdef USE_ALPHAMAP\n\tuniform mat3 alphaMapTransform;\n\tvarying vec2 vAlphaMapUv;\n#endif\n#ifdef USE_LIGHTMAP\n\tuniform mat3 lightMapTransform;\n\tvarying vec2 vLightMapUv;\n#endif\n#ifdef USE_AOMAP\n\tuniform mat3 aoMapTransform;\n\tvarying vec2 vAoMapUv;\n#endif\n#ifdef USE_BUMPMAP\n\tuniform mat3 bumpMapTransform;\n\tvarying vec2 vBumpMapUv;\n#endif\n#ifdef USE_NORMALMAP\n\tuniform mat3 normalMapTransform;\n\tvarying vec2 vNormalMapUv;\n#endif\n#ifdef USE_DISPLACEMENTMAP\n\tuniform mat3 displacementMapTransform;\n\tvarying vec2 vDisplacementMapUv;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tuniform mat3 emissiveMapTransform;\n\tvarying vec2 vEmissiveMapUv;\n#endif\n#ifdef USE_METALNESSMAP\n\tuniform mat3 metalnessMapTransform;\n\tvarying vec2 vMetalnessMapUv;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tuniform mat3 roughnessMapTransform;\n\tvarying vec2 vRoughnessMapUv;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tvarying vec2 vAnisotropyMapUv;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tuniform mat3 clearcoatMapTransform;\n\tvarying vec2 vClearcoatMapUv;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tuniform mat3 clearcoatNormalMapTransform;\n\tvarying vec2 vClearcoatNormalMapUv;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tuniform mat3 clearcoatRoughnessMapTransform;\n\tvarying vec2 vClearcoatRoughnessMapUv;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tuniform mat3 sheenColorMapTransform;\n\tvarying vec2 vSheenColorMapUv;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tuniform mat3 sheenRoughnessMapTransform;\n\tvarying vec2 vSheenRoughnessMapUv;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tuniform mat3 iridescenceMapTransform;\n\tvarying vec2 vIridescenceMapUv;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tuniform mat3 iridescenceThicknessMapTransform;\n\tvarying vec2 vIridescenceThicknessMapUv;\n#endif\n#ifdef USE_SPECULARMAP\n\tuniform mat3 specularMapTransform;\n\tvarying vec2 vSpecularMapUv;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tuniform mat3 specularColorMapTransform;\n\tvarying vec2 vSpecularColorMapUv;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tuniform mat3 specularIntensityMapTransform;\n\tvarying vec2 vSpecularIntensityMapUv;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tuniform mat3 transmissionMapTransform;\n\tvarying vec2 vTransmissionMapUv;\n#endif\n#ifdef USE_THICKNESSMAP\n\tuniform mat3 thicknessMapTransform;\n\tvarying vec2 vThicknessMapUv;\n#endif"; - - var uv_vertex = "#ifdef USE_UV\n\tvUv = vec3( uv, 1 ).xy;\n#endif\n#ifdef USE_MAP\n\tvMapUv = ( mapTransform * vec3( MAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ALPHAMAP\n\tvAlphaMapUv = ( alphaMapTransform * vec3( ALPHAMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_LIGHTMAP\n\tvLightMapUv = ( lightMapTransform * vec3( LIGHTMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_AOMAP\n\tvAoMapUv = ( aoMapTransform * vec3( AOMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_BUMPMAP\n\tvBumpMapUv = ( bumpMapTransform * vec3( BUMPMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_NORMALMAP\n\tvNormalMapUv = ( normalMapTransform * vec3( NORMALMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_DISPLACEMENTMAP\n\tvDisplacementMapUv = ( displacementMapTransform * vec3( DISPLACEMENTMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tvEmissiveMapUv = ( emissiveMapTransform * vec3( EMISSIVEMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_METALNESSMAP\n\tvMetalnessMapUv = ( metalnessMapTransform * vec3( METALNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tvRoughnessMapUv = ( roughnessMapTransform * vec3( ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tvAnisotropyMapUv = ( vec3( ANISOTROPYMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tvClearcoatMapUv = ( clearcoatMapTransform * vec3( CLEARCOATMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tvClearcoatNormalMapUv = ( clearcoatNormalMapTransform * vec3( CLEARCOAT_NORMALMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tvClearcoatRoughnessMapUv = ( clearcoatRoughnessMapTransform * vec3( CLEARCOAT_ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tvIridescenceMapUv = ( iridescenceMapTransform * vec3( IRIDESCENCEMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tvIridescenceThicknessMapUv = ( iridescenceThicknessMapTransform * vec3( IRIDESCENCE_THICKNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tvSheenColorMapUv = ( sheenColorMapTransform * vec3( SHEEN_COLORMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tvSheenRoughnessMapUv = ( sheenRoughnessMapTransform * vec3( SHEEN_ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULARMAP\n\tvSpecularMapUv = ( specularMapTransform * vec3( SPECULARMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tvSpecularColorMapUv = ( specularColorMapTransform * vec3( SPECULAR_COLORMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tvSpecularIntensityMapUv = ( specularIntensityMapTransform * vec3( SPECULAR_INTENSITYMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tvTransmissionMapUv = ( transmissionMapTransform * vec3( TRANSMISSIONMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_THICKNESSMAP\n\tvThicknessMapUv = ( thicknessMapTransform * vec3( THICKNESSMAP_UV, 1 ) ).xy;\n#endif"; - - var worldpos_vertex = "#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined ( USE_SHADOWMAP ) || defined ( USE_TRANSMISSION ) || NUM_SPOT_LIGHT_COORDS > 0\n\tvec4 worldPosition = vec4( transformed, 1.0 );\n\t#ifdef USE_INSTANCING\n\t\tworldPosition = instanceMatrix * worldPosition;\n\t#endif\n\tworldPosition = modelMatrix * worldPosition;\n#endif"; - - const vertex$h = "varying vec2 vUv;\nuniform mat3 uvTransform;\nvoid main() {\n\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n\tgl_Position = vec4( position.xy, 1.0, 1.0 );\n}"; - - const fragment$h = "uniform sampler2D t2D;\nuniform float backgroundIntensity;\nvarying vec2 vUv;\nvoid main() {\n\tvec4 texColor = texture2D( t2D, vUv );\n\ttexColor.rgb *= backgroundIntensity;\n\tgl_FragColor = texColor;\n\t#include \n\t#include \n}"; - - const vertex$g = "varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n\tgl_Position.z = gl_Position.w;\n}"; - - const fragment$g = "#ifdef ENVMAP_TYPE_CUBE\n\tuniform samplerCube envMap;\n#elif defined( ENVMAP_TYPE_CUBE_UV )\n\tuniform sampler2D envMap;\n#endif\nuniform float flipEnvMap;\nuniform float backgroundBlurriness;\nuniform float backgroundIntensity;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 texColor = textureCube( envMap, vec3( flipEnvMap * vWorldDirection.x, vWorldDirection.yz ) );\n\t#elif defined( ENVMAP_TYPE_CUBE_UV )\n\t\tvec4 texColor = textureCubeUV( envMap, vWorldDirection, backgroundBlurriness );\n\t#else\n\t\tvec4 texColor = vec4( 0.0, 0.0, 0.0, 1.0 );\n\t#endif\n\ttexColor.rgb *= backgroundIntensity;\n\tgl_FragColor = texColor;\n\t#include \n\t#include \n}"; - - const vertex$f = "varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n\tgl_Position.z = gl_Position.w;\n}"; - - const fragment$f = "uniform samplerCube tCube;\nuniform float tFlip;\nuniform float opacity;\nvarying vec3 vWorldDirection;\nvoid main() {\n\tvec4 texColor = textureCube( tCube, vec3( tFlip * vWorldDirection.x, vWorldDirection.yz ) );\n\tgl_FragColor = texColor;\n\tgl_FragColor.a *= opacity;\n\t#include \n\t#include \n}"; - - const vertex$e = "#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvHighPrecisionZW = gl_Position.zw;\n}"; - - const fragment$e = "#if DEPTH_PACKING == 3200\n\tuniform float opacity;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( 1.0 );\n\t#if DEPTH_PACKING == 3200\n\t\tdiffuseColor.a = opacity;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\tfloat fragCoordZ = 0.5 * vHighPrecisionZW[0] / vHighPrecisionZW[1] + 0.5;\n\t#if DEPTH_PACKING == 3200\n\t\tgl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity );\n\t#elif DEPTH_PACKING == 3201\n\t\tgl_FragColor = packDepthToRGBA( fragCoordZ );\n\t#endif\n}"; - - const vertex$d = "#define DISTANCE\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvWorldPosition = worldPosition.xyz;\n}"; - - const fragment$d = "#define DISTANCE\nuniform vec3 referencePosition;\nuniform float nearDistance;\nuniform float farDistance;\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main () {\n\t#include \n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include \n\t#include \n\t#include \n\tfloat dist = length( vWorldPosition - referencePosition );\n\tdist = ( dist - nearDistance ) / ( farDistance - nearDistance );\n\tdist = saturate( dist );\n\tgl_FragColor = packDepthToRGBA( dist );\n}"; - - const vertex$c = "varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n}"; - - const fragment$c = "uniform sampler2D tEquirect;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvec3 direction = normalize( vWorldDirection );\n\tvec2 sampleUV = equirectUv( direction );\n\tgl_FragColor = texture2D( tEquirect, sampleUV );\n\t#include \n\t#include \n}"; - - const vertex$b = "uniform float scale;\nattribute float lineDistance;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvLineDistance = scale * lineDistance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - - const fragment$b = "uniform vec3 diffuse;\nuniform float opacity;\nuniform float dashSize;\nuniform float totalSize;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tif ( mod( vLineDistance, totalSize ) > dashSize ) {\n\t\tdiscard;\n\t}\n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - - const vertex$a = "#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#if defined ( USE_ENVMAP ) || defined ( USE_SKINNING )\n\t\t#include \n\t\t#include \n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - - const fragment$a = "uniform vec3 diffuse;\nuniform float opacity;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel = texture2D( lightMap, vLightMapUv );\n\t\treflectedLight.indirectDiffuse += lightMapTexel.rgb * lightMapIntensity * RECIPROCAL_PI;\n\t#else\n\t\treflectedLight.indirectDiffuse += vec3( 1.0 );\n\t#endif\n\t#include \n\treflectedLight.indirectDiffuse *= diffuseColor.rgb;\n\tvec3 outgoingLight = reflectedLight.indirectDiffuse;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - - const vertex$9 = "#define LAMBERT\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n\t#include \n}"; - - const fragment$9 = "#define LAMBERT\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - - const vertex$8 = "#define MATCAP\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n}"; - - const fragment$8 = "#define MATCAP\nuniform vec3 diffuse;\nuniform float opacity;\nuniform sampler2D matcap;\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 viewDir = normalize( vViewPosition );\n\tvec3 x = normalize( vec3( viewDir.z, 0.0, - viewDir.x ) );\n\tvec3 y = cross( viewDir, x );\n\tvec2 uv = vec2( dot( x, normal ), dot( y, normal ) ) * 0.495 + 0.5;\n\t#ifdef USE_MATCAP\n\t\tvec4 matcapColor = texture2D( matcap, uv );\n\t#else\n\t\tvec4 matcapColor = vec4( vec3( mix( 0.2, 0.8, uv.y ) ), 1.0 );\n\t#endif\n\tvec3 outgoingLight = diffuseColor.rgb * matcapColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - - const vertex$7 = "#define NORMAL\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvarying vec3 vViewPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvViewPosition = - mvPosition.xyz;\n#endif\n}"; - - const fragment$7 = "#define NORMAL\nuniform float opacity;\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvarying vec3 vViewPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\tgl_FragColor = vec4( packNormalToRGB( normal ), opacity );\n\t#ifdef OPAQUE\n\t\tgl_FragColor.a = 1.0;\n\t#endif\n}"; - - const vertex$6 = "#define PHONG\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n\t#include \n}"; - - const fragment$6 = "#define PHONG\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform vec3 specular;\nuniform float shininess;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - - const vertex$5 = "#define STANDARD\nvarying vec3 vViewPosition;\n#ifdef USE_TRANSMISSION\n\tvarying vec3 vWorldPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n#ifdef USE_TRANSMISSION\n\tvWorldPosition = worldPosition.xyz;\n#endif\n}"; - - const fragment$5 = "#define STANDARD\n#ifdef PHYSICAL\n\t#define IOR\n\t#define USE_SPECULAR\n#endif\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float roughness;\nuniform float metalness;\nuniform float opacity;\n#ifdef IOR\n\tuniform float ior;\n#endif\n#ifdef USE_SPECULAR\n\tuniform float specularIntensity;\n\tuniform vec3 specularColor;\n\t#ifdef USE_SPECULAR_COLORMAP\n\t\tuniform sampler2D specularColorMap;\n\t#endif\n\t#ifdef USE_SPECULAR_INTENSITYMAP\n\t\tuniform sampler2D specularIntensityMap;\n\t#endif\n#endif\n#ifdef USE_CLEARCOAT\n\tuniform float clearcoat;\n\tuniform float clearcoatRoughness;\n#endif\n#ifdef USE_IRIDESCENCE\n\tuniform float iridescence;\n\tuniform float iridescenceIOR;\n\tuniform float iridescenceThicknessMinimum;\n\tuniform float iridescenceThicknessMaximum;\n#endif\n#ifdef USE_SHEEN\n\tuniform vec3 sheenColor;\n\tuniform float sheenRoughness;\n\t#ifdef USE_SHEEN_COLORMAP\n\t\tuniform sampler2D sheenColorMap;\n\t#endif\n\t#ifdef USE_SHEEN_ROUGHNESSMAP\n\t\tuniform sampler2D sheenRoughnessMap;\n\t#endif\n#endif\n#ifdef USE_ANISOTROPY\n\tuniform vec2 anisotropyVector;\n\t#ifdef USE_ANISOTROPYMAP\n\t\tuniform sampler2D anisotropyMap;\n\t#endif\n#endif\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 totalDiffuse = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse;\n\tvec3 totalSpecular = reflectedLight.directSpecular + reflectedLight.indirectSpecular;\n\t#include \n\tvec3 outgoingLight = totalDiffuse + totalSpecular + totalEmissiveRadiance;\n\t#ifdef USE_SHEEN\n\t\tfloat sheenEnergyComp = 1.0 - 0.157 * max3( material.sheenColor );\n\t\toutgoingLight = outgoingLight * sheenEnergyComp + sheenSpecular;\n\t#endif\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNVcc = saturate( dot( geometry.clearcoatNormal, geometry.viewDir ) );\n\t\tvec3 Fcc = F_Schlick( material.clearcoatF0, material.clearcoatF90, dotNVcc );\n\t\toutgoingLight = outgoingLight * ( 1.0 - material.clearcoat * Fcc ) + clearcoatSpecular * material.clearcoat;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - - const vertex$4 = "#define TOON\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n}"; - - const fragment$4 = "#define TOON\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - - const vertex$3 = "uniform float size;\nuniform float scale;\n#include \n#include \n#include \n#include \n#include \n#include \n#ifdef USE_POINTS_UV\n\tvarying vec2 vUv;\n\tuniform mat3 uvTransform;\n#endif\nvoid main() {\n\t#ifdef USE_POINTS_UV\n\t\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tgl_PointSize = size;\n\t#ifdef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) gl_PointSize *= ( scale / - mvPosition.z );\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n}"; - - const fragment$3 = "uniform vec3 diffuse;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - - const vertex$2 = "#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - - const fragment$2 = "uniform vec3 color;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tgl_FragColor = vec4( color, opacity * ( 1.0 - getShadowMask() ) );\n\t#include \n\t#include \n\t#include \n}"; - - const vertex$1 = "uniform float rotation;\nuniform vec2 center;\n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 mvPosition = modelViewMatrix * vec4( 0.0, 0.0, 0.0, 1.0 );\n\tvec2 scale;\n\tscale.x = length( vec3( modelMatrix[ 0 ].x, modelMatrix[ 0 ].y, modelMatrix[ 0 ].z ) );\n\tscale.y = length( vec3( modelMatrix[ 1 ].x, modelMatrix[ 1 ].y, modelMatrix[ 1 ].z ) );\n\t#ifndef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) scale *= - mvPosition.z;\n\t#endif\n\tvec2 alignedPosition = ( position.xy - ( center - vec2( 0.5 ) ) ) * scale;\n\tvec2 rotatedPosition;\n\trotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;\n\trotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;\n\tmvPosition.xy += rotatedPosition;\n\tgl_Position = projectionMatrix * mvPosition;\n\t#include \n\t#include \n\t#include \n}"; - - const fragment$1 = "uniform vec3 diffuse;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n}"; - - const ShaderChunk = { - alphamap_fragment: alphamap_fragment, - alphamap_pars_fragment: alphamap_pars_fragment, - alphatest_fragment: alphatest_fragment, - alphatest_pars_fragment: alphatest_pars_fragment, - aomap_fragment: aomap_fragment, - aomap_pars_fragment: aomap_pars_fragment, - begin_vertex: begin_vertex, - beginnormal_vertex: beginnormal_vertex, - bsdfs: bsdfs, - iridescence_fragment: iridescence_fragment, - bumpmap_pars_fragment: bumpmap_pars_fragment, - clipping_planes_fragment: clipping_planes_fragment, - clipping_planes_pars_fragment: clipping_planes_pars_fragment, - clipping_planes_pars_vertex: clipping_planes_pars_vertex, - clipping_planes_vertex: clipping_planes_vertex, - color_fragment: color_fragment, - color_pars_fragment: color_pars_fragment, - color_pars_vertex: color_pars_vertex, - color_vertex: color_vertex, - common: common, - cube_uv_reflection_fragment: cube_uv_reflection_fragment, - defaultnormal_vertex: defaultnormal_vertex, - displacementmap_pars_vertex: displacementmap_pars_vertex, - displacementmap_vertex: displacementmap_vertex, - emissivemap_fragment: emissivemap_fragment, - emissivemap_pars_fragment: emissivemap_pars_fragment, - encodings_fragment: encodings_fragment, - encodings_pars_fragment: encodings_pars_fragment, - envmap_fragment: envmap_fragment, - envmap_common_pars_fragment: envmap_common_pars_fragment, - envmap_pars_fragment: envmap_pars_fragment, - envmap_pars_vertex: envmap_pars_vertex, - envmap_physical_pars_fragment: envmap_physical_pars_fragment, - envmap_vertex: envmap_vertex, - fog_vertex: fog_vertex, - fog_pars_vertex: fog_pars_vertex, - fog_fragment: fog_fragment, - fog_pars_fragment: fog_pars_fragment, - gradientmap_pars_fragment: gradientmap_pars_fragment, - lightmap_fragment: lightmap_fragment, - lightmap_pars_fragment: lightmap_pars_fragment, - lights_lambert_fragment: lights_lambert_fragment, - lights_lambert_pars_fragment: lights_lambert_pars_fragment, - lights_pars_begin: lights_pars_begin, - lights_toon_fragment: lights_toon_fragment, - lights_toon_pars_fragment: lights_toon_pars_fragment, - lights_phong_fragment: lights_phong_fragment, - lights_phong_pars_fragment: lights_phong_pars_fragment, - lights_physical_fragment: lights_physical_fragment, - lights_physical_pars_fragment: lights_physical_pars_fragment, - lights_fragment_begin: lights_fragment_begin, - lights_fragment_maps: lights_fragment_maps, - lights_fragment_end: lights_fragment_end, - logdepthbuf_fragment: logdepthbuf_fragment, - logdepthbuf_pars_fragment: logdepthbuf_pars_fragment, - logdepthbuf_pars_vertex: logdepthbuf_pars_vertex, - logdepthbuf_vertex: logdepthbuf_vertex, - map_fragment: map_fragment, - map_pars_fragment: map_pars_fragment, - map_particle_fragment: map_particle_fragment, - map_particle_pars_fragment: map_particle_pars_fragment, - metalnessmap_fragment: metalnessmap_fragment, - metalnessmap_pars_fragment: metalnessmap_pars_fragment, - morphcolor_vertex: morphcolor_vertex, - morphnormal_vertex: morphnormal_vertex, - morphtarget_pars_vertex: morphtarget_pars_vertex, - morphtarget_vertex: morphtarget_vertex, - normal_fragment_begin: normal_fragment_begin, - normal_fragment_maps: normal_fragment_maps, - normal_pars_fragment: normal_pars_fragment, - normal_pars_vertex: normal_pars_vertex, - normal_vertex: normal_vertex, - normalmap_pars_fragment: normalmap_pars_fragment, - clearcoat_normal_fragment_begin: clearcoat_normal_fragment_begin, - clearcoat_normal_fragment_maps: clearcoat_normal_fragment_maps, - clearcoat_pars_fragment: clearcoat_pars_fragment, - iridescence_pars_fragment: iridescence_pars_fragment, - output_fragment: output_fragment, - packing: packing, - premultiplied_alpha_fragment: premultiplied_alpha_fragment, - project_vertex: project_vertex, - dithering_fragment: dithering_fragment, - dithering_pars_fragment: dithering_pars_fragment, - roughnessmap_fragment: roughnessmap_fragment, - roughnessmap_pars_fragment: roughnessmap_pars_fragment, - shadowmap_pars_fragment: shadowmap_pars_fragment, - shadowmap_pars_vertex: shadowmap_pars_vertex, - shadowmap_vertex: shadowmap_vertex, - shadowmask_pars_fragment: shadowmask_pars_fragment, - skinbase_vertex: skinbase_vertex, - skinning_pars_vertex: skinning_pars_vertex, - skinning_vertex: skinning_vertex, - skinnormal_vertex: skinnormal_vertex, - specularmap_fragment: specularmap_fragment, - specularmap_pars_fragment: specularmap_pars_fragment, - tonemapping_fragment: tonemapping_fragment, - tonemapping_pars_fragment: tonemapping_pars_fragment, - transmission_fragment: transmission_fragment, - transmission_pars_fragment: transmission_pars_fragment, - uv_pars_fragment: uv_pars_fragment, - uv_pars_vertex: uv_pars_vertex, - uv_vertex: uv_vertex, - worldpos_vertex: worldpos_vertex, - - background_vert: vertex$h, - background_frag: fragment$h, - backgroundCube_vert: vertex$g, - backgroundCube_frag: fragment$g, - cube_vert: vertex$f, - cube_frag: fragment$f, - depth_vert: vertex$e, - depth_frag: fragment$e, - distanceRGBA_vert: vertex$d, - distanceRGBA_frag: fragment$d, - equirect_vert: vertex$c, - equirect_frag: fragment$c, - linedashed_vert: vertex$b, - linedashed_frag: fragment$b, - meshbasic_vert: vertex$a, - meshbasic_frag: fragment$a, - meshlambert_vert: vertex$9, - meshlambert_frag: fragment$9, - meshmatcap_vert: vertex$8, - meshmatcap_frag: fragment$8, - meshnormal_vert: vertex$7, - meshnormal_frag: fragment$7, - meshphong_vert: vertex$6, - meshphong_frag: fragment$6, - meshphysical_vert: vertex$5, - meshphysical_frag: fragment$5, - meshtoon_vert: vertex$4, - meshtoon_frag: fragment$4, - points_vert: vertex$3, - points_frag: fragment$3, - shadow_vert: vertex$2, - shadow_frag: fragment$2, - sprite_vert: vertex$1, - sprite_frag: fragment$1 - }; - - /** - * Uniforms library for shared webgl shaders - */ - - const UniformsLib = { - - common: { - - diffuse: { value: /*@__PURE__*/ new Color( 0xffffff ) }, - opacity: { value: 1.0 }, - - map: { value: null }, - mapTransform: { value: /*@__PURE__*/ new Matrix3() }, - - alphaMap: { value: null }, - alphaMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - - alphaTest: { value: 0 } - - }, - - specularmap: { - - specularMap: { value: null }, - specularMapTransform: { value: /*@__PURE__*/ new Matrix3() } - - }, - - envmap: { - - envMap: { value: null }, - flipEnvMap: { value: - 1 }, - reflectivity: { value: 1.0 }, // basic, lambert, phong - ior: { value: 1.5 }, // physical - refractionRatio: { value: 0.98 }, // basic, lambert, phong - - }, - - aomap: { - - aoMap: { value: null }, - aoMapIntensity: { value: 1 }, - aoMapTransform: { value: /*@__PURE__*/ new Matrix3() } - - }, - - lightmap: { - - lightMap: { value: null }, - lightMapIntensity: { value: 1 }, - lightMapTransform: { value: /*@__PURE__*/ new Matrix3() } - - }, - - bumpmap: { - - bumpMap: { value: null }, - bumpMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - bumpScale: { value: 1 } - - }, - - normalmap: { - - normalMap: { value: null }, - normalMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - normalScale: { value: /*@__PURE__*/ new Vector2( 1, 1 ) } - - }, - - displacementmap: { - - displacementMap: { value: null }, - displacementMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - displacementScale: { value: 1 }, - displacementBias: { value: 0 } - - }, - - emissivemap: { - - emissiveMap: { value: null }, - emissiveMapTransform: { value: /*@__PURE__*/ new Matrix3() } - - }, - - metalnessmap: { - - metalnessMap: { value: null }, - metalnessMapTransform: { value: /*@__PURE__*/ new Matrix3() } - - }, - - roughnessmap: { - - roughnessMap: { value: null }, - roughnessMapTransform: { value: /*@__PURE__*/ new Matrix3() } - - }, - - gradientmap: { - - gradientMap: { value: null } - - }, - - fog: { - - fogDensity: { value: 0.00025 }, - fogNear: { value: 1 }, - fogFar: { value: 2000 }, - fogColor: { value: /*@__PURE__*/ new Color( 0xffffff ) } - - }, - - lights: { - - ambientLightColor: { value: [] }, - - lightProbe: { value: [] }, - - directionalLights: { value: [], properties: { - direction: {}, - color: {} - } }, - - directionalLightShadows: { value: [], properties: { - shadowBias: {}, - shadowNormalBias: {}, - shadowRadius: {}, - shadowMapSize: {} - } }, - - directionalShadowMap: { value: [] }, - directionalShadowMatrix: { value: [] }, - - spotLights: { value: [], properties: { - color: {}, - position: {}, - direction: {}, - distance: {}, - coneCos: {}, - penumbraCos: {}, - decay: {} - } }, - - spotLightShadows: { value: [], properties: { - shadowBias: {}, - shadowNormalBias: {}, - shadowRadius: {}, - shadowMapSize: {} - } }, - - spotLightMap: { value: [] }, - spotShadowMap: { value: [] }, - spotLightMatrix: { value: [] }, - - pointLights: { value: [], properties: { - color: {}, - position: {}, - decay: {}, - distance: {} - } }, - - pointLightShadows: { value: [], properties: { - shadowBias: {}, - shadowNormalBias: {}, - shadowRadius: {}, - shadowMapSize: {}, - shadowCameraNear: {}, - shadowCameraFar: {} - } }, - - pointShadowMap: { value: [] }, - pointShadowMatrix: { value: [] }, - - hemisphereLights: { value: [], properties: { - direction: {}, - skyColor: {}, - groundColor: {} - } }, - - // TODO (abelnation): RectAreaLight BRDF data needs to be moved from example to main src - rectAreaLights: { value: [], properties: { - color: {}, - position: {}, - width: {}, - height: {} - } }, - - ltc_1: { value: null }, - ltc_2: { value: null } - - }, - - points: { - - diffuse: { value: /*@__PURE__*/ new Color( 0xffffff ) }, - opacity: { value: 1.0 }, - size: { value: 1.0 }, - scale: { value: 1.0 }, - map: { value: null }, - alphaMap: { value: null }, - alphaTest: { value: 0 }, - uvTransform: { value: /*@__PURE__*/ new Matrix3() } - - }, - - sprite: { - - diffuse: { value: /*@__PURE__*/ new Color( 0xffffff ) }, - opacity: { value: 1.0 }, - center: { value: /*@__PURE__*/ new Vector2( 0.5, 0.5 ) }, - rotation: { value: 0.0 }, - map: { value: null }, - mapTransform: { value: /*@__PURE__*/ new Matrix3() }, - alphaMap: { value: null }, - alphaTest: { value: 0 } - - } - - }; - - const ShaderLib = { - - basic: { - - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.specularmap, - UniformsLib.envmap, - UniformsLib.aomap, - UniformsLib.lightmap, - UniformsLib.fog - ] ), - - vertexShader: ShaderChunk.meshbasic_vert, - fragmentShader: ShaderChunk.meshbasic_frag - - }, - - lambert: { - - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.specularmap, - UniformsLib.envmap, - UniformsLib.aomap, - UniformsLib.lightmap, - UniformsLib.emissivemap, - UniformsLib.bumpmap, - UniformsLib.normalmap, - UniformsLib.displacementmap, - UniformsLib.fog, - UniformsLib.lights, - { - emissive: { value: /*@__PURE__*/ new Color( 0x000000 ) } - } - ] ), - - vertexShader: ShaderChunk.meshlambert_vert, - fragmentShader: ShaderChunk.meshlambert_frag - - }, - - phong: { - - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.specularmap, - UniformsLib.envmap, - UniformsLib.aomap, - UniformsLib.lightmap, - UniformsLib.emissivemap, - UniformsLib.bumpmap, - UniformsLib.normalmap, - UniformsLib.displacementmap, - UniformsLib.fog, - UniformsLib.lights, - { - emissive: { value: /*@__PURE__*/ new Color( 0x000000 ) }, - specular: { value: /*@__PURE__*/ new Color( 0x111111 ) }, - shininess: { value: 30 } - } - ] ), - - vertexShader: ShaderChunk.meshphong_vert, - fragmentShader: ShaderChunk.meshphong_frag - - }, - - standard: { - - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.envmap, - UniformsLib.aomap, - UniformsLib.lightmap, - UniformsLib.emissivemap, - UniformsLib.bumpmap, - UniformsLib.normalmap, - UniformsLib.displacementmap, - UniformsLib.roughnessmap, - UniformsLib.metalnessmap, - UniformsLib.fog, - UniformsLib.lights, - { - emissive: { value: /*@__PURE__*/ new Color( 0x000000 ) }, - roughness: { value: 1.0 }, - metalness: { value: 0.0 }, - envMapIntensity: { value: 1 } // temporary - } - ] ), - - vertexShader: ShaderChunk.meshphysical_vert, - fragmentShader: ShaderChunk.meshphysical_frag - - }, - - toon: { - - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.aomap, - UniformsLib.lightmap, - UniformsLib.emissivemap, - UniformsLib.bumpmap, - UniformsLib.normalmap, - UniformsLib.displacementmap, - UniformsLib.gradientmap, - UniformsLib.fog, - UniformsLib.lights, - { - emissive: { value: /*@__PURE__*/ new Color( 0x000000 ) } - } - ] ), - - vertexShader: ShaderChunk.meshtoon_vert, - fragmentShader: ShaderChunk.meshtoon_frag - - }, - - matcap: { - - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.bumpmap, - UniformsLib.normalmap, - UniformsLib.displacementmap, - UniformsLib.fog, - { - matcap: { value: null } - } - ] ), - - vertexShader: ShaderChunk.meshmatcap_vert, - fragmentShader: ShaderChunk.meshmatcap_frag - - }, - - points: { - - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.points, - UniformsLib.fog - ] ), - - vertexShader: ShaderChunk.points_vert, - fragmentShader: ShaderChunk.points_frag - - }, - - dashed: { - - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.fog, - { - scale: { value: 1 }, - dashSize: { value: 1 }, - totalSize: { value: 2 } - } - ] ), - - vertexShader: ShaderChunk.linedashed_vert, - fragmentShader: ShaderChunk.linedashed_frag - - }, - - depth: { - - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.displacementmap - ] ), - - vertexShader: ShaderChunk.depth_vert, - fragmentShader: ShaderChunk.depth_frag - - }, - - normal: { - - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.bumpmap, - UniformsLib.normalmap, - UniformsLib.displacementmap, - { - opacity: { value: 1.0 } - } - ] ), - - vertexShader: ShaderChunk.meshnormal_vert, - fragmentShader: ShaderChunk.meshnormal_frag - - }, - - sprite: { - - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.sprite, - UniformsLib.fog - ] ), - - vertexShader: ShaderChunk.sprite_vert, - fragmentShader: ShaderChunk.sprite_frag - - }, - - background: { - - uniforms: { - uvTransform: { value: /*@__PURE__*/ new Matrix3() }, - t2D: { value: null }, - backgroundIntensity: { value: 1 } - }, - - vertexShader: ShaderChunk.background_vert, - fragmentShader: ShaderChunk.background_frag - - }, - - backgroundCube: { - - uniforms: { - envMap: { value: null }, - flipEnvMap: { value: - 1 }, - backgroundBlurriness: { value: 0 }, - backgroundIntensity: { value: 1 } - }, - - vertexShader: ShaderChunk.backgroundCube_vert, - fragmentShader: ShaderChunk.backgroundCube_frag - - }, - - cube: { - - uniforms: { - tCube: { value: null }, - tFlip: { value: - 1 }, - opacity: { value: 1.0 } - }, - - vertexShader: ShaderChunk.cube_vert, - fragmentShader: ShaderChunk.cube_frag - - }, - - equirect: { - - uniforms: { - tEquirect: { value: null }, - }, - - vertexShader: ShaderChunk.equirect_vert, - fragmentShader: ShaderChunk.equirect_frag - - }, - - distanceRGBA: { - - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.displacementmap, - { - referencePosition: { value: /*@__PURE__*/ new Vector3() }, - nearDistance: { value: 1 }, - farDistance: { value: 1000 } - } - ] ), - - vertexShader: ShaderChunk.distanceRGBA_vert, - fragmentShader: ShaderChunk.distanceRGBA_frag - - }, - - shadow: { - - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.lights, - UniformsLib.fog, - { - color: { value: /*@__PURE__*/ new Color( 0x00000 ) }, - opacity: { value: 1.0 } - }, - ] ), - - vertexShader: ShaderChunk.shadow_vert, - fragmentShader: ShaderChunk.shadow_frag - - } - - }; - - ShaderLib.physical = { - - uniforms: /*@__PURE__*/ mergeUniforms( [ - ShaderLib.standard.uniforms, - { - clearcoat: { value: 0 }, - clearcoatMap: { value: null }, - clearcoatMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - clearcoatNormalMap: { value: null }, - clearcoatNormalMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - clearcoatNormalScale: { value: /*@__PURE__*/ new Vector2( 1, 1 ) }, - clearcoatRoughness: { value: 0 }, - clearcoatRoughnessMap: { value: null }, - clearcoatRoughnessMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - iridescence: { value: 0 }, - iridescenceMap: { value: null }, - iridescenceMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - iridescenceIOR: { value: 1.3 }, - iridescenceThicknessMinimum: { value: 100 }, - iridescenceThicknessMaximum: { value: 400 }, - iridescenceThicknessMap: { value: null }, - iridescenceThicknessMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - sheen: { value: 0 }, - sheenColor: { value: /*@__PURE__*/ new Color( 0x000000 ) }, - sheenColorMap: { value: null }, - sheenColorMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - sheenRoughness: { value: 1 }, - sheenRoughnessMap: { value: null }, - sheenRoughnessMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - transmission: { value: 0 }, - transmissionMap: { value: null }, - transmissionMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - transmissionSamplerSize: { value: /*@__PURE__*/ new Vector2() }, - transmissionSamplerMap: { value: null }, - thickness: { value: 0 }, - thicknessMap: { value: null }, - thicknessMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - attenuationDistance: { value: 0 }, - attenuationColor: { value: /*@__PURE__*/ new Color( 0x000000 ) }, - specularColor: { value: /*@__PURE__*/ new Color( 1, 1, 1 ) }, - specularColorMap: { value: null }, - specularColorMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - specularIntensity: { value: 1 }, - specularIntensityMap: { value: null }, - specularIntensityMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - anisotropyVector: { value: /*@__PURE__*/ new Vector2() }, - anisotropyMap: { value: null }, - } - ] ), - - vertexShader: ShaderChunk.meshphysical_vert, - fragmentShader: ShaderChunk.meshphysical_frag - - }; - - const _rgb = { r: 0, b: 0, g: 0 }; - - function WebGLBackground( renderer, cubemaps, cubeuvmaps, state, objects, alpha, premultipliedAlpha ) { - - const clearColor = new Color( 0x000000 ); - let clearAlpha = alpha === true ? 0 : 1; - - let planeMesh; - let boxMesh; - - let currentBackground = null; - let currentBackgroundVersion = 0; - let currentTonemapping = null; - - function render( renderList, scene ) { - - let forceClear = false; - let background = scene.isScene === true ? scene.background : null; - - if ( background && background.isTexture ) { - - const usePMREM = scene.backgroundBlurriness > 0; // use PMREM if the user wants to blur the background - background = ( usePMREM ? cubeuvmaps : cubemaps ).get( background ); - - } - - if ( background === null ) { - - setClear( clearColor, clearAlpha ); - - } else if ( background && background.isColor ) { - - setClear( background, 1 ); - forceClear = true; - - } - - const xr = renderer.xr; - const environmentBlendMode = xr.getEnvironmentBlendMode(); - - switch ( environmentBlendMode ) { - - case 'opaque': - forceClear = true; - break; - - case 'additive': - state.buffers.color.setClear( 0, 0, 0, 1, premultipliedAlpha ); - forceClear = true; - break; - - case 'alpha-blend': - state.buffers.color.setClear( 0, 0, 0, 0, premultipliedAlpha ); - forceClear = true; - break; - - } - - if ( renderer.autoClear || forceClear ) { - - renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil ); - - } - - if ( background && ( background.isCubeTexture || background.mapping === CubeUVReflectionMapping ) ) { - - if ( boxMesh === undefined ) { - - boxMesh = new Mesh( - new BoxGeometry( 1, 1, 1 ), - new ShaderMaterial( { - name: 'BackgroundCubeMaterial', - uniforms: cloneUniforms( ShaderLib.backgroundCube.uniforms ), - vertexShader: ShaderLib.backgroundCube.vertexShader, - fragmentShader: ShaderLib.backgroundCube.fragmentShader, - side: BackSide, - depthTest: false, - depthWrite: false, - fog: false - } ) - ); - - boxMesh.geometry.deleteAttribute( 'normal' ); - boxMesh.geometry.deleteAttribute( 'uv' ); - - boxMesh.onBeforeRender = function ( renderer, scene, camera ) { - - this.matrixWorld.copyPosition( camera.matrixWorld ); - - }; - - // add "envMap" material property so the renderer can evaluate it like for built-in materials - Object.defineProperty( boxMesh.material, 'envMap', { - - get: function () { - - return this.uniforms.envMap.value; - - } - - } ); - - objects.update( boxMesh ); - - } - - boxMesh.material.uniforms.envMap.value = background; - boxMesh.material.uniforms.flipEnvMap.value = ( background.isCubeTexture && background.isRenderTargetTexture === false ) ? - 1 : 1; - boxMesh.material.uniforms.backgroundBlurriness.value = scene.backgroundBlurriness; - boxMesh.material.uniforms.backgroundIntensity.value = scene.backgroundIntensity; - boxMesh.material.toneMapped = ( background.colorSpace === SRGBColorSpace ) ? false : true; - - if ( currentBackground !== background || - currentBackgroundVersion !== background.version || - currentTonemapping !== renderer.toneMapping ) { - - boxMesh.material.needsUpdate = true; - - currentBackground = background; - currentBackgroundVersion = background.version; - currentTonemapping = renderer.toneMapping; - - } - - boxMesh.layers.enableAll(); - - // push to the pre-sorted opaque render list - renderList.unshift( boxMesh, boxMesh.geometry, boxMesh.material, 0, 0, null ); - - } else if ( background && background.isTexture ) { - - if ( planeMesh === undefined ) { - - planeMesh = new Mesh( - new PlaneGeometry( 2, 2 ), - new ShaderMaterial( { - name: 'BackgroundMaterial', - uniforms: cloneUniforms( ShaderLib.background.uniforms ), - vertexShader: ShaderLib.background.vertexShader, - fragmentShader: ShaderLib.background.fragmentShader, - side: FrontSide, - depthTest: false, - depthWrite: false, - fog: false - } ) - ); - - planeMesh.geometry.deleteAttribute( 'normal' ); - - // add "map" material property so the renderer can evaluate it like for built-in materials - Object.defineProperty( planeMesh.material, 'map', { - - get: function () { - - return this.uniforms.t2D.value; - - } - - } ); - - objects.update( planeMesh ); - - } - - planeMesh.material.uniforms.t2D.value = background; - planeMesh.material.uniforms.backgroundIntensity.value = scene.backgroundIntensity; - planeMesh.material.toneMapped = ( background.colorSpace === SRGBColorSpace ) ? false : true; - - if ( background.matrixAutoUpdate === true ) { - - background.updateMatrix(); - - } - - planeMesh.material.uniforms.uvTransform.value.copy( background.matrix ); - - if ( currentBackground !== background || - currentBackgroundVersion !== background.version || - currentTonemapping !== renderer.toneMapping ) { - - planeMesh.material.needsUpdate = true; - - currentBackground = background; - currentBackgroundVersion = background.version; - currentTonemapping = renderer.toneMapping; - - } - - planeMesh.layers.enableAll(); - - // push to the pre-sorted opaque render list - renderList.unshift( planeMesh, planeMesh.geometry, planeMesh.material, 0, 0, null ); - - } - - } - - function setClear( color, alpha ) { - - color.getRGB( _rgb, getUnlitUniformColorSpace( renderer ) ); - - state.buffers.color.setClear( _rgb.r, _rgb.g, _rgb.b, alpha, premultipliedAlpha ); - - } - - return { - - getClearColor: function () { - - return clearColor; - - }, - setClearColor: function ( color, alpha = 1 ) { - - clearColor.set( color ); - clearAlpha = alpha; - setClear( clearColor, clearAlpha ); - - }, - getClearAlpha: function () { - - return clearAlpha; - - }, - setClearAlpha: function ( alpha ) { - - clearAlpha = alpha; - setClear( clearColor, clearAlpha ); - - }, - render: render - - }; - - } - - function WebGLBindingStates( gl, extensions, attributes, capabilities ) { - - const maxVertexAttributes = gl.getParameter( gl.MAX_VERTEX_ATTRIBS ); - - const extension = capabilities.isWebGL2 ? null : extensions.get( 'OES_vertex_array_object' ); - const vaoAvailable = capabilities.isWebGL2 || extension !== null; - - const bindingStates = {}; - - const defaultState = createBindingState( null ); - let currentState = defaultState; - let forceUpdate = false; - - function setup( object, material, program, geometry, index ) { - - let updateBuffers = false; - - if ( vaoAvailable ) { - - const state = getBindingState( geometry, program, material ); - - if ( currentState !== state ) { - - currentState = state; - bindVertexArrayObject( currentState.object ); - - } - - updateBuffers = needsUpdate( object, geometry, program, index ); - - if ( updateBuffers ) saveCache( object, geometry, program, index ); - - } else { - - const wireframe = ( material.wireframe === true ); - - if ( currentState.geometry !== geometry.id || - currentState.program !== program.id || - currentState.wireframe !== wireframe ) { - - currentState.geometry = geometry.id; - currentState.program = program.id; - currentState.wireframe = wireframe; - - updateBuffers = true; - - } - - } - - if ( index !== null ) { - - attributes.update( index, gl.ELEMENT_ARRAY_BUFFER ); - - } - - if ( updateBuffers || forceUpdate ) { - - forceUpdate = false; - - setupVertexAttributes( object, material, program, geometry ); - - if ( index !== null ) { - - gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, attributes.get( index ).buffer ); - - } - - } - - } - - function createVertexArrayObject() { - - if ( capabilities.isWebGL2 ) return gl.createVertexArray(); - - return extension.createVertexArrayOES(); - - } - - function bindVertexArrayObject( vao ) { - - if ( capabilities.isWebGL2 ) return gl.bindVertexArray( vao ); - - return extension.bindVertexArrayOES( vao ); - - } - - function deleteVertexArrayObject( vao ) { - - if ( capabilities.isWebGL2 ) return gl.deleteVertexArray( vao ); - - return extension.deleteVertexArrayOES( vao ); - - } - - function getBindingState( geometry, program, material ) { - - const wireframe = ( material.wireframe === true ); - - let programMap = bindingStates[ geometry.id ]; - - if ( programMap === undefined ) { - - programMap = {}; - bindingStates[ geometry.id ] = programMap; - - } - - let stateMap = programMap[ program.id ]; - - if ( stateMap === undefined ) { - - stateMap = {}; - programMap[ program.id ] = stateMap; - - } - - let state = stateMap[ wireframe ]; - - if ( state === undefined ) { - - state = createBindingState( createVertexArrayObject() ); - stateMap[ wireframe ] = state; - - } - - return state; - - } - - function createBindingState( vao ) { - - const newAttributes = []; - const enabledAttributes = []; - const attributeDivisors = []; - - for ( let i = 0; i < maxVertexAttributes; i ++ ) { - - newAttributes[ i ] = 0; - enabledAttributes[ i ] = 0; - attributeDivisors[ i ] = 0; - - } - - return { - - // for backward compatibility on non-VAO support browser - geometry: null, - program: null, - wireframe: false, - - newAttributes: newAttributes, - enabledAttributes: enabledAttributes, - attributeDivisors: attributeDivisors, - object: vao, - attributes: {}, - index: null - - }; - - } - - function needsUpdate( object, geometry, program, index ) { - - const cachedAttributes = currentState.attributes; - const geometryAttributes = geometry.attributes; - - let attributesNum = 0; - - const programAttributes = program.getAttributes(); - - for ( const name in programAttributes ) { - - const programAttribute = programAttributes[ name ]; - - if ( programAttribute.location >= 0 ) { - - const cachedAttribute = cachedAttributes[ name ]; - let geometryAttribute = geometryAttributes[ name ]; - - if ( geometryAttribute === undefined ) { - - if ( name === 'instanceMatrix' && object.instanceMatrix ) geometryAttribute = object.instanceMatrix; - if ( name === 'instanceColor' && object.instanceColor ) geometryAttribute = object.instanceColor; - - } - - if ( cachedAttribute === undefined ) return true; - - if ( cachedAttribute.attribute !== geometryAttribute ) return true; - - if ( geometryAttribute && cachedAttribute.data !== geometryAttribute.data ) return true; - - attributesNum ++; - - } - - } - - if ( currentState.attributesNum !== attributesNum ) return true; - - if ( currentState.index !== index ) return true; - - return false; - - } - - function saveCache( object, geometry, program, index ) { - - const cache = {}; - const attributes = geometry.attributes; - let attributesNum = 0; - - const programAttributes = program.getAttributes(); - - for ( const name in programAttributes ) { - - const programAttribute = programAttributes[ name ]; - - if ( programAttribute.location >= 0 ) { - - let attribute = attributes[ name ]; - - if ( attribute === undefined ) { - - if ( name === 'instanceMatrix' && object.instanceMatrix ) attribute = object.instanceMatrix; - if ( name === 'instanceColor' && object.instanceColor ) attribute = object.instanceColor; - - } - - const data = {}; - data.attribute = attribute; - - if ( attribute && attribute.data ) { - - data.data = attribute.data; - - } - - cache[ name ] = data; - - attributesNum ++; - - } - - } - - currentState.attributes = cache; - currentState.attributesNum = attributesNum; - - currentState.index = index; - - } - - function initAttributes() { - - const newAttributes = currentState.newAttributes; - - for ( let i = 0, il = newAttributes.length; i < il; i ++ ) { - - newAttributes[ i ] = 0; - - } - - } - - function enableAttribute( attribute ) { - - enableAttributeAndDivisor( attribute, 0 ); - - } - - function enableAttributeAndDivisor( attribute, meshPerAttribute ) { - - const newAttributes = currentState.newAttributes; - const enabledAttributes = currentState.enabledAttributes; - const attributeDivisors = currentState.attributeDivisors; - - newAttributes[ attribute ] = 1; - - if ( enabledAttributes[ attribute ] === 0 ) { - - gl.enableVertexAttribArray( attribute ); - enabledAttributes[ attribute ] = 1; - - } - - if ( attributeDivisors[ attribute ] !== meshPerAttribute ) { - - const extension = capabilities.isWebGL2 ? gl : extensions.get( 'ANGLE_instanced_arrays' ); - - extension[ capabilities.isWebGL2 ? 'vertexAttribDivisor' : 'vertexAttribDivisorANGLE' ]( attribute, meshPerAttribute ); - attributeDivisors[ attribute ] = meshPerAttribute; - - } - - } - - function disableUnusedAttributes() { - - const newAttributes = currentState.newAttributes; - const enabledAttributes = currentState.enabledAttributes; - - for ( let i = 0, il = enabledAttributes.length; i < il; i ++ ) { - - if ( enabledAttributes[ i ] !== newAttributes[ i ] ) { - - gl.disableVertexAttribArray( i ); - enabledAttributes[ i ] = 0; - - } - - } - - } - - function vertexAttribPointer( index, size, type, normalized, stride, offset, integer ) { - - if ( integer === true ) { - - gl.vertexAttribIPointer( index, size, type, stride, offset ); - - } else { - - gl.vertexAttribPointer( index, size, type, normalized, stride, offset ); - - } - - } - - function setupVertexAttributes( object, material, program, geometry ) { - - if ( capabilities.isWebGL2 === false && ( object.isInstancedMesh || geometry.isInstancedBufferGeometry ) ) { - - if ( extensions.get( 'ANGLE_instanced_arrays' ) === null ) return; - - } - - initAttributes(); - - const geometryAttributes = geometry.attributes; - - const programAttributes = program.getAttributes(); - - const materialDefaultAttributeValues = material.defaultAttributeValues; - - for ( const name in programAttributes ) { - - const programAttribute = programAttributes[ name ]; - - if ( programAttribute.location >= 0 ) { - - let geometryAttribute = geometryAttributes[ name ]; - - if ( geometryAttribute === undefined ) { - - if ( name === 'instanceMatrix' && object.instanceMatrix ) geometryAttribute = object.instanceMatrix; - if ( name === 'instanceColor' && object.instanceColor ) geometryAttribute = object.instanceColor; - - } - - if ( geometryAttribute !== undefined ) { - - const normalized = geometryAttribute.normalized; - const size = geometryAttribute.itemSize; - - const attribute = attributes.get( geometryAttribute ); - - // TODO Attribute may not be available on context restore - - if ( attribute === undefined ) continue; - - const buffer = attribute.buffer; - const type = attribute.type; - const bytesPerElement = attribute.bytesPerElement; - - // check for integer attributes (WebGL 2 only) - - const integer = ( capabilities.isWebGL2 === true && ( type === gl.INT || type === gl.UNSIGNED_INT || geometryAttribute.gpuType === IntType ) ); - - if ( geometryAttribute.isInterleavedBufferAttribute ) { - - const data = geometryAttribute.data; - const stride = data.stride; - const offset = geometryAttribute.offset; - - if ( data.isInstancedInterleavedBuffer ) { - - for ( let i = 0; i < programAttribute.locationSize; i ++ ) { - - enableAttributeAndDivisor( programAttribute.location + i, data.meshPerAttribute ); - - } - - if ( object.isInstancedMesh !== true && geometry._maxInstanceCount === undefined ) { - - geometry._maxInstanceCount = data.meshPerAttribute * data.count; - - } - - } else { - - for ( let i = 0; i < programAttribute.locationSize; i ++ ) { - - enableAttribute( programAttribute.location + i ); - - } - - } - - gl.bindBuffer( gl.ARRAY_BUFFER, buffer ); - - for ( let i = 0; i < programAttribute.locationSize; i ++ ) { - - vertexAttribPointer( - programAttribute.location + i, - size / programAttribute.locationSize, - type, - normalized, - stride * bytesPerElement, - ( offset + ( size / programAttribute.locationSize ) * i ) * bytesPerElement, - integer - ); - - } - - } else { - - if ( geometryAttribute.isInstancedBufferAttribute ) { - - for ( let i = 0; i < programAttribute.locationSize; i ++ ) { - - enableAttributeAndDivisor( programAttribute.location + i, geometryAttribute.meshPerAttribute ); - - } - - if ( object.isInstancedMesh !== true && geometry._maxInstanceCount === undefined ) { - - geometry._maxInstanceCount = geometryAttribute.meshPerAttribute * geometryAttribute.count; - - } - - } else { - - for ( let i = 0; i < programAttribute.locationSize; i ++ ) { - - enableAttribute( programAttribute.location + i ); - - } - - } - - gl.bindBuffer( gl.ARRAY_BUFFER, buffer ); - - for ( let i = 0; i < programAttribute.locationSize; i ++ ) { - - vertexAttribPointer( - programAttribute.location + i, - size / programAttribute.locationSize, - type, - normalized, - size * bytesPerElement, - ( size / programAttribute.locationSize ) * i * bytesPerElement, - integer - ); - - } - - } - - } else if ( materialDefaultAttributeValues !== undefined ) { - - const value = materialDefaultAttributeValues[ name ]; - - if ( value !== undefined ) { - - switch ( value.length ) { - - case 2: - gl.vertexAttrib2fv( programAttribute.location, value ); - break; - - case 3: - gl.vertexAttrib3fv( programAttribute.location, value ); - break; - - case 4: - gl.vertexAttrib4fv( programAttribute.location, value ); - break; - - default: - gl.vertexAttrib1fv( programAttribute.location, value ); - - } - - } - - } - - } - - } - - disableUnusedAttributes(); - - } - - function dispose() { - - reset(); - - for ( const geometryId in bindingStates ) { - - const programMap = bindingStates[ geometryId ]; - - for ( const programId in programMap ) { - - const stateMap = programMap[ programId ]; - - for ( const wireframe in stateMap ) { - - deleteVertexArrayObject( stateMap[ wireframe ].object ); - - delete stateMap[ wireframe ]; - - } - - delete programMap[ programId ]; - - } - - delete bindingStates[ geometryId ]; - - } - - } - - function releaseStatesOfGeometry( geometry ) { - - if ( bindingStates[ geometry.id ] === undefined ) return; - - const programMap = bindingStates[ geometry.id ]; - - for ( const programId in programMap ) { - - const stateMap = programMap[ programId ]; - - for ( const wireframe in stateMap ) { - - deleteVertexArrayObject( stateMap[ wireframe ].object ); - - delete stateMap[ wireframe ]; - - } - - delete programMap[ programId ]; - - } - - delete bindingStates[ geometry.id ]; - - } - - function releaseStatesOfProgram( program ) { - - for ( const geometryId in bindingStates ) { - - const programMap = bindingStates[ geometryId ]; - - if ( programMap[ program.id ] === undefined ) continue; - - const stateMap = programMap[ program.id ]; - - for ( const wireframe in stateMap ) { - - deleteVertexArrayObject( stateMap[ wireframe ].object ); - - delete stateMap[ wireframe ]; - - } - - delete programMap[ program.id ]; - - } - - } - - function reset() { - - resetDefaultState(); - forceUpdate = true; - - if ( currentState === defaultState ) return; - - currentState = defaultState; - bindVertexArrayObject( currentState.object ); - - } - - // for backward-compatibility - - function resetDefaultState() { - - defaultState.geometry = null; - defaultState.program = null; - defaultState.wireframe = false; - - } - - return { - - setup: setup, - reset: reset, - resetDefaultState: resetDefaultState, - dispose: dispose, - releaseStatesOfGeometry: releaseStatesOfGeometry, - releaseStatesOfProgram: releaseStatesOfProgram, - - initAttributes: initAttributes, - enableAttribute: enableAttribute, - disableUnusedAttributes: disableUnusedAttributes - - }; - - } - - function WebGLBufferRenderer( gl, extensions, info, capabilities ) { - - const isWebGL2 = capabilities.isWebGL2; - - let mode; - - function setMode( value ) { - - mode = value; - - } - - function render( start, count ) { - - gl.drawArrays( mode, start, count ); - - info.update( count, mode, 1 ); - - } - - function renderInstances( start, count, primcount ) { - - if ( primcount === 0 ) return; - - let extension, methodName; - - if ( isWebGL2 ) { - - extension = gl; - methodName = 'drawArraysInstanced'; - - } else { - - extension = extensions.get( 'ANGLE_instanced_arrays' ); - methodName = 'drawArraysInstancedANGLE'; - - if ( extension === null ) { - - console.error( 'THREE.WebGLBufferRenderer: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.' ); - return; - - } - - } - - extension[ methodName ]( mode, start, count, primcount ); - - info.update( count, mode, primcount ); - - } - - // - - this.setMode = setMode; - this.render = render; - this.renderInstances = renderInstances; - - } - - function WebGLCapabilities( gl, extensions, parameters ) { - - let maxAnisotropy; - - function getMaxAnisotropy() { - - if ( maxAnisotropy !== undefined ) return maxAnisotropy; - - if ( extensions.has( 'EXT_texture_filter_anisotropic' ) === true ) { - - const extension = extensions.get( 'EXT_texture_filter_anisotropic' ); - - maxAnisotropy = gl.getParameter( extension.MAX_TEXTURE_MAX_ANISOTROPY_EXT ); - - } else { - - maxAnisotropy = 0; - - } - - return maxAnisotropy; - - } - - function getMaxPrecision( precision ) { - - if ( precision === 'highp' ) { - - if ( gl.getShaderPrecisionFormat( gl.VERTEX_SHADER, gl.HIGH_FLOAT ).precision > 0 && - gl.getShaderPrecisionFormat( gl.FRAGMENT_SHADER, gl.HIGH_FLOAT ).precision > 0 ) { - - return 'highp'; - - } - - precision = 'mediump'; - - } - - if ( precision === 'mediump' ) { - - if ( gl.getShaderPrecisionFormat( gl.VERTEX_SHADER, gl.MEDIUM_FLOAT ).precision > 0 && - gl.getShaderPrecisionFormat( gl.FRAGMENT_SHADER, gl.MEDIUM_FLOAT ).precision > 0 ) { - - return 'mediump'; - - } - - } - - return 'lowp'; - - } - - const isWebGL2 = typeof WebGL2RenderingContext !== 'undefined' && gl.constructor.name === 'WebGL2RenderingContext'; - - let precision = parameters.precision !== undefined ? parameters.precision : 'highp'; - const maxPrecision = getMaxPrecision( precision ); - - if ( maxPrecision !== precision ) { - - console.warn( 'THREE.WebGLRenderer:', precision, 'not supported, using', maxPrecision, 'instead.' ); - precision = maxPrecision; - - } - - const drawBuffers = isWebGL2 || extensions.has( 'WEBGL_draw_buffers' ); - - const logarithmicDepthBuffer = parameters.logarithmicDepthBuffer === true; - - const maxTextures = gl.getParameter( gl.MAX_TEXTURE_IMAGE_UNITS ); - const maxVertexTextures = gl.getParameter( gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS ); - const maxTextureSize = gl.getParameter( gl.MAX_TEXTURE_SIZE ); - const maxCubemapSize = gl.getParameter( gl.MAX_CUBE_MAP_TEXTURE_SIZE ); - - const maxAttributes = gl.getParameter( gl.MAX_VERTEX_ATTRIBS ); - const maxVertexUniforms = gl.getParameter( gl.MAX_VERTEX_UNIFORM_VECTORS ); - const maxVaryings = gl.getParameter( gl.MAX_VARYING_VECTORS ); - const maxFragmentUniforms = gl.getParameter( gl.MAX_FRAGMENT_UNIFORM_VECTORS ); - - const vertexTextures = maxVertexTextures > 0; - const floatFragmentTextures = isWebGL2 || extensions.has( 'OES_texture_float' ); - const floatVertexTextures = vertexTextures && floatFragmentTextures; - - const maxSamples = isWebGL2 ? gl.getParameter( gl.MAX_SAMPLES ) : 0; - - return { - - isWebGL2: isWebGL2, - - drawBuffers: drawBuffers, - - getMaxAnisotropy: getMaxAnisotropy, - getMaxPrecision: getMaxPrecision, - - precision: precision, - logarithmicDepthBuffer: logarithmicDepthBuffer, - - maxTextures: maxTextures, - maxVertexTextures: maxVertexTextures, - maxTextureSize: maxTextureSize, - maxCubemapSize: maxCubemapSize, - - maxAttributes: maxAttributes, - maxVertexUniforms: maxVertexUniforms, - maxVaryings: maxVaryings, - maxFragmentUniforms: maxFragmentUniforms, - - vertexTextures: vertexTextures, - floatFragmentTextures: floatFragmentTextures, - floatVertexTextures: floatVertexTextures, - - maxSamples: maxSamples - - }; - - } - - function WebGLClipping( properties ) { - - const scope = this; - - let globalState = null, - numGlobalPlanes = 0, - localClippingEnabled = false, - renderingShadows = false; - - const plane = new Plane(), - viewNormalMatrix = new Matrix3(), - - uniform = { value: null, needsUpdate: false }; - - this.uniform = uniform; - this.numPlanes = 0; - this.numIntersection = 0; - - this.init = function ( planes, enableLocalClipping ) { - - const enabled = - planes.length !== 0 || - enableLocalClipping || - // enable state of previous frame - the clipping code has to - // run another frame in order to reset the state: - numGlobalPlanes !== 0 || - localClippingEnabled; - - localClippingEnabled = enableLocalClipping; - - numGlobalPlanes = planes.length; - - return enabled; - - }; - - this.beginShadows = function () { - - renderingShadows = true; - projectPlanes( null ); - - }; - - this.endShadows = function () { - - renderingShadows = false; - - }; - - this.setGlobalState = function ( planes, camera ) { - - globalState = projectPlanes( planes, camera, 0 ); - - }; - - this.setState = function ( material, camera, useCache ) { - - const planes = material.clippingPlanes, - clipIntersection = material.clipIntersection, - clipShadows = material.clipShadows; - - const materialProperties = properties.get( material ); - - if ( ! localClippingEnabled || planes === null || planes.length === 0 || renderingShadows && ! clipShadows ) { - - // there's no local clipping - - if ( renderingShadows ) { - - // there's no global clipping - - projectPlanes( null ); - - } else { - - resetGlobalState(); - - } - - } else { - - const nGlobal = renderingShadows ? 0 : numGlobalPlanes, - lGlobal = nGlobal * 4; - - let dstArray = materialProperties.clippingState || null; - - uniform.value = dstArray; // ensure unique state - - dstArray = projectPlanes( planes, camera, lGlobal, useCache ); - - for ( let i = 0; i !== lGlobal; ++ i ) { - - dstArray[ i ] = globalState[ i ]; - - } - - materialProperties.clippingState = dstArray; - this.numIntersection = clipIntersection ? this.numPlanes : 0; - this.numPlanes += nGlobal; - - } - - - }; - - function resetGlobalState() { - - if ( uniform.value !== globalState ) { - - uniform.value = globalState; - uniform.needsUpdate = numGlobalPlanes > 0; - - } - - scope.numPlanes = numGlobalPlanes; - scope.numIntersection = 0; - - } - - function projectPlanes( planes, camera, dstOffset, skipTransform ) { - - const nPlanes = planes !== null ? planes.length : 0; - let dstArray = null; - - if ( nPlanes !== 0 ) { - - dstArray = uniform.value; - - if ( skipTransform !== true || dstArray === null ) { - - const flatSize = dstOffset + nPlanes * 4, - viewMatrix = camera.matrixWorldInverse; - - viewNormalMatrix.getNormalMatrix( viewMatrix ); - - if ( dstArray === null || dstArray.length < flatSize ) { - - dstArray = new Float32Array( flatSize ); - - } - - for ( let i = 0, i4 = dstOffset; i !== nPlanes; ++ i, i4 += 4 ) { - - plane.copy( planes[ i ] ).applyMatrix4( viewMatrix, viewNormalMatrix ); - - plane.normal.toArray( dstArray, i4 ); - dstArray[ i4 + 3 ] = plane.constant; - - } - - } - - uniform.value = dstArray; - uniform.needsUpdate = true; - - } - - scope.numPlanes = nPlanes; - scope.numIntersection = 0; - - return dstArray; - - } - - } - - function WebGLCubeMaps( renderer ) { - - let cubemaps = new WeakMap(); - - function mapTextureMapping( texture, mapping ) { - - if ( mapping === EquirectangularReflectionMapping ) { - - texture.mapping = CubeReflectionMapping; - - } else if ( mapping === EquirectangularRefractionMapping ) { - - texture.mapping = CubeRefractionMapping; - - } - - return texture; - - } - - function get( texture ) { - - if ( texture && texture.isTexture && texture.isRenderTargetTexture === false ) { - - const mapping = texture.mapping; - - if ( mapping === EquirectangularReflectionMapping || mapping === EquirectangularRefractionMapping ) { - - if ( cubemaps.has( texture ) ) { - - const cubemap = cubemaps.get( texture ).texture; - return mapTextureMapping( cubemap, texture.mapping ); - - } else { - - const image = texture.image; - - if ( image && image.height > 0 ) { - - const renderTarget = new WebGLCubeRenderTarget( image.height / 2 ); - renderTarget.fromEquirectangularTexture( renderer, texture ); - cubemaps.set( texture, renderTarget ); - - texture.addEventListener( 'dispose', onTextureDispose ); - - return mapTextureMapping( renderTarget.texture, texture.mapping ); - - } else { - - // image not yet ready. try the conversion next frame - - return null; - - } - - } - - } - - } - - return texture; - - } - - function onTextureDispose( event ) { - - const texture = event.target; - - texture.removeEventListener( 'dispose', onTextureDispose ); - - const cubemap = cubemaps.get( texture ); - - if ( cubemap !== undefined ) { - - cubemaps.delete( texture ); - cubemap.dispose(); - - } - - } - - function dispose() { - - cubemaps = new WeakMap(); - - } - - return { - get: get, - dispose: dispose - }; - - } - - class OrthographicCamera extends Camera { - - constructor( left = - 1, right = 1, top = 1, bottom = - 1, near = 0.1, far = 2000 ) { - - super(); - - this.isOrthographicCamera = true; - - this.type = 'OrthographicCamera'; - - this.zoom = 1; - this.view = null; - - this.left = left; - this.right = right; - this.top = top; - this.bottom = bottom; - - this.near = near; - this.far = far; - - this.updateProjectionMatrix(); - - } - - copy( source, recursive ) { - - super.copy( source, recursive ); - - this.left = source.left; - this.right = source.right; - this.top = source.top; - this.bottom = source.bottom; - this.near = source.near; - this.far = source.far; - - this.zoom = source.zoom; - this.view = source.view === null ? null : Object.assign( {}, source.view ); - - return this; - - } - - setViewOffset( fullWidth, fullHeight, x, y, width, height ) { - - if ( this.view === null ) { - - this.view = { - enabled: true, - fullWidth: 1, - fullHeight: 1, - offsetX: 0, - offsetY: 0, - width: 1, - height: 1 - }; - - } - - this.view.enabled = true; - this.view.fullWidth = fullWidth; - this.view.fullHeight = fullHeight; - this.view.offsetX = x; - this.view.offsetY = y; - this.view.width = width; - this.view.height = height; - - this.updateProjectionMatrix(); - - } - - clearViewOffset() { - - if ( this.view !== null ) { - - this.view.enabled = false; - - } - - this.updateProjectionMatrix(); - - } - - updateProjectionMatrix() { - - const dx = ( this.right - this.left ) / ( 2 * this.zoom ); - const dy = ( this.top - this.bottom ) / ( 2 * this.zoom ); - const cx = ( this.right + this.left ) / 2; - const cy = ( this.top + this.bottom ) / 2; - - let left = cx - dx; - let right = cx + dx; - let top = cy + dy; - let bottom = cy - dy; - - if ( this.view !== null && this.view.enabled ) { - - const scaleW = ( this.right - this.left ) / this.view.fullWidth / this.zoom; - const scaleH = ( this.top - this.bottom ) / this.view.fullHeight / this.zoom; - - left += scaleW * this.view.offsetX; - right = left + scaleW * this.view.width; - top -= scaleH * this.view.offsetY; - bottom = top - scaleH * this.view.height; - - } - - this.projectionMatrix.makeOrthographic( left, right, top, bottom, this.near, this.far ); - - this.projectionMatrixInverse.copy( this.projectionMatrix ).invert(); - - } - - toJSON( meta ) { - - const data = super.toJSON( meta ); - - data.object.zoom = this.zoom; - data.object.left = this.left; - data.object.right = this.right; - data.object.top = this.top; - data.object.bottom = this.bottom; - data.object.near = this.near; - data.object.far = this.far; - - if ( this.view !== null ) data.object.view = Object.assign( {}, this.view ); - - return data; - - } - - } - - const LOD_MIN = 4; - - // The standard deviations (radians) associated with the extra mips. These are - // chosen to approximate a Trowbridge-Reitz distribution function times the - // geometric shadowing function. These sigma values squared must match the - // variance #defines in cube_uv_reflection_fragment.glsl.js. - const EXTRA_LOD_SIGMA = [ 0.125, 0.215, 0.35, 0.446, 0.526, 0.582 ]; - - // The maximum length of the blur for loop. Smaller sigmas will use fewer - // samples and exit early, but not recompile the shader. - const MAX_SAMPLES = 20; - - const _flatCamera = /*@__PURE__*/ new OrthographicCamera(); - const _clearColor = /*@__PURE__*/ new Color(); - let _oldTarget = null; - - // Golden Ratio - const PHI = ( 1 + Math.sqrt( 5 ) ) / 2; - const INV_PHI = 1 / PHI; - - // Vertices of a dodecahedron (except the opposites, which represent the - // same axis), used as axis directions evenly spread on a sphere. - const _axisDirections = [ - /*@__PURE__*/ new Vector3( 1, 1, 1 ), - /*@__PURE__*/ new Vector3( - 1, 1, 1 ), - /*@__PURE__*/ new Vector3( 1, 1, - 1 ), - /*@__PURE__*/ new Vector3( - 1, 1, - 1 ), - /*@__PURE__*/ new Vector3( 0, PHI, INV_PHI ), - /*@__PURE__*/ new Vector3( 0, PHI, - INV_PHI ), - /*@__PURE__*/ new Vector3( INV_PHI, 0, PHI ), - /*@__PURE__*/ new Vector3( - INV_PHI, 0, PHI ), - /*@__PURE__*/ new Vector3( PHI, INV_PHI, 0 ), - /*@__PURE__*/ new Vector3( - PHI, INV_PHI, 0 ) ]; - - /** - * This class generates a Prefiltered, Mipmapped Radiance Environment Map - * (PMREM) from a cubeMap environment texture. This allows different levels of - * blur to be quickly accessed based on material roughness. It is packed into a - * special CubeUV format that allows us to perform custom interpolation so that - * we can support nonlinear formats such as RGBE. Unlike a traditional mipmap - * chain, it only goes down to the LOD_MIN level (above), and then creates extra - * even more filtered 'mips' at the same LOD_MIN resolution, associated with - * higher roughness levels. In this way we maintain resolution to smoothly - * interpolate diffuse lighting while limiting sampling computation. - * - * Paper: Fast, Accurate Image-Based Lighting - * https://drive.google.com/file/d/15y8r_UpKlU9SvV4ILb0C3qCPecS8pvLz/view - */ - - class PMREMGenerator { - - constructor( renderer ) { - - this._renderer = renderer; - this._pingPongRenderTarget = null; - - this._lodMax = 0; - this._cubeSize = 0; - this._lodPlanes = []; - this._sizeLods = []; - this._sigmas = []; - - this._blurMaterial = null; - this._cubemapMaterial = null; - this._equirectMaterial = null; - - this._compileMaterial( this._blurMaterial ); - - } - - /** - * Generates a PMREM from a supplied Scene, which can be faster than using an - * image if networking bandwidth is low. Optional sigma specifies a blur radius - * in radians to be applied to the scene before PMREM generation. Optional near - * and far planes ensure the scene is rendered in its entirety (the cubeCamera - * is placed at the origin). - */ - fromScene( scene, sigma = 0, near = 0.1, far = 100 ) { - - _oldTarget = this._renderer.getRenderTarget(); - - this._setSize( 256 ); - - const cubeUVRenderTarget = this._allocateTargets(); - cubeUVRenderTarget.depthBuffer = true; - - this._sceneToCubeUV( scene, near, far, cubeUVRenderTarget ); - - if ( sigma > 0 ) { - - this._blur( cubeUVRenderTarget, 0, 0, sigma ); - - } - - this._applyPMREM( cubeUVRenderTarget ); - this._cleanup( cubeUVRenderTarget ); - - return cubeUVRenderTarget; - - } - - /** - * Generates a PMREM from an equirectangular texture, which can be either LDR - * or HDR. The ideal input image size is 1k (1024 x 512), - * as this matches best with the 256 x 256 cubemap output. - */ - fromEquirectangular( equirectangular, renderTarget = null ) { - - return this._fromTexture( equirectangular, renderTarget ); - - } - - /** - * Generates a PMREM from an cubemap texture, which can be either LDR - * or HDR. The ideal input cube size is 256 x 256, - * as this matches best with the 256 x 256 cubemap output. - */ - fromCubemap( cubemap, renderTarget = null ) { - - return this._fromTexture( cubemap, renderTarget ); - - } - - /** - * Pre-compiles the cubemap shader. You can get faster start-up by invoking this method during - * your texture's network fetch for increased concurrency. - */ - compileCubemapShader() { - - if ( this._cubemapMaterial === null ) { - - this._cubemapMaterial = _getCubemapMaterial(); - this._compileMaterial( this._cubemapMaterial ); - - } - - } - - /** - * Pre-compiles the equirectangular shader. You can get faster start-up by invoking this method during - * your texture's network fetch for increased concurrency. - */ - compileEquirectangularShader() { - - if ( this._equirectMaterial === null ) { - - this._equirectMaterial = _getEquirectMaterial(); - this._compileMaterial( this._equirectMaterial ); - - } - - } - - /** - * Disposes of the PMREMGenerator's internal memory. Note that PMREMGenerator is a static class, - * so you should not need more than one PMREMGenerator object. If you do, calling dispose() on - * one of them will cause any others to also become unusable. - */ - dispose() { - - this._dispose(); - - if ( this._cubemapMaterial !== null ) this._cubemapMaterial.dispose(); - if ( this._equirectMaterial !== null ) this._equirectMaterial.dispose(); - - } - - // private interface - - _setSize( cubeSize ) { - - this._lodMax = Math.floor( Math.log2( cubeSize ) ); - this._cubeSize = Math.pow( 2, this._lodMax ); - - } - - _dispose() { - - if ( this._blurMaterial !== null ) this._blurMaterial.dispose(); - - if ( this._pingPongRenderTarget !== null ) this._pingPongRenderTarget.dispose(); - - for ( let i = 0; i < this._lodPlanes.length; i ++ ) { - - this._lodPlanes[ i ].dispose(); - - } - - } - - _cleanup( outputTarget ) { - - this._renderer.setRenderTarget( _oldTarget ); - outputTarget.scissorTest = false; - _setViewport( outputTarget, 0, 0, outputTarget.width, outputTarget.height ); - - } - - _fromTexture( texture, renderTarget ) { - - if ( texture.mapping === CubeReflectionMapping || texture.mapping === CubeRefractionMapping ) { - - this._setSize( texture.image.length === 0 ? 16 : ( texture.image[ 0 ].width || texture.image[ 0 ].image.width ) ); - - } else { // Equirectangular - - this._setSize( texture.image.width / 4 ); - - } - - _oldTarget = this._renderer.getRenderTarget(); - - const cubeUVRenderTarget = renderTarget || this._allocateTargets(); - this._textureToCubeUV( texture, cubeUVRenderTarget ); - this._applyPMREM( cubeUVRenderTarget ); - this._cleanup( cubeUVRenderTarget ); - - return cubeUVRenderTarget; - - } - - _allocateTargets() { - - const width = 3 * Math.max( this._cubeSize, 16 * 7 ); - const height = 4 * this._cubeSize; - - const params = { - magFilter: LinearFilter, - minFilter: LinearFilter, - generateMipmaps: false, - type: HalfFloatType, - format: RGBAFormat, - colorSpace: LinearSRGBColorSpace, - depthBuffer: false - }; - - const cubeUVRenderTarget = _createRenderTarget( width, height, params ); - - if ( this._pingPongRenderTarget === null || this._pingPongRenderTarget.width !== width || this._pingPongRenderTarget.height !== height ) { - - if ( this._pingPongRenderTarget !== null ) { - - this._dispose(); - - } - - this._pingPongRenderTarget = _createRenderTarget( width, height, params ); - - const { _lodMax } = this; - ( { sizeLods: this._sizeLods, lodPlanes: this._lodPlanes, sigmas: this._sigmas } = _createPlanes( _lodMax ) ); - - this._blurMaterial = _getBlurShader( _lodMax, width, height ); - - } - - return cubeUVRenderTarget; - - } - - _compileMaterial( material ) { - - const tmpMesh = new Mesh( this._lodPlanes[ 0 ], material ); - this._renderer.compile( tmpMesh, _flatCamera ); - - } - - _sceneToCubeUV( scene, near, far, cubeUVRenderTarget ) { - - const fov = 90; - const aspect = 1; - const cubeCamera = new PerspectiveCamera( fov, aspect, near, far ); - const upSign = [ 1, - 1, 1, 1, 1, 1 ]; - const forwardSign = [ 1, 1, 1, - 1, - 1, - 1 ]; - const renderer = this._renderer; - - const originalAutoClear = renderer.autoClear; - const toneMapping = renderer.toneMapping; - renderer.getClearColor( _clearColor ); - - renderer.toneMapping = NoToneMapping; - renderer.autoClear = false; - - const backgroundMaterial = new MeshBasicMaterial( { - name: 'PMREM.Background', - side: BackSide, - depthWrite: false, - depthTest: false, - } ); - - const backgroundBox = new Mesh( new BoxGeometry(), backgroundMaterial ); - - let useSolidColor = false; - const background = scene.background; - - if ( background ) { - - if ( background.isColor ) { - - backgroundMaterial.color.copy( background ); - scene.background = null; - useSolidColor = true; - - } - - } else { - - backgroundMaterial.color.copy( _clearColor ); - useSolidColor = true; - - } - - for ( let i = 0; i < 6; i ++ ) { - - const col = i % 3; - - if ( col === 0 ) { - - cubeCamera.up.set( 0, upSign[ i ], 0 ); - cubeCamera.lookAt( forwardSign[ i ], 0, 0 ); - - } else if ( col === 1 ) { - - cubeCamera.up.set( 0, 0, upSign[ i ] ); - cubeCamera.lookAt( 0, forwardSign[ i ], 0 ); - - } else { - - cubeCamera.up.set( 0, upSign[ i ], 0 ); - cubeCamera.lookAt( 0, 0, forwardSign[ i ] ); - - } - - const size = this._cubeSize; - - _setViewport( cubeUVRenderTarget, col * size, i > 2 ? size : 0, size, size ); - - renderer.setRenderTarget( cubeUVRenderTarget ); - - if ( useSolidColor ) { - - renderer.render( backgroundBox, cubeCamera ); - - } - - renderer.render( scene, cubeCamera ); - - } - - backgroundBox.geometry.dispose(); - backgroundBox.material.dispose(); - - renderer.toneMapping = toneMapping; - renderer.autoClear = originalAutoClear; - scene.background = background; - - } - - _textureToCubeUV( texture, cubeUVRenderTarget ) { - - const renderer = this._renderer; - - const isCubeTexture = ( texture.mapping === CubeReflectionMapping || texture.mapping === CubeRefractionMapping ); - - if ( isCubeTexture ) { - - if ( this._cubemapMaterial === null ) { - - this._cubemapMaterial = _getCubemapMaterial(); - - } - - this._cubemapMaterial.uniforms.flipEnvMap.value = ( texture.isRenderTargetTexture === false ) ? - 1 : 1; - - } else { - - if ( this._equirectMaterial === null ) { - - this._equirectMaterial = _getEquirectMaterial(); - - } - - } - - const material = isCubeTexture ? this._cubemapMaterial : this._equirectMaterial; - const mesh = new Mesh( this._lodPlanes[ 0 ], material ); - - const uniforms = material.uniforms; - - uniforms[ 'envMap' ].value = texture; - - const size = this._cubeSize; - - _setViewport( cubeUVRenderTarget, 0, 0, 3 * size, 2 * size ); - - renderer.setRenderTarget( cubeUVRenderTarget ); - renderer.render( mesh, _flatCamera ); - - } - - _applyPMREM( cubeUVRenderTarget ) { - - const renderer = this._renderer; - const autoClear = renderer.autoClear; - renderer.autoClear = false; - - for ( let i = 1; i < this._lodPlanes.length; i ++ ) { - - const sigma = Math.sqrt( this._sigmas[ i ] * this._sigmas[ i ] - this._sigmas[ i - 1 ] * this._sigmas[ i - 1 ] ); - - const poleAxis = _axisDirections[ ( i - 1 ) % _axisDirections.length ]; - - this._blur( cubeUVRenderTarget, i - 1, i, sigma, poleAxis ); - - } - - renderer.autoClear = autoClear; - - } - - /** - * This is a two-pass Gaussian blur for a cubemap. Normally this is done - * vertically and horizontally, but this breaks down on a cube. Here we apply - * the blur latitudinally (around the poles), and then longitudinally (towards - * the poles) to approximate the orthogonally-separable blur. It is least - * accurate at the poles, but still does a decent job. - */ - _blur( cubeUVRenderTarget, lodIn, lodOut, sigma, poleAxis ) { - - const pingPongRenderTarget = this._pingPongRenderTarget; - - this._halfBlur( - cubeUVRenderTarget, - pingPongRenderTarget, - lodIn, - lodOut, - sigma, - 'latitudinal', - poleAxis ); - - this._halfBlur( - pingPongRenderTarget, - cubeUVRenderTarget, - lodOut, - lodOut, - sigma, - 'longitudinal', - poleAxis ); - - } - - _halfBlur( targetIn, targetOut, lodIn, lodOut, sigmaRadians, direction, poleAxis ) { - - const renderer = this._renderer; - const blurMaterial = this._blurMaterial; - - if ( direction !== 'latitudinal' && direction !== 'longitudinal' ) { - - console.error( - 'blur direction must be either latitudinal or longitudinal!' ); - - } - - // Number of standard deviations at which to cut off the discrete approximation. - const STANDARD_DEVIATIONS = 3; - - const blurMesh = new Mesh( this._lodPlanes[ lodOut ], blurMaterial ); - const blurUniforms = blurMaterial.uniforms; - - const pixels = this._sizeLods[ lodIn ] - 1; - const radiansPerPixel = isFinite( sigmaRadians ) ? Math.PI / ( 2 * pixels ) : 2 * Math.PI / ( 2 * MAX_SAMPLES - 1 ); - const sigmaPixels = sigmaRadians / radiansPerPixel; - const samples = isFinite( sigmaRadians ) ? 1 + Math.floor( STANDARD_DEVIATIONS * sigmaPixels ) : MAX_SAMPLES; - - if ( samples > MAX_SAMPLES ) { - - console.warn( `sigmaRadians, ${ - sigmaRadians}, is too large and will clip, as it requested ${ - samples} samples when the maximum is set to ${MAX_SAMPLES}` ); - - } - - const weights = []; - let sum = 0; - - for ( let i = 0; i < MAX_SAMPLES; ++ i ) { - - const x = i / sigmaPixels; - const weight = Math.exp( - x * x / 2 ); - weights.push( weight ); - - if ( i === 0 ) { - - sum += weight; - - } else if ( i < samples ) { - - sum += 2 * weight; - - } - - } - - for ( let i = 0; i < weights.length; i ++ ) { - - weights[ i ] = weights[ i ] / sum; - - } - - blurUniforms[ 'envMap' ].value = targetIn.texture; - blurUniforms[ 'samples' ].value = samples; - blurUniforms[ 'weights' ].value = weights; - blurUniforms[ 'latitudinal' ].value = direction === 'latitudinal'; - - if ( poleAxis ) { - - blurUniforms[ 'poleAxis' ].value = poleAxis; - - } - - const { _lodMax } = this; - blurUniforms[ 'dTheta' ].value = radiansPerPixel; - blurUniforms[ 'mipInt' ].value = _lodMax - lodIn; - - const outputSize = this._sizeLods[ lodOut ]; - const x = 3 * outputSize * ( lodOut > _lodMax - LOD_MIN ? lodOut - _lodMax + LOD_MIN : 0 ); - const y = 4 * ( this._cubeSize - outputSize ); - - _setViewport( targetOut, x, y, 3 * outputSize, 2 * outputSize ); - renderer.setRenderTarget( targetOut ); - renderer.render( blurMesh, _flatCamera ); - - } - - } - - - - function _createPlanes( lodMax ) { - - const lodPlanes = []; - const sizeLods = []; - const sigmas = []; - - let lod = lodMax; - - const totalLods = lodMax - LOD_MIN + 1 + EXTRA_LOD_SIGMA.length; - - for ( let i = 0; i < totalLods; i ++ ) { - - const sizeLod = Math.pow( 2, lod ); - sizeLods.push( sizeLod ); - let sigma = 1.0 / sizeLod; - - if ( i > lodMax - LOD_MIN ) { - - sigma = EXTRA_LOD_SIGMA[ i - lodMax + LOD_MIN - 1 ]; - - } else if ( i === 0 ) { - - sigma = 0; - - } - - sigmas.push( sigma ); - - const texelSize = 1.0 / ( sizeLod - 2 ); - const min = - texelSize; - const max = 1 + texelSize; - const uv1 = [ min, min, max, min, max, max, min, min, max, max, min, max ]; - - const cubeFaces = 6; - const vertices = 6; - const positionSize = 3; - const uvSize = 2; - const faceIndexSize = 1; - - const position = new Float32Array( positionSize * vertices * cubeFaces ); - const uv = new Float32Array( uvSize * vertices * cubeFaces ); - const faceIndex = new Float32Array( faceIndexSize * vertices * cubeFaces ); - - for ( let face = 0; face < cubeFaces; face ++ ) { - - const x = ( face % 3 ) * 2 / 3 - 1; - const y = face > 2 ? 0 : - 1; - const coordinates = [ - x, y, 0, - x + 2 / 3, y, 0, - x + 2 / 3, y + 1, 0, - x, y, 0, - x + 2 / 3, y + 1, 0, - x, y + 1, 0 - ]; - position.set( coordinates, positionSize * vertices * face ); - uv.set( uv1, uvSize * vertices * face ); - const fill = [ face, face, face, face, face, face ]; - faceIndex.set( fill, faceIndexSize * vertices * face ); - - } - - const planes = new BufferGeometry(); - planes.setAttribute( 'position', new BufferAttribute( position, positionSize ) ); - planes.setAttribute( 'uv', new BufferAttribute( uv, uvSize ) ); - planes.setAttribute( 'faceIndex', new BufferAttribute( faceIndex, faceIndexSize ) ); - lodPlanes.push( planes ); - - if ( lod > LOD_MIN ) { - - lod --; - - } - - } - - return { lodPlanes, sizeLods, sigmas }; - - } - - function _createRenderTarget( width, height, params ) { - - const cubeUVRenderTarget = new WebGLRenderTarget( width, height, params ); - cubeUVRenderTarget.texture.mapping = CubeUVReflectionMapping; - cubeUVRenderTarget.texture.name = 'PMREM.cubeUv'; - cubeUVRenderTarget.scissorTest = true; - return cubeUVRenderTarget; - - } - - function _setViewport( target, x, y, width, height ) { - - target.viewport.set( x, y, width, height ); - target.scissor.set( x, y, width, height ); - - } - - function _getBlurShader( lodMax, width, height ) { - - const weights = new Float32Array( MAX_SAMPLES ); - const poleAxis = new Vector3( 0, 1, 0 ); - const shaderMaterial = new ShaderMaterial( { - - name: 'SphericalGaussianBlur', - - defines: { - 'n': MAX_SAMPLES, - 'CUBEUV_TEXEL_WIDTH': 1.0 / width, - 'CUBEUV_TEXEL_HEIGHT': 1.0 / height, - 'CUBEUV_MAX_MIP': `${lodMax}.0`, - }, - - uniforms: { - 'envMap': { value: null }, - 'samples': { value: 1 }, - 'weights': { value: weights }, - 'latitudinal': { value: false }, - 'dTheta': { value: 0 }, - 'mipInt': { value: 0 }, - 'poleAxis': { value: poleAxis } - }, - - vertexShader: _getCommonVertexShader(), - - fragmentShader: /* glsl */` - - precision mediump float; - precision mediump int; - - varying vec3 vOutputDirection; - - uniform sampler2D envMap; - uniform int samples; - uniform float weights[ n ]; - uniform bool latitudinal; - uniform float dTheta; - uniform float mipInt; - uniform vec3 poleAxis; - - #define ENVMAP_TYPE_CUBE_UV - #include - - vec3 getSample( float theta, vec3 axis ) { - - float cosTheta = cos( theta ); - // Rodrigues' axis-angle rotation - vec3 sampleDirection = vOutputDirection * cosTheta - + cross( axis, vOutputDirection ) * sin( theta ) - + axis * dot( axis, vOutputDirection ) * ( 1.0 - cosTheta ); - - return bilinearCubeUV( envMap, sampleDirection, mipInt ); - - } - - void main() { - - vec3 axis = latitudinal ? poleAxis : cross( poleAxis, vOutputDirection ); - - if ( all( equal( axis, vec3( 0.0 ) ) ) ) { - - axis = vec3( vOutputDirection.z, 0.0, - vOutputDirection.x ); - - } - - axis = normalize( axis ); - - gl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 ); - gl_FragColor.rgb += weights[ 0 ] * getSample( 0.0, axis ); - - for ( int i = 1; i < n; i++ ) { - - if ( i >= samples ) { - - break; - - } - - float theta = dTheta * float( i ); - gl_FragColor.rgb += weights[ i ] * getSample( -1.0 * theta, axis ); - gl_FragColor.rgb += weights[ i ] * getSample( theta, axis ); - - } - - } - `, - - blending: NoBlending, - depthTest: false, - depthWrite: false - - } ); - - return shaderMaterial; - - } - - function _getEquirectMaterial() { - - return new ShaderMaterial( { - - name: 'EquirectangularToCubeUV', - - uniforms: { - 'envMap': { value: null } - }, - - vertexShader: _getCommonVertexShader(), - - fragmentShader: /* glsl */` - - precision mediump float; - precision mediump int; - - varying vec3 vOutputDirection; - - uniform sampler2D envMap; - - #include - - void main() { - - vec3 outputDirection = normalize( vOutputDirection ); - vec2 uv = equirectUv( outputDirection ); - - gl_FragColor = vec4( texture2D ( envMap, uv ).rgb, 1.0 ); - - } - `, - - blending: NoBlending, - depthTest: false, - depthWrite: false - - } ); - - } - - function _getCubemapMaterial() { - - return new ShaderMaterial( { - - name: 'CubemapToCubeUV', - - uniforms: { - 'envMap': { value: null }, - 'flipEnvMap': { value: - 1 } - }, - - vertexShader: _getCommonVertexShader(), - - fragmentShader: /* glsl */` - - precision mediump float; - precision mediump int; - - uniform float flipEnvMap; - - varying vec3 vOutputDirection; - - uniform samplerCube envMap; - - void main() { - - gl_FragColor = textureCube( envMap, vec3( flipEnvMap * vOutputDirection.x, vOutputDirection.yz ) ); - - } - `, - - blending: NoBlending, - depthTest: false, - depthWrite: false - - } ); - - } - - function _getCommonVertexShader() { - - return /* glsl */` - - precision mediump float; - precision mediump int; - - attribute float faceIndex; - - varying vec3 vOutputDirection; - - // RH coordinate system; PMREM face-indexing convention - vec3 getDirection( vec2 uv, float face ) { - - uv = 2.0 * uv - 1.0; - - vec3 direction = vec3( uv, 1.0 ); - - if ( face == 0.0 ) { - - direction = direction.zyx; // ( 1, v, u ) pos x - - } else if ( face == 1.0 ) { - - direction = direction.xzy; - direction.xz *= -1.0; // ( -u, 1, -v ) pos y - - } else if ( face == 2.0 ) { - - direction.x *= -1.0; // ( -u, v, 1 ) pos z - - } else if ( face == 3.0 ) { - - direction = direction.zyx; - direction.xz *= -1.0; // ( -1, v, -u ) neg x - - } else if ( face == 4.0 ) { - - direction = direction.xzy; - direction.xy *= -1.0; // ( -u, -1, v ) neg y - - } else if ( face == 5.0 ) { - - direction.z *= -1.0; // ( u, v, -1 ) neg z - - } - - return direction; - - } - - void main() { - - vOutputDirection = getDirection( uv, faceIndex ); - gl_Position = vec4( position, 1.0 ); - - } - `; - - } - - function WebGLCubeUVMaps( renderer ) { - - let cubeUVmaps = new WeakMap(); - - let pmremGenerator = null; - - function get( texture ) { - - if ( texture && texture.isTexture ) { - - const mapping = texture.mapping; - - const isEquirectMap = ( mapping === EquirectangularReflectionMapping || mapping === EquirectangularRefractionMapping ); - const isCubeMap = ( mapping === CubeReflectionMapping || mapping === CubeRefractionMapping ); - - // equirect/cube map to cubeUV conversion - - if ( isEquirectMap || isCubeMap ) { - - if ( texture.isRenderTargetTexture && texture.needsPMREMUpdate === true ) { - - texture.needsPMREMUpdate = false; - - let renderTarget = cubeUVmaps.get( texture ); - - if ( pmremGenerator === null ) pmremGenerator = new PMREMGenerator( renderer ); - - renderTarget = isEquirectMap ? pmremGenerator.fromEquirectangular( texture, renderTarget ) : pmremGenerator.fromCubemap( texture, renderTarget ); - cubeUVmaps.set( texture, renderTarget ); - - return renderTarget.texture; - - } else { - - if ( cubeUVmaps.has( texture ) ) { - - return cubeUVmaps.get( texture ).texture; - - } else { - - const image = texture.image; - - if ( ( isEquirectMap && image && image.height > 0 ) || ( isCubeMap && image && isCubeTextureComplete( image ) ) ) { - - if ( pmremGenerator === null ) pmremGenerator = new PMREMGenerator( renderer ); - - const renderTarget = isEquirectMap ? pmremGenerator.fromEquirectangular( texture ) : pmremGenerator.fromCubemap( texture ); - cubeUVmaps.set( texture, renderTarget ); - - texture.addEventListener( 'dispose', onTextureDispose ); - - return renderTarget.texture; - - } else { - - // image not yet ready. try the conversion next frame - - return null; - - } - - } - - } - - } - - } - - return texture; - - } - - function isCubeTextureComplete( image ) { - - let count = 0; - const length = 6; - - for ( let i = 0; i < length; i ++ ) { - - if ( image[ i ] !== undefined ) count ++; - - } - - return count === length; - - - } - - function onTextureDispose( event ) { - - const texture = event.target; - - texture.removeEventListener( 'dispose', onTextureDispose ); - - const cubemapUV = cubeUVmaps.get( texture ); - - if ( cubemapUV !== undefined ) { - - cubeUVmaps.delete( texture ); - cubemapUV.dispose(); - - } - - } - - function dispose() { - - cubeUVmaps = new WeakMap(); - - if ( pmremGenerator !== null ) { - - pmremGenerator.dispose(); - pmremGenerator = null; - - } - - } - - return { - get: get, - dispose: dispose - }; - - } - - function WebGLExtensions( gl ) { - - const extensions = {}; - - function getExtension( name ) { - - if ( extensions[ name ] !== undefined ) { - - return extensions[ name ]; - - } - - let extension; - - switch ( name ) { - - case 'WEBGL_depth_texture': - extension = gl.getExtension( 'WEBGL_depth_texture' ) || gl.getExtension( 'MOZ_WEBGL_depth_texture' ) || gl.getExtension( 'WEBKIT_WEBGL_depth_texture' ); - break; - - case 'EXT_texture_filter_anisotropic': - extension = gl.getExtension( 'EXT_texture_filter_anisotropic' ) || gl.getExtension( 'MOZ_EXT_texture_filter_anisotropic' ) || gl.getExtension( 'WEBKIT_EXT_texture_filter_anisotropic' ); - break; - - case 'WEBGL_compressed_texture_s3tc': - extension = gl.getExtension( 'WEBGL_compressed_texture_s3tc' ) || gl.getExtension( 'MOZ_WEBGL_compressed_texture_s3tc' ) || gl.getExtension( 'WEBKIT_WEBGL_compressed_texture_s3tc' ); - break; - - case 'WEBGL_compressed_texture_pvrtc': - extension = gl.getExtension( 'WEBGL_compressed_texture_pvrtc' ) || gl.getExtension( 'WEBKIT_WEBGL_compressed_texture_pvrtc' ); - break; - - default: - extension = gl.getExtension( name ); - - } - - extensions[ name ] = extension; - - return extension; - - } - - return { - - has: function ( name ) { - - return getExtension( name ) !== null; - - }, - - init: function ( capabilities ) { - - if ( capabilities.isWebGL2 ) { - - getExtension( 'EXT_color_buffer_float' ); - - } else { - - getExtension( 'WEBGL_depth_texture' ); - getExtension( 'OES_texture_float' ); - getExtension( 'OES_texture_half_float' ); - getExtension( 'OES_texture_half_float_linear' ); - getExtension( 'OES_standard_derivatives' ); - getExtension( 'OES_element_index_uint' ); - getExtension( 'OES_vertex_array_object' ); - getExtension( 'ANGLE_instanced_arrays' ); - - } - - getExtension( 'OES_texture_float_linear' ); - getExtension( 'EXT_color_buffer_half_float' ); - getExtension( 'WEBGL_multisampled_render_to_texture' ); - - }, - - get: function ( name ) { - - const extension = getExtension( name ); - - if ( extension === null ) { - - console.warn( 'THREE.WebGLRenderer: ' + name + ' extension not supported.' ); - - } - - return extension; - - } - - }; - - } - - function WebGLGeometries( gl, attributes, info, bindingStates ) { - - const geometries = {}; - const wireframeAttributes = new WeakMap(); - - function onGeometryDispose( event ) { - - const geometry = event.target; - - if ( geometry.index !== null ) { - - attributes.remove( geometry.index ); - - } - - for ( const name in geometry.attributes ) { - - attributes.remove( geometry.attributes[ name ] ); - - } - - for ( const name in geometry.morphAttributes ) { - - const array = geometry.morphAttributes[ name ]; - - for ( let i = 0, l = array.length; i < l; i ++ ) { - - attributes.remove( array[ i ] ); - - } - - } - - geometry.removeEventListener( 'dispose', onGeometryDispose ); - - delete geometries[ geometry.id ]; - - const attribute = wireframeAttributes.get( geometry ); - - if ( attribute ) { - - attributes.remove( attribute ); - wireframeAttributes.delete( geometry ); - - } - - bindingStates.releaseStatesOfGeometry( geometry ); - - if ( geometry.isInstancedBufferGeometry === true ) { - - delete geometry._maxInstanceCount; - - } - - // - - info.memory.geometries --; - - } - - function get( object, geometry ) { - - if ( geometries[ geometry.id ] === true ) return geometry; - - geometry.addEventListener( 'dispose', onGeometryDispose ); - - geometries[ geometry.id ] = true; - - info.memory.geometries ++; - - return geometry; - - } - - function update( geometry ) { - - const geometryAttributes = geometry.attributes; - - // Updating index buffer in VAO now. See WebGLBindingStates. - - for ( const name in geometryAttributes ) { - - attributes.update( geometryAttributes[ name ], gl.ARRAY_BUFFER ); - - } - - // morph targets - - const morphAttributes = geometry.morphAttributes; - - for ( const name in morphAttributes ) { - - const array = morphAttributes[ name ]; - - for ( let i = 0, l = array.length; i < l; i ++ ) { - - attributes.update( array[ i ], gl.ARRAY_BUFFER ); - - } - - } - - } - - function updateWireframeAttribute( geometry ) { - - const indices = []; - - const geometryIndex = geometry.index; - const geometryPosition = geometry.attributes.position; - let version = 0; - - if ( geometryIndex !== null ) { - - const array = geometryIndex.array; - version = geometryIndex.version; - - for ( let i = 0, l = array.length; i < l; i += 3 ) { - - const a = array[ i + 0 ]; - const b = array[ i + 1 ]; - const c = array[ i + 2 ]; - - indices.push( a, b, b, c, c, a ); - - } - - } else { - - const array = geometryPosition.array; - version = geometryPosition.version; - - for ( let i = 0, l = ( array.length / 3 ) - 1; i < l; i += 3 ) { - - const a = i + 0; - const b = i + 1; - const c = i + 2; - - indices.push( a, b, b, c, c, a ); - - } - - } - - const attribute = new ( arrayNeedsUint32( indices ) ? Uint32BufferAttribute : Uint16BufferAttribute )( indices, 1 ); - attribute.version = version; - - // Updating index buffer in VAO now. See WebGLBindingStates - - // - - const previousAttribute = wireframeAttributes.get( geometry ); - - if ( previousAttribute ) attributes.remove( previousAttribute ); - - // - - wireframeAttributes.set( geometry, attribute ); - - } - - function getWireframeAttribute( geometry ) { - - const currentAttribute = wireframeAttributes.get( geometry ); - - if ( currentAttribute ) { - - const geometryIndex = geometry.index; - - if ( geometryIndex !== null ) { - - // if the attribute is obsolete, create a new one - - if ( currentAttribute.version < geometryIndex.version ) { - - updateWireframeAttribute( geometry ); - - } - - } - - } else { - - updateWireframeAttribute( geometry ); - - } - - return wireframeAttributes.get( geometry ); - - } - - return { - - get: get, - update: update, - - getWireframeAttribute: getWireframeAttribute - - }; - - } - - function WebGLIndexedBufferRenderer( gl, extensions, info, capabilities ) { - - const isWebGL2 = capabilities.isWebGL2; - - let mode; - - function setMode( value ) { - - mode = value; - - } - - let type, bytesPerElement; - - function setIndex( value ) { - - type = value.type; - bytesPerElement = value.bytesPerElement; - - } - - function render( start, count ) { - - gl.drawElements( mode, count, type, start * bytesPerElement ); - - info.update( count, mode, 1 ); - - } - - function renderInstances( start, count, primcount ) { - - if ( primcount === 0 ) return; - - let extension, methodName; - - if ( isWebGL2 ) { - - extension = gl; - methodName = 'drawElementsInstanced'; - - } else { - - extension = extensions.get( 'ANGLE_instanced_arrays' ); - methodName = 'drawElementsInstancedANGLE'; - - if ( extension === null ) { - - console.error( 'THREE.WebGLIndexedBufferRenderer: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.' ); - return; - - } - - } - - extension[ methodName ]( mode, count, type, start * bytesPerElement, primcount ); - - info.update( count, mode, primcount ); - - } - - // - - this.setMode = setMode; - this.setIndex = setIndex; - this.render = render; - this.renderInstances = renderInstances; - - } - - function WebGLInfo( gl ) { - - const memory = { - geometries: 0, - textures: 0 - }; - - const render = { - frame: 0, - calls: 0, - triangles: 0, - points: 0, - lines: 0 - }; - - function update( count, mode, instanceCount ) { - - render.calls ++; - - switch ( mode ) { - - case gl.TRIANGLES: - render.triangles += instanceCount * ( count / 3 ); - break; - - case gl.LINES: - render.lines += instanceCount * ( count / 2 ); - break; - - case gl.LINE_STRIP: - render.lines += instanceCount * ( count - 1 ); - break; - - case gl.LINE_LOOP: - render.lines += instanceCount * count; - break; - - case gl.POINTS: - render.points += instanceCount * count; - break; - - default: - console.error( 'THREE.WebGLInfo: Unknown draw mode:', mode ); - break; - - } - - } - - function reset() { - - render.calls = 0; - render.triangles = 0; - render.points = 0; - render.lines = 0; - - } - - return { - memory: memory, - render: render, - programs: null, - autoReset: true, - reset: reset, - update: update - }; - - } - - function numericalSort( a, b ) { - - return a[ 0 ] - b[ 0 ]; - - } - - function absNumericalSort( a, b ) { - - return Math.abs( b[ 1 ] ) - Math.abs( a[ 1 ] ); - - } - - function WebGLMorphtargets( gl, capabilities, textures ) { - - const influencesList = {}; - const morphInfluences = new Float32Array( 8 ); - const morphTextures = new WeakMap(); - const morph = new Vector4(); - - const workInfluences = []; - - for ( let i = 0; i < 8; i ++ ) { - - workInfluences[ i ] = [ i, 0 ]; - - } - - function update( object, geometry, program ) { - - const objectInfluences = object.morphTargetInfluences; - - if ( capabilities.isWebGL2 === true ) { - - // instead of using attributes, the WebGL 2 code path encodes morph targets - // into an array of data textures. Each layer represents a single morph target. - - const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color; - const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0; - - let entry = morphTextures.get( geometry ); - - if ( entry === undefined || entry.count !== morphTargetsCount ) { - - if ( entry !== undefined ) entry.texture.dispose(); - - const hasMorphPosition = geometry.morphAttributes.position !== undefined; - const hasMorphNormals = geometry.morphAttributes.normal !== undefined; - const hasMorphColors = geometry.morphAttributes.color !== undefined; - - const morphTargets = geometry.morphAttributes.position || []; - const morphNormals = geometry.morphAttributes.normal || []; - const morphColors = geometry.morphAttributes.color || []; - - let vertexDataCount = 0; - - if ( hasMorphPosition === true ) vertexDataCount = 1; - if ( hasMorphNormals === true ) vertexDataCount = 2; - if ( hasMorphColors === true ) vertexDataCount = 3; - - let width = geometry.attributes.position.count * vertexDataCount; - let height = 1; - - if ( width > capabilities.maxTextureSize ) { - - height = Math.ceil( width / capabilities.maxTextureSize ); - width = capabilities.maxTextureSize; - - } - - const buffer = new Float32Array( width * height * 4 * morphTargetsCount ); - - const texture = new DataArrayTexture( buffer, width, height, morphTargetsCount ); - texture.type = FloatType; - texture.needsUpdate = true; - - // fill buffer - - const vertexDataStride = vertexDataCount * 4; - - for ( let i = 0; i < morphTargetsCount; i ++ ) { - - const morphTarget = morphTargets[ i ]; - const morphNormal = morphNormals[ i ]; - const morphColor = morphColors[ i ]; - - const offset = width * height * 4 * i; - - for ( let j = 0; j < morphTarget.count; j ++ ) { - - const stride = j * vertexDataStride; - - if ( hasMorphPosition === true ) { - - morph.fromBufferAttribute( morphTarget, j ); - - buffer[ offset + stride + 0 ] = morph.x; - buffer[ offset + stride + 1 ] = morph.y; - buffer[ offset + stride + 2 ] = morph.z; - buffer[ offset + stride + 3 ] = 0; - - } - - if ( hasMorphNormals === true ) { - - morph.fromBufferAttribute( morphNormal, j ); - - buffer[ offset + stride + 4 ] = morph.x; - buffer[ offset + stride + 5 ] = morph.y; - buffer[ offset + stride + 6 ] = morph.z; - buffer[ offset + stride + 7 ] = 0; - - } - - if ( hasMorphColors === true ) { - - morph.fromBufferAttribute( morphColor, j ); - - buffer[ offset + stride + 8 ] = morph.x; - buffer[ offset + stride + 9 ] = morph.y; - buffer[ offset + stride + 10 ] = morph.z; - buffer[ offset + stride + 11 ] = ( morphColor.itemSize === 4 ) ? morph.w : 1; - - } - - } - - } - - entry = { - count: morphTargetsCount, - texture: texture, - size: new Vector2( width, height ) - }; - - morphTextures.set( geometry, entry ); - - function disposeTexture() { - - texture.dispose(); - - morphTextures.delete( geometry ); - - geometry.removeEventListener( 'dispose', disposeTexture ); - - } - - geometry.addEventListener( 'dispose', disposeTexture ); - - } - - // - - let morphInfluencesSum = 0; - - for ( let i = 0; i < objectInfluences.length; i ++ ) { - - morphInfluencesSum += objectInfluences[ i ]; - - } - - const morphBaseInfluence = geometry.morphTargetsRelative ? 1 : 1 - morphInfluencesSum; - - program.getUniforms().setValue( gl, 'morphTargetBaseInfluence', morphBaseInfluence ); - program.getUniforms().setValue( gl, 'morphTargetInfluences', objectInfluences ); - - program.getUniforms().setValue( gl, 'morphTargetsTexture', entry.texture, textures ); - program.getUniforms().setValue( gl, 'morphTargetsTextureSize', entry.size ); - - - } else { - - // When object doesn't have morph target influences defined, we treat it as a 0-length array - // This is important to make sure we set up morphTargetBaseInfluence / morphTargetInfluences - - const length = objectInfluences === undefined ? 0 : objectInfluences.length; - - let influences = influencesList[ geometry.id ]; - - if ( influences === undefined || influences.length !== length ) { - - // initialise list - - influences = []; - - for ( let i = 0; i < length; i ++ ) { - - influences[ i ] = [ i, 0 ]; - - } - - influencesList[ geometry.id ] = influences; - - } - - // Collect influences - - for ( let i = 0; i < length; i ++ ) { - - const influence = influences[ i ]; - - influence[ 0 ] = i; - influence[ 1 ] = objectInfluences[ i ]; - - } - - influences.sort( absNumericalSort ); - - for ( let i = 0; i < 8; i ++ ) { - - if ( i < length && influences[ i ][ 1 ] ) { - - workInfluences[ i ][ 0 ] = influences[ i ][ 0 ]; - workInfluences[ i ][ 1 ] = influences[ i ][ 1 ]; - - } else { - - workInfluences[ i ][ 0 ] = Number.MAX_SAFE_INTEGER; - workInfluences[ i ][ 1 ] = 0; - - } - - } - - workInfluences.sort( numericalSort ); - - const morphTargets = geometry.morphAttributes.position; - const morphNormals = geometry.morphAttributes.normal; - - let morphInfluencesSum = 0; - - for ( let i = 0; i < 8; i ++ ) { - - const influence = workInfluences[ i ]; - const index = influence[ 0 ]; - const value = influence[ 1 ]; - - if ( index !== Number.MAX_SAFE_INTEGER && value ) { - - if ( morphTargets && geometry.getAttribute( 'morphTarget' + i ) !== morphTargets[ index ] ) { - - geometry.setAttribute( 'morphTarget' + i, morphTargets[ index ] ); - - } - - if ( morphNormals && geometry.getAttribute( 'morphNormal' + i ) !== morphNormals[ index ] ) { - - geometry.setAttribute( 'morphNormal' + i, morphNormals[ index ] ); - - } - - morphInfluences[ i ] = value; - morphInfluencesSum += value; - - } else { - - if ( morphTargets && geometry.hasAttribute( 'morphTarget' + i ) === true ) { - - geometry.deleteAttribute( 'morphTarget' + i ); - - } - - if ( morphNormals && geometry.hasAttribute( 'morphNormal' + i ) === true ) { - - geometry.deleteAttribute( 'morphNormal' + i ); - - } - - morphInfluences[ i ] = 0; - - } - - } - - // GLSL shader uses formula baseinfluence * base + sum(target * influence) - // This allows us to switch between absolute morphs and relative morphs without changing shader code - // When baseinfluence = 1 - sum(influence), the above is equivalent to sum((target - base) * influence) - const morphBaseInfluence = geometry.morphTargetsRelative ? 1 : 1 - morphInfluencesSum; - - program.getUniforms().setValue( gl, 'morphTargetBaseInfluence', morphBaseInfluence ); - program.getUniforms().setValue( gl, 'morphTargetInfluences', morphInfluences ); - - } - - } - - return { - - update: update - - }; - - } - - function WebGLObjects( gl, geometries, attributes, info ) { - - let updateMap = new WeakMap(); - - function update( object ) { - - const frame = info.render.frame; - - const geometry = object.geometry; - const buffergeometry = geometries.get( object, geometry ); - - // Update once per frame - - if ( updateMap.get( buffergeometry ) !== frame ) { - - geometries.update( buffergeometry ); - - updateMap.set( buffergeometry, frame ); - - } - - if ( object.isInstancedMesh ) { - - if ( object.hasEventListener( 'dispose', onInstancedMeshDispose ) === false ) { - - object.addEventListener( 'dispose', onInstancedMeshDispose ); - - } - - attributes.update( object.instanceMatrix, gl.ARRAY_BUFFER ); - - if ( object.instanceColor !== null ) { - - attributes.update( object.instanceColor, gl.ARRAY_BUFFER ); - - } - - } - - return buffergeometry; - - } - - function dispose() { - - updateMap = new WeakMap(); - - } - - function onInstancedMeshDispose( event ) { - - const instancedMesh = event.target; - - instancedMesh.removeEventListener( 'dispose', onInstancedMeshDispose ); - - attributes.remove( instancedMesh.instanceMatrix ); - - if ( instancedMesh.instanceColor !== null ) attributes.remove( instancedMesh.instanceColor ); - - } - - return { - - update: update, - dispose: dispose - - }; - - } - - /** - * Uniforms of a program. - * Those form a tree structure with a special top-level container for the root, - * which you get by calling 'new WebGLUniforms( gl, program )'. - * - * - * Properties of inner nodes including the top-level container: - * - * .seq - array of nested uniforms - * .map - nested uniforms by name - * - * - * Methods of all nodes except the top-level container: - * - * .setValue( gl, value, [textures] ) - * - * uploads a uniform value(s) - * the 'textures' parameter is needed for sampler uniforms - * - * - * Static methods of the top-level container (textures factorizations): - * - * .upload( gl, seq, values, textures ) - * - * sets uniforms in 'seq' to 'values[id].value' - * - * .seqWithValue( seq, values ) : filteredSeq - * - * filters 'seq' entries with corresponding entry in values - * - * - * Methods of the top-level container (textures factorizations): - * - * .setValue( gl, name, value, textures ) - * - * sets uniform with name 'name' to 'value' - * - * .setOptional( gl, obj, prop ) - * - * like .set for an optional property of the object - * - */ - - - const emptyTexture = /*@__PURE__*/ new Texture(); - const emptyArrayTexture = /*@__PURE__*/ new DataArrayTexture(); - const empty3dTexture = /*@__PURE__*/ new Data3DTexture(); - const emptyCubeTexture = /*@__PURE__*/ new CubeTexture(); - - // --- Utilities --- - - // Array Caches (provide typed arrays for temporary by size) - - const arrayCacheF32 = []; - const arrayCacheI32 = []; - - // Float32Array caches used for uploading Matrix uniforms - - const mat4array = new Float32Array( 16 ); - const mat3array = new Float32Array( 9 ); - const mat2array = new Float32Array( 4 ); - - // Flattening for arrays of vectors and matrices - - function flatten( array, nBlocks, blockSize ) { - - const firstElem = array[ 0 ]; - - if ( firstElem <= 0 || firstElem > 0 ) return array; - // unoptimized: ! isNaN( firstElem ) - // see http://jacksondunstan.com/articles/983 - - const n = nBlocks * blockSize; - let r = arrayCacheF32[ n ]; - - if ( r === undefined ) { - - r = new Float32Array( n ); - arrayCacheF32[ n ] = r; - - } - - if ( nBlocks !== 0 ) { - - firstElem.toArray( r, 0 ); - - for ( let i = 1, offset = 0; i !== nBlocks; ++ i ) { - - offset += blockSize; - array[ i ].toArray( r, offset ); - - } - - } - - return r; - - } - - function arraysEqual( a, b ) { - - if ( a.length !== b.length ) return false; - - for ( let i = 0, l = a.length; i < l; i ++ ) { - - if ( a[ i ] !== b[ i ] ) return false; - - } - - return true; - - } - - function copyArray( a, b ) { - - for ( let i = 0, l = b.length; i < l; i ++ ) { - - a[ i ] = b[ i ]; - - } - - } - - // Texture unit allocation - - function allocTexUnits( textures, n ) { - - let r = arrayCacheI32[ n ]; - - if ( r === undefined ) { - - r = new Int32Array( n ); - arrayCacheI32[ n ] = r; - - } - - for ( let i = 0; i !== n; ++ i ) { - - r[ i ] = textures.allocateTextureUnit(); - - } - - return r; - - } - - // --- Setters --- - - // Note: Defining these methods externally, because they come in a bunch - // and this way their names minify. - - // Single scalar - - function setValueV1f( gl, v ) { - - const cache = this.cache; - - if ( cache[ 0 ] === v ) return; - - gl.uniform1f( this.addr, v ); - - cache[ 0 ] = v; - - } - - // Single float vector (from flat array or THREE.VectorN) - - function setValueV2f( gl, v ) { - - const cache = this.cache; - - if ( v.x !== undefined ) { - - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y ) { - - gl.uniform2f( this.addr, v.x, v.y ); - - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; - - } - - } else { - - if ( arraysEqual( cache, v ) ) return; - - gl.uniform2fv( this.addr, v ); - - copyArray( cache, v ); - - } - - } - - function setValueV3f( gl, v ) { - - const cache = this.cache; - - if ( v.x !== undefined ) { - - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z ) { - - gl.uniform3f( this.addr, v.x, v.y, v.z ); - - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; - cache[ 2 ] = v.z; - - } - - } else if ( v.r !== undefined ) { - - if ( cache[ 0 ] !== v.r || cache[ 1 ] !== v.g || cache[ 2 ] !== v.b ) { - - gl.uniform3f( this.addr, v.r, v.g, v.b ); - - cache[ 0 ] = v.r; - cache[ 1 ] = v.g; - cache[ 2 ] = v.b; - - } - - } else { - - if ( arraysEqual( cache, v ) ) return; - - gl.uniform3fv( this.addr, v ); - - copyArray( cache, v ); - - } - - } - - function setValueV4f( gl, v ) { - - const cache = this.cache; - - if ( v.x !== undefined ) { - - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z || cache[ 3 ] !== v.w ) { - - gl.uniform4f( this.addr, v.x, v.y, v.z, v.w ); - - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; - cache[ 2 ] = v.z; - cache[ 3 ] = v.w; - - } - - } else { - - if ( arraysEqual( cache, v ) ) return; - - gl.uniform4fv( this.addr, v ); - - copyArray( cache, v ); - - } - - } - - // Single matrix (from flat array or THREE.MatrixN) - - function setValueM2( gl, v ) { - - const cache = this.cache; - const elements = v.elements; - - if ( elements === undefined ) { - - if ( arraysEqual( cache, v ) ) return; - - gl.uniformMatrix2fv( this.addr, false, v ); - - copyArray( cache, v ); - - } else { - - if ( arraysEqual( cache, elements ) ) return; - - mat2array.set( elements ); - - gl.uniformMatrix2fv( this.addr, false, mat2array ); - - copyArray( cache, elements ); - - } - - } - - function setValueM3( gl, v ) { - - const cache = this.cache; - const elements = v.elements; - - if ( elements === undefined ) { - - if ( arraysEqual( cache, v ) ) return; - - gl.uniformMatrix3fv( this.addr, false, v ); - - copyArray( cache, v ); - - } else { - - if ( arraysEqual( cache, elements ) ) return; - - mat3array.set( elements ); - - gl.uniformMatrix3fv( this.addr, false, mat3array ); - - copyArray( cache, elements ); - - } - - } - - function setValueM4( gl, v ) { - - const cache = this.cache; - const elements = v.elements; - - if ( elements === undefined ) { - - if ( arraysEqual( cache, v ) ) return; - - gl.uniformMatrix4fv( this.addr, false, v ); - - copyArray( cache, v ); - - } else { - - if ( arraysEqual( cache, elements ) ) return; - - mat4array.set( elements ); - - gl.uniformMatrix4fv( this.addr, false, mat4array ); - - copyArray( cache, elements ); - - } - - } - - // Single integer / boolean - - function setValueV1i( gl, v ) { - - const cache = this.cache; - - if ( cache[ 0 ] === v ) return; - - gl.uniform1i( this.addr, v ); - - cache[ 0 ] = v; - - } - - // Single integer / boolean vector (from flat array or THREE.VectorN) - - function setValueV2i( gl, v ) { - - const cache = this.cache; - - if ( v.x !== undefined ) { - - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y ) { - - gl.uniform2i( this.addr, v.x, v.y ); - - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; - - } - - } else { - - if ( arraysEqual( cache, v ) ) return; - - gl.uniform2iv( this.addr, v ); - - copyArray( cache, v ); - - } - - } - - function setValueV3i( gl, v ) { - - const cache = this.cache; - - if ( v.x !== undefined ) { - - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z ) { - - gl.uniform3i( this.addr, v.x, v.y, v.z ); - - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; - cache[ 2 ] = v.z; - - } - - } else { - - if ( arraysEqual( cache, v ) ) return; - - gl.uniform3iv( this.addr, v ); - - copyArray( cache, v ); - - } - - } - - function setValueV4i( gl, v ) { - - const cache = this.cache; - - if ( v.x !== undefined ) { - - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z || cache[ 3 ] !== v.w ) { - - gl.uniform4i( this.addr, v.x, v.y, v.z, v.w ); - - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; - cache[ 2 ] = v.z; - cache[ 3 ] = v.w; - - } - - } else { - - if ( arraysEqual( cache, v ) ) return; - - gl.uniform4iv( this.addr, v ); - - copyArray( cache, v ); - - } - - } - - // Single unsigned integer - - function setValueV1ui( gl, v ) { - - const cache = this.cache; - - if ( cache[ 0 ] === v ) return; - - gl.uniform1ui( this.addr, v ); - - cache[ 0 ] = v; - - } - - // Single unsigned integer vector (from flat array or THREE.VectorN) - - function setValueV2ui( gl, v ) { - - const cache = this.cache; - - if ( v.x !== undefined ) { - - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y ) { - - gl.uniform2ui( this.addr, v.x, v.y ); - - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; - - } - - } else { - - if ( arraysEqual( cache, v ) ) return; - - gl.uniform2uiv( this.addr, v ); - - copyArray( cache, v ); - - } - - } - - function setValueV3ui( gl, v ) { - - const cache = this.cache; - - if ( v.x !== undefined ) { - - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z ) { - - gl.uniform3ui( this.addr, v.x, v.y, v.z ); - - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; - cache[ 2 ] = v.z; - - } - - } else { - - if ( arraysEqual( cache, v ) ) return; - - gl.uniform3uiv( this.addr, v ); - - copyArray( cache, v ); - - } - - } - - function setValueV4ui( gl, v ) { - - const cache = this.cache; - - if ( v.x !== undefined ) { - - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z || cache[ 3 ] !== v.w ) { - - gl.uniform4ui( this.addr, v.x, v.y, v.z, v.w ); - - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; - cache[ 2 ] = v.z; - cache[ 3 ] = v.w; - - } - - } else { - - if ( arraysEqual( cache, v ) ) return; - - gl.uniform4uiv( this.addr, v ); - - copyArray( cache, v ); - - } - - } - - - // Single texture (2D / Cube) - - function setValueT1( gl, v, textures ) { - - const cache = this.cache; - const unit = textures.allocateTextureUnit(); - - if ( cache[ 0 ] !== unit ) { - - gl.uniform1i( this.addr, unit ); - cache[ 0 ] = unit; - - } - - textures.setTexture2D( v || emptyTexture, unit ); - - } - - function setValueT3D1( gl, v, textures ) { - - const cache = this.cache; - const unit = textures.allocateTextureUnit(); - - if ( cache[ 0 ] !== unit ) { - - gl.uniform1i( this.addr, unit ); - cache[ 0 ] = unit; - - } - - textures.setTexture3D( v || empty3dTexture, unit ); - - } - - function setValueT6( gl, v, textures ) { - - const cache = this.cache; - const unit = textures.allocateTextureUnit(); - - if ( cache[ 0 ] !== unit ) { - - gl.uniform1i( this.addr, unit ); - cache[ 0 ] = unit; - - } - - textures.setTextureCube( v || emptyCubeTexture, unit ); - - } - - function setValueT2DArray1( gl, v, textures ) { - - const cache = this.cache; - const unit = textures.allocateTextureUnit(); - - if ( cache[ 0 ] !== unit ) { - - gl.uniform1i( this.addr, unit ); - cache[ 0 ] = unit; - - } - - textures.setTexture2DArray( v || emptyArrayTexture, unit ); - - } - - // Helper to pick the right setter for the singular case - - function getSingularSetter( type ) { - - switch ( type ) { - - case 0x1406: return setValueV1f; // FLOAT - case 0x8b50: return setValueV2f; // _VEC2 - case 0x8b51: return setValueV3f; // _VEC3 - case 0x8b52: return setValueV4f; // _VEC4 - - case 0x8b5a: return setValueM2; // _MAT2 - case 0x8b5b: return setValueM3; // _MAT3 - case 0x8b5c: return setValueM4; // _MAT4 - - case 0x1404: case 0x8b56: return setValueV1i; // INT, BOOL - case 0x8b53: case 0x8b57: return setValueV2i; // _VEC2 - case 0x8b54: case 0x8b58: return setValueV3i; // _VEC3 - case 0x8b55: case 0x8b59: return setValueV4i; // _VEC4 - - case 0x1405: return setValueV1ui; // UINT - case 0x8dc6: return setValueV2ui; // _VEC2 - case 0x8dc7: return setValueV3ui; // _VEC3 - case 0x8dc8: return setValueV4ui; // _VEC4 - - case 0x8b5e: // SAMPLER_2D - case 0x8d66: // SAMPLER_EXTERNAL_OES - case 0x8dca: // INT_SAMPLER_2D - case 0x8dd2: // UNSIGNED_INT_SAMPLER_2D - case 0x8b62: // SAMPLER_2D_SHADOW - return setValueT1; - - case 0x8b5f: // SAMPLER_3D - case 0x8dcb: // INT_SAMPLER_3D - case 0x8dd3: // UNSIGNED_INT_SAMPLER_3D - return setValueT3D1; - - case 0x8b60: // SAMPLER_CUBE - case 0x8dcc: // INT_SAMPLER_CUBE - case 0x8dd4: // UNSIGNED_INT_SAMPLER_CUBE - case 0x8dc5: // SAMPLER_CUBE_SHADOW - return setValueT6; - - case 0x8dc1: // SAMPLER_2D_ARRAY - case 0x8dcf: // INT_SAMPLER_2D_ARRAY - case 0x8dd7: // UNSIGNED_INT_SAMPLER_2D_ARRAY - case 0x8dc4: // SAMPLER_2D_ARRAY_SHADOW - return setValueT2DArray1; - - } - - } - - - // Array of scalars - - function setValueV1fArray( gl, v ) { - - gl.uniform1fv( this.addr, v ); - - } - - // Array of vectors (from flat array or array of THREE.VectorN) - - function setValueV2fArray( gl, v ) { - - const data = flatten( v, this.size, 2 ); - - gl.uniform2fv( this.addr, data ); - - } - - function setValueV3fArray( gl, v ) { - - const data = flatten( v, this.size, 3 ); - - gl.uniform3fv( this.addr, data ); - - } - - function setValueV4fArray( gl, v ) { - - const data = flatten( v, this.size, 4 ); - - gl.uniform4fv( this.addr, data ); - - } - - // Array of matrices (from flat array or array of THREE.MatrixN) - - function setValueM2Array( gl, v ) { - - const data = flatten( v, this.size, 4 ); - - gl.uniformMatrix2fv( this.addr, false, data ); - - } - - function setValueM3Array( gl, v ) { - - const data = flatten( v, this.size, 9 ); - - gl.uniformMatrix3fv( this.addr, false, data ); - - } - - function setValueM4Array( gl, v ) { - - const data = flatten( v, this.size, 16 ); - - gl.uniformMatrix4fv( this.addr, false, data ); - - } - - // Array of integer / boolean - - function setValueV1iArray( gl, v ) { - - gl.uniform1iv( this.addr, v ); - - } - - // Array of integer / boolean vectors (from flat array) - - function setValueV2iArray( gl, v ) { - - gl.uniform2iv( this.addr, v ); - - } - - function setValueV3iArray( gl, v ) { - - gl.uniform3iv( this.addr, v ); - - } - - function setValueV4iArray( gl, v ) { - - gl.uniform4iv( this.addr, v ); - - } - - // Array of unsigned integer - - function setValueV1uiArray( gl, v ) { - - gl.uniform1uiv( this.addr, v ); - - } - - // Array of unsigned integer vectors (from flat array) - - function setValueV2uiArray( gl, v ) { - - gl.uniform2uiv( this.addr, v ); - - } - - function setValueV3uiArray( gl, v ) { - - gl.uniform3uiv( this.addr, v ); - - } - - function setValueV4uiArray( gl, v ) { - - gl.uniform4uiv( this.addr, v ); - - } - - - // Array of textures (2D / 3D / Cube / 2DArray) - - function setValueT1Array( gl, v, textures ) { - - const cache = this.cache; - - const n = v.length; - - const units = allocTexUnits( textures, n ); - - if ( ! arraysEqual( cache, units ) ) { - - gl.uniform1iv( this.addr, units ); - - copyArray( cache, units ); - - } - - for ( let i = 0; i !== n; ++ i ) { - - textures.setTexture2D( v[ i ] || emptyTexture, units[ i ] ); - - } - - } - - function setValueT3DArray( gl, v, textures ) { - - const cache = this.cache; - - const n = v.length; - - const units = allocTexUnits( textures, n ); - - if ( ! arraysEqual( cache, units ) ) { - - gl.uniform1iv( this.addr, units ); - - copyArray( cache, units ); - - } - - for ( let i = 0; i !== n; ++ i ) { - - textures.setTexture3D( v[ i ] || empty3dTexture, units[ i ] ); - - } - - } - - function setValueT6Array( gl, v, textures ) { - - const cache = this.cache; - - const n = v.length; - - const units = allocTexUnits( textures, n ); - - if ( ! arraysEqual( cache, units ) ) { - - gl.uniform1iv( this.addr, units ); - - copyArray( cache, units ); - - } - - for ( let i = 0; i !== n; ++ i ) { - - textures.setTextureCube( v[ i ] || emptyCubeTexture, units[ i ] ); - - } - - } - - function setValueT2DArrayArray( gl, v, textures ) { - - const cache = this.cache; - - const n = v.length; - - const units = allocTexUnits( textures, n ); - - if ( ! arraysEqual( cache, units ) ) { - - gl.uniform1iv( this.addr, units ); - - copyArray( cache, units ); - - } - - for ( let i = 0; i !== n; ++ i ) { - - textures.setTexture2DArray( v[ i ] || emptyArrayTexture, units[ i ] ); - - } - - } - - - // Helper to pick the right setter for a pure (bottom-level) array - - function getPureArraySetter( type ) { - - switch ( type ) { - - case 0x1406: return setValueV1fArray; // FLOAT - case 0x8b50: return setValueV2fArray; // _VEC2 - case 0x8b51: return setValueV3fArray; // _VEC3 - case 0x8b52: return setValueV4fArray; // _VEC4 - - case 0x8b5a: return setValueM2Array; // _MAT2 - case 0x8b5b: return setValueM3Array; // _MAT3 - case 0x8b5c: return setValueM4Array; // _MAT4 - - case 0x1404: case 0x8b56: return setValueV1iArray; // INT, BOOL - case 0x8b53: case 0x8b57: return setValueV2iArray; // _VEC2 - case 0x8b54: case 0x8b58: return setValueV3iArray; // _VEC3 - case 0x8b55: case 0x8b59: return setValueV4iArray; // _VEC4 - - case 0x1405: return setValueV1uiArray; // UINT - case 0x8dc6: return setValueV2uiArray; // _VEC2 - case 0x8dc7: return setValueV3uiArray; // _VEC3 - case 0x8dc8: return setValueV4uiArray; // _VEC4 - - case 0x8b5e: // SAMPLER_2D - case 0x8d66: // SAMPLER_EXTERNAL_OES - case 0x8dca: // INT_SAMPLER_2D - case 0x8dd2: // UNSIGNED_INT_SAMPLER_2D - case 0x8b62: // SAMPLER_2D_SHADOW - return setValueT1Array; - - case 0x8b5f: // SAMPLER_3D - case 0x8dcb: // INT_SAMPLER_3D - case 0x8dd3: // UNSIGNED_INT_SAMPLER_3D - return setValueT3DArray; - - case 0x8b60: // SAMPLER_CUBE - case 0x8dcc: // INT_SAMPLER_CUBE - case 0x8dd4: // UNSIGNED_INT_SAMPLER_CUBE - case 0x8dc5: // SAMPLER_CUBE_SHADOW - return setValueT6Array; - - case 0x8dc1: // SAMPLER_2D_ARRAY - case 0x8dcf: // INT_SAMPLER_2D_ARRAY - case 0x8dd7: // UNSIGNED_INT_SAMPLER_2D_ARRAY - case 0x8dc4: // SAMPLER_2D_ARRAY_SHADOW - return setValueT2DArrayArray; - - } - - } - - // --- Uniform Classes --- - - class SingleUniform { - - constructor( id, activeInfo, addr ) { - - this.id = id; - this.addr = addr; - this.cache = []; - this.setValue = getSingularSetter( activeInfo.type ); - - // this.path = activeInfo.name; // DEBUG - - } - - } - - class PureArrayUniform { - - constructor( id, activeInfo, addr ) { - - this.id = id; - this.addr = addr; - this.cache = []; - this.size = activeInfo.size; - this.setValue = getPureArraySetter( activeInfo.type ); - - // this.path = activeInfo.name; // DEBUG - - } - - } - - class StructuredUniform { - - constructor( id ) { - - this.id = id; - - this.seq = []; - this.map = {}; - - } - - setValue( gl, value, textures ) { - - const seq = this.seq; - - for ( let i = 0, n = seq.length; i !== n; ++ i ) { - - const u = seq[ i ]; - u.setValue( gl, value[ u.id ], textures ); - - } - - } - - } - - // --- Top-level --- - - // Parser - builds up the property tree from the path strings - - const RePathPart = /(\w+)(\])?(\[|\.)?/g; - - // extracts - // - the identifier (member name or array index) - // - followed by an optional right bracket (found when array index) - // - followed by an optional left bracket or dot (type of subscript) - // - // Note: These portions can be read in a non-overlapping fashion and - // allow straightforward parsing of the hierarchy that WebGL encodes - // in the uniform names. - - function addUniform( container, uniformObject ) { - - container.seq.push( uniformObject ); - container.map[ uniformObject.id ] = uniformObject; - - } - - function parseUniform( activeInfo, addr, container ) { - - const path = activeInfo.name, - pathLength = path.length; - - // reset RegExp object, because of the early exit of a previous run - RePathPart.lastIndex = 0; - - while ( true ) { - - const match = RePathPart.exec( path ), - matchEnd = RePathPart.lastIndex; - - let id = match[ 1 ]; - const idIsIndex = match[ 2 ] === ']', - subscript = match[ 3 ]; - - if ( idIsIndex ) id = id | 0; // convert to integer - - if ( subscript === undefined || subscript === '[' && matchEnd + 2 === pathLength ) { - - // bare name or "pure" bottom-level array "[0]" suffix - - addUniform( container, subscript === undefined ? - new SingleUniform( id, activeInfo, addr ) : - new PureArrayUniform( id, activeInfo, addr ) ); - - break; - - } else { - - // step into inner node / create it in case it doesn't exist - - const map = container.map; - let next = map[ id ]; - - if ( next === undefined ) { - - next = new StructuredUniform( id ); - addUniform( container, next ); - - } - - container = next; - - } - - } - - } - - // Root Container - - class WebGLUniforms { - - constructor( gl, program ) { - - this.seq = []; - this.map = {}; - - const n = gl.getProgramParameter( program, gl.ACTIVE_UNIFORMS ); - - for ( let i = 0; i < n; ++ i ) { - - const info = gl.getActiveUniform( program, i ), - addr = gl.getUniformLocation( program, info.name ); - - parseUniform( info, addr, this ); - - } - - } - - setValue( gl, name, value, textures ) { - - const u = this.map[ name ]; - - if ( u !== undefined ) u.setValue( gl, value, textures ); - - } - - setOptional( gl, object, name ) { - - const v = object[ name ]; - - if ( v !== undefined ) this.setValue( gl, name, v ); - - } - - static upload( gl, seq, values, textures ) { - - for ( let i = 0, n = seq.length; i !== n; ++ i ) { - - const u = seq[ i ], - v = values[ u.id ]; - - if ( v.needsUpdate !== false ) { - - // note: always updating when .needsUpdate is undefined - u.setValue( gl, v.value, textures ); - - } - - } - - } - - static seqWithValue( seq, values ) { - - const r = []; - - for ( let i = 0, n = seq.length; i !== n; ++ i ) { - - const u = seq[ i ]; - if ( u.id in values ) r.push( u ); - - } - - return r; - - } - - } - - function WebGLShader( gl, type, string ) { - - const shader = gl.createShader( type ); - - gl.shaderSource( shader, string ); - gl.compileShader( shader ); - - return shader; - - } - - let programIdCount = 0; - - function handleSource( string, errorLine ) { - - const lines = string.split( '\n' ); - const lines2 = []; - - const from = Math.max( errorLine - 6, 0 ); - const to = Math.min( errorLine + 6, lines.length ); - - for ( let i = from; i < to; i ++ ) { - - const line = i + 1; - lines2.push( `${line === errorLine ? '>' : ' '} ${line}: ${lines[ i ]}` ); - - } - - return lines2.join( '\n' ); - - } - - function getEncodingComponents( colorSpace ) { - - switch ( colorSpace ) { - - case LinearSRGBColorSpace: - return [ 'Linear', '( value )' ]; - case SRGBColorSpace: - return [ 'sRGB', '( value )' ]; - default: - console.warn( 'THREE.WebGLProgram: Unsupported color space:', colorSpace ); - return [ 'Linear', '( value )' ]; - - } - - } - - function getShaderErrors( gl, shader, type ) { - - const status = gl.getShaderParameter( shader, gl.COMPILE_STATUS ); - const errors = gl.getShaderInfoLog( shader ).trim(); - - if ( status && errors === '' ) return ''; - - const errorMatches = /ERROR: 0:(\d+)/.exec( errors ); - if ( errorMatches ) { - - // --enable-privileged-webgl-extension - // console.log( '**' + type + '**', gl.getExtension( 'WEBGL_debug_shaders' ).getTranslatedShaderSource( shader ) ); - - const errorLine = parseInt( errorMatches[ 1 ] ); - return type.toUpperCase() + '\n\n' + errors + '\n\n' + handleSource( gl.getShaderSource( shader ), errorLine ); - - } else { - - return errors; - - } - - } - - function getTexelEncodingFunction( functionName, colorSpace ) { - - const components = getEncodingComponents( colorSpace ); - return 'vec4 ' + functionName + '( vec4 value ) { return LinearTo' + components[ 0 ] + components[ 1 ] + '; }'; - - } - - function getToneMappingFunction( functionName, toneMapping ) { - - let toneMappingName; - - switch ( toneMapping ) { - - case LinearToneMapping: - toneMappingName = 'Linear'; - break; - - case ReinhardToneMapping: - toneMappingName = 'Reinhard'; - break; - - case CineonToneMapping: - toneMappingName = 'OptimizedCineon'; - break; - - case ACESFilmicToneMapping: - toneMappingName = 'ACESFilmic'; - break; - - case CustomToneMapping: - toneMappingName = 'Custom'; - break; - - default: - console.warn( 'THREE.WebGLProgram: Unsupported toneMapping:', toneMapping ); - toneMappingName = 'Linear'; - - } - - return 'vec3 ' + functionName + '( vec3 color ) { return ' + toneMappingName + 'ToneMapping( color ); }'; - - } - - function generateExtensions( parameters ) { - - const chunks = [ - ( parameters.extensionDerivatives || !! parameters.envMapCubeUVHeight || parameters.bumpMap || parameters.normalMapTangentSpace || parameters.clearcoatNormalMap || parameters.flatShading || parameters.shaderID === 'physical' ) ? '#extension GL_OES_standard_derivatives : enable' : '', - ( parameters.extensionFragDepth || parameters.logarithmicDepthBuffer ) && parameters.rendererExtensionFragDepth ? '#extension GL_EXT_frag_depth : enable' : '', - ( parameters.extensionDrawBuffers && parameters.rendererExtensionDrawBuffers ) ? '#extension GL_EXT_draw_buffers : require' : '', - ( parameters.extensionShaderTextureLOD || parameters.envMap || parameters.transmission ) && parameters.rendererExtensionShaderTextureLod ? '#extension GL_EXT_shader_texture_lod : enable' : '' - ]; - - return chunks.filter( filterEmptyLine ).join( '\n' ); - - } - - function generateDefines( defines ) { - - const chunks = []; - - for ( const name in defines ) { - - const value = defines[ name ]; - - if ( value === false ) continue; - - chunks.push( '#define ' + name + ' ' + value ); - - } - - return chunks.join( '\n' ); - - } - - function fetchAttributeLocations( gl, program ) { - - const attributes = {}; - - const n = gl.getProgramParameter( program, gl.ACTIVE_ATTRIBUTES ); - - for ( let i = 0; i < n; i ++ ) { - - const info = gl.getActiveAttrib( program, i ); - const name = info.name; - - let locationSize = 1; - if ( info.type === gl.FLOAT_MAT2 ) locationSize = 2; - if ( info.type === gl.FLOAT_MAT3 ) locationSize = 3; - if ( info.type === gl.FLOAT_MAT4 ) locationSize = 4; - - // console.log( 'THREE.WebGLProgram: ACTIVE VERTEX ATTRIBUTE:', name, i ); - - attributes[ name ] = { - type: info.type, - location: gl.getAttribLocation( program, name ), - locationSize: locationSize - }; - - } - - return attributes; - - } - - function filterEmptyLine( string ) { - - return string !== ''; - - } - - function replaceLightNums( string, parameters ) { - - const numSpotLightCoords = parameters.numSpotLightShadows + parameters.numSpotLightMaps - parameters.numSpotLightShadowsWithMaps; - - return string - .replace( /NUM_DIR_LIGHTS/g, parameters.numDirLights ) - .replace( /NUM_SPOT_LIGHTS/g, parameters.numSpotLights ) - .replace( /NUM_SPOT_LIGHT_MAPS/g, parameters.numSpotLightMaps ) - .replace( /NUM_SPOT_LIGHT_COORDS/g, numSpotLightCoords ) - .replace( /NUM_RECT_AREA_LIGHTS/g, parameters.numRectAreaLights ) - .replace( /NUM_POINT_LIGHTS/g, parameters.numPointLights ) - .replace( /NUM_HEMI_LIGHTS/g, parameters.numHemiLights ) - .replace( /NUM_DIR_LIGHT_SHADOWS/g, parameters.numDirLightShadows ) - .replace( /NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS/g, parameters.numSpotLightShadowsWithMaps ) - .replace( /NUM_SPOT_LIGHT_SHADOWS/g, parameters.numSpotLightShadows ) - .replace( /NUM_POINT_LIGHT_SHADOWS/g, parameters.numPointLightShadows ); - - } - - function replaceClippingPlaneNums( string, parameters ) { - - return string - .replace( /NUM_CLIPPING_PLANES/g, parameters.numClippingPlanes ) - .replace( /UNION_CLIPPING_PLANES/g, ( parameters.numClippingPlanes - parameters.numClipIntersection ) ); - - } - - // Resolve Includes - - const includePattern = /^[ \t]*#include +<([\w\d./]+)>/gm; - - function resolveIncludes( string ) { - - return string.replace( includePattern, includeReplacer ); - - } - - function includeReplacer( match, include ) { - - const string = ShaderChunk[ include ]; - - if ( string === undefined ) { - - throw new Error( 'Can not resolve #include <' + include + '>' ); - - } - - return resolveIncludes( string ); - - } - - // Unroll Loops - - const unrollLoopPattern = /#pragma unroll_loop_start\s+for\s*\(\s*int\s+i\s*=\s*(\d+)\s*;\s*i\s*<\s*(\d+)\s*;\s*i\s*\+\+\s*\)\s*{([\s\S]+?)}\s+#pragma unroll_loop_end/g; - - function unrollLoops( string ) { - - return string.replace( unrollLoopPattern, loopReplacer ); - - } - - function loopReplacer( match, start, end, snippet ) { - - let string = ''; - - for ( let i = parseInt( start ); i < parseInt( end ); i ++ ) { - - string += snippet - .replace( /\[\s*i\s*\]/g, '[ ' + i + ' ]' ) - .replace( /UNROLLED_LOOP_INDEX/g, i ); - - } - - return string; - - } - - // - - function generatePrecision( parameters ) { - - let precisionstring = 'precision ' + parameters.precision + ' float;\nprecision ' + parameters.precision + ' int;'; - - if ( parameters.precision === 'highp' ) { - - precisionstring += '\n#define HIGH_PRECISION'; - - } else if ( parameters.precision === 'mediump' ) { - - precisionstring += '\n#define MEDIUM_PRECISION'; - - } else if ( parameters.precision === 'lowp' ) { - - precisionstring += '\n#define LOW_PRECISION'; - - } - - return precisionstring; - - } - - function generateShadowMapTypeDefine( parameters ) { - - let shadowMapTypeDefine = 'SHADOWMAP_TYPE_BASIC'; - - if ( parameters.shadowMapType === PCFShadowMap ) { - - shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF'; - - } else if ( parameters.shadowMapType === PCFSoftShadowMap ) { - - shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF_SOFT'; - - } else if ( parameters.shadowMapType === VSMShadowMap ) { - - shadowMapTypeDefine = 'SHADOWMAP_TYPE_VSM'; - - } - - return shadowMapTypeDefine; - - } - - function generateEnvMapTypeDefine( parameters ) { - - let envMapTypeDefine = 'ENVMAP_TYPE_CUBE'; - - if ( parameters.envMap ) { - - switch ( parameters.envMapMode ) { - - case CubeReflectionMapping: - case CubeRefractionMapping: - envMapTypeDefine = 'ENVMAP_TYPE_CUBE'; - break; - - case CubeUVReflectionMapping: - envMapTypeDefine = 'ENVMAP_TYPE_CUBE_UV'; - break; - - } - - } - - return envMapTypeDefine; - - } - - function generateEnvMapModeDefine( parameters ) { - - let envMapModeDefine = 'ENVMAP_MODE_REFLECTION'; - - if ( parameters.envMap ) { - - switch ( parameters.envMapMode ) { - - case CubeRefractionMapping: - - envMapModeDefine = 'ENVMAP_MODE_REFRACTION'; - break; - - } - - } - - return envMapModeDefine; - - } - - function generateEnvMapBlendingDefine( parameters ) { - - let envMapBlendingDefine = 'ENVMAP_BLENDING_NONE'; - - if ( parameters.envMap ) { - - switch ( parameters.combine ) { - - case MultiplyOperation: - envMapBlendingDefine = 'ENVMAP_BLENDING_MULTIPLY'; - break; - - case MixOperation: - envMapBlendingDefine = 'ENVMAP_BLENDING_MIX'; - break; - - case AddOperation: - envMapBlendingDefine = 'ENVMAP_BLENDING_ADD'; - break; - - } - - } - - return envMapBlendingDefine; - - } - - function generateCubeUVSize( parameters ) { - - const imageHeight = parameters.envMapCubeUVHeight; - - if ( imageHeight === null ) return null; - - const maxMip = Math.log2( imageHeight ) - 2; - - const texelHeight = 1.0 / imageHeight; - - const texelWidth = 1.0 / ( 3 * Math.max( Math.pow( 2, maxMip ), 7 * 16 ) ); - - return { texelWidth, texelHeight, maxMip }; - - } - - function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) { - - // TODO Send this event to Three.js DevTools - // console.log( 'WebGLProgram', cacheKey ); - - const gl = renderer.getContext(); - - const defines = parameters.defines; - - let vertexShader = parameters.vertexShader; - let fragmentShader = parameters.fragmentShader; - - const shadowMapTypeDefine = generateShadowMapTypeDefine( parameters ); - const envMapTypeDefine = generateEnvMapTypeDefine( parameters ); - const envMapModeDefine = generateEnvMapModeDefine( parameters ); - const envMapBlendingDefine = generateEnvMapBlendingDefine( parameters ); - const envMapCubeUVSize = generateCubeUVSize( parameters ); - - const customExtensions = parameters.isWebGL2 ? '' : generateExtensions( parameters ); - - const customDefines = generateDefines( defines ); - - const program = gl.createProgram(); - - let prefixVertex, prefixFragment; - let versionString = parameters.glslVersion ? '#version ' + parameters.glslVersion + '\n' : ''; - - if ( parameters.isRawShaderMaterial ) { - - prefixVertex = [ - - customDefines - - ].filter( filterEmptyLine ).join( '\n' ); - - if ( prefixVertex.length > 0 ) { - - prefixVertex += '\n'; - - } - - prefixFragment = [ - - customExtensions, - customDefines - - ].filter( filterEmptyLine ).join( '\n' ); - - if ( prefixFragment.length > 0 ) { - - prefixFragment += '\n'; - - } - - } else { - - prefixVertex = [ - - generatePrecision( parameters ), - - '#define SHADER_NAME ' + parameters.shaderName, - - customDefines, - - parameters.instancing ? '#define USE_INSTANCING' : '', - parameters.instancingColor ? '#define USE_INSTANCING_COLOR' : '', - - parameters.useFog && parameters.fog ? '#define USE_FOG' : '', - parameters.useFog && parameters.fogExp2 ? '#define FOG_EXP2' : '', - - parameters.map ? '#define USE_MAP' : '', - parameters.envMap ? '#define USE_ENVMAP' : '', - parameters.envMap ? '#define ' + envMapModeDefine : '', - parameters.lightMap ? '#define USE_LIGHTMAP' : '', - parameters.aoMap ? '#define USE_AOMAP' : '', - parameters.bumpMap ? '#define USE_BUMPMAP' : '', - parameters.normalMap ? '#define USE_NORMALMAP' : '', - parameters.normalMapObjectSpace ? '#define USE_NORMALMAP_OBJECTSPACE' : '', - parameters.normalMapTangentSpace ? '#define USE_NORMALMAP_TANGENTSPACE' : '', - parameters.displacementMap ? '#define USE_DISPLACEMENTMAP' : '', - parameters.emissiveMap ? '#define USE_EMISSIVEMAP' : '', - - parameters.anisotropyMap ? '#define USE_ANISOTROPYMAP' : '', - - parameters.clearcoatMap ? '#define USE_CLEARCOATMAP' : '', - parameters.clearcoatRoughnessMap ? '#define USE_CLEARCOAT_ROUGHNESSMAP' : '', - parameters.clearcoatNormalMap ? '#define USE_CLEARCOAT_NORMALMAP' : '', - - parameters.iridescenceMap ? '#define USE_IRIDESCENCEMAP' : '', - parameters.iridescenceThicknessMap ? '#define USE_IRIDESCENCE_THICKNESSMAP' : '', - - parameters.specularMap ? '#define USE_SPECULARMAP' : '', - parameters.specularColorMap ? '#define USE_SPECULAR_COLORMAP' : '', - parameters.specularIntensityMap ? '#define USE_SPECULAR_INTENSITYMAP' : '', - - parameters.roughnessMap ? '#define USE_ROUGHNESSMAP' : '', - parameters.metalnessMap ? '#define USE_METALNESSMAP' : '', - parameters.alphaMap ? '#define USE_ALPHAMAP' : '', - - parameters.transmission ? '#define USE_TRANSMISSION' : '', - parameters.transmissionMap ? '#define USE_TRANSMISSIONMAP' : '', - parameters.thicknessMap ? '#define USE_THICKNESSMAP' : '', - - parameters.sheenColorMap ? '#define USE_SHEEN_COLORMAP' : '', - parameters.sheenRoughnessMap ? '#define USE_SHEEN_ROUGHNESSMAP' : '', - - // - - parameters.mapUv ? '#define MAP_UV ' + parameters.mapUv : '', - parameters.alphaMapUv ? '#define ALPHAMAP_UV ' + parameters.alphaMapUv : '', - parameters.lightMapUv ? '#define LIGHTMAP_UV ' + parameters.lightMapUv : '', - parameters.aoMapUv ? '#define AOMAP_UV ' + parameters.aoMapUv : '', - parameters.emissiveMapUv ? '#define EMISSIVEMAP_UV ' + parameters.emissiveMapUv : '', - parameters.bumpMapUv ? '#define BUMPMAP_UV ' + parameters.bumpMapUv : '', - parameters.normalMapUv ? '#define NORMALMAP_UV ' + parameters.normalMapUv : '', - parameters.displacementMapUv ? '#define DISPLACEMENTMAP_UV ' + parameters.displacementMapUv : '', - - parameters.metalnessMapUv ? '#define METALNESSMAP_UV ' + parameters.metalnessMapUv : '', - parameters.roughnessMapUv ? '#define ROUGHNESSMAP_UV ' + parameters.roughnessMapUv : '', - - parameters.anisotropyMapUv ? '#define ANISOTROPYMAP_UV ' + parameters.anisotropyMapUv : '', - - parameters.clearcoatMapUv ? '#define CLEARCOATMAP_UV ' + parameters.clearcoatMapUv : '', - parameters.clearcoatNormalMapUv ? '#define CLEARCOAT_NORMALMAP_UV ' + parameters.clearcoatNormalMapUv : '', - parameters.clearcoatRoughnessMapUv ? '#define CLEARCOAT_ROUGHNESSMAP_UV ' + parameters.clearcoatRoughnessMapUv : '', - - parameters.iridescenceMapUv ? '#define IRIDESCENCEMAP_UV ' + parameters.iridescenceMapUv : '', - parameters.iridescenceThicknessMapUv ? '#define IRIDESCENCE_THICKNESSMAP_UV ' + parameters.iridescenceThicknessMapUv : '', - - parameters.sheenColorMapUv ? '#define SHEEN_COLORMAP_UV ' + parameters.sheenColorMapUv : '', - parameters.sheenRoughnessMapUv ? '#define SHEEN_ROUGHNESSMAP_UV ' + parameters.sheenRoughnessMapUv : '', - - parameters.specularMapUv ? '#define SPECULARMAP_UV ' + parameters.specularMapUv : '', - parameters.specularColorMapUv ? '#define SPECULAR_COLORMAP_UV ' + parameters.specularColorMapUv : '', - parameters.specularIntensityMapUv ? '#define SPECULAR_INTENSITYMAP_UV ' + parameters.specularIntensityMapUv : '', - - parameters.transmissionMapUv ? '#define TRANSMISSIONMAP_UV ' + parameters.transmissionMapUv : '', - parameters.thicknessMapUv ? '#define THICKNESSMAP_UV ' + parameters.thicknessMapUv : '', - - // - - parameters.vertexTangents ? '#define USE_TANGENT' : '', - parameters.vertexColors ? '#define USE_COLOR' : '', - parameters.vertexAlphas ? '#define USE_COLOR_ALPHA' : '', - parameters.vertexUv1s ? '#define USE_UV1' : '', - parameters.vertexUv2s ? '#define USE_UV2' : '', - parameters.vertexUv3s ? '#define USE_UV3' : '', - - parameters.pointsUvs ? '#define USE_POINTS_UV' : '', - - parameters.flatShading ? '#define FLAT_SHADED' : '', - - parameters.skinning ? '#define USE_SKINNING' : '', - - parameters.morphTargets ? '#define USE_MORPHTARGETS' : '', - parameters.morphNormals && parameters.flatShading === false ? '#define USE_MORPHNORMALS' : '', - ( parameters.morphColors && parameters.isWebGL2 ) ? '#define USE_MORPHCOLORS' : '', - ( parameters.morphTargetsCount > 0 && parameters.isWebGL2 ) ? '#define MORPHTARGETS_TEXTURE' : '', - ( parameters.morphTargetsCount > 0 && parameters.isWebGL2 ) ? '#define MORPHTARGETS_TEXTURE_STRIDE ' + parameters.morphTextureStride : '', - ( parameters.morphTargetsCount > 0 && parameters.isWebGL2 ) ? '#define MORPHTARGETS_COUNT ' + parameters.morphTargetsCount : '', - parameters.doubleSided ? '#define DOUBLE_SIDED' : '', - parameters.flipSided ? '#define FLIP_SIDED' : '', - - parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '', - parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '', - - parameters.sizeAttenuation ? '#define USE_SIZEATTENUATION' : '', - - parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '', - ( parameters.logarithmicDepthBuffer && parameters.rendererExtensionFragDepth ) ? '#define USE_LOGDEPTHBUF_EXT' : '', - - 'uniform mat4 modelMatrix;', - 'uniform mat4 modelViewMatrix;', - 'uniform mat4 projectionMatrix;', - 'uniform mat4 viewMatrix;', - 'uniform mat3 normalMatrix;', - 'uniform vec3 cameraPosition;', - 'uniform bool isOrthographic;', - - '#ifdef USE_INSTANCING', - - ' attribute mat4 instanceMatrix;', - - '#endif', - - '#ifdef USE_INSTANCING_COLOR', - - ' attribute vec3 instanceColor;', - - '#endif', - - 'attribute vec3 position;', - 'attribute vec3 normal;', - 'attribute vec2 uv;', - - '#ifdef USE_UV1', - - ' attribute vec2 uv1;', - - '#endif', - - '#ifdef USE_UV2', - - ' attribute vec2 uv2;', - - '#endif', - - '#ifdef USE_UV3', - - ' attribute vec2 uv3;', - - '#endif', - - '#ifdef USE_TANGENT', - - ' attribute vec4 tangent;', - - '#endif', - - '#if defined( USE_COLOR_ALPHA )', - - ' attribute vec4 color;', - - '#elif defined( USE_COLOR )', - - ' attribute vec3 color;', - - '#endif', - - '#if ( defined( USE_MORPHTARGETS ) && ! defined( MORPHTARGETS_TEXTURE ) )', - - ' attribute vec3 morphTarget0;', - ' attribute vec3 morphTarget1;', - ' attribute vec3 morphTarget2;', - ' attribute vec3 morphTarget3;', - - ' #ifdef USE_MORPHNORMALS', - - ' attribute vec3 morphNormal0;', - ' attribute vec3 morphNormal1;', - ' attribute vec3 morphNormal2;', - ' attribute vec3 morphNormal3;', - - ' #else', - - ' attribute vec3 morphTarget4;', - ' attribute vec3 morphTarget5;', - ' attribute vec3 morphTarget6;', - ' attribute vec3 morphTarget7;', - - ' #endif', - - '#endif', - - '#ifdef USE_SKINNING', - - ' attribute vec4 skinIndex;', - ' attribute vec4 skinWeight;', - - '#endif', - - '\n' - - ].filter( filterEmptyLine ).join( '\n' ); - - prefixFragment = [ - - customExtensions, - - generatePrecision( parameters ), - - '#define SHADER_NAME ' + parameters.shaderName, - - customDefines, - - parameters.useFog && parameters.fog ? '#define USE_FOG' : '', - parameters.useFog && parameters.fogExp2 ? '#define FOG_EXP2' : '', - - parameters.map ? '#define USE_MAP' : '', - parameters.matcap ? '#define USE_MATCAP' : '', - parameters.envMap ? '#define USE_ENVMAP' : '', - parameters.envMap ? '#define ' + envMapTypeDefine : '', - parameters.envMap ? '#define ' + envMapModeDefine : '', - parameters.envMap ? '#define ' + envMapBlendingDefine : '', - envMapCubeUVSize ? '#define CUBEUV_TEXEL_WIDTH ' + envMapCubeUVSize.texelWidth : '', - envMapCubeUVSize ? '#define CUBEUV_TEXEL_HEIGHT ' + envMapCubeUVSize.texelHeight : '', - envMapCubeUVSize ? '#define CUBEUV_MAX_MIP ' + envMapCubeUVSize.maxMip + '.0' : '', - parameters.lightMap ? '#define USE_LIGHTMAP' : '', - parameters.aoMap ? '#define USE_AOMAP' : '', - parameters.bumpMap ? '#define USE_BUMPMAP' : '', - parameters.normalMap ? '#define USE_NORMALMAP' : '', - parameters.normalMapObjectSpace ? '#define USE_NORMALMAP_OBJECTSPACE' : '', - parameters.normalMapTangentSpace ? '#define USE_NORMALMAP_TANGENTSPACE' : '', - parameters.emissiveMap ? '#define USE_EMISSIVEMAP' : '', - - parameters.anisotropy ? '#define USE_ANISOTROPY' : '', - parameters.anisotropyMap ? '#define USE_ANISOTROPYMAP' : '', - - parameters.clearcoat ? '#define USE_CLEARCOAT' : '', - parameters.clearcoatMap ? '#define USE_CLEARCOATMAP' : '', - parameters.clearcoatRoughnessMap ? '#define USE_CLEARCOAT_ROUGHNESSMAP' : '', - parameters.clearcoatNormalMap ? '#define USE_CLEARCOAT_NORMALMAP' : '', - - parameters.iridescence ? '#define USE_IRIDESCENCE' : '', - parameters.iridescenceMap ? '#define USE_IRIDESCENCEMAP' : '', - parameters.iridescenceThicknessMap ? '#define USE_IRIDESCENCE_THICKNESSMAP' : '', - - parameters.specularMap ? '#define USE_SPECULARMAP' : '', - parameters.specularColorMap ? '#define USE_SPECULAR_COLORMAP' : '', - parameters.specularIntensityMap ? '#define USE_SPECULAR_INTENSITYMAP' : '', - - parameters.roughnessMap ? '#define USE_ROUGHNESSMAP' : '', - parameters.metalnessMap ? '#define USE_METALNESSMAP' : '', - - parameters.alphaMap ? '#define USE_ALPHAMAP' : '', - parameters.alphaTest ? '#define USE_ALPHATEST' : '', - - parameters.sheen ? '#define USE_SHEEN' : '', - parameters.sheenColorMap ? '#define USE_SHEEN_COLORMAP' : '', - parameters.sheenRoughnessMap ? '#define USE_SHEEN_ROUGHNESSMAP' : '', - - parameters.transmission ? '#define USE_TRANSMISSION' : '', - parameters.transmissionMap ? '#define USE_TRANSMISSIONMAP' : '', - parameters.thicknessMap ? '#define USE_THICKNESSMAP' : '', - - parameters.vertexTangents ? '#define USE_TANGENT' : '', - parameters.vertexColors || parameters.instancingColor ? '#define USE_COLOR' : '', - parameters.vertexAlphas ? '#define USE_COLOR_ALPHA' : '', - parameters.vertexUv1s ? '#define USE_UV1' : '', - parameters.vertexUv2s ? '#define USE_UV2' : '', - parameters.vertexUv3s ? '#define USE_UV3' : '', - - parameters.pointsUvs ? '#define USE_POINTS_UV' : '', - - parameters.gradientMap ? '#define USE_GRADIENTMAP' : '', - - parameters.flatShading ? '#define FLAT_SHADED' : '', - - parameters.doubleSided ? '#define DOUBLE_SIDED' : '', - parameters.flipSided ? '#define FLIP_SIDED' : '', - - parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '', - parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '', - - parameters.premultipliedAlpha ? '#define PREMULTIPLIED_ALPHA' : '', - - parameters.useLegacyLights ? '#define LEGACY_LIGHTS' : '', - - parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '', - ( parameters.logarithmicDepthBuffer && parameters.rendererExtensionFragDepth ) ? '#define USE_LOGDEPTHBUF_EXT' : '', - - 'uniform mat4 viewMatrix;', - 'uniform vec3 cameraPosition;', - 'uniform bool isOrthographic;', - - ( parameters.toneMapping !== NoToneMapping ) ? '#define TONE_MAPPING' : '', - ( parameters.toneMapping !== NoToneMapping ) ? ShaderChunk[ 'tonemapping_pars_fragment' ] : '', // this code is required here because it is used by the toneMapping() function defined below - ( parameters.toneMapping !== NoToneMapping ) ? getToneMappingFunction( 'toneMapping', parameters.toneMapping ) : '', - - parameters.dithering ? '#define DITHERING' : '', - parameters.opaque ? '#define OPAQUE' : '', - - ShaderChunk[ 'encodings_pars_fragment' ], // this code is required here because it is used by the various encoding/decoding function defined below - getTexelEncodingFunction( 'linearToOutputTexel', parameters.outputColorSpace ), - - parameters.useDepthPacking ? '#define DEPTH_PACKING ' + parameters.depthPacking : '', - - '\n' - - ].filter( filterEmptyLine ).join( '\n' ); - - } - - vertexShader = resolveIncludes( vertexShader ); - vertexShader = replaceLightNums( vertexShader, parameters ); - vertexShader = replaceClippingPlaneNums( vertexShader, parameters ); - - fragmentShader = resolveIncludes( fragmentShader ); - fragmentShader = replaceLightNums( fragmentShader, parameters ); - fragmentShader = replaceClippingPlaneNums( fragmentShader, parameters ); - - vertexShader = unrollLoops( vertexShader ); - fragmentShader = unrollLoops( fragmentShader ); - - if ( parameters.isWebGL2 && parameters.isRawShaderMaterial !== true ) { - - // GLSL 3.0 conversion for built-in materials and ShaderMaterial - - versionString = '#version 300 es\n'; - - prefixVertex = [ - 'precision mediump sampler2DArray;', - '#define attribute in', - '#define varying out', - '#define texture2D texture' - ].join( '\n' ) + '\n' + prefixVertex; - - prefixFragment = [ - '#define varying in', - ( parameters.glslVersion === GLSL3 ) ? '' : 'layout(location = 0) out highp vec4 pc_fragColor;', - ( parameters.glslVersion === GLSL3 ) ? '' : '#define gl_FragColor pc_fragColor', - '#define gl_FragDepthEXT gl_FragDepth', - '#define texture2D texture', - '#define textureCube texture', - '#define texture2DProj textureProj', - '#define texture2DLodEXT textureLod', - '#define texture2DProjLodEXT textureProjLod', - '#define textureCubeLodEXT textureLod', - '#define texture2DGradEXT textureGrad', - '#define texture2DProjGradEXT textureProjGrad', - '#define textureCubeGradEXT textureGrad' - ].join( '\n' ) + '\n' + prefixFragment; - - } - - const vertexGlsl = versionString + prefixVertex + vertexShader; - const fragmentGlsl = versionString + prefixFragment + fragmentShader; - - // console.log( '*VERTEX*', vertexGlsl ); - // console.log( '*FRAGMENT*', fragmentGlsl ); - - const glVertexShader = WebGLShader( gl, gl.VERTEX_SHADER, vertexGlsl ); - const glFragmentShader = WebGLShader( gl, gl.FRAGMENT_SHADER, fragmentGlsl ); - - gl.attachShader( program, glVertexShader ); - gl.attachShader( program, glFragmentShader ); - - // Force a particular attribute to index 0. - - if ( parameters.index0AttributeName !== undefined ) { - - gl.bindAttribLocation( program, 0, parameters.index0AttributeName ); - - } else if ( parameters.morphTargets === true ) { - - // programs with morphTargets displace position out of attribute 0 - gl.bindAttribLocation( program, 0, 'position' ); - - } - - gl.linkProgram( program ); - - // check for link errors - if ( renderer.debug.checkShaderErrors ) { - - const programLog = gl.getProgramInfoLog( program ).trim(); - const vertexLog = gl.getShaderInfoLog( glVertexShader ).trim(); - const fragmentLog = gl.getShaderInfoLog( glFragmentShader ).trim(); - - let runnable = true; - let haveDiagnostics = true; - - if ( gl.getProgramParameter( program, gl.LINK_STATUS ) === false ) { - - runnable = false; - - if ( typeof renderer.debug.onShaderError === 'function' ) { - - renderer.debug.onShaderError( gl, program, glVertexShader, glFragmentShader ); - - } else { - - // default error reporting - - const vertexErrors = getShaderErrors( gl, glVertexShader, 'vertex' ); - const fragmentErrors = getShaderErrors( gl, glFragmentShader, 'fragment' ); - - console.error( - 'THREE.WebGLProgram: Shader Error ' + gl.getError() + ' - ' + - 'VALIDATE_STATUS ' + gl.getProgramParameter( program, gl.VALIDATE_STATUS ) + '\n\n' + - 'Program Info Log: ' + programLog + '\n' + - vertexErrors + '\n' + - fragmentErrors - ); - - } - - } else if ( programLog !== '' ) { - - console.warn( 'THREE.WebGLProgram: Program Info Log:', programLog ); - - } else if ( vertexLog === '' || fragmentLog === '' ) { - - haveDiagnostics = false; - - } - - if ( haveDiagnostics ) { - - this.diagnostics = { - - runnable: runnable, - - programLog: programLog, - - vertexShader: { - - log: vertexLog, - prefix: prefixVertex - - }, - - fragmentShader: { - - log: fragmentLog, - prefix: prefixFragment - - } - - }; - - } - - } - - // Clean up - - // Crashes in iOS9 and iOS10. #18402 - // gl.detachShader( program, glVertexShader ); - // gl.detachShader( program, glFragmentShader ); - - gl.deleteShader( glVertexShader ); - gl.deleteShader( glFragmentShader ); - - // set up caching for uniform locations - - let cachedUniforms; - - this.getUniforms = function () { - - if ( cachedUniforms === undefined ) { - - cachedUniforms = new WebGLUniforms( gl, program ); - - } - - return cachedUniforms; - - }; - - // set up caching for attribute locations - - let cachedAttributes; - - this.getAttributes = function () { - - if ( cachedAttributes === undefined ) { - - cachedAttributes = fetchAttributeLocations( gl, program ); - - } - - return cachedAttributes; - - }; - - // free resource - - this.destroy = function () { - - bindingStates.releaseStatesOfProgram( this ); - - gl.deleteProgram( program ); - this.program = undefined; - - }; - - // - - this.name = parameters.shaderName; - this.id = programIdCount ++; - this.cacheKey = cacheKey; - this.usedTimes = 1; - this.program = program; - this.vertexShader = glVertexShader; - this.fragmentShader = glFragmentShader; - - return this; - - } - - let _id = 0; - - class WebGLShaderCache { - - constructor() { - - this.shaderCache = new Map(); - this.materialCache = new Map(); - - } - - update( material ) { - - const vertexShader = material.vertexShader; - const fragmentShader = material.fragmentShader; - - const vertexShaderStage = this._getShaderStage( vertexShader ); - const fragmentShaderStage = this._getShaderStage( fragmentShader ); - - const materialShaders = this._getShaderCacheForMaterial( material ); - - if ( materialShaders.has( vertexShaderStage ) === false ) { - - materialShaders.add( vertexShaderStage ); - vertexShaderStage.usedTimes ++; - - } - - if ( materialShaders.has( fragmentShaderStage ) === false ) { - - materialShaders.add( fragmentShaderStage ); - fragmentShaderStage.usedTimes ++; - - } - - return this; - - } - - remove( material ) { - - const materialShaders = this.materialCache.get( material ); - - for ( const shaderStage of materialShaders ) { - - shaderStage.usedTimes --; - - if ( shaderStage.usedTimes === 0 ) this.shaderCache.delete( shaderStage.code ); - - } - - this.materialCache.delete( material ); - - return this; - - } - - getVertexShaderID( material ) { - - return this._getShaderStage( material.vertexShader ).id; - - } - - getFragmentShaderID( material ) { - - return this._getShaderStage( material.fragmentShader ).id; - - } - - dispose() { - - this.shaderCache.clear(); - this.materialCache.clear(); - - } - - _getShaderCacheForMaterial( material ) { - - const cache = this.materialCache; - let set = cache.get( material ); - - if ( set === undefined ) { - - set = new Set(); - cache.set( material, set ); - - } - - return set; - - } - - _getShaderStage( code ) { - - const cache = this.shaderCache; - let stage = cache.get( code ); - - if ( stage === undefined ) { - - stage = new WebGLShaderStage( code ); - cache.set( code, stage ); - - } - - return stage; - - } - - } - - class WebGLShaderStage { - - constructor( code ) { - - this.id = _id ++; - - this.code = code; - this.usedTimes = 0; - - } - - } - - function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities, bindingStates, clipping ) { - - const _programLayers = new Layers(); - const _customShaders = new WebGLShaderCache(); - const programs = []; - - const IS_WEBGL2 = capabilities.isWebGL2; - const logarithmicDepthBuffer = capabilities.logarithmicDepthBuffer; - const SUPPORTS_VERTEX_TEXTURES = capabilities.vertexTextures; - - let precision = capabilities.precision; - - const shaderIDs = { - MeshDepthMaterial: 'depth', - MeshDistanceMaterial: 'distanceRGBA', - MeshNormalMaterial: 'normal', - MeshBasicMaterial: 'basic', - MeshLambertMaterial: 'lambert', - MeshPhongMaterial: 'phong', - MeshToonMaterial: 'toon', - MeshStandardMaterial: 'physical', - MeshPhysicalMaterial: 'physical', - MeshMatcapMaterial: 'matcap', - LineBasicMaterial: 'basic', - LineDashedMaterial: 'dashed', - PointsMaterial: 'points', - ShadowMaterial: 'shadow', - SpriteMaterial: 'sprite' - }; - - function getChannel( value ) { - - if ( value === 0 ) return 'uv'; - - return `uv${ value }`; - - } - - function getParameters( material, lights, shadows, scene, object ) { - - const fog = scene.fog; - const geometry = object.geometry; - const environment = material.isMeshStandardMaterial ? scene.environment : null; - - const envMap = ( material.isMeshStandardMaterial ? cubeuvmaps : cubemaps ).get( material.envMap || environment ); - const envMapCubeUVHeight = ( !! envMap ) && ( envMap.mapping === CubeUVReflectionMapping ) ? envMap.image.height : null; - - const shaderID = shaderIDs[ material.type ]; - - // heuristics to create shader parameters according to lights in the scene - // (not to blow over maxLights budget) - - if ( material.precision !== null ) { - - precision = capabilities.getMaxPrecision( material.precision ); - - if ( precision !== material.precision ) { - - console.warn( 'THREE.WebGLProgram.getParameters:', material.precision, 'not supported, using', precision, 'instead.' ); - - } - - } - - // - - const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color; - const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0; - - let morphTextureStride = 0; - - if ( geometry.morphAttributes.position !== undefined ) morphTextureStride = 1; - if ( geometry.morphAttributes.normal !== undefined ) morphTextureStride = 2; - if ( geometry.morphAttributes.color !== undefined ) morphTextureStride = 3; - - // - - let vertexShader, fragmentShader; - let customVertexShaderID, customFragmentShaderID; - - if ( shaderID ) { - - const shader = ShaderLib[ shaderID ]; - - vertexShader = shader.vertexShader; - fragmentShader = shader.fragmentShader; - - } else { - - vertexShader = material.vertexShader; - fragmentShader = material.fragmentShader; - - _customShaders.update( material ); - - customVertexShaderID = _customShaders.getVertexShaderID( material ); - customFragmentShaderID = _customShaders.getFragmentShaderID( material ); - - } - - const currentRenderTarget = renderer.getRenderTarget(); - - const IS_INSTANCEDMESH = object.isInstancedMesh === true; - - const HAS_MAP = !! material.map; - const HAS_MATCAP = !! material.matcap; - const HAS_ENVMAP = !! envMap; - const HAS_AOMAP = !! material.aoMap; - const HAS_LIGHTMAP = !! material.lightMap; - const HAS_BUMPMAP = !! material.bumpMap; - const HAS_NORMALMAP = !! material.normalMap; - const HAS_DISPLACEMENTMAP = !! material.displacementMap; - const HAS_EMISSIVEMAP = !! material.emissiveMap; - - const HAS_METALNESSMAP = !! material.metalnessMap; - const HAS_ROUGHNESSMAP = !! material.roughnessMap; - - const HAS_ANISOTROPY = material.anisotropy > 0; - const HAS_CLEARCOAT = material.clearcoat > 0; - const HAS_IRIDESCENCE = material.iridescence > 0; - const HAS_SHEEN = material.sheen > 0; - const HAS_TRANSMISSION = material.transmission > 0; - - const HAS_ANISOTROPYMAP = HAS_ANISOTROPY && !! material.anisotropyMap; - - const HAS_CLEARCOATMAP = HAS_CLEARCOAT && !! material.clearcoatMap; - const HAS_CLEARCOAT_NORMALMAP = HAS_CLEARCOAT && !! material.clearcoatNormalMap; - const HAS_CLEARCOAT_ROUGHNESSMAP = HAS_CLEARCOAT && !! material.clearcoatRoughnessMap; - - const HAS_IRIDESCENCEMAP = HAS_IRIDESCENCE && !! material.iridescenceMap; - const HAS_IRIDESCENCE_THICKNESSMAP = HAS_IRIDESCENCE && !! material.iridescenceThicknessMap; - - const HAS_SHEEN_COLORMAP = HAS_SHEEN && !! material.sheenColorMap; - const HAS_SHEEN_ROUGHNESSMAP = HAS_SHEEN && !! material.sheenRoughnessMap; - - const HAS_SPECULARMAP = !! material.specularMap; - const HAS_SPECULAR_COLORMAP = !! material.specularColorMap; - const HAS_SPECULAR_INTENSITYMAP = !! material.specularIntensityMap; - - const HAS_TRANSMISSIONMAP = HAS_TRANSMISSION && !! material.transmissionMap; - const HAS_THICKNESSMAP = HAS_TRANSMISSION && !! material.thicknessMap; - - const HAS_GRADIENTMAP = !! material.gradientMap; - - const HAS_ALPHAMAP = !! material.alphaMap; - - const HAS_ALPHATEST = material.alphaTest > 0; - - const HAS_EXTENSIONS = !! material.extensions; - - const HAS_ATTRIBUTE_UV1 = !! geometry.attributes.uv1; - const HAS_ATTRIBUTE_UV2 = !! geometry.attributes.uv2; - const HAS_ATTRIBUTE_UV3 = !! geometry.attributes.uv3; - - const parameters = { - - isWebGL2: IS_WEBGL2, - - shaderID: shaderID, - shaderName: material.type, - - vertexShader: vertexShader, - fragmentShader: fragmentShader, - defines: material.defines, - - customVertexShaderID: customVertexShaderID, - customFragmentShaderID: customFragmentShaderID, - - isRawShaderMaterial: material.isRawShaderMaterial === true, - glslVersion: material.glslVersion, - - precision: precision, - - instancing: IS_INSTANCEDMESH, - instancingColor: IS_INSTANCEDMESH && object.instanceColor !== null, - - supportsVertexTextures: SUPPORTS_VERTEX_TEXTURES, - outputColorSpace: ( currentRenderTarget === null ) ? renderer.outputColorSpace : ( currentRenderTarget.isXRRenderTarget === true ? currentRenderTarget.texture.colorSpace : LinearSRGBColorSpace ), - - map: HAS_MAP, - matcap: HAS_MATCAP, - envMap: HAS_ENVMAP, - envMapMode: HAS_ENVMAP && envMap.mapping, - envMapCubeUVHeight: envMapCubeUVHeight, - aoMap: HAS_AOMAP, - lightMap: HAS_LIGHTMAP, - bumpMap: HAS_BUMPMAP, - normalMap: HAS_NORMALMAP, - displacementMap: SUPPORTS_VERTEX_TEXTURES && HAS_DISPLACEMENTMAP, - emissiveMap: HAS_EMISSIVEMAP, - - normalMapObjectSpace: HAS_NORMALMAP && material.normalMapType === ObjectSpaceNormalMap, - normalMapTangentSpace: HAS_NORMALMAP && material.normalMapType === TangentSpaceNormalMap, - - metalnessMap: HAS_METALNESSMAP, - roughnessMap: HAS_ROUGHNESSMAP, - - anisotropy: HAS_ANISOTROPY, - anisotropyMap: HAS_ANISOTROPYMAP, - - clearcoat: HAS_CLEARCOAT, - clearcoatMap: HAS_CLEARCOATMAP, - clearcoatNormalMap: HAS_CLEARCOAT_NORMALMAP, - clearcoatRoughnessMap: HAS_CLEARCOAT_ROUGHNESSMAP, - - iridescence: HAS_IRIDESCENCE, - iridescenceMap: HAS_IRIDESCENCEMAP, - iridescenceThicknessMap: HAS_IRIDESCENCE_THICKNESSMAP, - - sheen: HAS_SHEEN, - sheenColorMap: HAS_SHEEN_COLORMAP, - sheenRoughnessMap: HAS_SHEEN_ROUGHNESSMAP, - - specularMap: HAS_SPECULARMAP, - specularColorMap: HAS_SPECULAR_COLORMAP, - specularIntensityMap: HAS_SPECULAR_INTENSITYMAP, - - transmission: HAS_TRANSMISSION, - transmissionMap: HAS_TRANSMISSIONMAP, - thicknessMap: HAS_THICKNESSMAP, - - gradientMap: HAS_GRADIENTMAP, - - opaque: material.transparent === false && material.blending === NormalBlending, - - alphaMap: HAS_ALPHAMAP, - alphaTest: HAS_ALPHATEST, - - combine: material.combine, - - // - - mapUv: HAS_MAP && getChannel( material.map.channel ), - aoMapUv: HAS_AOMAP && getChannel( material.aoMap.channel ), - lightMapUv: HAS_LIGHTMAP && getChannel( material.lightMap.channel ), - bumpMapUv: HAS_BUMPMAP && getChannel( material.bumpMap.channel ), - normalMapUv: HAS_NORMALMAP && getChannel( material.normalMap.channel ), - displacementMapUv: HAS_DISPLACEMENTMAP && getChannel( material.displacementMap.channel ), - emissiveMapUv: HAS_EMISSIVEMAP && getChannel( material.emissiveMap.channel ), - - metalnessMapUv: HAS_METALNESSMAP && getChannel( material.metalnessMap.channel ), - roughnessMapUv: HAS_ROUGHNESSMAP && getChannel( material.roughnessMap.channel ), - - anisotropyMapUv: HAS_ANISOTROPYMAP && getChannel( material.anisotropyMap.channel ), - - clearcoatMapUv: HAS_CLEARCOATMAP && getChannel( material.clearcoatMap.channel ), - clearcoatNormalMapUv: HAS_CLEARCOAT_NORMALMAP && getChannel( material.clearcoatNormalMap.channel ), - clearcoatRoughnessMapUv: HAS_CLEARCOAT_ROUGHNESSMAP && getChannel( material.clearcoatRoughnessMap.channel ), - - iridescenceMapUv: HAS_IRIDESCENCEMAP && getChannel( material.iridescenceMap.channel ), - iridescenceThicknessMapUv: HAS_IRIDESCENCE_THICKNESSMAP && getChannel( material.iridescenceThicknessMap.channel ), - - sheenColorMapUv: HAS_SHEEN_COLORMAP && getChannel( material.sheenColorMap.channel ), - sheenRoughnessMapUv: HAS_SHEEN_ROUGHNESSMAP && getChannel( material.sheenRoughnessMap.channel ), - - specularMapUv: HAS_SPECULARMAP && getChannel( material.specularMap.channel ), - specularColorMapUv: HAS_SPECULAR_COLORMAP && getChannel( material.specularColorMap.channel ), - specularIntensityMapUv: HAS_SPECULAR_INTENSITYMAP && getChannel( material.specularIntensityMap.channel ), - - transmissionMapUv: HAS_TRANSMISSIONMAP && getChannel( material.transmissionMap.channel ), - thicknessMapUv: HAS_THICKNESSMAP && getChannel( material.thicknessMap.channel ), - - alphaMapUv: HAS_ALPHAMAP && getChannel( material.alphaMap.channel ), - - // - - vertexTangents: !! geometry.attributes.tangent && ( HAS_NORMALMAP || HAS_ANISOTROPY ), - vertexColors: material.vertexColors, - vertexAlphas: material.vertexColors === true && !! geometry.attributes.color && geometry.attributes.color.itemSize === 4, - vertexUv1s: HAS_ATTRIBUTE_UV1, - vertexUv2s: HAS_ATTRIBUTE_UV2, - vertexUv3s: HAS_ATTRIBUTE_UV3, - - pointsUvs: object.isPoints === true && !! geometry.attributes.uv && ( HAS_MAP || HAS_ALPHAMAP ), - - fog: !! fog, - useFog: material.fog === true, - fogExp2: ( fog && fog.isFogExp2 ), - - flatShading: material.flatShading === true, - - sizeAttenuation: material.sizeAttenuation === true, - logarithmicDepthBuffer: logarithmicDepthBuffer, - - skinning: object.isSkinnedMesh === true, - - morphTargets: geometry.morphAttributes.position !== undefined, - morphNormals: geometry.morphAttributes.normal !== undefined, - morphColors: geometry.morphAttributes.color !== undefined, - morphTargetsCount: morphTargetsCount, - morphTextureStride: morphTextureStride, - - numDirLights: lights.directional.length, - numPointLights: lights.point.length, - numSpotLights: lights.spot.length, - numSpotLightMaps: lights.spotLightMap.length, - numRectAreaLights: lights.rectArea.length, - numHemiLights: lights.hemi.length, - - numDirLightShadows: lights.directionalShadowMap.length, - numPointLightShadows: lights.pointShadowMap.length, - numSpotLightShadows: lights.spotShadowMap.length, - numSpotLightShadowsWithMaps: lights.numSpotLightShadowsWithMaps, - - numClippingPlanes: clipping.numPlanes, - numClipIntersection: clipping.numIntersection, - - dithering: material.dithering, - - shadowMapEnabled: renderer.shadowMap.enabled && shadows.length > 0, - shadowMapType: renderer.shadowMap.type, - - toneMapping: material.toneMapped ? renderer.toneMapping : NoToneMapping, - useLegacyLights: renderer.useLegacyLights, - - premultipliedAlpha: material.premultipliedAlpha, - - doubleSided: material.side === DoubleSide, - flipSided: material.side === BackSide, - - useDepthPacking: material.depthPacking >= 0, - depthPacking: material.depthPacking || 0, - - index0AttributeName: material.index0AttributeName, - - extensionDerivatives: HAS_EXTENSIONS && material.extensions.derivatives === true, - extensionFragDepth: HAS_EXTENSIONS && material.extensions.fragDepth === true, - extensionDrawBuffers: HAS_EXTENSIONS && material.extensions.drawBuffers === true, - extensionShaderTextureLOD: HAS_EXTENSIONS && material.extensions.shaderTextureLOD === true, - - rendererExtensionFragDepth: IS_WEBGL2 || extensions.has( 'EXT_frag_depth' ), - rendererExtensionDrawBuffers: IS_WEBGL2 || extensions.has( 'WEBGL_draw_buffers' ), - rendererExtensionShaderTextureLod: IS_WEBGL2 || extensions.has( 'EXT_shader_texture_lod' ), - - customProgramCacheKey: material.customProgramCacheKey() - - }; - - return parameters; - - } - - function getProgramCacheKey( parameters ) { - - const array = []; - - if ( parameters.shaderID ) { - - array.push( parameters.shaderID ); - - } else { - - array.push( parameters.customVertexShaderID ); - array.push( parameters.customFragmentShaderID ); - - } - - if ( parameters.defines !== undefined ) { - - for ( const name in parameters.defines ) { - - array.push( name ); - array.push( parameters.defines[ name ] ); - - } - - } - - if ( parameters.isRawShaderMaterial === false ) { - - getProgramCacheKeyParameters( array, parameters ); - getProgramCacheKeyBooleans( array, parameters ); - array.push( renderer.outputColorSpace ); - - } - - array.push( parameters.customProgramCacheKey ); - - return array.join(); - - } - - function getProgramCacheKeyParameters( array, parameters ) { - - array.push( parameters.precision ); - array.push( parameters.outputColorSpace ); - array.push( parameters.envMapMode ); - array.push( parameters.envMapCubeUVHeight ); - array.push( parameters.mapUv ); - array.push( parameters.alphaMapUv ); - array.push( parameters.lightMapUv ); - array.push( parameters.aoMapUv ); - array.push( parameters.bumpMapUv ); - array.push( parameters.normalMapUv ); - array.push( parameters.displacementMapUv ); - array.push( parameters.emissiveMapUv ); - array.push( parameters.metalnessMapUv ); - array.push( parameters.roughnessMapUv ); - array.push( parameters.anisotropyMapUv ); - array.push( parameters.clearcoatMapUv ); - array.push( parameters.clearcoatNormalMapUv ); - array.push( parameters.clearcoatRoughnessMapUv ); - array.push( parameters.iridescenceMapUv ); - array.push( parameters.iridescenceThicknessMapUv ); - array.push( parameters.sheenColorMapUv ); - array.push( parameters.sheenRoughnessMapUv ); - array.push( parameters.specularMapUv ); - array.push( parameters.specularColorMapUv ); - array.push( parameters.specularIntensityMapUv ); - array.push( parameters.transmissionMapUv ); - array.push( parameters.thicknessMapUv ); - array.push( parameters.combine ); - array.push( parameters.fogExp2 ); - array.push( parameters.sizeAttenuation ); - array.push( parameters.morphTargetsCount ); - array.push( parameters.morphAttributeCount ); - array.push( parameters.numDirLights ); - array.push( parameters.numPointLights ); - array.push( parameters.numSpotLights ); - array.push( parameters.numSpotLightMaps ); - array.push( parameters.numHemiLights ); - array.push( parameters.numRectAreaLights ); - array.push( parameters.numDirLightShadows ); - array.push( parameters.numPointLightShadows ); - array.push( parameters.numSpotLightShadows ); - array.push( parameters.numSpotLightShadowsWithMaps ); - array.push( parameters.shadowMapType ); - array.push( parameters.toneMapping ); - array.push( parameters.numClippingPlanes ); - array.push( parameters.numClipIntersection ); - array.push( parameters.depthPacking ); - - } - - function getProgramCacheKeyBooleans( array, parameters ) { - - _programLayers.disableAll(); - - if ( parameters.isWebGL2 ) - _programLayers.enable( 0 ); - if ( parameters.supportsVertexTextures ) - _programLayers.enable( 1 ); - if ( parameters.instancing ) - _programLayers.enable( 2 ); - if ( parameters.instancingColor ) - _programLayers.enable( 3 ); - if ( parameters.matcap ) - _programLayers.enable( 4 ); - if ( parameters.envMap ) - _programLayers.enable( 5 ); - if ( parameters.normalMapObjectSpace ) - _programLayers.enable( 6 ); - if ( parameters.normalMapTangentSpace ) - _programLayers.enable( 7 ); - if ( parameters.clearcoat ) - _programLayers.enable( 8 ); - if ( parameters.iridescence ) - _programLayers.enable( 9 ); - if ( parameters.alphaTest ) - _programLayers.enable( 10 ); - if ( parameters.vertexColors ) - _programLayers.enable( 11 ); - if ( parameters.vertexAlphas ) - _programLayers.enable( 12 ); - if ( parameters.vertexUv1s ) - _programLayers.enable( 13 ); - if ( parameters.vertexUv2s ) - _programLayers.enable( 14 ); - if ( parameters.vertexUv3s ) - _programLayers.enable( 15 ); - if ( parameters.vertexTangents ) - _programLayers.enable( 16 ); - if ( parameters.anisotropy ) - _programLayers.enable( 17 ); - - array.push( _programLayers.mask ); - _programLayers.disableAll(); - - if ( parameters.fog ) - _programLayers.enable( 0 ); - if ( parameters.useFog ) - _programLayers.enable( 1 ); - if ( parameters.flatShading ) - _programLayers.enable( 2 ); - if ( parameters.logarithmicDepthBuffer ) - _programLayers.enable( 3 ); - if ( parameters.skinning ) - _programLayers.enable( 4 ); - if ( parameters.morphTargets ) - _programLayers.enable( 5 ); - if ( parameters.morphNormals ) - _programLayers.enable( 6 ); - if ( parameters.morphColors ) - _programLayers.enable( 7 ); - if ( parameters.premultipliedAlpha ) - _programLayers.enable( 8 ); - if ( parameters.shadowMapEnabled ) - _programLayers.enable( 9 ); - if ( parameters.useLegacyLights ) - _programLayers.enable( 10 ); - if ( parameters.doubleSided ) - _programLayers.enable( 11 ); - if ( parameters.flipSided ) - _programLayers.enable( 12 ); - if ( parameters.useDepthPacking ) - _programLayers.enable( 13 ); - if ( parameters.dithering ) - _programLayers.enable( 14 ); - if ( parameters.transmission ) - _programLayers.enable( 15 ); - if ( parameters.sheen ) - _programLayers.enable( 16 ); - if ( parameters.opaque ) - _programLayers.enable( 17 ); - if ( parameters.pointsUvs ) - _programLayers.enable( 18 ); - - array.push( _programLayers.mask ); - - } - - function getUniforms( material ) { - - const shaderID = shaderIDs[ material.type ]; - let uniforms; - - if ( shaderID ) { - - const shader = ShaderLib[ shaderID ]; - uniforms = UniformsUtils.clone( shader.uniforms ); - - } else { - - uniforms = material.uniforms; - - } - - return uniforms; - - } - - function acquireProgram( parameters, cacheKey ) { - - let program; - - // Check if code has been already compiled - for ( let p = 0, pl = programs.length; p < pl; p ++ ) { - - const preexistingProgram = programs[ p ]; - - if ( preexistingProgram.cacheKey === cacheKey ) { - - program = preexistingProgram; - ++ program.usedTimes; - - break; - - } - - } - - if ( program === undefined ) { - - program = new WebGLProgram( renderer, cacheKey, parameters, bindingStates ); - programs.push( program ); - - } - - return program; - - } - - function releaseProgram( program ) { - - if ( -- program.usedTimes === 0 ) { - - // Remove from unordered set - const i = programs.indexOf( program ); - programs[ i ] = programs[ programs.length - 1 ]; - programs.pop(); - - // Free WebGL resources - program.destroy(); - - } - - } - - function releaseShaderCache( material ) { - - _customShaders.remove( material ); - - } - - function dispose() { - - _customShaders.dispose(); - - } - - return { - getParameters: getParameters, - getProgramCacheKey: getProgramCacheKey, - getUniforms: getUniforms, - acquireProgram: acquireProgram, - releaseProgram: releaseProgram, - releaseShaderCache: releaseShaderCache, - // Exposed for resource monitoring & error feedback via renderer.info: - programs: programs, - dispose: dispose - }; - - } - - function WebGLProperties() { - - let properties = new WeakMap(); - - function get( object ) { - - let map = properties.get( object ); - - if ( map === undefined ) { - - map = {}; - properties.set( object, map ); - - } - - return map; - - } - - function remove( object ) { - - properties.delete( object ); - - } - - function update( object, key, value ) { - - properties.get( object )[ key ] = value; - - } - - function dispose() { - - properties = new WeakMap(); - - } - - return { - get: get, - remove: remove, - update: update, - dispose: dispose - }; - - } - - function painterSortStable( a, b ) { - - if ( a.groupOrder !== b.groupOrder ) { - - return a.groupOrder - b.groupOrder; - - } else if ( a.renderOrder !== b.renderOrder ) { - - return a.renderOrder - b.renderOrder; - - } else if ( a.material.id !== b.material.id ) { - - return a.material.id - b.material.id; - - } else if ( a.z !== b.z ) { - - return a.z - b.z; - - } else { - - return a.id - b.id; - - } - - } - - function reversePainterSortStable( a, b ) { - - if ( a.groupOrder !== b.groupOrder ) { - - return a.groupOrder - b.groupOrder; - - } else if ( a.renderOrder !== b.renderOrder ) { - - return a.renderOrder - b.renderOrder; - - } else if ( a.z !== b.z ) { - - return b.z - a.z; - - } else { - - return a.id - b.id; - - } - - } - - - function WebGLRenderList() { - - const renderItems = []; - let renderItemsIndex = 0; - - const opaque = []; - const transmissive = []; - const transparent = []; - - function init() { - - renderItemsIndex = 0; - - opaque.length = 0; - transmissive.length = 0; - transparent.length = 0; - - } - - function getNextRenderItem( object, geometry, material, groupOrder, z, group ) { - - let renderItem = renderItems[ renderItemsIndex ]; - - if ( renderItem === undefined ) { - - renderItem = { - id: object.id, - object: object, - geometry: geometry, - material: material, - groupOrder: groupOrder, - renderOrder: object.renderOrder, - z: z, - group: group - }; - - renderItems[ renderItemsIndex ] = renderItem; - - } else { - - renderItem.id = object.id; - renderItem.object = object; - renderItem.geometry = geometry; - renderItem.material = material; - renderItem.groupOrder = groupOrder; - renderItem.renderOrder = object.renderOrder; - renderItem.z = z; - renderItem.group = group; - - } - - renderItemsIndex ++; - - return renderItem; - - } - - function push( object, geometry, material, groupOrder, z, group ) { - - const renderItem = getNextRenderItem( object, geometry, material, groupOrder, z, group ); - - if ( material.transmission > 0.0 ) { - - transmissive.push( renderItem ); - - } else if ( material.transparent === true ) { - - transparent.push( renderItem ); - - } else { - - opaque.push( renderItem ); - - } - - } - - function unshift( object, geometry, material, groupOrder, z, group ) { - - const renderItem = getNextRenderItem( object, geometry, material, groupOrder, z, group ); - - if ( material.transmission > 0.0 ) { - - transmissive.unshift( renderItem ); - - } else if ( material.transparent === true ) { - - transparent.unshift( renderItem ); - - } else { - - opaque.unshift( renderItem ); - - } - - } - - function sort( customOpaqueSort, customTransparentSort ) { - - if ( opaque.length > 1 ) opaque.sort( customOpaqueSort || painterSortStable ); - if ( transmissive.length > 1 ) transmissive.sort( customTransparentSort || reversePainterSortStable ); - if ( transparent.length > 1 ) transparent.sort( customTransparentSort || reversePainterSortStable ); - - } - - function finish() { - - // Clear references from inactive renderItems in the list - - for ( let i = renderItemsIndex, il = renderItems.length; i < il; i ++ ) { - - const renderItem = renderItems[ i ]; - - if ( renderItem.id === null ) break; - - renderItem.id = null; - renderItem.object = null; - renderItem.geometry = null; - renderItem.material = null; - renderItem.group = null; - - } - - } - - return { - - opaque: opaque, - transmissive: transmissive, - transparent: transparent, - - init: init, - push: push, - unshift: unshift, - finish: finish, - - sort: sort - }; - - } - - function WebGLRenderLists() { - - let lists = new WeakMap(); - - function get( scene, renderCallDepth ) { - - const listArray = lists.get( scene ); - let list; - - if ( listArray === undefined ) { - - list = new WebGLRenderList(); - lists.set( scene, [ list ] ); - - } else { - - if ( renderCallDepth >= listArray.length ) { - - list = new WebGLRenderList(); - listArray.push( list ); - - } else { - - list = listArray[ renderCallDepth ]; - - } - - } - - return list; - - } - - function dispose() { - - lists = new WeakMap(); - - } - - return { - get: get, - dispose: dispose - }; - - } - - function UniformsCache() { - - const lights = {}; - - return { - - get: function ( light ) { - - if ( lights[ light.id ] !== undefined ) { - - return lights[ light.id ]; - - } - - let uniforms; - - switch ( light.type ) { - - case 'DirectionalLight': - uniforms = { - direction: new Vector3(), - color: new Color() - }; - break; - - case 'SpotLight': - uniforms = { - position: new Vector3(), - direction: new Vector3(), - color: new Color(), - distance: 0, - coneCos: 0, - penumbraCos: 0, - decay: 0 - }; - break; - - case 'PointLight': - uniforms = { - position: new Vector3(), - color: new Color(), - distance: 0, - decay: 0 - }; - break; - - case 'HemisphereLight': - uniforms = { - direction: new Vector3(), - skyColor: new Color(), - groundColor: new Color() - }; - break; - - case 'RectAreaLight': - uniforms = { - color: new Color(), - position: new Vector3(), - halfWidth: new Vector3(), - halfHeight: new Vector3() - }; - break; - - } - - lights[ light.id ] = uniforms; - - return uniforms; - - } - - }; - - } - - function ShadowUniformsCache() { - - const lights = {}; - - return { - - get: function ( light ) { - - if ( lights[ light.id ] !== undefined ) { - - return lights[ light.id ]; - - } - - let uniforms; - - switch ( light.type ) { - - case 'DirectionalLight': - uniforms = { - shadowBias: 0, - shadowNormalBias: 0, - shadowRadius: 1, - shadowMapSize: new Vector2() - }; - break; - - case 'SpotLight': - uniforms = { - shadowBias: 0, - shadowNormalBias: 0, - shadowRadius: 1, - shadowMapSize: new Vector2() - }; - break; - - case 'PointLight': - uniforms = { - shadowBias: 0, - shadowNormalBias: 0, - shadowRadius: 1, - shadowMapSize: new Vector2(), - shadowCameraNear: 1, - shadowCameraFar: 1000 - }; - break; - - // TODO (abelnation): set RectAreaLight shadow uniforms - - } - - lights[ light.id ] = uniforms; - - return uniforms; - - } - - }; - - } - - - - let nextVersion = 0; - - function shadowCastingAndTexturingLightsFirst( lightA, lightB ) { - - return ( lightB.castShadow ? 2 : 0 ) - ( lightA.castShadow ? 2 : 0 ) + ( lightB.map ? 1 : 0 ) - ( lightA.map ? 1 : 0 ); - - } - - function WebGLLights( extensions, capabilities ) { - - const cache = new UniformsCache(); - - const shadowCache = ShadowUniformsCache(); - - const state = { - - version: 0, - - hash: { - directionalLength: - 1, - pointLength: - 1, - spotLength: - 1, - rectAreaLength: - 1, - hemiLength: - 1, - - numDirectionalShadows: - 1, - numPointShadows: - 1, - numSpotShadows: - 1, - numSpotMaps: - 1 - }, - - ambient: [ 0, 0, 0 ], - probe: [], - directional: [], - directionalShadow: [], - directionalShadowMap: [], - directionalShadowMatrix: [], - spot: [], - spotLightMap: [], - spotShadow: [], - spotShadowMap: [], - spotLightMatrix: [], - rectArea: [], - rectAreaLTC1: null, - rectAreaLTC2: null, - point: [], - pointShadow: [], - pointShadowMap: [], - pointShadowMatrix: [], - hemi: [], - numSpotLightShadowsWithMaps: 0 - - }; - - for ( let i = 0; i < 9; i ++ ) state.probe.push( new Vector3() ); - - const vector3 = new Vector3(); - const matrix4 = new Matrix4(); - const matrix42 = new Matrix4(); - - function setup( lights, useLegacyLights ) { - - let r = 0, g = 0, b = 0; - - for ( let i = 0; i < 9; i ++ ) state.probe[ i ].set( 0, 0, 0 ); - - let directionalLength = 0; - let pointLength = 0; - let spotLength = 0; - let rectAreaLength = 0; - let hemiLength = 0; - - let numDirectionalShadows = 0; - let numPointShadows = 0; - let numSpotShadows = 0; - let numSpotMaps = 0; - let numSpotShadowsWithMaps = 0; - - // ordering : [shadow casting + map texturing, map texturing, shadow casting, none ] - lights.sort( shadowCastingAndTexturingLightsFirst ); - - // artist-friendly light intensity scaling factor - const scaleFactor = ( useLegacyLights === true ) ? Math.PI : 1; - - for ( let i = 0, l = lights.length; i < l; i ++ ) { - - const light = lights[ i ]; - - const color = light.color; - const intensity = light.intensity; - const distance = light.distance; - - const shadowMap = ( light.shadow && light.shadow.map ) ? light.shadow.map.texture : null; - - if ( light.isAmbientLight ) { - - r += color.r * intensity * scaleFactor; - g += color.g * intensity * scaleFactor; - b += color.b * intensity * scaleFactor; - - } else if ( light.isLightProbe ) { - - for ( let j = 0; j < 9; j ++ ) { - - state.probe[ j ].addScaledVector( light.sh.coefficients[ j ], intensity ); - - } - - } else if ( light.isDirectionalLight ) { - - const uniforms = cache.get( light ); - - uniforms.color.copy( light.color ).multiplyScalar( light.intensity * scaleFactor ); - - if ( light.castShadow ) { - - const shadow = light.shadow; - - const shadowUniforms = shadowCache.get( light ); - - shadowUniforms.shadowBias = shadow.bias; - shadowUniforms.shadowNormalBias = shadow.normalBias; - shadowUniforms.shadowRadius = shadow.radius; - shadowUniforms.shadowMapSize = shadow.mapSize; - - state.directionalShadow[ directionalLength ] = shadowUniforms; - state.directionalShadowMap[ directionalLength ] = shadowMap; - state.directionalShadowMatrix[ directionalLength ] = light.shadow.matrix; - - numDirectionalShadows ++; - - } - - state.directional[ directionalLength ] = uniforms; - - directionalLength ++; - - } else if ( light.isSpotLight ) { - - const uniforms = cache.get( light ); - - uniforms.position.setFromMatrixPosition( light.matrixWorld ); - - uniforms.color.copy( color ).multiplyScalar( intensity * scaleFactor ); - uniforms.distance = distance; - - uniforms.coneCos = Math.cos( light.angle ); - uniforms.penumbraCos = Math.cos( light.angle * ( 1 - light.penumbra ) ); - uniforms.decay = light.decay; - - state.spot[ spotLength ] = uniforms; - - const shadow = light.shadow; - - if ( light.map ) { - - state.spotLightMap[ numSpotMaps ] = light.map; - numSpotMaps ++; - - // make sure the lightMatrix is up to date - // TODO : do it if required only - shadow.updateMatrices( light ); - - if ( light.castShadow ) numSpotShadowsWithMaps ++; - - } - - state.spotLightMatrix[ spotLength ] = shadow.matrix; - - if ( light.castShadow ) { - - const shadowUniforms = shadowCache.get( light ); - - shadowUniforms.shadowBias = shadow.bias; - shadowUniforms.shadowNormalBias = shadow.normalBias; - shadowUniforms.shadowRadius = shadow.radius; - shadowUniforms.shadowMapSize = shadow.mapSize; - - state.spotShadow[ spotLength ] = shadowUniforms; - state.spotShadowMap[ spotLength ] = shadowMap; - - numSpotShadows ++; - - } - - spotLength ++; - - } else if ( light.isRectAreaLight ) { - - const uniforms = cache.get( light ); - - uniforms.color.copy( color ).multiplyScalar( intensity ); - - uniforms.halfWidth.set( light.width * 0.5, 0.0, 0.0 ); - uniforms.halfHeight.set( 0.0, light.height * 0.5, 0.0 ); - - state.rectArea[ rectAreaLength ] = uniforms; - - rectAreaLength ++; - - } else if ( light.isPointLight ) { - - const uniforms = cache.get( light ); - - uniforms.color.copy( light.color ).multiplyScalar( light.intensity * scaleFactor ); - uniforms.distance = light.distance; - uniforms.decay = light.decay; - - if ( light.castShadow ) { - - const shadow = light.shadow; - - const shadowUniforms = shadowCache.get( light ); - - shadowUniforms.shadowBias = shadow.bias; - shadowUniforms.shadowNormalBias = shadow.normalBias; - shadowUniforms.shadowRadius = shadow.radius; - shadowUniforms.shadowMapSize = shadow.mapSize; - shadowUniforms.shadowCameraNear = shadow.camera.near; - shadowUniforms.shadowCameraFar = shadow.camera.far; - - state.pointShadow[ pointLength ] = shadowUniforms; - state.pointShadowMap[ pointLength ] = shadowMap; - state.pointShadowMatrix[ pointLength ] = light.shadow.matrix; - - numPointShadows ++; - - } - - state.point[ pointLength ] = uniforms; - - pointLength ++; - - } else if ( light.isHemisphereLight ) { - - const uniforms = cache.get( light ); - - uniforms.skyColor.copy( light.color ).multiplyScalar( intensity * scaleFactor ); - uniforms.groundColor.copy( light.groundColor ).multiplyScalar( intensity * scaleFactor ); - - state.hemi[ hemiLength ] = uniforms; - - hemiLength ++; - - } - - } - - if ( rectAreaLength > 0 ) { - - if ( capabilities.isWebGL2 ) { - - // WebGL 2 - - state.rectAreaLTC1 = UniformsLib.LTC_FLOAT_1; - state.rectAreaLTC2 = UniformsLib.LTC_FLOAT_2; - - } else { - - // WebGL 1 - - if ( extensions.has( 'OES_texture_float_linear' ) === true ) { - - state.rectAreaLTC1 = UniformsLib.LTC_FLOAT_1; - state.rectAreaLTC2 = UniformsLib.LTC_FLOAT_2; - - } else if ( extensions.has( 'OES_texture_half_float_linear' ) === true ) { - - state.rectAreaLTC1 = UniformsLib.LTC_HALF_1; - state.rectAreaLTC2 = UniformsLib.LTC_HALF_2; - - } else { - - console.error( 'THREE.WebGLRenderer: Unable to use RectAreaLight. Missing WebGL extensions.' ); - - } - - } - - } - - state.ambient[ 0 ] = r; - state.ambient[ 1 ] = g; - state.ambient[ 2 ] = b; - - const hash = state.hash; - - if ( hash.directionalLength !== directionalLength || - hash.pointLength !== pointLength || - hash.spotLength !== spotLength || - hash.rectAreaLength !== rectAreaLength || - hash.hemiLength !== hemiLength || - hash.numDirectionalShadows !== numDirectionalShadows || - hash.numPointShadows !== numPointShadows || - hash.numSpotShadows !== numSpotShadows || - hash.numSpotMaps !== numSpotMaps ) { - - state.directional.length = directionalLength; - state.spot.length = spotLength; - state.rectArea.length = rectAreaLength; - state.point.length = pointLength; - state.hemi.length = hemiLength; - - state.directionalShadow.length = numDirectionalShadows; - state.directionalShadowMap.length = numDirectionalShadows; - state.pointShadow.length = numPointShadows; - state.pointShadowMap.length = numPointShadows; - state.spotShadow.length = numSpotShadows; - state.spotShadowMap.length = numSpotShadows; - state.directionalShadowMatrix.length = numDirectionalShadows; - state.pointShadowMatrix.length = numPointShadows; - state.spotLightMatrix.length = numSpotShadows + numSpotMaps - numSpotShadowsWithMaps; - state.spotLightMap.length = numSpotMaps; - state.numSpotLightShadowsWithMaps = numSpotShadowsWithMaps; - - hash.directionalLength = directionalLength; - hash.pointLength = pointLength; - hash.spotLength = spotLength; - hash.rectAreaLength = rectAreaLength; - hash.hemiLength = hemiLength; - - hash.numDirectionalShadows = numDirectionalShadows; - hash.numPointShadows = numPointShadows; - hash.numSpotShadows = numSpotShadows; - hash.numSpotMaps = numSpotMaps; - - state.version = nextVersion ++; - - } - - } - - function setupView( lights, camera ) { - - let directionalLength = 0; - let pointLength = 0; - let spotLength = 0; - let rectAreaLength = 0; - let hemiLength = 0; - - const viewMatrix = camera.matrixWorldInverse; - - for ( let i = 0, l = lights.length; i < l; i ++ ) { - - const light = lights[ i ]; - - if ( light.isDirectionalLight ) { - - const uniforms = state.directional[ directionalLength ]; - - uniforms.direction.setFromMatrixPosition( light.matrixWorld ); - vector3.setFromMatrixPosition( light.target.matrixWorld ); - uniforms.direction.sub( vector3 ); - uniforms.direction.transformDirection( viewMatrix ); - - directionalLength ++; - - } else if ( light.isSpotLight ) { - - const uniforms = state.spot[ spotLength ]; - - uniforms.position.setFromMatrixPosition( light.matrixWorld ); - uniforms.position.applyMatrix4( viewMatrix ); - - uniforms.direction.setFromMatrixPosition( light.matrixWorld ); - vector3.setFromMatrixPosition( light.target.matrixWorld ); - uniforms.direction.sub( vector3 ); - uniforms.direction.transformDirection( viewMatrix ); - - spotLength ++; - - } else if ( light.isRectAreaLight ) { - - const uniforms = state.rectArea[ rectAreaLength ]; - - uniforms.position.setFromMatrixPosition( light.matrixWorld ); - uniforms.position.applyMatrix4( viewMatrix ); - - // extract local rotation of light to derive width/height half vectors - matrix42.identity(); - matrix4.copy( light.matrixWorld ); - matrix4.premultiply( viewMatrix ); - matrix42.extractRotation( matrix4 ); - - uniforms.halfWidth.set( light.width * 0.5, 0.0, 0.0 ); - uniforms.halfHeight.set( 0.0, light.height * 0.5, 0.0 ); - - uniforms.halfWidth.applyMatrix4( matrix42 ); - uniforms.halfHeight.applyMatrix4( matrix42 ); - - rectAreaLength ++; - - } else if ( light.isPointLight ) { - - const uniforms = state.point[ pointLength ]; - - uniforms.position.setFromMatrixPosition( light.matrixWorld ); - uniforms.position.applyMatrix4( viewMatrix ); - - pointLength ++; - - } else if ( light.isHemisphereLight ) { - - const uniforms = state.hemi[ hemiLength ]; - - uniforms.direction.setFromMatrixPosition( light.matrixWorld ); - uniforms.direction.transformDirection( viewMatrix ); - - hemiLength ++; - - } - - } - - } - - return { - setup: setup, - setupView: setupView, - state: state - }; - - } - - function WebGLRenderState( extensions, capabilities ) { - - const lights = new WebGLLights( extensions, capabilities ); - - const lightsArray = []; - const shadowsArray = []; - - function init() { - - lightsArray.length = 0; - shadowsArray.length = 0; - - } - - function pushLight( light ) { - - lightsArray.push( light ); - - } - - function pushShadow( shadowLight ) { - - shadowsArray.push( shadowLight ); - - } - - function setupLights( useLegacyLights ) { - - lights.setup( lightsArray, useLegacyLights ); - - } - - function setupLightsView( camera ) { - - lights.setupView( lightsArray, camera ); - - } - - const state = { - lightsArray: lightsArray, - shadowsArray: shadowsArray, - - lights: lights - }; - - return { - init: init, - state: state, - setupLights: setupLights, - setupLightsView: setupLightsView, - - pushLight: pushLight, - pushShadow: pushShadow - }; - - } - - function WebGLRenderStates( extensions, capabilities ) { - - let renderStates = new WeakMap(); - - function get( scene, renderCallDepth = 0 ) { - - const renderStateArray = renderStates.get( scene ); - let renderState; - - if ( renderStateArray === undefined ) { - - renderState = new WebGLRenderState( extensions, capabilities ); - renderStates.set( scene, [ renderState ] ); - - } else { - - if ( renderCallDepth >= renderStateArray.length ) { - - renderState = new WebGLRenderState( extensions, capabilities ); - renderStateArray.push( renderState ); - - } else { - - renderState = renderStateArray[ renderCallDepth ]; - - } - - } - - return renderState; - - } - - function dispose() { - - renderStates = new WeakMap(); - - } - - return { - get: get, - dispose: dispose - }; - - } - - class MeshDepthMaterial extends Material { - - constructor( parameters ) { - - super(); - - this.isMeshDepthMaterial = true; - - this.type = 'MeshDepthMaterial'; - - this.depthPacking = BasicDepthPacking; - - this.map = null; - - this.alphaMap = null; - - this.displacementMap = null; - this.displacementScale = 1; - this.displacementBias = 0; - - this.wireframe = false; - this.wireframeLinewidth = 1; - - this.setValues( parameters ); - - } - - copy( source ) { - - super.copy( source ); - - this.depthPacking = source.depthPacking; - - this.map = source.map; - - this.alphaMap = source.alphaMap; - - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; - - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; - - return this; - - } - - } - - class MeshDistanceMaterial extends Material { - - constructor( parameters ) { - - super(); - - this.isMeshDistanceMaterial = true; - - this.type = 'MeshDistanceMaterial'; - - this.map = null; - - this.alphaMap = null; - - this.displacementMap = null; - this.displacementScale = 1; - this.displacementBias = 0; - - this.setValues( parameters ); - - } - - copy( source ) { - - super.copy( source ); - - this.map = source.map; - - this.alphaMap = source.alphaMap; - - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; - - return this; - - } - - } - - const vertex = "void main() {\n\tgl_Position = vec4( position, 1.0 );\n}"; - - const fragment = "uniform sampler2D shadow_pass;\nuniform vec2 resolution;\nuniform float radius;\n#include \nvoid main() {\n\tconst float samples = float( VSM_SAMPLES );\n\tfloat mean = 0.0;\n\tfloat squared_mean = 0.0;\n\tfloat uvStride = samples <= 1.0 ? 0.0 : 2.0 / ( samples - 1.0 );\n\tfloat uvStart = samples <= 1.0 ? 0.0 : - 1.0;\n\tfor ( float i = 0.0; i < samples; i ++ ) {\n\t\tfloat uvOffset = uvStart + i * uvStride;\n\t\t#ifdef HORIZONTAL_PASS\n\t\t\tvec2 distribution = unpackRGBATo2Half( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( uvOffset, 0.0 ) * radius ) / resolution ) );\n\t\t\tmean += distribution.x;\n\t\t\tsquared_mean += distribution.y * distribution.y + distribution.x * distribution.x;\n\t\t#else\n\t\t\tfloat depth = unpackRGBAToDepth( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( 0.0, uvOffset ) * radius ) / resolution ) );\n\t\t\tmean += depth;\n\t\t\tsquared_mean += depth * depth;\n\t\t#endif\n\t}\n\tmean = mean / samples;\n\tsquared_mean = squared_mean / samples;\n\tfloat std_dev = sqrt( squared_mean - mean * mean );\n\tgl_FragColor = pack2HalfToRGBA( vec2( mean, std_dev ) );\n}"; - - function WebGLShadowMap( _renderer, _objects, _capabilities ) { - - let _frustum = new Frustum(); - - const _shadowMapSize = new Vector2(), - _viewportSize = new Vector2(), - - _viewport = new Vector4(), - - _depthMaterial = new MeshDepthMaterial( { depthPacking: RGBADepthPacking } ), - _distanceMaterial = new MeshDistanceMaterial(), - - _materialCache = {}, - - _maxTextureSize = _capabilities.maxTextureSize; - - const shadowSide = { [ FrontSide ]: BackSide, [ BackSide ]: FrontSide, [ DoubleSide ]: DoubleSide }; - - const shadowMaterialVertical = new ShaderMaterial( { - defines: { - VSM_SAMPLES: 8 - }, - uniforms: { - shadow_pass: { value: null }, - resolution: { value: new Vector2() }, - radius: { value: 4.0 } - }, - - vertexShader: vertex, - fragmentShader: fragment - - } ); - - const shadowMaterialHorizontal = shadowMaterialVertical.clone(); - shadowMaterialHorizontal.defines.HORIZONTAL_PASS = 1; - - const fullScreenTri = new BufferGeometry(); - fullScreenTri.setAttribute( - 'position', - new BufferAttribute( - new Float32Array( [ - 1, - 1, 0.5, 3, - 1, 0.5, - 1, 3, 0.5 ] ), - 3 - ) - ); - - const fullScreenMesh = new Mesh( fullScreenTri, shadowMaterialVertical ); - - const scope = this; - - this.enabled = false; - - this.autoUpdate = true; - this.needsUpdate = false; - - this.type = PCFShadowMap; - let _previousType = this.type; - - this.render = function ( lights, scene, camera ) { - - if ( scope.enabled === false ) return; - if ( scope.autoUpdate === false && scope.needsUpdate === false ) return; - - if ( lights.length === 0 ) return; - - const currentRenderTarget = _renderer.getRenderTarget(); - const activeCubeFace = _renderer.getActiveCubeFace(); - const activeMipmapLevel = _renderer.getActiveMipmapLevel(); - - const _state = _renderer.state; - - // Set GL state for depth map. - _state.setBlending( NoBlending ); - _state.buffers.color.setClear( 1, 1, 1, 1 ); - _state.buffers.depth.setTest( true ); - _state.setScissorTest( false ); - - // check for shadow map type changes - - const toVSM = ( _previousType !== VSMShadowMap && this.type === VSMShadowMap ); - const fromVSM = ( _previousType === VSMShadowMap && this.type !== VSMShadowMap ); - - // render depth map - - for ( let i = 0, il = lights.length; i < il; i ++ ) { - - const light = lights[ i ]; - const shadow = light.shadow; - - if ( shadow === undefined ) { - - console.warn( 'THREE.WebGLShadowMap:', light, 'has no shadow.' ); - continue; - - } - - if ( shadow.autoUpdate === false && shadow.needsUpdate === false ) continue; - - _shadowMapSize.copy( shadow.mapSize ); - - const shadowFrameExtents = shadow.getFrameExtents(); - - _shadowMapSize.multiply( shadowFrameExtents ); - - _viewportSize.copy( shadow.mapSize ); - - if ( _shadowMapSize.x > _maxTextureSize || _shadowMapSize.y > _maxTextureSize ) { - - if ( _shadowMapSize.x > _maxTextureSize ) { - - _viewportSize.x = Math.floor( _maxTextureSize / shadowFrameExtents.x ); - _shadowMapSize.x = _viewportSize.x * shadowFrameExtents.x; - shadow.mapSize.x = _viewportSize.x; - - } - - if ( _shadowMapSize.y > _maxTextureSize ) { - - _viewportSize.y = Math.floor( _maxTextureSize / shadowFrameExtents.y ); - _shadowMapSize.y = _viewportSize.y * shadowFrameExtents.y; - shadow.mapSize.y = _viewportSize.y; - - } - - } - - if ( shadow.map === null || toVSM === true || fromVSM === true ) { - - const pars = ( this.type !== VSMShadowMap ) ? { minFilter: NearestFilter, magFilter: NearestFilter } : {}; - - if ( shadow.map !== null ) { - - shadow.map.dispose(); - - } - - shadow.map = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y, pars ); - shadow.map.texture.name = light.name + '.shadowMap'; - - shadow.camera.updateProjectionMatrix(); - - } - - _renderer.setRenderTarget( shadow.map ); - _renderer.clear(); - - const viewportCount = shadow.getViewportCount(); - - for ( let vp = 0; vp < viewportCount; vp ++ ) { - - const viewport = shadow.getViewport( vp ); - - _viewport.set( - _viewportSize.x * viewport.x, - _viewportSize.y * viewport.y, - _viewportSize.x * viewport.z, - _viewportSize.y * viewport.w - ); - - _state.viewport( _viewport ); - - shadow.updateMatrices( light, vp ); - - _frustum = shadow.getFrustum(); - - renderObject( scene, camera, shadow.camera, light, this.type ); - - } - - // do blur pass for VSM - - if ( shadow.isPointLightShadow !== true && this.type === VSMShadowMap ) { - - VSMPass( shadow, camera ); - - } - - shadow.needsUpdate = false; - - } - - _previousType = this.type; - - scope.needsUpdate = false; - - _renderer.setRenderTarget( currentRenderTarget, activeCubeFace, activeMipmapLevel ); - - }; - - function VSMPass( shadow, camera ) { - - const geometry = _objects.update( fullScreenMesh ); - - if ( shadowMaterialVertical.defines.VSM_SAMPLES !== shadow.blurSamples ) { - - shadowMaterialVertical.defines.VSM_SAMPLES = shadow.blurSamples; - shadowMaterialHorizontal.defines.VSM_SAMPLES = shadow.blurSamples; - - shadowMaterialVertical.needsUpdate = true; - shadowMaterialHorizontal.needsUpdate = true; - - } - - if ( shadow.mapPass === null ) { - - shadow.mapPass = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y ); - - } - - // vertical pass - - shadowMaterialVertical.uniforms.shadow_pass.value = shadow.map.texture; - shadowMaterialVertical.uniforms.resolution.value = shadow.mapSize; - shadowMaterialVertical.uniforms.radius.value = shadow.radius; - _renderer.setRenderTarget( shadow.mapPass ); - _renderer.clear(); - _renderer.renderBufferDirect( camera, null, geometry, shadowMaterialVertical, fullScreenMesh, null ); - - // horizontal pass - - shadowMaterialHorizontal.uniforms.shadow_pass.value = shadow.mapPass.texture; - shadowMaterialHorizontal.uniforms.resolution.value = shadow.mapSize; - shadowMaterialHorizontal.uniforms.radius.value = shadow.radius; - _renderer.setRenderTarget( shadow.map ); - _renderer.clear(); - _renderer.renderBufferDirect( camera, null, geometry, shadowMaterialHorizontal, fullScreenMesh, null ); - - } - - function getDepthMaterial( object, material, light, type ) { - - let result = null; - - const customMaterial = ( light.isPointLight === true ) ? object.customDistanceMaterial : object.customDepthMaterial; - - if ( customMaterial !== undefined ) { - - result = customMaterial; - - } else { - - result = ( light.isPointLight === true ) ? _distanceMaterial : _depthMaterial; - - if ( ( _renderer.localClippingEnabled && material.clipShadows === true && Array.isArray( material.clippingPlanes ) && material.clippingPlanes.length !== 0 ) || - ( material.displacementMap && material.displacementScale !== 0 ) || - ( material.alphaMap && material.alphaTest > 0 ) || - ( material.map && material.alphaTest > 0 ) ) { - - // in this case we need a unique material instance reflecting the - // appropriate state - - const keyA = result.uuid, keyB = material.uuid; - - let materialsForVariant = _materialCache[ keyA ]; - - if ( materialsForVariant === undefined ) { - - materialsForVariant = {}; - _materialCache[ keyA ] = materialsForVariant; - - } - - let cachedMaterial = materialsForVariant[ keyB ]; - - if ( cachedMaterial === undefined ) { - - cachedMaterial = result.clone(); - materialsForVariant[ keyB ] = cachedMaterial; - - } - - result = cachedMaterial; - - } - - } - - result.visible = material.visible; - result.wireframe = material.wireframe; - - if ( type === VSMShadowMap ) { - - result.side = ( material.shadowSide !== null ) ? material.shadowSide : material.side; - - } else { - - result.side = ( material.shadowSide !== null ) ? material.shadowSide : shadowSide[ material.side ]; - - } - - result.alphaMap = material.alphaMap; - result.alphaTest = material.alphaTest; - result.map = material.map; - - result.clipShadows = material.clipShadows; - result.clippingPlanes = material.clippingPlanes; - result.clipIntersection = material.clipIntersection; - - result.displacementMap = material.displacementMap; - result.displacementScale = material.displacementScale; - result.displacementBias = material.displacementBias; - - result.wireframeLinewidth = material.wireframeLinewidth; - result.linewidth = material.linewidth; - - if ( light.isPointLight === true && result.isMeshDistanceMaterial === true ) { - - const materialProperties = _renderer.properties.get( result ); - materialProperties.light = light; - - } - - return result; - - } - - function renderObject( object, camera, shadowCamera, light, type ) { - - if ( object.visible === false ) return; - - const visible = object.layers.test( camera.layers ); - - if ( visible && ( object.isMesh || object.isLine || object.isPoints ) ) { - - if ( ( object.castShadow || ( object.receiveShadow && type === VSMShadowMap ) ) && ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) ) { - - object.modelViewMatrix.multiplyMatrices( shadowCamera.matrixWorldInverse, object.matrixWorld ); - - const geometry = _objects.update( object ); - const material = object.material; - - if ( Array.isArray( material ) ) { - - const groups = geometry.groups; - - for ( let k = 0, kl = groups.length; k < kl; k ++ ) { - - const group = groups[ k ]; - const groupMaterial = material[ group.materialIndex ]; - - if ( groupMaterial && groupMaterial.visible ) { - - const depthMaterial = getDepthMaterial( object, groupMaterial, light, type ); - - _renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, group ); - - } - - } - - } else if ( material.visible ) { - - const depthMaterial = getDepthMaterial( object, material, light, type ); - - _renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, null ); - - } - - } - - } - - const children = object.children; - - for ( let i = 0, l = children.length; i < l; i ++ ) { - - renderObject( children[ i ], camera, shadowCamera, light, type ); - - } - - } - - } - - function WebGLState( gl, extensions, capabilities ) { - - const isWebGL2 = capabilities.isWebGL2; - - function ColorBuffer() { - - let locked = false; - - const color = new Vector4(); - let currentColorMask = null; - const currentColorClear = new Vector4( 0, 0, 0, 0 ); - - return { - - setMask: function ( colorMask ) { - - if ( currentColorMask !== colorMask && ! locked ) { - - gl.colorMask( colorMask, colorMask, colorMask, colorMask ); - currentColorMask = colorMask; - - } - - }, - - setLocked: function ( lock ) { - - locked = lock; - - }, - - setClear: function ( r, g, b, a, premultipliedAlpha ) { - - if ( premultipliedAlpha === true ) { - - r *= a; g *= a; b *= a; - - } - - color.set( r, g, b, a ); - - if ( currentColorClear.equals( color ) === false ) { - - gl.clearColor( r, g, b, a ); - currentColorClear.copy( color ); - - } - - }, - - reset: function () { - - locked = false; - - currentColorMask = null; - currentColorClear.set( - 1, 0, 0, 0 ); // set to invalid state - - } - - }; - - } - - function DepthBuffer() { - - let locked = false; - - let currentDepthMask = null; - let currentDepthFunc = null; - let currentDepthClear = null; - - return { - - setTest: function ( depthTest ) { - - if ( depthTest ) { - - enable( gl.DEPTH_TEST ); - - } else { - - disable( gl.DEPTH_TEST ); - - } - - }, - - setMask: function ( depthMask ) { - - if ( currentDepthMask !== depthMask && ! locked ) { - - gl.depthMask( depthMask ); - currentDepthMask = depthMask; - - } - - }, - - setFunc: function ( depthFunc ) { - - if ( currentDepthFunc !== depthFunc ) { - - switch ( depthFunc ) { - - case NeverDepth: - - gl.depthFunc( gl.NEVER ); - break; - - case AlwaysDepth: - - gl.depthFunc( gl.ALWAYS ); - break; - - case LessDepth: - - gl.depthFunc( gl.LESS ); - break; - - case LessEqualDepth: - - gl.depthFunc( gl.LEQUAL ); - break; - - case EqualDepth: - - gl.depthFunc( gl.EQUAL ); - break; - - case GreaterEqualDepth: - - gl.depthFunc( gl.GEQUAL ); - break; - - case GreaterDepth: - - gl.depthFunc( gl.GREATER ); - break; - - case NotEqualDepth: - - gl.depthFunc( gl.NOTEQUAL ); - break; - - default: - - gl.depthFunc( gl.LEQUAL ); - - } - - currentDepthFunc = depthFunc; - - } - - }, - - setLocked: function ( lock ) { - - locked = lock; - - }, - - setClear: function ( depth ) { - - if ( currentDepthClear !== depth ) { - - gl.clearDepth( depth ); - currentDepthClear = depth; - - } - - }, - - reset: function () { - - locked = false; - - currentDepthMask = null; - currentDepthFunc = null; - currentDepthClear = null; - - } - - }; - - } - - function StencilBuffer() { - - let locked = false; - - let currentStencilMask = null; - let currentStencilFunc = null; - let currentStencilRef = null; - let currentStencilFuncMask = null; - let currentStencilFail = null; - let currentStencilZFail = null; - let currentStencilZPass = null; - let currentStencilClear = null; - - return { - - setTest: function ( stencilTest ) { - - if ( ! locked ) { - - if ( stencilTest ) { - - enable( gl.STENCIL_TEST ); - - } else { - - disable( gl.STENCIL_TEST ); - - } - - } - - }, - - setMask: function ( stencilMask ) { - - if ( currentStencilMask !== stencilMask && ! locked ) { - - gl.stencilMask( stencilMask ); - currentStencilMask = stencilMask; - - } - - }, - - setFunc: function ( stencilFunc, stencilRef, stencilMask ) { - - if ( currentStencilFunc !== stencilFunc || - currentStencilRef !== stencilRef || - currentStencilFuncMask !== stencilMask ) { - - gl.stencilFunc( stencilFunc, stencilRef, stencilMask ); - - currentStencilFunc = stencilFunc; - currentStencilRef = stencilRef; - currentStencilFuncMask = stencilMask; - - } - - }, - - setOp: function ( stencilFail, stencilZFail, stencilZPass ) { - - if ( currentStencilFail !== stencilFail || - currentStencilZFail !== stencilZFail || - currentStencilZPass !== stencilZPass ) { - - gl.stencilOp( stencilFail, stencilZFail, stencilZPass ); - - currentStencilFail = stencilFail; - currentStencilZFail = stencilZFail; - currentStencilZPass = stencilZPass; - - } - - }, - - setLocked: function ( lock ) { - - locked = lock; - - }, - - setClear: function ( stencil ) { - - if ( currentStencilClear !== stencil ) { - - gl.clearStencil( stencil ); - currentStencilClear = stencil; - - } - - }, - - reset: function () { - - locked = false; - - currentStencilMask = null; - currentStencilFunc = null; - currentStencilRef = null; - currentStencilFuncMask = null; - currentStencilFail = null; - currentStencilZFail = null; - currentStencilZPass = null; - currentStencilClear = null; - - } - - }; - - } - - // - - const colorBuffer = new ColorBuffer(); - const depthBuffer = new DepthBuffer(); - const stencilBuffer = new StencilBuffer(); - - const uboBindings = new WeakMap(); - const uboProgramMap = new WeakMap(); - - let enabledCapabilities = {}; - - let currentBoundFramebuffers = {}; - let currentDrawbuffers = new WeakMap(); - let defaultDrawbuffers = []; - - let currentProgram = null; - - let currentBlendingEnabled = false; - let currentBlending = null; - let currentBlendEquation = null; - let currentBlendSrc = null; - let currentBlendDst = null; - let currentBlendEquationAlpha = null; - let currentBlendSrcAlpha = null; - let currentBlendDstAlpha = null; - let currentPremultipledAlpha = false; - - let currentFlipSided = null; - let currentCullFace = null; - - let currentLineWidth = null; - - let currentPolygonOffsetFactor = null; - let currentPolygonOffsetUnits = null; - - const maxTextures = gl.getParameter( gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS ); - - let lineWidthAvailable = false; - let version = 0; - const glVersion = gl.getParameter( gl.VERSION ); - - if ( glVersion.indexOf( 'WebGL' ) !== - 1 ) { - - version = parseFloat( /^WebGL (\d)/.exec( glVersion )[ 1 ] ); - lineWidthAvailable = ( version >= 1.0 ); - - } else if ( glVersion.indexOf( 'OpenGL ES' ) !== - 1 ) { - - version = parseFloat( /^OpenGL ES (\d)/.exec( glVersion )[ 1 ] ); - lineWidthAvailable = ( version >= 2.0 ); - - } - - let currentTextureSlot = null; - let currentBoundTextures = {}; - - const scissorParam = gl.getParameter( gl.SCISSOR_BOX ); - const viewportParam = gl.getParameter( gl.VIEWPORT ); - - const currentScissor = new Vector4().fromArray( scissorParam ); - const currentViewport = new Vector4().fromArray( viewportParam ); - - function createTexture( type, target, count, dimensions ) { - - const data = new Uint8Array( 4 ); // 4 is required to match default unpack alignment of 4. - const texture = gl.createTexture(); - - gl.bindTexture( type, texture ); - gl.texParameteri( type, gl.TEXTURE_MIN_FILTER, gl.NEAREST ); - gl.texParameteri( type, gl.TEXTURE_MAG_FILTER, gl.NEAREST ); - - for ( let i = 0; i < count; i ++ ) { - - if ( isWebGL2 && ( type === gl.TEXTURE_3D || type === gl.TEXTURE_2D_ARRAY ) ) { - - gl.texImage3D( target, 0, gl.RGBA, 1, 1, dimensions, 0, gl.RGBA, gl.UNSIGNED_BYTE, data ); - - } else { - - gl.texImage2D( target + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, data ); - - } - - } - - return texture; - - } - - const emptyTextures = {}; - emptyTextures[ gl.TEXTURE_2D ] = createTexture( gl.TEXTURE_2D, gl.TEXTURE_2D, 1 ); - emptyTextures[ gl.TEXTURE_CUBE_MAP ] = createTexture( gl.TEXTURE_CUBE_MAP, gl.TEXTURE_CUBE_MAP_POSITIVE_X, 6 ); - - if ( isWebGL2 ) { - - emptyTextures[ gl.TEXTURE_2D_ARRAY ] = createTexture( gl.TEXTURE_2D_ARRAY, gl.TEXTURE_2D_ARRAY, 1, 1 ); - emptyTextures[ gl.TEXTURE_3D ] = createTexture( gl.TEXTURE_3D, gl.TEXTURE_3D, 1, 1 ); - - } - - // init - - colorBuffer.setClear( 0, 0, 0, 1 ); - depthBuffer.setClear( 1 ); - stencilBuffer.setClear( 0 ); - - enable( gl.DEPTH_TEST ); - depthBuffer.setFunc( LessEqualDepth ); - - setFlipSided( false ); - setCullFace( CullFaceBack ); - enable( gl.CULL_FACE ); - - setBlending( NoBlending ); - - // - - function enable( id ) { - - if ( enabledCapabilities[ id ] !== true ) { - - gl.enable( id ); - enabledCapabilities[ id ] = true; - - } - - } - - function disable( id ) { - - if ( enabledCapabilities[ id ] !== false ) { - - gl.disable( id ); - enabledCapabilities[ id ] = false; - - } - - } - - function bindFramebuffer( target, framebuffer ) { - - if ( currentBoundFramebuffers[ target ] !== framebuffer ) { - - gl.bindFramebuffer( target, framebuffer ); - - currentBoundFramebuffers[ target ] = framebuffer; - - if ( isWebGL2 ) { - - // gl.DRAW_FRAMEBUFFER is equivalent to gl.FRAMEBUFFER - - if ( target === gl.DRAW_FRAMEBUFFER ) { - - currentBoundFramebuffers[ gl.FRAMEBUFFER ] = framebuffer; - - } - - if ( target === gl.FRAMEBUFFER ) { - - currentBoundFramebuffers[ gl.DRAW_FRAMEBUFFER ] = framebuffer; - - } - - } - - return true; - - } - - return false; - - } - - function drawBuffers( renderTarget, framebuffer ) { - - let drawBuffers = defaultDrawbuffers; - - let needsUpdate = false; - - if ( renderTarget ) { - - drawBuffers = currentDrawbuffers.get( framebuffer ); - - if ( drawBuffers === undefined ) { - - drawBuffers = []; - currentDrawbuffers.set( framebuffer, drawBuffers ); - - } - - if ( renderTarget.isWebGLMultipleRenderTargets ) { - - const textures = renderTarget.texture; - - if ( drawBuffers.length !== textures.length || drawBuffers[ 0 ] !== gl.COLOR_ATTACHMENT0 ) { - - for ( let i = 0, il = textures.length; i < il; i ++ ) { - - drawBuffers[ i ] = gl.COLOR_ATTACHMENT0 + i; - - } - - drawBuffers.length = textures.length; - - needsUpdate = true; - - } - - } else { - - if ( drawBuffers[ 0 ] !== gl.COLOR_ATTACHMENT0 ) { - - drawBuffers[ 0 ] = gl.COLOR_ATTACHMENT0; - - needsUpdate = true; - - } - - } - - } else { - - if ( drawBuffers[ 0 ] !== gl.BACK ) { - - drawBuffers[ 0 ] = gl.BACK; - - needsUpdate = true; - - } - - } - - if ( needsUpdate ) { - - if ( capabilities.isWebGL2 ) { - - gl.drawBuffers( drawBuffers ); - - } else { - - extensions.get( 'WEBGL_draw_buffers' ).drawBuffersWEBGL( drawBuffers ); - - } - - } - - - } - - function useProgram( program ) { - - if ( currentProgram !== program ) { - - gl.useProgram( program ); - - currentProgram = program; - - return true; - - } - - return false; - - } - - const equationToGL = { - [ AddEquation ]: gl.FUNC_ADD, - [ SubtractEquation ]: gl.FUNC_SUBTRACT, - [ ReverseSubtractEquation ]: gl.FUNC_REVERSE_SUBTRACT - }; - - if ( isWebGL2 ) { - - equationToGL[ MinEquation ] = gl.MIN; - equationToGL[ MaxEquation ] = gl.MAX; - - } else { - - const extension = extensions.get( 'EXT_blend_minmax' ); - - if ( extension !== null ) { - - equationToGL[ MinEquation ] = extension.MIN_EXT; - equationToGL[ MaxEquation ] = extension.MAX_EXT; - - } - - } - - const factorToGL = { - [ ZeroFactor ]: gl.ZERO, - [ OneFactor ]: gl.ONE, - [ SrcColorFactor ]: gl.SRC_COLOR, - [ SrcAlphaFactor ]: gl.SRC_ALPHA, - [ SrcAlphaSaturateFactor ]: gl.SRC_ALPHA_SATURATE, - [ DstColorFactor ]: gl.DST_COLOR, - [ DstAlphaFactor ]: gl.DST_ALPHA, - [ OneMinusSrcColorFactor ]: gl.ONE_MINUS_SRC_COLOR, - [ OneMinusSrcAlphaFactor ]: gl.ONE_MINUS_SRC_ALPHA, - [ OneMinusDstColorFactor ]: gl.ONE_MINUS_DST_COLOR, - [ OneMinusDstAlphaFactor ]: gl.ONE_MINUS_DST_ALPHA - }; - - function setBlending( blending, blendEquation, blendSrc, blendDst, blendEquationAlpha, blendSrcAlpha, blendDstAlpha, premultipliedAlpha ) { - - if ( blending === NoBlending ) { - - if ( currentBlendingEnabled === true ) { - - disable( gl.BLEND ); - currentBlendingEnabled = false; - - } - - return; - - } - - if ( currentBlendingEnabled === false ) { - - enable( gl.BLEND ); - currentBlendingEnabled = true; - - } - - if ( blending !== CustomBlending ) { - - if ( blending !== currentBlending || premultipliedAlpha !== currentPremultipledAlpha ) { - - if ( currentBlendEquation !== AddEquation || currentBlendEquationAlpha !== AddEquation ) { - - gl.blendEquation( gl.FUNC_ADD ); - - currentBlendEquation = AddEquation; - currentBlendEquationAlpha = AddEquation; - - } - - if ( premultipliedAlpha ) { - - switch ( blending ) { - - case NormalBlending: - gl.blendFuncSeparate( gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA ); - break; - - case AdditiveBlending: - gl.blendFunc( gl.ONE, gl.ONE ); - break; - - case SubtractiveBlending: - gl.blendFuncSeparate( gl.ZERO, gl.ONE_MINUS_SRC_COLOR, gl.ZERO, gl.ONE ); - break; - - case MultiplyBlending: - gl.blendFuncSeparate( gl.ZERO, gl.SRC_COLOR, gl.ZERO, gl.SRC_ALPHA ); - break; - - default: - console.error( 'THREE.WebGLState: Invalid blending: ', blending ); - break; - - } - - } else { - - switch ( blending ) { - - case NormalBlending: - gl.blendFuncSeparate( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA ); - break; - - case AdditiveBlending: - gl.blendFunc( gl.SRC_ALPHA, gl.ONE ); - break; - - case SubtractiveBlending: - gl.blendFuncSeparate( gl.ZERO, gl.ONE_MINUS_SRC_COLOR, gl.ZERO, gl.ONE ); - break; - - case MultiplyBlending: - gl.blendFunc( gl.ZERO, gl.SRC_COLOR ); - break; - - default: - console.error( 'THREE.WebGLState: Invalid blending: ', blending ); - break; - - } - - } - - currentBlendSrc = null; - currentBlendDst = null; - currentBlendSrcAlpha = null; - currentBlendDstAlpha = null; - - currentBlending = blending; - currentPremultipledAlpha = premultipliedAlpha; - - } - - return; - - } - - // custom blending - - blendEquationAlpha = blendEquationAlpha || blendEquation; - blendSrcAlpha = blendSrcAlpha || blendSrc; - blendDstAlpha = blendDstAlpha || blendDst; - - if ( blendEquation !== currentBlendEquation || blendEquationAlpha !== currentBlendEquationAlpha ) { - - gl.blendEquationSeparate( equationToGL[ blendEquation ], equationToGL[ blendEquationAlpha ] ); - - currentBlendEquation = blendEquation; - currentBlendEquationAlpha = blendEquationAlpha; - - } - - if ( blendSrc !== currentBlendSrc || blendDst !== currentBlendDst || blendSrcAlpha !== currentBlendSrcAlpha || blendDstAlpha !== currentBlendDstAlpha ) { - - gl.blendFuncSeparate( factorToGL[ blendSrc ], factorToGL[ blendDst ], factorToGL[ blendSrcAlpha ], factorToGL[ blendDstAlpha ] ); - - currentBlendSrc = blendSrc; - currentBlendDst = blendDst; - currentBlendSrcAlpha = blendSrcAlpha; - currentBlendDstAlpha = blendDstAlpha; - - } - - currentBlending = blending; - currentPremultipledAlpha = false; - - } - - function setMaterial( material, frontFaceCW ) { - - material.side === DoubleSide - ? disable( gl.CULL_FACE ) - : enable( gl.CULL_FACE ); - - let flipSided = ( material.side === BackSide ); - if ( frontFaceCW ) flipSided = ! flipSided; - - setFlipSided( flipSided ); - - ( material.blending === NormalBlending && material.transparent === false ) - ? setBlending( NoBlending ) - : setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst, material.blendEquationAlpha, material.blendSrcAlpha, material.blendDstAlpha, material.premultipliedAlpha ); - - depthBuffer.setFunc( material.depthFunc ); - depthBuffer.setTest( material.depthTest ); - depthBuffer.setMask( material.depthWrite ); - colorBuffer.setMask( material.colorWrite ); - - const stencilWrite = material.stencilWrite; - stencilBuffer.setTest( stencilWrite ); - if ( stencilWrite ) { - - stencilBuffer.setMask( material.stencilWriteMask ); - stencilBuffer.setFunc( material.stencilFunc, material.stencilRef, material.stencilFuncMask ); - stencilBuffer.setOp( material.stencilFail, material.stencilZFail, material.stencilZPass ); - - } - - setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits ); - - material.alphaToCoverage === true - ? enable( gl.SAMPLE_ALPHA_TO_COVERAGE ) - : disable( gl.SAMPLE_ALPHA_TO_COVERAGE ); - - } - - // - - function setFlipSided( flipSided ) { - - if ( currentFlipSided !== flipSided ) { - - if ( flipSided ) { - - gl.frontFace( gl.CW ); - - } else { - - gl.frontFace( gl.CCW ); - - } - - currentFlipSided = flipSided; - - } - - } - - function setCullFace( cullFace ) { - - if ( cullFace !== CullFaceNone ) { - - enable( gl.CULL_FACE ); - - if ( cullFace !== currentCullFace ) { - - if ( cullFace === CullFaceBack ) { - - gl.cullFace( gl.BACK ); - - } else if ( cullFace === CullFaceFront ) { - - gl.cullFace( gl.FRONT ); - - } else { - - gl.cullFace( gl.FRONT_AND_BACK ); - - } - - } - - } else { - - disable( gl.CULL_FACE ); - - } - - currentCullFace = cullFace; - - } - - function setLineWidth( width ) { - - if ( width !== currentLineWidth ) { - - if ( lineWidthAvailable ) gl.lineWidth( width ); - - currentLineWidth = width; - - } - - } - - function setPolygonOffset( polygonOffset, factor, units ) { - - if ( polygonOffset ) { - - enable( gl.POLYGON_OFFSET_FILL ); - - if ( currentPolygonOffsetFactor !== factor || currentPolygonOffsetUnits !== units ) { - - gl.polygonOffset( factor, units ); - - currentPolygonOffsetFactor = factor; - currentPolygonOffsetUnits = units; - - } - - } else { - - disable( gl.POLYGON_OFFSET_FILL ); - - } - - } - - function setScissorTest( scissorTest ) { - - if ( scissorTest ) { - - enable( gl.SCISSOR_TEST ); - - } else { - - disable( gl.SCISSOR_TEST ); - - } - - } - - // texture - - function activeTexture( webglSlot ) { - - if ( webglSlot === undefined ) webglSlot = gl.TEXTURE0 + maxTextures - 1; - - if ( currentTextureSlot !== webglSlot ) { - - gl.activeTexture( webglSlot ); - currentTextureSlot = webglSlot; - - } - - } - - function bindTexture( webglType, webglTexture, webglSlot ) { - - if ( webglSlot === undefined ) { - - if ( currentTextureSlot === null ) { - - webglSlot = gl.TEXTURE0 + maxTextures - 1; - - } else { - - webglSlot = currentTextureSlot; - - } - - } - - let boundTexture = currentBoundTextures[ webglSlot ]; - - if ( boundTexture === undefined ) { - - boundTexture = { type: undefined, texture: undefined }; - currentBoundTextures[ webglSlot ] = boundTexture; - - } - - if ( boundTexture.type !== webglType || boundTexture.texture !== webglTexture ) { - - if ( currentTextureSlot !== webglSlot ) { - - gl.activeTexture( webglSlot ); - currentTextureSlot = webglSlot; - - } - - gl.bindTexture( webglType, webglTexture || emptyTextures[ webglType ] ); - - boundTexture.type = webglType; - boundTexture.texture = webglTexture; - - } - - } - - function unbindTexture() { - - const boundTexture = currentBoundTextures[ currentTextureSlot ]; - - if ( boundTexture !== undefined && boundTexture.type !== undefined ) { - - gl.bindTexture( boundTexture.type, null ); - - boundTexture.type = undefined; - boundTexture.texture = undefined; - - } - - } - - function compressedTexImage2D() { - - try { - - gl.compressedTexImage2D.apply( gl, arguments ); - - } catch ( error ) { - - console.error( 'THREE.WebGLState:', error ); - - } - - } - - function compressedTexImage3D() { - - try { - - gl.compressedTexImage3D.apply( gl, arguments ); - - } catch ( error ) { - - console.error( 'THREE.WebGLState:', error ); - - } - - } - - function texSubImage2D() { - - try { - - gl.texSubImage2D.apply( gl, arguments ); - - } catch ( error ) { - - console.error( 'THREE.WebGLState:', error ); - - } - - } - - function texSubImage3D() { - - try { - - gl.texSubImage3D.apply( gl, arguments ); - - } catch ( error ) { - - console.error( 'THREE.WebGLState:', error ); - - } - - } - - function compressedTexSubImage2D() { - - try { - - gl.compressedTexSubImage2D.apply( gl, arguments ); - - } catch ( error ) { - - console.error( 'THREE.WebGLState:', error ); - - } - - } - - function compressedTexSubImage3D() { - - try { - - gl.compressedTexSubImage3D.apply( gl, arguments ); - - } catch ( error ) { - - console.error( 'THREE.WebGLState:', error ); - - } - - } - - function texStorage2D() { - - try { - - gl.texStorage2D.apply( gl, arguments ); - - } catch ( error ) { - - console.error( 'THREE.WebGLState:', error ); - - } - - } - - function texStorage3D() { - - try { - - gl.texStorage3D.apply( gl, arguments ); - - } catch ( error ) { - - console.error( 'THREE.WebGLState:', error ); - - } - - } - - function texImage2D() { - - try { - - gl.texImage2D.apply( gl, arguments ); - - } catch ( error ) { - - console.error( 'THREE.WebGLState:', error ); - - } - - } - - function texImage3D() { - - try { - - gl.texImage3D.apply( gl, arguments ); - - } catch ( error ) { - - console.error( 'THREE.WebGLState:', error ); - - } - - } - - // - - function scissor( scissor ) { - - if ( currentScissor.equals( scissor ) === false ) { - - gl.scissor( scissor.x, scissor.y, scissor.z, scissor.w ); - currentScissor.copy( scissor ); - - } - - } - - function viewport( viewport ) { - - if ( currentViewport.equals( viewport ) === false ) { - - gl.viewport( viewport.x, viewport.y, viewport.z, viewport.w ); - currentViewport.copy( viewport ); - - } - - } - - function updateUBOMapping( uniformsGroup, program ) { - - let mapping = uboProgramMap.get( program ); - - if ( mapping === undefined ) { - - mapping = new WeakMap(); - - uboProgramMap.set( program, mapping ); - - } - - let blockIndex = mapping.get( uniformsGroup ); - - if ( blockIndex === undefined ) { - - blockIndex = gl.getUniformBlockIndex( program, uniformsGroup.name ); - - mapping.set( uniformsGroup, blockIndex ); - - } - - } - - function uniformBlockBinding( uniformsGroup, program ) { - - const mapping = uboProgramMap.get( program ); - const blockIndex = mapping.get( uniformsGroup ); - - if ( uboBindings.get( program ) !== blockIndex ) { - - // bind shader specific block index to global block point - gl.uniformBlockBinding( program, blockIndex, uniformsGroup.__bindingPointIndex ); - - uboBindings.set( program, blockIndex ); - - } - - } - - // - - function reset() { - - // reset state - - gl.disable( gl.BLEND ); - gl.disable( gl.CULL_FACE ); - gl.disable( gl.DEPTH_TEST ); - gl.disable( gl.POLYGON_OFFSET_FILL ); - gl.disable( gl.SCISSOR_TEST ); - gl.disable( gl.STENCIL_TEST ); - gl.disable( gl.SAMPLE_ALPHA_TO_COVERAGE ); - - gl.blendEquation( gl.FUNC_ADD ); - gl.blendFunc( gl.ONE, gl.ZERO ); - gl.blendFuncSeparate( gl.ONE, gl.ZERO, gl.ONE, gl.ZERO ); - - gl.colorMask( true, true, true, true ); - gl.clearColor( 0, 0, 0, 0 ); - - gl.depthMask( true ); - gl.depthFunc( gl.LESS ); - gl.clearDepth( 1 ); - - gl.stencilMask( 0xffffffff ); - gl.stencilFunc( gl.ALWAYS, 0, 0xffffffff ); - gl.stencilOp( gl.KEEP, gl.KEEP, gl.KEEP ); - gl.clearStencil( 0 ); - - gl.cullFace( gl.BACK ); - gl.frontFace( gl.CCW ); - - gl.polygonOffset( 0, 0 ); - - gl.activeTexture( gl.TEXTURE0 ); - - gl.bindFramebuffer( gl.FRAMEBUFFER, null ); - - if ( isWebGL2 === true ) { - - gl.bindFramebuffer( gl.DRAW_FRAMEBUFFER, null ); - gl.bindFramebuffer( gl.READ_FRAMEBUFFER, null ); - - } - - gl.useProgram( null ); - - gl.lineWidth( 1 ); - - gl.scissor( 0, 0, gl.canvas.width, gl.canvas.height ); - gl.viewport( 0, 0, gl.canvas.width, gl.canvas.height ); - - // reset internals - - enabledCapabilities = {}; - - currentTextureSlot = null; - currentBoundTextures = {}; - - currentBoundFramebuffers = {}; - currentDrawbuffers = new WeakMap(); - defaultDrawbuffers = []; - - currentProgram = null; - - currentBlendingEnabled = false; - currentBlending = null; - currentBlendEquation = null; - currentBlendSrc = null; - currentBlendDst = null; - currentBlendEquationAlpha = null; - currentBlendSrcAlpha = null; - currentBlendDstAlpha = null; - currentPremultipledAlpha = false; - - currentFlipSided = null; - currentCullFace = null; - - currentLineWidth = null; - - currentPolygonOffsetFactor = null; - currentPolygonOffsetUnits = null; - - currentScissor.set( 0, 0, gl.canvas.width, gl.canvas.height ); - currentViewport.set( 0, 0, gl.canvas.width, gl.canvas.height ); - - colorBuffer.reset(); - depthBuffer.reset(); - stencilBuffer.reset(); - - } - - return { - - buffers: { - color: colorBuffer, - depth: depthBuffer, - stencil: stencilBuffer - }, - - enable: enable, - disable: disable, - - bindFramebuffer: bindFramebuffer, - drawBuffers: drawBuffers, - - useProgram: useProgram, - - setBlending: setBlending, - setMaterial: setMaterial, - - setFlipSided: setFlipSided, - setCullFace: setCullFace, - - setLineWidth: setLineWidth, - setPolygonOffset: setPolygonOffset, - - setScissorTest: setScissorTest, - - activeTexture: activeTexture, - bindTexture: bindTexture, - unbindTexture: unbindTexture, - compressedTexImage2D: compressedTexImage2D, - compressedTexImage3D: compressedTexImage3D, - texImage2D: texImage2D, - texImage3D: texImage3D, - - updateUBOMapping: updateUBOMapping, - uniformBlockBinding: uniformBlockBinding, - - texStorage2D: texStorage2D, - texStorage3D: texStorage3D, - texSubImage2D: texSubImage2D, - texSubImage3D: texSubImage3D, - compressedTexSubImage2D: compressedTexSubImage2D, - compressedTexSubImage3D: compressedTexSubImage3D, - - scissor: scissor, - viewport: viewport, - - reset: reset - - }; - - } - - function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, info ) { - - const isWebGL2 = capabilities.isWebGL2; - const maxTextures = capabilities.maxTextures; - const maxCubemapSize = capabilities.maxCubemapSize; - const maxTextureSize = capabilities.maxTextureSize; - const maxSamples = capabilities.maxSamples; - const multisampledRTTExt = extensions.has( 'WEBGL_multisampled_render_to_texture' ) ? extensions.get( 'WEBGL_multisampled_render_to_texture' ) : null; - const supportsInvalidateFramebuffer = typeof navigator === 'undefined' ? false : /OculusBrowser/g.test( navigator.userAgent ); - - const _videoTextures = new WeakMap(); - let _canvas; - - const _sources = new WeakMap(); // maps WebglTexture objects to instances of Source - - // cordova iOS (as of 5.0) still uses UIWebView, which provides OffscreenCanvas, - // also OffscreenCanvas.getContext("webgl"), but not OffscreenCanvas.getContext("2d")! - // Some implementations may only implement OffscreenCanvas partially (e.g. lacking 2d). - - let useOffscreenCanvas = false; - - try { - - useOffscreenCanvas = typeof OffscreenCanvas !== 'undefined' - // eslint-disable-next-line compat/compat - && ( new OffscreenCanvas( 1, 1 ).getContext( '2d' ) ) !== null; - - } catch ( err ) { - - // Ignore any errors - - } - - function createCanvas( width, height ) { - - // Use OffscreenCanvas when available. Specially needed in web workers - - return useOffscreenCanvas ? - // eslint-disable-next-line compat/compat - new OffscreenCanvas( width, height ) : createElementNS( 'canvas' ); - - } - - function resizeImage( image, needsPowerOfTwo, needsNewCanvas, maxSize ) { - - let scale = 1; - - // handle case if texture exceeds max size - - if ( image.width > maxSize || image.height > maxSize ) { - - scale = maxSize / Math.max( image.width, image.height ); - - } - - // only perform resize if necessary - - if ( scale < 1 || needsPowerOfTwo === true ) { - - // only perform resize for certain image types - - if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || - ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) || - ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) { - - const floor = needsPowerOfTwo ? floorPowerOfTwo : Math.floor; - - const width = floor( scale * image.width ); - const height = floor( scale * image.height ); - - if ( _canvas === undefined ) _canvas = createCanvas( width, height ); - - // cube textures can't reuse the same canvas - - const canvas = needsNewCanvas ? createCanvas( width, height ) : _canvas; - - canvas.width = width; - canvas.height = height; - - const context = canvas.getContext( '2d' ); - context.drawImage( image, 0, 0, width, height ); - - console.warn( 'THREE.WebGLRenderer: Texture has been resized from (' + image.width + 'x' + image.height + ') to (' + width + 'x' + height + ').' ); - - return canvas; - - } else { - - if ( 'data' in image ) { - - console.warn( 'THREE.WebGLRenderer: Image in DataTexture is too big (' + image.width + 'x' + image.height + ').' ); - - } - - return image; - - } - - } - - return image; - - } - - function isPowerOfTwo$1( image ) { - - return isPowerOfTwo( image.width ) && isPowerOfTwo( image.height ); - - } - - function textureNeedsPowerOfTwo( texture ) { - - if ( isWebGL2 ) return false; - - return ( texture.wrapS !== ClampToEdgeWrapping || texture.wrapT !== ClampToEdgeWrapping ) || - ( texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter ); - - } - - function textureNeedsGenerateMipmaps( texture, supportsMips ) { - - return texture.generateMipmaps && supportsMips && - texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter; - - } - - function generateMipmap( target ) { - - _gl.generateMipmap( target ); - - } - - function getInternalFormat( internalFormatName, glFormat, glType, colorSpace, forceLinearTransfer = false ) { - - if ( isWebGL2 === false ) return glFormat; - - if ( internalFormatName !== null ) { - - if ( _gl[ internalFormatName ] !== undefined ) return _gl[ internalFormatName ]; - - console.warn( 'THREE.WebGLRenderer: Attempt to use non-existing WebGL internal format \'' + internalFormatName + '\'' ); - - } - - let internalFormat = glFormat; - - if ( glFormat === _gl.RED ) { - - if ( glType === _gl.FLOAT ) internalFormat = _gl.R32F; - if ( glType === _gl.HALF_FLOAT ) internalFormat = _gl.R16F; - if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = _gl.R8; - - } - - if ( glFormat === _gl.RG ) { - - if ( glType === _gl.FLOAT ) internalFormat = _gl.RG32F; - if ( glType === _gl.HALF_FLOAT ) internalFormat = _gl.RG16F; - if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = _gl.RG8; - - } - - if ( glFormat === _gl.RGBA ) { - - if ( glType === _gl.FLOAT ) internalFormat = _gl.RGBA32F; - if ( glType === _gl.HALF_FLOAT ) internalFormat = _gl.RGBA16F; - if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = ( colorSpace === SRGBColorSpace && forceLinearTransfer === false ) ? _gl.SRGB8_ALPHA8 : _gl.RGBA8; - if ( glType === _gl.UNSIGNED_SHORT_4_4_4_4 ) internalFormat = _gl.RGBA4; - if ( glType === _gl.UNSIGNED_SHORT_5_5_5_1 ) internalFormat = _gl.RGB5_A1; - - } - - if ( internalFormat === _gl.R16F || internalFormat === _gl.R32F || - internalFormat === _gl.RG16F || internalFormat === _gl.RG32F || - internalFormat === _gl.RGBA16F || internalFormat === _gl.RGBA32F ) { - - extensions.get( 'EXT_color_buffer_float' ); - - } - - return internalFormat; - - } - - function getMipLevels( texture, image, supportsMips ) { - - if ( textureNeedsGenerateMipmaps( texture, supportsMips ) === true || ( texture.isFramebufferTexture && texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter ) ) { - - return Math.log2( Math.max( image.width, image.height ) ) + 1; - - } else if ( texture.mipmaps !== undefined && texture.mipmaps.length > 0 ) { - - // user-defined mipmaps - - return texture.mipmaps.length; - - } else if ( texture.isCompressedTexture && Array.isArray( texture.image ) ) { - - return image.mipmaps.length; - - } else { - - // texture without mipmaps (only base level) - - return 1; - - } - - } - - // Fallback filters for non-power-of-2 textures - - function filterFallback( f ) { - - if ( f === NearestFilter || f === NearestMipmapNearestFilter || f === NearestMipmapLinearFilter ) { - - return _gl.NEAREST; - - } - - return _gl.LINEAR; - - } - - // - - function onTextureDispose( event ) { - - const texture = event.target; - - texture.removeEventListener( 'dispose', onTextureDispose ); - - deallocateTexture( texture ); - - if ( texture.isVideoTexture ) { - - _videoTextures.delete( texture ); - - } - - } - - function onRenderTargetDispose( event ) { - - const renderTarget = event.target; - - renderTarget.removeEventListener( 'dispose', onRenderTargetDispose ); - - deallocateRenderTarget( renderTarget ); - - } - - // - - function deallocateTexture( texture ) { - - const textureProperties = properties.get( texture ); - - if ( textureProperties.__webglInit === undefined ) return; - - // check if it's necessary to remove the WebGLTexture object - - const source = texture.source; - const webglTextures = _sources.get( source ); - - if ( webglTextures ) { - - const webglTexture = webglTextures[ textureProperties.__cacheKey ]; - webglTexture.usedTimes --; - - // the WebGLTexture object is not used anymore, remove it - - if ( webglTexture.usedTimes === 0 ) { - - deleteTexture( texture ); - - } - - // remove the weak map entry if no WebGLTexture uses the source anymore - - if ( Object.keys( webglTextures ).length === 0 ) { - - _sources.delete( source ); - - } - - } - - properties.remove( texture ); - - } - - function deleteTexture( texture ) { - - const textureProperties = properties.get( texture ); - _gl.deleteTexture( textureProperties.__webglTexture ); - - const source = texture.source; - const webglTextures = _sources.get( source ); - delete webglTextures[ textureProperties.__cacheKey ]; - - info.memory.textures --; - - } - - function deallocateRenderTarget( renderTarget ) { - - const texture = renderTarget.texture; - - const renderTargetProperties = properties.get( renderTarget ); - const textureProperties = properties.get( texture ); - - if ( textureProperties.__webglTexture !== undefined ) { - - _gl.deleteTexture( textureProperties.__webglTexture ); - - info.memory.textures --; - - } - - if ( renderTarget.depthTexture ) { - - renderTarget.depthTexture.dispose(); - - } - - if ( renderTarget.isWebGLCubeRenderTarget ) { - - for ( let i = 0; i < 6; i ++ ) { - - _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer[ i ] ); - if ( renderTargetProperties.__webglDepthbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthbuffer[ i ] ); - - } - - } else { - - _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer ); - if ( renderTargetProperties.__webglDepthbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthbuffer ); - if ( renderTargetProperties.__webglMultisampledFramebuffer ) _gl.deleteFramebuffer( renderTargetProperties.__webglMultisampledFramebuffer ); - - if ( renderTargetProperties.__webglColorRenderbuffer ) { - - for ( let i = 0; i < renderTargetProperties.__webglColorRenderbuffer.length; i ++ ) { - - if ( renderTargetProperties.__webglColorRenderbuffer[ i ] ) _gl.deleteRenderbuffer( renderTargetProperties.__webglColorRenderbuffer[ i ] ); - - } - - } - - if ( renderTargetProperties.__webglDepthRenderbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthRenderbuffer ); - - } - - if ( renderTarget.isWebGLMultipleRenderTargets ) { - - for ( let i = 0, il = texture.length; i < il; i ++ ) { - - const attachmentProperties = properties.get( texture[ i ] ); - - if ( attachmentProperties.__webglTexture ) { - - _gl.deleteTexture( attachmentProperties.__webglTexture ); - - info.memory.textures --; - - } - - properties.remove( texture[ i ] ); - - } - - } - - properties.remove( texture ); - properties.remove( renderTarget ); - - } - - // - - let textureUnits = 0; - - function resetTextureUnits() { - - textureUnits = 0; - - } - - function allocateTextureUnit() { - - const textureUnit = textureUnits; - - if ( textureUnit >= maxTextures ) { - - console.warn( 'THREE.WebGLTextures: Trying to use ' + textureUnit + ' texture units while this GPU supports only ' + maxTextures ); - - } - - textureUnits += 1; - - return textureUnit; - - } - - function getTextureCacheKey( texture ) { - - const array = []; - - array.push( texture.wrapS ); - array.push( texture.wrapT ); - array.push( texture.wrapR || 0 ); - array.push( texture.magFilter ); - array.push( texture.minFilter ); - array.push( texture.anisotropy ); - array.push( texture.internalFormat ); - array.push( texture.format ); - array.push( texture.type ); - array.push( texture.generateMipmaps ); - array.push( texture.premultiplyAlpha ); - array.push( texture.flipY ); - array.push( texture.unpackAlignment ); - array.push( texture.colorSpace ); - - return array.join(); - - } - - // - - function setTexture2D( texture, slot ) { - - const textureProperties = properties.get( texture ); - - if ( texture.isVideoTexture ) updateVideoTexture( texture ); - - if ( texture.isRenderTargetTexture === false && texture.version > 0 && textureProperties.__version !== texture.version ) { - - const image = texture.image; - - if ( image === null ) { - - console.warn( 'THREE.WebGLRenderer: Texture marked for update but no image data found.' ); - - } else if ( image.complete === false ) { - - console.warn( 'THREE.WebGLRenderer: Texture marked for update but image is incomplete' ); - - } else { - - uploadTexture( textureProperties, texture, slot ); - return; - - } - - } - - state.bindTexture( _gl.TEXTURE_2D, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); - - } - - function setTexture2DArray( texture, slot ) { - - const textureProperties = properties.get( texture ); - - if ( texture.version > 0 && textureProperties.__version !== texture.version ) { - - uploadTexture( textureProperties, texture, slot ); - return; - - } - - state.bindTexture( _gl.TEXTURE_2D_ARRAY, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); - - } - - function setTexture3D( texture, slot ) { - - const textureProperties = properties.get( texture ); - - if ( texture.version > 0 && textureProperties.__version !== texture.version ) { - - uploadTexture( textureProperties, texture, slot ); - return; - - } - - state.bindTexture( _gl.TEXTURE_3D, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); - - } - - function setTextureCube( texture, slot ) { - - const textureProperties = properties.get( texture ); - - if ( texture.version > 0 && textureProperties.__version !== texture.version ) { - - uploadCubeTexture( textureProperties, texture, slot ); - return; - - } - - state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); - - } - - const wrappingToGL = { - [ RepeatWrapping ]: _gl.REPEAT, - [ ClampToEdgeWrapping ]: _gl.CLAMP_TO_EDGE, - [ MirroredRepeatWrapping ]: _gl.MIRRORED_REPEAT - }; - - const filterToGL = { - [ NearestFilter ]: _gl.NEAREST, - [ NearestMipmapNearestFilter ]: _gl.NEAREST_MIPMAP_NEAREST, - [ NearestMipmapLinearFilter ]: _gl.NEAREST_MIPMAP_LINEAR, - - [ LinearFilter ]: _gl.LINEAR, - [ LinearMipmapNearestFilter ]: _gl.LINEAR_MIPMAP_NEAREST, - [ LinearMipmapLinearFilter ]: _gl.LINEAR_MIPMAP_LINEAR - }; - - const compareToGL = { - [ NeverCompare ]: _gl.NEVER, - [ AlwaysCompare ]: _gl.ALWAYS, - [ LessCompare ]: _gl.LESS, - [ LessEqualCompare ]: _gl.LEQUAL, - [ EqualCompare ]: _gl.EQUAL, - [ GreaterEqualCompare ]: _gl.GEQUAL, - [ GreaterCompare ]: _gl.GREATER, - [ NotEqualCompare ]: _gl.NOTEQUAL - }; - - function setTextureParameters( textureType, texture, supportsMips ) { - - if ( supportsMips ) { - - _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_S, wrappingToGL[ texture.wrapS ] ); - _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_T, wrappingToGL[ texture.wrapT ] ); - - if ( textureType === _gl.TEXTURE_3D || textureType === _gl.TEXTURE_2D_ARRAY ) { - - _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_R, wrappingToGL[ texture.wrapR ] ); - - } - - _gl.texParameteri( textureType, _gl.TEXTURE_MAG_FILTER, filterToGL[ texture.magFilter ] ); - _gl.texParameteri( textureType, _gl.TEXTURE_MIN_FILTER, filterToGL[ texture.minFilter ] ); - - } else { - - _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_S, _gl.CLAMP_TO_EDGE ); - _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_T, _gl.CLAMP_TO_EDGE ); - - if ( textureType === _gl.TEXTURE_3D || textureType === _gl.TEXTURE_2D_ARRAY ) { - - _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_R, _gl.CLAMP_TO_EDGE ); - - } - - if ( texture.wrapS !== ClampToEdgeWrapping || texture.wrapT !== ClampToEdgeWrapping ) { - - console.warn( 'THREE.WebGLRenderer: Texture is not power of two. Texture.wrapS and Texture.wrapT should be set to THREE.ClampToEdgeWrapping.' ); - - } - - _gl.texParameteri( textureType, _gl.TEXTURE_MAG_FILTER, filterFallback( texture.magFilter ) ); - _gl.texParameteri( textureType, _gl.TEXTURE_MIN_FILTER, filterFallback( texture.minFilter ) ); - - if ( texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter ) { - - console.warn( 'THREE.WebGLRenderer: Texture is not power of two. Texture.minFilter should be set to THREE.NearestFilter or THREE.LinearFilter.' ); - - } - - } - - if ( texture.compareFunction ) { - - _gl.texParameteri( textureType, _gl.TEXTURE_COMPARE_MODE, _gl.COMPARE_REF_TO_TEXTURE ); - _gl.texParameteri( textureType, _gl.TEXTURE_COMPARE_FUNC, compareToGL[ texture.compareFunction ] ); - - } - - if ( extensions.has( 'EXT_texture_filter_anisotropic' ) === true ) { - - const extension = extensions.get( 'EXT_texture_filter_anisotropic' ); - - if ( texture.magFilter === NearestFilter ) return; - if ( texture.minFilter !== NearestMipmapLinearFilter && texture.minFilter !== LinearMipmapLinearFilter ) return; - if ( texture.type === FloatType && extensions.has( 'OES_texture_float_linear' ) === false ) return; // verify extension for WebGL 1 and WebGL 2 - if ( isWebGL2 === false && ( texture.type === HalfFloatType && extensions.has( 'OES_texture_half_float_linear' ) === false ) ) return; // verify extension for WebGL 1 only - - if ( texture.anisotropy > 1 || properties.get( texture ).__currentAnisotropy ) { - - _gl.texParameterf( textureType, extension.TEXTURE_MAX_ANISOTROPY_EXT, Math.min( texture.anisotropy, capabilities.getMaxAnisotropy() ) ); - properties.get( texture ).__currentAnisotropy = texture.anisotropy; - - } - - } - - } - - function initTexture( textureProperties, texture ) { - - let forceUpload = false; - - if ( textureProperties.__webglInit === undefined ) { - - textureProperties.__webglInit = true; - - texture.addEventListener( 'dispose', onTextureDispose ); - - } - - // create Source <-> WebGLTextures mapping if necessary - - const source = texture.source; - let webglTextures = _sources.get( source ); - - if ( webglTextures === undefined ) { - - webglTextures = {}; - _sources.set( source, webglTextures ); - - } - - // check if there is already a WebGLTexture object for the given texture parameters - - const textureCacheKey = getTextureCacheKey( texture ); - - if ( textureCacheKey !== textureProperties.__cacheKey ) { - - // if not, create a new instance of WebGLTexture - - if ( webglTextures[ textureCacheKey ] === undefined ) { - - // create new entry - - webglTextures[ textureCacheKey ] = { - texture: _gl.createTexture(), - usedTimes: 0 - }; - - info.memory.textures ++; - - // when a new instance of WebGLTexture was created, a texture upload is required - // even if the image contents are identical - - forceUpload = true; - - } - - webglTextures[ textureCacheKey ].usedTimes ++; - - // every time the texture cache key changes, it's necessary to check if an instance of - // WebGLTexture can be deleted in order to avoid a memory leak. - - const webglTexture = webglTextures[ textureProperties.__cacheKey ]; - - if ( webglTexture !== undefined ) { - - webglTextures[ textureProperties.__cacheKey ].usedTimes --; - - if ( webglTexture.usedTimes === 0 ) { - - deleteTexture( texture ); - - } - - } - - // store references to cache key and WebGLTexture object - - textureProperties.__cacheKey = textureCacheKey; - textureProperties.__webglTexture = webglTextures[ textureCacheKey ].texture; - - } - - return forceUpload; - - } - - function uploadTexture( textureProperties, texture, slot ) { - - let textureType = _gl.TEXTURE_2D; - - if ( texture.isDataArrayTexture || texture.isCompressedArrayTexture ) textureType = _gl.TEXTURE_2D_ARRAY; - if ( texture.isData3DTexture ) textureType = _gl.TEXTURE_3D; - - const forceUpload = initTexture( textureProperties, texture ); - const source = texture.source; - - state.bindTexture( textureType, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); - - const sourceProperties = properties.get( source ); - - if ( source.version !== sourceProperties.__version || forceUpload === true ) { - - state.activeTexture( _gl.TEXTURE0 + slot ); - - _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY ); - _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha ); - _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, texture.unpackAlignment ); - _gl.pixelStorei( _gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, _gl.NONE ); - - const needsPowerOfTwo = textureNeedsPowerOfTwo( texture ) && isPowerOfTwo$1( texture.image ) === false; - let image = resizeImage( texture.image, needsPowerOfTwo, false, maxTextureSize ); - image = verifyColorSpace( texture, image ); - - const supportsMips = isPowerOfTwo$1( image ) || isWebGL2, - glFormat = utils.convert( texture.format, texture.colorSpace ); - - let glType = utils.convert( texture.type ), - glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace ); - - setTextureParameters( textureType, texture, supportsMips ); - - let mipmap; - const mipmaps = texture.mipmaps; - - const useTexStorage = ( isWebGL2 && texture.isVideoTexture !== true ); - const allocateMemory = ( sourceProperties.__version === undefined ) || ( forceUpload === true ); - const levels = getMipLevels( texture, image, supportsMips ); - - if ( texture.isDepthTexture ) { - - // populate depth texture with dummy data - - glInternalFormat = _gl.DEPTH_COMPONENT; - - if ( isWebGL2 ) { - - if ( texture.type === FloatType ) { - - glInternalFormat = _gl.DEPTH_COMPONENT32F; - - } else if ( texture.type === UnsignedIntType ) { - - glInternalFormat = _gl.DEPTH_COMPONENT24; - - } else if ( texture.type === UnsignedInt248Type ) { - - glInternalFormat = _gl.DEPTH24_STENCIL8; - - } else { - - glInternalFormat = _gl.DEPTH_COMPONENT16; // WebGL2 requires sized internalformat for glTexImage2D - - } - - } else { - - if ( texture.type === FloatType ) { - - console.error( 'WebGLRenderer: Floating point depth texture requires WebGL2.' ); - - } - - } - - // validation checks for WebGL 1 - - if ( texture.format === DepthFormat && glInternalFormat === _gl.DEPTH_COMPONENT ) { - - // The error INVALID_OPERATION is generated by texImage2D if format and internalformat are - // DEPTH_COMPONENT and type is not UNSIGNED_SHORT or UNSIGNED_INT - // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/) - if ( texture.type !== UnsignedShortType && texture.type !== UnsignedIntType ) { - - console.warn( 'THREE.WebGLRenderer: Use UnsignedShortType or UnsignedIntType for DepthFormat DepthTexture.' ); - - texture.type = UnsignedIntType; - glType = utils.convert( texture.type ); - - } - - } - - if ( texture.format === DepthStencilFormat && glInternalFormat === _gl.DEPTH_COMPONENT ) { - - // Depth stencil textures need the DEPTH_STENCIL internal format - // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/) - glInternalFormat = _gl.DEPTH_STENCIL; - - // The error INVALID_OPERATION is generated by texImage2D if format and internalformat are - // DEPTH_STENCIL and type is not UNSIGNED_INT_24_8_WEBGL. - // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/) - if ( texture.type !== UnsignedInt248Type ) { - - console.warn( 'THREE.WebGLRenderer: Use UnsignedInt248Type for DepthStencilFormat DepthTexture.' ); - - texture.type = UnsignedInt248Type; - glType = utils.convert( texture.type ); - - } - - } - - // - - if ( allocateMemory ) { - - if ( useTexStorage ) { - - state.texStorage2D( _gl.TEXTURE_2D, 1, glInternalFormat, image.width, image.height ); - - } else { - - state.texImage2D( _gl.TEXTURE_2D, 0, glInternalFormat, image.width, image.height, 0, glFormat, glType, null ); - - } - - } - - } else if ( texture.isDataTexture ) { - - // use manually created mipmaps if available - // if there are no manual mipmaps - // set 0 level mipmap and then use GL to generate other mipmap levels - - if ( mipmaps.length > 0 && supportsMips ) { - - if ( useTexStorage && allocateMemory ) { - - state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, mipmaps[ 0 ].width, mipmaps[ 0 ].height ); - - } - - for ( let i = 0, il = mipmaps.length; i < il; i ++ ) { - - mipmap = mipmaps[ i ]; - - if ( useTexStorage ) { - - state.texSubImage2D( _gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, glType, mipmap.data ); - - } else { - - state.texImage2D( _gl.TEXTURE_2D, i, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); - - } - - } - - texture.generateMipmaps = false; - - } else { - - if ( useTexStorage ) { - - if ( allocateMemory ) { - - state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, image.width, image.height ); - - } - - state.texSubImage2D( _gl.TEXTURE_2D, 0, 0, 0, image.width, image.height, glFormat, glType, image.data ); - - } else { - - state.texImage2D( _gl.TEXTURE_2D, 0, glInternalFormat, image.width, image.height, 0, glFormat, glType, image.data ); - - } - - } - - } else if ( texture.isCompressedTexture ) { - - if ( texture.isCompressedArrayTexture ) { - - if ( useTexStorage && allocateMemory ) { - - state.texStorage3D( _gl.TEXTURE_2D_ARRAY, levels, glInternalFormat, mipmaps[ 0 ].width, mipmaps[ 0 ].height, image.depth ); - - } - - for ( let i = 0, il = mipmaps.length; i < il; i ++ ) { - - mipmap = mipmaps[ i ]; - - if ( texture.format !== RGBAFormat ) { - - if ( glFormat !== null ) { - - if ( useTexStorage ) { - - state.compressedTexSubImage3D( _gl.TEXTURE_2D_ARRAY, i, 0, 0, 0, mipmap.width, mipmap.height, image.depth, glFormat, mipmap.data, 0, 0 ); - - } else { - - state.compressedTexImage3D( _gl.TEXTURE_2D_ARRAY, i, glInternalFormat, mipmap.width, mipmap.height, image.depth, 0, mipmap.data, 0, 0 ); - - } - - } else { - - console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()' ); - - } - - } else { - - if ( useTexStorage ) { - - state.texSubImage3D( _gl.TEXTURE_2D_ARRAY, i, 0, 0, 0, mipmap.width, mipmap.height, image.depth, glFormat, glType, mipmap.data ); - - } else { - - state.texImage3D( _gl.TEXTURE_2D_ARRAY, i, glInternalFormat, mipmap.width, mipmap.height, image.depth, 0, glFormat, glType, mipmap.data ); - - } - - } - - } - - } else { - - if ( useTexStorage && allocateMemory ) { - - state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, mipmaps[ 0 ].width, mipmaps[ 0 ].height ); - - } - - for ( let i = 0, il = mipmaps.length; i < il; i ++ ) { - - mipmap = mipmaps[ i ]; - - if ( texture.format !== RGBAFormat ) { - - if ( glFormat !== null ) { - - if ( useTexStorage ) { - - state.compressedTexSubImage2D( _gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, mipmap.data ); - - } else { - - state.compressedTexImage2D( _gl.TEXTURE_2D, i, glInternalFormat, mipmap.width, mipmap.height, 0, mipmap.data ); - - } - - } else { - - console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()' ); - - } - - } else { - - if ( useTexStorage ) { - - state.texSubImage2D( _gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, glType, mipmap.data ); - - } else { - - state.texImage2D( _gl.TEXTURE_2D, i, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); - - } - - } - - } - - } - - } else if ( texture.isDataArrayTexture ) { - - if ( useTexStorage ) { - - if ( allocateMemory ) { - - state.texStorage3D( _gl.TEXTURE_2D_ARRAY, levels, glInternalFormat, image.width, image.height, image.depth ); - - } - - state.texSubImage3D( _gl.TEXTURE_2D_ARRAY, 0, 0, 0, 0, image.width, image.height, image.depth, glFormat, glType, image.data ); - - } else { - - state.texImage3D( _gl.TEXTURE_2D_ARRAY, 0, glInternalFormat, image.width, image.height, image.depth, 0, glFormat, glType, image.data ); - - } - - } else if ( texture.isData3DTexture ) { - - if ( useTexStorage ) { - - if ( allocateMemory ) { - - state.texStorage3D( _gl.TEXTURE_3D, levels, glInternalFormat, image.width, image.height, image.depth ); - - } - - state.texSubImage3D( _gl.TEXTURE_3D, 0, 0, 0, 0, image.width, image.height, image.depth, glFormat, glType, image.data ); - - } else { - - state.texImage3D( _gl.TEXTURE_3D, 0, glInternalFormat, image.width, image.height, image.depth, 0, glFormat, glType, image.data ); - - } - - } else if ( texture.isFramebufferTexture ) { - - if ( allocateMemory ) { - - if ( useTexStorage ) { - - state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, image.width, image.height ); - - } else { - - let width = image.width, height = image.height; - - for ( let i = 0; i < levels; i ++ ) { - - state.texImage2D( _gl.TEXTURE_2D, i, glInternalFormat, width, height, 0, glFormat, glType, null ); - - width >>= 1; - height >>= 1; - - } - - } - - } - - } else { - - // regular Texture (image, video, canvas) - - // use manually created mipmaps if available - // if there are no manual mipmaps - // set 0 level mipmap and then use GL to generate other mipmap levels - - if ( mipmaps.length > 0 && supportsMips ) { - - if ( useTexStorage && allocateMemory ) { - - state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, mipmaps[ 0 ].width, mipmaps[ 0 ].height ); - - } - - for ( let i = 0, il = mipmaps.length; i < il; i ++ ) { - - mipmap = mipmaps[ i ]; - - if ( useTexStorage ) { - - state.texSubImage2D( _gl.TEXTURE_2D, i, 0, 0, glFormat, glType, mipmap ); - - } else { - - state.texImage2D( _gl.TEXTURE_2D, i, glInternalFormat, glFormat, glType, mipmap ); - - } - - } - - texture.generateMipmaps = false; - - } else { - - if ( useTexStorage ) { - - if ( allocateMemory ) { - - state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, image.width, image.height ); - - } - - state.texSubImage2D( _gl.TEXTURE_2D, 0, 0, 0, glFormat, glType, image ); - - } else { - - state.texImage2D( _gl.TEXTURE_2D, 0, glInternalFormat, glFormat, glType, image ); - - } - - } - - } - - if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) { - - generateMipmap( textureType ); - - } - - sourceProperties.__version = source.version; - - if ( texture.onUpdate ) texture.onUpdate( texture ); - - } - - textureProperties.__version = texture.version; - - } - - function uploadCubeTexture( textureProperties, texture, slot ) { - - if ( texture.image.length !== 6 ) return; - - const forceUpload = initTexture( textureProperties, texture ); - const source = texture.source; - - state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); - - const sourceProperties = properties.get( source ); - - if ( source.version !== sourceProperties.__version || forceUpload === true ) { - - state.activeTexture( _gl.TEXTURE0 + slot ); - - _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY ); - _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha ); - _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, texture.unpackAlignment ); - _gl.pixelStorei( _gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, _gl.NONE ); - - const isCompressed = ( texture.isCompressedTexture || texture.image[ 0 ].isCompressedTexture ); - const isDataTexture = ( texture.image[ 0 ] && texture.image[ 0 ].isDataTexture ); - - const cubeImage = []; - - for ( let i = 0; i < 6; i ++ ) { - - if ( ! isCompressed && ! isDataTexture ) { - - cubeImage[ i ] = resizeImage( texture.image[ i ], false, true, maxCubemapSize ); - - } else { - - cubeImage[ i ] = isDataTexture ? texture.image[ i ].image : texture.image[ i ]; - - } - - cubeImage[ i ] = verifyColorSpace( texture, cubeImage[ i ] ); - - } - - const image = cubeImage[ 0 ], - supportsMips = isPowerOfTwo$1( image ) || isWebGL2, - glFormat = utils.convert( texture.format, texture.colorSpace ), - glType = utils.convert( texture.type ), - glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace ); - - const useTexStorage = ( isWebGL2 && texture.isVideoTexture !== true ); - const allocateMemory = ( sourceProperties.__version === undefined ) || ( forceUpload === true ); - let levels = getMipLevels( texture, image, supportsMips ); - - setTextureParameters( _gl.TEXTURE_CUBE_MAP, texture, supportsMips ); - - let mipmaps; - - if ( isCompressed ) { - - if ( useTexStorage && allocateMemory ) { - - state.texStorage2D( _gl.TEXTURE_CUBE_MAP, levels, glInternalFormat, image.width, image.height ); - - } - - for ( let i = 0; i < 6; i ++ ) { - - mipmaps = cubeImage[ i ].mipmaps; - - for ( let j = 0; j < mipmaps.length; j ++ ) { - - const mipmap = mipmaps[ j ]; - - if ( texture.format !== RGBAFormat ) { - - if ( glFormat !== null ) { - - if ( useTexStorage ) { - - state.compressedTexSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, 0, 0, mipmap.width, mipmap.height, glFormat, mipmap.data ); - - } else { - - state.compressedTexImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glInternalFormat, mipmap.width, mipmap.height, 0, mipmap.data ); - - } - - } else { - - console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .setTextureCube()' ); - - } - - } else { - - if ( useTexStorage ) { - - state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, 0, 0, mipmap.width, mipmap.height, glFormat, glType, mipmap.data ); - - } else { - - state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); - - } - - } - - } - - } - - } else { - - mipmaps = texture.mipmaps; - - if ( useTexStorage && allocateMemory ) { - - // TODO: Uniformly handle mipmap definitions - // Normal textures and compressed cube textures define base level + mips with their mipmap array - // Uncompressed cube textures use their mipmap array only for mips (no base level) - - if ( mipmaps.length > 0 ) levels ++; - - state.texStorage2D( _gl.TEXTURE_CUBE_MAP, levels, glInternalFormat, cubeImage[ 0 ].width, cubeImage[ 0 ].height ); - - } - - for ( let i = 0; i < 6; i ++ ) { - - if ( isDataTexture ) { - - if ( useTexStorage ) { - - state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, 0, 0, cubeImage[ i ].width, cubeImage[ i ].height, glFormat, glType, cubeImage[ i ].data ); - - } else { - - state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, cubeImage[ i ].width, cubeImage[ i ].height, 0, glFormat, glType, cubeImage[ i ].data ); - - } - - for ( let j = 0; j < mipmaps.length; j ++ ) { - - const mipmap = mipmaps[ j ]; - const mipmapImage = mipmap.image[ i ].image; - - if ( useTexStorage ) { - - state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, 0, 0, mipmapImage.width, mipmapImage.height, glFormat, glType, mipmapImage.data ); - - } else { - - state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, glInternalFormat, mipmapImage.width, mipmapImage.height, 0, glFormat, glType, mipmapImage.data ); - - } - - } - - } else { - - if ( useTexStorage ) { - - state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, 0, 0, glFormat, glType, cubeImage[ i ] ); - - } else { - - state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, glFormat, glType, cubeImage[ i ] ); - - } - - for ( let j = 0; j < mipmaps.length; j ++ ) { - - const mipmap = mipmaps[ j ]; - - if ( useTexStorage ) { - - state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, 0, 0, glFormat, glType, mipmap.image[ i ] ); - - } else { - - state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, glInternalFormat, glFormat, glType, mipmap.image[ i ] ); - - } - - } - - } - - } - - } - - if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) { - - // We assume images for cube map have the same size. - generateMipmap( _gl.TEXTURE_CUBE_MAP ); - - } - - sourceProperties.__version = source.version; - - if ( texture.onUpdate ) texture.onUpdate( texture ); - - } - - textureProperties.__version = texture.version; - - } - - // Render targets - - // Setup storage for target texture and bind it to correct framebuffer - function setupFrameBufferTexture( framebuffer, renderTarget, texture, attachment, textureTarget ) { - - const glFormat = utils.convert( texture.format, texture.colorSpace ); - const glType = utils.convert( texture.type ); - const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace ); - const renderTargetProperties = properties.get( renderTarget ); - - if ( ! renderTargetProperties.__hasExternalTextures ) { - - if ( textureTarget === _gl.TEXTURE_3D || textureTarget === _gl.TEXTURE_2D_ARRAY ) { - - state.texImage3D( textureTarget, 0, glInternalFormat, renderTarget.width, renderTarget.height, renderTarget.depth, 0, glFormat, glType, null ); - - } else { - - state.texImage2D( textureTarget, 0, glInternalFormat, renderTarget.width, renderTarget.height, 0, glFormat, glType, null ); - - } - - } - - state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); - - if ( useMultisampledRTT( renderTarget ) ) { - - multisampledRTTExt.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, attachment, textureTarget, properties.get( texture ).__webglTexture, 0, getRenderTargetSamples( renderTarget ) ); - - } else if ( textureTarget === _gl.TEXTURE_2D || ( textureTarget >= _gl.TEXTURE_CUBE_MAP_POSITIVE_X && textureTarget <= _gl.TEXTURE_CUBE_MAP_NEGATIVE_Z ) ) { // see #24753 - - _gl.framebufferTexture2D( _gl.FRAMEBUFFER, attachment, textureTarget, properties.get( texture ).__webglTexture, 0 ); - - } - - state.bindFramebuffer( _gl.FRAMEBUFFER, null ); - - } - - - // Setup storage for internal depth/stencil buffers and bind to correct framebuffer - function setupRenderBufferStorage( renderbuffer, renderTarget, isMultisample ) { - - _gl.bindRenderbuffer( _gl.RENDERBUFFER, renderbuffer ); - - if ( renderTarget.depthBuffer && ! renderTarget.stencilBuffer ) { - - let glInternalFormat = _gl.DEPTH_COMPONENT16; - - if ( isMultisample || useMultisampledRTT( renderTarget ) ) { - - const depthTexture = renderTarget.depthTexture; - - if ( depthTexture && depthTexture.isDepthTexture ) { - - if ( depthTexture.type === FloatType ) { - - glInternalFormat = _gl.DEPTH_COMPONENT32F; - - } else if ( depthTexture.type === UnsignedIntType ) { - - glInternalFormat = _gl.DEPTH_COMPONENT24; - - } - - } - - const samples = getRenderTargetSamples( renderTarget ); - - if ( useMultisampledRTT( renderTarget ) ) { - - multisampledRTTExt.renderbufferStorageMultisampleEXT( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); - - } else { - - _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); - - } - - } else { - - _gl.renderbufferStorage( _gl.RENDERBUFFER, glInternalFormat, renderTarget.width, renderTarget.height ); - - } - - _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer ); - - } else if ( renderTarget.depthBuffer && renderTarget.stencilBuffer ) { - - const samples = getRenderTargetSamples( renderTarget ); - - if ( isMultisample && useMultisampledRTT( renderTarget ) === false ) { - - _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, _gl.DEPTH24_STENCIL8, renderTarget.width, renderTarget.height ); - - } else if ( useMultisampledRTT( renderTarget ) ) { - - multisampledRTTExt.renderbufferStorageMultisampleEXT( _gl.RENDERBUFFER, samples, _gl.DEPTH24_STENCIL8, renderTarget.width, renderTarget.height ); - - } else { - - _gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.DEPTH_STENCIL, renderTarget.width, renderTarget.height ); - - } - - - _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer ); - - } else { - - const textures = renderTarget.isWebGLMultipleRenderTargets === true ? renderTarget.texture : [ renderTarget.texture ]; - - for ( let i = 0; i < textures.length; i ++ ) { - - const texture = textures[ i ]; - - const glFormat = utils.convert( texture.format, texture.colorSpace ); - const glType = utils.convert( texture.type ); - const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace ); - const samples = getRenderTargetSamples( renderTarget ); - - if ( isMultisample && useMultisampledRTT( renderTarget ) === false ) { - - _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); - - } else if ( useMultisampledRTT( renderTarget ) ) { - - multisampledRTTExt.renderbufferStorageMultisampleEXT( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); - - } else { - - _gl.renderbufferStorage( _gl.RENDERBUFFER, glInternalFormat, renderTarget.width, renderTarget.height ); - - } - - } - - } - - _gl.bindRenderbuffer( _gl.RENDERBUFFER, null ); - - } - - // Setup resources for a Depth Texture for a FBO (needs an extension) - function setupDepthTexture( framebuffer, renderTarget ) { - - const isCube = ( renderTarget && renderTarget.isWebGLCubeRenderTarget ); - if ( isCube ) throw new Error( 'Depth Texture with cube render targets is not supported' ); - - state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); - - if ( ! ( renderTarget.depthTexture && renderTarget.depthTexture.isDepthTexture ) ) { - - throw new Error( 'renderTarget.depthTexture must be an instance of THREE.DepthTexture' ); - - } - - // upload an empty depth texture with framebuffer size - if ( ! properties.get( renderTarget.depthTexture ).__webglTexture || - renderTarget.depthTexture.image.width !== renderTarget.width || - renderTarget.depthTexture.image.height !== renderTarget.height ) { - - renderTarget.depthTexture.image.width = renderTarget.width; - renderTarget.depthTexture.image.height = renderTarget.height; - renderTarget.depthTexture.needsUpdate = true; - - } - - setTexture2D( renderTarget.depthTexture, 0 ); - - const webglDepthTexture = properties.get( renderTarget.depthTexture ).__webglTexture; - const samples = getRenderTargetSamples( renderTarget ); - - if ( renderTarget.depthTexture.format === DepthFormat ) { - - if ( useMultisampledRTT( renderTarget ) ) { - - multisampledRTTExt.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0, samples ); - - } else { - - _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0 ); - - } - - } else if ( renderTarget.depthTexture.format === DepthStencilFormat ) { - - if ( useMultisampledRTT( renderTarget ) ) { - - multisampledRTTExt.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0, samples ); - - } else { - - _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0 ); - - } - - } else { - - throw new Error( 'Unknown depthTexture format' ); - - } - - } - - // Setup GL resources for a non-texture depth buffer - function setupDepthRenderbuffer( renderTarget ) { - - const renderTargetProperties = properties.get( renderTarget ); - const isCube = ( renderTarget.isWebGLCubeRenderTarget === true ); - - if ( renderTarget.depthTexture && ! renderTargetProperties.__autoAllocateDepthBuffer ) { - - if ( isCube ) throw new Error( 'target.depthTexture not supported in Cube render targets' ); - - setupDepthTexture( renderTargetProperties.__webglFramebuffer, renderTarget ); - - } else { - - if ( isCube ) { - - renderTargetProperties.__webglDepthbuffer = []; - - for ( let i = 0; i < 6; i ++ ) { - - state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer[ i ] ); - renderTargetProperties.__webglDepthbuffer[ i ] = _gl.createRenderbuffer(); - setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer[ i ], renderTarget, false ); - - } - - } else { - - state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); - renderTargetProperties.__webglDepthbuffer = _gl.createRenderbuffer(); - setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer, renderTarget, false ); - - } - - } - - state.bindFramebuffer( _gl.FRAMEBUFFER, null ); - - } - - // rebind framebuffer with external textures - function rebindTextures( renderTarget, colorTexture, depthTexture ) { - - const renderTargetProperties = properties.get( renderTarget ); - - if ( colorTexture !== undefined ) { - - setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, renderTarget.texture, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D ); - - } - - if ( depthTexture !== undefined ) { - - setupDepthRenderbuffer( renderTarget ); - - } - - } - - // Set up GL resources for the render target - function setupRenderTarget( renderTarget ) { - - const texture = renderTarget.texture; - - const renderTargetProperties = properties.get( renderTarget ); - const textureProperties = properties.get( texture ); - - renderTarget.addEventListener( 'dispose', onRenderTargetDispose ); - - if ( renderTarget.isWebGLMultipleRenderTargets !== true ) { - - if ( textureProperties.__webglTexture === undefined ) { - - textureProperties.__webglTexture = _gl.createTexture(); - - } - - textureProperties.__version = texture.version; - info.memory.textures ++; - - } - - const isCube = ( renderTarget.isWebGLCubeRenderTarget === true ); - const isMultipleRenderTargets = ( renderTarget.isWebGLMultipleRenderTargets === true ); - const supportsMips = isPowerOfTwo$1( renderTarget ) || isWebGL2; - - // Setup framebuffer - - if ( isCube ) { - - renderTargetProperties.__webglFramebuffer = []; - - for ( let i = 0; i < 6; i ++ ) { - - renderTargetProperties.__webglFramebuffer[ i ] = _gl.createFramebuffer(); - - } - - } else { - - renderTargetProperties.__webglFramebuffer = _gl.createFramebuffer(); - - if ( isMultipleRenderTargets ) { - - if ( capabilities.drawBuffers ) { - - const textures = renderTarget.texture; - - for ( let i = 0, il = textures.length; i < il; i ++ ) { - - const attachmentProperties = properties.get( textures[ i ] ); - - if ( attachmentProperties.__webglTexture === undefined ) { - - attachmentProperties.__webglTexture = _gl.createTexture(); - - info.memory.textures ++; - - } - - } - - } else { - - console.warn( 'THREE.WebGLRenderer: WebGLMultipleRenderTargets can only be used with WebGL2 or WEBGL_draw_buffers extension.' ); - - } - - } - - if ( ( isWebGL2 && renderTarget.samples > 0 ) && useMultisampledRTT( renderTarget ) === false ) { - - const textures = isMultipleRenderTargets ? texture : [ texture ]; - - renderTargetProperties.__webglMultisampledFramebuffer = _gl.createFramebuffer(); - renderTargetProperties.__webglColorRenderbuffer = []; - - state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); - - for ( let i = 0; i < textures.length; i ++ ) { - - const texture = textures[ i ]; - renderTargetProperties.__webglColorRenderbuffer[ i ] = _gl.createRenderbuffer(); - - _gl.bindRenderbuffer( _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] ); - - const glFormat = utils.convert( texture.format, texture.colorSpace ); - const glType = utils.convert( texture.type ); - const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace, renderTarget.isXRRenderTarget === true ); - const samples = getRenderTargetSamples( renderTarget ); - _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); - - _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] ); - - } - - _gl.bindRenderbuffer( _gl.RENDERBUFFER, null ); - - if ( renderTarget.depthBuffer ) { - - renderTargetProperties.__webglDepthRenderbuffer = _gl.createRenderbuffer(); - setupRenderBufferStorage( renderTargetProperties.__webglDepthRenderbuffer, renderTarget, true ); - - } - - state.bindFramebuffer( _gl.FRAMEBUFFER, null ); - - } - - } - - // Setup color buffer - - if ( isCube ) { - - state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__webglTexture ); - setTextureParameters( _gl.TEXTURE_CUBE_MAP, texture, supportsMips ); - - for ( let i = 0; i < 6; i ++ ) { - - setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer[ i ], renderTarget, texture, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i ); - - } - - if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) { - - generateMipmap( _gl.TEXTURE_CUBE_MAP ); - - } - - state.unbindTexture(); - - } else if ( isMultipleRenderTargets ) { - - const textures = renderTarget.texture; - - for ( let i = 0, il = textures.length; i < il; i ++ ) { - - const attachment = textures[ i ]; - const attachmentProperties = properties.get( attachment ); - - state.bindTexture( _gl.TEXTURE_2D, attachmentProperties.__webglTexture ); - setTextureParameters( _gl.TEXTURE_2D, attachment, supportsMips ); - setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, attachment, _gl.COLOR_ATTACHMENT0 + i, _gl.TEXTURE_2D ); - - if ( textureNeedsGenerateMipmaps( attachment, supportsMips ) ) { - - generateMipmap( _gl.TEXTURE_2D ); - - } - - } - - state.unbindTexture(); - - } else { - - let glTextureType = _gl.TEXTURE_2D; - - if ( renderTarget.isWebGL3DRenderTarget || renderTarget.isWebGLArrayRenderTarget ) { - - if ( isWebGL2 ) { - - glTextureType = renderTarget.isWebGL3DRenderTarget ? _gl.TEXTURE_3D : _gl.TEXTURE_2D_ARRAY; - - } else { - - console.error( 'THREE.WebGLTextures: THREE.Data3DTexture and THREE.DataArrayTexture only supported with WebGL2.' ); - - } - - } - - state.bindTexture( glTextureType, textureProperties.__webglTexture ); - setTextureParameters( glTextureType, texture, supportsMips ); - setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, texture, _gl.COLOR_ATTACHMENT0, glTextureType ); - - if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) { - - generateMipmap( glTextureType ); - - } - - state.unbindTexture(); - - } - - // Setup depth and stencil buffers - - if ( renderTarget.depthBuffer ) { - - setupDepthRenderbuffer( renderTarget ); - - } - - } - - function updateRenderTargetMipmap( renderTarget ) { - - const supportsMips = isPowerOfTwo$1( renderTarget ) || isWebGL2; - - const textures = renderTarget.isWebGLMultipleRenderTargets === true ? renderTarget.texture : [ renderTarget.texture ]; - - for ( let i = 0, il = textures.length; i < il; i ++ ) { - - const texture = textures[ i ]; - - if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) { - - const target = renderTarget.isWebGLCubeRenderTarget ? _gl.TEXTURE_CUBE_MAP : _gl.TEXTURE_2D; - const webglTexture = properties.get( texture ).__webglTexture; - - state.bindTexture( target, webglTexture ); - generateMipmap( target ); - state.unbindTexture(); - - } - - } - - } - - function updateMultisampleRenderTarget( renderTarget ) { - - if ( ( isWebGL2 && renderTarget.samples > 0 ) && useMultisampledRTT( renderTarget ) === false ) { - - const textures = renderTarget.isWebGLMultipleRenderTargets ? renderTarget.texture : [ renderTarget.texture ]; - const width = renderTarget.width; - const height = renderTarget.height; - let mask = _gl.COLOR_BUFFER_BIT; - const invalidationArray = []; - const depthStyle = renderTarget.stencilBuffer ? _gl.DEPTH_STENCIL_ATTACHMENT : _gl.DEPTH_ATTACHMENT; - const renderTargetProperties = properties.get( renderTarget ); - const isMultipleRenderTargets = ( renderTarget.isWebGLMultipleRenderTargets === true ); - - // If MRT we need to remove FBO attachments - if ( isMultipleRenderTargets ) { - - for ( let i = 0; i < textures.length; i ++ ) { - - state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); - _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.RENDERBUFFER, null ); - - state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); - _gl.framebufferTexture2D( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.TEXTURE_2D, null, 0 ); - - } - - } - - state.bindFramebuffer( _gl.READ_FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); - state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); - - for ( let i = 0; i < textures.length; i ++ ) { - - invalidationArray.push( _gl.COLOR_ATTACHMENT0 + i ); - - if ( renderTarget.depthBuffer ) { - - invalidationArray.push( depthStyle ); - - } - - const ignoreDepthValues = ( renderTargetProperties.__ignoreDepthValues !== undefined ) ? renderTargetProperties.__ignoreDepthValues : false; - - if ( ignoreDepthValues === false ) { - - if ( renderTarget.depthBuffer ) mask |= _gl.DEPTH_BUFFER_BIT; - if ( renderTarget.stencilBuffer ) mask |= _gl.STENCIL_BUFFER_BIT; - - } - - if ( isMultipleRenderTargets ) { - - _gl.framebufferRenderbuffer( _gl.READ_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] ); - - } - - if ( ignoreDepthValues === true ) { - - _gl.invalidateFramebuffer( _gl.READ_FRAMEBUFFER, [ depthStyle ] ); - _gl.invalidateFramebuffer( _gl.DRAW_FRAMEBUFFER, [ depthStyle ] ); - - } - - if ( isMultipleRenderTargets ) { - - const webglTexture = properties.get( textures[ i ] ).__webglTexture; - _gl.framebufferTexture2D( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D, webglTexture, 0 ); - - } - - _gl.blitFramebuffer( 0, 0, width, height, 0, 0, width, height, mask, _gl.NEAREST ); - - if ( supportsInvalidateFramebuffer ) { - - _gl.invalidateFramebuffer( _gl.READ_FRAMEBUFFER, invalidationArray ); - - } - - - } - - state.bindFramebuffer( _gl.READ_FRAMEBUFFER, null ); - state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, null ); - - // If MRT since pre-blit we removed the FBO we need to reconstruct the attachments - if ( isMultipleRenderTargets ) { - - for ( let i = 0; i < textures.length; i ++ ) { - - state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); - _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] ); - - const webglTexture = properties.get( textures[ i ] ).__webglTexture; - - state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); - _gl.framebufferTexture2D( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.TEXTURE_2D, webglTexture, 0 ); - - } - - } - - state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); - - } - - } - - function getRenderTargetSamples( renderTarget ) { - - return Math.min( maxSamples, renderTarget.samples ); - - } - - function useMultisampledRTT( renderTarget ) { - - const renderTargetProperties = properties.get( renderTarget ); - - return isWebGL2 && renderTarget.samples > 0 && extensions.has( 'WEBGL_multisampled_render_to_texture' ) === true && renderTargetProperties.__useRenderToTexture !== false; - - } - - function updateVideoTexture( texture ) { - - const frame = info.render.frame; - - // Check the last frame we updated the VideoTexture - - if ( _videoTextures.get( texture ) !== frame ) { - - _videoTextures.set( texture, frame ); - texture.update(); - - } - - } - - function verifyColorSpace( texture, image ) { - - const colorSpace = texture.colorSpace; - const format = texture.format; - const type = texture.type; - - if ( texture.isCompressedTexture === true || texture.format === _SRGBAFormat ) return image; - - if ( colorSpace !== LinearSRGBColorSpace && colorSpace !== NoColorSpace ) { - - // sRGB - - if ( colorSpace === SRGBColorSpace ) { - - if ( isWebGL2 === false ) { - - // in WebGL 1, try to use EXT_sRGB extension and unsized formats - - if ( extensions.has( 'EXT_sRGB' ) === true && format === RGBAFormat ) { - - texture.format = _SRGBAFormat; - - // it's not possible to generate mips in WebGL 1 with this extension - - texture.minFilter = LinearFilter; - texture.generateMipmaps = false; - - } else { - - // slow fallback (CPU decode) - - image = ImageUtils.sRGBToLinear( image ); - - } - - } else { - - // in WebGL 2 uncompressed textures can only be sRGB encoded if they have the RGBA8 format - - if ( format !== RGBAFormat || type !== UnsignedByteType ) { - - console.warn( 'THREE.WebGLTextures: sRGB encoded textures have to use RGBAFormat and UnsignedByteType.' ); - - } - - } - - } else { - - console.error( 'THREE.WebGLTextures: Unsupported texture color space:', colorSpace ); - - } - - } - - return image; - - } - - // - - this.allocateTextureUnit = allocateTextureUnit; - this.resetTextureUnits = resetTextureUnits; - - this.setTexture2D = setTexture2D; - this.setTexture2DArray = setTexture2DArray; - this.setTexture3D = setTexture3D; - this.setTextureCube = setTextureCube; - this.rebindTextures = rebindTextures; - this.setupRenderTarget = setupRenderTarget; - this.updateRenderTargetMipmap = updateRenderTargetMipmap; - this.updateMultisampleRenderTarget = updateMultisampleRenderTarget; - this.setupDepthRenderbuffer = setupDepthRenderbuffer; - this.setupFrameBufferTexture = setupFrameBufferTexture; - this.useMultisampledRTT = useMultisampledRTT; - - } - - function WebGLUtils( gl, extensions, capabilities ) { - - const isWebGL2 = capabilities.isWebGL2; - - function convert( p, colorSpace = NoColorSpace ) { - - let extension; - - if ( p === UnsignedByteType ) return gl.UNSIGNED_BYTE; - if ( p === UnsignedShort4444Type ) return gl.UNSIGNED_SHORT_4_4_4_4; - if ( p === UnsignedShort5551Type ) return gl.UNSIGNED_SHORT_5_5_5_1; - - if ( p === ByteType ) return gl.BYTE; - if ( p === ShortType ) return gl.SHORT; - if ( p === UnsignedShortType ) return gl.UNSIGNED_SHORT; - if ( p === IntType ) return gl.INT; - if ( p === UnsignedIntType ) return gl.UNSIGNED_INT; - if ( p === FloatType ) return gl.FLOAT; - - if ( p === HalfFloatType ) { - - if ( isWebGL2 ) return gl.HALF_FLOAT; - - extension = extensions.get( 'OES_texture_half_float' ); - - if ( extension !== null ) { - - return extension.HALF_FLOAT_OES; - - } else { - - return null; - - } - - } - - if ( p === AlphaFormat ) return gl.ALPHA; - if ( p === RGBAFormat ) return gl.RGBA; - if ( p === LuminanceFormat ) return gl.LUMINANCE; - if ( p === LuminanceAlphaFormat ) return gl.LUMINANCE_ALPHA; - if ( p === DepthFormat ) return gl.DEPTH_COMPONENT; - if ( p === DepthStencilFormat ) return gl.DEPTH_STENCIL; - - // WebGL 1 sRGB fallback - - if ( p === _SRGBAFormat ) { - - extension = extensions.get( 'EXT_sRGB' ); - - if ( extension !== null ) { - - return extension.SRGB_ALPHA_EXT; - - } else { - - return null; - - } - - } - - // WebGL2 formats. - - if ( p === RedFormat ) return gl.RED; - if ( p === RedIntegerFormat ) return gl.RED_INTEGER; - if ( p === RGFormat ) return gl.RG; - if ( p === RGIntegerFormat ) return gl.RG_INTEGER; - if ( p === RGBAIntegerFormat ) return gl.RGBA_INTEGER; - - // S3TC - - if ( p === RGB_S3TC_DXT1_Format || p === RGBA_S3TC_DXT1_Format || p === RGBA_S3TC_DXT3_Format || p === RGBA_S3TC_DXT5_Format ) { - - if ( colorSpace === SRGBColorSpace ) { - - extension = extensions.get( 'WEBGL_compressed_texture_s3tc_srgb' ); - - if ( extension !== null ) { - - if ( p === RGB_S3TC_DXT1_Format ) return extension.COMPRESSED_SRGB_S3TC_DXT1_EXT; - if ( p === RGBA_S3TC_DXT1_Format ) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT; - if ( p === RGBA_S3TC_DXT3_Format ) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT; - if ( p === RGBA_S3TC_DXT5_Format ) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT; - - } else { - - return null; - - } - - } else { - - extension = extensions.get( 'WEBGL_compressed_texture_s3tc' ); - - if ( extension !== null ) { - - if ( p === RGB_S3TC_DXT1_Format ) return extension.COMPRESSED_RGB_S3TC_DXT1_EXT; - if ( p === RGBA_S3TC_DXT1_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT1_EXT; - if ( p === RGBA_S3TC_DXT3_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT3_EXT; - if ( p === RGBA_S3TC_DXT5_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT5_EXT; - - } else { - - return null; - - } - - } - - } - - // PVRTC - - if ( p === RGB_PVRTC_4BPPV1_Format || p === RGB_PVRTC_2BPPV1_Format || p === RGBA_PVRTC_4BPPV1_Format || p === RGBA_PVRTC_2BPPV1_Format ) { - - extension = extensions.get( 'WEBGL_compressed_texture_pvrtc' ); - - if ( extension !== null ) { - - if ( p === RGB_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_4BPPV1_IMG; - if ( p === RGB_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_2BPPV1_IMG; - if ( p === RGBA_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; - if ( p === RGBA_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG; - - } else { - - return null; - - } - - } - - // ETC1 - - if ( p === RGB_ETC1_Format ) { - - extension = extensions.get( 'WEBGL_compressed_texture_etc1' ); - - if ( extension !== null ) { - - return extension.COMPRESSED_RGB_ETC1_WEBGL; - - } else { - - return null; - - } - - } - - // ETC2 - - if ( p === RGB_ETC2_Format || p === RGBA_ETC2_EAC_Format ) { - - extension = extensions.get( 'WEBGL_compressed_texture_etc' ); - - if ( extension !== null ) { - - if ( p === RGB_ETC2_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ETC2 : extension.COMPRESSED_RGB8_ETC2; - if ( p === RGBA_ETC2_EAC_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ETC2_EAC : extension.COMPRESSED_RGBA8_ETC2_EAC; - - } else { - - return null; - - } - - } - - // ASTC - - if ( p === RGBA_ASTC_4x4_Format || p === RGBA_ASTC_5x4_Format || p === RGBA_ASTC_5x5_Format || - p === RGBA_ASTC_6x5_Format || p === RGBA_ASTC_6x6_Format || p === RGBA_ASTC_8x5_Format || - p === RGBA_ASTC_8x6_Format || p === RGBA_ASTC_8x8_Format || p === RGBA_ASTC_10x5_Format || - p === RGBA_ASTC_10x6_Format || p === RGBA_ASTC_10x8_Format || p === RGBA_ASTC_10x10_Format || - p === RGBA_ASTC_12x10_Format || p === RGBA_ASTC_12x12_Format ) { - - extension = extensions.get( 'WEBGL_compressed_texture_astc' ); - - if ( extension !== null ) { - - if ( p === RGBA_ASTC_4x4_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR : extension.COMPRESSED_RGBA_ASTC_4x4_KHR; - if ( p === RGBA_ASTC_5x4_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR : extension.COMPRESSED_RGBA_ASTC_5x4_KHR; - if ( p === RGBA_ASTC_5x5_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR : extension.COMPRESSED_RGBA_ASTC_5x5_KHR; - if ( p === RGBA_ASTC_6x5_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR : extension.COMPRESSED_RGBA_ASTC_6x5_KHR; - if ( p === RGBA_ASTC_6x6_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR : extension.COMPRESSED_RGBA_ASTC_6x6_KHR; - if ( p === RGBA_ASTC_8x5_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR : extension.COMPRESSED_RGBA_ASTC_8x5_KHR; - if ( p === RGBA_ASTC_8x6_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR : extension.COMPRESSED_RGBA_ASTC_8x6_KHR; - if ( p === RGBA_ASTC_8x8_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR : extension.COMPRESSED_RGBA_ASTC_8x8_KHR; - if ( p === RGBA_ASTC_10x5_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR : extension.COMPRESSED_RGBA_ASTC_10x5_KHR; - if ( p === RGBA_ASTC_10x6_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR : extension.COMPRESSED_RGBA_ASTC_10x6_KHR; - if ( p === RGBA_ASTC_10x8_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR : extension.COMPRESSED_RGBA_ASTC_10x8_KHR; - if ( p === RGBA_ASTC_10x10_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR : extension.COMPRESSED_RGBA_ASTC_10x10_KHR; - if ( p === RGBA_ASTC_12x10_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR : extension.COMPRESSED_RGBA_ASTC_12x10_KHR; - if ( p === RGBA_ASTC_12x12_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR : extension.COMPRESSED_RGBA_ASTC_12x12_KHR; - - } else { - - return null; - - } - - } - - // BPTC - - if ( p === RGBA_BPTC_Format ) { - - extension = extensions.get( 'EXT_texture_compression_bptc' ); - - if ( extension !== null ) { - - if ( p === RGBA_BPTC_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT : extension.COMPRESSED_RGBA_BPTC_UNORM_EXT; - - } else { - - return null; - - } - - } - - // RGTC - - if ( p === RED_RGTC1_Format || p === SIGNED_RED_RGTC1_Format || p === RED_GREEN_RGTC2_Format || p === SIGNED_RED_GREEN_RGTC2_Format ) { - - extension = extensions.get( 'EXT_texture_compression_rgtc' ); - - if ( extension !== null ) { - - if ( p === RGBA_BPTC_Format ) return extension.COMPRESSED_RED_RGTC1_EXT; - if ( p === SIGNED_RED_RGTC1_Format ) return extension.COMPRESSED_SIGNED_RED_RGTC1_EXT; - if ( p === RED_GREEN_RGTC2_Format ) return extension.COMPRESSED_RED_GREEN_RGTC2_EXT; - if ( p === SIGNED_RED_GREEN_RGTC2_Format ) return extension.COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT; - - } else { - - return null; - - } - - } - - // - - if ( p === UnsignedInt248Type ) { - - if ( isWebGL2 ) return gl.UNSIGNED_INT_24_8; - - extension = extensions.get( 'WEBGL_depth_texture' ); - - if ( extension !== null ) { - - return extension.UNSIGNED_INT_24_8_WEBGL; - - } else { - - return null; - - } - - } - - // if "p" can't be resolved, assume the user defines a WebGL constant as a string (fallback/workaround for packed RGB formats) - - return ( gl[ p ] !== undefined ) ? gl[ p ] : null; - - } - - return { convert: convert }; - - } - - class ArrayCamera extends PerspectiveCamera { - - constructor( array = [] ) { - - super(); - - this.isArrayCamera = true; - - this.cameras = array; - - } - - } - - class Group extends Object3D { - - constructor() { - - super(); - - this.isGroup = true; - - this.type = 'Group'; - - } - - } - - const _moveEvent = { type: 'move' }; - - class WebXRController { - - constructor() { - - this._targetRay = null; - this._grip = null; - this._hand = null; - - } - - getHandSpace() { - - if ( this._hand === null ) { - - this._hand = new Group(); - this._hand.matrixAutoUpdate = false; - this._hand.visible = false; - - this._hand.joints = {}; - this._hand.inputState = { pinching: false }; - - } - - return this._hand; - - } - - getTargetRaySpace() { - - if ( this._targetRay === null ) { - - this._targetRay = new Group(); - this._targetRay.matrixAutoUpdate = false; - this._targetRay.visible = false; - this._targetRay.hasLinearVelocity = false; - this._targetRay.linearVelocity = new Vector3(); - this._targetRay.hasAngularVelocity = false; - this._targetRay.angularVelocity = new Vector3(); - - } - - return this._targetRay; - - } - - getGripSpace() { - - if ( this._grip === null ) { - - this._grip = new Group(); - this._grip.matrixAutoUpdate = false; - this._grip.visible = false; - this._grip.hasLinearVelocity = false; - this._grip.linearVelocity = new Vector3(); - this._grip.hasAngularVelocity = false; - this._grip.angularVelocity = new Vector3(); - - } - - return this._grip; - - } - - dispatchEvent( event ) { - - if ( this._targetRay !== null ) { - - this._targetRay.dispatchEvent( event ); - - } - - if ( this._grip !== null ) { - - this._grip.dispatchEvent( event ); - - } - - if ( this._hand !== null ) { - - this._hand.dispatchEvent( event ); - - } - - return this; - - } - - connect( inputSource ) { - - if ( inputSource && inputSource.hand ) { - - const hand = this._hand; - - if ( hand ) { - - for ( const inputjoint of inputSource.hand.values() ) { - - // Initialize hand with joints when connected - this._getHandJoint( hand, inputjoint ); - - } - - } - - } - - this.dispatchEvent( { type: 'connected', data: inputSource } ); - - return this; - - } - - disconnect( inputSource ) { - - this.dispatchEvent( { type: 'disconnected', data: inputSource } ); - - if ( this._targetRay !== null ) { - - this._targetRay.visible = false; - - } - - if ( this._grip !== null ) { - - this._grip.visible = false; - - } - - if ( this._hand !== null ) { - - this._hand.visible = false; - - } - - return this; - - } - - update( inputSource, frame, referenceSpace ) { - - let inputPose = null; - let gripPose = null; - let handPose = null; - - const targetRay = this._targetRay; - const grip = this._grip; - const hand = this._hand; - - if ( inputSource && frame.session.visibilityState !== 'visible-blurred' ) { - - if ( hand && inputSource.hand ) { - - handPose = true; - - for ( const inputjoint of inputSource.hand.values() ) { - - // Update the joints groups with the XRJoint poses - const jointPose = frame.getJointPose( inputjoint, referenceSpace ); - - // The transform of this joint will be updated with the joint pose on each frame - const joint = this._getHandJoint( hand, inputjoint ); - - if ( jointPose !== null ) { - - joint.matrix.fromArray( jointPose.transform.matrix ); - joint.matrix.decompose( joint.position, joint.rotation, joint.scale ); - joint.matrixWorldNeedsUpdate = true; - joint.jointRadius = jointPose.radius; - - } - - joint.visible = jointPose !== null; - - } - - // Custom events - - // Check pinchz - const indexTip = hand.joints[ 'index-finger-tip' ]; - const thumbTip = hand.joints[ 'thumb-tip' ]; - const distance = indexTip.position.distanceTo( thumbTip.position ); - - const distanceToPinch = 0.02; - const threshold = 0.005; - - if ( hand.inputState.pinching && distance > distanceToPinch + threshold ) { - - hand.inputState.pinching = false; - this.dispatchEvent( { - type: 'pinchend', - handedness: inputSource.handedness, - target: this - } ); - - } else if ( ! hand.inputState.pinching && distance <= distanceToPinch - threshold ) { - - hand.inputState.pinching = true; - this.dispatchEvent( { - type: 'pinchstart', - handedness: inputSource.handedness, - target: this - } ); - - } - - } else { - - if ( grip !== null && inputSource.gripSpace ) { - - gripPose = frame.getPose( inputSource.gripSpace, referenceSpace ); - - if ( gripPose !== null ) { - - grip.matrix.fromArray( gripPose.transform.matrix ); - grip.matrix.decompose( grip.position, grip.rotation, grip.scale ); - grip.matrixWorldNeedsUpdate = true; - - if ( gripPose.linearVelocity ) { - - grip.hasLinearVelocity = true; - grip.linearVelocity.copy( gripPose.linearVelocity ); - - } else { - - grip.hasLinearVelocity = false; - - } - - if ( gripPose.angularVelocity ) { - - grip.hasAngularVelocity = true; - grip.angularVelocity.copy( gripPose.angularVelocity ); - - } else { - - grip.hasAngularVelocity = false; - - } - - } - - } - - } - - if ( targetRay !== null ) { - - inputPose = frame.getPose( inputSource.targetRaySpace, referenceSpace ); - - // Some runtimes (namely Vive Cosmos with Vive OpenXR Runtime) have only grip space and ray space is equal to it - if ( inputPose === null && gripPose !== null ) { - - inputPose = gripPose; - - } - - if ( inputPose !== null ) { - - targetRay.matrix.fromArray( inputPose.transform.matrix ); - targetRay.matrix.decompose( targetRay.position, targetRay.rotation, targetRay.scale ); - targetRay.matrixWorldNeedsUpdate = true; - - if ( inputPose.linearVelocity ) { - - targetRay.hasLinearVelocity = true; - targetRay.linearVelocity.copy( inputPose.linearVelocity ); - - } else { - - targetRay.hasLinearVelocity = false; - - } - - if ( inputPose.angularVelocity ) { - - targetRay.hasAngularVelocity = true; - targetRay.angularVelocity.copy( inputPose.angularVelocity ); - - } else { - - targetRay.hasAngularVelocity = false; - - } - - this.dispatchEvent( _moveEvent ); - - } - - } - - - } - - if ( targetRay !== null ) { - - targetRay.visible = ( inputPose !== null ); - - } - - if ( grip !== null ) { - - grip.visible = ( gripPose !== null ); - - } - - if ( hand !== null ) { - - hand.visible = ( handPose !== null ); - - } - - return this; - - } - - // private method - - _getHandJoint( hand, inputjoint ) { - - if ( hand.joints[ inputjoint.jointName ] === undefined ) { - - const joint = new Group(); - joint.matrixAutoUpdate = false; - joint.visible = false; - hand.joints[ inputjoint.jointName ] = joint; - - hand.add( joint ); - - } - - return hand.joints[ inputjoint.jointName ]; - - } - - } - - class DepthTexture extends Texture { - - constructor( width, height, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, format ) { - - format = format !== undefined ? format : DepthFormat; - - if ( format !== DepthFormat && format !== DepthStencilFormat ) { - - throw new Error( 'DepthTexture format must be either THREE.DepthFormat or THREE.DepthStencilFormat' ); - - } - - if ( type === undefined && format === DepthFormat ) type = UnsignedIntType; - if ( type === undefined && format === DepthStencilFormat ) type = UnsignedInt248Type; - - super( null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); - - this.isDepthTexture = true; - - this.image = { width: width, height: height }; - - this.magFilter = magFilter !== undefined ? magFilter : NearestFilter; - this.minFilter = minFilter !== undefined ? minFilter : NearestFilter; - - this.flipY = false; - this.generateMipmaps = false; - - this.compareFunction = null; - - } - - - copy( source ) { - - super.copy( source ); - - this.compareFunction = source.compareFunction; - - return this; - - } - - toJSON( meta ) { - - const data = super.toJSON( meta ); - - if ( this.compareFunction !== null ) data.compareFunction = this.compareFunction; - - return data; - - } - - } - - class WebXRManager extends EventDispatcher { - - constructor( renderer, gl ) { - - super(); - - const scope = this; - - let session = null; - - let framebufferScaleFactor = 1.0; - - let referenceSpace = null; - let referenceSpaceType = 'local-floor'; - // Set default foveation to maximum. - let foveation = 1.0; - let customReferenceSpace = null; - - let pose = null; - let glBinding = null; - let glProjLayer = null; - let glBaseLayer = null; - let xrFrame = null; - const attributes = gl.getContextAttributes(); - let initialRenderTarget = null; - let newRenderTarget = null; - - const controllers = []; - const controllerInputSources = []; - - const planes = new Set(); - const planesLastChangedTimes = new Map(); - - // - - let userCamera = null; - - const cameraL = new PerspectiveCamera(); - cameraL.layers.enable( 1 ); - cameraL.viewport = new Vector4(); - - const cameraR = new PerspectiveCamera(); - cameraR.layers.enable( 2 ); - cameraR.viewport = new Vector4(); - - const cameras = [ cameraL, cameraR ]; - - const cameraXR = new ArrayCamera(); - cameraXR.layers.enable( 1 ); - cameraXR.layers.enable( 2 ); - - let _currentDepthNear = null; - let _currentDepthFar = null; - - // - - this.cameraAutoUpdate = true; // @deprecated, r153 - this.enabled = false; - - this.isPresenting = false; - - this.getCamera = function () {}; // @deprecated, r153 - - this.setUserCamera = function ( value ) { - - userCamera = value; - - }; - - this.getController = function ( index ) { - - let controller = controllers[ index ]; - - if ( controller === undefined ) { - - controller = new WebXRController(); - controllers[ index ] = controller; - - } - - return controller.getTargetRaySpace(); - - }; - - this.getControllerGrip = function ( index ) { - - let controller = controllers[ index ]; - - if ( controller === undefined ) { - - controller = new WebXRController(); - controllers[ index ] = controller; - - } - - return controller.getGripSpace(); - - }; - - this.getHand = function ( index ) { - - let controller = controllers[ index ]; - - if ( controller === undefined ) { - - controller = new WebXRController(); - controllers[ index ] = controller; - - } - - return controller.getHandSpace(); - - }; - - // - - function onSessionEvent( event ) { - - const controllerIndex = controllerInputSources.indexOf( event.inputSource ); - - if ( controllerIndex === - 1 ) { - - return; - - } - - const controller = controllers[ controllerIndex ]; - - if ( controller !== undefined ) { - - controller.update( event.inputSource, event.frame, customReferenceSpace || referenceSpace ); - controller.dispatchEvent( { type: event.type, data: event.inputSource } ); - - } - - } - - function onSessionEnd() { - - session.removeEventListener( 'select', onSessionEvent ); - session.removeEventListener( 'selectstart', onSessionEvent ); - session.removeEventListener( 'selectend', onSessionEvent ); - session.removeEventListener( 'squeeze', onSessionEvent ); - session.removeEventListener( 'squeezestart', onSessionEvent ); - session.removeEventListener( 'squeezeend', onSessionEvent ); - session.removeEventListener( 'end', onSessionEnd ); - session.removeEventListener( 'inputsourceschange', onInputSourcesChange ); - - for ( let i = 0; i < controllers.length; i ++ ) { - - const inputSource = controllerInputSources[ i ]; - - if ( inputSource === null ) continue; - - controllerInputSources[ i ] = null; - - controllers[ i ].disconnect( inputSource ); - - } - - _currentDepthNear = null; - _currentDepthFar = null; - - // restore framebuffer/rendering state - - renderer.setRenderTarget( initialRenderTarget ); - - glBaseLayer = null; - glProjLayer = null; - glBinding = null; - session = null; - newRenderTarget = null; - - // - - animation.stop(); - - scope.isPresenting = false; - - scope.dispatchEvent( { type: 'sessionend' } ); - - } - - this.setFramebufferScaleFactor = function ( value ) { - - framebufferScaleFactor = value; - - if ( scope.isPresenting === true ) { - - console.warn( 'THREE.WebXRManager: Cannot change framebuffer scale while presenting.' ); - - } - - }; - - this.setReferenceSpaceType = function ( value ) { - - referenceSpaceType = value; - - if ( scope.isPresenting === true ) { - - console.warn( 'THREE.WebXRManager: Cannot change reference space type while presenting.' ); - - } - - }; - - this.getReferenceSpace = function () { - - return customReferenceSpace || referenceSpace; - - }; - - this.setReferenceSpace = function ( space ) { - - customReferenceSpace = space; - - }; - - this.getBaseLayer = function () { - - return glProjLayer !== null ? glProjLayer : glBaseLayer; - - }; - - this.getBinding = function () { - - return glBinding; - - }; - - this.getFrame = function () { - - return xrFrame; - - }; - - this.getSession = function () { - - return session; - - }; - - this.setSession = async function ( value ) { - - session = value; - - if ( session !== null ) { - - initialRenderTarget = renderer.getRenderTarget(); - - session.addEventListener( 'select', onSessionEvent ); - session.addEventListener( 'selectstart', onSessionEvent ); - session.addEventListener( 'selectend', onSessionEvent ); - session.addEventListener( 'squeeze', onSessionEvent ); - session.addEventListener( 'squeezestart', onSessionEvent ); - session.addEventListener( 'squeezeend', onSessionEvent ); - session.addEventListener( 'end', onSessionEnd ); - session.addEventListener( 'inputsourceschange', onInputSourcesChange ); - - if ( attributes.xrCompatible !== true ) { - - await gl.makeXRCompatible(); - - } - - if ( ( session.renderState.layers === undefined ) || ( renderer.capabilities.isWebGL2 === false ) ) { - - const layerInit = { - antialias: ( session.renderState.layers === undefined ) ? attributes.antialias : true, - alpha: true, - depth: attributes.depth, - stencil: attributes.stencil, - framebufferScaleFactor: framebufferScaleFactor - }; - - glBaseLayer = new XRWebGLLayer( session, gl, layerInit ); - - session.updateRenderState( { baseLayer: glBaseLayer } ); - - newRenderTarget = new WebGLRenderTarget( - glBaseLayer.framebufferWidth, - glBaseLayer.framebufferHeight, - { - format: RGBAFormat, - type: UnsignedByteType, - colorSpace: renderer.outputColorSpace, - stencilBuffer: attributes.stencil - } - ); - - } else { - - let depthFormat = null; - let depthType = null; - let glDepthFormat = null; - - if ( attributes.depth ) { - - glDepthFormat = attributes.stencil ? gl.DEPTH24_STENCIL8 : gl.DEPTH_COMPONENT24; - depthFormat = attributes.stencil ? DepthStencilFormat : DepthFormat; - depthType = attributes.stencil ? UnsignedInt248Type : UnsignedIntType; - - } - - const projectionlayerInit = { - colorFormat: gl.RGBA8, - depthFormat: glDepthFormat, - scaleFactor: framebufferScaleFactor - }; - - glBinding = new XRWebGLBinding( session, gl ); - - glProjLayer = glBinding.createProjectionLayer( projectionlayerInit ); - - session.updateRenderState( { layers: [ glProjLayer ] } ); - - newRenderTarget = new WebGLRenderTarget( - glProjLayer.textureWidth, - glProjLayer.textureHeight, - { - format: RGBAFormat, - type: UnsignedByteType, - depthTexture: new DepthTexture( glProjLayer.textureWidth, glProjLayer.textureHeight, depthType, undefined, undefined, undefined, undefined, undefined, undefined, depthFormat ), - stencilBuffer: attributes.stencil, - colorSpace: renderer.outputColorSpace, - samples: attributes.antialias ? 4 : 0 - } ); - - const renderTargetProperties = renderer.properties.get( newRenderTarget ); - renderTargetProperties.__ignoreDepthValues = glProjLayer.ignoreDepthValues; - - } - - newRenderTarget.isXRRenderTarget = true; // TODO Remove this when possible, see #23278 - - this.setFoveation( foveation ); - - customReferenceSpace = null; - referenceSpace = await session.requestReferenceSpace( referenceSpaceType ); - - animation.setContext( session ); - animation.start(); - - scope.isPresenting = true; - - scope.dispatchEvent( { type: 'sessionstart' } ); - - } - - }; - - this.getEnvironmentBlendMode = function () { - - if ( session !== null ) { - - return session.environmentBlendMode; - - } - - }; - - function onInputSourcesChange( event ) { - - // Notify disconnected - - for ( let i = 0; i < event.removed.length; i ++ ) { - - const inputSource = event.removed[ i ]; - const index = controllerInputSources.indexOf( inputSource ); - - if ( index >= 0 ) { - - controllerInputSources[ index ] = null; - controllers[ index ].disconnect( inputSource ); - - } - - } - - // Notify connected - - for ( let i = 0; i < event.added.length; i ++ ) { - - const inputSource = event.added[ i ]; - - let controllerIndex = controllerInputSources.indexOf( inputSource ); - - if ( controllerIndex === - 1 ) { - - // Assign input source a controller that currently has no input source - - for ( let i = 0; i < controllers.length; i ++ ) { - - if ( i >= controllerInputSources.length ) { - - controllerInputSources.push( inputSource ); - controllerIndex = i; - break; - - } else if ( controllerInputSources[ i ] === null ) { - - controllerInputSources[ i ] = inputSource; - controllerIndex = i; - break; - - } - - } - - // If all controllers do currently receive input we ignore new ones - - if ( controllerIndex === - 1 ) break; - - } - - const controller = controllers[ controllerIndex ]; - - if ( controller ) { - - controller.connect( inputSource ); - - } - - } - - } - - // - - const cameraLPos = new Vector3(); - const cameraRPos = new Vector3(); - - /** - * Assumes 2 cameras that are parallel and share an X-axis, and that - * the cameras' projection and world matrices have already been set. - * And that near and far planes are identical for both cameras. - * Visualization of this technique: https://computergraphics.stackexchange.com/a/4765 - */ - function setProjectionFromUnion( camera, cameraL, cameraR ) { - - cameraLPos.setFromMatrixPosition( cameraL.matrixWorld ); - cameraRPos.setFromMatrixPosition( cameraR.matrixWorld ); - - const ipd = cameraLPos.distanceTo( cameraRPos ); - - const projL = cameraL.projectionMatrix.elements; - const projR = cameraR.projectionMatrix.elements; - - // VR systems will have identical far and near planes, and - // most likely identical top and bottom frustum extents. - // Use the left camera for these values. - const near = projL[ 14 ] / ( projL[ 10 ] - 1 ); - const far = projL[ 14 ] / ( projL[ 10 ] + 1 ); - const topFov = ( projL[ 9 ] + 1 ) / projL[ 5 ]; - const bottomFov = ( projL[ 9 ] - 1 ) / projL[ 5 ]; - - const leftFov = ( projL[ 8 ] - 1 ) / projL[ 0 ]; - const rightFov = ( projR[ 8 ] + 1 ) / projR[ 0 ]; - const left = near * leftFov; - const right = near * rightFov; - - // Calculate the new camera's position offset from the - // left camera. xOffset should be roughly half `ipd`. - const zOffset = ipd / ( - leftFov + rightFov ); - const xOffset = zOffset * - leftFov; - - // TODO: Better way to apply this offset? - cameraL.matrixWorld.decompose( camera.position, camera.quaternion, camera.scale ); - camera.translateX( xOffset ); - camera.translateZ( zOffset ); - camera.matrixWorld.compose( camera.position, camera.quaternion, camera.scale ); - camera.matrixWorldInverse.copy( camera.matrixWorld ).invert(); - - // Find the union of the frustum values of the cameras and scale - // the values so that the near plane's position does not change in world space, - // although must now be relative to the new union camera. - const near2 = near + zOffset; - const far2 = far + zOffset; - const left2 = left - xOffset; - const right2 = right + ( ipd - xOffset ); - const top2 = topFov * far / far2 * near2; - const bottom2 = bottomFov * far / far2 * near2; - - camera.projectionMatrix.makePerspective( left2, right2, top2, bottom2, near2, far2 ); - camera.projectionMatrixInverse.copy( camera.projectionMatrix ).invert(); - - } - - function updateCamera( camera, parent ) { - - if ( parent === null ) { - - camera.matrixWorld.copy( camera.matrix ); - - } else { - - camera.matrixWorld.multiplyMatrices( parent.matrixWorld, camera.matrix ); - - } - - camera.matrixWorldInverse.copy( camera.matrixWorld ).invert(); - - } - - this.updateCameraXR = function ( camera ) { - - if ( session === null ) return camera; - - if ( userCamera ) { - - camera = userCamera; - - } - - cameraXR.near = cameraR.near = cameraL.near = camera.near; - cameraXR.far = cameraR.far = cameraL.far = camera.far; - - if ( _currentDepthNear !== cameraXR.near || _currentDepthFar !== cameraXR.far ) { - - // Note that the new renderState won't apply until the next frame. See #18320 - - session.updateRenderState( { - depthNear: cameraXR.near, - depthFar: cameraXR.far - } ); - - _currentDepthNear = cameraXR.near; - _currentDepthFar = cameraXR.far; - - } - - const parent = camera.parent; - const cameras = cameraXR.cameras; - - updateCamera( cameraXR, parent ); - - for ( let i = 0; i < cameras.length; i ++ ) { - - updateCamera( cameras[ i ], parent ); - - } - - // update projection matrix for proper view frustum culling - - if ( cameras.length === 2 ) { - - setProjectionFromUnion( cameraXR, cameraL, cameraR ); - - } else { - - // assume single camera setup (AR) - - cameraXR.projectionMatrix.copy( cameraL.projectionMatrix ); - - } - - // update user camera and its children - - if ( userCamera ) { - - updateUserCamera( cameraXR, parent ); - - } - - return cameraXR; - - }; - - function updateUserCamera( cameraXR, parent ) { - - const camera = userCamera; - - if ( parent === null ) { - - camera.matrix.copy( cameraXR.matrixWorld ); - - } else { - - camera.matrix.copy( parent.matrixWorld ); - camera.matrix.invert(); - camera.matrix.multiply( cameraXR.matrixWorld ); - - } - - camera.matrix.decompose( camera.position, camera.quaternion, camera.scale ); - camera.updateMatrixWorld( true ); - - const children = camera.children; - - for ( let i = 0, l = children.length; i < l; i ++ ) { - - children[ i ].updateMatrixWorld( true ); - - } - - camera.projectionMatrix.copy( cameraXR.projectionMatrix ); - camera.projectionMatrixInverse.copy( cameraXR.projectionMatrixInverse ); - - if ( camera.isPerspectiveCamera ) { - - camera.fov = RAD2DEG * 2 * Math.atan( 1 / camera.projectionMatrix.elements[ 5 ] ); - camera.zoom = 1; - - } - - } - - this.getFoveation = function () { - - if ( glProjLayer === null && glBaseLayer === null ) { - - return undefined; - - } - - return foveation; - - }; - - this.setFoveation = function ( value ) { - - // 0 = no foveation = full resolution - // 1 = maximum foveation = the edges render at lower resolution - - foveation = value; - - if ( glProjLayer !== null ) { - - glProjLayer.fixedFoveation = value; - - } - - if ( glBaseLayer !== null && glBaseLayer.fixedFoveation !== undefined ) { - - glBaseLayer.fixedFoveation = value; - - } - - }; - - this.getPlanes = function () { - - return planes; - - }; - - // Animation Loop - - let onAnimationFrameCallback = null; - - function onAnimationFrame( time, frame ) { - - pose = frame.getViewerPose( customReferenceSpace || referenceSpace ); - xrFrame = frame; - - if ( pose !== null ) { - - const views = pose.views; - - if ( glBaseLayer !== null ) { - - renderer.setRenderTargetFramebuffer( newRenderTarget, glBaseLayer.framebuffer ); - renderer.setRenderTarget( newRenderTarget ); - - } - - let cameraXRNeedsUpdate = false; - - // check if it's necessary to rebuild cameraXR's camera list - - if ( views.length !== cameraXR.cameras.length ) { - - cameraXR.cameras.length = 0; - cameraXRNeedsUpdate = true; - - } - - for ( let i = 0; i < views.length; i ++ ) { - - const view = views[ i ]; - - let viewport = null; - - if ( glBaseLayer !== null ) { - - viewport = glBaseLayer.getViewport( view ); - - } else { - - const glSubImage = glBinding.getViewSubImage( glProjLayer, view ); - viewport = glSubImage.viewport; - - // For side-by-side projection, we only produce a single texture for both eyes. - if ( i === 0 ) { - - renderer.setRenderTargetTextures( - newRenderTarget, - glSubImage.colorTexture, - glProjLayer.ignoreDepthValues ? undefined : glSubImage.depthStencilTexture ); - - renderer.setRenderTarget( newRenderTarget ); - - } - - } - - let camera = cameras[ i ]; - - if ( camera === undefined ) { - - camera = new PerspectiveCamera(); - camera.layers.enable( i ); - camera.viewport = new Vector4(); - cameras[ i ] = camera; - - } - - camera.matrix.fromArray( view.transform.matrix ); - camera.matrix.decompose( camera.position, camera.quaternion, camera.scale ); - camera.projectionMatrix.fromArray( view.projectionMatrix ); - camera.projectionMatrixInverse.copy( camera.projectionMatrix ).invert(); - camera.viewport.set( viewport.x, viewport.y, viewport.width, viewport.height ); - - if ( i === 0 ) { - - cameraXR.matrix.copy( camera.matrix ); - cameraXR.matrix.decompose( cameraXR.position, cameraXR.quaternion, cameraXR.scale ); - - } - - if ( cameraXRNeedsUpdate === true ) { - - cameraXR.cameras.push( camera ); - - } - - } - - } - - // - - for ( let i = 0; i < controllers.length; i ++ ) { - - const inputSource = controllerInputSources[ i ]; - const controller = controllers[ i ]; - - if ( inputSource !== null && controller !== undefined ) { - - controller.update( inputSource, frame, customReferenceSpace || referenceSpace ); - - } - - } - - if ( onAnimationFrameCallback ) onAnimationFrameCallback( time, frame ); - - if ( frame.detectedPlanes ) { - - scope.dispatchEvent( { type: 'planesdetected', data: frame.detectedPlanes } ); - - let planesToRemove = null; - - for ( const plane of planes ) { - - if ( ! frame.detectedPlanes.has( plane ) ) { - - if ( planesToRemove === null ) { - - planesToRemove = []; - - } - - planesToRemove.push( plane ); - - } - - } - - if ( planesToRemove !== null ) { - - for ( const plane of planesToRemove ) { - - planes.delete( plane ); - planesLastChangedTimes.delete( plane ); - scope.dispatchEvent( { type: 'planeremoved', data: plane } ); - - } - - } - - for ( const plane of frame.detectedPlanes ) { - - if ( ! planes.has( plane ) ) { - - planes.add( plane ); - planesLastChangedTimes.set( plane, frame.lastChangedTime ); - scope.dispatchEvent( { type: 'planeadded', data: plane } ); - - } else { - - const lastKnownTime = planesLastChangedTimes.get( plane ); - - if ( plane.lastChangedTime > lastKnownTime ) { - - planesLastChangedTimes.set( plane, plane.lastChangedTime ); - scope.dispatchEvent( { type: 'planechanged', data: plane } ); - - } - - } - - } - - } - - xrFrame = null; - - } - - const animation = new WebGLAnimation(); - - animation.setAnimationLoop( onAnimationFrame ); - - this.setAnimationLoop = function ( callback ) { - - onAnimationFrameCallback = callback; - - }; - - this.dispose = function () {}; - - } - - } - - function WebGLMaterials( renderer, properties ) { - - function refreshTransformUniform( map, uniform ) { - - if ( map.matrixAutoUpdate === true ) { - - map.updateMatrix(); - - } - - uniform.value.copy( map.matrix ); - - } - - function refreshFogUniforms( uniforms, fog ) { - - fog.color.getRGB( uniforms.fogColor.value, getUnlitUniformColorSpace( renderer ) ); - - if ( fog.isFog ) { - - uniforms.fogNear.value = fog.near; - uniforms.fogFar.value = fog.far; - - } else if ( fog.isFogExp2 ) { - - uniforms.fogDensity.value = fog.density; - - } - - } - - function refreshMaterialUniforms( uniforms, material, pixelRatio, height, transmissionRenderTarget ) { - - if ( material.isMeshBasicMaterial ) { - - refreshUniformsCommon( uniforms, material ); - - } else if ( material.isMeshLambertMaterial ) { - - refreshUniformsCommon( uniforms, material ); - - } else if ( material.isMeshToonMaterial ) { - - refreshUniformsCommon( uniforms, material ); - refreshUniformsToon( uniforms, material ); - - } else if ( material.isMeshPhongMaterial ) { - - refreshUniformsCommon( uniforms, material ); - refreshUniformsPhong( uniforms, material ); - - } else if ( material.isMeshStandardMaterial ) { - - refreshUniformsCommon( uniforms, material ); - refreshUniformsStandard( uniforms, material ); - - if ( material.isMeshPhysicalMaterial ) { - - refreshUniformsPhysical( uniforms, material, transmissionRenderTarget ); - - } - - } else if ( material.isMeshMatcapMaterial ) { - - refreshUniformsCommon( uniforms, material ); - refreshUniformsMatcap( uniforms, material ); - - } else if ( material.isMeshDepthMaterial ) { - - refreshUniformsCommon( uniforms, material ); - - } else if ( material.isMeshDistanceMaterial ) { - - refreshUniformsCommon( uniforms, material ); - refreshUniformsDistance( uniforms, material ); - - } else if ( material.isMeshNormalMaterial ) { - - refreshUniformsCommon( uniforms, material ); - - } else if ( material.isLineBasicMaterial ) { - - refreshUniformsLine( uniforms, material ); - - if ( material.isLineDashedMaterial ) { - - refreshUniformsDash( uniforms, material ); - - } - - } else if ( material.isPointsMaterial ) { - - refreshUniformsPoints( uniforms, material, pixelRatio, height ); - - } else if ( material.isSpriteMaterial ) { - - refreshUniformsSprites( uniforms, material ); - - } else if ( material.isShadowMaterial ) { - - uniforms.color.value.copy( material.color ); - uniforms.opacity.value = material.opacity; - - } else if ( material.isShaderMaterial ) { - - material.uniformsNeedUpdate = false; // #15581 - - } - - } - - function refreshUniformsCommon( uniforms, material ) { - - uniforms.opacity.value = material.opacity; - - if ( material.color ) { - - uniforms.diffuse.value.copy( material.color ); - - } - - if ( material.emissive ) { - - uniforms.emissive.value.copy( material.emissive ).multiplyScalar( material.emissiveIntensity ); - - } - - if ( material.map ) { - - uniforms.map.value = material.map; - - refreshTransformUniform( material.map, uniforms.mapTransform ); - - } - - if ( material.alphaMap ) { - - uniforms.alphaMap.value = material.alphaMap; - - refreshTransformUniform( material.alphaMap, uniforms.alphaMapTransform ); - - } - - if ( material.bumpMap ) { - - uniforms.bumpMap.value = material.bumpMap; - - refreshTransformUniform( material.bumpMap, uniforms.bumpMapTransform ); - - uniforms.bumpScale.value = material.bumpScale; - - if ( material.side === BackSide ) { - - uniforms.bumpScale.value *= - 1; - - } - - } - - if ( material.normalMap ) { - - uniforms.normalMap.value = material.normalMap; - - refreshTransformUniform( material.normalMap, uniforms.normalMapTransform ); - - uniforms.normalScale.value.copy( material.normalScale ); - - if ( material.side === BackSide ) { - - uniforms.normalScale.value.negate(); - - } - - } - - if ( material.displacementMap ) { - - uniforms.displacementMap.value = material.displacementMap; - - refreshTransformUniform( material.displacementMap, uniforms.displacementMapTransform ); - - uniforms.displacementScale.value = material.displacementScale; - uniforms.displacementBias.value = material.displacementBias; - - } - - if ( material.emissiveMap ) { - - uniforms.emissiveMap.value = material.emissiveMap; - - refreshTransformUniform( material.emissiveMap, uniforms.emissiveMapTransform ); - - } - - if ( material.specularMap ) { - - uniforms.specularMap.value = material.specularMap; - - refreshTransformUniform( material.specularMap, uniforms.specularMapTransform ); - - } - - if ( material.alphaTest > 0 ) { - - uniforms.alphaTest.value = material.alphaTest; - - } - - const envMap = properties.get( material ).envMap; - - if ( envMap ) { - - uniforms.envMap.value = envMap; - - uniforms.flipEnvMap.value = ( envMap.isCubeTexture && envMap.isRenderTargetTexture === false ) ? - 1 : 1; - - uniforms.reflectivity.value = material.reflectivity; - uniforms.ior.value = material.ior; - uniforms.refractionRatio.value = material.refractionRatio; - - } - - if ( material.lightMap ) { - - uniforms.lightMap.value = material.lightMap; - - // artist-friendly light intensity scaling factor - const scaleFactor = ( renderer.useLegacyLights === true ) ? Math.PI : 1; - - uniforms.lightMapIntensity.value = material.lightMapIntensity * scaleFactor; - - refreshTransformUniform( material.lightMap, uniforms.lightMapTransform ); - - } - - if ( material.aoMap ) { - - uniforms.aoMap.value = material.aoMap; - uniforms.aoMapIntensity.value = material.aoMapIntensity; - - refreshTransformUniform( material.aoMap, uniforms.aoMapTransform ); - - } - - } - - function refreshUniformsLine( uniforms, material ) { - - uniforms.diffuse.value.copy( material.color ); - uniforms.opacity.value = material.opacity; - - if ( material.map ) { - - uniforms.map.value = material.map; - - refreshTransformUniform( material.map, uniforms.mapTransform ); - - } - - } - - function refreshUniformsDash( uniforms, material ) { - - uniforms.dashSize.value = material.dashSize; - uniforms.totalSize.value = material.dashSize + material.gapSize; - uniforms.scale.value = material.scale; - - } - - function refreshUniformsPoints( uniforms, material, pixelRatio, height ) { - - uniforms.diffuse.value.copy( material.color ); - uniforms.opacity.value = material.opacity; - uniforms.size.value = material.size * pixelRatio; - uniforms.scale.value = height * 0.5; - - if ( material.map ) { - - uniforms.map.value = material.map; - - refreshTransformUniform( material.map, uniforms.uvTransform ); - - } - - if ( material.alphaMap ) { - - uniforms.alphaMap.value = material.alphaMap; - - } - - if ( material.alphaTest > 0 ) { - - uniforms.alphaTest.value = material.alphaTest; - - } - - } - - function refreshUniformsSprites( uniforms, material ) { - - uniforms.diffuse.value.copy( material.color ); - uniforms.opacity.value = material.opacity; - uniforms.rotation.value = material.rotation; - - if ( material.map ) { - - uniforms.map.value = material.map; - - refreshTransformUniform( material.map, uniforms.mapTransform ); - - } - - if ( material.alphaMap ) { - - uniforms.alphaMap.value = material.alphaMap; - - } - - if ( material.alphaTest > 0 ) { - - uniforms.alphaTest.value = material.alphaTest; - - } - - } - - function refreshUniformsPhong( uniforms, material ) { - - uniforms.specular.value.copy( material.specular ); - uniforms.shininess.value = Math.max( material.shininess, 1e-4 ); // to prevent pow( 0.0, 0.0 ) - - } - - function refreshUniformsToon( uniforms, material ) { - - if ( material.gradientMap ) { - - uniforms.gradientMap.value = material.gradientMap; - - } - - } - - function refreshUniformsStandard( uniforms, material ) { - - uniforms.metalness.value = material.metalness; - - if ( material.metalnessMap ) { - - uniforms.metalnessMap.value = material.metalnessMap; - - refreshTransformUniform( material.metalnessMap, uniforms.metalnessMapTransform ); - - } - - uniforms.roughness.value = material.roughness; - - if ( material.roughnessMap ) { - - uniforms.roughnessMap.value = material.roughnessMap; - - refreshTransformUniform( material.roughnessMap, uniforms.roughnessMapTransform ); - - } - - const envMap = properties.get( material ).envMap; - - if ( envMap ) { - - //uniforms.envMap.value = material.envMap; // part of uniforms common - uniforms.envMapIntensity.value = material.envMapIntensity; - - } - - } - - function refreshUniformsPhysical( uniforms, material, transmissionRenderTarget ) { - - uniforms.ior.value = material.ior; // also part of uniforms common - - if ( material.sheen > 0 ) { - - uniforms.sheenColor.value.copy( material.sheenColor ).multiplyScalar( material.sheen ); - - uniforms.sheenRoughness.value = material.sheenRoughness; - - if ( material.sheenColorMap ) { - - uniforms.sheenColorMap.value = material.sheenColorMap; - - refreshTransformUniform( material.sheenColorMap, uniforms.sheenColorMapTransform ); - - } - - if ( material.sheenRoughnessMap ) { - - uniforms.sheenRoughnessMap.value = material.sheenRoughnessMap; - - refreshTransformUniform( material.sheenRoughnessMap, uniforms.sheenRoughnessMapTransform ); - - } - - } - - if ( material.clearcoat > 0 ) { - - uniforms.clearcoat.value = material.clearcoat; - uniforms.clearcoatRoughness.value = material.clearcoatRoughness; - - if ( material.clearcoatMap ) { - - uniforms.clearcoatMap.value = material.clearcoatMap; - - refreshTransformUniform( material.clearcoatMap, uniforms.clearcoatMapTransform ); - - } - - if ( material.clearcoatRoughnessMap ) { - - uniforms.clearcoatRoughnessMap.value = material.clearcoatRoughnessMap; - - refreshTransformUniform( material.clearcoatRoughnessMap, uniforms.clearcoatRoughnessMapTransform ); - - } - - if ( material.clearcoatNormalMap ) { - - uniforms.clearcoatNormalMap.value = material.clearcoatNormalMap; - - refreshTransformUniform( material.clearcoatNormalMap, uniforms.clearcoatNormalMapTransform ); - - uniforms.clearcoatNormalScale.value.copy( material.clearcoatNormalScale ); - - if ( material.side === BackSide ) { - - uniforms.clearcoatNormalScale.value.negate(); - - } - - } - - } - - if ( material.iridescence > 0 ) { - - uniforms.iridescence.value = material.iridescence; - uniforms.iridescenceIOR.value = material.iridescenceIOR; - uniforms.iridescenceThicknessMinimum.value = material.iridescenceThicknessRange[ 0 ]; - uniforms.iridescenceThicknessMaximum.value = material.iridescenceThicknessRange[ 1 ]; - - if ( material.iridescenceMap ) { - - uniforms.iridescenceMap.value = material.iridescenceMap; - - refreshTransformUniform( material.iridescenceMap, uniforms.iridescenceMapTransform ); - - } - - if ( material.iridescenceThicknessMap ) { - - uniforms.iridescenceThicknessMap.value = material.iridescenceThicknessMap; - - refreshTransformUniform( material.iridescenceThicknessMap, uniforms.iridescenceThicknessMapTransform ); - - } - - } - - if ( material.transmission > 0 ) { - - uniforms.transmission.value = material.transmission; - uniforms.transmissionSamplerMap.value = transmissionRenderTarget.texture; - uniforms.transmissionSamplerSize.value.set( transmissionRenderTarget.width, transmissionRenderTarget.height ); - - if ( material.transmissionMap ) { - - uniforms.transmissionMap.value = material.transmissionMap; - - refreshTransformUniform( material.transmissionMap, uniforms.transmissionMapTransform ); - - } - - uniforms.thickness.value = material.thickness; - - if ( material.thicknessMap ) { - - uniforms.thicknessMap.value = material.thicknessMap; - - refreshTransformUniform( material.thicknessMap, uniforms.thicknessMapTransform ); - - } - - uniforms.attenuationDistance.value = material.attenuationDistance; - uniforms.attenuationColor.value.copy( material.attenuationColor ); - - } - - if ( material.anisotropy > 0 ) { - - uniforms.anisotropyVector.value.set( material.anisotropy * Math.cos( material.anisotropyRotation ), material.anisotropy * Math.sin( material.anisotropyRotation ) ); - - if ( material.anisotropyMap ) { - - uniforms.anisotropyMap.value = material.anisotropyMap; - - } - - } - - uniforms.specularIntensity.value = material.specularIntensity; - uniforms.specularColor.value.copy( material.specularColor ); - - if ( material.specularColorMap ) { - - uniforms.specularColorMap.value = material.specularColorMap; - - refreshTransformUniform( material.specularColorMap, uniforms.specularColorMapTransform ); - - } - - if ( material.specularIntensityMap ) { - - uniforms.specularIntensityMap.value = material.specularIntensityMap; - - refreshTransformUniform( material.specularIntensityMap, uniforms.specularIntensityMapTransform ); - - } - - } - - function refreshUniformsMatcap( uniforms, material ) { - - if ( material.matcap ) { - - uniforms.matcap.value = material.matcap; - - } - - } - - function refreshUniformsDistance( uniforms, material ) { - - const light = properties.get( material ).light; - - uniforms.referencePosition.value.setFromMatrixPosition( light.matrixWorld ); - uniforms.nearDistance.value = light.shadow.camera.near; - uniforms.farDistance.value = light.shadow.camera.far; - - } - - return { - refreshFogUniforms: refreshFogUniforms, - refreshMaterialUniforms: refreshMaterialUniforms - }; - - } - - function WebGLUniformsGroups( gl, info, capabilities, state ) { - - let buffers = {}; - let updateList = {}; - let allocatedBindingPoints = []; - - const maxBindingPoints = ( capabilities.isWebGL2 ) ? gl.getParameter( gl.MAX_UNIFORM_BUFFER_BINDINGS ) : 0; // binding points are global whereas block indices are per shader program - - function bind( uniformsGroup, program ) { - - const webglProgram = program.program; - state.uniformBlockBinding( uniformsGroup, webglProgram ); - - } - - function update( uniformsGroup, program ) { - - let buffer = buffers[ uniformsGroup.id ]; - - if ( buffer === undefined ) { - - prepareUniformsGroup( uniformsGroup ); - - buffer = createBuffer( uniformsGroup ); - buffers[ uniformsGroup.id ] = buffer; - - uniformsGroup.addEventListener( 'dispose', onUniformsGroupsDispose ); - - } - - // ensure to update the binding points/block indices mapping for this program - - const webglProgram = program.program; - state.updateUBOMapping( uniformsGroup, webglProgram ); - - // update UBO once per frame - - const frame = info.render.frame; - - if ( updateList[ uniformsGroup.id ] !== frame ) { - - updateBufferData( uniformsGroup ); - - updateList[ uniformsGroup.id ] = frame; - - } - - } - - function createBuffer( uniformsGroup ) { - - // the setup of an UBO is independent of a particular shader program but global - - const bindingPointIndex = allocateBindingPointIndex(); - uniformsGroup.__bindingPointIndex = bindingPointIndex; - - const buffer = gl.createBuffer(); - const size = uniformsGroup.__size; - const usage = uniformsGroup.usage; - - gl.bindBuffer( gl.UNIFORM_BUFFER, buffer ); - gl.bufferData( gl.UNIFORM_BUFFER, size, usage ); - gl.bindBuffer( gl.UNIFORM_BUFFER, null ); - gl.bindBufferBase( gl.UNIFORM_BUFFER, bindingPointIndex, buffer ); - - return buffer; - - } - - function allocateBindingPointIndex() { - - for ( let i = 0; i < maxBindingPoints; i ++ ) { - - if ( allocatedBindingPoints.indexOf( i ) === - 1 ) { - - allocatedBindingPoints.push( i ); - return i; - - } - - } - - console.error( 'THREE.WebGLRenderer: Maximum number of simultaneously usable uniforms groups reached.' ); - - return 0; - - } - - function updateBufferData( uniformsGroup ) { - - const buffer = buffers[ uniformsGroup.id ]; - const uniforms = uniformsGroup.uniforms; - const cache = uniformsGroup.__cache; - - gl.bindBuffer( gl.UNIFORM_BUFFER, buffer ); - - for ( let i = 0, il = uniforms.length; i < il; i ++ ) { - - const uniform = uniforms[ i ]; - - // partly update the buffer if necessary - - if ( hasUniformChanged( uniform, i, cache ) === true ) { - - const offset = uniform.__offset; - - const values = Array.isArray( uniform.value ) ? uniform.value : [ uniform.value ]; - - let arrayOffset = 0; - - for ( let i = 0; i < values.length; i ++ ) { - - const value = values[ i ]; - - const info = getUniformSize( value ); - - if ( typeof value === 'number' ) { - - uniform.__data[ 0 ] = value; - gl.bufferSubData( gl.UNIFORM_BUFFER, offset + arrayOffset, uniform.__data ); - - } else if ( value.isMatrix3 ) { - - // manually converting 3x3 to 3x4 - - uniform.__data[ 0 ] = value.elements[ 0 ]; - uniform.__data[ 1 ] = value.elements[ 1 ]; - uniform.__data[ 2 ] = value.elements[ 2 ]; - uniform.__data[ 3 ] = value.elements[ 0 ]; - uniform.__data[ 4 ] = value.elements[ 3 ]; - uniform.__data[ 5 ] = value.elements[ 4 ]; - uniform.__data[ 6 ] = value.elements[ 5 ]; - uniform.__data[ 7 ] = value.elements[ 0 ]; - uniform.__data[ 8 ] = value.elements[ 6 ]; - uniform.__data[ 9 ] = value.elements[ 7 ]; - uniform.__data[ 10 ] = value.elements[ 8 ]; - uniform.__data[ 11 ] = value.elements[ 0 ]; - - } else { - - value.toArray( uniform.__data, arrayOffset ); - - arrayOffset += info.storage / Float32Array.BYTES_PER_ELEMENT; - - } - - } - - gl.bufferSubData( gl.UNIFORM_BUFFER, offset, uniform.__data ); - - } - - } - - gl.bindBuffer( gl.UNIFORM_BUFFER, null ); - - } - - function hasUniformChanged( uniform, index, cache ) { - - const value = uniform.value; - - if ( cache[ index ] === undefined ) { - - // cache entry does not exist so far - - if ( typeof value === 'number' ) { - - cache[ index ] = value; - - } else { - - const values = Array.isArray( value ) ? value : [ value ]; - - const tempValues = []; - - for ( let i = 0; i < values.length; i ++ ) { - - tempValues.push( values[ i ].clone() ); - - } - - cache[ index ] = tempValues; - - } - - return true; - - } else { - - // compare current value with cached entry - - if ( typeof value === 'number' ) { - - if ( cache[ index ] !== value ) { - - cache[ index ] = value; - return true; - - } - - } else { - - const cachedObjects = Array.isArray( cache[ index ] ) ? cache[ index ] : [ cache[ index ] ]; - const values = Array.isArray( value ) ? value : [ value ]; - - for ( let i = 0; i < cachedObjects.length; i ++ ) { - - const cachedObject = cachedObjects[ i ]; - - if ( cachedObject.equals( values[ i ] ) === false ) { - - cachedObject.copy( values[ i ] ); - return true; - - } - - } - - } - - } - - return false; - - } - - function prepareUniformsGroup( uniformsGroup ) { - - // determine total buffer size according to the STD140 layout - // Hint: STD140 is the only supported layout in WebGL 2 - - const uniforms = uniformsGroup.uniforms; - - let offset = 0; // global buffer offset in bytes - const chunkSize = 16; // size of a chunk in bytes - let chunkOffset = 0; // offset within a single chunk in bytes - - for ( let i = 0, l = uniforms.length; i < l; i ++ ) { - - const uniform = uniforms[ i ]; - - const infos = { - boundary: 0, // bytes - storage: 0 // bytes - }; - - const values = Array.isArray( uniform.value ) ? uniform.value : [ uniform.value ]; - - for ( let j = 0, jl = values.length; j < jl; j ++ ) { - - const value = values[ j ]; - - const info = getUniformSize( value ); - - infos.boundary += info.boundary; - infos.storage += info.storage; - - } - - // the following two properties will be used for partial buffer updates - - uniform.__data = new Float32Array( infos.storage / Float32Array.BYTES_PER_ELEMENT ); - uniform.__offset = offset; - - // - - if ( i > 0 ) { - - chunkOffset = offset % chunkSize; - - const remainingSizeInChunk = chunkSize - chunkOffset; - - // check for chunk overflow - - if ( chunkOffset !== 0 && ( remainingSizeInChunk - infos.boundary ) < 0 ) { - - // add padding and adjust offset - - offset += ( chunkSize - chunkOffset ); - uniform.__offset = offset; - - } - - } - - offset += infos.storage; - - } - - // ensure correct final padding - - chunkOffset = offset % chunkSize; - - if ( chunkOffset > 0 ) offset += ( chunkSize - chunkOffset ); - - // - - uniformsGroup.__size = offset; - uniformsGroup.__cache = {}; - - return this; - - } - - function getUniformSize( value ) { - - const info = { - boundary: 0, // bytes - storage: 0 // bytes - }; - - // determine sizes according to STD140 - - if ( typeof value === 'number' ) { - - // float/int - - info.boundary = 4; - info.storage = 4; - - } else if ( value.isVector2 ) { - - // vec2 - - info.boundary = 8; - info.storage = 8; - - } else if ( value.isVector3 || value.isColor ) { - - // vec3 - - info.boundary = 16; - info.storage = 12; // evil: vec3 must start on a 16-byte boundary but it only consumes 12 bytes - - } else if ( value.isVector4 ) { - - // vec4 - - info.boundary = 16; - info.storage = 16; - - } else if ( value.isMatrix3 ) { - - // mat3 (in STD140 a 3x3 matrix is represented as 3x4) - - info.boundary = 48; - info.storage = 48; - - } else if ( value.isMatrix4 ) { - - // mat4 - - info.boundary = 64; - info.storage = 64; - - } else if ( value.isTexture ) { - - console.warn( 'THREE.WebGLRenderer: Texture samplers can not be part of an uniforms group.' ); - - } else { - - console.warn( 'THREE.WebGLRenderer: Unsupported uniform value type.', value ); - - } - - return info; - - } - - function onUniformsGroupsDispose( event ) { - - const uniformsGroup = event.target; - - uniformsGroup.removeEventListener( 'dispose', onUniformsGroupsDispose ); - - const index = allocatedBindingPoints.indexOf( uniformsGroup.__bindingPointIndex ); - allocatedBindingPoints.splice( index, 1 ); - - gl.deleteBuffer( buffers[ uniformsGroup.id ] ); - - delete buffers[ uniformsGroup.id ]; - delete updateList[ uniformsGroup.id ]; - - } - - function dispose() { - - for ( const id in buffers ) { - - gl.deleteBuffer( buffers[ id ] ); - - } - - allocatedBindingPoints = []; - buffers = {}; - updateList = {}; - - } - - return { - - bind: bind, - update: update, - - dispose: dispose - - }; - - } - - function createCanvasElement() { - - const canvas = createElementNS( 'canvas' ); - canvas.style.display = 'block'; - return canvas; - - } - - class WebGLRenderer { - - constructor( parameters = {} ) { - - const { - canvas = createCanvasElement(), - context = null, - depth = true, - stencil = true, - alpha = false, - antialias = false, - premultipliedAlpha = true, - preserveDrawingBuffer = false, - powerPreference = 'default', - failIfMajorPerformanceCaveat = false, - } = parameters; - - this.isWebGLRenderer = true; - - let _alpha; - - if ( context !== null ) { - - _alpha = context.getContextAttributes().alpha; - - } else { - - _alpha = alpha; - - } - - const uintClearColor = new Uint32Array( 4 ); - const intClearColor = new Int32Array( 4 ); - - let currentRenderList = null; - let currentRenderState = null; - - // render() can be called from within a callback triggered by another render. - // We track this so that the nested render call gets its list and state isolated from the parent render call. - - const renderListStack = []; - const renderStateStack = []; - - // public properties - - this.domElement = canvas; - - // Debug configuration container - this.debug = { - - /** - * Enables error checking and reporting when shader programs are being compiled - * @type {boolean} - */ - checkShaderErrors: true, - /** - * Callback for custom error reporting. - * @type {?Function} - */ - onShaderError: null - }; - - // clearing - - this.autoClear = true; - this.autoClearColor = true; - this.autoClearDepth = true; - this.autoClearStencil = true; - - // scene graph - - this.sortObjects = true; - - // user-defined clipping - - this.clippingPlanes = []; - this.localClippingEnabled = false; - - // physically based shading - - this.outputColorSpace = SRGBColorSpace; - - // physical lights - - this.useLegacyLights = true; - - // tone mapping - - this.toneMapping = NoToneMapping; - this.toneMappingExposure = 1.0; - - // internal properties - - const _this = this; - - let _isContextLost = false; - - // internal state cache - - let _currentActiveCubeFace = 0; - let _currentActiveMipmapLevel = 0; - let _currentRenderTarget = null; - let _currentMaterialId = - 1; - - let _currentCamera = null; - - const _currentViewport = new Vector4(); - const _currentScissor = new Vector4(); - let _currentScissorTest = null; - - const _currentClearColor = new Color( 0x000000 ); - let _currentClearAlpha = 0; - - // - - let _width = canvas.width; - let _height = canvas.height; - - let _pixelRatio = 1; - let _opaqueSort = null; - let _transparentSort = null; - - const _viewport = new Vector4( 0, 0, _width, _height ); - const _scissor = new Vector4( 0, 0, _width, _height ); - let _scissorTest = false; - - // frustum - - const _frustum = new Frustum(); - - // clipping - - let _clippingEnabled = false; - let _localClippingEnabled = false; - - // transmission - - let _transmissionRenderTarget = null; - - // camera matrices cache - - const _projScreenMatrix = new Matrix4(); - - const _vector3 = new Vector3(); - - const _emptyScene = { background: null, fog: null, environment: null, overrideMaterial: null, isScene: true }; - - function getTargetPixelRatio() { - - return _currentRenderTarget === null ? _pixelRatio : 1; - - } - - // initialize - - let _gl = context; - - function getContext( contextNames, contextAttributes ) { - - for ( let i = 0; i < contextNames.length; i ++ ) { - - const contextName = contextNames[ i ]; - const context = canvas.getContext( contextName, contextAttributes ); - if ( context !== null ) return context; - - } - - return null; - - } - - try { - - const contextAttributes = { - alpha: true, - depth, - stencil, - antialias, - premultipliedAlpha, - preserveDrawingBuffer, - powerPreference, - failIfMajorPerformanceCaveat, - }; - - // OffscreenCanvas does not have setAttribute, see #22811 - if ( 'setAttribute' in canvas ) canvas.setAttribute( 'data-engine', `three.js r${REVISION}` ); - - // event listeners must be registered before WebGL context is created, see #12753 - canvas.addEventListener( 'webglcontextlost', onContextLost, false ); - canvas.addEventListener( 'webglcontextrestored', onContextRestore, false ); - canvas.addEventListener( 'webglcontextcreationerror', onContextCreationError, false ); - - if ( _gl === null ) { - - const contextNames = [ 'webgl2', 'webgl', 'experimental-webgl' ]; - - if ( _this.isWebGL1Renderer === true ) { - - contextNames.shift(); - - } - - _gl = getContext( contextNames, contextAttributes ); - - if ( _gl === null ) { - - if ( getContext( contextNames ) ) { - - throw new Error( 'Error creating WebGL context with your selected attributes.' ); - - } else { - - throw new Error( 'Error creating WebGL context.' ); - - } - - } - - } - - if ( _gl instanceof WebGLRenderingContext ) { // @deprecated, r153 - - console.warn( 'THREE.WebGLRenderer: WebGL 1 support was deprecated in r153 and will be removed in r163.' ); - - } - - // Some experimental-webgl implementations do not have getShaderPrecisionFormat - - if ( _gl.getShaderPrecisionFormat === undefined ) { - - _gl.getShaderPrecisionFormat = function () { - - return { 'rangeMin': 1, 'rangeMax': 1, 'precision': 1 }; - - }; - - } - - } catch ( error ) { - - console.error( 'THREE.WebGLRenderer: ' + error.message ); - throw error; - - } - - let extensions, capabilities, state, info; - let properties, textures, cubemaps, cubeuvmaps, attributes, geometries, objects; - let programCache, materials, renderLists, renderStates, clipping, shadowMap; - - let background, morphtargets, bufferRenderer, indexedBufferRenderer; - - let utils, bindingStates, uniformsGroups; - - function initGLContext() { - - extensions = new WebGLExtensions( _gl ); - - capabilities = new WebGLCapabilities( _gl, extensions, parameters ); - - extensions.init( capabilities ); - - utils = new WebGLUtils( _gl, extensions, capabilities ); - - state = new WebGLState( _gl, extensions, capabilities ); - - info = new WebGLInfo( _gl ); - properties = new WebGLProperties(); - textures = new WebGLTextures( _gl, extensions, state, properties, capabilities, utils, info ); - cubemaps = new WebGLCubeMaps( _this ); - cubeuvmaps = new WebGLCubeUVMaps( _this ); - attributes = new WebGLAttributes( _gl, capabilities ); - bindingStates = new WebGLBindingStates( _gl, extensions, attributes, capabilities ); - geometries = new WebGLGeometries( _gl, attributes, info, bindingStates ); - objects = new WebGLObjects( _gl, geometries, attributes, info ); - morphtargets = new WebGLMorphtargets( _gl, capabilities, textures ); - clipping = new WebGLClipping( properties ); - programCache = new WebGLPrograms( _this, cubemaps, cubeuvmaps, extensions, capabilities, bindingStates, clipping ); - materials = new WebGLMaterials( _this, properties ); - renderLists = new WebGLRenderLists(); - renderStates = new WebGLRenderStates( extensions, capabilities ); - background = new WebGLBackground( _this, cubemaps, cubeuvmaps, state, objects, _alpha, premultipliedAlpha ); - shadowMap = new WebGLShadowMap( _this, objects, capabilities ); - uniformsGroups = new WebGLUniformsGroups( _gl, info, capabilities, state ); - - bufferRenderer = new WebGLBufferRenderer( _gl, extensions, info, capabilities ); - indexedBufferRenderer = new WebGLIndexedBufferRenderer( _gl, extensions, info, capabilities ); - - info.programs = programCache.programs; - - _this.capabilities = capabilities; - _this.extensions = extensions; - _this.properties = properties; - _this.renderLists = renderLists; - _this.shadowMap = shadowMap; - _this.state = state; - _this.info = info; - - } - - initGLContext(); - - // xr - - const xr = new WebXRManager( _this, _gl ); - - this.xr = xr; - - // API - - this.getContext = function () { - - return _gl; - - }; - - this.getContextAttributes = function () { - - return _gl.getContextAttributes(); - - }; - - this.forceContextLoss = function () { - - const extension = extensions.get( 'WEBGL_lose_context' ); - if ( extension ) extension.loseContext(); - - }; - - this.forceContextRestore = function () { - - const extension = extensions.get( 'WEBGL_lose_context' ); - if ( extension ) extension.restoreContext(); - - }; - - this.getPixelRatio = function () { - - return _pixelRatio; - - }; - - this.setPixelRatio = function ( value ) { - - if ( value === undefined ) return; - - _pixelRatio = value; - - this.setSize( _width, _height, false ); - - }; - - this.getSize = function ( target ) { - - return target.set( _width, _height ); - - }; - - this.setSize = function ( width, height, updateStyle = true ) { - - if ( xr.isPresenting ) { - - console.warn( 'THREE.WebGLRenderer: Can\'t change size while VR device is presenting.' ); - return; - - } - - _width = width; - _height = height; - - canvas.width = Math.floor( width * _pixelRatio ); - canvas.height = Math.floor( height * _pixelRatio ); - - if ( updateStyle === true ) { - - canvas.style.width = width + 'px'; - canvas.style.height = height + 'px'; - - } - - this.setViewport( 0, 0, width, height ); - - }; - - this.getDrawingBufferSize = function ( target ) { - - return target.set( _width * _pixelRatio, _height * _pixelRatio ).floor(); - - }; - - this.setDrawingBufferSize = function ( width, height, pixelRatio ) { - - _width = width; - _height = height; - - _pixelRatio = pixelRatio; - - canvas.width = Math.floor( width * pixelRatio ); - canvas.height = Math.floor( height * pixelRatio ); - - this.setViewport( 0, 0, width, height ); - - }; - - this.getCurrentViewport = function ( target ) { - - return target.copy( _currentViewport ); - - }; - - this.getViewport = function ( target ) { - - return target.copy( _viewport ); - - }; - - this.setViewport = function ( x, y, width, height ) { - - if ( x.isVector4 ) { - - _viewport.set( x.x, x.y, x.z, x.w ); - - } else { - - _viewport.set( x, y, width, height ); - - } - - state.viewport( _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ).floor() ); - - }; - - this.getScissor = function ( target ) { - - return target.copy( _scissor ); - - }; - - this.setScissor = function ( x, y, width, height ) { - - if ( x.isVector4 ) { - - _scissor.set( x.x, x.y, x.z, x.w ); - - } else { - - _scissor.set( x, y, width, height ); - - } - - state.scissor( _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ).floor() ); - - }; - - this.getScissorTest = function () { - - return _scissorTest; - - }; - - this.setScissorTest = function ( boolean ) { - - state.setScissorTest( _scissorTest = boolean ); - - }; - - this.setOpaqueSort = function ( method ) { - - _opaqueSort = method; - - }; - - this.setTransparentSort = function ( method ) { - - _transparentSort = method; - - }; - - // Clearing - - this.getClearColor = function ( target ) { - - return target.copy( background.getClearColor() ); - - }; - - this.setClearColor = function () { - - background.setClearColor.apply( background, arguments ); - - }; - - this.getClearAlpha = function () { - - return background.getClearAlpha(); - - }; - - this.setClearAlpha = function () { - - background.setClearAlpha.apply( background, arguments ); - - }; - - this.clear = function ( color = true, depth = true, stencil = true ) { - - let bits = 0; - - if ( color ) { - - // check if we're trying to clear an integer target - let isIntegerFormat = false; - if ( _currentRenderTarget !== null ) { - - const targetFormat = _currentRenderTarget.texture.format; - isIntegerFormat = targetFormat === RGBAIntegerFormat || - targetFormat === RGIntegerFormat || - targetFormat === RedIntegerFormat; - - } - - // use the appropriate clear functions to clear the target if it's a signed - // or unsigned integer target - if ( isIntegerFormat ) { - - const targetType = _currentRenderTarget.texture.type; - const isUnsignedType = targetType === UnsignedByteType || - targetType === UnsignedIntType || - targetType === UnsignedShortType || - targetType === UnsignedInt248Type || - targetType === UnsignedShort4444Type || - targetType === UnsignedShort5551Type; - - const clearColor = background.getClearColor(); - const a = background.getClearAlpha(); - const r = clearColor.r; - const g = clearColor.g; - const b = clearColor.b; - - const __webglFramebuffer = properties.get( _currentRenderTarget ).__webglFramebuffer; - - if ( isUnsignedType ) { - - uintClearColor[ 0 ] = r; - uintClearColor[ 1 ] = g; - uintClearColor[ 2 ] = b; - uintClearColor[ 3 ] = a; - _gl.clearBufferuiv( _gl.COLOR, __webglFramebuffer, uintClearColor ); - - } else { - - intClearColor[ 0 ] = r; - intClearColor[ 1 ] = g; - intClearColor[ 2 ] = b; - intClearColor[ 3 ] = a; - _gl.clearBufferiv( _gl.COLOR, __webglFramebuffer, intClearColor ); - - } - - } else { - - bits |= _gl.COLOR_BUFFER_BIT; - - } - - } - - if ( depth ) bits |= _gl.DEPTH_BUFFER_BIT; - if ( stencil ) bits |= _gl.STENCIL_BUFFER_BIT; - - _gl.clear( bits ); - - }; - - this.clearColor = function () { - - this.clear( true, false, false ); - - }; - - this.clearDepth = function () { - - this.clear( false, true, false ); - - }; - - this.clearStencil = function () { - - this.clear( false, false, true ); - - }; - - // - - this.dispose = function () { - - canvas.removeEventListener( 'webglcontextlost', onContextLost, false ); - canvas.removeEventListener( 'webglcontextrestored', onContextRestore, false ); - canvas.removeEventListener( 'webglcontextcreationerror', onContextCreationError, false ); - - renderLists.dispose(); - renderStates.dispose(); - properties.dispose(); - cubemaps.dispose(); - cubeuvmaps.dispose(); - objects.dispose(); - bindingStates.dispose(); - uniformsGroups.dispose(); - programCache.dispose(); - - xr.dispose(); - - xr.removeEventListener( 'sessionstart', onXRSessionStart ); - xr.removeEventListener( 'sessionend', onXRSessionEnd ); - - if ( _transmissionRenderTarget ) { - - _transmissionRenderTarget.dispose(); - _transmissionRenderTarget = null; - - } - - animation.stop(); - - }; - - // Events - - function onContextLost( event ) { - - event.preventDefault(); - - console.log( 'THREE.WebGLRenderer: Context Lost.' ); - - _isContextLost = true; - - } - - function onContextRestore( /* event */ ) { - - console.log( 'THREE.WebGLRenderer: Context Restored.' ); - - _isContextLost = false; - - const infoAutoReset = info.autoReset; - const shadowMapEnabled = shadowMap.enabled; - const shadowMapAutoUpdate = shadowMap.autoUpdate; - const shadowMapNeedsUpdate = shadowMap.needsUpdate; - const shadowMapType = shadowMap.type; - - initGLContext(); - - info.autoReset = infoAutoReset; - shadowMap.enabled = shadowMapEnabled; - shadowMap.autoUpdate = shadowMapAutoUpdate; - shadowMap.needsUpdate = shadowMapNeedsUpdate; - shadowMap.type = shadowMapType; - - } - - function onContextCreationError( event ) { - - console.error( 'THREE.WebGLRenderer: A WebGL context could not be created. Reason: ', event.statusMessage ); - - } - - function onMaterialDispose( event ) { - - const material = event.target; - - material.removeEventListener( 'dispose', onMaterialDispose ); - - deallocateMaterial( material ); - - } - - // Buffer deallocation - - function deallocateMaterial( material ) { - - releaseMaterialProgramReferences( material ); - - properties.remove( material ); - - } - - - function releaseMaterialProgramReferences( material ) { - - const programs = properties.get( material ).programs; - - if ( programs !== undefined ) { - - programs.forEach( function ( program ) { - - programCache.releaseProgram( program ); - - } ); - - if ( material.isShaderMaterial ) { - - programCache.releaseShaderCache( material ); - - } - - } - - } - - // Buffer rendering - - this.renderBufferDirect = function ( camera, scene, geometry, material, object, group ) { - - if ( scene === null ) scene = _emptyScene; // renderBufferDirect second parameter used to be fog (could be null) - - const frontFaceCW = ( object.isMesh && object.matrixWorld.determinant() < 0 ); - - const program = setProgram( camera, scene, geometry, material, object ); - - state.setMaterial( material, frontFaceCW ); - - // - - let index = geometry.index; - let rangeFactor = 1; - - if ( material.wireframe === true ) { - - index = geometries.getWireframeAttribute( geometry ); - rangeFactor = 2; - - } - - // - - const drawRange = geometry.drawRange; - const position = geometry.attributes.position; - - let drawStart = drawRange.start * rangeFactor; - let drawEnd = ( drawRange.start + drawRange.count ) * rangeFactor; - - if ( group !== null ) { - - drawStart = Math.max( drawStart, group.start * rangeFactor ); - drawEnd = Math.min( drawEnd, ( group.start + group.count ) * rangeFactor ); - - } - - if ( index !== null ) { - - drawStart = Math.max( drawStart, 0 ); - drawEnd = Math.min( drawEnd, index.count ); - - } else if ( position !== undefined && position !== null ) { - - drawStart = Math.max( drawStart, 0 ); - drawEnd = Math.min( drawEnd, position.count ); - - } - - const drawCount = drawEnd - drawStart; - - if ( drawCount < 0 || drawCount === Infinity ) return; - - // - - bindingStates.setup( object, material, program, geometry, index ); - - let attribute; - let renderer = bufferRenderer; - - if ( index !== null ) { - - attribute = attributes.get( index ); - - renderer = indexedBufferRenderer; - renderer.setIndex( attribute ); - - } - - // - - if ( object.isMesh ) { - - if ( material.wireframe === true ) { - - state.setLineWidth( material.wireframeLinewidth * getTargetPixelRatio() ); - renderer.setMode( _gl.LINES ); - - } else { - - renderer.setMode( _gl.TRIANGLES ); - - } - - } else if ( object.isLine ) { - - let lineWidth = material.linewidth; - - if ( lineWidth === undefined ) lineWidth = 1; // Not using Line*Material - - state.setLineWidth( lineWidth * getTargetPixelRatio() ); - - if ( object.isLineSegments ) { - - renderer.setMode( _gl.LINES ); - - } else if ( object.isLineLoop ) { - - renderer.setMode( _gl.LINE_LOOP ); - - } else { - - renderer.setMode( _gl.LINE_STRIP ); - - } - - } else if ( object.isPoints ) { - - renderer.setMode( _gl.POINTS ); - - } else if ( object.isSprite ) { - - renderer.setMode( _gl.TRIANGLES ); - - } - - if ( object.isInstancedMesh ) { - - renderer.renderInstances( drawStart, drawCount, object.count ); - - } else if ( geometry.isInstancedBufferGeometry ) { - - const maxInstanceCount = geometry._maxInstanceCount !== undefined ? geometry._maxInstanceCount : Infinity; - const instanceCount = Math.min( geometry.instanceCount, maxInstanceCount ); - - renderer.renderInstances( drawStart, drawCount, instanceCount ); - - } else { - - renderer.render( drawStart, drawCount ); - - } - - }; - - // Compile - - this.compile = function ( scene, camera ) { - - function prepare( material, scene, object ) { - - if ( material.transparent === true && material.side === DoubleSide && material.forceSinglePass === false ) { - - material.side = BackSide; - material.needsUpdate = true; - getProgram( material, scene, object ); - - material.side = FrontSide; - material.needsUpdate = true; - getProgram( material, scene, object ); - - material.side = DoubleSide; - - } else { - - getProgram( material, scene, object ); - - } - - } - - currentRenderState = renderStates.get( scene ); - currentRenderState.init(); - - renderStateStack.push( currentRenderState ); - - scene.traverseVisible( function ( object ) { - - if ( object.isLight && object.layers.test( camera.layers ) ) { - - currentRenderState.pushLight( object ); - - if ( object.castShadow ) { - - currentRenderState.pushShadow( object ); - - } - - } - - } ); - - currentRenderState.setupLights( _this.useLegacyLights ); - - scene.traverse( function ( object ) { - - const material = object.material; - - if ( material ) { - - if ( Array.isArray( material ) ) { - - for ( let i = 0; i < material.length; i ++ ) { - - const material2 = material[ i ]; - - prepare( material2, scene, object ); - - } - - } else { - - prepare( material, scene, object ); - - } - - } - - } ); - - renderStateStack.pop(); - currentRenderState = null; - - }; - - // Animation Loop - - let onAnimationFrameCallback = null; - - function onAnimationFrame( time ) { - - if ( onAnimationFrameCallback ) onAnimationFrameCallback( time ); - - } - - function onXRSessionStart() { - - animation.stop(); - - } - - function onXRSessionEnd() { - - animation.start(); - - } - - const animation = new WebGLAnimation(); - animation.setAnimationLoop( onAnimationFrame ); - - if ( typeof self !== 'undefined' ) animation.setContext( self ); - - this.setAnimationLoop = function ( callback ) { - - onAnimationFrameCallback = callback; - xr.setAnimationLoop( callback ); - - ( callback === null ) ? animation.stop() : animation.start(); - - }; - - xr.addEventListener( 'sessionstart', onXRSessionStart ); - xr.addEventListener( 'sessionend', onXRSessionEnd ); - - // Rendering - - this.render = function ( scene, camera ) { - - if ( camera !== undefined && camera.isCamera !== true ) { - - console.error( 'THREE.WebGLRenderer.render: camera is not an instance of THREE.Camera.' ); - return; - - } - - if ( _isContextLost === true ) return; - - // update scene graph - - if ( scene.matrixWorldAutoUpdate === true ) scene.updateMatrixWorld(); - - // update camera matrices and frustum - - if ( camera.parent === null && camera.matrixWorldAutoUpdate === true ) camera.updateMatrixWorld(); - - if ( xr.enabled === true && xr.isPresenting === true ) { - - camera = xr.updateCameraXR( camera ); // use XR camera for rendering - - } - - // - if ( scene.isScene === true ) scene.onBeforeRender( _this, scene, camera, _currentRenderTarget ); - - currentRenderState = renderStates.get( scene, renderStateStack.length ); - currentRenderState.init(); - - renderStateStack.push( currentRenderState ); - - _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); - _frustum.setFromProjectionMatrix( _projScreenMatrix ); - - _localClippingEnabled = this.localClippingEnabled; - _clippingEnabled = clipping.init( this.clippingPlanes, _localClippingEnabled ); - - currentRenderList = renderLists.get( scene, renderListStack.length ); - currentRenderList.init(); - - renderListStack.push( currentRenderList ); - - projectObject( scene, camera, 0, _this.sortObjects ); - - currentRenderList.finish(); - - if ( _this.sortObjects === true ) { - - currentRenderList.sort( _opaqueSort, _transparentSort ); - - } - - // - - if ( _clippingEnabled === true ) clipping.beginShadows(); - - const shadowsArray = currentRenderState.state.shadowsArray; - - shadowMap.render( shadowsArray, scene, camera ); - - if ( _clippingEnabled === true ) clipping.endShadows(); - - // - - if ( this.info.autoReset === true ) this.info.reset(); - - this.info.render.frame ++; - - // - - background.render( currentRenderList, scene ); - - // render scene - - currentRenderState.setupLights( _this.useLegacyLights ); - - if ( camera.isArrayCamera ) { - - const cameras = camera.cameras; - - for ( let i = 0, l = cameras.length; i < l; i ++ ) { - - const camera2 = cameras[ i ]; - - renderScene( currentRenderList, scene, camera2, camera2.viewport ); - - } - - } else { - - renderScene( currentRenderList, scene, camera ); - - } - - // - - if ( _currentRenderTarget !== null ) { - - // resolve multisample renderbuffers to a single-sample texture if necessary - - textures.updateMultisampleRenderTarget( _currentRenderTarget ); - - // Generate mipmap if we're using any kind of mipmap filtering - - textures.updateRenderTargetMipmap( _currentRenderTarget ); - - } - - // - - if ( scene.isScene === true ) scene.onAfterRender( _this, scene, camera ); - - // _gl.finish(); - - bindingStates.resetDefaultState(); - _currentMaterialId = - 1; - _currentCamera = null; - - renderStateStack.pop(); - - if ( renderStateStack.length > 0 ) { - - currentRenderState = renderStateStack[ renderStateStack.length - 1 ]; - - } else { - - currentRenderState = null; - - } - - renderListStack.pop(); - - if ( renderListStack.length > 0 ) { - - currentRenderList = renderListStack[ renderListStack.length - 1 ]; - - } else { - - currentRenderList = null; - - } - - }; - - function projectObject( object, camera, groupOrder, sortObjects ) { - - if ( object.visible === false ) return; - - const visible = object.layers.test( camera.layers ); - - if ( visible ) { - - if ( object.isGroup ) { - - groupOrder = object.renderOrder; - - } else if ( object.isLOD ) { - - if ( object.autoUpdate === true ) object.update( camera ); - - } else if ( object.isLight ) { - - currentRenderState.pushLight( object ); - - if ( object.castShadow ) { - - currentRenderState.pushShadow( object ); - - } - - } else if ( object.isSprite ) { - - if ( ! object.frustumCulled || _frustum.intersectsSprite( object ) ) { - - if ( sortObjects ) { - - _vector3.setFromMatrixPosition( object.matrixWorld ) - .applyMatrix4( _projScreenMatrix ); - - } - - const geometry = objects.update( object ); - const material = object.material; - - if ( material.visible ) { - - currentRenderList.push( object, geometry, material, groupOrder, _vector3.z, null ); - - } - - } - - } else if ( object.isMesh || object.isLine || object.isPoints ) { - - if ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) { - - if ( object.isSkinnedMesh ) { - - // update skeleton only once in a frame - - if ( object.skeleton.frame !== info.render.frame ) { - - object.skeleton.update(); - object.skeleton.frame = info.render.frame; - - } - - } - - const geometry = objects.update( object ); - const material = object.material; - - if ( sortObjects ) { - - if ( object.boundingSphere !== undefined ) { - - if ( object.boundingSphere === null ) object.computeBoundingSphere(); - _vector3.copy( object.boundingSphere.center ); - - } else { - - if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); - _vector3.copy( geometry.boundingSphere.center ); - - } - - _vector3 - .applyMatrix4( object.matrixWorld ) - .applyMatrix4( _projScreenMatrix ); - - } - - if ( Array.isArray( material ) ) { - - const groups = geometry.groups; - - for ( let i = 0, l = groups.length; i < l; i ++ ) { - - const group = groups[ i ]; - const groupMaterial = material[ group.materialIndex ]; - - if ( groupMaterial && groupMaterial.visible ) { - - currentRenderList.push( object, geometry, groupMaterial, groupOrder, _vector3.z, group ); - - } - - } - - } else if ( material.visible ) { - - currentRenderList.push( object, geometry, material, groupOrder, _vector3.z, null ); - - } - - } - - } - - } - - const children = object.children; - - for ( let i = 0, l = children.length; i < l; i ++ ) { - - projectObject( children[ i ], camera, groupOrder, sortObjects ); - - } - - } - - function renderScene( currentRenderList, scene, camera, viewport ) { - - const opaqueObjects = currentRenderList.opaque; - const transmissiveObjects = currentRenderList.transmissive; - const transparentObjects = currentRenderList.transparent; - - currentRenderState.setupLightsView( camera ); - - if ( _clippingEnabled === true ) clipping.setGlobalState( _this.clippingPlanes, camera ); - - if ( transmissiveObjects.length > 0 ) renderTransmissionPass( opaqueObjects, transmissiveObjects, scene, camera ); - - if ( viewport ) state.viewport( _currentViewport.copy( viewport ) ); - - if ( opaqueObjects.length > 0 ) renderObjects( opaqueObjects, scene, camera ); - if ( transmissiveObjects.length > 0 ) renderObjects( transmissiveObjects, scene, camera ); - if ( transparentObjects.length > 0 ) renderObjects( transparentObjects, scene, camera ); - - // Ensure depth buffer writing is enabled so it can be cleared on next render - - state.buffers.depth.setTest( true ); - state.buffers.depth.setMask( true ); - state.buffers.color.setMask( true ); - - state.setPolygonOffset( false ); - - } - - function renderTransmissionPass( opaqueObjects, transmissiveObjects, scene, camera ) { - - if ( _transmissionRenderTarget === null ) { - - const isWebGL2 = capabilities.isWebGL2; - - _transmissionRenderTarget = new WebGLRenderTarget( 1024, 1024, { - generateMipmaps: true, - type: extensions.has( 'EXT_color_buffer_half_float' ) ? HalfFloatType : UnsignedByteType, - minFilter: LinearMipmapLinearFilter, - samples: ( isWebGL2 && antialias === true ) ? 4 : 0 - } ); - - // debug - - /* - const geometry = new PlaneGeometry(); - const material = new MeshBasicMaterial( { map: _transmissionRenderTarget.texture } ); - - const mesh = new Mesh( geometry, material ); - scene.add( mesh ); - */ - - } - - // - - const currentRenderTarget = _this.getRenderTarget(); - _this.setRenderTarget( _transmissionRenderTarget ); - - _this.getClearColor( _currentClearColor ); - _currentClearAlpha = _this.getClearAlpha(); - if ( _currentClearAlpha < 1 ) _this.setClearColor( 0xffffff, 0.5 ); - - _this.clear(); - - // Turn off the features which can affect the frag color for opaque objects pass. - // Otherwise they are applied twice in opaque objects pass and transmission objects pass. - const currentToneMapping = _this.toneMapping; - _this.toneMapping = NoToneMapping; - - renderObjects( opaqueObjects, scene, camera ); - - textures.updateMultisampleRenderTarget( _transmissionRenderTarget ); - textures.updateRenderTargetMipmap( _transmissionRenderTarget ); - - let renderTargetNeedsUpdate = false; - - for ( let i = 0, l = transmissiveObjects.length; i < l; i ++ ) { - - const renderItem = transmissiveObjects[ i ]; - - const object = renderItem.object; - const geometry = renderItem.geometry; - const material = renderItem.material; - const group = renderItem.group; - - if ( material.side === DoubleSide && object.layers.test( camera.layers ) ) { - - const currentSide = material.side; - - material.side = BackSide; - material.needsUpdate = true; - - renderObject( object, scene, camera, geometry, material, group ); - - material.side = currentSide; - material.needsUpdate = true; - - renderTargetNeedsUpdate = true; - - } - - } - - if ( renderTargetNeedsUpdate === true ) { - - textures.updateMultisampleRenderTarget( _transmissionRenderTarget ); - textures.updateRenderTargetMipmap( _transmissionRenderTarget ); - - } - - _this.setRenderTarget( currentRenderTarget ); - - _this.setClearColor( _currentClearColor, _currentClearAlpha ); - - _this.toneMapping = currentToneMapping; - - } - - function renderObjects( renderList, scene, camera ) { - - const overrideMaterial = scene.isScene === true ? scene.overrideMaterial : null; - - for ( let i = 0, l = renderList.length; i < l; i ++ ) { - - const renderItem = renderList[ i ]; - - const object = renderItem.object; - const geometry = renderItem.geometry; - const material = overrideMaterial === null ? renderItem.material : overrideMaterial; - const group = renderItem.group; - - if ( object.layers.test( camera.layers ) ) { - - renderObject( object, scene, camera, geometry, material, group ); - - } - - } - - } - - function renderObject( object, scene, camera, geometry, material, group ) { - - object.onBeforeRender( _this, scene, camera, geometry, material, group ); - - object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld ); - object.normalMatrix.getNormalMatrix( object.modelViewMatrix ); - - material.onBeforeRender( _this, scene, camera, geometry, object, group ); - - if ( material.transparent === true && material.side === DoubleSide && material.forceSinglePass === false ) { - - material.side = BackSide; - material.needsUpdate = true; - _this.renderBufferDirect( camera, scene, geometry, material, object, group ); - - material.side = FrontSide; - material.needsUpdate = true; - _this.renderBufferDirect( camera, scene, geometry, material, object, group ); - - material.side = DoubleSide; - - } else { - - _this.renderBufferDirect( camera, scene, geometry, material, object, group ); - - } - - object.onAfterRender( _this, scene, camera, geometry, material, group ); - - } - - function getProgram( material, scene, object ) { - - if ( scene.isScene !== true ) scene = _emptyScene; // scene could be a Mesh, Line, Points, ... - - const materialProperties = properties.get( material ); - - const lights = currentRenderState.state.lights; - const shadowsArray = currentRenderState.state.shadowsArray; - - const lightsStateVersion = lights.state.version; - - const parameters = programCache.getParameters( material, lights.state, shadowsArray, scene, object ); - const programCacheKey = programCache.getProgramCacheKey( parameters ); - - let programs = materialProperties.programs; - - // always update environment and fog - changing these trigger an getProgram call, but it's possible that the program doesn't change - - materialProperties.environment = material.isMeshStandardMaterial ? scene.environment : null; - materialProperties.fog = scene.fog; - materialProperties.envMap = ( material.isMeshStandardMaterial ? cubeuvmaps : cubemaps ).get( material.envMap || materialProperties.environment ); - - if ( programs === undefined ) { - - // new material - - material.addEventListener( 'dispose', onMaterialDispose ); - - programs = new Map(); - materialProperties.programs = programs; - - } - - let program = programs.get( programCacheKey ); - - if ( program !== undefined ) { - - // early out if program and light state is identical - - if ( materialProperties.currentProgram === program && materialProperties.lightsStateVersion === lightsStateVersion ) { - - updateCommonMaterialProperties( material, parameters ); - - return program; - - } - - } else { - - parameters.uniforms = programCache.getUniforms( material ); - - material.onBuild( object, parameters, _this ); - - material.onBeforeCompile( parameters, _this ); - - program = programCache.acquireProgram( parameters, programCacheKey ); - programs.set( programCacheKey, program ); - - materialProperties.uniforms = parameters.uniforms; - - } - - const uniforms = materialProperties.uniforms; - - if ( ( ! material.isShaderMaterial && ! material.isRawShaderMaterial ) || material.clipping === true ) { - - uniforms.clippingPlanes = clipping.uniform; - - } - - updateCommonMaterialProperties( material, parameters ); - - // store the light setup it was created for - - materialProperties.needsLights = materialNeedsLights( material ); - materialProperties.lightsStateVersion = lightsStateVersion; - - if ( materialProperties.needsLights ) { - - // wire up the material to this renderer's lighting state - - uniforms.ambientLightColor.value = lights.state.ambient; - uniforms.lightProbe.value = lights.state.probe; - uniforms.directionalLights.value = lights.state.directional; - uniforms.directionalLightShadows.value = lights.state.directionalShadow; - uniforms.spotLights.value = lights.state.spot; - uniforms.spotLightShadows.value = lights.state.spotShadow; - uniforms.rectAreaLights.value = lights.state.rectArea; - uniforms.ltc_1.value = lights.state.rectAreaLTC1; - uniforms.ltc_2.value = lights.state.rectAreaLTC2; - uniforms.pointLights.value = lights.state.point; - uniforms.pointLightShadows.value = lights.state.pointShadow; - uniforms.hemisphereLights.value = lights.state.hemi; - - uniforms.directionalShadowMap.value = lights.state.directionalShadowMap; - uniforms.directionalShadowMatrix.value = lights.state.directionalShadowMatrix; - uniforms.spotShadowMap.value = lights.state.spotShadowMap; - uniforms.spotLightMatrix.value = lights.state.spotLightMatrix; - uniforms.spotLightMap.value = lights.state.spotLightMap; - uniforms.pointShadowMap.value = lights.state.pointShadowMap; - uniforms.pointShadowMatrix.value = lights.state.pointShadowMatrix; - // TODO (abelnation): add area lights shadow info to uniforms - - } - - const progUniforms = program.getUniforms(); - const uniformsList = WebGLUniforms.seqWithValue( progUniforms.seq, uniforms ); - - materialProperties.currentProgram = program; - materialProperties.uniformsList = uniformsList; - - return program; - - } - - function updateCommonMaterialProperties( material, parameters ) { - - const materialProperties = properties.get( material ); - - materialProperties.outputColorSpace = parameters.outputColorSpace; - materialProperties.instancing = parameters.instancing; - materialProperties.skinning = parameters.skinning; - materialProperties.morphTargets = parameters.morphTargets; - materialProperties.morphNormals = parameters.morphNormals; - materialProperties.morphColors = parameters.morphColors; - materialProperties.morphTargetsCount = parameters.morphTargetsCount; - materialProperties.numClippingPlanes = parameters.numClippingPlanes; - materialProperties.numIntersection = parameters.numClipIntersection; - materialProperties.vertexAlphas = parameters.vertexAlphas; - materialProperties.vertexTangents = parameters.vertexTangents; - materialProperties.toneMapping = parameters.toneMapping; - - } - - function setProgram( camera, scene, geometry, material, object ) { - - if ( scene.isScene !== true ) scene = _emptyScene; // scene could be a Mesh, Line, Points, ... - - textures.resetTextureUnits(); - - const fog = scene.fog; - const environment = material.isMeshStandardMaterial ? scene.environment : null; - const colorSpace = ( _currentRenderTarget === null ) ? _this.outputColorSpace : ( _currentRenderTarget.isXRRenderTarget === true ? _currentRenderTarget.texture.colorSpace : LinearSRGBColorSpace ); - const envMap = ( material.isMeshStandardMaterial ? cubeuvmaps : cubemaps ).get( material.envMap || environment ); - const vertexAlphas = material.vertexColors === true && !! geometry.attributes.color && geometry.attributes.color.itemSize === 4; - const vertexTangents = !! geometry.attributes.tangent && ( !! material.normalMap || material.anisotropy > 0 ); - const morphTargets = !! geometry.morphAttributes.position; - const morphNormals = !! geometry.morphAttributes.normal; - const morphColors = !! geometry.morphAttributes.color; - const toneMapping = material.toneMapped ? _this.toneMapping : NoToneMapping; - - const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color; - const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0; - - const materialProperties = properties.get( material ); - const lights = currentRenderState.state.lights; - - if ( _clippingEnabled === true ) { - - if ( _localClippingEnabled === true || camera !== _currentCamera ) { - - const useCache = - camera === _currentCamera && - material.id === _currentMaterialId; - - // we might want to call this function with some ClippingGroup - // object instead of the material, once it becomes feasible - // (#8465, #8379) - clipping.setState( material, camera, useCache ); - - } - - } - - // - - let needsProgramChange = false; - - if ( material.version === materialProperties.__version ) { - - if ( materialProperties.needsLights && ( materialProperties.lightsStateVersion !== lights.state.version ) ) { - - needsProgramChange = true; - - } else if ( materialProperties.outputColorSpace !== colorSpace ) { - - needsProgramChange = true; - - } else if ( object.isInstancedMesh && materialProperties.instancing === false ) { - - needsProgramChange = true; - - } else if ( ! object.isInstancedMesh && materialProperties.instancing === true ) { - - needsProgramChange = true; - - } else if ( object.isSkinnedMesh && materialProperties.skinning === false ) { - - needsProgramChange = true; - - } else if ( ! object.isSkinnedMesh && materialProperties.skinning === true ) { - - needsProgramChange = true; - - } else if ( materialProperties.envMap !== envMap ) { - - needsProgramChange = true; - - } else if ( material.fog === true && materialProperties.fog !== fog ) { - - needsProgramChange = true; - - } else if ( materialProperties.numClippingPlanes !== undefined && - ( materialProperties.numClippingPlanes !== clipping.numPlanes || - materialProperties.numIntersection !== clipping.numIntersection ) ) { - - needsProgramChange = true; - - } else if ( materialProperties.vertexAlphas !== vertexAlphas ) { - - needsProgramChange = true; - - } else if ( materialProperties.vertexTangents !== vertexTangents ) { - - needsProgramChange = true; - - } else if ( materialProperties.morphTargets !== morphTargets ) { - - needsProgramChange = true; - - } else if ( materialProperties.morphNormals !== morphNormals ) { - - needsProgramChange = true; - - } else if ( materialProperties.morphColors !== morphColors ) { - - needsProgramChange = true; - - } else if ( materialProperties.toneMapping !== toneMapping ) { - - needsProgramChange = true; - - } else if ( capabilities.isWebGL2 === true && materialProperties.morphTargetsCount !== morphTargetsCount ) { - - needsProgramChange = true; - - } - - } else { - - needsProgramChange = true; - materialProperties.__version = material.version; - - } - - // - - let program = materialProperties.currentProgram; - - if ( needsProgramChange === true ) { - - program = getProgram( material, scene, object ); - - } - - let refreshProgram = false; - let refreshMaterial = false; - let refreshLights = false; - - const p_uniforms = program.getUniforms(), - m_uniforms = materialProperties.uniforms; - - if ( state.useProgram( program.program ) ) { - - refreshProgram = true; - refreshMaterial = true; - refreshLights = true; - - } - - if ( material.id !== _currentMaterialId ) { - - _currentMaterialId = material.id; - - refreshMaterial = true; - - } - - if ( refreshProgram || _currentCamera !== camera ) { - - p_uniforms.setValue( _gl, 'projectionMatrix', camera.projectionMatrix ); - - if ( capabilities.logarithmicDepthBuffer ) { - - p_uniforms.setValue( _gl, 'logDepthBufFC', - 2.0 / ( Math.log( camera.far + 1.0 ) / Math.LN2 ) ); - - } - - if ( _currentCamera !== camera ) { - - _currentCamera = camera; - - // lighting uniforms depend on the camera so enforce an update - // now, in case this material supports lights - or later, when - // the next material that does gets activated: - - refreshMaterial = true; // set to true on material change - refreshLights = true; // remains set until update done - - } - - // load material specific uniforms - // (shader material also gets them for the sake of genericity) - - if ( material.isShaderMaterial || - material.isMeshPhongMaterial || - material.isMeshToonMaterial || - material.isMeshStandardMaterial || - material.envMap ) { - - const uCamPos = p_uniforms.map.cameraPosition; - - if ( uCamPos !== undefined ) { - - uCamPos.setValue( _gl, - _vector3.setFromMatrixPosition( camera.matrixWorld ) ); - - } - - } - - if ( material.isMeshPhongMaterial || - material.isMeshToonMaterial || - material.isMeshLambertMaterial || - material.isMeshBasicMaterial || - material.isMeshStandardMaterial || - material.isShaderMaterial ) { - - p_uniforms.setValue( _gl, 'isOrthographic', camera.isOrthographicCamera === true ); - - } - - if ( material.isMeshPhongMaterial || - material.isMeshToonMaterial || - material.isMeshLambertMaterial || - material.isMeshBasicMaterial || - material.isMeshStandardMaterial || - material.isShaderMaterial || - material.isShadowMaterial || - object.isSkinnedMesh ) { - - p_uniforms.setValue( _gl, 'viewMatrix', camera.matrixWorldInverse ); - - } - - } - - // skinning and morph target uniforms must be set even if material didn't change - // auto-setting of texture unit for bone and morph texture must go before other textures - // otherwise textures used for skinning and morphing can take over texture units reserved for other material textures - - if ( object.isSkinnedMesh ) { - - p_uniforms.setOptional( _gl, object, 'bindMatrix' ); - p_uniforms.setOptional( _gl, object, 'bindMatrixInverse' ); - - const skeleton = object.skeleton; - - if ( skeleton ) { - - if ( capabilities.floatVertexTextures ) { - - if ( skeleton.boneTexture === null ) skeleton.computeBoneTexture(); - - p_uniforms.setValue( _gl, 'boneTexture', skeleton.boneTexture, textures ); - p_uniforms.setValue( _gl, 'boneTextureSize', skeleton.boneTextureSize ); - - } else { - - console.warn( 'THREE.WebGLRenderer: SkinnedMesh can only be used with WebGL 2. With WebGL 1 OES_texture_float and vertex textures support is required.' ); - - } - - } - - } - - const morphAttributes = geometry.morphAttributes; - - if ( morphAttributes.position !== undefined || morphAttributes.normal !== undefined || ( morphAttributes.color !== undefined && capabilities.isWebGL2 === true ) ) { - - morphtargets.update( object, geometry, program ); - - } - - if ( refreshMaterial || materialProperties.receiveShadow !== object.receiveShadow ) { - - materialProperties.receiveShadow = object.receiveShadow; - p_uniforms.setValue( _gl, 'receiveShadow', object.receiveShadow ); - - } - - // https://github.com/mrdoob/three.js/pull/24467#issuecomment-1209031512 - - if ( material.isMeshGouraudMaterial && material.envMap !== null ) { - - m_uniforms.envMap.value = envMap; - - m_uniforms.flipEnvMap.value = ( envMap.isCubeTexture && envMap.isRenderTargetTexture === false ) ? - 1 : 1; - - } - - if ( refreshMaterial ) { - - p_uniforms.setValue( _gl, 'toneMappingExposure', _this.toneMappingExposure ); - - if ( materialProperties.needsLights ) { - - // the current material requires lighting info - - // note: all lighting uniforms are always set correctly - // they simply reference the renderer's state for their - // values - // - // use the current material's .needsUpdate flags to set - // the GL state when required - - markUniformsLightsNeedsUpdate( m_uniforms, refreshLights ); - - } - - // refresh uniforms common to several materials - - if ( fog && material.fog === true ) { - - materials.refreshFogUniforms( m_uniforms, fog ); - - } - - materials.refreshMaterialUniforms( m_uniforms, material, _pixelRatio, _height, _transmissionRenderTarget ); - - WebGLUniforms.upload( _gl, materialProperties.uniformsList, m_uniforms, textures ); - - } - - if ( material.isShaderMaterial && material.uniformsNeedUpdate === true ) { - - WebGLUniforms.upload( _gl, materialProperties.uniformsList, m_uniforms, textures ); - material.uniformsNeedUpdate = false; - - } - - if ( material.isSpriteMaterial ) { - - p_uniforms.setValue( _gl, 'center', object.center ); - - } - - // common matrices - - p_uniforms.setValue( _gl, 'modelViewMatrix', object.modelViewMatrix ); - p_uniforms.setValue( _gl, 'normalMatrix', object.normalMatrix ); - p_uniforms.setValue( _gl, 'modelMatrix', object.matrixWorld ); - - // UBOs - - if ( material.isShaderMaterial || material.isRawShaderMaterial ) { - - const groups = material.uniformsGroups; - - for ( let i = 0, l = groups.length; i < l; i ++ ) { - - if ( capabilities.isWebGL2 ) { - - const group = groups[ i ]; - - uniformsGroups.update( group, program ); - uniformsGroups.bind( group, program ); - - } else { - - console.warn( 'THREE.WebGLRenderer: Uniform Buffer Objects can only be used with WebGL 2.' ); - - } - - } - - } - - return program; - - } - - // If uniforms are marked as clean, they don't need to be loaded to the GPU. - - function markUniformsLightsNeedsUpdate( uniforms, value ) { - - uniforms.ambientLightColor.needsUpdate = value; - uniforms.lightProbe.needsUpdate = value; - - uniforms.directionalLights.needsUpdate = value; - uniforms.directionalLightShadows.needsUpdate = value; - uniforms.pointLights.needsUpdate = value; - uniforms.pointLightShadows.needsUpdate = value; - uniforms.spotLights.needsUpdate = value; - uniforms.spotLightShadows.needsUpdate = value; - uniforms.rectAreaLights.needsUpdate = value; - uniforms.hemisphereLights.needsUpdate = value; - - } - - function materialNeedsLights( material ) { - - return material.isMeshLambertMaterial || material.isMeshToonMaterial || material.isMeshPhongMaterial || - material.isMeshStandardMaterial || material.isShadowMaterial || - ( material.isShaderMaterial && material.lights === true ); - - } - - this.getActiveCubeFace = function () { - - return _currentActiveCubeFace; - - }; - - this.getActiveMipmapLevel = function () { - - return _currentActiveMipmapLevel; - - }; - - this.getRenderTarget = function () { - - return _currentRenderTarget; - - }; - - this.setRenderTargetTextures = function ( renderTarget, colorTexture, depthTexture ) { - - properties.get( renderTarget.texture ).__webglTexture = colorTexture; - properties.get( renderTarget.depthTexture ).__webglTexture = depthTexture; - - const renderTargetProperties = properties.get( renderTarget ); - renderTargetProperties.__hasExternalTextures = true; - - if ( renderTargetProperties.__hasExternalTextures ) { - - renderTargetProperties.__autoAllocateDepthBuffer = depthTexture === undefined; - - if ( ! renderTargetProperties.__autoAllocateDepthBuffer ) { - - // The multisample_render_to_texture extension doesn't work properly if there - // are midframe flushes and an external depth buffer. Disable use of the extension. - if ( extensions.has( 'WEBGL_multisampled_render_to_texture' ) === true ) { - - console.warn( 'THREE.WebGLRenderer: Render-to-texture extension was disabled because an external texture was provided' ); - renderTargetProperties.__useRenderToTexture = false; - - } - - } - - } - - }; - - this.setRenderTargetFramebuffer = function ( renderTarget, defaultFramebuffer ) { - - const renderTargetProperties = properties.get( renderTarget ); - renderTargetProperties.__webglFramebuffer = defaultFramebuffer; - renderTargetProperties.__useDefaultFramebuffer = defaultFramebuffer === undefined; - - }; - - this.setRenderTarget = function ( renderTarget, activeCubeFace = 0, activeMipmapLevel = 0 ) { - - _currentRenderTarget = renderTarget; - _currentActiveCubeFace = activeCubeFace; - _currentActiveMipmapLevel = activeMipmapLevel; - - let useDefaultFramebuffer = true; - let framebuffer = null; - let isCube = false; - let isRenderTarget3D = false; - - if ( renderTarget ) { - - const renderTargetProperties = properties.get( renderTarget ); - - if ( renderTargetProperties.__useDefaultFramebuffer !== undefined ) { - - // We need to make sure to rebind the framebuffer. - state.bindFramebuffer( _gl.FRAMEBUFFER, null ); - useDefaultFramebuffer = false; - - } else if ( renderTargetProperties.__webglFramebuffer === undefined ) { - - textures.setupRenderTarget( renderTarget ); - - } else if ( renderTargetProperties.__hasExternalTextures ) { - - // Color and depth texture must be rebound in order for the swapchain to update. - textures.rebindTextures( renderTarget, properties.get( renderTarget.texture ).__webglTexture, properties.get( renderTarget.depthTexture ).__webglTexture ); - - } - - const texture = renderTarget.texture; - - if ( texture.isData3DTexture || texture.isDataArrayTexture || texture.isCompressedArrayTexture ) { - - isRenderTarget3D = true; - - } - - const __webglFramebuffer = properties.get( renderTarget ).__webglFramebuffer; - - if ( renderTarget.isWebGLCubeRenderTarget ) { - - framebuffer = __webglFramebuffer[ activeCubeFace ]; - isCube = true; - - } else if ( ( capabilities.isWebGL2 && renderTarget.samples > 0 ) && textures.useMultisampledRTT( renderTarget ) === false ) { - - framebuffer = properties.get( renderTarget ).__webglMultisampledFramebuffer; - - } else { - - framebuffer = __webglFramebuffer; - - } - - _currentViewport.copy( renderTarget.viewport ); - _currentScissor.copy( renderTarget.scissor ); - _currentScissorTest = renderTarget.scissorTest; - - } else { - - _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ).floor(); - _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ).floor(); - _currentScissorTest = _scissorTest; - - } - - const framebufferBound = state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); - - if ( framebufferBound && capabilities.drawBuffers && useDefaultFramebuffer ) { - - state.drawBuffers( renderTarget, framebuffer ); - - } - - state.viewport( _currentViewport ); - state.scissor( _currentScissor ); - state.setScissorTest( _currentScissorTest ); - - if ( isCube ) { - - const textureProperties = properties.get( renderTarget.texture ); - _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + activeCubeFace, textureProperties.__webglTexture, activeMipmapLevel ); - - } else if ( isRenderTarget3D ) { - - const textureProperties = properties.get( renderTarget.texture ); - const layer = activeCubeFace || 0; - _gl.framebufferTextureLayer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, textureProperties.__webglTexture, activeMipmapLevel || 0, layer ); - - } - - _currentMaterialId = - 1; // reset current material to ensure correct uniform bindings - - }; - - this.readRenderTargetPixels = function ( renderTarget, x, y, width, height, buffer, activeCubeFaceIndex ) { - - if ( ! ( renderTarget && renderTarget.isWebGLRenderTarget ) ) { - - console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.' ); - return; - - } - - let framebuffer = properties.get( renderTarget ).__webglFramebuffer; - - if ( renderTarget.isWebGLCubeRenderTarget && activeCubeFaceIndex !== undefined ) { - - framebuffer = framebuffer[ activeCubeFaceIndex ]; - - } - - if ( framebuffer ) { - - state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); - - try { - - const texture = renderTarget.texture; - const textureFormat = texture.format; - const textureType = texture.type; - - if ( textureFormat !== RGBAFormat && utils.convert( textureFormat ) !== _gl.getParameter( _gl.IMPLEMENTATION_COLOR_READ_FORMAT ) ) { - - console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in RGBA or implementation defined format.' ); - return; - - } - - const halfFloatSupportedByExt = ( textureType === HalfFloatType ) && ( extensions.has( 'EXT_color_buffer_half_float' ) || ( capabilities.isWebGL2 && extensions.has( 'EXT_color_buffer_float' ) ) ); - - if ( textureType !== UnsignedByteType && utils.convert( textureType ) !== _gl.getParameter( _gl.IMPLEMENTATION_COLOR_READ_TYPE ) && // Edge and Chrome Mac < 52 (#9513) - ! ( textureType === FloatType && ( capabilities.isWebGL2 || extensions.has( 'OES_texture_float' ) || extensions.has( 'WEBGL_color_buffer_float' ) ) ) && // Chrome Mac >= 52 and Firefox - ! halfFloatSupportedByExt ) { - - console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in UnsignedByteType or implementation defined type.' ); - return; - - } - - // the following if statement ensures valid read requests (no out-of-bounds pixels, see #8604) - - if ( ( x >= 0 && x <= ( renderTarget.width - width ) ) && ( y >= 0 && y <= ( renderTarget.height - height ) ) ) { - - _gl.readPixels( x, y, width, height, utils.convert( textureFormat ), utils.convert( textureType ), buffer ); - - } - - } finally { - - // restore framebuffer of current render target if necessary - - const framebuffer = ( _currentRenderTarget !== null ) ? properties.get( _currentRenderTarget ).__webglFramebuffer : null; - state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); - - } - - } - - }; - - this.copyFramebufferToTexture = function ( position, texture, level = 0 ) { - - const levelScale = Math.pow( 2, - level ); - const width = Math.floor( texture.image.width * levelScale ); - const height = Math.floor( texture.image.height * levelScale ); - - textures.setTexture2D( texture, 0 ); - - _gl.copyTexSubImage2D( _gl.TEXTURE_2D, level, 0, 0, position.x, position.y, width, height ); - - state.unbindTexture(); - - }; - - this.copyTextureToTexture = function ( position, srcTexture, dstTexture, level = 0 ) { - - const width = srcTexture.image.width; - const height = srcTexture.image.height; - const glFormat = utils.convert( dstTexture.format ); - const glType = utils.convert( dstTexture.type ); - - textures.setTexture2D( dstTexture, 0 ); - - // As another texture upload may have changed pixelStorei - // parameters, make sure they are correct for the dstTexture - _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, dstTexture.flipY ); - _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, dstTexture.premultiplyAlpha ); - _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, dstTexture.unpackAlignment ); - - if ( srcTexture.isDataTexture ) { - - _gl.texSubImage2D( _gl.TEXTURE_2D, level, position.x, position.y, width, height, glFormat, glType, srcTexture.image.data ); - - } else { - - if ( srcTexture.isCompressedTexture ) { - - _gl.compressedTexSubImage2D( _gl.TEXTURE_2D, level, position.x, position.y, srcTexture.mipmaps[ 0 ].width, srcTexture.mipmaps[ 0 ].height, glFormat, srcTexture.mipmaps[ 0 ].data ); - - } else { - - _gl.texSubImage2D( _gl.TEXTURE_2D, level, position.x, position.y, glFormat, glType, srcTexture.image ); - - } - - } - - // Generate mipmaps only when copying level 0 - if ( level === 0 && dstTexture.generateMipmaps ) _gl.generateMipmap( _gl.TEXTURE_2D ); - - state.unbindTexture(); - - }; - - this.copyTextureToTexture3D = function ( sourceBox, position, srcTexture, dstTexture, level = 0 ) { - - if ( _this.isWebGL1Renderer ) { - - console.warn( 'THREE.WebGLRenderer.copyTextureToTexture3D: can only be used with WebGL2.' ); - return; - - } - - const width = sourceBox.max.x - sourceBox.min.x + 1; - const height = sourceBox.max.y - sourceBox.min.y + 1; - const depth = sourceBox.max.z - sourceBox.min.z + 1; - const glFormat = utils.convert( dstTexture.format ); - const glType = utils.convert( dstTexture.type ); - let glTarget; - - if ( dstTexture.isData3DTexture ) { - - textures.setTexture3D( dstTexture, 0 ); - glTarget = _gl.TEXTURE_3D; - - } else if ( dstTexture.isDataArrayTexture ) { - - textures.setTexture2DArray( dstTexture, 0 ); - glTarget = _gl.TEXTURE_2D_ARRAY; - - } else { - - console.warn( 'THREE.WebGLRenderer.copyTextureToTexture3D: only supports THREE.DataTexture3D and THREE.DataTexture2DArray.' ); - return; - - } - - _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, dstTexture.flipY ); - _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, dstTexture.premultiplyAlpha ); - _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, dstTexture.unpackAlignment ); - - const unpackRowLen = _gl.getParameter( _gl.UNPACK_ROW_LENGTH ); - const unpackImageHeight = _gl.getParameter( _gl.UNPACK_IMAGE_HEIGHT ); - const unpackSkipPixels = _gl.getParameter( _gl.UNPACK_SKIP_PIXELS ); - const unpackSkipRows = _gl.getParameter( _gl.UNPACK_SKIP_ROWS ); - const unpackSkipImages = _gl.getParameter( _gl.UNPACK_SKIP_IMAGES ); - - const image = srcTexture.isCompressedTexture ? srcTexture.mipmaps[ 0 ] : srcTexture.image; - - _gl.pixelStorei( _gl.UNPACK_ROW_LENGTH, image.width ); - _gl.pixelStorei( _gl.UNPACK_IMAGE_HEIGHT, image.height ); - _gl.pixelStorei( _gl.UNPACK_SKIP_PIXELS, sourceBox.min.x ); - _gl.pixelStorei( _gl.UNPACK_SKIP_ROWS, sourceBox.min.y ); - _gl.pixelStorei( _gl.UNPACK_SKIP_IMAGES, sourceBox.min.z ); - - if ( srcTexture.isDataTexture || srcTexture.isData3DTexture ) { - - _gl.texSubImage3D( glTarget, level, position.x, position.y, position.z, width, height, depth, glFormat, glType, image.data ); - - } else { - - if ( srcTexture.isCompressedArrayTexture ) { - - console.warn( 'THREE.WebGLRenderer.copyTextureToTexture3D: untested support for compressed srcTexture.' ); - _gl.compressedTexSubImage3D( glTarget, level, position.x, position.y, position.z, width, height, depth, glFormat, image.data ); - - } else { - - _gl.texSubImage3D( glTarget, level, position.x, position.y, position.z, width, height, depth, glFormat, glType, image ); - - } - - } - - _gl.pixelStorei( _gl.UNPACK_ROW_LENGTH, unpackRowLen ); - _gl.pixelStorei( _gl.UNPACK_IMAGE_HEIGHT, unpackImageHeight ); - _gl.pixelStorei( _gl.UNPACK_SKIP_PIXELS, unpackSkipPixels ); - _gl.pixelStorei( _gl.UNPACK_SKIP_ROWS, unpackSkipRows ); - _gl.pixelStorei( _gl.UNPACK_SKIP_IMAGES, unpackSkipImages ); - - // Generate mipmaps only when copying level 0 - if ( level === 0 && dstTexture.generateMipmaps ) _gl.generateMipmap( glTarget ); - - state.unbindTexture(); - - }; - - this.initTexture = function ( texture ) { - - if ( texture.isCubeTexture ) { - - textures.setTextureCube( texture, 0 ); - - } else if ( texture.isData3DTexture ) { - - textures.setTexture3D( texture, 0 ); - - } else if ( texture.isDataArrayTexture || texture.isCompressedArrayTexture ) { - - textures.setTexture2DArray( texture, 0 ); - - } else { - - textures.setTexture2D( texture, 0 ); - - } - - state.unbindTexture(); - - }; - - this.resetState = function () { - - _currentActiveCubeFace = 0; - _currentActiveMipmapLevel = 0; - _currentRenderTarget = null; - - state.reset(); - bindingStates.reset(); - - }; - - if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) { - - __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'observe', { detail: this } ) ); - - } - - } - - get physicallyCorrectLights() { // @deprecated, r150 - - console.warn( 'THREE.WebGLRenderer: the property .physicallyCorrectLights has been removed. Set renderer.useLegacyLights instead.' ); - return ! this.useLegacyLights; - - } - - set physicallyCorrectLights( value ) { // @deprecated, r150 - - console.warn( 'THREE.WebGLRenderer: the property .physicallyCorrectLights has been removed. Set renderer.useLegacyLights instead.' ); - this.useLegacyLights = ! value; - - } - - get outputEncoding() { // @deprecated, r152 - - console.warn( 'THREE.WebGLRenderer: Property .outputEncoding has been removed. Use .outputColorSpace instead.' ); - return this.outputColorSpace === SRGBColorSpace ? sRGBEncoding : LinearEncoding; - - } - - set outputEncoding( encoding ) { // @deprecated, r152 - - console.warn( 'THREE.WebGLRenderer: Property .outputEncoding has been removed. Use .outputColorSpace instead.' ); - this.outputColorSpace = encoding === sRGBEncoding ? SRGBColorSpace : LinearSRGBColorSpace; - - } - - } - - class WebGL1Renderer extends WebGLRenderer {} - - WebGL1Renderer.prototype.isWebGL1Renderer = true; - - class FogExp2 { - - constructor( color, density = 0.00025 ) { - - this.isFogExp2 = true; - - this.name = ''; - - this.color = new Color( color ); - this.density = density; - - } - - clone() { - - return new FogExp2( this.color, this.density ); - - } - - toJSON( /* meta */ ) { - - return { - type: 'FogExp2', - color: this.color.getHex(), - density: this.density - }; - - } - - } - - class Fog { - - constructor( color, near = 1, far = 1000 ) { - - this.isFog = true; - - this.name = ''; - - this.color = new Color( color ); - - this.near = near; - this.far = far; - - } - - clone() { - - return new Fog( this.color, this.near, this.far ); - - } - - toJSON( /* meta */ ) { - - return { - type: 'Fog', - color: this.color.getHex(), - near: this.near, - far: this.far - }; - - } - - } - - class Scene extends Object3D { - - constructor() { - - super(); - - this.isScene = true; - - this.type = 'Scene'; - - this.background = null; - this.environment = null; - this.fog = null; - - this.backgroundBlurriness = 0; - this.backgroundIntensity = 1; - - this.overrideMaterial = null; - - if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) { - - __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'observe', { detail: this } ) ); - - } - - } - - copy( source, recursive ) { - - super.copy( source, recursive ); - - if ( source.background !== null ) this.background = source.background.clone(); - if ( source.environment !== null ) this.environment = source.environment.clone(); - if ( source.fog !== null ) this.fog = source.fog.clone(); - - this.backgroundBlurriness = source.backgroundBlurriness; - this.backgroundIntensity = source.backgroundIntensity; - - if ( source.overrideMaterial !== null ) this.overrideMaterial = source.overrideMaterial.clone(); - - this.matrixAutoUpdate = source.matrixAutoUpdate; - - return this; - - } - - toJSON( meta ) { - - const data = super.toJSON( meta ); - - if ( this.fog !== null ) data.object.fog = this.fog.toJSON(); - if ( this.backgroundBlurriness > 0 ) data.object.backgroundBlurriness = this.backgroundBlurriness; - if ( this.backgroundIntensity !== 1 ) data.object.backgroundIntensity = this.backgroundIntensity; - - return data; - - } - - get autoUpdate() { // @deprecated, r144 - - console.warn( 'THREE.Scene: autoUpdate was renamed to matrixWorldAutoUpdate in r144.' ); - return this.matrixWorldAutoUpdate; - - } - - set autoUpdate( value ) { // @deprecated, r144 - - console.warn( 'THREE.Scene: autoUpdate was renamed to matrixWorldAutoUpdate in r144.' ); - this.matrixWorldAutoUpdate = value; - - } - - } - - class InterleavedBuffer { - - constructor( array, stride ) { - - this.isInterleavedBuffer = true; - - this.array = array; - this.stride = stride; - this.count = array !== undefined ? array.length / stride : 0; - - this.usage = StaticDrawUsage; - this.updateRange = { offset: 0, count: - 1 }; - - this.version = 0; - - this.uuid = generateUUID(); - - } - - onUploadCallback() {} - - set needsUpdate( value ) { - - if ( value === true ) this.version ++; - - } - - setUsage( value ) { - - this.usage = value; - - return this; - - } - - copy( source ) { - - this.array = new source.array.constructor( source.array ); - this.count = source.count; - this.stride = source.stride; - this.usage = source.usage; - - return this; - - } - - copyAt( index1, attribute, index2 ) { - - index1 *= this.stride; - index2 *= attribute.stride; - - for ( let i = 0, l = this.stride; i < l; i ++ ) { - - this.array[ index1 + i ] = attribute.array[ index2 + i ]; - - } - - return this; - - } - - set( value, offset = 0 ) { - - this.array.set( value, offset ); - - return this; - - } - - clone( data ) { - - if ( data.arrayBuffers === undefined ) { - - data.arrayBuffers = {}; - - } - - if ( this.array.buffer._uuid === undefined ) { - - this.array.buffer._uuid = generateUUID(); - - } - - if ( data.arrayBuffers[ this.array.buffer._uuid ] === undefined ) { - - data.arrayBuffers[ this.array.buffer._uuid ] = this.array.slice( 0 ).buffer; - - } - - const array = new this.array.constructor( data.arrayBuffers[ this.array.buffer._uuid ] ); - - const ib = new this.constructor( array, this.stride ); - ib.setUsage( this.usage ); - - return ib; - - } - - onUpload( callback ) { - - this.onUploadCallback = callback; - - return this; - - } - - toJSON( data ) { - - if ( data.arrayBuffers === undefined ) { - - data.arrayBuffers = {}; - - } - - // generate UUID for array buffer if necessary - - if ( this.array.buffer._uuid === undefined ) { - - this.array.buffer._uuid = generateUUID(); - - } - - if ( data.arrayBuffers[ this.array.buffer._uuid ] === undefined ) { - - data.arrayBuffers[ this.array.buffer._uuid ] = Array.from( new Uint32Array( this.array.buffer ) ); - - } - - // - - return { - uuid: this.uuid, - buffer: this.array.buffer._uuid, - type: this.array.constructor.name, - stride: this.stride - }; - - } - - } - - const _vector$5 = /*@__PURE__*/ new Vector3(); - - class InterleavedBufferAttribute { - - constructor( interleavedBuffer, itemSize, offset, normalized = false ) { - - this.isInterleavedBufferAttribute = true; - - this.name = ''; - - this.data = interleavedBuffer; - this.itemSize = itemSize; - this.offset = offset; - - this.normalized = normalized; - - } - - get count() { - - return this.data.count; - - } - - get array() { - - return this.data.array; - - } - - set needsUpdate( value ) { - - this.data.needsUpdate = value; - - } - - applyMatrix4( m ) { - - for ( let i = 0, l = this.data.count; i < l; i ++ ) { - - _vector$5.fromBufferAttribute( this, i ); - - _vector$5.applyMatrix4( m ); - - this.setXYZ( i, _vector$5.x, _vector$5.y, _vector$5.z ); - - } - - return this; - - } - - applyNormalMatrix( m ) { - - for ( let i = 0, l = this.count; i < l; i ++ ) { - - _vector$5.fromBufferAttribute( this, i ); - - _vector$5.applyNormalMatrix( m ); - - this.setXYZ( i, _vector$5.x, _vector$5.y, _vector$5.z ); - - } - - return this; - - } - - transformDirection( m ) { - - for ( let i = 0, l = this.count; i < l; i ++ ) { - - _vector$5.fromBufferAttribute( this, i ); - - _vector$5.transformDirection( m ); - - this.setXYZ( i, _vector$5.x, _vector$5.y, _vector$5.z ); - - } - - return this; - - } - - setX( index, x ) { - - if ( this.normalized ) x = normalize( x, this.array ); - - this.data.array[ index * this.data.stride + this.offset ] = x; - - return this; - - } - - setY( index, y ) { - - if ( this.normalized ) y = normalize( y, this.array ); - - this.data.array[ index * this.data.stride + this.offset + 1 ] = y; - - return this; - - } - - setZ( index, z ) { - - if ( this.normalized ) z = normalize( z, this.array ); - - this.data.array[ index * this.data.stride + this.offset + 2 ] = z; - - return this; - - } - - setW( index, w ) { - - if ( this.normalized ) w = normalize( w, this.array ); - - this.data.array[ index * this.data.stride + this.offset + 3 ] = w; - - return this; - - } - - getX( index ) { - - let x = this.data.array[ index * this.data.stride + this.offset ]; - - if ( this.normalized ) x = denormalize( x, this.array ); - - return x; - - } - - getY( index ) { - - let y = this.data.array[ index * this.data.stride + this.offset + 1 ]; - - if ( this.normalized ) y = denormalize( y, this.array ); - - return y; - - } - - getZ( index ) { - - let z = this.data.array[ index * this.data.stride + this.offset + 2 ]; - - if ( this.normalized ) z = denormalize( z, this.array ); - - return z; - - } - - getW( index ) { - - let w = this.data.array[ index * this.data.stride + this.offset + 3 ]; - - if ( this.normalized ) w = denormalize( w, this.array ); - - return w; - - } - - setXY( index, x, y ) { - - index = index * this.data.stride + this.offset; - - if ( this.normalized ) { - - x = normalize( x, this.array ); - y = normalize( y, this.array ); - - } - - this.data.array[ index + 0 ] = x; - this.data.array[ index + 1 ] = y; - - return this; - - } - - setXYZ( index, x, y, z ) { - - index = index * this.data.stride + this.offset; - - if ( this.normalized ) { - - x = normalize( x, this.array ); - y = normalize( y, this.array ); - z = normalize( z, this.array ); - - } - - this.data.array[ index + 0 ] = x; - this.data.array[ index + 1 ] = y; - this.data.array[ index + 2 ] = z; - - return this; - - } - - setXYZW( index, x, y, z, w ) { - - index = index * this.data.stride + this.offset; - - if ( this.normalized ) { - - x = normalize( x, this.array ); - y = normalize( y, this.array ); - z = normalize( z, this.array ); - w = normalize( w, this.array ); - - } - - this.data.array[ index + 0 ] = x; - this.data.array[ index + 1 ] = y; - this.data.array[ index + 2 ] = z; - this.data.array[ index + 3 ] = w; - - return this; - - } - - clone( data ) { - - if ( data === undefined ) { - - console.log( 'THREE.InterleavedBufferAttribute.clone(): Cloning an interleaved buffer attribute will de-interleave buffer data.' ); - - const array = []; - - for ( let i = 0; i < this.count; i ++ ) { - - const index = i * this.data.stride + this.offset; - - for ( let j = 0; j < this.itemSize; j ++ ) { - - array.push( this.data.array[ index + j ] ); - - } - - } - - return new BufferAttribute( new this.array.constructor( array ), this.itemSize, this.normalized ); - - } else { - - if ( data.interleavedBuffers === undefined ) { - - data.interleavedBuffers = {}; - - } - - if ( data.interleavedBuffers[ this.data.uuid ] === undefined ) { - - data.interleavedBuffers[ this.data.uuid ] = this.data.clone( data ); - - } - - return new InterleavedBufferAttribute( data.interleavedBuffers[ this.data.uuid ], this.itemSize, this.offset, this.normalized ); - - } - - } - - toJSON( data ) { - - if ( data === undefined ) { - - console.log( 'THREE.InterleavedBufferAttribute.toJSON(): Serializing an interleaved buffer attribute will de-interleave buffer data.' ); - - const array = []; - - for ( let i = 0; i < this.count; i ++ ) { - - const index = i * this.data.stride + this.offset; - - for ( let j = 0; j < this.itemSize; j ++ ) { - - array.push( this.data.array[ index + j ] ); - - } - - } - - // de-interleave data and save it as an ordinary buffer attribute for now - - return { - itemSize: this.itemSize, - type: this.array.constructor.name, - array: array, - normalized: this.normalized - }; - - } else { - - // save as true interleaved attribute - - if ( data.interleavedBuffers === undefined ) { - - data.interleavedBuffers = {}; - - } - - if ( data.interleavedBuffers[ this.data.uuid ] === undefined ) { - - data.interleavedBuffers[ this.data.uuid ] = this.data.toJSON( data ); - - } - - return { - isInterleavedBufferAttribute: true, - itemSize: this.itemSize, - data: this.data.uuid, - offset: this.offset, - normalized: this.normalized - }; - - } - - } - - } - - class SpriteMaterial extends Material { - - constructor( parameters ) { - - super(); - - this.isSpriteMaterial = true; - - this.type = 'SpriteMaterial'; - - this.color = new Color( 0xffffff ); - - this.map = null; - - this.alphaMap = null; - - this.rotation = 0; - - this.sizeAttenuation = true; - - this.transparent = true; - - this.fog = true; - - this.setValues( parameters ); - - } - - copy( source ) { - - super.copy( source ); - - this.color.copy( source.color ); - - this.map = source.map; - - this.alphaMap = source.alphaMap; - - this.rotation = source.rotation; - - this.sizeAttenuation = source.sizeAttenuation; - - this.fog = source.fog; - - return this; - - } - - } - - let _geometry; - - const _intersectPoint = /*@__PURE__*/ new Vector3(); - const _worldScale = /*@__PURE__*/ new Vector3(); - const _mvPosition = /*@__PURE__*/ new Vector3(); - - const _alignedPosition = /*@__PURE__*/ new Vector2(); - const _rotatedPosition = /*@__PURE__*/ new Vector2(); - const _viewWorldMatrix = /*@__PURE__*/ new Matrix4(); - - const _vA = /*@__PURE__*/ new Vector3(); - const _vB = /*@__PURE__*/ new Vector3(); - const _vC = /*@__PURE__*/ new Vector3(); - - const _uvA = /*@__PURE__*/ new Vector2(); - const _uvB = /*@__PURE__*/ new Vector2(); - const _uvC = /*@__PURE__*/ new Vector2(); - - class Sprite extends Object3D { - - constructor( material ) { - - super(); - - this.isSprite = true; - - this.type = 'Sprite'; - - if ( _geometry === undefined ) { - - _geometry = new BufferGeometry(); - - const float32Array = new Float32Array( [ - - 0.5, - 0.5, 0, 0, 0, - 0.5, - 0.5, 0, 1, 0, - 0.5, 0.5, 0, 1, 1, - - 0.5, 0.5, 0, 0, 1 - ] ); - - const interleavedBuffer = new InterleavedBuffer( float32Array, 5 ); - - _geometry.setIndex( [ 0, 1, 2, 0, 2, 3 ] ); - _geometry.setAttribute( 'position', new InterleavedBufferAttribute( interleavedBuffer, 3, 0, false ) ); - _geometry.setAttribute( 'uv', new InterleavedBufferAttribute( interleavedBuffer, 2, 3, false ) ); - - } - - this.geometry = _geometry; - this.material = ( material !== undefined ) ? material : new SpriteMaterial(); - - this.center = new Vector2( 0.5, 0.5 ); - - } - - raycast( raycaster, intersects ) { - - if ( raycaster.camera === null ) { - - console.error( 'THREE.Sprite: "Raycaster.camera" needs to be set in order to raycast against sprites.' ); - - } - - _worldScale.setFromMatrixScale( this.matrixWorld ); - - _viewWorldMatrix.copy( raycaster.camera.matrixWorld ); - this.modelViewMatrix.multiplyMatrices( raycaster.camera.matrixWorldInverse, this.matrixWorld ); - - _mvPosition.setFromMatrixPosition( this.modelViewMatrix ); - - if ( raycaster.camera.isPerspectiveCamera && this.material.sizeAttenuation === false ) { - - _worldScale.multiplyScalar( - _mvPosition.z ); - - } - - const rotation = this.material.rotation; - let sin, cos; - - if ( rotation !== 0 ) { - - cos = Math.cos( rotation ); - sin = Math.sin( rotation ); - - } - - const center = this.center; - - transformVertex( _vA.set( - 0.5, - 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos ); - transformVertex( _vB.set( 0.5, - 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos ); - transformVertex( _vC.set( 0.5, 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos ); - - _uvA.set( 0, 0 ); - _uvB.set( 1, 0 ); - _uvC.set( 1, 1 ); - - // check first triangle - let intersect = raycaster.ray.intersectTriangle( _vA, _vB, _vC, false, _intersectPoint ); - - if ( intersect === null ) { - - // check second triangle - transformVertex( _vB.set( - 0.5, 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos ); - _uvB.set( 0, 1 ); - - intersect = raycaster.ray.intersectTriangle( _vA, _vC, _vB, false, _intersectPoint ); - if ( intersect === null ) { - - return; - - } - - } - - const distance = raycaster.ray.origin.distanceTo( _intersectPoint ); - - if ( distance < raycaster.near || distance > raycaster.far ) return; - - intersects.push( { - - distance: distance, - point: _intersectPoint.clone(), - uv: Triangle.getInterpolation( _intersectPoint, _vA, _vB, _vC, _uvA, _uvB, _uvC, new Vector2() ), - face: null, - object: this - - } ); - - } - - copy( source, recursive ) { - - super.copy( source, recursive ); - - if ( source.center !== undefined ) this.center.copy( source.center ); - - this.material = source.material; - - return this; - - } - - } - - function transformVertex( vertexPosition, mvPosition, center, scale, sin, cos ) { - - // compute position in camera space - _alignedPosition.subVectors( vertexPosition, center ).addScalar( 0.5 ).multiply( scale ); - - // to check if rotation is not zero - if ( sin !== undefined ) { - - _rotatedPosition.x = ( cos * _alignedPosition.x ) - ( sin * _alignedPosition.y ); - _rotatedPosition.y = ( sin * _alignedPosition.x ) + ( cos * _alignedPosition.y ); - - } else { - - _rotatedPosition.copy( _alignedPosition ); - - } - - - vertexPosition.copy( mvPosition ); - vertexPosition.x += _rotatedPosition.x; - vertexPosition.y += _rotatedPosition.y; - - // transform to world space - vertexPosition.applyMatrix4( _viewWorldMatrix ); - - } - - const _v1$2 = /*@__PURE__*/ new Vector3(); - const _v2$1 = /*@__PURE__*/ new Vector3(); - - class LOD extends Object3D { - - constructor() { - - super(); - - this._currentLevel = 0; - - this.type = 'LOD'; - - Object.defineProperties( this, { - levels: { - enumerable: true, - value: [] - }, - isLOD: { - value: true, - } - } ); - - this.autoUpdate = true; - - } - - copy( source ) { - - super.copy( source, false ); - - const levels = source.levels; - - for ( let i = 0, l = levels.length; i < l; i ++ ) { - - const level = levels[ i ]; - - this.addLevel( level.object.clone(), level.distance, level.hysteresis ); - - } - - this.autoUpdate = source.autoUpdate; - - return this; - - } - - addLevel( object, distance = 0, hysteresis = 0 ) { - - distance = Math.abs( distance ); - - const levels = this.levels; - - let l; - - for ( l = 0; l < levels.length; l ++ ) { - - if ( distance < levels[ l ].distance ) { - - break; - - } - - } - - levels.splice( l, 0, { distance: distance, hysteresis: hysteresis, object: object } ); - - this.add( object ); - - return this; - - } - - getCurrentLevel() { - - return this._currentLevel; - - } - - - - getObjectForDistance( distance ) { - - const levels = this.levels; - - if ( levels.length > 0 ) { - - let i, l; - - for ( i = 1, l = levels.length; i < l; i ++ ) { - - let levelDistance = levels[ i ].distance; - - if ( levels[ i ].object.visible ) { - - levelDistance -= levelDistance * levels[ i ].hysteresis; - - } - - if ( distance < levelDistance ) { - - break; - - } - - } - - return levels[ i - 1 ].object; - - } - - return null; - - } - - raycast( raycaster, intersects ) { - - const levels = this.levels; - - if ( levels.length > 0 ) { - - _v1$2.setFromMatrixPosition( this.matrixWorld ); - - const distance = raycaster.ray.origin.distanceTo( _v1$2 ); - - this.getObjectForDistance( distance ).raycast( raycaster, intersects ); - - } - - } - - update( camera ) { - - const levels = this.levels; - - if ( levels.length > 1 ) { - - _v1$2.setFromMatrixPosition( camera.matrixWorld ); - _v2$1.setFromMatrixPosition( this.matrixWorld ); - - const distance = _v1$2.distanceTo( _v2$1 ) / camera.zoom; - - levels[ 0 ].object.visible = true; - - let i, l; - - for ( i = 1, l = levels.length; i < l; i ++ ) { - - let levelDistance = levels[ i ].distance; - - if ( levels[ i ].object.visible ) { - - levelDistance -= levelDistance * levels[ i ].hysteresis; - - } - - if ( distance >= levelDistance ) { - - levels[ i - 1 ].object.visible = false; - levels[ i ].object.visible = true; - - } else { - - break; - - } - - } - - this._currentLevel = i - 1; - - for ( ; i < l; i ++ ) { - - levels[ i ].object.visible = false; - - } - - } - - } - - toJSON( meta ) { - - const data = super.toJSON( meta ); - - if ( this.autoUpdate === false ) data.object.autoUpdate = false; - - data.object.levels = []; - - const levels = this.levels; - - for ( let i = 0, l = levels.length; i < l; i ++ ) { - - const level = levels[ i ]; - - data.object.levels.push( { - object: level.object.uuid, - distance: level.distance, - hysteresis: level.hysteresis - } ); - - } - - return data; - - } - - } - - const _basePosition = /*@__PURE__*/ new Vector3(); - - const _skinIndex = /*@__PURE__*/ new Vector4(); - const _skinWeight = /*@__PURE__*/ new Vector4(); - - const _vector3 = /*@__PURE__*/ new Vector3(); - const _matrix4 = /*@__PURE__*/ new Matrix4(); - const _vertex = /*@__PURE__*/ new Vector3(); - - const _sphere$3 = /*@__PURE__*/ new Sphere(); - const _inverseMatrix$2 = /*@__PURE__*/ new Matrix4(); - const _ray$2 = /*@__PURE__*/ new Ray(); - - class SkinnedMesh extends Mesh { - - constructor( geometry, material ) { - - super( geometry, material ); - - this.isSkinnedMesh = true; - - this.type = 'SkinnedMesh'; - - this.bindMode = 'attached'; - this.bindMatrix = new Matrix4(); - this.bindMatrixInverse = new Matrix4(); - - this.boundingBox = null; - this.boundingSphere = null; - - } - - computeBoundingBox() { - - const geometry = this.geometry; - - if ( this.boundingBox === null ) { - - this.boundingBox = new Box3(); - - } - - this.boundingBox.makeEmpty(); - - const positionAttribute = geometry.getAttribute( 'position' ); - - for ( let i = 0; i < positionAttribute.count; i ++ ) { - - _vertex.fromBufferAttribute( positionAttribute, i ); - this.applyBoneTransform( i, _vertex ); - this.boundingBox.expandByPoint( _vertex ); - - } - - } - - computeBoundingSphere() { - - const geometry = this.geometry; - - if ( this.boundingSphere === null ) { - - this.boundingSphere = new Sphere(); - - } - - this.boundingSphere.makeEmpty(); - - const positionAttribute = geometry.getAttribute( 'position' ); - - for ( let i = 0; i < positionAttribute.count; i ++ ) { - - _vertex.fromBufferAttribute( positionAttribute, i ); - this.applyBoneTransform( i, _vertex ); - this.boundingSphere.expandByPoint( _vertex ); - - } - - } - - copy( source, recursive ) { - - super.copy( source, recursive ); - - this.bindMode = source.bindMode; - this.bindMatrix.copy( source.bindMatrix ); - this.bindMatrixInverse.copy( source.bindMatrixInverse ); - - this.skeleton = source.skeleton; - - if ( source.boundingBox !== null ) this.boundingBox = source.boundingBox.clone(); - if ( source.boundingSphere !== null ) this.boundingSphere = source.boundingSphere.clone(); - - return this; - - } - - raycast( raycaster, intersects ) { - - const material = this.material; - const matrixWorld = this.matrixWorld; - - if ( material === undefined ) return; - - // test with bounding sphere in world space - - if ( this.boundingSphere === null ) this.computeBoundingSphere(); - - _sphere$3.copy( this.boundingSphere ); - _sphere$3.applyMatrix4( matrixWorld ); - - if ( raycaster.ray.intersectsSphere( _sphere$3 ) === false ) return; - - // convert ray to local space of skinned mesh - - _inverseMatrix$2.copy( matrixWorld ).invert(); - _ray$2.copy( raycaster.ray ).applyMatrix4( _inverseMatrix$2 ); - - // test with bounding box in local space - - if ( this.boundingBox !== null ) { - - if ( _ray$2.intersectsBox( this.boundingBox ) === false ) return; - - } - - // test for intersections with geometry - - this._computeIntersections( raycaster, intersects, _ray$2 ); - - } - - getVertexPosition( index, target ) { - - super.getVertexPosition( index, target ); - - this.applyBoneTransform( index, target ); - - return target; - - } - - bind( skeleton, bindMatrix ) { - - this.skeleton = skeleton; - - if ( bindMatrix === undefined ) { - - this.updateMatrixWorld( true ); - - this.skeleton.calculateInverses(); - - bindMatrix = this.matrixWorld; - - } - - this.bindMatrix.copy( bindMatrix ); - this.bindMatrixInverse.copy( bindMatrix ).invert(); - - } - - pose() { - - this.skeleton.pose(); - - } - - normalizeSkinWeights() { - - const vector = new Vector4(); - - const skinWeight = this.geometry.attributes.skinWeight; - - for ( let i = 0, l = skinWeight.count; i < l; i ++ ) { - - vector.fromBufferAttribute( skinWeight, i ); - - const scale = 1.0 / vector.manhattanLength(); - - if ( scale !== Infinity ) { - - vector.multiplyScalar( scale ); - - } else { - - vector.set( 1, 0, 0, 0 ); // do something reasonable - - } - - skinWeight.setXYZW( i, vector.x, vector.y, vector.z, vector.w ); - - } - - } - - updateMatrixWorld( force ) { - - super.updateMatrixWorld( force ); - - if ( this.bindMode === 'attached' ) { - - this.bindMatrixInverse.copy( this.matrixWorld ).invert(); - - } else if ( this.bindMode === 'detached' ) { - - this.bindMatrixInverse.copy( this.bindMatrix ).invert(); - - } else { - - console.warn( 'THREE.SkinnedMesh: Unrecognized bindMode: ' + this.bindMode ); - - } - - } - - applyBoneTransform( index, vector ) { - - const skeleton = this.skeleton; - const geometry = this.geometry; - - _skinIndex.fromBufferAttribute( geometry.attributes.skinIndex, index ); - _skinWeight.fromBufferAttribute( geometry.attributes.skinWeight, index ); - - _basePosition.copy( vector ).applyMatrix4( this.bindMatrix ); - - vector.set( 0, 0, 0 ); - - for ( let i = 0; i < 4; i ++ ) { - - const weight = _skinWeight.getComponent( i ); - - if ( weight !== 0 ) { - - const boneIndex = _skinIndex.getComponent( i ); - - _matrix4.multiplyMatrices( skeleton.bones[ boneIndex ].matrixWorld, skeleton.boneInverses[ boneIndex ] ); - - vector.addScaledVector( _vector3.copy( _basePosition ).applyMatrix4( _matrix4 ), weight ); - - } - - } - - return vector.applyMatrix4( this.bindMatrixInverse ); - - } - - boneTransform( index, vector ) { // @deprecated, r151 - - console.warn( 'THREE.SkinnedMesh: .boneTransform() was renamed to .applyBoneTransform() in r151.' ); - return this.applyBoneTransform( index, vector ); - - } - - - } - - class Bone extends Object3D { - - constructor() { - - super(); - - this.isBone = true; - - this.type = 'Bone'; - - } - - } - - class DataTexture extends Texture { - - constructor( data = null, width = 1, height = 1, format, type, mapping, wrapS, wrapT, magFilter = NearestFilter, minFilter = NearestFilter, anisotropy, colorSpace ) { - - super( null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, colorSpace ); - - this.isDataTexture = true; - - this.image = { data: data, width: width, height: height }; - - this.generateMipmaps = false; - this.flipY = false; - this.unpackAlignment = 1; - - } - - } - - const _offsetMatrix = /*@__PURE__*/ new Matrix4(); - const _identityMatrix = /*@__PURE__*/ new Matrix4(); - - class Skeleton { - - constructor( bones = [], boneInverses = [] ) { - - this.uuid = generateUUID(); - - this.bones = bones.slice( 0 ); - this.boneInverses = boneInverses; - this.boneMatrices = null; - - this.boneTexture = null; - this.boneTextureSize = 0; - - this.frame = - 1; - - this.init(); - - } - - init() { - - const bones = this.bones; - const boneInverses = this.boneInverses; - - this.boneMatrices = new Float32Array( bones.length * 16 ); - - // calculate inverse bone matrices if necessary - - if ( boneInverses.length === 0 ) { - - this.calculateInverses(); - - } else { - - // handle special case - - if ( bones.length !== boneInverses.length ) { - - console.warn( 'THREE.Skeleton: Number of inverse bone matrices does not match amount of bones.' ); - - this.boneInverses = []; - - for ( let i = 0, il = this.bones.length; i < il; i ++ ) { - - this.boneInverses.push( new Matrix4() ); - - } - - } - - } - - } - - calculateInverses() { - - this.boneInverses.length = 0; - - for ( let i = 0, il = this.bones.length; i < il; i ++ ) { - - const inverse = new Matrix4(); - - if ( this.bones[ i ] ) { - - inverse.copy( this.bones[ i ].matrixWorld ).invert(); - - } - - this.boneInverses.push( inverse ); - - } - - } - - pose() { - - // recover the bind-time world matrices - - for ( let i = 0, il = this.bones.length; i < il; i ++ ) { - - const bone = this.bones[ i ]; - - if ( bone ) { - - bone.matrixWorld.copy( this.boneInverses[ i ] ).invert(); - - } - - } - - // compute the local matrices, positions, rotations and scales - - for ( let i = 0, il = this.bones.length; i < il; i ++ ) { - - const bone = this.bones[ i ]; - - if ( bone ) { - - if ( bone.parent && bone.parent.isBone ) { - - bone.matrix.copy( bone.parent.matrixWorld ).invert(); - bone.matrix.multiply( bone.matrixWorld ); - - } else { - - bone.matrix.copy( bone.matrixWorld ); - - } - - bone.matrix.decompose( bone.position, bone.quaternion, bone.scale ); - - } - - } - - } - - update() { - - const bones = this.bones; - const boneInverses = this.boneInverses; - const boneMatrices = this.boneMatrices; - const boneTexture = this.boneTexture; - - // flatten bone matrices to array - - for ( let i = 0, il = bones.length; i < il; i ++ ) { - - // compute the offset between the current and the original transform - - const matrix = bones[ i ] ? bones[ i ].matrixWorld : _identityMatrix; - - _offsetMatrix.multiplyMatrices( matrix, boneInverses[ i ] ); - _offsetMatrix.toArray( boneMatrices, i * 16 ); - - } - - if ( boneTexture !== null ) { - - boneTexture.needsUpdate = true; - - } - - } - - clone() { - - return new Skeleton( this.bones, this.boneInverses ); - - } - - computeBoneTexture() { - - // layout (1 matrix = 4 pixels) - // RGBA RGBA RGBA RGBA (=> column1, column2, column3, column4) - // with 8x8 pixel texture max 16 bones * 4 pixels = (8 * 8) - // 16x16 pixel texture max 64 bones * 4 pixels = (16 * 16) - // 32x32 pixel texture max 256 bones * 4 pixels = (32 * 32) - // 64x64 pixel texture max 1024 bones * 4 pixels = (64 * 64) - - let size = Math.sqrt( this.bones.length * 4 ); // 4 pixels needed for 1 matrix - size = ceilPowerOfTwo( size ); - size = Math.max( size, 4 ); - - const boneMatrices = new Float32Array( size * size * 4 ); // 4 floats per RGBA pixel - boneMatrices.set( this.boneMatrices ); // copy current values - - const boneTexture = new DataTexture( boneMatrices, size, size, RGBAFormat, FloatType ); - boneTexture.needsUpdate = true; - - this.boneMatrices = boneMatrices; - this.boneTexture = boneTexture; - this.boneTextureSize = size; - - return this; - - } - - getBoneByName( name ) { - - for ( let i = 0, il = this.bones.length; i < il; i ++ ) { - - const bone = this.bones[ i ]; - - if ( bone.name === name ) { - - return bone; - - } - - } - - return undefined; - - } - - dispose( ) { - - if ( this.boneTexture !== null ) { - - this.boneTexture.dispose(); - - this.boneTexture = null; - - } - - } - - fromJSON( json, bones ) { - - this.uuid = json.uuid; - - for ( let i = 0, l = json.bones.length; i < l; i ++ ) { - - const uuid = json.bones[ i ]; - let bone = bones[ uuid ]; - - if ( bone === undefined ) { - - console.warn( 'THREE.Skeleton: No bone found with UUID:', uuid ); - bone = new Bone(); - - } - - this.bones.push( bone ); - this.boneInverses.push( new Matrix4().fromArray( json.boneInverses[ i ] ) ); - - } - - this.init(); - - return this; - - } - - toJSON() { - - const data = { - metadata: { - version: 4.6, - type: 'Skeleton', - generator: 'Skeleton.toJSON' - }, - bones: [], - boneInverses: [] - }; - - data.uuid = this.uuid; - - const bones = this.bones; - const boneInverses = this.boneInverses; - - for ( let i = 0, l = bones.length; i < l; i ++ ) { - - const bone = bones[ i ]; - data.bones.push( bone.uuid ); - - const boneInverse = boneInverses[ i ]; - data.boneInverses.push( boneInverse.toArray() ); - - } - - return data; - - } - - } - - class InstancedBufferAttribute extends BufferAttribute { - - constructor( array, itemSize, normalized, meshPerAttribute = 1 ) { - - super( array, itemSize, normalized ); - - this.isInstancedBufferAttribute = true; - - this.meshPerAttribute = meshPerAttribute; - - } - - copy( source ) { - - super.copy( source ); - - this.meshPerAttribute = source.meshPerAttribute; - - return this; - - } - - toJSON() { - - const data = super.toJSON(); - - data.meshPerAttribute = this.meshPerAttribute; - - data.isInstancedBufferAttribute = true; - - return data; - - } - - } - - const _instanceLocalMatrix = /*@__PURE__*/ new Matrix4(); - const _instanceWorldMatrix = /*@__PURE__*/ new Matrix4(); - - const _instanceIntersects = []; - - const _box3 = /*@__PURE__*/ new Box3(); - const _identity = /*@__PURE__*/ new Matrix4(); - const _mesh = /*@__PURE__*/ new Mesh(); - const _sphere$2 = /*@__PURE__*/ new Sphere(); - - class InstancedMesh extends Mesh { - - constructor( geometry, material, count ) { - - super( geometry, material ); - - this.isInstancedMesh = true; - - this.instanceMatrix = new InstancedBufferAttribute( new Float32Array( count * 16 ), 16 ); - this.instanceColor = null; - - this.count = count; - - this.boundingBox = null; - this.boundingSphere = null; - - for ( let i = 0; i < count; i ++ ) { - - this.setMatrixAt( i, _identity ); - - } - - } - - computeBoundingBox() { - - const geometry = this.geometry; - const count = this.count; - - if ( this.boundingBox === null ) { - - this.boundingBox = new Box3(); - - } - - if ( geometry.boundingBox === null ) { - - geometry.computeBoundingBox(); - - } - - this.boundingBox.makeEmpty(); - - for ( let i = 0; i < count; i ++ ) { - - this.getMatrixAt( i, _instanceLocalMatrix ); - - _box3.copy( geometry.boundingBox ).applyMatrix4( _instanceLocalMatrix ); - - this.boundingBox.union( _box3 ); - - } - - } - - computeBoundingSphere() { - - const geometry = this.geometry; - const count = this.count; - - if ( this.boundingSphere === null ) { - - this.boundingSphere = new Sphere(); - - } - - if ( geometry.boundingSphere === null ) { - - geometry.computeBoundingSphere(); - - } - - this.boundingSphere.makeEmpty(); - - for ( let i = 0; i < count; i ++ ) { - - this.getMatrixAt( i, _instanceLocalMatrix ); - - _sphere$2.copy( geometry.boundingSphere ).applyMatrix4( _instanceLocalMatrix ); - - this.boundingSphere.union( _sphere$2 ); - - } - - } - - copy( source, recursive ) { - - super.copy( source, recursive ); - - this.instanceMatrix.copy( source.instanceMatrix ); - - if ( source.instanceColor !== null ) this.instanceColor = source.instanceColor.clone(); - - this.count = source.count; - - if ( source.boundingBox !== null ) this.boundingBox = source.boundingBox.clone(); - if ( source.boundingSphere !== null ) this.boundingSphere = source.boundingSphere.clone(); - - return this; - - } - - getColorAt( index, color ) { - - color.fromArray( this.instanceColor.array, index * 3 ); - - } - - getMatrixAt( index, matrix ) { - - matrix.fromArray( this.instanceMatrix.array, index * 16 ); - - } - - raycast( raycaster, intersects ) { - - const matrixWorld = this.matrixWorld; - const raycastTimes = this.count; - - _mesh.geometry = this.geometry; - _mesh.material = this.material; - - if ( _mesh.material === undefined ) return; - - // test with bounding sphere first - - if ( this.boundingSphere === null ) this.computeBoundingSphere(); - - _sphere$2.copy( this.boundingSphere ); - _sphere$2.applyMatrix4( matrixWorld ); - - if ( raycaster.ray.intersectsSphere( _sphere$2 ) === false ) return; - - // now test each instance - - for ( let instanceId = 0; instanceId < raycastTimes; instanceId ++ ) { - - // calculate the world matrix for each instance - - this.getMatrixAt( instanceId, _instanceLocalMatrix ); - - _instanceWorldMatrix.multiplyMatrices( matrixWorld, _instanceLocalMatrix ); - - // the mesh represents this single instance - - _mesh.matrixWorld = _instanceWorldMatrix; - - _mesh.raycast( raycaster, _instanceIntersects ); - - // process the result of raycast - - for ( let i = 0, l = _instanceIntersects.length; i < l; i ++ ) { - - const intersect = _instanceIntersects[ i ]; - intersect.instanceId = instanceId; - intersect.object = this; - intersects.push( intersect ); - - } - - _instanceIntersects.length = 0; - - } - - } - - setColorAt( index, color ) { - - if ( this.instanceColor === null ) { - - this.instanceColor = new InstancedBufferAttribute( new Float32Array( this.instanceMatrix.count * 3 ), 3 ); - - } - - color.toArray( this.instanceColor.array, index * 3 ); - - } - - setMatrixAt( index, matrix ) { - - matrix.toArray( this.instanceMatrix.array, index * 16 ); - - } - - updateMorphTargets() { - - } - - dispose() { - - this.dispatchEvent( { type: 'dispose' } ); - - } - - } - - class LineBasicMaterial extends Material { - - constructor( parameters ) { - - super(); - - this.isLineBasicMaterial = true; - - this.type = 'LineBasicMaterial'; - - this.color = new Color( 0xffffff ); - - this.map = null; - - this.linewidth = 1; - this.linecap = 'round'; - this.linejoin = 'round'; - - this.fog = true; - - this.setValues( parameters ); - - } - - - copy( source ) { - - super.copy( source ); - - this.color.copy( source.color ); - - this.map = source.map; - - this.linewidth = source.linewidth; - this.linecap = source.linecap; - this.linejoin = source.linejoin; - - this.fog = source.fog; - - return this; - - } - - } - - const _start$1 = /*@__PURE__*/ new Vector3(); - const _end$1 = /*@__PURE__*/ new Vector3(); - const _inverseMatrix$1 = /*@__PURE__*/ new Matrix4(); - const _ray$1 = /*@__PURE__*/ new Ray(); - const _sphere$1 = /*@__PURE__*/ new Sphere(); - - class Line extends Object3D { - - constructor( geometry = new BufferGeometry(), material = new LineBasicMaterial() ) { - - super(); - - this.isLine = true; - - this.type = 'Line'; - - this.geometry = geometry; - this.material = material; - - this.updateMorphTargets(); - - } - - copy( source, recursive ) { - - super.copy( source, recursive ); - - this.material = source.material; - this.geometry = source.geometry; - - return this; - - } - - computeLineDistances() { - - const geometry = this.geometry; - - // we assume non-indexed geometry - - if ( geometry.index === null ) { - - const positionAttribute = geometry.attributes.position; - const lineDistances = [ 0 ]; - - for ( let i = 1, l = positionAttribute.count; i < l; i ++ ) { - - _start$1.fromBufferAttribute( positionAttribute, i - 1 ); - _end$1.fromBufferAttribute( positionAttribute, i ); - - lineDistances[ i ] = lineDistances[ i - 1 ]; - lineDistances[ i ] += _start$1.distanceTo( _end$1 ); - - } - - geometry.setAttribute( 'lineDistance', new Float32BufferAttribute( lineDistances, 1 ) ); - - } else { - - console.warn( 'THREE.Line.computeLineDistances(): Computation only possible with non-indexed BufferGeometry.' ); - - } - - return this; - - } - - raycast( raycaster, intersects ) { - - const geometry = this.geometry; - const matrixWorld = this.matrixWorld; - const threshold = raycaster.params.Line.threshold; - const drawRange = geometry.drawRange; - - // Checking boundingSphere distance to ray - - if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); - - _sphere$1.copy( geometry.boundingSphere ); - _sphere$1.applyMatrix4( matrixWorld ); - _sphere$1.radius += threshold; - - if ( raycaster.ray.intersectsSphere( _sphere$1 ) === false ) return; - - // - - _inverseMatrix$1.copy( matrixWorld ).invert(); - _ray$1.copy( raycaster.ray ).applyMatrix4( _inverseMatrix$1 ); - - const localThreshold = threshold / ( ( this.scale.x + this.scale.y + this.scale.z ) / 3 ); - const localThresholdSq = localThreshold * localThreshold; - - const vStart = new Vector3(); - const vEnd = new Vector3(); - const interSegment = new Vector3(); - const interRay = new Vector3(); - const step = this.isLineSegments ? 2 : 1; - - const index = geometry.index; - const attributes = geometry.attributes; - const positionAttribute = attributes.position; - - if ( index !== null ) { - - const start = Math.max( 0, drawRange.start ); - const end = Math.min( index.count, ( drawRange.start + drawRange.count ) ); - - for ( let i = start, l = end - 1; i < l; i += step ) { - - const a = index.getX( i ); - const b = index.getX( i + 1 ); - - vStart.fromBufferAttribute( positionAttribute, a ); - vEnd.fromBufferAttribute( positionAttribute, b ); - - const distSq = _ray$1.distanceSqToSegment( vStart, vEnd, interRay, interSegment ); - - if ( distSq > localThresholdSq ) continue; - - interRay.applyMatrix4( this.matrixWorld ); //Move back to world space for distance calculation - - const distance = raycaster.ray.origin.distanceTo( interRay ); - - if ( distance < raycaster.near || distance > raycaster.far ) continue; - - intersects.push( { - - distance: distance, - // What do we want? intersection point on the ray or on the segment?? - // point: raycaster.ray.at( distance ), - point: interSegment.clone().applyMatrix4( this.matrixWorld ), - index: i, - face: null, - faceIndex: null, - object: this - - } ); - - } - - } else { - - const start = Math.max( 0, drawRange.start ); - const end = Math.min( positionAttribute.count, ( drawRange.start + drawRange.count ) ); - - for ( let i = start, l = end - 1; i < l; i += step ) { - - vStart.fromBufferAttribute( positionAttribute, i ); - vEnd.fromBufferAttribute( positionAttribute, i + 1 ); - - const distSq = _ray$1.distanceSqToSegment( vStart, vEnd, interRay, interSegment ); - - if ( distSq > localThresholdSq ) continue; - - interRay.applyMatrix4( this.matrixWorld ); //Move back to world space for distance calculation - - const distance = raycaster.ray.origin.distanceTo( interRay ); - - if ( distance < raycaster.near || distance > raycaster.far ) continue; - - intersects.push( { - - distance: distance, - // What do we want? intersection point on the ray or on the segment?? - // point: raycaster.ray.at( distance ), - point: interSegment.clone().applyMatrix4( this.matrixWorld ), - index: i, - face: null, - faceIndex: null, - object: this - - } ); - - } - - } - - } - - updateMorphTargets() { - - const geometry = this.geometry; - - const morphAttributes = geometry.morphAttributes; - const keys = Object.keys( morphAttributes ); - - if ( keys.length > 0 ) { - - const morphAttribute = morphAttributes[ keys[ 0 ] ]; - - if ( morphAttribute !== undefined ) { - - this.morphTargetInfluences = []; - this.morphTargetDictionary = {}; - - for ( let m = 0, ml = morphAttribute.length; m < ml; m ++ ) { - - const name = morphAttribute[ m ].name || String( m ); - - this.morphTargetInfluences.push( 0 ); - this.morphTargetDictionary[ name ] = m; - - } - - } - - } - - } - - } - - const _start = /*@__PURE__*/ new Vector3(); - const _end = /*@__PURE__*/ new Vector3(); - - class LineSegments extends Line { - - constructor( geometry, material ) { - - super( geometry, material ); - - this.isLineSegments = true; - - this.type = 'LineSegments'; - - } - - computeLineDistances() { - - const geometry = this.geometry; - - // we assume non-indexed geometry - - if ( geometry.index === null ) { - - const positionAttribute = geometry.attributes.position; - const lineDistances = []; - - for ( let i = 0, l = positionAttribute.count; i < l; i += 2 ) { - - _start.fromBufferAttribute( positionAttribute, i ); - _end.fromBufferAttribute( positionAttribute, i + 1 ); - - lineDistances[ i ] = ( i === 0 ) ? 0 : lineDistances[ i - 1 ]; - lineDistances[ i + 1 ] = lineDistances[ i ] + _start.distanceTo( _end ); - - } - - geometry.setAttribute( 'lineDistance', new Float32BufferAttribute( lineDistances, 1 ) ); - - } else { - - console.warn( 'THREE.LineSegments.computeLineDistances(): Computation only possible with non-indexed BufferGeometry.' ); - - } - - return this; - - } - - } - - class LineLoop extends Line { - - constructor( geometry, material ) { - - super( geometry, material ); - - this.isLineLoop = true; - - this.type = 'LineLoop'; - - } - - } - - class PointsMaterial extends Material { - - constructor( parameters ) { - - super(); - - this.isPointsMaterial = true; - - this.type = 'PointsMaterial'; - - this.color = new Color( 0xffffff ); - - this.map = null; - - this.alphaMap = null; - - this.size = 1; - this.sizeAttenuation = true; - - this.fog = true; - - this.setValues( parameters ); - - } - - copy( source ) { - - super.copy( source ); - - this.color.copy( source.color ); - - this.map = source.map; - - this.alphaMap = source.alphaMap; - - this.size = source.size; - this.sizeAttenuation = source.sizeAttenuation; - - this.fog = source.fog; - - return this; - - } - - } - - const _inverseMatrix = /*@__PURE__*/ new Matrix4(); - const _ray = /*@__PURE__*/ new Ray(); - const _sphere = /*@__PURE__*/ new Sphere(); - const _position$2 = /*@__PURE__*/ new Vector3(); - - class Points extends Object3D { - - constructor( geometry = new BufferGeometry(), material = new PointsMaterial() ) { - - super(); - - this.isPoints = true; - - this.type = 'Points'; - - this.geometry = geometry; - this.material = material; - - this.updateMorphTargets(); - - } - - copy( source, recursive ) { - - super.copy( source, recursive ); - - this.material = source.material; - this.geometry = source.geometry; - - return this; - - } - - raycast( raycaster, intersects ) { - - const geometry = this.geometry; - const matrixWorld = this.matrixWorld; - const threshold = raycaster.params.Points.threshold; - const drawRange = geometry.drawRange; - - // Checking boundingSphere distance to ray - - if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); - - _sphere.copy( geometry.boundingSphere ); - _sphere.applyMatrix4( matrixWorld ); - _sphere.radius += threshold; - - if ( raycaster.ray.intersectsSphere( _sphere ) === false ) return; - - // - - _inverseMatrix.copy( matrixWorld ).invert(); - _ray.copy( raycaster.ray ).applyMatrix4( _inverseMatrix ); - - const localThreshold = threshold / ( ( this.scale.x + this.scale.y + this.scale.z ) / 3 ); - const localThresholdSq = localThreshold * localThreshold; - - const index = geometry.index; - const attributes = geometry.attributes; - const positionAttribute = attributes.position; - - if ( index !== null ) { - - const start = Math.max( 0, drawRange.start ); - const end = Math.min( index.count, ( drawRange.start + drawRange.count ) ); - - for ( let i = start, il = end; i < il; i ++ ) { - - const a = index.getX( i ); - - _position$2.fromBufferAttribute( positionAttribute, a ); - - testPoint( _position$2, a, localThresholdSq, matrixWorld, raycaster, intersects, this ); - - } - - } else { - - const start = Math.max( 0, drawRange.start ); - const end = Math.min( positionAttribute.count, ( drawRange.start + drawRange.count ) ); - - for ( let i = start, l = end; i < l; i ++ ) { - - _position$2.fromBufferAttribute( positionAttribute, i ); - - testPoint( _position$2, i, localThresholdSq, matrixWorld, raycaster, intersects, this ); - - } - - } - - } - - updateMorphTargets() { - - const geometry = this.geometry; - - const morphAttributes = geometry.morphAttributes; - const keys = Object.keys( morphAttributes ); - - if ( keys.length > 0 ) { - - const morphAttribute = morphAttributes[ keys[ 0 ] ]; - - if ( morphAttribute !== undefined ) { - - this.morphTargetInfluences = []; - this.morphTargetDictionary = {}; - - for ( let m = 0, ml = morphAttribute.length; m < ml; m ++ ) { - - const name = morphAttribute[ m ].name || String( m ); - - this.morphTargetInfluences.push( 0 ); - this.morphTargetDictionary[ name ] = m; - - } - - } - - } - - } - - } - - function testPoint( point, index, localThresholdSq, matrixWorld, raycaster, intersects, object ) { - - const rayPointDistanceSq = _ray.distanceSqToPoint( point ); - - if ( rayPointDistanceSq < localThresholdSq ) { - - const intersectPoint = new Vector3(); - - _ray.closestPointToPoint( point, intersectPoint ); - intersectPoint.applyMatrix4( matrixWorld ); - - const distance = raycaster.ray.origin.distanceTo( intersectPoint ); - - if ( distance < raycaster.near || distance > raycaster.far ) return; - - intersects.push( { - - distance: distance, - distanceToRay: Math.sqrt( rayPointDistanceSq ), - point: intersectPoint, - index: index, - face: null, - object: object - - } ); - - } - - } - - class VideoTexture extends Texture { - - constructor( video, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) { - - super( video, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); - - this.isVideoTexture = true; - - this.minFilter = minFilter !== undefined ? minFilter : LinearFilter; - this.magFilter = magFilter !== undefined ? magFilter : LinearFilter; - - this.generateMipmaps = false; - - const scope = this; - - function updateVideo() { - - scope.needsUpdate = true; - video.requestVideoFrameCallback( updateVideo ); - - } - - if ( 'requestVideoFrameCallback' in video ) { - - video.requestVideoFrameCallback( updateVideo ); - - } - - } - - clone() { - - return new this.constructor( this.image ).copy( this ); - - } - - update() { - - const video = this.image; - const hasVideoFrameCallback = 'requestVideoFrameCallback' in video; - - if ( hasVideoFrameCallback === false && video.readyState >= video.HAVE_CURRENT_DATA ) { - - this.needsUpdate = true; - - } - - } - - } - - class FramebufferTexture extends Texture { - - constructor( width, height ) { - - super( { width, height } ); - - this.isFramebufferTexture = true; - - this.magFilter = NearestFilter; - this.minFilter = NearestFilter; - - this.generateMipmaps = false; - - this.needsUpdate = true; - - } - - } - - class CompressedTexture extends Texture { - - constructor( mipmaps, width, height, format, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, colorSpace ) { - - super( null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, colorSpace ); - - this.isCompressedTexture = true; - - this.image = { width: width, height: height }; - this.mipmaps = mipmaps; - - // no flipping for cube textures - // (also flipping doesn't work for compressed textures ) - - this.flipY = false; - - // can't generate mipmaps for compressed textures - // mips must be embedded in DDS files - - this.generateMipmaps = false; - - } - - } - - class CompressedArrayTexture extends CompressedTexture { - - constructor( mipmaps, width, height, depth, format, type ) { - - super( mipmaps, width, height, format, type ); - - this.isCompressedArrayTexture = true; - this.image.depth = depth; - this.wrapR = ClampToEdgeWrapping; - - } - - } - - class CanvasTexture extends Texture { - - constructor( canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) { - - super( canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); - - this.isCanvasTexture = true; - - this.needsUpdate = true; - - } - - } - - /** - * Extensible curve object. - * - * Some common of curve methods: - * .getPoint( t, optionalTarget ), .getTangent( t, optionalTarget ) - * .getPointAt( u, optionalTarget ), .getTangentAt( u, optionalTarget ) - * .getPoints(), .getSpacedPoints() - * .getLength() - * .updateArcLengths() - * - * This following curves inherit from THREE.Curve: - * - * -- 2D curves -- - * THREE.ArcCurve - * THREE.CubicBezierCurve - * THREE.EllipseCurve - * THREE.LineCurve - * THREE.QuadraticBezierCurve - * THREE.SplineCurve - * - * -- 3D curves -- - * THREE.CatmullRomCurve3 - * THREE.CubicBezierCurve3 - * THREE.LineCurve3 - * THREE.QuadraticBezierCurve3 - * - * A series of curves can be represented as a THREE.CurvePath. - * - **/ - - class Curve { - - constructor() { - - this.type = 'Curve'; - - this.arcLengthDivisions = 200; - - } - - // Virtual base class method to overwrite and implement in subclasses - // - t [0 .. 1] - - getPoint( /* t, optionalTarget */ ) { - - console.warn( 'THREE.Curve: .getPoint() not implemented.' ); - return null; - - } - - // Get point at relative position in curve according to arc length - // - u [0 .. 1] - - getPointAt( u, optionalTarget ) { - - const t = this.getUtoTmapping( u ); - return this.getPoint( t, optionalTarget ); - - } - - // Get sequence of points using getPoint( t ) - - getPoints( divisions = 5 ) { - - const points = []; - - for ( let d = 0; d <= divisions; d ++ ) { - - points.push( this.getPoint( d / divisions ) ); - - } - - return points; - - } - - // Get sequence of points using getPointAt( u ) - - getSpacedPoints( divisions = 5 ) { - - const points = []; - - for ( let d = 0; d <= divisions; d ++ ) { - - points.push( this.getPointAt( d / divisions ) ); - - } - - return points; - - } - - // Get total curve arc length - - getLength() { - - const lengths = this.getLengths(); - return lengths[ lengths.length - 1 ]; - - } - - // Get list of cumulative segment lengths - - getLengths( divisions = this.arcLengthDivisions ) { - - if ( this.cacheArcLengths && - ( this.cacheArcLengths.length === divisions + 1 ) && - ! this.needsUpdate ) { - - return this.cacheArcLengths; - - } - - this.needsUpdate = false; - - const cache = []; - let current, last = this.getPoint( 0 ); - let sum = 0; - - cache.push( 0 ); - - for ( let p = 1; p <= divisions; p ++ ) { - - current = this.getPoint( p / divisions ); - sum += current.distanceTo( last ); - cache.push( sum ); - last = current; - - } - - this.cacheArcLengths = cache; - - return cache; // { sums: cache, sum: sum }; Sum is in the last element. - - } - - updateArcLengths() { - - this.needsUpdate = true; - this.getLengths(); - - } - - // Given u ( 0 .. 1 ), get a t to find p. This gives you points which are equidistant - - getUtoTmapping( u, distance ) { - - const arcLengths = this.getLengths(); - - let i = 0; - const il = arcLengths.length; - - let targetArcLength; // The targeted u distance value to get - - if ( distance ) { - - targetArcLength = distance; - - } else { - - targetArcLength = u * arcLengths[ il - 1 ]; - - } - - // binary search for the index with largest value smaller than target u distance - - let low = 0, high = il - 1, comparison; - - while ( low <= high ) { - - i = Math.floor( low + ( high - low ) / 2 ); // less likely to overflow, though probably not issue here, JS doesn't really have integers, all numbers are floats - - comparison = arcLengths[ i ] - targetArcLength; - - if ( comparison < 0 ) { - - low = i + 1; - - } else if ( comparison > 0 ) { - - high = i - 1; - - } else { - - high = i; - break; - - // DONE - - } - - } - - i = high; - - if ( arcLengths[ i ] === targetArcLength ) { - - return i / ( il - 1 ); - - } - - // we could get finer grain at lengths, or use simple interpolation between two points - - const lengthBefore = arcLengths[ i ]; - const lengthAfter = arcLengths[ i + 1 ]; - - const segmentLength = lengthAfter - lengthBefore; - - // determine where we are between the 'before' and 'after' points - - const segmentFraction = ( targetArcLength - lengthBefore ) / segmentLength; - - // add that fractional amount to t - - const t = ( i + segmentFraction ) / ( il - 1 ); - - return t; - - } - - // Returns a unit vector tangent at t - // In case any sub curve does not implement its tangent derivation, - // 2 points a small delta apart will be used to find its gradient - // which seems to give a reasonable approximation - - getTangent( t, optionalTarget ) { - - const delta = 0.0001; - let t1 = t - delta; - let t2 = t + delta; - - // Capping in case of danger - - if ( t1 < 0 ) t1 = 0; - if ( t2 > 1 ) t2 = 1; - - const pt1 = this.getPoint( t1 ); - const pt2 = this.getPoint( t2 ); - - const tangent = optionalTarget || ( ( pt1.isVector2 ) ? new Vector2() : new Vector3() ); - - tangent.copy( pt2 ).sub( pt1 ).normalize(); - - return tangent; - - } - - getTangentAt( u, optionalTarget ) { - - const t = this.getUtoTmapping( u ); - return this.getTangent( t, optionalTarget ); - - } - - computeFrenetFrames( segments, closed ) { - - // see http://www.cs.indiana.edu/pub/techreports/TR425.pdf - - const normal = new Vector3(); - - const tangents = []; - const normals = []; - const binormals = []; - - const vec = new Vector3(); - const mat = new Matrix4(); - - // compute the tangent vectors for each segment on the curve - - for ( let i = 0; i <= segments; i ++ ) { - - const u = i / segments; - - tangents[ i ] = this.getTangentAt( u, new Vector3() ); - - } - - // select an initial normal vector perpendicular to the first tangent vector, - // and in the direction of the minimum tangent xyz component - - normals[ 0 ] = new Vector3(); - binormals[ 0 ] = new Vector3(); - let min = Number.MAX_VALUE; - const tx = Math.abs( tangents[ 0 ].x ); - const ty = Math.abs( tangents[ 0 ].y ); - const tz = Math.abs( tangents[ 0 ].z ); - - if ( tx <= min ) { - - min = tx; - normal.set( 1, 0, 0 ); - - } - - if ( ty <= min ) { - - min = ty; - normal.set( 0, 1, 0 ); - - } - - if ( tz <= min ) { - - normal.set( 0, 0, 1 ); - - } - - vec.crossVectors( tangents[ 0 ], normal ).normalize(); - - normals[ 0 ].crossVectors( tangents[ 0 ], vec ); - binormals[ 0 ].crossVectors( tangents[ 0 ], normals[ 0 ] ); - - - // compute the slowly-varying normal and binormal vectors for each segment on the curve - - for ( let i = 1; i <= segments; i ++ ) { - - normals[ i ] = normals[ i - 1 ].clone(); - - binormals[ i ] = binormals[ i - 1 ].clone(); - - vec.crossVectors( tangents[ i - 1 ], tangents[ i ] ); - - if ( vec.length() > Number.EPSILON ) { - - vec.normalize(); - - const theta = Math.acos( clamp( tangents[ i - 1 ].dot( tangents[ i ] ), - 1, 1 ) ); // clamp for floating pt errors - - normals[ i ].applyMatrix4( mat.makeRotationAxis( vec, theta ) ); - - } - - binormals[ i ].crossVectors( tangents[ i ], normals[ i ] ); - - } - - // if the curve is closed, postprocess the vectors so the first and last normal vectors are the same - - if ( closed === true ) { - - let theta = Math.acos( clamp( normals[ 0 ].dot( normals[ segments ] ), - 1, 1 ) ); - theta /= segments; - - if ( tangents[ 0 ].dot( vec.crossVectors( normals[ 0 ], normals[ segments ] ) ) > 0 ) { - - theta = - theta; - - } - - for ( let i = 1; i <= segments; i ++ ) { - - // twist a little... - normals[ i ].applyMatrix4( mat.makeRotationAxis( tangents[ i ], theta * i ) ); - binormals[ i ].crossVectors( tangents[ i ], normals[ i ] ); - - } - - } - - return { - tangents: tangents, - normals: normals, - binormals: binormals - }; - - } - - clone() { - - return new this.constructor().copy( this ); - - } - - copy( source ) { - - this.arcLengthDivisions = source.arcLengthDivisions; - - return this; - - } - - toJSON() { - - const data = { - metadata: { - version: 4.6, - type: 'Curve', - generator: 'Curve.toJSON' - } - }; - - data.arcLengthDivisions = this.arcLengthDivisions; - data.type = this.type; - - return data; - - } - - fromJSON( json ) { - - this.arcLengthDivisions = json.arcLengthDivisions; - - return this; - - } - - } - - class EllipseCurve extends Curve { - - constructor( aX = 0, aY = 0, xRadius = 1, yRadius = 1, aStartAngle = 0, aEndAngle = Math.PI * 2, aClockwise = false, aRotation = 0 ) { - - super(); - - this.isEllipseCurve = true; - - this.type = 'EllipseCurve'; - - this.aX = aX; - this.aY = aY; - - this.xRadius = xRadius; - this.yRadius = yRadius; - - this.aStartAngle = aStartAngle; - this.aEndAngle = aEndAngle; - - this.aClockwise = aClockwise; - - this.aRotation = aRotation; - - } - - getPoint( t, optionalTarget ) { - - const point = optionalTarget || new Vector2(); - - const twoPi = Math.PI * 2; - let deltaAngle = this.aEndAngle - this.aStartAngle; - const samePoints = Math.abs( deltaAngle ) < Number.EPSILON; - - // ensures that deltaAngle is 0 .. 2 PI - while ( deltaAngle < 0 ) deltaAngle += twoPi; - while ( deltaAngle > twoPi ) deltaAngle -= twoPi; - - if ( deltaAngle < Number.EPSILON ) { - - if ( samePoints ) { - - deltaAngle = 0; - - } else { - - deltaAngle = twoPi; - - } - - } - - if ( this.aClockwise === true && ! samePoints ) { - - if ( deltaAngle === twoPi ) { - - deltaAngle = - twoPi; - - } else { - - deltaAngle = deltaAngle - twoPi; - - } - - } - - const angle = this.aStartAngle + t * deltaAngle; - let x = this.aX + this.xRadius * Math.cos( angle ); - let y = this.aY + this.yRadius * Math.sin( angle ); - - if ( this.aRotation !== 0 ) { - - const cos = Math.cos( this.aRotation ); - const sin = Math.sin( this.aRotation ); - - const tx = x - this.aX; - const ty = y - this.aY; - - // Rotate the point about the center of the ellipse. - x = tx * cos - ty * sin + this.aX; - y = tx * sin + ty * cos + this.aY; - - } - - return point.set( x, y ); - - } - - copy( source ) { - - super.copy( source ); - - this.aX = source.aX; - this.aY = source.aY; - - this.xRadius = source.xRadius; - this.yRadius = source.yRadius; - - this.aStartAngle = source.aStartAngle; - this.aEndAngle = source.aEndAngle; - - this.aClockwise = source.aClockwise; - - this.aRotation = source.aRotation; - - return this; - - } - - toJSON() { - - const data = super.toJSON(); - - data.aX = this.aX; - data.aY = this.aY; - - data.xRadius = this.xRadius; - data.yRadius = this.yRadius; - - data.aStartAngle = this.aStartAngle; - data.aEndAngle = this.aEndAngle; - - data.aClockwise = this.aClockwise; - - data.aRotation = this.aRotation; - - return data; - - } - - fromJSON( json ) { - - super.fromJSON( json ); - - this.aX = json.aX; - this.aY = json.aY; - - this.xRadius = json.xRadius; - this.yRadius = json.yRadius; - - this.aStartAngle = json.aStartAngle; - this.aEndAngle = json.aEndAngle; - - this.aClockwise = json.aClockwise; - - this.aRotation = json.aRotation; - - return this; - - } - - } - - class ArcCurve extends EllipseCurve { - - constructor( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) { - - super( aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise ); - - this.isArcCurve = true; - - this.type = 'ArcCurve'; - - } - - } - - /** - * Centripetal CatmullRom Curve - which is useful for avoiding - * cusps and self-intersections in non-uniform catmull rom curves. - * http://www.cemyuksel.com/research/catmullrom_param/catmullrom.pdf - * - * curve.type accepts centripetal(default), chordal and catmullrom - * curve.tension is used for catmullrom which defaults to 0.5 - */ - - - /* - Based on an optimized c++ solution in - - http://stackoverflow.com/questions/9489736/catmull-rom-curve-with-no-cusps-and-no-self-intersections/ - - http://ideone.com/NoEbVM - - This CubicPoly class could be used for reusing some variables and calculations, - but for three.js curve use, it could be possible inlined and flatten into a single function call - which can be placed in CurveUtils. - */ - - function CubicPoly() { - - let c0 = 0, c1 = 0, c2 = 0, c3 = 0; - - /* - * Compute coefficients for a cubic polynomial - * p(s) = c0 + c1*s + c2*s^2 + c3*s^3 - * such that - * p(0) = x0, p(1) = x1 - * and - * p'(0) = t0, p'(1) = t1. - */ - function init( x0, x1, t0, t1 ) { - - c0 = x0; - c1 = t0; - c2 = - 3 * x0 + 3 * x1 - 2 * t0 - t1; - c3 = 2 * x0 - 2 * x1 + t0 + t1; - - } - - return { - - initCatmullRom: function ( x0, x1, x2, x3, tension ) { - - init( x1, x2, tension * ( x2 - x0 ), tension * ( x3 - x1 ) ); - - }, - - initNonuniformCatmullRom: function ( x0, x1, x2, x3, dt0, dt1, dt2 ) { - - // compute tangents when parameterized in [t1,t2] - let t1 = ( x1 - x0 ) / dt0 - ( x2 - x0 ) / ( dt0 + dt1 ) + ( x2 - x1 ) / dt1; - let t2 = ( x2 - x1 ) / dt1 - ( x3 - x1 ) / ( dt1 + dt2 ) + ( x3 - x2 ) / dt2; - - // rescale tangents for parametrization in [0,1] - t1 *= dt1; - t2 *= dt1; - - init( x1, x2, t1, t2 ); - - }, - - calc: function ( t ) { - - const t2 = t * t; - const t3 = t2 * t; - return c0 + c1 * t + c2 * t2 + c3 * t3; - - } - - }; - - } - - // - - const tmp = /*@__PURE__*/ new Vector3(); - const px = /*@__PURE__*/ new CubicPoly(); - const py = /*@__PURE__*/ new CubicPoly(); - const pz = /*@__PURE__*/ new CubicPoly(); - - class CatmullRomCurve3 extends Curve { - - constructor( points = [], closed = false, curveType = 'centripetal', tension = 0.5 ) { - - super(); - - this.isCatmullRomCurve3 = true; - - this.type = 'CatmullRomCurve3'; - - this.points = points; - this.closed = closed; - this.curveType = curveType; - this.tension = tension; - - } - - getPoint( t, optionalTarget = new Vector3() ) { - - const point = optionalTarget; - - const points = this.points; - const l = points.length; - - const p = ( l - ( this.closed ? 0 : 1 ) ) * t; - let intPoint = Math.floor( p ); - let weight = p - intPoint; - - if ( this.closed ) { - - intPoint += intPoint > 0 ? 0 : ( Math.floor( Math.abs( intPoint ) / l ) + 1 ) * l; - - } else if ( weight === 0 && intPoint === l - 1 ) { - - intPoint = l - 2; - weight = 1; - - } - - let p0, p3; // 4 points (p1 & p2 defined below) - - if ( this.closed || intPoint > 0 ) { - - p0 = points[ ( intPoint - 1 ) % l ]; - - } else { - - // extrapolate first point - tmp.subVectors( points[ 0 ], points[ 1 ] ).add( points[ 0 ] ); - p0 = tmp; - - } - - const p1 = points[ intPoint % l ]; - const p2 = points[ ( intPoint + 1 ) % l ]; - - if ( this.closed || intPoint + 2 < l ) { - - p3 = points[ ( intPoint + 2 ) % l ]; - - } else { - - // extrapolate last point - tmp.subVectors( points[ l - 1 ], points[ l - 2 ] ).add( points[ l - 1 ] ); - p3 = tmp; - - } - - if ( this.curveType === 'centripetal' || this.curveType === 'chordal' ) { - - // init Centripetal / Chordal Catmull-Rom - const pow = this.curveType === 'chordal' ? 0.5 : 0.25; - let dt0 = Math.pow( p0.distanceToSquared( p1 ), pow ); - let dt1 = Math.pow( p1.distanceToSquared( p2 ), pow ); - let dt2 = Math.pow( p2.distanceToSquared( p3 ), pow ); - - // safety check for repeated points - if ( dt1 < 1e-4 ) dt1 = 1.0; - if ( dt0 < 1e-4 ) dt0 = dt1; - if ( dt2 < 1e-4 ) dt2 = dt1; - - px.initNonuniformCatmullRom( p0.x, p1.x, p2.x, p3.x, dt0, dt1, dt2 ); - py.initNonuniformCatmullRom( p0.y, p1.y, p2.y, p3.y, dt0, dt1, dt2 ); - pz.initNonuniformCatmullRom( p0.z, p1.z, p2.z, p3.z, dt0, dt1, dt2 ); - - } else if ( this.curveType === 'catmullrom' ) { - - px.initCatmullRom( p0.x, p1.x, p2.x, p3.x, this.tension ); - py.initCatmullRom( p0.y, p1.y, p2.y, p3.y, this.tension ); - pz.initCatmullRom( p0.z, p1.z, p2.z, p3.z, this.tension ); - - } - - point.set( - px.calc( weight ), - py.calc( weight ), - pz.calc( weight ) - ); - - return point; - - } - - copy( source ) { - - super.copy( source ); - - this.points = []; - - for ( let i = 0, l = source.points.length; i < l; i ++ ) { - - const point = source.points[ i ]; - - this.points.push( point.clone() ); - - } - - this.closed = source.closed; - this.curveType = source.curveType; - this.tension = source.tension; - - return this; - - } - - toJSON() { - - const data = super.toJSON(); - - data.points = []; - - for ( let i = 0, l = this.points.length; i < l; i ++ ) { - - const point = this.points[ i ]; - data.points.push( point.toArray() ); - - } - - data.closed = this.closed; - data.curveType = this.curveType; - data.tension = this.tension; - - return data; - - } - - fromJSON( json ) { - - super.fromJSON( json ); - - this.points = []; - - for ( let i = 0, l = json.points.length; i < l; i ++ ) { - - const point = json.points[ i ]; - this.points.push( new Vector3().fromArray( point ) ); - - } - - this.closed = json.closed; - this.curveType = json.curveType; - this.tension = json.tension; - - return this; - - } - - } - - /** - * Bezier Curves formulas obtained from - * https://en.wikipedia.org/wiki/B%C3%A9zier_curve - */ - - function CatmullRom( t, p0, p1, p2, p3 ) { - - const v0 = ( p2 - p0 ) * 0.5; - const v1 = ( p3 - p1 ) * 0.5; - const t2 = t * t; - const t3 = t * t2; - return ( 2 * p1 - 2 * p2 + v0 + v1 ) * t3 + ( - 3 * p1 + 3 * p2 - 2 * v0 - v1 ) * t2 + v0 * t + p1; - - } - - // - - function QuadraticBezierP0( t, p ) { - - const k = 1 - t; - return k * k * p; - - } - - function QuadraticBezierP1( t, p ) { - - return 2 * ( 1 - t ) * t * p; - - } - - function QuadraticBezierP2( t, p ) { - - return t * t * p; - - } - - function QuadraticBezier( t, p0, p1, p2 ) { - - return QuadraticBezierP0( t, p0 ) + QuadraticBezierP1( t, p1 ) + - QuadraticBezierP2( t, p2 ); - - } - - // - - function CubicBezierP0( t, p ) { - - const k = 1 - t; - return k * k * k * p; - - } - - function CubicBezierP1( t, p ) { - - const k = 1 - t; - return 3 * k * k * t * p; - - } - - function CubicBezierP2( t, p ) { - - return 3 * ( 1 - t ) * t * t * p; - - } - - function CubicBezierP3( t, p ) { - - return t * t * t * p; - - } - - function CubicBezier( t, p0, p1, p2, p3 ) { - - return CubicBezierP0( t, p0 ) + CubicBezierP1( t, p1 ) + CubicBezierP2( t, p2 ) + - CubicBezierP3( t, p3 ); - - } - - class CubicBezierCurve extends Curve { - - constructor( v0 = new Vector2(), v1 = new Vector2(), v2 = new Vector2(), v3 = new Vector2() ) { - - super(); - - this.isCubicBezierCurve = true; - - this.type = 'CubicBezierCurve'; - - this.v0 = v0; - this.v1 = v1; - this.v2 = v2; - this.v3 = v3; - - } - - getPoint( t, optionalTarget = new Vector2() ) { - - const point = optionalTarget; - - const v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3; - - point.set( - CubicBezier( t, v0.x, v1.x, v2.x, v3.x ), - CubicBezier( t, v0.y, v1.y, v2.y, v3.y ) - ); - - return point; - - } - - copy( source ) { - - super.copy( source ); - - this.v0.copy( source.v0 ); - this.v1.copy( source.v1 ); - this.v2.copy( source.v2 ); - this.v3.copy( source.v3 ); - - return this; - - } - - toJSON() { - - const data = super.toJSON(); - - data.v0 = this.v0.toArray(); - data.v1 = this.v1.toArray(); - data.v2 = this.v2.toArray(); - data.v3 = this.v3.toArray(); - - return data; - - } - - fromJSON( json ) { - - super.fromJSON( json ); - - this.v0.fromArray( json.v0 ); - this.v1.fromArray( json.v1 ); - this.v2.fromArray( json.v2 ); - this.v3.fromArray( json.v3 ); - - return this; - - } - - } - - class CubicBezierCurve3 extends Curve { - - constructor( v0 = new Vector3(), v1 = new Vector3(), v2 = new Vector3(), v3 = new Vector3() ) { - - super(); - - this.isCubicBezierCurve3 = true; - - this.type = 'CubicBezierCurve3'; - - this.v0 = v0; - this.v1 = v1; - this.v2 = v2; - this.v3 = v3; - - } - - getPoint( t, optionalTarget = new Vector3() ) { - - const point = optionalTarget; - - const v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3; - - point.set( - CubicBezier( t, v0.x, v1.x, v2.x, v3.x ), - CubicBezier( t, v0.y, v1.y, v2.y, v3.y ), - CubicBezier( t, v0.z, v1.z, v2.z, v3.z ) - ); - - return point; - - } - - copy( source ) { - - super.copy( source ); - - this.v0.copy( source.v0 ); - this.v1.copy( source.v1 ); - this.v2.copy( source.v2 ); - this.v3.copy( source.v3 ); - - return this; - - } - - toJSON() { - - const data = super.toJSON(); - - data.v0 = this.v0.toArray(); - data.v1 = this.v1.toArray(); - data.v2 = this.v2.toArray(); - data.v3 = this.v3.toArray(); - - return data; - - } - - fromJSON( json ) { - - super.fromJSON( json ); - - this.v0.fromArray( json.v0 ); - this.v1.fromArray( json.v1 ); - this.v2.fromArray( json.v2 ); - this.v3.fromArray( json.v3 ); - - return this; - - } - - } - - class LineCurve extends Curve { - - constructor( v1 = new Vector2(), v2 = new Vector2() ) { - - super(); - - this.isLineCurve = true; - - this.type = 'LineCurve'; - - this.v1 = v1; - this.v2 = v2; - - } - - getPoint( t, optionalTarget = new Vector2() ) { - - const point = optionalTarget; - - if ( t === 1 ) { - - point.copy( this.v2 ); - - } else { - - point.copy( this.v2 ).sub( this.v1 ); - point.multiplyScalar( t ).add( this.v1 ); - - } - - return point; - - } - - // Line curve is linear, so we can overwrite default getPointAt - getPointAt( u, optionalTarget ) { - - return this.getPoint( u, optionalTarget ); - - } - - getTangent( t, optionalTarget = new Vector2() ) { - - return optionalTarget.subVectors( this.v2, this.v1 ).normalize(); - - } - - getTangentAt( u, optionalTarget ) { - - return this.getTangent( u, optionalTarget ); - - } - - copy( source ) { - - super.copy( source ); - - this.v1.copy( source.v1 ); - this.v2.copy( source.v2 ); - - return this; - - } - - toJSON() { - - const data = super.toJSON(); - - data.v1 = this.v1.toArray(); - data.v2 = this.v2.toArray(); - - return data; - - } - - fromJSON( json ) { - - super.fromJSON( json ); - - this.v1.fromArray( json.v1 ); - this.v2.fromArray( json.v2 ); - - return this; - - } - - } - - class LineCurve3 extends Curve { - - constructor( v1 = new Vector3(), v2 = new Vector3() ) { - - super(); - - this.isLineCurve3 = true; - - this.type = 'LineCurve3'; - - this.v1 = v1; - this.v2 = v2; - - } - getPoint( t, optionalTarget = new Vector3() ) { - - const point = optionalTarget; - - if ( t === 1 ) { - - point.copy( this.v2 ); - - } else { - - point.copy( this.v2 ).sub( this.v1 ); - point.multiplyScalar( t ).add( this.v1 ); - - } - - return point; - - } - // Line curve is linear, so we can overwrite default getPointAt - getPointAt( u, optionalTarget ) { - - return this.getPoint( u, optionalTarget ); - - } - - getTangent( t, optionalTarget = new Vector3() ) { - - return optionalTarget.subVectors( this.v2, this.v1 ).normalize(); - - } - - getTangentAt( u, optionalTarget ) { - - return this.getTangent( u, optionalTarget ); - - } - - copy( source ) { - - super.copy( source ); - - this.v1.copy( source.v1 ); - this.v2.copy( source.v2 ); - - return this; - - } - toJSON() { - - const data = super.toJSON(); - - data.v1 = this.v1.toArray(); - data.v2 = this.v2.toArray(); - - return data; - - } - fromJSON( json ) { - - super.fromJSON( json ); - - this.v1.fromArray( json.v1 ); - this.v2.fromArray( json.v2 ); - - return this; - - } - - } - - class QuadraticBezierCurve extends Curve { - - constructor( v0 = new Vector2(), v1 = new Vector2(), v2 = new Vector2() ) { - - super(); - - this.isQuadraticBezierCurve = true; - - this.type = 'QuadraticBezierCurve'; - - this.v0 = v0; - this.v1 = v1; - this.v2 = v2; - - } - - getPoint( t, optionalTarget = new Vector2() ) { - - const point = optionalTarget; - - const v0 = this.v0, v1 = this.v1, v2 = this.v2; - - point.set( - QuadraticBezier( t, v0.x, v1.x, v2.x ), - QuadraticBezier( t, v0.y, v1.y, v2.y ) - ); - - return point; - - } - - copy( source ) { - - super.copy( source ); - - this.v0.copy( source.v0 ); - this.v1.copy( source.v1 ); - this.v2.copy( source.v2 ); - - return this; - - } - - toJSON() { - - const data = super.toJSON(); - - data.v0 = this.v0.toArray(); - data.v1 = this.v1.toArray(); - data.v2 = this.v2.toArray(); - - return data; - - } - - fromJSON( json ) { - - super.fromJSON( json ); - - this.v0.fromArray( json.v0 ); - this.v1.fromArray( json.v1 ); - this.v2.fromArray( json.v2 ); - - return this; - - } - - } - - class QuadraticBezierCurve3 extends Curve { - - constructor( v0 = new Vector3(), v1 = new Vector3(), v2 = new Vector3() ) { - - super(); - - this.isQuadraticBezierCurve3 = true; - - this.type = 'QuadraticBezierCurve3'; - - this.v0 = v0; - this.v1 = v1; - this.v2 = v2; - - } - - getPoint( t, optionalTarget = new Vector3() ) { - - const point = optionalTarget; - - const v0 = this.v0, v1 = this.v1, v2 = this.v2; - - point.set( - QuadraticBezier( t, v0.x, v1.x, v2.x ), - QuadraticBezier( t, v0.y, v1.y, v2.y ), - QuadraticBezier( t, v0.z, v1.z, v2.z ) - ); - - return point; - - } - - copy( source ) { - - super.copy( source ); - - this.v0.copy( source.v0 ); - this.v1.copy( source.v1 ); - this.v2.copy( source.v2 ); - - return this; - - } - - toJSON() { - - const data = super.toJSON(); - - data.v0 = this.v0.toArray(); - data.v1 = this.v1.toArray(); - data.v2 = this.v2.toArray(); - - return data; - - } - - fromJSON( json ) { - - super.fromJSON( json ); - - this.v0.fromArray( json.v0 ); - this.v1.fromArray( json.v1 ); - this.v2.fromArray( json.v2 ); - - return this; - - } - - } - - class SplineCurve extends Curve { - - constructor( points = [] ) { - - super(); - - this.isSplineCurve = true; - - this.type = 'SplineCurve'; - - this.points = points; - - } - - getPoint( t, optionalTarget = new Vector2() ) { - - const point = optionalTarget; - - const points = this.points; - const p = ( points.length - 1 ) * t; - - const intPoint = Math.floor( p ); - const weight = p - intPoint; - - const p0 = points[ intPoint === 0 ? intPoint : intPoint - 1 ]; - const p1 = points[ intPoint ]; - const p2 = points[ intPoint > points.length - 2 ? points.length - 1 : intPoint + 1 ]; - const p3 = points[ intPoint > points.length - 3 ? points.length - 1 : intPoint + 2 ]; - - point.set( - CatmullRom( weight, p0.x, p1.x, p2.x, p3.x ), - CatmullRom( weight, p0.y, p1.y, p2.y, p3.y ) - ); - - return point; - - } - - copy( source ) { - - super.copy( source ); - - this.points = []; - - for ( let i = 0, l = source.points.length; i < l; i ++ ) { - - const point = source.points[ i ]; - - this.points.push( point.clone() ); - - } - - return this; - - } - - toJSON() { - - const data = super.toJSON(); - - data.points = []; - - for ( let i = 0, l = this.points.length; i < l; i ++ ) { - - const point = this.points[ i ]; - data.points.push( point.toArray() ); - - } - - return data; - - } - - fromJSON( json ) { - - super.fromJSON( json ); - - this.points = []; - - for ( let i = 0, l = json.points.length; i < l; i ++ ) { - - const point = json.points[ i ]; - this.points.push( new Vector2().fromArray( point ) ); - - } - - return this; - - } - - } - - var Curves = /*#__PURE__*/Object.freeze({ - __proto__: null, - ArcCurve: ArcCurve, - CatmullRomCurve3: CatmullRomCurve3, - CubicBezierCurve: CubicBezierCurve, - CubicBezierCurve3: CubicBezierCurve3, - EllipseCurve: EllipseCurve, - LineCurve: LineCurve, - LineCurve3: LineCurve3, - QuadraticBezierCurve: QuadraticBezierCurve, - QuadraticBezierCurve3: QuadraticBezierCurve3, - SplineCurve: SplineCurve - }); - - /************************************************************** - * Curved Path - a curve path is simply a array of connected - * curves, but retains the api of a curve - **************************************************************/ - - class CurvePath extends Curve { - - constructor() { - - super(); - - this.type = 'CurvePath'; - - this.curves = []; - this.autoClose = false; // Automatically closes the path - - } - - add( curve ) { - - this.curves.push( curve ); - - } - - closePath() { - - // Add a line curve if start and end of lines are not connected - const startPoint = this.curves[ 0 ].getPoint( 0 ); - const endPoint = this.curves[ this.curves.length - 1 ].getPoint( 1 ); - - if ( ! startPoint.equals( endPoint ) ) { - - this.curves.push( new LineCurve( endPoint, startPoint ) ); - - } - - } - - // To get accurate point with reference to - // entire path distance at time t, - // following has to be done: - - // 1. Length of each sub path have to be known - // 2. Locate and identify type of curve - // 3. Get t for the curve - // 4. Return curve.getPointAt(t') - - getPoint( t, optionalTarget ) { - - const d = t * this.getLength(); - const curveLengths = this.getCurveLengths(); - let i = 0; - - // To think about boundaries points. - - while ( i < curveLengths.length ) { - - if ( curveLengths[ i ] >= d ) { - - const diff = curveLengths[ i ] - d; - const curve = this.curves[ i ]; - - const segmentLength = curve.getLength(); - const u = segmentLength === 0 ? 0 : 1 - diff / segmentLength; - - return curve.getPointAt( u, optionalTarget ); - - } - - i ++; - - } - - return null; - - // loop where sum != 0, sum > d , sum+1 1 && ! points[ points.length - 1 ].equals( points[ 0 ] ) ) { - - points.push( points[ 0 ] ); - - } - - return points; - - } - - copy( source ) { - - super.copy( source ); - - this.curves = []; - - for ( let i = 0, l = source.curves.length; i < l; i ++ ) { - - const curve = source.curves[ i ]; - - this.curves.push( curve.clone() ); - - } - - this.autoClose = source.autoClose; - - return this; - - } - - toJSON() { - - const data = super.toJSON(); - - data.autoClose = this.autoClose; - data.curves = []; - - for ( let i = 0, l = this.curves.length; i < l; i ++ ) { - - const curve = this.curves[ i ]; - data.curves.push( curve.toJSON() ); - - } - - return data; - - } - - fromJSON( json ) { - - super.fromJSON( json ); - - this.autoClose = json.autoClose; - this.curves = []; - - for ( let i = 0, l = json.curves.length; i < l; i ++ ) { - - const curve = json.curves[ i ]; - this.curves.push( new Curves[ curve.type ]().fromJSON( curve ) ); - - } - - return this; - - } - - } - - class Path extends CurvePath { - - constructor( points ) { - - super(); - - this.type = 'Path'; - - this.currentPoint = new Vector2(); - - if ( points ) { - - this.setFromPoints( points ); - - } - - } - - setFromPoints( points ) { - - this.moveTo( points[ 0 ].x, points[ 0 ].y ); - - for ( let i = 1, l = points.length; i < l; i ++ ) { - - this.lineTo( points[ i ].x, points[ i ].y ); - - } - - return this; - - } - - moveTo( x, y ) { - - this.currentPoint.set( x, y ); // TODO consider referencing vectors instead of copying? - - return this; - - } - - lineTo( x, y ) { - - const curve = new LineCurve( this.currentPoint.clone(), new Vector2( x, y ) ); - this.curves.push( curve ); - - this.currentPoint.set( x, y ); - - return this; - - } - - quadraticCurveTo( aCPx, aCPy, aX, aY ) { - - const curve = new QuadraticBezierCurve( - this.currentPoint.clone(), - new Vector2( aCPx, aCPy ), - new Vector2( aX, aY ) - ); - - this.curves.push( curve ); - - this.currentPoint.set( aX, aY ); - - return this; - - } - - bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) { - - const curve = new CubicBezierCurve( - this.currentPoint.clone(), - new Vector2( aCP1x, aCP1y ), - new Vector2( aCP2x, aCP2y ), - new Vector2( aX, aY ) - ); - - this.curves.push( curve ); - - this.currentPoint.set( aX, aY ); - - return this; - - } - - splineThru( pts /*Array of Vector*/ ) { - - const npts = [ this.currentPoint.clone() ].concat( pts ); - - const curve = new SplineCurve( npts ); - this.curves.push( curve ); - - this.currentPoint.copy( pts[ pts.length - 1 ] ); - - return this; - - } - - arc( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) { - - const x0 = this.currentPoint.x; - const y0 = this.currentPoint.y; - - this.absarc( aX + x0, aY + y0, aRadius, - aStartAngle, aEndAngle, aClockwise ); - - return this; - - } - - absarc( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) { - - this.absellipse( aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise ); - - return this; - - } - - ellipse( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) { - - const x0 = this.currentPoint.x; - const y0 = this.currentPoint.y; - - this.absellipse( aX + x0, aY + y0, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ); - - return this; - - } - - absellipse( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) { - - const curve = new EllipseCurve( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ); - - if ( this.curves.length > 0 ) { - - // if a previous curve is present, attempt to join - const firstPoint = curve.getPoint( 0 ); - - if ( ! firstPoint.equals( this.currentPoint ) ) { - - this.lineTo( firstPoint.x, firstPoint.y ); - - } - - } - - this.curves.push( curve ); - - const lastPoint = curve.getPoint( 1 ); - this.currentPoint.copy( lastPoint ); - - return this; - - } - - copy( source ) { - - super.copy( source ); - - this.currentPoint.copy( source.currentPoint ); - - return this; - - } - - toJSON() { - - const data = super.toJSON(); - - data.currentPoint = this.currentPoint.toArray(); - - return data; - - } - - fromJSON( json ) { - - super.fromJSON( json ); - - this.currentPoint.fromArray( json.currentPoint ); - - return this; - - } - - } - - class LatheGeometry extends BufferGeometry { - - constructor( points = [ new Vector2( 0, - 0.5 ), new Vector2( 0.5, 0 ), new Vector2( 0, 0.5 ) ], segments = 12, phiStart = 0, phiLength = Math.PI * 2 ) { - - super(); - - this.type = 'LatheGeometry'; - - this.parameters = { - points: points, - segments: segments, - phiStart: phiStart, - phiLength: phiLength - }; - - segments = Math.floor( segments ); - - // clamp phiLength so it's in range of [ 0, 2PI ] - - phiLength = clamp( phiLength, 0, Math.PI * 2 ); - - // buffers - - const indices = []; - const vertices = []; - const uvs = []; - const initNormals = []; - const normals = []; - - // helper variables - - const inverseSegments = 1.0 / segments; - const vertex = new Vector3(); - const uv = new Vector2(); - const normal = new Vector3(); - const curNormal = new Vector3(); - const prevNormal = new Vector3(); - let dx = 0; - let dy = 0; - - // pre-compute normals for initial "meridian" - - for ( let j = 0; j <= ( points.length - 1 ); j ++ ) { - - switch ( j ) { - - case 0: // special handling for 1st vertex on path - - dx = points[ j + 1 ].x - points[ j ].x; - dy = points[ j + 1 ].y - points[ j ].y; - - normal.x = dy * 1.0; - normal.y = - dx; - normal.z = dy * 0.0; - - prevNormal.copy( normal ); - - normal.normalize(); - - initNormals.push( normal.x, normal.y, normal.z ); - - break; - - case ( points.length - 1 ): // special handling for last Vertex on path - - initNormals.push( prevNormal.x, prevNormal.y, prevNormal.z ); - - break; - - default: // default handling for all vertices in between - - dx = points[ j + 1 ].x - points[ j ].x; - dy = points[ j + 1 ].y - points[ j ].y; - - normal.x = dy * 1.0; - normal.y = - dx; - normal.z = dy * 0.0; - - curNormal.copy( normal ); - - normal.x += prevNormal.x; - normal.y += prevNormal.y; - normal.z += prevNormal.z; - - normal.normalize(); - - initNormals.push( normal.x, normal.y, normal.z ); - - prevNormal.copy( curNormal ); - - } - - } - - // generate vertices, uvs and normals - - for ( let i = 0; i <= segments; i ++ ) { - - const phi = phiStart + i * inverseSegments * phiLength; - - const sin = Math.sin( phi ); - const cos = Math.cos( phi ); - - for ( let j = 0; j <= ( points.length - 1 ); j ++ ) { - - // vertex - - vertex.x = points[ j ].x * sin; - vertex.y = points[ j ].y; - vertex.z = points[ j ].x * cos; - - vertices.push( vertex.x, vertex.y, vertex.z ); - - // uv - - uv.x = i / segments; - uv.y = j / ( points.length - 1 ); - - uvs.push( uv.x, uv.y ); - - // normal - - const x = initNormals[ 3 * j + 0 ] * sin; - const y = initNormals[ 3 * j + 1 ]; - const z = initNormals[ 3 * j + 0 ] * cos; - - normals.push( x, y, z ); - - } - - } - - // indices - - for ( let i = 0; i < segments; i ++ ) { - - for ( let j = 0; j < ( points.length - 1 ); j ++ ) { - - const base = j + i * points.length; - - const a = base; - const b = base + points.length; - const c = base + points.length + 1; - const d = base + 1; - - // faces - - indices.push( a, b, d ); - indices.push( c, d, b ); - - } - - } - - // build geometry - - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - - } - - copy( source ) { - - super.copy( source ); - - this.parameters = Object.assign( {}, source.parameters ); - - return this; - - } - - static fromJSON( data ) { - - return new LatheGeometry( data.points, data.segments, data.phiStart, data.phiLength ); - - } - - } - - class CapsuleGeometry extends LatheGeometry { - - constructor( radius = 1, length = 1, capSegments = 4, radialSegments = 8 ) { - - const path = new Path(); - path.absarc( 0, - length / 2, radius, Math.PI * 1.5, 0 ); - path.absarc( 0, length / 2, radius, 0, Math.PI * 0.5 ); - - super( path.getPoints( capSegments ), radialSegments ); - - this.type = 'CapsuleGeometry'; - - this.parameters = { - radius: radius, - height: length, - capSegments: capSegments, - radialSegments: radialSegments, - }; - - } - - static fromJSON( data ) { - - return new CapsuleGeometry( data.radius, data.length, data.capSegments, data.radialSegments ); - - } - - } - - class CircleGeometry extends BufferGeometry { - - constructor( radius = 1, segments = 32, thetaStart = 0, thetaLength = Math.PI * 2 ) { - - super(); - - this.type = 'CircleGeometry'; - - this.parameters = { - radius: radius, - segments: segments, - thetaStart: thetaStart, - thetaLength: thetaLength - }; - - segments = Math.max( 3, segments ); - - // buffers - - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; - - // helper variables - - const vertex = new Vector3(); - const uv = new Vector2(); - - // center point - - vertices.push( 0, 0, 0 ); - normals.push( 0, 0, 1 ); - uvs.push( 0.5, 0.5 ); - - for ( let s = 0, i = 3; s <= segments; s ++, i += 3 ) { - - const segment = thetaStart + s / segments * thetaLength; - - // vertex - - vertex.x = radius * Math.cos( segment ); - vertex.y = radius * Math.sin( segment ); - - vertices.push( vertex.x, vertex.y, vertex.z ); - - // normal - - normals.push( 0, 0, 1 ); - - // uvs - - uv.x = ( vertices[ i ] / radius + 1 ) / 2; - uv.y = ( vertices[ i + 1 ] / radius + 1 ) / 2; - - uvs.push( uv.x, uv.y ); - - } - - // indices - - for ( let i = 1; i <= segments; i ++ ) { - - indices.push( i, i + 1, 0 ); - - } - - // build geometry - - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - - } - - copy( source ) { - - super.copy( source ); - - this.parameters = Object.assign( {}, source.parameters ); - - return this; - - } - - static fromJSON( data ) { - - return new CircleGeometry( data.radius, data.segments, data.thetaStart, data.thetaLength ); - - } - - } - - class CylinderGeometry extends BufferGeometry { - - constructor( radiusTop = 1, radiusBottom = 1, height = 1, radialSegments = 32, heightSegments = 1, openEnded = false, thetaStart = 0, thetaLength = Math.PI * 2 ) { - - super(); - - this.type = 'CylinderGeometry'; - - this.parameters = { - radiusTop: radiusTop, - radiusBottom: radiusBottom, - height: height, - radialSegments: radialSegments, - heightSegments: heightSegments, - openEnded: openEnded, - thetaStart: thetaStart, - thetaLength: thetaLength - }; - - const scope = this; - - radialSegments = Math.floor( radialSegments ); - heightSegments = Math.floor( heightSegments ); - - // buffers - - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; - - // helper variables - - let index = 0; - const indexArray = []; - const halfHeight = height / 2; - let groupStart = 0; - - // generate geometry - - generateTorso(); - - if ( openEnded === false ) { - - if ( radiusTop > 0 ) generateCap( true ); - if ( radiusBottom > 0 ) generateCap( false ); - - } - - // build geometry - - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - - function generateTorso() { - - const normal = new Vector3(); - const vertex = new Vector3(); - - let groupCount = 0; - - // this will be used to calculate the normal - const slope = ( radiusBottom - radiusTop ) / height; - - // generate vertices, normals and uvs - - for ( let y = 0; y <= heightSegments; y ++ ) { - - const indexRow = []; - - const v = y / heightSegments; - - // calculate the radius of the current row - - const radius = v * ( radiusBottom - radiusTop ) + radiusTop; - - for ( let x = 0; x <= radialSegments; x ++ ) { - - const u = x / radialSegments; - - const theta = u * thetaLength + thetaStart; - - const sinTheta = Math.sin( theta ); - const cosTheta = Math.cos( theta ); - - // vertex - - vertex.x = radius * sinTheta; - vertex.y = - v * height + halfHeight; - vertex.z = radius * cosTheta; - vertices.push( vertex.x, vertex.y, vertex.z ); - - // normal - - normal.set( sinTheta, slope, cosTheta ).normalize(); - normals.push( normal.x, normal.y, normal.z ); - - // uv - - uvs.push( u, 1 - v ); - - // save index of vertex in respective row - - indexRow.push( index ++ ); - - } - - // now save vertices of the row in our index array - - indexArray.push( indexRow ); - - } - - // generate indices - - for ( let x = 0; x < radialSegments; x ++ ) { - - for ( let y = 0; y < heightSegments; y ++ ) { - - // we use the index array to access the correct indices - - const a = indexArray[ y ][ x ]; - const b = indexArray[ y + 1 ][ x ]; - const c = indexArray[ y + 1 ][ x + 1 ]; - const d = indexArray[ y ][ x + 1 ]; - - // faces - - indices.push( a, b, d ); - indices.push( b, c, d ); - - // update group counter - - groupCount += 6; - - } - - } - - // add a group to the geometry. this will ensure multi material support - - scope.addGroup( groupStart, groupCount, 0 ); - - // calculate new start value for groups - - groupStart += groupCount; - - } - - function generateCap( top ) { - - // save the index of the first center vertex - const centerIndexStart = index; - - const uv = new Vector2(); - const vertex = new Vector3(); - - let groupCount = 0; - - const radius = ( top === true ) ? radiusTop : radiusBottom; - const sign = ( top === true ) ? 1 : - 1; - - // first we generate the center vertex data of the cap. - // because the geometry needs one set of uvs per face, - // we must generate a center vertex per face/segment - - for ( let x = 1; x <= radialSegments; x ++ ) { - - // vertex - - vertices.push( 0, halfHeight * sign, 0 ); - - // normal - - normals.push( 0, sign, 0 ); - - // uv - - uvs.push( 0.5, 0.5 ); - - // increase index - - index ++; - - } - - // save the index of the last center vertex - const centerIndexEnd = index; - - // now we generate the surrounding vertices, normals and uvs - - for ( let x = 0; x <= radialSegments; x ++ ) { - - const u = x / radialSegments; - const theta = u * thetaLength + thetaStart; - - const cosTheta = Math.cos( theta ); - const sinTheta = Math.sin( theta ); - - // vertex - - vertex.x = radius * sinTheta; - vertex.y = halfHeight * sign; - vertex.z = radius * cosTheta; - vertices.push( vertex.x, vertex.y, vertex.z ); - - // normal - - normals.push( 0, sign, 0 ); - - // uv - - uv.x = ( cosTheta * 0.5 ) + 0.5; - uv.y = ( sinTheta * 0.5 * sign ) + 0.5; - uvs.push( uv.x, uv.y ); - - // increase index - - index ++; - - } - - // generate indices - - for ( let x = 0; x < radialSegments; x ++ ) { - - const c = centerIndexStart + x; - const i = centerIndexEnd + x; - - if ( top === true ) { - - // face top - - indices.push( i, i + 1, c ); - - } else { - - // face bottom - - indices.push( i + 1, i, c ); - - } - - groupCount += 3; - - } - - // add a group to the geometry. this will ensure multi material support - - scope.addGroup( groupStart, groupCount, top === true ? 1 : 2 ); - - // calculate new start value for groups - - groupStart += groupCount; - - } - - } - - copy( source ) { - - super.copy( source ); - - this.parameters = Object.assign( {}, source.parameters ); - - return this; - - } - - static fromJSON( data ) { - - return new CylinderGeometry( data.radiusTop, data.radiusBottom, data.height, data.radialSegments, data.heightSegments, data.openEnded, data.thetaStart, data.thetaLength ); - - } - - } - - class ConeGeometry extends CylinderGeometry { - - constructor( radius = 1, height = 1, radialSegments = 32, heightSegments = 1, openEnded = false, thetaStart = 0, thetaLength = Math.PI * 2 ) { - - super( 0, radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ); - - this.type = 'ConeGeometry'; - - this.parameters = { - radius: radius, - height: height, - radialSegments: radialSegments, - heightSegments: heightSegments, - openEnded: openEnded, - thetaStart: thetaStart, - thetaLength: thetaLength - }; - - } - - static fromJSON( data ) { - - return new ConeGeometry( data.radius, data.height, data.radialSegments, data.heightSegments, data.openEnded, data.thetaStart, data.thetaLength ); - - } - - } - - class PolyhedronGeometry extends BufferGeometry { - - constructor( vertices = [], indices = [], radius = 1, detail = 0 ) { - - super(); - - this.type = 'PolyhedronGeometry'; - - this.parameters = { - vertices: vertices, - indices: indices, - radius: radius, - detail: detail - }; - - // default buffer data - - const vertexBuffer = []; - const uvBuffer = []; - - // the subdivision creates the vertex buffer data - - subdivide( detail ); - - // all vertices should lie on a conceptual sphere with a given radius - - applyRadius( radius ); - - // finally, create the uv data - - generateUVs(); - - // build non-indexed geometry - - this.setAttribute( 'position', new Float32BufferAttribute( vertexBuffer, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( vertexBuffer.slice(), 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvBuffer, 2 ) ); - - if ( detail === 0 ) { - - this.computeVertexNormals(); // flat normals - - } else { - - this.normalizeNormals(); // smooth normals - - } - - // helper functions - - function subdivide( detail ) { - - const a = new Vector3(); - const b = new Vector3(); - const c = new Vector3(); - - // iterate over all faces and apply a subdivision with the given detail value - - for ( let i = 0; i < indices.length; i += 3 ) { - - // get the vertices of the face - - getVertexByIndex( indices[ i + 0 ], a ); - getVertexByIndex( indices[ i + 1 ], b ); - getVertexByIndex( indices[ i + 2 ], c ); - - // perform subdivision - - subdivideFace( a, b, c, detail ); - - } - - } - - function subdivideFace( a, b, c, detail ) { - - const cols = detail + 1; - - // we use this multidimensional array as a data structure for creating the subdivision - - const v = []; - - // construct all of the vertices for this subdivision - - for ( let i = 0; i <= cols; i ++ ) { - - v[ i ] = []; - - const aj = a.clone().lerp( c, i / cols ); - const bj = b.clone().lerp( c, i / cols ); - - const rows = cols - i; - - for ( let j = 0; j <= rows; j ++ ) { - - if ( j === 0 && i === cols ) { - - v[ i ][ j ] = aj; - - } else { - - v[ i ][ j ] = aj.clone().lerp( bj, j / rows ); - - } - - } - - } - - // construct all of the faces - - for ( let i = 0; i < cols; i ++ ) { - - for ( let j = 0; j < 2 * ( cols - i ) - 1; j ++ ) { - - const k = Math.floor( j / 2 ); - - if ( j % 2 === 0 ) { - - pushVertex( v[ i ][ k + 1 ] ); - pushVertex( v[ i + 1 ][ k ] ); - pushVertex( v[ i ][ k ] ); - - } else { - - pushVertex( v[ i ][ k + 1 ] ); - pushVertex( v[ i + 1 ][ k + 1 ] ); - pushVertex( v[ i + 1 ][ k ] ); - - } - - } - - } - - } - - function applyRadius( radius ) { - - const vertex = new Vector3(); - - // iterate over the entire buffer and apply the radius to each vertex - - for ( let i = 0; i < vertexBuffer.length; i += 3 ) { - - vertex.x = vertexBuffer[ i + 0 ]; - vertex.y = vertexBuffer[ i + 1 ]; - vertex.z = vertexBuffer[ i + 2 ]; - - vertex.normalize().multiplyScalar( radius ); - - vertexBuffer[ i + 0 ] = vertex.x; - vertexBuffer[ i + 1 ] = vertex.y; - vertexBuffer[ i + 2 ] = vertex.z; - - } - - } - - function generateUVs() { - - const vertex = new Vector3(); - - for ( let i = 0; i < vertexBuffer.length; i += 3 ) { - - vertex.x = vertexBuffer[ i + 0 ]; - vertex.y = vertexBuffer[ i + 1 ]; - vertex.z = vertexBuffer[ i + 2 ]; - - const u = azimuth( vertex ) / 2 / Math.PI + 0.5; - const v = inclination( vertex ) / Math.PI + 0.5; - uvBuffer.push( u, 1 - v ); - - } - - correctUVs(); - - correctSeam(); - - } - - function correctSeam() { - - // handle case when face straddles the seam, see #3269 - - for ( let i = 0; i < uvBuffer.length; i += 6 ) { - - // uv data of a single face - - const x0 = uvBuffer[ i + 0 ]; - const x1 = uvBuffer[ i + 2 ]; - const x2 = uvBuffer[ i + 4 ]; - - const max = Math.max( x0, x1, x2 ); - const min = Math.min( x0, x1, x2 ); - - // 0.9 is somewhat arbitrary - - if ( max > 0.9 && min < 0.1 ) { - - if ( x0 < 0.2 ) uvBuffer[ i + 0 ] += 1; - if ( x1 < 0.2 ) uvBuffer[ i + 2 ] += 1; - if ( x2 < 0.2 ) uvBuffer[ i + 4 ] += 1; - - } - - } - - } - - function pushVertex( vertex ) { - - vertexBuffer.push( vertex.x, vertex.y, vertex.z ); - - } - - function getVertexByIndex( index, vertex ) { - - const stride = index * 3; - - vertex.x = vertices[ stride + 0 ]; - vertex.y = vertices[ stride + 1 ]; - vertex.z = vertices[ stride + 2 ]; - - } - - function correctUVs() { - - const a = new Vector3(); - const b = new Vector3(); - const c = new Vector3(); - - const centroid = new Vector3(); - - const uvA = new Vector2(); - const uvB = new Vector2(); - const uvC = new Vector2(); - - for ( let i = 0, j = 0; i < vertexBuffer.length; i += 9, j += 6 ) { - - a.set( vertexBuffer[ i + 0 ], vertexBuffer[ i + 1 ], vertexBuffer[ i + 2 ] ); - b.set( vertexBuffer[ i + 3 ], vertexBuffer[ i + 4 ], vertexBuffer[ i + 5 ] ); - c.set( vertexBuffer[ i + 6 ], vertexBuffer[ i + 7 ], vertexBuffer[ i + 8 ] ); - - uvA.set( uvBuffer[ j + 0 ], uvBuffer[ j + 1 ] ); - uvB.set( uvBuffer[ j + 2 ], uvBuffer[ j + 3 ] ); - uvC.set( uvBuffer[ j + 4 ], uvBuffer[ j + 5 ] ); - - centroid.copy( a ).add( b ).add( c ).divideScalar( 3 ); - - const azi = azimuth( centroid ); - - correctUV( uvA, j + 0, a, azi ); - correctUV( uvB, j + 2, b, azi ); - correctUV( uvC, j + 4, c, azi ); - - } - - } - - function correctUV( uv, stride, vector, azimuth ) { - - if ( ( azimuth < 0 ) && ( uv.x === 1 ) ) { - - uvBuffer[ stride ] = uv.x - 1; - - } - - if ( ( vector.x === 0 ) && ( vector.z === 0 ) ) { - - uvBuffer[ stride ] = azimuth / 2 / Math.PI + 0.5; - - } - - } - - // Angle around the Y axis, counter-clockwise when looking from above. - - function azimuth( vector ) { - - return Math.atan2( vector.z, - vector.x ); - - } - - - // Angle above the XZ plane. - - function inclination( vector ) { - - return Math.atan2( - vector.y, Math.sqrt( ( vector.x * vector.x ) + ( vector.z * vector.z ) ) ); - - } - - } - - copy( source ) { - - super.copy( source ); - - this.parameters = Object.assign( {}, source.parameters ); - - return this; - - } - - static fromJSON( data ) { - - return new PolyhedronGeometry( data.vertices, data.indices, data.radius, data.details ); - - } - - } - - class DodecahedronGeometry extends PolyhedronGeometry { - - constructor( radius = 1, detail = 0 ) { - - const t = ( 1 + Math.sqrt( 5 ) ) / 2; - const r = 1 / t; - - const vertices = [ - - // (±1, ±1, ±1) - - 1, - 1, - 1, - 1, - 1, 1, - - 1, 1, - 1, - 1, 1, 1, - 1, - 1, - 1, 1, - 1, 1, - 1, 1, - 1, 1, 1, 1, - - // (0, ±1/φ, ±φ) - 0, - r, - t, 0, - r, t, - 0, r, - t, 0, r, t, - - // (±1/φ, ±φ, 0) - - r, - t, 0, - r, t, 0, - r, - t, 0, r, t, 0, - - // (±φ, 0, ±1/φ) - - t, 0, - r, t, 0, - r, - - t, 0, r, t, 0, r - ]; - - const indices = [ - 3, 11, 7, 3, 7, 15, 3, 15, 13, - 7, 19, 17, 7, 17, 6, 7, 6, 15, - 17, 4, 8, 17, 8, 10, 17, 10, 6, - 8, 0, 16, 8, 16, 2, 8, 2, 10, - 0, 12, 1, 0, 1, 18, 0, 18, 16, - 6, 10, 2, 6, 2, 13, 6, 13, 15, - 2, 16, 18, 2, 18, 3, 2, 3, 13, - 18, 1, 9, 18, 9, 11, 18, 11, 3, - 4, 14, 12, 4, 12, 0, 4, 0, 8, - 11, 9, 5, 11, 5, 19, 11, 19, 7, - 19, 5, 14, 19, 14, 4, 19, 4, 17, - 1, 12, 14, 1, 14, 5, 1, 5, 9 - ]; - - super( vertices, indices, radius, detail ); - - this.type = 'DodecahedronGeometry'; - - this.parameters = { - radius: radius, - detail: detail - }; - - } - - static fromJSON( data ) { - - return new DodecahedronGeometry( data.radius, data.detail ); - - } - - } - - const _v0 = /*@__PURE__*/ new Vector3(); - const _v1$1 = /*@__PURE__*/ new Vector3(); - const _normal = /*@__PURE__*/ new Vector3(); - const _triangle = /*@__PURE__*/ new Triangle(); - - class EdgesGeometry extends BufferGeometry { - - constructor( geometry = null, thresholdAngle = 1 ) { - - super(); - - this.type = 'EdgesGeometry'; - - this.parameters = { - geometry: geometry, - thresholdAngle: thresholdAngle - }; - - if ( geometry !== null ) { - - const precisionPoints = 4; - const precision = Math.pow( 10, precisionPoints ); - const thresholdDot = Math.cos( DEG2RAD * thresholdAngle ); - - const indexAttr = geometry.getIndex(); - const positionAttr = geometry.getAttribute( 'position' ); - const indexCount = indexAttr ? indexAttr.count : positionAttr.count; - - const indexArr = [ 0, 0, 0 ]; - const vertKeys = [ 'a', 'b', 'c' ]; - const hashes = new Array( 3 ); - - const edgeData = {}; - const vertices = []; - for ( let i = 0; i < indexCount; i += 3 ) { - - if ( indexAttr ) { - - indexArr[ 0 ] = indexAttr.getX( i ); - indexArr[ 1 ] = indexAttr.getX( i + 1 ); - indexArr[ 2 ] = indexAttr.getX( i + 2 ); - - } else { - - indexArr[ 0 ] = i; - indexArr[ 1 ] = i + 1; - indexArr[ 2 ] = i + 2; - - } - - const { a, b, c } = _triangle; - a.fromBufferAttribute( positionAttr, indexArr[ 0 ] ); - b.fromBufferAttribute( positionAttr, indexArr[ 1 ] ); - c.fromBufferAttribute( positionAttr, indexArr[ 2 ] ); - _triangle.getNormal( _normal ); - - // create hashes for the edge from the vertices - hashes[ 0 ] = `${ Math.round( a.x * precision ) },${ Math.round( a.y * precision ) },${ Math.round( a.z * precision ) }`; - hashes[ 1 ] = `${ Math.round( b.x * precision ) },${ Math.round( b.y * precision ) },${ Math.round( b.z * precision ) }`; - hashes[ 2 ] = `${ Math.round( c.x * precision ) },${ Math.round( c.y * precision ) },${ Math.round( c.z * precision ) }`; - - // skip degenerate triangles - if ( hashes[ 0 ] === hashes[ 1 ] || hashes[ 1 ] === hashes[ 2 ] || hashes[ 2 ] === hashes[ 0 ] ) { - - continue; - - } - - // iterate over every edge - for ( let j = 0; j < 3; j ++ ) { - - // get the first and next vertex making up the edge - const jNext = ( j + 1 ) % 3; - const vecHash0 = hashes[ j ]; - const vecHash1 = hashes[ jNext ]; - const v0 = _triangle[ vertKeys[ j ] ]; - const v1 = _triangle[ vertKeys[ jNext ] ]; - - const hash = `${ vecHash0 }_${ vecHash1 }`; - const reverseHash = `${ vecHash1 }_${ vecHash0 }`; - - if ( reverseHash in edgeData && edgeData[ reverseHash ] ) { - - // if we found a sibling edge add it into the vertex array if - // it meets the angle threshold and delete the edge from the map. - if ( _normal.dot( edgeData[ reverseHash ].normal ) <= thresholdDot ) { - - vertices.push( v0.x, v0.y, v0.z ); - vertices.push( v1.x, v1.y, v1.z ); - - } - - edgeData[ reverseHash ] = null; - - } else if ( ! ( hash in edgeData ) ) { - - // if we've already got an edge here then skip adding a new one - edgeData[ hash ] = { - - index0: indexArr[ j ], - index1: indexArr[ jNext ], - normal: _normal.clone(), - - }; - - } - - } - - } - - // iterate over all remaining, unmatched edges and add them to the vertex array - for ( const key in edgeData ) { - - if ( edgeData[ key ] ) { - - const { index0, index1 } = edgeData[ key ]; - _v0.fromBufferAttribute( positionAttr, index0 ); - _v1$1.fromBufferAttribute( positionAttr, index1 ); - - vertices.push( _v0.x, _v0.y, _v0.z ); - vertices.push( _v1$1.x, _v1$1.y, _v1$1.z ); - - } - - } - - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - - } - - } - - copy( source ) { - - super.copy( source ); - - this.parameters = Object.assign( {}, source.parameters ); - - return this; - - } - - } - - class Shape extends Path { - - constructor( points ) { - - super( points ); - - this.uuid = generateUUID(); - - this.type = 'Shape'; - - this.holes = []; - - } - - getPointsHoles( divisions ) { - - const holesPts = []; - - for ( let i = 0, l = this.holes.length; i < l; i ++ ) { - - holesPts[ i ] = this.holes[ i ].getPoints( divisions ); - - } - - return holesPts; - - } - - // get points of shape and holes (keypoints based on segments parameter) - - extractPoints( divisions ) { - - return { - - shape: this.getPoints( divisions ), - holes: this.getPointsHoles( divisions ) - - }; - - } - - copy( source ) { - - super.copy( source ); - - this.holes = []; - - for ( let i = 0, l = source.holes.length; i < l; i ++ ) { - - const hole = source.holes[ i ]; - - this.holes.push( hole.clone() ); - - } - - return this; - - } - - toJSON() { - - const data = super.toJSON(); - - data.uuid = this.uuid; - data.holes = []; - - for ( let i = 0, l = this.holes.length; i < l; i ++ ) { - - const hole = this.holes[ i ]; - data.holes.push( hole.toJSON() ); - - } - - return data; - - } - - fromJSON( json ) { - - super.fromJSON( json ); - - this.uuid = json.uuid; - this.holes = []; - - for ( let i = 0, l = json.holes.length; i < l; i ++ ) { - - const hole = json.holes[ i ]; - this.holes.push( new Path().fromJSON( hole ) ); - - } - - return this; - - } - - } - - /** - * Port from https://github.com/mapbox/earcut (v2.2.4) - */ - - const Earcut = { - - triangulate: function ( data, holeIndices, dim = 2 ) { - - const hasHoles = holeIndices && holeIndices.length; - const outerLen = hasHoles ? holeIndices[ 0 ] * dim : data.length; - let outerNode = linkedList( data, 0, outerLen, dim, true ); - const triangles = []; - - if ( ! outerNode || outerNode.next === outerNode.prev ) return triangles; - - let minX, minY, maxX, maxY, x, y, invSize; - - if ( hasHoles ) outerNode = eliminateHoles( data, holeIndices, outerNode, dim ); - - // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox - if ( data.length > 80 * dim ) { - - minX = maxX = data[ 0 ]; - minY = maxY = data[ 1 ]; - - for ( let i = dim; i < outerLen; i += dim ) { - - x = data[ i ]; - y = data[ i + 1 ]; - if ( x < minX ) minX = x; - if ( y < minY ) minY = y; - if ( x > maxX ) maxX = x; - if ( y > maxY ) maxY = y; - - } - - // minX, minY and invSize are later used to transform coords into integers for z-order calculation - invSize = Math.max( maxX - minX, maxY - minY ); - invSize = invSize !== 0 ? 32767 / invSize : 0; - - } - - earcutLinked( outerNode, triangles, dim, minX, minY, invSize, 0 ); - - return triangles; - - } - - }; - - // create a circular doubly linked list from polygon points in the specified winding order - function linkedList( data, start, end, dim, clockwise ) { - - let i, last; - - if ( clockwise === ( signedArea( data, start, end, dim ) > 0 ) ) { - - for ( i = start; i < end; i += dim ) last = insertNode( i, data[ i ], data[ i + 1 ], last ); - - } else { - - for ( i = end - dim; i >= start; i -= dim ) last = insertNode( i, data[ i ], data[ i + 1 ], last ); - - } - - if ( last && equals( last, last.next ) ) { - - removeNode( last ); - last = last.next; - - } - - return last; - - } - - // eliminate colinear or duplicate points - function filterPoints( start, end ) { - - if ( ! start ) return start; - if ( ! end ) end = start; - - let p = start, - again; - do { - - again = false; - - if ( ! p.steiner && ( equals( p, p.next ) || area( p.prev, p, p.next ) === 0 ) ) { - - removeNode( p ); - p = end = p.prev; - if ( p === p.next ) break; - again = true; - - } else { - - p = p.next; - - } - - } while ( again || p !== end ); - - return end; - - } - - // main ear slicing loop which triangulates a polygon (given as a linked list) - function earcutLinked( ear, triangles, dim, minX, minY, invSize, pass ) { - - if ( ! ear ) return; - - // interlink polygon nodes in z-order - if ( ! pass && invSize ) indexCurve( ear, minX, minY, invSize ); - - let stop = ear, - prev, next; - - // iterate through ears, slicing them one by one - while ( ear.prev !== ear.next ) { - - prev = ear.prev; - next = ear.next; - - if ( invSize ? isEarHashed( ear, minX, minY, invSize ) : isEar( ear ) ) { - - // cut off the triangle - triangles.push( prev.i / dim | 0 ); - triangles.push( ear.i / dim | 0 ); - triangles.push( next.i / dim | 0 ); - - removeNode( ear ); - - // skipping the next vertex leads to less sliver triangles - ear = next.next; - stop = next.next; - - continue; - - } - - ear = next; - - // if we looped through the whole remaining polygon and can't find any more ears - if ( ear === stop ) { - - // try filtering points and slicing again - if ( ! pass ) { - - earcutLinked( filterPoints( ear ), triangles, dim, minX, minY, invSize, 1 ); - - // if this didn't work, try curing all small self-intersections locally - - } else if ( pass === 1 ) { - - ear = cureLocalIntersections( filterPoints( ear ), triangles, dim ); - earcutLinked( ear, triangles, dim, minX, minY, invSize, 2 ); - - // as a last resort, try splitting the remaining polygon into two - - } else if ( pass === 2 ) { - - splitEarcut( ear, triangles, dim, minX, minY, invSize ); - - } - - break; - - } - - } - - } - - // check whether a polygon node forms a valid ear with adjacent nodes - function isEar( ear ) { - - const a = ear.prev, - b = ear, - c = ear.next; - - if ( area( a, b, c ) >= 0 ) return false; // reflex, can't be an ear - - // now make sure we don't have other points inside the potential ear - const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y; - - // triangle bbox; min & max are calculated like this for speed - const x0 = ax < bx ? ( ax < cx ? ax : cx ) : ( bx < cx ? bx : cx ), - y0 = ay < by ? ( ay < cy ? ay : cy ) : ( by < cy ? by : cy ), - x1 = ax > bx ? ( ax > cx ? ax : cx ) : ( bx > cx ? bx : cx ), - y1 = ay > by ? ( ay > cy ? ay : cy ) : ( by > cy ? by : cy ); - - let p = c.next; - while ( p !== a ) { - - if ( p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && - pointInTriangle( ax, ay, bx, by, cx, cy, p.x, p.y ) && - area( p.prev, p, p.next ) >= 0 ) return false; - p = p.next; - - } - - return true; - - } - - function isEarHashed( ear, minX, minY, invSize ) { - - const a = ear.prev, - b = ear, - c = ear.next; - - if ( area( a, b, c ) >= 0 ) return false; // reflex, can't be an ear - - const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y; - - // triangle bbox; min & max are calculated like this for speed - const x0 = ax < bx ? ( ax < cx ? ax : cx ) : ( bx < cx ? bx : cx ), - y0 = ay < by ? ( ay < cy ? ay : cy ) : ( by < cy ? by : cy ), - x1 = ax > bx ? ( ax > cx ? ax : cx ) : ( bx > cx ? bx : cx ), - y1 = ay > by ? ( ay > cy ? ay : cy ) : ( by > cy ? by : cy ); - - // z-order range for the current triangle bbox; - const minZ = zOrder( x0, y0, minX, minY, invSize ), - maxZ = zOrder( x1, y1, minX, minY, invSize ); - - let p = ear.prevZ, - n = ear.nextZ; - - // look for points inside the triangle in both directions - while ( p && p.z >= minZ && n && n.z <= maxZ ) { - - if ( p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c && - pointInTriangle( ax, ay, bx, by, cx, cy, p.x, p.y ) && area( p.prev, p, p.next ) >= 0 ) return false; - p = p.prevZ; - - if ( n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c && - pointInTriangle( ax, ay, bx, by, cx, cy, n.x, n.y ) && area( n.prev, n, n.next ) >= 0 ) return false; - n = n.nextZ; - - } - - // look for remaining points in decreasing z-order - while ( p && p.z >= minZ ) { - - if ( p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c && - pointInTriangle( ax, ay, bx, by, cx, cy, p.x, p.y ) && area( p.prev, p, p.next ) >= 0 ) return false; - p = p.prevZ; - - } - - // look for remaining points in increasing z-order - while ( n && n.z <= maxZ ) { - - if ( n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c && - pointInTriangle( ax, ay, bx, by, cx, cy, n.x, n.y ) && area( n.prev, n, n.next ) >= 0 ) return false; - n = n.nextZ; - - } - - return true; - - } - - // go through all polygon nodes and cure small local self-intersections - function cureLocalIntersections( start, triangles, dim ) { - - let p = start; - do { - - const a = p.prev, - b = p.next.next; - - if ( ! equals( a, b ) && intersects( a, p, p.next, b ) && locallyInside( a, b ) && locallyInside( b, a ) ) { - - triangles.push( a.i / dim | 0 ); - triangles.push( p.i / dim | 0 ); - triangles.push( b.i / dim | 0 ); - - // remove two nodes involved - removeNode( p ); - removeNode( p.next ); - - p = start = b; - - } - - p = p.next; - - } while ( p !== start ); - - return filterPoints( p ); - - } - - // try splitting polygon into two and triangulate them independently - function splitEarcut( start, triangles, dim, minX, minY, invSize ) { - - // look for a valid diagonal that divides the polygon into two - let a = start; - do { - - let b = a.next.next; - while ( b !== a.prev ) { - - if ( a.i !== b.i && isValidDiagonal( a, b ) ) { - - // split the polygon in two by the diagonal - let c = splitPolygon( a, b ); - - // filter colinear points around the cuts - a = filterPoints( a, a.next ); - c = filterPoints( c, c.next ); - - // run earcut on each half - earcutLinked( a, triangles, dim, minX, minY, invSize, 0 ); - earcutLinked( c, triangles, dim, minX, minY, invSize, 0 ); - return; - - } - - b = b.next; - - } - - a = a.next; - - } while ( a !== start ); - - } - - // link every hole into the outer loop, producing a single-ring polygon without holes - function eliminateHoles( data, holeIndices, outerNode, dim ) { - - const queue = []; - let i, len, start, end, list; - - for ( i = 0, len = holeIndices.length; i < len; i ++ ) { - - start = holeIndices[ i ] * dim; - end = i < len - 1 ? holeIndices[ i + 1 ] * dim : data.length; - list = linkedList( data, start, end, dim, false ); - if ( list === list.next ) list.steiner = true; - queue.push( getLeftmost( list ) ); - - } - - queue.sort( compareX ); - - // process holes from left to right - for ( i = 0; i < queue.length; i ++ ) { - - outerNode = eliminateHole( queue[ i ], outerNode ); - - } - - return outerNode; - - } - - function compareX( a, b ) { - - return a.x - b.x; - - } - - // find a bridge between vertices that connects hole with an outer ring and link it - function eliminateHole( hole, outerNode ) { - - const bridge = findHoleBridge( hole, outerNode ); - if ( ! bridge ) { - - return outerNode; - - } - - const bridgeReverse = splitPolygon( bridge, hole ); - - // filter collinear points around the cuts - filterPoints( bridgeReverse, bridgeReverse.next ); - return filterPoints( bridge, bridge.next ); - - } - - // David Eberly's algorithm for finding a bridge between hole and outer polygon - function findHoleBridge( hole, outerNode ) { - - let p = outerNode, - qx = - Infinity, - m; - - const hx = hole.x, hy = hole.y; - - // find a segment intersected by a ray from the hole's leftmost point to the left; - // segment's endpoint with lesser x will be potential connection point - do { - - if ( hy <= p.y && hy >= p.next.y && p.next.y !== p.y ) { - - const x = p.x + ( hy - p.y ) * ( p.next.x - p.x ) / ( p.next.y - p.y ); - if ( x <= hx && x > qx ) { - - qx = x; - m = p.x < p.next.x ? p : p.next; - if ( x === hx ) return m; // hole touches outer segment; pick leftmost endpoint - - } - - } - - p = p.next; - - } while ( p !== outerNode ); - - if ( ! m ) return null; - - // look for points inside the triangle of hole point, segment intersection and endpoint; - // if there are no points found, we have a valid connection; - // otherwise choose the point of the minimum angle with the ray as connection point - - const stop = m, - mx = m.x, - my = m.y; - let tanMin = Infinity, tan; - - p = m; - - do { - - if ( hx >= p.x && p.x >= mx && hx !== p.x && - pointInTriangle( hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y ) ) { - - tan = Math.abs( hy - p.y ) / ( hx - p.x ); // tangential - - if ( locallyInside( p, hole ) && ( tan < tanMin || ( tan === tanMin && ( p.x > m.x || ( p.x === m.x && sectorContainsSector( m, p ) ) ) ) ) ) { - - m = p; - tanMin = tan; - - } - - } - - p = p.next; - - } while ( p !== stop ); - - return m; - - } - - // whether sector in vertex m contains sector in vertex p in the same coordinates - function sectorContainsSector( m, p ) { - - return area( m.prev, m, p.prev ) < 0 && area( p.next, m, m.next ) < 0; - - } - - // interlink polygon nodes in z-order - function indexCurve( start, minX, minY, invSize ) { - - let p = start; - do { - - if ( p.z === 0 ) p.z = zOrder( p.x, p.y, minX, minY, invSize ); - p.prevZ = p.prev; - p.nextZ = p.next; - p = p.next; - - } while ( p !== start ); - - p.prevZ.nextZ = null; - p.prevZ = null; - - sortLinked( p ); - - } - - // Simon Tatham's linked list merge sort algorithm - // http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html - function sortLinked( list ) { - - let i, p, q, e, tail, numMerges, pSize, qSize, - inSize = 1; - - do { - - p = list; - list = null; - tail = null; - numMerges = 0; - - while ( p ) { - - numMerges ++; - q = p; - pSize = 0; - for ( i = 0; i < inSize; i ++ ) { - - pSize ++; - q = q.nextZ; - if ( ! q ) break; - - } - - qSize = inSize; - - while ( pSize > 0 || ( qSize > 0 && q ) ) { - - if ( pSize !== 0 && ( qSize === 0 || ! q || p.z <= q.z ) ) { - - e = p; - p = p.nextZ; - pSize --; - - } else { - - e = q; - q = q.nextZ; - qSize --; - - } - - if ( tail ) tail.nextZ = e; - else list = e; - - e.prevZ = tail; - tail = e; - - } - - p = q; - - } - - tail.nextZ = null; - inSize *= 2; - - } while ( numMerges > 1 ); - - return list; - - } - - // z-order of a point given coords and inverse of the longer side of data bbox - function zOrder( x, y, minX, minY, invSize ) { - - // coords are transformed into non-negative 15-bit integer range - x = ( x - minX ) * invSize | 0; - y = ( y - minY ) * invSize | 0; - - x = ( x | ( x << 8 ) ) & 0x00FF00FF; - x = ( x | ( x << 4 ) ) & 0x0F0F0F0F; - x = ( x | ( x << 2 ) ) & 0x33333333; - x = ( x | ( x << 1 ) ) & 0x55555555; - - y = ( y | ( y << 8 ) ) & 0x00FF00FF; - y = ( y | ( y << 4 ) ) & 0x0F0F0F0F; - y = ( y | ( y << 2 ) ) & 0x33333333; - y = ( y | ( y << 1 ) ) & 0x55555555; - - return x | ( y << 1 ); - - } - - // find the leftmost node of a polygon ring - function getLeftmost( start ) { - - let p = start, - leftmost = start; - do { - - if ( p.x < leftmost.x || ( p.x === leftmost.x && p.y < leftmost.y ) ) leftmost = p; - p = p.next; - - } while ( p !== start ); - - return leftmost; - - } - - // check if a point lies within a convex triangle - function pointInTriangle( ax, ay, bx, by, cx, cy, px, py ) { - - return ( cx - px ) * ( ay - py ) >= ( ax - px ) * ( cy - py ) && - ( ax - px ) * ( by - py ) >= ( bx - px ) * ( ay - py ) && - ( bx - px ) * ( cy - py ) >= ( cx - px ) * ( by - py ); - - } - - // check if a diagonal between two polygon nodes is valid (lies in polygon interior) - function isValidDiagonal( a, b ) { - - return a.next.i !== b.i && a.prev.i !== b.i && ! intersectsPolygon( a, b ) && // dones't intersect other edges - ( locallyInside( a, b ) && locallyInside( b, a ) && middleInside( a, b ) && // locally visible - ( area( a.prev, a, b.prev ) || area( a, b.prev, b ) ) || // does not create opposite-facing sectors - equals( a, b ) && area( a.prev, a, a.next ) > 0 && area( b.prev, b, b.next ) > 0 ); // special zero-length case - - } - - // signed area of a triangle - function area( p, q, r ) { - - return ( q.y - p.y ) * ( r.x - q.x ) - ( q.x - p.x ) * ( r.y - q.y ); - - } - - // check if two points are equal - function equals( p1, p2 ) { - - return p1.x === p2.x && p1.y === p2.y; - - } - - // check if two segments intersect - function intersects( p1, q1, p2, q2 ) { - - const o1 = sign( area( p1, q1, p2 ) ); - const o2 = sign( area( p1, q1, q2 ) ); - const o3 = sign( area( p2, q2, p1 ) ); - const o4 = sign( area( p2, q2, q1 ) ); - - if ( o1 !== o2 && o3 !== o4 ) return true; // general case - - if ( o1 === 0 && onSegment( p1, p2, q1 ) ) return true; // p1, q1 and p2 are collinear and p2 lies on p1q1 - if ( o2 === 0 && onSegment( p1, q2, q1 ) ) return true; // p1, q1 and q2 are collinear and q2 lies on p1q1 - if ( o3 === 0 && onSegment( p2, p1, q2 ) ) return true; // p2, q2 and p1 are collinear and p1 lies on p2q2 - if ( o4 === 0 && onSegment( p2, q1, q2 ) ) return true; // p2, q2 and q1 are collinear and q1 lies on p2q2 - - return false; - - } - - // for collinear points p, q, r, check if point q lies on segment pr - function onSegment( p, q, r ) { - - return q.x <= Math.max( p.x, r.x ) && q.x >= Math.min( p.x, r.x ) && q.y <= Math.max( p.y, r.y ) && q.y >= Math.min( p.y, r.y ); - - } - - function sign( num ) { - - return num > 0 ? 1 : num < 0 ? - 1 : 0; - - } - - // check if a polygon diagonal intersects any polygon segments - function intersectsPolygon( a, b ) { - - let p = a; - do { - - if ( p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i && - intersects( p, p.next, a, b ) ) return true; - p = p.next; - - } while ( p !== a ); - - return false; - - } - - // check if a polygon diagonal is locally inside the polygon - function locallyInside( a, b ) { - - return area( a.prev, a, a.next ) < 0 ? - area( a, b, a.next ) >= 0 && area( a, a.prev, b ) >= 0 : - area( a, b, a.prev ) < 0 || area( a, a.next, b ) < 0; - - } - - // check if the middle point of a polygon diagonal is inside the polygon - function middleInside( a, b ) { - - let p = a, - inside = false; - const px = ( a.x + b.x ) / 2, - py = ( a.y + b.y ) / 2; - do { - - if ( ( ( p.y > py ) !== ( p.next.y > py ) ) && p.next.y !== p.y && - ( px < ( p.next.x - p.x ) * ( py - p.y ) / ( p.next.y - p.y ) + p.x ) ) - inside = ! inside; - p = p.next; - - } while ( p !== a ); - - return inside; - - } - - // link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two; - // if one belongs to the outer ring and another to a hole, it merges it into a single ring - function splitPolygon( a, b ) { - - const a2 = new Node( a.i, a.x, a.y ), - b2 = new Node( b.i, b.x, b.y ), - an = a.next, - bp = b.prev; - - a.next = b; - b.prev = a; - - a2.next = an; - an.prev = a2; - - b2.next = a2; - a2.prev = b2; - - bp.next = b2; - b2.prev = bp; - - return b2; - - } - - // create a node and optionally link it with previous one (in a circular doubly linked list) - function insertNode( i, x, y, last ) { - - const p = new Node( i, x, y ); - - if ( ! last ) { - - p.prev = p; - p.next = p; - - } else { - - p.next = last.next; - p.prev = last; - last.next.prev = p; - last.next = p; - - } - - return p; - - } - - function removeNode( p ) { - - p.next.prev = p.prev; - p.prev.next = p.next; - - if ( p.prevZ ) p.prevZ.nextZ = p.nextZ; - if ( p.nextZ ) p.nextZ.prevZ = p.prevZ; - - } - - function Node( i, x, y ) { - - // vertex index in coordinates array - this.i = i; - - // vertex coordinates - this.x = x; - this.y = y; - - // previous and next vertex nodes in a polygon ring - this.prev = null; - this.next = null; - - // z-order curve value - this.z = 0; - - // previous and next nodes in z-order - this.prevZ = null; - this.nextZ = null; - - // indicates whether this is a steiner point - this.steiner = false; - - } - - function signedArea( data, start, end, dim ) { - - let sum = 0; - for ( let i = start, j = end - dim; i < end; i += dim ) { - - sum += ( data[ j ] - data[ i ] ) * ( data[ i + 1 ] + data[ j + 1 ] ); - j = i; - - } - - return sum; - - } - - class ShapeUtils { - - // calculate area of the contour polygon - - static area( contour ) { - - const n = contour.length; - let a = 0.0; - - for ( let p = n - 1, q = 0; q < n; p = q ++ ) { - - a += contour[ p ].x * contour[ q ].y - contour[ q ].x * contour[ p ].y; - - } - - return a * 0.5; - - } - - static isClockWise( pts ) { - - return ShapeUtils.area( pts ) < 0; - - } - - static triangulateShape( contour, holes ) { - - const vertices = []; // flat array of vertices like [ x0,y0, x1,y1, x2,y2, ... ] - const holeIndices = []; // array of hole indices - const faces = []; // final array of vertex indices like [ [ a,b,d ], [ b,c,d ] ] - - removeDupEndPts( contour ); - addContour( vertices, contour ); - - // - - let holeIndex = contour.length; - - holes.forEach( removeDupEndPts ); - - for ( let i = 0; i < holes.length; i ++ ) { - - holeIndices.push( holeIndex ); - holeIndex += holes[ i ].length; - addContour( vertices, holes[ i ] ); - - } - - // - - const triangles = Earcut.triangulate( vertices, holeIndices ); - - // - - for ( let i = 0; i < triangles.length; i += 3 ) { - - faces.push( triangles.slice( i, i + 3 ) ); - - } - - return faces; - - } - - } - - function removeDupEndPts( points ) { - - const l = points.length; - - if ( l > 2 && points[ l - 1 ].equals( points[ 0 ] ) ) { - - points.pop(); - - } - - } - - function addContour( vertices, contour ) { - - for ( let i = 0; i < contour.length; i ++ ) { - - vertices.push( contour[ i ].x ); - vertices.push( contour[ i ].y ); - - } - - } - - /** - * Creates extruded geometry from a path shape. - * - * parameters = { - * - * curveSegments: , // number of points on the curves - * steps: , // number of points for z-side extrusions / used for subdividing segments of extrude spline too - * depth: , // Depth to extrude the shape - * - * bevelEnabled: , // turn on bevel - * bevelThickness: , // how deep into the original shape bevel goes - * bevelSize: , // how far from shape outline (including bevelOffset) is bevel - * bevelOffset: , // how far from shape outline does bevel start - * bevelSegments: , // number of bevel layers - * - * extrudePath: // curve to extrude shape along - * - * UVGenerator: // object that provides UV generator functions - * - * } - */ - - - class ExtrudeGeometry extends BufferGeometry { - - constructor( shapes = new Shape( [ new Vector2( 0.5, 0.5 ), new Vector2( - 0.5, 0.5 ), new Vector2( - 0.5, - 0.5 ), new Vector2( 0.5, - 0.5 ) ] ), options = {} ) { - - super(); - - this.type = 'ExtrudeGeometry'; - - this.parameters = { - shapes: shapes, - options: options - }; - - shapes = Array.isArray( shapes ) ? shapes : [ shapes ]; - - const scope = this; - - const verticesArray = []; - const uvArray = []; - - for ( let i = 0, l = shapes.length; i < l; i ++ ) { - - const shape = shapes[ i ]; - addShape( shape ); - - } - - // build geometry - - this.setAttribute( 'position', new Float32BufferAttribute( verticesArray, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvArray, 2 ) ); - - this.computeVertexNormals(); - - // functions - - function addShape( shape ) { - - const placeholder = []; - - // options - - const curveSegments = options.curveSegments !== undefined ? options.curveSegments : 12; - const steps = options.steps !== undefined ? options.steps : 1; - const depth = options.depth !== undefined ? options.depth : 1; - - let bevelEnabled = options.bevelEnabled !== undefined ? options.bevelEnabled : true; - let bevelThickness = options.bevelThickness !== undefined ? options.bevelThickness : 0.2; - let bevelSize = options.bevelSize !== undefined ? options.bevelSize : bevelThickness - 0.1; - let bevelOffset = options.bevelOffset !== undefined ? options.bevelOffset : 0; - let bevelSegments = options.bevelSegments !== undefined ? options.bevelSegments : 3; - - const extrudePath = options.extrudePath; - - const uvgen = options.UVGenerator !== undefined ? options.UVGenerator : WorldUVGenerator; - - // - - let extrudePts, extrudeByPath = false; - let splineTube, binormal, normal, position2; - - if ( extrudePath ) { - - extrudePts = extrudePath.getSpacedPoints( steps ); - - extrudeByPath = true; - bevelEnabled = false; // bevels not supported for path extrusion - - // SETUP TNB variables - - // TODO1 - have a .isClosed in spline? - - splineTube = extrudePath.computeFrenetFrames( steps, false ); - - // console.log(splineTube, 'splineTube', splineTube.normals.length, 'steps', steps, 'extrudePts', extrudePts.length); - - binormal = new Vector3(); - normal = new Vector3(); - position2 = new Vector3(); - - } - - // Safeguards if bevels are not enabled - - if ( ! bevelEnabled ) { - - bevelSegments = 0; - bevelThickness = 0; - bevelSize = 0; - bevelOffset = 0; - - } - - // Variables initialization - - const shapePoints = shape.extractPoints( curveSegments ); - - let vertices = shapePoints.shape; - const holes = shapePoints.holes; - - const reverse = ! ShapeUtils.isClockWise( vertices ); - - if ( reverse ) { - - vertices = vertices.reverse(); - - // Maybe we should also check if holes are in the opposite direction, just to be safe ... - - for ( let h = 0, hl = holes.length; h < hl; h ++ ) { - - const ahole = holes[ h ]; - - if ( ShapeUtils.isClockWise( ahole ) ) { - - holes[ h ] = ahole.reverse(); - - } - - } - - } - - - const faces = ShapeUtils.triangulateShape( vertices, holes ); - - /* Vertices */ - - const contour = vertices; // vertices has all points but contour has only points of circumference - - for ( let h = 0, hl = holes.length; h < hl; h ++ ) { - - const ahole = holes[ h ]; - - vertices = vertices.concat( ahole ); - - } - - - function scalePt2( pt, vec, size ) { - - if ( ! vec ) console.error( 'THREE.ExtrudeGeometry: vec does not exist' ); - - return pt.clone().addScaledVector( vec, size ); - - } - - const vlen = vertices.length, flen = faces.length; - - - // Find directions for point movement - - - function getBevelVec( inPt, inPrev, inNext ) { - - // computes for inPt the corresponding point inPt' on a new contour - // shifted by 1 unit (length of normalized vector) to the left - // if we walk along contour clockwise, this new contour is outside the old one - // - // inPt' is the intersection of the two lines parallel to the two - // adjacent edges of inPt at a distance of 1 unit on the left side. - - let v_trans_x, v_trans_y, shrink_by; // resulting translation vector for inPt - - // good reading for geometry algorithms (here: line-line intersection) - // http://geomalgorithms.com/a05-_intersect-1.html - - const v_prev_x = inPt.x - inPrev.x, - v_prev_y = inPt.y - inPrev.y; - const v_next_x = inNext.x - inPt.x, - v_next_y = inNext.y - inPt.y; - - const v_prev_lensq = ( v_prev_x * v_prev_x + v_prev_y * v_prev_y ); - - // check for collinear edges - const collinear0 = ( v_prev_x * v_next_y - v_prev_y * v_next_x ); - - if ( Math.abs( collinear0 ) > Number.EPSILON ) { - - // not collinear - - // length of vectors for normalizing - - const v_prev_len = Math.sqrt( v_prev_lensq ); - const v_next_len = Math.sqrt( v_next_x * v_next_x + v_next_y * v_next_y ); - - // shift adjacent points by unit vectors to the left - - const ptPrevShift_x = ( inPrev.x - v_prev_y / v_prev_len ); - const ptPrevShift_y = ( inPrev.y + v_prev_x / v_prev_len ); - - const ptNextShift_x = ( inNext.x - v_next_y / v_next_len ); - const ptNextShift_y = ( inNext.y + v_next_x / v_next_len ); - - // scaling factor for v_prev to intersection point - - const sf = ( ( ptNextShift_x - ptPrevShift_x ) * v_next_y - - ( ptNextShift_y - ptPrevShift_y ) * v_next_x ) / - ( v_prev_x * v_next_y - v_prev_y * v_next_x ); - - // vector from inPt to intersection point - - v_trans_x = ( ptPrevShift_x + v_prev_x * sf - inPt.x ); - v_trans_y = ( ptPrevShift_y + v_prev_y * sf - inPt.y ); - - // Don't normalize!, otherwise sharp corners become ugly - // but prevent crazy spikes - const v_trans_lensq = ( v_trans_x * v_trans_x + v_trans_y * v_trans_y ); - if ( v_trans_lensq <= 2 ) { - - return new Vector2( v_trans_x, v_trans_y ); - - } else { - - shrink_by = Math.sqrt( v_trans_lensq / 2 ); - - } - - } else { - - // handle special case of collinear edges - - let direction_eq = false; // assumes: opposite - - if ( v_prev_x > Number.EPSILON ) { - - if ( v_next_x > Number.EPSILON ) { - - direction_eq = true; - - } - - } else { - - if ( v_prev_x < - Number.EPSILON ) { - - if ( v_next_x < - Number.EPSILON ) { - - direction_eq = true; - - } - - } else { - - if ( Math.sign( v_prev_y ) === Math.sign( v_next_y ) ) { - - direction_eq = true; - - } - - } - - } - - if ( direction_eq ) { - - // console.log("Warning: lines are a straight sequence"); - v_trans_x = - v_prev_y; - v_trans_y = v_prev_x; - shrink_by = Math.sqrt( v_prev_lensq ); - - } else { - - // console.log("Warning: lines are a straight spike"); - v_trans_x = v_prev_x; - v_trans_y = v_prev_y; - shrink_by = Math.sqrt( v_prev_lensq / 2 ); - - } - - } - - return new Vector2( v_trans_x / shrink_by, v_trans_y / shrink_by ); - - } - - - const contourMovements = []; - - for ( let i = 0, il = contour.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) { - - if ( j === il ) j = 0; - if ( k === il ) k = 0; - - // (j)---(i)---(k) - // console.log('i,j,k', i, j , k) - - contourMovements[ i ] = getBevelVec( contour[ i ], contour[ j ], contour[ k ] ); - - } - - const holesMovements = []; - let oneHoleMovements, verticesMovements = contourMovements.concat(); - - for ( let h = 0, hl = holes.length; h < hl; h ++ ) { - - const ahole = holes[ h ]; - - oneHoleMovements = []; - - for ( let i = 0, il = ahole.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) { - - if ( j === il ) j = 0; - if ( k === il ) k = 0; - - // (j)---(i)---(k) - oneHoleMovements[ i ] = getBevelVec( ahole[ i ], ahole[ j ], ahole[ k ] ); - - } - - holesMovements.push( oneHoleMovements ); - verticesMovements = verticesMovements.concat( oneHoleMovements ); - - } - - - // Loop bevelSegments, 1 for the front, 1 for the back - - for ( let b = 0; b < bevelSegments; b ++ ) { - - //for ( b = bevelSegments; b > 0; b -- ) { - - const t = b / bevelSegments; - const z = bevelThickness * Math.cos( t * Math.PI / 2 ); - const bs = bevelSize * Math.sin( t * Math.PI / 2 ) + bevelOffset; - - // contract shape - - for ( let i = 0, il = contour.length; i < il; i ++ ) { - - const vert = scalePt2( contour[ i ], contourMovements[ i ], bs ); - - v( vert.x, vert.y, - z ); - - } - - // expand holes - - for ( let h = 0, hl = holes.length; h < hl; h ++ ) { - - const ahole = holes[ h ]; - oneHoleMovements = holesMovements[ h ]; - - for ( let i = 0, il = ahole.length; i < il; i ++ ) { - - const vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs ); - - v( vert.x, vert.y, - z ); - - } - - } - - } - - const bs = bevelSize + bevelOffset; - - // Back facing vertices - - for ( let i = 0; i < vlen; i ++ ) { - - const vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ]; - - if ( ! extrudeByPath ) { - - v( vert.x, vert.y, 0 ); - - } else { - - // v( vert.x, vert.y + extrudePts[ 0 ].y, extrudePts[ 0 ].x ); - - normal.copy( splineTube.normals[ 0 ] ).multiplyScalar( vert.x ); - binormal.copy( splineTube.binormals[ 0 ] ).multiplyScalar( vert.y ); - - position2.copy( extrudePts[ 0 ] ).add( normal ).add( binormal ); - - v( position2.x, position2.y, position2.z ); - - } - - } - - // Add stepped vertices... - // Including front facing vertices - - for ( let s = 1; s <= steps; s ++ ) { - - for ( let i = 0; i < vlen; i ++ ) { - - const vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ]; - - if ( ! extrudeByPath ) { - - v( vert.x, vert.y, depth / steps * s ); - - } else { - - // v( vert.x, vert.y + extrudePts[ s - 1 ].y, extrudePts[ s - 1 ].x ); - - normal.copy( splineTube.normals[ s ] ).multiplyScalar( vert.x ); - binormal.copy( splineTube.binormals[ s ] ).multiplyScalar( vert.y ); - - position2.copy( extrudePts[ s ] ).add( normal ).add( binormal ); - - v( position2.x, position2.y, position2.z ); - - } - - } - - } - - - // Add bevel segments planes - - //for ( b = 1; b <= bevelSegments; b ++ ) { - for ( let b = bevelSegments - 1; b >= 0; b -- ) { - - const t = b / bevelSegments; - const z = bevelThickness * Math.cos( t * Math.PI / 2 ); - const bs = bevelSize * Math.sin( t * Math.PI / 2 ) + bevelOffset; - - // contract shape - - for ( let i = 0, il = contour.length; i < il; i ++ ) { - - const vert = scalePt2( contour[ i ], contourMovements[ i ], bs ); - v( vert.x, vert.y, depth + z ); - - } - - // expand holes - - for ( let h = 0, hl = holes.length; h < hl; h ++ ) { - - const ahole = holes[ h ]; - oneHoleMovements = holesMovements[ h ]; - - for ( let i = 0, il = ahole.length; i < il; i ++ ) { - - const vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs ); - - if ( ! extrudeByPath ) { - - v( vert.x, vert.y, depth + z ); - - } else { - - v( vert.x, vert.y + extrudePts[ steps - 1 ].y, extrudePts[ steps - 1 ].x + z ); - - } - - } - - } - - } - - /* Faces */ - - // Top and bottom faces - - buildLidFaces(); - - // Sides faces - - buildSideFaces(); - - - ///// Internal functions - - function buildLidFaces() { - - const start = verticesArray.length / 3; - - if ( bevelEnabled ) { - - let layer = 0; // steps + 1 - let offset = vlen * layer; - - // Bottom faces - - for ( let i = 0; i < flen; i ++ ) { - - const face = faces[ i ]; - f3( face[ 2 ] + offset, face[ 1 ] + offset, face[ 0 ] + offset ); - - } - - layer = steps + bevelSegments * 2; - offset = vlen * layer; - - // Top faces - - for ( let i = 0; i < flen; i ++ ) { - - const face = faces[ i ]; - f3( face[ 0 ] + offset, face[ 1 ] + offset, face[ 2 ] + offset ); - - } - - } else { - - // Bottom faces - - for ( let i = 0; i < flen; i ++ ) { - - const face = faces[ i ]; - f3( face[ 2 ], face[ 1 ], face[ 0 ] ); - - } - - // Top faces - - for ( let i = 0; i < flen; i ++ ) { - - const face = faces[ i ]; - f3( face[ 0 ] + vlen * steps, face[ 1 ] + vlen * steps, face[ 2 ] + vlen * steps ); - - } - - } - - scope.addGroup( start, verticesArray.length / 3 - start, 0 ); - - } - - // Create faces for the z-sides of the shape - - function buildSideFaces() { - - const start = verticesArray.length / 3; - let layeroffset = 0; - sidewalls( contour, layeroffset ); - layeroffset += contour.length; - - for ( let h = 0, hl = holes.length; h < hl; h ++ ) { - - const ahole = holes[ h ]; - sidewalls( ahole, layeroffset ); - - //, true - layeroffset += ahole.length; - - } - - - scope.addGroup( start, verticesArray.length / 3 - start, 1 ); - - - } - - function sidewalls( contour, layeroffset ) { - - let i = contour.length; - - while ( -- i >= 0 ) { - - const j = i; - let k = i - 1; - if ( k < 0 ) k = contour.length - 1; - - //console.log('b', i,j, i-1, k,vertices.length); - - for ( let s = 0, sl = ( steps + bevelSegments * 2 ); s < sl; s ++ ) { - - const slen1 = vlen * s; - const slen2 = vlen * ( s + 1 ); - - const a = layeroffset + j + slen1, - b = layeroffset + k + slen1, - c = layeroffset + k + slen2, - d = layeroffset + j + slen2; - - f4( a, b, c, d ); - - } - - } - - } - - function v( x, y, z ) { - - placeholder.push( x ); - placeholder.push( y ); - placeholder.push( z ); - - } - - - function f3( a, b, c ) { - - addVertex( a ); - addVertex( b ); - addVertex( c ); - - const nextIndex = verticesArray.length / 3; - const uvs = uvgen.generateTopUV( scope, verticesArray, nextIndex - 3, nextIndex - 2, nextIndex - 1 ); - - addUV( uvs[ 0 ] ); - addUV( uvs[ 1 ] ); - addUV( uvs[ 2 ] ); - - } - - function f4( a, b, c, d ) { - - addVertex( a ); - addVertex( b ); - addVertex( d ); - - addVertex( b ); - addVertex( c ); - addVertex( d ); - - - const nextIndex = verticesArray.length / 3; - const uvs = uvgen.generateSideWallUV( scope, verticesArray, nextIndex - 6, nextIndex - 3, nextIndex - 2, nextIndex - 1 ); - - addUV( uvs[ 0 ] ); - addUV( uvs[ 1 ] ); - addUV( uvs[ 3 ] ); - - addUV( uvs[ 1 ] ); - addUV( uvs[ 2 ] ); - addUV( uvs[ 3 ] ); - - } - - function addVertex( index ) { - - verticesArray.push( placeholder[ index * 3 + 0 ] ); - verticesArray.push( placeholder[ index * 3 + 1 ] ); - verticesArray.push( placeholder[ index * 3 + 2 ] ); - - } - - - function addUV( vector2 ) { - - uvArray.push( vector2.x ); - uvArray.push( vector2.y ); - - } - - } - - } - - copy( source ) { - - super.copy( source ); - - this.parameters = Object.assign( {}, source.parameters ); - - return this; - - } - - toJSON() { - - const data = super.toJSON(); - - const shapes = this.parameters.shapes; - const options = this.parameters.options; - - return toJSON$1( shapes, options, data ); - - } - - static fromJSON( data, shapes ) { - - const geometryShapes = []; - - for ( let j = 0, jl = data.shapes.length; j < jl; j ++ ) { - - const shape = shapes[ data.shapes[ j ] ]; - - geometryShapes.push( shape ); - - } - - const extrudePath = data.options.extrudePath; - - if ( extrudePath !== undefined ) { - - data.options.extrudePath = new Curves[ extrudePath.type ]().fromJSON( extrudePath ); - - } - - return new ExtrudeGeometry( geometryShapes, data.options ); - - } - - } - - const WorldUVGenerator = { - - generateTopUV: function ( geometry, vertices, indexA, indexB, indexC ) { - - const a_x = vertices[ indexA * 3 ]; - const a_y = vertices[ indexA * 3 + 1 ]; - const b_x = vertices[ indexB * 3 ]; - const b_y = vertices[ indexB * 3 + 1 ]; - const c_x = vertices[ indexC * 3 ]; - const c_y = vertices[ indexC * 3 + 1 ]; - - return [ - new Vector2( a_x, a_y ), - new Vector2( b_x, b_y ), - new Vector2( c_x, c_y ) - ]; - - }, - - generateSideWallUV: function ( geometry, vertices, indexA, indexB, indexC, indexD ) { - - const a_x = vertices[ indexA * 3 ]; - const a_y = vertices[ indexA * 3 + 1 ]; - const a_z = vertices[ indexA * 3 + 2 ]; - const b_x = vertices[ indexB * 3 ]; - const b_y = vertices[ indexB * 3 + 1 ]; - const b_z = vertices[ indexB * 3 + 2 ]; - const c_x = vertices[ indexC * 3 ]; - const c_y = vertices[ indexC * 3 + 1 ]; - const c_z = vertices[ indexC * 3 + 2 ]; - const d_x = vertices[ indexD * 3 ]; - const d_y = vertices[ indexD * 3 + 1 ]; - const d_z = vertices[ indexD * 3 + 2 ]; - - if ( Math.abs( a_y - b_y ) < Math.abs( a_x - b_x ) ) { - - return [ - new Vector2( a_x, 1 - a_z ), - new Vector2( b_x, 1 - b_z ), - new Vector2( c_x, 1 - c_z ), - new Vector2( d_x, 1 - d_z ) - ]; - - } else { - - return [ - new Vector2( a_y, 1 - a_z ), - new Vector2( b_y, 1 - b_z ), - new Vector2( c_y, 1 - c_z ), - new Vector2( d_y, 1 - d_z ) - ]; - - } - - } - - }; - - function toJSON$1( shapes, options, data ) { - - data.shapes = []; - - if ( Array.isArray( shapes ) ) { - - for ( let i = 0, l = shapes.length; i < l; i ++ ) { - - const shape = shapes[ i ]; - - data.shapes.push( shape.uuid ); - - } - - } else { - - data.shapes.push( shapes.uuid ); - - } - - data.options = Object.assign( {}, options ); - - if ( options.extrudePath !== undefined ) data.options.extrudePath = options.extrudePath.toJSON(); - - return data; - - } - - class IcosahedronGeometry extends PolyhedronGeometry { - - constructor( radius = 1, detail = 0 ) { - - const t = ( 1 + Math.sqrt( 5 ) ) / 2; - - const vertices = [ - - 1, t, 0, 1, t, 0, - 1, - t, 0, 1, - t, 0, - 0, - 1, t, 0, 1, t, 0, - 1, - t, 0, 1, - t, - t, 0, - 1, t, 0, 1, - t, 0, - 1, - t, 0, 1 - ]; - - const indices = [ - 0, 11, 5, 0, 5, 1, 0, 1, 7, 0, 7, 10, 0, 10, 11, - 1, 5, 9, 5, 11, 4, 11, 10, 2, 10, 7, 6, 7, 1, 8, - 3, 9, 4, 3, 4, 2, 3, 2, 6, 3, 6, 8, 3, 8, 9, - 4, 9, 5, 2, 4, 11, 6, 2, 10, 8, 6, 7, 9, 8, 1 - ]; - - super( vertices, indices, radius, detail ); - - this.type = 'IcosahedronGeometry'; - - this.parameters = { - radius: radius, - detail: detail - }; - - } - - static fromJSON( data ) { - - return new IcosahedronGeometry( data.radius, data.detail ); - - } - - } - - class OctahedronGeometry extends PolyhedronGeometry { - - constructor( radius = 1, detail = 0 ) { - - const vertices = [ - 1, 0, 0, - 1, 0, 0, 0, 1, 0, - 0, - 1, 0, 0, 0, 1, 0, 0, - 1 - ]; - - const indices = [ - 0, 2, 4, 0, 4, 3, 0, 3, 5, - 0, 5, 2, 1, 2, 5, 1, 5, 3, - 1, 3, 4, 1, 4, 2 - ]; - - super( vertices, indices, radius, detail ); - - this.type = 'OctahedronGeometry'; - - this.parameters = { - radius: radius, - detail: detail - }; - - } - - static fromJSON( data ) { - - return new OctahedronGeometry( data.radius, data.detail ); - - } - - } - - class RingGeometry extends BufferGeometry { - - constructor( innerRadius = 0.5, outerRadius = 1, thetaSegments = 32, phiSegments = 1, thetaStart = 0, thetaLength = Math.PI * 2 ) { - - super(); - - this.type = 'RingGeometry'; - - this.parameters = { - innerRadius: innerRadius, - outerRadius: outerRadius, - thetaSegments: thetaSegments, - phiSegments: phiSegments, - thetaStart: thetaStart, - thetaLength: thetaLength - }; - - thetaSegments = Math.max( 3, thetaSegments ); - phiSegments = Math.max( 1, phiSegments ); - - // buffers - - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; - - // some helper variables - - let radius = innerRadius; - const radiusStep = ( ( outerRadius - innerRadius ) / phiSegments ); - const vertex = new Vector3(); - const uv = new Vector2(); - - // generate vertices, normals and uvs - - for ( let j = 0; j <= phiSegments; j ++ ) { - - for ( let i = 0; i <= thetaSegments; i ++ ) { - - // values are generate from the inside of the ring to the outside - - const segment = thetaStart + i / thetaSegments * thetaLength; - - // vertex - - vertex.x = radius * Math.cos( segment ); - vertex.y = radius * Math.sin( segment ); - - vertices.push( vertex.x, vertex.y, vertex.z ); - - // normal - - normals.push( 0, 0, 1 ); - - // uv - - uv.x = ( vertex.x / outerRadius + 1 ) / 2; - uv.y = ( vertex.y / outerRadius + 1 ) / 2; - - uvs.push( uv.x, uv.y ); - - } - - // increase the radius for next row of vertices - - radius += radiusStep; - - } - - // indices - - for ( let j = 0; j < phiSegments; j ++ ) { - - const thetaSegmentLevel = j * ( thetaSegments + 1 ); - - for ( let i = 0; i < thetaSegments; i ++ ) { - - const segment = i + thetaSegmentLevel; - - const a = segment; - const b = segment + thetaSegments + 1; - const c = segment + thetaSegments + 2; - const d = segment + 1; - - // faces - - indices.push( a, b, d ); - indices.push( b, c, d ); - - } - - } - - // build geometry - - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - - } - - copy( source ) { - - super.copy( source ); - - this.parameters = Object.assign( {}, source.parameters ); - - return this; - - } - - static fromJSON( data ) { - - return new RingGeometry( data.innerRadius, data.outerRadius, data.thetaSegments, data.phiSegments, data.thetaStart, data.thetaLength ); - - } - - } - - class ShapeGeometry extends BufferGeometry { - - constructor( shapes = new Shape( [ new Vector2( 0, 0.5 ), new Vector2( - 0.5, - 0.5 ), new Vector2( 0.5, - 0.5 ) ] ), curveSegments = 12 ) { - - super(); - - this.type = 'ShapeGeometry'; - - this.parameters = { - shapes: shapes, - curveSegments: curveSegments - }; - - // buffers - - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; - - // helper variables - - let groupStart = 0; - let groupCount = 0; - - // allow single and array values for "shapes" parameter - - if ( Array.isArray( shapes ) === false ) { - - addShape( shapes ); - - } else { - - for ( let i = 0; i < shapes.length; i ++ ) { - - addShape( shapes[ i ] ); - - this.addGroup( groupStart, groupCount, i ); // enables MultiMaterial support - - groupStart += groupCount; - groupCount = 0; - - } - - } - - // build geometry - - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - - - // helper functions - - function addShape( shape ) { - - const indexOffset = vertices.length / 3; - const points = shape.extractPoints( curveSegments ); - - let shapeVertices = points.shape; - const shapeHoles = points.holes; - - // check direction of vertices - - if ( ShapeUtils.isClockWise( shapeVertices ) === false ) { - - shapeVertices = shapeVertices.reverse(); - - } - - for ( let i = 0, l = shapeHoles.length; i < l; i ++ ) { - - const shapeHole = shapeHoles[ i ]; - - if ( ShapeUtils.isClockWise( shapeHole ) === true ) { - - shapeHoles[ i ] = shapeHole.reverse(); - - } - - } - - const faces = ShapeUtils.triangulateShape( shapeVertices, shapeHoles ); - - // join vertices of inner and outer paths to a single array - - for ( let i = 0, l = shapeHoles.length; i < l; i ++ ) { - - const shapeHole = shapeHoles[ i ]; - shapeVertices = shapeVertices.concat( shapeHole ); - - } - - // vertices, normals, uvs - - for ( let i = 0, l = shapeVertices.length; i < l; i ++ ) { - - const vertex = shapeVertices[ i ]; - - vertices.push( vertex.x, vertex.y, 0 ); - normals.push( 0, 0, 1 ); - uvs.push( vertex.x, vertex.y ); // world uvs - - } - - // indices - - for ( let i = 0, l = faces.length; i < l; i ++ ) { - - const face = faces[ i ]; - - const a = face[ 0 ] + indexOffset; - const b = face[ 1 ] + indexOffset; - const c = face[ 2 ] + indexOffset; - - indices.push( a, b, c ); - groupCount += 3; - - } - - } - - } - - copy( source ) { - - super.copy( source ); - - this.parameters = Object.assign( {}, source.parameters ); - - return this; - - } - - toJSON() { - - const data = super.toJSON(); - - const shapes = this.parameters.shapes; - - return toJSON( shapes, data ); - - } - - static fromJSON( data, shapes ) { - - const geometryShapes = []; - - for ( let j = 0, jl = data.shapes.length; j < jl; j ++ ) { - - const shape = shapes[ data.shapes[ j ] ]; - - geometryShapes.push( shape ); - - } - - return new ShapeGeometry( geometryShapes, data.curveSegments ); - - } - - } - - function toJSON( shapes, data ) { - - data.shapes = []; - - if ( Array.isArray( shapes ) ) { - - for ( let i = 0, l = shapes.length; i < l; i ++ ) { - - const shape = shapes[ i ]; - - data.shapes.push( shape.uuid ); - - } - - } else { - - data.shapes.push( shapes.uuid ); - - } - - return data; - - } - - class SphereGeometry extends BufferGeometry { - - constructor( radius = 1, widthSegments = 32, heightSegments = 16, phiStart = 0, phiLength = Math.PI * 2, thetaStart = 0, thetaLength = Math.PI ) { - - super(); - - this.type = 'SphereGeometry'; - - this.parameters = { - radius: radius, - widthSegments: widthSegments, - heightSegments: heightSegments, - phiStart: phiStart, - phiLength: phiLength, - thetaStart: thetaStart, - thetaLength: thetaLength - }; - - widthSegments = Math.max( 3, Math.floor( widthSegments ) ); - heightSegments = Math.max( 2, Math.floor( heightSegments ) ); - - const thetaEnd = Math.min( thetaStart + thetaLength, Math.PI ); - - let index = 0; - const grid = []; - - const vertex = new Vector3(); - const normal = new Vector3(); - - // buffers - - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; - - // generate vertices, normals and uvs - - for ( let iy = 0; iy <= heightSegments; iy ++ ) { - - const verticesRow = []; - - const v = iy / heightSegments; - - // special case for the poles - - let uOffset = 0; - - if ( iy === 0 && thetaStart === 0 ) { - - uOffset = 0.5 / widthSegments; - - } else if ( iy === heightSegments && thetaEnd === Math.PI ) { - - uOffset = - 0.5 / widthSegments; - - } - - for ( let ix = 0; ix <= widthSegments; ix ++ ) { - - const u = ix / widthSegments; - - // vertex - - vertex.x = - radius * Math.cos( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength ); - vertex.y = radius * Math.cos( thetaStart + v * thetaLength ); - vertex.z = radius * Math.sin( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength ); - - vertices.push( vertex.x, vertex.y, vertex.z ); - - // normal - - normal.copy( vertex ).normalize(); - normals.push( normal.x, normal.y, normal.z ); - - // uv - - uvs.push( u + uOffset, 1 - v ); - - verticesRow.push( index ++ ); - - } - - grid.push( verticesRow ); - - } - - // indices - - for ( let iy = 0; iy < heightSegments; iy ++ ) { - - for ( let ix = 0; ix < widthSegments; ix ++ ) { - - const a = grid[ iy ][ ix + 1 ]; - const b = grid[ iy ][ ix ]; - const c = grid[ iy + 1 ][ ix ]; - const d = grid[ iy + 1 ][ ix + 1 ]; - - if ( iy !== 0 || thetaStart > 0 ) indices.push( a, b, d ); - if ( iy !== heightSegments - 1 || thetaEnd < Math.PI ) indices.push( b, c, d ); - - } - - } - - // build geometry - - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - - } - - copy( source ) { - - super.copy( source ); - - this.parameters = Object.assign( {}, source.parameters ); - - return this; - - } - - static fromJSON( data ) { - - return new SphereGeometry( data.radius, data.widthSegments, data.heightSegments, data.phiStart, data.phiLength, data.thetaStart, data.thetaLength ); - - } - - } - - class TetrahedronGeometry extends PolyhedronGeometry { - - constructor( radius = 1, detail = 0 ) { - - const vertices = [ - 1, 1, 1, - 1, - 1, 1, - 1, 1, - 1, 1, - 1, - 1 - ]; - - const indices = [ - 2, 1, 0, 0, 3, 2, 1, 3, 0, 2, 3, 1 - ]; - - super( vertices, indices, radius, detail ); - - this.type = 'TetrahedronGeometry'; - - this.parameters = { - radius: radius, - detail: detail - }; - - } - - static fromJSON( data ) { - - return new TetrahedronGeometry( data.radius, data.detail ); - - } - - } - - class TorusGeometry extends BufferGeometry { - - constructor( radius = 1, tube = 0.4, radialSegments = 12, tubularSegments = 48, arc = Math.PI * 2 ) { - - super(); - - this.type = 'TorusGeometry'; - - this.parameters = { - radius: radius, - tube: tube, - radialSegments: radialSegments, - tubularSegments: tubularSegments, - arc: arc - }; - - radialSegments = Math.floor( radialSegments ); - tubularSegments = Math.floor( tubularSegments ); - - // buffers - - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; - - // helper variables - - const center = new Vector3(); - const vertex = new Vector3(); - const normal = new Vector3(); - - // generate vertices, normals and uvs - - for ( let j = 0; j <= radialSegments; j ++ ) { - - for ( let i = 0; i <= tubularSegments; i ++ ) { - - const u = i / tubularSegments * arc; - const v = j / radialSegments * Math.PI * 2; - - // vertex - - vertex.x = ( radius + tube * Math.cos( v ) ) * Math.cos( u ); - vertex.y = ( radius + tube * Math.cos( v ) ) * Math.sin( u ); - vertex.z = tube * Math.sin( v ); - - vertices.push( vertex.x, vertex.y, vertex.z ); - - // normal - - center.x = radius * Math.cos( u ); - center.y = radius * Math.sin( u ); - normal.subVectors( vertex, center ).normalize(); - - normals.push( normal.x, normal.y, normal.z ); - - // uv - - uvs.push( i / tubularSegments ); - uvs.push( j / radialSegments ); - - } - - } - - // generate indices - - for ( let j = 1; j <= radialSegments; j ++ ) { - - for ( let i = 1; i <= tubularSegments; i ++ ) { - - // indices - - const a = ( tubularSegments + 1 ) * j + i - 1; - const b = ( tubularSegments + 1 ) * ( j - 1 ) + i - 1; - const c = ( tubularSegments + 1 ) * ( j - 1 ) + i; - const d = ( tubularSegments + 1 ) * j + i; - - // faces - - indices.push( a, b, d ); - indices.push( b, c, d ); - - } - - } - - // build geometry - - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - - } - - copy( source ) { - - super.copy( source ); - - this.parameters = Object.assign( {}, source.parameters ); - - return this; - - } - - static fromJSON( data ) { - - return new TorusGeometry( data.radius, data.tube, data.radialSegments, data.tubularSegments, data.arc ); - - } - - } - - class TorusKnotGeometry extends BufferGeometry { - - constructor( radius = 1, tube = 0.4, tubularSegments = 64, radialSegments = 8, p = 2, q = 3 ) { - - super(); - - this.type = 'TorusKnotGeometry'; - - this.parameters = { - radius: radius, - tube: tube, - tubularSegments: tubularSegments, - radialSegments: radialSegments, - p: p, - q: q - }; - - tubularSegments = Math.floor( tubularSegments ); - radialSegments = Math.floor( radialSegments ); - - // buffers - - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; - - // helper variables - - const vertex = new Vector3(); - const normal = new Vector3(); - - const P1 = new Vector3(); - const P2 = new Vector3(); - - const B = new Vector3(); - const T = new Vector3(); - const N = new Vector3(); - - // generate vertices, normals and uvs - - for ( let i = 0; i <= tubularSegments; ++ i ) { - - // the radian "u" is used to calculate the position on the torus curve of the current tubular segment - - const u = i / tubularSegments * p * Math.PI * 2; - - // now we calculate two points. P1 is our current position on the curve, P2 is a little farther ahead. - // these points are used to create a special "coordinate space", which is necessary to calculate the correct vertex positions - - calculatePositionOnCurve( u, p, q, radius, P1 ); - calculatePositionOnCurve( u + 0.01, p, q, radius, P2 ); - - // calculate orthonormal basis - - T.subVectors( P2, P1 ); - N.addVectors( P2, P1 ); - B.crossVectors( T, N ); - N.crossVectors( B, T ); - - // normalize B, N. T can be ignored, we don't use it - - B.normalize(); - N.normalize(); - - for ( let j = 0; j <= radialSegments; ++ j ) { - - // now calculate the vertices. they are nothing more than an extrusion of the torus curve. - // because we extrude a shape in the xy-plane, there is no need to calculate a z-value. - - const v = j / radialSegments * Math.PI * 2; - const cx = - tube * Math.cos( v ); - const cy = tube * Math.sin( v ); - - // now calculate the final vertex position. - // first we orient the extrusion with our basis vectors, then we add it to the current position on the curve - - vertex.x = P1.x + ( cx * N.x + cy * B.x ); - vertex.y = P1.y + ( cx * N.y + cy * B.y ); - vertex.z = P1.z + ( cx * N.z + cy * B.z ); - - vertices.push( vertex.x, vertex.y, vertex.z ); - - // normal (P1 is always the center/origin of the extrusion, thus we can use it to calculate the normal) - - normal.subVectors( vertex, P1 ).normalize(); - - normals.push( normal.x, normal.y, normal.z ); - - // uv - - uvs.push( i / tubularSegments ); - uvs.push( j / radialSegments ); - - } - - } - - // generate indices - - for ( let j = 1; j <= tubularSegments; j ++ ) { - - for ( let i = 1; i <= radialSegments; i ++ ) { - - // indices - - const a = ( radialSegments + 1 ) * ( j - 1 ) + ( i - 1 ); - const b = ( radialSegments + 1 ) * j + ( i - 1 ); - const c = ( radialSegments + 1 ) * j + i; - const d = ( radialSegments + 1 ) * ( j - 1 ) + i; - - // faces - - indices.push( a, b, d ); - indices.push( b, c, d ); - - } - - } - - // build geometry - - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - - // this function calculates the current position on the torus curve - - function calculatePositionOnCurve( u, p, q, radius, position ) { - - const cu = Math.cos( u ); - const su = Math.sin( u ); - const quOverP = q / p * u; - const cs = Math.cos( quOverP ); - - position.x = radius * ( 2 + cs ) * 0.5 * cu; - position.y = radius * ( 2 + cs ) * su * 0.5; - position.z = radius * Math.sin( quOverP ) * 0.5; - - } - - } - - copy( source ) { - - super.copy( source ); - - this.parameters = Object.assign( {}, source.parameters ); - - return this; - - } - - static fromJSON( data ) { - - return new TorusKnotGeometry( data.radius, data.tube, data.tubularSegments, data.radialSegments, data.p, data.q ); - - } - - } - - class TubeGeometry extends BufferGeometry { - - constructor( path = new QuadraticBezierCurve3( new Vector3( - 1, - 1, 0 ), new Vector3( - 1, 1, 0 ), new Vector3( 1, 1, 0 ) ), tubularSegments = 64, radius = 1, radialSegments = 8, closed = false ) { - - super(); - - this.type = 'TubeGeometry'; - - this.parameters = { - path: path, - tubularSegments: tubularSegments, - radius: radius, - radialSegments: radialSegments, - closed: closed - }; - - const frames = path.computeFrenetFrames( tubularSegments, closed ); - - // expose internals - - this.tangents = frames.tangents; - this.normals = frames.normals; - this.binormals = frames.binormals; - - // helper variables - - const vertex = new Vector3(); - const normal = new Vector3(); - const uv = new Vector2(); - let P = new Vector3(); - - // buffer - - const vertices = []; - const normals = []; - const uvs = []; - const indices = []; - - // create buffer data - - generateBufferData(); - - // build geometry - - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - - // functions - - function generateBufferData() { - - for ( let i = 0; i < tubularSegments; i ++ ) { - - generateSegment( i ); - - } - - // if the geometry is not closed, generate the last row of vertices and normals - // at the regular position on the given path - // - // if the geometry is closed, duplicate the first row of vertices and normals (uvs will differ) - - generateSegment( ( closed === false ) ? tubularSegments : 0 ); - - // uvs are generated in a separate function. - // this makes it easy compute correct values for closed geometries - - generateUVs(); - - // finally create faces - - generateIndices(); - - } - - function generateSegment( i ) { - - // we use getPointAt to sample evenly distributed points from the given path - - P = path.getPointAt( i / tubularSegments, P ); - - // retrieve corresponding normal and binormal - - const N = frames.normals[ i ]; - const B = frames.binormals[ i ]; - - // generate normals and vertices for the current segment - - for ( let j = 0; j <= radialSegments; j ++ ) { - - const v = j / radialSegments * Math.PI * 2; - - const sin = Math.sin( v ); - const cos = - Math.cos( v ); - - // normal - - normal.x = ( cos * N.x + sin * B.x ); - normal.y = ( cos * N.y + sin * B.y ); - normal.z = ( cos * N.z + sin * B.z ); - normal.normalize(); - - normals.push( normal.x, normal.y, normal.z ); - - // vertex - - vertex.x = P.x + radius * normal.x; - vertex.y = P.y + radius * normal.y; - vertex.z = P.z + radius * normal.z; - - vertices.push( vertex.x, vertex.y, vertex.z ); - - } - - } - - function generateIndices() { - - for ( let j = 1; j <= tubularSegments; j ++ ) { - - for ( let i = 1; i <= radialSegments; i ++ ) { - - const a = ( radialSegments + 1 ) * ( j - 1 ) + ( i - 1 ); - const b = ( radialSegments + 1 ) * j + ( i - 1 ); - const c = ( radialSegments + 1 ) * j + i; - const d = ( radialSegments + 1 ) * ( j - 1 ) + i; - - // faces - - indices.push( a, b, d ); - indices.push( b, c, d ); - - } - - } - - } - - function generateUVs() { - - for ( let i = 0; i <= tubularSegments; i ++ ) { - - for ( let j = 0; j <= radialSegments; j ++ ) { - - uv.x = i / tubularSegments; - uv.y = j / radialSegments; - - uvs.push( uv.x, uv.y ); - - } - - } - - } - - } - - copy( source ) { - - super.copy( source ); - - this.parameters = Object.assign( {}, source.parameters ); - - return this; - - } - - toJSON() { - - const data = super.toJSON(); - - data.path = this.parameters.path.toJSON(); - - return data; - - } - - static fromJSON( data ) { - - // This only works for built-in curves (e.g. CatmullRomCurve3). - // User defined curves or instances of CurvePath will not be deserialized. - return new TubeGeometry( - new Curves[ data.path.type ]().fromJSON( data.path ), - data.tubularSegments, - data.radius, - data.radialSegments, - data.closed - ); - - } - - } - - class WireframeGeometry extends BufferGeometry { - - constructor( geometry = null ) { - - super(); - - this.type = 'WireframeGeometry'; - - this.parameters = { - geometry: geometry - }; - - if ( geometry !== null ) { - - // buffer - - const vertices = []; - const edges = new Set(); - - // helper variables - - const start = new Vector3(); - const end = new Vector3(); - - if ( geometry.index !== null ) { - - // indexed BufferGeometry - - const position = geometry.attributes.position; - const indices = geometry.index; - let groups = geometry.groups; - - if ( groups.length === 0 ) { - - groups = [ { start: 0, count: indices.count, materialIndex: 0 } ]; - - } - - // create a data structure that contains all edges without duplicates - - for ( let o = 0, ol = groups.length; o < ol; ++ o ) { - - const group = groups[ o ]; - - const groupStart = group.start; - const groupCount = group.count; - - for ( let i = groupStart, l = ( groupStart + groupCount ); i < l; i += 3 ) { - - for ( let j = 0; j < 3; j ++ ) { - - const index1 = indices.getX( i + j ); - const index2 = indices.getX( i + ( j + 1 ) % 3 ); - - start.fromBufferAttribute( position, index1 ); - end.fromBufferAttribute( position, index2 ); - - if ( isUniqueEdge( start, end, edges ) === true ) { - - vertices.push( start.x, start.y, start.z ); - vertices.push( end.x, end.y, end.z ); - - } - - } - - } - - } - - } else { - - // non-indexed BufferGeometry - - const position = geometry.attributes.position; - - for ( let i = 0, l = ( position.count / 3 ); i < l; i ++ ) { - - for ( let j = 0; j < 3; j ++ ) { - - // three edges per triangle, an edge is represented as (index1, index2) - // e.g. the first triangle has the following edges: (0,1),(1,2),(2,0) - - const index1 = 3 * i + j; - const index2 = 3 * i + ( ( j + 1 ) % 3 ); - - start.fromBufferAttribute( position, index1 ); - end.fromBufferAttribute( position, index2 ); - - if ( isUniqueEdge( start, end, edges ) === true ) { - - vertices.push( start.x, start.y, start.z ); - vertices.push( end.x, end.y, end.z ); - - } - - } - - } - - } - - // build geometry - - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - - } - - } - - copy( source ) { - - super.copy( source ); - - this.parameters = Object.assign( {}, source.parameters ); - - return this; - - } - - } - - function isUniqueEdge( start, end, edges ) { - - const hash1 = `${start.x},${start.y},${start.z}-${end.x},${end.y},${end.z}`; - const hash2 = `${end.x},${end.y},${end.z}-${start.x},${start.y},${start.z}`; // coincident edge - - if ( edges.has( hash1 ) === true || edges.has( hash2 ) === true ) { - - return false; - - } else { - - edges.add( hash1 ); - edges.add( hash2 ); - return true; - - } - - } - - var Geometries = /*#__PURE__*/Object.freeze({ - __proto__: null, - BoxGeometry: BoxGeometry, - CapsuleGeometry: CapsuleGeometry, - CircleGeometry: CircleGeometry, - ConeGeometry: ConeGeometry, - CylinderGeometry: CylinderGeometry, - DodecahedronGeometry: DodecahedronGeometry, - EdgesGeometry: EdgesGeometry, - ExtrudeGeometry: ExtrudeGeometry, - IcosahedronGeometry: IcosahedronGeometry, - LatheGeometry: LatheGeometry, - OctahedronGeometry: OctahedronGeometry, - PlaneGeometry: PlaneGeometry, - PolyhedronGeometry: PolyhedronGeometry, - RingGeometry: RingGeometry, - ShapeGeometry: ShapeGeometry, - SphereGeometry: SphereGeometry, - TetrahedronGeometry: TetrahedronGeometry, - TorusGeometry: TorusGeometry, - TorusKnotGeometry: TorusKnotGeometry, - TubeGeometry: TubeGeometry, - WireframeGeometry: WireframeGeometry - }); - - class ShadowMaterial extends Material { - - constructor( parameters ) { - - super(); - - this.isShadowMaterial = true; - - this.type = 'ShadowMaterial'; - - this.color = new Color( 0x000000 ); - this.transparent = true; - - this.fog = true; - - this.setValues( parameters ); - - } - - copy( source ) { - - super.copy( source ); - - this.color.copy( source.color ); - - this.fog = source.fog; - - return this; - - } - - } - - class RawShaderMaterial extends ShaderMaterial { - - constructor( parameters ) { - - super( parameters ); - - this.isRawShaderMaterial = true; - - this.type = 'RawShaderMaterial'; - - } - - } - - class MeshStandardMaterial extends Material { - - constructor( parameters ) { - - super(); - - this.isMeshStandardMaterial = true; - - this.defines = { 'STANDARD': '' }; - - this.type = 'MeshStandardMaterial'; - - this.color = new Color( 0xffffff ); // diffuse - this.roughness = 1.0; - this.metalness = 0.0; - - this.map = null; - - this.lightMap = null; - this.lightMapIntensity = 1.0; - - this.aoMap = null; - this.aoMapIntensity = 1.0; - - this.emissive = new Color( 0x000000 ); - this.emissiveIntensity = 1.0; - this.emissiveMap = null; - - this.bumpMap = null; - this.bumpScale = 1; - - this.normalMap = null; - this.normalMapType = TangentSpaceNormalMap; - this.normalScale = new Vector2( 1, 1 ); - - this.displacementMap = null; - this.displacementScale = 1; - this.displacementBias = 0; - - this.roughnessMap = null; - - this.metalnessMap = null; - - this.alphaMap = null; - - this.envMap = null; - this.envMapIntensity = 1.0; - - this.wireframe = false; - this.wireframeLinewidth = 1; - this.wireframeLinecap = 'round'; - this.wireframeLinejoin = 'round'; - - this.flatShading = false; - - this.fog = true; - - this.setValues( parameters ); - - } - - copy( source ) { - - super.copy( source ); - - this.defines = { 'STANDARD': '' }; - - this.color.copy( source.color ); - this.roughness = source.roughness; - this.metalness = source.metalness; - - this.map = source.map; - - this.lightMap = source.lightMap; - this.lightMapIntensity = source.lightMapIntensity; - - this.aoMap = source.aoMap; - this.aoMapIntensity = source.aoMapIntensity; - - this.emissive.copy( source.emissive ); - this.emissiveMap = source.emissiveMap; - this.emissiveIntensity = source.emissiveIntensity; - - this.bumpMap = source.bumpMap; - this.bumpScale = source.bumpScale; - - this.normalMap = source.normalMap; - this.normalMapType = source.normalMapType; - this.normalScale.copy( source.normalScale ); - - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; - - this.roughnessMap = source.roughnessMap; - - this.metalnessMap = source.metalnessMap; - - this.alphaMap = source.alphaMap; - - this.envMap = source.envMap; - this.envMapIntensity = source.envMapIntensity; - - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; - this.wireframeLinecap = source.wireframeLinecap; - this.wireframeLinejoin = source.wireframeLinejoin; - - this.flatShading = source.flatShading; - - this.fog = source.fog; - - return this; - - } - - } - - class MeshPhysicalMaterial extends MeshStandardMaterial { - - constructor( parameters ) { - - super(); - - this.isMeshPhysicalMaterial = true; - - this.defines = { - - 'STANDARD': '', - 'PHYSICAL': '' - - }; - - this.type = 'MeshPhysicalMaterial'; - - this.anisotropyRotation = 0; - this.anisotropyMap = null; - - this.clearcoatMap = null; - this.clearcoatRoughness = 0.0; - this.clearcoatRoughnessMap = null; - this.clearcoatNormalScale = new Vector2( 1, 1 ); - this.clearcoatNormalMap = null; - - this.ior = 1.5; - - Object.defineProperty( this, 'reflectivity', { - get: function () { - - return ( clamp( 2.5 * ( this.ior - 1 ) / ( this.ior + 1 ), 0, 1 ) ); - - }, - set: function ( reflectivity ) { - - this.ior = ( 1 + 0.4 * reflectivity ) / ( 1 - 0.4 * reflectivity ); - - } - } ); - - this.iridescenceMap = null; - this.iridescenceIOR = 1.3; - this.iridescenceThicknessRange = [ 100, 400 ]; - this.iridescenceThicknessMap = null; - - this.sheenColor = new Color( 0x000000 ); - this.sheenColorMap = null; - this.sheenRoughness = 1.0; - this.sheenRoughnessMap = null; - - this.transmissionMap = null; - - this.thickness = 0; - this.thicknessMap = null; - this.attenuationDistance = Infinity; - this.attenuationColor = new Color( 1, 1, 1 ); - - this.specularIntensity = 1.0; - this.specularIntensityMap = null; - this.specularColor = new Color( 1, 1, 1 ); - this.specularColorMap = null; - - this._anisotropy = 0; - this._clearcoat = 0; - this._iridescence = 0; - this._sheen = 0.0; - this._transmission = 0; - - this.setValues( parameters ); - - } - - get anisotropy() { - - return this._anisotropy; - - } - - set anisotropy( value ) { - - if ( this._anisotropy > 0 !== value > 0 ) { - - this.version ++; - - } - - this._anisotropy = value; - - } - - get clearcoat() { - - return this._clearcoat; - - } - - set clearcoat( value ) { - - if ( this._clearcoat > 0 !== value > 0 ) { - - this.version ++; - - } - - this._clearcoat = value; - - } - - get iridescence() { - - return this._iridescence; - - } - - set iridescence( value ) { - - if ( this._iridescence > 0 !== value > 0 ) { - - this.version ++; - - } - - this._iridescence = value; - - } - - get sheen() { - - return this._sheen; - - } - - set sheen( value ) { - - if ( this._sheen > 0 !== value > 0 ) { - - this.version ++; - - } - - this._sheen = value; - - } - - get transmission() { - - return this._transmission; - - } - - set transmission( value ) { - - if ( this._transmission > 0 !== value > 0 ) { - - this.version ++; - - } - - this._transmission = value; - - } - - copy( source ) { - - super.copy( source ); - - this.defines = { - - 'STANDARD': '', - 'PHYSICAL': '' - - }; - - this.anisotropy = source.anisotropy; - this.anisotropyRotation = source.anisotropyRotation; - this.anisotropyMap = source.anisotropyMap; - - this.clearcoat = source.clearcoat; - this.clearcoatMap = source.clearcoatMap; - this.clearcoatRoughness = source.clearcoatRoughness; - this.clearcoatRoughnessMap = source.clearcoatRoughnessMap; - this.clearcoatNormalMap = source.clearcoatNormalMap; - this.clearcoatNormalScale.copy( source.clearcoatNormalScale ); - - this.ior = source.ior; - - this.iridescence = source.iridescence; - this.iridescenceMap = source.iridescenceMap; - this.iridescenceIOR = source.iridescenceIOR; - this.iridescenceThicknessRange = [ ...source.iridescenceThicknessRange ]; - this.iridescenceThicknessMap = source.iridescenceThicknessMap; - - this.sheen = source.sheen; - this.sheenColor.copy( source.sheenColor ); - this.sheenColorMap = source.sheenColorMap; - this.sheenRoughness = source.sheenRoughness; - this.sheenRoughnessMap = source.sheenRoughnessMap; - - this.transmission = source.transmission; - this.transmissionMap = source.transmissionMap; - - this.thickness = source.thickness; - this.thicknessMap = source.thicknessMap; - this.attenuationDistance = source.attenuationDistance; - this.attenuationColor.copy( source.attenuationColor ); - - this.specularIntensity = source.specularIntensity; - this.specularIntensityMap = source.specularIntensityMap; - this.specularColor.copy( source.specularColor ); - this.specularColorMap = source.specularColorMap; - - return this; - - } - - } - - class MeshPhongMaterial extends Material { - - constructor( parameters ) { - - super(); - - this.isMeshPhongMaterial = true; - - this.type = 'MeshPhongMaterial'; - - this.color = new Color( 0xffffff ); // diffuse - this.specular = new Color( 0x111111 ); - this.shininess = 30; - - this.map = null; - - this.lightMap = null; - this.lightMapIntensity = 1.0; - - this.aoMap = null; - this.aoMapIntensity = 1.0; - - this.emissive = new Color( 0x000000 ); - this.emissiveIntensity = 1.0; - this.emissiveMap = null; - - this.bumpMap = null; - this.bumpScale = 1; - - this.normalMap = null; - this.normalMapType = TangentSpaceNormalMap; - this.normalScale = new Vector2( 1, 1 ); - - this.displacementMap = null; - this.displacementScale = 1; - this.displacementBias = 0; - - this.specularMap = null; - - this.alphaMap = null; - - this.envMap = null; - this.combine = MultiplyOperation; - this.reflectivity = 1; - this.refractionRatio = 0.98; - - this.wireframe = false; - this.wireframeLinewidth = 1; - this.wireframeLinecap = 'round'; - this.wireframeLinejoin = 'round'; - - this.flatShading = false; - - this.fog = true; - - this.setValues( parameters ); - - } - - copy( source ) { - - super.copy( source ); - - this.color.copy( source.color ); - this.specular.copy( source.specular ); - this.shininess = source.shininess; - - this.map = source.map; - - this.lightMap = source.lightMap; - this.lightMapIntensity = source.lightMapIntensity; - - this.aoMap = source.aoMap; - this.aoMapIntensity = source.aoMapIntensity; - - this.emissive.copy( source.emissive ); - this.emissiveMap = source.emissiveMap; - this.emissiveIntensity = source.emissiveIntensity; - - this.bumpMap = source.bumpMap; - this.bumpScale = source.bumpScale; - - this.normalMap = source.normalMap; - this.normalMapType = source.normalMapType; - this.normalScale.copy( source.normalScale ); - - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; - - this.specularMap = source.specularMap; - - this.alphaMap = source.alphaMap; - - this.envMap = source.envMap; - this.combine = source.combine; - this.reflectivity = source.reflectivity; - this.refractionRatio = source.refractionRatio; - - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; - this.wireframeLinecap = source.wireframeLinecap; - this.wireframeLinejoin = source.wireframeLinejoin; - - this.flatShading = source.flatShading; - - this.fog = source.fog; - - return this; - - } - - } - - class MeshToonMaterial extends Material { - - constructor( parameters ) { - - super(); - - this.isMeshToonMaterial = true; - - this.defines = { 'TOON': '' }; - - this.type = 'MeshToonMaterial'; - - this.color = new Color( 0xffffff ); - - this.map = null; - this.gradientMap = null; - - this.lightMap = null; - this.lightMapIntensity = 1.0; - - this.aoMap = null; - this.aoMapIntensity = 1.0; - - this.emissive = new Color( 0x000000 ); - this.emissiveIntensity = 1.0; - this.emissiveMap = null; - - this.bumpMap = null; - this.bumpScale = 1; - - this.normalMap = null; - this.normalMapType = TangentSpaceNormalMap; - this.normalScale = new Vector2( 1, 1 ); - - this.displacementMap = null; - this.displacementScale = 1; - this.displacementBias = 0; - - this.alphaMap = null; - - this.wireframe = false; - this.wireframeLinewidth = 1; - this.wireframeLinecap = 'round'; - this.wireframeLinejoin = 'round'; - - this.fog = true; - - this.setValues( parameters ); - - } - - copy( source ) { - - super.copy( source ); - - this.color.copy( source.color ); - - this.map = source.map; - this.gradientMap = source.gradientMap; - - this.lightMap = source.lightMap; - this.lightMapIntensity = source.lightMapIntensity; - - this.aoMap = source.aoMap; - this.aoMapIntensity = source.aoMapIntensity; - - this.emissive.copy( source.emissive ); - this.emissiveMap = source.emissiveMap; - this.emissiveIntensity = source.emissiveIntensity; - - this.bumpMap = source.bumpMap; - this.bumpScale = source.bumpScale; - - this.normalMap = source.normalMap; - this.normalMapType = source.normalMapType; - this.normalScale.copy( source.normalScale ); - - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; - - this.alphaMap = source.alphaMap; - - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; - this.wireframeLinecap = source.wireframeLinecap; - this.wireframeLinejoin = source.wireframeLinejoin; - - this.fog = source.fog; - - return this; - - } - - } - - class MeshNormalMaterial extends Material { - - constructor( parameters ) { - - super(); - - this.isMeshNormalMaterial = true; - - this.type = 'MeshNormalMaterial'; - - this.bumpMap = null; - this.bumpScale = 1; - - this.normalMap = null; - this.normalMapType = TangentSpaceNormalMap; - this.normalScale = new Vector2( 1, 1 ); - - this.displacementMap = null; - this.displacementScale = 1; - this.displacementBias = 0; - - this.wireframe = false; - this.wireframeLinewidth = 1; - - this.flatShading = false; - - this.setValues( parameters ); - - } - - copy( source ) { - - super.copy( source ); - - this.bumpMap = source.bumpMap; - this.bumpScale = source.bumpScale; - - this.normalMap = source.normalMap; - this.normalMapType = source.normalMapType; - this.normalScale.copy( source.normalScale ); - - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; - - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; - - this.flatShading = source.flatShading; - - return this; - - } - - } - - class MeshLambertMaterial extends Material { - - constructor( parameters ) { - - super(); - - this.isMeshLambertMaterial = true; - - this.type = 'MeshLambertMaterial'; - - this.color = new Color( 0xffffff ); // diffuse - - this.map = null; - - this.lightMap = null; - this.lightMapIntensity = 1.0; - - this.aoMap = null; - this.aoMapIntensity = 1.0; - - this.emissive = new Color( 0x000000 ); - this.emissiveIntensity = 1.0; - this.emissiveMap = null; - - this.bumpMap = null; - this.bumpScale = 1; - - this.normalMap = null; - this.normalMapType = TangentSpaceNormalMap; - this.normalScale = new Vector2( 1, 1 ); - - this.displacementMap = null; - this.displacementScale = 1; - this.displacementBias = 0; - - this.specularMap = null; - - this.alphaMap = null; - - this.envMap = null; - this.combine = MultiplyOperation; - this.reflectivity = 1; - this.refractionRatio = 0.98; - - this.wireframe = false; - this.wireframeLinewidth = 1; - this.wireframeLinecap = 'round'; - this.wireframeLinejoin = 'round'; - - this.flatShading = false; - - this.fog = true; - - this.setValues( parameters ); - - } - - copy( source ) { - - super.copy( source ); - - this.color.copy( source.color ); - - this.map = source.map; - - this.lightMap = source.lightMap; - this.lightMapIntensity = source.lightMapIntensity; - - this.aoMap = source.aoMap; - this.aoMapIntensity = source.aoMapIntensity; - - this.emissive.copy( source.emissive ); - this.emissiveMap = source.emissiveMap; - this.emissiveIntensity = source.emissiveIntensity; - - this.bumpMap = source.bumpMap; - this.bumpScale = source.bumpScale; - - this.normalMap = source.normalMap; - this.normalMapType = source.normalMapType; - this.normalScale.copy( source.normalScale ); - - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; - - this.specularMap = source.specularMap; - - this.alphaMap = source.alphaMap; - - this.envMap = source.envMap; - this.combine = source.combine; - this.reflectivity = source.reflectivity; - this.refractionRatio = source.refractionRatio; - - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; - this.wireframeLinecap = source.wireframeLinecap; - this.wireframeLinejoin = source.wireframeLinejoin; - - this.flatShading = source.flatShading; - - this.fog = source.fog; - - return this; - - } - - } - - class MeshMatcapMaterial extends Material { - - constructor( parameters ) { - - super(); - - this.isMeshMatcapMaterial = true; - - this.defines = { 'MATCAP': '' }; - - this.type = 'MeshMatcapMaterial'; - - this.color = new Color( 0xffffff ); // diffuse - - this.matcap = null; - - this.map = null; - - this.bumpMap = null; - this.bumpScale = 1; - - this.normalMap = null; - this.normalMapType = TangentSpaceNormalMap; - this.normalScale = new Vector2( 1, 1 ); - - this.displacementMap = null; - this.displacementScale = 1; - this.displacementBias = 0; - - this.alphaMap = null; - - this.flatShading = false; - - this.fog = true; - - this.setValues( parameters ); - - } - - - copy( source ) { - - super.copy( source ); - - this.defines = { 'MATCAP': '' }; - - this.color.copy( source.color ); - - this.matcap = source.matcap; - - this.map = source.map; - - this.bumpMap = source.bumpMap; - this.bumpScale = source.bumpScale; - - this.normalMap = source.normalMap; - this.normalMapType = source.normalMapType; - this.normalScale.copy( source.normalScale ); - - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; - - this.alphaMap = source.alphaMap; - - this.flatShading = source.flatShading; - - this.fog = source.fog; - - return this; - - } - - } - - class LineDashedMaterial extends LineBasicMaterial { - - constructor( parameters ) { - - super(); - - this.isLineDashedMaterial = true; - - this.type = 'LineDashedMaterial'; - - this.scale = 1; - this.dashSize = 3; - this.gapSize = 1; - - this.setValues( parameters ); - - } - - copy( source ) { - - super.copy( source ); - - this.scale = source.scale; - this.dashSize = source.dashSize; - this.gapSize = source.gapSize; - - return this; - - } - - } - - // same as Array.prototype.slice, but also works on typed arrays - function arraySlice( array, from, to ) { - - if ( isTypedArray( array ) ) { - - // in ios9 array.subarray(from, undefined) will return empty array - // but array.subarray(from) or array.subarray(from, len) is correct - return new array.constructor( array.subarray( from, to !== undefined ? to : array.length ) ); - - } - - return array.slice( from, to ); - - } - - // converts an array to a specific type - function convertArray( array, type, forceClone ) { - - if ( ! array || // let 'undefined' and 'null' pass - ! forceClone && array.constructor === type ) return array; - - if ( typeof type.BYTES_PER_ELEMENT === 'number' ) { - - return new type( array ); // create typed array - - } - - return Array.prototype.slice.call( array ); // create Array - - } - - function isTypedArray( object ) { - - return ArrayBuffer.isView( object ) && - ! ( object instanceof DataView ); - - } - - // returns an array by which times and values can be sorted - function getKeyframeOrder( times ) { - - function compareTime( i, j ) { - - return times[ i ] - times[ j ]; - - } - - const n = times.length; - const result = new Array( n ); - for ( let i = 0; i !== n; ++ i ) result[ i ] = i; - - result.sort( compareTime ); - - return result; - - } - - // uses the array previously returned by 'getKeyframeOrder' to sort data - function sortedArray( values, stride, order ) { - - const nValues = values.length; - const result = new values.constructor( nValues ); - - for ( let i = 0, dstOffset = 0; dstOffset !== nValues; ++ i ) { - - const srcOffset = order[ i ] * stride; - - for ( let j = 0; j !== stride; ++ j ) { - - result[ dstOffset ++ ] = values[ srcOffset + j ]; - - } - - } - - return result; - - } - - // function for parsing AOS keyframe formats - function flattenJSON( jsonKeys, times, values, valuePropertyName ) { - - let i = 1, key = jsonKeys[ 0 ]; - - while ( key !== undefined && key[ valuePropertyName ] === undefined ) { - - key = jsonKeys[ i ++ ]; - - } - - if ( key === undefined ) return; // no data - - let value = key[ valuePropertyName ]; - if ( value === undefined ) return; // no data - - if ( Array.isArray( value ) ) { - - do { - - value = key[ valuePropertyName ]; - - if ( value !== undefined ) { - - times.push( key.time ); - values.push.apply( values, value ); // push all elements - - } - - key = jsonKeys[ i ++ ]; - - } while ( key !== undefined ); - - } else if ( value.toArray !== undefined ) { - - // ...assume THREE.Math-ish - - do { - - value = key[ valuePropertyName ]; - - if ( value !== undefined ) { - - times.push( key.time ); - value.toArray( values, values.length ); - - } - - key = jsonKeys[ i ++ ]; - - } while ( key !== undefined ); - - } else { - - // otherwise push as-is - - do { - - value = key[ valuePropertyName ]; - - if ( value !== undefined ) { - - times.push( key.time ); - values.push( value ); - - } - - key = jsonKeys[ i ++ ]; - - } while ( key !== undefined ); - - } - - } - - function subclip( sourceClip, name, startFrame, endFrame, fps = 30 ) { - - const clip = sourceClip.clone(); - - clip.name = name; - - const tracks = []; - - for ( let i = 0; i < clip.tracks.length; ++ i ) { - - const track = clip.tracks[ i ]; - const valueSize = track.getValueSize(); - - const times = []; - const values = []; - - for ( let j = 0; j < track.times.length; ++ j ) { - - const frame = track.times[ j ] * fps; - - if ( frame < startFrame || frame >= endFrame ) continue; - - times.push( track.times[ j ] ); - - for ( let k = 0; k < valueSize; ++ k ) { - - values.push( track.values[ j * valueSize + k ] ); - - } - - } - - if ( times.length === 0 ) continue; - - track.times = convertArray( times, track.times.constructor ); - track.values = convertArray( values, track.values.constructor ); - - tracks.push( track ); - - } - - clip.tracks = tracks; - - // find minimum .times value across all tracks in the trimmed clip - - let minStartTime = Infinity; - - for ( let i = 0; i < clip.tracks.length; ++ i ) { - - if ( minStartTime > clip.tracks[ i ].times[ 0 ] ) { - - minStartTime = clip.tracks[ i ].times[ 0 ]; - - } - - } - - // shift all tracks such that clip begins at t=0 - - for ( let i = 0; i < clip.tracks.length; ++ i ) { - - clip.tracks[ i ].shift( - 1 * minStartTime ); - - } - - clip.resetDuration(); - - return clip; - - } - - function makeClipAdditive( targetClip, referenceFrame = 0, referenceClip = targetClip, fps = 30 ) { - - if ( fps <= 0 ) fps = 30; - - const numTracks = referenceClip.tracks.length; - const referenceTime = referenceFrame / fps; - - // Make each track's values relative to the values at the reference frame - for ( let i = 0; i < numTracks; ++ i ) { - - const referenceTrack = referenceClip.tracks[ i ]; - const referenceTrackType = referenceTrack.ValueTypeName; - - // Skip this track if it's non-numeric - if ( referenceTrackType === 'bool' || referenceTrackType === 'string' ) continue; - - // Find the track in the target clip whose name and type matches the reference track - const targetTrack = targetClip.tracks.find( function ( track ) { - - return track.name === referenceTrack.name - && track.ValueTypeName === referenceTrackType; - - } ); - - if ( targetTrack === undefined ) continue; - - let referenceOffset = 0; - const referenceValueSize = referenceTrack.getValueSize(); - - if ( referenceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) { - - referenceOffset = referenceValueSize / 3; - - } - - let targetOffset = 0; - const targetValueSize = targetTrack.getValueSize(); - - if ( targetTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) { - - targetOffset = targetValueSize / 3; - - } - - const lastIndex = referenceTrack.times.length - 1; - let referenceValue; - - // Find the value to subtract out of the track - if ( referenceTime <= referenceTrack.times[ 0 ] ) { - - // Reference frame is earlier than the first keyframe, so just use the first keyframe - const startIndex = referenceOffset; - const endIndex = referenceValueSize - referenceOffset; - referenceValue = arraySlice( referenceTrack.values, startIndex, endIndex ); - - } else if ( referenceTime >= referenceTrack.times[ lastIndex ] ) { - - // Reference frame is after the last keyframe, so just use the last keyframe - const startIndex = lastIndex * referenceValueSize + referenceOffset; - const endIndex = startIndex + referenceValueSize - referenceOffset; - referenceValue = arraySlice( referenceTrack.values, startIndex, endIndex ); - - } else { - - // Interpolate to the reference value - const interpolant = referenceTrack.createInterpolant(); - const startIndex = referenceOffset; - const endIndex = referenceValueSize - referenceOffset; - interpolant.evaluate( referenceTime ); - referenceValue = arraySlice( interpolant.resultBuffer, startIndex, endIndex ); - - } - - // Conjugate the quaternion - if ( referenceTrackType === 'quaternion' ) { - - const referenceQuat = new Quaternion().fromArray( referenceValue ).normalize().conjugate(); - referenceQuat.toArray( referenceValue ); - - } - - // Subtract the reference value from all of the track values - - const numTimes = targetTrack.times.length; - for ( let j = 0; j < numTimes; ++ j ) { - - const valueStart = j * targetValueSize + targetOffset; - - if ( referenceTrackType === 'quaternion' ) { - - // Multiply the conjugate for quaternion track types - Quaternion.multiplyQuaternionsFlat( - targetTrack.values, - valueStart, - referenceValue, - 0, - targetTrack.values, - valueStart - ); - - } else { - - const valueEnd = targetValueSize - targetOffset * 2; - - // Subtract each value for all other numeric track types - for ( let k = 0; k < valueEnd; ++ k ) { - - targetTrack.values[ valueStart + k ] -= referenceValue[ k ]; - - } - - } - - } - - } - - targetClip.blendMode = AdditiveAnimationBlendMode; - - return targetClip; - - } - - const AnimationUtils = { - arraySlice: arraySlice, - convertArray: convertArray, - isTypedArray: isTypedArray, - getKeyframeOrder: getKeyframeOrder, - sortedArray: sortedArray, - flattenJSON: flattenJSON, - subclip: subclip, - makeClipAdditive: makeClipAdditive - }; - - /** - * Abstract base class of interpolants over parametric samples. - * - * The parameter domain is one dimensional, typically the time or a path - * along a curve defined by the data. - * - * The sample values can have any dimensionality and derived classes may - * apply special interpretations to the data. - * - * This class provides the interval seek in a Template Method, deferring - * the actual interpolation to derived classes. - * - * Time complexity is O(1) for linear access crossing at most two points - * and O(log N) for random access, where N is the number of positions. - * - * References: - * - * http://www.oodesign.com/template-method-pattern.html - * - */ - - class Interpolant { - - constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { - - this.parameterPositions = parameterPositions; - this._cachedIndex = 0; - - this.resultBuffer = resultBuffer !== undefined ? - resultBuffer : new sampleValues.constructor( sampleSize ); - this.sampleValues = sampleValues; - this.valueSize = sampleSize; - - this.settings = null; - this.DefaultSettings_ = {}; - - } - - evaluate( t ) { - - const pp = this.parameterPositions; - let i1 = this._cachedIndex, - t1 = pp[ i1 ], - t0 = pp[ i1 - 1 ]; - - validate_interval: { - - seek: { - - let right; - - linear_scan: { - - //- See http://jsperf.com/comparison-to-undefined/3 - //- slower code: - //- - //- if ( t >= t1 || t1 === undefined ) { - forward_scan: if ( ! ( t < t1 ) ) { - - for ( let giveUpAt = i1 + 2; ; ) { - - if ( t1 === undefined ) { - - if ( t < t0 ) break forward_scan; - - // after end - - i1 = pp.length; - this._cachedIndex = i1; - return this.copySampleValue_( i1 - 1 ); - - } - - if ( i1 === giveUpAt ) break; // this loop - - t0 = t1; - t1 = pp[ ++ i1 ]; - - if ( t < t1 ) { - - // we have arrived at the sought interval - break seek; - - } - - } - - // prepare binary search on the right side of the index - right = pp.length; - break linear_scan; - - } - - //- slower code: - //- if ( t < t0 || t0 === undefined ) { - if ( ! ( t >= t0 ) ) { - - // looping? - - const t1global = pp[ 1 ]; - - if ( t < t1global ) { - - i1 = 2; // + 1, using the scan for the details - t0 = t1global; - - } - - // linear reverse scan - - for ( let giveUpAt = i1 - 2; ; ) { - - if ( t0 === undefined ) { - - // before start - - this._cachedIndex = 0; - return this.copySampleValue_( 0 ); - - } - - if ( i1 === giveUpAt ) break; // this loop - - t1 = t0; - t0 = pp[ -- i1 - 1 ]; - - if ( t >= t0 ) { - - // we have arrived at the sought interval - break seek; - - } - - } - - // prepare binary search on the left side of the index - right = i1; - i1 = 0; - break linear_scan; - - } - - // the interval is valid - - break validate_interval; - - } // linear scan - - // binary search - - while ( i1 < right ) { - - const mid = ( i1 + right ) >>> 1; - - if ( t < pp[ mid ] ) { - - right = mid; - - } else { - - i1 = mid + 1; - - } - - } - - t1 = pp[ i1 ]; - t0 = pp[ i1 - 1 ]; - - // check boundary cases, again - - if ( t0 === undefined ) { - - this._cachedIndex = 0; - return this.copySampleValue_( 0 ); - - } - - if ( t1 === undefined ) { - - i1 = pp.length; - this._cachedIndex = i1; - return this.copySampleValue_( i1 - 1 ); - - } - - } // seek - - this._cachedIndex = i1; - - this.intervalChanged_( i1, t0, t1 ); - - } // validate_interval - - return this.interpolate_( i1, t0, t, t1 ); - - } - - getSettings_() { - - return this.settings || this.DefaultSettings_; - - } - - copySampleValue_( index ) { - - // copies a sample value to the result buffer - - const result = this.resultBuffer, - values = this.sampleValues, - stride = this.valueSize, - offset = index * stride; - - for ( let i = 0; i !== stride; ++ i ) { - - result[ i ] = values[ offset + i ]; - - } - - return result; - - } - - // Template methods for derived classes: - - interpolate_( /* i1, t0, t, t1 */ ) { - - throw new Error( 'call to abstract method' ); - // implementations shall return this.resultBuffer - - } - - intervalChanged_( /* i1, t0, t1 */ ) { - - // empty - - } - - } - - /** - * Fast and simple cubic spline interpolant. - * - * It was derived from a Hermitian construction setting the first derivative - * at each sample position to the linear slope between neighboring positions - * over their parameter interval. - */ - - class CubicInterpolant extends Interpolant { - - constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { - - super( parameterPositions, sampleValues, sampleSize, resultBuffer ); - - this._weightPrev = - 0; - this._offsetPrev = - 0; - this._weightNext = - 0; - this._offsetNext = - 0; - - this.DefaultSettings_ = { - - endingStart: ZeroCurvatureEnding, - endingEnd: ZeroCurvatureEnding - - }; - - } - - intervalChanged_( i1, t0, t1 ) { - - const pp = this.parameterPositions; - let iPrev = i1 - 2, - iNext = i1 + 1, - - tPrev = pp[ iPrev ], - tNext = pp[ iNext ]; - - if ( tPrev === undefined ) { - - switch ( this.getSettings_().endingStart ) { - - case ZeroSlopeEnding: - - // f'(t0) = 0 - iPrev = i1; - tPrev = 2 * t0 - t1; - - break; - - case WrapAroundEnding: - - // use the other end of the curve - iPrev = pp.length - 2; - tPrev = t0 + pp[ iPrev ] - pp[ iPrev + 1 ]; - - break; - - default: // ZeroCurvatureEnding - - // f''(t0) = 0 a.k.a. Natural Spline - iPrev = i1; - tPrev = t1; - - } - - } - - if ( tNext === undefined ) { - - switch ( this.getSettings_().endingEnd ) { - - case ZeroSlopeEnding: - - // f'(tN) = 0 - iNext = i1; - tNext = 2 * t1 - t0; - - break; - - case WrapAroundEnding: - - // use the other end of the curve - iNext = 1; - tNext = t1 + pp[ 1 ] - pp[ 0 ]; - - break; - - default: // ZeroCurvatureEnding - - // f''(tN) = 0, a.k.a. Natural Spline - iNext = i1 - 1; - tNext = t0; - - } - - } - - const halfDt = ( t1 - t0 ) * 0.5, - stride = this.valueSize; - - this._weightPrev = halfDt / ( t0 - tPrev ); - this._weightNext = halfDt / ( tNext - t1 ); - this._offsetPrev = iPrev * stride; - this._offsetNext = iNext * stride; - - } - - interpolate_( i1, t0, t, t1 ) { - - const result = this.resultBuffer, - values = this.sampleValues, - stride = this.valueSize, - - o1 = i1 * stride, o0 = o1 - stride, - oP = this._offsetPrev, oN = this._offsetNext, - wP = this._weightPrev, wN = this._weightNext, - - p = ( t - t0 ) / ( t1 - t0 ), - pp = p * p, - ppp = pp * p; - - // evaluate polynomials - - const sP = - wP * ppp + 2 * wP * pp - wP * p; - const s0 = ( 1 + wP ) * ppp + ( - 1.5 - 2 * wP ) * pp + ( - 0.5 + wP ) * p + 1; - const s1 = ( - 1 - wN ) * ppp + ( 1.5 + wN ) * pp + 0.5 * p; - const sN = wN * ppp - wN * pp; - - // combine data linearly - - for ( let i = 0; i !== stride; ++ i ) { - - result[ i ] = - sP * values[ oP + i ] + - s0 * values[ o0 + i ] + - s1 * values[ o1 + i ] + - sN * values[ oN + i ]; - - } - - return result; - - } - - } - - class LinearInterpolant extends Interpolant { - - constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { - - super( parameterPositions, sampleValues, sampleSize, resultBuffer ); - - } - - interpolate_( i1, t0, t, t1 ) { - - const result = this.resultBuffer, - values = this.sampleValues, - stride = this.valueSize, - - offset1 = i1 * stride, - offset0 = offset1 - stride, - - weight1 = ( t - t0 ) / ( t1 - t0 ), - weight0 = 1 - weight1; - - for ( let i = 0; i !== stride; ++ i ) { - - result[ i ] = - values[ offset0 + i ] * weight0 + - values[ offset1 + i ] * weight1; - - } - - return result; - - } - - } - - /** - * - * Interpolant that evaluates to the sample value at the position preceding - * the parameter. - */ - - class DiscreteInterpolant extends Interpolant { - - constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { - - super( parameterPositions, sampleValues, sampleSize, resultBuffer ); - - } - - interpolate_( i1 /*, t0, t, t1 */ ) { - - return this.copySampleValue_( i1 - 1 ); - - } - - } - - class KeyframeTrack { - - constructor( name, times, values, interpolation ) { - - if ( name === undefined ) throw new Error( 'THREE.KeyframeTrack: track name is undefined' ); - if ( times === undefined || times.length === 0 ) throw new Error( 'THREE.KeyframeTrack: no keyframes in track named ' + name ); - - this.name = name; - - this.times = convertArray( times, this.TimeBufferType ); - this.values = convertArray( values, this.ValueBufferType ); - - this.setInterpolation( interpolation || this.DefaultInterpolation ); - - } - - // Serialization (in static context, because of constructor invocation - // and automatic invocation of .toJSON): - - static toJSON( track ) { - - const trackType = track.constructor; - - let json; - - // derived classes can define a static toJSON method - if ( trackType.toJSON !== this.toJSON ) { - - json = trackType.toJSON( track ); - - } else { - - // by default, we assume the data can be serialized as-is - json = { - - 'name': track.name, - 'times': convertArray( track.times, Array ), - 'values': convertArray( track.values, Array ) - - }; - - const interpolation = track.getInterpolation(); - - if ( interpolation !== track.DefaultInterpolation ) { - - json.interpolation = interpolation; - - } - - } - - json.type = track.ValueTypeName; // mandatory - - return json; - - } - - InterpolantFactoryMethodDiscrete( result ) { - - return new DiscreteInterpolant( this.times, this.values, this.getValueSize(), result ); - - } - - InterpolantFactoryMethodLinear( result ) { - - return new LinearInterpolant( this.times, this.values, this.getValueSize(), result ); - - } - - InterpolantFactoryMethodSmooth( result ) { - - return new CubicInterpolant( this.times, this.values, this.getValueSize(), result ); - - } - - setInterpolation( interpolation ) { - - let factoryMethod; - - switch ( interpolation ) { - - case InterpolateDiscrete: - - factoryMethod = this.InterpolantFactoryMethodDiscrete; - - break; - - case InterpolateLinear: - - factoryMethod = this.InterpolantFactoryMethodLinear; - - break; - - case InterpolateSmooth: - - factoryMethod = this.InterpolantFactoryMethodSmooth; - - break; - - } - - if ( factoryMethod === undefined ) { - - const message = 'unsupported interpolation for ' + - this.ValueTypeName + ' keyframe track named ' + this.name; - - if ( this.createInterpolant === undefined ) { - - // fall back to default, unless the default itself is messed up - if ( interpolation !== this.DefaultInterpolation ) { - - this.setInterpolation( this.DefaultInterpolation ); - - } else { - - throw new Error( message ); // fatal, in this case - - } - - } - - console.warn( 'THREE.KeyframeTrack:', message ); - return this; - - } - - this.createInterpolant = factoryMethod; - - return this; - - } - - getInterpolation() { - - switch ( this.createInterpolant ) { - - case this.InterpolantFactoryMethodDiscrete: - - return InterpolateDiscrete; - - case this.InterpolantFactoryMethodLinear: - - return InterpolateLinear; - - case this.InterpolantFactoryMethodSmooth: - - return InterpolateSmooth; - - } - - } - - getValueSize() { - - return this.values.length / this.times.length; - - } - - // move all keyframes either forwards or backwards in time - shift( timeOffset ) { - - if ( timeOffset !== 0.0 ) { - - const times = this.times; - - for ( let i = 0, n = times.length; i !== n; ++ i ) { - - times[ i ] += timeOffset; - - } - - } - - return this; - - } - - // scale all keyframe times by a factor (useful for frame <-> seconds conversions) - scale( timeScale ) { - - if ( timeScale !== 1.0 ) { - - const times = this.times; - - for ( let i = 0, n = times.length; i !== n; ++ i ) { - - times[ i ] *= timeScale; - - } - - } - - return this; - - } - - // removes keyframes before and after animation without changing any values within the range [startTime, endTime]. - // IMPORTANT: We do not shift around keys to the start of the track time, because for interpolated keys this will change their values - trim( startTime, endTime ) { - - const times = this.times, - nKeys = times.length; - - let from = 0, - to = nKeys - 1; - - while ( from !== nKeys && times[ from ] < startTime ) { - - ++ from; - - } - - while ( to !== - 1 && times[ to ] > endTime ) { - - -- to; - - } - - ++ to; // inclusive -> exclusive bound - - if ( from !== 0 || to !== nKeys ) { - - // empty tracks are forbidden, so keep at least one keyframe - if ( from >= to ) { - - to = Math.max( to, 1 ); - from = to - 1; - - } - - const stride = this.getValueSize(); - this.times = arraySlice( times, from, to ); - this.values = arraySlice( this.values, from * stride, to * stride ); - - } - - return this; - - } - - // ensure we do not get a GarbageInGarbageOut situation, make sure tracks are at least minimally viable - validate() { - - let valid = true; - - const valueSize = this.getValueSize(); - if ( valueSize - Math.floor( valueSize ) !== 0 ) { - - console.error( 'THREE.KeyframeTrack: Invalid value size in track.', this ); - valid = false; - - } - - const times = this.times, - values = this.values, - - nKeys = times.length; - - if ( nKeys === 0 ) { - - console.error( 'THREE.KeyframeTrack: Track is empty.', this ); - valid = false; - - } - - let prevTime = null; - - for ( let i = 0; i !== nKeys; i ++ ) { - - const currTime = times[ i ]; - - if ( typeof currTime === 'number' && isNaN( currTime ) ) { - - console.error( 'THREE.KeyframeTrack: Time is not a valid number.', this, i, currTime ); - valid = false; - break; - - } - - if ( prevTime !== null && prevTime > currTime ) { - - console.error( 'THREE.KeyframeTrack: Out of order keys.', this, i, currTime, prevTime ); - valid = false; - break; - - } - - prevTime = currTime; - - } - - if ( values !== undefined ) { - - if ( isTypedArray( values ) ) { - - for ( let i = 0, n = values.length; i !== n; ++ i ) { - - const value = values[ i ]; - - if ( isNaN( value ) ) { - - console.error( 'THREE.KeyframeTrack: Value is not a valid number.', this, i, value ); - valid = false; - break; - - } - - } - - } - - } - - return valid; - - } - - // removes equivalent sequential keys as common in morph target sequences - // (0,0,0,0,1,1,1,0,0,0,0,0,0,0) --> (0,0,1,1,0,0) - optimize() { - - // times or values may be shared with other tracks, so overwriting is unsafe - const times = arraySlice( this.times ), - values = arraySlice( this.values ), - stride = this.getValueSize(), - - smoothInterpolation = this.getInterpolation() === InterpolateSmooth, - - lastIndex = times.length - 1; - - let writeIndex = 1; - - for ( let i = 1; i < lastIndex; ++ i ) { - - let keep = false; - - const time = times[ i ]; - const timeNext = times[ i + 1 ]; - - // remove adjacent keyframes scheduled at the same time - - if ( time !== timeNext && ( i !== 1 || time !== times[ 0 ] ) ) { - - if ( ! smoothInterpolation ) { - - // remove unnecessary keyframes same as their neighbors - - const offset = i * stride, - offsetP = offset - stride, - offsetN = offset + stride; - - for ( let j = 0; j !== stride; ++ j ) { - - const value = values[ offset + j ]; - - if ( value !== values[ offsetP + j ] || - value !== values[ offsetN + j ] ) { - - keep = true; - break; - - } - - } - - } else { - - keep = true; - - } - - } - - // in-place compaction - - if ( keep ) { - - if ( i !== writeIndex ) { - - times[ writeIndex ] = times[ i ]; - - const readOffset = i * stride, - writeOffset = writeIndex * stride; - - for ( let j = 0; j !== stride; ++ j ) { - - values[ writeOffset + j ] = values[ readOffset + j ]; - - } - - } - - ++ writeIndex; - - } - - } - - // flush last keyframe (compaction looks ahead) - - if ( lastIndex > 0 ) { - - times[ writeIndex ] = times[ lastIndex ]; - - for ( let readOffset = lastIndex * stride, writeOffset = writeIndex * stride, j = 0; j !== stride; ++ j ) { - - values[ writeOffset + j ] = values[ readOffset + j ]; - - } - - ++ writeIndex; - - } - - if ( writeIndex !== times.length ) { - - this.times = arraySlice( times, 0, writeIndex ); - this.values = arraySlice( values, 0, writeIndex * stride ); - - } else { - - this.times = times; - this.values = values; - - } - - return this; - - } - - clone() { - - const times = arraySlice( this.times, 0 ); - const values = arraySlice( this.values, 0 ); - - const TypedKeyframeTrack = this.constructor; - const track = new TypedKeyframeTrack( this.name, times, values ); - - // Interpolant argument to constructor is not saved, so copy the factory method directly. - track.createInterpolant = this.createInterpolant; - - return track; - - } - - } - - KeyframeTrack.prototype.TimeBufferType = Float32Array; - KeyframeTrack.prototype.ValueBufferType = Float32Array; - KeyframeTrack.prototype.DefaultInterpolation = InterpolateLinear; - - /** - * A Track of Boolean keyframe values. - */ - class BooleanKeyframeTrack extends KeyframeTrack {} - - BooleanKeyframeTrack.prototype.ValueTypeName = 'bool'; - BooleanKeyframeTrack.prototype.ValueBufferType = Array; - BooleanKeyframeTrack.prototype.DefaultInterpolation = InterpolateDiscrete; - BooleanKeyframeTrack.prototype.InterpolantFactoryMethodLinear = undefined; - BooleanKeyframeTrack.prototype.InterpolantFactoryMethodSmooth = undefined; - - /** - * A Track of keyframe values that represent color. - */ - class ColorKeyframeTrack extends KeyframeTrack {} - - ColorKeyframeTrack.prototype.ValueTypeName = 'color'; - - /** - * A Track of numeric keyframe values. - */ - class NumberKeyframeTrack extends KeyframeTrack {} - - NumberKeyframeTrack.prototype.ValueTypeName = 'number'; - - /** - * Spherical linear unit quaternion interpolant. - */ - - class QuaternionLinearInterpolant extends Interpolant { - - constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { - - super( parameterPositions, sampleValues, sampleSize, resultBuffer ); - - } - - interpolate_( i1, t0, t, t1 ) { - - const result = this.resultBuffer, - values = this.sampleValues, - stride = this.valueSize, - - alpha = ( t - t0 ) / ( t1 - t0 ); - - let offset = i1 * stride; - - for ( let end = offset + stride; offset !== end; offset += 4 ) { - - Quaternion.slerpFlat( result, 0, values, offset - stride, values, offset, alpha ); - - } - - return result; - - } - - } - - /** - * A Track of quaternion keyframe values. - */ - class QuaternionKeyframeTrack extends KeyframeTrack { - - InterpolantFactoryMethodLinear( result ) { - - return new QuaternionLinearInterpolant( this.times, this.values, this.getValueSize(), result ); - - } - - } - - QuaternionKeyframeTrack.prototype.ValueTypeName = 'quaternion'; - // ValueBufferType is inherited - QuaternionKeyframeTrack.prototype.DefaultInterpolation = InterpolateLinear; - QuaternionKeyframeTrack.prototype.InterpolantFactoryMethodSmooth = undefined; - - /** - * A Track that interpolates Strings - */ - class StringKeyframeTrack extends KeyframeTrack {} - - StringKeyframeTrack.prototype.ValueTypeName = 'string'; - StringKeyframeTrack.prototype.ValueBufferType = Array; - StringKeyframeTrack.prototype.DefaultInterpolation = InterpolateDiscrete; - StringKeyframeTrack.prototype.InterpolantFactoryMethodLinear = undefined; - StringKeyframeTrack.prototype.InterpolantFactoryMethodSmooth = undefined; - - /** - * A Track of vectored keyframe values. - */ - class VectorKeyframeTrack extends KeyframeTrack {} - - VectorKeyframeTrack.prototype.ValueTypeName = 'vector'; - - class AnimationClip { - - constructor( name, duration = - 1, tracks, blendMode = NormalAnimationBlendMode ) { - - this.name = name; - this.tracks = tracks; - this.duration = duration; - this.blendMode = blendMode; - - this.uuid = generateUUID(); - - // this means it should figure out its duration by scanning the tracks - if ( this.duration < 0 ) { - - this.resetDuration(); - - } - - } - - - static parse( json ) { - - const tracks = [], - jsonTracks = json.tracks, - frameTime = 1.0 / ( json.fps || 1.0 ); - - for ( let i = 0, n = jsonTracks.length; i !== n; ++ i ) { - - tracks.push( parseKeyframeTrack( jsonTracks[ i ] ).scale( frameTime ) ); - - } - - const clip = new this( json.name, json.duration, tracks, json.blendMode ); - clip.uuid = json.uuid; - - return clip; - - } - - static toJSON( clip ) { - - const tracks = [], - clipTracks = clip.tracks; - - const json = { - - 'name': clip.name, - 'duration': clip.duration, - 'tracks': tracks, - 'uuid': clip.uuid, - 'blendMode': clip.blendMode - - }; - - for ( let i = 0, n = clipTracks.length; i !== n; ++ i ) { - - tracks.push( KeyframeTrack.toJSON( clipTracks[ i ] ) ); - - } - - return json; - - } - - static CreateFromMorphTargetSequence( name, morphTargetSequence, fps, noLoop ) { - - const numMorphTargets = morphTargetSequence.length; - const tracks = []; - - for ( let i = 0; i < numMorphTargets; i ++ ) { - - let times = []; - let values = []; - - times.push( - ( i + numMorphTargets - 1 ) % numMorphTargets, - i, - ( i + 1 ) % numMorphTargets ); - - values.push( 0, 1, 0 ); - - const order = getKeyframeOrder( times ); - times = sortedArray( times, 1, order ); - values = sortedArray( values, 1, order ); - - // if there is a key at the first frame, duplicate it as the - // last frame as well for perfect loop. - if ( ! noLoop && times[ 0 ] === 0 ) { - - times.push( numMorphTargets ); - values.push( values[ 0 ] ); - - } - - tracks.push( - new NumberKeyframeTrack( - '.morphTargetInfluences[' + morphTargetSequence[ i ].name + ']', - times, values - ).scale( 1.0 / fps ) ); - - } - - return new this( name, - 1, tracks ); - - } - - static findByName( objectOrClipArray, name ) { - - let clipArray = objectOrClipArray; - - if ( ! Array.isArray( objectOrClipArray ) ) { - - const o = objectOrClipArray; - clipArray = o.geometry && o.geometry.animations || o.animations; - - } - - for ( let i = 0; i < clipArray.length; i ++ ) { - - if ( clipArray[ i ].name === name ) { - - return clipArray[ i ]; - - } - - } - - return null; - - } - - static CreateClipsFromMorphTargetSequences( morphTargets, fps, noLoop ) { - - const animationToMorphTargets = {}; - - // tested with https://regex101.com/ on trick sequences - // such flamingo_flyA_003, flamingo_run1_003, crdeath0059 - const pattern = /^([\w-]*?)([\d]+)$/; - - // sort morph target names into animation groups based - // patterns like Walk_001, Walk_002, Run_001, Run_002 - for ( let i = 0, il = morphTargets.length; i < il; i ++ ) { - - const morphTarget = morphTargets[ i ]; - const parts = morphTarget.name.match( pattern ); - - if ( parts && parts.length > 1 ) { - - const name = parts[ 1 ]; - - let animationMorphTargets = animationToMorphTargets[ name ]; - - if ( ! animationMorphTargets ) { - - animationToMorphTargets[ name ] = animationMorphTargets = []; - - } - - animationMorphTargets.push( morphTarget ); - - } - - } - - const clips = []; - - for ( const name in animationToMorphTargets ) { - - clips.push( this.CreateFromMorphTargetSequence( name, animationToMorphTargets[ name ], fps, noLoop ) ); - - } - - return clips; - - } - - // parse the animation.hierarchy format - static parseAnimation( animation, bones ) { - - if ( ! animation ) { - - console.error( 'THREE.AnimationClip: No animation in JSONLoader data.' ); - return null; - - } - - const addNonemptyTrack = function ( trackType, trackName, animationKeys, propertyName, destTracks ) { - - // only return track if there are actually keys. - if ( animationKeys.length !== 0 ) { - - const times = []; - const values = []; - - flattenJSON( animationKeys, times, values, propertyName ); - - // empty keys are filtered out, so check again - if ( times.length !== 0 ) { - - destTracks.push( new trackType( trackName, times, values ) ); - - } - - } - - }; - - const tracks = []; - - const clipName = animation.name || 'default'; - const fps = animation.fps || 30; - const blendMode = animation.blendMode; - - // automatic length determination in AnimationClip. - let duration = animation.length || - 1; - - const hierarchyTracks = animation.hierarchy || []; - - for ( let h = 0; h < hierarchyTracks.length; h ++ ) { - - const animationKeys = hierarchyTracks[ h ].keys; - - // skip empty tracks - if ( ! animationKeys || animationKeys.length === 0 ) continue; - - // process morph targets - if ( animationKeys[ 0 ].morphTargets ) { - - // figure out all morph targets used in this track - const morphTargetNames = {}; - - let k; - - for ( k = 0; k < animationKeys.length; k ++ ) { - - if ( animationKeys[ k ].morphTargets ) { - - for ( let m = 0; m < animationKeys[ k ].morphTargets.length; m ++ ) { - - morphTargetNames[ animationKeys[ k ].morphTargets[ m ] ] = - 1; - - } - - } - - } - - // create a track for each morph target with all zero - // morphTargetInfluences except for the keys in which - // the morphTarget is named. - for ( const morphTargetName in morphTargetNames ) { - - const times = []; - const values = []; - - for ( let m = 0; m !== animationKeys[ k ].morphTargets.length; ++ m ) { - - const animationKey = animationKeys[ k ]; - - times.push( animationKey.time ); - values.push( ( animationKey.morphTarget === morphTargetName ) ? 1 : 0 ); - - } - - tracks.push( new NumberKeyframeTrack( '.morphTargetInfluence[' + morphTargetName + ']', times, values ) ); - - } - - duration = morphTargetNames.length * fps; - - } else { - - // ...assume skeletal animation - - const boneName = '.bones[' + bones[ h ].name + ']'; - - addNonemptyTrack( - VectorKeyframeTrack, boneName + '.position', - animationKeys, 'pos', tracks ); - - addNonemptyTrack( - QuaternionKeyframeTrack, boneName + '.quaternion', - animationKeys, 'rot', tracks ); - - addNonemptyTrack( - VectorKeyframeTrack, boneName + '.scale', - animationKeys, 'scl', tracks ); - - } - - } - - if ( tracks.length === 0 ) { - - return null; - - } - - const clip = new this( clipName, duration, tracks, blendMode ); - - return clip; - - } - - resetDuration() { - - const tracks = this.tracks; - let duration = 0; - - for ( let i = 0, n = tracks.length; i !== n; ++ i ) { - - const track = this.tracks[ i ]; - - duration = Math.max( duration, track.times[ track.times.length - 1 ] ); - - } - - this.duration = duration; - - return this; - - } - - trim() { - - for ( let i = 0; i < this.tracks.length; i ++ ) { - - this.tracks[ i ].trim( 0, this.duration ); - - } - - return this; - - } - - validate() { - - let valid = true; - - for ( let i = 0; i < this.tracks.length; i ++ ) { - - valid = valid && this.tracks[ i ].validate(); - - } - - return valid; - - } - - optimize() { - - for ( let i = 0; i < this.tracks.length; i ++ ) { - - this.tracks[ i ].optimize(); - - } - - return this; - - } - - clone() { - - const tracks = []; - - for ( let i = 0; i < this.tracks.length; i ++ ) { - - tracks.push( this.tracks[ i ].clone() ); - - } - - return new this.constructor( this.name, this.duration, tracks, this.blendMode ); - - } - - toJSON() { - - return this.constructor.toJSON( this ); - - } - - } - - function getTrackTypeForValueTypeName( typeName ) { - - switch ( typeName.toLowerCase() ) { - - case 'scalar': - case 'double': - case 'float': - case 'number': - case 'integer': - - return NumberKeyframeTrack; - - case 'vector': - case 'vector2': - case 'vector3': - case 'vector4': - - return VectorKeyframeTrack; - - case 'color': - - return ColorKeyframeTrack; - - case 'quaternion': - - return QuaternionKeyframeTrack; - - case 'bool': - case 'boolean': - - return BooleanKeyframeTrack; - - case 'string': - - return StringKeyframeTrack; - - } - - throw new Error( 'THREE.KeyframeTrack: Unsupported typeName: ' + typeName ); - - } - - function parseKeyframeTrack( json ) { - - if ( json.type === undefined ) { - - throw new Error( 'THREE.KeyframeTrack: track type undefined, can not parse' ); - - } - - const trackType = getTrackTypeForValueTypeName( json.type ); - - if ( json.times === undefined ) { - - const times = [], values = []; - - flattenJSON( json.keys, times, values, 'value' ); - - json.times = times; - json.values = values; - - } - - // derived classes can define a static parse method - if ( trackType.parse !== undefined ) { - - return trackType.parse( json ); - - } else { - - // by default, we assume a constructor compatible with the base - return new trackType( json.name, json.times, json.values, json.interpolation ); - - } - - } - - const Cache = { - - enabled: false, - - files: {}, - - add: function ( key, file ) { - - if ( this.enabled === false ) return; - - // console.log( 'THREE.Cache', 'Adding key:', key ); - - this.files[ key ] = file; - - }, - - get: function ( key ) { - - if ( this.enabled === false ) return; - - // console.log( 'THREE.Cache', 'Checking key:', key ); - - return this.files[ key ]; - - }, - - remove: function ( key ) { - - delete this.files[ key ]; - - }, - - clear: function () { - - this.files = {}; - - } - - }; - - class LoadingManager { - - constructor( onLoad, onProgress, onError ) { - - const scope = this; - - let isLoading = false; - let itemsLoaded = 0; - let itemsTotal = 0; - let urlModifier = undefined; - const handlers = []; - - // Refer to #5689 for the reason why we don't set .onStart - // in the constructor - - this.onStart = undefined; - this.onLoad = onLoad; - this.onProgress = onProgress; - this.onError = onError; - - this.itemStart = function ( url ) { - - itemsTotal ++; - - if ( isLoading === false ) { - - if ( scope.onStart !== undefined ) { - - scope.onStart( url, itemsLoaded, itemsTotal ); - - } - - } - - isLoading = true; - - }; - - this.itemEnd = function ( url ) { - - itemsLoaded ++; - - if ( scope.onProgress !== undefined ) { - - scope.onProgress( url, itemsLoaded, itemsTotal ); - - } - - if ( itemsLoaded === itemsTotal ) { - - isLoading = false; - - if ( scope.onLoad !== undefined ) { - - scope.onLoad(); - - } - - } - - }; - - this.itemError = function ( url ) { - - if ( scope.onError !== undefined ) { - - scope.onError( url ); - - } - - }; - - this.resolveURL = function ( url ) { - - if ( urlModifier ) { - - return urlModifier( url ); - - } - - return url; - - }; - - this.setURLModifier = function ( transform ) { - - urlModifier = transform; - - return this; - - }; - - this.addHandler = function ( regex, loader ) { - - handlers.push( regex, loader ); - - return this; - - }; - - this.removeHandler = function ( regex ) { - - const index = handlers.indexOf( regex ); - - if ( index !== - 1 ) { - - handlers.splice( index, 2 ); - - } - - return this; - - }; - - this.getHandler = function ( file ) { - - for ( let i = 0, l = handlers.length; i < l; i += 2 ) { - - const regex = handlers[ i ]; - const loader = handlers[ i + 1 ]; - - if ( regex.global ) regex.lastIndex = 0; // see #17920 - - if ( regex.test( file ) ) { - - return loader; - - } - - } - - return null; - - }; - - } - - } - - const DefaultLoadingManager = /*@__PURE__*/ new LoadingManager(); - - class Loader { - - constructor( manager ) { - - this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager; - - this.crossOrigin = 'anonymous'; - this.withCredentials = false; - this.path = ''; - this.resourcePath = ''; - this.requestHeader = {}; - - } - - load( /* url, onLoad, onProgress, onError */ ) {} - - loadAsync( url, onProgress ) { - - const scope = this; - - return new Promise( function ( resolve, reject ) { - - scope.load( url, resolve, onProgress, reject ); - - } ); - - } - - parse( /* data */ ) {} - - setCrossOrigin( crossOrigin ) { - - this.crossOrigin = crossOrigin; - return this; - - } - - setWithCredentials( value ) { - - this.withCredentials = value; - return this; - - } - - setPath( path ) { - - this.path = path; - return this; - - } - - setResourcePath( resourcePath ) { - - this.resourcePath = resourcePath; - return this; - - } - - setRequestHeader( requestHeader ) { - - this.requestHeader = requestHeader; - return this; - - } - - } - - const loading = {}; - - class HttpError extends Error { - - constructor( message, response ) { - - super( message ); - this.response = response; - - } - - } - - class FileLoader extends Loader { - - constructor( manager ) { - - super( manager ); - - } - - load( url, onLoad, onProgress, onError ) { - - if ( url === undefined ) url = ''; - - if ( this.path !== undefined ) url = this.path + url; - - url = this.manager.resolveURL( url ); - - const cached = Cache.get( url ); - - if ( cached !== undefined ) { - - this.manager.itemStart( url ); - - setTimeout( () => { - - if ( onLoad ) onLoad( cached ); - - this.manager.itemEnd( url ); - - }, 0 ); - - return cached; - - } - - // Check if request is duplicate - - if ( loading[ url ] !== undefined ) { - - loading[ url ].push( { - - onLoad: onLoad, - onProgress: onProgress, - onError: onError - - } ); - - return; - - } - - // Initialise array for duplicate requests - loading[ url ] = []; - - loading[ url ].push( { - onLoad: onLoad, - onProgress: onProgress, - onError: onError, - } ); - - // create request - const req = new Request( url, { - headers: new Headers( this.requestHeader ), - credentials: this.withCredentials ? 'include' : 'same-origin', - // An abort controller could be added within a future PR - } ); - - // record states ( avoid data race ) - const mimeType = this.mimeType; - const responseType = this.responseType; - - // start the fetch - fetch( req ) - .then( response => { - - if ( response.status === 200 || response.status === 0 ) { - - // Some browsers return HTTP Status 0 when using non-http protocol - // e.g. 'file://' or 'data://'. Handle as success. - - if ( response.status === 0 ) { - - console.warn( 'THREE.FileLoader: HTTP Status 0 received.' ); - - } - - // Workaround: Checking if response.body === undefined for Alipay browser #23548 - - if ( typeof ReadableStream === 'undefined' || response.body === undefined || response.body.getReader === undefined ) { - - return response; - - } - - const callbacks = loading[ url ]; - const reader = response.body.getReader(); - - // Nginx needs X-File-Size check - // https://serverfault.com/questions/482875/why-does-nginx-remove-content-length-header-for-chunked-content - const contentLength = response.headers.get( 'Content-Length' ) || response.headers.get( 'X-File-Size' ); - const total = contentLength ? parseInt( contentLength ) : 0; - const lengthComputable = total !== 0; - let loaded = 0; - - // periodically read data into the new stream tracking while download progress - const stream = new ReadableStream( { - start( controller ) { - - readData(); - - function readData() { - - reader.read().then( ( { done, value } ) => { - - if ( done ) { - - controller.close(); - - } else { - - loaded += value.byteLength; - - const event = new ProgressEvent( 'progress', { lengthComputable, loaded, total } ); - for ( let i = 0, il = callbacks.length; i < il; i ++ ) { - - const callback = callbacks[ i ]; - if ( callback.onProgress ) callback.onProgress( event ); - - } - - controller.enqueue( value ); - readData(); - - } - - } ); - - } - - } - - } ); - - return new Response( stream ); - - } else { - - throw new HttpError( `fetch for "${response.url}" responded with ${response.status}: ${response.statusText}`, response ); - - } - - } ) - .then( response => { - - switch ( responseType ) { - - case 'arraybuffer': - - return response.arrayBuffer(); - - case 'blob': - - return response.blob(); - - case 'document': - - return response.text() - .then( text => { - - const parser = new DOMParser(); - return parser.parseFromString( text, mimeType ); - - } ); - - case 'json': - - return response.json(); - - default: - - if ( mimeType === undefined ) { - - return response.text(); - - } else { - - // sniff encoding - const re = /charset="?([^;"\s]*)"?/i; - const exec = re.exec( mimeType ); - const label = exec && exec[ 1 ] ? exec[ 1 ].toLowerCase() : undefined; - const decoder = new TextDecoder( label ); - return response.arrayBuffer().then( ab => decoder.decode( ab ) ); - - } - - } - - } ) - .then( data => { - - // Add to cache only on HTTP success, so that we do not cache - // error response bodies as proper responses to requests. - Cache.add( url, data ); - - const callbacks = loading[ url ]; - delete loading[ url ]; - - for ( let i = 0, il = callbacks.length; i < il; i ++ ) { - - const callback = callbacks[ i ]; - if ( callback.onLoad ) callback.onLoad( data ); - - } - - } ) - .catch( err => { - - // Abort errors and other errors are handled the same - - const callbacks = loading[ url ]; - - if ( callbacks === undefined ) { - - // When onLoad was called and url was deleted in `loading` - this.manager.itemError( url ); - throw err; - - } - - delete loading[ url ]; - - for ( let i = 0, il = callbacks.length; i < il; i ++ ) { - - const callback = callbacks[ i ]; - if ( callback.onError ) callback.onError( err ); - - } - - this.manager.itemError( url ); - - } ) - .finally( () => { - - this.manager.itemEnd( url ); - - } ); - - this.manager.itemStart( url ); - - } - - setResponseType( value ) { - - this.responseType = value; - return this; - - } - - setMimeType( value ) { - - this.mimeType = value; - return this; - - } - - } - - class AnimationLoader extends Loader { - - constructor( manager ) { - - super( manager ); - - } - - load( url, onLoad, onProgress, onError ) { - - const scope = this; - - const loader = new FileLoader( this.manager ); - loader.setPath( this.path ); - loader.setRequestHeader( this.requestHeader ); - loader.setWithCredentials( this.withCredentials ); - loader.load( url, function ( text ) { - - try { - - onLoad( scope.parse( JSON.parse( text ) ) ); - - } catch ( e ) { - - if ( onError ) { - - onError( e ); - - } else { - - console.error( e ); - - } - - scope.manager.itemError( url ); - - } - - }, onProgress, onError ); - - } - - parse( json ) { - - const animations = []; - - for ( let i = 0; i < json.length; i ++ ) { - - const clip = AnimationClip.parse( json[ i ] ); - - animations.push( clip ); - - } - - return animations; - - } - - } - - /** - * Abstract Base class to block based textures loader (dds, pvr, ...) - * - * Sub classes have to implement the parse() method which will be used in load(). - */ - - class CompressedTextureLoader extends Loader { - - constructor( manager ) { - - super( manager ); - - } - - load( url, onLoad, onProgress, onError ) { - - const scope = this; - - const images = []; - - const texture = new CompressedTexture(); - - const loader = new FileLoader( this.manager ); - loader.setPath( this.path ); - loader.setResponseType( 'arraybuffer' ); - loader.setRequestHeader( this.requestHeader ); - loader.setWithCredentials( scope.withCredentials ); - - let loaded = 0; - - function loadTexture( i ) { - - loader.load( url[ i ], function ( buffer ) { - - const texDatas = scope.parse( buffer, true ); - - images[ i ] = { - width: texDatas.width, - height: texDatas.height, - format: texDatas.format, - mipmaps: texDatas.mipmaps - }; - - loaded += 1; - - if ( loaded === 6 ) { - - if ( texDatas.mipmapCount === 1 ) texture.minFilter = LinearFilter; - - texture.image = images; - texture.format = texDatas.format; - texture.needsUpdate = true; - - if ( onLoad ) onLoad( texture ); - - } - - }, onProgress, onError ); - - } - - if ( Array.isArray( url ) ) { - - for ( let i = 0, il = url.length; i < il; ++ i ) { - - loadTexture( i ); - - } - - } else { - - // compressed cubemap texture stored in a single DDS file - - loader.load( url, function ( buffer ) { - - const texDatas = scope.parse( buffer, true ); - - if ( texDatas.isCubemap ) { - - const faces = texDatas.mipmaps.length / texDatas.mipmapCount; - - for ( let f = 0; f < faces; f ++ ) { - - images[ f ] = { mipmaps: [] }; - - for ( let i = 0; i < texDatas.mipmapCount; i ++ ) { - - images[ f ].mipmaps.push( texDatas.mipmaps[ f * texDatas.mipmapCount + i ] ); - images[ f ].format = texDatas.format; - images[ f ].width = texDatas.width; - images[ f ].height = texDatas.height; - - } - - } - - texture.image = images; - - } else { - - texture.image.width = texDatas.width; - texture.image.height = texDatas.height; - texture.mipmaps = texDatas.mipmaps; - - } - - if ( texDatas.mipmapCount === 1 ) { - - texture.minFilter = LinearFilter; - - } - - texture.format = texDatas.format; - texture.needsUpdate = true; - - if ( onLoad ) onLoad( texture ); - - }, onProgress, onError ); - - } - - return texture; - - } - - } - - class ImageLoader extends Loader { - - constructor( manager ) { - - super( manager ); - - } - - load( url, onLoad, onProgress, onError ) { - - if ( this.path !== undefined ) url = this.path + url; - - url = this.manager.resolveURL( url ); - - const scope = this; - - const cached = Cache.get( url ); - - if ( cached !== undefined ) { - - scope.manager.itemStart( url ); - - setTimeout( function () { - - if ( onLoad ) onLoad( cached ); - - scope.manager.itemEnd( url ); - - }, 0 ); - - return cached; - - } - - const image = createElementNS( 'img' ); - - function onImageLoad() { - - removeEventListeners(); - - Cache.add( url, this ); - - if ( onLoad ) onLoad( this ); - - scope.manager.itemEnd( url ); - - } - - function onImageError( event ) { - - removeEventListeners(); - - if ( onError ) onError( event ); - - scope.manager.itemError( url ); - scope.manager.itemEnd( url ); - - } - - function removeEventListeners() { - - image.removeEventListener( 'load', onImageLoad, false ); - image.removeEventListener( 'error', onImageError, false ); - - } - - image.addEventListener( 'load', onImageLoad, false ); - image.addEventListener( 'error', onImageError, false ); - - if ( url.slice( 0, 5 ) !== 'data:' ) { - - if ( this.crossOrigin !== undefined ) image.crossOrigin = this.crossOrigin; - - } - - scope.manager.itemStart( url ); - - image.src = url; - - return image; - - } - - } - - class CubeTextureLoader extends Loader { - - constructor( manager ) { - - super( manager ); - - } - - load( urls, onLoad, onProgress, onError ) { - - const texture = new CubeTexture(); - - const loader = new ImageLoader( this.manager ); - loader.setCrossOrigin( this.crossOrigin ); - loader.setPath( this.path ); - - let loaded = 0; - - function loadTexture( i ) { - - loader.load( urls[ i ], function ( image ) { - - texture.images[ i ] = image; - - loaded ++; - - if ( loaded === 6 ) { - - texture.needsUpdate = true; - - if ( onLoad ) onLoad( texture ); - - } - - }, undefined, onError ); - - } - - for ( let i = 0; i < urls.length; ++ i ) { - - loadTexture( i ); - - } - - return texture; - - } - - } - - /** - * Abstract Base class to load generic binary textures formats (rgbe, hdr, ...) - * - * Sub classes have to implement the parse() method which will be used in load(). - */ - - class DataTextureLoader extends Loader { - - constructor( manager ) { - - super( manager ); - - } - - load( url, onLoad, onProgress, onError ) { - - const scope = this; - - const texture = new DataTexture(); - - const loader = new FileLoader( this.manager ); - loader.setResponseType( 'arraybuffer' ); - loader.setRequestHeader( this.requestHeader ); - loader.setPath( this.path ); - loader.setWithCredentials( scope.withCredentials ); - loader.load( url, function ( buffer ) { - - const texData = scope.parse( buffer ); - - if ( ! texData ) return; - - if ( texData.image !== undefined ) { - - texture.image = texData.image; - - } else if ( texData.data !== undefined ) { - - texture.image.width = texData.width; - texture.image.height = texData.height; - texture.image.data = texData.data; - - } - - texture.wrapS = texData.wrapS !== undefined ? texData.wrapS : ClampToEdgeWrapping; - texture.wrapT = texData.wrapT !== undefined ? texData.wrapT : ClampToEdgeWrapping; - - texture.magFilter = texData.magFilter !== undefined ? texData.magFilter : LinearFilter; - texture.minFilter = texData.minFilter !== undefined ? texData.minFilter : LinearFilter; - - texture.anisotropy = texData.anisotropy !== undefined ? texData.anisotropy : 1; - - if ( texData.colorSpace !== undefined ) { - - texture.colorSpace = texData.colorSpace; - - } else if ( texData.encoding !== undefined ) { // @deprecated, r152 - - texture.encoding = texData.encoding; - - } - - if ( texData.flipY !== undefined ) { - - texture.flipY = texData.flipY; - - } - - if ( texData.format !== undefined ) { - - texture.format = texData.format; - - } - - if ( texData.type !== undefined ) { - - texture.type = texData.type; - - } - - if ( texData.mipmaps !== undefined ) { - - texture.mipmaps = texData.mipmaps; - texture.minFilter = LinearMipmapLinearFilter; // presumably... - - } - - if ( texData.mipmapCount === 1 ) { - - texture.minFilter = LinearFilter; - - } - - if ( texData.generateMipmaps !== undefined ) { - - texture.generateMipmaps = texData.generateMipmaps; - - } - - texture.needsUpdate = true; - - if ( onLoad ) onLoad( texture, texData ); - - }, onProgress, onError ); - - - return texture; - - } - - } - - class TextureLoader extends Loader { - - constructor( manager ) { - - super( manager ); - - } - - load( url, onLoad, onProgress, onError ) { - - const texture = new Texture(); - - const loader = new ImageLoader( this.manager ); - loader.setCrossOrigin( this.crossOrigin ); - loader.setPath( this.path ); - - loader.load( url, function ( image ) { - - texture.image = image; - texture.needsUpdate = true; - - if ( onLoad !== undefined ) { - - onLoad( texture ); - - } - - }, onProgress, onError ); - - return texture; - - } - - } - - class Light extends Object3D { - - constructor( color, intensity = 1 ) { - - super(); - - this.isLight = true; - - this.type = 'Light'; - - this.color = new Color( color ); - this.intensity = intensity; - - } - - dispose() { - - // Empty here in base class; some subclasses override. - - } - - copy( source, recursive ) { - - super.copy( source, recursive ); - - this.color.copy( source.color ); - this.intensity = source.intensity; - - return this; - - } - - toJSON( meta ) { - - const data = super.toJSON( meta ); - - data.object.color = this.color.getHex(); - data.object.intensity = this.intensity; - - if ( this.groundColor !== undefined ) data.object.groundColor = this.groundColor.getHex(); - - if ( this.distance !== undefined ) data.object.distance = this.distance; - if ( this.angle !== undefined ) data.object.angle = this.angle; - if ( this.decay !== undefined ) data.object.decay = this.decay; - if ( this.penumbra !== undefined ) data.object.penumbra = this.penumbra; - - if ( this.shadow !== undefined ) data.object.shadow = this.shadow.toJSON(); - - return data; - - } - - } - - class HemisphereLight extends Light { - - constructor( skyColor, groundColor, intensity ) { - - super( skyColor, intensity ); - - this.isHemisphereLight = true; - - this.type = 'HemisphereLight'; - - this.position.copy( Object3D.DEFAULT_UP ); - this.updateMatrix(); - - this.groundColor = new Color( groundColor ); - - } - - copy( source, recursive ) { - - super.copy( source, recursive ); - - this.groundColor.copy( source.groundColor ); - - return this; - - } - - } - - const _projScreenMatrix$1 = /*@__PURE__*/ new Matrix4(); - const _lightPositionWorld$1 = /*@__PURE__*/ new Vector3(); - const _lookTarget$1 = /*@__PURE__*/ new Vector3(); - - class LightShadow { - - constructor( camera ) { - - this.camera = camera; - - this.bias = 0; - this.normalBias = 0; - this.radius = 1; - this.blurSamples = 8; - - this.mapSize = new Vector2( 512, 512 ); - - this.map = null; - this.mapPass = null; - this.matrix = new Matrix4(); - - this.autoUpdate = true; - this.needsUpdate = false; - - this._frustum = new Frustum(); - this._frameExtents = new Vector2( 1, 1 ); - - this._viewportCount = 1; - - this._viewports = [ - - new Vector4( 0, 0, 1, 1 ) - - ]; - - } - - getViewportCount() { - - return this._viewportCount; - - } - - getFrustum() { - - return this._frustum; - - } - - updateMatrices( light ) { - - const shadowCamera = this.camera; - const shadowMatrix = this.matrix; - - _lightPositionWorld$1.setFromMatrixPosition( light.matrixWorld ); - shadowCamera.position.copy( _lightPositionWorld$1 ); - - _lookTarget$1.setFromMatrixPosition( light.target.matrixWorld ); - shadowCamera.lookAt( _lookTarget$1 ); - shadowCamera.updateMatrixWorld(); - - _projScreenMatrix$1.multiplyMatrices( shadowCamera.projectionMatrix, shadowCamera.matrixWorldInverse ); - this._frustum.setFromProjectionMatrix( _projScreenMatrix$1 ); - - shadowMatrix.set( - 0.5, 0.0, 0.0, 0.5, - 0.0, 0.5, 0.0, 0.5, - 0.0, 0.0, 0.5, 0.5, - 0.0, 0.0, 0.0, 1.0 - ); - - shadowMatrix.multiply( _projScreenMatrix$1 ); - - } - - getViewport( viewportIndex ) { - - return this._viewports[ viewportIndex ]; - - } - - getFrameExtents() { - - return this._frameExtents; - - } - - dispose() { - - if ( this.map ) { - - this.map.dispose(); - - } - - if ( this.mapPass ) { - - this.mapPass.dispose(); - - } - - } - - copy( source ) { - - this.camera = source.camera.clone(); - - this.bias = source.bias; - this.radius = source.radius; - - this.mapSize.copy( source.mapSize ); - - return this; - - } - - clone() { - - return new this.constructor().copy( this ); - - } - - toJSON() { - - const object = {}; - - if ( this.bias !== 0 ) object.bias = this.bias; - if ( this.normalBias !== 0 ) object.normalBias = this.normalBias; - if ( this.radius !== 1 ) object.radius = this.radius; - if ( this.mapSize.x !== 512 || this.mapSize.y !== 512 ) object.mapSize = this.mapSize.toArray(); - - object.camera = this.camera.toJSON( false ).object; - delete object.camera.matrix; - - return object; - - } - - } - - class SpotLightShadow extends LightShadow { - - constructor() { - - super( new PerspectiveCamera( 50, 1, 0.5, 500 ) ); - - this.isSpotLightShadow = true; - - this.focus = 1; - - } - - updateMatrices( light ) { - - const camera = this.camera; - - const fov = RAD2DEG * 2 * light.angle * this.focus; - const aspect = this.mapSize.width / this.mapSize.height; - const far = light.distance || camera.far; - - if ( fov !== camera.fov || aspect !== camera.aspect || far !== camera.far ) { - - camera.fov = fov; - camera.aspect = aspect; - camera.far = far; - camera.updateProjectionMatrix(); - - } - - super.updateMatrices( light ); - - } - - copy( source ) { - - super.copy( source ); - - this.focus = source.focus; - - return this; - - } - - } - - class SpotLight extends Light { - - constructor( color, intensity, distance = 0, angle = Math.PI / 3, penumbra = 0, decay = 2 ) { - - super( color, intensity ); - - this.isSpotLight = true; - - this.type = 'SpotLight'; - - this.position.copy( Object3D.DEFAULT_UP ); - this.updateMatrix(); - - this.target = new Object3D(); - - this.distance = distance; - this.angle = angle; - this.penumbra = penumbra; - this.decay = decay; - - this.map = null; - - this.shadow = new SpotLightShadow(); - - } - - get power() { - - // compute the light's luminous power (in lumens) from its intensity (in candela) - // by convention for a spotlight, luminous power (lm) = π * luminous intensity (cd) - return this.intensity * Math.PI; - - } - - set power( power ) { - - // set the light's intensity (in candela) from the desired luminous power (in lumens) - this.intensity = power / Math.PI; - - } - - dispose() { - - this.shadow.dispose(); - - } - - copy( source, recursive ) { - - super.copy( source, recursive ); - - this.distance = source.distance; - this.angle = source.angle; - this.penumbra = source.penumbra; - this.decay = source.decay; - - this.target = source.target.clone(); - - this.shadow = source.shadow.clone(); - - return this; - - } - - } - - const _projScreenMatrix = /*@__PURE__*/ new Matrix4(); - const _lightPositionWorld = /*@__PURE__*/ new Vector3(); - const _lookTarget = /*@__PURE__*/ new Vector3(); - - class PointLightShadow extends LightShadow { - - constructor() { - - super( new PerspectiveCamera( 90, 1, 0.5, 500 ) ); - - this.isPointLightShadow = true; - - this._frameExtents = new Vector2( 4, 2 ); - - this._viewportCount = 6; - - this._viewports = [ - // These viewports map a cube-map onto a 2D texture with the - // following orientation: - // - // xzXZ - // y Y - // - // X - Positive x direction - // x - Negative x direction - // Y - Positive y direction - // y - Negative y direction - // Z - Positive z direction - // z - Negative z direction - - // positive X - new Vector4( 2, 1, 1, 1 ), - // negative X - new Vector4( 0, 1, 1, 1 ), - // positive Z - new Vector4( 3, 1, 1, 1 ), - // negative Z - new Vector4( 1, 1, 1, 1 ), - // positive Y - new Vector4( 3, 0, 1, 1 ), - // negative Y - new Vector4( 1, 0, 1, 1 ) - ]; - - this._cubeDirections = [ - new Vector3( 1, 0, 0 ), new Vector3( - 1, 0, 0 ), new Vector3( 0, 0, 1 ), - new Vector3( 0, 0, - 1 ), new Vector3( 0, 1, 0 ), new Vector3( 0, - 1, 0 ) - ]; - - this._cubeUps = [ - new Vector3( 0, 1, 0 ), new Vector3( 0, 1, 0 ), new Vector3( 0, 1, 0 ), - new Vector3( 0, 1, 0 ), new Vector3( 0, 0, 1 ), new Vector3( 0, 0, - 1 ) - ]; - - } - - updateMatrices( light, viewportIndex = 0 ) { - - const camera = this.camera; - const shadowMatrix = this.matrix; - - const far = light.distance || camera.far; - - if ( far !== camera.far ) { - - camera.far = far; - camera.updateProjectionMatrix(); - - } - - _lightPositionWorld.setFromMatrixPosition( light.matrixWorld ); - camera.position.copy( _lightPositionWorld ); - - _lookTarget.copy( camera.position ); - _lookTarget.add( this._cubeDirections[ viewportIndex ] ); - camera.up.copy( this._cubeUps[ viewportIndex ] ); - camera.lookAt( _lookTarget ); - camera.updateMatrixWorld(); - - shadowMatrix.makeTranslation( - _lightPositionWorld.x, - _lightPositionWorld.y, - _lightPositionWorld.z ); - - _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); - this._frustum.setFromProjectionMatrix( _projScreenMatrix ); - - } - - } - - class PointLight extends Light { - - constructor( color, intensity, distance = 0, decay = 2 ) { - - super( color, intensity ); - - this.isPointLight = true; - - this.type = 'PointLight'; - - this.distance = distance; - this.decay = decay; - - this.shadow = new PointLightShadow(); - - } - - get power() { - - // compute the light's luminous power (in lumens) from its intensity (in candela) - // for an isotropic light source, luminous power (lm) = 4 π luminous intensity (cd) - return this.intensity * 4 * Math.PI; - - } - - set power( power ) { - - // set the light's intensity (in candela) from the desired luminous power (in lumens) - this.intensity = power / ( 4 * Math.PI ); - - } - - dispose() { - - this.shadow.dispose(); - - } - - copy( source, recursive ) { - - super.copy( source, recursive ); - - this.distance = source.distance; - this.decay = source.decay; - - this.shadow = source.shadow.clone(); - - return this; - - } - - } - - class DirectionalLightShadow extends LightShadow { - - constructor() { - - super( new OrthographicCamera( - 5, 5, 5, - 5, 0.5, 500 ) ); - - this.isDirectionalLightShadow = true; - - } - - } - - class DirectionalLight extends Light { - - constructor( color, intensity ) { - - super( color, intensity ); - - this.isDirectionalLight = true; - - this.type = 'DirectionalLight'; - - this.position.copy( Object3D.DEFAULT_UP ); - this.updateMatrix(); - - this.target = new Object3D(); - - this.shadow = new DirectionalLightShadow(); - - } - - dispose() { - - this.shadow.dispose(); - - } - - copy( source ) { - - super.copy( source ); - - this.target = source.target.clone(); - this.shadow = source.shadow.clone(); - - return this; - - } - - } - - class AmbientLight extends Light { - - constructor( color, intensity ) { - - super( color, intensity ); - - this.isAmbientLight = true; - - this.type = 'AmbientLight'; - - } - - } - - class RectAreaLight extends Light { - - constructor( color, intensity, width = 10, height = 10 ) { - - super( color, intensity ); - - this.isRectAreaLight = true; - - this.type = 'RectAreaLight'; - - this.width = width; - this.height = height; - - } - - get power() { - - // compute the light's luminous power (in lumens) from its intensity (in nits) - return this.intensity * this.width * this.height * Math.PI; - - } - - set power( power ) { - - // set the light's intensity (in nits) from the desired luminous power (in lumens) - this.intensity = power / ( this.width * this.height * Math.PI ); - - } - - copy( source ) { - - super.copy( source ); - - this.width = source.width; - this.height = source.height; - - return this; - - } - - toJSON( meta ) { - - const data = super.toJSON( meta ); - - data.object.width = this.width; - data.object.height = this.height; - - return data; - - } - - } - - /** - * Primary reference: - * https://graphics.stanford.edu/papers/envmap/envmap.pdf - * - * Secondary reference: - * https://www.ppsloan.org/publications/StupidSH36.pdf - */ - - // 3-band SH defined by 9 coefficients - - class SphericalHarmonics3 { - - constructor() { - - this.isSphericalHarmonics3 = true; - - this.coefficients = []; - - for ( let i = 0; i < 9; i ++ ) { - - this.coefficients.push( new Vector3() ); - - } - - } - - set( coefficients ) { - - for ( let i = 0; i < 9; i ++ ) { - - this.coefficients[ i ].copy( coefficients[ i ] ); - - } - - return this; - - } - - zero() { - - for ( let i = 0; i < 9; i ++ ) { - - this.coefficients[ i ].set( 0, 0, 0 ); - - } - - return this; - - } - - // get the radiance in the direction of the normal - // target is a Vector3 - getAt( normal, target ) { - - // normal is assumed to be unit length - - const x = normal.x, y = normal.y, z = normal.z; - - const coeff = this.coefficients; - - // band 0 - target.copy( coeff[ 0 ] ).multiplyScalar( 0.282095 ); - - // band 1 - target.addScaledVector( coeff[ 1 ], 0.488603 * y ); - target.addScaledVector( coeff[ 2 ], 0.488603 * z ); - target.addScaledVector( coeff[ 3 ], 0.488603 * x ); - - // band 2 - target.addScaledVector( coeff[ 4 ], 1.092548 * ( x * y ) ); - target.addScaledVector( coeff[ 5 ], 1.092548 * ( y * z ) ); - target.addScaledVector( coeff[ 6 ], 0.315392 * ( 3.0 * z * z - 1.0 ) ); - target.addScaledVector( coeff[ 7 ], 1.092548 * ( x * z ) ); - target.addScaledVector( coeff[ 8 ], 0.546274 * ( x * x - y * y ) ); - - return target; - - } - - // get the irradiance (radiance convolved with cosine lobe) in the direction of the normal - // target is a Vector3 - // https://graphics.stanford.edu/papers/envmap/envmap.pdf - getIrradianceAt( normal, target ) { - - // normal is assumed to be unit length - - const x = normal.x, y = normal.y, z = normal.z; - - const coeff = this.coefficients; - - // band 0 - target.copy( coeff[ 0 ] ).multiplyScalar( 0.886227 ); // π * 0.282095 - - // band 1 - target.addScaledVector( coeff[ 1 ], 2.0 * 0.511664 * y ); // ( 2 * π / 3 ) * 0.488603 - target.addScaledVector( coeff[ 2 ], 2.0 * 0.511664 * z ); - target.addScaledVector( coeff[ 3 ], 2.0 * 0.511664 * x ); - - // band 2 - target.addScaledVector( coeff[ 4 ], 2.0 * 0.429043 * x * y ); // ( π / 4 ) * 1.092548 - target.addScaledVector( coeff[ 5 ], 2.0 * 0.429043 * y * z ); - target.addScaledVector( coeff[ 6 ], 0.743125 * z * z - 0.247708 ); // ( π / 4 ) * 0.315392 * 3 - target.addScaledVector( coeff[ 7 ], 2.0 * 0.429043 * x * z ); - target.addScaledVector( coeff[ 8 ], 0.429043 * ( x * x - y * y ) ); // ( π / 4 ) * 0.546274 - - return target; - - } - - add( sh ) { - - for ( let i = 0; i < 9; i ++ ) { - - this.coefficients[ i ].add( sh.coefficients[ i ] ); - - } - - return this; - - } - - addScaledSH( sh, s ) { - - for ( let i = 0; i < 9; i ++ ) { - - this.coefficients[ i ].addScaledVector( sh.coefficients[ i ], s ); - - } - - return this; - - } - - scale( s ) { - - for ( let i = 0; i < 9; i ++ ) { - - this.coefficients[ i ].multiplyScalar( s ); - - } - - return this; - - } - - lerp( sh, alpha ) { - - for ( let i = 0; i < 9; i ++ ) { - - this.coefficients[ i ].lerp( sh.coefficients[ i ], alpha ); - - } - - return this; - - } - - equals( sh ) { - - for ( let i = 0; i < 9; i ++ ) { - - if ( ! this.coefficients[ i ].equals( sh.coefficients[ i ] ) ) { - - return false; - - } - - } - - return true; - - } - - copy( sh ) { - - return this.set( sh.coefficients ); - - } - - clone() { - - return new this.constructor().copy( this ); - - } - - fromArray( array, offset = 0 ) { - - const coefficients = this.coefficients; - - for ( let i = 0; i < 9; i ++ ) { - - coefficients[ i ].fromArray( array, offset + ( i * 3 ) ); - - } - - return this; - - } - - toArray( array = [], offset = 0 ) { - - const coefficients = this.coefficients; - - for ( let i = 0; i < 9; i ++ ) { - - coefficients[ i ].toArray( array, offset + ( i * 3 ) ); - - } - - return array; - - } - - // evaluate the basis functions - // shBasis is an Array[ 9 ] - static getBasisAt( normal, shBasis ) { - - // normal is assumed to be unit length - - const x = normal.x, y = normal.y, z = normal.z; - - // band 0 - shBasis[ 0 ] = 0.282095; - - // band 1 - shBasis[ 1 ] = 0.488603 * y; - shBasis[ 2 ] = 0.488603 * z; - shBasis[ 3 ] = 0.488603 * x; - - // band 2 - shBasis[ 4 ] = 1.092548 * x * y; - shBasis[ 5 ] = 1.092548 * y * z; - shBasis[ 6 ] = 0.315392 * ( 3 * z * z - 1 ); - shBasis[ 7 ] = 1.092548 * x * z; - shBasis[ 8 ] = 0.546274 * ( x * x - y * y ); - - } - - } - - class LightProbe extends Light { - - constructor( sh = new SphericalHarmonics3(), intensity = 1 ) { - - super( undefined, intensity ); - - this.isLightProbe = true; - - this.sh = sh; - - } - - copy( source ) { - - super.copy( source ); - - this.sh.copy( source.sh ); - - return this; - - } - - fromJSON( json ) { - - this.intensity = json.intensity; // TODO: Move this bit to Light.fromJSON(); - this.sh.fromArray( json.sh ); - - return this; - - } - - toJSON( meta ) { - - const data = super.toJSON( meta ); - - data.object.sh = this.sh.toArray(); - - return data; - - } - - } - - class MaterialLoader extends Loader { - - constructor( manager ) { - - super( manager ); - this.textures = {}; - - } - - load( url, onLoad, onProgress, onError ) { - - const scope = this; - - const loader = new FileLoader( scope.manager ); - loader.setPath( scope.path ); - loader.setRequestHeader( scope.requestHeader ); - loader.setWithCredentials( scope.withCredentials ); - loader.load( url, function ( text ) { - - try { - - onLoad( scope.parse( JSON.parse( text ) ) ); - - } catch ( e ) { - - if ( onError ) { - - onError( e ); - - } else { - - console.error( e ); - - } - - scope.manager.itemError( url ); - - } - - }, onProgress, onError ); - - } - - parse( json ) { - - const textures = this.textures; - - function getTexture( name ) { - - if ( textures[ name ] === undefined ) { - - console.warn( 'THREE.MaterialLoader: Undefined texture', name ); - - } - - return textures[ name ]; - - } - - const material = MaterialLoader.createMaterialFromType( json.type ); - - if ( json.uuid !== undefined ) material.uuid = json.uuid; - if ( json.name !== undefined ) material.name = json.name; - if ( json.color !== undefined && material.color !== undefined ) material.color.setHex( json.color ); - if ( json.roughness !== undefined ) material.roughness = json.roughness; - if ( json.metalness !== undefined ) material.metalness = json.metalness; - if ( json.sheen !== undefined ) material.sheen = json.sheen; - if ( json.sheenColor !== undefined ) material.sheenColor = new Color().setHex( json.sheenColor ); - if ( json.sheenRoughness !== undefined ) material.sheenRoughness = json.sheenRoughness; - if ( json.emissive !== undefined && material.emissive !== undefined ) material.emissive.setHex( json.emissive ); - if ( json.specular !== undefined && material.specular !== undefined ) material.specular.setHex( json.specular ); - if ( json.specularIntensity !== undefined ) material.specularIntensity = json.specularIntensity; - if ( json.specularColor !== undefined && material.specularColor !== undefined ) material.specularColor.setHex( json.specularColor ); - if ( json.shininess !== undefined ) material.shininess = json.shininess; - if ( json.clearcoat !== undefined ) material.clearcoat = json.clearcoat; - if ( json.clearcoatRoughness !== undefined ) material.clearcoatRoughness = json.clearcoatRoughness; - if ( json.iridescence !== undefined ) material.iridescence = json.iridescence; - if ( json.iridescenceIOR !== undefined ) material.iridescenceIOR = json.iridescenceIOR; - if ( json.iridescenceThicknessRange !== undefined ) material.iridescenceThicknessRange = json.iridescenceThicknessRange; - if ( json.transmission !== undefined ) material.transmission = json.transmission; - if ( json.thickness !== undefined ) material.thickness = json.thickness; - if ( json.attenuationDistance !== undefined ) material.attenuationDistance = json.attenuationDistance; - if ( json.attenuationColor !== undefined && material.attenuationColor !== undefined ) material.attenuationColor.setHex( json.attenuationColor ); - if ( json.anisotropy !== undefined ) material.anisotropy = json.anisotropy; - if ( json.anisotropyRotation !== undefined ) material.anisotropyRotation = json.anisotropyRotation; - if ( json.fog !== undefined ) material.fog = json.fog; - if ( json.flatShading !== undefined ) material.flatShading = json.flatShading; - if ( json.blending !== undefined ) material.blending = json.blending; - if ( json.combine !== undefined ) material.combine = json.combine; - if ( json.side !== undefined ) material.side = json.side; - if ( json.shadowSide !== undefined ) material.shadowSide = json.shadowSide; - if ( json.opacity !== undefined ) material.opacity = json.opacity; - if ( json.transparent !== undefined ) material.transparent = json.transparent; - if ( json.alphaTest !== undefined ) material.alphaTest = json.alphaTest; - if ( json.depthTest !== undefined ) material.depthTest = json.depthTest; - if ( json.depthWrite !== undefined ) material.depthWrite = json.depthWrite; - if ( json.colorWrite !== undefined ) material.colorWrite = json.colorWrite; - - if ( json.stencilWrite !== undefined ) material.stencilWrite = json.stencilWrite; - if ( json.stencilWriteMask !== undefined ) material.stencilWriteMask = json.stencilWriteMask; - if ( json.stencilFunc !== undefined ) material.stencilFunc = json.stencilFunc; - if ( json.stencilRef !== undefined ) material.stencilRef = json.stencilRef; - if ( json.stencilFuncMask !== undefined ) material.stencilFuncMask = json.stencilFuncMask; - if ( json.stencilFail !== undefined ) material.stencilFail = json.stencilFail; - if ( json.stencilZFail !== undefined ) material.stencilZFail = json.stencilZFail; - if ( json.stencilZPass !== undefined ) material.stencilZPass = json.stencilZPass; - - if ( json.wireframe !== undefined ) material.wireframe = json.wireframe; - if ( json.wireframeLinewidth !== undefined ) material.wireframeLinewidth = json.wireframeLinewidth; - if ( json.wireframeLinecap !== undefined ) material.wireframeLinecap = json.wireframeLinecap; - if ( json.wireframeLinejoin !== undefined ) material.wireframeLinejoin = json.wireframeLinejoin; - - if ( json.rotation !== undefined ) material.rotation = json.rotation; - - if ( json.linewidth !== 1 ) material.linewidth = json.linewidth; - if ( json.dashSize !== undefined ) material.dashSize = json.dashSize; - if ( json.gapSize !== undefined ) material.gapSize = json.gapSize; - if ( json.scale !== undefined ) material.scale = json.scale; - - if ( json.polygonOffset !== undefined ) material.polygonOffset = json.polygonOffset; - if ( json.polygonOffsetFactor !== undefined ) material.polygonOffsetFactor = json.polygonOffsetFactor; - if ( json.polygonOffsetUnits !== undefined ) material.polygonOffsetUnits = json.polygonOffsetUnits; - - if ( json.dithering !== undefined ) material.dithering = json.dithering; - - if ( json.alphaToCoverage !== undefined ) material.alphaToCoverage = json.alphaToCoverage; - if ( json.premultipliedAlpha !== undefined ) material.premultipliedAlpha = json.premultipliedAlpha; - if ( json.forceSinglePass !== undefined ) material.forceSinglePass = json.forceSinglePass; - - if ( json.visible !== undefined ) material.visible = json.visible; - - if ( json.toneMapped !== undefined ) material.toneMapped = json.toneMapped; - - if ( json.userData !== undefined ) material.userData = json.userData; - - if ( json.vertexColors !== undefined ) { - - if ( typeof json.vertexColors === 'number' ) { - - material.vertexColors = ( json.vertexColors > 0 ) ? true : false; - - } else { - - material.vertexColors = json.vertexColors; - - } - - } - - // Shader Material - - if ( json.uniforms !== undefined ) { - - for ( const name in json.uniforms ) { - - const uniform = json.uniforms[ name ]; - - material.uniforms[ name ] = {}; - - switch ( uniform.type ) { - - case 't': - material.uniforms[ name ].value = getTexture( uniform.value ); - break; - - case 'c': - material.uniforms[ name ].value = new Color().setHex( uniform.value ); - break; - - case 'v2': - material.uniforms[ name ].value = new Vector2().fromArray( uniform.value ); - break; - - case 'v3': - material.uniforms[ name ].value = new Vector3().fromArray( uniform.value ); - break; - - case 'v4': - material.uniforms[ name ].value = new Vector4().fromArray( uniform.value ); - break; - - case 'm3': - material.uniforms[ name ].value = new Matrix3().fromArray( uniform.value ); - break; - - case 'm4': - material.uniforms[ name ].value = new Matrix4().fromArray( uniform.value ); - break; - - default: - material.uniforms[ name ].value = uniform.value; - - } - - } - - } - - if ( json.defines !== undefined ) material.defines = json.defines; - if ( json.vertexShader !== undefined ) material.vertexShader = json.vertexShader; - if ( json.fragmentShader !== undefined ) material.fragmentShader = json.fragmentShader; - if ( json.glslVersion !== undefined ) material.glslVersion = json.glslVersion; - - if ( json.extensions !== undefined ) { - - for ( const key in json.extensions ) { - - material.extensions[ key ] = json.extensions[ key ]; - - } - - } - - if ( json.lights !== undefined ) material.lights = json.lights; - if ( json.clipping !== undefined ) material.clipping = json.clipping; - - // for PointsMaterial - - if ( json.size !== undefined ) material.size = json.size; - if ( json.sizeAttenuation !== undefined ) material.sizeAttenuation = json.sizeAttenuation; - - // maps - - if ( json.map !== undefined ) material.map = getTexture( json.map ); - if ( json.matcap !== undefined ) material.matcap = getTexture( json.matcap ); - - if ( json.alphaMap !== undefined ) material.alphaMap = getTexture( json.alphaMap ); - - if ( json.bumpMap !== undefined ) material.bumpMap = getTexture( json.bumpMap ); - if ( json.bumpScale !== undefined ) material.bumpScale = json.bumpScale; - - if ( json.normalMap !== undefined ) material.normalMap = getTexture( json.normalMap ); - if ( json.normalMapType !== undefined ) material.normalMapType = json.normalMapType; - if ( json.normalScale !== undefined ) { - - let normalScale = json.normalScale; - - if ( Array.isArray( normalScale ) === false ) { - - // Blender exporter used to export a scalar. See #7459 - - normalScale = [ normalScale, normalScale ]; - - } - - material.normalScale = new Vector2().fromArray( normalScale ); - - } - - if ( json.displacementMap !== undefined ) material.displacementMap = getTexture( json.displacementMap ); - if ( json.displacementScale !== undefined ) material.displacementScale = json.displacementScale; - if ( json.displacementBias !== undefined ) material.displacementBias = json.displacementBias; - - if ( json.roughnessMap !== undefined ) material.roughnessMap = getTexture( json.roughnessMap ); - if ( json.metalnessMap !== undefined ) material.metalnessMap = getTexture( json.metalnessMap ); - - if ( json.emissiveMap !== undefined ) material.emissiveMap = getTexture( json.emissiveMap ); - if ( json.emissiveIntensity !== undefined ) material.emissiveIntensity = json.emissiveIntensity; - - if ( json.specularMap !== undefined ) material.specularMap = getTexture( json.specularMap ); - if ( json.specularIntensityMap !== undefined ) material.specularIntensityMap = getTexture( json.specularIntensityMap ); - if ( json.specularColorMap !== undefined ) material.specularColorMap = getTexture( json.specularColorMap ); - - if ( json.envMap !== undefined ) material.envMap = getTexture( json.envMap ); - if ( json.envMapIntensity !== undefined ) material.envMapIntensity = json.envMapIntensity; - - if ( json.reflectivity !== undefined ) material.reflectivity = json.reflectivity; - if ( json.refractionRatio !== undefined ) material.refractionRatio = json.refractionRatio; - - if ( json.lightMap !== undefined ) material.lightMap = getTexture( json.lightMap ); - if ( json.lightMapIntensity !== undefined ) material.lightMapIntensity = json.lightMapIntensity; - - if ( json.aoMap !== undefined ) material.aoMap = getTexture( json.aoMap ); - if ( json.aoMapIntensity !== undefined ) material.aoMapIntensity = json.aoMapIntensity; - - if ( json.gradientMap !== undefined ) material.gradientMap = getTexture( json.gradientMap ); - - if ( json.clearcoatMap !== undefined ) material.clearcoatMap = getTexture( json.clearcoatMap ); - if ( json.clearcoatRoughnessMap !== undefined ) material.clearcoatRoughnessMap = getTexture( json.clearcoatRoughnessMap ); - if ( json.clearcoatNormalMap !== undefined ) material.clearcoatNormalMap = getTexture( json.clearcoatNormalMap ); - if ( json.clearcoatNormalScale !== undefined ) material.clearcoatNormalScale = new Vector2().fromArray( json.clearcoatNormalScale ); - - if ( json.iridescenceMap !== undefined ) material.iridescenceMap = getTexture( json.iridescenceMap ); - if ( json.iridescenceThicknessMap !== undefined ) material.iridescenceThicknessMap = getTexture( json.iridescenceThicknessMap ); - - if ( json.transmissionMap !== undefined ) material.transmissionMap = getTexture( json.transmissionMap ); - if ( json.thicknessMap !== undefined ) material.thicknessMap = getTexture( json.thicknessMap ); - - if ( json.anisotropyMap !== undefined ) material.anisotropyMap = getTexture( json.anisotropyMap ); - - if ( json.sheenColorMap !== undefined ) material.sheenColorMap = getTexture( json.sheenColorMap ); - if ( json.sheenRoughnessMap !== undefined ) material.sheenRoughnessMap = getTexture( json.sheenRoughnessMap ); - - return material; - - } - - setTextures( value ) { - - this.textures = value; - return this; - - } - - static createMaterialFromType( type ) { - - const materialLib = { - ShadowMaterial, - SpriteMaterial, - RawShaderMaterial, - ShaderMaterial, - PointsMaterial, - MeshPhysicalMaterial, - MeshStandardMaterial, - MeshPhongMaterial, - MeshToonMaterial, - MeshNormalMaterial, - MeshLambertMaterial, - MeshDepthMaterial, - MeshDistanceMaterial, - MeshBasicMaterial, - MeshMatcapMaterial, - LineDashedMaterial, - LineBasicMaterial, - Material - }; - - return new materialLib[ type ](); - - } - - } - - class LoaderUtils { - - static decodeText( array ) { - - if ( typeof TextDecoder !== 'undefined' ) { - - return new TextDecoder().decode( array ); - - } - - // Avoid the String.fromCharCode.apply(null, array) shortcut, which - // throws a "maximum call stack size exceeded" error for large arrays. - - let s = ''; - - for ( let i = 0, il = array.length; i < il; i ++ ) { - - // Implicitly assumes little-endian. - s += String.fromCharCode( array[ i ] ); - - } - - try { - - // merges multi-byte utf-8 characters. - - return decodeURIComponent( escape( s ) ); - - } catch ( e ) { // see #16358 - - return s; - - } - - } - - static extractUrlBase( url ) { - - const index = url.lastIndexOf( '/' ); - - if ( index === - 1 ) return './'; - - return url.slice( 0, index + 1 ); - - } - - static resolveURL( url, path ) { - - // Invalid URL - if ( typeof url !== 'string' || url === '' ) return ''; - - // Host Relative URL - if ( /^https?:\/\//i.test( path ) && /^\//.test( url ) ) { - - path = path.replace( /(^https?:\/\/[^\/]+).*/i, '$1' ); - - } - - // Absolute URL http://,https://,// - if ( /^(https?:)?\/\//i.test( url ) ) return url; - - // Data URI - if ( /^data:.*,.*$/i.test( url ) ) return url; - - // Blob URL - if ( /^blob:.*$/i.test( url ) ) return url; - - // Relative URL - return path + url; - - } - - } - - class InstancedBufferGeometry extends BufferGeometry { - - constructor() { - - super(); - - this.isInstancedBufferGeometry = true; - - this.type = 'InstancedBufferGeometry'; - this.instanceCount = Infinity; - - } - - copy( source ) { - - super.copy( source ); - - this.instanceCount = source.instanceCount; - - return this; - - } - - toJSON() { - - const data = super.toJSON(); - - data.instanceCount = this.instanceCount; - - data.isInstancedBufferGeometry = true; - - return data; - - } - - } - - class BufferGeometryLoader extends Loader { - - constructor( manager ) { - - super( manager ); - - } - - load( url, onLoad, onProgress, onError ) { - - const scope = this; - - const loader = new FileLoader( scope.manager ); - loader.setPath( scope.path ); - loader.setRequestHeader( scope.requestHeader ); - loader.setWithCredentials( scope.withCredentials ); - loader.load( url, function ( text ) { - - try { - - onLoad( scope.parse( JSON.parse( text ) ) ); - - } catch ( e ) { - - if ( onError ) { - - onError( e ); - - } else { - - console.error( e ); - - } - - scope.manager.itemError( url ); - - } - - }, onProgress, onError ); - - } - - parse( json ) { - - const interleavedBufferMap = {}; - const arrayBufferMap = {}; - - function getInterleavedBuffer( json, uuid ) { - - if ( interleavedBufferMap[ uuid ] !== undefined ) return interleavedBufferMap[ uuid ]; - - const interleavedBuffers = json.interleavedBuffers; - const interleavedBuffer = interleavedBuffers[ uuid ]; - - const buffer = getArrayBuffer( json, interleavedBuffer.buffer ); - - const array = getTypedArray( interleavedBuffer.type, buffer ); - const ib = new InterleavedBuffer( array, interleavedBuffer.stride ); - ib.uuid = interleavedBuffer.uuid; - - interleavedBufferMap[ uuid ] = ib; - - return ib; - - } - - function getArrayBuffer( json, uuid ) { - - if ( arrayBufferMap[ uuid ] !== undefined ) return arrayBufferMap[ uuid ]; - - const arrayBuffers = json.arrayBuffers; - const arrayBuffer = arrayBuffers[ uuid ]; - - const ab = new Uint32Array( arrayBuffer ).buffer; - - arrayBufferMap[ uuid ] = ab; - - return ab; - - } - - const geometry = json.isInstancedBufferGeometry ? new InstancedBufferGeometry() : new BufferGeometry(); - - const index = json.data.index; - - if ( index !== undefined ) { - - const typedArray = getTypedArray( index.type, index.array ); - geometry.setIndex( new BufferAttribute( typedArray, 1 ) ); - - } - - const attributes = json.data.attributes; - - for ( const key in attributes ) { - - const attribute = attributes[ key ]; - let bufferAttribute; - - if ( attribute.isInterleavedBufferAttribute ) { - - const interleavedBuffer = getInterleavedBuffer( json.data, attribute.data ); - bufferAttribute = new InterleavedBufferAttribute( interleavedBuffer, attribute.itemSize, attribute.offset, attribute.normalized ); - - } else { - - const typedArray = getTypedArray( attribute.type, attribute.array ); - const bufferAttributeConstr = attribute.isInstancedBufferAttribute ? InstancedBufferAttribute : BufferAttribute; - bufferAttribute = new bufferAttributeConstr( typedArray, attribute.itemSize, attribute.normalized ); - - } - - if ( attribute.name !== undefined ) bufferAttribute.name = attribute.name; - if ( attribute.usage !== undefined ) bufferAttribute.setUsage( attribute.usage ); - - if ( attribute.updateRange !== undefined ) { - - bufferAttribute.updateRange.offset = attribute.updateRange.offset; - bufferAttribute.updateRange.count = attribute.updateRange.count; - - } - - geometry.setAttribute( key, bufferAttribute ); - - } - - const morphAttributes = json.data.morphAttributes; - - if ( morphAttributes ) { - - for ( const key in morphAttributes ) { - - const attributeArray = morphAttributes[ key ]; - - const array = []; - - for ( let i = 0, il = attributeArray.length; i < il; i ++ ) { - - const attribute = attributeArray[ i ]; - let bufferAttribute; - - if ( attribute.isInterleavedBufferAttribute ) { - - const interleavedBuffer = getInterleavedBuffer( json.data, attribute.data ); - bufferAttribute = new InterleavedBufferAttribute( interleavedBuffer, attribute.itemSize, attribute.offset, attribute.normalized ); - - } else { - - const typedArray = getTypedArray( attribute.type, attribute.array ); - bufferAttribute = new BufferAttribute( typedArray, attribute.itemSize, attribute.normalized ); - - } - - if ( attribute.name !== undefined ) bufferAttribute.name = attribute.name; - array.push( bufferAttribute ); - - } - - geometry.morphAttributes[ key ] = array; - - } - - } - - const morphTargetsRelative = json.data.morphTargetsRelative; - - if ( morphTargetsRelative ) { - - geometry.morphTargetsRelative = true; - - } - - const groups = json.data.groups || json.data.drawcalls || json.data.offsets; - - if ( groups !== undefined ) { - - for ( let i = 0, n = groups.length; i !== n; ++ i ) { - - const group = groups[ i ]; - - geometry.addGroup( group.start, group.count, group.materialIndex ); - - } - - } - - const boundingSphere = json.data.boundingSphere; - - if ( boundingSphere !== undefined ) { - - const center = new Vector3(); - - if ( boundingSphere.center !== undefined ) { - - center.fromArray( boundingSphere.center ); - - } - - geometry.boundingSphere = new Sphere( center, boundingSphere.radius ); - - } - - if ( json.name ) geometry.name = json.name; - if ( json.userData ) geometry.userData = json.userData; - - return geometry; - - } - - } - - class ObjectLoader extends Loader { - - constructor( manager ) { - - super( manager ); - - } - - load( url, onLoad, onProgress, onError ) { - - const scope = this; - - const path = ( this.path === '' ) ? LoaderUtils.extractUrlBase( url ) : this.path; - this.resourcePath = this.resourcePath || path; - - const loader = new FileLoader( this.manager ); - loader.setPath( this.path ); - loader.setRequestHeader( this.requestHeader ); - loader.setWithCredentials( this.withCredentials ); - loader.load( url, function ( text ) { - - let json = null; - - try { - - json = JSON.parse( text ); - - } catch ( error ) { - - if ( onError !== undefined ) onError( error ); - - console.error( 'THREE:ObjectLoader: Can\'t parse ' + url + '.', error.message ); - - return; - - } - - const metadata = json.metadata; - - if ( metadata === undefined || metadata.type === undefined || metadata.type.toLowerCase() === 'geometry' ) { - - if ( onError !== undefined ) onError( new Error( 'THREE.ObjectLoader: Can\'t load ' + url ) ); - - console.error( 'THREE.ObjectLoader: Can\'t load ' + url ); - return; - - } - - scope.parse( json, onLoad ); - - }, onProgress, onError ); - - } - - async loadAsync( url, onProgress ) { - - const scope = this; - - const path = ( this.path === '' ) ? LoaderUtils.extractUrlBase( url ) : this.path; - this.resourcePath = this.resourcePath || path; - - const loader = new FileLoader( this.manager ); - loader.setPath( this.path ); - loader.setRequestHeader( this.requestHeader ); - loader.setWithCredentials( this.withCredentials ); - - const text = await loader.loadAsync( url, onProgress ); - - const json = JSON.parse( text ); - - const metadata = json.metadata; - - if ( metadata === undefined || metadata.type === undefined || metadata.type.toLowerCase() === 'geometry' ) { - - throw new Error( 'THREE.ObjectLoader: Can\'t load ' + url ); - - } - - return await scope.parseAsync( json ); - - } - - parse( json, onLoad ) { - - const animations = this.parseAnimations( json.animations ); - const shapes = this.parseShapes( json.shapes ); - const geometries = this.parseGeometries( json.geometries, shapes ); - - const images = this.parseImages( json.images, function () { - - if ( onLoad !== undefined ) onLoad( object ); - - } ); - - const textures = this.parseTextures( json.textures, images ); - const materials = this.parseMaterials( json.materials, textures ); - - const object = this.parseObject( json.object, geometries, materials, textures, animations ); - const skeletons = this.parseSkeletons( json.skeletons, object ); - - this.bindSkeletons( object, skeletons ); - - // - - if ( onLoad !== undefined ) { - - let hasImages = false; - - for ( const uuid in images ) { - - if ( images[ uuid ].data instanceof HTMLImageElement ) { - - hasImages = true; - break; - - } - - } - - if ( hasImages === false ) onLoad( object ); - - } - - return object; - - } - - async parseAsync( json ) { - - const animations = this.parseAnimations( json.animations ); - const shapes = this.parseShapes( json.shapes ); - const geometries = this.parseGeometries( json.geometries, shapes ); - - const images = await this.parseImagesAsync( json.images ); - - const textures = this.parseTextures( json.textures, images ); - const materials = this.parseMaterials( json.materials, textures ); - - const object = this.parseObject( json.object, geometries, materials, textures, animations ); - const skeletons = this.parseSkeletons( json.skeletons, object ); - - this.bindSkeletons( object, skeletons ); - - return object; - - } - - parseShapes( json ) { - - const shapes = {}; - - if ( json !== undefined ) { - - for ( let i = 0, l = json.length; i < l; i ++ ) { - - const shape = new Shape().fromJSON( json[ i ] ); - - shapes[ shape.uuid ] = shape; - - } - - } - - return shapes; - - } - - parseSkeletons( json, object ) { - - const skeletons = {}; - const bones = {}; - - // generate bone lookup table - - object.traverse( function ( child ) { - - if ( child.isBone ) bones[ child.uuid ] = child; - - } ); - - // create skeletons - - if ( json !== undefined ) { - - for ( let i = 0, l = json.length; i < l; i ++ ) { - - const skeleton = new Skeleton().fromJSON( json[ i ], bones ); - - skeletons[ skeleton.uuid ] = skeleton; - - } - - } - - return skeletons; - - } - - parseGeometries( json, shapes ) { - - const geometries = {}; - - if ( json !== undefined ) { - - const bufferGeometryLoader = new BufferGeometryLoader(); - - for ( let i = 0, l = json.length; i < l; i ++ ) { - - let geometry; - const data = json[ i ]; - - switch ( data.type ) { - - case 'BufferGeometry': - case 'InstancedBufferGeometry': - - geometry = bufferGeometryLoader.parse( data ); - break; - - default: - - if ( data.type in Geometries ) { - - geometry = Geometries[ data.type ].fromJSON( data, shapes ); - - } else { - - console.warn( `THREE.ObjectLoader: Unsupported geometry type "${ data.type }"` ); - - } - - } - - geometry.uuid = data.uuid; - - if ( data.name !== undefined ) geometry.name = data.name; - if ( data.userData !== undefined ) geometry.userData = data.userData; - - geometries[ data.uuid ] = geometry; - - } - - } - - return geometries; - - } - - parseMaterials( json, textures ) { - - const cache = {}; // MultiMaterial - const materials = {}; - - if ( json !== undefined ) { - - const loader = new MaterialLoader(); - loader.setTextures( textures ); - - for ( let i = 0, l = json.length; i < l; i ++ ) { - - const data = json[ i ]; - - if ( cache[ data.uuid ] === undefined ) { - - cache[ data.uuid ] = loader.parse( data ); - - } - - materials[ data.uuid ] = cache[ data.uuid ]; - - } - - } - - return materials; - - } - - parseAnimations( json ) { - - const animations = {}; - - if ( json !== undefined ) { - - for ( let i = 0; i < json.length; i ++ ) { - - const data = json[ i ]; - - const clip = AnimationClip.parse( data ); - - animations[ clip.uuid ] = clip; - - } - - } - - return animations; - - } - - parseImages( json, onLoad ) { - - const scope = this; - const images = {}; - - let loader; - - function loadImage( url ) { - - scope.manager.itemStart( url ); - - return loader.load( url, function () { - - scope.manager.itemEnd( url ); - - }, undefined, function () { - - scope.manager.itemError( url ); - scope.manager.itemEnd( url ); - - } ); - - } - - function deserializeImage( image ) { - - if ( typeof image === 'string' ) { - - const url = image; - - const path = /^(\/\/)|([a-z]+:(\/\/)?)/i.test( url ) ? url : scope.resourcePath + url; - - return loadImage( path ); - - } else { - - if ( image.data ) { - - return { - data: getTypedArray( image.type, image.data ), - width: image.width, - height: image.height - }; - - } else { - - return null; - - } - - } - - } - - if ( json !== undefined && json.length > 0 ) { - - const manager = new LoadingManager( onLoad ); - - loader = new ImageLoader( manager ); - loader.setCrossOrigin( this.crossOrigin ); - - for ( let i = 0, il = json.length; i < il; i ++ ) { - - const image = json[ i ]; - const url = image.url; - - if ( Array.isArray( url ) ) { - - // load array of images e.g CubeTexture - - const imageArray = []; - - for ( let j = 0, jl = url.length; j < jl; j ++ ) { - - const currentUrl = url[ j ]; - - const deserializedImage = deserializeImage( currentUrl ); - - if ( deserializedImage !== null ) { - - if ( deserializedImage instanceof HTMLImageElement ) { - - imageArray.push( deserializedImage ); - - } else { - - // special case: handle array of data textures for cube textures - - imageArray.push( new DataTexture( deserializedImage.data, deserializedImage.width, deserializedImage.height ) ); - - } - - } - - } - - images[ image.uuid ] = new Source( imageArray ); - - } else { - - // load single image - - const deserializedImage = deserializeImage( image.url ); - images[ image.uuid ] = new Source( deserializedImage ); - - - } - - } - - } - - return images; - - } - - async parseImagesAsync( json ) { - - const scope = this; - const images = {}; - - let loader; - - async function deserializeImage( image ) { - - if ( typeof image === 'string' ) { - - const url = image; - - const path = /^(\/\/)|([a-z]+:(\/\/)?)/i.test( url ) ? url : scope.resourcePath + url; - - return await loader.loadAsync( path ); - - } else { - - if ( image.data ) { - - return { - data: getTypedArray( image.type, image.data ), - width: image.width, - height: image.height - }; - - } else { - - return null; - - } - - } - - } - - if ( json !== undefined && json.length > 0 ) { - - loader = new ImageLoader( this.manager ); - loader.setCrossOrigin( this.crossOrigin ); - - for ( let i = 0, il = json.length; i < il; i ++ ) { - - const image = json[ i ]; - const url = image.url; - - if ( Array.isArray( url ) ) { - - // load array of images e.g CubeTexture - - const imageArray = []; - - for ( let j = 0, jl = url.length; j < jl; j ++ ) { - - const currentUrl = url[ j ]; - - const deserializedImage = await deserializeImage( currentUrl ); - - if ( deserializedImage !== null ) { - - if ( deserializedImage instanceof HTMLImageElement ) { - - imageArray.push( deserializedImage ); - - } else { - - // special case: handle array of data textures for cube textures - - imageArray.push( new DataTexture( deserializedImage.data, deserializedImage.width, deserializedImage.height ) ); - - } - - } - - } - - images[ image.uuid ] = new Source( imageArray ); - - } else { - - // load single image - - const deserializedImage = await deserializeImage( image.url ); - images[ image.uuid ] = new Source( deserializedImage ); - - } - - } - - } - - return images; - - } - - parseTextures( json, images ) { - - function parseConstant( value, type ) { - - if ( typeof value === 'number' ) return value; - - console.warn( 'THREE.ObjectLoader.parseTexture: Constant should be in numeric form.', value ); - - return type[ value ]; - - } - - const textures = {}; - - if ( json !== undefined ) { - - for ( let i = 0, l = json.length; i < l; i ++ ) { - - const data = json[ i ]; - - if ( data.image === undefined ) { - - console.warn( 'THREE.ObjectLoader: No "image" specified for', data.uuid ); - - } - - if ( images[ data.image ] === undefined ) { - - console.warn( 'THREE.ObjectLoader: Undefined image', data.image ); - - } - - const source = images[ data.image ]; - const image = source.data; - - let texture; - - if ( Array.isArray( image ) ) { - - texture = new CubeTexture(); - - if ( image.length === 6 ) texture.needsUpdate = true; - - } else { - - if ( image && image.data ) { - - texture = new DataTexture(); - - } else { - - texture = new Texture(); - - } - - if ( image ) texture.needsUpdate = true; // textures can have undefined image data - - } - - texture.source = source; - - texture.uuid = data.uuid; - - if ( data.name !== undefined ) texture.name = data.name; - - if ( data.mapping !== undefined ) texture.mapping = parseConstant( data.mapping, TEXTURE_MAPPING ); - if ( data.channel !== undefined ) texture.channel = data.channel; - - if ( data.offset !== undefined ) texture.offset.fromArray( data.offset ); - if ( data.repeat !== undefined ) texture.repeat.fromArray( data.repeat ); - if ( data.center !== undefined ) texture.center.fromArray( data.center ); - if ( data.rotation !== undefined ) texture.rotation = data.rotation; - - if ( data.wrap !== undefined ) { - - texture.wrapS = parseConstant( data.wrap[ 0 ], TEXTURE_WRAPPING ); - texture.wrapT = parseConstant( data.wrap[ 1 ], TEXTURE_WRAPPING ); - - } - - if ( data.format !== undefined ) texture.format = data.format; - if ( data.internalFormat !== undefined ) texture.internalFormat = data.internalFormat; - if ( data.type !== undefined ) texture.type = data.type; - if ( data.colorSpace !== undefined ) texture.colorSpace = data.colorSpace; - if ( data.encoding !== undefined ) texture.encoding = data.encoding; // @deprecated, r152 - - if ( data.minFilter !== undefined ) texture.minFilter = parseConstant( data.minFilter, TEXTURE_FILTER ); - if ( data.magFilter !== undefined ) texture.magFilter = parseConstant( data.magFilter, TEXTURE_FILTER ); - if ( data.anisotropy !== undefined ) texture.anisotropy = data.anisotropy; - - if ( data.flipY !== undefined ) texture.flipY = data.flipY; - - if ( data.generateMipmaps !== undefined ) texture.generateMipmaps = data.generateMipmaps; - if ( data.premultiplyAlpha !== undefined ) texture.premultiplyAlpha = data.premultiplyAlpha; - if ( data.unpackAlignment !== undefined ) texture.unpackAlignment = data.unpackAlignment; - if ( data.compareFunction !== undefined ) texture.compareFunction = data.compareFunction; - - if ( data.userData !== undefined ) texture.userData = data.userData; - - textures[ data.uuid ] = texture; - - } - - } - - return textures; - - } - - parseObject( data, geometries, materials, textures, animations ) { - - let object; - - function getGeometry( name ) { - - if ( geometries[ name ] === undefined ) { - - console.warn( 'THREE.ObjectLoader: Undefined geometry', name ); - - } - - return geometries[ name ]; - - } - - function getMaterial( name ) { - - if ( name === undefined ) return undefined; - - if ( Array.isArray( name ) ) { - - const array = []; - - for ( let i = 0, l = name.length; i < l; i ++ ) { - - const uuid = name[ i ]; - - if ( materials[ uuid ] === undefined ) { - - console.warn( 'THREE.ObjectLoader: Undefined material', uuid ); - - } - - array.push( materials[ uuid ] ); - - } - - return array; - - } - - if ( materials[ name ] === undefined ) { - - console.warn( 'THREE.ObjectLoader: Undefined material', name ); - - } - - return materials[ name ]; - - } - - function getTexture( uuid ) { - - if ( textures[ uuid ] === undefined ) { - - console.warn( 'THREE.ObjectLoader: Undefined texture', uuid ); - - } - - return textures[ uuid ]; - - } - - let geometry, material; - - switch ( data.type ) { - - case 'Scene': - - object = new Scene(); - - if ( data.background !== undefined ) { - - if ( Number.isInteger( data.background ) ) { - - object.background = new Color( data.background ); - - } else { - - object.background = getTexture( data.background ); - - } - - } - - if ( data.environment !== undefined ) { - - object.environment = getTexture( data.environment ); - - } - - if ( data.fog !== undefined ) { - - if ( data.fog.type === 'Fog' ) { - - object.fog = new Fog( data.fog.color, data.fog.near, data.fog.far ); - - } else if ( data.fog.type === 'FogExp2' ) { - - object.fog = new FogExp2( data.fog.color, data.fog.density ); - - } - - } - - if ( data.backgroundBlurriness !== undefined ) object.backgroundBlurriness = data.backgroundBlurriness; - if ( data.backgroundIntensity !== undefined ) object.backgroundIntensity = data.backgroundIntensity; - - break; - - case 'PerspectiveCamera': - - object = new PerspectiveCamera( data.fov, data.aspect, data.near, data.far ); - - if ( data.focus !== undefined ) object.focus = data.focus; - if ( data.zoom !== undefined ) object.zoom = data.zoom; - if ( data.filmGauge !== undefined ) object.filmGauge = data.filmGauge; - if ( data.filmOffset !== undefined ) object.filmOffset = data.filmOffset; - if ( data.view !== undefined ) object.view = Object.assign( {}, data.view ); - - break; - - case 'OrthographicCamera': - - object = new OrthographicCamera( data.left, data.right, data.top, data.bottom, data.near, data.far ); - - if ( data.zoom !== undefined ) object.zoom = data.zoom; - if ( data.view !== undefined ) object.view = Object.assign( {}, data.view ); - - break; - - case 'AmbientLight': - - object = new AmbientLight( data.color, data.intensity ); - - break; - - case 'DirectionalLight': - - object = new DirectionalLight( data.color, data.intensity ); - - break; - - case 'PointLight': - - object = new PointLight( data.color, data.intensity, data.distance, data.decay ); - - break; - - case 'RectAreaLight': - - object = new RectAreaLight( data.color, data.intensity, data.width, data.height ); - - break; - - case 'SpotLight': - - object = new SpotLight( data.color, data.intensity, data.distance, data.angle, data.penumbra, data.decay ); - - break; - - case 'HemisphereLight': - - object = new HemisphereLight( data.color, data.groundColor, data.intensity ); - - break; - - case 'LightProbe': - - object = new LightProbe().fromJSON( data ); - - break; - - case 'SkinnedMesh': - - geometry = getGeometry( data.geometry ); - material = getMaterial( data.material ); - - object = new SkinnedMesh( geometry, material ); - - if ( data.bindMode !== undefined ) object.bindMode = data.bindMode; - if ( data.bindMatrix !== undefined ) object.bindMatrix.fromArray( data.bindMatrix ); - if ( data.skeleton !== undefined ) object.skeleton = data.skeleton; - - break; - - case 'Mesh': - - geometry = getGeometry( data.geometry ); - material = getMaterial( data.material ); - - object = new Mesh( geometry, material ); - - break; - - case 'InstancedMesh': - - geometry = getGeometry( data.geometry ); - material = getMaterial( data.material ); - const count = data.count; - const instanceMatrix = data.instanceMatrix; - const instanceColor = data.instanceColor; - - object = new InstancedMesh( geometry, material, count ); - object.instanceMatrix = new InstancedBufferAttribute( new Float32Array( instanceMatrix.array ), 16 ); - if ( instanceColor !== undefined ) object.instanceColor = new InstancedBufferAttribute( new Float32Array( instanceColor.array ), instanceColor.itemSize ); - - break; - - case 'LOD': - - object = new LOD(); - - break; - - case 'Line': - - object = new Line( getGeometry( data.geometry ), getMaterial( data.material ) ); - - break; - - case 'LineLoop': - - object = new LineLoop( getGeometry( data.geometry ), getMaterial( data.material ) ); - - break; - - case 'LineSegments': - - object = new LineSegments( getGeometry( data.geometry ), getMaterial( data.material ) ); - - break; - - case 'PointCloud': - case 'Points': - - object = new Points( getGeometry( data.geometry ), getMaterial( data.material ) ); - - break; - - case 'Sprite': - - object = new Sprite( getMaterial( data.material ) ); - - break; - - case 'Group': - - object = new Group(); - - break; - - case 'Bone': - - object = new Bone(); - - break; - - default: - - object = new Object3D(); - - } - - object.uuid = data.uuid; - - if ( data.name !== undefined ) object.name = data.name; - - if ( data.matrix !== undefined ) { - - object.matrix.fromArray( data.matrix ); - - if ( data.matrixAutoUpdate !== undefined ) object.matrixAutoUpdate = data.matrixAutoUpdate; - if ( object.matrixAutoUpdate ) object.matrix.decompose( object.position, object.quaternion, object.scale ); - - } else { - - if ( data.position !== undefined ) object.position.fromArray( data.position ); - if ( data.rotation !== undefined ) object.rotation.fromArray( data.rotation ); - if ( data.quaternion !== undefined ) object.quaternion.fromArray( data.quaternion ); - if ( data.scale !== undefined ) object.scale.fromArray( data.scale ); - - } - - if ( data.up !== undefined ) object.up.fromArray( data.up ); - - if ( data.castShadow !== undefined ) object.castShadow = data.castShadow; - if ( data.receiveShadow !== undefined ) object.receiveShadow = data.receiveShadow; - - if ( data.shadow ) { - - if ( data.shadow.bias !== undefined ) object.shadow.bias = data.shadow.bias; - if ( data.shadow.normalBias !== undefined ) object.shadow.normalBias = data.shadow.normalBias; - if ( data.shadow.radius !== undefined ) object.shadow.radius = data.shadow.radius; - if ( data.shadow.mapSize !== undefined ) object.shadow.mapSize.fromArray( data.shadow.mapSize ); - if ( data.shadow.camera !== undefined ) object.shadow.camera = this.parseObject( data.shadow.camera ); - - } - - if ( data.visible !== undefined ) object.visible = data.visible; - if ( data.frustumCulled !== undefined ) object.frustumCulled = data.frustumCulled; - if ( data.renderOrder !== undefined ) object.renderOrder = data.renderOrder; - if ( data.userData !== undefined ) object.userData = data.userData; - if ( data.layers !== undefined ) object.layers.mask = data.layers; - - if ( data.children !== undefined ) { - - const children = data.children; - - for ( let i = 0; i < children.length; i ++ ) { - - object.add( this.parseObject( children[ i ], geometries, materials, textures, animations ) ); - - } - - } - - if ( data.animations !== undefined ) { - - const objectAnimations = data.animations; - - for ( let i = 0; i < objectAnimations.length; i ++ ) { - - const uuid = objectAnimations[ i ]; - - object.animations.push( animations[ uuid ] ); - - } - - } - - if ( data.type === 'LOD' ) { - - if ( data.autoUpdate !== undefined ) object.autoUpdate = data.autoUpdate; - - const levels = data.levels; - - for ( let l = 0; l < levels.length; l ++ ) { - - const level = levels[ l ]; - const child = object.getObjectByProperty( 'uuid', level.object ); - - if ( child !== undefined ) { - - object.addLevel( child, level.distance, level.hysteresis ); - - } - - } - - } - - return object; - - } - - bindSkeletons( object, skeletons ) { - - if ( Object.keys( skeletons ).length === 0 ) return; - - object.traverse( function ( child ) { - - if ( child.isSkinnedMesh === true && child.skeleton !== undefined ) { - - const skeleton = skeletons[ child.skeleton ]; - - if ( skeleton === undefined ) { - - console.warn( 'THREE.ObjectLoader: No skeleton found with UUID:', child.skeleton ); - - } else { - - child.bind( skeleton, child.bindMatrix ); - - } - - } - - } ); - - } - - } - - const TEXTURE_MAPPING = { - UVMapping: UVMapping, - CubeReflectionMapping: CubeReflectionMapping, - CubeRefractionMapping: CubeRefractionMapping, - EquirectangularReflectionMapping: EquirectangularReflectionMapping, - EquirectangularRefractionMapping: EquirectangularRefractionMapping, - CubeUVReflectionMapping: CubeUVReflectionMapping - }; - - const TEXTURE_WRAPPING = { - RepeatWrapping: RepeatWrapping, - ClampToEdgeWrapping: ClampToEdgeWrapping, - MirroredRepeatWrapping: MirroredRepeatWrapping - }; - - const TEXTURE_FILTER = { - NearestFilter: NearestFilter, - NearestMipmapNearestFilter: NearestMipmapNearestFilter, - NearestMipmapLinearFilter: NearestMipmapLinearFilter, - LinearFilter: LinearFilter, - LinearMipmapNearestFilter: LinearMipmapNearestFilter, - LinearMipmapLinearFilter: LinearMipmapLinearFilter - }; - - class ImageBitmapLoader extends Loader { - - constructor( manager ) { - - super( manager ); - - this.isImageBitmapLoader = true; - - if ( typeof createImageBitmap === 'undefined' ) { - - console.warn( 'THREE.ImageBitmapLoader: createImageBitmap() not supported.' ); - - } - - if ( typeof fetch === 'undefined' ) { - - console.warn( 'THREE.ImageBitmapLoader: fetch() not supported.' ); - - } - - this.options = { premultiplyAlpha: 'none' }; - - } - - setOptions( options ) { - - this.options = options; - - return this; - - } - - load( url, onLoad, onProgress, onError ) { - - if ( url === undefined ) url = ''; - - if ( this.path !== undefined ) url = this.path + url; - - url = this.manager.resolveURL( url ); - - const scope = this; - - const cached = Cache.get( url ); - - if ( cached !== undefined ) { - - scope.manager.itemStart( url ); - - setTimeout( function () { - - if ( onLoad ) onLoad( cached ); - - scope.manager.itemEnd( url ); - - }, 0 ); - - return cached; - - } - - const fetchOptions = {}; - fetchOptions.credentials = ( this.crossOrigin === 'anonymous' ) ? 'same-origin' : 'include'; - fetchOptions.headers = this.requestHeader; - - fetch( url, fetchOptions ).then( function ( res ) { - - return res.blob(); - - } ).then( function ( blob ) { - - return createImageBitmap( blob, Object.assign( scope.options, { colorSpaceConversion: 'none' } ) ); - - } ).then( function ( imageBitmap ) { - - Cache.add( url, imageBitmap ); - - if ( onLoad ) onLoad( imageBitmap ); - - scope.manager.itemEnd( url ); - - } ).catch( function ( e ) { - - if ( onError ) onError( e ); - - scope.manager.itemError( url ); - scope.manager.itemEnd( url ); - - } ); - - scope.manager.itemStart( url ); - - } - - } - - let _context; - - class AudioContext { - - static getContext() { - - if ( _context === undefined ) { - - _context = new ( window.AudioContext || window.webkitAudioContext )(); - - } - - return _context; - - } - - static setContext( value ) { - - _context = value; - - } - - } - - class AudioLoader extends Loader { - - constructor( manager ) { - - super( manager ); - - } - - load( url, onLoad, onProgress, onError ) { - - const scope = this; - - const loader = new FileLoader( this.manager ); - loader.setResponseType( 'arraybuffer' ); - loader.setPath( this.path ); - loader.setRequestHeader( this.requestHeader ); - loader.setWithCredentials( this.withCredentials ); - loader.load( url, function ( buffer ) { - - try { - - // Create a copy of the buffer. The `decodeAudioData` method - // detaches the buffer when complete, preventing reuse. - const bufferCopy = buffer.slice( 0 ); - - const context = AudioContext.getContext(); - context.decodeAudioData( bufferCopy, function ( audioBuffer ) { - - onLoad( audioBuffer ); - - }, handleError ); - - } catch ( e ) { - - handleError( e ); - - } - - }, onProgress, onError ); - - function handleError( e ) { - - if ( onError ) { - - onError( e ); - - } else { - - console.error( e ); - - } - - scope.manager.itemError( url ); - - } - - } - - } - - class HemisphereLightProbe extends LightProbe { - - constructor( skyColor, groundColor, intensity = 1 ) { - - super( undefined, intensity ); - - this.isHemisphereLightProbe = true; - - const color1 = new Color().set( skyColor ); - const color2 = new Color().set( groundColor ); - - const sky = new Vector3( color1.r, color1.g, color1.b ); - const ground = new Vector3( color2.r, color2.g, color2.b ); - - // without extra factor of PI in the shader, should = 1 / Math.sqrt( Math.PI ); - const c0 = Math.sqrt( Math.PI ); - const c1 = c0 * Math.sqrt( 0.75 ); - - this.sh.coefficients[ 0 ].copy( sky ).add( ground ).multiplyScalar( c0 ); - this.sh.coefficients[ 1 ].copy( sky ).sub( ground ).multiplyScalar( c1 ); - - } - - } - - class AmbientLightProbe extends LightProbe { - - constructor( color, intensity = 1 ) { - - super( undefined, intensity ); - - this.isAmbientLightProbe = true; - - const color1 = new Color().set( color ); - - // without extra factor of PI in the shader, would be 2 / Math.sqrt( Math.PI ); - this.sh.coefficients[ 0 ].set( color1.r, color1.g, color1.b ).multiplyScalar( 2 * Math.sqrt( Math.PI ) ); - - } - - } - - const _eyeRight = /*@__PURE__*/ new Matrix4(); - const _eyeLeft = /*@__PURE__*/ new Matrix4(); - const _projectionMatrix = /*@__PURE__*/ new Matrix4(); - - class StereoCamera { - - constructor() { - - this.type = 'StereoCamera'; - - this.aspect = 1; - - this.eyeSep = 0.064; - - this.cameraL = new PerspectiveCamera(); - this.cameraL.layers.enable( 1 ); - this.cameraL.matrixAutoUpdate = false; - - this.cameraR = new PerspectiveCamera(); - this.cameraR.layers.enable( 2 ); - this.cameraR.matrixAutoUpdate = false; - - this._cache = { - focus: null, - fov: null, - aspect: null, - near: null, - far: null, - zoom: null, - eyeSep: null - }; - - } - - update( camera ) { - - const cache = this._cache; - - const needsUpdate = cache.focus !== camera.focus || cache.fov !== camera.fov || - cache.aspect !== camera.aspect * this.aspect || cache.near !== camera.near || - cache.far !== camera.far || cache.zoom !== camera.zoom || cache.eyeSep !== this.eyeSep; - - if ( needsUpdate ) { - - cache.focus = camera.focus; - cache.fov = camera.fov; - cache.aspect = camera.aspect * this.aspect; - cache.near = camera.near; - cache.far = camera.far; - cache.zoom = camera.zoom; - cache.eyeSep = this.eyeSep; - - // Off-axis stereoscopic effect based on - // http://paulbourke.net/stereographics/stereorender/ - - _projectionMatrix.copy( camera.projectionMatrix ); - const eyeSepHalf = cache.eyeSep / 2; - const eyeSepOnProjection = eyeSepHalf * cache.near / cache.focus; - const ymax = ( cache.near * Math.tan( DEG2RAD * cache.fov * 0.5 ) ) / cache.zoom; - let xmin, xmax; - - // translate xOffset - - _eyeLeft.elements[ 12 ] = - eyeSepHalf; - _eyeRight.elements[ 12 ] = eyeSepHalf; - - // for left eye - - xmin = - ymax * cache.aspect + eyeSepOnProjection; - xmax = ymax * cache.aspect + eyeSepOnProjection; - - _projectionMatrix.elements[ 0 ] = 2 * cache.near / ( xmax - xmin ); - _projectionMatrix.elements[ 8 ] = ( xmax + xmin ) / ( xmax - xmin ); - - this.cameraL.projectionMatrix.copy( _projectionMatrix ); - - // for right eye - - xmin = - ymax * cache.aspect - eyeSepOnProjection; - xmax = ymax * cache.aspect - eyeSepOnProjection; - - _projectionMatrix.elements[ 0 ] = 2 * cache.near / ( xmax - xmin ); - _projectionMatrix.elements[ 8 ] = ( xmax + xmin ) / ( xmax - xmin ); - - this.cameraR.projectionMatrix.copy( _projectionMatrix ); - - } - - this.cameraL.matrixWorld.copy( camera.matrixWorld ).multiply( _eyeLeft ); - this.cameraR.matrixWorld.copy( camera.matrixWorld ).multiply( _eyeRight ); - - } - - } - - class Clock { - - constructor( autoStart = true ) { - - this.autoStart = autoStart; - - this.startTime = 0; - this.oldTime = 0; - this.elapsedTime = 0; - - this.running = false; - - } - - start() { - - this.startTime = now(); - - this.oldTime = this.startTime; - this.elapsedTime = 0; - this.running = true; - - } - - stop() { - - this.getElapsedTime(); - this.running = false; - this.autoStart = false; - - } - - getElapsedTime() { - - this.getDelta(); - return this.elapsedTime; - - } - - getDelta() { - - let diff = 0; - - if ( this.autoStart && ! this.running ) { - - this.start(); - return 0; - - } - - if ( this.running ) { - - const newTime = now(); - - diff = ( newTime - this.oldTime ) / 1000; - this.oldTime = newTime; - - this.elapsedTime += diff; - - } - - return diff; - - } - - } - - function now() { - - return ( typeof performance === 'undefined' ? Date : performance ).now(); // see #10732 - - } - - const _position$1 = /*@__PURE__*/ new Vector3(); - const _quaternion$1 = /*@__PURE__*/ new Quaternion(); - const _scale$1 = /*@__PURE__*/ new Vector3(); - const _orientation$1 = /*@__PURE__*/ new Vector3(); - - class AudioListener extends Object3D { - - constructor() { - - super(); - - this.type = 'AudioListener'; - - this.context = AudioContext.getContext(); - - this.gain = this.context.createGain(); - this.gain.connect( this.context.destination ); - - this.filter = null; - - this.timeDelta = 0; - - // private - - this._clock = new Clock(); - - } - - getInput() { - - return this.gain; - - } - - removeFilter() { - - if ( this.filter !== null ) { - - this.gain.disconnect( this.filter ); - this.filter.disconnect( this.context.destination ); - this.gain.connect( this.context.destination ); - this.filter = null; - - } - - return this; - - } - - getFilter() { - - return this.filter; - - } - - setFilter( value ) { - - if ( this.filter !== null ) { - - this.gain.disconnect( this.filter ); - this.filter.disconnect( this.context.destination ); - - } else { - - this.gain.disconnect( this.context.destination ); - - } - - this.filter = value; - this.gain.connect( this.filter ); - this.filter.connect( this.context.destination ); - - return this; - - } - - getMasterVolume() { - - return this.gain.gain.value; - - } - - setMasterVolume( value ) { - - this.gain.gain.setTargetAtTime( value, this.context.currentTime, 0.01 ); - - return this; - - } - - updateMatrixWorld( force ) { - - super.updateMatrixWorld( force ); - - const listener = this.context.listener; - const up = this.up; - - this.timeDelta = this._clock.getDelta(); - - this.matrixWorld.decompose( _position$1, _quaternion$1, _scale$1 ); - - _orientation$1.set( 0, 0, - 1 ).applyQuaternion( _quaternion$1 ); - - if ( listener.positionX ) { - - // code path for Chrome (see #14393) - - const endTime = this.context.currentTime + this.timeDelta; - - listener.positionX.linearRampToValueAtTime( _position$1.x, endTime ); - listener.positionY.linearRampToValueAtTime( _position$1.y, endTime ); - listener.positionZ.linearRampToValueAtTime( _position$1.z, endTime ); - listener.forwardX.linearRampToValueAtTime( _orientation$1.x, endTime ); - listener.forwardY.linearRampToValueAtTime( _orientation$1.y, endTime ); - listener.forwardZ.linearRampToValueAtTime( _orientation$1.z, endTime ); - listener.upX.linearRampToValueAtTime( up.x, endTime ); - listener.upY.linearRampToValueAtTime( up.y, endTime ); - listener.upZ.linearRampToValueAtTime( up.z, endTime ); - - } else { - - listener.setPosition( _position$1.x, _position$1.y, _position$1.z ); - listener.setOrientation( _orientation$1.x, _orientation$1.y, _orientation$1.z, up.x, up.y, up.z ); - - } - - } - - } - - class Audio extends Object3D { - - constructor( listener ) { - - super(); - - this.type = 'Audio'; - - this.listener = listener; - this.context = listener.context; - - this.gain = this.context.createGain(); - this.gain.connect( listener.getInput() ); - - this.autoplay = false; - - this.buffer = null; - this.detune = 0; - this.loop = false; - this.loopStart = 0; - this.loopEnd = 0; - this.offset = 0; - this.duration = undefined; - this.playbackRate = 1; - this.isPlaying = false; - this.hasPlaybackControl = true; - this.source = null; - this.sourceType = 'empty'; - - this._startedAt = 0; - this._progress = 0; - this._connected = false; - - this.filters = []; - - } - - getOutput() { - - return this.gain; - - } - - setNodeSource( audioNode ) { - - this.hasPlaybackControl = false; - this.sourceType = 'audioNode'; - this.source = audioNode; - this.connect(); - - return this; - - } - - setMediaElementSource( mediaElement ) { - - this.hasPlaybackControl = false; - this.sourceType = 'mediaNode'; - this.source = this.context.createMediaElementSource( mediaElement ); - this.connect(); - - return this; - - } - - setMediaStreamSource( mediaStream ) { - - this.hasPlaybackControl = false; - this.sourceType = 'mediaStreamNode'; - this.source = this.context.createMediaStreamSource( mediaStream ); - this.connect(); - - return this; - - } - - setBuffer( audioBuffer ) { - - this.buffer = audioBuffer; - this.sourceType = 'buffer'; - - if ( this.autoplay ) this.play(); - - return this; - - } - - play( delay = 0 ) { - - if ( this.isPlaying === true ) { - - console.warn( 'THREE.Audio: Audio is already playing.' ); - return; - - } - - if ( this.hasPlaybackControl === false ) { - - console.warn( 'THREE.Audio: this Audio has no playback control.' ); - return; - - } - - this._startedAt = this.context.currentTime + delay; - - const source = this.context.createBufferSource(); - source.buffer = this.buffer; - source.loop = this.loop; - source.loopStart = this.loopStart; - source.loopEnd = this.loopEnd; - source.onended = this.onEnded.bind( this ); - source.start( this._startedAt, this._progress + this.offset, this.duration ); - - this.isPlaying = true; - - this.source = source; - - this.setDetune( this.detune ); - this.setPlaybackRate( this.playbackRate ); - - return this.connect(); - - } - - pause() { - - if ( this.hasPlaybackControl === false ) { - - console.warn( 'THREE.Audio: this Audio has no playback control.' ); - return; - - } - - if ( this.isPlaying === true ) { - - // update current progress - - this._progress += Math.max( this.context.currentTime - this._startedAt, 0 ) * this.playbackRate; - - if ( this.loop === true ) { - - // ensure _progress does not exceed duration with looped audios - - this._progress = this._progress % ( this.duration || this.buffer.duration ); - - } - - this.source.stop(); - this.source.onended = null; - - this.isPlaying = false; - - } - - return this; - - } - - stop() { - - if ( this.hasPlaybackControl === false ) { - - console.warn( 'THREE.Audio: this Audio has no playback control.' ); - return; - - } - - this._progress = 0; - - if ( this.source !== null ) { - - this.source.stop(); - this.source.onended = null; - - } - - this.isPlaying = false; - - return this; - - } - - connect() { - - if ( this.filters.length > 0 ) { - - this.source.connect( this.filters[ 0 ] ); - - for ( let i = 1, l = this.filters.length; i < l; i ++ ) { - - this.filters[ i - 1 ].connect( this.filters[ i ] ); - - } - - this.filters[ this.filters.length - 1 ].connect( this.getOutput() ); - - } else { - - this.source.connect( this.getOutput() ); - - } - - this._connected = true; - - return this; - - } - - disconnect() { - - if ( this.filters.length > 0 ) { - - this.source.disconnect( this.filters[ 0 ] ); - - for ( let i = 1, l = this.filters.length; i < l; i ++ ) { - - this.filters[ i - 1 ].disconnect( this.filters[ i ] ); - - } - - this.filters[ this.filters.length - 1 ].disconnect( this.getOutput() ); - - } else { - - this.source.disconnect( this.getOutput() ); - - } - - this._connected = false; - - return this; - - } - - getFilters() { - - return this.filters; - - } - - setFilters( value ) { - - if ( ! value ) value = []; - - if ( this._connected === true ) { - - this.disconnect(); - this.filters = value.slice(); - this.connect(); - - } else { - - this.filters = value.slice(); - - } - - return this; - - } - - setDetune( value ) { - - this.detune = value; - - if ( this.source.detune === undefined ) return; // only set detune when available - - if ( this.isPlaying === true ) { - - this.source.detune.setTargetAtTime( this.detune, this.context.currentTime, 0.01 ); - - } - - return this; - - } - - getDetune() { - - return this.detune; - - } - - getFilter() { - - return this.getFilters()[ 0 ]; - - } - - setFilter( filter ) { - - return this.setFilters( filter ? [ filter ] : [] ); - - } - - setPlaybackRate( value ) { - - if ( this.hasPlaybackControl === false ) { - - console.warn( 'THREE.Audio: this Audio has no playback control.' ); - return; - - } - - this.playbackRate = value; - - if ( this.isPlaying === true ) { - - this.source.playbackRate.setTargetAtTime( this.playbackRate, this.context.currentTime, 0.01 ); - - } - - return this; - - } - - getPlaybackRate() { - - return this.playbackRate; - - } - - onEnded() { - - this.isPlaying = false; - - } - - getLoop() { - - if ( this.hasPlaybackControl === false ) { - - console.warn( 'THREE.Audio: this Audio has no playback control.' ); - return false; - - } - - return this.loop; - - } - - setLoop( value ) { - - if ( this.hasPlaybackControl === false ) { - - console.warn( 'THREE.Audio: this Audio has no playback control.' ); - return; - - } - - this.loop = value; - - if ( this.isPlaying === true ) { - - this.source.loop = this.loop; - - } - - return this; - - } - - setLoopStart( value ) { - - this.loopStart = value; - - return this; - - } - - setLoopEnd( value ) { - - this.loopEnd = value; - - return this; - - } - - getVolume() { - - return this.gain.gain.value; - - } - - setVolume( value ) { - - this.gain.gain.setTargetAtTime( value, this.context.currentTime, 0.01 ); - - return this; - - } - - } - - const _position = /*@__PURE__*/ new Vector3(); - const _quaternion = /*@__PURE__*/ new Quaternion(); - const _scale = /*@__PURE__*/ new Vector3(); - const _orientation = /*@__PURE__*/ new Vector3(); - - class PositionalAudio extends Audio { - - constructor( listener ) { - - super( listener ); - - this.panner = this.context.createPanner(); - this.panner.panningModel = 'HRTF'; - this.panner.connect( this.gain ); - - } - - disconnect() { - - super.disconnect(); - - this.panner.disconnect( this.gain ); - - } - - getOutput() { - - return this.panner; - - } - - getRefDistance() { - - return this.panner.refDistance; - - } - - setRefDistance( value ) { - - this.panner.refDistance = value; - - return this; - - } - - getRolloffFactor() { - - return this.panner.rolloffFactor; - - } - - setRolloffFactor( value ) { - - this.panner.rolloffFactor = value; - - return this; - - } - - getDistanceModel() { - - return this.panner.distanceModel; - - } - - setDistanceModel( value ) { - - this.panner.distanceModel = value; - - return this; - - } - - getMaxDistance() { - - return this.panner.maxDistance; - - } - - setMaxDistance( value ) { - - this.panner.maxDistance = value; - - return this; - - } - - setDirectionalCone( coneInnerAngle, coneOuterAngle, coneOuterGain ) { - - this.panner.coneInnerAngle = coneInnerAngle; - this.panner.coneOuterAngle = coneOuterAngle; - this.panner.coneOuterGain = coneOuterGain; - - return this; - - } - - updateMatrixWorld( force ) { - - super.updateMatrixWorld( force ); - - if ( this.hasPlaybackControl === true && this.isPlaying === false ) return; - - this.matrixWorld.decompose( _position, _quaternion, _scale ); - - _orientation.set( 0, 0, 1 ).applyQuaternion( _quaternion ); - - const panner = this.panner; - - if ( panner.positionX ) { - - // code path for Chrome and Firefox (see #14393) - - const endTime = this.context.currentTime + this.listener.timeDelta; - - panner.positionX.linearRampToValueAtTime( _position.x, endTime ); - panner.positionY.linearRampToValueAtTime( _position.y, endTime ); - panner.positionZ.linearRampToValueAtTime( _position.z, endTime ); - panner.orientationX.linearRampToValueAtTime( _orientation.x, endTime ); - panner.orientationY.linearRampToValueAtTime( _orientation.y, endTime ); - panner.orientationZ.linearRampToValueAtTime( _orientation.z, endTime ); - - } else { - - panner.setPosition( _position.x, _position.y, _position.z ); - panner.setOrientation( _orientation.x, _orientation.y, _orientation.z ); - - } - - } - - } - - class AudioAnalyser { - - constructor( audio, fftSize = 2048 ) { - - this.analyser = audio.context.createAnalyser(); - this.analyser.fftSize = fftSize; - - this.data = new Uint8Array( this.analyser.frequencyBinCount ); - - audio.getOutput().connect( this.analyser ); - - } - - - getFrequencyData() { - - this.analyser.getByteFrequencyData( this.data ); - - return this.data; - - } - - getAverageFrequency() { - - let value = 0; - const data = this.getFrequencyData(); - - for ( let i = 0; i < data.length; i ++ ) { - - value += data[ i ]; - - } - - return value / data.length; - - } - - } - - class PropertyMixer { - - constructor( binding, typeName, valueSize ) { - - this.binding = binding; - this.valueSize = valueSize; - - let mixFunction, - mixFunctionAdditive, - setIdentity; - - // buffer layout: [ incoming | accu0 | accu1 | orig | addAccu | (optional work) ] - // - // interpolators can use .buffer as their .result - // the data then goes to 'incoming' - // - // 'accu0' and 'accu1' are used frame-interleaved for - // the cumulative result and are compared to detect - // changes - // - // 'orig' stores the original state of the property - // - // 'add' is used for additive cumulative results - // - // 'work' is optional and is only present for quaternion types. It is used - // to store intermediate quaternion multiplication results - - switch ( typeName ) { - - case 'quaternion': - mixFunction = this._slerp; - mixFunctionAdditive = this._slerpAdditive; - setIdentity = this._setAdditiveIdentityQuaternion; - - this.buffer = new Float64Array( valueSize * 6 ); - this._workIndex = 5; - break; - - case 'string': - case 'bool': - mixFunction = this._select; - - // Use the regular mix function and for additive on these types, - // additive is not relevant for non-numeric types - mixFunctionAdditive = this._select; - - setIdentity = this._setAdditiveIdentityOther; - - this.buffer = new Array( valueSize * 5 ); - break; - - default: - mixFunction = this._lerp; - mixFunctionAdditive = this._lerpAdditive; - setIdentity = this._setAdditiveIdentityNumeric; - - this.buffer = new Float64Array( valueSize * 5 ); - - } - - this._mixBufferRegion = mixFunction; - this._mixBufferRegionAdditive = mixFunctionAdditive; - this._setIdentity = setIdentity; - this._origIndex = 3; - this._addIndex = 4; - - this.cumulativeWeight = 0; - this.cumulativeWeightAdditive = 0; - - this.useCount = 0; - this.referenceCount = 0; - - } - - // accumulate data in the 'incoming' region into 'accu' - accumulate( accuIndex, weight ) { - - // note: happily accumulating nothing when weight = 0, the caller knows - // the weight and shouldn't have made the call in the first place - - const buffer = this.buffer, - stride = this.valueSize, - offset = accuIndex * stride + stride; - - let currentWeight = this.cumulativeWeight; - - if ( currentWeight === 0 ) { - - // accuN := incoming * weight - - for ( let i = 0; i !== stride; ++ i ) { - - buffer[ offset + i ] = buffer[ i ]; - - } - - currentWeight = weight; - - } else { - - // accuN := accuN + incoming * weight - - currentWeight += weight; - const mix = weight / currentWeight; - this._mixBufferRegion( buffer, offset, 0, mix, stride ); - - } - - this.cumulativeWeight = currentWeight; - - } - - // accumulate data in the 'incoming' region into 'add' - accumulateAdditive( weight ) { - - const buffer = this.buffer, - stride = this.valueSize, - offset = stride * this._addIndex; - - if ( this.cumulativeWeightAdditive === 0 ) { - - // add = identity - - this._setIdentity(); - - } - - // add := add + incoming * weight - - this._mixBufferRegionAdditive( buffer, offset, 0, weight, stride ); - this.cumulativeWeightAdditive += weight; - - } - - // apply the state of 'accu' to the binding when accus differ - apply( accuIndex ) { - - const stride = this.valueSize, - buffer = this.buffer, - offset = accuIndex * stride + stride, - - weight = this.cumulativeWeight, - weightAdditive = this.cumulativeWeightAdditive, - - binding = this.binding; - - this.cumulativeWeight = 0; - this.cumulativeWeightAdditive = 0; - - if ( weight < 1 ) { - - // accuN := accuN + original * ( 1 - cumulativeWeight ) - - const originalValueOffset = stride * this._origIndex; - - this._mixBufferRegion( - buffer, offset, originalValueOffset, 1 - weight, stride ); - - } - - if ( weightAdditive > 0 ) { - - // accuN := accuN + additive accuN - - this._mixBufferRegionAdditive( buffer, offset, this._addIndex * stride, 1, stride ); - - } - - for ( let i = stride, e = stride + stride; i !== e; ++ i ) { - - if ( buffer[ i ] !== buffer[ i + stride ] ) { - - // value has changed -> update scene graph - - binding.setValue( buffer, offset ); - break; - - } - - } - - } - - // remember the state of the bound property and copy it to both accus - saveOriginalState() { - - const binding = this.binding; - - const buffer = this.buffer, - stride = this.valueSize, - - originalValueOffset = stride * this._origIndex; - - binding.getValue( buffer, originalValueOffset ); - - // accu[0..1] := orig -- initially detect changes against the original - for ( let i = stride, e = originalValueOffset; i !== e; ++ i ) { - - buffer[ i ] = buffer[ originalValueOffset + ( i % stride ) ]; - - } - - // Add to identity for additive - this._setIdentity(); - - this.cumulativeWeight = 0; - this.cumulativeWeightAdditive = 0; - - } - - // apply the state previously taken via 'saveOriginalState' to the binding - restoreOriginalState() { - - const originalValueOffset = this.valueSize * 3; - this.binding.setValue( this.buffer, originalValueOffset ); - - } - - _setAdditiveIdentityNumeric() { - - const startIndex = this._addIndex * this.valueSize; - const endIndex = startIndex + this.valueSize; - - for ( let i = startIndex; i < endIndex; i ++ ) { - - this.buffer[ i ] = 0; - - } - - } - - _setAdditiveIdentityQuaternion() { - - this._setAdditiveIdentityNumeric(); - this.buffer[ this._addIndex * this.valueSize + 3 ] = 1; - - } - - _setAdditiveIdentityOther() { - - const startIndex = this._origIndex * this.valueSize; - const targetIndex = this._addIndex * this.valueSize; - - for ( let i = 0; i < this.valueSize; i ++ ) { - - this.buffer[ targetIndex + i ] = this.buffer[ startIndex + i ]; - - } - - } - - - // mix functions - - _select( buffer, dstOffset, srcOffset, t, stride ) { - - if ( t >= 0.5 ) { - - for ( let i = 0; i !== stride; ++ i ) { - - buffer[ dstOffset + i ] = buffer[ srcOffset + i ]; - - } - - } - - } - - _slerp( buffer, dstOffset, srcOffset, t ) { - - Quaternion.slerpFlat( buffer, dstOffset, buffer, dstOffset, buffer, srcOffset, t ); - - } - - _slerpAdditive( buffer, dstOffset, srcOffset, t, stride ) { - - const workOffset = this._workIndex * stride; - - // Store result in intermediate buffer offset - Quaternion.multiplyQuaternionsFlat( buffer, workOffset, buffer, dstOffset, buffer, srcOffset ); - - // Slerp to the intermediate result - Quaternion.slerpFlat( buffer, dstOffset, buffer, dstOffset, buffer, workOffset, t ); - - } - - _lerp( buffer, dstOffset, srcOffset, t, stride ) { - - const s = 1 - t; - - for ( let i = 0; i !== stride; ++ i ) { - - const j = dstOffset + i; - - buffer[ j ] = buffer[ j ] * s + buffer[ srcOffset + i ] * t; - - } - - } - - _lerpAdditive( buffer, dstOffset, srcOffset, t, stride ) { - - for ( let i = 0; i !== stride; ++ i ) { - - const j = dstOffset + i; - - buffer[ j ] = buffer[ j ] + buffer[ srcOffset + i ] * t; - - } - - } - - } - - // Characters [].:/ are reserved for track binding syntax. - const _RESERVED_CHARS_RE = '\\[\\]\\.:\\/'; - const _reservedRe = new RegExp( '[' + _RESERVED_CHARS_RE + ']', 'g' ); - - // Attempts to allow node names from any language. ES5's `\w` regexp matches - // only latin characters, and the unicode \p{L} is not yet supported. So - // instead, we exclude reserved characters and match everything else. - const _wordChar = '[^' + _RESERVED_CHARS_RE + ']'; - const _wordCharOrDot = '[^' + _RESERVED_CHARS_RE.replace( '\\.', '' ) + ']'; - - // Parent directories, delimited by '/' or ':'. Currently unused, but must - // be matched to parse the rest of the track name. - const _directoryRe = /*@__PURE__*/ /((?:WC+[\/:])*)/.source.replace( 'WC', _wordChar ); - - // Target node. May contain word characters (a-zA-Z0-9_) and '.' or '-'. - const _nodeRe = /*@__PURE__*/ /(WCOD+)?/.source.replace( 'WCOD', _wordCharOrDot ); - - // Object on target node, and accessor. May not contain reserved - // characters. Accessor may contain any character except closing bracket. - const _objectRe = /*@__PURE__*/ /(?:\.(WC+)(?:\[(.+)\])?)?/.source.replace( 'WC', _wordChar ); - - // Property and accessor. May not contain reserved characters. Accessor may - // contain any non-bracket characters. - const _propertyRe = /*@__PURE__*/ /\.(WC+)(?:\[(.+)\])?/.source.replace( 'WC', _wordChar ); - - const _trackRe = new RegExp( '' - + '^' - + _directoryRe - + _nodeRe - + _objectRe - + _propertyRe - + '$' - ); - - const _supportedObjectNames = [ 'material', 'materials', 'bones', 'map' ]; - - class Composite { - - constructor( targetGroup, path, optionalParsedPath ) { - - const parsedPath = optionalParsedPath || PropertyBinding.parseTrackName( path ); - - this._targetGroup = targetGroup; - this._bindings = targetGroup.subscribe_( path, parsedPath ); - - } - - getValue( array, offset ) { - - this.bind(); // bind all binding - - const firstValidIndex = this._targetGroup.nCachedObjects_, - binding = this._bindings[ firstValidIndex ]; - - // and only call .getValue on the first - if ( binding !== undefined ) binding.getValue( array, offset ); - - } - - setValue( array, offset ) { - - const bindings = this._bindings; - - for ( let i = this._targetGroup.nCachedObjects_, n = bindings.length; i !== n; ++ i ) { - - bindings[ i ].setValue( array, offset ); - - } - - } - - bind() { - - const bindings = this._bindings; - - for ( let i = this._targetGroup.nCachedObjects_, n = bindings.length; i !== n; ++ i ) { - - bindings[ i ].bind(); - - } - - } - - unbind() { - - const bindings = this._bindings; - - for ( let i = this._targetGroup.nCachedObjects_, n = bindings.length; i !== n; ++ i ) { - - bindings[ i ].unbind(); - - } - - } - - } - - // Note: This class uses a State pattern on a per-method basis: - // 'bind' sets 'this.getValue' / 'setValue' and shadows the - // prototype version of these methods with one that represents - // the bound state. When the property is not found, the methods - // become no-ops. - class PropertyBinding { - - constructor( rootNode, path, parsedPath ) { - - this.path = path; - this.parsedPath = parsedPath || PropertyBinding.parseTrackName( path ); - - this.node = PropertyBinding.findNode( rootNode, this.parsedPath.nodeName ); - - this.rootNode = rootNode; - - // initial state of these methods that calls 'bind' - this.getValue = this._getValue_unbound; - this.setValue = this._setValue_unbound; - - } - - - static create( root, path, parsedPath ) { - - if ( ! ( root && root.isAnimationObjectGroup ) ) { - - return new PropertyBinding( root, path, parsedPath ); - - } else { - - return new PropertyBinding.Composite( root, path, parsedPath ); - - } - - } - - /** - * Replaces spaces with underscores and removes unsupported characters from - * node names, to ensure compatibility with parseTrackName(). - * - * @param {string} name Node name to be sanitized. - * @return {string} - */ - static sanitizeNodeName( name ) { - - return name.replace( /\s/g, '_' ).replace( _reservedRe, '' ); - - } - - static parseTrackName( trackName ) { - - const matches = _trackRe.exec( trackName ); - - if ( matches === null ) { - - throw new Error( 'PropertyBinding: Cannot parse trackName: ' + trackName ); - - } - - const results = { - // directoryName: matches[ 1 ], // (tschw) currently unused - nodeName: matches[ 2 ], - objectName: matches[ 3 ], - objectIndex: matches[ 4 ], - propertyName: matches[ 5 ], // required - propertyIndex: matches[ 6 ] - }; - - const lastDot = results.nodeName && results.nodeName.lastIndexOf( '.' ); - - if ( lastDot !== undefined && lastDot !== - 1 ) { - - const objectName = results.nodeName.substring( lastDot + 1 ); - - // Object names must be checked against an allowlist. Otherwise, there - // is no way to parse 'foo.bar.baz': 'baz' must be a property, but - // 'bar' could be the objectName, or part of a nodeName (which can - // include '.' characters). - if ( _supportedObjectNames.indexOf( objectName ) !== - 1 ) { - - results.nodeName = results.nodeName.substring( 0, lastDot ); - results.objectName = objectName; - - } - - } - - if ( results.propertyName === null || results.propertyName.length === 0 ) { - - throw new Error( 'PropertyBinding: can not parse propertyName from trackName: ' + trackName ); - - } - - return results; - - } - - static findNode( root, nodeName ) { - - if ( nodeName === undefined || nodeName === '' || nodeName === '.' || nodeName === - 1 || nodeName === root.name || nodeName === root.uuid ) { - - return root; - - } - - // search into skeleton bones. - if ( root.skeleton ) { - - const bone = root.skeleton.getBoneByName( nodeName ); - - if ( bone !== undefined ) { - - return bone; - - } - - } - - // search into node subtree. - if ( root.children ) { - - const searchNodeSubtree = function ( children ) { - - for ( let i = 0; i < children.length; i ++ ) { - - const childNode = children[ i ]; - - if ( childNode.name === nodeName || childNode.uuid === nodeName ) { - - return childNode; - - } - - const result = searchNodeSubtree( childNode.children ); - - if ( result ) return result; - - } - - return null; - - }; - - const subTreeNode = searchNodeSubtree( root.children ); - - if ( subTreeNode ) { - - return subTreeNode; - - } - - } - - return null; - - } - - // these are used to "bind" a nonexistent property - _getValue_unavailable() {} - _setValue_unavailable() {} - - // Getters - - _getValue_direct( buffer, offset ) { - - buffer[ offset ] = this.targetObject[ this.propertyName ]; - - } - - _getValue_array( buffer, offset ) { - - const source = this.resolvedProperty; - - for ( let i = 0, n = source.length; i !== n; ++ i ) { - - buffer[ offset ++ ] = source[ i ]; - - } - - } - - _getValue_arrayElement( buffer, offset ) { - - buffer[ offset ] = this.resolvedProperty[ this.propertyIndex ]; - - } - - _getValue_toArray( buffer, offset ) { - - this.resolvedProperty.toArray( buffer, offset ); - - } - - // Direct - - _setValue_direct( buffer, offset ) { - - this.targetObject[ this.propertyName ] = buffer[ offset ]; - - } - - _setValue_direct_setNeedsUpdate( buffer, offset ) { - - this.targetObject[ this.propertyName ] = buffer[ offset ]; - this.targetObject.needsUpdate = true; - - } - - _setValue_direct_setMatrixWorldNeedsUpdate( buffer, offset ) { - - this.targetObject[ this.propertyName ] = buffer[ offset ]; - this.targetObject.matrixWorldNeedsUpdate = true; - - } - - // EntireArray - - _setValue_array( buffer, offset ) { - - const dest = this.resolvedProperty; - - for ( let i = 0, n = dest.length; i !== n; ++ i ) { - - dest[ i ] = buffer[ offset ++ ]; - - } - - } - - _setValue_array_setNeedsUpdate( buffer, offset ) { - - const dest = this.resolvedProperty; - - for ( let i = 0, n = dest.length; i !== n; ++ i ) { - - dest[ i ] = buffer[ offset ++ ]; - - } - - this.targetObject.needsUpdate = true; - - } - - _setValue_array_setMatrixWorldNeedsUpdate( buffer, offset ) { - - const dest = this.resolvedProperty; - - for ( let i = 0, n = dest.length; i !== n; ++ i ) { - - dest[ i ] = buffer[ offset ++ ]; - - } - - this.targetObject.matrixWorldNeedsUpdate = true; - - } - - // ArrayElement - - _setValue_arrayElement( buffer, offset ) { - - this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ]; - - } - - _setValue_arrayElement_setNeedsUpdate( buffer, offset ) { - - this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ]; - this.targetObject.needsUpdate = true; - - } - - _setValue_arrayElement_setMatrixWorldNeedsUpdate( buffer, offset ) { - - this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ]; - this.targetObject.matrixWorldNeedsUpdate = true; - - } - - // HasToFromArray - - _setValue_fromArray( buffer, offset ) { - - this.resolvedProperty.fromArray( buffer, offset ); - - } - - _setValue_fromArray_setNeedsUpdate( buffer, offset ) { - - this.resolvedProperty.fromArray( buffer, offset ); - this.targetObject.needsUpdate = true; - - } - - _setValue_fromArray_setMatrixWorldNeedsUpdate( buffer, offset ) { - - this.resolvedProperty.fromArray( buffer, offset ); - this.targetObject.matrixWorldNeedsUpdate = true; - - } - - _getValue_unbound( targetArray, offset ) { - - this.bind(); - this.getValue( targetArray, offset ); - - } - - _setValue_unbound( sourceArray, offset ) { - - this.bind(); - this.setValue( sourceArray, offset ); - - } - - // create getter / setter pair for a property in the scene graph - bind() { - - let targetObject = this.node; - const parsedPath = this.parsedPath; - - const objectName = parsedPath.objectName; - const propertyName = parsedPath.propertyName; - let propertyIndex = parsedPath.propertyIndex; - - if ( ! targetObject ) { - - targetObject = PropertyBinding.findNode( this.rootNode, parsedPath.nodeName ); - - this.node = targetObject; - - } - - // set fail state so we can just 'return' on error - this.getValue = this._getValue_unavailable; - this.setValue = this._setValue_unavailable; - - // ensure there is a value node - if ( ! targetObject ) { - - console.error( 'THREE.PropertyBinding: Trying to update node for track: ' + this.path + ' but it wasn\'t found.' ); - return; - - } - - if ( objectName ) { - - let objectIndex = parsedPath.objectIndex; - - // special cases were we need to reach deeper into the hierarchy to get the face materials.... - switch ( objectName ) { - - case 'materials': - - if ( ! targetObject.material ) { - - console.error( 'THREE.PropertyBinding: Can not bind to material as node does not have a material.', this ); - return; - - } - - if ( ! targetObject.material.materials ) { - - console.error( 'THREE.PropertyBinding: Can not bind to material.materials as node.material does not have a materials array.', this ); - return; - - } - - targetObject = targetObject.material.materials; - - break; - - case 'bones': - - if ( ! targetObject.skeleton ) { - - console.error( 'THREE.PropertyBinding: Can not bind to bones as node does not have a skeleton.', this ); - return; - - } - - // potential future optimization: skip this if propertyIndex is already an integer - // and convert the integer string to a true integer. - - targetObject = targetObject.skeleton.bones; - - // support resolving morphTarget names into indices. - for ( let i = 0; i < targetObject.length; i ++ ) { - - if ( targetObject[ i ].name === objectIndex ) { - - objectIndex = i; - break; - - } - - } - - break; - - case 'map': - - if ( 'map' in targetObject ) { - - targetObject = targetObject.map; - break; - - } - - if ( ! targetObject.material ) { - - console.error( 'THREE.PropertyBinding: Can not bind to material as node does not have a material.', this ); - return; - - } - - if ( ! targetObject.material.map ) { - - console.error( 'THREE.PropertyBinding: Can not bind to material.map as node.material does not have a map.', this ); - return; - - } - - targetObject = targetObject.material.map; - break; - - default: - - if ( targetObject[ objectName ] === undefined ) { - - console.error( 'THREE.PropertyBinding: Can not bind to objectName of node undefined.', this ); - return; - - } - - targetObject = targetObject[ objectName ]; - - } - - - if ( objectIndex !== undefined ) { - - if ( targetObject[ objectIndex ] === undefined ) { - - console.error( 'THREE.PropertyBinding: Trying to bind to objectIndex of objectName, but is undefined.', this, targetObject ); - return; - - } - - targetObject = targetObject[ objectIndex ]; - - } - - } - - // resolve property - const nodeProperty = targetObject[ propertyName ]; - - if ( nodeProperty === undefined ) { - - const nodeName = parsedPath.nodeName; - - console.error( 'THREE.PropertyBinding: Trying to update property for track: ' + nodeName + - '.' + propertyName + ' but it wasn\'t found.', targetObject ); - return; - - } - - // determine versioning scheme - let versioning = this.Versioning.None; - - this.targetObject = targetObject; - - if ( targetObject.needsUpdate !== undefined ) { // material - - versioning = this.Versioning.NeedsUpdate; - - } else if ( targetObject.matrixWorldNeedsUpdate !== undefined ) { // node transform - - versioning = this.Versioning.MatrixWorldNeedsUpdate; - - } - - // determine how the property gets bound - let bindingType = this.BindingType.Direct; - - if ( propertyIndex !== undefined ) { - - // access a sub element of the property array (only primitives are supported right now) - - if ( propertyName === 'morphTargetInfluences' ) { - - // potential optimization, skip this if propertyIndex is already an integer, and convert the integer string to a true integer. - - // support resolving morphTarget names into indices. - if ( ! targetObject.geometry ) { - - console.error( 'THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.', this ); - return; - - } - - if ( ! targetObject.geometry.morphAttributes ) { - - console.error( 'THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.morphAttributes.', this ); - return; - - } - - if ( targetObject.morphTargetDictionary[ propertyIndex ] !== undefined ) { - - propertyIndex = targetObject.morphTargetDictionary[ propertyIndex ]; - - } - - } - - bindingType = this.BindingType.ArrayElement; - - this.resolvedProperty = nodeProperty; - this.propertyIndex = propertyIndex; - - } else if ( nodeProperty.fromArray !== undefined && nodeProperty.toArray !== undefined ) { - - // must use copy for Object3D.Euler/Quaternion - - bindingType = this.BindingType.HasFromToArray; - - this.resolvedProperty = nodeProperty; - - } else if ( Array.isArray( nodeProperty ) ) { - - bindingType = this.BindingType.EntireArray; - - this.resolvedProperty = nodeProperty; - - } else { - - this.propertyName = propertyName; - - } - - // select getter / setter - this.getValue = this.GetterByBindingType[ bindingType ]; - this.setValue = this.SetterByBindingTypeAndVersioning[ bindingType ][ versioning ]; - - } - - unbind() { - - this.node = null; - - // back to the prototype version of getValue / setValue - // note: avoiding to mutate the shape of 'this' via 'delete' - this.getValue = this._getValue_unbound; - this.setValue = this._setValue_unbound; - - } - - } - - PropertyBinding.Composite = Composite; - - PropertyBinding.prototype.BindingType = { - Direct: 0, - EntireArray: 1, - ArrayElement: 2, - HasFromToArray: 3 - }; - - PropertyBinding.prototype.Versioning = { - None: 0, - NeedsUpdate: 1, - MatrixWorldNeedsUpdate: 2 - }; - - PropertyBinding.prototype.GetterByBindingType = [ - - PropertyBinding.prototype._getValue_direct, - PropertyBinding.prototype._getValue_array, - PropertyBinding.prototype._getValue_arrayElement, - PropertyBinding.prototype._getValue_toArray, - - ]; - - PropertyBinding.prototype.SetterByBindingTypeAndVersioning = [ - - [ - // Direct - PropertyBinding.prototype._setValue_direct, - PropertyBinding.prototype._setValue_direct_setNeedsUpdate, - PropertyBinding.prototype._setValue_direct_setMatrixWorldNeedsUpdate, - - ], [ - - // EntireArray - - PropertyBinding.prototype._setValue_array, - PropertyBinding.prototype._setValue_array_setNeedsUpdate, - PropertyBinding.prototype._setValue_array_setMatrixWorldNeedsUpdate, - - ], [ - - // ArrayElement - PropertyBinding.prototype._setValue_arrayElement, - PropertyBinding.prototype._setValue_arrayElement_setNeedsUpdate, - PropertyBinding.prototype._setValue_arrayElement_setMatrixWorldNeedsUpdate, - - ], [ - - // HasToFromArray - PropertyBinding.prototype._setValue_fromArray, - PropertyBinding.prototype._setValue_fromArray_setNeedsUpdate, - PropertyBinding.prototype._setValue_fromArray_setMatrixWorldNeedsUpdate, - - ] - - ]; - - /** - * - * A group of objects that receives a shared animation state. - * - * Usage: - * - * - Add objects you would otherwise pass as 'root' to the - * constructor or the .clipAction method of AnimationMixer. - * - * - Instead pass this object as 'root'. - * - * - You can also add and remove objects later when the mixer - * is running. - * - * Note: - * - * Objects of this class appear as one object to the mixer, - * so cache control of the individual objects must be done - * on the group. - * - * Limitation: - * - * - The animated properties must be compatible among the - * all objects in the group. - * - * - A single property can either be controlled through a - * target group or directly, but not both. - */ - - class AnimationObjectGroup { - - constructor() { - - this.isAnimationObjectGroup = true; - - this.uuid = generateUUID(); - - // cached objects followed by the active ones - this._objects = Array.prototype.slice.call( arguments ); - - this.nCachedObjects_ = 0; // threshold - // note: read by PropertyBinding.Composite - - const indices = {}; - this._indicesByUUID = indices; // for bookkeeping - - for ( let i = 0, n = arguments.length; i !== n; ++ i ) { - - indices[ arguments[ i ].uuid ] = i; - - } - - this._paths = []; // inside: string - this._parsedPaths = []; // inside: { we don't care, here } - this._bindings = []; // inside: Array< PropertyBinding > - this._bindingsIndicesByPath = {}; // inside: indices in these arrays - - const scope = this; - - this.stats = { - - objects: { - get total() { - - return scope._objects.length; - - }, - get inUse() { - - return this.total - scope.nCachedObjects_; - - } - }, - get bindingsPerObject() { - - return scope._bindings.length; - - } - - }; - - } - - add() { - - const objects = this._objects, - indicesByUUID = this._indicesByUUID, - paths = this._paths, - parsedPaths = this._parsedPaths, - bindings = this._bindings, - nBindings = bindings.length; - - let knownObject = undefined, - nObjects = objects.length, - nCachedObjects = this.nCachedObjects_; - - for ( let i = 0, n = arguments.length; i !== n; ++ i ) { - - const object = arguments[ i ], - uuid = object.uuid; - let index = indicesByUUID[ uuid ]; - - if ( index === undefined ) { - - // unknown object -> add it to the ACTIVE region - - index = nObjects ++; - indicesByUUID[ uuid ] = index; - objects.push( object ); - - // accounting is done, now do the same for all bindings - - for ( let j = 0, m = nBindings; j !== m; ++ j ) { - - bindings[ j ].push( new PropertyBinding( object, paths[ j ], parsedPaths[ j ] ) ); - - } - - } else if ( index < nCachedObjects ) { - - knownObject = objects[ index ]; - - // move existing object to the ACTIVE region - - const firstActiveIndex = -- nCachedObjects, - lastCachedObject = objects[ firstActiveIndex ]; - - indicesByUUID[ lastCachedObject.uuid ] = index; - objects[ index ] = lastCachedObject; - - indicesByUUID[ uuid ] = firstActiveIndex; - objects[ firstActiveIndex ] = object; - - // accounting is done, now do the same for all bindings - - for ( let j = 0, m = nBindings; j !== m; ++ j ) { - - const bindingsForPath = bindings[ j ], - lastCached = bindingsForPath[ firstActiveIndex ]; - - let binding = bindingsForPath[ index ]; - - bindingsForPath[ index ] = lastCached; - - if ( binding === undefined ) { - - // since we do not bother to create new bindings - // for objects that are cached, the binding may - // or may not exist - - binding = new PropertyBinding( object, paths[ j ], parsedPaths[ j ] ); - - } - - bindingsForPath[ firstActiveIndex ] = binding; - - } - - } else if ( objects[ index ] !== knownObject ) { - - console.error( 'THREE.AnimationObjectGroup: Different objects with the same UUID ' + - 'detected. Clean the caches or recreate your infrastructure when reloading scenes.' ); - - } // else the object is already where we want it to be - - } // for arguments - - this.nCachedObjects_ = nCachedObjects; - - } - - remove() { - - const objects = this._objects, - indicesByUUID = this._indicesByUUID, - bindings = this._bindings, - nBindings = bindings.length; - - let nCachedObjects = this.nCachedObjects_; - - for ( let i = 0, n = arguments.length; i !== n; ++ i ) { - - const object = arguments[ i ], - uuid = object.uuid, - index = indicesByUUID[ uuid ]; - - if ( index !== undefined && index >= nCachedObjects ) { - - // move existing object into the CACHED region - - const lastCachedIndex = nCachedObjects ++, - firstActiveObject = objects[ lastCachedIndex ]; - - indicesByUUID[ firstActiveObject.uuid ] = index; - objects[ index ] = firstActiveObject; - - indicesByUUID[ uuid ] = lastCachedIndex; - objects[ lastCachedIndex ] = object; - - // accounting is done, now do the same for all bindings - - for ( let j = 0, m = nBindings; j !== m; ++ j ) { - - const bindingsForPath = bindings[ j ], - firstActive = bindingsForPath[ lastCachedIndex ], - binding = bindingsForPath[ index ]; - - bindingsForPath[ index ] = firstActive; - bindingsForPath[ lastCachedIndex ] = binding; - - } - - } - - } // for arguments - - this.nCachedObjects_ = nCachedObjects; - - } - - // remove & forget - uncache() { - - const objects = this._objects, - indicesByUUID = this._indicesByUUID, - bindings = this._bindings, - nBindings = bindings.length; - - let nCachedObjects = this.nCachedObjects_, - nObjects = objects.length; - - for ( let i = 0, n = arguments.length; i !== n; ++ i ) { - - const object = arguments[ i ], - uuid = object.uuid, - index = indicesByUUID[ uuid ]; - - if ( index !== undefined ) { - - delete indicesByUUID[ uuid ]; - - if ( index < nCachedObjects ) { - - // object is cached, shrink the CACHED region - - const firstActiveIndex = -- nCachedObjects, - lastCachedObject = objects[ firstActiveIndex ], - lastIndex = -- nObjects, - lastObject = objects[ lastIndex ]; - - // last cached object takes this object's place - indicesByUUID[ lastCachedObject.uuid ] = index; - objects[ index ] = lastCachedObject; - - // last object goes to the activated slot and pop - indicesByUUID[ lastObject.uuid ] = firstActiveIndex; - objects[ firstActiveIndex ] = lastObject; - objects.pop(); - - // accounting is done, now do the same for all bindings - - for ( let j = 0, m = nBindings; j !== m; ++ j ) { - - const bindingsForPath = bindings[ j ], - lastCached = bindingsForPath[ firstActiveIndex ], - last = bindingsForPath[ lastIndex ]; - - bindingsForPath[ index ] = lastCached; - bindingsForPath[ firstActiveIndex ] = last; - bindingsForPath.pop(); - - } - - } else { - - // object is active, just swap with the last and pop - - const lastIndex = -- nObjects, - lastObject = objects[ lastIndex ]; - - if ( lastIndex > 0 ) { - - indicesByUUID[ lastObject.uuid ] = index; - - } - - objects[ index ] = lastObject; - objects.pop(); - - // accounting is done, now do the same for all bindings - - for ( let j = 0, m = nBindings; j !== m; ++ j ) { - - const bindingsForPath = bindings[ j ]; - - bindingsForPath[ index ] = bindingsForPath[ lastIndex ]; - bindingsForPath.pop(); - - } - - } // cached or active - - } // if object is known - - } // for arguments - - this.nCachedObjects_ = nCachedObjects; - - } - - // Internal interface used by befriended PropertyBinding.Composite: - - subscribe_( path, parsedPath ) { - - // returns an array of bindings for the given path that is changed - // according to the contained objects in the group - - const indicesByPath = this._bindingsIndicesByPath; - let index = indicesByPath[ path ]; - const bindings = this._bindings; - - if ( index !== undefined ) return bindings[ index ]; - - const paths = this._paths, - parsedPaths = this._parsedPaths, - objects = this._objects, - nObjects = objects.length, - nCachedObjects = this.nCachedObjects_, - bindingsForPath = new Array( nObjects ); - - index = bindings.length; - - indicesByPath[ path ] = index; - - paths.push( path ); - parsedPaths.push( parsedPath ); - bindings.push( bindingsForPath ); - - for ( let i = nCachedObjects, n = objects.length; i !== n; ++ i ) { - - const object = objects[ i ]; - bindingsForPath[ i ] = new PropertyBinding( object, path, parsedPath ); - - } - - return bindingsForPath; - - } - - unsubscribe_( path ) { - - // tells the group to forget about a property path and no longer - // update the array previously obtained with 'subscribe_' - - const indicesByPath = this._bindingsIndicesByPath, - index = indicesByPath[ path ]; - - if ( index !== undefined ) { - - const paths = this._paths, - parsedPaths = this._parsedPaths, - bindings = this._bindings, - lastBindingsIndex = bindings.length - 1, - lastBindings = bindings[ lastBindingsIndex ], - lastBindingsPath = path[ lastBindingsIndex ]; - - indicesByPath[ lastBindingsPath ] = index; - - bindings[ index ] = lastBindings; - bindings.pop(); - - parsedPaths[ index ] = parsedPaths[ lastBindingsIndex ]; - parsedPaths.pop(); - - paths[ index ] = paths[ lastBindingsIndex ]; - paths.pop(); - - } - - } - - } - - class AnimationAction { - - constructor( mixer, clip, localRoot = null, blendMode = clip.blendMode ) { - - this._mixer = mixer; - this._clip = clip; - this._localRoot = localRoot; - this.blendMode = blendMode; - - const tracks = clip.tracks, - nTracks = tracks.length, - interpolants = new Array( nTracks ); - - const interpolantSettings = { - endingStart: ZeroCurvatureEnding, - endingEnd: ZeroCurvatureEnding - }; - - for ( let i = 0; i !== nTracks; ++ i ) { - - const interpolant = tracks[ i ].createInterpolant( null ); - interpolants[ i ] = interpolant; - interpolant.settings = interpolantSettings; - - } - - this._interpolantSettings = interpolantSettings; - - this._interpolants = interpolants; // bound by the mixer - - // inside: PropertyMixer (managed by the mixer) - this._propertyBindings = new Array( nTracks ); - - this._cacheIndex = null; // for the memory manager - this._byClipCacheIndex = null; // for the memory manager - - this._timeScaleInterpolant = null; - this._weightInterpolant = null; - - this.loop = LoopRepeat; - this._loopCount = - 1; - - // global mixer time when the action is to be started - // it's set back to 'null' upon start of the action - this._startTime = null; - - // scaled local time of the action - // gets clamped or wrapped to 0..clip.duration according to loop - this.time = 0; - - this.timeScale = 1; - this._effectiveTimeScale = 1; - - this.weight = 1; - this._effectiveWeight = 1; - - this.repetitions = Infinity; // no. of repetitions when looping - - this.paused = false; // true -> zero effective time scale - this.enabled = true; // false -> zero effective weight - - this.clampWhenFinished = false;// keep feeding the last frame? - - this.zeroSlopeAtStart = true;// for smooth interpolation w/o separate - this.zeroSlopeAtEnd = true;// clips for start, loop and end - - } - - // State & Scheduling - - play() { - - this._mixer._activateAction( this ); - - return this; - - } - - stop() { - - this._mixer._deactivateAction( this ); - - return this.reset(); - - } - - reset() { - - this.paused = false; - this.enabled = true; - - this.time = 0; // restart clip - this._loopCount = - 1;// forget previous loops - this._startTime = null;// forget scheduling - - return this.stopFading().stopWarping(); - - } - - isRunning() { - - return this.enabled && ! this.paused && this.timeScale !== 0 && - this._startTime === null && this._mixer._isActiveAction( this ); - - } - - // return true when play has been called - isScheduled() { - - return this._mixer._isActiveAction( this ); - - } - - startAt( time ) { - - this._startTime = time; - - return this; - - } - - setLoop( mode, repetitions ) { - - this.loop = mode; - this.repetitions = repetitions; - - return this; - - } - - // Weight - - // set the weight stopping any scheduled fading - // although .enabled = false yields an effective weight of zero, this - // method does *not* change .enabled, because it would be confusing - setEffectiveWeight( weight ) { - - this.weight = weight; - - // note: same logic as when updated at runtime - this._effectiveWeight = this.enabled ? weight : 0; - - return this.stopFading(); - - } - - // return the weight considering fading and .enabled - getEffectiveWeight() { - - return this._effectiveWeight; - - } - - fadeIn( duration ) { - - return this._scheduleFading( duration, 0, 1 ); - - } - - fadeOut( duration ) { - - return this._scheduleFading( duration, 1, 0 ); - - } - - crossFadeFrom( fadeOutAction, duration, warp ) { - - fadeOutAction.fadeOut( duration ); - this.fadeIn( duration ); - - if ( warp ) { - - const fadeInDuration = this._clip.duration, - fadeOutDuration = fadeOutAction._clip.duration, - - startEndRatio = fadeOutDuration / fadeInDuration, - endStartRatio = fadeInDuration / fadeOutDuration; - - fadeOutAction.warp( 1.0, startEndRatio, duration ); - this.warp( endStartRatio, 1.0, duration ); - - } - - return this; - - } - - crossFadeTo( fadeInAction, duration, warp ) { - - return fadeInAction.crossFadeFrom( this, duration, warp ); - - } - - stopFading() { - - const weightInterpolant = this._weightInterpolant; - - if ( weightInterpolant !== null ) { - - this._weightInterpolant = null; - this._mixer._takeBackControlInterpolant( weightInterpolant ); - - } - - return this; - - } - - // Time Scale Control - - // set the time scale stopping any scheduled warping - // although .paused = true yields an effective time scale of zero, this - // method does *not* change .paused, because it would be confusing - setEffectiveTimeScale( timeScale ) { - - this.timeScale = timeScale; - this._effectiveTimeScale = this.paused ? 0 : timeScale; - - return this.stopWarping(); - - } - - // return the time scale considering warping and .paused - getEffectiveTimeScale() { - - return this._effectiveTimeScale; - - } - - setDuration( duration ) { - - this.timeScale = this._clip.duration / duration; - - return this.stopWarping(); - - } - - syncWith( action ) { - - this.time = action.time; - this.timeScale = action.timeScale; - - return this.stopWarping(); - - } - - halt( duration ) { - - return this.warp( this._effectiveTimeScale, 0, duration ); - - } - - warp( startTimeScale, endTimeScale, duration ) { - - const mixer = this._mixer, - now = mixer.time, - timeScale = this.timeScale; - - let interpolant = this._timeScaleInterpolant; - - if ( interpolant === null ) { - - interpolant = mixer._lendControlInterpolant(); - this._timeScaleInterpolant = interpolant; - - } - - const times = interpolant.parameterPositions, - values = interpolant.sampleValues; - - times[ 0 ] = now; - times[ 1 ] = now + duration; - - values[ 0 ] = startTimeScale / timeScale; - values[ 1 ] = endTimeScale / timeScale; - - return this; - - } - - stopWarping() { - - const timeScaleInterpolant = this._timeScaleInterpolant; - - if ( timeScaleInterpolant !== null ) { - - this._timeScaleInterpolant = null; - this._mixer._takeBackControlInterpolant( timeScaleInterpolant ); - - } - - return this; - - } - - // Object Accessors - - getMixer() { - - return this._mixer; - - } - - getClip() { - - return this._clip; - - } - - getRoot() { - - return this._localRoot || this._mixer._root; - - } - - // Interna - - _update( time, deltaTime, timeDirection, accuIndex ) { - - // called by the mixer - - if ( ! this.enabled ) { - - // call ._updateWeight() to update ._effectiveWeight - - this._updateWeight( time ); - return; - - } - - const startTime = this._startTime; - - if ( startTime !== null ) { - - // check for scheduled start of action - - const timeRunning = ( time - startTime ) * timeDirection; - if ( timeRunning < 0 || timeDirection === 0 ) { - - deltaTime = 0; - - } else { - - - this._startTime = null; // unschedule - deltaTime = timeDirection * timeRunning; - - } - - } - - // apply time scale and advance time - - deltaTime *= this._updateTimeScale( time ); - const clipTime = this._updateTime( deltaTime ); - - // note: _updateTime may disable the action resulting in - // an effective weight of 0 - - const weight = this._updateWeight( time ); - - if ( weight > 0 ) { - - const interpolants = this._interpolants; - const propertyMixers = this._propertyBindings; - - switch ( this.blendMode ) { - - case AdditiveAnimationBlendMode: - - for ( let j = 0, m = interpolants.length; j !== m; ++ j ) { - - interpolants[ j ].evaluate( clipTime ); - propertyMixers[ j ].accumulateAdditive( weight ); - - } - - break; - - case NormalAnimationBlendMode: - default: - - for ( let j = 0, m = interpolants.length; j !== m; ++ j ) { - - interpolants[ j ].evaluate( clipTime ); - propertyMixers[ j ].accumulate( accuIndex, weight ); - - } - - } - - } - - } - - _updateWeight( time ) { - - let weight = 0; - - if ( this.enabled ) { - - weight = this.weight; - const interpolant = this._weightInterpolant; - - if ( interpolant !== null ) { - - const interpolantValue = interpolant.evaluate( time )[ 0 ]; - - weight *= interpolantValue; - - if ( time > interpolant.parameterPositions[ 1 ] ) { - - this.stopFading(); - - if ( interpolantValue === 0 ) { - - // faded out, disable - this.enabled = false; - - } - - } - - } - - } - - this._effectiveWeight = weight; - return weight; - - } - - _updateTimeScale( time ) { - - let timeScale = 0; - - if ( ! this.paused ) { - - timeScale = this.timeScale; - - const interpolant = this._timeScaleInterpolant; - - if ( interpolant !== null ) { - - const interpolantValue = interpolant.evaluate( time )[ 0 ]; - - timeScale *= interpolantValue; - - if ( time > interpolant.parameterPositions[ 1 ] ) { - - this.stopWarping(); - - if ( timeScale === 0 ) { - - // motion has halted, pause - this.paused = true; - - } else { - - // warp done - apply final time scale - this.timeScale = timeScale; - - } - - } - - } - - } - - this._effectiveTimeScale = timeScale; - return timeScale; - - } - - _updateTime( deltaTime ) { - - const duration = this._clip.duration; - const loop = this.loop; - - let time = this.time + deltaTime; - let loopCount = this._loopCount; - - const pingPong = ( loop === LoopPingPong ); - - if ( deltaTime === 0 ) { - - if ( loopCount === - 1 ) return time; - - return ( pingPong && ( loopCount & 1 ) === 1 ) ? duration - time : time; - - } - - if ( loop === LoopOnce ) { - - if ( loopCount === - 1 ) { - - // just started - - this._loopCount = 0; - this._setEndings( true, true, false ); - - } - - handle_stop: { - - if ( time >= duration ) { - - time = duration; - - } else if ( time < 0 ) { - - time = 0; - - } else { - - this.time = time; - - break handle_stop; - - } - - if ( this.clampWhenFinished ) this.paused = true; - else this.enabled = false; - - this.time = time; - - this._mixer.dispatchEvent( { - type: 'finished', action: this, - direction: deltaTime < 0 ? - 1 : 1 - } ); - - } - - } else { // repetitive Repeat or PingPong - - if ( loopCount === - 1 ) { - - // just started - - if ( deltaTime >= 0 ) { - - loopCount = 0; - - this._setEndings( true, this.repetitions === 0, pingPong ); - - } else { - - // when looping in reverse direction, the initial - // transition through zero counts as a repetition, - // so leave loopCount at -1 - - this._setEndings( this.repetitions === 0, true, pingPong ); - - } - - } - - if ( time >= duration || time < 0 ) { - - // wrap around - - const loopDelta = Math.floor( time / duration ); // signed - time -= duration * loopDelta; - - loopCount += Math.abs( loopDelta ); - - const pending = this.repetitions - loopCount; - - if ( pending <= 0 ) { - - // have to stop (switch state, clamp time, fire event) - - if ( this.clampWhenFinished ) this.paused = true; - else this.enabled = false; - - time = deltaTime > 0 ? duration : 0; - - this.time = time; - - this._mixer.dispatchEvent( { - type: 'finished', action: this, - direction: deltaTime > 0 ? 1 : - 1 - } ); - - } else { - - // keep running - - if ( pending === 1 ) { - - // entering the last round - - const atStart = deltaTime < 0; - this._setEndings( atStart, ! atStart, pingPong ); - - } else { - - this._setEndings( false, false, pingPong ); - - } - - this._loopCount = loopCount; - - this.time = time; - - this._mixer.dispatchEvent( { - type: 'loop', action: this, loopDelta: loopDelta - } ); - - } - - } else { - - this.time = time; - - } - - if ( pingPong && ( loopCount & 1 ) === 1 ) { - - // invert time for the "pong round" - - return duration - time; - - } - - } - - return time; - - } - - _setEndings( atStart, atEnd, pingPong ) { - - const settings = this._interpolantSettings; - - if ( pingPong ) { - - settings.endingStart = ZeroSlopeEnding; - settings.endingEnd = ZeroSlopeEnding; - - } else { - - // assuming for LoopOnce atStart == atEnd == true - - if ( atStart ) { - - settings.endingStart = this.zeroSlopeAtStart ? ZeroSlopeEnding : ZeroCurvatureEnding; - - } else { - - settings.endingStart = WrapAroundEnding; - - } - - if ( atEnd ) { - - settings.endingEnd = this.zeroSlopeAtEnd ? ZeroSlopeEnding : ZeroCurvatureEnding; - - } else { - - settings.endingEnd = WrapAroundEnding; - - } - - } - - } - - _scheduleFading( duration, weightNow, weightThen ) { - - const mixer = this._mixer, now = mixer.time; - let interpolant = this._weightInterpolant; - - if ( interpolant === null ) { - - interpolant = mixer._lendControlInterpolant(); - this._weightInterpolant = interpolant; - - } - - const times = interpolant.parameterPositions, - values = interpolant.sampleValues; - - times[ 0 ] = now; - values[ 0 ] = weightNow; - times[ 1 ] = now + duration; - values[ 1 ] = weightThen; - - return this; - - } - - } - - const _controlInterpolantsResultBuffer = new Float32Array( 1 ); - - - class AnimationMixer extends EventDispatcher { - - constructor( root ) { - - super(); - - this._root = root; - this._initMemoryManager(); - this._accuIndex = 0; - this.time = 0; - this.timeScale = 1.0; - - } - - _bindAction( action, prototypeAction ) { - - const root = action._localRoot || this._root, - tracks = action._clip.tracks, - nTracks = tracks.length, - bindings = action._propertyBindings, - interpolants = action._interpolants, - rootUuid = root.uuid, - bindingsByRoot = this._bindingsByRootAndName; - - let bindingsByName = bindingsByRoot[ rootUuid ]; - - if ( bindingsByName === undefined ) { - - bindingsByName = {}; - bindingsByRoot[ rootUuid ] = bindingsByName; - - } - - for ( let i = 0; i !== nTracks; ++ i ) { - - const track = tracks[ i ], - trackName = track.name; - - let binding = bindingsByName[ trackName ]; - - if ( binding !== undefined ) { - - ++ binding.referenceCount; - bindings[ i ] = binding; - - } else { - - binding = bindings[ i ]; - - if ( binding !== undefined ) { - - // existing binding, make sure the cache knows - - if ( binding._cacheIndex === null ) { - - ++ binding.referenceCount; - this._addInactiveBinding( binding, rootUuid, trackName ); - - } - - continue; - - } - - const path = prototypeAction && prototypeAction. - _propertyBindings[ i ].binding.parsedPath; - - binding = new PropertyMixer( - PropertyBinding.create( root, trackName, path ), - track.ValueTypeName, track.getValueSize() ); - - ++ binding.referenceCount; - this._addInactiveBinding( binding, rootUuid, trackName ); - - bindings[ i ] = binding; - - } - - interpolants[ i ].resultBuffer = binding.buffer; - - } - - } - - _activateAction( action ) { - - if ( ! this._isActiveAction( action ) ) { - - if ( action._cacheIndex === null ) { - - // this action has been forgotten by the cache, but the user - // appears to be still using it -> rebind - - const rootUuid = ( action._localRoot || this._root ).uuid, - clipUuid = action._clip.uuid, - actionsForClip = this._actionsByClip[ clipUuid ]; - - this._bindAction( action, - actionsForClip && actionsForClip.knownActions[ 0 ] ); - - this._addInactiveAction( action, clipUuid, rootUuid ); - - } - - const bindings = action._propertyBindings; - - // increment reference counts / sort out state - for ( let i = 0, n = bindings.length; i !== n; ++ i ) { - - const binding = bindings[ i ]; - - if ( binding.useCount ++ === 0 ) { - - this._lendBinding( binding ); - binding.saveOriginalState(); - - } - - } - - this._lendAction( action ); - - } - - } - - _deactivateAction( action ) { - - if ( this._isActiveAction( action ) ) { - - const bindings = action._propertyBindings; - - // decrement reference counts / sort out state - for ( let i = 0, n = bindings.length; i !== n; ++ i ) { - - const binding = bindings[ i ]; - - if ( -- binding.useCount === 0 ) { - - binding.restoreOriginalState(); - this._takeBackBinding( binding ); - - } - - } - - this._takeBackAction( action ); - - } - - } - - // Memory manager - - _initMemoryManager() { - - this._actions = []; // 'nActiveActions' followed by inactive ones - this._nActiveActions = 0; - - this._actionsByClip = {}; - // inside: - // { - // knownActions: Array< AnimationAction > - used as prototypes - // actionByRoot: AnimationAction - lookup - // } - - - this._bindings = []; // 'nActiveBindings' followed by inactive ones - this._nActiveBindings = 0; - - this._bindingsByRootAndName = {}; // inside: Map< name, PropertyMixer > - - - this._controlInterpolants = []; // same game as above - this._nActiveControlInterpolants = 0; - - const scope = this; - - this.stats = { - - actions: { - get total() { - - return scope._actions.length; - - }, - get inUse() { - - return scope._nActiveActions; - - } - }, - bindings: { - get total() { - - return scope._bindings.length; - - }, - get inUse() { - - return scope._nActiveBindings; - - } - }, - controlInterpolants: { - get total() { - - return scope._controlInterpolants.length; - - }, - get inUse() { - - return scope._nActiveControlInterpolants; - - } - } - - }; - - } - - // Memory management for AnimationAction objects - - _isActiveAction( action ) { - - const index = action._cacheIndex; - return index !== null && index < this._nActiveActions; - - } - - _addInactiveAction( action, clipUuid, rootUuid ) { - - const actions = this._actions, - actionsByClip = this._actionsByClip; - - let actionsForClip = actionsByClip[ clipUuid ]; - - if ( actionsForClip === undefined ) { - - actionsForClip = { - - knownActions: [ action ], - actionByRoot: {} - - }; - - action._byClipCacheIndex = 0; - - actionsByClip[ clipUuid ] = actionsForClip; - - } else { - - const knownActions = actionsForClip.knownActions; - - action._byClipCacheIndex = knownActions.length; - knownActions.push( action ); - - } - - action._cacheIndex = actions.length; - actions.push( action ); - - actionsForClip.actionByRoot[ rootUuid ] = action; - - } - - _removeInactiveAction( action ) { - - const actions = this._actions, - lastInactiveAction = actions[ actions.length - 1 ], - cacheIndex = action._cacheIndex; - - lastInactiveAction._cacheIndex = cacheIndex; - actions[ cacheIndex ] = lastInactiveAction; - actions.pop(); - - action._cacheIndex = null; - - - const clipUuid = action._clip.uuid, - actionsByClip = this._actionsByClip, - actionsForClip = actionsByClip[ clipUuid ], - knownActionsForClip = actionsForClip.knownActions, - - lastKnownAction = - knownActionsForClip[ knownActionsForClip.length - 1 ], - - byClipCacheIndex = action._byClipCacheIndex; - - lastKnownAction._byClipCacheIndex = byClipCacheIndex; - knownActionsForClip[ byClipCacheIndex ] = lastKnownAction; - knownActionsForClip.pop(); - - action._byClipCacheIndex = null; - - - const actionByRoot = actionsForClip.actionByRoot, - rootUuid = ( action._localRoot || this._root ).uuid; - - delete actionByRoot[ rootUuid ]; - - if ( knownActionsForClip.length === 0 ) { - - delete actionsByClip[ clipUuid ]; - - } - - this._removeInactiveBindingsForAction( action ); - - } - - _removeInactiveBindingsForAction( action ) { - - const bindings = action._propertyBindings; - - for ( let i = 0, n = bindings.length; i !== n; ++ i ) { - - const binding = bindings[ i ]; - - if ( -- binding.referenceCount === 0 ) { - - this._removeInactiveBinding( binding ); - - } - - } - - } - - _lendAction( action ) { - - // [ active actions | inactive actions ] - // [ active actions >| inactive actions ] - // s a - // <-swap-> - // a s - - const actions = this._actions, - prevIndex = action._cacheIndex, - - lastActiveIndex = this._nActiveActions ++, - - firstInactiveAction = actions[ lastActiveIndex ]; - - action._cacheIndex = lastActiveIndex; - actions[ lastActiveIndex ] = action; - - firstInactiveAction._cacheIndex = prevIndex; - actions[ prevIndex ] = firstInactiveAction; - - } - - _takeBackAction( action ) { - - // [ active actions | inactive actions ] - // [ active actions |< inactive actions ] - // a s - // <-swap-> - // s a - - const actions = this._actions, - prevIndex = action._cacheIndex, - - firstInactiveIndex = -- this._nActiveActions, - - lastActiveAction = actions[ firstInactiveIndex ]; - - action._cacheIndex = firstInactiveIndex; - actions[ firstInactiveIndex ] = action; - - lastActiveAction._cacheIndex = prevIndex; - actions[ prevIndex ] = lastActiveAction; - - } - - // Memory management for PropertyMixer objects - - _addInactiveBinding( binding, rootUuid, trackName ) { - - const bindingsByRoot = this._bindingsByRootAndName, - bindings = this._bindings; - - let bindingByName = bindingsByRoot[ rootUuid ]; - - if ( bindingByName === undefined ) { - - bindingByName = {}; - bindingsByRoot[ rootUuid ] = bindingByName; - - } - - bindingByName[ trackName ] = binding; - - binding._cacheIndex = bindings.length; - bindings.push( binding ); - - } - - _removeInactiveBinding( binding ) { - - const bindings = this._bindings, - propBinding = binding.binding, - rootUuid = propBinding.rootNode.uuid, - trackName = propBinding.path, - bindingsByRoot = this._bindingsByRootAndName, - bindingByName = bindingsByRoot[ rootUuid ], - - lastInactiveBinding = bindings[ bindings.length - 1 ], - cacheIndex = binding._cacheIndex; - - lastInactiveBinding._cacheIndex = cacheIndex; - bindings[ cacheIndex ] = lastInactiveBinding; - bindings.pop(); - - delete bindingByName[ trackName ]; - - if ( Object.keys( bindingByName ).length === 0 ) { - - delete bindingsByRoot[ rootUuid ]; - - } - - } - - _lendBinding( binding ) { - - const bindings = this._bindings, - prevIndex = binding._cacheIndex, - - lastActiveIndex = this._nActiveBindings ++, - - firstInactiveBinding = bindings[ lastActiveIndex ]; - - binding._cacheIndex = lastActiveIndex; - bindings[ lastActiveIndex ] = binding; - - firstInactiveBinding._cacheIndex = prevIndex; - bindings[ prevIndex ] = firstInactiveBinding; - - } - - _takeBackBinding( binding ) { - - const bindings = this._bindings, - prevIndex = binding._cacheIndex, - - firstInactiveIndex = -- this._nActiveBindings, - - lastActiveBinding = bindings[ firstInactiveIndex ]; - - binding._cacheIndex = firstInactiveIndex; - bindings[ firstInactiveIndex ] = binding; - - lastActiveBinding._cacheIndex = prevIndex; - bindings[ prevIndex ] = lastActiveBinding; - - } - - - // Memory management of Interpolants for weight and time scale - - _lendControlInterpolant() { - - const interpolants = this._controlInterpolants, - lastActiveIndex = this._nActiveControlInterpolants ++; - - let interpolant = interpolants[ lastActiveIndex ]; - - if ( interpolant === undefined ) { - - interpolant = new LinearInterpolant( - new Float32Array( 2 ), new Float32Array( 2 ), - 1, _controlInterpolantsResultBuffer ); - - interpolant.__cacheIndex = lastActiveIndex; - interpolants[ lastActiveIndex ] = interpolant; - - } - - return interpolant; - - } - - _takeBackControlInterpolant( interpolant ) { - - const interpolants = this._controlInterpolants, - prevIndex = interpolant.__cacheIndex, - - firstInactiveIndex = -- this._nActiveControlInterpolants, - - lastActiveInterpolant = interpolants[ firstInactiveIndex ]; - - interpolant.__cacheIndex = firstInactiveIndex; - interpolants[ firstInactiveIndex ] = interpolant; - - lastActiveInterpolant.__cacheIndex = prevIndex; - interpolants[ prevIndex ] = lastActiveInterpolant; - - } - - // return an action for a clip optionally using a custom root target - // object (this method allocates a lot of dynamic memory in case a - // previously unknown clip/root combination is specified) - clipAction( clip, optionalRoot, blendMode ) { - - const root = optionalRoot || this._root, - rootUuid = root.uuid; - - let clipObject = typeof clip === 'string' ? AnimationClip.findByName( root, clip ) : clip; - - const clipUuid = clipObject !== null ? clipObject.uuid : clip; - - const actionsForClip = this._actionsByClip[ clipUuid ]; - let prototypeAction = null; - - if ( blendMode === undefined ) { - - if ( clipObject !== null ) { - - blendMode = clipObject.blendMode; - - } else { - - blendMode = NormalAnimationBlendMode; - - } - - } - - if ( actionsForClip !== undefined ) { - - const existingAction = actionsForClip.actionByRoot[ rootUuid ]; - - if ( existingAction !== undefined && existingAction.blendMode === blendMode ) { - - return existingAction; - - } - - // we know the clip, so we don't have to parse all - // the bindings again but can just copy - prototypeAction = actionsForClip.knownActions[ 0 ]; - - // also, take the clip from the prototype action - if ( clipObject === null ) - clipObject = prototypeAction._clip; - - } - - // clip must be known when specified via string - if ( clipObject === null ) return null; - - // allocate all resources required to run it - const newAction = new AnimationAction( this, clipObject, optionalRoot, blendMode ); - - this._bindAction( newAction, prototypeAction ); - - // and make the action known to the memory manager - this._addInactiveAction( newAction, clipUuid, rootUuid ); - - return newAction; - - } - - // get an existing action - existingAction( clip, optionalRoot ) { - - const root = optionalRoot || this._root, - rootUuid = root.uuid, - - clipObject = typeof clip === 'string' ? - AnimationClip.findByName( root, clip ) : clip, - - clipUuid = clipObject ? clipObject.uuid : clip, - - actionsForClip = this._actionsByClip[ clipUuid ]; - - if ( actionsForClip !== undefined ) { - - return actionsForClip.actionByRoot[ rootUuid ] || null; - - } - - return null; - - } - - // deactivates all previously scheduled actions - stopAllAction() { - - const actions = this._actions, - nActions = this._nActiveActions; - - for ( let i = nActions - 1; i >= 0; -- i ) { - - actions[ i ].stop(); - - } - - return this; - - } - - // advance the time and update apply the animation - update( deltaTime ) { - - deltaTime *= this.timeScale; - - const actions = this._actions, - nActions = this._nActiveActions, - - time = this.time += deltaTime, - timeDirection = Math.sign( deltaTime ), - - accuIndex = this._accuIndex ^= 1; - - // run active actions - - for ( let i = 0; i !== nActions; ++ i ) { - - const action = actions[ i ]; - - action._update( time, deltaTime, timeDirection, accuIndex ); - - } - - // update scene graph - - const bindings = this._bindings, - nBindings = this._nActiveBindings; - - for ( let i = 0; i !== nBindings; ++ i ) { - - bindings[ i ].apply( accuIndex ); - - } - - return this; - - } - - // Allows you to seek to a specific time in an animation. - setTime( timeInSeconds ) { - - this.time = 0; // Zero out time attribute for AnimationMixer object; - for ( let i = 0; i < this._actions.length; i ++ ) { - - this._actions[ i ].time = 0; // Zero out time attribute for all associated AnimationAction objects. - - } - - return this.update( timeInSeconds ); // Update used to set exact time. Returns "this" AnimationMixer object. - - } - - // return this mixer's root target object - getRoot() { - - return this._root; - - } - - // free all resources specific to a particular clip - uncacheClip( clip ) { - - const actions = this._actions, - clipUuid = clip.uuid, - actionsByClip = this._actionsByClip, - actionsForClip = actionsByClip[ clipUuid ]; - - if ( actionsForClip !== undefined ) { - - // note: just calling _removeInactiveAction would mess up the - // iteration state and also require updating the state we can - // just throw away - - const actionsToRemove = actionsForClip.knownActions; - - for ( let i = 0, n = actionsToRemove.length; i !== n; ++ i ) { - - const action = actionsToRemove[ i ]; - - this._deactivateAction( action ); - - const cacheIndex = action._cacheIndex, - lastInactiveAction = actions[ actions.length - 1 ]; - - action._cacheIndex = null; - action._byClipCacheIndex = null; - - lastInactiveAction._cacheIndex = cacheIndex; - actions[ cacheIndex ] = lastInactiveAction; - actions.pop(); - - this._removeInactiveBindingsForAction( action ); - - } - - delete actionsByClip[ clipUuid ]; - - } - - } - - // free all resources specific to a particular root target object - uncacheRoot( root ) { - - const rootUuid = root.uuid, - actionsByClip = this._actionsByClip; - - for ( const clipUuid in actionsByClip ) { - - const actionByRoot = actionsByClip[ clipUuid ].actionByRoot, - action = actionByRoot[ rootUuid ]; - - if ( action !== undefined ) { - - this._deactivateAction( action ); - this._removeInactiveAction( action ); - - } - - } - - const bindingsByRoot = this._bindingsByRootAndName, - bindingByName = bindingsByRoot[ rootUuid ]; - - if ( bindingByName !== undefined ) { - - for ( const trackName in bindingByName ) { - - const binding = bindingByName[ trackName ]; - binding.restoreOriginalState(); - this._removeInactiveBinding( binding ); - - } - - } - - } - - // remove a targeted clip from the cache - uncacheAction( clip, optionalRoot ) { - - const action = this.existingAction( clip, optionalRoot ); - - if ( action !== null ) { - - this._deactivateAction( action ); - this._removeInactiveAction( action ); - - } - - } - - } - - class Uniform { - - constructor( value ) { - - this.value = value; - - } - - clone() { - - return new Uniform( this.value.clone === undefined ? this.value : this.value.clone() ); - - } - - } - - let id = 0; - - class UniformsGroup extends EventDispatcher { - - constructor() { - - super(); - - this.isUniformsGroup = true; - - Object.defineProperty( this, 'id', { value: id ++ } ); - - this.name = ''; - - this.usage = StaticDrawUsage; - this.uniforms = []; - - } - - add( uniform ) { - - this.uniforms.push( uniform ); - - return this; - - } - - remove( uniform ) { - - const index = this.uniforms.indexOf( uniform ); - - if ( index !== - 1 ) this.uniforms.splice( index, 1 ); - - return this; - - } - - setName( name ) { - - this.name = name; - - return this; - - } - - setUsage( value ) { - - this.usage = value; - - return this; - - } - - dispose() { - - this.dispatchEvent( { type: 'dispose' } ); - - return this; - - } - - copy( source ) { - - this.name = source.name; - this.usage = source.usage; - - const uniformsSource = source.uniforms; - - this.uniforms.length = 0; - - for ( let i = 0, l = uniformsSource.length; i < l; i ++ ) { - - this.uniforms.push( uniformsSource[ i ].clone() ); - - } - - return this; - - } - - clone() { - - return new this.constructor().copy( this ); - - } - - } - - class InstancedInterleavedBuffer extends InterleavedBuffer { - - constructor( array, stride, meshPerAttribute = 1 ) { - - super( array, stride ); - - this.isInstancedInterleavedBuffer = true; - - this.meshPerAttribute = meshPerAttribute; - - } - - copy( source ) { - - super.copy( source ); - - this.meshPerAttribute = source.meshPerAttribute; - - return this; - - } - - clone( data ) { - - const ib = super.clone( data ); - - ib.meshPerAttribute = this.meshPerAttribute; - - return ib; - - } - - toJSON( data ) { - - const json = super.toJSON( data ); - - json.isInstancedInterleavedBuffer = true; - json.meshPerAttribute = this.meshPerAttribute; - - return json; - - } - - } - - class GLBufferAttribute { - - constructor( buffer, type, itemSize, elementSize, count ) { - - this.isGLBufferAttribute = true; - - this.name = ''; - - this.buffer = buffer; - this.type = type; - this.itemSize = itemSize; - this.elementSize = elementSize; - this.count = count; - - this.version = 0; - - } - - set needsUpdate( value ) { - - if ( value === true ) this.version ++; - - } - - setBuffer( buffer ) { - - this.buffer = buffer; - - return this; - - } - - setType( type, elementSize ) { - - this.type = type; - this.elementSize = elementSize; - - return this; - - } - - setItemSize( itemSize ) { - - this.itemSize = itemSize; - - return this; - - } - - setCount( count ) { - - this.count = count; - - return this; - - } - - } - - class Raycaster { - - constructor( origin, direction, near = 0, far = Infinity ) { - - this.ray = new Ray( origin, direction ); - // direction is assumed to be normalized (for accurate distance calculations) - - this.near = near; - this.far = far; - this.camera = null; - this.layers = new Layers(); - - this.params = { - Mesh: {}, - Line: { threshold: 1 }, - LOD: {}, - Points: { threshold: 1 }, - Sprite: {} - }; - - } - - set( origin, direction ) { - - // direction is assumed to be normalized (for accurate distance calculations) - - this.ray.set( origin, direction ); - - } - - setFromCamera( coords, camera ) { - - if ( camera.isPerspectiveCamera ) { - - this.ray.origin.setFromMatrixPosition( camera.matrixWorld ); - this.ray.direction.set( coords.x, coords.y, 0.5 ).unproject( camera ).sub( this.ray.origin ).normalize(); - this.camera = camera; - - } else if ( camera.isOrthographicCamera ) { - - this.ray.origin.set( coords.x, coords.y, ( camera.near + camera.far ) / ( camera.near - camera.far ) ).unproject( camera ); // set origin in plane of camera - this.ray.direction.set( 0, 0, - 1 ).transformDirection( camera.matrixWorld ); - this.camera = camera; - - } else { - - console.error( 'THREE.Raycaster: Unsupported camera type: ' + camera.type ); - - } - - } - - intersectObject( object, recursive = true, intersects = [] ) { - - intersectObject( object, this, intersects, recursive ); - - intersects.sort( ascSort ); - - return intersects; - - } - - intersectObjects( objects, recursive = true, intersects = [] ) { - - for ( let i = 0, l = objects.length; i < l; i ++ ) { - - intersectObject( objects[ i ], this, intersects, recursive ); - - } - - intersects.sort( ascSort ); - - return intersects; - - } - - } - - function ascSort( a, b ) { - - return a.distance - b.distance; - - } - - function intersectObject( object, raycaster, intersects, recursive ) { - - if ( object.layers.test( raycaster.layers ) ) { - - object.raycast( raycaster, intersects ); - - } - - if ( recursive === true ) { - - const children = object.children; - - for ( let i = 0, l = children.length; i < l; i ++ ) { - - intersectObject( children[ i ], raycaster, intersects, true ); - - } - - } - - } - - /** - * Ref: https://en.wikipedia.org/wiki/Spherical_coordinate_system - * - * The polar angle (phi) is measured from the positive y-axis. The positive y-axis is up. - * The azimuthal angle (theta) is measured from the positive z-axis. - */ - - - class Spherical { - - constructor( radius = 1, phi = 0, theta = 0 ) { - - this.radius = radius; - this.phi = phi; // polar angle - this.theta = theta; // azimuthal angle - - return this; - - } - - set( radius, phi, theta ) { - - this.radius = radius; - this.phi = phi; - this.theta = theta; - - return this; - - } - - copy( other ) { - - this.radius = other.radius; - this.phi = other.phi; - this.theta = other.theta; - - return this; - - } - - // restrict phi to be between EPS and PI-EPS - makeSafe() { - - const EPS = 0.000001; - this.phi = Math.max( EPS, Math.min( Math.PI - EPS, this.phi ) ); - - return this; - - } - - setFromVector3( v ) { - - return this.setFromCartesianCoords( v.x, v.y, v.z ); - - } - - setFromCartesianCoords( x, y, z ) { - - this.radius = Math.sqrt( x * x + y * y + z * z ); - - if ( this.radius === 0 ) { - - this.theta = 0; - this.phi = 0; - - } else { - - this.theta = Math.atan2( x, z ); - this.phi = Math.acos( clamp( y / this.radius, - 1, 1 ) ); - - } - - return this; - - } - - clone() { - - return new this.constructor().copy( this ); - - } - - } - - /** - * Ref: https://en.wikipedia.org/wiki/Cylindrical_coordinate_system - */ - - class Cylindrical { - - constructor( radius = 1, theta = 0, y = 0 ) { - - this.radius = radius; // distance from the origin to a point in the x-z plane - this.theta = theta; // counterclockwise angle in the x-z plane measured in radians from the positive z-axis - this.y = y; // height above the x-z plane - - return this; - - } - - set( radius, theta, y ) { - - this.radius = radius; - this.theta = theta; - this.y = y; - - return this; - - } - - copy( other ) { - - this.radius = other.radius; - this.theta = other.theta; - this.y = other.y; - - return this; - - } - - setFromVector3( v ) { - - return this.setFromCartesianCoords( v.x, v.y, v.z ); - - } - - setFromCartesianCoords( x, y, z ) { - - this.radius = Math.sqrt( x * x + z * z ); - this.theta = Math.atan2( x, z ); - this.y = y; - - return this; - - } - - clone() { - - return new this.constructor().copy( this ); - - } - - } - - const _vector$4 = /*@__PURE__*/ new Vector2(); - - class Box2 { - - constructor( min = new Vector2( + Infinity, + Infinity ), max = new Vector2( - Infinity, - Infinity ) ) { - - this.isBox2 = true; - - this.min = min; - this.max = max; - - } - - set( min, max ) { - - this.min.copy( min ); - this.max.copy( max ); - - return this; - - } - - setFromPoints( points ) { - - this.makeEmpty(); - - for ( let i = 0, il = points.length; i < il; i ++ ) { - - this.expandByPoint( points[ i ] ); - - } - - return this; - - } - - setFromCenterAndSize( center, size ) { - - const halfSize = _vector$4.copy( size ).multiplyScalar( 0.5 ); - this.min.copy( center ).sub( halfSize ); - this.max.copy( center ).add( halfSize ); - - return this; - - } - - clone() { - - return new this.constructor().copy( this ); - - } - - copy( box ) { - - this.min.copy( box.min ); - this.max.copy( box.max ); - - return this; - - } - - makeEmpty() { - - this.min.x = this.min.y = + Infinity; - this.max.x = this.max.y = - Infinity; - - return this; - - } - - isEmpty() { - - // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes - - return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y ); - - } - - getCenter( target ) { - - return this.isEmpty() ? target.set( 0, 0 ) : target.addVectors( this.min, this.max ).multiplyScalar( 0.5 ); - - } - - getSize( target ) { - - return this.isEmpty() ? target.set( 0, 0 ) : target.subVectors( this.max, this.min ); - - } - - expandByPoint( point ) { - - this.min.min( point ); - this.max.max( point ); - - return this; - - } - - expandByVector( vector ) { - - this.min.sub( vector ); - this.max.add( vector ); - - return this; - - } - - expandByScalar( scalar ) { - - this.min.addScalar( - scalar ); - this.max.addScalar( scalar ); - - return this; - - } - - containsPoint( point ) { - - return point.x < this.min.x || point.x > this.max.x || - point.y < this.min.y || point.y > this.max.y ? false : true; - - } - - containsBox( box ) { - - return this.min.x <= box.min.x && box.max.x <= this.max.x && - this.min.y <= box.min.y && box.max.y <= this.max.y; - - } - - getParameter( point, target ) { - - // This can potentially have a divide by zero if the box - // has a size dimension of 0. - - return target.set( - ( point.x - this.min.x ) / ( this.max.x - this.min.x ), - ( point.y - this.min.y ) / ( this.max.y - this.min.y ) - ); - - } - - intersectsBox( box ) { - - // using 4 splitting planes to rule out intersections - - return box.max.x < this.min.x || box.min.x > this.max.x || - box.max.y < this.min.y || box.min.y > this.max.y ? false : true; - - } - - clampPoint( point, target ) { - - return target.copy( point ).clamp( this.min, this.max ); - - } - - distanceToPoint( point ) { - - return this.clampPoint( point, _vector$4 ).distanceTo( point ); - - } - - intersect( box ) { - - this.min.max( box.min ); - this.max.min( box.max ); - - if ( this.isEmpty() ) this.makeEmpty(); - - return this; - - } - - union( box ) { - - this.min.min( box.min ); - this.max.max( box.max ); - - return this; - - } - - translate( offset ) { - - this.min.add( offset ); - this.max.add( offset ); - - return this; - - } - - equals( box ) { - - return box.min.equals( this.min ) && box.max.equals( this.max ); - - } - - } - - const _startP = /*@__PURE__*/ new Vector3(); - const _startEnd = /*@__PURE__*/ new Vector3(); - - class Line3 { - - constructor( start = new Vector3(), end = new Vector3() ) { - - this.start = start; - this.end = end; - - } - - set( start, end ) { - - this.start.copy( start ); - this.end.copy( end ); - - return this; - - } - - copy( line ) { - - this.start.copy( line.start ); - this.end.copy( line.end ); - - return this; - - } - - getCenter( target ) { - - return target.addVectors( this.start, this.end ).multiplyScalar( 0.5 ); - - } - - delta( target ) { - - return target.subVectors( this.end, this.start ); - - } - - distanceSq() { - - return this.start.distanceToSquared( this.end ); - - } - - distance() { - - return this.start.distanceTo( this.end ); - - } - - at( t, target ) { - - return this.delta( target ).multiplyScalar( t ).add( this.start ); - - } - - closestPointToPointParameter( point, clampToLine ) { - - _startP.subVectors( point, this.start ); - _startEnd.subVectors( this.end, this.start ); - - const startEnd2 = _startEnd.dot( _startEnd ); - const startEnd_startP = _startEnd.dot( _startP ); - - let t = startEnd_startP / startEnd2; - - if ( clampToLine ) { - - t = clamp( t, 0, 1 ); - - } - - return t; - - } - - closestPointToPoint( point, clampToLine, target ) { - - const t = this.closestPointToPointParameter( point, clampToLine ); - - return this.delta( target ).multiplyScalar( t ).add( this.start ); - - } - - applyMatrix4( matrix ) { - - this.start.applyMatrix4( matrix ); - this.end.applyMatrix4( matrix ); - - return this; - - } - - equals( line ) { - - return line.start.equals( this.start ) && line.end.equals( this.end ); - - } - - clone() { - - return new this.constructor().copy( this ); - - } - - } - - const _vector$3 = /*@__PURE__*/ new Vector3(); - - class SpotLightHelper extends Object3D { - - constructor( light, color ) { - - super(); - - this.light = light; - - this.matrix = light.matrixWorld; - this.matrixAutoUpdate = false; - - this.color = color; - - this.type = 'SpotLightHelper'; - - const geometry = new BufferGeometry(); - - const positions = [ - 0, 0, 0, 0, 0, 1, - 0, 0, 0, 1, 0, 1, - 0, 0, 0, - 1, 0, 1, - 0, 0, 0, 0, 1, 1, - 0, 0, 0, 0, - 1, 1 - ]; - - for ( let i = 0, j = 1, l = 32; i < l; i ++, j ++ ) { - - const p1 = ( i / l ) * Math.PI * 2; - const p2 = ( j / l ) * Math.PI * 2; - - positions.push( - Math.cos( p1 ), Math.sin( p1 ), 1, - Math.cos( p2 ), Math.sin( p2 ), 1 - ); - - } - - geometry.setAttribute( 'position', new Float32BufferAttribute( positions, 3 ) ); - - const material = new LineBasicMaterial( { fog: false, toneMapped: false } ); - - this.cone = new LineSegments( geometry, material ); - this.add( this.cone ); - - this.update(); - - } - - dispose() { - - this.cone.geometry.dispose(); - this.cone.material.dispose(); - - } - - update() { - - this.light.updateWorldMatrix( true, false ); - this.light.target.updateWorldMatrix( true, false ); - - const coneLength = this.light.distance ? this.light.distance : 1000; - const coneWidth = coneLength * Math.tan( this.light.angle ); - - this.cone.scale.set( coneWidth, coneWidth, coneLength ); - - _vector$3.setFromMatrixPosition( this.light.target.matrixWorld ); - - this.cone.lookAt( _vector$3 ); - - if ( this.color !== undefined ) { - - this.cone.material.color.set( this.color ); - - } else { - - this.cone.material.color.copy( this.light.color ); - - } - - } - - } - - const _vector$2 = /*@__PURE__*/ new Vector3(); - const _boneMatrix = /*@__PURE__*/ new Matrix4(); - const _matrixWorldInv = /*@__PURE__*/ new Matrix4(); - - - class SkeletonHelper extends LineSegments { - - constructor( object ) { - - const bones = getBoneList( object ); - - const geometry = new BufferGeometry(); - - const vertices = []; - const colors = []; - - const color1 = new Color( 0, 0, 1 ); - const color2 = new Color( 0, 1, 0 ); - - for ( let i = 0; i < bones.length; i ++ ) { - - const bone = bones[ i ]; - - if ( bone.parent && bone.parent.isBone ) { - - vertices.push( 0, 0, 0 ); - vertices.push( 0, 0, 0 ); - colors.push( color1.r, color1.g, color1.b ); - colors.push( color2.r, color2.g, color2.b ); - - } - - } - - geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - geometry.setAttribute( 'color', new Float32BufferAttribute( colors, 3 ) ); - - const material = new LineBasicMaterial( { vertexColors: true, depthTest: false, depthWrite: false, toneMapped: false, transparent: true } ); - - super( geometry, material ); - - this.isSkeletonHelper = true; - - this.type = 'SkeletonHelper'; - - this.root = object; - this.bones = bones; - - this.matrix = object.matrixWorld; - this.matrixAutoUpdate = false; - - } - - updateMatrixWorld( force ) { - - const bones = this.bones; - - const geometry = this.geometry; - const position = geometry.getAttribute( 'position' ); - - _matrixWorldInv.copy( this.root.matrixWorld ).invert(); - - for ( let i = 0, j = 0; i < bones.length; i ++ ) { - - const bone = bones[ i ]; - - if ( bone.parent && bone.parent.isBone ) { - - _boneMatrix.multiplyMatrices( _matrixWorldInv, bone.matrixWorld ); - _vector$2.setFromMatrixPosition( _boneMatrix ); - position.setXYZ( j, _vector$2.x, _vector$2.y, _vector$2.z ); - - _boneMatrix.multiplyMatrices( _matrixWorldInv, bone.parent.matrixWorld ); - _vector$2.setFromMatrixPosition( _boneMatrix ); - position.setXYZ( j + 1, _vector$2.x, _vector$2.y, _vector$2.z ); - - j += 2; - - } - - } - - geometry.getAttribute( 'position' ).needsUpdate = true; - - super.updateMatrixWorld( force ); - - } - - dispose() { - - this.geometry.dispose(); - this.material.dispose(); - - } - - } - - - function getBoneList( object ) { - - const boneList = []; - - if ( object.isBone === true ) { - - boneList.push( object ); - - } - - for ( let i = 0; i < object.children.length; i ++ ) { - - boneList.push.apply( boneList, getBoneList( object.children[ i ] ) ); - - } - - return boneList; - - } - - class PointLightHelper extends Mesh { - - constructor( light, sphereSize, color ) { - - const geometry = new SphereGeometry( sphereSize, 4, 2 ); - const material = new MeshBasicMaterial( { wireframe: true, fog: false, toneMapped: false } ); - - super( geometry, material ); - - this.light = light; - - this.color = color; - - this.type = 'PointLightHelper'; - - this.matrix = this.light.matrixWorld; - this.matrixAutoUpdate = false; - - this.update(); - - - /* - // TODO: delete this comment? - const distanceGeometry = new THREE.IcosahedronGeometry( 1, 2 ); - const distanceMaterial = new THREE.MeshBasicMaterial( { color: hexColor, fog: false, wireframe: true, opacity: 0.1, transparent: true } ); - - this.lightSphere = new THREE.Mesh( bulbGeometry, bulbMaterial ); - this.lightDistance = new THREE.Mesh( distanceGeometry, distanceMaterial ); - - const d = light.distance; - - if ( d === 0.0 ) { - - this.lightDistance.visible = false; - - } else { - - this.lightDistance.scale.set( d, d, d ); - - } - - this.add( this.lightDistance ); - */ - - } - - dispose() { - - this.geometry.dispose(); - this.material.dispose(); - - } - - update() { - - this.light.updateWorldMatrix( true, false ); - - if ( this.color !== undefined ) { - - this.material.color.set( this.color ); - - } else { - - this.material.color.copy( this.light.color ); - - } - - /* - const d = this.light.distance; - - if ( d === 0.0 ) { - - this.lightDistance.visible = false; - - } else { - - this.lightDistance.visible = true; - this.lightDistance.scale.set( d, d, d ); - - } - */ - - } - - } - - const _vector$1 = /*@__PURE__*/ new Vector3(); - const _color1 = /*@__PURE__*/ new Color(); - const _color2 = /*@__PURE__*/ new Color(); - - class HemisphereLightHelper extends Object3D { - - constructor( light, size, color ) { - - super(); - - this.light = light; - - this.matrix = light.matrixWorld; - this.matrixAutoUpdate = false; - - this.color = color; - - this.type = 'HemisphereLightHelper'; - - const geometry = new OctahedronGeometry( size ); - geometry.rotateY( Math.PI * 0.5 ); - - this.material = new MeshBasicMaterial( { wireframe: true, fog: false, toneMapped: false } ); - if ( this.color === undefined ) this.material.vertexColors = true; - - const position = geometry.getAttribute( 'position' ); - const colors = new Float32Array( position.count * 3 ); - - geometry.setAttribute( 'color', new BufferAttribute( colors, 3 ) ); - - this.add( new Mesh( geometry, this.material ) ); - - this.update(); - - } - - dispose() { - - this.children[ 0 ].geometry.dispose(); - this.children[ 0 ].material.dispose(); - - } - - update() { - - const mesh = this.children[ 0 ]; - - if ( this.color !== undefined ) { - - this.material.color.set( this.color ); - - } else { - - const colors = mesh.geometry.getAttribute( 'color' ); - - _color1.copy( this.light.color ); - _color2.copy( this.light.groundColor ); - - for ( let i = 0, l = colors.count; i < l; i ++ ) { - - const color = ( i < ( l / 2 ) ) ? _color1 : _color2; - - colors.setXYZ( i, color.r, color.g, color.b ); - - } - - colors.needsUpdate = true; - - } - - this.light.updateWorldMatrix( true, false ); - - mesh.lookAt( _vector$1.setFromMatrixPosition( this.light.matrixWorld ).negate() ); - - } - - } - - class GridHelper extends LineSegments { - - constructor( size = 10, divisions = 10, color1 = 0x444444, color2 = 0x888888 ) { - - color1 = new Color( color1 ); - color2 = new Color( color2 ); - - const center = divisions / 2; - const step = size / divisions; - const halfSize = size / 2; - - const vertices = [], colors = []; - - for ( let i = 0, j = 0, k = - halfSize; i <= divisions; i ++, k += step ) { - - vertices.push( - halfSize, 0, k, halfSize, 0, k ); - vertices.push( k, 0, - halfSize, k, 0, halfSize ); - - const color = i === center ? color1 : color2; - - color.toArray( colors, j ); j += 3; - color.toArray( colors, j ); j += 3; - color.toArray( colors, j ); j += 3; - color.toArray( colors, j ); j += 3; - - } - - const geometry = new BufferGeometry(); - geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - geometry.setAttribute( 'color', new Float32BufferAttribute( colors, 3 ) ); - - const material = new LineBasicMaterial( { vertexColors: true, toneMapped: false } ); - - super( geometry, material ); - - this.type = 'GridHelper'; - - } - - dispose() { - - this.geometry.dispose(); - this.material.dispose(); - - } - - } - - class PolarGridHelper extends LineSegments { - - constructor( radius = 10, sectors = 16, rings = 8, divisions = 64, color1 = 0x444444, color2 = 0x888888 ) { - - color1 = new Color( color1 ); - color2 = new Color( color2 ); - - const vertices = []; - const colors = []; - - // create the sectors - - if ( sectors > 1 ) { - - for ( let i = 0; i < sectors; i ++ ) { - - const v = ( i / sectors ) * ( Math.PI * 2 ); - - const x = Math.sin( v ) * radius; - const z = Math.cos( v ) * radius; - - vertices.push( 0, 0, 0 ); - vertices.push( x, 0, z ); - - const color = ( i & 1 ) ? color1 : color2; - - colors.push( color.r, color.g, color.b ); - colors.push( color.r, color.g, color.b ); - - } - - } - - // create the rings - - for ( let i = 0; i < rings; i ++ ) { - - const color = ( i & 1 ) ? color1 : color2; - - const r = radius - ( radius / rings * i ); - - for ( let j = 0; j < divisions; j ++ ) { - - // first vertex - - let v = ( j / divisions ) * ( Math.PI * 2 ); - - let x = Math.sin( v ) * r; - let z = Math.cos( v ) * r; - - vertices.push( x, 0, z ); - colors.push( color.r, color.g, color.b ); - - // second vertex - - v = ( ( j + 1 ) / divisions ) * ( Math.PI * 2 ); - - x = Math.sin( v ) * r; - z = Math.cos( v ) * r; - - vertices.push( x, 0, z ); - colors.push( color.r, color.g, color.b ); - - } - - } - - const geometry = new BufferGeometry(); - geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - geometry.setAttribute( 'color', new Float32BufferAttribute( colors, 3 ) ); - - const material = new LineBasicMaterial( { vertexColors: true, toneMapped: false } ); - - super( geometry, material ); - - this.type = 'PolarGridHelper'; - - } - - dispose() { - - this.geometry.dispose(); - this.material.dispose(); - - } - - } - - const _v1 = /*@__PURE__*/ new Vector3(); - const _v2 = /*@__PURE__*/ new Vector3(); - const _v3 = /*@__PURE__*/ new Vector3(); - - class DirectionalLightHelper extends Object3D { - - constructor( light, size, color ) { - - super(); - - this.light = light; - - this.matrix = light.matrixWorld; - this.matrixAutoUpdate = false; - - this.color = color; - - this.type = 'DirectionalLightHelper'; - - if ( size === undefined ) size = 1; - - let geometry = new BufferGeometry(); - geometry.setAttribute( 'position', new Float32BufferAttribute( [ - - size, size, 0, - size, size, 0, - size, - size, 0, - - size, - size, 0, - - size, size, 0 - ], 3 ) ); - - const material = new LineBasicMaterial( { fog: false, toneMapped: false } ); - - this.lightPlane = new Line( geometry, material ); - this.add( this.lightPlane ); - - geometry = new BufferGeometry(); - geometry.setAttribute( 'position', new Float32BufferAttribute( [ 0, 0, 0, 0, 0, 1 ], 3 ) ); - - this.targetLine = new Line( geometry, material ); - this.add( this.targetLine ); - - this.update(); - - } - - dispose() { - - this.lightPlane.geometry.dispose(); - this.lightPlane.material.dispose(); - this.targetLine.geometry.dispose(); - this.targetLine.material.dispose(); - - } - - update() { - - this.light.updateWorldMatrix( true, false ); - this.light.target.updateWorldMatrix( true, false ); - - _v1.setFromMatrixPosition( this.light.matrixWorld ); - _v2.setFromMatrixPosition( this.light.target.matrixWorld ); - _v3.subVectors( _v2, _v1 ); - - this.lightPlane.lookAt( _v2 ); - - if ( this.color !== undefined ) { - - this.lightPlane.material.color.set( this.color ); - this.targetLine.material.color.set( this.color ); - - } else { - - this.lightPlane.material.color.copy( this.light.color ); - this.targetLine.material.color.copy( this.light.color ); - - } - - this.targetLine.lookAt( _v2 ); - this.targetLine.scale.z = _v3.length(); - - } - - } - - const _vector = /*@__PURE__*/ new Vector3(); - const _camera = /*@__PURE__*/ new Camera(); - - /** - * - shows frustum, line of sight and up of the camera - * - suitable for fast updates - * - based on frustum visualization in lightgl.js shadowmap example - * https://github.com/evanw/lightgl.js/blob/master/tests/shadowmap.html - */ - - class CameraHelper extends LineSegments { - - constructor( camera ) { - - const geometry = new BufferGeometry(); - const material = new LineBasicMaterial( { color: 0xffffff, vertexColors: true, toneMapped: false } ); - - const vertices = []; - const colors = []; - - const pointMap = {}; - - // near - - addLine( 'n1', 'n2' ); - addLine( 'n2', 'n4' ); - addLine( 'n4', 'n3' ); - addLine( 'n3', 'n1' ); - - // far - - addLine( 'f1', 'f2' ); - addLine( 'f2', 'f4' ); - addLine( 'f4', 'f3' ); - addLine( 'f3', 'f1' ); - - // sides - - addLine( 'n1', 'f1' ); - addLine( 'n2', 'f2' ); - addLine( 'n3', 'f3' ); - addLine( 'n4', 'f4' ); - - // cone - - addLine( 'p', 'n1' ); - addLine( 'p', 'n2' ); - addLine( 'p', 'n3' ); - addLine( 'p', 'n4' ); - - // up - - addLine( 'u1', 'u2' ); - addLine( 'u2', 'u3' ); - addLine( 'u3', 'u1' ); - - // target - - addLine( 'c', 't' ); - addLine( 'p', 'c' ); - - // cross - - addLine( 'cn1', 'cn2' ); - addLine( 'cn3', 'cn4' ); - - addLine( 'cf1', 'cf2' ); - addLine( 'cf3', 'cf4' ); - - function addLine( a, b ) { - - addPoint( a ); - addPoint( b ); - - } - - function addPoint( id ) { - - vertices.push( 0, 0, 0 ); - colors.push( 0, 0, 0 ); - - if ( pointMap[ id ] === undefined ) { - - pointMap[ id ] = []; - - } - - pointMap[ id ].push( ( vertices.length / 3 ) - 1 ); - - } - - geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - geometry.setAttribute( 'color', new Float32BufferAttribute( colors, 3 ) ); - - super( geometry, material ); - - this.type = 'CameraHelper'; - - this.camera = camera; - if ( this.camera.updateProjectionMatrix ) this.camera.updateProjectionMatrix(); - - this.matrix = camera.matrixWorld; - this.matrixAutoUpdate = false; - - this.pointMap = pointMap; - - this.update(); - - // colors - - const colorFrustum = new Color( 0xffaa00 ); - const colorCone = new Color( 0xff0000 ); - const colorUp = new Color( 0x00aaff ); - const colorTarget = new Color( 0xffffff ); - const colorCross = new Color( 0x333333 ); - - this.setColors( colorFrustum, colorCone, colorUp, colorTarget, colorCross ); - - } - - setColors( frustum, cone, up, target, cross ) { - - const geometry = this.geometry; - - const colorAttribute = geometry.getAttribute( 'color' ); - - // near - - colorAttribute.setXYZ( 0, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 1, frustum.r, frustum.g, frustum.b ); // n1, n2 - colorAttribute.setXYZ( 2, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 3, frustum.r, frustum.g, frustum.b ); // n2, n4 - colorAttribute.setXYZ( 4, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 5, frustum.r, frustum.g, frustum.b ); // n4, n3 - colorAttribute.setXYZ( 6, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 7, frustum.r, frustum.g, frustum.b ); // n3, n1 - - // far - - colorAttribute.setXYZ( 8, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 9, frustum.r, frustum.g, frustum.b ); // f1, f2 - colorAttribute.setXYZ( 10, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 11, frustum.r, frustum.g, frustum.b ); // f2, f4 - colorAttribute.setXYZ( 12, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 13, frustum.r, frustum.g, frustum.b ); // f4, f3 - colorAttribute.setXYZ( 14, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 15, frustum.r, frustum.g, frustum.b ); // f3, f1 - - // sides - - colorAttribute.setXYZ( 16, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 17, frustum.r, frustum.g, frustum.b ); // n1, f1 - colorAttribute.setXYZ( 18, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 19, frustum.r, frustum.g, frustum.b ); // n2, f2 - colorAttribute.setXYZ( 20, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 21, frustum.r, frustum.g, frustum.b ); // n3, f3 - colorAttribute.setXYZ( 22, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 23, frustum.r, frustum.g, frustum.b ); // n4, f4 - - // cone - - colorAttribute.setXYZ( 24, cone.r, cone.g, cone.b ); colorAttribute.setXYZ( 25, cone.r, cone.g, cone.b ); // p, n1 - colorAttribute.setXYZ( 26, cone.r, cone.g, cone.b ); colorAttribute.setXYZ( 27, cone.r, cone.g, cone.b ); // p, n2 - colorAttribute.setXYZ( 28, cone.r, cone.g, cone.b ); colorAttribute.setXYZ( 29, cone.r, cone.g, cone.b ); // p, n3 - colorAttribute.setXYZ( 30, cone.r, cone.g, cone.b ); colorAttribute.setXYZ( 31, cone.r, cone.g, cone.b ); // p, n4 - - // up - - colorAttribute.setXYZ( 32, up.r, up.g, up.b ); colorAttribute.setXYZ( 33, up.r, up.g, up.b ); // u1, u2 - colorAttribute.setXYZ( 34, up.r, up.g, up.b ); colorAttribute.setXYZ( 35, up.r, up.g, up.b ); // u2, u3 - colorAttribute.setXYZ( 36, up.r, up.g, up.b ); colorAttribute.setXYZ( 37, up.r, up.g, up.b ); // u3, u1 - - // target - - colorAttribute.setXYZ( 38, target.r, target.g, target.b ); colorAttribute.setXYZ( 39, target.r, target.g, target.b ); // c, t - colorAttribute.setXYZ( 40, cross.r, cross.g, cross.b ); colorAttribute.setXYZ( 41, cross.r, cross.g, cross.b ); // p, c - - // cross - - colorAttribute.setXYZ( 42, cross.r, cross.g, cross.b ); colorAttribute.setXYZ( 43, cross.r, cross.g, cross.b ); // cn1, cn2 - colorAttribute.setXYZ( 44, cross.r, cross.g, cross.b ); colorAttribute.setXYZ( 45, cross.r, cross.g, cross.b ); // cn3, cn4 - - colorAttribute.setXYZ( 46, cross.r, cross.g, cross.b ); colorAttribute.setXYZ( 47, cross.r, cross.g, cross.b ); // cf1, cf2 - colorAttribute.setXYZ( 48, cross.r, cross.g, cross.b ); colorAttribute.setXYZ( 49, cross.r, cross.g, cross.b ); // cf3, cf4 - - colorAttribute.needsUpdate = true; - - } - - update() { - - const geometry = this.geometry; - const pointMap = this.pointMap; - - const w = 1, h = 1; - - // we need just camera projection matrix inverse - // world matrix must be identity - - _camera.projectionMatrixInverse.copy( this.camera.projectionMatrixInverse ); - - // center / target - - setPoint( 'c', pointMap, geometry, _camera, 0, 0, - 1 ); - setPoint( 't', pointMap, geometry, _camera, 0, 0, 1 ); - - // near - - setPoint( 'n1', pointMap, geometry, _camera, - w, - h, - 1 ); - setPoint( 'n2', pointMap, geometry, _camera, w, - h, - 1 ); - setPoint( 'n3', pointMap, geometry, _camera, - w, h, - 1 ); - setPoint( 'n4', pointMap, geometry, _camera, w, h, - 1 ); - - // far - - setPoint( 'f1', pointMap, geometry, _camera, - w, - h, 1 ); - setPoint( 'f2', pointMap, geometry, _camera, w, - h, 1 ); - setPoint( 'f3', pointMap, geometry, _camera, - w, h, 1 ); - setPoint( 'f4', pointMap, geometry, _camera, w, h, 1 ); - - // up - - setPoint( 'u1', pointMap, geometry, _camera, w * 0.7, h * 1.1, - 1 ); - setPoint( 'u2', pointMap, geometry, _camera, - w * 0.7, h * 1.1, - 1 ); - setPoint( 'u3', pointMap, geometry, _camera, 0, h * 2, - 1 ); - - // cross - - setPoint( 'cf1', pointMap, geometry, _camera, - w, 0, 1 ); - setPoint( 'cf2', pointMap, geometry, _camera, w, 0, 1 ); - setPoint( 'cf3', pointMap, geometry, _camera, 0, - h, 1 ); - setPoint( 'cf4', pointMap, geometry, _camera, 0, h, 1 ); - - setPoint( 'cn1', pointMap, geometry, _camera, - w, 0, - 1 ); - setPoint( 'cn2', pointMap, geometry, _camera, w, 0, - 1 ); - setPoint( 'cn3', pointMap, geometry, _camera, 0, - h, - 1 ); - setPoint( 'cn4', pointMap, geometry, _camera, 0, h, - 1 ); - - geometry.getAttribute( 'position' ).needsUpdate = true; - - } - - dispose() { - - this.geometry.dispose(); - this.material.dispose(); - - } - - } - - - function setPoint( point, pointMap, geometry, camera, x, y, z ) { - - _vector.set( x, y, z ).unproject( camera ); - - const points = pointMap[ point ]; - - if ( points !== undefined ) { - - const position = geometry.getAttribute( 'position' ); - - for ( let i = 0, l = points.length; i < l; i ++ ) { - - position.setXYZ( points[ i ], _vector.x, _vector.y, _vector.z ); - - } - - } - - } - - const _box = /*@__PURE__*/ new Box3(); - - class BoxHelper extends LineSegments { - - constructor( object, color = 0xffff00 ) { - - const indices = new Uint16Array( [ 0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7 ] ); - const positions = new Float32Array( 8 * 3 ); - - const geometry = new BufferGeometry(); - geometry.setIndex( new BufferAttribute( indices, 1 ) ); - geometry.setAttribute( 'position', new BufferAttribute( positions, 3 ) ); - - super( geometry, new LineBasicMaterial( { color: color, toneMapped: false } ) ); - - this.object = object; - this.type = 'BoxHelper'; - - this.matrixAutoUpdate = false; - - this.update(); - - } - - update( object ) { - - if ( object !== undefined ) { - - console.warn( 'THREE.BoxHelper: .update() has no longer arguments.' ); - - } - - if ( this.object !== undefined ) { - - _box.setFromObject( this.object ); - - } - - if ( _box.isEmpty() ) return; - - const min = _box.min; - const max = _box.max; - - /* - 5____4 - 1/___0/| - | 6__|_7 - 2/___3/ - - 0: max.x, max.y, max.z - 1: min.x, max.y, max.z - 2: min.x, min.y, max.z - 3: max.x, min.y, max.z - 4: max.x, max.y, min.z - 5: min.x, max.y, min.z - 6: min.x, min.y, min.z - 7: max.x, min.y, min.z - */ - - const position = this.geometry.attributes.position; - const array = position.array; - - array[ 0 ] = max.x; array[ 1 ] = max.y; array[ 2 ] = max.z; - array[ 3 ] = min.x; array[ 4 ] = max.y; array[ 5 ] = max.z; - array[ 6 ] = min.x; array[ 7 ] = min.y; array[ 8 ] = max.z; - array[ 9 ] = max.x; array[ 10 ] = min.y; array[ 11 ] = max.z; - array[ 12 ] = max.x; array[ 13 ] = max.y; array[ 14 ] = min.z; - array[ 15 ] = min.x; array[ 16 ] = max.y; array[ 17 ] = min.z; - array[ 18 ] = min.x; array[ 19 ] = min.y; array[ 20 ] = min.z; - array[ 21 ] = max.x; array[ 22 ] = min.y; array[ 23 ] = min.z; - - position.needsUpdate = true; - - this.geometry.computeBoundingSphere(); - - } - - setFromObject( object ) { - - this.object = object; - this.update(); - - return this; - - } - - copy( source, recursive ) { - - super.copy( source, recursive ); - - this.object = source.object; - - return this; - - } - - dispose() { - - this.geometry.dispose(); - this.material.dispose(); - - } - - } - - class Box3Helper extends LineSegments { - - constructor( box, color = 0xffff00 ) { - - const indices = new Uint16Array( [ 0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7 ] ); - - const positions = [ 1, 1, 1, - 1, 1, 1, - 1, - 1, 1, 1, - 1, 1, 1, 1, - 1, - 1, 1, - 1, - 1, - 1, - 1, 1, - 1, - 1 ]; - - const geometry = new BufferGeometry(); - - geometry.setIndex( new BufferAttribute( indices, 1 ) ); - - geometry.setAttribute( 'position', new Float32BufferAttribute( positions, 3 ) ); - - super( geometry, new LineBasicMaterial( { color: color, toneMapped: false } ) ); - - this.box = box; - - this.type = 'Box3Helper'; - - this.geometry.computeBoundingSphere(); - - } - - updateMatrixWorld( force ) { - - const box = this.box; - - if ( box.isEmpty() ) return; - - box.getCenter( this.position ); - - box.getSize( this.scale ); - - this.scale.multiplyScalar( 0.5 ); - - super.updateMatrixWorld( force ); - - } - - dispose() { - - this.geometry.dispose(); - this.material.dispose(); - - } - - } - - class PlaneHelper extends Line { - - constructor( plane, size = 1, hex = 0xffff00 ) { - - const color = hex; - - const positions = [ 1, - 1, 0, - 1, 1, 0, - 1, - 1, 0, 1, 1, 0, - 1, 1, 0, - 1, - 1, 0, 1, - 1, 0, 1, 1, 0 ]; - - const geometry = new BufferGeometry(); - geometry.setAttribute( 'position', new Float32BufferAttribute( positions, 3 ) ); - geometry.computeBoundingSphere(); - - super( geometry, new LineBasicMaterial( { color: color, toneMapped: false } ) ); - - this.type = 'PlaneHelper'; - - this.plane = plane; - - this.size = size; - - const positions2 = [ 1, 1, 0, - 1, 1, 0, - 1, - 1, 0, 1, 1, 0, - 1, - 1, 0, 1, - 1, 0 ]; - - const geometry2 = new BufferGeometry(); - geometry2.setAttribute( 'position', new Float32BufferAttribute( positions2, 3 ) ); - geometry2.computeBoundingSphere(); - - this.add( new Mesh( geometry2, new MeshBasicMaterial( { color: color, opacity: 0.2, transparent: true, depthWrite: false, toneMapped: false } ) ) ); - - } - - updateMatrixWorld( force ) { - - this.position.set( 0, 0, 0 ); - - this.scale.set( 0.5 * this.size, 0.5 * this.size, 1 ); - - this.lookAt( this.plane.normal ); - - this.translateZ( - this.plane.constant ); - - super.updateMatrixWorld( force ); - - } - - dispose() { - - this.geometry.dispose(); - this.material.dispose(); - this.children[ 0 ].geometry.dispose(); - this.children[ 0 ].material.dispose(); - - } - - } - - const _axis = /*@__PURE__*/ new Vector3(); - let _lineGeometry, _coneGeometry; - - class ArrowHelper extends Object3D { - - // dir is assumed to be normalized - - constructor( dir = new Vector3( 0, 0, 1 ), origin = new Vector3( 0, 0, 0 ), length = 1, color = 0xffff00, headLength = length * 0.2, headWidth = headLength * 0.2 ) { - - super(); - - this.type = 'ArrowHelper'; - - if ( _lineGeometry === undefined ) { - - _lineGeometry = new BufferGeometry(); - _lineGeometry.setAttribute( 'position', new Float32BufferAttribute( [ 0, 0, 0, 0, 1, 0 ], 3 ) ); - - _coneGeometry = new CylinderGeometry( 0, 0.5, 1, 5, 1 ); - _coneGeometry.translate( 0, - 0.5, 0 ); - - } - - this.position.copy( origin ); - - this.line = new Line( _lineGeometry, new LineBasicMaterial( { color: color, toneMapped: false } ) ); - this.line.matrixAutoUpdate = false; - this.add( this.line ); - - this.cone = new Mesh( _coneGeometry, new MeshBasicMaterial( { color: color, toneMapped: false } ) ); - this.cone.matrixAutoUpdate = false; - this.add( this.cone ); - - this.setDirection( dir ); - this.setLength( length, headLength, headWidth ); - - } - - setDirection( dir ) { - - // dir is assumed to be normalized - - if ( dir.y > 0.99999 ) { - - this.quaternion.set( 0, 0, 0, 1 ); - - } else if ( dir.y < - 0.99999 ) { - - this.quaternion.set( 1, 0, 0, 0 ); - - } else { - - _axis.set( dir.z, 0, - dir.x ).normalize(); - - const radians = Math.acos( dir.y ); - - this.quaternion.setFromAxisAngle( _axis, radians ); - - } - - } - - setLength( length, headLength = length * 0.2, headWidth = headLength * 0.2 ) { - - this.line.scale.set( 1, Math.max( 0.0001, length - headLength ), 1 ); // see #17458 - this.line.updateMatrix(); - - this.cone.scale.set( headWidth, headLength, headWidth ); - this.cone.position.y = length; - this.cone.updateMatrix(); - - } - - setColor( color ) { - - this.line.material.color.set( color ); - this.cone.material.color.set( color ); - - } - - copy( source ) { - - super.copy( source, false ); - - this.line.copy( source.line ); - this.cone.copy( source.cone ); - - return this; - - } - - dispose() { - - this.line.geometry.dispose(); - this.line.material.dispose(); - this.cone.geometry.dispose(); - this.cone.material.dispose(); - - } - - } - - class AxesHelper extends LineSegments { - - constructor( size = 1 ) { - - const vertices = [ - 0, 0, 0, size, 0, 0, - 0, 0, 0, 0, size, 0, - 0, 0, 0, 0, 0, size - ]; - - const colors = [ - 1, 0, 0, 1, 0.6, 0, - 0, 1, 0, 0.6, 1, 0, - 0, 0, 1, 0, 0.6, 1 - ]; - - const geometry = new BufferGeometry(); - geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - geometry.setAttribute( 'color', new Float32BufferAttribute( colors, 3 ) ); - - const material = new LineBasicMaterial( { vertexColors: true, toneMapped: false } ); - - super( geometry, material ); - - this.type = 'AxesHelper'; - - } - - setColors( xAxisColor, yAxisColor, zAxisColor ) { - - const color = new Color(); - const array = this.geometry.attributes.color.array; - - color.set( xAxisColor ); - color.toArray( array, 0 ); - color.toArray( array, 3 ); - - color.set( yAxisColor ); - color.toArray( array, 6 ); - color.toArray( array, 9 ); - - color.set( zAxisColor ); - color.toArray( array, 12 ); - color.toArray( array, 15 ); - - this.geometry.attributes.color.needsUpdate = true; - - return this; - - } - - dispose() { - - this.geometry.dispose(); - this.material.dispose(); - - } - - } - - class ShapePath { - - constructor() { - - this.type = 'ShapePath'; - - this.color = new Color(); - - this.subPaths = []; - this.currentPath = null; - - } - - moveTo( x, y ) { - - this.currentPath = new Path(); - this.subPaths.push( this.currentPath ); - this.currentPath.moveTo( x, y ); - - return this; - - } - - lineTo( x, y ) { - - this.currentPath.lineTo( x, y ); - - return this; - - } - - quadraticCurveTo( aCPx, aCPy, aX, aY ) { - - this.currentPath.quadraticCurveTo( aCPx, aCPy, aX, aY ); - - return this; - - } - - bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) { - - this.currentPath.bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ); - - return this; - - } - - splineThru( pts ) { - - this.currentPath.splineThru( pts ); - - return this; - - } - - toShapes( isCCW ) { - - function toShapesNoHoles( inSubpaths ) { - - const shapes = []; - - for ( let i = 0, l = inSubpaths.length; i < l; i ++ ) { - - const tmpPath = inSubpaths[ i ]; - - const tmpShape = new Shape(); - tmpShape.curves = tmpPath.curves; - - shapes.push( tmpShape ); - - } - - return shapes; - - } - - function isPointInsidePolygon( inPt, inPolygon ) { - - const polyLen = inPolygon.length; - - // inPt on polygon contour => immediate success or - // toggling of inside/outside at every single! intersection point of an edge - // with the horizontal line through inPt, left of inPt - // not counting lowerY endpoints of edges and whole edges on that line - let inside = false; - for ( let p = polyLen - 1, q = 0; q < polyLen; p = q ++ ) { - - let edgeLowPt = inPolygon[ p ]; - let edgeHighPt = inPolygon[ q ]; - - let edgeDx = edgeHighPt.x - edgeLowPt.x; - let edgeDy = edgeHighPt.y - edgeLowPt.y; - - if ( Math.abs( edgeDy ) > Number.EPSILON ) { - - // not parallel - if ( edgeDy < 0 ) { - - edgeLowPt = inPolygon[ q ]; edgeDx = - edgeDx; - edgeHighPt = inPolygon[ p ]; edgeDy = - edgeDy; - - } - - if ( ( inPt.y < edgeLowPt.y ) || ( inPt.y > edgeHighPt.y ) ) continue; - - if ( inPt.y === edgeLowPt.y ) { - - if ( inPt.x === edgeLowPt.x ) return true; // inPt is on contour ? - // continue; // no intersection or edgeLowPt => doesn't count !!! - - } else { - - const perpEdge = edgeDy * ( inPt.x - edgeLowPt.x ) - edgeDx * ( inPt.y - edgeLowPt.y ); - if ( perpEdge === 0 ) return true; // inPt is on contour ? - if ( perpEdge < 0 ) continue; - inside = ! inside; // true intersection left of inPt - - } - - } else { - - // parallel or collinear - if ( inPt.y !== edgeLowPt.y ) continue; // parallel - // edge lies on the same horizontal line as inPt - if ( ( ( edgeHighPt.x <= inPt.x ) && ( inPt.x <= edgeLowPt.x ) ) || - ( ( edgeLowPt.x <= inPt.x ) && ( inPt.x <= edgeHighPt.x ) ) ) return true; // inPt: Point on contour ! - // continue; - - } - - } - - return inside; - - } - - const isClockWise = ShapeUtils.isClockWise; - - const subPaths = this.subPaths; - if ( subPaths.length === 0 ) return []; - - let solid, tmpPath, tmpShape; - const shapes = []; - - if ( subPaths.length === 1 ) { - - tmpPath = subPaths[ 0 ]; - tmpShape = new Shape(); - tmpShape.curves = tmpPath.curves; - shapes.push( tmpShape ); - return shapes; - - } - - let holesFirst = ! isClockWise( subPaths[ 0 ].getPoints() ); - holesFirst = isCCW ? ! holesFirst : holesFirst; - - // console.log("Holes first", holesFirst); - - const betterShapeHoles = []; - const newShapes = []; - let newShapeHoles = []; - let mainIdx = 0; - let tmpPoints; - - newShapes[ mainIdx ] = undefined; - newShapeHoles[ mainIdx ] = []; - - for ( let i = 0, l = subPaths.length; i < l; i ++ ) { - - tmpPath = subPaths[ i ]; - tmpPoints = tmpPath.getPoints(); - solid = isClockWise( tmpPoints ); - solid = isCCW ? ! solid : solid; - - if ( solid ) { - - if ( ( ! holesFirst ) && ( newShapes[ mainIdx ] ) ) mainIdx ++; - - newShapes[ mainIdx ] = { s: new Shape(), p: tmpPoints }; - newShapes[ mainIdx ].s.curves = tmpPath.curves; - - if ( holesFirst ) mainIdx ++; - newShapeHoles[ mainIdx ] = []; - - //console.log('cw', i); - - } else { - - newShapeHoles[ mainIdx ].push( { h: tmpPath, p: tmpPoints[ 0 ] } ); - - //console.log('ccw', i); - - } - - } - - // only Holes? -> probably all Shapes with wrong orientation - if ( ! newShapes[ 0 ] ) return toShapesNoHoles( subPaths ); - - - if ( newShapes.length > 1 ) { - - let ambiguous = false; - let toChange = 0; - - for ( let sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) { - - betterShapeHoles[ sIdx ] = []; - - } - - for ( let sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) { - - const sho = newShapeHoles[ sIdx ]; - - for ( let hIdx = 0; hIdx < sho.length; hIdx ++ ) { - - const ho = sho[ hIdx ]; - let hole_unassigned = true; - - for ( let s2Idx = 0; s2Idx < newShapes.length; s2Idx ++ ) { - - if ( isPointInsidePolygon( ho.p, newShapes[ s2Idx ].p ) ) { - - if ( sIdx !== s2Idx ) toChange ++; - - if ( hole_unassigned ) { - - hole_unassigned = false; - betterShapeHoles[ s2Idx ].push( ho ); - - } else { - - ambiguous = true; - - } - - } - - } - - if ( hole_unassigned ) { - - betterShapeHoles[ sIdx ].push( ho ); - - } - - } - - } - - if ( toChange > 0 && ambiguous === false ) { - - newShapeHoles = betterShapeHoles; - - } - - } - - let tmpHoles; - - for ( let i = 0, il = newShapes.length; i < il; i ++ ) { - - tmpShape = newShapes[ i ].s; - shapes.push( tmpShape ); - tmpHoles = newShapeHoles[ i ]; - - for ( let j = 0, jl = tmpHoles.length; j < jl; j ++ ) { - - tmpShape.holes.push( tmpHoles[ j ].h ); - - } - - } - - //console.log("shape", shapes); - - return shapes; - - } - - } - - class BoxBufferGeometry extends BoxGeometry { // @deprecated, r144 - - constructor( width, height, depth, widthSegments, heightSegments, depthSegments ) { - - console.warn( 'THREE.BoxBufferGeometry has been renamed to THREE.BoxGeometry.' ); - super( width, height, depth, widthSegments, heightSegments, depthSegments ); - - - } - - } - - class CapsuleBufferGeometry extends CapsuleGeometry { // @deprecated, r144 - - constructor( radius, length, capSegments, radialSegments ) { - - console.warn( 'THREE.CapsuleBufferGeometry has been renamed to THREE.CapsuleGeometry.' ); - super( radius, length, capSegments, radialSegments ); - - } - - } - - class CircleBufferGeometry extends CircleGeometry { // @deprecated, r144 - - constructor( radius, segments, thetaStart, thetaLength ) { - - console.warn( 'THREE.CircleBufferGeometry has been renamed to THREE.CircleGeometry.' ); - super( radius, segments, thetaStart, thetaLength ); - - } - - } - - class ConeBufferGeometry extends ConeGeometry { // @deprecated, r144 - - constructor( radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ) { - - console.warn( 'THREE.ConeBufferGeometry has been renamed to THREE.ConeGeometry.' ); - super( radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ); - - } - - } - - class CylinderBufferGeometry extends CylinderGeometry { // @deprecated, r144 - - constructor( radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ) { - - console.warn( 'THREE.CylinderBufferGeometry has been renamed to THREE.CylinderGeometry.' ); - super( radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ); - - } - - } - - class DodecahedronBufferGeometry extends DodecahedronGeometry { // @deprecated, r144 - - constructor( radius, detail ) { - - console.warn( 'THREE.DodecahedronBufferGeometry has been renamed to THREE.DodecahedronGeometry.' ); - super( radius, detail ); - - } - - } - - class ExtrudeBufferGeometry extends ExtrudeGeometry { // @deprecated, r144 - - constructor( shapes, options ) { - - console.warn( 'THREE.ExtrudeBufferGeometry has been renamed to THREE.ExtrudeGeometry.' ); - super( shapes, options ); - - } - - } - - class IcosahedronBufferGeometry extends IcosahedronGeometry { // @deprecated, r144 - - constructor( radius, detail ) { - - console.warn( 'THREE.IcosahedronBufferGeometry has been renamed to THREE.IcosahedronGeometry.' ); - super( radius, detail ); - - } - - } - - class LatheBufferGeometry extends LatheGeometry { // @deprecated, r144 - - constructor( points, segments, phiStart, phiLength ) { - - console.warn( 'THREE.LatheBufferGeometry has been renamed to THREE.LatheGeometry.' ); - super( points, segments, phiStart, phiLength ); - - } - - } - - class OctahedronBufferGeometry extends OctahedronGeometry { // @deprecated, r144 - - constructor( radius, detail ) { - - console.warn( 'THREE.OctahedronBufferGeometry has been renamed to THREE.OctahedronGeometry.' ); - super( radius, detail ); - - } - - } - - class PlaneBufferGeometry extends PlaneGeometry { // @deprecated, r144 - - constructor( width, height, widthSegments, heightSegments ) { - - console.warn( 'THREE.PlaneBufferGeometry has been renamed to THREE.PlaneGeometry.' ); - super( width, height, widthSegments, heightSegments ); - - } - - } - - class PolyhedronBufferGeometry extends PolyhedronGeometry { // @deprecated, r144 - - constructor( vertices, indices, radius, detail ) { - - console.warn( 'THREE.PolyhedronBufferGeometry has been renamed to THREE.PolyhedronGeometry.' ); - super( vertices, indices, radius, detail ); - - } - - } - - class RingBufferGeometry extends RingGeometry { // @deprecated, r144 - - constructor( innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength ) { - - console.warn( 'THREE.RingBufferGeometry has been renamed to THREE.RingGeometry.' ); - super( innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength ); - - } - - } - - class ShapeBufferGeometry extends ShapeGeometry { // @deprecated, r144 - - constructor( shapes, curveSegments ) { - - console.warn( 'THREE.ShapeBufferGeometry has been renamed to THREE.ShapeGeometry.' ); - super( shapes, curveSegments ); - - } - - } - - class SphereBufferGeometry extends SphereGeometry { // @deprecated, r144 - - constructor( radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength ) { - - console.warn( 'THREE.SphereBufferGeometry has been renamed to THREE.SphereGeometry.' ); - super( radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength ); - - } - - } - - class TetrahedronBufferGeometry extends TetrahedronGeometry { // @deprecated, r144 - - constructor( radius, detail ) { - - console.warn( 'THREE.TetrahedronBufferGeometry has been renamed to THREE.TetrahedronGeometry.' ); - super( radius, detail ); - - } - - } - - class TorusBufferGeometry extends TorusGeometry { // @deprecated, r144 - - constructor( radius, tube, radialSegments, tubularSegments, arc ) { - - console.warn( 'THREE.TorusBufferGeometry has been renamed to THREE.TorusGeometry.' ); - super( radius, tube, radialSegments, tubularSegments, arc ); - - } - - } - - class TorusKnotBufferGeometry extends TorusKnotGeometry { // @deprecated, r144 - - constructor( radius, tube, tubularSegments, radialSegments, p, q ) { - - console.warn( 'THREE.TorusKnotBufferGeometry has been renamed to THREE.TorusKnotGeometry.' ); - super( radius, tube, tubularSegments, radialSegments, p, q ); - - } - - } - - class TubeBufferGeometry extends TubeGeometry { // @deprecated, r144 - - constructor( path, tubularSegments, radius, radialSegments, closed ) { - - console.warn( 'THREE.TubeBufferGeometry has been renamed to THREE.TubeGeometry.' ); - super( path, tubularSegments, radius, radialSegments, closed ); - - } - - } - - if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) { - - __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'register', { detail: { - revision: REVISION, - } } ) ); - - } - - if ( typeof window !== 'undefined' ) { - - if ( window.__THREE__ ) { - - console.warn( 'WARNING: Multiple instances of Three.js being imported.' ); - - } else { - - window.__THREE__ = REVISION; - - } - - } - - exports.ACESFilmicToneMapping = ACESFilmicToneMapping; - exports.AddEquation = AddEquation; - exports.AddOperation = AddOperation; - exports.AdditiveAnimationBlendMode = AdditiveAnimationBlendMode; - exports.AdditiveBlending = AdditiveBlending; - exports.AlphaFormat = AlphaFormat; - exports.AlwaysCompare = AlwaysCompare; - exports.AlwaysDepth = AlwaysDepth; - exports.AlwaysStencilFunc = AlwaysStencilFunc; - exports.AmbientLight = AmbientLight; - exports.AmbientLightProbe = AmbientLightProbe; - exports.AnimationAction = AnimationAction; - exports.AnimationClip = AnimationClip; - exports.AnimationLoader = AnimationLoader; - exports.AnimationMixer = AnimationMixer; - exports.AnimationObjectGroup = AnimationObjectGroup; - exports.AnimationUtils = AnimationUtils; - exports.ArcCurve = ArcCurve; - exports.ArrayCamera = ArrayCamera; - exports.ArrowHelper = ArrowHelper; - exports.Audio = Audio; - exports.AudioAnalyser = AudioAnalyser; - exports.AudioContext = AudioContext; - exports.AudioListener = AudioListener; - exports.AudioLoader = AudioLoader; - exports.AxesHelper = AxesHelper; - exports.BackSide = BackSide; - exports.BasicDepthPacking = BasicDepthPacking; - exports.BasicShadowMap = BasicShadowMap; - exports.Bone = Bone; - exports.BooleanKeyframeTrack = BooleanKeyframeTrack; - exports.Box2 = Box2; - exports.Box3 = Box3; - exports.Box3Helper = Box3Helper; - exports.BoxBufferGeometry = BoxBufferGeometry; - exports.BoxGeometry = BoxGeometry; - exports.BoxHelper = BoxHelper; - exports.BufferAttribute = BufferAttribute; - exports.BufferGeometry = BufferGeometry; - exports.BufferGeometryLoader = BufferGeometryLoader; - exports.ByteType = ByteType; - exports.Cache = Cache; - exports.Camera = Camera; - exports.CameraHelper = CameraHelper; - exports.CanvasTexture = CanvasTexture; - exports.CapsuleBufferGeometry = CapsuleBufferGeometry; - exports.CapsuleGeometry = CapsuleGeometry; - exports.CatmullRomCurve3 = CatmullRomCurve3; - exports.CineonToneMapping = CineonToneMapping; - exports.CircleBufferGeometry = CircleBufferGeometry; - exports.CircleGeometry = CircleGeometry; - exports.ClampToEdgeWrapping = ClampToEdgeWrapping; - exports.Clock = Clock; - exports.Color = Color; - exports.ColorKeyframeTrack = ColorKeyframeTrack; - exports.ColorManagement = ColorManagement; - exports.CompressedArrayTexture = CompressedArrayTexture; - exports.CompressedTexture = CompressedTexture; - exports.CompressedTextureLoader = CompressedTextureLoader; - exports.ConeBufferGeometry = ConeBufferGeometry; - exports.ConeGeometry = ConeGeometry; - exports.CubeCamera = CubeCamera; - exports.CubeReflectionMapping = CubeReflectionMapping; - exports.CubeRefractionMapping = CubeRefractionMapping; - exports.CubeTexture = CubeTexture; - exports.CubeTextureLoader = CubeTextureLoader; - exports.CubeUVReflectionMapping = CubeUVReflectionMapping; - exports.CubicBezierCurve = CubicBezierCurve; - exports.CubicBezierCurve3 = CubicBezierCurve3; - exports.CubicInterpolant = CubicInterpolant; - exports.CullFaceBack = CullFaceBack; - exports.CullFaceFront = CullFaceFront; - exports.CullFaceFrontBack = CullFaceFrontBack; - exports.CullFaceNone = CullFaceNone; - exports.Curve = Curve; - exports.CurvePath = CurvePath; - exports.CustomBlending = CustomBlending; - exports.CustomToneMapping = CustomToneMapping; - exports.CylinderBufferGeometry = CylinderBufferGeometry; - exports.CylinderGeometry = CylinderGeometry; - exports.Cylindrical = Cylindrical; - exports.Data3DTexture = Data3DTexture; - exports.DataArrayTexture = DataArrayTexture; - exports.DataTexture = DataTexture; - exports.DataTextureLoader = DataTextureLoader; - exports.DataUtils = DataUtils; - exports.DecrementStencilOp = DecrementStencilOp; - exports.DecrementWrapStencilOp = DecrementWrapStencilOp; - exports.DefaultLoadingManager = DefaultLoadingManager; - exports.DepthFormat = DepthFormat; - exports.DepthStencilFormat = DepthStencilFormat; - exports.DepthTexture = DepthTexture; - exports.DirectionalLight = DirectionalLight; - exports.DirectionalLightHelper = DirectionalLightHelper; - exports.DiscreteInterpolant = DiscreteInterpolant; - exports.DisplayP3ColorSpace = DisplayP3ColorSpace; - exports.DodecahedronBufferGeometry = DodecahedronBufferGeometry; - exports.DodecahedronGeometry = DodecahedronGeometry; - exports.DoubleSide = DoubleSide; - exports.DstAlphaFactor = DstAlphaFactor; - exports.DstColorFactor = DstColorFactor; - exports.DynamicCopyUsage = DynamicCopyUsage; - exports.DynamicDrawUsage = DynamicDrawUsage; - exports.DynamicReadUsage = DynamicReadUsage; - exports.EdgesGeometry = EdgesGeometry; - exports.EllipseCurve = EllipseCurve; - exports.EqualCompare = EqualCompare; - exports.EqualDepth = EqualDepth; - exports.EqualStencilFunc = EqualStencilFunc; - exports.EquirectangularReflectionMapping = EquirectangularReflectionMapping; - exports.EquirectangularRefractionMapping = EquirectangularRefractionMapping; - exports.Euler = Euler; - exports.EventDispatcher = EventDispatcher; - exports.ExtrudeBufferGeometry = ExtrudeBufferGeometry; - exports.ExtrudeGeometry = ExtrudeGeometry; - exports.FileLoader = FileLoader; - exports.Float16BufferAttribute = Float16BufferAttribute; - exports.Float32BufferAttribute = Float32BufferAttribute; - exports.Float64BufferAttribute = Float64BufferAttribute; - exports.FloatType = FloatType; - exports.Fog = Fog; - exports.FogExp2 = FogExp2; - exports.FramebufferTexture = FramebufferTexture; - exports.FrontSide = FrontSide; - exports.Frustum = Frustum; - exports.GLBufferAttribute = GLBufferAttribute; - exports.GLSL1 = GLSL1; - exports.GLSL3 = GLSL3; - exports.GreaterCompare = GreaterCompare; - exports.GreaterDepth = GreaterDepth; - exports.GreaterEqualCompare = GreaterEqualCompare; - exports.GreaterEqualDepth = GreaterEqualDepth; - exports.GreaterEqualStencilFunc = GreaterEqualStencilFunc; - exports.GreaterStencilFunc = GreaterStencilFunc; - exports.GridHelper = GridHelper; - exports.Group = Group; - exports.HalfFloatType = HalfFloatType; - exports.HemisphereLight = HemisphereLight; - exports.HemisphereLightHelper = HemisphereLightHelper; - exports.HemisphereLightProbe = HemisphereLightProbe; - exports.IcosahedronBufferGeometry = IcosahedronBufferGeometry; - exports.IcosahedronGeometry = IcosahedronGeometry; - exports.ImageBitmapLoader = ImageBitmapLoader; - exports.ImageLoader = ImageLoader; - exports.ImageUtils = ImageUtils; - exports.IncrementStencilOp = IncrementStencilOp; - exports.IncrementWrapStencilOp = IncrementWrapStencilOp; - exports.InstancedBufferAttribute = InstancedBufferAttribute; - exports.InstancedBufferGeometry = InstancedBufferGeometry; - exports.InstancedInterleavedBuffer = InstancedInterleavedBuffer; - exports.InstancedMesh = InstancedMesh; - exports.Int16BufferAttribute = Int16BufferAttribute; - exports.Int32BufferAttribute = Int32BufferAttribute; - exports.Int8BufferAttribute = Int8BufferAttribute; - exports.IntType = IntType; - exports.InterleavedBuffer = InterleavedBuffer; - exports.InterleavedBufferAttribute = InterleavedBufferAttribute; - exports.Interpolant = Interpolant; - exports.InterpolateDiscrete = InterpolateDiscrete; - exports.InterpolateLinear = InterpolateLinear; - exports.InterpolateSmooth = InterpolateSmooth; - exports.InvertStencilOp = InvertStencilOp; - exports.KeepStencilOp = KeepStencilOp; - exports.KeyframeTrack = KeyframeTrack; - exports.LOD = LOD; - exports.LatheBufferGeometry = LatheBufferGeometry; - exports.LatheGeometry = LatheGeometry; - exports.Layers = Layers; - exports.LessCompare = LessCompare; - exports.LessDepth = LessDepth; - exports.LessEqualCompare = LessEqualCompare; - exports.LessEqualDepth = LessEqualDepth; - exports.LessEqualStencilFunc = LessEqualStencilFunc; - exports.LessStencilFunc = LessStencilFunc; - exports.Light = Light; - exports.LightProbe = LightProbe; - exports.Line = Line; - exports.Line3 = Line3; - exports.LineBasicMaterial = LineBasicMaterial; - exports.LineCurve = LineCurve; - exports.LineCurve3 = LineCurve3; - exports.LineDashedMaterial = LineDashedMaterial; - exports.LineLoop = LineLoop; - exports.LineSegments = LineSegments; - exports.LinearEncoding = LinearEncoding; - exports.LinearFilter = LinearFilter; - exports.LinearInterpolant = LinearInterpolant; - exports.LinearMipMapLinearFilter = LinearMipMapLinearFilter; - exports.LinearMipMapNearestFilter = LinearMipMapNearestFilter; - exports.LinearMipmapLinearFilter = LinearMipmapLinearFilter; - exports.LinearMipmapNearestFilter = LinearMipmapNearestFilter; - exports.LinearSRGBColorSpace = LinearSRGBColorSpace; - exports.LinearToneMapping = LinearToneMapping; - exports.Loader = Loader; - exports.LoaderUtils = LoaderUtils; - exports.LoadingManager = LoadingManager; - exports.LoopOnce = LoopOnce; - exports.LoopPingPong = LoopPingPong; - exports.LoopRepeat = LoopRepeat; - exports.LuminanceAlphaFormat = LuminanceAlphaFormat; - exports.LuminanceFormat = LuminanceFormat; - exports.MOUSE = MOUSE; - exports.Material = Material; - exports.MaterialLoader = MaterialLoader; - exports.MathUtils = MathUtils; - exports.Matrix3 = Matrix3; - exports.Matrix4 = Matrix4; - exports.MaxEquation = MaxEquation; - exports.Mesh = Mesh; - exports.MeshBasicMaterial = MeshBasicMaterial; - exports.MeshDepthMaterial = MeshDepthMaterial; - exports.MeshDistanceMaterial = MeshDistanceMaterial; - exports.MeshLambertMaterial = MeshLambertMaterial; - exports.MeshMatcapMaterial = MeshMatcapMaterial; - exports.MeshNormalMaterial = MeshNormalMaterial; - exports.MeshPhongMaterial = MeshPhongMaterial; - exports.MeshPhysicalMaterial = MeshPhysicalMaterial; - exports.MeshStandardMaterial = MeshStandardMaterial; - exports.MeshToonMaterial = MeshToonMaterial; - exports.MinEquation = MinEquation; - exports.MirroredRepeatWrapping = MirroredRepeatWrapping; - exports.MixOperation = MixOperation; - exports.MultiplyBlending = MultiplyBlending; - exports.MultiplyOperation = MultiplyOperation; - exports.NearestFilter = NearestFilter; - exports.NearestMipMapLinearFilter = NearestMipMapLinearFilter; - exports.NearestMipMapNearestFilter = NearestMipMapNearestFilter; - exports.NearestMipmapLinearFilter = NearestMipmapLinearFilter; - exports.NearestMipmapNearestFilter = NearestMipmapNearestFilter; - exports.NeverCompare = NeverCompare; - exports.NeverDepth = NeverDepth; - exports.NeverStencilFunc = NeverStencilFunc; - exports.NoBlending = NoBlending; - exports.NoColorSpace = NoColorSpace; - exports.NoToneMapping = NoToneMapping; - exports.NormalAnimationBlendMode = NormalAnimationBlendMode; - exports.NormalBlending = NormalBlending; - exports.NotEqualCompare = NotEqualCompare; - exports.NotEqualDepth = NotEqualDepth; - exports.NotEqualStencilFunc = NotEqualStencilFunc; - exports.NumberKeyframeTrack = NumberKeyframeTrack; - exports.Object3D = Object3D; - exports.ObjectLoader = ObjectLoader; - exports.ObjectSpaceNormalMap = ObjectSpaceNormalMap; - exports.OctahedronBufferGeometry = OctahedronBufferGeometry; - exports.OctahedronGeometry = OctahedronGeometry; - exports.OneFactor = OneFactor; - exports.OneMinusDstAlphaFactor = OneMinusDstAlphaFactor; - exports.OneMinusDstColorFactor = OneMinusDstColorFactor; - exports.OneMinusSrcAlphaFactor = OneMinusSrcAlphaFactor; - exports.OneMinusSrcColorFactor = OneMinusSrcColorFactor; - exports.OrthographicCamera = OrthographicCamera; - exports.PCFShadowMap = PCFShadowMap; - exports.PCFSoftShadowMap = PCFSoftShadowMap; - exports.PMREMGenerator = PMREMGenerator; - exports.Path = Path; - exports.PerspectiveCamera = PerspectiveCamera; - exports.Plane = Plane; - exports.PlaneBufferGeometry = PlaneBufferGeometry; - exports.PlaneGeometry = PlaneGeometry; - exports.PlaneHelper = PlaneHelper; - exports.PointLight = PointLight; - exports.PointLightHelper = PointLightHelper; - exports.Points = Points; - exports.PointsMaterial = PointsMaterial; - exports.PolarGridHelper = PolarGridHelper; - exports.PolyhedronBufferGeometry = PolyhedronBufferGeometry; - exports.PolyhedronGeometry = PolyhedronGeometry; - exports.PositionalAudio = PositionalAudio; - exports.PropertyBinding = PropertyBinding; - exports.PropertyMixer = PropertyMixer; - exports.QuadraticBezierCurve = QuadraticBezierCurve; - exports.QuadraticBezierCurve3 = QuadraticBezierCurve3; - exports.Quaternion = Quaternion; - exports.QuaternionKeyframeTrack = QuaternionKeyframeTrack; - exports.QuaternionLinearInterpolant = QuaternionLinearInterpolant; - exports.RED_GREEN_RGTC2_Format = RED_GREEN_RGTC2_Format; - exports.RED_RGTC1_Format = RED_RGTC1_Format; - exports.REVISION = REVISION; - exports.RGBADepthPacking = RGBADepthPacking; - exports.RGBAFormat = RGBAFormat; - exports.RGBAIntegerFormat = RGBAIntegerFormat; - exports.RGBA_ASTC_10x10_Format = RGBA_ASTC_10x10_Format; - exports.RGBA_ASTC_10x5_Format = RGBA_ASTC_10x5_Format; - exports.RGBA_ASTC_10x6_Format = RGBA_ASTC_10x6_Format; - exports.RGBA_ASTC_10x8_Format = RGBA_ASTC_10x8_Format; - exports.RGBA_ASTC_12x10_Format = RGBA_ASTC_12x10_Format; - exports.RGBA_ASTC_12x12_Format = RGBA_ASTC_12x12_Format; - exports.RGBA_ASTC_4x4_Format = RGBA_ASTC_4x4_Format; - exports.RGBA_ASTC_5x4_Format = RGBA_ASTC_5x4_Format; - exports.RGBA_ASTC_5x5_Format = RGBA_ASTC_5x5_Format; - exports.RGBA_ASTC_6x5_Format = RGBA_ASTC_6x5_Format; - exports.RGBA_ASTC_6x6_Format = RGBA_ASTC_6x6_Format; - exports.RGBA_ASTC_8x5_Format = RGBA_ASTC_8x5_Format; - exports.RGBA_ASTC_8x6_Format = RGBA_ASTC_8x6_Format; - exports.RGBA_ASTC_8x8_Format = RGBA_ASTC_8x8_Format; - exports.RGBA_BPTC_Format = RGBA_BPTC_Format; - exports.RGBA_ETC2_EAC_Format = RGBA_ETC2_EAC_Format; - exports.RGBA_PVRTC_2BPPV1_Format = RGBA_PVRTC_2BPPV1_Format; - exports.RGBA_PVRTC_4BPPV1_Format = RGBA_PVRTC_4BPPV1_Format; - exports.RGBA_S3TC_DXT1_Format = RGBA_S3TC_DXT1_Format; - exports.RGBA_S3TC_DXT3_Format = RGBA_S3TC_DXT3_Format; - exports.RGBA_S3TC_DXT5_Format = RGBA_S3TC_DXT5_Format; - exports.RGB_ETC1_Format = RGB_ETC1_Format; - exports.RGB_ETC2_Format = RGB_ETC2_Format; - exports.RGB_PVRTC_2BPPV1_Format = RGB_PVRTC_2BPPV1_Format; - exports.RGB_PVRTC_4BPPV1_Format = RGB_PVRTC_4BPPV1_Format; - exports.RGB_S3TC_DXT1_Format = RGB_S3TC_DXT1_Format; - exports.RGFormat = RGFormat; - exports.RGIntegerFormat = RGIntegerFormat; - exports.RawShaderMaterial = RawShaderMaterial; - exports.Ray = Ray; - exports.Raycaster = Raycaster; - exports.RectAreaLight = RectAreaLight; - exports.RedFormat = RedFormat; - exports.RedIntegerFormat = RedIntegerFormat; - exports.ReinhardToneMapping = ReinhardToneMapping; - exports.RepeatWrapping = RepeatWrapping; - exports.ReplaceStencilOp = ReplaceStencilOp; - exports.ReverseSubtractEquation = ReverseSubtractEquation; - exports.RingBufferGeometry = RingBufferGeometry; - exports.RingGeometry = RingGeometry; - exports.SIGNED_RED_GREEN_RGTC2_Format = SIGNED_RED_GREEN_RGTC2_Format; - exports.SIGNED_RED_RGTC1_Format = SIGNED_RED_RGTC1_Format; - exports.SRGBColorSpace = SRGBColorSpace; - exports.Scene = Scene; - exports.ShaderChunk = ShaderChunk; - exports.ShaderLib = ShaderLib; - exports.ShaderMaterial = ShaderMaterial; - exports.ShadowMaterial = ShadowMaterial; - exports.Shape = Shape; - exports.ShapeBufferGeometry = ShapeBufferGeometry; - exports.ShapeGeometry = ShapeGeometry; - exports.ShapePath = ShapePath; - exports.ShapeUtils = ShapeUtils; - exports.ShortType = ShortType; - exports.Skeleton = Skeleton; - exports.SkeletonHelper = SkeletonHelper; - exports.SkinnedMesh = SkinnedMesh; - exports.Source = Source; - exports.Sphere = Sphere; - exports.SphereBufferGeometry = SphereBufferGeometry; - exports.SphereGeometry = SphereGeometry; - exports.Spherical = Spherical; - exports.SphericalHarmonics3 = SphericalHarmonics3; - exports.SplineCurve = SplineCurve; - exports.SpotLight = SpotLight; - exports.SpotLightHelper = SpotLightHelper; - exports.Sprite = Sprite; - exports.SpriteMaterial = SpriteMaterial; - exports.SrcAlphaFactor = SrcAlphaFactor; - exports.SrcAlphaSaturateFactor = SrcAlphaSaturateFactor; - exports.SrcColorFactor = SrcColorFactor; - exports.StaticCopyUsage = StaticCopyUsage; - exports.StaticDrawUsage = StaticDrawUsage; - exports.StaticReadUsage = StaticReadUsage; - exports.StereoCamera = StereoCamera; - exports.StreamCopyUsage = StreamCopyUsage; - exports.StreamDrawUsage = StreamDrawUsage; - exports.StreamReadUsage = StreamReadUsage; - exports.StringKeyframeTrack = StringKeyframeTrack; - exports.SubtractEquation = SubtractEquation; - exports.SubtractiveBlending = SubtractiveBlending; - exports.TOUCH = TOUCH; - exports.TangentSpaceNormalMap = TangentSpaceNormalMap; - exports.TetrahedronBufferGeometry = TetrahedronBufferGeometry; - exports.TetrahedronGeometry = TetrahedronGeometry; - exports.Texture = Texture; - exports.TextureLoader = TextureLoader; - exports.TorusBufferGeometry = TorusBufferGeometry; - exports.TorusGeometry = TorusGeometry; - exports.TorusKnotBufferGeometry = TorusKnotBufferGeometry; - exports.TorusKnotGeometry = TorusKnotGeometry; - exports.Triangle = Triangle; - exports.TriangleFanDrawMode = TriangleFanDrawMode; - exports.TriangleStripDrawMode = TriangleStripDrawMode; - exports.TrianglesDrawMode = TrianglesDrawMode; - exports.TubeBufferGeometry = TubeBufferGeometry; - exports.TubeGeometry = TubeGeometry; - exports.TwoPassDoubleSide = TwoPassDoubleSide; - exports.UVMapping = UVMapping; - exports.Uint16BufferAttribute = Uint16BufferAttribute; - exports.Uint32BufferAttribute = Uint32BufferAttribute; - exports.Uint8BufferAttribute = Uint8BufferAttribute; - exports.Uint8ClampedBufferAttribute = Uint8ClampedBufferAttribute; - exports.Uniform = Uniform; - exports.UniformsGroup = UniformsGroup; - exports.UniformsLib = UniformsLib; - exports.UniformsUtils = UniformsUtils; - exports.UnsignedByteType = UnsignedByteType; - exports.UnsignedInt248Type = UnsignedInt248Type; - exports.UnsignedIntType = UnsignedIntType; - exports.UnsignedShort4444Type = UnsignedShort4444Type; - exports.UnsignedShort5551Type = UnsignedShort5551Type; - exports.UnsignedShortType = UnsignedShortType; - exports.VSMShadowMap = VSMShadowMap; - exports.Vector2 = Vector2; - exports.Vector3 = Vector3; - exports.Vector4 = Vector4; - exports.VectorKeyframeTrack = VectorKeyframeTrack; - exports.VideoTexture = VideoTexture; - exports.WebGL1Renderer = WebGL1Renderer; - exports.WebGL3DRenderTarget = WebGL3DRenderTarget; - exports.WebGLArrayRenderTarget = WebGLArrayRenderTarget; - exports.WebGLCubeRenderTarget = WebGLCubeRenderTarget; - exports.WebGLMultipleRenderTargets = WebGLMultipleRenderTargets; - exports.WebGLRenderTarget = WebGLRenderTarget; - exports.WebGLRenderer = WebGLRenderer; - exports.WebGLUtils = WebGLUtils; - exports.WireframeGeometry = WireframeGeometry; - exports.WrapAroundEnding = WrapAroundEnding; - exports.ZeroCurvatureEnding = ZeroCurvatureEnding; - exports.ZeroFactor = ZeroFactor; - exports.ZeroSlopeEnding = ZeroSlopeEnding; - exports.ZeroStencilOp = ZeroStencilOp; - exports._SRGBAFormat = _SRGBAFormat; - exports.sRGBEncoding = sRGBEncoding; - -})); diff --git a/build/three.min.js b/build/three.min.js deleted file mode 100644 index cd5b77b7adc72a..00000000000000 --- a/build/three.min.js +++ /dev/null @@ -1,7 +0,0 @@ -console.warn( 'Scripts "build/three.js" and "build/three.min.js" are deprecated with r150+, and will be removed with r160. Please use ES Modules or alternatives: https://threejs.org/docs/index.html#manual/en/introduction/Installation' ); -/** - * @license - * Copyright 2010-2023 Three.js Authors - * SPDX-License-Identifier: MIT - */ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).THREE={})}(this,(function(t){"use strict";const e="153dev",n=100,i=300,r=301,s=302,a=303,o=304,l=306,c=1e3,h=1001,u=1002,d=1003,p=1004,m=1005,f=1006,g=1007,v=1008,_=1009,y=1012,x=1013,M=1014,S=1015,b=1016,T=1017,E=1018,w=1020,A=1023,R=1026,C=1027,L=1029,P=1031,I=1033,U=33776,D=33777,N=33778,O=33779,F=35840,B=35841,z=35842,G=35843,H=37492,k=37496,V=37808,W=37809,X=37810,j=37811,q=37812,Y=37813,Z=37814,J=37815,K=37816,$=37817,Q=37818,tt=37819,et=37820,nt=37821,it=36492,rt=36284,st=36285,at=36286,ot=2300,lt=2301,ct=2302,ht=2400,ut=2401,dt=2402,pt=2500,mt=2501,ft=3e3,gt=3001,vt="",_t="srgb",yt="srgb-linear",xt="display-p3",Mt=7680,St=35044,bt="300 es",Tt=1035;class Et{addEventListener(t,e){void 0===this._listeners&&(this._listeners={});const n=this._listeners;void 0===n[t]&&(n[t]=[]),-1===n[t].indexOf(e)&&n[t].push(e)}hasEventListener(t,e){if(void 0===this._listeners)return!1;const n=this._listeners;return void 0!==n[t]&&-1!==n[t].indexOf(e)}removeEventListener(t,e){if(void 0===this._listeners)return;const n=this._listeners[t];if(void 0!==n){const t=n.indexOf(e);-1!==t&&n.splice(t,1)}}dispatchEvent(t){if(void 0===this._listeners)return;const e=this._listeners[t.type];if(void 0!==e){t.target=this;const n=e.slice(0);for(let e=0,i=n.length;e>8&255]+wt[t>>16&255]+wt[t>>24&255]+"-"+wt[255&e]+wt[e>>8&255]+"-"+wt[e>>16&15|64]+wt[e>>24&255]+"-"+wt[63&n|128]+wt[n>>8&255]+"-"+wt[n>>16&255]+wt[n>>24&255]+wt[255&i]+wt[i>>8&255]+wt[i>>16&255]+wt[i>>24&255]).toLowerCase()}function Pt(t,e,n){return Math.max(e,Math.min(n,t))}function It(t,e){return(t%e+e)%e}function Ut(t,e,n){return(1-n)*t+n*e}function Dt(t){return 0==(t&t-1)&&0!==t}function Nt(t){return Math.pow(2,Math.ceil(Math.log(t)/Math.LN2))}function Ot(t){return Math.pow(2,Math.floor(Math.log(t)/Math.LN2))}function Ft(t,e){switch(e.constructor){case Float32Array:return t;case Uint32Array:return t/4294967295;case Uint16Array:return t/65535;case Uint8Array:return t/255;case Int32Array:return Math.max(t/2147483647,-1);case Int16Array:return Math.max(t/32767,-1);case Int8Array:return Math.max(t/127,-1);default:throw new Error("Invalid component type.")}}function Bt(t,e){switch(e.constructor){case Float32Array:return t;case Uint32Array:return Math.round(4294967295*t);case Uint16Array:return Math.round(65535*t);case Uint8Array:return Math.round(255*t);case Int32Array:return Math.round(2147483647*t);case Int16Array:return Math.round(32767*t);case Int8Array:return Math.round(127*t);default:throw new Error("Invalid component type.")}}const zt={DEG2RAD:Rt,RAD2DEG:Ct,generateUUID:Lt,clamp:Pt,euclideanModulo:It,mapLinear:function(t,e,n,i,r){return i+(t-e)*(r-i)/(n-e)},inverseLerp:function(t,e,n){return t!==e?(n-t)/(e-t):0},lerp:Ut,damp:function(t,e,n,i){return Ut(t,e,1-Math.exp(-n*i))},pingpong:function(t,e=1){return e-Math.abs(It(t,2*e)-e)},smoothstep:function(t,e,n){return t<=e?0:t>=n?1:(t=(t-e)/(n-e))*t*(3-2*t)},smootherstep:function(t,e,n){return t<=e?0:t>=n?1:(t=(t-e)/(n-e))*t*t*(t*(6*t-15)+10)},randInt:function(t,e){return t+Math.floor(Math.random()*(e-t+1))},randFloat:function(t,e){return t+Math.random()*(e-t)},randFloatSpread:function(t){return t*(.5-Math.random())},seededRandom:function(t){void 0!==t&&(At=t);let e=At+=1831565813;return e=Math.imul(e^e>>>15,1|e),e^=e+Math.imul(e^e>>>7,61|e),((e^e>>>14)>>>0)/4294967296},degToRad:function(t){return t*Rt},radToDeg:function(t){return t*Ct},isPowerOfTwo:Dt,ceilPowerOfTwo:Nt,floorPowerOfTwo:Ot,setQuaternionFromProperEuler:function(t,e,n,i,r){const s=Math.cos,a=Math.sin,o=s(n/2),l=a(n/2),c=s((e+i)/2),h=a((e+i)/2),u=s((e-i)/2),d=a((e-i)/2),p=s((i-e)/2),m=a((i-e)/2);switch(r){case"XYX":t.set(o*h,l*u,l*d,o*c);break;case"YZY":t.set(l*d,o*h,l*u,o*c);break;case"ZXZ":t.set(l*u,l*d,o*h,o*c);break;case"XZX":t.set(o*h,l*m,l*p,o*c);break;case"YXY":t.set(l*p,o*h,l*m,o*c);break;case"ZYZ":t.set(l*m,l*p,o*h,o*c);break;default:console.warn("THREE.MathUtils: .setQuaternionFromProperEuler() encountered an unknown order: "+r)}},normalize:Bt,denormalize:Ft};class Gt{constructor(t=0,e=0){Gt.prototype.isVector2=!0,this.x=t,this.y=e}get width(){return this.x}set width(t){this.x=t}get height(){return this.y}set height(t){this.y=t}set(t,e){return this.x=t,this.y=e,this}setScalar(t){return this.x=t,this.y=t,this}setX(t){return this.x=t,this}setY(t){return this.y=t,this}setComponent(t,e){switch(t){case 0:this.x=e;break;case 1:this.y=e;break;default:throw new Error("index is out of range: "+t)}return this}getComponent(t){switch(t){case 0:return this.x;case 1:return this.y;default:throw new Error("index is out of range: "+t)}}clone(){return new this.constructor(this.x,this.y)}copy(t){return this.x=t.x,this.y=t.y,this}add(t){return this.x+=t.x,this.y+=t.y,this}addScalar(t){return this.x+=t,this.y+=t,this}addVectors(t,e){return this.x=t.x+e.x,this.y=t.y+e.y,this}addScaledVector(t,e){return this.x+=t.x*e,this.y+=t.y*e,this}sub(t){return this.x-=t.x,this.y-=t.y,this}subScalar(t){return this.x-=t,this.y-=t,this}subVectors(t,e){return this.x=t.x-e.x,this.y=t.y-e.y,this}multiply(t){return this.x*=t.x,this.y*=t.y,this}multiplyScalar(t){return this.x*=t,this.y*=t,this}divide(t){return this.x/=t.x,this.y/=t.y,this}divideScalar(t){return this.multiplyScalar(1/t)}applyMatrix3(t){const e=this.x,n=this.y,i=t.elements;return this.x=i[0]*e+i[3]*n+i[6],this.y=i[1]*e+i[4]*n+i[7],this}min(t){return this.x=Math.min(this.x,t.x),this.y=Math.min(this.y,t.y),this}max(t){return this.x=Math.max(this.x,t.x),this.y=Math.max(this.y,t.y),this}clamp(t,e){return this.x=Math.max(t.x,Math.min(e.x,this.x)),this.y=Math.max(t.y,Math.min(e.y,this.y)),this}clampScalar(t,e){return this.x=Math.max(t,Math.min(e,this.x)),this.y=Math.max(t,Math.min(e,this.y)),this}clampLength(t,e){const n=this.length();return this.divideScalar(n||1).multiplyScalar(Math.max(t,Math.min(e,n)))}floor(){return this.x=Math.floor(this.x),this.y=Math.floor(this.y),this}ceil(){return this.x=Math.ceil(this.x),this.y=Math.ceil(this.y),this}round(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this}roundToZero(){return this.x=this.x<0?Math.ceil(this.x):Math.floor(this.x),this.y=this.y<0?Math.ceil(this.y):Math.floor(this.y),this}negate(){return this.x=-this.x,this.y=-this.y,this}dot(t){return this.x*t.x+this.y*t.y}cross(t){return this.x*t.y-this.y*t.x}lengthSq(){return this.x*this.x+this.y*this.y}length(){return Math.sqrt(this.x*this.x+this.y*this.y)}manhattanLength(){return Math.abs(this.x)+Math.abs(this.y)}normalize(){return this.divideScalar(this.length()||1)}angle(){return Math.atan2(-this.y,-this.x)+Math.PI}angleTo(t){const e=Math.sqrt(this.lengthSq()*t.lengthSq());if(0===e)return Math.PI/2;const n=this.dot(t)/e;return Math.acos(Pt(n,-1,1))}distanceTo(t){return Math.sqrt(this.distanceToSquared(t))}distanceToSquared(t){const e=this.x-t.x,n=this.y-t.y;return e*e+n*n}manhattanDistanceTo(t){return Math.abs(this.x-t.x)+Math.abs(this.y-t.y)}setLength(t){return this.normalize().multiplyScalar(t)}lerp(t,e){return this.x+=(t.x-this.x)*e,this.y+=(t.y-this.y)*e,this}lerpVectors(t,e,n){return this.x=t.x+(e.x-t.x)*n,this.y=t.y+(e.y-t.y)*n,this}equals(t){return t.x===this.x&&t.y===this.y}fromArray(t,e=0){return this.x=t[e],this.y=t[e+1],this}toArray(t=[],e=0){return t[e]=this.x,t[e+1]=this.y,t}fromBufferAttribute(t,e){return this.x=t.getX(e),this.y=t.getY(e),this}rotateAround(t,e){const n=Math.cos(e),i=Math.sin(e),r=this.x-t.x,s=this.y-t.y;return this.x=r*n-s*i+t.x,this.y=r*i+s*n+t.y,this}random(){return this.x=Math.random(),this.y=Math.random(),this}*[Symbol.iterator](){yield this.x,yield this.y}}class Ht{constructor(t,e,n,i,r,s,a,o,l){Ht.prototype.isMatrix3=!0,this.elements=[1,0,0,0,1,0,0,0,1],void 0!==t&&this.set(t,e,n,i,r,s,a,o,l)}set(t,e,n,i,r,s,a,o,l){const c=this.elements;return c[0]=t,c[1]=i,c[2]=a,c[3]=e,c[4]=r,c[5]=o,c[6]=n,c[7]=s,c[8]=l,this}identity(){return this.set(1,0,0,0,1,0,0,0,1),this}copy(t){const e=this.elements,n=t.elements;return e[0]=n[0],e[1]=n[1],e[2]=n[2],e[3]=n[3],e[4]=n[4],e[5]=n[5],e[6]=n[6],e[7]=n[7],e[8]=n[8],this}extractBasis(t,e,n){return t.setFromMatrix3Column(this,0),e.setFromMatrix3Column(this,1),n.setFromMatrix3Column(this,2),this}setFromMatrix4(t){const e=t.elements;return this.set(e[0],e[4],e[8],e[1],e[5],e[9],e[2],e[6],e[10]),this}multiply(t){return this.multiplyMatrices(this,t)}premultiply(t){return this.multiplyMatrices(t,this)}multiplyMatrices(t,e){const n=t.elements,i=e.elements,r=this.elements,s=n[0],a=n[3],o=n[6],l=n[1],c=n[4],h=n[7],u=n[2],d=n[5],p=n[8],m=i[0],f=i[3],g=i[6],v=i[1],_=i[4],y=i[7],x=i[2],M=i[5],S=i[8];return r[0]=s*m+a*v+o*x,r[3]=s*f+a*_+o*M,r[6]=s*g+a*y+o*S,r[1]=l*m+c*v+h*x,r[4]=l*f+c*_+h*M,r[7]=l*g+c*y+h*S,r[2]=u*m+d*v+p*x,r[5]=u*f+d*_+p*M,r[8]=u*g+d*y+p*S,this}multiplyScalar(t){const e=this.elements;return e[0]*=t,e[3]*=t,e[6]*=t,e[1]*=t,e[4]*=t,e[7]*=t,e[2]*=t,e[5]*=t,e[8]*=t,this}determinant(){const t=this.elements,e=t[0],n=t[1],i=t[2],r=t[3],s=t[4],a=t[5],o=t[6],l=t[7],c=t[8];return e*s*c-e*a*l-n*r*c+n*a*o+i*r*l-i*s*o}invert(){const t=this.elements,e=t[0],n=t[1],i=t[2],r=t[3],s=t[4],a=t[5],o=t[6],l=t[7],c=t[8],h=c*s-a*l,u=a*o-c*r,d=l*r-s*o,p=e*h+n*u+i*d;if(0===p)return this.set(0,0,0,0,0,0,0,0,0);const m=1/p;return t[0]=h*m,t[1]=(i*l-c*n)*m,t[2]=(a*n-i*s)*m,t[3]=u*m,t[4]=(c*e-i*o)*m,t[5]=(i*r-a*e)*m,t[6]=d*m,t[7]=(n*o-l*e)*m,t[8]=(s*e-n*r)*m,this}transpose(){let t;const e=this.elements;return t=e[1],e[1]=e[3],e[3]=t,t=e[2],e[2]=e[6],e[6]=t,t=e[5],e[5]=e[7],e[7]=t,this}getNormalMatrix(t){return this.setFromMatrix4(t).invert().transpose()}transposeIntoArray(t){const e=this.elements;return t[0]=e[0],t[1]=e[3],t[2]=e[6],t[3]=e[1],t[4]=e[4],t[5]=e[7],t[6]=e[2],t[7]=e[5],t[8]=e[8],this}setUvTransform(t,e,n,i,r,s,a){const o=Math.cos(r),l=Math.sin(r);return this.set(n*o,n*l,-n*(o*s+l*a)+s+t,-i*l,i*o,-i*(-l*s+o*a)+a+e,0,0,1),this}scale(t,e){return this.premultiply(kt.makeScale(t,e)),this}rotate(t){return this.premultiply(kt.makeRotation(-t)),this}translate(t,e){return this.premultiply(kt.makeTranslation(t,e)),this}makeTranslation(t,e){return this.set(1,0,t,0,1,e,0,0,1),this}makeRotation(t){const e=Math.cos(t),n=Math.sin(t);return this.set(e,-n,0,n,e,0,0,0,1),this}makeScale(t,e){return this.set(t,0,0,0,e,0,0,0,1),this}equals(t){const e=this.elements,n=t.elements;for(let t=0;t<9;t++)if(e[t]!==n[t])return!1;return!0}fromArray(t,e=0){for(let n=0;n<9;n++)this.elements[n]=t[n+e];return this}toArray(t=[],e=0){const n=this.elements;return t[e]=n[0],t[e+1]=n[1],t[e+2]=n[2],t[e+3]=n[3],t[e+4]=n[4],t[e+5]=n[5],t[e+6]=n[6],t[e+7]=n[7],t[e+8]=n[8],t}clone(){return(new this.constructor).fromArray(this.elements)}}const kt=new Ht;function Vt(t){for(let e=t.length-1;e>=0;--e)if(t[e]>=65535)return!0;return!1}const Wt={Int8Array:Int8Array,Uint8Array:Uint8Array,Uint8ClampedArray:Uint8ClampedArray,Int16Array:Int16Array,Uint16Array:Uint16Array,Int32Array:Int32Array,Uint32Array:Uint32Array,Float32Array:Float32Array,Float64Array:Float64Array};function Xt(t,e){return new Wt[t](e)}function jt(t){return document.createElementNS("http://www.w3.org/1999/xhtml",t)}const qt={};function Yt(t){t in qt||(qt[t]=!0,console.warn(t))}function Zt(t){return t<.04045?.0773993808*t:Math.pow(.9478672986*t+.0521327014,2.4)}function Jt(t){return t<.0031308?12.92*t:1.055*Math.pow(t,.41666)-.055}const Kt=(new Ht).fromArray([.8224621,.0331941,.0170827,.177538,.9668058,.0723974,-1e-7,1e-7,.9105199]),$t=(new Ht).fromArray([1.2249401,-.0420569,-.0196376,-.2249404,1.0420571,-.0786361,1e-7,0,1.0982735]);const Qt={[yt]:t=>t,[_t]:t=>t.convertSRGBToLinear(),[xt]:function(t){return t.convertSRGBToLinear().applyMatrix3($t)}},te={[yt]:t=>t,[_t]:t=>t.convertLinearToSRGB(),[xt]:function(t){return t.applyMatrix3(Kt).convertLinearToSRGB()}},ee={enabled:!0,get legacyMode(){return console.warn("THREE.ColorManagement: .legacyMode=false renamed to .enabled=true in r150."),!this.enabled},set legacyMode(t){console.warn("THREE.ColorManagement: .legacyMode=false renamed to .enabled=true in r150."),this.enabled=!t},get workingColorSpace(){return yt},set workingColorSpace(t){console.warn("THREE.ColorManagement: .workingColorSpace is readonly.")},convert:function(t,e,n){if(!1===this.enabled||e===n||!e||!n)return t;const i=Qt[e],r=te[n];if(void 0===i||void 0===r)throw new Error(`Unsupported color space conversion, "${e}" to "${n}".`);return r(i(t))},fromWorkingColorSpace:function(t,e){return this.convert(t,this.workingColorSpace,e)},toWorkingColorSpace:function(t,e){return this.convert(t,e,this.workingColorSpace)}};let ne;class ie{static getDataURL(t){if(/^data:/i.test(t.src))return t.src;if("undefined"==typeof HTMLCanvasElement)return t.src;let e;if(t instanceof HTMLCanvasElement)e=t;else{void 0===ne&&(ne=jt("canvas")),ne.width=t.width,ne.height=t.height;const n=ne.getContext("2d");t instanceof ImageData?n.putImageData(t,0,0):n.drawImage(t,0,0,t.width,t.height),e=ne}return e.width>2048||e.height>2048?(console.warn("THREE.ImageUtils.getDataURL: Image converted to jpg for performance reasons",t),e.toDataURL("image/jpeg",.6)):e.toDataURL("image/png")}static sRGBToLinear(t){if("undefined"!=typeof HTMLImageElement&&t instanceof HTMLImageElement||"undefined"!=typeof HTMLCanvasElement&&t instanceof HTMLCanvasElement||"undefined"!=typeof ImageBitmap&&t instanceof ImageBitmap){const e=jt("canvas");e.width=t.width,e.height=t.height;const n=e.getContext("2d");n.drawImage(t,0,0,t.width,t.height);const i=n.getImageData(0,0,t.width,t.height),r=i.data;for(let t=0;t0&&(n.userData=this.userData),e||(t.textures[this.uuid]=n),n}dispose(){this.dispatchEvent({type:"dispose"})}transformUv(t){if(this.mapping!==i)return t;if(t.applyMatrix3(this.matrix),t.x<0||t.x>1)switch(this.wrapS){case c:t.x=t.x-Math.floor(t.x);break;case h:t.x=t.x<0?0:1;break;case u:1===Math.abs(Math.floor(t.x)%2)?t.x=Math.ceil(t.x)-t.x:t.x=t.x-Math.floor(t.x)}if(t.y<0||t.y>1)switch(this.wrapT){case c:t.y=t.y-Math.floor(t.y);break;case h:t.y=t.y<0?0:1;break;case u:1===Math.abs(Math.floor(t.y)%2)?t.y=Math.ceil(t.y)-t.y:t.y=t.y-Math.floor(t.y)}return this.flipY&&(t.y=1-t.y),t}set needsUpdate(t){!0===t&&(this.version++,this.source.needsUpdate=!0)}get encoding(){return Yt("THREE.Texture: Property .encoding has been replaced by .colorSpace."),this.colorSpace===_t?gt:ft}set encoding(t){Yt("THREE.Texture: Property .encoding has been replaced by .colorSpace."),this.colorSpace=t===gt?_t:vt}}le.DEFAULT_IMAGE=null,le.DEFAULT_MAPPING=i,le.DEFAULT_ANISOTROPY=1;class ce{constructor(t=0,e=0,n=0,i=1){ce.prototype.isVector4=!0,this.x=t,this.y=e,this.z=n,this.w=i}get width(){return this.z}set width(t){this.z=t}get height(){return this.w}set height(t){this.w=t}set(t,e,n,i){return this.x=t,this.y=e,this.z=n,this.w=i,this}setScalar(t){return this.x=t,this.y=t,this.z=t,this.w=t,this}setX(t){return this.x=t,this}setY(t){return this.y=t,this}setZ(t){return this.z=t,this}setW(t){return this.w=t,this}setComponent(t,e){switch(t){case 0:this.x=e;break;case 1:this.y=e;break;case 2:this.z=e;break;case 3:this.w=e;break;default:throw new Error("index is out of range: "+t)}return this}getComponent(t){switch(t){case 0:return this.x;case 1:return this.y;case 2:return this.z;case 3:return this.w;default:throw new Error("index is out of range: "+t)}}clone(){return new this.constructor(this.x,this.y,this.z,this.w)}copy(t){return this.x=t.x,this.y=t.y,this.z=t.z,this.w=void 0!==t.w?t.w:1,this}add(t){return this.x+=t.x,this.y+=t.y,this.z+=t.z,this.w+=t.w,this}addScalar(t){return this.x+=t,this.y+=t,this.z+=t,this.w+=t,this}addVectors(t,e){return this.x=t.x+e.x,this.y=t.y+e.y,this.z=t.z+e.z,this.w=t.w+e.w,this}addScaledVector(t,e){return this.x+=t.x*e,this.y+=t.y*e,this.z+=t.z*e,this.w+=t.w*e,this}sub(t){return this.x-=t.x,this.y-=t.y,this.z-=t.z,this.w-=t.w,this}subScalar(t){return this.x-=t,this.y-=t,this.z-=t,this.w-=t,this}subVectors(t,e){return this.x=t.x-e.x,this.y=t.y-e.y,this.z=t.z-e.z,this.w=t.w-e.w,this}multiply(t){return this.x*=t.x,this.y*=t.y,this.z*=t.z,this.w*=t.w,this}multiplyScalar(t){return this.x*=t,this.y*=t,this.z*=t,this.w*=t,this}applyMatrix4(t){const e=this.x,n=this.y,i=this.z,r=this.w,s=t.elements;return this.x=s[0]*e+s[4]*n+s[8]*i+s[12]*r,this.y=s[1]*e+s[5]*n+s[9]*i+s[13]*r,this.z=s[2]*e+s[6]*n+s[10]*i+s[14]*r,this.w=s[3]*e+s[7]*n+s[11]*i+s[15]*r,this}divideScalar(t){return this.multiplyScalar(1/t)}setAxisAngleFromQuaternion(t){this.w=2*Math.acos(t.w);const e=Math.sqrt(1-t.w*t.w);return e<1e-4?(this.x=1,this.y=0,this.z=0):(this.x=t.x/e,this.y=t.y/e,this.z=t.z/e),this}setAxisAngleFromRotationMatrix(t){let e,n,i,r;const s=.01,a=.1,o=t.elements,l=o[0],c=o[4],h=o[8],u=o[1],d=o[5],p=o[9],m=o[2],f=o[6],g=o[10];if(Math.abs(c-u)o&&t>v?tv?o=0?1:-1,i=1-e*e;if(i>Number.EPSILON){const r=Math.sqrt(i),s=Math.atan2(r,e*n);t=Math.sin(t*s)/r,a=Math.sin(a*s)/r}const r=a*n;if(o=o*t+u*r,l=l*t+d*r,c=c*t+p*r,h=h*t+m*r,t===1-a){const t=1/Math.sqrt(o*o+l*l+c*c+h*h);o*=t,l*=t,c*=t,h*=t}}t[e]=o,t[e+1]=l,t[e+2]=c,t[e+3]=h}static multiplyQuaternionsFlat(t,e,n,i,r,s){const a=n[i],o=n[i+1],l=n[i+2],c=n[i+3],h=r[s],u=r[s+1],d=r[s+2],p=r[s+3];return t[e]=a*p+c*h+o*d-l*u,t[e+1]=o*p+c*u+l*h-a*d,t[e+2]=l*p+c*d+a*u-o*h,t[e+3]=c*p-a*h-o*u-l*d,t}get x(){return this._x}set x(t){this._x=t,this._onChangeCallback()}get y(){return this._y}set y(t){this._y=t,this._onChangeCallback()}get z(){return this._z}set z(t){this._z=t,this._onChangeCallback()}get w(){return this._w}set w(t){this._w=t,this._onChangeCallback()}set(t,e,n,i){return this._x=t,this._y=e,this._z=n,this._w=i,this._onChangeCallback(),this}clone(){return new this.constructor(this._x,this._y,this._z,this._w)}copy(t){return this._x=t.x,this._y=t.y,this._z=t.z,this._w=t.w,this._onChangeCallback(),this}setFromEuler(t,e){const n=t._x,i=t._y,r=t._z,s=t._order,a=Math.cos,o=Math.sin,l=a(n/2),c=a(i/2),h=a(r/2),u=o(n/2),d=o(i/2),p=o(r/2);switch(s){case"XYZ":this._x=u*c*h+l*d*p,this._y=l*d*h-u*c*p,this._z=l*c*p+u*d*h,this._w=l*c*h-u*d*p;break;case"YXZ":this._x=u*c*h+l*d*p,this._y=l*d*h-u*c*p,this._z=l*c*p-u*d*h,this._w=l*c*h+u*d*p;break;case"ZXY":this._x=u*c*h-l*d*p,this._y=l*d*h+u*c*p,this._z=l*c*p+u*d*h,this._w=l*c*h-u*d*p;break;case"ZYX":this._x=u*c*h-l*d*p,this._y=l*d*h+u*c*p,this._z=l*c*p-u*d*h,this._w=l*c*h+u*d*p;break;case"YZX":this._x=u*c*h+l*d*p,this._y=l*d*h+u*c*p,this._z=l*c*p-u*d*h,this._w=l*c*h-u*d*p;break;case"XZY":this._x=u*c*h-l*d*p,this._y=l*d*h-u*c*p,this._z=l*c*p+u*d*h,this._w=l*c*h+u*d*p;break;default:console.warn("THREE.Quaternion: .setFromEuler() encountered an unknown order: "+s)}return!1!==e&&this._onChangeCallback(),this}setFromAxisAngle(t,e){const n=e/2,i=Math.sin(n);return this._x=t.x*i,this._y=t.y*i,this._z=t.z*i,this._w=Math.cos(n),this._onChangeCallback(),this}setFromRotationMatrix(t){const e=t.elements,n=e[0],i=e[4],r=e[8],s=e[1],a=e[5],o=e[9],l=e[2],c=e[6],h=e[10],u=n+a+h;if(u>0){const t=.5/Math.sqrt(u+1);this._w=.25/t,this._x=(c-o)*t,this._y=(r-l)*t,this._z=(s-i)*t}else if(n>a&&n>h){const t=2*Math.sqrt(1+n-a-h);this._w=(c-o)/t,this._x=.25*t,this._y=(i+s)/t,this._z=(r+l)/t}else if(a>h){const t=2*Math.sqrt(1+a-n-h);this._w=(r-l)/t,this._x=(i+s)/t,this._y=.25*t,this._z=(o+c)/t}else{const t=2*Math.sqrt(1+h-n-a);this._w=(s-i)/t,this._x=(r+l)/t,this._y=(o+c)/t,this._z=.25*t}return this._onChangeCallback(),this}setFromUnitVectors(t,e){let n=t.dot(e)+1;return nMath.abs(t.z)?(this._x=-t.y,this._y=t.x,this._z=0,this._w=n):(this._x=0,this._y=-t.z,this._z=t.y,this._w=n)):(this._x=t.y*e.z-t.z*e.y,this._y=t.z*e.x-t.x*e.z,this._z=t.x*e.y-t.y*e.x,this._w=n),this.normalize()}angleTo(t){return 2*Math.acos(Math.abs(Pt(this.dot(t),-1,1)))}rotateTowards(t,e){const n=this.angleTo(t);if(0===n)return this;const i=Math.min(1,e/n);return this.slerp(t,i),this}identity(){return this.set(0,0,0,1)}invert(){return this.conjugate()}conjugate(){return this._x*=-1,this._y*=-1,this._z*=-1,this._onChangeCallback(),this}dot(t){return this._x*t._x+this._y*t._y+this._z*t._z+this._w*t._w}lengthSq(){return this._x*this._x+this._y*this._y+this._z*this._z+this._w*this._w}length(){return Math.sqrt(this._x*this._x+this._y*this._y+this._z*this._z+this._w*this._w)}normalize(){let t=this.length();return 0===t?(this._x=0,this._y=0,this._z=0,this._w=1):(t=1/t,this._x=this._x*t,this._y=this._y*t,this._z=this._z*t,this._w=this._w*t),this._onChangeCallback(),this}multiply(t){return this.multiplyQuaternions(this,t)}premultiply(t){return this.multiplyQuaternions(t,this)}multiplyQuaternions(t,e){const n=t._x,i=t._y,r=t._z,s=t._w,a=e._x,o=e._y,l=e._z,c=e._w;return this._x=n*c+s*a+i*l-r*o,this._y=i*c+s*o+r*a-n*l,this._z=r*c+s*l+n*o-i*a,this._w=s*c-n*a-i*o-r*l,this._onChangeCallback(),this}slerp(t,e){if(0===e)return this;if(1===e)return this.copy(t);const n=this._x,i=this._y,r=this._z,s=this._w;let a=s*t._w+n*t._x+i*t._y+r*t._z;if(a<0?(this._w=-t._w,this._x=-t._x,this._y=-t._y,this._z=-t._z,a=-a):this.copy(t),a>=1)return this._w=s,this._x=n,this._y=i,this._z=r,this;const o=1-a*a;if(o<=Number.EPSILON){const t=1-e;return this._w=t*s+e*this._w,this._x=t*n+e*this._x,this._y=t*i+e*this._y,this._z=t*r+e*this._z,this.normalize(),this._onChangeCallback(),this}const l=Math.sqrt(o),c=Math.atan2(l,a),h=Math.sin((1-e)*c)/l,u=Math.sin(e*c)/l;return this._w=s*h+this._w*u,this._x=n*h+this._x*u,this._y=i*h+this._y*u,this._z=r*h+this._z*u,this._onChangeCallback(),this}slerpQuaternions(t,e,n){return this.copy(t).slerp(e,n)}random(){const t=Math.random(),e=Math.sqrt(1-t),n=Math.sqrt(t),i=2*Math.PI*Math.random(),r=2*Math.PI*Math.random();return this.set(e*Math.cos(i),n*Math.sin(r),n*Math.cos(r),e*Math.sin(i))}equals(t){return t._x===this._x&&t._y===this._y&&t._z===this._z&&t._w===this._w}fromArray(t,e=0){return this._x=t[e],this._y=t[e+1],this._z=t[e+2],this._w=t[e+3],this._onChangeCallback(),this}toArray(t=[],e=0){return t[e]=this._x,t[e+1]=this._y,t[e+2]=this._z,t[e+3]=this._w,t}fromBufferAttribute(t,e){return this._x=t.getX(e),this._y=t.getY(e),this._z=t.getZ(e),this._w=t.getW(e),this}toJSON(){return this.toArray()}_onChange(t){return this._onChangeCallback=t,this}_onChangeCallback(){}*[Symbol.iterator](){yield this._x,yield this._y,yield this._z,yield this._w}}class me{constructor(t=0,e=0,n=0){me.prototype.isVector3=!0,this.x=t,this.y=e,this.z=n}set(t,e,n){return void 0===n&&(n=this.z),this.x=t,this.y=e,this.z=n,this}setScalar(t){return this.x=t,this.y=t,this.z=t,this}setX(t){return this.x=t,this}setY(t){return this.y=t,this}setZ(t){return this.z=t,this}setComponent(t,e){switch(t){case 0:this.x=e;break;case 1:this.y=e;break;case 2:this.z=e;break;default:throw new Error("index is out of range: "+t)}return this}getComponent(t){switch(t){case 0:return this.x;case 1:return this.y;case 2:return this.z;default:throw new Error("index is out of range: "+t)}}clone(){return new this.constructor(this.x,this.y,this.z)}copy(t){return this.x=t.x,this.y=t.y,this.z=t.z,this}add(t){return this.x+=t.x,this.y+=t.y,this.z+=t.z,this}addScalar(t){return this.x+=t,this.y+=t,this.z+=t,this}addVectors(t,e){return this.x=t.x+e.x,this.y=t.y+e.y,this.z=t.z+e.z,this}addScaledVector(t,e){return this.x+=t.x*e,this.y+=t.y*e,this.z+=t.z*e,this}sub(t){return this.x-=t.x,this.y-=t.y,this.z-=t.z,this}subScalar(t){return this.x-=t,this.y-=t,this.z-=t,this}subVectors(t,e){return this.x=t.x-e.x,this.y=t.y-e.y,this.z=t.z-e.z,this}multiply(t){return this.x*=t.x,this.y*=t.y,this.z*=t.z,this}multiplyScalar(t){return this.x*=t,this.y*=t,this.z*=t,this}multiplyVectors(t,e){return this.x=t.x*e.x,this.y=t.y*e.y,this.z=t.z*e.z,this}applyEuler(t){return this.applyQuaternion(ge.setFromEuler(t))}applyAxisAngle(t,e){return this.applyQuaternion(ge.setFromAxisAngle(t,e))}applyMatrix3(t){const e=this.x,n=this.y,i=this.z,r=t.elements;return this.x=r[0]*e+r[3]*n+r[6]*i,this.y=r[1]*e+r[4]*n+r[7]*i,this.z=r[2]*e+r[5]*n+r[8]*i,this}applyNormalMatrix(t){return this.applyMatrix3(t).normalize()}applyMatrix4(t){const e=this.x,n=this.y,i=this.z,r=t.elements,s=1/(r[3]*e+r[7]*n+r[11]*i+r[15]);return this.x=(r[0]*e+r[4]*n+r[8]*i+r[12])*s,this.y=(r[1]*e+r[5]*n+r[9]*i+r[13])*s,this.z=(r[2]*e+r[6]*n+r[10]*i+r[14])*s,this}applyQuaternion(t){const e=this.x,n=this.y,i=this.z,r=t.x,s=t.y,a=t.z,o=t.w,l=o*e+s*i-a*n,c=o*n+a*e-r*i,h=o*i+r*n-s*e,u=-r*e-s*n-a*i;return this.x=l*o+u*-r+c*-a-h*-s,this.y=c*o+u*-s+h*-r-l*-a,this.z=h*o+u*-a+l*-s-c*-r,this}project(t){return this.applyMatrix4(t.matrixWorldInverse).applyMatrix4(t.projectionMatrix)}unproject(t){return this.applyMatrix4(t.projectionMatrixInverse).applyMatrix4(t.matrixWorld)}transformDirection(t){const e=this.x,n=this.y,i=this.z,r=t.elements;return this.x=r[0]*e+r[4]*n+r[8]*i,this.y=r[1]*e+r[5]*n+r[9]*i,this.z=r[2]*e+r[6]*n+r[10]*i,this.normalize()}divide(t){return this.x/=t.x,this.y/=t.y,this.z/=t.z,this}divideScalar(t){return this.multiplyScalar(1/t)}min(t){return this.x=Math.min(this.x,t.x),this.y=Math.min(this.y,t.y),this.z=Math.min(this.z,t.z),this}max(t){return this.x=Math.max(this.x,t.x),this.y=Math.max(this.y,t.y),this.z=Math.max(this.z,t.z),this}clamp(t,e){return this.x=Math.max(t.x,Math.min(e.x,this.x)),this.y=Math.max(t.y,Math.min(e.y,this.y)),this.z=Math.max(t.z,Math.min(e.z,this.z)),this}clampScalar(t,e){return this.x=Math.max(t,Math.min(e,this.x)),this.y=Math.max(t,Math.min(e,this.y)),this.z=Math.max(t,Math.min(e,this.z)),this}clampLength(t,e){const n=this.length();return this.divideScalar(n||1).multiplyScalar(Math.max(t,Math.min(e,n)))}floor(){return this.x=Math.floor(this.x),this.y=Math.floor(this.y),this.z=Math.floor(this.z),this}ceil(){return this.x=Math.ceil(this.x),this.y=Math.ceil(this.y),this.z=Math.ceil(this.z),this}round(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this.z=Math.round(this.z),this}roundToZero(){return this.x=this.x<0?Math.ceil(this.x):Math.floor(this.x),this.y=this.y<0?Math.ceil(this.y):Math.floor(this.y),this.z=this.z<0?Math.ceil(this.z):Math.floor(this.z),this}negate(){return this.x=-this.x,this.y=-this.y,this.z=-this.z,this}dot(t){return this.x*t.x+this.y*t.y+this.z*t.z}lengthSq(){return this.x*this.x+this.y*this.y+this.z*this.z}length(){return Math.sqrt(this.x*this.x+this.y*this.y+this.z*this.z)}manhattanLength(){return Math.abs(this.x)+Math.abs(this.y)+Math.abs(this.z)}normalize(){return this.divideScalar(this.length()||1)}setLength(t){return this.normalize().multiplyScalar(t)}lerp(t,e){return this.x+=(t.x-this.x)*e,this.y+=(t.y-this.y)*e,this.z+=(t.z-this.z)*e,this}lerpVectors(t,e,n){return this.x=t.x+(e.x-t.x)*n,this.y=t.y+(e.y-t.y)*n,this.z=t.z+(e.z-t.z)*n,this}cross(t){return this.crossVectors(this,t)}crossVectors(t,e){const n=t.x,i=t.y,r=t.z,s=e.x,a=e.y,o=e.z;return this.x=i*o-r*a,this.y=r*s-n*o,this.z=n*a-i*s,this}projectOnVector(t){const e=t.lengthSq();if(0===e)return this.set(0,0,0);const n=t.dot(this)/e;return this.copy(t).multiplyScalar(n)}projectOnPlane(t){return fe.copy(this).projectOnVector(t),this.sub(fe)}reflect(t){return this.sub(fe.copy(t).multiplyScalar(2*this.dot(t)))}angleTo(t){const e=Math.sqrt(this.lengthSq()*t.lengthSq());if(0===e)return Math.PI/2;const n=this.dot(t)/e;return Math.acos(Pt(n,-1,1))}distanceTo(t){return Math.sqrt(this.distanceToSquared(t))}distanceToSquared(t){const e=this.x-t.x,n=this.y-t.y,i=this.z-t.z;return e*e+n*n+i*i}manhattanDistanceTo(t){return Math.abs(this.x-t.x)+Math.abs(this.y-t.y)+Math.abs(this.z-t.z)}setFromSpherical(t){return this.setFromSphericalCoords(t.radius,t.phi,t.theta)}setFromSphericalCoords(t,e,n){const i=Math.sin(e)*t;return this.x=i*Math.sin(n),this.y=Math.cos(e)*t,this.z=i*Math.cos(n),this}setFromCylindrical(t){return this.setFromCylindricalCoords(t.radius,t.theta,t.y)}setFromCylindricalCoords(t,e,n){return this.x=t*Math.sin(e),this.y=n,this.z=t*Math.cos(e),this}setFromMatrixPosition(t){const e=t.elements;return this.x=e[12],this.y=e[13],this.z=e[14],this}setFromMatrixScale(t){const e=this.setFromMatrixColumn(t,0).length(),n=this.setFromMatrixColumn(t,1).length(),i=this.setFromMatrixColumn(t,2).length();return this.x=e,this.y=n,this.z=i,this}setFromMatrixColumn(t,e){return this.fromArray(t.elements,4*e)}setFromMatrix3Column(t,e){return this.fromArray(t.elements,3*e)}setFromEuler(t){return this.x=t._x,this.y=t._y,this.z=t._z,this}setFromColor(t){return this.x=t.r,this.y=t.g,this.z=t.b,this}equals(t){return t.x===this.x&&t.y===this.y&&t.z===this.z}fromArray(t,e=0){return this.x=t[e],this.y=t[e+1],this.z=t[e+2],this}toArray(t=[],e=0){return t[e]=this.x,t[e+1]=this.y,t[e+2]=this.z,t}fromBufferAttribute(t,e){return this.x=t.getX(e),this.y=t.getY(e),this.z=t.getZ(e),this}random(){return this.x=Math.random(),this.y=Math.random(),this.z=Math.random(),this}randomDirection(){const t=2*(Math.random()-.5),e=Math.random()*Math.PI*2,n=Math.sqrt(1-t**2);return this.x=n*Math.cos(e),this.y=n*Math.sin(e),this.z=t,this}*[Symbol.iterator](){yield this.x,yield this.y,yield this.z}}const fe=new me,ge=new pe;class ve{constructor(t=new me(1/0,1/0,1/0),e=new me(-1/0,-1/0,-1/0)){this.isBox3=!0,this.min=t,this.max=e}set(t,e){return this.min.copy(t),this.max.copy(e),this}setFromArray(t){this.makeEmpty();for(let e=0,n=t.length;ethis.max.x||t.ythis.max.y||t.zthis.max.z)}containsBox(t){return this.min.x<=t.min.x&&t.max.x<=this.max.x&&this.min.y<=t.min.y&&t.max.y<=this.max.y&&this.min.z<=t.min.z&&t.max.z<=this.max.z}getParameter(t,e){return e.set((t.x-this.min.x)/(this.max.x-this.min.x),(t.y-this.min.y)/(this.max.y-this.min.y),(t.z-this.min.z)/(this.max.z-this.min.z))}intersectsBox(t){return!(t.max.xthis.max.x||t.max.ythis.max.y||t.max.zthis.max.z)}intersectsSphere(t){return this.clampPoint(t.center,ye),ye.distanceToSquared(t.center)<=t.radius*t.radius}intersectsPlane(t){let e,n;return t.normal.x>0?(e=t.normal.x*this.min.x,n=t.normal.x*this.max.x):(e=t.normal.x*this.max.x,n=t.normal.x*this.min.x),t.normal.y>0?(e+=t.normal.y*this.min.y,n+=t.normal.y*this.max.y):(e+=t.normal.y*this.max.y,n+=t.normal.y*this.min.y),t.normal.z>0?(e+=t.normal.z*this.min.z,n+=t.normal.z*this.max.z):(e+=t.normal.z*this.max.z,n+=t.normal.z*this.min.z),e<=-t.constant&&n>=-t.constant}intersectsTriangle(t){if(this.isEmpty())return!1;this.getCenter(Ae),Re.subVectors(this.max,Ae),Me.subVectors(t.a,Ae),Se.subVectors(t.b,Ae),be.subVectors(t.c,Ae),Te.subVectors(Se,Me),Ee.subVectors(be,Se),we.subVectors(Me,be);let e=[0,-Te.z,Te.y,0,-Ee.z,Ee.y,0,-we.z,we.y,Te.z,0,-Te.x,Ee.z,0,-Ee.x,we.z,0,-we.x,-Te.y,Te.x,0,-Ee.y,Ee.x,0,-we.y,we.x,0];return!!Pe(e,Me,Se,be,Re)&&(e=[1,0,0,0,1,0,0,0,1],!!Pe(e,Me,Se,be,Re)&&(Ce.crossVectors(Te,Ee),e=[Ce.x,Ce.y,Ce.z],Pe(e,Me,Se,be,Re)))}clampPoint(t,e){return e.copy(t).clamp(this.min,this.max)}distanceToPoint(t){return this.clampPoint(t,ye).distanceTo(t)}getBoundingSphere(t){return this.isEmpty()?t.makeEmpty():(this.getCenter(t.center),t.radius=.5*this.getSize(ye).length()),t}intersect(t){return this.min.max(t.min),this.max.min(t.max),this.isEmpty()&&this.makeEmpty(),this}union(t){return this.min.min(t.min),this.max.max(t.max),this}applyMatrix4(t){return this.isEmpty()||(_e[0].set(this.min.x,this.min.y,this.min.z).applyMatrix4(t),_e[1].set(this.min.x,this.min.y,this.max.z).applyMatrix4(t),_e[2].set(this.min.x,this.max.y,this.min.z).applyMatrix4(t),_e[3].set(this.min.x,this.max.y,this.max.z).applyMatrix4(t),_e[4].set(this.max.x,this.min.y,this.min.z).applyMatrix4(t),_e[5].set(this.max.x,this.min.y,this.max.z).applyMatrix4(t),_e[6].set(this.max.x,this.max.y,this.min.z).applyMatrix4(t),_e[7].set(this.max.x,this.max.y,this.max.z).applyMatrix4(t),this.setFromPoints(_e)),this}translate(t){return this.min.add(t),this.max.add(t),this}equals(t){return t.min.equals(this.min)&&t.max.equals(this.max)}}const _e=[new me,new me,new me,new me,new me,new me,new me,new me],ye=new me,xe=new ve,Me=new me,Se=new me,be=new me,Te=new me,Ee=new me,we=new me,Ae=new me,Re=new me,Ce=new me,Le=new me;function Pe(t,e,n,i,r){for(let s=0,a=t.length-3;s<=a;s+=3){Le.fromArray(t,s);const a=r.x*Math.abs(Le.x)+r.y*Math.abs(Le.y)+r.z*Math.abs(Le.z),o=e.dot(Le),l=n.dot(Le),c=i.dot(Le);if(Math.max(-Math.max(o,l,c),Math.min(o,l,c))>a)return!1}return!0}const Ie=new ve,Ue=new me,De=new me;class Ne{constructor(t=new me,e=-1){this.center=t,this.radius=e}set(t,e){return this.center.copy(t),this.radius=e,this}setFromPoints(t,e){const n=this.center;void 0!==e?n.copy(e):Ie.setFromPoints(t).getCenter(n);let i=0;for(let e=0,r=t.length;ethis.radius*this.radius&&(e.sub(this.center).normalize(),e.multiplyScalar(this.radius).add(this.center)),e}getBoundingBox(t){return this.isEmpty()?(t.makeEmpty(),t):(t.set(this.center,this.center),t.expandByScalar(this.radius),t)}applyMatrix4(t){return this.center.applyMatrix4(t),this.radius=this.radius*t.getMaxScaleOnAxis(),this}translate(t){return this.center.add(t),this}expandByPoint(t){if(this.isEmpty())return this.center.copy(t),this.radius=0,this;Ue.subVectors(t,this.center);const e=Ue.lengthSq();if(e>this.radius*this.radius){const t=Math.sqrt(e),n=.5*(t-this.radius);this.center.addScaledVector(Ue,n/t),this.radius+=n}return this}union(t){return t.isEmpty()?this:this.isEmpty()?(this.copy(t),this):(!0===this.center.equals(t.center)?this.radius=Math.max(this.radius,t.radius):(De.subVectors(t.center,this.center).setLength(t.radius),this.expandByPoint(Ue.copy(t.center).add(De)),this.expandByPoint(Ue.copy(t.center).sub(De))),this)}equals(t){return t.center.equals(this.center)&&t.radius===this.radius}clone(){return(new this.constructor).copy(this)}}const Oe=new me,Fe=new me,Be=new me,ze=new me,Ge=new me,He=new me,ke=new me;class Ve{constructor(t=new me,e=new me(0,0,-1)){this.origin=t,this.direction=e}set(t,e){return this.origin.copy(t),this.direction.copy(e),this}copy(t){return this.origin.copy(t.origin),this.direction.copy(t.direction),this}at(t,e){return e.copy(this.origin).addScaledVector(this.direction,t)}lookAt(t){return this.direction.copy(t).sub(this.origin).normalize(),this}recast(t){return this.origin.copy(this.at(t,Oe)),this}closestPointToPoint(t,e){e.subVectors(t,this.origin);const n=e.dot(this.direction);return n<0?e.copy(this.origin):e.copy(this.origin).addScaledVector(this.direction,n)}distanceToPoint(t){return Math.sqrt(this.distanceSqToPoint(t))}distanceSqToPoint(t){const e=Oe.subVectors(t,this.origin).dot(this.direction);return e<0?this.origin.distanceToSquared(t):(Oe.copy(this.origin).addScaledVector(this.direction,e),Oe.distanceToSquared(t))}distanceSqToSegment(t,e,n,i){Fe.copy(t).add(e).multiplyScalar(.5),Be.copy(e).sub(t).normalize(),ze.copy(this.origin).sub(Fe);const r=.5*t.distanceTo(e),s=-this.direction.dot(Be),a=ze.dot(this.direction),o=-ze.dot(Be),l=ze.lengthSq(),c=Math.abs(1-s*s);let h,u,d,p;if(c>0)if(h=s*o-a,u=s*a-o,p=r*c,h>=0)if(u>=-p)if(u<=p){const t=1/c;h*=t,u*=t,d=h*(h+s*u+2*a)+u*(s*h+u+2*o)+l}else u=r,h=Math.max(0,-(s*u+a)),d=-h*h+u*(u+2*o)+l;else u=-r,h=Math.max(0,-(s*u+a)),d=-h*h+u*(u+2*o)+l;else u<=-p?(h=Math.max(0,-(-s*r+a)),u=h>0?-r:Math.min(Math.max(-r,-o),r),d=-h*h+u*(u+2*o)+l):u<=p?(h=0,u=Math.min(Math.max(-r,-o),r),d=u*(u+2*o)+l):(h=Math.max(0,-(s*r+a)),u=h>0?r:Math.min(Math.max(-r,-o),r),d=-h*h+u*(u+2*o)+l);else u=s>0?-r:r,h=Math.max(0,-(s*u+a)),d=-h*h+u*(u+2*o)+l;return n&&n.copy(this.origin).addScaledVector(this.direction,h),i&&i.copy(Fe).addScaledVector(Be,u),d}intersectSphere(t,e){Oe.subVectors(t.center,this.origin);const n=Oe.dot(this.direction),i=Oe.dot(Oe)-n*n,r=t.radius*t.radius;if(i>r)return null;const s=Math.sqrt(r-i),a=n-s,o=n+s;return o<0?null:a<0?this.at(o,e):this.at(a,e)}intersectsSphere(t){return this.distanceSqToPoint(t.center)<=t.radius*t.radius}distanceToPlane(t){const e=t.normal.dot(this.direction);if(0===e)return 0===t.distanceToPoint(this.origin)?0:null;const n=-(this.origin.dot(t.normal)+t.constant)/e;return n>=0?n:null}intersectPlane(t,e){const n=this.distanceToPlane(t);return null===n?null:this.at(n,e)}intersectsPlane(t){const e=t.distanceToPoint(this.origin);if(0===e)return!0;return t.normal.dot(this.direction)*e<0}intersectBox(t,e){let n,i,r,s,a,o;const l=1/this.direction.x,c=1/this.direction.y,h=1/this.direction.z,u=this.origin;return l>=0?(n=(t.min.x-u.x)*l,i=(t.max.x-u.x)*l):(n=(t.max.x-u.x)*l,i=(t.min.x-u.x)*l),c>=0?(r=(t.min.y-u.y)*c,s=(t.max.y-u.y)*c):(r=(t.max.y-u.y)*c,s=(t.min.y-u.y)*c),n>s||r>i?null:((r>n||isNaN(n))&&(n=r),(s=0?(a=(t.min.z-u.z)*h,o=(t.max.z-u.z)*h):(a=(t.max.z-u.z)*h,o=(t.min.z-u.z)*h),n>o||a>i?null:((a>n||n!=n)&&(n=a),(o=0?n:i,e)))}intersectsBox(t){return null!==this.intersectBox(t,Oe)}intersectTriangle(t,e,n,i,r){Ge.subVectors(e,t),He.subVectors(n,t),ke.crossVectors(Ge,He);let s,a=this.direction.dot(ke);if(a>0){if(i)return null;s=1}else{if(!(a<0))return null;s=-1,a=-a}ze.subVectors(this.origin,t);const o=s*this.direction.dot(He.crossVectors(ze,He));if(o<0)return null;const l=s*this.direction.dot(Ge.cross(ze));if(l<0)return null;if(o+l>a)return null;const c=-s*ze.dot(ke);return c<0?null:this.at(c/a,r)}applyMatrix4(t){return this.origin.applyMatrix4(t),this.direction.transformDirection(t),this}equals(t){return t.origin.equals(this.origin)&&t.direction.equals(this.direction)}clone(){return(new this.constructor).copy(this)}}class We{constructor(t,e,n,i,r,s,a,o,l,c,h,u,d,p,m,f){We.prototype.isMatrix4=!0,this.elements=[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],void 0!==t&&this.set(t,e,n,i,r,s,a,o,l,c,h,u,d,p,m,f)}set(t,e,n,i,r,s,a,o,l,c,h,u,d,p,m,f){const g=this.elements;return g[0]=t,g[4]=e,g[8]=n,g[12]=i,g[1]=r,g[5]=s,g[9]=a,g[13]=o,g[2]=l,g[6]=c,g[10]=h,g[14]=u,g[3]=d,g[7]=p,g[11]=m,g[15]=f,this}identity(){return this.set(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1),this}clone(){return(new We).fromArray(this.elements)}copy(t){const e=this.elements,n=t.elements;return e[0]=n[0],e[1]=n[1],e[2]=n[2],e[3]=n[3],e[4]=n[4],e[5]=n[5],e[6]=n[6],e[7]=n[7],e[8]=n[8],e[9]=n[9],e[10]=n[10],e[11]=n[11],e[12]=n[12],e[13]=n[13],e[14]=n[14],e[15]=n[15],this}copyPosition(t){const e=this.elements,n=t.elements;return e[12]=n[12],e[13]=n[13],e[14]=n[14],this}setFromMatrix3(t){const e=t.elements;return this.set(e[0],e[3],e[6],0,e[1],e[4],e[7],0,e[2],e[5],e[8],0,0,0,0,1),this}extractBasis(t,e,n){return t.setFromMatrixColumn(this,0),e.setFromMatrixColumn(this,1),n.setFromMatrixColumn(this,2),this}makeBasis(t,e,n){return this.set(t.x,e.x,n.x,0,t.y,e.y,n.y,0,t.z,e.z,n.z,0,0,0,0,1),this}extractRotation(t){const e=this.elements,n=t.elements,i=1/Xe.setFromMatrixColumn(t,0).length(),r=1/Xe.setFromMatrixColumn(t,1).length(),s=1/Xe.setFromMatrixColumn(t,2).length();return e[0]=n[0]*i,e[1]=n[1]*i,e[2]=n[2]*i,e[3]=0,e[4]=n[4]*r,e[5]=n[5]*r,e[6]=n[6]*r,e[7]=0,e[8]=n[8]*s,e[9]=n[9]*s,e[10]=n[10]*s,e[11]=0,e[12]=0,e[13]=0,e[14]=0,e[15]=1,this}makeRotationFromEuler(t){const e=this.elements,n=t.x,i=t.y,r=t.z,s=Math.cos(n),a=Math.sin(n),o=Math.cos(i),l=Math.sin(i),c=Math.cos(r),h=Math.sin(r);if("XYZ"===t.order){const t=s*c,n=s*h,i=a*c,r=a*h;e[0]=o*c,e[4]=-o*h,e[8]=l,e[1]=n+i*l,e[5]=t-r*l,e[9]=-a*o,e[2]=r-t*l,e[6]=i+n*l,e[10]=s*o}else if("YXZ"===t.order){const t=o*c,n=o*h,i=l*c,r=l*h;e[0]=t+r*a,e[4]=i*a-n,e[8]=s*l,e[1]=s*h,e[5]=s*c,e[9]=-a,e[2]=n*a-i,e[6]=r+t*a,e[10]=s*o}else if("ZXY"===t.order){const t=o*c,n=o*h,i=l*c,r=l*h;e[0]=t-r*a,e[4]=-s*h,e[8]=i+n*a,e[1]=n+i*a,e[5]=s*c,e[9]=r-t*a,e[2]=-s*l,e[6]=a,e[10]=s*o}else if("ZYX"===t.order){const t=s*c,n=s*h,i=a*c,r=a*h;e[0]=o*c,e[4]=i*l-n,e[8]=t*l+r,e[1]=o*h,e[5]=r*l+t,e[9]=n*l-i,e[2]=-l,e[6]=a*o,e[10]=s*o}else if("YZX"===t.order){const t=s*o,n=s*l,i=a*o,r=a*l;e[0]=o*c,e[4]=r-t*h,e[8]=i*h+n,e[1]=h,e[5]=s*c,e[9]=-a*c,e[2]=-l*c,e[6]=n*h+i,e[10]=t-r*h}else if("XZY"===t.order){const t=s*o,n=s*l,i=a*o,r=a*l;e[0]=o*c,e[4]=-h,e[8]=l*c,e[1]=t*h+r,e[5]=s*c,e[9]=n*h-i,e[2]=i*h-n,e[6]=a*c,e[10]=r*h+t}return e[3]=0,e[7]=0,e[11]=0,e[12]=0,e[13]=0,e[14]=0,e[15]=1,this}makeRotationFromQuaternion(t){return this.compose(qe,t,Ye)}lookAt(t,e,n){const i=this.elements;return Ke.subVectors(t,e),0===Ke.lengthSq()&&(Ke.z=1),Ke.normalize(),Ze.crossVectors(n,Ke),0===Ze.lengthSq()&&(1===Math.abs(n.z)?Ke.x+=1e-4:Ke.z+=1e-4,Ke.normalize(),Ze.crossVectors(n,Ke)),Ze.normalize(),Je.crossVectors(Ke,Ze),i[0]=Ze.x,i[4]=Je.x,i[8]=Ke.x,i[1]=Ze.y,i[5]=Je.y,i[9]=Ke.y,i[2]=Ze.z,i[6]=Je.z,i[10]=Ke.z,this}multiply(t){return this.multiplyMatrices(this,t)}premultiply(t){return this.multiplyMatrices(t,this)}multiplyMatrices(t,e){const n=t.elements,i=e.elements,r=this.elements,s=n[0],a=n[4],o=n[8],l=n[12],c=n[1],h=n[5],u=n[9],d=n[13],p=n[2],m=n[6],f=n[10],g=n[14],v=n[3],_=n[7],y=n[11],x=n[15],M=i[0],S=i[4],b=i[8],T=i[12],E=i[1],w=i[5],A=i[9],R=i[13],C=i[2],L=i[6],P=i[10],I=i[14],U=i[3],D=i[7],N=i[11],O=i[15];return r[0]=s*M+a*E+o*C+l*U,r[4]=s*S+a*w+o*L+l*D,r[8]=s*b+a*A+o*P+l*N,r[12]=s*T+a*R+o*I+l*O,r[1]=c*M+h*E+u*C+d*U,r[5]=c*S+h*w+u*L+d*D,r[9]=c*b+h*A+u*P+d*N,r[13]=c*T+h*R+u*I+d*O,r[2]=p*M+m*E+f*C+g*U,r[6]=p*S+m*w+f*L+g*D,r[10]=p*b+m*A+f*P+g*N,r[14]=p*T+m*R+f*I+g*O,r[3]=v*M+_*E+y*C+x*U,r[7]=v*S+_*w+y*L+x*D,r[11]=v*b+_*A+y*P+x*N,r[15]=v*T+_*R+y*I+x*O,this}multiplyScalar(t){const e=this.elements;return e[0]*=t,e[4]*=t,e[8]*=t,e[12]*=t,e[1]*=t,e[5]*=t,e[9]*=t,e[13]*=t,e[2]*=t,e[6]*=t,e[10]*=t,e[14]*=t,e[3]*=t,e[7]*=t,e[11]*=t,e[15]*=t,this}determinant(){const t=this.elements,e=t[0],n=t[4],i=t[8],r=t[12],s=t[1],a=t[5],o=t[9],l=t[13],c=t[2],h=t[6],u=t[10],d=t[14];return t[3]*(+r*o*h-i*l*h-r*a*u+n*l*u+i*a*d-n*o*d)+t[7]*(+e*o*d-e*l*u+r*s*u-i*s*d+i*l*c-r*o*c)+t[11]*(+e*l*h-e*a*d-r*s*h+n*s*d+r*a*c-n*l*c)+t[15]*(-i*a*c-e*o*h+e*a*u+i*s*h-n*s*u+n*o*c)}transpose(){const t=this.elements;let e;return e=t[1],t[1]=t[4],t[4]=e,e=t[2],t[2]=t[8],t[8]=e,e=t[6],t[6]=t[9],t[9]=e,e=t[3],t[3]=t[12],t[12]=e,e=t[7],t[7]=t[13],t[13]=e,e=t[11],t[11]=t[14],t[14]=e,this}setPosition(t,e,n){const i=this.elements;return t.isVector3?(i[12]=t.x,i[13]=t.y,i[14]=t.z):(i[12]=t,i[13]=e,i[14]=n),this}invert(){const t=this.elements,e=t[0],n=t[1],i=t[2],r=t[3],s=t[4],a=t[5],o=t[6],l=t[7],c=t[8],h=t[9],u=t[10],d=t[11],p=t[12],m=t[13],f=t[14],g=t[15],v=h*f*l-m*u*l+m*o*d-a*f*d-h*o*g+a*u*g,_=p*u*l-c*f*l-p*o*d+s*f*d+c*o*g-s*u*g,y=c*m*l-p*h*l+p*a*d-s*m*d-c*a*g+s*h*g,x=p*h*o-c*m*o-p*a*u+s*m*u+c*a*f-s*h*f,M=e*v+n*_+i*y+r*x;if(0===M)return this.set(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);const S=1/M;return t[0]=v*S,t[1]=(m*u*r-h*f*r-m*i*d+n*f*d+h*i*g-n*u*g)*S,t[2]=(a*f*r-m*o*r+m*i*l-n*f*l-a*i*g+n*o*g)*S,t[3]=(h*o*r-a*u*r-h*i*l+n*u*l+a*i*d-n*o*d)*S,t[4]=_*S,t[5]=(c*f*r-p*u*r+p*i*d-e*f*d-c*i*g+e*u*g)*S,t[6]=(p*o*r-s*f*r-p*i*l+e*f*l+s*i*g-e*o*g)*S,t[7]=(s*u*r-c*o*r+c*i*l-e*u*l-s*i*d+e*o*d)*S,t[8]=y*S,t[9]=(p*h*r-c*m*r-p*n*d+e*m*d+c*n*g-e*h*g)*S,t[10]=(s*m*r-p*a*r+p*n*l-e*m*l-s*n*g+e*a*g)*S,t[11]=(c*a*r-s*h*r-c*n*l+e*h*l+s*n*d-e*a*d)*S,t[12]=x*S,t[13]=(c*m*i-p*h*i+p*n*u-e*m*u-c*n*f+e*h*f)*S,t[14]=(p*a*i-s*m*i-p*n*o+e*m*o+s*n*f-e*a*f)*S,t[15]=(s*h*i-c*a*i+c*n*o-e*h*o-s*n*u+e*a*u)*S,this}scale(t){const e=this.elements,n=t.x,i=t.y,r=t.z;return e[0]*=n,e[4]*=i,e[8]*=r,e[1]*=n,e[5]*=i,e[9]*=r,e[2]*=n,e[6]*=i,e[10]*=r,e[3]*=n,e[7]*=i,e[11]*=r,this}getMaxScaleOnAxis(){const t=this.elements,e=t[0]*t[0]+t[1]*t[1]+t[2]*t[2],n=t[4]*t[4]+t[5]*t[5]+t[6]*t[6],i=t[8]*t[8]+t[9]*t[9]+t[10]*t[10];return Math.sqrt(Math.max(e,n,i))}makeTranslation(t,e,n){return this.set(1,0,0,t,0,1,0,e,0,0,1,n,0,0,0,1),this}makeRotationX(t){const e=Math.cos(t),n=Math.sin(t);return this.set(1,0,0,0,0,e,-n,0,0,n,e,0,0,0,0,1),this}makeRotationY(t){const e=Math.cos(t),n=Math.sin(t);return this.set(e,0,n,0,0,1,0,0,-n,0,e,0,0,0,0,1),this}makeRotationZ(t){const e=Math.cos(t),n=Math.sin(t);return this.set(e,-n,0,0,n,e,0,0,0,0,1,0,0,0,0,1),this}makeRotationAxis(t,e){const n=Math.cos(e),i=Math.sin(e),r=1-n,s=t.x,a=t.y,o=t.z,l=r*s,c=r*a;return this.set(l*s+n,l*a-i*o,l*o+i*a,0,l*a+i*o,c*a+n,c*o-i*s,0,l*o-i*a,c*o+i*s,r*o*o+n,0,0,0,0,1),this}makeScale(t,e,n){return this.set(t,0,0,0,0,e,0,0,0,0,n,0,0,0,0,1),this}makeShear(t,e,n,i,r,s){return this.set(1,n,r,0,t,1,s,0,e,i,1,0,0,0,0,1),this}compose(t,e,n){const i=this.elements,r=e._x,s=e._y,a=e._z,o=e._w,l=r+r,c=s+s,h=a+a,u=r*l,d=r*c,p=r*h,m=s*c,f=s*h,g=a*h,v=o*l,_=o*c,y=o*h,x=n.x,M=n.y,S=n.z;return i[0]=(1-(m+g))*x,i[1]=(d+y)*x,i[2]=(p-_)*x,i[3]=0,i[4]=(d-y)*M,i[5]=(1-(u+g))*M,i[6]=(f+v)*M,i[7]=0,i[8]=(p+_)*S,i[9]=(f-v)*S,i[10]=(1-(u+m))*S,i[11]=0,i[12]=t.x,i[13]=t.y,i[14]=t.z,i[15]=1,this}decompose(t,e,n){const i=this.elements;let r=Xe.set(i[0],i[1],i[2]).length();const s=Xe.set(i[4],i[5],i[6]).length(),a=Xe.set(i[8],i[9],i[10]).length();this.determinant()<0&&(r=-r),t.x=i[12],t.y=i[13],t.z=i[14],je.copy(this);const o=1/r,l=1/s,c=1/a;return je.elements[0]*=o,je.elements[1]*=o,je.elements[2]*=o,je.elements[4]*=l,je.elements[5]*=l,je.elements[6]*=l,je.elements[8]*=c,je.elements[9]*=c,je.elements[10]*=c,e.setFromRotationMatrix(je),n.x=r,n.y=s,n.z=a,this}makePerspective(t,e,n,i,r,s){const a=this.elements,o=2*r/(e-t),l=2*r/(n-i),c=(e+t)/(e-t),h=(n+i)/(n-i),u=-(s+r)/(s-r),d=-2*s*r/(s-r);return a[0]=o,a[4]=0,a[8]=c,a[12]=0,a[1]=0,a[5]=l,a[9]=h,a[13]=0,a[2]=0,a[6]=0,a[10]=u,a[14]=d,a[3]=0,a[7]=0,a[11]=-1,a[15]=0,this}makeOrthographic(t,e,n,i,r,s){const a=this.elements,o=1/(e-t),l=1/(n-i),c=1/(s-r),h=(e+t)*o,u=(n+i)*l,d=(s+r)*c;return a[0]=2*o,a[4]=0,a[8]=0,a[12]=-h,a[1]=0,a[5]=2*l,a[9]=0,a[13]=-u,a[2]=0,a[6]=0,a[10]=-2*c,a[14]=-d,a[3]=0,a[7]=0,a[11]=0,a[15]=1,this}equals(t){const e=this.elements,n=t.elements;for(let t=0;t<16;t++)if(e[t]!==n[t])return!1;return!0}fromArray(t,e=0){for(let n=0;n<16;n++)this.elements[n]=t[n+e];return this}toArray(t=[],e=0){const n=this.elements;return t[e]=n[0],t[e+1]=n[1],t[e+2]=n[2],t[e+3]=n[3],t[e+4]=n[4],t[e+5]=n[5],t[e+6]=n[6],t[e+7]=n[7],t[e+8]=n[8],t[e+9]=n[9],t[e+10]=n[10],t[e+11]=n[11],t[e+12]=n[12],t[e+13]=n[13],t[e+14]=n[14],t[e+15]=n[15],t}}const Xe=new me,je=new We,qe=new me(0,0,0),Ye=new me(1,1,1),Ze=new me,Je=new me,Ke=new me,$e=new We,Qe=new pe;class tn{constructor(t=0,e=0,n=0,i=tn.DEFAULT_ORDER){this.isEuler=!0,this._x=t,this._y=e,this._z=n,this._order=i}get x(){return this._x}set x(t){this._x=t,this._onChangeCallback()}get y(){return this._y}set y(t){this._y=t,this._onChangeCallback()}get z(){return this._z}set z(t){this._z=t,this._onChangeCallback()}get order(){return this._order}set order(t){this._order=t,this._onChangeCallback()}set(t,e,n,i=this._order){return this._x=t,this._y=e,this._z=n,this._order=i,this._onChangeCallback(),this}clone(){return new this.constructor(this._x,this._y,this._z,this._order)}copy(t){return this._x=t._x,this._y=t._y,this._z=t._z,this._order=t._order,this._onChangeCallback(),this}setFromRotationMatrix(t,e=this._order,n=!0){const i=t.elements,r=i[0],s=i[4],a=i[8],o=i[1],l=i[5],c=i[9],h=i[2],u=i[6],d=i[10];switch(e){case"XYZ":this._y=Math.asin(Pt(a,-1,1)),Math.abs(a)<.9999999?(this._x=Math.atan2(-c,d),this._z=Math.atan2(-s,r)):(this._x=Math.atan2(u,l),this._z=0);break;case"YXZ":this._x=Math.asin(-Pt(c,-1,1)),Math.abs(c)<.9999999?(this._y=Math.atan2(a,d),this._z=Math.atan2(o,l)):(this._y=Math.atan2(-h,r),this._z=0);break;case"ZXY":this._x=Math.asin(Pt(u,-1,1)),Math.abs(u)<.9999999?(this._y=Math.atan2(-h,d),this._z=Math.atan2(-s,l)):(this._y=0,this._z=Math.atan2(o,r));break;case"ZYX":this._y=Math.asin(-Pt(h,-1,1)),Math.abs(h)<.9999999?(this._x=Math.atan2(u,d),this._z=Math.atan2(o,r)):(this._x=0,this._z=Math.atan2(-s,l));break;case"YZX":this._z=Math.asin(Pt(o,-1,1)),Math.abs(o)<.9999999?(this._x=Math.atan2(-c,l),this._y=Math.atan2(-h,r)):(this._x=0,this._y=Math.atan2(a,d));break;case"XZY":this._z=Math.asin(-Pt(s,-1,1)),Math.abs(s)<.9999999?(this._x=Math.atan2(u,l),this._y=Math.atan2(a,r)):(this._x=Math.atan2(-c,d),this._y=0);break;default:console.warn("THREE.Euler: .setFromRotationMatrix() encountered an unknown order: "+e)}return this._order=e,!0===n&&this._onChangeCallback(),this}setFromQuaternion(t,e,n){return $e.makeRotationFromQuaternion(t),this.setFromRotationMatrix($e,e,n)}setFromVector3(t,e=this._order){return this.set(t.x,t.y,t.z,e)}reorder(t){return Qe.setFromEuler(this),this.setFromQuaternion(Qe,t)}equals(t){return t._x===this._x&&t._y===this._y&&t._z===this._z&&t._order===this._order}fromArray(t){return this._x=t[0],this._y=t[1],this._z=t[2],void 0!==t[3]&&(this._order=t[3]),this._onChangeCallback(),this}toArray(t=[],e=0){return t[e]=this._x,t[e+1]=this._y,t[e+2]=this._z,t[e+3]=this._order,t}_onChange(t){return this._onChangeCallback=t,this}_onChangeCallback(){}*[Symbol.iterator](){yield this._x,yield this._y,yield this._z,yield this._order}}tn.DEFAULT_ORDER="XYZ";class en{constructor(){this.mask=1}set(t){this.mask=(1<>>0}enable(t){this.mask|=1<1){for(let t=0;t1){for(let t=0;t0&&(n=n.concat(r))}return n}getWorldPosition(t){return this.updateWorldMatrix(!0,!1),t.setFromMatrixPosition(this.matrixWorld)}getWorldQuaternion(t){return this.updateWorldMatrix(!0,!1),this.matrixWorld.decompose(ln,t,cn),t}getWorldScale(t){return this.updateWorldMatrix(!0,!1),this.matrixWorld.decompose(ln,hn,t),t}getWorldDirection(t){this.updateWorldMatrix(!0,!1);const e=this.matrixWorld.elements;return t.set(e[8],e[9],e[10]).normalize()}raycast(){}traverse(t){t(this);const e=this.children;for(let n=0,i=e.length;n0&&(i.userData=this.userData),i.layers=this.layers.mask,i.matrix=this.matrix.toArray(),i.up=this.up.toArray(),!1===this.matrixAutoUpdate&&(i.matrixAutoUpdate=!1),this.isInstancedMesh&&(i.type="InstancedMesh",i.count=this.count,i.instanceMatrix=this.instanceMatrix.toJSON(),null!==this.instanceColor&&(i.instanceColor=this.instanceColor.toJSON())),this.isScene)this.background&&(this.background.isColor?i.background=this.background.toJSON():this.background.isTexture&&(i.background=this.background.toJSON(t).uuid)),this.environment&&this.environment.isTexture&&!0!==this.environment.isRenderTargetTexture&&(i.environment=this.environment.toJSON(t).uuid);else if(this.isMesh||this.isLine||this.isPoints){i.geometry=r(t.geometries,this.geometry);const e=this.geometry.parameters;if(void 0!==e&&void 0!==e.shapes){const n=e.shapes;if(Array.isArray(n))for(let e=0,i=n.length;e0){i.children=[];for(let e=0;e0){i.animations=[];for(let e=0;e0&&(n.geometries=e),i.length>0&&(n.materials=i),r.length>0&&(n.textures=r),a.length>0&&(n.images=a),o.length>0&&(n.shapes=o),l.length>0&&(n.skeletons=l),c.length>0&&(n.animations=c),h.length>0&&(n.nodes=h)}return n.object=i,n;function s(t){const e=[];for(const n in t){const i=t[n];delete i.metadata,e.push(i)}return e}}clone(t){return(new this.constructor).copy(this,t)}copy(t,e=!0){if(this.name=t.name,this.up.copy(t.up),this.position.copy(t.position),this.rotation.order=t.rotation.order,this.quaternion.copy(t.quaternion),this.scale.copy(t.scale),this.matrix.copy(t.matrix),this.matrixWorld.copy(t.matrixWorld),this.matrixAutoUpdate=t.matrixAutoUpdate,this.matrixWorldNeedsUpdate=t.matrixWorldNeedsUpdate,this.matrixWorldAutoUpdate=t.matrixWorldAutoUpdate,this.layers.mask=t.layers.mask,this.visible=t.visible,this.castShadow=t.castShadow,this.receiveShadow=t.receiveShadow,this.frustumCulled=t.frustumCulled,this.renderOrder=t.renderOrder,this.animations=t.animations,this.userData=JSON.parse(JSON.stringify(t.userData)),!0===e)for(let e=0;e0?i.multiplyScalar(1/Math.sqrt(r)):i.set(0,0,0)}static getBarycoord(t,e,n,i,r){vn.subVectors(i,e),_n.subVectors(n,e),yn.subVectors(t,e);const s=vn.dot(vn),a=vn.dot(_n),o=vn.dot(yn),l=_n.dot(_n),c=_n.dot(yn),h=s*l-a*a;if(0===h)return r.set(-2,-1,-1);const u=1/h,d=(l*o-a*c)*u,p=(s*c-a*o)*u;return r.set(1-d-p,p,d)}static containsPoint(t,e,n,i){return this.getBarycoord(t,e,n,i,xn),xn.x>=0&&xn.y>=0&&xn.x+xn.y<=1}static getUV(t,e,n,i,r,s,a,o){return!1===An&&(console.warn("THREE.Triangle.getUV() has been renamed to THREE.Triangle.getInterpolation()."),An=!0),this.getInterpolation(t,e,n,i,r,s,a,o)}static getInterpolation(t,e,n,i,r,s,a,o){return this.getBarycoord(t,e,n,i,xn),o.setScalar(0),o.addScaledVector(r,xn.x),o.addScaledVector(s,xn.y),o.addScaledVector(a,xn.z),o}static isFrontFacing(t,e,n,i){return vn.subVectors(n,e),_n.subVectors(t,e),vn.cross(_n).dot(i)<0}set(t,e,n){return this.a.copy(t),this.b.copy(e),this.c.copy(n),this}setFromPointsAndIndices(t,e,n,i){return this.a.copy(t[e]),this.b.copy(t[n]),this.c.copy(t[i]),this}setFromAttributeAndIndices(t,e,n,i){return this.a.fromBufferAttribute(t,e),this.b.fromBufferAttribute(t,n),this.c.fromBufferAttribute(t,i),this}clone(){return(new this.constructor).copy(this)}copy(t){return this.a.copy(t.a),this.b.copy(t.b),this.c.copy(t.c),this}getArea(){return vn.subVectors(this.c,this.b),_n.subVectors(this.a,this.b),.5*vn.cross(_n).length()}getMidpoint(t){return t.addVectors(this.a,this.b).add(this.c).multiplyScalar(1/3)}getNormal(t){return Rn.getNormal(this.a,this.b,this.c,t)}getPlane(t){return t.setFromCoplanarPoints(this.a,this.b,this.c)}getBarycoord(t,e){return Rn.getBarycoord(t,this.a,this.b,this.c,e)}getUV(t,e,n,i,r){return!1===An&&(console.warn("THREE.Triangle.getUV() has been renamed to THREE.Triangle.getInterpolation()."),An=!0),Rn.getInterpolation(t,this.a,this.b,this.c,e,n,i,r)}getInterpolation(t,e,n,i,r){return Rn.getInterpolation(t,this.a,this.b,this.c,e,n,i,r)}containsPoint(t){return Rn.containsPoint(t,this.a,this.b,this.c)}isFrontFacing(t){return Rn.isFrontFacing(this.a,this.b,this.c,t)}intersectsBox(t){return t.intersectsTriangle(this)}closestPointToPoint(t,e){const n=this.a,i=this.b,r=this.c;let s,a;Mn.subVectors(i,n),Sn.subVectors(r,n),Tn.subVectors(t,n);const o=Mn.dot(Tn),l=Sn.dot(Tn);if(o<=0&&l<=0)return e.copy(n);En.subVectors(t,i);const c=Mn.dot(En),h=Sn.dot(En);if(c>=0&&h<=c)return e.copy(i);const u=o*h-c*l;if(u<=0&&o>=0&&c<=0)return s=o/(o-c),e.copy(n).addScaledVector(Mn,s);wn.subVectors(t,r);const d=Mn.dot(wn),p=Sn.dot(wn);if(p>=0&&d<=p)return e.copy(r);const m=d*l-o*p;if(m<=0&&l>=0&&p<=0)return a=l/(l-p),e.copy(n).addScaledVector(Sn,a);const f=c*p-d*h;if(f<=0&&h-c>=0&&d-p>=0)return bn.subVectors(r,i),a=(h-c)/(h-c+(d-p)),e.copy(i).addScaledVector(bn,a);const g=1/(f+m+u);return s=m*g,a=u*g,e.copy(n).addScaledVector(Mn,s).addScaledVector(Sn,a)}equals(t){return t.a.equals(this.a)&&t.b.equals(this.b)&&t.c.equals(this.c)}}let Cn=0;class Ln extends Et{constructor(){super(),this.isMaterial=!0,Object.defineProperty(this,"id",{value:Cn++}),this.uuid=Lt(),this.name="",this.type="Material",this.blending=1,this.side=0,this.vertexColors=!1,this.opacity=1,this.transparent=!1,this.blendSrc=204,this.blendDst=205,this.blendEquation=n,this.blendSrcAlpha=null,this.blendDstAlpha=null,this.blendEquationAlpha=null,this.depthFunc=3,this.depthTest=!0,this.depthWrite=!0,this.stencilWriteMask=255,this.stencilFunc=519,this.stencilRef=0,this.stencilFuncMask=255,this.stencilFail=Mt,this.stencilZFail=Mt,this.stencilZPass=Mt,this.stencilWrite=!1,this.clippingPlanes=null,this.clipIntersection=!1,this.clipShadows=!1,this.shadowSide=null,this.colorWrite=!0,this.precision=null,this.polygonOffset=!1,this.polygonOffsetFactor=0,this.polygonOffsetUnits=0,this.dithering=!1,this.alphaToCoverage=!1,this.premultipliedAlpha=!1,this.forceSinglePass=!1,this.visible=!0,this.toneMapped=!0,this.userData={},this.version=0,this._alphaTest=0}get alphaTest(){return this._alphaTest}set alphaTest(t){this._alphaTest>0!=t>0&&this.version++,this._alphaTest=t}onBuild(){}onBeforeRender(){}onBeforeCompile(){}customProgramCacheKey(){return this.onBeforeCompile.toString()}setValues(t){if(void 0!==t)for(const e in t){const n=t[e];if(void 0===n){console.warn(`THREE.Material: parameter '${e}' has value of undefined.`);continue}const i=this[e];void 0!==i?i&&i.isColor?i.set(n):i&&i.isVector3&&n&&n.isVector3?i.copy(n):this[e]=n:console.warn(`THREE.Material: '${e}' is not a property of THREE.${this.type}.`)}}toJSON(t){const e=void 0===t||"string"==typeof t;e&&(t={textures:{},images:{}});const n={metadata:{version:4.6,type:"Material",generator:"Material.toJSON"}};function i(t){const e=[];for(const n in t){const i=t[n];delete i.metadata,e.push(i)}return e}if(n.uuid=this.uuid,n.type=this.type,""!==this.name&&(n.name=this.name),this.color&&this.color.isColor&&(n.color=this.color.getHex()),void 0!==this.roughness&&(n.roughness=this.roughness),void 0!==this.metalness&&(n.metalness=this.metalness),void 0!==this.sheen&&(n.sheen=this.sheen),this.sheenColor&&this.sheenColor.isColor&&(n.sheenColor=this.sheenColor.getHex()),void 0!==this.sheenRoughness&&(n.sheenRoughness=this.sheenRoughness),this.emissive&&this.emissive.isColor&&(n.emissive=this.emissive.getHex()),this.emissiveIntensity&&1!==this.emissiveIntensity&&(n.emissiveIntensity=this.emissiveIntensity),this.specular&&this.specular.isColor&&(n.specular=this.specular.getHex()),void 0!==this.specularIntensity&&(n.specularIntensity=this.specularIntensity),this.specularColor&&this.specularColor.isColor&&(n.specularColor=this.specularColor.getHex()),void 0!==this.shininess&&(n.shininess=this.shininess),void 0!==this.clearcoat&&(n.clearcoat=this.clearcoat),void 0!==this.clearcoatRoughness&&(n.clearcoatRoughness=this.clearcoatRoughness),this.clearcoatMap&&this.clearcoatMap.isTexture&&(n.clearcoatMap=this.clearcoatMap.toJSON(t).uuid),this.clearcoatRoughnessMap&&this.clearcoatRoughnessMap.isTexture&&(n.clearcoatRoughnessMap=this.clearcoatRoughnessMap.toJSON(t).uuid),this.clearcoatNormalMap&&this.clearcoatNormalMap.isTexture&&(n.clearcoatNormalMap=this.clearcoatNormalMap.toJSON(t).uuid,n.clearcoatNormalScale=this.clearcoatNormalScale.toArray()),void 0!==this.iridescence&&(n.iridescence=this.iridescence),void 0!==this.iridescenceIOR&&(n.iridescenceIOR=this.iridescenceIOR),void 0!==this.iridescenceThicknessRange&&(n.iridescenceThicknessRange=this.iridescenceThicknessRange),this.iridescenceMap&&this.iridescenceMap.isTexture&&(n.iridescenceMap=this.iridescenceMap.toJSON(t).uuid),this.iridescenceThicknessMap&&this.iridescenceThicknessMap.isTexture&&(n.iridescenceThicknessMap=this.iridescenceThicknessMap.toJSON(t).uuid),void 0!==this.anisotropy&&(n.anisotropy=this.anisotropy),void 0!==this.anisotropyRotation&&(n.anisotropyRotation=this.anisotropyRotation),this.anisotropyMap&&this.anisotropyMap.isTexture&&(n.anisotropyMap=this.anisotropyMap.toJSON(t).uuid),this.map&&this.map.isTexture&&(n.map=this.map.toJSON(t).uuid),this.matcap&&this.matcap.isTexture&&(n.matcap=this.matcap.toJSON(t).uuid),this.alphaMap&&this.alphaMap.isTexture&&(n.alphaMap=this.alphaMap.toJSON(t).uuid),this.lightMap&&this.lightMap.isTexture&&(n.lightMap=this.lightMap.toJSON(t).uuid,n.lightMapIntensity=this.lightMapIntensity),this.aoMap&&this.aoMap.isTexture&&(n.aoMap=this.aoMap.toJSON(t).uuid,n.aoMapIntensity=this.aoMapIntensity),this.bumpMap&&this.bumpMap.isTexture&&(n.bumpMap=this.bumpMap.toJSON(t).uuid,n.bumpScale=this.bumpScale),this.normalMap&&this.normalMap.isTexture&&(n.normalMap=this.normalMap.toJSON(t).uuid,n.normalMapType=this.normalMapType,n.normalScale=this.normalScale.toArray()),this.displacementMap&&this.displacementMap.isTexture&&(n.displacementMap=this.displacementMap.toJSON(t).uuid,n.displacementScale=this.displacementScale,n.displacementBias=this.displacementBias),this.roughnessMap&&this.roughnessMap.isTexture&&(n.roughnessMap=this.roughnessMap.toJSON(t).uuid),this.metalnessMap&&this.metalnessMap.isTexture&&(n.metalnessMap=this.metalnessMap.toJSON(t).uuid),this.emissiveMap&&this.emissiveMap.isTexture&&(n.emissiveMap=this.emissiveMap.toJSON(t).uuid),this.specularMap&&this.specularMap.isTexture&&(n.specularMap=this.specularMap.toJSON(t).uuid),this.specularIntensityMap&&this.specularIntensityMap.isTexture&&(n.specularIntensityMap=this.specularIntensityMap.toJSON(t).uuid),this.specularColorMap&&this.specularColorMap.isTexture&&(n.specularColorMap=this.specularColorMap.toJSON(t).uuid),this.envMap&&this.envMap.isTexture&&(n.envMap=this.envMap.toJSON(t).uuid,void 0!==this.combine&&(n.combine=this.combine)),void 0!==this.envMapIntensity&&(n.envMapIntensity=this.envMapIntensity),void 0!==this.reflectivity&&(n.reflectivity=this.reflectivity),void 0!==this.refractionRatio&&(n.refractionRatio=this.refractionRatio),this.gradientMap&&this.gradientMap.isTexture&&(n.gradientMap=this.gradientMap.toJSON(t).uuid),void 0!==this.transmission&&(n.transmission=this.transmission),this.transmissionMap&&this.transmissionMap.isTexture&&(n.transmissionMap=this.transmissionMap.toJSON(t).uuid),void 0!==this.thickness&&(n.thickness=this.thickness),this.thicknessMap&&this.thicknessMap.isTexture&&(n.thicknessMap=this.thicknessMap.toJSON(t).uuid),void 0!==this.attenuationDistance&&this.attenuationDistance!==1/0&&(n.attenuationDistance=this.attenuationDistance),void 0!==this.attenuationColor&&(n.attenuationColor=this.attenuationColor.getHex()),void 0!==this.size&&(n.size=this.size),null!==this.shadowSide&&(n.shadowSide=this.shadowSide),void 0!==this.sizeAttenuation&&(n.sizeAttenuation=this.sizeAttenuation),1!==this.blending&&(n.blending=this.blending),0!==this.side&&(n.side=this.side),this.vertexColors&&(n.vertexColors=!0),this.opacity<1&&(n.opacity=this.opacity),!0===this.transparent&&(n.transparent=this.transparent),n.depthFunc=this.depthFunc,n.depthTest=this.depthTest,n.depthWrite=this.depthWrite,n.colorWrite=this.colorWrite,n.stencilWrite=this.stencilWrite,n.stencilWriteMask=this.stencilWriteMask,n.stencilFunc=this.stencilFunc,n.stencilRef=this.stencilRef,n.stencilFuncMask=this.stencilFuncMask,n.stencilFail=this.stencilFail,n.stencilZFail=this.stencilZFail,n.stencilZPass=this.stencilZPass,void 0!==this.rotation&&0!==this.rotation&&(n.rotation=this.rotation),!0===this.polygonOffset&&(n.polygonOffset=!0),0!==this.polygonOffsetFactor&&(n.polygonOffsetFactor=this.polygonOffsetFactor),0!==this.polygonOffsetUnits&&(n.polygonOffsetUnits=this.polygonOffsetUnits),void 0!==this.linewidth&&1!==this.linewidth&&(n.linewidth=this.linewidth),void 0!==this.dashSize&&(n.dashSize=this.dashSize),void 0!==this.gapSize&&(n.gapSize=this.gapSize),void 0!==this.scale&&(n.scale=this.scale),!0===this.dithering&&(n.dithering=!0),this.alphaTest>0&&(n.alphaTest=this.alphaTest),!0===this.alphaToCoverage&&(n.alphaToCoverage=this.alphaToCoverage),!0===this.premultipliedAlpha&&(n.premultipliedAlpha=this.premultipliedAlpha),!0===this.forceSinglePass&&(n.forceSinglePass=this.forceSinglePass),!0===this.wireframe&&(n.wireframe=this.wireframe),this.wireframeLinewidth>1&&(n.wireframeLinewidth=this.wireframeLinewidth),"round"!==this.wireframeLinecap&&(n.wireframeLinecap=this.wireframeLinecap),"round"!==this.wireframeLinejoin&&(n.wireframeLinejoin=this.wireframeLinejoin),!0===this.flatShading&&(n.flatShading=this.flatShading),!1===this.visible&&(n.visible=!1),!1===this.toneMapped&&(n.toneMapped=!1),!1===this.fog&&(n.fog=!1),Object.keys(this.userData).length>0&&(n.userData=this.userData),e){const e=i(t.textures),r=i(t.images);e.length>0&&(n.textures=e),r.length>0&&(n.images=r)}return n}clone(){return(new this.constructor).copy(this)}copy(t){this.name=t.name,this.blending=t.blending,this.side=t.side,this.vertexColors=t.vertexColors,this.opacity=t.opacity,this.transparent=t.transparent,this.blendSrc=t.blendSrc,this.blendDst=t.blendDst,this.blendEquation=t.blendEquation,this.blendSrcAlpha=t.blendSrcAlpha,this.blendDstAlpha=t.blendDstAlpha,this.blendEquationAlpha=t.blendEquationAlpha,this.depthFunc=t.depthFunc,this.depthTest=t.depthTest,this.depthWrite=t.depthWrite,this.stencilWriteMask=t.stencilWriteMask,this.stencilFunc=t.stencilFunc,this.stencilRef=t.stencilRef,this.stencilFuncMask=t.stencilFuncMask,this.stencilFail=t.stencilFail,this.stencilZFail=t.stencilZFail,this.stencilZPass=t.stencilZPass,this.stencilWrite=t.stencilWrite;const e=t.clippingPlanes;let n=null;if(null!==e){const t=e.length;n=new Array(t);for(let i=0;i!==t;++i)n[i]=e[i].clone()}return this.clippingPlanes=n,this.clipIntersection=t.clipIntersection,this.clipShadows=t.clipShadows,this.shadowSide=t.shadowSide,this.colorWrite=t.colorWrite,this.precision=t.precision,this.polygonOffset=t.polygonOffset,this.polygonOffsetFactor=t.polygonOffsetFactor,this.polygonOffsetUnits=t.polygonOffsetUnits,this.dithering=t.dithering,this.alphaTest=t.alphaTest,this.alphaToCoverage=t.alphaToCoverage,this.premultipliedAlpha=t.premultipliedAlpha,this.forceSinglePass=t.forceSinglePass,this.visible=t.visible,this.toneMapped=t.toneMapped,this.userData=JSON.parse(JSON.stringify(t.userData)),this}dispose(){this.dispatchEvent({type:"dispose"})}set needsUpdate(t){!0===t&&this.version++}}const Pn={aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074},In={h:0,s:0,l:0},Un={h:0,s:0,l:0};function Dn(t,e,n){return n<0&&(n+=1),n>1&&(n-=1),n<1/6?t+6*(e-t)*n:n<.5?e:n<2/3?t+6*(e-t)*(2/3-n):t}class Nn{constructor(t,e,n){return this.isColor=!0,this.r=1,this.g=1,this.b=1,this.set(t,e,n)}set(t,e,n){if(void 0===e&&void 0===n){const e=t;e&&e.isColor?this.copy(e):"number"==typeof e?this.setHex(e):"string"==typeof e&&this.setStyle(e)}else this.setRGB(t,e,n);return this}setScalar(t){return this.r=t,this.g=t,this.b=t,this}setHex(t,e=_t){return t=Math.floor(t),this.r=(t>>16&255)/255,this.g=(t>>8&255)/255,this.b=(255&t)/255,ee.toWorkingColorSpace(this,e),this}setRGB(t,e,n,i=ee.workingColorSpace){return this.r=t,this.g=e,this.b=n,ee.toWorkingColorSpace(this,i),this}setHSL(t,e,n,i=ee.workingColorSpace){if(t=It(t,1),e=Pt(e,0,1),n=Pt(n,0,1),0===e)this.r=this.g=this.b=n;else{const i=n<=.5?n*(1+e):n+e-n*e,r=2*n-i;this.r=Dn(r,i,t+1/3),this.g=Dn(r,i,t),this.b=Dn(r,i,t-1/3)}return ee.toWorkingColorSpace(this,i),this}setStyle(t,e=_t){function n(e){void 0!==e&&parseFloat(e)<1&&console.warn("THREE.Color: Alpha component of "+t+" will be ignored.")}let i;if(i=/^(\w+)\(([^\)]*)\)/.exec(t)){let r;const s=i[1],a=i[2];switch(s){case"rgb":case"rgba":if(r=/^\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(a))return n(r[4]),this.setRGB(Math.min(255,parseInt(r[1],10))/255,Math.min(255,parseInt(r[2],10))/255,Math.min(255,parseInt(r[3],10))/255,e);if(r=/^\s*(\d+)\%\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(a))return n(r[4]),this.setRGB(Math.min(100,parseInt(r[1],10))/100,Math.min(100,parseInt(r[2],10))/100,Math.min(100,parseInt(r[3],10))/100,e);break;case"hsl":case"hsla":if(r=/^\s*(\d*\.?\d+)\s*,\s*(\d*\.?\d+)\%\s*,\s*(\d*\.?\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(a))return n(r[4]),this.setHSL(parseFloat(r[1])/360,parseFloat(r[2])/100,parseFloat(r[3])/100,e);break;default:console.warn("THREE.Color: Unknown color model "+t)}}else if(i=/^\#([A-Fa-f\d]+)$/.exec(t)){const n=i[1],r=n.length;if(3===r)return this.setRGB(parseInt(n.charAt(0),16)/15,parseInt(n.charAt(1),16)/15,parseInt(n.charAt(2),16)/15,e);if(6===r)return this.setHex(parseInt(n,16),e);console.warn("THREE.Color: Invalid hex color "+t)}else if(t&&t.length>0)return this.setColorName(t,e);return this}setColorName(t,e=_t){const n=Pn[t.toLowerCase()];return void 0!==n?this.setHex(n,e):console.warn("THREE.Color: Unknown color "+t),this}clone(){return new this.constructor(this.r,this.g,this.b)}copy(t){return this.r=t.r,this.g=t.g,this.b=t.b,this}copySRGBToLinear(t){return this.r=Zt(t.r),this.g=Zt(t.g),this.b=Zt(t.b),this}copyLinearToSRGB(t){return this.r=Jt(t.r),this.g=Jt(t.g),this.b=Jt(t.b),this}convertSRGBToLinear(){return this.copySRGBToLinear(this),this}convertLinearToSRGB(){return this.copyLinearToSRGB(this),this}getHex(t=_t){return ee.fromWorkingColorSpace(On.copy(this),t),65536*Math.round(Pt(255*On.r,0,255))+256*Math.round(Pt(255*On.g,0,255))+Math.round(Pt(255*On.b,0,255))}getHexString(t=_t){return("000000"+this.getHex(t).toString(16)).slice(-6)}getHSL(t,e=ee.workingColorSpace){ee.fromWorkingColorSpace(On.copy(this),e);const n=On.r,i=On.g,r=On.b,s=Math.max(n,i,r),a=Math.min(n,i,r);let o,l;const c=(a+s)/2;if(a===s)o=0,l=0;else{const t=s-a;switch(l=c<=.5?t/(s+a):t/(2-s-a),s){case n:o=(i-r)/t+(i>-e-14,i[256|t]=1024>>-e-14|32768,r[t]=-e-1,r[256|t]=-e-1):e<=15?(i[t]=e+15<<10,i[256|t]=e+15<<10|32768,r[t]=13,r[256|t]=13):e<128?(i[t]=31744,i[256|t]=64512,r[t]=24,r[256|t]=24):(i[t]=31744,i[256|t]=64512,r[t]=13,r[256|t]=13)}const s=new Uint32Array(2048),a=new Uint32Array(64),o=new Uint32Array(64);for(let t=1;t<1024;++t){let e=t<<13,n=0;for(;0==(8388608&e);)e<<=1,n-=8388608;e&=-8388609,n+=947912704,s[t]=e|n}for(let t=1024;t<2048;++t)s[t]=939524096+(t-1024<<13);for(let t=1;t<31;++t)a[t]=t<<23;a[31]=1199570944,a[32]=2147483648;for(let t=33;t<63;++t)a[t]=2147483648+(t-32<<23);a[63]=3347054592;for(let t=1;t<64;++t)32!==t&&(o[t]=1024);return{floatView:e,uint32View:n,baseTable:i,shiftTable:r,mantissaTable:s,exponentTable:a,offsetTable:o}}function Gn(t){Math.abs(t)>65504&&console.warn("THREE.DataUtils.toHalfFloat(): Value out of range."),t=Pt(t,-65504,65504),Bn.floatView[0]=t;const e=Bn.uint32View[0],n=e>>23&511;return Bn.baseTable[n]+((8388607&e)>>Bn.shiftTable[n])}function Hn(t){const e=t>>10;return Bn.uint32View[0]=Bn.mantissaTable[Bn.offsetTable[e]+(1023&t)]+Bn.exponentTable[e],Bn.floatView[0]}const kn={toHalfFloat:Gn,fromHalfFloat:Hn},Vn=new me,Wn=new Gt;class Xn{constructor(t,e,n=!1){if(Array.isArray(t))throw new TypeError("THREE.BufferAttribute: array should be a Typed Array.");this.isBufferAttribute=!0,this.name="",this.array=t,this.itemSize=e,this.count=void 0!==t?t.length/e:0,this.normalized=n,this.usage=St,this.updateRange={offset:0,count:-1},this.version=0}onUploadCallback(){}set needsUpdate(t){!0===t&&this.version++}setUsage(t){return this.usage=t,this}copy(t){return this.name=t.name,this.array=new t.array.constructor(t.array),this.itemSize=t.itemSize,this.count=t.count,this.normalized=t.normalized,this.usage=t.usage,this}copyAt(t,e,n){t*=this.itemSize,n*=e.itemSize;for(let i=0,r=this.itemSize;i0&&(t.userData=this.userData),void 0!==this.parameters){const e=this.parameters;for(const n in e)void 0!==e[n]&&(t[n]=e[n]);return t}t.data={attributes:{}};const e=this.index;null!==e&&(t.data.index={type:e.array.constructor.name,array:Array.prototype.slice.call(e.array)});const n=this.attributes;for(const e in n){const i=n[e];t.data.attributes[e]=i.toJSON(t.data)}const i={};let r=!1;for(const e in this.morphAttributes){const n=this.morphAttributes[e],s=[];for(let e=0,i=n.length;e0&&(i[e]=s,r=!0)}r&&(t.data.morphAttributes=i,t.data.morphTargetsRelative=this.morphTargetsRelative);const s=this.groups;s.length>0&&(t.data.groups=JSON.parse(JSON.stringify(s)));const a=this.boundingSphere;return null!==a&&(t.data.boundingSphere={center:a.center.toArray(),radius:a.radius}),t}clone(){return(new this.constructor).copy(this)}copy(t){this.index=null,this.attributes={},this.morphAttributes={},this.groups=[],this.boundingBox=null,this.boundingSphere=null;const e={};this.name=t.name;const n=t.index;null!==n&&this.setIndex(n.clone(e));const i=t.attributes;for(const t in i){const n=i[t];this.setAttribute(t,n.clone(e))}const r=t.morphAttributes;for(const t in r){const n=[],i=r[t];for(let t=0,r=i.length;t0){const n=t[e[0]];if(void 0!==n){this.morphTargetInfluences=[],this.morphTargetDictionary={};for(let t=0,e=n.length;t(t.far-t.near)**2)return}ii.copy(r).invert(),ri.copy(t.ray).applyMatrix4(ii),null!==n.boundingBox&&!1===ri.intersectsBox(n.boundingBox)||this._computeIntersections(t,e,ri)}}_computeIntersections(t,e,n){let i;const r=this.geometry,s=this.material,a=r.index,o=r.attributes.position,l=r.attributes.uv,c=r.attributes.uv1,h=r.attributes.normal,u=r.groups,d=r.drawRange;if(null!==a)if(Array.isArray(s))for(let r=0,o=u.length;rn.far?null:{distance:c,point:yi.clone(),object:t}}(t,e,n,i,oi,li,ci,_i);if(h){r&&(di.fromBufferAttribute(r,o),pi.fromBufferAttribute(r,l),mi.fromBufferAttribute(r,c),h.uv=Rn.getInterpolation(_i,oi,li,ci,di,pi,mi,new Gt)),s&&(di.fromBufferAttribute(s,o),pi.fromBufferAttribute(s,l),mi.fromBufferAttribute(s,c),h.uv1=Rn.getInterpolation(_i,oi,li,ci,di,pi,mi,new Gt),h.uv2=h.uv1),a&&(fi.fromBufferAttribute(a,o),gi.fromBufferAttribute(a,l),vi.fromBufferAttribute(a,c),h.normal=Rn.getInterpolation(_i,oi,li,ci,fi,gi,vi,new me),h.normal.dot(i.direction)>0&&h.normal.multiplyScalar(-1));const t={a:o,b:l,c:c,normal:new me,materialIndex:0};Rn.getNormal(oi,li,ci,t.normal),h.face=t}return h}class Si extends ni{constructor(t=1,e=1,n=1,i=1,r=1,s=1){super(),this.type="BoxGeometry",this.parameters={width:t,height:e,depth:n,widthSegments:i,heightSegments:r,depthSegments:s};const a=this;i=Math.floor(i),r=Math.floor(r),s=Math.floor(s);const o=[],l=[],c=[],h=[];let u=0,d=0;function p(t,e,n,i,r,s,p,m,f,g,v){const _=s/f,y=p/g,x=s/2,M=p/2,S=m/2,b=f+1,T=g+1;let E=0,w=0;const A=new me;for(let s=0;s0?1:-1,c.push(A.x,A.y,A.z),h.push(o/f),h.push(1-s/g),E+=1}}for(let t=0;t0&&(e.defines=this.defines),e.vertexShader=this.vertexShader,e.fragmentShader=this.fragmentShader,e.lights=this.lights,e.clipping=this.clipping;const n={};for(const t in this.extensions)!0===this.extensions[t]&&(n[t]=!0);return Object.keys(n).length>0&&(e.extensions=n),e}}class Ri extends gn{constructor(){super(),this.isCamera=!0,this.type="Camera",this.matrixWorldInverse=new We,this.projectionMatrix=new We,this.projectionMatrixInverse=new We}copy(t,e){return super.copy(t,e),this.matrixWorldInverse.copy(t.matrixWorldInverse),this.projectionMatrix.copy(t.projectionMatrix),this.projectionMatrixInverse.copy(t.projectionMatrixInverse),this}getWorldDirection(t){this.updateWorldMatrix(!0,!1);const e=this.matrixWorld.elements;return t.set(-e[8],-e[9],-e[10]).normalize()}updateMatrixWorld(t){super.updateMatrixWorld(t),this.matrixWorldInverse.copy(this.matrixWorld).invert()}updateWorldMatrix(t,e){super.updateWorldMatrix(t,e),this.matrixWorldInverse.copy(this.matrixWorld).invert()}clone(){return(new this.constructor).copy(this)}}class Ci extends Ri{constructor(t=50,e=1,n=.1,i=2e3){super(),this.isPerspectiveCamera=!0,this.type="PerspectiveCamera",this.fov=t,this.zoom=1,this.near=n,this.far=i,this.focus=10,this.aspect=e,this.view=null,this.filmGauge=35,this.filmOffset=0,this.updateProjectionMatrix()}copy(t,e){return super.copy(t,e),this.fov=t.fov,this.zoom=t.zoom,this.near=t.near,this.far=t.far,this.focus=t.focus,this.aspect=t.aspect,this.view=null===t.view?null:Object.assign({},t.view),this.filmGauge=t.filmGauge,this.filmOffset=t.filmOffset,this}setFocalLength(t){const e=.5*this.getFilmHeight()/t;this.fov=2*Ct*Math.atan(e),this.updateProjectionMatrix()}getFocalLength(){const t=Math.tan(.5*Rt*this.fov);return.5*this.getFilmHeight()/t}getEffectiveFOV(){return 2*Ct*Math.atan(Math.tan(.5*Rt*this.fov)/this.zoom)}getFilmWidth(){return this.filmGauge*Math.min(this.aspect,1)}getFilmHeight(){return this.filmGauge/Math.max(this.aspect,1)}setViewOffset(t,e,n,i,r,s){this.aspect=t/e,null===this.view&&(this.view={enabled:!0,fullWidth:1,fullHeight:1,offsetX:0,offsetY:0,width:1,height:1}),this.view.enabled=!0,this.view.fullWidth=t,this.view.fullHeight=e,this.view.offsetX=n,this.view.offsetY=i,this.view.width=r,this.view.height=s,this.updateProjectionMatrix()}clearViewOffset(){null!==this.view&&(this.view.enabled=!1),this.updateProjectionMatrix()}updateProjectionMatrix(){const t=this.near;let e=t*Math.tan(.5*Rt*this.fov)/this.zoom,n=2*e,i=this.aspect*n,r=-.5*i;const s=this.view;if(null!==this.view&&this.view.enabled){const t=s.fullWidth,a=s.fullHeight;r+=s.offsetX*i/t,e-=s.offsetY*n/a,i*=s.width/t,n*=s.height/a}const a=this.filmOffset;0!==a&&(r+=t*a/this.getFilmWidth()),this.projectionMatrix.makePerspective(r,r+i,e,e-n,t,this.far),this.projectionMatrixInverse.copy(this.projectionMatrix).invert()}toJSON(t){const e=super.toJSON(t);return e.object.fov=this.fov,e.object.zoom=this.zoom,e.object.near=this.near,e.object.far=this.far,e.object.focus=this.focus,e.object.aspect=this.aspect,null!==this.view&&(e.object.view=Object.assign({},this.view)),e.object.filmGauge=this.filmGauge,e.object.filmOffset=this.filmOffset,e}}const Li=-90;class Pi extends gn{constructor(t,e,n){super(),this.type="CubeCamera",this.renderTarget=n;const i=new Ci(Li,1,t,e);i.layers=this.layers,i.up.set(0,1,0),i.lookAt(1,0,0),this.add(i);const r=new Ci(Li,1,t,e);r.layers=this.layers,r.up.set(0,1,0),r.lookAt(-1,0,0),this.add(r);const s=new Ci(Li,1,t,e);s.layers=this.layers,s.up.set(0,0,-1),s.lookAt(0,1,0),this.add(s);const a=new Ci(Li,1,t,e);a.layers=this.layers,a.up.set(0,0,1),a.lookAt(0,-1,0),this.add(a);const o=new Ci(Li,1,t,e);o.layers=this.layers,o.up.set(0,1,0),o.lookAt(0,0,1),this.add(o);const l=new Ci(Li,1,t,e);l.layers=this.layers,l.up.set(0,1,0),l.lookAt(0,0,-1),this.add(l)}update(t,e){null===this.parent&&this.updateMatrixWorld();const n=this.renderTarget,[i,r,s,a,o,l]=this.children,c=t.getRenderTarget(),h=t.toneMapping,u=t.xr.enabled;t.toneMapping=0,t.xr.enabled=!1;const d=n.texture.generateMipmaps;n.texture.generateMipmaps=!1,t.setRenderTarget(n,0),t.render(e,i),t.setRenderTarget(n,1),t.render(e,r),t.setRenderTarget(n,2),t.render(e,s),t.setRenderTarget(n,3),t.render(e,a),t.setRenderTarget(n,4),t.render(e,o),n.texture.generateMipmaps=d,t.setRenderTarget(n,5),t.render(e,l),t.setRenderTarget(c),t.toneMapping=h,t.xr.enabled=u,n.texture.needsPMREMUpdate=!0}}class Ii extends le{constructor(t,e,n,i,s,a,o,l,c,h){super(t=void 0!==t?t:[],e=void 0!==e?e:r,n,i,s,a,o,l,c,h),this.isCubeTexture=!0,this.flipY=!1}get images(){return this.image}set images(t){this.image=t}}class Ui extends he{constructor(t=1,e={}){super(t,t,e),this.isWebGLCubeRenderTarget=!0;const n={width:t,height:t,depth:1},i=[n,n,n,n,n,n];void 0!==e.encoding&&(Yt("THREE.WebGLCubeRenderTarget: option.encoding has been replaced by option.colorSpace."),e.colorSpace=e.encoding===gt?_t:vt),this.texture=new Ii(i,e.mapping,e.wrapS,e.wrapT,e.magFilter,e.minFilter,e.format,e.type,e.anisotropy,e.colorSpace),this.texture.isRenderTargetTexture=!0,this.texture.generateMipmaps=void 0!==e.generateMipmaps&&e.generateMipmaps,this.texture.minFilter=void 0!==e.minFilter?e.minFilter:f}fromEquirectangularTexture(t,e){this.texture.type=e.type,this.texture.colorSpace=e.colorSpace,this.texture.generateMipmaps=e.generateMipmaps,this.texture.minFilter=e.minFilter,this.texture.magFilter=e.magFilter;const n={uniforms:{tEquirect:{value:null}},vertexShader:"\n\n\t\t\t\tvarying vec3 vWorldDirection;\n\n\t\t\t\tvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n\n\t\t\t\t\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n\n\t\t\t\t}\n\n\t\t\t\tvoid main() {\n\n\t\t\t\t\tvWorldDirection = transformDirection( position, modelMatrix );\n\n\t\t\t\t\t#include \n\t\t\t\t\t#include \n\n\t\t\t\t}\n\t\t\t",fragmentShader:"\n\n\t\t\t\tuniform sampler2D tEquirect;\n\n\t\t\t\tvarying vec3 vWorldDirection;\n\n\t\t\t\t#include \n\n\t\t\t\tvoid main() {\n\n\t\t\t\t\tvec3 direction = normalize( vWorldDirection );\n\n\t\t\t\t\tvec2 sampleUV = equirectUv( direction );\n\n\t\t\t\t\tgl_FragColor = texture2D( tEquirect, sampleUV );\n\n\t\t\t\t}\n\t\t\t"},i=new Si(5,5,5),r=new Ai({name:"CubemapFromEquirect",uniforms:bi(n.uniforms),vertexShader:n.vertexShader,fragmentShader:n.fragmentShader,side:1,blending:0});r.uniforms.tEquirect.value=e;const s=new xi(i,r),a=e.minFilter;e.minFilter===v&&(e.minFilter=f);return new Pi(1,10,this).update(t,s),e.minFilter=a,s.geometry.dispose(),s.material.dispose(),this}clear(t,e,n,i){const r=t.getRenderTarget();for(let r=0;r<6;r++)t.setRenderTarget(this,r),t.clear(e,n,i);t.setRenderTarget(r)}}const Di=new me,Ni=new me,Oi=new Ht;class Fi{constructor(t=new me(1,0,0),e=0){this.isPlane=!0,this.normal=t,this.constant=e}set(t,e){return this.normal.copy(t),this.constant=e,this}setComponents(t,e,n,i){return this.normal.set(t,e,n),this.constant=i,this}setFromNormalAndCoplanarPoint(t,e){return this.normal.copy(t),this.constant=-e.dot(this.normal),this}setFromCoplanarPoints(t,e,n){const i=Di.subVectors(n,e).cross(Ni.subVectors(t,e)).normalize();return this.setFromNormalAndCoplanarPoint(i,t),this}copy(t){return this.normal.copy(t.normal),this.constant=t.constant,this}normalize(){const t=1/this.normal.length();return this.normal.multiplyScalar(t),this.constant*=t,this}negate(){return this.constant*=-1,this.normal.negate(),this}distanceToPoint(t){return this.normal.dot(t)+this.constant}distanceToSphere(t){return this.distanceToPoint(t.center)-t.radius}projectPoint(t,e){return e.copy(t).addScaledVector(this.normal,-this.distanceToPoint(t))}intersectLine(t,e){const n=t.delta(Di),i=this.normal.dot(n);if(0===i)return 0===this.distanceToPoint(t.start)?e.copy(t.start):null;const r=-(t.start.dot(this.normal)+this.constant)/i;return r<0||r>1?null:e.copy(t.start).addScaledVector(n,r)}intersectsLine(t){const e=this.distanceToPoint(t.start),n=this.distanceToPoint(t.end);return e<0&&n>0||n<0&&e>0}intersectsBox(t){return t.intersectsPlane(this)}intersectsSphere(t){return t.intersectsPlane(this)}coplanarPoint(t){return t.copy(this.normal).multiplyScalar(-this.constant)}applyMatrix4(t,e){const n=e||Oi.getNormalMatrix(t),i=this.coplanarPoint(Di).applyMatrix4(t),r=this.normal.applyMatrix3(n).normalize();return this.constant=-i.dot(r),this}translate(t){return this.constant-=t.dot(this.normal),this}equals(t){return t.normal.equals(this.normal)&&t.constant===this.constant}clone(){return(new this.constructor).copy(this)}}const Bi=new Ne,zi=new me;class Gi{constructor(t=new Fi,e=new Fi,n=new Fi,i=new Fi,r=new Fi,s=new Fi){this.planes=[t,e,n,i,r,s]}set(t,e,n,i,r,s){const a=this.planes;return a[0].copy(t),a[1].copy(e),a[2].copy(n),a[3].copy(i),a[4].copy(r),a[5].copy(s),this}copy(t){const e=this.planes;for(let n=0;n<6;n++)e[n].copy(t.planes[n]);return this}setFromProjectionMatrix(t){const e=this.planes,n=t.elements,i=n[0],r=n[1],s=n[2],a=n[3],o=n[4],l=n[5],c=n[6],h=n[7],u=n[8],d=n[9],p=n[10],m=n[11],f=n[12],g=n[13],v=n[14],_=n[15];return e[0].setComponents(a-i,h-o,m-u,_-f).normalize(),e[1].setComponents(a+i,h+o,m+u,_+f).normalize(),e[2].setComponents(a+r,h+l,m+d,_+g).normalize(),e[3].setComponents(a-r,h-l,m-d,_-g).normalize(),e[4].setComponents(a-s,h-c,m-p,_-v).normalize(),e[5].setComponents(a+s,h+c,m+p,_+v).normalize(),this}intersectsObject(t){if(void 0!==t.boundingSphere)null===t.boundingSphere&&t.computeBoundingSphere(),Bi.copy(t.boundingSphere).applyMatrix4(t.matrixWorld);else{const e=t.geometry;null===e.boundingSphere&&e.computeBoundingSphere(),Bi.copy(e.boundingSphere).applyMatrix4(t.matrixWorld)}return this.intersectsSphere(Bi)}intersectsSprite(t){return Bi.center.set(0,0,0),Bi.radius=.7071067811865476,Bi.applyMatrix4(t.matrixWorld),this.intersectsSphere(Bi)}intersectsSphere(t){const e=this.planes,n=t.center,i=-t.radius;for(let t=0;t<6;t++){if(e[t].distanceToPoint(n)0?t.max.x:t.min.x,zi.y=i.normal.y>0?t.max.y:t.min.y,zi.z=i.normal.z>0?t.max.z:t.min.z,i.distanceToPoint(zi)<0)return!1}return!0}containsPoint(t){const e=this.planes;for(let n=0;n<6;n++)if(e[n].distanceToPoint(t)<0)return!1;return!0}clone(){return(new this.constructor).copy(this)}}function Hi(){let t=null,e=!1,n=null,i=null;function r(e,s){n(e,s),i=t.requestAnimationFrame(r)}return{start:function(){!0!==e&&null!==n&&(i=t.requestAnimationFrame(r),e=!0)},stop:function(){t.cancelAnimationFrame(i),e=!1},setAnimationLoop:function(t){n=t},setContext:function(e){t=e}}}function ki(t,e){const n=e.isWebGL2,i=new WeakMap;return{get:function(t){return t.isInterleavedBufferAttribute&&(t=t.data),i.get(t)},remove:function(e){e.isInterleavedBufferAttribute&&(e=e.data);const n=i.get(e);n&&(t.deleteBuffer(n.buffer),i.delete(e))},update:function(e,r){if(e.isGLBufferAttribute){const t=i.get(e);return void((!t||t.version 0\n\tvec4 plane;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) {\n\t\tplane = clippingPlanes[ i ];\n\t\tif ( dot( vClipPosition, plane.xyz ) > plane.w ) discard;\n\t}\n\t#pragma unroll_loop_end\n\t#if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES\n\t\tbool clipped = true;\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) {\n\t\t\tplane = clippingPlanes[ i ];\n\t\t\tclipped = ( dot( vClipPosition, plane.xyz ) > plane.w ) && clipped;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t\tif ( clipped ) discard;\n\t#endif\n#endif",clipping_planes_pars_fragment:"#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n\tuniform vec4 clippingPlanes[ NUM_CLIPPING_PLANES ];\n#endif",clipping_planes_pars_vertex:"#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n#endif",clipping_planes_vertex:"#if NUM_CLIPPING_PLANES > 0\n\tvClipPosition = - mvPosition.xyz;\n#endif",color_fragment:"#if defined( USE_COLOR_ALPHA )\n\tdiffuseColor *= vColor;\n#elif defined( USE_COLOR )\n\tdiffuseColor.rgb *= vColor;\n#endif",color_pars_fragment:"#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR )\n\tvarying vec3 vColor;\n#endif",color_pars_vertex:"#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR )\n\tvarying vec3 vColor;\n#endif",color_vertex:"#if defined( USE_COLOR_ALPHA )\n\tvColor = vec4( 1.0 );\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR )\n\tvColor = vec3( 1.0 );\n#endif\n#ifdef USE_COLOR\n\tvColor *= color;\n#endif\n#ifdef USE_INSTANCING_COLOR\n\tvColor.xyz *= instanceColor.xyz;\n#endif",common:"#define PI 3.141592653589793\n#define PI2 6.283185307179586\n#define PI_HALF 1.5707963267948966\n#define RECIPROCAL_PI 0.3183098861837907\n#define RECIPROCAL_PI2 0.15915494309189535\n#define EPSILON 1e-6\n#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\n#define whiteComplement( a ) ( 1.0 - saturate( a ) )\nfloat pow2( const in float x ) { return x*x; }\nvec3 pow2( const in vec3 x ) { return x*x; }\nfloat pow3( const in float x ) { return x*x*x; }\nfloat pow4( const in float x ) { float x2 = x*x; return x2*x2; }\nfloat max3( const in vec3 v ) { return max( max( v.x, v.y ), v.z ); }\nfloat average( const in vec3 v ) { return dot( v, vec3( 0.3333333 ) ); }\nhighp float rand( const in vec2 uv ) {\n\tconst highp float a = 12.9898, b = 78.233, c = 43758.5453;\n\thighp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI );\n\treturn fract( sin( sn ) * c );\n}\n#ifdef HIGH_PRECISION\n\tfloat precisionSafeLength( vec3 v ) { return length( v ); }\n#else\n\tfloat precisionSafeLength( vec3 v ) {\n\t\tfloat maxComponent = max3( abs( v ) );\n\t\treturn length( v / maxComponent ) * maxComponent;\n\t}\n#endif\nstruct IncidentLight {\n\tvec3 color;\n\tvec3 direction;\n\tbool visible;\n};\nstruct ReflectedLight {\n\tvec3 directDiffuse;\n\tvec3 directSpecular;\n\tvec3 indirectDiffuse;\n\tvec3 indirectSpecular;\n};\nstruct GeometricContext {\n\tvec3 position;\n\tvec3 normal;\n\tvec3 viewDir;\n#ifdef USE_CLEARCOAT\n\tvec3 clearcoatNormal;\n#endif\n};\nvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n}\nvec3 inverseTransformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( vec4( dir, 0.0 ) * matrix ).xyz );\n}\nmat3 transposeMat3( const in mat3 m ) {\n\tmat3 tmp;\n\ttmp[ 0 ] = vec3( m[ 0 ].x, m[ 1 ].x, m[ 2 ].x );\n\ttmp[ 1 ] = vec3( m[ 0 ].y, m[ 1 ].y, m[ 2 ].y );\n\ttmp[ 2 ] = vec3( m[ 0 ].z, m[ 1 ].z, m[ 2 ].z );\n\treturn tmp;\n}\nfloat luminance( const in vec3 rgb ) {\n\tconst vec3 weights = vec3( 0.2126729, 0.7151522, 0.0721750 );\n\treturn dot( weights, rgb );\n}\nbool isPerspectiveMatrix( mat4 m ) {\n\treturn m[ 2 ][ 3 ] == - 1.0;\n}\nvec2 equirectUv( in vec3 dir ) {\n\tfloat u = atan( dir.z, dir.x ) * RECIPROCAL_PI2 + 0.5;\n\tfloat v = asin( clamp( dir.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\n\treturn vec2( u, v );\n}\nvec3 BRDF_Lambert( const in vec3 diffuseColor ) {\n\treturn RECIPROCAL_PI * diffuseColor;\n}\nvec3 F_Schlick( const in vec3 f0, const in float f90, const in float dotVH ) {\n\tfloat fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );\n\treturn f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );\n}\nfloat F_Schlick( const in float f0, const in float f90, const in float dotVH ) {\n\tfloat fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );\n\treturn f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );\n} // validated",cube_uv_reflection_fragment:"#ifdef ENVMAP_TYPE_CUBE_UV\n\t#define cubeUV_minMipLevel 4.0\n\t#define cubeUV_minTileSize 16.0\n\tfloat getFace( vec3 direction ) {\n\t\tvec3 absDirection = abs( direction );\n\t\tfloat face = - 1.0;\n\t\tif ( absDirection.x > absDirection.z ) {\n\t\t\tif ( absDirection.x > absDirection.y )\n\t\t\t\tface = direction.x > 0.0 ? 0.0 : 3.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t} else {\n\t\t\tif ( absDirection.z > absDirection.y )\n\t\t\t\tface = direction.z > 0.0 ? 2.0 : 5.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t}\n\t\treturn face;\n\t}\n\tvec2 getUV( vec3 direction, float face ) {\n\t\tvec2 uv;\n\t\tif ( face == 0.0 ) {\n\t\t\tuv = vec2( direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 1.0 ) {\n\t\t\tuv = vec2( - direction.x, - direction.z ) / abs( direction.y );\n\t\t} else if ( face == 2.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.y ) / abs( direction.z );\n\t\t} else if ( face == 3.0 ) {\n\t\t\tuv = vec2( - direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 4.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.z ) / abs( direction.y );\n\t\t} else {\n\t\t\tuv = vec2( direction.x, direction.y ) / abs( direction.z );\n\t\t}\n\t\treturn 0.5 * ( uv + 1.0 );\n\t}\n\tvec3 bilinearCubeUV( sampler2D envMap, vec3 direction, float mipInt ) {\n\t\tfloat face = getFace( direction );\n\t\tfloat filterInt = max( cubeUV_minMipLevel - mipInt, 0.0 );\n\t\tmipInt = max( mipInt, cubeUV_minMipLevel );\n\t\tfloat faceSize = exp2( mipInt );\n\t\thighp vec2 uv = getUV( direction, face ) * ( faceSize - 2.0 ) + 1.0;\n\t\tif ( face > 2.0 ) {\n\t\t\tuv.y += faceSize;\n\t\t\tface -= 3.0;\n\t\t}\n\t\tuv.x += face * faceSize;\n\t\tuv.x += filterInt * 3.0 * cubeUV_minTileSize;\n\t\tuv.y += 4.0 * ( exp2( CUBEUV_MAX_MIP ) - faceSize );\n\t\tuv.x *= CUBEUV_TEXEL_WIDTH;\n\t\tuv.y *= CUBEUV_TEXEL_HEIGHT;\n\t\t#ifdef texture2DGradEXT\n\t\t\treturn texture2DGradEXT( envMap, uv, vec2( 0.0 ), vec2( 0.0 ) ).rgb;\n\t\t#else\n\t\t\treturn texture2D( envMap, uv ).rgb;\n\t\t#endif\n\t}\n\t#define cubeUV_r0 1.0\n\t#define cubeUV_v0 0.339\n\t#define cubeUV_m0 - 2.0\n\t#define cubeUV_r1 0.8\n\t#define cubeUV_v1 0.276\n\t#define cubeUV_m1 - 1.0\n\t#define cubeUV_r4 0.4\n\t#define cubeUV_v4 0.046\n\t#define cubeUV_m4 2.0\n\t#define cubeUV_r5 0.305\n\t#define cubeUV_v5 0.016\n\t#define cubeUV_m5 3.0\n\t#define cubeUV_r6 0.21\n\t#define cubeUV_v6 0.0038\n\t#define cubeUV_m6 4.0\n\tfloat roughnessToMip( float roughness ) {\n\t\tfloat mip = 0.0;\n\t\tif ( roughness >= cubeUV_r1 ) {\n\t\t\tmip = ( cubeUV_r0 - roughness ) * ( cubeUV_m1 - cubeUV_m0 ) / ( cubeUV_r0 - cubeUV_r1 ) + cubeUV_m0;\n\t\t} else if ( roughness >= cubeUV_r4 ) {\n\t\t\tmip = ( cubeUV_r1 - roughness ) * ( cubeUV_m4 - cubeUV_m1 ) / ( cubeUV_r1 - cubeUV_r4 ) + cubeUV_m1;\n\t\t} else if ( roughness >= cubeUV_r5 ) {\n\t\t\tmip = ( cubeUV_r4 - roughness ) * ( cubeUV_m5 - cubeUV_m4 ) / ( cubeUV_r4 - cubeUV_r5 ) + cubeUV_m4;\n\t\t} else if ( roughness >= cubeUV_r6 ) {\n\t\t\tmip = ( cubeUV_r5 - roughness ) * ( cubeUV_m6 - cubeUV_m5 ) / ( cubeUV_r5 - cubeUV_r6 ) + cubeUV_m5;\n\t\t} else {\n\t\t\tmip = - 2.0 * log2( 1.16 * roughness );\t\t}\n\t\treturn mip;\n\t}\n\tvec4 textureCubeUV( sampler2D envMap, vec3 sampleDir, float roughness ) {\n\t\tfloat mip = clamp( roughnessToMip( roughness ), cubeUV_m0, CUBEUV_MAX_MIP );\n\t\tfloat mipF = fract( mip );\n\t\tfloat mipInt = floor( mip );\n\t\tvec3 color0 = bilinearCubeUV( envMap, sampleDir, mipInt );\n\t\tif ( mipF == 0.0 ) {\n\t\t\treturn vec4( color0, 1.0 );\n\t\t} else {\n\t\t\tvec3 color1 = bilinearCubeUV( envMap, sampleDir, mipInt + 1.0 );\n\t\t\treturn vec4( mix( color0, color1, mipF ), 1.0 );\n\t\t}\n\t}\n#endif",defaultnormal_vertex:"vec3 transformedNormal = objectNormal;\n#ifdef USE_INSTANCING\n\tmat3 m = mat3( instanceMatrix );\n\ttransformedNormal /= vec3( dot( m[ 0 ], m[ 0 ] ), dot( m[ 1 ], m[ 1 ] ), dot( m[ 2 ], m[ 2 ] ) );\n\ttransformedNormal = m * transformedNormal;\n#endif\ntransformedNormal = normalMatrix * transformedNormal;\n#ifdef FLIP_SIDED\n\ttransformedNormal = - transformedNormal;\n#endif\n#ifdef USE_TANGENT\n\tvec3 transformedTangent = ( modelViewMatrix * vec4( objectTangent, 0.0 ) ).xyz;\n\t#ifdef FLIP_SIDED\n\t\ttransformedTangent = - transformedTangent;\n\t#endif\n#endif",displacementmap_pars_vertex:"#ifdef USE_DISPLACEMENTMAP\n\tuniform sampler2D displacementMap;\n\tuniform float displacementScale;\n\tuniform float displacementBias;\n#endif",displacementmap_vertex:"#ifdef USE_DISPLACEMENTMAP\n\ttransformed += normalize( objectNormal ) * ( texture2D( displacementMap, vDisplacementMapUv ).x * displacementScale + displacementBias );\n#endif",emissivemap_fragment:"#ifdef USE_EMISSIVEMAP\n\tvec4 emissiveColor = texture2D( emissiveMap, vEmissiveMapUv );\n\ttotalEmissiveRadiance *= emissiveColor.rgb;\n#endif",emissivemap_pars_fragment:"#ifdef USE_EMISSIVEMAP\n\tuniform sampler2D emissiveMap;\n#endif",encodings_fragment:"gl_FragColor = linearToOutputTexel( gl_FragColor );",encodings_pars_fragment:"vec4 LinearToLinear( in vec4 value ) {\n\treturn value;\n}\nvec4 LinearTosRGB( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a );\n}",envmap_fragment:"#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvec3 cameraToFrag;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToFrag = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToFrag = normalize( vWorldPosition - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvec3 reflectVec = reflect( cameraToFrag, worldNormal );\n\t\t#else\n\t\t\tvec3 reflectVec = refract( cameraToFrag, worldNormal, refractionRatio );\n\t\t#endif\n\t#else\n\t\tvec3 reflectVec = vReflect;\n\t#endif\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 envColor = textureCube( envMap, vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );\n\t#else\n\t\tvec4 envColor = vec4( 0.0 );\n\t#endif\n\t#ifdef ENVMAP_BLENDING_MULTIPLY\n\t\toutgoingLight = mix( outgoingLight, outgoingLight * envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_MIX )\n\t\toutgoingLight = mix( outgoingLight, envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_ADD )\n\t\toutgoingLight += envColor.xyz * specularStrength * reflectivity;\n\t#endif\n#endif",envmap_common_pars_fragment:"#ifdef USE_ENVMAP\n\tuniform float envMapIntensity;\n\tuniform float flipEnvMap;\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tuniform samplerCube envMap;\n\t#else\n\t\tuniform sampler2D envMap;\n\t#endif\n\t\n#endif",envmap_pars_fragment:"#ifdef USE_ENVMAP\n\tuniform float reflectivity;\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( LAMBERT )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\tvarying vec3 vWorldPosition;\n\t\tuniform float refractionRatio;\n\t#else\n\t\tvarying vec3 vReflect;\n\t#endif\n#endif",envmap_pars_vertex:"#ifdef USE_ENVMAP\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( LAMBERT )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\t\n\t\tvarying vec3 vWorldPosition;\n\t#else\n\t\tvarying vec3 vReflect;\n\t\tuniform float refractionRatio;\n\t#endif\n#endif",envmap_physical_pars_fragment:"#ifdef USE_ENVMAP\n\tvec3 getIBLIrradiance( const in vec3 normal ) {\n\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, worldNormal, 1.0 );\n\t\t\treturn PI * envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n\tvec3 getIBLRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness ) {\n\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\tvec3 reflectVec = reflect( - viewDir, normal );\n\t\t\treflectVec = normalize( mix( reflectVec, normal, roughness * roughness) );\n\t\t\treflectVec = inverseTransformDirection( reflectVec, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, reflectVec, roughness );\n\t\t\treturn envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n\t#ifdef USE_ANISOTROPY\n\t\tvec3 getIBLAnisotropyRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness, const in vec3 bitangent, const in float anisotropy ) {\n\t\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\t\tvec3 bentNormal = cross( bitangent, viewDir );\n\t\t\t\tbentNormal = normalize( cross( bentNormal, bitangent ) );\n\t\t\t\tbentNormal = normalize( mix( bentNormal, normal, pow2( pow2( 1.0 - anisotropy * ( 1.0 - roughness ) ) ) ) );\n\t\t\t\treturn getIBLRadiance( viewDir, bentNormal, roughness );\n\t\t\t#else\n\t\t\t\treturn vec3( 0.0 );\n\t\t\t#endif\n\t\t}\n\t#endif\n#endif",envmap_vertex:"#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvWorldPosition = worldPosition.xyz;\n\t#else\n\t\tvec3 cameraToVertex;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToVertex = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToVertex = normalize( worldPosition.xyz - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvReflect = reflect( cameraToVertex, worldNormal );\n\t\t#else\n\t\t\tvReflect = refract( cameraToVertex, worldNormal, refractionRatio );\n\t\t#endif\n\t#endif\n#endif",fog_vertex:"#ifdef USE_FOG\n\tvFogDepth = - mvPosition.z;\n#endif",fog_pars_vertex:"#ifdef USE_FOG\n\tvarying float vFogDepth;\n#endif",fog_fragment:"#ifdef USE_FOG\n\t#ifdef FOG_EXP2\n\t\tfloat fogFactor = 1.0 - exp( - fogDensity * fogDensity * vFogDepth * vFogDepth );\n\t#else\n\t\tfloat fogFactor = smoothstep( fogNear, fogFar, vFogDepth );\n\t#endif\n\tgl_FragColor.rgb = mix( gl_FragColor.rgb, fogColor, fogFactor );\n#endif",fog_pars_fragment:"#ifdef USE_FOG\n\tuniform vec3 fogColor;\n\tvarying float vFogDepth;\n\t#ifdef FOG_EXP2\n\t\tuniform float fogDensity;\n\t#else\n\t\tuniform float fogNear;\n\t\tuniform float fogFar;\n\t#endif\n#endif",gradientmap_pars_fragment:"#ifdef USE_GRADIENTMAP\n\tuniform sampler2D gradientMap;\n#endif\nvec3 getGradientIrradiance( vec3 normal, vec3 lightDirection ) {\n\tfloat dotNL = dot( normal, lightDirection );\n\tvec2 coord = vec2( dotNL * 0.5 + 0.5, 0.0 );\n\t#ifdef USE_GRADIENTMAP\n\t\treturn vec3( texture2D( gradientMap, coord ).r );\n\t#else\n\t\tvec2 fw = fwidth( coord ) * 0.5;\n\t\treturn mix( vec3( 0.7 ), vec3( 1.0 ), smoothstep( 0.7 - fw.x, 0.7 + fw.x, coord.x ) );\n\t#endif\n}",lightmap_fragment:"#ifdef USE_LIGHTMAP\n\tvec4 lightMapTexel = texture2D( lightMap, vLightMapUv );\n\tvec3 lightMapIrradiance = lightMapTexel.rgb * lightMapIntensity;\n\treflectedLight.indirectDiffuse += lightMapIrradiance;\n#endif",lightmap_pars_fragment:"#ifdef USE_LIGHTMAP\n\tuniform sampler2D lightMap;\n\tuniform float lightMapIntensity;\n#endif",lights_lambert_fragment:"LambertMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularStrength = specularStrength;",lights_lambert_pars_fragment:"varying vec3 vViewPosition;\nstruct LambertMaterial {\n\tvec3 diffuseColor;\n\tfloat specularStrength;\n};\nvoid RE_Direct_Lambert( const in IncidentLight directLight, const in GeometricContext geometry, const in LambertMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Lambert( const in vec3 irradiance, const in GeometricContext geometry, const in LambertMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Lambert\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Lambert",lights_pars_begin:"uniform bool receiveShadow;\nuniform vec3 ambientLightColor;\nuniform vec3 lightProbe[ 9 ];\nvec3 shGetIrradianceAt( in vec3 normal, in vec3 shCoefficients[ 9 ] ) {\n\tfloat x = normal.x, y = normal.y, z = normal.z;\n\tvec3 result = shCoefficients[ 0 ] * 0.886227;\n\tresult += shCoefficients[ 1 ] * 2.0 * 0.511664 * y;\n\tresult += shCoefficients[ 2 ] * 2.0 * 0.511664 * z;\n\tresult += shCoefficients[ 3 ] * 2.0 * 0.511664 * x;\n\tresult += shCoefficients[ 4 ] * 2.0 * 0.429043 * x * y;\n\tresult += shCoefficients[ 5 ] * 2.0 * 0.429043 * y * z;\n\tresult += shCoefficients[ 6 ] * ( 0.743125 * z * z - 0.247708 );\n\tresult += shCoefficients[ 7 ] * 2.0 * 0.429043 * x * z;\n\tresult += shCoefficients[ 8 ] * 0.429043 * ( x * x - y * y );\n\treturn result;\n}\nvec3 getLightProbeIrradiance( const in vec3 lightProbe[ 9 ], const in vec3 normal ) {\n\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\tvec3 irradiance = shGetIrradianceAt( worldNormal, lightProbe );\n\treturn irradiance;\n}\nvec3 getAmbientLightIrradiance( const in vec3 ambientLightColor ) {\n\tvec3 irradiance = ambientLightColor;\n\treturn irradiance;\n}\nfloat getDistanceAttenuation( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) {\n\t#if defined ( LEGACY_LIGHTS )\n\t\tif ( cutoffDistance > 0.0 && decayExponent > 0.0 ) {\n\t\t\treturn pow( saturate( - lightDistance / cutoffDistance + 1.0 ), decayExponent );\n\t\t}\n\t\treturn 1.0;\n\t#else\n\t\tfloat distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );\n\t\tif ( cutoffDistance > 0.0 ) {\n\t\t\tdistanceFalloff *= pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );\n\t\t}\n\t\treturn distanceFalloff;\n\t#endif\n}\nfloat getSpotAttenuation( const in float coneCosine, const in float penumbraCosine, const in float angleCosine ) {\n\treturn smoothstep( coneCosine, penumbraCosine, angleCosine );\n}\n#if NUM_DIR_LIGHTS > 0\n\tstruct DirectionalLight {\n\t\tvec3 direction;\n\t\tvec3 color;\n\t};\n\tuniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ];\n\tvoid getDirectionalLightInfo( const in DirectionalLight directionalLight, const in GeometricContext geometry, out IncidentLight light ) {\n\t\tlight.color = directionalLight.color;\n\t\tlight.direction = directionalLight.direction;\n\t\tlight.visible = true;\n\t}\n#endif\n#if NUM_POINT_LIGHTS > 0\n\tstruct PointLight {\n\t\tvec3 position;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t};\n\tuniform PointLight pointLights[ NUM_POINT_LIGHTS ];\n\tvoid getPointLightInfo( const in PointLight pointLight, const in GeometricContext geometry, out IncidentLight light ) {\n\t\tvec3 lVector = pointLight.position - geometry.position;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat lightDistance = length( lVector );\n\t\tlight.color = pointLight.color;\n\t\tlight.color *= getDistanceAttenuation( lightDistance, pointLight.distance, pointLight.decay );\n\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t}\n#endif\n#if NUM_SPOT_LIGHTS > 0\n\tstruct SpotLight {\n\t\tvec3 position;\n\t\tvec3 direction;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t\tfloat coneCos;\n\t\tfloat penumbraCos;\n\t};\n\tuniform SpotLight spotLights[ NUM_SPOT_LIGHTS ];\n\tvoid getSpotLightInfo( const in SpotLight spotLight, const in GeometricContext geometry, out IncidentLight light ) {\n\t\tvec3 lVector = spotLight.position - geometry.position;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat angleCos = dot( light.direction, spotLight.direction );\n\t\tfloat spotAttenuation = getSpotAttenuation( spotLight.coneCos, spotLight.penumbraCos, angleCos );\n\t\tif ( spotAttenuation > 0.0 ) {\n\t\t\tfloat lightDistance = length( lVector );\n\t\t\tlight.color = spotLight.color * spotAttenuation;\n\t\t\tlight.color *= getDistanceAttenuation( lightDistance, spotLight.distance, spotLight.decay );\n\t\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t\t} else {\n\t\t\tlight.color = vec3( 0.0 );\n\t\t\tlight.visible = false;\n\t\t}\n\t}\n#endif\n#if NUM_RECT_AREA_LIGHTS > 0\n\tstruct RectAreaLight {\n\t\tvec3 color;\n\t\tvec3 position;\n\t\tvec3 halfWidth;\n\t\tvec3 halfHeight;\n\t};\n\tuniform sampler2D ltc_1;\tuniform sampler2D ltc_2;\n\tuniform RectAreaLight rectAreaLights[ NUM_RECT_AREA_LIGHTS ];\n#endif\n#if NUM_HEMI_LIGHTS > 0\n\tstruct HemisphereLight {\n\t\tvec3 direction;\n\t\tvec3 skyColor;\n\t\tvec3 groundColor;\n\t};\n\tuniform HemisphereLight hemisphereLights[ NUM_HEMI_LIGHTS ];\n\tvec3 getHemisphereLightIrradiance( const in HemisphereLight hemiLight, const in vec3 normal ) {\n\t\tfloat dotNL = dot( normal, hemiLight.direction );\n\t\tfloat hemiDiffuseWeight = 0.5 * dotNL + 0.5;\n\t\tvec3 irradiance = mix( hemiLight.groundColor, hemiLight.skyColor, hemiDiffuseWeight );\n\t\treturn irradiance;\n\t}\n#endif",lights_toon_fragment:"ToonMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;",lights_toon_pars_fragment:"varying vec3 vViewPosition;\nstruct ToonMaterial {\n\tvec3 diffuseColor;\n};\nvoid RE_Direct_Toon( const in IncidentLight directLight, const in GeometricContext geometry, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\tvec3 irradiance = getGradientIrradiance( geometry.normal, directLight.direction ) * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Toon( const in vec3 irradiance, const in GeometricContext geometry, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Toon\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Toon",lights_phong_fragment:"BlinnPhongMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularColor = specular;\nmaterial.specularShininess = shininess;\nmaterial.specularStrength = specularStrength;",lights_phong_pars_fragment:"varying vec3 vViewPosition;\nstruct BlinnPhongMaterial {\n\tvec3 diffuseColor;\n\tvec3 specularColor;\n\tfloat specularShininess;\n\tfloat specularStrength;\n};\nvoid RE_Direct_BlinnPhong( const in IncidentLight directLight, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n\treflectedLight.directSpecular += irradiance * BRDF_BlinnPhong( directLight.direction, geometry.viewDir, geometry.normal, material.specularColor, material.specularShininess ) * material.specularStrength;\n}\nvoid RE_IndirectDiffuse_BlinnPhong( const in vec3 irradiance, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_BlinnPhong\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_BlinnPhong",lights_physical_fragment:"PhysicalMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb * ( 1.0 - metalnessFactor );\nvec3 dxy = max( abs( dFdx( geometryNormal ) ), abs( dFdy( geometryNormal ) ) );\nfloat geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z );\nmaterial.roughness = max( roughnessFactor, 0.0525 );material.roughness += geometryRoughness;\nmaterial.roughness = min( material.roughness, 1.0 );\n#ifdef IOR\n\tmaterial.ior = ior;\n\t#ifdef USE_SPECULAR\n\t\tfloat specularIntensityFactor = specularIntensity;\n\t\tvec3 specularColorFactor = specularColor;\n\t\t#ifdef USE_SPECULAR_COLORMAP\n\t\t\tspecularColorFactor *= texture2D( specularColorMap, vSpecularColorMapUv ).rgb;\n\t\t#endif\n\t\t#ifdef USE_SPECULAR_INTENSITYMAP\n\t\t\tspecularIntensityFactor *= texture2D( specularIntensityMap, vSpecularIntensityMapUv ).a;\n\t\t#endif\n\t\tmaterial.specularF90 = mix( specularIntensityFactor, 1.0, metalnessFactor );\n\t#else\n\t\tfloat specularIntensityFactor = 1.0;\n\t\tvec3 specularColorFactor = vec3( 1.0 );\n\t\tmaterial.specularF90 = 1.0;\n\t#endif\n\tmaterial.specularColor = mix( min( pow2( ( material.ior - 1.0 ) / ( material.ior + 1.0 ) ) * specularColorFactor, vec3( 1.0 ) ) * specularIntensityFactor, diffuseColor.rgb, metalnessFactor );\n#else\n\tmaterial.specularColor = mix( vec3( 0.04 ), diffuseColor.rgb, metalnessFactor );\n\tmaterial.specularF90 = 1.0;\n#endif\n#ifdef USE_CLEARCOAT\n\tmaterial.clearcoat = clearcoat;\n\tmaterial.clearcoatRoughness = clearcoatRoughness;\n\tmaterial.clearcoatF0 = vec3( 0.04 );\n\tmaterial.clearcoatF90 = 1.0;\n\t#ifdef USE_CLEARCOATMAP\n\t\tmaterial.clearcoat *= texture2D( clearcoatMap, vClearcoatMapUv ).x;\n\t#endif\n\t#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\t\tmaterial.clearcoatRoughness *= texture2D( clearcoatRoughnessMap, vClearcoatRoughnessMapUv ).y;\n\t#endif\n\tmaterial.clearcoat = saturate( material.clearcoat );\tmaterial.clearcoatRoughness = max( material.clearcoatRoughness, 0.0525 );\n\tmaterial.clearcoatRoughness += geometryRoughness;\n\tmaterial.clearcoatRoughness = min( material.clearcoatRoughness, 1.0 );\n#endif\n#ifdef USE_IRIDESCENCE\n\tmaterial.iridescence = iridescence;\n\tmaterial.iridescenceIOR = iridescenceIOR;\n\t#ifdef USE_IRIDESCENCEMAP\n\t\tmaterial.iridescence *= texture2D( iridescenceMap, vIridescenceMapUv ).r;\n\t#endif\n\t#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\t\tmaterial.iridescenceThickness = (iridescenceThicknessMaximum - iridescenceThicknessMinimum) * texture2D( iridescenceThicknessMap, vIridescenceThicknessMapUv ).g + iridescenceThicknessMinimum;\n\t#else\n\t\tmaterial.iridescenceThickness = iridescenceThicknessMaximum;\n\t#endif\n#endif\n#ifdef USE_SHEEN\n\tmaterial.sheenColor = sheenColor;\n\t#ifdef USE_SHEEN_COLORMAP\n\t\tmaterial.sheenColor *= texture2D( sheenColorMap, vSheenColorMapUv ).rgb;\n\t#endif\n\tmaterial.sheenRoughness = clamp( sheenRoughness, 0.07, 1.0 );\n\t#ifdef USE_SHEEN_ROUGHNESSMAP\n\t\tmaterial.sheenRoughness *= texture2D( sheenRoughnessMap, vSheenRoughnessMapUv ).a;\n\t#endif\n#endif\n#ifdef USE_ANISOTROPY\n\t#ifdef USE_ANISOTROPYMAP\n\t\tmat2 anisotropyMat = mat2( anisotropyVector.x, anisotropyVector.y, - anisotropyVector.y, anisotropyVector.x );\n\t\tvec3 anisotropyPolar = texture2D( anisotropyMap, vAnisotropyMapUv ).rgb;\n\t\tvec2 anisotropyV = anisotropyMat * normalize( 2.0 * anisotropyPolar.rg - vec2( 1.0 ) ) * anisotropyPolar.b;\n\t#else\n\t\tvec2 anisotropyV = anisotropyVector;\n\t#endif\n\tmaterial.anisotropy = length( anisotropyV );\n\tanisotropyV /= material.anisotropy;\n\tmaterial.anisotropy = saturate( material.anisotropy );\n\tmaterial.alphaT = mix( pow2( material.roughness ), 1.0, pow2( material.anisotropy ) );\n\tmaterial.anisotropyT = tbn[ 0 ] * anisotropyV.x - tbn[ 1 ] * anisotropyV.y;\n\tmaterial.anisotropyB = tbn[ 1 ] * anisotropyV.x + tbn[ 0 ] * anisotropyV.y;\n#endif",lights_physical_pars_fragment:"struct PhysicalMaterial {\n\tvec3 diffuseColor;\n\tfloat roughness;\n\tvec3 specularColor;\n\tfloat specularF90;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat clearcoat;\n\t\tfloat clearcoatRoughness;\n\t\tvec3 clearcoatF0;\n\t\tfloat clearcoatF90;\n\t#endif\n\t#ifdef USE_IRIDESCENCE\n\t\tfloat iridescence;\n\t\tfloat iridescenceIOR;\n\t\tfloat iridescenceThickness;\n\t\tvec3 iridescenceFresnel;\n\t\tvec3 iridescenceF0;\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tvec3 sheenColor;\n\t\tfloat sheenRoughness;\n\t#endif\n\t#ifdef IOR\n\t\tfloat ior;\n\t#endif\n\t#ifdef USE_TRANSMISSION\n\t\tfloat transmission;\n\t\tfloat transmissionAlpha;\n\t\tfloat thickness;\n\t\tfloat attenuationDistance;\n\t\tvec3 attenuationColor;\n\t#endif\n\t#ifdef USE_ANISOTROPY\n\t\tfloat anisotropy;\n\t\tfloat alphaT;\n\t\tvec3 anisotropyT;\n\t\tvec3 anisotropyB;\n\t#endif\n};\nvec3 clearcoatSpecular = vec3( 0.0 );\nvec3 sheenSpecular = vec3( 0.0 );\nvec3 Schlick_to_F0( const in vec3 f, const in float f90, const in float dotVH ) {\n float x = clamp( 1.0 - dotVH, 0.0, 1.0 );\n float x2 = x * x;\n float x5 = clamp( x * x2 * x2, 0.0, 0.9999 );\n return ( f - vec3( f90 ) * x5 ) / ( 1.0 - x5 );\n}\nfloat V_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const in float dotNV ) {\n\tfloat a2 = pow2( alpha );\n\tfloat gv = dotNL * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );\n\tfloat gl = dotNV * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );\n\treturn 0.5 / max( gv + gl, EPSILON );\n}\nfloat D_GGX( const in float alpha, const in float dotNH ) {\n\tfloat a2 = pow2( alpha );\n\tfloat denom = pow2( dotNH ) * ( a2 - 1.0 ) + 1.0;\n\treturn RECIPROCAL_PI * a2 / pow2( denom );\n}\n#ifdef USE_ANISOTROPY\n\tfloat V_GGX_SmithCorrelated_Anisotropic( const in float alphaT, const in float alphaB, const in float dotTV, const in float dotBV, const in float dotTL, const in float dotBL, const in float dotNV, const in float dotNL ) {\n\t\tfloat gv = dotNL * length( vec3( alphaT * dotTV, alphaB * dotBV, dotNV ) );\n\t\tfloat gl = dotNV * length( vec3( alphaT * dotTL, alphaB * dotBL, dotNL ) );\n\t\tfloat v = 0.5 / ( gv + gl );\n\t\treturn saturate(v);\n\t}\n\tfloat D_GGX_Anisotropic( const in float alphaT, const in float alphaB, const in float dotNH, const in float dotTH, const in float dotBH ) {\n\t\tfloat a2 = alphaT * alphaB;\n\t\thighp vec3 v = vec3( alphaB * dotTH, alphaT * dotBH, a2 * dotNH );\n\t\thighp float v2 = dot( v, v );\n\t\tfloat w2 = a2 / v2;\n\t\treturn RECIPROCAL_PI * a2 * pow2 ( w2 );\n\t}\n#endif\n#ifdef USE_CLEARCOAT\n\tvec3 BRDF_GGX_Clearcoat( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in PhysicalMaterial material) {\n\t\tvec3 f0 = material.clearcoatF0;\n\t\tfloat f90 = material.clearcoatF90;\n\t\tfloat roughness = material.clearcoatRoughness;\n\t\tfloat alpha = pow2( roughness );\n\t\tvec3 halfDir = normalize( lightDir + viewDir );\n\t\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\t\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\t\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\t\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\t\tvec3 F = F_Schlick( f0, f90, dotVH );\n\t\tfloat V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\t\tfloat D = D_GGX( alpha, dotNH );\n\t\treturn F * ( V * D );\n\t}\n#endif\nvec3 BRDF_GGX( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in PhysicalMaterial material ) {\n\tvec3 f0 = material.specularColor;\n\tfloat f90 = material.specularF90;\n\tfloat roughness = material.roughness;\n\tfloat alpha = pow2( roughness );\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\tvec3 F = F_Schlick( f0, f90, dotVH );\n\t#ifdef USE_IRIDESCENCE\n\t\tF = mix( F, material.iridescenceFresnel, material.iridescence );\n\t#endif\n\t#ifdef USE_ANISOTROPY\n\t\tfloat dotTL = dot( material.anisotropyT, lightDir );\n\t\tfloat dotTV = dot( material.anisotropyT, viewDir );\n\t\tfloat dotTH = dot( material.anisotropyT, halfDir );\n\t\tfloat dotBL = dot( material.anisotropyB, lightDir );\n\t\tfloat dotBV = dot( material.anisotropyB, viewDir );\n\t\tfloat dotBH = dot( material.anisotropyB, halfDir );\n\t\tfloat V = V_GGX_SmithCorrelated_Anisotropic( material.alphaT, alpha, dotTV, dotBV, dotTL, dotBL, dotNV, dotNL );\n\t\tfloat D = D_GGX_Anisotropic( material.alphaT, alpha, dotNH, dotTH, dotBH );\n\t#else\n\t\tfloat V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\t\tfloat D = D_GGX( alpha, dotNH );\n\t#endif\n\treturn F * ( V * D );\n}\nvec2 LTC_Uv( const in vec3 N, const in vec3 V, const in float roughness ) {\n\tconst float LUT_SIZE = 64.0;\n\tconst float LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE;\n\tconst float LUT_BIAS = 0.5 / LUT_SIZE;\n\tfloat dotNV = saturate( dot( N, V ) );\n\tvec2 uv = vec2( roughness, sqrt( 1.0 - dotNV ) );\n\tuv = uv * LUT_SCALE + LUT_BIAS;\n\treturn uv;\n}\nfloat LTC_ClippedSphereFormFactor( const in vec3 f ) {\n\tfloat l = length( f );\n\treturn max( ( l * l + f.z ) / ( l + 1.0 ), 0.0 );\n}\nvec3 LTC_EdgeVectorFormFactor( const in vec3 v1, const in vec3 v2 ) {\n\tfloat x = dot( v1, v2 );\n\tfloat y = abs( x );\n\tfloat a = 0.8543985 + ( 0.4965155 + 0.0145206 * y ) * y;\n\tfloat b = 3.4175940 + ( 4.1616724 + y ) * y;\n\tfloat v = a / b;\n\tfloat theta_sintheta = ( x > 0.0 ) ? v : 0.5 * inversesqrt( max( 1.0 - x * x, 1e-7 ) ) - v;\n\treturn cross( v1, v2 ) * theta_sintheta;\n}\nvec3 LTC_Evaluate( const in vec3 N, const in vec3 V, const in vec3 P, const in mat3 mInv, const in vec3 rectCoords[ 4 ] ) {\n\tvec3 v1 = rectCoords[ 1 ] - rectCoords[ 0 ];\n\tvec3 v2 = rectCoords[ 3 ] - rectCoords[ 0 ];\n\tvec3 lightNormal = cross( v1, v2 );\n\tif( dot( lightNormal, P - rectCoords[ 0 ] ) < 0.0 ) return vec3( 0.0 );\n\tvec3 T1, T2;\n\tT1 = normalize( V - N * dot( V, N ) );\n\tT2 = - cross( N, T1 );\n\tmat3 mat = mInv * transposeMat3( mat3( T1, T2, N ) );\n\tvec3 coords[ 4 ];\n\tcoords[ 0 ] = mat * ( rectCoords[ 0 ] - P );\n\tcoords[ 1 ] = mat * ( rectCoords[ 1 ] - P );\n\tcoords[ 2 ] = mat * ( rectCoords[ 2 ] - P );\n\tcoords[ 3 ] = mat * ( rectCoords[ 3 ] - P );\n\tcoords[ 0 ] = normalize( coords[ 0 ] );\n\tcoords[ 1 ] = normalize( coords[ 1 ] );\n\tcoords[ 2 ] = normalize( coords[ 2 ] );\n\tcoords[ 3 ] = normalize( coords[ 3 ] );\n\tvec3 vectorFormFactor = vec3( 0.0 );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 0 ], coords[ 1 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 1 ], coords[ 2 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 2 ], coords[ 3 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 3 ], coords[ 0 ] );\n\tfloat result = LTC_ClippedSphereFormFactor( vectorFormFactor );\n\treturn vec3( result );\n}\n#if defined( USE_SHEEN )\nfloat D_Charlie( float roughness, float dotNH ) {\n\tfloat alpha = pow2( roughness );\n\tfloat invAlpha = 1.0 / alpha;\n\tfloat cos2h = dotNH * dotNH;\n\tfloat sin2h = max( 1.0 - cos2h, 0.0078125 );\n\treturn ( 2.0 + invAlpha ) * pow( sin2h, invAlpha * 0.5 ) / ( 2.0 * PI );\n}\nfloat V_Neubelt( float dotNV, float dotNL ) {\n\treturn saturate( 1.0 / ( 4.0 * ( dotNL + dotNV - dotNL * dotNV ) ) );\n}\nvec3 BRDF_Sheen( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, vec3 sheenColor, const in float sheenRoughness ) {\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat D = D_Charlie( sheenRoughness, dotNH );\n\tfloat V = V_Neubelt( dotNV, dotNL );\n\treturn sheenColor * ( D * V );\n}\n#endif\nfloat IBLSheenBRDF( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat r2 = roughness * roughness;\n\tfloat a = roughness < 0.25 ? -339.2 * r2 + 161.4 * roughness - 25.9 : -8.48 * r2 + 14.3 * roughness - 9.95;\n\tfloat b = roughness < 0.25 ? 44.0 * r2 - 23.7 * roughness + 3.26 : 1.97 * r2 - 3.27 * roughness + 0.72;\n\tfloat DG = exp( a * dotNV + b ) + ( roughness < 0.25 ? 0.0 : 0.1 * ( roughness - 0.25 ) );\n\treturn saturate( DG * RECIPROCAL_PI );\n}\nvec2 DFGApprox( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tconst vec4 c0 = vec4( - 1, - 0.0275, - 0.572, 0.022 );\n\tconst vec4 c1 = vec4( 1, 0.0425, 1.04, - 0.04 );\n\tvec4 r = roughness * c0 + c1;\n\tfloat a004 = min( r.x * r.x, exp2( - 9.28 * dotNV ) ) * r.x + r.y;\n\tvec2 fab = vec2( - 1.04, 1.04 ) * a004 + r.zw;\n\treturn fab;\n}\nvec3 EnvironmentBRDF( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness ) {\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\treturn specularColor * fab.x + specularF90 * fab.y;\n}\n#ifdef USE_IRIDESCENCE\nvoid computeMultiscatteringIridescence( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float iridescence, const in vec3 iridescenceF0, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#else\nvoid computeMultiscattering( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#endif\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\t#ifdef USE_IRIDESCENCE\n\t\tvec3 Fr = mix( specularColor, iridescenceF0, iridescence );\n\t#else\n\t\tvec3 Fr = specularColor;\n\t#endif\n\tvec3 FssEss = Fr * fab.x + specularF90 * fab.y;\n\tfloat Ess = fab.x + fab.y;\n\tfloat Ems = 1.0 - Ess;\n\tvec3 Favg = Fr + ( 1.0 - Fr ) * 0.047619;\tvec3 Fms = FssEss * Favg / ( 1.0 - Ems * Favg );\n\tsingleScatter += FssEss;\n\tmultiScatter += Fms * Ems;\n}\n#if NUM_RECT_AREA_LIGHTS > 0\n\tvoid RE_Direct_RectArea_Physical( const in RectAreaLight rectAreaLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\t\tvec3 normal = geometry.normal;\n\t\tvec3 viewDir = geometry.viewDir;\n\t\tvec3 position = geometry.position;\n\t\tvec3 lightPos = rectAreaLight.position;\n\t\tvec3 halfWidth = rectAreaLight.halfWidth;\n\t\tvec3 halfHeight = rectAreaLight.halfHeight;\n\t\tvec3 lightColor = rectAreaLight.color;\n\t\tfloat roughness = material.roughness;\n\t\tvec3 rectCoords[ 4 ];\n\t\trectCoords[ 0 ] = lightPos + halfWidth - halfHeight;\t\trectCoords[ 1 ] = lightPos - halfWidth - halfHeight;\n\t\trectCoords[ 2 ] = lightPos - halfWidth + halfHeight;\n\t\trectCoords[ 3 ] = lightPos + halfWidth + halfHeight;\n\t\tvec2 uv = LTC_Uv( normal, viewDir, roughness );\n\t\tvec4 t1 = texture2D( ltc_1, uv );\n\t\tvec4 t2 = texture2D( ltc_2, uv );\n\t\tmat3 mInv = mat3(\n\t\t\tvec3( t1.x, 0, t1.y ),\n\t\t\tvec3( 0, 1, 0 ),\n\t\t\tvec3( t1.z, 0, t1.w )\n\t\t);\n\t\tvec3 fresnel = ( material.specularColor * t2.x + ( vec3( 1.0 ) - material.specularColor ) * t2.y );\n\t\treflectedLight.directSpecular += lightColor * fresnel * LTC_Evaluate( normal, viewDir, position, mInv, rectCoords );\n\t\treflectedLight.directDiffuse += lightColor * material.diffuseColor * LTC_Evaluate( normal, viewDir, position, mat3( 1.0 ), rectCoords );\n\t}\n#endif\nvoid RE_Direct_Physical( const in IncidentLight directLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNLcc = saturate( dot( geometry.clearcoatNormal, directLight.direction ) );\n\t\tvec3 ccIrradiance = dotNLcc * directLight.color;\n\t\tclearcoatSpecular += ccIrradiance * BRDF_GGX_Clearcoat( directLight.direction, geometry.viewDir, geometry.clearcoatNormal, material );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tsheenSpecular += irradiance * BRDF_Sheen( directLight.direction, geometry.viewDir, geometry.normal, material.sheenColor, material.sheenRoughness );\n\t#endif\n\treflectedLight.directSpecular += irradiance * BRDF_GGX( directLight.direction, geometry.viewDir, geometry.normal, material );\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Physical( const in vec3 irradiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 irradiance, const in vec3 clearcoatRadiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatSpecular += clearcoatRadiance * EnvironmentBRDF( geometry.clearcoatNormal, geometry.viewDir, material.clearcoatF0, material.clearcoatF90, material.clearcoatRoughness );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tsheenSpecular += irradiance * material.sheenColor * IBLSheenBRDF( geometry.normal, geometry.viewDir, material.sheenRoughness );\n\t#endif\n\tvec3 singleScattering = vec3( 0.0 );\n\tvec3 multiScattering = vec3( 0.0 );\n\tvec3 cosineWeightedIrradiance = irradiance * RECIPROCAL_PI;\n\t#ifdef USE_IRIDESCENCE\n\t\tcomputeMultiscatteringIridescence( geometry.normal, geometry.viewDir, material.specularColor, material.specularF90, material.iridescence, material.iridescenceFresnel, material.roughness, singleScattering, multiScattering );\n\t#else\n\t\tcomputeMultiscattering( geometry.normal, geometry.viewDir, material.specularColor, material.specularF90, material.roughness, singleScattering, multiScattering );\n\t#endif\n\tvec3 totalScattering = singleScattering + multiScattering;\n\tvec3 diffuse = material.diffuseColor * ( 1.0 - max( max( totalScattering.r, totalScattering.g ), totalScattering.b ) );\n\treflectedLight.indirectSpecular += radiance * singleScattering;\n\treflectedLight.indirectSpecular += multiScattering * cosineWeightedIrradiance;\n\treflectedLight.indirectDiffuse += diffuse * cosineWeightedIrradiance;\n}\n#define RE_Direct\t\t\t\tRE_Direct_Physical\n#define RE_Direct_RectArea\t\tRE_Direct_RectArea_Physical\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Physical\n#define RE_IndirectSpecular\t\tRE_IndirectSpecular_Physical\nfloat computeSpecularOcclusion( const in float dotNV, const in float ambientOcclusion, const in float roughness ) {\n\treturn saturate( pow( dotNV + ambientOcclusion, exp2( - 16.0 * roughness - 1.0 ) ) - 1.0 + ambientOcclusion );\n}",lights_fragment_begin:"\nGeometricContext geometry;\ngeometry.position = - vViewPosition;\ngeometry.normal = normal;\ngeometry.viewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( vViewPosition );\n#ifdef USE_CLEARCOAT\n\tgeometry.clearcoatNormal = clearcoatNormal;\n#endif\n#ifdef USE_IRIDESCENCE\n\tfloat dotNVi = saturate( dot( normal, geometry.viewDir ) );\n\tif ( material.iridescenceThickness == 0.0 ) {\n\t\tmaterial.iridescence = 0.0;\n\t} else {\n\t\tmaterial.iridescence = saturate( material.iridescence );\n\t}\n\tif ( material.iridescence > 0.0 ) {\n\t\tmaterial.iridescenceFresnel = evalIridescence( 1.0, material.iridescenceIOR, dotNVi, material.iridescenceThickness, material.specularColor );\n\t\tmaterial.iridescenceF0 = Schlick_to_F0( material.iridescenceFresnel, 1.0, dotNVi );\n\t}\n#endif\nIncidentLight directLight;\n#if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct )\n\tPointLight pointLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tpointLight = pointLights[ i ];\n\t\tgetPointLightInfo( pointLight, geometry, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_POINT_LIGHT_SHADOWS )\n\t\tpointLightShadow = pointLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getPointShadow( pointShadowMap[ i ], pointLightShadow.shadowMapSize, pointLightShadow.shadowBias, pointLightShadow.shadowRadius, vPointShadowCoord[ i ], pointLightShadow.shadowCameraNear, pointLightShadow.shadowCameraFar ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct )\n\tSpotLight spotLight;\n\tvec4 spotColor;\n\tvec3 spotLightCoord;\n\tbool inSpotLightMap;\n\t#if defined( USE_SHADOWMAP ) && NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tspotLight = spotLights[ i ];\n\t\tgetSpotLightInfo( spotLight, geometry, directLight );\n\t\t#if ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )\n\t\t#define SPOT_LIGHT_MAP_INDEX UNROLLED_LOOP_INDEX\n\t\t#elif ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\t#define SPOT_LIGHT_MAP_INDEX NUM_SPOT_LIGHT_MAPS\n\t\t#else\n\t\t#define SPOT_LIGHT_MAP_INDEX ( UNROLLED_LOOP_INDEX - NUM_SPOT_LIGHT_SHADOWS + NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )\n\t\t#endif\n\t\t#if ( SPOT_LIGHT_MAP_INDEX < NUM_SPOT_LIGHT_MAPS )\n\t\t\tspotLightCoord = vSpotLightCoord[ i ].xyz / vSpotLightCoord[ i ].w;\n\t\t\tinSpotLightMap = all( lessThan( abs( spotLightCoord * 2. - 1. ), vec3( 1.0 ) ) );\n\t\t\tspotColor = texture2D( spotLightMap[ SPOT_LIGHT_MAP_INDEX ], spotLightCoord.xy );\n\t\t\tdirectLight.color = inSpotLightMap ? directLight.color * spotColor.rgb : directLight.color;\n\t\t#endif\n\t\t#undef SPOT_LIGHT_MAP_INDEX\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\tspotLightShadow = spotLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( spotShadowMap[ i ], spotLightShadow.shadowMapSize, spotLightShadow.shadowBias, spotLightShadow.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct )\n\tDirectionalLight directionalLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tdirectionalLight = directionalLights[ i ];\n\t\tgetDirectionalLightInfo( directionalLight, geometry, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )\n\t\tdirectionalLightShadow = directionalLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea )\n\tRectAreaLight rectAreaLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) {\n\t\trectAreaLight = rectAreaLights[ i ];\n\t\tRE_Direct_RectArea( rectAreaLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if defined( RE_IndirectDiffuse )\n\tvec3 iblIrradiance = vec3( 0.0 );\n\tvec3 irradiance = getAmbientLightIrradiance( ambientLightColor );\n\tirradiance += getLightProbeIrradiance( lightProbe, geometry.normal );\n\t#if ( NUM_HEMI_LIGHTS > 0 )\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n\t\t\tirradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry.normal );\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n#endif\n#if defined( RE_IndirectSpecular )\n\tvec3 radiance = vec3( 0.0 );\n\tvec3 clearcoatRadiance = vec3( 0.0 );\n#endif",lights_fragment_maps:"#if defined( RE_IndirectDiffuse )\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel = texture2D( lightMap, vLightMapUv );\n\t\tvec3 lightMapIrradiance = lightMapTexel.rgb * lightMapIntensity;\n\t\tirradiance += lightMapIrradiance;\n\t#endif\n\t#if defined( USE_ENVMAP ) && defined( STANDARD ) && defined( ENVMAP_TYPE_CUBE_UV )\n\t\tiblIrradiance += getIBLIrradiance( geometry.normal );\n\t#endif\n#endif\n#if defined( USE_ENVMAP ) && defined( RE_IndirectSpecular )\n\t#ifdef USE_ANISOTROPY\n\t\tradiance += getIBLAnisotropyRadiance( geometry.viewDir, geometry.normal, material.roughness, material.anisotropyB, material.anisotropy );\n\t#else\n\t\tradiance += getIBLRadiance( geometry.viewDir, geometry.normal, material.roughness );\n\t#endif\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatRadiance += getIBLRadiance( geometry.viewDir, geometry.clearcoatNormal, material.clearcoatRoughness );\n\t#endif\n#endif",lights_fragment_end:"#if defined( RE_IndirectDiffuse )\n\tRE_IndirectDiffuse( irradiance, geometry, material, reflectedLight );\n#endif\n#if defined( RE_IndirectSpecular )\n\tRE_IndirectSpecular( radiance, iblIrradiance, clearcoatRadiance, geometry, material, reflectedLight );\n#endif",logdepthbuf_fragment:"#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tgl_FragDepthEXT = vIsPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\n#endif",logdepthbuf_pars_fragment:"#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tuniform float logDepthBufFC;\n\tvarying float vFragDepth;\n\tvarying float vIsPerspective;\n#endif",logdepthbuf_pars_vertex:"#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvarying float vFragDepth;\n\t\tvarying float vIsPerspective;\n\t#else\n\t\tuniform float logDepthBufFC;\n\t#endif\n#endif",logdepthbuf_vertex:"#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvFragDepth = 1.0 + gl_Position.w;\n\t\tvIsPerspective = float( isPerspectiveMatrix( projectionMatrix ) );\n\t#else\n\t\tif ( isPerspectiveMatrix( projectionMatrix ) ) {\n\t\t\tgl_Position.z = log2( max( EPSILON, gl_Position.w + 1.0 ) ) * logDepthBufFC - 1.0;\n\t\t\tgl_Position.z *= gl_Position.w;\n\t\t}\n\t#endif\n#endif",map_fragment:"#ifdef USE_MAP\n\tdiffuseColor *= texture2D( map, vMapUv );\n#endif",map_pars_fragment:"#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif",map_particle_fragment:"#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\t#if defined( USE_POINTS_UV )\n\t\tvec2 uv = vUv;\n\t#else\n\t\tvec2 uv = ( uvTransform * vec3( gl_PointCoord.x, 1.0 - gl_PointCoord.y, 1 ) ).xy;\n\t#endif\n#endif\n#ifdef USE_MAP\n\tdiffuseColor *= texture2D( map, uv );\n#endif\n#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, uv ).g;\n#endif",map_particle_pars_fragment:"#if defined( USE_POINTS_UV )\n\tvarying vec2 vUv;\n#else\n\t#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\t\tuniform mat3 uvTransform;\n\t#endif\n#endif\n#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif\n#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif",metalnessmap_fragment:"float metalnessFactor = metalness;\n#ifdef USE_METALNESSMAP\n\tvec4 texelMetalness = texture2D( metalnessMap, vMetalnessMapUv );\n\tmetalnessFactor *= texelMetalness.b;\n#endif",metalnessmap_pars_fragment:"#ifdef USE_METALNESSMAP\n\tuniform sampler2D metalnessMap;\n#endif",morphcolor_vertex:"#if defined( USE_MORPHCOLORS ) && defined( MORPHTARGETS_TEXTURE )\n\tvColor *= morphTargetBaseInfluence;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t#if defined( USE_COLOR_ALPHA )\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ) * morphTargetInfluences[ i ];\n\t\t#elif defined( USE_COLOR )\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ).rgb * morphTargetInfluences[ i ];\n\t\t#endif\n\t}\n#endif",morphnormal_vertex:"#ifdef USE_MORPHNORMALS\n\tobjectNormal *= morphTargetBaseInfluence;\n\t#ifdef MORPHTARGETS_TEXTURE\n\t\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) objectNormal += getMorph( gl_VertexID, i, 1 ).xyz * morphTargetInfluences[ i ];\n\t\t}\n\t#else\n\t\tobjectNormal += morphNormal0 * morphTargetInfluences[ 0 ];\n\t\tobjectNormal += morphNormal1 * morphTargetInfluences[ 1 ];\n\t\tobjectNormal += morphNormal2 * morphTargetInfluences[ 2 ];\n\t\tobjectNormal += morphNormal3 * morphTargetInfluences[ 3 ];\n\t#endif\n#endif",morphtarget_pars_vertex:"#ifdef USE_MORPHTARGETS\n\tuniform float morphTargetBaseInfluence;\n\t#ifdef MORPHTARGETS_TEXTURE\n\t\tuniform float morphTargetInfluences[ MORPHTARGETS_COUNT ];\n\t\tuniform sampler2DArray morphTargetsTexture;\n\t\tuniform ivec2 morphTargetsTextureSize;\n\t\tvec4 getMorph( const in int vertexIndex, const in int morphTargetIndex, const in int offset ) {\n\t\t\tint texelIndex = vertexIndex * MORPHTARGETS_TEXTURE_STRIDE + offset;\n\t\t\tint y = texelIndex / morphTargetsTextureSize.x;\n\t\t\tint x = texelIndex - y * morphTargetsTextureSize.x;\n\t\t\tivec3 morphUV = ivec3( x, y, morphTargetIndex );\n\t\t\treturn texelFetch( morphTargetsTexture, morphUV, 0 );\n\t\t}\n\t#else\n\t\t#ifndef USE_MORPHNORMALS\n\t\t\tuniform float morphTargetInfluences[ 8 ];\n\t\t#else\n\t\t\tuniform float morphTargetInfluences[ 4 ];\n\t\t#endif\n\t#endif\n#endif",morphtarget_vertex:"#ifdef USE_MORPHTARGETS\n\ttransformed *= morphTargetBaseInfluence;\n\t#ifdef MORPHTARGETS_TEXTURE\n\t\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) transformed += getMorph( gl_VertexID, i, 0 ).xyz * morphTargetInfluences[ i ];\n\t\t}\n\t#else\n\t\ttransformed += morphTarget0 * morphTargetInfluences[ 0 ];\n\t\ttransformed += morphTarget1 * morphTargetInfluences[ 1 ];\n\t\ttransformed += morphTarget2 * morphTargetInfluences[ 2 ];\n\t\ttransformed += morphTarget3 * morphTargetInfluences[ 3 ];\n\t\t#ifndef USE_MORPHNORMALS\n\t\t\ttransformed += morphTarget4 * morphTargetInfluences[ 4 ];\n\t\t\ttransformed += morphTarget5 * morphTargetInfluences[ 5 ];\n\t\t\ttransformed += morphTarget6 * morphTargetInfluences[ 6 ];\n\t\t\ttransformed += morphTarget7 * morphTargetInfluences[ 7 ];\n\t\t#endif\n\t#endif\n#endif",normal_fragment_begin:"float faceDirection = gl_FrontFacing ? 1.0 : - 1.0;\n#ifdef FLAT_SHADED\n\tvec3 fdx = dFdx( vViewPosition );\n\tvec3 fdy = dFdy( vViewPosition );\n\tvec3 normal = normalize( cross( fdx, fdy ) );\n#else\n\tvec3 normal = normalize( vNormal );\n\t#ifdef DOUBLE_SIDED\n\t\tnormal *= faceDirection;\n\t#endif\n#endif\n#if defined( USE_NORMALMAP_TANGENTSPACE ) || defined( USE_CLEARCOAT_NORMALMAP ) || defined( USE_ANISOTROPY )\n\t#ifdef USE_TANGENT\n\t\tmat3 tbn = mat3( normalize( vTangent ), normalize( vBitangent ), normal );\n\t#else\n\t\tmat3 tbn = getTangentFrame( - vViewPosition, normal, vNormalMapUv );\n\t#endif\n\t#if defined( DOUBLE_SIDED ) && ! defined( FLAT_SHADED )\n\t\ttbn[0] *= faceDirection;\n\t\ttbn[1] *= faceDirection;\n\t#endif\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\t#ifdef USE_TANGENT\n\t\tmat3 tbn2 = mat3( normalize( vTangent ), normalize( vBitangent ), normal );\n\t#else\n\t\tmat3 tbn2 = getTangentFrame( - vViewPosition, normal, vClearcoatNormalMapUv );\n\t#endif\n\t#if defined( DOUBLE_SIDED ) && ! defined( FLAT_SHADED )\n\t\ttbn2[0] *= faceDirection;\n\t\ttbn2[1] *= faceDirection;\n\t#endif\n#endif\nvec3 geometryNormal = normal;",normal_fragment_maps:"#ifdef USE_NORMALMAP_OBJECTSPACE\n\tnormal = texture2D( normalMap, vNormalMapUv ).xyz * 2.0 - 1.0;\n\t#ifdef FLIP_SIDED\n\t\tnormal = - normal;\n\t#endif\n\t#ifdef DOUBLE_SIDED\n\t\tnormal = normal * faceDirection;\n\t#endif\n\tnormal = normalize( normalMatrix * normal );\n#elif defined( USE_NORMALMAP_TANGENTSPACE )\n\tvec3 mapN = texture2D( normalMap, vNormalMapUv ).xyz * 2.0 - 1.0;\n\tmapN.xy *= normalScale;\n\tnormal = normalize( tbn * mapN );\n#elif defined( USE_BUMPMAP )\n\tnormal = perturbNormalArb( - vViewPosition, normal, dHdxy_fwd(), faceDirection );\n#endif",normal_pars_fragment:"#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif",normal_pars_vertex:"#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif",normal_vertex:"#ifndef FLAT_SHADED\n\tvNormal = normalize( transformedNormal );\n\t#ifdef USE_TANGENT\n\t\tvTangent = normalize( transformedTangent );\n\t\tvBitangent = normalize( cross( vNormal, vTangent ) * tangent.w );\n\t#endif\n#endif",normalmap_pars_fragment:"#ifdef USE_NORMALMAP\n\tuniform sampler2D normalMap;\n\tuniform vec2 normalScale;\n#endif\n#ifdef USE_NORMALMAP_OBJECTSPACE\n\tuniform mat3 normalMatrix;\n#endif\n#if ! defined ( USE_TANGENT ) && ( defined ( USE_NORMALMAP_TANGENTSPACE ) || defined ( USE_CLEARCOAT_NORMALMAP ) || defined( USE_ANISOTROPY ) )\n\tmat3 getTangentFrame( vec3 eye_pos, vec3 surf_norm, vec2 uv ) {\n\t\tvec3 q0 = dFdx( eye_pos.xyz );\n\t\tvec3 q1 = dFdy( eye_pos.xyz );\n\t\tvec2 st0 = dFdx( uv.st );\n\t\tvec2 st1 = dFdy( uv.st );\n\t\tvec3 N = surf_norm;\n\t\tvec3 q1perp = cross( q1, N );\n\t\tvec3 q0perp = cross( N, q0 );\n\t\tvec3 T = q1perp * st0.x + q0perp * st1.x;\n\t\tvec3 B = q1perp * st0.y + q0perp * st1.y;\n\t\tfloat det = max( dot( T, T ), dot( B, B ) );\n\t\tfloat scale = ( det == 0.0 ) ? 0.0 : inversesqrt( det );\n\t\treturn mat3( T * scale, B * scale, N );\n\t}\n#endif",clearcoat_normal_fragment_begin:"#ifdef USE_CLEARCOAT\n\tvec3 clearcoatNormal = geometryNormal;\n#endif",clearcoat_normal_fragment_maps:"#ifdef USE_CLEARCOAT_NORMALMAP\n\tvec3 clearcoatMapN = texture2D( clearcoatNormalMap, vClearcoatNormalMapUv ).xyz * 2.0 - 1.0;\n\tclearcoatMapN.xy *= clearcoatNormalScale;\n\tclearcoatNormal = normalize( tbn2 * clearcoatMapN );\n#endif",clearcoat_pars_fragment:"#ifdef USE_CLEARCOATMAP\n\tuniform sampler2D clearcoatMap;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tuniform sampler2D clearcoatNormalMap;\n\tuniform vec2 clearcoatNormalScale;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tuniform sampler2D clearcoatRoughnessMap;\n#endif",iridescence_pars_fragment:"#ifdef USE_IRIDESCENCEMAP\n\tuniform sampler2D iridescenceMap;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tuniform sampler2D iridescenceThicknessMap;\n#endif",output_fragment:"#ifdef OPAQUE\ndiffuseColor.a = 1.0;\n#endif\n#ifdef USE_TRANSMISSION\ndiffuseColor.a *= material.transmissionAlpha;\n#endif\ngl_FragColor = vec4( outgoingLight, diffuseColor.a );",packing:"vec3 packNormalToRGB( const in vec3 normal ) {\n\treturn normalize( normal ) * 0.5 + 0.5;\n}\nvec3 unpackRGBToNormal( const in vec3 rgb ) {\n\treturn 2.0 * rgb.xyz - 1.0;\n}\nconst float PackUpscale = 256. / 255.;const float UnpackDownscale = 255. / 256.;\nconst vec3 PackFactors = vec3( 256. * 256. * 256., 256. * 256., 256. );\nconst vec4 UnpackFactors = UnpackDownscale / vec4( PackFactors, 1. );\nconst float ShiftRight8 = 1. / 256.;\nvec4 packDepthToRGBA( const in float v ) {\n\tvec4 r = vec4( fract( v * PackFactors ), v );\n\tr.yzw -= r.xyz * ShiftRight8;\treturn r * PackUpscale;\n}\nfloat unpackRGBAToDepth( const in vec4 v ) {\n\treturn dot( v, UnpackFactors );\n}\nvec2 packDepthToRG( in highp float v ) {\n\treturn packDepthToRGBA( v ).yx;\n}\nfloat unpackRGToDepth( const in highp vec2 v ) {\n\treturn unpackRGBAToDepth( vec4( v.xy, 0.0, 0.0 ) );\n}\nvec4 pack2HalfToRGBA( vec2 v ) {\n\tvec4 r = vec4( v.x, fract( v.x * 255.0 ), v.y, fract( v.y * 255.0 ) );\n\treturn vec4( r.x - r.y / 255.0, r.y, r.z - r.w / 255.0, r.w );\n}\nvec2 unpackRGBATo2Half( vec4 v ) {\n\treturn vec2( v.x + ( v.y / 255.0 ), v.z + ( v.w / 255.0 ) );\n}\nfloat viewZToOrthographicDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( viewZ + near ) / ( near - far );\n}\nfloat orthographicDepthToViewZ( const in float depth, const in float near, const in float far ) {\n\treturn depth * ( near - far ) - near;\n}\nfloat viewZToPerspectiveDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( ( near + viewZ ) * far ) / ( ( far - near ) * viewZ );\n}\nfloat perspectiveDepthToViewZ( const in float depth, const in float near, const in float far ) {\n\treturn ( near * far ) / ( ( far - near ) * depth - far );\n}",premultiplied_alpha_fragment:"#ifdef PREMULTIPLIED_ALPHA\n\tgl_FragColor.rgb *= gl_FragColor.a;\n#endif",project_vertex:"vec4 mvPosition = vec4( transformed, 1.0 );\n#ifdef USE_INSTANCING\n\tmvPosition = instanceMatrix * mvPosition;\n#endif\nmvPosition = modelViewMatrix * mvPosition;\ngl_Position = projectionMatrix * mvPosition;",dithering_fragment:"#ifdef DITHERING\n\tgl_FragColor.rgb = dithering( gl_FragColor.rgb );\n#endif",dithering_pars_fragment:"#ifdef DITHERING\n\tvec3 dithering( vec3 color ) {\n\t\tfloat grid_position = rand( gl_FragCoord.xy );\n\t\tvec3 dither_shift_RGB = vec3( 0.25 / 255.0, -0.25 / 255.0, 0.25 / 255.0 );\n\t\tdither_shift_RGB = mix( 2.0 * dither_shift_RGB, -2.0 * dither_shift_RGB, grid_position );\n\t\treturn color + dither_shift_RGB;\n\t}\n#endif",roughnessmap_fragment:"float roughnessFactor = roughness;\n#ifdef USE_ROUGHNESSMAP\n\tvec4 texelRoughness = texture2D( roughnessMap, vRoughnessMapUv );\n\troughnessFactor *= texelRoughness.g;\n#endif",roughnessmap_pars_fragment:"#ifdef USE_ROUGHNESSMAP\n\tuniform sampler2D roughnessMap;\n#endif",shadowmap_pars_fragment:"#if NUM_SPOT_LIGHT_COORDS > 0\n\tvarying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ];\n#endif\n#if NUM_SPOT_LIGHT_MAPS > 0\n\tuniform sampler2D spotLightMap[ NUM_SPOT_LIGHT_MAPS ];\n#endif\n#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D directionalShadowMap[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D spotShadowMap[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D pointShadowMap[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n\tfloat texture2DCompare( sampler2D depths, vec2 uv, float compare ) {\n\t\treturn step( compare, unpackRGBAToDepth( texture2D( depths, uv ) ) );\n\t}\n\tvec2 texture2DDistribution( sampler2D shadow, vec2 uv ) {\n\t\treturn unpackRGBATo2Half( texture2D( shadow, uv ) );\n\t}\n\tfloat VSMShadow (sampler2D shadow, vec2 uv, float compare ){\n\t\tfloat occlusion = 1.0;\n\t\tvec2 distribution = texture2DDistribution( shadow, uv );\n\t\tfloat hard_shadow = step( compare , distribution.x );\n\t\tif (hard_shadow != 1.0 ) {\n\t\t\tfloat distance = compare - distribution.x ;\n\t\t\tfloat variance = max( 0.00000, distribution.y * distribution.y );\n\t\t\tfloat softness_probability = variance / (variance + distance * distance );\t\t\tsoftness_probability = clamp( ( softness_probability - 0.3 ) / ( 0.95 - 0.3 ), 0.0, 1.0 );\t\t\tocclusion = clamp( max( hard_shadow, softness_probability ), 0.0, 1.0 );\n\t\t}\n\t\treturn occlusion;\n\t}\n\tfloat getShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord ) {\n\t\tfloat shadow = 1.0;\n\t\tshadowCoord.xyz /= shadowCoord.w;\n\t\tshadowCoord.z += shadowBias;\n\t\tbool inFrustum = shadowCoord.x >= 0.0 && shadowCoord.x <= 1.0 && shadowCoord.y >= 0.0 && shadowCoord.y <= 1.0;\n\t\tbool frustumTest = inFrustum && shadowCoord.z <= 1.0;\n\t\tif ( frustumTest ) {\n\t\t#if defined( SHADOWMAP_TYPE_PCF )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx0 = - texelSize.x * shadowRadius;\n\t\t\tfloat dy0 = - texelSize.y * shadowRadius;\n\t\t\tfloat dx1 = + texelSize.x * shadowRadius;\n\t\t\tfloat dy1 = + texelSize.y * shadowRadius;\n\t\t\tfloat dx2 = dx0 / 2.0;\n\t\t\tfloat dy2 = dy0 / 2.0;\n\t\t\tfloat dx3 = dx1 / 2.0;\n\t\t\tfloat dy3 = dy1 / 2.0;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy1 ), shadowCoord.z )\n\t\t\t) * ( 1.0 / 17.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_PCF_SOFT )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx = texelSize.x;\n\t\t\tfloat dy = texelSize.y;\n\t\t\tvec2 uv = shadowCoord.xy;\n\t\t\tvec2 f = fract( uv * shadowMapSize + 0.5 );\n\t\t\tuv -= f * texelSize;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, uv, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( dx, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( 0.0, dy ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + texelSize, shadowCoord.z ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, dy ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( 0.0, -dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 0.0, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( mix( texture2DCompare( shadowMap, uv + vec2( -dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t f.x ),\n\t\t\t\t\t mix( texture2DCompare( shadowMap, uv + vec2( -dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t f.x ),\n\t\t\t\t\t f.y )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_VSM )\n\t\t\tshadow = VSMShadow( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#else\n\t\t\tshadow = texture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#endif\n\t\t}\n\t\treturn shadow;\n\t}\n\tvec2 cubeToUV( vec3 v, float texelSizeY ) {\n\t\tvec3 absV = abs( v );\n\t\tfloat scaleToCube = 1.0 / max( absV.x, max( absV.y, absV.z ) );\n\t\tabsV *= scaleToCube;\n\t\tv *= scaleToCube * ( 1.0 - 2.0 * texelSizeY );\n\t\tvec2 planar = v.xy;\n\t\tfloat almostATexel = 1.5 * texelSizeY;\n\t\tfloat almostOne = 1.0 - almostATexel;\n\t\tif ( absV.z >= almostOne ) {\n\t\t\tif ( v.z > 0.0 )\n\t\t\t\tplanar.x = 4.0 - v.x;\n\t\t} else if ( absV.x >= almostOne ) {\n\t\t\tfloat signX = sign( v.x );\n\t\t\tplanar.x = v.z * signX + 2.0 * signX;\n\t\t} else if ( absV.y >= almostOne ) {\n\t\t\tfloat signY = sign( v.y );\n\t\t\tplanar.x = v.x + 2.0 * signY + 2.0;\n\t\t\tplanar.y = v.z * signY - 2.0;\n\t\t}\n\t\treturn vec2( 0.125, 0.25 ) * planar + vec2( 0.375, 0.75 );\n\t}\n\tfloat getPointShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord, float shadowCameraNear, float shadowCameraFar ) {\n\t\tvec2 texelSize = vec2( 1.0 ) / ( shadowMapSize * vec2( 4.0, 2.0 ) );\n\t\tvec3 lightToPosition = shadowCoord.xyz;\n\t\tfloat dp = ( length( lightToPosition ) - shadowCameraNear ) / ( shadowCameraFar - shadowCameraNear );\t\tdp += shadowBias;\n\t\tvec3 bd3D = normalize( lightToPosition );\n\t\t#if defined( SHADOWMAP_TYPE_PCF ) || defined( SHADOWMAP_TYPE_PCF_SOFT ) || defined( SHADOWMAP_TYPE_VSM )\n\t\t\tvec2 offset = vec2( - 1, 1 ) * shadowRadius * texelSize.y;\n\t\t\treturn (\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxx, texelSize.y ), dp )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#else\n\t\t\treturn texture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp );\n\t\t#endif\n\t}\n#endif",shadowmap_pars_vertex:"#if NUM_SPOT_LIGHT_COORDS > 0\n\tuniform mat4 spotLightMatrix[ NUM_SPOT_LIGHT_COORDS ];\n\tvarying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ];\n#endif\n#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform mat4 directionalShadowMatrix[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform mat4 pointShadowMatrix[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n#endif",shadowmap_vertex:"#if ( defined( USE_SHADOWMAP ) && ( NUM_DIR_LIGHT_SHADOWS > 0 || NUM_POINT_LIGHT_SHADOWS > 0 ) ) || ( NUM_SPOT_LIGHT_COORDS > 0 )\n\tvec3 shadowWorldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\tvec4 shadowWorldPosition;\n#endif\n#if defined( USE_SHADOWMAP )\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * directionalLightShadows[ i ].shadowNormalBias, 0 );\n\t\t\tvDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * shadowWorldPosition;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * pointLightShadows[ i ].shadowNormalBias, 0 );\n\t\t\tvPointShadowCoord[ i ] = pointShadowMatrix[ i ] * shadowWorldPosition;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n#endif\n#if NUM_SPOT_LIGHT_COORDS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_COORDS; i ++ ) {\n\t\tshadowWorldPosition = worldPosition;\n\t\t#if ( defined( USE_SHADOWMAP ) && UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\t\tshadowWorldPosition.xyz += shadowWorldNormal * spotLightShadows[ i ].shadowNormalBias;\n\t\t#endif\n\t\tvSpotLightCoord[ i ] = spotLightMatrix[ i ] * shadowWorldPosition;\n\t}\n\t#pragma unroll_loop_end\n#endif",shadowmask_pars_fragment:"float getShadowMask() {\n\tfloat shadow = 1.0;\n\t#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\tdirectionalLight = directionalLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_SHADOWS; i ++ ) {\n\t\tspotLight = spotLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowBias, spotLight.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\tpointLight = pointLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#endif\n\treturn shadow;\n}",skinbase_vertex:"#ifdef USE_SKINNING\n\tmat4 boneMatX = getBoneMatrix( skinIndex.x );\n\tmat4 boneMatY = getBoneMatrix( skinIndex.y );\n\tmat4 boneMatZ = getBoneMatrix( skinIndex.z );\n\tmat4 boneMatW = getBoneMatrix( skinIndex.w );\n#endif",skinning_pars_vertex:"#ifdef USE_SKINNING\n\tuniform mat4 bindMatrix;\n\tuniform mat4 bindMatrixInverse;\n\tuniform highp sampler2D boneTexture;\n\tuniform int boneTextureSize;\n\tmat4 getBoneMatrix( const in float i ) {\n\t\tfloat j = i * 4.0;\n\t\tfloat x = mod( j, float( boneTextureSize ) );\n\t\tfloat y = floor( j / float( boneTextureSize ) );\n\t\tfloat dx = 1.0 / float( boneTextureSize );\n\t\tfloat dy = 1.0 / float( boneTextureSize );\n\t\ty = dy * ( y + 0.5 );\n\t\tvec4 v1 = texture2D( boneTexture, vec2( dx * ( x + 0.5 ), y ) );\n\t\tvec4 v2 = texture2D( boneTexture, vec2( dx * ( x + 1.5 ), y ) );\n\t\tvec4 v3 = texture2D( boneTexture, vec2( dx * ( x + 2.5 ), y ) );\n\t\tvec4 v4 = texture2D( boneTexture, vec2( dx * ( x + 3.5 ), y ) );\n\t\tmat4 bone = mat4( v1, v2, v3, v4 );\n\t\treturn bone;\n\t}\n#endif",skinning_vertex:"#ifdef USE_SKINNING\n\tvec4 skinVertex = bindMatrix * vec4( transformed, 1.0 );\n\tvec4 skinned = vec4( 0.0 );\n\tskinned += boneMatX * skinVertex * skinWeight.x;\n\tskinned += boneMatY * skinVertex * skinWeight.y;\n\tskinned += boneMatZ * skinVertex * skinWeight.z;\n\tskinned += boneMatW * skinVertex * skinWeight.w;\n\ttransformed = ( bindMatrixInverse * skinned ).xyz;\n#endif",skinnormal_vertex:"#ifdef USE_SKINNING\n\tmat4 skinMatrix = mat4( 0.0 );\n\tskinMatrix += skinWeight.x * boneMatX;\n\tskinMatrix += skinWeight.y * boneMatY;\n\tskinMatrix += skinWeight.z * boneMatZ;\n\tskinMatrix += skinWeight.w * boneMatW;\n\tskinMatrix = bindMatrixInverse * skinMatrix * bindMatrix;\n\tobjectNormal = vec4( skinMatrix * vec4( objectNormal, 0.0 ) ).xyz;\n\t#ifdef USE_TANGENT\n\t\tobjectTangent = vec4( skinMatrix * vec4( objectTangent, 0.0 ) ).xyz;\n\t#endif\n#endif",specularmap_fragment:"float specularStrength;\n#ifdef USE_SPECULARMAP\n\tvec4 texelSpecular = texture2D( specularMap, vSpecularMapUv );\n\tspecularStrength = texelSpecular.r;\n#else\n\tspecularStrength = 1.0;\n#endif",specularmap_pars_fragment:"#ifdef USE_SPECULARMAP\n\tuniform sampler2D specularMap;\n#endif",tonemapping_fragment:"#if defined( TONE_MAPPING )\n\tgl_FragColor.rgb = toneMapping( gl_FragColor.rgb );\n#endif",tonemapping_pars_fragment:"#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\nuniform float toneMappingExposure;\nvec3 LinearToneMapping( vec3 color ) {\n\treturn toneMappingExposure * color;\n}\nvec3 ReinhardToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\treturn saturate( color / ( vec3( 1.0 ) + color ) );\n}\nvec3 OptimizedCineonToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\tcolor = max( vec3( 0.0 ), color - 0.004 );\n\treturn pow( ( color * ( 6.2 * color + 0.5 ) ) / ( color * ( 6.2 * color + 1.7 ) + 0.06 ), vec3( 2.2 ) );\n}\nvec3 RRTAndODTFit( vec3 v ) {\n\tvec3 a = v * ( v + 0.0245786 ) - 0.000090537;\n\tvec3 b = v * ( 0.983729 * v + 0.4329510 ) + 0.238081;\n\treturn a / b;\n}\nvec3 ACESFilmicToneMapping( vec3 color ) {\n\tconst mat3 ACESInputMat = mat3(\n\t\tvec3( 0.59719, 0.07600, 0.02840 ),\t\tvec3( 0.35458, 0.90834, 0.13383 ),\n\t\tvec3( 0.04823, 0.01566, 0.83777 )\n\t);\n\tconst mat3 ACESOutputMat = mat3(\n\t\tvec3( 1.60475, -0.10208, -0.00327 ),\t\tvec3( -0.53108, 1.10813, -0.07276 ),\n\t\tvec3( -0.07367, -0.00605, 1.07602 )\n\t);\n\tcolor *= toneMappingExposure / 0.6;\n\tcolor = ACESInputMat * color;\n\tcolor = RRTAndODTFit( color );\n\tcolor = ACESOutputMat * color;\n\treturn saturate( color );\n}\nvec3 CustomToneMapping( vec3 color ) { return color; }",transmission_fragment:"#ifdef USE_TRANSMISSION\n\tmaterial.transmission = transmission;\n\tmaterial.transmissionAlpha = 1.0;\n\tmaterial.thickness = thickness;\n\tmaterial.attenuationDistance = attenuationDistance;\n\tmaterial.attenuationColor = attenuationColor;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tmaterial.transmission *= texture2D( transmissionMap, vTransmissionMapUv ).r;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tmaterial.thickness *= texture2D( thicknessMap, vThicknessMapUv ).g;\n\t#endif\n\tvec3 pos = vWorldPosition;\n\tvec3 v = normalize( cameraPosition - pos );\n\tvec3 n = inverseTransformDirection( normal, viewMatrix );\n\tvec4 transmitted = getIBLVolumeRefraction(\n\t\tn, v, material.roughness, material.diffuseColor, material.specularColor, material.specularF90,\n\t\tpos, modelMatrix, viewMatrix, projectionMatrix, material.ior, material.thickness,\n\t\tmaterial.attenuationColor, material.attenuationDistance );\n\tmaterial.transmissionAlpha = mix( material.transmissionAlpha, transmitted.a, material.transmission );\n\ttotalDiffuse = mix( totalDiffuse, transmitted.rgb, material.transmission );\n#endif",transmission_pars_fragment:"#ifdef USE_TRANSMISSION\n\tuniform float transmission;\n\tuniform float thickness;\n\tuniform float attenuationDistance;\n\tuniform vec3 attenuationColor;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tuniform sampler2D transmissionMap;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tuniform sampler2D thicknessMap;\n\t#endif\n\tuniform vec2 transmissionSamplerSize;\n\tuniform sampler2D transmissionSamplerMap;\n\tuniform mat4 modelMatrix;\n\tuniform mat4 projectionMatrix;\n\tvarying vec3 vWorldPosition;\n\tfloat w0( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * ( a * ( - a + 3.0 ) - 3.0 ) + 1.0 );\n\t}\n\tfloat w1( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * a * ( 3.0 * a - 6.0 ) + 4.0 );\n\t}\n\tfloat w2( float a ){\n\t\treturn ( 1.0 / 6.0 ) * ( a * ( a * ( - 3.0 * a + 3.0 ) + 3.0 ) + 1.0 );\n\t}\n\tfloat w3( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * a * a );\n\t}\n\tfloat g0( float a ) {\n\t\treturn w0( a ) + w1( a );\n\t}\n\tfloat g1( float a ) {\n\t\treturn w2( a ) + w3( a );\n\t}\n\tfloat h0( float a ) {\n\t\treturn - 1.0 + w1( a ) / ( w0( a ) + w1( a ) );\n\t}\n\tfloat h1( float a ) {\n\t\treturn 1.0 + w3( a ) / ( w2( a ) + w3( a ) );\n\t}\n\tvec4 bicubic( sampler2D tex, vec2 uv, vec4 texelSize, float lod ) {\n\t\tuv = uv * texelSize.zw + 0.5;\n\t\tvec2 iuv = floor( uv );\n\t\tvec2 fuv = fract( uv );\n\t\tfloat g0x = g0( fuv.x );\n\t\tfloat g1x = g1( fuv.x );\n\t\tfloat h0x = h0( fuv.x );\n\t\tfloat h1x = h1( fuv.x );\n\t\tfloat h0y = h0( fuv.y );\n\t\tfloat h1y = h1( fuv.y );\n\t\tvec2 p0 = ( vec2( iuv.x + h0x, iuv.y + h0y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p1 = ( vec2( iuv.x + h1x, iuv.y + h0y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p2 = ( vec2( iuv.x + h0x, iuv.y + h1y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p3 = ( vec2( iuv.x + h1x, iuv.y + h1y ) - 0.5 ) * texelSize.xy;\n\t\treturn g0( fuv.y ) * ( g0x * textureLod( tex, p0, lod ) + g1x * textureLod( tex, p1, lod ) ) +\n\t\t\tg1( fuv.y ) * ( g0x * textureLod( tex, p2, lod ) + g1x * textureLod( tex, p3, lod ) );\n\t}\n\tvec4 textureBicubic( sampler2D sampler, vec2 uv, float lod ) {\n\t\tvec2 fLodSize = vec2( textureSize( sampler, int( lod ) ) );\n\t\tvec2 cLodSize = vec2( textureSize( sampler, int( lod + 1.0 ) ) );\n\t\tvec2 fLodSizeInv = 1.0 / fLodSize;\n\t\tvec2 cLodSizeInv = 1.0 / cLodSize;\n\t\tvec4 fSample = bicubic( sampler, uv, vec4( fLodSizeInv, fLodSize ), floor( lod ) );\n\t\tvec4 cSample = bicubic( sampler, uv, vec4( cLodSizeInv, cLodSize ), ceil( lod ) );\n\t\treturn mix( fSample, cSample, fract( lod ) );\n\t}\n\tvec3 getVolumeTransmissionRay( const in vec3 n, const in vec3 v, const in float thickness, const in float ior, const in mat4 modelMatrix ) {\n\t\tvec3 refractionVector = refract( - v, normalize( n ), 1.0 / ior );\n\t\tvec3 modelScale;\n\t\tmodelScale.x = length( vec3( modelMatrix[ 0 ].xyz ) );\n\t\tmodelScale.y = length( vec3( modelMatrix[ 1 ].xyz ) );\n\t\tmodelScale.z = length( vec3( modelMatrix[ 2 ].xyz ) );\n\t\treturn normalize( refractionVector ) * thickness * modelScale;\n\t}\n\tfloat applyIorToRoughness( const in float roughness, const in float ior ) {\n\t\treturn roughness * clamp( ior * 2.0 - 2.0, 0.0, 1.0 );\n\t}\n\tvec4 getTransmissionSample( const in vec2 fragCoord, const in float roughness, const in float ior ) {\n\t\tfloat lod = log2( transmissionSamplerSize.x ) * applyIorToRoughness( roughness, ior );\n\t\treturn textureBicubic( transmissionSamplerMap, fragCoord.xy, lod );\n\t}\n\tvec3 volumeAttenuation( const in float transmissionDistance, const in vec3 attenuationColor, const in float attenuationDistance ) {\n\t\tif ( isinf( attenuationDistance ) ) {\n\t\t\treturn vec3( 1.0 );\n\t\t} else {\n\t\t\tvec3 attenuationCoefficient = -log( attenuationColor ) / attenuationDistance;\n\t\t\tvec3 transmittance = exp( - attenuationCoefficient * transmissionDistance );\t\t\treturn transmittance;\n\t\t}\n\t}\n\tvec4 getIBLVolumeRefraction( const in vec3 n, const in vec3 v, const in float roughness, const in vec3 diffuseColor,\n\t\tconst in vec3 specularColor, const in float specularF90, const in vec3 position, const in mat4 modelMatrix,\n\t\tconst in mat4 viewMatrix, const in mat4 projMatrix, const in float ior, const in float thickness,\n\t\tconst in vec3 attenuationColor, const in float attenuationDistance ) {\n\t\tvec3 transmissionRay = getVolumeTransmissionRay( n, v, thickness, ior, modelMatrix );\n\t\tvec3 refractedRayExit = position + transmissionRay;\n\t\tvec4 ndcPos = projMatrix * viewMatrix * vec4( refractedRayExit, 1.0 );\n\t\tvec2 refractionCoords = ndcPos.xy / ndcPos.w;\n\t\trefractionCoords += 1.0;\n\t\trefractionCoords /= 2.0;\n\t\tvec4 transmittedLight = getTransmissionSample( refractionCoords, roughness, ior );\n\t\tvec3 transmittance = diffuseColor * volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance );\n\t\tvec3 attenuatedColor = transmittance * transmittedLight.rgb;\n\t\tvec3 F = EnvironmentBRDF( n, v, specularColor, specularF90, roughness );\n\t\tfloat transmittanceFactor = ( transmittance.r + transmittance.g + transmittance.b ) / 3.0;\n\t\treturn vec4( ( 1.0 - F ) * attenuatedColor, 1.0 - ( 1.0 - transmittedLight.a ) * transmittanceFactor );\n\t}\n#endif",uv_pars_fragment:"#ifdef USE_UV\n\tvarying vec2 vUv;\n#endif\n#ifdef USE_MAP\n\tvarying vec2 vMapUv;\n#endif\n#ifdef USE_ALPHAMAP\n\tvarying vec2 vAlphaMapUv;\n#endif\n#ifdef USE_LIGHTMAP\n\tvarying vec2 vLightMapUv;\n#endif\n#ifdef USE_AOMAP\n\tvarying vec2 vAoMapUv;\n#endif\n#ifdef USE_BUMPMAP\n\tvarying vec2 vBumpMapUv;\n#endif\n#ifdef USE_NORMALMAP\n\tvarying vec2 vNormalMapUv;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tvarying vec2 vEmissiveMapUv;\n#endif\n#ifdef USE_METALNESSMAP\n\tvarying vec2 vMetalnessMapUv;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tvarying vec2 vRoughnessMapUv;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tvarying vec2 vAnisotropyMapUv;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tvarying vec2 vClearcoatMapUv;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tvarying vec2 vClearcoatNormalMapUv;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tvarying vec2 vClearcoatRoughnessMapUv;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tvarying vec2 vIridescenceMapUv;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tvarying vec2 vIridescenceThicknessMapUv;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tvarying vec2 vSheenColorMapUv;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tvarying vec2 vSheenRoughnessMapUv;\n#endif\n#ifdef USE_SPECULARMAP\n\tvarying vec2 vSpecularMapUv;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tvarying vec2 vSpecularColorMapUv;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tvarying vec2 vSpecularIntensityMapUv;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tuniform mat3 transmissionMapTransform;\n\tvarying vec2 vTransmissionMapUv;\n#endif\n#ifdef USE_THICKNESSMAP\n\tuniform mat3 thicknessMapTransform;\n\tvarying vec2 vThicknessMapUv;\n#endif",uv_pars_vertex:"#ifdef USE_UV\n\tvarying vec2 vUv;\n#endif\n#ifdef USE_MAP\n\tuniform mat3 mapTransform;\n\tvarying vec2 vMapUv;\n#endif\n#ifdef USE_ALPHAMAP\n\tuniform mat3 alphaMapTransform;\n\tvarying vec2 vAlphaMapUv;\n#endif\n#ifdef USE_LIGHTMAP\n\tuniform mat3 lightMapTransform;\n\tvarying vec2 vLightMapUv;\n#endif\n#ifdef USE_AOMAP\n\tuniform mat3 aoMapTransform;\n\tvarying vec2 vAoMapUv;\n#endif\n#ifdef USE_BUMPMAP\n\tuniform mat3 bumpMapTransform;\n\tvarying vec2 vBumpMapUv;\n#endif\n#ifdef USE_NORMALMAP\n\tuniform mat3 normalMapTransform;\n\tvarying vec2 vNormalMapUv;\n#endif\n#ifdef USE_DISPLACEMENTMAP\n\tuniform mat3 displacementMapTransform;\n\tvarying vec2 vDisplacementMapUv;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tuniform mat3 emissiveMapTransform;\n\tvarying vec2 vEmissiveMapUv;\n#endif\n#ifdef USE_METALNESSMAP\n\tuniform mat3 metalnessMapTransform;\n\tvarying vec2 vMetalnessMapUv;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tuniform mat3 roughnessMapTransform;\n\tvarying vec2 vRoughnessMapUv;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tvarying vec2 vAnisotropyMapUv;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tuniform mat3 clearcoatMapTransform;\n\tvarying vec2 vClearcoatMapUv;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tuniform mat3 clearcoatNormalMapTransform;\n\tvarying vec2 vClearcoatNormalMapUv;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tuniform mat3 clearcoatRoughnessMapTransform;\n\tvarying vec2 vClearcoatRoughnessMapUv;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tuniform mat3 sheenColorMapTransform;\n\tvarying vec2 vSheenColorMapUv;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tuniform mat3 sheenRoughnessMapTransform;\n\tvarying vec2 vSheenRoughnessMapUv;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tuniform mat3 iridescenceMapTransform;\n\tvarying vec2 vIridescenceMapUv;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tuniform mat3 iridescenceThicknessMapTransform;\n\tvarying vec2 vIridescenceThicknessMapUv;\n#endif\n#ifdef USE_SPECULARMAP\n\tuniform mat3 specularMapTransform;\n\tvarying vec2 vSpecularMapUv;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tuniform mat3 specularColorMapTransform;\n\tvarying vec2 vSpecularColorMapUv;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tuniform mat3 specularIntensityMapTransform;\n\tvarying vec2 vSpecularIntensityMapUv;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tuniform mat3 transmissionMapTransform;\n\tvarying vec2 vTransmissionMapUv;\n#endif\n#ifdef USE_THICKNESSMAP\n\tuniform mat3 thicknessMapTransform;\n\tvarying vec2 vThicknessMapUv;\n#endif",uv_vertex:"#ifdef USE_UV\n\tvUv = vec3( uv, 1 ).xy;\n#endif\n#ifdef USE_MAP\n\tvMapUv = ( mapTransform * vec3( MAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ALPHAMAP\n\tvAlphaMapUv = ( alphaMapTransform * vec3( ALPHAMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_LIGHTMAP\n\tvLightMapUv = ( lightMapTransform * vec3( LIGHTMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_AOMAP\n\tvAoMapUv = ( aoMapTransform * vec3( AOMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_BUMPMAP\n\tvBumpMapUv = ( bumpMapTransform * vec3( BUMPMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_NORMALMAP\n\tvNormalMapUv = ( normalMapTransform * vec3( NORMALMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_DISPLACEMENTMAP\n\tvDisplacementMapUv = ( displacementMapTransform * vec3( DISPLACEMENTMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tvEmissiveMapUv = ( emissiveMapTransform * vec3( EMISSIVEMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_METALNESSMAP\n\tvMetalnessMapUv = ( metalnessMapTransform * vec3( METALNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tvRoughnessMapUv = ( roughnessMapTransform * vec3( ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tvAnisotropyMapUv = ( vec3( ANISOTROPYMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tvClearcoatMapUv = ( clearcoatMapTransform * vec3( CLEARCOATMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tvClearcoatNormalMapUv = ( clearcoatNormalMapTransform * vec3( CLEARCOAT_NORMALMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tvClearcoatRoughnessMapUv = ( clearcoatRoughnessMapTransform * vec3( CLEARCOAT_ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tvIridescenceMapUv = ( iridescenceMapTransform * vec3( IRIDESCENCEMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tvIridescenceThicknessMapUv = ( iridescenceThicknessMapTransform * vec3( IRIDESCENCE_THICKNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tvSheenColorMapUv = ( sheenColorMapTransform * vec3( SHEEN_COLORMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tvSheenRoughnessMapUv = ( sheenRoughnessMapTransform * vec3( SHEEN_ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULARMAP\n\tvSpecularMapUv = ( specularMapTransform * vec3( SPECULARMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tvSpecularColorMapUv = ( specularColorMapTransform * vec3( SPECULAR_COLORMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tvSpecularIntensityMapUv = ( specularIntensityMapTransform * vec3( SPECULAR_INTENSITYMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tvTransmissionMapUv = ( transmissionMapTransform * vec3( TRANSMISSIONMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_THICKNESSMAP\n\tvThicknessMapUv = ( thicknessMapTransform * vec3( THICKNESSMAP_UV, 1 ) ).xy;\n#endif",worldpos_vertex:"#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined ( USE_SHADOWMAP ) || defined ( USE_TRANSMISSION ) || NUM_SPOT_LIGHT_COORDS > 0\n\tvec4 worldPosition = vec4( transformed, 1.0 );\n\t#ifdef USE_INSTANCING\n\t\tworldPosition = instanceMatrix * worldPosition;\n\t#endif\n\tworldPosition = modelMatrix * worldPosition;\n#endif",background_vert:"varying vec2 vUv;\nuniform mat3 uvTransform;\nvoid main() {\n\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n\tgl_Position = vec4( position.xy, 1.0, 1.0 );\n}",background_frag:"uniform sampler2D t2D;\nuniform float backgroundIntensity;\nvarying vec2 vUv;\nvoid main() {\n\tvec4 texColor = texture2D( t2D, vUv );\n\ttexColor.rgb *= backgroundIntensity;\n\tgl_FragColor = texColor;\n\t#include \n\t#include \n}",backgroundCube_vert:"varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n\tgl_Position.z = gl_Position.w;\n}",backgroundCube_frag:"#ifdef ENVMAP_TYPE_CUBE\n\tuniform samplerCube envMap;\n#elif defined( ENVMAP_TYPE_CUBE_UV )\n\tuniform sampler2D envMap;\n#endif\nuniform float flipEnvMap;\nuniform float backgroundBlurriness;\nuniform float backgroundIntensity;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 texColor = textureCube( envMap, vec3( flipEnvMap * vWorldDirection.x, vWorldDirection.yz ) );\n\t#elif defined( ENVMAP_TYPE_CUBE_UV )\n\t\tvec4 texColor = textureCubeUV( envMap, vWorldDirection, backgroundBlurriness );\n\t#else\n\t\tvec4 texColor = vec4( 0.0, 0.0, 0.0, 1.0 );\n\t#endif\n\ttexColor.rgb *= backgroundIntensity;\n\tgl_FragColor = texColor;\n\t#include \n\t#include \n}",cube_vert:"varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n\tgl_Position.z = gl_Position.w;\n}",cube_frag:"uniform samplerCube tCube;\nuniform float tFlip;\nuniform float opacity;\nvarying vec3 vWorldDirection;\nvoid main() {\n\tvec4 texColor = textureCube( tCube, vec3( tFlip * vWorldDirection.x, vWorldDirection.yz ) );\n\tgl_FragColor = texColor;\n\tgl_FragColor.a *= opacity;\n\t#include \n\t#include \n}",depth_vert:"#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvHighPrecisionZW = gl_Position.zw;\n}",depth_frag:"#if DEPTH_PACKING == 3200\n\tuniform float opacity;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( 1.0 );\n\t#if DEPTH_PACKING == 3200\n\t\tdiffuseColor.a = opacity;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\tfloat fragCoordZ = 0.5 * vHighPrecisionZW[0] / vHighPrecisionZW[1] + 0.5;\n\t#if DEPTH_PACKING == 3200\n\t\tgl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity );\n\t#elif DEPTH_PACKING == 3201\n\t\tgl_FragColor = packDepthToRGBA( fragCoordZ );\n\t#endif\n}",distanceRGBA_vert:"#define DISTANCE\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvWorldPosition = worldPosition.xyz;\n}",distanceRGBA_frag:"#define DISTANCE\nuniform vec3 referencePosition;\nuniform float nearDistance;\nuniform float farDistance;\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main () {\n\t#include \n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include \n\t#include \n\t#include \n\tfloat dist = length( vWorldPosition - referencePosition );\n\tdist = ( dist - nearDistance ) / ( farDistance - nearDistance );\n\tdist = saturate( dist );\n\tgl_FragColor = packDepthToRGBA( dist );\n}",equirect_vert:"varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n}",equirect_frag:"uniform sampler2D tEquirect;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvec3 direction = normalize( vWorldDirection );\n\tvec2 sampleUV = equirectUv( direction );\n\tgl_FragColor = texture2D( tEquirect, sampleUV );\n\t#include \n\t#include \n}",linedashed_vert:"uniform float scale;\nattribute float lineDistance;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvLineDistance = scale * lineDistance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",linedashed_frag:"uniform vec3 diffuse;\nuniform float opacity;\nuniform float dashSize;\nuniform float totalSize;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tif ( mod( vLineDistance, totalSize ) > dashSize ) {\n\t\tdiscard;\n\t}\n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshbasic_vert:"#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#if defined ( USE_ENVMAP ) || defined ( USE_SKINNING )\n\t\t#include \n\t\t#include \n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshbasic_frag:"uniform vec3 diffuse;\nuniform float opacity;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel = texture2D( lightMap, vLightMapUv );\n\t\treflectedLight.indirectDiffuse += lightMapTexel.rgb * lightMapIntensity * RECIPROCAL_PI;\n\t#else\n\t\treflectedLight.indirectDiffuse += vec3( 1.0 );\n\t#endif\n\t#include \n\treflectedLight.indirectDiffuse *= diffuseColor.rgb;\n\tvec3 outgoingLight = reflectedLight.indirectDiffuse;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshlambert_vert:"#define LAMBERT\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n\t#include \n}",meshlambert_frag:"#define LAMBERT\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshmatcap_vert:"#define MATCAP\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n}",meshmatcap_frag:"#define MATCAP\nuniform vec3 diffuse;\nuniform float opacity;\nuniform sampler2D matcap;\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 viewDir = normalize( vViewPosition );\n\tvec3 x = normalize( vec3( viewDir.z, 0.0, - viewDir.x ) );\n\tvec3 y = cross( viewDir, x );\n\tvec2 uv = vec2( dot( x, normal ), dot( y, normal ) ) * 0.495 + 0.5;\n\t#ifdef USE_MATCAP\n\t\tvec4 matcapColor = texture2D( matcap, uv );\n\t#else\n\t\tvec4 matcapColor = vec4( vec3( mix( 0.2, 0.8, uv.y ) ), 1.0 );\n\t#endif\n\tvec3 outgoingLight = diffuseColor.rgb * matcapColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshnormal_vert:"#define NORMAL\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvarying vec3 vViewPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvViewPosition = - mvPosition.xyz;\n#endif\n}",meshnormal_frag:"#define NORMAL\nuniform float opacity;\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvarying vec3 vViewPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\tgl_FragColor = vec4( packNormalToRGB( normal ), opacity );\n\t#ifdef OPAQUE\n\t\tgl_FragColor.a = 1.0;\n\t#endif\n}",meshphong_vert:"#define PHONG\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n\t#include \n}",meshphong_frag:"#define PHONG\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform vec3 specular;\nuniform float shininess;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshphysical_vert:"#define STANDARD\nvarying vec3 vViewPosition;\n#ifdef USE_TRANSMISSION\n\tvarying vec3 vWorldPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n#ifdef USE_TRANSMISSION\n\tvWorldPosition = worldPosition.xyz;\n#endif\n}",meshphysical_frag:"#define STANDARD\n#ifdef PHYSICAL\n\t#define IOR\n\t#define USE_SPECULAR\n#endif\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float roughness;\nuniform float metalness;\nuniform float opacity;\n#ifdef IOR\n\tuniform float ior;\n#endif\n#ifdef USE_SPECULAR\n\tuniform float specularIntensity;\n\tuniform vec3 specularColor;\n\t#ifdef USE_SPECULAR_COLORMAP\n\t\tuniform sampler2D specularColorMap;\n\t#endif\n\t#ifdef USE_SPECULAR_INTENSITYMAP\n\t\tuniform sampler2D specularIntensityMap;\n\t#endif\n#endif\n#ifdef USE_CLEARCOAT\n\tuniform float clearcoat;\n\tuniform float clearcoatRoughness;\n#endif\n#ifdef USE_IRIDESCENCE\n\tuniform float iridescence;\n\tuniform float iridescenceIOR;\n\tuniform float iridescenceThicknessMinimum;\n\tuniform float iridescenceThicknessMaximum;\n#endif\n#ifdef USE_SHEEN\n\tuniform vec3 sheenColor;\n\tuniform float sheenRoughness;\n\t#ifdef USE_SHEEN_COLORMAP\n\t\tuniform sampler2D sheenColorMap;\n\t#endif\n\t#ifdef USE_SHEEN_ROUGHNESSMAP\n\t\tuniform sampler2D sheenRoughnessMap;\n\t#endif\n#endif\n#ifdef USE_ANISOTROPY\n\tuniform vec2 anisotropyVector;\n\t#ifdef USE_ANISOTROPYMAP\n\t\tuniform sampler2D anisotropyMap;\n\t#endif\n#endif\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 totalDiffuse = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse;\n\tvec3 totalSpecular = reflectedLight.directSpecular + reflectedLight.indirectSpecular;\n\t#include \n\tvec3 outgoingLight = totalDiffuse + totalSpecular + totalEmissiveRadiance;\n\t#ifdef USE_SHEEN\n\t\tfloat sheenEnergyComp = 1.0 - 0.157 * max3( material.sheenColor );\n\t\toutgoingLight = outgoingLight * sheenEnergyComp + sheenSpecular;\n\t#endif\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNVcc = saturate( dot( geometry.clearcoatNormal, geometry.viewDir ) );\n\t\tvec3 Fcc = F_Schlick( material.clearcoatF0, material.clearcoatF90, dotNVcc );\n\t\toutgoingLight = outgoingLight * ( 1.0 - material.clearcoat * Fcc ) + clearcoatSpecular * material.clearcoat;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshtoon_vert:"#define TOON\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n}",meshtoon_frag:"#define TOON\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",points_vert:"uniform float size;\nuniform float scale;\n#include \n#include \n#include \n#include \n#include \n#include \n#ifdef USE_POINTS_UV\n\tvarying vec2 vUv;\n\tuniform mat3 uvTransform;\n#endif\nvoid main() {\n\t#ifdef USE_POINTS_UV\n\t\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tgl_PointSize = size;\n\t#ifdef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) gl_PointSize *= ( scale / - mvPosition.z );\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n}",points_frag:"uniform vec3 diffuse;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",shadow_vert:"#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",shadow_frag:"uniform vec3 color;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tgl_FragColor = vec4( color, opacity * ( 1.0 - getShadowMask() ) );\n\t#include \n\t#include \n\t#include \n}",sprite_vert:"uniform float rotation;\nuniform vec2 center;\n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 mvPosition = modelViewMatrix * vec4( 0.0, 0.0, 0.0, 1.0 );\n\tvec2 scale;\n\tscale.x = length( vec3( modelMatrix[ 0 ].x, modelMatrix[ 0 ].y, modelMatrix[ 0 ].z ) );\n\tscale.y = length( vec3( modelMatrix[ 1 ].x, modelMatrix[ 1 ].y, modelMatrix[ 1 ].z ) );\n\t#ifndef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) scale *= - mvPosition.z;\n\t#endif\n\tvec2 alignedPosition = ( position.xy - ( center - vec2( 0.5 ) ) ) * scale;\n\tvec2 rotatedPosition;\n\trotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;\n\trotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;\n\tmvPosition.xy += rotatedPosition;\n\tgl_Position = projectionMatrix * mvPosition;\n\t#include \n\t#include \n\t#include \n}",sprite_frag:"uniform vec3 diffuse;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n}"},Xi={common:{diffuse:{value:new Nn(16777215)},opacity:{value:1},map:{value:null},mapTransform:{value:new Ht},alphaMap:{value:null},alphaMapTransform:{value:new Ht},alphaTest:{value:0}},specularmap:{specularMap:{value:null},specularMapTransform:{value:new Ht}},envmap:{envMap:{value:null},flipEnvMap:{value:-1},reflectivity:{value:1},ior:{value:1.5},refractionRatio:{value:.98}},aomap:{aoMap:{value:null},aoMapIntensity:{value:1},aoMapTransform:{value:new Ht}},lightmap:{lightMap:{value:null},lightMapIntensity:{value:1},lightMapTransform:{value:new Ht}},bumpmap:{bumpMap:{value:null},bumpMapTransform:{value:new Ht},bumpScale:{value:1}},normalmap:{normalMap:{value:null},normalMapTransform:{value:new Ht},normalScale:{value:new Gt(1,1)}},displacementmap:{displacementMap:{value:null},displacementMapTransform:{value:new Ht},displacementScale:{value:1},displacementBias:{value:0}},emissivemap:{emissiveMap:{value:null},emissiveMapTransform:{value:new Ht}},metalnessmap:{metalnessMap:{value:null},metalnessMapTransform:{value:new Ht}},roughnessmap:{roughnessMap:{value:null},roughnessMapTransform:{value:new Ht}},gradientmap:{gradientMap:{value:null}},fog:{fogDensity:{value:25e-5},fogNear:{value:1},fogFar:{value:2e3},fogColor:{value:new Nn(16777215)}},lights:{ambientLightColor:{value:[]},lightProbe:{value:[]},directionalLights:{value:[],properties:{direction:{},color:{}}},directionalLightShadows:{value:[],properties:{shadowBias:{},shadowNormalBias:{},shadowRadius:{},shadowMapSize:{}}},directionalShadowMap:{value:[]},directionalShadowMatrix:{value:[]},spotLights:{value:[],properties:{color:{},position:{},direction:{},distance:{},coneCos:{},penumbraCos:{},decay:{}}},spotLightShadows:{value:[],properties:{shadowBias:{},shadowNormalBias:{},shadowRadius:{},shadowMapSize:{}}},spotLightMap:{value:[]},spotShadowMap:{value:[]},spotLightMatrix:{value:[]},pointLights:{value:[],properties:{color:{},position:{},decay:{},distance:{}}},pointLightShadows:{value:[],properties:{shadowBias:{},shadowNormalBias:{},shadowRadius:{},shadowMapSize:{},shadowCameraNear:{},shadowCameraFar:{}}},pointShadowMap:{value:[]},pointShadowMatrix:{value:[]},hemisphereLights:{value:[],properties:{direction:{},skyColor:{},groundColor:{}}},rectAreaLights:{value:[],properties:{color:{},position:{},width:{},height:{}}},ltc_1:{value:null},ltc_2:{value:null}},points:{diffuse:{value:new Nn(16777215)},opacity:{value:1},size:{value:1},scale:{value:1},map:{value:null},alphaMap:{value:null},alphaTest:{value:0},uvTransform:{value:new Ht}},sprite:{diffuse:{value:new Nn(16777215)},opacity:{value:1},center:{value:new Gt(.5,.5)},rotation:{value:0},map:{value:null},mapTransform:{value:new Ht},alphaMap:{value:null},alphaTest:{value:0}}},ji={basic:{uniforms:Ti([Xi.common,Xi.specularmap,Xi.envmap,Xi.aomap,Xi.lightmap,Xi.fog]),vertexShader:Wi.meshbasic_vert,fragmentShader:Wi.meshbasic_frag},lambert:{uniforms:Ti([Xi.common,Xi.specularmap,Xi.envmap,Xi.aomap,Xi.lightmap,Xi.emissivemap,Xi.bumpmap,Xi.normalmap,Xi.displacementmap,Xi.fog,Xi.lights,{emissive:{value:new Nn(0)}}]),vertexShader:Wi.meshlambert_vert,fragmentShader:Wi.meshlambert_frag},phong:{uniforms:Ti([Xi.common,Xi.specularmap,Xi.envmap,Xi.aomap,Xi.lightmap,Xi.emissivemap,Xi.bumpmap,Xi.normalmap,Xi.displacementmap,Xi.fog,Xi.lights,{emissive:{value:new Nn(0)},specular:{value:new Nn(1118481)},shininess:{value:30}}]),vertexShader:Wi.meshphong_vert,fragmentShader:Wi.meshphong_frag},standard:{uniforms:Ti([Xi.common,Xi.envmap,Xi.aomap,Xi.lightmap,Xi.emissivemap,Xi.bumpmap,Xi.normalmap,Xi.displacementmap,Xi.roughnessmap,Xi.metalnessmap,Xi.fog,Xi.lights,{emissive:{value:new Nn(0)},roughness:{value:1},metalness:{value:0},envMapIntensity:{value:1}}]),vertexShader:Wi.meshphysical_vert,fragmentShader:Wi.meshphysical_frag},toon:{uniforms:Ti([Xi.common,Xi.aomap,Xi.lightmap,Xi.emissivemap,Xi.bumpmap,Xi.normalmap,Xi.displacementmap,Xi.gradientmap,Xi.fog,Xi.lights,{emissive:{value:new Nn(0)}}]),vertexShader:Wi.meshtoon_vert,fragmentShader:Wi.meshtoon_frag},matcap:{uniforms:Ti([Xi.common,Xi.bumpmap,Xi.normalmap,Xi.displacementmap,Xi.fog,{matcap:{value:null}}]),vertexShader:Wi.meshmatcap_vert,fragmentShader:Wi.meshmatcap_frag},points:{uniforms:Ti([Xi.points,Xi.fog]),vertexShader:Wi.points_vert,fragmentShader:Wi.points_frag},dashed:{uniforms:Ti([Xi.common,Xi.fog,{scale:{value:1},dashSize:{value:1},totalSize:{value:2}}]),vertexShader:Wi.linedashed_vert,fragmentShader:Wi.linedashed_frag},depth:{uniforms:Ti([Xi.common,Xi.displacementmap]),vertexShader:Wi.depth_vert,fragmentShader:Wi.depth_frag},normal:{uniforms:Ti([Xi.common,Xi.bumpmap,Xi.normalmap,Xi.displacementmap,{opacity:{value:1}}]),vertexShader:Wi.meshnormal_vert,fragmentShader:Wi.meshnormal_frag},sprite:{uniforms:Ti([Xi.sprite,Xi.fog]),vertexShader:Wi.sprite_vert,fragmentShader:Wi.sprite_frag},background:{uniforms:{uvTransform:{value:new Ht},t2D:{value:null},backgroundIntensity:{value:1}},vertexShader:Wi.background_vert,fragmentShader:Wi.background_frag},backgroundCube:{uniforms:{envMap:{value:null},flipEnvMap:{value:-1},backgroundBlurriness:{value:0},backgroundIntensity:{value:1}},vertexShader:Wi.backgroundCube_vert,fragmentShader:Wi.backgroundCube_frag},cube:{uniforms:{tCube:{value:null},tFlip:{value:-1},opacity:{value:1}},vertexShader:Wi.cube_vert,fragmentShader:Wi.cube_frag},equirect:{uniforms:{tEquirect:{value:null}},vertexShader:Wi.equirect_vert,fragmentShader:Wi.equirect_frag},distanceRGBA:{uniforms:Ti([Xi.common,Xi.displacementmap,{referencePosition:{value:new me},nearDistance:{value:1},farDistance:{value:1e3}}]),vertexShader:Wi.distanceRGBA_vert,fragmentShader:Wi.distanceRGBA_frag},shadow:{uniforms:Ti([Xi.lights,Xi.fog,{color:{value:new Nn(0)},opacity:{value:1}}]),vertexShader:Wi.shadow_vert,fragmentShader:Wi.shadow_frag}};ji.physical={uniforms:Ti([ji.standard.uniforms,{clearcoat:{value:0},clearcoatMap:{value:null},clearcoatMapTransform:{value:new Ht},clearcoatNormalMap:{value:null},clearcoatNormalMapTransform:{value:new Ht},clearcoatNormalScale:{value:new Gt(1,1)},clearcoatRoughness:{value:0},clearcoatRoughnessMap:{value:null},clearcoatRoughnessMapTransform:{value:new Ht},iridescence:{value:0},iridescenceMap:{value:null},iridescenceMapTransform:{value:new Ht},iridescenceIOR:{value:1.3},iridescenceThicknessMinimum:{value:100},iridescenceThicknessMaximum:{value:400},iridescenceThicknessMap:{value:null},iridescenceThicknessMapTransform:{value:new Ht},sheen:{value:0},sheenColor:{value:new Nn(0)},sheenColorMap:{value:null},sheenColorMapTransform:{value:new Ht},sheenRoughness:{value:1},sheenRoughnessMap:{value:null},sheenRoughnessMapTransform:{value:new Ht},transmission:{value:0},transmissionMap:{value:null},transmissionMapTransform:{value:new Ht},transmissionSamplerSize:{value:new Gt},transmissionSamplerMap:{value:null},thickness:{value:0},thicknessMap:{value:null},thicknessMapTransform:{value:new Ht},attenuationDistance:{value:0},attenuationColor:{value:new Nn(0)},specularColor:{value:new Nn(1,1,1)},specularColorMap:{value:null},specularColorMapTransform:{value:new Ht},specularIntensity:{value:1},specularIntensityMap:{value:null},specularIntensityMapTransform:{value:new Ht},anisotropyVector:{value:new Gt},anisotropyMap:{value:null}}]),vertexShader:Wi.meshphysical_vert,fragmentShader:Wi.meshphysical_frag};const qi={r:0,b:0,g:0};function Yi(t,e,n,i,r,s,a){const o=new Nn(0);let c,h,u=!0===s?0:1,d=null,p=0,m=null;function f(e,n){e.getRGB(qi,Ei(t)),i.buffers.color.setClear(qi.r,qi.g,qi.b,n,a)}return{getClearColor:function(){return o},setClearColor:function(t,e=1){o.set(t),u=e,f(o,u)},getClearAlpha:function(){return u},setClearAlpha:function(t){u=t,f(o,u)},render:function(s,g){let v=!1,_=!0===g.isScene?g.background:null;if(_&&_.isTexture){_=(g.backgroundBlurriness>0?n:e).get(_)}switch(null===_?f(o,u):_&&_.isColor&&(f(_,1),v=!0),t.xr.getEnvironmentBlendMode()){case"opaque":v=!0;break;case"additive":i.buffers.color.setClear(0,0,0,1,a),v=!0;break;case"alpha-blend":i.buffers.color.setClear(0,0,0,0,a),v=!0}(t.autoClear||v)&&t.clear(t.autoClearColor,t.autoClearDepth,t.autoClearStencil),_&&(_.isCubeTexture||_.mapping===l)?(void 0===h&&(h=new xi(new Si(1,1,1),new Ai({name:"BackgroundCubeMaterial",uniforms:bi(ji.backgroundCube.uniforms),vertexShader:ji.backgroundCube.vertexShader,fragmentShader:ji.backgroundCube.fragmentShader,side:1,depthTest:!1,depthWrite:!1,fog:!1})),h.geometry.deleteAttribute("normal"),h.geometry.deleteAttribute("uv"),h.onBeforeRender=function(t,e,n){this.matrixWorld.copyPosition(n.matrixWorld)},Object.defineProperty(h.material,"envMap",{get:function(){return this.uniforms.envMap.value}}),r.update(h)),h.material.uniforms.envMap.value=_,h.material.uniforms.flipEnvMap.value=_.isCubeTexture&&!1===_.isRenderTargetTexture?-1:1,h.material.uniforms.backgroundBlurriness.value=g.backgroundBlurriness,h.material.uniforms.backgroundIntensity.value=g.backgroundIntensity,h.material.toneMapped=_.colorSpace!==_t,d===_&&p===_.version&&m===t.toneMapping||(h.material.needsUpdate=!0,d=_,p=_.version,m=t.toneMapping),h.layers.enableAll(),s.unshift(h,h.geometry,h.material,0,0,null)):_&&_.isTexture&&(void 0===c&&(c=new xi(new Vi(2,2),new Ai({name:"BackgroundMaterial",uniforms:bi(ji.background.uniforms),vertexShader:ji.background.vertexShader,fragmentShader:ji.background.fragmentShader,side:0,depthTest:!1,depthWrite:!1,fog:!1})),c.geometry.deleteAttribute("normal"),Object.defineProperty(c.material,"map",{get:function(){return this.uniforms.t2D.value}}),r.update(c)),c.material.uniforms.t2D.value=_,c.material.uniforms.backgroundIntensity.value=g.backgroundIntensity,c.material.toneMapped=_.colorSpace!==_t,!0===_.matrixAutoUpdate&&_.updateMatrix(),c.material.uniforms.uvTransform.value.copy(_.matrix),d===_&&p===_.version&&m===t.toneMapping||(c.material.needsUpdate=!0,d=_,p=_.version,m=t.toneMapping),c.layers.enableAll(),s.unshift(c,c.geometry,c.material,0,0,null))}}}function Zi(t,e,n,i){const r=t.getParameter(t.MAX_VERTEX_ATTRIBS),s=i.isWebGL2?null:e.get("OES_vertex_array_object"),a=i.isWebGL2||null!==s,o={},l=p(null);let c=l,h=!1;function u(e){return i.isWebGL2?t.bindVertexArray(e):s.bindVertexArrayOES(e)}function d(e){return i.isWebGL2?t.deleteVertexArray(e):s.deleteVertexArrayOES(e)}function p(t){const e=[],n=[],i=[];for(let t=0;t=0){const n=r[e];let i=s[e];if(void 0===i&&("instanceMatrix"===e&&t.instanceMatrix&&(i=t.instanceMatrix),"instanceColor"===e&&t.instanceColor&&(i=t.instanceColor)),void 0===n)return!0;if(n.attribute!==i)return!0;if(i&&n.data!==i.data)return!0;a++}}return c.attributesNum!==a||c.index!==i}(r,y,d,M),S&&function(t,e,n,i){const r={},s=e.attributes;let a=0;const o=n.getAttributes();for(const e in o){if(o[e].location>=0){let n=s[e];void 0===n&&("instanceMatrix"===e&&t.instanceMatrix&&(n=t.instanceMatrix),"instanceColor"===e&&t.instanceColor&&(n=t.instanceColor));const i={};i.attribute=n,n&&n.data&&(i.data=n.data),r[e]=i,a++}}c.attributes=r,c.attributesNum=a,c.index=i}(r,y,d,M)}else{const t=!0===l.wireframe;c.geometry===y.id&&c.program===d.id&&c.wireframe===t||(c.geometry=y.id,c.program=d.id,c.wireframe=t,S=!0)}null!==M&&n.update(M,t.ELEMENT_ARRAY_BUFFER),(S||h)&&(h=!1,function(r,s,a,o){if(!1===i.isWebGL2&&(r.isInstancedMesh||o.isInstancedBufferGeometry)&&null===e.get("ANGLE_instanced_arrays"))return;m();const l=o.attributes,c=a.getAttributes(),h=s.defaultAttributeValues;for(const e in c){const s=c[e];if(s.location>=0){let a=l[e];if(void 0===a&&("instanceMatrix"===e&&r.instanceMatrix&&(a=r.instanceMatrix),"instanceColor"===e&&r.instanceColor&&(a=r.instanceColor)),void 0!==a){const e=a.normalized,l=a.itemSize,c=n.get(a);if(void 0===c)continue;const h=c.buffer,u=c.type,d=c.bytesPerElement,p=!0===i.isWebGL2&&(u===t.INT||u===t.UNSIGNED_INT||a.gpuType===x);if(a.isInterleavedBufferAttribute){const n=a.data,i=n.stride,c=a.offset;if(n.isInstancedInterleavedBuffer){for(let t=0;t0&&t.getShaderPrecisionFormat(t.FRAGMENT_SHADER,t.HIGH_FLOAT).precision>0)return"highp";e="mediump"}return"mediump"===e&&t.getShaderPrecisionFormat(t.VERTEX_SHADER,t.MEDIUM_FLOAT).precision>0&&t.getShaderPrecisionFormat(t.FRAGMENT_SHADER,t.MEDIUM_FLOAT).precision>0?"mediump":"lowp"}const s="undefined"!=typeof WebGL2RenderingContext&&"WebGL2RenderingContext"===t.constructor.name;let a=void 0!==n.precision?n.precision:"highp";const o=r(a);o!==a&&(console.warn("THREE.WebGLRenderer:",a,"not supported, using",o,"instead."),a=o);const l=s||e.has("WEBGL_draw_buffers"),c=!0===n.logarithmicDepthBuffer,h=t.getParameter(t.MAX_TEXTURE_IMAGE_UNITS),u=t.getParameter(t.MAX_VERTEX_TEXTURE_IMAGE_UNITS),d=t.getParameter(t.MAX_TEXTURE_SIZE),p=t.getParameter(t.MAX_CUBE_MAP_TEXTURE_SIZE),m=t.getParameter(t.MAX_VERTEX_ATTRIBS),f=t.getParameter(t.MAX_VERTEX_UNIFORM_VECTORS),g=t.getParameter(t.MAX_VARYING_VECTORS),v=t.getParameter(t.MAX_FRAGMENT_UNIFORM_VECTORS),_=u>0,y=s||e.has("OES_texture_float");return{isWebGL2:s,drawBuffers:l,getMaxAnisotropy:function(){if(void 0!==i)return i;if(!0===e.has("EXT_texture_filter_anisotropic")){const n=e.get("EXT_texture_filter_anisotropic");i=t.getParameter(n.MAX_TEXTURE_MAX_ANISOTROPY_EXT)}else i=0;return i},getMaxPrecision:r,precision:a,logarithmicDepthBuffer:c,maxTextures:h,maxVertexTextures:u,maxTextureSize:d,maxCubemapSize:p,maxAttributes:m,maxVertexUniforms:f,maxVaryings:g,maxFragmentUniforms:v,vertexTextures:_,floatFragmentTextures:y,floatVertexTextures:_&&y,maxSamples:s?t.getParameter(t.MAX_SAMPLES):0}}function $i(t){const e=this;let n=null,i=0,r=!1,s=!1;const a=new Fi,o=new Ht,l={value:null,needsUpdate:!1};function c(t,n,i,r){const s=null!==t?t.length:0;let c=null;if(0!==s){if(c=l.value,!0!==r||null===c){const e=i+4*s,r=n.matrixWorldInverse;o.getNormalMatrix(r),(null===c||c.length0);e.numPlanes=i,e.numIntersection=0}();else{const t=s?0:i,e=4*t;let r=m.clippingState||null;l.value=r,r=c(u,o,e,h);for(let t=0;t!==e;++t)r[t]=n[t];m.clippingState=r,this.numIntersection=d?this.numPlanes:0,this.numPlanes+=t}}}function Qi(t){let e=new WeakMap;function n(t,e){return e===a?t.mapping=r:e===o&&(t.mapping=s),t}function i(t){const n=t.target;n.removeEventListener("dispose",i);const r=e.get(n);void 0!==r&&(e.delete(n),r.dispose())}return{get:function(r){if(r&&r.isTexture&&!1===r.isRenderTargetTexture){const s=r.mapping;if(s===a||s===o){if(e.has(r)){return n(e.get(r).texture,r.mapping)}{const s=r.image;if(s&&s.height>0){const a=new Ui(s.height/2);return a.fromEquirectangularTexture(t,r),e.set(r,a),r.addEventListener("dispose",i),n(a.texture,r.mapping)}return null}}}return r},dispose:function(){e=new WeakMap}}}class tr extends Ri{constructor(t=-1,e=1,n=1,i=-1,r=.1,s=2e3){super(),this.isOrthographicCamera=!0,this.type="OrthographicCamera",this.zoom=1,this.view=null,this.left=t,this.right=e,this.top=n,this.bottom=i,this.near=r,this.far=s,this.updateProjectionMatrix()}copy(t,e){return super.copy(t,e),this.left=t.left,this.right=t.right,this.top=t.top,this.bottom=t.bottom,this.near=t.near,this.far=t.far,this.zoom=t.zoom,this.view=null===t.view?null:Object.assign({},t.view),this}setViewOffset(t,e,n,i,r,s){null===this.view&&(this.view={enabled:!0,fullWidth:1,fullHeight:1,offsetX:0,offsetY:0,width:1,height:1}),this.view.enabled=!0,this.view.fullWidth=t,this.view.fullHeight=e,this.view.offsetX=n,this.view.offsetY=i,this.view.width=r,this.view.height=s,this.updateProjectionMatrix()}clearViewOffset(){null!==this.view&&(this.view.enabled=!1),this.updateProjectionMatrix()}updateProjectionMatrix(){const t=(this.right-this.left)/(2*this.zoom),e=(this.top-this.bottom)/(2*this.zoom),n=(this.right+this.left)/2,i=(this.top+this.bottom)/2;let r=n-t,s=n+t,a=i+e,o=i-e;if(null!==this.view&&this.view.enabled){const t=(this.right-this.left)/this.view.fullWidth/this.zoom,e=(this.top-this.bottom)/this.view.fullHeight/this.zoom;r+=t*this.view.offsetX,s=r+t*this.view.width,a-=e*this.view.offsetY,o=a-e*this.view.height}this.projectionMatrix.makeOrthographic(r,s,a,o,this.near,this.far),this.projectionMatrixInverse.copy(this.projectionMatrix).invert()}toJSON(t){const e=super.toJSON(t);return e.object.zoom=this.zoom,e.object.left=this.left,e.object.right=this.right,e.object.top=this.top,e.object.bottom=this.bottom,e.object.near=this.near,e.object.far=this.far,null!==this.view&&(e.object.view=Object.assign({},this.view)),e}}const er=[.125,.215,.35,.446,.526,.582],nr=20,ir=new tr,rr=new Nn;let sr=null;const ar=(1+Math.sqrt(5))/2,or=1/ar,lr=[new me(1,1,1),new me(-1,1,1),new me(1,1,-1),new me(-1,1,-1),new me(0,ar,or),new me(0,ar,-or),new me(or,0,ar),new me(-or,0,ar),new me(ar,or,0),new me(-ar,or,0)];class cr{constructor(t){this._renderer=t,this._pingPongRenderTarget=null,this._lodMax=0,this._cubeSize=0,this._lodPlanes=[],this._sizeLods=[],this._sigmas=[],this._blurMaterial=null,this._cubemapMaterial=null,this._equirectMaterial=null,this._compileMaterial(this._blurMaterial)}fromScene(t,e=0,n=.1,i=100){sr=this._renderer.getRenderTarget(),this._setSize(256);const r=this._allocateTargets();return r.depthBuffer=!0,this._sceneToCubeUV(t,n,i,r),e>0&&this._blur(r,0,0,e),this._applyPMREM(r),this._cleanup(r),r}fromEquirectangular(t,e=null){return this._fromTexture(t,e)}fromCubemap(t,e=null){return this._fromTexture(t,e)}compileCubemapShader(){null===this._cubemapMaterial&&(this._cubemapMaterial=pr(),this._compileMaterial(this._cubemapMaterial))}compileEquirectangularShader(){null===this._equirectMaterial&&(this._equirectMaterial=dr(),this._compileMaterial(this._equirectMaterial))}dispose(){this._dispose(),null!==this._cubemapMaterial&&this._cubemapMaterial.dispose(),null!==this._equirectMaterial&&this._equirectMaterial.dispose()}_setSize(t){this._lodMax=Math.floor(Math.log2(t)),this._cubeSize=Math.pow(2,this._lodMax)}_dispose(){null!==this._blurMaterial&&this._blurMaterial.dispose(),null!==this._pingPongRenderTarget&&this._pingPongRenderTarget.dispose();for(let t=0;tt-4?o=er[a-t+4-1]:0===a&&(o=0),i.push(o);const l=1/(s-2),c=-l,h=1+l,u=[c,c,h,c,h,h,c,c,h,h,c,h],d=6,p=6,m=3,f=2,g=1,v=new Float32Array(m*p*d),_=new Float32Array(f*p*d),y=new Float32Array(g*p*d);for(let t=0;t2?0:-1,i=[e,n,0,e+2/3,n,0,e+2/3,n+1,0,e,n,0,e+2/3,n+1,0,e,n+1,0];v.set(i,m*p*t),_.set(u,f*p*t);const r=[t,t,t,t,t,t];y.set(r,g*p*t)}const x=new ni;x.setAttribute("position",new Xn(v,m)),x.setAttribute("uv",new Xn(_,f)),x.setAttribute("faceIndex",new Xn(y,g)),e.push(x),r>4&&r--}return{lodPlanes:e,sizeLods:n,sigmas:i}}(i)),this._blurMaterial=function(t,e,n){const i=new Float32Array(nr),r=new me(0,1,0),s=new Ai({name:"SphericalGaussianBlur",defines:{n:nr,CUBEUV_TEXEL_WIDTH:1/e,CUBEUV_TEXEL_HEIGHT:1/n,CUBEUV_MAX_MIP:`${t}.0`},uniforms:{envMap:{value:null},samples:{value:1},weights:{value:i},latitudinal:{value:!1},dTheta:{value:0},mipInt:{value:0},poleAxis:{value:r}},vertexShader:mr(),fragmentShader:"\n\n\t\t\tprecision mediump float;\n\t\t\tprecision mediump int;\n\n\t\t\tvarying vec3 vOutputDirection;\n\n\t\t\tuniform sampler2D envMap;\n\t\t\tuniform int samples;\n\t\t\tuniform float weights[ n ];\n\t\t\tuniform bool latitudinal;\n\t\t\tuniform float dTheta;\n\t\t\tuniform float mipInt;\n\t\t\tuniform vec3 poleAxis;\n\n\t\t\t#define ENVMAP_TYPE_CUBE_UV\n\t\t\t#include \n\n\t\t\tvec3 getSample( float theta, vec3 axis ) {\n\n\t\t\t\tfloat cosTheta = cos( theta );\n\t\t\t\t// Rodrigues' axis-angle rotation\n\t\t\t\tvec3 sampleDirection = vOutputDirection * cosTheta\n\t\t\t\t\t+ cross( axis, vOutputDirection ) * sin( theta )\n\t\t\t\t\t+ axis * dot( axis, vOutputDirection ) * ( 1.0 - cosTheta );\n\n\t\t\t\treturn bilinearCubeUV( envMap, sampleDirection, mipInt );\n\n\t\t\t}\n\n\t\t\tvoid main() {\n\n\t\t\t\tvec3 axis = latitudinal ? poleAxis : cross( poleAxis, vOutputDirection );\n\n\t\t\t\tif ( all( equal( axis, vec3( 0.0 ) ) ) ) {\n\n\t\t\t\t\taxis = vec3( vOutputDirection.z, 0.0, - vOutputDirection.x );\n\n\t\t\t\t}\n\n\t\t\t\taxis = normalize( axis );\n\n\t\t\t\tgl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 );\n\t\t\t\tgl_FragColor.rgb += weights[ 0 ] * getSample( 0.0, axis );\n\n\t\t\t\tfor ( int i = 1; i < n; i++ ) {\n\n\t\t\t\t\tif ( i >= samples ) {\n\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\t}\n\n\t\t\t\t\tfloat theta = dTheta * float( i );\n\t\t\t\t\tgl_FragColor.rgb += weights[ i ] * getSample( -1.0 * theta, axis );\n\t\t\t\t\tgl_FragColor.rgb += weights[ i ] * getSample( theta, axis );\n\n\t\t\t\t}\n\n\t\t\t}\n\t\t",blending:0,depthTest:!1,depthWrite:!1});return s}(i,t,e)}return i}_compileMaterial(t){const e=new xi(this._lodPlanes[0],t);this._renderer.compile(e,ir)}_sceneToCubeUV(t,e,n,i){const r=new Ci(90,1,e,n),s=[1,-1,1,1,1,1],a=[1,1,1,-1,-1,-1],o=this._renderer,l=o.autoClear,c=o.toneMapping;o.getClearColor(rr),o.toneMapping=0,o.autoClear=!1;const h=new Fn({name:"PMREM.Background",side:1,depthWrite:!1,depthTest:!1}),u=new xi(new Si,h);let d=!1;const p=t.background;p?p.isColor&&(h.color.copy(p),t.background=null,d=!0):(h.color.copy(rr),d=!0);for(let e=0;e<6;e++){const n=e%3;0===n?(r.up.set(0,s[e],0),r.lookAt(a[e],0,0)):1===n?(r.up.set(0,0,s[e]),r.lookAt(0,a[e],0)):(r.up.set(0,s[e],0),r.lookAt(0,0,a[e]));const l=this._cubeSize;ur(i,n*l,e>2?l:0,l,l),o.setRenderTarget(i),d&&o.render(u,r),o.render(t,r)}u.geometry.dispose(),u.material.dispose(),o.toneMapping=c,o.autoClear=l,t.background=p}_textureToCubeUV(t,e){const n=this._renderer,i=t.mapping===r||t.mapping===s;i?(null===this._cubemapMaterial&&(this._cubemapMaterial=pr()),this._cubemapMaterial.uniforms.flipEnvMap.value=!1===t.isRenderTargetTexture?-1:1):null===this._equirectMaterial&&(this._equirectMaterial=dr());const a=i?this._cubemapMaterial:this._equirectMaterial,o=new xi(this._lodPlanes[0],a);a.uniforms.envMap.value=t;const l=this._cubeSize;ur(e,0,0,3*l,2*l),n.setRenderTarget(e),n.render(o,ir)}_applyPMREM(t){const e=this._renderer,n=e.autoClear;e.autoClear=!1;for(let e=1;enr&&console.warn(`sigmaRadians, ${r}, is too large and will clip, as it requested ${m} samples when the maximum is set to 20`);const f=[];let g=0;for(let t=0;tv-4?i-v+4:0),4*(this._cubeSize-_),3*_,2*_),o.setRenderTarget(e),o.render(c,ir)}}function hr(t,e,n){const i=new he(t,e,n);return i.texture.mapping=l,i.texture.name="PMREM.cubeUv",i.scissorTest=!0,i}function ur(t,e,n,i,r){t.viewport.set(e,n,i,r),t.scissor.set(e,n,i,r)}function dr(){return new Ai({name:"EquirectangularToCubeUV",uniforms:{envMap:{value:null}},vertexShader:mr(),fragmentShader:"\n\n\t\t\tprecision mediump float;\n\t\t\tprecision mediump int;\n\n\t\t\tvarying vec3 vOutputDirection;\n\n\t\t\tuniform sampler2D envMap;\n\n\t\t\t#include \n\n\t\t\tvoid main() {\n\n\t\t\t\tvec3 outputDirection = normalize( vOutputDirection );\n\t\t\t\tvec2 uv = equirectUv( outputDirection );\n\n\t\t\t\tgl_FragColor = vec4( texture2D ( envMap, uv ).rgb, 1.0 );\n\n\t\t\t}\n\t\t",blending:0,depthTest:!1,depthWrite:!1})}function pr(){return new Ai({name:"CubemapToCubeUV",uniforms:{envMap:{value:null},flipEnvMap:{value:-1}},vertexShader:mr(),fragmentShader:"\n\n\t\t\tprecision mediump float;\n\t\t\tprecision mediump int;\n\n\t\t\tuniform float flipEnvMap;\n\n\t\t\tvarying vec3 vOutputDirection;\n\n\t\t\tuniform samplerCube envMap;\n\n\t\t\tvoid main() {\n\n\t\t\t\tgl_FragColor = textureCube( envMap, vec3( flipEnvMap * vOutputDirection.x, vOutputDirection.yz ) );\n\n\t\t\t}\n\t\t",blending:0,depthTest:!1,depthWrite:!1})}function mr(){return"\n\n\t\tprecision mediump float;\n\t\tprecision mediump int;\n\n\t\tattribute float faceIndex;\n\n\t\tvarying vec3 vOutputDirection;\n\n\t\t// RH coordinate system; PMREM face-indexing convention\n\t\tvec3 getDirection( vec2 uv, float face ) {\n\n\t\t\tuv = 2.0 * uv - 1.0;\n\n\t\t\tvec3 direction = vec3( uv, 1.0 );\n\n\t\t\tif ( face == 0.0 ) {\n\n\t\t\t\tdirection = direction.zyx; // ( 1, v, u ) pos x\n\n\t\t\t} else if ( face == 1.0 ) {\n\n\t\t\t\tdirection = direction.xzy;\n\t\t\t\tdirection.xz *= -1.0; // ( -u, 1, -v ) pos y\n\n\t\t\t} else if ( face == 2.0 ) {\n\n\t\t\t\tdirection.x *= -1.0; // ( -u, v, 1 ) pos z\n\n\t\t\t} else if ( face == 3.0 ) {\n\n\t\t\t\tdirection = direction.zyx;\n\t\t\t\tdirection.xz *= -1.0; // ( -1, v, -u ) neg x\n\n\t\t\t} else if ( face == 4.0 ) {\n\n\t\t\t\tdirection = direction.xzy;\n\t\t\t\tdirection.xy *= -1.0; // ( -u, -1, v ) neg y\n\n\t\t\t} else if ( face == 5.0 ) {\n\n\t\t\t\tdirection.z *= -1.0; // ( u, v, -1 ) neg z\n\n\t\t\t}\n\n\t\t\treturn direction;\n\n\t\t}\n\n\t\tvoid main() {\n\n\t\t\tvOutputDirection = getDirection( uv, faceIndex );\n\t\t\tgl_Position = vec4( position, 1.0 );\n\n\t\t}\n\t"}function fr(t){let e=new WeakMap,n=null;function i(t){const n=t.target;n.removeEventListener("dispose",i);const r=e.get(n);void 0!==r&&(e.delete(n),r.dispose())}return{get:function(l){if(l&&l.isTexture){const c=l.mapping,h=c===a||c===o,u=c===r||c===s;if(h||u){if(l.isRenderTargetTexture&&!0===l.needsPMREMUpdate){l.needsPMREMUpdate=!1;let i=e.get(l);return null===n&&(n=new cr(t)),i=h?n.fromEquirectangular(l,i):n.fromCubemap(l,i),e.set(l,i),i.texture}if(e.has(l))return e.get(l).texture;{const r=l.image;if(h&&r&&r.height>0||u&&r&&function(t){let e=0;const n=6;for(let i=0;ie.maxTextureSize&&(w=Math.ceil(E/e.maxTextureSize),E=e.maxTextureSize);const A=new Float32Array(E*w*4*p),R=new ue(A,E,w,p);R.type=S,R.needsUpdate=!0;const C=4*T;for(let P=0;P0)return t;const r=e*n;let s=Rr[r];if(void 0===s&&(s=new Float32Array(r),Rr[r]=s),0!==e){i.toArray(s,0);for(let i=1,r=0;i!==e;++i)r+=n,t[i].toArray(s,r)}return s}function Dr(t,e){if(t.length!==e.length)return!1;for(let n=0,i=t.length;n":" "} ${r}: ${n[t]}`)}return i.join("\n")}(t.getShaderSource(e),i)}return r}function Ps(t,e){const n=function(t){switch(t){case yt:return["Linear","( value )"];case _t:return["sRGB","( value )"];default:return console.warn("THREE.WebGLProgram: Unsupported color space:",t),["Linear","( value )"]}}(e);return"vec4 "+t+"( vec4 value ) { return LinearTo"+n[0]+n[1]+"; }"}function Is(t,e){let n;switch(e){case 1:n="Linear";break;case 2:n="Reinhard";break;case 3:n="OptimizedCineon";break;case 4:n="ACESFilmic";break;case 5:n="Custom";break;default:console.warn("THREE.WebGLProgram: Unsupported toneMapping:",e),n="Linear"}return"vec3 "+t+"( vec3 color ) { return "+n+"ToneMapping( color ); }"}function Us(t){return""!==t}function Ds(t,e){const n=e.numSpotLightShadows+e.numSpotLightMaps-e.numSpotLightShadowsWithMaps;return t.replace(/NUM_DIR_LIGHTS/g,e.numDirLights).replace(/NUM_SPOT_LIGHTS/g,e.numSpotLights).replace(/NUM_SPOT_LIGHT_MAPS/g,e.numSpotLightMaps).replace(/NUM_SPOT_LIGHT_COORDS/g,n).replace(/NUM_RECT_AREA_LIGHTS/g,e.numRectAreaLights).replace(/NUM_POINT_LIGHTS/g,e.numPointLights).replace(/NUM_HEMI_LIGHTS/g,e.numHemiLights).replace(/NUM_DIR_LIGHT_SHADOWS/g,e.numDirLightShadows).replace(/NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS/g,e.numSpotLightShadowsWithMaps).replace(/NUM_SPOT_LIGHT_SHADOWS/g,e.numSpotLightShadows).replace(/NUM_POINT_LIGHT_SHADOWS/g,e.numPointLightShadows)}function Ns(t,e){return t.replace(/NUM_CLIPPING_PLANES/g,e.numClippingPlanes).replace(/UNION_CLIPPING_PLANES/g,e.numClippingPlanes-e.numClipIntersection)}const Os=/^[ \t]*#include +<([\w\d./]+)>/gm;function Fs(t){return t.replace(Os,Bs)}function Bs(t,e){const n=Wi[e];if(void 0===n)throw new Error("Can not resolve #include <"+e+">");return Fs(n)}const zs=/#pragma unroll_loop_start\s+for\s*\(\s*int\s+i\s*=\s*(\d+)\s*;\s*i\s*<\s*(\d+)\s*;\s*i\s*\+\+\s*\)\s*{([\s\S]+?)}\s+#pragma unroll_loop_end/g;function Gs(t){return t.replace(zs,Hs)}function Hs(t,e,n,i){let r="";for(let t=parseInt(e);t0&&(y+="\n"),x=[g,v].filter(Us).join("\n"),x.length>0&&(x+="\n")):(y=[ks(n),"#define SHADER_NAME "+n.shaderName,v,n.instancing?"#define USE_INSTANCING":"",n.instancingColor?"#define USE_INSTANCING_COLOR":"",n.useFog&&n.fog?"#define USE_FOG":"",n.useFog&&n.fogExp2?"#define FOG_EXP2":"",n.map?"#define USE_MAP":"",n.envMap?"#define USE_ENVMAP":"",n.envMap?"#define "+p:"",n.lightMap?"#define USE_LIGHTMAP":"",n.aoMap?"#define USE_AOMAP":"",n.bumpMap?"#define USE_BUMPMAP":"",n.normalMap?"#define USE_NORMALMAP":"",n.normalMapObjectSpace?"#define USE_NORMALMAP_OBJECTSPACE":"",n.normalMapTangentSpace?"#define USE_NORMALMAP_TANGENTSPACE":"",n.displacementMap?"#define USE_DISPLACEMENTMAP":"",n.emissiveMap?"#define USE_EMISSIVEMAP":"",n.anisotropyMap?"#define USE_ANISOTROPYMAP":"",n.clearcoatMap?"#define USE_CLEARCOATMAP":"",n.clearcoatRoughnessMap?"#define USE_CLEARCOAT_ROUGHNESSMAP":"",n.clearcoatNormalMap?"#define USE_CLEARCOAT_NORMALMAP":"",n.iridescenceMap?"#define USE_IRIDESCENCEMAP":"",n.iridescenceThicknessMap?"#define USE_IRIDESCENCE_THICKNESSMAP":"",n.specularMap?"#define USE_SPECULARMAP":"",n.specularColorMap?"#define USE_SPECULAR_COLORMAP":"",n.specularIntensityMap?"#define USE_SPECULAR_INTENSITYMAP":"",n.roughnessMap?"#define USE_ROUGHNESSMAP":"",n.metalnessMap?"#define USE_METALNESSMAP":"",n.alphaMap?"#define USE_ALPHAMAP":"",n.transmission?"#define USE_TRANSMISSION":"",n.transmissionMap?"#define USE_TRANSMISSIONMAP":"",n.thicknessMap?"#define USE_THICKNESSMAP":"",n.sheenColorMap?"#define USE_SHEEN_COLORMAP":"",n.sheenRoughnessMap?"#define USE_SHEEN_ROUGHNESSMAP":"",n.mapUv?"#define MAP_UV "+n.mapUv:"",n.alphaMapUv?"#define ALPHAMAP_UV "+n.alphaMapUv:"",n.lightMapUv?"#define LIGHTMAP_UV "+n.lightMapUv:"",n.aoMapUv?"#define AOMAP_UV "+n.aoMapUv:"",n.emissiveMapUv?"#define EMISSIVEMAP_UV "+n.emissiveMapUv:"",n.bumpMapUv?"#define BUMPMAP_UV "+n.bumpMapUv:"",n.normalMapUv?"#define NORMALMAP_UV "+n.normalMapUv:"",n.displacementMapUv?"#define DISPLACEMENTMAP_UV "+n.displacementMapUv:"",n.metalnessMapUv?"#define METALNESSMAP_UV "+n.metalnessMapUv:"",n.roughnessMapUv?"#define ROUGHNESSMAP_UV "+n.roughnessMapUv:"",n.anisotropyMapUv?"#define ANISOTROPYMAP_UV "+n.anisotropyMapUv:"",n.clearcoatMapUv?"#define CLEARCOATMAP_UV "+n.clearcoatMapUv:"",n.clearcoatNormalMapUv?"#define CLEARCOAT_NORMALMAP_UV "+n.clearcoatNormalMapUv:"",n.clearcoatRoughnessMapUv?"#define CLEARCOAT_ROUGHNESSMAP_UV "+n.clearcoatRoughnessMapUv:"",n.iridescenceMapUv?"#define IRIDESCENCEMAP_UV "+n.iridescenceMapUv:"",n.iridescenceThicknessMapUv?"#define IRIDESCENCE_THICKNESSMAP_UV "+n.iridescenceThicknessMapUv:"",n.sheenColorMapUv?"#define SHEEN_COLORMAP_UV "+n.sheenColorMapUv:"",n.sheenRoughnessMapUv?"#define SHEEN_ROUGHNESSMAP_UV "+n.sheenRoughnessMapUv:"",n.specularMapUv?"#define SPECULARMAP_UV "+n.specularMapUv:"",n.specularColorMapUv?"#define SPECULAR_COLORMAP_UV "+n.specularColorMapUv:"",n.specularIntensityMapUv?"#define SPECULAR_INTENSITYMAP_UV "+n.specularIntensityMapUv:"",n.transmissionMapUv?"#define TRANSMISSIONMAP_UV "+n.transmissionMapUv:"",n.thicknessMapUv?"#define THICKNESSMAP_UV "+n.thicknessMapUv:"",n.vertexTangents?"#define USE_TANGENT":"",n.vertexColors?"#define USE_COLOR":"",n.vertexAlphas?"#define USE_COLOR_ALPHA":"",n.vertexUv1s?"#define USE_UV1":"",n.vertexUv2s?"#define USE_UV2":"",n.vertexUv3s?"#define USE_UV3":"",n.pointsUvs?"#define USE_POINTS_UV":"",n.flatShading?"#define FLAT_SHADED":"",n.skinning?"#define USE_SKINNING":"",n.morphTargets?"#define USE_MORPHTARGETS":"",n.morphNormals&&!1===n.flatShading?"#define USE_MORPHNORMALS":"",n.morphColors&&n.isWebGL2?"#define USE_MORPHCOLORS":"",n.morphTargetsCount>0&&n.isWebGL2?"#define MORPHTARGETS_TEXTURE":"",n.morphTargetsCount>0&&n.isWebGL2?"#define MORPHTARGETS_TEXTURE_STRIDE "+n.morphTextureStride:"",n.morphTargetsCount>0&&n.isWebGL2?"#define MORPHTARGETS_COUNT "+n.morphTargetsCount:"",n.doubleSided?"#define DOUBLE_SIDED":"",n.flipSided?"#define FLIP_SIDED":"",n.shadowMapEnabled?"#define USE_SHADOWMAP":"",n.shadowMapEnabled?"#define "+u:"",n.sizeAttenuation?"#define USE_SIZEATTENUATION":"",n.logarithmicDepthBuffer?"#define USE_LOGDEPTHBUF":"",n.logarithmicDepthBuffer&&n.rendererExtensionFragDepth?"#define USE_LOGDEPTHBUF_EXT":"","uniform mat4 modelMatrix;","uniform mat4 modelViewMatrix;","uniform mat4 projectionMatrix;","uniform mat4 viewMatrix;","uniform mat3 normalMatrix;","uniform vec3 cameraPosition;","uniform bool isOrthographic;","#ifdef USE_INSTANCING","\tattribute mat4 instanceMatrix;","#endif","#ifdef USE_INSTANCING_COLOR","\tattribute vec3 instanceColor;","#endif","attribute vec3 position;","attribute vec3 normal;","attribute vec2 uv;","#ifdef USE_UV1","\tattribute vec2 uv1;","#endif","#ifdef USE_UV2","\tattribute vec2 uv2;","#endif","#ifdef USE_UV3","\tattribute vec2 uv3;","#endif","#ifdef USE_TANGENT","\tattribute vec4 tangent;","#endif","#if defined( USE_COLOR_ALPHA )","\tattribute vec4 color;","#elif defined( USE_COLOR )","\tattribute vec3 color;","#endif","#if ( defined( USE_MORPHTARGETS ) && ! defined( MORPHTARGETS_TEXTURE ) )","\tattribute vec3 morphTarget0;","\tattribute vec3 morphTarget1;","\tattribute vec3 morphTarget2;","\tattribute vec3 morphTarget3;","\t#ifdef USE_MORPHNORMALS","\t\tattribute vec3 morphNormal0;","\t\tattribute vec3 morphNormal1;","\t\tattribute vec3 morphNormal2;","\t\tattribute vec3 morphNormal3;","\t#else","\t\tattribute vec3 morphTarget4;","\t\tattribute vec3 morphTarget5;","\t\tattribute vec3 morphTarget6;","\t\tattribute vec3 morphTarget7;","\t#endif","#endif","#ifdef USE_SKINNING","\tattribute vec4 skinIndex;","\tattribute vec4 skinWeight;","#endif","\n"].filter(Us).join("\n"),x=[g,ks(n),"#define SHADER_NAME "+n.shaderName,v,n.useFog&&n.fog?"#define USE_FOG":"",n.useFog&&n.fogExp2?"#define FOG_EXP2":"",n.map?"#define USE_MAP":"",n.matcap?"#define USE_MATCAP":"",n.envMap?"#define USE_ENVMAP":"",n.envMap?"#define "+d:"",n.envMap?"#define "+p:"",n.envMap?"#define "+m:"",f?"#define CUBEUV_TEXEL_WIDTH "+f.texelWidth:"",f?"#define CUBEUV_TEXEL_HEIGHT "+f.texelHeight:"",f?"#define CUBEUV_MAX_MIP "+f.maxMip+".0":"",n.lightMap?"#define USE_LIGHTMAP":"",n.aoMap?"#define USE_AOMAP":"",n.bumpMap?"#define USE_BUMPMAP":"",n.normalMap?"#define USE_NORMALMAP":"",n.normalMapObjectSpace?"#define USE_NORMALMAP_OBJECTSPACE":"",n.normalMapTangentSpace?"#define USE_NORMALMAP_TANGENTSPACE":"",n.emissiveMap?"#define USE_EMISSIVEMAP":"",n.anisotropy?"#define USE_ANISOTROPY":"",n.anisotropyMap?"#define USE_ANISOTROPYMAP":"",n.clearcoat?"#define USE_CLEARCOAT":"",n.clearcoatMap?"#define USE_CLEARCOATMAP":"",n.clearcoatRoughnessMap?"#define USE_CLEARCOAT_ROUGHNESSMAP":"",n.clearcoatNormalMap?"#define USE_CLEARCOAT_NORMALMAP":"",n.iridescence?"#define USE_IRIDESCENCE":"",n.iridescenceMap?"#define USE_IRIDESCENCEMAP":"",n.iridescenceThicknessMap?"#define USE_IRIDESCENCE_THICKNESSMAP":"",n.specularMap?"#define USE_SPECULARMAP":"",n.specularColorMap?"#define USE_SPECULAR_COLORMAP":"",n.specularIntensityMap?"#define USE_SPECULAR_INTENSITYMAP":"",n.roughnessMap?"#define USE_ROUGHNESSMAP":"",n.metalnessMap?"#define USE_METALNESSMAP":"",n.alphaMap?"#define USE_ALPHAMAP":"",n.alphaTest?"#define USE_ALPHATEST":"",n.sheen?"#define USE_SHEEN":"",n.sheenColorMap?"#define USE_SHEEN_COLORMAP":"",n.sheenRoughnessMap?"#define USE_SHEEN_ROUGHNESSMAP":"",n.transmission?"#define USE_TRANSMISSION":"",n.transmissionMap?"#define USE_TRANSMISSIONMAP":"",n.thicknessMap?"#define USE_THICKNESSMAP":"",n.vertexTangents?"#define USE_TANGENT":"",n.vertexColors||n.instancingColor?"#define USE_COLOR":"",n.vertexAlphas?"#define USE_COLOR_ALPHA":"",n.vertexUv1s?"#define USE_UV1":"",n.vertexUv2s?"#define USE_UV2":"",n.vertexUv3s?"#define USE_UV3":"",n.pointsUvs?"#define USE_POINTS_UV":"",n.gradientMap?"#define USE_GRADIENTMAP":"",n.flatShading?"#define FLAT_SHADED":"",n.doubleSided?"#define DOUBLE_SIDED":"",n.flipSided?"#define FLIP_SIDED":"",n.shadowMapEnabled?"#define USE_SHADOWMAP":"",n.shadowMapEnabled?"#define "+u:"",n.premultipliedAlpha?"#define PREMULTIPLIED_ALPHA":"",n.useLegacyLights?"#define LEGACY_LIGHTS":"",n.logarithmicDepthBuffer?"#define USE_LOGDEPTHBUF":"",n.logarithmicDepthBuffer&&n.rendererExtensionFragDepth?"#define USE_LOGDEPTHBUF_EXT":"","uniform mat4 viewMatrix;","uniform vec3 cameraPosition;","uniform bool isOrthographic;",0!==n.toneMapping?"#define TONE_MAPPING":"",0!==n.toneMapping?Wi.tonemapping_pars_fragment:"",0!==n.toneMapping?Is("toneMapping",n.toneMapping):"",n.dithering?"#define DITHERING":"",n.opaque?"#define OPAQUE":"",Wi.encodings_pars_fragment,Ps("linearToOutputTexel",n.outputColorSpace),n.useDepthPacking?"#define DEPTH_PACKING "+n.depthPacking:"","\n"].filter(Us).join("\n")),c=Fs(c),c=Ds(c,n),c=Ns(c,n),h=Fs(h),h=Ds(h,n),h=Ns(h,n),c=Gs(c),h=Gs(h),n.isWebGL2&&!0!==n.isRawShaderMaterial&&(M="#version 300 es\n",y=["precision mediump sampler2DArray;","#define attribute in","#define varying out","#define texture2D texture"].join("\n")+"\n"+y,x=["#define varying in",n.glslVersion===bt?"":"layout(location = 0) out highp vec4 pc_fragColor;",n.glslVersion===bt?"":"#define gl_FragColor pc_fragColor","#define gl_FragDepthEXT gl_FragDepth","#define texture2D texture","#define textureCube texture","#define texture2DProj textureProj","#define texture2DLodEXT textureLod","#define texture2DProjLodEXT textureProjLod","#define textureCubeLodEXT textureLod","#define texture2DGradEXT textureGrad","#define texture2DProjGradEXT textureProjGrad","#define textureCubeGradEXT textureGrad"].join("\n")+"\n"+x);const S=M+y+c,b=M+x+h,T=Rs(a,a.VERTEX_SHADER,S),E=Rs(a,a.FRAGMENT_SHADER,b);if(a.attachShader(_,T),a.attachShader(_,E),void 0!==n.index0AttributeName?a.bindAttribLocation(_,0,n.index0AttributeName):!0===n.morphTargets&&a.bindAttribLocation(_,0,"position"),a.linkProgram(_),t.debug.checkShaderErrors){const e=a.getProgramInfoLog(_).trim(),n=a.getShaderInfoLog(T).trim(),i=a.getShaderInfoLog(E).trim();let r=!0,s=!0;if(!1===a.getProgramParameter(_,a.LINK_STATUS))if(r=!1,"function"==typeof t.debug.onShaderError)t.debug.onShaderError(a,_,T,E);else{const t=Ls(a,T,"vertex"),n=Ls(a,E,"fragment");console.error("THREE.WebGLProgram: Shader Error "+a.getError()+" - VALIDATE_STATUS "+a.getProgramParameter(_,a.VALIDATE_STATUS)+"\n\nProgram Info Log: "+e+"\n"+t+"\n"+n)}else""!==e?console.warn("THREE.WebGLProgram: Program Info Log:",e):""!==n&&""!==i||(s=!1);s&&(this.diagnostics={runnable:r,programLog:e,vertexShader:{log:n,prefix:y},fragmentShader:{log:i,prefix:x}})}let w,A;return a.deleteShader(T),a.deleteShader(E),this.getUniforms=function(){return void 0===w&&(w=new As(a,_)),w},this.getAttributes=function(){return void 0===A&&(A=function(t,e){const n={},i=t.getProgramParameter(e,t.ACTIVE_ATTRIBUTES);for(let r=0;r0,j=s.clearcoat>0,q=s.iridescence>0,Y=s.sheen>0,Z=s.transmission>0,J=X&&!!s.anisotropyMap,K=j&&!!s.clearcoatMap,$=j&&!!s.clearcoatNormalMap,Q=j&&!!s.clearcoatRoughnessMap,tt=q&&!!s.iridescenceMap,et=q&&!!s.iridescenceThicknessMap,nt=Y&&!!s.sheenColorMap,it=Y&&!!s.sheenRoughnessMap,rt=!!s.specularMap,st=!!s.specularColorMap,at=!!s.specularIntensityMap,ot=Z&&!!s.transmissionMap,lt=Z&&!!s.thicknessMap,ct=!!s.gradientMap,ht=!!s.alphaMap,ut=s.alphaTest>0,dt=!!s.extensions,pt=!!x.attributes.uv1,mt=!!x.attributes.uv2,ft=!!x.attributes.uv3;return{isWebGL2:u,shaderID:T,shaderName:s.type,vertexShader:A,fragmentShader:R,defines:s.defines,customVertexShaderID:C,customFragmentShaderID:L,isRawShaderMaterial:!0===s.isRawShaderMaterial,glslVersion:s.glslVersion,precision:m,instancing:U,instancingColor:U&&null!==_.instanceColor,supportsVertexTextures:p,outputColorSpace:null===I?t.outputColorSpace:!0===I.isXRRenderTarget?I.texture.colorSpace:yt,map:D,matcap:N,envMap:O,envMapMode:O&&S.mapping,envMapCubeUVHeight:b,aoMap:F,lightMap:B,bumpMap:z,normalMap:G,displacementMap:p&&H,emissiveMap:k,normalMapObjectSpace:G&&1===s.normalMapType,normalMapTangentSpace:G&&0===s.normalMapType,metalnessMap:V,roughnessMap:W,anisotropy:X,anisotropyMap:J,clearcoat:j,clearcoatMap:K,clearcoatNormalMap:$,clearcoatRoughnessMap:Q,iridescence:q,iridescenceMap:tt,iridescenceThicknessMap:et,sheen:Y,sheenColorMap:nt,sheenRoughnessMap:it,specularMap:rt,specularColorMap:st,specularIntensityMap:at,transmission:Z,transmissionMap:ot,thicknessMap:lt,gradientMap:ct,opaque:!1===s.transparent&&1===s.blending,alphaMap:ht,alphaTest:ut,combine:s.combine,mapUv:D&&g(s.map.channel),aoMapUv:F&&g(s.aoMap.channel),lightMapUv:B&&g(s.lightMap.channel),bumpMapUv:z&&g(s.bumpMap.channel),normalMapUv:G&&g(s.normalMap.channel),displacementMapUv:H&&g(s.displacementMap.channel),emissiveMapUv:k&&g(s.emissiveMap.channel),metalnessMapUv:V&&g(s.metalnessMap.channel),roughnessMapUv:W&&g(s.roughnessMap.channel),anisotropyMapUv:J&&g(s.anisotropyMap.channel),clearcoatMapUv:K&&g(s.clearcoatMap.channel),clearcoatNormalMapUv:$&&g(s.clearcoatNormalMap.channel),clearcoatRoughnessMapUv:Q&&g(s.clearcoatRoughnessMap.channel),iridescenceMapUv:tt&&g(s.iridescenceMap.channel),iridescenceThicknessMapUv:et&&g(s.iridescenceThicknessMap.channel),sheenColorMapUv:nt&&g(s.sheenColorMap.channel),sheenRoughnessMapUv:it&&g(s.sheenRoughnessMap.channel),specularMapUv:rt&&g(s.specularMap.channel),specularColorMapUv:st&&g(s.specularColorMap.channel),specularIntensityMapUv:at&&g(s.specularIntensityMap.channel),transmissionMapUv:ot&&g(s.transmissionMap.channel),thicknessMapUv:lt&&g(s.thicknessMap.channel),alphaMapUv:ht&&g(s.alphaMap.channel),vertexTangents:!!x.attributes.tangent&&(G||X),vertexColors:s.vertexColors,vertexAlphas:!0===s.vertexColors&&!!x.attributes.color&&4===x.attributes.color.itemSize,vertexUv1s:pt,vertexUv2s:mt,vertexUv3s:ft,pointsUvs:!0===_.isPoints&&!!x.attributes.uv&&(D||ht),fog:!!y,useFog:!0===s.fog,fogExp2:y&&y.isFogExp2,flatShading:!0===s.flatShading,sizeAttenuation:!0===s.sizeAttenuation,logarithmicDepthBuffer:d,skinning:!0===_.isSkinnedMesh,morphTargets:void 0!==x.morphAttributes.position,morphNormals:void 0!==x.morphAttributes.normal,morphColors:void 0!==x.morphAttributes.color,morphTargetsCount:w,morphTextureStride:P,numDirLights:o.directional.length,numPointLights:o.point.length,numSpotLights:o.spot.length,numSpotLightMaps:o.spotLightMap.length,numRectAreaLights:o.rectArea.length,numHemiLights:o.hemi.length,numDirLightShadows:o.directionalShadowMap.length,numPointLightShadows:o.pointShadowMap.length,numSpotLightShadows:o.spotShadowMap.length,numSpotLightShadowsWithMaps:o.numSpotLightShadowsWithMaps,numClippingPlanes:a.numPlanes,numClipIntersection:a.numIntersection,dithering:s.dithering,shadowMapEnabled:t.shadowMap.enabled&&h.length>0,shadowMapType:t.shadowMap.type,toneMapping:s.toneMapped?t.toneMapping:0,useLegacyLights:t.useLegacyLights,premultipliedAlpha:s.premultipliedAlpha,doubleSided:2===s.side,flipSided:1===s.side,useDepthPacking:s.depthPacking>=0,depthPacking:s.depthPacking||0,index0AttributeName:s.index0AttributeName,extensionDerivatives:dt&&!0===s.extensions.derivatives,extensionFragDepth:dt&&!0===s.extensions.fragDepth,extensionDrawBuffers:dt&&!0===s.extensions.drawBuffers,extensionShaderTextureLOD:dt&&!0===s.extensions.shaderTextureLOD,rendererExtensionFragDepth:u||i.has("EXT_frag_depth"),rendererExtensionDrawBuffers:u||i.has("WEBGL_draw_buffers"),rendererExtensionShaderTextureLod:u||i.has("EXT_shader_texture_lod"),customProgramCacheKey:s.customProgramCacheKey()}},getProgramCacheKey:function(e){const n=[];if(e.shaderID?n.push(e.shaderID):(n.push(e.customVertexShaderID),n.push(e.customFragmentShaderID)),void 0!==e.defines)for(const t in e.defines)n.push(t),n.push(e.defines[t]);return!1===e.isRawShaderMaterial&&(!function(t,e){t.push(e.precision),t.push(e.outputColorSpace),t.push(e.envMapMode),t.push(e.envMapCubeUVHeight),t.push(e.mapUv),t.push(e.alphaMapUv),t.push(e.lightMapUv),t.push(e.aoMapUv),t.push(e.bumpMapUv),t.push(e.normalMapUv),t.push(e.displacementMapUv),t.push(e.emissiveMapUv),t.push(e.metalnessMapUv),t.push(e.roughnessMapUv),t.push(e.anisotropyMapUv),t.push(e.clearcoatMapUv),t.push(e.clearcoatNormalMapUv),t.push(e.clearcoatRoughnessMapUv),t.push(e.iridescenceMapUv),t.push(e.iridescenceThicknessMapUv),t.push(e.sheenColorMapUv),t.push(e.sheenRoughnessMapUv),t.push(e.specularMapUv),t.push(e.specularColorMapUv),t.push(e.specularIntensityMapUv),t.push(e.transmissionMapUv),t.push(e.thicknessMapUv),t.push(e.combine),t.push(e.fogExp2),t.push(e.sizeAttenuation),t.push(e.morphTargetsCount),t.push(e.morphAttributeCount),t.push(e.numDirLights),t.push(e.numPointLights),t.push(e.numSpotLights),t.push(e.numSpotLightMaps),t.push(e.numHemiLights),t.push(e.numRectAreaLights),t.push(e.numDirLightShadows),t.push(e.numPointLightShadows),t.push(e.numSpotLightShadows),t.push(e.numSpotLightShadowsWithMaps),t.push(e.shadowMapType),t.push(e.toneMapping),t.push(e.numClippingPlanes),t.push(e.numClipIntersection),t.push(e.depthPacking)}(n,e),function(t,e){o.disableAll(),e.isWebGL2&&o.enable(0);e.supportsVertexTextures&&o.enable(1);e.instancing&&o.enable(2);e.instancingColor&&o.enable(3);e.matcap&&o.enable(4);e.envMap&&o.enable(5);e.normalMapObjectSpace&&o.enable(6);e.normalMapTangentSpace&&o.enable(7);e.clearcoat&&o.enable(8);e.iridescence&&o.enable(9);e.alphaTest&&o.enable(10);e.vertexColors&&o.enable(11);e.vertexAlphas&&o.enable(12);e.vertexUv1s&&o.enable(13);e.vertexUv2s&&o.enable(14);e.vertexUv3s&&o.enable(15);e.vertexTangents&&o.enable(16);e.anisotropy&&o.enable(17);t.push(o.mask),o.disableAll(),e.fog&&o.enable(0);e.useFog&&o.enable(1);e.flatShading&&o.enable(2);e.logarithmicDepthBuffer&&o.enable(3);e.skinning&&o.enable(4);e.morphTargets&&o.enable(5);e.morphNormals&&o.enable(6);e.morphColors&&o.enable(7);e.premultipliedAlpha&&o.enable(8);e.shadowMapEnabled&&o.enable(9);e.useLegacyLights&&o.enable(10);e.doubleSided&&o.enable(11);e.flipSided&&o.enable(12);e.useDepthPacking&&o.enable(13);e.dithering&&o.enable(14);e.transmission&&o.enable(15);e.sheen&&o.enable(16);e.opaque&&o.enable(17);e.pointsUvs&&o.enable(18);t.push(o.mask)}(n,e),n.push(t.outputColorSpace)),n.push(e.customProgramCacheKey),n.join()},getUniforms:function(t){const e=f[t.type];let n;if(e){const t=ji[e];n=wi.clone(t.uniforms)}else n=t.uniforms;return n},acquireProgram:function(e,n){let i;for(let t=0,e=h.length;t0?i.push(h):!0===a.transparent?r.push(h):n.push(h)},unshift:function(t,e,a,o,l,c){const h=s(t,e,a,o,l,c);a.transmission>0?i.unshift(h):!0===a.transparent?r.unshift(h):n.unshift(h)},finish:function(){for(let n=e,i=t.length;n1&&n.sort(t||Zs),i.length>1&&i.sort(e||Js),r.length>1&&r.sort(e||Js)}}}function $s(){let t=new WeakMap;return{get:function(e,n){const i=t.get(e);let r;return void 0===i?(r=new Ks,t.set(e,[r])):n>=i.length?(r=new Ks,i.push(r)):r=i[n],r},dispose:function(){t=new WeakMap}}}function Qs(){const t={};return{get:function(e){if(void 0!==t[e.id])return t[e.id];let n;switch(e.type){case"DirectionalLight":n={direction:new me,color:new Nn};break;case"SpotLight":n={position:new me,direction:new me,color:new Nn,distance:0,coneCos:0,penumbraCos:0,decay:0};break;case"PointLight":n={position:new me,color:new Nn,distance:0,decay:0};break;case"HemisphereLight":n={direction:new me,skyColor:new Nn,groundColor:new Nn};break;case"RectAreaLight":n={color:new Nn,position:new me,halfWidth:new me,halfHeight:new me}}return t[e.id]=n,n}}}let ta=0;function ea(t,e){return(e.castShadow?2:0)-(t.castShadow?2:0)+(e.map?1:0)-(t.map?1:0)}function na(t,e){const n=new Qs,i=function(){const t={};return{get:function(e){if(void 0!==t[e.id])return t[e.id];let n;switch(e.type){case"DirectionalLight":case"SpotLight":n={shadowBias:0,shadowNormalBias:0,shadowRadius:1,shadowMapSize:new Gt};break;case"PointLight":n={shadowBias:0,shadowNormalBias:0,shadowRadius:1,shadowMapSize:new Gt,shadowCameraNear:1,shadowCameraFar:1e3}}return t[e.id]=n,n}}}(),r={version:0,hash:{directionalLength:-1,pointLength:-1,spotLength:-1,rectAreaLength:-1,hemiLength:-1,numDirectionalShadows:-1,numPointShadows:-1,numSpotShadows:-1,numSpotMaps:-1},ambient:[0,0,0],probe:[],directional:[],directionalShadow:[],directionalShadowMap:[],directionalShadowMatrix:[],spot:[],spotLightMap:[],spotShadow:[],spotShadowMap:[],spotLightMatrix:[],rectArea:[],rectAreaLTC1:null,rectAreaLTC2:null,point:[],pointShadow:[],pointShadowMap:[],pointShadowMatrix:[],hemi:[],numSpotLightShadowsWithMaps:0};for(let t=0;t<9;t++)r.probe.push(new me);const s=new me,a=new We,o=new We;return{setup:function(s,a){let o=0,l=0,c=0;for(let t=0;t<9;t++)r.probe[t].set(0,0,0);let h=0,u=0,d=0,p=0,m=0,f=0,g=0,v=0,_=0,y=0;s.sort(ea);const x=!0===a?Math.PI:1;for(let t=0,e=s.length;t0&&(e.isWebGL2||!0===t.has("OES_texture_float_linear")?(r.rectAreaLTC1=Xi.LTC_FLOAT_1,r.rectAreaLTC2=Xi.LTC_FLOAT_2):!0===t.has("OES_texture_half_float_linear")?(r.rectAreaLTC1=Xi.LTC_HALF_1,r.rectAreaLTC2=Xi.LTC_HALF_2):console.error("THREE.WebGLRenderer: Unable to use RectAreaLight. Missing WebGL extensions.")),r.ambient[0]=o,r.ambient[1]=l,r.ambient[2]=c;const M=r.hash;M.directionalLength===h&&M.pointLength===u&&M.spotLength===d&&M.rectAreaLength===p&&M.hemiLength===m&&M.numDirectionalShadows===f&&M.numPointShadows===g&&M.numSpotShadows===v&&M.numSpotMaps===_||(r.directional.length=h,r.spot.length=d,r.rectArea.length=p,r.point.length=u,r.hemi.length=m,r.directionalShadow.length=f,r.directionalShadowMap.length=f,r.pointShadow.length=g,r.pointShadowMap.length=g,r.spotShadow.length=v,r.spotShadowMap.length=v,r.directionalShadowMatrix.length=f,r.pointShadowMatrix.length=g,r.spotLightMatrix.length=v+_-y,r.spotLightMap.length=_,r.numSpotLightShadowsWithMaps=y,M.directionalLength=h,M.pointLength=u,M.spotLength=d,M.rectAreaLength=p,M.hemiLength=m,M.numDirectionalShadows=f,M.numPointShadows=g,M.numSpotShadows=v,M.numSpotMaps=_,r.version=ta++)},setupView:function(t,e){let n=0,i=0,l=0,c=0,h=0;const u=e.matrixWorldInverse;for(let e=0,d=t.length;e=s.length?(a=new ia(t,e),s.push(a)):a=s[r],a},dispose:function(){n=new WeakMap}}}class sa extends Ln{constructor(t){super(),this.isMeshDepthMaterial=!0,this.type="MeshDepthMaterial",this.depthPacking=3200,this.map=null,this.alphaMap=null,this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.wireframe=!1,this.wireframeLinewidth=1,this.setValues(t)}copy(t){return super.copy(t),this.depthPacking=t.depthPacking,this.map=t.map,this.alphaMap=t.alphaMap,this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this}}class aa extends Ln{constructor(t){super(),this.isMeshDistanceMaterial=!0,this.type="MeshDistanceMaterial",this.map=null,this.alphaMap=null,this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.setValues(t)}copy(t){return super.copy(t),this.map=t.map,this.alphaMap=t.alphaMap,this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this}}function oa(t,e,n){let i=new Gi;const r=new Gt,s=new Gt,a=new ce,o=new sa({depthPacking:3201}),l=new aa,c={},h=n.maxTextureSize,u={0:1,1:0,2:2},p=new Ai({defines:{VSM_SAMPLES:8},uniforms:{shadow_pass:{value:null},resolution:{value:new Gt},radius:{value:4}},vertexShader:"void main() {\n\tgl_Position = vec4( position, 1.0 );\n}",fragmentShader:"uniform sampler2D shadow_pass;\nuniform vec2 resolution;\nuniform float radius;\n#include \nvoid main() {\n\tconst float samples = float( VSM_SAMPLES );\n\tfloat mean = 0.0;\n\tfloat squared_mean = 0.0;\n\tfloat uvStride = samples <= 1.0 ? 0.0 : 2.0 / ( samples - 1.0 );\n\tfloat uvStart = samples <= 1.0 ? 0.0 : - 1.0;\n\tfor ( float i = 0.0; i < samples; i ++ ) {\n\t\tfloat uvOffset = uvStart + i * uvStride;\n\t\t#ifdef HORIZONTAL_PASS\n\t\t\tvec2 distribution = unpackRGBATo2Half( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( uvOffset, 0.0 ) * radius ) / resolution ) );\n\t\t\tmean += distribution.x;\n\t\t\tsquared_mean += distribution.y * distribution.y + distribution.x * distribution.x;\n\t\t#else\n\t\t\tfloat depth = unpackRGBAToDepth( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( 0.0, uvOffset ) * radius ) / resolution ) );\n\t\t\tmean += depth;\n\t\t\tsquared_mean += depth * depth;\n\t\t#endif\n\t}\n\tmean = mean / samples;\n\tsquared_mean = squared_mean / samples;\n\tfloat std_dev = sqrt( squared_mean - mean * mean );\n\tgl_FragColor = pack2HalfToRGBA( vec2( mean, std_dev ) );\n}"}),m=p.clone();m.defines.HORIZONTAL_PASS=1;const f=new ni;f.setAttribute("position",new Xn(new Float32Array([-1,-1,.5,3,-1,.5,-1,3,.5]),3));const g=new xi(f,p),v=this;this.enabled=!1,this.autoUpdate=!0,this.needsUpdate=!1,this.type=1;let _=this.type;function y(n,i){const s=e.update(g);p.defines.VSM_SAMPLES!==n.blurSamples&&(p.defines.VSM_SAMPLES=n.blurSamples,m.defines.VSM_SAMPLES=n.blurSamples,p.needsUpdate=!0,m.needsUpdate=!0),null===n.mapPass&&(n.mapPass=new he(r.x,r.y)),p.uniforms.shadow_pass.value=n.map.texture,p.uniforms.resolution.value=n.mapSize,p.uniforms.radius.value=n.radius,t.setRenderTarget(n.mapPass),t.clear(),t.renderBufferDirect(i,null,s,p,g,null),m.uniforms.shadow_pass.value=n.mapPass.texture,m.uniforms.resolution.value=n.mapSize,m.uniforms.radius.value=n.radius,t.setRenderTarget(n.map),t.clear(),t.renderBufferDirect(i,null,s,m,g,null)}function x(e,n,i,r){let s=null;const a=!0===i.isPointLight?e.customDistanceMaterial:e.customDepthMaterial;if(void 0!==a)s=a;else if(s=!0===i.isPointLight?l:o,t.localClippingEnabled&&!0===n.clipShadows&&Array.isArray(n.clippingPlanes)&&0!==n.clippingPlanes.length||n.displacementMap&&0!==n.displacementScale||n.alphaMap&&n.alphaTest>0||n.map&&n.alphaTest>0){const t=s.uuid,e=n.uuid;let i=c[t];void 0===i&&(i={},c[t]=i);let r=i[e];void 0===r&&(r=s.clone(),i[e]=r),s=r}if(s.visible=n.visible,s.wireframe=n.wireframe,s.side=3===r?null!==n.shadowSide?n.shadowSide:n.side:null!==n.shadowSide?n.shadowSide:u[n.side],s.alphaMap=n.alphaMap,s.alphaTest=n.alphaTest,s.map=n.map,s.clipShadows=n.clipShadows,s.clippingPlanes=n.clippingPlanes,s.clipIntersection=n.clipIntersection,s.displacementMap=n.displacementMap,s.displacementScale=n.displacementScale,s.displacementBias=n.displacementBias,s.wireframeLinewidth=n.wireframeLinewidth,s.linewidth=n.linewidth,!0===i.isPointLight&&!0===s.isMeshDistanceMaterial){t.properties.get(s).light=i}return s}function M(n,r,s,a,o){if(!1===n.visible)return;if(n.layers.test(r.layers)&&(n.isMesh||n.isLine||n.isPoints)&&(n.castShadow||n.receiveShadow&&3===o)&&(!n.frustumCulled||i.intersectsObject(n))){n.modelViewMatrix.multiplyMatrices(s.matrixWorldInverse,n.matrixWorld);const i=e.update(n),r=n.material;if(Array.isArray(r)){const e=i.groups;for(let l=0,c=e.length;lh||r.y>h)&&(r.x>h&&(s.x=Math.floor(h/g.x),r.x=s.x*g.x,u.mapSize.x=s.x),r.y>h&&(s.y=Math.floor(h/g.y),r.y=s.y*g.y,u.mapSize.y=s.y)),null===u.map||!0===m||!0===f){const t=3!==this.type?{minFilter:d,magFilter:d}:{};null!==u.map&&u.map.dispose(),u.map=new he(r.x,r.y,t),u.map.texture.name=c.name+".shadowMap",u.camera.updateProjectionMatrix()}t.setRenderTarget(u.map),t.clear();const v=u.getViewportCount();for(let t=0;t=1):-1!==I.indexOf("OpenGL ES")&&(P=parseFloat(/^OpenGL ES (\d)/.exec(I)[1]),L=P>=2);let U=null,D={};const N=t.getParameter(t.SCISSOR_BOX),O=t.getParameter(t.VIEWPORT),F=(new ce).fromArray(N),B=(new ce).fromArray(O);function z(e,n,i,s){const a=new Uint8Array(4),o=t.createTexture();t.bindTexture(e,o),t.texParameteri(e,t.TEXTURE_MIN_FILTER,t.NEAREST),t.texParameteri(e,t.TEXTURE_MAG_FILTER,t.NEAREST);for(let o=0;oi||t.height>i)&&(r=i/Math.max(t.width,t.height)),r<1||!0===e){if("undefined"!=typeof HTMLImageElement&&t instanceof HTMLImageElement||"undefined"!=typeof HTMLCanvasElement&&t instanceof HTMLCanvasElement||"undefined"!=typeof ImageBitmap&&t instanceof ImageBitmap){const i=e?Ot:Math.floor,s=i(r*t.width),a=i(r*t.height);void 0===U&&(U=O(s,a));const o=n?O(s,a):U;o.width=s,o.height=a;return o.getContext("2d").drawImage(t,0,0,s,a),console.warn("THREE.WebGLRenderer: Texture has been resized from ("+t.width+"x"+t.height+") to ("+s+"x"+a+")."),o}return"data"in t&&console.warn("THREE.WebGLRenderer: Image in DataTexture is too big ("+t.width+"x"+t.height+")."),t}return t}function B(t){return Dt(t.width)&&Dt(t.height)}function z(t,e){return t.generateMipmaps&&e&&t.minFilter!==d&&t.minFilter!==f}function G(e){t.generateMipmap(e)}function H(n,i,r,s,a=!1){if(!1===o)return i;if(null!==n){if(void 0!==t[n])return t[n];console.warn("THREE.WebGLRenderer: Attempt to use non-existing WebGL internal format '"+n+"'")}let l=i;return i===t.RED&&(r===t.FLOAT&&(l=t.R32F),r===t.HALF_FLOAT&&(l=t.R16F),r===t.UNSIGNED_BYTE&&(l=t.R8)),i===t.RG&&(r===t.FLOAT&&(l=t.RG32F),r===t.HALF_FLOAT&&(l=t.RG16F),r===t.UNSIGNED_BYTE&&(l=t.RG8)),i===t.RGBA&&(r===t.FLOAT&&(l=t.RGBA32F),r===t.HALF_FLOAT&&(l=t.RGBA16F),r===t.UNSIGNED_BYTE&&(l=s===_t&&!1===a?t.SRGB8_ALPHA8:t.RGBA8),r===t.UNSIGNED_SHORT_4_4_4_4&&(l=t.RGBA4),r===t.UNSIGNED_SHORT_5_5_5_1&&(l=t.RGB5_A1)),l!==t.R16F&&l!==t.R32F&&l!==t.RG16F&&l!==t.RG32F&&l!==t.RGBA16F&&l!==t.RGBA32F||e.get("EXT_color_buffer_float"),l}function k(t,e,n){return!0===z(t,n)||t.isFramebufferTexture&&t.minFilter!==d&&t.minFilter!==f?Math.log2(Math.max(e.width,e.height))+1:void 0!==t.mipmaps&&t.mipmaps.length>0?t.mipmaps.length:t.isCompressedTexture&&Array.isArray(t.image)?e.mipmaps.length:1}function V(e){return e===d||e===p||e===m?t.NEAREST:t.LINEAR}function W(t){const e=t.target;e.removeEventListener("dispose",W),function(t){const e=i.get(t);if(void 0===e.__webglInit)return;const n=t.source,r=D.get(n);if(r){const i=r[e.__cacheKey];i.usedTimes--,0===i.usedTimes&&j(t),0===Object.keys(r).length&&D.delete(n)}i.remove(t)}(e),e.isVideoTexture&&I.delete(e)}function X(e){const n=e.target;n.removeEventListener("dispose",X),function(e){const n=e.texture,r=i.get(e),s=i.get(n);void 0!==s.__webglTexture&&(t.deleteTexture(s.__webglTexture),a.memory.textures--);e.depthTexture&&e.depthTexture.dispose();if(e.isWebGLCubeRenderTarget)for(let e=0;e<6;e++)t.deleteFramebuffer(r.__webglFramebuffer[e]),r.__webglDepthbuffer&&t.deleteRenderbuffer(r.__webglDepthbuffer[e]);else{if(t.deleteFramebuffer(r.__webglFramebuffer),r.__webglDepthbuffer&&t.deleteRenderbuffer(r.__webglDepthbuffer),r.__webglMultisampledFramebuffer&&t.deleteFramebuffer(r.__webglMultisampledFramebuffer),r.__webglColorRenderbuffer)for(let e=0;e0&&s.__version!==e.version){const t=e.image;if(null===t)console.warn("THREE.WebGLRenderer: Texture marked for update but no image data found.");else{if(!1!==t.complete)return void tt(s,e,r);console.warn("THREE.WebGLRenderer: Texture marked for update but image is incomplete")}}n.bindTexture(t.TEXTURE_2D,s.__webglTexture,t.TEXTURE0+r)}const Z={[c]:t.REPEAT,[h]:t.CLAMP_TO_EDGE,[u]:t.MIRRORED_REPEAT},J={[d]:t.NEAREST,[p]:t.NEAREST_MIPMAP_NEAREST,[m]:t.NEAREST_MIPMAP_LINEAR,[f]:t.LINEAR,[g]:t.LINEAR_MIPMAP_NEAREST,[v]:t.LINEAR_MIPMAP_LINEAR},K={512:t.NEVER,519:t.ALWAYS,513:t.LESS,515:t.LEQUAL,514:t.EQUAL,518:t.GEQUAL,516:t.GREATER,517:t.NOTEQUAL};function $(n,s,a){if(a?(t.texParameteri(n,t.TEXTURE_WRAP_S,Z[s.wrapS]),t.texParameteri(n,t.TEXTURE_WRAP_T,Z[s.wrapT]),n!==t.TEXTURE_3D&&n!==t.TEXTURE_2D_ARRAY||t.texParameteri(n,t.TEXTURE_WRAP_R,Z[s.wrapR]),t.texParameteri(n,t.TEXTURE_MAG_FILTER,J[s.magFilter]),t.texParameteri(n,t.TEXTURE_MIN_FILTER,J[s.minFilter])):(t.texParameteri(n,t.TEXTURE_WRAP_S,t.CLAMP_TO_EDGE),t.texParameteri(n,t.TEXTURE_WRAP_T,t.CLAMP_TO_EDGE),n!==t.TEXTURE_3D&&n!==t.TEXTURE_2D_ARRAY||t.texParameteri(n,t.TEXTURE_WRAP_R,t.CLAMP_TO_EDGE),s.wrapS===h&&s.wrapT===h||console.warn("THREE.WebGLRenderer: Texture is not power of two. Texture.wrapS and Texture.wrapT should be set to THREE.ClampToEdgeWrapping."),t.texParameteri(n,t.TEXTURE_MAG_FILTER,V(s.magFilter)),t.texParameteri(n,t.TEXTURE_MIN_FILTER,V(s.minFilter)),s.minFilter!==d&&s.minFilter!==f&&console.warn("THREE.WebGLRenderer: Texture is not power of two. Texture.minFilter should be set to THREE.NearestFilter or THREE.LinearFilter.")),s.compareFunction&&(t.texParameteri(n,t.TEXTURE_COMPARE_MODE,t.COMPARE_REF_TO_TEXTURE),t.texParameteri(n,t.TEXTURE_COMPARE_FUNC,K[s.compareFunction])),!0===e.has("EXT_texture_filter_anisotropic")){const a=e.get("EXT_texture_filter_anisotropic");if(s.magFilter===d)return;if(s.minFilter!==m&&s.minFilter!==v)return;if(s.type===S&&!1===e.has("OES_texture_float_linear"))return;if(!1===o&&s.type===b&&!1===e.has("OES_texture_half_float_linear"))return;(s.anisotropy>1||i.get(s).__currentAnisotropy)&&(t.texParameterf(n,a.TEXTURE_MAX_ANISOTROPY_EXT,Math.min(s.anisotropy,r.getMaxAnisotropy())),i.get(s).__currentAnisotropy=s.anisotropy)}}function Q(e,n){let i=!1;void 0===e.__webglInit&&(e.__webglInit=!0,n.addEventListener("dispose",W));const r=n.source;let s=D.get(r);void 0===s&&(s={},D.set(r,s));const o=function(t){const e=[];return e.push(t.wrapS),e.push(t.wrapT),e.push(t.wrapR||0),e.push(t.magFilter),e.push(t.minFilter),e.push(t.anisotropy),e.push(t.internalFormat),e.push(t.format),e.push(t.type),e.push(t.generateMipmaps),e.push(t.premultiplyAlpha),e.push(t.flipY),e.push(t.unpackAlignment),e.push(t.colorSpace),e.join()}(n);if(o!==e.__cacheKey){void 0===s[o]&&(s[o]={texture:t.createTexture(),usedTimes:0},a.memory.textures++,i=!0),s[o].usedTimes++;const r=s[e.__cacheKey];void 0!==r&&(s[e.__cacheKey].usedTimes--,0===r.usedTimes&&j(n)),e.__cacheKey=o,e.__webglTexture=s[o].texture}return i}function tt(e,r,a){let l=t.TEXTURE_2D;(r.isDataArrayTexture||r.isCompressedArrayTexture)&&(l=t.TEXTURE_2D_ARRAY),r.isData3DTexture&&(l=t.TEXTURE_3D);const c=Q(e,r),u=r.source;n.bindTexture(l,e.__webglTexture,t.TEXTURE0+a);const p=i.get(u);if(u.version!==p.__version||!0===c){n.activeTexture(t.TEXTURE0+a),t.pixelStorei(t.UNPACK_FLIP_Y_WEBGL,r.flipY),t.pixelStorei(t.UNPACK_PREMULTIPLY_ALPHA_WEBGL,r.premultiplyAlpha),t.pixelStorei(t.UNPACK_ALIGNMENT,r.unpackAlignment),t.pixelStorei(t.UNPACK_COLORSPACE_CONVERSION_WEBGL,t.NONE);const e=function(t){return!o&&(t.wrapS!==h||t.wrapT!==h||t.minFilter!==d&&t.minFilter!==f)}(r)&&!1===B(r.image);let i=F(r.image,e,!1,T);i=at(r,i);const m=B(i)||o,g=s.convert(r.format,r.colorSpace);let v,_=s.convert(r.type),x=H(r.internalFormat,g,_,r.colorSpace);$(l,r,m);const b=r.mipmaps,E=o&&!0!==r.isVideoTexture,L=void 0===p.__version||!0===c,P=k(r,i,m);if(r.isDepthTexture)x=t.DEPTH_COMPONENT,o?x=r.type===S?t.DEPTH_COMPONENT32F:r.type===M?t.DEPTH_COMPONENT24:r.type===w?t.DEPTH24_STENCIL8:t.DEPTH_COMPONENT16:r.type===S&&console.error("WebGLRenderer: Floating point depth texture requires WebGL2."),r.format===R&&x===t.DEPTH_COMPONENT&&r.type!==y&&r.type!==M&&(console.warn("THREE.WebGLRenderer: Use UnsignedShortType or UnsignedIntType for DepthFormat DepthTexture."),r.type=M,_=s.convert(r.type)),r.format===C&&x===t.DEPTH_COMPONENT&&(x=t.DEPTH_STENCIL,r.type!==w&&(console.warn("THREE.WebGLRenderer: Use UnsignedInt248Type for DepthStencilFormat DepthTexture."),r.type=w,_=s.convert(r.type))),L&&(E?n.texStorage2D(t.TEXTURE_2D,1,x,i.width,i.height):n.texImage2D(t.TEXTURE_2D,0,x,i.width,i.height,0,g,_,null));else if(r.isDataTexture)if(b.length>0&&m){E&&L&&n.texStorage2D(t.TEXTURE_2D,P,x,b[0].width,b[0].height);for(let e=0,i=b.length;e>=1,r>>=1}}else if(b.length>0&&m){E&&L&&n.texStorage2D(t.TEXTURE_2D,P,x,b[0].width,b[0].height);for(let e=0,i=b.length;e=t.TEXTURE_CUBE_MAP_POSITIVE_X&&l<=t.TEXTURE_CUBE_MAP_NEGATIVE_Z)&&t.framebufferTexture2D(t.FRAMEBUFFER,o,l,i.get(a).__webglTexture,0),n.bindFramebuffer(t.FRAMEBUFFER,null)}function nt(e,n,i){if(t.bindRenderbuffer(t.RENDERBUFFER,e),n.depthBuffer&&!n.stencilBuffer){let r=t.DEPTH_COMPONENT16;if(i||st(n)){const e=n.depthTexture;e&&e.isDepthTexture&&(e.type===S?r=t.DEPTH_COMPONENT32F:e.type===M&&(r=t.DEPTH_COMPONENT24));const i=rt(n);st(n)?L.renderbufferStorageMultisampleEXT(t.RENDERBUFFER,i,r,n.width,n.height):t.renderbufferStorageMultisample(t.RENDERBUFFER,i,r,n.width,n.height)}else t.renderbufferStorage(t.RENDERBUFFER,r,n.width,n.height);t.framebufferRenderbuffer(t.FRAMEBUFFER,t.DEPTH_ATTACHMENT,t.RENDERBUFFER,e)}else if(n.depthBuffer&&n.stencilBuffer){const r=rt(n);i&&!1===st(n)?t.renderbufferStorageMultisample(t.RENDERBUFFER,r,t.DEPTH24_STENCIL8,n.width,n.height):st(n)?L.renderbufferStorageMultisampleEXT(t.RENDERBUFFER,r,t.DEPTH24_STENCIL8,n.width,n.height):t.renderbufferStorage(t.RENDERBUFFER,t.DEPTH_STENCIL,n.width,n.height),t.framebufferRenderbuffer(t.FRAMEBUFFER,t.DEPTH_STENCIL_ATTACHMENT,t.RENDERBUFFER,e)}else{const e=!0===n.isWebGLMultipleRenderTargets?n.texture:[n.texture];for(let r=0;r0&&!0===e.has("WEBGL_multisampled_render_to_texture")&&!1!==n.__useRenderToTexture}function at(t,n){const i=t.colorSpace,r=t.format,s=t.type;return!0===t.isCompressedTexture||t.format===Tt||i!==yt&&i!==vt&&(i===_t?!1===o?!0===e.has("EXT_sRGB")&&r===A?(t.format=Tt,t.minFilter=f,t.generateMipmaps=!1):n=ie.sRGBToLinear(n):r===A&&s===_||console.warn("THREE.WebGLTextures: sRGB encoded textures have to use RGBAFormat and UnsignedByteType."):console.error("THREE.WebGLTextures: Unsupported texture color space:",i)),n}this.allocateTextureUnit=function(){const t=q;return t>=l&&console.warn("THREE.WebGLTextures: Trying to use "+t+" texture units while this GPU supports only "+l),q+=1,t},this.resetTextureUnits=function(){q=0},this.setTexture2D=Y,this.setTexture2DArray=function(e,r){const s=i.get(e);e.version>0&&s.__version!==e.version?tt(s,e,r):n.bindTexture(t.TEXTURE_2D_ARRAY,s.__webglTexture,t.TEXTURE0+r)},this.setTexture3D=function(e,r){const s=i.get(e);e.version>0&&s.__version!==e.version?tt(s,e,r):n.bindTexture(t.TEXTURE_3D,s.__webglTexture,t.TEXTURE0+r)},this.setTextureCube=function(e,r){const a=i.get(e);e.version>0&&a.__version!==e.version?function(e,r,a){if(6!==r.image.length)return;const l=Q(e,r),c=r.source;n.bindTexture(t.TEXTURE_CUBE_MAP,e.__webglTexture,t.TEXTURE0+a);const h=i.get(c);if(c.version!==h.__version||!0===l){n.activeTexture(t.TEXTURE0+a),t.pixelStorei(t.UNPACK_FLIP_Y_WEBGL,r.flipY),t.pixelStorei(t.UNPACK_PREMULTIPLY_ALPHA_WEBGL,r.premultiplyAlpha),t.pixelStorei(t.UNPACK_ALIGNMENT,r.unpackAlignment),t.pixelStorei(t.UNPACK_COLORSPACE_CONVERSION_WEBGL,t.NONE);const e=r.isCompressedTexture||r.image[0].isCompressedTexture,i=r.image[0]&&r.image[0].isDataTexture,u=[];for(let t=0;t<6;t++)u[t]=e||i?i?r.image[t].image:r.image[t]:F(r.image[t],!1,!0,x),u[t]=at(r,u[t]);const d=u[0],p=B(d)||o,m=s.convert(r.format,r.colorSpace),f=s.convert(r.type),g=H(r.internalFormat,m,f,r.colorSpace),v=o&&!0!==r.isVideoTexture,_=void 0===h.__version||!0===l;let y,M=k(r,d,p);if($(t.TEXTURE_CUBE_MAP,r,p),e){v&&_&&n.texStorage2D(t.TEXTURE_CUBE_MAP,M,g,d.width,d.height);for(let e=0;e<6;e++){y=u[e].mipmaps;for(let i=0;i0&&M++,n.texStorage2D(t.TEXTURE_CUBE_MAP,M,g,u[0].width,u[0].height));for(let e=0;e<6;e++)if(i){v?n.texSubImage2D(t.TEXTURE_CUBE_MAP_POSITIVE_X+e,0,0,0,u[e].width,u[e].height,m,f,u[e].data):n.texImage2D(t.TEXTURE_CUBE_MAP_POSITIVE_X+e,0,g,u[e].width,u[e].height,0,m,f,u[e].data);for(let i=0;i0&&!1===st(e)){const i=d?l:[l];c.__webglMultisampledFramebuffer=t.createFramebuffer(),c.__webglColorRenderbuffer=[],n.bindFramebuffer(t.FRAMEBUFFER,c.__webglMultisampledFramebuffer);for(let n=0;n0&&!1===st(e)){const r=e.isWebGLMultipleRenderTargets?e.texture:[e.texture],s=e.width,a=e.height;let o=t.COLOR_BUFFER_BIT;const l=[],c=e.stencilBuffer?t.DEPTH_STENCIL_ATTACHMENT:t.DEPTH_ATTACHMENT,h=i.get(e),u=!0===e.isWebGLMultipleRenderTargets;if(u)for(let e=0;eo+c?(l.inputState.pinching=!1,this.dispatchEvent({type:"pinchend",handedness:t.handedness,target:this})):!l.inputState.pinching&&a<=o-c&&(l.inputState.pinching=!0,this.dispatchEvent({type:"pinchstart",handedness:t.handedness,target:this}))}else null!==o&&t.gripSpace&&(r=e.getPose(t.gripSpace,n),null!==r&&(o.matrix.fromArray(r.transform.matrix),o.matrix.decompose(o.position,o.rotation,o.scale),o.matrixWorldNeedsUpdate=!0,r.linearVelocity?(o.hasLinearVelocity=!0,o.linearVelocity.copy(r.linearVelocity)):o.hasLinearVelocity=!1,r.angularVelocity?(o.hasAngularVelocity=!0,o.angularVelocity.copy(r.angularVelocity)):o.hasAngularVelocity=!1));null!==a&&(i=e.getPose(t.targetRaySpace,n),null===i&&null!==r&&(i=r),null!==i&&(a.matrix.fromArray(i.transform.matrix),a.matrix.decompose(a.position,a.rotation,a.scale),a.matrixWorldNeedsUpdate=!0,i.linearVelocity?(a.hasLinearVelocity=!0,a.linearVelocity.copy(i.linearVelocity)):a.hasLinearVelocity=!1,i.angularVelocity?(a.hasAngularVelocity=!0,a.angularVelocity.copy(i.angularVelocity)):a.hasAngularVelocity=!1,this.dispatchEvent(pa)))}return null!==a&&(a.visible=null!==i),null!==o&&(o.visible=null!==r),null!==l&&(l.visible=null!==s),this}_getHandJoint(t,e){if(void 0===t.joints[e.jointName]){const n=new da;n.matrixAutoUpdate=!1,n.visible=!1,t.joints[e.jointName]=n,t.add(n)}return t.joints[e.jointName]}}class fa extends le{constructor(t,e,n,i,r,s,a,o,l,c){if((c=void 0!==c?c:R)!==R&&c!==C)throw new Error("DepthTexture format must be either THREE.DepthFormat or THREE.DepthStencilFormat");void 0===n&&c===R&&(n=M),void 0===n&&c===C&&(n=w),super(null,i,r,s,a,o,c,n,l),this.isDepthTexture=!0,this.image={width:t,height:e},this.magFilter=void 0!==a?a:d,this.minFilter=void 0!==o?o:d,this.flipY=!1,this.generateMipmaps=!1,this.compareFunction=null}copy(t){return super.copy(t),this.compareFunction=t.compareFunction,this}toJSON(t){const e=super.toJSON(t);return null!==this.compareFunction&&(e.compareFunction=this.compareFunction),e}}class ga extends Et{constructor(t,e){super();const n=this;let i=null,r=1,s=null,a="local-floor",o=1,l=null,c=null,h=null,u=null,d=null,p=null;const m=e.getContextAttributes();let f=null,g=null;const v=[],y=[],x=new Set,S=new Map;let b=null;const T=new Ci;T.layers.enable(1),T.viewport=new ce;const E=new Ci;E.layers.enable(2),E.viewport=new ce;const L=[T,E],P=new ua;P.layers.enable(1),P.layers.enable(2);let I=null,U=null;function D(t){const e=y.indexOf(t.inputSource);if(-1===e)return;const n=v[e];void 0!==n&&(n.update(t.inputSource,t.frame,l||s),n.dispatchEvent({type:t.type,data:t.inputSource}))}function N(){i.removeEventListener("select",D),i.removeEventListener("selectstart",D),i.removeEventListener("selectend",D),i.removeEventListener("squeeze",D),i.removeEventListener("squeezestart",D),i.removeEventListener("squeezeend",D),i.removeEventListener("end",N),i.removeEventListener("inputsourceschange",O);for(let t=0;t=0&&(y[i]=null,v[i].disconnect(n))}for(let e=0;e=y.length){y.push(n),i=t;break}if(null===y[t]){y[t]=n,i=t;break}}if(-1===i)break}const r=v[i];r&&r.connect(n)}}this.cameraAutoUpdate=!0,this.enabled=!1,this.isPresenting=!1,this.getCamera=function(){},this.setUserCamera=function(t){b=t},this.getController=function(t){let e=v[t];return void 0===e&&(e=new ma,v[t]=e),e.getTargetRaySpace()},this.getControllerGrip=function(t){let e=v[t];return void 0===e&&(e=new ma,v[t]=e),e.getGripSpace()},this.getHand=function(t){let e=v[t];return void 0===e&&(e=new ma,v[t]=e),e.getHandSpace()},this.setFramebufferScaleFactor=function(t){r=t,!0===n.isPresenting&&console.warn("THREE.WebXRManager: Cannot change framebuffer scale while presenting.")},this.setReferenceSpaceType=function(t){a=t,!0===n.isPresenting&&console.warn("THREE.WebXRManager: Cannot change reference space type while presenting.")},this.getReferenceSpace=function(){return l||s},this.setReferenceSpace=function(t){l=t},this.getBaseLayer=function(){return null!==u?u:d},this.getBinding=function(){return h},this.getFrame=function(){return p},this.getSession=function(){return i},this.setSession=async function(c){if(i=c,null!==i){if(f=t.getRenderTarget(),i.addEventListener("select",D),i.addEventListener("selectstart",D),i.addEventListener("selectend",D),i.addEventListener("squeeze",D),i.addEventListener("squeezestart",D),i.addEventListener("squeezeend",D),i.addEventListener("end",N),i.addEventListener("inputsourceschange",O),!0!==m.xrCompatible&&await e.makeXRCompatible(),void 0===i.renderState.layers||!1===t.capabilities.isWebGL2){const n={antialias:void 0!==i.renderState.layers||m.antialias,alpha:!0,depth:m.depth,stencil:m.stencil,framebufferScaleFactor:r};d=new XRWebGLLayer(i,e,n),i.updateRenderState({baseLayer:d}),g=new he(d.framebufferWidth,d.framebufferHeight,{format:A,type:_,colorSpace:t.outputColorSpace,stencilBuffer:m.stencil})}else{let n=null,s=null,a=null;m.depth&&(a=m.stencil?e.DEPTH24_STENCIL8:e.DEPTH_COMPONENT24,n=m.stencil?C:R,s=m.stencil?w:M);const o={colorFormat:e.RGBA8,depthFormat:a,scaleFactor:r};h=new XRWebGLBinding(i,e),u=h.createProjectionLayer(o),i.updateRenderState({layers:[u]}),g=new he(u.textureWidth,u.textureHeight,{format:A,type:_,depthTexture:new fa(u.textureWidth,u.textureHeight,s,void 0,void 0,void 0,void 0,void 0,void 0,n),stencilBuffer:m.stencil,colorSpace:t.outputColorSpace,samples:m.antialias?4:0});t.properties.get(g).__ignoreDepthValues=u.ignoreDepthValues}g.isXRRenderTarget=!0,this.setFoveation(o),l=null,s=await i.requestReferenceSpace(a),H.setContext(i),H.start(),n.isPresenting=!0,n.dispatchEvent({type:"sessionstart"})}},this.getEnvironmentBlendMode=function(){if(null!==i)return i.environmentBlendMode};const F=new me,B=new me;function z(t,e){null===e?t.matrixWorld.copy(t.matrix):t.matrixWorld.multiplyMatrices(e.matrixWorld,t.matrix),t.matrixWorldInverse.copy(t.matrixWorld).invert()}this.updateCameraXR=function(t){if(null===i)return t;b&&(t=b),P.near=E.near=T.near=t.near,P.far=E.far=T.far=t.far,I===P.near&&U===P.far||(i.updateRenderState({depthNear:P.near,depthFar:P.far}),I=P.near,U=P.far);const e=t.parent,n=P.cameras;z(P,e);for(let t=0;te&&(S.set(t,t.lastChangedTime),n.dispatchEvent({type:"planechanged",data:t}))}else x.add(t),S.set(t,i.lastChangedTime),n.dispatchEvent({type:"planeadded",data:t})}p=null})),this.setAnimationLoop=function(t){G=t},this.dispose=function(){}}}function va(t,e){function n(t,e){!0===t.matrixAutoUpdate&&t.updateMatrix(),e.value.copy(t.matrix)}function i(i,r){i.opacity.value=r.opacity,r.color&&i.diffuse.value.copy(r.color),r.emissive&&i.emissive.value.copy(r.emissive).multiplyScalar(r.emissiveIntensity),r.map&&(i.map.value=r.map,n(r.map,i.mapTransform)),r.alphaMap&&(i.alphaMap.value=r.alphaMap,n(r.alphaMap,i.alphaMapTransform)),r.bumpMap&&(i.bumpMap.value=r.bumpMap,n(r.bumpMap,i.bumpMapTransform),i.bumpScale.value=r.bumpScale,1===r.side&&(i.bumpScale.value*=-1)),r.normalMap&&(i.normalMap.value=r.normalMap,n(r.normalMap,i.normalMapTransform),i.normalScale.value.copy(r.normalScale),1===r.side&&i.normalScale.value.negate()),r.displacementMap&&(i.displacementMap.value=r.displacementMap,n(r.displacementMap,i.displacementMapTransform),i.displacementScale.value=r.displacementScale,i.displacementBias.value=r.displacementBias),r.emissiveMap&&(i.emissiveMap.value=r.emissiveMap,n(r.emissiveMap,i.emissiveMapTransform)),r.specularMap&&(i.specularMap.value=r.specularMap,n(r.specularMap,i.specularMapTransform)),r.alphaTest>0&&(i.alphaTest.value=r.alphaTest);const s=e.get(r).envMap;if(s&&(i.envMap.value=s,i.flipEnvMap.value=s.isCubeTexture&&!1===s.isRenderTargetTexture?-1:1,i.reflectivity.value=r.reflectivity,i.ior.value=r.ior,i.refractionRatio.value=r.refractionRatio),r.lightMap){i.lightMap.value=r.lightMap;const e=!0===t.useLegacyLights?Math.PI:1;i.lightMapIntensity.value=r.lightMapIntensity*e,n(r.lightMap,i.lightMapTransform)}r.aoMap&&(i.aoMap.value=r.aoMap,i.aoMapIntensity.value=r.aoMapIntensity,n(r.aoMap,i.aoMapTransform))}return{refreshFogUniforms:function(e,n){n.color.getRGB(e.fogColor.value,Ei(t)),n.isFog?(e.fogNear.value=n.near,e.fogFar.value=n.far):n.isFogExp2&&(e.fogDensity.value=n.density)},refreshMaterialUniforms:function(t,r,s,a,o){r.isMeshBasicMaterial||r.isMeshLambertMaterial?i(t,r):r.isMeshToonMaterial?(i(t,r),function(t,e){e.gradientMap&&(t.gradientMap.value=e.gradientMap)}(t,r)):r.isMeshPhongMaterial?(i(t,r),function(t,e){t.specular.value.copy(e.specular),t.shininess.value=Math.max(e.shininess,1e-4)}(t,r)):r.isMeshStandardMaterial?(i(t,r),function(t,i){t.metalness.value=i.metalness,i.metalnessMap&&(t.metalnessMap.value=i.metalnessMap,n(i.metalnessMap,t.metalnessMapTransform));t.roughness.value=i.roughness,i.roughnessMap&&(t.roughnessMap.value=i.roughnessMap,n(i.roughnessMap,t.roughnessMapTransform));const r=e.get(i).envMap;r&&(t.envMapIntensity.value=i.envMapIntensity)}(t,r),r.isMeshPhysicalMaterial&&function(t,e,i){t.ior.value=e.ior,e.sheen>0&&(t.sheenColor.value.copy(e.sheenColor).multiplyScalar(e.sheen),t.sheenRoughness.value=e.sheenRoughness,e.sheenColorMap&&(t.sheenColorMap.value=e.sheenColorMap,n(e.sheenColorMap,t.sheenColorMapTransform)),e.sheenRoughnessMap&&(t.sheenRoughnessMap.value=e.sheenRoughnessMap,n(e.sheenRoughnessMap,t.sheenRoughnessMapTransform)));e.clearcoat>0&&(t.clearcoat.value=e.clearcoat,t.clearcoatRoughness.value=e.clearcoatRoughness,e.clearcoatMap&&(t.clearcoatMap.value=e.clearcoatMap,n(e.clearcoatMap,t.clearcoatMapTransform)),e.clearcoatRoughnessMap&&(t.clearcoatRoughnessMap.value=e.clearcoatRoughnessMap,n(e.clearcoatRoughnessMap,t.clearcoatRoughnessMapTransform)),e.clearcoatNormalMap&&(t.clearcoatNormalMap.value=e.clearcoatNormalMap,n(e.clearcoatNormalMap,t.clearcoatNormalMapTransform),t.clearcoatNormalScale.value.copy(e.clearcoatNormalScale),1===e.side&&t.clearcoatNormalScale.value.negate()));e.iridescence>0&&(t.iridescence.value=e.iridescence,t.iridescenceIOR.value=e.iridescenceIOR,t.iridescenceThicknessMinimum.value=e.iridescenceThicknessRange[0],t.iridescenceThicknessMaximum.value=e.iridescenceThicknessRange[1],e.iridescenceMap&&(t.iridescenceMap.value=e.iridescenceMap,n(e.iridescenceMap,t.iridescenceMapTransform)),e.iridescenceThicknessMap&&(t.iridescenceThicknessMap.value=e.iridescenceThicknessMap,n(e.iridescenceThicknessMap,t.iridescenceThicknessMapTransform)));e.transmission>0&&(t.transmission.value=e.transmission,t.transmissionSamplerMap.value=i.texture,t.transmissionSamplerSize.value.set(i.width,i.height),e.transmissionMap&&(t.transmissionMap.value=e.transmissionMap,n(e.transmissionMap,t.transmissionMapTransform)),t.thickness.value=e.thickness,e.thicknessMap&&(t.thicknessMap.value=e.thicknessMap,n(e.thicknessMap,t.thicknessMapTransform)),t.attenuationDistance.value=e.attenuationDistance,t.attenuationColor.value.copy(e.attenuationColor));e.anisotropy>0&&(t.anisotropyVector.value.set(e.anisotropy*Math.cos(e.anisotropyRotation),e.anisotropy*Math.sin(e.anisotropyRotation)),e.anisotropyMap&&(t.anisotropyMap.value=e.anisotropyMap));t.specularIntensity.value=e.specularIntensity,t.specularColor.value.copy(e.specularColor),e.specularColorMap&&(t.specularColorMap.value=e.specularColorMap,n(e.specularColorMap,t.specularColorMapTransform));e.specularIntensityMap&&(t.specularIntensityMap.value=e.specularIntensityMap,n(e.specularIntensityMap,t.specularIntensityMapTransform))}(t,r,o)):r.isMeshMatcapMaterial?(i(t,r),function(t,e){e.matcap&&(t.matcap.value=e.matcap)}(t,r)):r.isMeshDepthMaterial?i(t,r):r.isMeshDistanceMaterial?(i(t,r),function(t,n){const i=e.get(n).light;t.referencePosition.value.setFromMatrixPosition(i.matrixWorld),t.nearDistance.value=i.shadow.camera.near,t.farDistance.value=i.shadow.camera.far}(t,r)):r.isMeshNormalMaterial?i(t,r):r.isLineBasicMaterial?(function(t,e){t.diffuse.value.copy(e.color),t.opacity.value=e.opacity,e.map&&(t.map.value=e.map,n(e.map,t.mapTransform))}(t,r),r.isLineDashedMaterial&&function(t,e){t.dashSize.value=e.dashSize,t.totalSize.value=e.dashSize+e.gapSize,t.scale.value=e.scale}(t,r)):r.isPointsMaterial?function(t,e,i,r){t.diffuse.value.copy(e.color),t.opacity.value=e.opacity,t.size.value=e.size*i,t.scale.value=.5*r,e.map&&(t.map.value=e.map,n(e.map,t.uvTransform));e.alphaMap&&(t.alphaMap.value=e.alphaMap);e.alphaTest>0&&(t.alphaTest.value=e.alphaTest)}(t,r,s,a):r.isSpriteMaterial?function(t,e){t.diffuse.value.copy(e.color),t.opacity.value=e.opacity,t.rotation.value=e.rotation,e.map&&(t.map.value=e.map,n(e.map,t.mapTransform));e.alphaMap&&(t.alphaMap.value=e.alphaMap);e.alphaTest>0&&(t.alphaTest.value=e.alphaTest)}(t,r):r.isShadowMaterial?(t.color.value.copy(r.color),t.opacity.value=r.opacity):r.isShaderMaterial&&(r.uniformsNeedUpdate=!1)}}}function _a(t,e,n,i){let r={},s={},a=[];const o=n.isWebGL2?t.getParameter(t.MAX_UNIFORM_BUFFER_BINDINGS):0;function l(t,e,n){const i=t.value;if(void 0===n[e]){if("number"==typeof i)n[e]=i;else{const t=Array.isArray(i)?i:[i],r=[];for(let e=0;e0){r=n%i;0!==r&&i-r-a.boundary<0&&(n+=i-r,s.__offset=n)}n+=a.storage}r=n%i,r>0&&(n+=i-r);t.__size=n,t.__cache={}}(n),d=function(e){const n=function(){for(let t=0;t0),u=!!n.morphAttributes.position,d=!!n.morphAttributes.normal,p=!!n.morphAttributes.color,m=i.toneMapped?C.toneMapping:0,f=n.morphAttributes.position||n.morphAttributes.normal||n.morphAttributes.color,v=void 0!==f?f.length:0,_=ht.get(i),y=g.state.lights;if(!0===Q&&(!0===tt||t!==B)){const e=t===B&&i.id===F;bt.setState(i,t,e)}let x=!1;i.version===_.__version?_.needsLights&&_.lightsStateVersion!==y.state.version||_.outputColorSpace!==o||r.isInstancedMesh&&!1===_.instancing?x=!0:r.isInstancedMesh||!0!==_.instancing?r.isSkinnedMesh&&!1===_.skinning?x=!0:r.isSkinnedMesh||!0!==_.skinning?_.envMap!==l||!0===i.fog&&_.fog!==s?x=!0:void 0===_.numClippingPlanes||_.numClippingPlanes===bt.numPlanes&&_.numIntersection===bt.numIntersection?(_.vertexAlphas!==c||_.vertexTangents!==h||_.morphTargets!==u||_.morphNormals!==d||_.morphColors!==p||_.toneMapping!==m||!0===ot.isWebGL2&&_.morphTargetsCount!==v)&&(x=!0):x=!0:x=!0:x=!0:(x=!0,_.__version=i.version);let M=_.currentProgram;!0===x&&(M=Yt(i,e,r));let S=!1,b=!1,T=!1;const E=M.getUniforms(),w=_.uniforms;lt.useProgram(M.program)&&(S=!0,b=!0,T=!0);i.id!==F&&(F=i.id,b=!0);if(S||B!==t){if(E.setValue(It,"projectionMatrix",t.projectionMatrix),ot.logarithmicDepthBuffer&&E.setValue(It,"logDepthBufFC",2/(Math.log(t.far+1)/Math.LN2)),B!==t&&(B=t,b=!0,T=!0),i.isShaderMaterial||i.isMeshPhongMaterial||i.isMeshToonMaterial||i.isMeshStandardMaterial||i.envMap){const e=E.map.cameraPosition;void 0!==e&&e.setValue(It,it.setFromMatrixPosition(t.matrixWorld))}(i.isMeshPhongMaterial||i.isMeshToonMaterial||i.isMeshLambertMaterial||i.isMeshBasicMaterial||i.isMeshStandardMaterial||i.isShaderMaterial)&&E.setValue(It,"isOrthographic",!0===t.isOrthographicCamera),(i.isMeshPhongMaterial||i.isMeshToonMaterial||i.isMeshLambertMaterial||i.isMeshBasicMaterial||i.isMeshStandardMaterial||i.isShaderMaterial||i.isShadowMaterial||r.isSkinnedMesh)&&E.setValue(It,"viewMatrix",t.matrixWorldInverse)}if(r.isSkinnedMesh){E.setOptional(It,r,"bindMatrix"),E.setOptional(It,r,"bindMatrixInverse");const t=r.skeleton;t&&(ot.floatVertexTextures?(null===t.boneTexture&&t.computeBoneTexture(),E.setValue(It,"boneTexture",t.boneTexture,ut),E.setValue(It,"boneTextureSize",t.boneTextureSize)):console.warn("THREE.WebGLRenderer: SkinnedMesh can only be used with WebGL 2. With WebGL 1 OES_texture_float and vertex textures support is required."))}const A=n.morphAttributes;(void 0!==A.position||void 0!==A.normal||void 0!==A.color&&!0===ot.isWebGL2)&&wt.update(r,n,M);(b||_.receiveShadow!==r.receiveShadow)&&(_.receiveShadow=r.receiveShadow,E.setValue(It,"receiveShadow",r.receiveShadow));i.isMeshGouraudMaterial&&null!==i.envMap&&(w.envMap.value=l,w.flipEnvMap.value=l.isCubeTexture&&!1===l.isRenderTargetTexture?-1:1);b&&(E.setValue(It,"toneMappingExposure",C.toneMappingExposure),_.needsLights&&(L=T,(R=w).ambientLightColor.needsUpdate=L,R.lightProbe.needsUpdate=L,R.directionalLights.needsUpdate=L,R.directionalLightShadows.needsUpdate=L,R.pointLights.needsUpdate=L,R.pointLightShadows.needsUpdate=L,R.spotLights.needsUpdate=L,R.spotLightShadows.needsUpdate=L,R.rectAreaLights.needsUpdate=L,R.hemisphereLights.needsUpdate=L),s&&!0===i.fog&&xt.refreshFogUniforms(w,s),xt.refreshMaterialUniforms(w,i,j,X,et),As.upload(It,_.uniformsList,w,ut));var R,L;i.isShaderMaterial&&!0===i.uniformsNeedUpdate&&(As.upload(It,_.uniformsList,w,ut),i.uniformsNeedUpdate=!1);i.isSpriteMaterial&&E.setValue(It,"center",r.center);if(E.setValue(It,"modelViewMatrix",r.modelViewMatrix),E.setValue(It,"normalMatrix",r.normalMatrix),E.setValue(It,"modelMatrix",r.matrixWorld),i.isShaderMaterial||i.isRawShaderMaterial){const t=i.uniformsGroups;for(let e=0,n=t.length;e0&&function(t,e,n,i){if(null===et){const t=ot.isWebGL2;et=new he(1024,1024,{generateMipmaps:!0,type:at.has("EXT_color_buffer_half_float")?b:_,minFilter:v,samples:t&&!0===o?4:0})}const r=C.getRenderTarget();C.setRenderTarget(et),C.getClearColor(k),V=C.getClearAlpha(),V<1&&C.setClearColor(16777215,.5);C.clear();const s=C.toneMapping;C.toneMapping=0,jt(t,n,i),ut.updateMultisampleRenderTarget(et),ut.updateRenderTargetMipmap(et);let a=!1;for(let t=0,r=e.length;t0&&jt(r,e,n),s.length>0&&jt(s,e,n),a.length>0&&jt(a,e,n),lt.buffers.depth.setTest(!0),lt.buffers.depth.setMask(!0),lt.buffers.color.setMask(!0),lt.setPolygonOffset(!1)}function jt(t,e,n){const i=!0===e.isScene?e.overrideMaterial:null;for(let r=0,s=t.length;r0?R[R.length-1]:null,x.pop(),f=x.length>0?x[x.length-1]:null},this.getActiveCubeFace=function(){return D},this.getActiveMipmapLevel=function(){return N},this.getRenderTarget=function(){return O},this.setRenderTargetTextures=function(t,e,n){ht.get(t.texture).__webglTexture=e,ht.get(t.depthTexture).__webglTexture=n;const i=ht.get(t);i.__hasExternalTextures=!0,i.__hasExternalTextures&&(i.__autoAllocateDepthBuffer=void 0===n,i.__autoAllocateDepthBuffer||!0===at.has("WEBGL_multisampled_render_to_texture")&&(console.warn("THREE.WebGLRenderer: Render-to-texture extension was disabled because an external texture was provided"),i.__useRenderToTexture=!1))},this.setRenderTargetFramebuffer=function(t,e){const n=ht.get(t);n.__webglFramebuffer=e,n.__useDefaultFramebuffer=void 0===e},this.setRenderTarget=function(t,e=0,n=0){O=t,D=e,N=n;let i=!0,r=null,s=!1,a=!1;if(t){const n=ht.get(t);void 0!==n.__useDefaultFramebuffer?(lt.bindFramebuffer(It.FRAMEBUFFER,null),i=!1):void 0===n.__webglFramebuffer?ut.setupRenderTarget(t):n.__hasExternalTextures&&ut.rebindTextures(t,ht.get(t.texture).__webglTexture,ht.get(t.depthTexture).__webglTexture);const o=t.texture;(o.isData3DTexture||o.isDataArrayTexture||o.isCompressedArrayTexture)&&(a=!0);const l=ht.get(t).__webglFramebuffer;t.isWebGLCubeRenderTarget?(r=l[e],s=!0):r=ot.isWebGL2&&t.samples>0&&!1===ut.useMultisampledRTT(t)?ht.get(t).__webglMultisampledFramebuffer:l,z.copy(t.viewport),G.copy(t.scissor),H=t.scissorTest}else z.copy(Z).multiplyScalar(j).floor(),G.copy(J).multiplyScalar(j).floor(),H=K;if(lt.bindFramebuffer(It.FRAMEBUFFER,r)&&ot.drawBuffers&&i&<.drawBuffers(t,r),lt.viewport(z),lt.scissor(G),lt.setScissorTest(H),s){const i=ht.get(t.texture);It.framebufferTexture2D(It.FRAMEBUFFER,It.COLOR_ATTACHMENT0,It.TEXTURE_CUBE_MAP_POSITIVE_X+e,i.__webglTexture,n)}else if(a){const i=ht.get(t.texture),r=e||0;It.framebufferTextureLayer(It.FRAMEBUFFER,It.COLOR_ATTACHMENT0,i.__webglTexture,n||0,r)}F=-1},this.readRenderTargetPixels=function(t,e,n,i,r,s,a){if(!t||!t.isWebGLRenderTarget)return void console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.");let o=ht.get(t).__webglFramebuffer;if(t.isWebGLCubeRenderTarget&&void 0!==a&&(o=o[a]),o){lt.bindFramebuffer(It.FRAMEBUFFER,o);try{const a=t.texture,o=a.format,l=a.type;if(o!==A&&Ct.convert(o)!==It.getParameter(It.IMPLEMENTATION_COLOR_READ_FORMAT))return void console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in RGBA or implementation defined format.");const c=l===b&&(at.has("EXT_color_buffer_half_float")||ot.isWebGL2&&at.has("EXT_color_buffer_float"));if(!(l===_||Ct.convert(l)===It.getParameter(It.IMPLEMENTATION_COLOR_READ_TYPE)||l===S&&(ot.isWebGL2||at.has("OES_texture_float")||at.has("WEBGL_color_buffer_float"))||c))return void console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in UnsignedByteType or implementation defined type.");e>=0&&e<=t.width-i&&n>=0&&n<=t.height-r&&It.readPixels(e,n,i,r,Ct.convert(o),Ct.convert(l),s)}finally{const t=null!==O?ht.get(O).__webglFramebuffer:null;lt.bindFramebuffer(It.FRAMEBUFFER,t)}}},this.copyFramebufferToTexture=function(t,e,n=0){const i=Math.pow(2,-n),r=Math.floor(e.image.width*i),s=Math.floor(e.image.height*i);ut.setTexture2D(e,0),It.copyTexSubImage2D(It.TEXTURE_2D,n,0,0,t.x,t.y,r,s),lt.unbindTexture()},this.copyTextureToTexture=function(t,e,n,i=0){const r=e.image.width,s=e.image.height,a=Ct.convert(n.format),o=Ct.convert(n.type);ut.setTexture2D(n,0),It.pixelStorei(It.UNPACK_FLIP_Y_WEBGL,n.flipY),It.pixelStorei(It.UNPACK_PREMULTIPLY_ALPHA_WEBGL,n.premultiplyAlpha),It.pixelStorei(It.UNPACK_ALIGNMENT,n.unpackAlignment),e.isDataTexture?It.texSubImage2D(It.TEXTURE_2D,i,t.x,t.y,r,s,a,o,e.image.data):e.isCompressedTexture?It.compressedTexSubImage2D(It.TEXTURE_2D,i,t.x,t.y,e.mipmaps[0].width,e.mipmaps[0].height,a,e.mipmaps[0].data):It.texSubImage2D(It.TEXTURE_2D,i,t.x,t.y,a,o,e.image),0===i&&n.generateMipmaps&&It.generateMipmap(It.TEXTURE_2D),lt.unbindTexture()},this.copyTextureToTexture3D=function(t,e,n,i,r=0){if(C.isWebGL1Renderer)return void console.warn("THREE.WebGLRenderer.copyTextureToTexture3D: can only be used with WebGL2.");const s=t.max.x-t.min.x+1,a=t.max.y-t.min.y+1,o=t.max.z-t.min.z+1,l=Ct.convert(i.format),c=Ct.convert(i.type);let h;if(i.isData3DTexture)ut.setTexture3D(i,0),h=It.TEXTURE_3D;else{if(!i.isDataArrayTexture)return void console.warn("THREE.WebGLRenderer.copyTextureToTexture3D: only supports THREE.DataTexture3D and THREE.DataTexture2DArray.");ut.setTexture2DArray(i,0),h=It.TEXTURE_2D_ARRAY}It.pixelStorei(It.UNPACK_FLIP_Y_WEBGL,i.flipY),It.pixelStorei(It.UNPACK_PREMULTIPLY_ALPHA_WEBGL,i.premultiplyAlpha),It.pixelStorei(It.UNPACK_ALIGNMENT,i.unpackAlignment);const u=It.getParameter(It.UNPACK_ROW_LENGTH),d=It.getParameter(It.UNPACK_IMAGE_HEIGHT),p=It.getParameter(It.UNPACK_SKIP_PIXELS),m=It.getParameter(It.UNPACK_SKIP_ROWS),f=It.getParameter(It.UNPACK_SKIP_IMAGES),g=n.isCompressedTexture?n.mipmaps[0]:n.image;It.pixelStorei(It.UNPACK_ROW_LENGTH,g.width),It.pixelStorei(It.UNPACK_IMAGE_HEIGHT,g.height),It.pixelStorei(It.UNPACK_SKIP_PIXELS,t.min.x),It.pixelStorei(It.UNPACK_SKIP_ROWS,t.min.y),It.pixelStorei(It.UNPACK_SKIP_IMAGES,t.min.z),n.isDataTexture||n.isData3DTexture?It.texSubImage3D(h,r,e.x,e.y,e.z,s,a,o,l,c,g.data):n.isCompressedArrayTexture?(console.warn("THREE.WebGLRenderer.copyTextureToTexture3D: untested support for compressed srcTexture."),It.compressedTexSubImage3D(h,r,e.x,e.y,e.z,s,a,o,l,g.data)):It.texSubImage3D(h,r,e.x,e.y,e.z,s,a,o,l,c,g),It.pixelStorei(It.UNPACK_ROW_LENGTH,u),It.pixelStorei(It.UNPACK_IMAGE_HEIGHT,d),It.pixelStorei(It.UNPACK_SKIP_PIXELS,p),It.pixelStorei(It.UNPACK_SKIP_ROWS,m),It.pixelStorei(It.UNPACK_SKIP_IMAGES,f),0===r&&i.generateMipmaps&&It.generateMipmap(h),lt.unbindTexture()},this.initTexture=function(t){t.isCubeTexture?ut.setTextureCube(t,0):t.isData3DTexture?ut.setTexture3D(t,0):t.isDataArrayTexture||t.isCompressedArrayTexture?ut.setTexture2DArray(t,0):ut.setTexture2D(t,0),lt.unbindTexture()},this.resetState=function(){D=0,N=0,O=null,lt.reset(),Lt.reset()},"undefined"!=typeof __THREE_DEVTOOLS__&&__THREE_DEVTOOLS__.dispatchEvent(new CustomEvent("observe",{detail:this}))}get physicallyCorrectLights(){return console.warn("THREE.WebGLRenderer: the property .physicallyCorrectLights has been removed. Set renderer.useLegacyLights instead."),!this.useLegacyLights}set physicallyCorrectLights(t){console.warn("THREE.WebGLRenderer: the property .physicallyCorrectLights has been removed. Set renderer.useLegacyLights instead."),this.useLegacyLights=!t}get outputEncoding(){return console.warn("THREE.WebGLRenderer: Property .outputEncoding has been removed. Use .outputColorSpace instead."),this.outputColorSpace===_t?gt:ft}set outputEncoding(t){console.warn("THREE.WebGLRenderer: Property .outputEncoding has been removed. Use .outputColorSpace instead."),this.outputColorSpace=t===gt?_t:yt}}class Ma extends xa{}Ma.prototype.isWebGL1Renderer=!0;class Sa{constructor(t,e=25e-5){this.isFogExp2=!0,this.name="",this.color=new Nn(t),this.density=e}clone(){return new Sa(this.color,this.density)}toJSON(){return{type:"FogExp2",color:this.color.getHex(),density:this.density}}}class ba{constructor(t,e=1,n=1e3){this.isFog=!0,this.name="",this.color=new Nn(t),this.near=e,this.far=n}clone(){return new ba(this.color,this.near,this.far)}toJSON(){return{type:"Fog",color:this.color.getHex(),near:this.near,far:this.far}}}class Ta extends gn{constructor(){super(),this.isScene=!0,this.type="Scene",this.background=null,this.environment=null,this.fog=null,this.backgroundBlurriness=0,this.backgroundIntensity=1,this.overrideMaterial=null,"undefined"!=typeof __THREE_DEVTOOLS__&&__THREE_DEVTOOLS__.dispatchEvent(new CustomEvent("observe",{detail:this}))}copy(t,e){return super.copy(t,e),null!==t.background&&(this.background=t.background.clone()),null!==t.environment&&(this.environment=t.environment.clone()),null!==t.fog&&(this.fog=t.fog.clone()),this.backgroundBlurriness=t.backgroundBlurriness,this.backgroundIntensity=t.backgroundIntensity,null!==t.overrideMaterial&&(this.overrideMaterial=t.overrideMaterial.clone()),this.matrixAutoUpdate=t.matrixAutoUpdate,this}toJSON(t){const e=super.toJSON(t);return null!==this.fog&&(e.object.fog=this.fog.toJSON()),this.backgroundBlurriness>0&&(e.object.backgroundBlurriness=this.backgroundBlurriness),1!==this.backgroundIntensity&&(e.object.backgroundIntensity=this.backgroundIntensity),e}get autoUpdate(){return console.warn("THREE.Scene: autoUpdate was renamed to matrixWorldAutoUpdate in r144."),this.matrixWorldAutoUpdate}set autoUpdate(t){console.warn("THREE.Scene: autoUpdate was renamed to matrixWorldAutoUpdate in r144."),this.matrixWorldAutoUpdate=t}}class Ea{constructor(t,e){this.isInterleavedBuffer=!0,this.array=t,this.stride=e,this.count=void 0!==t?t.length/e:0,this.usage=St,this.updateRange={offset:0,count:-1},this.version=0,this.uuid=Lt()}onUploadCallback(){}set needsUpdate(t){!0===t&&this.version++}setUsage(t){return this.usage=t,this}copy(t){return this.array=new t.array.constructor(t.array),this.count=t.count,this.stride=t.stride,this.usage=t.usage,this}copyAt(t,e,n){t*=this.stride,n*=e.stride;for(let i=0,r=this.stride;it.far||e.push({distance:o,point:La.clone(),uv:Rn.getInterpolation(La,Oa,Fa,Ba,za,Ga,Ha,new Gt),face:null,object:this})}copy(t,e){return super.copy(t,e),void 0!==t.center&&this.center.copy(t.center),this.material=t.material,this}}function Va(t,e,n,i,r,s){Ua.subVectors(t,n).addScalar(.5).multiply(i),void 0!==r?(Da.x=s*Ua.x-r*Ua.y,Da.y=r*Ua.x+s*Ua.y):Da.copy(Ua),t.copy(e),t.x+=Da.x,t.y+=Da.y,t.applyMatrix4(Na)}const Wa=new me,Xa=new me;class ja extends gn{constructor(){super(),this._currentLevel=0,this.type="LOD",Object.defineProperties(this,{levels:{enumerable:!0,value:[]},isLOD:{value:!0}}),this.autoUpdate=!0}copy(t){super.copy(t,!1);const e=t.levels;for(let t=0,n=e.length;t0){let n,i;for(n=1,i=e.length;n0){Wa.setFromMatrixPosition(this.matrixWorld);const n=t.ray.origin.distanceTo(Wa);this.getObjectForDistance(n).raycast(t,e)}}update(t){const e=this.levels;if(e.length>1){Wa.setFromMatrixPosition(t.matrixWorld),Xa.setFromMatrixPosition(this.matrixWorld);const n=Wa.distanceTo(Xa)/t.zoom;let i,r;for(e[0].object.visible=!0,i=1,r=e.length;i=t))break;e[i-1].object.visible=!1,e[i].object.visible=!0}for(this._currentLevel=i-1;io)continue;u.applyMatrix4(this.matrixWorld);const s=t.ray.origin.distanceTo(u);st.far||e.push({distance:s,point:h.clone().applyMatrix4(this.matrixWorld),index:n,face:null,faceIndex:null,object:this})}}else{for(let n=Math.max(0,s.start),i=Math.min(m.count,s.start+s.count)-1;no)continue;u.applyMatrix4(this.matrixWorld);const i=t.ray.origin.distanceTo(u);it.far||e.push({distance:i,point:h.clone().applyMatrix4(this.matrixWorld),index:n,face:null,faceIndex:null,object:this})}}}updateMorphTargets(){const t=this.geometry.morphAttributes,e=Object.keys(t);if(e.length>0){const n=t[e[0]];if(void 0!==n){this.morphTargetInfluences=[],this.morphTargetDictionary={};for(let t=0,e=n.length;t0){const n=t[e[0]];if(void 0!==n){this.morphTargetInfluences=[],this.morphTargetDictionary={};for(let t=0,e=n.length;tr.far)return;s.push({distance:l,distanceToRay:Math.sqrt(o),point:n,index:e,face:null,object:a})}}class Oo extends le{constructor(t,e,n,i,r,s,a,o,l,c,h,u){super(null,s,a,o,l,c,i,r,h,u),this.isCompressedTexture=!0,this.image={width:e,height:n},this.mipmaps=t,this.flipY=!1,this.generateMipmaps=!1}}class Fo{constructor(){this.type="Curve",this.arcLengthDivisions=200}getPoint(){return console.warn("THREE.Curve: .getPoint() not implemented."),null}getPointAt(t,e){const n=this.getUtoTmapping(t);return this.getPoint(n,e)}getPoints(t=5){const e=[];for(let n=0;n<=t;n++)e.push(this.getPoint(n/t));return e}getSpacedPoints(t=5){const e=[];for(let n=0;n<=t;n++)e.push(this.getPointAt(n/t));return e}getLength(){const t=this.getLengths();return t[t.length-1]}getLengths(t=this.arcLengthDivisions){if(this.cacheArcLengths&&this.cacheArcLengths.length===t+1&&!this.needsUpdate)return this.cacheArcLengths;this.needsUpdate=!1;const e=[];let n,i=this.getPoint(0),r=0;e.push(0);for(let s=1;s<=t;s++)n=this.getPoint(s/t),r+=n.distanceTo(i),e.push(r),i=n;return this.cacheArcLengths=e,e}updateArcLengths(){this.needsUpdate=!0,this.getLengths()}getUtoTmapping(t,e){const n=this.getLengths();let i=0;const r=n.length;let s;s=e||t*n[r-1];let a,o=0,l=r-1;for(;o<=l;)if(i=Math.floor(o+(l-o)/2),a=n[i]-s,a<0)o=i+1;else{if(!(a>0)){l=i;break}l=i-1}if(i=l,n[i]===s)return i/(r-1);const c=n[i];return(i+(s-c)/(n[i+1]-c))/(r-1)}getTangent(t,e){const n=1e-4;let i=t-n,r=t+n;i<0&&(i=0),r>1&&(r=1);const s=this.getPoint(i),a=this.getPoint(r),o=e||(s.isVector2?new Gt:new me);return o.copy(a).sub(s).normalize(),o}getTangentAt(t,e){const n=this.getUtoTmapping(t);return this.getTangent(n,e)}computeFrenetFrames(t,e){const n=new me,i=[],r=[],s=[],a=new me,o=new We;for(let e=0;e<=t;e++){const n=e/t;i[e]=this.getTangentAt(n,new me)}r[0]=new me,s[0]=new me;let l=Number.MAX_VALUE;const c=Math.abs(i[0].x),h=Math.abs(i[0].y),u=Math.abs(i[0].z);c<=l&&(l=c,n.set(1,0,0)),h<=l&&(l=h,n.set(0,1,0)),u<=l&&n.set(0,0,1),a.crossVectors(i[0],n).normalize(),r[0].crossVectors(i[0],a),s[0].crossVectors(i[0],r[0]);for(let e=1;e<=t;e++){if(r[e]=r[e-1].clone(),s[e]=s[e-1].clone(),a.crossVectors(i[e-1],i[e]),a.length()>Number.EPSILON){a.normalize();const t=Math.acos(Pt(i[e-1].dot(i[e]),-1,1));r[e].applyMatrix4(o.makeRotationAxis(a,t))}s[e].crossVectors(i[e],r[e])}if(!0===e){let e=Math.acos(Pt(r[0].dot(r[t]),-1,1));e/=t,i[0].dot(a.crossVectors(r[0],r[t]))>0&&(e=-e);for(let n=1;n<=t;n++)r[n].applyMatrix4(o.makeRotationAxis(i[n],e*n)),s[n].crossVectors(i[n],r[n])}return{tangents:i,normals:r,binormals:s}}clone(){return(new this.constructor).copy(this)}copy(t){return this.arcLengthDivisions=t.arcLengthDivisions,this}toJSON(){const t={metadata:{version:4.6,type:"Curve",generator:"Curve.toJSON"}};return t.arcLengthDivisions=this.arcLengthDivisions,t.type=this.type,t}fromJSON(t){return this.arcLengthDivisions=t.arcLengthDivisions,this}}class Bo extends Fo{constructor(t=0,e=0,n=1,i=1,r=0,s=2*Math.PI,a=!1,o=0){super(),this.isEllipseCurve=!0,this.type="EllipseCurve",this.aX=t,this.aY=e,this.xRadius=n,this.yRadius=i,this.aStartAngle=r,this.aEndAngle=s,this.aClockwise=a,this.aRotation=o}getPoint(t,e){const n=e||new Gt,i=2*Math.PI;let r=this.aEndAngle-this.aStartAngle;const s=Math.abs(r)i;)r-=i;r0?0:(Math.floor(Math.abs(l)/r)+1)*r:0===c&&l===r-1&&(l=r-2,c=1),this.closed||l>0?a=i[(l-1)%r]:(Ho.subVectors(i[0],i[1]).add(i[0]),a=Ho);const h=i[l%r],u=i[(l+1)%r];if(this.closed||l+2i.length-2?i.length-1:s+1],h=i[s>i.length-3?i.length-1:s+2];return n.set(jo(a,o.x,l.x,c.x,h.x),jo(a,o.y,l.y,c.y,h.y)),n}copy(t){super.copy(t),this.points=[];for(let e=0,n=t.points.length;e=n){const t=i[r]-n,s=this.curves[r],a=s.getLength(),o=0===a?0:1-t/a;return s.getPointAt(o,e)}r++}return null}getLength(){const t=this.getCurveLengths();return t[t.length-1]}updateArcLengths(){this.needsUpdate=!0,this.cacheLengths=null,this.getCurveLengths()}getCurveLengths(){if(this.cacheLengths&&this.cacheLengths.length===this.curves.length)return this.cacheLengths;const t=[];let e=0;for(let n=0,i=this.curves.length;n1&&!e[e.length-1].equals(e[0])&&e.push(e[0]),e}copy(t){super.copy(t),this.curves=[];for(let e=0,n=t.curves.length;e0){const t=l.getPoint(0);t.equals(this.currentPoint)||this.lineTo(t.x,t.y)}this.curves.push(l);const c=l.getPoint(1);return this.currentPoint.copy(c),this}copy(t){return super.copy(t),this.currentPoint.copy(t.currentPoint),this}toJSON(){const t=super.toJSON();return t.currentPoint=this.currentPoint.toArray(),t}fromJSON(t){return super.fromJSON(t),this.currentPoint.fromArray(t.currentPoint),this}}class sl extends ni{constructor(t=[new Gt(0,-.5),new Gt(.5,0),new Gt(0,.5)],e=12,n=0,i=2*Math.PI){super(),this.type="LatheGeometry",this.parameters={points:t,segments:e,phiStart:n,phiLength:i},e=Math.floor(e),i=Pt(i,0,2*Math.PI);const r=[],s=[],a=[],o=[],l=[],c=1/e,h=new me,u=new Gt,d=new me,p=new me,m=new me;let f=0,g=0;for(let e=0;e<=t.length-1;e++)switch(e){case 0:f=t[e+1].x-t[e].x,g=t[e+1].y-t[e].y,d.x=1*g,d.y=-f,d.z=0*g,m.copy(d),d.normalize(),o.push(d.x,d.y,d.z);break;case t.length-1:o.push(m.x,m.y,m.z);break;default:f=t[e+1].x-t[e].x,g=t[e+1].y-t[e].y,d.x=1*g,d.y=-f,d.z=0*g,p.copy(d),d.x+=m.x,d.y+=m.y,d.z+=m.z,d.normalize(),o.push(d.x,d.y,d.z),m.copy(p)}for(let r=0;r<=e;r++){const d=n+r*c*i,p=Math.sin(d),m=Math.cos(d);for(let n=0;n<=t.length-1;n++){h.x=t[n].x*p,h.y=t[n].y,h.z=t[n].x*m,s.push(h.x,h.y,h.z),u.x=r/e,u.y=n/(t.length-1),a.push(u.x,u.y);const i=o[3*n+0]*p,c=o[3*n+1],d=o[3*n+0]*m;l.push(i,c,d)}}for(let n=0;n0&&v(!0),e>0&&v(!1)),this.setIndex(c),this.setAttribute("position",new Yn(h,3)),this.setAttribute("normal",new Yn(u,3)),this.setAttribute("uv",new Yn(d,2))}copy(t){return super.copy(t),this.parameters=Object.assign({},t.parameters),this}static fromJSON(t){return new ll(t.radiusTop,t.radiusBottom,t.height,t.radialSegments,t.heightSegments,t.openEnded,t.thetaStart,t.thetaLength)}}class cl extends ll{constructor(t=1,e=1,n=32,i=1,r=!1,s=0,a=2*Math.PI){super(0,t,e,n,i,r,s,a),this.type="ConeGeometry",this.parameters={radius:t,height:e,radialSegments:n,heightSegments:i,openEnded:r,thetaStart:s,thetaLength:a}}static fromJSON(t){return new cl(t.radius,t.height,t.radialSegments,t.heightSegments,t.openEnded,t.thetaStart,t.thetaLength)}}class hl extends ni{constructor(t=[],e=[],n=1,i=0){super(),this.type="PolyhedronGeometry",this.parameters={vertices:t,indices:e,radius:n,detail:i};const r=[],s=[];function a(t,e,n,i){const r=i+1,s=[];for(let i=0;i<=r;i++){s[i]=[];const a=t.clone().lerp(n,i/r),o=e.clone().lerp(n,i/r),l=r-i;for(let t=0;t<=l;t++)s[i][t]=0===t&&i===r?a:a.clone().lerp(o,t/l)}for(let t=0;t.9&&a<.1&&(e<.2&&(s[t+0]+=1),n<.2&&(s[t+2]+=1),i<.2&&(s[t+4]+=1))}}()}(),this.setAttribute("position",new Yn(r,3)),this.setAttribute("normal",new Yn(r.slice(),3)),this.setAttribute("uv",new Yn(s,2)),0===i?this.computeVertexNormals():this.normalizeNormals()}copy(t){return super.copy(t),this.parameters=Object.assign({},t.parameters),this}static fromJSON(t){return new hl(t.vertices,t.indices,t.radius,t.details)}}class ul extends hl{constructor(t=1,e=0){const n=(1+Math.sqrt(5))/2,i=1/n;super([-1,-1,-1,-1,-1,1,-1,1,-1,-1,1,1,1,-1,-1,1,-1,1,1,1,-1,1,1,1,0,-i,-n,0,-i,n,0,i,-n,0,i,n,-i,-n,0,-i,n,0,i,-n,0,i,n,0,-n,0,-i,n,0,-i,-n,0,i,n,0,i],[3,11,7,3,7,15,3,15,13,7,19,17,7,17,6,7,6,15,17,4,8,17,8,10,17,10,6,8,0,16,8,16,2,8,2,10,0,12,1,0,1,18,0,18,16,6,10,2,6,2,13,6,13,15,2,16,18,2,18,3,2,3,13,18,1,9,18,9,11,18,11,3,4,14,12,4,12,0,4,0,8,11,9,5,11,5,19,11,19,7,19,5,14,19,14,4,19,4,17,1,12,14,1,14,5,1,5,9],t,e),this.type="DodecahedronGeometry",this.parameters={radius:t,detail:e}}static fromJSON(t){return new ul(t.radius,t.detail)}}const dl=new me,pl=new me,ml=new me,fl=new Rn;class gl extends ni{constructor(t=null,e=1){if(super(),this.type="EdgesGeometry",this.parameters={geometry:t,thresholdAngle:e},null!==t){const n=4,i=Math.pow(10,n),r=Math.cos(Rt*e),s=t.getIndex(),a=t.getAttribute("position"),o=s?s.count:a.count,l=[0,0,0],c=["a","b","c"],h=new Array(3),u={},d=[];for(let t=0;t80*n){o=c=t[0],l=h=t[1];for(let e=n;ec&&(c=u),d>h&&(h=d);p=Math.max(c-o,h-l),p=0!==p?32767/p:0}return Ml(s,a,n,o,l,p,0),a};function yl(t,e,n,i,r){let s,a;if(r===function(t,e,n,i){let r=0;for(let s=e,a=n-i;s0)for(s=e;s=e;s-=i)a=Gl(s,t[s],t[s+1],a);return a&&Dl(a,a.next)&&(Hl(a),a=a.next),a}function xl(t,e){if(!t)return t;e||(e=t);let n,i=t;do{if(n=!1,i.steiner||!Dl(i,i.next)&&0!==Ul(i.prev,i,i.next))i=i.next;else{if(Hl(i),i=e=i.prev,i===i.next)break;n=!0}}while(n||i!==e);return e}function Ml(t,e,n,i,r,s,a){if(!t)return;!a&&s&&function(t,e,n,i){let r=t;do{0===r.z&&(r.z=Cl(r.x,r.y,e,n,i)),r.prevZ=r.prev,r.nextZ=r.next,r=r.next}while(r!==t);r.prevZ.nextZ=null,r.prevZ=null,function(t){let e,n,i,r,s,a,o,l,c=1;do{for(n=t,t=null,s=null,a=0;n;){for(a++,i=n,o=0,e=0;e0||l>0&&i;)0!==o&&(0===l||!i||n.z<=i.z)?(r=n,n=n.nextZ,o--):(r=i,i=i.nextZ,l--),s?s.nextZ=r:t=r,r.prevZ=s,s=r;n=i}s.nextZ=null,c*=2}while(a>1)}(r)}(t,i,r,s);let o,l,c=t;for(;t.prev!==t.next;)if(o=t.prev,l=t.next,s?bl(t,i,r,s):Sl(t))e.push(o.i/n|0),e.push(t.i/n|0),e.push(l.i/n|0),Hl(t),t=l.next,c=l.next;else if((t=l)===c){a?1===a?Ml(t=Tl(xl(t),e,n),e,n,i,r,s,2):2===a&&El(t,e,n,i,r,s):Ml(xl(t),e,n,i,r,s,1);break}}function Sl(t){const e=t.prev,n=t,i=t.next;if(Ul(e,n,i)>=0)return!1;const r=e.x,s=n.x,a=i.x,o=e.y,l=n.y,c=i.y,h=rs?r>a?r:a:s>a?s:a,p=o>l?o>c?o:c:l>c?l:c;let m=i.next;for(;m!==e;){if(m.x>=h&&m.x<=d&&m.y>=u&&m.y<=p&&Pl(r,o,s,l,a,c,m.x,m.y)&&Ul(m.prev,m,m.next)>=0)return!1;m=m.next}return!0}function bl(t,e,n,i){const r=t.prev,s=t,a=t.next;if(Ul(r,s,a)>=0)return!1;const o=r.x,l=s.x,c=a.x,h=r.y,u=s.y,d=a.y,p=ol?o>c?o:c:l>c?l:c,g=h>u?h>d?h:d:u>d?u:d,v=Cl(p,m,e,n,i),_=Cl(f,g,e,n,i);let y=t.prevZ,x=t.nextZ;for(;y&&y.z>=v&&x&&x.z<=_;){if(y.x>=p&&y.x<=f&&y.y>=m&&y.y<=g&&y!==r&&y!==a&&Pl(o,h,l,u,c,d,y.x,y.y)&&Ul(y.prev,y,y.next)>=0)return!1;if(y=y.prevZ,x.x>=p&&x.x<=f&&x.y>=m&&x.y<=g&&x!==r&&x!==a&&Pl(o,h,l,u,c,d,x.x,x.y)&&Ul(x.prev,x,x.next)>=0)return!1;x=x.nextZ}for(;y&&y.z>=v;){if(y.x>=p&&y.x<=f&&y.y>=m&&y.y<=g&&y!==r&&y!==a&&Pl(o,h,l,u,c,d,y.x,y.y)&&Ul(y.prev,y,y.next)>=0)return!1;y=y.prevZ}for(;x&&x.z<=_;){if(x.x>=p&&x.x<=f&&x.y>=m&&x.y<=g&&x!==r&&x!==a&&Pl(o,h,l,u,c,d,x.x,x.y)&&Ul(x.prev,x,x.next)>=0)return!1;x=x.nextZ}return!0}function Tl(t,e,n){let i=t;do{const r=i.prev,s=i.next.next;!Dl(r,s)&&Nl(r,i,i.next,s)&&Bl(r,s)&&Bl(s,r)&&(e.push(r.i/n|0),e.push(i.i/n|0),e.push(s.i/n|0),Hl(i),Hl(i.next),i=t=s),i=i.next}while(i!==t);return xl(i)}function El(t,e,n,i,r,s){let a=t;do{let t=a.next.next;for(;t!==a.prev;){if(a.i!==t.i&&Il(a,t)){let o=zl(a,t);return a=xl(a,a.next),o=xl(o,o.next),Ml(a,e,n,i,r,s,0),void Ml(o,e,n,i,r,s,0)}t=t.next}a=a.next}while(a!==t)}function wl(t,e){return t.x-e.x}function Al(t,e){const n=function(t,e){let n,i=e,r=-1/0;const s=t.x,a=t.y;do{if(a<=i.y&&a>=i.next.y&&i.next.y!==i.y){const t=i.x+(a-i.y)*(i.next.x-i.x)/(i.next.y-i.y);if(t<=s&&t>r&&(r=t,n=i.x=i.x&&i.x>=l&&s!==i.x&&Pl(an.x||i.x===n.x&&Rl(n,i)))&&(n=i,u=h)),i=i.next}while(i!==o);return n}(t,e);if(!n)return e;const i=zl(n,t);return xl(i,i.next),xl(n,n.next)}function Rl(t,e){return Ul(t.prev,t,e.prev)<0&&Ul(e.next,t,t.next)<0}function Cl(t,e,n,i,r){return(t=1431655765&((t=858993459&((t=252645135&((t=16711935&((t=(t-n)*r|0)|t<<8))|t<<4))|t<<2))|t<<1))|(e=1431655765&((e=858993459&((e=252645135&((e=16711935&((e=(e-i)*r|0)|e<<8))|e<<4))|e<<2))|e<<1))<<1}function Ll(t){let e=t,n=t;do{(e.x=(t-a)*(s-o)&&(t-a)*(i-o)>=(n-a)*(e-o)&&(n-a)*(s-o)>=(r-a)*(i-o)}function Il(t,e){return t.next.i!==e.i&&t.prev.i!==e.i&&!function(t,e){let n=t;do{if(n.i!==t.i&&n.next.i!==t.i&&n.i!==e.i&&n.next.i!==e.i&&Nl(n,n.next,t,e))return!0;n=n.next}while(n!==t);return!1}(t,e)&&(Bl(t,e)&&Bl(e,t)&&function(t,e){let n=t,i=!1;const r=(t.x+e.x)/2,s=(t.y+e.y)/2;do{n.y>s!=n.next.y>s&&n.next.y!==n.y&&r<(n.next.x-n.x)*(s-n.y)/(n.next.y-n.y)+n.x&&(i=!i),n=n.next}while(n!==t);return i}(t,e)&&(Ul(t.prev,t,e.prev)||Ul(t,e.prev,e))||Dl(t,e)&&Ul(t.prev,t,t.next)>0&&Ul(e.prev,e,e.next)>0)}function Ul(t,e,n){return(e.y-t.y)*(n.x-e.x)-(e.x-t.x)*(n.y-e.y)}function Dl(t,e){return t.x===e.x&&t.y===e.y}function Nl(t,e,n,i){const r=Fl(Ul(t,e,n)),s=Fl(Ul(t,e,i)),a=Fl(Ul(n,i,t)),o=Fl(Ul(n,i,e));return r!==s&&a!==o||(!(0!==r||!Ol(t,n,e))||(!(0!==s||!Ol(t,i,e))||(!(0!==a||!Ol(n,t,i))||!(0!==o||!Ol(n,e,i)))))}function Ol(t,e,n){return e.x<=Math.max(t.x,n.x)&&e.x>=Math.min(t.x,n.x)&&e.y<=Math.max(t.y,n.y)&&e.y>=Math.min(t.y,n.y)}function Fl(t){return t>0?1:t<0?-1:0}function Bl(t,e){return Ul(t.prev,t,t.next)<0?Ul(t,e,t.next)>=0&&Ul(t,t.prev,e)>=0:Ul(t,e,t.prev)<0||Ul(t,t.next,e)<0}function zl(t,e){const n=new kl(t.i,t.x,t.y),i=new kl(e.i,e.x,e.y),r=t.next,s=e.prev;return t.next=e,e.prev=t,n.next=r,r.prev=n,i.next=n,n.prev=i,s.next=i,i.prev=s,i}function Gl(t,e,n,i){const r=new kl(t,e,n);return i?(r.next=i.next,r.prev=i,i.next.prev=r,i.next=r):(r.prev=r,r.next=r),r}function Hl(t){t.next.prev=t.prev,t.prev.next=t.next,t.prevZ&&(t.prevZ.nextZ=t.nextZ),t.nextZ&&(t.nextZ.prevZ=t.prevZ)}function kl(t,e,n){this.i=t,this.x=e,this.y=n,this.prev=null,this.next=null,this.z=0,this.prevZ=null,this.nextZ=null,this.steiner=!1}class Vl{static area(t){const e=t.length;let n=0;for(let i=e-1,r=0;r2&&t[e-1].equals(t[0])&&t.pop()}function Xl(t,e){for(let n=0;nNumber.EPSILON){const u=Math.sqrt(h),d=Math.sqrt(l*l+c*c),p=e.x-o/u,m=e.y+a/u,f=((n.x-c/d-p)*c-(n.y+l/d-m)*l)/(a*c-o*l);i=p+a*f-t.x,r=m+o*f-t.y;const g=i*i+r*r;if(g<=2)return new Gt(i,r);s=Math.sqrt(g/2)}else{let t=!1;a>Number.EPSILON?l>Number.EPSILON&&(t=!0):a<-Number.EPSILON?l<-Number.EPSILON&&(t=!0):Math.sign(o)===Math.sign(c)&&(t=!0),t?(i=-o,r=a,s=Math.sqrt(h)):(i=a,r=o,s=Math.sqrt(h/2))}return new Gt(i/s,r/s)}const P=[];for(let t=0,e=w.length,n=e-1,i=t+1;t=0;t--){const e=t/p,n=h*Math.cos(e*Math.PI/2),i=u*Math.sin(e*Math.PI/2)+d;for(let t=0,e=w.length;t=0;){const i=n;let r=n-1;r<0&&(r=t.length-1);for(let t=0,n=o+2*p;t0)&&d.push(e,r,l),(t!==n-1||o0!=t>0&&this.version++,this._anisotropy=t}get clearcoat(){return this._clearcoat}set clearcoat(t){this._clearcoat>0!=t>0&&this.version++,this._clearcoat=t}get iridescence(){return this._iridescence}set iridescence(t){this._iridescence>0!=t>0&&this.version++,this._iridescence=t}get sheen(){return this._sheen}set sheen(t){this._sheen>0!=t>0&&this.version++,this._sheen=t}get transmission(){return this._transmission}set transmission(t){this._transmission>0!=t>0&&this.version++,this._transmission=t}copy(t){return super.copy(t),this.defines={STANDARD:"",PHYSICAL:""},this.anisotropy=t.anisotropy,this.anisotropyRotation=t.anisotropyRotation,this.anisotropyMap=t.anisotropyMap,this.clearcoat=t.clearcoat,this.clearcoatMap=t.clearcoatMap,this.clearcoatRoughness=t.clearcoatRoughness,this.clearcoatRoughnessMap=t.clearcoatRoughnessMap,this.clearcoatNormalMap=t.clearcoatNormalMap,this.clearcoatNormalScale.copy(t.clearcoatNormalScale),this.ior=t.ior,this.iridescence=t.iridescence,this.iridescenceMap=t.iridescenceMap,this.iridescenceIOR=t.iridescenceIOR,this.iridescenceThicknessRange=[...t.iridescenceThicknessRange],this.iridescenceThicknessMap=t.iridescenceThicknessMap,this.sheen=t.sheen,this.sheenColor.copy(t.sheenColor),this.sheenColorMap=t.sheenColorMap,this.sheenRoughness=t.sheenRoughness,this.sheenRoughnessMap=t.sheenRoughnessMap,this.transmission=t.transmission,this.transmissionMap=t.transmissionMap,this.thickness=t.thickness,this.thicknessMap=t.thicknessMap,this.attenuationDistance=t.attenuationDistance,this.attenuationColor.copy(t.attenuationColor),this.specularIntensity=t.specularIntensity,this.specularIntensityMap=t.specularIntensityMap,this.specularColor.copy(t.specularColor),this.specularColorMap=t.specularColorMap,this}}class hc extends Ln{constructor(t){super(),this.isMeshPhongMaterial=!0,this.type="MeshPhongMaterial",this.color=new Nn(16777215),this.specular=new Nn(1118481),this.shininess=30,this.map=null,this.lightMap=null,this.lightMapIntensity=1,this.aoMap=null,this.aoMapIntensity=1,this.emissive=new Nn(0),this.emissiveIntensity=1,this.emissiveMap=null,this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new Gt(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.specularMap=null,this.alphaMap=null,this.envMap=null,this.combine=0,this.reflectivity=1,this.refractionRatio=.98,this.wireframe=!1,this.wireframeLinewidth=1,this.wireframeLinecap="round",this.wireframeLinejoin="round",this.flatShading=!1,this.fog=!0,this.setValues(t)}copy(t){return super.copy(t),this.color.copy(t.color),this.specular.copy(t.specular),this.shininess=t.shininess,this.map=t.map,this.lightMap=t.lightMap,this.lightMapIntensity=t.lightMapIntensity,this.aoMap=t.aoMap,this.aoMapIntensity=t.aoMapIntensity,this.emissive.copy(t.emissive),this.emissiveMap=t.emissiveMap,this.emissiveIntensity=t.emissiveIntensity,this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.specularMap=t.specularMap,this.alphaMap=t.alphaMap,this.envMap=t.envMap,this.combine=t.combine,this.reflectivity=t.reflectivity,this.refractionRatio=t.refractionRatio,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.wireframeLinecap=t.wireframeLinecap,this.wireframeLinejoin=t.wireframeLinejoin,this.flatShading=t.flatShading,this.fog=t.fog,this}}class uc extends Ln{constructor(t){super(),this.isMeshToonMaterial=!0,this.defines={TOON:""},this.type="MeshToonMaterial",this.color=new Nn(16777215),this.map=null,this.gradientMap=null,this.lightMap=null,this.lightMapIntensity=1,this.aoMap=null,this.aoMapIntensity=1,this.emissive=new Nn(0),this.emissiveIntensity=1,this.emissiveMap=null,this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new Gt(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.alphaMap=null,this.wireframe=!1,this.wireframeLinewidth=1,this.wireframeLinecap="round",this.wireframeLinejoin="round",this.fog=!0,this.setValues(t)}copy(t){return super.copy(t),this.color.copy(t.color),this.map=t.map,this.gradientMap=t.gradientMap,this.lightMap=t.lightMap,this.lightMapIntensity=t.lightMapIntensity,this.aoMap=t.aoMap,this.aoMapIntensity=t.aoMapIntensity,this.emissive.copy(t.emissive),this.emissiveMap=t.emissiveMap,this.emissiveIntensity=t.emissiveIntensity,this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.alphaMap=t.alphaMap,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.wireframeLinecap=t.wireframeLinecap,this.wireframeLinejoin=t.wireframeLinejoin,this.fog=t.fog,this}}class dc extends Ln{constructor(t){super(),this.isMeshNormalMaterial=!0,this.type="MeshNormalMaterial",this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new Gt(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.wireframe=!1,this.wireframeLinewidth=1,this.flatShading=!1,this.setValues(t)}copy(t){return super.copy(t),this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.flatShading=t.flatShading,this}}class pc extends Ln{constructor(t){super(),this.isMeshLambertMaterial=!0,this.type="MeshLambertMaterial",this.color=new Nn(16777215),this.map=null,this.lightMap=null,this.lightMapIntensity=1,this.aoMap=null,this.aoMapIntensity=1,this.emissive=new Nn(0),this.emissiveIntensity=1,this.emissiveMap=null,this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new Gt(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.specularMap=null,this.alphaMap=null,this.envMap=null,this.combine=0,this.reflectivity=1,this.refractionRatio=.98,this.wireframe=!1,this.wireframeLinewidth=1,this.wireframeLinecap="round",this.wireframeLinejoin="round",this.flatShading=!1,this.fog=!0,this.setValues(t)}copy(t){return super.copy(t),this.color.copy(t.color),this.map=t.map,this.lightMap=t.lightMap,this.lightMapIntensity=t.lightMapIntensity,this.aoMap=t.aoMap,this.aoMapIntensity=t.aoMapIntensity,this.emissive.copy(t.emissive),this.emissiveMap=t.emissiveMap,this.emissiveIntensity=t.emissiveIntensity,this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.specularMap=t.specularMap,this.alphaMap=t.alphaMap,this.envMap=t.envMap,this.combine=t.combine,this.reflectivity=t.reflectivity,this.refractionRatio=t.refractionRatio,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.wireframeLinecap=t.wireframeLinecap,this.wireframeLinejoin=t.wireframeLinejoin,this.flatShading=t.flatShading,this.fog=t.fog,this}}class mc extends Ln{constructor(t){super(),this.isMeshMatcapMaterial=!0,this.defines={MATCAP:""},this.type="MeshMatcapMaterial",this.color=new Nn(16777215),this.matcap=null,this.map=null,this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new Gt(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.alphaMap=null,this.flatShading=!1,this.fog=!0,this.setValues(t)}copy(t){return super.copy(t),this.defines={MATCAP:""},this.color.copy(t.color),this.matcap=t.matcap,this.map=t.map,this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.alphaMap=t.alphaMap,this.flatShading=t.flatShading,this.fog=t.fog,this}}class fc extends _o{constructor(t){super(),this.isLineDashedMaterial=!0,this.type="LineDashedMaterial",this.scale=1,this.dashSize=3,this.gapSize=1,this.setValues(t)}copy(t){return super.copy(t),this.scale=t.scale,this.dashSize=t.dashSize,this.gapSize=t.gapSize,this}}function gc(t,e,n){return _c(t)?new t.constructor(t.subarray(e,void 0!==n?n:t.length)):t.slice(e,n)}function vc(t,e,n){return!t||!n&&t.constructor===e?t:"number"==typeof e.BYTES_PER_ELEMENT?new e(t):Array.prototype.slice.call(t)}function _c(t){return ArrayBuffer.isView(t)&&!(t instanceof DataView)}function yc(t){const e=t.length,n=new Array(e);for(let t=0;t!==e;++t)n[t]=t;return n.sort((function(e,n){return t[e]-t[n]})),n}function xc(t,e,n){const i=t.length,r=new t.constructor(i);for(let s=0,a=0;a!==i;++s){const i=n[s]*e;for(let n=0;n!==e;++n)r[a++]=t[i+n]}return r}function Mc(t,e,n,i){let r=1,s=t[0];for(;void 0!==s&&void 0===s[i];)s=t[r++];if(void 0===s)return;let a=s[i];if(void 0!==a)if(Array.isArray(a))do{a=s[i],void 0!==a&&(e.push(s.time),n.push.apply(n,a)),s=t[r++]}while(void 0!==s);else if(void 0!==a.toArray)do{a=s[i],void 0!==a&&(e.push(s.time),a.toArray(n,n.length)),s=t[r++]}while(void 0!==s);else do{a=s[i],void 0!==a&&(e.push(s.time),n.push(a)),s=t[r++]}while(void 0!==s)}const Sc={arraySlice:gc,convertArray:vc,isTypedArray:_c,getKeyframeOrder:yc,sortedArray:xc,flattenJSON:Mc,subclip:function(t,e,n,i,r=30){const s=t.clone();s.name=e;const a=[];for(let t=0;t=i)){l.push(e.times[t]);for(let n=0;ns.tracks[t].times[0]&&(o=s.tracks[t].times[0]);for(let t=0;t=i.times[u]){const t=u*l+o,e=t+l-o;d=gc(i.values,t,e)}else{const t=i.createInterpolant(),e=o,n=l-o;t.evaluate(s),d=gc(t.resultBuffer,e,n)}if("quaternion"===r){(new pe).fromArray(d).normalize().conjugate().toArray(d)}const p=a.times.length;for(let t=0;t=r)break t;{const a=e[1];t=r)break e}s=n,n=0}}for(;n>>1;te;)--s;if(++s,0!==r||s!==i){r>=s&&(s=Math.max(s,1),r=s-1);const t=this.getValueSize();this.times=gc(n,r,s),this.values=gc(this.values,r*t,s*t)}return this}validate(){let t=!0;const e=this.getValueSize();e-Math.floor(e)!=0&&(console.error("THREE.KeyframeTrack: Invalid value size in track.",this),t=!1);const n=this.times,i=this.values,r=n.length;0===r&&(console.error("THREE.KeyframeTrack: Track is empty.",this),t=!1);let s=null;for(let e=0;e!==r;e++){const i=n[e];if("number"==typeof i&&isNaN(i)){console.error("THREE.KeyframeTrack: Time is not a valid number.",this,e,i),t=!1;break}if(null!==s&&s>i){console.error("THREE.KeyframeTrack: Out of order keys.",this,e,i,s),t=!1;break}s=i}if(void 0!==i&&_c(i))for(let e=0,n=i.length;e!==n;++e){const n=i[e];if(isNaN(n)){console.error("THREE.KeyframeTrack: Value is not a valid number.",this,e,n),t=!1;break}}return t}optimize(){const t=gc(this.times),e=gc(this.values),n=this.getValueSize(),i=this.getInterpolation()===ct,r=t.length-1;let s=1;for(let a=1;a0){t[s]=t[r];for(let t=r*n,i=s*n,a=0;a!==n;++a)e[i+a]=e[t+a];++s}return s!==t.length?(this.times=gc(t,0,s),this.values=gc(e,0,s*n)):(this.times=t,this.values=e),this}clone(){const t=gc(this.times,0),e=gc(this.values,0),n=new(0,this.constructor)(this.name,t,e);return n.createInterpolant=this.createInterpolant,n}}Ac.prototype.TimeBufferType=Float32Array,Ac.prototype.ValueBufferType=Float32Array,Ac.prototype.DefaultInterpolation=lt;class Rc extends Ac{}Rc.prototype.ValueTypeName="bool",Rc.prototype.ValueBufferType=Array,Rc.prototype.DefaultInterpolation=ot,Rc.prototype.InterpolantFactoryMethodLinear=void 0,Rc.prototype.InterpolantFactoryMethodSmooth=void 0;class Cc extends Ac{}Cc.prototype.ValueTypeName="color";class Lc extends Ac{}Lc.prototype.ValueTypeName="number";class Pc extends bc{constructor(t,e,n,i){super(t,e,n,i)}interpolate_(t,e,n,i){const r=this.resultBuffer,s=this.sampleValues,a=this.valueSize,o=(n-e)/(i-e);let l=t*a;for(let t=l+a;l!==t;l+=4)pe.slerpFlat(r,0,s,l-a,s,l,o);return r}}class Ic extends Ac{InterpolantFactoryMethodLinear(t){return new Pc(this.times,this.values,this.getValueSize(),t)}}Ic.prototype.ValueTypeName="quaternion",Ic.prototype.DefaultInterpolation=lt,Ic.prototype.InterpolantFactoryMethodSmooth=void 0;class Uc extends Ac{}Uc.prototype.ValueTypeName="string",Uc.prototype.ValueBufferType=Array,Uc.prototype.DefaultInterpolation=ot,Uc.prototype.InterpolantFactoryMethodLinear=void 0,Uc.prototype.InterpolantFactoryMethodSmooth=void 0;class Dc extends Ac{}Dc.prototype.ValueTypeName="vector";class Nc{constructor(t,e=-1,n,i=2500){this.name=t,this.tracks=n,this.duration=e,this.blendMode=i,this.uuid=Lt(),this.duration<0&&this.resetDuration()}static parse(t){const e=[],n=t.tracks,i=1/(t.fps||1);for(let t=0,r=n.length;t!==r;++t)e.push(Oc(n[t]).scale(i));const r=new this(t.name,t.duration,e,t.blendMode);return r.uuid=t.uuid,r}static toJSON(t){const e=[],n=t.tracks,i={name:t.name,duration:t.duration,tracks:e,uuid:t.uuid,blendMode:t.blendMode};for(let t=0,i=n.length;t!==i;++t)e.push(Ac.toJSON(n[t]));return i}static CreateFromMorphTargetSequence(t,e,n,i){const r=e.length,s=[];for(let t=0;t1){const t=s[1];let e=i[t];e||(i[t]=e=[]),e.push(n)}}const s=[];for(const t in i)s.push(this.CreateFromMorphTargetSequence(t,i[t],e,n));return s}static parseAnimation(t,e){if(!t)return console.error("THREE.AnimationClip: No animation in JSONLoader data."),null;const n=function(t,e,n,i,r){if(0!==n.length){const s=[],a=[];Mc(n,s,a,i),0!==s.length&&r.push(new t(e,s,a))}},i=[],r=t.name||"default",s=t.fps||30,a=t.blendMode;let o=t.length||-1;const l=t.hierarchy||[];for(let t=0;t{e&&e(r),this.manager.itemEnd(t)}),0),r;if(void 0!==Hc[t])return void Hc[t].push({onLoad:e,onProgress:n,onError:i});Hc[t]=[],Hc[t].push({onLoad:e,onProgress:n,onError:i});const s=new Request(t,{headers:new Headers(this.requestHeader),credentials:this.withCredentials?"include":"same-origin"}),a=this.mimeType,o=this.responseType;fetch(s).then((e=>{if(200===e.status||0===e.status){if(0===e.status&&console.warn("THREE.FileLoader: HTTP Status 0 received."),"undefined"==typeof ReadableStream||void 0===e.body||void 0===e.body.getReader)return e;const n=Hc[t],i=e.body.getReader(),r=e.headers.get("Content-Length")||e.headers.get("X-File-Size"),s=r?parseInt(r):0,a=0!==s;let o=0;const l=new ReadableStream({start(t){!function e(){i.read().then((({done:i,value:r})=>{if(i)t.close();else{o+=r.byteLength;const i=new ProgressEvent("progress",{lengthComputable:a,loaded:o,total:s});for(let t=0,e=n.length;t{switch(o){case"arraybuffer":return t.arrayBuffer();case"blob":return t.blob();case"document":return t.text().then((t=>(new DOMParser).parseFromString(t,a)));case"json":return t.json();default:if(void 0===a)return t.text();{const e=/charset="?([^;"\s]*)"?/i.exec(a),n=e&&e[1]?e[1].toLowerCase():void 0,i=new TextDecoder(n);return t.arrayBuffer().then((t=>i.decode(t)))}}})).then((e=>{Fc.add(t,e);const n=Hc[t];delete Hc[t];for(let t=0,i=n.length;t{const n=Hc[t];if(void 0===n)throw this.manager.itemError(t),e;delete Hc[t];for(let t=0,i=n.length;t{this.manager.itemEnd(t)})),this.manager.itemStart(t)}setResponseType(t){return this.responseType=t,this}setMimeType(t){return this.mimeType=t,this}}class Wc extends Gc{constructor(t){super(t)}load(t,e,n,i){void 0!==this.path&&(t=this.path+t),t=this.manager.resolveURL(t);const r=this,s=Fc.get(t);if(void 0!==s)return r.manager.itemStart(t),setTimeout((function(){e&&e(s),r.manager.itemEnd(t)}),0),s;const a=jt("img");function o(){c(),Fc.add(t,this),e&&e(this),r.manager.itemEnd(t)}function l(e){c(),i&&i(e),r.manager.itemError(t),r.manager.itemEnd(t)}function c(){a.removeEventListener("load",o,!1),a.removeEventListener("error",l,!1)}return a.addEventListener("load",o,!1),a.addEventListener("error",l,!1),"data:"!==t.slice(0,5)&&void 0!==this.crossOrigin&&(a.crossOrigin=this.crossOrigin),r.manager.itemStart(t),a.src=t,a}}class Xc extends gn{constructor(t,e=1){super(),this.isLight=!0,this.type="Light",this.color=new Nn(t),this.intensity=e}dispose(){}copy(t,e){return super.copy(t,e),this.color.copy(t.color),this.intensity=t.intensity,this}toJSON(t){const e=super.toJSON(t);return e.object.color=this.color.getHex(),e.object.intensity=this.intensity,void 0!==this.groundColor&&(e.object.groundColor=this.groundColor.getHex()),void 0!==this.distance&&(e.object.distance=this.distance),void 0!==this.angle&&(e.object.angle=this.angle),void 0!==this.decay&&(e.object.decay=this.decay),void 0!==this.penumbra&&(e.object.penumbra=this.penumbra),void 0!==this.shadow&&(e.object.shadow=this.shadow.toJSON()),e}}class jc extends Xc{constructor(t,e,n){super(t,n),this.isHemisphereLight=!0,this.type="HemisphereLight",this.position.copy(gn.DEFAULT_UP),this.updateMatrix(),this.groundColor=new Nn(e)}copy(t,e){return super.copy(t,e),this.groundColor.copy(t.groundColor),this}}const qc=new We,Yc=new me,Zc=new me;class Jc{constructor(t){this.camera=t,this.bias=0,this.normalBias=0,this.radius=1,this.blurSamples=8,this.mapSize=new Gt(512,512),this.map=null,this.mapPass=null,this.matrix=new We,this.autoUpdate=!0,this.needsUpdate=!1,this._frustum=new Gi,this._frameExtents=new Gt(1,1),this._viewportCount=1,this._viewports=[new ce(0,0,1,1)]}getViewportCount(){return this._viewportCount}getFrustum(){return this._frustum}updateMatrices(t){const e=this.camera,n=this.matrix;Yc.setFromMatrixPosition(t.matrixWorld),e.position.copy(Yc),Zc.setFromMatrixPosition(t.target.matrixWorld),e.lookAt(Zc),e.updateMatrixWorld(),qc.multiplyMatrices(e.projectionMatrix,e.matrixWorldInverse),this._frustum.setFromProjectionMatrix(qc),n.set(.5,0,0,.5,0,.5,0,.5,0,0,.5,.5,0,0,0,1),n.multiply(qc)}getViewport(t){return this._viewports[t]}getFrameExtents(){return this._frameExtents}dispose(){this.map&&this.map.dispose(),this.mapPass&&this.mapPass.dispose()}copy(t){return this.camera=t.camera.clone(),this.bias=t.bias,this.radius=t.radius,this.mapSize.copy(t.mapSize),this}clone(){return(new this.constructor).copy(this)}toJSON(){const t={};return 0!==this.bias&&(t.bias=this.bias),0!==this.normalBias&&(t.normalBias=this.normalBias),1!==this.radius&&(t.radius=this.radius),512===this.mapSize.x&&512===this.mapSize.y||(t.mapSize=this.mapSize.toArray()),t.camera=this.camera.toJSON(!1).object,delete t.camera.matrix,t}}class Kc extends Jc{constructor(){super(new Ci(50,1,.5,500)),this.isSpotLightShadow=!0,this.focus=1}updateMatrices(t){const e=this.camera,n=2*Ct*t.angle*this.focus,i=this.mapSize.width/this.mapSize.height,r=t.distance||e.far;n===e.fov&&i===e.aspect&&r===e.far||(e.fov=n,e.aspect=i,e.far=r,e.updateProjectionMatrix()),super.updateMatrices(t)}copy(t){return super.copy(t),this.focus=t.focus,this}}class $c extends Xc{constructor(t,e,n=0,i=Math.PI/3,r=0,s=2){super(t,e),this.isSpotLight=!0,this.type="SpotLight",this.position.copy(gn.DEFAULT_UP),this.updateMatrix(),this.target=new gn,this.distance=n,this.angle=i,this.penumbra=r,this.decay=s,this.map=null,this.shadow=new Kc}get power(){return this.intensity*Math.PI}set power(t){this.intensity=t/Math.PI}dispose(){this.shadow.dispose()}copy(t,e){return super.copy(t,e),this.distance=t.distance,this.angle=t.angle,this.penumbra=t.penumbra,this.decay=t.decay,this.target=t.target.clone(),this.shadow=t.shadow.clone(),this}}const Qc=new We,th=new me,eh=new me;class nh extends Jc{constructor(){super(new Ci(90,1,.5,500)),this.isPointLightShadow=!0,this._frameExtents=new Gt(4,2),this._viewportCount=6,this._viewports=[new ce(2,1,1,1),new ce(0,1,1,1),new ce(3,1,1,1),new ce(1,1,1,1),new ce(3,0,1,1),new ce(1,0,1,1)],this._cubeDirections=[new me(1,0,0),new me(-1,0,0),new me(0,0,1),new me(0,0,-1),new me(0,1,0),new me(0,-1,0)],this._cubeUps=[new me(0,1,0),new me(0,1,0),new me(0,1,0),new me(0,1,0),new me(0,0,1),new me(0,0,-1)]}updateMatrices(t,e=0){const n=this.camera,i=this.matrix,r=t.distance||n.far;r!==n.far&&(n.far=r,n.updateProjectionMatrix()),th.setFromMatrixPosition(t.matrixWorld),n.position.copy(th),eh.copy(n.position),eh.add(this._cubeDirections[e]),n.up.copy(this._cubeUps[e]),n.lookAt(eh),n.updateMatrixWorld(),i.makeTranslation(-th.x,-th.y,-th.z),Qc.multiplyMatrices(n.projectionMatrix,n.matrixWorldInverse),this._frustum.setFromProjectionMatrix(Qc)}}class ih extends Xc{constructor(t,e,n=0,i=2){super(t,e),this.isPointLight=!0,this.type="PointLight",this.distance=n,this.decay=i,this.shadow=new nh}get power(){return 4*this.intensity*Math.PI}set power(t){this.intensity=t/(4*Math.PI)}dispose(){this.shadow.dispose()}copy(t,e){return super.copy(t,e),this.distance=t.distance,this.decay=t.decay,this.shadow=t.shadow.clone(),this}}class rh extends Jc{constructor(){super(new tr(-5,5,5,-5,.5,500)),this.isDirectionalLightShadow=!0}}class sh extends Xc{constructor(t,e){super(t,e),this.isDirectionalLight=!0,this.type="DirectionalLight",this.position.copy(gn.DEFAULT_UP),this.updateMatrix(),this.target=new gn,this.shadow=new rh}dispose(){this.shadow.dispose()}copy(t){return super.copy(t),this.target=t.target.clone(),this.shadow=t.shadow.clone(),this}}class ah extends Xc{constructor(t,e){super(t,e),this.isAmbientLight=!0,this.type="AmbientLight"}}class oh extends Xc{constructor(t,e,n=10,i=10){super(t,e),this.isRectAreaLight=!0,this.type="RectAreaLight",this.width=n,this.height=i}get power(){return this.intensity*this.width*this.height*Math.PI}set power(t){this.intensity=t/(this.width*this.height*Math.PI)}copy(t){return super.copy(t),this.width=t.width,this.height=t.height,this}toJSON(t){const e=super.toJSON(t);return e.object.width=this.width,e.object.height=this.height,e}}class lh{constructor(){this.isSphericalHarmonics3=!0,this.coefficients=[];for(let t=0;t<9;t++)this.coefficients.push(new me)}set(t){for(let e=0;e<9;e++)this.coefficients[e].copy(t[e]);return this}zero(){for(let t=0;t<9;t++)this.coefficients[t].set(0,0,0);return this}getAt(t,e){const n=t.x,i=t.y,r=t.z,s=this.coefficients;return e.copy(s[0]).multiplyScalar(.282095),e.addScaledVector(s[1],.488603*i),e.addScaledVector(s[2],.488603*r),e.addScaledVector(s[3],.488603*n),e.addScaledVector(s[4],n*i*1.092548),e.addScaledVector(s[5],i*r*1.092548),e.addScaledVector(s[6],.315392*(3*r*r-1)),e.addScaledVector(s[7],n*r*1.092548),e.addScaledVector(s[8],.546274*(n*n-i*i)),e}getIrradianceAt(t,e){const n=t.x,i=t.y,r=t.z,s=this.coefficients;return e.copy(s[0]).multiplyScalar(.886227),e.addScaledVector(s[1],1.023328*i),e.addScaledVector(s[2],1.023328*r),e.addScaledVector(s[3],1.023328*n),e.addScaledVector(s[4],.858086*n*i),e.addScaledVector(s[5],.858086*i*r),e.addScaledVector(s[6],.743125*r*r-.247708),e.addScaledVector(s[7],.858086*n*r),e.addScaledVector(s[8],.429043*(n*n-i*i)),e}add(t){for(let e=0;e<9;e++)this.coefficients[e].add(t.coefficients[e]);return this}addScaledSH(t,e){for(let n=0;n<9;n++)this.coefficients[n].addScaledVector(t.coefficients[n],e);return this}scale(t){for(let e=0;e<9;e++)this.coefficients[e].multiplyScalar(t);return this}lerp(t,e){for(let n=0;n<9;n++)this.coefficients[n].lerp(t.coefficients[n],e);return this}equals(t){for(let e=0;e<9;e++)if(!this.coefficients[e].equals(t.coefficients[e]))return!1;return!0}copy(t){return this.set(t.coefficients)}clone(){return(new this.constructor).copy(this)}fromArray(t,e=0){const n=this.coefficients;for(let i=0;i<9;i++)n[i].fromArray(t,e+3*i);return this}toArray(t=[],e=0){const n=this.coefficients;for(let i=0;i<9;i++)n[i].toArray(t,e+3*i);return t}static getBasisAt(t,e){const n=t.x,i=t.y,r=t.z;e[0]=.282095,e[1]=.488603*i,e[2]=.488603*r,e[3]=.488603*n,e[4]=1.092548*n*i,e[5]=1.092548*i*r,e[6]=.315392*(3*r*r-1),e[7]=1.092548*n*r,e[8]=.546274*(n*n-i*i)}}class ch extends Xc{constructor(t=new lh,e=1){super(void 0,e),this.isLightProbe=!0,this.sh=t}copy(t){return super.copy(t),this.sh.copy(t.sh),this}fromJSON(t){return this.intensity=t.intensity,this.sh.fromArray(t.sh),this}toJSON(t){const e=super.toJSON(t);return e.object.sh=this.sh.toArray(),e}}class hh extends Gc{constructor(t){super(t),this.textures={}}load(t,e,n,i){const r=this,s=new Vc(r.manager);s.setPath(r.path),s.setRequestHeader(r.requestHeader),s.setWithCredentials(r.withCredentials),s.load(t,(function(n){try{e(r.parse(JSON.parse(n)))}catch(e){i?i(e):console.error(e),r.manager.itemError(t)}}),n,i)}parse(t){const e=this.textures;function n(t){return void 0===e[t]&&console.warn("THREE.MaterialLoader: Undefined texture",t),e[t]}const i=hh.createMaterialFromType(t.type);if(void 0!==t.uuid&&(i.uuid=t.uuid),void 0!==t.name&&(i.name=t.name),void 0!==t.color&&void 0!==i.color&&i.color.setHex(t.color),void 0!==t.roughness&&(i.roughness=t.roughness),void 0!==t.metalness&&(i.metalness=t.metalness),void 0!==t.sheen&&(i.sheen=t.sheen),void 0!==t.sheenColor&&(i.sheenColor=(new Nn).setHex(t.sheenColor)),void 0!==t.sheenRoughness&&(i.sheenRoughness=t.sheenRoughness),void 0!==t.emissive&&void 0!==i.emissive&&i.emissive.setHex(t.emissive),void 0!==t.specular&&void 0!==i.specular&&i.specular.setHex(t.specular),void 0!==t.specularIntensity&&(i.specularIntensity=t.specularIntensity),void 0!==t.specularColor&&void 0!==i.specularColor&&i.specularColor.setHex(t.specularColor),void 0!==t.shininess&&(i.shininess=t.shininess),void 0!==t.clearcoat&&(i.clearcoat=t.clearcoat),void 0!==t.clearcoatRoughness&&(i.clearcoatRoughness=t.clearcoatRoughness),void 0!==t.iridescence&&(i.iridescence=t.iridescence),void 0!==t.iridescenceIOR&&(i.iridescenceIOR=t.iridescenceIOR),void 0!==t.iridescenceThicknessRange&&(i.iridescenceThicknessRange=t.iridescenceThicknessRange),void 0!==t.transmission&&(i.transmission=t.transmission),void 0!==t.thickness&&(i.thickness=t.thickness),void 0!==t.attenuationDistance&&(i.attenuationDistance=t.attenuationDistance),void 0!==t.attenuationColor&&void 0!==i.attenuationColor&&i.attenuationColor.setHex(t.attenuationColor),void 0!==t.anisotropy&&(i.anisotropy=t.anisotropy),void 0!==t.anisotropyRotation&&(i.anisotropyRotation=t.anisotropyRotation),void 0!==t.fog&&(i.fog=t.fog),void 0!==t.flatShading&&(i.flatShading=t.flatShading),void 0!==t.blending&&(i.blending=t.blending),void 0!==t.combine&&(i.combine=t.combine),void 0!==t.side&&(i.side=t.side),void 0!==t.shadowSide&&(i.shadowSide=t.shadowSide),void 0!==t.opacity&&(i.opacity=t.opacity),void 0!==t.transparent&&(i.transparent=t.transparent),void 0!==t.alphaTest&&(i.alphaTest=t.alphaTest),void 0!==t.depthTest&&(i.depthTest=t.depthTest),void 0!==t.depthWrite&&(i.depthWrite=t.depthWrite),void 0!==t.colorWrite&&(i.colorWrite=t.colorWrite),void 0!==t.stencilWrite&&(i.stencilWrite=t.stencilWrite),void 0!==t.stencilWriteMask&&(i.stencilWriteMask=t.stencilWriteMask),void 0!==t.stencilFunc&&(i.stencilFunc=t.stencilFunc),void 0!==t.stencilRef&&(i.stencilRef=t.stencilRef),void 0!==t.stencilFuncMask&&(i.stencilFuncMask=t.stencilFuncMask),void 0!==t.stencilFail&&(i.stencilFail=t.stencilFail),void 0!==t.stencilZFail&&(i.stencilZFail=t.stencilZFail),void 0!==t.stencilZPass&&(i.stencilZPass=t.stencilZPass),void 0!==t.wireframe&&(i.wireframe=t.wireframe),void 0!==t.wireframeLinewidth&&(i.wireframeLinewidth=t.wireframeLinewidth),void 0!==t.wireframeLinecap&&(i.wireframeLinecap=t.wireframeLinecap),void 0!==t.wireframeLinejoin&&(i.wireframeLinejoin=t.wireframeLinejoin),void 0!==t.rotation&&(i.rotation=t.rotation),1!==t.linewidth&&(i.linewidth=t.linewidth),void 0!==t.dashSize&&(i.dashSize=t.dashSize),void 0!==t.gapSize&&(i.gapSize=t.gapSize),void 0!==t.scale&&(i.scale=t.scale),void 0!==t.polygonOffset&&(i.polygonOffset=t.polygonOffset),void 0!==t.polygonOffsetFactor&&(i.polygonOffsetFactor=t.polygonOffsetFactor),void 0!==t.polygonOffsetUnits&&(i.polygonOffsetUnits=t.polygonOffsetUnits),void 0!==t.dithering&&(i.dithering=t.dithering),void 0!==t.alphaToCoverage&&(i.alphaToCoverage=t.alphaToCoverage),void 0!==t.premultipliedAlpha&&(i.premultipliedAlpha=t.premultipliedAlpha),void 0!==t.forceSinglePass&&(i.forceSinglePass=t.forceSinglePass),void 0!==t.visible&&(i.visible=t.visible),void 0!==t.toneMapped&&(i.toneMapped=t.toneMapped),void 0!==t.userData&&(i.userData=t.userData),void 0!==t.vertexColors&&("number"==typeof t.vertexColors?i.vertexColors=t.vertexColors>0:i.vertexColors=t.vertexColors),void 0!==t.uniforms)for(const e in t.uniforms){const r=t.uniforms[e];switch(i.uniforms[e]={},r.type){case"t":i.uniforms[e].value=n(r.value);break;case"c":i.uniforms[e].value=(new Nn).setHex(r.value);break;case"v2":i.uniforms[e].value=(new Gt).fromArray(r.value);break;case"v3":i.uniforms[e].value=(new me).fromArray(r.value);break;case"v4":i.uniforms[e].value=(new ce).fromArray(r.value);break;case"m3":i.uniforms[e].value=(new Ht).fromArray(r.value);break;case"m4":i.uniforms[e].value=(new We).fromArray(r.value);break;default:i.uniforms[e].value=r.value}}if(void 0!==t.defines&&(i.defines=t.defines),void 0!==t.vertexShader&&(i.vertexShader=t.vertexShader),void 0!==t.fragmentShader&&(i.fragmentShader=t.fragmentShader),void 0!==t.glslVersion&&(i.glslVersion=t.glslVersion),void 0!==t.extensions)for(const e in t.extensions)i.extensions[e]=t.extensions[e];if(void 0!==t.lights&&(i.lights=t.lights),void 0!==t.clipping&&(i.clipping=t.clipping),void 0!==t.size&&(i.size=t.size),void 0!==t.sizeAttenuation&&(i.sizeAttenuation=t.sizeAttenuation),void 0!==t.map&&(i.map=n(t.map)),void 0!==t.matcap&&(i.matcap=n(t.matcap)),void 0!==t.alphaMap&&(i.alphaMap=n(t.alphaMap)),void 0!==t.bumpMap&&(i.bumpMap=n(t.bumpMap)),void 0!==t.bumpScale&&(i.bumpScale=t.bumpScale),void 0!==t.normalMap&&(i.normalMap=n(t.normalMap)),void 0!==t.normalMapType&&(i.normalMapType=t.normalMapType),void 0!==t.normalScale){let e=t.normalScale;!1===Array.isArray(e)&&(e=[e,e]),i.normalScale=(new Gt).fromArray(e)}return void 0!==t.displacementMap&&(i.displacementMap=n(t.displacementMap)),void 0!==t.displacementScale&&(i.displacementScale=t.displacementScale),void 0!==t.displacementBias&&(i.displacementBias=t.displacementBias),void 0!==t.roughnessMap&&(i.roughnessMap=n(t.roughnessMap)),void 0!==t.metalnessMap&&(i.metalnessMap=n(t.metalnessMap)),void 0!==t.emissiveMap&&(i.emissiveMap=n(t.emissiveMap)),void 0!==t.emissiveIntensity&&(i.emissiveIntensity=t.emissiveIntensity),void 0!==t.specularMap&&(i.specularMap=n(t.specularMap)),void 0!==t.specularIntensityMap&&(i.specularIntensityMap=n(t.specularIntensityMap)),void 0!==t.specularColorMap&&(i.specularColorMap=n(t.specularColorMap)),void 0!==t.envMap&&(i.envMap=n(t.envMap)),void 0!==t.envMapIntensity&&(i.envMapIntensity=t.envMapIntensity),void 0!==t.reflectivity&&(i.reflectivity=t.reflectivity),void 0!==t.refractionRatio&&(i.refractionRatio=t.refractionRatio),void 0!==t.lightMap&&(i.lightMap=n(t.lightMap)),void 0!==t.lightMapIntensity&&(i.lightMapIntensity=t.lightMapIntensity),void 0!==t.aoMap&&(i.aoMap=n(t.aoMap)),void 0!==t.aoMapIntensity&&(i.aoMapIntensity=t.aoMapIntensity),void 0!==t.gradientMap&&(i.gradientMap=n(t.gradientMap)),void 0!==t.clearcoatMap&&(i.clearcoatMap=n(t.clearcoatMap)),void 0!==t.clearcoatRoughnessMap&&(i.clearcoatRoughnessMap=n(t.clearcoatRoughnessMap)),void 0!==t.clearcoatNormalMap&&(i.clearcoatNormalMap=n(t.clearcoatNormalMap)),void 0!==t.clearcoatNormalScale&&(i.clearcoatNormalScale=(new Gt).fromArray(t.clearcoatNormalScale)),void 0!==t.iridescenceMap&&(i.iridescenceMap=n(t.iridescenceMap)),void 0!==t.iridescenceThicknessMap&&(i.iridescenceThicknessMap=n(t.iridescenceThicknessMap)),void 0!==t.transmissionMap&&(i.transmissionMap=n(t.transmissionMap)),void 0!==t.thicknessMap&&(i.thicknessMap=n(t.thicknessMap)),void 0!==t.anisotropyMap&&(i.anisotropyMap=n(t.anisotropyMap)),void 0!==t.sheenColorMap&&(i.sheenColorMap=n(t.sheenColorMap)),void 0!==t.sheenRoughnessMap&&(i.sheenRoughnessMap=n(t.sheenRoughnessMap)),i}setTextures(t){return this.textures=t,this}static createMaterialFromType(t){return new{ShadowMaterial:ac,SpriteMaterial:Ra,RawShaderMaterial:oc,ShaderMaterial:Ai,PointsMaterial:Co,MeshPhysicalMaterial:cc,MeshStandardMaterial:lc,MeshPhongMaterial:hc,MeshToonMaterial:uc,MeshNormalMaterial:dc,MeshLambertMaterial:pc,MeshDepthMaterial:sa,MeshDistanceMaterial:aa,MeshBasicMaterial:Fn,MeshMatcapMaterial:mc,LineDashedMaterial:fc,LineBasicMaterial:_o,Material:Ln}[t]}}class uh{static decodeText(t){if("undefined"!=typeof TextDecoder)return(new TextDecoder).decode(t);let e="";for(let n=0,i=t.length;n0){this.source.connect(this.filters[0]);for(let t=1,e=this.filters.length;t0){this.source.disconnect(this.filters[0]);for(let t=1,e=this.filters.length;t0&&this._mixBufferRegionAdditive(n,i,this._addIndex*e,1,e);for(let t=e,r=e+e;t!==r;++t)if(n[t]!==n[t+e]){a.setValue(n,i);break}}saveOriginalState(){const t=this.binding,e=this.buffer,n=this.valueSize,i=n*this._origIndex;t.getValue(e,i);for(let t=n,r=i;t!==r;++t)e[t]=e[i+t%n];this._setIdentity(),this.cumulativeWeight=0,this.cumulativeWeightAdditive=0}restoreOriginalState(){const t=3*this.valueSize;this.binding.setValue(this.buffer,t)}_setAdditiveIdentityNumeric(){const t=this._addIndex*this.valueSize,e=t+this.valueSize;for(let n=t;n=.5)for(let i=0;i!==r;++i)t[e+i]=t[n+i]}_slerp(t,e,n,i){pe.slerpFlat(t,e,t,e,t,n,i)}_slerpAdditive(t,e,n,i,r){const s=this._workIndex*r;pe.multiplyQuaternionsFlat(t,s,t,e,t,n),pe.slerpFlat(t,e,t,e,t,s,i)}_lerp(t,e,n,i,r){const s=1-i;for(let a=0;a!==r;++a){const r=e+a;t[r]=t[r]*s+t[n+a]*i}}_lerpAdditive(t,e,n,i,r){for(let s=0;s!==r;++s){const r=e+s;t[r]=t[r]+t[n+s]*i}}}const Dh="\\[\\]\\.:\\/",Nh=new RegExp("["+Dh+"]","g"),Oh="[^"+Dh+"]",Fh="[^"+Dh.replace("\\.","")+"]",Bh=new RegExp("^"+/((?:WC+[\/:])*)/.source.replace("WC",Oh)+/(WCOD+)?/.source.replace("WCOD",Fh)+/(?:\.(WC+)(?:\[(.+)\])?)?/.source.replace("WC",Oh)+/\.(WC+)(?:\[(.+)\])?/.source.replace("WC",Oh)+"$"),zh=["material","materials","bones","map"];class Gh{constructor(t,e,n){this.path=e,this.parsedPath=n||Gh.parseTrackName(e),this.node=Gh.findNode(t,this.parsedPath.nodeName),this.rootNode=t,this.getValue=this._getValue_unbound,this.setValue=this._setValue_unbound}static create(t,e,n){return t&&t.isAnimationObjectGroup?new Gh.Composite(t,e,n):new Gh(t,e,n)}static sanitizeNodeName(t){return t.replace(/\s/g,"_").replace(Nh,"")}static parseTrackName(t){const e=Bh.exec(t);if(null===e)throw new Error("PropertyBinding: Cannot parse trackName: "+t);const n={nodeName:e[2],objectName:e[3],objectIndex:e[4],propertyName:e[5],propertyIndex:e[6]},i=n.nodeName&&n.nodeName.lastIndexOf(".");if(void 0!==i&&-1!==i){const t=n.nodeName.substring(i+1);-1!==zh.indexOf(t)&&(n.nodeName=n.nodeName.substring(0,i),n.objectName=t)}if(null===n.propertyName||0===n.propertyName.length)throw new Error("PropertyBinding: can not parse propertyName from trackName: "+t);return n}static findNode(t,e){if(void 0===e||""===e||"."===e||-1===e||e===t.name||e===t.uuid)return t;if(t.skeleton){const n=t.skeleton.getBoneByName(e);if(void 0!==n)return n}if(t.children){const n=function(t){for(let i=0;i0){const t=this._interpolants,e=this._propertyBindings;if(this.blendMode===mt)for(let n=0,i=t.length;n!==i;++n)t[n].evaluate(s),e[n].accumulateAdditive(a);else for(let n=0,r=t.length;n!==r;++n)t[n].evaluate(s),e[n].accumulate(i,a)}}_updateWeight(t){let e=0;if(this.enabled){e=this.weight;const n=this._weightInterpolant;if(null!==n){const i=n.evaluate(t)[0];e*=i,t>n.parameterPositions[1]&&(this.stopFading(),0===i&&(this.enabled=!1))}}return this._effectiveWeight=e,e}_updateTimeScale(t){let e=0;if(!this.paused){e=this.timeScale;const n=this._timeScaleInterpolant;if(null!==n){e*=n.evaluate(t)[0],t>n.parameterPositions[1]&&(this.stopWarping(),0===e?this.paused=!0:this.timeScale=e)}}return this._effectiveTimeScale=e,e}_updateTime(t){const e=this._clip.duration,n=this.loop;let i=this.time+t,r=this._loopCount;const s=2202===n;if(0===t)return-1===r?i:s&&1==(1&r)?e-i:i;if(2200===n){-1===r&&(this._loopCount=0,this._setEndings(!0,!0,!1));t:{if(i>=e)i=e;else{if(!(i<0)){this.time=i;break t}i=0}this.clampWhenFinished?this.paused=!0:this.enabled=!1,this.time=i,this._mixer.dispatchEvent({type:"finished",action:this,direction:t<0?-1:1})}}else{if(-1===r&&(t>=0?(r=0,this._setEndings(!0,0===this.repetitions,s)):this._setEndings(0===this.repetitions,!0,s)),i>=e||i<0){const n=Math.floor(i/e);i-=e*n,r+=Math.abs(n);const a=this.repetitions-r;if(a<=0)this.clampWhenFinished?this.paused=!0:this.enabled=!1,i=t>0?e:0,this.time=i,this._mixer.dispatchEvent({type:"finished",action:this,direction:t>0?1:-1});else{if(1===a){const e=t<0;this._setEndings(e,!e,s)}else this._setEndings(!1,!1,s);this._loopCount=r,this.time=i,this._mixer.dispatchEvent({type:"loop",action:this,loopDelta:n})}}else this.time=i;if(s&&1==(1&r))return e-i}return i}_setEndings(t,e,n){const i=this._interpolantSettings;n?(i.endingStart=ut,i.endingEnd=ut):(i.endingStart=t?this.zeroSlopeAtStart?ut:ht:dt,i.endingEnd=e?this.zeroSlopeAtEnd?ut:ht:dt)}_scheduleFading(t,e,n){const i=this._mixer,r=i.time;let s=this._weightInterpolant;null===s&&(s=i._lendControlInterpolant(),this._weightInterpolant=s);const a=s.parameterPositions,o=s.sampleValues;return a[0]=r,o[0]=e,a[1]=r+t,o[1]=n,this}}const kh=new Float32Array(1);class Vh{constructor(t){this.value=t}clone(){return new Vh(void 0===this.value.clone?this.value:this.value.clone())}}let Wh=0;function Xh(t,e){return t.distance-e.distance}function jh(t,e,n,i){if(t.layers.test(e.layers)&&t.raycast(e,n),!0===i){const i=t.children;for(let t=0,r=i.length;t=0;--e)t[e].stop();return this}update(t){t*=this.timeScale;const e=this._actions,n=this._nActiveActions,i=this.time+=t,r=Math.sign(t),s=this._accuIndex^=1;for(let a=0;a!==n;++a){e[a]._update(i,t,r,s)}const a=this._bindings,o=this._nActiveBindings;for(let t=0;t!==o;++t)a[t].apply(s);return this}setTime(t){this.time=0;for(let t=0;t=r){const s=r++,c=t[s];e[c.uuid]=l,t[l]=c,e[o]=s,t[s]=a;for(let t=0,e=i;t!==e;++t){const e=n[t],i=e[s],r=e[l];e[l]=i,e[s]=r}}}this.nCachedObjects_=r}uncache(){const t=this._objects,e=this._indicesByUUID,n=this._bindings,i=n.length;let r=this.nCachedObjects_,s=t.length;for(let a=0,o=arguments.length;a!==o;++a){const o=arguments[a].uuid,l=e[o];if(void 0!==l)if(delete e[o],l0&&(e[a.uuid]=l),t[l]=a,t.pop();for(let t=0,e=i;t!==e;++t){const e=n[t];e[l]=e[r],e.pop()}}}this.nCachedObjects_=r}subscribe_(t,e){const n=this._bindingsIndicesByPath;let i=n[t];const r=this._bindings;if(void 0!==i)return r[i];const s=this._paths,a=this._parsedPaths,o=this._objects,l=o.length,c=this.nCachedObjects_,h=new Array(l);i=r.length,n[t]=i,s.push(t),a.push(e),r.push(h);for(let n=c,i=o.length;n!==i;++n){const i=o[n];h[n]=new Gh(i,t,e)}return h}unsubscribe_(t){const e=this._bindingsIndicesByPath,n=e[t];if(void 0!==n){const i=this._paths,r=this._parsedPaths,s=this._bindings,a=s.length-1,o=s[a];e[t[a]]=n,s[n]=o,s.pop(),r[n]=r[a],r.pop(),i[n]=i[a],i.pop()}}},t.AnimationUtils=Sc,t.ArcCurve=zo,t.ArrayCamera=ua,t.ArrowHelper=class extends gn{constructor(t=new me(0,0,1),e=new me(0,0,0),n=1,i=16776960,r=.2*n,s=.2*r){super(),this.type="ArrowHelper",void 0===du&&(du=new ni,du.setAttribute("position",new Yn([0,0,0,0,1,0],3)),pu=new ll(0,.5,1,5,1),pu.translate(0,-.5,0)),this.position.copy(e),this.line=new To(du,new _o({color:i,toneMapped:!1})),this.line.matrixAutoUpdate=!1,this.add(this.line),this.cone=new xi(pu,new Fn({color:i,toneMapped:!1})),this.cone.matrixAutoUpdate=!1,this.add(this.cone),this.setDirection(t),this.setLength(n,r,s)}setDirection(t){if(t.y>.99999)this.quaternion.set(0,0,0,1);else if(t.y<-.99999)this.quaternion.set(1,0,0,0);else{uu.set(t.z,0,-t.x).normalize();const e=Math.acos(t.y);this.quaternion.setFromAxisAngle(uu,e)}}setLength(t,e=.2*t,n=.2*e){this.line.scale.set(1,Math.max(1e-4,t-e),1),this.line.updateMatrix(),this.cone.scale.set(n,e,n),this.cone.position.y=t,this.cone.updateMatrix()}setColor(t){this.line.material.color.set(t),this.cone.material.color.set(t)}copy(t){return super.copy(t,!1),this.line.copy(t.line),this.cone.copy(t.cone),this}dispose(){this.line.geometry.dispose(),this.line.material.dispose(),this.cone.geometry.dispose(),this.cone.material.dispose()}},t.Audio=Rh,t.AudioAnalyser=class{constructor(t,e=2048){this.analyser=t.context.createAnalyser(),this.analyser.fftSize=e,this.data=new Uint8Array(this.analyser.frequencyBinCount),t.getOutput().connect(this.analyser)}getFrequencyData(){return this.analyser.getByteFrequencyData(this.data),this.data}getAverageFrequency(){let t=0;const e=this.getFrequencyData();for(let n=0;nthis.max.x||t.ythis.max.y)}containsBox(t){return this.min.x<=t.min.x&&t.max.x<=this.max.x&&this.min.y<=t.min.y&&t.max.y<=this.max.y}getParameter(t,e){return e.set((t.x-this.min.x)/(this.max.x-this.min.x),(t.y-this.min.y)/(this.max.y-this.min.y))}intersectsBox(t){return!(t.max.xthis.max.x||t.max.ythis.max.y)}clampPoint(t,e){return e.copy(t).clamp(this.min,this.max)}distanceToPoint(t){return this.clampPoint(t,qh).distanceTo(t)}intersect(t){return this.min.max(t.min),this.max.min(t.max),this.isEmpty()&&this.makeEmpty(),this}union(t){return this.min.min(t.min),this.max.max(t.max),this}translate(t){return this.min.add(t),this.max.add(t),this}equals(t){return t.min.equals(this.min)&&t.max.equals(this.max)}},t.Box3=ve,t.Box3Helper=class extends Ao{constructor(t,e=16776960){const n=new Uint16Array([0,1,1,2,2,3,3,0,4,5,5,6,6,7,7,4,0,4,1,5,2,6,3,7]),i=new ni;i.setIndex(new Xn(n,1)),i.setAttribute("position",new Yn([1,1,1,-1,1,1,-1,-1,1,1,-1,1,1,1,-1,-1,1,-1,-1,-1,-1,1,-1,-1],3)),super(i,new _o({color:e,toneMapped:!1})),this.box=t,this.type="Box3Helper",this.geometry.computeBoundingSphere()}updateMatrixWorld(t){const e=this.box;e.isEmpty()||(e.getCenter(this.position),e.getSize(this.scale),this.scale.multiplyScalar(.5),super.updateMatrixWorld(t))}dispose(){this.geometry.dispose(),this.material.dispose()}},t.BoxBufferGeometry=class extends Si{constructor(t,e,n,i,r,s){console.warn("THREE.BoxBufferGeometry has been renamed to THREE.BoxGeometry."),super(t,e,n,i,r,s)}},t.BoxGeometry=Si,t.BoxHelper=class extends Ao{constructor(t,e=16776960){const n=new Uint16Array([0,1,1,2,2,3,3,0,4,5,5,6,6,7,7,4,0,4,1,5,2,6,3,7]),i=new Float32Array(24),r=new ni;r.setIndex(new Xn(n,1)),r.setAttribute("position",new Xn(i,3)),super(r,new _o({color:e,toneMapped:!1})),this.object=t,this.type="BoxHelper",this.matrixAutoUpdate=!1,this.update()}update(t){if(void 0!==t&&console.warn("THREE.BoxHelper: .update() has no longer arguments."),void 0!==this.object&&hu.setFromObject(this.object),hu.isEmpty())return;const e=hu.min,n=hu.max,i=this.geometry.attributes.position,r=i.array;r[0]=n.x,r[1]=n.y,r[2]=n.z,r[3]=e.x,r[4]=n.y,r[5]=n.z,r[6]=e.x,r[7]=e.y,r[8]=n.z,r[9]=n.x,r[10]=e.y,r[11]=n.z,r[12]=n.x,r[13]=n.y,r[14]=e.z,r[15]=e.x,r[16]=n.y,r[17]=e.z,r[18]=e.x,r[19]=e.y,r[20]=e.z,r[21]=n.x,r[22]=e.y,r[23]=e.z,i.needsUpdate=!0,this.geometry.computeBoundingSphere()}setFromObject(t){return this.object=t,this.update(),this}copy(t,e){return super.copy(t,e),this.object=t.object,this}dispose(){this.geometry.dispose(),this.material.dispose()}},t.BufferAttribute=Xn,t.BufferGeometry=ni,t.BufferGeometryLoader=ph,t.ByteType=1010,t.Cache=Fc,t.Camera=Ri,t.CameraHelper=class extends Ao{constructor(t){const e=new ni,n=new _o({color:16777215,vertexColors:!0,toneMapped:!1}),i=[],r=[],s={};function a(t,e){o(t),o(e)}function o(t){i.push(0,0,0),r.push(0,0,0),void 0===s[t]&&(s[t]=[]),s[t].push(i.length/3-1)}a("n1","n2"),a("n2","n4"),a("n4","n3"),a("n3","n1"),a("f1","f2"),a("f2","f4"),a("f4","f3"),a("f3","f1"),a("n1","f1"),a("n2","f2"),a("n3","f3"),a("n4","f4"),a("p","n1"),a("p","n2"),a("p","n3"),a("p","n4"),a("u1","u2"),a("u2","u3"),a("u3","u1"),a("c","t"),a("p","c"),a("cn1","cn2"),a("cn3","cn4"),a("cf1","cf2"),a("cf3","cf4"),e.setAttribute("position",new Yn(i,3)),e.setAttribute("color",new Yn(r,3)),super(e,n),this.type="CameraHelper",this.camera=t,this.camera.updateProjectionMatrix&&this.camera.updateProjectionMatrix(),this.matrix=t.matrixWorld,this.matrixAutoUpdate=!1,this.pointMap=s,this.update();const l=new Nn(16755200),c=new Nn(16711680),h=new Nn(43775),u=new Nn(16777215),d=new Nn(3355443);this.setColors(l,c,h,u,d)}setColors(t,e,n,i,r){const s=this.geometry.getAttribute("color");s.setXYZ(0,t.r,t.g,t.b),s.setXYZ(1,t.r,t.g,t.b),s.setXYZ(2,t.r,t.g,t.b),s.setXYZ(3,t.r,t.g,t.b),s.setXYZ(4,t.r,t.g,t.b),s.setXYZ(5,t.r,t.g,t.b),s.setXYZ(6,t.r,t.g,t.b),s.setXYZ(7,t.r,t.g,t.b),s.setXYZ(8,t.r,t.g,t.b),s.setXYZ(9,t.r,t.g,t.b),s.setXYZ(10,t.r,t.g,t.b),s.setXYZ(11,t.r,t.g,t.b),s.setXYZ(12,t.r,t.g,t.b),s.setXYZ(13,t.r,t.g,t.b),s.setXYZ(14,t.r,t.g,t.b),s.setXYZ(15,t.r,t.g,t.b),s.setXYZ(16,t.r,t.g,t.b),s.setXYZ(17,t.r,t.g,t.b),s.setXYZ(18,t.r,t.g,t.b),s.setXYZ(19,t.r,t.g,t.b),s.setXYZ(20,t.r,t.g,t.b),s.setXYZ(21,t.r,t.g,t.b),s.setXYZ(22,t.r,t.g,t.b),s.setXYZ(23,t.r,t.g,t.b),s.setXYZ(24,e.r,e.g,e.b),s.setXYZ(25,e.r,e.g,e.b),s.setXYZ(26,e.r,e.g,e.b),s.setXYZ(27,e.r,e.g,e.b),s.setXYZ(28,e.r,e.g,e.b),s.setXYZ(29,e.r,e.g,e.b),s.setXYZ(30,e.r,e.g,e.b),s.setXYZ(31,e.r,e.g,e.b),s.setXYZ(32,n.r,n.g,n.b),s.setXYZ(33,n.r,n.g,n.b),s.setXYZ(34,n.r,n.g,n.b),s.setXYZ(35,n.r,n.g,n.b),s.setXYZ(36,n.r,n.g,n.b),s.setXYZ(37,n.r,n.g,n.b),s.setXYZ(38,i.r,i.g,i.b),s.setXYZ(39,i.r,i.g,i.b),s.setXYZ(40,r.r,r.g,r.b),s.setXYZ(41,r.r,r.g,r.b),s.setXYZ(42,r.r,r.g,r.b),s.setXYZ(43,r.r,r.g,r.b),s.setXYZ(44,r.r,r.g,r.b),s.setXYZ(45,r.r,r.g,r.b),s.setXYZ(46,r.r,r.g,r.b),s.setXYZ(47,r.r,r.g,r.b),s.setXYZ(48,r.r,r.g,r.b),s.setXYZ(49,r.r,r.g,r.b),s.needsUpdate=!0}update(){const t=this.geometry,e=this.pointMap;lu.projectionMatrixInverse.copy(this.camera.projectionMatrixInverse),cu("c",e,t,lu,0,0,-1),cu("t",e,t,lu,0,0,1),cu("n1",e,t,lu,-1,-1,-1),cu("n2",e,t,lu,1,-1,-1),cu("n3",e,t,lu,-1,1,-1),cu("n4",e,t,lu,1,1,-1),cu("f1",e,t,lu,-1,-1,1),cu("f2",e,t,lu,1,-1,1),cu("f3",e,t,lu,-1,1,1),cu("f4",e,t,lu,1,1,1),cu("u1",e,t,lu,.7,1.1,-1),cu("u2",e,t,lu,-.7,1.1,-1),cu("u3",e,t,lu,0,2,-1),cu("cf1",e,t,lu,-1,0,1),cu("cf2",e,t,lu,1,0,1),cu("cf3",e,t,lu,0,-1,1),cu("cf4",e,t,lu,0,1,1),cu("cn1",e,t,lu,-1,0,-1),cu("cn2",e,t,lu,1,0,-1),cu("cn3",e,t,lu,0,-1,-1),cu("cn4",e,t,lu,0,1,-1),t.getAttribute("position").needsUpdate=!0}dispose(){this.geometry.dispose(),this.material.dispose()}},t.CanvasTexture=class extends le{constructor(t,e,n,i,r,s,a,o,l){super(t,e,n,i,r,s,a,o,l),this.isCanvasTexture=!0,this.needsUpdate=!0}},t.CapsuleBufferGeometry=class extends al{constructor(t,e,n,i){console.warn("THREE.CapsuleBufferGeometry has been renamed to THREE.CapsuleGeometry."),super(t,e,n,i)}},t.CapsuleGeometry=al,t.CatmullRomCurve3=Xo,t.CineonToneMapping=3,t.CircleBufferGeometry=class extends ol{constructor(t,e,n,i){console.warn("THREE.CircleBufferGeometry has been renamed to THREE.CircleGeometry."),super(t,e,n,i)}},t.CircleGeometry=ol,t.ClampToEdgeWrapping=h,t.Clock=Sh,t.Color=Nn,t.ColorKeyframeTrack=Cc,t.ColorManagement=ee,t.CompressedArrayTexture=class extends Oo{constructor(t,e,n,i,r,s){super(t,e,n,r,s),this.isCompressedArrayTexture=!0,this.image.depth=i,this.wrapR=h}},t.CompressedTexture=Oo,t.CompressedTextureLoader=class extends Gc{constructor(t){super(t)}load(t,e,n,i){const r=this,s=[],a=new Oo,o=new Vc(this.manager);o.setPath(this.path),o.setResponseType("arraybuffer"),o.setRequestHeader(this.requestHeader),o.setWithCredentials(r.withCredentials);let l=0;function c(c){o.load(t[c],(function(t){const n=r.parse(t,!0);s[c]={width:n.width,height:n.height,format:n.format,mipmaps:n.mipmaps},l+=1,6===l&&(1===n.mipmapCount&&(a.minFilter=f),a.image=s,a.format=n.format,a.needsUpdate=!0,e&&e(a))}),n,i)}if(Array.isArray(t))for(let e=0,n=t.length;e0){const n=new Bc(e);r=new Wc(n),r.setCrossOrigin(this.crossOrigin);for(let e=0,n=t.length;e0){i=new Wc(this.manager),i.setCrossOrigin(this.crossOrigin);for(let e=0,i=t.length;e1)for(let n=0;nNumber.EPSILON){if(l<0&&(n=e[s],o=-o,a=e[r],l=-l),t.ya.y)continue;if(t.y===n.y){if(t.x===n.x)return!0}else{const e=l*(t.x-n.x)-o*(t.y-n.y);if(0===e)return!0;if(e<0)continue;i=!i}}else{if(t.y!==n.y)continue;if(a.x<=t.x&&t.x<=n.x||n.x<=t.x&&t.x<=a.x)return!0}}return i}const n=Vl.isClockWise,i=this.subPaths;if(0===i.length)return[];let r,s,a;const o=[];if(1===i.length)return s=i[0],a=new vl,a.curves=s.curves,o.push(a),o;let l=!n(i[0].getPoints());l=t?!l:l;const c=[],h=[];let u,d,p=[],m=0;h[m]=void 0,p[m]=[];for(let e=0,a=i.length;e1){let t=!1,n=0;for(let t=0,e=h.length;t0&&!1===t&&(p=c)}for(let t=0,e=h.length;t=t.HAVE_CURRENT_DATA&&(this.needsUpdate=!0)}},t.WebGL1Renderer=Ma,t.WebGL3DRenderTarget=class extends he{constructor(t=1,e=1,n=1){super(t,e),this.isWebGL3DRenderTarget=!0,this.depth=n,this.texture=new de(null,t,e,n),this.texture.isRenderTargetTexture=!0}},t.WebGLArrayRenderTarget=class extends he{constructor(t=1,e=1,n=1){super(t,e),this.isWebGLArrayRenderTarget=!0,this.depth=n,this.texture=new ue(null,t,e,n),this.texture.isRenderTargetTexture=!0}},t.WebGLCubeRenderTarget=Ui,t.WebGLMultipleRenderTargets=class extends he{constructor(t=1,e=1,n=1,i={}){super(t,e,i),this.isWebGLMultipleRenderTargets=!0;const r=this.texture;this.texture=[];for(let t=0;t> 8 & 0xff ] + _lut[ d0 >> 16 & 0xff ] + _lut[ d0 >> 24 & 0xff ] + '-' + - _lut[ d1 & 0xff ] + _lut[ d1 >> 8 & 0xff ] + '-' + _lut[ d1 >> 16 & 0x0f | 0x40 ] + _lut[ d1 >> 24 & 0xff ] + '-' + - _lut[ d2 & 0x3f | 0x80 ] + _lut[ d2 >> 8 & 0xff ] + '-' + _lut[ d2 >> 16 & 0xff ] + _lut[ d2 >> 24 & 0xff ] + - _lut[ d3 & 0xff ] + _lut[ d3 >> 8 & 0xff ] + _lut[ d3 >> 16 & 0xff ] + _lut[ d3 >> 24 & 0xff ]; + } - // .toLowerCase() here flattens concatenated strings to save heap memory space. - return uuid.toLowerCase(); + } else if ( array instanceof Int16Array ) { -} + type = gl.SHORT; -function clamp( value, min, max ) { + } else if ( array instanceof Uint32Array ) { - return Math.max( min, Math.min( max, value ) ); + type = gl.UNSIGNED_INT; -} + } else if ( array instanceof Int32Array ) { -// compute euclidean modulo of m % n -// https://en.wikipedia.org/wiki/Modulo_operation -function euclideanModulo( n, m ) { + type = gl.INT; - return ( ( n % m ) + m ) % m; + } else if ( array instanceof Int8Array ) { -} + type = gl.BYTE; -// Linear mapping from range to range -function mapLinear( x, a1, a2, b1, b2 ) { + } else if ( array instanceof Uint8Array ) { - return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 ); + type = gl.UNSIGNED_BYTE; -} + } else if ( array instanceof Uint8ClampedArray ) { -// https://www.gamedev.net/tutorials/programming/general-and-gameplay-programming/inverse-lerp-a-super-useful-yet-often-overlooked-function-r5230/ -function inverseLerp( x, y, value ) { + type = gl.UNSIGNED_BYTE; - if ( x !== y ) { + } else { - return ( value - x ) / ( y - x ); + throw new Error( 'THREE.WebGLAttributes: Unsupported buffer data format: ' + array ); - } else { + } - return 0; + return { + buffer: buffer, + type: type, + bytesPerElement: array.BYTES_PER_ELEMENT, + version: attribute.version, + size: size + }; } -} + function updateBuffer( buffer, attribute, bufferType ) { -// https://en.wikipedia.org/wiki/Linear_interpolation -function lerp( x, y, t ) { + const array = attribute.array; + const updateRanges = attribute.updateRanges; - return ( 1 - t ) * x + t * y; + gl.bindBuffer( bufferType, buffer ); -} + if ( updateRanges.length === 0 ) { -// http://www.rorydriscoll.com/2016/03/07/frame-rate-independent-damping-using-lerp/ -function damp( x, y, lambda, dt ) { + // Not using update ranges + gl.bufferSubData( bufferType, 0, array ); - return lerp( x, y, 1 - Math.exp( - lambda * dt ) ); + } else { -} + // Before applying update ranges, we merge any adjacent / overlapping + // ranges to reduce load on `gl.bufferSubData`. Empirically, this has led + // to performance improvements for applications which make heavy use of + // update ranges. Likely due to GPU command overhead. + // + // Note that to reduce garbage collection between frames, we merge the + // update ranges in-place. This is safe because this method will clear the + // update ranges once updated. -// https://www.desmos.com/calculator/vcsjnyz7x4 -function pingpong( x, length = 1 ) { + updateRanges.sort( ( a, b ) => a.start - b.start ); - return length - Math.abs( euclideanModulo( x, length * 2 ) - length ); + // To merge the update ranges in-place, we work from left to right in the + // existing updateRanges array, merging ranges. This may result in a final + // array which is smaller than the original. This index tracks the last + // index representing a merged range, any data after this index can be + // trimmed once the merge algorithm is completed. + let mergeIndex = 0; -} + for ( let i = 1; i < updateRanges.length; i ++ ) { -// http://en.wikipedia.org/wiki/Smoothstep -function smoothstep( x, min, max ) { + const previousRange = updateRanges[ mergeIndex ]; + const range = updateRanges[ i ]; - if ( x <= min ) return 0; - if ( x >= max ) return 1; + // We add one here to merge adjacent ranges. This is safe because ranges + // operate over positive integers. + if ( range.start <= previousRange.start + previousRange.count + 1 ) { - x = ( x - min ) / ( max - min ); + previousRange.count = Math.max( + previousRange.count, + range.start + range.count - previousRange.start + ); - return x * x * ( 3 - 2 * x ); + } else { -} + ++ mergeIndex; + updateRanges[ mergeIndex ] = range; -function smootherstep( x, min, max ) { + } - if ( x <= min ) return 0; - if ( x >= max ) return 1; + } - x = ( x - min ) / ( max - min ); + // Trim the array to only contain the merged ranges. + updateRanges.length = mergeIndex + 1; - return x * x * x * ( x * ( x * 6 - 15 ) + 10 ); + for ( let i = 0, l = updateRanges.length; i < l; i ++ ) { -} + const range = updateRanges[ i ]; -// Random integer from interval -function randInt( low, high ) { + gl.bufferSubData( bufferType, range.start * array.BYTES_PER_ELEMENT, + array, range.start, range.count ); - return low + Math.floor( Math.random() * ( high - low + 1 ) ); + } -} + attribute.clearUpdateRanges(); -// Random float from interval -function randFloat( low, high ) { + } - return low + Math.random() * ( high - low ); + attribute.onUploadCallback(); -} + } -// Random float from <-range/2, range/2> interval -function randFloatSpread( range ) { + // - return range * ( 0.5 - Math.random() ); + function get( attribute ) { -} + if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; -// Deterministic pseudo-random float in the interval [ 0, 1 ] -function seededRandom( s ) { + return buffers.get( attribute ); - if ( s !== undefined ) _seed = s; + } - // Mulberry32 generator + function remove( attribute ) { - let t = _seed += 0x6D2B79F5; + if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; - t = Math.imul( t ^ t >>> 15, t | 1 ); + const data = buffers.get( attribute ); - t ^= t + Math.imul( t ^ t >>> 7, t | 61 ); + if ( data ) { - return ( ( t ^ t >>> 14 ) >>> 0 ) / 4294967296; + gl.deleteBuffer( data.buffer ); -} + buffers.delete( attribute ); -function degToRad( degrees ) { + } - return degrees * DEG2RAD; + } -} + function update( attribute, bufferType ) { -function radToDeg( radians ) { + if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; - return radians * RAD2DEG; + if ( attribute.isGLBufferAttribute ) { -} + const cached = buffers.get( attribute ); -function isPowerOfTwo( value ) { + if ( ! cached || cached.version < attribute.version ) { - return ( value & ( value - 1 ) ) === 0 && value !== 0; + buffers.set( attribute, { + buffer: attribute.buffer, + type: attribute.type, + bytesPerElement: attribute.elementSize, + version: attribute.version + } ); -} + } -function ceilPowerOfTwo( value ) { + return; - return Math.pow( 2, Math.ceil( Math.log( value ) / Math.LN2 ) ); + } -} + const data = buffers.get( attribute ); -function floorPowerOfTwo( value ) { + if ( data === undefined ) { - return Math.pow( 2, Math.floor( Math.log( value ) / Math.LN2 ) ); + buffers.set( attribute, createBuffer( attribute, bufferType ) ); -} + } else if ( data.version < attribute.version ) { -function setQuaternionFromProperEuler( q, a, b, c, order ) { + if ( data.size !== attribute.array.byteLength ) { - // Intrinsic Proper Euler Angles - see https://en.wikipedia.org/wiki/Euler_angles + throw new Error( 'THREE.WebGLAttributes: The size of the buffer attribute\'s array buffer does not match the original size. Resizing buffer attributes is not supported.' ); - // rotations are applied to the axes in the order specified by 'order' - // rotation by angle 'a' is applied first, then by angle 'b', then by angle 'c' - // angles are in radians + } - const cos = Math.cos; - const sin = Math.sin; + updateBuffer( data.buffer, attribute, bufferType ); - const c2 = cos( b / 2 ); - const s2 = sin( b / 2 ); + data.version = attribute.version; - const c13 = cos( ( a + c ) / 2 ); - const s13 = sin( ( a + c ) / 2 ); + } - const c1_3 = cos( ( a - c ) / 2 ); - const s1_3 = sin( ( a - c ) / 2 ); + } - const c3_1 = cos( ( c - a ) / 2 ); - const s3_1 = sin( ( c - a ) / 2 ); + return { - switch ( order ) { + get: get, + remove: remove, + update: update - case 'XYX': - q.set( c2 * s13, s2 * c1_3, s2 * s1_3, c2 * c13 ); - break; + }; - case 'YZY': - q.set( s2 * s1_3, c2 * s13, s2 * c1_3, c2 * c13 ); - break; +} - case 'ZXZ': - q.set( s2 * c1_3, s2 * s1_3, c2 * s13, c2 * c13 ); - break; +var alphahash_fragment = "#ifdef USE_ALPHAHASH\n\tif ( diffuseColor.a < getAlphaHashThreshold( vPosition ) ) discard;\n#endif"; - case 'XZX': - q.set( c2 * s13, s2 * s3_1, s2 * c3_1, c2 * c13 ); - break; +var alphahash_pars_fragment = "#ifdef USE_ALPHAHASH\n\tconst float ALPHA_HASH_SCALE = 0.05;\n\tfloat hash2D( vec2 value ) {\n\t\treturn fract( 1.0e4 * sin( 17.0 * value.x + 0.1 * value.y ) * ( 0.1 + abs( sin( 13.0 * value.y + value.x ) ) ) );\n\t}\n\tfloat hash3D( vec3 value ) {\n\t\treturn hash2D( vec2( hash2D( value.xy ), value.z ) );\n\t}\n\tfloat getAlphaHashThreshold( vec3 position ) {\n\t\tfloat maxDeriv = max(\n\t\t\tlength( dFdx( position.xyz ) ),\n\t\t\tlength( dFdy( position.xyz ) )\n\t\t);\n\t\tfloat pixScale = 1.0 / ( ALPHA_HASH_SCALE * maxDeriv );\n\t\tvec2 pixScales = vec2(\n\t\t\texp2( floor( log2( pixScale ) ) ),\n\t\t\texp2( ceil( log2( pixScale ) ) )\n\t\t);\n\t\tvec2 alpha = vec2(\n\t\t\thash3D( floor( pixScales.x * position.xyz ) ),\n\t\t\thash3D( floor( pixScales.y * position.xyz ) )\n\t\t);\n\t\tfloat lerpFactor = fract( log2( pixScale ) );\n\t\tfloat x = ( 1.0 - lerpFactor ) * alpha.x + lerpFactor * alpha.y;\n\t\tfloat a = min( lerpFactor, 1.0 - lerpFactor );\n\t\tvec3 cases = vec3(\n\t\t\tx * x / ( 2.0 * a * ( 1.0 - a ) ),\n\t\t\t( x - 0.5 * a ) / ( 1.0 - a ),\n\t\t\t1.0 - ( ( 1.0 - x ) * ( 1.0 - x ) / ( 2.0 * a * ( 1.0 - a ) ) )\n\t\t);\n\t\tfloat threshold = ( x < ( 1.0 - a ) )\n\t\t\t? ( ( x < a ) ? cases.x : cases.y )\n\t\t\t: cases.z;\n\t\treturn clamp( threshold , 1.0e-6, 1.0 );\n\t}\n#endif"; - case 'YXY': - q.set( s2 * c3_1, c2 * s13, s2 * s3_1, c2 * c13 ); - break; +var alphamap_fragment = "#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, vAlphaMapUv ).g;\n#endif"; - case 'ZYZ': - q.set( s2 * s3_1, s2 * c3_1, c2 * s13, c2 * c13 ); - break; +var alphamap_pars_fragment = "#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif"; - default: - console.warn( 'THREE.MathUtils: .setQuaternionFromProperEuler() encountered an unknown order: ' + order ); +var alphatest_fragment = "#ifdef USE_ALPHATEST\n\t#ifdef ALPHA_TO_COVERAGE\n\tdiffuseColor.a = smoothstep( alphaTest, alphaTest + fwidth( diffuseColor.a ), diffuseColor.a );\n\tif ( diffuseColor.a == 0.0 ) discard;\n\t#else\n\tif ( diffuseColor.a < alphaTest ) discard;\n\t#endif\n#endif"; - } +var alphatest_pars_fragment = "#ifdef USE_ALPHATEST\n\tuniform float alphaTest;\n#endif"; -} +var aomap_fragment = "#ifdef USE_AOMAP\n\tfloat ambientOcclusion = ( texture2D( aoMap, vAoMapUv ).r - 1.0 ) * aoMapIntensity + 1.0;\n\treflectedLight.indirectDiffuse *= ambientOcclusion;\n\t#if defined( USE_CLEARCOAT ) \n\t\tclearcoatSpecularIndirect *= ambientOcclusion;\n\t#endif\n\t#if defined( USE_SHEEN ) \n\t\tsheenSpecularIndirect *= ambientOcclusion;\n\t#endif\n\t#if defined( USE_ENVMAP ) && defined( STANDARD )\n\t\tfloat dotNV = saturate( dot( geometryNormal, geometryViewDir ) );\n\t\treflectedLight.indirectSpecular *= computeSpecularOcclusion( dotNV, ambientOcclusion, material.roughness );\n\t#endif\n#endif"; -function denormalize( value, array ) { +var aomap_pars_fragment = "#ifdef USE_AOMAP\n\tuniform sampler2D aoMap;\n\tuniform float aoMapIntensity;\n#endif"; - switch ( array.constructor ) { +var batching_pars_vertex = "#ifdef USE_BATCHING\n\t#if ! defined( GL_ANGLE_multi_draw )\n\t#define gl_DrawID _gl_DrawID\n\tuniform int _gl_DrawID;\n\t#endif\n\tuniform highp sampler2D batchingTexture;\n\tuniform highp usampler2D batchingIdTexture;\n\tmat4 getBatchingMatrix( const in float i ) {\n\t\tint size = textureSize( batchingTexture, 0 ).x;\n\t\tint j = int( i ) * 4;\n\t\tint x = j % size;\n\t\tint y = j / size;\n\t\tvec4 v1 = texelFetch( batchingTexture, ivec2( x, y ), 0 );\n\t\tvec4 v2 = texelFetch( batchingTexture, ivec2( x + 1, y ), 0 );\n\t\tvec4 v3 = texelFetch( batchingTexture, ivec2( x + 2, y ), 0 );\n\t\tvec4 v4 = texelFetch( batchingTexture, ivec2( x + 3, y ), 0 );\n\t\treturn mat4( v1, v2, v3, v4 );\n\t}\n\tfloat getIndirectIndex( const in int i ) {\n\t\tint size = textureSize( batchingIdTexture, 0 ).x;\n\t\tint x = i % size;\n\t\tint y = i / size;\n\t\treturn float( texelFetch( batchingIdTexture, ivec2( x, y ), 0 ).r );\n\t}\n#endif\n#ifdef USE_BATCHING_COLOR\n\tuniform sampler2D batchingColorTexture;\n\tvec3 getBatchingColor( const in float i ) {\n\t\tint size = textureSize( batchingColorTexture, 0 ).x;\n\t\tint j = int( i );\n\t\tint x = j % size;\n\t\tint y = j / size;\n\t\treturn texelFetch( batchingColorTexture, ivec2( x, y ), 0 ).rgb;\n\t}\n#endif"; - case Float32Array: +var batching_vertex = "#ifdef USE_BATCHING\n\tmat4 batchingMatrix = getBatchingMatrix( getIndirectIndex( gl_DrawID ) );\n#endif"; - return value; +var begin_vertex = "vec3 transformed = vec3( position );\n#ifdef USE_ALPHAHASH\n\tvPosition = vec3( position );\n#endif"; - case Uint32Array: +var beginnormal_vertex = "vec3 objectNormal = vec3( normal );\n#ifdef USE_TANGENT\n\tvec3 objectTangent = vec3( tangent.xyz );\n#endif"; - return value / 4294967295.0; +var bsdfs = "float G_BlinnPhong_Implicit( ) {\n\treturn 0.25;\n}\nfloat D_BlinnPhong( const in float shininess, const in float dotNH ) {\n\treturn RECIPROCAL_PI * ( shininess * 0.5 + 1.0 ) * pow( dotNH, shininess );\n}\nvec3 BRDF_BlinnPhong( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in vec3 specularColor, const in float shininess ) {\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\tvec3 F = F_Schlick( specularColor, 1.0, dotVH );\n\tfloat G = G_BlinnPhong_Implicit( );\n\tfloat D = D_BlinnPhong( shininess, dotNH );\n\treturn F * ( G * D );\n} // validated"; - case Uint16Array: +var iridescence_fragment = "#ifdef USE_IRIDESCENCE\n\tconst mat3 XYZ_TO_REC709 = mat3(\n\t\t 3.2404542, -0.9692660, 0.0556434,\n\t\t-1.5371385, 1.8760108, -0.2040259,\n\t\t-0.4985314, 0.0415560, 1.0572252\n\t);\n\tvec3 Fresnel0ToIor( vec3 fresnel0 ) {\n\t\tvec3 sqrtF0 = sqrt( fresnel0 );\n\t\treturn ( vec3( 1.0 ) + sqrtF0 ) / ( vec3( 1.0 ) - sqrtF0 );\n\t}\n\tvec3 IorToFresnel0( vec3 transmittedIor, float incidentIor ) {\n\t\treturn pow2( ( transmittedIor - vec3( incidentIor ) ) / ( transmittedIor + vec3( incidentIor ) ) );\n\t}\n\tfloat IorToFresnel0( float transmittedIor, float incidentIor ) {\n\t\treturn pow2( ( transmittedIor - incidentIor ) / ( transmittedIor + incidentIor ));\n\t}\n\tvec3 evalSensitivity( float OPD, vec3 shift ) {\n\t\tfloat phase = 2.0 * PI * OPD * 1.0e-9;\n\t\tvec3 val = vec3( 5.4856e-13, 4.4201e-13, 5.2481e-13 );\n\t\tvec3 pos = vec3( 1.6810e+06, 1.7953e+06, 2.2084e+06 );\n\t\tvec3 var = vec3( 4.3278e+09, 9.3046e+09, 6.6121e+09 );\n\t\tvec3 xyz = val * sqrt( 2.0 * PI * var ) * cos( pos * phase + shift ) * exp( - pow2( phase ) * var );\n\t\txyz.x += 9.7470e-14 * sqrt( 2.0 * PI * 4.5282e+09 ) * cos( 2.2399e+06 * phase + shift[ 0 ] ) * exp( - 4.5282e+09 * pow2( phase ) );\n\t\txyz /= 1.0685e-7;\n\t\tvec3 rgb = XYZ_TO_REC709 * xyz;\n\t\treturn rgb;\n\t}\n\tvec3 evalIridescence( float outsideIOR, float eta2, float cosTheta1, float thinFilmThickness, vec3 baseF0 ) {\n\t\tvec3 I;\n\t\tfloat iridescenceIOR = mix( outsideIOR, eta2, smoothstep( 0.0, 0.03, thinFilmThickness ) );\n\t\tfloat sinTheta2Sq = pow2( outsideIOR / iridescenceIOR ) * ( 1.0 - pow2( cosTheta1 ) );\n\t\tfloat cosTheta2Sq = 1.0 - sinTheta2Sq;\n\t\tif ( cosTheta2Sq < 0.0 ) {\n\t\t\treturn vec3( 1.0 );\n\t\t}\n\t\tfloat cosTheta2 = sqrt( cosTheta2Sq );\n\t\tfloat R0 = IorToFresnel0( iridescenceIOR, outsideIOR );\n\t\tfloat R12 = F_Schlick( R0, 1.0, cosTheta1 );\n\t\tfloat T121 = 1.0 - R12;\n\t\tfloat phi12 = 0.0;\n\t\tif ( iridescenceIOR < outsideIOR ) phi12 = PI;\n\t\tfloat phi21 = PI - phi12;\n\t\tvec3 baseIOR = Fresnel0ToIor( clamp( baseF0, 0.0, 0.9999 ) );\t\tvec3 R1 = IorToFresnel0( baseIOR, iridescenceIOR );\n\t\tvec3 R23 = F_Schlick( R1, 1.0, cosTheta2 );\n\t\tvec3 phi23 = vec3( 0.0 );\n\t\tif ( baseIOR[ 0 ] < iridescenceIOR ) phi23[ 0 ] = PI;\n\t\tif ( baseIOR[ 1 ] < iridescenceIOR ) phi23[ 1 ] = PI;\n\t\tif ( baseIOR[ 2 ] < iridescenceIOR ) phi23[ 2 ] = PI;\n\t\tfloat OPD = 2.0 * iridescenceIOR * thinFilmThickness * cosTheta2;\n\t\tvec3 phi = vec3( phi21 ) + phi23;\n\t\tvec3 R123 = clamp( R12 * R23, 1e-5, 0.9999 );\n\t\tvec3 r123 = sqrt( R123 );\n\t\tvec3 Rs = pow2( T121 ) * R23 / ( vec3( 1.0 ) - R123 );\n\t\tvec3 C0 = R12 + Rs;\n\t\tI = C0;\n\t\tvec3 Cm = Rs - T121;\n\t\tfor ( int m = 1; m <= 2; ++ m ) {\n\t\t\tCm *= r123;\n\t\t\tvec3 Sm = 2.0 * evalSensitivity( float( m ) * OPD, float( m ) * phi );\n\t\t\tI += Cm * Sm;\n\t\t}\n\t\treturn max( I, vec3( 0.0 ) );\n\t}\n#endif"; - return value / 65535.0; +var bumpmap_pars_fragment = "#ifdef USE_BUMPMAP\n\tuniform sampler2D bumpMap;\n\tuniform float bumpScale;\n\tvec2 dHdxy_fwd() {\n\t\tvec2 dSTdx = dFdx( vBumpMapUv );\n\t\tvec2 dSTdy = dFdy( vBumpMapUv );\n\t\tfloat Hll = bumpScale * texture2D( bumpMap, vBumpMapUv ).x;\n\t\tfloat dBx = bumpScale * texture2D( bumpMap, vBumpMapUv + dSTdx ).x - Hll;\n\t\tfloat dBy = bumpScale * texture2D( bumpMap, vBumpMapUv + dSTdy ).x - Hll;\n\t\treturn vec2( dBx, dBy );\n\t}\n\tvec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy, float faceDirection ) {\n\t\tvec3 vSigmaX = normalize( dFdx( surf_pos.xyz ) );\n\t\tvec3 vSigmaY = normalize( dFdy( surf_pos.xyz ) );\n\t\tvec3 vN = surf_norm;\n\t\tvec3 R1 = cross( vSigmaY, vN );\n\t\tvec3 R2 = cross( vN, vSigmaX );\n\t\tfloat fDet = dot( vSigmaX, R1 ) * faceDirection;\n\t\tvec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );\n\t\treturn normalize( abs( fDet ) * surf_norm - vGrad );\n\t}\n#endif"; - case Uint8Array: +var clipping_planes_fragment = "#if NUM_CLIPPING_PLANES > 0\n\tvec4 plane;\n\t#ifdef ALPHA_TO_COVERAGE\n\t\tfloat distanceToPlane, distanceGradient;\n\t\tfloat clipOpacity = 1.0;\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) {\n\t\t\tplane = clippingPlanes[ i ];\n\t\t\tdistanceToPlane = - dot( vClipPosition, plane.xyz ) + plane.w;\n\t\t\tdistanceGradient = fwidth( distanceToPlane ) / 2.0;\n\t\t\tclipOpacity *= smoothstep( - distanceGradient, distanceGradient, distanceToPlane );\n\t\t\tif ( clipOpacity == 0.0 ) discard;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t\t#if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES\n\t\t\tfloat unionClipOpacity = 1.0;\n\t\t\t#pragma unroll_loop_start\n\t\t\tfor ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) {\n\t\t\t\tplane = clippingPlanes[ i ];\n\t\t\t\tdistanceToPlane = - dot( vClipPosition, plane.xyz ) + plane.w;\n\t\t\t\tdistanceGradient = fwidth( distanceToPlane ) / 2.0;\n\t\t\t\tunionClipOpacity *= 1.0 - smoothstep( - distanceGradient, distanceGradient, distanceToPlane );\n\t\t\t}\n\t\t\t#pragma unroll_loop_end\n\t\t\tclipOpacity *= 1.0 - unionClipOpacity;\n\t\t#endif\n\t\tdiffuseColor.a *= clipOpacity;\n\t\tif ( diffuseColor.a == 0.0 ) discard;\n\t#else\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) {\n\t\t\tplane = clippingPlanes[ i ];\n\t\t\tif ( dot( vClipPosition, plane.xyz ) > plane.w ) discard;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t\t#if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES\n\t\t\tbool clipped = true;\n\t\t\t#pragma unroll_loop_start\n\t\t\tfor ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) {\n\t\t\t\tplane = clippingPlanes[ i ];\n\t\t\t\tclipped = ( dot( vClipPosition, plane.xyz ) > plane.w ) && clipped;\n\t\t\t}\n\t\t\t#pragma unroll_loop_end\n\t\t\tif ( clipped ) discard;\n\t\t#endif\n\t#endif\n#endif"; - return value / 255.0; +var clipping_planes_pars_fragment = "#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n\tuniform vec4 clippingPlanes[ NUM_CLIPPING_PLANES ];\n#endif"; - case Int32Array: +var clipping_planes_pars_vertex = "#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n#endif"; - return Math.max( value / 2147483647.0, - 1.0 ); +var clipping_planes_vertex = "#if NUM_CLIPPING_PLANES > 0\n\tvClipPosition = - mvPosition.xyz;\n#endif"; - case Int16Array: +var color_fragment = "#if defined( USE_COLOR_ALPHA )\n\tdiffuseColor *= vColor;\n#elif defined( USE_COLOR )\n\tdiffuseColor.rgb *= vColor;\n#endif"; - return Math.max( value / 32767.0, - 1.0 ); +var color_pars_fragment = "#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR )\n\tvarying vec3 vColor;\n#endif"; - case Int8Array: +var color_pars_vertex = "#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR ) || defined( USE_BATCHING_COLOR )\n\tvarying vec3 vColor;\n#endif"; - return Math.max( value / 127.0, - 1.0 ); +var color_vertex = "#if defined( USE_COLOR_ALPHA )\n\tvColor = vec4( 1.0 );\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR ) || defined( USE_BATCHING_COLOR )\n\tvColor = vec3( 1.0 );\n#endif\n#ifdef USE_COLOR\n\tvColor *= color;\n#endif\n#ifdef USE_INSTANCING_COLOR\n\tvColor.xyz *= instanceColor.xyz;\n#endif\n#ifdef USE_BATCHING_COLOR\n\tvec3 batchingColor = getBatchingColor( getIndirectIndex( gl_DrawID ) );\n\tvColor.xyz *= batchingColor.xyz;\n#endif"; - default: +var common = "#define PI 3.141592653589793\n#define PI2 6.283185307179586\n#define PI_HALF 1.5707963267948966\n#define RECIPROCAL_PI 0.3183098861837907\n#define RECIPROCAL_PI2 0.15915494309189535\n#define EPSILON 1e-6\n#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\n#define whiteComplement( a ) ( 1.0 - saturate( a ) )\nfloat pow2( const in float x ) { return x*x; }\nvec3 pow2( const in vec3 x ) { return x*x; }\nfloat pow3( const in float x ) { return x*x*x; }\nfloat pow4( const in float x ) { float x2 = x*x; return x2*x2; }\nfloat max3( const in vec3 v ) { return max( max( v.x, v.y ), v.z ); }\nfloat average( const in vec3 v ) { return dot( v, vec3( 0.3333333 ) ); }\nhighp float rand( const in vec2 uv ) {\n\tconst highp float a = 12.9898, b = 78.233, c = 43758.5453;\n\thighp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI );\n\treturn fract( sin( sn ) * c );\n}\n#ifdef HIGH_PRECISION\n\tfloat precisionSafeLength( vec3 v ) { return length( v ); }\n#else\n\tfloat precisionSafeLength( vec3 v ) {\n\t\tfloat maxComponent = max3( abs( v ) );\n\t\treturn length( v / maxComponent ) * maxComponent;\n\t}\n#endif\nstruct IncidentLight {\n\tvec3 color;\n\tvec3 direction;\n\tbool visible;\n};\nstruct ReflectedLight {\n\tvec3 directDiffuse;\n\tvec3 directSpecular;\n\tvec3 indirectDiffuse;\n\tvec3 indirectSpecular;\n};\n#ifdef USE_ALPHAHASH\n\tvarying vec3 vPosition;\n#endif\nvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n}\nvec3 inverseTransformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( vec4( dir, 0.0 ) * matrix ).xyz );\n}\nmat3 transposeMat3( const in mat3 m ) {\n\tmat3 tmp;\n\ttmp[ 0 ] = vec3( m[ 0 ].x, m[ 1 ].x, m[ 2 ].x );\n\ttmp[ 1 ] = vec3( m[ 0 ].y, m[ 1 ].y, m[ 2 ].y );\n\ttmp[ 2 ] = vec3( m[ 0 ].z, m[ 1 ].z, m[ 2 ].z );\n\treturn tmp;\n}\nbool isPerspectiveMatrix( mat4 m ) {\n\treturn m[ 2 ][ 3 ] == - 1.0;\n}\nvec2 equirectUv( in vec3 dir ) {\n\tfloat u = atan( dir.z, dir.x ) * RECIPROCAL_PI2 + 0.5;\n\tfloat v = asin( clamp( dir.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\n\treturn vec2( u, v );\n}\nvec3 BRDF_Lambert( const in vec3 diffuseColor ) {\n\treturn RECIPROCAL_PI * diffuseColor;\n}\nvec3 F_Schlick( const in vec3 f0, const in float f90, const in float dotVH ) {\n\tfloat fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );\n\treturn f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );\n}\nfloat F_Schlick( const in float f0, const in float f90, const in float dotVH ) {\n\tfloat fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );\n\treturn f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );\n} // validated"; - throw new Error( 'Invalid component type.' ); +var cube_uv_reflection_fragment = "#ifdef ENVMAP_TYPE_CUBE_UV\n\t#define cubeUV_minMipLevel 4.0\n\t#define cubeUV_minTileSize 16.0\n\tfloat getFace( vec3 direction ) {\n\t\tvec3 absDirection = abs( direction );\n\t\tfloat face = - 1.0;\n\t\tif ( absDirection.x > absDirection.z ) {\n\t\t\tif ( absDirection.x > absDirection.y )\n\t\t\t\tface = direction.x > 0.0 ? 0.0 : 3.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t} else {\n\t\t\tif ( absDirection.z > absDirection.y )\n\t\t\t\tface = direction.z > 0.0 ? 2.0 : 5.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t}\n\t\treturn face;\n\t}\n\tvec2 getUV( vec3 direction, float face ) {\n\t\tvec2 uv;\n\t\tif ( face == 0.0 ) {\n\t\t\tuv = vec2( direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 1.0 ) {\n\t\t\tuv = vec2( - direction.x, - direction.z ) / abs( direction.y );\n\t\t} else if ( face == 2.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.y ) / abs( direction.z );\n\t\t} else if ( face == 3.0 ) {\n\t\t\tuv = vec2( - direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 4.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.z ) / abs( direction.y );\n\t\t} else {\n\t\t\tuv = vec2( direction.x, direction.y ) / abs( direction.z );\n\t\t}\n\t\treturn 0.5 * ( uv + 1.0 );\n\t}\n\tvec3 bilinearCubeUV( sampler2D envMap, vec3 direction, float mipInt ) {\n\t\tfloat face = getFace( direction );\n\t\tfloat filterInt = max( cubeUV_minMipLevel - mipInt, 0.0 );\n\t\tmipInt = max( mipInt, cubeUV_minMipLevel );\n\t\tfloat faceSize = exp2( mipInt );\n\t\thighp vec2 uv = getUV( direction, face ) * ( faceSize - 2.0 ) + 1.0;\n\t\tif ( face > 2.0 ) {\n\t\t\tuv.y += faceSize;\n\t\t\tface -= 3.0;\n\t\t}\n\t\tuv.x += face * faceSize;\n\t\tuv.x += filterInt * 3.0 * cubeUV_minTileSize;\n\t\tuv.y += 4.0 * ( exp2( CUBEUV_MAX_MIP ) - faceSize );\n\t\tuv.x *= CUBEUV_TEXEL_WIDTH;\n\t\tuv.y *= CUBEUV_TEXEL_HEIGHT;\n\t\t#ifdef texture2DGradEXT\n\t\t\treturn texture2DGradEXT( envMap, uv, vec2( 0.0 ), vec2( 0.0 ) ).rgb;\n\t\t#else\n\t\t\treturn texture2D( envMap, uv ).rgb;\n\t\t#endif\n\t}\n\t#define cubeUV_r0 1.0\n\t#define cubeUV_m0 - 2.0\n\t#define cubeUV_r1 0.8\n\t#define cubeUV_m1 - 1.0\n\t#define cubeUV_r4 0.4\n\t#define cubeUV_m4 2.0\n\t#define cubeUV_r5 0.305\n\t#define cubeUV_m5 3.0\n\t#define cubeUV_r6 0.21\n\t#define cubeUV_m6 4.0\n\tfloat roughnessToMip( float roughness ) {\n\t\tfloat mip = 0.0;\n\t\tif ( roughness >= cubeUV_r1 ) {\n\t\t\tmip = ( cubeUV_r0 - roughness ) * ( cubeUV_m1 - cubeUV_m0 ) / ( cubeUV_r0 - cubeUV_r1 ) + cubeUV_m0;\n\t\t} else if ( roughness >= cubeUV_r4 ) {\n\t\t\tmip = ( cubeUV_r1 - roughness ) * ( cubeUV_m4 - cubeUV_m1 ) / ( cubeUV_r1 - cubeUV_r4 ) + cubeUV_m1;\n\t\t} else if ( roughness >= cubeUV_r5 ) {\n\t\t\tmip = ( cubeUV_r4 - roughness ) * ( cubeUV_m5 - cubeUV_m4 ) / ( cubeUV_r4 - cubeUV_r5 ) + cubeUV_m4;\n\t\t} else if ( roughness >= cubeUV_r6 ) {\n\t\t\tmip = ( cubeUV_r5 - roughness ) * ( cubeUV_m6 - cubeUV_m5 ) / ( cubeUV_r5 - cubeUV_r6 ) + cubeUV_m5;\n\t\t} else {\n\t\t\tmip = - 2.0 * log2( 1.16 * roughness );\t\t}\n\t\treturn mip;\n\t}\n\tvec4 textureCubeUV( sampler2D envMap, vec3 sampleDir, float roughness ) {\n\t\tfloat mip = clamp( roughnessToMip( roughness ), cubeUV_m0, CUBEUV_MAX_MIP );\n\t\tfloat mipF = fract( mip );\n\t\tfloat mipInt = floor( mip );\n\t\tvec3 color0 = bilinearCubeUV( envMap, sampleDir, mipInt );\n\t\tif ( mipF == 0.0 ) {\n\t\t\treturn vec4( color0, 1.0 );\n\t\t} else {\n\t\t\tvec3 color1 = bilinearCubeUV( envMap, sampleDir, mipInt + 1.0 );\n\t\t\treturn vec4( mix( color0, color1, mipF ), 1.0 );\n\t\t}\n\t}\n#endif"; - } +var defaultnormal_vertex = "vec3 transformedNormal = objectNormal;\n#ifdef USE_TANGENT\n\tvec3 transformedTangent = objectTangent;\n#endif\n#ifdef USE_BATCHING\n\tmat3 bm = mat3( batchingMatrix );\n\ttransformedNormal /= vec3( dot( bm[ 0 ], bm[ 0 ] ), dot( bm[ 1 ], bm[ 1 ] ), dot( bm[ 2 ], bm[ 2 ] ) );\n\ttransformedNormal = bm * transformedNormal;\n\t#ifdef USE_TANGENT\n\t\ttransformedTangent = bm * transformedTangent;\n\t#endif\n#endif\n#ifdef USE_INSTANCING\n\tmat3 im = mat3( instanceMatrix );\n\ttransformedNormal /= vec3( dot( im[ 0 ], im[ 0 ] ), dot( im[ 1 ], im[ 1 ] ), dot( im[ 2 ], im[ 2 ] ) );\n\ttransformedNormal = im * transformedNormal;\n\t#ifdef USE_TANGENT\n\t\ttransformedTangent = im * transformedTangent;\n\t#endif\n#endif\ntransformedNormal = normalMatrix * transformedNormal;\n#ifdef FLIP_SIDED\n\ttransformedNormal = - transformedNormal;\n#endif\n#ifdef USE_TANGENT\n\ttransformedTangent = ( modelViewMatrix * vec4( transformedTangent, 0.0 ) ).xyz;\n\t#ifdef FLIP_SIDED\n\t\ttransformedTangent = - transformedTangent;\n\t#endif\n#endif"; -} +var displacementmap_pars_vertex = "#ifdef USE_DISPLACEMENTMAP\n\tuniform sampler2D displacementMap;\n\tuniform float displacementScale;\n\tuniform float displacementBias;\n#endif"; -function normalize( value, array ) { +var displacementmap_vertex = "#ifdef USE_DISPLACEMENTMAP\n\ttransformed += normalize( objectNormal ) * ( texture2D( displacementMap, vDisplacementMapUv ).x * displacementScale + displacementBias );\n#endif"; - switch ( array.constructor ) { +var emissivemap_fragment = "#ifdef USE_EMISSIVEMAP\n\tvec4 emissiveColor = texture2D( emissiveMap, vEmissiveMapUv );\n\t#ifdef DECODE_VIDEO_TEXTURE_EMISSIVE\n\t\temissiveColor = sRGBTransferEOTF( emissiveColor );\n\t#endif\n\ttotalEmissiveRadiance *= emissiveColor.rgb;\n#endif"; - case Float32Array: +var emissivemap_pars_fragment = "#ifdef USE_EMISSIVEMAP\n\tuniform sampler2D emissiveMap;\n#endif"; - return value; +var colorspace_fragment = "gl_FragColor = linearToOutputTexel( gl_FragColor );"; - case Uint32Array: +var colorspace_pars_fragment = "vec4 LinearTransferOETF( in vec4 value ) {\n\treturn value;\n}\nvec4 sRGBTransferEOTF( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), value.rgb * 0.0773993808, vec3( lessThanEqual( value.rgb, vec3( 0.04045 ) ) ) ), value.a );\n}\nvec4 sRGBTransferOETF( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a );\n}"; - return Math.round( value * 4294967295.0 ); +var envmap_fragment = "#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvec3 cameraToFrag;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToFrag = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToFrag = normalize( vWorldPosition - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvec3 reflectVec = reflect( cameraToFrag, worldNormal );\n\t\t#else\n\t\t\tvec3 reflectVec = refract( cameraToFrag, worldNormal, refractionRatio );\n\t\t#endif\n\t#else\n\t\tvec3 reflectVec = vReflect;\n\t#endif\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 envColor = textureCube( envMap, envMapRotation * vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );\n\t#else\n\t\tvec4 envColor = vec4( 0.0 );\n\t#endif\n\t#ifdef ENVMAP_BLENDING_MULTIPLY\n\t\toutgoingLight = mix( outgoingLight, outgoingLight * envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_MIX )\n\t\toutgoingLight = mix( outgoingLight, envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_ADD )\n\t\toutgoingLight += envColor.xyz * specularStrength * reflectivity;\n\t#endif\n#endif"; - case Uint16Array: +var envmap_common_pars_fragment = "#ifdef USE_ENVMAP\n\tuniform float envMapIntensity;\n\tuniform float flipEnvMap;\n\tuniform mat3 envMapRotation;\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tuniform samplerCube envMap;\n\t#else\n\t\tuniform sampler2D envMap;\n\t#endif\n\t\n#endif"; - return Math.round( value * 65535.0 ); +var envmap_pars_fragment = "#ifdef USE_ENVMAP\n\tuniform float reflectivity;\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( LAMBERT )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\tvarying vec3 vWorldPosition;\n\t\tuniform float refractionRatio;\n\t#else\n\t\tvarying vec3 vReflect;\n\t#endif\n#endif"; - case Uint8Array: +var envmap_pars_vertex = "#ifdef USE_ENVMAP\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( LAMBERT )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\t\n\t\tvarying vec3 vWorldPosition;\n\t#else\n\t\tvarying vec3 vReflect;\n\t\tuniform float refractionRatio;\n\t#endif\n#endif"; - return Math.round( value * 255.0 ); +var envmap_vertex = "#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvWorldPosition = worldPosition.xyz;\n\t#else\n\t\tvec3 cameraToVertex;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToVertex = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToVertex = normalize( worldPosition.xyz - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvReflect = reflect( cameraToVertex, worldNormal );\n\t\t#else\n\t\t\tvReflect = refract( cameraToVertex, worldNormal, refractionRatio );\n\t\t#endif\n\t#endif\n#endif"; - case Int32Array: +var fog_vertex = "#ifdef USE_FOG\n\tvFogDepth = - mvPosition.z;\n#endif"; - return Math.round( value * 2147483647.0 ); +var fog_pars_vertex = "#ifdef USE_FOG\n\tvarying float vFogDepth;\n#endif"; - case Int16Array: +var fog_fragment = "#ifdef USE_FOG\n\t#ifdef FOG_EXP2\n\t\tfloat fogFactor = 1.0 - exp( - fogDensity * fogDensity * vFogDepth * vFogDepth );\n\t#else\n\t\tfloat fogFactor = smoothstep( fogNear, fogFar, vFogDepth );\n\t#endif\n\tgl_FragColor.rgb = mix( gl_FragColor.rgb, fogColor, fogFactor );\n#endif"; - return Math.round( value * 32767.0 ); +var fog_pars_fragment = "#ifdef USE_FOG\n\tuniform vec3 fogColor;\n\tvarying float vFogDepth;\n\t#ifdef FOG_EXP2\n\t\tuniform float fogDensity;\n\t#else\n\t\tuniform float fogNear;\n\t\tuniform float fogFar;\n\t#endif\n#endif"; - case Int8Array: +var gradientmap_pars_fragment = "#ifdef USE_GRADIENTMAP\n\tuniform sampler2D gradientMap;\n#endif\nvec3 getGradientIrradiance( vec3 normal, vec3 lightDirection ) {\n\tfloat dotNL = dot( normal, lightDirection );\n\tvec2 coord = vec2( dotNL * 0.5 + 0.5, 0.0 );\n\t#ifdef USE_GRADIENTMAP\n\t\treturn vec3( texture2D( gradientMap, coord ).r );\n\t#else\n\t\tvec2 fw = fwidth( coord ) * 0.5;\n\t\treturn mix( vec3( 0.7 ), vec3( 1.0 ), smoothstep( 0.7 - fw.x, 0.7 + fw.x, coord.x ) );\n\t#endif\n}"; - return Math.round( value * 127.0 ); +var lightmap_pars_fragment = "#ifdef USE_LIGHTMAP\n\tuniform sampler2D lightMap;\n\tuniform float lightMapIntensity;\n#endif"; - default: +var lights_lambert_fragment = "LambertMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularStrength = specularStrength;"; - throw new Error( 'Invalid component type.' ); +var lights_lambert_pars_fragment = "varying vec3 vViewPosition;\nstruct LambertMaterial {\n\tvec3 diffuseColor;\n\tfloat specularStrength;\n};\nvoid RE_Direct_Lambert( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in LambertMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometryNormal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Lambert( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in LambertMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Lambert\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Lambert"; - } +var lights_pars_begin = "uniform bool receiveShadow;\nuniform vec3 ambientLightColor;\n#if defined( USE_LIGHT_PROBES )\n\tuniform vec3 lightProbe[ 9 ];\n#endif\nvec3 shGetIrradianceAt( in vec3 normal, in vec3 shCoefficients[ 9 ] ) {\n\tfloat x = normal.x, y = normal.y, z = normal.z;\n\tvec3 result = shCoefficients[ 0 ] * 0.886227;\n\tresult += shCoefficients[ 1 ] * 2.0 * 0.511664 * y;\n\tresult += shCoefficients[ 2 ] * 2.0 * 0.511664 * z;\n\tresult += shCoefficients[ 3 ] * 2.0 * 0.511664 * x;\n\tresult += shCoefficients[ 4 ] * 2.0 * 0.429043 * x * y;\n\tresult += shCoefficients[ 5 ] * 2.0 * 0.429043 * y * z;\n\tresult += shCoefficients[ 6 ] * ( 0.743125 * z * z - 0.247708 );\n\tresult += shCoefficients[ 7 ] * 2.0 * 0.429043 * x * z;\n\tresult += shCoefficients[ 8 ] * 0.429043 * ( x * x - y * y );\n\treturn result;\n}\nvec3 getLightProbeIrradiance( const in vec3 lightProbe[ 9 ], const in vec3 normal ) {\n\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\tvec3 irradiance = shGetIrradianceAt( worldNormal, lightProbe );\n\treturn irradiance;\n}\nvec3 getAmbientLightIrradiance( const in vec3 ambientLightColor ) {\n\tvec3 irradiance = ambientLightColor;\n\treturn irradiance;\n}\nfloat getDistanceAttenuation( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) {\n\tfloat distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );\n\tif ( cutoffDistance > 0.0 ) {\n\t\tdistanceFalloff *= pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );\n\t}\n\treturn distanceFalloff;\n}\nfloat getSpotAttenuation( const in float coneCosine, const in float penumbraCosine, const in float angleCosine ) {\n\treturn smoothstep( coneCosine, penumbraCosine, angleCosine );\n}\n#if NUM_DIR_LIGHTS > 0\n\tstruct DirectionalLight {\n\t\tvec3 direction;\n\t\tvec3 color;\n\t};\n\tuniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ];\n\tvoid getDirectionalLightInfo( const in DirectionalLight directionalLight, out IncidentLight light ) {\n\t\tlight.color = directionalLight.color;\n\t\tlight.direction = directionalLight.direction;\n\t\tlight.visible = true;\n\t}\n#endif\n#if NUM_POINT_LIGHTS > 0\n\tstruct PointLight {\n\t\tvec3 position;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t};\n\tuniform PointLight pointLights[ NUM_POINT_LIGHTS ];\n\tvoid getPointLightInfo( const in PointLight pointLight, const in vec3 geometryPosition, out IncidentLight light ) {\n\t\tvec3 lVector = pointLight.position - geometryPosition;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat lightDistance = length( lVector );\n\t\tlight.color = pointLight.color;\n\t\tlight.color *= getDistanceAttenuation( lightDistance, pointLight.distance, pointLight.decay );\n\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t}\n#endif\n#if NUM_SPOT_LIGHTS > 0\n\tstruct SpotLight {\n\t\tvec3 position;\n\t\tvec3 direction;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t\tfloat coneCos;\n\t\tfloat penumbraCos;\n\t};\n\tuniform SpotLight spotLights[ NUM_SPOT_LIGHTS ];\n\tvoid getSpotLightInfo( const in SpotLight spotLight, const in vec3 geometryPosition, out IncidentLight light ) {\n\t\tvec3 lVector = spotLight.position - geometryPosition;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat angleCos = dot( light.direction, spotLight.direction );\n\t\tfloat spotAttenuation = getSpotAttenuation( spotLight.coneCos, spotLight.penumbraCos, angleCos );\n\t\tif ( spotAttenuation > 0.0 ) {\n\t\t\tfloat lightDistance = length( lVector );\n\t\t\tlight.color = spotLight.color * spotAttenuation;\n\t\t\tlight.color *= getDistanceAttenuation( lightDistance, spotLight.distance, spotLight.decay );\n\t\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t\t} else {\n\t\t\tlight.color = vec3( 0.0 );\n\t\t\tlight.visible = false;\n\t\t}\n\t}\n#endif\n#if NUM_RECT_AREA_LIGHTS > 0\n\tstruct RectAreaLight {\n\t\tvec3 color;\n\t\tvec3 position;\n\t\tvec3 halfWidth;\n\t\tvec3 halfHeight;\n\t};\n\tuniform sampler2D ltc_1;\tuniform sampler2D ltc_2;\n\tuniform RectAreaLight rectAreaLights[ NUM_RECT_AREA_LIGHTS ];\n#endif\n#if NUM_HEMI_LIGHTS > 0\n\tstruct HemisphereLight {\n\t\tvec3 direction;\n\t\tvec3 skyColor;\n\t\tvec3 groundColor;\n\t};\n\tuniform HemisphereLight hemisphereLights[ NUM_HEMI_LIGHTS ];\n\tvec3 getHemisphereLightIrradiance( const in HemisphereLight hemiLight, const in vec3 normal ) {\n\t\tfloat dotNL = dot( normal, hemiLight.direction );\n\t\tfloat hemiDiffuseWeight = 0.5 * dotNL + 0.5;\n\t\tvec3 irradiance = mix( hemiLight.groundColor, hemiLight.skyColor, hemiDiffuseWeight );\n\t\treturn irradiance;\n\t}\n#endif"; -} +var envmap_physical_pars_fragment = "#ifdef USE_ENVMAP\n\tvec3 getIBLIrradiance( const in vec3 normal ) {\n\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, envMapRotation * worldNormal, 1.0 );\n\t\t\treturn PI * envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n\tvec3 getIBLRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness ) {\n\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\tvec3 reflectVec = reflect( - viewDir, normal );\n\t\t\treflectVec = normalize( mix( reflectVec, normal, roughness * roughness) );\n\t\t\treflectVec = inverseTransformDirection( reflectVec, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, envMapRotation * reflectVec, roughness );\n\t\t\treturn envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n\t#ifdef USE_ANISOTROPY\n\t\tvec3 getIBLAnisotropyRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness, const in vec3 bitangent, const in float anisotropy ) {\n\t\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\t\tvec3 bentNormal = cross( bitangent, viewDir );\n\t\t\t\tbentNormal = normalize( cross( bentNormal, bitangent ) );\n\t\t\t\tbentNormal = normalize( mix( bentNormal, normal, pow2( pow2( 1.0 - anisotropy * ( 1.0 - roughness ) ) ) ) );\n\t\t\t\treturn getIBLRadiance( viewDir, bentNormal, roughness );\n\t\t\t#else\n\t\t\t\treturn vec3( 0.0 );\n\t\t\t#endif\n\t\t}\n\t#endif\n#endif"; -const MathUtils = { - DEG2RAD: DEG2RAD, - RAD2DEG: RAD2DEG, - generateUUID: generateUUID, - clamp: clamp, - euclideanModulo: euclideanModulo, - mapLinear: mapLinear, - inverseLerp: inverseLerp, - lerp: lerp, - damp: damp, - pingpong: pingpong, - smoothstep: smoothstep, - smootherstep: smootherstep, - randInt: randInt, - randFloat: randFloat, - randFloatSpread: randFloatSpread, - seededRandom: seededRandom, - degToRad: degToRad, - radToDeg: radToDeg, - isPowerOfTwo: isPowerOfTwo, - ceilPowerOfTwo: ceilPowerOfTwo, - floorPowerOfTwo: floorPowerOfTwo, - setQuaternionFromProperEuler: setQuaternionFromProperEuler, - normalize: normalize, - denormalize: denormalize -}; +var lights_toon_fragment = "ToonMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;"; -class Vector2 { +var lights_toon_pars_fragment = "varying vec3 vViewPosition;\nstruct ToonMaterial {\n\tvec3 diffuseColor;\n};\nvoid RE_Direct_Toon( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\tvec3 irradiance = getGradientIrradiance( geometryNormal, directLight.direction ) * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Toon( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Toon\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Toon"; - constructor( x = 0, y = 0 ) { +var lights_phong_fragment = "BlinnPhongMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularColor = specular;\nmaterial.specularShininess = shininess;\nmaterial.specularStrength = specularStrength;"; - Vector2.prototype.isVector2 = true; +var lights_phong_pars_fragment = "varying vec3 vViewPosition;\nstruct BlinnPhongMaterial {\n\tvec3 diffuseColor;\n\tvec3 specularColor;\n\tfloat specularShininess;\n\tfloat specularStrength;\n};\nvoid RE_Direct_BlinnPhong( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometryNormal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n\treflectedLight.directSpecular += irradiance * BRDF_BlinnPhong( directLight.direction, geometryViewDir, geometryNormal, material.specularColor, material.specularShininess ) * material.specularStrength;\n}\nvoid RE_IndirectDiffuse_BlinnPhong( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_BlinnPhong\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_BlinnPhong"; - this.x = x; - this.y = y; +var lights_physical_fragment = "PhysicalMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb * ( 1.0 - metalnessFactor );\nvec3 dxy = max( abs( dFdx( nonPerturbedNormal ) ), abs( dFdy( nonPerturbedNormal ) ) );\nfloat geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z );\nmaterial.roughness = max( roughnessFactor, 0.0525 );material.roughness += geometryRoughness;\nmaterial.roughness = min( material.roughness, 1.0 );\n#ifdef IOR\n\tmaterial.ior = ior;\n\t#ifdef USE_SPECULAR\n\t\tfloat specularIntensityFactor = specularIntensity;\n\t\tvec3 specularColorFactor = specularColor;\n\t\t#ifdef USE_SPECULAR_COLORMAP\n\t\t\tspecularColorFactor *= texture2D( specularColorMap, vSpecularColorMapUv ).rgb;\n\t\t#endif\n\t\t#ifdef USE_SPECULAR_INTENSITYMAP\n\t\t\tspecularIntensityFactor *= texture2D( specularIntensityMap, vSpecularIntensityMapUv ).a;\n\t\t#endif\n\t\tmaterial.specularF90 = mix( specularIntensityFactor, 1.0, metalnessFactor );\n\t#else\n\t\tfloat specularIntensityFactor = 1.0;\n\t\tvec3 specularColorFactor = vec3( 1.0 );\n\t\tmaterial.specularF90 = 1.0;\n\t#endif\n\tmaterial.specularColor = mix( min( pow2( ( material.ior - 1.0 ) / ( material.ior + 1.0 ) ) * specularColorFactor, vec3( 1.0 ) ) * specularIntensityFactor, diffuseColor.rgb, metalnessFactor );\n#else\n\tmaterial.specularColor = mix( vec3( 0.04 ), diffuseColor.rgb, metalnessFactor );\n\tmaterial.specularF90 = 1.0;\n#endif\n#ifdef USE_CLEARCOAT\n\tmaterial.clearcoat = clearcoat;\n\tmaterial.clearcoatRoughness = clearcoatRoughness;\n\tmaterial.clearcoatF0 = vec3( 0.04 );\n\tmaterial.clearcoatF90 = 1.0;\n\t#ifdef USE_CLEARCOATMAP\n\t\tmaterial.clearcoat *= texture2D( clearcoatMap, vClearcoatMapUv ).x;\n\t#endif\n\t#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\t\tmaterial.clearcoatRoughness *= texture2D( clearcoatRoughnessMap, vClearcoatRoughnessMapUv ).y;\n\t#endif\n\tmaterial.clearcoat = saturate( material.clearcoat );\tmaterial.clearcoatRoughness = max( material.clearcoatRoughness, 0.0525 );\n\tmaterial.clearcoatRoughness += geometryRoughness;\n\tmaterial.clearcoatRoughness = min( material.clearcoatRoughness, 1.0 );\n#endif\n#ifdef USE_DISPERSION\n\tmaterial.dispersion = dispersion;\n#endif\n#ifdef USE_IRIDESCENCE\n\tmaterial.iridescence = iridescence;\n\tmaterial.iridescenceIOR = iridescenceIOR;\n\t#ifdef USE_IRIDESCENCEMAP\n\t\tmaterial.iridescence *= texture2D( iridescenceMap, vIridescenceMapUv ).r;\n\t#endif\n\t#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\t\tmaterial.iridescenceThickness = (iridescenceThicknessMaximum - iridescenceThicknessMinimum) * texture2D( iridescenceThicknessMap, vIridescenceThicknessMapUv ).g + iridescenceThicknessMinimum;\n\t#else\n\t\tmaterial.iridescenceThickness = iridescenceThicknessMaximum;\n\t#endif\n#endif\n#ifdef USE_SHEEN\n\tmaterial.sheenColor = sheenColor;\n\t#ifdef USE_SHEEN_COLORMAP\n\t\tmaterial.sheenColor *= texture2D( sheenColorMap, vSheenColorMapUv ).rgb;\n\t#endif\n\tmaterial.sheenRoughness = clamp( sheenRoughness, 0.07, 1.0 );\n\t#ifdef USE_SHEEN_ROUGHNESSMAP\n\t\tmaterial.sheenRoughness *= texture2D( sheenRoughnessMap, vSheenRoughnessMapUv ).a;\n\t#endif\n#endif\n#ifdef USE_ANISOTROPY\n\t#ifdef USE_ANISOTROPYMAP\n\t\tmat2 anisotropyMat = mat2( anisotropyVector.x, anisotropyVector.y, - anisotropyVector.y, anisotropyVector.x );\n\t\tvec3 anisotropyPolar = texture2D( anisotropyMap, vAnisotropyMapUv ).rgb;\n\t\tvec2 anisotropyV = anisotropyMat * normalize( 2.0 * anisotropyPolar.rg - vec2( 1.0 ) ) * anisotropyPolar.b;\n\t#else\n\t\tvec2 anisotropyV = anisotropyVector;\n\t#endif\n\tmaterial.anisotropy = length( anisotropyV );\n\tif( material.anisotropy == 0.0 ) {\n\t\tanisotropyV = vec2( 1.0, 0.0 );\n\t} else {\n\t\tanisotropyV /= material.anisotropy;\n\t\tmaterial.anisotropy = saturate( material.anisotropy );\n\t}\n\tmaterial.alphaT = mix( pow2( material.roughness ), 1.0, pow2( material.anisotropy ) );\n\tmaterial.anisotropyT = tbn[ 0 ] * anisotropyV.x + tbn[ 1 ] * anisotropyV.y;\n\tmaterial.anisotropyB = tbn[ 1 ] * anisotropyV.x - tbn[ 0 ] * anisotropyV.y;\n#endif"; - } +var lights_physical_pars_fragment = "struct PhysicalMaterial {\n\tvec3 diffuseColor;\n\tfloat roughness;\n\tvec3 specularColor;\n\tfloat specularF90;\n\tfloat dispersion;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat clearcoat;\n\t\tfloat clearcoatRoughness;\n\t\tvec3 clearcoatF0;\n\t\tfloat clearcoatF90;\n\t#endif\n\t#ifdef USE_IRIDESCENCE\n\t\tfloat iridescence;\n\t\tfloat iridescenceIOR;\n\t\tfloat iridescenceThickness;\n\t\tvec3 iridescenceFresnel;\n\t\tvec3 iridescenceF0;\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tvec3 sheenColor;\n\t\tfloat sheenRoughness;\n\t#endif\n\t#ifdef IOR\n\t\tfloat ior;\n\t#endif\n\t#ifdef USE_TRANSMISSION\n\t\tfloat transmission;\n\t\tfloat transmissionAlpha;\n\t\tfloat thickness;\n\t\tfloat attenuationDistance;\n\t\tvec3 attenuationColor;\n\t#endif\n\t#ifdef USE_ANISOTROPY\n\t\tfloat anisotropy;\n\t\tfloat alphaT;\n\t\tvec3 anisotropyT;\n\t\tvec3 anisotropyB;\n\t#endif\n};\nvec3 clearcoatSpecularDirect = vec3( 0.0 );\nvec3 clearcoatSpecularIndirect = vec3( 0.0 );\nvec3 sheenSpecularDirect = vec3( 0.0 );\nvec3 sheenSpecularIndirect = vec3(0.0 );\nvec3 Schlick_to_F0( const in vec3 f, const in float f90, const in float dotVH ) {\n float x = clamp( 1.0 - dotVH, 0.0, 1.0 );\n float x2 = x * x;\n float x5 = clamp( x * x2 * x2, 0.0, 0.9999 );\n return ( f - vec3( f90 ) * x5 ) / ( 1.0 - x5 );\n}\nfloat V_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const in float dotNV ) {\n\tfloat a2 = pow2( alpha );\n\tfloat gv = dotNL * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );\n\tfloat gl = dotNV * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );\n\treturn 0.5 / max( gv + gl, EPSILON );\n}\nfloat D_GGX( const in float alpha, const in float dotNH ) {\n\tfloat a2 = pow2( alpha );\n\tfloat denom = pow2( dotNH ) * ( a2 - 1.0 ) + 1.0;\n\treturn RECIPROCAL_PI * a2 / pow2( denom );\n}\n#ifdef USE_ANISOTROPY\n\tfloat V_GGX_SmithCorrelated_Anisotropic( const in float alphaT, const in float alphaB, const in float dotTV, const in float dotBV, const in float dotTL, const in float dotBL, const in float dotNV, const in float dotNL ) {\n\t\tfloat gv = dotNL * length( vec3( alphaT * dotTV, alphaB * dotBV, dotNV ) );\n\t\tfloat gl = dotNV * length( vec3( alphaT * dotTL, alphaB * dotBL, dotNL ) );\n\t\tfloat v = 0.5 / ( gv + gl );\n\t\treturn saturate(v);\n\t}\n\tfloat D_GGX_Anisotropic( const in float alphaT, const in float alphaB, const in float dotNH, const in float dotTH, const in float dotBH ) {\n\t\tfloat a2 = alphaT * alphaB;\n\t\thighp vec3 v = vec3( alphaB * dotTH, alphaT * dotBH, a2 * dotNH );\n\t\thighp float v2 = dot( v, v );\n\t\tfloat w2 = a2 / v2;\n\t\treturn RECIPROCAL_PI * a2 * pow2 ( w2 );\n\t}\n#endif\n#ifdef USE_CLEARCOAT\n\tvec3 BRDF_GGX_Clearcoat( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in PhysicalMaterial material) {\n\t\tvec3 f0 = material.clearcoatF0;\n\t\tfloat f90 = material.clearcoatF90;\n\t\tfloat roughness = material.clearcoatRoughness;\n\t\tfloat alpha = pow2( roughness );\n\t\tvec3 halfDir = normalize( lightDir + viewDir );\n\t\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\t\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\t\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\t\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\t\tvec3 F = F_Schlick( f0, f90, dotVH );\n\t\tfloat V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\t\tfloat D = D_GGX( alpha, dotNH );\n\t\treturn F * ( V * D );\n\t}\n#endif\nvec3 BRDF_GGX( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in PhysicalMaterial material ) {\n\tvec3 f0 = material.specularColor;\n\tfloat f90 = material.specularF90;\n\tfloat roughness = material.roughness;\n\tfloat alpha = pow2( roughness );\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\tvec3 F = F_Schlick( f0, f90, dotVH );\n\t#ifdef USE_IRIDESCENCE\n\t\tF = mix( F, material.iridescenceFresnel, material.iridescence );\n\t#endif\n\t#ifdef USE_ANISOTROPY\n\t\tfloat dotTL = dot( material.anisotropyT, lightDir );\n\t\tfloat dotTV = dot( material.anisotropyT, viewDir );\n\t\tfloat dotTH = dot( material.anisotropyT, halfDir );\n\t\tfloat dotBL = dot( material.anisotropyB, lightDir );\n\t\tfloat dotBV = dot( material.anisotropyB, viewDir );\n\t\tfloat dotBH = dot( material.anisotropyB, halfDir );\n\t\tfloat V = V_GGX_SmithCorrelated_Anisotropic( material.alphaT, alpha, dotTV, dotBV, dotTL, dotBL, dotNV, dotNL );\n\t\tfloat D = D_GGX_Anisotropic( material.alphaT, alpha, dotNH, dotTH, dotBH );\n\t#else\n\t\tfloat V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\t\tfloat D = D_GGX( alpha, dotNH );\n\t#endif\n\treturn F * ( V * D );\n}\nvec2 LTC_Uv( const in vec3 N, const in vec3 V, const in float roughness ) {\n\tconst float LUT_SIZE = 64.0;\n\tconst float LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE;\n\tconst float LUT_BIAS = 0.5 / LUT_SIZE;\n\tfloat dotNV = saturate( dot( N, V ) );\n\tvec2 uv = vec2( roughness, sqrt( 1.0 - dotNV ) );\n\tuv = uv * LUT_SCALE + LUT_BIAS;\n\treturn uv;\n}\nfloat LTC_ClippedSphereFormFactor( const in vec3 f ) {\n\tfloat l = length( f );\n\treturn max( ( l * l + f.z ) / ( l + 1.0 ), 0.0 );\n}\nvec3 LTC_EdgeVectorFormFactor( const in vec3 v1, const in vec3 v2 ) {\n\tfloat x = dot( v1, v2 );\n\tfloat y = abs( x );\n\tfloat a = 0.8543985 + ( 0.4965155 + 0.0145206 * y ) * y;\n\tfloat b = 3.4175940 + ( 4.1616724 + y ) * y;\n\tfloat v = a / b;\n\tfloat theta_sintheta = ( x > 0.0 ) ? v : 0.5 * inversesqrt( max( 1.0 - x * x, 1e-7 ) ) - v;\n\treturn cross( v1, v2 ) * theta_sintheta;\n}\nvec3 LTC_Evaluate( const in vec3 N, const in vec3 V, const in vec3 P, const in mat3 mInv, const in vec3 rectCoords[ 4 ] ) {\n\tvec3 v1 = rectCoords[ 1 ] - rectCoords[ 0 ];\n\tvec3 v2 = rectCoords[ 3 ] - rectCoords[ 0 ];\n\tvec3 lightNormal = cross( v1, v2 );\n\tif( dot( lightNormal, P - rectCoords[ 0 ] ) < 0.0 ) return vec3( 0.0 );\n\tvec3 T1, T2;\n\tT1 = normalize( V - N * dot( V, N ) );\n\tT2 = - cross( N, T1 );\n\tmat3 mat = mInv * transposeMat3( mat3( T1, T2, N ) );\n\tvec3 coords[ 4 ];\n\tcoords[ 0 ] = mat * ( rectCoords[ 0 ] - P );\n\tcoords[ 1 ] = mat * ( rectCoords[ 1 ] - P );\n\tcoords[ 2 ] = mat * ( rectCoords[ 2 ] - P );\n\tcoords[ 3 ] = mat * ( rectCoords[ 3 ] - P );\n\tcoords[ 0 ] = normalize( coords[ 0 ] );\n\tcoords[ 1 ] = normalize( coords[ 1 ] );\n\tcoords[ 2 ] = normalize( coords[ 2 ] );\n\tcoords[ 3 ] = normalize( coords[ 3 ] );\n\tvec3 vectorFormFactor = vec3( 0.0 );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 0 ], coords[ 1 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 1 ], coords[ 2 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 2 ], coords[ 3 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 3 ], coords[ 0 ] );\n\tfloat result = LTC_ClippedSphereFormFactor( vectorFormFactor );\n\treturn vec3( result );\n}\n#if defined( USE_SHEEN )\nfloat D_Charlie( float roughness, float dotNH ) {\n\tfloat alpha = pow2( roughness );\n\tfloat invAlpha = 1.0 / alpha;\n\tfloat cos2h = dotNH * dotNH;\n\tfloat sin2h = max( 1.0 - cos2h, 0.0078125 );\n\treturn ( 2.0 + invAlpha ) * pow( sin2h, invAlpha * 0.5 ) / ( 2.0 * PI );\n}\nfloat V_Neubelt( float dotNV, float dotNL ) {\n\treturn saturate( 1.0 / ( 4.0 * ( dotNL + dotNV - dotNL * dotNV ) ) );\n}\nvec3 BRDF_Sheen( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, vec3 sheenColor, const in float sheenRoughness ) {\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat D = D_Charlie( sheenRoughness, dotNH );\n\tfloat V = V_Neubelt( dotNV, dotNL );\n\treturn sheenColor * ( D * V );\n}\n#endif\nfloat IBLSheenBRDF( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat r2 = roughness * roughness;\n\tfloat a = roughness < 0.25 ? -339.2 * r2 + 161.4 * roughness - 25.9 : -8.48 * r2 + 14.3 * roughness - 9.95;\n\tfloat b = roughness < 0.25 ? 44.0 * r2 - 23.7 * roughness + 3.26 : 1.97 * r2 - 3.27 * roughness + 0.72;\n\tfloat DG = exp( a * dotNV + b ) + ( roughness < 0.25 ? 0.0 : 0.1 * ( roughness - 0.25 ) );\n\treturn saturate( DG * RECIPROCAL_PI );\n}\nvec2 DFGApprox( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tconst vec4 c0 = vec4( - 1, - 0.0275, - 0.572, 0.022 );\n\tconst vec4 c1 = vec4( 1, 0.0425, 1.04, - 0.04 );\n\tvec4 r = roughness * c0 + c1;\n\tfloat a004 = min( r.x * r.x, exp2( - 9.28 * dotNV ) ) * r.x + r.y;\n\tvec2 fab = vec2( - 1.04, 1.04 ) * a004 + r.zw;\n\treturn fab;\n}\nvec3 EnvironmentBRDF( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness ) {\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\treturn specularColor * fab.x + specularF90 * fab.y;\n}\n#ifdef USE_IRIDESCENCE\nvoid computeMultiscatteringIridescence( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float iridescence, const in vec3 iridescenceF0, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#else\nvoid computeMultiscattering( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#endif\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\t#ifdef USE_IRIDESCENCE\n\t\tvec3 Fr = mix( specularColor, iridescenceF0, iridescence );\n\t#else\n\t\tvec3 Fr = specularColor;\n\t#endif\n\tvec3 FssEss = Fr * fab.x + specularF90 * fab.y;\n\tfloat Ess = fab.x + fab.y;\n\tfloat Ems = 1.0 - Ess;\n\tvec3 Favg = Fr + ( 1.0 - Fr ) * 0.047619;\tvec3 Fms = FssEss * Favg / ( 1.0 - Ems * Favg );\n\tsingleScatter += FssEss;\n\tmultiScatter += Fms * Ems;\n}\n#if NUM_RECT_AREA_LIGHTS > 0\n\tvoid RE_Direct_RectArea_Physical( const in RectAreaLight rectAreaLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\t\tvec3 normal = geometryNormal;\n\t\tvec3 viewDir = geometryViewDir;\n\t\tvec3 position = geometryPosition;\n\t\tvec3 lightPos = rectAreaLight.position;\n\t\tvec3 halfWidth = rectAreaLight.halfWidth;\n\t\tvec3 halfHeight = rectAreaLight.halfHeight;\n\t\tvec3 lightColor = rectAreaLight.color;\n\t\tfloat roughness = material.roughness;\n\t\tvec3 rectCoords[ 4 ];\n\t\trectCoords[ 0 ] = lightPos + halfWidth - halfHeight;\t\trectCoords[ 1 ] = lightPos - halfWidth - halfHeight;\n\t\trectCoords[ 2 ] = lightPos - halfWidth + halfHeight;\n\t\trectCoords[ 3 ] = lightPos + halfWidth + halfHeight;\n\t\tvec2 uv = LTC_Uv( normal, viewDir, roughness );\n\t\tvec4 t1 = texture2D( ltc_1, uv );\n\t\tvec4 t2 = texture2D( ltc_2, uv );\n\t\tmat3 mInv = mat3(\n\t\t\tvec3( t1.x, 0, t1.y ),\n\t\t\tvec3( 0, 1, 0 ),\n\t\t\tvec3( t1.z, 0, t1.w )\n\t\t);\n\t\tvec3 fresnel = ( material.specularColor * t2.x + ( vec3( 1.0 ) - material.specularColor ) * t2.y );\n\t\treflectedLight.directSpecular += lightColor * fresnel * LTC_Evaluate( normal, viewDir, position, mInv, rectCoords );\n\t\treflectedLight.directDiffuse += lightColor * material.diffuseColor * LTC_Evaluate( normal, viewDir, position, mat3( 1.0 ), rectCoords );\n\t}\n#endif\nvoid RE_Direct_Physical( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometryNormal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNLcc = saturate( dot( geometryClearcoatNormal, directLight.direction ) );\n\t\tvec3 ccIrradiance = dotNLcc * directLight.color;\n\t\tclearcoatSpecularDirect += ccIrradiance * BRDF_GGX_Clearcoat( directLight.direction, geometryViewDir, geometryClearcoatNormal, material );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tsheenSpecularDirect += irradiance * BRDF_Sheen( directLight.direction, geometryViewDir, geometryNormal, material.sheenColor, material.sheenRoughness );\n\t#endif\n\treflectedLight.directSpecular += irradiance * BRDF_GGX( directLight.direction, geometryViewDir, geometryNormal, material );\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Physical( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 irradiance, const in vec3 clearcoatRadiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatSpecularIndirect += clearcoatRadiance * EnvironmentBRDF( geometryClearcoatNormal, geometryViewDir, material.clearcoatF0, material.clearcoatF90, material.clearcoatRoughness );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tsheenSpecularIndirect += irradiance * material.sheenColor * IBLSheenBRDF( geometryNormal, geometryViewDir, material.sheenRoughness );\n\t#endif\n\tvec3 singleScattering = vec3( 0.0 );\n\tvec3 multiScattering = vec3( 0.0 );\n\tvec3 cosineWeightedIrradiance = irradiance * RECIPROCAL_PI;\n\t#ifdef USE_IRIDESCENCE\n\t\tcomputeMultiscatteringIridescence( geometryNormal, geometryViewDir, material.specularColor, material.specularF90, material.iridescence, material.iridescenceFresnel, material.roughness, singleScattering, multiScattering );\n\t#else\n\t\tcomputeMultiscattering( geometryNormal, geometryViewDir, material.specularColor, material.specularF90, material.roughness, singleScattering, multiScattering );\n\t#endif\n\tvec3 totalScattering = singleScattering + multiScattering;\n\tvec3 diffuse = material.diffuseColor * ( 1.0 - max( max( totalScattering.r, totalScattering.g ), totalScattering.b ) );\n\treflectedLight.indirectSpecular += radiance * singleScattering;\n\treflectedLight.indirectSpecular += multiScattering * cosineWeightedIrradiance;\n\treflectedLight.indirectDiffuse += diffuse * cosineWeightedIrradiance;\n}\n#define RE_Direct\t\t\t\tRE_Direct_Physical\n#define RE_Direct_RectArea\t\tRE_Direct_RectArea_Physical\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Physical\n#define RE_IndirectSpecular\t\tRE_IndirectSpecular_Physical\nfloat computeSpecularOcclusion( const in float dotNV, const in float ambientOcclusion, const in float roughness ) {\n\treturn saturate( pow( dotNV + ambientOcclusion, exp2( - 16.0 * roughness - 1.0 ) ) - 1.0 + ambientOcclusion );\n}"; - get width() { +var lights_fragment_begin = "\nvec3 geometryPosition = - vViewPosition;\nvec3 geometryNormal = normal;\nvec3 geometryViewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( vViewPosition );\nvec3 geometryClearcoatNormal = vec3( 0.0 );\n#ifdef USE_CLEARCOAT\n\tgeometryClearcoatNormal = clearcoatNormal;\n#endif\n#ifdef USE_IRIDESCENCE\n\tfloat dotNVi = saturate( dot( normal, geometryViewDir ) );\n\tif ( material.iridescenceThickness == 0.0 ) {\n\t\tmaterial.iridescence = 0.0;\n\t} else {\n\t\tmaterial.iridescence = saturate( material.iridescence );\n\t}\n\tif ( material.iridescence > 0.0 ) {\n\t\tmaterial.iridescenceFresnel = evalIridescence( 1.0, material.iridescenceIOR, dotNVi, material.iridescenceThickness, material.specularColor );\n\t\tmaterial.iridescenceF0 = Schlick_to_F0( material.iridescenceFresnel, 1.0, dotNVi );\n\t}\n#endif\nIncidentLight directLight;\n#if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct )\n\tPointLight pointLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tpointLight = pointLights[ i ];\n\t\tgetPointLightInfo( pointLight, geometryPosition, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_POINT_LIGHT_SHADOWS )\n\t\tpointLightShadow = pointLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getPointShadow( pointShadowMap[ i ], pointLightShadow.shadowMapSize, pointLightShadow.shadowIntensity, pointLightShadow.shadowBias, pointLightShadow.shadowRadius, vPointShadowCoord[ i ], pointLightShadow.shadowCameraNear, pointLightShadow.shadowCameraFar ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct )\n\tSpotLight spotLight;\n\tvec4 spotColor;\n\tvec3 spotLightCoord;\n\tbool inSpotLightMap;\n\t#if defined( USE_SHADOWMAP ) && NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tspotLight = spotLights[ i ];\n\t\tgetSpotLightInfo( spotLight, geometryPosition, directLight );\n\t\t#if ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )\n\t\t#define SPOT_LIGHT_MAP_INDEX UNROLLED_LOOP_INDEX\n\t\t#elif ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\t#define SPOT_LIGHT_MAP_INDEX NUM_SPOT_LIGHT_MAPS\n\t\t#else\n\t\t#define SPOT_LIGHT_MAP_INDEX ( UNROLLED_LOOP_INDEX - NUM_SPOT_LIGHT_SHADOWS + NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )\n\t\t#endif\n\t\t#if ( SPOT_LIGHT_MAP_INDEX < NUM_SPOT_LIGHT_MAPS )\n\t\t\tspotLightCoord = vSpotLightCoord[ i ].xyz / vSpotLightCoord[ i ].w;\n\t\t\tinSpotLightMap = all( lessThan( abs( spotLightCoord * 2. - 1. ), vec3( 1.0 ) ) );\n\t\t\tspotColor = texture2D( spotLightMap[ SPOT_LIGHT_MAP_INDEX ], spotLightCoord.xy );\n\t\t\tdirectLight.color = inSpotLightMap ? directLight.color * spotColor.rgb : directLight.color;\n\t\t#endif\n\t\t#undef SPOT_LIGHT_MAP_INDEX\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\tspotLightShadow = spotLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( spotShadowMap[ i ], spotLightShadow.shadowMapSize, spotLightShadow.shadowIntensity, spotLightShadow.shadowBias, spotLightShadow.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct )\n\tDirectionalLight directionalLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tdirectionalLight = directionalLights[ i ];\n\t\tgetDirectionalLightInfo( directionalLight, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )\n\t\tdirectionalLightShadow = directionalLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowIntensity, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea )\n\tRectAreaLight rectAreaLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) {\n\t\trectAreaLight = rectAreaLights[ i ];\n\t\tRE_Direct_RectArea( rectAreaLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if defined( RE_IndirectDiffuse )\n\tvec3 iblIrradiance = vec3( 0.0 );\n\tvec3 irradiance = getAmbientLightIrradiance( ambientLightColor );\n\t#if defined( USE_LIGHT_PROBES )\n\t\tirradiance += getLightProbeIrradiance( lightProbe, geometryNormal );\n\t#endif\n\t#if ( NUM_HEMI_LIGHTS > 0 )\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n\t\t\tirradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometryNormal );\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n#endif\n#if defined( RE_IndirectSpecular )\n\tvec3 radiance = vec3( 0.0 );\n\tvec3 clearcoatRadiance = vec3( 0.0 );\n#endif"; - return this.x; +var lights_fragment_maps = "#if defined( RE_IndirectDiffuse )\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel = texture2D( lightMap, vLightMapUv );\n\t\tvec3 lightMapIrradiance = lightMapTexel.rgb * lightMapIntensity;\n\t\tirradiance += lightMapIrradiance;\n\t#endif\n\t#if defined( USE_ENVMAP ) && defined( STANDARD ) && defined( ENVMAP_TYPE_CUBE_UV )\n\t\tiblIrradiance += getIBLIrradiance( geometryNormal );\n\t#endif\n#endif\n#if defined( USE_ENVMAP ) && defined( RE_IndirectSpecular )\n\t#ifdef USE_ANISOTROPY\n\t\tradiance += getIBLAnisotropyRadiance( geometryViewDir, geometryNormal, material.roughness, material.anisotropyB, material.anisotropy );\n\t#else\n\t\tradiance += getIBLRadiance( geometryViewDir, geometryNormal, material.roughness );\n\t#endif\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatRadiance += getIBLRadiance( geometryViewDir, geometryClearcoatNormal, material.clearcoatRoughness );\n\t#endif\n#endif"; - } +var lights_fragment_end = "#if defined( RE_IndirectDiffuse )\n\tRE_IndirectDiffuse( irradiance, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n#endif\n#if defined( RE_IndirectSpecular )\n\tRE_IndirectSpecular( radiance, iblIrradiance, clearcoatRadiance, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n#endif"; - set width( value ) { +var logdepthbuf_fragment = "#if defined( USE_LOGDEPTHBUF )\n\tgl_FragDepth = vIsPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\n#endif"; - this.x = value; +var logdepthbuf_pars_fragment = "#if defined( USE_LOGDEPTHBUF )\n\tuniform float logDepthBufFC;\n\tvarying float vFragDepth;\n\tvarying float vIsPerspective;\n#endif"; - } +var logdepthbuf_pars_vertex = "#ifdef USE_LOGDEPTHBUF\n\tvarying float vFragDepth;\n\tvarying float vIsPerspective;\n#endif"; - get height() { +var logdepthbuf_vertex = "#ifdef USE_LOGDEPTHBUF\n\tvFragDepth = 1.0 + gl_Position.w;\n\tvIsPerspective = float( isPerspectiveMatrix( projectionMatrix ) );\n#endif"; - return this.y; +var map_fragment = "#ifdef USE_MAP\n\tvec4 sampledDiffuseColor = texture2D( map, vMapUv );\n\t#ifdef DECODE_VIDEO_TEXTURE\n\t\tsampledDiffuseColor = sRGBTransferEOTF( sampledDiffuseColor );\n\t#endif\n\tdiffuseColor *= sampledDiffuseColor;\n#endif"; - } +var map_pars_fragment = "#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif"; - set height( value ) { +var map_particle_fragment = "#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\t#if defined( USE_POINTS_UV )\n\t\tvec2 uv = vUv;\n\t#else\n\t\tvec2 uv = ( uvTransform * vec3( gl_PointCoord.x, 1.0 - gl_PointCoord.y, 1 ) ).xy;\n\t#endif\n#endif\n#ifdef USE_MAP\n\tdiffuseColor *= texture2D( map, uv );\n#endif\n#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, uv ).g;\n#endif"; - this.y = value; +var map_particle_pars_fragment = "#if defined( USE_POINTS_UV )\n\tvarying vec2 vUv;\n#else\n\t#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\t\tuniform mat3 uvTransform;\n\t#endif\n#endif\n#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif\n#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif"; - } +var metalnessmap_fragment = "float metalnessFactor = metalness;\n#ifdef USE_METALNESSMAP\n\tvec4 texelMetalness = texture2D( metalnessMap, vMetalnessMapUv );\n\tmetalnessFactor *= texelMetalness.b;\n#endif"; - set( x, y ) { +var metalnessmap_pars_fragment = "#ifdef USE_METALNESSMAP\n\tuniform sampler2D metalnessMap;\n#endif"; - this.x = x; - this.y = y; +var morphinstance_vertex = "#ifdef USE_INSTANCING_MORPH\n\tfloat morphTargetInfluences[ MORPHTARGETS_COUNT ];\n\tfloat morphTargetBaseInfluence = texelFetch( morphTexture, ivec2( 0, gl_InstanceID ), 0 ).r;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\tmorphTargetInfluences[i] = texelFetch( morphTexture, ivec2( i + 1, gl_InstanceID ), 0 ).r;\n\t}\n#endif"; - return this; +var morphcolor_vertex = "#if defined( USE_MORPHCOLORS )\n\tvColor *= morphTargetBaseInfluence;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t#if defined( USE_COLOR_ALPHA )\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ) * morphTargetInfluences[ i ];\n\t\t#elif defined( USE_COLOR )\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ).rgb * morphTargetInfluences[ i ];\n\t\t#endif\n\t}\n#endif"; - } +var morphnormal_vertex = "#ifdef USE_MORPHNORMALS\n\tobjectNormal *= morphTargetBaseInfluence;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\tif ( morphTargetInfluences[ i ] != 0.0 ) objectNormal += getMorph( gl_VertexID, i, 1 ).xyz * morphTargetInfluences[ i ];\n\t}\n#endif"; - setScalar( scalar ) { +var morphtarget_pars_vertex = "#ifdef USE_MORPHTARGETS\n\t#ifndef USE_INSTANCING_MORPH\n\t\tuniform float morphTargetBaseInfluence;\n\t\tuniform float morphTargetInfluences[ MORPHTARGETS_COUNT ];\n\t#endif\n\tuniform sampler2DArray morphTargetsTexture;\n\tuniform ivec2 morphTargetsTextureSize;\n\tvec4 getMorph( const in int vertexIndex, const in int morphTargetIndex, const in int offset ) {\n\t\tint texelIndex = vertexIndex * MORPHTARGETS_TEXTURE_STRIDE + offset;\n\t\tint y = texelIndex / morphTargetsTextureSize.x;\n\t\tint x = texelIndex - y * morphTargetsTextureSize.x;\n\t\tivec3 morphUV = ivec3( x, y, morphTargetIndex );\n\t\treturn texelFetch( morphTargetsTexture, morphUV, 0 );\n\t}\n#endif"; - this.x = scalar; - this.y = scalar; +var morphtarget_vertex = "#ifdef USE_MORPHTARGETS\n\ttransformed *= morphTargetBaseInfluence;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\tif ( morphTargetInfluences[ i ] != 0.0 ) transformed += getMorph( gl_VertexID, i, 0 ).xyz * morphTargetInfluences[ i ];\n\t}\n#endif"; - return this; +var normal_fragment_begin = "float faceDirection = gl_FrontFacing ? 1.0 : - 1.0;\n#ifdef FLAT_SHADED\n\tvec3 fdx = dFdx( vViewPosition );\n\tvec3 fdy = dFdy( vViewPosition );\n\tvec3 normal = normalize( cross( fdx, fdy ) );\n#else\n\tvec3 normal = normalize( vNormal );\n\t#ifdef DOUBLE_SIDED\n\t\tnormal *= faceDirection;\n\t#endif\n#endif\n#if defined( USE_NORMALMAP_TANGENTSPACE ) || defined( USE_CLEARCOAT_NORMALMAP ) || defined( USE_ANISOTROPY )\n\t#ifdef USE_TANGENT\n\t\tmat3 tbn = mat3( normalize( vTangent ), normalize( vBitangent ), normal );\n\t#else\n\t\tmat3 tbn = getTangentFrame( - vViewPosition, normal,\n\t\t#if defined( USE_NORMALMAP )\n\t\t\tvNormalMapUv\n\t\t#elif defined( USE_CLEARCOAT_NORMALMAP )\n\t\t\tvClearcoatNormalMapUv\n\t\t#else\n\t\t\tvUv\n\t\t#endif\n\t\t);\n\t#endif\n\t#if defined( DOUBLE_SIDED ) && ! defined( FLAT_SHADED )\n\t\ttbn[0] *= faceDirection;\n\t\ttbn[1] *= faceDirection;\n\t#endif\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\t#ifdef USE_TANGENT\n\t\tmat3 tbn2 = mat3( normalize( vTangent ), normalize( vBitangent ), normal );\n\t#else\n\t\tmat3 tbn2 = getTangentFrame( - vViewPosition, normal, vClearcoatNormalMapUv );\n\t#endif\n\t#if defined( DOUBLE_SIDED ) && ! defined( FLAT_SHADED )\n\t\ttbn2[0] *= faceDirection;\n\t\ttbn2[1] *= faceDirection;\n\t#endif\n#endif\nvec3 nonPerturbedNormal = normal;"; - } +var normal_fragment_maps = "#ifdef USE_NORMALMAP_OBJECTSPACE\n\tnormal = texture2D( normalMap, vNormalMapUv ).xyz * 2.0 - 1.0;\n\t#ifdef FLIP_SIDED\n\t\tnormal = - normal;\n\t#endif\n\t#ifdef DOUBLE_SIDED\n\t\tnormal = normal * faceDirection;\n\t#endif\n\tnormal = normalize( normalMatrix * normal );\n#elif defined( USE_NORMALMAP_TANGENTSPACE )\n\tvec3 mapN = texture2D( normalMap, vNormalMapUv ).xyz * 2.0 - 1.0;\n\tmapN.xy *= normalScale;\n\tnormal = normalize( tbn * mapN );\n#elif defined( USE_BUMPMAP )\n\tnormal = perturbNormalArb( - vViewPosition, normal, dHdxy_fwd(), faceDirection );\n#endif"; - setX( x ) { +var normal_pars_fragment = "#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif"; - this.x = x; +var normal_pars_vertex = "#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif"; - return this; +var normal_vertex = "#ifndef FLAT_SHADED\n\tvNormal = normalize( transformedNormal );\n\t#ifdef USE_TANGENT\n\t\tvTangent = normalize( transformedTangent );\n\t\tvBitangent = normalize( cross( vNormal, vTangent ) * tangent.w );\n\t#endif\n#endif"; - } +var normalmap_pars_fragment = "#ifdef USE_NORMALMAP\n\tuniform sampler2D normalMap;\n\tuniform vec2 normalScale;\n#endif\n#ifdef USE_NORMALMAP_OBJECTSPACE\n\tuniform mat3 normalMatrix;\n#endif\n#if ! defined ( USE_TANGENT ) && ( defined ( USE_NORMALMAP_TANGENTSPACE ) || defined ( USE_CLEARCOAT_NORMALMAP ) || defined( USE_ANISOTROPY ) )\n\tmat3 getTangentFrame( vec3 eye_pos, vec3 surf_norm, vec2 uv ) {\n\t\tvec3 q0 = dFdx( eye_pos.xyz );\n\t\tvec3 q1 = dFdy( eye_pos.xyz );\n\t\tvec2 st0 = dFdx( uv.st );\n\t\tvec2 st1 = dFdy( uv.st );\n\t\tvec3 N = surf_norm;\n\t\tvec3 q1perp = cross( q1, N );\n\t\tvec3 q0perp = cross( N, q0 );\n\t\tvec3 T = q1perp * st0.x + q0perp * st1.x;\n\t\tvec3 B = q1perp * st0.y + q0perp * st1.y;\n\t\tfloat det = max( dot( T, T ), dot( B, B ) );\n\t\tfloat scale = ( det == 0.0 ) ? 0.0 : inversesqrt( det );\n\t\treturn mat3( T * scale, B * scale, N );\n\t}\n#endif"; - setY( y ) { +var clearcoat_normal_fragment_begin = "#ifdef USE_CLEARCOAT\n\tvec3 clearcoatNormal = nonPerturbedNormal;\n#endif"; - this.y = y; +var clearcoat_normal_fragment_maps = "#ifdef USE_CLEARCOAT_NORMALMAP\n\tvec3 clearcoatMapN = texture2D( clearcoatNormalMap, vClearcoatNormalMapUv ).xyz * 2.0 - 1.0;\n\tclearcoatMapN.xy *= clearcoatNormalScale;\n\tclearcoatNormal = normalize( tbn2 * clearcoatMapN );\n#endif"; - return this; +var clearcoat_pars_fragment = "#ifdef USE_CLEARCOATMAP\n\tuniform sampler2D clearcoatMap;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tuniform sampler2D clearcoatNormalMap;\n\tuniform vec2 clearcoatNormalScale;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tuniform sampler2D clearcoatRoughnessMap;\n#endif"; - } +var iridescence_pars_fragment = "#ifdef USE_IRIDESCENCEMAP\n\tuniform sampler2D iridescenceMap;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tuniform sampler2D iridescenceThicknessMap;\n#endif"; - setComponent( index, value ) { +var opaque_fragment = "#ifdef OPAQUE\ndiffuseColor.a = 1.0;\n#endif\n#ifdef USE_TRANSMISSION\ndiffuseColor.a *= material.transmissionAlpha;\n#endif\ngl_FragColor = vec4( outgoingLight, diffuseColor.a );"; - switch ( index ) { +var packing = "vec3 packNormalToRGB( const in vec3 normal ) {\n\treturn normalize( normal ) * 0.5 + 0.5;\n}\nvec3 unpackRGBToNormal( const in vec3 rgb ) {\n\treturn 2.0 * rgb.xyz - 1.0;\n}\nconst float PackUpscale = 256. / 255.;const float UnpackDownscale = 255. / 256.;const float ShiftRight8 = 1. / 256.;\nconst float Inv255 = 1. / 255.;\nconst vec4 PackFactors = vec4( 1.0, 256.0, 256.0 * 256.0, 256.0 * 256.0 * 256.0 );\nconst vec2 UnpackFactors2 = vec2( UnpackDownscale, 1.0 / PackFactors.g );\nconst vec3 UnpackFactors3 = vec3( UnpackDownscale / PackFactors.rg, 1.0 / PackFactors.b );\nconst vec4 UnpackFactors4 = vec4( UnpackDownscale / PackFactors.rgb, 1.0 / PackFactors.a );\nvec4 packDepthToRGBA( const in float v ) {\n\tif( v <= 0.0 )\n\t\treturn vec4( 0., 0., 0., 0. );\n\tif( v >= 1.0 )\n\t\treturn vec4( 1., 1., 1., 1. );\n\tfloat vuf;\n\tfloat af = modf( v * PackFactors.a, vuf );\n\tfloat bf = modf( vuf * ShiftRight8, vuf );\n\tfloat gf = modf( vuf * ShiftRight8, vuf );\n\treturn vec4( vuf * Inv255, gf * PackUpscale, bf * PackUpscale, af );\n}\nvec3 packDepthToRGB( const in float v ) {\n\tif( v <= 0.0 )\n\t\treturn vec3( 0., 0., 0. );\n\tif( v >= 1.0 )\n\t\treturn vec3( 1., 1., 1. );\n\tfloat vuf;\n\tfloat bf = modf( v * PackFactors.b, vuf );\n\tfloat gf = modf( vuf * ShiftRight8, vuf );\n\treturn vec3( vuf * Inv255, gf * PackUpscale, bf );\n}\nvec2 packDepthToRG( const in float v ) {\n\tif( v <= 0.0 )\n\t\treturn vec2( 0., 0. );\n\tif( v >= 1.0 )\n\t\treturn vec2( 1., 1. );\n\tfloat vuf;\n\tfloat gf = modf( v * 256., vuf );\n\treturn vec2( vuf * Inv255, gf );\n}\nfloat unpackRGBAToDepth( const in vec4 v ) {\n\treturn dot( v, UnpackFactors4 );\n}\nfloat unpackRGBToDepth( const in vec3 v ) {\n\treturn dot( v, UnpackFactors3 );\n}\nfloat unpackRGToDepth( const in vec2 v ) {\n\treturn v.r * UnpackFactors2.r + v.g * UnpackFactors2.g;\n}\nvec4 pack2HalfToRGBA( const in vec2 v ) {\n\tvec4 r = vec4( v.x, fract( v.x * 255.0 ), v.y, fract( v.y * 255.0 ) );\n\treturn vec4( r.x - r.y / 255.0, r.y, r.z - r.w / 255.0, r.w );\n}\nvec2 unpackRGBATo2Half( const in vec4 v ) {\n\treturn vec2( v.x + ( v.y / 255.0 ), v.z + ( v.w / 255.0 ) );\n}\nfloat viewZToOrthographicDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( viewZ + near ) / ( near - far );\n}\nfloat orthographicDepthToViewZ( const in float depth, const in float near, const in float far ) {\n\treturn depth * ( near - far ) - near;\n}\nfloat viewZToPerspectiveDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( ( near + viewZ ) * far ) / ( ( far - near ) * viewZ );\n}\nfloat perspectiveDepthToViewZ( const in float depth, const in float near, const in float far ) {\n\treturn ( near * far ) / ( ( far - near ) * depth - far );\n}"; - case 0: this.x = value; break; - case 1: this.y = value; break; - default: throw new Error( 'index is out of range: ' + index ); +var premultiplied_alpha_fragment = "#ifdef PREMULTIPLIED_ALPHA\n\tgl_FragColor.rgb *= gl_FragColor.a;\n#endif"; - } +var project_vertex = "vec4 mvPosition = vec4( transformed, 1.0 );\n#ifdef USE_BATCHING\n\tmvPosition = batchingMatrix * mvPosition;\n#endif\n#ifdef USE_INSTANCING\n\tmvPosition = instanceMatrix * mvPosition;\n#endif\nmvPosition = modelViewMatrix * mvPosition;\ngl_Position = projectionMatrix * mvPosition;"; - return this; +var dithering_fragment = "#ifdef DITHERING\n\tgl_FragColor.rgb = dithering( gl_FragColor.rgb );\n#endif"; - } +var dithering_pars_fragment = "#ifdef DITHERING\n\tvec3 dithering( vec3 color ) {\n\t\tfloat grid_position = rand( gl_FragCoord.xy );\n\t\tvec3 dither_shift_RGB = vec3( 0.25 / 255.0, -0.25 / 255.0, 0.25 / 255.0 );\n\t\tdither_shift_RGB = mix( 2.0 * dither_shift_RGB, -2.0 * dither_shift_RGB, grid_position );\n\t\treturn color + dither_shift_RGB;\n\t}\n#endif"; - getComponent( index ) { +var roughnessmap_fragment = "float roughnessFactor = roughness;\n#ifdef USE_ROUGHNESSMAP\n\tvec4 texelRoughness = texture2D( roughnessMap, vRoughnessMapUv );\n\troughnessFactor *= texelRoughness.g;\n#endif"; - switch ( index ) { +var roughnessmap_pars_fragment = "#ifdef USE_ROUGHNESSMAP\n\tuniform sampler2D roughnessMap;\n#endif"; - case 0: return this.x; - case 1: return this.y; - default: throw new Error( 'index is out of range: ' + index ); +var shadowmap_pars_fragment = "#if NUM_SPOT_LIGHT_COORDS > 0\n\tvarying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ];\n#endif\n#if NUM_SPOT_LIGHT_MAPS > 0\n\tuniform sampler2D spotLightMap[ NUM_SPOT_LIGHT_MAPS ];\n#endif\n#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D directionalShadowMap[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D spotShadowMap[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D pointShadowMap[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n\tfloat texture2DCompare( sampler2D depths, vec2 uv, float compare ) {\n\t\treturn step( compare, unpackRGBAToDepth( texture2D( depths, uv ) ) );\n\t}\n\tvec2 texture2DDistribution( sampler2D shadow, vec2 uv ) {\n\t\treturn unpackRGBATo2Half( texture2D( shadow, uv ) );\n\t}\n\tfloat VSMShadow (sampler2D shadow, vec2 uv, float compare ){\n\t\tfloat occlusion = 1.0;\n\t\tvec2 distribution = texture2DDistribution( shadow, uv );\n\t\tfloat hard_shadow = step( compare , distribution.x );\n\t\tif (hard_shadow != 1.0 ) {\n\t\t\tfloat distance = compare - distribution.x ;\n\t\t\tfloat variance = max( 0.00000, distribution.y * distribution.y );\n\t\t\tfloat softness_probability = variance / (variance + distance * distance );\t\t\tsoftness_probability = clamp( ( softness_probability - 0.3 ) / ( 0.95 - 0.3 ), 0.0, 1.0 );\t\t\tocclusion = clamp( max( hard_shadow, softness_probability ), 0.0, 1.0 );\n\t\t}\n\t\treturn occlusion;\n\t}\n\tfloat getShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowIntensity, float shadowBias, float shadowRadius, vec4 shadowCoord ) {\n\t\tfloat shadow = 1.0;\n\t\tshadowCoord.xyz /= shadowCoord.w;\n\t\tshadowCoord.z += shadowBias;\n\t\tbool inFrustum = shadowCoord.x >= 0.0 && shadowCoord.x <= 1.0 && shadowCoord.y >= 0.0 && shadowCoord.y <= 1.0;\n\t\tbool frustumTest = inFrustum && shadowCoord.z <= 1.0;\n\t\tif ( frustumTest ) {\n\t\t#if defined( SHADOWMAP_TYPE_PCF )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx0 = - texelSize.x * shadowRadius;\n\t\t\tfloat dy0 = - texelSize.y * shadowRadius;\n\t\t\tfloat dx1 = + texelSize.x * shadowRadius;\n\t\t\tfloat dy1 = + texelSize.y * shadowRadius;\n\t\t\tfloat dx2 = dx0 / 2.0;\n\t\t\tfloat dy2 = dy0 / 2.0;\n\t\t\tfloat dx3 = dx1 / 2.0;\n\t\t\tfloat dy3 = dy1 / 2.0;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy1 ), shadowCoord.z )\n\t\t\t) * ( 1.0 / 17.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_PCF_SOFT )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx = texelSize.x;\n\t\t\tfloat dy = texelSize.y;\n\t\t\tvec2 uv = shadowCoord.xy;\n\t\t\tvec2 f = fract( uv * shadowMapSize + 0.5 );\n\t\t\tuv -= f * texelSize;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, uv, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( dx, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( 0.0, dy ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + texelSize, shadowCoord.z ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, dy ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( 0.0, -dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 0.0, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( mix( texture2DCompare( shadowMap, uv + vec2( -dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t f.x ),\n\t\t\t\t\t mix( texture2DCompare( shadowMap, uv + vec2( -dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t f.x ),\n\t\t\t\t\t f.y )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_VSM )\n\t\t\tshadow = VSMShadow( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#else\n\t\t\tshadow = texture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#endif\n\t\t}\n\t\treturn mix( 1.0, shadow, shadowIntensity );\n\t}\n\tvec2 cubeToUV( vec3 v, float texelSizeY ) {\n\t\tvec3 absV = abs( v );\n\t\tfloat scaleToCube = 1.0 / max( absV.x, max( absV.y, absV.z ) );\n\t\tabsV *= scaleToCube;\n\t\tv *= scaleToCube * ( 1.0 - 2.0 * texelSizeY );\n\t\tvec2 planar = v.xy;\n\t\tfloat almostATexel = 1.5 * texelSizeY;\n\t\tfloat almostOne = 1.0 - almostATexel;\n\t\tif ( absV.z >= almostOne ) {\n\t\t\tif ( v.z > 0.0 )\n\t\t\t\tplanar.x = 4.0 - v.x;\n\t\t} else if ( absV.x >= almostOne ) {\n\t\t\tfloat signX = sign( v.x );\n\t\t\tplanar.x = v.z * signX + 2.0 * signX;\n\t\t} else if ( absV.y >= almostOne ) {\n\t\t\tfloat signY = sign( v.y );\n\t\t\tplanar.x = v.x + 2.0 * signY + 2.0;\n\t\t\tplanar.y = v.z * signY - 2.0;\n\t\t}\n\t\treturn vec2( 0.125, 0.25 ) * planar + vec2( 0.375, 0.75 );\n\t}\n\tfloat getPointShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowIntensity, float shadowBias, float shadowRadius, vec4 shadowCoord, float shadowCameraNear, float shadowCameraFar ) {\n\t\tfloat shadow = 1.0;\n\t\tvec3 lightToPosition = shadowCoord.xyz;\n\t\t\n\t\tfloat lightToPositionLength = length( lightToPosition );\n\t\tif ( lightToPositionLength - shadowCameraFar <= 0.0 && lightToPositionLength - shadowCameraNear >= 0.0 ) {\n\t\t\tfloat dp = ( lightToPositionLength - shadowCameraNear ) / ( shadowCameraFar - shadowCameraNear );\t\t\tdp += shadowBias;\n\t\t\tvec3 bd3D = normalize( lightToPosition );\n\t\t\tvec2 texelSize = vec2( 1.0 ) / ( shadowMapSize * vec2( 4.0, 2.0 ) );\n\t\t\t#if defined( SHADOWMAP_TYPE_PCF ) || defined( SHADOWMAP_TYPE_PCF_SOFT ) || defined( SHADOWMAP_TYPE_VSM )\n\t\t\t\tvec2 offset = vec2( - 1, 1 ) * shadowRadius * texelSize.y;\n\t\t\t\tshadow = (\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyy, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyy, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyx, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyx, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxy, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxy, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxx, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxx, texelSize.y ), dp )\n\t\t\t\t) * ( 1.0 / 9.0 );\n\t\t\t#else\n\t\t\t\tshadow = texture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp );\n\t\t\t#endif\n\t\t}\n\t\treturn mix( 1.0, shadow, shadowIntensity );\n\t}\n#endif"; - } +var shadowmap_pars_vertex = "#if NUM_SPOT_LIGHT_COORDS > 0\n\tuniform mat4 spotLightMatrix[ NUM_SPOT_LIGHT_COORDS ];\n\tvarying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ];\n#endif\n#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform mat4 directionalShadowMatrix[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform mat4 pointShadowMatrix[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n#endif"; - } +var shadowmap_vertex = "#if ( defined( USE_SHADOWMAP ) && ( NUM_DIR_LIGHT_SHADOWS > 0 || NUM_POINT_LIGHT_SHADOWS > 0 ) ) || ( NUM_SPOT_LIGHT_COORDS > 0 )\n\tvec3 shadowWorldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\tvec4 shadowWorldPosition;\n#endif\n#if defined( USE_SHADOWMAP )\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * directionalLightShadows[ i ].shadowNormalBias, 0 );\n\t\t\tvDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * shadowWorldPosition;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * pointLightShadows[ i ].shadowNormalBias, 0 );\n\t\t\tvPointShadowCoord[ i ] = pointShadowMatrix[ i ] * shadowWorldPosition;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n#endif\n#if NUM_SPOT_LIGHT_COORDS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_COORDS; i ++ ) {\n\t\tshadowWorldPosition = worldPosition;\n\t\t#if ( defined( USE_SHADOWMAP ) && UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\t\tshadowWorldPosition.xyz += shadowWorldNormal * spotLightShadows[ i ].shadowNormalBias;\n\t\t#endif\n\t\tvSpotLightCoord[ i ] = spotLightMatrix[ i ] * shadowWorldPosition;\n\t}\n\t#pragma unroll_loop_end\n#endif"; - clone() { +var shadowmask_pars_fragment = "float getShadowMask() {\n\tfloat shadow = 1.0;\n\t#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\tdirectionalLight = directionalLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowIntensity, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_SHADOWS; i ++ ) {\n\t\tspotLight = spotLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowIntensity, spotLight.shadowBias, spotLight.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\tpointLight = pointLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowIntensity, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#endif\n\treturn shadow;\n}"; - return new this.constructor( this.x, this.y ); +var skinbase_vertex = "#ifdef USE_SKINNING\n\tmat4 boneMatX = getBoneMatrix( skinIndex.x );\n\tmat4 boneMatY = getBoneMatrix( skinIndex.y );\n\tmat4 boneMatZ = getBoneMatrix( skinIndex.z );\n\tmat4 boneMatW = getBoneMatrix( skinIndex.w );\n#endif"; - } +var skinning_pars_vertex = "#ifdef USE_SKINNING\n\tuniform mat4 bindMatrix;\n\tuniform mat4 bindMatrixInverse;\n\tuniform highp sampler2D boneTexture;\n\tmat4 getBoneMatrix( const in float i ) {\n\t\tint size = textureSize( boneTexture, 0 ).x;\n\t\tint j = int( i ) * 4;\n\t\tint x = j % size;\n\t\tint y = j / size;\n\t\tvec4 v1 = texelFetch( boneTexture, ivec2( x, y ), 0 );\n\t\tvec4 v2 = texelFetch( boneTexture, ivec2( x + 1, y ), 0 );\n\t\tvec4 v3 = texelFetch( boneTexture, ivec2( x + 2, y ), 0 );\n\t\tvec4 v4 = texelFetch( boneTexture, ivec2( x + 3, y ), 0 );\n\t\treturn mat4( v1, v2, v3, v4 );\n\t}\n#endif"; - copy( v ) { +var skinning_vertex = "#ifdef USE_SKINNING\n\tvec4 skinVertex = bindMatrix * vec4( transformed, 1.0 );\n\tvec4 skinned = vec4( 0.0 );\n\tskinned += boneMatX * skinVertex * skinWeight.x;\n\tskinned += boneMatY * skinVertex * skinWeight.y;\n\tskinned += boneMatZ * skinVertex * skinWeight.z;\n\tskinned += boneMatW * skinVertex * skinWeight.w;\n\ttransformed = ( bindMatrixInverse * skinned ).xyz;\n#endif"; - this.x = v.x; - this.y = v.y; +var skinnormal_vertex = "#ifdef USE_SKINNING\n\tmat4 skinMatrix = mat4( 0.0 );\n\tskinMatrix += skinWeight.x * boneMatX;\n\tskinMatrix += skinWeight.y * boneMatY;\n\tskinMatrix += skinWeight.z * boneMatZ;\n\tskinMatrix += skinWeight.w * boneMatW;\n\tskinMatrix = bindMatrixInverse * skinMatrix * bindMatrix;\n\tobjectNormal = vec4( skinMatrix * vec4( objectNormal, 0.0 ) ).xyz;\n\t#ifdef USE_TANGENT\n\t\tobjectTangent = vec4( skinMatrix * vec4( objectTangent, 0.0 ) ).xyz;\n\t#endif\n#endif"; - return this; +var specularmap_fragment = "float specularStrength;\n#ifdef USE_SPECULARMAP\n\tvec4 texelSpecular = texture2D( specularMap, vSpecularMapUv );\n\tspecularStrength = texelSpecular.r;\n#else\n\tspecularStrength = 1.0;\n#endif"; - } +var specularmap_pars_fragment = "#ifdef USE_SPECULARMAP\n\tuniform sampler2D specularMap;\n#endif"; - add( v ) { +var tonemapping_fragment = "#if defined( TONE_MAPPING )\n\tgl_FragColor.rgb = toneMapping( gl_FragColor.rgb );\n#endif"; - this.x += v.x; - this.y += v.y; +var tonemapping_pars_fragment = "#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\nuniform float toneMappingExposure;\nvec3 LinearToneMapping( vec3 color ) {\n\treturn saturate( toneMappingExposure * color );\n}\nvec3 ReinhardToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\treturn saturate( color / ( vec3( 1.0 ) + color ) );\n}\nvec3 CineonToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\tcolor = max( vec3( 0.0 ), color - 0.004 );\n\treturn pow( ( color * ( 6.2 * color + 0.5 ) ) / ( color * ( 6.2 * color + 1.7 ) + 0.06 ), vec3( 2.2 ) );\n}\nvec3 RRTAndODTFit( vec3 v ) {\n\tvec3 a = v * ( v + 0.0245786 ) - 0.000090537;\n\tvec3 b = v * ( 0.983729 * v + 0.4329510 ) + 0.238081;\n\treturn a / b;\n}\nvec3 ACESFilmicToneMapping( vec3 color ) {\n\tconst mat3 ACESInputMat = mat3(\n\t\tvec3( 0.59719, 0.07600, 0.02840 ),\t\tvec3( 0.35458, 0.90834, 0.13383 ),\n\t\tvec3( 0.04823, 0.01566, 0.83777 )\n\t);\n\tconst mat3 ACESOutputMat = mat3(\n\t\tvec3( 1.60475, -0.10208, -0.00327 ),\t\tvec3( -0.53108, 1.10813, -0.07276 ),\n\t\tvec3( -0.07367, -0.00605, 1.07602 )\n\t);\n\tcolor *= toneMappingExposure / 0.6;\n\tcolor = ACESInputMat * color;\n\tcolor = RRTAndODTFit( color );\n\tcolor = ACESOutputMat * color;\n\treturn saturate( color );\n}\nconst mat3 LINEAR_REC2020_TO_LINEAR_SRGB = mat3(\n\tvec3( 1.6605, - 0.1246, - 0.0182 ),\n\tvec3( - 0.5876, 1.1329, - 0.1006 ),\n\tvec3( - 0.0728, - 0.0083, 1.1187 )\n);\nconst mat3 LINEAR_SRGB_TO_LINEAR_REC2020 = mat3(\n\tvec3( 0.6274, 0.0691, 0.0164 ),\n\tvec3( 0.3293, 0.9195, 0.0880 ),\n\tvec3( 0.0433, 0.0113, 0.8956 )\n);\nvec3 agxDefaultContrastApprox( vec3 x ) {\n\tvec3 x2 = x * x;\n\tvec3 x4 = x2 * x2;\n\treturn + 15.5 * x4 * x2\n\t\t- 40.14 * x4 * x\n\t\t+ 31.96 * x4\n\t\t- 6.868 * x2 * x\n\t\t+ 0.4298 * x2\n\t\t+ 0.1191 * x\n\t\t- 0.00232;\n}\nvec3 AgXToneMapping( vec3 color ) {\n\tconst mat3 AgXInsetMatrix = mat3(\n\t\tvec3( 0.856627153315983, 0.137318972929847, 0.11189821299995 ),\n\t\tvec3( 0.0951212405381588, 0.761241990602591, 0.0767994186031903 ),\n\t\tvec3( 0.0482516061458583, 0.101439036467562, 0.811302368396859 )\n\t);\n\tconst mat3 AgXOutsetMatrix = mat3(\n\t\tvec3( 1.1271005818144368, - 0.1413297634984383, - 0.14132976349843826 ),\n\t\tvec3( - 0.11060664309660323, 1.157823702216272, - 0.11060664309660294 ),\n\t\tvec3( - 0.016493938717834573, - 0.016493938717834257, 1.2519364065950405 )\n\t);\n\tconst float AgxMinEv = - 12.47393;\tconst float AgxMaxEv = 4.026069;\n\tcolor *= toneMappingExposure;\n\tcolor = LINEAR_SRGB_TO_LINEAR_REC2020 * color;\n\tcolor = AgXInsetMatrix * color;\n\tcolor = max( color, 1e-10 );\tcolor = log2( color );\n\tcolor = ( color - AgxMinEv ) / ( AgxMaxEv - AgxMinEv );\n\tcolor = clamp( color, 0.0, 1.0 );\n\tcolor = agxDefaultContrastApprox( color );\n\tcolor = AgXOutsetMatrix * color;\n\tcolor = pow( max( vec3( 0.0 ), color ), vec3( 2.2 ) );\n\tcolor = LINEAR_REC2020_TO_LINEAR_SRGB * color;\n\tcolor = clamp( color, 0.0, 1.0 );\n\treturn color;\n}\nvec3 NeutralToneMapping( vec3 color ) {\n\tconst float StartCompression = 0.8 - 0.04;\n\tconst float Desaturation = 0.15;\n\tcolor *= toneMappingExposure;\n\tfloat x = min( color.r, min( color.g, color.b ) );\n\tfloat offset = x < 0.08 ? x - 6.25 * x * x : 0.04;\n\tcolor -= offset;\n\tfloat peak = max( color.r, max( color.g, color.b ) );\n\tif ( peak < StartCompression ) return color;\n\tfloat d = 1. - StartCompression;\n\tfloat newPeak = 1. - d * d / ( peak + d - StartCompression );\n\tcolor *= newPeak / peak;\n\tfloat g = 1. - 1. / ( Desaturation * ( peak - newPeak ) + 1. );\n\treturn mix( color, vec3( newPeak ), g );\n}\nvec3 CustomToneMapping( vec3 color ) { return color; }"; - return this; +var transmission_fragment = "#ifdef USE_TRANSMISSION\n\tmaterial.transmission = transmission;\n\tmaterial.transmissionAlpha = 1.0;\n\tmaterial.thickness = thickness;\n\tmaterial.attenuationDistance = attenuationDistance;\n\tmaterial.attenuationColor = attenuationColor;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tmaterial.transmission *= texture2D( transmissionMap, vTransmissionMapUv ).r;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tmaterial.thickness *= texture2D( thicknessMap, vThicknessMapUv ).g;\n\t#endif\n\tvec3 pos = vWorldPosition;\n\tvec3 v = normalize( cameraPosition - pos );\n\tvec3 n = inverseTransformDirection( normal, viewMatrix );\n\tvec4 transmitted = getIBLVolumeRefraction(\n\t\tn, v, material.roughness, material.diffuseColor, material.specularColor, material.specularF90,\n\t\tpos, modelMatrix, viewMatrix, projectionMatrix, material.dispersion, material.ior, material.thickness,\n\t\tmaterial.attenuationColor, material.attenuationDistance );\n\tmaterial.transmissionAlpha = mix( material.transmissionAlpha, transmitted.a, material.transmission );\n\ttotalDiffuse = mix( totalDiffuse, transmitted.rgb, material.transmission );\n#endif"; - } +var transmission_pars_fragment = "#ifdef USE_TRANSMISSION\n\tuniform float transmission;\n\tuniform float thickness;\n\tuniform float attenuationDistance;\n\tuniform vec3 attenuationColor;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tuniform sampler2D transmissionMap;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tuniform sampler2D thicknessMap;\n\t#endif\n\tuniform vec2 transmissionSamplerSize;\n\tuniform sampler2D transmissionSamplerMap;\n\tuniform mat4 modelMatrix;\n\tuniform mat4 projectionMatrix;\n\tvarying vec3 vWorldPosition;\n\tfloat w0( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * ( a * ( - a + 3.0 ) - 3.0 ) + 1.0 );\n\t}\n\tfloat w1( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * a * ( 3.0 * a - 6.0 ) + 4.0 );\n\t}\n\tfloat w2( float a ){\n\t\treturn ( 1.0 / 6.0 ) * ( a * ( a * ( - 3.0 * a + 3.0 ) + 3.0 ) + 1.0 );\n\t}\n\tfloat w3( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * a * a );\n\t}\n\tfloat g0( float a ) {\n\t\treturn w0( a ) + w1( a );\n\t}\n\tfloat g1( float a ) {\n\t\treturn w2( a ) + w3( a );\n\t}\n\tfloat h0( float a ) {\n\t\treturn - 1.0 + w1( a ) / ( w0( a ) + w1( a ) );\n\t}\n\tfloat h1( float a ) {\n\t\treturn 1.0 + w3( a ) / ( w2( a ) + w3( a ) );\n\t}\n\tvec4 bicubic( sampler2D tex, vec2 uv, vec4 texelSize, float lod ) {\n\t\tuv = uv * texelSize.zw + 0.5;\n\t\tvec2 iuv = floor( uv );\n\t\tvec2 fuv = fract( uv );\n\t\tfloat g0x = g0( fuv.x );\n\t\tfloat g1x = g1( fuv.x );\n\t\tfloat h0x = h0( fuv.x );\n\t\tfloat h1x = h1( fuv.x );\n\t\tfloat h0y = h0( fuv.y );\n\t\tfloat h1y = h1( fuv.y );\n\t\tvec2 p0 = ( vec2( iuv.x + h0x, iuv.y + h0y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p1 = ( vec2( iuv.x + h1x, iuv.y + h0y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p2 = ( vec2( iuv.x + h0x, iuv.y + h1y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p3 = ( vec2( iuv.x + h1x, iuv.y + h1y ) - 0.5 ) * texelSize.xy;\n\t\treturn g0( fuv.y ) * ( g0x * textureLod( tex, p0, lod ) + g1x * textureLod( tex, p1, lod ) ) +\n\t\t\tg1( fuv.y ) * ( g0x * textureLod( tex, p2, lod ) + g1x * textureLod( tex, p3, lod ) );\n\t}\n\tvec4 textureBicubic( sampler2D sampler, vec2 uv, float lod ) {\n\t\tvec2 fLodSize = vec2( textureSize( sampler, int( lod ) ) );\n\t\tvec2 cLodSize = vec2( textureSize( sampler, int( lod + 1.0 ) ) );\n\t\tvec2 fLodSizeInv = 1.0 / fLodSize;\n\t\tvec2 cLodSizeInv = 1.0 / cLodSize;\n\t\tvec4 fSample = bicubic( sampler, uv, vec4( fLodSizeInv, fLodSize ), floor( lod ) );\n\t\tvec4 cSample = bicubic( sampler, uv, vec4( cLodSizeInv, cLodSize ), ceil( lod ) );\n\t\treturn mix( fSample, cSample, fract( lod ) );\n\t}\n\tvec3 getVolumeTransmissionRay( const in vec3 n, const in vec3 v, const in float thickness, const in float ior, const in mat4 modelMatrix ) {\n\t\tvec3 refractionVector = refract( - v, normalize( n ), 1.0 / ior );\n\t\tvec3 modelScale;\n\t\tmodelScale.x = length( vec3( modelMatrix[ 0 ].xyz ) );\n\t\tmodelScale.y = length( vec3( modelMatrix[ 1 ].xyz ) );\n\t\tmodelScale.z = length( vec3( modelMatrix[ 2 ].xyz ) );\n\t\treturn normalize( refractionVector ) * thickness * modelScale;\n\t}\n\tfloat applyIorToRoughness( const in float roughness, const in float ior ) {\n\t\treturn roughness * clamp( ior * 2.0 - 2.0, 0.0, 1.0 );\n\t}\n\tvec4 getTransmissionSample( const in vec2 fragCoord, const in float roughness, const in float ior ) {\n\t\tfloat lod = log2( transmissionSamplerSize.x ) * applyIorToRoughness( roughness, ior );\n\t\treturn textureBicubic( transmissionSamplerMap, fragCoord.xy, lod );\n\t}\n\tvec3 volumeAttenuation( const in float transmissionDistance, const in vec3 attenuationColor, const in float attenuationDistance ) {\n\t\tif ( isinf( attenuationDistance ) ) {\n\t\t\treturn vec3( 1.0 );\n\t\t} else {\n\t\t\tvec3 attenuationCoefficient = -log( attenuationColor ) / attenuationDistance;\n\t\t\tvec3 transmittance = exp( - attenuationCoefficient * transmissionDistance );\t\t\treturn transmittance;\n\t\t}\n\t}\n\tvec4 getIBLVolumeRefraction( const in vec3 n, const in vec3 v, const in float roughness, const in vec3 diffuseColor,\n\t\tconst in vec3 specularColor, const in float specularF90, const in vec3 position, const in mat4 modelMatrix,\n\t\tconst in mat4 viewMatrix, const in mat4 projMatrix, const in float dispersion, const in float ior, const in float thickness,\n\t\tconst in vec3 attenuationColor, const in float attenuationDistance ) {\n\t\tvec4 transmittedLight;\n\t\tvec3 transmittance;\n\t\t#ifdef USE_DISPERSION\n\t\t\tfloat halfSpread = ( ior - 1.0 ) * 0.025 * dispersion;\n\t\t\tvec3 iors = vec3( ior - halfSpread, ior, ior + halfSpread );\n\t\t\tfor ( int i = 0; i < 3; i ++ ) {\n\t\t\t\tvec3 transmissionRay = getVolumeTransmissionRay( n, v, thickness, iors[ i ], modelMatrix );\n\t\t\t\tvec3 refractedRayExit = position + transmissionRay;\n\t\t\t\tvec4 ndcPos = projMatrix * viewMatrix * vec4( refractedRayExit, 1.0 );\n\t\t\t\tvec2 refractionCoords = ndcPos.xy / ndcPos.w;\n\t\t\t\trefractionCoords += 1.0;\n\t\t\t\trefractionCoords /= 2.0;\n\t\t\t\tvec4 transmissionSample = getTransmissionSample( refractionCoords, roughness, iors[ i ] );\n\t\t\t\ttransmittedLight[ i ] = transmissionSample[ i ];\n\t\t\t\ttransmittedLight.a += transmissionSample.a;\n\t\t\t\ttransmittance[ i ] = diffuseColor[ i ] * volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance )[ i ];\n\t\t\t}\n\t\t\ttransmittedLight.a /= 3.0;\n\t\t#else\n\t\t\tvec3 transmissionRay = getVolumeTransmissionRay( n, v, thickness, ior, modelMatrix );\n\t\t\tvec3 refractedRayExit = position + transmissionRay;\n\t\t\tvec4 ndcPos = projMatrix * viewMatrix * vec4( refractedRayExit, 1.0 );\n\t\t\tvec2 refractionCoords = ndcPos.xy / ndcPos.w;\n\t\t\trefractionCoords += 1.0;\n\t\t\trefractionCoords /= 2.0;\n\t\t\ttransmittedLight = getTransmissionSample( refractionCoords, roughness, ior );\n\t\t\ttransmittance = diffuseColor * volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance );\n\t\t#endif\n\t\tvec3 attenuatedColor = transmittance * transmittedLight.rgb;\n\t\tvec3 F = EnvironmentBRDF( n, v, specularColor, specularF90, roughness );\n\t\tfloat transmittanceFactor = ( transmittance.r + transmittance.g + transmittance.b ) / 3.0;\n\t\treturn vec4( ( 1.0 - F ) * attenuatedColor, 1.0 - ( 1.0 - transmittedLight.a ) * transmittanceFactor );\n\t}\n#endif"; - addScalar( s ) { +var uv_pars_fragment = "#if defined( USE_UV ) || defined( USE_ANISOTROPY )\n\tvarying vec2 vUv;\n#endif\n#ifdef USE_MAP\n\tvarying vec2 vMapUv;\n#endif\n#ifdef USE_ALPHAMAP\n\tvarying vec2 vAlphaMapUv;\n#endif\n#ifdef USE_LIGHTMAP\n\tvarying vec2 vLightMapUv;\n#endif\n#ifdef USE_AOMAP\n\tvarying vec2 vAoMapUv;\n#endif\n#ifdef USE_BUMPMAP\n\tvarying vec2 vBumpMapUv;\n#endif\n#ifdef USE_NORMALMAP\n\tvarying vec2 vNormalMapUv;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tvarying vec2 vEmissiveMapUv;\n#endif\n#ifdef USE_METALNESSMAP\n\tvarying vec2 vMetalnessMapUv;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tvarying vec2 vRoughnessMapUv;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tvarying vec2 vAnisotropyMapUv;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tvarying vec2 vClearcoatMapUv;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tvarying vec2 vClearcoatNormalMapUv;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tvarying vec2 vClearcoatRoughnessMapUv;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tvarying vec2 vIridescenceMapUv;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tvarying vec2 vIridescenceThicknessMapUv;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tvarying vec2 vSheenColorMapUv;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tvarying vec2 vSheenRoughnessMapUv;\n#endif\n#ifdef USE_SPECULARMAP\n\tvarying vec2 vSpecularMapUv;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tvarying vec2 vSpecularColorMapUv;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tvarying vec2 vSpecularIntensityMapUv;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tuniform mat3 transmissionMapTransform;\n\tvarying vec2 vTransmissionMapUv;\n#endif\n#ifdef USE_THICKNESSMAP\n\tuniform mat3 thicknessMapTransform;\n\tvarying vec2 vThicknessMapUv;\n#endif"; - this.x += s; - this.y += s; +var uv_pars_vertex = "#if defined( USE_UV ) || defined( USE_ANISOTROPY )\n\tvarying vec2 vUv;\n#endif\n#ifdef USE_MAP\n\tuniform mat3 mapTransform;\n\tvarying vec2 vMapUv;\n#endif\n#ifdef USE_ALPHAMAP\n\tuniform mat3 alphaMapTransform;\n\tvarying vec2 vAlphaMapUv;\n#endif\n#ifdef USE_LIGHTMAP\n\tuniform mat3 lightMapTransform;\n\tvarying vec2 vLightMapUv;\n#endif\n#ifdef USE_AOMAP\n\tuniform mat3 aoMapTransform;\n\tvarying vec2 vAoMapUv;\n#endif\n#ifdef USE_BUMPMAP\n\tuniform mat3 bumpMapTransform;\n\tvarying vec2 vBumpMapUv;\n#endif\n#ifdef USE_NORMALMAP\n\tuniform mat3 normalMapTransform;\n\tvarying vec2 vNormalMapUv;\n#endif\n#ifdef USE_DISPLACEMENTMAP\n\tuniform mat3 displacementMapTransform;\n\tvarying vec2 vDisplacementMapUv;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tuniform mat3 emissiveMapTransform;\n\tvarying vec2 vEmissiveMapUv;\n#endif\n#ifdef USE_METALNESSMAP\n\tuniform mat3 metalnessMapTransform;\n\tvarying vec2 vMetalnessMapUv;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tuniform mat3 roughnessMapTransform;\n\tvarying vec2 vRoughnessMapUv;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tuniform mat3 anisotropyMapTransform;\n\tvarying vec2 vAnisotropyMapUv;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tuniform mat3 clearcoatMapTransform;\n\tvarying vec2 vClearcoatMapUv;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tuniform mat3 clearcoatNormalMapTransform;\n\tvarying vec2 vClearcoatNormalMapUv;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tuniform mat3 clearcoatRoughnessMapTransform;\n\tvarying vec2 vClearcoatRoughnessMapUv;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tuniform mat3 sheenColorMapTransform;\n\tvarying vec2 vSheenColorMapUv;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tuniform mat3 sheenRoughnessMapTransform;\n\tvarying vec2 vSheenRoughnessMapUv;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tuniform mat3 iridescenceMapTransform;\n\tvarying vec2 vIridescenceMapUv;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tuniform mat3 iridescenceThicknessMapTransform;\n\tvarying vec2 vIridescenceThicknessMapUv;\n#endif\n#ifdef USE_SPECULARMAP\n\tuniform mat3 specularMapTransform;\n\tvarying vec2 vSpecularMapUv;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tuniform mat3 specularColorMapTransform;\n\tvarying vec2 vSpecularColorMapUv;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tuniform mat3 specularIntensityMapTransform;\n\tvarying vec2 vSpecularIntensityMapUv;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tuniform mat3 transmissionMapTransform;\n\tvarying vec2 vTransmissionMapUv;\n#endif\n#ifdef USE_THICKNESSMAP\n\tuniform mat3 thicknessMapTransform;\n\tvarying vec2 vThicknessMapUv;\n#endif"; - return this; +var uv_vertex = "#if defined( USE_UV ) || defined( USE_ANISOTROPY )\n\tvUv = vec3( uv, 1 ).xy;\n#endif\n#ifdef USE_MAP\n\tvMapUv = ( mapTransform * vec3( MAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ALPHAMAP\n\tvAlphaMapUv = ( alphaMapTransform * vec3( ALPHAMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_LIGHTMAP\n\tvLightMapUv = ( lightMapTransform * vec3( LIGHTMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_AOMAP\n\tvAoMapUv = ( aoMapTransform * vec3( AOMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_BUMPMAP\n\tvBumpMapUv = ( bumpMapTransform * vec3( BUMPMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_NORMALMAP\n\tvNormalMapUv = ( normalMapTransform * vec3( NORMALMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_DISPLACEMENTMAP\n\tvDisplacementMapUv = ( displacementMapTransform * vec3( DISPLACEMENTMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tvEmissiveMapUv = ( emissiveMapTransform * vec3( EMISSIVEMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_METALNESSMAP\n\tvMetalnessMapUv = ( metalnessMapTransform * vec3( METALNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tvRoughnessMapUv = ( roughnessMapTransform * vec3( ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tvAnisotropyMapUv = ( anisotropyMapTransform * vec3( ANISOTROPYMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tvClearcoatMapUv = ( clearcoatMapTransform * vec3( CLEARCOATMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tvClearcoatNormalMapUv = ( clearcoatNormalMapTransform * vec3( CLEARCOAT_NORMALMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tvClearcoatRoughnessMapUv = ( clearcoatRoughnessMapTransform * vec3( CLEARCOAT_ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tvIridescenceMapUv = ( iridescenceMapTransform * vec3( IRIDESCENCEMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tvIridescenceThicknessMapUv = ( iridescenceThicknessMapTransform * vec3( IRIDESCENCE_THICKNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tvSheenColorMapUv = ( sheenColorMapTransform * vec3( SHEEN_COLORMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tvSheenRoughnessMapUv = ( sheenRoughnessMapTransform * vec3( SHEEN_ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULARMAP\n\tvSpecularMapUv = ( specularMapTransform * vec3( SPECULARMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tvSpecularColorMapUv = ( specularColorMapTransform * vec3( SPECULAR_COLORMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tvSpecularIntensityMapUv = ( specularIntensityMapTransform * vec3( SPECULAR_INTENSITYMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tvTransmissionMapUv = ( transmissionMapTransform * vec3( TRANSMISSIONMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_THICKNESSMAP\n\tvThicknessMapUv = ( thicknessMapTransform * vec3( THICKNESSMAP_UV, 1 ) ).xy;\n#endif"; - } +var worldpos_vertex = "#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined ( USE_SHADOWMAP ) || defined ( USE_TRANSMISSION ) || NUM_SPOT_LIGHT_COORDS > 0\n\tvec4 worldPosition = vec4( transformed, 1.0 );\n\t#ifdef USE_BATCHING\n\t\tworldPosition = batchingMatrix * worldPosition;\n\t#endif\n\t#ifdef USE_INSTANCING\n\t\tworldPosition = instanceMatrix * worldPosition;\n\t#endif\n\tworldPosition = modelMatrix * worldPosition;\n#endif"; - addVectors( a, b ) { +const vertex$h = "varying vec2 vUv;\nuniform mat3 uvTransform;\nvoid main() {\n\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n\tgl_Position = vec4( position.xy, 1.0, 1.0 );\n}"; - this.x = a.x + b.x; - this.y = a.y + b.y; +const fragment$h = "uniform sampler2D t2D;\nuniform float backgroundIntensity;\nvarying vec2 vUv;\nvoid main() {\n\tvec4 texColor = texture2D( t2D, vUv );\n\t#ifdef DECODE_VIDEO_TEXTURE\n\t\ttexColor = vec4( mix( pow( texColor.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), texColor.rgb * 0.0773993808, vec3( lessThanEqual( texColor.rgb, vec3( 0.04045 ) ) ) ), texColor.w );\n\t#endif\n\ttexColor.rgb *= backgroundIntensity;\n\tgl_FragColor = texColor;\n\t#include \n\t#include \n}"; - return this; +const vertex$g = "varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n\tgl_Position.z = gl_Position.w;\n}"; - } +const fragment$g = "#ifdef ENVMAP_TYPE_CUBE\n\tuniform samplerCube envMap;\n#elif defined( ENVMAP_TYPE_CUBE_UV )\n\tuniform sampler2D envMap;\n#endif\nuniform float flipEnvMap;\nuniform float backgroundBlurriness;\nuniform float backgroundIntensity;\nuniform mat3 backgroundRotation;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 texColor = textureCube( envMap, backgroundRotation * vec3( flipEnvMap * vWorldDirection.x, vWorldDirection.yz ) );\n\t#elif defined( ENVMAP_TYPE_CUBE_UV )\n\t\tvec4 texColor = textureCubeUV( envMap, backgroundRotation * vWorldDirection, backgroundBlurriness );\n\t#else\n\t\tvec4 texColor = vec4( 0.0, 0.0, 0.0, 1.0 );\n\t#endif\n\ttexColor.rgb *= backgroundIntensity;\n\tgl_FragColor = texColor;\n\t#include \n\t#include \n}"; - addScaledVector( v, s ) { +const vertex$f = "varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n\tgl_Position.z = gl_Position.w;\n}"; - this.x += v.x * s; - this.y += v.y * s; +const fragment$f = "uniform samplerCube tCube;\nuniform float tFlip;\nuniform float opacity;\nvarying vec3 vWorldDirection;\nvoid main() {\n\tvec4 texColor = textureCube( tCube, vec3( tFlip * vWorldDirection.x, vWorldDirection.yz ) );\n\tgl_FragColor = texColor;\n\tgl_FragColor.a *= opacity;\n\t#include \n\t#include \n}"; - return this; +const vertex$e = "#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvHighPrecisionZW = gl_Position.zw;\n}"; - } +const fragment$e = "#if DEPTH_PACKING == 3200\n\tuniform float opacity;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include \n\t#if DEPTH_PACKING == 3200\n\t\tdiffuseColor.a = opacity;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tfloat fragCoordZ = 0.5 * vHighPrecisionZW[0] / vHighPrecisionZW[1] + 0.5;\n\t#if DEPTH_PACKING == 3200\n\t\tgl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity );\n\t#elif DEPTH_PACKING == 3201\n\t\tgl_FragColor = packDepthToRGBA( fragCoordZ );\n\t#elif DEPTH_PACKING == 3202\n\t\tgl_FragColor = vec4( packDepthToRGB( fragCoordZ ), 1.0 );\n\t#elif DEPTH_PACKING == 3203\n\t\tgl_FragColor = vec4( packDepthToRG( fragCoordZ ), 0.0, 1.0 );\n\t#endif\n}"; - sub( v ) { +const vertex$d = "#define DISTANCE\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvWorldPosition = worldPosition.xyz;\n}"; - this.x -= v.x; - this.y -= v.y; +const fragment$d = "#define DISTANCE\nuniform vec3 referencePosition;\nuniform float nearDistance;\nuniform float farDistance;\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main () {\n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tfloat dist = length( vWorldPosition - referencePosition );\n\tdist = ( dist - nearDistance ) / ( farDistance - nearDistance );\n\tdist = saturate( dist );\n\tgl_FragColor = packDepthToRGBA( dist );\n}"; - return this; - - } - - subScalar( s ) { - - this.x -= s; - this.y -= s; - - return this; - - } - - subVectors( a, b ) { - - this.x = a.x - b.x; - this.y = a.y - b.y; - - return this; - - } - - multiply( v ) { - - this.x *= v.x; - this.y *= v.y; - - return this; - - } - - multiplyScalar( scalar ) { - - this.x *= scalar; - this.y *= scalar; - - return this; - - } - - divide( v ) { - - this.x /= v.x; - this.y /= v.y; - - return this; - - } - - divideScalar( scalar ) { - - return this.multiplyScalar( 1 / scalar ); - - } - - applyMatrix3( m ) { - - const x = this.x, y = this.y; - const e = m.elements; - - this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ]; - this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ]; - - return this; - - } - - min( v ) { - - this.x = Math.min( this.x, v.x ); - this.y = Math.min( this.y, v.y ); - - return this; - - } - - max( v ) { - - this.x = Math.max( this.x, v.x ); - this.y = Math.max( this.y, v.y ); - - return this; - - } - - clamp( min, max ) { - - // assumes min < max, componentwise - - this.x = Math.max( min.x, Math.min( max.x, this.x ) ); - this.y = Math.max( min.y, Math.min( max.y, this.y ) ); - - return this; - - } - - clampScalar( minVal, maxVal ) { - - this.x = Math.max( minVal, Math.min( maxVal, this.x ) ); - this.y = Math.max( minVal, Math.min( maxVal, this.y ) ); - - return this; - - } - - clampLength( min, max ) { - - const length = this.length(); - - return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) ); - - } - - floor() { - - this.x = Math.floor( this.x ); - this.y = Math.floor( this.y ); - - return this; - - } - - ceil() { - - this.x = Math.ceil( this.x ); - this.y = Math.ceil( this.y ); - - return this; - - } - - round() { - - this.x = Math.round( this.x ); - this.y = Math.round( this.y ); - - return this; - - } - - roundToZero() { - - this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x ); - this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y ); - - return this; - - } - - negate() { - - this.x = - this.x; - this.y = - this.y; - - return this; - - } - - dot( v ) { - - return this.x * v.x + this.y * v.y; - - } - - cross( v ) { - - return this.x * v.y - this.y * v.x; - - } - - lengthSq() { - - return this.x * this.x + this.y * this.y; - - } - - length() { - - return Math.sqrt( this.x * this.x + this.y * this.y ); - - } - - manhattanLength() { - - return Math.abs( this.x ) + Math.abs( this.y ); - - } - - normalize() { - - return this.divideScalar( this.length() || 1 ); - - } - - angle() { - - // computes the angle in radians with respect to the positive x-axis - - const angle = Math.atan2( - this.y, - this.x ) + Math.PI; - - return angle; - - } - - angleTo( v ) { - - const denominator = Math.sqrt( this.lengthSq() * v.lengthSq() ); - - if ( denominator === 0 ) return Math.PI / 2; - - const theta = this.dot( v ) / denominator; - - // clamp, to handle numerical problems - - return Math.acos( clamp( theta, - 1, 1 ) ); - - } - - distanceTo( v ) { - - return Math.sqrt( this.distanceToSquared( v ) ); - - } - - distanceToSquared( v ) { - - const dx = this.x - v.x, dy = this.y - v.y; - return dx * dx + dy * dy; - - } - - manhattanDistanceTo( v ) { - - return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y ); - - } - - setLength( length ) { - - return this.normalize().multiplyScalar( length ); - - } - - lerp( v, alpha ) { - - this.x += ( v.x - this.x ) * alpha; - this.y += ( v.y - this.y ) * alpha; - - return this; - - } - - lerpVectors( v1, v2, alpha ) { - - this.x = v1.x + ( v2.x - v1.x ) * alpha; - this.y = v1.y + ( v2.y - v1.y ) * alpha; - - return this; - - } - - equals( v ) { - - return ( ( v.x === this.x ) && ( v.y === this.y ) ); - - } - - fromArray( array, offset = 0 ) { - - this.x = array[ offset ]; - this.y = array[ offset + 1 ]; - - return this; - - } - - toArray( array = [], offset = 0 ) { - - array[ offset ] = this.x; - array[ offset + 1 ] = this.y; - - return array; - - } - - fromBufferAttribute( attribute, index ) { - - this.x = attribute.getX( index ); - this.y = attribute.getY( index ); - - return this; - - } - - rotateAround( center, angle ) { - - const c = Math.cos( angle ), s = Math.sin( angle ); - - const x = this.x - center.x; - const y = this.y - center.y; - - this.x = x * c - y * s + center.x; - this.y = x * s + y * c + center.y; - - return this; - - } - - random() { - - this.x = Math.random(); - this.y = Math.random(); - - return this; - - } - - *[ Symbol.iterator ]() { - - yield this.x; - yield this.y; - - } - -} - -class Matrix3 { - - constructor( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) { - - Matrix3.prototype.isMatrix3 = true; - - this.elements = [ - - 1, 0, 0, - 0, 1, 0, - 0, 0, 1 - - ]; - - if ( n11 !== undefined ) { - - this.set( n11, n12, n13, n21, n22, n23, n31, n32, n33 ); - - } - - } - - set( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) { - - const te = this.elements; - - te[ 0 ] = n11; te[ 1 ] = n21; te[ 2 ] = n31; - te[ 3 ] = n12; te[ 4 ] = n22; te[ 5 ] = n32; - te[ 6 ] = n13; te[ 7 ] = n23; te[ 8 ] = n33; - - return this; - - } - - identity() { - - this.set( - - 1, 0, 0, - 0, 1, 0, - 0, 0, 1 - - ); - - return this; - - } - - copy( m ) { - - const te = this.elements; - const me = m.elements; - - te[ 0 ] = me[ 0 ]; te[ 1 ] = me[ 1 ]; te[ 2 ] = me[ 2 ]; - te[ 3 ] = me[ 3 ]; te[ 4 ] = me[ 4 ]; te[ 5 ] = me[ 5 ]; - te[ 6 ] = me[ 6 ]; te[ 7 ] = me[ 7 ]; te[ 8 ] = me[ 8 ]; - - return this; - - } - - extractBasis( xAxis, yAxis, zAxis ) { - - xAxis.setFromMatrix3Column( this, 0 ); - yAxis.setFromMatrix3Column( this, 1 ); - zAxis.setFromMatrix3Column( this, 2 ); - - return this; - - } - - setFromMatrix4( m ) { - - const me = m.elements; - - this.set( - - me[ 0 ], me[ 4 ], me[ 8 ], - me[ 1 ], me[ 5 ], me[ 9 ], - me[ 2 ], me[ 6 ], me[ 10 ] - - ); - - return this; - - } - - multiply( m ) { - - return this.multiplyMatrices( this, m ); - - } - - premultiply( m ) { - - return this.multiplyMatrices( m, this ); - - } - - multiplyMatrices( a, b ) { - - const ae = a.elements; - const be = b.elements; - const te = this.elements; - - const a11 = ae[ 0 ], a12 = ae[ 3 ], a13 = ae[ 6 ]; - const a21 = ae[ 1 ], a22 = ae[ 4 ], a23 = ae[ 7 ]; - const a31 = ae[ 2 ], a32 = ae[ 5 ], a33 = ae[ 8 ]; - - const b11 = be[ 0 ], b12 = be[ 3 ], b13 = be[ 6 ]; - const b21 = be[ 1 ], b22 = be[ 4 ], b23 = be[ 7 ]; - const b31 = be[ 2 ], b32 = be[ 5 ], b33 = be[ 8 ]; - - te[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31; - te[ 3 ] = a11 * b12 + a12 * b22 + a13 * b32; - te[ 6 ] = a11 * b13 + a12 * b23 + a13 * b33; - - te[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31; - te[ 4 ] = a21 * b12 + a22 * b22 + a23 * b32; - te[ 7 ] = a21 * b13 + a22 * b23 + a23 * b33; - - te[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31; - te[ 5 ] = a31 * b12 + a32 * b22 + a33 * b32; - te[ 8 ] = a31 * b13 + a32 * b23 + a33 * b33; - - return this; - - } - - multiplyScalar( s ) { - - const te = this.elements; - - te[ 0 ] *= s; te[ 3 ] *= s; te[ 6 ] *= s; - te[ 1 ] *= s; te[ 4 ] *= s; te[ 7 ] *= s; - te[ 2 ] *= s; te[ 5 ] *= s; te[ 8 ] *= s; - - return this; - - } - - determinant() { - - const te = this.elements; - - const a = te[ 0 ], b = te[ 1 ], c = te[ 2 ], - d = te[ 3 ], e = te[ 4 ], f = te[ 5 ], - g = te[ 6 ], h = te[ 7 ], i = te[ 8 ]; - - return a * e * i - a * f * h - b * d * i + b * f * g + c * d * h - c * e * g; - - } - - invert() { - - const te = this.elements, - - n11 = te[ 0 ], n21 = te[ 1 ], n31 = te[ 2 ], - n12 = te[ 3 ], n22 = te[ 4 ], n32 = te[ 5 ], - n13 = te[ 6 ], n23 = te[ 7 ], n33 = te[ 8 ], - - t11 = n33 * n22 - n32 * n23, - t12 = n32 * n13 - n33 * n12, - t13 = n23 * n12 - n22 * n13, - - det = n11 * t11 + n21 * t12 + n31 * t13; - - if ( det === 0 ) return this.set( 0, 0, 0, 0, 0, 0, 0, 0, 0 ); - - const detInv = 1 / det; - - te[ 0 ] = t11 * detInv; - te[ 1 ] = ( n31 * n23 - n33 * n21 ) * detInv; - te[ 2 ] = ( n32 * n21 - n31 * n22 ) * detInv; - - te[ 3 ] = t12 * detInv; - te[ 4 ] = ( n33 * n11 - n31 * n13 ) * detInv; - te[ 5 ] = ( n31 * n12 - n32 * n11 ) * detInv; - - te[ 6 ] = t13 * detInv; - te[ 7 ] = ( n21 * n13 - n23 * n11 ) * detInv; - te[ 8 ] = ( n22 * n11 - n21 * n12 ) * detInv; - - return this; - - } - - transpose() { - - let tmp; - const m = this.elements; - - tmp = m[ 1 ]; m[ 1 ] = m[ 3 ]; m[ 3 ] = tmp; - tmp = m[ 2 ]; m[ 2 ] = m[ 6 ]; m[ 6 ] = tmp; - tmp = m[ 5 ]; m[ 5 ] = m[ 7 ]; m[ 7 ] = tmp; - - return this; - - } - - getNormalMatrix( matrix4 ) { - - return this.setFromMatrix4( matrix4 ).invert().transpose(); - - } - - transposeIntoArray( r ) { - - const m = this.elements; - - r[ 0 ] = m[ 0 ]; - r[ 1 ] = m[ 3 ]; - r[ 2 ] = m[ 6 ]; - r[ 3 ] = m[ 1 ]; - r[ 4 ] = m[ 4 ]; - r[ 5 ] = m[ 7 ]; - r[ 6 ] = m[ 2 ]; - r[ 7 ] = m[ 5 ]; - r[ 8 ] = m[ 8 ]; - - return this; - - } - - setUvTransform( tx, ty, sx, sy, rotation, cx, cy ) { - - const c = Math.cos( rotation ); - const s = Math.sin( rotation ); - - this.set( - sx * c, sx * s, - sx * ( c * cx + s * cy ) + cx + tx, - - sy * s, sy * c, - sy * ( - s * cx + c * cy ) + cy + ty, - 0, 0, 1 - ); - - return this; - - } - - // - - scale( sx, sy ) { - - this.premultiply( _m3.makeScale( sx, sy ) ); - - return this; - - } - - rotate( theta ) { - - this.premultiply( _m3.makeRotation( - theta ) ); - - return this; - - } - - translate( tx, ty ) { - - this.premultiply( _m3.makeTranslation( tx, ty ) ); - - return this; - - } - - // for 2D Transforms - - makeTranslation( x, y ) { - - this.set( - - 1, 0, x, - 0, 1, y, - 0, 0, 1 - - ); - - return this; - - } - - makeRotation( theta ) { - - // counterclockwise - - const c = Math.cos( theta ); - const s = Math.sin( theta ); - - this.set( - - c, - s, 0, - s, c, 0, - 0, 0, 1 - - ); - - return this; - - } - - makeScale( x, y ) { - - this.set( - - x, 0, 0, - 0, y, 0, - 0, 0, 1 - - ); - - return this; - - } - - // - - equals( matrix ) { - - const te = this.elements; - const me = matrix.elements; - - for ( let i = 0; i < 9; i ++ ) { - - if ( te[ i ] !== me[ i ] ) return false; - - } - - return true; - - } - - fromArray( array, offset = 0 ) { - - for ( let i = 0; i < 9; i ++ ) { - - this.elements[ i ] = array[ i + offset ]; - - } - - return this; - - } - - toArray( array = [], offset = 0 ) { - - const te = this.elements; - - array[ offset ] = te[ 0 ]; - array[ offset + 1 ] = te[ 1 ]; - array[ offset + 2 ] = te[ 2 ]; - - array[ offset + 3 ] = te[ 3 ]; - array[ offset + 4 ] = te[ 4 ]; - array[ offset + 5 ] = te[ 5 ]; - - array[ offset + 6 ] = te[ 6 ]; - array[ offset + 7 ] = te[ 7 ]; - array[ offset + 8 ] = te[ 8 ]; - - return array; - - } - - clone() { - - return new this.constructor().fromArray( this.elements ); - - } - -} - -const _m3 = /*@__PURE__*/ new Matrix3(); - -function arrayNeedsUint32( array ) { - - // assumes larger values usually on last - - for ( let i = array.length - 1; i >= 0; -- i ) { - - if ( array[ i ] >= 65535 ) return true; // account for PRIMITIVE_RESTART_FIXED_INDEX, #24565 - - } - - return false; - -} - -const TYPED_ARRAYS = { - Int8Array: Int8Array, - Uint8Array: Uint8Array, - Uint8ClampedArray: Uint8ClampedArray, - Int16Array: Int16Array, - Uint16Array: Uint16Array, - Int32Array: Int32Array, - Uint32Array: Uint32Array, - Float32Array: Float32Array, - Float64Array: Float64Array -}; - -function getTypedArray( type, buffer ) { - - return new TYPED_ARRAYS[ type ]( buffer ); - -} - -function createElementNS( name ) { - - return document.createElementNS( 'http://www.w3.org/1999/xhtml', name ); - -} - -const _cache = {}; - -function warnOnce( message ) { - - if ( message in _cache ) return; - - _cache[ message ] = true; - - console.warn( message ); - -} - -function SRGBToLinear( c ) { - - return ( c < 0.04045 ) ? c * 0.0773993808 : Math.pow( c * 0.9478672986 + 0.0521327014, 2.4 ); - -} - -function LinearToSRGB( c ) { - - return ( c < 0.0031308 ) ? c * 12.92 : 1.055 * ( Math.pow( c, 0.41666 ) ) - 0.055; - -} - -/** - * Matrices converting P3 <-> Rec. 709 primaries, without gamut mapping - * or clipping. Based on W3C specifications for sRGB and Display P3, - * and ICC specifications for the D50 connection space. Values in/out - * are _linear_ sRGB and _linear_ Display P3. - * - * Note that both sRGB and Display P3 use the sRGB transfer functions. - * - * Reference: - * - http://www.russellcottrell.com/photo/matrixCalculator.htm - */ - -const LINEAR_SRGB_TO_LINEAR_DISPLAY_P3 = /*@__PURE__*/ new Matrix3().fromArray( [ - 0.8224621, 0.0331941, 0.0170827, - 0.1775380, 0.9668058, 0.0723974, - - 0.0000001, 0.0000001, 0.9105199 -] ); - -const LINEAR_DISPLAY_P3_TO_LINEAR_SRGB = /*@__PURE__*/ new Matrix3().fromArray( [ - 1.2249401, - 0.0420569, - 0.0196376, - - 0.2249404, 1.0420571, - 0.0786361, - 0.0000001, 0.0000000, 1.0982735 -] ); - -function DisplayP3ToLinearSRGB( color ) { - - // Display P3 uses the sRGB transfer functions - return color.convertSRGBToLinear().applyMatrix3( LINEAR_DISPLAY_P3_TO_LINEAR_SRGB ); - -} - -function LinearSRGBToDisplayP3( color ) { - - // Display P3 uses the sRGB transfer functions - return color.applyMatrix3( LINEAR_SRGB_TO_LINEAR_DISPLAY_P3 ).convertLinearToSRGB(); - -} - -// Conversions from to Linear-sRGB reference space. -const TO_LINEAR = { - [ LinearSRGBColorSpace ]: ( color ) => color, - [ SRGBColorSpace ]: ( color ) => color.convertSRGBToLinear(), - [ DisplayP3ColorSpace ]: DisplayP3ToLinearSRGB, -}; - -// Conversions to from Linear-sRGB reference space. -const FROM_LINEAR = { - [ LinearSRGBColorSpace ]: ( color ) => color, - [ SRGBColorSpace ]: ( color ) => color.convertLinearToSRGB(), - [ DisplayP3ColorSpace ]: LinearSRGBToDisplayP3, -}; - -const ColorManagement = { - - enabled: true, - - get legacyMode() { - - console.warn( 'THREE.ColorManagement: .legacyMode=false renamed to .enabled=true in r150.' ); - - return ! this.enabled; - - }, - - set legacyMode( legacyMode ) { - - console.warn( 'THREE.ColorManagement: .legacyMode=false renamed to .enabled=true in r150.' ); - - this.enabled = ! legacyMode; - - }, - - get workingColorSpace() { - - return LinearSRGBColorSpace; - - }, - - set workingColorSpace( colorSpace ) { - - console.warn( 'THREE.ColorManagement: .workingColorSpace is readonly.' ); - - }, - - convert: function ( color, sourceColorSpace, targetColorSpace ) { - - if ( this.enabled === false || sourceColorSpace === targetColorSpace || ! sourceColorSpace || ! targetColorSpace ) { - - return color; - - } - - const sourceToLinear = TO_LINEAR[ sourceColorSpace ]; - const targetFromLinear = FROM_LINEAR[ targetColorSpace ]; - - if ( sourceToLinear === undefined || targetFromLinear === undefined ) { - - throw new Error( `Unsupported color space conversion, "${ sourceColorSpace }" to "${ targetColorSpace }".` ); - - } - - return targetFromLinear( sourceToLinear( color ) ); - - }, - - fromWorkingColorSpace: function ( color, targetColorSpace ) { - - return this.convert( color, this.workingColorSpace, targetColorSpace ); - - }, - - toWorkingColorSpace: function ( color, sourceColorSpace ) { - - return this.convert( color, sourceColorSpace, this.workingColorSpace ); - - }, - -}; - -let _canvas; - -class ImageUtils { - - static getDataURL( image ) { - - if ( /^data:/i.test( image.src ) ) { - - return image.src; - - } - - if ( typeof HTMLCanvasElement === 'undefined' ) { - - return image.src; - - } - - let canvas; - - if ( image instanceof HTMLCanvasElement ) { - - canvas = image; - - } else { - - if ( _canvas === undefined ) _canvas = createElementNS( 'canvas' ); - - _canvas.width = image.width; - _canvas.height = image.height; - - const context = _canvas.getContext( '2d' ); - - if ( image instanceof ImageData ) { - - context.putImageData( image, 0, 0 ); - - } else { - - context.drawImage( image, 0, 0, image.width, image.height ); - - } - - canvas = _canvas; - - } - - if ( canvas.width > 2048 || canvas.height > 2048 ) { - - console.warn( 'THREE.ImageUtils.getDataURL: Image converted to jpg for performance reasons', image ); - - return canvas.toDataURL( 'image/jpeg', 0.6 ); - - } else { - - return canvas.toDataURL( 'image/png' ); - - } - - } - - static sRGBToLinear( image ) { - - if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || - ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) || - ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) { - - const canvas = createElementNS( 'canvas' ); - - canvas.width = image.width; - canvas.height = image.height; - - const context = canvas.getContext( '2d' ); - context.drawImage( image, 0, 0, image.width, image.height ); - - const imageData = context.getImageData( 0, 0, image.width, image.height ); - const data = imageData.data; - - for ( let i = 0; i < data.length; i ++ ) { - - data[ i ] = SRGBToLinear( data[ i ] / 255 ) * 255; - - } - - context.putImageData( imageData, 0, 0 ); - - return canvas; - - } else if ( image.data ) { - - const data = image.data.slice( 0 ); - - for ( let i = 0; i < data.length; i ++ ) { - - if ( data instanceof Uint8Array || data instanceof Uint8ClampedArray ) { - - data[ i ] = Math.floor( SRGBToLinear( data[ i ] / 255 ) * 255 ); - - } else { - - // assuming float - - data[ i ] = SRGBToLinear( data[ i ] ); - - } - - } - - return { - data: data, - width: image.width, - height: image.height - }; - - } else { - - console.warn( 'THREE.ImageUtils.sRGBToLinear(): Unsupported image type. No color space conversion applied.' ); - return image; - - } - - } - -} - -let sourceId = 0; - -class Source { - - constructor( data = null ) { - - this.isSource = true; - - Object.defineProperty( this, 'id', { value: sourceId ++ } ); - - this.uuid = generateUUID(); - - this.data = data; - - this.version = 0; - - } - - set needsUpdate( value ) { - - if ( value === true ) this.version ++; - - } - - toJSON( meta ) { - - const isRootObject = ( meta === undefined || typeof meta === 'string' ); - - if ( ! isRootObject && meta.images[ this.uuid ] !== undefined ) { - - return meta.images[ this.uuid ]; - - } - - const output = { - uuid: this.uuid, - url: '' - }; - - const data = this.data; - - if ( data !== null ) { - - let url; - - if ( Array.isArray( data ) ) { - - // cube texture - - url = []; - - for ( let i = 0, l = data.length; i < l; i ++ ) { - - if ( data[ i ].isDataTexture ) { - - url.push( serializeImage( data[ i ].image ) ); - - } else { - - url.push( serializeImage( data[ i ] ) ); - - } - - } - - } else { - - // texture - - url = serializeImage( data ); - - } - - output.url = url; - - } - - if ( ! isRootObject ) { - - meta.images[ this.uuid ] = output; - - } - - return output; - - } - -} - -function serializeImage( image ) { - - if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || - ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) || - ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) { - - // default images - - return ImageUtils.getDataURL( image ); - - } else { - - if ( image.data ) { - - // images of DataTexture - - return { - data: Array.from( image.data ), - width: image.width, - height: image.height, - type: image.data.constructor.name - }; - - } else { - - console.warn( 'THREE.Texture: Unable to serialize Texture.' ); - return {}; - - } - - } - -} - -let textureId = 0; - -class Texture extends EventDispatcher { - - constructor( image = Texture.DEFAULT_IMAGE, mapping = Texture.DEFAULT_MAPPING, wrapS = ClampToEdgeWrapping, wrapT = ClampToEdgeWrapping, magFilter = LinearFilter, minFilter = LinearMipmapLinearFilter, format = RGBAFormat, type = UnsignedByteType, anisotropy = Texture.DEFAULT_ANISOTROPY, colorSpace = NoColorSpace ) { - - super(); - - this.isTexture = true; - - Object.defineProperty( this, 'id', { value: textureId ++ } ); - - this.uuid = generateUUID(); - - this.name = ''; - - this.source = new Source( image ); - this.mipmaps = []; - - this.mapping = mapping; - this.channel = 0; - - this.wrapS = wrapS; - this.wrapT = wrapT; - - this.magFilter = magFilter; - this.minFilter = minFilter; - - this.anisotropy = anisotropy; - - this.format = format; - this.internalFormat = null; - this.type = type; - - this.offset = new Vector2( 0, 0 ); - this.repeat = new Vector2( 1, 1 ); - this.center = new Vector2( 0, 0 ); - this.rotation = 0; - - this.matrixAutoUpdate = true; - this.matrix = new Matrix3(); - - this.generateMipmaps = true; - this.premultiplyAlpha = false; - this.flipY = true; - this.unpackAlignment = 4; // valid values: 1, 2, 4, 8 (see http://www.khronos.org/opengles/sdk/docs/man/xhtml/glPixelStorei.xml) - - if ( typeof colorSpace === 'string' ) { - - this.colorSpace = colorSpace; - - } else { // @deprecated, r152 - - warnOnce( 'THREE.Texture: Property .encoding has been replaced by .colorSpace.' ); - this.colorSpace = colorSpace === sRGBEncoding ? SRGBColorSpace : NoColorSpace; - - } - - - this.userData = {}; - - this.version = 0; - this.onUpdate = null; - - this.isRenderTargetTexture = false; // indicates whether a texture belongs to a render target or not - this.needsPMREMUpdate = false; // indicates whether this texture should be processed by PMREMGenerator or not (only relevant for render target textures) - - } - - get image() { - - return this.source.data; - - } - - set image( value = null ) { - - this.source.data = value; - - } - - updateMatrix() { - - this.matrix.setUvTransform( this.offset.x, this.offset.y, this.repeat.x, this.repeat.y, this.rotation, this.center.x, this.center.y ); - - } - - clone() { - - return new this.constructor().copy( this ); - - } - - copy( source ) { - - this.name = source.name; - - this.source = source.source; - this.mipmaps = source.mipmaps.slice( 0 ); - - this.mapping = source.mapping; - this.channel = source.channel; - - this.wrapS = source.wrapS; - this.wrapT = source.wrapT; - - this.magFilter = source.magFilter; - this.minFilter = source.minFilter; - - this.anisotropy = source.anisotropy; - - this.format = source.format; - this.internalFormat = source.internalFormat; - this.type = source.type; - - this.offset.copy( source.offset ); - this.repeat.copy( source.repeat ); - this.center.copy( source.center ); - this.rotation = source.rotation; - - this.matrixAutoUpdate = source.matrixAutoUpdate; - this.matrix.copy( source.matrix ); - - this.generateMipmaps = source.generateMipmaps; - this.premultiplyAlpha = source.premultiplyAlpha; - this.flipY = source.flipY; - this.unpackAlignment = source.unpackAlignment; - this.colorSpace = source.colorSpace; - - this.userData = JSON.parse( JSON.stringify( source.userData ) ); - - this.needsUpdate = true; - - return this; - - } - - toJSON( meta ) { - - const isRootObject = ( meta === undefined || typeof meta === 'string' ); - - if ( ! isRootObject && meta.textures[ this.uuid ] !== undefined ) { - - return meta.textures[ this.uuid ]; - - } - - const output = { - - metadata: { - version: 4.6, - type: 'Texture', - generator: 'Texture.toJSON' - }, - - uuid: this.uuid, - name: this.name, - - image: this.source.toJSON( meta ).uuid, - - mapping: this.mapping, - channel: this.channel, - - repeat: [ this.repeat.x, this.repeat.y ], - offset: [ this.offset.x, this.offset.y ], - center: [ this.center.x, this.center.y ], - rotation: this.rotation, - - wrap: [ this.wrapS, this.wrapT ], - - format: this.format, - internalFormat: this.internalFormat, - type: this.type, - colorSpace: this.colorSpace, - - minFilter: this.minFilter, - magFilter: this.magFilter, - anisotropy: this.anisotropy, - - flipY: this.flipY, - - generateMipmaps: this.generateMipmaps, - premultiplyAlpha: this.premultiplyAlpha, - unpackAlignment: this.unpackAlignment - - }; - - if ( Object.keys( this.userData ).length > 0 ) output.userData = this.userData; - - if ( ! isRootObject ) { - - meta.textures[ this.uuid ] = output; - - } - - return output; - - } - - dispose() { - - this.dispatchEvent( { type: 'dispose' } ); - - } - - transformUv( uv ) { - - if ( this.mapping !== UVMapping ) return uv; - - uv.applyMatrix3( this.matrix ); - - if ( uv.x < 0 || uv.x > 1 ) { - - switch ( this.wrapS ) { - - case RepeatWrapping: - - uv.x = uv.x - Math.floor( uv.x ); - break; - - case ClampToEdgeWrapping: - - uv.x = uv.x < 0 ? 0 : 1; - break; - - case MirroredRepeatWrapping: - - if ( Math.abs( Math.floor( uv.x ) % 2 ) === 1 ) { - - uv.x = Math.ceil( uv.x ) - uv.x; - - } else { - - uv.x = uv.x - Math.floor( uv.x ); - - } - - break; - - } - - } - - if ( uv.y < 0 || uv.y > 1 ) { - - switch ( this.wrapT ) { - - case RepeatWrapping: - - uv.y = uv.y - Math.floor( uv.y ); - break; - - case ClampToEdgeWrapping: - - uv.y = uv.y < 0 ? 0 : 1; - break; - - case MirroredRepeatWrapping: - - if ( Math.abs( Math.floor( uv.y ) % 2 ) === 1 ) { - - uv.y = Math.ceil( uv.y ) - uv.y; - - } else { - - uv.y = uv.y - Math.floor( uv.y ); - - } - - break; - - } - - } - - if ( this.flipY ) { - - uv.y = 1 - uv.y; - - } - - return uv; - - } - - set needsUpdate( value ) { - - if ( value === true ) { - - this.version ++; - this.source.needsUpdate = true; - - } - - } - - get encoding() { // @deprecated, r152 - - warnOnce( 'THREE.Texture: Property .encoding has been replaced by .colorSpace.' ); - return this.colorSpace === SRGBColorSpace ? sRGBEncoding : LinearEncoding; - - } - - set encoding( encoding ) { // @deprecated, r152 - - warnOnce( 'THREE.Texture: Property .encoding has been replaced by .colorSpace.' ); - this.colorSpace = encoding === sRGBEncoding ? SRGBColorSpace : NoColorSpace; - - } - -} - -Texture.DEFAULT_IMAGE = null; -Texture.DEFAULT_MAPPING = UVMapping; -Texture.DEFAULT_ANISOTROPY = 1; - -class Vector4 { - - constructor( x = 0, y = 0, z = 0, w = 1 ) { - - Vector4.prototype.isVector4 = true; - - this.x = x; - this.y = y; - this.z = z; - this.w = w; - - } - - get width() { - - return this.z; - - } - - set width( value ) { - - this.z = value; - - } - - get height() { - - return this.w; - - } - - set height( value ) { - - this.w = value; - - } - - set( x, y, z, w ) { - - this.x = x; - this.y = y; - this.z = z; - this.w = w; - - return this; - - } - - setScalar( scalar ) { - - this.x = scalar; - this.y = scalar; - this.z = scalar; - this.w = scalar; - - return this; - - } - - setX( x ) { - - this.x = x; - - return this; - - } - - setY( y ) { - - this.y = y; - - return this; - - } - - setZ( z ) { - - this.z = z; - - return this; - - } - - setW( w ) { - - this.w = w; - - return this; - - } - - setComponent( index, value ) { - - switch ( index ) { - - case 0: this.x = value; break; - case 1: this.y = value; break; - case 2: this.z = value; break; - case 3: this.w = value; break; - default: throw new Error( 'index is out of range: ' + index ); - - } - - return this; - - } - - getComponent( index ) { - - switch ( index ) { - - case 0: return this.x; - case 1: return this.y; - case 2: return this.z; - case 3: return this.w; - default: throw new Error( 'index is out of range: ' + index ); - - } - - } - - clone() { - - return new this.constructor( this.x, this.y, this.z, this.w ); - - } - - copy( v ) { - - this.x = v.x; - this.y = v.y; - this.z = v.z; - this.w = ( v.w !== undefined ) ? v.w : 1; - - return this; - - } - - add( v ) { - - this.x += v.x; - this.y += v.y; - this.z += v.z; - this.w += v.w; - - return this; - - } - - addScalar( s ) { - - this.x += s; - this.y += s; - this.z += s; - this.w += s; - - return this; - - } - - addVectors( a, b ) { - - this.x = a.x + b.x; - this.y = a.y + b.y; - this.z = a.z + b.z; - this.w = a.w + b.w; - - return this; - - } - - addScaledVector( v, s ) { - - this.x += v.x * s; - this.y += v.y * s; - this.z += v.z * s; - this.w += v.w * s; - - return this; - - } - - sub( v ) { - - this.x -= v.x; - this.y -= v.y; - this.z -= v.z; - this.w -= v.w; - - return this; - - } - - subScalar( s ) { - - this.x -= s; - this.y -= s; - this.z -= s; - this.w -= s; - - return this; - - } - - subVectors( a, b ) { - - this.x = a.x - b.x; - this.y = a.y - b.y; - this.z = a.z - b.z; - this.w = a.w - b.w; - - return this; - - } - - multiply( v ) { - - this.x *= v.x; - this.y *= v.y; - this.z *= v.z; - this.w *= v.w; - - return this; - - } - - multiplyScalar( scalar ) { - - this.x *= scalar; - this.y *= scalar; - this.z *= scalar; - this.w *= scalar; - - return this; - - } - - applyMatrix4( m ) { - - const x = this.x, y = this.y, z = this.z, w = this.w; - const e = m.elements; - - this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] * w; - this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] * w; - this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] * w; - this.w = e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] * w; - - return this; - - } - - divideScalar( scalar ) { - - return this.multiplyScalar( 1 / scalar ); - - } - - setAxisAngleFromQuaternion( q ) { - - // http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToAngle/index.htm - - // q is assumed to be normalized - - this.w = 2 * Math.acos( q.w ); - - const s = Math.sqrt( 1 - q.w * q.w ); - - if ( s < 0.0001 ) { - - this.x = 1; - this.y = 0; - this.z = 0; - - } else { - - this.x = q.x / s; - this.y = q.y / s; - this.z = q.z / s; - - } - - return this; - - } - - setAxisAngleFromRotationMatrix( m ) { - - // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToAngle/index.htm - - // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) - - let angle, x, y, z; // variables for result - const epsilon = 0.01, // margin to allow for rounding errors - epsilon2 = 0.1, // margin to distinguish between 0 and 180 degrees - - te = m.elements, - - m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ], - m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ], - m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ]; - - if ( ( Math.abs( m12 - m21 ) < epsilon ) && - ( Math.abs( m13 - m31 ) < epsilon ) && - ( Math.abs( m23 - m32 ) < epsilon ) ) { - - // singularity found - // first check for identity matrix which must have +1 for all terms - // in leading diagonal and zero in other terms - - if ( ( Math.abs( m12 + m21 ) < epsilon2 ) && - ( Math.abs( m13 + m31 ) < epsilon2 ) && - ( Math.abs( m23 + m32 ) < epsilon2 ) && - ( Math.abs( m11 + m22 + m33 - 3 ) < epsilon2 ) ) { - - // this singularity is identity matrix so angle = 0 - - this.set( 1, 0, 0, 0 ); - - return this; // zero angle, arbitrary axis - - } - - // otherwise this singularity is angle = 180 - - angle = Math.PI; - - const xx = ( m11 + 1 ) / 2; - const yy = ( m22 + 1 ) / 2; - const zz = ( m33 + 1 ) / 2; - const xy = ( m12 + m21 ) / 4; - const xz = ( m13 + m31 ) / 4; - const yz = ( m23 + m32 ) / 4; - - if ( ( xx > yy ) && ( xx > zz ) ) { - - // m11 is the largest diagonal term - - if ( xx < epsilon ) { - - x = 0; - y = 0.707106781; - z = 0.707106781; - - } else { - - x = Math.sqrt( xx ); - y = xy / x; - z = xz / x; - - } - - } else if ( yy > zz ) { - - // m22 is the largest diagonal term - - if ( yy < epsilon ) { - - x = 0.707106781; - y = 0; - z = 0.707106781; - - } else { - - y = Math.sqrt( yy ); - x = xy / y; - z = yz / y; - - } - - } else { - - // m33 is the largest diagonal term so base result on this - - if ( zz < epsilon ) { - - x = 0.707106781; - y = 0.707106781; - z = 0; - - } else { - - z = Math.sqrt( zz ); - x = xz / z; - y = yz / z; - - } - - } - - this.set( x, y, z, angle ); - - return this; // return 180 deg rotation - - } - - // as we have reached here there are no singularities so we can handle normally - - let s = Math.sqrt( ( m32 - m23 ) * ( m32 - m23 ) + - ( m13 - m31 ) * ( m13 - m31 ) + - ( m21 - m12 ) * ( m21 - m12 ) ); // used to normalize - - if ( Math.abs( s ) < 0.001 ) s = 1; - - // prevent divide by zero, should not happen if matrix is orthogonal and should be - // caught by singularity test above, but I've left it in just in case - - this.x = ( m32 - m23 ) / s; - this.y = ( m13 - m31 ) / s; - this.z = ( m21 - m12 ) / s; - this.w = Math.acos( ( m11 + m22 + m33 - 1 ) / 2 ); - - return this; - - } - - min( v ) { - - this.x = Math.min( this.x, v.x ); - this.y = Math.min( this.y, v.y ); - this.z = Math.min( this.z, v.z ); - this.w = Math.min( this.w, v.w ); - - return this; - - } - - max( v ) { - - this.x = Math.max( this.x, v.x ); - this.y = Math.max( this.y, v.y ); - this.z = Math.max( this.z, v.z ); - this.w = Math.max( this.w, v.w ); - - return this; - - } - - clamp( min, max ) { - - // assumes min < max, componentwise - - this.x = Math.max( min.x, Math.min( max.x, this.x ) ); - this.y = Math.max( min.y, Math.min( max.y, this.y ) ); - this.z = Math.max( min.z, Math.min( max.z, this.z ) ); - this.w = Math.max( min.w, Math.min( max.w, this.w ) ); - - return this; - - } - - clampScalar( minVal, maxVal ) { - - this.x = Math.max( minVal, Math.min( maxVal, this.x ) ); - this.y = Math.max( minVal, Math.min( maxVal, this.y ) ); - this.z = Math.max( minVal, Math.min( maxVal, this.z ) ); - this.w = Math.max( minVal, Math.min( maxVal, this.w ) ); - - return this; - - } - - clampLength( min, max ) { - - const length = this.length(); - - return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) ); - - } - - floor() { - - this.x = Math.floor( this.x ); - this.y = Math.floor( this.y ); - this.z = Math.floor( this.z ); - this.w = Math.floor( this.w ); - - return this; - - } - - ceil() { - - this.x = Math.ceil( this.x ); - this.y = Math.ceil( this.y ); - this.z = Math.ceil( this.z ); - this.w = Math.ceil( this.w ); - - return this; - - } - - round() { - - this.x = Math.round( this.x ); - this.y = Math.round( this.y ); - this.z = Math.round( this.z ); - this.w = Math.round( this.w ); - - return this; - - } - - roundToZero() { - - this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x ); - this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y ); - this.z = ( this.z < 0 ) ? Math.ceil( this.z ) : Math.floor( this.z ); - this.w = ( this.w < 0 ) ? Math.ceil( this.w ) : Math.floor( this.w ); - - return this; - - } - - negate() { - - this.x = - this.x; - this.y = - this.y; - this.z = - this.z; - this.w = - this.w; - - return this; - - } - - dot( v ) { - - return this.x * v.x + this.y * v.y + this.z * v.z + this.w * v.w; - - } - - lengthSq() { - - return this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w; - - } - - length() { - - return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w ); - - } - - manhattanLength() { - - return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ) + Math.abs( this.w ); - - } - - normalize() { - - return this.divideScalar( this.length() || 1 ); - - } - - setLength( length ) { - - return this.normalize().multiplyScalar( length ); - - } - - lerp( v, alpha ) { - - this.x += ( v.x - this.x ) * alpha; - this.y += ( v.y - this.y ) * alpha; - this.z += ( v.z - this.z ) * alpha; - this.w += ( v.w - this.w ) * alpha; - - return this; - - } - - lerpVectors( v1, v2, alpha ) { - - this.x = v1.x + ( v2.x - v1.x ) * alpha; - this.y = v1.y + ( v2.y - v1.y ) * alpha; - this.z = v1.z + ( v2.z - v1.z ) * alpha; - this.w = v1.w + ( v2.w - v1.w ) * alpha; - - return this; - - } - - equals( v ) { - - return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) && ( v.w === this.w ) ); - - } - - fromArray( array, offset = 0 ) { - - this.x = array[ offset ]; - this.y = array[ offset + 1 ]; - this.z = array[ offset + 2 ]; - this.w = array[ offset + 3 ]; - - return this; - - } - - toArray( array = [], offset = 0 ) { - - array[ offset ] = this.x; - array[ offset + 1 ] = this.y; - array[ offset + 2 ] = this.z; - array[ offset + 3 ] = this.w; - - return array; - - } - - fromBufferAttribute( attribute, index ) { - - this.x = attribute.getX( index ); - this.y = attribute.getY( index ); - this.z = attribute.getZ( index ); - this.w = attribute.getW( index ); - - return this; - - } - - random() { - - this.x = Math.random(); - this.y = Math.random(); - this.z = Math.random(); - this.w = Math.random(); - - return this; - - } - - *[ Symbol.iterator ]() { - - yield this.x; - yield this.y; - yield this.z; - yield this.w; - - } - -} - -/* - In options, we can specify: - * Texture parameters for an auto-generated target texture - * depthBuffer/stencilBuffer: Booleans to indicate if we should generate these buffers -*/ -class WebGLRenderTarget extends EventDispatcher { - - constructor( width = 1, height = 1, options = {} ) { - - super(); - - this.isWebGLRenderTarget = true; - - this.width = width; - this.height = height; - this.depth = 1; - - this.scissor = new Vector4( 0, 0, width, height ); - this.scissorTest = false; - - this.viewport = new Vector4( 0, 0, width, height ); - - const image = { width: width, height: height, depth: 1 }; - - if ( options.encoding !== undefined ) { - - // @deprecated, r152 - warnOnce( 'THREE.WebGLRenderTarget: option.encoding has been replaced by option.colorSpace.' ); - options.colorSpace = options.encoding === sRGBEncoding ? SRGBColorSpace : NoColorSpace; - - } - - this.texture = new Texture( image, options.mapping, options.wrapS, options.wrapT, options.magFilter, options.minFilter, options.format, options.type, options.anisotropy, options.colorSpace ); - this.texture.isRenderTargetTexture = true; - - this.texture.flipY = false; - this.texture.generateMipmaps = options.generateMipmaps !== undefined ? options.generateMipmaps : false; - this.texture.internalFormat = options.internalFormat !== undefined ? options.internalFormat : null; - this.texture.minFilter = options.minFilter !== undefined ? options.minFilter : LinearFilter; - - this.depthBuffer = options.depthBuffer !== undefined ? options.depthBuffer : true; - this.stencilBuffer = options.stencilBuffer !== undefined ? options.stencilBuffer : false; - - this.depthTexture = options.depthTexture !== undefined ? options.depthTexture : null; - - this.samples = options.samples !== undefined ? options.samples : 0; - - } - - setSize( width, height, depth = 1 ) { - - if ( this.width !== width || this.height !== height || this.depth !== depth ) { - - this.width = width; - this.height = height; - this.depth = depth; - - this.texture.image.width = width; - this.texture.image.height = height; - this.texture.image.depth = depth; - - this.dispose(); - - } - - this.viewport.set( 0, 0, width, height ); - this.scissor.set( 0, 0, width, height ); - - } - - clone() { - - return new this.constructor().copy( this ); - - } - - copy( source ) { - - this.width = source.width; - this.height = source.height; - this.depth = source.depth; - - this.scissor.copy( source.scissor ); - this.scissorTest = source.scissorTest; - - this.viewport.copy( source.viewport ); - - this.texture = source.texture.clone(); - this.texture.isRenderTargetTexture = true; - - // ensure image object is not shared, see #20328 - - const image = Object.assign( {}, source.texture.image ); - this.texture.source = new Source( image ); - - this.depthBuffer = source.depthBuffer; - this.stencilBuffer = source.stencilBuffer; - - if ( source.depthTexture !== null ) this.depthTexture = source.depthTexture.clone(); - - this.samples = source.samples; - - return this; - - } - - dispose() { - - this.dispatchEvent( { type: 'dispose' } ); - - } - -} - -class DataArrayTexture extends Texture { - - constructor( data = null, width = 1, height = 1, depth = 1 ) { - - super( null ); - - this.isDataArrayTexture = true; - - this.image = { data, width, height, depth }; - - this.magFilter = NearestFilter; - this.minFilter = NearestFilter; - - this.wrapR = ClampToEdgeWrapping; - - this.generateMipmaps = false; - this.flipY = false; - this.unpackAlignment = 1; - - } - -} - -class WebGLArrayRenderTarget extends WebGLRenderTarget { - - constructor( width = 1, height = 1, depth = 1 ) { - - super( width, height ); - - this.isWebGLArrayRenderTarget = true; - - this.depth = depth; - - this.texture = new DataArrayTexture( null, width, height, depth ); - - this.texture.isRenderTargetTexture = true; - - } - -} - -class Data3DTexture extends Texture { - - constructor( data = null, width = 1, height = 1, depth = 1 ) { - - // We're going to add .setXXX() methods for setting properties later. - // Users can still set in DataTexture3D directly. - // - // const texture = new THREE.DataTexture3D( data, width, height, depth ); - // texture.anisotropy = 16; - // - // See #14839 - - super( null ); - - this.isData3DTexture = true; - - this.image = { data, width, height, depth }; - - this.magFilter = NearestFilter; - this.minFilter = NearestFilter; - - this.wrapR = ClampToEdgeWrapping; - - this.generateMipmaps = false; - this.flipY = false; - this.unpackAlignment = 1; - - } - -} - -class WebGL3DRenderTarget extends WebGLRenderTarget { - - constructor( width = 1, height = 1, depth = 1 ) { - - super( width, height ); - - this.isWebGL3DRenderTarget = true; - - this.depth = depth; - - this.texture = new Data3DTexture( null, width, height, depth ); - - this.texture.isRenderTargetTexture = true; - - } - -} - -class WebGLMultipleRenderTargets extends WebGLRenderTarget { - - constructor( width = 1, height = 1, count = 1, options = {} ) { - - super( width, height, options ); - - this.isWebGLMultipleRenderTargets = true; - - const texture = this.texture; - - this.texture = []; - - for ( let i = 0; i < count; i ++ ) { - - this.texture[ i ] = texture.clone(); - this.texture[ i ].isRenderTargetTexture = true; - - } - - } - - setSize( width, height, depth = 1 ) { - - if ( this.width !== width || this.height !== height || this.depth !== depth ) { - - this.width = width; - this.height = height; - this.depth = depth; - - for ( let i = 0, il = this.texture.length; i < il; i ++ ) { - - this.texture[ i ].image.width = width; - this.texture[ i ].image.height = height; - this.texture[ i ].image.depth = depth; - - } - - this.dispose(); - - } - - this.viewport.set( 0, 0, width, height ); - this.scissor.set( 0, 0, width, height ); - - return this; - - } - - copy( source ) { - - this.dispose(); - - this.width = source.width; - this.height = source.height; - this.depth = source.depth; - - this.scissor.copy( source.scissor ); - this.scissorTest = source.scissorTest; - - this.viewport.copy( source.viewport ); - - this.depthBuffer = source.depthBuffer; - this.stencilBuffer = source.stencilBuffer; - - if ( source.depthTexture !== null ) this.depthTexture = source.depthTexture.clone(); - - this.texture.length = 0; - - for ( let i = 0, il = source.texture.length; i < il; i ++ ) { - - this.texture[ i ] = source.texture[ i ].clone(); - this.texture[ i ].isRenderTargetTexture = true; - - } - - return this; - - } - -} - -class Quaternion { - - constructor( x = 0, y = 0, z = 0, w = 1 ) { - - this.isQuaternion = true; - - this._x = x; - this._y = y; - this._z = z; - this._w = w; - - } - - static slerpFlat( dst, dstOffset, src0, srcOffset0, src1, srcOffset1, t ) { - - // fuzz-free, array-based Quaternion SLERP operation - - let x0 = src0[ srcOffset0 + 0 ], - y0 = src0[ srcOffset0 + 1 ], - z0 = src0[ srcOffset0 + 2 ], - w0 = src0[ srcOffset0 + 3 ]; - - const x1 = src1[ srcOffset1 + 0 ], - y1 = src1[ srcOffset1 + 1 ], - z1 = src1[ srcOffset1 + 2 ], - w1 = src1[ srcOffset1 + 3 ]; - - if ( t === 0 ) { - - dst[ dstOffset + 0 ] = x0; - dst[ dstOffset + 1 ] = y0; - dst[ dstOffset + 2 ] = z0; - dst[ dstOffset + 3 ] = w0; - return; - - } - - if ( t === 1 ) { - - dst[ dstOffset + 0 ] = x1; - dst[ dstOffset + 1 ] = y1; - dst[ dstOffset + 2 ] = z1; - dst[ dstOffset + 3 ] = w1; - return; - - } - - if ( w0 !== w1 || x0 !== x1 || y0 !== y1 || z0 !== z1 ) { - - let s = 1 - t; - const cos = x0 * x1 + y0 * y1 + z0 * z1 + w0 * w1, - dir = ( cos >= 0 ? 1 : - 1 ), - sqrSin = 1 - cos * cos; - - // Skip the Slerp for tiny steps to avoid numeric problems: - if ( sqrSin > Number.EPSILON ) { - - const sin = Math.sqrt( sqrSin ), - len = Math.atan2( sin, cos * dir ); - - s = Math.sin( s * len ) / sin; - t = Math.sin( t * len ) / sin; - - } - - const tDir = t * dir; - - x0 = x0 * s + x1 * tDir; - y0 = y0 * s + y1 * tDir; - z0 = z0 * s + z1 * tDir; - w0 = w0 * s + w1 * tDir; - - // Normalize in case we just did a lerp: - if ( s === 1 - t ) { - - const f = 1 / Math.sqrt( x0 * x0 + y0 * y0 + z0 * z0 + w0 * w0 ); - - x0 *= f; - y0 *= f; - z0 *= f; - w0 *= f; - - } - - } - - dst[ dstOffset ] = x0; - dst[ dstOffset + 1 ] = y0; - dst[ dstOffset + 2 ] = z0; - dst[ dstOffset + 3 ] = w0; - - } - - static multiplyQuaternionsFlat( dst, dstOffset, src0, srcOffset0, src1, srcOffset1 ) { - - const x0 = src0[ srcOffset0 ]; - const y0 = src0[ srcOffset0 + 1 ]; - const z0 = src0[ srcOffset0 + 2 ]; - const w0 = src0[ srcOffset0 + 3 ]; - - const x1 = src1[ srcOffset1 ]; - const y1 = src1[ srcOffset1 + 1 ]; - const z1 = src1[ srcOffset1 + 2 ]; - const w1 = src1[ srcOffset1 + 3 ]; - - dst[ dstOffset ] = x0 * w1 + w0 * x1 + y0 * z1 - z0 * y1; - dst[ dstOffset + 1 ] = y0 * w1 + w0 * y1 + z0 * x1 - x0 * z1; - dst[ dstOffset + 2 ] = z0 * w1 + w0 * z1 + x0 * y1 - y0 * x1; - dst[ dstOffset + 3 ] = w0 * w1 - x0 * x1 - y0 * y1 - z0 * z1; - - return dst; - - } - - get x() { - - return this._x; - - } - - set x( value ) { - - this._x = value; - this._onChangeCallback(); - - } - - get y() { - - return this._y; - - } - - set y( value ) { - - this._y = value; - this._onChangeCallback(); - - } - - get z() { - - return this._z; - - } - - set z( value ) { - - this._z = value; - this._onChangeCallback(); - - } - - get w() { - - return this._w; - - } - - set w( value ) { - - this._w = value; - this._onChangeCallback(); - - } - - set( x, y, z, w ) { - - this._x = x; - this._y = y; - this._z = z; - this._w = w; - - this._onChangeCallback(); - - return this; - - } - - clone() { - - return new this.constructor( this._x, this._y, this._z, this._w ); - - } - - copy( quaternion ) { - - this._x = quaternion.x; - this._y = quaternion.y; - this._z = quaternion.z; - this._w = quaternion.w; - - this._onChangeCallback(); - - return this; - - } - - setFromEuler( euler, update ) { - - const x = euler._x, y = euler._y, z = euler._z, order = euler._order; - - // http://www.mathworks.com/matlabcentral/fileexchange/ - // 20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/ - // content/SpinCalc.m - - const cos = Math.cos; - const sin = Math.sin; - - const c1 = cos( x / 2 ); - const c2 = cos( y / 2 ); - const c3 = cos( z / 2 ); - - const s1 = sin( x / 2 ); - const s2 = sin( y / 2 ); - const s3 = sin( z / 2 ); - - switch ( order ) { - - case 'XYZ': - this._x = s1 * c2 * c3 + c1 * s2 * s3; - this._y = c1 * s2 * c3 - s1 * c2 * s3; - this._z = c1 * c2 * s3 + s1 * s2 * c3; - this._w = c1 * c2 * c3 - s1 * s2 * s3; - break; - - case 'YXZ': - this._x = s1 * c2 * c3 + c1 * s2 * s3; - this._y = c1 * s2 * c3 - s1 * c2 * s3; - this._z = c1 * c2 * s3 - s1 * s2 * c3; - this._w = c1 * c2 * c3 + s1 * s2 * s3; - break; - - case 'ZXY': - this._x = s1 * c2 * c3 - c1 * s2 * s3; - this._y = c1 * s2 * c3 + s1 * c2 * s3; - this._z = c1 * c2 * s3 + s1 * s2 * c3; - this._w = c1 * c2 * c3 - s1 * s2 * s3; - break; - - case 'ZYX': - this._x = s1 * c2 * c3 - c1 * s2 * s3; - this._y = c1 * s2 * c3 + s1 * c2 * s3; - this._z = c1 * c2 * s3 - s1 * s2 * c3; - this._w = c1 * c2 * c3 + s1 * s2 * s3; - break; - - case 'YZX': - this._x = s1 * c2 * c3 + c1 * s2 * s3; - this._y = c1 * s2 * c3 + s1 * c2 * s3; - this._z = c1 * c2 * s3 - s1 * s2 * c3; - this._w = c1 * c2 * c3 - s1 * s2 * s3; - break; - - case 'XZY': - this._x = s1 * c2 * c3 - c1 * s2 * s3; - this._y = c1 * s2 * c3 - s1 * c2 * s3; - this._z = c1 * c2 * s3 + s1 * s2 * c3; - this._w = c1 * c2 * c3 + s1 * s2 * s3; - break; - - default: - console.warn( 'THREE.Quaternion: .setFromEuler() encountered an unknown order: ' + order ); - - } - - if ( update !== false ) this._onChangeCallback(); - - return this; - - } - - setFromAxisAngle( axis, angle ) { - - // http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm - - // assumes axis is normalized - - const halfAngle = angle / 2, s = Math.sin( halfAngle ); - - this._x = axis.x * s; - this._y = axis.y * s; - this._z = axis.z * s; - this._w = Math.cos( halfAngle ); - - this._onChangeCallback(); - - return this; - - } - - setFromRotationMatrix( m ) { - - // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm - - // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) - - const te = m.elements, - - m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ], - m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ], - m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ], - - trace = m11 + m22 + m33; - - if ( trace > 0 ) { - - const s = 0.5 / Math.sqrt( trace + 1.0 ); - - this._w = 0.25 / s; - this._x = ( m32 - m23 ) * s; - this._y = ( m13 - m31 ) * s; - this._z = ( m21 - m12 ) * s; - - } else if ( m11 > m22 && m11 > m33 ) { - - const s = 2.0 * Math.sqrt( 1.0 + m11 - m22 - m33 ); - - this._w = ( m32 - m23 ) / s; - this._x = 0.25 * s; - this._y = ( m12 + m21 ) / s; - this._z = ( m13 + m31 ) / s; - - } else if ( m22 > m33 ) { - - const s = 2.0 * Math.sqrt( 1.0 + m22 - m11 - m33 ); - - this._w = ( m13 - m31 ) / s; - this._x = ( m12 + m21 ) / s; - this._y = 0.25 * s; - this._z = ( m23 + m32 ) / s; - - } else { - - const s = 2.0 * Math.sqrt( 1.0 + m33 - m11 - m22 ); - - this._w = ( m21 - m12 ) / s; - this._x = ( m13 + m31 ) / s; - this._y = ( m23 + m32 ) / s; - this._z = 0.25 * s; - - } - - this._onChangeCallback(); - - return this; - - } - - setFromUnitVectors( vFrom, vTo ) { - - // assumes direction vectors vFrom and vTo are normalized - - let r = vFrom.dot( vTo ) + 1; - - if ( r < Number.EPSILON ) { - - // vFrom and vTo point in opposite directions - - r = 0; - - if ( Math.abs( vFrom.x ) > Math.abs( vFrom.z ) ) { - - this._x = - vFrom.y; - this._y = vFrom.x; - this._z = 0; - this._w = r; - - } else { - - this._x = 0; - this._y = - vFrom.z; - this._z = vFrom.y; - this._w = r; - - } - - } else { - - // crossVectors( vFrom, vTo ); // inlined to avoid cyclic dependency on Vector3 - - this._x = vFrom.y * vTo.z - vFrom.z * vTo.y; - this._y = vFrom.z * vTo.x - vFrom.x * vTo.z; - this._z = vFrom.x * vTo.y - vFrom.y * vTo.x; - this._w = r; - - } - - return this.normalize(); - - } - - angleTo( q ) { - - return 2 * Math.acos( Math.abs( clamp( this.dot( q ), - 1, 1 ) ) ); - - } - - rotateTowards( q, step ) { - - const angle = this.angleTo( q ); - - if ( angle === 0 ) return this; - - const t = Math.min( 1, step / angle ); - - this.slerp( q, t ); - - return this; - - } - - identity() { - - return this.set( 0, 0, 0, 1 ); - - } - - invert() { - - // quaternion is assumed to have unit length - - return this.conjugate(); - - } - - conjugate() { - - this._x *= - 1; - this._y *= - 1; - this._z *= - 1; - - this._onChangeCallback(); - - return this; - - } - - dot( v ) { - - return this._x * v._x + this._y * v._y + this._z * v._z + this._w * v._w; - - } - - lengthSq() { - - return this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w; - - } - - length() { - - return Math.sqrt( this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w ); - - } - - normalize() { - - let l = this.length(); - - if ( l === 0 ) { - - this._x = 0; - this._y = 0; - this._z = 0; - this._w = 1; - - } else { - - l = 1 / l; - - this._x = this._x * l; - this._y = this._y * l; - this._z = this._z * l; - this._w = this._w * l; - - } - - this._onChangeCallback(); - - return this; - - } - - multiply( q ) { - - return this.multiplyQuaternions( this, q ); - - } - - premultiply( q ) { - - return this.multiplyQuaternions( q, this ); - - } - - multiplyQuaternions( a, b ) { - - // from http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/code/index.htm - - const qax = a._x, qay = a._y, qaz = a._z, qaw = a._w; - const qbx = b._x, qby = b._y, qbz = b._z, qbw = b._w; - - this._x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby; - this._y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz; - this._z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx; - this._w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz; - - this._onChangeCallback(); - - return this; - - } - - slerp( qb, t ) { - - if ( t === 0 ) return this; - if ( t === 1 ) return this.copy( qb ); - - const x = this._x, y = this._y, z = this._z, w = this._w; - - // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/ - - let cosHalfTheta = w * qb._w + x * qb._x + y * qb._y + z * qb._z; - - if ( cosHalfTheta < 0 ) { - - this._w = - qb._w; - this._x = - qb._x; - this._y = - qb._y; - this._z = - qb._z; - - cosHalfTheta = - cosHalfTheta; - - } else { - - this.copy( qb ); - - } - - if ( cosHalfTheta >= 1.0 ) { - - this._w = w; - this._x = x; - this._y = y; - this._z = z; - - return this; - - } - - const sqrSinHalfTheta = 1.0 - cosHalfTheta * cosHalfTheta; - - if ( sqrSinHalfTheta <= Number.EPSILON ) { - - const s = 1 - t; - this._w = s * w + t * this._w; - this._x = s * x + t * this._x; - this._y = s * y + t * this._y; - this._z = s * z + t * this._z; - - this.normalize(); - this._onChangeCallback(); - - return this; - - } - - const sinHalfTheta = Math.sqrt( sqrSinHalfTheta ); - const halfTheta = Math.atan2( sinHalfTheta, cosHalfTheta ); - const ratioA = Math.sin( ( 1 - t ) * halfTheta ) / sinHalfTheta, - ratioB = Math.sin( t * halfTheta ) / sinHalfTheta; - - this._w = ( w * ratioA + this._w * ratioB ); - this._x = ( x * ratioA + this._x * ratioB ); - this._y = ( y * ratioA + this._y * ratioB ); - this._z = ( z * ratioA + this._z * ratioB ); - - this._onChangeCallback(); - - return this; - - } - - slerpQuaternions( qa, qb, t ) { - - return this.copy( qa ).slerp( qb, t ); - - } - - random() { - - // Derived from http://planning.cs.uiuc.edu/node198.html - // Note, this source uses w, x, y, z ordering, - // so we swap the order below. - - const u1 = Math.random(); - const sqrt1u1 = Math.sqrt( 1 - u1 ); - const sqrtu1 = Math.sqrt( u1 ); - - const u2 = 2 * Math.PI * Math.random(); - - const u3 = 2 * Math.PI * Math.random(); - - return this.set( - sqrt1u1 * Math.cos( u2 ), - sqrtu1 * Math.sin( u3 ), - sqrtu1 * Math.cos( u3 ), - sqrt1u1 * Math.sin( u2 ), - ); - - } - - equals( quaternion ) { - - return ( quaternion._x === this._x ) && ( quaternion._y === this._y ) && ( quaternion._z === this._z ) && ( quaternion._w === this._w ); - - } - - fromArray( array, offset = 0 ) { - - this._x = array[ offset ]; - this._y = array[ offset + 1 ]; - this._z = array[ offset + 2 ]; - this._w = array[ offset + 3 ]; - - this._onChangeCallback(); - - return this; - - } - - toArray( array = [], offset = 0 ) { - - array[ offset ] = this._x; - array[ offset + 1 ] = this._y; - array[ offset + 2 ] = this._z; - array[ offset + 3 ] = this._w; - - return array; - - } - - fromBufferAttribute( attribute, index ) { - - this._x = attribute.getX( index ); - this._y = attribute.getY( index ); - this._z = attribute.getZ( index ); - this._w = attribute.getW( index ); - - return this; - - } - - toJSON() { - - return this.toArray(); - - } - - _onChange( callback ) { - - this._onChangeCallback = callback; - - return this; - - } - - _onChangeCallback() {} - - *[ Symbol.iterator ]() { - - yield this._x; - yield this._y; - yield this._z; - yield this._w; - - } - -} - -class Vector3 { - - constructor( x = 0, y = 0, z = 0 ) { - - Vector3.prototype.isVector3 = true; - - this.x = x; - this.y = y; - this.z = z; - - } - - set( x, y, z ) { - - if ( z === undefined ) z = this.z; // sprite.scale.set(x,y) - - this.x = x; - this.y = y; - this.z = z; - - return this; - - } - - setScalar( scalar ) { - - this.x = scalar; - this.y = scalar; - this.z = scalar; - - return this; - - } - - setX( x ) { - - this.x = x; - - return this; - - } - - setY( y ) { - - this.y = y; - - return this; - - } - - setZ( z ) { - - this.z = z; - - return this; - - } - - setComponent( index, value ) { - - switch ( index ) { - - case 0: this.x = value; break; - case 1: this.y = value; break; - case 2: this.z = value; break; - default: throw new Error( 'index is out of range: ' + index ); - - } - - return this; - - } - - getComponent( index ) { - - switch ( index ) { - - case 0: return this.x; - case 1: return this.y; - case 2: return this.z; - default: throw new Error( 'index is out of range: ' + index ); - - } - - } - - clone() { - - return new this.constructor( this.x, this.y, this.z ); - - } - - copy( v ) { - - this.x = v.x; - this.y = v.y; - this.z = v.z; - - return this; - - } - - add( v ) { - - this.x += v.x; - this.y += v.y; - this.z += v.z; - - return this; - - } - - addScalar( s ) { - - this.x += s; - this.y += s; - this.z += s; - - return this; - - } - - addVectors( a, b ) { - - this.x = a.x + b.x; - this.y = a.y + b.y; - this.z = a.z + b.z; - - return this; - - } - - addScaledVector( v, s ) { - - this.x += v.x * s; - this.y += v.y * s; - this.z += v.z * s; - - return this; - - } - - sub( v ) { - - this.x -= v.x; - this.y -= v.y; - this.z -= v.z; - - return this; - - } - - subScalar( s ) { - - this.x -= s; - this.y -= s; - this.z -= s; - - return this; - - } - - subVectors( a, b ) { - - this.x = a.x - b.x; - this.y = a.y - b.y; - this.z = a.z - b.z; - - return this; - - } - - multiply( v ) { - - this.x *= v.x; - this.y *= v.y; - this.z *= v.z; - - return this; - - } - - multiplyScalar( scalar ) { - - this.x *= scalar; - this.y *= scalar; - this.z *= scalar; - - return this; - - } - - multiplyVectors( a, b ) { - - this.x = a.x * b.x; - this.y = a.y * b.y; - this.z = a.z * b.z; - - return this; - - } - - applyEuler( euler ) { - - return this.applyQuaternion( _quaternion$4.setFromEuler( euler ) ); - - } - - applyAxisAngle( axis, angle ) { - - return this.applyQuaternion( _quaternion$4.setFromAxisAngle( axis, angle ) ); - - } - - applyMatrix3( m ) { - - const x = this.x, y = this.y, z = this.z; - const e = m.elements; - - this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ] * z; - this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ] * z; - this.z = e[ 2 ] * x + e[ 5 ] * y + e[ 8 ] * z; - - return this; - - } - - applyNormalMatrix( m ) { - - return this.applyMatrix3( m ).normalize(); - - } - - applyMatrix4( m ) { - - const x = this.x, y = this.y, z = this.z; - const e = m.elements; - - const w = 1 / ( e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] ); - - this.x = ( e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] ) * w; - this.y = ( e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] ) * w; - this.z = ( e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] ) * w; - - return this; - - } - - applyQuaternion( q ) { - - const x = this.x, y = this.y, z = this.z; - const qx = q.x, qy = q.y, qz = q.z, qw = q.w; - - // calculate quat * vector - - const ix = qw * x + qy * z - qz * y; - const iy = qw * y + qz * x - qx * z; - const iz = qw * z + qx * y - qy * x; - const iw = - qx * x - qy * y - qz * z; - - // calculate result * inverse quat - - this.x = ix * qw + iw * - qx + iy * - qz - iz * - qy; - this.y = iy * qw + iw * - qy + iz * - qx - ix * - qz; - this.z = iz * qw + iw * - qz + ix * - qy - iy * - qx; - - return this; - - } - - project( camera ) { - - return this.applyMatrix4( camera.matrixWorldInverse ).applyMatrix4( camera.projectionMatrix ); - - } - - unproject( camera ) { - - return this.applyMatrix4( camera.projectionMatrixInverse ).applyMatrix4( camera.matrixWorld ); - - } - - transformDirection( m ) { - - // input: THREE.Matrix4 affine matrix - // vector interpreted as a direction - - const x = this.x, y = this.y, z = this.z; - const e = m.elements; - - this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z; - this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z; - this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z; - - return this.normalize(); - - } - - divide( v ) { - - this.x /= v.x; - this.y /= v.y; - this.z /= v.z; - - return this; - - } - - divideScalar( scalar ) { - - return this.multiplyScalar( 1 / scalar ); - - } - - min( v ) { - - this.x = Math.min( this.x, v.x ); - this.y = Math.min( this.y, v.y ); - this.z = Math.min( this.z, v.z ); - - return this; - - } - - max( v ) { - - this.x = Math.max( this.x, v.x ); - this.y = Math.max( this.y, v.y ); - this.z = Math.max( this.z, v.z ); - - return this; - - } - - clamp( min, max ) { - - // assumes min < max, componentwise - - this.x = Math.max( min.x, Math.min( max.x, this.x ) ); - this.y = Math.max( min.y, Math.min( max.y, this.y ) ); - this.z = Math.max( min.z, Math.min( max.z, this.z ) ); - - return this; - - } - - clampScalar( minVal, maxVal ) { - - this.x = Math.max( minVal, Math.min( maxVal, this.x ) ); - this.y = Math.max( minVal, Math.min( maxVal, this.y ) ); - this.z = Math.max( minVal, Math.min( maxVal, this.z ) ); - - return this; - - } - - clampLength( min, max ) { - - const length = this.length(); - - return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) ); - - } - - floor() { - - this.x = Math.floor( this.x ); - this.y = Math.floor( this.y ); - this.z = Math.floor( this.z ); - - return this; - - } - - ceil() { - - this.x = Math.ceil( this.x ); - this.y = Math.ceil( this.y ); - this.z = Math.ceil( this.z ); - - return this; - - } - - round() { - - this.x = Math.round( this.x ); - this.y = Math.round( this.y ); - this.z = Math.round( this.z ); - - return this; - - } - - roundToZero() { - - this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x ); - this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y ); - this.z = ( this.z < 0 ) ? Math.ceil( this.z ) : Math.floor( this.z ); - - return this; - - } - - negate() { - - this.x = - this.x; - this.y = - this.y; - this.z = - this.z; - - return this; - - } - - dot( v ) { - - return this.x * v.x + this.y * v.y + this.z * v.z; - - } - - // TODO lengthSquared? - - lengthSq() { - - return this.x * this.x + this.y * this.y + this.z * this.z; - - } - - length() { - - return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z ); - - } - - manhattanLength() { - - return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ); - - } - - normalize() { - - return this.divideScalar( this.length() || 1 ); - - } - - setLength( length ) { - - return this.normalize().multiplyScalar( length ); - - } - - lerp( v, alpha ) { - - this.x += ( v.x - this.x ) * alpha; - this.y += ( v.y - this.y ) * alpha; - this.z += ( v.z - this.z ) * alpha; - - return this; - - } - - lerpVectors( v1, v2, alpha ) { - - this.x = v1.x + ( v2.x - v1.x ) * alpha; - this.y = v1.y + ( v2.y - v1.y ) * alpha; - this.z = v1.z + ( v2.z - v1.z ) * alpha; - - return this; - - } - - cross( v ) { - - return this.crossVectors( this, v ); - - } - - crossVectors( a, b ) { - - const ax = a.x, ay = a.y, az = a.z; - const bx = b.x, by = b.y, bz = b.z; - - this.x = ay * bz - az * by; - this.y = az * bx - ax * bz; - this.z = ax * by - ay * bx; - - return this; - - } - - projectOnVector( v ) { - - const denominator = v.lengthSq(); - - if ( denominator === 0 ) return this.set( 0, 0, 0 ); - - const scalar = v.dot( this ) / denominator; - - return this.copy( v ).multiplyScalar( scalar ); - - } - - projectOnPlane( planeNormal ) { - - _vector$b.copy( this ).projectOnVector( planeNormal ); - - return this.sub( _vector$b ); - - } - - reflect( normal ) { - - // reflect incident vector off plane orthogonal to normal - // normal is assumed to have unit length - - return this.sub( _vector$b.copy( normal ).multiplyScalar( 2 * this.dot( normal ) ) ); - - } - - angleTo( v ) { - - const denominator = Math.sqrt( this.lengthSq() * v.lengthSq() ); - - if ( denominator === 0 ) return Math.PI / 2; - - const theta = this.dot( v ) / denominator; - - // clamp, to handle numerical problems - - return Math.acos( clamp( theta, - 1, 1 ) ); - - } - - distanceTo( v ) { - - return Math.sqrt( this.distanceToSquared( v ) ); - - } - - distanceToSquared( v ) { - - const dx = this.x - v.x, dy = this.y - v.y, dz = this.z - v.z; - - return dx * dx + dy * dy + dz * dz; - - } - - manhattanDistanceTo( v ) { - - return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y ) + Math.abs( this.z - v.z ); - - } - - setFromSpherical( s ) { - - return this.setFromSphericalCoords( s.radius, s.phi, s.theta ); - - } - - setFromSphericalCoords( radius, phi, theta ) { - - const sinPhiRadius = Math.sin( phi ) * radius; - - this.x = sinPhiRadius * Math.sin( theta ); - this.y = Math.cos( phi ) * radius; - this.z = sinPhiRadius * Math.cos( theta ); - - return this; - - } - - setFromCylindrical( c ) { - - return this.setFromCylindricalCoords( c.radius, c.theta, c.y ); - - } - - setFromCylindricalCoords( radius, theta, y ) { - - this.x = radius * Math.sin( theta ); - this.y = y; - this.z = radius * Math.cos( theta ); - - return this; - - } - - setFromMatrixPosition( m ) { - - const e = m.elements; - - this.x = e[ 12 ]; - this.y = e[ 13 ]; - this.z = e[ 14 ]; - - return this; - - } - - setFromMatrixScale( m ) { - - const sx = this.setFromMatrixColumn( m, 0 ).length(); - const sy = this.setFromMatrixColumn( m, 1 ).length(); - const sz = this.setFromMatrixColumn( m, 2 ).length(); - - this.x = sx; - this.y = sy; - this.z = sz; - - return this; - - } - - setFromMatrixColumn( m, index ) { - - return this.fromArray( m.elements, index * 4 ); - - } - - setFromMatrix3Column( m, index ) { - - return this.fromArray( m.elements, index * 3 ); - - } - - setFromEuler( e ) { - - this.x = e._x; - this.y = e._y; - this.z = e._z; - - return this; - - } - - setFromColor( c ) { - - this.x = c.r; - this.y = c.g; - this.z = c.b; - - return this; - - } - - equals( v ) { - - return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) ); - - } - - fromArray( array, offset = 0 ) { - - this.x = array[ offset ]; - this.y = array[ offset + 1 ]; - this.z = array[ offset + 2 ]; - - return this; - - } - - toArray( array = [], offset = 0 ) { - - array[ offset ] = this.x; - array[ offset + 1 ] = this.y; - array[ offset + 2 ] = this.z; - - return array; - - } - - fromBufferAttribute( attribute, index ) { - - this.x = attribute.getX( index ); - this.y = attribute.getY( index ); - this.z = attribute.getZ( index ); - - return this; - - } - - random() { - - this.x = Math.random(); - this.y = Math.random(); - this.z = Math.random(); - - return this; - - } - - randomDirection() { - - // Derived from https://mathworld.wolfram.com/SpherePointPicking.html - - const u = ( Math.random() - 0.5 ) * 2; - const t = Math.random() * Math.PI * 2; - const f = Math.sqrt( 1 - u ** 2 ); - - this.x = f * Math.cos( t ); - this.y = f * Math.sin( t ); - this.z = u; - - return this; - - } - - *[ Symbol.iterator ]() { - - yield this.x; - yield this.y; - yield this.z; - - } - -} - -const _vector$b = /*@__PURE__*/ new Vector3(); -const _quaternion$4 = /*@__PURE__*/ new Quaternion(); - -class Box3 { - - constructor( min = new Vector3( + Infinity, + Infinity, + Infinity ), max = new Vector3( - Infinity, - Infinity, - Infinity ) ) { - - this.isBox3 = true; - - this.min = min; - this.max = max; - - } - - set( min, max ) { - - this.min.copy( min ); - this.max.copy( max ); - - return this; - - } - - setFromArray( array ) { - - this.makeEmpty(); - - for ( let i = 0, il = array.length; i < il; i += 3 ) { - - this.expandByPoint( _vector$a.fromArray( array, i ) ); - - } - - return this; - - } - - setFromBufferAttribute( attribute ) { - - this.makeEmpty(); - - for ( let i = 0, il = attribute.count; i < il; i ++ ) { - - this.expandByPoint( _vector$a.fromBufferAttribute( attribute, i ) ); - - } - - return this; - - } - - setFromPoints( points ) { - - this.makeEmpty(); - - for ( let i = 0, il = points.length; i < il; i ++ ) { - - this.expandByPoint( points[ i ] ); - - } - - return this; - - } - - setFromCenterAndSize( center, size ) { - - const halfSize = _vector$a.copy( size ).multiplyScalar( 0.5 ); - - this.min.copy( center ).sub( halfSize ); - this.max.copy( center ).add( halfSize ); - - return this; - - } - - setFromObject( object, precise = false ) { - - this.makeEmpty(); - - return this.expandByObject( object, precise ); - - } - - clone() { - - return new this.constructor().copy( this ); - - } - - copy( box ) { - - this.min.copy( box.min ); - this.max.copy( box.max ); - - return this; - - } - - makeEmpty() { - - this.min.x = this.min.y = this.min.z = + Infinity; - this.max.x = this.max.y = this.max.z = - Infinity; - - return this; - - } - - isEmpty() { - - // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes - - return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y ) || ( this.max.z < this.min.z ); - - } - - getCenter( target ) { - - return this.isEmpty() ? target.set( 0, 0, 0 ) : target.addVectors( this.min, this.max ).multiplyScalar( 0.5 ); - - } - - getSize( target ) { - - return this.isEmpty() ? target.set( 0, 0, 0 ) : target.subVectors( this.max, this.min ); - - } - - expandByPoint( point ) { - - this.min.min( point ); - this.max.max( point ); - - return this; - - } - - expandByVector( vector ) { - - this.min.sub( vector ); - this.max.add( vector ); - - return this; - - } - - expandByScalar( scalar ) { - - this.min.addScalar( - scalar ); - this.max.addScalar( scalar ); - - return this; - - } - - expandByObject( object, precise = false ) { - - // Computes the world-axis-aligned bounding box of an object (including its children), - // accounting for both the object's, and children's, world transforms - - object.updateWorldMatrix( false, false ); - - if ( object.boundingBox !== undefined ) { - - if ( object.boundingBox === null ) { - - object.computeBoundingBox(); - - } - - _box$3.copy( object.boundingBox ); - _box$3.applyMatrix4( object.matrixWorld ); - - this.union( _box$3 ); - - } else { - - const geometry = object.geometry; - - if ( geometry !== undefined ) { - - if ( precise && geometry.attributes !== undefined && geometry.attributes.position !== undefined ) { - - const position = geometry.attributes.position; - for ( let i = 0, l = position.count; i < l; i ++ ) { - - _vector$a.fromBufferAttribute( position, i ).applyMatrix4( object.matrixWorld ); - this.expandByPoint( _vector$a ); - - } - - } else { - - if ( geometry.boundingBox === null ) { - - geometry.computeBoundingBox(); - - } - - _box$3.copy( geometry.boundingBox ); - _box$3.applyMatrix4( object.matrixWorld ); - - this.union( _box$3 ); - - } - - } - - } - - const children = object.children; - - for ( let i = 0, l = children.length; i < l; i ++ ) { - - this.expandByObject( children[ i ], precise ); - - } - - return this; - - } - - containsPoint( point ) { - - return point.x < this.min.x || point.x > this.max.x || - point.y < this.min.y || point.y > this.max.y || - point.z < this.min.z || point.z > this.max.z ? false : true; - - } - - containsBox( box ) { - - return this.min.x <= box.min.x && box.max.x <= this.max.x && - this.min.y <= box.min.y && box.max.y <= this.max.y && - this.min.z <= box.min.z && box.max.z <= this.max.z; - - } - - getParameter( point, target ) { - - // This can potentially have a divide by zero if the box - // has a size dimension of 0. - - return target.set( - ( point.x - this.min.x ) / ( this.max.x - this.min.x ), - ( point.y - this.min.y ) / ( this.max.y - this.min.y ), - ( point.z - this.min.z ) / ( this.max.z - this.min.z ) - ); - - } - - intersectsBox( box ) { - - // using 6 splitting planes to rule out intersections. - return box.max.x < this.min.x || box.min.x > this.max.x || - box.max.y < this.min.y || box.min.y > this.max.y || - box.max.z < this.min.z || box.min.z > this.max.z ? false : true; - - } - - intersectsSphere( sphere ) { - - // Find the point on the AABB closest to the sphere center. - this.clampPoint( sphere.center, _vector$a ); - - // If that point is inside the sphere, the AABB and sphere intersect. - return _vector$a.distanceToSquared( sphere.center ) <= ( sphere.radius * sphere.radius ); - - } - - intersectsPlane( plane ) { - - // We compute the minimum and maximum dot product values. If those values - // are on the same side (back or front) of the plane, then there is no intersection. - - let min, max; - - if ( plane.normal.x > 0 ) { - - min = plane.normal.x * this.min.x; - max = plane.normal.x * this.max.x; - - } else { - - min = plane.normal.x * this.max.x; - max = plane.normal.x * this.min.x; - - } - - if ( plane.normal.y > 0 ) { - - min += plane.normal.y * this.min.y; - max += plane.normal.y * this.max.y; - - } else { - - min += plane.normal.y * this.max.y; - max += plane.normal.y * this.min.y; - - } - - if ( plane.normal.z > 0 ) { - - min += plane.normal.z * this.min.z; - max += plane.normal.z * this.max.z; - - } else { - - min += plane.normal.z * this.max.z; - max += plane.normal.z * this.min.z; - - } - - return ( min <= - plane.constant && max >= - plane.constant ); - - } - - intersectsTriangle( triangle ) { - - if ( this.isEmpty() ) { - - return false; - - } - - // compute box center and extents - this.getCenter( _center ); - _extents.subVectors( this.max, _center ); - - // translate triangle to aabb origin - _v0$2.subVectors( triangle.a, _center ); - _v1$7.subVectors( triangle.b, _center ); - _v2$4.subVectors( triangle.c, _center ); - - // compute edge vectors for triangle - _f0.subVectors( _v1$7, _v0$2 ); - _f1.subVectors( _v2$4, _v1$7 ); - _f2.subVectors( _v0$2, _v2$4 ); - - // test against axes that are given by cross product combinations of the edges of the triangle and the edges of the aabb - // make an axis testing of each of the 3 sides of the aabb against each of the 3 sides of the triangle = 9 axis of separation - // axis_ij = u_i x f_j (u0, u1, u2 = face normals of aabb = x,y,z axes vectors since aabb is axis aligned) - let axes = [ - 0, - _f0.z, _f0.y, 0, - _f1.z, _f1.y, 0, - _f2.z, _f2.y, - _f0.z, 0, - _f0.x, _f1.z, 0, - _f1.x, _f2.z, 0, - _f2.x, - - _f0.y, _f0.x, 0, - _f1.y, _f1.x, 0, - _f2.y, _f2.x, 0 - ]; - if ( ! satForAxes( axes, _v0$2, _v1$7, _v2$4, _extents ) ) { - - return false; - - } - - // test 3 face normals from the aabb - axes = [ 1, 0, 0, 0, 1, 0, 0, 0, 1 ]; - if ( ! satForAxes( axes, _v0$2, _v1$7, _v2$4, _extents ) ) { - - return false; - - } - - // finally testing the face normal of the triangle - // use already existing triangle edge vectors here - _triangleNormal.crossVectors( _f0, _f1 ); - axes = [ _triangleNormal.x, _triangleNormal.y, _triangleNormal.z ]; - - return satForAxes( axes, _v0$2, _v1$7, _v2$4, _extents ); - - } - - clampPoint( point, target ) { - - return target.copy( point ).clamp( this.min, this.max ); - - } - - distanceToPoint( point ) { - - return this.clampPoint( point, _vector$a ).distanceTo( point ); - - } - - getBoundingSphere( target ) { - - if ( this.isEmpty() ) { - - target.makeEmpty(); - - } else { - - this.getCenter( target.center ); - - target.radius = this.getSize( _vector$a ).length() * 0.5; - - } - - return target; - - } - - intersect( box ) { - - this.min.max( box.min ); - this.max.min( box.max ); - - // ensure that if there is no overlap, the result is fully empty, not slightly empty with non-inf/+inf values that will cause subsequence intersects to erroneously return valid values. - if ( this.isEmpty() ) this.makeEmpty(); - - return this; - - } - - union( box ) { - - this.min.min( box.min ); - this.max.max( box.max ); - - return this; - - } - - applyMatrix4( matrix ) { - - // transform of empty box is an empty box. - if ( this.isEmpty() ) return this; - - // NOTE: I am using a binary pattern to specify all 2^3 combinations below - _points[ 0 ].set( this.min.x, this.min.y, this.min.z ).applyMatrix4( matrix ); // 000 - _points[ 1 ].set( this.min.x, this.min.y, this.max.z ).applyMatrix4( matrix ); // 001 - _points[ 2 ].set( this.min.x, this.max.y, this.min.z ).applyMatrix4( matrix ); // 010 - _points[ 3 ].set( this.min.x, this.max.y, this.max.z ).applyMatrix4( matrix ); // 011 - _points[ 4 ].set( this.max.x, this.min.y, this.min.z ).applyMatrix4( matrix ); // 100 - _points[ 5 ].set( this.max.x, this.min.y, this.max.z ).applyMatrix4( matrix ); // 101 - _points[ 6 ].set( this.max.x, this.max.y, this.min.z ).applyMatrix4( matrix ); // 110 - _points[ 7 ].set( this.max.x, this.max.y, this.max.z ).applyMatrix4( matrix ); // 111 - - this.setFromPoints( _points ); - - return this; - - } - - translate( offset ) { - - this.min.add( offset ); - this.max.add( offset ); - - return this; - - } - - equals( box ) { - - return box.min.equals( this.min ) && box.max.equals( this.max ); - - } - -} - -const _points = [ - /*@__PURE__*/ new Vector3(), - /*@__PURE__*/ new Vector3(), - /*@__PURE__*/ new Vector3(), - /*@__PURE__*/ new Vector3(), - /*@__PURE__*/ new Vector3(), - /*@__PURE__*/ new Vector3(), - /*@__PURE__*/ new Vector3(), - /*@__PURE__*/ new Vector3() -]; - -const _vector$a = /*@__PURE__*/ new Vector3(); - -const _box$3 = /*@__PURE__*/ new Box3(); - -// triangle centered vertices - -const _v0$2 = /*@__PURE__*/ new Vector3(); -const _v1$7 = /*@__PURE__*/ new Vector3(); -const _v2$4 = /*@__PURE__*/ new Vector3(); - -// triangle edge vectors - -const _f0 = /*@__PURE__*/ new Vector3(); -const _f1 = /*@__PURE__*/ new Vector3(); -const _f2 = /*@__PURE__*/ new Vector3(); - -const _center = /*@__PURE__*/ new Vector3(); -const _extents = /*@__PURE__*/ new Vector3(); -const _triangleNormal = /*@__PURE__*/ new Vector3(); -const _testAxis = /*@__PURE__*/ new Vector3(); - -function satForAxes( axes, v0, v1, v2, extents ) { - - for ( let i = 0, j = axes.length - 3; i <= j; i += 3 ) { - - _testAxis.fromArray( axes, i ); - // project the aabb onto the separating axis - const r = extents.x * Math.abs( _testAxis.x ) + extents.y * Math.abs( _testAxis.y ) + extents.z * Math.abs( _testAxis.z ); - // project all 3 vertices of the triangle onto the separating axis - const p0 = v0.dot( _testAxis ); - const p1 = v1.dot( _testAxis ); - const p2 = v2.dot( _testAxis ); - // actual test, basically see if either of the most extreme of the triangle points intersects r - if ( Math.max( - Math.max( p0, p1, p2 ), Math.min( p0, p1, p2 ) ) > r ) { - - // points of the projected triangle are outside the projected half-length of the aabb - // the axis is separating and we can exit - return false; - - } - - } - - return true; - -} - -const _box$2 = /*@__PURE__*/ new Box3(); -const _v1$6 = /*@__PURE__*/ new Vector3(); -const _v2$3 = /*@__PURE__*/ new Vector3(); - -class Sphere { - - constructor( center = new Vector3(), radius = - 1 ) { - - this.center = center; - this.radius = radius; - - } - - set( center, radius ) { - - this.center.copy( center ); - this.radius = radius; - - return this; - - } - - setFromPoints( points, optionalCenter ) { - - const center = this.center; - - if ( optionalCenter !== undefined ) { - - center.copy( optionalCenter ); - - } else { - - _box$2.setFromPoints( points ).getCenter( center ); - - } - - let maxRadiusSq = 0; - - for ( let i = 0, il = points.length; i < il; i ++ ) { - - maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( points[ i ] ) ); - - } - - this.radius = Math.sqrt( maxRadiusSq ); - - return this; - - } - - copy( sphere ) { - - this.center.copy( sphere.center ); - this.radius = sphere.radius; - - return this; - - } - - isEmpty() { - - return ( this.radius < 0 ); - - } - - makeEmpty() { - - this.center.set( 0, 0, 0 ); - this.radius = - 1; - - return this; - - } - - containsPoint( point ) { - - return ( point.distanceToSquared( this.center ) <= ( this.radius * this.radius ) ); - - } - - distanceToPoint( point ) { - - return ( point.distanceTo( this.center ) - this.radius ); - - } - - intersectsSphere( sphere ) { - - const radiusSum = this.radius + sphere.radius; - - return sphere.center.distanceToSquared( this.center ) <= ( radiusSum * radiusSum ); - - } - - intersectsBox( box ) { - - return box.intersectsSphere( this ); - - } - - intersectsPlane( plane ) { - - return Math.abs( plane.distanceToPoint( this.center ) ) <= this.radius; - - } - - clampPoint( point, target ) { - - const deltaLengthSq = this.center.distanceToSquared( point ); - - target.copy( point ); - - if ( deltaLengthSq > ( this.radius * this.radius ) ) { - - target.sub( this.center ).normalize(); - target.multiplyScalar( this.radius ).add( this.center ); - - } - - return target; - - } - - getBoundingBox( target ) { - - if ( this.isEmpty() ) { - - // Empty sphere produces empty bounding box - target.makeEmpty(); - return target; - - } - - target.set( this.center, this.center ); - target.expandByScalar( this.radius ); - - return target; - - } - - applyMatrix4( matrix ) { - - this.center.applyMatrix4( matrix ); - this.radius = this.radius * matrix.getMaxScaleOnAxis(); - - return this; - - } - - translate( offset ) { - - this.center.add( offset ); - - return this; - - } - - expandByPoint( point ) { - - if ( this.isEmpty() ) { - - this.center.copy( point ); - - this.radius = 0; - - return this; - - } - - _v1$6.subVectors( point, this.center ); - - const lengthSq = _v1$6.lengthSq(); - - if ( lengthSq > ( this.radius * this.radius ) ) { - - // calculate the minimal sphere - - const length = Math.sqrt( lengthSq ); - - const delta = ( length - this.radius ) * 0.5; - - this.center.addScaledVector( _v1$6, delta / length ); - - this.radius += delta; - - } - - return this; - - } - - union( sphere ) { - - if ( sphere.isEmpty() ) { - - return this; - - } - - if ( this.isEmpty() ) { - - this.copy( sphere ); - - return this; - - } - - if ( this.center.equals( sphere.center ) === true ) { - - this.radius = Math.max( this.radius, sphere.radius ); - - } else { - - _v2$3.subVectors( sphere.center, this.center ).setLength( sphere.radius ); - - this.expandByPoint( _v1$6.copy( sphere.center ).add( _v2$3 ) ); - - this.expandByPoint( _v1$6.copy( sphere.center ).sub( _v2$3 ) ); - - } - - return this; - - } - - equals( sphere ) { - - return sphere.center.equals( this.center ) && ( sphere.radius === this.radius ); - - } - - clone() { - - return new this.constructor().copy( this ); - - } - -} - -const _vector$9 = /*@__PURE__*/ new Vector3(); -const _segCenter = /*@__PURE__*/ new Vector3(); -const _segDir = /*@__PURE__*/ new Vector3(); -const _diff = /*@__PURE__*/ new Vector3(); - -const _edge1 = /*@__PURE__*/ new Vector3(); -const _edge2 = /*@__PURE__*/ new Vector3(); -const _normal$1 = /*@__PURE__*/ new Vector3(); - -class Ray { - - constructor( origin = new Vector3(), direction = new Vector3( 0, 0, - 1 ) ) { - - this.origin = origin; - this.direction = direction; - - } - - set( origin, direction ) { - - this.origin.copy( origin ); - this.direction.copy( direction ); - - return this; - - } - - copy( ray ) { - - this.origin.copy( ray.origin ); - this.direction.copy( ray.direction ); - - return this; - - } - - at( t, target ) { - - return target.copy( this.origin ).addScaledVector( this.direction, t ); - - } - - lookAt( v ) { - - this.direction.copy( v ).sub( this.origin ).normalize(); - - return this; - - } - - recast( t ) { - - this.origin.copy( this.at( t, _vector$9 ) ); - - return this; - - } - - closestPointToPoint( point, target ) { - - target.subVectors( point, this.origin ); - - const directionDistance = target.dot( this.direction ); - - if ( directionDistance < 0 ) { - - return target.copy( this.origin ); - - } - - return target.copy( this.origin ).addScaledVector( this.direction, directionDistance ); - - } - - distanceToPoint( point ) { - - return Math.sqrt( this.distanceSqToPoint( point ) ); - - } - - distanceSqToPoint( point ) { - - const directionDistance = _vector$9.subVectors( point, this.origin ).dot( this.direction ); - - // point behind the ray - - if ( directionDistance < 0 ) { - - return this.origin.distanceToSquared( point ); - - } - - _vector$9.copy( this.origin ).addScaledVector( this.direction, directionDistance ); - - return _vector$9.distanceToSquared( point ); - - } - - distanceSqToSegment( v0, v1, optionalPointOnRay, optionalPointOnSegment ) { - - // from https://github.com/pmjoniak/GeometricTools/blob/master/GTEngine/Include/Mathematics/GteDistRaySegment.h - // It returns the min distance between the ray and the segment - // defined by v0 and v1 - // It can also set two optional targets : - // - The closest point on the ray - // - The closest point on the segment - - _segCenter.copy( v0 ).add( v1 ).multiplyScalar( 0.5 ); - _segDir.copy( v1 ).sub( v0 ).normalize(); - _diff.copy( this.origin ).sub( _segCenter ); - - const segExtent = v0.distanceTo( v1 ) * 0.5; - const a01 = - this.direction.dot( _segDir ); - const b0 = _diff.dot( this.direction ); - const b1 = - _diff.dot( _segDir ); - const c = _diff.lengthSq(); - const det = Math.abs( 1 - a01 * a01 ); - let s0, s1, sqrDist, extDet; - - if ( det > 0 ) { - - // The ray and segment are not parallel. - - s0 = a01 * b1 - b0; - s1 = a01 * b0 - b1; - extDet = segExtent * det; - - if ( s0 >= 0 ) { - - if ( s1 >= - extDet ) { - - if ( s1 <= extDet ) { - - // region 0 - // Minimum at interior points of ray and segment. - - const invDet = 1 / det; - s0 *= invDet; - s1 *= invDet; - sqrDist = s0 * ( s0 + a01 * s1 + 2 * b0 ) + s1 * ( a01 * s0 + s1 + 2 * b1 ) + c; - - } else { - - // region 1 - - s1 = segExtent; - s0 = Math.max( 0, - ( a01 * s1 + b0 ) ); - sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; - - } - - } else { - - // region 5 - - s1 = - segExtent; - s0 = Math.max( 0, - ( a01 * s1 + b0 ) ); - sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; - - } - - } else { - - if ( s1 <= - extDet ) { - - // region 4 - - s0 = Math.max( 0, - ( - a01 * segExtent + b0 ) ); - s1 = ( s0 > 0 ) ? - segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent ); - sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; - - } else if ( s1 <= extDet ) { - - // region 3 - - s0 = 0; - s1 = Math.min( Math.max( - segExtent, - b1 ), segExtent ); - sqrDist = s1 * ( s1 + 2 * b1 ) + c; - - } else { - - // region 2 - - s0 = Math.max( 0, - ( a01 * segExtent + b0 ) ); - s1 = ( s0 > 0 ) ? segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent ); - sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; - - } - - } - - } else { - - // Ray and segment are parallel. - - s1 = ( a01 > 0 ) ? - segExtent : segExtent; - s0 = Math.max( 0, - ( a01 * s1 + b0 ) ); - sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; - - } - - if ( optionalPointOnRay ) { - - optionalPointOnRay.copy( this.origin ).addScaledVector( this.direction, s0 ); - - } - - if ( optionalPointOnSegment ) { - - optionalPointOnSegment.copy( _segCenter ).addScaledVector( _segDir, s1 ); - - } - - return sqrDist; - - } - - intersectSphere( sphere, target ) { - - _vector$9.subVectors( sphere.center, this.origin ); - const tca = _vector$9.dot( this.direction ); - const d2 = _vector$9.dot( _vector$9 ) - tca * tca; - const radius2 = sphere.radius * sphere.radius; - - if ( d2 > radius2 ) return null; - - const thc = Math.sqrt( radius2 - d2 ); - - // t0 = first intersect point - entrance on front of sphere - const t0 = tca - thc; - - // t1 = second intersect point - exit point on back of sphere - const t1 = tca + thc; - - // test to see if t1 is behind the ray - if so, return null - if ( t1 < 0 ) return null; - - // test to see if t0 is behind the ray: - // if it is, the ray is inside the sphere, so return the second exit point scaled by t1, - // in order to always return an intersect point that is in front of the ray. - if ( t0 < 0 ) return this.at( t1, target ); - - // else t0 is in front of the ray, so return the first collision point scaled by t0 - return this.at( t0, target ); - - } - - intersectsSphere( sphere ) { - - return this.distanceSqToPoint( sphere.center ) <= ( sphere.radius * sphere.radius ); - - } - - distanceToPlane( plane ) { - - const denominator = plane.normal.dot( this.direction ); - - if ( denominator === 0 ) { - - // line is coplanar, return origin - if ( plane.distanceToPoint( this.origin ) === 0 ) { - - return 0; - - } - - // Null is preferable to undefined since undefined means.... it is undefined - - return null; - - } - - const t = - ( this.origin.dot( plane.normal ) + plane.constant ) / denominator; - - // Return if the ray never intersects the plane - - return t >= 0 ? t : null; - - } - - intersectPlane( plane, target ) { - - const t = this.distanceToPlane( plane ); - - if ( t === null ) { - - return null; - - } - - return this.at( t, target ); - - } - - intersectsPlane( plane ) { - - // check if the ray lies on the plane first - - const distToPoint = plane.distanceToPoint( this.origin ); - - if ( distToPoint === 0 ) { - - return true; - - } - - const denominator = plane.normal.dot( this.direction ); - - if ( denominator * distToPoint < 0 ) { - - return true; - - } - - // ray origin is behind the plane (and is pointing behind it) - - return false; - - } - - intersectBox( box, target ) { - - let tmin, tmax, tymin, tymax, tzmin, tzmax; - - const invdirx = 1 / this.direction.x, - invdiry = 1 / this.direction.y, - invdirz = 1 / this.direction.z; - - const origin = this.origin; - - if ( invdirx >= 0 ) { - - tmin = ( box.min.x - origin.x ) * invdirx; - tmax = ( box.max.x - origin.x ) * invdirx; - - } else { - - tmin = ( box.max.x - origin.x ) * invdirx; - tmax = ( box.min.x - origin.x ) * invdirx; - - } - - if ( invdiry >= 0 ) { - - tymin = ( box.min.y - origin.y ) * invdiry; - tymax = ( box.max.y - origin.y ) * invdiry; - - } else { - - tymin = ( box.max.y - origin.y ) * invdiry; - tymax = ( box.min.y - origin.y ) * invdiry; - - } - - if ( ( tmin > tymax ) || ( tymin > tmax ) ) return null; - - if ( tymin > tmin || isNaN( tmin ) ) tmin = tymin; - - if ( tymax < tmax || isNaN( tmax ) ) tmax = tymax; - - if ( invdirz >= 0 ) { - - tzmin = ( box.min.z - origin.z ) * invdirz; - tzmax = ( box.max.z - origin.z ) * invdirz; - - } else { - - tzmin = ( box.max.z - origin.z ) * invdirz; - tzmax = ( box.min.z - origin.z ) * invdirz; - - } - - if ( ( tmin > tzmax ) || ( tzmin > tmax ) ) return null; - - if ( tzmin > tmin || tmin !== tmin ) tmin = tzmin; - - if ( tzmax < tmax || tmax !== tmax ) tmax = tzmax; - - //return point closest to the ray (positive side) - - if ( tmax < 0 ) return null; - - return this.at( tmin >= 0 ? tmin : tmax, target ); - - } - - intersectsBox( box ) { - - return this.intersectBox( box, _vector$9 ) !== null; - - } - - intersectTriangle( a, b, c, backfaceCulling, target ) { - - // Compute the offset origin, edges, and normal. - - // from https://github.com/pmjoniak/GeometricTools/blob/master/GTEngine/Include/Mathematics/GteIntrRay3Triangle3.h - - _edge1.subVectors( b, a ); - _edge2.subVectors( c, a ); - _normal$1.crossVectors( _edge1, _edge2 ); - - // Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction, - // E1 = kEdge1, E2 = kEdge2, N = Cross(E1,E2)) by - // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2)) - // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q)) - // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N) - let DdN = this.direction.dot( _normal$1 ); - let sign; - - if ( DdN > 0 ) { - - if ( backfaceCulling ) return null; - sign = 1; - - } else if ( DdN < 0 ) { - - sign = - 1; - DdN = - DdN; - - } else { - - return null; - - } - - _diff.subVectors( this.origin, a ); - const DdQxE2 = sign * this.direction.dot( _edge2.crossVectors( _diff, _edge2 ) ); - - // b1 < 0, no intersection - if ( DdQxE2 < 0 ) { - - return null; - - } - - const DdE1xQ = sign * this.direction.dot( _edge1.cross( _diff ) ); - - // b2 < 0, no intersection - if ( DdE1xQ < 0 ) { - - return null; - - } - - // b1+b2 > 1, no intersection - if ( DdQxE2 + DdE1xQ > DdN ) { - - return null; - - } - - // Line intersects triangle, check if ray does. - const QdN = - sign * _diff.dot( _normal$1 ); - - // t < 0, no intersection - if ( QdN < 0 ) { - - return null; - - } - - // Ray intersects triangle. - return this.at( QdN / DdN, target ); - - } - - applyMatrix4( matrix4 ) { - - this.origin.applyMatrix4( matrix4 ); - this.direction.transformDirection( matrix4 ); - - return this; - - } - - equals( ray ) { - - return ray.origin.equals( this.origin ) && ray.direction.equals( this.direction ); - - } - - clone() { - - return new this.constructor().copy( this ); - - } - -} - -class Matrix4 { - - constructor( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ) { - - Matrix4.prototype.isMatrix4 = true; - - this.elements = [ - - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1 - - ]; - - if ( n11 !== undefined ) { - - this.set( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ); - - } - - } - - set( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ) { - - const te = this.elements; - - te[ 0 ] = n11; te[ 4 ] = n12; te[ 8 ] = n13; te[ 12 ] = n14; - te[ 1 ] = n21; te[ 5 ] = n22; te[ 9 ] = n23; te[ 13 ] = n24; - te[ 2 ] = n31; te[ 6 ] = n32; te[ 10 ] = n33; te[ 14 ] = n34; - te[ 3 ] = n41; te[ 7 ] = n42; te[ 11 ] = n43; te[ 15 ] = n44; - - return this; - - } - - identity() { - - this.set( - - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1 - - ); - - return this; - - } - - clone() { - - return new Matrix4().fromArray( this.elements ); - - } - - copy( m ) { - - const te = this.elements; - const me = m.elements; - - te[ 0 ] = me[ 0 ]; te[ 1 ] = me[ 1 ]; te[ 2 ] = me[ 2 ]; te[ 3 ] = me[ 3 ]; - te[ 4 ] = me[ 4 ]; te[ 5 ] = me[ 5 ]; te[ 6 ] = me[ 6 ]; te[ 7 ] = me[ 7 ]; - te[ 8 ] = me[ 8 ]; te[ 9 ] = me[ 9 ]; te[ 10 ] = me[ 10 ]; te[ 11 ] = me[ 11 ]; - te[ 12 ] = me[ 12 ]; te[ 13 ] = me[ 13 ]; te[ 14 ] = me[ 14 ]; te[ 15 ] = me[ 15 ]; - - return this; - - } - - copyPosition( m ) { - - const te = this.elements, me = m.elements; - - te[ 12 ] = me[ 12 ]; - te[ 13 ] = me[ 13 ]; - te[ 14 ] = me[ 14 ]; - - return this; - - } - - setFromMatrix3( m ) { - - const me = m.elements; - - this.set( - - me[ 0 ], me[ 3 ], me[ 6 ], 0, - me[ 1 ], me[ 4 ], me[ 7 ], 0, - me[ 2 ], me[ 5 ], me[ 8 ], 0, - 0, 0, 0, 1 - - ); - - return this; - - } - - extractBasis( xAxis, yAxis, zAxis ) { - - xAxis.setFromMatrixColumn( this, 0 ); - yAxis.setFromMatrixColumn( this, 1 ); - zAxis.setFromMatrixColumn( this, 2 ); - - return this; - - } - - makeBasis( xAxis, yAxis, zAxis ) { - - this.set( - xAxis.x, yAxis.x, zAxis.x, 0, - xAxis.y, yAxis.y, zAxis.y, 0, - xAxis.z, yAxis.z, zAxis.z, 0, - 0, 0, 0, 1 - ); - - return this; - - } - - extractRotation( m ) { - - // this method does not support reflection matrices - - const te = this.elements; - const me = m.elements; - - const scaleX = 1 / _v1$5.setFromMatrixColumn( m, 0 ).length(); - const scaleY = 1 / _v1$5.setFromMatrixColumn( m, 1 ).length(); - const scaleZ = 1 / _v1$5.setFromMatrixColumn( m, 2 ).length(); - - te[ 0 ] = me[ 0 ] * scaleX; - te[ 1 ] = me[ 1 ] * scaleX; - te[ 2 ] = me[ 2 ] * scaleX; - te[ 3 ] = 0; - - te[ 4 ] = me[ 4 ] * scaleY; - te[ 5 ] = me[ 5 ] * scaleY; - te[ 6 ] = me[ 6 ] * scaleY; - te[ 7 ] = 0; - - te[ 8 ] = me[ 8 ] * scaleZ; - te[ 9 ] = me[ 9 ] * scaleZ; - te[ 10 ] = me[ 10 ] * scaleZ; - te[ 11 ] = 0; - - te[ 12 ] = 0; - te[ 13 ] = 0; - te[ 14 ] = 0; - te[ 15 ] = 1; - - return this; - - } - - makeRotationFromEuler( euler ) { - - const te = this.elements; - - const x = euler.x, y = euler.y, z = euler.z; - const a = Math.cos( x ), b = Math.sin( x ); - const c = Math.cos( y ), d = Math.sin( y ); - const e = Math.cos( z ), f = Math.sin( z ); - - if ( euler.order === 'XYZ' ) { - - const ae = a * e, af = a * f, be = b * e, bf = b * f; - - te[ 0 ] = c * e; - te[ 4 ] = - c * f; - te[ 8 ] = d; - - te[ 1 ] = af + be * d; - te[ 5 ] = ae - bf * d; - te[ 9 ] = - b * c; - - te[ 2 ] = bf - ae * d; - te[ 6 ] = be + af * d; - te[ 10 ] = a * c; - - } else if ( euler.order === 'YXZ' ) { - - const ce = c * e, cf = c * f, de = d * e, df = d * f; - - te[ 0 ] = ce + df * b; - te[ 4 ] = de * b - cf; - te[ 8 ] = a * d; - - te[ 1 ] = a * f; - te[ 5 ] = a * e; - te[ 9 ] = - b; - - te[ 2 ] = cf * b - de; - te[ 6 ] = df + ce * b; - te[ 10 ] = a * c; - - } else if ( euler.order === 'ZXY' ) { - - const ce = c * e, cf = c * f, de = d * e, df = d * f; - - te[ 0 ] = ce - df * b; - te[ 4 ] = - a * f; - te[ 8 ] = de + cf * b; - - te[ 1 ] = cf + de * b; - te[ 5 ] = a * e; - te[ 9 ] = df - ce * b; - - te[ 2 ] = - a * d; - te[ 6 ] = b; - te[ 10 ] = a * c; - - } else if ( euler.order === 'ZYX' ) { - - const ae = a * e, af = a * f, be = b * e, bf = b * f; - - te[ 0 ] = c * e; - te[ 4 ] = be * d - af; - te[ 8 ] = ae * d + bf; - - te[ 1 ] = c * f; - te[ 5 ] = bf * d + ae; - te[ 9 ] = af * d - be; - - te[ 2 ] = - d; - te[ 6 ] = b * c; - te[ 10 ] = a * c; - - } else if ( euler.order === 'YZX' ) { - - const ac = a * c, ad = a * d, bc = b * c, bd = b * d; - - te[ 0 ] = c * e; - te[ 4 ] = bd - ac * f; - te[ 8 ] = bc * f + ad; - - te[ 1 ] = f; - te[ 5 ] = a * e; - te[ 9 ] = - b * e; - - te[ 2 ] = - d * e; - te[ 6 ] = ad * f + bc; - te[ 10 ] = ac - bd * f; - - } else if ( euler.order === 'XZY' ) { - - const ac = a * c, ad = a * d, bc = b * c, bd = b * d; - - te[ 0 ] = c * e; - te[ 4 ] = - f; - te[ 8 ] = d * e; - - te[ 1 ] = ac * f + bd; - te[ 5 ] = a * e; - te[ 9 ] = ad * f - bc; - - te[ 2 ] = bc * f - ad; - te[ 6 ] = b * e; - te[ 10 ] = bd * f + ac; - - } - - // bottom row - te[ 3 ] = 0; - te[ 7 ] = 0; - te[ 11 ] = 0; - - // last column - te[ 12 ] = 0; - te[ 13 ] = 0; - te[ 14 ] = 0; - te[ 15 ] = 1; - - return this; - - } - - makeRotationFromQuaternion( q ) { - - return this.compose( _zero, q, _one ); - - } - - lookAt( eye, target, up ) { - - const te = this.elements; - - _z.subVectors( eye, target ); - - if ( _z.lengthSq() === 0 ) { - - // eye and target are in the same position - - _z.z = 1; - - } - - _z.normalize(); - _x.crossVectors( up, _z ); - - if ( _x.lengthSq() === 0 ) { - - // up and z are parallel - - if ( Math.abs( up.z ) === 1 ) { - - _z.x += 0.0001; - - } else { - - _z.z += 0.0001; - - } - - _z.normalize(); - _x.crossVectors( up, _z ); - - } - - _x.normalize(); - _y.crossVectors( _z, _x ); - - te[ 0 ] = _x.x; te[ 4 ] = _y.x; te[ 8 ] = _z.x; - te[ 1 ] = _x.y; te[ 5 ] = _y.y; te[ 9 ] = _z.y; - te[ 2 ] = _x.z; te[ 6 ] = _y.z; te[ 10 ] = _z.z; - - return this; - - } - - multiply( m ) { - - return this.multiplyMatrices( this, m ); - - } - - premultiply( m ) { - - return this.multiplyMatrices( m, this ); - - } - - multiplyMatrices( a, b ) { - - const ae = a.elements; - const be = b.elements; - const te = this.elements; - - const a11 = ae[ 0 ], a12 = ae[ 4 ], a13 = ae[ 8 ], a14 = ae[ 12 ]; - const a21 = ae[ 1 ], a22 = ae[ 5 ], a23 = ae[ 9 ], a24 = ae[ 13 ]; - const a31 = ae[ 2 ], a32 = ae[ 6 ], a33 = ae[ 10 ], a34 = ae[ 14 ]; - const a41 = ae[ 3 ], a42 = ae[ 7 ], a43 = ae[ 11 ], a44 = ae[ 15 ]; - - const b11 = be[ 0 ], b12 = be[ 4 ], b13 = be[ 8 ], b14 = be[ 12 ]; - const b21 = be[ 1 ], b22 = be[ 5 ], b23 = be[ 9 ], b24 = be[ 13 ]; - const b31 = be[ 2 ], b32 = be[ 6 ], b33 = be[ 10 ], b34 = be[ 14 ]; - const b41 = be[ 3 ], b42 = be[ 7 ], b43 = be[ 11 ], b44 = be[ 15 ]; - - te[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41; - te[ 4 ] = a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42; - te[ 8 ] = a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43; - te[ 12 ] = a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44; - - te[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41; - te[ 5 ] = a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42; - te[ 9 ] = a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43; - te[ 13 ] = a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44; - - te[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41; - te[ 6 ] = a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42; - te[ 10 ] = a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43; - te[ 14 ] = a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44; - - te[ 3 ] = a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41; - te[ 7 ] = a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42; - te[ 11 ] = a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43; - te[ 15 ] = a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44; - - return this; - - } - - multiplyScalar( s ) { - - const te = this.elements; - - te[ 0 ] *= s; te[ 4 ] *= s; te[ 8 ] *= s; te[ 12 ] *= s; - te[ 1 ] *= s; te[ 5 ] *= s; te[ 9 ] *= s; te[ 13 ] *= s; - te[ 2 ] *= s; te[ 6 ] *= s; te[ 10 ] *= s; te[ 14 ] *= s; - te[ 3 ] *= s; te[ 7 ] *= s; te[ 11 ] *= s; te[ 15 ] *= s; - - return this; - - } - - determinant() { - - const te = this.elements; - - const n11 = te[ 0 ], n12 = te[ 4 ], n13 = te[ 8 ], n14 = te[ 12 ]; - const n21 = te[ 1 ], n22 = te[ 5 ], n23 = te[ 9 ], n24 = te[ 13 ]; - const n31 = te[ 2 ], n32 = te[ 6 ], n33 = te[ 10 ], n34 = te[ 14 ]; - const n41 = te[ 3 ], n42 = te[ 7 ], n43 = te[ 11 ], n44 = te[ 15 ]; - - //TODO: make this more efficient - //( based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm ) - - return ( - n41 * ( - + n14 * n23 * n32 - - n13 * n24 * n32 - - n14 * n22 * n33 - + n12 * n24 * n33 - + n13 * n22 * n34 - - n12 * n23 * n34 - ) + - n42 * ( - + n11 * n23 * n34 - - n11 * n24 * n33 - + n14 * n21 * n33 - - n13 * n21 * n34 - + n13 * n24 * n31 - - n14 * n23 * n31 - ) + - n43 * ( - + n11 * n24 * n32 - - n11 * n22 * n34 - - n14 * n21 * n32 - + n12 * n21 * n34 - + n14 * n22 * n31 - - n12 * n24 * n31 - ) + - n44 * ( - - n13 * n22 * n31 - - n11 * n23 * n32 - + n11 * n22 * n33 - + n13 * n21 * n32 - - n12 * n21 * n33 - + n12 * n23 * n31 - ) - - ); - - } - - transpose() { - - const te = this.elements; - let tmp; - - tmp = te[ 1 ]; te[ 1 ] = te[ 4 ]; te[ 4 ] = tmp; - tmp = te[ 2 ]; te[ 2 ] = te[ 8 ]; te[ 8 ] = tmp; - tmp = te[ 6 ]; te[ 6 ] = te[ 9 ]; te[ 9 ] = tmp; - - tmp = te[ 3 ]; te[ 3 ] = te[ 12 ]; te[ 12 ] = tmp; - tmp = te[ 7 ]; te[ 7 ] = te[ 13 ]; te[ 13 ] = tmp; - tmp = te[ 11 ]; te[ 11 ] = te[ 14 ]; te[ 14 ] = tmp; - - return this; - - } - - setPosition( x, y, z ) { - - const te = this.elements; - - if ( x.isVector3 ) { - - te[ 12 ] = x.x; - te[ 13 ] = x.y; - te[ 14 ] = x.z; - - } else { - - te[ 12 ] = x; - te[ 13 ] = y; - te[ 14 ] = z; - - } - - return this; - - } - - invert() { - - // based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm - const te = this.elements, - - n11 = te[ 0 ], n21 = te[ 1 ], n31 = te[ 2 ], n41 = te[ 3 ], - n12 = te[ 4 ], n22 = te[ 5 ], n32 = te[ 6 ], n42 = te[ 7 ], - n13 = te[ 8 ], n23 = te[ 9 ], n33 = te[ 10 ], n43 = te[ 11 ], - n14 = te[ 12 ], n24 = te[ 13 ], n34 = te[ 14 ], n44 = te[ 15 ], - - t11 = n23 * n34 * n42 - n24 * n33 * n42 + n24 * n32 * n43 - n22 * n34 * n43 - n23 * n32 * n44 + n22 * n33 * n44, - t12 = n14 * n33 * n42 - n13 * n34 * n42 - n14 * n32 * n43 + n12 * n34 * n43 + n13 * n32 * n44 - n12 * n33 * n44, - t13 = n13 * n24 * n42 - n14 * n23 * n42 + n14 * n22 * n43 - n12 * n24 * n43 - n13 * n22 * n44 + n12 * n23 * n44, - t14 = n14 * n23 * n32 - n13 * n24 * n32 - n14 * n22 * n33 + n12 * n24 * n33 + n13 * n22 * n34 - n12 * n23 * n34; - - const det = n11 * t11 + n21 * t12 + n31 * t13 + n41 * t14; - - if ( det === 0 ) return this.set( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ); - - const detInv = 1 / det; - - te[ 0 ] = t11 * detInv; - te[ 1 ] = ( n24 * n33 * n41 - n23 * n34 * n41 - n24 * n31 * n43 + n21 * n34 * n43 + n23 * n31 * n44 - n21 * n33 * n44 ) * detInv; - te[ 2 ] = ( n22 * n34 * n41 - n24 * n32 * n41 + n24 * n31 * n42 - n21 * n34 * n42 - n22 * n31 * n44 + n21 * n32 * n44 ) * detInv; - te[ 3 ] = ( n23 * n32 * n41 - n22 * n33 * n41 - n23 * n31 * n42 + n21 * n33 * n42 + n22 * n31 * n43 - n21 * n32 * n43 ) * detInv; - - te[ 4 ] = t12 * detInv; - te[ 5 ] = ( n13 * n34 * n41 - n14 * n33 * n41 + n14 * n31 * n43 - n11 * n34 * n43 - n13 * n31 * n44 + n11 * n33 * n44 ) * detInv; - te[ 6 ] = ( n14 * n32 * n41 - n12 * n34 * n41 - n14 * n31 * n42 + n11 * n34 * n42 + n12 * n31 * n44 - n11 * n32 * n44 ) * detInv; - te[ 7 ] = ( n12 * n33 * n41 - n13 * n32 * n41 + n13 * n31 * n42 - n11 * n33 * n42 - n12 * n31 * n43 + n11 * n32 * n43 ) * detInv; - - te[ 8 ] = t13 * detInv; - te[ 9 ] = ( n14 * n23 * n41 - n13 * n24 * n41 - n14 * n21 * n43 + n11 * n24 * n43 + n13 * n21 * n44 - n11 * n23 * n44 ) * detInv; - te[ 10 ] = ( n12 * n24 * n41 - n14 * n22 * n41 + n14 * n21 * n42 - n11 * n24 * n42 - n12 * n21 * n44 + n11 * n22 * n44 ) * detInv; - te[ 11 ] = ( n13 * n22 * n41 - n12 * n23 * n41 - n13 * n21 * n42 + n11 * n23 * n42 + n12 * n21 * n43 - n11 * n22 * n43 ) * detInv; - - te[ 12 ] = t14 * detInv; - te[ 13 ] = ( n13 * n24 * n31 - n14 * n23 * n31 + n14 * n21 * n33 - n11 * n24 * n33 - n13 * n21 * n34 + n11 * n23 * n34 ) * detInv; - te[ 14 ] = ( n14 * n22 * n31 - n12 * n24 * n31 - n14 * n21 * n32 + n11 * n24 * n32 + n12 * n21 * n34 - n11 * n22 * n34 ) * detInv; - te[ 15 ] = ( n12 * n23 * n31 - n13 * n22 * n31 + n13 * n21 * n32 - n11 * n23 * n32 - n12 * n21 * n33 + n11 * n22 * n33 ) * detInv; - - return this; - - } - - scale( v ) { - - const te = this.elements; - const x = v.x, y = v.y, z = v.z; - - te[ 0 ] *= x; te[ 4 ] *= y; te[ 8 ] *= z; - te[ 1 ] *= x; te[ 5 ] *= y; te[ 9 ] *= z; - te[ 2 ] *= x; te[ 6 ] *= y; te[ 10 ] *= z; - te[ 3 ] *= x; te[ 7 ] *= y; te[ 11 ] *= z; - - return this; - - } - - getMaxScaleOnAxis() { - - const te = this.elements; - - const scaleXSq = te[ 0 ] * te[ 0 ] + te[ 1 ] * te[ 1 ] + te[ 2 ] * te[ 2 ]; - const scaleYSq = te[ 4 ] * te[ 4 ] + te[ 5 ] * te[ 5 ] + te[ 6 ] * te[ 6 ]; - const scaleZSq = te[ 8 ] * te[ 8 ] + te[ 9 ] * te[ 9 ] + te[ 10 ] * te[ 10 ]; - - return Math.sqrt( Math.max( scaleXSq, scaleYSq, scaleZSq ) ); - - } - - makeTranslation( x, y, z ) { - - this.set( - - 1, 0, 0, x, - 0, 1, 0, y, - 0, 0, 1, z, - 0, 0, 0, 1 - - ); - - return this; - - } - - makeRotationX( theta ) { - - const c = Math.cos( theta ), s = Math.sin( theta ); - - this.set( - - 1, 0, 0, 0, - 0, c, - s, 0, - 0, s, c, 0, - 0, 0, 0, 1 - - ); - - return this; - - } - - makeRotationY( theta ) { - - const c = Math.cos( theta ), s = Math.sin( theta ); - - this.set( - - c, 0, s, 0, - 0, 1, 0, 0, - - s, 0, c, 0, - 0, 0, 0, 1 - - ); - - return this; - - } - - makeRotationZ( theta ) { - - const c = Math.cos( theta ), s = Math.sin( theta ); - - this.set( - - c, - s, 0, 0, - s, c, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1 - - ); - - return this; - - } - - makeRotationAxis( axis, angle ) { - - // Based on http://www.gamedev.net/reference/articles/article1199.asp - - const c = Math.cos( angle ); - const s = Math.sin( angle ); - const t = 1 - c; - const x = axis.x, y = axis.y, z = axis.z; - const tx = t * x, ty = t * y; - - this.set( - - tx * x + c, tx * y - s * z, tx * z + s * y, 0, - tx * y + s * z, ty * y + c, ty * z - s * x, 0, - tx * z - s * y, ty * z + s * x, t * z * z + c, 0, - 0, 0, 0, 1 - - ); - - return this; - - } - - makeScale( x, y, z ) { - - this.set( - - x, 0, 0, 0, - 0, y, 0, 0, - 0, 0, z, 0, - 0, 0, 0, 1 - - ); - - return this; - - } - - makeShear( xy, xz, yx, yz, zx, zy ) { - - this.set( - - 1, yx, zx, 0, - xy, 1, zy, 0, - xz, yz, 1, 0, - 0, 0, 0, 1 - - ); - - return this; - - } - - compose( position, quaternion, scale ) { - - const te = this.elements; - - const x = quaternion._x, y = quaternion._y, z = quaternion._z, w = quaternion._w; - const x2 = x + x, y2 = y + y, z2 = z + z; - const xx = x * x2, xy = x * y2, xz = x * z2; - const yy = y * y2, yz = y * z2, zz = z * z2; - const wx = w * x2, wy = w * y2, wz = w * z2; - - const sx = scale.x, sy = scale.y, sz = scale.z; - - te[ 0 ] = ( 1 - ( yy + zz ) ) * sx; - te[ 1 ] = ( xy + wz ) * sx; - te[ 2 ] = ( xz - wy ) * sx; - te[ 3 ] = 0; - - te[ 4 ] = ( xy - wz ) * sy; - te[ 5 ] = ( 1 - ( xx + zz ) ) * sy; - te[ 6 ] = ( yz + wx ) * sy; - te[ 7 ] = 0; - - te[ 8 ] = ( xz + wy ) * sz; - te[ 9 ] = ( yz - wx ) * sz; - te[ 10 ] = ( 1 - ( xx + yy ) ) * sz; - te[ 11 ] = 0; - - te[ 12 ] = position.x; - te[ 13 ] = position.y; - te[ 14 ] = position.z; - te[ 15 ] = 1; - - return this; - - } - - decompose( position, quaternion, scale ) { - - const te = this.elements; - - let sx = _v1$5.set( te[ 0 ], te[ 1 ], te[ 2 ] ).length(); - const sy = _v1$5.set( te[ 4 ], te[ 5 ], te[ 6 ] ).length(); - const sz = _v1$5.set( te[ 8 ], te[ 9 ], te[ 10 ] ).length(); - - // if determine is negative, we need to invert one scale - const det = this.determinant(); - if ( det < 0 ) sx = - sx; - - position.x = te[ 12 ]; - position.y = te[ 13 ]; - position.z = te[ 14 ]; - - // scale the rotation part - _m1$2.copy( this ); - - const invSX = 1 / sx; - const invSY = 1 / sy; - const invSZ = 1 / sz; - - _m1$2.elements[ 0 ] *= invSX; - _m1$2.elements[ 1 ] *= invSX; - _m1$2.elements[ 2 ] *= invSX; - - _m1$2.elements[ 4 ] *= invSY; - _m1$2.elements[ 5 ] *= invSY; - _m1$2.elements[ 6 ] *= invSY; - - _m1$2.elements[ 8 ] *= invSZ; - _m1$2.elements[ 9 ] *= invSZ; - _m1$2.elements[ 10 ] *= invSZ; - - quaternion.setFromRotationMatrix( _m1$2 ); - - scale.x = sx; - scale.y = sy; - scale.z = sz; - - return this; - - } - - makePerspective( left, right, top, bottom, near, far ) { - - const te = this.elements; - const x = 2 * near / ( right - left ); - const y = 2 * near / ( top - bottom ); - - const a = ( right + left ) / ( right - left ); - const b = ( top + bottom ) / ( top - bottom ); - const c = - ( far + near ) / ( far - near ); - const d = - 2 * far * near / ( far - near ); - - te[ 0 ] = x; te[ 4 ] = 0; te[ 8 ] = a; te[ 12 ] = 0; - te[ 1 ] = 0; te[ 5 ] = y; te[ 9 ] = b; te[ 13 ] = 0; - te[ 2 ] = 0; te[ 6 ] = 0; te[ 10 ] = c; te[ 14 ] = d; - te[ 3 ] = 0; te[ 7 ] = 0; te[ 11 ] = - 1; te[ 15 ] = 0; - - return this; - - } - - makeOrthographic( left, right, top, bottom, near, far ) { - - const te = this.elements; - const w = 1.0 / ( right - left ); - const h = 1.0 / ( top - bottom ); - const p = 1.0 / ( far - near ); - - const x = ( right + left ) * w; - const y = ( top + bottom ) * h; - const z = ( far + near ) * p; - - te[ 0 ] = 2 * w; te[ 4 ] = 0; te[ 8 ] = 0; te[ 12 ] = - x; - te[ 1 ] = 0; te[ 5 ] = 2 * h; te[ 9 ] = 0; te[ 13 ] = - y; - te[ 2 ] = 0; te[ 6 ] = 0; te[ 10 ] = - 2 * p; te[ 14 ] = - z; - te[ 3 ] = 0; te[ 7 ] = 0; te[ 11 ] = 0; te[ 15 ] = 1; - - return this; - - } - - equals( matrix ) { - - const te = this.elements; - const me = matrix.elements; - - for ( let i = 0; i < 16; i ++ ) { - - if ( te[ i ] !== me[ i ] ) return false; - - } - - return true; - - } - - fromArray( array, offset = 0 ) { - - for ( let i = 0; i < 16; i ++ ) { - - this.elements[ i ] = array[ i + offset ]; - - } - - return this; - - } - - toArray( array = [], offset = 0 ) { - - const te = this.elements; - - array[ offset ] = te[ 0 ]; - array[ offset + 1 ] = te[ 1 ]; - array[ offset + 2 ] = te[ 2 ]; - array[ offset + 3 ] = te[ 3 ]; - - array[ offset + 4 ] = te[ 4 ]; - array[ offset + 5 ] = te[ 5 ]; - array[ offset + 6 ] = te[ 6 ]; - array[ offset + 7 ] = te[ 7 ]; - - array[ offset + 8 ] = te[ 8 ]; - array[ offset + 9 ] = te[ 9 ]; - array[ offset + 10 ] = te[ 10 ]; - array[ offset + 11 ] = te[ 11 ]; - - array[ offset + 12 ] = te[ 12 ]; - array[ offset + 13 ] = te[ 13 ]; - array[ offset + 14 ] = te[ 14 ]; - array[ offset + 15 ] = te[ 15 ]; - - return array; - - } - -} - -const _v1$5 = /*@__PURE__*/ new Vector3(); -const _m1$2 = /*@__PURE__*/ new Matrix4(); -const _zero = /*@__PURE__*/ new Vector3( 0, 0, 0 ); -const _one = /*@__PURE__*/ new Vector3( 1, 1, 1 ); -const _x = /*@__PURE__*/ new Vector3(); -const _y = /*@__PURE__*/ new Vector3(); -const _z = /*@__PURE__*/ new Vector3(); - -const _matrix = /*@__PURE__*/ new Matrix4(); -const _quaternion$3 = /*@__PURE__*/ new Quaternion(); - -class Euler { - - constructor( x = 0, y = 0, z = 0, order = Euler.DEFAULT_ORDER ) { - - this.isEuler = true; - - this._x = x; - this._y = y; - this._z = z; - this._order = order; - - } - - get x() { - - return this._x; - - } - - set x( value ) { - - this._x = value; - this._onChangeCallback(); - - } - - get y() { - - return this._y; - - } - - set y( value ) { - - this._y = value; - this._onChangeCallback(); - - } - - get z() { - - return this._z; - - } - - set z( value ) { - - this._z = value; - this._onChangeCallback(); - - } - - get order() { - - return this._order; - - } - - set order( value ) { - - this._order = value; - this._onChangeCallback(); - - } - - set( x, y, z, order = this._order ) { - - this._x = x; - this._y = y; - this._z = z; - this._order = order; - - this._onChangeCallback(); - - return this; - - } - - clone() { - - return new this.constructor( this._x, this._y, this._z, this._order ); - - } - - copy( euler ) { - - this._x = euler._x; - this._y = euler._y; - this._z = euler._z; - this._order = euler._order; - - this._onChangeCallback(); - - return this; - - } - - setFromRotationMatrix( m, order = this._order, update = true ) { - - // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) - - const te = m.elements; - const m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ]; - const m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ]; - const m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ]; - - switch ( order ) { - - case 'XYZ': - - this._y = Math.asin( clamp( m13, - 1, 1 ) ); - - if ( Math.abs( m13 ) < 0.9999999 ) { - - this._x = Math.atan2( - m23, m33 ); - this._z = Math.atan2( - m12, m11 ); - - } else { - - this._x = Math.atan2( m32, m22 ); - this._z = 0; - - } - - break; - - case 'YXZ': - - this._x = Math.asin( - clamp( m23, - 1, 1 ) ); - - if ( Math.abs( m23 ) < 0.9999999 ) { - - this._y = Math.atan2( m13, m33 ); - this._z = Math.atan2( m21, m22 ); - - } else { - - this._y = Math.atan2( - m31, m11 ); - this._z = 0; - - } - - break; - - case 'ZXY': - - this._x = Math.asin( clamp( m32, - 1, 1 ) ); - - if ( Math.abs( m32 ) < 0.9999999 ) { - - this._y = Math.atan2( - m31, m33 ); - this._z = Math.atan2( - m12, m22 ); - - } else { - - this._y = 0; - this._z = Math.atan2( m21, m11 ); - - } - - break; - - case 'ZYX': - - this._y = Math.asin( - clamp( m31, - 1, 1 ) ); - - if ( Math.abs( m31 ) < 0.9999999 ) { - - this._x = Math.atan2( m32, m33 ); - this._z = Math.atan2( m21, m11 ); - - } else { - - this._x = 0; - this._z = Math.atan2( - m12, m22 ); - - } - - break; - - case 'YZX': - - this._z = Math.asin( clamp( m21, - 1, 1 ) ); - - if ( Math.abs( m21 ) < 0.9999999 ) { - - this._x = Math.atan2( - m23, m22 ); - this._y = Math.atan2( - m31, m11 ); - - } else { - - this._x = 0; - this._y = Math.atan2( m13, m33 ); - - } - - break; - - case 'XZY': - - this._z = Math.asin( - clamp( m12, - 1, 1 ) ); - - if ( Math.abs( m12 ) < 0.9999999 ) { - - this._x = Math.atan2( m32, m22 ); - this._y = Math.atan2( m13, m11 ); - - } else { - - this._x = Math.atan2( - m23, m33 ); - this._y = 0; - - } - - break; - - default: - - console.warn( 'THREE.Euler: .setFromRotationMatrix() encountered an unknown order: ' + order ); - - } - - this._order = order; - - if ( update === true ) this._onChangeCallback(); - - return this; - - } - - setFromQuaternion( q, order, update ) { - - _matrix.makeRotationFromQuaternion( q ); - - return this.setFromRotationMatrix( _matrix, order, update ); - - } - - setFromVector3( v, order = this._order ) { - - return this.set( v.x, v.y, v.z, order ); - - } - - reorder( newOrder ) { - - // WARNING: this discards revolution information -bhouston - - _quaternion$3.setFromEuler( this ); - - return this.setFromQuaternion( _quaternion$3, newOrder ); - - } - - equals( euler ) { - - return ( euler._x === this._x ) && ( euler._y === this._y ) && ( euler._z === this._z ) && ( euler._order === this._order ); - - } - - fromArray( array ) { - - this._x = array[ 0 ]; - this._y = array[ 1 ]; - this._z = array[ 2 ]; - if ( array[ 3 ] !== undefined ) this._order = array[ 3 ]; - - this._onChangeCallback(); - - return this; - - } - - toArray( array = [], offset = 0 ) { - - array[ offset ] = this._x; - array[ offset + 1 ] = this._y; - array[ offset + 2 ] = this._z; - array[ offset + 3 ] = this._order; - - return array; - - } - - _onChange( callback ) { - - this._onChangeCallback = callback; - - return this; - - } - - _onChangeCallback() {} - - *[ Symbol.iterator ]() { - - yield this._x; - yield this._y; - yield this._z; - yield this._order; - - } - -} - -Euler.DEFAULT_ORDER = 'XYZ'; - -class Layers { - - constructor() { - - this.mask = 1 | 0; - - } - - set( channel ) { - - this.mask = ( 1 << channel | 0 ) >>> 0; - - } - - enable( channel ) { - - this.mask |= 1 << channel | 0; - - } - - enableAll() { - - this.mask = 0xffffffff | 0; - - } - - toggle( channel ) { - - this.mask ^= 1 << channel | 0; - - } - - disable( channel ) { - - this.mask &= ~ ( 1 << channel | 0 ); - - } - - disableAll() { - - this.mask = 0; - - } - - test( layers ) { - - return ( this.mask & layers.mask ) !== 0; - - } - - isEnabled( channel ) { - - return ( this.mask & ( 1 << channel | 0 ) ) !== 0; - - } - -} - -let _object3DId = 0; - -const _v1$4 = /*@__PURE__*/ new Vector3(); -const _q1 = /*@__PURE__*/ new Quaternion(); -const _m1$1 = /*@__PURE__*/ new Matrix4(); -const _target = /*@__PURE__*/ new Vector3(); - -const _position$3 = /*@__PURE__*/ new Vector3(); -const _scale$2 = /*@__PURE__*/ new Vector3(); -const _quaternion$2 = /*@__PURE__*/ new Quaternion(); - -const _xAxis = /*@__PURE__*/ new Vector3( 1, 0, 0 ); -const _yAxis = /*@__PURE__*/ new Vector3( 0, 1, 0 ); -const _zAxis = /*@__PURE__*/ new Vector3( 0, 0, 1 ); - -const _addedEvent = { type: 'added' }; -const _removedEvent = { type: 'removed' }; - -class Object3D extends EventDispatcher { - - constructor() { - - super(); - - this.isObject3D = true; - - Object.defineProperty( this, 'id', { value: _object3DId ++ } ); - - this.uuid = generateUUID(); - - this.name = ''; - this.type = 'Object3D'; - - this.parent = null; - this.children = []; - - this.up = Object3D.DEFAULT_UP.clone(); - - const position = new Vector3(); - const rotation = new Euler(); - const quaternion = new Quaternion(); - const scale = new Vector3( 1, 1, 1 ); - - function onRotationChange() { - - quaternion.setFromEuler( rotation, false ); - - } - - function onQuaternionChange() { - - rotation.setFromQuaternion( quaternion, undefined, false ); - - } - - rotation._onChange( onRotationChange ); - quaternion._onChange( onQuaternionChange ); - - Object.defineProperties( this, { - position: { - configurable: true, - enumerable: true, - value: position - }, - rotation: { - configurable: true, - enumerable: true, - value: rotation - }, - quaternion: { - configurable: true, - enumerable: true, - value: quaternion - }, - scale: { - configurable: true, - enumerable: true, - value: scale - }, - modelViewMatrix: { - value: new Matrix4() - }, - normalMatrix: { - value: new Matrix3() - } - } ); - - this.matrix = new Matrix4(); - this.matrixWorld = new Matrix4(); - - this.matrixAutoUpdate = Object3D.DEFAULT_MATRIX_AUTO_UPDATE; - this.matrixWorldNeedsUpdate = false; - - this.matrixWorldAutoUpdate = Object3D.DEFAULT_MATRIX_WORLD_AUTO_UPDATE; // checked by the renderer - - this.layers = new Layers(); - this.visible = true; - - this.castShadow = false; - this.receiveShadow = false; - - this.frustumCulled = true; - this.renderOrder = 0; - - this.animations = []; - - this.userData = {}; - - } - - onBeforeRender( /* renderer, scene, camera, geometry, material, group */ ) {} - - onAfterRender( /* renderer, scene, camera, geometry, material, group */ ) {} - - applyMatrix4( matrix ) { - - if ( this.matrixAutoUpdate ) this.updateMatrix(); - - this.matrix.premultiply( matrix ); - - this.matrix.decompose( this.position, this.quaternion, this.scale ); - - } - - applyQuaternion( q ) { - - this.quaternion.premultiply( q ); - - return this; - - } - - setRotationFromAxisAngle( axis, angle ) { - - // assumes axis is normalized - - this.quaternion.setFromAxisAngle( axis, angle ); - - } - - setRotationFromEuler( euler ) { - - this.quaternion.setFromEuler( euler, true ); - - } - - setRotationFromMatrix( m ) { - - // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) - - this.quaternion.setFromRotationMatrix( m ); - - } - - setRotationFromQuaternion( q ) { - - // assumes q is normalized - - this.quaternion.copy( q ); - - } - - rotateOnAxis( axis, angle ) { - - // rotate object on axis in object space - // axis is assumed to be normalized - - _q1.setFromAxisAngle( axis, angle ); - - this.quaternion.multiply( _q1 ); - - return this; - - } - - rotateOnWorldAxis( axis, angle ) { - - // rotate object on axis in world space - // axis is assumed to be normalized - // method assumes no rotated parent - - _q1.setFromAxisAngle( axis, angle ); - - this.quaternion.premultiply( _q1 ); - - return this; - - } - - rotateX( angle ) { - - return this.rotateOnAxis( _xAxis, angle ); - - } - - rotateY( angle ) { - - return this.rotateOnAxis( _yAxis, angle ); - - } - - rotateZ( angle ) { - - return this.rotateOnAxis( _zAxis, angle ); - - } - - translateOnAxis( axis, distance ) { - - // translate object by distance along axis in object space - // axis is assumed to be normalized - - _v1$4.copy( axis ).applyQuaternion( this.quaternion ); - - this.position.add( _v1$4.multiplyScalar( distance ) ); - - return this; - - } - - translateX( distance ) { - - return this.translateOnAxis( _xAxis, distance ); - - } - - translateY( distance ) { - - return this.translateOnAxis( _yAxis, distance ); - - } - - translateZ( distance ) { - - return this.translateOnAxis( _zAxis, distance ); - - } - - localToWorld( vector ) { - - this.updateWorldMatrix( true, false ); - - return vector.applyMatrix4( this.matrixWorld ); - - } - - worldToLocal( vector ) { - - this.updateWorldMatrix( true, false ); - - return vector.applyMatrix4( _m1$1.copy( this.matrixWorld ).invert() ); - - } - - lookAt( x, y, z ) { - - // This method does not support objects having non-uniformly-scaled parent(s) - - if ( x.isVector3 ) { - - _target.copy( x ); - - } else { - - _target.set( x, y, z ); - - } - - const parent = this.parent; - - this.updateWorldMatrix( true, false ); - - _position$3.setFromMatrixPosition( this.matrixWorld ); - - if ( this.isCamera || this.isLight ) { - - _m1$1.lookAt( _position$3, _target, this.up ); - - } else { - - _m1$1.lookAt( _target, _position$3, this.up ); - - } - - this.quaternion.setFromRotationMatrix( _m1$1 ); - - if ( parent ) { - - _m1$1.extractRotation( parent.matrixWorld ); - _q1.setFromRotationMatrix( _m1$1 ); - this.quaternion.premultiply( _q1.invert() ); - - } - - } - - add( object ) { - - if ( arguments.length > 1 ) { - - for ( let i = 0; i < arguments.length; i ++ ) { - - this.add( arguments[ i ] ); - - } - - return this; - - } - - if ( object === this ) { - - console.error( 'THREE.Object3D.add: object can\'t be added as a child of itself.', object ); - return this; - - } - - if ( object && object.isObject3D ) { - - if ( object.parent !== null ) { - - object.parent.remove( object ); - - } - - object.parent = this; - this.children.push( object ); - - object.dispatchEvent( _addedEvent ); - - } else { - - console.error( 'THREE.Object3D.add: object not an instance of THREE.Object3D.', object ); - - } - - return this; - - } - - remove( object ) { - - if ( arguments.length > 1 ) { - - for ( let i = 0; i < arguments.length; i ++ ) { - - this.remove( arguments[ i ] ); - - } - - return this; - - } - - const index = this.children.indexOf( object ); - - if ( index !== - 1 ) { - - object.parent = null; - this.children.splice( index, 1 ); - - object.dispatchEvent( _removedEvent ); - - } - - return this; - - } - - removeFromParent() { - - const parent = this.parent; - - if ( parent !== null ) { - - parent.remove( this ); - - } - - return this; - - } - - clear() { - - for ( let i = 0; i < this.children.length; i ++ ) { - - const object = this.children[ i ]; - - object.parent = null; - - object.dispatchEvent( _removedEvent ); - - } - - this.children.length = 0; - - return this; - - - } - - attach( object ) { - - // adds object as a child of this, while maintaining the object's world transform - - // Note: This method does not support scene graphs having non-uniformly-scaled nodes(s) - - this.updateWorldMatrix( true, false ); - - _m1$1.copy( this.matrixWorld ).invert(); - - if ( object.parent !== null ) { - - object.parent.updateWorldMatrix( true, false ); - - _m1$1.multiply( object.parent.matrixWorld ); - - } - - object.applyMatrix4( _m1$1 ); - - this.add( object ); - - object.updateWorldMatrix( false, true ); - - return this; - - } - - getObjectById( id ) { - - return this.getObjectByProperty( 'id', id ); - - } - - getObjectByName( name ) { - - return this.getObjectByProperty( 'name', name ); - - } - - getObjectByProperty( name, value ) { - - if ( this[ name ] === value ) return this; - - for ( let i = 0, l = this.children.length; i < l; i ++ ) { - - const child = this.children[ i ]; - const object = child.getObjectByProperty( name, value ); - - if ( object !== undefined ) { - - return object; - - } - - } - - return undefined; - - } - - getObjectsByProperty( name, value ) { - - let result = []; - - if ( this[ name ] === value ) result.push( this ); - - for ( let i = 0, l = this.children.length; i < l; i ++ ) { - - const childResult = this.children[ i ].getObjectsByProperty( name, value ); - - if ( childResult.length > 0 ) { - - result = result.concat( childResult ); - - } - - } - - return result; - - } - - getWorldPosition( target ) { - - this.updateWorldMatrix( true, false ); - - return target.setFromMatrixPosition( this.matrixWorld ); - - } - - getWorldQuaternion( target ) { - - this.updateWorldMatrix( true, false ); - - this.matrixWorld.decompose( _position$3, target, _scale$2 ); - - return target; - - } - - getWorldScale( target ) { - - this.updateWorldMatrix( true, false ); - - this.matrixWorld.decompose( _position$3, _quaternion$2, target ); - - return target; - - } - - getWorldDirection( target ) { - - this.updateWorldMatrix( true, false ); - - const e = this.matrixWorld.elements; - - return target.set( e[ 8 ], e[ 9 ], e[ 10 ] ).normalize(); - - } - - raycast( /* raycaster, intersects */ ) {} - - traverse( callback ) { - - callback( this ); - - const children = this.children; - - for ( let i = 0, l = children.length; i < l; i ++ ) { - - children[ i ].traverse( callback ); - - } - - } - - traverseVisible( callback ) { - - if ( this.visible === false ) return; - - callback( this ); - - const children = this.children; - - for ( let i = 0, l = children.length; i < l; i ++ ) { - - children[ i ].traverseVisible( callback ); - - } - - } - - traverseAncestors( callback ) { - - const parent = this.parent; - - if ( parent !== null ) { - - callback( parent ); - - parent.traverseAncestors( callback ); - - } - - } - - updateMatrix() { - - this.matrix.compose( this.position, this.quaternion, this.scale ); - - this.matrixWorldNeedsUpdate = true; - - } - - updateMatrixWorld( force ) { - - if ( this.matrixAutoUpdate ) this.updateMatrix(); - - if ( this.matrixWorldNeedsUpdate || force ) { - - if ( this.parent === null ) { - - this.matrixWorld.copy( this.matrix ); - - } else { - - this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix ); - - } - - this.matrixWorldNeedsUpdate = false; - - force = true; - - } - - // update children - - const children = this.children; - - for ( let i = 0, l = children.length; i < l; i ++ ) { - - const child = children[ i ]; - - if ( child.matrixWorldAutoUpdate === true || force === true ) { - - child.updateMatrixWorld( force ); - - } - - } - - } - - updateWorldMatrix( updateParents, updateChildren ) { - - const parent = this.parent; - - if ( updateParents === true && parent !== null && parent.matrixWorldAutoUpdate === true ) { - - parent.updateWorldMatrix( true, false ); - - } - - if ( this.matrixAutoUpdate ) this.updateMatrix(); - - if ( this.parent === null ) { - - this.matrixWorld.copy( this.matrix ); - - } else { - - this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix ); - - } - - // update children - - if ( updateChildren === true ) { - - const children = this.children; - - for ( let i = 0, l = children.length; i < l; i ++ ) { - - const child = children[ i ]; - - if ( child.matrixWorldAutoUpdate === true ) { - - child.updateWorldMatrix( false, true ); - - } - - } - - } - - } - - toJSON( meta ) { - - // meta is a string when called from JSON.stringify - const isRootObject = ( meta === undefined || typeof meta === 'string' ); - - const output = {}; - - // meta is a hash used to collect geometries, materials. - // not providing it implies that this is the root object - // being serialized. - if ( isRootObject ) { - - // initialize meta obj - meta = { - geometries: {}, - materials: {}, - textures: {}, - images: {}, - shapes: {}, - skeletons: {}, - animations: {}, - nodes: {} - }; - - output.metadata = { - version: 4.6, - type: 'Object', - generator: 'Object3D.toJSON' - }; - - } - - // standard Object3D serialization - - const object = {}; - - object.uuid = this.uuid; - object.type = this.type; - - if ( this.name !== '' ) object.name = this.name; - if ( this.castShadow === true ) object.castShadow = true; - if ( this.receiveShadow === true ) object.receiveShadow = true; - if ( this.visible === false ) object.visible = false; - if ( this.frustumCulled === false ) object.frustumCulled = false; - if ( this.renderOrder !== 0 ) object.renderOrder = this.renderOrder; - if ( Object.keys( this.userData ).length > 0 ) object.userData = this.userData; - - object.layers = this.layers.mask; - object.matrix = this.matrix.toArray(); - object.up = this.up.toArray(); - - if ( this.matrixAutoUpdate === false ) object.matrixAutoUpdate = false; - - // object specific properties - - if ( this.isInstancedMesh ) { - - object.type = 'InstancedMesh'; - object.count = this.count; - object.instanceMatrix = this.instanceMatrix.toJSON(); - if ( this.instanceColor !== null ) object.instanceColor = this.instanceColor.toJSON(); - - } - - // - - function serialize( library, element ) { - - if ( library[ element.uuid ] === undefined ) { - - library[ element.uuid ] = element.toJSON( meta ); - - } - - return element.uuid; - - } - - if ( this.isScene ) { - - if ( this.background ) { - - if ( this.background.isColor ) { - - object.background = this.background.toJSON(); - - } else if ( this.background.isTexture ) { - - object.background = this.background.toJSON( meta ).uuid; - - } - - } - - if ( this.environment && this.environment.isTexture && this.environment.isRenderTargetTexture !== true ) { - - object.environment = this.environment.toJSON( meta ).uuid; - - } - - } else if ( this.isMesh || this.isLine || this.isPoints ) { - - object.geometry = serialize( meta.geometries, this.geometry ); - - const parameters = this.geometry.parameters; - - if ( parameters !== undefined && parameters.shapes !== undefined ) { - - const shapes = parameters.shapes; - - if ( Array.isArray( shapes ) ) { - - for ( let i = 0, l = shapes.length; i < l; i ++ ) { - - const shape = shapes[ i ]; - - serialize( meta.shapes, shape ); - - } - - } else { - - serialize( meta.shapes, shapes ); - - } - - } - - } - - if ( this.isSkinnedMesh ) { - - object.bindMode = this.bindMode; - object.bindMatrix = this.bindMatrix.toArray(); - - if ( this.skeleton !== undefined ) { - - serialize( meta.skeletons, this.skeleton ); - - object.skeleton = this.skeleton.uuid; - - } - - } - - if ( this.material !== undefined ) { - - if ( Array.isArray( this.material ) ) { - - const uuids = []; - - for ( let i = 0, l = this.material.length; i < l; i ++ ) { - - uuids.push( serialize( meta.materials, this.material[ i ] ) ); - - } - - object.material = uuids; - - } else { - - object.material = serialize( meta.materials, this.material ); - - } - - } - - // - - if ( this.children.length > 0 ) { - - object.children = []; - - for ( let i = 0; i < this.children.length; i ++ ) { - - object.children.push( this.children[ i ].toJSON( meta ).object ); - - } - - } - - // - - if ( this.animations.length > 0 ) { - - object.animations = []; - - for ( let i = 0; i < this.animations.length; i ++ ) { - - const animation = this.animations[ i ]; - - object.animations.push( serialize( meta.animations, animation ) ); - - } - - } - - if ( isRootObject ) { - - const geometries = extractFromCache( meta.geometries ); - const materials = extractFromCache( meta.materials ); - const textures = extractFromCache( meta.textures ); - const images = extractFromCache( meta.images ); - const shapes = extractFromCache( meta.shapes ); - const skeletons = extractFromCache( meta.skeletons ); - const animations = extractFromCache( meta.animations ); - const nodes = extractFromCache( meta.nodes ); - - if ( geometries.length > 0 ) output.geometries = geometries; - if ( materials.length > 0 ) output.materials = materials; - if ( textures.length > 0 ) output.textures = textures; - if ( images.length > 0 ) output.images = images; - if ( shapes.length > 0 ) output.shapes = shapes; - if ( skeletons.length > 0 ) output.skeletons = skeletons; - if ( animations.length > 0 ) output.animations = animations; - if ( nodes.length > 0 ) output.nodes = nodes; - - } - - output.object = object; - - return output; - - // extract data from the cache hash - // remove metadata on each item - // and return as array - function extractFromCache( cache ) { - - const values = []; - for ( const key in cache ) { - - const data = cache[ key ]; - delete data.metadata; - values.push( data ); - - } - - return values; - - } - - } - - clone( recursive ) { - - return new this.constructor().copy( this, recursive ); - - } - - copy( source, recursive = true ) { - - this.name = source.name; - - this.up.copy( source.up ); - - this.position.copy( source.position ); - this.rotation.order = source.rotation.order; - this.quaternion.copy( source.quaternion ); - this.scale.copy( source.scale ); - - this.matrix.copy( source.matrix ); - this.matrixWorld.copy( source.matrixWorld ); - - this.matrixAutoUpdate = source.matrixAutoUpdate; - this.matrixWorldNeedsUpdate = source.matrixWorldNeedsUpdate; - - this.matrixWorldAutoUpdate = source.matrixWorldAutoUpdate; - - this.layers.mask = source.layers.mask; - this.visible = source.visible; - - this.castShadow = source.castShadow; - this.receiveShadow = source.receiveShadow; - - this.frustumCulled = source.frustumCulled; - this.renderOrder = source.renderOrder; - - this.animations = source.animations; - - this.userData = JSON.parse( JSON.stringify( source.userData ) ); - - if ( recursive === true ) { - - for ( let i = 0; i < source.children.length; i ++ ) { - - const child = source.children[ i ]; - this.add( child.clone() ); - - } - - } - - return this; - - } - -} - -Object3D.DEFAULT_UP = /*@__PURE__*/ new Vector3( 0, 1, 0 ); -Object3D.DEFAULT_MATRIX_AUTO_UPDATE = true; -Object3D.DEFAULT_MATRIX_WORLD_AUTO_UPDATE = true; - -const _v0$1 = /*@__PURE__*/ new Vector3(); -const _v1$3 = /*@__PURE__*/ new Vector3(); -const _v2$2 = /*@__PURE__*/ new Vector3(); -const _v3$1 = /*@__PURE__*/ new Vector3(); - -const _vab = /*@__PURE__*/ new Vector3(); -const _vac = /*@__PURE__*/ new Vector3(); -const _vbc = /*@__PURE__*/ new Vector3(); -const _vap = /*@__PURE__*/ new Vector3(); -const _vbp = /*@__PURE__*/ new Vector3(); -const _vcp = /*@__PURE__*/ new Vector3(); - -let warnedGetUV = false; - -class Triangle { - - constructor( a = new Vector3(), b = new Vector3(), c = new Vector3() ) { - - this.a = a; - this.b = b; - this.c = c; - - } - - static getNormal( a, b, c, target ) { - - target.subVectors( c, b ); - _v0$1.subVectors( a, b ); - target.cross( _v0$1 ); - - const targetLengthSq = target.lengthSq(); - if ( targetLengthSq > 0 ) { - - return target.multiplyScalar( 1 / Math.sqrt( targetLengthSq ) ); - - } - - return target.set( 0, 0, 0 ); - - } - - // static/instance method to calculate barycentric coordinates - // based on: http://www.blackpawn.com/texts/pointinpoly/default.html - static getBarycoord( point, a, b, c, target ) { - - _v0$1.subVectors( c, a ); - _v1$3.subVectors( b, a ); - _v2$2.subVectors( point, a ); - - const dot00 = _v0$1.dot( _v0$1 ); - const dot01 = _v0$1.dot( _v1$3 ); - const dot02 = _v0$1.dot( _v2$2 ); - const dot11 = _v1$3.dot( _v1$3 ); - const dot12 = _v1$3.dot( _v2$2 ); - - const denom = ( dot00 * dot11 - dot01 * dot01 ); - - // collinear or singular triangle - if ( denom === 0 ) { - - // arbitrary location outside of triangle? - // not sure if this is the best idea, maybe should be returning undefined - return target.set( - 2, - 1, - 1 ); - - } - - const invDenom = 1 / denom; - const u = ( dot11 * dot02 - dot01 * dot12 ) * invDenom; - const v = ( dot00 * dot12 - dot01 * dot02 ) * invDenom; - - // barycentric coordinates must always sum to 1 - return target.set( 1 - u - v, v, u ); - - } - - static containsPoint( point, a, b, c ) { - - this.getBarycoord( point, a, b, c, _v3$1 ); - - return ( _v3$1.x >= 0 ) && ( _v3$1.y >= 0 ) && ( ( _v3$1.x + _v3$1.y ) <= 1 ); - - } - - static getUV( point, p1, p2, p3, uv1, uv2, uv3, target ) { // @deprecated, r151 - - if ( warnedGetUV === false ) { - - console.warn( 'THREE.Triangle.getUV() has been renamed to THREE.Triangle.getInterpolation().' ); - - warnedGetUV = true; - - } - - return this.getInterpolation( point, p1, p2, p3, uv1, uv2, uv3, target ); - - } - - static getInterpolation( point, p1, p2, p3, v1, v2, v3, target ) { - - this.getBarycoord( point, p1, p2, p3, _v3$1 ); - - target.setScalar( 0 ); - target.addScaledVector( v1, _v3$1.x ); - target.addScaledVector( v2, _v3$1.y ); - target.addScaledVector( v3, _v3$1.z ); - - return target; - - } - - static isFrontFacing( a, b, c, direction ) { - - _v0$1.subVectors( c, b ); - _v1$3.subVectors( a, b ); - - // strictly front facing - return ( _v0$1.cross( _v1$3 ).dot( direction ) < 0 ) ? true : false; - - } - - set( a, b, c ) { - - this.a.copy( a ); - this.b.copy( b ); - this.c.copy( c ); - - return this; - - } - - setFromPointsAndIndices( points, i0, i1, i2 ) { - - this.a.copy( points[ i0 ] ); - this.b.copy( points[ i1 ] ); - this.c.copy( points[ i2 ] ); - - return this; - - } - - setFromAttributeAndIndices( attribute, i0, i1, i2 ) { - - this.a.fromBufferAttribute( attribute, i0 ); - this.b.fromBufferAttribute( attribute, i1 ); - this.c.fromBufferAttribute( attribute, i2 ); - - return this; - - } - - clone() { - - return new this.constructor().copy( this ); - - } - - copy( triangle ) { - - this.a.copy( triangle.a ); - this.b.copy( triangle.b ); - this.c.copy( triangle.c ); - - return this; - - } - - getArea() { - - _v0$1.subVectors( this.c, this.b ); - _v1$3.subVectors( this.a, this.b ); - - return _v0$1.cross( _v1$3 ).length() * 0.5; - - } - - getMidpoint( target ) { - - return target.addVectors( this.a, this.b ).add( this.c ).multiplyScalar( 1 / 3 ); - - } - - getNormal( target ) { - - return Triangle.getNormal( this.a, this.b, this.c, target ); - - } - - getPlane( target ) { - - return target.setFromCoplanarPoints( this.a, this.b, this.c ); - - } - - getBarycoord( point, target ) { - - return Triangle.getBarycoord( point, this.a, this.b, this.c, target ); - - } - - getUV( point, uv1, uv2, uv3, target ) { // @deprecated, r151 - - if ( warnedGetUV === false ) { - - console.warn( 'THREE.Triangle.getUV() has been renamed to THREE.Triangle.getInterpolation().' ); - - warnedGetUV = true; - - } - - return Triangle.getInterpolation( point, this.a, this.b, this.c, uv1, uv2, uv3, target ); - - } - - getInterpolation( point, v1, v2, v3, target ) { - - return Triangle.getInterpolation( point, this.a, this.b, this.c, v1, v2, v3, target ); - - } - - containsPoint( point ) { - - return Triangle.containsPoint( point, this.a, this.b, this.c ); - - } - - isFrontFacing( direction ) { - - return Triangle.isFrontFacing( this.a, this.b, this.c, direction ); - - } - - intersectsBox( box ) { - - return box.intersectsTriangle( this ); - - } - - closestPointToPoint( p, target ) { - - const a = this.a, b = this.b, c = this.c; - let v, w; - - // algorithm thanks to Real-Time Collision Detection by Christer Ericson, - // published by Morgan Kaufmann Publishers, (c) 2005 Elsevier Inc., - // under the accompanying license; see chapter 5.1.5 for detailed explanation. - // basically, we're distinguishing which of the voronoi regions of the triangle - // the point lies in with the minimum amount of redundant computation. - - _vab.subVectors( b, a ); - _vac.subVectors( c, a ); - _vap.subVectors( p, a ); - const d1 = _vab.dot( _vap ); - const d2 = _vac.dot( _vap ); - if ( d1 <= 0 && d2 <= 0 ) { - - // vertex region of A; barycentric coords (1, 0, 0) - return target.copy( a ); - - } - - _vbp.subVectors( p, b ); - const d3 = _vab.dot( _vbp ); - const d4 = _vac.dot( _vbp ); - if ( d3 >= 0 && d4 <= d3 ) { - - // vertex region of B; barycentric coords (0, 1, 0) - return target.copy( b ); - - } - - const vc = d1 * d4 - d3 * d2; - if ( vc <= 0 && d1 >= 0 && d3 <= 0 ) { - - v = d1 / ( d1 - d3 ); - // edge region of AB; barycentric coords (1-v, v, 0) - return target.copy( a ).addScaledVector( _vab, v ); - - } - - _vcp.subVectors( p, c ); - const d5 = _vab.dot( _vcp ); - const d6 = _vac.dot( _vcp ); - if ( d6 >= 0 && d5 <= d6 ) { - - // vertex region of C; barycentric coords (0, 0, 1) - return target.copy( c ); - - } - - const vb = d5 * d2 - d1 * d6; - if ( vb <= 0 && d2 >= 0 && d6 <= 0 ) { - - w = d2 / ( d2 - d6 ); - // edge region of AC; barycentric coords (1-w, 0, w) - return target.copy( a ).addScaledVector( _vac, w ); - - } - - const va = d3 * d6 - d5 * d4; - if ( va <= 0 && ( d4 - d3 ) >= 0 && ( d5 - d6 ) >= 0 ) { - - _vbc.subVectors( c, b ); - w = ( d4 - d3 ) / ( ( d4 - d3 ) + ( d5 - d6 ) ); - // edge region of BC; barycentric coords (0, 1-w, w) - return target.copy( b ).addScaledVector( _vbc, w ); // edge region of BC - - } - - // face region - const denom = 1 / ( va + vb + vc ); - // u = va * denom - v = vb * denom; - w = vc * denom; - - return target.copy( a ).addScaledVector( _vab, v ).addScaledVector( _vac, w ); - - } - - equals( triangle ) { - - return triangle.a.equals( this.a ) && triangle.b.equals( this.b ) && triangle.c.equals( this.c ); - - } - -} - -let materialId = 0; - -class Material extends EventDispatcher { - - constructor() { - - super(); - - this.isMaterial = true; - - Object.defineProperty( this, 'id', { value: materialId ++ } ); - - this.uuid = generateUUID(); - - this.name = ''; - this.type = 'Material'; - - this.blending = NormalBlending; - this.side = FrontSide; - this.vertexColors = false; - - this.opacity = 1; - this.transparent = false; - - this.blendSrc = SrcAlphaFactor; - this.blendDst = OneMinusSrcAlphaFactor; - this.blendEquation = AddEquation; - this.blendSrcAlpha = null; - this.blendDstAlpha = null; - this.blendEquationAlpha = null; - - this.depthFunc = LessEqualDepth; - this.depthTest = true; - this.depthWrite = true; - - this.stencilWriteMask = 0xff; - this.stencilFunc = AlwaysStencilFunc; - this.stencilRef = 0; - this.stencilFuncMask = 0xff; - this.stencilFail = KeepStencilOp; - this.stencilZFail = KeepStencilOp; - this.stencilZPass = KeepStencilOp; - this.stencilWrite = false; - - this.clippingPlanes = null; - this.clipIntersection = false; - this.clipShadows = false; - - this.shadowSide = null; - - this.colorWrite = true; - - this.precision = null; // override the renderer's default precision for this material - - this.polygonOffset = false; - this.polygonOffsetFactor = 0; - this.polygonOffsetUnits = 0; - - this.dithering = false; - - this.alphaToCoverage = false; - this.premultipliedAlpha = false; - this.forceSinglePass = false; - - this.visible = true; - - this.toneMapped = true; - - this.userData = {}; - - this.version = 0; - - this._alphaTest = 0; - - } - - get alphaTest() { - - return this._alphaTest; - - } - - set alphaTest( value ) { - - if ( this._alphaTest > 0 !== value > 0 ) { - - this.version ++; - - } - - this._alphaTest = value; - - } - - onBuild( /* shaderobject, renderer */ ) {} - - onBeforeRender( /* renderer, scene, camera, geometry, object, group */ ) {} - - onBeforeCompile( /* shaderobject, renderer */ ) {} - - customProgramCacheKey() { - - return this.onBeforeCompile.toString(); - - } - - setValues( values ) { - - if ( values === undefined ) return; - - for ( const key in values ) { - - const newValue = values[ key ]; - - if ( newValue === undefined ) { - - console.warn( `THREE.Material: parameter '${ key }' has value of undefined.` ); - continue; - - } - - const currentValue = this[ key ]; - - if ( currentValue === undefined ) { - - console.warn( `THREE.Material: '${ key }' is not a property of THREE.${ this.type }.` ); - continue; - - } - - if ( currentValue && currentValue.isColor ) { - - currentValue.set( newValue ); - - } else if ( ( currentValue && currentValue.isVector3 ) && ( newValue && newValue.isVector3 ) ) { - - currentValue.copy( newValue ); - - } else { - - this[ key ] = newValue; - - } - - } - - } - - toJSON( meta ) { - - const isRootObject = ( meta === undefined || typeof meta === 'string' ); - - if ( isRootObject ) { - - meta = { - textures: {}, - images: {} - }; - - } - - const data = { - metadata: { - version: 4.6, - type: 'Material', - generator: 'Material.toJSON' - } - }; - - // standard Material serialization - data.uuid = this.uuid; - data.type = this.type; - - if ( this.name !== '' ) data.name = this.name; - - if ( this.color && this.color.isColor ) data.color = this.color.getHex(); - - if ( this.roughness !== undefined ) data.roughness = this.roughness; - if ( this.metalness !== undefined ) data.metalness = this.metalness; - - if ( this.sheen !== undefined ) data.sheen = this.sheen; - if ( this.sheenColor && this.sheenColor.isColor ) data.sheenColor = this.sheenColor.getHex(); - if ( this.sheenRoughness !== undefined ) data.sheenRoughness = this.sheenRoughness; - if ( this.emissive && this.emissive.isColor ) data.emissive = this.emissive.getHex(); - if ( this.emissiveIntensity && this.emissiveIntensity !== 1 ) data.emissiveIntensity = this.emissiveIntensity; - - if ( this.specular && this.specular.isColor ) data.specular = this.specular.getHex(); - if ( this.specularIntensity !== undefined ) data.specularIntensity = this.specularIntensity; - if ( this.specularColor && this.specularColor.isColor ) data.specularColor = this.specularColor.getHex(); - if ( this.shininess !== undefined ) data.shininess = this.shininess; - if ( this.clearcoat !== undefined ) data.clearcoat = this.clearcoat; - if ( this.clearcoatRoughness !== undefined ) data.clearcoatRoughness = this.clearcoatRoughness; - - if ( this.clearcoatMap && this.clearcoatMap.isTexture ) { - - data.clearcoatMap = this.clearcoatMap.toJSON( meta ).uuid; - - } - - if ( this.clearcoatRoughnessMap && this.clearcoatRoughnessMap.isTexture ) { - - data.clearcoatRoughnessMap = this.clearcoatRoughnessMap.toJSON( meta ).uuid; - - } - - if ( this.clearcoatNormalMap && this.clearcoatNormalMap.isTexture ) { - - data.clearcoatNormalMap = this.clearcoatNormalMap.toJSON( meta ).uuid; - data.clearcoatNormalScale = this.clearcoatNormalScale.toArray(); - - } - - if ( this.iridescence !== undefined ) data.iridescence = this.iridescence; - if ( this.iridescenceIOR !== undefined ) data.iridescenceIOR = this.iridescenceIOR; - if ( this.iridescenceThicknessRange !== undefined ) data.iridescenceThicknessRange = this.iridescenceThicknessRange; - - if ( this.iridescenceMap && this.iridescenceMap.isTexture ) { - - data.iridescenceMap = this.iridescenceMap.toJSON( meta ).uuid; - - } - - if ( this.iridescenceThicknessMap && this.iridescenceThicknessMap.isTexture ) { - - data.iridescenceThicknessMap = this.iridescenceThicknessMap.toJSON( meta ).uuid; - - } - - if ( this.anisotropy !== undefined ) data.anisotropy = this.anisotropy; - if ( this.anisotropyRotation !== undefined ) data.anisotropyRotation = this.anisotropyRotation; - - if ( this.anisotropyMap && this.anisotropyMap.isTexture ) { - - data.anisotropyMap = this.anisotropyMap.toJSON( meta ).uuid; - - } - - if ( this.map && this.map.isTexture ) data.map = this.map.toJSON( meta ).uuid; - if ( this.matcap && this.matcap.isTexture ) data.matcap = this.matcap.toJSON( meta ).uuid; - if ( this.alphaMap && this.alphaMap.isTexture ) data.alphaMap = this.alphaMap.toJSON( meta ).uuid; - - if ( this.lightMap && this.lightMap.isTexture ) { - - data.lightMap = this.lightMap.toJSON( meta ).uuid; - data.lightMapIntensity = this.lightMapIntensity; - - } - - if ( this.aoMap && this.aoMap.isTexture ) { - - data.aoMap = this.aoMap.toJSON( meta ).uuid; - data.aoMapIntensity = this.aoMapIntensity; - - } - - if ( this.bumpMap && this.bumpMap.isTexture ) { - - data.bumpMap = this.bumpMap.toJSON( meta ).uuid; - data.bumpScale = this.bumpScale; - - } - - if ( this.normalMap && this.normalMap.isTexture ) { - - data.normalMap = this.normalMap.toJSON( meta ).uuid; - data.normalMapType = this.normalMapType; - data.normalScale = this.normalScale.toArray(); - - } - - if ( this.displacementMap && this.displacementMap.isTexture ) { - - data.displacementMap = this.displacementMap.toJSON( meta ).uuid; - data.displacementScale = this.displacementScale; - data.displacementBias = this.displacementBias; - - } - - if ( this.roughnessMap && this.roughnessMap.isTexture ) data.roughnessMap = this.roughnessMap.toJSON( meta ).uuid; - if ( this.metalnessMap && this.metalnessMap.isTexture ) data.metalnessMap = this.metalnessMap.toJSON( meta ).uuid; - - if ( this.emissiveMap && this.emissiveMap.isTexture ) data.emissiveMap = this.emissiveMap.toJSON( meta ).uuid; - if ( this.specularMap && this.specularMap.isTexture ) data.specularMap = this.specularMap.toJSON( meta ).uuid; - if ( this.specularIntensityMap && this.specularIntensityMap.isTexture ) data.specularIntensityMap = this.specularIntensityMap.toJSON( meta ).uuid; - if ( this.specularColorMap && this.specularColorMap.isTexture ) data.specularColorMap = this.specularColorMap.toJSON( meta ).uuid; - - if ( this.envMap && this.envMap.isTexture ) { - - data.envMap = this.envMap.toJSON( meta ).uuid; - - if ( this.combine !== undefined ) data.combine = this.combine; - - } - - if ( this.envMapIntensity !== undefined ) data.envMapIntensity = this.envMapIntensity; - if ( this.reflectivity !== undefined ) data.reflectivity = this.reflectivity; - if ( this.refractionRatio !== undefined ) data.refractionRatio = this.refractionRatio; - - if ( this.gradientMap && this.gradientMap.isTexture ) { - - data.gradientMap = this.gradientMap.toJSON( meta ).uuid; - - } - - if ( this.transmission !== undefined ) data.transmission = this.transmission; - if ( this.transmissionMap && this.transmissionMap.isTexture ) data.transmissionMap = this.transmissionMap.toJSON( meta ).uuid; - if ( this.thickness !== undefined ) data.thickness = this.thickness; - if ( this.thicknessMap && this.thicknessMap.isTexture ) data.thicknessMap = this.thicknessMap.toJSON( meta ).uuid; - if ( this.attenuationDistance !== undefined && this.attenuationDistance !== Infinity ) data.attenuationDistance = this.attenuationDistance; - if ( this.attenuationColor !== undefined ) data.attenuationColor = this.attenuationColor.getHex(); - - if ( this.size !== undefined ) data.size = this.size; - if ( this.shadowSide !== null ) data.shadowSide = this.shadowSide; - if ( this.sizeAttenuation !== undefined ) data.sizeAttenuation = this.sizeAttenuation; - - if ( this.blending !== NormalBlending ) data.blending = this.blending; - if ( this.side !== FrontSide ) data.side = this.side; - if ( this.vertexColors ) data.vertexColors = true; - - if ( this.opacity < 1 ) data.opacity = this.opacity; - if ( this.transparent === true ) data.transparent = this.transparent; - - data.depthFunc = this.depthFunc; - data.depthTest = this.depthTest; - data.depthWrite = this.depthWrite; - data.colorWrite = this.colorWrite; - - data.stencilWrite = this.stencilWrite; - data.stencilWriteMask = this.stencilWriteMask; - data.stencilFunc = this.stencilFunc; - data.stencilRef = this.stencilRef; - data.stencilFuncMask = this.stencilFuncMask; - data.stencilFail = this.stencilFail; - data.stencilZFail = this.stencilZFail; - data.stencilZPass = this.stencilZPass; - - // rotation (SpriteMaterial) - if ( this.rotation !== undefined && this.rotation !== 0 ) data.rotation = this.rotation; - - if ( this.polygonOffset === true ) data.polygonOffset = true; - if ( this.polygonOffsetFactor !== 0 ) data.polygonOffsetFactor = this.polygonOffsetFactor; - if ( this.polygonOffsetUnits !== 0 ) data.polygonOffsetUnits = this.polygonOffsetUnits; - - if ( this.linewidth !== undefined && this.linewidth !== 1 ) data.linewidth = this.linewidth; - if ( this.dashSize !== undefined ) data.dashSize = this.dashSize; - if ( this.gapSize !== undefined ) data.gapSize = this.gapSize; - if ( this.scale !== undefined ) data.scale = this.scale; - - if ( this.dithering === true ) data.dithering = true; - - if ( this.alphaTest > 0 ) data.alphaTest = this.alphaTest; - if ( this.alphaToCoverage === true ) data.alphaToCoverage = this.alphaToCoverage; - if ( this.premultipliedAlpha === true ) data.premultipliedAlpha = this.premultipliedAlpha; - if ( this.forceSinglePass === true ) data.forceSinglePass = this.forceSinglePass; - - if ( this.wireframe === true ) data.wireframe = this.wireframe; - if ( this.wireframeLinewidth > 1 ) data.wireframeLinewidth = this.wireframeLinewidth; - if ( this.wireframeLinecap !== 'round' ) data.wireframeLinecap = this.wireframeLinecap; - if ( this.wireframeLinejoin !== 'round' ) data.wireframeLinejoin = this.wireframeLinejoin; - - if ( this.flatShading === true ) data.flatShading = this.flatShading; - - if ( this.visible === false ) data.visible = false; - - if ( this.toneMapped === false ) data.toneMapped = false; - - if ( this.fog === false ) data.fog = false; - - if ( Object.keys( this.userData ).length > 0 ) data.userData = this.userData; - - // TODO: Copied from Object3D.toJSON - - function extractFromCache( cache ) { - - const values = []; - - for ( const key in cache ) { - - const data = cache[ key ]; - delete data.metadata; - values.push( data ); - - } - - return values; - - } - - if ( isRootObject ) { - - const textures = extractFromCache( meta.textures ); - const images = extractFromCache( meta.images ); - - if ( textures.length > 0 ) data.textures = textures; - if ( images.length > 0 ) data.images = images; - - } - - return data; - - } - - clone() { - - return new this.constructor().copy( this ); - - } - - copy( source ) { - - this.name = source.name; - - this.blending = source.blending; - this.side = source.side; - this.vertexColors = source.vertexColors; - - this.opacity = source.opacity; - this.transparent = source.transparent; - - this.blendSrc = source.blendSrc; - this.blendDst = source.blendDst; - this.blendEquation = source.blendEquation; - this.blendSrcAlpha = source.blendSrcAlpha; - this.blendDstAlpha = source.blendDstAlpha; - this.blendEquationAlpha = source.blendEquationAlpha; - - this.depthFunc = source.depthFunc; - this.depthTest = source.depthTest; - this.depthWrite = source.depthWrite; - - this.stencilWriteMask = source.stencilWriteMask; - this.stencilFunc = source.stencilFunc; - this.stencilRef = source.stencilRef; - this.stencilFuncMask = source.stencilFuncMask; - this.stencilFail = source.stencilFail; - this.stencilZFail = source.stencilZFail; - this.stencilZPass = source.stencilZPass; - this.stencilWrite = source.stencilWrite; - - const srcPlanes = source.clippingPlanes; - let dstPlanes = null; - - if ( srcPlanes !== null ) { - - const n = srcPlanes.length; - dstPlanes = new Array( n ); - - for ( let i = 0; i !== n; ++ i ) { - - dstPlanes[ i ] = srcPlanes[ i ].clone(); - - } - - } - - this.clippingPlanes = dstPlanes; - this.clipIntersection = source.clipIntersection; - this.clipShadows = source.clipShadows; - - this.shadowSide = source.shadowSide; - - this.colorWrite = source.colorWrite; - - this.precision = source.precision; - - this.polygonOffset = source.polygonOffset; - this.polygonOffsetFactor = source.polygonOffsetFactor; - this.polygonOffsetUnits = source.polygonOffsetUnits; - - this.dithering = source.dithering; - - this.alphaTest = source.alphaTest; - this.alphaToCoverage = source.alphaToCoverage; - this.premultipliedAlpha = source.premultipliedAlpha; - this.forceSinglePass = source.forceSinglePass; - - this.visible = source.visible; - - this.toneMapped = source.toneMapped; - - this.userData = JSON.parse( JSON.stringify( source.userData ) ); - - return this; - - } - - dispose() { - - this.dispatchEvent( { type: 'dispose' } ); - - } - - set needsUpdate( value ) { - - if ( value === true ) this.version ++; - - } - -} - -const _colorKeywords = { 'aliceblue': 0xF0F8FF, 'antiquewhite': 0xFAEBD7, 'aqua': 0x00FFFF, 'aquamarine': 0x7FFFD4, 'azure': 0xF0FFFF, - 'beige': 0xF5F5DC, 'bisque': 0xFFE4C4, 'black': 0x000000, 'blanchedalmond': 0xFFEBCD, 'blue': 0x0000FF, 'blueviolet': 0x8A2BE2, - 'brown': 0xA52A2A, 'burlywood': 0xDEB887, 'cadetblue': 0x5F9EA0, 'chartreuse': 0x7FFF00, 'chocolate': 0xD2691E, 'coral': 0xFF7F50, - 'cornflowerblue': 0x6495ED, 'cornsilk': 0xFFF8DC, 'crimson': 0xDC143C, 'cyan': 0x00FFFF, 'darkblue': 0x00008B, 'darkcyan': 0x008B8B, - 'darkgoldenrod': 0xB8860B, 'darkgray': 0xA9A9A9, 'darkgreen': 0x006400, 'darkgrey': 0xA9A9A9, 'darkkhaki': 0xBDB76B, 'darkmagenta': 0x8B008B, - 'darkolivegreen': 0x556B2F, 'darkorange': 0xFF8C00, 'darkorchid': 0x9932CC, 'darkred': 0x8B0000, 'darksalmon': 0xE9967A, 'darkseagreen': 0x8FBC8F, - 'darkslateblue': 0x483D8B, 'darkslategray': 0x2F4F4F, 'darkslategrey': 0x2F4F4F, 'darkturquoise': 0x00CED1, 'darkviolet': 0x9400D3, - 'deeppink': 0xFF1493, 'deepskyblue': 0x00BFFF, 'dimgray': 0x696969, 'dimgrey': 0x696969, 'dodgerblue': 0x1E90FF, 'firebrick': 0xB22222, - 'floralwhite': 0xFFFAF0, 'forestgreen': 0x228B22, 'fuchsia': 0xFF00FF, 'gainsboro': 0xDCDCDC, 'ghostwhite': 0xF8F8FF, 'gold': 0xFFD700, - 'goldenrod': 0xDAA520, 'gray': 0x808080, 'green': 0x008000, 'greenyellow': 0xADFF2F, 'grey': 0x808080, 'honeydew': 0xF0FFF0, 'hotpink': 0xFF69B4, - 'indianred': 0xCD5C5C, 'indigo': 0x4B0082, 'ivory': 0xFFFFF0, 'khaki': 0xF0E68C, 'lavender': 0xE6E6FA, 'lavenderblush': 0xFFF0F5, 'lawngreen': 0x7CFC00, - 'lemonchiffon': 0xFFFACD, 'lightblue': 0xADD8E6, 'lightcoral': 0xF08080, 'lightcyan': 0xE0FFFF, 'lightgoldenrodyellow': 0xFAFAD2, 'lightgray': 0xD3D3D3, - 'lightgreen': 0x90EE90, 'lightgrey': 0xD3D3D3, 'lightpink': 0xFFB6C1, 'lightsalmon': 0xFFA07A, 'lightseagreen': 0x20B2AA, 'lightskyblue': 0x87CEFA, - 'lightslategray': 0x778899, 'lightslategrey': 0x778899, 'lightsteelblue': 0xB0C4DE, 'lightyellow': 0xFFFFE0, 'lime': 0x00FF00, 'limegreen': 0x32CD32, - 'linen': 0xFAF0E6, 'magenta': 0xFF00FF, 'maroon': 0x800000, 'mediumaquamarine': 0x66CDAA, 'mediumblue': 0x0000CD, 'mediumorchid': 0xBA55D3, - 'mediumpurple': 0x9370DB, 'mediumseagreen': 0x3CB371, 'mediumslateblue': 0x7B68EE, 'mediumspringgreen': 0x00FA9A, 'mediumturquoise': 0x48D1CC, - 'mediumvioletred': 0xC71585, 'midnightblue': 0x191970, 'mintcream': 0xF5FFFA, 'mistyrose': 0xFFE4E1, 'moccasin': 0xFFE4B5, 'navajowhite': 0xFFDEAD, - 'navy': 0x000080, 'oldlace': 0xFDF5E6, 'olive': 0x808000, 'olivedrab': 0x6B8E23, 'orange': 0xFFA500, 'orangered': 0xFF4500, 'orchid': 0xDA70D6, - 'palegoldenrod': 0xEEE8AA, 'palegreen': 0x98FB98, 'paleturquoise': 0xAFEEEE, 'palevioletred': 0xDB7093, 'papayawhip': 0xFFEFD5, 'peachpuff': 0xFFDAB9, - 'peru': 0xCD853F, 'pink': 0xFFC0CB, 'plum': 0xDDA0DD, 'powderblue': 0xB0E0E6, 'purple': 0x800080, 'rebeccapurple': 0x663399, 'red': 0xFF0000, 'rosybrown': 0xBC8F8F, - 'royalblue': 0x4169E1, 'saddlebrown': 0x8B4513, 'salmon': 0xFA8072, 'sandybrown': 0xF4A460, 'seagreen': 0x2E8B57, 'seashell': 0xFFF5EE, - 'sienna': 0xA0522D, 'silver': 0xC0C0C0, 'skyblue': 0x87CEEB, 'slateblue': 0x6A5ACD, 'slategray': 0x708090, 'slategrey': 0x708090, 'snow': 0xFFFAFA, - 'springgreen': 0x00FF7F, 'steelblue': 0x4682B4, 'tan': 0xD2B48C, 'teal': 0x008080, 'thistle': 0xD8BFD8, 'tomato': 0xFF6347, 'turquoise': 0x40E0D0, - 'violet': 0xEE82EE, 'wheat': 0xF5DEB3, 'white': 0xFFFFFF, 'whitesmoke': 0xF5F5F5, 'yellow': 0xFFFF00, 'yellowgreen': 0x9ACD32 }; - -const _hslA = { h: 0, s: 0, l: 0 }; -const _hslB = { h: 0, s: 0, l: 0 }; - -function hue2rgb( p, q, t ) { - - if ( t < 0 ) t += 1; - if ( t > 1 ) t -= 1; - if ( t < 1 / 6 ) return p + ( q - p ) * 6 * t; - if ( t < 1 / 2 ) return q; - if ( t < 2 / 3 ) return p + ( q - p ) * 6 * ( 2 / 3 - t ); - return p; - -} - -class Color { - - constructor( r, g, b ) { - - this.isColor = true; - - this.r = 1; - this.g = 1; - this.b = 1; - - return this.set( r, g, b ); - - } - - set( r, g, b ) { - - if ( g === undefined && b === undefined ) { - - // r is THREE.Color, hex or string - - const value = r; - - if ( value && value.isColor ) { - - this.copy( value ); - - } else if ( typeof value === 'number' ) { - - this.setHex( value ); - - } else if ( typeof value === 'string' ) { - - this.setStyle( value ); - - } - - } else { - - this.setRGB( r, g, b ); - - } - - return this; - - } - - setScalar( scalar ) { - - this.r = scalar; - this.g = scalar; - this.b = scalar; - - return this; - - } - - setHex( hex, colorSpace = SRGBColorSpace ) { - - hex = Math.floor( hex ); - - this.r = ( hex >> 16 & 255 ) / 255; - this.g = ( hex >> 8 & 255 ) / 255; - this.b = ( hex & 255 ) / 255; - - ColorManagement.toWorkingColorSpace( this, colorSpace ); - - return this; - - } - - setRGB( r, g, b, colorSpace = ColorManagement.workingColorSpace ) { - - this.r = r; - this.g = g; - this.b = b; - - ColorManagement.toWorkingColorSpace( this, colorSpace ); - - return this; - - } - - setHSL( h, s, l, colorSpace = ColorManagement.workingColorSpace ) { - - // h,s,l ranges are in 0.0 - 1.0 - h = euclideanModulo( h, 1 ); - s = clamp( s, 0, 1 ); - l = clamp( l, 0, 1 ); - - if ( s === 0 ) { - - this.r = this.g = this.b = l; - - } else { - - const p = l <= 0.5 ? l * ( 1 + s ) : l + s - ( l * s ); - const q = ( 2 * l ) - p; - - this.r = hue2rgb( q, p, h + 1 / 3 ); - this.g = hue2rgb( q, p, h ); - this.b = hue2rgb( q, p, h - 1 / 3 ); - - } - - ColorManagement.toWorkingColorSpace( this, colorSpace ); - - return this; - - } - - setStyle( style, colorSpace = SRGBColorSpace ) { - - function handleAlpha( string ) { - - if ( string === undefined ) return; - - if ( parseFloat( string ) < 1 ) { - - console.warn( 'THREE.Color: Alpha component of ' + style + ' will be ignored.' ); - - } - - } - - - let m; - - if ( m = /^(\w+)\(([^\)]*)\)/.exec( style ) ) { - - // rgb / hsl - - let color; - const name = m[ 1 ]; - const components = m[ 2 ]; - - switch ( name ) { - - case 'rgb': - case 'rgba': - - if ( color = /^\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) { - - // rgb(255,0,0) rgba(255,0,0,0.5) - - handleAlpha( color[ 4 ] ); - - return this.setRGB( - Math.min( 255, parseInt( color[ 1 ], 10 ) ) / 255, - Math.min( 255, parseInt( color[ 2 ], 10 ) ) / 255, - Math.min( 255, parseInt( color[ 3 ], 10 ) ) / 255, - colorSpace - ); - - } - - if ( color = /^\s*(\d+)\%\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) { - - // rgb(100%,0%,0%) rgba(100%,0%,0%,0.5) - - handleAlpha( color[ 4 ] ); - - return this.setRGB( - Math.min( 100, parseInt( color[ 1 ], 10 ) ) / 100, - Math.min( 100, parseInt( color[ 2 ], 10 ) ) / 100, - Math.min( 100, parseInt( color[ 3 ], 10 ) ) / 100, - colorSpace - ); - - } - - break; - - case 'hsl': - case 'hsla': - - if ( color = /^\s*(\d*\.?\d+)\s*,\s*(\d*\.?\d+)\%\s*,\s*(\d*\.?\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) { - - // hsl(120,50%,50%) hsla(120,50%,50%,0.5) - - handleAlpha( color[ 4 ] ); - - return this.setHSL( - parseFloat( color[ 1 ] ) / 360, - parseFloat( color[ 2 ] ) / 100, - parseFloat( color[ 3 ] ) / 100, - colorSpace - ); - - } - - break; - - default: - - console.warn( 'THREE.Color: Unknown color model ' + style ); - - } - - } else if ( m = /^\#([A-Fa-f\d]+)$/.exec( style ) ) { - - // hex color - - const hex = m[ 1 ]; - const size = hex.length; - - if ( size === 3 ) { - - // #ff0 - return this.setRGB( - parseInt( hex.charAt( 0 ), 16 ) / 15, - parseInt( hex.charAt( 1 ), 16 ) / 15, - parseInt( hex.charAt( 2 ), 16 ) / 15, - colorSpace - ); - - } else if ( size === 6 ) { - - // #ff0000 - return this.setHex( parseInt( hex, 16 ), colorSpace ); - - } else { - - console.warn( 'THREE.Color: Invalid hex color ' + style ); - - } - - } else if ( style && style.length > 0 ) { - - return this.setColorName( style, colorSpace ); - - } - - return this; - - } - - setColorName( style, colorSpace = SRGBColorSpace ) { - - // color keywords - const hex = _colorKeywords[ style.toLowerCase() ]; - - if ( hex !== undefined ) { - - // red - this.setHex( hex, colorSpace ); - - } else { - - // unknown color - console.warn( 'THREE.Color: Unknown color ' + style ); - - } - - return this; - - } - - clone() { - - return new this.constructor( this.r, this.g, this.b ); - - } - - copy( color ) { - - this.r = color.r; - this.g = color.g; - this.b = color.b; - - return this; - - } - - copySRGBToLinear( color ) { - - this.r = SRGBToLinear( color.r ); - this.g = SRGBToLinear( color.g ); - this.b = SRGBToLinear( color.b ); - - return this; - - } - - copyLinearToSRGB( color ) { - - this.r = LinearToSRGB( color.r ); - this.g = LinearToSRGB( color.g ); - this.b = LinearToSRGB( color.b ); - - return this; - - } - - convertSRGBToLinear() { - - this.copySRGBToLinear( this ); - - return this; - - } - - convertLinearToSRGB() { - - this.copyLinearToSRGB( this ); - - return this; - - } - - getHex( colorSpace = SRGBColorSpace ) { - - ColorManagement.fromWorkingColorSpace( _color.copy( this ), colorSpace ); - - return Math.round( clamp( _color.r * 255, 0, 255 ) ) * 65536 + Math.round( clamp( _color.g * 255, 0, 255 ) ) * 256 + Math.round( clamp( _color.b * 255, 0, 255 ) ); - - } - - getHexString( colorSpace = SRGBColorSpace ) { - - return ( '000000' + this.getHex( colorSpace ).toString( 16 ) ).slice( - 6 ); - - } - - getHSL( target, colorSpace = ColorManagement.workingColorSpace ) { - - // h,s,l ranges are in 0.0 - 1.0 - - ColorManagement.fromWorkingColorSpace( _color.copy( this ), colorSpace ); - - const r = _color.r, g = _color.g, b = _color.b; - - const max = Math.max( r, g, b ); - const min = Math.min( r, g, b ); - - let hue, saturation; - const lightness = ( min + max ) / 2.0; - - if ( min === max ) { - - hue = 0; - saturation = 0; - - } else { - - const delta = max - min; - - saturation = lightness <= 0.5 ? delta / ( max + min ) : delta / ( 2 - max - min ); - - switch ( max ) { - - case r: hue = ( g - b ) / delta + ( g < b ? 6 : 0 ); break; - case g: hue = ( b - r ) / delta + 2; break; - case b: hue = ( r - g ) / delta + 4; break; - - } - - hue /= 6; - - } - - target.h = hue; - target.s = saturation; - target.l = lightness; - - return target; - - } - - getRGB( target, colorSpace = ColorManagement.workingColorSpace ) { - - ColorManagement.fromWorkingColorSpace( _color.copy( this ), colorSpace ); - - target.r = _color.r; - target.g = _color.g; - target.b = _color.b; - - return target; - - } - - getStyle( colorSpace = SRGBColorSpace ) { - - ColorManagement.fromWorkingColorSpace( _color.copy( this ), colorSpace ); - - const r = _color.r, g = _color.g, b = _color.b; - - if ( colorSpace !== SRGBColorSpace ) { - - // Requires CSS Color Module Level 4 (https://www.w3.org/TR/css-color-4/). - return `color(${ colorSpace } ${ r.toFixed( 3 ) } ${ g.toFixed( 3 ) } ${ b.toFixed( 3 ) })`; - - } - - return `rgb(${ Math.round( r * 255 ) },${ Math.round( g * 255 ) },${ Math.round( b * 255 ) })`; - - } - - offsetHSL( h, s, l ) { - - this.getHSL( _hslA ); - - _hslA.h += h; _hslA.s += s; _hslA.l += l; - - this.setHSL( _hslA.h, _hslA.s, _hslA.l ); - - return this; - - } - - add( color ) { - - this.r += color.r; - this.g += color.g; - this.b += color.b; - - return this; - - } - - addColors( color1, color2 ) { - - this.r = color1.r + color2.r; - this.g = color1.g + color2.g; - this.b = color1.b + color2.b; - - return this; - - } - - addScalar( s ) { - - this.r += s; - this.g += s; - this.b += s; - - return this; - - } - - sub( color ) { - - this.r = Math.max( 0, this.r - color.r ); - this.g = Math.max( 0, this.g - color.g ); - this.b = Math.max( 0, this.b - color.b ); - - return this; - - } - - multiply( color ) { - - this.r *= color.r; - this.g *= color.g; - this.b *= color.b; - - return this; - - } - - multiplyScalar( s ) { - - this.r *= s; - this.g *= s; - this.b *= s; - - return this; - - } - - lerp( color, alpha ) { - - this.r += ( color.r - this.r ) * alpha; - this.g += ( color.g - this.g ) * alpha; - this.b += ( color.b - this.b ) * alpha; - - return this; - - } - - lerpColors( color1, color2, alpha ) { - - this.r = color1.r + ( color2.r - color1.r ) * alpha; - this.g = color1.g + ( color2.g - color1.g ) * alpha; - this.b = color1.b + ( color2.b - color1.b ) * alpha; - - return this; - - } - - lerpHSL( color, alpha ) { - - this.getHSL( _hslA ); - color.getHSL( _hslB ); - - const h = lerp( _hslA.h, _hslB.h, alpha ); - const s = lerp( _hslA.s, _hslB.s, alpha ); - const l = lerp( _hslA.l, _hslB.l, alpha ); - - this.setHSL( h, s, l ); - - return this; - - } - - setFromVector3( v ) { - - this.r = v.x; - this.g = v.y; - this.b = v.z; - - return this; - - } - - applyMatrix3( m ) { - - const r = this.r, g = this.g, b = this.b; - const e = m.elements; - - this.r = e[ 0 ] * r + e[ 3 ] * g + e[ 6 ] * b; - this.g = e[ 1 ] * r + e[ 4 ] * g + e[ 7 ] * b; - this.b = e[ 2 ] * r + e[ 5 ] * g + e[ 8 ] * b; - - return this; - - } - - equals( c ) { - - return ( c.r === this.r ) && ( c.g === this.g ) && ( c.b === this.b ); - - } - - fromArray( array, offset = 0 ) { - - this.r = array[ offset ]; - this.g = array[ offset + 1 ]; - this.b = array[ offset + 2 ]; - - return this; - - } - - toArray( array = [], offset = 0 ) { - - array[ offset ] = this.r; - array[ offset + 1 ] = this.g; - array[ offset + 2 ] = this.b; - - return array; - - } - - fromBufferAttribute( attribute, index ) { - - this.r = attribute.getX( index ); - this.g = attribute.getY( index ); - this.b = attribute.getZ( index ); - - return this; - - } - - toJSON() { - - return this.getHex(); - - } - - *[ Symbol.iterator ]() { - - yield this.r; - yield this.g; - yield this.b; - - } - -} - -const _color = /*@__PURE__*/ new Color(); - -Color.NAMES = _colorKeywords; - -class MeshBasicMaterial extends Material { - - constructor( parameters ) { - - super(); - - this.isMeshBasicMaterial = true; - - this.type = 'MeshBasicMaterial'; - - this.color = new Color( 0xffffff ); // emissive - - this.map = null; - - this.lightMap = null; - this.lightMapIntensity = 1.0; - - this.aoMap = null; - this.aoMapIntensity = 1.0; - - this.specularMap = null; - - this.alphaMap = null; - - this.envMap = null; - this.combine = MultiplyOperation; - this.reflectivity = 1; - this.refractionRatio = 0.98; - - this.wireframe = false; - this.wireframeLinewidth = 1; - this.wireframeLinecap = 'round'; - this.wireframeLinejoin = 'round'; - - this.fog = true; - - this.setValues( parameters ); - - } - - copy( source ) { - - super.copy( source ); - - this.color.copy( source.color ); - - this.map = source.map; - - this.lightMap = source.lightMap; - this.lightMapIntensity = source.lightMapIntensity; - - this.aoMap = source.aoMap; - this.aoMapIntensity = source.aoMapIntensity; - - this.specularMap = source.specularMap; - - this.alphaMap = source.alphaMap; - - this.envMap = source.envMap; - this.combine = source.combine; - this.reflectivity = source.reflectivity; - this.refractionRatio = source.refractionRatio; - - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; - this.wireframeLinecap = source.wireframeLinecap; - this.wireframeLinejoin = source.wireframeLinejoin; - - this.fog = source.fog; - - return this; - - } - -} - -// Fast Half Float Conversions, http://www.fox-toolkit.org/ftp/fasthalffloatconversion.pdf - -const _tables = /*@__PURE__*/ _generateTables(); - -function _generateTables() { - - // float32 to float16 helpers - - const buffer = new ArrayBuffer( 4 ); - const floatView = new Float32Array( buffer ); - const uint32View = new Uint32Array( buffer ); - - const baseTable = new Uint32Array( 512 ); - const shiftTable = new Uint32Array( 512 ); - - for ( let i = 0; i < 256; ++ i ) { - - const e = i - 127; - - // very small number (0, -0) - - if ( e < - 27 ) { - - baseTable[ i ] = 0x0000; - baseTable[ i | 0x100 ] = 0x8000; - shiftTable[ i ] = 24; - shiftTable[ i | 0x100 ] = 24; - - // small number (denorm) - - } else if ( e < - 14 ) { - - baseTable[ i ] = 0x0400 >> ( - e - 14 ); - baseTable[ i | 0x100 ] = ( 0x0400 >> ( - e - 14 ) ) | 0x8000; - shiftTable[ i ] = - e - 1; - shiftTable[ i | 0x100 ] = - e - 1; - - // normal number - - } else if ( e <= 15 ) { - - baseTable[ i ] = ( e + 15 ) << 10; - baseTable[ i | 0x100 ] = ( ( e + 15 ) << 10 ) | 0x8000; - shiftTable[ i ] = 13; - shiftTable[ i | 0x100 ] = 13; - - // large number (Infinity, -Infinity) - - } else if ( e < 128 ) { - - baseTable[ i ] = 0x7c00; - baseTable[ i | 0x100 ] = 0xfc00; - shiftTable[ i ] = 24; - shiftTable[ i | 0x100 ] = 24; - - // stay (NaN, Infinity, -Infinity) - - } else { - - baseTable[ i ] = 0x7c00; - baseTable[ i | 0x100 ] = 0xfc00; - shiftTable[ i ] = 13; - shiftTable[ i | 0x100 ] = 13; - - } - - } - - // float16 to float32 helpers - - const mantissaTable = new Uint32Array( 2048 ); - const exponentTable = new Uint32Array( 64 ); - const offsetTable = new Uint32Array( 64 ); - - for ( let i = 1; i < 1024; ++ i ) { - - let m = i << 13; // zero pad mantissa bits - let e = 0; // zero exponent - - // normalized - while ( ( m & 0x00800000 ) === 0 ) { - - m <<= 1; - e -= 0x00800000; // decrement exponent - - } - - m &= ~ 0x00800000; // clear leading 1 bit - e += 0x38800000; // adjust bias - - mantissaTable[ i ] = m | e; - - } - - for ( let i = 1024; i < 2048; ++ i ) { - - mantissaTable[ i ] = 0x38000000 + ( ( i - 1024 ) << 13 ); - - } - - for ( let i = 1; i < 31; ++ i ) { - - exponentTable[ i ] = i << 23; - - } - - exponentTable[ 31 ] = 0x47800000; - exponentTable[ 32 ] = 0x80000000; - - for ( let i = 33; i < 63; ++ i ) { - - exponentTable[ i ] = 0x80000000 + ( ( i - 32 ) << 23 ); - - } - - exponentTable[ 63 ] = 0xc7800000; - - for ( let i = 1; i < 64; ++ i ) { - - if ( i !== 32 ) { - - offsetTable[ i ] = 1024; - - } - - } - - return { - floatView: floatView, - uint32View: uint32View, - baseTable: baseTable, - shiftTable: shiftTable, - mantissaTable: mantissaTable, - exponentTable: exponentTable, - offsetTable: offsetTable - }; - -} - -// float32 to float16 - -function toHalfFloat( val ) { - - if ( Math.abs( val ) > 65504 ) console.warn( 'THREE.DataUtils.toHalfFloat(): Value out of range.' ); - - val = clamp( val, - 65504, 65504 ); - - _tables.floatView[ 0 ] = val; - const f = _tables.uint32View[ 0 ]; - const e = ( f >> 23 ) & 0x1ff; - return _tables.baseTable[ e ] + ( ( f & 0x007fffff ) >> _tables.shiftTable[ e ] ); - -} - -// float16 to float32 - -function fromHalfFloat( val ) { - - const m = val >> 10; - _tables.uint32View[ 0 ] = _tables.mantissaTable[ _tables.offsetTable[ m ] + ( val & 0x3ff ) ] + _tables.exponentTable[ m ]; - return _tables.floatView[ 0 ]; - -} - -const DataUtils = { - toHalfFloat: toHalfFloat, - fromHalfFloat: fromHalfFloat, -}; - -const _vector$8 = /*@__PURE__*/ new Vector3(); -const _vector2$1 = /*@__PURE__*/ new Vector2(); - -class BufferAttribute { - - constructor( array, itemSize, normalized = false ) { - - if ( Array.isArray( array ) ) { - - throw new TypeError( 'THREE.BufferAttribute: array should be a Typed Array.' ); - - } - - this.isBufferAttribute = true; - - this.name = ''; - - this.array = array; - this.itemSize = itemSize; - this.count = array !== undefined ? array.length / itemSize : 0; - this.normalized = normalized; - - this.usage = StaticDrawUsage; - this.updateRange = { offset: 0, count: - 1 }; - - this.version = 0; - - } - - onUploadCallback() {} - - set needsUpdate( value ) { - - if ( value === true ) this.version ++; - - } - - setUsage( value ) { - - this.usage = value; - - return this; - - } - - copy( source ) { - - this.name = source.name; - this.array = new source.array.constructor( source.array ); - this.itemSize = source.itemSize; - this.count = source.count; - this.normalized = source.normalized; - - this.usage = source.usage; - - return this; - - } - - copyAt( index1, attribute, index2 ) { - - index1 *= this.itemSize; - index2 *= attribute.itemSize; - - for ( let i = 0, l = this.itemSize; i < l; i ++ ) { - - this.array[ index1 + i ] = attribute.array[ index2 + i ]; - - } - - return this; - - } - - copyArray( array ) { - - this.array.set( array ); - - return this; - - } - - applyMatrix3( m ) { - - if ( this.itemSize === 2 ) { - - for ( let i = 0, l = this.count; i < l; i ++ ) { - - _vector2$1.fromBufferAttribute( this, i ); - _vector2$1.applyMatrix3( m ); - - this.setXY( i, _vector2$1.x, _vector2$1.y ); - - } - - } else if ( this.itemSize === 3 ) { - - for ( let i = 0, l = this.count; i < l; i ++ ) { - - _vector$8.fromBufferAttribute( this, i ); - _vector$8.applyMatrix3( m ); - - this.setXYZ( i, _vector$8.x, _vector$8.y, _vector$8.z ); - - } - - } - - return this; - - } - - applyMatrix4( m ) { - - for ( let i = 0, l = this.count; i < l; i ++ ) { - - _vector$8.fromBufferAttribute( this, i ); - - _vector$8.applyMatrix4( m ); - - this.setXYZ( i, _vector$8.x, _vector$8.y, _vector$8.z ); - - } - - return this; - - } - - applyNormalMatrix( m ) { - - for ( let i = 0, l = this.count; i < l; i ++ ) { - - _vector$8.fromBufferAttribute( this, i ); - - _vector$8.applyNormalMatrix( m ); - - this.setXYZ( i, _vector$8.x, _vector$8.y, _vector$8.z ); - - } - - return this; - - } - - transformDirection( m ) { - - for ( let i = 0, l = this.count; i < l; i ++ ) { - - _vector$8.fromBufferAttribute( this, i ); - - _vector$8.transformDirection( m ); - - this.setXYZ( i, _vector$8.x, _vector$8.y, _vector$8.z ); - - } - - return this; - - } - - set( value, offset = 0 ) { - - // Matching BufferAttribute constructor, do not normalize the array. - this.array.set( value, offset ); - - return this; - - } - - getX( index ) { - - let x = this.array[ index * this.itemSize ]; - - if ( this.normalized ) x = denormalize( x, this.array ); - - return x; - - } - - setX( index, x ) { - - if ( this.normalized ) x = normalize( x, this.array ); - - this.array[ index * this.itemSize ] = x; - - return this; - - } - - getY( index ) { - - let y = this.array[ index * this.itemSize + 1 ]; - - if ( this.normalized ) y = denormalize( y, this.array ); - - return y; - - } - - setY( index, y ) { - - if ( this.normalized ) y = normalize( y, this.array ); - - this.array[ index * this.itemSize + 1 ] = y; - - return this; - - } - - getZ( index ) { - - let z = this.array[ index * this.itemSize + 2 ]; - - if ( this.normalized ) z = denormalize( z, this.array ); - - return z; - - } - - setZ( index, z ) { - - if ( this.normalized ) z = normalize( z, this.array ); - - this.array[ index * this.itemSize + 2 ] = z; - - return this; - - } - - getW( index ) { - - let w = this.array[ index * this.itemSize + 3 ]; - - if ( this.normalized ) w = denormalize( w, this.array ); - - return w; - - } - - setW( index, w ) { - - if ( this.normalized ) w = normalize( w, this.array ); - - this.array[ index * this.itemSize + 3 ] = w; - - return this; - - } - - setXY( index, x, y ) { - - index *= this.itemSize; - - if ( this.normalized ) { - - x = normalize( x, this.array ); - y = normalize( y, this.array ); - - } - - this.array[ index + 0 ] = x; - this.array[ index + 1 ] = y; - - return this; - - } - - setXYZ( index, x, y, z ) { - - index *= this.itemSize; - - if ( this.normalized ) { - - x = normalize( x, this.array ); - y = normalize( y, this.array ); - z = normalize( z, this.array ); - - } - - this.array[ index + 0 ] = x; - this.array[ index + 1 ] = y; - this.array[ index + 2 ] = z; - - return this; - - } - - setXYZW( index, x, y, z, w ) { - - index *= this.itemSize; - - if ( this.normalized ) { - - x = normalize( x, this.array ); - y = normalize( y, this.array ); - z = normalize( z, this.array ); - w = normalize( w, this.array ); - - } - - this.array[ index + 0 ] = x; - this.array[ index + 1 ] = y; - this.array[ index + 2 ] = z; - this.array[ index + 3 ] = w; - - return this; - - } - - onUpload( callback ) { - - this.onUploadCallback = callback; - - return this; - - } - - clone() { - - return new this.constructor( this.array, this.itemSize ).copy( this ); - - } - - toJSON() { - - const data = { - itemSize: this.itemSize, - type: this.array.constructor.name, - array: Array.from( this.array ), - normalized: this.normalized - }; - - if ( this.name !== '' ) data.name = this.name; - if ( this.usage !== StaticDrawUsage ) data.usage = this.usage; - if ( this.updateRange.offset !== 0 || this.updateRange.count !== - 1 ) data.updateRange = this.updateRange; - - return data; - - } - - copyColorsArray() { // @deprecated, r144 - - console.error( 'THREE.BufferAttribute: copyColorsArray() was removed in r144.' ); - - } - - copyVector2sArray() { // @deprecated, r144 - - console.error( 'THREE.BufferAttribute: copyVector2sArray() was removed in r144.' ); - - } - - copyVector3sArray() { // @deprecated, r144 - - console.error( 'THREE.BufferAttribute: copyVector3sArray() was removed in r144.' ); - - } - - copyVector4sArray() { // @deprecated, r144 - - console.error( 'THREE.BufferAttribute: copyVector4sArray() was removed in r144.' ); - - } - -} - -// - -class Int8BufferAttribute extends BufferAttribute { - - constructor( array, itemSize, normalized ) { - - super( new Int8Array( array ), itemSize, normalized ); - - this.gpuType = FloatType; - - } - - copy( source ) { - - super.copy( source ); - - this.gpuType = source.gpuType; - - return this; - - } - -} - -class Uint8BufferAttribute extends BufferAttribute { - - constructor( array, itemSize, normalized ) { - - super( new Uint8Array( array ), itemSize, normalized ); - - this.gpuType = FloatType; - - } - - copy( source ) { - - super.copy( source ); - - this.gpuType = source.gpuType; - - return this; - - } - -} - -class Uint8ClampedBufferAttribute extends BufferAttribute { - - constructor( array, itemSize, normalized ) { - - super( new Uint8ClampedArray( array ), itemSize, normalized ); - - } - -} - -class Int16BufferAttribute extends BufferAttribute { - - constructor( array, itemSize, normalized ) { - - super( new Int16Array( array ), itemSize, normalized ); - - this.gpuType = FloatType; - - } - - copy( source ) { - - super.copy( source ); - - this.gpuType = source.gpuType; - - return this; - - } - -} - -class Uint16BufferAttribute extends BufferAttribute { - - constructor( array, itemSize, normalized ) { - - super( new Uint16Array( array ), itemSize, normalized ); - - this.gpuType = FloatType; - - } - - copy( source ) { - - super.copy( source ); - - this.gpuType = source.gpuType; - - return this; - - } - -} - -class Int32BufferAttribute extends BufferAttribute { - - constructor( array, itemSize, normalized ) { - - super( new Int32Array( array ), itemSize, normalized ); - - } - -} - -class Uint32BufferAttribute extends BufferAttribute { - - constructor( array, itemSize, normalized ) { - - super( new Uint32Array( array ), itemSize, normalized ); - - } - -} - -class Float16BufferAttribute extends BufferAttribute { - - constructor( array, itemSize, normalized ) { - - super( new Uint16Array( array ), itemSize, normalized ); - - this.isFloat16BufferAttribute = true; - - } - - getX( index ) { - - let x = fromHalfFloat( this.array[ index * this.itemSize ] ); - - if ( this.normalized ) x = denormalize( x, this.array ); - - return x; - - } - - setX( index, x ) { - - if ( this.normalized ) x = normalize( x, this.array ); - - this.array[ index * this.itemSize ] = toHalfFloat( x ); - - return this; - - } - - getY( index ) { - - let y = fromHalfFloat( this.array[ index * this.itemSize + 1 ] ); - - if ( this.normalized ) y = denormalize( y, this.array ); - - return y; - - } - - setY( index, y ) { - - if ( this.normalized ) y = normalize( y, this.array ); - - this.array[ index * this.itemSize + 1 ] = toHalfFloat( y ); - - return this; - - } - - getZ( index ) { - - let z = fromHalfFloat( this.array[ index * this.itemSize + 2 ] ); - - if ( this.normalized ) z = denormalize( z, this.array ); - - return z; - - } - - setZ( index, z ) { - - if ( this.normalized ) z = normalize( z, this.array ); - - this.array[ index * this.itemSize + 2 ] = toHalfFloat( z ); - - return this; - - } - - getW( index ) { - - let w = fromHalfFloat( this.array[ index * this.itemSize + 3 ] ); - - if ( this.normalized ) w = denormalize( w, this.array ); - - return w; - - } - - setW( index, w ) { - - if ( this.normalized ) w = normalize( w, this.array ); - - this.array[ index * this.itemSize + 3 ] = toHalfFloat( w ); - - return this; - - } - - setXY( index, x, y ) { - - index *= this.itemSize; - - if ( this.normalized ) { - - x = normalize( x, this.array ); - y = normalize( y, this.array ); - - } - - this.array[ index + 0 ] = toHalfFloat( x ); - this.array[ index + 1 ] = toHalfFloat( y ); - - return this; - - } - - setXYZ( index, x, y, z ) { - - index *= this.itemSize; - - if ( this.normalized ) { - - x = normalize( x, this.array ); - y = normalize( y, this.array ); - z = normalize( z, this.array ); - - } - - this.array[ index + 0 ] = toHalfFloat( x ); - this.array[ index + 1 ] = toHalfFloat( y ); - this.array[ index + 2 ] = toHalfFloat( z ); - - return this; - - } - - setXYZW( index, x, y, z, w ) { - - index *= this.itemSize; - - if ( this.normalized ) { - - x = normalize( x, this.array ); - y = normalize( y, this.array ); - z = normalize( z, this.array ); - w = normalize( w, this.array ); - - } - - this.array[ index + 0 ] = toHalfFloat( x ); - this.array[ index + 1 ] = toHalfFloat( y ); - this.array[ index + 2 ] = toHalfFloat( z ); - this.array[ index + 3 ] = toHalfFloat( w ); - - return this; - - } - -} - - -class Float32BufferAttribute extends BufferAttribute { - - constructor( array, itemSize, normalized ) { - - super( new Float32Array( array ), itemSize, normalized ); - - } - -} - -class Float64BufferAttribute extends BufferAttribute { - - constructor( array, itemSize, normalized ) { - - super( new Float64Array( array ), itemSize, normalized ); - - } - -} - -let _id$1 = 0; - -const _m1 = /*@__PURE__*/ new Matrix4(); -const _obj = /*@__PURE__*/ new Object3D(); -const _offset = /*@__PURE__*/ new Vector3(); -const _box$1 = /*@__PURE__*/ new Box3(); -const _boxMorphTargets = /*@__PURE__*/ new Box3(); -const _vector$7 = /*@__PURE__*/ new Vector3(); - -class BufferGeometry extends EventDispatcher { - - constructor() { - - super(); - - this.isBufferGeometry = true; - - Object.defineProperty( this, 'id', { value: _id$1 ++ } ); - - this.uuid = generateUUID(); - - this.name = ''; - this.type = 'BufferGeometry'; - - this.index = null; - this.attributes = {}; - - this.morphAttributes = {}; - this.morphTargetsRelative = false; - - this.groups = []; - - this.boundingBox = null; - this.boundingSphere = null; - - this.drawRange = { start: 0, count: Infinity }; - - this.userData = {}; - - } - - getIndex() { - - return this.index; - - } - - setIndex( index ) { - - if ( Array.isArray( index ) ) { - - this.index = new ( arrayNeedsUint32( index ) ? Uint32BufferAttribute : Uint16BufferAttribute )( index, 1 ); - - } else { - - this.index = index; - - } - - return this; - - } - - getAttribute( name ) { - - return this.attributes[ name ]; - - } - - setAttribute( name, attribute ) { - - this.attributes[ name ] = attribute; - - return this; - - } - - deleteAttribute( name ) { - - delete this.attributes[ name ]; - - return this; - - } - - hasAttribute( name ) { - - return this.attributes[ name ] !== undefined; - - } - - addGroup( start, count, materialIndex = 0 ) { - - this.groups.push( { - - start: start, - count: count, - materialIndex: materialIndex - - } ); - - } - - clearGroups() { - - this.groups = []; - - } - - setDrawRange( start, count ) { - - this.drawRange.start = start; - this.drawRange.count = count; - - } - - applyMatrix4( matrix ) { - - const position = this.attributes.position; - - if ( position !== undefined ) { - - position.applyMatrix4( matrix ); - - position.needsUpdate = true; - - } - - const normal = this.attributes.normal; - - if ( normal !== undefined ) { - - const normalMatrix = new Matrix3().getNormalMatrix( matrix ); - - normal.applyNormalMatrix( normalMatrix ); - - normal.needsUpdate = true; - - } - - const tangent = this.attributes.tangent; - - if ( tangent !== undefined ) { - - tangent.transformDirection( matrix ); - - tangent.needsUpdate = true; - - } - - if ( this.boundingBox !== null ) { - - this.computeBoundingBox(); - - } - - if ( this.boundingSphere !== null ) { - - this.computeBoundingSphere(); - - } - - return this; - - } - - applyQuaternion( q ) { - - _m1.makeRotationFromQuaternion( q ); - - this.applyMatrix4( _m1 ); - - return this; - - } - - rotateX( angle ) { - - // rotate geometry around world x-axis - - _m1.makeRotationX( angle ); - - this.applyMatrix4( _m1 ); - - return this; - - } - - rotateY( angle ) { - - // rotate geometry around world y-axis - - _m1.makeRotationY( angle ); - - this.applyMatrix4( _m1 ); - - return this; - - } - - rotateZ( angle ) { - - // rotate geometry around world z-axis - - _m1.makeRotationZ( angle ); - - this.applyMatrix4( _m1 ); - - return this; - - } - - translate( x, y, z ) { - - // translate geometry - - _m1.makeTranslation( x, y, z ); - - this.applyMatrix4( _m1 ); - - return this; - - } - - scale( x, y, z ) { - - // scale geometry - - _m1.makeScale( x, y, z ); - - this.applyMatrix4( _m1 ); - - return this; - - } - - lookAt( vector ) { - - _obj.lookAt( vector ); - - _obj.updateMatrix(); - - this.applyMatrix4( _obj.matrix ); - - return this; - - } - - center() { - - this.computeBoundingBox(); - - this.boundingBox.getCenter( _offset ).negate(); - - this.translate( _offset.x, _offset.y, _offset.z ); - - return this; - - } - - setFromPoints( points ) { - - const position = []; - - for ( let i = 0, l = points.length; i < l; i ++ ) { - - const point = points[ i ]; - position.push( point.x, point.y, point.z || 0 ); - - } - - this.setAttribute( 'position', new Float32BufferAttribute( position, 3 ) ); - - return this; - - } - - computeBoundingBox() { - - if ( this.boundingBox === null ) { - - this.boundingBox = new Box3(); - - } - - const position = this.attributes.position; - const morphAttributesPosition = this.morphAttributes.position; - - if ( position && position.isGLBufferAttribute ) { - - console.error( 'THREE.BufferGeometry.computeBoundingBox(): GLBufferAttribute requires a manual bounding box. Alternatively set "mesh.frustumCulled" to "false".', this ); - - this.boundingBox.set( - new Vector3( - Infinity, - Infinity, - Infinity ), - new Vector3( + Infinity, + Infinity, + Infinity ) - ); - - return; - - } - - if ( position !== undefined ) { - - this.boundingBox.setFromBufferAttribute( position ); - - // process morph attributes if present - - if ( morphAttributesPosition ) { - - for ( let i = 0, il = morphAttributesPosition.length; i < il; i ++ ) { - - const morphAttribute = morphAttributesPosition[ i ]; - _box$1.setFromBufferAttribute( morphAttribute ); - - if ( this.morphTargetsRelative ) { - - _vector$7.addVectors( this.boundingBox.min, _box$1.min ); - this.boundingBox.expandByPoint( _vector$7 ); - - _vector$7.addVectors( this.boundingBox.max, _box$1.max ); - this.boundingBox.expandByPoint( _vector$7 ); - - } else { - - this.boundingBox.expandByPoint( _box$1.min ); - this.boundingBox.expandByPoint( _box$1.max ); - - } - - } - - } - - } else { - - this.boundingBox.makeEmpty(); - - } - - if ( isNaN( this.boundingBox.min.x ) || isNaN( this.boundingBox.min.y ) || isNaN( this.boundingBox.min.z ) ) { - - console.error( 'THREE.BufferGeometry.computeBoundingBox(): Computed min/max have NaN values. The "position" attribute is likely to have NaN values.', this ); - - } - - } - - computeBoundingSphere() { - - if ( this.boundingSphere === null ) { - - this.boundingSphere = new Sphere(); - - } - - const position = this.attributes.position; - const morphAttributesPosition = this.morphAttributes.position; - - if ( position && position.isGLBufferAttribute ) { - - console.error( 'THREE.BufferGeometry.computeBoundingSphere(): GLBufferAttribute requires a manual bounding sphere. Alternatively set "mesh.frustumCulled" to "false".', this ); - - this.boundingSphere.set( new Vector3(), Infinity ); - - return; - - } - - if ( position ) { - - // first, find the center of the bounding sphere - - const center = this.boundingSphere.center; - - _box$1.setFromBufferAttribute( position ); - - // process morph attributes if present - - if ( morphAttributesPosition ) { - - for ( let i = 0, il = morphAttributesPosition.length; i < il; i ++ ) { - - const morphAttribute = morphAttributesPosition[ i ]; - _boxMorphTargets.setFromBufferAttribute( morphAttribute ); - - if ( this.morphTargetsRelative ) { - - _vector$7.addVectors( _box$1.min, _boxMorphTargets.min ); - _box$1.expandByPoint( _vector$7 ); - - _vector$7.addVectors( _box$1.max, _boxMorphTargets.max ); - _box$1.expandByPoint( _vector$7 ); - - } else { - - _box$1.expandByPoint( _boxMorphTargets.min ); - _box$1.expandByPoint( _boxMorphTargets.max ); - - } - - } - - } - - _box$1.getCenter( center ); - - // second, try to find a boundingSphere with a radius smaller than the - // boundingSphere of the boundingBox: sqrt(3) smaller in the best case - - let maxRadiusSq = 0; - - for ( let i = 0, il = position.count; i < il; i ++ ) { - - _vector$7.fromBufferAttribute( position, i ); - - maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( _vector$7 ) ); - - } - - // process morph attributes if present - - if ( morphAttributesPosition ) { - - for ( let i = 0, il = morphAttributesPosition.length; i < il; i ++ ) { - - const morphAttribute = morphAttributesPosition[ i ]; - const morphTargetsRelative = this.morphTargetsRelative; - - for ( let j = 0, jl = morphAttribute.count; j < jl; j ++ ) { - - _vector$7.fromBufferAttribute( morphAttribute, j ); - - if ( morphTargetsRelative ) { - - _offset.fromBufferAttribute( position, j ); - _vector$7.add( _offset ); - - } - - maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( _vector$7 ) ); - - } - - } - - } - - this.boundingSphere.radius = Math.sqrt( maxRadiusSq ); - - if ( isNaN( this.boundingSphere.radius ) ) { - - console.error( 'THREE.BufferGeometry.computeBoundingSphere(): Computed radius is NaN. The "position" attribute is likely to have NaN values.', this ); - - } - - } - - } - - computeTangents() { - - const index = this.index; - const attributes = this.attributes; - - // based on http://www.terathon.com/code/tangent.html - // (per vertex tangents) - - if ( index === null || - attributes.position === undefined || - attributes.normal === undefined || - attributes.uv === undefined ) { - - console.error( 'THREE.BufferGeometry: .computeTangents() failed. Missing required attributes (index, position, normal or uv)' ); - return; - - } - - const indices = index.array; - const positions = attributes.position.array; - const normals = attributes.normal.array; - const uvs = attributes.uv.array; - - const nVertices = positions.length / 3; - - if ( this.hasAttribute( 'tangent' ) === false ) { - - this.setAttribute( 'tangent', new BufferAttribute( new Float32Array( 4 * nVertices ), 4 ) ); - - } - - const tangents = this.getAttribute( 'tangent' ).array; - - const tan1 = [], tan2 = []; - - for ( let i = 0; i < nVertices; i ++ ) { - - tan1[ i ] = new Vector3(); - tan2[ i ] = new Vector3(); - - } - - const vA = new Vector3(), - vB = new Vector3(), - vC = new Vector3(), - - uvA = new Vector2(), - uvB = new Vector2(), - uvC = new Vector2(), - - sdir = new Vector3(), - tdir = new Vector3(); - - function handleTriangle( a, b, c ) { - - vA.fromArray( positions, a * 3 ); - vB.fromArray( positions, b * 3 ); - vC.fromArray( positions, c * 3 ); - - uvA.fromArray( uvs, a * 2 ); - uvB.fromArray( uvs, b * 2 ); - uvC.fromArray( uvs, c * 2 ); - - vB.sub( vA ); - vC.sub( vA ); - - uvB.sub( uvA ); - uvC.sub( uvA ); - - const r = 1.0 / ( uvB.x * uvC.y - uvC.x * uvB.y ); - - // silently ignore degenerate uv triangles having coincident or colinear vertices - - if ( ! isFinite( r ) ) return; - - sdir.copy( vB ).multiplyScalar( uvC.y ).addScaledVector( vC, - uvB.y ).multiplyScalar( r ); - tdir.copy( vC ).multiplyScalar( uvB.x ).addScaledVector( vB, - uvC.x ).multiplyScalar( r ); - - tan1[ a ].add( sdir ); - tan1[ b ].add( sdir ); - tan1[ c ].add( sdir ); - - tan2[ a ].add( tdir ); - tan2[ b ].add( tdir ); - tan2[ c ].add( tdir ); - - } - - let groups = this.groups; - - if ( groups.length === 0 ) { - - groups = [ { - start: 0, - count: indices.length - } ]; - - } - - for ( let i = 0, il = groups.length; i < il; ++ i ) { - - const group = groups[ i ]; - - const start = group.start; - const count = group.count; - - for ( let j = start, jl = start + count; j < jl; j += 3 ) { - - handleTriangle( - indices[ j + 0 ], - indices[ j + 1 ], - indices[ j + 2 ] - ); - - } - - } - - const tmp = new Vector3(), tmp2 = new Vector3(); - const n = new Vector3(), n2 = new Vector3(); - - function handleVertex( v ) { - - n.fromArray( normals, v * 3 ); - n2.copy( n ); - - const t = tan1[ v ]; - - // Gram-Schmidt orthogonalize - - tmp.copy( t ); - tmp.sub( n.multiplyScalar( n.dot( t ) ) ).normalize(); - - // Calculate handedness - - tmp2.crossVectors( n2, t ); - const test = tmp2.dot( tan2[ v ] ); - const w = ( test < 0.0 ) ? - 1.0 : 1.0; - - tangents[ v * 4 ] = tmp.x; - tangents[ v * 4 + 1 ] = tmp.y; - tangents[ v * 4 + 2 ] = tmp.z; - tangents[ v * 4 + 3 ] = w; - - } - - for ( let i = 0, il = groups.length; i < il; ++ i ) { - - const group = groups[ i ]; - - const start = group.start; - const count = group.count; - - for ( let j = start, jl = start + count; j < jl; j += 3 ) { - - handleVertex( indices[ j + 0 ] ); - handleVertex( indices[ j + 1 ] ); - handleVertex( indices[ j + 2 ] ); - - } - - } - - } - - computeVertexNormals() { - - const index = this.index; - const positionAttribute = this.getAttribute( 'position' ); - - if ( positionAttribute !== undefined ) { - - let normalAttribute = this.getAttribute( 'normal' ); - - if ( normalAttribute === undefined ) { - - normalAttribute = new BufferAttribute( new Float32Array( positionAttribute.count * 3 ), 3 ); - this.setAttribute( 'normal', normalAttribute ); - - } else { - - // reset existing normals to zero - - for ( let i = 0, il = normalAttribute.count; i < il; i ++ ) { - - normalAttribute.setXYZ( i, 0, 0, 0 ); - - } - - } - - const pA = new Vector3(), pB = new Vector3(), pC = new Vector3(); - const nA = new Vector3(), nB = new Vector3(), nC = new Vector3(); - const cb = new Vector3(), ab = new Vector3(); - - // indexed elements - - if ( index ) { - - for ( let i = 0, il = index.count; i < il; i += 3 ) { - - const vA = index.getX( i + 0 ); - const vB = index.getX( i + 1 ); - const vC = index.getX( i + 2 ); - - pA.fromBufferAttribute( positionAttribute, vA ); - pB.fromBufferAttribute( positionAttribute, vB ); - pC.fromBufferAttribute( positionAttribute, vC ); - - cb.subVectors( pC, pB ); - ab.subVectors( pA, pB ); - cb.cross( ab ); - - nA.fromBufferAttribute( normalAttribute, vA ); - nB.fromBufferAttribute( normalAttribute, vB ); - nC.fromBufferAttribute( normalAttribute, vC ); - - nA.add( cb ); - nB.add( cb ); - nC.add( cb ); - - normalAttribute.setXYZ( vA, nA.x, nA.y, nA.z ); - normalAttribute.setXYZ( vB, nB.x, nB.y, nB.z ); - normalAttribute.setXYZ( vC, nC.x, nC.y, nC.z ); - - } - - } else { - - // non-indexed elements (unconnected triangle soup) - - for ( let i = 0, il = positionAttribute.count; i < il; i += 3 ) { - - pA.fromBufferAttribute( positionAttribute, i + 0 ); - pB.fromBufferAttribute( positionAttribute, i + 1 ); - pC.fromBufferAttribute( positionAttribute, i + 2 ); - - cb.subVectors( pC, pB ); - ab.subVectors( pA, pB ); - cb.cross( ab ); - - normalAttribute.setXYZ( i + 0, cb.x, cb.y, cb.z ); - normalAttribute.setXYZ( i + 1, cb.x, cb.y, cb.z ); - normalAttribute.setXYZ( i + 2, cb.x, cb.y, cb.z ); - - } - - } - - this.normalizeNormals(); - - normalAttribute.needsUpdate = true; - - } - - } - - merge() { // @deprecated, r144 - - console.error( 'THREE.BufferGeometry.merge() has been removed. Use THREE.BufferGeometryUtils.mergeGeometries() instead.' ); - return this; - - } - - normalizeNormals() { - - const normals = this.attributes.normal; - - for ( let i = 0, il = normals.count; i < il; i ++ ) { - - _vector$7.fromBufferAttribute( normals, i ); - - _vector$7.normalize(); - - normals.setXYZ( i, _vector$7.x, _vector$7.y, _vector$7.z ); - - } - - } - - toNonIndexed() { - - function convertBufferAttribute( attribute, indices ) { - - const array = attribute.array; - const itemSize = attribute.itemSize; - const normalized = attribute.normalized; - - const array2 = new array.constructor( indices.length * itemSize ); - - let index = 0, index2 = 0; - - for ( let i = 0, l = indices.length; i < l; i ++ ) { - - if ( attribute.isInterleavedBufferAttribute ) { - - index = indices[ i ] * attribute.data.stride + attribute.offset; - - } else { - - index = indices[ i ] * itemSize; - - } - - for ( let j = 0; j < itemSize; j ++ ) { - - array2[ index2 ++ ] = array[ index ++ ]; - - } - - } - - return new BufferAttribute( array2, itemSize, normalized ); - - } - - // - - if ( this.index === null ) { - - console.warn( 'THREE.BufferGeometry.toNonIndexed(): BufferGeometry is already non-indexed.' ); - return this; - - } - - const geometry2 = new BufferGeometry(); - - const indices = this.index.array; - const attributes = this.attributes; - - // attributes - - for ( const name in attributes ) { - - const attribute = attributes[ name ]; - - const newAttribute = convertBufferAttribute( attribute, indices ); - - geometry2.setAttribute( name, newAttribute ); - - } - - // morph attributes - - const morphAttributes = this.morphAttributes; - - for ( const name in morphAttributes ) { - - const morphArray = []; - const morphAttribute = morphAttributes[ name ]; // morphAttribute: array of Float32BufferAttributes - - for ( let i = 0, il = morphAttribute.length; i < il; i ++ ) { - - const attribute = morphAttribute[ i ]; - - const newAttribute = convertBufferAttribute( attribute, indices ); - - morphArray.push( newAttribute ); - - } - - geometry2.morphAttributes[ name ] = morphArray; - - } - - geometry2.morphTargetsRelative = this.morphTargetsRelative; - - // groups - - const groups = this.groups; - - for ( let i = 0, l = groups.length; i < l; i ++ ) { - - const group = groups[ i ]; - geometry2.addGroup( group.start, group.count, group.materialIndex ); - - } - - return geometry2; - - } - - toJSON() { - - const data = { - metadata: { - version: 4.6, - type: 'BufferGeometry', - generator: 'BufferGeometry.toJSON' - } - }; - - // standard BufferGeometry serialization - - data.uuid = this.uuid; - data.type = this.type; - if ( this.name !== '' ) data.name = this.name; - if ( Object.keys( this.userData ).length > 0 ) data.userData = this.userData; - - if ( this.parameters !== undefined ) { - - const parameters = this.parameters; - - for ( const key in parameters ) { - - if ( parameters[ key ] !== undefined ) data[ key ] = parameters[ key ]; - - } - - return data; - - } - - // for simplicity the code assumes attributes are not shared across geometries, see #15811 - - data.data = { attributes: {} }; - - const index = this.index; - - if ( index !== null ) { - - data.data.index = { - type: index.array.constructor.name, - array: Array.prototype.slice.call( index.array ) - }; - - } - - const attributes = this.attributes; - - for ( const key in attributes ) { - - const attribute = attributes[ key ]; - - data.data.attributes[ key ] = attribute.toJSON( data.data ); - - } - - const morphAttributes = {}; - let hasMorphAttributes = false; - - for ( const key in this.morphAttributes ) { - - const attributeArray = this.morphAttributes[ key ]; - - const array = []; - - for ( let i = 0, il = attributeArray.length; i < il; i ++ ) { - - const attribute = attributeArray[ i ]; - - array.push( attribute.toJSON( data.data ) ); - - } - - if ( array.length > 0 ) { - - morphAttributes[ key ] = array; - - hasMorphAttributes = true; - - } - - } - - if ( hasMorphAttributes ) { - - data.data.morphAttributes = morphAttributes; - data.data.morphTargetsRelative = this.morphTargetsRelative; - - } - - const groups = this.groups; - - if ( groups.length > 0 ) { - - data.data.groups = JSON.parse( JSON.stringify( groups ) ); - - } - - const boundingSphere = this.boundingSphere; - - if ( boundingSphere !== null ) { - - data.data.boundingSphere = { - center: boundingSphere.center.toArray(), - radius: boundingSphere.radius - }; - - } - - return data; - - } - - clone() { - - return new this.constructor().copy( this ); - - } - - copy( source ) { - - // reset - - this.index = null; - this.attributes = {}; - this.morphAttributes = {}; - this.groups = []; - this.boundingBox = null; - this.boundingSphere = null; - - // used for storing cloned, shared data - - const data = {}; - - // name - - this.name = source.name; - - // index - - const index = source.index; - - if ( index !== null ) { - - this.setIndex( index.clone( data ) ); - - } - - // attributes - - const attributes = source.attributes; - - for ( const name in attributes ) { - - const attribute = attributes[ name ]; - this.setAttribute( name, attribute.clone( data ) ); - - } - - // morph attributes - - const morphAttributes = source.morphAttributes; - - for ( const name in morphAttributes ) { - - const array = []; - const morphAttribute = morphAttributes[ name ]; // morphAttribute: array of Float32BufferAttributes - - for ( let i = 0, l = morphAttribute.length; i < l; i ++ ) { - - array.push( morphAttribute[ i ].clone( data ) ); - - } - - this.morphAttributes[ name ] = array; - - } - - this.morphTargetsRelative = source.morphTargetsRelative; - - // groups - - const groups = source.groups; - - for ( let i = 0, l = groups.length; i < l; i ++ ) { - - const group = groups[ i ]; - this.addGroup( group.start, group.count, group.materialIndex ); - - } - - // bounding box - - const boundingBox = source.boundingBox; - - if ( boundingBox !== null ) { - - this.boundingBox = boundingBox.clone(); - - } - - // bounding sphere - - const boundingSphere = source.boundingSphere; - - if ( boundingSphere !== null ) { - - this.boundingSphere = boundingSphere.clone(); - - } - - // draw range - - this.drawRange.start = source.drawRange.start; - this.drawRange.count = source.drawRange.count; - - // user data - - this.userData = source.userData; - - return this; - - } - - dispose() { - - this.dispatchEvent( { type: 'dispose' } ); - - } - -} - -const _inverseMatrix$3 = /*@__PURE__*/ new Matrix4(); -const _ray$3 = /*@__PURE__*/ new Ray(); -const _sphere$5 = /*@__PURE__*/ new Sphere(); -const _sphereHitAt = /*@__PURE__*/ new Vector3(); - -const _vA$1 = /*@__PURE__*/ new Vector3(); -const _vB$1 = /*@__PURE__*/ new Vector3(); -const _vC$1 = /*@__PURE__*/ new Vector3(); - -const _tempA = /*@__PURE__*/ new Vector3(); -const _morphA = /*@__PURE__*/ new Vector3(); - -const _uvA$1 = /*@__PURE__*/ new Vector2(); -const _uvB$1 = /*@__PURE__*/ new Vector2(); -const _uvC$1 = /*@__PURE__*/ new Vector2(); - -const _normalA = /*@__PURE__*/ new Vector3(); -const _normalB = /*@__PURE__*/ new Vector3(); -const _normalC = /*@__PURE__*/ new Vector3(); - -const _intersectionPoint = /*@__PURE__*/ new Vector3(); -const _intersectionPointWorld = /*@__PURE__*/ new Vector3(); - -class Mesh extends Object3D { - - constructor( geometry = new BufferGeometry(), material = new MeshBasicMaterial() ) { - - super(); - - this.isMesh = true; - - this.type = 'Mesh'; - - this.geometry = geometry; - this.material = material; - - this.updateMorphTargets(); - - } - - copy( source, recursive ) { - - super.copy( source, recursive ); - - if ( source.morphTargetInfluences !== undefined ) { - - this.morphTargetInfluences = source.morphTargetInfluences.slice(); - - } - - if ( source.morphTargetDictionary !== undefined ) { - - this.morphTargetDictionary = Object.assign( {}, source.morphTargetDictionary ); - - } - - this.material = source.material; - this.geometry = source.geometry; - - return this; - - } - - updateMorphTargets() { - - const geometry = this.geometry; - - const morphAttributes = geometry.morphAttributes; - const keys = Object.keys( morphAttributes ); - - if ( keys.length > 0 ) { - - const morphAttribute = morphAttributes[ keys[ 0 ] ]; - - if ( morphAttribute !== undefined ) { - - this.morphTargetInfluences = []; - this.morphTargetDictionary = {}; - - for ( let m = 0, ml = morphAttribute.length; m < ml; m ++ ) { - - const name = morphAttribute[ m ].name || String( m ); - - this.morphTargetInfluences.push( 0 ); - this.morphTargetDictionary[ name ] = m; - - } - - } - - } - - } - - getVertexPosition( index, target ) { - - const geometry = this.geometry; - const position = geometry.attributes.position; - const morphPosition = geometry.morphAttributes.position; - const morphTargetsRelative = geometry.morphTargetsRelative; - - target.fromBufferAttribute( position, index ); - - const morphInfluences = this.morphTargetInfluences; - - if ( morphPosition && morphInfluences ) { - - _morphA.set( 0, 0, 0 ); - - for ( let i = 0, il = morphPosition.length; i < il; i ++ ) { - - const influence = morphInfluences[ i ]; - const morphAttribute = morphPosition[ i ]; - - if ( influence === 0 ) continue; - - _tempA.fromBufferAttribute( morphAttribute, index ); - - if ( morphTargetsRelative ) { - - _morphA.addScaledVector( _tempA, influence ); - - } else { - - _morphA.addScaledVector( _tempA.sub( target ), influence ); - - } - - } - - target.add( _morphA ); - - } - - return target; - - } - - raycast( raycaster, intersects ) { - - const geometry = this.geometry; - const material = this.material; - const matrixWorld = this.matrixWorld; - - if ( material === undefined ) return; - - // test with bounding sphere in world space - - if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); - - _sphere$5.copy( geometry.boundingSphere ); - _sphere$5.applyMatrix4( matrixWorld ); - - // check distance from ray origin to bounding sphere - - _ray$3.copy( raycaster.ray ).recast( raycaster.near ); - - if ( _sphere$5.containsPoint( _ray$3.origin ) === false ) { - - if ( _ray$3.intersectSphere( _sphere$5, _sphereHitAt ) === null ) return; - - if ( _ray$3.origin.distanceToSquared( _sphereHitAt ) > ( raycaster.far - raycaster.near ) ** 2 ) return; - - } - - // convert ray to local space of mesh - - _inverseMatrix$3.copy( matrixWorld ).invert(); - _ray$3.copy( raycaster.ray ).applyMatrix4( _inverseMatrix$3 ); - - // test with bounding box in local space - - if ( geometry.boundingBox !== null ) { - - if ( _ray$3.intersectsBox( geometry.boundingBox ) === false ) return; - - } - - // test for intersections with geometry - - this._computeIntersections( raycaster, intersects, _ray$3 ); - - } - - _computeIntersections( raycaster, intersects, rayLocalSpace ) { - - let intersection; - - const geometry = this.geometry; - const material = this.material; - - const index = geometry.index; - const position = geometry.attributes.position; - const uv = geometry.attributes.uv; - const uv1 = geometry.attributes.uv1; - const normal = geometry.attributes.normal; - const groups = geometry.groups; - const drawRange = geometry.drawRange; - - if ( index !== null ) { - - // indexed buffer geometry - - if ( Array.isArray( material ) ) { - - for ( let i = 0, il = groups.length; i < il; i ++ ) { - - const group = groups[ i ]; - const groupMaterial = material[ group.materialIndex ]; - - const start = Math.max( group.start, drawRange.start ); - const end = Math.min( index.count, Math.min( ( group.start + group.count ), ( drawRange.start + drawRange.count ) ) ); - - for ( let j = start, jl = end; j < jl; j += 3 ) { - - const a = index.getX( j ); - const b = index.getX( j + 1 ); - const c = index.getX( j + 2 ); - - intersection = checkGeometryIntersection( this, groupMaterial, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c ); - - if ( intersection ) { - - intersection.faceIndex = Math.floor( j / 3 ); // triangle number in indexed buffer semantics - intersection.face.materialIndex = group.materialIndex; - intersects.push( intersection ); - - } - - } - - } - - } else { - - const start = Math.max( 0, drawRange.start ); - const end = Math.min( index.count, ( drawRange.start + drawRange.count ) ); - - for ( let i = start, il = end; i < il; i += 3 ) { - - const a = index.getX( i ); - const b = index.getX( i + 1 ); - const c = index.getX( i + 2 ); - - intersection = checkGeometryIntersection( this, material, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c ); - - if ( intersection ) { - - intersection.faceIndex = Math.floor( i / 3 ); // triangle number in indexed buffer semantics - intersects.push( intersection ); - - } - - } - - } - - } else if ( position !== undefined ) { - - // non-indexed buffer geometry - - if ( Array.isArray( material ) ) { - - for ( let i = 0, il = groups.length; i < il; i ++ ) { - - const group = groups[ i ]; - const groupMaterial = material[ group.materialIndex ]; - - const start = Math.max( group.start, drawRange.start ); - const end = Math.min( position.count, Math.min( ( group.start + group.count ), ( drawRange.start + drawRange.count ) ) ); - - for ( let j = start, jl = end; j < jl; j += 3 ) { - - const a = j; - const b = j + 1; - const c = j + 2; - - intersection = checkGeometryIntersection( this, groupMaterial, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c ); - - if ( intersection ) { - - intersection.faceIndex = Math.floor( j / 3 ); // triangle number in non-indexed buffer semantics - intersection.face.materialIndex = group.materialIndex; - intersects.push( intersection ); - - } - - } - - } - - } else { - - const start = Math.max( 0, drawRange.start ); - const end = Math.min( position.count, ( drawRange.start + drawRange.count ) ); - - for ( let i = start, il = end; i < il; i += 3 ) { - - const a = i; - const b = i + 1; - const c = i + 2; - - intersection = checkGeometryIntersection( this, material, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c ); - - if ( intersection ) { - - intersection.faceIndex = Math.floor( i / 3 ); // triangle number in non-indexed buffer semantics - intersects.push( intersection ); - - } - - } - - } - - } - - } - -} - -function checkIntersection( object, material, raycaster, ray, pA, pB, pC, point ) { - - let intersect; - - if ( material.side === BackSide ) { - - intersect = ray.intersectTriangle( pC, pB, pA, true, point ); - - } else { - - intersect = ray.intersectTriangle( pA, pB, pC, ( material.side === FrontSide ), point ); - - } - - if ( intersect === null ) return null; - - _intersectionPointWorld.copy( point ); - _intersectionPointWorld.applyMatrix4( object.matrixWorld ); - - const distance = raycaster.ray.origin.distanceTo( _intersectionPointWorld ); - - if ( distance < raycaster.near || distance > raycaster.far ) return null; - - return { - distance: distance, - point: _intersectionPointWorld.clone(), - object: object - }; - -} - -function checkGeometryIntersection( object, material, raycaster, ray, uv, uv1, normal, a, b, c ) { - - object.getVertexPosition( a, _vA$1 ); - object.getVertexPosition( b, _vB$1 ); - object.getVertexPosition( c, _vC$1 ); - - const intersection = checkIntersection( object, material, raycaster, ray, _vA$1, _vB$1, _vC$1, _intersectionPoint ); - - if ( intersection ) { - - if ( uv ) { - - _uvA$1.fromBufferAttribute( uv, a ); - _uvB$1.fromBufferAttribute( uv, b ); - _uvC$1.fromBufferAttribute( uv, c ); - - intersection.uv = Triangle.getInterpolation( _intersectionPoint, _vA$1, _vB$1, _vC$1, _uvA$1, _uvB$1, _uvC$1, new Vector2() ); - - } - - if ( uv1 ) { - - _uvA$1.fromBufferAttribute( uv1, a ); - _uvB$1.fromBufferAttribute( uv1, b ); - _uvC$1.fromBufferAttribute( uv1, c ); - - intersection.uv1 = Triangle.getInterpolation( _intersectionPoint, _vA$1, _vB$1, _vC$1, _uvA$1, _uvB$1, _uvC$1, new Vector2() ); - intersection.uv2 = intersection.uv1; // @deprecated, r152 - - } - - if ( normal ) { - - _normalA.fromBufferAttribute( normal, a ); - _normalB.fromBufferAttribute( normal, b ); - _normalC.fromBufferAttribute( normal, c ); - - intersection.normal = Triangle.getInterpolation( _intersectionPoint, _vA$1, _vB$1, _vC$1, _normalA, _normalB, _normalC, new Vector3() ); - - if ( intersection.normal.dot( ray.direction ) > 0 ) { - - intersection.normal.multiplyScalar( - 1 ); - - } - - } - - const face = { - a: a, - b: b, - c: c, - normal: new Vector3(), - materialIndex: 0 - }; - - Triangle.getNormal( _vA$1, _vB$1, _vC$1, face.normal ); - - intersection.face = face; - - } - - return intersection; - -} - -class BoxGeometry extends BufferGeometry { - - constructor( width = 1, height = 1, depth = 1, widthSegments = 1, heightSegments = 1, depthSegments = 1 ) { - - super(); - - this.type = 'BoxGeometry'; - - this.parameters = { - width: width, - height: height, - depth: depth, - widthSegments: widthSegments, - heightSegments: heightSegments, - depthSegments: depthSegments - }; - - const scope = this; - - // segments - - widthSegments = Math.floor( widthSegments ); - heightSegments = Math.floor( heightSegments ); - depthSegments = Math.floor( depthSegments ); - - // buffers - - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; - - // helper variables - - let numberOfVertices = 0; - let groupStart = 0; - - // build each side of the box geometry - - buildPlane( 'z', 'y', 'x', - 1, - 1, depth, height, width, depthSegments, heightSegments, 0 ); // px - buildPlane( 'z', 'y', 'x', 1, - 1, depth, height, - width, depthSegments, heightSegments, 1 ); // nx - buildPlane( 'x', 'z', 'y', 1, 1, width, depth, height, widthSegments, depthSegments, 2 ); // py - buildPlane( 'x', 'z', 'y', 1, - 1, width, depth, - height, widthSegments, depthSegments, 3 ); // ny - buildPlane( 'x', 'y', 'z', 1, - 1, width, height, depth, widthSegments, heightSegments, 4 ); // pz - buildPlane( 'x', 'y', 'z', - 1, - 1, width, height, - depth, widthSegments, heightSegments, 5 ); // nz - - // build geometry - - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - - function buildPlane( u, v, w, udir, vdir, width, height, depth, gridX, gridY, materialIndex ) { - - const segmentWidth = width / gridX; - const segmentHeight = height / gridY; - - const widthHalf = width / 2; - const heightHalf = height / 2; - const depthHalf = depth / 2; - - const gridX1 = gridX + 1; - const gridY1 = gridY + 1; - - let vertexCounter = 0; - let groupCount = 0; - - const vector = new Vector3(); - - // generate vertices, normals and uvs - - for ( let iy = 0; iy < gridY1; iy ++ ) { - - const y = iy * segmentHeight - heightHalf; - - for ( let ix = 0; ix < gridX1; ix ++ ) { - - const x = ix * segmentWidth - widthHalf; - - // set values to correct vector component - - vector[ u ] = x * udir; - vector[ v ] = y * vdir; - vector[ w ] = depthHalf; - - // now apply vector to vertex buffer - - vertices.push( vector.x, vector.y, vector.z ); - - // set values to correct vector component - - vector[ u ] = 0; - vector[ v ] = 0; - vector[ w ] = depth > 0 ? 1 : - 1; - - // now apply vector to normal buffer - - normals.push( vector.x, vector.y, vector.z ); - - // uvs - - uvs.push( ix / gridX ); - uvs.push( 1 - ( iy / gridY ) ); - - // counters - - vertexCounter += 1; - - } - - } - - // indices - - // 1. you need three indices to draw a single face - // 2. a single segment consists of two faces - // 3. so we need to generate six (2*3) indices per segment - - for ( let iy = 0; iy < gridY; iy ++ ) { - - for ( let ix = 0; ix < gridX; ix ++ ) { - - const a = numberOfVertices + ix + gridX1 * iy; - const b = numberOfVertices + ix + gridX1 * ( iy + 1 ); - const c = numberOfVertices + ( ix + 1 ) + gridX1 * ( iy + 1 ); - const d = numberOfVertices + ( ix + 1 ) + gridX1 * iy; - - // faces - - indices.push( a, b, d ); - indices.push( b, c, d ); - - // increase counter - - groupCount += 6; - - } - - } - - // add a group to the geometry. this will ensure multi material support - - scope.addGroup( groupStart, groupCount, materialIndex ); - - // calculate new start value for groups - - groupStart += groupCount; - - // update total number of vertices - - numberOfVertices += vertexCounter; - - } - - } - - copy( source ) { - - super.copy( source ); - - this.parameters = Object.assign( {}, source.parameters ); - - return this; - - } - - static fromJSON( data ) { - - return new BoxGeometry( data.width, data.height, data.depth, data.widthSegments, data.heightSegments, data.depthSegments ); - - } - -} - -/** - * Uniform Utilities - */ - -function cloneUniforms( src ) { - - const dst = {}; - - for ( const u in src ) { - - dst[ u ] = {}; - - for ( const p in src[ u ] ) { - - const property = src[ u ][ p ]; - - if ( property && ( property.isColor || - property.isMatrix3 || property.isMatrix4 || - property.isVector2 || property.isVector3 || property.isVector4 || - property.isTexture || property.isQuaternion ) ) { - - if ( property.isRenderTargetTexture ) { - - console.warn( 'UniformsUtils: Textures of render targets cannot be cloned via cloneUniforms() or mergeUniforms().' ); - dst[ u ][ p ] = null; - - } else { - - dst[ u ][ p ] = property.clone(); - - } - - } else if ( Array.isArray( property ) ) { - - dst[ u ][ p ] = property.slice(); - - } else { - - dst[ u ][ p ] = property; - - } - - } - - } - - return dst; - -} - -function mergeUniforms( uniforms ) { - - const merged = {}; - - for ( let u = 0; u < uniforms.length; u ++ ) { - - const tmp = cloneUniforms( uniforms[ u ] ); - - for ( const p in tmp ) { - - merged[ p ] = tmp[ p ]; - - } - - } - - return merged; - -} - -function cloneUniformsGroups( src ) { - - const dst = []; - - for ( let u = 0; u < src.length; u ++ ) { - - dst.push( src[ u ].clone() ); - - } - - return dst; - -} - -function getUnlitUniformColorSpace( renderer ) { - - if ( renderer.getRenderTarget() === null ) { - - // https://github.com/mrdoob/three.js/pull/23937#issuecomment-1111067398 - return renderer.outputColorSpace; - - } - - return LinearSRGBColorSpace; - -} - -// Legacy - -const UniformsUtils = { clone: cloneUniforms, merge: mergeUniforms }; - -var default_vertex = "void main() {\n\tgl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n}"; - -var default_fragment = "void main() {\n\tgl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0 );\n}"; - -class ShaderMaterial extends Material { - - constructor( parameters ) { - - super(); - - this.isShaderMaterial = true; - - this.type = 'ShaderMaterial'; - - this.defines = {}; - this.uniforms = {}; - this.uniformsGroups = []; - - this.vertexShader = default_vertex; - this.fragmentShader = default_fragment; - - this.linewidth = 1; - - this.wireframe = false; - this.wireframeLinewidth = 1; - - this.fog = false; // set to use scene fog - this.lights = false; // set to use scene lights - this.clipping = false; // set to use user-defined clipping planes - - this.forceSinglePass = true; - - this.extensions = { - derivatives: false, // set to use derivatives - fragDepth: false, // set to use fragment depth values - drawBuffers: false, // set to use draw buffers - shaderTextureLOD: false // set to use shader texture LOD - }; - - // When rendered geometry doesn't include these attributes but the material does, - // use these default values in WebGL. This avoids errors when buffer data is missing. - this.defaultAttributeValues = { - 'color': [ 1, 1, 1 ], - 'uv': [ 0, 0 ], - 'uv1': [ 0, 0 ] - }; - - this.index0AttributeName = undefined; - this.uniformsNeedUpdate = false; - - this.glslVersion = null; - - if ( parameters !== undefined ) { - - this.setValues( parameters ); - - } - - } - - copy( source ) { - - super.copy( source ); - - this.fragmentShader = source.fragmentShader; - this.vertexShader = source.vertexShader; - - this.uniforms = cloneUniforms( source.uniforms ); - this.uniformsGroups = cloneUniformsGroups( source.uniformsGroups ); - - this.defines = Object.assign( {}, source.defines ); - - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; - - this.fog = source.fog; - this.lights = source.lights; - this.clipping = source.clipping; - - this.extensions = Object.assign( {}, source.extensions ); - - this.glslVersion = source.glslVersion; - - return this; - - } - - toJSON( meta ) { - - const data = super.toJSON( meta ); - - data.glslVersion = this.glslVersion; - data.uniforms = {}; - - for ( const name in this.uniforms ) { - - const uniform = this.uniforms[ name ]; - const value = uniform.value; - - if ( value && value.isTexture ) { - - data.uniforms[ name ] = { - type: 't', - value: value.toJSON( meta ).uuid - }; - - } else if ( value && value.isColor ) { - - data.uniforms[ name ] = { - type: 'c', - value: value.getHex() - }; - - } else if ( value && value.isVector2 ) { - - data.uniforms[ name ] = { - type: 'v2', - value: value.toArray() - }; - - } else if ( value && value.isVector3 ) { - - data.uniforms[ name ] = { - type: 'v3', - value: value.toArray() - }; - - } else if ( value && value.isVector4 ) { - - data.uniforms[ name ] = { - type: 'v4', - value: value.toArray() - }; - - } else if ( value && value.isMatrix3 ) { - - data.uniforms[ name ] = { - type: 'm3', - value: value.toArray() - }; - - } else if ( value && value.isMatrix4 ) { - - data.uniforms[ name ] = { - type: 'm4', - value: value.toArray() - }; - - } else { - - data.uniforms[ name ] = { - value: value - }; - - // note: the array variants v2v, v3v, v4v, m4v and tv are not supported so far - - } - - } - - if ( Object.keys( this.defines ).length > 0 ) data.defines = this.defines; - - data.vertexShader = this.vertexShader; - data.fragmentShader = this.fragmentShader; - - data.lights = this.lights; - data.clipping = this.clipping; - - const extensions = {}; - - for ( const key in this.extensions ) { - - if ( this.extensions[ key ] === true ) extensions[ key ] = true; - - } - - if ( Object.keys( extensions ).length > 0 ) data.extensions = extensions; - - return data; - - } - -} - -class Camera extends Object3D { - - constructor() { - - super(); - - this.isCamera = true; - - this.type = 'Camera'; - - this.matrixWorldInverse = new Matrix4(); - - this.projectionMatrix = new Matrix4(); - this.projectionMatrixInverse = new Matrix4(); - - } - - copy( source, recursive ) { - - super.copy( source, recursive ); - - this.matrixWorldInverse.copy( source.matrixWorldInverse ); - - this.projectionMatrix.copy( source.projectionMatrix ); - this.projectionMatrixInverse.copy( source.projectionMatrixInverse ); - - return this; - - } - - getWorldDirection( target ) { - - this.updateWorldMatrix( true, false ); - - const e = this.matrixWorld.elements; - - return target.set( - e[ 8 ], - e[ 9 ], - e[ 10 ] ).normalize(); - - } - - updateMatrixWorld( force ) { - - super.updateMatrixWorld( force ); - - this.matrixWorldInverse.copy( this.matrixWorld ).invert(); - - } - - updateWorldMatrix( updateParents, updateChildren ) { - - super.updateWorldMatrix( updateParents, updateChildren ); - - this.matrixWorldInverse.copy( this.matrixWorld ).invert(); - - } - - clone() { - - return new this.constructor().copy( this ); - - } - -} - -class PerspectiveCamera extends Camera { - - constructor( fov = 50, aspect = 1, near = 0.1, far = 2000 ) { - - super(); - - this.isPerspectiveCamera = true; - - this.type = 'PerspectiveCamera'; - - this.fov = fov; - this.zoom = 1; - - this.near = near; - this.far = far; - this.focus = 10; - - this.aspect = aspect; - this.view = null; - - this.filmGauge = 35; // width of the film (default in millimeters) - this.filmOffset = 0; // horizontal film offset (same unit as gauge) - - this.updateProjectionMatrix(); - - } - - copy( source, recursive ) { - - super.copy( source, recursive ); - - this.fov = source.fov; - this.zoom = source.zoom; - - this.near = source.near; - this.far = source.far; - this.focus = source.focus; - - this.aspect = source.aspect; - this.view = source.view === null ? null : Object.assign( {}, source.view ); - - this.filmGauge = source.filmGauge; - this.filmOffset = source.filmOffset; - - return this; - - } - - /** - * Sets the FOV by focal length in respect to the current .filmGauge. - * - * The default film gauge is 35, so that the focal length can be specified for - * a 35mm (full frame) camera. - * - * Values for focal length and film gauge must have the same unit. - */ - setFocalLength( focalLength ) { - - /** see {@link http://www.bobatkins.com/photography/technical/field_of_view.html} */ - const vExtentSlope = 0.5 * this.getFilmHeight() / focalLength; - - this.fov = RAD2DEG * 2 * Math.atan( vExtentSlope ); - this.updateProjectionMatrix(); - - } - - /** - * Calculates the focal length from the current .fov and .filmGauge. - */ - getFocalLength() { - - const vExtentSlope = Math.tan( DEG2RAD * 0.5 * this.fov ); - - return 0.5 * this.getFilmHeight() / vExtentSlope; - - } - - getEffectiveFOV() { - - return RAD2DEG * 2 * Math.atan( - Math.tan( DEG2RAD * 0.5 * this.fov ) / this.zoom ); - - } - - getFilmWidth() { - - // film not completely covered in portrait format (aspect < 1) - return this.filmGauge * Math.min( this.aspect, 1 ); - - } - - getFilmHeight() { - - // film not completely covered in landscape format (aspect > 1) - return this.filmGauge / Math.max( this.aspect, 1 ); - - } - - /** - * Sets an offset in a larger frustum. This is useful for multi-window or - * multi-monitor/multi-machine setups. - * - * For example, if you have 3x2 monitors and each monitor is 1920x1080 and - * the monitors are in grid like this - * - * +---+---+---+ - * | A | B | C | - * +---+---+---+ - * | D | E | F | - * +---+---+---+ - * - * then for each monitor you would call it like this - * - * const w = 1920; - * const h = 1080; - * const fullWidth = w * 3; - * const fullHeight = h * 2; - * - * --A-- - * camera.setViewOffset( fullWidth, fullHeight, w * 0, h * 0, w, h ); - * --B-- - * camera.setViewOffset( fullWidth, fullHeight, w * 1, h * 0, w, h ); - * --C-- - * camera.setViewOffset( fullWidth, fullHeight, w * 2, h * 0, w, h ); - * --D-- - * camera.setViewOffset( fullWidth, fullHeight, w * 0, h * 1, w, h ); - * --E-- - * camera.setViewOffset( fullWidth, fullHeight, w * 1, h * 1, w, h ); - * --F-- - * camera.setViewOffset( fullWidth, fullHeight, w * 2, h * 1, w, h ); - * - * Note there is no reason monitors have to be the same size or in a grid. - */ - setViewOffset( fullWidth, fullHeight, x, y, width, height ) { - - this.aspect = fullWidth / fullHeight; - - if ( this.view === null ) { - - this.view = { - enabled: true, - fullWidth: 1, - fullHeight: 1, - offsetX: 0, - offsetY: 0, - width: 1, - height: 1 - }; - - } - - this.view.enabled = true; - this.view.fullWidth = fullWidth; - this.view.fullHeight = fullHeight; - this.view.offsetX = x; - this.view.offsetY = y; - this.view.width = width; - this.view.height = height; - - this.updateProjectionMatrix(); - - } - - clearViewOffset() { - - if ( this.view !== null ) { - - this.view.enabled = false; - - } - - this.updateProjectionMatrix(); - - } - - updateProjectionMatrix() { - - const near = this.near; - let top = near * Math.tan( DEG2RAD * 0.5 * this.fov ) / this.zoom; - let height = 2 * top; - let width = this.aspect * height; - let left = - 0.5 * width; - const view = this.view; - - if ( this.view !== null && this.view.enabled ) { - - const fullWidth = view.fullWidth, - fullHeight = view.fullHeight; - - left += view.offsetX * width / fullWidth; - top -= view.offsetY * height / fullHeight; - width *= view.width / fullWidth; - height *= view.height / fullHeight; - - } - - const skew = this.filmOffset; - if ( skew !== 0 ) left += near * skew / this.getFilmWidth(); - - this.projectionMatrix.makePerspective( left, left + width, top, top - height, near, this.far ); - - this.projectionMatrixInverse.copy( this.projectionMatrix ).invert(); - - } - - toJSON( meta ) { - - const data = super.toJSON( meta ); - - data.object.fov = this.fov; - data.object.zoom = this.zoom; - - data.object.near = this.near; - data.object.far = this.far; - data.object.focus = this.focus; - - data.object.aspect = this.aspect; - - if ( this.view !== null ) data.object.view = Object.assign( {}, this.view ); - - data.object.filmGauge = this.filmGauge; - data.object.filmOffset = this.filmOffset; - - return data; - - } - -} - -const fov = - 90; // negative fov is not an error -const aspect = 1; - -class CubeCamera extends Object3D { - - constructor( near, far, renderTarget ) { - - super(); - - this.type = 'CubeCamera'; - - this.renderTarget = renderTarget; - - const cameraPX = new PerspectiveCamera( fov, aspect, near, far ); - cameraPX.layers = this.layers; - cameraPX.up.set( 0, 1, 0 ); - cameraPX.lookAt( 1, 0, 0 ); - this.add( cameraPX ); - - const cameraNX = new PerspectiveCamera( fov, aspect, near, far ); - cameraNX.layers = this.layers; - cameraNX.up.set( 0, 1, 0 ); - cameraNX.lookAt( - 1, 0, 0 ); - this.add( cameraNX ); - - const cameraPY = new PerspectiveCamera( fov, aspect, near, far ); - cameraPY.layers = this.layers; - cameraPY.up.set( 0, 0, - 1 ); - cameraPY.lookAt( 0, 1, 0 ); - this.add( cameraPY ); - - const cameraNY = new PerspectiveCamera( fov, aspect, near, far ); - cameraNY.layers = this.layers; - cameraNY.up.set( 0, 0, 1 ); - cameraNY.lookAt( 0, - 1, 0 ); - this.add( cameraNY ); - - const cameraPZ = new PerspectiveCamera( fov, aspect, near, far ); - cameraPZ.layers = this.layers; - cameraPZ.up.set( 0, 1, 0 ); - cameraPZ.lookAt( 0, 0, 1 ); - this.add( cameraPZ ); - - const cameraNZ = new PerspectiveCamera( fov, aspect, near, far ); - cameraNZ.layers = this.layers; - cameraNZ.up.set( 0, 1, 0 ); - cameraNZ.lookAt( 0, 0, - 1 ); - this.add( cameraNZ ); - - } - - update( renderer, scene ) { - - if ( this.parent === null ) this.updateMatrixWorld(); - - const renderTarget = this.renderTarget; - - const [ cameraPX, cameraNX, cameraPY, cameraNY, cameraPZ, cameraNZ ] = this.children; - - const currentRenderTarget = renderer.getRenderTarget(); - - const currentToneMapping = renderer.toneMapping; - const currentXrEnabled = renderer.xr.enabled; - - renderer.toneMapping = NoToneMapping; - renderer.xr.enabled = false; - - const generateMipmaps = renderTarget.texture.generateMipmaps; - - renderTarget.texture.generateMipmaps = false; - - renderer.setRenderTarget( renderTarget, 0 ); - renderer.render( scene, cameraPX ); - - renderer.setRenderTarget( renderTarget, 1 ); - renderer.render( scene, cameraNX ); - - renderer.setRenderTarget( renderTarget, 2 ); - renderer.render( scene, cameraPY ); - - renderer.setRenderTarget( renderTarget, 3 ); - renderer.render( scene, cameraNY ); - - renderer.setRenderTarget( renderTarget, 4 ); - renderer.render( scene, cameraPZ ); - - renderTarget.texture.generateMipmaps = generateMipmaps; - - renderer.setRenderTarget( renderTarget, 5 ); - renderer.render( scene, cameraNZ ); - - renderer.setRenderTarget( currentRenderTarget ); - - renderer.toneMapping = currentToneMapping; - renderer.xr.enabled = currentXrEnabled; - - renderTarget.texture.needsPMREMUpdate = true; - - } - -} - -class CubeTexture extends Texture { - - constructor( images, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, colorSpace ) { - - images = images !== undefined ? images : []; - mapping = mapping !== undefined ? mapping : CubeReflectionMapping; - - super( images, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, colorSpace ); - - this.isCubeTexture = true; - - this.flipY = false; - - } - - get images() { - - return this.image; - - } - - set images( value ) { - - this.image = value; - - } - -} - -class WebGLCubeRenderTarget extends WebGLRenderTarget { - - constructor( size = 1, options = {} ) { - - super( size, size, options ); - - this.isWebGLCubeRenderTarget = true; - - const image = { width: size, height: size, depth: 1 }; - const images = [ image, image, image, image, image, image ]; - - if ( options.encoding !== undefined ) { - - // @deprecated, r152 - warnOnce( 'THREE.WebGLCubeRenderTarget: option.encoding has been replaced by option.colorSpace.' ); - options.colorSpace = options.encoding === sRGBEncoding ? SRGBColorSpace : NoColorSpace; - - } - - this.texture = new CubeTexture( images, options.mapping, options.wrapS, options.wrapT, options.magFilter, options.minFilter, options.format, options.type, options.anisotropy, options.colorSpace ); - - // By convention -- likely based on the RenderMan spec from the 1990's -- cube maps are specified by WebGL (and three.js) - // in a coordinate system in which positive-x is to the right when looking up the positive-z axis -- in other words, - // in a left-handed coordinate system. By continuing this convention, preexisting cube maps continued to render correctly. - - // three.js uses a right-handed coordinate system. So environment maps used in three.js appear to have px and nx swapped - // and the flag isRenderTargetTexture controls this conversion. The flip is not required when using WebGLCubeRenderTarget.texture - // as a cube texture (this is detected when isRenderTargetTexture is set to true for cube textures). - - this.texture.isRenderTargetTexture = true; - - this.texture.generateMipmaps = options.generateMipmaps !== undefined ? options.generateMipmaps : false; - this.texture.minFilter = options.minFilter !== undefined ? options.minFilter : LinearFilter; - - } - - fromEquirectangularTexture( renderer, texture ) { - - this.texture.type = texture.type; - this.texture.colorSpace = texture.colorSpace; - - this.texture.generateMipmaps = texture.generateMipmaps; - this.texture.minFilter = texture.minFilter; - this.texture.magFilter = texture.magFilter; - - const shader = { - - uniforms: { - tEquirect: { value: null }, - }, - - vertexShader: /* glsl */` - - varying vec3 vWorldDirection; - - vec3 transformDirection( in vec3 dir, in mat4 matrix ) { - - return normalize( ( matrix * vec4( dir, 0.0 ) ).xyz ); - - } - - void main() { - - vWorldDirection = transformDirection( position, modelMatrix ); - - #include - #include - - } - `, - - fragmentShader: /* glsl */` - - uniform sampler2D tEquirect; - - varying vec3 vWorldDirection; - - #include - - void main() { - - vec3 direction = normalize( vWorldDirection ); - - vec2 sampleUV = equirectUv( direction ); - - gl_FragColor = texture2D( tEquirect, sampleUV ); - - } - ` - }; - - const geometry = new BoxGeometry( 5, 5, 5 ); - - const material = new ShaderMaterial( { - - name: 'CubemapFromEquirect', - - uniforms: cloneUniforms( shader.uniforms ), - vertexShader: shader.vertexShader, - fragmentShader: shader.fragmentShader, - side: BackSide, - blending: NoBlending - - } ); - - material.uniforms.tEquirect.value = texture; - - const mesh = new Mesh( geometry, material ); - - const currentMinFilter = texture.minFilter; - - // Avoid blurred poles - if ( texture.minFilter === LinearMipmapLinearFilter ) texture.minFilter = LinearFilter; - - const camera = new CubeCamera( 1, 10, this ); - camera.update( renderer, mesh ); - - texture.minFilter = currentMinFilter; - - mesh.geometry.dispose(); - mesh.material.dispose(); - - return this; - - } - - clear( renderer, color, depth, stencil ) { - - const currentRenderTarget = renderer.getRenderTarget(); - - for ( let i = 0; i < 6; i ++ ) { - - renderer.setRenderTarget( this, i ); - - renderer.clear( color, depth, stencil ); - - } - - renderer.setRenderTarget( currentRenderTarget ); - - } - -} - -const _vector1 = /*@__PURE__*/ new Vector3(); -const _vector2 = /*@__PURE__*/ new Vector3(); -const _normalMatrix = /*@__PURE__*/ new Matrix3(); - -class Plane { - - constructor( normal = new Vector3( 1, 0, 0 ), constant = 0 ) { - - this.isPlane = true; - - // normal is assumed to be normalized - - this.normal = normal; - this.constant = constant; - - } - - set( normal, constant ) { - - this.normal.copy( normal ); - this.constant = constant; - - return this; - - } - - setComponents( x, y, z, w ) { - - this.normal.set( x, y, z ); - this.constant = w; - - return this; - - } - - setFromNormalAndCoplanarPoint( normal, point ) { - - this.normal.copy( normal ); - this.constant = - point.dot( this.normal ); - - return this; - - } - - setFromCoplanarPoints( a, b, c ) { - - const normal = _vector1.subVectors( c, b ).cross( _vector2.subVectors( a, b ) ).normalize(); - - // Q: should an error be thrown if normal is zero (e.g. degenerate plane)? - - this.setFromNormalAndCoplanarPoint( normal, a ); - - return this; - - } - - copy( plane ) { - - this.normal.copy( plane.normal ); - this.constant = plane.constant; - - return this; - - } - - normalize() { - - // Note: will lead to a divide by zero if the plane is invalid. - - const inverseNormalLength = 1.0 / this.normal.length(); - this.normal.multiplyScalar( inverseNormalLength ); - this.constant *= inverseNormalLength; - - return this; - - } - - negate() { - - this.constant *= - 1; - this.normal.negate(); - - return this; - - } - - distanceToPoint( point ) { - - return this.normal.dot( point ) + this.constant; - - } - - distanceToSphere( sphere ) { - - return this.distanceToPoint( sphere.center ) - sphere.radius; - - } - - projectPoint( point, target ) { - - return target.copy( point ).addScaledVector( this.normal, - this.distanceToPoint( point ) ); - - } - - intersectLine( line, target ) { - - const direction = line.delta( _vector1 ); - - const denominator = this.normal.dot( direction ); - - if ( denominator === 0 ) { - - // line is coplanar, return origin - if ( this.distanceToPoint( line.start ) === 0 ) { - - return target.copy( line.start ); - - } - - // Unsure if this is the correct method to handle this case. - return null; - - } - - const t = - ( line.start.dot( this.normal ) + this.constant ) / denominator; - - if ( t < 0 || t > 1 ) { - - return null; - - } - - return target.copy( line.start ).addScaledVector( direction, t ); - - } - - intersectsLine( line ) { - - // Note: this tests if a line intersects the plane, not whether it (or its end-points) are coplanar with it. - - const startSign = this.distanceToPoint( line.start ); - const endSign = this.distanceToPoint( line.end ); - - return ( startSign < 0 && endSign > 0 ) || ( endSign < 0 && startSign > 0 ); - - } - - intersectsBox( box ) { - - return box.intersectsPlane( this ); - - } - - intersectsSphere( sphere ) { - - return sphere.intersectsPlane( this ); - - } - - coplanarPoint( target ) { - - return target.copy( this.normal ).multiplyScalar( - this.constant ); - - } - - applyMatrix4( matrix, optionalNormalMatrix ) { - - const normalMatrix = optionalNormalMatrix || _normalMatrix.getNormalMatrix( matrix ); - - const referencePoint = this.coplanarPoint( _vector1 ).applyMatrix4( matrix ); - - const normal = this.normal.applyMatrix3( normalMatrix ).normalize(); - - this.constant = - referencePoint.dot( normal ); - - return this; - - } - - translate( offset ) { - - this.constant -= offset.dot( this.normal ); - - return this; - - } - - equals( plane ) { - - return plane.normal.equals( this.normal ) && ( plane.constant === this.constant ); - - } - - clone() { - - return new this.constructor().copy( this ); - - } - -} - -const _sphere$4 = /*@__PURE__*/ new Sphere(); -const _vector$6 = /*@__PURE__*/ new Vector3(); - -class Frustum { - - constructor( p0 = new Plane(), p1 = new Plane(), p2 = new Plane(), p3 = new Plane(), p4 = new Plane(), p5 = new Plane() ) { - - this.planes = [ p0, p1, p2, p3, p4, p5 ]; - - } - - set( p0, p1, p2, p3, p4, p5 ) { - - const planes = this.planes; - - planes[ 0 ].copy( p0 ); - planes[ 1 ].copy( p1 ); - planes[ 2 ].copy( p2 ); - planes[ 3 ].copy( p3 ); - planes[ 4 ].copy( p4 ); - planes[ 5 ].copy( p5 ); - - return this; - - } - - copy( frustum ) { - - const planes = this.planes; - - for ( let i = 0; i < 6; i ++ ) { - - planes[ i ].copy( frustum.planes[ i ] ); - - } - - return this; - - } - - setFromProjectionMatrix( m ) { - - const planes = this.planes; - const me = m.elements; - const me0 = me[ 0 ], me1 = me[ 1 ], me2 = me[ 2 ], me3 = me[ 3 ]; - const me4 = me[ 4 ], me5 = me[ 5 ], me6 = me[ 6 ], me7 = me[ 7 ]; - const me8 = me[ 8 ], me9 = me[ 9 ], me10 = me[ 10 ], me11 = me[ 11 ]; - const me12 = me[ 12 ], me13 = me[ 13 ], me14 = me[ 14 ], me15 = me[ 15 ]; - - planes[ 0 ].setComponents( me3 - me0, me7 - me4, me11 - me8, me15 - me12 ).normalize(); - planes[ 1 ].setComponents( me3 + me0, me7 + me4, me11 + me8, me15 + me12 ).normalize(); - planes[ 2 ].setComponents( me3 + me1, me7 + me5, me11 + me9, me15 + me13 ).normalize(); - planes[ 3 ].setComponents( me3 - me1, me7 - me5, me11 - me9, me15 - me13 ).normalize(); - planes[ 4 ].setComponents( me3 - me2, me7 - me6, me11 - me10, me15 - me14 ).normalize(); - planes[ 5 ].setComponents( me3 + me2, me7 + me6, me11 + me10, me15 + me14 ).normalize(); - - return this; - - } - - intersectsObject( object ) { - - if ( object.boundingSphere !== undefined ) { - - if ( object.boundingSphere === null ) object.computeBoundingSphere(); - - _sphere$4.copy( object.boundingSphere ).applyMatrix4( object.matrixWorld ); - - } else { - - const geometry = object.geometry; - - if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); - - _sphere$4.copy( geometry.boundingSphere ).applyMatrix4( object.matrixWorld ); - - } - - return this.intersectsSphere( _sphere$4 ); - - } - - intersectsSprite( sprite ) { - - _sphere$4.center.set( 0, 0, 0 ); - _sphere$4.radius = 0.7071067811865476; - _sphere$4.applyMatrix4( sprite.matrixWorld ); - - return this.intersectsSphere( _sphere$4 ); - - } - - intersectsSphere( sphere ) { - - const planes = this.planes; - const center = sphere.center; - const negRadius = - sphere.radius; - - for ( let i = 0; i < 6; i ++ ) { - - const distance = planes[ i ].distanceToPoint( center ); - - if ( distance < negRadius ) { - - return false; - - } - - } - - return true; - - } - - intersectsBox( box ) { - - const planes = this.planes; - - for ( let i = 0; i < 6; i ++ ) { - - const plane = planes[ i ]; - - // corner at max distance - - _vector$6.x = plane.normal.x > 0 ? box.max.x : box.min.x; - _vector$6.y = plane.normal.y > 0 ? box.max.y : box.min.y; - _vector$6.z = plane.normal.z > 0 ? box.max.z : box.min.z; - - if ( plane.distanceToPoint( _vector$6 ) < 0 ) { - - return false; - - } - - } - - return true; - - } - - containsPoint( point ) { - - const planes = this.planes; - - for ( let i = 0; i < 6; i ++ ) { - - if ( planes[ i ].distanceToPoint( point ) < 0 ) { - - return false; - - } - - } - - return true; - - } - - clone() { - - return new this.constructor().copy( this ); - - } - -} - -function WebGLAnimation() { - - let context = null; - let isAnimating = false; - let animationLoop = null; - let requestId = null; - - function onAnimationFrame( time, frame ) { - - animationLoop( time, frame ); - - requestId = context.requestAnimationFrame( onAnimationFrame ); - - } - - return { - - start: function () { - - if ( isAnimating === true ) return; - if ( animationLoop === null ) return; - - requestId = context.requestAnimationFrame( onAnimationFrame ); - - isAnimating = true; - - }, - - stop: function () { - - context.cancelAnimationFrame( requestId ); - - isAnimating = false; - - }, - - setAnimationLoop: function ( callback ) { - - animationLoop = callback; - - }, - - setContext: function ( value ) { - - context = value; - - } - - }; - -} - -function WebGLAttributes( gl, capabilities ) { - - const isWebGL2 = capabilities.isWebGL2; - - const buffers = new WeakMap(); - - function createBuffer( attribute, bufferType ) { - - const array = attribute.array; - const usage = attribute.usage; - - const buffer = gl.createBuffer(); - - gl.bindBuffer( bufferType, buffer ); - gl.bufferData( bufferType, array, usage ); - - attribute.onUploadCallback(); - - let type; - - if ( array instanceof Float32Array ) { - - type = gl.FLOAT; - - } else if ( array instanceof Uint16Array ) { - - if ( attribute.isFloat16BufferAttribute ) { - - if ( isWebGL2 ) { - - type = gl.HALF_FLOAT; - - } else { - - throw new Error( 'THREE.WebGLAttributes: Usage of Float16BufferAttribute requires WebGL2.' ); - - } - - } else { - - type = gl.UNSIGNED_SHORT; - - } - - } else if ( array instanceof Int16Array ) { - - type = gl.SHORT; - - } else if ( array instanceof Uint32Array ) { - - type = gl.UNSIGNED_INT; - - } else if ( array instanceof Int32Array ) { - - type = gl.INT; - - } else if ( array instanceof Int8Array ) { - - type = gl.BYTE; - - } else if ( array instanceof Uint8Array ) { - - type = gl.UNSIGNED_BYTE; - - } else if ( array instanceof Uint8ClampedArray ) { - - type = gl.UNSIGNED_BYTE; - - } else { - - throw new Error( 'THREE.WebGLAttributes: Unsupported buffer data format: ' + array ); - - } - - return { - buffer: buffer, - type: type, - bytesPerElement: array.BYTES_PER_ELEMENT, - version: attribute.version - }; - - } - - function updateBuffer( buffer, attribute, bufferType ) { - - const array = attribute.array; - const updateRange = attribute.updateRange; - - gl.bindBuffer( bufferType, buffer ); - - if ( updateRange.count === - 1 ) { - - // Not using update ranges - - gl.bufferSubData( bufferType, 0, array ); - - } else { - - if ( isWebGL2 ) { - - gl.bufferSubData( bufferType, updateRange.offset * array.BYTES_PER_ELEMENT, - array, updateRange.offset, updateRange.count ); - - } else { - - gl.bufferSubData( bufferType, updateRange.offset * array.BYTES_PER_ELEMENT, - array.subarray( updateRange.offset, updateRange.offset + updateRange.count ) ); - - } - - updateRange.count = - 1; // reset range - - } - - attribute.onUploadCallback(); - - } - - // - - function get( attribute ) { - - if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; - - return buffers.get( attribute ); - - } - - function remove( attribute ) { - - if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; - - const data = buffers.get( attribute ); - - if ( data ) { - - gl.deleteBuffer( data.buffer ); - - buffers.delete( attribute ); - - } - - } - - function update( attribute, bufferType ) { - - if ( attribute.isGLBufferAttribute ) { - - const cached = buffers.get( attribute ); - - if ( ! cached || cached.version < attribute.version ) { - - buffers.set( attribute, { - buffer: attribute.buffer, - type: attribute.type, - bytesPerElement: attribute.elementSize, - version: attribute.version - } ); - - } - - return; - - } - - if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; - - const data = buffers.get( attribute ); - - if ( data === undefined ) { - - buffers.set( attribute, createBuffer( attribute, bufferType ) ); - - } else if ( data.version < attribute.version ) { - - updateBuffer( data.buffer, attribute, bufferType ); - - data.version = attribute.version; - - } - - } - - return { - - get: get, - remove: remove, - update: update - - }; - -} - -class PlaneGeometry extends BufferGeometry { - - constructor( width = 1, height = 1, widthSegments = 1, heightSegments = 1 ) { - - super(); - - this.type = 'PlaneGeometry'; - - this.parameters = { - width: width, - height: height, - widthSegments: widthSegments, - heightSegments: heightSegments - }; - - const width_half = width / 2; - const height_half = height / 2; - - const gridX = Math.floor( widthSegments ); - const gridY = Math.floor( heightSegments ); - - const gridX1 = gridX + 1; - const gridY1 = gridY + 1; - - const segment_width = width / gridX; - const segment_height = height / gridY; - - // - - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; - - for ( let iy = 0; iy < gridY1; iy ++ ) { - - const y = iy * segment_height - height_half; - - for ( let ix = 0; ix < gridX1; ix ++ ) { - - const x = ix * segment_width - width_half; - - vertices.push( x, - y, 0 ); - - normals.push( 0, 0, 1 ); - - uvs.push( ix / gridX ); - uvs.push( 1 - ( iy / gridY ) ); - - } - - } - - for ( let iy = 0; iy < gridY; iy ++ ) { - - for ( let ix = 0; ix < gridX; ix ++ ) { - - const a = ix + gridX1 * iy; - const b = ix + gridX1 * ( iy + 1 ); - const c = ( ix + 1 ) + gridX1 * ( iy + 1 ); - const d = ( ix + 1 ) + gridX1 * iy; - - indices.push( a, b, d ); - indices.push( b, c, d ); - - } - - } - - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - - } - - copy( source ) { - - super.copy( source ); - - this.parameters = Object.assign( {}, source.parameters ); - - return this; - - } - - static fromJSON( data ) { - - return new PlaneGeometry( data.width, data.height, data.widthSegments, data.heightSegments ); - - } - -} - -var alphamap_fragment = "#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, vAlphaMapUv ).g;\n#endif"; - -var alphamap_pars_fragment = "#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif"; - -var alphatest_fragment = "#ifdef USE_ALPHATEST\n\tif ( diffuseColor.a < alphaTest ) discard;\n#endif"; - -var alphatest_pars_fragment = "#ifdef USE_ALPHATEST\n\tuniform float alphaTest;\n#endif"; - -var aomap_fragment = "#ifdef USE_AOMAP\n\tfloat ambientOcclusion = ( texture2D( aoMap, vAoMapUv ).r - 1.0 ) * aoMapIntensity + 1.0;\n\treflectedLight.indirectDiffuse *= ambientOcclusion;\n\t#if defined( USE_ENVMAP ) && defined( STANDARD )\n\t\tfloat dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );\n\t\treflectedLight.indirectSpecular *= computeSpecularOcclusion( dotNV, ambientOcclusion, material.roughness );\n\t#endif\n#endif"; - -var aomap_pars_fragment = "#ifdef USE_AOMAP\n\tuniform sampler2D aoMap;\n\tuniform float aoMapIntensity;\n#endif"; - -var begin_vertex = "vec3 transformed = vec3( position );"; - -var beginnormal_vertex = "vec3 objectNormal = vec3( normal );\n#ifdef USE_TANGENT\n\tvec3 objectTangent = vec3( tangent.xyz );\n#endif"; - -var bsdfs = "float G_BlinnPhong_Implicit( ) {\n\treturn 0.25;\n}\nfloat D_BlinnPhong( const in float shininess, const in float dotNH ) {\n\treturn RECIPROCAL_PI * ( shininess * 0.5 + 1.0 ) * pow( dotNH, shininess );\n}\nvec3 BRDF_BlinnPhong( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in vec3 specularColor, const in float shininess ) {\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\tvec3 F = F_Schlick( specularColor, 1.0, dotVH );\n\tfloat G = G_BlinnPhong_Implicit( );\n\tfloat D = D_BlinnPhong( shininess, dotNH );\n\treturn F * ( G * D );\n} // validated"; - -var iridescence_fragment = "#ifdef USE_IRIDESCENCE\n\tconst mat3 XYZ_TO_REC709 = mat3(\n\t\t 3.2404542, -0.9692660, 0.0556434,\n\t\t-1.5371385, 1.8760108, -0.2040259,\n\t\t-0.4985314, 0.0415560, 1.0572252\n\t);\n\tvec3 Fresnel0ToIor( vec3 fresnel0 ) {\n\t\tvec3 sqrtF0 = sqrt( fresnel0 );\n\t\treturn ( vec3( 1.0 ) + sqrtF0 ) / ( vec3( 1.0 ) - sqrtF0 );\n\t}\n\tvec3 IorToFresnel0( vec3 transmittedIor, float incidentIor ) {\n\t\treturn pow2( ( transmittedIor - vec3( incidentIor ) ) / ( transmittedIor + vec3( incidentIor ) ) );\n\t}\n\tfloat IorToFresnel0( float transmittedIor, float incidentIor ) {\n\t\treturn pow2( ( transmittedIor - incidentIor ) / ( transmittedIor + incidentIor ));\n\t}\n\tvec3 evalSensitivity( float OPD, vec3 shift ) {\n\t\tfloat phase = 2.0 * PI * OPD * 1.0e-9;\n\t\tvec3 val = vec3( 5.4856e-13, 4.4201e-13, 5.2481e-13 );\n\t\tvec3 pos = vec3( 1.6810e+06, 1.7953e+06, 2.2084e+06 );\n\t\tvec3 var = vec3( 4.3278e+09, 9.3046e+09, 6.6121e+09 );\n\t\tvec3 xyz = val * sqrt( 2.0 * PI * var ) * cos( pos * phase + shift ) * exp( - pow2( phase ) * var );\n\t\txyz.x += 9.7470e-14 * sqrt( 2.0 * PI * 4.5282e+09 ) * cos( 2.2399e+06 * phase + shift[ 0 ] ) * exp( - 4.5282e+09 * pow2( phase ) );\n\t\txyz /= 1.0685e-7;\n\t\tvec3 rgb = XYZ_TO_REC709 * xyz;\n\t\treturn rgb;\n\t}\n\tvec3 evalIridescence( float outsideIOR, float eta2, float cosTheta1, float thinFilmThickness, vec3 baseF0 ) {\n\t\tvec3 I;\n\t\tfloat iridescenceIOR = mix( outsideIOR, eta2, smoothstep( 0.0, 0.03, thinFilmThickness ) );\n\t\tfloat sinTheta2Sq = pow2( outsideIOR / iridescenceIOR ) * ( 1.0 - pow2( cosTheta1 ) );\n\t\tfloat cosTheta2Sq = 1.0 - sinTheta2Sq;\n\t\tif ( cosTheta2Sq < 0.0 ) {\n\t\t\t return vec3( 1.0 );\n\t\t}\n\t\tfloat cosTheta2 = sqrt( cosTheta2Sq );\n\t\tfloat R0 = IorToFresnel0( iridescenceIOR, outsideIOR );\n\t\tfloat R12 = F_Schlick( R0, 1.0, cosTheta1 );\n\t\tfloat R21 = R12;\n\t\tfloat T121 = 1.0 - R12;\n\t\tfloat phi12 = 0.0;\n\t\tif ( iridescenceIOR < outsideIOR ) phi12 = PI;\n\t\tfloat phi21 = PI - phi12;\n\t\tvec3 baseIOR = Fresnel0ToIor( clamp( baseF0, 0.0, 0.9999 ) );\t\tvec3 R1 = IorToFresnel0( baseIOR, iridescenceIOR );\n\t\tvec3 R23 = F_Schlick( R1, 1.0, cosTheta2 );\n\t\tvec3 phi23 = vec3( 0.0 );\n\t\tif ( baseIOR[ 0 ] < iridescenceIOR ) phi23[ 0 ] = PI;\n\t\tif ( baseIOR[ 1 ] < iridescenceIOR ) phi23[ 1 ] = PI;\n\t\tif ( baseIOR[ 2 ] < iridescenceIOR ) phi23[ 2 ] = PI;\n\t\tfloat OPD = 2.0 * iridescenceIOR * thinFilmThickness * cosTheta2;\n\t\tvec3 phi = vec3( phi21 ) + phi23;\n\t\tvec3 R123 = clamp( R12 * R23, 1e-5, 0.9999 );\n\t\tvec3 r123 = sqrt( R123 );\n\t\tvec3 Rs = pow2( T121 ) * R23 / ( vec3( 1.0 ) - R123 );\n\t\tvec3 C0 = R12 + Rs;\n\t\tI = C0;\n\t\tvec3 Cm = Rs - T121;\n\t\tfor ( int m = 1; m <= 2; ++ m ) {\n\t\t\tCm *= r123;\n\t\t\tvec3 Sm = 2.0 * evalSensitivity( float( m ) * OPD, float( m ) * phi );\n\t\t\tI += Cm * Sm;\n\t\t}\n\t\treturn max( I, vec3( 0.0 ) );\n\t}\n#endif"; - -var bumpmap_pars_fragment = "#ifdef USE_BUMPMAP\n\tuniform sampler2D bumpMap;\n\tuniform float bumpScale;\n\tvec2 dHdxy_fwd() {\n\t\tvec2 dSTdx = dFdx( vBumpMapUv );\n\t\tvec2 dSTdy = dFdy( vBumpMapUv );\n\t\tfloat Hll = bumpScale * texture2D( bumpMap, vBumpMapUv ).x;\n\t\tfloat dBx = bumpScale * texture2D( bumpMap, vBumpMapUv + dSTdx ).x - Hll;\n\t\tfloat dBy = bumpScale * texture2D( bumpMap, vBumpMapUv + dSTdy ).x - Hll;\n\t\treturn vec2( dBx, dBy );\n\t}\n\tvec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy, float faceDirection ) {\n\t\tvec3 vSigmaX = dFdx( surf_pos.xyz );\n\t\tvec3 vSigmaY = dFdy( surf_pos.xyz );\n\t\tvec3 vN = surf_norm;\n\t\tvec3 R1 = cross( vSigmaY, vN );\n\t\tvec3 R2 = cross( vN, vSigmaX );\n\t\tfloat fDet = dot( vSigmaX, R1 ) * faceDirection;\n\t\tvec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );\n\t\treturn normalize( abs( fDet ) * surf_norm - vGrad );\n\t}\n#endif"; - -var clipping_planes_fragment = "#if NUM_CLIPPING_PLANES > 0\n\tvec4 plane;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) {\n\t\tplane = clippingPlanes[ i ];\n\t\tif ( dot( vClipPosition, plane.xyz ) > plane.w ) discard;\n\t}\n\t#pragma unroll_loop_end\n\t#if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES\n\t\tbool clipped = true;\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) {\n\t\t\tplane = clippingPlanes[ i ];\n\t\t\tclipped = ( dot( vClipPosition, plane.xyz ) > plane.w ) && clipped;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t\tif ( clipped ) discard;\n\t#endif\n#endif"; - -var clipping_planes_pars_fragment = "#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n\tuniform vec4 clippingPlanes[ NUM_CLIPPING_PLANES ];\n#endif"; - -var clipping_planes_pars_vertex = "#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n#endif"; - -var clipping_planes_vertex = "#if NUM_CLIPPING_PLANES > 0\n\tvClipPosition = - mvPosition.xyz;\n#endif"; - -var color_fragment = "#if defined( USE_COLOR_ALPHA )\n\tdiffuseColor *= vColor;\n#elif defined( USE_COLOR )\n\tdiffuseColor.rgb *= vColor;\n#endif"; - -var color_pars_fragment = "#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR )\n\tvarying vec3 vColor;\n#endif"; - -var color_pars_vertex = "#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR )\n\tvarying vec3 vColor;\n#endif"; - -var color_vertex = "#if defined( USE_COLOR_ALPHA )\n\tvColor = vec4( 1.0 );\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR )\n\tvColor = vec3( 1.0 );\n#endif\n#ifdef USE_COLOR\n\tvColor *= color;\n#endif\n#ifdef USE_INSTANCING_COLOR\n\tvColor.xyz *= instanceColor.xyz;\n#endif"; - -var common = "#define PI 3.141592653589793\n#define PI2 6.283185307179586\n#define PI_HALF 1.5707963267948966\n#define RECIPROCAL_PI 0.3183098861837907\n#define RECIPROCAL_PI2 0.15915494309189535\n#define EPSILON 1e-6\n#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\n#define whiteComplement( a ) ( 1.0 - saturate( a ) )\nfloat pow2( const in float x ) { return x*x; }\nvec3 pow2( const in vec3 x ) { return x*x; }\nfloat pow3( const in float x ) { return x*x*x; }\nfloat pow4( const in float x ) { float x2 = x*x; return x2*x2; }\nfloat max3( const in vec3 v ) { return max( max( v.x, v.y ), v.z ); }\nfloat average( const in vec3 v ) { return dot( v, vec3( 0.3333333 ) ); }\nhighp float rand( const in vec2 uv ) {\n\tconst highp float a = 12.9898, b = 78.233, c = 43758.5453;\n\thighp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI );\n\treturn fract( sin( sn ) * c );\n}\n#ifdef HIGH_PRECISION\n\tfloat precisionSafeLength( vec3 v ) { return length( v ); }\n#else\n\tfloat precisionSafeLength( vec3 v ) {\n\t\tfloat maxComponent = max3( abs( v ) );\n\t\treturn length( v / maxComponent ) * maxComponent;\n\t}\n#endif\nstruct IncidentLight {\n\tvec3 color;\n\tvec3 direction;\n\tbool visible;\n};\nstruct ReflectedLight {\n\tvec3 directDiffuse;\n\tvec3 directSpecular;\n\tvec3 indirectDiffuse;\n\tvec3 indirectSpecular;\n};\nstruct GeometricContext {\n\tvec3 position;\n\tvec3 normal;\n\tvec3 viewDir;\n#ifdef USE_CLEARCOAT\n\tvec3 clearcoatNormal;\n#endif\n};\nvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n}\nvec3 inverseTransformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( vec4( dir, 0.0 ) * matrix ).xyz );\n}\nmat3 transposeMat3( const in mat3 m ) {\n\tmat3 tmp;\n\ttmp[ 0 ] = vec3( m[ 0 ].x, m[ 1 ].x, m[ 2 ].x );\n\ttmp[ 1 ] = vec3( m[ 0 ].y, m[ 1 ].y, m[ 2 ].y );\n\ttmp[ 2 ] = vec3( m[ 0 ].z, m[ 1 ].z, m[ 2 ].z );\n\treturn tmp;\n}\nfloat luminance( const in vec3 rgb ) {\n\tconst vec3 weights = vec3( 0.2126729, 0.7151522, 0.0721750 );\n\treturn dot( weights, rgb );\n}\nbool isPerspectiveMatrix( mat4 m ) {\n\treturn m[ 2 ][ 3 ] == - 1.0;\n}\nvec2 equirectUv( in vec3 dir ) {\n\tfloat u = atan( dir.z, dir.x ) * RECIPROCAL_PI2 + 0.5;\n\tfloat v = asin( clamp( dir.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\n\treturn vec2( u, v );\n}\nvec3 BRDF_Lambert( const in vec3 diffuseColor ) {\n\treturn RECIPROCAL_PI * diffuseColor;\n}\nvec3 F_Schlick( const in vec3 f0, const in float f90, const in float dotVH ) {\n\tfloat fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );\n\treturn f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );\n}\nfloat F_Schlick( const in float f0, const in float f90, const in float dotVH ) {\n\tfloat fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );\n\treturn f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );\n} // validated"; - -var cube_uv_reflection_fragment = "#ifdef ENVMAP_TYPE_CUBE_UV\n\t#define cubeUV_minMipLevel 4.0\n\t#define cubeUV_minTileSize 16.0\n\tfloat getFace( vec3 direction ) {\n\t\tvec3 absDirection = abs( direction );\n\t\tfloat face = - 1.0;\n\t\tif ( absDirection.x > absDirection.z ) {\n\t\t\tif ( absDirection.x > absDirection.y )\n\t\t\t\tface = direction.x > 0.0 ? 0.0 : 3.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t} else {\n\t\t\tif ( absDirection.z > absDirection.y )\n\t\t\t\tface = direction.z > 0.0 ? 2.0 : 5.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t}\n\t\treturn face;\n\t}\n\tvec2 getUV( vec3 direction, float face ) {\n\t\tvec2 uv;\n\t\tif ( face == 0.0 ) {\n\t\t\tuv = vec2( direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 1.0 ) {\n\t\t\tuv = vec2( - direction.x, - direction.z ) / abs( direction.y );\n\t\t} else if ( face == 2.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.y ) / abs( direction.z );\n\t\t} else if ( face == 3.0 ) {\n\t\t\tuv = vec2( - direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 4.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.z ) / abs( direction.y );\n\t\t} else {\n\t\t\tuv = vec2( direction.x, direction.y ) / abs( direction.z );\n\t\t}\n\t\treturn 0.5 * ( uv + 1.0 );\n\t}\n\tvec3 bilinearCubeUV( sampler2D envMap, vec3 direction, float mipInt ) {\n\t\tfloat face = getFace( direction );\n\t\tfloat filterInt = max( cubeUV_minMipLevel - mipInt, 0.0 );\n\t\tmipInt = max( mipInt, cubeUV_minMipLevel );\n\t\tfloat faceSize = exp2( mipInt );\n\t\thighp vec2 uv = getUV( direction, face ) * ( faceSize - 2.0 ) + 1.0;\n\t\tif ( face > 2.0 ) {\n\t\t\tuv.y += faceSize;\n\t\t\tface -= 3.0;\n\t\t}\n\t\tuv.x += face * faceSize;\n\t\tuv.x += filterInt * 3.0 * cubeUV_minTileSize;\n\t\tuv.y += 4.0 * ( exp2( CUBEUV_MAX_MIP ) - faceSize );\n\t\tuv.x *= CUBEUV_TEXEL_WIDTH;\n\t\tuv.y *= CUBEUV_TEXEL_HEIGHT;\n\t\t#ifdef texture2DGradEXT\n\t\t\treturn texture2DGradEXT( envMap, uv, vec2( 0.0 ), vec2( 0.0 ) ).rgb;\n\t\t#else\n\t\t\treturn texture2D( envMap, uv ).rgb;\n\t\t#endif\n\t}\n\t#define cubeUV_r0 1.0\n\t#define cubeUV_v0 0.339\n\t#define cubeUV_m0 - 2.0\n\t#define cubeUV_r1 0.8\n\t#define cubeUV_v1 0.276\n\t#define cubeUV_m1 - 1.0\n\t#define cubeUV_r4 0.4\n\t#define cubeUV_v4 0.046\n\t#define cubeUV_m4 2.0\n\t#define cubeUV_r5 0.305\n\t#define cubeUV_v5 0.016\n\t#define cubeUV_m5 3.0\n\t#define cubeUV_r6 0.21\n\t#define cubeUV_v6 0.0038\n\t#define cubeUV_m6 4.0\n\tfloat roughnessToMip( float roughness ) {\n\t\tfloat mip = 0.0;\n\t\tif ( roughness >= cubeUV_r1 ) {\n\t\t\tmip = ( cubeUV_r0 - roughness ) * ( cubeUV_m1 - cubeUV_m0 ) / ( cubeUV_r0 - cubeUV_r1 ) + cubeUV_m0;\n\t\t} else if ( roughness >= cubeUV_r4 ) {\n\t\t\tmip = ( cubeUV_r1 - roughness ) * ( cubeUV_m4 - cubeUV_m1 ) / ( cubeUV_r1 - cubeUV_r4 ) + cubeUV_m1;\n\t\t} else if ( roughness >= cubeUV_r5 ) {\n\t\t\tmip = ( cubeUV_r4 - roughness ) * ( cubeUV_m5 - cubeUV_m4 ) / ( cubeUV_r4 - cubeUV_r5 ) + cubeUV_m4;\n\t\t} else if ( roughness >= cubeUV_r6 ) {\n\t\t\tmip = ( cubeUV_r5 - roughness ) * ( cubeUV_m6 - cubeUV_m5 ) / ( cubeUV_r5 - cubeUV_r6 ) + cubeUV_m5;\n\t\t} else {\n\t\t\tmip = - 2.0 * log2( 1.16 * roughness );\t\t}\n\t\treturn mip;\n\t}\n\tvec4 textureCubeUV( sampler2D envMap, vec3 sampleDir, float roughness ) {\n\t\tfloat mip = clamp( roughnessToMip( roughness ), cubeUV_m0, CUBEUV_MAX_MIP );\n\t\tfloat mipF = fract( mip );\n\t\tfloat mipInt = floor( mip );\n\t\tvec3 color0 = bilinearCubeUV( envMap, sampleDir, mipInt );\n\t\tif ( mipF == 0.0 ) {\n\t\t\treturn vec4( color0, 1.0 );\n\t\t} else {\n\t\t\tvec3 color1 = bilinearCubeUV( envMap, sampleDir, mipInt + 1.0 );\n\t\t\treturn vec4( mix( color0, color1, mipF ), 1.0 );\n\t\t}\n\t}\n#endif"; - -var defaultnormal_vertex = "vec3 transformedNormal = objectNormal;\n#ifdef USE_INSTANCING\n\tmat3 m = mat3( instanceMatrix );\n\ttransformedNormal /= vec3( dot( m[ 0 ], m[ 0 ] ), dot( m[ 1 ], m[ 1 ] ), dot( m[ 2 ], m[ 2 ] ) );\n\ttransformedNormal = m * transformedNormal;\n#endif\ntransformedNormal = normalMatrix * transformedNormal;\n#ifdef FLIP_SIDED\n\ttransformedNormal = - transformedNormal;\n#endif\n#ifdef USE_TANGENT\n\tvec3 transformedTangent = ( modelViewMatrix * vec4( objectTangent, 0.0 ) ).xyz;\n\t#ifdef FLIP_SIDED\n\t\ttransformedTangent = - transformedTangent;\n\t#endif\n#endif"; - -var displacementmap_pars_vertex = "#ifdef USE_DISPLACEMENTMAP\n\tuniform sampler2D displacementMap;\n\tuniform float displacementScale;\n\tuniform float displacementBias;\n#endif"; - -var displacementmap_vertex = "#ifdef USE_DISPLACEMENTMAP\n\ttransformed += normalize( objectNormal ) * ( texture2D( displacementMap, vDisplacementMapUv ).x * displacementScale + displacementBias );\n#endif"; - -var emissivemap_fragment = "#ifdef USE_EMISSIVEMAP\n\tvec4 emissiveColor = texture2D( emissiveMap, vEmissiveMapUv );\n\ttotalEmissiveRadiance *= emissiveColor.rgb;\n#endif"; - -var emissivemap_pars_fragment = "#ifdef USE_EMISSIVEMAP\n\tuniform sampler2D emissiveMap;\n#endif"; - -var encodings_fragment = "gl_FragColor = linearToOutputTexel( gl_FragColor );"; - -var encodings_pars_fragment = "vec4 LinearToLinear( in vec4 value ) {\n\treturn value;\n}\nvec4 LinearTosRGB( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a );\n}"; - -var envmap_fragment = "#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvec3 cameraToFrag;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToFrag = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToFrag = normalize( vWorldPosition - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvec3 reflectVec = reflect( cameraToFrag, worldNormal );\n\t\t#else\n\t\t\tvec3 reflectVec = refract( cameraToFrag, worldNormal, refractionRatio );\n\t\t#endif\n\t#else\n\t\tvec3 reflectVec = vReflect;\n\t#endif\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 envColor = textureCube( envMap, vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );\n\t#else\n\t\tvec4 envColor = vec4( 0.0 );\n\t#endif\n\t#ifdef ENVMAP_BLENDING_MULTIPLY\n\t\toutgoingLight = mix( outgoingLight, outgoingLight * envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_MIX )\n\t\toutgoingLight = mix( outgoingLight, envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_ADD )\n\t\toutgoingLight += envColor.xyz * specularStrength * reflectivity;\n\t#endif\n#endif"; - -var envmap_common_pars_fragment = "#ifdef USE_ENVMAP\n\tuniform float envMapIntensity;\n\tuniform float flipEnvMap;\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tuniform samplerCube envMap;\n\t#else\n\t\tuniform sampler2D envMap;\n\t#endif\n\t\n#endif"; - -var envmap_pars_fragment = "#ifdef USE_ENVMAP\n\tuniform float reflectivity;\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( LAMBERT )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\tvarying vec3 vWorldPosition;\n\t\tuniform float refractionRatio;\n\t#else\n\t\tvarying vec3 vReflect;\n\t#endif\n#endif"; - -var envmap_pars_vertex = "#ifdef USE_ENVMAP\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( LAMBERT )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\t\n\t\tvarying vec3 vWorldPosition;\n\t#else\n\t\tvarying vec3 vReflect;\n\t\tuniform float refractionRatio;\n\t#endif\n#endif"; - -var envmap_vertex = "#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvWorldPosition = worldPosition.xyz;\n\t#else\n\t\tvec3 cameraToVertex;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToVertex = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToVertex = normalize( worldPosition.xyz - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvReflect = reflect( cameraToVertex, worldNormal );\n\t\t#else\n\t\t\tvReflect = refract( cameraToVertex, worldNormal, refractionRatio );\n\t\t#endif\n\t#endif\n#endif"; - -var fog_vertex = "#ifdef USE_FOG\n\tvFogDepth = - mvPosition.z;\n#endif"; - -var fog_pars_vertex = "#ifdef USE_FOG\n\tvarying float vFogDepth;\n#endif"; - -var fog_fragment = "#ifdef USE_FOG\n\t#ifdef FOG_EXP2\n\t\tfloat fogFactor = 1.0 - exp( - fogDensity * fogDensity * vFogDepth * vFogDepth );\n\t#else\n\t\tfloat fogFactor = smoothstep( fogNear, fogFar, vFogDepth );\n\t#endif\n\tgl_FragColor.rgb = mix( gl_FragColor.rgb, fogColor, fogFactor );\n#endif"; - -var fog_pars_fragment = "#ifdef USE_FOG\n\tuniform vec3 fogColor;\n\tvarying float vFogDepth;\n\t#ifdef FOG_EXP2\n\t\tuniform float fogDensity;\n\t#else\n\t\tuniform float fogNear;\n\t\tuniform float fogFar;\n\t#endif\n#endif"; - -var gradientmap_pars_fragment = "#ifdef USE_GRADIENTMAP\n\tuniform sampler2D gradientMap;\n#endif\nvec3 getGradientIrradiance( vec3 normal, vec3 lightDirection ) {\n\tfloat dotNL = dot( normal, lightDirection );\n\tvec2 coord = vec2( dotNL * 0.5 + 0.5, 0.0 );\n\t#ifdef USE_GRADIENTMAP\n\t\treturn vec3( texture2D( gradientMap, coord ).r );\n\t#else\n\t\tvec2 fw = fwidth( coord ) * 0.5;\n\t\treturn mix( vec3( 0.7 ), vec3( 1.0 ), smoothstep( 0.7 - fw.x, 0.7 + fw.x, coord.x ) );\n\t#endif\n}"; - -var lightmap_fragment = "#ifdef USE_LIGHTMAP\n\tvec4 lightMapTexel = texture2D( lightMap, vLightMapUv );\n\tvec3 lightMapIrradiance = lightMapTexel.rgb * lightMapIntensity;\n\treflectedLight.indirectDiffuse += lightMapIrradiance;\n#endif"; - -var lightmap_pars_fragment = "#ifdef USE_LIGHTMAP\n\tuniform sampler2D lightMap;\n\tuniform float lightMapIntensity;\n#endif"; - -var lights_lambert_fragment = "LambertMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularStrength = specularStrength;"; - -var lights_lambert_pars_fragment = "varying vec3 vViewPosition;\nstruct LambertMaterial {\n\tvec3 diffuseColor;\n\tfloat specularStrength;\n};\nvoid RE_Direct_Lambert( const in IncidentLight directLight, const in GeometricContext geometry, const in LambertMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Lambert( const in vec3 irradiance, const in GeometricContext geometry, const in LambertMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Lambert\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Lambert"; - -var lights_pars_begin = "uniform bool receiveShadow;\nuniform vec3 ambientLightColor;\nuniform vec3 lightProbe[ 9 ];\nvec3 shGetIrradianceAt( in vec3 normal, in vec3 shCoefficients[ 9 ] ) {\n\tfloat x = normal.x, y = normal.y, z = normal.z;\n\tvec3 result = shCoefficients[ 0 ] * 0.886227;\n\tresult += shCoefficients[ 1 ] * 2.0 * 0.511664 * y;\n\tresult += shCoefficients[ 2 ] * 2.0 * 0.511664 * z;\n\tresult += shCoefficients[ 3 ] * 2.0 * 0.511664 * x;\n\tresult += shCoefficients[ 4 ] * 2.0 * 0.429043 * x * y;\n\tresult += shCoefficients[ 5 ] * 2.0 * 0.429043 * y * z;\n\tresult += shCoefficients[ 6 ] * ( 0.743125 * z * z - 0.247708 );\n\tresult += shCoefficients[ 7 ] * 2.0 * 0.429043 * x * z;\n\tresult += shCoefficients[ 8 ] * 0.429043 * ( x * x - y * y );\n\treturn result;\n}\nvec3 getLightProbeIrradiance( const in vec3 lightProbe[ 9 ], const in vec3 normal ) {\n\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\tvec3 irradiance = shGetIrradianceAt( worldNormal, lightProbe );\n\treturn irradiance;\n}\nvec3 getAmbientLightIrradiance( const in vec3 ambientLightColor ) {\n\tvec3 irradiance = ambientLightColor;\n\treturn irradiance;\n}\nfloat getDistanceAttenuation( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) {\n\t#if defined ( LEGACY_LIGHTS )\n\t\tif ( cutoffDistance > 0.0 && decayExponent > 0.0 ) {\n\t\t\treturn pow( saturate( - lightDistance / cutoffDistance + 1.0 ), decayExponent );\n\t\t}\n\t\treturn 1.0;\n\t#else\n\t\tfloat distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );\n\t\tif ( cutoffDistance > 0.0 ) {\n\t\t\tdistanceFalloff *= pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );\n\t\t}\n\t\treturn distanceFalloff;\n\t#endif\n}\nfloat getSpotAttenuation( const in float coneCosine, const in float penumbraCosine, const in float angleCosine ) {\n\treturn smoothstep( coneCosine, penumbraCosine, angleCosine );\n}\n#if NUM_DIR_LIGHTS > 0\n\tstruct DirectionalLight {\n\t\tvec3 direction;\n\t\tvec3 color;\n\t};\n\tuniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ];\n\tvoid getDirectionalLightInfo( const in DirectionalLight directionalLight, const in GeometricContext geometry, out IncidentLight light ) {\n\t\tlight.color = directionalLight.color;\n\t\tlight.direction = directionalLight.direction;\n\t\tlight.visible = true;\n\t}\n#endif\n#if NUM_POINT_LIGHTS > 0\n\tstruct PointLight {\n\t\tvec3 position;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t};\n\tuniform PointLight pointLights[ NUM_POINT_LIGHTS ];\n\tvoid getPointLightInfo( const in PointLight pointLight, const in GeometricContext geometry, out IncidentLight light ) {\n\t\tvec3 lVector = pointLight.position - geometry.position;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat lightDistance = length( lVector );\n\t\tlight.color = pointLight.color;\n\t\tlight.color *= getDistanceAttenuation( lightDistance, pointLight.distance, pointLight.decay );\n\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t}\n#endif\n#if NUM_SPOT_LIGHTS > 0\n\tstruct SpotLight {\n\t\tvec3 position;\n\t\tvec3 direction;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t\tfloat coneCos;\n\t\tfloat penumbraCos;\n\t};\n\tuniform SpotLight spotLights[ NUM_SPOT_LIGHTS ];\n\tvoid getSpotLightInfo( const in SpotLight spotLight, const in GeometricContext geometry, out IncidentLight light ) {\n\t\tvec3 lVector = spotLight.position - geometry.position;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat angleCos = dot( light.direction, spotLight.direction );\n\t\tfloat spotAttenuation = getSpotAttenuation( spotLight.coneCos, spotLight.penumbraCos, angleCos );\n\t\tif ( spotAttenuation > 0.0 ) {\n\t\t\tfloat lightDistance = length( lVector );\n\t\t\tlight.color = spotLight.color * spotAttenuation;\n\t\t\tlight.color *= getDistanceAttenuation( lightDistance, spotLight.distance, spotLight.decay );\n\t\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t\t} else {\n\t\t\tlight.color = vec3( 0.0 );\n\t\t\tlight.visible = false;\n\t\t}\n\t}\n#endif\n#if NUM_RECT_AREA_LIGHTS > 0\n\tstruct RectAreaLight {\n\t\tvec3 color;\n\t\tvec3 position;\n\t\tvec3 halfWidth;\n\t\tvec3 halfHeight;\n\t};\n\tuniform sampler2D ltc_1;\tuniform sampler2D ltc_2;\n\tuniform RectAreaLight rectAreaLights[ NUM_RECT_AREA_LIGHTS ];\n#endif\n#if NUM_HEMI_LIGHTS > 0\n\tstruct HemisphereLight {\n\t\tvec3 direction;\n\t\tvec3 skyColor;\n\t\tvec3 groundColor;\n\t};\n\tuniform HemisphereLight hemisphereLights[ NUM_HEMI_LIGHTS ];\n\tvec3 getHemisphereLightIrradiance( const in HemisphereLight hemiLight, const in vec3 normal ) {\n\t\tfloat dotNL = dot( normal, hemiLight.direction );\n\t\tfloat hemiDiffuseWeight = 0.5 * dotNL + 0.5;\n\t\tvec3 irradiance = mix( hemiLight.groundColor, hemiLight.skyColor, hemiDiffuseWeight );\n\t\treturn irradiance;\n\t}\n#endif"; - -var envmap_physical_pars_fragment = "#ifdef USE_ENVMAP\n\tvec3 getIBLIrradiance( const in vec3 normal ) {\n\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, worldNormal, 1.0 );\n\t\t\treturn PI * envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n\tvec3 getIBLRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness ) {\n\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\tvec3 reflectVec = reflect( - viewDir, normal );\n\t\t\treflectVec = normalize( mix( reflectVec, normal, roughness * roughness) );\n\t\t\treflectVec = inverseTransformDirection( reflectVec, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, reflectVec, roughness );\n\t\t\treturn envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n\t#ifdef USE_ANISOTROPY\n\t\tvec3 getIBLAnisotropyRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness, const in vec3 bitangent, const in float anisotropy ) {\n\t\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\t\tvec3 bentNormal = cross( bitangent, viewDir );\n\t\t\t\tbentNormal = normalize( cross( bentNormal, bitangent ) );\n\t\t\t\tbentNormal = normalize( mix( bentNormal, normal, pow2( pow2( 1.0 - anisotropy * ( 1.0 - roughness ) ) ) ) );\n\t\t\t\treturn getIBLRadiance( viewDir, bentNormal, roughness );\n\t\t\t#else\n\t\t\t\treturn vec3( 0.0 );\n\t\t\t#endif\n\t\t}\n\t#endif\n#endif"; - -var lights_toon_fragment = "ToonMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;"; - -var lights_toon_pars_fragment = "varying vec3 vViewPosition;\nstruct ToonMaterial {\n\tvec3 diffuseColor;\n};\nvoid RE_Direct_Toon( const in IncidentLight directLight, const in GeometricContext geometry, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\tvec3 irradiance = getGradientIrradiance( geometry.normal, directLight.direction ) * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Toon( const in vec3 irradiance, const in GeometricContext geometry, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Toon\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Toon"; - -var lights_phong_fragment = "BlinnPhongMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularColor = specular;\nmaterial.specularShininess = shininess;\nmaterial.specularStrength = specularStrength;"; - -var lights_phong_pars_fragment = "varying vec3 vViewPosition;\nstruct BlinnPhongMaterial {\n\tvec3 diffuseColor;\n\tvec3 specularColor;\n\tfloat specularShininess;\n\tfloat specularStrength;\n};\nvoid RE_Direct_BlinnPhong( const in IncidentLight directLight, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n\treflectedLight.directSpecular += irradiance * BRDF_BlinnPhong( directLight.direction, geometry.viewDir, geometry.normal, material.specularColor, material.specularShininess ) * material.specularStrength;\n}\nvoid RE_IndirectDiffuse_BlinnPhong( const in vec3 irradiance, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_BlinnPhong\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_BlinnPhong"; - -var lights_physical_fragment = "PhysicalMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb * ( 1.0 - metalnessFactor );\nvec3 dxy = max( abs( dFdx( geometryNormal ) ), abs( dFdy( geometryNormal ) ) );\nfloat geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z );\nmaterial.roughness = max( roughnessFactor, 0.0525 );material.roughness += geometryRoughness;\nmaterial.roughness = min( material.roughness, 1.0 );\n#ifdef IOR\n\tmaterial.ior = ior;\n\t#ifdef USE_SPECULAR\n\t\tfloat specularIntensityFactor = specularIntensity;\n\t\tvec3 specularColorFactor = specularColor;\n\t\t#ifdef USE_SPECULAR_COLORMAP\n\t\t\tspecularColorFactor *= texture2D( specularColorMap, vSpecularColorMapUv ).rgb;\n\t\t#endif\n\t\t#ifdef USE_SPECULAR_INTENSITYMAP\n\t\t\tspecularIntensityFactor *= texture2D( specularIntensityMap, vSpecularIntensityMapUv ).a;\n\t\t#endif\n\t\tmaterial.specularF90 = mix( specularIntensityFactor, 1.0, metalnessFactor );\n\t#else\n\t\tfloat specularIntensityFactor = 1.0;\n\t\tvec3 specularColorFactor = vec3( 1.0 );\n\t\tmaterial.specularF90 = 1.0;\n\t#endif\n\tmaterial.specularColor = mix( min( pow2( ( material.ior - 1.0 ) / ( material.ior + 1.0 ) ) * specularColorFactor, vec3( 1.0 ) ) * specularIntensityFactor, diffuseColor.rgb, metalnessFactor );\n#else\n\tmaterial.specularColor = mix( vec3( 0.04 ), diffuseColor.rgb, metalnessFactor );\n\tmaterial.specularF90 = 1.0;\n#endif\n#ifdef USE_CLEARCOAT\n\tmaterial.clearcoat = clearcoat;\n\tmaterial.clearcoatRoughness = clearcoatRoughness;\n\tmaterial.clearcoatF0 = vec3( 0.04 );\n\tmaterial.clearcoatF90 = 1.0;\n\t#ifdef USE_CLEARCOATMAP\n\t\tmaterial.clearcoat *= texture2D( clearcoatMap, vClearcoatMapUv ).x;\n\t#endif\n\t#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\t\tmaterial.clearcoatRoughness *= texture2D( clearcoatRoughnessMap, vClearcoatRoughnessMapUv ).y;\n\t#endif\n\tmaterial.clearcoat = saturate( material.clearcoat );\tmaterial.clearcoatRoughness = max( material.clearcoatRoughness, 0.0525 );\n\tmaterial.clearcoatRoughness += geometryRoughness;\n\tmaterial.clearcoatRoughness = min( material.clearcoatRoughness, 1.0 );\n#endif\n#ifdef USE_IRIDESCENCE\n\tmaterial.iridescence = iridescence;\n\tmaterial.iridescenceIOR = iridescenceIOR;\n\t#ifdef USE_IRIDESCENCEMAP\n\t\tmaterial.iridescence *= texture2D( iridescenceMap, vIridescenceMapUv ).r;\n\t#endif\n\t#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\t\tmaterial.iridescenceThickness = (iridescenceThicknessMaximum - iridescenceThicknessMinimum) * texture2D( iridescenceThicknessMap, vIridescenceThicknessMapUv ).g + iridescenceThicknessMinimum;\n\t#else\n\t\tmaterial.iridescenceThickness = iridescenceThicknessMaximum;\n\t#endif\n#endif\n#ifdef USE_SHEEN\n\tmaterial.sheenColor = sheenColor;\n\t#ifdef USE_SHEEN_COLORMAP\n\t\tmaterial.sheenColor *= texture2D( sheenColorMap, vSheenColorMapUv ).rgb;\n\t#endif\n\tmaterial.sheenRoughness = clamp( sheenRoughness, 0.07, 1.0 );\n\t#ifdef USE_SHEEN_ROUGHNESSMAP\n\t\tmaterial.sheenRoughness *= texture2D( sheenRoughnessMap, vSheenRoughnessMapUv ).a;\n\t#endif\n#endif\n#ifdef USE_ANISOTROPY\n\t#ifdef USE_ANISOTROPYMAP\n\t\tmat2 anisotropyMat = mat2( anisotropyVector.x, anisotropyVector.y, - anisotropyVector.y, anisotropyVector.x );\n\t\tvec3 anisotropyPolar = texture2D( anisotropyMap, vAnisotropyMapUv ).rgb;\n\t\tvec2 anisotropyV = anisotropyMat * normalize( 2.0 * anisotropyPolar.rg - vec2( 1.0 ) ) * anisotropyPolar.b;\n\t#else\n\t\tvec2 anisotropyV = anisotropyVector;\n\t#endif\n\tmaterial.anisotropy = length( anisotropyV );\n\tanisotropyV /= material.anisotropy;\n\tmaterial.anisotropy = saturate( material.anisotropy );\n\tmaterial.alphaT = mix( pow2( material.roughness ), 1.0, pow2( material.anisotropy ) );\n\tmaterial.anisotropyT = tbn[ 0 ] * anisotropyV.x - tbn[ 1 ] * anisotropyV.y;\n\tmaterial.anisotropyB = tbn[ 1 ] * anisotropyV.x + tbn[ 0 ] * anisotropyV.y;\n#endif"; - -var lights_physical_pars_fragment = "struct PhysicalMaterial {\n\tvec3 diffuseColor;\n\tfloat roughness;\n\tvec3 specularColor;\n\tfloat specularF90;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat clearcoat;\n\t\tfloat clearcoatRoughness;\n\t\tvec3 clearcoatF0;\n\t\tfloat clearcoatF90;\n\t#endif\n\t#ifdef USE_IRIDESCENCE\n\t\tfloat iridescence;\n\t\tfloat iridescenceIOR;\n\t\tfloat iridescenceThickness;\n\t\tvec3 iridescenceFresnel;\n\t\tvec3 iridescenceF0;\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tvec3 sheenColor;\n\t\tfloat sheenRoughness;\n\t#endif\n\t#ifdef IOR\n\t\tfloat ior;\n\t#endif\n\t#ifdef USE_TRANSMISSION\n\t\tfloat transmission;\n\t\tfloat transmissionAlpha;\n\t\tfloat thickness;\n\t\tfloat attenuationDistance;\n\t\tvec3 attenuationColor;\n\t#endif\n\t#ifdef USE_ANISOTROPY\n\t\tfloat anisotropy;\n\t\tfloat alphaT;\n\t\tvec3 anisotropyT;\n\t\tvec3 anisotropyB;\n\t#endif\n};\nvec3 clearcoatSpecular = vec3( 0.0 );\nvec3 sheenSpecular = vec3( 0.0 );\nvec3 Schlick_to_F0( const in vec3 f, const in float f90, const in float dotVH ) {\n float x = clamp( 1.0 - dotVH, 0.0, 1.0 );\n float x2 = x * x;\n float x5 = clamp( x * x2 * x2, 0.0, 0.9999 );\n return ( f - vec3( f90 ) * x5 ) / ( 1.0 - x5 );\n}\nfloat V_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const in float dotNV ) {\n\tfloat a2 = pow2( alpha );\n\tfloat gv = dotNL * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );\n\tfloat gl = dotNV * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );\n\treturn 0.5 / max( gv + gl, EPSILON );\n}\nfloat D_GGX( const in float alpha, const in float dotNH ) {\n\tfloat a2 = pow2( alpha );\n\tfloat denom = pow2( dotNH ) * ( a2 - 1.0 ) + 1.0;\n\treturn RECIPROCAL_PI * a2 / pow2( denom );\n}\n#ifdef USE_ANISOTROPY\n\tfloat V_GGX_SmithCorrelated_Anisotropic( const in float alphaT, const in float alphaB, const in float dotTV, const in float dotBV, const in float dotTL, const in float dotBL, const in float dotNV, const in float dotNL ) {\n\t\tfloat gv = dotNL * length( vec3( alphaT * dotTV, alphaB * dotBV, dotNV ) );\n\t\tfloat gl = dotNV * length( vec3( alphaT * dotTL, alphaB * dotBL, dotNL ) );\n\t\tfloat v = 0.5 / ( gv + gl );\n\t\treturn saturate(v);\n\t}\n\tfloat D_GGX_Anisotropic( const in float alphaT, const in float alphaB, const in float dotNH, const in float dotTH, const in float dotBH ) {\n\t\tfloat a2 = alphaT * alphaB;\n\t\thighp vec3 v = vec3( alphaB * dotTH, alphaT * dotBH, a2 * dotNH );\n\t\thighp float v2 = dot( v, v );\n\t\tfloat w2 = a2 / v2;\n\t\treturn RECIPROCAL_PI * a2 * pow2 ( w2 );\n\t}\n#endif\n#ifdef USE_CLEARCOAT\n\tvec3 BRDF_GGX_Clearcoat( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in PhysicalMaterial material) {\n\t\tvec3 f0 = material.clearcoatF0;\n\t\tfloat f90 = material.clearcoatF90;\n\t\tfloat roughness = material.clearcoatRoughness;\n\t\tfloat alpha = pow2( roughness );\n\t\tvec3 halfDir = normalize( lightDir + viewDir );\n\t\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\t\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\t\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\t\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\t\tvec3 F = F_Schlick( f0, f90, dotVH );\n\t\tfloat V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\t\tfloat D = D_GGX( alpha, dotNH );\n\t\treturn F * ( V * D );\n\t}\n#endif\nvec3 BRDF_GGX( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in PhysicalMaterial material ) {\n\tvec3 f0 = material.specularColor;\n\tfloat f90 = material.specularF90;\n\tfloat roughness = material.roughness;\n\tfloat alpha = pow2( roughness );\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\tvec3 F = F_Schlick( f0, f90, dotVH );\n\t#ifdef USE_IRIDESCENCE\n\t\tF = mix( F, material.iridescenceFresnel, material.iridescence );\n\t#endif\n\t#ifdef USE_ANISOTROPY\n\t\tfloat dotTL = dot( material.anisotropyT, lightDir );\n\t\tfloat dotTV = dot( material.anisotropyT, viewDir );\n\t\tfloat dotTH = dot( material.anisotropyT, halfDir );\n\t\tfloat dotBL = dot( material.anisotropyB, lightDir );\n\t\tfloat dotBV = dot( material.anisotropyB, viewDir );\n\t\tfloat dotBH = dot( material.anisotropyB, halfDir );\n\t\tfloat V = V_GGX_SmithCorrelated_Anisotropic( material.alphaT, alpha, dotTV, dotBV, dotTL, dotBL, dotNV, dotNL );\n\t\tfloat D = D_GGX_Anisotropic( material.alphaT, alpha, dotNH, dotTH, dotBH );\n\t#else\n\t\tfloat V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\t\tfloat D = D_GGX( alpha, dotNH );\n\t#endif\n\treturn F * ( V * D );\n}\nvec2 LTC_Uv( const in vec3 N, const in vec3 V, const in float roughness ) {\n\tconst float LUT_SIZE = 64.0;\n\tconst float LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE;\n\tconst float LUT_BIAS = 0.5 / LUT_SIZE;\n\tfloat dotNV = saturate( dot( N, V ) );\n\tvec2 uv = vec2( roughness, sqrt( 1.0 - dotNV ) );\n\tuv = uv * LUT_SCALE + LUT_BIAS;\n\treturn uv;\n}\nfloat LTC_ClippedSphereFormFactor( const in vec3 f ) {\n\tfloat l = length( f );\n\treturn max( ( l * l + f.z ) / ( l + 1.0 ), 0.0 );\n}\nvec3 LTC_EdgeVectorFormFactor( const in vec3 v1, const in vec3 v2 ) {\n\tfloat x = dot( v1, v2 );\n\tfloat y = abs( x );\n\tfloat a = 0.8543985 + ( 0.4965155 + 0.0145206 * y ) * y;\n\tfloat b = 3.4175940 + ( 4.1616724 + y ) * y;\n\tfloat v = a / b;\n\tfloat theta_sintheta = ( x > 0.0 ) ? v : 0.5 * inversesqrt( max( 1.0 - x * x, 1e-7 ) ) - v;\n\treturn cross( v1, v2 ) * theta_sintheta;\n}\nvec3 LTC_Evaluate( const in vec3 N, const in vec3 V, const in vec3 P, const in mat3 mInv, const in vec3 rectCoords[ 4 ] ) {\n\tvec3 v1 = rectCoords[ 1 ] - rectCoords[ 0 ];\n\tvec3 v2 = rectCoords[ 3 ] - rectCoords[ 0 ];\n\tvec3 lightNormal = cross( v1, v2 );\n\tif( dot( lightNormal, P - rectCoords[ 0 ] ) < 0.0 ) return vec3( 0.0 );\n\tvec3 T1, T2;\n\tT1 = normalize( V - N * dot( V, N ) );\n\tT2 = - cross( N, T1 );\n\tmat3 mat = mInv * transposeMat3( mat3( T1, T2, N ) );\n\tvec3 coords[ 4 ];\n\tcoords[ 0 ] = mat * ( rectCoords[ 0 ] - P );\n\tcoords[ 1 ] = mat * ( rectCoords[ 1 ] - P );\n\tcoords[ 2 ] = mat * ( rectCoords[ 2 ] - P );\n\tcoords[ 3 ] = mat * ( rectCoords[ 3 ] - P );\n\tcoords[ 0 ] = normalize( coords[ 0 ] );\n\tcoords[ 1 ] = normalize( coords[ 1 ] );\n\tcoords[ 2 ] = normalize( coords[ 2 ] );\n\tcoords[ 3 ] = normalize( coords[ 3 ] );\n\tvec3 vectorFormFactor = vec3( 0.0 );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 0 ], coords[ 1 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 1 ], coords[ 2 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 2 ], coords[ 3 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 3 ], coords[ 0 ] );\n\tfloat result = LTC_ClippedSphereFormFactor( vectorFormFactor );\n\treturn vec3( result );\n}\n#if defined( USE_SHEEN )\nfloat D_Charlie( float roughness, float dotNH ) {\n\tfloat alpha = pow2( roughness );\n\tfloat invAlpha = 1.0 / alpha;\n\tfloat cos2h = dotNH * dotNH;\n\tfloat sin2h = max( 1.0 - cos2h, 0.0078125 );\n\treturn ( 2.0 + invAlpha ) * pow( sin2h, invAlpha * 0.5 ) / ( 2.0 * PI );\n}\nfloat V_Neubelt( float dotNV, float dotNL ) {\n\treturn saturate( 1.0 / ( 4.0 * ( dotNL + dotNV - dotNL * dotNV ) ) );\n}\nvec3 BRDF_Sheen( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, vec3 sheenColor, const in float sheenRoughness ) {\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat D = D_Charlie( sheenRoughness, dotNH );\n\tfloat V = V_Neubelt( dotNV, dotNL );\n\treturn sheenColor * ( D * V );\n}\n#endif\nfloat IBLSheenBRDF( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat r2 = roughness * roughness;\n\tfloat a = roughness < 0.25 ? -339.2 * r2 + 161.4 * roughness - 25.9 : -8.48 * r2 + 14.3 * roughness - 9.95;\n\tfloat b = roughness < 0.25 ? 44.0 * r2 - 23.7 * roughness + 3.26 : 1.97 * r2 - 3.27 * roughness + 0.72;\n\tfloat DG = exp( a * dotNV + b ) + ( roughness < 0.25 ? 0.0 : 0.1 * ( roughness - 0.25 ) );\n\treturn saturate( DG * RECIPROCAL_PI );\n}\nvec2 DFGApprox( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tconst vec4 c0 = vec4( - 1, - 0.0275, - 0.572, 0.022 );\n\tconst vec4 c1 = vec4( 1, 0.0425, 1.04, - 0.04 );\n\tvec4 r = roughness * c0 + c1;\n\tfloat a004 = min( r.x * r.x, exp2( - 9.28 * dotNV ) ) * r.x + r.y;\n\tvec2 fab = vec2( - 1.04, 1.04 ) * a004 + r.zw;\n\treturn fab;\n}\nvec3 EnvironmentBRDF( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness ) {\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\treturn specularColor * fab.x + specularF90 * fab.y;\n}\n#ifdef USE_IRIDESCENCE\nvoid computeMultiscatteringIridescence( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float iridescence, const in vec3 iridescenceF0, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#else\nvoid computeMultiscattering( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#endif\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\t#ifdef USE_IRIDESCENCE\n\t\tvec3 Fr = mix( specularColor, iridescenceF0, iridescence );\n\t#else\n\t\tvec3 Fr = specularColor;\n\t#endif\n\tvec3 FssEss = Fr * fab.x + specularF90 * fab.y;\n\tfloat Ess = fab.x + fab.y;\n\tfloat Ems = 1.0 - Ess;\n\tvec3 Favg = Fr + ( 1.0 - Fr ) * 0.047619;\tvec3 Fms = FssEss * Favg / ( 1.0 - Ems * Favg );\n\tsingleScatter += FssEss;\n\tmultiScatter += Fms * Ems;\n}\n#if NUM_RECT_AREA_LIGHTS > 0\n\tvoid RE_Direct_RectArea_Physical( const in RectAreaLight rectAreaLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\t\tvec3 normal = geometry.normal;\n\t\tvec3 viewDir = geometry.viewDir;\n\t\tvec3 position = geometry.position;\n\t\tvec3 lightPos = rectAreaLight.position;\n\t\tvec3 halfWidth = rectAreaLight.halfWidth;\n\t\tvec3 halfHeight = rectAreaLight.halfHeight;\n\t\tvec3 lightColor = rectAreaLight.color;\n\t\tfloat roughness = material.roughness;\n\t\tvec3 rectCoords[ 4 ];\n\t\trectCoords[ 0 ] = lightPos + halfWidth - halfHeight;\t\trectCoords[ 1 ] = lightPos - halfWidth - halfHeight;\n\t\trectCoords[ 2 ] = lightPos - halfWidth + halfHeight;\n\t\trectCoords[ 3 ] = lightPos + halfWidth + halfHeight;\n\t\tvec2 uv = LTC_Uv( normal, viewDir, roughness );\n\t\tvec4 t1 = texture2D( ltc_1, uv );\n\t\tvec4 t2 = texture2D( ltc_2, uv );\n\t\tmat3 mInv = mat3(\n\t\t\tvec3( t1.x, 0, t1.y ),\n\t\t\tvec3( 0, 1, 0 ),\n\t\t\tvec3( t1.z, 0, t1.w )\n\t\t);\n\t\tvec3 fresnel = ( material.specularColor * t2.x + ( vec3( 1.0 ) - material.specularColor ) * t2.y );\n\t\treflectedLight.directSpecular += lightColor * fresnel * LTC_Evaluate( normal, viewDir, position, mInv, rectCoords );\n\t\treflectedLight.directDiffuse += lightColor * material.diffuseColor * LTC_Evaluate( normal, viewDir, position, mat3( 1.0 ), rectCoords );\n\t}\n#endif\nvoid RE_Direct_Physical( const in IncidentLight directLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNLcc = saturate( dot( geometry.clearcoatNormal, directLight.direction ) );\n\t\tvec3 ccIrradiance = dotNLcc * directLight.color;\n\t\tclearcoatSpecular += ccIrradiance * BRDF_GGX_Clearcoat( directLight.direction, geometry.viewDir, geometry.clearcoatNormal, material );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tsheenSpecular += irradiance * BRDF_Sheen( directLight.direction, geometry.viewDir, geometry.normal, material.sheenColor, material.sheenRoughness );\n\t#endif\n\treflectedLight.directSpecular += irradiance * BRDF_GGX( directLight.direction, geometry.viewDir, geometry.normal, material );\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Physical( const in vec3 irradiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 irradiance, const in vec3 clearcoatRadiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatSpecular += clearcoatRadiance * EnvironmentBRDF( geometry.clearcoatNormal, geometry.viewDir, material.clearcoatF0, material.clearcoatF90, material.clearcoatRoughness );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tsheenSpecular += irradiance * material.sheenColor * IBLSheenBRDF( geometry.normal, geometry.viewDir, material.sheenRoughness );\n\t#endif\n\tvec3 singleScattering = vec3( 0.0 );\n\tvec3 multiScattering = vec3( 0.0 );\n\tvec3 cosineWeightedIrradiance = irradiance * RECIPROCAL_PI;\n\t#ifdef USE_IRIDESCENCE\n\t\tcomputeMultiscatteringIridescence( geometry.normal, geometry.viewDir, material.specularColor, material.specularF90, material.iridescence, material.iridescenceFresnel, material.roughness, singleScattering, multiScattering );\n\t#else\n\t\tcomputeMultiscattering( geometry.normal, geometry.viewDir, material.specularColor, material.specularF90, material.roughness, singleScattering, multiScattering );\n\t#endif\n\tvec3 totalScattering = singleScattering + multiScattering;\n\tvec3 diffuse = material.diffuseColor * ( 1.0 - max( max( totalScattering.r, totalScattering.g ), totalScattering.b ) );\n\treflectedLight.indirectSpecular += radiance * singleScattering;\n\treflectedLight.indirectSpecular += multiScattering * cosineWeightedIrradiance;\n\treflectedLight.indirectDiffuse += diffuse * cosineWeightedIrradiance;\n}\n#define RE_Direct\t\t\t\tRE_Direct_Physical\n#define RE_Direct_RectArea\t\tRE_Direct_RectArea_Physical\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Physical\n#define RE_IndirectSpecular\t\tRE_IndirectSpecular_Physical\nfloat computeSpecularOcclusion( const in float dotNV, const in float ambientOcclusion, const in float roughness ) {\n\treturn saturate( pow( dotNV + ambientOcclusion, exp2( - 16.0 * roughness - 1.0 ) ) - 1.0 + ambientOcclusion );\n}"; - -var lights_fragment_begin = "\nGeometricContext geometry;\ngeometry.position = - vViewPosition;\ngeometry.normal = normal;\ngeometry.viewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( vViewPosition );\n#ifdef USE_CLEARCOAT\n\tgeometry.clearcoatNormal = clearcoatNormal;\n#endif\n#ifdef USE_IRIDESCENCE\n\tfloat dotNVi = saturate( dot( normal, geometry.viewDir ) );\n\tif ( material.iridescenceThickness == 0.0 ) {\n\t\tmaterial.iridescence = 0.0;\n\t} else {\n\t\tmaterial.iridescence = saturate( material.iridescence );\n\t}\n\tif ( material.iridescence > 0.0 ) {\n\t\tmaterial.iridescenceFresnel = evalIridescence( 1.0, material.iridescenceIOR, dotNVi, material.iridescenceThickness, material.specularColor );\n\t\tmaterial.iridescenceF0 = Schlick_to_F0( material.iridescenceFresnel, 1.0, dotNVi );\n\t}\n#endif\nIncidentLight directLight;\n#if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct )\n\tPointLight pointLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tpointLight = pointLights[ i ];\n\t\tgetPointLightInfo( pointLight, geometry, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_POINT_LIGHT_SHADOWS )\n\t\tpointLightShadow = pointLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getPointShadow( pointShadowMap[ i ], pointLightShadow.shadowMapSize, pointLightShadow.shadowBias, pointLightShadow.shadowRadius, vPointShadowCoord[ i ], pointLightShadow.shadowCameraNear, pointLightShadow.shadowCameraFar ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct )\n\tSpotLight spotLight;\n\tvec4 spotColor;\n\tvec3 spotLightCoord;\n\tbool inSpotLightMap;\n\t#if defined( USE_SHADOWMAP ) && NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tspotLight = spotLights[ i ];\n\t\tgetSpotLightInfo( spotLight, geometry, directLight );\n\t\t#if ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )\n\t\t#define SPOT_LIGHT_MAP_INDEX UNROLLED_LOOP_INDEX\n\t\t#elif ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\t#define SPOT_LIGHT_MAP_INDEX NUM_SPOT_LIGHT_MAPS\n\t\t#else\n\t\t#define SPOT_LIGHT_MAP_INDEX ( UNROLLED_LOOP_INDEX - NUM_SPOT_LIGHT_SHADOWS + NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )\n\t\t#endif\n\t\t#if ( SPOT_LIGHT_MAP_INDEX < NUM_SPOT_LIGHT_MAPS )\n\t\t\tspotLightCoord = vSpotLightCoord[ i ].xyz / vSpotLightCoord[ i ].w;\n\t\t\tinSpotLightMap = all( lessThan( abs( spotLightCoord * 2. - 1. ), vec3( 1.0 ) ) );\n\t\t\tspotColor = texture2D( spotLightMap[ SPOT_LIGHT_MAP_INDEX ], spotLightCoord.xy );\n\t\t\tdirectLight.color = inSpotLightMap ? directLight.color * spotColor.rgb : directLight.color;\n\t\t#endif\n\t\t#undef SPOT_LIGHT_MAP_INDEX\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\tspotLightShadow = spotLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( spotShadowMap[ i ], spotLightShadow.shadowMapSize, spotLightShadow.shadowBias, spotLightShadow.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct )\n\tDirectionalLight directionalLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tdirectionalLight = directionalLights[ i ];\n\t\tgetDirectionalLightInfo( directionalLight, geometry, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )\n\t\tdirectionalLightShadow = directionalLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea )\n\tRectAreaLight rectAreaLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) {\n\t\trectAreaLight = rectAreaLights[ i ];\n\t\tRE_Direct_RectArea( rectAreaLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if defined( RE_IndirectDiffuse )\n\tvec3 iblIrradiance = vec3( 0.0 );\n\tvec3 irradiance = getAmbientLightIrradiance( ambientLightColor );\n\tirradiance += getLightProbeIrradiance( lightProbe, geometry.normal );\n\t#if ( NUM_HEMI_LIGHTS > 0 )\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n\t\t\tirradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry.normal );\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n#endif\n#if defined( RE_IndirectSpecular )\n\tvec3 radiance = vec3( 0.0 );\n\tvec3 clearcoatRadiance = vec3( 0.0 );\n#endif"; - -var lights_fragment_maps = "#if defined( RE_IndirectDiffuse )\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel = texture2D( lightMap, vLightMapUv );\n\t\tvec3 lightMapIrradiance = lightMapTexel.rgb * lightMapIntensity;\n\t\tirradiance += lightMapIrradiance;\n\t#endif\n\t#if defined( USE_ENVMAP ) && defined( STANDARD ) && defined( ENVMAP_TYPE_CUBE_UV )\n\t\tiblIrradiance += getIBLIrradiance( geometry.normal );\n\t#endif\n#endif\n#if defined( USE_ENVMAP ) && defined( RE_IndirectSpecular )\n\t#ifdef USE_ANISOTROPY\n\t\tradiance += getIBLAnisotropyRadiance( geometry.viewDir, geometry.normal, material.roughness, material.anisotropyB, material.anisotropy );\n\t#else\n\t\tradiance += getIBLRadiance( geometry.viewDir, geometry.normal, material.roughness );\n\t#endif\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatRadiance += getIBLRadiance( geometry.viewDir, geometry.clearcoatNormal, material.clearcoatRoughness );\n\t#endif\n#endif"; - -var lights_fragment_end = "#if defined( RE_IndirectDiffuse )\n\tRE_IndirectDiffuse( irradiance, geometry, material, reflectedLight );\n#endif\n#if defined( RE_IndirectSpecular )\n\tRE_IndirectSpecular( radiance, iblIrradiance, clearcoatRadiance, geometry, material, reflectedLight );\n#endif"; - -var logdepthbuf_fragment = "#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tgl_FragDepthEXT = vIsPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\n#endif"; - -var logdepthbuf_pars_fragment = "#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tuniform float logDepthBufFC;\n\tvarying float vFragDepth;\n\tvarying float vIsPerspective;\n#endif"; - -var logdepthbuf_pars_vertex = "#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvarying float vFragDepth;\n\t\tvarying float vIsPerspective;\n\t#else\n\t\tuniform float logDepthBufFC;\n\t#endif\n#endif"; - -var logdepthbuf_vertex = "#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvFragDepth = 1.0 + gl_Position.w;\n\t\tvIsPerspective = float( isPerspectiveMatrix( projectionMatrix ) );\n\t#else\n\t\tif ( isPerspectiveMatrix( projectionMatrix ) ) {\n\t\t\tgl_Position.z = log2( max( EPSILON, gl_Position.w + 1.0 ) ) * logDepthBufFC - 1.0;\n\t\t\tgl_Position.z *= gl_Position.w;\n\t\t}\n\t#endif\n#endif"; - -var map_fragment = "#ifdef USE_MAP\n\tdiffuseColor *= texture2D( map, vMapUv );\n#endif"; - -var map_pars_fragment = "#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif"; - -var map_particle_fragment = "#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\t#if defined( USE_POINTS_UV )\n\t\tvec2 uv = vUv;\n\t#else\n\t\tvec2 uv = ( uvTransform * vec3( gl_PointCoord.x, 1.0 - gl_PointCoord.y, 1 ) ).xy;\n\t#endif\n#endif\n#ifdef USE_MAP\n\tdiffuseColor *= texture2D( map, uv );\n#endif\n#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, uv ).g;\n#endif"; - -var map_particle_pars_fragment = "#if defined( USE_POINTS_UV )\n\tvarying vec2 vUv;\n#else\n\t#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\t\tuniform mat3 uvTransform;\n\t#endif\n#endif\n#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif\n#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif"; - -var metalnessmap_fragment = "float metalnessFactor = metalness;\n#ifdef USE_METALNESSMAP\n\tvec4 texelMetalness = texture2D( metalnessMap, vMetalnessMapUv );\n\tmetalnessFactor *= texelMetalness.b;\n#endif"; - -var metalnessmap_pars_fragment = "#ifdef USE_METALNESSMAP\n\tuniform sampler2D metalnessMap;\n#endif"; - -var morphcolor_vertex = "#if defined( USE_MORPHCOLORS ) && defined( MORPHTARGETS_TEXTURE )\n\tvColor *= morphTargetBaseInfluence;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t#if defined( USE_COLOR_ALPHA )\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ) * morphTargetInfluences[ i ];\n\t\t#elif defined( USE_COLOR )\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ).rgb * morphTargetInfluences[ i ];\n\t\t#endif\n\t}\n#endif"; - -var morphnormal_vertex = "#ifdef USE_MORPHNORMALS\n\tobjectNormal *= morphTargetBaseInfluence;\n\t#ifdef MORPHTARGETS_TEXTURE\n\t\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) objectNormal += getMorph( gl_VertexID, i, 1 ).xyz * morphTargetInfluences[ i ];\n\t\t}\n\t#else\n\t\tobjectNormal += morphNormal0 * morphTargetInfluences[ 0 ];\n\t\tobjectNormal += morphNormal1 * morphTargetInfluences[ 1 ];\n\t\tobjectNormal += morphNormal2 * morphTargetInfluences[ 2 ];\n\t\tobjectNormal += morphNormal3 * morphTargetInfluences[ 3 ];\n\t#endif\n#endif"; - -var morphtarget_pars_vertex = "#ifdef USE_MORPHTARGETS\n\tuniform float morphTargetBaseInfluence;\n\t#ifdef MORPHTARGETS_TEXTURE\n\t\tuniform float morphTargetInfluences[ MORPHTARGETS_COUNT ];\n\t\tuniform sampler2DArray morphTargetsTexture;\n\t\tuniform ivec2 morphTargetsTextureSize;\n\t\tvec4 getMorph( const in int vertexIndex, const in int morphTargetIndex, const in int offset ) {\n\t\t\tint texelIndex = vertexIndex * MORPHTARGETS_TEXTURE_STRIDE + offset;\n\t\t\tint y = texelIndex / morphTargetsTextureSize.x;\n\t\t\tint x = texelIndex - y * morphTargetsTextureSize.x;\n\t\t\tivec3 morphUV = ivec3( x, y, morphTargetIndex );\n\t\t\treturn texelFetch( morphTargetsTexture, morphUV, 0 );\n\t\t}\n\t#else\n\t\t#ifndef USE_MORPHNORMALS\n\t\t\tuniform float morphTargetInfluences[ 8 ];\n\t\t#else\n\t\t\tuniform float morphTargetInfluences[ 4 ];\n\t\t#endif\n\t#endif\n#endif"; - -var morphtarget_vertex = "#ifdef USE_MORPHTARGETS\n\ttransformed *= morphTargetBaseInfluence;\n\t#ifdef MORPHTARGETS_TEXTURE\n\t\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) transformed += getMorph( gl_VertexID, i, 0 ).xyz * morphTargetInfluences[ i ];\n\t\t}\n\t#else\n\t\ttransformed += morphTarget0 * morphTargetInfluences[ 0 ];\n\t\ttransformed += morphTarget1 * morphTargetInfluences[ 1 ];\n\t\ttransformed += morphTarget2 * morphTargetInfluences[ 2 ];\n\t\ttransformed += morphTarget3 * morphTargetInfluences[ 3 ];\n\t\t#ifndef USE_MORPHNORMALS\n\t\t\ttransformed += morphTarget4 * morphTargetInfluences[ 4 ];\n\t\t\ttransformed += morphTarget5 * morphTargetInfluences[ 5 ];\n\t\t\ttransformed += morphTarget6 * morphTargetInfluences[ 6 ];\n\t\t\ttransformed += morphTarget7 * morphTargetInfluences[ 7 ];\n\t\t#endif\n\t#endif\n#endif"; - -var normal_fragment_begin = "float faceDirection = gl_FrontFacing ? 1.0 : - 1.0;\n#ifdef FLAT_SHADED\n\tvec3 fdx = dFdx( vViewPosition );\n\tvec3 fdy = dFdy( vViewPosition );\n\tvec3 normal = normalize( cross( fdx, fdy ) );\n#else\n\tvec3 normal = normalize( vNormal );\n\t#ifdef DOUBLE_SIDED\n\t\tnormal *= faceDirection;\n\t#endif\n#endif\n#if defined( USE_NORMALMAP_TANGENTSPACE ) || defined( USE_CLEARCOAT_NORMALMAP ) || defined( USE_ANISOTROPY )\n\t#ifdef USE_TANGENT\n\t\tmat3 tbn = mat3( normalize( vTangent ), normalize( vBitangent ), normal );\n\t#else\n\t\tmat3 tbn = getTangentFrame( - vViewPosition, normal, vNormalMapUv );\n\t#endif\n\t#if defined( DOUBLE_SIDED ) && ! defined( FLAT_SHADED )\n\t\ttbn[0] *= faceDirection;\n\t\ttbn[1] *= faceDirection;\n\t#endif\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\t#ifdef USE_TANGENT\n\t\tmat3 tbn2 = mat3( normalize( vTangent ), normalize( vBitangent ), normal );\n\t#else\n\t\tmat3 tbn2 = getTangentFrame( - vViewPosition, normal, vClearcoatNormalMapUv );\n\t#endif\n\t#if defined( DOUBLE_SIDED ) && ! defined( FLAT_SHADED )\n\t\ttbn2[0] *= faceDirection;\n\t\ttbn2[1] *= faceDirection;\n\t#endif\n#endif\nvec3 geometryNormal = normal;"; - -var normal_fragment_maps = "#ifdef USE_NORMALMAP_OBJECTSPACE\n\tnormal = texture2D( normalMap, vNormalMapUv ).xyz * 2.0 - 1.0;\n\t#ifdef FLIP_SIDED\n\t\tnormal = - normal;\n\t#endif\n\t#ifdef DOUBLE_SIDED\n\t\tnormal = normal * faceDirection;\n\t#endif\n\tnormal = normalize( normalMatrix * normal );\n#elif defined( USE_NORMALMAP_TANGENTSPACE )\n\tvec3 mapN = texture2D( normalMap, vNormalMapUv ).xyz * 2.0 - 1.0;\n\tmapN.xy *= normalScale;\n\tnormal = normalize( tbn * mapN );\n#elif defined( USE_BUMPMAP )\n\tnormal = perturbNormalArb( - vViewPosition, normal, dHdxy_fwd(), faceDirection );\n#endif"; - -var normal_pars_fragment = "#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif"; - -var normal_pars_vertex = "#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif"; - -var normal_vertex = "#ifndef FLAT_SHADED\n\tvNormal = normalize( transformedNormal );\n\t#ifdef USE_TANGENT\n\t\tvTangent = normalize( transformedTangent );\n\t\tvBitangent = normalize( cross( vNormal, vTangent ) * tangent.w );\n\t#endif\n#endif"; - -var normalmap_pars_fragment = "#ifdef USE_NORMALMAP\n\tuniform sampler2D normalMap;\n\tuniform vec2 normalScale;\n#endif\n#ifdef USE_NORMALMAP_OBJECTSPACE\n\tuniform mat3 normalMatrix;\n#endif\n#if ! defined ( USE_TANGENT ) && ( defined ( USE_NORMALMAP_TANGENTSPACE ) || defined ( USE_CLEARCOAT_NORMALMAP ) || defined( USE_ANISOTROPY ) )\n\tmat3 getTangentFrame( vec3 eye_pos, vec3 surf_norm, vec2 uv ) {\n\t\tvec3 q0 = dFdx( eye_pos.xyz );\n\t\tvec3 q1 = dFdy( eye_pos.xyz );\n\t\tvec2 st0 = dFdx( uv.st );\n\t\tvec2 st1 = dFdy( uv.st );\n\t\tvec3 N = surf_norm;\n\t\tvec3 q1perp = cross( q1, N );\n\t\tvec3 q0perp = cross( N, q0 );\n\t\tvec3 T = q1perp * st0.x + q0perp * st1.x;\n\t\tvec3 B = q1perp * st0.y + q0perp * st1.y;\n\t\tfloat det = max( dot( T, T ), dot( B, B ) );\n\t\tfloat scale = ( det == 0.0 ) ? 0.0 : inversesqrt( det );\n\t\treturn mat3( T * scale, B * scale, N );\n\t}\n#endif"; - -var clearcoat_normal_fragment_begin = "#ifdef USE_CLEARCOAT\n\tvec3 clearcoatNormal = geometryNormal;\n#endif"; - -var clearcoat_normal_fragment_maps = "#ifdef USE_CLEARCOAT_NORMALMAP\n\tvec3 clearcoatMapN = texture2D( clearcoatNormalMap, vClearcoatNormalMapUv ).xyz * 2.0 - 1.0;\n\tclearcoatMapN.xy *= clearcoatNormalScale;\n\tclearcoatNormal = normalize( tbn2 * clearcoatMapN );\n#endif"; - -var clearcoat_pars_fragment = "#ifdef USE_CLEARCOATMAP\n\tuniform sampler2D clearcoatMap;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tuniform sampler2D clearcoatNormalMap;\n\tuniform vec2 clearcoatNormalScale;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tuniform sampler2D clearcoatRoughnessMap;\n#endif"; - -var iridescence_pars_fragment = "#ifdef USE_IRIDESCENCEMAP\n\tuniform sampler2D iridescenceMap;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tuniform sampler2D iridescenceThicknessMap;\n#endif"; - -var output_fragment = "#ifdef OPAQUE\ndiffuseColor.a = 1.0;\n#endif\n#ifdef USE_TRANSMISSION\ndiffuseColor.a *= material.transmissionAlpha;\n#endif\ngl_FragColor = vec4( outgoingLight, diffuseColor.a );"; - -var packing = "vec3 packNormalToRGB( const in vec3 normal ) {\n\treturn normalize( normal ) * 0.5 + 0.5;\n}\nvec3 unpackRGBToNormal( const in vec3 rgb ) {\n\treturn 2.0 * rgb.xyz - 1.0;\n}\nconst float PackUpscale = 256. / 255.;const float UnpackDownscale = 255. / 256.;\nconst vec3 PackFactors = vec3( 256. * 256. * 256., 256. * 256., 256. );\nconst vec4 UnpackFactors = UnpackDownscale / vec4( PackFactors, 1. );\nconst float ShiftRight8 = 1. / 256.;\nvec4 packDepthToRGBA( const in float v ) {\n\tvec4 r = vec4( fract( v * PackFactors ), v );\n\tr.yzw -= r.xyz * ShiftRight8;\treturn r * PackUpscale;\n}\nfloat unpackRGBAToDepth( const in vec4 v ) {\n\treturn dot( v, UnpackFactors );\n}\nvec2 packDepthToRG( in highp float v ) {\n\treturn packDepthToRGBA( v ).yx;\n}\nfloat unpackRGToDepth( const in highp vec2 v ) {\n\treturn unpackRGBAToDepth( vec4( v.xy, 0.0, 0.0 ) );\n}\nvec4 pack2HalfToRGBA( vec2 v ) {\n\tvec4 r = vec4( v.x, fract( v.x * 255.0 ), v.y, fract( v.y * 255.0 ) );\n\treturn vec4( r.x - r.y / 255.0, r.y, r.z - r.w / 255.0, r.w );\n}\nvec2 unpackRGBATo2Half( vec4 v ) {\n\treturn vec2( v.x + ( v.y / 255.0 ), v.z + ( v.w / 255.0 ) );\n}\nfloat viewZToOrthographicDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( viewZ + near ) / ( near - far );\n}\nfloat orthographicDepthToViewZ( const in float depth, const in float near, const in float far ) {\n\treturn depth * ( near - far ) - near;\n}\nfloat viewZToPerspectiveDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( ( near + viewZ ) * far ) / ( ( far - near ) * viewZ );\n}\nfloat perspectiveDepthToViewZ( const in float depth, const in float near, const in float far ) {\n\treturn ( near * far ) / ( ( far - near ) * depth - far );\n}"; - -var premultiplied_alpha_fragment = "#ifdef PREMULTIPLIED_ALPHA\n\tgl_FragColor.rgb *= gl_FragColor.a;\n#endif"; - -var project_vertex = "vec4 mvPosition = vec4( transformed, 1.0 );\n#ifdef USE_INSTANCING\n\tmvPosition = instanceMatrix * mvPosition;\n#endif\nmvPosition = modelViewMatrix * mvPosition;\ngl_Position = projectionMatrix * mvPosition;"; - -var dithering_fragment = "#ifdef DITHERING\n\tgl_FragColor.rgb = dithering( gl_FragColor.rgb );\n#endif"; - -var dithering_pars_fragment = "#ifdef DITHERING\n\tvec3 dithering( vec3 color ) {\n\t\tfloat grid_position = rand( gl_FragCoord.xy );\n\t\tvec3 dither_shift_RGB = vec3( 0.25 / 255.0, -0.25 / 255.0, 0.25 / 255.0 );\n\t\tdither_shift_RGB = mix( 2.0 * dither_shift_RGB, -2.0 * dither_shift_RGB, grid_position );\n\t\treturn color + dither_shift_RGB;\n\t}\n#endif"; - -var roughnessmap_fragment = "float roughnessFactor = roughness;\n#ifdef USE_ROUGHNESSMAP\n\tvec4 texelRoughness = texture2D( roughnessMap, vRoughnessMapUv );\n\troughnessFactor *= texelRoughness.g;\n#endif"; - -var roughnessmap_pars_fragment = "#ifdef USE_ROUGHNESSMAP\n\tuniform sampler2D roughnessMap;\n#endif"; - -var shadowmap_pars_fragment = "#if NUM_SPOT_LIGHT_COORDS > 0\n\tvarying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ];\n#endif\n#if NUM_SPOT_LIGHT_MAPS > 0\n\tuniform sampler2D spotLightMap[ NUM_SPOT_LIGHT_MAPS ];\n#endif\n#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D directionalShadowMap[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D spotShadowMap[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D pointShadowMap[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n\tfloat texture2DCompare( sampler2D depths, vec2 uv, float compare ) {\n\t\treturn step( compare, unpackRGBAToDepth( texture2D( depths, uv ) ) );\n\t}\n\tvec2 texture2DDistribution( sampler2D shadow, vec2 uv ) {\n\t\treturn unpackRGBATo2Half( texture2D( shadow, uv ) );\n\t}\n\tfloat VSMShadow (sampler2D shadow, vec2 uv, float compare ){\n\t\tfloat occlusion = 1.0;\n\t\tvec2 distribution = texture2DDistribution( shadow, uv );\n\t\tfloat hard_shadow = step( compare , distribution.x );\n\t\tif (hard_shadow != 1.0 ) {\n\t\t\tfloat distance = compare - distribution.x ;\n\t\t\tfloat variance = max( 0.00000, distribution.y * distribution.y );\n\t\t\tfloat softness_probability = variance / (variance + distance * distance );\t\t\tsoftness_probability = clamp( ( softness_probability - 0.3 ) / ( 0.95 - 0.3 ), 0.0, 1.0 );\t\t\tocclusion = clamp( max( hard_shadow, softness_probability ), 0.0, 1.0 );\n\t\t}\n\t\treturn occlusion;\n\t}\n\tfloat getShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord ) {\n\t\tfloat shadow = 1.0;\n\t\tshadowCoord.xyz /= shadowCoord.w;\n\t\tshadowCoord.z += shadowBias;\n\t\tbool inFrustum = shadowCoord.x >= 0.0 && shadowCoord.x <= 1.0 && shadowCoord.y >= 0.0 && shadowCoord.y <= 1.0;\n\t\tbool frustumTest = inFrustum && shadowCoord.z <= 1.0;\n\t\tif ( frustumTest ) {\n\t\t#if defined( SHADOWMAP_TYPE_PCF )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx0 = - texelSize.x * shadowRadius;\n\t\t\tfloat dy0 = - texelSize.y * shadowRadius;\n\t\t\tfloat dx1 = + texelSize.x * shadowRadius;\n\t\t\tfloat dy1 = + texelSize.y * shadowRadius;\n\t\t\tfloat dx2 = dx0 / 2.0;\n\t\t\tfloat dy2 = dy0 / 2.0;\n\t\t\tfloat dx3 = dx1 / 2.0;\n\t\t\tfloat dy3 = dy1 / 2.0;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy1 ), shadowCoord.z )\n\t\t\t) * ( 1.0 / 17.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_PCF_SOFT )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx = texelSize.x;\n\t\t\tfloat dy = texelSize.y;\n\t\t\tvec2 uv = shadowCoord.xy;\n\t\t\tvec2 f = fract( uv * shadowMapSize + 0.5 );\n\t\t\tuv -= f * texelSize;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, uv, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( dx, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( 0.0, dy ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + texelSize, shadowCoord.z ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, dy ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( 0.0, -dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 0.0, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( mix( texture2DCompare( shadowMap, uv + vec2( -dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t f.x ),\n\t\t\t\t\t mix( texture2DCompare( shadowMap, uv + vec2( -dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t f.x ),\n\t\t\t\t\t f.y )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_VSM )\n\t\t\tshadow = VSMShadow( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#else\n\t\t\tshadow = texture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#endif\n\t\t}\n\t\treturn shadow;\n\t}\n\tvec2 cubeToUV( vec3 v, float texelSizeY ) {\n\t\tvec3 absV = abs( v );\n\t\tfloat scaleToCube = 1.0 / max( absV.x, max( absV.y, absV.z ) );\n\t\tabsV *= scaleToCube;\n\t\tv *= scaleToCube * ( 1.0 - 2.0 * texelSizeY );\n\t\tvec2 planar = v.xy;\n\t\tfloat almostATexel = 1.5 * texelSizeY;\n\t\tfloat almostOne = 1.0 - almostATexel;\n\t\tif ( absV.z >= almostOne ) {\n\t\t\tif ( v.z > 0.0 )\n\t\t\t\tplanar.x = 4.0 - v.x;\n\t\t} else if ( absV.x >= almostOne ) {\n\t\t\tfloat signX = sign( v.x );\n\t\t\tplanar.x = v.z * signX + 2.0 * signX;\n\t\t} else if ( absV.y >= almostOne ) {\n\t\t\tfloat signY = sign( v.y );\n\t\t\tplanar.x = v.x + 2.0 * signY + 2.0;\n\t\t\tplanar.y = v.z * signY - 2.0;\n\t\t}\n\t\treturn vec2( 0.125, 0.25 ) * planar + vec2( 0.375, 0.75 );\n\t}\n\tfloat getPointShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord, float shadowCameraNear, float shadowCameraFar ) {\n\t\tvec2 texelSize = vec2( 1.0 ) / ( shadowMapSize * vec2( 4.0, 2.0 ) );\n\t\tvec3 lightToPosition = shadowCoord.xyz;\n\t\tfloat dp = ( length( lightToPosition ) - shadowCameraNear ) / ( shadowCameraFar - shadowCameraNear );\t\tdp += shadowBias;\n\t\tvec3 bd3D = normalize( lightToPosition );\n\t\t#if defined( SHADOWMAP_TYPE_PCF ) || defined( SHADOWMAP_TYPE_PCF_SOFT ) || defined( SHADOWMAP_TYPE_VSM )\n\t\t\tvec2 offset = vec2( - 1, 1 ) * shadowRadius * texelSize.y;\n\t\t\treturn (\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxx, texelSize.y ), dp )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#else\n\t\t\treturn texture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp );\n\t\t#endif\n\t}\n#endif"; - -var shadowmap_pars_vertex = "#if NUM_SPOT_LIGHT_COORDS > 0\n\tuniform mat4 spotLightMatrix[ NUM_SPOT_LIGHT_COORDS ];\n\tvarying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ];\n#endif\n#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform mat4 directionalShadowMatrix[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform mat4 pointShadowMatrix[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n#endif"; - -var shadowmap_vertex = "#if ( defined( USE_SHADOWMAP ) && ( NUM_DIR_LIGHT_SHADOWS > 0 || NUM_POINT_LIGHT_SHADOWS > 0 ) ) || ( NUM_SPOT_LIGHT_COORDS > 0 )\n\tvec3 shadowWorldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\tvec4 shadowWorldPosition;\n#endif\n#if defined( USE_SHADOWMAP )\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * directionalLightShadows[ i ].shadowNormalBias, 0 );\n\t\t\tvDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * shadowWorldPosition;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * pointLightShadows[ i ].shadowNormalBias, 0 );\n\t\t\tvPointShadowCoord[ i ] = pointShadowMatrix[ i ] * shadowWorldPosition;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n#endif\n#if NUM_SPOT_LIGHT_COORDS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_COORDS; i ++ ) {\n\t\tshadowWorldPosition = worldPosition;\n\t\t#if ( defined( USE_SHADOWMAP ) && UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\t\tshadowWorldPosition.xyz += shadowWorldNormal * spotLightShadows[ i ].shadowNormalBias;\n\t\t#endif\n\t\tvSpotLightCoord[ i ] = spotLightMatrix[ i ] * shadowWorldPosition;\n\t}\n\t#pragma unroll_loop_end\n#endif"; - -var shadowmask_pars_fragment = "float getShadowMask() {\n\tfloat shadow = 1.0;\n\t#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\tdirectionalLight = directionalLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_SHADOWS; i ++ ) {\n\t\tspotLight = spotLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowBias, spotLight.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\tpointLight = pointLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#endif\n\treturn shadow;\n}"; - -var skinbase_vertex = "#ifdef USE_SKINNING\n\tmat4 boneMatX = getBoneMatrix( skinIndex.x );\n\tmat4 boneMatY = getBoneMatrix( skinIndex.y );\n\tmat4 boneMatZ = getBoneMatrix( skinIndex.z );\n\tmat4 boneMatW = getBoneMatrix( skinIndex.w );\n#endif"; - -var skinning_pars_vertex = "#ifdef USE_SKINNING\n\tuniform mat4 bindMatrix;\n\tuniform mat4 bindMatrixInverse;\n\tuniform highp sampler2D boneTexture;\n\tuniform int boneTextureSize;\n\tmat4 getBoneMatrix( const in float i ) {\n\t\tfloat j = i * 4.0;\n\t\tfloat x = mod( j, float( boneTextureSize ) );\n\t\tfloat y = floor( j / float( boneTextureSize ) );\n\t\tfloat dx = 1.0 / float( boneTextureSize );\n\t\tfloat dy = 1.0 / float( boneTextureSize );\n\t\ty = dy * ( y + 0.5 );\n\t\tvec4 v1 = texture2D( boneTexture, vec2( dx * ( x + 0.5 ), y ) );\n\t\tvec4 v2 = texture2D( boneTexture, vec2( dx * ( x + 1.5 ), y ) );\n\t\tvec4 v3 = texture2D( boneTexture, vec2( dx * ( x + 2.5 ), y ) );\n\t\tvec4 v4 = texture2D( boneTexture, vec2( dx * ( x + 3.5 ), y ) );\n\t\tmat4 bone = mat4( v1, v2, v3, v4 );\n\t\treturn bone;\n\t}\n#endif"; - -var skinning_vertex = "#ifdef USE_SKINNING\n\tvec4 skinVertex = bindMatrix * vec4( transformed, 1.0 );\n\tvec4 skinned = vec4( 0.0 );\n\tskinned += boneMatX * skinVertex * skinWeight.x;\n\tskinned += boneMatY * skinVertex * skinWeight.y;\n\tskinned += boneMatZ * skinVertex * skinWeight.z;\n\tskinned += boneMatW * skinVertex * skinWeight.w;\n\ttransformed = ( bindMatrixInverse * skinned ).xyz;\n#endif"; - -var skinnormal_vertex = "#ifdef USE_SKINNING\n\tmat4 skinMatrix = mat4( 0.0 );\n\tskinMatrix += skinWeight.x * boneMatX;\n\tskinMatrix += skinWeight.y * boneMatY;\n\tskinMatrix += skinWeight.z * boneMatZ;\n\tskinMatrix += skinWeight.w * boneMatW;\n\tskinMatrix = bindMatrixInverse * skinMatrix * bindMatrix;\n\tobjectNormal = vec4( skinMatrix * vec4( objectNormal, 0.0 ) ).xyz;\n\t#ifdef USE_TANGENT\n\t\tobjectTangent = vec4( skinMatrix * vec4( objectTangent, 0.0 ) ).xyz;\n\t#endif\n#endif"; - -var specularmap_fragment = "float specularStrength;\n#ifdef USE_SPECULARMAP\n\tvec4 texelSpecular = texture2D( specularMap, vSpecularMapUv );\n\tspecularStrength = texelSpecular.r;\n#else\n\tspecularStrength = 1.0;\n#endif"; - -var specularmap_pars_fragment = "#ifdef USE_SPECULARMAP\n\tuniform sampler2D specularMap;\n#endif"; - -var tonemapping_fragment = "#if defined( TONE_MAPPING )\n\tgl_FragColor.rgb = toneMapping( gl_FragColor.rgb );\n#endif"; - -var tonemapping_pars_fragment = "#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\nuniform float toneMappingExposure;\nvec3 LinearToneMapping( vec3 color ) {\n\treturn toneMappingExposure * color;\n}\nvec3 ReinhardToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\treturn saturate( color / ( vec3( 1.0 ) + color ) );\n}\nvec3 OptimizedCineonToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\tcolor = max( vec3( 0.0 ), color - 0.004 );\n\treturn pow( ( color * ( 6.2 * color + 0.5 ) ) / ( color * ( 6.2 * color + 1.7 ) + 0.06 ), vec3( 2.2 ) );\n}\nvec3 RRTAndODTFit( vec3 v ) {\n\tvec3 a = v * ( v + 0.0245786 ) - 0.000090537;\n\tvec3 b = v * ( 0.983729 * v + 0.4329510 ) + 0.238081;\n\treturn a / b;\n}\nvec3 ACESFilmicToneMapping( vec3 color ) {\n\tconst mat3 ACESInputMat = mat3(\n\t\tvec3( 0.59719, 0.07600, 0.02840 ),\t\tvec3( 0.35458, 0.90834, 0.13383 ),\n\t\tvec3( 0.04823, 0.01566, 0.83777 )\n\t);\n\tconst mat3 ACESOutputMat = mat3(\n\t\tvec3( 1.60475, -0.10208, -0.00327 ),\t\tvec3( -0.53108, 1.10813, -0.07276 ),\n\t\tvec3( -0.07367, -0.00605, 1.07602 )\n\t);\n\tcolor *= toneMappingExposure / 0.6;\n\tcolor = ACESInputMat * color;\n\tcolor = RRTAndODTFit( color );\n\tcolor = ACESOutputMat * color;\n\treturn saturate( color );\n}\nvec3 CustomToneMapping( vec3 color ) { return color; }"; - -var transmission_fragment = "#ifdef USE_TRANSMISSION\n\tmaterial.transmission = transmission;\n\tmaterial.transmissionAlpha = 1.0;\n\tmaterial.thickness = thickness;\n\tmaterial.attenuationDistance = attenuationDistance;\n\tmaterial.attenuationColor = attenuationColor;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tmaterial.transmission *= texture2D( transmissionMap, vTransmissionMapUv ).r;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tmaterial.thickness *= texture2D( thicknessMap, vThicknessMapUv ).g;\n\t#endif\n\tvec3 pos = vWorldPosition;\n\tvec3 v = normalize( cameraPosition - pos );\n\tvec3 n = inverseTransformDirection( normal, viewMatrix );\n\tvec4 transmitted = getIBLVolumeRefraction(\n\t\tn, v, material.roughness, material.diffuseColor, material.specularColor, material.specularF90,\n\t\tpos, modelMatrix, viewMatrix, projectionMatrix, material.ior, material.thickness,\n\t\tmaterial.attenuationColor, material.attenuationDistance );\n\tmaterial.transmissionAlpha = mix( material.transmissionAlpha, transmitted.a, material.transmission );\n\ttotalDiffuse = mix( totalDiffuse, transmitted.rgb, material.transmission );\n#endif"; - -var transmission_pars_fragment = "#ifdef USE_TRANSMISSION\n\tuniform float transmission;\n\tuniform float thickness;\n\tuniform float attenuationDistance;\n\tuniform vec3 attenuationColor;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tuniform sampler2D transmissionMap;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tuniform sampler2D thicknessMap;\n\t#endif\n\tuniform vec2 transmissionSamplerSize;\n\tuniform sampler2D transmissionSamplerMap;\n\tuniform mat4 modelMatrix;\n\tuniform mat4 projectionMatrix;\n\tvarying vec3 vWorldPosition;\n\tfloat w0( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * ( a * ( - a + 3.0 ) - 3.0 ) + 1.0 );\n\t}\n\tfloat w1( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * a * ( 3.0 * a - 6.0 ) + 4.0 );\n\t}\n\tfloat w2( float a ){\n\t\treturn ( 1.0 / 6.0 ) * ( a * ( a * ( - 3.0 * a + 3.0 ) + 3.0 ) + 1.0 );\n\t}\n\tfloat w3( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * a * a );\n\t}\n\tfloat g0( float a ) {\n\t\treturn w0( a ) + w1( a );\n\t}\n\tfloat g1( float a ) {\n\t\treturn w2( a ) + w3( a );\n\t}\n\tfloat h0( float a ) {\n\t\treturn - 1.0 + w1( a ) / ( w0( a ) + w1( a ) );\n\t}\n\tfloat h1( float a ) {\n\t\treturn 1.0 + w3( a ) / ( w2( a ) + w3( a ) );\n\t}\n\tvec4 bicubic( sampler2D tex, vec2 uv, vec4 texelSize, float lod ) {\n\t\tuv = uv * texelSize.zw + 0.5;\n\t\tvec2 iuv = floor( uv );\n\t\tvec2 fuv = fract( uv );\n\t\tfloat g0x = g0( fuv.x );\n\t\tfloat g1x = g1( fuv.x );\n\t\tfloat h0x = h0( fuv.x );\n\t\tfloat h1x = h1( fuv.x );\n\t\tfloat h0y = h0( fuv.y );\n\t\tfloat h1y = h1( fuv.y );\n\t\tvec2 p0 = ( vec2( iuv.x + h0x, iuv.y + h0y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p1 = ( vec2( iuv.x + h1x, iuv.y + h0y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p2 = ( vec2( iuv.x + h0x, iuv.y + h1y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p3 = ( vec2( iuv.x + h1x, iuv.y + h1y ) - 0.5 ) * texelSize.xy;\n\t\treturn g0( fuv.y ) * ( g0x * textureLod( tex, p0, lod ) + g1x * textureLod( tex, p1, lod ) ) +\n\t\t\tg1( fuv.y ) * ( g0x * textureLod( tex, p2, lod ) + g1x * textureLod( tex, p3, lod ) );\n\t}\n\tvec4 textureBicubic( sampler2D sampler, vec2 uv, float lod ) {\n\t\tvec2 fLodSize = vec2( textureSize( sampler, int( lod ) ) );\n\t\tvec2 cLodSize = vec2( textureSize( sampler, int( lod + 1.0 ) ) );\n\t\tvec2 fLodSizeInv = 1.0 / fLodSize;\n\t\tvec2 cLodSizeInv = 1.0 / cLodSize;\n\t\tvec4 fSample = bicubic( sampler, uv, vec4( fLodSizeInv, fLodSize ), floor( lod ) );\n\t\tvec4 cSample = bicubic( sampler, uv, vec4( cLodSizeInv, cLodSize ), ceil( lod ) );\n\t\treturn mix( fSample, cSample, fract( lod ) );\n\t}\n\tvec3 getVolumeTransmissionRay( const in vec3 n, const in vec3 v, const in float thickness, const in float ior, const in mat4 modelMatrix ) {\n\t\tvec3 refractionVector = refract( - v, normalize( n ), 1.0 / ior );\n\t\tvec3 modelScale;\n\t\tmodelScale.x = length( vec3( modelMatrix[ 0 ].xyz ) );\n\t\tmodelScale.y = length( vec3( modelMatrix[ 1 ].xyz ) );\n\t\tmodelScale.z = length( vec3( modelMatrix[ 2 ].xyz ) );\n\t\treturn normalize( refractionVector ) * thickness * modelScale;\n\t}\n\tfloat applyIorToRoughness( const in float roughness, const in float ior ) {\n\t\treturn roughness * clamp( ior * 2.0 - 2.0, 0.0, 1.0 );\n\t}\n\tvec4 getTransmissionSample( const in vec2 fragCoord, const in float roughness, const in float ior ) {\n\t\tfloat lod = log2( transmissionSamplerSize.x ) * applyIorToRoughness( roughness, ior );\n\t\treturn textureBicubic( transmissionSamplerMap, fragCoord.xy, lod );\n\t}\n\tvec3 volumeAttenuation( const in float transmissionDistance, const in vec3 attenuationColor, const in float attenuationDistance ) {\n\t\tif ( isinf( attenuationDistance ) ) {\n\t\t\treturn vec3( 1.0 );\n\t\t} else {\n\t\t\tvec3 attenuationCoefficient = -log( attenuationColor ) / attenuationDistance;\n\t\t\tvec3 transmittance = exp( - attenuationCoefficient * transmissionDistance );\t\t\treturn transmittance;\n\t\t}\n\t}\n\tvec4 getIBLVolumeRefraction( const in vec3 n, const in vec3 v, const in float roughness, const in vec3 diffuseColor,\n\t\tconst in vec3 specularColor, const in float specularF90, const in vec3 position, const in mat4 modelMatrix,\n\t\tconst in mat4 viewMatrix, const in mat4 projMatrix, const in float ior, const in float thickness,\n\t\tconst in vec3 attenuationColor, const in float attenuationDistance ) {\n\t\tvec3 transmissionRay = getVolumeTransmissionRay( n, v, thickness, ior, modelMatrix );\n\t\tvec3 refractedRayExit = position + transmissionRay;\n\t\tvec4 ndcPos = projMatrix * viewMatrix * vec4( refractedRayExit, 1.0 );\n\t\tvec2 refractionCoords = ndcPos.xy / ndcPos.w;\n\t\trefractionCoords += 1.0;\n\t\trefractionCoords /= 2.0;\n\t\tvec4 transmittedLight = getTransmissionSample( refractionCoords, roughness, ior );\n\t\tvec3 transmittance = diffuseColor * volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance );\n\t\tvec3 attenuatedColor = transmittance * transmittedLight.rgb;\n\t\tvec3 F = EnvironmentBRDF( n, v, specularColor, specularF90, roughness );\n\t\tfloat transmittanceFactor = ( transmittance.r + transmittance.g + transmittance.b ) / 3.0;\n\t\treturn vec4( ( 1.0 - F ) * attenuatedColor, 1.0 - ( 1.0 - transmittedLight.a ) * transmittanceFactor );\n\t}\n#endif"; - -var uv_pars_fragment = "#ifdef USE_UV\n\tvarying vec2 vUv;\n#endif\n#ifdef USE_MAP\n\tvarying vec2 vMapUv;\n#endif\n#ifdef USE_ALPHAMAP\n\tvarying vec2 vAlphaMapUv;\n#endif\n#ifdef USE_LIGHTMAP\n\tvarying vec2 vLightMapUv;\n#endif\n#ifdef USE_AOMAP\n\tvarying vec2 vAoMapUv;\n#endif\n#ifdef USE_BUMPMAP\n\tvarying vec2 vBumpMapUv;\n#endif\n#ifdef USE_NORMALMAP\n\tvarying vec2 vNormalMapUv;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tvarying vec2 vEmissiveMapUv;\n#endif\n#ifdef USE_METALNESSMAP\n\tvarying vec2 vMetalnessMapUv;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tvarying vec2 vRoughnessMapUv;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tvarying vec2 vAnisotropyMapUv;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tvarying vec2 vClearcoatMapUv;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tvarying vec2 vClearcoatNormalMapUv;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tvarying vec2 vClearcoatRoughnessMapUv;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tvarying vec2 vIridescenceMapUv;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tvarying vec2 vIridescenceThicknessMapUv;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tvarying vec2 vSheenColorMapUv;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tvarying vec2 vSheenRoughnessMapUv;\n#endif\n#ifdef USE_SPECULARMAP\n\tvarying vec2 vSpecularMapUv;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tvarying vec2 vSpecularColorMapUv;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tvarying vec2 vSpecularIntensityMapUv;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tuniform mat3 transmissionMapTransform;\n\tvarying vec2 vTransmissionMapUv;\n#endif\n#ifdef USE_THICKNESSMAP\n\tuniform mat3 thicknessMapTransform;\n\tvarying vec2 vThicknessMapUv;\n#endif"; - -var uv_pars_vertex = "#ifdef USE_UV\n\tvarying vec2 vUv;\n#endif\n#ifdef USE_MAP\n\tuniform mat3 mapTransform;\n\tvarying vec2 vMapUv;\n#endif\n#ifdef USE_ALPHAMAP\n\tuniform mat3 alphaMapTransform;\n\tvarying vec2 vAlphaMapUv;\n#endif\n#ifdef USE_LIGHTMAP\n\tuniform mat3 lightMapTransform;\n\tvarying vec2 vLightMapUv;\n#endif\n#ifdef USE_AOMAP\n\tuniform mat3 aoMapTransform;\n\tvarying vec2 vAoMapUv;\n#endif\n#ifdef USE_BUMPMAP\n\tuniform mat3 bumpMapTransform;\n\tvarying vec2 vBumpMapUv;\n#endif\n#ifdef USE_NORMALMAP\n\tuniform mat3 normalMapTransform;\n\tvarying vec2 vNormalMapUv;\n#endif\n#ifdef USE_DISPLACEMENTMAP\n\tuniform mat3 displacementMapTransform;\n\tvarying vec2 vDisplacementMapUv;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tuniform mat3 emissiveMapTransform;\n\tvarying vec2 vEmissiveMapUv;\n#endif\n#ifdef USE_METALNESSMAP\n\tuniform mat3 metalnessMapTransform;\n\tvarying vec2 vMetalnessMapUv;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tuniform mat3 roughnessMapTransform;\n\tvarying vec2 vRoughnessMapUv;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tvarying vec2 vAnisotropyMapUv;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tuniform mat3 clearcoatMapTransform;\n\tvarying vec2 vClearcoatMapUv;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tuniform mat3 clearcoatNormalMapTransform;\n\tvarying vec2 vClearcoatNormalMapUv;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tuniform mat3 clearcoatRoughnessMapTransform;\n\tvarying vec2 vClearcoatRoughnessMapUv;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tuniform mat3 sheenColorMapTransform;\n\tvarying vec2 vSheenColorMapUv;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tuniform mat3 sheenRoughnessMapTransform;\n\tvarying vec2 vSheenRoughnessMapUv;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tuniform mat3 iridescenceMapTransform;\n\tvarying vec2 vIridescenceMapUv;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tuniform mat3 iridescenceThicknessMapTransform;\n\tvarying vec2 vIridescenceThicknessMapUv;\n#endif\n#ifdef USE_SPECULARMAP\n\tuniform mat3 specularMapTransform;\n\tvarying vec2 vSpecularMapUv;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tuniform mat3 specularColorMapTransform;\n\tvarying vec2 vSpecularColorMapUv;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tuniform mat3 specularIntensityMapTransform;\n\tvarying vec2 vSpecularIntensityMapUv;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tuniform mat3 transmissionMapTransform;\n\tvarying vec2 vTransmissionMapUv;\n#endif\n#ifdef USE_THICKNESSMAP\n\tuniform mat3 thicknessMapTransform;\n\tvarying vec2 vThicknessMapUv;\n#endif"; - -var uv_vertex = "#ifdef USE_UV\n\tvUv = vec3( uv, 1 ).xy;\n#endif\n#ifdef USE_MAP\n\tvMapUv = ( mapTransform * vec3( MAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ALPHAMAP\n\tvAlphaMapUv = ( alphaMapTransform * vec3( ALPHAMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_LIGHTMAP\n\tvLightMapUv = ( lightMapTransform * vec3( LIGHTMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_AOMAP\n\tvAoMapUv = ( aoMapTransform * vec3( AOMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_BUMPMAP\n\tvBumpMapUv = ( bumpMapTransform * vec3( BUMPMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_NORMALMAP\n\tvNormalMapUv = ( normalMapTransform * vec3( NORMALMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_DISPLACEMENTMAP\n\tvDisplacementMapUv = ( displacementMapTransform * vec3( DISPLACEMENTMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tvEmissiveMapUv = ( emissiveMapTransform * vec3( EMISSIVEMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_METALNESSMAP\n\tvMetalnessMapUv = ( metalnessMapTransform * vec3( METALNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tvRoughnessMapUv = ( roughnessMapTransform * vec3( ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tvAnisotropyMapUv = ( vec3( ANISOTROPYMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tvClearcoatMapUv = ( clearcoatMapTransform * vec3( CLEARCOATMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tvClearcoatNormalMapUv = ( clearcoatNormalMapTransform * vec3( CLEARCOAT_NORMALMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tvClearcoatRoughnessMapUv = ( clearcoatRoughnessMapTransform * vec3( CLEARCOAT_ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tvIridescenceMapUv = ( iridescenceMapTransform * vec3( IRIDESCENCEMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tvIridescenceThicknessMapUv = ( iridescenceThicknessMapTransform * vec3( IRIDESCENCE_THICKNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tvSheenColorMapUv = ( sheenColorMapTransform * vec3( SHEEN_COLORMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tvSheenRoughnessMapUv = ( sheenRoughnessMapTransform * vec3( SHEEN_ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULARMAP\n\tvSpecularMapUv = ( specularMapTransform * vec3( SPECULARMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tvSpecularColorMapUv = ( specularColorMapTransform * vec3( SPECULAR_COLORMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tvSpecularIntensityMapUv = ( specularIntensityMapTransform * vec3( SPECULAR_INTENSITYMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tvTransmissionMapUv = ( transmissionMapTransform * vec3( TRANSMISSIONMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_THICKNESSMAP\n\tvThicknessMapUv = ( thicknessMapTransform * vec3( THICKNESSMAP_UV, 1 ) ).xy;\n#endif"; - -var worldpos_vertex = "#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined ( USE_SHADOWMAP ) || defined ( USE_TRANSMISSION ) || NUM_SPOT_LIGHT_COORDS > 0\n\tvec4 worldPosition = vec4( transformed, 1.0 );\n\t#ifdef USE_INSTANCING\n\t\tworldPosition = instanceMatrix * worldPosition;\n\t#endif\n\tworldPosition = modelMatrix * worldPosition;\n#endif"; - -const vertex$h = "varying vec2 vUv;\nuniform mat3 uvTransform;\nvoid main() {\n\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n\tgl_Position = vec4( position.xy, 1.0, 1.0 );\n}"; - -const fragment$h = "uniform sampler2D t2D;\nuniform float backgroundIntensity;\nvarying vec2 vUv;\nvoid main() {\n\tvec4 texColor = texture2D( t2D, vUv );\n\ttexColor.rgb *= backgroundIntensity;\n\tgl_FragColor = texColor;\n\t#include \n\t#include \n}"; - -const vertex$g = "varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n\tgl_Position.z = gl_Position.w;\n}"; - -const fragment$g = "#ifdef ENVMAP_TYPE_CUBE\n\tuniform samplerCube envMap;\n#elif defined( ENVMAP_TYPE_CUBE_UV )\n\tuniform sampler2D envMap;\n#endif\nuniform float flipEnvMap;\nuniform float backgroundBlurriness;\nuniform float backgroundIntensity;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 texColor = textureCube( envMap, vec3( flipEnvMap * vWorldDirection.x, vWorldDirection.yz ) );\n\t#elif defined( ENVMAP_TYPE_CUBE_UV )\n\t\tvec4 texColor = textureCubeUV( envMap, vWorldDirection, backgroundBlurriness );\n\t#else\n\t\tvec4 texColor = vec4( 0.0, 0.0, 0.0, 1.0 );\n\t#endif\n\ttexColor.rgb *= backgroundIntensity;\n\tgl_FragColor = texColor;\n\t#include \n\t#include \n}"; - -const vertex$f = "varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n\tgl_Position.z = gl_Position.w;\n}"; - -const fragment$f = "uniform samplerCube tCube;\nuniform float tFlip;\nuniform float opacity;\nvarying vec3 vWorldDirection;\nvoid main() {\n\tvec4 texColor = textureCube( tCube, vec3( tFlip * vWorldDirection.x, vWorldDirection.yz ) );\n\tgl_FragColor = texColor;\n\tgl_FragColor.a *= opacity;\n\t#include \n\t#include \n}"; - -const vertex$e = "#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvHighPrecisionZW = gl_Position.zw;\n}"; - -const fragment$e = "#if DEPTH_PACKING == 3200\n\tuniform float opacity;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( 1.0 );\n\t#if DEPTH_PACKING == 3200\n\t\tdiffuseColor.a = opacity;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\tfloat fragCoordZ = 0.5 * vHighPrecisionZW[0] / vHighPrecisionZW[1] + 0.5;\n\t#if DEPTH_PACKING == 3200\n\t\tgl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity );\n\t#elif DEPTH_PACKING == 3201\n\t\tgl_FragColor = packDepthToRGBA( fragCoordZ );\n\t#endif\n}"; - -const vertex$d = "#define DISTANCE\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvWorldPosition = worldPosition.xyz;\n}"; - -const fragment$d = "#define DISTANCE\nuniform vec3 referencePosition;\nuniform float nearDistance;\nuniform float farDistance;\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main () {\n\t#include \n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include \n\t#include \n\t#include \n\tfloat dist = length( vWorldPosition - referencePosition );\n\tdist = ( dist - nearDistance ) / ( farDistance - nearDistance );\n\tdist = saturate( dist );\n\tgl_FragColor = packDepthToRGBA( dist );\n}"; - -const vertex$c = "varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n}"; - -const fragment$c = "uniform sampler2D tEquirect;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvec3 direction = normalize( vWorldDirection );\n\tvec2 sampleUV = equirectUv( direction );\n\tgl_FragColor = texture2D( tEquirect, sampleUV );\n\t#include \n\t#include \n}"; - -const vertex$b = "uniform float scale;\nattribute float lineDistance;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvLineDistance = scale * lineDistance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - -const fragment$b = "uniform vec3 diffuse;\nuniform float opacity;\nuniform float dashSize;\nuniform float totalSize;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tif ( mod( vLineDistance, totalSize ) > dashSize ) {\n\t\tdiscard;\n\t}\n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - -const vertex$a = "#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#if defined ( USE_ENVMAP ) || defined ( USE_SKINNING )\n\t\t#include \n\t\t#include \n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - -const fragment$a = "uniform vec3 diffuse;\nuniform float opacity;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel = texture2D( lightMap, vLightMapUv );\n\t\treflectedLight.indirectDiffuse += lightMapTexel.rgb * lightMapIntensity * RECIPROCAL_PI;\n\t#else\n\t\treflectedLight.indirectDiffuse += vec3( 1.0 );\n\t#endif\n\t#include \n\treflectedLight.indirectDiffuse *= diffuseColor.rgb;\n\tvec3 outgoingLight = reflectedLight.indirectDiffuse;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - -const vertex$9 = "#define LAMBERT\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n\t#include \n}"; - -const fragment$9 = "#define LAMBERT\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - -const vertex$8 = "#define MATCAP\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n}"; - -const fragment$8 = "#define MATCAP\nuniform vec3 diffuse;\nuniform float opacity;\nuniform sampler2D matcap;\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 viewDir = normalize( vViewPosition );\n\tvec3 x = normalize( vec3( viewDir.z, 0.0, - viewDir.x ) );\n\tvec3 y = cross( viewDir, x );\n\tvec2 uv = vec2( dot( x, normal ), dot( y, normal ) ) * 0.495 + 0.5;\n\t#ifdef USE_MATCAP\n\t\tvec4 matcapColor = texture2D( matcap, uv );\n\t#else\n\t\tvec4 matcapColor = vec4( vec3( mix( 0.2, 0.8, uv.y ) ), 1.0 );\n\t#endif\n\tvec3 outgoingLight = diffuseColor.rgb * matcapColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - -const vertex$7 = "#define NORMAL\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvarying vec3 vViewPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvViewPosition = - mvPosition.xyz;\n#endif\n}"; - -const fragment$7 = "#define NORMAL\nuniform float opacity;\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvarying vec3 vViewPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\tgl_FragColor = vec4( packNormalToRGB( normal ), opacity );\n\t#ifdef OPAQUE\n\t\tgl_FragColor.a = 1.0;\n\t#endif\n}"; - -const vertex$6 = "#define PHONG\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n\t#include \n}"; - -const fragment$6 = "#define PHONG\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform vec3 specular;\nuniform float shininess;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - -const vertex$5 = "#define STANDARD\nvarying vec3 vViewPosition;\n#ifdef USE_TRANSMISSION\n\tvarying vec3 vWorldPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n#ifdef USE_TRANSMISSION\n\tvWorldPosition = worldPosition.xyz;\n#endif\n}"; - -const fragment$5 = "#define STANDARD\n#ifdef PHYSICAL\n\t#define IOR\n\t#define USE_SPECULAR\n#endif\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float roughness;\nuniform float metalness;\nuniform float opacity;\n#ifdef IOR\n\tuniform float ior;\n#endif\n#ifdef USE_SPECULAR\n\tuniform float specularIntensity;\n\tuniform vec3 specularColor;\n\t#ifdef USE_SPECULAR_COLORMAP\n\t\tuniform sampler2D specularColorMap;\n\t#endif\n\t#ifdef USE_SPECULAR_INTENSITYMAP\n\t\tuniform sampler2D specularIntensityMap;\n\t#endif\n#endif\n#ifdef USE_CLEARCOAT\n\tuniform float clearcoat;\n\tuniform float clearcoatRoughness;\n#endif\n#ifdef USE_IRIDESCENCE\n\tuniform float iridescence;\n\tuniform float iridescenceIOR;\n\tuniform float iridescenceThicknessMinimum;\n\tuniform float iridescenceThicknessMaximum;\n#endif\n#ifdef USE_SHEEN\n\tuniform vec3 sheenColor;\n\tuniform float sheenRoughness;\n\t#ifdef USE_SHEEN_COLORMAP\n\t\tuniform sampler2D sheenColorMap;\n\t#endif\n\t#ifdef USE_SHEEN_ROUGHNESSMAP\n\t\tuniform sampler2D sheenRoughnessMap;\n\t#endif\n#endif\n#ifdef USE_ANISOTROPY\n\tuniform vec2 anisotropyVector;\n\t#ifdef USE_ANISOTROPYMAP\n\t\tuniform sampler2D anisotropyMap;\n\t#endif\n#endif\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 totalDiffuse = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse;\n\tvec3 totalSpecular = reflectedLight.directSpecular + reflectedLight.indirectSpecular;\n\t#include \n\tvec3 outgoingLight = totalDiffuse + totalSpecular + totalEmissiveRadiance;\n\t#ifdef USE_SHEEN\n\t\tfloat sheenEnergyComp = 1.0 - 0.157 * max3( material.sheenColor );\n\t\toutgoingLight = outgoingLight * sheenEnergyComp + sheenSpecular;\n\t#endif\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNVcc = saturate( dot( geometry.clearcoatNormal, geometry.viewDir ) );\n\t\tvec3 Fcc = F_Schlick( material.clearcoatF0, material.clearcoatF90, dotNVcc );\n\t\toutgoingLight = outgoingLight * ( 1.0 - material.clearcoat * Fcc ) + clearcoatSpecular * material.clearcoat;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - -const vertex$4 = "#define TOON\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n}"; - -const fragment$4 = "#define TOON\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - -const vertex$3 = "uniform float size;\nuniform float scale;\n#include \n#include \n#include \n#include \n#include \n#include \n#ifdef USE_POINTS_UV\n\tvarying vec2 vUv;\n\tuniform mat3 uvTransform;\n#endif\nvoid main() {\n\t#ifdef USE_POINTS_UV\n\t\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tgl_PointSize = size;\n\t#ifdef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) gl_PointSize *= ( scale / - mvPosition.z );\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n}"; - -const fragment$3 = "uniform vec3 diffuse;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - -const vertex$2 = "#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - -const fragment$2 = "uniform vec3 color;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tgl_FragColor = vec4( color, opacity * ( 1.0 - getShadowMask() ) );\n\t#include \n\t#include \n\t#include \n}"; - -const vertex$1 = "uniform float rotation;\nuniform vec2 center;\n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 mvPosition = modelViewMatrix * vec4( 0.0, 0.0, 0.0, 1.0 );\n\tvec2 scale;\n\tscale.x = length( vec3( modelMatrix[ 0 ].x, modelMatrix[ 0 ].y, modelMatrix[ 0 ].z ) );\n\tscale.y = length( vec3( modelMatrix[ 1 ].x, modelMatrix[ 1 ].y, modelMatrix[ 1 ].z ) );\n\t#ifndef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) scale *= - mvPosition.z;\n\t#endif\n\tvec2 alignedPosition = ( position.xy - ( center - vec2( 0.5 ) ) ) * scale;\n\tvec2 rotatedPosition;\n\trotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;\n\trotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;\n\tmvPosition.xy += rotatedPosition;\n\tgl_Position = projectionMatrix * mvPosition;\n\t#include \n\t#include \n\t#include \n}"; - -const fragment$1 = "uniform vec3 diffuse;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n}"; - -const ShaderChunk = { - alphamap_fragment: alphamap_fragment, - alphamap_pars_fragment: alphamap_pars_fragment, - alphatest_fragment: alphatest_fragment, - alphatest_pars_fragment: alphatest_pars_fragment, - aomap_fragment: aomap_fragment, - aomap_pars_fragment: aomap_pars_fragment, - begin_vertex: begin_vertex, - beginnormal_vertex: beginnormal_vertex, - bsdfs: bsdfs, - iridescence_fragment: iridescence_fragment, - bumpmap_pars_fragment: bumpmap_pars_fragment, - clipping_planes_fragment: clipping_planes_fragment, - clipping_planes_pars_fragment: clipping_planes_pars_fragment, - clipping_planes_pars_vertex: clipping_planes_pars_vertex, - clipping_planes_vertex: clipping_planes_vertex, - color_fragment: color_fragment, - color_pars_fragment: color_pars_fragment, - color_pars_vertex: color_pars_vertex, - color_vertex: color_vertex, - common: common, - cube_uv_reflection_fragment: cube_uv_reflection_fragment, - defaultnormal_vertex: defaultnormal_vertex, - displacementmap_pars_vertex: displacementmap_pars_vertex, - displacementmap_vertex: displacementmap_vertex, - emissivemap_fragment: emissivemap_fragment, - emissivemap_pars_fragment: emissivemap_pars_fragment, - encodings_fragment: encodings_fragment, - encodings_pars_fragment: encodings_pars_fragment, - envmap_fragment: envmap_fragment, - envmap_common_pars_fragment: envmap_common_pars_fragment, - envmap_pars_fragment: envmap_pars_fragment, - envmap_pars_vertex: envmap_pars_vertex, - envmap_physical_pars_fragment: envmap_physical_pars_fragment, - envmap_vertex: envmap_vertex, - fog_vertex: fog_vertex, - fog_pars_vertex: fog_pars_vertex, - fog_fragment: fog_fragment, - fog_pars_fragment: fog_pars_fragment, - gradientmap_pars_fragment: gradientmap_pars_fragment, - lightmap_fragment: lightmap_fragment, - lightmap_pars_fragment: lightmap_pars_fragment, - lights_lambert_fragment: lights_lambert_fragment, - lights_lambert_pars_fragment: lights_lambert_pars_fragment, - lights_pars_begin: lights_pars_begin, - lights_toon_fragment: lights_toon_fragment, - lights_toon_pars_fragment: lights_toon_pars_fragment, - lights_phong_fragment: lights_phong_fragment, - lights_phong_pars_fragment: lights_phong_pars_fragment, - lights_physical_fragment: lights_physical_fragment, - lights_physical_pars_fragment: lights_physical_pars_fragment, - lights_fragment_begin: lights_fragment_begin, - lights_fragment_maps: lights_fragment_maps, - lights_fragment_end: lights_fragment_end, - logdepthbuf_fragment: logdepthbuf_fragment, - logdepthbuf_pars_fragment: logdepthbuf_pars_fragment, - logdepthbuf_pars_vertex: logdepthbuf_pars_vertex, - logdepthbuf_vertex: logdepthbuf_vertex, - map_fragment: map_fragment, - map_pars_fragment: map_pars_fragment, - map_particle_fragment: map_particle_fragment, - map_particle_pars_fragment: map_particle_pars_fragment, - metalnessmap_fragment: metalnessmap_fragment, - metalnessmap_pars_fragment: metalnessmap_pars_fragment, - morphcolor_vertex: morphcolor_vertex, - morphnormal_vertex: morphnormal_vertex, - morphtarget_pars_vertex: morphtarget_pars_vertex, - morphtarget_vertex: morphtarget_vertex, - normal_fragment_begin: normal_fragment_begin, - normal_fragment_maps: normal_fragment_maps, - normal_pars_fragment: normal_pars_fragment, - normal_pars_vertex: normal_pars_vertex, - normal_vertex: normal_vertex, - normalmap_pars_fragment: normalmap_pars_fragment, - clearcoat_normal_fragment_begin: clearcoat_normal_fragment_begin, - clearcoat_normal_fragment_maps: clearcoat_normal_fragment_maps, - clearcoat_pars_fragment: clearcoat_pars_fragment, - iridescence_pars_fragment: iridescence_pars_fragment, - output_fragment: output_fragment, - packing: packing, - premultiplied_alpha_fragment: premultiplied_alpha_fragment, - project_vertex: project_vertex, - dithering_fragment: dithering_fragment, - dithering_pars_fragment: dithering_pars_fragment, - roughnessmap_fragment: roughnessmap_fragment, - roughnessmap_pars_fragment: roughnessmap_pars_fragment, - shadowmap_pars_fragment: shadowmap_pars_fragment, - shadowmap_pars_vertex: shadowmap_pars_vertex, - shadowmap_vertex: shadowmap_vertex, - shadowmask_pars_fragment: shadowmask_pars_fragment, - skinbase_vertex: skinbase_vertex, - skinning_pars_vertex: skinning_pars_vertex, - skinning_vertex: skinning_vertex, - skinnormal_vertex: skinnormal_vertex, - specularmap_fragment: specularmap_fragment, - specularmap_pars_fragment: specularmap_pars_fragment, - tonemapping_fragment: tonemapping_fragment, - tonemapping_pars_fragment: tonemapping_pars_fragment, - transmission_fragment: transmission_fragment, - transmission_pars_fragment: transmission_pars_fragment, - uv_pars_fragment: uv_pars_fragment, - uv_pars_vertex: uv_pars_vertex, - uv_vertex: uv_vertex, - worldpos_vertex: worldpos_vertex, - - background_vert: vertex$h, - background_frag: fragment$h, - backgroundCube_vert: vertex$g, - backgroundCube_frag: fragment$g, - cube_vert: vertex$f, - cube_frag: fragment$f, - depth_vert: vertex$e, - depth_frag: fragment$e, - distanceRGBA_vert: vertex$d, - distanceRGBA_frag: fragment$d, - equirect_vert: vertex$c, - equirect_frag: fragment$c, - linedashed_vert: vertex$b, - linedashed_frag: fragment$b, - meshbasic_vert: vertex$a, - meshbasic_frag: fragment$a, - meshlambert_vert: vertex$9, - meshlambert_frag: fragment$9, - meshmatcap_vert: vertex$8, - meshmatcap_frag: fragment$8, - meshnormal_vert: vertex$7, - meshnormal_frag: fragment$7, - meshphong_vert: vertex$6, - meshphong_frag: fragment$6, - meshphysical_vert: vertex$5, - meshphysical_frag: fragment$5, - meshtoon_vert: vertex$4, - meshtoon_frag: fragment$4, - points_vert: vertex$3, - points_frag: fragment$3, - shadow_vert: vertex$2, - shadow_frag: fragment$2, - sprite_vert: vertex$1, - sprite_frag: fragment$1 -}; - -/** - * Uniforms library for shared webgl shaders - */ - -const UniformsLib = { - - common: { - - diffuse: { value: /*@__PURE__*/ new Color( 0xffffff ) }, - opacity: { value: 1.0 }, - - map: { value: null }, - mapTransform: { value: /*@__PURE__*/ new Matrix3() }, - - alphaMap: { value: null }, - alphaMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - - alphaTest: { value: 0 } - - }, - - specularmap: { - - specularMap: { value: null }, - specularMapTransform: { value: /*@__PURE__*/ new Matrix3() } - - }, - - envmap: { - - envMap: { value: null }, - flipEnvMap: { value: - 1 }, - reflectivity: { value: 1.0 }, // basic, lambert, phong - ior: { value: 1.5 }, // physical - refractionRatio: { value: 0.98 }, // basic, lambert, phong - - }, - - aomap: { - - aoMap: { value: null }, - aoMapIntensity: { value: 1 }, - aoMapTransform: { value: /*@__PURE__*/ new Matrix3() } - - }, - - lightmap: { - - lightMap: { value: null }, - lightMapIntensity: { value: 1 }, - lightMapTransform: { value: /*@__PURE__*/ new Matrix3() } - - }, - - bumpmap: { - - bumpMap: { value: null }, - bumpMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - bumpScale: { value: 1 } - - }, - - normalmap: { - - normalMap: { value: null }, - normalMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - normalScale: { value: /*@__PURE__*/ new Vector2( 1, 1 ) } - - }, - - displacementmap: { - - displacementMap: { value: null }, - displacementMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - displacementScale: { value: 1 }, - displacementBias: { value: 0 } - - }, - - emissivemap: { - - emissiveMap: { value: null }, - emissiveMapTransform: { value: /*@__PURE__*/ new Matrix3() } - - }, - - metalnessmap: { - - metalnessMap: { value: null }, - metalnessMapTransform: { value: /*@__PURE__*/ new Matrix3() } - - }, - - roughnessmap: { - - roughnessMap: { value: null }, - roughnessMapTransform: { value: /*@__PURE__*/ new Matrix3() } - - }, - - gradientmap: { - - gradientMap: { value: null } - - }, - - fog: { - - fogDensity: { value: 0.00025 }, - fogNear: { value: 1 }, - fogFar: { value: 2000 }, - fogColor: { value: /*@__PURE__*/ new Color( 0xffffff ) } - - }, - - lights: { - - ambientLightColor: { value: [] }, - - lightProbe: { value: [] }, - - directionalLights: { value: [], properties: { - direction: {}, - color: {} - } }, - - directionalLightShadows: { value: [], properties: { - shadowBias: {}, - shadowNormalBias: {}, - shadowRadius: {}, - shadowMapSize: {} - } }, - - directionalShadowMap: { value: [] }, - directionalShadowMatrix: { value: [] }, - - spotLights: { value: [], properties: { - color: {}, - position: {}, - direction: {}, - distance: {}, - coneCos: {}, - penumbraCos: {}, - decay: {} - } }, - - spotLightShadows: { value: [], properties: { - shadowBias: {}, - shadowNormalBias: {}, - shadowRadius: {}, - shadowMapSize: {} - } }, - - spotLightMap: { value: [] }, - spotShadowMap: { value: [] }, - spotLightMatrix: { value: [] }, - - pointLights: { value: [], properties: { - color: {}, - position: {}, - decay: {}, - distance: {} - } }, - - pointLightShadows: { value: [], properties: { - shadowBias: {}, - shadowNormalBias: {}, - shadowRadius: {}, - shadowMapSize: {}, - shadowCameraNear: {}, - shadowCameraFar: {} - } }, - - pointShadowMap: { value: [] }, - pointShadowMatrix: { value: [] }, - - hemisphereLights: { value: [], properties: { - direction: {}, - skyColor: {}, - groundColor: {} - } }, - - // TODO (abelnation): RectAreaLight BRDF data needs to be moved from example to main src - rectAreaLights: { value: [], properties: { - color: {}, - position: {}, - width: {}, - height: {} - } }, - - ltc_1: { value: null }, - ltc_2: { value: null } - - }, - - points: { - - diffuse: { value: /*@__PURE__*/ new Color( 0xffffff ) }, - opacity: { value: 1.0 }, - size: { value: 1.0 }, - scale: { value: 1.0 }, - map: { value: null }, - alphaMap: { value: null }, - alphaTest: { value: 0 }, - uvTransform: { value: /*@__PURE__*/ new Matrix3() } - - }, - - sprite: { - - diffuse: { value: /*@__PURE__*/ new Color( 0xffffff ) }, - opacity: { value: 1.0 }, - center: { value: /*@__PURE__*/ new Vector2( 0.5, 0.5 ) }, - rotation: { value: 0.0 }, - map: { value: null }, - mapTransform: { value: /*@__PURE__*/ new Matrix3() }, - alphaMap: { value: null }, - alphaTest: { value: 0 } - - } - -}; - -const ShaderLib = { - - basic: { - - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.specularmap, - UniformsLib.envmap, - UniformsLib.aomap, - UniformsLib.lightmap, - UniformsLib.fog - ] ), - - vertexShader: ShaderChunk.meshbasic_vert, - fragmentShader: ShaderChunk.meshbasic_frag - - }, - - lambert: { - - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.specularmap, - UniformsLib.envmap, - UniformsLib.aomap, - UniformsLib.lightmap, - UniformsLib.emissivemap, - UniformsLib.bumpmap, - UniformsLib.normalmap, - UniformsLib.displacementmap, - UniformsLib.fog, - UniformsLib.lights, - { - emissive: { value: /*@__PURE__*/ new Color( 0x000000 ) } - } - ] ), - - vertexShader: ShaderChunk.meshlambert_vert, - fragmentShader: ShaderChunk.meshlambert_frag - - }, - - phong: { - - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.specularmap, - UniformsLib.envmap, - UniformsLib.aomap, - UniformsLib.lightmap, - UniformsLib.emissivemap, - UniformsLib.bumpmap, - UniformsLib.normalmap, - UniformsLib.displacementmap, - UniformsLib.fog, - UniformsLib.lights, - { - emissive: { value: /*@__PURE__*/ new Color( 0x000000 ) }, - specular: { value: /*@__PURE__*/ new Color( 0x111111 ) }, - shininess: { value: 30 } - } - ] ), - - vertexShader: ShaderChunk.meshphong_vert, - fragmentShader: ShaderChunk.meshphong_frag - - }, - - standard: { - - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.envmap, - UniformsLib.aomap, - UniformsLib.lightmap, - UniformsLib.emissivemap, - UniformsLib.bumpmap, - UniformsLib.normalmap, - UniformsLib.displacementmap, - UniformsLib.roughnessmap, - UniformsLib.metalnessmap, - UniformsLib.fog, - UniformsLib.lights, - { - emissive: { value: /*@__PURE__*/ new Color( 0x000000 ) }, - roughness: { value: 1.0 }, - metalness: { value: 0.0 }, - envMapIntensity: { value: 1 } // temporary - } - ] ), - - vertexShader: ShaderChunk.meshphysical_vert, - fragmentShader: ShaderChunk.meshphysical_frag - - }, - - toon: { - - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.aomap, - UniformsLib.lightmap, - UniformsLib.emissivemap, - UniformsLib.bumpmap, - UniformsLib.normalmap, - UniformsLib.displacementmap, - UniformsLib.gradientmap, - UniformsLib.fog, - UniformsLib.lights, - { - emissive: { value: /*@__PURE__*/ new Color( 0x000000 ) } - } - ] ), - - vertexShader: ShaderChunk.meshtoon_vert, - fragmentShader: ShaderChunk.meshtoon_frag - - }, - - matcap: { - - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.bumpmap, - UniformsLib.normalmap, - UniformsLib.displacementmap, - UniformsLib.fog, - { - matcap: { value: null } - } - ] ), - - vertexShader: ShaderChunk.meshmatcap_vert, - fragmentShader: ShaderChunk.meshmatcap_frag - - }, - - points: { - - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.points, - UniformsLib.fog - ] ), - - vertexShader: ShaderChunk.points_vert, - fragmentShader: ShaderChunk.points_frag - - }, - - dashed: { - - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.fog, - { - scale: { value: 1 }, - dashSize: { value: 1 }, - totalSize: { value: 2 } - } - ] ), - - vertexShader: ShaderChunk.linedashed_vert, - fragmentShader: ShaderChunk.linedashed_frag - - }, - - depth: { - - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.displacementmap - ] ), - - vertexShader: ShaderChunk.depth_vert, - fragmentShader: ShaderChunk.depth_frag - - }, - - normal: { - - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.bumpmap, - UniformsLib.normalmap, - UniformsLib.displacementmap, - { - opacity: { value: 1.0 } - } - ] ), - - vertexShader: ShaderChunk.meshnormal_vert, - fragmentShader: ShaderChunk.meshnormal_frag - - }, - - sprite: { - - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.sprite, - UniformsLib.fog - ] ), - - vertexShader: ShaderChunk.sprite_vert, - fragmentShader: ShaderChunk.sprite_frag - - }, - - background: { - - uniforms: { - uvTransform: { value: /*@__PURE__*/ new Matrix3() }, - t2D: { value: null }, - backgroundIntensity: { value: 1 } - }, - - vertexShader: ShaderChunk.background_vert, - fragmentShader: ShaderChunk.background_frag - - }, - - backgroundCube: { - - uniforms: { - envMap: { value: null }, - flipEnvMap: { value: - 1 }, - backgroundBlurriness: { value: 0 }, - backgroundIntensity: { value: 1 } - }, - - vertexShader: ShaderChunk.backgroundCube_vert, - fragmentShader: ShaderChunk.backgroundCube_frag - - }, - - cube: { - - uniforms: { - tCube: { value: null }, - tFlip: { value: - 1 }, - opacity: { value: 1.0 } - }, - - vertexShader: ShaderChunk.cube_vert, - fragmentShader: ShaderChunk.cube_frag - - }, - - equirect: { - - uniforms: { - tEquirect: { value: null }, - }, - - vertexShader: ShaderChunk.equirect_vert, - fragmentShader: ShaderChunk.equirect_frag - - }, - - distanceRGBA: { - - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.common, - UniformsLib.displacementmap, - { - referencePosition: { value: /*@__PURE__*/ new Vector3() }, - nearDistance: { value: 1 }, - farDistance: { value: 1000 } - } - ] ), - - vertexShader: ShaderChunk.distanceRGBA_vert, - fragmentShader: ShaderChunk.distanceRGBA_frag - - }, - - shadow: { - - uniforms: /*@__PURE__*/ mergeUniforms( [ - UniformsLib.lights, - UniformsLib.fog, - { - color: { value: /*@__PURE__*/ new Color( 0x00000 ) }, - opacity: { value: 1.0 } - }, - ] ), - - vertexShader: ShaderChunk.shadow_vert, - fragmentShader: ShaderChunk.shadow_frag - - } - -}; - -ShaderLib.physical = { - - uniforms: /*@__PURE__*/ mergeUniforms( [ - ShaderLib.standard.uniforms, - { - clearcoat: { value: 0 }, - clearcoatMap: { value: null }, - clearcoatMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - clearcoatNormalMap: { value: null }, - clearcoatNormalMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - clearcoatNormalScale: { value: /*@__PURE__*/ new Vector2( 1, 1 ) }, - clearcoatRoughness: { value: 0 }, - clearcoatRoughnessMap: { value: null }, - clearcoatRoughnessMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - iridescence: { value: 0 }, - iridescenceMap: { value: null }, - iridescenceMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - iridescenceIOR: { value: 1.3 }, - iridescenceThicknessMinimum: { value: 100 }, - iridescenceThicknessMaximum: { value: 400 }, - iridescenceThicknessMap: { value: null }, - iridescenceThicknessMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - sheen: { value: 0 }, - sheenColor: { value: /*@__PURE__*/ new Color( 0x000000 ) }, - sheenColorMap: { value: null }, - sheenColorMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - sheenRoughness: { value: 1 }, - sheenRoughnessMap: { value: null }, - sheenRoughnessMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - transmission: { value: 0 }, - transmissionMap: { value: null }, - transmissionMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - transmissionSamplerSize: { value: /*@__PURE__*/ new Vector2() }, - transmissionSamplerMap: { value: null }, - thickness: { value: 0 }, - thicknessMap: { value: null }, - thicknessMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - attenuationDistance: { value: 0 }, - attenuationColor: { value: /*@__PURE__*/ new Color( 0x000000 ) }, - specularColor: { value: /*@__PURE__*/ new Color( 1, 1, 1 ) }, - specularColorMap: { value: null }, - specularColorMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - specularIntensity: { value: 1 }, - specularIntensityMap: { value: null }, - specularIntensityMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - anisotropyVector: { value: /*@__PURE__*/ new Vector2() }, - anisotropyMap: { value: null }, - } - ] ), - - vertexShader: ShaderChunk.meshphysical_vert, - fragmentShader: ShaderChunk.meshphysical_frag - -}; - -const _rgb = { r: 0, b: 0, g: 0 }; - -function WebGLBackground( renderer, cubemaps, cubeuvmaps, state, objects, alpha, premultipliedAlpha ) { - - const clearColor = new Color( 0x000000 ); - let clearAlpha = alpha === true ? 0 : 1; - - let planeMesh; - let boxMesh; - - let currentBackground = null; - let currentBackgroundVersion = 0; - let currentTonemapping = null; - - function render( renderList, scene ) { - - let forceClear = false; - let background = scene.isScene === true ? scene.background : null; - - if ( background && background.isTexture ) { - - const usePMREM = scene.backgroundBlurriness > 0; // use PMREM if the user wants to blur the background - background = ( usePMREM ? cubeuvmaps : cubemaps ).get( background ); - - } - - if ( background === null ) { - - setClear( clearColor, clearAlpha ); - - } else if ( background && background.isColor ) { - - setClear( background, 1 ); - forceClear = true; - - } - - const xr = renderer.xr; - const environmentBlendMode = xr.getEnvironmentBlendMode(); - - switch ( environmentBlendMode ) { - - case 'opaque': - forceClear = true; - break; - - case 'additive': - state.buffers.color.setClear( 0, 0, 0, 1, premultipliedAlpha ); - forceClear = true; - break; - - case 'alpha-blend': - state.buffers.color.setClear( 0, 0, 0, 0, premultipliedAlpha ); - forceClear = true; - break; - - } - - if ( renderer.autoClear || forceClear ) { - - renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil ); - - } - - if ( background && ( background.isCubeTexture || background.mapping === CubeUVReflectionMapping ) ) { - - if ( boxMesh === undefined ) { - - boxMesh = new Mesh( - new BoxGeometry( 1, 1, 1 ), - new ShaderMaterial( { - name: 'BackgroundCubeMaterial', - uniforms: cloneUniforms( ShaderLib.backgroundCube.uniforms ), - vertexShader: ShaderLib.backgroundCube.vertexShader, - fragmentShader: ShaderLib.backgroundCube.fragmentShader, - side: BackSide, - depthTest: false, - depthWrite: false, - fog: false - } ) - ); - - boxMesh.geometry.deleteAttribute( 'normal' ); - boxMesh.geometry.deleteAttribute( 'uv' ); - - boxMesh.onBeforeRender = function ( renderer, scene, camera ) { - - this.matrixWorld.copyPosition( camera.matrixWorld ); - - }; - - // add "envMap" material property so the renderer can evaluate it like for built-in materials - Object.defineProperty( boxMesh.material, 'envMap', { - - get: function () { - - return this.uniforms.envMap.value; - - } - - } ); - - objects.update( boxMesh ); - - } - - boxMesh.material.uniforms.envMap.value = background; - boxMesh.material.uniforms.flipEnvMap.value = ( background.isCubeTexture && background.isRenderTargetTexture === false ) ? - 1 : 1; - boxMesh.material.uniforms.backgroundBlurriness.value = scene.backgroundBlurriness; - boxMesh.material.uniforms.backgroundIntensity.value = scene.backgroundIntensity; - boxMesh.material.toneMapped = ( background.colorSpace === SRGBColorSpace ) ? false : true; - - if ( currentBackground !== background || - currentBackgroundVersion !== background.version || - currentTonemapping !== renderer.toneMapping ) { - - boxMesh.material.needsUpdate = true; - - currentBackground = background; - currentBackgroundVersion = background.version; - currentTonemapping = renderer.toneMapping; - - } - - boxMesh.layers.enableAll(); - - // push to the pre-sorted opaque render list - renderList.unshift( boxMesh, boxMesh.geometry, boxMesh.material, 0, 0, null ); - - } else if ( background && background.isTexture ) { - - if ( planeMesh === undefined ) { - - planeMesh = new Mesh( - new PlaneGeometry( 2, 2 ), - new ShaderMaterial( { - name: 'BackgroundMaterial', - uniforms: cloneUniforms( ShaderLib.background.uniforms ), - vertexShader: ShaderLib.background.vertexShader, - fragmentShader: ShaderLib.background.fragmentShader, - side: FrontSide, - depthTest: false, - depthWrite: false, - fog: false - } ) - ); - - planeMesh.geometry.deleteAttribute( 'normal' ); - - // add "map" material property so the renderer can evaluate it like for built-in materials - Object.defineProperty( planeMesh.material, 'map', { - - get: function () { - - return this.uniforms.t2D.value; - - } - - } ); - - objects.update( planeMesh ); - - } - - planeMesh.material.uniforms.t2D.value = background; - planeMesh.material.uniforms.backgroundIntensity.value = scene.backgroundIntensity; - planeMesh.material.toneMapped = ( background.colorSpace === SRGBColorSpace ) ? false : true; - - if ( background.matrixAutoUpdate === true ) { - - background.updateMatrix(); - - } - - planeMesh.material.uniforms.uvTransform.value.copy( background.matrix ); - - if ( currentBackground !== background || - currentBackgroundVersion !== background.version || - currentTonemapping !== renderer.toneMapping ) { - - planeMesh.material.needsUpdate = true; - - currentBackground = background; - currentBackgroundVersion = background.version; - currentTonemapping = renderer.toneMapping; - - } - - planeMesh.layers.enableAll(); - - // push to the pre-sorted opaque render list - renderList.unshift( planeMesh, planeMesh.geometry, planeMesh.material, 0, 0, null ); - - } - - } - - function setClear( color, alpha ) { - - color.getRGB( _rgb, getUnlitUniformColorSpace( renderer ) ); - - state.buffers.color.setClear( _rgb.r, _rgb.g, _rgb.b, alpha, premultipliedAlpha ); - - } - - return { - - getClearColor: function () { - - return clearColor; - - }, - setClearColor: function ( color, alpha = 1 ) { - - clearColor.set( color ); - clearAlpha = alpha; - setClear( clearColor, clearAlpha ); - - }, - getClearAlpha: function () { - - return clearAlpha; - - }, - setClearAlpha: function ( alpha ) { - - clearAlpha = alpha; - setClear( clearColor, clearAlpha ); - - }, - render: render - - }; - -} - -function WebGLBindingStates( gl, extensions, attributes, capabilities ) { - - const maxVertexAttributes = gl.getParameter( gl.MAX_VERTEX_ATTRIBS ); - - const extension = capabilities.isWebGL2 ? null : extensions.get( 'OES_vertex_array_object' ); - const vaoAvailable = capabilities.isWebGL2 || extension !== null; - - const bindingStates = {}; - - const defaultState = createBindingState( null ); - let currentState = defaultState; - let forceUpdate = false; - - function setup( object, material, program, geometry, index ) { - - let updateBuffers = false; - - if ( vaoAvailable ) { - - const state = getBindingState( geometry, program, material ); - - if ( currentState !== state ) { - - currentState = state; - bindVertexArrayObject( currentState.object ); - - } - - updateBuffers = needsUpdate( object, geometry, program, index ); - - if ( updateBuffers ) saveCache( object, geometry, program, index ); - - } else { - - const wireframe = ( material.wireframe === true ); - - if ( currentState.geometry !== geometry.id || - currentState.program !== program.id || - currentState.wireframe !== wireframe ) { - - currentState.geometry = geometry.id; - currentState.program = program.id; - currentState.wireframe = wireframe; - - updateBuffers = true; - - } - - } - - if ( index !== null ) { - - attributes.update( index, gl.ELEMENT_ARRAY_BUFFER ); - - } - - if ( updateBuffers || forceUpdate ) { - - forceUpdate = false; - - setupVertexAttributes( object, material, program, geometry ); - - if ( index !== null ) { - - gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, attributes.get( index ).buffer ); - - } - - } - - } - - function createVertexArrayObject() { - - if ( capabilities.isWebGL2 ) return gl.createVertexArray(); - - return extension.createVertexArrayOES(); - - } - - function bindVertexArrayObject( vao ) { - - if ( capabilities.isWebGL2 ) return gl.bindVertexArray( vao ); - - return extension.bindVertexArrayOES( vao ); - - } - - function deleteVertexArrayObject( vao ) { - - if ( capabilities.isWebGL2 ) return gl.deleteVertexArray( vao ); - - return extension.deleteVertexArrayOES( vao ); - - } - - function getBindingState( geometry, program, material ) { - - const wireframe = ( material.wireframe === true ); - - let programMap = bindingStates[ geometry.id ]; - - if ( programMap === undefined ) { - - programMap = {}; - bindingStates[ geometry.id ] = programMap; - - } - - let stateMap = programMap[ program.id ]; - - if ( stateMap === undefined ) { - - stateMap = {}; - programMap[ program.id ] = stateMap; - - } - - let state = stateMap[ wireframe ]; - - if ( state === undefined ) { - - state = createBindingState( createVertexArrayObject() ); - stateMap[ wireframe ] = state; - - } - - return state; - - } - - function createBindingState( vao ) { - - const newAttributes = []; - const enabledAttributes = []; - const attributeDivisors = []; - - for ( let i = 0; i < maxVertexAttributes; i ++ ) { - - newAttributes[ i ] = 0; - enabledAttributes[ i ] = 0; - attributeDivisors[ i ] = 0; - - } - - return { - - // for backward compatibility on non-VAO support browser - geometry: null, - program: null, - wireframe: false, - - newAttributes: newAttributes, - enabledAttributes: enabledAttributes, - attributeDivisors: attributeDivisors, - object: vao, - attributes: {}, - index: null - - }; - - } - - function needsUpdate( object, geometry, program, index ) { - - const cachedAttributes = currentState.attributes; - const geometryAttributes = geometry.attributes; - - let attributesNum = 0; - - const programAttributes = program.getAttributes(); - - for ( const name in programAttributes ) { - - const programAttribute = programAttributes[ name ]; - - if ( programAttribute.location >= 0 ) { - - const cachedAttribute = cachedAttributes[ name ]; - let geometryAttribute = geometryAttributes[ name ]; - - if ( geometryAttribute === undefined ) { - - if ( name === 'instanceMatrix' && object.instanceMatrix ) geometryAttribute = object.instanceMatrix; - if ( name === 'instanceColor' && object.instanceColor ) geometryAttribute = object.instanceColor; - - } - - if ( cachedAttribute === undefined ) return true; - - if ( cachedAttribute.attribute !== geometryAttribute ) return true; - - if ( geometryAttribute && cachedAttribute.data !== geometryAttribute.data ) return true; - - attributesNum ++; - - } - - } - - if ( currentState.attributesNum !== attributesNum ) return true; - - if ( currentState.index !== index ) return true; - - return false; - - } - - function saveCache( object, geometry, program, index ) { - - const cache = {}; - const attributes = geometry.attributes; - let attributesNum = 0; - - const programAttributes = program.getAttributes(); - - for ( const name in programAttributes ) { - - const programAttribute = programAttributes[ name ]; - - if ( programAttribute.location >= 0 ) { - - let attribute = attributes[ name ]; - - if ( attribute === undefined ) { - - if ( name === 'instanceMatrix' && object.instanceMatrix ) attribute = object.instanceMatrix; - if ( name === 'instanceColor' && object.instanceColor ) attribute = object.instanceColor; - - } - - const data = {}; - data.attribute = attribute; - - if ( attribute && attribute.data ) { - - data.data = attribute.data; - - } - - cache[ name ] = data; - - attributesNum ++; - - } - - } - - currentState.attributes = cache; - currentState.attributesNum = attributesNum; - - currentState.index = index; - - } - - function initAttributes() { - - const newAttributes = currentState.newAttributes; - - for ( let i = 0, il = newAttributes.length; i < il; i ++ ) { - - newAttributes[ i ] = 0; - - } - - } - - function enableAttribute( attribute ) { - - enableAttributeAndDivisor( attribute, 0 ); - - } - - function enableAttributeAndDivisor( attribute, meshPerAttribute ) { - - const newAttributes = currentState.newAttributes; - const enabledAttributes = currentState.enabledAttributes; - const attributeDivisors = currentState.attributeDivisors; - - newAttributes[ attribute ] = 1; - - if ( enabledAttributes[ attribute ] === 0 ) { - - gl.enableVertexAttribArray( attribute ); - enabledAttributes[ attribute ] = 1; - - } - - if ( attributeDivisors[ attribute ] !== meshPerAttribute ) { - - const extension = capabilities.isWebGL2 ? gl : extensions.get( 'ANGLE_instanced_arrays' ); - - extension[ capabilities.isWebGL2 ? 'vertexAttribDivisor' : 'vertexAttribDivisorANGLE' ]( attribute, meshPerAttribute ); - attributeDivisors[ attribute ] = meshPerAttribute; - - } - - } - - function disableUnusedAttributes() { - - const newAttributes = currentState.newAttributes; - const enabledAttributes = currentState.enabledAttributes; - - for ( let i = 0, il = enabledAttributes.length; i < il; i ++ ) { - - if ( enabledAttributes[ i ] !== newAttributes[ i ] ) { - - gl.disableVertexAttribArray( i ); - enabledAttributes[ i ] = 0; - - } - - } - - } - - function vertexAttribPointer( index, size, type, normalized, stride, offset, integer ) { - - if ( integer === true ) { - - gl.vertexAttribIPointer( index, size, type, stride, offset ); - - } else { - - gl.vertexAttribPointer( index, size, type, normalized, stride, offset ); - - } - - } - - function setupVertexAttributes( object, material, program, geometry ) { - - if ( capabilities.isWebGL2 === false && ( object.isInstancedMesh || geometry.isInstancedBufferGeometry ) ) { - - if ( extensions.get( 'ANGLE_instanced_arrays' ) === null ) return; - - } - - initAttributes(); - - const geometryAttributes = geometry.attributes; - - const programAttributes = program.getAttributes(); - - const materialDefaultAttributeValues = material.defaultAttributeValues; - - for ( const name in programAttributes ) { - - const programAttribute = programAttributes[ name ]; - - if ( programAttribute.location >= 0 ) { - - let geometryAttribute = geometryAttributes[ name ]; - - if ( geometryAttribute === undefined ) { - - if ( name === 'instanceMatrix' && object.instanceMatrix ) geometryAttribute = object.instanceMatrix; - if ( name === 'instanceColor' && object.instanceColor ) geometryAttribute = object.instanceColor; - - } - - if ( geometryAttribute !== undefined ) { - - const normalized = geometryAttribute.normalized; - const size = geometryAttribute.itemSize; - - const attribute = attributes.get( geometryAttribute ); - - // TODO Attribute may not be available on context restore - - if ( attribute === undefined ) continue; - - const buffer = attribute.buffer; - const type = attribute.type; - const bytesPerElement = attribute.bytesPerElement; - - // check for integer attributes (WebGL 2 only) - - const integer = ( capabilities.isWebGL2 === true && ( type === gl.INT || type === gl.UNSIGNED_INT || geometryAttribute.gpuType === IntType ) ); - - if ( geometryAttribute.isInterleavedBufferAttribute ) { - - const data = geometryAttribute.data; - const stride = data.stride; - const offset = geometryAttribute.offset; - - if ( data.isInstancedInterleavedBuffer ) { - - for ( let i = 0; i < programAttribute.locationSize; i ++ ) { - - enableAttributeAndDivisor( programAttribute.location + i, data.meshPerAttribute ); - - } - - if ( object.isInstancedMesh !== true && geometry._maxInstanceCount === undefined ) { - - geometry._maxInstanceCount = data.meshPerAttribute * data.count; - - } - - } else { - - for ( let i = 0; i < programAttribute.locationSize; i ++ ) { - - enableAttribute( programAttribute.location + i ); - - } - - } - - gl.bindBuffer( gl.ARRAY_BUFFER, buffer ); - - for ( let i = 0; i < programAttribute.locationSize; i ++ ) { - - vertexAttribPointer( - programAttribute.location + i, - size / programAttribute.locationSize, - type, - normalized, - stride * bytesPerElement, - ( offset + ( size / programAttribute.locationSize ) * i ) * bytesPerElement, - integer - ); - - } - - } else { - - if ( geometryAttribute.isInstancedBufferAttribute ) { - - for ( let i = 0; i < programAttribute.locationSize; i ++ ) { - - enableAttributeAndDivisor( programAttribute.location + i, geometryAttribute.meshPerAttribute ); - - } - - if ( object.isInstancedMesh !== true && geometry._maxInstanceCount === undefined ) { - - geometry._maxInstanceCount = geometryAttribute.meshPerAttribute * geometryAttribute.count; - - } - - } else { - - for ( let i = 0; i < programAttribute.locationSize; i ++ ) { - - enableAttribute( programAttribute.location + i ); - - } - - } - - gl.bindBuffer( gl.ARRAY_BUFFER, buffer ); - - for ( let i = 0; i < programAttribute.locationSize; i ++ ) { - - vertexAttribPointer( - programAttribute.location + i, - size / programAttribute.locationSize, - type, - normalized, - size * bytesPerElement, - ( size / programAttribute.locationSize ) * i * bytesPerElement, - integer - ); - - } - - } - - } else if ( materialDefaultAttributeValues !== undefined ) { - - const value = materialDefaultAttributeValues[ name ]; - - if ( value !== undefined ) { - - switch ( value.length ) { - - case 2: - gl.vertexAttrib2fv( programAttribute.location, value ); - break; - - case 3: - gl.vertexAttrib3fv( programAttribute.location, value ); - break; - - case 4: - gl.vertexAttrib4fv( programAttribute.location, value ); - break; - - default: - gl.vertexAttrib1fv( programAttribute.location, value ); - - } - - } - - } - - } - - } - - disableUnusedAttributes(); - - } - - function dispose() { - - reset(); - - for ( const geometryId in bindingStates ) { - - const programMap = bindingStates[ geometryId ]; - - for ( const programId in programMap ) { - - const stateMap = programMap[ programId ]; - - for ( const wireframe in stateMap ) { - - deleteVertexArrayObject( stateMap[ wireframe ].object ); - - delete stateMap[ wireframe ]; - - } - - delete programMap[ programId ]; - - } - - delete bindingStates[ geometryId ]; - - } - - } - - function releaseStatesOfGeometry( geometry ) { - - if ( bindingStates[ geometry.id ] === undefined ) return; - - const programMap = bindingStates[ geometry.id ]; - - for ( const programId in programMap ) { - - const stateMap = programMap[ programId ]; - - for ( const wireframe in stateMap ) { - - deleteVertexArrayObject( stateMap[ wireframe ].object ); - - delete stateMap[ wireframe ]; - - } - - delete programMap[ programId ]; - - } - - delete bindingStates[ geometry.id ]; - - } - - function releaseStatesOfProgram( program ) { - - for ( const geometryId in bindingStates ) { - - const programMap = bindingStates[ geometryId ]; - - if ( programMap[ program.id ] === undefined ) continue; - - const stateMap = programMap[ program.id ]; - - for ( const wireframe in stateMap ) { - - deleteVertexArrayObject( stateMap[ wireframe ].object ); - - delete stateMap[ wireframe ]; - - } - - delete programMap[ program.id ]; - - } - - } - - function reset() { - - resetDefaultState(); - forceUpdate = true; - - if ( currentState === defaultState ) return; - - currentState = defaultState; - bindVertexArrayObject( currentState.object ); - - } - - // for backward-compatibility - - function resetDefaultState() { - - defaultState.geometry = null; - defaultState.program = null; - defaultState.wireframe = false; - - } - - return { - - setup: setup, - reset: reset, - resetDefaultState: resetDefaultState, - dispose: dispose, - releaseStatesOfGeometry: releaseStatesOfGeometry, - releaseStatesOfProgram: releaseStatesOfProgram, - - initAttributes: initAttributes, - enableAttribute: enableAttribute, - disableUnusedAttributes: disableUnusedAttributes - - }; - -} - -function WebGLBufferRenderer( gl, extensions, info, capabilities ) { - - const isWebGL2 = capabilities.isWebGL2; - - let mode; - - function setMode( value ) { - - mode = value; - - } - - function render( start, count ) { - - gl.drawArrays( mode, start, count ); - - info.update( count, mode, 1 ); - - } - - function renderInstances( start, count, primcount ) { - - if ( primcount === 0 ) return; - - let extension, methodName; - - if ( isWebGL2 ) { - - extension = gl; - methodName = 'drawArraysInstanced'; - - } else { - - extension = extensions.get( 'ANGLE_instanced_arrays' ); - methodName = 'drawArraysInstancedANGLE'; - - if ( extension === null ) { - - console.error( 'THREE.WebGLBufferRenderer: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.' ); - return; - - } - - } - - extension[ methodName ]( mode, start, count, primcount ); - - info.update( count, mode, primcount ); - - } - - // - - this.setMode = setMode; - this.render = render; - this.renderInstances = renderInstances; - -} - -function WebGLCapabilities( gl, extensions, parameters ) { - - let maxAnisotropy; - - function getMaxAnisotropy() { - - if ( maxAnisotropy !== undefined ) return maxAnisotropy; - - if ( extensions.has( 'EXT_texture_filter_anisotropic' ) === true ) { - - const extension = extensions.get( 'EXT_texture_filter_anisotropic' ); - - maxAnisotropy = gl.getParameter( extension.MAX_TEXTURE_MAX_ANISOTROPY_EXT ); - - } else { - - maxAnisotropy = 0; - - } - - return maxAnisotropy; - - } - - function getMaxPrecision( precision ) { - - if ( precision === 'highp' ) { - - if ( gl.getShaderPrecisionFormat( gl.VERTEX_SHADER, gl.HIGH_FLOAT ).precision > 0 && - gl.getShaderPrecisionFormat( gl.FRAGMENT_SHADER, gl.HIGH_FLOAT ).precision > 0 ) { - - return 'highp'; - - } - - precision = 'mediump'; - - } - - if ( precision === 'mediump' ) { - - if ( gl.getShaderPrecisionFormat( gl.VERTEX_SHADER, gl.MEDIUM_FLOAT ).precision > 0 && - gl.getShaderPrecisionFormat( gl.FRAGMENT_SHADER, gl.MEDIUM_FLOAT ).precision > 0 ) { - - return 'mediump'; - - } - - } - - return 'lowp'; - - } - - const isWebGL2 = typeof WebGL2RenderingContext !== 'undefined' && gl.constructor.name === 'WebGL2RenderingContext'; - - let precision = parameters.precision !== undefined ? parameters.precision : 'highp'; - const maxPrecision = getMaxPrecision( precision ); - - if ( maxPrecision !== precision ) { - - console.warn( 'THREE.WebGLRenderer:', precision, 'not supported, using', maxPrecision, 'instead.' ); - precision = maxPrecision; - - } - - const drawBuffers = isWebGL2 || extensions.has( 'WEBGL_draw_buffers' ); - - const logarithmicDepthBuffer = parameters.logarithmicDepthBuffer === true; - - const maxTextures = gl.getParameter( gl.MAX_TEXTURE_IMAGE_UNITS ); - const maxVertexTextures = gl.getParameter( gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS ); - const maxTextureSize = gl.getParameter( gl.MAX_TEXTURE_SIZE ); - const maxCubemapSize = gl.getParameter( gl.MAX_CUBE_MAP_TEXTURE_SIZE ); - - const maxAttributes = gl.getParameter( gl.MAX_VERTEX_ATTRIBS ); - const maxVertexUniforms = gl.getParameter( gl.MAX_VERTEX_UNIFORM_VECTORS ); - const maxVaryings = gl.getParameter( gl.MAX_VARYING_VECTORS ); - const maxFragmentUniforms = gl.getParameter( gl.MAX_FRAGMENT_UNIFORM_VECTORS ); - - const vertexTextures = maxVertexTextures > 0; - const floatFragmentTextures = isWebGL2 || extensions.has( 'OES_texture_float' ); - const floatVertexTextures = vertexTextures && floatFragmentTextures; - - const maxSamples = isWebGL2 ? gl.getParameter( gl.MAX_SAMPLES ) : 0; - - return { - - isWebGL2: isWebGL2, - - drawBuffers: drawBuffers, - - getMaxAnisotropy: getMaxAnisotropy, - getMaxPrecision: getMaxPrecision, - - precision: precision, - logarithmicDepthBuffer: logarithmicDepthBuffer, - - maxTextures: maxTextures, - maxVertexTextures: maxVertexTextures, - maxTextureSize: maxTextureSize, - maxCubemapSize: maxCubemapSize, - - maxAttributes: maxAttributes, - maxVertexUniforms: maxVertexUniforms, - maxVaryings: maxVaryings, - maxFragmentUniforms: maxFragmentUniforms, - - vertexTextures: vertexTextures, - floatFragmentTextures: floatFragmentTextures, - floatVertexTextures: floatVertexTextures, - - maxSamples: maxSamples - - }; - -} - -function WebGLClipping( properties ) { - - const scope = this; - - let globalState = null, - numGlobalPlanes = 0, - localClippingEnabled = false, - renderingShadows = false; - - const plane = new Plane(), - viewNormalMatrix = new Matrix3(), - - uniform = { value: null, needsUpdate: false }; - - this.uniform = uniform; - this.numPlanes = 0; - this.numIntersection = 0; - - this.init = function ( planes, enableLocalClipping ) { - - const enabled = - planes.length !== 0 || - enableLocalClipping || - // enable state of previous frame - the clipping code has to - // run another frame in order to reset the state: - numGlobalPlanes !== 0 || - localClippingEnabled; - - localClippingEnabled = enableLocalClipping; - - numGlobalPlanes = planes.length; - - return enabled; - - }; - - this.beginShadows = function () { - - renderingShadows = true; - projectPlanes( null ); - - }; - - this.endShadows = function () { - - renderingShadows = false; - - }; - - this.setGlobalState = function ( planes, camera ) { - - globalState = projectPlanes( planes, camera, 0 ); - - }; - - this.setState = function ( material, camera, useCache ) { - - const planes = material.clippingPlanes, - clipIntersection = material.clipIntersection, - clipShadows = material.clipShadows; - - const materialProperties = properties.get( material ); - - if ( ! localClippingEnabled || planes === null || planes.length === 0 || renderingShadows && ! clipShadows ) { - - // there's no local clipping - - if ( renderingShadows ) { - - // there's no global clipping - - projectPlanes( null ); - - } else { - - resetGlobalState(); - - } - - } else { - - const nGlobal = renderingShadows ? 0 : numGlobalPlanes, - lGlobal = nGlobal * 4; - - let dstArray = materialProperties.clippingState || null; - - uniform.value = dstArray; // ensure unique state - - dstArray = projectPlanes( planes, camera, lGlobal, useCache ); - - for ( let i = 0; i !== lGlobal; ++ i ) { - - dstArray[ i ] = globalState[ i ]; - - } - - materialProperties.clippingState = dstArray; - this.numIntersection = clipIntersection ? this.numPlanes : 0; - this.numPlanes += nGlobal; - - } - - - }; - - function resetGlobalState() { - - if ( uniform.value !== globalState ) { - - uniform.value = globalState; - uniform.needsUpdate = numGlobalPlanes > 0; - - } - - scope.numPlanes = numGlobalPlanes; - scope.numIntersection = 0; - - } - - function projectPlanes( planes, camera, dstOffset, skipTransform ) { - - const nPlanes = planes !== null ? planes.length : 0; - let dstArray = null; - - if ( nPlanes !== 0 ) { - - dstArray = uniform.value; - - if ( skipTransform !== true || dstArray === null ) { - - const flatSize = dstOffset + nPlanes * 4, - viewMatrix = camera.matrixWorldInverse; - - viewNormalMatrix.getNormalMatrix( viewMatrix ); - - if ( dstArray === null || dstArray.length < flatSize ) { - - dstArray = new Float32Array( flatSize ); - - } - - for ( let i = 0, i4 = dstOffset; i !== nPlanes; ++ i, i4 += 4 ) { - - plane.copy( planes[ i ] ).applyMatrix4( viewMatrix, viewNormalMatrix ); - - plane.normal.toArray( dstArray, i4 ); - dstArray[ i4 + 3 ] = plane.constant; - - } - - } - - uniform.value = dstArray; - uniform.needsUpdate = true; - - } - - scope.numPlanes = nPlanes; - scope.numIntersection = 0; - - return dstArray; - - } - -} - -function WebGLCubeMaps( renderer ) { - - let cubemaps = new WeakMap(); - - function mapTextureMapping( texture, mapping ) { - - if ( mapping === EquirectangularReflectionMapping ) { - - texture.mapping = CubeReflectionMapping; - - } else if ( mapping === EquirectangularRefractionMapping ) { - - texture.mapping = CubeRefractionMapping; - - } - - return texture; - - } - - function get( texture ) { - - if ( texture && texture.isTexture && texture.isRenderTargetTexture === false ) { - - const mapping = texture.mapping; - - if ( mapping === EquirectangularReflectionMapping || mapping === EquirectangularRefractionMapping ) { - - if ( cubemaps.has( texture ) ) { - - const cubemap = cubemaps.get( texture ).texture; - return mapTextureMapping( cubemap, texture.mapping ); - - } else { - - const image = texture.image; - - if ( image && image.height > 0 ) { - - const renderTarget = new WebGLCubeRenderTarget( image.height / 2 ); - renderTarget.fromEquirectangularTexture( renderer, texture ); - cubemaps.set( texture, renderTarget ); - - texture.addEventListener( 'dispose', onTextureDispose ); - - return mapTextureMapping( renderTarget.texture, texture.mapping ); - - } else { - - // image not yet ready. try the conversion next frame - - return null; - - } - - } - - } - - } - - return texture; - - } - - function onTextureDispose( event ) { - - const texture = event.target; - - texture.removeEventListener( 'dispose', onTextureDispose ); - - const cubemap = cubemaps.get( texture ); - - if ( cubemap !== undefined ) { - - cubemaps.delete( texture ); - cubemap.dispose(); - - } - - } - - function dispose() { - - cubemaps = new WeakMap(); - - } - - return { - get: get, - dispose: dispose - }; - -} - -class OrthographicCamera extends Camera { - - constructor( left = - 1, right = 1, top = 1, bottom = - 1, near = 0.1, far = 2000 ) { - - super(); - - this.isOrthographicCamera = true; - - this.type = 'OrthographicCamera'; - - this.zoom = 1; - this.view = null; - - this.left = left; - this.right = right; - this.top = top; - this.bottom = bottom; - - this.near = near; - this.far = far; - - this.updateProjectionMatrix(); - - } - - copy( source, recursive ) { - - super.copy( source, recursive ); - - this.left = source.left; - this.right = source.right; - this.top = source.top; - this.bottom = source.bottom; - this.near = source.near; - this.far = source.far; - - this.zoom = source.zoom; - this.view = source.view === null ? null : Object.assign( {}, source.view ); - - return this; - - } - - setViewOffset( fullWidth, fullHeight, x, y, width, height ) { - - if ( this.view === null ) { - - this.view = { - enabled: true, - fullWidth: 1, - fullHeight: 1, - offsetX: 0, - offsetY: 0, - width: 1, - height: 1 - }; - - } - - this.view.enabled = true; - this.view.fullWidth = fullWidth; - this.view.fullHeight = fullHeight; - this.view.offsetX = x; - this.view.offsetY = y; - this.view.width = width; - this.view.height = height; - - this.updateProjectionMatrix(); - - } - - clearViewOffset() { - - if ( this.view !== null ) { - - this.view.enabled = false; - - } - - this.updateProjectionMatrix(); - - } - - updateProjectionMatrix() { - - const dx = ( this.right - this.left ) / ( 2 * this.zoom ); - const dy = ( this.top - this.bottom ) / ( 2 * this.zoom ); - const cx = ( this.right + this.left ) / 2; - const cy = ( this.top + this.bottom ) / 2; - - let left = cx - dx; - let right = cx + dx; - let top = cy + dy; - let bottom = cy - dy; - - if ( this.view !== null && this.view.enabled ) { - - const scaleW = ( this.right - this.left ) / this.view.fullWidth / this.zoom; - const scaleH = ( this.top - this.bottom ) / this.view.fullHeight / this.zoom; - - left += scaleW * this.view.offsetX; - right = left + scaleW * this.view.width; - top -= scaleH * this.view.offsetY; - bottom = top - scaleH * this.view.height; - - } - - this.projectionMatrix.makeOrthographic( left, right, top, bottom, this.near, this.far ); - - this.projectionMatrixInverse.copy( this.projectionMatrix ).invert(); - - } - - toJSON( meta ) { - - const data = super.toJSON( meta ); - - data.object.zoom = this.zoom; - data.object.left = this.left; - data.object.right = this.right; - data.object.top = this.top; - data.object.bottom = this.bottom; - data.object.near = this.near; - data.object.far = this.far; - - if ( this.view !== null ) data.object.view = Object.assign( {}, this.view ); - - return data; - - } - -} - -const LOD_MIN = 4; - -// The standard deviations (radians) associated with the extra mips. These are -// chosen to approximate a Trowbridge-Reitz distribution function times the -// geometric shadowing function. These sigma values squared must match the -// variance #defines in cube_uv_reflection_fragment.glsl.js. -const EXTRA_LOD_SIGMA = [ 0.125, 0.215, 0.35, 0.446, 0.526, 0.582 ]; - -// The maximum length of the blur for loop. Smaller sigmas will use fewer -// samples and exit early, but not recompile the shader. -const MAX_SAMPLES = 20; - -const _flatCamera = /*@__PURE__*/ new OrthographicCamera(); -const _clearColor = /*@__PURE__*/ new Color(); -let _oldTarget = null; - -// Golden Ratio -const PHI = ( 1 + Math.sqrt( 5 ) ) / 2; -const INV_PHI = 1 / PHI; - -// Vertices of a dodecahedron (except the opposites, which represent the -// same axis), used as axis directions evenly spread on a sphere. -const _axisDirections = [ - /*@__PURE__*/ new Vector3( 1, 1, 1 ), - /*@__PURE__*/ new Vector3( - 1, 1, 1 ), - /*@__PURE__*/ new Vector3( 1, 1, - 1 ), - /*@__PURE__*/ new Vector3( - 1, 1, - 1 ), - /*@__PURE__*/ new Vector3( 0, PHI, INV_PHI ), - /*@__PURE__*/ new Vector3( 0, PHI, - INV_PHI ), - /*@__PURE__*/ new Vector3( INV_PHI, 0, PHI ), - /*@__PURE__*/ new Vector3( - INV_PHI, 0, PHI ), - /*@__PURE__*/ new Vector3( PHI, INV_PHI, 0 ), - /*@__PURE__*/ new Vector3( - PHI, INV_PHI, 0 ) ]; - -/** - * This class generates a Prefiltered, Mipmapped Radiance Environment Map - * (PMREM) from a cubeMap environment texture. This allows different levels of - * blur to be quickly accessed based on material roughness. It is packed into a - * special CubeUV format that allows us to perform custom interpolation so that - * we can support nonlinear formats such as RGBE. Unlike a traditional mipmap - * chain, it only goes down to the LOD_MIN level (above), and then creates extra - * even more filtered 'mips' at the same LOD_MIN resolution, associated with - * higher roughness levels. In this way we maintain resolution to smoothly - * interpolate diffuse lighting while limiting sampling computation. - * - * Paper: Fast, Accurate Image-Based Lighting - * https://drive.google.com/file/d/15y8r_UpKlU9SvV4ILb0C3qCPecS8pvLz/view -*/ - -class PMREMGenerator { - - constructor( renderer ) { - - this._renderer = renderer; - this._pingPongRenderTarget = null; - - this._lodMax = 0; - this._cubeSize = 0; - this._lodPlanes = []; - this._sizeLods = []; - this._sigmas = []; - - this._blurMaterial = null; - this._cubemapMaterial = null; - this._equirectMaterial = null; - - this._compileMaterial( this._blurMaterial ); - - } - - /** - * Generates a PMREM from a supplied Scene, which can be faster than using an - * image if networking bandwidth is low. Optional sigma specifies a blur radius - * in radians to be applied to the scene before PMREM generation. Optional near - * and far planes ensure the scene is rendered in its entirety (the cubeCamera - * is placed at the origin). - */ - fromScene( scene, sigma = 0, near = 0.1, far = 100 ) { - - _oldTarget = this._renderer.getRenderTarget(); - - this._setSize( 256 ); - - const cubeUVRenderTarget = this._allocateTargets(); - cubeUVRenderTarget.depthBuffer = true; - - this._sceneToCubeUV( scene, near, far, cubeUVRenderTarget ); - - if ( sigma > 0 ) { - - this._blur( cubeUVRenderTarget, 0, 0, sigma ); - - } - - this._applyPMREM( cubeUVRenderTarget ); - this._cleanup( cubeUVRenderTarget ); - - return cubeUVRenderTarget; - - } - - /** - * Generates a PMREM from an equirectangular texture, which can be either LDR - * or HDR. The ideal input image size is 1k (1024 x 512), - * as this matches best with the 256 x 256 cubemap output. - */ - fromEquirectangular( equirectangular, renderTarget = null ) { - - return this._fromTexture( equirectangular, renderTarget ); - - } - - /** - * Generates a PMREM from an cubemap texture, which can be either LDR - * or HDR. The ideal input cube size is 256 x 256, - * as this matches best with the 256 x 256 cubemap output. - */ - fromCubemap( cubemap, renderTarget = null ) { - - return this._fromTexture( cubemap, renderTarget ); - - } - - /** - * Pre-compiles the cubemap shader. You can get faster start-up by invoking this method during - * your texture's network fetch for increased concurrency. - */ - compileCubemapShader() { - - if ( this._cubemapMaterial === null ) { - - this._cubemapMaterial = _getCubemapMaterial(); - this._compileMaterial( this._cubemapMaterial ); - - } - - } - - /** - * Pre-compiles the equirectangular shader. You can get faster start-up by invoking this method during - * your texture's network fetch for increased concurrency. - */ - compileEquirectangularShader() { - - if ( this._equirectMaterial === null ) { - - this._equirectMaterial = _getEquirectMaterial(); - this._compileMaterial( this._equirectMaterial ); - - } - - } - - /** - * Disposes of the PMREMGenerator's internal memory. Note that PMREMGenerator is a static class, - * so you should not need more than one PMREMGenerator object. If you do, calling dispose() on - * one of them will cause any others to also become unusable. - */ - dispose() { - - this._dispose(); - - if ( this._cubemapMaterial !== null ) this._cubemapMaterial.dispose(); - if ( this._equirectMaterial !== null ) this._equirectMaterial.dispose(); - - } - - // private interface - - _setSize( cubeSize ) { - - this._lodMax = Math.floor( Math.log2( cubeSize ) ); - this._cubeSize = Math.pow( 2, this._lodMax ); - - } - - _dispose() { - - if ( this._blurMaterial !== null ) this._blurMaterial.dispose(); - - if ( this._pingPongRenderTarget !== null ) this._pingPongRenderTarget.dispose(); - - for ( let i = 0; i < this._lodPlanes.length; i ++ ) { - - this._lodPlanes[ i ].dispose(); - - } - - } - - _cleanup( outputTarget ) { - - this._renderer.setRenderTarget( _oldTarget ); - outputTarget.scissorTest = false; - _setViewport( outputTarget, 0, 0, outputTarget.width, outputTarget.height ); - - } - - _fromTexture( texture, renderTarget ) { - - if ( texture.mapping === CubeReflectionMapping || texture.mapping === CubeRefractionMapping ) { - - this._setSize( texture.image.length === 0 ? 16 : ( texture.image[ 0 ].width || texture.image[ 0 ].image.width ) ); - - } else { // Equirectangular - - this._setSize( texture.image.width / 4 ); - - } - - _oldTarget = this._renderer.getRenderTarget(); - - const cubeUVRenderTarget = renderTarget || this._allocateTargets(); - this._textureToCubeUV( texture, cubeUVRenderTarget ); - this._applyPMREM( cubeUVRenderTarget ); - this._cleanup( cubeUVRenderTarget ); - - return cubeUVRenderTarget; - - } - - _allocateTargets() { - - const width = 3 * Math.max( this._cubeSize, 16 * 7 ); - const height = 4 * this._cubeSize; - - const params = { - magFilter: LinearFilter, - minFilter: LinearFilter, - generateMipmaps: false, - type: HalfFloatType, - format: RGBAFormat, - colorSpace: LinearSRGBColorSpace, - depthBuffer: false - }; - - const cubeUVRenderTarget = _createRenderTarget( width, height, params ); - - if ( this._pingPongRenderTarget === null || this._pingPongRenderTarget.width !== width || this._pingPongRenderTarget.height !== height ) { - - if ( this._pingPongRenderTarget !== null ) { - - this._dispose(); - - } - - this._pingPongRenderTarget = _createRenderTarget( width, height, params ); - - const { _lodMax } = this; - ( { sizeLods: this._sizeLods, lodPlanes: this._lodPlanes, sigmas: this._sigmas } = _createPlanes( _lodMax ) ); - - this._blurMaterial = _getBlurShader( _lodMax, width, height ); - - } - - return cubeUVRenderTarget; - - } - - _compileMaterial( material ) { - - const tmpMesh = new Mesh( this._lodPlanes[ 0 ], material ); - this._renderer.compile( tmpMesh, _flatCamera ); - - } - - _sceneToCubeUV( scene, near, far, cubeUVRenderTarget ) { - - const fov = 90; - const aspect = 1; - const cubeCamera = new PerspectiveCamera( fov, aspect, near, far ); - const upSign = [ 1, - 1, 1, 1, 1, 1 ]; - const forwardSign = [ 1, 1, 1, - 1, - 1, - 1 ]; - const renderer = this._renderer; - - const originalAutoClear = renderer.autoClear; - const toneMapping = renderer.toneMapping; - renderer.getClearColor( _clearColor ); - - renderer.toneMapping = NoToneMapping; - renderer.autoClear = false; - - const backgroundMaterial = new MeshBasicMaterial( { - name: 'PMREM.Background', - side: BackSide, - depthWrite: false, - depthTest: false, - } ); - - const backgroundBox = new Mesh( new BoxGeometry(), backgroundMaterial ); - - let useSolidColor = false; - const background = scene.background; - - if ( background ) { - - if ( background.isColor ) { - - backgroundMaterial.color.copy( background ); - scene.background = null; - useSolidColor = true; - - } - - } else { - - backgroundMaterial.color.copy( _clearColor ); - useSolidColor = true; - - } - - for ( let i = 0; i < 6; i ++ ) { - - const col = i % 3; - - if ( col === 0 ) { - - cubeCamera.up.set( 0, upSign[ i ], 0 ); - cubeCamera.lookAt( forwardSign[ i ], 0, 0 ); - - } else if ( col === 1 ) { - - cubeCamera.up.set( 0, 0, upSign[ i ] ); - cubeCamera.lookAt( 0, forwardSign[ i ], 0 ); - - } else { - - cubeCamera.up.set( 0, upSign[ i ], 0 ); - cubeCamera.lookAt( 0, 0, forwardSign[ i ] ); - - } - - const size = this._cubeSize; - - _setViewport( cubeUVRenderTarget, col * size, i > 2 ? size : 0, size, size ); - - renderer.setRenderTarget( cubeUVRenderTarget ); - - if ( useSolidColor ) { - - renderer.render( backgroundBox, cubeCamera ); - - } - - renderer.render( scene, cubeCamera ); - - } - - backgroundBox.geometry.dispose(); - backgroundBox.material.dispose(); - - renderer.toneMapping = toneMapping; - renderer.autoClear = originalAutoClear; - scene.background = background; - - } - - _textureToCubeUV( texture, cubeUVRenderTarget ) { - - const renderer = this._renderer; - - const isCubeTexture = ( texture.mapping === CubeReflectionMapping || texture.mapping === CubeRefractionMapping ); - - if ( isCubeTexture ) { - - if ( this._cubemapMaterial === null ) { - - this._cubemapMaterial = _getCubemapMaterial(); - - } - - this._cubemapMaterial.uniforms.flipEnvMap.value = ( texture.isRenderTargetTexture === false ) ? - 1 : 1; - - } else { - - if ( this._equirectMaterial === null ) { - - this._equirectMaterial = _getEquirectMaterial(); - - } - - } - - const material = isCubeTexture ? this._cubemapMaterial : this._equirectMaterial; - const mesh = new Mesh( this._lodPlanes[ 0 ], material ); - - const uniforms = material.uniforms; - - uniforms[ 'envMap' ].value = texture; - - const size = this._cubeSize; - - _setViewport( cubeUVRenderTarget, 0, 0, 3 * size, 2 * size ); - - renderer.setRenderTarget( cubeUVRenderTarget ); - renderer.render( mesh, _flatCamera ); - - } - - _applyPMREM( cubeUVRenderTarget ) { - - const renderer = this._renderer; - const autoClear = renderer.autoClear; - renderer.autoClear = false; - - for ( let i = 1; i < this._lodPlanes.length; i ++ ) { - - const sigma = Math.sqrt( this._sigmas[ i ] * this._sigmas[ i ] - this._sigmas[ i - 1 ] * this._sigmas[ i - 1 ] ); - - const poleAxis = _axisDirections[ ( i - 1 ) % _axisDirections.length ]; - - this._blur( cubeUVRenderTarget, i - 1, i, sigma, poleAxis ); - - } - - renderer.autoClear = autoClear; - - } - - /** - * This is a two-pass Gaussian blur for a cubemap. Normally this is done - * vertically and horizontally, but this breaks down on a cube. Here we apply - * the blur latitudinally (around the poles), and then longitudinally (towards - * the poles) to approximate the orthogonally-separable blur. It is least - * accurate at the poles, but still does a decent job. - */ - _blur( cubeUVRenderTarget, lodIn, lodOut, sigma, poleAxis ) { - - const pingPongRenderTarget = this._pingPongRenderTarget; - - this._halfBlur( - cubeUVRenderTarget, - pingPongRenderTarget, - lodIn, - lodOut, - sigma, - 'latitudinal', - poleAxis ); - - this._halfBlur( - pingPongRenderTarget, - cubeUVRenderTarget, - lodOut, - lodOut, - sigma, - 'longitudinal', - poleAxis ); - - } - - _halfBlur( targetIn, targetOut, lodIn, lodOut, sigmaRadians, direction, poleAxis ) { - - const renderer = this._renderer; - const blurMaterial = this._blurMaterial; - - if ( direction !== 'latitudinal' && direction !== 'longitudinal' ) { - - console.error( - 'blur direction must be either latitudinal or longitudinal!' ); - - } - - // Number of standard deviations at which to cut off the discrete approximation. - const STANDARD_DEVIATIONS = 3; - - const blurMesh = new Mesh( this._lodPlanes[ lodOut ], blurMaterial ); - const blurUniforms = blurMaterial.uniforms; - - const pixels = this._sizeLods[ lodIn ] - 1; - const radiansPerPixel = isFinite( sigmaRadians ) ? Math.PI / ( 2 * pixels ) : 2 * Math.PI / ( 2 * MAX_SAMPLES - 1 ); - const sigmaPixels = sigmaRadians / radiansPerPixel; - const samples = isFinite( sigmaRadians ) ? 1 + Math.floor( STANDARD_DEVIATIONS * sigmaPixels ) : MAX_SAMPLES; - - if ( samples > MAX_SAMPLES ) { - - console.warn( `sigmaRadians, ${ - sigmaRadians}, is too large and will clip, as it requested ${ - samples} samples when the maximum is set to ${MAX_SAMPLES}` ); - - } - - const weights = []; - let sum = 0; - - for ( let i = 0; i < MAX_SAMPLES; ++ i ) { - - const x = i / sigmaPixels; - const weight = Math.exp( - x * x / 2 ); - weights.push( weight ); - - if ( i === 0 ) { - - sum += weight; - - } else if ( i < samples ) { - - sum += 2 * weight; - - } - - } - - for ( let i = 0; i < weights.length; i ++ ) { - - weights[ i ] = weights[ i ] / sum; - - } - - blurUniforms[ 'envMap' ].value = targetIn.texture; - blurUniforms[ 'samples' ].value = samples; - blurUniforms[ 'weights' ].value = weights; - blurUniforms[ 'latitudinal' ].value = direction === 'latitudinal'; - - if ( poleAxis ) { - - blurUniforms[ 'poleAxis' ].value = poleAxis; - - } - - const { _lodMax } = this; - blurUniforms[ 'dTheta' ].value = radiansPerPixel; - blurUniforms[ 'mipInt' ].value = _lodMax - lodIn; - - const outputSize = this._sizeLods[ lodOut ]; - const x = 3 * outputSize * ( lodOut > _lodMax - LOD_MIN ? lodOut - _lodMax + LOD_MIN : 0 ); - const y = 4 * ( this._cubeSize - outputSize ); - - _setViewport( targetOut, x, y, 3 * outputSize, 2 * outputSize ); - renderer.setRenderTarget( targetOut ); - renderer.render( blurMesh, _flatCamera ); - - } - -} - - - -function _createPlanes( lodMax ) { - - const lodPlanes = []; - const sizeLods = []; - const sigmas = []; - - let lod = lodMax; - - const totalLods = lodMax - LOD_MIN + 1 + EXTRA_LOD_SIGMA.length; - - for ( let i = 0; i < totalLods; i ++ ) { - - const sizeLod = Math.pow( 2, lod ); - sizeLods.push( sizeLod ); - let sigma = 1.0 / sizeLod; - - if ( i > lodMax - LOD_MIN ) { - - sigma = EXTRA_LOD_SIGMA[ i - lodMax + LOD_MIN - 1 ]; - - } else if ( i === 0 ) { - - sigma = 0; - - } - - sigmas.push( sigma ); - - const texelSize = 1.0 / ( sizeLod - 2 ); - const min = - texelSize; - const max = 1 + texelSize; - const uv1 = [ min, min, max, min, max, max, min, min, max, max, min, max ]; - - const cubeFaces = 6; - const vertices = 6; - const positionSize = 3; - const uvSize = 2; - const faceIndexSize = 1; - - const position = new Float32Array( positionSize * vertices * cubeFaces ); - const uv = new Float32Array( uvSize * vertices * cubeFaces ); - const faceIndex = new Float32Array( faceIndexSize * vertices * cubeFaces ); - - for ( let face = 0; face < cubeFaces; face ++ ) { - - const x = ( face % 3 ) * 2 / 3 - 1; - const y = face > 2 ? 0 : - 1; - const coordinates = [ - x, y, 0, - x + 2 / 3, y, 0, - x + 2 / 3, y + 1, 0, - x, y, 0, - x + 2 / 3, y + 1, 0, - x, y + 1, 0 - ]; - position.set( coordinates, positionSize * vertices * face ); - uv.set( uv1, uvSize * vertices * face ); - const fill = [ face, face, face, face, face, face ]; - faceIndex.set( fill, faceIndexSize * vertices * face ); - - } - - const planes = new BufferGeometry(); - planes.setAttribute( 'position', new BufferAttribute( position, positionSize ) ); - planes.setAttribute( 'uv', new BufferAttribute( uv, uvSize ) ); - planes.setAttribute( 'faceIndex', new BufferAttribute( faceIndex, faceIndexSize ) ); - lodPlanes.push( planes ); - - if ( lod > LOD_MIN ) { - - lod --; - - } - - } - - return { lodPlanes, sizeLods, sigmas }; - -} - -function _createRenderTarget( width, height, params ) { - - const cubeUVRenderTarget = new WebGLRenderTarget( width, height, params ); - cubeUVRenderTarget.texture.mapping = CubeUVReflectionMapping; - cubeUVRenderTarget.texture.name = 'PMREM.cubeUv'; - cubeUVRenderTarget.scissorTest = true; - return cubeUVRenderTarget; - -} - -function _setViewport( target, x, y, width, height ) { - - target.viewport.set( x, y, width, height ); - target.scissor.set( x, y, width, height ); - -} - -function _getBlurShader( lodMax, width, height ) { - - const weights = new Float32Array( MAX_SAMPLES ); - const poleAxis = new Vector3( 0, 1, 0 ); - const shaderMaterial = new ShaderMaterial( { - - name: 'SphericalGaussianBlur', - - defines: { - 'n': MAX_SAMPLES, - 'CUBEUV_TEXEL_WIDTH': 1.0 / width, - 'CUBEUV_TEXEL_HEIGHT': 1.0 / height, - 'CUBEUV_MAX_MIP': `${lodMax}.0`, - }, - - uniforms: { - 'envMap': { value: null }, - 'samples': { value: 1 }, - 'weights': { value: weights }, - 'latitudinal': { value: false }, - 'dTheta': { value: 0 }, - 'mipInt': { value: 0 }, - 'poleAxis': { value: poleAxis } - }, - - vertexShader: _getCommonVertexShader(), - - fragmentShader: /* glsl */` - - precision mediump float; - precision mediump int; - - varying vec3 vOutputDirection; - - uniform sampler2D envMap; - uniform int samples; - uniform float weights[ n ]; - uniform bool latitudinal; - uniform float dTheta; - uniform float mipInt; - uniform vec3 poleAxis; - - #define ENVMAP_TYPE_CUBE_UV - #include - - vec3 getSample( float theta, vec3 axis ) { - - float cosTheta = cos( theta ); - // Rodrigues' axis-angle rotation - vec3 sampleDirection = vOutputDirection * cosTheta - + cross( axis, vOutputDirection ) * sin( theta ) - + axis * dot( axis, vOutputDirection ) * ( 1.0 - cosTheta ); - - return bilinearCubeUV( envMap, sampleDirection, mipInt ); - - } - - void main() { - - vec3 axis = latitudinal ? poleAxis : cross( poleAxis, vOutputDirection ); - - if ( all( equal( axis, vec3( 0.0 ) ) ) ) { - - axis = vec3( vOutputDirection.z, 0.0, - vOutputDirection.x ); - - } - - axis = normalize( axis ); - - gl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 ); - gl_FragColor.rgb += weights[ 0 ] * getSample( 0.0, axis ); - - for ( int i = 1; i < n; i++ ) { - - if ( i >= samples ) { - - break; - - } - - float theta = dTheta * float( i ); - gl_FragColor.rgb += weights[ i ] * getSample( -1.0 * theta, axis ); - gl_FragColor.rgb += weights[ i ] * getSample( theta, axis ); - - } - - } - `, - - blending: NoBlending, - depthTest: false, - depthWrite: false - - } ); - - return shaderMaterial; - -} - -function _getEquirectMaterial() { - - return new ShaderMaterial( { - - name: 'EquirectangularToCubeUV', - - uniforms: { - 'envMap': { value: null } - }, - - vertexShader: _getCommonVertexShader(), - - fragmentShader: /* glsl */` - - precision mediump float; - precision mediump int; - - varying vec3 vOutputDirection; - - uniform sampler2D envMap; - - #include - - void main() { - - vec3 outputDirection = normalize( vOutputDirection ); - vec2 uv = equirectUv( outputDirection ); - - gl_FragColor = vec4( texture2D ( envMap, uv ).rgb, 1.0 ); - - } - `, - - blending: NoBlending, - depthTest: false, - depthWrite: false - - } ); - -} - -function _getCubemapMaterial() { - - return new ShaderMaterial( { - - name: 'CubemapToCubeUV', - - uniforms: { - 'envMap': { value: null }, - 'flipEnvMap': { value: - 1 } - }, - - vertexShader: _getCommonVertexShader(), - - fragmentShader: /* glsl */` - - precision mediump float; - precision mediump int; - - uniform float flipEnvMap; - - varying vec3 vOutputDirection; - - uniform samplerCube envMap; - - void main() { - - gl_FragColor = textureCube( envMap, vec3( flipEnvMap * vOutputDirection.x, vOutputDirection.yz ) ); - - } - `, - - blending: NoBlending, - depthTest: false, - depthWrite: false - - } ); - -} - -function _getCommonVertexShader() { - - return /* glsl */` - - precision mediump float; - precision mediump int; - - attribute float faceIndex; - - varying vec3 vOutputDirection; - - // RH coordinate system; PMREM face-indexing convention - vec3 getDirection( vec2 uv, float face ) { - - uv = 2.0 * uv - 1.0; - - vec3 direction = vec3( uv, 1.0 ); - - if ( face == 0.0 ) { - - direction = direction.zyx; // ( 1, v, u ) pos x - - } else if ( face == 1.0 ) { - - direction = direction.xzy; - direction.xz *= -1.0; // ( -u, 1, -v ) pos y - - } else if ( face == 2.0 ) { - - direction.x *= -1.0; // ( -u, v, 1 ) pos z - - } else if ( face == 3.0 ) { - - direction = direction.zyx; - direction.xz *= -1.0; // ( -1, v, -u ) neg x - - } else if ( face == 4.0 ) { - - direction = direction.xzy; - direction.xy *= -1.0; // ( -u, -1, v ) neg y - - } else if ( face == 5.0 ) { - - direction.z *= -1.0; // ( u, v, -1 ) neg z - - } - - return direction; - - } - - void main() { - - vOutputDirection = getDirection( uv, faceIndex ); - gl_Position = vec4( position, 1.0 ); - - } - `; - -} - -function WebGLCubeUVMaps( renderer ) { - - let cubeUVmaps = new WeakMap(); - - let pmremGenerator = null; - - function get( texture ) { - - if ( texture && texture.isTexture ) { - - const mapping = texture.mapping; - - const isEquirectMap = ( mapping === EquirectangularReflectionMapping || mapping === EquirectangularRefractionMapping ); - const isCubeMap = ( mapping === CubeReflectionMapping || mapping === CubeRefractionMapping ); - - // equirect/cube map to cubeUV conversion - - if ( isEquirectMap || isCubeMap ) { - - if ( texture.isRenderTargetTexture && texture.needsPMREMUpdate === true ) { - - texture.needsPMREMUpdate = false; - - let renderTarget = cubeUVmaps.get( texture ); - - if ( pmremGenerator === null ) pmremGenerator = new PMREMGenerator( renderer ); - - renderTarget = isEquirectMap ? pmremGenerator.fromEquirectangular( texture, renderTarget ) : pmremGenerator.fromCubemap( texture, renderTarget ); - cubeUVmaps.set( texture, renderTarget ); - - return renderTarget.texture; - - } else { - - if ( cubeUVmaps.has( texture ) ) { - - return cubeUVmaps.get( texture ).texture; - - } else { - - const image = texture.image; - - if ( ( isEquirectMap && image && image.height > 0 ) || ( isCubeMap && image && isCubeTextureComplete( image ) ) ) { - - if ( pmremGenerator === null ) pmremGenerator = new PMREMGenerator( renderer ); - - const renderTarget = isEquirectMap ? pmremGenerator.fromEquirectangular( texture ) : pmremGenerator.fromCubemap( texture ); - cubeUVmaps.set( texture, renderTarget ); - - texture.addEventListener( 'dispose', onTextureDispose ); - - return renderTarget.texture; - - } else { - - // image not yet ready. try the conversion next frame - - return null; - - } - - } - - } - - } - - } - - return texture; - - } - - function isCubeTextureComplete( image ) { - - let count = 0; - const length = 6; - - for ( let i = 0; i < length; i ++ ) { - - if ( image[ i ] !== undefined ) count ++; - - } - - return count === length; - - - } - - function onTextureDispose( event ) { - - const texture = event.target; - - texture.removeEventListener( 'dispose', onTextureDispose ); - - const cubemapUV = cubeUVmaps.get( texture ); - - if ( cubemapUV !== undefined ) { - - cubeUVmaps.delete( texture ); - cubemapUV.dispose(); - - } - - } - - function dispose() { - - cubeUVmaps = new WeakMap(); - - if ( pmremGenerator !== null ) { - - pmremGenerator.dispose(); - pmremGenerator = null; - - } - - } - - return { - get: get, - dispose: dispose - }; - -} - -function WebGLExtensions( gl ) { - - const extensions = {}; - - function getExtension( name ) { - - if ( extensions[ name ] !== undefined ) { - - return extensions[ name ]; - - } - - let extension; - - switch ( name ) { - - case 'WEBGL_depth_texture': - extension = gl.getExtension( 'WEBGL_depth_texture' ) || gl.getExtension( 'MOZ_WEBGL_depth_texture' ) || gl.getExtension( 'WEBKIT_WEBGL_depth_texture' ); - break; - - case 'EXT_texture_filter_anisotropic': - extension = gl.getExtension( 'EXT_texture_filter_anisotropic' ) || gl.getExtension( 'MOZ_EXT_texture_filter_anisotropic' ) || gl.getExtension( 'WEBKIT_EXT_texture_filter_anisotropic' ); - break; - - case 'WEBGL_compressed_texture_s3tc': - extension = gl.getExtension( 'WEBGL_compressed_texture_s3tc' ) || gl.getExtension( 'MOZ_WEBGL_compressed_texture_s3tc' ) || gl.getExtension( 'WEBKIT_WEBGL_compressed_texture_s3tc' ); - break; - - case 'WEBGL_compressed_texture_pvrtc': - extension = gl.getExtension( 'WEBGL_compressed_texture_pvrtc' ) || gl.getExtension( 'WEBKIT_WEBGL_compressed_texture_pvrtc' ); - break; - - default: - extension = gl.getExtension( name ); - - } - - extensions[ name ] = extension; - - return extension; - - } - - return { - - has: function ( name ) { - - return getExtension( name ) !== null; - - }, - - init: function ( capabilities ) { - - if ( capabilities.isWebGL2 ) { - - getExtension( 'EXT_color_buffer_float' ); - - } else { - - getExtension( 'WEBGL_depth_texture' ); - getExtension( 'OES_texture_float' ); - getExtension( 'OES_texture_half_float' ); - getExtension( 'OES_texture_half_float_linear' ); - getExtension( 'OES_standard_derivatives' ); - getExtension( 'OES_element_index_uint' ); - getExtension( 'OES_vertex_array_object' ); - getExtension( 'ANGLE_instanced_arrays' ); - - } - - getExtension( 'OES_texture_float_linear' ); - getExtension( 'EXT_color_buffer_half_float' ); - getExtension( 'WEBGL_multisampled_render_to_texture' ); - - }, - - get: function ( name ) { - - const extension = getExtension( name ); - - if ( extension === null ) { - - console.warn( 'THREE.WebGLRenderer: ' + name + ' extension not supported.' ); - - } - - return extension; - - } - - }; - -} - -function WebGLGeometries( gl, attributes, info, bindingStates ) { - - const geometries = {}; - const wireframeAttributes = new WeakMap(); - - function onGeometryDispose( event ) { - - const geometry = event.target; - - if ( geometry.index !== null ) { - - attributes.remove( geometry.index ); - - } - - for ( const name in geometry.attributes ) { - - attributes.remove( geometry.attributes[ name ] ); - - } - - for ( const name in geometry.morphAttributes ) { - - const array = geometry.morphAttributes[ name ]; - - for ( let i = 0, l = array.length; i < l; i ++ ) { - - attributes.remove( array[ i ] ); - - } - - } - - geometry.removeEventListener( 'dispose', onGeometryDispose ); - - delete geometries[ geometry.id ]; - - const attribute = wireframeAttributes.get( geometry ); - - if ( attribute ) { - - attributes.remove( attribute ); - wireframeAttributes.delete( geometry ); - - } - - bindingStates.releaseStatesOfGeometry( geometry ); - - if ( geometry.isInstancedBufferGeometry === true ) { - - delete geometry._maxInstanceCount; - - } - - // - - info.memory.geometries --; - - } - - function get( object, geometry ) { - - if ( geometries[ geometry.id ] === true ) return geometry; - - geometry.addEventListener( 'dispose', onGeometryDispose ); - - geometries[ geometry.id ] = true; - - info.memory.geometries ++; - - return geometry; - - } - - function update( geometry ) { - - const geometryAttributes = geometry.attributes; - - // Updating index buffer in VAO now. See WebGLBindingStates. - - for ( const name in geometryAttributes ) { - - attributes.update( geometryAttributes[ name ], gl.ARRAY_BUFFER ); - - } - - // morph targets - - const morphAttributes = geometry.morphAttributes; - - for ( const name in morphAttributes ) { - - const array = morphAttributes[ name ]; - - for ( let i = 0, l = array.length; i < l; i ++ ) { - - attributes.update( array[ i ], gl.ARRAY_BUFFER ); - - } - - } - - } - - function updateWireframeAttribute( geometry ) { - - const indices = []; - - const geometryIndex = geometry.index; - const geometryPosition = geometry.attributes.position; - let version = 0; - - if ( geometryIndex !== null ) { - - const array = geometryIndex.array; - version = geometryIndex.version; - - for ( let i = 0, l = array.length; i < l; i += 3 ) { - - const a = array[ i + 0 ]; - const b = array[ i + 1 ]; - const c = array[ i + 2 ]; - - indices.push( a, b, b, c, c, a ); - - } - - } else { - - const array = geometryPosition.array; - version = geometryPosition.version; - - for ( let i = 0, l = ( array.length / 3 ) - 1; i < l; i += 3 ) { - - const a = i + 0; - const b = i + 1; - const c = i + 2; - - indices.push( a, b, b, c, c, a ); - - } - - } - - const attribute = new ( arrayNeedsUint32( indices ) ? Uint32BufferAttribute : Uint16BufferAttribute )( indices, 1 ); - attribute.version = version; - - // Updating index buffer in VAO now. See WebGLBindingStates - - // - - const previousAttribute = wireframeAttributes.get( geometry ); - - if ( previousAttribute ) attributes.remove( previousAttribute ); - - // - - wireframeAttributes.set( geometry, attribute ); - - } - - function getWireframeAttribute( geometry ) { - - const currentAttribute = wireframeAttributes.get( geometry ); - - if ( currentAttribute ) { - - const geometryIndex = geometry.index; - - if ( geometryIndex !== null ) { - - // if the attribute is obsolete, create a new one - - if ( currentAttribute.version < geometryIndex.version ) { - - updateWireframeAttribute( geometry ); - - } - - } - - } else { - - updateWireframeAttribute( geometry ); - - } - - return wireframeAttributes.get( geometry ); - - } - - return { - - get: get, - update: update, - - getWireframeAttribute: getWireframeAttribute - - }; - -} - -function WebGLIndexedBufferRenderer( gl, extensions, info, capabilities ) { - - const isWebGL2 = capabilities.isWebGL2; - - let mode; - - function setMode( value ) { - - mode = value; - - } - - let type, bytesPerElement; - - function setIndex( value ) { - - type = value.type; - bytesPerElement = value.bytesPerElement; - - } - - function render( start, count ) { - - gl.drawElements( mode, count, type, start * bytesPerElement ); - - info.update( count, mode, 1 ); - - } - - function renderInstances( start, count, primcount ) { - - if ( primcount === 0 ) return; - - let extension, methodName; - - if ( isWebGL2 ) { - - extension = gl; - methodName = 'drawElementsInstanced'; - - } else { - - extension = extensions.get( 'ANGLE_instanced_arrays' ); - methodName = 'drawElementsInstancedANGLE'; - - if ( extension === null ) { - - console.error( 'THREE.WebGLIndexedBufferRenderer: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.' ); - return; - - } - - } - - extension[ methodName ]( mode, count, type, start * bytesPerElement, primcount ); - - info.update( count, mode, primcount ); - - } - - // - - this.setMode = setMode; - this.setIndex = setIndex; - this.render = render; - this.renderInstances = renderInstances; - -} - -function WebGLInfo( gl ) { - - const memory = { - geometries: 0, - textures: 0 - }; - - const render = { - frame: 0, - calls: 0, - triangles: 0, - points: 0, - lines: 0 - }; - - function update( count, mode, instanceCount ) { - - render.calls ++; - - switch ( mode ) { - - case gl.TRIANGLES: - render.triangles += instanceCount * ( count / 3 ); - break; - - case gl.LINES: - render.lines += instanceCount * ( count / 2 ); - break; - - case gl.LINE_STRIP: - render.lines += instanceCount * ( count - 1 ); - break; - - case gl.LINE_LOOP: - render.lines += instanceCount * count; - break; - - case gl.POINTS: - render.points += instanceCount * count; - break; - - default: - console.error( 'THREE.WebGLInfo: Unknown draw mode:', mode ); - break; - - } - - } - - function reset() { - - render.calls = 0; - render.triangles = 0; - render.points = 0; - render.lines = 0; - - } - - return { - memory: memory, - render: render, - programs: null, - autoReset: true, - reset: reset, - update: update - }; - -} - -function numericalSort( a, b ) { - - return a[ 0 ] - b[ 0 ]; - -} - -function absNumericalSort( a, b ) { - - return Math.abs( b[ 1 ] ) - Math.abs( a[ 1 ] ); - -} - -function WebGLMorphtargets( gl, capabilities, textures ) { - - const influencesList = {}; - const morphInfluences = new Float32Array( 8 ); - const morphTextures = new WeakMap(); - const morph = new Vector4(); - - const workInfluences = []; - - for ( let i = 0; i < 8; i ++ ) { - - workInfluences[ i ] = [ i, 0 ]; - - } - - function update( object, geometry, program ) { - - const objectInfluences = object.morphTargetInfluences; - - if ( capabilities.isWebGL2 === true ) { - - // instead of using attributes, the WebGL 2 code path encodes morph targets - // into an array of data textures. Each layer represents a single morph target. - - const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color; - const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0; - - let entry = morphTextures.get( geometry ); - - if ( entry === undefined || entry.count !== morphTargetsCount ) { - - if ( entry !== undefined ) entry.texture.dispose(); - - const hasMorphPosition = geometry.morphAttributes.position !== undefined; - const hasMorphNormals = geometry.morphAttributes.normal !== undefined; - const hasMorphColors = geometry.morphAttributes.color !== undefined; - - const morphTargets = geometry.morphAttributes.position || []; - const morphNormals = geometry.morphAttributes.normal || []; - const morphColors = geometry.morphAttributes.color || []; - - let vertexDataCount = 0; - - if ( hasMorphPosition === true ) vertexDataCount = 1; - if ( hasMorphNormals === true ) vertexDataCount = 2; - if ( hasMorphColors === true ) vertexDataCount = 3; - - let width = geometry.attributes.position.count * vertexDataCount; - let height = 1; - - if ( width > capabilities.maxTextureSize ) { - - height = Math.ceil( width / capabilities.maxTextureSize ); - width = capabilities.maxTextureSize; - - } - - const buffer = new Float32Array( width * height * 4 * morphTargetsCount ); - - const texture = new DataArrayTexture( buffer, width, height, morphTargetsCount ); - texture.type = FloatType; - texture.needsUpdate = true; - - // fill buffer - - const vertexDataStride = vertexDataCount * 4; - - for ( let i = 0; i < morphTargetsCount; i ++ ) { - - const morphTarget = morphTargets[ i ]; - const morphNormal = morphNormals[ i ]; - const morphColor = morphColors[ i ]; - - const offset = width * height * 4 * i; - - for ( let j = 0; j < morphTarget.count; j ++ ) { - - const stride = j * vertexDataStride; - - if ( hasMorphPosition === true ) { - - morph.fromBufferAttribute( morphTarget, j ); - - buffer[ offset + stride + 0 ] = morph.x; - buffer[ offset + stride + 1 ] = morph.y; - buffer[ offset + stride + 2 ] = morph.z; - buffer[ offset + stride + 3 ] = 0; - - } - - if ( hasMorphNormals === true ) { - - morph.fromBufferAttribute( morphNormal, j ); - - buffer[ offset + stride + 4 ] = morph.x; - buffer[ offset + stride + 5 ] = morph.y; - buffer[ offset + stride + 6 ] = morph.z; - buffer[ offset + stride + 7 ] = 0; - - } - - if ( hasMorphColors === true ) { - - morph.fromBufferAttribute( morphColor, j ); - - buffer[ offset + stride + 8 ] = morph.x; - buffer[ offset + stride + 9 ] = morph.y; - buffer[ offset + stride + 10 ] = morph.z; - buffer[ offset + stride + 11 ] = ( morphColor.itemSize === 4 ) ? morph.w : 1; - - } - - } - - } - - entry = { - count: morphTargetsCount, - texture: texture, - size: new Vector2( width, height ) - }; - - morphTextures.set( geometry, entry ); - - function disposeTexture() { - - texture.dispose(); - - morphTextures.delete( geometry ); - - geometry.removeEventListener( 'dispose', disposeTexture ); - - } - - geometry.addEventListener( 'dispose', disposeTexture ); - - } - - // - - let morphInfluencesSum = 0; - - for ( let i = 0; i < objectInfluences.length; i ++ ) { - - morphInfluencesSum += objectInfluences[ i ]; - - } - - const morphBaseInfluence = geometry.morphTargetsRelative ? 1 : 1 - morphInfluencesSum; - - program.getUniforms().setValue( gl, 'morphTargetBaseInfluence', morphBaseInfluence ); - program.getUniforms().setValue( gl, 'morphTargetInfluences', objectInfluences ); - - program.getUniforms().setValue( gl, 'morphTargetsTexture', entry.texture, textures ); - program.getUniforms().setValue( gl, 'morphTargetsTextureSize', entry.size ); - - - } else { - - // When object doesn't have morph target influences defined, we treat it as a 0-length array - // This is important to make sure we set up morphTargetBaseInfluence / morphTargetInfluences - - const length = objectInfluences === undefined ? 0 : objectInfluences.length; - - let influences = influencesList[ geometry.id ]; - - if ( influences === undefined || influences.length !== length ) { - - // initialise list - - influences = []; - - for ( let i = 0; i < length; i ++ ) { - - influences[ i ] = [ i, 0 ]; - - } - - influencesList[ geometry.id ] = influences; - - } - - // Collect influences - - for ( let i = 0; i < length; i ++ ) { - - const influence = influences[ i ]; - - influence[ 0 ] = i; - influence[ 1 ] = objectInfluences[ i ]; - - } - - influences.sort( absNumericalSort ); - - for ( let i = 0; i < 8; i ++ ) { - - if ( i < length && influences[ i ][ 1 ] ) { - - workInfluences[ i ][ 0 ] = influences[ i ][ 0 ]; - workInfluences[ i ][ 1 ] = influences[ i ][ 1 ]; - - } else { - - workInfluences[ i ][ 0 ] = Number.MAX_SAFE_INTEGER; - workInfluences[ i ][ 1 ] = 0; - - } - - } - - workInfluences.sort( numericalSort ); - - const morphTargets = geometry.morphAttributes.position; - const morphNormals = geometry.morphAttributes.normal; - - let morphInfluencesSum = 0; - - for ( let i = 0; i < 8; i ++ ) { - - const influence = workInfluences[ i ]; - const index = influence[ 0 ]; - const value = influence[ 1 ]; - - if ( index !== Number.MAX_SAFE_INTEGER && value ) { - - if ( morphTargets && geometry.getAttribute( 'morphTarget' + i ) !== morphTargets[ index ] ) { - - geometry.setAttribute( 'morphTarget' + i, morphTargets[ index ] ); - - } - - if ( morphNormals && geometry.getAttribute( 'morphNormal' + i ) !== morphNormals[ index ] ) { - - geometry.setAttribute( 'morphNormal' + i, morphNormals[ index ] ); - - } - - morphInfluences[ i ] = value; - morphInfluencesSum += value; - - } else { - - if ( morphTargets && geometry.hasAttribute( 'morphTarget' + i ) === true ) { - - geometry.deleteAttribute( 'morphTarget' + i ); - - } - - if ( morphNormals && geometry.hasAttribute( 'morphNormal' + i ) === true ) { - - geometry.deleteAttribute( 'morphNormal' + i ); - - } - - morphInfluences[ i ] = 0; - - } - - } - - // GLSL shader uses formula baseinfluence * base + sum(target * influence) - // This allows us to switch between absolute morphs and relative morphs without changing shader code - // When baseinfluence = 1 - sum(influence), the above is equivalent to sum((target - base) * influence) - const morphBaseInfluence = geometry.morphTargetsRelative ? 1 : 1 - morphInfluencesSum; - - program.getUniforms().setValue( gl, 'morphTargetBaseInfluence', morphBaseInfluence ); - program.getUniforms().setValue( gl, 'morphTargetInfluences', morphInfluences ); - - } - - } - - return { - - update: update - - }; - -} - -function WebGLObjects( gl, geometries, attributes, info ) { - - let updateMap = new WeakMap(); - - function update( object ) { - - const frame = info.render.frame; - - const geometry = object.geometry; - const buffergeometry = geometries.get( object, geometry ); - - // Update once per frame - - if ( updateMap.get( buffergeometry ) !== frame ) { - - geometries.update( buffergeometry ); - - updateMap.set( buffergeometry, frame ); - - } - - if ( object.isInstancedMesh ) { - - if ( object.hasEventListener( 'dispose', onInstancedMeshDispose ) === false ) { - - object.addEventListener( 'dispose', onInstancedMeshDispose ); - - } - - attributes.update( object.instanceMatrix, gl.ARRAY_BUFFER ); - - if ( object.instanceColor !== null ) { - - attributes.update( object.instanceColor, gl.ARRAY_BUFFER ); - - } - - } - - return buffergeometry; - - } - - function dispose() { - - updateMap = new WeakMap(); - - } - - function onInstancedMeshDispose( event ) { - - const instancedMesh = event.target; - - instancedMesh.removeEventListener( 'dispose', onInstancedMeshDispose ); - - attributes.remove( instancedMesh.instanceMatrix ); - - if ( instancedMesh.instanceColor !== null ) attributes.remove( instancedMesh.instanceColor ); - - } - - return { - - update: update, - dispose: dispose - - }; - -} - -/** - * Uniforms of a program. - * Those form a tree structure with a special top-level container for the root, - * which you get by calling 'new WebGLUniforms( gl, program )'. - * - * - * Properties of inner nodes including the top-level container: - * - * .seq - array of nested uniforms - * .map - nested uniforms by name - * - * - * Methods of all nodes except the top-level container: - * - * .setValue( gl, value, [textures] ) - * - * uploads a uniform value(s) - * the 'textures' parameter is needed for sampler uniforms - * - * - * Static methods of the top-level container (textures factorizations): - * - * .upload( gl, seq, values, textures ) - * - * sets uniforms in 'seq' to 'values[id].value' - * - * .seqWithValue( seq, values ) : filteredSeq - * - * filters 'seq' entries with corresponding entry in values - * - * - * Methods of the top-level container (textures factorizations): - * - * .setValue( gl, name, value, textures ) - * - * sets uniform with name 'name' to 'value' - * - * .setOptional( gl, obj, prop ) - * - * like .set for an optional property of the object - * - */ - - -const emptyTexture = /*@__PURE__*/ new Texture(); -const emptyArrayTexture = /*@__PURE__*/ new DataArrayTexture(); -const empty3dTexture = /*@__PURE__*/ new Data3DTexture(); -const emptyCubeTexture = /*@__PURE__*/ new CubeTexture(); - -// --- Utilities --- - -// Array Caches (provide typed arrays for temporary by size) - -const arrayCacheF32 = []; -const arrayCacheI32 = []; - -// Float32Array caches used for uploading Matrix uniforms - -const mat4array = new Float32Array( 16 ); -const mat3array = new Float32Array( 9 ); -const mat2array = new Float32Array( 4 ); - -// Flattening for arrays of vectors and matrices - -function flatten( array, nBlocks, blockSize ) { - - const firstElem = array[ 0 ]; - - if ( firstElem <= 0 || firstElem > 0 ) return array; - // unoptimized: ! isNaN( firstElem ) - // see http://jacksondunstan.com/articles/983 - - const n = nBlocks * blockSize; - let r = arrayCacheF32[ n ]; - - if ( r === undefined ) { - - r = new Float32Array( n ); - arrayCacheF32[ n ] = r; - - } - - if ( nBlocks !== 0 ) { - - firstElem.toArray( r, 0 ); - - for ( let i = 1, offset = 0; i !== nBlocks; ++ i ) { - - offset += blockSize; - array[ i ].toArray( r, offset ); - - } - - } - - return r; - -} - -function arraysEqual( a, b ) { - - if ( a.length !== b.length ) return false; - - for ( let i = 0, l = a.length; i < l; i ++ ) { - - if ( a[ i ] !== b[ i ] ) return false; - - } - - return true; - -} - -function copyArray( a, b ) { - - for ( let i = 0, l = b.length; i < l; i ++ ) { - - a[ i ] = b[ i ]; - - } - -} - -// Texture unit allocation - -function allocTexUnits( textures, n ) { - - let r = arrayCacheI32[ n ]; - - if ( r === undefined ) { - - r = new Int32Array( n ); - arrayCacheI32[ n ] = r; - - } - - for ( let i = 0; i !== n; ++ i ) { - - r[ i ] = textures.allocateTextureUnit(); - - } - - return r; - -} - -// --- Setters --- - -// Note: Defining these methods externally, because they come in a bunch -// and this way their names minify. - -// Single scalar - -function setValueV1f( gl, v ) { - - const cache = this.cache; - - if ( cache[ 0 ] === v ) return; - - gl.uniform1f( this.addr, v ); - - cache[ 0 ] = v; - -} - -// Single float vector (from flat array or THREE.VectorN) - -function setValueV2f( gl, v ) { - - const cache = this.cache; - - if ( v.x !== undefined ) { - - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y ) { - - gl.uniform2f( this.addr, v.x, v.y ); - - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; - - } - - } else { - - if ( arraysEqual( cache, v ) ) return; - - gl.uniform2fv( this.addr, v ); - - copyArray( cache, v ); - - } - -} - -function setValueV3f( gl, v ) { - - const cache = this.cache; - - if ( v.x !== undefined ) { - - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z ) { - - gl.uniform3f( this.addr, v.x, v.y, v.z ); - - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; - cache[ 2 ] = v.z; - - } - - } else if ( v.r !== undefined ) { - - if ( cache[ 0 ] !== v.r || cache[ 1 ] !== v.g || cache[ 2 ] !== v.b ) { - - gl.uniform3f( this.addr, v.r, v.g, v.b ); - - cache[ 0 ] = v.r; - cache[ 1 ] = v.g; - cache[ 2 ] = v.b; - - } - - } else { - - if ( arraysEqual( cache, v ) ) return; - - gl.uniform3fv( this.addr, v ); - - copyArray( cache, v ); - - } - -} - -function setValueV4f( gl, v ) { - - const cache = this.cache; - - if ( v.x !== undefined ) { - - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z || cache[ 3 ] !== v.w ) { - - gl.uniform4f( this.addr, v.x, v.y, v.z, v.w ); - - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; - cache[ 2 ] = v.z; - cache[ 3 ] = v.w; - - } - - } else { - - if ( arraysEqual( cache, v ) ) return; - - gl.uniform4fv( this.addr, v ); - - copyArray( cache, v ); - - } - -} - -// Single matrix (from flat array or THREE.MatrixN) - -function setValueM2( gl, v ) { - - const cache = this.cache; - const elements = v.elements; - - if ( elements === undefined ) { - - if ( arraysEqual( cache, v ) ) return; - - gl.uniformMatrix2fv( this.addr, false, v ); - - copyArray( cache, v ); - - } else { - - if ( arraysEqual( cache, elements ) ) return; - - mat2array.set( elements ); - - gl.uniformMatrix2fv( this.addr, false, mat2array ); - - copyArray( cache, elements ); - - } - -} - -function setValueM3( gl, v ) { - - const cache = this.cache; - const elements = v.elements; - - if ( elements === undefined ) { - - if ( arraysEqual( cache, v ) ) return; - - gl.uniformMatrix3fv( this.addr, false, v ); - - copyArray( cache, v ); - - } else { - - if ( arraysEqual( cache, elements ) ) return; - - mat3array.set( elements ); - - gl.uniformMatrix3fv( this.addr, false, mat3array ); - - copyArray( cache, elements ); - - } - -} - -function setValueM4( gl, v ) { - - const cache = this.cache; - const elements = v.elements; - - if ( elements === undefined ) { - - if ( arraysEqual( cache, v ) ) return; - - gl.uniformMatrix4fv( this.addr, false, v ); - - copyArray( cache, v ); - - } else { - - if ( arraysEqual( cache, elements ) ) return; - - mat4array.set( elements ); - - gl.uniformMatrix4fv( this.addr, false, mat4array ); - - copyArray( cache, elements ); - - } - -} - -// Single integer / boolean - -function setValueV1i( gl, v ) { - - const cache = this.cache; - - if ( cache[ 0 ] === v ) return; - - gl.uniform1i( this.addr, v ); - - cache[ 0 ] = v; - -} - -// Single integer / boolean vector (from flat array or THREE.VectorN) - -function setValueV2i( gl, v ) { - - const cache = this.cache; - - if ( v.x !== undefined ) { - - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y ) { - - gl.uniform2i( this.addr, v.x, v.y ); - - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; - - } - - } else { - - if ( arraysEqual( cache, v ) ) return; - - gl.uniform2iv( this.addr, v ); - - copyArray( cache, v ); - - } - -} - -function setValueV3i( gl, v ) { - - const cache = this.cache; - - if ( v.x !== undefined ) { - - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z ) { - - gl.uniform3i( this.addr, v.x, v.y, v.z ); - - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; - cache[ 2 ] = v.z; - - } - - } else { - - if ( arraysEqual( cache, v ) ) return; - - gl.uniform3iv( this.addr, v ); - - copyArray( cache, v ); - - } - -} - -function setValueV4i( gl, v ) { - - const cache = this.cache; - - if ( v.x !== undefined ) { - - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z || cache[ 3 ] !== v.w ) { - - gl.uniform4i( this.addr, v.x, v.y, v.z, v.w ); - - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; - cache[ 2 ] = v.z; - cache[ 3 ] = v.w; - - } - - } else { - - if ( arraysEqual( cache, v ) ) return; - - gl.uniform4iv( this.addr, v ); - - copyArray( cache, v ); - - } - -} - -// Single unsigned integer - -function setValueV1ui( gl, v ) { - - const cache = this.cache; - - if ( cache[ 0 ] === v ) return; - - gl.uniform1ui( this.addr, v ); - - cache[ 0 ] = v; - -} - -// Single unsigned integer vector (from flat array or THREE.VectorN) - -function setValueV2ui( gl, v ) { - - const cache = this.cache; - - if ( v.x !== undefined ) { - - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y ) { - - gl.uniform2ui( this.addr, v.x, v.y ); - - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; - - } - - } else { - - if ( arraysEqual( cache, v ) ) return; - - gl.uniform2uiv( this.addr, v ); - - copyArray( cache, v ); - - } - -} - -function setValueV3ui( gl, v ) { - - const cache = this.cache; - - if ( v.x !== undefined ) { - - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z ) { - - gl.uniform3ui( this.addr, v.x, v.y, v.z ); - - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; - cache[ 2 ] = v.z; - - } - - } else { - - if ( arraysEqual( cache, v ) ) return; - - gl.uniform3uiv( this.addr, v ); - - copyArray( cache, v ); - - } - -} - -function setValueV4ui( gl, v ) { - - const cache = this.cache; - - if ( v.x !== undefined ) { - - if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z || cache[ 3 ] !== v.w ) { - - gl.uniform4ui( this.addr, v.x, v.y, v.z, v.w ); - - cache[ 0 ] = v.x; - cache[ 1 ] = v.y; - cache[ 2 ] = v.z; - cache[ 3 ] = v.w; - - } - - } else { - - if ( arraysEqual( cache, v ) ) return; - - gl.uniform4uiv( this.addr, v ); - - copyArray( cache, v ); - - } - -} - - -// Single texture (2D / Cube) - -function setValueT1( gl, v, textures ) { - - const cache = this.cache; - const unit = textures.allocateTextureUnit(); - - if ( cache[ 0 ] !== unit ) { - - gl.uniform1i( this.addr, unit ); - cache[ 0 ] = unit; - - } - - textures.setTexture2D( v || emptyTexture, unit ); - -} - -function setValueT3D1( gl, v, textures ) { - - const cache = this.cache; - const unit = textures.allocateTextureUnit(); - - if ( cache[ 0 ] !== unit ) { - - gl.uniform1i( this.addr, unit ); - cache[ 0 ] = unit; - - } - - textures.setTexture3D( v || empty3dTexture, unit ); - -} - -function setValueT6( gl, v, textures ) { - - const cache = this.cache; - const unit = textures.allocateTextureUnit(); - - if ( cache[ 0 ] !== unit ) { - - gl.uniform1i( this.addr, unit ); - cache[ 0 ] = unit; - - } - - textures.setTextureCube( v || emptyCubeTexture, unit ); - -} - -function setValueT2DArray1( gl, v, textures ) { - - const cache = this.cache; - const unit = textures.allocateTextureUnit(); - - if ( cache[ 0 ] !== unit ) { - - gl.uniform1i( this.addr, unit ); - cache[ 0 ] = unit; - - } - - textures.setTexture2DArray( v || emptyArrayTexture, unit ); - -} - -// Helper to pick the right setter for the singular case - -function getSingularSetter( type ) { - - switch ( type ) { - - case 0x1406: return setValueV1f; // FLOAT - case 0x8b50: return setValueV2f; // _VEC2 - case 0x8b51: return setValueV3f; // _VEC3 - case 0x8b52: return setValueV4f; // _VEC4 - - case 0x8b5a: return setValueM2; // _MAT2 - case 0x8b5b: return setValueM3; // _MAT3 - case 0x8b5c: return setValueM4; // _MAT4 - - case 0x1404: case 0x8b56: return setValueV1i; // INT, BOOL - case 0x8b53: case 0x8b57: return setValueV2i; // _VEC2 - case 0x8b54: case 0x8b58: return setValueV3i; // _VEC3 - case 0x8b55: case 0x8b59: return setValueV4i; // _VEC4 - - case 0x1405: return setValueV1ui; // UINT - case 0x8dc6: return setValueV2ui; // _VEC2 - case 0x8dc7: return setValueV3ui; // _VEC3 - case 0x8dc8: return setValueV4ui; // _VEC4 - - case 0x8b5e: // SAMPLER_2D - case 0x8d66: // SAMPLER_EXTERNAL_OES - case 0x8dca: // INT_SAMPLER_2D - case 0x8dd2: // UNSIGNED_INT_SAMPLER_2D - case 0x8b62: // SAMPLER_2D_SHADOW - return setValueT1; - - case 0x8b5f: // SAMPLER_3D - case 0x8dcb: // INT_SAMPLER_3D - case 0x8dd3: // UNSIGNED_INT_SAMPLER_3D - return setValueT3D1; - - case 0x8b60: // SAMPLER_CUBE - case 0x8dcc: // INT_SAMPLER_CUBE - case 0x8dd4: // UNSIGNED_INT_SAMPLER_CUBE - case 0x8dc5: // SAMPLER_CUBE_SHADOW - return setValueT6; - - case 0x8dc1: // SAMPLER_2D_ARRAY - case 0x8dcf: // INT_SAMPLER_2D_ARRAY - case 0x8dd7: // UNSIGNED_INT_SAMPLER_2D_ARRAY - case 0x8dc4: // SAMPLER_2D_ARRAY_SHADOW - return setValueT2DArray1; - - } - -} - - -// Array of scalars - -function setValueV1fArray( gl, v ) { - - gl.uniform1fv( this.addr, v ); - -} - -// Array of vectors (from flat array or array of THREE.VectorN) - -function setValueV2fArray( gl, v ) { - - const data = flatten( v, this.size, 2 ); - - gl.uniform2fv( this.addr, data ); - -} - -function setValueV3fArray( gl, v ) { - - const data = flatten( v, this.size, 3 ); - - gl.uniform3fv( this.addr, data ); - -} - -function setValueV4fArray( gl, v ) { - - const data = flatten( v, this.size, 4 ); - - gl.uniform4fv( this.addr, data ); - -} - -// Array of matrices (from flat array or array of THREE.MatrixN) - -function setValueM2Array( gl, v ) { - - const data = flatten( v, this.size, 4 ); - - gl.uniformMatrix2fv( this.addr, false, data ); - -} - -function setValueM3Array( gl, v ) { - - const data = flatten( v, this.size, 9 ); - - gl.uniformMatrix3fv( this.addr, false, data ); - -} - -function setValueM4Array( gl, v ) { - - const data = flatten( v, this.size, 16 ); - - gl.uniformMatrix4fv( this.addr, false, data ); - -} - -// Array of integer / boolean - -function setValueV1iArray( gl, v ) { - - gl.uniform1iv( this.addr, v ); - -} - -// Array of integer / boolean vectors (from flat array) - -function setValueV2iArray( gl, v ) { - - gl.uniform2iv( this.addr, v ); - -} - -function setValueV3iArray( gl, v ) { - - gl.uniform3iv( this.addr, v ); - -} - -function setValueV4iArray( gl, v ) { - - gl.uniform4iv( this.addr, v ); - -} - -// Array of unsigned integer - -function setValueV1uiArray( gl, v ) { - - gl.uniform1uiv( this.addr, v ); - -} - -// Array of unsigned integer vectors (from flat array) - -function setValueV2uiArray( gl, v ) { - - gl.uniform2uiv( this.addr, v ); - -} - -function setValueV3uiArray( gl, v ) { - - gl.uniform3uiv( this.addr, v ); - -} - -function setValueV4uiArray( gl, v ) { - - gl.uniform4uiv( this.addr, v ); - -} - - -// Array of textures (2D / 3D / Cube / 2DArray) - -function setValueT1Array( gl, v, textures ) { - - const cache = this.cache; - - const n = v.length; - - const units = allocTexUnits( textures, n ); - - if ( ! arraysEqual( cache, units ) ) { - - gl.uniform1iv( this.addr, units ); - - copyArray( cache, units ); - - } - - for ( let i = 0; i !== n; ++ i ) { - - textures.setTexture2D( v[ i ] || emptyTexture, units[ i ] ); - - } - -} - -function setValueT3DArray( gl, v, textures ) { - - const cache = this.cache; - - const n = v.length; - - const units = allocTexUnits( textures, n ); - - if ( ! arraysEqual( cache, units ) ) { - - gl.uniform1iv( this.addr, units ); - - copyArray( cache, units ); - - } - - for ( let i = 0; i !== n; ++ i ) { - - textures.setTexture3D( v[ i ] || empty3dTexture, units[ i ] ); - - } - -} - -function setValueT6Array( gl, v, textures ) { - - const cache = this.cache; - - const n = v.length; - - const units = allocTexUnits( textures, n ); - - if ( ! arraysEqual( cache, units ) ) { - - gl.uniform1iv( this.addr, units ); - - copyArray( cache, units ); - - } - - for ( let i = 0; i !== n; ++ i ) { - - textures.setTextureCube( v[ i ] || emptyCubeTexture, units[ i ] ); - - } - -} - -function setValueT2DArrayArray( gl, v, textures ) { - - const cache = this.cache; - - const n = v.length; - - const units = allocTexUnits( textures, n ); - - if ( ! arraysEqual( cache, units ) ) { - - gl.uniform1iv( this.addr, units ); - - copyArray( cache, units ); - - } - - for ( let i = 0; i !== n; ++ i ) { - - textures.setTexture2DArray( v[ i ] || emptyArrayTexture, units[ i ] ); - - } - -} - - -// Helper to pick the right setter for a pure (bottom-level) array - -function getPureArraySetter( type ) { - - switch ( type ) { - - case 0x1406: return setValueV1fArray; // FLOAT - case 0x8b50: return setValueV2fArray; // _VEC2 - case 0x8b51: return setValueV3fArray; // _VEC3 - case 0x8b52: return setValueV4fArray; // _VEC4 - - case 0x8b5a: return setValueM2Array; // _MAT2 - case 0x8b5b: return setValueM3Array; // _MAT3 - case 0x8b5c: return setValueM4Array; // _MAT4 - - case 0x1404: case 0x8b56: return setValueV1iArray; // INT, BOOL - case 0x8b53: case 0x8b57: return setValueV2iArray; // _VEC2 - case 0x8b54: case 0x8b58: return setValueV3iArray; // _VEC3 - case 0x8b55: case 0x8b59: return setValueV4iArray; // _VEC4 - - case 0x1405: return setValueV1uiArray; // UINT - case 0x8dc6: return setValueV2uiArray; // _VEC2 - case 0x8dc7: return setValueV3uiArray; // _VEC3 - case 0x8dc8: return setValueV4uiArray; // _VEC4 - - case 0x8b5e: // SAMPLER_2D - case 0x8d66: // SAMPLER_EXTERNAL_OES - case 0x8dca: // INT_SAMPLER_2D - case 0x8dd2: // UNSIGNED_INT_SAMPLER_2D - case 0x8b62: // SAMPLER_2D_SHADOW - return setValueT1Array; - - case 0x8b5f: // SAMPLER_3D - case 0x8dcb: // INT_SAMPLER_3D - case 0x8dd3: // UNSIGNED_INT_SAMPLER_3D - return setValueT3DArray; - - case 0x8b60: // SAMPLER_CUBE - case 0x8dcc: // INT_SAMPLER_CUBE - case 0x8dd4: // UNSIGNED_INT_SAMPLER_CUBE - case 0x8dc5: // SAMPLER_CUBE_SHADOW - return setValueT6Array; - - case 0x8dc1: // SAMPLER_2D_ARRAY - case 0x8dcf: // INT_SAMPLER_2D_ARRAY - case 0x8dd7: // UNSIGNED_INT_SAMPLER_2D_ARRAY - case 0x8dc4: // SAMPLER_2D_ARRAY_SHADOW - return setValueT2DArrayArray; - - } - -} - -// --- Uniform Classes --- - -class SingleUniform { - - constructor( id, activeInfo, addr ) { - - this.id = id; - this.addr = addr; - this.cache = []; - this.setValue = getSingularSetter( activeInfo.type ); - - // this.path = activeInfo.name; // DEBUG - - } - -} - -class PureArrayUniform { - - constructor( id, activeInfo, addr ) { - - this.id = id; - this.addr = addr; - this.cache = []; - this.size = activeInfo.size; - this.setValue = getPureArraySetter( activeInfo.type ); - - // this.path = activeInfo.name; // DEBUG - - } - -} - -class StructuredUniform { - - constructor( id ) { - - this.id = id; - - this.seq = []; - this.map = {}; - - } - - setValue( gl, value, textures ) { - - const seq = this.seq; - - for ( let i = 0, n = seq.length; i !== n; ++ i ) { - - const u = seq[ i ]; - u.setValue( gl, value[ u.id ], textures ); - - } - - } - -} - -// --- Top-level --- - -// Parser - builds up the property tree from the path strings - -const RePathPart = /(\w+)(\])?(\[|\.)?/g; - -// extracts -// - the identifier (member name or array index) -// - followed by an optional right bracket (found when array index) -// - followed by an optional left bracket or dot (type of subscript) -// -// Note: These portions can be read in a non-overlapping fashion and -// allow straightforward parsing of the hierarchy that WebGL encodes -// in the uniform names. - -function addUniform( container, uniformObject ) { - - container.seq.push( uniformObject ); - container.map[ uniformObject.id ] = uniformObject; - -} - -function parseUniform( activeInfo, addr, container ) { - - const path = activeInfo.name, - pathLength = path.length; - - // reset RegExp object, because of the early exit of a previous run - RePathPart.lastIndex = 0; - - while ( true ) { - - const match = RePathPart.exec( path ), - matchEnd = RePathPart.lastIndex; - - let id = match[ 1 ]; - const idIsIndex = match[ 2 ] === ']', - subscript = match[ 3 ]; - - if ( idIsIndex ) id = id | 0; // convert to integer - - if ( subscript === undefined || subscript === '[' && matchEnd + 2 === pathLength ) { - - // bare name or "pure" bottom-level array "[0]" suffix - - addUniform( container, subscript === undefined ? - new SingleUniform( id, activeInfo, addr ) : - new PureArrayUniform( id, activeInfo, addr ) ); - - break; - - } else { - - // step into inner node / create it in case it doesn't exist - - const map = container.map; - let next = map[ id ]; - - if ( next === undefined ) { - - next = new StructuredUniform( id ); - addUniform( container, next ); - - } - - container = next; - - } - - } - -} - -// Root Container - -class WebGLUniforms { - - constructor( gl, program ) { - - this.seq = []; - this.map = {}; - - const n = gl.getProgramParameter( program, gl.ACTIVE_UNIFORMS ); - - for ( let i = 0; i < n; ++ i ) { - - const info = gl.getActiveUniform( program, i ), - addr = gl.getUniformLocation( program, info.name ); - - parseUniform( info, addr, this ); - - } - - } - - setValue( gl, name, value, textures ) { - - const u = this.map[ name ]; - - if ( u !== undefined ) u.setValue( gl, value, textures ); - - } - - setOptional( gl, object, name ) { - - const v = object[ name ]; - - if ( v !== undefined ) this.setValue( gl, name, v ); - - } - - static upload( gl, seq, values, textures ) { - - for ( let i = 0, n = seq.length; i !== n; ++ i ) { - - const u = seq[ i ], - v = values[ u.id ]; - - if ( v.needsUpdate !== false ) { - - // note: always updating when .needsUpdate is undefined - u.setValue( gl, v.value, textures ); - - } - - } - - } - - static seqWithValue( seq, values ) { - - const r = []; - - for ( let i = 0, n = seq.length; i !== n; ++ i ) { - - const u = seq[ i ]; - if ( u.id in values ) r.push( u ); - - } - - return r; - - } - -} - -function WebGLShader( gl, type, string ) { - - const shader = gl.createShader( type ); - - gl.shaderSource( shader, string ); - gl.compileShader( shader ); - - return shader; - -} - -let programIdCount = 0; - -function handleSource( string, errorLine ) { - - const lines = string.split( '\n' ); - const lines2 = []; - - const from = Math.max( errorLine - 6, 0 ); - const to = Math.min( errorLine + 6, lines.length ); - - for ( let i = from; i < to; i ++ ) { - - const line = i + 1; - lines2.push( `${line === errorLine ? '>' : ' '} ${line}: ${lines[ i ]}` ); - - } - - return lines2.join( '\n' ); - -} - -function getEncodingComponents( colorSpace ) { - - switch ( colorSpace ) { - - case LinearSRGBColorSpace: - return [ 'Linear', '( value )' ]; - case SRGBColorSpace: - return [ 'sRGB', '( value )' ]; - default: - console.warn( 'THREE.WebGLProgram: Unsupported color space:', colorSpace ); - return [ 'Linear', '( value )' ]; - - } - -} - -function getShaderErrors( gl, shader, type ) { - - const status = gl.getShaderParameter( shader, gl.COMPILE_STATUS ); - const errors = gl.getShaderInfoLog( shader ).trim(); - - if ( status && errors === '' ) return ''; - - const errorMatches = /ERROR: 0:(\d+)/.exec( errors ); - if ( errorMatches ) { - - // --enable-privileged-webgl-extension - // console.log( '**' + type + '**', gl.getExtension( 'WEBGL_debug_shaders' ).getTranslatedShaderSource( shader ) ); - - const errorLine = parseInt( errorMatches[ 1 ] ); - return type.toUpperCase() + '\n\n' + errors + '\n\n' + handleSource( gl.getShaderSource( shader ), errorLine ); - - } else { - - return errors; - - } - -} - -function getTexelEncodingFunction( functionName, colorSpace ) { - - const components = getEncodingComponents( colorSpace ); - return 'vec4 ' + functionName + '( vec4 value ) { return LinearTo' + components[ 0 ] + components[ 1 ] + '; }'; - -} - -function getToneMappingFunction( functionName, toneMapping ) { - - let toneMappingName; - - switch ( toneMapping ) { - - case LinearToneMapping: - toneMappingName = 'Linear'; - break; - - case ReinhardToneMapping: - toneMappingName = 'Reinhard'; - break; - - case CineonToneMapping: - toneMappingName = 'OptimizedCineon'; - break; - - case ACESFilmicToneMapping: - toneMappingName = 'ACESFilmic'; - break; - - case CustomToneMapping: - toneMappingName = 'Custom'; - break; - - default: - console.warn( 'THREE.WebGLProgram: Unsupported toneMapping:', toneMapping ); - toneMappingName = 'Linear'; - - } - - return 'vec3 ' + functionName + '( vec3 color ) { return ' + toneMappingName + 'ToneMapping( color ); }'; - -} - -function generateExtensions( parameters ) { - - const chunks = [ - ( parameters.extensionDerivatives || !! parameters.envMapCubeUVHeight || parameters.bumpMap || parameters.normalMapTangentSpace || parameters.clearcoatNormalMap || parameters.flatShading || parameters.shaderID === 'physical' ) ? '#extension GL_OES_standard_derivatives : enable' : '', - ( parameters.extensionFragDepth || parameters.logarithmicDepthBuffer ) && parameters.rendererExtensionFragDepth ? '#extension GL_EXT_frag_depth : enable' : '', - ( parameters.extensionDrawBuffers && parameters.rendererExtensionDrawBuffers ) ? '#extension GL_EXT_draw_buffers : require' : '', - ( parameters.extensionShaderTextureLOD || parameters.envMap || parameters.transmission ) && parameters.rendererExtensionShaderTextureLod ? '#extension GL_EXT_shader_texture_lod : enable' : '' - ]; - - return chunks.filter( filterEmptyLine ).join( '\n' ); - -} - -function generateDefines( defines ) { - - const chunks = []; - - for ( const name in defines ) { - - const value = defines[ name ]; - - if ( value === false ) continue; - - chunks.push( '#define ' + name + ' ' + value ); - - } - - return chunks.join( '\n' ); - -} - -function fetchAttributeLocations( gl, program ) { - - const attributes = {}; - - const n = gl.getProgramParameter( program, gl.ACTIVE_ATTRIBUTES ); - - for ( let i = 0; i < n; i ++ ) { - - const info = gl.getActiveAttrib( program, i ); - const name = info.name; - - let locationSize = 1; - if ( info.type === gl.FLOAT_MAT2 ) locationSize = 2; - if ( info.type === gl.FLOAT_MAT3 ) locationSize = 3; - if ( info.type === gl.FLOAT_MAT4 ) locationSize = 4; - - // console.log( 'THREE.WebGLProgram: ACTIVE VERTEX ATTRIBUTE:', name, i ); - - attributes[ name ] = { - type: info.type, - location: gl.getAttribLocation( program, name ), - locationSize: locationSize - }; - - } - - return attributes; - -} - -function filterEmptyLine( string ) { - - return string !== ''; - -} - -function replaceLightNums( string, parameters ) { - - const numSpotLightCoords = parameters.numSpotLightShadows + parameters.numSpotLightMaps - parameters.numSpotLightShadowsWithMaps; - - return string - .replace( /NUM_DIR_LIGHTS/g, parameters.numDirLights ) - .replace( /NUM_SPOT_LIGHTS/g, parameters.numSpotLights ) - .replace( /NUM_SPOT_LIGHT_MAPS/g, parameters.numSpotLightMaps ) - .replace( /NUM_SPOT_LIGHT_COORDS/g, numSpotLightCoords ) - .replace( /NUM_RECT_AREA_LIGHTS/g, parameters.numRectAreaLights ) - .replace( /NUM_POINT_LIGHTS/g, parameters.numPointLights ) - .replace( /NUM_HEMI_LIGHTS/g, parameters.numHemiLights ) - .replace( /NUM_DIR_LIGHT_SHADOWS/g, parameters.numDirLightShadows ) - .replace( /NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS/g, parameters.numSpotLightShadowsWithMaps ) - .replace( /NUM_SPOT_LIGHT_SHADOWS/g, parameters.numSpotLightShadows ) - .replace( /NUM_POINT_LIGHT_SHADOWS/g, parameters.numPointLightShadows ); - -} - -function replaceClippingPlaneNums( string, parameters ) { - - return string - .replace( /NUM_CLIPPING_PLANES/g, parameters.numClippingPlanes ) - .replace( /UNION_CLIPPING_PLANES/g, ( parameters.numClippingPlanes - parameters.numClipIntersection ) ); - -} - -// Resolve Includes - -const includePattern = /^[ \t]*#include +<([\w\d./]+)>/gm; - -function resolveIncludes( string ) { - - return string.replace( includePattern, includeReplacer ); - -} - -function includeReplacer( match, include ) { - - const string = ShaderChunk[ include ]; - - if ( string === undefined ) { - - throw new Error( 'Can not resolve #include <' + include + '>' ); - - } - - return resolveIncludes( string ); - -} - -// Unroll Loops - -const unrollLoopPattern = /#pragma unroll_loop_start\s+for\s*\(\s*int\s+i\s*=\s*(\d+)\s*;\s*i\s*<\s*(\d+)\s*;\s*i\s*\+\+\s*\)\s*{([\s\S]+?)}\s+#pragma unroll_loop_end/g; - -function unrollLoops( string ) { - - return string.replace( unrollLoopPattern, loopReplacer ); - -} - -function loopReplacer( match, start, end, snippet ) { - - let string = ''; - - for ( let i = parseInt( start ); i < parseInt( end ); i ++ ) { - - string += snippet - .replace( /\[\s*i\s*\]/g, '[ ' + i + ' ]' ) - .replace( /UNROLLED_LOOP_INDEX/g, i ); - - } - - return string; - -} - -// - -function generatePrecision( parameters ) { - - let precisionstring = 'precision ' + parameters.precision + ' float;\nprecision ' + parameters.precision + ' int;'; - - if ( parameters.precision === 'highp' ) { - - precisionstring += '\n#define HIGH_PRECISION'; - - } else if ( parameters.precision === 'mediump' ) { - - precisionstring += '\n#define MEDIUM_PRECISION'; - - } else if ( parameters.precision === 'lowp' ) { - - precisionstring += '\n#define LOW_PRECISION'; - - } - - return precisionstring; - -} - -function generateShadowMapTypeDefine( parameters ) { - - let shadowMapTypeDefine = 'SHADOWMAP_TYPE_BASIC'; - - if ( parameters.shadowMapType === PCFShadowMap ) { - - shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF'; - - } else if ( parameters.shadowMapType === PCFSoftShadowMap ) { - - shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF_SOFT'; - - } else if ( parameters.shadowMapType === VSMShadowMap ) { - - shadowMapTypeDefine = 'SHADOWMAP_TYPE_VSM'; - - } - - return shadowMapTypeDefine; - -} - -function generateEnvMapTypeDefine( parameters ) { - - let envMapTypeDefine = 'ENVMAP_TYPE_CUBE'; - - if ( parameters.envMap ) { - - switch ( parameters.envMapMode ) { - - case CubeReflectionMapping: - case CubeRefractionMapping: - envMapTypeDefine = 'ENVMAP_TYPE_CUBE'; - break; - - case CubeUVReflectionMapping: - envMapTypeDefine = 'ENVMAP_TYPE_CUBE_UV'; - break; - - } - - } - - return envMapTypeDefine; - -} - -function generateEnvMapModeDefine( parameters ) { - - let envMapModeDefine = 'ENVMAP_MODE_REFLECTION'; - - if ( parameters.envMap ) { - - switch ( parameters.envMapMode ) { - - case CubeRefractionMapping: - - envMapModeDefine = 'ENVMAP_MODE_REFRACTION'; - break; - - } - - } - - return envMapModeDefine; - -} - -function generateEnvMapBlendingDefine( parameters ) { - - let envMapBlendingDefine = 'ENVMAP_BLENDING_NONE'; - - if ( parameters.envMap ) { - - switch ( parameters.combine ) { - - case MultiplyOperation: - envMapBlendingDefine = 'ENVMAP_BLENDING_MULTIPLY'; - break; - - case MixOperation: - envMapBlendingDefine = 'ENVMAP_BLENDING_MIX'; - break; - - case AddOperation: - envMapBlendingDefine = 'ENVMAP_BLENDING_ADD'; - break; - - } - - } - - return envMapBlendingDefine; - -} - -function generateCubeUVSize( parameters ) { - - const imageHeight = parameters.envMapCubeUVHeight; - - if ( imageHeight === null ) return null; - - const maxMip = Math.log2( imageHeight ) - 2; - - const texelHeight = 1.0 / imageHeight; - - const texelWidth = 1.0 / ( 3 * Math.max( Math.pow( 2, maxMip ), 7 * 16 ) ); - - return { texelWidth, texelHeight, maxMip }; - -} - -function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) { - - // TODO Send this event to Three.js DevTools - // console.log( 'WebGLProgram', cacheKey ); - - const gl = renderer.getContext(); - - const defines = parameters.defines; - - let vertexShader = parameters.vertexShader; - let fragmentShader = parameters.fragmentShader; - - const shadowMapTypeDefine = generateShadowMapTypeDefine( parameters ); - const envMapTypeDefine = generateEnvMapTypeDefine( parameters ); - const envMapModeDefine = generateEnvMapModeDefine( parameters ); - const envMapBlendingDefine = generateEnvMapBlendingDefine( parameters ); - const envMapCubeUVSize = generateCubeUVSize( parameters ); - - const customExtensions = parameters.isWebGL2 ? '' : generateExtensions( parameters ); - - const customDefines = generateDefines( defines ); - - const program = gl.createProgram(); - - let prefixVertex, prefixFragment; - let versionString = parameters.glslVersion ? '#version ' + parameters.glslVersion + '\n' : ''; - - if ( parameters.isRawShaderMaterial ) { - - prefixVertex = [ - - customDefines - - ].filter( filterEmptyLine ).join( '\n' ); - - if ( prefixVertex.length > 0 ) { - - prefixVertex += '\n'; - - } - - prefixFragment = [ - - customExtensions, - customDefines - - ].filter( filterEmptyLine ).join( '\n' ); - - if ( prefixFragment.length > 0 ) { - - prefixFragment += '\n'; - - } - - } else { - - prefixVertex = [ - - generatePrecision( parameters ), - - '#define SHADER_NAME ' + parameters.shaderName, - - customDefines, - - parameters.instancing ? '#define USE_INSTANCING' : '', - parameters.instancingColor ? '#define USE_INSTANCING_COLOR' : '', - - parameters.useFog && parameters.fog ? '#define USE_FOG' : '', - parameters.useFog && parameters.fogExp2 ? '#define FOG_EXP2' : '', - - parameters.map ? '#define USE_MAP' : '', - parameters.envMap ? '#define USE_ENVMAP' : '', - parameters.envMap ? '#define ' + envMapModeDefine : '', - parameters.lightMap ? '#define USE_LIGHTMAP' : '', - parameters.aoMap ? '#define USE_AOMAP' : '', - parameters.bumpMap ? '#define USE_BUMPMAP' : '', - parameters.normalMap ? '#define USE_NORMALMAP' : '', - parameters.normalMapObjectSpace ? '#define USE_NORMALMAP_OBJECTSPACE' : '', - parameters.normalMapTangentSpace ? '#define USE_NORMALMAP_TANGENTSPACE' : '', - parameters.displacementMap ? '#define USE_DISPLACEMENTMAP' : '', - parameters.emissiveMap ? '#define USE_EMISSIVEMAP' : '', - - parameters.anisotropyMap ? '#define USE_ANISOTROPYMAP' : '', - - parameters.clearcoatMap ? '#define USE_CLEARCOATMAP' : '', - parameters.clearcoatRoughnessMap ? '#define USE_CLEARCOAT_ROUGHNESSMAP' : '', - parameters.clearcoatNormalMap ? '#define USE_CLEARCOAT_NORMALMAP' : '', - - parameters.iridescenceMap ? '#define USE_IRIDESCENCEMAP' : '', - parameters.iridescenceThicknessMap ? '#define USE_IRIDESCENCE_THICKNESSMAP' : '', - - parameters.specularMap ? '#define USE_SPECULARMAP' : '', - parameters.specularColorMap ? '#define USE_SPECULAR_COLORMAP' : '', - parameters.specularIntensityMap ? '#define USE_SPECULAR_INTENSITYMAP' : '', - - parameters.roughnessMap ? '#define USE_ROUGHNESSMAP' : '', - parameters.metalnessMap ? '#define USE_METALNESSMAP' : '', - parameters.alphaMap ? '#define USE_ALPHAMAP' : '', - - parameters.transmission ? '#define USE_TRANSMISSION' : '', - parameters.transmissionMap ? '#define USE_TRANSMISSIONMAP' : '', - parameters.thicknessMap ? '#define USE_THICKNESSMAP' : '', - - parameters.sheenColorMap ? '#define USE_SHEEN_COLORMAP' : '', - parameters.sheenRoughnessMap ? '#define USE_SHEEN_ROUGHNESSMAP' : '', - - // - - parameters.mapUv ? '#define MAP_UV ' + parameters.mapUv : '', - parameters.alphaMapUv ? '#define ALPHAMAP_UV ' + parameters.alphaMapUv : '', - parameters.lightMapUv ? '#define LIGHTMAP_UV ' + parameters.lightMapUv : '', - parameters.aoMapUv ? '#define AOMAP_UV ' + parameters.aoMapUv : '', - parameters.emissiveMapUv ? '#define EMISSIVEMAP_UV ' + parameters.emissiveMapUv : '', - parameters.bumpMapUv ? '#define BUMPMAP_UV ' + parameters.bumpMapUv : '', - parameters.normalMapUv ? '#define NORMALMAP_UV ' + parameters.normalMapUv : '', - parameters.displacementMapUv ? '#define DISPLACEMENTMAP_UV ' + parameters.displacementMapUv : '', - - parameters.metalnessMapUv ? '#define METALNESSMAP_UV ' + parameters.metalnessMapUv : '', - parameters.roughnessMapUv ? '#define ROUGHNESSMAP_UV ' + parameters.roughnessMapUv : '', - - parameters.anisotropyMapUv ? '#define ANISOTROPYMAP_UV ' + parameters.anisotropyMapUv : '', - - parameters.clearcoatMapUv ? '#define CLEARCOATMAP_UV ' + parameters.clearcoatMapUv : '', - parameters.clearcoatNormalMapUv ? '#define CLEARCOAT_NORMALMAP_UV ' + parameters.clearcoatNormalMapUv : '', - parameters.clearcoatRoughnessMapUv ? '#define CLEARCOAT_ROUGHNESSMAP_UV ' + parameters.clearcoatRoughnessMapUv : '', - - parameters.iridescenceMapUv ? '#define IRIDESCENCEMAP_UV ' + parameters.iridescenceMapUv : '', - parameters.iridescenceThicknessMapUv ? '#define IRIDESCENCE_THICKNESSMAP_UV ' + parameters.iridescenceThicknessMapUv : '', - - parameters.sheenColorMapUv ? '#define SHEEN_COLORMAP_UV ' + parameters.sheenColorMapUv : '', - parameters.sheenRoughnessMapUv ? '#define SHEEN_ROUGHNESSMAP_UV ' + parameters.sheenRoughnessMapUv : '', - - parameters.specularMapUv ? '#define SPECULARMAP_UV ' + parameters.specularMapUv : '', - parameters.specularColorMapUv ? '#define SPECULAR_COLORMAP_UV ' + parameters.specularColorMapUv : '', - parameters.specularIntensityMapUv ? '#define SPECULAR_INTENSITYMAP_UV ' + parameters.specularIntensityMapUv : '', - - parameters.transmissionMapUv ? '#define TRANSMISSIONMAP_UV ' + parameters.transmissionMapUv : '', - parameters.thicknessMapUv ? '#define THICKNESSMAP_UV ' + parameters.thicknessMapUv : '', - - // - - parameters.vertexTangents ? '#define USE_TANGENT' : '', - parameters.vertexColors ? '#define USE_COLOR' : '', - parameters.vertexAlphas ? '#define USE_COLOR_ALPHA' : '', - parameters.vertexUv1s ? '#define USE_UV1' : '', - parameters.vertexUv2s ? '#define USE_UV2' : '', - parameters.vertexUv3s ? '#define USE_UV3' : '', - - parameters.pointsUvs ? '#define USE_POINTS_UV' : '', - - parameters.flatShading ? '#define FLAT_SHADED' : '', - - parameters.skinning ? '#define USE_SKINNING' : '', - - parameters.morphTargets ? '#define USE_MORPHTARGETS' : '', - parameters.morphNormals && parameters.flatShading === false ? '#define USE_MORPHNORMALS' : '', - ( parameters.morphColors && parameters.isWebGL2 ) ? '#define USE_MORPHCOLORS' : '', - ( parameters.morphTargetsCount > 0 && parameters.isWebGL2 ) ? '#define MORPHTARGETS_TEXTURE' : '', - ( parameters.morphTargetsCount > 0 && parameters.isWebGL2 ) ? '#define MORPHTARGETS_TEXTURE_STRIDE ' + parameters.morphTextureStride : '', - ( parameters.morphTargetsCount > 0 && parameters.isWebGL2 ) ? '#define MORPHTARGETS_COUNT ' + parameters.morphTargetsCount : '', - parameters.doubleSided ? '#define DOUBLE_SIDED' : '', - parameters.flipSided ? '#define FLIP_SIDED' : '', - - parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '', - parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '', - - parameters.sizeAttenuation ? '#define USE_SIZEATTENUATION' : '', - - parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '', - ( parameters.logarithmicDepthBuffer && parameters.rendererExtensionFragDepth ) ? '#define USE_LOGDEPTHBUF_EXT' : '', - - 'uniform mat4 modelMatrix;', - 'uniform mat4 modelViewMatrix;', - 'uniform mat4 projectionMatrix;', - 'uniform mat4 viewMatrix;', - 'uniform mat3 normalMatrix;', - 'uniform vec3 cameraPosition;', - 'uniform bool isOrthographic;', - - '#ifdef USE_INSTANCING', - - ' attribute mat4 instanceMatrix;', - - '#endif', - - '#ifdef USE_INSTANCING_COLOR', - - ' attribute vec3 instanceColor;', - - '#endif', - - 'attribute vec3 position;', - 'attribute vec3 normal;', - 'attribute vec2 uv;', - - '#ifdef USE_UV1', - - ' attribute vec2 uv1;', - - '#endif', - - '#ifdef USE_UV2', - - ' attribute vec2 uv2;', - - '#endif', - - '#ifdef USE_UV3', - - ' attribute vec2 uv3;', - - '#endif', - - '#ifdef USE_TANGENT', - - ' attribute vec4 tangent;', - - '#endif', - - '#if defined( USE_COLOR_ALPHA )', - - ' attribute vec4 color;', - - '#elif defined( USE_COLOR )', - - ' attribute vec3 color;', - - '#endif', - - '#if ( defined( USE_MORPHTARGETS ) && ! defined( MORPHTARGETS_TEXTURE ) )', - - ' attribute vec3 morphTarget0;', - ' attribute vec3 morphTarget1;', - ' attribute vec3 morphTarget2;', - ' attribute vec3 morphTarget3;', - - ' #ifdef USE_MORPHNORMALS', - - ' attribute vec3 morphNormal0;', - ' attribute vec3 morphNormal1;', - ' attribute vec3 morphNormal2;', - ' attribute vec3 morphNormal3;', - - ' #else', - - ' attribute vec3 morphTarget4;', - ' attribute vec3 morphTarget5;', - ' attribute vec3 morphTarget6;', - ' attribute vec3 morphTarget7;', - - ' #endif', - - '#endif', - - '#ifdef USE_SKINNING', - - ' attribute vec4 skinIndex;', - ' attribute vec4 skinWeight;', - - '#endif', - - '\n' - - ].filter( filterEmptyLine ).join( '\n' ); - - prefixFragment = [ - - customExtensions, - - generatePrecision( parameters ), - - '#define SHADER_NAME ' + parameters.shaderName, - - customDefines, - - parameters.useFog && parameters.fog ? '#define USE_FOG' : '', - parameters.useFog && parameters.fogExp2 ? '#define FOG_EXP2' : '', - - parameters.map ? '#define USE_MAP' : '', - parameters.matcap ? '#define USE_MATCAP' : '', - parameters.envMap ? '#define USE_ENVMAP' : '', - parameters.envMap ? '#define ' + envMapTypeDefine : '', - parameters.envMap ? '#define ' + envMapModeDefine : '', - parameters.envMap ? '#define ' + envMapBlendingDefine : '', - envMapCubeUVSize ? '#define CUBEUV_TEXEL_WIDTH ' + envMapCubeUVSize.texelWidth : '', - envMapCubeUVSize ? '#define CUBEUV_TEXEL_HEIGHT ' + envMapCubeUVSize.texelHeight : '', - envMapCubeUVSize ? '#define CUBEUV_MAX_MIP ' + envMapCubeUVSize.maxMip + '.0' : '', - parameters.lightMap ? '#define USE_LIGHTMAP' : '', - parameters.aoMap ? '#define USE_AOMAP' : '', - parameters.bumpMap ? '#define USE_BUMPMAP' : '', - parameters.normalMap ? '#define USE_NORMALMAP' : '', - parameters.normalMapObjectSpace ? '#define USE_NORMALMAP_OBJECTSPACE' : '', - parameters.normalMapTangentSpace ? '#define USE_NORMALMAP_TANGENTSPACE' : '', - parameters.emissiveMap ? '#define USE_EMISSIVEMAP' : '', - - parameters.anisotropy ? '#define USE_ANISOTROPY' : '', - parameters.anisotropyMap ? '#define USE_ANISOTROPYMAP' : '', - - parameters.clearcoat ? '#define USE_CLEARCOAT' : '', - parameters.clearcoatMap ? '#define USE_CLEARCOATMAP' : '', - parameters.clearcoatRoughnessMap ? '#define USE_CLEARCOAT_ROUGHNESSMAP' : '', - parameters.clearcoatNormalMap ? '#define USE_CLEARCOAT_NORMALMAP' : '', - - parameters.iridescence ? '#define USE_IRIDESCENCE' : '', - parameters.iridescenceMap ? '#define USE_IRIDESCENCEMAP' : '', - parameters.iridescenceThicknessMap ? '#define USE_IRIDESCENCE_THICKNESSMAP' : '', - - parameters.specularMap ? '#define USE_SPECULARMAP' : '', - parameters.specularColorMap ? '#define USE_SPECULAR_COLORMAP' : '', - parameters.specularIntensityMap ? '#define USE_SPECULAR_INTENSITYMAP' : '', - - parameters.roughnessMap ? '#define USE_ROUGHNESSMAP' : '', - parameters.metalnessMap ? '#define USE_METALNESSMAP' : '', - - parameters.alphaMap ? '#define USE_ALPHAMAP' : '', - parameters.alphaTest ? '#define USE_ALPHATEST' : '', - - parameters.sheen ? '#define USE_SHEEN' : '', - parameters.sheenColorMap ? '#define USE_SHEEN_COLORMAP' : '', - parameters.sheenRoughnessMap ? '#define USE_SHEEN_ROUGHNESSMAP' : '', - - parameters.transmission ? '#define USE_TRANSMISSION' : '', - parameters.transmissionMap ? '#define USE_TRANSMISSIONMAP' : '', - parameters.thicknessMap ? '#define USE_THICKNESSMAP' : '', - - parameters.vertexTangents ? '#define USE_TANGENT' : '', - parameters.vertexColors || parameters.instancingColor ? '#define USE_COLOR' : '', - parameters.vertexAlphas ? '#define USE_COLOR_ALPHA' : '', - parameters.vertexUv1s ? '#define USE_UV1' : '', - parameters.vertexUv2s ? '#define USE_UV2' : '', - parameters.vertexUv3s ? '#define USE_UV3' : '', - - parameters.pointsUvs ? '#define USE_POINTS_UV' : '', - - parameters.gradientMap ? '#define USE_GRADIENTMAP' : '', - - parameters.flatShading ? '#define FLAT_SHADED' : '', - - parameters.doubleSided ? '#define DOUBLE_SIDED' : '', - parameters.flipSided ? '#define FLIP_SIDED' : '', - - parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '', - parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '', - - parameters.premultipliedAlpha ? '#define PREMULTIPLIED_ALPHA' : '', - - parameters.useLegacyLights ? '#define LEGACY_LIGHTS' : '', - - parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '', - ( parameters.logarithmicDepthBuffer && parameters.rendererExtensionFragDepth ) ? '#define USE_LOGDEPTHBUF_EXT' : '', - - 'uniform mat4 viewMatrix;', - 'uniform vec3 cameraPosition;', - 'uniform bool isOrthographic;', - - ( parameters.toneMapping !== NoToneMapping ) ? '#define TONE_MAPPING' : '', - ( parameters.toneMapping !== NoToneMapping ) ? ShaderChunk[ 'tonemapping_pars_fragment' ] : '', // this code is required here because it is used by the toneMapping() function defined below - ( parameters.toneMapping !== NoToneMapping ) ? getToneMappingFunction( 'toneMapping', parameters.toneMapping ) : '', - - parameters.dithering ? '#define DITHERING' : '', - parameters.opaque ? '#define OPAQUE' : '', - - ShaderChunk[ 'encodings_pars_fragment' ], // this code is required here because it is used by the various encoding/decoding function defined below - getTexelEncodingFunction( 'linearToOutputTexel', parameters.outputColorSpace ), - - parameters.useDepthPacking ? '#define DEPTH_PACKING ' + parameters.depthPacking : '', - - '\n' - - ].filter( filterEmptyLine ).join( '\n' ); - - } - - vertexShader = resolveIncludes( vertexShader ); - vertexShader = replaceLightNums( vertexShader, parameters ); - vertexShader = replaceClippingPlaneNums( vertexShader, parameters ); - - fragmentShader = resolveIncludes( fragmentShader ); - fragmentShader = replaceLightNums( fragmentShader, parameters ); - fragmentShader = replaceClippingPlaneNums( fragmentShader, parameters ); - - vertexShader = unrollLoops( vertexShader ); - fragmentShader = unrollLoops( fragmentShader ); - - if ( parameters.isWebGL2 && parameters.isRawShaderMaterial !== true ) { - - // GLSL 3.0 conversion for built-in materials and ShaderMaterial - - versionString = '#version 300 es\n'; - - prefixVertex = [ - 'precision mediump sampler2DArray;', - '#define attribute in', - '#define varying out', - '#define texture2D texture' - ].join( '\n' ) + '\n' + prefixVertex; - - prefixFragment = [ - '#define varying in', - ( parameters.glslVersion === GLSL3 ) ? '' : 'layout(location = 0) out highp vec4 pc_fragColor;', - ( parameters.glslVersion === GLSL3 ) ? '' : '#define gl_FragColor pc_fragColor', - '#define gl_FragDepthEXT gl_FragDepth', - '#define texture2D texture', - '#define textureCube texture', - '#define texture2DProj textureProj', - '#define texture2DLodEXT textureLod', - '#define texture2DProjLodEXT textureProjLod', - '#define textureCubeLodEXT textureLod', - '#define texture2DGradEXT textureGrad', - '#define texture2DProjGradEXT textureProjGrad', - '#define textureCubeGradEXT textureGrad' - ].join( '\n' ) + '\n' + prefixFragment; - - } - - const vertexGlsl = versionString + prefixVertex + vertexShader; - const fragmentGlsl = versionString + prefixFragment + fragmentShader; - - // console.log( '*VERTEX*', vertexGlsl ); - // console.log( '*FRAGMENT*', fragmentGlsl ); - - const glVertexShader = WebGLShader( gl, gl.VERTEX_SHADER, vertexGlsl ); - const glFragmentShader = WebGLShader( gl, gl.FRAGMENT_SHADER, fragmentGlsl ); - - gl.attachShader( program, glVertexShader ); - gl.attachShader( program, glFragmentShader ); - - // Force a particular attribute to index 0. - - if ( parameters.index0AttributeName !== undefined ) { - - gl.bindAttribLocation( program, 0, parameters.index0AttributeName ); - - } else if ( parameters.morphTargets === true ) { - - // programs with morphTargets displace position out of attribute 0 - gl.bindAttribLocation( program, 0, 'position' ); - - } - - gl.linkProgram( program ); - - // check for link errors - if ( renderer.debug.checkShaderErrors ) { - - const programLog = gl.getProgramInfoLog( program ).trim(); - const vertexLog = gl.getShaderInfoLog( glVertexShader ).trim(); - const fragmentLog = gl.getShaderInfoLog( glFragmentShader ).trim(); - - let runnable = true; - let haveDiagnostics = true; - - if ( gl.getProgramParameter( program, gl.LINK_STATUS ) === false ) { - - runnable = false; - - if ( typeof renderer.debug.onShaderError === 'function' ) { - - renderer.debug.onShaderError( gl, program, glVertexShader, glFragmentShader ); - - } else { - - // default error reporting - - const vertexErrors = getShaderErrors( gl, glVertexShader, 'vertex' ); - const fragmentErrors = getShaderErrors( gl, glFragmentShader, 'fragment' ); - - console.error( - 'THREE.WebGLProgram: Shader Error ' + gl.getError() + ' - ' + - 'VALIDATE_STATUS ' + gl.getProgramParameter( program, gl.VALIDATE_STATUS ) + '\n\n' + - 'Program Info Log: ' + programLog + '\n' + - vertexErrors + '\n' + - fragmentErrors - ); - - } - - } else if ( programLog !== '' ) { - - console.warn( 'THREE.WebGLProgram: Program Info Log:', programLog ); - - } else if ( vertexLog === '' || fragmentLog === '' ) { - - haveDiagnostics = false; - - } - - if ( haveDiagnostics ) { - - this.diagnostics = { - - runnable: runnable, - - programLog: programLog, - - vertexShader: { - - log: vertexLog, - prefix: prefixVertex - - }, - - fragmentShader: { - - log: fragmentLog, - prefix: prefixFragment - - } - - }; - - } - - } - - // Clean up - - // Crashes in iOS9 and iOS10. #18402 - // gl.detachShader( program, glVertexShader ); - // gl.detachShader( program, glFragmentShader ); - - gl.deleteShader( glVertexShader ); - gl.deleteShader( glFragmentShader ); - - // set up caching for uniform locations - - let cachedUniforms; - - this.getUniforms = function () { - - if ( cachedUniforms === undefined ) { - - cachedUniforms = new WebGLUniforms( gl, program ); - - } - - return cachedUniforms; - - }; - - // set up caching for attribute locations - - let cachedAttributes; - - this.getAttributes = function () { - - if ( cachedAttributes === undefined ) { - - cachedAttributes = fetchAttributeLocations( gl, program ); - - } - - return cachedAttributes; - - }; - - // free resource - - this.destroy = function () { - - bindingStates.releaseStatesOfProgram( this ); - - gl.deleteProgram( program ); - this.program = undefined; - - }; - - // - - this.name = parameters.shaderName; - this.id = programIdCount ++; - this.cacheKey = cacheKey; - this.usedTimes = 1; - this.program = program; - this.vertexShader = glVertexShader; - this.fragmentShader = glFragmentShader; - - return this; - -} - -let _id = 0; - -class WebGLShaderCache { - - constructor() { - - this.shaderCache = new Map(); - this.materialCache = new Map(); - - } - - update( material ) { - - const vertexShader = material.vertexShader; - const fragmentShader = material.fragmentShader; - - const vertexShaderStage = this._getShaderStage( vertexShader ); - const fragmentShaderStage = this._getShaderStage( fragmentShader ); - - const materialShaders = this._getShaderCacheForMaterial( material ); - - if ( materialShaders.has( vertexShaderStage ) === false ) { - - materialShaders.add( vertexShaderStage ); - vertexShaderStage.usedTimes ++; - - } - - if ( materialShaders.has( fragmentShaderStage ) === false ) { - - materialShaders.add( fragmentShaderStage ); - fragmentShaderStage.usedTimes ++; - - } - - return this; - - } - - remove( material ) { - - const materialShaders = this.materialCache.get( material ); - - for ( const shaderStage of materialShaders ) { - - shaderStage.usedTimes --; - - if ( shaderStage.usedTimes === 0 ) this.shaderCache.delete( shaderStage.code ); - - } - - this.materialCache.delete( material ); - - return this; - - } - - getVertexShaderID( material ) { - - return this._getShaderStage( material.vertexShader ).id; - - } - - getFragmentShaderID( material ) { - - return this._getShaderStage( material.fragmentShader ).id; - - } - - dispose() { - - this.shaderCache.clear(); - this.materialCache.clear(); - - } - - _getShaderCacheForMaterial( material ) { - - const cache = this.materialCache; - let set = cache.get( material ); - - if ( set === undefined ) { - - set = new Set(); - cache.set( material, set ); - - } - - return set; - - } - - _getShaderStage( code ) { - - const cache = this.shaderCache; - let stage = cache.get( code ); - - if ( stage === undefined ) { - - stage = new WebGLShaderStage( code ); - cache.set( code, stage ); - - } - - return stage; - - } - -} - -class WebGLShaderStage { - - constructor( code ) { - - this.id = _id ++; - - this.code = code; - this.usedTimes = 0; - - } - -} - -function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities, bindingStates, clipping ) { - - const _programLayers = new Layers(); - const _customShaders = new WebGLShaderCache(); - const programs = []; - - const IS_WEBGL2 = capabilities.isWebGL2; - const logarithmicDepthBuffer = capabilities.logarithmicDepthBuffer; - const SUPPORTS_VERTEX_TEXTURES = capabilities.vertexTextures; - - let precision = capabilities.precision; - - const shaderIDs = { - MeshDepthMaterial: 'depth', - MeshDistanceMaterial: 'distanceRGBA', - MeshNormalMaterial: 'normal', - MeshBasicMaterial: 'basic', - MeshLambertMaterial: 'lambert', - MeshPhongMaterial: 'phong', - MeshToonMaterial: 'toon', - MeshStandardMaterial: 'physical', - MeshPhysicalMaterial: 'physical', - MeshMatcapMaterial: 'matcap', - LineBasicMaterial: 'basic', - LineDashedMaterial: 'dashed', - PointsMaterial: 'points', - ShadowMaterial: 'shadow', - SpriteMaterial: 'sprite' - }; - - function getChannel( value ) { - - if ( value === 0 ) return 'uv'; - - return `uv${ value }`; - - } - - function getParameters( material, lights, shadows, scene, object ) { - - const fog = scene.fog; - const geometry = object.geometry; - const environment = material.isMeshStandardMaterial ? scene.environment : null; - - const envMap = ( material.isMeshStandardMaterial ? cubeuvmaps : cubemaps ).get( material.envMap || environment ); - const envMapCubeUVHeight = ( !! envMap ) && ( envMap.mapping === CubeUVReflectionMapping ) ? envMap.image.height : null; - - const shaderID = shaderIDs[ material.type ]; - - // heuristics to create shader parameters according to lights in the scene - // (not to blow over maxLights budget) - - if ( material.precision !== null ) { - - precision = capabilities.getMaxPrecision( material.precision ); - - if ( precision !== material.precision ) { - - console.warn( 'THREE.WebGLProgram.getParameters:', material.precision, 'not supported, using', precision, 'instead.' ); - - } - - } - - // - - const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color; - const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0; - - let morphTextureStride = 0; - - if ( geometry.morphAttributes.position !== undefined ) morphTextureStride = 1; - if ( geometry.morphAttributes.normal !== undefined ) morphTextureStride = 2; - if ( geometry.morphAttributes.color !== undefined ) morphTextureStride = 3; - - // - - let vertexShader, fragmentShader; - let customVertexShaderID, customFragmentShaderID; - - if ( shaderID ) { - - const shader = ShaderLib[ shaderID ]; - - vertexShader = shader.vertexShader; - fragmentShader = shader.fragmentShader; - - } else { - - vertexShader = material.vertexShader; - fragmentShader = material.fragmentShader; - - _customShaders.update( material ); - - customVertexShaderID = _customShaders.getVertexShaderID( material ); - customFragmentShaderID = _customShaders.getFragmentShaderID( material ); - - } - - const currentRenderTarget = renderer.getRenderTarget(); - - const IS_INSTANCEDMESH = object.isInstancedMesh === true; - - const HAS_MAP = !! material.map; - const HAS_MATCAP = !! material.matcap; - const HAS_ENVMAP = !! envMap; - const HAS_AOMAP = !! material.aoMap; - const HAS_LIGHTMAP = !! material.lightMap; - const HAS_BUMPMAP = !! material.bumpMap; - const HAS_NORMALMAP = !! material.normalMap; - const HAS_DISPLACEMENTMAP = !! material.displacementMap; - const HAS_EMISSIVEMAP = !! material.emissiveMap; - - const HAS_METALNESSMAP = !! material.metalnessMap; - const HAS_ROUGHNESSMAP = !! material.roughnessMap; - - const HAS_ANISOTROPY = material.anisotropy > 0; - const HAS_CLEARCOAT = material.clearcoat > 0; - const HAS_IRIDESCENCE = material.iridescence > 0; - const HAS_SHEEN = material.sheen > 0; - const HAS_TRANSMISSION = material.transmission > 0; - - const HAS_ANISOTROPYMAP = HAS_ANISOTROPY && !! material.anisotropyMap; - - const HAS_CLEARCOATMAP = HAS_CLEARCOAT && !! material.clearcoatMap; - const HAS_CLEARCOAT_NORMALMAP = HAS_CLEARCOAT && !! material.clearcoatNormalMap; - const HAS_CLEARCOAT_ROUGHNESSMAP = HAS_CLEARCOAT && !! material.clearcoatRoughnessMap; - - const HAS_IRIDESCENCEMAP = HAS_IRIDESCENCE && !! material.iridescenceMap; - const HAS_IRIDESCENCE_THICKNESSMAP = HAS_IRIDESCENCE && !! material.iridescenceThicknessMap; - - const HAS_SHEEN_COLORMAP = HAS_SHEEN && !! material.sheenColorMap; - const HAS_SHEEN_ROUGHNESSMAP = HAS_SHEEN && !! material.sheenRoughnessMap; - - const HAS_SPECULARMAP = !! material.specularMap; - const HAS_SPECULAR_COLORMAP = !! material.specularColorMap; - const HAS_SPECULAR_INTENSITYMAP = !! material.specularIntensityMap; - - const HAS_TRANSMISSIONMAP = HAS_TRANSMISSION && !! material.transmissionMap; - const HAS_THICKNESSMAP = HAS_TRANSMISSION && !! material.thicknessMap; - - const HAS_GRADIENTMAP = !! material.gradientMap; - - const HAS_ALPHAMAP = !! material.alphaMap; - - const HAS_ALPHATEST = material.alphaTest > 0; - - const HAS_EXTENSIONS = !! material.extensions; - - const HAS_ATTRIBUTE_UV1 = !! geometry.attributes.uv1; - const HAS_ATTRIBUTE_UV2 = !! geometry.attributes.uv2; - const HAS_ATTRIBUTE_UV3 = !! geometry.attributes.uv3; - - const parameters = { - - isWebGL2: IS_WEBGL2, - - shaderID: shaderID, - shaderName: material.type, - - vertexShader: vertexShader, - fragmentShader: fragmentShader, - defines: material.defines, - - customVertexShaderID: customVertexShaderID, - customFragmentShaderID: customFragmentShaderID, - - isRawShaderMaterial: material.isRawShaderMaterial === true, - glslVersion: material.glslVersion, - - precision: precision, - - instancing: IS_INSTANCEDMESH, - instancingColor: IS_INSTANCEDMESH && object.instanceColor !== null, - - supportsVertexTextures: SUPPORTS_VERTEX_TEXTURES, - outputColorSpace: ( currentRenderTarget === null ) ? renderer.outputColorSpace : ( currentRenderTarget.isXRRenderTarget === true ? currentRenderTarget.texture.colorSpace : LinearSRGBColorSpace ), - - map: HAS_MAP, - matcap: HAS_MATCAP, - envMap: HAS_ENVMAP, - envMapMode: HAS_ENVMAP && envMap.mapping, - envMapCubeUVHeight: envMapCubeUVHeight, - aoMap: HAS_AOMAP, - lightMap: HAS_LIGHTMAP, - bumpMap: HAS_BUMPMAP, - normalMap: HAS_NORMALMAP, - displacementMap: SUPPORTS_VERTEX_TEXTURES && HAS_DISPLACEMENTMAP, - emissiveMap: HAS_EMISSIVEMAP, - - normalMapObjectSpace: HAS_NORMALMAP && material.normalMapType === ObjectSpaceNormalMap, - normalMapTangentSpace: HAS_NORMALMAP && material.normalMapType === TangentSpaceNormalMap, - - metalnessMap: HAS_METALNESSMAP, - roughnessMap: HAS_ROUGHNESSMAP, - - anisotropy: HAS_ANISOTROPY, - anisotropyMap: HAS_ANISOTROPYMAP, - - clearcoat: HAS_CLEARCOAT, - clearcoatMap: HAS_CLEARCOATMAP, - clearcoatNormalMap: HAS_CLEARCOAT_NORMALMAP, - clearcoatRoughnessMap: HAS_CLEARCOAT_ROUGHNESSMAP, - - iridescence: HAS_IRIDESCENCE, - iridescenceMap: HAS_IRIDESCENCEMAP, - iridescenceThicknessMap: HAS_IRIDESCENCE_THICKNESSMAP, - - sheen: HAS_SHEEN, - sheenColorMap: HAS_SHEEN_COLORMAP, - sheenRoughnessMap: HAS_SHEEN_ROUGHNESSMAP, - - specularMap: HAS_SPECULARMAP, - specularColorMap: HAS_SPECULAR_COLORMAP, - specularIntensityMap: HAS_SPECULAR_INTENSITYMAP, - - transmission: HAS_TRANSMISSION, - transmissionMap: HAS_TRANSMISSIONMAP, - thicknessMap: HAS_THICKNESSMAP, - - gradientMap: HAS_GRADIENTMAP, - - opaque: material.transparent === false && material.blending === NormalBlending, - - alphaMap: HAS_ALPHAMAP, - alphaTest: HAS_ALPHATEST, - - combine: material.combine, - - // - - mapUv: HAS_MAP && getChannel( material.map.channel ), - aoMapUv: HAS_AOMAP && getChannel( material.aoMap.channel ), - lightMapUv: HAS_LIGHTMAP && getChannel( material.lightMap.channel ), - bumpMapUv: HAS_BUMPMAP && getChannel( material.bumpMap.channel ), - normalMapUv: HAS_NORMALMAP && getChannel( material.normalMap.channel ), - displacementMapUv: HAS_DISPLACEMENTMAP && getChannel( material.displacementMap.channel ), - emissiveMapUv: HAS_EMISSIVEMAP && getChannel( material.emissiveMap.channel ), - - metalnessMapUv: HAS_METALNESSMAP && getChannel( material.metalnessMap.channel ), - roughnessMapUv: HAS_ROUGHNESSMAP && getChannel( material.roughnessMap.channel ), - - anisotropyMapUv: HAS_ANISOTROPYMAP && getChannel( material.anisotropyMap.channel ), - - clearcoatMapUv: HAS_CLEARCOATMAP && getChannel( material.clearcoatMap.channel ), - clearcoatNormalMapUv: HAS_CLEARCOAT_NORMALMAP && getChannel( material.clearcoatNormalMap.channel ), - clearcoatRoughnessMapUv: HAS_CLEARCOAT_ROUGHNESSMAP && getChannel( material.clearcoatRoughnessMap.channel ), - - iridescenceMapUv: HAS_IRIDESCENCEMAP && getChannel( material.iridescenceMap.channel ), - iridescenceThicknessMapUv: HAS_IRIDESCENCE_THICKNESSMAP && getChannel( material.iridescenceThicknessMap.channel ), - - sheenColorMapUv: HAS_SHEEN_COLORMAP && getChannel( material.sheenColorMap.channel ), - sheenRoughnessMapUv: HAS_SHEEN_ROUGHNESSMAP && getChannel( material.sheenRoughnessMap.channel ), - - specularMapUv: HAS_SPECULARMAP && getChannel( material.specularMap.channel ), - specularColorMapUv: HAS_SPECULAR_COLORMAP && getChannel( material.specularColorMap.channel ), - specularIntensityMapUv: HAS_SPECULAR_INTENSITYMAP && getChannel( material.specularIntensityMap.channel ), - - transmissionMapUv: HAS_TRANSMISSIONMAP && getChannel( material.transmissionMap.channel ), - thicknessMapUv: HAS_THICKNESSMAP && getChannel( material.thicknessMap.channel ), - - alphaMapUv: HAS_ALPHAMAP && getChannel( material.alphaMap.channel ), - - // - - vertexTangents: !! geometry.attributes.tangent && ( HAS_NORMALMAP || HAS_ANISOTROPY ), - vertexColors: material.vertexColors, - vertexAlphas: material.vertexColors === true && !! geometry.attributes.color && geometry.attributes.color.itemSize === 4, - vertexUv1s: HAS_ATTRIBUTE_UV1, - vertexUv2s: HAS_ATTRIBUTE_UV2, - vertexUv3s: HAS_ATTRIBUTE_UV3, - - pointsUvs: object.isPoints === true && !! geometry.attributes.uv && ( HAS_MAP || HAS_ALPHAMAP ), - - fog: !! fog, - useFog: material.fog === true, - fogExp2: ( fog && fog.isFogExp2 ), - - flatShading: material.flatShading === true, - - sizeAttenuation: material.sizeAttenuation === true, - logarithmicDepthBuffer: logarithmicDepthBuffer, - - skinning: object.isSkinnedMesh === true, - - morphTargets: geometry.morphAttributes.position !== undefined, - morphNormals: geometry.morphAttributes.normal !== undefined, - morphColors: geometry.morphAttributes.color !== undefined, - morphTargetsCount: morphTargetsCount, - morphTextureStride: morphTextureStride, - - numDirLights: lights.directional.length, - numPointLights: lights.point.length, - numSpotLights: lights.spot.length, - numSpotLightMaps: lights.spotLightMap.length, - numRectAreaLights: lights.rectArea.length, - numHemiLights: lights.hemi.length, - - numDirLightShadows: lights.directionalShadowMap.length, - numPointLightShadows: lights.pointShadowMap.length, - numSpotLightShadows: lights.spotShadowMap.length, - numSpotLightShadowsWithMaps: lights.numSpotLightShadowsWithMaps, - - numClippingPlanes: clipping.numPlanes, - numClipIntersection: clipping.numIntersection, - - dithering: material.dithering, - - shadowMapEnabled: renderer.shadowMap.enabled && shadows.length > 0, - shadowMapType: renderer.shadowMap.type, - - toneMapping: material.toneMapped ? renderer.toneMapping : NoToneMapping, - useLegacyLights: renderer.useLegacyLights, - - premultipliedAlpha: material.premultipliedAlpha, - - doubleSided: material.side === DoubleSide, - flipSided: material.side === BackSide, - - useDepthPacking: material.depthPacking >= 0, - depthPacking: material.depthPacking || 0, - - index0AttributeName: material.index0AttributeName, - - extensionDerivatives: HAS_EXTENSIONS && material.extensions.derivatives === true, - extensionFragDepth: HAS_EXTENSIONS && material.extensions.fragDepth === true, - extensionDrawBuffers: HAS_EXTENSIONS && material.extensions.drawBuffers === true, - extensionShaderTextureLOD: HAS_EXTENSIONS && material.extensions.shaderTextureLOD === true, - - rendererExtensionFragDepth: IS_WEBGL2 || extensions.has( 'EXT_frag_depth' ), - rendererExtensionDrawBuffers: IS_WEBGL2 || extensions.has( 'WEBGL_draw_buffers' ), - rendererExtensionShaderTextureLod: IS_WEBGL2 || extensions.has( 'EXT_shader_texture_lod' ), - - customProgramCacheKey: material.customProgramCacheKey() - - }; - - return parameters; - - } - - function getProgramCacheKey( parameters ) { - - const array = []; - - if ( parameters.shaderID ) { - - array.push( parameters.shaderID ); - - } else { - - array.push( parameters.customVertexShaderID ); - array.push( parameters.customFragmentShaderID ); - - } - - if ( parameters.defines !== undefined ) { - - for ( const name in parameters.defines ) { - - array.push( name ); - array.push( parameters.defines[ name ] ); - - } - - } - - if ( parameters.isRawShaderMaterial === false ) { - - getProgramCacheKeyParameters( array, parameters ); - getProgramCacheKeyBooleans( array, parameters ); - array.push( renderer.outputColorSpace ); - - } - - array.push( parameters.customProgramCacheKey ); - - return array.join(); - - } - - function getProgramCacheKeyParameters( array, parameters ) { - - array.push( parameters.precision ); - array.push( parameters.outputColorSpace ); - array.push( parameters.envMapMode ); - array.push( parameters.envMapCubeUVHeight ); - array.push( parameters.mapUv ); - array.push( parameters.alphaMapUv ); - array.push( parameters.lightMapUv ); - array.push( parameters.aoMapUv ); - array.push( parameters.bumpMapUv ); - array.push( parameters.normalMapUv ); - array.push( parameters.displacementMapUv ); - array.push( parameters.emissiveMapUv ); - array.push( parameters.metalnessMapUv ); - array.push( parameters.roughnessMapUv ); - array.push( parameters.anisotropyMapUv ); - array.push( parameters.clearcoatMapUv ); - array.push( parameters.clearcoatNormalMapUv ); - array.push( parameters.clearcoatRoughnessMapUv ); - array.push( parameters.iridescenceMapUv ); - array.push( parameters.iridescenceThicknessMapUv ); - array.push( parameters.sheenColorMapUv ); - array.push( parameters.sheenRoughnessMapUv ); - array.push( parameters.specularMapUv ); - array.push( parameters.specularColorMapUv ); - array.push( parameters.specularIntensityMapUv ); - array.push( parameters.transmissionMapUv ); - array.push( parameters.thicknessMapUv ); - array.push( parameters.combine ); - array.push( parameters.fogExp2 ); - array.push( parameters.sizeAttenuation ); - array.push( parameters.morphTargetsCount ); - array.push( parameters.morphAttributeCount ); - array.push( parameters.numDirLights ); - array.push( parameters.numPointLights ); - array.push( parameters.numSpotLights ); - array.push( parameters.numSpotLightMaps ); - array.push( parameters.numHemiLights ); - array.push( parameters.numRectAreaLights ); - array.push( parameters.numDirLightShadows ); - array.push( parameters.numPointLightShadows ); - array.push( parameters.numSpotLightShadows ); - array.push( parameters.numSpotLightShadowsWithMaps ); - array.push( parameters.shadowMapType ); - array.push( parameters.toneMapping ); - array.push( parameters.numClippingPlanes ); - array.push( parameters.numClipIntersection ); - array.push( parameters.depthPacking ); - - } - - function getProgramCacheKeyBooleans( array, parameters ) { - - _programLayers.disableAll(); - - if ( parameters.isWebGL2 ) - _programLayers.enable( 0 ); - if ( parameters.supportsVertexTextures ) - _programLayers.enable( 1 ); - if ( parameters.instancing ) - _programLayers.enable( 2 ); - if ( parameters.instancingColor ) - _programLayers.enable( 3 ); - if ( parameters.matcap ) - _programLayers.enable( 4 ); - if ( parameters.envMap ) - _programLayers.enable( 5 ); - if ( parameters.normalMapObjectSpace ) - _programLayers.enable( 6 ); - if ( parameters.normalMapTangentSpace ) - _programLayers.enable( 7 ); - if ( parameters.clearcoat ) - _programLayers.enable( 8 ); - if ( parameters.iridescence ) - _programLayers.enable( 9 ); - if ( parameters.alphaTest ) - _programLayers.enable( 10 ); - if ( parameters.vertexColors ) - _programLayers.enable( 11 ); - if ( parameters.vertexAlphas ) - _programLayers.enable( 12 ); - if ( parameters.vertexUv1s ) - _programLayers.enable( 13 ); - if ( parameters.vertexUv2s ) - _programLayers.enable( 14 ); - if ( parameters.vertexUv3s ) - _programLayers.enable( 15 ); - if ( parameters.vertexTangents ) - _programLayers.enable( 16 ); - if ( parameters.anisotropy ) - _programLayers.enable( 17 ); - - array.push( _programLayers.mask ); - _programLayers.disableAll(); - - if ( parameters.fog ) - _programLayers.enable( 0 ); - if ( parameters.useFog ) - _programLayers.enable( 1 ); - if ( parameters.flatShading ) - _programLayers.enable( 2 ); - if ( parameters.logarithmicDepthBuffer ) - _programLayers.enable( 3 ); - if ( parameters.skinning ) - _programLayers.enable( 4 ); - if ( parameters.morphTargets ) - _programLayers.enable( 5 ); - if ( parameters.morphNormals ) - _programLayers.enable( 6 ); - if ( parameters.morphColors ) - _programLayers.enable( 7 ); - if ( parameters.premultipliedAlpha ) - _programLayers.enable( 8 ); - if ( parameters.shadowMapEnabled ) - _programLayers.enable( 9 ); - if ( parameters.useLegacyLights ) - _programLayers.enable( 10 ); - if ( parameters.doubleSided ) - _programLayers.enable( 11 ); - if ( parameters.flipSided ) - _programLayers.enable( 12 ); - if ( parameters.useDepthPacking ) - _programLayers.enable( 13 ); - if ( parameters.dithering ) - _programLayers.enable( 14 ); - if ( parameters.transmission ) - _programLayers.enable( 15 ); - if ( parameters.sheen ) - _programLayers.enable( 16 ); - if ( parameters.opaque ) - _programLayers.enable( 17 ); - if ( parameters.pointsUvs ) - _programLayers.enable( 18 ); - - array.push( _programLayers.mask ); - - } - - function getUniforms( material ) { - - const shaderID = shaderIDs[ material.type ]; - let uniforms; - - if ( shaderID ) { - - const shader = ShaderLib[ shaderID ]; - uniforms = UniformsUtils.clone( shader.uniforms ); - - } else { - - uniforms = material.uniforms; - - } - - return uniforms; - - } - - function acquireProgram( parameters, cacheKey ) { - - let program; - - // Check if code has been already compiled - for ( let p = 0, pl = programs.length; p < pl; p ++ ) { - - const preexistingProgram = programs[ p ]; - - if ( preexistingProgram.cacheKey === cacheKey ) { - - program = preexistingProgram; - ++ program.usedTimes; - - break; - - } - - } - - if ( program === undefined ) { - - program = new WebGLProgram( renderer, cacheKey, parameters, bindingStates ); - programs.push( program ); - - } - - return program; - - } - - function releaseProgram( program ) { - - if ( -- program.usedTimes === 0 ) { - - // Remove from unordered set - const i = programs.indexOf( program ); - programs[ i ] = programs[ programs.length - 1 ]; - programs.pop(); - - // Free WebGL resources - program.destroy(); - - } - - } - - function releaseShaderCache( material ) { - - _customShaders.remove( material ); - - } - - function dispose() { - - _customShaders.dispose(); - - } - - return { - getParameters: getParameters, - getProgramCacheKey: getProgramCacheKey, - getUniforms: getUniforms, - acquireProgram: acquireProgram, - releaseProgram: releaseProgram, - releaseShaderCache: releaseShaderCache, - // Exposed for resource monitoring & error feedback via renderer.info: - programs: programs, - dispose: dispose - }; - -} - -function WebGLProperties() { - - let properties = new WeakMap(); - - function get( object ) { - - let map = properties.get( object ); - - if ( map === undefined ) { - - map = {}; - properties.set( object, map ); - - } - - return map; - - } - - function remove( object ) { - - properties.delete( object ); - - } - - function update( object, key, value ) { - - properties.get( object )[ key ] = value; - - } - - function dispose() { - - properties = new WeakMap(); - - } - - return { - get: get, - remove: remove, - update: update, - dispose: dispose - }; - -} - -function painterSortStable( a, b ) { - - if ( a.groupOrder !== b.groupOrder ) { - - return a.groupOrder - b.groupOrder; - - } else if ( a.renderOrder !== b.renderOrder ) { - - return a.renderOrder - b.renderOrder; - - } else if ( a.material.id !== b.material.id ) { - - return a.material.id - b.material.id; - - } else if ( a.z !== b.z ) { - - return a.z - b.z; - - } else { - - return a.id - b.id; - - } - -} - -function reversePainterSortStable( a, b ) { - - if ( a.groupOrder !== b.groupOrder ) { - - return a.groupOrder - b.groupOrder; - - } else if ( a.renderOrder !== b.renderOrder ) { - - return a.renderOrder - b.renderOrder; - - } else if ( a.z !== b.z ) { - - return b.z - a.z; - - } else { - - return a.id - b.id; - - } - -} - - -function WebGLRenderList() { - - const renderItems = []; - let renderItemsIndex = 0; - - const opaque = []; - const transmissive = []; - const transparent = []; - - function init() { - - renderItemsIndex = 0; - - opaque.length = 0; - transmissive.length = 0; - transparent.length = 0; - - } - - function getNextRenderItem( object, geometry, material, groupOrder, z, group ) { - - let renderItem = renderItems[ renderItemsIndex ]; - - if ( renderItem === undefined ) { - - renderItem = { - id: object.id, - object: object, - geometry: geometry, - material: material, - groupOrder: groupOrder, - renderOrder: object.renderOrder, - z: z, - group: group - }; - - renderItems[ renderItemsIndex ] = renderItem; - - } else { - - renderItem.id = object.id; - renderItem.object = object; - renderItem.geometry = geometry; - renderItem.material = material; - renderItem.groupOrder = groupOrder; - renderItem.renderOrder = object.renderOrder; - renderItem.z = z; - renderItem.group = group; - - } - - renderItemsIndex ++; - - return renderItem; - - } - - function push( object, geometry, material, groupOrder, z, group ) { - - const renderItem = getNextRenderItem( object, geometry, material, groupOrder, z, group ); - - if ( material.transmission > 0.0 ) { - - transmissive.push( renderItem ); - - } else if ( material.transparent === true ) { - - transparent.push( renderItem ); - - } else { - - opaque.push( renderItem ); - - } - - } - - function unshift( object, geometry, material, groupOrder, z, group ) { - - const renderItem = getNextRenderItem( object, geometry, material, groupOrder, z, group ); - - if ( material.transmission > 0.0 ) { - - transmissive.unshift( renderItem ); - - } else if ( material.transparent === true ) { - - transparent.unshift( renderItem ); - - } else { - - opaque.unshift( renderItem ); - - } - - } - - function sort( customOpaqueSort, customTransparentSort ) { - - if ( opaque.length > 1 ) opaque.sort( customOpaqueSort || painterSortStable ); - if ( transmissive.length > 1 ) transmissive.sort( customTransparentSort || reversePainterSortStable ); - if ( transparent.length > 1 ) transparent.sort( customTransparentSort || reversePainterSortStable ); - - } - - function finish() { - - // Clear references from inactive renderItems in the list - - for ( let i = renderItemsIndex, il = renderItems.length; i < il; i ++ ) { - - const renderItem = renderItems[ i ]; - - if ( renderItem.id === null ) break; - - renderItem.id = null; - renderItem.object = null; - renderItem.geometry = null; - renderItem.material = null; - renderItem.group = null; - - } - - } - - return { - - opaque: opaque, - transmissive: transmissive, - transparent: transparent, - - init: init, - push: push, - unshift: unshift, - finish: finish, - - sort: sort - }; - -} - -function WebGLRenderLists() { - - let lists = new WeakMap(); - - function get( scene, renderCallDepth ) { - - const listArray = lists.get( scene ); - let list; - - if ( listArray === undefined ) { - - list = new WebGLRenderList(); - lists.set( scene, [ list ] ); - - } else { - - if ( renderCallDepth >= listArray.length ) { - - list = new WebGLRenderList(); - listArray.push( list ); - - } else { - - list = listArray[ renderCallDepth ]; - - } - - } - - return list; - - } - - function dispose() { - - lists = new WeakMap(); - - } - - return { - get: get, - dispose: dispose - }; - -} - -function UniformsCache() { - - const lights = {}; - - return { - - get: function ( light ) { - - if ( lights[ light.id ] !== undefined ) { - - return lights[ light.id ]; - - } - - let uniforms; - - switch ( light.type ) { - - case 'DirectionalLight': - uniforms = { - direction: new Vector3(), - color: new Color() - }; - break; - - case 'SpotLight': - uniforms = { - position: new Vector3(), - direction: new Vector3(), - color: new Color(), - distance: 0, - coneCos: 0, - penumbraCos: 0, - decay: 0 - }; - break; - - case 'PointLight': - uniforms = { - position: new Vector3(), - color: new Color(), - distance: 0, - decay: 0 - }; - break; - - case 'HemisphereLight': - uniforms = { - direction: new Vector3(), - skyColor: new Color(), - groundColor: new Color() - }; - break; - - case 'RectAreaLight': - uniforms = { - color: new Color(), - position: new Vector3(), - halfWidth: new Vector3(), - halfHeight: new Vector3() - }; - break; - - } - - lights[ light.id ] = uniforms; - - return uniforms; - - } - - }; - -} - -function ShadowUniformsCache() { - - const lights = {}; - - return { - - get: function ( light ) { - - if ( lights[ light.id ] !== undefined ) { - - return lights[ light.id ]; - - } - - let uniforms; - - switch ( light.type ) { - - case 'DirectionalLight': - uniforms = { - shadowBias: 0, - shadowNormalBias: 0, - shadowRadius: 1, - shadowMapSize: new Vector2() - }; - break; - - case 'SpotLight': - uniforms = { - shadowBias: 0, - shadowNormalBias: 0, - shadowRadius: 1, - shadowMapSize: new Vector2() - }; - break; - - case 'PointLight': - uniforms = { - shadowBias: 0, - shadowNormalBias: 0, - shadowRadius: 1, - shadowMapSize: new Vector2(), - shadowCameraNear: 1, - shadowCameraFar: 1000 - }; - break; - - // TODO (abelnation): set RectAreaLight shadow uniforms - - } - - lights[ light.id ] = uniforms; - - return uniforms; - - } - - }; - -} - - - -let nextVersion = 0; - -function shadowCastingAndTexturingLightsFirst( lightA, lightB ) { - - return ( lightB.castShadow ? 2 : 0 ) - ( lightA.castShadow ? 2 : 0 ) + ( lightB.map ? 1 : 0 ) - ( lightA.map ? 1 : 0 ); - -} - -function WebGLLights( extensions, capabilities ) { - - const cache = new UniformsCache(); - - const shadowCache = ShadowUniformsCache(); - - const state = { - - version: 0, - - hash: { - directionalLength: - 1, - pointLength: - 1, - spotLength: - 1, - rectAreaLength: - 1, - hemiLength: - 1, - - numDirectionalShadows: - 1, - numPointShadows: - 1, - numSpotShadows: - 1, - numSpotMaps: - 1 - }, - - ambient: [ 0, 0, 0 ], - probe: [], - directional: [], - directionalShadow: [], - directionalShadowMap: [], - directionalShadowMatrix: [], - spot: [], - spotLightMap: [], - spotShadow: [], - spotShadowMap: [], - spotLightMatrix: [], - rectArea: [], - rectAreaLTC1: null, - rectAreaLTC2: null, - point: [], - pointShadow: [], - pointShadowMap: [], - pointShadowMatrix: [], - hemi: [], - numSpotLightShadowsWithMaps: 0 - - }; - - for ( let i = 0; i < 9; i ++ ) state.probe.push( new Vector3() ); - - const vector3 = new Vector3(); - const matrix4 = new Matrix4(); - const matrix42 = new Matrix4(); - - function setup( lights, useLegacyLights ) { - - let r = 0, g = 0, b = 0; - - for ( let i = 0; i < 9; i ++ ) state.probe[ i ].set( 0, 0, 0 ); - - let directionalLength = 0; - let pointLength = 0; - let spotLength = 0; - let rectAreaLength = 0; - let hemiLength = 0; - - let numDirectionalShadows = 0; - let numPointShadows = 0; - let numSpotShadows = 0; - let numSpotMaps = 0; - let numSpotShadowsWithMaps = 0; - - // ordering : [shadow casting + map texturing, map texturing, shadow casting, none ] - lights.sort( shadowCastingAndTexturingLightsFirst ); - - // artist-friendly light intensity scaling factor - const scaleFactor = ( useLegacyLights === true ) ? Math.PI : 1; - - for ( let i = 0, l = lights.length; i < l; i ++ ) { - - const light = lights[ i ]; - - const color = light.color; - const intensity = light.intensity; - const distance = light.distance; - - const shadowMap = ( light.shadow && light.shadow.map ) ? light.shadow.map.texture : null; - - if ( light.isAmbientLight ) { - - r += color.r * intensity * scaleFactor; - g += color.g * intensity * scaleFactor; - b += color.b * intensity * scaleFactor; - - } else if ( light.isLightProbe ) { - - for ( let j = 0; j < 9; j ++ ) { - - state.probe[ j ].addScaledVector( light.sh.coefficients[ j ], intensity ); - - } - - } else if ( light.isDirectionalLight ) { - - const uniforms = cache.get( light ); - - uniforms.color.copy( light.color ).multiplyScalar( light.intensity * scaleFactor ); - - if ( light.castShadow ) { - - const shadow = light.shadow; - - const shadowUniforms = shadowCache.get( light ); - - shadowUniforms.shadowBias = shadow.bias; - shadowUniforms.shadowNormalBias = shadow.normalBias; - shadowUniforms.shadowRadius = shadow.radius; - shadowUniforms.shadowMapSize = shadow.mapSize; - - state.directionalShadow[ directionalLength ] = shadowUniforms; - state.directionalShadowMap[ directionalLength ] = shadowMap; - state.directionalShadowMatrix[ directionalLength ] = light.shadow.matrix; - - numDirectionalShadows ++; - - } - - state.directional[ directionalLength ] = uniforms; - - directionalLength ++; - - } else if ( light.isSpotLight ) { - - const uniforms = cache.get( light ); - - uniforms.position.setFromMatrixPosition( light.matrixWorld ); - - uniforms.color.copy( color ).multiplyScalar( intensity * scaleFactor ); - uniforms.distance = distance; - - uniforms.coneCos = Math.cos( light.angle ); - uniforms.penumbraCos = Math.cos( light.angle * ( 1 - light.penumbra ) ); - uniforms.decay = light.decay; - - state.spot[ spotLength ] = uniforms; - - const shadow = light.shadow; - - if ( light.map ) { - - state.spotLightMap[ numSpotMaps ] = light.map; - numSpotMaps ++; - - // make sure the lightMatrix is up to date - // TODO : do it if required only - shadow.updateMatrices( light ); - - if ( light.castShadow ) numSpotShadowsWithMaps ++; - - } - - state.spotLightMatrix[ spotLength ] = shadow.matrix; - - if ( light.castShadow ) { - - const shadowUniforms = shadowCache.get( light ); - - shadowUniforms.shadowBias = shadow.bias; - shadowUniforms.shadowNormalBias = shadow.normalBias; - shadowUniforms.shadowRadius = shadow.radius; - shadowUniforms.shadowMapSize = shadow.mapSize; - - state.spotShadow[ spotLength ] = shadowUniforms; - state.spotShadowMap[ spotLength ] = shadowMap; - - numSpotShadows ++; - - } - - spotLength ++; - - } else if ( light.isRectAreaLight ) { - - const uniforms = cache.get( light ); - - uniforms.color.copy( color ).multiplyScalar( intensity ); - - uniforms.halfWidth.set( light.width * 0.5, 0.0, 0.0 ); - uniforms.halfHeight.set( 0.0, light.height * 0.5, 0.0 ); - - state.rectArea[ rectAreaLength ] = uniforms; - - rectAreaLength ++; - - } else if ( light.isPointLight ) { - - const uniforms = cache.get( light ); - - uniforms.color.copy( light.color ).multiplyScalar( light.intensity * scaleFactor ); - uniforms.distance = light.distance; - uniforms.decay = light.decay; - - if ( light.castShadow ) { - - const shadow = light.shadow; - - const shadowUniforms = shadowCache.get( light ); - - shadowUniforms.shadowBias = shadow.bias; - shadowUniforms.shadowNormalBias = shadow.normalBias; - shadowUniforms.shadowRadius = shadow.radius; - shadowUniforms.shadowMapSize = shadow.mapSize; - shadowUniforms.shadowCameraNear = shadow.camera.near; - shadowUniforms.shadowCameraFar = shadow.camera.far; - - state.pointShadow[ pointLength ] = shadowUniforms; - state.pointShadowMap[ pointLength ] = shadowMap; - state.pointShadowMatrix[ pointLength ] = light.shadow.matrix; - - numPointShadows ++; - - } - - state.point[ pointLength ] = uniforms; - - pointLength ++; - - } else if ( light.isHemisphereLight ) { - - const uniforms = cache.get( light ); - - uniforms.skyColor.copy( light.color ).multiplyScalar( intensity * scaleFactor ); - uniforms.groundColor.copy( light.groundColor ).multiplyScalar( intensity * scaleFactor ); - - state.hemi[ hemiLength ] = uniforms; - - hemiLength ++; - - } - - } - - if ( rectAreaLength > 0 ) { - - if ( capabilities.isWebGL2 ) { - - // WebGL 2 - - state.rectAreaLTC1 = UniformsLib.LTC_FLOAT_1; - state.rectAreaLTC2 = UniformsLib.LTC_FLOAT_2; - - } else { - - // WebGL 1 - - if ( extensions.has( 'OES_texture_float_linear' ) === true ) { - - state.rectAreaLTC1 = UniformsLib.LTC_FLOAT_1; - state.rectAreaLTC2 = UniformsLib.LTC_FLOAT_2; - - } else if ( extensions.has( 'OES_texture_half_float_linear' ) === true ) { - - state.rectAreaLTC1 = UniformsLib.LTC_HALF_1; - state.rectAreaLTC2 = UniformsLib.LTC_HALF_2; - - } else { - - console.error( 'THREE.WebGLRenderer: Unable to use RectAreaLight. Missing WebGL extensions.' ); - - } - - } - - } - - state.ambient[ 0 ] = r; - state.ambient[ 1 ] = g; - state.ambient[ 2 ] = b; - - const hash = state.hash; - - if ( hash.directionalLength !== directionalLength || - hash.pointLength !== pointLength || - hash.spotLength !== spotLength || - hash.rectAreaLength !== rectAreaLength || - hash.hemiLength !== hemiLength || - hash.numDirectionalShadows !== numDirectionalShadows || - hash.numPointShadows !== numPointShadows || - hash.numSpotShadows !== numSpotShadows || - hash.numSpotMaps !== numSpotMaps ) { - - state.directional.length = directionalLength; - state.spot.length = spotLength; - state.rectArea.length = rectAreaLength; - state.point.length = pointLength; - state.hemi.length = hemiLength; - - state.directionalShadow.length = numDirectionalShadows; - state.directionalShadowMap.length = numDirectionalShadows; - state.pointShadow.length = numPointShadows; - state.pointShadowMap.length = numPointShadows; - state.spotShadow.length = numSpotShadows; - state.spotShadowMap.length = numSpotShadows; - state.directionalShadowMatrix.length = numDirectionalShadows; - state.pointShadowMatrix.length = numPointShadows; - state.spotLightMatrix.length = numSpotShadows + numSpotMaps - numSpotShadowsWithMaps; - state.spotLightMap.length = numSpotMaps; - state.numSpotLightShadowsWithMaps = numSpotShadowsWithMaps; - - hash.directionalLength = directionalLength; - hash.pointLength = pointLength; - hash.spotLength = spotLength; - hash.rectAreaLength = rectAreaLength; - hash.hemiLength = hemiLength; - - hash.numDirectionalShadows = numDirectionalShadows; - hash.numPointShadows = numPointShadows; - hash.numSpotShadows = numSpotShadows; - hash.numSpotMaps = numSpotMaps; - - state.version = nextVersion ++; - - } - - } - - function setupView( lights, camera ) { - - let directionalLength = 0; - let pointLength = 0; - let spotLength = 0; - let rectAreaLength = 0; - let hemiLength = 0; - - const viewMatrix = camera.matrixWorldInverse; - - for ( let i = 0, l = lights.length; i < l; i ++ ) { - - const light = lights[ i ]; - - if ( light.isDirectionalLight ) { - - const uniforms = state.directional[ directionalLength ]; - - uniforms.direction.setFromMatrixPosition( light.matrixWorld ); - vector3.setFromMatrixPosition( light.target.matrixWorld ); - uniforms.direction.sub( vector3 ); - uniforms.direction.transformDirection( viewMatrix ); - - directionalLength ++; - - } else if ( light.isSpotLight ) { - - const uniforms = state.spot[ spotLength ]; - - uniforms.position.setFromMatrixPosition( light.matrixWorld ); - uniforms.position.applyMatrix4( viewMatrix ); - - uniforms.direction.setFromMatrixPosition( light.matrixWorld ); - vector3.setFromMatrixPosition( light.target.matrixWorld ); - uniforms.direction.sub( vector3 ); - uniforms.direction.transformDirection( viewMatrix ); - - spotLength ++; - - } else if ( light.isRectAreaLight ) { - - const uniforms = state.rectArea[ rectAreaLength ]; - - uniforms.position.setFromMatrixPosition( light.matrixWorld ); - uniforms.position.applyMatrix4( viewMatrix ); - - // extract local rotation of light to derive width/height half vectors - matrix42.identity(); - matrix4.copy( light.matrixWorld ); - matrix4.premultiply( viewMatrix ); - matrix42.extractRotation( matrix4 ); - - uniforms.halfWidth.set( light.width * 0.5, 0.0, 0.0 ); - uniforms.halfHeight.set( 0.0, light.height * 0.5, 0.0 ); - - uniforms.halfWidth.applyMatrix4( matrix42 ); - uniforms.halfHeight.applyMatrix4( matrix42 ); - - rectAreaLength ++; - - } else if ( light.isPointLight ) { - - const uniforms = state.point[ pointLength ]; - - uniforms.position.setFromMatrixPosition( light.matrixWorld ); - uniforms.position.applyMatrix4( viewMatrix ); - - pointLength ++; - - } else if ( light.isHemisphereLight ) { - - const uniforms = state.hemi[ hemiLength ]; - - uniforms.direction.setFromMatrixPosition( light.matrixWorld ); - uniforms.direction.transformDirection( viewMatrix ); - - hemiLength ++; - - } - - } - - } - - return { - setup: setup, - setupView: setupView, - state: state - }; - -} - -function WebGLRenderState( extensions, capabilities ) { - - const lights = new WebGLLights( extensions, capabilities ); - - const lightsArray = []; - const shadowsArray = []; - - function init() { - - lightsArray.length = 0; - shadowsArray.length = 0; - - } - - function pushLight( light ) { - - lightsArray.push( light ); - - } - - function pushShadow( shadowLight ) { - - shadowsArray.push( shadowLight ); - - } - - function setupLights( useLegacyLights ) { - - lights.setup( lightsArray, useLegacyLights ); - - } - - function setupLightsView( camera ) { - - lights.setupView( lightsArray, camera ); - - } - - const state = { - lightsArray: lightsArray, - shadowsArray: shadowsArray, - - lights: lights - }; - - return { - init: init, - state: state, - setupLights: setupLights, - setupLightsView: setupLightsView, - - pushLight: pushLight, - pushShadow: pushShadow - }; - -} - -function WebGLRenderStates( extensions, capabilities ) { - - let renderStates = new WeakMap(); - - function get( scene, renderCallDepth = 0 ) { - - const renderStateArray = renderStates.get( scene ); - let renderState; - - if ( renderStateArray === undefined ) { - - renderState = new WebGLRenderState( extensions, capabilities ); - renderStates.set( scene, [ renderState ] ); - - } else { - - if ( renderCallDepth >= renderStateArray.length ) { - - renderState = new WebGLRenderState( extensions, capabilities ); - renderStateArray.push( renderState ); - - } else { - - renderState = renderStateArray[ renderCallDepth ]; - - } - - } - - return renderState; - - } - - function dispose() { - - renderStates = new WeakMap(); - - } - - return { - get: get, - dispose: dispose - }; - -} - -class MeshDepthMaterial extends Material { - - constructor( parameters ) { - - super(); - - this.isMeshDepthMaterial = true; - - this.type = 'MeshDepthMaterial'; - - this.depthPacking = BasicDepthPacking; - - this.map = null; - - this.alphaMap = null; - - this.displacementMap = null; - this.displacementScale = 1; - this.displacementBias = 0; - - this.wireframe = false; - this.wireframeLinewidth = 1; - - this.setValues( parameters ); - - } - - copy( source ) { - - super.copy( source ); - - this.depthPacking = source.depthPacking; - - this.map = source.map; - - this.alphaMap = source.alphaMap; - - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; - - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; - - return this; - - } - -} - -class MeshDistanceMaterial extends Material { - - constructor( parameters ) { - - super(); - - this.isMeshDistanceMaterial = true; - - this.type = 'MeshDistanceMaterial'; - - this.map = null; - - this.alphaMap = null; - - this.displacementMap = null; - this.displacementScale = 1; - this.displacementBias = 0; - - this.setValues( parameters ); - - } - - copy( source ) { - - super.copy( source ); - - this.map = source.map; - - this.alphaMap = source.alphaMap; - - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; - - return this; - - } - -} - -const vertex = "void main() {\n\tgl_Position = vec4( position, 1.0 );\n}"; - -const fragment = "uniform sampler2D shadow_pass;\nuniform vec2 resolution;\nuniform float radius;\n#include \nvoid main() {\n\tconst float samples = float( VSM_SAMPLES );\n\tfloat mean = 0.0;\n\tfloat squared_mean = 0.0;\n\tfloat uvStride = samples <= 1.0 ? 0.0 : 2.0 / ( samples - 1.0 );\n\tfloat uvStart = samples <= 1.0 ? 0.0 : - 1.0;\n\tfor ( float i = 0.0; i < samples; i ++ ) {\n\t\tfloat uvOffset = uvStart + i * uvStride;\n\t\t#ifdef HORIZONTAL_PASS\n\t\t\tvec2 distribution = unpackRGBATo2Half( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( uvOffset, 0.0 ) * radius ) / resolution ) );\n\t\t\tmean += distribution.x;\n\t\t\tsquared_mean += distribution.y * distribution.y + distribution.x * distribution.x;\n\t\t#else\n\t\t\tfloat depth = unpackRGBAToDepth( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( 0.0, uvOffset ) * radius ) / resolution ) );\n\t\t\tmean += depth;\n\t\t\tsquared_mean += depth * depth;\n\t\t#endif\n\t}\n\tmean = mean / samples;\n\tsquared_mean = squared_mean / samples;\n\tfloat std_dev = sqrt( squared_mean - mean * mean );\n\tgl_FragColor = pack2HalfToRGBA( vec2( mean, std_dev ) );\n}"; - -function WebGLShadowMap( _renderer, _objects, _capabilities ) { - - let _frustum = new Frustum(); - - const _shadowMapSize = new Vector2(), - _viewportSize = new Vector2(), - - _viewport = new Vector4(), - - _depthMaterial = new MeshDepthMaterial( { depthPacking: RGBADepthPacking } ), - _distanceMaterial = new MeshDistanceMaterial(), - - _materialCache = {}, - - _maxTextureSize = _capabilities.maxTextureSize; - - const shadowSide = { [ FrontSide ]: BackSide, [ BackSide ]: FrontSide, [ DoubleSide ]: DoubleSide }; - - const shadowMaterialVertical = new ShaderMaterial( { - defines: { - VSM_SAMPLES: 8 - }, - uniforms: { - shadow_pass: { value: null }, - resolution: { value: new Vector2() }, - radius: { value: 4.0 } - }, - - vertexShader: vertex, - fragmentShader: fragment - - } ); - - const shadowMaterialHorizontal = shadowMaterialVertical.clone(); - shadowMaterialHorizontal.defines.HORIZONTAL_PASS = 1; - - const fullScreenTri = new BufferGeometry(); - fullScreenTri.setAttribute( - 'position', - new BufferAttribute( - new Float32Array( [ - 1, - 1, 0.5, 3, - 1, 0.5, - 1, 3, 0.5 ] ), - 3 - ) - ); - - const fullScreenMesh = new Mesh( fullScreenTri, shadowMaterialVertical ); - - const scope = this; - - this.enabled = false; - - this.autoUpdate = true; - this.needsUpdate = false; - - this.type = PCFShadowMap; - let _previousType = this.type; - - this.render = function ( lights, scene, camera ) { - - if ( scope.enabled === false ) return; - if ( scope.autoUpdate === false && scope.needsUpdate === false ) return; - - if ( lights.length === 0 ) return; - - const currentRenderTarget = _renderer.getRenderTarget(); - const activeCubeFace = _renderer.getActiveCubeFace(); - const activeMipmapLevel = _renderer.getActiveMipmapLevel(); - - const _state = _renderer.state; - - // Set GL state for depth map. - _state.setBlending( NoBlending ); - _state.buffers.color.setClear( 1, 1, 1, 1 ); - _state.buffers.depth.setTest( true ); - _state.setScissorTest( false ); - - // check for shadow map type changes - - const toVSM = ( _previousType !== VSMShadowMap && this.type === VSMShadowMap ); - const fromVSM = ( _previousType === VSMShadowMap && this.type !== VSMShadowMap ); - - // render depth map - - for ( let i = 0, il = lights.length; i < il; i ++ ) { - - const light = lights[ i ]; - const shadow = light.shadow; - - if ( shadow === undefined ) { - - console.warn( 'THREE.WebGLShadowMap:', light, 'has no shadow.' ); - continue; - - } - - if ( shadow.autoUpdate === false && shadow.needsUpdate === false ) continue; - - _shadowMapSize.copy( shadow.mapSize ); - - const shadowFrameExtents = shadow.getFrameExtents(); - - _shadowMapSize.multiply( shadowFrameExtents ); - - _viewportSize.copy( shadow.mapSize ); - - if ( _shadowMapSize.x > _maxTextureSize || _shadowMapSize.y > _maxTextureSize ) { - - if ( _shadowMapSize.x > _maxTextureSize ) { - - _viewportSize.x = Math.floor( _maxTextureSize / shadowFrameExtents.x ); - _shadowMapSize.x = _viewportSize.x * shadowFrameExtents.x; - shadow.mapSize.x = _viewportSize.x; - - } - - if ( _shadowMapSize.y > _maxTextureSize ) { - - _viewportSize.y = Math.floor( _maxTextureSize / shadowFrameExtents.y ); - _shadowMapSize.y = _viewportSize.y * shadowFrameExtents.y; - shadow.mapSize.y = _viewportSize.y; - - } - - } - - if ( shadow.map === null || toVSM === true || fromVSM === true ) { - - const pars = ( this.type !== VSMShadowMap ) ? { minFilter: NearestFilter, magFilter: NearestFilter } : {}; - - if ( shadow.map !== null ) { - - shadow.map.dispose(); - - } - - shadow.map = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y, pars ); - shadow.map.texture.name = light.name + '.shadowMap'; - - shadow.camera.updateProjectionMatrix(); - - } - - _renderer.setRenderTarget( shadow.map ); - _renderer.clear(); - - const viewportCount = shadow.getViewportCount(); - - for ( let vp = 0; vp < viewportCount; vp ++ ) { - - const viewport = shadow.getViewport( vp ); - - _viewport.set( - _viewportSize.x * viewport.x, - _viewportSize.y * viewport.y, - _viewportSize.x * viewport.z, - _viewportSize.y * viewport.w - ); - - _state.viewport( _viewport ); - - shadow.updateMatrices( light, vp ); - - _frustum = shadow.getFrustum(); - - renderObject( scene, camera, shadow.camera, light, this.type ); - - } - - // do blur pass for VSM - - if ( shadow.isPointLightShadow !== true && this.type === VSMShadowMap ) { - - VSMPass( shadow, camera ); - - } - - shadow.needsUpdate = false; - - } - - _previousType = this.type; - - scope.needsUpdate = false; - - _renderer.setRenderTarget( currentRenderTarget, activeCubeFace, activeMipmapLevel ); - - }; - - function VSMPass( shadow, camera ) { - - const geometry = _objects.update( fullScreenMesh ); - - if ( shadowMaterialVertical.defines.VSM_SAMPLES !== shadow.blurSamples ) { - - shadowMaterialVertical.defines.VSM_SAMPLES = shadow.blurSamples; - shadowMaterialHorizontal.defines.VSM_SAMPLES = shadow.blurSamples; - - shadowMaterialVertical.needsUpdate = true; - shadowMaterialHorizontal.needsUpdate = true; - - } - - if ( shadow.mapPass === null ) { - - shadow.mapPass = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y ); - - } - - // vertical pass - - shadowMaterialVertical.uniforms.shadow_pass.value = shadow.map.texture; - shadowMaterialVertical.uniforms.resolution.value = shadow.mapSize; - shadowMaterialVertical.uniforms.radius.value = shadow.radius; - _renderer.setRenderTarget( shadow.mapPass ); - _renderer.clear(); - _renderer.renderBufferDirect( camera, null, geometry, shadowMaterialVertical, fullScreenMesh, null ); - - // horizontal pass - - shadowMaterialHorizontal.uniforms.shadow_pass.value = shadow.mapPass.texture; - shadowMaterialHorizontal.uniforms.resolution.value = shadow.mapSize; - shadowMaterialHorizontal.uniforms.radius.value = shadow.radius; - _renderer.setRenderTarget( shadow.map ); - _renderer.clear(); - _renderer.renderBufferDirect( camera, null, geometry, shadowMaterialHorizontal, fullScreenMesh, null ); - - } - - function getDepthMaterial( object, material, light, type ) { - - let result = null; - - const customMaterial = ( light.isPointLight === true ) ? object.customDistanceMaterial : object.customDepthMaterial; - - if ( customMaterial !== undefined ) { - - result = customMaterial; - - } else { - - result = ( light.isPointLight === true ) ? _distanceMaterial : _depthMaterial; - - if ( ( _renderer.localClippingEnabled && material.clipShadows === true && Array.isArray( material.clippingPlanes ) && material.clippingPlanes.length !== 0 ) || - ( material.displacementMap && material.displacementScale !== 0 ) || - ( material.alphaMap && material.alphaTest > 0 ) || - ( material.map && material.alphaTest > 0 ) ) { - - // in this case we need a unique material instance reflecting the - // appropriate state - - const keyA = result.uuid, keyB = material.uuid; - - let materialsForVariant = _materialCache[ keyA ]; - - if ( materialsForVariant === undefined ) { - - materialsForVariant = {}; - _materialCache[ keyA ] = materialsForVariant; - - } - - let cachedMaterial = materialsForVariant[ keyB ]; - - if ( cachedMaterial === undefined ) { - - cachedMaterial = result.clone(); - materialsForVariant[ keyB ] = cachedMaterial; - - } - - result = cachedMaterial; - - } - - } - - result.visible = material.visible; - result.wireframe = material.wireframe; - - if ( type === VSMShadowMap ) { - - result.side = ( material.shadowSide !== null ) ? material.shadowSide : material.side; - - } else { - - result.side = ( material.shadowSide !== null ) ? material.shadowSide : shadowSide[ material.side ]; - - } - - result.alphaMap = material.alphaMap; - result.alphaTest = material.alphaTest; - result.map = material.map; - - result.clipShadows = material.clipShadows; - result.clippingPlanes = material.clippingPlanes; - result.clipIntersection = material.clipIntersection; - - result.displacementMap = material.displacementMap; - result.displacementScale = material.displacementScale; - result.displacementBias = material.displacementBias; - - result.wireframeLinewidth = material.wireframeLinewidth; - result.linewidth = material.linewidth; - - if ( light.isPointLight === true && result.isMeshDistanceMaterial === true ) { - - const materialProperties = _renderer.properties.get( result ); - materialProperties.light = light; - - } - - return result; - - } - - function renderObject( object, camera, shadowCamera, light, type ) { - - if ( object.visible === false ) return; - - const visible = object.layers.test( camera.layers ); - - if ( visible && ( object.isMesh || object.isLine || object.isPoints ) ) { - - if ( ( object.castShadow || ( object.receiveShadow && type === VSMShadowMap ) ) && ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) ) { - - object.modelViewMatrix.multiplyMatrices( shadowCamera.matrixWorldInverse, object.matrixWorld ); - - const geometry = _objects.update( object ); - const material = object.material; - - if ( Array.isArray( material ) ) { - - const groups = geometry.groups; - - for ( let k = 0, kl = groups.length; k < kl; k ++ ) { - - const group = groups[ k ]; - const groupMaterial = material[ group.materialIndex ]; - - if ( groupMaterial && groupMaterial.visible ) { - - const depthMaterial = getDepthMaterial( object, groupMaterial, light, type ); - - _renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, group ); - - } - - } - - } else if ( material.visible ) { - - const depthMaterial = getDepthMaterial( object, material, light, type ); - - _renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, null ); - - } - - } - - } - - const children = object.children; - - for ( let i = 0, l = children.length; i < l; i ++ ) { - - renderObject( children[ i ], camera, shadowCamera, light, type ); - - } - - } - -} - -function WebGLState( gl, extensions, capabilities ) { - - const isWebGL2 = capabilities.isWebGL2; - - function ColorBuffer() { - - let locked = false; - - const color = new Vector4(); - let currentColorMask = null; - const currentColorClear = new Vector4( 0, 0, 0, 0 ); - - return { - - setMask: function ( colorMask ) { - - if ( currentColorMask !== colorMask && ! locked ) { - - gl.colorMask( colorMask, colorMask, colorMask, colorMask ); - currentColorMask = colorMask; - - } - - }, - - setLocked: function ( lock ) { - - locked = lock; - - }, - - setClear: function ( r, g, b, a, premultipliedAlpha ) { - - if ( premultipliedAlpha === true ) { - - r *= a; g *= a; b *= a; - - } - - color.set( r, g, b, a ); - - if ( currentColorClear.equals( color ) === false ) { - - gl.clearColor( r, g, b, a ); - currentColorClear.copy( color ); - - } - - }, - - reset: function () { - - locked = false; - - currentColorMask = null; - currentColorClear.set( - 1, 0, 0, 0 ); // set to invalid state - - } - - }; - - } - - function DepthBuffer() { - - let locked = false; - - let currentDepthMask = null; - let currentDepthFunc = null; - let currentDepthClear = null; - - return { - - setTest: function ( depthTest ) { - - if ( depthTest ) { - - enable( gl.DEPTH_TEST ); - - } else { - - disable( gl.DEPTH_TEST ); - - } - - }, - - setMask: function ( depthMask ) { - - if ( currentDepthMask !== depthMask && ! locked ) { - - gl.depthMask( depthMask ); - currentDepthMask = depthMask; - - } - - }, - - setFunc: function ( depthFunc ) { - - if ( currentDepthFunc !== depthFunc ) { - - switch ( depthFunc ) { - - case NeverDepth: - - gl.depthFunc( gl.NEVER ); - break; - - case AlwaysDepth: - - gl.depthFunc( gl.ALWAYS ); - break; - - case LessDepth: - - gl.depthFunc( gl.LESS ); - break; - - case LessEqualDepth: - - gl.depthFunc( gl.LEQUAL ); - break; - - case EqualDepth: - - gl.depthFunc( gl.EQUAL ); - break; - - case GreaterEqualDepth: - - gl.depthFunc( gl.GEQUAL ); - break; - - case GreaterDepth: - - gl.depthFunc( gl.GREATER ); - break; - - case NotEqualDepth: - - gl.depthFunc( gl.NOTEQUAL ); - break; - - default: - - gl.depthFunc( gl.LEQUAL ); - - } - - currentDepthFunc = depthFunc; - - } - - }, - - setLocked: function ( lock ) { - - locked = lock; - - }, - - setClear: function ( depth ) { - - if ( currentDepthClear !== depth ) { - - gl.clearDepth( depth ); - currentDepthClear = depth; - - } - - }, - - reset: function () { - - locked = false; - - currentDepthMask = null; - currentDepthFunc = null; - currentDepthClear = null; - - } - - }; - - } - - function StencilBuffer() { - - let locked = false; - - let currentStencilMask = null; - let currentStencilFunc = null; - let currentStencilRef = null; - let currentStencilFuncMask = null; - let currentStencilFail = null; - let currentStencilZFail = null; - let currentStencilZPass = null; - let currentStencilClear = null; - - return { - - setTest: function ( stencilTest ) { - - if ( ! locked ) { - - if ( stencilTest ) { - - enable( gl.STENCIL_TEST ); - - } else { - - disable( gl.STENCIL_TEST ); - - } - - } - - }, - - setMask: function ( stencilMask ) { - - if ( currentStencilMask !== stencilMask && ! locked ) { - - gl.stencilMask( stencilMask ); - currentStencilMask = stencilMask; - - } - - }, - - setFunc: function ( stencilFunc, stencilRef, stencilMask ) { - - if ( currentStencilFunc !== stencilFunc || - currentStencilRef !== stencilRef || - currentStencilFuncMask !== stencilMask ) { - - gl.stencilFunc( stencilFunc, stencilRef, stencilMask ); - - currentStencilFunc = stencilFunc; - currentStencilRef = stencilRef; - currentStencilFuncMask = stencilMask; - - } - - }, - - setOp: function ( stencilFail, stencilZFail, stencilZPass ) { - - if ( currentStencilFail !== stencilFail || - currentStencilZFail !== stencilZFail || - currentStencilZPass !== stencilZPass ) { - - gl.stencilOp( stencilFail, stencilZFail, stencilZPass ); - - currentStencilFail = stencilFail; - currentStencilZFail = stencilZFail; - currentStencilZPass = stencilZPass; - - } - - }, - - setLocked: function ( lock ) { - - locked = lock; - - }, - - setClear: function ( stencil ) { - - if ( currentStencilClear !== stencil ) { - - gl.clearStencil( stencil ); - currentStencilClear = stencil; - - } - - }, - - reset: function () { - - locked = false; - - currentStencilMask = null; - currentStencilFunc = null; - currentStencilRef = null; - currentStencilFuncMask = null; - currentStencilFail = null; - currentStencilZFail = null; - currentStencilZPass = null; - currentStencilClear = null; - - } - - }; - - } - - // - - const colorBuffer = new ColorBuffer(); - const depthBuffer = new DepthBuffer(); - const stencilBuffer = new StencilBuffer(); - - const uboBindings = new WeakMap(); - const uboProgramMap = new WeakMap(); - - let enabledCapabilities = {}; - - let currentBoundFramebuffers = {}; - let currentDrawbuffers = new WeakMap(); - let defaultDrawbuffers = []; - - let currentProgram = null; - - let currentBlendingEnabled = false; - let currentBlending = null; - let currentBlendEquation = null; - let currentBlendSrc = null; - let currentBlendDst = null; - let currentBlendEquationAlpha = null; - let currentBlendSrcAlpha = null; - let currentBlendDstAlpha = null; - let currentPremultipledAlpha = false; - - let currentFlipSided = null; - let currentCullFace = null; - - let currentLineWidth = null; - - let currentPolygonOffsetFactor = null; - let currentPolygonOffsetUnits = null; - - const maxTextures = gl.getParameter( gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS ); - - let lineWidthAvailable = false; - let version = 0; - const glVersion = gl.getParameter( gl.VERSION ); - - if ( glVersion.indexOf( 'WebGL' ) !== - 1 ) { - - version = parseFloat( /^WebGL (\d)/.exec( glVersion )[ 1 ] ); - lineWidthAvailable = ( version >= 1.0 ); - - } else if ( glVersion.indexOf( 'OpenGL ES' ) !== - 1 ) { - - version = parseFloat( /^OpenGL ES (\d)/.exec( glVersion )[ 1 ] ); - lineWidthAvailable = ( version >= 2.0 ); - - } - - let currentTextureSlot = null; - let currentBoundTextures = {}; - - const scissorParam = gl.getParameter( gl.SCISSOR_BOX ); - const viewportParam = gl.getParameter( gl.VIEWPORT ); - - const currentScissor = new Vector4().fromArray( scissorParam ); - const currentViewport = new Vector4().fromArray( viewportParam ); - - function createTexture( type, target, count, dimensions ) { - - const data = new Uint8Array( 4 ); // 4 is required to match default unpack alignment of 4. - const texture = gl.createTexture(); - - gl.bindTexture( type, texture ); - gl.texParameteri( type, gl.TEXTURE_MIN_FILTER, gl.NEAREST ); - gl.texParameteri( type, gl.TEXTURE_MAG_FILTER, gl.NEAREST ); - - for ( let i = 0; i < count; i ++ ) { - - if ( isWebGL2 && ( type === gl.TEXTURE_3D || type === gl.TEXTURE_2D_ARRAY ) ) { - - gl.texImage3D( target, 0, gl.RGBA, 1, 1, dimensions, 0, gl.RGBA, gl.UNSIGNED_BYTE, data ); - - } else { - - gl.texImage2D( target + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, data ); - - } - - } - - return texture; - - } - - const emptyTextures = {}; - emptyTextures[ gl.TEXTURE_2D ] = createTexture( gl.TEXTURE_2D, gl.TEXTURE_2D, 1 ); - emptyTextures[ gl.TEXTURE_CUBE_MAP ] = createTexture( gl.TEXTURE_CUBE_MAP, gl.TEXTURE_CUBE_MAP_POSITIVE_X, 6 ); - - if ( isWebGL2 ) { - - emptyTextures[ gl.TEXTURE_2D_ARRAY ] = createTexture( gl.TEXTURE_2D_ARRAY, gl.TEXTURE_2D_ARRAY, 1, 1 ); - emptyTextures[ gl.TEXTURE_3D ] = createTexture( gl.TEXTURE_3D, gl.TEXTURE_3D, 1, 1 ); - - } - - // init - - colorBuffer.setClear( 0, 0, 0, 1 ); - depthBuffer.setClear( 1 ); - stencilBuffer.setClear( 0 ); - - enable( gl.DEPTH_TEST ); - depthBuffer.setFunc( LessEqualDepth ); - - setFlipSided( false ); - setCullFace( CullFaceBack ); - enable( gl.CULL_FACE ); - - setBlending( NoBlending ); - - // - - function enable( id ) { - - if ( enabledCapabilities[ id ] !== true ) { - - gl.enable( id ); - enabledCapabilities[ id ] = true; - - } - - } - - function disable( id ) { - - if ( enabledCapabilities[ id ] !== false ) { - - gl.disable( id ); - enabledCapabilities[ id ] = false; - - } - - } - - function bindFramebuffer( target, framebuffer ) { - - if ( currentBoundFramebuffers[ target ] !== framebuffer ) { - - gl.bindFramebuffer( target, framebuffer ); - - currentBoundFramebuffers[ target ] = framebuffer; - - if ( isWebGL2 ) { - - // gl.DRAW_FRAMEBUFFER is equivalent to gl.FRAMEBUFFER - - if ( target === gl.DRAW_FRAMEBUFFER ) { - - currentBoundFramebuffers[ gl.FRAMEBUFFER ] = framebuffer; - - } - - if ( target === gl.FRAMEBUFFER ) { - - currentBoundFramebuffers[ gl.DRAW_FRAMEBUFFER ] = framebuffer; - - } - - } - - return true; - - } - - return false; - - } - - function drawBuffers( renderTarget, framebuffer ) { - - let drawBuffers = defaultDrawbuffers; - - let needsUpdate = false; - - if ( renderTarget ) { - - drawBuffers = currentDrawbuffers.get( framebuffer ); - - if ( drawBuffers === undefined ) { - - drawBuffers = []; - currentDrawbuffers.set( framebuffer, drawBuffers ); - - } - - if ( renderTarget.isWebGLMultipleRenderTargets ) { - - const textures = renderTarget.texture; - - if ( drawBuffers.length !== textures.length || drawBuffers[ 0 ] !== gl.COLOR_ATTACHMENT0 ) { - - for ( let i = 0, il = textures.length; i < il; i ++ ) { - - drawBuffers[ i ] = gl.COLOR_ATTACHMENT0 + i; - - } - - drawBuffers.length = textures.length; - - needsUpdate = true; - - } - - } else { - - if ( drawBuffers[ 0 ] !== gl.COLOR_ATTACHMENT0 ) { - - drawBuffers[ 0 ] = gl.COLOR_ATTACHMENT0; - - needsUpdate = true; - - } - - } - - } else { - - if ( drawBuffers[ 0 ] !== gl.BACK ) { - - drawBuffers[ 0 ] = gl.BACK; - - needsUpdate = true; - - } - - } - - if ( needsUpdate ) { - - if ( capabilities.isWebGL2 ) { - - gl.drawBuffers( drawBuffers ); - - } else { - - extensions.get( 'WEBGL_draw_buffers' ).drawBuffersWEBGL( drawBuffers ); - - } - - } - - - } - - function useProgram( program ) { - - if ( currentProgram !== program ) { - - gl.useProgram( program ); - - currentProgram = program; - - return true; - - } - - return false; - - } - - const equationToGL = { - [ AddEquation ]: gl.FUNC_ADD, - [ SubtractEquation ]: gl.FUNC_SUBTRACT, - [ ReverseSubtractEquation ]: gl.FUNC_REVERSE_SUBTRACT - }; - - if ( isWebGL2 ) { - - equationToGL[ MinEquation ] = gl.MIN; - equationToGL[ MaxEquation ] = gl.MAX; - - } else { - - const extension = extensions.get( 'EXT_blend_minmax' ); - - if ( extension !== null ) { - - equationToGL[ MinEquation ] = extension.MIN_EXT; - equationToGL[ MaxEquation ] = extension.MAX_EXT; - - } - - } - - const factorToGL = { - [ ZeroFactor ]: gl.ZERO, - [ OneFactor ]: gl.ONE, - [ SrcColorFactor ]: gl.SRC_COLOR, - [ SrcAlphaFactor ]: gl.SRC_ALPHA, - [ SrcAlphaSaturateFactor ]: gl.SRC_ALPHA_SATURATE, - [ DstColorFactor ]: gl.DST_COLOR, - [ DstAlphaFactor ]: gl.DST_ALPHA, - [ OneMinusSrcColorFactor ]: gl.ONE_MINUS_SRC_COLOR, - [ OneMinusSrcAlphaFactor ]: gl.ONE_MINUS_SRC_ALPHA, - [ OneMinusDstColorFactor ]: gl.ONE_MINUS_DST_COLOR, - [ OneMinusDstAlphaFactor ]: gl.ONE_MINUS_DST_ALPHA - }; - - function setBlending( blending, blendEquation, blendSrc, blendDst, blendEquationAlpha, blendSrcAlpha, blendDstAlpha, premultipliedAlpha ) { - - if ( blending === NoBlending ) { - - if ( currentBlendingEnabled === true ) { - - disable( gl.BLEND ); - currentBlendingEnabled = false; - - } - - return; - - } - - if ( currentBlendingEnabled === false ) { - - enable( gl.BLEND ); - currentBlendingEnabled = true; - - } - - if ( blending !== CustomBlending ) { - - if ( blending !== currentBlending || premultipliedAlpha !== currentPremultipledAlpha ) { - - if ( currentBlendEquation !== AddEquation || currentBlendEquationAlpha !== AddEquation ) { - - gl.blendEquation( gl.FUNC_ADD ); - - currentBlendEquation = AddEquation; - currentBlendEquationAlpha = AddEquation; - - } - - if ( premultipliedAlpha ) { - - switch ( blending ) { - - case NormalBlending: - gl.blendFuncSeparate( gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA ); - break; - - case AdditiveBlending: - gl.blendFunc( gl.ONE, gl.ONE ); - break; - - case SubtractiveBlending: - gl.blendFuncSeparate( gl.ZERO, gl.ONE_MINUS_SRC_COLOR, gl.ZERO, gl.ONE ); - break; - - case MultiplyBlending: - gl.blendFuncSeparate( gl.ZERO, gl.SRC_COLOR, gl.ZERO, gl.SRC_ALPHA ); - break; - - default: - console.error( 'THREE.WebGLState: Invalid blending: ', blending ); - break; - - } - - } else { - - switch ( blending ) { - - case NormalBlending: - gl.blendFuncSeparate( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA ); - break; - - case AdditiveBlending: - gl.blendFunc( gl.SRC_ALPHA, gl.ONE ); - break; - - case SubtractiveBlending: - gl.blendFuncSeparate( gl.ZERO, gl.ONE_MINUS_SRC_COLOR, gl.ZERO, gl.ONE ); - break; - - case MultiplyBlending: - gl.blendFunc( gl.ZERO, gl.SRC_COLOR ); - break; - - default: - console.error( 'THREE.WebGLState: Invalid blending: ', blending ); - break; - - } - - } - - currentBlendSrc = null; - currentBlendDst = null; - currentBlendSrcAlpha = null; - currentBlendDstAlpha = null; - - currentBlending = blending; - currentPremultipledAlpha = premultipliedAlpha; - - } - - return; - - } - - // custom blending - - blendEquationAlpha = blendEquationAlpha || blendEquation; - blendSrcAlpha = blendSrcAlpha || blendSrc; - blendDstAlpha = blendDstAlpha || blendDst; - - if ( blendEquation !== currentBlendEquation || blendEquationAlpha !== currentBlendEquationAlpha ) { - - gl.blendEquationSeparate( equationToGL[ blendEquation ], equationToGL[ blendEquationAlpha ] ); - - currentBlendEquation = blendEquation; - currentBlendEquationAlpha = blendEquationAlpha; - - } - - if ( blendSrc !== currentBlendSrc || blendDst !== currentBlendDst || blendSrcAlpha !== currentBlendSrcAlpha || blendDstAlpha !== currentBlendDstAlpha ) { - - gl.blendFuncSeparate( factorToGL[ blendSrc ], factorToGL[ blendDst ], factorToGL[ blendSrcAlpha ], factorToGL[ blendDstAlpha ] ); - - currentBlendSrc = blendSrc; - currentBlendDst = blendDst; - currentBlendSrcAlpha = blendSrcAlpha; - currentBlendDstAlpha = blendDstAlpha; - - } - - currentBlending = blending; - currentPremultipledAlpha = false; - - } - - function setMaterial( material, frontFaceCW ) { - - material.side === DoubleSide - ? disable( gl.CULL_FACE ) - : enable( gl.CULL_FACE ); - - let flipSided = ( material.side === BackSide ); - if ( frontFaceCW ) flipSided = ! flipSided; - - setFlipSided( flipSided ); - - ( material.blending === NormalBlending && material.transparent === false ) - ? setBlending( NoBlending ) - : setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst, material.blendEquationAlpha, material.blendSrcAlpha, material.blendDstAlpha, material.premultipliedAlpha ); - - depthBuffer.setFunc( material.depthFunc ); - depthBuffer.setTest( material.depthTest ); - depthBuffer.setMask( material.depthWrite ); - colorBuffer.setMask( material.colorWrite ); - - const stencilWrite = material.stencilWrite; - stencilBuffer.setTest( stencilWrite ); - if ( stencilWrite ) { - - stencilBuffer.setMask( material.stencilWriteMask ); - stencilBuffer.setFunc( material.stencilFunc, material.stencilRef, material.stencilFuncMask ); - stencilBuffer.setOp( material.stencilFail, material.stencilZFail, material.stencilZPass ); - - } - - setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits ); - - material.alphaToCoverage === true - ? enable( gl.SAMPLE_ALPHA_TO_COVERAGE ) - : disable( gl.SAMPLE_ALPHA_TO_COVERAGE ); - - } - - // - - function setFlipSided( flipSided ) { - - if ( currentFlipSided !== flipSided ) { - - if ( flipSided ) { - - gl.frontFace( gl.CW ); - - } else { - - gl.frontFace( gl.CCW ); - - } - - currentFlipSided = flipSided; - - } - - } - - function setCullFace( cullFace ) { - - if ( cullFace !== CullFaceNone ) { - - enable( gl.CULL_FACE ); - - if ( cullFace !== currentCullFace ) { - - if ( cullFace === CullFaceBack ) { - - gl.cullFace( gl.BACK ); - - } else if ( cullFace === CullFaceFront ) { - - gl.cullFace( gl.FRONT ); - - } else { - - gl.cullFace( gl.FRONT_AND_BACK ); - - } - - } - - } else { - - disable( gl.CULL_FACE ); - - } - - currentCullFace = cullFace; - - } - - function setLineWidth( width ) { - - if ( width !== currentLineWidth ) { - - if ( lineWidthAvailable ) gl.lineWidth( width ); - - currentLineWidth = width; - - } - - } - - function setPolygonOffset( polygonOffset, factor, units ) { - - if ( polygonOffset ) { - - enable( gl.POLYGON_OFFSET_FILL ); - - if ( currentPolygonOffsetFactor !== factor || currentPolygonOffsetUnits !== units ) { - - gl.polygonOffset( factor, units ); - - currentPolygonOffsetFactor = factor; - currentPolygonOffsetUnits = units; - - } - - } else { - - disable( gl.POLYGON_OFFSET_FILL ); - - } - - } - - function setScissorTest( scissorTest ) { - - if ( scissorTest ) { - - enable( gl.SCISSOR_TEST ); - - } else { - - disable( gl.SCISSOR_TEST ); - - } - - } - - // texture - - function activeTexture( webglSlot ) { - - if ( webglSlot === undefined ) webglSlot = gl.TEXTURE0 + maxTextures - 1; - - if ( currentTextureSlot !== webglSlot ) { - - gl.activeTexture( webglSlot ); - currentTextureSlot = webglSlot; - - } - - } - - function bindTexture( webglType, webglTexture, webglSlot ) { - - if ( webglSlot === undefined ) { - - if ( currentTextureSlot === null ) { - - webglSlot = gl.TEXTURE0 + maxTextures - 1; - - } else { - - webglSlot = currentTextureSlot; - - } - - } - - let boundTexture = currentBoundTextures[ webglSlot ]; - - if ( boundTexture === undefined ) { - - boundTexture = { type: undefined, texture: undefined }; - currentBoundTextures[ webglSlot ] = boundTexture; - - } - - if ( boundTexture.type !== webglType || boundTexture.texture !== webglTexture ) { - - if ( currentTextureSlot !== webglSlot ) { - - gl.activeTexture( webglSlot ); - currentTextureSlot = webglSlot; - - } - - gl.bindTexture( webglType, webglTexture || emptyTextures[ webglType ] ); - - boundTexture.type = webglType; - boundTexture.texture = webglTexture; - - } - - } - - function unbindTexture() { - - const boundTexture = currentBoundTextures[ currentTextureSlot ]; - - if ( boundTexture !== undefined && boundTexture.type !== undefined ) { - - gl.bindTexture( boundTexture.type, null ); - - boundTexture.type = undefined; - boundTexture.texture = undefined; - - } - - } - - function compressedTexImage2D() { - - try { - - gl.compressedTexImage2D.apply( gl, arguments ); - - } catch ( error ) { - - console.error( 'THREE.WebGLState:', error ); - - } - - } - - function compressedTexImage3D() { - - try { - - gl.compressedTexImage3D.apply( gl, arguments ); - - } catch ( error ) { - - console.error( 'THREE.WebGLState:', error ); - - } - - } - - function texSubImage2D() { - - try { - - gl.texSubImage2D.apply( gl, arguments ); - - } catch ( error ) { - - console.error( 'THREE.WebGLState:', error ); - - } - - } - - function texSubImage3D() { - - try { - - gl.texSubImage3D.apply( gl, arguments ); - - } catch ( error ) { - - console.error( 'THREE.WebGLState:', error ); - - } - - } - - function compressedTexSubImage2D() { - - try { - - gl.compressedTexSubImage2D.apply( gl, arguments ); - - } catch ( error ) { - - console.error( 'THREE.WebGLState:', error ); - - } - - } - - function compressedTexSubImage3D() { - - try { - - gl.compressedTexSubImage3D.apply( gl, arguments ); - - } catch ( error ) { - - console.error( 'THREE.WebGLState:', error ); - - } - - } - - function texStorage2D() { - - try { - - gl.texStorage2D.apply( gl, arguments ); - - } catch ( error ) { - - console.error( 'THREE.WebGLState:', error ); - - } - - } - - function texStorage3D() { - - try { - - gl.texStorage3D.apply( gl, arguments ); - - } catch ( error ) { - - console.error( 'THREE.WebGLState:', error ); - - } - - } - - function texImage2D() { - - try { - - gl.texImage2D.apply( gl, arguments ); - - } catch ( error ) { - - console.error( 'THREE.WebGLState:', error ); - - } - - } - - function texImage3D() { - - try { - - gl.texImage3D.apply( gl, arguments ); - - } catch ( error ) { - - console.error( 'THREE.WebGLState:', error ); - - } - - } - - // - - function scissor( scissor ) { - - if ( currentScissor.equals( scissor ) === false ) { - - gl.scissor( scissor.x, scissor.y, scissor.z, scissor.w ); - currentScissor.copy( scissor ); - - } - - } - - function viewport( viewport ) { - - if ( currentViewport.equals( viewport ) === false ) { - - gl.viewport( viewport.x, viewport.y, viewport.z, viewport.w ); - currentViewport.copy( viewport ); - - } - - } - - function updateUBOMapping( uniformsGroup, program ) { - - let mapping = uboProgramMap.get( program ); - - if ( mapping === undefined ) { - - mapping = new WeakMap(); - - uboProgramMap.set( program, mapping ); - - } - - let blockIndex = mapping.get( uniformsGroup ); - - if ( blockIndex === undefined ) { - - blockIndex = gl.getUniformBlockIndex( program, uniformsGroup.name ); - - mapping.set( uniformsGroup, blockIndex ); - - } - - } - - function uniformBlockBinding( uniformsGroup, program ) { - - const mapping = uboProgramMap.get( program ); - const blockIndex = mapping.get( uniformsGroup ); - - if ( uboBindings.get( program ) !== blockIndex ) { - - // bind shader specific block index to global block point - gl.uniformBlockBinding( program, blockIndex, uniformsGroup.__bindingPointIndex ); - - uboBindings.set( program, blockIndex ); - - } - - } - - // - - function reset() { - - // reset state - - gl.disable( gl.BLEND ); - gl.disable( gl.CULL_FACE ); - gl.disable( gl.DEPTH_TEST ); - gl.disable( gl.POLYGON_OFFSET_FILL ); - gl.disable( gl.SCISSOR_TEST ); - gl.disable( gl.STENCIL_TEST ); - gl.disable( gl.SAMPLE_ALPHA_TO_COVERAGE ); - - gl.blendEquation( gl.FUNC_ADD ); - gl.blendFunc( gl.ONE, gl.ZERO ); - gl.blendFuncSeparate( gl.ONE, gl.ZERO, gl.ONE, gl.ZERO ); - - gl.colorMask( true, true, true, true ); - gl.clearColor( 0, 0, 0, 0 ); - - gl.depthMask( true ); - gl.depthFunc( gl.LESS ); - gl.clearDepth( 1 ); - - gl.stencilMask( 0xffffffff ); - gl.stencilFunc( gl.ALWAYS, 0, 0xffffffff ); - gl.stencilOp( gl.KEEP, gl.KEEP, gl.KEEP ); - gl.clearStencil( 0 ); - - gl.cullFace( gl.BACK ); - gl.frontFace( gl.CCW ); - - gl.polygonOffset( 0, 0 ); - - gl.activeTexture( gl.TEXTURE0 ); - - gl.bindFramebuffer( gl.FRAMEBUFFER, null ); - - if ( isWebGL2 === true ) { - - gl.bindFramebuffer( gl.DRAW_FRAMEBUFFER, null ); - gl.bindFramebuffer( gl.READ_FRAMEBUFFER, null ); - - } - - gl.useProgram( null ); - - gl.lineWidth( 1 ); - - gl.scissor( 0, 0, gl.canvas.width, gl.canvas.height ); - gl.viewport( 0, 0, gl.canvas.width, gl.canvas.height ); - - // reset internals - - enabledCapabilities = {}; - - currentTextureSlot = null; - currentBoundTextures = {}; - - currentBoundFramebuffers = {}; - currentDrawbuffers = new WeakMap(); - defaultDrawbuffers = []; - - currentProgram = null; - - currentBlendingEnabled = false; - currentBlending = null; - currentBlendEquation = null; - currentBlendSrc = null; - currentBlendDst = null; - currentBlendEquationAlpha = null; - currentBlendSrcAlpha = null; - currentBlendDstAlpha = null; - currentPremultipledAlpha = false; - - currentFlipSided = null; - currentCullFace = null; - - currentLineWidth = null; - - currentPolygonOffsetFactor = null; - currentPolygonOffsetUnits = null; - - currentScissor.set( 0, 0, gl.canvas.width, gl.canvas.height ); - currentViewport.set( 0, 0, gl.canvas.width, gl.canvas.height ); - - colorBuffer.reset(); - depthBuffer.reset(); - stencilBuffer.reset(); - - } - - return { - - buffers: { - color: colorBuffer, - depth: depthBuffer, - stencil: stencilBuffer - }, - - enable: enable, - disable: disable, - - bindFramebuffer: bindFramebuffer, - drawBuffers: drawBuffers, - - useProgram: useProgram, - - setBlending: setBlending, - setMaterial: setMaterial, - - setFlipSided: setFlipSided, - setCullFace: setCullFace, - - setLineWidth: setLineWidth, - setPolygonOffset: setPolygonOffset, - - setScissorTest: setScissorTest, - - activeTexture: activeTexture, - bindTexture: bindTexture, - unbindTexture: unbindTexture, - compressedTexImage2D: compressedTexImage2D, - compressedTexImage3D: compressedTexImage3D, - texImage2D: texImage2D, - texImage3D: texImage3D, - - updateUBOMapping: updateUBOMapping, - uniformBlockBinding: uniformBlockBinding, - - texStorage2D: texStorage2D, - texStorage3D: texStorage3D, - texSubImage2D: texSubImage2D, - texSubImage3D: texSubImage3D, - compressedTexSubImage2D: compressedTexSubImage2D, - compressedTexSubImage3D: compressedTexSubImage3D, - - scissor: scissor, - viewport: viewport, - - reset: reset - - }; - -} - -function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, info ) { - - const isWebGL2 = capabilities.isWebGL2; - const maxTextures = capabilities.maxTextures; - const maxCubemapSize = capabilities.maxCubemapSize; - const maxTextureSize = capabilities.maxTextureSize; - const maxSamples = capabilities.maxSamples; - const multisampledRTTExt = extensions.has( 'WEBGL_multisampled_render_to_texture' ) ? extensions.get( 'WEBGL_multisampled_render_to_texture' ) : null; - const supportsInvalidateFramebuffer = typeof navigator === 'undefined' ? false : /OculusBrowser/g.test( navigator.userAgent ); - - const _videoTextures = new WeakMap(); - let _canvas; - - const _sources = new WeakMap(); // maps WebglTexture objects to instances of Source - - // cordova iOS (as of 5.0) still uses UIWebView, which provides OffscreenCanvas, - // also OffscreenCanvas.getContext("webgl"), but not OffscreenCanvas.getContext("2d")! - // Some implementations may only implement OffscreenCanvas partially (e.g. lacking 2d). - - let useOffscreenCanvas = false; - - try { - - useOffscreenCanvas = typeof OffscreenCanvas !== 'undefined' - // eslint-disable-next-line compat/compat - && ( new OffscreenCanvas( 1, 1 ).getContext( '2d' ) ) !== null; - - } catch ( err ) { - - // Ignore any errors - - } - - function createCanvas( width, height ) { - - // Use OffscreenCanvas when available. Specially needed in web workers - - return useOffscreenCanvas ? - // eslint-disable-next-line compat/compat - new OffscreenCanvas( width, height ) : createElementNS( 'canvas' ); - - } - - function resizeImage( image, needsPowerOfTwo, needsNewCanvas, maxSize ) { - - let scale = 1; - - // handle case if texture exceeds max size - - if ( image.width > maxSize || image.height > maxSize ) { - - scale = maxSize / Math.max( image.width, image.height ); - - } - - // only perform resize if necessary - - if ( scale < 1 || needsPowerOfTwo === true ) { - - // only perform resize for certain image types - - if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || - ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) || - ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) { - - const floor = needsPowerOfTwo ? floorPowerOfTwo : Math.floor; - - const width = floor( scale * image.width ); - const height = floor( scale * image.height ); - - if ( _canvas === undefined ) _canvas = createCanvas( width, height ); - - // cube textures can't reuse the same canvas - - const canvas = needsNewCanvas ? createCanvas( width, height ) : _canvas; - - canvas.width = width; - canvas.height = height; - - const context = canvas.getContext( '2d' ); - context.drawImage( image, 0, 0, width, height ); - - console.warn( 'THREE.WebGLRenderer: Texture has been resized from (' + image.width + 'x' + image.height + ') to (' + width + 'x' + height + ').' ); - - return canvas; - - } else { - - if ( 'data' in image ) { - - console.warn( 'THREE.WebGLRenderer: Image in DataTexture is too big (' + image.width + 'x' + image.height + ').' ); - - } - - return image; - - } - - } - - return image; - - } - - function isPowerOfTwo$1( image ) { - - return isPowerOfTwo( image.width ) && isPowerOfTwo( image.height ); - - } - - function textureNeedsPowerOfTwo( texture ) { - - if ( isWebGL2 ) return false; - - return ( texture.wrapS !== ClampToEdgeWrapping || texture.wrapT !== ClampToEdgeWrapping ) || - ( texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter ); - - } - - function textureNeedsGenerateMipmaps( texture, supportsMips ) { - - return texture.generateMipmaps && supportsMips && - texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter; - - } - - function generateMipmap( target ) { - - _gl.generateMipmap( target ); - - } - - function getInternalFormat( internalFormatName, glFormat, glType, colorSpace, forceLinearTransfer = false ) { - - if ( isWebGL2 === false ) return glFormat; - - if ( internalFormatName !== null ) { - - if ( _gl[ internalFormatName ] !== undefined ) return _gl[ internalFormatName ]; - - console.warn( 'THREE.WebGLRenderer: Attempt to use non-existing WebGL internal format \'' + internalFormatName + '\'' ); - - } - - let internalFormat = glFormat; - - if ( glFormat === _gl.RED ) { - - if ( glType === _gl.FLOAT ) internalFormat = _gl.R32F; - if ( glType === _gl.HALF_FLOAT ) internalFormat = _gl.R16F; - if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = _gl.R8; - - } - - if ( glFormat === _gl.RG ) { - - if ( glType === _gl.FLOAT ) internalFormat = _gl.RG32F; - if ( glType === _gl.HALF_FLOAT ) internalFormat = _gl.RG16F; - if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = _gl.RG8; - - } - - if ( glFormat === _gl.RGBA ) { - - if ( glType === _gl.FLOAT ) internalFormat = _gl.RGBA32F; - if ( glType === _gl.HALF_FLOAT ) internalFormat = _gl.RGBA16F; - if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = ( colorSpace === SRGBColorSpace && forceLinearTransfer === false ) ? _gl.SRGB8_ALPHA8 : _gl.RGBA8; - if ( glType === _gl.UNSIGNED_SHORT_4_4_4_4 ) internalFormat = _gl.RGBA4; - if ( glType === _gl.UNSIGNED_SHORT_5_5_5_1 ) internalFormat = _gl.RGB5_A1; - - } - - if ( internalFormat === _gl.R16F || internalFormat === _gl.R32F || - internalFormat === _gl.RG16F || internalFormat === _gl.RG32F || - internalFormat === _gl.RGBA16F || internalFormat === _gl.RGBA32F ) { - - extensions.get( 'EXT_color_buffer_float' ); - - } - - return internalFormat; - - } - - function getMipLevels( texture, image, supportsMips ) { - - if ( textureNeedsGenerateMipmaps( texture, supportsMips ) === true || ( texture.isFramebufferTexture && texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter ) ) { - - return Math.log2( Math.max( image.width, image.height ) ) + 1; - - } else if ( texture.mipmaps !== undefined && texture.mipmaps.length > 0 ) { - - // user-defined mipmaps - - return texture.mipmaps.length; - - } else if ( texture.isCompressedTexture && Array.isArray( texture.image ) ) { - - return image.mipmaps.length; - - } else { - - // texture without mipmaps (only base level) - - return 1; - - } - - } - - // Fallback filters for non-power-of-2 textures - - function filterFallback( f ) { - - if ( f === NearestFilter || f === NearestMipmapNearestFilter || f === NearestMipmapLinearFilter ) { - - return _gl.NEAREST; - - } - - return _gl.LINEAR; - - } - - // - - function onTextureDispose( event ) { - - const texture = event.target; - - texture.removeEventListener( 'dispose', onTextureDispose ); - - deallocateTexture( texture ); - - if ( texture.isVideoTexture ) { - - _videoTextures.delete( texture ); - - } - - } - - function onRenderTargetDispose( event ) { - - const renderTarget = event.target; - - renderTarget.removeEventListener( 'dispose', onRenderTargetDispose ); - - deallocateRenderTarget( renderTarget ); - - } - - // - - function deallocateTexture( texture ) { - - const textureProperties = properties.get( texture ); - - if ( textureProperties.__webglInit === undefined ) return; - - // check if it's necessary to remove the WebGLTexture object - - const source = texture.source; - const webglTextures = _sources.get( source ); - - if ( webglTextures ) { - - const webglTexture = webglTextures[ textureProperties.__cacheKey ]; - webglTexture.usedTimes --; - - // the WebGLTexture object is not used anymore, remove it - - if ( webglTexture.usedTimes === 0 ) { - - deleteTexture( texture ); - - } - - // remove the weak map entry if no WebGLTexture uses the source anymore - - if ( Object.keys( webglTextures ).length === 0 ) { - - _sources.delete( source ); - - } - - } - - properties.remove( texture ); - - } - - function deleteTexture( texture ) { - - const textureProperties = properties.get( texture ); - _gl.deleteTexture( textureProperties.__webglTexture ); - - const source = texture.source; - const webglTextures = _sources.get( source ); - delete webglTextures[ textureProperties.__cacheKey ]; - - info.memory.textures --; - - } - - function deallocateRenderTarget( renderTarget ) { - - const texture = renderTarget.texture; - - const renderTargetProperties = properties.get( renderTarget ); - const textureProperties = properties.get( texture ); - - if ( textureProperties.__webglTexture !== undefined ) { - - _gl.deleteTexture( textureProperties.__webglTexture ); - - info.memory.textures --; - - } - - if ( renderTarget.depthTexture ) { - - renderTarget.depthTexture.dispose(); - - } - - if ( renderTarget.isWebGLCubeRenderTarget ) { - - for ( let i = 0; i < 6; i ++ ) { - - _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer[ i ] ); - if ( renderTargetProperties.__webglDepthbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthbuffer[ i ] ); - - } - - } else { - - _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer ); - if ( renderTargetProperties.__webglDepthbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthbuffer ); - if ( renderTargetProperties.__webglMultisampledFramebuffer ) _gl.deleteFramebuffer( renderTargetProperties.__webglMultisampledFramebuffer ); - - if ( renderTargetProperties.__webglColorRenderbuffer ) { - - for ( let i = 0; i < renderTargetProperties.__webglColorRenderbuffer.length; i ++ ) { - - if ( renderTargetProperties.__webglColorRenderbuffer[ i ] ) _gl.deleteRenderbuffer( renderTargetProperties.__webglColorRenderbuffer[ i ] ); - - } - - } - - if ( renderTargetProperties.__webglDepthRenderbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthRenderbuffer ); - - } - - if ( renderTarget.isWebGLMultipleRenderTargets ) { - - for ( let i = 0, il = texture.length; i < il; i ++ ) { - - const attachmentProperties = properties.get( texture[ i ] ); - - if ( attachmentProperties.__webglTexture ) { - - _gl.deleteTexture( attachmentProperties.__webglTexture ); - - info.memory.textures --; - - } - - properties.remove( texture[ i ] ); - - } - - } - - properties.remove( texture ); - properties.remove( renderTarget ); - - } - - // - - let textureUnits = 0; - - function resetTextureUnits() { - - textureUnits = 0; - - } - - function allocateTextureUnit() { - - const textureUnit = textureUnits; - - if ( textureUnit >= maxTextures ) { - - console.warn( 'THREE.WebGLTextures: Trying to use ' + textureUnit + ' texture units while this GPU supports only ' + maxTextures ); - - } - - textureUnits += 1; - - return textureUnit; - - } - - function getTextureCacheKey( texture ) { - - const array = []; - - array.push( texture.wrapS ); - array.push( texture.wrapT ); - array.push( texture.wrapR || 0 ); - array.push( texture.magFilter ); - array.push( texture.minFilter ); - array.push( texture.anisotropy ); - array.push( texture.internalFormat ); - array.push( texture.format ); - array.push( texture.type ); - array.push( texture.generateMipmaps ); - array.push( texture.premultiplyAlpha ); - array.push( texture.flipY ); - array.push( texture.unpackAlignment ); - array.push( texture.colorSpace ); - - return array.join(); - - } - - // - - function setTexture2D( texture, slot ) { - - const textureProperties = properties.get( texture ); - - if ( texture.isVideoTexture ) updateVideoTexture( texture ); - - if ( texture.isRenderTargetTexture === false && texture.version > 0 && textureProperties.__version !== texture.version ) { - - const image = texture.image; - - if ( image === null ) { - - console.warn( 'THREE.WebGLRenderer: Texture marked for update but no image data found.' ); - - } else if ( image.complete === false ) { - - console.warn( 'THREE.WebGLRenderer: Texture marked for update but image is incomplete' ); - - } else { - - uploadTexture( textureProperties, texture, slot ); - return; - - } - - } - - state.bindTexture( _gl.TEXTURE_2D, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); - - } - - function setTexture2DArray( texture, slot ) { - - const textureProperties = properties.get( texture ); - - if ( texture.version > 0 && textureProperties.__version !== texture.version ) { - - uploadTexture( textureProperties, texture, slot ); - return; - - } - - state.bindTexture( _gl.TEXTURE_2D_ARRAY, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); - - } - - function setTexture3D( texture, slot ) { - - const textureProperties = properties.get( texture ); - - if ( texture.version > 0 && textureProperties.__version !== texture.version ) { - - uploadTexture( textureProperties, texture, slot ); - return; - - } - - state.bindTexture( _gl.TEXTURE_3D, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); - - } - - function setTextureCube( texture, slot ) { - - const textureProperties = properties.get( texture ); - - if ( texture.version > 0 && textureProperties.__version !== texture.version ) { - - uploadCubeTexture( textureProperties, texture, slot ); - return; - - } - - state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); - - } - - const wrappingToGL = { - [ RepeatWrapping ]: _gl.REPEAT, - [ ClampToEdgeWrapping ]: _gl.CLAMP_TO_EDGE, - [ MirroredRepeatWrapping ]: _gl.MIRRORED_REPEAT - }; - - const filterToGL = { - [ NearestFilter ]: _gl.NEAREST, - [ NearestMipmapNearestFilter ]: _gl.NEAREST_MIPMAP_NEAREST, - [ NearestMipmapLinearFilter ]: _gl.NEAREST_MIPMAP_LINEAR, - - [ LinearFilter ]: _gl.LINEAR, - [ LinearMipmapNearestFilter ]: _gl.LINEAR_MIPMAP_NEAREST, - [ LinearMipmapLinearFilter ]: _gl.LINEAR_MIPMAP_LINEAR - }; - - const compareToGL = { - [ NeverCompare ]: _gl.NEVER, - [ AlwaysCompare ]: _gl.ALWAYS, - [ LessCompare ]: _gl.LESS, - [ LessEqualCompare ]: _gl.LEQUAL, - [ EqualCompare ]: _gl.EQUAL, - [ GreaterEqualCompare ]: _gl.GEQUAL, - [ GreaterCompare ]: _gl.GREATER, - [ NotEqualCompare ]: _gl.NOTEQUAL - }; - - function setTextureParameters( textureType, texture, supportsMips ) { - - if ( supportsMips ) { - - _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_S, wrappingToGL[ texture.wrapS ] ); - _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_T, wrappingToGL[ texture.wrapT ] ); - - if ( textureType === _gl.TEXTURE_3D || textureType === _gl.TEXTURE_2D_ARRAY ) { - - _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_R, wrappingToGL[ texture.wrapR ] ); - - } - - _gl.texParameteri( textureType, _gl.TEXTURE_MAG_FILTER, filterToGL[ texture.magFilter ] ); - _gl.texParameteri( textureType, _gl.TEXTURE_MIN_FILTER, filterToGL[ texture.minFilter ] ); - - } else { - - _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_S, _gl.CLAMP_TO_EDGE ); - _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_T, _gl.CLAMP_TO_EDGE ); - - if ( textureType === _gl.TEXTURE_3D || textureType === _gl.TEXTURE_2D_ARRAY ) { - - _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_R, _gl.CLAMP_TO_EDGE ); - - } - - if ( texture.wrapS !== ClampToEdgeWrapping || texture.wrapT !== ClampToEdgeWrapping ) { - - console.warn( 'THREE.WebGLRenderer: Texture is not power of two. Texture.wrapS and Texture.wrapT should be set to THREE.ClampToEdgeWrapping.' ); - - } - - _gl.texParameteri( textureType, _gl.TEXTURE_MAG_FILTER, filterFallback( texture.magFilter ) ); - _gl.texParameteri( textureType, _gl.TEXTURE_MIN_FILTER, filterFallback( texture.minFilter ) ); - - if ( texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter ) { - - console.warn( 'THREE.WebGLRenderer: Texture is not power of two. Texture.minFilter should be set to THREE.NearestFilter or THREE.LinearFilter.' ); - - } - - } - - if ( texture.compareFunction ) { - - _gl.texParameteri( textureType, _gl.TEXTURE_COMPARE_MODE, _gl.COMPARE_REF_TO_TEXTURE ); - _gl.texParameteri( textureType, _gl.TEXTURE_COMPARE_FUNC, compareToGL[ texture.compareFunction ] ); - - } - - if ( extensions.has( 'EXT_texture_filter_anisotropic' ) === true ) { - - const extension = extensions.get( 'EXT_texture_filter_anisotropic' ); - - if ( texture.magFilter === NearestFilter ) return; - if ( texture.minFilter !== NearestMipmapLinearFilter && texture.minFilter !== LinearMipmapLinearFilter ) return; - if ( texture.type === FloatType && extensions.has( 'OES_texture_float_linear' ) === false ) return; // verify extension for WebGL 1 and WebGL 2 - if ( isWebGL2 === false && ( texture.type === HalfFloatType && extensions.has( 'OES_texture_half_float_linear' ) === false ) ) return; // verify extension for WebGL 1 only - - if ( texture.anisotropy > 1 || properties.get( texture ).__currentAnisotropy ) { - - _gl.texParameterf( textureType, extension.TEXTURE_MAX_ANISOTROPY_EXT, Math.min( texture.anisotropy, capabilities.getMaxAnisotropy() ) ); - properties.get( texture ).__currentAnisotropy = texture.anisotropy; - - } - - } - - } - - function initTexture( textureProperties, texture ) { - - let forceUpload = false; - - if ( textureProperties.__webglInit === undefined ) { - - textureProperties.__webglInit = true; - - texture.addEventListener( 'dispose', onTextureDispose ); - - } - - // create Source <-> WebGLTextures mapping if necessary - - const source = texture.source; - let webglTextures = _sources.get( source ); - - if ( webglTextures === undefined ) { - - webglTextures = {}; - _sources.set( source, webglTextures ); - - } - - // check if there is already a WebGLTexture object for the given texture parameters - - const textureCacheKey = getTextureCacheKey( texture ); - - if ( textureCacheKey !== textureProperties.__cacheKey ) { - - // if not, create a new instance of WebGLTexture - - if ( webglTextures[ textureCacheKey ] === undefined ) { - - // create new entry - - webglTextures[ textureCacheKey ] = { - texture: _gl.createTexture(), - usedTimes: 0 - }; - - info.memory.textures ++; - - // when a new instance of WebGLTexture was created, a texture upload is required - // even if the image contents are identical - - forceUpload = true; - - } - - webglTextures[ textureCacheKey ].usedTimes ++; - - // every time the texture cache key changes, it's necessary to check if an instance of - // WebGLTexture can be deleted in order to avoid a memory leak. - - const webglTexture = webglTextures[ textureProperties.__cacheKey ]; - - if ( webglTexture !== undefined ) { - - webglTextures[ textureProperties.__cacheKey ].usedTimes --; - - if ( webglTexture.usedTimes === 0 ) { - - deleteTexture( texture ); - - } - - } - - // store references to cache key and WebGLTexture object - - textureProperties.__cacheKey = textureCacheKey; - textureProperties.__webglTexture = webglTextures[ textureCacheKey ].texture; - - } - - return forceUpload; - - } - - function uploadTexture( textureProperties, texture, slot ) { - - let textureType = _gl.TEXTURE_2D; - - if ( texture.isDataArrayTexture || texture.isCompressedArrayTexture ) textureType = _gl.TEXTURE_2D_ARRAY; - if ( texture.isData3DTexture ) textureType = _gl.TEXTURE_3D; - - const forceUpload = initTexture( textureProperties, texture ); - const source = texture.source; - - state.bindTexture( textureType, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); - - const sourceProperties = properties.get( source ); - - if ( source.version !== sourceProperties.__version || forceUpload === true ) { - - state.activeTexture( _gl.TEXTURE0 + slot ); - - _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY ); - _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha ); - _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, texture.unpackAlignment ); - _gl.pixelStorei( _gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, _gl.NONE ); - - const needsPowerOfTwo = textureNeedsPowerOfTwo( texture ) && isPowerOfTwo$1( texture.image ) === false; - let image = resizeImage( texture.image, needsPowerOfTwo, false, maxTextureSize ); - image = verifyColorSpace( texture, image ); - - const supportsMips = isPowerOfTwo$1( image ) || isWebGL2, - glFormat = utils.convert( texture.format, texture.colorSpace ); - - let glType = utils.convert( texture.type ), - glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace ); - - setTextureParameters( textureType, texture, supportsMips ); - - let mipmap; - const mipmaps = texture.mipmaps; - - const useTexStorage = ( isWebGL2 && texture.isVideoTexture !== true ); - const allocateMemory = ( sourceProperties.__version === undefined ) || ( forceUpload === true ); - const levels = getMipLevels( texture, image, supportsMips ); - - if ( texture.isDepthTexture ) { - - // populate depth texture with dummy data - - glInternalFormat = _gl.DEPTH_COMPONENT; - - if ( isWebGL2 ) { - - if ( texture.type === FloatType ) { - - glInternalFormat = _gl.DEPTH_COMPONENT32F; - - } else if ( texture.type === UnsignedIntType ) { - - glInternalFormat = _gl.DEPTH_COMPONENT24; - - } else if ( texture.type === UnsignedInt248Type ) { - - glInternalFormat = _gl.DEPTH24_STENCIL8; - - } else { - - glInternalFormat = _gl.DEPTH_COMPONENT16; // WebGL2 requires sized internalformat for glTexImage2D - - } - - } else { - - if ( texture.type === FloatType ) { - - console.error( 'WebGLRenderer: Floating point depth texture requires WebGL2.' ); - - } - - } - - // validation checks for WebGL 1 - - if ( texture.format === DepthFormat && glInternalFormat === _gl.DEPTH_COMPONENT ) { - - // The error INVALID_OPERATION is generated by texImage2D if format and internalformat are - // DEPTH_COMPONENT and type is not UNSIGNED_SHORT or UNSIGNED_INT - // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/) - if ( texture.type !== UnsignedShortType && texture.type !== UnsignedIntType ) { - - console.warn( 'THREE.WebGLRenderer: Use UnsignedShortType or UnsignedIntType for DepthFormat DepthTexture.' ); - - texture.type = UnsignedIntType; - glType = utils.convert( texture.type ); - - } - - } - - if ( texture.format === DepthStencilFormat && glInternalFormat === _gl.DEPTH_COMPONENT ) { - - // Depth stencil textures need the DEPTH_STENCIL internal format - // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/) - glInternalFormat = _gl.DEPTH_STENCIL; - - // The error INVALID_OPERATION is generated by texImage2D if format and internalformat are - // DEPTH_STENCIL and type is not UNSIGNED_INT_24_8_WEBGL. - // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/) - if ( texture.type !== UnsignedInt248Type ) { - - console.warn( 'THREE.WebGLRenderer: Use UnsignedInt248Type for DepthStencilFormat DepthTexture.' ); - - texture.type = UnsignedInt248Type; - glType = utils.convert( texture.type ); - - } - - } - - // - - if ( allocateMemory ) { - - if ( useTexStorage ) { - - state.texStorage2D( _gl.TEXTURE_2D, 1, glInternalFormat, image.width, image.height ); - - } else { - - state.texImage2D( _gl.TEXTURE_2D, 0, glInternalFormat, image.width, image.height, 0, glFormat, glType, null ); - - } - - } - - } else if ( texture.isDataTexture ) { - - // use manually created mipmaps if available - // if there are no manual mipmaps - // set 0 level mipmap and then use GL to generate other mipmap levels - - if ( mipmaps.length > 0 && supportsMips ) { - - if ( useTexStorage && allocateMemory ) { - - state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, mipmaps[ 0 ].width, mipmaps[ 0 ].height ); - - } - - for ( let i = 0, il = mipmaps.length; i < il; i ++ ) { - - mipmap = mipmaps[ i ]; - - if ( useTexStorage ) { - - state.texSubImage2D( _gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, glType, mipmap.data ); - - } else { - - state.texImage2D( _gl.TEXTURE_2D, i, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); - - } - - } - - texture.generateMipmaps = false; - - } else { - - if ( useTexStorage ) { - - if ( allocateMemory ) { - - state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, image.width, image.height ); - - } - - state.texSubImage2D( _gl.TEXTURE_2D, 0, 0, 0, image.width, image.height, glFormat, glType, image.data ); - - } else { - - state.texImage2D( _gl.TEXTURE_2D, 0, glInternalFormat, image.width, image.height, 0, glFormat, glType, image.data ); - - } - - } - - } else if ( texture.isCompressedTexture ) { - - if ( texture.isCompressedArrayTexture ) { - - if ( useTexStorage && allocateMemory ) { - - state.texStorage3D( _gl.TEXTURE_2D_ARRAY, levels, glInternalFormat, mipmaps[ 0 ].width, mipmaps[ 0 ].height, image.depth ); - - } - - for ( let i = 0, il = mipmaps.length; i < il; i ++ ) { - - mipmap = mipmaps[ i ]; - - if ( texture.format !== RGBAFormat ) { - - if ( glFormat !== null ) { - - if ( useTexStorage ) { - - state.compressedTexSubImage3D( _gl.TEXTURE_2D_ARRAY, i, 0, 0, 0, mipmap.width, mipmap.height, image.depth, glFormat, mipmap.data, 0, 0 ); - - } else { - - state.compressedTexImage3D( _gl.TEXTURE_2D_ARRAY, i, glInternalFormat, mipmap.width, mipmap.height, image.depth, 0, mipmap.data, 0, 0 ); - - } - - } else { - - console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()' ); - - } - - } else { - - if ( useTexStorage ) { - - state.texSubImage3D( _gl.TEXTURE_2D_ARRAY, i, 0, 0, 0, mipmap.width, mipmap.height, image.depth, glFormat, glType, mipmap.data ); - - } else { - - state.texImage3D( _gl.TEXTURE_2D_ARRAY, i, glInternalFormat, mipmap.width, mipmap.height, image.depth, 0, glFormat, glType, mipmap.data ); - - } - - } - - } - - } else { - - if ( useTexStorage && allocateMemory ) { - - state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, mipmaps[ 0 ].width, mipmaps[ 0 ].height ); - - } - - for ( let i = 0, il = mipmaps.length; i < il; i ++ ) { - - mipmap = mipmaps[ i ]; - - if ( texture.format !== RGBAFormat ) { - - if ( glFormat !== null ) { - - if ( useTexStorage ) { - - state.compressedTexSubImage2D( _gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, mipmap.data ); - - } else { - - state.compressedTexImage2D( _gl.TEXTURE_2D, i, glInternalFormat, mipmap.width, mipmap.height, 0, mipmap.data ); - - } - - } else { - - console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()' ); - - } - - } else { - - if ( useTexStorage ) { - - state.texSubImage2D( _gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, glType, mipmap.data ); - - } else { - - state.texImage2D( _gl.TEXTURE_2D, i, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); - - } - - } - - } - - } - - } else if ( texture.isDataArrayTexture ) { - - if ( useTexStorage ) { - - if ( allocateMemory ) { - - state.texStorage3D( _gl.TEXTURE_2D_ARRAY, levels, glInternalFormat, image.width, image.height, image.depth ); - - } - - state.texSubImage3D( _gl.TEXTURE_2D_ARRAY, 0, 0, 0, 0, image.width, image.height, image.depth, glFormat, glType, image.data ); - - } else { - - state.texImage3D( _gl.TEXTURE_2D_ARRAY, 0, glInternalFormat, image.width, image.height, image.depth, 0, glFormat, glType, image.data ); - - } - - } else if ( texture.isData3DTexture ) { - - if ( useTexStorage ) { - - if ( allocateMemory ) { - - state.texStorage3D( _gl.TEXTURE_3D, levels, glInternalFormat, image.width, image.height, image.depth ); - - } - - state.texSubImage3D( _gl.TEXTURE_3D, 0, 0, 0, 0, image.width, image.height, image.depth, glFormat, glType, image.data ); - - } else { - - state.texImage3D( _gl.TEXTURE_3D, 0, glInternalFormat, image.width, image.height, image.depth, 0, glFormat, glType, image.data ); - - } - - } else if ( texture.isFramebufferTexture ) { - - if ( allocateMemory ) { - - if ( useTexStorage ) { - - state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, image.width, image.height ); - - } else { - - let width = image.width, height = image.height; - - for ( let i = 0; i < levels; i ++ ) { - - state.texImage2D( _gl.TEXTURE_2D, i, glInternalFormat, width, height, 0, glFormat, glType, null ); - - width >>= 1; - height >>= 1; - - } - - } - - } - - } else { - - // regular Texture (image, video, canvas) - - // use manually created mipmaps if available - // if there are no manual mipmaps - // set 0 level mipmap and then use GL to generate other mipmap levels - - if ( mipmaps.length > 0 && supportsMips ) { - - if ( useTexStorage && allocateMemory ) { - - state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, mipmaps[ 0 ].width, mipmaps[ 0 ].height ); - - } - - for ( let i = 0, il = mipmaps.length; i < il; i ++ ) { - - mipmap = mipmaps[ i ]; - - if ( useTexStorage ) { - - state.texSubImage2D( _gl.TEXTURE_2D, i, 0, 0, glFormat, glType, mipmap ); - - } else { - - state.texImage2D( _gl.TEXTURE_2D, i, glInternalFormat, glFormat, glType, mipmap ); - - } - - } - - texture.generateMipmaps = false; - - } else { - - if ( useTexStorage ) { - - if ( allocateMemory ) { - - state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, image.width, image.height ); - - } - - state.texSubImage2D( _gl.TEXTURE_2D, 0, 0, 0, glFormat, glType, image ); - - } else { - - state.texImage2D( _gl.TEXTURE_2D, 0, glInternalFormat, glFormat, glType, image ); - - } - - } - - } - - if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) { - - generateMipmap( textureType ); - - } - - sourceProperties.__version = source.version; - - if ( texture.onUpdate ) texture.onUpdate( texture ); - - } - - textureProperties.__version = texture.version; - - } - - function uploadCubeTexture( textureProperties, texture, slot ) { - - if ( texture.image.length !== 6 ) return; - - const forceUpload = initTexture( textureProperties, texture ); - const source = texture.source; - - state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); - - const sourceProperties = properties.get( source ); - - if ( source.version !== sourceProperties.__version || forceUpload === true ) { - - state.activeTexture( _gl.TEXTURE0 + slot ); - - _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY ); - _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha ); - _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, texture.unpackAlignment ); - _gl.pixelStorei( _gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, _gl.NONE ); - - const isCompressed = ( texture.isCompressedTexture || texture.image[ 0 ].isCompressedTexture ); - const isDataTexture = ( texture.image[ 0 ] && texture.image[ 0 ].isDataTexture ); - - const cubeImage = []; - - for ( let i = 0; i < 6; i ++ ) { - - if ( ! isCompressed && ! isDataTexture ) { - - cubeImage[ i ] = resizeImage( texture.image[ i ], false, true, maxCubemapSize ); - - } else { - - cubeImage[ i ] = isDataTexture ? texture.image[ i ].image : texture.image[ i ]; - - } - - cubeImage[ i ] = verifyColorSpace( texture, cubeImage[ i ] ); - - } - - const image = cubeImage[ 0 ], - supportsMips = isPowerOfTwo$1( image ) || isWebGL2, - glFormat = utils.convert( texture.format, texture.colorSpace ), - glType = utils.convert( texture.type ), - glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace ); - - const useTexStorage = ( isWebGL2 && texture.isVideoTexture !== true ); - const allocateMemory = ( sourceProperties.__version === undefined ) || ( forceUpload === true ); - let levels = getMipLevels( texture, image, supportsMips ); - - setTextureParameters( _gl.TEXTURE_CUBE_MAP, texture, supportsMips ); - - let mipmaps; - - if ( isCompressed ) { - - if ( useTexStorage && allocateMemory ) { - - state.texStorage2D( _gl.TEXTURE_CUBE_MAP, levels, glInternalFormat, image.width, image.height ); - - } - - for ( let i = 0; i < 6; i ++ ) { - - mipmaps = cubeImage[ i ].mipmaps; - - for ( let j = 0; j < mipmaps.length; j ++ ) { - - const mipmap = mipmaps[ j ]; - - if ( texture.format !== RGBAFormat ) { - - if ( glFormat !== null ) { - - if ( useTexStorage ) { - - state.compressedTexSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, 0, 0, mipmap.width, mipmap.height, glFormat, mipmap.data ); - - } else { - - state.compressedTexImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glInternalFormat, mipmap.width, mipmap.height, 0, mipmap.data ); - - } - - } else { - - console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .setTextureCube()' ); - - } - - } else { - - if ( useTexStorage ) { - - state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, 0, 0, mipmap.width, mipmap.height, glFormat, glType, mipmap.data ); - - } else { - - state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); - - } - - } - - } - - } - - } else { - - mipmaps = texture.mipmaps; - - if ( useTexStorage && allocateMemory ) { - - // TODO: Uniformly handle mipmap definitions - // Normal textures and compressed cube textures define base level + mips with their mipmap array - // Uncompressed cube textures use their mipmap array only for mips (no base level) - - if ( mipmaps.length > 0 ) levels ++; - - state.texStorage2D( _gl.TEXTURE_CUBE_MAP, levels, glInternalFormat, cubeImage[ 0 ].width, cubeImage[ 0 ].height ); - - } - - for ( let i = 0; i < 6; i ++ ) { - - if ( isDataTexture ) { - - if ( useTexStorage ) { - - state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, 0, 0, cubeImage[ i ].width, cubeImage[ i ].height, glFormat, glType, cubeImage[ i ].data ); - - } else { - - state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, cubeImage[ i ].width, cubeImage[ i ].height, 0, glFormat, glType, cubeImage[ i ].data ); - - } - - for ( let j = 0; j < mipmaps.length; j ++ ) { - - const mipmap = mipmaps[ j ]; - const mipmapImage = mipmap.image[ i ].image; - - if ( useTexStorage ) { - - state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, 0, 0, mipmapImage.width, mipmapImage.height, glFormat, glType, mipmapImage.data ); - - } else { - - state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, glInternalFormat, mipmapImage.width, mipmapImage.height, 0, glFormat, glType, mipmapImage.data ); - - } - - } - - } else { - - if ( useTexStorage ) { - - state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, 0, 0, glFormat, glType, cubeImage[ i ] ); - - } else { - - state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, glFormat, glType, cubeImage[ i ] ); - - } - - for ( let j = 0; j < mipmaps.length; j ++ ) { - - const mipmap = mipmaps[ j ]; - - if ( useTexStorage ) { - - state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, 0, 0, glFormat, glType, mipmap.image[ i ] ); - - } else { - - state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, glInternalFormat, glFormat, glType, mipmap.image[ i ] ); - - } - - } - - } - - } - - } - - if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) { - - // We assume images for cube map have the same size. - generateMipmap( _gl.TEXTURE_CUBE_MAP ); - - } - - sourceProperties.__version = source.version; - - if ( texture.onUpdate ) texture.onUpdate( texture ); - - } - - textureProperties.__version = texture.version; - - } - - // Render targets - - // Setup storage for target texture and bind it to correct framebuffer - function setupFrameBufferTexture( framebuffer, renderTarget, texture, attachment, textureTarget ) { - - const glFormat = utils.convert( texture.format, texture.colorSpace ); - const glType = utils.convert( texture.type ); - const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace ); - const renderTargetProperties = properties.get( renderTarget ); - - if ( ! renderTargetProperties.__hasExternalTextures ) { - - if ( textureTarget === _gl.TEXTURE_3D || textureTarget === _gl.TEXTURE_2D_ARRAY ) { - - state.texImage3D( textureTarget, 0, glInternalFormat, renderTarget.width, renderTarget.height, renderTarget.depth, 0, glFormat, glType, null ); - - } else { - - state.texImage2D( textureTarget, 0, glInternalFormat, renderTarget.width, renderTarget.height, 0, glFormat, glType, null ); - - } - - } - - state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); - - if ( useMultisampledRTT( renderTarget ) ) { - - multisampledRTTExt.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, attachment, textureTarget, properties.get( texture ).__webglTexture, 0, getRenderTargetSamples( renderTarget ) ); - - } else if ( textureTarget === _gl.TEXTURE_2D || ( textureTarget >= _gl.TEXTURE_CUBE_MAP_POSITIVE_X && textureTarget <= _gl.TEXTURE_CUBE_MAP_NEGATIVE_Z ) ) { // see #24753 - - _gl.framebufferTexture2D( _gl.FRAMEBUFFER, attachment, textureTarget, properties.get( texture ).__webglTexture, 0 ); - - } - - state.bindFramebuffer( _gl.FRAMEBUFFER, null ); - - } - - - // Setup storage for internal depth/stencil buffers and bind to correct framebuffer - function setupRenderBufferStorage( renderbuffer, renderTarget, isMultisample ) { - - _gl.bindRenderbuffer( _gl.RENDERBUFFER, renderbuffer ); - - if ( renderTarget.depthBuffer && ! renderTarget.stencilBuffer ) { - - let glInternalFormat = _gl.DEPTH_COMPONENT16; - - if ( isMultisample || useMultisampledRTT( renderTarget ) ) { - - const depthTexture = renderTarget.depthTexture; - - if ( depthTexture && depthTexture.isDepthTexture ) { - - if ( depthTexture.type === FloatType ) { - - glInternalFormat = _gl.DEPTH_COMPONENT32F; - - } else if ( depthTexture.type === UnsignedIntType ) { - - glInternalFormat = _gl.DEPTH_COMPONENT24; - - } - - } - - const samples = getRenderTargetSamples( renderTarget ); - - if ( useMultisampledRTT( renderTarget ) ) { - - multisampledRTTExt.renderbufferStorageMultisampleEXT( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); - - } else { - - _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); - - } - - } else { - - _gl.renderbufferStorage( _gl.RENDERBUFFER, glInternalFormat, renderTarget.width, renderTarget.height ); - - } - - _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer ); - - } else if ( renderTarget.depthBuffer && renderTarget.stencilBuffer ) { - - const samples = getRenderTargetSamples( renderTarget ); - - if ( isMultisample && useMultisampledRTT( renderTarget ) === false ) { - - _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, _gl.DEPTH24_STENCIL8, renderTarget.width, renderTarget.height ); - - } else if ( useMultisampledRTT( renderTarget ) ) { - - multisampledRTTExt.renderbufferStorageMultisampleEXT( _gl.RENDERBUFFER, samples, _gl.DEPTH24_STENCIL8, renderTarget.width, renderTarget.height ); - - } else { - - _gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.DEPTH_STENCIL, renderTarget.width, renderTarget.height ); - - } - - - _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer ); - - } else { - - const textures = renderTarget.isWebGLMultipleRenderTargets === true ? renderTarget.texture : [ renderTarget.texture ]; - - for ( let i = 0; i < textures.length; i ++ ) { - - const texture = textures[ i ]; - - const glFormat = utils.convert( texture.format, texture.colorSpace ); - const glType = utils.convert( texture.type ); - const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace ); - const samples = getRenderTargetSamples( renderTarget ); - - if ( isMultisample && useMultisampledRTT( renderTarget ) === false ) { - - _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); - - } else if ( useMultisampledRTT( renderTarget ) ) { - - multisampledRTTExt.renderbufferStorageMultisampleEXT( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); - - } else { - - _gl.renderbufferStorage( _gl.RENDERBUFFER, glInternalFormat, renderTarget.width, renderTarget.height ); - - } - - } - - } - - _gl.bindRenderbuffer( _gl.RENDERBUFFER, null ); - - } - - // Setup resources for a Depth Texture for a FBO (needs an extension) - function setupDepthTexture( framebuffer, renderTarget ) { - - const isCube = ( renderTarget && renderTarget.isWebGLCubeRenderTarget ); - if ( isCube ) throw new Error( 'Depth Texture with cube render targets is not supported' ); - - state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); - - if ( ! ( renderTarget.depthTexture && renderTarget.depthTexture.isDepthTexture ) ) { - - throw new Error( 'renderTarget.depthTexture must be an instance of THREE.DepthTexture' ); - - } - - // upload an empty depth texture with framebuffer size - if ( ! properties.get( renderTarget.depthTexture ).__webglTexture || - renderTarget.depthTexture.image.width !== renderTarget.width || - renderTarget.depthTexture.image.height !== renderTarget.height ) { - - renderTarget.depthTexture.image.width = renderTarget.width; - renderTarget.depthTexture.image.height = renderTarget.height; - renderTarget.depthTexture.needsUpdate = true; - - } - - setTexture2D( renderTarget.depthTexture, 0 ); - - const webglDepthTexture = properties.get( renderTarget.depthTexture ).__webglTexture; - const samples = getRenderTargetSamples( renderTarget ); - - if ( renderTarget.depthTexture.format === DepthFormat ) { - - if ( useMultisampledRTT( renderTarget ) ) { - - multisampledRTTExt.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0, samples ); - - } else { - - _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0 ); - - } - - } else if ( renderTarget.depthTexture.format === DepthStencilFormat ) { - - if ( useMultisampledRTT( renderTarget ) ) { - - multisampledRTTExt.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0, samples ); - - } else { - - _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0 ); - - } - - } else { - - throw new Error( 'Unknown depthTexture format' ); - - } - - } - - // Setup GL resources for a non-texture depth buffer - function setupDepthRenderbuffer( renderTarget ) { - - const renderTargetProperties = properties.get( renderTarget ); - const isCube = ( renderTarget.isWebGLCubeRenderTarget === true ); - - if ( renderTarget.depthTexture && ! renderTargetProperties.__autoAllocateDepthBuffer ) { - - if ( isCube ) throw new Error( 'target.depthTexture not supported in Cube render targets' ); - - setupDepthTexture( renderTargetProperties.__webglFramebuffer, renderTarget ); - - } else { - - if ( isCube ) { - - renderTargetProperties.__webglDepthbuffer = []; - - for ( let i = 0; i < 6; i ++ ) { - - state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer[ i ] ); - renderTargetProperties.__webglDepthbuffer[ i ] = _gl.createRenderbuffer(); - setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer[ i ], renderTarget, false ); - - } - - } else { - - state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); - renderTargetProperties.__webglDepthbuffer = _gl.createRenderbuffer(); - setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer, renderTarget, false ); - - } - - } - - state.bindFramebuffer( _gl.FRAMEBUFFER, null ); - - } - - // rebind framebuffer with external textures - function rebindTextures( renderTarget, colorTexture, depthTexture ) { - - const renderTargetProperties = properties.get( renderTarget ); - - if ( colorTexture !== undefined ) { - - setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, renderTarget.texture, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D ); - - } - - if ( depthTexture !== undefined ) { - - setupDepthRenderbuffer( renderTarget ); - - } - - } - - // Set up GL resources for the render target - function setupRenderTarget( renderTarget ) { - - const texture = renderTarget.texture; - - const renderTargetProperties = properties.get( renderTarget ); - const textureProperties = properties.get( texture ); - - renderTarget.addEventListener( 'dispose', onRenderTargetDispose ); - - if ( renderTarget.isWebGLMultipleRenderTargets !== true ) { - - if ( textureProperties.__webglTexture === undefined ) { - - textureProperties.__webglTexture = _gl.createTexture(); - - } - - textureProperties.__version = texture.version; - info.memory.textures ++; - - } - - const isCube = ( renderTarget.isWebGLCubeRenderTarget === true ); - const isMultipleRenderTargets = ( renderTarget.isWebGLMultipleRenderTargets === true ); - const supportsMips = isPowerOfTwo$1( renderTarget ) || isWebGL2; - - // Setup framebuffer - - if ( isCube ) { - - renderTargetProperties.__webglFramebuffer = []; - - for ( let i = 0; i < 6; i ++ ) { - - renderTargetProperties.__webglFramebuffer[ i ] = _gl.createFramebuffer(); - - } - - } else { - - renderTargetProperties.__webglFramebuffer = _gl.createFramebuffer(); - - if ( isMultipleRenderTargets ) { - - if ( capabilities.drawBuffers ) { - - const textures = renderTarget.texture; - - for ( let i = 0, il = textures.length; i < il; i ++ ) { - - const attachmentProperties = properties.get( textures[ i ] ); - - if ( attachmentProperties.__webglTexture === undefined ) { - - attachmentProperties.__webglTexture = _gl.createTexture(); - - info.memory.textures ++; - - } - - } - - } else { - - console.warn( 'THREE.WebGLRenderer: WebGLMultipleRenderTargets can only be used with WebGL2 or WEBGL_draw_buffers extension.' ); - - } - - } - - if ( ( isWebGL2 && renderTarget.samples > 0 ) && useMultisampledRTT( renderTarget ) === false ) { - - const textures = isMultipleRenderTargets ? texture : [ texture ]; - - renderTargetProperties.__webglMultisampledFramebuffer = _gl.createFramebuffer(); - renderTargetProperties.__webglColorRenderbuffer = []; - - state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); - - for ( let i = 0; i < textures.length; i ++ ) { - - const texture = textures[ i ]; - renderTargetProperties.__webglColorRenderbuffer[ i ] = _gl.createRenderbuffer(); - - _gl.bindRenderbuffer( _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] ); - - const glFormat = utils.convert( texture.format, texture.colorSpace ); - const glType = utils.convert( texture.type ); - const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace, renderTarget.isXRRenderTarget === true ); - const samples = getRenderTargetSamples( renderTarget ); - _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); - - _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] ); - - } - - _gl.bindRenderbuffer( _gl.RENDERBUFFER, null ); - - if ( renderTarget.depthBuffer ) { - - renderTargetProperties.__webglDepthRenderbuffer = _gl.createRenderbuffer(); - setupRenderBufferStorage( renderTargetProperties.__webglDepthRenderbuffer, renderTarget, true ); - - } - - state.bindFramebuffer( _gl.FRAMEBUFFER, null ); - - } - - } - - // Setup color buffer - - if ( isCube ) { - - state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__webglTexture ); - setTextureParameters( _gl.TEXTURE_CUBE_MAP, texture, supportsMips ); - - for ( let i = 0; i < 6; i ++ ) { - - setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer[ i ], renderTarget, texture, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i ); - - } - - if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) { - - generateMipmap( _gl.TEXTURE_CUBE_MAP ); - - } - - state.unbindTexture(); - - } else if ( isMultipleRenderTargets ) { - - const textures = renderTarget.texture; - - for ( let i = 0, il = textures.length; i < il; i ++ ) { - - const attachment = textures[ i ]; - const attachmentProperties = properties.get( attachment ); - - state.bindTexture( _gl.TEXTURE_2D, attachmentProperties.__webglTexture ); - setTextureParameters( _gl.TEXTURE_2D, attachment, supportsMips ); - setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, attachment, _gl.COLOR_ATTACHMENT0 + i, _gl.TEXTURE_2D ); - - if ( textureNeedsGenerateMipmaps( attachment, supportsMips ) ) { - - generateMipmap( _gl.TEXTURE_2D ); - - } - - } - - state.unbindTexture(); - - } else { - - let glTextureType = _gl.TEXTURE_2D; - - if ( renderTarget.isWebGL3DRenderTarget || renderTarget.isWebGLArrayRenderTarget ) { - - if ( isWebGL2 ) { - - glTextureType = renderTarget.isWebGL3DRenderTarget ? _gl.TEXTURE_3D : _gl.TEXTURE_2D_ARRAY; - - } else { - - console.error( 'THREE.WebGLTextures: THREE.Data3DTexture and THREE.DataArrayTexture only supported with WebGL2.' ); - - } - - } - - state.bindTexture( glTextureType, textureProperties.__webglTexture ); - setTextureParameters( glTextureType, texture, supportsMips ); - setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, texture, _gl.COLOR_ATTACHMENT0, glTextureType ); - - if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) { - - generateMipmap( glTextureType ); - - } - - state.unbindTexture(); - - } - - // Setup depth and stencil buffers - - if ( renderTarget.depthBuffer ) { - - setupDepthRenderbuffer( renderTarget ); - - } - - } - - function updateRenderTargetMipmap( renderTarget ) { - - const supportsMips = isPowerOfTwo$1( renderTarget ) || isWebGL2; - - const textures = renderTarget.isWebGLMultipleRenderTargets === true ? renderTarget.texture : [ renderTarget.texture ]; - - for ( let i = 0, il = textures.length; i < il; i ++ ) { - - const texture = textures[ i ]; - - if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) { - - const target = renderTarget.isWebGLCubeRenderTarget ? _gl.TEXTURE_CUBE_MAP : _gl.TEXTURE_2D; - const webglTexture = properties.get( texture ).__webglTexture; - - state.bindTexture( target, webglTexture ); - generateMipmap( target ); - state.unbindTexture(); - - } - - } - - } - - function updateMultisampleRenderTarget( renderTarget ) { - - if ( ( isWebGL2 && renderTarget.samples > 0 ) && useMultisampledRTT( renderTarget ) === false ) { - - const textures = renderTarget.isWebGLMultipleRenderTargets ? renderTarget.texture : [ renderTarget.texture ]; - const width = renderTarget.width; - const height = renderTarget.height; - let mask = _gl.COLOR_BUFFER_BIT; - const invalidationArray = []; - const depthStyle = renderTarget.stencilBuffer ? _gl.DEPTH_STENCIL_ATTACHMENT : _gl.DEPTH_ATTACHMENT; - const renderTargetProperties = properties.get( renderTarget ); - const isMultipleRenderTargets = ( renderTarget.isWebGLMultipleRenderTargets === true ); - - // If MRT we need to remove FBO attachments - if ( isMultipleRenderTargets ) { - - for ( let i = 0; i < textures.length; i ++ ) { - - state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); - _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.RENDERBUFFER, null ); - - state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); - _gl.framebufferTexture2D( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.TEXTURE_2D, null, 0 ); - - } - - } - - state.bindFramebuffer( _gl.READ_FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); - state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); - - for ( let i = 0; i < textures.length; i ++ ) { - - invalidationArray.push( _gl.COLOR_ATTACHMENT0 + i ); - - if ( renderTarget.depthBuffer ) { - - invalidationArray.push( depthStyle ); - - } - - const ignoreDepthValues = ( renderTargetProperties.__ignoreDepthValues !== undefined ) ? renderTargetProperties.__ignoreDepthValues : false; - - if ( ignoreDepthValues === false ) { - - if ( renderTarget.depthBuffer ) mask |= _gl.DEPTH_BUFFER_BIT; - if ( renderTarget.stencilBuffer ) mask |= _gl.STENCIL_BUFFER_BIT; - - } - - if ( isMultipleRenderTargets ) { - - _gl.framebufferRenderbuffer( _gl.READ_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] ); - - } - - if ( ignoreDepthValues === true ) { - - _gl.invalidateFramebuffer( _gl.READ_FRAMEBUFFER, [ depthStyle ] ); - _gl.invalidateFramebuffer( _gl.DRAW_FRAMEBUFFER, [ depthStyle ] ); - - } - - if ( isMultipleRenderTargets ) { - - const webglTexture = properties.get( textures[ i ] ).__webglTexture; - _gl.framebufferTexture2D( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D, webglTexture, 0 ); - - } - - _gl.blitFramebuffer( 0, 0, width, height, 0, 0, width, height, mask, _gl.NEAREST ); - - if ( supportsInvalidateFramebuffer ) { - - _gl.invalidateFramebuffer( _gl.READ_FRAMEBUFFER, invalidationArray ); - - } - - - } - - state.bindFramebuffer( _gl.READ_FRAMEBUFFER, null ); - state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, null ); - - // If MRT since pre-blit we removed the FBO we need to reconstruct the attachments - if ( isMultipleRenderTargets ) { - - for ( let i = 0; i < textures.length; i ++ ) { - - state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); - _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] ); - - const webglTexture = properties.get( textures[ i ] ).__webglTexture; - - state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); - _gl.framebufferTexture2D( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.TEXTURE_2D, webglTexture, 0 ); - - } - - } - - state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); - - } - - } - - function getRenderTargetSamples( renderTarget ) { - - return Math.min( maxSamples, renderTarget.samples ); - - } - - function useMultisampledRTT( renderTarget ) { - - const renderTargetProperties = properties.get( renderTarget ); - - return isWebGL2 && renderTarget.samples > 0 && extensions.has( 'WEBGL_multisampled_render_to_texture' ) === true && renderTargetProperties.__useRenderToTexture !== false; - - } - - function updateVideoTexture( texture ) { - - const frame = info.render.frame; - - // Check the last frame we updated the VideoTexture - - if ( _videoTextures.get( texture ) !== frame ) { - - _videoTextures.set( texture, frame ); - texture.update(); - - } - - } - - function verifyColorSpace( texture, image ) { - - const colorSpace = texture.colorSpace; - const format = texture.format; - const type = texture.type; - - if ( texture.isCompressedTexture === true || texture.format === _SRGBAFormat ) return image; - - if ( colorSpace !== LinearSRGBColorSpace && colorSpace !== NoColorSpace ) { - - // sRGB - - if ( colorSpace === SRGBColorSpace ) { - - if ( isWebGL2 === false ) { - - // in WebGL 1, try to use EXT_sRGB extension and unsized formats - - if ( extensions.has( 'EXT_sRGB' ) === true && format === RGBAFormat ) { - - texture.format = _SRGBAFormat; - - // it's not possible to generate mips in WebGL 1 with this extension - - texture.minFilter = LinearFilter; - texture.generateMipmaps = false; - - } else { - - // slow fallback (CPU decode) - - image = ImageUtils.sRGBToLinear( image ); - - } - - } else { - - // in WebGL 2 uncompressed textures can only be sRGB encoded if they have the RGBA8 format - - if ( format !== RGBAFormat || type !== UnsignedByteType ) { - - console.warn( 'THREE.WebGLTextures: sRGB encoded textures have to use RGBAFormat and UnsignedByteType.' ); - - } - - } - - } else { - - console.error( 'THREE.WebGLTextures: Unsupported texture color space:', colorSpace ); - - } - - } - - return image; - - } - - // - - this.allocateTextureUnit = allocateTextureUnit; - this.resetTextureUnits = resetTextureUnits; - - this.setTexture2D = setTexture2D; - this.setTexture2DArray = setTexture2DArray; - this.setTexture3D = setTexture3D; - this.setTextureCube = setTextureCube; - this.rebindTextures = rebindTextures; - this.setupRenderTarget = setupRenderTarget; - this.updateRenderTargetMipmap = updateRenderTargetMipmap; - this.updateMultisampleRenderTarget = updateMultisampleRenderTarget; - this.setupDepthRenderbuffer = setupDepthRenderbuffer; - this.setupFrameBufferTexture = setupFrameBufferTexture; - this.useMultisampledRTT = useMultisampledRTT; - -} - -function WebGLUtils( gl, extensions, capabilities ) { - - const isWebGL2 = capabilities.isWebGL2; - - function convert( p, colorSpace = NoColorSpace ) { - - let extension; - - if ( p === UnsignedByteType ) return gl.UNSIGNED_BYTE; - if ( p === UnsignedShort4444Type ) return gl.UNSIGNED_SHORT_4_4_4_4; - if ( p === UnsignedShort5551Type ) return gl.UNSIGNED_SHORT_5_5_5_1; - - if ( p === ByteType ) return gl.BYTE; - if ( p === ShortType ) return gl.SHORT; - if ( p === UnsignedShortType ) return gl.UNSIGNED_SHORT; - if ( p === IntType ) return gl.INT; - if ( p === UnsignedIntType ) return gl.UNSIGNED_INT; - if ( p === FloatType ) return gl.FLOAT; - - if ( p === HalfFloatType ) { - - if ( isWebGL2 ) return gl.HALF_FLOAT; - - extension = extensions.get( 'OES_texture_half_float' ); - - if ( extension !== null ) { - - return extension.HALF_FLOAT_OES; - - } else { - - return null; - - } - - } - - if ( p === AlphaFormat ) return gl.ALPHA; - if ( p === RGBAFormat ) return gl.RGBA; - if ( p === LuminanceFormat ) return gl.LUMINANCE; - if ( p === LuminanceAlphaFormat ) return gl.LUMINANCE_ALPHA; - if ( p === DepthFormat ) return gl.DEPTH_COMPONENT; - if ( p === DepthStencilFormat ) return gl.DEPTH_STENCIL; - - // WebGL 1 sRGB fallback - - if ( p === _SRGBAFormat ) { - - extension = extensions.get( 'EXT_sRGB' ); - - if ( extension !== null ) { - - return extension.SRGB_ALPHA_EXT; - - } else { - - return null; - - } - - } - - // WebGL2 formats. - - if ( p === RedFormat ) return gl.RED; - if ( p === RedIntegerFormat ) return gl.RED_INTEGER; - if ( p === RGFormat ) return gl.RG; - if ( p === RGIntegerFormat ) return gl.RG_INTEGER; - if ( p === RGBAIntegerFormat ) return gl.RGBA_INTEGER; - - // S3TC - - if ( p === RGB_S3TC_DXT1_Format || p === RGBA_S3TC_DXT1_Format || p === RGBA_S3TC_DXT3_Format || p === RGBA_S3TC_DXT5_Format ) { - - if ( colorSpace === SRGBColorSpace ) { - - extension = extensions.get( 'WEBGL_compressed_texture_s3tc_srgb' ); - - if ( extension !== null ) { - - if ( p === RGB_S3TC_DXT1_Format ) return extension.COMPRESSED_SRGB_S3TC_DXT1_EXT; - if ( p === RGBA_S3TC_DXT1_Format ) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT; - if ( p === RGBA_S3TC_DXT3_Format ) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT; - if ( p === RGBA_S3TC_DXT5_Format ) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT; - - } else { - - return null; - - } - - } else { - - extension = extensions.get( 'WEBGL_compressed_texture_s3tc' ); - - if ( extension !== null ) { - - if ( p === RGB_S3TC_DXT1_Format ) return extension.COMPRESSED_RGB_S3TC_DXT1_EXT; - if ( p === RGBA_S3TC_DXT1_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT1_EXT; - if ( p === RGBA_S3TC_DXT3_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT3_EXT; - if ( p === RGBA_S3TC_DXT5_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT5_EXT; - - } else { - - return null; - - } - - } - - } - - // PVRTC - - if ( p === RGB_PVRTC_4BPPV1_Format || p === RGB_PVRTC_2BPPV1_Format || p === RGBA_PVRTC_4BPPV1_Format || p === RGBA_PVRTC_2BPPV1_Format ) { - - extension = extensions.get( 'WEBGL_compressed_texture_pvrtc' ); - - if ( extension !== null ) { - - if ( p === RGB_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_4BPPV1_IMG; - if ( p === RGB_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_2BPPV1_IMG; - if ( p === RGBA_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; - if ( p === RGBA_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG; - - } else { - - return null; - - } - - } - - // ETC1 - - if ( p === RGB_ETC1_Format ) { - - extension = extensions.get( 'WEBGL_compressed_texture_etc1' ); - - if ( extension !== null ) { - - return extension.COMPRESSED_RGB_ETC1_WEBGL; - - } else { - - return null; - - } - - } - - // ETC2 - - if ( p === RGB_ETC2_Format || p === RGBA_ETC2_EAC_Format ) { - - extension = extensions.get( 'WEBGL_compressed_texture_etc' ); - - if ( extension !== null ) { - - if ( p === RGB_ETC2_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ETC2 : extension.COMPRESSED_RGB8_ETC2; - if ( p === RGBA_ETC2_EAC_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ETC2_EAC : extension.COMPRESSED_RGBA8_ETC2_EAC; - - } else { - - return null; - - } - - } - - // ASTC - - if ( p === RGBA_ASTC_4x4_Format || p === RGBA_ASTC_5x4_Format || p === RGBA_ASTC_5x5_Format || - p === RGBA_ASTC_6x5_Format || p === RGBA_ASTC_6x6_Format || p === RGBA_ASTC_8x5_Format || - p === RGBA_ASTC_8x6_Format || p === RGBA_ASTC_8x8_Format || p === RGBA_ASTC_10x5_Format || - p === RGBA_ASTC_10x6_Format || p === RGBA_ASTC_10x8_Format || p === RGBA_ASTC_10x10_Format || - p === RGBA_ASTC_12x10_Format || p === RGBA_ASTC_12x12_Format ) { - - extension = extensions.get( 'WEBGL_compressed_texture_astc' ); - - if ( extension !== null ) { - - if ( p === RGBA_ASTC_4x4_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR : extension.COMPRESSED_RGBA_ASTC_4x4_KHR; - if ( p === RGBA_ASTC_5x4_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR : extension.COMPRESSED_RGBA_ASTC_5x4_KHR; - if ( p === RGBA_ASTC_5x5_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR : extension.COMPRESSED_RGBA_ASTC_5x5_KHR; - if ( p === RGBA_ASTC_6x5_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR : extension.COMPRESSED_RGBA_ASTC_6x5_KHR; - if ( p === RGBA_ASTC_6x6_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR : extension.COMPRESSED_RGBA_ASTC_6x6_KHR; - if ( p === RGBA_ASTC_8x5_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR : extension.COMPRESSED_RGBA_ASTC_8x5_KHR; - if ( p === RGBA_ASTC_8x6_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR : extension.COMPRESSED_RGBA_ASTC_8x6_KHR; - if ( p === RGBA_ASTC_8x8_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR : extension.COMPRESSED_RGBA_ASTC_8x8_KHR; - if ( p === RGBA_ASTC_10x5_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR : extension.COMPRESSED_RGBA_ASTC_10x5_KHR; - if ( p === RGBA_ASTC_10x6_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR : extension.COMPRESSED_RGBA_ASTC_10x6_KHR; - if ( p === RGBA_ASTC_10x8_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR : extension.COMPRESSED_RGBA_ASTC_10x8_KHR; - if ( p === RGBA_ASTC_10x10_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR : extension.COMPRESSED_RGBA_ASTC_10x10_KHR; - if ( p === RGBA_ASTC_12x10_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR : extension.COMPRESSED_RGBA_ASTC_12x10_KHR; - if ( p === RGBA_ASTC_12x12_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR : extension.COMPRESSED_RGBA_ASTC_12x12_KHR; - - } else { - - return null; - - } - - } - - // BPTC - - if ( p === RGBA_BPTC_Format ) { - - extension = extensions.get( 'EXT_texture_compression_bptc' ); - - if ( extension !== null ) { - - if ( p === RGBA_BPTC_Format ) return ( colorSpace === SRGBColorSpace ) ? extension.COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT : extension.COMPRESSED_RGBA_BPTC_UNORM_EXT; - - } else { - - return null; - - } - - } - - // RGTC - - if ( p === RED_RGTC1_Format || p === SIGNED_RED_RGTC1_Format || p === RED_GREEN_RGTC2_Format || p === SIGNED_RED_GREEN_RGTC2_Format ) { - - extension = extensions.get( 'EXT_texture_compression_rgtc' ); - - if ( extension !== null ) { - - if ( p === RGBA_BPTC_Format ) return extension.COMPRESSED_RED_RGTC1_EXT; - if ( p === SIGNED_RED_RGTC1_Format ) return extension.COMPRESSED_SIGNED_RED_RGTC1_EXT; - if ( p === RED_GREEN_RGTC2_Format ) return extension.COMPRESSED_RED_GREEN_RGTC2_EXT; - if ( p === SIGNED_RED_GREEN_RGTC2_Format ) return extension.COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT; - - } else { - - return null; - - } - - } - - // - - if ( p === UnsignedInt248Type ) { - - if ( isWebGL2 ) return gl.UNSIGNED_INT_24_8; - - extension = extensions.get( 'WEBGL_depth_texture' ); - - if ( extension !== null ) { - - return extension.UNSIGNED_INT_24_8_WEBGL; - - } else { - - return null; - - } - - } - - // if "p" can't be resolved, assume the user defines a WebGL constant as a string (fallback/workaround for packed RGB formats) - - return ( gl[ p ] !== undefined ) ? gl[ p ] : null; - - } - - return { convert: convert }; - -} - -class ArrayCamera extends PerspectiveCamera { - - constructor( array = [] ) { - - super(); - - this.isArrayCamera = true; - - this.cameras = array; - - } - -} - -class Group extends Object3D { - - constructor() { - - super(); - - this.isGroup = true; - - this.type = 'Group'; - - } - -} - -const _moveEvent = { type: 'move' }; - -class WebXRController { - - constructor() { - - this._targetRay = null; - this._grip = null; - this._hand = null; - - } - - getHandSpace() { - - if ( this._hand === null ) { - - this._hand = new Group(); - this._hand.matrixAutoUpdate = false; - this._hand.visible = false; - - this._hand.joints = {}; - this._hand.inputState = { pinching: false }; - - } - - return this._hand; - - } - - getTargetRaySpace() { - - if ( this._targetRay === null ) { - - this._targetRay = new Group(); - this._targetRay.matrixAutoUpdate = false; - this._targetRay.visible = false; - this._targetRay.hasLinearVelocity = false; - this._targetRay.linearVelocity = new Vector3(); - this._targetRay.hasAngularVelocity = false; - this._targetRay.angularVelocity = new Vector3(); - - } - - return this._targetRay; - - } - - getGripSpace() { - - if ( this._grip === null ) { - - this._grip = new Group(); - this._grip.matrixAutoUpdate = false; - this._grip.visible = false; - this._grip.hasLinearVelocity = false; - this._grip.linearVelocity = new Vector3(); - this._grip.hasAngularVelocity = false; - this._grip.angularVelocity = new Vector3(); - - } - - return this._grip; - - } - - dispatchEvent( event ) { - - if ( this._targetRay !== null ) { - - this._targetRay.dispatchEvent( event ); - - } - - if ( this._grip !== null ) { - - this._grip.dispatchEvent( event ); - - } - - if ( this._hand !== null ) { - - this._hand.dispatchEvent( event ); - - } - - return this; - - } - - connect( inputSource ) { - - if ( inputSource && inputSource.hand ) { - - const hand = this._hand; - - if ( hand ) { - - for ( const inputjoint of inputSource.hand.values() ) { - - // Initialize hand with joints when connected - this._getHandJoint( hand, inputjoint ); - - } - - } - - } - - this.dispatchEvent( { type: 'connected', data: inputSource } ); - - return this; - - } - - disconnect( inputSource ) { - - this.dispatchEvent( { type: 'disconnected', data: inputSource } ); - - if ( this._targetRay !== null ) { - - this._targetRay.visible = false; - - } - - if ( this._grip !== null ) { - - this._grip.visible = false; - - } - - if ( this._hand !== null ) { - - this._hand.visible = false; - - } - - return this; - - } - - update( inputSource, frame, referenceSpace ) { - - let inputPose = null; - let gripPose = null; - let handPose = null; - - const targetRay = this._targetRay; - const grip = this._grip; - const hand = this._hand; - - if ( inputSource && frame.session.visibilityState !== 'visible-blurred' ) { - - if ( hand && inputSource.hand ) { - - handPose = true; - - for ( const inputjoint of inputSource.hand.values() ) { - - // Update the joints groups with the XRJoint poses - const jointPose = frame.getJointPose( inputjoint, referenceSpace ); - - // The transform of this joint will be updated with the joint pose on each frame - const joint = this._getHandJoint( hand, inputjoint ); - - if ( jointPose !== null ) { - - joint.matrix.fromArray( jointPose.transform.matrix ); - joint.matrix.decompose( joint.position, joint.rotation, joint.scale ); - joint.matrixWorldNeedsUpdate = true; - joint.jointRadius = jointPose.radius; - - } - - joint.visible = jointPose !== null; - - } - - // Custom events - - // Check pinchz - const indexTip = hand.joints[ 'index-finger-tip' ]; - const thumbTip = hand.joints[ 'thumb-tip' ]; - const distance = indexTip.position.distanceTo( thumbTip.position ); - - const distanceToPinch = 0.02; - const threshold = 0.005; - - if ( hand.inputState.pinching && distance > distanceToPinch + threshold ) { - - hand.inputState.pinching = false; - this.dispatchEvent( { - type: 'pinchend', - handedness: inputSource.handedness, - target: this - } ); - - } else if ( ! hand.inputState.pinching && distance <= distanceToPinch - threshold ) { - - hand.inputState.pinching = true; - this.dispatchEvent( { - type: 'pinchstart', - handedness: inputSource.handedness, - target: this - } ); - - } - - } else { - - if ( grip !== null && inputSource.gripSpace ) { - - gripPose = frame.getPose( inputSource.gripSpace, referenceSpace ); - - if ( gripPose !== null ) { - - grip.matrix.fromArray( gripPose.transform.matrix ); - grip.matrix.decompose( grip.position, grip.rotation, grip.scale ); - grip.matrixWorldNeedsUpdate = true; - - if ( gripPose.linearVelocity ) { - - grip.hasLinearVelocity = true; - grip.linearVelocity.copy( gripPose.linearVelocity ); - - } else { - - grip.hasLinearVelocity = false; - - } - - if ( gripPose.angularVelocity ) { - - grip.hasAngularVelocity = true; - grip.angularVelocity.copy( gripPose.angularVelocity ); - - } else { - - grip.hasAngularVelocity = false; - - } - - } - - } - - } - - if ( targetRay !== null ) { - - inputPose = frame.getPose( inputSource.targetRaySpace, referenceSpace ); - - // Some runtimes (namely Vive Cosmos with Vive OpenXR Runtime) have only grip space and ray space is equal to it - if ( inputPose === null && gripPose !== null ) { - - inputPose = gripPose; - - } - - if ( inputPose !== null ) { - - targetRay.matrix.fromArray( inputPose.transform.matrix ); - targetRay.matrix.decompose( targetRay.position, targetRay.rotation, targetRay.scale ); - targetRay.matrixWorldNeedsUpdate = true; - - if ( inputPose.linearVelocity ) { - - targetRay.hasLinearVelocity = true; - targetRay.linearVelocity.copy( inputPose.linearVelocity ); - - } else { - - targetRay.hasLinearVelocity = false; - - } - - if ( inputPose.angularVelocity ) { - - targetRay.hasAngularVelocity = true; - targetRay.angularVelocity.copy( inputPose.angularVelocity ); - - } else { - - targetRay.hasAngularVelocity = false; - - } - - this.dispatchEvent( _moveEvent ); - - } - - } - - - } - - if ( targetRay !== null ) { - - targetRay.visible = ( inputPose !== null ); - - } - - if ( grip !== null ) { - - grip.visible = ( gripPose !== null ); - - } - - if ( hand !== null ) { - - hand.visible = ( handPose !== null ); - - } - - return this; - - } - - // private method - - _getHandJoint( hand, inputjoint ) { - - if ( hand.joints[ inputjoint.jointName ] === undefined ) { - - const joint = new Group(); - joint.matrixAutoUpdate = false; - joint.visible = false; - hand.joints[ inputjoint.jointName ] = joint; - - hand.add( joint ); - - } - - return hand.joints[ inputjoint.jointName ]; - - } - -} - -class DepthTexture extends Texture { - - constructor( width, height, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, format ) { - - format = format !== undefined ? format : DepthFormat; - - if ( format !== DepthFormat && format !== DepthStencilFormat ) { - - throw new Error( 'DepthTexture format must be either THREE.DepthFormat or THREE.DepthStencilFormat' ); - - } - - if ( type === undefined && format === DepthFormat ) type = UnsignedIntType; - if ( type === undefined && format === DepthStencilFormat ) type = UnsignedInt248Type; - - super( null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); - - this.isDepthTexture = true; - - this.image = { width: width, height: height }; - - this.magFilter = magFilter !== undefined ? magFilter : NearestFilter; - this.minFilter = minFilter !== undefined ? minFilter : NearestFilter; - - this.flipY = false; - this.generateMipmaps = false; - - this.compareFunction = null; - - } - - - copy( source ) { - - super.copy( source ); - - this.compareFunction = source.compareFunction; - - return this; - - } - - toJSON( meta ) { - - const data = super.toJSON( meta ); - - if ( this.compareFunction !== null ) data.compareFunction = this.compareFunction; - - return data; - - } - -} - -class WebXRManager extends EventDispatcher { - - constructor( renderer, gl ) { - - super(); - - const scope = this; - - let session = null; - - let framebufferScaleFactor = 1.0; - - let referenceSpace = null; - let referenceSpaceType = 'local-floor'; - // Set default foveation to maximum. - let foveation = 1.0; - let customReferenceSpace = null; - - let pose = null; - let glBinding = null; - let glProjLayer = null; - let glBaseLayer = null; - let xrFrame = null; - const attributes = gl.getContextAttributes(); - let initialRenderTarget = null; - let newRenderTarget = null; - - const controllers = []; - const controllerInputSources = []; - - const planes = new Set(); - const planesLastChangedTimes = new Map(); - - // - - let userCamera = null; - - const cameraL = new PerspectiveCamera(); - cameraL.layers.enable( 1 ); - cameraL.viewport = new Vector4(); - - const cameraR = new PerspectiveCamera(); - cameraR.layers.enable( 2 ); - cameraR.viewport = new Vector4(); - - const cameras = [ cameraL, cameraR ]; - - const cameraXR = new ArrayCamera(); - cameraXR.layers.enable( 1 ); - cameraXR.layers.enable( 2 ); - - let _currentDepthNear = null; - let _currentDepthFar = null; - - // - - this.cameraAutoUpdate = true; // @deprecated, r153 - this.enabled = false; - - this.isPresenting = false; - - this.getCamera = function () {}; // @deprecated, r153 - - this.setUserCamera = function ( value ) { - - userCamera = value; - - }; - - this.getController = function ( index ) { - - let controller = controllers[ index ]; - - if ( controller === undefined ) { - - controller = new WebXRController(); - controllers[ index ] = controller; - - } - - return controller.getTargetRaySpace(); - - }; - - this.getControllerGrip = function ( index ) { - - let controller = controllers[ index ]; - - if ( controller === undefined ) { - - controller = new WebXRController(); - controllers[ index ] = controller; - - } - - return controller.getGripSpace(); - - }; - - this.getHand = function ( index ) { - - let controller = controllers[ index ]; - - if ( controller === undefined ) { - - controller = new WebXRController(); - controllers[ index ] = controller; - - } - - return controller.getHandSpace(); - - }; - - // - - function onSessionEvent( event ) { - - const controllerIndex = controllerInputSources.indexOf( event.inputSource ); - - if ( controllerIndex === - 1 ) { - - return; - - } - - const controller = controllers[ controllerIndex ]; - - if ( controller !== undefined ) { - - controller.update( event.inputSource, event.frame, customReferenceSpace || referenceSpace ); - controller.dispatchEvent( { type: event.type, data: event.inputSource } ); - - } - - } - - function onSessionEnd() { - - session.removeEventListener( 'select', onSessionEvent ); - session.removeEventListener( 'selectstart', onSessionEvent ); - session.removeEventListener( 'selectend', onSessionEvent ); - session.removeEventListener( 'squeeze', onSessionEvent ); - session.removeEventListener( 'squeezestart', onSessionEvent ); - session.removeEventListener( 'squeezeend', onSessionEvent ); - session.removeEventListener( 'end', onSessionEnd ); - session.removeEventListener( 'inputsourceschange', onInputSourcesChange ); - - for ( let i = 0; i < controllers.length; i ++ ) { - - const inputSource = controllerInputSources[ i ]; - - if ( inputSource === null ) continue; - - controllerInputSources[ i ] = null; - - controllers[ i ].disconnect( inputSource ); - - } - - _currentDepthNear = null; - _currentDepthFar = null; - - // restore framebuffer/rendering state - - renderer.setRenderTarget( initialRenderTarget ); - - glBaseLayer = null; - glProjLayer = null; - glBinding = null; - session = null; - newRenderTarget = null; - - // - - animation.stop(); - - scope.isPresenting = false; - - scope.dispatchEvent( { type: 'sessionend' } ); - - } - - this.setFramebufferScaleFactor = function ( value ) { - - framebufferScaleFactor = value; - - if ( scope.isPresenting === true ) { - - console.warn( 'THREE.WebXRManager: Cannot change framebuffer scale while presenting.' ); - - } - - }; - - this.setReferenceSpaceType = function ( value ) { - - referenceSpaceType = value; - - if ( scope.isPresenting === true ) { - - console.warn( 'THREE.WebXRManager: Cannot change reference space type while presenting.' ); - - } - - }; - - this.getReferenceSpace = function () { - - return customReferenceSpace || referenceSpace; - - }; - - this.setReferenceSpace = function ( space ) { - - customReferenceSpace = space; - - }; - - this.getBaseLayer = function () { - - return glProjLayer !== null ? glProjLayer : glBaseLayer; - - }; - - this.getBinding = function () { - - return glBinding; - - }; - - this.getFrame = function () { - - return xrFrame; - - }; - - this.getSession = function () { - - return session; - - }; - - this.setSession = async function ( value ) { - - session = value; - - if ( session !== null ) { - - initialRenderTarget = renderer.getRenderTarget(); - - session.addEventListener( 'select', onSessionEvent ); - session.addEventListener( 'selectstart', onSessionEvent ); - session.addEventListener( 'selectend', onSessionEvent ); - session.addEventListener( 'squeeze', onSessionEvent ); - session.addEventListener( 'squeezestart', onSessionEvent ); - session.addEventListener( 'squeezeend', onSessionEvent ); - session.addEventListener( 'end', onSessionEnd ); - session.addEventListener( 'inputsourceschange', onInputSourcesChange ); - - if ( attributes.xrCompatible !== true ) { - - await gl.makeXRCompatible(); - - } - - if ( ( session.renderState.layers === undefined ) || ( renderer.capabilities.isWebGL2 === false ) ) { - - const layerInit = { - antialias: ( session.renderState.layers === undefined ) ? attributes.antialias : true, - alpha: true, - depth: attributes.depth, - stencil: attributes.stencil, - framebufferScaleFactor: framebufferScaleFactor - }; - - glBaseLayer = new XRWebGLLayer( session, gl, layerInit ); - - session.updateRenderState( { baseLayer: glBaseLayer } ); - - newRenderTarget = new WebGLRenderTarget( - glBaseLayer.framebufferWidth, - glBaseLayer.framebufferHeight, - { - format: RGBAFormat, - type: UnsignedByteType, - colorSpace: renderer.outputColorSpace, - stencilBuffer: attributes.stencil - } - ); - - } else { - - let depthFormat = null; - let depthType = null; - let glDepthFormat = null; - - if ( attributes.depth ) { - - glDepthFormat = attributes.stencil ? gl.DEPTH24_STENCIL8 : gl.DEPTH_COMPONENT24; - depthFormat = attributes.stencil ? DepthStencilFormat : DepthFormat; - depthType = attributes.stencil ? UnsignedInt248Type : UnsignedIntType; - - } - - const projectionlayerInit = { - colorFormat: gl.RGBA8, - depthFormat: glDepthFormat, - scaleFactor: framebufferScaleFactor - }; - - glBinding = new XRWebGLBinding( session, gl ); - - glProjLayer = glBinding.createProjectionLayer( projectionlayerInit ); - - session.updateRenderState( { layers: [ glProjLayer ] } ); - - newRenderTarget = new WebGLRenderTarget( - glProjLayer.textureWidth, - glProjLayer.textureHeight, - { - format: RGBAFormat, - type: UnsignedByteType, - depthTexture: new DepthTexture( glProjLayer.textureWidth, glProjLayer.textureHeight, depthType, undefined, undefined, undefined, undefined, undefined, undefined, depthFormat ), - stencilBuffer: attributes.stencil, - colorSpace: renderer.outputColorSpace, - samples: attributes.antialias ? 4 : 0 - } ); - - const renderTargetProperties = renderer.properties.get( newRenderTarget ); - renderTargetProperties.__ignoreDepthValues = glProjLayer.ignoreDepthValues; - - } - - newRenderTarget.isXRRenderTarget = true; // TODO Remove this when possible, see #23278 - - this.setFoveation( foveation ); - - customReferenceSpace = null; - referenceSpace = await session.requestReferenceSpace( referenceSpaceType ); - - animation.setContext( session ); - animation.start(); - - scope.isPresenting = true; - - scope.dispatchEvent( { type: 'sessionstart' } ); - - } - - }; - - this.getEnvironmentBlendMode = function () { - - if ( session !== null ) { - - return session.environmentBlendMode; - - } - - }; - - function onInputSourcesChange( event ) { - - // Notify disconnected - - for ( let i = 0; i < event.removed.length; i ++ ) { - - const inputSource = event.removed[ i ]; - const index = controllerInputSources.indexOf( inputSource ); - - if ( index >= 0 ) { - - controllerInputSources[ index ] = null; - controllers[ index ].disconnect( inputSource ); - - } - - } - - // Notify connected - - for ( let i = 0; i < event.added.length; i ++ ) { - - const inputSource = event.added[ i ]; - - let controllerIndex = controllerInputSources.indexOf( inputSource ); - - if ( controllerIndex === - 1 ) { - - // Assign input source a controller that currently has no input source - - for ( let i = 0; i < controllers.length; i ++ ) { - - if ( i >= controllerInputSources.length ) { - - controllerInputSources.push( inputSource ); - controllerIndex = i; - break; - - } else if ( controllerInputSources[ i ] === null ) { - - controllerInputSources[ i ] = inputSource; - controllerIndex = i; - break; - - } - - } - - // If all controllers do currently receive input we ignore new ones - - if ( controllerIndex === - 1 ) break; - - } - - const controller = controllers[ controllerIndex ]; - - if ( controller ) { - - controller.connect( inputSource ); - - } - - } - - } - - // - - const cameraLPos = new Vector3(); - const cameraRPos = new Vector3(); - - /** - * Assumes 2 cameras that are parallel and share an X-axis, and that - * the cameras' projection and world matrices have already been set. - * And that near and far planes are identical for both cameras. - * Visualization of this technique: https://computergraphics.stackexchange.com/a/4765 - */ - function setProjectionFromUnion( camera, cameraL, cameraR ) { - - cameraLPos.setFromMatrixPosition( cameraL.matrixWorld ); - cameraRPos.setFromMatrixPosition( cameraR.matrixWorld ); - - const ipd = cameraLPos.distanceTo( cameraRPos ); - - const projL = cameraL.projectionMatrix.elements; - const projR = cameraR.projectionMatrix.elements; - - // VR systems will have identical far and near planes, and - // most likely identical top and bottom frustum extents. - // Use the left camera for these values. - const near = projL[ 14 ] / ( projL[ 10 ] - 1 ); - const far = projL[ 14 ] / ( projL[ 10 ] + 1 ); - const topFov = ( projL[ 9 ] + 1 ) / projL[ 5 ]; - const bottomFov = ( projL[ 9 ] - 1 ) / projL[ 5 ]; - - const leftFov = ( projL[ 8 ] - 1 ) / projL[ 0 ]; - const rightFov = ( projR[ 8 ] + 1 ) / projR[ 0 ]; - const left = near * leftFov; - const right = near * rightFov; - - // Calculate the new camera's position offset from the - // left camera. xOffset should be roughly half `ipd`. - const zOffset = ipd / ( - leftFov + rightFov ); - const xOffset = zOffset * - leftFov; - - // TODO: Better way to apply this offset? - cameraL.matrixWorld.decompose( camera.position, camera.quaternion, camera.scale ); - camera.translateX( xOffset ); - camera.translateZ( zOffset ); - camera.matrixWorld.compose( camera.position, camera.quaternion, camera.scale ); - camera.matrixWorldInverse.copy( camera.matrixWorld ).invert(); - - // Find the union of the frustum values of the cameras and scale - // the values so that the near plane's position does not change in world space, - // although must now be relative to the new union camera. - const near2 = near + zOffset; - const far2 = far + zOffset; - const left2 = left - xOffset; - const right2 = right + ( ipd - xOffset ); - const top2 = topFov * far / far2 * near2; - const bottom2 = bottomFov * far / far2 * near2; - - camera.projectionMatrix.makePerspective( left2, right2, top2, bottom2, near2, far2 ); - camera.projectionMatrixInverse.copy( camera.projectionMatrix ).invert(); - - } - - function updateCamera( camera, parent ) { - - if ( parent === null ) { - - camera.matrixWorld.copy( camera.matrix ); - - } else { - - camera.matrixWorld.multiplyMatrices( parent.matrixWorld, camera.matrix ); - - } - - camera.matrixWorldInverse.copy( camera.matrixWorld ).invert(); - - } - - this.updateCameraXR = function ( camera ) { - - if ( session === null ) return camera; - - if ( userCamera ) { - - camera = userCamera; - - } - - cameraXR.near = cameraR.near = cameraL.near = camera.near; - cameraXR.far = cameraR.far = cameraL.far = camera.far; - - if ( _currentDepthNear !== cameraXR.near || _currentDepthFar !== cameraXR.far ) { - - // Note that the new renderState won't apply until the next frame. See #18320 - - session.updateRenderState( { - depthNear: cameraXR.near, - depthFar: cameraXR.far - } ); - - _currentDepthNear = cameraXR.near; - _currentDepthFar = cameraXR.far; - - } - - const parent = camera.parent; - const cameras = cameraXR.cameras; - - updateCamera( cameraXR, parent ); - - for ( let i = 0; i < cameras.length; i ++ ) { - - updateCamera( cameras[ i ], parent ); - - } - - // update projection matrix for proper view frustum culling - - if ( cameras.length === 2 ) { - - setProjectionFromUnion( cameraXR, cameraL, cameraR ); - - } else { - - // assume single camera setup (AR) - - cameraXR.projectionMatrix.copy( cameraL.projectionMatrix ); - - } - - // update user camera and its children - - if ( userCamera ) { - - updateUserCamera( cameraXR, parent ); - - } - - return cameraXR; - - }; - - function updateUserCamera( cameraXR, parent ) { - - const camera = userCamera; - - if ( parent === null ) { - - camera.matrix.copy( cameraXR.matrixWorld ); - - } else { - - camera.matrix.copy( parent.matrixWorld ); - camera.matrix.invert(); - camera.matrix.multiply( cameraXR.matrixWorld ); - - } - - camera.matrix.decompose( camera.position, camera.quaternion, camera.scale ); - camera.updateMatrixWorld( true ); - - const children = camera.children; - - for ( let i = 0, l = children.length; i < l; i ++ ) { - - children[ i ].updateMatrixWorld( true ); - - } - - camera.projectionMatrix.copy( cameraXR.projectionMatrix ); - camera.projectionMatrixInverse.copy( cameraXR.projectionMatrixInverse ); - - if ( camera.isPerspectiveCamera ) { - - camera.fov = RAD2DEG * 2 * Math.atan( 1 / camera.projectionMatrix.elements[ 5 ] ); - camera.zoom = 1; - - } - - } - - this.getFoveation = function () { - - if ( glProjLayer === null && glBaseLayer === null ) { - - return undefined; - - } - - return foveation; - - }; - - this.setFoveation = function ( value ) { - - // 0 = no foveation = full resolution - // 1 = maximum foveation = the edges render at lower resolution - - foveation = value; - - if ( glProjLayer !== null ) { - - glProjLayer.fixedFoveation = value; - - } - - if ( glBaseLayer !== null && glBaseLayer.fixedFoveation !== undefined ) { - - glBaseLayer.fixedFoveation = value; - - } - - }; - - this.getPlanes = function () { - - return planes; - - }; - - // Animation Loop - - let onAnimationFrameCallback = null; - - function onAnimationFrame( time, frame ) { - - pose = frame.getViewerPose( customReferenceSpace || referenceSpace ); - xrFrame = frame; - - if ( pose !== null ) { - - const views = pose.views; - - if ( glBaseLayer !== null ) { - - renderer.setRenderTargetFramebuffer( newRenderTarget, glBaseLayer.framebuffer ); - renderer.setRenderTarget( newRenderTarget ); - - } - - let cameraXRNeedsUpdate = false; - - // check if it's necessary to rebuild cameraXR's camera list - - if ( views.length !== cameraXR.cameras.length ) { - - cameraXR.cameras.length = 0; - cameraXRNeedsUpdate = true; - - } - - for ( let i = 0; i < views.length; i ++ ) { - - const view = views[ i ]; - - let viewport = null; - - if ( glBaseLayer !== null ) { - - viewport = glBaseLayer.getViewport( view ); - - } else { - - const glSubImage = glBinding.getViewSubImage( glProjLayer, view ); - viewport = glSubImage.viewport; - - // For side-by-side projection, we only produce a single texture for both eyes. - if ( i === 0 ) { - - renderer.setRenderTargetTextures( - newRenderTarget, - glSubImage.colorTexture, - glProjLayer.ignoreDepthValues ? undefined : glSubImage.depthStencilTexture ); - - renderer.setRenderTarget( newRenderTarget ); - - } - - } - - let camera = cameras[ i ]; - - if ( camera === undefined ) { - - camera = new PerspectiveCamera(); - camera.layers.enable( i ); - camera.viewport = new Vector4(); - cameras[ i ] = camera; - - } - - camera.matrix.fromArray( view.transform.matrix ); - camera.matrix.decompose( camera.position, camera.quaternion, camera.scale ); - camera.projectionMatrix.fromArray( view.projectionMatrix ); - camera.projectionMatrixInverse.copy( camera.projectionMatrix ).invert(); - camera.viewport.set( viewport.x, viewport.y, viewport.width, viewport.height ); - - if ( i === 0 ) { - - cameraXR.matrix.copy( camera.matrix ); - cameraXR.matrix.decompose( cameraXR.position, cameraXR.quaternion, cameraXR.scale ); - - } - - if ( cameraXRNeedsUpdate === true ) { - - cameraXR.cameras.push( camera ); - - } - - } - - } - - // - - for ( let i = 0; i < controllers.length; i ++ ) { - - const inputSource = controllerInputSources[ i ]; - const controller = controllers[ i ]; - - if ( inputSource !== null && controller !== undefined ) { - - controller.update( inputSource, frame, customReferenceSpace || referenceSpace ); - - } - - } - - if ( onAnimationFrameCallback ) onAnimationFrameCallback( time, frame ); - - if ( frame.detectedPlanes ) { - - scope.dispatchEvent( { type: 'planesdetected', data: frame.detectedPlanes } ); - - let planesToRemove = null; - - for ( const plane of planes ) { - - if ( ! frame.detectedPlanes.has( plane ) ) { - - if ( planesToRemove === null ) { - - planesToRemove = []; - - } - - planesToRemove.push( plane ); - - } - - } - - if ( planesToRemove !== null ) { - - for ( const plane of planesToRemove ) { - - planes.delete( plane ); - planesLastChangedTimes.delete( plane ); - scope.dispatchEvent( { type: 'planeremoved', data: plane } ); - - } - - } - - for ( const plane of frame.detectedPlanes ) { - - if ( ! planes.has( plane ) ) { - - planes.add( plane ); - planesLastChangedTimes.set( plane, frame.lastChangedTime ); - scope.dispatchEvent( { type: 'planeadded', data: plane } ); - - } else { - - const lastKnownTime = planesLastChangedTimes.get( plane ); - - if ( plane.lastChangedTime > lastKnownTime ) { - - planesLastChangedTimes.set( plane, plane.lastChangedTime ); - scope.dispatchEvent( { type: 'planechanged', data: plane } ); - - } - - } - - } - - } - - xrFrame = null; - - } - - const animation = new WebGLAnimation(); - - animation.setAnimationLoop( onAnimationFrame ); - - this.setAnimationLoop = function ( callback ) { - - onAnimationFrameCallback = callback; - - }; - - this.dispose = function () {}; - - } - -} - -function WebGLMaterials( renderer, properties ) { - - function refreshTransformUniform( map, uniform ) { - - if ( map.matrixAutoUpdate === true ) { - - map.updateMatrix(); - - } - - uniform.value.copy( map.matrix ); - - } - - function refreshFogUniforms( uniforms, fog ) { - - fog.color.getRGB( uniforms.fogColor.value, getUnlitUniformColorSpace( renderer ) ); - - if ( fog.isFog ) { - - uniforms.fogNear.value = fog.near; - uniforms.fogFar.value = fog.far; - - } else if ( fog.isFogExp2 ) { - - uniforms.fogDensity.value = fog.density; - - } - - } - - function refreshMaterialUniforms( uniforms, material, pixelRatio, height, transmissionRenderTarget ) { - - if ( material.isMeshBasicMaterial ) { - - refreshUniformsCommon( uniforms, material ); - - } else if ( material.isMeshLambertMaterial ) { - - refreshUniformsCommon( uniforms, material ); - - } else if ( material.isMeshToonMaterial ) { - - refreshUniformsCommon( uniforms, material ); - refreshUniformsToon( uniforms, material ); - - } else if ( material.isMeshPhongMaterial ) { - - refreshUniformsCommon( uniforms, material ); - refreshUniformsPhong( uniforms, material ); - - } else if ( material.isMeshStandardMaterial ) { - - refreshUniformsCommon( uniforms, material ); - refreshUniformsStandard( uniforms, material ); - - if ( material.isMeshPhysicalMaterial ) { - - refreshUniformsPhysical( uniforms, material, transmissionRenderTarget ); - - } - - } else if ( material.isMeshMatcapMaterial ) { - - refreshUniformsCommon( uniforms, material ); - refreshUniformsMatcap( uniforms, material ); - - } else if ( material.isMeshDepthMaterial ) { - - refreshUniformsCommon( uniforms, material ); - - } else if ( material.isMeshDistanceMaterial ) { - - refreshUniformsCommon( uniforms, material ); - refreshUniformsDistance( uniforms, material ); - - } else if ( material.isMeshNormalMaterial ) { - - refreshUniformsCommon( uniforms, material ); - - } else if ( material.isLineBasicMaterial ) { - - refreshUniformsLine( uniforms, material ); - - if ( material.isLineDashedMaterial ) { - - refreshUniformsDash( uniforms, material ); - - } - - } else if ( material.isPointsMaterial ) { - - refreshUniformsPoints( uniforms, material, pixelRatio, height ); - - } else if ( material.isSpriteMaterial ) { - - refreshUniformsSprites( uniforms, material ); - - } else if ( material.isShadowMaterial ) { - - uniforms.color.value.copy( material.color ); - uniforms.opacity.value = material.opacity; - - } else if ( material.isShaderMaterial ) { - - material.uniformsNeedUpdate = false; // #15581 - - } - - } - - function refreshUniformsCommon( uniforms, material ) { - - uniforms.opacity.value = material.opacity; - - if ( material.color ) { - - uniforms.diffuse.value.copy( material.color ); - - } - - if ( material.emissive ) { - - uniforms.emissive.value.copy( material.emissive ).multiplyScalar( material.emissiveIntensity ); - - } - - if ( material.map ) { - - uniforms.map.value = material.map; - - refreshTransformUniform( material.map, uniforms.mapTransform ); - - } - - if ( material.alphaMap ) { - - uniforms.alphaMap.value = material.alphaMap; - - refreshTransformUniform( material.alphaMap, uniforms.alphaMapTransform ); - - } - - if ( material.bumpMap ) { - - uniforms.bumpMap.value = material.bumpMap; - - refreshTransformUniform( material.bumpMap, uniforms.bumpMapTransform ); - - uniforms.bumpScale.value = material.bumpScale; - - if ( material.side === BackSide ) { - - uniforms.bumpScale.value *= - 1; - - } - - } - - if ( material.normalMap ) { - - uniforms.normalMap.value = material.normalMap; - - refreshTransformUniform( material.normalMap, uniforms.normalMapTransform ); - - uniforms.normalScale.value.copy( material.normalScale ); - - if ( material.side === BackSide ) { - - uniforms.normalScale.value.negate(); - - } - - } - - if ( material.displacementMap ) { - - uniforms.displacementMap.value = material.displacementMap; - - refreshTransformUniform( material.displacementMap, uniforms.displacementMapTransform ); - - uniforms.displacementScale.value = material.displacementScale; - uniforms.displacementBias.value = material.displacementBias; - - } - - if ( material.emissiveMap ) { - - uniforms.emissiveMap.value = material.emissiveMap; - - refreshTransformUniform( material.emissiveMap, uniforms.emissiveMapTransform ); - - } - - if ( material.specularMap ) { - - uniforms.specularMap.value = material.specularMap; - - refreshTransformUniform( material.specularMap, uniforms.specularMapTransform ); - - } - - if ( material.alphaTest > 0 ) { - - uniforms.alphaTest.value = material.alphaTest; - - } - - const envMap = properties.get( material ).envMap; - - if ( envMap ) { - - uniforms.envMap.value = envMap; - - uniforms.flipEnvMap.value = ( envMap.isCubeTexture && envMap.isRenderTargetTexture === false ) ? - 1 : 1; - - uniforms.reflectivity.value = material.reflectivity; - uniforms.ior.value = material.ior; - uniforms.refractionRatio.value = material.refractionRatio; - - } - - if ( material.lightMap ) { - - uniforms.lightMap.value = material.lightMap; - - // artist-friendly light intensity scaling factor - const scaleFactor = ( renderer.useLegacyLights === true ) ? Math.PI : 1; - - uniforms.lightMapIntensity.value = material.lightMapIntensity * scaleFactor; - - refreshTransformUniform( material.lightMap, uniforms.lightMapTransform ); - - } - - if ( material.aoMap ) { - - uniforms.aoMap.value = material.aoMap; - uniforms.aoMapIntensity.value = material.aoMapIntensity; - - refreshTransformUniform( material.aoMap, uniforms.aoMapTransform ); - - } - - } - - function refreshUniformsLine( uniforms, material ) { - - uniforms.diffuse.value.copy( material.color ); - uniforms.opacity.value = material.opacity; - - if ( material.map ) { - - uniforms.map.value = material.map; - - refreshTransformUniform( material.map, uniforms.mapTransform ); - - } - - } - - function refreshUniformsDash( uniforms, material ) { - - uniforms.dashSize.value = material.dashSize; - uniforms.totalSize.value = material.dashSize + material.gapSize; - uniforms.scale.value = material.scale; - - } - - function refreshUniformsPoints( uniforms, material, pixelRatio, height ) { - - uniforms.diffuse.value.copy( material.color ); - uniforms.opacity.value = material.opacity; - uniforms.size.value = material.size * pixelRatio; - uniforms.scale.value = height * 0.5; - - if ( material.map ) { - - uniforms.map.value = material.map; - - refreshTransformUniform( material.map, uniforms.uvTransform ); - - } - - if ( material.alphaMap ) { - - uniforms.alphaMap.value = material.alphaMap; - - } - - if ( material.alphaTest > 0 ) { - - uniforms.alphaTest.value = material.alphaTest; - - } - - } - - function refreshUniformsSprites( uniforms, material ) { - - uniforms.diffuse.value.copy( material.color ); - uniforms.opacity.value = material.opacity; - uniforms.rotation.value = material.rotation; - - if ( material.map ) { - - uniforms.map.value = material.map; - - refreshTransformUniform( material.map, uniforms.mapTransform ); - - } - - if ( material.alphaMap ) { - - uniforms.alphaMap.value = material.alphaMap; - - } - - if ( material.alphaTest > 0 ) { - - uniforms.alphaTest.value = material.alphaTest; - - } - - } - - function refreshUniformsPhong( uniforms, material ) { - - uniforms.specular.value.copy( material.specular ); - uniforms.shininess.value = Math.max( material.shininess, 1e-4 ); // to prevent pow( 0.0, 0.0 ) - - } - - function refreshUniformsToon( uniforms, material ) { - - if ( material.gradientMap ) { - - uniforms.gradientMap.value = material.gradientMap; - - } - - } - - function refreshUniformsStandard( uniforms, material ) { - - uniforms.metalness.value = material.metalness; - - if ( material.metalnessMap ) { - - uniforms.metalnessMap.value = material.metalnessMap; - - refreshTransformUniform( material.metalnessMap, uniforms.metalnessMapTransform ); - - } - - uniforms.roughness.value = material.roughness; - - if ( material.roughnessMap ) { - - uniforms.roughnessMap.value = material.roughnessMap; - - refreshTransformUniform( material.roughnessMap, uniforms.roughnessMapTransform ); - - } - - const envMap = properties.get( material ).envMap; - - if ( envMap ) { - - //uniforms.envMap.value = material.envMap; // part of uniforms common - uniforms.envMapIntensity.value = material.envMapIntensity; - - } - - } - - function refreshUniformsPhysical( uniforms, material, transmissionRenderTarget ) { - - uniforms.ior.value = material.ior; // also part of uniforms common - - if ( material.sheen > 0 ) { - - uniforms.sheenColor.value.copy( material.sheenColor ).multiplyScalar( material.sheen ); - - uniforms.sheenRoughness.value = material.sheenRoughness; - - if ( material.sheenColorMap ) { - - uniforms.sheenColorMap.value = material.sheenColorMap; - - refreshTransformUniform( material.sheenColorMap, uniforms.sheenColorMapTransform ); - - } - - if ( material.sheenRoughnessMap ) { - - uniforms.sheenRoughnessMap.value = material.sheenRoughnessMap; - - refreshTransformUniform( material.sheenRoughnessMap, uniforms.sheenRoughnessMapTransform ); - - } - - } - - if ( material.clearcoat > 0 ) { - - uniforms.clearcoat.value = material.clearcoat; - uniforms.clearcoatRoughness.value = material.clearcoatRoughness; - - if ( material.clearcoatMap ) { - - uniforms.clearcoatMap.value = material.clearcoatMap; - - refreshTransformUniform( material.clearcoatMap, uniforms.clearcoatMapTransform ); - - } - - if ( material.clearcoatRoughnessMap ) { - - uniforms.clearcoatRoughnessMap.value = material.clearcoatRoughnessMap; - - refreshTransformUniform( material.clearcoatRoughnessMap, uniforms.clearcoatRoughnessMapTransform ); - - } - - if ( material.clearcoatNormalMap ) { - - uniforms.clearcoatNormalMap.value = material.clearcoatNormalMap; - - refreshTransformUniform( material.clearcoatNormalMap, uniforms.clearcoatNormalMapTransform ); - - uniforms.clearcoatNormalScale.value.copy( material.clearcoatNormalScale ); - - if ( material.side === BackSide ) { - - uniforms.clearcoatNormalScale.value.negate(); - - } - - } - - } - - if ( material.iridescence > 0 ) { - - uniforms.iridescence.value = material.iridescence; - uniforms.iridescenceIOR.value = material.iridescenceIOR; - uniforms.iridescenceThicknessMinimum.value = material.iridescenceThicknessRange[ 0 ]; - uniforms.iridescenceThicknessMaximum.value = material.iridescenceThicknessRange[ 1 ]; - - if ( material.iridescenceMap ) { - - uniforms.iridescenceMap.value = material.iridescenceMap; - - refreshTransformUniform( material.iridescenceMap, uniforms.iridescenceMapTransform ); - - } - - if ( material.iridescenceThicknessMap ) { - - uniforms.iridescenceThicknessMap.value = material.iridescenceThicknessMap; - - refreshTransformUniform( material.iridescenceThicknessMap, uniforms.iridescenceThicknessMapTransform ); - - } - - } - - if ( material.transmission > 0 ) { - - uniforms.transmission.value = material.transmission; - uniforms.transmissionSamplerMap.value = transmissionRenderTarget.texture; - uniforms.transmissionSamplerSize.value.set( transmissionRenderTarget.width, transmissionRenderTarget.height ); - - if ( material.transmissionMap ) { - - uniforms.transmissionMap.value = material.transmissionMap; - - refreshTransformUniform( material.transmissionMap, uniforms.transmissionMapTransform ); - - } - - uniforms.thickness.value = material.thickness; - - if ( material.thicknessMap ) { - - uniforms.thicknessMap.value = material.thicknessMap; - - refreshTransformUniform( material.thicknessMap, uniforms.thicknessMapTransform ); - - } - - uniforms.attenuationDistance.value = material.attenuationDistance; - uniforms.attenuationColor.value.copy( material.attenuationColor ); - - } - - if ( material.anisotropy > 0 ) { - - uniforms.anisotropyVector.value.set( material.anisotropy * Math.cos( material.anisotropyRotation ), material.anisotropy * Math.sin( material.anisotropyRotation ) ); - - if ( material.anisotropyMap ) { - - uniforms.anisotropyMap.value = material.anisotropyMap; - - } - - } - - uniforms.specularIntensity.value = material.specularIntensity; - uniforms.specularColor.value.copy( material.specularColor ); - - if ( material.specularColorMap ) { - - uniforms.specularColorMap.value = material.specularColorMap; - - refreshTransformUniform( material.specularColorMap, uniforms.specularColorMapTransform ); - - } - - if ( material.specularIntensityMap ) { - - uniforms.specularIntensityMap.value = material.specularIntensityMap; - - refreshTransformUniform( material.specularIntensityMap, uniforms.specularIntensityMapTransform ); - - } - - } - - function refreshUniformsMatcap( uniforms, material ) { - - if ( material.matcap ) { - - uniforms.matcap.value = material.matcap; - - } - - } - - function refreshUniformsDistance( uniforms, material ) { - - const light = properties.get( material ).light; - - uniforms.referencePosition.value.setFromMatrixPosition( light.matrixWorld ); - uniforms.nearDistance.value = light.shadow.camera.near; - uniforms.farDistance.value = light.shadow.camera.far; - - } - - return { - refreshFogUniforms: refreshFogUniforms, - refreshMaterialUniforms: refreshMaterialUniforms - }; - -} - -function WebGLUniformsGroups( gl, info, capabilities, state ) { - - let buffers = {}; - let updateList = {}; - let allocatedBindingPoints = []; - - const maxBindingPoints = ( capabilities.isWebGL2 ) ? gl.getParameter( gl.MAX_UNIFORM_BUFFER_BINDINGS ) : 0; // binding points are global whereas block indices are per shader program - - function bind( uniformsGroup, program ) { - - const webglProgram = program.program; - state.uniformBlockBinding( uniformsGroup, webglProgram ); - - } - - function update( uniformsGroup, program ) { - - let buffer = buffers[ uniformsGroup.id ]; - - if ( buffer === undefined ) { - - prepareUniformsGroup( uniformsGroup ); - - buffer = createBuffer( uniformsGroup ); - buffers[ uniformsGroup.id ] = buffer; - - uniformsGroup.addEventListener( 'dispose', onUniformsGroupsDispose ); - - } - - // ensure to update the binding points/block indices mapping for this program - - const webglProgram = program.program; - state.updateUBOMapping( uniformsGroup, webglProgram ); - - // update UBO once per frame - - const frame = info.render.frame; - - if ( updateList[ uniformsGroup.id ] !== frame ) { - - updateBufferData( uniformsGroup ); - - updateList[ uniformsGroup.id ] = frame; - - } - - } - - function createBuffer( uniformsGroup ) { - - // the setup of an UBO is independent of a particular shader program but global - - const bindingPointIndex = allocateBindingPointIndex(); - uniformsGroup.__bindingPointIndex = bindingPointIndex; - - const buffer = gl.createBuffer(); - const size = uniformsGroup.__size; - const usage = uniformsGroup.usage; - - gl.bindBuffer( gl.UNIFORM_BUFFER, buffer ); - gl.bufferData( gl.UNIFORM_BUFFER, size, usage ); - gl.bindBuffer( gl.UNIFORM_BUFFER, null ); - gl.bindBufferBase( gl.UNIFORM_BUFFER, bindingPointIndex, buffer ); - - return buffer; - - } - - function allocateBindingPointIndex() { - - for ( let i = 0; i < maxBindingPoints; i ++ ) { - - if ( allocatedBindingPoints.indexOf( i ) === - 1 ) { - - allocatedBindingPoints.push( i ); - return i; - - } - - } - - console.error( 'THREE.WebGLRenderer: Maximum number of simultaneously usable uniforms groups reached.' ); - - return 0; - - } - - function updateBufferData( uniformsGroup ) { - - const buffer = buffers[ uniformsGroup.id ]; - const uniforms = uniformsGroup.uniforms; - const cache = uniformsGroup.__cache; - - gl.bindBuffer( gl.UNIFORM_BUFFER, buffer ); - - for ( let i = 0, il = uniforms.length; i < il; i ++ ) { - - const uniform = uniforms[ i ]; - - // partly update the buffer if necessary - - if ( hasUniformChanged( uniform, i, cache ) === true ) { - - const offset = uniform.__offset; - - const values = Array.isArray( uniform.value ) ? uniform.value : [ uniform.value ]; - - let arrayOffset = 0; - - for ( let i = 0; i < values.length; i ++ ) { - - const value = values[ i ]; - - const info = getUniformSize( value ); - - if ( typeof value === 'number' ) { - - uniform.__data[ 0 ] = value; - gl.bufferSubData( gl.UNIFORM_BUFFER, offset + arrayOffset, uniform.__data ); - - } else if ( value.isMatrix3 ) { - - // manually converting 3x3 to 3x4 - - uniform.__data[ 0 ] = value.elements[ 0 ]; - uniform.__data[ 1 ] = value.elements[ 1 ]; - uniform.__data[ 2 ] = value.elements[ 2 ]; - uniform.__data[ 3 ] = value.elements[ 0 ]; - uniform.__data[ 4 ] = value.elements[ 3 ]; - uniform.__data[ 5 ] = value.elements[ 4 ]; - uniform.__data[ 6 ] = value.elements[ 5 ]; - uniform.__data[ 7 ] = value.elements[ 0 ]; - uniform.__data[ 8 ] = value.elements[ 6 ]; - uniform.__data[ 9 ] = value.elements[ 7 ]; - uniform.__data[ 10 ] = value.elements[ 8 ]; - uniform.__data[ 11 ] = value.elements[ 0 ]; - - } else { - - value.toArray( uniform.__data, arrayOffset ); - - arrayOffset += info.storage / Float32Array.BYTES_PER_ELEMENT; - - } - - } - - gl.bufferSubData( gl.UNIFORM_BUFFER, offset, uniform.__data ); - - } - - } - - gl.bindBuffer( gl.UNIFORM_BUFFER, null ); - - } - - function hasUniformChanged( uniform, index, cache ) { - - const value = uniform.value; - - if ( cache[ index ] === undefined ) { - - // cache entry does not exist so far - - if ( typeof value === 'number' ) { - - cache[ index ] = value; - - } else { - - const values = Array.isArray( value ) ? value : [ value ]; - - const tempValues = []; - - for ( let i = 0; i < values.length; i ++ ) { - - tempValues.push( values[ i ].clone() ); - - } - - cache[ index ] = tempValues; - - } - - return true; - - } else { - - // compare current value with cached entry - - if ( typeof value === 'number' ) { - - if ( cache[ index ] !== value ) { - - cache[ index ] = value; - return true; - - } - - } else { - - const cachedObjects = Array.isArray( cache[ index ] ) ? cache[ index ] : [ cache[ index ] ]; - const values = Array.isArray( value ) ? value : [ value ]; - - for ( let i = 0; i < cachedObjects.length; i ++ ) { - - const cachedObject = cachedObjects[ i ]; - - if ( cachedObject.equals( values[ i ] ) === false ) { - - cachedObject.copy( values[ i ] ); - return true; - - } - - } - - } - - } - - return false; - - } - - function prepareUniformsGroup( uniformsGroup ) { - - // determine total buffer size according to the STD140 layout - // Hint: STD140 is the only supported layout in WebGL 2 - - const uniforms = uniformsGroup.uniforms; - - let offset = 0; // global buffer offset in bytes - const chunkSize = 16; // size of a chunk in bytes - let chunkOffset = 0; // offset within a single chunk in bytes - - for ( let i = 0, l = uniforms.length; i < l; i ++ ) { - - const uniform = uniforms[ i ]; - - const infos = { - boundary: 0, // bytes - storage: 0 // bytes - }; - - const values = Array.isArray( uniform.value ) ? uniform.value : [ uniform.value ]; - - for ( let j = 0, jl = values.length; j < jl; j ++ ) { - - const value = values[ j ]; - - const info = getUniformSize( value ); - - infos.boundary += info.boundary; - infos.storage += info.storage; - - } - - // the following two properties will be used for partial buffer updates - - uniform.__data = new Float32Array( infos.storage / Float32Array.BYTES_PER_ELEMENT ); - uniform.__offset = offset; - - // - - if ( i > 0 ) { - - chunkOffset = offset % chunkSize; - - const remainingSizeInChunk = chunkSize - chunkOffset; - - // check for chunk overflow - - if ( chunkOffset !== 0 && ( remainingSizeInChunk - infos.boundary ) < 0 ) { - - // add padding and adjust offset - - offset += ( chunkSize - chunkOffset ); - uniform.__offset = offset; - - } - - } - - offset += infos.storage; - - } - - // ensure correct final padding - - chunkOffset = offset % chunkSize; - - if ( chunkOffset > 0 ) offset += ( chunkSize - chunkOffset ); - - // - - uniformsGroup.__size = offset; - uniformsGroup.__cache = {}; - - return this; - - } - - function getUniformSize( value ) { - - const info = { - boundary: 0, // bytes - storage: 0 // bytes - }; - - // determine sizes according to STD140 - - if ( typeof value === 'number' ) { - - // float/int - - info.boundary = 4; - info.storage = 4; - - } else if ( value.isVector2 ) { - - // vec2 - - info.boundary = 8; - info.storage = 8; - - } else if ( value.isVector3 || value.isColor ) { - - // vec3 - - info.boundary = 16; - info.storage = 12; // evil: vec3 must start on a 16-byte boundary but it only consumes 12 bytes - - } else if ( value.isVector4 ) { - - // vec4 - - info.boundary = 16; - info.storage = 16; - - } else if ( value.isMatrix3 ) { - - // mat3 (in STD140 a 3x3 matrix is represented as 3x4) - - info.boundary = 48; - info.storage = 48; - - } else if ( value.isMatrix4 ) { - - // mat4 - - info.boundary = 64; - info.storage = 64; - - } else if ( value.isTexture ) { - - console.warn( 'THREE.WebGLRenderer: Texture samplers can not be part of an uniforms group.' ); - - } else { - - console.warn( 'THREE.WebGLRenderer: Unsupported uniform value type.', value ); - - } - - return info; - - } - - function onUniformsGroupsDispose( event ) { - - const uniformsGroup = event.target; - - uniformsGroup.removeEventListener( 'dispose', onUniformsGroupsDispose ); - - const index = allocatedBindingPoints.indexOf( uniformsGroup.__bindingPointIndex ); - allocatedBindingPoints.splice( index, 1 ); - - gl.deleteBuffer( buffers[ uniformsGroup.id ] ); - - delete buffers[ uniformsGroup.id ]; - delete updateList[ uniformsGroup.id ]; - - } - - function dispose() { - - for ( const id in buffers ) { - - gl.deleteBuffer( buffers[ id ] ); - - } - - allocatedBindingPoints = []; - buffers = {}; - updateList = {}; - - } - - return { - - bind: bind, - update: update, - - dispose: dispose - - }; - -} - -function createCanvasElement() { - - const canvas = createElementNS( 'canvas' ); - canvas.style.display = 'block'; - return canvas; - -} - -class WebGLRenderer { - - constructor( parameters = {} ) { - - const { - canvas = createCanvasElement(), - context = null, - depth = true, - stencil = true, - alpha = false, - antialias = false, - premultipliedAlpha = true, - preserveDrawingBuffer = false, - powerPreference = 'default', - failIfMajorPerformanceCaveat = false, - } = parameters; - - this.isWebGLRenderer = true; - - let _alpha; - - if ( context !== null ) { - - _alpha = context.getContextAttributes().alpha; - - } else { - - _alpha = alpha; - - } - - const uintClearColor = new Uint32Array( 4 ); - const intClearColor = new Int32Array( 4 ); - - let currentRenderList = null; - let currentRenderState = null; - - // render() can be called from within a callback triggered by another render. - // We track this so that the nested render call gets its list and state isolated from the parent render call. - - const renderListStack = []; - const renderStateStack = []; - - // public properties - - this.domElement = canvas; - - // Debug configuration container - this.debug = { - - /** - * Enables error checking and reporting when shader programs are being compiled - * @type {boolean} - */ - checkShaderErrors: true, - /** - * Callback for custom error reporting. - * @type {?Function} - */ - onShaderError: null - }; - - // clearing - - this.autoClear = true; - this.autoClearColor = true; - this.autoClearDepth = true; - this.autoClearStencil = true; - - // scene graph - - this.sortObjects = true; - - // user-defined clipping - - this.clippingPlanes = []; - this.localClippingEnabled = false; - - // physically based shading - - this.outputColorSpace = SRGBColorSpace; - - // physical lights - - this.useLegacyLights = true; - - // tone mapping - - this.toneMapping = NoToneMapping; - this.toneMappingExposure = 1.0; - - // internal properties - - const _this = this; - - let _isContextLost = false; - - // internal state cache - - let _currentActiveCubeFace = 0; - let _currentActiveMipmapLevel = 0; - let _currentRenderTarget = null; - let _currentMaterialId = - 1; - - let _currentCamera = null; - - const _currentViewport = new Vector4(); - const _currentScissor = new Vector4(); - let _currentScissorTest = null; - - const _currentClearColor = new Color( 0x000000 ); - let _currentClearAlpha = 0; - - // - - let _width = canvas.width; - let _height = canvas.height; - - let _pixelRatio = 1; - let _opaqueSort = null; - let _transparentSort = null; - - const _viewport = new Vector4( 0, 0, _width, _height ); - const _scissor = new Vector4( 0, 0, _width, _height ); - let _scissorTest = false; - - // frustum - - const _frustum = new Frustum(); - - // clipping - - let _clippingEnabled = false; - let _localClippingEnabled = false; - - // transmission - - let _transmissionRenderTarget = null; - - // camera matrices cache - - const _projScreenMatrix = new Matrix4(); - - const _vector3 = new Vector3(); - - const _emptyScene = { background: null, fog: null, environment: null, overrideMaterial: null, isScene: true }; - - function getTargetPixelRatio() { - - return _currentRenderTarget === null ? _pixelRatio : 1; - - } - - // initialize - - let _gl = context; - - function getContext( contextNames, contextAttributes ) { - - for ( let i = 0; i < contextNames.length; i ++ ) { - - const contextName = contextNames[ i ]; - const context = canvas.getContext( contextName, contextAttributes ); - if ( context !== null ) return context; - - } - - return null; - - } - - try { - - const contextAttributes = { - alpha: true, - depth, - stencil, - antialias, - premultipliedAlpha, - preserveDrawingBuffer, - powerPreference, - failIfMajorPerformanceCaveat, - }; - - // OffscreenCanvas does not have setAttribute, see #22811 - if ( 'setAttribute' in canvas ) canvas.setAttribute( 'data-engine', `three.js r${REVISION}` ); - - // event listeners must be registered before WebGL context is created, see #12753 - canvas.addEventListener( 'webglcontextlost', onContextLost, false ); - canvas.addEventListener( 'webglcontextrestored', onContextRestore, false ); - canvas.addEventListener( 'webglcontextcreationerror', onContextCreationError, false ); - - if ( _gl === null ) { - - const contextNames = [ 'webgl2', 'webgl', 'experimental-webgl' ]; - - if ( _this.isWebGL1Renderer === true ) { - - contextNames.shift(); - - } - - _gl = getContext( contextNames, contextAttributes ); - - if ( _gl === null ) { - - if ( getContext( contextNames ) ) { - - throw new Error( 'Error creating WebGL context with your selected attributes.' ); - - } else { - - throw new Error( 'Error creating WebGL context.' ); - - } - - } - - } - - if ( _gl instanceof WebGLRenderingContext ) { // @deprecated, r153 - - console.warn( 'THREE.WebGLRenderer: WebGL 1 support was deprecated in r153 and will be removed in r163.' ); - - } - - // Some experimental-webgl implementations do not have getShaderPrecisionFormat - - if ( _gl.getShaderPrecisionFormat === undefined ) { - - _gl.getShaderPrecisionFormat = function () { - - return { 'rangeMin': 1, 'rangeMax': 1, 'precision': 1 }; - - }; - - } - - } catch ( error ) { - - console.error( 'THREE.WebGLRenderer: ' + error.message ); - throw error; - - } - - let extensions, capabilities, state, info; - let properties, textures, cubemaps, cubeuvmaps, attributes, geometries, objects; - let programCache, materials, renderLists, renderStates, clipping, shadowMap; - - let background, morphtargets, bufferRenderer, indexedBufferRenderer; - - let utils, bindingStates, uniformsGroups; - - function initGLContext() { - - extensions = new WebGLExtensions( _gl ); - - capabilities = new WebGLCapabilities( _gl, extensions, parameters ); - - extensions.init( capabilities ); - - utils = new WebGLUtils( _gl, extensions, capabilities ); - - state = new WebGLState( _gl, extensions, capabilities ); - - info = new WebGLInfo( _gl ); - properties = new WebGLProperties(); - textures = new WebGLTextures( _gl, extensions, state, properties, capabilities, utils, info ); - cubemaps = new WebGLCubeMaps( _this ); - cubeuvmaps = new WebGLCubeUVMaps( _this ); - attributes = new WebGLAttributes( _gl, capabilities ); - bindingStates = new WebGLBindingStates( _gl, extensions, attributes, capabilities ); - geometries = new WebGLGeometries( _gl, attributes, info, bindingStates ); - objects = new WebGLObjects( _gl, geometries, attributes, info ); - morphtargets = new WebGLMorphtargets( _gl, capabilities, textures ); - clipping = new WebGLClipping( properties ); - programCache = new WebGLPrograms( _this, cubemaps, cubeuvmaps, extensions, capabilities, bindingStates, clipping ); - materials = new WebGLMaterials( _this, properties ); - renderLists = new WebGLRenderLists(); - renderStates = new WebGLRenderStates( extensions, capabilities ); - background = new WebGLBackground( _this, cubemaps, cubeuvmaps, state, objects, _alpha, premultipliedAlpha ); - shadowMap = new WebGLShadowMap( _this, objects, capabilities ); - uniformsGroups = new WebGLUniformsGroups( _gl, info, capabilities, state ); - - bufferRenderer = new WebGLBufferRenderer( _gl, extensions, info, capabilities ); - indexedBufferRenderer = new WebGLIndexedBufferRenderer( _gl, extensions, info, capabilities ); - - info.programs = programCache.programs; - - _this.capabilities = capabilities; - _this.extensions = extensions; - _this.properties = properties; - _this.renderLists = renderLists; - _this.shadowMap = shadowMap; - _this.state = state; - _this.info = info; - - } - - initGLContext(); - - // xr - - const xr = new WebXRManager( _this, _gl ); - - this.xr = xr; - - // API - - this.getContext = function () { - - return _gl; - - }; - - this.getContextAttributes = function () { - - return _gl.getContextAttributes(); - - }; - - this.forceContextLoss = function () { - - const extension = extensions.get( 'WEBGL_lose_context' ); - if ( extension ) extension.loseContext(); - - }; - - this.forceContextRestore = function () { - - const extension = extensions.get( 'WEBGL_lose_context' ); - if ( extension ) extension.restoreContext(); - - }; - - this.getPixelRatio = function () { - - return _pixelRatio; - - }; - - this.setPixelRatio = function ( value ) { - - if ( value === undefined ) return; - - _pixelRatio = value; - - this.setSize( _width, _height, false ); - - }; - - this.getSize = function ( target ) { - - return target.set( _width, _height ); - - }; - - this.setSize = function ( width, height, updateStyle = true ) { - - if ( xr.isPresenting ) { - - console.warn( 'THREE.WebGLRenderer: Can\'t change size while VR device is presenting.' ); - return; - - } - - _width = width; - _height = height; - - canvas.width = Math.floor( width * _pixelRatio ); - canvas.height = Math.floor( height * _pixelRatio ); - - if ( updateStyle === true ) { - - canvas.style.width = width + 'px'; - canvas.style.height = height + 'px'; - - } - - this.setViewport( 0, 0, width, height ); - - }; - - this.getDrawingBufferSize = function ( target ) { - - return target.set( _width * _pixelRatio, _height * _pixelRatio ).floor(); - - }; - - this.setDrawingBufferSize = function ( width, height, pixelRatio ) { - - _width = width; - _height = height; - - _pixelRatio = pixelRatio; - - canvas.width = Math.floor( width * pixelRatio ); - canvas.height = Math.floor( height * pixelRatio ); - - this.setViewport( 0, 0, width, height ); - - }; - - this.getCurrentViewport = function ( target ) { - - return target.copy( _currentViewport ); - - }; - - this.getViewport = function ( target ) { - - return target.copy( _viewport ); - - }; - - this.setViewport = function ( x, y, width, height ) { - - if ( x.isVector4 ) { - - _viewport.set( x.x, x.y, x.z, x.w ); - - } else { - - _viewport.set( x, y, width, height ); - - } - - state.viewport( _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ).floor() ); - - }; - - this.getScissor = function ( target ) { - - return target.copy( _scissor ); - - }; - - this.setScissor = function ( x, y, width, height ) { - - if ( x.isVector4 ) { - - _scissor.set( x.x, x.y, x.z, x.w ); - - } else { - - _scissor.set( x, y, width, height ); - - } - - state.scissor( _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ).floor() ); - - }; - - this.getScissorTest = function () { - - return _scissorTest; - - }; - - this.setScissorTest = function ( boolean ) { - - state.setScissorTest( _scissorTest = boolean ); - - }; - - this.setOpaqueSort = function ( method ) { - - _opaqueSort = method; - - }; - - this.setTransparentSort = function ( method ) { - - _transparentSort = method; - - }; - - // Clearing - - this.getClearColor = function ( target ) { - - return target.copy( background.getClearColor() ); - - }; - - this.setClearColor = function () { - - background.setClearColor.apply( background, arguments ); - - }; - - this.getClearAlpha = function () { - - return background.getClearAlpha(); - - }; - - this.setClearAlpha = function () { - - background.setClearAlpha.apply( background, arguments ); - - }; - - this.clear = function ( color = true, depth = true, stencil = true ) { - - let bits = 0; - - if ( color ) { - - // check if we're trying to clear an integer target - let isIntegerFormat = false; - if ( _currentRenderTarget !== null ) { - - const targetFormat = _currentRenderTarget.texture.format; - isIntegerFormat = targetFormat === RGBAIntegerFormat || - targetFormat === RGIntegerFormat || - targetFormat === RedIntegerFormat; - - } - - // use the appropriate clear functions to clear the target if it's a signed - // or unsigned integer target - if ( isIntegerFormat ) { - - const targetType = _currentRenderTarget.texture.type; - const isUnsignedType = targetType === UnsignedByteType || - targetType === UnsignedIntType || - targetType === UnsignedShortType || - targetType === UnsignedInt248Type || - targetType === UnsignedShort4444Type || - targetType === UnsignedShort5551Type; - - const clearColor = background.getClearColor(); - const a = background.getClearAlpha(); - const r = clearColor.r; - const g = clearColor.g; - const b = clearColor.b; - - const __webglFramebuffer = properties.get( _currentRenderTarget ).__webglFramebuffer; - - if ( isUnsignedType ) { - - uintClearColor[ 0 ] = r; - uintClearColor[ 1 ] = g; - uintClearColor[ 2 ] = b; - uintClearColor[ 3 ] = a; - _gl.clearBufferuiv( _gl.COLOR, __webglFramebuffer, uintClearColor ); - - } else { - - intClearColor[ 0 ] = r; - intClearColor[ 1 ] = g; - intClearColor[ 2 ] = b; - intClearColor[ 3 ] = a; - _gl.clearBufferiv( _gl.COLOR, __webglFramebuffer, intClearColor ); - - } - - } else { - - bits |= _gl.COLOR_BUFFER_BIT; - - } - - } - - if ( depth ) bits |= _gl.DEPTH_BUFFER_BIT; - if ( stencil ) bits |= _gl.STENCIL_BUFFER_BIT; - - _gl.clear( bits ); - - }; - - this.clearColor = function () { - - this.clear( true, false, false ); - - }; - - this.clearDepth = function () { - - this.clear( false, true, false ); - - }; - - this.clearStencil = function () { - - this.clear( false, false, true ); - - }; - - // - - this.dispose = function () { - - canvas.removeEventListener( 'webglcontextlost', onContextLost, false ); - canvas.removeEventListener( 'webglcontextrestored', onContextRestore, false ); - canvas.removeEventListener( 'webglcontextcreationerror', onContextCreationError, false ); - - renderLists.dispose(); - renderStates.dispose(); - properties.dispose(); - cubemaps.dispose(); - cubeuvmaps.dispose(); - objects.dispose(); - bindingStates.dispose(); - uniformsGroups.dispose(); - programCache.dispose(); - - xr.dispose(); - - xr.removeEventListener( 'sessionstart', onXRSessionStart ); - xr.removeEventListener( 'sessionend', onXRSessionEnd ); - - if ( _transmissionRenderTarget ) { - - _transmissionRenderTarget.dispose(); - _transmissionRenderTarget = null; - - } - - animation.stop(); - - }; - - // Events - - function onContextLost( event ) { - - event.preventDefault(); - - console.log( 'THREE.WebGLRenderer: Context Lost.' ); - - _isContextLost = true; - - } - - function onContextRestore( /* event */ ) { - - console.log( 'THREE.WebGLRenderer: Context Restored.' ); - - _isContextLost = false; - - const infoAutoReset = info.autoReset; - const shadowMapEnabled = shadowMap.enabled; - const shadowMapAutoUpdate = shadowMap.autoUpdate; - const shadowMapNeedsUpdate = shadowMap.needsUpdate; - const shadowMapType = shadowMap.type; - - initGLContext(); - - info.autoReset = infoAutoReset; - shadowMap.enabled = shadowMapEnabled; - shadowMap.autoUpdate = shadowMapAutoUpdate; - shadowMap.needsUpdate = shadowMapNeedsUpdate; - shadowMap.type = shadowMapType; - - } - - function onContextCreationError( event ) { - - console.error( 'THREE.WebGLRenderer: A WebGL context could not be created. Reason: ', event.statusMessage ); - - } - - function onMaterialDispose( event ) { - - const material = event.target; - - material.removeEventListener( 'dispose', onMaterialDispose ); - - deallocateMaterial( material ); - - } - - // Buffer deallocation - - function deallocateMaterial( material ) { - - releaseMaterialProgramReferences( material ); - - properties.remove( material ); - - } - - - function releaseMaterialProgramReferences( material ) { - - const programs = properties.get( material ).programs; - - if ( programs !== undefined ) { - - programs.forEach( function ( program ) { - - programCache.releaseProgram( program ); - - } ); - - if ( material.isShaderMaterial ) { - - programCache.releaseShaderCache( material ); - - } - - } - - } - - // Buffer rendering - - this.renderBufferDirect = function ( camera, scene, geometry, material, object, group ) { - - if ( scene === null ) scene = _emptyScene; // renderBufferDirect second parameter used to be fog (could be null) - - const frontFaceCW = ( object.isMesh && object.matrixWorld.determinant() < 0 ); - - const program = setProgram( camera, scene, geometry, material, object ); - - state.setMaterial( material, frontFaceCW ); - - // - - let index = geometry.index; - let rangeFactor = 1; - - if ( material.wireframe === true ) { - - index = geometries.getWireframeAttribute( geometry ); - rangeFactor = 2; - - } - - // - - const drawRange = geometry.drawRange; - const position = geometry.attributes.position; - - let drawStart = drawRange.start * rangeFactor; - let drawEnd = ( drawRange.start + drawRange.count ) * rangeFactor; - - if ( group !== null ) { - - drawStart = Math.max( drawStart, group.start * rangeFactor ); - drawEnd = Math.min( drawEnd, ( group.start + group.count ) * rangeFactor ); - - } - - if ( index !== null ) { - - drawStart = Math.max( drawStart, 0 ); - drawEnd = Math.min( drawEnd, index.count ); - - } else if ( position !== undefined && position !== null ) { - - drawStart = Math.max( drawStart, 0 ); - drawEnd = Math.min( drawEnd, position.count ); - - } - - const drawCount = drawEnd - drawStart; - - if ( drawCount < 0 || drawCount === Infinity ) return; - - // - - bindingStates.setup( object, material, program, geometry, index ); - - let attribute; - let renderer = bufferRenderer; - - if ( index !== null ) { - - attribute = attributes.get( index ); - - renderer = indexedBufferRenderer; - renderer.setIndex( attribute ); - - } - - // - - if ( object.isMesh ) { - - if ( material.wireframe === true ) { - - state.setLineWidth( material.wireframeLinewidth * getTargetPixelRatio() ); - renderer.setMode( _gl.LINES ); - - } else { - - renderer.setMode( _gl.TRIANGLES ); - - } - - } else if ( object.isLine ) { - - let lineWidth = material.linewidth; - - if ( lineWidth === undefined ) lineWidth = 1; // Not using Line*Material - - state.setLineWidth( lineWidth * getTargetPixelRatio() ); - - if ( object.isLineSegments ) { - - renderer.setMode( _gl.LINES ); - - } else if ( object.isLineLoop ) { - - renderer.setMode( _gl.LINE_LOOP ); - - } else { - - renderer.setMode( _gl.LINE_STRIP ); - - } - - } else if ( object.isPoints ) { - - renderer.setMode( _gl.POINTS ); - - } else if ( object.isSprite ) { - - renderer.setMode( _gl.TRIANGLES ); - - } - - if ( object.isInstancedMesh ) { - - renderer.renderInstances( drawStart, drawCount, object.count ); - - } else if ( geometry.isInstancedBufferGeometry ) { - - const maxInstanceCount = geometry._maxInstanceCount !== undefined ? geometry._maxInstanceCount : Infinity; - const instanceCount = Math.min( geometry.instanceCount, maxInstanceCount ); - - renderer.renderInstances( drawStart, drawCount, instanceCount ); - - } else { - - renderer.render( drawStart, drawCount ); - - } - - }; - - // Compile - - this.compile = function ( scene, camera ) { - - function prepare( material, scene, object ) { - - if ( material.transparent === true && material.side === DoubleSide && material.forceSinglePass === false ) { - - material.side = BackSide; - material.needsUpdate = true; - getProgram( material, scene, object ); - - material.side = FrontSide; - material.needsUpdate = true; - getProgram( material, scene, object ); - - material.side = DoubleSide; - - } else { - - getProgram( material, scene, object ); - - } - - } - - currentRenderState = renderStates.get( scene ); - currentRenderState.init(); - - renderStateStack.push( currentRenderState ); - - scene.traverseVisible( function ( object ) { - - if ( object.isLight && object.layers.test( camera.layers ) ) { - - currentRenderState.pushLight( object ); - - if ( object.castShadow ) { - - currentRenderState.pushShadow( object ); - - } - - } - - } ); - - currentRenderState.setupLights( _this.useLegacyLights ); - - scene.traverse( function ( object ) { - - const material = object.material; - - if ( material ) { - - if ( Array.isArray( material ) ) { - - for ( let i = 0; i < material.length; i ++ ) { - - const material2 = material[ i ]; - - prepare( material2, scene, object ); - - } - - } else { - - prepare( material, scene, object ); - - } - - } - - } ); - - renderStateStack.pop(); - currentRenderState = null; - - }; - - // Animation Loop - - let onAnimationFrameCallback = null; - - function onAnimationFrame( time ) { - - if ( onAnimationFrameCallback ) onAnimationFrameCallback( time ); - - } - - function onXRSessionStart() { - - animation.stop(); - - } - - function onXRSessionEnd() { - - animation.start(); - - } - - const animation = new WebGLAnimation(); - animation.setAnimationLoop( onAnimationFrame ); - - if ( typeof self !== 'undefined' ) animation.setContext( self ); - - this.setAnimationLoop = function ( callback ) { - - onAnimationFrameCallback = callback; - xr.setAnimationLoop( callback ); - - ( callback === null ) ? animation.stop() : animation.start(); - - }; - - xr.addEventListener( 'sessionstart', onXRSessionStart ); - xr.addEventListener( 'sessionend', onXRSessionEnd ); - - // Rendering - - this.render = function ( scene, camera ) { - - if ( camera !== undefined && camera.isCamera !== true ) { - - console.error( 'THREE.WebGLRenderer.render: camera is not an instance of THREE.Camera.' ); - return; - - } - - if ( _isContextLost === true ) return; - - // update scene graph - - if ( scene.matrixWorldAutoUpdate === true ) scene.updateMatrixWorld(); - - // update camera matrices and frustum - - if ( camera.parent === null && camera.matrixWorldAutoUpdate === true ) camera.updateMatrixWorld(); - - if ( xr.enabled === true && xr.isPresenting === true ) { - - camera = xr.updateCameraXR( camera ); // use XR camera for rendering - - } - - // - if ( scene.isScene === true ) scene.onBeforeRender( _this, scene, camera, _currentRenderTarget ); - - currentRenderState = renderStates.get( scene, renderStateStack.length ); - currentRenderState.init(); - - renderStateStack.push( currentRenderState ); - - _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); - _frustum.setFromProjectionMatrix( _projScreenMatrix ); - - _localClippingEnabled = this.localClippingEnabled; - _clippingEnabled = clipping.init( this.clippingPlanes, _localClippingEnabled ); - - currentRenderList = renderLists.get( scene, renderListStack.length ); - currentRenderList.init(); - - renderListStack.push( currentRenderList ); - - projectObject( scene, camera, 0, _this.sortObjects ); - - currentRenderList.finish(); - - if ( _this.sortObjects === true ) { - - currentRenderList.sort( _opaqueSort, _transparentSort ); - - } - - // - - if ( _clippingEnabled === true ) clipping.beginShadows(); - - const shadowsArray = currentRenderState.state.shadowsArray; - - shadowMap.render( shadowsArray, scene, camera ); - - if ( _clippingEnabled === true ) clipping.endShadows(); - - // - - if ( this.info.autoReset === true ) this.info.reset(); - - this.info.render.frame ++; - - // - - background.render( currentRenderList, scene ); - - // render scene - - currentRenderState.setupLights( _this.useLegacyLights ); - - if ( camera.isArrayCamera ) { - - const cameras = camera.cameras; - - for ( let i = 0, l = cameras.length; i < l; i ++ ) { - - const camera2 = cameras[ i ]; - - renderScene( currentRenderList, scene, camera2, camera2.viewport ); - - } - - } else { - - renderScene( currentRenderList, scene, camera ); - - } - - // - - if ( _currentRenderTarget !== null ) { - - // resolve multisample renderbuffers to a single-sample texture if necessary - - textures.updateMultisampleRenderTarget( _currentRenderTarget ); - - // Generate mipmap if we're using any kind of mipmap filtering - - textures.updateRenderTargetMipmap( _currentRenderTarget ); - - } - - // - - if ( scene.isScene === true ) scene.onAfterRender( _this, scene, camera ); - - // _gl.finish(); - - bindingStates.resetDefaultState(); - _currentMaterialId = - 1; - _currentCamera = null; - - renderStateStack.pop(); - - if ( renderStateStack.length > 0 ) { - - currentRenderState = renderStateStack[ renderStateStack.length - 1 ]; - - } else { - - currentRenderState = null; - - } - - renderListStack.pop(); - - if ( renderListStack.length > 0 ) { - - currentRenderList = renderListStack[ renderListStack.length - 1 ]; - - } else { - - currentRenderList = null; - - } - - }; - - function projectObject( object, camera, groupOrder, sortObjects ) { - - if ( object.visible === false ) return; - - const visible = object.layers.test( camera.layers ); - - if ( visible ) { - - if ( object.isGroup ) { - - groupOrder = object.renderOrder; - - } else if ( object.isLOD ) { - - if ( object.autoUpdate === true ) object.update( camera ); - - } else if ( object.isLight ) { - - currentRenderState.pushLight( object ); - - if ( object.castShadow ) { - - currentRenderState.pushShadow( object ); - - } - - } else if ( object.isSprite ) { - - if ( ! object.frustumCulled || _frustum.intersectsSprite( object ) ) { - - if ( sortObjects ) { - - _vector3.setFromMatrixPosition( object.matrixWorld ) - .applyMatrix4( _projScreenMatrix ); - - } - - const geometry = objects.update( object ); - const material = object.material; - - if ( material.visible ) { - - currentRenderList.push( object, geometry, material, groupOrder, _vector3.z, null ); - - } - - } - - } else if ( object.isMesh || object.isLine || object.isPoints ) { - - if ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) { - - if ( object.isSkinnedMesh ) { - - // update skeleton only once in a frame - - if ( object.skeleton.frame !== info.render.frame ) { - - object.skeleton.update(); - object.skeleton.frame = info.render.frame; - - } - - } - - const geometry = objects.update( object ); - const material = object.material; - - if ( sortObjects ) { - - if ( object.boundingSphere !== undefined ) { - - if ( object.boundingSphere === null ) object.computeBoundingSphere(); - _vector3.copy( object.boundingSphere.center ); - - } else { - - if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); - _vector3.copy( geometry.boundingSphere.center ); - - } - - _vector3 - .applyMatrix4( object.matrixWorld ) - .applyMatrix4( _projScreenMatrix ); - - } - - if ( Array.isArray( material ) ) { - - const groups = geometry.groups; - - for ( let i = 0, l = groups.length; i < l; i ++ ) { - - const group = groups[ i ]; - const groupMaterial = material[ group.materialIndex ]; - - if ( groupMaterial && groupMaterial.visible ) { - - currentRenderList.push( object, geometry, groupMaterial, groupOrder, _vector3.z, group ); - - } - - } - - } else if ( material.visible ) { - - currentRenderList.push( object, geometry, material, groupOrder, _vector3.z, null ); - - } - - } - - } - - } - - const children = object.children; - - for ( let i = 0, l = children.length; i < l; i ++ ) { - - projectObject( children[ i ], camera, groupOrder, sortObjects ); - - } - - } - - function renderScene( currentRenderList, scene, camera, viewport ) { - - const opaqueObjects = currentRenderList.opaque; - const transmissiveObjects = currentRenderList.transmissive; - const transparentObjects = currentRenderList.transparent; - - currentRenderState.setupLightsView( camera ); - - if ( _clippingEnabled === true ) clipping.setGlobalState( _this.clippingPlanes, camera ); - - if ( transmissiveObjects.length > 0 ) renderTransmissionPass( opaqueObjects, transmissiveObjects, scene, camera ); - - if ( viewport ) state.viewport( _currentViewport.copy( viewport ) ); - - if ( opaqueObjects.length > 0 ) renderObjects( opaqueObjects, scene, camera ); - if ( transmissiveObjects.length > 0 ) renderObjects( transmissiveObjects, scene, camera ); - if ( transparentObjects.length > 0 ) renderObjects( transparentObjects, scene, camera ); - - // Ensure depth buffer writing is enabled so it can be cleared on next render - - state.buffers.depth.setTest( true ); - state.buffers.depth.setMask( true ); - state.buffers.color.setMask( true ); - - state.setPolygonOffset( false ); - - } - - function renderTransmissionPass( opaqueObjects, transmissiveObjects, scene, camera ) { - - if ( _transmissionRenderTarget === null ) { - - const isWebGL2 = capabilities.isWebGL2; - - _transmissionRenderTarget = new WebGLRenderTarget( 1024, 1024, { - generateMipmaps: true, - type: extensions.has( 'EXT_color_buffer_half_float' ) ? HalfFloatType : UnsignedByteType, - minFilter: LinearMipmapLinearFilter, - samples: ( isWebGL2 && antialias === true ) ? 4 : 0 - } ); - - // debug - - /* - const geometry = new PlaneGeometry(); - const material = new MeshBasicMaterial( { map: _transmissionRenderTarget.texture } ); - - const mesh = new Mesh( geometry, material ); - scene.add( mesh ); - */ - - } - - // - - const currentRenderTarget = _this.getRenderTarget(); - _this.setRenderTarget( _transmissionRenderTarget ); - - _this.getClearColor( _currentClearColor ); - _currentClearAlpha = _this.getClearAlpha(); - if ( _currentClearAlpha < 1 ) _this.setClearColor( 0xffffff, 0.5 ); - - _this.clear(); - - // Turn off the features which can affect the frag color for opaque objects pass. - // Otherwise they are applied twice in opaque objects pass and transmission objects pass. - const currentToneMapping = _this.toneMapping; - _this.toneMapping = NoToneMapping; - - renderObjects( opaqueObjects, scene, camera ); - - textures.updateMultisampleRenderTarget( _transmissionRenderTarget ); - textures.updateRenderTargetMipmap( _transmissionRenderTarget ); - - let renderTargetNeedsUpdate = false; - - for ( let i = 0, l = transmissiveObjects.length; i < l; i ++ ) { - - const renderItem = transmissiveObjects[ i ]; - - const object = renderItem.object; - const geometry = renderItem.geometry; - const material = renderItem.material; - const group = renderItem.group; - - if ( material.side === DoubleSide && object.layers.test( camera.layers ) ) { - - const currentSide = material.side; - - material.side = BackSide; - material.needsUpdate = true; - - renderObject( object, scene, camera, geometry, material, group ); - - material.side = currentSide; - material.needsUpdate = true; - - renderTargetNeedsUpdate = true; - - } - - } - - if ( renderTargetNeedsUpdate === true ) { - - textures.updateMultisampleRenderTarget( _transmissionRenderTarget ); - textures.updateRenderTargetMipmap( _transmissionRenderTarget ); - - } - - _this.setRenderTarget( currentRenderTarget ); - - _this.setClearColor( _currentClearColor, _currentClearAlpha ); - - _this.toneMapping = currentToneMapping; - - } - - function renderObjects( renderList, scene, camera ) { - - const overrideMaterial = scene.isScene === true ? scene.overrideMaterial : null; - - for ( let i = 0, l = renderList.length; i < l; i ++ ) { - - const renderItem = renderList[ i ]; - - const object = renderItem.object; - const geometry = renderItem.geometry; - const material = overrideMaterial === null ? renderItem.material : overrideMaterial; - const group = renderItem.group; - - if ( object.layers.test( camera.layers ) ) { - - renderObject( object, scene, camera, geometry, material, group ); - - } - - } - - } - - function renderObject( object, scene, camera, geometry, material, group ) { - - object.onBeforeRender( _this, scene, camera, geometry, material, group ); - - object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld ); - object.normalMatrix.getNormalMatrix( object.modelViewMatrix ); - - material.onBeforeRender( _this, scene, camera, geometry, object, group ); - - if ( material.transparent === true && material.side === DoubleSide && material.forceSinglePass === false ) { - - material.side = BackSide; - material.needsUpdate = true; - _this.renderBufferDirect( camera, scene, geometry, material, object, group ); - - material.side = FrontSide; - material.needsUpdate = true; - _this.renderBufferDirect( camera, scene, geometry, material, object, group ); - - material.side = DoubleSide; - - } else { - - _this.renderBufferDirect( camera, scene, geometry, material, object, group ); - - } - - object.onAfterRender( _this, scene, camera, geometry, material, group ); - - } - - function getProgram( material, scene, object ) { - - if ( scene.isScene !== true ) scene = _emptyScene; // scene could be a Mesh, Line, Points, ... - - const materialProperties = properties.get( material ); - - const lights = currentRenderState.state.lights; - const shadowsArray = currentRenderState.state.shadowsArray; - - const lightsStateVersion = lights.state.version; - - const parameters = programCache.getParameters( material, lights.state, shadowsArray, scene, object ); - const programCacheKey = programCache.getProgramCacheKey( parameters ); - - let programs = materialProperties.programs; - - // always update environment and fog - changing these trigger an getProgram call, but it's possible that the program doesn't change - - materialProperties.environment = material.isMeshStandardMaterial ? scene.environment : null; - materialProperties.fog = scene.fog; - materialProperties.envMap = ( material.isMeshStandardMaterial ? cubeuvmaps : cubemaps ).get( material.envMap || materialProperties.environment ); - - if ( programs === undefined ) { - - // new material - - material.addEventListener( 'dispose', onMaterialDispose ); - - programs = new Map(); - materialProperties.programs = programs; - - } - - let program = programs.get( programCacheKey ); - - if ( program !== undefined ) { - - // early out if program and light state is identical - - if ( materialProperties.currentProgram === program && materialProperties.lightsStateVersion === lightsStateVersion ) { - - updateCommonMaterialProperties( material, parameters ); - - return program; - - } - - } else { - - parameters.uniforms = programCache.getUniforms( material ); - - material.onBuild( object, parameters, _this ); - - material.onBeforeCompile( parameters, _this ); - - program = programCache.acquireProgram( parameters, programCacheKey ); - programs.set( programCacheKey, program ); - - materialProperties.uniforms = parameters.uniforms; - - } - - const uniforms = materialProperties.uniforms; - - if ( ( ! material.isShaderMaterial && ! material.isRawShaderMaterial ) || material.clipping === true ) { - - uniforms.clippingPlanes = clipping.uniform; - - } - - updateCommonMaterialProperties( material, parameters ); - - // store the light setup it was created for - - materialProperties.needsLights = materialNeedsLights( material ); - materialProperties.lightsStateVersion = lightsStateVersion; - - if ( materialProperties.needsLights ) { - - // wire up the material to this renderer's lighting state - - uniforms.ambientLightColor.value = lights.state.ambient; - uniforms.lightProbe.value = lights.state.probe; - uniforms.directionalLights.value = lights.state.directional; - uniforms.directionalLightShadows.value = lights.state.directionalShadow; - uniforms.spotLights.value = lights.state.spot; - uniforms.spotLightShadows.value = lights.state.spotShadow; - uniforms.rectAreaLights.value = lights.state.rectArea; - uniforms.ltc_1.value = lights.state.rectAreaLTC1; - uniforms.ltc_2.value = lights.state.rectAreaLTC2; - uniforms.pointLights.value = lights.state.point; - uniforms.pointLightShadows.value = lights.state.pointShadow; - uniforms.hemisphereLights.value = lights.state.hemi; - - uniforms.directionalShadowMap.value = lights.state.directionalShadowMap; - uniforms.directionalShadowMatrix.value = lights.state.directionalShadowMatrix; - uniforms.spotShadowMap.value = lights.state.spotShadowMap; - uniforms.spotLightMatrix.value = lights.state.spotLightMatrix; - uniforms.spotLightMap.value = lights.state.spotLightMap; - uniforms.pointShadowMap.value = lights.state.pointShadowMap; - uniforms.pointShadowMatrix.value = lights.state.pointShadowMatrix; - // TODO (abelnation): add area lights shadow info to uniforms - - } - - const progUniforms = program.getUniforms(); - const uniformsList = WebGLUniforms.seqWithValue( progUniforms.seq, uniforms ); - - materialProperties.currentProgram = program; - materialProperties.uniformsList = uniformsList; - - return program; - - } - - function updateCommonMaterialProperties( material, parameters ) { - - const materialProperties = properties.get( material ); - - materialProperties.outputColorSpace = parameters.outputColorSpace; - materialProperties.instancing = parameters.instancing; - materialProperties.skinning = parameters.skinning; - materialProperties.morphTargets = parameters.morphTargets; - materialProperties.morphNormals = parameters.morphNormals; - materialProperties.morphColors = parameters.morphColors; - materialProperties.morphTargetsCount = parameters.morphTargetsCount; - materialProperties.numClippingPlanes = parameters.numClippingPlanes; - materialProperties.numIntersection = parameters.numClipIntersection; - materialProperties.vertexAlphas = parameters.vertexAlphas; - materialProperties.vertexTangents = parameters.vertexTangents; - materialProperties.toneMapping = parameters.toneMapping; - - } - - function setProgram( camera, scene, geometry, material, object ) { - - if ( scene.isScene !== true ) scene = _emptyScene; // scene could be a Mesh, Line, Points, ... - - textures.resetTextureUnits(); - - const fog = scene.fog; - const environment = material.isMeshStandardMaterial ? scene.environment : null; - const colorSpace = ( _currentRenderTarget === null ) ? _this.outputColorSpace : ( _currentRenderTarget.isXRRenderTarget === true ? _currentRenderTarget.texture.colorSpace : LinearSRGBColorSpace ); - const envMap = ( material.isMeshStandardMaterial ? cubeuvmaps : cubemaps ).get( material.envMap || environment ); - const vertexAlphas = material.vertexColors === true && !! geometry.attributes.color && geometry.attributes.color.itemSize === 4; - const vertexTangents = !! geometry.attributes.tangent && ( !! material.normalMap || material.anisotropy > 0 ); - const morphTargets = !! geometry.morphAttributes.position; - const morphNormals = !! geometry.morphAttributes.normal; - const morphColors = !! geometry.morphAttributes.color; - const toneMapping = material.toneMapped ? _this.toneMapping : NoToneMapping; - - const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color; - const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0; - - const materialProperties = properties.get( material ); - const lights = currentRenderState.state.lights; - - if ( _clippingEnabled === true ) { - - if ( _localClippingEnabled === true || camera !== _currentCamera ) { - - const useCache = - camera === _currentCamera && - material.id === _currentMaterialId; - - // we might want to call this function with some ClippingGroup - // object instead of the material, once it becomes feasible - // (#8465, #8379) - clipping.setState( material, camera, useCache ); - - } - - } - - // - - let needsProgramChange = false; - - if ( material.version === materialProperties.__version ) { - - if ( materialProperties.needsLights && ( materialProperties.lightsStateVersion !== lights.state.version ) ) { - - needsProgramChange = true; - - } else if ( materialProperties.outputColorSpace !== colorSpace ) { - - needsProgramChange = true; - - } else if ( object.isInstancedMesh && materialProperties.instancing === false ) { - - needsProgramChange = true; - - } else if ( ! object.isInstancedMesh && materialProperties.instancing === true ) { - - needsProgramChange = true; - - } else if ( object.isSkinnedMesh && materialProperties.skinning === false ) { - - needsProgramChange = true; - - } else if ( ! object.isSkinnedMesh && materialProperties.skinning === true ) { - - needsProgramChange = true; - - } else if ( materialProperties.envMap !== envMap ) { - - needsProgramChange = true; - - } else if ( material.fog === true && materialProperties.fog !== fog ) { - - needsProgramChange = true; - - } else if ( materialProperties.numClippingPlanes !== undefined && - ( materialProperties.numClippingPlanes !== clipping.numPlanes || - materialProperties.numIntersection !== clipping.numIntersection ) ) { - - needsProgramChange = true; - - } else if ( materialProperties.vertexAlphas !== vertexAlphas ) { - - needsProgramChange = true; - - } else if ( materialProperties.vertexTangents !== vertexTangents ) { - - needsProgramChange = true; - - } else if ( materialProperties.morphTargets !== morphTargets ) { - - needsProgramChange = true; - - } else if ( materialProperties.morphNormals !== morphNormals ) { - - needsProgramChange = true; - - } else if ( materialProperties.morphColors !== morphColors ) { - - needsProgramChange = true; - - } else if ( materialProperties.toneMapping !== toneMapping ) { - - needsProgramChange = true; - - } else if ( capabilities.isWebGL2 === true && materialProperties.morphTargetsCount !== morphTargetsCount ) { - - needsProgramChange = true; - - } - - } else { - - needsProgramChange = true; - materialProperties.__version = material.version; - - } - - // - - let program = materialProperties.currentProgram; - - if ( needsProgramChange === true ) { - - program = getProgram( material, scene, object ); - - } - - let refreshProgram = false; - let refreshMaterial = false; - let refreshLights = false; - - const p_uniforms = program.getUniforms(), - m_uniforms = materialProperties.uniforms; - - if ( state.useProgram( program.program ) ) { - - refreshProgram = true; - refreshMaterial = true; - refreshLights = true; - - } - - if ( material.id !== _currentMaterialId ) { - - _currentMaterialId = material.id; - - refreshMaterial = true; - - } - - if ( refreshProgram || _currentCamera !== camera ) { - - p_uniforms.setValue( _gl, 'projectionMatrix', camera.projectionMatrix ); - - if ( capabilities.logarithmicDepthBuffer ) { - - p_uniforms.setValue( _gl, 'logDepthBufFC', - 2.0 / ( Math.log( camera.far + 1.0 ) / Math.LN2 ) ); - - } - - if ( _currentCamera !== camera ) { - - _currentCamera = camera; - - // lighting uniforms depend on the camera so enforce an update - // now, in case this material supports lights - or later, when - // the next material that does gets activated: - - refreshMaterial = true; // set to true on material change - refreshLights = true; // remains set until update done - - } - - // load material specific uniforms - // (shader material also gets them for the sake of genericity) - - if ( material.isShaderMaterial || - material.isMeshPhongMaterial || - material.isMeshToonMaterial || - material.isMeshStandardMaterial || - material.envMap ) { - - const uCamPos = p_uniforms.map.cameraPosition; - - if ( uCamPos !== undefined ) { - - uCamPos.setValue( _gl, - _vector3.setFromMatrixPosition( camera.matrixWorld ) ); - - } - - } - - if ( material.isMeshPhongMaterial || - material.isMeshToonMaterial || - material.isMeshLambertMaterial || - material.isMeshBasicMaterial || - material.isMeshStandardMaterial || - material.isShaderMaterial ) { - - p_uniforms.setValue( _gl, 'isOrthographic', camera.isOrthographicCamera === true ); - - } - - if ( material.isMeshPhongMaterial || - material.isMeshToonMaterial || - material.isMeshLambertMaterial || - material.isMeshBasicMaterial || - material.isMeshStandardMaterial || - material.isShaderMaterial || - material.isShadowMaterial || - object.isSkinnedMesh ) { - - p_uniforms.setValue( _gl, 'viewMatrix', camera.matrixWorldInverse ); - - } - - } - - // skinning and morph target uniforms must be set even if material didn't change - // auto-setting of texture unit for bone and morph texture must go before other textures - // otherwise textures used for skinning and morphing can take over texture units reserved for other material textures - - if ( object.isSkinnedMesh ) { - - p_uniforms.setOptional( _gl, object, 'bindMatrix' ); - p_uniforms.setOptional( _gl, object, 'bindMatrixInverse' ); - - const skeleton = object.skeleton; - - if ( skeleton ) { - - if ( capabilities.floatVertexTextures ) { - - if ( skeleton.boneTexture === null ) skeleton.computeBoneTexture(); - - p_uniforms.setValue( _gl, 'boneTexture', skeleton.boneTexture, textures ); - p_uniforms.setValue( _gl, 'boneTextureSize', skeleton.boneTextureSize ); - - } else { - - console.warn( 'THREE.WebGLRenderer: SkinnedMesh can only be used with WebGL 2. With WebGL 1 OES_texture_float and vertex textures support is required.' ); - - } - - } - - } - - const morphAttributes = geometry.morphAttributes; - - if ( morphAttributes.position !== undefined || morphAttributes.normal !== undefined || ( morphAttributes.color !== undefined && capabilities.isWebGL2 === true ) ) { - - morphtargets.update( object, geometry, program ); - - } - - if ( refreshMaterial || materialProperties.receiveShadow !== object.receiveShadow ) { - - materialProperties.receiveShadow = object.receiveShadow; - p_uniforms.setValue( _gl, 'receiveShadow', object.receiveShadow ); - - } - - // https://github.com/mrdoob/three.js/pull/24467#issuecomment-1209031512 - - if ( material.isMeshGouraudMaterial && material.envMap !== null ) { - - m_uniforms.envMap.value = envMap; - - m_uniforms.flipEnvMap.value = ( envMap.isCubeTexture && envMap.isRenderTargetTexture === false ) ? - 1 : 1; - - } - - if ( refreshMaterial ) { - - p_uniforms.setValue( _gl, 'toneMappingExposure', _this.toneMappingExposure ); - - if ( materialProperties.needsLights ) { - - // the current material requires lighting info - - // note: all lighting uniforms are always set correctly - // they simply reference the renderer's state for their - // values - // - // use the current material's .needsUpdate flags to set - // the GL state when required - - markUniformsLightsNeedsUpdate( m_uniforms, refreshLights ); - - } - - // refresh uniforms common to several materials - - if ( fog && material.fog === true ) { - - materials.refreshFogUniforms( m_uniforms, fog ); - - } - - materials.refreshMaterialUniforms( m_uniforms, material, _pixelRatio, _height, _transmissionRenderTarget ); - - WebGLUniforms.upload( _gl, materialProperties.uniformsList, m_uniforms, textures ); - - } - - if ( material.isShaderMaterial && material.uniformsNeedUpdate === true ) { - - WebGLUniforms.upload( _gl, materialProperties.uniformsList, m_uniforms, textures ); - material.uniformsNeedUpdate = false; - - } - - if ( material.isSpriteMaterial ) { - - p_uniforms.setValue( _gl, 'center', object.center ); - - } - - // common matrices - - p_uniforms.setValue( _gl, 'modelViewMatrix', object.modelViewMatrix ); - p_uniforms.setValue( _gl, 'normalMatrix', object.normalMatrix ); - p_uniforms.setValue( _gl, 'modelMatrix', object.matrixWorld ); - - // UBOs - - if ( material.isShaderMaterial || material.isRawShaderMaterial ) { - - const groups = material.uniformsGroups; - - for ( let i = 0, l = groups.length; i < l; i ++ ) { - - if ( capabilities.isWebGL2 ) { - - const group = groups[ i ]; - - uniformsGroups.update( group, program ); - uniformsGroups.bind( group, program ); - - } else { - - console.warn( 'THREE.WebGLRenderer: Uniform Buffer Objects can only be used with WebGL 2.' ); - - } - - } - - } - - return program; - - } - - // If uniforms are marked as clean, they don't need to be loaded to the GPU. - - function markUniformsLightsNeedsUpdate( uniforms, value ) { - - uniforms.ambientLightColor.needsUpdate = value; - uniforms.lightProbe.needsUpdate = value; - - uniforms.directionalLights.needsUpdate = value; - uniforms.directionalLightShadows.needsUpdate = value; - uniforms.pointLights.needsUpdate = value; - uniforms.pointLightShadows.needsUpdate = value; - uniforms.spotLights.needsUpdate = value; - uniforms.spotLightShadows.needsUpdate = value; - uniforms.rectAreaLights.needsUpdate = value; - uniforms.hemisphereLights.needsUpdate = value; - - } - - function materialNeedsLights( material ) { - - return material.isMeshLambertMaterial || material.isMeshToonMaterial || material.isMeshPhongMaterial || - material.isMeshStandardMaterial || material.isShadowMaterial || - ( material.isShaderMaterial && material.lights === true ); - - } - - this.getActiveCubeFace = function () { - - return _currentActiveCubeFace; - - }; - - this.getActiveMipmapLevel = function () { - - return _currentActiveMipmapLevel; - - }; - - this.getRenderTarget = function () { - - return _currentRenderTarget; - - }; - - this.setRenderTargetTextures = function ( renderTarget, colorTexture, depthTexture ) { - - properties.get( renderTarget.texture ).__webglTexture = colorTexture; - properties.get( renderTarget.depthTexture ).__webglTexture = depthTexture; - - const renderTargetProperties = properties.get( renderTarget ); - renderTargetProperties.__hasExternalTextures = true; - - if ( renderTargetProperties.__hasExternalTextures ) { - - renderTargetProperties.__autoAllocateDepthBuffer = depthTexture === undefined; - - if ( ! renderTargetProperties.__autoAllocateDepthBuffer ) { - - // The multisample_render_to_texture extension doesn't work properly if there - // are midframe flushes and an external depth buffer. Disable use of the extension. - if ( extensions.has( 'WEBGL_multisampled_render_to_texture' ) === true ) { - - console.warn( 'THREE.WebGLRenderer: Render-to-texture extension was disabled because an external texture was provided' ); - renderTargetProperties.__useRenderToTexture = false; - - } - - } - - } - - }; - - this.setRenderTargetFramebuffer = function ( renderTarget, defaultFramebuffer ) { - - const renderTargetProperties = properties.get( renderTarget ); - renderTargetProperties.__webglFramebuffer = defaultFramebuffer; - renderTargetProperties.__useDefaultFramebuffer = defaultFramebuffer === undefined; - - }; - - this.setRenderTarget = function ( renderTarget, activeCubeFace = 0, activeMipmapLevel = 0 ) { - - _currentRenderTarget = renderTarget; - _currentActiveCubeFace = activeCubeFace; - _currentActiveMipmapLevel = activeMipmapLevel; - - let useDefaultFramebuffer = true; - let framebuffer = null; - let isCube = false; - let isRenderTarget3D = false; - - if ( renderTarget ) { - - const renderTargetProperties = properties.get( renderTarget ); - - if ( renderTargetProperties.__useDefaultFramebuffer !== undefined ) { - - // We need to make sure to rebind the framebuffer. - state.bindFramebuffer( _gl.FRAMEBUFFER, null ); - useDefaultFramebuffer = false; - - } else if ( renderTargetProperties.__webglFramebuffer === undefined ) { - - textures.setupRenderTarget( renderTarget ); - - } else if ( renderTargetProperties.__hasExternalTextures ) { - - // Color and depth texture must be rebound in order for the swapchain to update. - textures.rebindTextures( renderTarget, properties.get( renderTarget.texture ).__webglTexture, properties.get( renderTarget.depthTexture ).__webglTexture ); - - } - - const texture = renderTarget.texture; - - if ( texture.isData3DTexture || texture.isDataArrayTexture || texture.isCompressedArrayTexture ) { - - isRenderTarget3D = true; - - } - - const __webglFramebuffer = properties.get( renderTarget ).__webglFramebuffer; - - if ( renderTarget.isWebGLCubeRenderTarget ) { - - framebuffer = __webglFramebuffer[ activeCubeFace ]; - isCube = true; - - } else if ( ( capabilities.isWebGL2 && renderTarget.samples > 0 ) && textures.useMultisampledRTT( renderTarget ) === false ) { - - framebuffer = properties.get( renderTarget ).__webglMultisampledFramebuffer; - - } else { - - framebuffer = __webglFramebuffer; - - } - - _currentViewport.copy( renderTarget.viewport ); - _currentScissor.copy( renderTarget.scissor ); - _currentScissorTest = renderTarget.scissorTest; - - } else { - - _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ).floor(); - _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ).floor(); - _currentScissorTest = _scissorTest; - - } - - const framebufferBound = state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); - - if ( framebufferBound && capabilities.drawBuffers && useDefaultFramebuffer ) { - - state.drawBuffers( renderTarget, framebuffer ); - - } - - state.viewport( _currentViewport ); - state.scissor( _currentScissor ); - state.setScissorTest( _currentScissorTest ); - - if ( isCube ) { - - const textureProperties = properties.get( renderTarget.texture ); - _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + activeCubeFace, textureProperties.__webglTexture, activeMipmapLevel ); - - } else if ( isRenderTarget3D ) { - - const textureProperties = properties.get( renderTarget.texture ); - const layer = activeCubeFace || 0; - _gl.framebufferTextureLayer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, textureProperties.__webglTexture, activeMipmapLevel || 0, layer ); - - } - - _currentMaterialId = - 1; // reset current material to ensure correct uniform bindings - - }; - - this.readRenderTargetPixels = function ( renderTarget, x, y, width, height, buffer, activeCubeFaceIndex ) { - - if ( ! ( renderTarget && renderTarget.isWebGLRenderTarget ) ) { - - console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.' ); - return; - - } - - let framebuffer = properties.get( renderTarget ).__webglFramebuffer; - - if ( renderTarget.isWebGLCubeRenderTarget && activeCubeFaceIndex !== undefined ) { - - framebuffer = framebuffer[ activeCubeFaceIndex ]; - - } - - if ( framebuffer ) { - - state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); - - try { - - const texture = renderTarget.texture; - const textureFormat = texture.format; - const textureType = texture.type; - - if ( textureFormat !== RGBAFormat && utils.convert( textureFormat ) !== _gl.getParameter( _gl.IMPLEMENTATION_COLOR_READ_FORMAT ) ) { - - console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in RGBA or implementation defined format.' ); - return; - - } - - const halfFloatSupportedByExt = ( textureType === HalfFloatType ) && ( extensions.has( 'EXT_color_buffer_half_float' ) || ( capabilities.isWebGL2 && extensions.has( 'EXT_color_buffer_float' ) ) ); - - if ( textureType !== UnsignedByteType && utils.convert( textureType ) !== _gl.getParameter( _gl.IMPLEMENTATION_COLOR_READ_TYPE ) && // Edge and Chrome Mac < 52 (#9513) - ! ( textureType === FloatType && ( capabilities.isWebGL2 || extensions.has( 'OES_texture_float' ) || extensions.has( 'WEBGL_color_buffer_float' ) ) ) && // Chrome Mac >= 52 and Firefox - ! halfFloatSupportedByExt ) { - - console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in UnsignedByteType or implementation defined type.' ); - return; - - } - - // the following if statement ensures valid read requests (no out-of-bounds pixels, see #8604) - - if ( ( x >= 0 && x <= ( renderTarget.width - width ) ) && ( y >= 0 && y <= ( renderTarget.height - height ) ) ) { - - _gl.readPixels( x, y, width, height, utils.convert( textureFormat ), utils.convert( textureType ), buffer ); - - } - - } finally { - - // restore framebuffer of current render target if necessary - - const framebuffer = ( _currentRenderTarget !== null ) ? properties.get( _currentRenderTarget ).__webglFramebuffer : null; - state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); - - } - - } - - }; - - this.copyFramebufferToTexture = function ( position, texture, level = 0 ) { - - const levelScale = Math.pow( 2, - level ); - const width = Math.floor( texture.image.width * levelScale ); - const height = Math.floor( texture.image.height * levelScale ); - - textures.setTexture2D( texture, 0 ); - - _gl.copyTexSubImage2D( _gl.TEXTURE_2D, level, 0, 0, position.x, position.y, width, height ); - - state.unbindTexture(); - - }; - - this.copyTextureToTexture = function ( position, srcTexture, dstTexture, level = 0 ) { - - const width = srcTexture.image.width; - const height = srcTexture.image.height; - const glFormat = utils.convert( dstTexture.format ); - const glType = utils.convert( dstTexture.type ); - - textures.setTexture2D( dstTexture, 0 ); - - // As another texture upload may have changed pixelStorei - // parameters, make sure they are correct for the dstTexture - _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, dstTexture.flipY ); - _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, dstTexture.premultiplyAlpha ); - _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, dstTexture.unpackAlignment ); - - if ( srcTexture.isDataTexture ) { - - _gl.texSubImage2D( _gl.TEXTURE_2D, level, position.x, position.y, width, height, glFormat, glType, srcTexture.image.data ); - - } else { - - if ( srcTexture.isCompressedTexture ) { - - _gl.compressedTexSubImage2D( _gl.TEXTURE_2D, level, position.x, position.y, srcTexture.mipmaps[ 0 ].width, srcTexture.mipmaps[ 0 ].height, glFormat, srcTexture.mipmaps[ 0 ].data ); - - } else { - - _gl.texSubImage2D( _gl.TEXTURE_2D, level, position.x, position.y, glFormat, glType, srcTexture.image ); - - } - - } - - // Generate mipmaps only when copying level 0 - if ( level === 0 && dstTexture.generateMipmaps ) _gl.generateMipmap( _gl.TEXTURE_2D ); - - state.unbindTexture(); - - }; - - this.copyTextureToTexture3D = function ( sourceBox, position, srcTexture, dstTexture, level = 0 ) { - - if ( _this.isWebGL1Renderer ) { - - console.warn( 'THREE.WebGLRenderer.copyTextureToTexture3D: can only be used with WebGL2.' ); - return; - - } - - const width = sourceBox.max.x - sourceBox.min.x + 1; - const height = sourceBox.max.y - sourceBox.min.y + 1; - const depth = sourceBox.max.z - sourceBox.min.z + 1; - const glFormat = utils.convert( dstTexture.format ); - const glType = utils.convert( dstTexture.type ); - let glTarget; - - if ( dstTexture.isData3DTexture ) { - - textures.setTexture3D( dstTexture, 0 ); - glTarget = _gl.TEXTURE_3D; - - } else if ( dstTexture.isDataArrayTexture ) { - - textures.setTexture2DArray( dstTexture, 0 ); - glTarget = _gl.TEXTURE_2D_ARRAY; - - } else { - - console.warn( 'THREE.WebGLRenderer.copyTextureToTexture3D: only supports THREE.DataTexture3D and THREE.DataTexture2DArray.' ); - return; - - } - - _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, dstTexture.flipY ); - _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, dstTexture.premultiplyAlpha ); - _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, dstTexture.unpackAlignment ); - - const unpackRowLen = _gl.getParameter( _gl.UNPACK_ROW_LENGTH ); - const unpackImageHeight = _gl.getParameter( _gl.UNPACK_IMAGE_HEIGHT ); - const unpackSkipPixels = _gl.getParameter( _gl.UNPACK_SKIP_PIXELS ); - const unpackSkipRows = _gl.getParameter( _gl.UNPACK_SKIP_ROWS ); - const unpackSkipImages = _gl.getParameter( _gl.UNPACK_SKIP_IMAGES ); - - const image = srcTexture.isCompressedTexture ? srcTexture.mipmaps[ 0 ] : srcTexture.image; - - _gl.pixelStorei( _gl.UNPACK_ROW_LENGTH, image.width ); - _gl.pixelStorei( _gl.UNPACK_IMAGE_HEIGHT, image.height ); - _gl.pixelStorei( _gl.UNPACK_SKIP_PIXELS, sourceBox.min.x ); - _gl.pixelStorei( _gl.UNPACK_SKIP_ROWS, sourceBox.min.y ); - _gl.pixelStorei( _gl.UNPACK_SKIP_IMAGES, sourceBox.min.z ); - - if ( srcTexture.isDataTexture || srcTexture.isData3DTexture ) { - - _gl.texSubImage3D( glTarget, level, position.x, position.y, position.z, width, height, depth, glFormat, glType, image.data ); - - } else { - - if ( srcTexture.isCompressedArrayTexture ) { - - console.warn( 'THREE.WebGLRenderer.copyTextureToTexture3D: untested support for compressed srcTexture.' ); - _gl.compressedTexSubImage3D( glTarget, level, position.x, position.y, position.z, width, height, depth, glFormat, image.data ); - - } else { - - _gl.texSubImage3D( glTarget, level, position.x, position.y, position.z, width, height, depth, glFormat, glType, image ); - - } - - } - - _gl.pixelStorei( _gl.UNPACK_ROW_LENGTH, unpackRowLen ); - _gl.pixelStorei( _gl.UNPACK_IMAGE_HEIGHT, unpackImageHeight ); - _gl.pixelStorei( _gl.UNPACK_SKIP_PIXELS, unpackSkipPixels ); - _gl.pixelStorei( _gl.UNPACK_SKIP_ROWS, unpackSkipRows ); - _gl.pixelStorei( _gl.UNPACK_SKIP_IMAGES, unpackSkipImages ); - - // Generate mipmaps only when copying level 0 - if ( level === 0 && dstTexture.generateMipmaps ) _gl.generateMipmap( glTarget ); - - state.unbindTexture(); - - }; - - this.initTexture = function ( texture ) { - - if ( texture.isCubeTexture ) { - - textures.setTextureCube( texture, 0 ); - - } else if ( texture.isData3DTexture ) { - - textures.setTexture3D( texture, 0 ); - - } else if ( texture.isDataArrayTexture || texture.isCompressedArrayTexture ) { - - textures.setTexture2DArray( texture, 0 ); - - } else { - - textures.setTexture2D( texture, 0 ); - - } - - state.unbindTexture(); - - }; - - this.resetState = function () { - - _currentActiveCubeFace = 0; - _currentActiveMipmapLevel = 0; - _currentRenderTarget = null; - - state.reset(); - bindingStates.reset(); - - }; - - if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) { - - __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'observe', { detail: this } ) ); - - } - - } - - get physicallyCorrectLights() { // @deprecated, r150 - - console.warn( 'THREE.WebGLRenderer: the property .physicallyCorrectLights has been removed. Set renderer.useLegacyLights instead.' ); - return ! this.useLegacyLights; - - } - - set physicallyCorrectLights( value ) { // @deprecated, r150 - - console.warn( 'THREE.WebGLRenderer: the property .physicallyCorrectLights has been removed. Set renderer.useLegacyLights instead.' ); - this.useLegacyLights = ! value; - - } - - get outputEncoding() { // @deprecated, r152 - - console.warn( 'THREE.WebGLRenderer: Property .outputEncoding has been removed. Use .outputColorSpace instead.' ); - return this.outputColorSpace === SRGBColorSpace ? sRGBEncoding : LinearEncoding; - - } - - set outputEncoding( encoding ) { // @deprecated, r152 - - console.warn( 'THREE.WebGLRenderer: Property .outputEncoding has been removed. Use .outputColorSpace instead.' ); - this.outputColorSpace = encoding === sRGBEncoding ? SRGBColorSpace : LinearSRGBColorSpace; - - } - -} - -class WebGL1Renderer extends WebGLRenderer {} - -WebGL1Renderer.prototype.isWebGL1Renderer = true; - -class FogExp2 { - - constructor( color, density = 0.00025 ) { - - this.isFogExp2 = true; - - this.name = ''; - - this.color = new Color( color ); - this.density = density; - - } - - clone() { - - return new FogExp2( this.color, this.density ); - - } - - toJSON( /* meta */ ) { - - return { - type: 'FogExp2', - color: this.color.getHex(), - density: this.density - }; - - } - -} - -class Fog { - - constructor( color, near = 1, far = 1000 ) { - - this.isFog = true; - - this.name = ''; - - this.color = new Color( color ); - - this.near = near; - this.far = far; - - } - - clone() { - - return new Fog( this.color, this.near, this.far ); - - } - - toJSON( /* meta */ ) { - - return { - type: 'Fog', - color: this.color.getHex(), - near: this.near, - far: this.far - }; - - } - -} - -class Scene extends Object3D { - - constructor() { - - super(); - - this.isScene = true; - - this.type = 'Scene'; - - this.background = null; - this.environment = null; - this.fog = null; - - this.backgroundBlurriness = 0; - this.backgroundIntensity = 1; - - this.overrideMaterial = null; - - if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) { - - __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'observe', { detail: this } ) ); - - } - - } - - copy( source, recursive ) { - - super.copy( source, recursive ); - - if ( source.background !== null ) this.background = source.background.clone(); - if ( source.environment !== null ) this.environment = source.environment.clone(); - if ( source.fog !== null ) this.fog = source.fog.clone(); - - this.backgroundBlurriness = source.backgroundBlurriness; - this.backgroundIntensity = source.backgroundIntensity; - - if ( source.overrideMaterial !== null ) this.overrideMaterial = source.overrideMaterial.clone(); - - this.matrixAutoUpdate = source.matrixAutoUpdate; - - return this; - - } - - toJSON( meta ) { - - const data = super.toJSON( meta ); - - if ( this.fog !== null ) data.object.fog = this.fog.toJSON(); - if ( this.backgroundBlurriness > 0 ) data.object.backgroundBlurriness = this.backgroundBlurriness; - if ( this.backgroundIntensity !== 1 ) data.object.backgroundIntensity = this.backgroundIntensity; - - return data; - - } - - get autoUpdate() { // @deprecated, r144 - - console.warn( 'THREE.Scene: autoUpdate was renamed to matrixWorldAutoUpdate in r144.' ); - return this.matrixWorldAutoUpdate; - - } - - set autoUpdate( value ) { // @deprecated, r144 - - console.warn( 'THREE.Scene: autoUpdate was renamed to matrixWorldAutoUpdate in r144.' ); - this.matrixWorldAutoUpdate = value; - - } - -} - -class InterleavedBuffer { - - constructor( array, stride ) { - - this.isInterleavedBuffer = true; - - this.array = array; - this.stride = stride; - this.count = array !== undefined ? array.length / stride : 0; - - this.usage = StaticDrawUsage; - this.updateRange = { offset: 0, count: - 1 }; - - this.version = 0; - - this.uuid = generateUUID(); - - } - - onUploadCallback() {} - - set needsUpdate( value ) { - - if ( value === true ) this.version ++; - - } - - setUsage( value ) { - - this.usage = value; - - return this; - - } - - copy( source ) { - - this.array = new source.array.constructor( source.array ); - this.count = source.count; - this.stride = source.stride; - this.usage = source.usage; - - return this; - - } - - copyAt( index1, attribute, index2 ) { - - index1 *= this.stride; - index2 *= attribute.stride; - - for ( let i = 0, l = this.stride; i < l; i ++ ) { - - this.array[ index1 + i ] = attribute.array[ index2 + i ]; - - } - - return this; - - } - - set( value, offset = 0 ) { - - this.array.set( value, offset ); - - return this; - - } - - clone( data ) { - - if ( data.arrayBuffers === undefined ) { - - data.arrayBuffers = {}; - - } - - if ( this.array.buffer._uuid === undefined ) { - - this.array.buffer._uuid = generateUUID(); - - } - - if ( data.arrayBuffers[ this.array.buffer._uuid ] === undefined ) { - - data.arrayBuffers[ this.array.buffer._uuid ] = this.array.slice( 0 ).buffer; - - } - - const array = new this.array.constructor( data.arrayBuffers[ this.array.buffer._uuid ] ); - - const ib = new this.constructor( array, this.stride ); - ib.setUsage( this.usage ); - - return ib; - - } - - onUpload( callback ) { - - this.onUploadCallback = callback; - - return this; - - } - - toJSON( data ) { - - if ( data.arrayBuffers === undefined ) { - - data.arrayBuffers = {}; - - } - - // generate UUID for array buffer if necessary - - if ( this.array.buffer._uuid === undefined ) { - - this.array.buffer._uuid = generateUUID(); - - } - - if ( data.arrayBuffers[ this.array.buffer._uuid ] === undefined ) { - - data.arrayBuffers[ this.array.buffer._uuid ] = Array.from( new Uint32Array( this.array.buffer ) ); - - } - - // - - return { - uuid: this.uuid, - buffer: this.array.buffer._uuid, - type: this.array.constructor.name, - stride: this.stride - }; - - } - -} - -const _vector$5 = /*@__PURE__*/ new Vector3(); - -class InterleavedBufferAttribute { - - constructor( interleavedBuffer, itemSize, offset, normalized = false ) { - - this.isInterleavedBufferAttribute = true; - - this.name = ''; - - this.data = interleavedBuffer; - this.itemSize = itemSize; - this.offset = offset; - - this.normalized = normalized; - - } - - get count() { - - return this.data.count; - - } - - get array() { - - return this.data.array; - - } - - set needsUpdate( value ) { - - this.data.needsUpdate = value; - - } - - applyMatrix4( m ) { - - for ( let i = 0, l = this.data.count; i < l; i ++ ) { - - _vector$5.fromBufferAttribute( this, i ); - - _vector$5.applyMatrix4( m ); - - this.setXYZ( i, _vector$5.x, _vector$5.y, _vector$5.z ); - - } - - return this; - - } - - applyNormalMatrix( m ) { - - for ( let i = 0, l = this.count; i < l; i ++ ) { - - _vector$5.fromBufferAttribute( this, i ); - - _vector$5.applyNormalMatrix( m ); - - this.setXYZ( i, _vector$5.x, _vector$5.y, _vector$5.z ); - - } - - return this; - - } - - transformDirection( m ) { - - for ( let i = 0, l = this.count; i < l; i ++ ) { - - _vector$5.fromBufferAttribute( this, i ); - - _vector$5.transformDirection( m ); - - this.setXYZ( i, _vector$5.x, _vector$5.y, _vector$5.z ); - - } - - return this; - - } - - setX( index, x ) { - - if ( this.normalized ) x = normalize( x, this.array ); - - this.data.array[ index * this.data.stride + this.offset ] = x; - - return this; - - } - - setY( index, y ) { - - if ( this.normalized ) y = normalize( y, this.array ); - - this.data.array[ index * this.data.stride + this.offset + 1 ] = y; - - return this; - - } - - setZ( index, z ) { - - if ( this.normalized ) z = normalize( z, this.array ); - - this.data.array[ index * this.data.stride + this.offset + 2 ] = z; - - return this; - - } - - setW( index, w ) { - - if ( this.normalized ) w = normalize( w, this.array ); - - this.data.array[ index * this.data.stride + this.offset + 3 ] = w; - - return this; - - } - - getX( index ) { - - let x = this.data.array[ index * this.data.stride + this.offset ]; - - if ( this.normalized ) x = denormalize( x, this.array ); - - return x; - - } - - getY( index ) { - - let y = this.data.array[ index * this.data.stride + this.offset + 1 ]; - - if ( this.normalized ) y = denormalize( y, this.array ); - - return y; - - } - - getZ( index ) { - - let z = this.data.array[ index * this.data.stride + this.offset + 2 ]; - - if ( this.normalized ) z = denormalize( z, this.array ); - - return z; - - } - - getW( index ) { - - let w = this.data.array[ index * this.data.stride + this.offset + 3 ]; - - if ( this.normalized ) w = denormalize( w, this.array ); - - return w; - - } - - setXY( index, x, y ) { - - index = index * this.data.stride + this.offset; - - if ( this.normalized ) { - - x = normalize( x, this.array ); - y = normalize( y, this.array ); - - } - - this.data.array[ index + 0 ] = x; - this.data.array[ index + 1 ] = y; - - return this; - - } - - setXYZ( index, x, y, z ) { - - index = index * this.data.stride + this.offset; - - if ( this.normalized ) { - - x = normalize( x, this.array ); - y = normalize( y, this.array ); - z = normalize( z, this.array ); - - } - - this.data.array[ index + 0 ] = x; - this.data.array[ index + 1 ] = y; - this.data.array[ index + 2 ] = z; - - return this; - - } - - setXYZW( index, x, y, z, w ) { - - index = index * this.data.stride + this.offset; - - if ( this.normalized ) { - - x = normalize( x, this.array ); - y = normalize( y, this.array ); - z = normalize( z, this.array ); - w = normalize( w, this.array ); - - } - - this.data.array[ index + 0 ] = x; - this.data.array[ index + 1 ] = y; - this.data.array[ index + 2 ] = z; - this.data.array[ index + 3 ] = w; - - return this; - - } - - clone( data ) { - - if ( data === undefined ) { - - console.log( 'THREE.InterleavedBufferAttribute.clone(): Cloning an interleaved buffer attribute will de-interleave buffer data.' ); - - const array = []; - - for ( let i = 0; i < this.count; i ++ ) { - - const index = i * this.data.stride + this.offset; - - for ( let j = 0; j < this.itemSize; j ++ ) { - - array.push( this.data.array[ index + j ] ); - - } - - } - - return new BufferAttribute( new this.array.constructor( array ), this.itemSize, this.normalized ); - - } else { - - if ( data.interleavedBuffers === undefined ) { - - data.interleavedBuffers = {}; - - } - - if ( data.interleavedBuffers[ this.data.uuid ] === undefined ) { - - data.interleavedBuffers[ this.data.uuid ] = this.data.clone( data ); - - } - - return new InterleavedBufferAttribute( data.interleavedBuffers[ this.data.uuid ], this.itemSize, this.offset, this.normalized ); - - } - - } - - toJSON( data ) { - - if ( data === undefined ) { - - console.log( 'THREE.InterleavedBufferAttribute.toJSON(): Serializing an interleaved buffer attribute will de-interleave buffer data.' ); - - const array = []; - - for ( let i = 0; i < this.count; i ++ ) { - - const index = i * this.data.stride + this.offset; - - for ( let j = 0; j < this.itemSize; j ++ ) { - - array.push( this.data.array[ index + j ] ); - - } - - } - - // de-interleave data and save it as an ordinary buffer attribute for now - - return { - itemSize: this.itemSize, - type: this.array.constructor.name, - array: array, - normalized: this.normalized - }; - - } else { - - // save as true interleaved attribute - - if ( data.interleavedBuffers === undefined ) { - - data.interleavedBuffers = {}; - - } - - if ( data.interleavedBuffers[ this.data.uuid ] === undefined ) { - - data.interleavedBuffers[ this.data.uuid ] = this.data.toJSON( data ); - - } - - return { - isInterleavedBufferAttribute: true, - itemSize: this.itemSize, - data: this.data.uuid, - offset: this.offset, - normalized: this.normalized - }; - - } - - } - -} - -class SpriteMaterial extends Material { - - constructor( parameters ) { - - super(); - - this.isSpriteMaterial = true; - - this.type = 'SpriteMaterial'; - - this.color = new Color( 0xffffff ); - - this.map = null; - - this.alphaMap = null; - - this.rotation = 0; - - this.sizeAttenuation = true; - - this.transparent = true; - - this.fog = true; - - this.setValues( parameters ); - - } - - copy( source ) { - - super.copy( source ); - - this.color.copy( source.color ); - - this.map = source.map; - - this.alphaMap = source.alphaMap; - - this.rotation = source.rotation; - - this.sizeAttenuation = source.sizeAttenuation; - - this.fog = source.fog; - - return this; - - } - -} - -let _geometry; - -const _intersectPoint = /*@__PURE__*/ new Vector3(); -const _worldScale = /*@__PURE__*/ new Vector3(); -const _mvPosition = /*@__PURE__*/ new Vector3(); - -const _alignedPosition = /*@__PURE__*/ new Vector2(); -const _rotatedPosition = /*@__PURE__*/ new Vector2(); -const _viewWorldMatrix = /*@__PURE__*/ new Matrix4(); - -const _vA = /*@__PURE__*/ new Vector3(); -const _vB = /*@__PURE__*/ new Vector3(); -const _vC = /*@__PURE__*/ new Vector3(); - -const _uvA = /*@__PURE__*/ new Vector2(); -const _uvB = /*@__PURE__*/ new Vector2(); -const _uvC = /*@__PURE__*/ new Vector2(); - -class Sprite extends Object3D { - - constructor( material ) { - - super(); - - this.isSprite = true; - - this.type = 'Sprite'; - - if ( _geometry === undefined ) { - - _geometry = new BufferGeometry(); - - const float32Array = new Float32Array( [ - - 0.5, - 0.5, 0, 0, 0, - 0.5, - 0.5, 0, 1, 0, - 0.5, 0.5, 0, 1, 1, - - 0.5, 0.5, 0, 0, 1 - ] ); - - const interleavedBuffer = new InterleavedBuffer( float32Array, 5 ); - - _geometry.setIndex( [ 0, 1, 2, 0, 2, 3 ] ); - _geometry.setAttribute( 'position', new InterleavedBufferAttribute( interleavedBuffer, 3, 0, false ) ); - _geometry.setAttribute( 'uv', new InterleavedBufferAttribute( interleavedBuffer, 2, 3, false ) ); - - } - - this.geometry = _geometry; - this.material = ( material !== undefined ) ? material : new SpriteMaterial(); - - this.center = new Vector2( 0.5, 0.5 ); - - } - - raycast( raycaster, intersects ) { - - if ( raycaster.camera === null ) { - - console.error( 'THREE.Sprite: "Raycaster.camera" needs to be set in order to raycast against sprites.' ); - - } - - _worldScale.setFromMatrixScale( this.matrixWorld ); - - _viewWorldMatrix.copy( raycaster.camera.matrixWorld ); - this.modelViewMatrix.multiplyMatrices( raycaster.camera.matrixWorldInverse, this.matrixWorld ); - - _mvPosition.setFromMatrixPosition( this.modelViewMatrix ); - - if ( raycaster.camera.isPerspectiveCamera && this.material.sizeAttenuation === false ) { - - _worldScale.multiplyScalar( - _mvPosition.z ); - - } - - const rotation = this.material.rotation; - let sin, cos; - - if ( rotation !== 0 ) { - - cos = Math.cos( rotation ); - sin = Math.sin( rotation ); - - } - - const center = this.center; - - transformVertex( _vA.set( - 0.5, - 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos ); - transformVertex( _vB.set( 0.5, - 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos ); - transformVertex( _vC.set( 0.5, 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos ); - - _uvA.set( 0, 0 ); - _uvB.set( 1, 0 ); - _uvC.set( 1, 1 ); - - // check first triangle - let intersect = raycaster.ray.intersectTriangle( _vA, _vB, _vC, false, _intersectPoint ); - - if ( intersect === null ) { - - // check second triangle - transformVertex( _vB.set( - 0.5, 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos ); - _uvB.set( 0, 1 ); - - intersect = raycaster.ray.intersectTriangle( _vA, _vC, _vB, false, _intersectPoint ); - if ( intersect === null ) { - - return; - - } - - } - - const distance = raycaster.ray.origin.distanceTo( _intersectPoint ); - - if ( distance < raycaster.near || distance > raycaster.far ) return; - - intersects.push( { - - distance: distance, - point: _intersectPoint.clone(), - uv: Triangle.getInterpolation( _intersectPoint, _vA, _vB, _vC, _uvA, _uvB, _uvC, new Vector2() ), - face: null, - object: this - - } ); - - } - - copy( source, recursive ) { - - super.copy( source, recursive ); - - if ( source.center !== undefined ) this.center.copy( source.center ); - - this.material = source.material; - - return this; - - } - -} - -function transformVertex( vertexPosition, mvPosition, center, scale, sin, cos ) { - - // compute position in camera space - _alignedPosition.subVectors( vertexPosition, center ).addScalar( 0.5 ).multiply( scale ); - - // to check if rotation is not zero - if ( sin !== undefined ) { - - _rotatedPosition.x = ( cos * _alignedPosition.x ) - ( sin * _alignedPosition.y ); - _rotatedPosition.y = ( sin * _alignedPosition.x ) + ( cos * _alignedPosition.y ); - - } else { - - _rotatedPosition.copy( _alignedPosition ); - - } - - - vertexPosition.copy( mvPosition ); - vertexPosition.x += _rotatedPosition.x; - vertexPosition.y += _rotatedPosition.y; - - // transform to world space - vertexPosition.applyMatrix4( _viewWorldMatrix ); - -} - -const _v1$2 = /*@__PURE__*/ new Vector3(); -const _v2$1 = /*@__PURE__*/ new Vector3(); - -class LOD extends Object3D { - - constructor() { - - super(); - - this._currentLevel = 0; - - this.type = 'LOD'; - - Object.defineProperties( this, { - levels: { - enumerable: true, - value: [] - }, - isLOD: { - value: true, - } - } ); - - this.autoUpdate = true; - - } - - copy( source ) { - - super.copy( source, false ); - - const levels = source.levels; - - for ( let i = 0, l = levels.length; i < l; i ++ ) { - - const level = levels[ i ]; - - this.addLevel( level.object.clone(), level.distance, level.hysteresis ); - - } - - this.autoUpdate = source.autoUpdate; - - return this; - - } - - addLevel( object, distance = 0, hysteresis = 0 ) { - - distance = Math.abs( distance ); - - const levels = this.levels; - - let l; - - for ( l = 0; l < levels.length; l ++ ) { - - if ( distance < levels[ l ].distance ) { - - break; - - } - - } - - levels.splice( l, 0, { distance: distance, hysteresis: hysteresis, object: object } ); - - this.add( object ); - - return this; - - } - - getCurrentLevel() { - - return this._currentLevel; - - } - - - - getObjectForDistance( distance ) { - - const levels = this.levels; - - if ( levels.length > 0 ) { - - let i, l; - - for ( i = 1, l = levels.length; i < l; i ++ ) { - - let levelDistance = levels[ i ].distance; - - if ( levels[ i ].object.visible ) { - - levelDistance -= levelDistance * levels[ i ].hysteresis; - - } - - if ( distance < levelDistance ) { - - break; - - } - - } - - return levels[ i - 1 ].object; - - } - - return null; - - } - - raycast( raycaster, intersects ) { - - const levels = this.levels; - - if ( levels.length > 0 ) { - - _v1$2.setFromMatrixPosition( this.matrixWorld ); - - const distance = raycaster.ray.origin.distanceTo( _v1$2 ); - - this.getObjectForDistance( distance ).raycast( raycaster, intersects ); - - } - - } - - update( camera ) { - - const levels = this.levels; - - if ( levels.length > 1 ) { - - _v1$2.setFromMatrixPosition( camera.matrixWorld ); - _v2$1.setFromMatrixPosition( this.matrixWorld ); - - const distance = _v1$2.distanceTo( _v2$1 ) / camera.zoom; - - levels[ 0 ].object.visible = true; - - let i, l; - - for ( i = 1, l = levels.length; i < l; i ++ ) { - - let levelDistance = levels[ i ].distance; - - if ( levels[ i ].object.visible ) { - - levelDistance -= levelDistance * levels[ i ].hysteresis; - - } - - if ( distance >= levelDistance ) { - - levels[ i - 1 ].object.visible = false; - levels[ i ].object.visible = true; - - } else { - - break; - - } - - } - - this._currentLevel = i - 1; - - for ( ; i < l; i ++ ) { - - levels[ i ].object.visible = false; - - } - - } - - } - - toJSON( meta ) { - - const data = super.toJSON( meta ); - - if ( this.autoUpdate === false ) data.object.autoUpdate = false; - - data.object.levels = []; - - const levels = this.levels; - - for ( let i = 0, l = levels.length; i < l; i ++ ) { - - const level = levels[ i ]; - - data.object.levels.push( { - object: level.object.uuid, - distance: level.distance, - hysteresis: level.hysteresis - } ); - - } - - return data; - - } - -} - -const _basePosition = /*@__PURE__*/ new Vector3(); - -const _skinIndex = /*@__PURE__*/ new Vector4(); -const _skinWeight = /*@__PURE__*/ new Vector4(); - -const _vector3 = /*@__PURE__*/ new Vector3(); -const _matrix4 = /*@__PURE__*/ new Matrix4(); -const _vertex = /*@__PURE__*/ new Vector3(); - -const _sphere$3 = /*@__PURE__*/ new Sphere(); -const _inverseMatrix$2 = /*@__PURE__*/ new Matrix4(); -const _ray$2 = /*@__PURE__*/ new Ray(); - -class SkinnedMesh extends Mesh { - - constructor( geometry, material ) { - - super( geometry, material ); - - this.isSkinnedMesh = true; - - this.type = 'SkinnedMesh'; - - this.bindMode = 'attached'; - this.bindMatrix = new Matrix4(); - this.bindMatrixInverse = new Matrix4(); - - this.boundingBox = null; - this.boundingSphere = null; - - } - - computeBoundingBox() { - - const geometry = this.geometry; - - if ( this.boundingBox === null ) { - - this.boundingBox = new Box3(); - - } - - this.boundingBox.makeEmpty(); - - const positionAttribute = geometry.getAttribute( 'position' ); - - for ( let i = 0; i < positionAttribute.count; i ++ ) { - - _vertex.fromBufferAttribute( positionAttribute, i ); - this.applyBoneTransform( i, _vertex ); - this.boundingBox.expandByPoint( _vertex ); - - } - - } - - computeBoundingSphere() { - - const geometry = this.geometry; - - if ( this.boundingSphere === null ) { - - this.boundingSphere = new Sphere(); - - } - - this.boundingSphere.makeEmpty(); - - const positionAttribute = geometry.getAttribute( 'position' ); - - for ( let i = 0; i < positionAttribute.count; i ++ ) { - - _vertex.fromBufferAttribute( positionAttribute, i ); - this.applyBoneTransform( i, _vertex ); - this.boundingSphere.expandByPoint( _vertex ); - - } - - } - - copy( source, recursive ) { - - super.copy( source, recursive ); - - this.bindMode = source.bindMode; - this.bindMatrix.copy( source.bindMatrix ); - this.bindMatrixInverse.copy( source.bindMatrixInverse ); - - this.skeleton = source.skeleton; - - if ( source.boundingBox !== null ) this.boundingBox = source.boundingBox.clone(); - if ( source.boundingSphere !== null ) this.boundingSphere = source.boundingSphere.clone(); - - return this; - - } - - raycast( raycaster, intersects ) { - - const material = this.material; - const matrixWorld = this.matrixWorld; - - if ( material === undefined ) return; - - // test with bounding sphere in world space - - if ( this.boundingSphere === null ) this.computeBoundingSphere(); - - _sphere$3.copy( this.boundingSphere ); - _sphere$3.applyMatrix4( matrixWorld ); - - if ( raycaster.ray.intersectsSphere( _sphere$3 ) === false ) return; - - // convert ray to local space of skinned mesh - - _inverseMatrix$2.copy( matrixWorld ).invert(); - _ray$2.copy( raycaster.ray ).applyMatrix4( _inverseMatrix$2 ); - - // test with bounding box in local space - - if ( this.boundingBox !== null ) { - - if ( _ray$2.intersectsBox( this.boundingBox ) === false ) return; - - } - - // test for intersections with geometry - - this._computeIntersections( raycaster, intersects, _ray$2 ); - - } - - getVertexPosition( index, target ) { - - super.getVertexPosition( index, target ); - - this.applyBoneTransform( index, target ); - - return target; - - } - - bind( skeleton, bindMatrix ) { - - this.skeleton = skeleton; - - if ( bindMatrix === undefined ) { - - this.updateMatrixWorld( true ); - - this.skeleton.calculateInverses(); - - bindMatrix = this.matrixWorld; - - } - - this.bindMatrix.copy( bindMatrix ); - this.bindMatrixInverse.copy( bindMatrix ).invert(); - - } - - pose() { - - this.skeleton.pose(); - - } - - normalizeSkinWeights() { - - const vector = new Vector4(); - - const skinWeight = this.geometry.attributes.skinWeight; - - for ( let i = 0, l = skinWeight.count; i < l; i ++ ) { - - vector.fromBufferAttribute( skinWeight, i ); - - const scale = 1.0 / vector.manhattanLength(); - - if ( scale !== Infinity ) { - - vector.multiplyScalar( scale ); - - } else { - - vector.set( 1, 0, 0, 0 ); // do something reasonable - - } - - skinWeight.setXYZW( i, vector.x, vector.y, vector.z, vector.w ); - - } - - } - - updateMatrixWorld( force ) { - - super.updateMatrixWorld( force ); - - if ( this.bindMode === 'attached' ) { - - this.bindMatrixInverse.copy( this.matrixWorld ).invert(); - - } else if ( this.bindMode === 'detached' ) { - - this.bindMatrixInverse.copy( this.bindMatrix ).invert(); - - } else { - - console.warn( 'THREE.SkinnedMesh: Unrecognized bindMode: ' + this.bindMode ); - - } - - } - - applyBoneTransform( index, vector ) { - - const skeleton = this.skeleton; - const geometry = this.geometry; - - _skinIndex.fromBufferAttribute( geometry.attributes.skinIndex, index ); - _skinWeight.fromBufferAttribute( geometry.attributes.skinWeight, index ); - - _basePosition.copy( vector ).applyMatrix4( this.bindMatrix ); - - vector.set( 0, 0, 0 ); - - for ( let i = 0; i < 4; i ++ ) { - - const weight = _skinWeight.getComponent( i ); - - if ( weight !== 0 ) { - - const boneIndex = _skinIndex.getComponent( i ); - - _matrix4.multiplyMatrices( skeleton.bones[ boneIndex ].matrixWorld, skeleton.boneInverses[ boneIndex ] ); - - vector.addScaledVector( _vector3.copy( _basePosition ).applyMatrix4( _matrix4 ), weight ); - - } - - } - - return vector.applyMatrix4( this.bindMatrixInverse ); - - } - - boneTransform( index, vector ) { // @deprecated, r151 - - console.warn( 'THREE.SkinnedMesh: .boneTransform() was renamed to .applyBoneTransform() in r151.' ); - return this.applyBoneTransform( index, vector ); - - } - - -} - -class Bone extends Object3D { - - constructor() { - - super(); - - this.isBone = true; - - this.type = 'Bone'; - - } - -} - -class DataTexture extends Texture { - - constructor( data = null, width = 1, height = 1, format, type, mapping, wrapS, wrapT, magFilter = NearestFilter, minFilter = NearestFilter, anisotropy, colorSpace ) { - - super( null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, colorSpace ); - - this.isDataTexture = true; - - this.image = { data: data, width: width, height: height }; - - this.generateMipmaps = false; - this.flipY = false; - this.unpackAlignment = 1; - - } - -} - -const _offsetMatrix = /*@__PURE__*/ new Matrix4(); -const _identityMatrix = /*@__PURE__*/ new Matrix4(); - -class Skeleton { - - constructor( bones = [], boneInverses = [] ) { - - this.uuid = generateUUID(); - - this.bones = bones.slice( 0 ); - this.boneInverses = boneInverses; - this.boneMatrices = null; - - this.boneTexture = null; - this.boneTextureSize = 0; - - this.frame = - 1; - - this.init(); - - } - - init() { - - const bones = this.bones; - const boneInverses = this.boneInverses; - - this.boneMatrices = new Float32Array( bones.length * 16 ); - - // calculate inverse bone matrices if necessary - - if ( boneInverses.length === 0 ) { - - this.calculateInverses(); - - } else { - - // handle special case - - if ( bones.length !== boneInverses.length ) { - - console.warn( 'THREE.Skeleton: Number of inverse bone matrices does not match amount of bones.' ); - - this.boneInverses = []; - - for ( let i = 0, il = this.bones.length; i < il; i ++ ) { - - this.boneInverses.push( new Matrix4() ); - - } - - } - - } - - } - - calculateInverses() { - - this.boneInverses.length = 0; - - for ( let i = 0, il = this.bones.length; i < il; i ++ ) { - - const inverse = new Matrix4(); - - if ( this.bones[ i ] ) { - - inverse.copy( this.bones[ i ].matrixWorld ).invert(); - - } - - this.boneInverses.push( inverse ); - - } - - } - - pose() { - - // recover the bind-time world matrices - - for ( let i = 0, il = this.bones.length; i < il; i ++ ) { - - const bone = this.bones[ i ]; - - if ( bone ) { - - bone.matrixWorld.copy( this.boneInverses[ i ] ).invert(); - - } - - } - - // compute the local matrices, positions, rotations and scales - - for ( let i = 0, il = this.bones.length; i < il; i ++ ) { - - const bone = this.bones[ i ]; - - if ( bone ) { - - if ( bone.parent && bone.parent.isBone ) { - - bone.matrix.copy( bone.parent.matrixWorld ).invert(); - bone.matrix.multiply( bone.matrixWorld ); - - } else { - - bone.matrix.copy( bone.matrixWorld ); - - } - - bone.matrix.decompose( bone.position, bone.quaternion, bone.scale ); - - } - - } - - } - - update() { - - const bones = this.bones; - const boneInverses = this.boneInverses; - const boneMatrices = this.boneMatrices; - const boneTexture = this.boneTexture; - - // flatten bone matrices to array - - for ( let i = 0, il = bones.length; i < il; i ++ ) { - - // compute the offset between the current and the original transform - - const matrix = bones[ i ] ? bones[ i ].matrixWorld : _identityMatrix; - - _offsetMatrix.multiplyMatrices( matrix, boneInverses[ i ] ); - _offsetMatrix.toArray( boneMatrices, i * 16 ); - - } - - if ( boneTexture !== null ) { - - boneTexture.needsUpdate = true; - - } - - } - - clone() { - - return new Skeleton( this.bones, this.boneInverses ); - - } - - computeBoneTexture() { - - // layout (1 matrix = 4 pixels) - // RGBA RGBA RGBA RGBA (=> column1, column2, column3, column4) - // with 8x8 pixel texture max 16 bones * 4 pixels = (8 * 8) - // 16x16 pixel texture max 64 bones * 4 pixels = (16 * 16) - // 32x32 pixel texture max 256 bones * 4 pixels = (32 * 32) - // 64x64 pixel texture max 1024 bones * 4 pixels = (64 * 64) - - let size = Math.sqrt( this.bones.length * 4 ); // 4 pixels needed for 1 matrix - size = ceilPowerOfTwo( size ); - size = Math.max( size, 4 ); - - const boneMatrices = new Float32Array( size * size * 4 ); // 4 floats per RGBA pixel - boneMatrices.set( this.boneMatrices ); // copy current values - - const boneTexture = new DataTexture( boneMatrices, size, size, RGBAFormat, FloatType ); - boneTexture.needsUpdate = true; - - this.boneMatrices = boneMatrices; - this.boneTexture = boneTexture; - this.boneTextureSize = size; - - return this; - - } - - getBoneByName( name ) { - - for ( let i = 0, il = this.bones.length; i < il; i ++ ) { - - const bone = this.bones[ i ]; - - if ( bone.name === name ) { - - return bone; - - } - - } - - return undefined; - - } - - dispose( ) { - - if ( this.boneTexture !== null ) { - - this.boneTexture.dispose(); - - this.boneTexture = null; - - } - - } - - fromJSON( json, bones ) { - - this.uuid = json.uuid; - - for ( let i = 0, l = json.bones.length; i < l; i ++ ) { - - const uuid = json.bones[ i ]; - let bone = bones[ uuid ]; - - if ( bone === undefined ) { - - console.warn( 'THREE.Skeleton: No bone found with UUID:', uuid ); - bone = new Bone(); - - } - - this.bones.push( bone ); - this.boneInverses.push( new Matrix4().fromArray( json.boneInverses[ i ] ) ); - - } - - this.init(); - - return this; - - } - - toJSON() { - - const data = { - metadata: { - version: 4.6, - type: 'Skeleton', - generator: 'Skeleton.toJSON' - }, - bones: [], - boneInverses: [] - }; - - data.uuid = this.uuid; - - const bones = this.bones; - const boneInverses = this.boneInverses; - - for ( let i = 0, l = bones.length; i < l; i ++ ) { - - const bone = bones[ i ]; - data.bones.push( bone.uuid ); - - const boneInverse = boneInverses[ i ]; - data.boneInverses.push( boneInverse.toArray() ); - - } - - return data; - - } - -} - -class InstancedBufferAttribute extends BufferAttribute { - - constructor( array, itemSize, normalized, meshPerAttribute = 1 ) { - - super( array, itemSize, normalized ); - - this.isInstancedBufferAttribute = true; - - this.meshPerAttribute = meshPerAttribute; - - } - - copy( source ) { - - super.copy( source ); - - this.meshPerAttribute = source.meshPerAttribute; - - return this; - - } - - toJSON() { - - const data = super.toJSON(); - - data.meshPerAttribute = this.meshPerAttribute; - - data.isInstancedBufferAttribute = true; - - return data; - - } - -} - -const _instanceLocalMatrix = /*@__PURE__*/ new Matrix4(); -const _instanceWorldMatrix = /*@__PURE__*/ new Matrix4(); - -const _instanceIntersects = []; - -const _box3 = /*@__PURE__*/ new Box3(); -const _identity = /*@__PURE__*/ new Matrix4(); -const _mesh = /*@__PURE__*/ new Mesh(); -const _sphere$2 = /*@__PURE__*/ new Sphere(); - -class InstancedMesh extends Mesh { - - constructor( geometry, material, count ) { - - super( geometry, material ); - - this.isInstancedMesh = true; - - this.instanceMatrix = new InstancedBufferAttribute( new Float32Array( count * 16 ), 16 ); - this.instanceColor = null; - - this.count = count; - - this.boundingBox = null; - this.boundingSphere = null; - - for ( let i = 0; i < count; i ++ ) { - - this.setMatrixAt( i, _identity ); - - } - - } - - computeBoundingBox() { - - const geometry = this.geometry; - const count = this.count; - - if ( this.boundingBox === null ) { - - this.boundingBox = new Box3(); - - } - - if ( geometry.boundingBox === null ) { - - geometry.computeBoundingBox(); - - } - - this.boundingBox.makeEmpty(); - - for ( let i = 0; i < count; i ++ ) { - - this.getMatrixAt( i, _instanceLocalMatrix ); - - _box3.copy( geometry.boundingBox ).applyMatrix4( _instanceLocalMatrix ); - - this.boundingBox.union( _box3 ); - - } - - } - - computeBoundingSphere() { - - const geometry = this.geometry; - const count = this.count; - - if ( this.boundingSphere === null ) { - - this.boundingSphere = new Sphere(); - - } - - if ( geometry.boundingSphere === null ) { - - geometry.computeBoundingSphere(); - - } - - this.boundingSphere.makeEmpty(); - - for ( let i = 0; i < count; i ++ ) { - - this.getMatrixAt( i, _instanceLocalMatrix ); - - _sphere$2.copy( geometry.boundingSphere ).applyMatrix4( _instanceLocalMatrix ); - - this.boundingSphere.union( _sphere$2 ); - - } - - } - - copy( source, recursive ) { - - super.copy( source, recursive ); - - this.instanceMatrix.copy( source.instanceMatrix ); - - if ( source.instanceColor !== null ) this.instanceColor = source.instanceColor.clone(); - - this.count = source.count; - - if ( source.boundingBox !== null ) this.boundingBox = source.boundingBox.clone(); - if ( source.boundingSphere !== null ) this.boundingSphere = source.boundingSphere.clone(); - - return this; - - } - - getColorAt( index, color ) { - - color.fromArray( this.instanceColor.array, index * 3 ); - - } - - getMatrixAt( index, matrix ) { - - matrix.fromArray( this.instanceMatrix.array, index * 16 ); - - } - - raycast( raycaster, intersects ) { - - const matrixWorld = this.matrixWorld; - const raycastTimes = this.count; - - _mesh.geometry = this.geometry; - _mesh.material = this.material; - - if ( _mesh.material === undefined ) return; - - // test with bounding sphere first - - if ( this.boundingSphere === null ) this.computeBoundingSphere(); - - _sphere$2.copy( this.boundingSphere ); - _sphere$2.applyMatrix4( matrixWorld ); - - if ( raycaster.ray.intersectsSphere( _sphere$2 ) === false ) return; - - // now test each instance - - for ( let instanceId = 0; instanceId < raycastTimes; instanceId ++ ) { - - // calculate the world matrix for each instance - - this.getMatrixAt( instanceId, _instanceLocalMatrix ); - - _instanceWorldMatrix.multiplyMatrices( matrixWorld, _instanceLocalMatrix ); - - // the mesh represents this single instance - - _mesh.matrixWorld = _instanceWorldMatrix; - - _mesh.raycast( raycaster, _instanceIntersects ); - - // process the result of raycast - - for ( let i = 0, l = _instanceIntersects.length; i < l; i ++ ) { - - const intersect = _instanceIntersects[ i ]; - intersect.instanceId = instanceId; - intersect.object = this; - intersects.push( intersect ); - - } - - _instanceIntersects.length = 0; - - } - - } - - setColorAt( index, color ) { - - if ( this.instanceColor === null ) { - - this.instanceColor = new InstancedBufferAttribute( new Float32Array( this.instanceMatrix.count * 3 ), 3 ); - - } - - color.toArray( this.instanceColor.array, index * 3 ); - - } - - setMatrixAt( index, matrix ) { - - matrix.toArray( this.instanceMatrix.array, index * 16 ); - - } - - updateMorphTargets() { - - } - - dispose() { - - this.dispatchEvent( { type: 'dispose' } ); - - } - -} - -class LineBasicMaterial extends Material { - - constructor( parameters ) { - - super(); - - this.isLineBasicMaterial = true; - - this.type = 'LineBasicMaterial'; - - this.color = new Color( 0xffffff ); - - this.map = null; - - this.linewidth = 1; - this.linecap = 'round'; - this.linejoin = 'round'; - - this.fog = true; - - this.setValues( parameters ); - - } - - - copy( source ) { - - super.copy( source ); - - this.color.copy( source.color ); - - this.map = source.map; - - this.linewidth = source.linewidth; - this.linecap = source.linecap; - this.linejoin = source.linejoin; - - this.fog = source.fog; - - return this; - - } - -} - -const _start$1 = /*@__PURE__*/ new Vector3(); -const _end$1 = /*@__PURE__*/ new Vector3(); -const _inverseMatrix$1 = /*@__PURE__*/ new Matrix4(); -const _ray$1 = /*@__PURE__*/ new Ray(); -const _sphere$1 = /*@__PURE__*/ new Sphere(); - -class Line extends Object3D { - - constructor( geometry = new BufferGeometry(), material = new LineBasicMaterial() ) { - - super(); - - this.isLine = true; - - this.type = 'Line'; - - this.geometry = geometry; - this.material = material; - - this.updateMorphTargets(); - - } - - copy( source, recursive ) { - - super.copy( source, recursive ); - - this.material = source.material; - this.geometry = source.geometry; - - return this; - - } - - computeLineDistances() { - - const geometry = this.geometry; - - // we assume non-indexed geometry - - if ( geometry.index === null ) { - - const positionAttribute = geometry.attributes.position; - const lineDistances = [ 0 ]; - - for ( let i = 1, l = positionAttribute.count; i < l; i ++ ) { - - _start$1.fromBufferAttribute( positionAttribute, i - 1 ); - _end$1.fromBufferAttribute( positionAttribute, i ); - - lineDistances[ i ] = lineDistances[ i - 1 ]; - lineDistances[ i ] += _start$1.distanceTo( _end$1 ); - - } - - geometry.setAttribute( 'lineDistance', new Float32BufferAttribute( lineDistances, 1 ) ); - - } else { - - console.warn( 'THREE.Line.computeLineDistances(): Computation only possible with non-indexed BufferGeometry.' ); - - } - - return this; - - } - - raycast( raycaster, intersects ) { - - const geometry = this.geometry; - const matrixWorld = this.matrixWorld; - const threshold = raycaster.params.Line.threshold; - const drawRange = geometry.drawRange; - - // Checking boundingSphere distance to ray - - if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); - - _sphere$1.copy( geometry.boundingSphere ); - _sphere$1.applyMatrix4( matrixWorld ); - _sphere$1.radius += threshold; - - if ( raycaster.ray.intersectsSphere( _sphere$1 ) === false ) return; - - // - - _inverseMatrix$1.copy( matrixWorld ).invert(); - _ray$1.copy( raycaster.ray ).applyMatrix4( _inverseMatrix$1 ); - - const localThreshold = threshold / ( ( this.scale.x + this.scale.y + this.scale.z ) / 3 ); - const localThresholdSq = localThreshold * localThreshold; - - const vStart = new Vector3(); - const vEnd = new Vector3(); - const interSegment = new Vector3(); - const interRay = new Vector3(); - const step = this.isLineSegments ? 2 : 1; - - const index = geometry.index; - const attributes = geometry.attributes; - const positionAttribute = attributes.position; - - if ( index !== null ) { - - const start = Math.max( 0, drawRange.start ); - const end = Math.min( index.count, ( drawRange.start + drawRange.count ) ); - - for ( let i = start, l = end - 1; i < l; i += step ) { - - const a = index.getX( i ); - const b = index.getX( i + 1 ); - - vStart.fromBufferAttribute( positionAttribute, a ); - vEnd.fromBufferAttribute( positionAttribute, b ); - - const distSq = _ray$1.distanceSqToSegment( vStart, vEnd, interRay, interSegment ); - - if ( distSq > localThresholdSq ) continue; - - interRay.applyMatrix4( this.matrixWorld ); //Move back to world space for distance calculation - - const distance = raycaster.ray.origin.distanceTo( interRay ); - - if ( distance < raycaster.near || distance > raycaster.far ) continue; - - intersects.push( { - - distance: distance, - // What do we want? intersection point on the ray or on the segment?? - // point: raycaster.ray.at( distance ), - point: interSegment.clone().applyMatrix4( this.matrixWorld ), - index: i, - face: null, - faceIndex: null, - object: this - - } ); - - } - - } else { - - const start = Math.max( 0, drawRange.start ); - const end = Math.min( positionAttribute.count, ( drawRange.start + drawRange.count ) ); - - for ( let i = start, l = end - 1; i < l; i += step ) { - - vStart.fromBufferAttribute( positionAttribute, i ); - vEnd.fromBufferAttribute( positionAttribute, i + 1 ); - - const distSq = _ray$1.distanceSqToSegment( vStart, vEnd, interRay, interSegment ); - - if ( distSq > localThresholdSq ) continue; - - interRay.applyMatrix4( this.matrixWorld ); //Move back to world space for distance calculation - - const distance = raycaster.ray.origin.distanceTo( interRay ); - - if ( distance < raycaster.near || distance > raycaster.far ) continue; - - intersects.push( { - - distance: distance, - // What do we want? intersection point on the ray or on the segment?? - // point: raycaster.ray.at( distance ), - point: interSegment.clone().applyMatrix4( this.matrixWorld ), - index: i, - face: null, - faceIndex: null, - object: this - - } ); - - } - - } - - } - - updateMorphTargets() { - - const geometry = this.geometry; - - const morphAttributes = geometry.morphAttributes; - const keys = Object.keys( morphAttributes ); - - if ( keys.length > 0 ) { - - const morphAttribute = morphAttributes[ keys[ 0 ] ]; - - if ( morphAttribute !== undefined ) { - - this.morphTargetInfluences = []; - this.morphTargetDictionary = {}; - - for ( let m = 0, ml = morphAttribute.length; m < ml; m ++ ) { - - const name = morphAttribute[ m ].name || String( m ); - - this.morphTargetInfluences.push( 0 ); - this.morphTargetDictionary[ name ] = m; - - } - - } - - } - - } - -} - -const _start = /*@__PURE__*/ new Vector3(); -const _end = /*@__PURE__*/ new Vector3(); - -class LineSegments extends Line { - - constructor( geometry, material ) { - - super( geometry, material ); - - this.isLineSegments = true; - - this.type = 'LineSegments'; - - } - - computeLineDistances() { - - const geometry = this.geometry; - - // we assume non-indexed geometry - - if ( geometry.index === null ) { - - const positionAttribute = geometry.attributes.position; - const lineDistances = []; - - for ( let i = 0, l = positionAttribute.count; i < l; i += 2 ) { - - _start.fromBufferAttribute( positionAttribute, i ); - _end.fromBufferAttribute( positionAttribute, i + 1 ); - - lineDistances[ i ] = ( i === 0 ) ? 0 : lineDistances[ i - 1 ]; - lineDistances[ i + 1 ] = lineDistances[ i ] + _start.distanceTo( _end ); - - } - - geometry.setAttribute( 'lineDistance', new Float32BufferAttribute( lineDistances, 1 ) ); - - } else { - - console.warn( 'THREE.LineSegments.computeLineDistances(): Computation only possible with non-indexed BufferGeometry.' ); - - } - - return this; - - } - -} - -class LineLoop extends Line { - - constructor( geometry, material ) { - - super( geometry, material ); - - this.isLineLoop = true; - - this.type = 'LineLoop'; - - } - -} - -class PointsMaterial extends Material { - - constructor( parameters ) { - - super(); - - this.isPointsMaterial = true; - - this.type = 'PointsMaterial'; - - this.color = new Color( 0xffffff ); - - this.map = null; - - this.alphaMap = null; - - this.size = 1; - this.sizeAttenuation = true; - - this.fog = true; - - this.setValues( parameters ); - - } - - copy( source ) { - - super.copy( source ); - - this.color.copy( source.color ); - - this.map = source.map; - - this.alphaMap = source.alphaMap; - - this.size = source.size; - this.sizeAttenuation = source.sizeAttenuation; - - this.fog = source.fog; - - return this; - - } - -} - -const _inverseMatrix = /*@__PURE__*/ new Matrix4(); -const _ray = /*@__PURE__*/ new Ray(); -const _sphere = /*@__PURE__*/ new Sphere(); -const _position$2 = /*@__PURE__*/ new Vector3(); - -class Points extends Object3D { - - constructor( geometry = new BufferGeometry(), material = new PointsMaterial() ) { - - super(); - - this.isPoints = true; - - this.type = 'Points'; - - this.geometry = geometry; - this.material = material; - - this.updateMorphTargets(); - - } - - copy( source, recursive ) { - - super.copy( source, recursive ); - - this.material = source.material; - this.geometry = source.geometry; - - return this; - - } - - raycast( raycaster, intersects ) { - - const geometry = this.geometry; - const matrixWorld = this.matrixWorld; - const threshold = raycaster.params.Points.threshold; - const drawRange = geometry.drawRange; - - // Checking boundingSphere distance to ray - - if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); - - _sphere.copy( geometry.boundingSphere ); - _sphere.applyMatrix4( matrixWorld ); - _sphere.radius += threshold; - - if ( raycaster.ray.intersectsSphere( _sphere ) === false ) return; - - // - - _inverseMatrix.copy( matrixWorld ).invert(); - _ray.copy( raycaster.ray ).applyMatrix4( _inverseMatrix ); - - const localThreshold = threshold / ( ( this.scale.x + this.scale.y + this.scale.z ) / 3 ); - const localThresholdSq = localThreshold * localThreshold; - - const index = geometry.index; - const attributes = geometry.attributes; - const positionAttribute = attributes.position; - - if ( index !== null ) { - - const start = Math.max( 0, drawRange.start ); - const end = Math.min( index.count, ( drawRange.start + drawRange.count ) ); - - for ( let i = start, il = end; i < il; i ++ ) { - - const a = index.getX( i ); - - _position$2.fromBufferAttribute( positionAttribute, a ); - - testPoint( _position$2, a, localThresholdSq, matrixWorld, raycaster, intersects, this ); - - } - - } else { - - const start = Math.max( 0, drawRange.start ); - const end = Math.min( positionAttribute.count, ( drawRange.start + drawRange.count ) ); - - for ( let i = start, l = end; i < l; i ++ ) { - - _position$2.fromBufferAttribute( positionAttribute, i ); - - testPoint( _position$2, i, localThresholdSq, matrixWorld, raycaster, intersects, this ); - - } - - } - - } - - updateMorphTargets() { - - const geometry = this.geometry; - - const morphAttributes = geometry.morphAttributes; - const keys = Object.keys( morphAttributes ); - - if ( keys.length > 0 ) { - - const morphAttribute = morphAttributes[ keys[ 0 ] ]; - - if ( morphAttribute !== undefined ) { - - this.morphTargetInfluences = []; - this.morphTargetDictionary = {}; - - for ( let m = 0, ml = morphAttribute.length; m < ml; m ++ ) { - - const name = morphAttribute[ m ].name || String( m ); - - this.morphTargetInfluences.push( 0 ); - this.morphTargetDictionary[ name ] = m; - - } - - } - - } - - } - -} - -function testPoint( point, index, localThresholdSq, matrixWorld, raycaster, intersects, object ) { - - const rayPointDistanceSq = _ray.distanceSqToPoint( point ); - - if ( rayPointDistanceSq < localThresholdSq ) { - - const intersectPoint = new Vector3(); - - _ray.closestPointToPoint( point, intersectPoint ); - intersectPoint.applyMatrix4( matrixWorld ); - - const distance = raycaster.ray.origin.distanceTo( intersectPoint ); - - if ( distance < raycaster.near || distance > raycaster.far ) return; - - intersects.push( { - - distance: distance, - distanceToRay: Math.sqrt( rayPointDistanceSq ), - point: intersectPoint, - index: index, - face: null, - object: object - - } ); - - } - -} - -class VideoTexture extends Texture { - - constructor( video, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) { - - super( video, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); - - this.isVideoTexture = true; - - this.minFilter = minFilter !== undefined ? minFilter : LinearFilter; - this.magFilter = magFilter !== undefined ? magFilter : LinearFilter; - - this.generateMipmaps = false; - - const scope = this; - - function updateVideo() { - - scope.needsUpdate = true; - video.requestVideoFrameCallback( updateVideo ); - - } - - if ( 'requestVideoFrameCallback' in video ) { - - video.requestVideoFrameCallback( updateVideo ); - - } - - } - - clone() { - - return new this.constructor( this.image ).copy( this ); - - } - - update() { - - const video = this.image; - const hasVideoFrameCallback = 'requestVideoFrameCallback' in video; - - if ( hasVideoFrameCallback === false && video.readyState >= video.HAVE_CURRENT_DATA ) { - - this.needsUpdate = true; - - } - - } - -} - -class FramebufferTexture extends Texture { - - constructor( width, height ) { - - super( { width, height } ); - - this.isFramebufferTexture = true; - - this.magFilter = NearestFilter; - this.minFilter = NearestFilter; - - this.generateMipmaps = false; - - this.needsUpdate = true; - - } - -} - -class CompressedTexture extends Texture { - - constructor( mipmaps, width, height, format, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, colorSpace ) { - - super( null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, colorSpace ); - - this.isCompressedTexture = true; - - this.image = { width: width, height: height }; - this.mipmaps = mipmaps; - - // no flipping for cube textures - // (also flipping doesn't work for compressed textures ) - - this.flipY = false; - - // can't generate mipmaps for compressed textures - // mips must be embedded in DDS files - - this.generateMipmaps = false; - - } - -} - -class CompressedArrayTexture extends CompressedTexture { - - constructor( mipmaps, width, height, depth, format, type ) { - - super( mipmaps, width, height, format, type ); - - this.isCompressedArrayTexture = true; - this.image.depth = depth; - this.wrapR = ClampToEdgeWrapping; - - } - -} - -class CanvasTexture extends Texture { - - constructor( canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) { - - super( canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); - - this.isCanvasTexture = true; - - this.needsUpdate = true; - - } - -} - -/** - * Extensible curve object. - * - * Some common of curve methods: - * .getPoint( t, optionalTarget ), .getTangent( t, optionalTarget ) - * .getPointAt( u, optionalTarget ), .getTangentAt( u, optionalTarget ) - * .getPoints(), .getSpacedPoints() - * .getLength() - * .updateArcLengths() - * - * This following curves inherit from THREE.Curve: - * - * -- 2D curves -- - * THREE.ArcCurve - * THREE.CubicBezierCurve - * THREE.EllipseCurve - * THREE.LineCurve - * THREE.QuadraticBezierCurve - * THREE.SplineCurve - * - * -- 3D curves -- - * THREE.CatmullRomCurve3 - * THREE.CubicBezierCurve3 - * THREE.LineCurve3 - * THREE.QuadraticBezierCurve3 - * - * A series of curves can be represented as a THREE.CurvePath. - * - **/ - -class Curve { - - constructor() { - - this.type = 'Curve'; - - this.arcLengthDivisions = 200; - - } - - // Virtual base class method to overwrite and implement in subclasses - // - t [0 .. 1] - - getPoint( /* t, optionalTarget */ ) { - - console.warn( 'THREE.Curve: .getPoint() not implemented.' ); - return null; - - } - - // Get point at relative position in curve according to arc length - // - u [0 .. 1] - - getPointAt( u, optionalTarget ) { - - const t = this.getUtoTmapping( u ); - return this.getPoint( t, optionalTarget ); - - } - - // Get sequence of points using getPoint( t ) - - getPoints( divisions = 5 ) { - - const points = []; - - for ( let d = 0; d <= divisions; d ++ ) { - - points.push( this.getPoint( d / divisions ) ); - - } - - return points; - - } - - // Get sequence of points using getPointAt( u ) - - getSpacedPoints( divisions = 5 ) { - - const points = []; - - for ( let d = 0; d <= divisions; d ++ ) { - - points.push( this.getPointAt( d / divisions ) ); - - } - - return points; - - } - - // Get total curve arc length - - getLength() { - - const lengths = this.getLengths(); - return lengths[ lengths.length - 1 ]; - - } - - // Get list of cumulative segment lengths - - getLengths( divisions = this.arcLengthDivisions ) { - - if ( this.cacheArcLengths && - ( this.cacheArcLengths.length === divisions + 1 ) && - ! this.needsUpdate ) { - - return this.cacheArcLengths; - - } - - this.needsUpdate = false; - - const cache = []; - let current, last = this.getPoint( 0 ); - let sum = 0; - - cache.push( 0 ); - - for ( let p = 1; p <= divisions; p ++ ) { - - current = this.getPoint( p / divisions ); - sum += current.distanceTo( last ); - cache.push( sum ); - last = current; - - } - - this.cacheArcLengths = cache; - - return cache; // { sums: cache, sum: sum }; Sum is in the last element. - - } - - updateArcLengths() { - - this.needsUpdate = true; - this.getLengths(); - - } - - // Given u ( 0 .. 1 ), get a t to find p. This gives you points which are equidistant - - getUtoTmapping( u, distance ) { - - const arcLengths = this.getLengths(); - - let i = 0; - const il = arcLengths.length; - - let targetArcLength; // The targeted u distance value to get - - if ( distance ) { - - targetArcLength = distance; - - } else { - - targetArcLength = u * arcLengths[ il - 1 ]; - - } - - // binary search for the index with largest value smaller than target u distance - - let low = 0, high = il - 1, comparison; - - while ( low <= high ) { - - i = Math.floor( low + ( high - low ) / 2 ); // less likely to overflow, though probably not issue here, JS doesn't really have integers, all numbers are floats - - comparison = arcLengths[ i ] - targetArcLength; - - if ( comparison < 0 ) { - - low = i + 1; - - } else if ( comparison > 0 ) { - - high = i - 1; - - } else { - - high = i; - break; - - // DONE - - } - - } - - i = high; - - if ( arcLengths[ i ] === targetArcLength ) { - - return i / ( il - 1 ); - - } - - // we could get finer grain at lengths, or use simple interpolation between two points - - const lengthBefore = arcLengths[ i ]; - const lengthAfter = arcLengths[ i + 1 ]; - - const segmentLength = lengthAfter - lengthBefore; - - // determine where we are between the 'before' and 'after' points - - const segmentFraction = ( targetArcLength - lengthBefore ) / segmentLength; - - // add that fractional amount to t - - const t = ( i + segmentFraction ) / ( il - 1 ); - - return t; - - } - - // Returns a unit vector tangent at t - // In case any sub curve does not implement its tangent derivation, - // 2 points a small delta apart will be used to find its gradient - // which seems to give a reasonable approximation - - getTangent( t, optionalTarget ) { - - const delta = 0.0001; - let t1 = t - delta; - let t2 = t + delta; - - // Capping in case of danger - - if ( t1 < 0 ) t1 = 0; - if ( t2 > 1 ) t2 = 1; - - const pt1 = this.getPoint( t1 ); - const pt2 = this.getPoint( t2 ); - - const tangent = optionalTarget || ( ( pt1.isVector2 ) ? new Vector2() : new Vector3() ); - - tangent.copy( pt2 ).sub( pt1 ).normalize(); - - return tangent; - - } - - getTangentAt( u, optionalTarget ) { - - const t = this.getUtoTmapping( u ); - return this.getTangent( t, optionalTarget ); - - } - - computeFrenetFrames( segments, closed ) { - - // see http://www.cs.indiana.edu/pub/techreports/TR425.pdf - - const normal = new Vector3(); - - const tangents = []; - const normals = []; - const binormals = []; - - const vec = new Vector3(); - const mat = new Matrix4(); - - // compute the tangent vectors for each segment on the curve - - for ( let i = 0; i <= segments; i ++ ) { - - const u = i / segments; - - tangents[ i ] = this.getTangentAt( u, new Vector3() ); - - } - - // select an initial normal vector perpendicular to the first tangent vector, - // and in the direction of the minimum tangent xyz component - - normals[ 0 ] = new Vector3(); - binormals[ 0 ] = new Vector3(); - let min = Number.MAX_VALUE; - const tx = Math.abs( tangents[ 0 ].x ); - const ty = Math.abs( tangents[ 0 ].y ); - const tz = Math.abs( tangents[ 0 ].z ); - - if ( tx <= min ) { - - min = tx; - normal.set( 1, 0, 0 ); - - } - - if ( ty <= min ) { - - min = ty; - normal.set( 0, 1, 0 ); - - } - - if ( tz <= min ) { - - normal.set( 0, 0, 1 ); - - } - - vec.crossVectors( tangents[ 0 ], normal ).normalize(); - - normals[ 0 ].crossVectors( tangents[ 0 ], vec ); - binormals[ 0 ].crossVectors( tangents[ 0 ], normals[ 0 ] ); - - - // compute the slowly-varying normal and binormal vectors for each segment on the curve - - for ( let i = 1; i <= segments; i ++ ) { - - normals[ i ] = normals[ i - 1 ].clone(); - - binormals[ i ] = binormals[ i - 1 ].clone(); - - vec.crossVectors( tangents[ i - 1 ], tangents[ i ] ); - - if ( vec.length() > Number.EPSILON ) { - - vec.normalize(); - - const theta = Math.acos( clamp( tangents[ i - 1 ].dot( tangents[ i ] ), - 1, 1 ) ); // clamp for floating pt errors - - normals[ i ].applyMatrix4( mat.makeRotationAxis( vec, theta ) ); - - } - - binormals[ i ].crossVectors( tangents[ i ], normals[ i ] ); - - } - - // if the curve is closed, postprocess the vectors so the first and last normal vectors are the same - - if ( closed === true ) { - - let theta = Math.acos( clamp( normals[ 0 ].dot( normals[ segments ] ), - 1, 1 ) ); - theta /= segments; - - if ( tangents[ 0 ].dot( vec.crossVectors( normals[ 0 ], normals[ segments ] ) ) > 0 ) { - - theta = - theta; - - } - - for ( let i = 1; i <= segments; i ++ ) { - - // twist a little... - normals[ i ].applyMatrix4( mat.makeRotationAxis( tangents[ i ], theta * i ) ); - binormals[ i ].crossVectors( tangents[ i ], normals[ i ] ); - - } - - } - - return { - tangents: tangents, - normals: normals, - binormals: binormals - }; - - } - - clone() { - - return new this.constructor().copy( this ); - - } - - copy( source ) { - - this.arcLengthDivisions = source.arcLengthDivisions; - - return this; - - } - - toJSON() { - - const data = { - metadata: { - version: 4.6, - type: 'Curve', - generator: 'Curve.toJSON' - } - }; - - data.arcLengthDivisions = this.arcLengthDivisions; - data.type = this.type; - - return data; - - } - - fromJSON( json ) { - - this.arcLengthDivisions = json.arcLengthDivisions; - - return this; - - } - -} - -class EllipseCurve extends Curve { - - constructor( aX = 0, aY = 0, xRadius = 1, yRadius = 1, aStartAngle = 0, aEndAngle = Math.PI * 2, aClockwise = false, aRotation = 0 ) { - - super(); - - this.isEllipseCurve = true; - - this.type = 'EllipseCurve'; - - this.aX = aX; - this.aY = aY; - - this.xRadius = xRadius; - this.yRadius = yRadius; - - this.aStartAngle = aStartAngle; - this.aEndAngle = aEndAngle; - - this.aClockwise = aClockwise; - - this.aRotation = aRotation; - - } - - getPoint( t, optionalTarget ) { - - const point = optionalTarget || new Vector2(); - - const twoPi = Math.PI * 2; - let deltaAngle = this.aEndAngle - this.aStartAngle; - const samePoints = Math.abs( deltaAngle ) < Number.EPSILON; - - // ensures that deltaAngle is 0 .. 2 PI - while ( deltaAngle < 0 ) deltaAngle += twoPi; - while ( deltaAngle > twoPi ) deltaAngle -= twoPi; - - if ( deltaAngle < Number.EPSILON ) { - - if ( samePoints ) { - - deltaAngle = 0; - - } else { - - deltaAngle = twoPi; - - } - - } - - if ( this.aClockwise === true && ! samePoints ) { - - if ( deltaAngle === twoPi ) { - - deltaAngle = - twoPi; - - } else { - - deltaAngle = deltaAngle - twoPi; - - } - - } - - const angle = this.aStartAngle + t * deltaAngle; - let x = this.aX + this.xRadius * Math.cos( angle ); - let y = this.aY + this.yRadius * Math.sin( angle ); - - if ( this.aRotation !== 0 ) { - - const cos = Math.cos( this.aRotation ); - const sin = Math.sin( this.aRotation ); - - const tx = x - this.aX; - const ty = y - this.aY; - - // Rotate the point about the center of the ellipse. - x = tx * cos - ty * sin + this.aX; - y = tx * sin + ty * cos + this.aY; - - } - - return point.set( x, y ); - - } - - copy( source ) { - - super.copy( source ); - - this.aX = source.aX; - this.aY = source.aY; - - this.xRadius = source.xRadius; - this.yRadius = source.yRadius; - - this.aStartAngle = source.aStartAngle; - this.aEndAngle = source.aEndAngle; - - this.aClockwise = source.aClockwise; - - this.aRotation = source.aRotation; - - return this; - - } - - toJSON() { - - const data = super.toJSON(); - - data.aX = this.aX; - data.aY = this.aY; - - data.xRadius = this.xRadius; - data.yRadius = this.yRadius; - - data.aStartAngle = this.aStartAngle; - data.aEndAngle = this.aEndAngle; - - data.aClockwise = this.aClockwise; - - data.aRotation = this.aRotation; - - return data; - - } - - fromJSON( json ) { - - super.fromJSON( json ); - - this.aX = json.aX; - this.aY = json.aY; - - this.xRadius = json.xRadius; - this.yRadius = json.yRadius; - - this.aStartAngle = json.aStartAngle; - this.aEndAngle = json.aEndAngle; - - this.aClockwise = json.aClockwise; - - this.aRotation = json.aRotation; - - return this; - - } - -} - -class ArcCurve extends EllipseCurve { - - constructor( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) { - - super( aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise ); - - this.isArcCurve = true; - - this.type = 'ArcCurve'; - - } - -} - -/** - * Centripetal CatmullRom Curve - which is useful for avoiding - * cusps and self-intersections in non-uniform catmull rom curves. - * http://www.cemyuksel.com/research/catmullrom_param/catmullrom.pdf - * - * curve.type accepts centripetal(default), chordal and catmullrom - * curve.tension is used for catmullrom which defaults to 0.5 - */ - - -/* -Based on an optimized c++ solution in - - http://stackoverflow.com/questions/9489736/catmull-rom-curve-with-no-cusps-and-no-self-intersections/ - - http://ideone.com/NoEbVM - -This CubicPoly class could be used for reusing some variables and calculations, -but for three.js curve use, it could be possible inlined and flatten into a single function call -which can be placed in CurveUtils. -*/ - -function CubicPoly() { - - let c0 = 0, c1 = 0, c2 = 0, c3 = 0; - - /* - * Compute coefficients for a cubic polynomial - * p(s) = c0 + c1*s + c2*s^2 + c3*s^3 - * such that - * p(0) = x0, p(1) = x1 - * and - * p'(0) = t0, p'(1) = t1. - */ - function init( x0, x1, t0, t1 ) { - - c0 = x0; - c1 = t0; - c2 = - 3 * x0 + 3 * x1 - 2 * t0 - t1; - c3 = 2 * x0 - 2 * x1 + t0 + t1; - - } - - return { - - initCatmullRom: function ( x0, x1, x2, x3, tension ) { - - init( x1, x2, tension * ( x2 - x0 ), tension * ( x3 - x1 ) ); - - }, - - initNonuniformCatmullRom: function ( x0, x1, x2, x3, dt0, dt1, dt2 ) { - - // compute tangents when parameterized in [t1,t2] - let t1 = ( x1 - x0 ) / dt0 - ( x2 - x0 ) / ( dt0 + dt1 ) + ( x2 - x1 ) / dt1; - let t2 = ( x2 - x1 ) / dt1 - ( x3 - x1 ) / ( dt1 + dt2 ) + ( x3 - x2 ) / dt2; - - // rescale tangents for parametrization in [0,1] - t1 *= dt1; - t2 *= dt1; - - init( x1, x2, t1, t2 ); - - }, - - calc: function ( t ) { - - const t2 = t * t; - const t3 = t2 * t; - return c0 + c1 * t + c2 * t2 + c3 * t3; - - } - - }; - -} - -// - -const tmp = /*@__PURE__*/ new Vector3(); -const px = /*@__PURE__*/ new CubicPoly(); -const py = /*@__PURE__*/ new CubicPoly(); -const pz = /*@__PURE__*/ new CubicPoly(); - -class CatmullRomCurve3 extends Curve { - - constructor( points = [], closed = false, curveType = 'centripetal', tension = 0.5 ) { - - super(); - - this.isCatmullRomCurve3 = true; - - this.type = 'CatmullRomCurve3'; - - this.points = points; - this.closed = closed; - this.curveType = curveType; - this.tension = tension; - - } - - getPoint( t, optionalTarget = new Vector3() ) { - - const point = optionalTarget; - - const points = this.points; - const l = points.length; - - const p = ( l - ( this.closed ? 0 : 1 ) ) * t; - let intPoint = Math.floor( p ); - let weight = p - intPoint; - - if ( this.closed ) { - - intPoint += intPoint > 0 ? 0 : ( Math.floor( Math.abs( intPoint ) / l ) + 1 ) * l; - - } else if ( weight === 0 && intPoint === l - 1 ) { - - intPoint = l - 2; - weight = 1; - - } - - let p0, p3; // 4 points (p1 & p2 defined below) - - if ( this.closed || intPoint > 0 ) { - - p0 = points[ ( intPoint - 1 ) % l ]; - - } else { - - // extrapolate first point - tmp.subVectors( points[ 0 ], points[ 1 ] ).add( points[ 0 ] ); - p0 = tmp; - - } - - const p1 = points[ intPoint % l ]; - const p2 = points[ ( intPoint + 1 ) % l ]; - - if ( this.closed || intPoint + 2 < l ) { - - p3 = points[ ( intPoint + 2 ) % l ]; - - } else { - - // extrapolate last point - tmp.subVectors( points[ l - 1 ], points[ l - 2 ] ).add( points[ l - 1 ] ); - p3 = tmp; - - } - - if ( this.curveType === 'centripetal' || this.curveType === 'chordal' ) { - - // init Centripetal / Chordal Catmull-Rom - const pow = this.curveType === 'chordal' ? 0.5 : 0.25; - let dt0 = Math.pow( p0.distanceToSquared( p1 ), pow ); - let dt1 = Math.pow( p1.distanceToSquared( p2 ), pow ); - let dt2 = Math.pow( p2.distanceToSquared( p3 ), pow ); - - // safety check for repeated points - if ( dt1 < 1e-4 ) dt1 = 1.0; - if ( dt0 < 1e-4 ) dt0 = dt1; - if ( dt2 < 1e-4 ) dt2 = dt1; - - px.initNonuniformCatmullRom( p0.x, p1.x, p2.x, p3.x, dt0, dt1, dt2 ); - py.initNonuniformCatmullRom( p0.y, p1.y, p2.y, p3.y, dt0, dt1, dt2 ); - pz.initNonuniformCatmullRom( p0.z, p1.z, p2.z, p3.z, dt0, dt1, dt2 ); - - } else if ( this.curveType === 'catmullrom' ) { - - px.initCatmullRom( p0.x, p1.x, p2.x, p3.x, this.tension ); - py.initCatmullRom( p0.y, p1.y, p2.y, p3.y, this.tension ); - pz.initCatmullRom( p0.z, p1.z, p2.z, p3.z, this.tension ); - - } - - point.set( - px.calc( weight ), - py.calc( weight ), - pz.calc( weight ) - ); - - return point; - - } - - copy( source ) { - - super.copy( source ); - - this.points = []; - - for ( let i = 0, l = source.points.length; i < l; i ++ ) { - - const point = source.points[ i ]; - - this.points.push( point.clone() ); - - } - - this.closed = source.closed; - this.curveType = source.curveType; - this.tension = source.tension; - - return this; - - } - - toJSON() { - - const data = super.toJSON(); - - data.points = []; - - for ( let i = 0, l = this.points.length; i < l; i ++ ) { - - const point = this.points[ i ]; - data.points.push( point.toArray() ); - - } - - data.closed = this.closed; - data.curveType = this.curveType; - data.tension = this.tension; - - return data; - - } - - fromJSON( json ) { - - super.fromJSON( json ); - - this.points = []; - - for ( let i = 0, l = json.points.length; i < l; i ++ ) { - - const point = json.points[ i ]; - this.points.push( new Vector3().fromArray( point ) ); - - } - - this.closed = json.closed; - this.curveType = json.curveType; - this.tension = json.tension; - - return this; - - } - -} - -/** - * Bezier Curves formulas obtained from - * https://en.wikipedia.org/wiki/B%C3%A9zier_curve - */ - -function CatmullRom( t, p0, p1, p2, p3 ) { - - const v0 = ( p2 - p0 ) * 0.5; - const v1 = ( p3 - p1 ) * 0.5; - const t2 = t * t; - const t3 = t * t2; - return ( 2 * p1 - 2 * p2 + v0 + v1 ) * t3 + ( - 3 * p1 + 3 * p2 - 2 * v0 - v1 ) * t2 + v0 * t + p1; - -} - -// - -function QuadraticBezierP0( t, p ) { - - const k = 1 - t; - return k * k * p; - -} - -function QuadraticBezierP1( t, p ) { - - return 2 * ( 1 - t ) * t * p; - -} - -function QuadraticBezierP2( t, p ) { - - return t * t * p; - -} - -function QuadraticBezier( t, p0, p1, p2 ) { - - return QuadraticBezierP0( t, p0 ) + QuadraticBezierP1( t, p1 ) + - QuadraticBezierP2( t, p2 ); - -} - -// - -function CubicBezierP0( t, p ) { - - const k = 1 - t; - return k * k * k * p; - -} - -function CubicBezierP1( t, p ) { - - const k = 1 - t; - return 3 * k * k * t * p; - -} - -function CubicBezierP2( t, p ) { - - return 3 * ( 1 - t ) * t * t * p; - -} - -function CubicBezierP3( t, p ) { - - return t * t * t * p; - -} - -function CubicBezier( t, p0, p1, p2, p3 ) { - - return CubicBezierP0( t, p0 ) + CubicBezierP1( t, p1 ) + CubicBezierP2( t, p2 ) + - CubicBezierP3( t, p3 ); - -} - -class CubicBezierCurve extends Curve { - - constructor( v0 = new Vector2(), v1 = new Vector2(), v2 = new Vector2(), v3 = new Vector2() ) { - - super(); - - this.isCubicBezierCurve = true; - - this.type = 'CubicBezierCurve'; - - this.v0 = v0; - this.v1 = v1; - this.v2 = v2; - this.v3 = v3; - - } - - getPoint( t, optionalTarget = new Vector2() ) { - - const point = optionalTarget; - - const v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3; - - point.set( - CubicBezier( t, v0.x, v1.x, v2.x, v3.x ), - CubicBezier( t, v0.y, v1.y, v2.y, v3.y ) - ); - - return point; - - } - - copy( source ) { - - super.copy( source ); - - this.v0.copy( source.v0 ); - this.v1.copy( source.v1 ); - this.v2.copy( source.v2 ); - this.v3.copy( source.v3 ); - - return this; - - } - - toJSON() { - - const data = super.toJSON(); - - data.v0 = this.v0.toArray(); - data.v1 = this.v1.toArray(); - data.v2 = this.v2.toArray(); - data.v3 = this.v3.toArray(); - - return data; - - } - - fromJSON( json ) { - - super.fromJSON( json ); - - this.v0.fromArray( json.v0 ); - this.v1.fromArray( json.v1 ); - this.v2.fromArray( json.v2 ); - this.v3.fromArray( json.v3 ); - - return this; - - } - -} - -class CubicBezierCurve3 extends Curve { - - constructor( v0 = new Vector3(), v1 = new Vector3(), v2 = new Vector3(), v3 = new Vector3() ) { - - super(); - - this.isCubicBezierCurve3 = true; - - this.type = 'CubicBezierCurve3'; - - this.v0 = v0; - this.v1 = v1; - this.v2 = v2; - this.v3 = v3; - - } - - getPoint( t, optionalTarget = new Vector3() ) { - - const point = optionalTarget; - - const v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3; - - point.set( - CubicBezier( t, v0.x, v1.x, v2.x, v3.x ), - CubicBezier( t, v0.y, v1.y, v2.y, v3.y ), - CubicBezier( t, v0.z, v1.z, v2.z, v3.z ) - ); - - return point; - - } - - copy( source ) { - - super.copy( source ); - - this.v0.copy( source.v0 ); - this.v1.copy( source.v1 ); - this.v2.copy( source.v2 ); - this.v3.copy( source.v3 ); - - return this; - - } - - toJSON() { - - const data = super.toJSON(); - - data.v0 = this.v0.toArray(); - data.v1 = this.v1.toArray(); - data.v2 = this.v2.toArray(); - data.v3 = this.v3.toArray(); - - return data; - - } - - fromJSON( json ) { - - super.fromJSON( json ); - - this.v0.fromArray( json.v0 ); - this.v1.fromArray( json.v1 ); - this.v2.fromArray( json.v2 ); - this.v3.fromArray( json.v3 ); - - return this; - - } - -} - -class LineCurve extends Curve { - - constructor( v1 = new Vector2(), v2 = new Vector2() ) { - - super(); - - this.isLineCurve = true; - - this.type = 'LineCurve'; - - this.v1 = v1; - this.v2 = v2; - - } - - getPoint( t, optionalTarget = new Vector2() ) { - - const point = optionalTarget; - - if ( t === 1 ) { - - point.copy( this.v2 ); - - } else { - - point.copy( this.v2 ).sub( this.v1 ); - point.multiplyScalar( t ).add( this.v1 ); - - } - - return point; - - } - - // Line curve is linear, so we can overwrite default getPointAt - getPointAt( u, optionalTarget ) { - - return this.getPoint( u, optionalTarget ); - - } - - getTangent( t, optionalTarget = new Vector2() ) { - - return optionalTarget.subVectors( this.v2, this.v1 ).normalize(); - - } - - getTangentAt( u, optionalTarget ) { - - return this.getTangent( u, optionalTarget ); - - } - - copy( source ) { - - super.copy( source ); - - this.v1.copy( source.v1 ); - this.v2.copy( source.v2 ); - - return this; - - } - - toJSON() { - - const data = super.toJSON(); - - data.v1 = this.v1.toArray(); - data.v2 = this.v2.toArray(); - - return data; - - } - - fromJSON( json ) { - - super.fromJSON( json ); - - this.v1.fromArray( json.v1 ); - this.v2.fromArray( json.v2 ); - - return this; - - } - -} - -class LineCurve3 extends Curve { - - constructor( v1 = new Vector3(), v2 = new Vector3() ) { - - super(); - - this.isLineCurve3 = true; - - this.type = 'LineCurve3'; - - this.v1 = v1; - this.v2 = v2; - - } - getPoint( t, optionalTarget = new Vector3() ) { - - const point = optionalTarget; - - if ( t === 1 ) { - - point.copy( this.v2 ); - - } else { - - point.copy( this.v2 ).sub( this.v1 ); - point.multiplyScalar( t ).add( this.v1 ); - - } - - return point; - - } - // Line curve is linear, so we can overwrite default getPointAt - getPointAt( u, optionalTarget ) { - - return this.getPoint( u, optionalTarget ); - - } - - getTangent( t, optionalTarget = new Vector3() ) { - - return optionalTarget.subVectors( this.v2, this.v1 ).normalize(); - - } - - getTangentAt( u, optionalTarget ) { - - return this.getTangent( u, optionalTarget ); - - } - - copy( source ) { - - super.copy( source ); - - this.v1.copy( source.v1 ); - this.v2.copy( source.v2 ); - - return this; - - } - toJSON() { - - const data = super.toJSON(); - - data.v1 = this.v1.toArray(); - data.v2 = this.v2.toArray(); - - return data; - - } - fromJSON( json ) { - - super.fromJSON( json ); - - this.v1.fromArray( json.v1 ); - this.v2.fromArray( json.v2 ); - - return this; - - } - -} - -class QuadraticBezierCurve extends Curve { - - constructor( v0 = new Vector2(), v1 = new Vector2(), v2 = new Vector2() ) { - - super(); - - this.isQuadraticBezierCurve = true; - - this.type = 'QuadraticBezierCurve'; - - this.v0 = v0; - this.v1 = v1; - this.v2 = v2; - - } - - getPoint( t, optionalTarget = new Vector2() ) { - - const point = optionalTarget; - - const v0 = this.v0, v1 = this.v1, v2 = this.v2; - - point.set( - QuadraticBezier( t, v0.x, v1.x, v2.x ), - QuadraticBezier( t, v0.y, v1.y, v2.y ) - ); - - return point; - - } - - copy( source ) { - - super.copy( source ); - - this.v0.copy( source.v0 ); - this.v1.copy( source.v1 ); - this.v2.copy( source.v2 ); - - return this; - - } - - toJSON() { - - const data = super.toJSON(); - - data.v0 = this.v0.toArray(); - data.v1 = this.v1.toArray(); - data.v2 = this.v2.toArray(); - - return data; - - } - - fromJSON( json ) { - - super.fromJSON( json ); - - this.v0.fromArray( json.v0 ); - this.v1.fromArray( json.v1 ); - this.v2.fromArray( json.v2 ); - - return this; - - } - -} - -class QuadraticBezierCurve3 extends Curve { - - constructor( v0 = new Vector3(), v1 = new Vector3(), v2 = new Vector3() ) { - - super(); - - this.isQuadraticBezierCurve3 = true; - - this.type = 'QuadraticBezierCurve3'; - - this.v0 = v0; - this.v1 = v1; - this.v2 = v2; - - } - - getPoint( t, optionalTarget = new Vector3() ) { - - const point = optionalTarget; - - const v0 = this.v0, v1 = this.v1, v2 = this.v2; - - point.set( - QuadraticBezier( t, v0.x, v1.x, v2.x ), - QuadraticBezier( t, v0.y, v1.y, v2.y ), - QuadraticBezier( t, v0.z, v1.z, v2.z ) - ); - - return point; - - } - - copy( source ) { - - super.copy( source ); - - this.v0.copy( source.v0 ); - this.v1.copy( source.v1 ); - this.v2.copy( source.v2 ); - - return this; - - } - - toJSON() { - - const data = super.toJSON(); - - data.v0 = this.v0.toArray(); - data.v1 = this.v1.toArray(); - data.v2 = this.v2.toArray(); - - return data; - - } - - fromJSON( json ) { - - super.fromJSON( json ); - - this.v0.fromArray( json.v0 ); - this.v1.fromArray( json.v1 ); - this.v2.fromArray( json.v2 ); - - return this; - - } - -} - -class SplineCurve extends Curve { - - constructor( points = [] ) { - - super(); - - this.isSplineCurve = true; - - this.type = 'SplineCurve'; - - this.points = points; - - } - - getPoint( t, optionalTarget = new Vector2() ) { - - const point = optionalTarget; - - const points = this.points; - const p = ( points.length - 1 ) * t; - - const intPoint = Math.floor( p ); - const weight = p - intPoint; - - const p0 = points[ intPoint === 0 ? intPoint : intPoint - 1 ]; - const p1 = points[ intPoint ]; - const p2 = points[ intPoint > points.length - 2 ? points.length - 1 : intPoint + 1 ]; - const p3 = points[ intPoint > points.length - 3 ? points.length - 1 : intPoint + 2 ]; - - point.set( - CatmullRom( weight, p0.x, p1.x, p2.x, p3.x ), - CatmullRom( weight, p0.y, p1.y, p2.y, p3.y ) - ); - - return point; - - } - - copy( source ) { - - super.copy( source ); - - this.points = []; - - for ( let i = 0, l = source.points.length; i < l; i ++ ) { - - const point = source.points[ i ]; - - this.points.push( point.clone() ); - - } - - return this; - - } - - toJSON() { - - const data = super.toJSON(); - - data.points = []; - - for ( let i = 0, l = this.points.length; i < l; i ++ ) { - - const point = this.points[ i ]; - data.points.push( point.toArray() ); - - } - - return data; - - } - - fromJSON( json ) { - - super.fromJSON( json ); - - this.points = []; - - for ( let i = 0, l = json.points.length; i < l; i ++ ) { - - const point = json.points[ i ]; - this.points.push( new Vector2().fromArray( point ) ); - - } - - return this; - - } - -} - -var Curves = /*#__PURE__*/Object.freeze({ - __proto__: null, - ArcCurve: ArcCurve, - CatmullRomCurve3: CatmullRomCurve3, - CubicBezierCurve: CubicBezierCurve, - CubicBezierCurve3: CubicBezierCurve3, - EllipseCurve: EllipseCurve, - LineCurve: LineCurve, - LineCurve3: LineCurve3, - QuadraticBezierCurve: QuadraticBezierCurve, - QuadraticBezierCurve3: QuadraticBezierCurve3, - SplineCurve: SplineCurve -}); - -/************************************************************** - * Curved Path - a curve path is simply a array of connected - * curves, but retains the api of a curve - **************************************************************/ - -class CurvePath extends Curve { - - constructor() { - - super(); - - this.type = 'CurvePath'; - - this.curves = []; - this.autoClose = false; // Automatically closes the path - - } - - add( curve ) { - - this.curves.push( curve ); - - } - - closePath() { - - // Add a line curve if start and end of lines are not connected - const startPoint = this.curves[ 0 ].getPoint( 0 ); - const endPoint = this.curves[ this.curves.length - 1 ].getPoint( 1 ); - - if ( ! startPoint.equals( endPoint ) ) { - - this.curves.push( new LineCurve( endPoint, startPoint ) ); - - } - - } - - // To get accurate point with reference to - // entire path distance at time t, - // following has to be done: - - // 1. Length of each sub path have to be known - // 2. Locate and identify type of curve - // 3. Get t for the curve - // 4. Return curve.getPointAt(t') - - getPoint( t, optionalTarget ) { - - const d = t * this.getLength(); - const curveLengths = this.getCurveLengths(); - let i = 0; - - // To think about boundaries points. - - while ( i < curveLengths.length ) { - - if ( curveLengths[ i ] >= d ) { - - const diff = curveLengths[ i ] - d; - const curve = this.curves[ i ]; - - const segmentLength = curve.getLength(); - const u = segmentLength === 0 ? 0 : 1 - diff / segmentLength; - - return curve.getPointAt( u, optionalTarget ); - - } - - i ++; - - } - - return null; - - // loop where sum != 0, sum > d , sum+1 1 && ! points[ points.length - 1 ].equals( points[ 0 ] ) ) { - - points.push( points[ 0 ] ); - - } - - return points; - - } - - copy( source ) { - - super.copy( source ); - - this.curves = []; - - for ( let i = 0, l = source.curves.length; i < l; i ++ ) { - - const curve = source.curves[ i ]; - - this.curves.push( curve.clone() ); - - } - - this.autoClose = source.autoClose; - - return this; - - } - - toJSON() { - - const data = super.toJSON(); - - data.autoClose = this.autoClose; - data.curves = []; - - for ( let i = 0, l = this.curves.length; i < l; i ++ ) { - - const curve = this.curves[ i ]; - data.curves.push( curve.toJSON() ); - - } - - return data; - - } - - fromJSON( json ) { - - super.fromJSON( json ); - - this.autoClose = json.autoClose; - this.curves = []; - - for ( let i = 0, l = json.curves.length; i < l; i ++ ) { - - const curve = json.curves[ i ]; - this.curves.push( new Curves[ curve.type ]().fromJSON( curve ) ); - - } - - return this; - - } - -} - -class Path extends CurvePath { - - constructor( points ) { - - super(); - - this.type = 'Path'; - - this.currentPoint = new Vector2(); - - if ( points ) { - - this.setFromPoints( points ); - - } - - } - - setFromPoints( points ) { - - this.moveTo( points[ 0 ].x, points[ 0 ].y ); - - for ( let i = 1, l = points.length; i < l; i ++ ) { - - this.lineTo( points[ i ].x, points[ i ].y ); - - } - - return this; - - } - - moveTo( x, y ) { - - this.currentPoint.set( x, y ); // TODO consider referencing vectors instead of copying? - - return this; - - } - - lineTo( x, y ) { - - const curve = new LineCurve( this.currentPoint.clone(), new Vector2( x, y ) ); - this.curves.push( curve ); - - this.currentPoint.set( x, y ); - - return this; - - } - - quadraticCurveTo( aCPx, aCPy, aX, aY ) { - - const curve = new QuadraticBezierCurve( - this.currentPoint.clone(), - new Vector2( aCPx, aCPy ), - new Vector2( aX, aY ) - ); - - this.curves.push( curve ); - - this.currentPoint.set( aX, aY ); - - return this; - - } - - bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) { - - const curve = new CubicBezierCurve( - this.currentPoint.clone(), - new Vector2( aCP1x, aCP1y ), - new Vector2( aCP2x, aCP2y ), - new Vector2( aX, aY ) - ); - - this.curves.push( curve ); - - this.currentPoint.set( aX, aY ); - - return this; - - } - - splineThru( pts /*Array of Vector*/ ) { - - const npts = [ this.currentPoint.clone() ].concat( pts ); - - const curve = new SplineCurve( npts ); - this.curves.push( curve ); - - this.currentPoint.copy( pts[ pts.length - 1 ] ); - - return this; - - } - - arc( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) { - - const x0 = this.currentPoint.x; - const y0 = this.currentPoint.y; - - this.absarc( aX + x0, aY + y0, aRadius, - aStartAngle, aEndAngle, aClockwise ); - - return this; - - } - - absarc( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) { - - this.absellipse( aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise ); - - return this; - - } - - ellipse( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) { - - const x0 = this.currentPoint.x; - const y0 = this.currentPoint.y; - - this.absellipse( aX + x0, aY + y0, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ); - - return this; - - } - - absellipse( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) { - - const curve = new EllipseCurve( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ); - - if ( this.curves.length > 0 ) { - - // if a previous curve is present, attempt to join - const firstPoint = curve.getPoint( 0 ); - - if ( ! firstPoint.equals( this.currentPoint ) ) { - - this.lineTo( firstPoint.x, firstPoint.y ); - - } - - } - - this.curves.push( curve ); - - const lastPoint = curve.getPoint( 1 ); - this.currentPoint.copy( lastPoint ); - - return this; - - } - - copy( source ) { - - super.copy( source ); - - this.currentPoint.copy( source.currentPoint ); - - return this; - - } - - toJSON() { - - const data = super.toJSON(); - - data.currentPoint = this.currentPoint.toArray(); - - return data; - - } - - fromJSON( json ) { - - super.fromJSON( json ); - - this.currentPoint.fromArray( json.currentPoint ); - - return this; - - } - -} - -class LatheGeometry extends BufferGeometry { - - constructor( points = [ new Vector2( 0, - 0.5 ), new Vector2( 0.5, 0 ), new Vector2( 0, 0.5 ) ], segments = 12, phiStart = 0, phiLength = Math.PI * 2 ) { - - super(); - - this.type = 'LatheGeometry'; - - this.parameters = { - points: points, - segments: segments, - phiStart: phiStart, - phiLength: phiLength - }; - - segments = Math.floor( segments ); - - // clamp phiLength so it's in range of [ 0, 2PI ] - - phiLength = clamp( phiLength, 0, Math.PI * 2 ); - - // buffers - - const indices = []; - const vertices = []; - const uvs = []; - const initNormals = []; - const normals = []; - - // helper variables - - const inverseSegments = 1.0 / segments; - const vertex = new Vector3(); - const uv = new Vector2(); - const normal = new Vector3(); - const curNormal = new Vector3(); - const prevNormal = new Vector3(); - let dx = 0; - let dy = 0; - - // pre-compute normals for initial "meridian" - - for ( let j = 0; j <= ( points.length - 1 ); j ++ ) { - - switch ( j ) { - - case 0: // special handling for 1st vertex on path - - dx = points[ j + 1 ].x - points[ j ].x; - dy = points[ j + 1 ].y - points[ j ].y; - - normal.x = dy * 1.0; - normal.y = - dx; - normal.z = dy * 0.0; - - prevNormal.copy( normal ); - - normal.normalize(); - - initNormals.push( normal.x, normal.y, normal.z ); - - break; - - case ( points.length - 1 ): // special handling for last Vertex on path - - initNormals.push( prevNormal.x, prevNormal.y, prevNormal.z ); - - break; - - default: // default handling for all vertices in between - - dx = points[ j + 1 ].x - points[ j ].x; - dy = points[ j + 1 ].y - points[ j ].y; - - normal.x = dy * 1.0; - normal.y = - dx; - normal.z = dy * 0.0; - - curNormal.copy( normal ); - - normal.x += prevNormal.x; - normal.y += prevNormal.y; - normal.z += prevNormal.z; - - normal.normalize(); - - initNormals.push( normal.x, normal.y, normal.z ); - - prevNormal.copy( curNormal ); - - } - - } - - // generate vertices, uvs and normals - - for ( let i = 0; i <= segments; i ++ ) { - - const phi = phiStart + i * inverseSegments * phiLength; - - const sin = Math.sin( phi ); - const cos = Math.cos( phi ); - - for ( let j = 0; j <= ( points.length - 1 ); j ++ ) { - - // vertex - - vertex.x = points[ j ].x * sin; - vertex.y = points[ j ].y; - vertex.z = points[ j ].x * cos; - - vertices.push( vertex.x, vertex.y, vertex.z ); - - // uv - - uv.x = i / segments; - uv.y = j / ( points.length - 1 ); - - uvs.push( uv.x, uv.y ); - - // normal - - const x = initNormals[ 3 * j + 0 ] * sin; - const y = initNormals[ 3 * j + 1 ]; - const z = initNormals[ 3 * j + 0 ] * cos; - - normals.push( x, y, z ); - - } - - } - - // indices - - for ( let i = 0; i < segments; i ++ ) { - - for ( let j = 0; j < ( points.length - 1 ); j ++ ) { - - const base = j + i * points.length; - - const a = base; - const b = base + points.length; - const c = base + points.length + 1; - const d = base + 1; - - // faces - - indices.push( a, b, d ); - indices.push( c, d, b ); - - } - - } - - // build geometry - - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - - } - - copy( source ) { - - super.copy( source ); - - this.parameters = Object.assign( {}, source.parameters ); - - return this; - - } - - static fromJSON( data ) { - - return new LatheGeometry( data.points, data.segments, data.phiStart, data.phiLength ); - - } - -} - -class CapsuleGeometry extends LatheGeometry { - - constructor( radius = 1, length = 1, capSegments = 4, radialSegments = 8 ) { - - const path = new Path(); - path.absarc( 0, - length / 2, radius, Math.PI * 1.5, 0 ); - path.absarc( 0, length / 2, radius, 0, Math.PI * 0.5 ); - - super( path.getPoints( capSegments ), radialSegments ); - - this.type = 'CapsuleGeometry'; - - this.parameters = { - radius: radius, - height: length, - capSegments: capSegments, - radialSegments: radialSegments, - }; +const vertex$c = "varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n}"; - } +const fragment$c = "uniform sampler2D tEquirect;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvec3 direction = normalize( vWorldDirection );\n\tvec2 sampleUV = equirectUv( direction );\n\tgl_FragColor = texture2D( tEquirect, sampleUV );\n\t#include \n\t#include \n}"; - static fromJSON( data ) { +const vertex$b = "uniform float scale;\nattribute float lineDistance;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvLineDistance = scale * lineDistance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - return new CapsuleGeometry( data.radius, data.length, data.capSegments, data.radialSegments ); +const fragment$b = "uniform vec3 diffuse;\nuniform float opacity;\nuniform float dashSize;\nuniform float totalSize;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tif ( mod( vLineDistance, totalSize ) > dashSize ) {\n\t\tdiscard;\n\t}\n\tvec3 outgoingLight = vec3( 0.0 );\n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - } +const vertex$a = "#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#if defined ( USE_ENVMAP ) || defined ( USE_SKINNING )\n\t\t#include \n\t\t#include \n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; -} +const fragment$a = "uniform vec3 diffuse;\nuniform float opacity;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel = texture2D( lightMap, vLightMapUv );\n\t\treflectedLight.indirectDiffuse += lightMapTexel.rgb * lightMapIntensity * RECIPROCAL_PI;\n\t#else\n\t\treflectedLight.indirectDiffuse += vec3( 1.0 );\n\t#endif\n\t#include \n\treflectedLight.indirectDiffuse *= diffuseColor.rgb;\n\tvec3 outgoingLight = reflectedLight.indirectDiffuse;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; -class CircleGeometry extends BufferGeometry { +const vertex$9 = "#define LAMBERT\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n\t#include \n}"; - constructor( radius = 1, segments = 32, thetaStart = 0, thetaLength = Math.PI * 2 ) { +const fragment$9 = "#define LAMBERT\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - super(); +const vertex$8 = "#define MATCAP\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n}"; - this.type = 'CircleGeometry'; +const fragment$8 = "#define MATCAP\nuniform vec3 diffuse;\nuniform float opacity;\nuniform sampler2D matcap;\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 viewDir = normalize( vViewPosition );\n\tvec3 x = normalize( vec3( viewDir.z, 0.0, - viewDir.x ) );\n\tvec3 y = cross( viewDir, x );\n\tvec2 uv = vec2( dot( x, normal ), dot( y, normal ) ) * 0.495 + 0.5;\n\t#ifdef USE_MATCAP\n\t\tvec4 matcapColor = texture2D( matcap, uv );\n\t#else\n\t\tvec4 matcapColor = vec4( vec3( mix( 0.2, 0.8, uv.y ) ), 1.0 );\n\t#endif\n\tvec3 outgoingLight = diffuseColor.rgb * matcapColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - this.parameters = { - radius: radius, - segments: segments, - thetaStart: thetaStart, - thetaLength: thetaLength - }; +const vertex$7 = "#define NORMAL\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvarying vec3 vViewPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvViewPosition = - mvPosition.xyz;\n#endif\n}"; - segments = Math.max( 3, segments ); +const fragment$7 = "#define NORMAL\nuniform float opacity;\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvarying vec3 vViewPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( 0.0, 0.0, 0.0, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\tgl_FragColor = vec4( packNormalToRGB( normal ), diffuseColor.a );\n\t#ifdef OPAQUE\n\t\tgl_FragColor.a = 1.0;\n\t#endif\n}"; - // buffers +const vertex$6 = "#define PHONG\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n\t#include \n}"; - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; +const fragment$6 = "#define PHONG\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform vec3 specular;\nuniform float shininess;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - // helper variables +const vertex$5 = "#define STANDARD\nvarying vec3 vViewPosition;\n#ifdef USE_TRANSMISSION\n\tvarying vec3 vWorldPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n#ifdef USE_TRANSMISSION\n\tvWorldPosition = worldPosition.xyz;\n#endif\n}"; - const vertex = new Vector3(); - const uv = new Vector2(); +const fragment$5 = "#define STANDARD\n#ifdef PHYSICAL\n\t#define IOR\n\t#define USE_SPECULAR\n#endif\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float roughness;\nuniform float metalness;\nuniform float opacity;\n#ifdef IOR\n\tuniform float ior;\n#endif\n#ifdef USE_SPECULAR\n\tuniform float specularIntensity;\n\tuniform vec3 specularColor;\n\t#ifdef USE_SPECULAR_COLORMAP\n\t\tuniform sampler2D specularColorMap;\n\t#endif\n\t#ifdef USE_SPECULAR_INTENSITYMAP\n\t\tuniform sampler2D specularIntensityMap;\n\t#endif\n#endif\n#ifdef USE_CLEARCOAT\n\tuniform float clearcoat;\n\tuniform float clearcoatRoughness;\n#endif\n#ifdef USE_DISPERSION\n\tuniform float dispersion;\n#endif\n#ifdef USE_IRIDESCENCE\n\tuniform float iridescence;\n\tuniform float iridescenceIOR;\n\tuniform float iridescenceThicknessMinimum;\n\tuniform float iridescenceThicknessMaximum;\n#endif\n#ifdef USE_SHEEN\n\tuniform vec3 sheenColor;\n\tuniform float sheenRoughness;\n\t#ifdef USE_SHEEN_COLORMAP\n\t\tuniform sampler2D sheenColorMap;\n\t#endif\n\t#ifdef USE_SHEEN_ROUGHNESSMAP\n\t\tuniform sampler2D sheenRoughnessMap;\n\t#endif\n#endif\n#ifdef USE_ANISOTROPY\n\tuniform vec2 anisotropyVector;\n\t#ifdef USE_ANISOTROPYMAP\n\t\tuniform sampler2D anisotropyMap;\n\t#endif\n#endif\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 totalDiffuse = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse;\n\tvec3 totalSpecular = reflectedLight.directSpecular + reflectedLight.indirectSpecular;\n\t#include \n\tvec3 outgoingLight = totalDiffuse + totalSpecular + totalEmissiveRadiance;\n\t#ifdef USE_SHEEN\n\t\tfloat sheenEnergyComp = 1.0 - 0.157 * max3( material.sheenColor );\n\t\toutgoingLight = outgoingLight * sheenEnergyComp + sheenSpecularDirect + sheenSpecularIndirect;\n\t#endif\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNVcc = saturate( dot( geometryClearcoatNormal, geometryViewDir ) );\n\t\tvec3 Fcc = F_Schlick( material.clearcoatF0, material.clearcoatF90, dotNVcc );\n\t\toutgoingLight = outgoingLight * ( 1.0 - material.clearcoat * Fcc ) + ( clearcoatSpecularDirect + clearcoatSpecularIndirect ) * material.clearcoat;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - // center point +const vertex$4 = "#define TOON\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n}"; - vertices.push( 0, 0, 0 ); - normals.push( 0, 0, 1 ); - uvs.push( 0.5, 0.5 ); +const fragment$4 = "#define TOON\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - for ( let s = 0, i = 3; s <= segments; s ++, i += 3 ) { +const vertex$3 = "uniform float size;\nuniform float scale;\n#include \n#include \n#include \n#include \n#include \n#include \n#ifdef USE_POINTS_UV\n\tvarying vec2 vUv;\n\tuniform mat3 uvTransform;\n#endif\nvoid main() {\n\t#ifdef USE_POINTS_UV\n\t\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tgl_PointSize = size;\n\t#ifdef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) gl_PointSize *= ( scale / - mvPosition.z );\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n}"; - const segment = thetaStart + s / segments * thetaLength; +const fragment$3 = "uniform vec3 diffuse;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tvec3 outgoingLight = vec3( 0.0 );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - // vertex +const vertex$2 = "#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}"; - vertex.x = radius * Math.cos( segment ); - vertex.y = radius * Math.sin( segment ); +const fragment$2 = "uniform vec3 color;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tgl_FragColor = vec4( color, opacity * ( 1.0 - getShadowMask() ) );\n\t#include \n\t#include \n\t#include \n}"; - vertices.push( vertex.x, vertex.y, vertex.z ); +const vertex$1 = "uniform float rotation;\nuniform vec2 center;\n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 mvPosition = modelViewMatrix[ 3 ];\n\tvec2 scale = vec2( length( modelMatrix[ 0 ].xyz ), length( modelMatrix[ 1 ].xyz ) );\n\t#ifndef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) scale *= - mvPosition.z;\n\t#endif\n\tvec2 alignedPosition = ( position.xy - ( center - vec2( 0.5 ) ) ) * scale;\n\tvec2 rotatedPosition;\n\trotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;\n\trotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;\n\tmvPosition.xy += rotatedPosition;\n\tgl_Position = projectionMatrix * mvPosition;\n\t#include \n\t#include \n\t#include \n}"; - // normal +const fragment$1 = "uniform vec3 diffuse;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tvec3 outgoingLight = vec3( 0.0 );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n}"; - normals.push( 0, 0, 1 ); +const ShaderChunk = { + alphahash_fragment: alphahash_fragment, + alphahash_pars_fragment: alphahash_pars_fragment, + alphamap_fragment: alphamap_fragment, + alphamap_pars_fragment: alphamap_pars_fragment, + alphatest_fragment: alphatest_fragment, + alphatest_pars_fragment: alphatest_pars_fragment, + aomap_fragment: aomap_fragment, + aomap_pars_fragment: aomap_pars_fragment, + batching_pars_vertex: batching_pars_vertex, + batching_vertex: batching_vertex, + begin_vertex: begin_vertex, + beginnormal_vertex: beginnormal_vertex, + bsdfs: bsdfs, + iridescence_fragment: iridescence_fragment, + bumpmap_pars_fragment: bumpmap_pars_fragment, + clipping_planes_fragment: clipping_planes_fragment, + clipping_planes_pars_fragment: clipping_planes_pars_fragment, + clipping_planes_pars_vertex: clipping_planes_pars_vertex, + clipping_planes_vertex: clipping_planes_vertex, + color_fragment: color_fragment, + color_pars_fragment: color_pars_fragment, + color_pars_vertex: color_pars_vertex, + color_vertex: color_vertex, + common: common, + cube_uv_reflection_fragment: cube_uv_reflection_fragment, + defaultnormal_vertex: defaultnormal_vertex, + displacementmap_pars_vertex: displacementmap_pars_vertex, + displacementmap_vertex: displacementmap_vertex, + emissivemap_fragment: emissivemap_fragment, + emissivemap_pars_fragment: emissivemap_pars_fragment, + colorspace_fragment: colorspace_fragment, + colorspace_pars_fragment: colorspace_pars_fragment, + envmap_fragment: envmap_fragment, + envmap_common_pars_fragment: envmap_common_pars_fragment, + envmap_pars_fragment: envmap_pars_fragment, + envmap_pars_vertex: envmap_pars_vertex, + envmap_physical_pars_fragment: envmap_physical_pars_fragment, + envmap_vertex: envmap_vertex, + fog_vertex: fog_vertex, + fog_pars_vertex: fog_pars_vertex, + fog_fragment: fog_fragment, + fog_pars_fragment: fog_pars_fragment, + gradientmap_pars_fragment: gradientmap_pars_fragment, + lightmap_pars_fragment: lightmap_pars_fragment, + lights_lambert_fragment: lights_lambert_fragment, + lights_lambert_pars_fragment: lights_lambert_pars_fragment, + lights_pars_begin: lights_pars_begin, + lights_toon_fragment: lights_toon_fragment, + lights_toon_pars_fragment: lights_toon_pars_fragment, + lights_phong_fragment: lights_phong_fragment, + lights_phong_pars_fragment: lights_phong_pars_fragment, + lights_physical_fragment: lights_physical_fragment, + lights_physical_pars_fragment: lights_physical_pars_fragment, + lights_fragment_begin: lights_fragment_begin, + lights_fragment_maps: lights_fragment_maps, + lights_fragment_end: lights_fragment_end, + logdepthbuf_fragment: logdepthbuf_fragment, + logdepthbuf_pars_fragment: logdepthbuf_pars_fragment, + logdepthbuf_pars_vertex: logdepthbuf_pars_vertex, + logdepthbuf_vertex: logdepthbuf_vertex, + map_fragment: map_fragment, + map_pars_fragment: map_pars_fragment, + map_particle_fragment: map_particle_fragment, + map_particle_pars_fragment: map_particle_pars_fragment, + metalnessmap_fragment: metalnessmap_fragment, + metalnessmap_pars_fragment: metalnessmap_pars_fragment, + morphinstance_vertex: morphinstance_vertex, + morphcolor_vertex: morphcolor_vertex, + morphnormal_vertex: morphnormal_vertex, + morphtarget_pars_vertex: morphtarget_pars_vertex, + morphtarget_vertex: morphtarget_vertex, + normal_fragment_begin: normal_fragment_begin, + normal_fragment_maps: normal_fragment_maps, + normal_pars_fragment: normal_pars_fragment, + normal_pars_vertex: normal_pars_vertex, + normal_vertex: normal_vertex, + normalmap_pars_fragment: normalmap_pars_fragment, + clearcoat_normal_fragment_begin: clearcoat_normal_fragment_begin, + clearcoat_normal_fragment_maps: clearcoat_normal_fragment_maps, + clearcoat_pars_fragment: clearcoat_pars_fragment, + iridescence_pars_fragment: iridescence_pars_fragment, + opaque_fragment: opaque_fragment, + packing: packing, + premultiplied_alpha_fragment: premultiplied_alpha_fragment, + project_vertex: project_vertex, + dithering_fragment: dithering_fragment, + dithering_pars_fragment: dithering_pars_fragment, + roughnessmap_fragment: roughnessmap_fragment, + roughnessmap_pars_fragment: roughnessmap_pars_fragment, + shadowmap_pars_fragment: shadowmap_pars_fragment, + shadowmap_pars_vertex: shadowmap_pars_vertex, + shadowmap_vertex: shadowmap_vertex, + shadowmask_pars_fragment: shadowmask_pars_fragment, + skinbase_vertex: skinbase_vertex, + skinning_pars_vertex: skinning_pars_vertex, + skinning_vertex: skinning_vertex, + skinnormal_vertex: skinnormal_vertex, + specularmap_fragment: specularmap_fragment, + specularmap_pars_fragment: specularmap_pars_fragment, + tonemapping_fragment: tonemapping_fragment, + tonemapping_pars_fragment: tonemapping_pars_fragment, + transmission_fragment: transmission_fragment, + transmission_pars_fragment: transmission_pars_fragment, + uv_pars_fragment: uv_pars_fragment, + uv_pars_vertex: uv_pars_vertex, + uv_vertex: uv_vertex, + worldpos_vertex: worldpos_vertex, - // uvs + background_vert: vertex$h, + background_frag: fragment$h, + backgroundCube_vert: vertex$g, + backgroundCube_frag: fragment$g, + cube_vert: vertex$f, + cube_frag: fragment$f, + depth_vert: vertex$e, + depth_frag: fragment$e, + distanceRGBA_vert: vertex$d, + distanceRGBA_frag: fragment$d, + equirect_vert: vertex$c, + equirect_frag: fragment$c, + linedashed_vert: vertex$b, + linedashed_frag: fragment$b, + meshbasic_vert: vertex$a, + meshbasic_frag: fragment$a, + meshlambert_vert: vertex$9, + meshlambert_frag: fragment$9, + meshmatcap_vert: vertex$8, + meshmatcap_frag: fragment$8, + meshnormal_vert: vertex$7, + meshnormal_frag: fragment$7, + meshphong_vert: vertex$6, + meshphong_frag: fragment$6, + meshphysical_vert: vertex$5, + meshphysical_frag: fragment$5, + meshtoon_vert: vertex$4, + meshtoon_frag: fragment$4, + points_vert: vertex$3, + points_frag: fragment$3, + shadow_vert: vertex$2, + shadow_frag: fragment$2, + sprite_vert: vertex$1, + sprite_frag: fragment$1 +}; - uv.x = ( vertices[ i ] / radius + 1 ) / 2; - uv.y = ( vertices[ i + 1 ] / radius + 1 ) / 2; +// Uniforms library for shared webgl shaders +const UniformsLib = { - uvs.push( uv.x, uv.y ); + common: { - } + diffuse: { value: /*@__PURE__*/ new Color( 0xffffff ) }, + opacity: { value: 1.0 }, - // indices + map: { value: null }, + mapTransform: { value: /*@__PURE__*/ new Matrix3() }, - for ( let i = 1; i <= segments; i ++ ) { + alphaMap: { value: null }, + alphaMapTransform: { value: /*@__PURE__*/ new Matrix3() }, - indices.push( i, i + 1, 0 ); + alphaTest: { value: 0 } - } + }, - // build geometry + specularmap: { - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + specularMap: { value: null }, + specularMapTransform: { value: /*@__PURE__*/ new Matrix3() } - } + }, - copy( source ) { + envmap: { - super.copy( source ); + envMap: { value: null }, + envMapRotation: { value: /*@__PURE__*/ new Matrix3() }, + flipEnvMap: { value: -1 }, + reflectivity: { value: 1.0 }, // basic, lambert, phong + ior: { value: 1.5 }, // physical + refractionRatio: { value: 0.98 }, // basic, lambert, phong - this.parameters = Object.assign( {}, source.parameters ); + }, - return this; + aomap: { - } + aoMap: { value: null }, + aoMapIntensity: { value: 1 }, + aoMapTransform: { value: /*@__PURE__*/ new Matrix3() } - static fromJSON( data ) { + }, - return new CircleGeometry( data.radius, data.segments, data.thetaStart, data.thetaLength ); + lightmap: { - } + lightMap: { value: null }, + lightMapIntensity: { value: 1 }, + lightMapTransform: { value: /*@__PURE__*/ new Matrix3() } -} + }, -class CylinderGeometry extends BufferGeometry { + bumpmap: { - constructor( radiusTop = 1, radiusBottom = 1, height = 1, radialSegments = 32, heightSegments = 1, openEnded = false, thetaStart = 0, thetaLength = Math.PI * 2 ) { + bumpMap: { value: null }, + bumpMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + bumpScale: { value: 1 } - super(); + }, - this.type = 'CylinderGeometry'; - - this.parameters = { - radiusTop: radiusTop, - radiusBottom: radiusBottom, - height: height, - radialSegments: radialSegments, - heightSegments: heightSegments, - openEnded: openEnded, - thetaStart: thetaStart, - thetaLength: thetaLength - }; + normalmap: { - const scope = this; + normalMap: { value: null }, + normalMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + normalScale: { value: /*@__PURE__*/ new Vector2( 1, 1 ) } - radialSegments = Math.floor( radialSegments ); - heightSegments = Math.floor( heightSegments ); + }, - // buffers + displacementmap: { - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; + displacementMap: { value: null }, + displacementMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + displacementScale: { value: 1 }, + displacementBias: { value: 0 } - // helper variables + }, - let index = 0; - const indexArray = []; - const halfHeight = height / 2; - let groupStart = 0; + emissivemap: { - // generate geometry + emissiveMap: { value: null }, + emissiveMapTransform: { value: /*@__PURE__*/ new Matrix3() } - generateTorso(); + }, - if ( openEnded === false ) { + metalnessmap: { - if ( radiusTop > 0 ) generateCap( true ); - if ( radiusBottom > 0 ) generateCap( false ); + metalnessMap: { value: null }, + metalnessMapTransform: { value: /*@__PURE__*/ new Matrix3() } - } + }, - // build geometry + roughnessmap: { - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + roughnessMap: { value: null }, + roughnessMapTransform: { value: /*@__PURE__*/ new Matrix3() } - function generateTorso() { + }, - const normal = new Vector3(); - const vertex = new Vector3(); + gradientmap: { - let groupCount = 0; + gradientMap: { value: null } - // this will be used to calculate the normal - const slope = ( radiusBottom - radiusTop ) / height; + }, - // generate vertices, normals and uvs + fog: { - for ( let y = 0; y <= heightSegments; y ++ ) { + fogDensity: { value: 0.00025 }, + fogNear: { value: 1 }, + fogFar: { value: 2000 }, + fogColor: { value: /*@__PURE__*/ new Color( 0xffffff ) } - const indexRow = []; + }, - const v = y / heightSegments; + lights: { - // calculate the radius of the current row + ambientLightColor: { value: [] }, - const radius = v * ( radiusBottom - radiusTop ) + radiusTop; + lightProbe: { value: [] }, - for ( let x = 0; x <= radialSegments; x ++ ) { + directionalLights: { value: [], properties: { + direction: {}, + color: {} + } }, - const u = x / radialSegments; + directionalLightShadows: { value: [], properties: { + shadowIntensity: 1, + shadowBias: {}, + shadowNormalBias: {}, + shadowRadius: {}, + shadowMapSize: {} + } }, - const theta = u * thetaLength + thetaStart; + directionalShadowMap: { value: [] }, + directionalShadowMatrix: { value: [] }, - const sinTheta = Math.sin( theta ); - const cosTheta = Math.cos( theta ); + spotLights: { value: [], properties: { + color: {}, + position: {}, + direction: {}, + distance: {}, + coneCos: {}, + penumbraCos: {}, + decay: {} + } }, - // vertex + spotLightShadows: { value: [], properties: { + shadowIntensity: 1, + shadowBias: {}, + shadowNormalBias: {}, + shadowRadius: {}, + shadowMapSize: {} + } }, - vertex.x = radius * sinTheta; - vertex.y = - v * height + halfHeight; - vertex.z = radius * cosTheta; - vertices.push( vertex.x, vertex.y, vertex.z ); + spotLightMap: { value: [] }, + spotShadowMap: { value: [] }, + spotLightMatrix: { value: [] }, - // normal + pointLights: { value: [], properties: { + color: {}, + position: {}, + decay: {}, + distance: {} + } }, - normal.set( sinTheta, slope, cosTheta ).normalize(); - normals.push( normal.x, normal.y, normal.z ); + pointLightShadows: { value: [], properties: { + shadowIntensity: 1, + shadowBias: {}, + shadowNormalBias: {}, + shadowRadius: {}, + shadowMapSize: {}, + shadowCameraNear: {}, + shadowCameraFar: {} + } }, - // uv + pointShadowMap: { value: [] }, + pointShadowMatrix: { value: [] }, - uvs.push( u, 1 - v ); + hemisphereLights: { value: [], properties: { + direction: {}, + skyColor: {}, + groundColor: {} + } }, - // save index of vertex in respective row + // TODO (abelnation): RectAreaLight BRDF data needs to be moved from example to main src + rectAreaLights: { value: [], properties: { + color: {}, + position: {}, + width: {}, + height: {} + } }, - indexRow.push( index ++ ); + ltc_1: { value: null }, + ltc_2: { value: null } - } + }, - // now save vertices of the row in our index array + points: { - indexArray.push( indexRow ); + diffuse: { value: /*@__PURE__*/ new Color( 0xffffff ) }, + opacity: { value: 1.0 }, + size: { value: 1.0 }, + scale: { value: 1.0 }, + map: { value: null }, + alphaMap: { value: null }, + alphaMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + alphaTest: { value: 0 }, + uvTransform: { value: /*@__PURE__*/ new Matrix3() } - } + }, - // generate indices + sprite: { - for ( let x = 0; x < radialSegments; x ++ ) { + diffuse: { value: /*@__PURE__*/ new Color( 0xffffff ) }, + opacity: { value: 1.0 }, + center: { value: /*@__PURE__*/ new Vector2( 0.5, 0.5 ) }, + rotation: { value: 0.0 }, + map: { value: null }, + mapTransform: { value: /*@__PURE__*/ new Matrix3() }, + alphaMap: { value: null }, + alphaMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + alphaTest: { value: 0 } - for ( let y = 0; y < heightSegments; y ++ ) { + } - // we use the index array to access the correct indices +}; - const a = indexArray[ y ][ x ]; - const b = indexArray[ y + 1 ][ x ]; - const c = indexArray[ y + 1 ][ x + 1 ]; - const d = indexArray[ y ][ x + 1 ]; +const ShaderLib = { - // faces + basic: { - indices.push( a, b, d ); - indices.push( b, c, d ); + uniforms: /*@__PURE__*/ mergeUniforms( [ + UniformsLib.common, + UniformsLib.specularmap, + UniformsLib.envmap, + UniformsLib.aomap, + UniformsLib.lightmap, + UniformsLib.fog + ] ), - // update group counter + vertexShader: ShaderChunk.meshbasic_vert, + fragmentShader: ShaderChunk.meshbasic_frag - groupCount += 6; + }, - } + lambert: { + uniforms: /*@__PURE__*/ mergeUniforms( [ + UniformsLib.common, + UniformsLib.specularmap, + UniformsLib.envmap, + UniformsLib.aomap, + UniformsLib.lightmap, + UniformsLib.emissivemap, + UniformsLib.bumpmap, + UniformsLib.normalmap, + UniformsLib.displacementmap, + UniformsLib.fog, + UniformsLib.lights, + { + emissive: { value: /*@__PURE__*/ new Color( 0x000000 ) } } + ] ), - // add a group to the geometry. this will ensure multi material support - - scope.addGroup( groupStart, groupCount, 0 ); - - // calculate new start value for groups - - groupStart += groupCount; - - } - - function generateCap( top ) { + vertexShader: ShaderChunk.meshlambert_vert, + fragmentShader: ShaderChunk.meshlambert_frag - // save the index of the first center vertex - const centerIndexStart = index; + }, - const uv = new Vector2(); - const vertex = new Vector3(); + phong: { - let groupCount = 0; + uniforms: /*@__PURE__*/ mergeUniforms( [ + UniformsLib.common, + UniformsLib.specularmap, + UniformsLib.envmap, + UniformsLib.aomap, + UniformsLib.lightmap, + UniformsLib.emissivemap, + UniformsLib.bumpmap, + UniformsLib.normalmap, + UniformsLib.displacementmap, + UniformsLib.fog, + UniformsLib.lights, + { + emissive: { value: /*@__PURE__*/ new Color( 0x000000 ) }, + specular: { value: /*@__PURE__*/ new Color( 0x111111 ) }, + shininess: { value: 30 } + } + ] ), - const radius = ( top === true ) ? radiusTop : radiusBottom; - const sign = ( top === true ) ? 1 : - 1; + vertexShader: ShaderChunk.meshphong_vert, + fragmentShader: ShaderChunk.meshphong_frag - // first we generate the center vertex data of the cap. - // because the geometry needs one set of uvs per face, - // we must generate a center vertex per face/segment + }, - for ( let x = 1; x <= radialSegments; x ++ ) { + standard: { - // vertex + uniforms: /*@__PURE__*/ mergeUniforms( [ + UniformsLib.common, + UniformsLib.envmap, + UniformsLib.aomap, + UniformsLib.lightmap, + UniformsLib.emissivemap, + UniformsLib.bumpmap, + UniformsLib.normalmap, + UniformsLib.displacementmap, + UniformsLib.roughnessmap, + UniformsLib.metalnessmap, + UniformsLib.fog, + UniformsLib.lights, + { + emissive: { value: /*@__PURE__*/ new Color( 0x000000 ) }, + roughness: { value: 1.0 }, + metalness: { value: 0.0 }, + envMapIntensity: { value: 1 } + } + ] ), - vertices.push( 0, halfHeight * sign, 0 ); + vertexShader: ShaderChunk.meshphysical_vert, + fragmentShader: ShaderChunk.meshphysical_frag - // normal + }, - normals.push( 0, sign, 0 ); + toon: { - // uv + uniforms: /*@__PURE__*/ mergeUniforms( [ + UniformsLib.common, + UniformsLib.aomap, + UniformsLib.lightmap, + UniformsLib.emissivemap, + UniformsLib.bumpmap, + UniformsLib.normalmap, + UniformsLib.displacementmap, + UniformsLib.gradientmap, + UniformsLib.fog, + UniformsLib.lights, + { + emissive: { value: /*@__PURE__*/ new Color( 0x000000 ) } + } + ] ), - uvs.push( 0.5, 0.5 ); + vertexShader: ShaderChunk.meshtoon_vert, + fragmentShader: ShaderChunk.meshtoon_frag - // increase index + }, - index ++; + matcap: { + uniforms: /*@__PURE__*/ mergeUniforms( [ + UniformsLib.common, + UniformsLib.bumpmap, + UniformsLib.normalmap, + UniformsLib.displacementmap, + UniformsLib.fog, + { + matcap: { value: null } } + ] ), - // save the index of the last center vertex - const centerIndexEnd = index; - - // now we generate the surrounding vertices, normals and uvs + vertexShader: ShaderChunk.meshmatcap_vert, + fragmentShader: ShaderChunk.meshmatcap_frag - for ( let x = 0; x <= radialSegments; x ++ ) { + }, - const u = x / radialSegments; - const theta = u * thetaLength + thetaStart; + points: { - const cosTheta = Math.cos( theta ); - const sinTheta = Math.sin( theta ); + uniforms: /*@__PURE__*/ mergeUniforms( [ + UniformsLib.points, + UniformsLib.fog + ] ), - // vertex + vertexShader: ShaderChunk.points_vert, + fragmentShader: ShaderChunk.points_frag - vertex.x = radius * sinTheta; - vertex.y = halfHeight * sign; - vertex.z = radius * cosTheta; - vertices.push( vertex.x, vertex.y, vertex.z ); + }, - // normal + dashed: { - normals.push( 0, sign, 0 ); + uniforms: /*@__PURE__*/ mergeUniforms( [ + UniformsLib.common, + UniformsLib.fog, + { + scale: { value: 1 }, + dashSize: { value: 1 }, + totalSize: { value: 2 } + } + ] ), - // uv + vertexShader: ShaderChunk.linedashed_vert, + fragmentShader: ShaderChunk.linedashed_frag - uv.x = ( cosTheta * 0.5 ) + 0.5; - uv.y = ( sinTheta * 0.5 * sign ) + 0.5; - uvs.push( uv.x, uv.y ); + }, - // increase index + depth: { - index ++; + uniforms: /*@__PURE__*/ mergeUniforms( [ + UniformsLib.common, + UniformsLib.displacementmap + ] ), - } + vertexShader: ShaderChunk.depth_vert, + fragmentShader: ShaderChunk.depth_frag - // generate indices + }, - for ( let x = 0; x < radialSegments; x ++ ) { + normal: { - const c = centerIndexStart + x; - const i = centerIndexEnd + x; + uniforms: /*@__PURE__*/ mergeUniforms( [ + UniformsLib.common, + UniformsLib.bumpmap, + UniformsLib.normalmap, + UniformsLib.displacementmap, + { + opacity: { value: 1.0 } + } + ] ), - if ( top === true ) { + vertexShader: ShaderChunk.meshnormal_vert, + fragmentShader: ShaderChunk.meshnormal_frag - // face top + }, - indices.push( i, i + 1, c ); + sprite: { - } else { + uniforms: /*@__PURE__*/ mergeUniforms( [ + UniformsLib.sprite, + UniformsLib.fog + ] ), - // face bottom + vertexShader: ShaderChunk.sprite_vert, + fragmentShader: ShaderChunk.sprite_frag - indices.push( i + 1, i, c ); + }, - } + background: { - groupCount += 3; + uniforms: { + uvTransform: { value: /*@__PURE__*/ new Matrix3() }, + t2D: { value: null }, + backgroundIntensity: { value: 1 } + }, - } + vertexShader: ShaderChunk.background_vert, + fragmentShader: ShaderChunk.background_frag - // add a group to the geometry. this will ensure multi material support + }, - scope.addGroup( groupStart, groupCount, top === true ? 1 : 2 ); + backgroundCube: { - // calculate new start value for groups + uniforms: { + envMap: { value: null }, + flipEnvMap: { value: -1 }, + backgroundBlurriness: { value: 0 }, + backgroundIntensity: { value: 1 }, + backgroundRotation: { value: /*@__PURE__*/ new Matrix3() } + }, - groupStart += groupCount; + vertexShader: ShaderChunk.backgroundCube_vert, + fragmentShader: ShaderChunk.backgroundCube_frag - } + }, - } + cube: { - copy( source ) { + uniforms: { + tCube: { value: null }, + tFlip: { value: -1 }, + opacity: { value: 1.0 } + }, - super.copy( source ); + vertexShader: ShaderChunk.cube_vert, + fragmentShader: ShaderChunk.cube_frag - this.parameters = Object.assign( {}, source.parameters ); + }, - return this; + equirect: { - } + uniforms: { + tEquirect: { value: null }, + }, - static fromJSON( data ) { + vertexShader: ShaderChunk.equirect_vert, + fragmentShader: ShaderChunk.equirect_frag - return new CylinderGeometry( data.radiusTop, data.radiusBottom, data.height, data.radialSegments, data.heightSegments, data.openEnded, data.thetaStart, data.thetaLength ); + }, - } + distanceRGBA: { -} + uniforms: /*@__PURE__*/ mergeUniforms( [ + UniformsLib.common, + UniformsLib.displacementmap, + { + referencePosition: { value: /*@__PURE__*/ new Vector3() }, + nearDistance: { value: 1 }, + farDistance: { value: 1000 } + } + ] ), -class ConeGeometry extends CylinderGeometry { + vertexShader: ShaderChunk.distanceRGBA_vert, + fragmentShader: ShaderChunk.distanceRGBA_frag - constructor( radius = 1, height = 1, radialSegments = 32, heightSegments = 1, openEnded = false, thetaStart = 0, thetaLength = Math.PI * 2 ) { + }, - super( 0, radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ); + shadow: { - this.type = 'ConeGeometry'; + uniforms: /*@__PURE__*/ mergeUniforms( [ + UniformsLib.lights, + UniformsLib.fog, + { + color: { value: /*@__PURE__*/ new Color( 0x00000 ) }, + opacity: { value: 1.0 } + }, + ] ), - this.parameters = { - radius: radius, - height: height, - radialSegments: radialSegments, - heightSegments: heightSegments, - openEnded: openEnded, - thetaStart: thetaStart, - thetaLength: thetaLength - }; + vertexShader: ShaderChunk.shadow_vert, + fragmentShader: ShaderChunk.shadow_frag } - static fromJSON( data ) { +}; - return new ConeGeometry( data.radius, data.height, data.radialSegments, data.heightSegments, data.openEnded, data.thetaStart, data.thetaLength ); +ShaderLib.physical = { - } + uniforms: /*@__PURE__*/ mergeUniforms( [ + ShaderLib.standard.uniforms, + { + clearcoat: { value: 0 }, + clearcoatMap: { value: null }, + clearcoatMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + clearcoatNormalMap: { value: null }, + clearcoatNormalMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + clearcoatNormalScale: { value: /*@__PURE__*/ new Vector2( 1, 1 ) }, + clearcoatRoughness: { value: 0 }, + clearcoatRoughnessMap: { value: null }, + clearcoatRoughnessMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + dispersion: { value: 0 }, + iridescence: { value: 0 }, + iridescenceMap: { value: null }, + iridescenceMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + iridescenceIOR: { value: 1.3 }, + iridescenceThicknessMinimum: { value: 100 }, + iridescenceThicknessMaximum: { value: 400 }, + iridescenceThicknessMap: { value: null }, + iridescenceThicknessMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + sheen: { value: 0 }, + sheenColor: { value: /*@__PURE__*/ new Color( 0x000000 ) }, + sheenColorMap: { value: null }, + sheenColorMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + sheenRoughness: { value: 1 }, + sheenRoughnessMap: { value: null }, + sheenRoughnessMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + transmission: { value: 0 }, + transmissionMap: { value: null }, + transmissionMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + transmissionSamplerSize: { value: /*@__PURE__*/ new Vector2() }, + transmissionSamplerMap: { value: null }, + thickness: { value: 0 }, + thicknessMap: { value: null }, + thicknessMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + attenuationDistance: { value: 0 }, + attenuationColor: { value: /*@__PURE__*/ new Color( 0x000000 ) }, + specularColor: { value: /*@__PURE__*/ new Color( 1, 1, 1 ) }, + specularColorMap: { value: null }, + specularColorMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + specularIntensity: { value: 1 }, + specularIntensityMap: { value: null }, + specularIntensityMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + anisotropyVector: { value: /*@__PURE__*/ new Vector2() }, + anisotropyMap: { value: null }, + anisotropyMapTransform: { value: /*@__PURE__*/ new Matrix3() }, + } + ] ), -} + vertexShader: ShaderChunk.meshphysical_vert, + fragmentShader: ShaderChunk.meshphysical_frag -class PolyhedronGeometry extends BufferGeometry { +}; - constructor( vertices = [], indices = [], radius = 1, detail = 0 ) { +const _rgb = { r: 0, b: 0, g: 0 }; +const _e1$1 = /*@__PURE__*/ new Euler(); +const _m1$1 = /*@__PURE__*/ new Matrix4(); - super(); +function WebGLBackground( renderer, cubemaps, cubeuvmaps, state, objects, alpha, premultipliedAlpha ) { - this.type = 'PolyhedronGeometry'; + const clearColor = new Color( 0x000000 ); + let clearAlpha = alpha === true ? 0 : 1; - this.parameters = { - vertices: vertices, - indices: indices, - radius: radius, - detail: detail - }; + let planeMesh; + let boxMesh; - // default buffer data + let currentBackground = null; + let currentBackgroundVersion = 0; + let currentTonemapping = null; - const vertexBuffer = []; - const uvBuffer = []; + function getBackground( scene ) { - // the subdivision creates the vertex buffer data + let background = scene.isScene === true ? scene.background : null; - subdivide( detail ); + if ( background && background.isTexture ) { - // all vertices should lie on a conceptual sphere with a given radius + const usePMREM = scene.backgroundBlurriness > 0; // use PMREM if the user wants to blur the background + background = ( usePMREM ? cubeuvmaps : cubemaps ).get( background ); - applyRadius( radius ); + } - // finally, create the uv data + return background; - generateUVs(); + } - // build non-indexed geometry + function render( scene ) { - this.setAttribute( 'position', new Float32BufferAttribute( vertexBuffer, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( vertexBuffer.slice(), 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvBuffer, 2 ) ); + let forceClear = false; + const background = getBackground( scene ); - if ( detail === 0 ) { + if ( background === null ) { - this.computeVertexNormals(); // flat normals + setClear( clearColor, clearAlpha ); - } else { + } else if ( background && background.isColor ) { - this.normalizeNormals(); // smooth normals + setClear( background, 1 ); + forceClear = true; } - // helper functions + const environmentBlendMode = renderer.xr.getEnvironmentBlendMode(); - function subdivide( detail ) { + if ( environmentBlendMode === 'additive' ) { - const a = new Vector3(); - const b = new Vector3(); - const c = new Vector3(); + state.buffers.color.setClear( 0, 0, 0, 1, premultipliedAlpha ); - // iterate over all faces and apply a subdivision with the given detail value + } else if ( environmentBlendMode === 'alpha-blend' ) { - for ( let i = 0; i < indices.length; i += 3 ) { + state.buffers.color.setClear( 0, 0, 0, 0, premultipliedAlpha ); - // get the vertices of the face + } - getVertexByIndex( indices[ i + 0 ], a ); - getVertexByIndex( indices[ i + 1 ], b ); - getVertexByIndex( indices[ i + 2 ], c ); + if ( renderer.autoClear || forceClear ) { - // perform subdivision + // buffers might not be writable which is required to ensure a correct clear - subdivideFace( a, b, c, detail ); + state.buffers.depth.setTest( true ); + state.buffers.depth.setMask( true ); + state.buffers.color.setMask( true ); - } + renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil ); } - function subdivideFace( a, b, c, detail ) { - - const cols = detail + 1; + } - // we use this multidimensional array as a data structure for creating the subdivision + function addToRenderList( renderList, scene ) { - const v = []; + const background = getBackground( scene ); - // construct all of the vertices for this subdivision + if ( background && ( background.isCubeTexture || background.mapping === CubeUVReflectionMapping ) ) { - for ( let i = 0; i <= cols; i ++ ) { + if ( boxMesh === undefined ) { - v[ i ] = []; + boxMesh = new Mesh( + new BoxGeometry( 1, 1, 1 ), + new ShaderMaterial( { + name: 'BackgroundCubeMaterial', + uniforms: cloneUniforms( ShaderLib.backgroundCube.uniforms ), + vertexShader: ShaderLib.backgroundCube.vertexShader, + fragmentShader: ShaderLib.backgroundCube.fragmentShader, + side: BackSide, + depthTest: false, + depthWrite: false, + fog: false, + allowOverride: false + } ) + ); - const aj = a.clone().lerp( c, i / cols ); - const bj = b.clone().lerp( c, i / cols ); + boxMesh.geometry.deleteAttribute( 'normal' ); + boxMesh.geometry.deleteAttribute( 'uv' ); - const rows = cols - i; + boxMesh.onBeforeRender = function ( renderer, scene, camera ) { - for ( let j = 0; j <= rows; j ++ ) { + this.matrixWorld.copyPosition( camera.matrixWorld ); - if ( j === 0 && i === cols ) { + }; - v[ i ][ j ] = aj; + // add "envMap" material property so the renderer can evaluate it like for built-in materials + Object.defineProperty( boxMesh.material, 'envMap', { - } else { + get: function () { - v[ i ][ j ] = aj.clone().lerp( bj, j / rows ); + return this.uniforms.envMap.value; } - } + } ); - } + objects.update( boxMesh ); - // construct all of the faces + } - for ( let i = 0; i < cols; i ++ ) { + _e1$1.copy( scene.backgroundRotation ); - for ( let j = 0; j < 2 * ( cols - i ) - 1; j ++ ) { + // accommodate left-handed frame + _e1$1.x *= -1; _e1$1.y *= -1; _e1$1.z *= -1; - const k = Math.floor( j / 2 ); + if ( background.isCubeTexture && background.isRenderTargetTexture === false ) { - if ( j % 2 === 0 ) { + // environment maps which are not cube render targets or PMREMs follow a different convention + _e1$1.y *= -1; + _e1$1.z *= -1; - pushVertex( v[ i ][ k + 1 ] ); - pushVertex( v[ i + 1 ][ k ] ); - pushVertex( v[ i ][ k ] ); + } - } else { + boxMesh.material.uniforms.envMap.value = background; + boxMesh.material.uniforms.flipEnvMap.value = ( background.isCubeTexture && background.isRenderTargetTexture === false ) ? -1 : 1; + boxMesh.material.uniforms.backgroundBlurriness.value = scene.backgroundBlurriness; + boxMesh.material.uniforms.backgroundIntensity.value = scene.backgroundIntensity; + boxMesh.material.uniforms.backgroundRotation.value.setFromMatrix4( _m1$1.makeRotationFromEuler( _e1$1 ) ); + boxMesh.material.toneMapped = ColorManagement.getTransfer( background.colorSpace ) !== SRGBTransfer; - pushVertex( v[ i ][ k + 1 ] ); - pushVertex( v[ i + 1 ][ k + 1 ] ); - pushVertex( v[ i + 1 ][ k ] ); + if ( currentBackground !== background || + currentBackgroundVersion !== background.version || + currentTonemapping !== renderer.toneMapping ) { - } + boxMesh.material.needsUpdate = true; - } + currentBackground = background; + currentBackgroundVersion = background.version; + currentTonemapping = renderer.toneMapping; } - } + boxMesh.layers.enableAll(); + + // push to the pre-sorted opaque render list + renderList.unshift( boxMesh, boxMesh.geometry, boxMesh.material, 0, 0, null ); - function applyRadius( radius ) { + } else if ( background && background.isTexture ) { - const vertex = new Vector3(); + if ( planeMesh === undefined ) { - // iterate over the entire buffer and apply the radius to each vertex + planeMesh = new Mesh( + new PlaneGeometry( 2, 2 ), + new ShaderMaterial( { + name: 'BackgroundMaterial', + uniforms: cloneUniforms( ShaderLib.background.uniforms ), + vertexShader: ShaderLib.background.vertexShader, + fragmentShader: ShaderLib.background.fragmentShader, + side: FrontSide, + depthTest: false, + depthWrite: false, + fog: false, + allowOverride: false + } ) + ); - for ( let i = 0; i < vertexBuffer.length; i += 3 ) { + planeMesh.geometry.deleteAttribute( 'normal' ); - vertex.x = vertexBuffer[ i + 0 ]; - vertex.y = vertexBuffer[ i + 1 ]; - vertex.z = vertexBuffer[ i + 2 ]; + // add "map" material property so the renderer can evaluate it like for built-in materials + Object.defineProperty( planeMesh.material, 'map', { - vertex.normalize().multiplyScalar( radius ); + get: function () { - vertexBuffer[ i + 0 ] = vertex.x; - vertexBuffer[ i + 1 ] = vertex.y; - vertexBuffer[ i + 2 ] = vertex.z; + return this.uniforms.t2D.value; - } + } - } + } ); - function generateUVs() { + objects.update( planeMesh ); - const vertex = new Vector3(); + } - for ( let i = 0; i < vertexBuffer.length; i += 3 ) { + planeMesh.material.uniforms.t2D.value = background; + planeMesh.material.uniforms.backgroundIntensity.value = scene.backgroundIntensity; + planeMesh.material.toneMapped = ColorManagement.getTransfer( background.colorSpace ) !== SRGBTransfer; - vertex.x = vertexBuffer[ i + 0 ]; - vertex.y = vertexBuffer[ i + 1 ]; - vertex.z = vertexBuffer[ i + 2 ]; + if ( background.matrixAutoUpdate === true ) { - const u = azimuth( vertex ) / 2 / Math.PI + 0.5; - const v = inclination( vertex ) / Math.PI + 0.5; - uvBuffer.push( u, 1 - v ); + background.updateMatrix(); } - correctUVs(); + planeMesh.material.uniforms.uvTransform.value.copy( background.matrix ); - correctSeam(); + if ( currentBackground !== background || + currentBackgroundVersion !== background.version || + currentTonemapping !== renderer.toneMapping ) { - } + planeMesh.material.needsUpdate = true; - function correctSeam() { + currentBackground = background; + currentBackgroundVersion = background.version; + currentTonemapping = renderer.toneMapping; - // handle case when face straddles the seam, see #3269 + } - for ( let i = 0; i < uvBuffer.length; i += 6 ) { + planeMesh.layers.enableAll(); - // uv data of a single face + // push to the pre-sorted opaque render list + renderList.unshift( planeMesh, planeMesh.geometry, planeMesh.material, 0, 0, null ); - const x0 = uvBuffer[ i + 0 ]; - const x1 = uvBuffer[ i + 2 ]; - const x2 = uvBuffer[ i + 4 ]; + } - const max = Math.max( x0, x1, x2 ); - const min = Math.min( x0, x1, x2 ); + } - // 0.9 is somewhat arbitrary + function setClear( color, alpha ) { - if ( max > 0.9 && min < 0.1 ) { + color.getRGB( _rgb, getUnlitUniformColorSpace( renderer ) ); - if ( x0 < 0.2 ) uvBuffer[ i + 0 ] += 1; - if ( x1 < 0.2 ) uvBuffer[ i + 2 ] += 1; - if ( x2 < 0.2 ) uvBuffer[ i + 4 ] += 1; + state.buffers.color.setClear( _rgb.r, _rgb.g, _rgb.b, alpha, premultipliedAlpha ); - } + } - } + function dispose() { - } + if ( boxMesh !== undefined ) { - function pushVertex( vertex ) { + boxMesh.geometry.dispose(); + boxMesh.material.dispose(); - vertexBuffer.push( vertex.x, vertex.y, vertex.z ); + boxMesh = undefined; } - function getVertexByIndex( index, vertex ) { + if ( planeMesh !== undefined ) { - const stride = index * 3; + planeMesh.geometry.dispose(); + planeMesh.material.dispose(); - vertex.x = vertices[ stride + 0 ]; - vertex.y = vertices[ stride + 1 ]; - vertex.z = vertices[ stride + 2 ]; + planeMesh = undefined; } - function correctUVs() { - - const a = new Vector3(); - const b = new Vector3(); - const c = new Vector3(); + } - const centroid = new Vector3(); + return { - const uvA = new Vector2(); - const uvB = new Vector2(); - const uvC = new Vector2(); + getClearColor: function () { - for ( let i = 0, j = 0; i < vertexBuffer.length; i += 9, j += 6 ) { + return clearColor; - a.set( vertexBuffer[ i + 0 ], vertexBuffer[ i + 1 ], vertexBuffer[ i + 2 ] ); - b.set( vertexBuffer[ i + 3 ], vertexBuffer[ i + 4 ], vertexBuffer[ i + 5 ] ); - c.set( vertexBuffer[ i + 6 ], vertexBuffer[ i + 7 ], vertexBuffer[ i + 8 ] ); + }, + setClearColor: function ( color, alpha = 1 ) { - uvA.set( uvBuffer[ j + 0 ], uvBuffer[ j + 1 ] ); - uvB.set( uvBuffer[ j + 2 ], uvBuffer[ j + 3 ] ); - uvC.set( uvBuffer[ j + 4 ], uvBuffer[ j + 5 ] ); + clearColor.set( color ); + clearAlpha = alpha; + setClear( clearColor, clearAlpha ); - centroid.copy( a ).add( b ).add( c ).divideScalar( 3 ); + }, + getClearAlpha: function () { - const azi = azimuth( centroid ); + return clearAlpha; - correctUV( uvA, j + 0, a, azi ); - correctUV( uvB, j + 2, b, azi ); - correctUV( uvC, j + 4, c, azi ); + }, + setClearAlpha: function ( alpha ) { - } + clearAlpha = alpha; + setClear( clearColor, clearAlpha ); - } + }, + render: render, + addToRenderList: addToRenderList, + dispose: dispose - function correctUV( uv, stride, vector, azimuth ) { + }; - if ( ( azimuth < 0 ) && ( uv.x === 1 ) ) { +} - uvBuffer[ stride ] = uv.x - 1; +function WebGLBindingStates( gl, attributes ) { - } + const maxVertexAttributes = gl.getParameter( gl.MAX_VERTEX_ATTRIBS ); - if ( ( vector.x === 0 ) && ( vector.z === 0 ) ) { + const bindingStates = {}; - uvBuffer[ stride ] = azimuth / 2 / Math.PI + 0.5; + const defaultState = createBindingState( null ); + let currentState = defaultState; + let forceUpdate = false; - } + function setup( object, material, program, geometry, index ) { - } + let updateBuffers = false; - // Angle around the Y axis, counter-clockwise when looking from above. + const state = getBindingState( geometry, program, material ); - function azimuth( vector ) { + if ( currentState !== state ) { - return Math.atan2( vector.z, - vector.x ); + currentState = state; + bindVertexArrayObject( currentState.object ); } + updateBuffers = needsUpdate( object, geometry, program, index ); - // Angle above the XZ plane. + if ( updateBuffers ) saveCache( object, geometry, program, index ); - function inclination( vector ) { + if ( index !== null ) { - return Math.atan2( - vector.y, Math.sqrt( ( vector.x * vector.x ) + ( vector.z * vector.z ) ) ); + attributes.update( index, gl.ELEMENT_ARRAY_BUFFER ); } - } - - copy( source ) { + if ( updateBuffers || forceUpdate ) { - super.copy( source ); + forceUpdate = false; - this.parameters = Object.assign( {}, source.parameters ); + setupVertexAttributes( object, material, program, geometry ); - return this; + if ( index !== null ) { - } + gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, attributes.get( index ).buffer ); - static fromJSON( data ) { + } - return new PolyhedronGeometry( data.vertices, data.indices, data.radius, data.details ); + } } -} - -class DodecahedronGeometry extends PolyhedronGeometry { - - constructor( radius = 1, detail = 0 ) { - - const t = ( 1 + Math.sqrt( 5 ) ) / 2; - const r = 1 / t; - - const vertices = [ - - // (±1, ±1, ±1) - - 1, - 1, - 1, - 1, - 1, 1, - - 1, 1, - 1, - 1, 1, 1, - 1, - 1, - 1, 1, - 1, 1, - 1, 1, - 1, 1, 1, 1, - - // (0, ±1/φ, ±φ) - 0, - r, - t, 0, - r, t, - 0, r, - t, 0, r, t, - - // (±1/φ, ±φ, 0) - - r, - t, 0, - r, t, 0, - r, - t, 0, r, t, 0, - - // (±φ, 0, ±1/φ) - - t, 0, - r, t, 0, - r, - - t, 0, r, t, 0, r - ]; + function createVertexArrayObject() { - const indices = [ - 3, 11, 7, 3, 7, 15, 3, 15, 13, - 7, 19, 17, 7, 17, 6, 7, 6, 15, - 17, 4, 8, 17, 8, 10, 17, 10, 6, - 8, 0, 16, 8, 16, 2, 8, 2, 10, - 0, 12, 1, 0, 1, 18, 0, 18, 16, - 6, 10, 2, 6, 2, 13, 6, 13, 15, - 2, 16, 18, 2, 18, 3, 2, 3, 13, - 18, 1, 9, 18, 9, 11, 18, 11, 3, - 4, 14, 12, 4, 12, 0, 4, 0, 8, - 11, 9, 5, 11, 5, 19, 11, 19, 7, - 19, 5, 14, 19, 14, 4, 19, 4, 17, - 1, 12, 14, 1, 14, 5, 1, 5, 9 - ]; + return gl.createVertexArray(); - super( vertices, indices, radius, detail ); + } - this.type = 'DodecahedronGeometry'; + function bindVertexArrayObject( vao ) { - this.parameters = { - radius: radius, - detail: detail - }; + return gl.bindVertexArray( vao ); } - static fromJSON( data ) { + function deleteVertexArrayObject( vao ) { - return new DodecahedronGeometry( data.radius, data.detail ); + return gl.deleteVertexArray( vao ); } -} - -const _v0 = /*@__PURE__*/ new Vector3(); -const _v1$1 = /*@__PURE__*/ new Vector3(); -const _normal = /*@__PURE__*/ new Vector3(); -const _triangle = /*@__PURE__*/ new Triangle(); + function getBindingState( geometry, program, material ) { -class EdgesGeometry extends BufferGeometry { + const wireframe = ( material.wireframe === true ); - constructor( geometry = null, thresholdAngle = 1 ) { + let programMap = bindingStates[ geometry.id ]; - super(); + if ( programMap === undefined ) { - this.type = 'EdgesGeometry'; + programMap = {}; + bindingStates[ geometry.id ] = programMap; - this.parameters = { - geometry: geometry, - thresholdAngle: thresholdAngle - }; + } - if ( geometry !== null ) { + let stateMap = programMap[ program.id ]; - const precisionPoints = 4; - const precision = Math.pow( 10, precisionPoints ); - const thresholdDot = Math.cos( DEG2RAD * thresholdAngle ); + if ( stateMap === undefined ) { - const indexAttr = geometry.getIndex(); - const positionAttr = geometry.getAttribute( 'position' ); - const indexCount = indexAttr ? indexAttr.count : positionAttr.count; + stateMap = {}; + programMap[ program.id ] = stateMap; - const indexArr = [ 0, 0, 0 ]; - const vertKeys = [ 'a', 'b', 'c' ]; - const hashes = new Array( 3 ); + } - const edgeData = {}; - const vertices = []; - for ( let i = 0; i < indexCount; i += 3 ) { + let state = stateMap[ wireframe ]; - if ( indexAttr ) { + if ( state === undefined ) { - indexArr[ 0 ] = indexAttr.getX( i ); - indexArr[ 1 ] = indexAttr.getX( i + 1 ); - indexArr[ 2 ] = indexAttr.getX( i + 2 ); + state = createBindingState( createVertexArrayObject() ); + stateMap[ wireframe ] = state; - } else { + } - indexArr[ 0 ] = i; - indexArr[ 1 ] = i + 1; - indexArr[ 2 ] = i + 2; + return state; - } + } - const { a, b, c } = _triangle; - a.fromBufferAttribute( positionAttr, indexArr[ 0 ] ); - b.fromBufferAttribute( positionAttr, indexArr[ 1 ] ); - c.fromBufferAttribute( positionAttr, indexArr[ 2 ] ); - _triangle.getNormal( _normal ); + function createBindingState( vao ) { - // create hashes for the edge from the vertices - hashes[ 0 ] = `${ Math.round( a.x * precision ) },${ Math.round( a.y * precision ) },${ Math.round( a.z * precision ) }`; - hashes[ 1 ] = `${ Math.round( b.x * precision ) },${ Math.round( b.y * precision ) },${ Math.round( b.z * precision ) }`; - hashes[ 2 ] = `${ Math.round( c.x * precision ) },${ Math.round( c.y * precision ) },${ Math.round( c.z * precision ) }`; + const newAttributes = []; + const enabledAttributes = []; + const attributeDivisors = []; - // skip degenerate triangles - if ( hashes[ 0 ] === hashes[ 1 ] || hashes[ 1 ] === hashes[ 2 ] || hashes[ 2 ] === hashes[ 0 ] ) { + for ( let i = 0; i < maxVertexAttributes; i ++ ) { - continue; + newAttributes[ i ] = 0; + enabledAttributes[ i ] = 0; + attributeDivisors[ i ] = 0; - } + } - // iterate over every edge - for ( let j = 0; j < 3; j ++ ) { + return { - // get the first and next vertex making up the edge - const jNext = ( j + 1 ) % 3; - const vecHash0 = hashes[ j ]; - const vecHash1 = hashes[ jNext ]; - const v0 = _triangle[ vertKeys[ j ] ]; - const v1 = _triangle[ vertKeys[ jNext ] ]; + // for backward compatibility on non-VAO support browser + geometry: null, + program: null, + wireframe: false, - const hash = `${ vecHash0 }_${ vecHash1 }`; - const reverseHash = `${ vecHash1 }_${ vecHash0 }`; + newAttributes: newAttributes, + enabledAttributes: enabledAttributes, + attributeDivisors: attributeDivisors, + object: vao, + attributes: {}, + index: null - if ( reverseHash in edgeData && edgeData[ reverseHash ] ) { + }; - // if we found a sibling edge add it into the vertex array if - // it meets the angle threshold and delete the edge from the map. - if ( _normal.dot( edgeData[ reverseHash ].normal ) <= thresholdDot ) { + } - vertices.push( v0.x, v0.y, v0.z ); - vertices.push( v1.x, v1.y, v1.z ); + function needsUpdate( object, geometry, program, index ) { - } + const cachedAttributes = currentState.attributes; + const geometryAttributes = geometry.attributes; - edgeData[ reverseHash ] = null; + let attributesNum = 0; - } else if ( ! ( hash in edgeData ) ) { + const programAttributes = program.getAttributes(); - // if we've already got an edge here then skip adding a new one - edgeData[ hash ] = { + for ( const name in programAttributes ) { - index0: indexArr[ j ], - index1: indexArr[ jNext ], - normal: _normal.clone(), + const programAttribute = programAttributes[ name ]; - }; + if ( programAttribute.location >= 0 ) { - } + const cachedAttribute = cachedAttributes[ name ]; + let geometryAttribute = geometryAttributes[ name ]; - } + if ( geometryAttribute === undefined ) { - } + if ( name === 'instanceMatrix' && object.instanceMatrix ) geometryAttribute = object.instanceMatrix; + if ( name === 'instanceColor' && object.instanceColor ) geometryAttribute = object.instanceColor; - // iterate over all remaining, unmatched edges and add them to the vertex array - for ( const key in edgeData ) { + } - if ( edgeData[ key ] ) { + if ( cachedAttribute === undefined ) return true; - const { index0, index1 } = edgeData[ key ]; - _v0.fromBufferAttribute( positionAttr, index0 ); - _v1$1.fromBufferAttribute( positionAttr, index1 ); + if ( cachedAttribute.attribute !== geometryAttribute ) return true; - vertices.push( _v0.x, _v0.y, _v0.z ); - vertices.push( _v1$1.x, _v1$1.y, _v1$1.z ); + if ( geometryAttribute && cachedAttribute.data !== geometryAttribute.data ) return true; - } + attributesNum ++; } - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - } - } - - copy( source ) { - - super.copy( source ); + if ( currentState.attributesNum !== attributesNum ) return true; - this.parameters = Object.assign( {}, source.parameters ); + if ( currentState.index !== index ) return true; - return this; + return false; } -} + function saveCache( object, geometry, program, index ) { -class Shape extends Path { + const cache = {}; + const attributes = geometry.attributes; + let attributesNum = 0; - constructor( points ) { + const programAttributes = program.getAttributes(); - super( points ); + for ( const name in programAttributes ) { - this.uuid = generateUUID(); + const programAttribute = programAttributes[ name ]; - this.type = 'Shape'; + if ( programAttribute.location >= 0 ) { - this.holes = []; + let attribute = attributes[ name ]; - } + if ( attribute === undefined ) { - getPointsHoles( divisions ) { + if ( name === 'instanceMatrix' && object.instanceMatrix ) attribute = object.instanceMatrix; + if ( name === 'instanceColor' && object.instanceColor ) attribute = object.instanceColor; - const holesPts = []; + } - for ( let i = 0, l = this.holes.length; i < l; i ++ ) { + const data = {}; + data.attribute = attribute; - holesPts[ i ] = this.holes[ i ].getPoints( divisions ); + if ( attribute && attribute.data ) { - } + data.data = attribute.data; - return holesPts; + } - } + cache[ name ] = data; - // get points of shape and holes (keypoints based on segments parameter) + attributesNum ++; - extractPoints( divisions ) { + } - return { + } - shape: this.getPoints( divisions ), - holes: this.getPointsHoles( divisions ) + currentState.attributes = cache; + currentState.attributesNum = attributesNum; - }; + currentState.index = index; } - copy( source ) { + function initAttributes() { - super.copy( source ); + const newAttributes = currentState.newAttributes; - this.holes = []; + for ( let i = 0, il = newAttributes.length; i < il; i ++ ) { - for ( let i = 0, l = source.holes.length; i < l; i ++ ) { + newAttributes[ i ] = 0; - const hole = source.holes[ i ]; + } - this.holes.push( hole.clone() ); + } - } + function enableAttribute( attribute ) { - return this; + enableAttributeAndDivisor( attribute, 0 ); } - toJSON() { + function enableAttributeAndDivisor( attribute, meshPerAttribute ) { - const data = super.toJSON(); + const newAttributes = currentState.newAttributes; + const enabledAttributes = currentState.enabledAttributes; + const attributeDivisors = currentState.attributeDivisors; - data.uuid = this.uuid; - data.holes = []; + newAttributes[ attribute ] = 1; - for ( let i = 0, l = this.holes.length; i < l; i ++ ) { + if ( enabledAttributes[ attribute ] === 0 ) { - const hole = this.holes[ i ]; - data.holes.push( hole.toJSON() ); + gl.enableVertexAttribArray( attribute ); + enabledAttributes[ attribute ] = 1; } - return data; + if ( attributeDivisors[ attribute ] !== meshPerAttribute ) { + + gl.vertexAttribDivisor( attribute, meshPerAttribute ); + attributeDivisors[ attribute ] = meshPerAttribute; + + } } - fromJSON( json ) { + function disableUnusedAttributes() { + + const newAttributes = currentState.newAttributes; + const enabledAttributes = currentState.enabledAttributes; - super.fromJSON( json ); + for ( let i = 0, il = enabledAttributes.length; i < il; i ++ ) { - this.uuid = json.uuid; - this.holes = []; + if ( enabledAttributes[ i ] !== newAttributes[ i ] ) { - for ( let i = 0, l = json.holes.length; i < l; i ++ ) { + gl.disableVertexAttribArray( i ); + enabledAttributes[ i ] = 0; - const hole = json.holes[ i ]; - this.holes.push( new Path().fromJSON( hole ) ); + } } - return this; - } -} + function vertexAttribPointer( index, size, type, normalized, stride, offset, integer ) { -/** - * Port from https://github.com/mapbox/earcut (v2.2.4) - */ + if ( integer === true ) { -const Earcut = { + gl.vertexAttribIPointer( index, size, type, stride, offset ); - triangulate: function ( data, holeIndices, dim = 2 ) { + } else { - const hasHoles = holeIndices && holeIndices.length; - const outerLen = hasHoles ? holeIndices[ 0 ] * dim : data.length; - let outerNode = linkedList( data, 0, outerLen, dim, true ); - const triangles = []; + gl.vertexAttribPointer( index, size, type, normalized, stride, offset ); - if ( ! outerNode || outerNode.next === outerNode.prev ) return triangles; + } - let minX, minY, maxX, maxY, x, y, invSize; + } - if ( hasHoles ) outerNode = eliminateHoles( data, holeIndices, outerNode, dim ); + function setupVertexAttributes( object, material, program, geometry ) { - // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox - if ( data.length > 80 * dim ) { + initAttributes(); - minX = maxX = data[ 0 ]; - minY = maxY = data[ 1 ]; + const geometryAttributes = geometry.attributes; - for ( let i = dim; i < outerLen; i += dim ) { + const programAttributes = program.getAttributes(); - x = data[ i ]; - y = data[ i + 1 ]; - if ( x < minX ) minX = x; - if ( y < minY ) minY = y; - if ( x > maxX ) maxX = x; - if ( y > maxY ) maxY = y; + const materialDefaultAttributeValues = material.defaultAttributeValues; - } + for ( const name in programAttributes ) { - // minX, minY and invSize are later used to transform coords into integers for z-order calculation - invSize = Math.max( maxX - minX, maxY - minY ); - invSize = invSize !== 0 ? 32767 / invSize : 0; + const programAttribute = programAttributes[ name ]; - } + if ( programAttribute.location >= 0 ) { - earcutLinked( outerNode, triangles, dim, minX, minY, invSize, 0 ); + let geometryAttribute = geometryAttributes[ name ]; - return triangles; + if ( geometryAttribute === undefined ) { - } + if ( name === 'instanceMatrix' && object.instanceMatrix ) geometryAttribute = object.instanceMatrix; + if ( name === 'instanceColor' && object.instanceColor ) geometryAttribute = object.instanceColor; -}; + } -// create a circular doubly linked list from polygon points in the specified winding order -function linkedList( data, start, end, dim, clockwise ) { + if ( geometryAttribute !== undefined ) { - let i, last; + const normalized = geometryAttribute.normalized; + const size = geometryAttribute.itemSize; - if ( clockwise === ( signedArea( data, start, end, dim ) > 0 ) ) { + const attribute = attributes.get( geometryAttribute ); - for ( i = start; i < end; i += dim ) last = insertNode( i, data[ i ], data[ i + 1 ], last ); + // TODO Attribute may not be available on context restore - } else { + if ( attribute === undefined ) continue; - for ( i = end - dim; i >= start; i -= dim ) last = insertNode( i, data[ i ], data[ i + 1 ], last ); + const buffer = attribute.buffer; + const type = attribute.type; + const bytesPerElement = attribute.bytesPerElement; - } + // check for integer attributes - if ( last && equals( last, last.next ) ) { + const integer = ( type === gl.INT || type === gl.UNSIGNED_INT || geometryAttribute.gpuType === IntType ); - removeNode( last ); - last = last.next; + if ( geometryAttribute.isInterleavedBufferAttribute ) { - } + const data = geometryAttribute.data; + const stride = data.stride; + const offset = geometryAttribute.offset; - return last; + if ( data.isInstancedInterleavedBuffer ) { -} + for ( let i = 0; i < programAttribute.locationSize; i ++ ) { -// eliminate colinear or duplicate points -function filterPoints( start, end ) { + enableAttributeAndDivisor( programAttribute.location + i, data.meshPerAttribute ); - if ( ! start ) return start; - if ( ! end ) end = start; + } - let p = start, - again; - do { + if ( object.isInstancedMesh !== true && geometry._maxInstanceCount === undefined ) { - again = false; + geometry._maxInstanceCount = data.meshPerAttribute * data.count; - if ( ! p.steiner && ( equals( p, p.next ) || area( p.prev, p, p.next ) === 0 ) ) { + } - removeNode( p ); - p = end = p.prev; - if ( p === p.next ) break; - again = true; + } else { - } else { + for ( let i = 0; i < programAttribute.locationSize; i ++ ) { - p = p.next; + enableAttribute( programAttribute.location + i ); - } + } - } while ( again || p !== end ); + } - return end; + gl.bindBuffer( gl.ARRAY_BUFFER, buffer ); -} + for ( let i = 0; i < programAttribute.locationSize; i ++ ) { -// main ear slicing loop which triangulates a polygon (given as a linked list) -function earcutLinked( ear, triangles, dim, minX, minY, invSize, pass ) { + vertexAttribPointer( + programAttribute.location + i, + size / programAttribute.locationSize, + type, + normalized, + stride * bytesPerElement, + ( offset + ( size / programAttribute.locationSize ) * i ) * bytesPerElement, + integer + ); - if ( ! ear ) return; + } - // interlink polygon nodes in z-order - if ( ! pass && invSize ) indexCurve( ear, minX, minY, invSize ); + } else { - let stop = ear, - prev, next; + if ( geometryAttribute.isInstancedBufferAttribute ) { - // iterate through ears, slicing them one by one - while ( ear.prev !== ear.next ) { + for ( let i = 0; i < programAttribute.locationSize; i ++ ) { - prev = ear.prev; - next = ear.next; + enableAttributeAndDivisor( programAttribute.location + i, geometryAttribute.meshPerAttribute ); - if ( invSize ? isEarHashed( ear, minX, minY, invSize ) : isEar( ear ) ) { + } - // cut off the triangle - triangles.push( prev.i / dim | 0 ); - triangles.push( ear.i / dim | 0 ); - triangles.push( next.i / dim | 0 ); + if ( object.isInstancedMesh !== true && geometry._maxInstanceCount === undefined ) { - removeNode( ear ); + geometry._maxInstanceCount = geometryAttribute.meshPerAttribute * geometryAttribute.count; - // skipping the next vertex leads to less sliver triangles - ear = next.next; - stop = next.next; + } - continue; + } else { - } + for ( let i = 0; i < programAttribute.locationSize; i ++ ) { - ear = next; + enableAttribute( programAttribute.location + i ); - // if we looped through the whole remaining polygon and can't find any more ears - if ( ear === stop ) { + } - // try filtering points and slicing again - if ( ! pass ) { + } - earcutLinked( filterPoints( ear ), triangles, dim, minX, minY, invSize, 1 ); + gl.bindBuffer( gl.ARRAY_BUFFER, buffer ); - // if this didn't work, try curing all small self-intersections locally + for ( let i = 0; i < programAttribute.locationSize; i ++ ) { - } else if ( pass === 1 ) { + vertexAttribPointer( + programAttribute.location + i, + size / programAttribute.locationSize, + type, + normalized, + size * bytesPerElement, + ( size / programAttribute.locationSize ) * i * bytesPerElement, + integer + ); - ear = cureLocalIntersections( filterPoints( ear ), triangles, dim ); - earcutLinked( ear, triangles, dim, minX, minY, invSize, 2 ); + } - // as a last resort, try splitting the remaining polygon into two + } - } else if ( pass === 2 ) { + } else if ( materialDefaultAttributeValues !== undefined ) { - splitEarcut( ear, triangles, dim, minX, minY, invSize ); + const value = materialDefaultAttributeValues[ name ]; - } + if ( value !== undefined ) { - break; + switch ( value.length ) { - } + case 2: + gl.vertexAttrib2fv( programAttribute.location, value ); + break; - } + case 3: + gl.vertexAttrib3fv( programAttribute.location, value ); + break; -} + case 4: + gl.vertexAttrib4fv( programAttribute.location, value ); + break; -// check whether a polygon node forms a valid ear with adjacent nodes -function isEar( ear ) { + default: + gl.vertexAttrib1fv( programAttribute.location, value ); - const a = ear.prev, - b = ear, - c = ear.next; + } - if ( area( a, b, c ) >= 0 ) return false; // reflex, can't be an ear + } - // now make sure we don't have other points inside the potential ear - const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y; + } - // triangle bbox; min & max are calculated like this for speed - const x0 = ax < bx ? ( ax < cx ? ax : cx ) : ( bx < cx ? bx : cx ), - y0 = ay < by ? ( ay < cy ? ay : cy ) : ( by < cy ? by : cy ), - x1 = ax > bx ? ( ax > cx ? ax : cx ) : ( bx > cx ? bx : cx ), - y1 = ay > by ? ( ay > cy ? ay : cy ) : ( by > cy ? by : cy ); + } - let p = c.next; - while ( p !== a ) { + } - if ( p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && - pointInTriangle( ax, ay, bx, by, cx, cy, p.x, p.y ) && - area( p.prev, p, p.next ) >= 0 ) return false; - p = p.next; + disableUnusedAttributes(); } - return true; - -} + function dispose() { -function isEarHashed( ear, minX, minY, invSize ) { + reset(); - const a = ear.prev, - b = ear, - c = ear.next; + for ( const geometryId in bindingStates ) { - if ( area( a, b, c ) >= 0 ) return false; // reflex, can't be an ear + const programMap = bindingStates[ geometryId ]; - const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y; + for ( const programId in programMap ) { - // triangle bbox; min & max are calculated like this for speed - const x0 = ax < bx ? ( ax < cx ? ax : cx ) : ( bx < cx ? bx : cx ), - y0 = ay < by ? ( ay < cy ? ay : cy ) : ( by < cy ? by : cy ), - x1 = ax > bx ? ( ax > cx ? ax : cx ) : ( bx > cx ? bx : cx ), - y1 = ay > by ? ( ay > cy ? ay : cy ) : ( by > cy ? by : cy ); + const stateMap = programMap[ programId ]; - // z-order range for the current triangle bbox; - const minZ = zOrder( x0, y0, minX, minY, invSize ), - maxZ = zOrder( x1, y1, minX, minY, invSize ); + for ( const wireframe in stateMap ) { - let p = ear.prevZ, - n = ear.nextZ; + deleteVertexArrayObject( stateMap[ wireframe ].object ); - // look for points inside the triangle in both directions - while ( p && p.z >= minZ && n && n.z <= maxZ ) { + delete stateMap[ wireframe ]; - if ( p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c && - pointInTriangle( ax, ay, bx, by, cx, cy, p.x, p.y ) && area( p.prev, p, p.next ) >= 0 ) return false; - p = p.prevZ; + } - if ( n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c && - pointInTriangle( ax, ay, bx, by, cx, cy, n.x, n.y ) && area( n.prev, n, n.next ) >= 0 ) return false; - n = n.nextZ; + delete programMap[ programId ]; - } + } - // look for remaining points in decreasing z-order - while ( p && p.z >= minZ ) { + delete bindingStates[ geometryId ]; - if ( p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c && - pointInTriangle( ax, ay, bx, by, cx, cy, p.x, p.y ) && area( p.prev, p, p.next ) >= 0 ) return false; - p = p.prevZ; + } } - // look for remaining points in increasing z-order - while ( n && n.z <= maxZ ) { - - if ( n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c && - pointInTriangle( ax, ay, bx, by, cx, cy, n.x, n.y ) && area( n.prev, n, n.next ) >= 0 ) return false; - n = n.nextZ; - - } + function releaseStatesOfGeometry( geometry ) { - return true; + if ( bindingStates[ geometry.id ] === undefined ) return; -} + const programMap = bindingStates[ geometry.id ]; -// go through all polygon nodes and cure small local self-intersections -function cureLocalIntersections( start, triangles, dim ) { + for ( const programId in programMap ) { - let p = start; - do { + const stateMap = programMap[ programId ]; - const a = p.prev, - b = p.next.next; + for ( const wireframe in stateMap ) { - if ( ! equals( a, b ) && intersects( a, p, p.next, b ) && locallyInside( a, b ) && locallyInside( b, a ) ) { + deleteVertexArrayObject( stateMap[ wireframe ].object ); - triangles.push( a.i / dim | 0 ); - triangles.push( p.i / dim | 0 ); - triangles.push( b.i / dim | 0 ); + delete stateMap[ wireframe ]; - // remove two nodes involved - removeNode( p ); - removeNode( p.next ); + } - p = start = b; + delete programMap[ programId ]; } - p = p.next; - - } while ( p !== start ); + delete bindingStates[ geometry.id ]; - return filterPoints( p ); + } -} + function releaseStatesOfProgram( program ) { -// try splitting polygon into two and triangulate them independently -function splitEarcut( start, triangles, dim, minX, minY, invSize ) { + for ( const geometryId in bindingStates ) { - // look for a valid diagonal that divides the polygon into two - let a = start; - do { + const programMap = bindingStates[ geometryId ]; - let b = a.next.next; - while ( b !== a.prev ) { + if ( programMap[ program.id ] === undefined ) continue; - if ( a.i !== b.i && isValidDiagonal( a, b ) ) { + const stateMap = programMap[ program.id ]; - // split the polygon in two by the diagonal - let c = splitPolygon( a, b ); + for ( const wireframe in stateMap ) { - // filter colinear points around the cuts - a = filterPoints( a, a.next ); - c = filterPoints( c, c.next ); + deleteVertexArrayObject( stateMap[ wireframe ].object ); - // run earcut on each half - earcutLinked( a, triangles, dim, minX, minY, invSize, 0 ); - earcutLinked( c, triangles, dim, minX, minY, invSize, 0 ); - return; + delete stateMap[ wireframe ]; } - b = b.next; + delete programMap[ program.id ]; } - a = a.next; + } + + function reset() { - } while ( a !== start ); + resetDefaultState(); + forceUpdate = true; -} + if ( currentState === defaultState ) return; -// link every hole into the outer loop, producing a single-ring polygon without holes -function eliminateHoles( data, holeIndices, outerNode, dim ) { + currentState = defaultState; + bindVertexArrayObject( currentState.object ); - const queue = []; - let i, len, start, end, list; + } - for ( i = 0, len = holeIndices.length; i < len; i ++ ) { + // for backward-compatibility - start = holeIndices[ i ] * dim; - end = i < len - 1 ? holeIndices[ i + 1 ] * dim : data.length; - list = linkedList( data, start, end, dim, false ); - if ( list === list.next ) list.steiner = true; - queue.push( getLeftmost( list ) ); + function resetDefaultState() { - } + defaultState.geometry = null; + defaultState.program = null; + defaultState.wireframe = false; - queue.sort( compareX ); + } - // process holes from left to right - for ( i = 0; i < queue.length; i ++ ) { + return { - outerNode = eliminateHole( queue[ i ], outerNode ); + setup: setup, + reset: reset, + resetDefaultState: resetDefaultState, + dispose: dispose, + releaseStatesOfGeometry: releaseStatesOfGeometry, + releaseStatesOfProgram: releaseStatesOfProgram, - } + initAttributes: initAttributes, + enableAttribute: enableAttribute, + disableUnusedAttributes: disableUnusedAttributes - return outerNode; + }; } -function compareX( a, b ) { - - return a.x - b.x; +function WebGLBufferRenderer( gl, extensions, info ) { -} - -// find a bridge between vertices that connects hole with an outer ring and link it -function eliminateHole( hole, outerNode ) { + let mode; - const bridge = findHoleBridge( hole, outerNode ); - if ( ! bridge ) { + function setMode( value ) { - return outerNode; + mode = value; } - const bridgeReverse = splitPolygon( bridge, hole ); + function render( start, count ) { - // filter collinear points around the cuts - filterPoints( bridgeReverse, bridgeReverse.next ); - return filterPoints( bridge, bridge.next ); + gl.drawArrays( mode, start, count ); -} + info.update( count, mode, 1 ); -// David Eberly's algorithm for finding a bridge between hole and outer polygon -function findHoleBridge( hole, outerNode ) { + } - let p = outerNode, - qx = - Infinity, - m; + function renderInstances( start, count, primcount ) { - const hx = hole.x, hy = hole.y; + if ( primcount === 0 ) return; - // find a segment intersected by a ray from the hole's leftmost point to the left; - // segment's endpoint with lesser x will be potential connection point - do { + gl.drawArraysInstanced( mode, start, count, primcount ); - if ( hy <= p.y && hy >= p.next.y && p.next.y !== p.y ) { + info.update( count, mode, primcount ); - const x = p.x + ( hy - p.y ) * ( p.next.x - p.x ) / ( p.next.y - p.y ); - if ( x <= hx && x > qx ) { + } - qx = x; - m = p.x < p.next.x ? p : p.next; - if ( x === hx ) return m; // hole touches outer segment; pick leftmost endpoint + function renderMultiDraw( starts, counts, drawCount ) { - } + if ( drawCount === 0 ) return; - } + const extension = extensions.get( 'WEBGL_multi_draw' ); + extension.multiDrawArraysWEBGL( mode, starts, 0, counts, 0, drawCount ); - p = p.next; + let elementCount = 0; + for ( let i = 0; i < drawCount; i ++ ) { - } while ( p !== outerNode ); + elementCount += counts[ i ]; - if ( ! m ) return null; + } - // look for points inside the triangle of hole point, segment intersection and endpoint; - // if there are no points found, we have a valid connection; - // otherwise choose the point of the minimum angle with the ray as connection point + info.update( elementCount, mode, 1 ); - const stop = m, - mx = m.x, - my = m.y; - let tanMin = Infinity, tan; + } - p = m; + function renderMultiDrawInstances( starts, counts, drawCount, primcount ) { - do { + if ( drawCount === 0 ) return; - if ( hx >= p.x && p.x >= mx && hx !== p.x && - pointInTriangle( hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y ) ) { + const extension = extensions.get( 'WEBGL_multi_draw' ); - tan = Math.abs( hy - p.y ) / ( hx - p.x ); // tangential + if ( extension === null ) { - if ( locallyInside( p, hole ) && ( tan < tanMin || ( tan === tanMin && ( p.x > m.x || ( p.x === m.x && sectorContainsSector( m, p ) ) ) ) ) ) { + for ( let i = 0; i < starts.length; i ++ ) { - m = p; - tanMin = tan; + renderInstances( starts[ i ], counts[ i ], primcount[ i ] ); } - } - - p = p.next; + } else { - } while ( p !== stop ); + extension.multiDrawArraysInstancedWEBGL( mode, starts, 0, counts, 0, primcount, 0, drawCount ); - return m; + let elementCount = 0; + for ( let i = 0; i < drawCount; i ++ ) { -} + elementCount += counts[ i ] * primcount[ i ]; -// whether sector in vertex m contains sector in vertex p in the same coordinates -function sectorContainsSector( m, p ) { + } - return area( m.prev, m, p.prev ) < 0 && area( p.next, m, m.next ) < 0; + info.update( elementCount, mode, 1 ); -} + } -// interlink polygon nodes in z-order -function indexCurve( start, minX, minY, invSize ) { + } - let p = start; - do { + // - if ( p.z === 0 ) p.z = zOrder( p.x, p.y, minX, minY, invSize ); - p.prevZ = p.prev; - p.nextZ = p.next; - p = p.next; + this.setMode = setMode; + this.render = render; + this.renderInstances = renderInstances; + this.renderMultiDraw = renderMultiDraw; + this.renderMultiDrawInstances = renderMultiDrawInstances; - } while ( p !== start ); +} - p.prevZ.nextZ = null; - p.prevZ = null; +function WebGLCapabilities( gl, extensions, parameters, utils ) { - sortLinked( p ); + let maxAnisotropy; -} + function getMaxAnisotropy() { -// Simon Tatham's linked list merge sort algorithm -// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html -function sortLinked( list ) { + if ( maxAnisotropy !== undefined ) return maxAnisotropy; - let i, p, q, e, tail, numMerges, pSize, qSize, - inSize = 1; + if ( extensions.has( 'EXT_texture_filter_anisotropic' ) === true ) { - do { + const extension = extensions.get( 'EXT_texture_filter_anisotropic' ); - p = list; - list = null; - tail = null; - numMerges = 0; + maxAnisotropy = gl.getParameter( extension.MAX_TEXTURE_MAX_ANISOTROPY_EXT ); - while ( p ) { + } else { - numMerges ++; - q = p; - pSize = 0; - for ( i = 0; i < inSize; i ++ ) { + maxAnisotropy = 0; - pSize ++; - q = q.nextZ; - if ( ! q ) break; + } - } + return maxAnisotropy; - qSize = inSize; + } - while ( pSize > 0 || ( qSize > 0 && q ) ) { + function textureFormatReadable( textureFormat ) { - if ( pSize !== 0 && ( qSize === 0 || ! q || p.z <= q.z ) ) { + if ( textureFormat !== RGBAFormat && utils.convert( textureFormat ) !== gl.getParameter( gl.IMPLEMENTATION_COLOR_READ_FORMAT ) ) { - e = p; - p = p.nextZ; - pSize --; + return false; - } else { + } - e = q; - q = q.nextZ; - qSize --; + return true; - } + } - if ( tail ) tail.nextZ = e; - else list = e; + function textureTypeReadable( textureType ) { - e.prevZ = tail; - tail = e; + const halfFloatSupportedByExt = ( textureType === HalfFloatType ) && ( extensions.has( 'EXT_color_buffer_half_float' ) || extensions.has( 'EXT_color_buffer_float' ) ); - } + if ( textureType !== UnsignedByteType && utils.convert( textureType ) !== gl.getParameter( gl.IMPLEMENTATION_COLOR_READ_TYPE ) && // Edge and Chrome Mac < 52 (#9513) + textureType !== FloatType && ! halfFloatSupportedByExt ) { - p = q; + return false; } - tail.nextZ = null; - inSize *= 2; - - } while ( numMerges > 1 ); + return true; - return list; + } -} + function getMaxPrecision( precision ) { -// z-order of a point given coords and inverse of the longer side of data bbox -function zOrder( x, y, minX, minY, invSize ) { + if ( precision === 'highp' ) { - // coords are transformed into non-negative 15-bit integer range - x = ( x - minX ) * invSize | 0; - y = ( y - minY ) * invSize | 0; + if ( gl.getShaderPrecisionFormat( gl.VERTEX_SHADER, gl.HIGH_FLOAT ).precision > 0 && + gl.getShaderPrecisionFormat( gl.FRAGMENT_SHADER, gl.HIGH_FLOAT ).precision > 0 ) { - x = ( x | ( x << 8 ) ) & 0x00FF00FF; - x = ( x | ( x << 4 ) ) & 0x0F0F0F0F; - x = ( x | ( x << 2 ) ) & 0x33333333; - x = ( x | ( x << 1 ) ) & 0x55555555; + return 'highp'; - y = ( y | ( y << 8 ) ) & 0x00FF00FF; - y = ( y | ( y << 4 ) ) & 0x0F0F0F0F; - y = ( y | ( y << 2 ) ) & 0x33333333; - y = ( y | ( y << 1 ) ) & 0x55555555; + } - return x | ( y << 1 ); + precision = 'mediump'; -} + } -// find the leftmost node of a polygon ring -function getLeftmost( start ) { + if ( precision === 'mediump' ) { - let p = start, - leftmost = start; - do { + if ( gl.getShaderPrecisionFormat( gl.VERTEX_SHADER, gl.MEDIUM_FLOAT ).precision > 0 && + gl.getShaderPrecisionFormat( gl.FRAGMENT_SHADER, gl.MEDIUM_FLOAT ).precision > 0 ) { - if ( p.x < leftmost.x || ( p.x === leftmost.x && p.y < leftmost.y ) ) leftmost = p; - p = p.next; + return 'mediump'; - } while ( p !== start ); + } - return leftmost; + } -} + return 'lowp'; -// check if a point lies within a convex triangle -function pointInTriangle( ax, ay, bx, by, cx, cy, px, py ) { + } - return ( cx - px ) * ( ay - py ) >= ( ax - px ) * ( cy - py ) && - ( ax - px ) * ( by - py ) >= ( bx - px ) * ( ay - py ) && - ( bx - px ) * ( cy - py ) >= ( cx - px ) * ( by - py ); + let precision = parameters.precision !== undefined ? parameters.precision : 'highp'; + const maxPrecision = getMaxPrecision( precision ); -} + if ( maxPrecision !== precision ) { -// check if a diagonal between two polygon nodes is valid (lies in polygon interior) -function isValidDiagonal( a, b ) { + console.warn( 'THREE.WebGLRenderer:', precision, 'not supported, using', maxPrecision, 'instead.' ); + precision = maxPrecision; - return a.next.i !== b.i && a.prev.i !== b.i && ! intersectsPolygon( a, b ) && // dones't intersect other edges - ( locallyInside( a, b ) && locallyInside( b, a ) && middleInside( a, b ) && // locally visible - ( area( a.prev, a, b.prev ) || area( a, b.prev, b ) ) || // does not create opposite-facing sectors - equals( a, b ) && area( a.prev, a, a.next ) > 0 && area( b.prev, b, b.next ) > 0 ); // special zero-length case + } -} + const logarithmicDepthBuffer = parameters.logarithmicDepthBuffer === true; + const reverseDepthBuffer = parameters.reverseDepthBuffer === true && extensions.has( 'EXT_clip_control' ); -// signed area of a triangle -function area( p, q, r ) { + const maxTextures = gl.getParameter( gl.MAX_TEXTURE_IMAGE_UNITS ); + const maxVertexTextures = gl.getParameter( gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS ); + const maxTextureSize = gl.getParameter( gl.MAX_TEXTURE_SIZE ); + const maxCubemapSize = gl.getParameter( gl.MAX_CUBE_MAP_TEXTURE_SIZE ); - return ( q.y - p.y ) * ( r.x - q.x ) - ( q.x - p.x ) * ( r.y - q.y ); + const maxAttributes = gl.getParameter( gl.MAX_VERTEX_ATTRIBS ); + const maxVertexUniforms = gl.getParameter( gl.MAX_VERTEX_UNIFORM_VECTORS ); + const maxVaryings = gl.getParameter( gl.MAX_VARYING_VECTORS ); + const maxFragmentUniforms = gl.getParameter( gl.MAX_FRAGMENT_UNIFORM_VECTORS ); -} + const vertexTextures = maxVertexTextures > 0; -// check if two points are equal -function equals( p1, p2 ) { + const maxSamples = gl.getParameter( gl.MAX_SAMPLES ); - return p1.x === p2.x && p1.y === p2.y; + return { -} + isWebGL2: true, // keeping this for backwards compatibility -// check if two segments intersect -function intersects( p1, q1, p2, q2 ) { + getMaxAnisotropy: getMaxAnisotropy, + getMaxPrecision: getMaxPrecision, - const o1 = sign( area( p1, q1, p2 ) ); - const o2 = sign( area( p1, q1, q2 ) ); - const o3 = sign( area( p2, q2, p1 ) ); - const o4 = sign( area( p2, q2, q1 ) ); + textureFormatReadable: textureFormatReadable, + textureTypeReadable: textureTypeReadable, - if ( o1 !== o2 && o3 !== o4 ) return true; // general case + precision: precision, + logarithmicDepthBuffer: logarithmicDepthBuffer, + reverseDepthBuffer: reverseDepthBuffer, - if ( o1 === 0 && onSegment( p1, p2, q1 ) ) return true; // p1, q1 and p2 are collinear and p2 lies on p1q1 - if ( o2 === 0 && onSegment( p1, q2, q1 ) ) return true; // p1, q1 and q2 are collinear and q2 lies on p1q1 - if ( o3 === 0 && onSegment( p2, p1, q2 ) ) return true; // p2, q2 and p1 are collinear and p1 lies on p2q2 - if ( o4 === 0 && onSegment( p2, q1, q2 ) ) return true; // p2, q2 and q1 are collinear and q1 lies on p2q2 + maxTextures: maxTextures, + maxVertexTextures: maxVertexTextures, + maxTextureSize: maxTextureSize, + maxCubemapSize: maxCubemapSize, - return false; + maxAttributes: maxAttributes, + maxVertexUniforms: maxVertexUniforms, + maxVaryings: maxVaryings, + maxFragmentUniforms: maxFragmentUniforms, -} + vertexTextures: vertexTextures, -// for collinear points p, q, r, check if point q lies on segment pr -function onSegment( p, q, r ) { + maxSamples: maxSamples - return q.x <= Math.max( p.x, r.x ) && q.x >= Math.min( p.x, r.x ) && q.y <= Math.max( p.y, r.y ) && q.y >= Math.min( p.y, r.y ); + }; } -function sign( num ) { - - return num > 0 ? 1 : num < 0 ? - 1 : 0; +function WebGLClipping( properties ) { -} + const scope = this; -// check if a polygon diagonal intersects any polygon segments -function intersectsPolygon( a, b ) { + let globalState = null, + numGlobalPlanes = 0, + localClippingEnabled = false, + renderingShadows = false; - let p = a; - do { + const plane = new Plane(), + viewNormalMatrix = new Matrix3(), - if ( p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i && - intersects( p, p.next, a, b ) ) return true; - p = p.next; + uniform = { value: null, needsUpdate: false }; - } while ( p !== a ); + this.uniform = uniform; + this.numPlanes = 0; + this.numIntersection = 0; - return false; + this.init = function ( planes, enableLocalClipping ) { -} + const enabled = + planes.length !== 0 || + enableLocalClipping || + // enable state of previous frame - the clipping code has to + // run another frame in order to reset the state: + numGlobalPlanes !== 0 || + localClippingEnabled; -// check if a polygon diagonal is locally inside the polygon -function locallyInside( a, b ) { + localClippingEnabled = enableLocalClipping; - return area( a.prev, a, a.next ) < 0 ? - area( a, b, a.next ) >= 0 && area( a, a.prev, b ) >= 0 : - area( a, b, a.prev ) < 0 || area( a, a.next, b ) < 0; + numGlobalPlanes = planes.length; -} + return enabled; -// check if the middle point of a polygon diagonal is inside the polygon -function middleInside( a, b ) { + }; - let p = a, - inside = false; - const px = ( a.x + b.x ) / 2, - py = ( a.y + b.y ) / 2; - do { + this.beginShadows = function () { - if ( ( ( p.y > py ) !== ( p.next.y > py ) ) && p.next.y !== p.y && - ( px < ( p.next.x - p.x ) * ( py - p.y ) / ( p.next.y - p.y ) + p.x ) ) - inside = ! inside; - p = p.next; + renderingShadows = true; + projectPlanes( null ); - } while ( p !== a ); + }; - return inside; + this.endShadows = function () { -} + renderingShadows = false; -// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two; -// if one belongs to the outer ring and another to a hole, it merges it into a single ring -function splitPolygon( a, b ) { + }; - const a2 = new Node( a.i, a.x, a.y ), - b2 = new Node( b.i, b.x, b.y ), - an = a.next, - bp = b.prev; + this.setGlobalState = function ( planes, camera ) { - a.next = b; - b.prev = a; + globalState = projectPlanes( planes, camera, 0 ); - a2.next = an; - an.prev = a2; + }; - b2.next = a2; - a2.prev = b2; + this.setState = function ( material, camera, useCache ) { - bp.next = b2; - b2.prev = bp; + const planes = material.clippingPlanes, + clipIntersection = material.clipIntersection, + clipShadows = material.clipShadows; - return b2; + const materialProperties = properties.get( material ); -} + if ( ! localClippingEnabled || planes === null || planes.length === 0 || renderingShadows && ! clipShadows ) { -// create a node and optionally link it with previous one (in a circular doubly linked list) -function insertNode( i, x, y, last ) { + // there's no local clipping - const p = new Node( i, x, y ); + if ( renderingShadows ) { - if ( ! last ) { + // there's no global clipping - p.prev = p; - p.next = p; + projectPlanes( null ); - } else { + } else { - p.next = last.next; - p.prev = last; - last.next.prev = p; - last.next = p; + resetGlobalState(); - } + } - return p; + } else { -} + const nGlobal = renderingShadows ? 0 : numGlobalPlanes, + lGlobal = nGlobal * 4; -function removeNode( p ) { + let dstArray = materialProperties.clippingState || null; - p.next.prev = p.prev; - p.prev.next = p.next; + uniform.value = dstArray; // ensure unique state - if ( p.prevZ ) p.prevZ.nextZ = p.nextZ; - if ( p.nextZ ) p.nextZ.prevZ = p.prevZ; + dstArray = projectPlanes( planes, camera, lGlobal, useCache ); -} + for ( let i = 0; i !== lGlobal; ++ i ) { -function Node( i, x, y ) { + dstArray[ i ] = globalState[ i ]; - // vertex index in coordinates array - this.i = i; + } - // vertex coordinates - this.x = x; - this.y = y; + materialProperties.clippingState = dstArray; + this.numIntersection = clipIntersection ? this.numPlanes : 0; + this.numPlanes += nGlobal; - // previous and next vertex nodes in a polygon ring - this.prev = null; - this.next = null; + } - // z-order curve value - this.z = 0; - // previous and next nodes in z-order - this.prevZ = null; - this.nextZ = null; + }; - // indicates whether this is a steiner point - this.steiner = false; + function resetGlobalState() { -} + if ( uniform.value !== globalState ) { -function signedArea( data, start, end, dim ) { + uniform.value = globalState; + uniform.needsUpdate = numGlobalPlanes > 0; - let sum = 0; - for ( let i = start, j = end - dim; i < end; i += dim ) { + } - sum += ( data[ j ] - data[ i ] ) * ( data[ i + 1 ] + data[ j + 1 ] ); - j = i; + scope.numPlanes = numGlobalPlanes; + scope.numIntersection = 0; } - return sum; + function projectPlanes( planes, camera, dstOffset, skipTransform ) { -} + const nPlanes = planes !== null ? planes.length : 0; + let dstArray = null; -class ShapeUtils { + if ( nPlanes !== 0 ) { - // calculate area of the contour polygon + dstArray = uniform.value; - static area( contour ) { + if ( skipTransform !== true || dstArray === null ) { - const n = contour.length; - let a = 0.0; + const flatSize = dstOffset + nPlanes * 4, + viewMatrix = camera.matrixWorldInverse; - for ( let p = n - 1, q = 0; q < n; p = q ++ ) { + viewNormalMatrix.getNormalMatrix( viewMatrix ); - a += contour[ p ].x * contour[ q ].y - contour[ q ].x * contour[ p ].y; + if ( dstArray === null || dstArray.length < flatSize ) { - } + dstArray = new Float32Array( flatSize ); - return a * 0.5; + } - } + for ( let i = 0, i4 = dstOffset; i !== nPlanes; ++ i, i4 += 4 ) { - static isClockWise( pts ) { + plane.copy( planes[ i ] ).applyMatrix4( viewMatrix, viewNormalMatrix ); - return ShapeUtils.area( pts ) < 0; + plane.normal.toArray( dstArray, i4 ); + dstArray[ i4 + 3 ] = plane.constant; - } + } - static triangulateShape( contour, holes ) { + } - const vertices = []; // flat array of vertices like [ x0,y0, x1,y1, x2,y2, ... ] - const holeIndices = []; // array of hole indices - const faces = []; // final array of vertex indices like [ [ a,b,d ], [ b,c,d ] ] + uniform.value = dstArray; + uniform.needsUpdate = true; - removeDupEndPts( contour ); - addContour( vertices, contour ); + } - // + scope.numPlanes = nPlanes; + scope.numIntersection = 0; - let holeIndex = contour.length; + return dstArray; - holes.forEach( removeDupEndPts ); + } - for ( let i = 0; i < holes.length; i ++ ) { +} - holeIndices.push( holeIndex ); - holeIndex += holes[ i ].length; - addContour( vertices, holes[ i ] ); +function WebGLCubeMaps( renderer ) { - } + let cubemaps = new WeakMap(); - // + function mapTextureMapping( texture, mapping ) { - const triangles = Earcut.triangulate( vertices, holeIndices ); + if ( mapping === EquirectangularReflectionMapping ) { - // + texture.mapping = CubeReflectionMapping; - for ( let i = 0; i < triangles.length; i += 3 ) { + } else if ( mapping === EquirectangularRefractionMapping ) { - faces.push( triangles.slice( i, i + 3 ) ); + texture.mapping = CubeRefractionMapping; } - return faces; + return texture; } -} - -function removeDupEndPts( points ) { - - const l = points.length; - - if ( l > 2 && points[ l - 1 ].equals( points[ 0 ] ) ) { + function get( texture ) { - points.pop(); + if ( texture && texture.isTexture ) { - } + const mapping = texture.mapping; -} + if ( mapping === EquirectangularReflectionMapping || mapping === EquirectangularRefractionMapping ) { -function addContour( vertices, contour ) { + if ( cubemaps.has( texture ) ) { - for ( let i = 0; i < contour.length; i ++ ) { + const cubemap = cubemaps.get( texture ).texture; + return mapTextureMapping( cubemap, texture.mapping ); - vertices.push( contour[ i ].x ); - vertices.push( contour[ i ].y ); + } else { - } + const image = texture.image; -} + if ( image && image.height > 0 ) { -/** - * Creates extruded geometry from a path shape. - * - * parameters = { - * - * curveSegments: , // number of points on the curves - * steps: , // number of points for z-side extrusions / used for subdividing segments of extrude spline too - * depth: , // Depth to extrude the shape - * - * bevelEnabled: , // turn on bevel - * bevelThickness: , // how deep into the original shape bevel goes - * bevelSize: , // how far from shape outline (including bevelOffset) is bevel - * bevelOffset: , // how far from shape outline does bevel start - * bevelSegments: , // number of bevel layers - * - * extrudePath: // curve to extrude shape along - * - * UVGenerator: // object that provides UV generator functions - * - * } - */ + const renderTarget = new WebGLCubeRenderTarget( image.height ); + renderTarget.fromEquirectangularTexture( renderer, texture ); + cubemaps.set( texture, renderTarget ); + texture.addEventListener( 'dispose', onTextureDispose ); -class ExtrudeGeometry extends BufferGeometry { + return mapTextureMapping( renderTarget.texture, texture.mapping ); - constructor( shapes = new Shape( [ new Vector2( 0.5, 0.5 ), new Vector2( - 0.5, 0.5 ), new Vector2( - 0.5, - 0.5 ), new Vector2( 0.5, - 0.5 ) ] ), options = {} ) { + } else { - super(); + // image not yet ready. try the conversion next frame - this.type = 'ExtrudeGeometry'; + return null; - this.parameters = { - shapes: shapes, - options: options - }; + } - shapes = Array.isArray( shapes ) ? shapes : [ shapes ]; + } - const scope = this; + } - const verticesArray = []; - const uvArray = []; + } - for ( let i = 0, l = shapes.length; i < l; i ++ ) { + return texture; - const shape = shapes[ i ]; - addShape( shape ); + } - } + function onTextureDispose( event ) { - // build geometry + const texture = event.target; - this.setAttribute( 'position', new Float32BufferAttribute( verticesArray, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvArray, 2 ) ); + texture.removeEventListener( 'dispose', onTextureDispose ); - this.computeVertexNormals(); + const cubemap = cubemaps.get( texture ); - // functions + if ( cubemap !== undefined ) { - function addShape( shape ) { + cubemaps.delete( texture ); + cubemap.dispose(); - const placeholder = []; + } - // options + } - const curveSegments = options.curveSegments !== undefined ? options.curveSegments : 12; - const steps = options.steps !== undefined ? options.steps : 1; - const depth = options.depth !== undefined ? options.depth : 1; + function dispose() { - let bevelEnabled = options.bevelEnabled !== undefined ? options.bevelEnabled : true; - let bevelThickness = options.bevelThickness !== undefined ? options.bevelThickness : 0.2; - let bevelSize = options.bevelSize !== undefined ? options.bevelSize : bevelThickness - 0.1; - let bevelOffset = options.bevelOffset !== undefined ? options.bevelOffset : 0; - let bevelSegments = options.bevelSegments !== undefined ? options.bevelSegments : 3; + cubemaps = new WeakMap(); - const extrudePath = options.extrudePath; + } - const uvgen = options.UVGenerator !== undefined ? options.UVGenerator : WorldUVGenerator; + return { + get: get, + dispose: dispose + }; - // +} - let extrudePts, extrudeByPath = false; - let splineTube, binormal, normal, position2; +const LOD_MIN = 4; - if ( extrudePath ) { +// The standard deviations (radians) associated with the extra mips. These are +// chosen to approximate a Trowbridge-Reitz distribution function times the +// geometric shadowing function. These sigma values squared must match the +// variance #defines in cube_uv_reflection_fragment.glsl.js. +const EXTRA_LOD_SIGMA = [ 0.125, 0.215, 0.35, 0.446, 0.526, 0.582 ]; - extrudePts = extrudePath.getSpacedPoints( steps ); +// The maximum length of the blur for loop. Smaller sigmas will use fewer +// samples and exit early, but not recompile the shader. +const MAX_SAMPLES = 20; - extrudeByPath = true; - bevelEnabled = false; // bevels not supported for path extrusion +const _flatCamera = /*@__PURE__*/ new OrthographicCamera(); +const _clearColor = /*@__PURE__*/ new Color(); +let _oldTarget = null; +let _oldActiveCubeFace = 0; +let _oldActiveMipmapLevel = 0; +let _oldXrEnabled = false; - // SETUP TNB variables +// Golden Ratio +const PHI = ( 1 + Math.sqrt( 5 ) ) / 2; +const INV_PHI = 1 / PHI; - // TODO1 - have a .isClosed in spline? +// Vertices of a dodecahedron (except the opposites, which represent the +// same axis), used as axis directions evenly spread on a sphere. +const _axisDirections = [ + /*@__PURE__*/ new Vector3( - PHI, INV_PHI, 0 ), + /*@__PURE__*/ new Vector3( PHI, INV_PHI, 0 ), + /*@__PURE__*/ new Vector3( - INV_PHI, 0, PHI ), + /*@__PURE__*/ new Vector3( INV_PHI, 0, PHI ), + /*@__PURE__*/ new Vector3( 0, PHI, - INV_PHI ), + /*@__PURE__*/ new Vector3( 0, PHI, INV_PHI ), + /*@__PURE__*/ new Vector3( -1, 1, -1 ), + /*@__PURE__*/ new Vector3( 1, 1, -1 ), + /*@__PURE__*/ new Vector3( -1, 1, 1 ), + /*@__PURE__*/ new Vector3( 1, 1, 1 ) ]; - splineTube = extrudePath.computeFrenetFrames( steps, false ); +const _origin = /*@__PURE__*/ new Vector3(); - // console.log(splineTube, 'splineTube', splineTube.normals.length, 'steps', steps, 'extrudePts', extrudePts.length); +/** + * This class generates a Prefiltered, Mipmapped Radiance Environment Map + * (PMREM) from a cubeMap environment texture. This allows different levels of + * blur to be quickly accessed based on material roughness. It is packed into a + * special CubeUV format that allows us to perform custom interpolation so that + * we can support nonlinear formats such as RGBE. Unlike a traditional mipmap + * chain, it only goes down to the LOD_MIN level (above), and then creates extra + * even more filtered 'mips' at the same LOD_MIN resolution, associated with + * higher roughness levels. In this way we maintain resolution to smoothly + * interpolate diffuse lighting while limiting sampling computation. + * + * Paper: Fast, Accurate Image-Based Lighting: + * {@link https://drive.google.com/file/d/15y8r_UpKlU9SvV4ILb0C3qCPecS8pvLz/view} +*/ +class PMREMGenerator { - binormal = new Vector3(); - normal = new Vector3(); - position2 = new Vector3(); + /** + * Constructs a new PMREM generator. + * + * @param {WebGLRenderer} renderer - The renderer. + */ + constructor( renderer ) { - } + this._renderer = renderer; + this._pingPongRenderTarget = null; - // Safeguards if bevels are not enabled + this._lodMax = 0; + this._cubeSize = 0; + this._lodPlanes = []; + this._sizeLods = []; + this._sigmas = []; - if ( ! bevelEnabled ) { + this._blurMaterial = null; + this._cubemapMaterial = null; + this._equirectMaterial = null; - bevelSegments = 0; - bevelThickness = 0; - bevelSize = 0; - bevelOffset = 0; + this._compileMaterial( this._blurMaterial ); - } + } - // Variables initialization + /** + * Generates a PMREM from a supplied Scene, which can be faster than using an + * image if networking bandwidth is low. Optional sigma specifies a blur radius + * in radians to be applied to the scene before PMREM generation. Optional near + * and far planes ensure the scene is rendered in its entirety. + * + * @param {Scene} scene - The scene to be captured. + * @param {number} [sigma=0] - The blur radius in radians. + * @param {number} [near=0.1] - The near plane distance. + * @param {number} [far=100] - The far plane distance. + * @param {Object} [options={}] - The configuration options. + * @param {number} [options.size=256] - The texture size of the PMREM. + * @param {Vector3} [options.renderTarget=origin] - The position of the internal cube camera that renders the scene. + * @return {WebGLRenderTarget} The resulting PMREM. + */ + fromScene( scene, sigma = 0, near = 0.1, far = 100, options = {} ) { - const shapePoints = shape.extractPoints( curveSegments ); + const { + size = 256, + position = _origin, + } = options; - let vertices = shapePoints.shape; - const holes = shapePoints.holes; + _oldTarget = this._renderer.getRenderTarget(); + _oldActiveCubeFace = this._renderer.getActiveCubeFace(); + _oldActiveMipmapLevel = this._renderer.getActiveMipmapLevel(); + _oldXrEnabled = this._renderer.xr.enabled; - const reverse = ! ShapeUtils.isClockWise( vertices ); + this._renderer.xr.enabled = false; - if ( reverse ) { + this._setSize( size ); - vertices = vertices.reverse(); + const cubeUVRenderTarget = this._allocateTargets(); + cubeUVRenderTarget.depthBuffer = true; - // Maybe we should also check if holes are in the opposite direction, just to be safe ... + this._sceneToCubeUV( scene, near, far, cubeUVRenderTarget, position ); - for ( let h = 0, hl = holes.length; h < hl; h ++ ) { + if ( sigma > 0 ) { - const ahole = holes[ h ]; + this._blur( cubeUVRenderTarget, 0, 0, sigma ); - if ( ShapeUtils.isClockWise( ahole ) ) { + } - holes[ h ] = ahole.reverse(); + this._applyPMREM( cubeUVRenderTarget ); + this._cleanup( cubeUVRenderTarget ); - } + return cubeUVRenderTarget; - } + } - } + /** + * Generates a PMREM from an equirectangular texture, which can be either LDR + * or HDR. The ideal input image size is 1k (1024 x 512), + * as this matches best with the 256 x 256 cubemap output. + * + * @param {Texture} equirectangular - The equirectangular texture to be converted. + * @param {?WebGLRenderTarget} [renderTarget=null] - The render target to use. + * @return {WebGLRenderTarget} The resulting PMREM. + */ + fromEquirectangular( equirectangular, renderTarget = null ) { + return this._fromTexture( equirectangular, renderTarget ); - const faces = ShapeUtils.triangulateShape( vertices, holes ); + } - /* Vertices */ + /** + * Generates a PMREM from an cubemap texture, which can be either LDR + * or HDR. The ideal input cube size is 256 x 256, + * as this matches best with the 256 x 256 cubemap output. + * + * @param {Texture} cubemap - The cubemap texture to be converted. + * @param {?WebGLRenderTarget} [renderTarget=null] - The render target to use. + * @return {WebGLRenderTarget} The resulting PMREM. + */ + fromCubemap( cubemap, renderTarget = null ) { - const contour = vertices; // vertices has all points but contour has only points of circumference + return this._fromTexture( cubemap, renderTarget ); - for ( let h = 0, hl = holes.length; h < hl; h ++ ) { + } - const ahole = holes[ h ]; + /** + * Pre-compiles the cubemap shader. You can get faster start-up by invoking this method during + * your texture's network fetch for increased concurrency. + */ + compileCubemapShader() { - vertices = vertices.concat( ahole ); + if ( this._cubemapMaterial === null ) { - } + this._cubemapMaterial = _getCubemapMaterial(); + this._compileMaterial( this._cubemapMaterial ); + } - function scalePt2( pt, vec, size ) { + } - if ( ! vec ) console.error( 'THREE.ExtrudeGeometry: vec does not exist' ); + /** + * Pre-compiles the equirectangular shader. You can get faster start-up by invoking this method during + * your texture's network fetch for increased concurrency. + */ + compileEquirectangularShader() { - return pt.clone().addScaledVector( vec, size ); + if ( this._equirectMaterial === null ) { - } + this._equirectMaterial = _getEquirectMaterial(); + this._compileMaterial( this._equirectMaterial ); - const vlen = vertices.length, flen = faces.length; + } + } - // Find directions for point movement + /** + * Disposes of the PMREMGenerator's internal memory. Note that PMREMGenerator is a static class, + * so you should not need more than one PMREMGenerator object. If you do, calling dispose() on + * one of them will cause any others to also become unusable. + */ + dispose() { + this._dispose(); - function getBevelVec( inPt, inPrev, inNext ) { + if ( this._cubemapMaterial !== null ) this._cubemapMaterial.dispose(); + if ( this._equirectMaterial !== null ) this._equirectMaterial.dispose(); - // computes for inPt the corresponding point inPt' on a new contour - // shifted by 1 unit (length of normalized vector) to the left - // if we walk along contour clockwise, this new contour is outside the old one - // - // inPt' is the intersection of the two lines parallel to the two - // adjacent edges of inPt at a distance of 1 unit on the left side. + } - let v_trans_x, v_trans_y, shrink_by; // resulting translation vector for inPt + // private interface - // good reading for geometry algorithms (here: line-line intersection) - // http://geomalgorithms.com/a05-_intersect-1.html + _setSize( cubeSize ) { - const v_prev_x = inPt.x - inPrev.x, - v_prev_y = inPt.y - inPrev.y; - const v_next_x = inNext.x - inPt.x, - v_next_y = inNext.y - inPt.y; + this._lodMax = Math.floor( Math.log2( cubeSize ) ); + this._cubeSize = Math.pow( 2, this._lodMax ); - const v_prev_lensq = ( v_prev_x * v_prev_x + v_prev_y * v_prev_y ); + } - // check for collinear edges - const collinear0 = ( v_prev_x * v_next_y - v_prev_y * v_next_x ); + _dispose() { - if ( Math.abs( collinear0 ) > Number.EPSILON ) { + if ( this._blurMaterial !== null ) this._blurMaterial.dispose(); - // not collinear + if ( this._pingPongRenderTarget !== null ) this._pingPongRenderTarget.dispose(); - // length of vectors for normalizing + for ( let i = 0; i < this._lodPlanes.length; i ++ ) { - const v_prev_len = Math.sqrt( v_prev_lensq ); - const v_next_len = Math.sqrt( v_next_x * v_next_x + v_next_y * v_next_y ); + this._lodPlanes[ i ].dispose(); - // shift adjacent points by unit vectors to the left + } - const ptPrevShift_x = ( inPrev.x - v_prev_y / v_prev_len ); - const ptPrevShift_y = ( inPrev.y + v_prev_x / v_prev_len ); + } - const ptNextShift_x = ( inNext.x - v_next_y / v_next_len ); - const ptNextShift_y = ( inNext.y + v_next_x / v_next_len ); + _cleanup( outputTarget ) { - // scaling factor for v_prev to intersection point + this._renderer.setRenderTarget( _oldTarget, _oldActiveCubeFace, _oldActiveMipmapLevel ); + this._renderer.xr.enabled = _oldXrEnabled; - const sf = ( ( ptNextShift_x - ptPrevShift_x ) * v_next_y - - ( ptNextShift_y - ptPrevShift_y ) * v_next_x ) / - ( v_prev_x * v_next_y - v_prev_y * v_next_x ); + outputTarget.scissorTest = false; + _setViewport( outputTarget, 0, 0, outputTarget.width, outputTarget.height ); - // vector from inPt to intersection point + } - v_trans_x = ( ptPrevShift_x + v_prev_x * sf - inPt.x ); - v_trans_y = ( ptPrevShift_y + v_prev_y * sf - inPt.y ); + _fromTexture( texture, renderTarget ) { - // Don't normalize!, otherwise sharp corners become ugly - // but prevent crazy spikes - const v_trans_lensq = ( v_trans_x * v_trans_x + v_trans_y * v_trans_y ); - if ( v_trans_lensq <= 2 ) { + if ( texture.mapping === CubeReflectionMapping || texture.mapping === CubeRefractionMapping ) { - return new Vector2( v_trans_x, v_trans_y ); + this._setSize( texture.image.length === 0 ? 16 : ( texture.image[ 0 ].width || texture.image[ 0 ].image.width ) ); - } else { + } else { // Equirectangular - shrink_by = Math.sqrt( v_trans_lensq / 2 ); + this._setSize( texture.image.width / 4 ); - } + } - } else { + _oldTarget = this._renderer.getRenderTarget(); + _oldActiveCubeFace = this._renderer.getActiveCubeFace(); + _oldActiveMipmapLevel = this._renderer.getActiveMipmapLevel(); + _oldXrEnabled = this._renderer.xr.enabled; - // handle special case of collinear edges + this._renderer.xr.enabled = false; - let direction_eq = false; // assumes: opposite + const cubeUVRenderTarget = renderTarget || this._allocateTargets(); + this._textureToCubeUV( texture, cubeUVRenderTarget ); + this._applyPMREM( cubeUVRenderTarget ); + this._cleanup( cubeUVRenderTarget ); - if ( v_prev_x > Number.EPSILON ) { + return cubeUVRenderTarget; - if ( v_next_x > Number.EPSILON ) { + } - direction_eq = true; + _allocateTargets() { - } + const width = 3 * Math.max( this._cubeSize, 16 * 7 ); + const height = 4 * this._cubeSize; - } else { + const params = { + magFilter: LinearFilter, + minFilter: LinearFilter, + generateMipmaps: false, + type: HalfFloatType, + format: RGBAFormat, + colorSpace: LinearSRGBColorSpace, + depthBuffer: false + }; - if ( v_prev_x < - Number.EPSILON ) { + const cubeUVRenderTarget = _createRenderTarget( width, height, params ); - if ( v_next_x < - Number.EPSILON ) { + if ( this._pingPongRenderTarget === null || this._pingPongRenderTarget.width !== width || this._pingPongRenderTarget.height !== height ) { - direction_eq = true; + if ( this._pingPongRenderTarget !== null ) { - } + this._dispose(); - } else { + } - if ( Math.sign( v_prev_y ) === Math.sign( v_next_y ) ) { + this._pingPongRenderTarget = _createRenderTarget( width, height, params ); - direction_eq = true; + const { _lodMax } = this; + ( { sizeLods: this._sizeLods, lodPlanes: this._lodPlanes, sigmas: this._sigmas } = _createPlanes( _lodMax ) ); - } + this._blurMaterial = _getBlurShader( _lodMax, width, height ); - } + } - } + return cubeUVRenderTarget; - if ( direction_eq ) { + } - // console.log("Warning: lines are a straight sequence"); - v_trans_x = - v_prev_y; - v_trans_y = v_prev_x; - shrink_by = Math.sqrt( v_prev_lensq ); + _compileMaterial( material ) { - } else { + const tmpMesh = new Mesh( this._lodPlanes[ 0 ], material ); + this._renderer.compile( tmpMesh, _flatCamera ); - // console.log("Warning: lines are a straight spike"); - v_trans_x = v_prev_x; - v_trans_y = v_prev_y; - shrink_by = Math.sqrt( v_prev_lensq / 2 ); + } - } + _sceneToCubeUV( scene, near, far, cubeUVRenderTarget, position ) { - } + const fov = 90; + const aspect = 1; + const cubeCamera = new PerspectiveCamera( fov, aspect, near, far ); + const upSign = [ 1, -1, 1, 1, 1, 1 ]; + const forwardSign = [ 1, 1, 1, -1, -1, -1 ]; + const renderer = this._renderer; - return new Vector2( v_trans_x / shrink_by, v_trans_y / shrink_by ); + const originalAutoClear = renderer.autoClear; + const toneMapping = renderer.toneMapping; + renderer.getClearColor( _clearColor ); - } + renderer.toneMapping = NoToneMapping; + renderer.autoClear = false; + const backgroundMaterial = new MeshBasicMaterial( { + name: 'PMREM.Background', + side: BackSide, + depthWrite: false, + depthTest: false, + } ); - const contourMovements = []; + const backgroundBox = new Mesh( new BoxGeometry(), backgroundMaterial ); - for ( let i = 0, il = contour.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) { + let useSolidColor = false; + const background = scene.background; - if ( j === il ) j = 0; - if ( k === il ) k = 0; + if ( background ) { - // (j)---(i)---(k) - // console.log('i,j,k', i, j , k) + if ( background.isColor ) { - contourMovements[ i ] = getBevelVec( contour[ i ], contour[ j ], contour[ k ] ); + backgroundMaterial.color.copy( background ); + scene.background = null; + useSolidColor = true; } - const holesMovements = []; - let oneHoleMovements, verticesMovements = contourMovements.concat(); + } else { - for ( let h = 0, hl = holes.length; h < hl; h ++ ) { + backgroundMaterial.color.copy( _clearColor ); + useSolidColor = true; - const ahole = holes[ h ]; + } - oneHoleMovements = []; + for ( let i = 0; i < 6; i ++ ) { - for ( let i = 0, il = ahole.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) { + const col = i % 3; - if ( j === il ) j = 0; - if ( k === il ) k = 0; + if ( col === 0 ) { - // (j)---(i)---(k) - oneHoleMovements[ i ] = getBevelVec( ahole[ i ], ahole[ j ], ahole[ k ] ); + cubeCamera.up.set( 0, upSign[ i ], 0 ); + cubeCamera.position.set( position.x, position.y, position.z ); + cubeCamera.lookAt( position.x + forwardSign[ i ], position.y, position.z ); - } + } else if ( col === 1 ) { - holesMovements.push( oneHoleMovements ); - verticesMovements = verticesMovements.concat( oneHoleMovements ); + cubeCamera.up.set( 0, 0, upSign[ i ] ); + cubeCamera.position.set( position.x, position.y, position.z ); + cubeCamera.lookAt( position.x, position.y + forwardSign[ i ], position.z ); - } + } else { - // Loop bevelSegments, 1 for the front, 1 for the back + cubeCamera.up.set( 0, upSign[ i ], 0 ); + cubeCamera.position.set( position.x, position.y, position.z ); + cubeCamera.lookAt( position.x, position.y, position.z + forwardSign[ i ] ); - for ( let b = 0; b < bevelSegments; b ++ ) { + } - //for ( b = bevelSegments; b > 0; b -- ) { + const size = this._cubeSize; - const t = b / bevelSegments; - const z = bevelThickness * Math.cos( t * Math.PI / 2 ); - const bs = bevelSize * Math.sin( t * Math.PI / 2 ) + bevelOffset; + _setViewport( cubeUVRenderTarget, col * size, i > 2 ? size : 0, size, size ); - // contract shape + renderer.setRenderTarget( cubeUVRenderTarget ); - for ( let i = 0, il = contour.length; i < il; i ++ ) { + if ( useSolidColor ) { - const vert = scalePt2( contour[ i ], contourMovements[ i ], bs ); + renderer.render( backgroundBox, cubeCamera ); - v( vert.x, vert.y, - z ); + } - } + renderer.render( scene, cubeCamera ); - // expand holes + } - for ( let h = 0, hl = holes.length; h < hl; h ++ ) { + backgroundBox.geometry.dispose(); + backgroundBox.material.dispose(); - const ahole = holes[ h ]; - oneHoleMovements = holesMovements[ h ]; + renderer.toneMapping = toneMapping; + renderer.autoClear = originalAutoClear; + scene.background = background; - for ( let i = 0, il = ahole.length; i < il; i ++ ) { + } - const vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs ); + _textureToCubeUV( texture, cubeUVRenderTarget ) { - v( vert.x, vert.y, - z ); + const renderer = this._renderer; - } + const isCubeTexture = ( texture.mapping === CubeReflectionMapping || texture.mapping === CubeRefractionMapping ); - } + if ( isCubeTexture ) { - } + if ( this._cubemapMaterial === null ) { - const bs = bevelSize + bevelOffset; + this._cubemapMaterial = _getCubemapMaterial(); - // Back facing vertices + } - for ( let i = 0; i < vlen; i ++ ) { + this._cubemapMaterial.uniforms.flipEnvMap.value = ( texture.isRenderTargetTexture === false ) ? -1 : 1; - const vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ]; + } else { - if ( ! extrudeByPath ) { + if ( this._equirectMaterial === null ) { - v( vert.x, vert.y, 0 ); + this._equirectMaterial = _getEquirectMaterial(); - } else { + } - // v( vert.x, vert.y + extrudePts[ 0 ].y, extrudePts[ 0 ].x ); + } - normal.copy( splineTube.normals[ 0 ] ).multiplyScalar( vert.x ); - binormal.copy( splineTube.binormals[ 0 ] ).multiplyScalar( vert.y ); + const material = isCubeTexture ? this._cubemapMaterial : this._equirectMaterial; + const mesh = new Mesh( this._lodPlanes[ 0 ], material ); - position2.copy( extrudePts[ 0 ] ).add( normal ).add( binormal ); + const uniforms = material.uniforms; - v( position2.x, position2.y, position2.z ); + uniforms[ 'envMap' ].value = texture; - } + const size = this._cubeSize; - } + _setViewport( cubeUVRenderTarget, 0, 0, 3 * size, 2 * size ); - // Add stepped vertices... - // Including front facing vertices + renderer.setRenderTarget( cubeUVRenderTarget ); + renderer.render( mesh, _flatCamera ); - for ( let s = 1; s <= steps; s ++ ) { + } - for ( let i = 0; i < vlen; i ++ ) { + _applyPMREM( cubeUVRenderTarget ) { - const vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ]; + const renderer = this._renderer; + const autoClear = renderer.autoClear; + renderer.autoClear = false; + const n = this._lodPlanes.length; - if ( ! extrudeByPath ) { + for ( let i = 1; i < n; i ++ ) { - v( vert.x, vert.y, depth / steps * s ); + const sigma = Math.sqrt( this._sigmas[ i ] * this._sigmas[ i ] - this._sigmas[ i - 1 ] * this._sigmas[ i - 1 ] ); - } else { + const poleAxis = _axisDirections[ ( n - i - 1 ) % _axisDirections.length ]; - // v( vert.x, vert.y + extrudePts[ s - 1 ].y, extrudePts[ s - 1 ].x ); + this._blur( cubeUVRenderTarget, i - 1, i, sigma, poleAxis ); - normal.copy( splineTube.normals[ s ] ).multiplyScalar( vert.x ); - binormal.copy( splineTube.binormals[ s ] ).multiplyScalar( vert.y ); + } - position2.copy( extrudePts[ s ] ).add( normal ).add( binormal ); + renderer.autoClear = autoClear; - v( position2.x, position2.y, position2.z ); + } - } + /** + * This is a two-pass Gaussian blur for a cubemap. Normally this is done + * vertically and horizontally, but this breaks down on a cube. Here we apply + * the blur latitudinally (around the poles), and then longitudinally (towards + * the poles) to approximate the orthogonally-separable blur. It is least + * accurate at the poles, but still does a decent job. + * + * @private + * @param {WebGLRenderTarget} cubeUVRenderTarget + * @param {number} lodIn + * @param {number} lodOut + * @param {number} sigma + * @param {Vector3} [poleAxis] + */ + _blur( cubeUVRenderTarget, lodIn, lodOut, sigma, poleAxis ) { - } + const pingPongRenderTarget = this._pingPongRenderTarget; - } + this._halfBlur( + cubeUVRenderTarget, + pingPongRenderTarget, + lodIn, + lodOut, + sigma, + 'latitudinal', + poleAxis ); + this._halfBlur( + pingPongRenderTarget, + cubeUVRenderTarget, + lodOut, + lodOut, + sigma, + 'longitudinal', + poleAxis ); - // Add bevel segments planes + } - //for ( b = 1; b <= bevelSegments; b ++ ) { - for ( let b = bevelSegments - 1; b >= 0; b -- ) { + _halfBlur( targetIn, targetOut, lodIn, lodOut, sigmaRadians, direction, poleAxis ) { - const t = b / bevelSegments; - const z = bevelThickness * Math.cos( t * Math.PI / 2 ); - const bs = bevelSize * Math.sin( t * Math.PI / 2 ) + bevelOffset; + const renderer = this._renderer; + const blurMaterial = this._blurMaterial; - // contract shape + if ( direction !== 'latitudinal' && direction !== 'longitudinal' ) { - for ( let i = 0, il = contour.length; i < il; i ++ ) { + console.error( + 'blur direction must be either latitudinal or longitudinal!' ); - const vert = scalePt2( contour[ i ], contourMovements[ i ], bs ); - v( vert.x, vert.y, depth + z ); + } - } + // Number of standard deviations at which to cut off the discrete approximation. + const STANDARD_DEVIATIONS = 3; - // expand holes + const blurMesh = new Mesh( this._lodPlanes[ lodOut ], blurMaterial ); + const blurUniforms = blurMaterial.uniforms; - for ( let h = 0, hl = holes.length; h < hl; h ++ ) { + const pixels = this._sizeLods[ lodIn ] - 1; + const radiansPerPixel = isFinite( sigmaRadians ) ? Math.PI / ( 2 * pixels ) : 2 * Math.PI / ( 2 * MAX_SAMPLES - 1 ); + const sigmaPixels = sigmaRadians / radiansPerPixel; + const samples = isFinite( sigmaRadians ) ? 1 + Math.floor( STANDARD_DEVIATIONS * sigmaPixels ) : MAX_SAMPLES; - const ahole = holes[ h ]; - oneHoleMovements = holesMovements[ h ]; + if ( samples > MAX_SAMPLES ) { - for ( let i = 0, il = ahole.length; i < il; i ++ ) { + console.warn( `sigmaRadians, ${ + sigmaRadians}, is too large and will clip, as it requested ${ + samples} samples when the maximum is set to ${MAX_SAMPLES}` ); - const vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs ); + } - if ( ! extrudeByPath ) { + const weights = []; + let sum = 0; - v( vert.x, vert.y, depth + z ); + for ( let i = 0; i < MAX_SAMPLES; ++ i ) { - } else { + const x = i / sigmaPixels; + const weight = Math.exp( - x * x / 2 ); + weights.push( weight ); - v( vert.x, vert.y + extrudePts[ steps - 1 ].y, extrudePts[ steps - 1 ].x + z ); + if ( i === 0 ) { - } + sum += weight; - } + } else if ( i < samples ) { - } + sum += 2 * weight; } - /* Faces */ - - // Top and bottom faces - - buildLidFaces(); - - // Sides faces + } - buildSideFaces(); + for ( let i = 0; i < weights.length; i ++ ) { + weights[ i ] = weights[ i ] / sum; - ///// Internal functions + } - function buildLidFaces() { + blurUniforms[ 'envMap' ].value = targetIn.texture; + blurUniforms[ 'samples' ].value = samples; + blurUniforms[ 'weights' ].value = weights; + blurUniforms[ 'latitudinal' ].value = direction === 'latitudinal'; - const start = verticesArray.length / 3; + if ( poleAxis ) { - if ( bevelEnabled ) { + blurUniforms[ 'poleAxis' ].value = poleAxis; - let layer = 0; // steps + 1 - let offset = vlen * layer; + } - // Bottom faces + const { _lodMax } = this; + blurUniforms[ 'dTheta' ].value = radiansPerPixel; + blurUniforms[ 'mipInt' ].value = _lodMax - lodIn; - for ( let i = 0; i < flen; i ++ ) { + const outputSize = this._sizeLods[ lodOut ]; + const x = 3 * outputSize * ( lodOut > _lodMax - LOD_MIN ? lodOut - _lodMax + LOD_MIN : 0 ); + const y = 4 * ( this._cubeSize - outputSize ); - const face = faces[ i ]; - f3( face[ 2 ] + offset, face[ 1 ] + offset, face[ 0 ] + offset ); + _setViewport( targetOut, x, y, 3 * outputSize, 2 * outputSize ); + renderer.setRenderTarget( targetOut ); + renderer.render( blurMesh, _flatCamera ); - } + } - layer = steps + bevelSegments * 2; - offset = vlen * layer; +} - // Top faces - for ( let i = 0; i < flen; i ++ ) { - const face = faces[ i ]; - f3( face[ 0 ] + offset, face[ 1 ] + offset, face[ 2 ] + offset ); +function _createPlanes( lodMax ) { - } + const lodPlanes = []; + const sizeLods = []; + const sigmas = []; - } else { + let lod = lodMax; - // Bottom faces + const totalLods = lodMax - LOD_MIN + 1 + EXTRA_LOD_SIGMA.length; - for ( let i = 0; i < flen; i ++ ) { + for ( let i = 0; i < totalLods; i ++ ) { - const face = faces[ i ]; - f3( face[ 2 ], face[ 1 ], face[ 0 ] ); + const sizeLod = Math.pow( 2, lod ); + sizeLods.push( sizeLod ); + let sigma = 1.0 / sizeLod; - } + if ( i > lodMax - LOD_MIN ) { - // Top faces + sigma = EXTRA_LOD_SIGMA[ i - lodMax + LOD_MIN - 1 ]; - for ( let i = 0; i < flen; i ++ ) { + } else if ( i === 0 ) { - const face = faces[ i ]; - f3( face[ 0 ] + vlen * steps, face[ 1 ] + vlen * steps, face[ 2 ] + vlen * steps ); + sigma = 0; - } + } - } + sigmas.push( sigma ); - scope.addGroup( start, verticesArray.length / 3 - start, 0 ); + const texelSize = 1.0 / ( sizeLod - 2 ); + const min = - texelSize; + const max = 1 + texelSize; + const uv1 = [ min, min, max, min, max, max, min, min, max, max, min, max ]; - } + const cubeFaces = 6; + const vertices = 6; + const positionSize = 3; + const uvSize = 2; + const faceIndexSize = 1; - // Create faces for the z-sides of the shape + const position = new Float32Array( positionSize * vertices * cubeFaces ); + const uv = new Float32Array( uvSize * vertices * cubeFaces ); + const faceIndex = new Float32Array( faceIndexSize * vertices * cubeFaces ); - function buildSideFaces() { + for ( let face = 0; face < cubeFaces; face ++ ) { - const start = verticesArray.length / 3; - let layeroffset = 0; - sidewalls( contour, layeroffset ); - layeroffset += contour.length; + const x = ( face % 3 ) * 2 / 3 - 1; + const y = face > 2 ? 0 : -1; + const coordinates = [ + x, y, 0, + x + 2 / 3, y, 0, + x + 2 / 3, y + 1, 0, + x, y, 0, + x + 2 / 3, y + 1, 0, + x, y + 1, 0 + ]; + position.set( coordinates, positionSize * vertices * face ); + uv.set( uv1, uvSize * vertices * face ); + const fill = [ face, face, face, face, face, face ]; + faceIndex.set( fill, faceIndexSize * vertices * face ); - for ( let h = 0, hl = holes.length; h < hl; h ++ ) { + } - const ahole = holes[ h ]; - sidewalls( ahole, layeroffset ); + const planes = new BufferGeometry(); + planes.setAttribute( 'position', new BufferAttribute( position, positionSize ) ); + planes.setAttribute( 'uv', new BufferAttribute( uv, uvSize ) ); + planes.setAttribute( 'faceIndex', new BufferAttribute( faceIndex, faceIndexSize ) ); + lodPlanes.push( planes ); - //, true - layeroffset += ahole.length; + if ( lod > LOD_MIN ) { - } + lod --; + } - scope.addGroup( start, verticesArray.length / 3 - start, 1 ); + } + return { lodPlanes, sizeLods, sigmas }; - } +} - function sidewalls( contour, layeroffset ) { +function _createRenderTarget( width, height, params ) { - let i = contour.length; + const cubeUVRenderTarget = new WebGLRenderTarget( width, height, params ); + cubeUVRenderTarget.texture.mapping = CubeUVReflectionMapping; + cubeUVRenderTarget.texture.name = 'PMREM.cubeUv'; + cubeUVRenderTarget.scissorTest = true; + return cubeUVRenderTarget; - while ( -- i >= 0 ) { +} - const j = i; - let k = i - 1; - if ( k < 0 ) k = contour.length - 1; +function _setViewport( target, x, y, width, height ) { - //console.log('b', i,j, i-1, k,vertices.length); + target.viewport.set( x, y, width, height ); + target.scissor.set( x, y, width, height ); - for ( let s = 0, sl = ( steps + bevelSegments * 2 ); s < sl; s ++ ) { +} - const slen1 = vlen * s; - const slen2 = vlen * ( s + 1 ); +function _getBlurShader( lodMax, width, height ) { - const a = layeroffset + j + slen1, - b = layeroffset + k + slen1, - c = layeroffset + k + slen2, - d = layeroffset + j + slen2; + const weights = new Float32Array( MAX_SAMPLES ); + const poleAxis = new Vector3( 0, 1, 0 ); + const shaderMaterial = new ShaderMaterial( { - f4( a, b, c, d ); + name: 'SphericalGaussianBlur', - } + defines: { + 'n': MAX_SAMPLES, + 'CUBEUV_TEXEL_WIDTH': 1.0 / width, + 'CUBEUV_TEXEL_HEIGHT': 1.0 / height, + 'CUBEUV_MAX_MIP': `${lodMax}.0`, + }, - } + uniforms: { + 'envMap': { value: null }, + 'samples': { value: 1 }, + 'weights': { value: weights }, + 'latitudinal': { value: false }, + 'dTheta': { value: 0 }, + 'mipInt': { value: 0 }, + 'poleAxis': { value: poleAxis } + }, - } + vertexShader: _getCommonVertexShader(), - function v( x, y, z ) { + fragmentShader: /* glsl */` - placeholder.push( x ); - placeholder.push( y ); - placeholder.push( z ); + precision mediump float; + precision mediump int; - } + varying vec3 vOutputDirection; + uniform sampler2D envMap; + uniform int samples; + uniform float weights[ n ]; + uniform bool latitudinal; + uniform float dTheta; + uniform float mipInt; + uniform vec3 poleAxis; - function f3( a, b, c ) { + #define ENVMAP_TYPE_CUBE_UV + #include - addVertex( a ); - addVertex( b ); - addVertex( c ); + vec3 getSample( float theta, vec3 axis ) { - const nextIndex = verticesArray.length / 3; - const uvs = uvgen.generateTopUV( scope, verticesArray, nextIndex - 3, nextIndex - 2, nextIndex - 1 ); + float cosTheta = cos( theta ); + // Rodrigues' axis-angle rotation + vec3 sampleDirection = vOutputDirection * cosTheta + + cross( axis, vOutputDirection ) * sin( theta ) + + axis * dot( axis, vOutputDirection ) * ( 1.0 - cosTheta ); - addUV( uvs[ 0 ] ); - addUV( uvs[ 1 ] ); - addUV( uvs[ 2 ] ); + return bilinearCubeUV( envMap, sampleDirection, mipInt ); } - function f4( a, b, c, d ) { - - addVertex( a ); - addVertex( b ); - addVertex( d ); + void main() { - addVertex( b ); - addVertex( c ); - addVertex( d ); + vec3 axis = latitudinal ? poleAxis : cross( poleAxis, vOutputDirection ); + if ( all( equal( axis, vec3( 0.0 ) ) ) ) { - const nextIndex = verticesArray.length / 3; - const uvs = uvgen.generateSideWallUV( scope, verticesArray, nextIndex - 6, nextIndex - 3, nextIndex - 2, nextIndex - 1 ); + axis = vec3( vOutputDirection.z, 0.0, - vOutputDirection.x ); - addUV( uvs[ 0 ] ); - addUV( uvs[ 1 ] ); - addUV( uvs[ 3 ] ); + } - addUV( uvs[ 1 ] ); - addUV( uvs[ 2 ] ); - addUV( uvs[ 3 ] ); + axis = normalize( axis ); - } + gl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 ); + gl_FragColor.rgb += weights[ 0 ] * getSample( 0.0, axis ); - function addVertex( index ) { + for ( int i = 1; i < n; i++ ) { - verticesArray.push( placeholder[ index * 3 + 0 ] ); - verticesArray.push( placeholder[ index * 3 + 1 ] ); - verticesArray.push( placeholder[ index * 3 + 2 ] ); + if ( i >= samples ) { - } + break; + } - function addUV( vector2 ) { + float theta = dTheta * float( i ); + gl_FragColor.rgb += weights[ i ] * getSample( -1.0 * theta, axis ); + gl_FragColor.rgb += weights[ i ] * getSample( theta, axis ); - uvArray.push( vector2.x ); - uvArray.push( vector2.y ); + } } + `, - } - - } + blending: NoBlending, + depthTest: false, + depthWrite: false - copy( source ) { + } ); - super.copy( source ); + return shaderMaterial; - this.parameters = Object.assign( {}, source.parameters ); +} - return this; +function _getEquirectMaterial() { - } + return new ShaderMaterial( { - toJSON() { + name: 'EquirectangularToCubeUV', - const data = super.toJSON(); + uniforms: { + 'envMap': { value: null } + }, - const shapes = this.parameters.shapes; - const options = this.parameters.options; + vertexShader: _getCommonVertexShader(), - return toJSON$1( shapes, options, data ); + fragmentShader: /* glsl */` - } + precision mediump float; + precision mediump int; - static fromJSON( data, shapes ) { + varying vec3 vOutputDirection; - const geometryShapes = []; + uniform sampler2D envMap; - for ( let j = 0, jl = data.shapes.length; j < jl; j ++ ) { + #include - const shape = shapes[ data.shapes[ j ] ]; + void main() { - geometryShapes.push( shape ); + vec3 outputDirection = normalize( vOutputDirection ); + vec2 uv = equirectUv( outputDirection ); - } + gl_FragColor = vec4( texture2D ( envMap, uv ).rgb, 1.0 ); - const extrudePath = data.options.extrudePath; + } + `, - if ( extrudePath !== undefined ) { + blending: NoBlending, + depthTest: false, + depthWrite: false - data.options.extrudePath = new Curves[ extrudePath.type ]().fromJSON( extrudePath ); + } ); - } +} - return new ExtrudeGeometry( geometryShapes, data.options ); +function _getCubemapMaterial() { - } + return new ShaderMaterial( { -} + name: 'CubemapToCubeUV', -const WorldUVGenerator = { + uniforms: { + 'envMap': { value: null }, + 'flipEnvMap': { value: -1 } + }, - generateTopUV: function ( geometry, vertices, indexA, indexB, indexC ) { + vertexShader: _getCommonVertexShader(), - const a_x = vertices[ indexA * 3 ]; - const a_y = vertices[ indexA * 3 + 1 ]; - const b_x = vertices[ indexB * 3 ]; - const b_y = vertices[ indexB * 3 + 1 ]; - const c_x = vertices[ indexC * 3 ]; - const c_y = vertices[ indexC * 3 + 1 ]; + fragmentShader: /* glsl */` - return [ - new Vector2( a_x, a_y ), - new Vector2( b_x, b_y ), - new Vector2( c_x, c_y ) - ]; + precision mediump float; + precision mediump int; - }, + uniform float flipEnvMap; - generateSideWallUV: function ( geometry, vertices, indexA, indexB, indexC, indexD ) { - - const a_x = vertices[ indexA * 3 ]; - const a_y = vertices[ indexA * 3 + 1 ]; - const a_z = vertices[ indexA * 3 + 2 ]; - const b_x = vertices[ indexB * 3 ]; - const b_y = vertices[ indexB * 3 + 1 ]; - const b_z = vertices[ indexB * 3 + 2 ]; - const c_x = vertices[ indexC * 3 ]; - const c_y = vertices[ indexC * 3 + 1 ]; - const c_z = vertices[ indexC * 3 + 2 ]; - const d_x = vertices[ indexD * 3 ]; - const d_y = vertices[ indexD * 3 + 1 ]; - const d_z = vertices[ indexD * 3 + 2 ]; - - if ( Math.abs( a_y - b_y ) < Math.abs( a_x - b_x ) ) { - - return [ - new Vector2( a_x, 1 - a_z ), - new Vector2( b_x, 1 - b_z ), - new Vector2( c_x, 1 - c_z ), - new Vector2( d_x, 1 - d_z ) - ]; + varying vec3 vOutputDirection; - } else { + uniform samplerCube envMap; - return [ - new Vector2( a_y, 1 - a_z ), - new Vector2( b_y, 1 - b_z ), - new Vector2( c_y, 1 - c_z ), - new Vector2( d_y, 1 - d_z ) - ]; + void main() { - } + gl_FragColor = textureCube( envMap, vec3( flipEnvMap * vOutputDirection.x, vOutputDirection.yz ) ); - } + } + `, -}; + blending: NoBlending, + depthTest: false, + depthWrite: false -function toJSON$1( shapes, options, data ) { + } ); - data.shapes = []; +} - if ( Array.isArray( shapes ) ) { +function _getCommonVertexShader() { - for ( let i = 0, l = shapes.length; i < l; i ++ ) { + return /* glsl */` - const shape = shapes[ i ]; + precision mediump float; + precision mediump int; - data.shapes.push( shape.uuid ); + attribute float faceIndex; - } + varying vec3 vOutputDirection; - } else { + // RH coordinate system; PMREM face-indexing convention + vec3 getDirection( vec2 uv, float face ) { - data.shapes.push( shapes.uuid ); + uv = 2.0 * uv - 1.0; - } + vec3 direction = vec3( uv, 1.0 ); - data.options = Object.assign( {}, options ); + if ( face == 0.0 ) { - if ( options.extrudePath !== undefined ) data.options.extrudePath = options.extrudePath.toJSON(); + direction = direction.zyx; // ( 1, v, u ) pos x - return data; + } else if ( face == 1.0 ) { -} + direction = direction.xzy; + direction.xz *= -1.0; // ( -u, 1, -v ) pos y -class IcosahedronGeometry extends PolyhedronGeometry { + } else if ( face == 2.0 ) { - constructor( radius = 1, detail = 0 ) { + direction.x *= -1.0; // ( -u, v, 1 ) pos z - const t = ( 1 + Math.sqrt( 5 ) ) / 2; + } else if ( face == 3.0 ) { - const vertices = [ - - 1, t, 0, 1, t, 0, - 1, - t, 0, 1, - t, 0, - 0, - 1, t, 0, 1, t, 0, - 1, - t, 0, 1, - t, - t, 0, - 1, t, 0, 1, - t, 0, - 1, - t, 0, 1 - ]; + direction = direction.zyx; + direction.xz *= -1.0; // ( -1, v, -u ) neg x - const indices = [ - 0, 11, 5, 0, 5, 1, 0, 1, 7, 0, 7, 10, 0, 10, 11, - 1, 5, 9, 5, 11, 4, 11, 10, 2, 10, 7, 6, 7, 1, 8, - 3, 9, 4, 3, 4, 2, 3, 2, 6, 3, 6, 8, 3, 8, 9, - 4, 9, 5, 2, 4, 11, 6, 2, 10, 8, 6, 7, 9, 8, 1 - ]; + } else if ( face == 4.0 ) { - super( vertices, indices, radius, detail ); + direction = direction.xzy; + direction.xy *= -1.0; // ( -u, -1, v ) neg y - this.type = 'IcosahedronGeometry'; + } else if ( face == 5.0 ) { - this.parameters = { - radius: radius, - detail: detail - }; + direction.z *= -1.0; // ( u, v, -1 ) neg z - } + } - static fromJSON( data ) { + return direction; - return new IcosahedronGeometry( data.radius, data.detail ); + } - } + void main() { -} + vOutputDirection = getDirection( uv, faceIndex ); + gl_Position = vec4( position, 1.0 ); -class OctahedronGeometry extends PolyhedronGeometry { + } + `; - constructor( radius = 1, detail = 0 ) { +} - const vertices = [ - 1, 0, 0, - 1, 0, 0, 0, 1, 0, - 0, - 1, 0, 0, 0, 1, 0, 0, - 1 - ]; +function WebGLCubeUVMaps( renderer ) { - const indices = [ - 0, 2, 4, 0, 4, 3, 0, 3, 5, - 0, 5, 2, 1, 2, 5, 1, 5, 3, - 1, 3, 4, 1, 4, 2 - ]; + let cubeUVmaps = new WeakMap(); - super( vertices, indices, radius, detail ); + let pmremGenerator = null; - this.type = 'OctahedronGeometry'; + function get( texture ) { - this.parameters = { - radius: radius, - detail: detail - }; + if ( texture && texture.isTexture ) { - } + const mapping = texture.mapping; - static fromJSON( data ) { + const isEquirectMap = ( mapping === EquirectangularReflectionMapping || mapping === EquirectangularRefractionMapping ); + const isCubeMap = ( mapping === CubeReflectionMapping || mapping === CubeRefractionMapping ); - return new OctahedronGeometry( data.radius, data.detail ); + // equirect/cube map to cubeUV conversion - } + if ( isEquirectMap || isCubeMap ) { -} + let renderTarget = cubeUVmaps.get( texture ); -class RingGeometry extends BufferGeometry { + const currentPMREMVersion = renderTarget !== undefined ? renderTarget.texture.pmremVersion : 0; - constructor( innerRadius = 0.5, outerRadius = 1, thetaSegments = 32, phiSegments = 1, thetaStart = 0, thetaLength = Math.PI * 2 ) { + if ( texture.isRenderTargetTexture && texture.pmremVersion !== currentPMREMVersion ) { - super(); + if ( pmremGenerator === null ) pmremGenerator = new PMREMGenerator( renderer ); - this.type = 'RingGeometry'; + renderTarget = isEquirectMap ? pmremGenerator.fromEquirectangular( texture, renderTarget ) : pmremGenerator.fromCubemap( texture, renderTarget ); + renderTarget.texture.pmremVersion = texture.pmremVersion; - this.parameters = { - innerRadius: innerRadius, - outerRadius: outerRadius, - thetaSegments: thetaSegments, - phiSegments: phiSegments, - thetaStart: thetaStart, - thetaLength: thetaLength - }; + cubeUVmaps.set( texture, renderTarget ); - thetaSegments = Math.max( 3, thetaSegments ); - phiSegments = Math.max( 1, phiSegments ); + return renderTarget.texture; - // buffers + } else { - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; + if ( renderTarget !== undefined ) { - // some helper variables + return renderTarget.texture; - let radius = innerRadius; - const radiusStep = ( ( outerRadius - innerRadius ) / phiSegments ); - const vertex = new Vector3(); - const uv = new Vector2(); + } else { - // generate vertices, normals and uvs + const image = texture.image; - for ( let j = 0; j <= phiSegments; j ++ ) { + if ( ( isEquirectMap && image && image.height > 0 ) || ( isCubeMap && image && isCubeTextureComplete( image ) ) ) { - for ( let i = 0; i <= thetaSegments; i ++ ) { + if ( pmremGenerator === null ) pmremGenerator = new PMREMGenerator( renderer ); - // values are generate from the inside of the ring to the outside + renderTarget = isEquirectMap ? pmremGenerator.fromEquirectangular( texture ) : pmremGenerator.fromCubemap( texture ); + renderTarget.texture.pmremVersion = texture.pmremVersion; - const segment = thetaStart + i / thetaSegments * thetaLength; + cubeUVmaps.set( texture, renderTarget ); - // vertex + texture.addEventListener( 'dispose', onTextureDispose ); - vertex.x = radius * Math.cos( segment ); - vertex.y = radius * Math.sin( segment ); + return renderTarget.texture; - vertices.push( vertex.x, vertex.y, vertex.z ); + } else { - // normal + // image not yet ready. try the conversion next frame - normals.push( 0, 0, 1 ); + return null; - // uv + } - uv.x = ( vertex.x / outerRadius + 1 ) / 2; - uv.y = ( vertex.y / outerRadius + 1 ) / 2; + } - uvs.push( uv.x, uv.y ); + } } - // increase the radius for next row of vertices - - radius += radiusStep; - } - // indices - - for ( let j = 0; j < phiSegments; j ++ ) { - - const thetaSegmentLevel = j * ( thetaSegments + 1 ); - - for ( let i = 0; i < thetaSegments; i ++ ) { + return texture; - const segment = i + thetaSegmentLevel; + } - const a = segment; - const b = segment + thetaSegments + 1; - const c = segment + thetaSegments + 2; - const d = segment + 1; + function isCubeTextureComplete( image ) { - // faces + let count = 0; + const length = 6; - indices.push( a, b, d ); - indices.push( b, c, d ); + for ( let i = 0; i < length; i ++ ) { - } + if ( image[ i ] !== undefined ) count ++; } - // build geometry + return count === length; - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); } - copy( source ) { + function onTextureDispose( event ) { - super.copy( source ); + const texture = event.target; - this.parameters = Object.assign( {}, source.parameters ); + texture.removeEventListener( 'dispose', onTextureDispose ); - return this; + const cubemapUV = cubeUVmaps.get( texture ); - } + if ( cubemapUV !== undefined ) { - static fromJSON( data ) { + cubeUVmaps.delete( texture ); + cubemapUV.dispose(); - return new RingGeometry( data.innerRadius, data.outerRadius, data.thetaSegments, data.phiSegments, data.thetaStart, data.thetaLength ); + } } -} + function dispose() { -class ShapeGeometry extends BufferGeometry { + cubeUVmaps = new WeakMap(); - constructor( shapes = new Shape( [ new Vector2( 0, 0.5 ), new Vector2( - 0.5, - 0.5 ), new Vector2( 0.5, - 0.5 ) ] ), curveSegments = 12 ) { + if ( pmremGenerator !== null ) { - super(); + pmremGenerator.dispose(); + pmremGenerator = null; - this.type = 'ShapeGeometry'; + } - this.parameters = { - shapes: shapes, - curveSegments: curveSegments - }; + } - // buffers + return { + get: get, + dispose: dispose + }; - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; +} - // helper variables +function WebGLExtensions( gl ) { - let groupStart = 0; - let groupCount = 0; + const extensions = {}; - // allow single and array values for "shapes" parameter + function getExtension( name ) { - if ( Array.isArray( shapes ) === false ) { + if ( extensions[ name ] !== undefined ) { - addShape( shapes ); + return extensions[ name ]; - } else { + } - for ( let i = 0; i < shapes.length; i ++ ) { + let extension; - addShape( shapes[ i ] ); + switch ( name ) { - this.addGroup( groupStart, groupCount, i ); // enables MultiMaterial support + case 'WEBGL_depth_texture': + extension = gl.getExtension( 'WEBGL_depth_texture' ) || gl.getExtension( 'MOZ_WEBGL_depth_texture' ) || gl.getExtension( 'WEBKIT_WEBGL_depth_texture' ); + break; - groupStart += groupCount; - groupCount = 0; + case 'EXT_texture_filter_anisotropic': + extension = gl.getExtension( 'EXT_texture_filter_anisotropic' ) || gl.getExtension( 'MOZ_EXT_texture_filter_anisotropic' ) || gl.getExtension( 'WEBKIT_EXT_texture_filter_anisotropic' ); + break; - } + case 'WEBGL_compressed_texture_s3tc': + extension = gl.getExtension( 'WEBGL_compressed_texture_s3tc' ) || gl.getExtension( 'MOZ_WEBGL_compressed_texture_s3tc' ) || gl.getExtension( 'WEBKIT_WEBGL_compressed_texture_s3tc' ); + break; - } + case 'WEBGL_compressed_texture_pvrtc': + extension = gl.getExtension( 'WEBGL_compressed_texture_pvrtc' ) || gl.getExtension( 'WEBKIT_WEBGL_compressed_texture_pvrtc' ); + break; - // build geometry + default: + extension = gl.getExtension( name ); - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + } + extensions[ name ] = extension; - // helper functions + return extension; - function addShape( shape ) { + } - const indexOffset = vertices.length / 3; - const points = shape.extractPoints( curveSegments ); + return { - let shapeVertices = points.shape; - const shapeHoles = points.holes; + has: function ( name ) { - // check direction of vertices + return getExtension( name ) !== null; - if ( ShapeUtils.isClockWise( shapeVertices ) === false ) { + }, - shapeVertices = shapeVertices.reverse(); + init: function () { - } + getExtension( 'EXT_color_buffer_float' ); + getExtension( 'WEBGL_clip_cull_distance' ); + getExtension( 'OES_texture_float_linear' ); + getExtension( 'EXT_color_buffer_half_float' ); + getExtension( 'WEBGL_multisampled_render_to_texture' ); + getExtension( 'WEBGL_render_shared_exponent' ); - for ( let i = 0, l = shapeHoles.length; i < l; i ++ ) { + }, - const shapeHole = shapeHoles[ i ]; + get: function ( name ) { - if ( ShapeUtils.isClockWise( shapeHole ) === true ) { + const extension = getExtension( name ); - shapeHoles[ i ] = shapeHole.reverse(); + if ( extension === null ) { - } + warnOnce( 'THREE.WebGLRenderer: ' + name + ' extension not supported.' ); } - const faces = ShapeUtils.triangulateShape( shapeVertices, shapeHoles ); - - // join vertices of inner and outer paths to a single array + return extension; - for ( let i = 0, l = shapeHoles.length; i < l; i ++ ) { + } - const shapeHole = shapeHoles[ i ]; - shapeVertices = shapeVertices.concat( shapeHole ); + }; - } +} - // vertices, normals, uvs +function WebGLGeometries( gl, attributes, info, bindingStates ) { - for ( let i = 0, l = shapeVertices.length; i < l; i ++ ) { + const geometries = {}; + const wireframeAttributes = new WeakMap(); - const vertex = shapeVertices[ i ]; + function onGeometryDispose( event ) { - vertices.push( vertex.x, vertex.y, 0 ); - normals.push( 0, 0, 1 ); - uvs.push( vertex.x, vertex.y ); // world uvs + const geometry = event.target; - } + if ( geometry.index !== null ) { - // indices + attributes.remove( geometry.index ); - for ( let i = 0, l = faces.length; i < l; i ++ ) { + } - const face = faces[ i ]; + for ( const name in geometry.attributes ) { - const a = face[ 0 ] + indexOffset; - const b = face[ 1 ] + indexOffset; - const c = face[ 2 ] + indexOffset; + attributes.remove( geometry.attributes[ name ] ); - indices.push( a, b, c ); - groupCount += 3; + } - } + geometry.removeEventListener( 'dispose', onGeometryDispose ); - } + delete geometries[ geometry.id ]; - } + const attribute = wireframeAttributes.get( geometry ); - copy( source ) { + if ( attribute ) { - super.copy( source ); + attributes.remove( attribute ); + wireframeAttributes.delete( geometry ); - this.parameters = Object.assign( {}, source.parameters ); + } - return this; + bindingStates.releaseStatesOfGeometry( geometry ); - } + if ( geometry.isInstancedBufferGeometry === true ) { - toJSON() { + delete geometry._maxInstanceCount; - const data = super.toJSON(); + } - const shapes = this.parameters.shapes; + // - return toJSON( shapes, data ); + info.memory.geometries --; } - static fromJSON( data, shapes ) { - - const geometryShapes = []; + function get( object, geometry ) { - for ( let j = 0, jl = data.shapes.length; j < jl; j ++ ) { + if ( geometries[ geometry.id ] === true ) return geometry; - const shape = shapes[ data.shapes[ j ] ]; + geometry.addEventListener( 'dispose', onGeometryDispose ); - geometryShapes.push( shape ); + geometries[ geometry.id ] = true; - } + info.memory.geometries ++; - return new ShapeGeometry( geometryShapes, data.curveSegments ); + return geometry; } -} - -function toJSON( shapes, data ) { - - data.shapes = []; + function update( geometry ) { - if ( Array.isArray( shapes ) ) { + const geometryAttributes = geometry.attributes; - for ( let i = 0, l = shapes.length; i < l; i ++ ) { + // Updating index buffer in VAO now. See WebGLBindingStates. - const shape = shapes[ i ]; + for ( const name in geometryAttributes ) { - data.shapes.push( shape.uuid ); + attributes.update( geometryAttributes[ name ], gl.ARRAY_BUFFER ); } - } else { - - data.shapes.push( shapes.uuid ); - } - return data; - -} + function updateWireframeAttribute( geometry ) { -class SphereGeometry extends BufferGeometry { + const indices = []; - constructor( radius = 1, widthSegments = 32, heightSegments = 16, phiStart = 0, phiLength = Math.PI * 2, thetaStart = 0, thetaLength = Math.PI ) { + const geometryIndex = geometry.index; + const geometryPosition = geometry.attributes.position; + let version = 0; - super(); + if ( geometryIndex !== null ) { - this.type = 'SphereGeometry'; + const array = geometryIndex.array; + version = geometryIndex.version; - this.parameters = { - radius: radius, - widthSegments: widthSegments, - heightSegments: heightSegments, - phiStart: phiStart, - phiLength: phiLength, - thetaStart: thetaStart, - thetaLength: thetaLength - }; + for ( let i = 0, l = array.length; i < l; i += 3 ) { - widthSegments = Math.max( 3, Math.floor( widthSegments ) ); - heightSegments = Math.max( 2, Math.floor( heightSegments ) ); + const a = array[ i + 0 ]; + const b = array[ i + 1 ]; + const c = array[ i + 2 ]; - const thetaEnd = Math.min( thetaStart + thetaLength, Math.PI ); + indices.push( a, b, b, c, c, a ); - let index = 0; - const grid = []; + } - const vertex = new Vector3(); - const normal = new Vector3(); + } else if ( geometryPosition !== undefined ) { - // buffers + const array = geometryPosition.array; + version = geometryPosition.version; - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; + for ( let i = 0, l = ( array.length / 3 ) - 1; i < l; i += 3 ) { - // generate vertices, normals and uvs + const a = i + 0; + const b = i + 1; + const c = i + 2; - for ( let iy = 0; iy <= heightSegments; iy ++ ) { + indices.push( a, b, b, c, c, a ); - const verticesRow = []; + } - const v = iy / heightSegments; + } else { - // special case for the poles + return; - let uOffset = 0; + } - if ( iy === 0 && thetaStart === 0 ) { + const attribute = new ( arrayNeedsUint32( indices ) ? Uint32BufferAttribute : Uint16BufferAttribute )( indices, 1 ); + attribute.version = version; - uOffset = 0.5 / widthSegments; + // Updating index buffer in VAO now. See WebGLBindingStates - } else if ( iy === heightSegments && thetaEnd === Math.PI ) { + // - uOffset = - 0.5 / widthSegments; + const previousAttribute = wireframeAttributes.get( geometry ); - } + if ( previousAttribute ) attributes.remove( previousAttribute ); - for ( let ix = 0; ix <= widthSegments; ix ++ ) { + // - const u = ix / widthSegments; + wireframeAttributes.set( geometry, attribute ); - // vertex + } - vertex.x = - radius * Math.cos( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength ); - vertex.y = radius * Math.cos( thetaStart + v * thetaLength ); - vertex.z = radius * Math.sin( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength ); + function getWireframeAttribute( geometry ) { - vertices.push( vertex.x, vertex.y, vertex.z ); + const currentAttribute = wireframeAttributes.get( geometry ); - // normal + if ( currentAttribute ) { - normal.copy( vertex ).normalize(); - normals.push( normal.x, normal.y, normal.z ); + const geometryIndex = geometry.index; - // uv + if ( geometryIndex !== null ) { - uvs.push( u + uOffset, 1 - v ); + // if the attribute is obsolete, create a new one - verticesRow.push( index ++ ); + if ( currentAttribute.version < geometryIndex.version ) { - } + updateWireframeAttribute( geometry ); - grid.push( verticesRow ); + } - } + } - // indices + } else { - for ( let iy = 0; iy < heightSegments; iy ++ ) { + updateWireframeAttribute( geometry ); - for ( let ix = 0; ix < widthSegments; ix ++ ) { + } - const a = grid[ iy ][ ix + 1 ]; - const b = grid[ iy ][ ix ]; - const c = grid[ iy + 1 ][ ix ]; - const d = grid[ iy + 1 ][ ix + 1 ]; + return wireframeAttributes.get( geometry ); - if ( iy !== 0 || thetaStart > 0 ) indices.push( a, b, d ); - if ( iy !== heightSegments - 1 || thetaEnd < Math.PI ) indices.push( b, c, d ); + } - } + return { - } + get: get, + update: update, - // build geometry + getWireframeAttribute: getWireframeAttribute - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + }; - } +} - copy( source ) { +function WebGLIndexedBufferRenderer( gl, extensions, info ) { - super.copy( source ); + let mode; - this.parameters = Object.assign( {}, source.parameters ); + function setMode( value ) { - return this; + mode = value; } - static fromJSON( data ) { + let type, bytesPerElement; + + function setIndex( value ) { - return new SphereGeometry( data.radius, data.widthSegments, data.heightSegments, data.phiStart, data.phiLength, data.thetaStart, data.thetaLength ); + type = value.type; + bytesPerElement = value.bytesPerElement; } -} + function render( start, count ) { -class TetrahedronGeometry extends PolyhedronGeometry { + gl.drawElements( mode, count, type, start * bytesPerElement ); - constructor( radius = 1, detail = 0 ) { + info.update( count, mode, 1 ); - const vertices = [ - 1, 1, 1, - 1, - 1, 1, - 1, 1, - 1, 1, - 1, - 1 - ]; + } - const indices = [ - 2, 1, 0, 0, 3, 2, 1, 3, 0, 2, 3, 1 - ]; + function renderInstances( start, count, primcount ) { - super( vertices, indices, radius, detail ); + if ( primcount === 0 ) return; - this.type = 'TetrahedronGeometry'; + gl.drawElementsInstanced( mode, count, type, start * bytesPerElement, primcount ); - this.parameters = { - radius: radius, - detail: detail - }; + info.update( count, mode, primcount ); } - static fromJSON( data ) { + function renderMultiDraw( starts, counts, drawCount ) { - return new TetrahedronGeometry( data.radius, data.detail ); + if ( drawCount === 0 ) return; - } + const extension = extensions.get( 'WEBGL_multi_draw' ); + extension.multiDrawElementsWEBGL( mode, counts, 0, type, starts, 0, drawCount ); -} + let elementCount = 0; + for ( let i = 0; i < drawCount; i ++ ) { -class TorusGeometry extends BufferGeometry { + elementCount += counts[ i ]; - constructor( radius = 1, tube = 0.4, radialSegments = 12, tubularSegments = 48, arc = Math.PI * 2 ) { + } - super(); + info.update( elementCount, mode, 1 ); - this.type = 'TorusGeometry'; - this.parameters = { - radius: radius, - tube: tube, - radialSegments: radialSegments, - tubularSegments: tubularSegments, - arc: arc - }; + } - radialSegments = Math.floor( radialSegments ); - tubularSegments = Math.floor( tubularSegments ); + function renderMultiDrawInstances( starts, counts, drawCount, primcount ) { - // buffers + if ( drawCount === 0 ) return; - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; + const extension = extensions.get( 'WEBGL_multi_draw' ); - // helper variables + if ( extension === null ) { - const center = new Vector3(); - const vertex = new Vector3(); - const normal = new Vector3(); + for ( let i = 0; i < starts.length; i ++ ) { - // generate vertices, normals and uvs + renderInstances( starts[ i ] / bytesPerElement, counts[ i ], primcount[ i ] ); + + } - for ( let j = 0; j <= radialSegments; j ++ ) { + } else { - for ( let i = 0; i <= tubularSegments; i ++ ) { + extension.multiDrawElementsInstancedWEBGL( mode, counts, 0, type, starts, 0, primcount, 0, drawCount ); - const u = i / tubularSegments * arc; - const v = j / radialSegments * Math.PI * 2; + let elementCount = 0; + for ( let i = 0; i < drawCount; i ++ ) { - // vertex + elementCount += counts[ i ] * primcount[ i ]; - vertex.x = ( radius + tube * Math.cos( v ) ) * Math.cos( u ); - vertex.y = ( radius + tube * Math.cos( v ) ) * Math.sin( u ); - vertex.z = tube * Math.sin( v ); + } - vertices.push( vertex.x, vertex.y, vertex.z ); + info.update( elementCount, mode, 1 ); - // normal + } - center.x = radius * Math.cos( u ); - center.y = radius * Math.sin( u ); - normal.subVectors( vertex, center ).normalize(); + } - normals.push( normal.x, normal.y, normal.z ); + // - // uv + this.setMode = setMode; + this.setIndex = setIndex; + this.render = render; + this.renderInstances = renderInstances; + this.renderMultiDraw = renderMultiDraw; + this.renderMultiDrawInstances = renderMultiDrawInstances; - uvs.push( i / tubularSegments ); - uvs.push( j / radialSegments ); +} - } +function WebGLInfo( gl ) { - } + const memory = { + geometries: 0, + textures: 0 + }; - // generate indices + const render = { + frame: 0, + calls: 0, + triangles: 0, + points: 0, + lines: 0 + }; - for ( let j = 1; j <= radialSegments; j ++ ) { + function update( count, mode, instanceCount ) { - for ( let i = 1; i <= tubularSegments; i ++ ) { + render.calls ++; - // indices + switch ( mode ) { - const a = ( tubularSegments + 1 ) * j + i - 1; - const b = ( tubularSegments + 1 ) * ( j - 1 ) + i - 1; - const c = ( tubularSegments + 1 ) * ( j - 1 ) + i; - const d = ( tubularSegments + 1 ) * j + i; + case gl.TRIANGLES: + render.triangles += instanceCount * ( count / 3 ); + break; - // faces + case gl.LINES: + render.lines += instanceCount * ( count / 2 ); + break; - indices.push( a, b, d ); - indices.push( b, c, d ); + case gl.LINE_STRIP: + render.lines += instanceCount * ( count - 1 ); + break; - } + case gl.LINE_LOOP: + render.lines += instanceCount * count; + break; - } + case gl.POINTS: + render.points += instanceCount * count; + break; - // build geometry + default: + console.error( 'THREE.WebGLInfo: Unknown draw mode:', mode ); + break; - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + } } - copy( source ) { + function reset() { - super.copy( source ); + render.calls = 0; + render.triangles = 0; + render.points = 0; + render.lines = 0; - this.parameters = Object.assign( {}, source.parameters ); + } - return this; + return { + memory: memory, + render: render, + programs: null, + autoReset: true, + reset: reset, + update: update + }; - } +} - static fromJSON( data ) { +function WebGLMorphtargets( gl, capabilities, textures ) { - return new TorusGeometry( data.radius, data.tube, data.radialSegments, data.tubularSegments, data.arc ); + const morphTextures = new WeakMap(); + const morph = new Vector4(); - } + function update( object, geometry, program ) { -} + const objectInfluences = object.morphTargetInfluences; -class TorusKnotGeometry extends BufferGeometry { + // the following encodes morph targets into an array of data textures. Each layer represents a single morph target. - constructor( radius = 1, tube = 0.4, tubularSegments = 64, radialSegments = 8, p = 2, q = 3 ) { + const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color; + const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0; - super(); + let entry = morphTextures.get( geometry ); - this.type = 'TorusKnotGeometry'; + if ( entry === undefined || entry.count !== morphTargetsCount ) { - this.parameters = { - radius: radius, - tube: tube, - tubularSegments: tubularSegments, - radialSegments: radialSegments, - p: p, - q: q - }; + if ( entry !== undefined ) entry.texture.dispose(); - tubularSegments = Math.floor( tubularSegments ); - radialSegments = Math.floor( radialSegments ); + const hasMorphPosition = geometry.morphAttributes.position !== undefined; + const hasMorphNormals = geometry.morphAttributes.normal !== undefined; + const hasMorphColors = geometry.morphAttributes.color !== undefined; - // buffers + const morphTargets = geometry.morphAttributes.position || []; + const morphNormals = geometry.morphAttributes.normal || []; + const morphColors = geometry.morphAttributes.color || []; - const indices = []; - const vertices = []; - const normals = []; - const uvs = []; + let vertexDataCount = 0; - // helper variables + if ( hasMorphPosition === true ) vertexDataCount = 1; + if ( hasMorphNormals === true ) vertexDataCount = 2; + if ( hasMorphColors === true ) vertexDataCount = 3; - const vertex = new Vector3(); - const normal = new Vector3(); + let width = geometry.attributes.position.count * vertexDataCount; + let height = 1; - const P1 = new Vector3(); - const P2 = new Vector3(); + if ( width > capabilities.maxTextureSize ) { - const B = new Vector3(); - const T = new Vector3(); - const N = new Vector3(); + height = Math.ceil( width / capabilities.maxTextureSize ); + width = capabilities.maxTextureSize; - // generate vertices, normals and uvs + } - for ( let i = 0; i <= tubularSegments; ++ i ) { + const buffer = new Float32Array( width * height * 4 * morphTargetsCount ); - // the radian "u" is used to calculate the position on the torus curve of the current tubular segment + const texture = new DataArrayTexture( buffer, width, height, morphTargetsCount ); + texture.type = FloatType; + texture.needsUpdate = true; - const u = i / tubularSegments * p * Math.PI * 2; + // fill buffer - // now we calculate two points. P1 is our current position on the curve, P2 is a little farther ahead. - // these points are used to create a special "coordinate space", which is necessary to calculate the correct vertex positions + const vertexDataStride = vertexDataCount * 4; - calculatePositionOnCurve( u, p, q, radius, P1 ); - calculatePositionOnCurve( u + 0.01, p, q, radius, P2 ); + for ( let i = 0; i < morphTargetsCount; i ++ ) { - // calculate orthonormal basis + const morphTarget = morphTargets[ i ]; + const morphNormal = morphNormals[ i ]; + const morphColor = morphColors[ i ]; - T.subVectors( P2, P1 ); - N.addVectors( P2, P1 ); - B.crossVectors( T, N ); - N.crossVectors( B, T ); + const offset = width * height * 4 * i; - // normalize B, N. T can be ignored, we don't use it + for ( let j = 0; j < morphTarget.count; j ++ ) { - B.normalize(); - N.normalize(); + const stride = j * vertexDataStride; - for ( let j = 0; j <= radialSegments; ++ j ) { + if ( hasMorphPosition === true ) { - // now calculate the vertices. they are nothing more than an extrusion of the torus curve. - // because we extrude a shape in the xy-plane, there is no need to calculate a z-value. + morph.fromBufferAttribute( morphTarget, j ); - const v = j / radialSegments * Math.PI * 2; - const cx = - tube * Math.cos( v ); - const cy = tube * Math.sin( v ); + buffer[ offset + stride + 0 ] = morph.x; + buffer[ offset + stride + 1 ] = morph.y; + buffer[ offset + stride + 2 ] = morph.z; + buffer[ offset + stride + 3 ] = 0; - // now calculate the final vertex position. - // first we orient the extrusion with our basis vectors, then we add it to the current position on the curve + } - vertex.x = P1.x + ( cx * N.x + cy * B.x ); - vertex.y = P1.y + ( cx * N.y + cy * B.y ); - vertex.z = P1.z + ( cx * N.z + cy * B.z ); + if ( hasMorphNormals === true ) { - vertices.push( vertex.x, vertex.y, vertex.z ); + morph.fromBufferAttribute( morphNormal, j ); - // normal (P1 is always the center/origin of the extrusion, thus we can use it to calculate the normal) + buffer[ offset + stride + 4 ] = morph.x; + buffer[ offset + stride + 5 ] = morph.y; + buffer[ offset + stride + 6 ] = morph.z; + buffer[ offset + stride + 7 ] = 0; - normal.subVectors( vertex, P1 ).normalize(); + } - normals.push( normal.x, normal.y, normal.z ); + if ( hasMorphColors === true ) { - // uv + morph.fromBufferAttribute( morphColor, j ); - uvs.push( i / tubularSegments ); - uvs.push( j / radialSegments ); + buffer[ offset + stride + 8 ] = morph.x; + buffer[ offset + stride + 9 ] = morph.y; + buffer[ offset + stride + 10 ] = morph.z; + buffer[ offset + stride + 11 ] = ( morphColor.itemSize === 4 ) ? morph.w : 1; - } + } - } + } - // generate indices + } - for ( let j = 1; j <= tubularSegments; j ++ ) { + entry = { + count: morphTargetsCount, + texture: texture, + size: new Vector2( width, height ) + }; - for ( let i = 1; i <= radialSegments; i ++ ) { + morphTextures.set( geometry, entry ); - // indices + function disposeTexture() { - const a = ( radialSegments + 1 ) * ( j - 1 ) + ( i - 1 ); - const b = ( radialSegments + 1 ) * j + ( i - 1 ); - const c = ( radialSegments + 1 ) * j + i; - const d = ( radialSegments + 1 ) * ( j - 1 ) + i; + texture.dispose(); - // faces + morphTextures.delete( geometry ); - indices.push( a, b, d ); - indices.push( b, c, d ); + geometry.removeEventListener( 'dispose', disposeTexture ); } + geometry.addEventListener( 'dispose', disposeTexture ); + } - // build geometry + // + if ( object.isInstancedMesh === true && object.morphTexture !== null ) { - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + program.getUniforms().setValue( gl, 'morphTexture', object.morphTexture, textures ); - // this function calculates the current position on the torus curve + } else { - function calculatePositionOnCurve( u, p, q, radius, position ) { + let morphInfluencesSum = 0; - const cu = Math.cos( u ); - const su = Math.sin( u ); - const quOverP = q / p * u; - const cs = Math.cos( quOverP ); + for ( let i = 0; i < objectInfluences.length; i ++ ) { - position.x = radius * ( 2 + cs ) * 0.5 * cu; - position.y = radius * ( 2 + cs ) * su * 0.5; - position.z = radius * Math.sin( quOverP ) * 0.5; + morphInfluencesSum += objectInfluences[ i ]; - } + } - } + const morphBaseInfluence = geometry.morphTargetsRelative ? 1 : 1 - morphInfluencesSum; - copy( source ) { - super.copy( source ); + program.getUniforms().setValue( gl, 'morphTargetBaseInfluence', morphBaseInfluence ); + program.getUniforms().setValue( gl, 'morphTargetInfluences', objectInfluences ); - this.parameters = Object.assign( {}, source.parameters ); + } - return this; + program.getUniforms().setValue( gl, 'morphTargetsTexture', entry.texture, textures ); + program.getUniforms().setValue( gl, 'morphTargetsTextureSize', entry.size ); } - static fromJSON( data ) { + return { - return new TorusKnotGeometry( data.radius, data.tube, data.tubularSegments, data.radialSegments, data.p, data.q ); + update: update - } + }; } -class TubeGeometry extends BufferGeometry { +function WebGLObjects( gl, geometries, attributes, info ) { - constructor( path = new QuadraticBezierCurve3( new Vector3( - 1, - 1, 0 ), new Vector3( - 1, 1, 0 ), new Vector3( 1, 1, 0 ) ), tubularSegments = 64, radius = 1, radialSegments = 8, closed = false ) { + let updateMap = new WeakMap(); - super(); + function update( object ) { - this.type = 'TubeGeometry'; + const frame = info.render.frame; - this.parameters = { - path: path, - tubularSegments: tubularSegments, - radius: radius, - radialSegments: radialSegments, - closed: closed - }; + const geometry = object.geometry; + const buffergeometry = geometries.get( object, geometry ); - const frames = path.computeFrenetFrames( tubularSegments, closed ); + // Update once per frame - // expose internals + if ( updateMap.get( buffergeometry ) !== frame ) { - this.tangents = frames.tangents; - this.normals = frames.normals; - this.binormals = frames.binormals; + geometries.update( buffergeometry ); - // helper variables + updateMap.set( buffergeometry, frame ); - const vertex = new Vector3(); - const normal = new Vector3(); - const uv = new Vector2(); - let P = new Vector3(); + } - // buffer + if ( object.isInstancedMesh ) { - const vertices = []; - const normals = []; - const uvs = []; - const indices = []; + if ( object.hasEventListener( 'dispose', onInstancedMeshDispose ) === false ) { - // create buffer data + object.addEventListener( 'dispose', onInstancedMeshDispose ); - generateBufferData(); + } - // build geometry + if ( updateMap.get( object ) !== frame ) { - this.setIndex( indices ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + attributes.update( object.instanceMatrix, gl.ARRAY_BUFFER ); - // functions + if ( object.instanceColor !== null ) { - function generateBufferData() { + attributes.update( object.instanceColor, gl.ARRAY_BUFFER ); - for ( let i = 0; i < tubularSegments; i ++ ) { + } - generateSegment( i ); + updateMap.set( object, frame ); } - // if the geometry is not closed, generate the last row of vertices and normals - // at the regular position on the given path - // - // if the geometry is closed, duplicate the first row of vertices and normals (uvs will differ) - - generateSegment( ( closed === false ) ? tubularSegments : 0 ); + } - // uvs are generated in a separate function. - // this makes it easy compute correct values for closed geometries + if ( object.isSkinnedMesh ) { - generateUVs(); + const skeleton = object.skeleton; - // finally create faces + if ( updateMap.get( skeleton ) !== frame ) { - generateIndices(); + skeleton.update(); - } + updateMap.set( skeleton, frame ); - function generateSegment( i ) { + } - // we use getPointAt to sample evenly distributed points from the given path + } - P = path.getPointAt( i / tubularSegments, P ); + return buffergeometry; - // retrieve corresponding normal and binormal + } - const N = frames.normals[ i ]; - const B = frames.binormals[ i ]; + function dispose() { - // generate normals and vertices for the current segment + updateMap = new WeakMap(); - for ( let j = 0; j <= radialSegments; j ++ ) { + } - const v = j / radialSegments * Math.PI * 2; + function onInstancedMeshDispose( event ) { - const sin = Math.sin( v ); - const cos = - Math.cos( v ); + const instancedMesh = event.target; - // normal + instancedMesh.removeEventListener( 'dispose', onInstancedMeshDispose ); - normal.x = ( cos * N.x + sin * B.x ); - normal.y = ( cos * N.y + sin * B.y ); - normal.z = ( cos * N.z + sin * B.z ); - normal.normalize(); + attributes.remove( instancedMesh.instanceMatrix ); - normals.push( normal.x, normal.y, normal.z ); + if ( instancedMesh.instanceColor !== null ) attributes.remove( instancedMesh.instanceColor ); - // vertex + } - vertex.x = P.x + radius * normal.x; - vertex.y = P.y + radius * normal.y; - vertex.z = P.z + radius * normal.z; + return { - vertices.push( vertex.x, vertex.y, vertex.z ); + update: update, + dispose: dispose - } + }; - } +} - function generateIndices() { +/** + * Uniforms of a program. + * Those form a tree structure with a special top-level container for the root, + * which you get by calling 'new WebGLUniforms( gl, program )'. + * + * + * Properties of inner nodes including the top-level container: + * + * .seq - array of nested uniforms + * .map - nested uniforms by name + * + * + * Methods of all nodes except the top-level container: + * + * .setValue( gl, value, [textures] ) + * + * uploads a uniform value(s) + * the 'textures' parameter is needed for sampler uniforms + * + * + * Static methods of the top-level container (textures factorizations): + * + * .upload( gl, seq, values, textures ) + * + * sets uniforms in 'seq' to 'values[id].value' + * + * .seqWithValue( seq, values ) : filteredSeq + * + * filters 'seq' entries with corresponding entry in values + * + * + * Methods of the top-level container (textures factorizations): + * + * .setValue( gl, name, value, textures ) + * + * sets uniform with name 'name' to 'value' + * + * .setOptional( gl, obj, prop ) + * + * like .set for an optional property of the object + * + */ - for ( let j = 1; j <= tubularSegments; j ++ ) { - for ( let i = 1; i <= radialSegments; i ++ ) { +const emptyTexture = /*@__PURE__*/ new Texture(); - const a = ( radialSegments + 1 ) * ( j - 1 ) + ( i - 1 ); - const b = ( radialSegments + 1 ) * j + ( i - 1 ); - const c = ( radialSegments + 1 ) * j + i; - const d = ( radialSegments + 1 ) * ( j - 1 ) + i; +const emptyShadowTexture = /*@__PURE__*/ new DepthTexture( 1, 1 ); - // faces +const emptyArrayTexture = /*@__PURE__*/ new DataArrayTexture(); +const empty3dTexture = /*@__PURE__*/ new Data3DTexture(); +const emptyCubeTexture = /*@__PURE__*/ new CubeTexture(); - indices.push( a, b, d ); - indices.push( b, c, d ); +// --- Utilities --- - } +// Array Caches (provide typed arrays for temporary by size) - } +const arrayCacheF32 = []; +const arrayCacheI32 = []; - } +// Float32Array caches used for uploading Matrix uniforms - function generateUVs() { +const mat4array = new Float32Array( 16 ); +const mat3array = new Float32Array( 9 ); +const mat2array = new Float32Array( 4 ); - for ( let i = 0; i <= tubularSegments; i ++ ) { +// Flattening for arrays of vectors and matrices - for ( let j = 0; j <= radialSegments; j ++ ) { +function flatten( array, nBlocks, blockSize ) { - uv.x = i / tubularSegments; - uv.y = j / radialSegments; + const firstElem = array[ 0 ]; - uvs.push( uv.x, uv.y ); + if ( firstElem <= 0 || firstElem > 0 ) return array; + // unoptimized: ! isNaN( firstElem ) + // see http://jacksondunstan.com/articles/983 - } + const n = nBlocks * blockSize; + let r = arrayCacheF32[ n ]; - } + if ( r === undefined ) { - } + r = new Float32Array( n ); + arrayCacheF32[ n ] = r; } - copy( source ) { + if ( nBlocks !== 0 ) { - super.copy( source ); + firstElem.toArray( r, 0 ); - this.parameters = Object.assign( {}, source.parameters ); + for ( let i = 1, offset = 0; i !== nBlocks; ++ i ) { - return this; + offset += blockSize; + array[ i ].toArray( r, offset ); - } + } - toJSON() { + } - const data = super.toJSON(); + return r; - data.path = this.parameters.path.toJSON(); +} - return data; +function arraysEqual( a, b ) { - } + if ( a.length !== b.length ) return false; - static fromJSON( data ) { + for ( let i = 0, l = a.length; i < l; i ++ ) { - // This only works for built-in curves (e.g. CatmullRomCurve3). - // User defined curves or instances of CurvePath will not be deserialized. - return new TubeGeometry( - new Curves[ data.path.type ]().fromJSON( data.path ), - data.tubularSegments, - data.radius, - data.radialSegments, - data.closed - ); + if ( a[ i ] !== b[ i ] ) return false; } -} + return true; -class WireframeGeometry extends BufferGeometry { +} - constructor( geometry = null ) { +function copyArray( a, b ) { - super(); + for ( let i = 0, l = b.length; i < l; i ++ ) { - this.type = 'WireframeGeometry'; + a[ i ] = b[ i ]; - this.parameters = { - geometry: geometry - }; + } - if ( geometry !== null ) { +} - // buffer +// Texture unit allocation - const vertices = []; - const edges = new Set(); +function allocTexUnits( textures, n ) { - // helper variables + let r = arrayCacheI32[ n ]; - const start = new Vector3(); - const end = new Vector3(); + if ( r === undefined ) { - if ( geometry.index !== null ) { + r = new Int32Array( n ); + arrayCacheI32[ n ] = r; - // indexed BufferGeometry + } - const position = geometry.attributes.position; - const indices = geometry.index; - let groups = geometry.groups; + for ( let i = 0; i !== n; ++ i ) { - if ( groups.length === 0 ) { + r[ i ] = textures.allocateTextureUnit(); - groups = [ { start: 0, count: indices.count, materialIndex: 0 } ]; + } - } + return r; - // create a data structure that contains all edges without duplicates +} - for ( let o = 0, ol = groups.length; o < ol; ++ o ) { +// --- Setters --- - const group = groups[ o ]; +// Note: Defining these methods externally, because they come in a bunch +// and this way their names minify. - const groupStart = group.start; - const groupCount = group.count; +// Single scalar - for ( let i = groupStart, l = ( groupStart + groupCount ); i < l; i += 3 ) { +function setValueV1f( gl, v ) { - for ( let j = 0; j < 3; j ++ ) { + const cache = this.cache; - const index1 = indices.getX( i + j ); - const index2 = indices.getX( i + ( j + 1 ) % 3 ); + if ( cache[ 0 ] === v ) return; - start.fromBufferAttribute( position, index1 ); - end.fromBufferAttribute( position, index2 ); + gl.uniform1f( this.addr, v ); - if ( isUniqueEdge( start, end, edges ) === true ) { + cache[ 0 ] = v; - vertices.push( start.x, start.y, start.z ); - vertices.push( end.x, end.y, end.z ); +} - } +// Single float vector (from flat array or THREE.VectorN) - } +function setValueV2f( gl, v ) { - } + const cache = this.cache; - } + if ( v.x !== undefined ) { - } else { + if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y ) { - // non-indexed BufferGeometry + gl.uniform2f( this.addr, v.x, v.y ); - const position = geometry.attributes.position; + cache[ 0 ] = v.x; + cache[ 1 ] = v.y; - for ( let i = 0, l = ( position.count / 3 ); i < l; i ++ ) { + } - for ( let j = 0; j < 3; j ++ ) { + } else { - // three edges per triangle, an edge is represented as (index1, index2) - // e.g. the first triangle has the following edges: (0,1),(1,2),(2,0) + if ( arraysEqual( cache, v ) ) return; - const index1 = 3 * i + j; - const index2 = 3 * i + ( ( j + 1 ) % 3 ); + gl.uniform2fv( this.addr, v ); - start.fromBufferAttribute( position, index1 ); - end.fromBufferAttribute( position, index2 ); + copyArray( cache, v ); - if ( isUniqueEdge( start, end, edges ) === true ) { + } - vertices.push( start.x, start.y, start.z ); - vertices.push( end.x, end.y, end.z ); +} - } +function setValueV3f( gl, v ) { - } + const cache = this.cache; - } + if ( v.x !== undefined ) { - } + if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z ) { - // build geometry + gl.uniform3f( this.addr, v.x, v.y, v.z ); - this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + cache[ 0 ] = v.x; + cache[ 1 ] = v.y; + cache[ 2 ] = v.z; } - } + } else if ( v.r !== undefined ) { - copy( source ) { + if ( cache[ 0 ] !== v.r || cache[ 1 ] !== v.g || cache[ 2 ] !== v.b ) { - super.copy( source ); + gl.uniform3f( this.addr, v.r, v.g, v.b ); - this.parameters = Object.assign( {}, source.parameters ); + cache[ 0 ] = v.r; + cache[ 1 ] = v.g; + cache[ 2 ] = v.b; - return this; + } - } + } else { -} + if ( arraysEqual( cache, v ) ) return; -function isUniqueEdge( start, end, edges ) { + gl.uniform3fv( this.addr, v ); - const hash1 = `${start.x},${start.y},${start.z}-${end.x},${end.y},${end.z}`; - const hash2 = `${end.x},${end.y},${end.z}-${start.x},${start.y},${start.z}`; // coincident edge + copyArray( cache, v ); - if ( edges.has( hash1 ) === true || edges.has( hash2 ) === true ) { + } - return false; +} - } else { +function setValueV4f( gl, v ) { - edges.add( hash1 ); - edges.add( hash2 ); - return true; + const cache = this.cache; - } + if ( v.x !== undefined ) { -} + if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z || cache[ 3 ] !== v.w ) { -var Geometries = /*#__PURE__*/Object.freeze({ - __proto__: null, - BoxGeometry: BoxGeometry, - CapsuleGeometry: CapsuleGeometry, - CircleGeometry: CircleGeometry, - ConeGeometry: ConeGeometry, - CylinderGeometry: CylinderGeometry, - DodecahedronGeometry: DodecahedronGeometry, - EdgesGeometry: EdgesGeometry, - ExtrudeGeometry: ExtrudeGeometry, - IcosahedronGeometry: IcosahedronGeometry, - LatheGeometry: LatheGeometry, - OctahedronGeometry: OctahedronGeometry, - PlaneGeometry: PlaneGeometry, - PolyhedronGeometry: PolyhedronGeometry, - RingGeometry: RingGeometry, - ShapeGeometry: ShapeGeometry, - SphereGeometry: SphereGeometry, - TetrahedronGeometry: TetrahedronGeometry, - TorusGeometry: TorusGeometry, - TorusKnotGeometry: TorusKnotGeometry, - TubeGeometry: TubeGeometry, - WireframeGeometry: WireframeGeometry -}); - -class ShadowMaterial extends Material { - - constructor( parameters ) { + gl.uniform4f( this.addr, v.x, v.y, v.z, v.w ); - super(); + cache[ 0 ] = v.x; + cache[ 1 ] = v.y; + cache[ 2 ] = v.z; + cache[ 3 ] = v.w; - this.isShadowMaterial = true; + } - this.type = 'ShadowMaterial'; + } else { - this.color = new Color( 0x000000 ); - this.transparent = true; + if ( arraysEqual( cache, v ) ) return; - this.fog = true; + gl.uniform4fv( this.addr, v ); - this.setValues( parameters ); + copyArray( cache, v ); } - copy( source ) { +} + +// Single matrix (from flat array or THREE.MatrixN) - super.copy( source ); +function setValueM2( gl, v ) { - this.color.copy( source.color ); + const cache = this.cache; + const elements = v.elements; - this.fog = source.fog; + if ( elements === undefined ) { - return this; + if ( arraysEqual( cache, v ) ) return; - } + gl.uniformMatrix2fv( this.addr, false, v ); -} + copyArray( cache, v ); -class RawShaderMaterial extends ShaderMaterial { + } else { - constructor( parameters ) { + if ( arraysEqual( cache, elements ) ) return; - super( parameters ); + mat2array.set( elements ); - this.isRawShaderMaterial = true; + gl.uniformMatrix2fv( this.addr, false, mat2array ); - this.type = 'RawShaderMaterial'; + copyArray( cache, elements ); } } -class MeshStandardMaterial extends Material { +function setValueM3( gl, v ) { - constructor( parameters ) { + const cache = this.cache; + const elements = v.elements; - super(); + if ( elements === undefined ) { - this.isMeshStandardMaterial = true; + if ( arraysEqual( cache, v ) ) return; + + gl.uniformMatrix3fv( this.addr, false, v ); + + copyArray( cache, v ); - this.defines = { 'STANDARD': '' }; + } else { - this.type = 'MeshStandardMaterial'; + if ( arraysEqual( cache, elements ) ) return; - this.color = new Color( 0xffffff ); // diffuse - this.roughness = 1.0; - this.metalness = 0.0; + mat3array.set( elements ); - this.map = null; + gl.uniformMatrix3fv( this.addr, false, mat3array ); - this.lightMap = null; - this.lightMapIntensity = 1.0; + copyArray( cache, elements ); - this.aoMap = null; - this.aoMapIntensity = 1.0; + } - this.emissive = new Color( 0x000000 ); - this.emissiveIntensity = 1.0; - this.emissiveMap = null; +} - this.bumpMap = null; - this.bumpScale = 1; +function setValueM4( gl, v ) { - this.normalMap = null; - this.normalMapType = TangentSpaceNormalMap; - this.normalScale = new Vector2( 1, 1 ); + const cache = this.cache; + const elements = v.elements; - this.displacementMap = null; - this.displacementScale = 1; - this.displacementBias = 0; + if ( elements === undefined ) { - this.roughnessMap = null; + if ( arraysEqual( cache, v ) ) return; - this.metalnessMap = null; + gl.uniformMatrix4fv( this.addr, false, v ); - this.alphaMap = null; + copyArray( cache, v ); - this.envMap = null; - this.envMapIntensity = 1.0; + } else { - this.wireframe = false; - this.wireframeLinewidth = 1; - this.wireframeLinecap = 'round'; - this.wireframeLinejoin = 'round'; + if ( arraysEqual( cache, elements ) ) return; - this.flatShading = false; + mat4array.set( elements ); - this.fog = true; + gl.uniformMatrix4fv( this.addr, false, mat4array ); - this.setValues( parameters ); + copyArray( cache, elements ); } - copy( source ) { +} + +// Single integer / boolean - super.copy( source ); +function setValueV1i( gl, v ) { - this.defines = { 'STANDARD': '' }; + const cache = this.cache; - this.color.copy( source.color ); - this.roughness = source.roughness; - this.metalness = source.metalness; + if ( cache[ 0 ] === v ) return; - this.map = source.map; + gl.uniform1i( this.addr, v ); - this.lightMap = source.lightMap; - this.lightMapIntensity = source.lightMapIntensity; + cache[ 0 ] = v; - this.aoMap = source.aoMap; - this.aoMapIntensity = source.aoMapIntensity; +} - this.emissive.copy( source.emissive ); - this.emissiveMap = source.emissiveMap; - this.emissiveIntensity = source.emissiveIntensity; +// Single integer / boolean vector (from flat array or THREE.VectorN) - this.bumpMap = source.bumpMap; - this.bumpScale = source.bumpScale; +function setValueV2i( gl, v ) { - this.normalMap = source.normalMap; - this.normalMapType = source.normalMapType; - this.normalScale.copy( source.normalScale ); + const cache = this.cache; - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; + if ( v.x !== undefined ) { - this.roughnessMap = source.roughnessMap; + if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y ) { - this.metalnessMap = source.metalnessMap; + gl.uniform2i( this.addr, v.x, v.y ); - this.alphaMap = source.alphaMap; + cache[ 0 ] = v.x; + cache[ 1 ] = v.y; - this.envMap = source.envMap; - this.envMapIntensity = source.envMapIntensity; + } - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; - this.wireframeLinecap = source.wireframeLinecap; - this.wireframeLinejoin = source.wireframeLinejoin; + } else { - this.flatShading = source.flatShading; + if ( arraysEqual( cache, v ) ) return; - this.fog = source.fog; + gl.uniform2iv( this.addr, v ); - return this; + copyArray( cache, v ); } } -class MeshPhysicalMaterial extends MeshStandardMaterial { +function setValueV3i( gl, v ) { + + const cache = this.cache; - constructor( parameters ) { + if ( v.x !== undefined ) { - super(); + if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z ) { - this.isMeshPhysicalMaterial = true; + gl.uniform3i( this.addr, v.x, v.y, v.z ); - this.defines = { + cache[ 0 ] = v.x; + cache[ 1 ] = v.y; + cache[ 2 ] = v.z; - 'STANDARD': '', - 'PHYSICAL': '' + } - }; + } else { - this.type = 'MeshPhysicalMaterial'; + if ( arraysEqual( cache, v ) ) return; - this.anisotropyRotation = 0; - this.anisotropyMap = null; + gl.uniform3iv( this.addr, v ); - this.clearcoatMap = null; - this.clearcoatRoughness = 0.0; - this.clearcoatRoughnessMap = null; - this.clearcoatNormalScale = new Vector2( 1, 1 ); - this.clearcoatNormalMap = null; + copyArray( cache, v ); - this.ior = 1.5; + } - Object.defineProperty( this, 'reflectivity', { - get: function () { +} - return ( clamp( 2.5 * ( this.ior - 1 ) / ( this.ior + 1 ), 0, 1 ) ); +function setValueV4i( gl, v ) { - }, - set: function ( reflectivity ) { + const cache = this.cache; - this.ior = ( 1 + 0.4 * reflectivity ) / ( 1 - 0.4 * reflectivity ); + if ( v.x !== undefined ) { - } - } ); + if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z || cache[ 3 ] !== v.w ) { - this.iridescenceMap = null; - this.iridescenceIOR = 1.3; - this.iridescenceThicknessRange = [ 100, 400 ]; - this.iridescenceThicknessMap = null; + gl.uniform4i( this.addr, v.x, v.y, v.z, v.w ); - this.sheenColor = new Color( 0x000000 ); - this.sheenColorMap = null; - this.sheenRoughness = 1.0; - this.sheenRoughnessMap = null; + cache[ 0 ] = v.x; + cache[ 1 ] = v.y; + cache[ 2 ] = v.z; + cache[ 3 ] = v.w; - this.transmissionMap = null; + } - this.thickness = 0; - this.thicknessMap = null; - this.attenuationDistance = Infinity; - this.attenuationColor = new Color( 1, 1, 1 ); + } else { - this.specularIntensity = 1.0; - this.specularIntensityMap = null; - this.specularColor = new Color( 1, 1, 1 ); - this.specularColorMap = null; + if ( arraysEqual( cache, v ) ) return; - this._anisotropy = 0; - this._clearcoat = 0; - this._iridescence = 0; - this._sheen = 0.0; - this._transmission = 0; + gl.uniform4iv( this.addr, v ); - this.setValues( parameters ); + copyArray( cache, v ); } - get anisotropy() { +} - return this._anisotropy; +// Single unsigned integer - } +function setValueV1ui( gl, v ) { - set anisotropy( value ) { + const cache = this.cache; - if ( this._anisotropy > 0 !== value > 0 ) { + if ( cache[ 0 ] === v ) return; - this.version ++; + gl.uniform1ui( this.addr, v ); - } + cache[ 0 ] = v; - this._anisotropy = value; +} - } +// Single unsigned integer vector (from flat array or THREE.VectorN) - get clearcoat() { +function setValueV2ui( gl, v ) { - return this._clearcoat; + const cache = this.cache; - } + if ( v.x !== undefined ) { - set clearcoat( value ) { + if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y ) { - if ( this._clearcoat > 0 !== value > 0 ) { + gl.uniform2ui( this.addr, v.x, v.y ); - this.version ++; + cache[ 0 ] = v.x; + cache[ 1 ] = v.y; } - this._clearcoat = value; + } else { - } + if ( arraysEqual( cache, v ) ) return; - get iridescence() { + gl.uniform2uiv( this.addr, v ); - return this._iridescence; + copyArray( cache, v ); } - set iridescence( value ) { - - if ( this._iridescence > 0 !== value > 0 ) { - - this.version ++; - - } - - this._iridescence = value; - - } +} - get sheen() { +function setValueV3ui( gl, v ) { - return this._sheen; + const cache = this.cache; - } + if ( v.x !== undefined ) { - set sheen( value ) { + if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z ) { - if ( this._sheen > 0 !== value > 0 ) { + gl.uniform3ui( this.addr, v.x, v.y, v.z ); - this.version ++; + cache[ 0 ] = v.x; + cache[ 1 ] = v.y; + cache[ 2 ] = v.z; } - this._sheen = value; + } else { - } + if ( arraysEqual( cache, v ) ) return; - get transmission() { + gl.uniform3uiv( this.addr, v ); - return this._transmission; + copyArray( cache, v ); } - set transmission( value ) { +} - if ( this._transmission > 0 !== value > 0 ) { +function setValueV4ui( gl, v ) { - this.version ++; + const cache = this.cache; - } + if ( v.x !== undefined ) { - this._transmission = value; + if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z || cache[ 3 ] !== v.w ) { - } + gl.uniform4ui( this.addr, v.x, v.y, v.z, v.w ); - copy( source ) { + cache[ 0 ] = v.x; + cache[ 1 ] = v.y; + cache[ 2 ] = v.z; + cache[ 3 ] = v.w; - super.copy( source ); + } - this.defines = { + } else { - 'STANDARD': '', - 'PHYSICAL': '' + if ( arraysEqual( cache, v ) ) return; - }; + gl.uniform4uiv( this.addr, v ); - this.anisotropy = source.anisotropy; - this.anisotropyRotation = source.anisotropyRotation; - this.anisotropyMap = source.anisotropyMap; + copyArray( cache, v ); - this.clearcoat = source.clearcoat; - this.clearcoatMap = source.clearcoatMap; - this.clearcoatRoughness = source.clearcoatRoughness; - this.clearcoatRoughnessMap = source.clearcoatRoughnessMap; - this.clearcoatNormalMap = source.clearcoatNormalMap; - this.clearcoatNormalScale.copy( source.clearcoatNormalScale ); + } - this.ior = source.ior; +} - this.iridescence = source.iridescence; - this.iridescenceMap = source.iridescenceMap; - this.iridescenceIOR = source.iridescenceIOR; - this.iridescenceThicknessRange = [ ...source.iridescenceThicknessRange ]; - this.iridescenceThicknessMap = source.iridescenceThicknessMap; - this.sheen = source.sheen; - this.sheenColor.copy( source.sheenColor ); - this.sheenColorMap = source.sheenColorMap; - this.sheenRoughness = source.sheenRoughness; - this.sheenRoughnessMap = source.sheenRoughnessMap; +// Single texture (2D / Cube) - this.transmission = source.transmission; - this.transmissionMap = source.transmissionMap; +function setValueT1( gl, v, textures ) { - this.thickness = source.thickness; - this.thicknessMap = source.thicknessMap; - this.attenuationDistance = source.attenuationDistance; - this.attenuationColor.copy( source.attenuationColor ); + const cache = this.cache; + const unit = textures.allocateTextureUnit(); - this.specularIntensity = source.specularIntensity; - this.specularIntensityMap = source.specularIntensityMap; - this.specularColor.copy( source.specularColor ); - this.specularColorMap = source.specularColorMap; + if ( cache[ 0 ] !== unit ) { - return this; + gl.uniform1i( this.addr, unit ); + cache[ 0 ] = unit; } -} - -class MeshPhongMaterial extends Material { - - constructor( parameters ) { + let emptyTexture2D; - super(); + if ( this.type === gl.SAMPLER_2D_SHADOW ) { - this.isMeshPhongMaterial = true; + emptyShadowTexture.compareFunction = LessEqualCompare; // #28670 + emptyTexture2D = emptyShadowTexture; - this.type = 'MeshPhongMaterial'; + } else { - this.color = new Color( 0xffffff ); // diffuse - this.specular = new Color( 0x111111 ); - this.shininess = 30; + emptyTexture2D = emptyTexture; - this.map = null; + } - this.lightMap = null; - this.lightMapIntensity = 1.0; + textures.setTexture2D( v || emptyTexture2D, unit ); - this.aoMap = null; - this.aoMapIntensity = 1.0; +} - this.emissive = new Color( 0x000000 ); - this.emissiveIntensity = 1.0; - this.emissiveMap = null; +function setValueT3D1( gl, v, textures ) { - this.bumpMap = null; - this.bumpScale = 1; + const cache = this.cache; + const unit = textures.allocateTextureUnit(); - this.normalMap = null; - this.normalMapType = TangentSpaceNormalMap; - this.normalScale = new Vector2( 1, 1 ); + if ( cache[ 0 ] !== unit ) { - this.displacementMap = null; - this.displacementScale = 1; - this.displacementBias = 0; + gl.uniform1i( this.addr, unit ); + cache[ 0 ] = unit; - this.specularMap = null; + } - this.alphaMap = null; + textures.setTexture3D( v || empty3dTexture, unit ); - this.envMap = null; - this.combine = MultiplyOperation; - this.reflectivity = 1; - this.refractionRatio = 0.98; +} - this.wireframe = false; - this.wireframeLinewidth = 1; - this.wireframeLinecap = 'round'; - this.wireframeLinejoin = 'round'; +function setValueT6( gl, v, textures ) { - this.flatShading = false; + const cache = this.cache; + const unit = textures.allocateTextureUnit(); - this.fog = true; + if ( cache[ 0 ] !== unit ) { - this.setValues( parameters ); + gl.uniform1i( this.addr, unit ); + cache[ 0 ] = unit; } - copy( source ) { - - super.copy( source ); + textures.setTextureCube( v || emptyCubeTexture, unit ); - this.color.copy( source.color ); - this.specular.copy( source.specular ); - this.shininess = source.shininess; +} - this.map = source.map; +function setValueT2DArray1( gl, v, textures ) { - this.lightMap = source.lightMap; - this.lightMapIntensity = source.lightMapIntensity; + const cache = this.cache; + const unit = textures.allocateTextureUnit(); - this.aoMap = source.aoMap; - this.aoMapIntensity = source.aoMapIntensity; + if ( cache[ 0 ] !== unit ) { - this.emissive.copy( source.emissive ); - this.emissiveMap = source.emissiveMap; - this.emissiveIntensity = source.emissiveIntensity; + gl.uniform1i( this.addr, unit ); + cache[ 0 ] = unit; - this.bumpMap = source.bumpMap; - this.bumpScale = source.bumpScale; + } - this.normalMap = source.normalMap; - this.normalMapType = source.normalMapType; - this.normalScale.copy( source.normalScale ); + textures.setTexture2DArray( v || emptyArrayTexture, unit ); - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; +} - this.specularMap = source.specularMap; +// Helper to pick the right setter for the singular case - this.alphaMap = source.alphaMap; +function getSingularSetter( type ) { - this.envMap = source.envMap; - this.combine = source.combine; - this.reflectivity = source.reflectivity; - this.refractionRatio = source.refractionRatio; + switch ( type ) { - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; - this.wireframeLinecap = source.wireframeLinecap; - this.wireframeLinejoin = source.wireframeLinejoin; + case 0x1406: return setValueV1f; // FLOAT + case 0x8b50: return setValueV2f; // _VEC2 + case 0x8b51: return setValueV3f; // _VEC3 + case 0x8b52: return setValueV4f; // _VEC4 - this.flatShading = source.flatShading; + case 0x8b5a: return setValueM2; // _MAT2 + case 0x8b5b: return setValueM3; // _MAT3 + case 0x8b5c: return setValueM4; // _MAT4 - this.fog = source.fog; + case 0x1404: case 0x8b56: return setValueV1i; // INT, BOOL + case 0x8b53: case 0x8b57: return setValueV2i; // _VEC2 + case 0x8b54: case 0x8b58: return setValueV3i; // _VEC3 + case 0x8b55: case 0x8b59: return setValueV4i; // _VEC4 - return this; + case 0x1405: return setValueV1ui; // UINT + case 0x8dc6: return setValueV2ui; // _VEC2 + case 0x8dc7: return setValueV3ui; // _VEC3 + case 0x8dc8: return setValueV4ui; // _VEC4 - } + case 0x8b5e: // SAMPLER_2D + case 0x8d66: // SAMPLER_EXTERNAL_OES + case 0x8dca: // INT_SAMPLER_2D + case 0x8dd2: // UNSIGNED_INT_SAMPLER_2D + case 0x8b62: // SAMPLER_2D_SHADOW + return setValueT1; -} + case 0x8b5f: // SAMPLER_3D + case 0x8dcb: // INT_SAMPLER_3D + case 0x8dd3: // UNSIGNED_INT_SAMPLER_3D + return setValueT3D1; -class MeshToonMaterial extends Material { + case 0x8b60: // SAMPLER_CUBE + case 0x8dcc: // INT_SAMPLER_CUBE + case 0x8dd4: // UNSIGNED_INT_SAMPLER_CUBE + case 0x8dc5: // SAMPLER_CUBE_SHADOW + return setValueT6; - constructor( parameters ) { + case 0x8dc1: // SAMPLER_2D_ARRAY + case 0x8dcf: // INT_SAMPLER_2D_ARRAY + case 0x8dd7: // UNSIGNED_INT_SAMPLER_2D_ARRAY + case 0x8dc4: // SAMPLER_2D_ARRAY_SHADOW + return setValueT2DArray1; - super(); + } - this.isMeshToonMaterial = true; +} - this.defines = { 'TOON': '' }; - this.type = 'MeshToonMaterial'; +// Array of scalars - this.color = new Color( 0xffffff ); +function setValueV1fArray( gl, v ) { - this.map = null; - this.gradientMap = null; + gl.uniform1fv( this.addr, v ); - this.lightMap = null; - this.lightMapIntensity = 1.0; +} - this.aoMap = null; - this.aoMapIntensity = 1.0; +// Array of vectors (from flat array or array of THREE.VectorN) - this.emissive = new Color( 0x000000 ); - this.emissiveIntensity = 1.0; - this.emissiveMap = null; +function setValueV2fArray( gl, v ) { - this.bumpMap = null; - this.bumpScale = 1; + const data = flatten( v, this.size, 2 ); - this.normalMap = null; - this.normalMapType = TangentSpaceNormalMap; - this.normalScale = new Vector2( 1, 1 ); + gl.uniform2fv( this.addr, data ); - this.displacementMap = null; - this.displacementScale = 1; - this.displacementBias = 0; +} - this.alphaMap = null; +function setValueV3fArray( gl, v ) { - this.wireframe = false; - this.wireframeLinewidth = 1; - this.wireframeLinecap = 'round'; - this.wireframeLinejoin = 'round'; + const data = flatten( v, this.size, 3 ); - this.fog = true; + gl.uniform3fv( this.addr, data ); - this.setValues( parameters ); +} - } +function setValueV4fArray( gl, v ) { - copy( source ) { + const data = flatten( v, this.size, 4 ); - super.copy( source ); + gl.uniform4fv( this.addr, data ); - this.color.copy( source.color ); +} - this.map = source.map; - this.gradientMap = source.gradientMap; +// Array of matrices (from flat array or array of THREE.MatrixN) - this.lightMap = source.lightMap; - this.lightMapIntensity = source.lightMapIntensity; +function setValueM2Array( gl, v ) { - this.aoMap = source.aoMap; - this.aoMapIntensity = source.aoMapIntensity; + const data = flatten( v, this.size, 4 ); - this.emissive.copy( source.emissive ); - this.emissiveMap = source.emissiveMap; - this.emissiveIntensity = source.emissiveIntensity; + gl.uniformMatrix2fv( this.addr, false, data ); - this.bumpMap = source.bumpMap; - this.bumpScale = source.bumpScale; +} - this.normalMap = source.normalMap; - this.normalMapType = source.normalMapType; - this.normalScale.copy( source.normalScale ); +function setValueM3Array( gl, v ) { - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; + const data = flatten( v, this.size, 9 ); - this.alphaMap = source.alphaMap; + gl.uniformMatrix3fv( this.addr, false, data ); - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; - this.wireframeLinecap = source.wireframeLinecap; - this.wireframeLinejoin = source.wireframeLinejoin; +} - this.fog = source.fog; +function setValueM4Array( gl, v ) { - return this; + const data = flatten( v, this.size, 16 ); - } + gl.uniformMatrix4fv( this.addr, false, data ); } -class MeshNormalMaterial extends Material { +// Array of integer / boolean - constructor( parameters ) { +function setValueV1iArray( gl, v ) { - super(); + gl.uniform1iv( this.addr, v ); - this.isMeshNormalMaterial = true; +} - this.type = 'MeshNormalMaterial'; +// Array of integer / boolean vectors (from flat array) - this.bumpMap = null; - this.bumpScale = 1; +function setValueV2iArray( gl, v ) { - this.normalMap = null; - this.normalMapType = TangentSpaceNormalMap; - this.normalScale = new Vector2( 1, 1 ); + gl.uniform2iv( this.addr, v ); - this.displacementMap = null; - this.displacementScale = 1; - this.displacementBias = 0; +} - this.wireframe = false; - this.wireframeLinewidth = 1; +function setValueV3iArray( gl, v ) { - this.flatShading = false; + gl.uniform3iv( this.addr, v ); - this.setValues( parameters ); +} - } +function setValueV4iArray( gl, v ) { - copy( source ) { + gl.uniform4iv( this.addr, v ); - super.copy( source ); +} - this.bumpMap = source.bumpMap; - this.bumpScale = source.bumpScale; +// Array of unsigned integer - this.normalMap = source.normalMap; - this.normalMapType = source.normalMapType; - this.normalScale.copy( source.normalScale ); +function setValueV1uiArray( gl, v ) { - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; + gl.uniform1uiv( this.addr, v ); - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; +} - this.flatShading = source.flatShading; +// Array of unsigned integer vectors (from flat array) - return this; +function setValueV2uiArray( gl, v ) { - } + gl.uniform2uiv( this.addr, v ); } -class MeshLambertMaterial extends Material { - - constructor( parameters ) { - - super(); +function setValueV3uiArray( gl, v ) { - this.isMeshLambertMaterial = true; + gl.uniform3uiv( this.addr, v ); - this.type = 'MeshLambertMaterial'; +} - this.color = new Color( 0xffffff ); // diffuse +function setValueV4uiArray( gl, v ) { - this.map = null; + gl.uniform4uiv( this.addr, v ); - this.lightMap = null; - this.lightMapIntensity = 1.0; +} - this.aoMap = null; - this.aoMapIntensity = 1.0; - this.emissive = new Color( 0x000000 ); - this.emissiveIntensity = 1.0; - this.emissiveMap = null; +// Array of textures (2D / 3D / Cube / 2DArray) - this.bumpMap = null; - this.bumpScale = 1; +function setValueT1Array( gl, v, textures ) { - this.normalMap = null; - this.normalMapType = TangentSpaceNormalMap; - this.normalScale = new Vector2( 1, 1 ); + const cache = this.cache; - this.displacementMap = null; - this.displacementScale = 1; - this.displacementBias = 0; + const n = v.length; - this.specularMap = null; + const units = allocTexUnits( textures, n ); - this.alphaMap = null; + if ( ! arraysEqual( cache, units ) ) { - this.envMap = null; - this.combine = MultiplyOperation; - this.reflectivity = 1; - this.refractionRatio = 0.98; + gl.uniform1iv( this.addr, units ); - this.wireframe = false; - this.wireframeLinewidth = 1; - this.wireframeLinecap = 'round'; - this.wireframeLinejoin = 'round'; + copyArray( cache, units ); - this.flatShading = false; + } - this.fog = true; + for ( let i = 0; i !== n; ++ i ) { - this.setValues( parameters ); + textures.setTexture2D( v[ i ] || emptyTexture, units[ i ] ); } - copy( source ) { +} - super.copy( source ); +function setValueT3DArray( gl, v, textures ) { - this.color.copy( source.color ); + const cache = this.cache; - this.map = source.map; + const n = v.length; - this.lightMap = source.lightMap; - this.lightMapIntensity = source.lightMapIntensity; + const units = allocTexUnits( textures, n ); - this.aoMap = source.aoMap; - this.aoMapIntensity = source.aoMapIntensity; + if ( ! arraysEqual( cache, units ) ) { - this.emissive.copy( source.emissive ); - this.emissiveMap = source.emissiveMap; - this.emissiveIntensity = source.emissiveIntensity; + gl.uniform1iv( this.addr, units ); - this.bumpMap = source.bumpMap; - this.bumpScale = source.bumpScale; + copyArray( cache, units ); - this.normalMap = source.normalMap; - this.normalMapType = source.normalMapType; - this.normalScale.copy( source.normalScale ); + } - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; + for ( let i = 0; i !== n; ++ i ) { - this.specularMap = source.specularMap; + textures.setTexture3D( v[ i ] || empty3dTexture, units[ i ] ); - this.alphaMap = source.alphaMap; + } - this.envMap = source.envMap; - this.combine = source.combine; - this.reflectivity = source.reflectivity; - this.refractionRatio = source.refractionRatio; +} - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; - this.wireframeLinecap = source.wireframeLinecap; - this.wireframeLinejoin = source.wireframeLinejoin; +function setValueT6Array( gl, v, textures ) { - this.flatShading = source.flatShading; + const cache = this.cache; - this.fog = source.fog; + const n = v.length; - return this; + const units = allocTexUnits( textures, n ); - } + if ( ! arraysEqual( cache, units ) ) { -} + gl.uniform1iv( this.addr, units ); -class MeshMatcapMaterial extends Material { + copyArray( cache, units ); - constructor( parameters ) { + } - super(); + for ( let i = 0; i !== n; ++ i ) { - this.isMeshMatcapMaterial = true; + textures.setTextureCube( v[ i ] || emptyCubeTexture, units[ i ] ); - this.defines = { 'MATCAP': '' }; + } - this.type = 'MeshMatcapMaterial'; +} - this.color = new Color( 0xffffff ); // diffuse +function setValueT2DArrayArray( gl, v, textures ) { - this.matcap = null; + const cache = this.cache; - this.map = null; + const n = v.length; - this.bumpMap = null; - this.bumpScale = 1; + const units = allocTexUnits( textures, n ); - this.normalMap = null; - this.normalMapType = TangentSpaceNormalMap; - this.normalScale = new Vector2( 1, 1 ); + if ( ! arraysEqual( cache, units ) ) { - this.displacementMap = null; - this.displacementScale = 1; - this.displacementBias = 0; + gl.uniform1iv( this.addr, units ); - this.alphaMap = null; + copyArray( cache, units ); - this.flatShading = false; + } - this.fog = true; + for ( let i = 0; i !== n; ++ i ) { - this.setValues( parameters ); + textures.setTexture2DArray( v[ i ] || emptyArrayTexture, units[ i ] ); } +} - copy( source ) { - - super.copy( source ); - this.defines = { 'MATCAP': '' }; +// Helper to pick the right setter for a pure (bottom-level) array - this.color.copy( source.color ); +function getPureArraySetter( type ) { - this.matcap = source.matcap; + switch ( type ) { - this.map = source.map; + case 0x1406: return setValueV1fArray; // FLOAT + case 0x8b50: return setValueV2fArray; // _VEC2 + case 0x8b51: return setValueV3fArray; // _VEC3 + case 0x8b52: return setValueV4fArray; // _VEC4 - this.bumpMap = source.bumpMap; - this.bumpScale = source.bumpScale; + case 0x8b5a: return setValueM2Array; // _MAT2 + case 0x8b5b: return setValueM3Array; // _MAT3 + case 0x8b5c: return setValueM4Array; // _MAT4 - this.normalMap = source.normalMap; - this.normalMapType = source.normalMapType; - this.normalScale.copy( source.normalScale ); + case 0x1404: case 0x8b56: return setValueV1iArray; // INT, BOOL + case 0x8b53: case 0x8b57: return setValueV2iArray; // _VEC2 + case 0x8b54: case 0x8b58: return setValueV3iArray; // _VEC3 + case 0x8b55: case 0x8b59: return setValueV4iArray; // _VEC4 - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; + case 0x1405: return setValueV1uiArray; // UINT + case 0x8dc6: return setValueV2uiArray; // _VEC2 + case 0x8dc7: return setValueV3uiArray; // _VEC3 + case 0x8dc8: return setValueV4uiArray; // _VEC4 - this.alphaMap = source.alphaMap; + case 0x8b5e: // SAMPLER_2D + case 0x8d66: // SAMPLER_EXTERNAL_OES + case 0x8dca: // INT_SAMPLER_2D + case 0x8dd2: // UNSIGNED_INT_SAMPLER_2D + case 0x8b62: // SAMPLER_2D_SHADOW + return setValueT1Array; - this.flatShading = source.flatShading; + case 0x8b5f: // SAMPLER_3D + case 0x8dcb: // INT_SAMPLER_3D + case 0x8dd3: // UNSIGNED_INT_SAMPLER_3D + return setValueT3DArray; - this.fog = source.fog; + case 0x8b60: // SAMPLER_CUBE + case 0x8dcc: // INT_SAMPLER_CUBE + case 0x8dd4: // UNSIGNED_INT_SAMPLER_CUBE + case 0x8dc5: // SAMPLER_CUBE_SHADOW + return setValueT6Array; - return this; + case 0x8dc1: // SAMPLER_2D_ARRAY + case 0x8dcf: // INT_SAMPLER_2D_ARRAY + case 0x8dd7: // UNSIGNED_INT_SAMPLER_2D_ARRAY + case 0x8dc4: // SAMPLER_2D_ARRAY_SHADOW + return setValueT2DArrayArray; } } -class LineDashedMaterial extends LineBasicMaterial { - - constructor( parameters ) { - - super(); +// --- Uniform Classes --- - this.isLineDashedMaterial = true; +class SingleUniform { - this.type = 'LineDashedMaterial'; + constructor( id, activeInfo, addr ) { - this.scale = 1; - this.dashSize = 3; - this.gapSize = 1; + this.id = id; + this.addr = addr; + this.cache = []; + this.type = activeInfo.type; + this.setValue = getSingularSetter( activeInfo.type ); - this.setValues( parameters ); + // this.path = activeInfo.name; // DEBUG } - copy( source ) { +} - super.copy( source ); +class PureArrayUniform { - this.scale = source.scale; - this.dashSize = source.dashSize; - this.gapSize = source.gapSize; + constructor( id, activeInfo, addr ) { - return this; + this.id = id; + this.addr = addr; + this.cache = []; + this.type = activeInfo.type; + this.size = activeInfo.size; + this.setValue = getPureArraySetter( activeInfo.type ); + + // this.path = activeInfo.name; // DEBUG } } -// same as Array.prototype.slice, but also works on typed arrays -function arraySlice( array, from, to ) { +class StructuredUniform { + + constructor( id ) { - if ( isTypedArray( array ) ) { + this.id = id; - // in ios9 array.subarray(from, undefined) will return empty array - // but array.subarray(from) or array.subarray(from, len) is correct - return new array.constructor( array.subarray( from, to !== undefined ? to : array.length ) ); + this.seq = []; + this.map = {}; } - return array.slice( from, to ); - -} + setValue( gl, value, textures ) { -// converts an array to a specific type -function convertArray( array, type, forceClone ) { + const seq = this.seq; - if ( ! array || // let 'undefined' and 'null' pass - ! forceClone && array.constructor === type ) return array; + for ( let i = 0, n = seq.length; i !== n; ++ i ) { - if ( typeof type.BYTES_PER_ELEMENT === 'number' ) { + const u = seq[ i ]; + u.setValue( gl, value[ u.id ], textures ); - return new type( array ); // create typed array + } } - return Array.prototype.slice.call( array ); // create Array - } -function isTypedArray( object ) { +// --- Top-level --- - return ArrayBuffer.isView( object ) && - ! ( object instanceof DataView ); +// Parser - builds up the property tree from the path strings -} +const RePathPart = /(\w+)(\])?(\[|\.)?/g; + +// extracts +// - the identifier (member name or array index) +// - followed by an optional right bracket (found when array index) +// - followed by an optional left bracket or dot (type of subscript) +// +// Note: These portions can be read in a non-overlapping fashion and +// allow straightforward parsing of the hierarchy that WebGL encodes +// in the uniform names. -// returns an array by which times and values can be sorted -function getKeyframeOrder( times ) { +function addUniform( container, uniformObject ) { - function compareTime( i, j ) { + container.seq.push( uniformObject ); + container.map[ uniformObject.id ] = uniformObject; - return times[ i ] - times[ j ]; +} - } +function parseUniform( activeInfo, addr, container ) { - const n = times.length; - const result = new Array( n ); - for ( let i = 0; i !== n; ++ i ) result[ i ] = i; + const path = activeInfo.name, + pathLength = path.length; - result.sort( compareTime ); + // reset RegExp object, because of the early exit of a previous run + RePathPart.lastIndex = 0; - return result; + while ( true ) { -} + const match = RePathPart.exec( path ), + matchEnd = RePathPart.lastIndex; -// uses the array previously returned by 'getKeyframeOrder' to sort data -function sortedArray( values, stride, order ) { + let id = match[ 1 ]; + const idIsIndex = match[ 2 ] === ']', + subscript = match[ 3 ]; - const nValues = values.length; - const result = new values.constructor( nValues ); + if ( idIsIndex ) id = id | 0; // convert to integer - for ( let i = 0, dstOffset = 0; dstOffset !== nValues; ++ i ) { + if ( subscript === undefined || subscript === '[' && matchEnd + 2 === pathLength ) { - const srcOffset = order[ i ] * stride; + // bare name or "pure" bottom-level array "[0]" suffix - for ( let j = 0; j !== stride; ++ j ) { + addUniform( container, subscript === undefined ? + new SingleUniform( id, activeInfo, addr ) : + new PureArrayUniform( id, activeInfo, addr ) ); - result[ dstOffset ++ ] = values[ srcOffset + j ]; + break; - } + } else { - } + // step into inner node / create it in case it doesn't exist - return result; + const map = container.map; + let next = map[ id ]; -} + if ( next === undefined ) { -// function for parsing AOS keyframe formats -function flattenJSON( jsonKeys, times, values, valuePropertyName ) { + next = new StructuredUniform( id ); + addUniform( container, next ); - let i = 1, key = jsonKeys[ 0 ]; + } - while ( key !== undefined && key[ valuePropertyName ] === undefined ) { + container = next; - key = jsonKeys[ i ++ ]; + } } - if ( key === undefined ) return; // no data - - let value = key[ valuePropertyName ]; - if ( value === undefined ) return; // no data +} - if ( Array.isArray( value ) ) { +// Root Container - do { +class WebGLUniforms { - value = key[ valuePropertyName ]; + constructor( gl, program ) { - if ( value !== undefined ) { + this.seq = []; + this.map = {}; - times.push( key.time ); - values.push.apply( values, value ); // push all elements + const n = gl.getProgramParameter( program, gl.ACTIVE_UNIFORMS ); - } + for ( let i = 0; i < n; ++ i ) { - key = jsonKeys[ i ++ ]; + const info = gl.getActiveUniform( program, i ), + addr = gl.getUniformLocation( program, info.name ); - } while ( key !== undefined ); + parseUniform( info, addr, this ); - } else if ( value.toArray !== undefined ) { + } - // ...assume THREE.Math-ish + } - do { + setValue( gl, name, value, textures ) { - value = key[ valuePropertyName ]; + const u = this.map[ name ]; - if ( value !== undefined ) { + if ( u !== undefined ) u.setValue( gl, value, textures ); - times.push( key.time ); - value.toArray( values, values.length ); + } - } + setOptional( gl, object, name ) { - key = jsonKeys[ i ++ ]; + const v = object[ name ]; - } while ( key !== undefined ); + if ( v !== undefined ) this.setValue( gl, name, v ); - } else { + } - // otherwise push as-is + static upload( gl, seq, values, textures ) { - do { + for ( let i = 0, n = seq.length; i !== n; ++ i ) { - value = key[ valuePropertyName ]; + const u = seq[ i ], + v = values[ u.id ]; - if ( value !== undefined ) { + if ( v.needsUpdate !== false ) { - times.push( key.time ); - values.push( value ); + // note: always updating when .needsUpdate is undefined + u.setValue( gl, v.value, textures ); } - key = jsonKeys[ i ++ ]; - - } while ( key !== undefined ); + } } -} + static seqWithValue( seq, values ) { + + const r = []; -function subclip( sourceClip, name, startFrame, endFrame, fps = 30 ) { + for ( let i = 0, n = seq.length; i !== n; ++ i ) { - const clip = sourceClip.clone(); + const u = seq[ i ]; + if ( u.id in values ) r.push( u ); - clip.name = name; + } - const tracks = []; + return r; - for ( let i = 0; i < clip.tracks.length; ++ i ) { + } - const track = clip.tracks[ i ]; - const valueSize = track.getValueSize(); +} - const times = []; - const values = []; +function WebGLShader( gl, type, string ) { - for ( let j = 0; j < track.times.length; ++ j ) { + const shader = gl.createShader( type ); - const frame = track.times[ j ] * fps; + gl.shaderSource( shader, string ); + gl.compileShader( shader ); - if ( frame < startFrame || frame >= endFrame ) continue; + return shader; - times.push( track.times[ j ] ); +} - for ( let k = 0; k < valueSize; ++ k ) { +// From https://www.khronos.org/registry/webgl/extensions/KHR_parallel_shader_compile/ +const COMPLETION_STATUS_KHR = 0x91B1; - values.push( track.values[ j * valueSize + k ] ); +let programIdCount = 0; - } +function handleSource( string, errorLine ) { - } + const lines = string.split( '\n' ); + const lines2 = []; - if ( times.length === 0 ) continue; + const from = Math.max( errorLine - 6, 0 ); + const to = Math.min( errorLine + 6, lines.length ); - track.times = convertArray( times, track.times.constructor ); - track.values = convertArray( values, track.values.constructor ); + for ( let i = from; i < to; i ++ ) { - tracks.push( track ); + const line = i + 1; + lines2.push( `${line === errorLine ? '>' : ' '} ${line}: ${lines[ i ]}` ); } - clip.tracks = tracks; - - // find minimum .times value across all tracks in the trimmed clip + return lines2.join( '\n' ); - let minStartTime = Infinity; +} - for ( let i = 0; i < clip.tracks.length; ++ i ) { +const _m0 = /*@__PURE__*/ new Matrix3(); - if ( minStartTime > clip.tracks[ i ].times[ 0 ] ) { +function getEncodingComponents( colorSpace ) { - minStartTime = clip.tracks[ i ].times[ 0 ]; + ColorManagement._getMatrix( _m0, ColorManagement.workingColorSpace, colorSpace ); - } + const encodingMatrix = `mat3( ${ _m0.elements.map( ( v ) => v.toFixed( 4 ) ) } )`; - } + switch ( ColorManagement.getTransfer( colorSpace ) ) { - // shift all tracks such that clip begins at t=0 + case LinearTransfer: + return [ encodingMatrix, 'LinearTransferOETF' ]; - for ( let i = 0; i < clip.tracks.length; ++ i ) { + case SRGBTransfer: + return [ encodingMatrix, 'sRGBTransferOETF' ]; - clip.tracks[ i ].shift( - 1 * minStartTime ); + default: + console.warn( 'THREE.WebGLProgram: Unsupported color space: ', colorSpace ); + return [ encodingMatrix, 'LinearTransferOETF' ]; } - clip.resetDuration(); +} - return clip; +function getShaderErrors( gl, shader, type ) { -} + const status = gl.getShaderParameter( shader, gl.COMPILE_STATUS ); + const errors = gl.getShaderInfoLog( shader ).trim(); -function makeClipAdditive( targetClip, referenceFrame = 0, referenceClip = targetClip, fps = 30 ) { + if ( status && errors === '' ) return ''; - if ( fps <= 0 ) fps = 30; + const errorMatches = /ERROR: 0:(\d+)/.exec( errors ); + if ( errorMatches ) { - const numTracks = referenceClip.tracks.length; - const referenceTime = referenceFrame / fps; + // --enable-privileged-webgl-extension + // console.log( '**' + type + '**', gl.getExtension( 'WEBGL_debug_shaders' ).getTranslatedShaderSource( shader ) ); - // Make each track's values relative to the values at the reference frame - for ( let i = 0; i < numTracks; ++ i ) { + const errorLine = parseInt( errorMatches[ 1 ] ); + return type.toUpperCase() + '\n\n' + errors + '\n\n' + handleSource( gl.getShaderSource( shader ), errorLine ); - const referenceTrack = referenceClip.tracks[ i ]; - const referenceTrackType = referenceTrack.ValueTypeName; + } else { - // Skip this track if it's non-numeric - if ( referenceTrackType === 'bool' || referenceTrackType === 'string' ) continue; + return errors; - // Find the track in the target clip whose name and type matches the reference track - const targetTrack = targetClip.tracks.find( function ( track ) { + } - return track.name === referenceTrack.name - && track.ValueTypeName === referenceTrackType; +} - } ); +function getTexelEncodingFunction( functionName, colorSpace ) { - if ( targetTrack === undefined ) continue; + const components = getEncodingComponents( colorSpace ); - let referenceOffset = 0; - const referenceValueSize = referenceTrack.getValueSize(); + return [ - if ( referenceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) { + `vec4 ${functionName}( vec4 value ) {`, - referenceOffset = referenceValueSize / 3; + ` return ${components[ 1 ]}( vec4( value.rgb * ${components[ 0 ]}, value.a ) );`, - } + '}', - let targetOffset = 0; - const targetValueSize = targetTrack.getValueSize(); + ].join( '\n' ); - if ( targetTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) { +} - targetOffset = targetValueSize / 3; +function getToneMappingFunction( functionName, toneMapping ) { - } + let toneMappingName; - const lastIndex = referenceTrack.times.length - 1; - let referenceValue; + switch ( toneMapping ) { - // Find the value to subtract out of the track - if ( referenceTime <= referenceTrack.times[ 0 ] ) { + case LinearToneMapping: + toneMappingName = 'Linear'; + break; - // Reference frame is earlier than the first keyframe, so just use the first keyframe - const startIndex = referenceOffset; - const endIndex = referenceValueSize - referenceOffset; - referenceValue = arraySlice( referenceTrack.values, startIndex, endIndex ); + case ReinhardToneMapping: + toneMappingName = 'Reinhard'; + break; - } else if ( referenceTime >= referenceTrack.times[ lastIndex ] ) { + case CineonToneMapping: + toneMappingName = 'Cineon'; + break; - // Reference frame is after the last keyframe, so just use the last keyframe - const startIndex = lastIndex * referenceValueSize + referenceOffset; - const endIndex = startIndex + referenceValueSize - referenceOffset; - referenceValue = arraySlice( referenceTrack.values, startIndex, endIndex ); + case ACESFilmicToneMapping: + toneMappingName = 'ACESFilmic'; + break; - } else { + case AgXToneMapping: + toneMappingName = 'AgX'; + break; - // Interpolate to the reference value - const interpolant = referenceTrack.createInterpolant(); - const startIndex = referenceOffset; - const endIndex = referenceValueSize - referenceOffset; - interpolant.evaluate( referenceTime ); - referenceValue = arraySlice( interpolant.resultBuffer, startIndex, endIndex ); + case NeutralToneMapping: + toneMappingName = 'Neutral'; + break; - } + case CustomToneMapping: + toneMappingName = 'Custom'; + break; - // Conjugate the quaternion - if ( referenceTrackType === 'quaternion' ) { + default: + console.warn( 'THREE.WebGLProgram: Unsupported toneMapping:', toneMapping ); + toneMappingName = 'Linear'; - const referenceQuat = new Quaternion().fromArray( referenceValue ).normalize().conjugate(); - referenceQuat.toArray( referenceValue ); + } - } + return 'vec3 ' + functionName + '( vec3 color ) { return ' + toneMappingName + 'ToneMapping( color ); }'; - // Subtract the reference value from all of the track values +} - const numTimes = targetTrack.times.length; - for ( let j = 0; j < numTimes; ++ j ) { +const _v0 = /*@__PURE__*/ new Vector3(); - const valueStart = j * targetValueSize + targetOffset; +function getLuminanceFunction() { - if ( referenceTrackType === 'quaternion' ) { + ColorManagement.getLuminanceCoefficients( _v0 ); - // Multiply the conjugate for quaternion track types - Quaternion.multiplyQuaternionsFlat( - targetTrack.values, - valueStart, - referenceValue, - 0, - targetTrack.values, - valueStart - ); + const r = _v0.x.toFixed( 4 ); + const g = _v0.y.toFixed( 4 ); + const b = _v0.z.toFixed( 4 ); - } else { + return [ - const valueEnd = targetValueSize - targetOffset * 2; + 'float luminance( const in vec3 rgb ) {', - // Subtract each value for all other numeric track types - for ( let k = 0; k < valueEnd; ++ k ) { + ` const vec3 weights = vec3( ${ r }, ${ g }, ${ b } );`, - targetTrack.values[ valueStart + k ] -= referenceValue[ k ]; + ' return dot( weights, rgb );', - } + '}' - } + ].join( '\n' ); - } +} - } +function generateVertexExtensions( parameters ) { - targetClip.blendMode = AdditiveAnimationBlendMode; + const chunks = [ + parameters.extensionClipCullDistance ? '#extension GL_ANGLE_clip_cull_distance : require' : '', + parameters.extensionMultiDraw ? '#extension GL_ANGLE_multi_draw : require' : '', + ]; - return targetClip; + return chunks.filter( filterEmptyLine ).join( '\n' ); } -const AnimationUtils = { - arraySlice: arraySlice, - convertArray: convertArray, - isTypedArray: isTypedArray, - getKeyframeOrder: getKeyframeOrder, - sortedArray: sortedArray, - flattenJSON: flattenJSON, - subclip: subclip, - makeClipAdditive: makeClipAdditive -}; - -/** - * Abstract base class of interpolants over parametric samples. - * - * The parameter domain is one dimensional, typically the time or a path - * along a curve defined by the data. - * - * The sample values can have any dimensionality and derived classes may - * apply special interpretations to the data. - * - * This class provides the interval seek in a Template Method, deferring - * the actual interpolation to derived classes. - * - * Time complexity is O(1) for linear access crossing at most two points - * and O(log N) for random access, where N is the number of positions. - * - * References: - * - * http://www.oodesign.com/template-method-pattern.html - * - */ +function generateDefines( defines ) { -class Interpolant { + const chunks = []; - constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { + for ( const name in defines ) { - this.parameterPositions = parameterPositions; - this._cachedIndex = 0; + const value = defines[ name ]; - this.resultBuffer = resultBuffer !== undefined ? - resultBuffer : new sampleValues.constructor( sampleSize ); - this.sampleValues = sampleValues; - this.valueSize = sampleSize; + if ( value === false ) continue; - this.settings = null; - this.DefaultSettings_ = {}; + chunks.push( '#define ' + name + ' ' + value ); } - evaluate( t ) { - - const pp = this.parameterPositions; - let i1 = this._cachedIndex, - t1 = pp[ i1 ], - t0 = pp[ i1 - 1 ]; + return chunks.join( '\n' ); - validate_interval: { +} - seek: { +function fetchAttributeLocations( gl, program ) { - let right; + const attributes = {}; - linear_scan: { + const n = gl.getProgramParameter( program, gl.ACTIVE_ATTRIBUTES ); - //- See http://jsperf.com/comparison-to-undefined/3 - //- slower code: - //- - //- if ( t >= t1 || t1 === undefined ) { - forward_scan: if ( ! ( t < t1 ) ) { + for ( let i = 0; i < n; i ++ ) { - for ( let giveUpAt = i1 + 2; ; ) { + const info = gl.getActiveAttrib( program, i ); + const name = info.name; - if ( t1 === undefined ) { + let locationSize = 1; + if ( info.type === gl.FLOAT_MAT2 ) locationSize = 2; + if ( info.type === gl.FLOAT_MAT3 ) locationSize = 3; + if ( info.type === gl.FLOAT_MAT4 ) locationSize = 4; - if ( t < t0 ) break forward_scan; + // console.log( 'THREE.WebGLProgram: ACTIVE VERTEX ATTRIBUTE:', name, i ); - // after end + attributes[ name ] = { + type: info.type, + location: gl.getAttribLocation( program, name ), + locationSize: locationSize + }; - i1 = pp.length; - this._cachedIndex = i1; - return this.copySampleValue_( i1 - 1 ); + } - } + return attributes; - if ( i1 === giveUpAt ) break; // this loop +} - t0 = t1; - t1 = pp[ ++ i1 ]; +function filterEmptyLine( string ) { - if ( t < t1 ) { + return string !== ''; - // we have arrived at the sought interval - break seek; +} - } +function replaceLightNums( string, parameters ) { - } + const numSpotLightCoords = parameters.numSpotLightShadows + parameters.numSpotLightMaps - parameters.numSpotLightShadowsWithMaps; - // prepare binary search on the right side of the index - right = pp.length; - break linear_scan; + return string + .replace( /NUM_DIR_LIGHTS/g, parameters.numDirLights ) + .replace( /NUM_SPOT_LIGHTS/g, parameters.numSpotLights ) + .replace( /NUM_SPOT_LIGHT_MAPS/g, parameters.numSpotLightMaps ) + .replace( /NUM_SPOT_LIGHT_COORDS/g, numSpotLightCoords ) + .replace( /NUM_RECT_AREA_LIGHTS/g, parameters.numRectAreaLights ) + .replace( /NUM_POINT_LIGHTS/g, parameters.numPointLights ) + .replace( /NUM_HEMI_LIGHTS/g, parameters.numHemiLights ) + .replace( /NUM_DIR_LIGHT_SHADOWS/g, parameters.numDirLightShadows ) + .replace( /NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS/g, parameters.numSpotLightShadowsWithMaps ) + .replace( /NUM_SPOT_LIGHT_SHADOWS/g, parameters.numSpotLightShadows ) + .replace( /NUM_POINT_LIGHT_SHADOWS/g, parameters.numPointLightShadows ); - } +} - //- slower code: - //- if ( t < t0 || t0 === undefined ) { - if ( ! ( t >= t0 ) ) { +function replaceClippingPlaneNums( string, parameters ) { - // looping? + return string + .replace( /NUM_CLIPPING_PLANES/g, parameters.numClippingPlanes ) + .replace( /UNION_CLIPPING_PLANES/g, ( parameters.numClippingPlanes - parameters.numClipIntersection ) ); - const t1global = pp[ 1 ]; +} - if ( t < t1global ) { +// Resolve Includes - i1 = 2; // + 1, using the scan for the details - t0 = t1global; +const includePattern = /^[ \t]*#include +<([\w\d./]+)>/gm; - } +function resolveIncludes( string ) { - // linear reverse scan + return string.replace( includePattern, includeReplacer ); - for ( let giveUpAt = i1 - 2; ; ) { +} - if ( t0 === undefined ) { +const shaderChunkMap = new Map(); - // before start +function includeReplacer( match, include ) { - this._cachedIndex = 0; - return this.copySampleValue_( 0 ); + let string = ShaderChunk[ include ]; - } + if ( string === undefined ) { - if ( i1 === giveUpAt ) break; // this loop + const newInclude = shaderChunkMap.get( include ); - t1 = t0; - t0 = pp[ -- i1 - 1 ]; + if ( newInclude !== undefined ) { - if ( t >= t0 ) { + string = ShaderChunk[ newInclude ]; + console.warn( 'THREE.WebGLRenderer: Shader chunk "%s" has been deprecated. Use "%s" instead.', include, newInclude ); - // we have arrived at the sought interval - break seek; + } else { - } + throw new Error( 'Can not resolve #include <' + include + '>' ); - } + } - // prepare binary search on the left side of the index - right = i1; - i1 = 0; - break linear_scan; + } - } + return resolveIncludes( string ); - // the interval is valid +} - break validate_interval; +// Unroll Loops - } // linear scan +const unrollLoopPattern = /#pragma unroll_loop_start\s+for\s*\(\s*int\s+i\s*=\s*(\d+)\s*;\s*i\s*<\s*(\d+)\s*;\s*i\s*\+\+\s*\)\s*{([\s\S]+?)}\s+#pragma unroll_loop_end/g; - // binary search +function unrollLoops( string ) { - while ( i1 < right ) { + return string.replace( unrollLoopPattern, loopReplacer ); - const mid = ( i1 + right ) >>> 1; +} - if ( t < pp[ mid ] ) { +function loopReplacer( match, start, end, snippet ) { - right = mid; + let string = ''; - } else { + for ( let i = parseInt( start ); i < parseInt( end ); i ++ ) { - i1 = mid + 1; + string += snippet + .replace( /\[\s*i\s*\]/g, '[ ' + i + ' ]' ) + .replace( /UNROLLED_LOOP_INDEX/g, i ); - } + } - } + return string; - t1 = pp[ i1 ]; - t0 = pp[ i1 - 1 ]; +} - // check boundary cases, again +// - if ( t0 === undefined ) { +function generatePrecision( parameters ) { - this._cachedIndex = 0; - return this.copySampleValue_( 0 ); + let precisionstring = `precision ${parameters.precision} float; + precision ${parameters.precision} int; + precision ${parameters.precision} sampler2D; + precision ${parameters.precision} samplerCube; + precision ${parameters.precision} sampler3D; + precision ${parameters.precision} sampler2DArray; + precision ${parameters.precision} sampler2DShadow; + precision ${parameters.precision} samplerCubeShadow; + precision ${parameters.precision} sampler2DArrayShadow; + precision ${parameters.precision} isampler2D; + precision ${parameters.precision} isampler3D; + precision ${parameters.precision} isamplerCube; + precision ${parameters.precision} isampler2DArray; + precision ${parameters.precision} usampler2D; + precision ${parameters.precision} usampler3D; + precision ${parameters.precision} usamplerCube; + precision ${parameters.precision} usampler2DArray; + `; - } + if ( parameters.precision === 'highp' ) { - if ( t1 === undefined ) { + precisionstring += '\n#define HIGH_PRECISION'; - i1 = pp.length; - this._cachedIndex = i1; - return this.copySampleValue_( i1 - 1 ); + } else if ( parameters.precision === 'mediump' ) { - } + precisionstring += '\n#define MEDIUM_PRECISION'; - } // seek + } else if ( parameters.precision === 'lowp' ) { - this._cachedIndex = i1; + precisionstring += '\n#define LOW_PRECISION'; - this.intervalChanged_( i1, t0, t1 ); + } - } // validate_interval + return precisionstring; - return this.interpolate_( i1, t0, t, t1 ); +} - } +function generateShadowMapTypeDefine( parameters ) { - getSettings_() { + let shadowMapTypeDefine = 'SHADOWMAP_TYPE_BASIC'; - return this.settings || this.DefaultSettings_; + if ( parameters.shadowMapType === PCFShadowMap ) { - } + shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF'; - copySampleValue_( index ) { + } else if ( parameters.shadowMapType === PCFSoftShadowMap ) { - // copies a sample value to the result buffer + shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF_SOFT'; - const result = this.resultBuffer, - values = this.sampleValues, - stride = this.valueSize, - offset = index * stride; + } else if ( parameters.shadowMapType === VSMShadowMap ) { - for ( let i = 0; i !== stride; ++ i ) { + shadowMapTypeDefine = 'SHADOWMAP_TYPE_VSM'; - result[ i ] = values[ offset + i ]; + } - } + return shadowMapTypeDefine; - return result; +} - } +function generateEnvMapTypeDefine( parameters ) { - // Template methods for derived classes: + let envMapTypeDefine = 'ENVMAP_TYPE_CUBE'; - interpolate_( /* i1, t0, t, t1 */ ) { + if ( parameters.envMap ) { - throw new Error( 'call to abstract method' ); - // implementations shall return this.resultBuffer + switch ( parameters.envMapMode ) { - } + case CubeReflectionMapping: + case CubeRefractionMapping: + envMapTypeDefine = 'ENVMAP_TYPE_CUBE'; + break; - intervalChanged_( /* i1, t0, t1 */ ) { + case CubeUVReflectionMapping: + envMapTypeDefine = 'ENVMAP_TYPE_CUBE_UV'; + break; - // empty + } } -} + return envMapTypeDefine; -/** - * Fast and simple cubic spline interpolant. - * - * It was derived from a Hermitian construction setting the first derivative - * at each sample position to the linear slope between neighboring positions - * over their parameter interval. - */ +} -class CubicInterpolant extends Interpolant { +function generateEnvMapModeDefine( parameters ) { - constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { + let envMapModeDefine = 'ENVMAP_MODE_REFLECTION'; - super( parameterPositions, sampleValues, sampleSize, resultBuffer ); + if ( parameters.envMap ) { - this._weightPrev = - 0; - this._offsetPrev = - 0; - this._weightNext = - 0; - this._offsetNext = - 0; + switch ( parameters.envMapMode ) { - this.DefaultSettings_ = { + case CubeRefractionMapping: - endingStart: ZeroCurvatureEnding, - endingEnd: ZeroCurvatureEnding + envMapModeDefine = 'ENVMAP_MODE_REFRACTION'; + break; - }; + } } - intervalChanged_( i1, t0, t1 ) { - - const pp = this.parameterPositions; - let iPrev = i1 - 2, - iNext = i1 + 1, + return envMapModeDefine; - tPrev = pp[ iPrev ], - tNext = pp[ iNext ]; +} - if ( tPrev === undefined ) { +function generateEnvMapBlendingDefine( parameters ) { - switch ( this.getSettings_().endingStart ) { + let envMapBlendingDefine = 'ENVMAP_BLENDING_NONE'; - case ZeroSlopeEnding: + if ( parameters.envMap ) { - // f'(t0) = 0 - iPrev = i1; - tPrev = 2 * t0 - t1; + switch ( parameters.combine ) { - break; + case MultiplyOperation: + envMapBlendingDefine = 'ENVMAP_BLENDING_MULTIPLY'; + break; - case WrapAroundEnding: + case MixOperation: + envMapBlendingDefine = 'ENVMAP_BLENDING_MIX'; + break; - // use the other end of the curve - iPrev = pp.length - 2; - tPrev = t0 + pp[ iPrev ] - pp[ iPrev + 1 ]; + case AddOperation: + envMapBlendingDefine = 'ENVMAP_BLENDING_ADD'; + break; - break; + } - default: // ZeroCurvatureEnding + } - // f''(t0) = 0 a.k.a. Natural Spline - iPrev = i1; - tPrev = t1; + return envMapBlendingDefine; - } +} - } +function generateCubeUVSize( parameters ) { - if ( tNext === undefined ) { + const imageHeight = parameters.envMapCubeUVHeight; - switch ( this.getSettings_().endingEnd ) { + if ( imageHeight === null ) return null; - case ZeroSlopeEnding: + const maxMip = Math.log2( imageHeight ) - 2; - // f'(tN) = 0 - iNext = i1; - tNext = 2 * t1 - t0; + const texelHeight = 1.0 / imageHeight; - break; + const texelWidth = 1.0 / ( 3 * Math.max( Math.pow( 2, maxMip ), 7 * 16 ) ); - case WrapAroundEnding: + return { texelWidth, texelHeight, maxMip }; - // use the other end of the curve - iNext = 1; - tNext = t1 + pp[ 1 ] - pp[ 0 ]; +} - break; +function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) { - default: // ZeroCurvatureEnding + // TODO Send this event to Three.js DevTools + // console.log( 'WebGLProgram', cacheKey ); - // f''(tN) = 0, a.k.a. Natural Spline - iNext = i1 - 1; - tNext = t0; + const gl = renderer.getContext(); - } + const defines = parameters.defines; - } + let vertexShader = parameters.vertexShader; + let fragmentShader = parameters.fragmentShader; - const halfDt = ( t1 - t0 ) * 0.5, - stride = this.valueSize; + const shadowMapTypeDefine = generateShadowMapTypeDefine( parameters ); + const envMapTypeDefine = generateEnvMapTypeDefine( parameters ); + const envMapModeDefine = generateEnvMapModeDefine( parameters ); + const envMapBlendingDefine = generateEnvMapBlendingDefine( parameters ); + const envMapCubeUVSize = generateCubeUVSize( parameters ); - this._weightPrev = halfDt / ( t0 - tPrev ); - this._weightNext = halfDt / ( tNext - t1 ); - this._offsetPrev = iPrev * stride; - this._offsetNext = iNext * stride; + const customVertexExtensions = generateVertexExtensions( parameters ); - } + const customDefines = generateDefines( defines ); - interpolate_( i1, t0, t, t1 ) { + const program = gl.createProgram(); - const result = this.resultBuffer, - values = this.sampleValues, - stride = this.valueSize, + let prefixVertex, prefixFragment; + let versionString = parameters.glslVersion ? '#version ' + parameters.glslVersion + '\n' : ''; - o1 = i1 * stride, o0 = o1 - stride, - oP = this._offsetPrev, oN = this._offsetNext, - wP = this._weightPrev, wN = this._weightNext, + if ( parameters.isRawShaderMaterial ) { - p = ( t - t0 ) / ( t1 - t0 ), - pp = p * p, - ppp = pp * p; + prefixVertex = [ - // evaluate polynomials + '#define SHADER_TYPE ' + parameters.shaderType, + '#define SHADER_NAME ' + parameters.shaderName, - const sP = - wP * ppp + 2 * wP * pp - wP * p; - const s0 = ( 1 + wP ) * ppp + ( - 1.5 - 2 * wP ) * pp + ( - 0.5 + wP ) * p + 1; - const s1 = ( - 1 - wN ) * ppp + ( 1.5 + wN ) * pp + 0.5 * p; - const sN = wN * ppp - wN * pp; + customDefines - // combine data linearly + ].filter( filterEmptyLine ).join( '\n' ); - for ( let i = 0; i !== stride; ++ i ) { + if ( prefixVertex.length > 0 ) { - result[ i ] = - sP * values[ oP + i ] + - s0 * values[ o0 + i ] + - s1 * values[ o1 + i ] + - sN * values[ oN + i ]; + prefixVertex += '\n'; } - return result; - - } - -} + prefixFragment = [ -class LinearInterpolant extends Interpolant { + '#define SHADER_TYPE ' + parameters.shaderType, + '#define SHADER_NAME ' + parameters.shaderName, - constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { + customDefines - super( parameterPositions, sampleValues, sampleSize, resultBuffer ); + ].filter( filterEmptyLine ).join( '\n' ); - } + if ( prefixFragment.length > 0 ) { - interpolate_( i1, t0, t, t1 ) { + prefixFragment += '\n'; - const result = this.resultBuffer, - values = this.sampleValues, - stride = this.valueSize, + } - offset1 = i1 * stride, - offset0 = offset1 - stride, + } else { - weight1 = ( t - t0 ) / ( t1 - t0 ), - weight0 = 1 - weight1; + prefixVertex = [ - for ( let i = 0; i !== stride; ++ i ) { + generatePrecision( parameters ), - result[ i ] = - values[ offset0 + i ] * weight0 + - values[ offset1 + i ] * weight1; + '#define SHADER_TYPE ' + parameters.shaderType, + '#define SHADER_NAME ' + parameters.shaderName, - } + customDefines, - return result; + parameters.extensionClipCullDistance ? '#define USE_CLIP_DISTANCE' : '', + parameters.batching ? '#define USE_BATCHING' : '', + parameters.batchingColor ? '#define USE_BATCHING_COLOR' : '', + parameters.instancing ? '#define USE_INSTANCING' : '', + parameters.instancingColor ? '#define USE_INSTANCING_COLOR' : '', + parameters.instancingMorph ? '#define USE_INSTANCING_MORPH' : '', - } + parameters.useFog && parameters.fog ? '#define USE_FOG' : '', + parameters.useFog && parameters.fogExp2 ? '#define FOG_EXP2' : '', -} + parameters.map ? '#define USE_MAP' : '', + parameters.envMap ? '#define USE_ENVMAP' : '', + parameters.envMap ? '#define ' + envMapModeDefine : '', + parameters.lightMap ? '#define USE_LIGHTMAP' : '', + parameters.aoMap ? '#define USE_AOMAP' : '', + parameters.bumpMap ? '#define USE_BUMPMAP' : '', + parameters.normalMap ? '#define USE_NORMALMAP' : '', + parameters.normalMapObjectSpace ? '#define USE_NORMALMAP_OBJECTSPACE' : '', + parameters.normalMapTangentSpace ? '#define USE_NORMALMAP_TANGENTSPACE' : '', + parameters.displacementMap ? '#define USE_DISPLACEMENTMAP' : '', + parameters.emissiveMap ? '#define USE_EMISSIVEMAP' : '', -/** - * - * Interpolant that evaluates to the sample value at the position preceding - * the parameter. - */ + parameters.anisotropy ? '#define USE_ANISOTROPY' : '', + parameters.anisotropyMap ? '#define USE_ANISOTROPYMAP' : '', -class DiscreteInterpolant extends Interpolant { + parameters.clearcoatMap ? '#define USE_CLEARCOATMAP' : '', + parameters.clearcoatRoughnessMap ? '#define USE_CLEARCOAT_ROUGHNESSMAP' : '', + parameters.clearcoatNormalMap ? '#define USE_CLEARCOAT_NORMALMAP' : '', - constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { + parameters.iridescenceMap ? '#define USE_IRIDESCENCEMAP' : '', + parameters.iridescenceThicknessMap ? '#define USE_IRIDESCENCE_THICKNESSMAP' : '', - super( parameterPositions, sampleValues, sampleSize, resultBuffer ); + parameters.specularMap ? '#define USE_SPECULARMAP' : '', + parameters.specularColorMap ? '#define USE_SPECULAR_COLORMAP' : '', + parameters.specularIntensityMap ? '#define USE_SPECULAR_INTENSITYMAP' : '', - } + parameters.roughnessMap ? '#define USE_ROUGHNESSMAP' : '', + parameters.metalnessMap ? '#define USE_METALNESSMAP' : '', + parameters.alphaMap ? '#define USE_ALPHAMAP' : '', + parameters.alphaHash ? '#define USE_ALPHAHASH' : '', - interpolate_( i1 /*, t0, t, t1 */ ) { + parameters.transmission ? '#define USE_TRANSMISSION' : '', + parameters.transmissionMap ? '#define USE_TRANSMISSIONMAP' : '', + parameters.thicknessMap ? '#define USE_THICKNESSMAP' : '', - return this.copySampleValue_( i1 - 1 ); + parameters.sheenColorMap ? '#define USE_SHEEN_COLORMAP' : '', + parameters.sheenRoughnessMap ? '#define USE_SHEEN_ROUGHNESSMAP' : '', - } + // -} + parameters.mapUv ? '#define MAP_UV ' + parameters.mapUv : '', + parameters.alphaMapUv ? '#define ALPHAMAP_UV ' + parameters.alphaMapUv : '', + parameters.lightMapUv ? '#define LIGHTMAP_UV ' + parameters.lightMapUv : '', + parameters.aoMapUv ? '#define AOMAP_UV ' + parameters.aoMapUv : '', + parameters.emissiveMapUv ? '#define EMISSIVEMAP_UV ' + parameters.emissiveMapUv : '', + parameters.bumpMapUv ? '#define BUMPMAP_UV ' + parameters.bumpMapUv : '', + parameters.normalMapUv ? '#define NORMALMAP_UV ' + parameters.normalMapUv : '', + parameters.displacementMapUv ? '#define DISPLACEMENTMAP_UV ' + parameters.displacementMapUv : '', -class KeyframeTrack { + parameters.metalnessMapUv ? '#define METALNESSMAP_UV ' + parameters.metalnessMapUv : '', + parameters.roughnessMapUv ? '#define ROUGHNESSMAP_UV ' + parameters.roughnessMapUv : '', - constructor( name, times, values, interpolation ) { + parameters.anisotropyMapUv ? '#define ANISOTROPYMAP_UV ' + parameters.anisotropyMapUv : '', - if ( name === undefined ) throw new Error( 'THREE.KeyframeTrack: track name is undefined' ); - if ( times === undefined || times.length === 0 ) throw new Error( 'THREE.KeyframeTrack: no keyframes in track named ' + name ); + parameters.clearcoatMapUv ? '#define CLEARCOATMAP_UV ' + parameters.clearcoatMapUv : '', + parameters.clearcoatNormalMapUv ? '#define CLEARCOAT_NORMALMAP_UV ' + parameters.clearcoatNormalMapUv : '', + parameters.clearcoatRoughnessMapUv ? '#define CLEARCOAT_ROUGHNESSMAP_UV ' + parameters.clearcoatRoughnessMapUv : '', - this.name = name; + parameters.iridescenceMapUv ? '#define IRIDESCENCEMAP_UV ' + parameters.iridescenceMapUv : '', + parameters.iridescenceThicknessMapUv ? '#define IRIDESCENCE_THICKNESSMAP_UV ' + parameters.iridescenceThicknessMapUv : '', - this.times = convertArray( times, this.TimeBufferType ); - this.values = convertArray( values, this.ValueBufferType ); + parameters.sheenColorMapUv ? '#define SHEEN_COLORMAP_UV ' + parameters.sheenColorMapUv : '', + parameters.sheenRoughnessMapUv ? '#define SHEEN_ROUGHNESSMAP_UV ' + parameters.sheenRoughnessMapUv : '', - this.setInterpolation( interpolation || this.DefaultInterpolation ); + parameters.specularMapUv ? '#define SPECULARMAP_UV ' + parameters.specularMapUv : '', + parameters.specularColorMapUv ? '#define SPECULAR_COLORMAP_UV ' + parameters.specularColorMapUv : '', + parameters.specularIntensityMapUv ? '#define SPECULAR_INTENSITYMAP_UV ' + parameters.specularIntensityMapUv : '', - } + parameters.transmissionMapUv ? '#define TRANSMISSIONMAP_UV ' + parameters.transmissionMapUv : '', + parameters.thicknessMapUv ? '#define THICKNESSMAP_UV ' + parameters.thicknessMapUv : '', - // Serialization (in static context, because of constructor invocation - // and automatic invocation of .toJSON): + // - static toJSON( track ) { + parameters.vertexTangents && parameters.flatShading === false ? '#define USE_TANGENT' : '', + parameters.vertexColors ? '#define USE_COLOR' : '', + parameters.vertexAlphas ? '#define USE_COLOR_ALPHA' : '', + parameters.vertexUv1s ? '#define USE_UV1' : '', + parameters.vertexUv2s ? '#define USE_UV2' : '', + parameters.vertexUv3s ? '#define USE_UV3' : '', - const trackType = track.constructor; + parameters.pointsUvs ? '#define USE_POINTS_UV' : '', - let json; + parameters.flatShading ? '#define FLAT_SHADED' : '', - // derived classes can define a static toJSON method - if ( trackType.toJSON !== this.toJSON ) { + parameters.skinning ? '#define USE_SKINNING' : '', - json = trackType.toJSON( track ); + parameters.morphTargets ? '#define USE_MORPHTARGETS' : '', + parameters.morphNormals && parameters.flatShading === false ? '#define USE_MORPHNORMALS' : '', + ( parameters.morphColors ) ? '#define USE_MORPHCOLORS' : '', + ( parameters.morphTargetsCount > 0 ) ? '#define MORPHTARGETS_TEXTURE_STRIDE ' + parameters.morphTextureStride : '', + ( parameters.morphTargetsCount > 0 ) ? '#define MORPHTARGETS_COUNT ' + parameters.morphTargetsCount : '', + parameters.doubleSided ? '#define DOUBLE_SIDED' : '', + parameters.flipSided ? '#define FLIP_SIDED' : '', - } else { + parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '', + parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '', - // by default, we assume the data can be serialized as-is - json = { + parameters.sizeAttenuation ? '#define USE_SIZEATTENUATION' : '', - 'name': track.name, - 'times': convertArray( track.times, Array ), - 'values': convertArray( track.values, Array ) + parameters.numLightProbes > 0 ? '#define USE_LIGHT_PROBES' : '', - }; + parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '', + parameters.reverseDepthBuffer ? '#define USE_REVERSEDEPTHBUF' : '', - const interpolation = track.getInterpolation(); + 'uniform mat4 modelMatrix;', + 'uniform mat4 modelViewMatrix;', + 'uniform mat4 projectionMatrix;', + 'uniform mat4 viewMatrix;', + 'uniform mat3 normalMatrix;', + 'uniform vec3 cameraPosition;', + 'uniform bool isOrthographic;', - if ( interpolation !== track.DefaultInterpolation ) { + '#ifdef USE_INSTANCING', - json.interpolation = interpolation; + ' attribute mat4 instanceMatrix;', - } + '#endif', - } + '#ifdef USE_INSTANCING_COLOR', - json.type = track.ValueTypeName; // mandatory + ' attribute vec3 instanceColor;', - return json; + '#endif', - } + '#ifdef USE_INSTANCING_MORPH', - InterpolantFactoryMethodDiscrete( result ) { + ' uniform sampler2D morphTexture;', - return new DiscreteInterpolant( this.times, this.values, this.getValueSize(), result ); + '#endif', - } + 'attribute vec3 position;', + 'attribute vec3 normal;', + 'attribute vec2 uv;', - InterpolantFactoryMethodLinear( result ) { + '#ifdef USE_UV1', - return new LinearInterpolant( this.times, this.values, this.getValueSize(), result ); + ' attribute vec2 uv1;', - } + '#endif', - InterpolantFactoryMethodSmooth( result ) { + '#ifdef USE_UV2', - return new CubicInterpolant( this.times, this.values, this.getValueSize(), result ); + ' attribute vec2 uv2;', - } + '#endif', - setInterpolation( interpolation ) { + '#ifdef USE_UV3', - let factoryMethod; + ' attribute vec2 uv3;', - switch ( interpolation ) { + '#endif', - case InterpolateDiscrete: + '#ifdef USE_TANGENT', - factoryMethod = this.InterpolantFactoryMethodDiscrete; + ' attribute vec4 tangent;', - break; + '#endif', - case InterpolateLinear: + '#if defined( USE_COLOR_ALPHA )', - factoryMethod = this.InterpolantFactoryMethodLinear; + ' attribute vec4 color;', - break; + '#elif defined( USE_COLOR )', - case InterpolateSmooth: + ' attribute vec3 color;', - factoryMethod = this.InterpolantFactoryMethodSmooth; + '#endif', - break; + '#ifdef USE_SKINNING', - } + ' attribute vec4 skinIndex;', + ' attribute vec4 skinWeight;', - if ( factoryMethod === undefined ) { + '#endif', - const message = 'unsupported interpolation for ' + - this.ValueTypeName + ' keyframe track named ' + this.name; + '\n' - if ( this.createInterpolant === undefined ) { + ].filter( filterEmptyLine ).join( '\n' ); - // fall back to default, unless the default itself is messed up - if ( interpolation !== this.DefaultInterpolation ) { + prefixFragment = [ - this.setInterpolation( this.DefaultInterpolation ); + generatePrecision( parameters ), - } else { + '#define SHADER_TYPE ' + parameters.shaderType, + '#define SHADER_NAME ' + parameters.shaderName, - throw new Error( message ); // fatal, in this case + customDefines, - } + parameters.useFog && parameters.fog ? '#define USE_FOG' : '', + parameters.useFog && parameters.fogExp2 ? '#define FOG_EXP2' : '', - } + parameters.alphaToCoverage ? '#define ALPHA_TO_COVERAGE' : '', + parameters.map ? '#define USE_MAP' : '', + parameters.matcap ? '#define USE_MATCAP' : '', + parameters.envMap ? '#define USE_ENVMAP' : '', + parameters.envMap ? '#define ' + envMapTypeDefine : '', + parameters.envMap ? '#define ' + envMapModeDefine : '', + parameters.envMap ? '#define ' + envMapBlendingDefine : '', + envMapCubeUVSize ? '#define CUBEUV_TEXEL_WIDTH ' + envMapCubeUVSize.texelWidth : '', + envMapCubeUVSize ? '#define CUBEUV_TEXEL_HEIGHT ' + envMapCubeUVSize.texelHeight : '', + envMapCubeUVSize ? '#define CUBEUV_MAX_MIP ' + envMapCubeUVSize.maxMip + '.0' : '', + parameters.lightMap ? '#define USE_LIGHTMAP' : '', + parameters.aoMap ? '#define USE_AOMAP' : '', + parameters.bumpMap ? '#define USE_BUMPMAP' : '', + parameters.normalMap ? '#define USE_NORMALMAP' : '', + parameters.normalMapObjectSpace ? '#define USE_NORMALMAP_OBJECTSPACE' : '', + parameters.normalMapTangentSpace ? '#define USE_NORMALMAP_TANGENTSPACE' : '', + parameters.emissiveMap ? '#define USE_EMISSIVEMAP' : '', - console.warn( 'THREE.KeyframeTrack:', message ); - return this; + parameters.anisotropy ? '#define USE_ANISOTROPY' : '', + parameters.anisotropyMap ? '#define USE_ANISOTROPYMAP' : '', - } + parameters.clearcoat ? '#define USE_CLEARCOAT' : '', + parameters.clearcoatMap ? '#define USE_CLEARCOATMAP' : '', + parameters.clearcoatRoughnessMap ? '#define USE_CLEARCOAT_ROUGHNESSMAP' : '', + parameters.clearcoatNormalMap ? '#define USE_CLEARCOAT_NORMALMAP' : '', - this.createInterpolant = factoryMethod; + parameters.dispersion ? '#define USE_DISPERSION' : '', - return this; + parameters.iridescence ? '#define USE_IRIDESCENCE' : '', + parameters.iridescenceMap ? '#define USE_IRIDESCENCEMAP' : '', + parameters.iridescenceThicknessMap ? '#define USE_IRIDESCENCE_THICKNESSMAP' : '', - } + parameters.specularMap ? '#define USE_SPECULARMAP' : '', + parameters.specularColorMap ? '#define USE_SPECULAR_COLORMAP' : '', + parameters.specularIntensityMap ? '#define USE_SPECULAR_INTENSITYMAP' : '', - getInterpolation() { + parameters.roughnessMap ? '#define USE_ROUGHNESSMAP' : '', + parameters.metalnessMap ? '#define USE_METALNESSMAP' : '', - switch ( this.createInterpolant ) { + parameters.alphaMap ? '#define USE_ALPHAMAP' : '', + parameters.alphaTest ? '#define USE_ALPHATEST' : '', + parameters.alphaHash ? '#define USE_ALPHAHASH' : '', - case this.InterpolantFactoryMethodDiscrete: + parameters.sheen ? '#define USE_SHEEN' : '', + parameters.sheenColorMap ? '#define USE_SHEEN_COLORMAP' : '', + parameters.sheenRoughnessMap ? '#define USE_SHEEN_ROUGHNESSMAP' : '', - return InterpolateDiscrete; + parameters.transmission ? '#define USE_TRANSMISSION' : '', + parameters.transmissionMap ? '#define USE_TRANSMISSIONMAP' : '', + parameters.thicknessMap ? '#define USE_THICKNESSMAP' : '', - case this.InterpolantFactoryMethodLinear: + parameters.vertexTangents && parameters.flatShading === false ? '#define USE_TANGENT' : '', + parameters.vertexColors || parameters.instancingColor || parameters.batchingColor ? '#define USE_COLOR' : '', + parameters.vertexAlphas ? '#define USE_COLOR_ALPHA' : '', + parameters.vertexUv1s ? '#define USE_UV1' : '', + parameters.vertexUv2s ? '#define USE_UV2' : '', + parameters.vertexUv3s ? '#define USE_UV3' : '', - return InterpolateLinear; + parameters.pointsUvs ? '#define USE_POINTS_UV' : '', - case this.InterpolantFactoryMethodSmooth: + parameters.gradientMap ? '#define USE_GRADIENTMAP' : '', - return InterpolateSmooth; + parameters.flatShading ? '#define FLAT_SHADED' : '', - } + parameters.doubleSided ? '#define DOUBLE_SIDED' : '', + parameters.flipSided ? '#define FLIP_SIDED' : '', - } + parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '', + parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '', - getValueSize() { + parameters.premultipliedAlpha ? '#define PREMULTIPLIED_ALPHA' : '', - return this.values.length / this.times.length; + parameters.numLightProbes > 0 ? '#define USE_LIGHT_PROBES' : '', - } + parameters.decodeVideoTexture ? '#define DECODE_VIDEO_TEXTURE' : '', + parameters.decodeVideoTextureEmissive ? '#define DECODE_VIDEO_TEXTURE_EMISSIVE' : '', - // move all keyframes either forwards or backwards in time - shift( timeOffset ) { + parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '', + parameters.reverseDepthBuffer ? '#define USE_REVERSEDEPTHBUF' : '', - if ( timeOffset !== 0.0 ) { + 'uniform mat4 viewMatrix;', + 'uniform vec3 cameraPosition;', + 'uniform bool isOrthographic;', - const times = this.times; + ( parameters.toneMapping !== NoToneMapping ) ? '#define TONE_MAPPING' : '', + ( parameters.toneMapping !== NoToneMapping ) ? ShaderChunk[ 'tonemapping_pars_fragment' ] : '', // this code is required here because it is used by the toneMapping() function defined below + ( parameters.toneMapping !== NoToneMapping ) ? getToneMappingFunction( 'toneMapping', parameters.toneMapping ) : '', - for ( let i = 0, n = times.length; i !== n; ++ i ) { + parameters.dithering ? '#define DITHERING' : '', + parameters.opaque ? '#define OPAQUE' : '', - times[ i ] += timeOffset; + ShaderChunk[ 'colorspace_pars_fragment' ], // this code is required here because it is used by the various encoding/decoding function defined below + getTexelEncodingFunction( 'linearToOutputTexel', parameters.outputColorSpace ), + getLuminanceFunction(), - } + parameters.useDepthPacking ? '#define DEPTH_PACKING ' + parameters.depthPacking : '', - } + '\n' - return this; + ].filter( filterEmptyLine ).join( '\n' ); } - // scale all keyframe times by a factor (useful for frame <-> seconds conversions) - scale( timeScale ) { + vertexShader = resolveIncludes( vertexShader ); + vertexShader = replaceLightNums( vertexShader, parameters ); + vertexShader = replaceClippingPlaneNums( vertexShader, parameters ); - if ( timeScale !== 1.0 ) { + fragmentShader = resolveIncludes( fragmentShader ); + fragmentShader = replaceLightNums( fragmentShader, parameters ); + fragmentShader = replaceClippingPlaneNums( fragmentShader, parameters ); - const times = this.times; + vertexShader = unrollLoops( vertexShader ); + fragmentShader = unrollLoops( fragmentShader ); - for ( let i = 0, n = times.length; i !== n; ++ i ) { + if ( parameters.isRawShaderMaterial !== true ) { - times[ i ] *= timeScale; + // GLSL 3.0 conversion for built-in materials and ShaderMaterial - } + versionString = '#version 300 es\n'; - } + prefixVertex = [ + customVertexExtensions, + '#define attribute in', + '#define varying out', + '#define texture2D texture' + ].join( '\n' ) + '\n' + prefixVertex; - return this; + prefixFragment = [ + '#define varying in', + ( parameters.glslVersion === GLSL3 ) ? '' : 'layout(location = 0) out highp vec4 pc_fragColor;', + ( parameters.glslVersion === GLSL3 ) ? '' : '#define gl_FragColor pc_fragColor', + '#define gl_FragDepthEXT gl_FragDepth', + '#define texture2D texture', + '#define textureCube texture', + '#define texture2DProj textureProj', + '#define texture2DLodEXT textureLod', + '#define texture2DProjLodEXT textureProjLod', + '#define textureCubeLodEXT textureLod', + '#define texture2DGradEXT textureGrad', + '#define texture2DProjGradEXT textureProjGrad', + '#define textureCubeGradEXT textureGrad' + ].join( '\n' ) + '\n' + prefixFragment; } - // removes keyframes before and after animation without changing any values within the range [startTime, endTime]. - // IMPORTANT: We do not shift around keys to the start of the track time, because for interpolated keys this will change their values - trim( startTime, endTime ) { - - const times = this.times, - nKeys = times.length; - - let from = 0, - to = nKeys - 1; - - while ( from !== nKeys && times[ from ] < startTime ) { - - ++ from; - - } - - while ( to !== - 1 && times[ to ] > endTime ) { - - -- to; - - } + const vertexGlsl = versionString + prefixVertex + vertexShader; + const fragmentGlsl = versionString + prefixFragment + fragmentShader; - ++ to; // inclusive -> exclusive bound + // console.log( '*VERTEX*', vertexGlsl ); + // console.log( '*FRAGMENT*', fragmentGlsl ); - if ( from !== 0 || to !== nKeys ) { + const glVertexShader = WebGLShader( gl, gl.VERTEX_SHADER, vertexGlsl ); + const glFragmentShader = WebGLShader( gl, gl.FRAGMENT_SHADER, fragmentGlsl ); - // empty tracks are forbidden, so keep at least one keyframe - if ( from >= to ) { + gl.attachShader( program, glVertexShader ); + gl.attachShader( program, glFragmentShader ); - to = Math.max( to, 1 ); - from = to - 1; + // Force a particular attribute to index 0. - } + if ( parameters.index0AttributeName !== undefined ) { - const stride = this.getValueSize(); - this.times = arraySlice( times, from, to ); - this.values = arraySlice( this.values, from * stride, to * stride ); + gl.bindAttribLocation( program, 0, parameters.index0AttributeName ); - } + } else if ( parameters.morphTargets === true ) { - return this; + // programs with morphTargets displace position out of attribute 0 + gl.bindAttribLocation( program, 0, 'position' ); } - // ensure we do not get a GarbageInGarbageOut situation, make sure tracks are at least minimally viable - validate() { + gl.linkProgram( program ); - let valid = true; + function onFirstUse( self ) { - const valueSize = this.getValueSize(); - if ( valueSize - Math.floor( valueSize ) !== 0 ) { + // check for link errors + if ( renderer.debug.checkShaderErrors ) { - console.error( 'THREE.KeyframeTrack: Invalid value size in track.', this ); - valid = false; + const programLog = gl.getProgramInfoLog( program ).trim(); + const vertexLog = gl.getShaderInfoLog( glVertexShader ).trim(); + const fragmentLog = gl.getShaderInfoLog( glFragmentShader ).trim(); - } + let runnable = true; + let haveDiagnostics = true; - const times = this.times, - values = this.values, + if ( gl.getProgramParameter( program, gl.LINK_STATUS ) === false ) { - nKeys = times.length; + runnable = false; - if ( nKeys === 0 ) { + if ( typeof renderer.debug.onShaderError === 'function' ) { - console.error( 'THREE.KeyframeTrack: Track is empty.', this ); - valid = false; + renderer.debug.onShaderError( gl, program, glVertexShader, glFragmentShader ); - } + } else { - let prevTime = null; + // default error reporting - for ( let i = 0; i !== nKeys; i ++ ) { + const vertexErrors = getShaderErrors( gl, glVertexShader, 'vertex' ); + const fragmentErrors = getShaderErrors( gl, glFragmentShader, 'fragment' ); - const currTime = times[ i ]; + console.error( + 'THREE.WebGLProgram: Shader Error ' + gl.getError() + ' - ' + + 'VALIDATE_STATUS ' + gl.getProgramParameter( program, gl.VALIDATE_STATUS ) + '\n\n' + + 'Material Name: ' + self.name + '\n' + + 'Material Type: ' + self.type + '\n\n' + + 'Program Info Log: ' + programLog + '\n' + + vertexErrors + '\n' + + fragmentErrors + ); - if ( typeof currTime === 'number' && isNaN( currTime ) ) { + } - console.error( 'THREE.KeyframeTrack: Time is not a valid number.', this, i, currTime ); - valid = false; - break; + } else if ( programLog !== '' ) { - } + console.warn( 'THREE.WebGLProgram: Program Info Log:', programLog ); - if ( prevTime !== null && prevTime > currTime ) { + } else if ( vertexLog === '' || fragmentLog === '' ) { - console.error( 'THREE.KeyframeTrack: Out of order keys.', this, i, currTime, prevTime ); - valid = false; - break; + haveDiagnostics = false; } - prevTime = currTime; + if ( haveDiagnostics ) { - } + self.diagnostics = { - if ( values !== undefined ) { + runnable: runnable, - if ( isTypedArray( values ) ) { + programLog: programLog, - for ( let i = 0, n = values.length; i !== n; ++ i ) { + vertexShader: { - const value = values[ i ]; + log: vertexLog, + prefix: prefixVertex - if ( isNaN( value ) ) { + }, - console.error( 'THREE.KeyframeTrack: Value is not a valid number.', this, i, value ); - valid = false; - break; + fragmentShader: { + + log: fragmentLog, + prefix: prefixFragment } - } + }; } } - return valid; + // Clean up - } + // Crashes in iOS9 and iOS10. #18402 + // gl.detachShader( program, glVertexShader ); + // gl.detachShader( program, glFragmentShader ); - // removes equivalent sequential keys as common in morph target sequences - // (0,0,0,0,1,1,1,0,0,0,0,0,0,0) --> (0,0,1,1,0,0) - optimize() { + gl.deleteShader( glVertexShader ); + gl.deleteShader( glFragmentShader ); - // times or values may be shared with other tracks, so overwriting is unsafe - const times = arraySlice( this.times ), - values = arraySlice( this.values ), - stride = this.getValueSize(), + cachedUniforms = new WebGLUniforms( gl, program ); + cachedAttributes = fetchAttributeLocations( gl, program ); - smoothInterpolation = this.getInterpolation() === InterpolateSmooth, + } - lastIndex = times.length - 1; + // set up caching for uniform locations - let writeIndex = 1; + let cachedUniforms; - for ( let i = 1; i < lastIndex; ++ i ) { + this.getUniforms = function () { - let keep = false; + if ( cachedUniforms === undefined ) { - const time = times[ i ]; - const timeNext = times[ i + 1 ]; + // Populates cachedUniforms and cachedAttributes + onFirstUse( this ); - // remove adjacent keyframes scheduled at the same time + } - if ( time !== timeNext && ( i !== 1 || time !== times[ 0 ] ) ) { + return cachedUniforms; - if ( ! smoothInterpolation ) { + }; - // remove unnecessary keyframes same as their neighbors + // set up caching for attribute locations - const offset = i * stride, - offsetP = offset - stride, - offsetN = offset + stride; + let cachedAttributes; - for ( let j = 0; j !== stride; ++ j ) { + this.getAttributes = function () { - const value = values[ offset + j ]; + if ( cachedAttributes === undefined ) { - if ( value !== values[ offsetP + j ] || - value !== values[ offsetN + j ] ) { + // Populates cachedAttributes and cachedUniforms + onFirstUse( this ); - keep = true; - break; + } - } + return cachedAttributes; - } + }; - } else { + // indicate when the program is ready to be used. if the KHR_parallel_shader_compile extension isn't supported, + // flag the program as ready immediately. It may cause a stall when it's first used. - keep = true; + let programReady = ( parameters.rendererExtensionParallelShaderCompile === false ); - } + this.isReady = function () { - } + if ( programReady === false ) { - // in-place compaction + programReady = gl.getProgramParameter( program, COMPLETION_STATUS_KHR ); - if ( keep ) { + } - if ( i !== writeIndex ) { + return programReady; - times[ writeIndex ] = times[ i ]; + }; - const readOffset = i * stride, - writeOffset = writeIndex * stride; + // free resource - for ( let j = 0; j !== stride; ++ j ) { + this.destroy = function () { - values[ writeOffset + j ] = values[ readOffset + j ]; + bindingStates.releaseStatesOfProgram( this ); - } + gl.deleteProgram( program ); + this.program = undefined; - } + }; - ++ writeIndex; + // - } + this.type = parameters.shaderType; + this.name = parameters.shaderName; + this.id = programIdCount ++; + this.cacheKey = cacheKey; + this.usedTimes = 1; + this.program = program; + this.vertexShader = glVertexShader; + this.fragmentShader = glFragmentShader; - } + return this; - // flush last keyframe (compaction looks ahead) +} - if ( lastIndex > 0 ) { +let _id = 0; - times[ writeIndex ] = times[ lastIndex ]; +class WebGLShaderCache { - for ( let readOffset = lastIndex * stride, writeOffset = writeIndex * stride, j = 0; j !== stride; ++ j ) { + constructor() { - values[ writeOffset + j ] = values[ readOffset + j ]; + this.shaderCache = new Map(); + this.materialCache = new Map(); - } + } - ++ writeIndex; + update( material ) { - } + const vertexShader = material.vertexShader; + const fragmentShader = material.fragmentShader; - if ( writeIndex !== times.length ) { + const vertexShaderStage = this._getShaderStage( vertexShader ); + const fragmentShaderStage = this._getShaderStage( fragmentShader ); - this.times = arraySlice( times, 0, writeIndex ); - this.values = arraySlice( values, 0, writeIndex * stride ); + const materialShaders = this._getShaderCacheForMaterial( material ); - } else { + if ( materialShaders.has( vertexShaderStage ) === false ) { - this.times = times; - this.values = values; + materialShaders.add( vertexShaderStage ); + vertexShaderStage.usedTimes ++; } - return this; - - } - - clone() { - - const times = arraySlice( this.times, 0 ); - const values = arraySlice( this.values, 0 ); + if ( materialShaders.has( fragmentShaderStage ) === false ) { - const TypedKeyframeTrack = this.constructor; - const track = new TypedKeyframeTrack( this.name, times, values ); + materialShaders.add( fragmentShaderStage ); + fragmentShaderStage.usedTimes ++; - // Interpolant argument to constructor is not saved, so copy the factory method directly. - track.createInterpolant = this.createInterpolant; + } - return track; + return this; } -} - -KeyframeTrack.prototype.TimeBufferType = Float32Array; -KeyframeTrack.prototype.ValueBufferType = Float32Array; -KeyframeTrack.prototype.DefaultInterpolation = InterpolateLinear; - -/** - * A Track of Boolean keyframe values. - */ -class BooleanKeyframeTrack extends KeyframeTrack {} - -BooleanKeyframeTrack.prototype.ValueTypeName = 'bool'; -BooleanKeyframeTrack.prototype.ValueBufferType = Array; -BooleanKeyframeTrack.prototype.DefaultInterpolation = InterpolateDiscrete; -BooleanKeyframeTrack.prototype.InterpolantFactoryMethodLinear = undefined; -BooleanKeyframeTrack.prototype.InterpolantFactoryMethodSmooth = undefined; - -/** - * A Track of keyframe values that represent color. - */ -class ColorKeyframeTrack extends KeyframeTrack {} + remove( material ) { -ColorKeyframeTrack.prototype.ValueTypeName = 'color'; + const materialShaders = this.materialCache.get( material ); -/** - * A Track of numeric keyframe values. - */ -class NumberKeyframeTrack extends KeyframeTrack {} + for ( const shaderStage of materialShaders ) { -NumberKeyframeTrack.prototype.ValueTypeName = 'number'; + shaderStage.usedTimes --; -/** - * Spherical linear unit quaternion interpolant. - */ + if ( shaderStage.usedTimes === 0 ) this.shaderCache.delete( shaderStage.code ); -class QuaternionLinearInterpolant extends Interpolant { + } - constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { + this.materialCache.delete( material ); - super( parameterPositions, sampleValues, sampleSize, resultBuffer ); + return this; } - interpolate_( i1, t0, t, t1 ) { - - const result = this.resultBuffer, - values = this.sampleValues, - stride = this.valueSize, - - alpha = ( t - t0 ) / ( t1 - t0 ); - - let offset = i1 * stride; + getVertexShaderID( material ) { - for ( let end = offset + stride; offset !== end; offset += 4 ) { + return this._getShaderStage( material.vertexShader ).id; - Quaternion.slerpFlat( result, 0, values, offset - stride, values, offset, alpha ); + } - } + getFragmentShaderID( material ) { - return result; + return this._getShaderStage( material.fragmentShader ).id; } -} - -/** - * A Track of quaternion keyframe values. - */ -class QuaternionKeyframeTrack extends KeyframeTrack { - - InterpolantFactoryMethodLinear( result ) { + dispose() { - return new QuaternionLinearInterpolant( this.times, this.values, this.getValueSize(), result ); + this.shaderCache.clear(); + this.materialCache.clear(); } -} - -QuaternionKeyframeTrack.prototype.ValueTypeName = 'quaternion'; -// ValueBufferType is inherited -QuaternionKeyframeTrack.prototype.DefaultInterpolation = InterpolateLinear; -QuaternionKeyframeTrack.prototype.InterpolantFactoryMethodSmooth = undefined; + _getShaderCacheForMaterial( material ) { -/** - * A Track that interpolates Strings - */ -class StringKeyframeTrack extends KeyframeTrack {} + const cache = this.materialCache; + let set = cache.get( material ); -StringKeyframeTrack.prototype.ValueTypeName = 'string'; -StringKeyframeTrack.prototype.ValueBufferType = Array; -StringKeyframeTrack.prototype.DefaultInterpolation = InterpolateDiscrete; -StringKeyframeTrack.prototype.InterpolantFactoryMethodLinear = undefined; -StringKeyframeTrack.prototype.InterpolantFactoryMethodSmooth = undefined; + if ( set === undefined ) { -/** - * A Track of vectored keyframe values. - */ -class VectorKeyframeTrack extends KeyframeTrack {} + set = new Set(); + cache.set( material, set ); -VectorKeyframeTrack.prototype.ValueTypeName = 'vector'; + } -class AnimationClip { + return set; - constructor( name, duration = - 1, tracks, blendMode = NormalAnimationBlendMode ) { + } - this.name = name; - this.tracks = tracks; - this.duration = duration; - this.blendMode = blendMode; + _getShaderStage( code ) { - this.uuid = generateUUID(); + const cache = this.shaderCache; + let stage = cache.get( code ); - // this means it should figure out its duration by scanning the tracks - if ( this.duration < 0 ) { + if ( stage === undefined ) { - this.resetDuration(); + stage = new WebGLShaderStage( code ); + cache.set( code, stage ); } - } - - - static parse( json ) { + return stage; - const tracks = [], - jsonTracks = json.tracks, - frameTime = 1.0 / ( json.fps || 1.0 ); + } - for ( let i = 0, n = jsonTracks.length; i !== n; ++ i ) { +} - tracks.push( parseKeyframeTrack( jsonTracks[ i ] ).scale( frameTime ) ); +class WebGLShaderStage { - } + constructor( code ) { - const clip = new this( json.name, json.duration, tracks, json.blendMode ); - clip.uuid = json.uuid; + this.id = _id ++; - return clip; + this.code = code; + this.usedTimes = 0; } - static toJSON( clip ) { +} + +function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities, bindingStates, clipping ) { - const tracks = [], - clipTracks = clip.tracks; + const _programLayers = new Layers(); + const _customShaders = new WebGLShaderCache(); + const _activeChannels = new Set(); + const programs = []; - const json = { + const logarithmicDepthBuffer = capabilities.logarithmicDepthBuffer; + const SUPPORTS_VERTEX_TEXTURES = capabilities.vertexTextures; - 'name': clip.name, - 'duration': clip.duration, - 'tracks': tracks, - 'uuid': clip.uuid, - 'blendMode': clip.blendMode + let precision = capabilities.precision; - }; + const shaderIDs = { + MeshDepthMaterial: 'depth', + MeshDistanceMaterial: 'distanceRGBA', + MeshNormalMaterial: 'normal', + MeshBasicMaterial: 'basic', + MeshLambertMaterial: 'lambert', + MeshPhongMaterial: 'phong', + MeshToonMaterial: 'toon', + MeshStandardMaterial: 'physical', + MeshPhysicalMaterial: 'physical', + MeshMatcapMaterial: 'matcap', + LineBasicMaterial: 'basic', + LineDashedMaterial: 'dashed', + PointsMaterial: 'points', + ShadowMaterial: 'shadow', + SpriteMaterial: 'sprite' + }; - for ( let i = 0, n = clipTracks.length; i !== n; ++ i ) { + function getChannel( value ) { - tracks.push( KeyframeTrack.toJSON( clipTracks[ i ] ) ); + _activeChannels.add( value ); - } + if ( value === 0 ) return 'uv'; - return json; + return `uv${ value }`; } - static CreateFromMorphTargetSequence( name, morphTargetSequence, fps, noLoop ) { + function getParameters( material, lights, shadows, scene, object ) { - const numMorphTargets = morphTargetSequence.length; - const tracks = []; + const fog = scene.fog; + const geometry = object.geometry; + const environment = material.isMeshStandardMaterial ? scene.environment : null; - for ( let i = 0; i < numMorphTargets; i ++ ) { + const envMap = ( material.isMeshStandardMaterial ? cubeuvmaps : cubemaps ).get( material.envMap || environment ); + const envMapCubeUVHeight = ( !! envMap ) && ( envMap.mapping === CubeUVReflectionMapping ) ? envMap.image.height : null; - let times = []; - let values = []; + const shaderID = shaderIDs[ material.type ]; - times.push( - ( i + numMorphTargets - 1 ) % numMorphTargets, - i, - ( i + 1 ) % numMorphTargets ); + // heuristics to create shader parameters according to lights in the scene + // (not to blow over maxLights budget) - values.push( 0, 1, 0 ); + if ( material.precision !== null ) { - const order = getKeyframeOrder( times ); - times = sortedArray( times, 1, order ); - values = sortedArray( values, 1, order ); + precision = capabilities.getMaxPrecision( material.precision ); - // if there is a key at the first frame, duplicate it as the - // last frame as well for perfect loop. - if ( ! noLoop && times[ 0 ] === 0 ) { + if ( precision !== material.precision ) { - times.push( numMorphTargets ); - values.push( values[ 0 ] ); + console.warn( 'THREE.WebGLProgram.getParameters:', material.precision, 'not supported, using', precision, 'instead.' ); } - tracks.push( - new NumberKeyframeTrack( - '.morphTargetInfluences[' + morphTargetSequence[ i ].name + ']', - times, values - ).scale( 1.0 / fps ) ); - } - return new this( name, - 1, tracks ); + // - } + const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color; + const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0; - static findByName( objectOrClipArray, name ) { + let morphTextureStride = 0; - let clipArray = objectOrClipArray; + if ( geometry.morphAttributes.position !== undefined ) morphTextureStride = 1; + if ( geometry.morphAttributes.normal !== undefined ) morphTextureStride = 2; + if ( geometry.morphAttributes.color !== undefined ) morphTextureStride = 3; - if ( ! Array.isArray( objectOrClipArray ) ) { + // - const o = objectOrClipArray; - clipArray = o.geometry && o.geometry.animations || o.animations; + let vertexShader, fragmentShader; + let customVertexShaderID, customFragmentShaderID; - } + if ( shaderID ) { - for ( let i = 0; i < clipArray.length; i ++ ) { + const shader = ShaderLib[ shaderID ]; - if ( clipArray[ i ].name === name ) { + vertexShader = shader.vertexShader; + fragmentShader = shader.fragmentShader; - return clipArray[ i ]; + } else { - } + vertexShader = material.vertexShader; + fragmentShader = material.fragmentShader; - } + _customShaders.update( material ); - return null; + customVertexShaderID = _customShaders.getVertexShaderID( material ); + customFragmentShaderID = _customShaders.getFragmentShaderID( material ); - } + } - static CreateClipsFromMorphTargetSequences( morphTargets, fps, noLoop ) { + const currentRenderTarget = renderer.getRenderTarget(); + const reverseDepthBuffer = renderer.state.buffers.depth.getReversed(); - const animationToMorphTargets = {}; + const IS_INSTANCEDMESH = object.isInstancedMesh === true; + const IS_BATCHEDMESH = object.isBatchedMesh === true; - // tested with https://regex101.com/ on trick sequences - // such flamingo_flyA_003, flamingo_run1_003, crdeath0059 - const pattern = /^([\w-]*?)([\d]+)$/; + const HAS_MAP = !! material.map; + const HAS_MATCAP = !! material.matcap; + const HAS_ENVMAP = !! envMap; + const HAS_AOMAP = !! material.aoMap; + const HAS_LIGHTMAP = !! material.lightMap; + const HAS_BUMPMAP = !! material.bumpMap; + const HAS_NORMALMAP = !! material.normalMap; + const HAS_DISPLACEMENTMAP = !! material.displacementMap; + const HAS_EMISSIVEMAP = !! material.emissiveMap; - // sort morph target names into animation groups based - // patterns like Walk_001, Walk_002, Run_001, Run_002 - for ( let i = 0, il = morphTargets.length; i < il; i ++ ) { + const HAS_METALNESSMAP = !! material.metalnessMap; + const HAS_ROUGHNESSMAP = !! material.roughnessMap; - const morphTarget = morphTargets[ i ]; - const parts = morphTarget.name.match( pattern ); + const HAS_ANISOTROPY = material.anisotropy > 0; + const HAS_CLEARCOAT = material.clearcoat > 0; + const HAS_DISPERSION = material.dispersion > 0; + const HAS_IRIDESCENCE = material.iridescence > 0; + const HAS_SHEEN = material.sheen > 0; + const HAS_TRANSMISSION = material.transmission > 0; - if ( parts && parts.length > 1 ) { + const HAS_ANISOTROPYMAP = HAS_ANISOTROPY && !! material.anisotropyMap; - const name = parts[ 1 ]; + const HAS_CLEARCOATMAP = HAS_CLEARCOAT && !! material.clearcoatMap; + const HAS_CLEARCOAT_NORMALMAP = HAS_CLEARCOAT && !! material.clearcoatNormalMap; + const HAS_CLEARCOAT_ROUGHNESSMAP = HAS_CLEARCOAT && !! material.clearcoatRoughnessMap; - let animationMorphTargets = animationToMorphTargets[ name ]; + const HAS_IRIDESCENCEMAP = HAS_IRIDESCENCE && !! material.iridescenceMap; + const HAS_IRIDESCENCE_THICKNESSMAP = HAS_IRIDESCENCE && !! material.iridescenceThicknessMap; - if ( ! animationMorphTargets ) { + const HAS_SHEEN_COLORMAP = HAS_SHEEN && !! material.sheenColorMap; + const HAS_SHEEN_ROUGHNESSMAP = HAS_SHEEN && !! material.sheenRoughnessMap; - animationToMorphTargets[ name ] = animationMorphTargets = []; + const HAS_SPECULARMAP = !! material.specularMap; + const HAS_SPECULAR_COLORMAP = !! material.specularColorMap; + const HAS_SPECULAR_INTENSITYMAP = !! material.specularIntensityMap; - } + const HAS_TRANSMISSIONMAP = HAS_TRANSMISSION && !! material.transmissionMap; + const HAS_THICKNESSMAP = HAS_TRANSMISSION && !! material.thicknessMap; - animationMorphTargets.push( morphTarget ); + const HAS_GRADIENTMAP = !! material.gradientMap; - } + const HAS_ALPHAMAP = !! material.alphaMap; - } + const HAS_ALPHATEST = material.alphaTest > 0; - const clips = []; + const HAS_ALPHAHASH = !! material.alphaHash; - for ( const name in animationToMorphTargets ) { + const HAS_EXTENSIONS = !! material.extensions; - clips.push( this.CreateFromMorphTargetSequence( name, animationToMorphTargets[ name ], fps, noLoop ) ); + let toneMapping = NoToneMapping; - } + if ( material.toneMapped ) { - return clips; + if ( currentRenderTarget === null || currentRenderTarget.isXRRenderTarget === true ) { - } + toneMapping = renderer.toneMapping; - // parse the animation.hierarchy format - static parseAnimation( animation, bones ) { + } - if ( ! animation ) { + } - console.error( 'THREE.AnimationClip: No animation in JSONLoader data.' ); - return null; + const parameters = { - } + shaderID: shaderID, + shaderType: material.type, + shaderName: material.name, - const addNonemptyTrack = function ( trackType, trackName, animationKeys, propertyName, destTracks ) { + vertexShader: vertexShader, + fragmentShader: fragmentShader, + defines: material.defines, - // only return track if there are actually keys. - if ( animationKeys.length !== 0 ) { + customVertexShaderID: customVertexShaderID, + customFragmentShaderID: customFragmentShaderID, - const times = []; - const values = []; + isRawShaderMaterial: material.isRawShaderMaterial === true, + glslVersion: material.glslVersion, - flattenJSON( animationKeys, times, values, propertyName ); + precision: precision, - // empty keys are filtered out, so check again - if ( times.length !== 0 ) { + batching: IS_BATCHEDMESH, + batchingColor: IS_BATCHEDMESH && object._colorsTexture !== null, + instancing: IS_INSTANCEDMESH, + instancingColor: IS_INSTANCEDMESH && object.instanceColor !== null, + instancingMorph: IS_INSTANCEDMESH && object.morphTexture !== null, - destTracks.push( new trackType( trackName, times, values ) ); + supportsVertexTextures: SUPPORTS_VERTEX_TEXTURES, + outputColorSpace: ( currentRenderTarget === null ) ? renderer.outputColorSpace : ( currentRenderTarget.isXRRenderTarget === true ? currentRenderTarget.texture.colorSpace : LinearSRGBColorSpace ), + alphaToCoverage: !! material.alphaToCoverage, - } + map: HAS_MAP, + matcap: HAS_MATCAP, + envMap: HAS_ENVMAP, + envMapMode: HAS_ENVMAP && envMap.mapping, + envMapCubeUVHeight: envMapCubeUVHeight, + aoMap: HAS_AOMAP, + lightMap: HAS_LIGHTMAP, + bumpMap: HAS_BUMPMAP, + normalMap: HAS_NORMALMAP, + displacementMap: SUPPORTS_VERTEX_TEXTURES && HAS_DISPLACEMENTMAP, + emissiveMap: HAS_EMISSIVEMAP, - } + normalMapObjectSpace: HAS_NORMALMAP && material.normalMapType === ObjectSpaceNormalMap, + normalMapTangentSpace: HAS_NORMALMAP && material.normalMapType === TangentSpaceNormalMap, - }; + metalnessMap: HAS_METALNESSMAP, + roughnessMap: HAS_ROUGHNESSMAP, + + anisotropy: HAS_ANISOTROPY, + anisotropyMap: HAS_ANISOTROPYMAP, - const tracks = []; + clearcoat: HAS_CLEARCOAT, + clearcoatMap: HAS_CLEARCOATMAP, + clearcoatNormalMap: HAS_CLEARCOAT_NORMALMAP, + clearcoatRoughnessMap: HAS_CLEARCOAT_ROUGHNESSMAP, - const clipName = animation.name || 'default'; - const fps = animation.fps || 30; - const blendMode = animation.blendMode; + dispersion: HAS_DISPERSION, - // automatic length determination in AnimationClip. - let duration = animation.length || - 1; + iridescence: HAS_IRIDESCENCE, + iridescenceMap: HAS_IRIDESCENCEMAP, + iridescenceThicknessMap: HAS_IRIDESCENCE_THICKNESSMAP, - const hierarchyTracks = animation.hierarchy || []; + sheen: HAS_SHEEN, + sheenColorMap: HAS_SHEEN_COLORMAP, + sheenRoughnessMap: HAS_SHEEN_ROUGHNESSMAP, - for ( let h = 0; h < hierarchyTracks.length; h ++ ) { + specularMap: HAS_SPECULARMAP, + specularColorMap: HAS_SPECULAR_COLORMAP, + specularIntensityMap: HAS_SPECULAR_INTENSITYMAP, - const animationKeys = hierarchyTracks[ h ].keys; + transmission: HAS_TRANSMISSION, + transmissionMap: HAS_TRANSMISSIONMAP, + thicknessMap: HAS_THICKNESSMAP, - // skip empty tracks - if ( ! animationKeys || animationKeys.length === 0 ) continue; + gradientMap: HAS_GRADIENTMAP, - // process morph targets - if ( animationKeys[ 0 ].morphTargets ) { + opaque: material.transparent === false && material.blending === NormalBlending && material.alphaToCoverage === false, - // figure out all morph targets used in this track - const morphTargetNames = {}; + alphaMap: HAS_ALPHAMAP, + alphaTest: HAS_ALPHATEST, + alphaHash: HAS_ALPHAHASH, - let k; + combine: material.combine, - for ( k = 0; k < animationKeys.length; k ++ ) { + // - if ( animationKeys[ k ].morphTargets ) { + mapUv: HAS_MAP && getChannel( material.map.channel ), + aoMapUv: HAS_AOMAP && getChannel( material.aoMap.channel ), + lightMapUv: HAS_LIGHTMAP && getChannel( material.lightMap.channel ), + bumpMapUv: HAS_BUMPMAP && getChannel( material.bumpMap.channel ), + normalMapUv: HAS_NORMALMAP && getChannel( material.normalMap.channel ), + displacementMapUv: HAS_DISPLACEMENTMAP && getChannel( material.displacementMap.channel ), + emissiveMapUv: HAS_EMISSIVEMAP && getChannel( material.emissiveMap.channel ), - for ( let m = 0; m < animationKeys[ k ].morphTargets.length; m ++ ) { + metalnessMapUv: HAS_METALNESSMAP && getChannel( material.metalnessMap.channel ), + roughnessMapUv: HAS_ROUGHNESSMAP && getChannel( material.roughnessMap.channel ), - morphTargetNames[ animationKeys[ k ].morphTargets[ m ] ] = - 1; + anisotropyMapUv: HAS_ANISOTROPYMAP && getChannel( material.anisotropyMap.channel ), - } + clearcoatMapUv: HAS_CLEARCOATMAP && getChannel( material.clearcoatMap.channel ), + clearcoatNormalMapUv: HAS_CLEARCOAT_NORMALMAP && getChannel( material.clearcoatNormalMap.channel ), + clearcoatRoughnessMapUv: HAS_CLEARCOAT_ROUGHNESSMAP && getChannel( material.clearcoatRoughnessMap.channel ), - } + iridescenceMapUv: HAS_IRIDESCENCEMAP && getChannel( material.iridescenceMap.channel ), + iridescenceThicknessMapUv: HAS_IRIDESCENCE_THICKNESSMAP && getChannel( material.iridescenceThicknessMap.channel ), - } + sheenColorMapUv: HAS_SHEEN_COLORMAP && getChannel( material.sheenColorMap.channel ), + sheenRoughnessMapUv: HAS_SHEEN_ROUGHNESSMAP && getChannel( material.sheenRoughnessMap.channel ), - // create a track for each morph target with all zero - // morphTargetInfluences except for the keys in which - // the morphTarget is named. - for ( const morphTargetName in morphTargetNames ) { + specularMapUv: HAS_SPECULARMAP && getChannel( material.specularMap.channel ), + specularColorMapUv: HAS_SPECULAR_COLORMAP && getChannel( material.specularColorMap.channel ), + specularIntensityMapUv: HAS_SPECULAR_INTENSITYMAP && getChannel( material.specularIntensityMap.channel ), - const times = []; - const values = []; + transmissionMapUv: HAS_TRANSMISSIONMAP && getChannel( material.transmissionMap.channel ), + thicknessMapUv: HAS_THICKNESSMAP && getChannel( material.thicknessMap.channel ), - for ( let m = 0; m !== animationKeys[ k ].morphTargets.length; ++ m ) { + alphaMapUv: HAS_ALPHAMAP && getChannel( material.alphaMap.channel ), - const animationKey = animationKeys[ k ]; + // - times.push( animationKey.time ); - values.push( ( animationKey.morphTarget === morphTargetName ) ? 1 : 0 ); + vertexTangents: !! geometry.attributes.tangent && ( HAS_NORMALMAP || HAS_ANISOTROPY ), + vertexColors: material.vertexColors, + vertexAlphas: material.vertexColors === true && !! geometry.attributes.color && geometry.attributes.color.itemSize === 4, - } + pointsUvs: object.isPoints === true && !! geometry.attributes.uv && ( HAS_MAP || HAS_ALPHAMAP ), - tracks.push( new NumberKeyframeTrack( '.morphTargetInfluence[' + morphTargetName + ']', times, values ) ); + fog: !! fog, + useFog: material.fog === true, + fogExp2: ( !! fog && fog.isFogExp2 ), - } + flatShading: ( material.flatShading === true && material.wireframe === false ), - duration = morphTargetNames.length * fps; + sizeAttenuation: material.sizeAttenuation === true, + logarithmicDepthBuffer: logarithmicDepthBuffer, + reverseDepthBuffer: reverseDepthBuffer, - } else { + skinning: object.isSkinnedMesh === true, - // ...assume skeletal animation + morphTargets: geometry.morphAttributes.position !== undefined, + morphNormals: geometry.morphAttributes.normal !== undefined, + morphColors: geometry.morphAttributes.color !== undefined, + morphTargetsCount: morphTargetsCount, + morphTextureStride: morphTextureStride, - const boneName = '.bones[' + bones[ h ].name + ']'; + numDirLights: lights.directional.length, + numPointLights: lights.point.length, + numSpotLights: lights.spot.length, + numSpotLightMaps: lights.spotLightMap.length, + numRectAreaLights: lights.rectArea.length, + numHemiLights: lights.hemi.length, - addNonemptyTrack( - VectorKeyframeTrack, boneName + '.position', - animationKeys, 'pos', tracks ); + numDirLightShadows: lights.directionalShadowMap.length, + numPointLightShadows: lights.pointShadowMap.length, + numSpotLightShadows: lights.spotShadowMap.length, + numSpotLightShadowsWithMaps: lights.numSpotLightShadowsWithMaps, - addNonemptyTrack( - QuaternionKeyframeTrack, boneName + '.quaternion', - animationKeys, 'rot', tracks ); + numLightProbes: lights.numLightProbes, - addNonemptyTrack( - VectorKeyframeTrack, boneName + '.scale', - animationKeys, 'scl', tracks ); + numClippingPlanes: clipping.numPlanes, + numClipIntersection: clipping.numIntersection, - } + dithering: material.dithering, - } + shadowMapEnabled: renderer.shadowMap.enabled && shadows.length > 0, + shadowMapType: renderer.shadowMap.type, - if ( tracks.length === 0 ) { + toneMapping: toneMapping, - return null; + decodeVideoTexture: HAS_MAP && ( material.map.isVideoTexture === true ) && ( ColorManagement.getTransfer( material.map.colorSpace ) === SRGBTransfer ), + decodeVideoTextureEmissive: HAS_EMISSIVEMAP && ( material.emissiveMap.isVideoTexture === true ) && ( ColorManagement.getTransfer( material.emissiveMap.colorSpace ) === SRGBTransfer ), - } + premultipliedAlpha: material.premultipliedAlpha, - const clip = new this( clipName, duration, tracks, blendMode ); + doubleSided: material.side === DoubleSide, + flipSided: material.side === BackSide, - return clip; + useDepthPacking: material.depthPacking >= 0, + depthPacking: material.depthPacking || 0, - } + index0AttributeName: material.index0AttributeName, - resetDuration() { + extensionClipCullDistance: HAS_EXTENSIONS && material.extensions.clipCullDistance === true && extensions.has( 'WEBGL_clip_cull_distance' ), + extensionMultiDraw: ( HAS_EXTENSIONS && material.extensions.multiDraw === true || IS_BATCHEDMESH ) && extensions.has( 'WEBGL_multi_draw' ), - const tracks = this.tracks; - let duration = 0; + rendererExtensionParallelShaderCompile: extensions.has( 'KHR_parallel_shader_compile' ), - for ( let i = 0, n = tracks.length; i !== n; ++ i ) { + customProgramCacheKey: material.customProgramCacheKey() - const track = this.tracks[ i ]; + }; - duration = Math.max( duration, track.times[ track.times.length - 1 ] ); + // the usage of getChannel() determines the active texture channels for this shader - } + parameters.vertexUv1s = _activeChannels.has( 1 ); + parameters.vertexUv2s = _activeChannels.has( 2 ); + parameters.vertexUv3s = _activeChannels.has( 3 ); - this.duration = duration; + _activeChannels.clear(); - return this; + return parameters; } - trim() { - - for ( let i = 0; i < this.tracks.length; i ++ ) { - - this.tracks[ i ].trim( 0, this.duration ); + function getProgramCacheKey( parameters ) { - } + const array = []; - return this; + if ( parameters.shaderID ) { - } + array.push( parameters.shaderID ); - validate() { + } else { - let valid = true; + array.push( parameters.customVertexShaderID ); + array.push( parameters.customFragmentShaderID ); - for ( let i = 0; i < this.tracks.length; i ++ ) { + } - valid = valid && this.tracks[ i ].validate(); + if ( parameters.defines !== undefined ) { - } + for ( const name in parameters.defines ) { - return valid; + array.push( name ); + array.push( parameters.defines[ name ] ); - } + } - optimize() { + } - for ( let i = 0; i < this.tracks.length; i ++ ) { + if ( parameters.isRawShaderMaterial === false ) { - this.tracks[ i ].optimize(); + getProgramCacheKeyParameters( array, parameters ); + getProgramCacheKeyBooleans( array, parameters ); + array.push( renderer.outputColorSpace ); } - return this; + array.push( parameters.customProgramCacheKey ); + + return array.join(); } - clone() { + function getProgramCacheKeyParameters( array, parameters ) { - const tracks = []; + array.push( parameters.precision ); + array.push( parameters.outputColorSpace ); + array.push( parameters.envMapMode ); + array.push( parameters.envMapCubeUVHeight ); + array.push( parameters.mapUv ); + array.push( parameters.alphaMapUv ); + array.push( parameters.lightMapUv ); + array.push( parameters.aoMapUv ); + array.push( parameters.bumpMapUv ); + array.push( parameters.normalMapUv ); + array.push( parameters.displacementMapUv ); + array.push( parameters.emissiveMapUv ); + array.push( parameters.metalnessMapUv ); + array.push( parameters.roughnessMapUv ); + array.push( parameters.anisotropyMapUv ); + array.push( parameters.clearcoatMapUv ); + array.push( parameters.clearcoatNormalMapUv ); + array.push( parameters.clearcoatRoughnessMapUv ); + array.push( parameters.iridescenceMapUv ); + array.push( parameters.iridescenceThicknessMapUv ); + array.push( parameters.sheenColorMapUv ); + array.push( parameters.sheenRoughnessMapUv ); + array.push( parameters.specularMapUv ); + array.push( parameters.specularColorMapUv ); + array.push( parameters.specularIntensityMapUv ); + array.push( parameters.transmissionMapUv ); + array.push( parameters.thicknessMapUv ); + array.push( parameters.combine ); + array.push( parameters.fogExp2 ); + array.push( parameters.sizeAttenuation ); + array.push( parameters.morphTargetsCount ); + array.push( parameters.morphAttributeCount ); + array.push( parameters.numDirLights ); + array.push( parameters.numPointLights ); + array.push( parameters.numSpotLights ); + array.push( parameters.numSpotLightMaps ); + array.push( parameters.numHemiLights ); + array.push( parameters.numRectAreaLights ); + array.push( parameters.numDirLightShadows ); + array.push( parameters.numPointLightShadows ); + array.push( parameters.numSpotLightShadows ); + array.push( parameters.numSpotLightShadowsWithMaps ); + array.push( parameters.numLightProbes ); + array.push( parameters.shadowMapType ); + array.push( parameters.toneMapping ); + array.push( parameters.numClippingPlanes ); + array.push( parameters.numClipIntersection ); + array.push( parameters.depthPacking ); - for ( let i = 0; i < this.tracks.length; i ++ ) { + } - tracks.push( this.tracks[ i ].clone() ); + function getProgramCacheKeyBooleans( array, parameters ) { - } + _programLayers.disableAll(); - return new this.constructor( this.name, this.duration, tracks, this.blendMode ); + if ( parameters.supportsVertexTextures ) + _programLayers.enable( 0 ); + if ( parameters.instancing ) + _programLayers.enable( 1 ); + if ( parameters.instancingColor ) + _programLayers.enable( 2 ); + if ( parameters.instancingMorph ) + _programLayers.enable( 3 ); + if ( parameters.matcap ) + _programLayers.enable( 4 ); + if ( parameters.envMap ) + _programLayers.enable( 5 ); + if ( parameters.normalMapObjectSpace ) + _programLayers.enable( 6 ); + if ( parameters.normalMapTangentSpace ) + _programLayers.enable( 7 ); + if ( parameters.clearcoat ) + _programLayers.enable( 8 ); + if ( parameters.iridescence ) + _programLayers.enable( 9 ); + if ( parameters.alphaTest ) + _programLayers.enable( 10 ); + if ( parameters.vertexColors ) + _programLayers.enable( 11 ); + if ( parameters.vertexAlphas ) + _programLayers.enable( 12 ); + if ( parameters.vertexUv1s ) + _programLayers.enable( 13 ); + if ( parameters.vertexUv2s ) + _programLayers.enable( 14 ); + if ( parameters.vertexUv3s ) + _programLayers.enable( 15 ); + if ( parameters.vertexTangents ) + _programLayers.enable( 16 ); + if ( parameters.anisotropy ) + _programLayers.enable( 17 ); + if ( parameters.alphaHash ) + _programLayers.enable( 18 ); + if ( parameters.batching ) + _programLayers.enable( 19 ); + if ( parameters.dispersion ) + _programLayers.enable( 20 ); + if ( parameters.batchingColor ) + _programLayers.enable( 21 ); + if ( parameters.gradientMap ) + _programLayers.enable( 22 ); - } + array.push( _programLayers.mask ); + _programLayers.disableAll(); - toJSON() { + if ( parameters.fog ) + _programLayers.enable( 0 ); + if ( parameters.useFog ) + _programLayers.enable( 1 ); + if ( parameters.flatShading ) + _programLayers.enable( 2 ); + if ( parameters.logarithmicDepthBuffer ) + _programLayers.enable( 3 ); + if ( parameters.reverseDepthBuffer ) + _programLayers.enable( 4 ); + if ( parameters.skinning ) + _programLayers.enable( 5 ); + if ( parameters.morphTargets ) + _programLayers.enable( 6 ); + if ( parameters.morphNormals ) + _programLayers.enable( 7 ); + if ( parameters.morphColors ) + _programLayers.enable( 8 ); + if ( parameters.premultipliedAlpha ) + _programLayers.enable( 9 ); + if ( parameters.shadowMapEnabled ) + _programLayers.enable( 10 ); + if ( parameters.doubleSided ) + _programLayers.enable( 11 ); + if ( parameters.flipSided ) + _programLayers.enable( 12 ); + if ( parameters.useDepthPacking ) + _programLayers.enable( 13 ); + if ( parameters.dithering ) + _programLayers.enable( 14 ); + if ( parameters.transmission ) + _programLayers.enable( 15 ); + if ( parameters.sheen ) + _programLayers.enable( 16 ); + if ( parameters.opaque ) + _programLayers.enable( 17 ); + if ( parameters.pointsUvs ) + _programLayers.enable( 18 ); + if ( parameters.decodeVideoTexture ) + _programLayers.enable( 19 ); + if ( parameters.decodeVideoTextureEmissive ) + _programLayers.enable( 20 ); + if ( parameters.alphaToCoverage ) + _programLayers.enable( 21 ); - return this.constructor.toJSON( this ); + array.push( _programLayers.mask ); } -} + function getUniforms( material ) { + + const shaderID = shaderIDs[ material.type ]; + let uniforms; -function getTrackTypeForValueTypeName( typeName ) { + if ( shaderID ) { - switch ( typeName.toLowerCase() ) { + const shader = ShaderLib[ shaderID ]; + uniforms = UniformsUtils.clone( shader.uniforms ); - case 'scalar': - case 'double': - case 'float': - case 'number': - case 'integer': + } else { - return NumberKeyframeTrack; + uniforms = material.uniforms; - case 'vector': - case 'vector2': - case 'vector3': - case 'vector4': + } - return VectorKeyframeTrack; + return uniforms; - case 'color': + } - return ColorKeyframeTrack; + function acquireProgram( parameters, cacheKey ) { - case 'quaternion': + let program; - return QuaternionKeyframeTrack; + // Check if code has been already compiled + for ( let p = 0, pl = programs.length; p < pl; p ++ ) { - case 'bool': - case 'boolean': + const preexistingProgram = programs[ p ]; - return BooleanKeyframeTrack; + if ( preexistingProgram.cacheKey === cacheKey ) { - case 'string': + program = preexistingProgram; + ++ program.usedTimes; - return StringKeyframeTrack; + break; - } + } - throw new Error( 'THREE.KeyframeTrack: Unsupported typeName: ' + typeName ); + } -} + if ( program === undefined ) { -function parseKeyframeTrack( json ) { + program = new WebGLProgram( renderer, cacheKey, parameters, bindingStates ); + programs.push( program ); - if ( json.type === undefined ) { + } - throw new Error( 'THREE.KeyframeTrack: track type undefined, can not parse' ); + return program; } - const trackType = getTrackTypeForValueTypeName( json.type ); + function releaseProgram( program ) { - if ( json.times === undefined ) { + if ( -- program.usedTimes === 0 ) { - const times = [], values = []; + // Remove from unordered set + const i = programs.indexOf( program ); + programs[ i ] = programs[ programs.length - 1 ]; + programs.pop(); - flattenJSON( json.keys, times, values, 'value' ); + // Free WebGL resources + program.destroy(); - json.times = times; - json.values = values; + } } - // derived classes can define a static parse method - if ( trackType.parse !== undefined ) { - - return trackType.parse( json ); - - } else { + function releaseShaderCache( material ) { - // by default, we assume a constructor compatible with the base - return new trackType( json.name, json.times, json.values, json.interpolation ); + _customShaders.remove( material ); } -} + function dispose() { -const Cache = { + _customShaders.dispose(); - enabled: false, + } - files: {}, + return { + getParameters: getParameters, + getProgramCacheKey: getProgramCacheKey, + getUniforms: getUniforms, + acquireProgram: acquireProgram, + releaseProgram: releaseProgram, + releaseShaderCache: releaseShaderCache, + // Exposed for resource monitoring & error feedback via renderer.info: + programs: programs, + dispose: dispose + }; - add: function ( key, file ) { +} - if ( this.enabled === false ) return; +function WebGLProperties() { - // console.log( 'THREE.Cache', 'Adding key:', key ); + let properties = new WeakMap(); - this.files[ key ] = file; + function has( object ) { - }, + return properties.has( object ); - get: function ( key ) { + } - if ( this.enabled === false ) return; + function get( object ) { - // console.log( 'THREE.Cache', 'Checking key:', key ); + let map = properties.get( object ); - return this.files[ key ]; + if ( map === undefined ) { - }, + map = {}; + properties.set( object, map ); - remove: function ( key ) { + } - delete this.files[ key ]; + return map; - }, + } - clear: function () { + function remove( object ) { - this.files = {}; + properties.delete( object ); } -}; - -class LoadingManager { + function update( object, key, value ) { - constructor( onLoad, onProgress, onError ) { + properties.get( object )[ key ] = value; - const scope = this; + } - let isLoading = false; - let itemsLoaded = 0; - let itemsTotal = 0; - let urlModifier = undefined; - const handlers = []; + function dispose() { - // Refer to #5689 for the reason why we don't set .onStart - // in the constructor + properties = new WeakMap(); - this.onStart = undefined; - this.onLoad = onLoad; - this.onProgress = onProgress; - this.onError = onError; + } - this.itemStart = function ( url ) { + return { + has: has, + get: get, + remove: remove, + update: update, + dispose: dispose + }; - itemsTotal ++; +} - if ( isLoading === false ) { +function painterSortStable( a, b ) { - if ( scope.onStart !== undefined ) { + if ( a.groupOrder !== b.groupOrder ) { - scope.onStart( url, itemsLoaded, itemsTotal ); + return a.groupOrder - b.groupOrder; - } + } else if ( a.renderOrder !== b.renderOrder ) { - } + return a.renderOrder - b.renderOrder; - isLoading = true; + } else if ( a.material.id !== b.material.id ) { - }; + return a.material.id - b.material.id; - this.itemEnd = function ( url ) { + } else if ( a.z !== b.z ) { - itemsLoaded ++; + return a.z - b.z; - if ( scope.onProgress !== undefined ) { + } else { - scope.onProgress( url, itemsLoaded, itemsTotal ); + return a.id - b.id; - } + } - if ( itemsLoaded === itemsTotal ) { +} - isLoading = false; +function reversePainterSortStable( a, b ) { - if ( scope.onLoad !== undefined ) { + if ( a.groupOrder !== b.groupOrder ) { - scope.onLoad(); + return a.groupOrder - b.groupOrder; - } + } else if ( a.renderOrder !== b.renderOrder ) { - } + return a.renderOrder - b.renderOrder; - }; + } else if ( a.z !== b.z ) { - this.itemError = function ( url ) { + return b.z - a.z; - if ( scope.onError !== undefined ) { + } else { - scope.onError( url ); + return a.id - b.id; - } + } - }; +} - this.resolveURL = function ( url ) { - if ( urlModifier ) { +function WebGLRenderList() { - return urlModifier( url ); + const renderItems = []; + let renderItemsIndex = 0; - } + const opaque = []; + const transmissive = []; + const transparent = []; - return url; + function init() { - }; + renderItemsIndex = 0; - this.setURLModifier = function ( transform ) { + opaque.length = 0; + transmissive.length = 0; + transparent.length = 0; - urlModifier = transform; + } - return this; + function getNextRenderItem( object, geometry, material, groupOrder, z, group ) { - }; + let renderItem = renderItems[ renderItemsIndex ]; - this.addHandler = function ( regex, loader ) { + if ( renderItem === undefined ) { - handlers.push( regex, loader ); + renderItem = { + id: object.id, + object: object, + geometry: geometry, + material: material, + groupOrder: groupOrder, + renderOrder: object.renderOrder, + z: z, + group: group + }; - return this; + renderItems[ renderItemsIndex ] = renderItem; - }; + } else { - this.removeHandler = function ( regex ) { + renderItem.id = object.id; + renderItem.object = object; + renderItem.geometry = geometry; + renderItem.material = material; + renderItem.groupOrder = groupOrder; + renderItem.renderOrder = object.renderOrder; + renderItem.z = z; + renderItem.group = group; - const index = handlers.indexOf( regex ); + } - if ( index !== - 1 ) { + renderItemsIndex ++; - handlers.splice( index, 2 ); + return renderItem; - } + } - return this; + function push( object, geometry, material, groupOrder, z, group ) { - }; + const renderItem = getNextRenderItem( object, geometry, material, groupOrder, z, group ); - this.getHandler = function ( file ) { + if ( material.transmission > 0.0 ) { - for ( let i = 0, l = handlers.length; i < l; i += 2 ) { + transmissive.push( renderItem ); - const regex = handlers[ i ]; - const loader = handlers[ i + 1 ]; + } else if ( material.transparent === true ) { - if ( regex.global ) regex.lastIndex = 0; // see #17920 + transparent.push( renderItem ); - if ( regex.test( file ) ) { + } else { - return loader; + opaque.push( renderItem ); - } + } - } + } - return null; + function unshift( object, geometry, material, groupOrder, z, group ) { - }; + const renderItem = getNextRenderItem( object, geometry, material, groupOrder, z, group ); - } + if ( material.transmission > 0.0 ) { -} + transmissive.unshift( renderItem ); -const DefaultLoadingManager = /*@__PURE__*/ new LoadingManager(); + } else if ( material.transparent === true ) { -class Loader { + transparent.unshift( renderItem ); - constructor( manager ) { + } else { - this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager; + opaque.unshift( renderItem ); - this.crossOrigin = 'anonymous'; - this.withCredentials = false; - this.path = ''; - this.resourcePath = ''; - this.requestHeader = {}; + } } - load( /* url, onLoad, onProgress, onError */ ) {} + function sort( customOpaqueSort, customTransparentSort ) { - loadAsync( url, onProgress ) { + if ( opaque.length > 1 ) opaque.sort( customOpaqueSort || painterSortStable ); + if ( transmissive.length > 1 ) transmissive.sort( customTransparentSort || reversePainterSortStable ); + if ( transparent.length > 1 ) transparent.sort( customTransparentSort || reversePainterSortStable ); - const scope = this; + } - return new Promise( function ( resolve, reject ) { + function finish() { - scope.load( url, resolve, onProgress, reject ); + // Clear references from inactive renderItems in the list - } ); + for ( let i = renderItemsIndex, il = renderItems.length; i < il; i ++ ) { - } + const renderItem = renderItems[ i ]; - parse( /* data */ ) {} + if ( renderItem.id === null ) break; - setCrossOrigin( crossOrigin ) { + renderItem.id = null; + renderItem.object = null; + renderItem.geometry = null; + renderItem.material = null; + renderItem.group = null; - this.crossOrigin = crossOrigin; - return this; + } } - setWithCredentials( value ) { + return { - this.withCredentials = value; - return this; + opaque: opaque, + transmissive: transmissive, + transparent: transparent, - } + init: init, + push: push, + unshift: unshift, + finish: finish, - setPath( path ) { + sort: sort + }; - this.path = path; - return this; +} - } +function WebGLRenderLists() { - setResourcePath( resourcePath ) { + let lists = new WeakMap(); - this.resourcePath = resourcePath; - return this; + function get( scene, renderCallDepth ) { - } + const listArray = lists.get( scene ); + let list; - setRequestHeader( requestHeader ) { + if ( listArray === undefined ) { - this.requestHeader = requestHeader; - return this; + list = new WebGLRenderList(); + lists.set( scene, [ list ] ); - } + } else { -} + if ( renderCallDepth >= listArray.length ) { -const loading = {}; + list = new WebGLRenderList(); + listArray.push( list ); -class HttpError extends Error { + } else { - constructor( message, response ) { + list = listArray[ renderCallDepth ]; - super( message ); - this.response = response; + } - } + } -} + return list; -class FileLoader extends Loader { + } - constructor( manager ) { + function dispose() { - super( manager ); + lists = new WeakMap(); } - load( url, onLoad, onProgress, onError ) { + return { + get: get, + dispose: dispose + }; - if ( url === undefined ) url = ''; +} - if ( this.path !== undefined ) url = this.path + url; +function UniformsCache() { - url = this.manager.resolveURL( url ); + const lights = {}; - const cached = Cache.get( url ); + return { - if ( cached !== undefined ) { + get: function ( light ) { - this.manager.itemStart( url ); + if ( lights[ light.id ] !== undefined ) { - setTimeout( () => { + return lights[ light.id ]; - if ( onLoad ) onLoad( cached ); + } - this.manager.itemEnd( url ); + let uniforms; - }, 0 ); + switch ( light.type ) { - return cached; + case 'DirectionalLight': + uniforms = { + direction: new Vector3(), + color: new Color() + }; + break; - } + case 'SpotLight': + uniforms = { + position: new Vector3(), + direction: new Vector3(), + color: new Color(), + distance: 0, + coneCos: 0, + penumbraCos: 0, + decay: 0 + }; + break; - // Check if request is duplicate + case 'PointLight': + uniforms = { + position: new Vector3(), + color: new Color(), + distance: 0, + decay: 0 + }; + break; - if ( loading[ url ] !== undefined ) { + case 'HemisphereLight': + uniforms = { + direction: new Vector3(), + skyColor: new Color(), + groundColor: new Color() + }; + break; - loading[ url ].push( { + case 'RectAreaLight': + uniforms = { + color: new Color(), + position: new Vector3(), + halfWidth: new Vector3(), + halfHeight: new Vector3() + }; + break; - onLoad: onLoad, - onProgress: onProgress, - onError: onError + } - } ); + lights[ light.id ] = uniforms; - return; + return uniforms; } - // Initialise array for duplicate requests - loading[ url ] = []; + }; - loading[ url ].push( { - onLoad: onLoad, - onProgress: onProgress, - onError: onError, - } ); +} - // create request - const req = new Request( url, { - headers: new Headers( this.requestHeader ), - credentials: this.withCredentials ? 'include' : 'same-origin', - // An abort controller could be added within a future PR - } ); +function ShadowUniformsCache() { + + const lights = {}; + + return { - // record states ( avoid data race ) - const mimeType = this.mimeType; - const responseType = this.responseType; + get: function ( light ) { - // start the fetch - fetch( req ) - .then( response => { + if ( lights[ light.id ] !== undefined ) { - if ( response.status === 200 || response.status === 0 ) { + return lights[ light.id ]; - // Some browsers return HTTP Status 0 when using non-http protocol - // e.g. 'file://' or 'data://'. Handle as success. + } - if ( response.status === 0 ) { + let uniforms; - console.warn( 'THREE.FileLoader: HTTP Status 0 received.' ); + switch ( light.type ) { - } + case 'DirectionalLight': + uniforms = { + shadowIntensity: 1, + shadowBias: 0, + shadowNormalBias: 0, + shadowRadius: 1, + shadowMapSize: new Vector2() + }; + break; + + case 'SpotLight': + uniforms = { + shadowIntensity: 1, + shadowBias: 0, + shadowNormalBias: 0, + shadowRadius: 1, + shadowMapSize: new Vector2() + }; + break; - // Workaround: Checking if response.body === undefined for Alipay browser #23548 + case 'PointLight': + uniforms = { + shadowIntensity: 1, + shadowBias: 0, + shadowNormalBias: 0, + shadowRadius: 1, + shadowMapSize: new Vector2(), + shadowCameraNear: 1, + shadowCameraFar: 1000 + }; + break; - if ( typeof ReadableStream === 'undefined' || response.body === undefined || response.body.getReader === undefined ) { + // TODO (abelnation): set RectAreaLight shadow uniforms - return response; + } - } + lights[ light.id ] = uniforms; - const callbacks = loading[ url ]; - const reader = response.body.getReader(); + return uniforms; - // Nginx needs X-File-Size check - // https://serverfault.com/questions/482875/why-does-nginx-remove-content-length-header-for-chunked-content - const contentLength = response.headers.get( 'Content-Length' ) || response.headers.get( 'X-File-Size' ); - const total = contentLength ? parseInt( contentLength ) : 0; - const lengthComputable = total !== 0; - let loaded = 0; + } - // periodically read data into the new stream tracking while download progress - const stream = new ReadableStream( { - start( controller ) { + }; - readData(); +} - function readData() { - reader.read().then( ( { done, value } ) => { - if ( done ) { +let nextVersion = 0; - controller.close(); +function shadowCastingAndTexturingLightsFirst( lightA, lightB ) { - } else { + return ( lightB.castShadow ? 2 : 0 ) - ( lightA.castShadow ? 2 : 0 ) + ( lightB.map ? 1 : 0 ) - ( lightA.map ? 1 : 0 ); - loaded += value.byteLength; +} - const event = new ProgressEvent( 'progress', { lengthComputable, loaded, total } ); - for ( let i = 0, il = callbacks.length; i < il; i ++ ) { +function WebGLLights( extensions ) { - const callback = callbacks[ i ]; - if ( callback.onProgress ) callback.onProgress( event ); + const cache = new UniformsCache(); - } + const shadowCache = ShadowUniformsCache(); - controller.enqueue( value ); - readData(); + const state = { - } + version: 0, - } ); + hash: { + directionalLength: -1, + pointLength: -1, + spotLength: -1, + rectAreaLength: -1, + hemiLength: -1, + + numDirectionalShadows: -1, + numPointShadows: -1, + numSpotShadows: -1, + numSpotMaps: -1, + + numLightProbes: -1 + }, - } + ambient: [ 0, 0, 0 ], + probe: [], + directional: [], + directionalShadow: [], + directionalShadowMap: [], + directionalShadowMatrix: [], + spot: [], + spotLightMap: [], + spotShadow: [], + spotShadowMap: [], + spotLightMatrix: [], + rectArea: [], + rectAreaLTC1: null, + rectAreaLTC2: null, + point: [], + pointShadow: [], + pointShadowMap: [], + pointShadowMatrix: [], + hemi: [], + numSpotLightShadowsWithMaps: 0, + numLightProbes: 0 - } + }; - } ); + for ( let i = 0; i < 9; i ++ ) state.probe.push( new Vector3() ); - return new Response( stream ); + const vector3 = new Vector3(); + const matrix4 = new Matrix4(); + const matrix42 = new Matrix4(); - } else { + function setup( lights ) { - throw new HttpError( `fetch for "${response.url}" responded with ${response.status}: ${response.statusText}`, response ); + let r = 0, g = 0, b = 0; - } + for ( let i = 0; i < 9; i ++ ) state.probe[ i ].set( 0, 0, 0 ); - } ) - .then( response => { + let directionalLength = 0; + let pointLength = 0; + let spotLength = 0; + let rectAreaLength = 0; + let hemiLength = 0; - switch ( responseType ) { + let numDirectionalShadows = 0; + let numPointShadows = 0; + let numSpotShadows = 0; + let numSpotMaps = 0; + let numSpotShadowsWithMaps = 0; - case 'arraybuffer': + let numLightProbes = 0; - return response.arrayBuffer(); + // ordering : [shadow casting + map texturing, map texturing, shadow casting, none ] + lights.sort( shadowCastingAndTexturingLightsFirst ); - case 'blob': + for ( let i = 0, l = lights.length; i < l; i ++ ) { - return response.blob(); + const light = lights[ i ]; - case 'document': + const color = light.color; + const intensity = light.intensity; + const distance = light.distance; - return response.text() - .then( text => { + const shadowMap = ( light.shadow && light.shadow.map ) ? light.shadow.map.texture : null; - const parser = new DOMParser(); - return parser.parseFromString( text, mimeType ); + if ( light.isAmbientLight ) { - } ); + r += color.r * intensity; + g += color.g * intensity; + b += color.b * intensity; - case 'json': + } else if ( light.isLightProbe ) { - return response.json(); + for ( let j = 0; j < 9; j ++ ) { - default: + state.probe[ j ].addScaledVector( light.sh.coefficients[ j ], intensity ); - if ( mimeType === undefined ) { + } - return response.text(); + numLightProbes ++; - } else { + } else if ( light.isDirectionalLight ) { - // sniff encoding - const re = /charset="?([^;"\s]*)"?/i; - const exec = re.exec( mimeType ); - const label = exec && exec[ 1 ] ? exec[ 1 ].toLowerCase() : undefined; - const decoder = new TextDecoder( label ); - return response.arrayBuffer().then( ab => decoder.decode( ab ) ); + const uniforms = cache.get( light ); - } + uniforms.color.copy( light.color ).multiplyScalar( light.intensity ); - } + if ( light.castShadow ) { - } ) - .then( data => { + const shadow = light.shadow; - // Add to cache only on HTTP success, so that we do not cache - // error response bodies as proper responses to requests. - Cache.add( url, data ); + const shadowUniforms = shadowCache.get( light ); - const callbacks = loading[ url ]; - delete loading[ url ]; + shadowUniforms.shadowIntensity = shadow.intensity; + shadowUniforms.shadowBias = shadow.bias; + shadowUniforms.shadowNormalBias = shadow.normalBias; + shadowUniforms.shadowRadius = shadow.radius; + shadowUniforms.shadowMapSize = shadow.mapSize; - for ( let i = 0, il = callbacks.length; i < il; i ++ ) { + state.directionalShadow[ directionalLength ] = shadowUniforms; + state.directionalShadowMap[ directionalLength ] = shadowMap; + state.directionalShadowMatrix[ directionalLength ] = light.shadow.matrix; - const callback = callbacks[ i ]; - if ( callback.onLoad ) callback.onLoad( data ); + numDirectionalShadows ++; } - } ) - .catch( err => { + state.directional[ directionalLength ] = uniforms; - // Abort errors and other errors are handled the same + directionalLength ++; - const callbacks = loading[ url ]; + } else if ( light.isSpotLight ) { - if ( callbacks === undefined ) { + const uniforms = cache.get( light ); - // When onLoad was called and url was deleted in `loading` - this.manager.itemError( url ); - throw err; + uniforms.position.setFromMatrixPosition( light.matrixWorld ); - } + uniforms.color.copy( color ).multiplyScalar( intensity ); + uniforms.distance = distance; - delete loading[ url ]; + uniforms.coneCos = Math.cos( light.angle ); + uniforms.penumbraCos = Math.cos( light.angle * ( 1 - light.penumbra ) ); + uniforms.decay = light.decay; - for ( let i = 0, il = callbacks.length; i < il; i ++ ) { + state.spot[ spotLength ] = uniforms; - const callback = callbacks[ i ]; - if ( callback.onError ) callback.onError( err ); + const shadow = light.shadow; - } + if ( light.map ) { - this.manager.itemError( url ); + state.spotLightMap[ numSpotMaps ] = light.map; + numSpotMaps ++; - } ) - .finally( () => { + // make sure the lightMatrix is up to date + // TODO : do it if required only + shadow.updateMatrices( light ); - this.manager.itemEnd( url ); + if ( light.castShadow ) numSpotShadowsWithMaps ++; - } ); + } - this.manager.itemStart( url ); + state.spotLightMatrix[ spotLength ] = shadow.matrix; - } + if ( light.castShadow ) { - setResponseType( value ) { + const shadowUniforms = shadowCache.get( light ); - this.responseType = value; - return this; + shadowUniforms.shadowIntensity = shadow.intensity; + shadowUniforms.shadowBias = shadow.bias; + shadowUniforms.shadowNormalBias = shadow.normalBias; + shadowUniforms.shadowRadius = shadow.radius; + shadowUniforms.shadowMapSize = shadow.mapSize; - } + state.spotShadow[ spotLength ] = shadowUniforms; + state.spotShadowMap[ spotLength ] = shadowMap; - setMimeType( value ) { + numSpotShadows ++; - this.mimeType = value; - return this; + } - } + spotLength ++; -} + } else if ( light.isRectAreaLight ) { -class AnimationLoader extends Loader { + const uniforms = cache.get( light ); - constructor( manager ) { + uniforms.color.copy( color ).multiplyScalar( intensity ); - super( manager ); + uniforms.halfWidth.set( light.width * 0.5, 0.0, 0.0 ); + uniforms.halfHeight.set( 0.0, light.height * 0.5, 0.0 ); - } + state.rectArea[ rectAreaLength ] = uniforms; - load( url, onLoad, onProgress, onError ) { + rectAreaLength ++; - const scope = this; + } else if ( light.isPointLight ) { - const loader = new FileLoader( this.manager ); - loader.setPath( this.path ); - loader.setRequestHeader( this.requestHeader ); - loader.setWithCredentials( this.withCredentials ); - loader.load( url, function ( text ) { + const uniforms = cache.get( light ); - try { + uniforms.color.copy( light.color ).multiplyScalar( light.intensity ); + uniforms.distance = light.distance; + uniforms.decay = light.decay; - onLoad( scope.parse( JSON.parse( text ) ) ); + if ( light.castShadow ) { - } catch ( e ) { + const shadow = light.shadow; - if ( onError ) { + const shadowUniforms = shadowCache.get( light ); - onError( e ); + shadowUniforms.shadowIntensity = shadow.intensity; + shadowUniforms.shadowBias = shadow.bias; + shadowUniforms.shadowNormalBias = shadow.normalBias; + shadowUniforms.shadowRadius = shadow.radius; + shadowUniforms.shadowMapSize = shadow.mapSize; + shadowUniforms.shadowCameraNear = shadow.camera.near; + shadowUniforms.shadowCameraFar = shadow.camera.far; - } else { + state.pointShadow[ pointLength ] = shadowUniforms; + state.pointShadowMap[ pointLength ] = shadowMap; + state.pointShadowMatrix[ pointLength ] = light.shadow.matrix; - console.error( e ); + numPointShadows ++; } - scope.manager.itemError( url ); - - } + state.point[ pointLength ] = uniforms; - }, onProgress, onError ); + pointLength ++; - } + } else if ( light.isHemisphereLight ) { - parse( json ) { + const uniforms = cache.get( light ); - const animations = []; + uniforms.skyColor.copy( light.color ).multiplyScalar( intensity ); + uniforms.groundColor.copy( light.groundColor ).multiplyScalar( intensity ); - for ( let i = 0; i < json.length; i ++ ) { + state.hemi[ hemiLength ] = uniforms; - const clip = AnimationClip.parse( json[ i ] ); + hemiLength ++; - animations.push( clip ); + } } - return animations; - - } - -} - -/** - * Abstract Base class to block based textures loader (dds, pvr, ...) - * - * Sub classes have to implement the parse() method which will be used in load(). - */ - -class CompressedTextureLoader extends Loader { + if ( rectAreaLength > 0 ) { - constructor( manager ) { + if ( extensions.has( 'OES_texture_float_linear' ) === true ) { - super( manager ); + state.rectAreaLTC1 = UniformsLib.LTC_FLOAT_1; + state.rectAreaLTC2 = UniformsLib.LTC_FLOAT_2; - } + } else { - load( url, onLoad, onProgress, onError ) { + state.rectAreaLTC1 = UniformsLib.LTC_HALF_1; + state.rectAreaLTC2 = UniformsLib.LTC_HALF_2; - const scope = this; + } - const images = []; + } - const texture = new CompressedTexture(); + state.ambient[ 0 ] = r; + state.ambient[ 1 ] = g; + state.ambient[ 2 ] = b; - const loader = new FileLoader( this.manager ); - loader.setPath( this.path ); - loader.setResponseType( 'arraybuffer' ); - loader.setRequestHeader( this.requestHeader ); - loader.setWithCredentials( scope.withCredentials ); + const hash = state.hash; - let loaded = 0; + if ( hash.directionalLength !== directionalLength || + hash.pointLength !== pointLength || + hash.spotLength !== spotLength || + hash.rectAreaLength !== rectAreaLength || + hash.hemiLength !== hemiLength || + hash.numDirectionalShadows !== numDirectionalShadows || + hash.numPointShadows !== numPointShadows || + hash.numSpotShadows !== numSpotShadows || + hash.numSpotMaps !== numSpotMaps || + hash.numLightProbes !== numLightProbes ) { - function loadTexture( i ) { + state.directional.length = directionalLength; + state.spot.length = spotLength; + state.rectArea.length = rectAreaLength; + state.point.length = pointLength; + state.hemi.length = hemiLength; - loader.load( url[ i ], function ( buffer ) { + state.directionalShadow.length = numDirectionalShadows; + state.directionalShadowMap.length = numDirectionalShadows; + state.pointShadow.length = numPointShadows; + state.pointShadowMap.length = numPointShadows; + state.spotShadow.length = numSpotShadows; + state.spotShadowMap.length = numSpotShadows; + state.directionalShadowMatrix.length = numDirectionalShadows; + state.pointShadowMatrix.length = numPointShadows; + state.spotLightMatrix.length = numSpotShadows + numSpotMaps - numSpotShadowsWithMaps; + state.spotLightMap.length = numSpotMaps; + state.numSpotLightShadowsWithMaps = numSpotShadowsWithMaps; + state.numLightProbes = numLightProbes; - const texDatas = scope.parse( buffer, true ); + hash.directionalLength = directionalLength; + hash.pointLength = pointLength; + hash.spotLength = spotLength; + hash.rectAreaLength = rectAreaLength; + hash.hemiLength = hemiLength; - images[ i ] = { - width: texDatas.width, - height: texDatas.height, - format: texDatas.format, - mipmaps: texDatas.mipmaps - }; + hash.numDirectionalShadows = numDirectionalShadows; + hash.numPointShadows = numPointShadows; + hash.numSpotShadows = numSpotShadows; + hash.numSpotMaps = numSpotMaps; - loaded += 1; + hash.numLightProbes = numLightProbes; - if ( loaded === 6 ) { + state.version = nextVersion ++; - if ( texDatas.mipmapCount === 1 ) texture.minFilter = LinearFilter; + } - texture.image = images; - texture.format = texDatas.format; - texture.needsUpdate = true; + } - if ( onLoad ) onLoad( texture ); + function setupView( lights, camera ) { - } + let directionalLength = 0; + let pointLength = 0; + let spotLength = 0; + let rectAreaLength = 0; + let hemiLength = 0; - }, onProgress, onError ); + const viewMatrix = camera.matrixWorldInverse; - } + for ( let i = 0, l = lights.length; i < l; i ++ ) { - if ( Array.isArray( url ) ) { + const light = lights[ i ]; - for ( let i = 0, il = url.length; i < il; ++ i ) { + if ( light.isDirectionalLight ) { - loadTexture( i ); + const uniforms = state.directional[ directionalLength ]; - } + uniforms.direction.setFromMatrixPosition( light.matrixWorld ); + vector3.setFromMatrixPosition( light.target.matrixWorld ); + uniforms.direction.sub( vector3 ); + uniforms.direction.transformDirection( viewMatrix ); - } else { + directionalLength ++; - // compressed cubemap texture stored in a single DDS file + } else if ( light.isSpotLight ) { - loader.load( url, function ( buffer ) { + const uniforms = state.spot[ spotLength ]; - const texDatas = scope.parse( buffer, true ); + uniforms.position.setFromMatrixPosition( light.matrixWorld ); + uniforms.position.applyMatrix4( viewMatrix ); - if ( texDatas.isCubemap ) { + uniforms.direction.setFromMatrixPosition( light.matrixWorld ); + vector3.setFromMatrixPosition( light.target.matrixWorld ); + uniforms.direction.sub( vector3 ); + uniforms.direction.transformDirection( viewMatrix ); - const faces = texDatas.mipmaps.length / texDatas.mipmapCount; + spotLength ++; - for ( let f = 0; f < faces; f ++ ) { + } else if ( light.isRectAreaLight ) { - images[ f ] = { mipmaps: [] }; + const uniforms = state.rectArea[ rectAreaLength ]; - for ( let i = 0; i < texDatas.mipmapCount; i ++ ) { + uniforms.position.setFromMatrixPosition( light.matrixWorld ); + uniforms.position.applyMatrix4( viewMatrix ); - images[ f ].mipmaps.push( texDatas.mipmaps[ f * texDatas.mipmapCount + i ] ); - images[ f ].format = texDatas.format; - images[ f ].width = texDatas.width; - images[ f ].height = texDatas.height; + // extract local rotation of light to derive width/height half vectors + matrix42.identity(); + matrix4.copy( light.matrixWorld ); + matrix4.premultiply( viewMatrix ); + matrix42.extractRotation( matrix4 ); - } + uniforms.halfWidth.set( light.width * 0.5, 0.0, 0.0 ); + uniforms.halfHeight.set( 0.0, light.height * 0.5, 0.0 ); - } + uniforms.halfWidth.applyMatrix4( matrix42 ); + uniforms.halfHeight.applyMatrix4( matrix42 ); - texture.image = images; + rectAreaLength ++; - } else { + } else if ( light.isPointLight ) { - texture.image.width = texDatas.width; - texture.image.height = texDatas.height; - texture.mipmaps = texDatas.mipmaps; + const uniforms = state.point[ pointLength ]; - } + uniforms.position.setFromMatrixPosition( light.matrixWorld ); + uniforms.position.applyMatrix4( viewMatrix ); - if ( texDatas.mipmapCount === 1 ) { + pointLength ++; - texture.minFilter = LinearFilter; + } else if ( light.isHemisphereLight ) { - } + const uniforms = state.hemi[ hemiLength ]; - texture.format = texDatas.format; - texture.needsUpdate = true; + uniforms.direction.setFromMatrixPosition( light.matrixWorld ); + uniforms.direction.transformDirection( viewMatrix ); - if ( onLoad ) onLoad( texture ); + hemiLength ++; - }, onProgress, onError ); + } } - return texture; - } -} + return { + setup: setup, + setupView: setupView, + state: state + }; -class ImageLoader extends Loader { +} - constructor( manager ) { +function WebGLRenderState( extensions ) { - super( manager ); + const lights = new WebGLLights( extensions ); - } + const lightsArray = []; + const shadowsArray = []; - load( url, onLoad, onProgress, onError ) { + function init( camera ) { - if ( this.path !== undefined ) url = this.path + url; + state.camera = camera; - url = this.manager.resolveURL( url ); + lightsArray.length = 0; + shadowsArray.length = 0; - const scope = this; + } - const cached = Cache.get( url ); + function pushLight( light ) { - if ( cached !== undefined ) { + lightsArray.push( light ); - scope.manager.itemStart( url ); + } - setTimeout( function () { + function pushShadow( shadowLight ) { - if ( onLoad ) onLoad( cached ); + shadowsArray.push( shadowLight ); - scope.manager.itemEnd( url ); + } - }, 0 ); + function setupLights() { - return cached; + lights.setup( lightsArray ); - } + } - const image = createElementNS( 'img' ); + function setupLightsView( camera ) { - function onImageLoad() { + lights.setupView( lightsArray, camera ); - removeEventListeners(); + } - Cache.add( url, this ); + const state = { + lightsArray: lightsArray, + shadowsArray: shadowsArray, - if ( onLoad ) onLoad( this ); + camera: null, - scope.manager.itemEnd( url ); + lights: lights, - } + transmissionRenderTarget: {} + }; - function onImageError( event ) { + return { + init: init, + state: state, + setupLights: setupLights, + setupLightsView: setupLightsView, - removeEventListeners(); + pushLight: pushLight, + pushShadow: pushShadow + }; - if ( onError ) onError( event ); +} - scope.manager.itemError( url ); - scope.manager.itemEnd( url ); +function WebGLRenderStates( extensions ) { - } + let renderStates = new WeakMap(); - function removeEventListeners() { + function get( scene, renderCallDepth = 0 ) { - image.removeEventListener( 'load', onImageLoad, false ); - image.removeEventListener( 'error', onImageError, false ); + const renderStateArray = renderStates.get( scene ); + let renderState; - } + if ( renderStateArray === undefined ) { - image.addEventListener( 'load', onImageLoad, false ); - image.addEventListener( 'error', onImageError, false ); + renderState = new WebGLRenderState( extensions ); + renderStates.set( scene, [ renderState ] ); - if ( url.slice( 0, 5 ) !== 'data:' ) { + } else { - if ( this.crossOrigin !== undefined ) image.crossOrigin = this.crossOrigin; + if ( renderCallDepth >= renderStateArray.length ) { - } + renderState = new WebGLRenderState( extensions ); + renderStateArray.push( renderState ); - scope.manager.itemStart( url ); + } else { - image.src = url; + renderState = renderStateArray[ renderCallDepth ]; - return image; + } - } + } -} + return renderState; -class CubeTextureLoader extends Loader { + } - constructor( manager ) { + function dispose() { - super( manager ); + renderStates = new WeakMap(); } - load( urls, onLoad, onProgress, onError ) { + return { + get: get, + dispose: dispose + }; - const texture = new CubeTexture(); +} - const loader = new ImageLoader( this.manager ); - loader.setCrossOrigin( this.crossOrigin ); - loader.setPath( this.path ); +const vertex = "void main() {\n\tgl_Position = vec4( position, 1.0 );\n}"; - let loaded = 0; +const fragment = "uniform sampler2D shadow_pass;\nuniform vec2 resolution;\nuniform float radius;\n#include \nvoid main() {\n\tconst float samples = float( VSM_SAMPLES );\n\tfloat mean = 0.0;\n\tfloat squared_mean = 0.0;\n\tfloat uvStride = samples <= 1.0 ? 0.0 : 2.0 / ( samples - 1.0 );\n\tfloat uvStart = samples <= 1.0 ? 0.0 : - 1.0;\n\tfor ( float i = 0.0; i < samples; i ++ ) {\n\t\tfloat uvOffset = uvStart + i * uvStride;\n\t\t#ifdef HORIZONTAL_PASS\n\t\t\tvec2 distribution = unpackRGBATo2Half( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( uvOffset, 0.0 ) * radius ) / resolution ) );\n\t\t\tmean += distribution.x;\n\t\t\tsquared_mean += distribution.y * distribution.y + distribution.x * distribution.x;\n\t\t#else\n\t\t\tfloat depth = unpackRGBAToDepth( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( 0.0, uvOffset ) * radius ) / resolution ) );\n\t\t\tmean += depth;\n\t\t\tsquared_mean += depth * depth;\n\t\t#endif\n\t}\n\tmean = mean / samples;\n\tsquared_mean = squared_mean / samples;\n\tfloat std_dev = sqrt( squared_mean - mean * mean );\n\tgl_FragColor = pack2HalfToRGBA( vec2( mean, std_dev ) );\n}"; - function loadTexture( i ) { +function WebGLShadowMap( renderer, objects, capabilities ) { - loader.load( urls[ i ], function ( image ) { + let _frustum = new Frustum(); - texture.images[ i ] = image; + const _shadowMapSize = new Vector2(), + _viewportSize = new Vector2(), - loaded ++; + _viewport = new Vector4(), - if ( loaded === 6 ) { + _depthMaterial = new MeshDepthMaterial( { depthPacking: RGBADepthPacking } ), + _distanceMaterial = new MeshDistanceMaterial(), - texture.needsUpdate = true; + _materialCache = {}, - if ( onLoad ) onLoad( texture ); + _maxTextureSize = capabilities.maxTextureSize; - } + const shadowSide = { [ FrontSide ]: BackSide, [ BackSide ]: FrontSide, [ DoubleSide ]: DoubleSide }; - }, undefined, onError ); + const shadowMaterialVertical = new ShaderMaterial( { + defines: { + VSM_SAMPLES: 8 + }, + uniforms: { + shadow_pass: { value: null }, + resolution: { value: new Vector2() }, + radius: { value: 4.0 } + }, - } + vertexShader: vertex, + fragmentShader: fragment - for ( let i = 0; i < urls.length; ++ i ) { + } ); - loadTexture( i ); + const shadowMaterialHorizontal = shadowMaterialVertical.clone(); + shadowMaterialHorizontal.defines.HORIZONTAL_PASS = 1; - } + const fullScreenTri = new BufferGeometry(); + fullScreenTri.setAttribute( + 'position', + new BufferAttribute( + new Float32Array( [ -1, -1, 0.5, 3, -1, 0.5, -1, 3, 0.5 ] ), + 3 + ) + ); - return texture; + const fullScreenMesh = new Mesh( fullScreenTri, shadowMaterialVertical ); - } + const scope = this; -} + this.enabled = false; -/** - * Abstract Base class to load generic binary textures formats (rgbe, hdr, ...) - * - * Sub classes have to implement the parse() method which will be used in load(). - */ + this.autoUpdate = true; + this.needsUpdate = false; -class DataTextureLoader extends Loader { + this.type = PCFShadowMap; + let _previousType = this.type; - constructor( manager ) { + this.render = function ( lights, scene, camera ) { - super( manager ); + if ( scope.enabled === false ) return; + if ( scope.autoUpdate === false && scope.needsUpdate === false ) return; - } + if ( lights.length === 0 ) return; - load( url, onLoad, onProgress, onError ) { + const currentRenderTarget = renderer.getRenderTarget(); + const activeCubeFace = renderer.getActiveCubeFace(); + const activeMipmapLevel = renderer.getActiveMipmapLevel(); - const scope = this; + const _state = renderer.state; - const texture = new DataTexture(); + // Set GL state for depth map. + _state.setBlending( NoBlending ); + _state.buffers.color.setClear( 1, 1, 1, 1 ); + _state.buffers.depth.setTest( true ); + _state.setScissorTest( false ); - const loader = new FileLoader( this.manager ); - loader.setResponseType( 'arraybuffer' ); - loader.setRequestHeader( this.requestHeader ); - loader.setPath( this.path ); - loader.setWithCredentials( scope.withCredentials ); - loader.load( url, function ( buffer ) { + // check for shadow map type changes - const texData = scope.parse( buffer ); + const toVSM = ( _previousType !== VSMShadowMap && this.type === VSMShadowMap ); + const fromVSM = ( _previousType === VSMShadowMap && this.type !== VSMShadowMap ); - if ( ! texData ) return; + // render depth map - if ( texData.image !== undefined ) { + for ( let i = 0, il = lights.length; i < il; i ++ ) { - texture.image = texData.image; + const light = lights[ i ]; + const shadow = light.shadow; - } else if ( texData.data !== undefined ) { + if ( shadow === undefined ) { - texture.image.width = texData.width; - texture.image.height = texData.height; - texture.image.data = texData.data; + console.warn( 'THREE.WebGLShadowMap:', light, 'has no shadow.' ); + continue; } - texture.wrapS = texData.wrapS !== undefined ? texData.wrapS : ClampToEdgeWrapping; - texture.wrapT = texData.wrapT !== undefined ? texData.wrapT : ClampToEdgeWrapping; - - texture.magFilter = texData.magFilter !== undefined ? texData.magFilter : LinearFilter; - texture.minFilter = texData.minFilter !== undefined ? texData.minFilter : LinearFilter; - - texture.anisotropy = texData.anisotropy !== undefined ? texData.anisotropy : 1; - - if ( texData.colorSpace !== undefined ) { - - texture.colorSpace = texData.colorSpace; + if ( shadow.autoUpdate === false && shadow.needsUpdate === false ) continue; - } else if ( texData.encoding !== undefined ) { // @deprecated, r152 + _shadowMapSize.copy( shadow.mapSize ); - texture.encoding = texData.encoding; + const shadowFrameExtents = shadow.getFrameExtents(); - } + _shadowMapSize.multiply( shadowFrameExtents ); - if ( texData.flipY !== undefined ) { + _viewportSize.copy( shadow.mapSize ); - texture.flipY = texData.flipY; + if ( _shadowMapSize.x > _maxTextureSize || _shadowMapSize.y > _maxTextureSize ) { - } + if ( _shadowMapSize.x > _maxTextureSize ) { - if ( texData.format !== undefined ) { + _viewportSize.x = Math.floor( _maxTextureSize / shadowFrameExtents.x ); + _shadowMapSize.x = _viewportSize.x * shadowFrameExtents.x; + shadow.mapSize.x = _viewportSize.x; - texture.format = texData.format; + } - } + if ( _shadowMapSize.y > _maxTextureSize ) { - if ( texData.type !== undefined ) { + _viewportSize.y = Math.floor( _maxTextureSize / shadowFrameExtents.y ); + _shadowMapSize.y = _viewportSize.y * shadowFrameExtents.y; + shadow.mapSize.y = _viewportSize.y; - texture.type = texData.type; + } } - if ( texData.mipmaps !== undefined ) { - - texture.mipmaps = texData.mipmaps; - texture.minFilter = LinearMipmapLinearFilter; // presumably... + if ( shadow.map === null || toVSM === true || fromVSM === true ) { - } + const pars = ( this.type !== VSMShadowMap ) ? { minFilter: NearestFilter, magFilter: NearestFilter } : {}; - if ( texData.mipmapCount === 1 ) { + if ( shadow.map !== null ) { - texture.minFilter = LinearFilter; + shadow.map.dispose(); - } + } - if ( texData.generateMipmaps !== undefined ) { + shadow.map = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y, pars ); + shadow.map.texture.name = light.name + '.shadowMap'; - texture.generateMipmaps = texData.generateMipmaps; + shadow.camera.updateProjectionMatrix(); } - texture.needsUpdate = true; - - if ( onLoad ) onLoad( texture, texData ); - - }, onProgress, onError ); - + renderer.setRenderTarget( shadow.map ); + renderer.clear(); - return texture; - - } - -} + const viewportCount = shadow.getViewportCount(); -class TextureLoader extends Loader { + for ( let vp = 0; vp < viewportCount; vp ++ ) { - constructor( manager ) { + const viewport = shadow.getViewport( vp ); - super( manager ); + _viewport.set( + _viewportSize.x * viewport.x, + _viewportSize.y * viewport.y, + _viewportSize.x * viewport.z, + _viewportSize.y * viewport.w + ); - } + _state.viewport( _viewport ); - load( url, onLoad, onProgress, onError ) { + shadow.updateMatrices( light, vp ); - const texture = new Texture(); + _frustum = shadow.getFrustum(); - const loader = new ImageLoader( this.manager ); - loader.setCrossOrigin( this.crossOrigin ); - loader.setPath( this.path ); + renderObject( scene, camera, shadow.camera, light, this.type ); - loader.load( url, function ( image ) { + } - texture.image = image; - texture.needsUpdate = true; + // do blur pass for VSM - if ( onLoad !== undefined ) { + if ( shadow.isPointLightShadow !== true && this.type === VSMShadowMap ) { - onLoad( texture ); + VSMPass( shadow, camera ); } - }, onProgress, onError ); + shadow.needsUpdate = false; - return texture; + } - } + _previousType = this.type; -} + scope.needsUpdate = false; + + renderer.setRenderTarget( currentRenderTarget, activeCubeFace, activeMipmapLevel ); -class Light extends Object3D { + }; - constructor( color, intensity = 1 ) { + function VSMPass( shadow, camera ) { - super(); + const geometry = objects.update( fullScreenMesh ); - this.isLight = true; + if ( shadowMaterialVertical.defines.VSM_SAMPLES !== shadow.blurSamples ) { - this.type = 'Light'; + shadowMaterialVertical.defines.VSM_SAMPLES = shadow.blurSamples; + shadowMaterialHorizontal.defines.VSM_SAMPLES = shadow.blurSamples; - this.color = new Color( color ); - this.intensity = intensity; + shadowMaterialVertical.needsUpdate = true; + shadowMaterialHorizontal.needsUpdate = true; - } + } - dispose() { + if ( shadow.mapPass === null ) { - // Empty here in base class; some subclasses override. + shadow.mapPass = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y ); - } + } - copy( source, recursive ) { + // vertical pass - super.copy( source, recursive ); + shadowMaterialVertical.uniforms.shadow_pass.value = shadow.map.texture; + shadowMaterialVertical.uniforms.resolution.value = shadow.mapSize; + shadowMaterialVertical.uniforms.radius.value = shadow.radius; + renderer.setRenderTarget( shadow.mapPass ); + renderer.clear(); + renderer.renderBufferDirect( camera, null, geometry, shadowMaterialVertical, fullScreenMesh, null ); - this.color.copy( source.color ); - this.intensity = source.intensity; + // horizontal pass - return this; + shadowMaterialHorizontal.uniforms.shadow_pass.value = shadow.mapPass.texture; + shadowMaterialHorizontal.uniforms.resolution.value = shadow.mapSize; + shadowMaterialHorizontal.uniforms.radius.value = shadow.radius; + renderer.setRenderTarget( shadow.map ); + renderer.clear(); + renderer.renderBufferDirect( camera, null, geometry, shadowMaterialHorizontal, fullScreenMesh, null ); } - toJSON( meta ) { - - const data = super.toJSON( meta ); + function getDepthMaterial( object, material, light, type ) { - data.object.color = this.color.getHex(); - data.object.intensity = this.intensity; + let result = null; - if ( this.groundColor !== undefined ) data.object.groundColor = this.groundColor.getHex(); + const customMaterial = ( light.isPointLight === true ) ? object.customDistanceMaterial : object.customDepthMaterial; - if ( this.distance !== undefined ) data.object.distance = this.distance; - if ( this.angle !== undefined ) data.object.angle = this.angle; - if ( this.decay !== undefined ) data.object.decay = this.decay; - if ( this.penumbra !== undefined ) data.object.penumbra = this.penumbra; + if ( customMaterial !== undefined ) { - if ( this.shadow !== undefined ) data.object.shadow = this.shadow.toJSON(); + result = customMaterial; - return data; + } else { - } + result = ( light.isPointLight === true ) ? _distanceMaterial : _depthMaterial; -} + if ( ( renderer.localClippingEnabled && material.clipShadows === true && Array.isArray( material.clippingPlanes ) && material.clippingPlanes.length !== 0 ) || + ( material.displacementMap && material.displacementScale !== 0 ) || + ( material.alphaMap && material.alphaTest > 0 ) || + ( material.map && material.alphaTest > 0 ) || + ( material.alphaToCoverage === true ) ) { -class HemisphereLight extends Light { + // in this case we need a unique material instance reflecting the + // appropriate state - constructor( skyColor, groundColor, intensity ) { + const keyA = result.uuid, keyB = material.uuid; - super( skyColor, intensity ); + let materialsForVariant = _materialCache[ keyA ]; - this.isHemisphereLight = true; + if ( materialsForVariant === undefined ) { - this.type = 'HemisphereLight'; + materialsForVariant = {}; + _materialCache[ keyA ] = materialsForVariant; - this.position.copy( Object3D.DEFAULT_UP ); - this.updateMatrix(); + } - this.groundColor = new Color( groundColor ); + let cachedMaterial = materialsForVariant[ keyB ]; - } + if ( cachedMaterial === undefined ) { - copy( source, recursive ) { + cachedMaterial = result.clone(); + materialsForVariant[ keyB ] = cachedMaterial; + material.addEventListener( 'dispose', onMaterialDispose ); - super.copy( source, recursive ); + } - this.groundColor.copy( source.groundColor ); + result = cachedMaterial; - return this; + } - } + } -} + result.visible = material.visible; + result.wireframe = material.wireframe; -const _projScreenMatrix$1 = /*@__PURE__*/ new Matrix4(); -const _lightPositionWorld$1 = /*@__PURE__*/ new Vector3(); -const _lookTarget$1 = /*@__PURE__*/ new Vector3(); + if ( type === VSMShadowMap ) { -class LightShadow { + result.side = ( material.shadowSide !== null ) ? material.shadowSide : material.side; - constructor( camera ) { + } else { - this.camera = camera; + result.side = ( material.shadowSide !== null ) ? material.shadowSide : shadowSide[ material.side ]; - this.bias = 0; - this.normalBias = 0; - this.radius = 1; - this.blurSamples = 8; + } - this.mapSize = new Vector2( 512, 512 ); + result.alphaMap = material.alphaMap; + result.alphaTest = ( material.alphaToCoverage === true ) ? 0.5 : material.alphaTest; // approximate alphaToCoverage by using a fixed alphaTest value + result.map = material.map; - this.map = null; - this.mapPass = null; - this.matrix = new Matrix4(); + result.clipShadows = material.clipShadows; + result.clippingPlanes = material.clippingPlanes; + result.clipIntersection = material.clipIntersection; - this.autoUpdate = true; - this.needsUpdate = false; + result.displacementMap = material.displacementMap; + result.displacementScale = material.displacementScale; + result.displacementBias = material.displacementBias; - this._frustum = new Frustum(); - this._frameExtents = new Vector2( 1, 1 ); + result.wireframeLinewidth = material.wireframeLinewidth; + result.linewidth = material.linewidth; - this._viewportCount = 1; + if ( light.isPointLight === true && result.isMeshDistanceMaterial === true ) { - this._viewports = [ + const materialProperties = renderer.properties.get( result ); + materialProperties.light = light; - new Vector4( 0, 0, 1, 1 ) + } - ]; + return result; } - getViewportCount() { - - return this._viewportCount; + function renderObject( object, camera, shadowCamera, light, type ) { - } + if ( object.visible === false ) return; - getFrustum() { + const visible = object.layers.test( camera.layers ); - return this._frustum; + if ( visible && ( object.isMesh || object.isLine || object.isPoints ) ) { - } + if ( ( object.castShadow || ( object.receiveShadow && type === VSMShadowMap ) ) && ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) ) { - updateMatrices( light ) { + object.modelViewMatrix.multiplyMatrices( shadowCamera.matrixWorldInverse, object.matrixWorld ); - const shadowCamera = this.camera; - const shadowMatrix = this.matrix; + const geometry = objects.update( object ); + const material = object.material; - _lightPositionWorld$1.setFromMatrixPosition( light.matrixWorld ); - shadowCamera.position.copy( _lightPositionWorld$1 ); + if ( Array.isArray( material ) ) { - _lookTarget$1.setFromMatrixPosition( light.target.matrixWorld ); - shadowCamera.lookAt( _lookTarget$1 ); - shadowCamera.updateMatrixWorld(); + const groups = geometry.groups; - _projScreenMatrix$1.multiplyMatrices( shadowCamera.projectionMatrix, shadowCamera.matrixWorldInverse ); - this._frustum.setFromProjectionMatrix( _projScreenMatrix$1 ); + for ( let k = 0, kl = groups.length; k < kl; k ++ ) { - shadowMatrix.set( - 0.5, 0.0, 0.0, 0.5, - 0.0, 0.5, 0.0, 0.5, - 0.0, 0.0, 0.5, 0.5, - 0.0, 0.0, 0.0, 1.0 - ); + const group = groups[ k ]; + const groupMaterial = material[ group.materialIndex ]; - shadowMatrix.multiply( _projScreenMatrix$1 ); + if ( groupMaterial && groupMaterial.visible ) { - } + const depthMaterial = getDepthMaterial( object, groupMaterial, light, type ); - getViewport( viewportIndex ) { + object.onBeforeShadow( renderer, object, camera, shadowCamera, geometry, depthMaterial, group ); - return this._viewports[ viewportIndex ]; + renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, group ); - } + object.onAfterShadow( renderer, object, camera, shadowCamera, geometry, depthMaterial, group ); - getFrameExtents() { + } - return this._frameExtents; + } - } + } else if ( material.visible ) { - dispose() { + const depthMaterial = getDepthMaterial( object, material, light, type ); - if ( this.map ) { + object.onBeforeShadow( renderer, object, camera, shadowCamera, geometry, depthMaterial, null ); - this.map.dispose(); + renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, null ); - } + object.onAfterShadow( renderer, object, camera, shadowCamera, geometry, depthMaterial, null ); - if ( this.mapPass ) { + } - this.mapPass.dispose(); + } } - } + const children = object.children; - copy( source ) { + for ( let i = 0, l = children.length; i < l; i ++ ) { - this.camera = source.camera.clone(); + renderObject( children[ i ], camera, shadowCamera, light, type ); - this.bias = source.bias; - this.radius = source.radius; + } - this.mapSize.copy( source.mapSize ); + } - return this; + function onMaterialDispose( event ) { - } + const material = event.target; - clone() { + material.removeEventListener( 'dispose', onMaterialDispose ); - return new this.constructor().copy( this ); + // make sure to remove the unique distance/depth materials used for shadow map rendering - } + for ( const id in _materialCache ) { - toJSON() { + const cache = _materialCache[ id ]; - const object = {}; + const uuid = event.target.uuid; - if ( this.bias !== 0 ) object.bias = this.bias; - if ( this.normalBias !== 0 ) object.normalBias = this.normalBias; - if ( this.radius !== 1 ) object.radius = this.radius; - if ( this.mapSize.x !== 512 || this.mapSize.y !== 512 ) object.mapSize = this.mapSize.toArray(); + if ( uuid in cache ) { - object.camera = this.camera.toJSON( false ).object; - delete object.camera.matrix; + const shadowMaterial = cache[ uuid ]; + shadowMaterial.dispose(); + delete cache[ uuid ]; + + } - return object; + } } } -class SpotLightShadow extends LightShadow { - - constructor() { +const reversedFuncs = { + [ NeverDepth ]: AlwaysDepth, + [ LessDepth ]: GreaterDepth, + [ EqualDepth ]: NotEqualDepth, + [ LessEqualDepth ]: GreaterEqualDepth, - super( new PerspectiveCamera( 50, 1, 0.5, 500 ) ); + [ AlwaysDepth ]: NeverDepth, + [ GreaterDepth ]: LessDepth, + [ NotEqualDepth ]: EqualDepth, + [ GreaterEqualDepth ]: LessEqualDepth, +}; - this.isSpotLightShadow = true; +function WebGLState( gl, extensions ) { - this.focus = 1; + function ColorBuffer() { - } + let locked = false; - updateMatrices( light ) { + const color = new Vector4(); + let currentColorMask = null; + const currentColorClear = new Vector4( 0, 0, 0, 0 ); - const camera = this.camera; + return { - const fov = RAD2DEG * 2 * light.angle * this.focus; - const aspect = this.mapSize.width / this.mapSize.height; - const far = light.distance || camera.far; + setMask: function ( colorMask ) { - if ( fov !== camera.fov || aspect !== camera.aspect || far !== camera.far ) { + if ( currentColorMask !== colorMask && ! locked ) { - camera.fov = fov; - camera.aspect = aspect; - camera.far = far; - camera.updateProjectionMatrix(); + gl.colorMask( colorMask, colorMask, colorMask, colorMask ); + currentColorMask = colorMask; - } + } - super.updateMatrices( light ); + }, - } + setLocked: function ( lock ) { - copy( source ) { + locked = lock; - super.copy( source ); + }, - this.focus = source.focus; + setClear: function ( r, g, b, a, premultipliedAlpha ) { - return this; + if ( premultipliedAlpha === true ) { - } + r *= a; g *= a; b *= a; -} + } -class SpotLight extends Light { + color.set( r, g, b, a ); - constructor( color, intensity, distance = 0, angle = Math.PI / 3, penumbra = 0, decay = 2 ) { + if ( currentColorClear.equals( color ) === false ) { - super( color, intensity ); + gl.clearColor( r, g, b, a ); + currentColorClear.copy( color ); - this.isSpotLight = true; + } - this.type = 'SpotLight'; + }, - this.position.copy( Object3D.DEFAULT_UP ); - this.updateMatrix(); + reset: function () { - this.target = new Object3D(); + locked = false; - this.distance = distance; - this.angle = angle; - this.penumbra = penumbra; - this.decay = decay; + currentColorMask = null; + currentColorClear.set( -1, 0, 0, 0 ); // set to invalid state - this.map = null; + } - this.shadow = new SpotLightShadow(); + }; } - get power() { - - // compute the light's luminous power (in lumens) from its intensity (in candela) - // by convention for a spotlight, luminous power (lm) = π * luminous intensity (cd) - return this.intensity * Math.PI; + function DepthBuffer() { - } + let locked = false; - set power( power ) { + let currentReversed = false; + let currentDepthMask = null; + let currentDepthFunc = null; + let currentDepthClear = null; - // set the light's intensity (in candela) from the desired luminous power (in lumens) - this.intensity = power / Math.PI; + return { - } + setReversed: function ( reversed ) { - dispose() { + if ( currentReversed !== reversed ) { - this.shadow.dispose(); + const ext = extensions.get( 'EXT_clip_control' ); - } + if ( reversed ) { - copy( source, recursive ) { + ext.clipControlEXT( ext.LOWER_LEFT_EXT, ext.ZERO_TO_ONE_EXT ); - super.copy( source, recursive ); + } else { - this.distance = source.distance; - this.angle = source.angle; - this.penumbra = source.penumbra; - this.decay = source.decay; + ext.clipControlEXT( ext.LOWER_LEFT_EXT, ext.NEGATIVE_ONE_TO_ONE_EXT ); - this.target = source.target.clone(); + } - this.shadow = source.shadow.clone(); + currentReversed = reversed; - return this; + const oldDepth = currentDepthClear; + currentDepthClear = null; + this.setClear( oldDepth ); - } + } -} + }, -const _projScreenMatrix = /*@__PURE__*/ new Matrix4(); -const _lightPositionWorld = /*@__PURE__*/ new Vector3(); -const _lookTarget = /*@__PURE__*/ new Vector3(); + getReversed: function () { -class PointLightShadow extends LightShadow { + return currentReversed; - constructor() { + }, - super( new PerspectiveCamera( 90, 1, 0.5, 500 ) ); + setTest: function ( depthTest ) { - this.isPointLightShadow = true; + if ( depthTest ) { - this._frameExtents = new Vector2( 4, 2 ); + enable( gl.DEPTH_TEST ); - this._viewportCount = 6; + } else { - this._viewports = [ - // These viewports map a cube-map onto a 2D texture with the - // following orientation: - // - // xzXZ - // y Y - // - // X - Positive x direction - // x - Negative x direction - // Y - Positive y direction - // y - Negative y direction - // Z - Positive z direction - // z - Negative z direction + disable( gl.DEPTH_TEST ); - // positive X - new Vector4( 2, 1, 1, 1 ), - // negative X - new Vector4( 0, 1, 1, 1 ), - // positive Z - new Vector4( 3, 1, 1, 1 ), - // negative Z - new Vector4( 1, 1, 1, 1 ), - // positive Y - new Vector4( 3, 0, 1, 1 ), - // negative Y - new Vector4( 1, 0, 1, 1 ) - ]; + } - this._cubeDirections = [ - new Vector3( 1, 0, 0 ), new Vector3( - 1, 0, 0 ), new Vector3( 0, 0, 1 ), - new Vector3( 0, 0, - 1 ), new Vector3( 0, 1, 0 ), new Vector3( 0, - 1, 0 ) - ]; + }, - this._cubeUps = [ - new Vector3( 0, 1, 0 ), new Vector3( 0, 1, 0 ), new Vector3( 0, 1, 0 ), - new Vector3( 0, 1, 0 ), new Vector3( 0, 0, 1 ), new Vector3( 0, 0, - 1 ) - ]; + setMask: function ( depthMask ) { - } + if ( currentDepthMask !== depthMask && ! locked ) { - updateMatrices( light, viewportIndex = 0 ) { + gl.depthMask( depthMask ); + currentDepthMask = depthMask; - const camera = this.camera; - const shadowMatrix = this.matrix; + } - const far = light.distance || camera.far; + }, - if ( far !== camera.far ) { + setFunc: function ( depthFunc ) { - camera.far = far; - camera.updateProjectionMatrix(); + if ( currentReversed ) depthFunc = reversedFuncs[ depthFunc ]; - } + if ( currentDepthFunc !== depthFunc ) { - _lightPositionWorld.setFromMatrixPosition( light.matrixWorld ); - camera.position.copy( _lightPositionWorld ); + switch ( depthFunc ) { - _lookTarget.copy( camera.position ); - _lookTarget.add( this._cubeDirections[ viewportIndex ] ); - camera.up.copy( this._cubeUps[ viewportIndex ] ); - camera.lookAt( _lookTarget ); - camera.updateMatrixWorld(); + case NeverDepth: - shadowMatrix.makeTranslation( - _lightPositionWorld.x, - _lightPositionWorld.y, - _lightPositionWorld.z ); + gl.depthFunc( gl.NEVER ); + break; - _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); - this._frustum.setFromProjectionMatrix( _projScreenMatrix ); + case AlwaysDepth: - } + gl.depthFunc( gl.ALWAYS ); + break; -} + case LessDepth: -class PointLight extends Light { + gl.depthFunc( gl.LESS ); + break; - constructor( color, intensity, distance = 0, decay = 2 ) { + case LessEqualDepth: - super( color, intensity ); + gl.depthFunc( gl.LEQUAL ); + break; - this.isPointLight = true; + case EqualDepth: - this.type = 'PointLight'; + gl.depthFunc( gl.EQUAL ); + break; - this.distance = distance; - this.decay = decay; + case GreaterEqualDepth: - this.shadow = new PointLightShadow(); + gl.depthFunc( gl.GEQUAL ); + break; - } + case GreaterDepth: - get power() { + gl.depthFunc( gl.GREATER ); + break; - // compute the light's luminous power (in lumens) from its intensity (in candela) - // for an isotropic light source, luminous power (lm) = 4 π luminous intensity (cd) - return this.intensity * 4 * Math.PI; + case NotEqualDepth: - } + gl.depthFunc( gl.NOTEQUAL ); + break; - set power( power ) { + default: - // set the light's intensity (in candela) from the desired luminous power (in lumens) - this.intensity = power / ( 4 * Math.PI ); + gl.depthFunc( gl.LEQUAL ); - } + } - dispose() { + currentDepthFunc = depthFunc; - this.shadow.dispose(); + } - } + }, - copy( source, recursive ) { + setLocked: function ( lock ) { - super.copy( source, recursive ); + locked = lock; - this.distance = source.distance; - this.decay = source.decay; + }, - this.shadow = source.shadow.clone(); + setClear: function ( depth ) { - return this; + if ( currentDepthClear !== depth ) { - } + if ( currentReversed ) { -} + depth = 1 - depth; -class DirectionalLightShadow extends LightShadow { + } - constructor() { + gl.clearDepth( depth ); + currentDepthClear = depth; - super( new OrthographicCamera( - 5, 5, 5, - 5, 0.5, 500 ) ); + } - this.isDirectionalLightShadow = true; + }, - } + reset: function () { -} + locked = false; -class DirectionalLight extends Light { + currentDepthMask = null; + currentDepthFunc = null; + currentDepthClear = null; + currentReversed = false; - constructor( color, intensity ) { + } - super( color, intensity ); + }; - this.isDirectionalLight = true; + } - this.type = 'DirectionalLight'; + function StencilBuffer() { - this.position.copy( Object3D.DEFAULT_UP ); - this.updateMatrix(); + let locked = false; - this.target = new Object3D(); + let currentStencilMask = null; + let currentStencilFunc = null; + let currentStencilRef = null; + let currentStencilFuncMask = null; + let currentStencilFail = null; + let currentStencilZFail = null; + let currentStencilZPass = null; + let currentStencilClear = null; - this.shadow = new DirectionalLightShadow(); + return { - } + setTest: function ( stencilTest ) { - dispose() { + if ( ! locked ) { - this.shadow.dispose(); + if ( stencilTest ) { - } + enable( gl.STENCIL_TEST ); - copy( source ) { + } else { - super.copy( source ); + disable( gl.STENCIL_TEST ); - this.target = source.target.clone(); - this.shadow = source.shadow.clone(); + } - return this; + } - } + }, -} + setMask: function ( stencilMask ) { -class AmbientLight extends Light { + if ( currentStencilMask !== stencilMask && ! locked ) { - constructor( color, intensity ) { + gl.stencilMask( stencilMask ); + currentStencilMask = stencilMask; - super( color, intensity ); + } - this.isAmbientLight = true; + }, - this.type = 'AmbientLight'; + setFunc: function ( stencilFunc, stencilRef, stencilMask ) { - } + if ( currentStencilFunc !== stencilFunc || + currentStencilRef !== stencilRef || + currentStencilFuncMask !== stencilMask ) { -} + gl.stencilFunc( stencilFunc, stencilRef, stencilMask ); -class RectAreaLight extends Light { + currentStencilFunc = stencilFunc; + currentStencilRef = stencilRef; + currentStencilFuncMask = stencilMask; - constructor( color, intensity, width = 10, height = 10 ) { + } - super( color, intensity ); + }, - this.isRectAreaLight = true; + setOp: function ( stencilFail, stencilZFail, stencilZPass ) { - this.type = 'RectAreaLight'; + if ( currentStencilFail !== stencilFail || + currentStencilZFail !== stencilZFail || + currentStencilZPass !== stencilZPass ) { - this.width = width; - this.height = height; + gl.stencilOp( stencilFail, stencilZFail, stencilZPass ); - } + currentStencilFail = stencilFail; + currentStencilZFail = stencilZFail; + currentStencilZPass = stencilZPass; - get power() { + } - // compute the light's luminous power (in lumens) from its intensity (in nits) - return this.intensity * this.width * this.height * Math.PI; + }, - } + setLocked: function ( lock ) { - set power( power ) { + locked = lock; - // set the light's intensity (in nits) from the desired luminous power (in lumens) - this.intensity = power / ( this.width * this.height * Math.PI ); + }, - } + setClear: function ( stencil ) { - copy( source ) { + if ( currentStencilClear !== stencil ) { - super.copy( source ); + gl.clearStencil( stencil ); + currentStencilClear = stencil; - this.width = source.width; - this.height = source.height; + } - return this; + }, - } + reset: function () { - toJSON( meta ) { + locked = false; - const data = super.toJSON( meta ); + currentStencilMask = null; + currentStencilFunc = null; + currentStencilRef = null; + currentStencilFuncMask = null; + currentStencilFail = null; + currentStencilZFail = null; + currentStencilZPass = null; + currentStencilClear = null; - data.object.width = this.width; - data.object.height = this.height; + } - return data; + }; } -} + // -/** - * Primary reference: - * https://graphics.stanford.edu/papers/envmap/envmap.pdf - * - * Secondary reference: - * https://www.ppsloan.org/publications/StupidSH36.pdf - */ + const colorBuffer = new ColorBuffer(); + const depthBuffer = new DepthBuffer(); + const stencilBuffer = new StencilBuffer(); -// 3-band SH defined by 9 coefficients + const uboBindings = new WeakMap(); + const uboProgramMap = new WeakMap(); -class SphericalHarmonics3 { + let enabledCapabilities = {}; - constructor() { + let currentBoundFramebuffers = {}; + let currentDrawbuffers = new WeakMap(); + let defaultDrawbuffers = []; - this.isSphericalHarmonics3 = true; + let currentProgram = null; - this.coefficients = []; + let currentBlendingEnabled = false; + let currentBlending = null; + let currentBlendEquation = null; + let currentBlendSrc = null; + let currentBlendDst = null; + let currentBlendEquationAlpha = null; + let currentBlendSrcAlpha = null; + let currentBlendDstAlpha = null; + let currentBlendColor = new Color( 0, 0, 0 ); + let currentBlendAlpha = 0; + let currentPremultipledAlpha = false; - for ( let i = 0; i < 9; i ++ ) { + let currentFlipSided = null; + let currentCullFace = null; - this.coefficients.push( new Vector3() ); + let currentLineWidth = null; - } + let currentPolygonOffsetFactor = null; + let currentPolygonOffsetUnits = null; - } + const maxTextures = gl.getParameter( gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS ); - set( coefficients ) { + let lineWidthAvailable = false; + let version = 0; + const glVersion = gl.getParameter( gl.VERSION ); - for ( let i = 0; i < 9; i ++ ) { + if ( glVersion.indexOf( 'WebGL' ) !== -1 ) { - this.coefficients[ i ].copy( coefficients[ i ] ); + version = parseFloat( /^WebGL (\d)/.exec( glVersion )[ 1 ] ); + lineWidthAvailable = ( version >= 1.0 ); - } + } else if ( glVersion.indexOf( 'OpenGL ES' ) !== -1 ) { - return this; + version = parseFloat( /^OpenGL ES (\d)/.exec( glVersion )[ 1 ] ); + lineWidthAvailable = ( version >= 2.0 ); } - zero() { + let currentTextureSlot = null; + let currentBoundTextures = {}; - for ( let i = 0; i < 9; i ++ ) { + const scissorParam = gl.getParameter( gl.SCISSOR_BOX ); + const viewportParam = gl.getParameter( gl.VIEWPORT ); - this.coefficients[ i ].set( 0, 0, 0 ); + const currentScissor = new Vector4().fromArray( scissorParam ); + const currentViewport = new Vector4().fromArray( viewportParam ); - } + function createTexture( type, target, count, dimensions ) { - return this; + const data = new Uint8Array( 4 ); // 4 is required to match default unpack alignment of 4. + const texture = gl.createTexture(); - } + gl.bindTexture( type, texture ); + gl.texParameteri( type, gl.TEXTURE_MIN_FILTER, gl.NEAREST ); + gl.texParameteri( type, gl.TEXTURE_MAG_FILTER, gl.NEAREST ); - // get the radiance in the direction of the normal - // target is a Vector3 - getAt( normal, target ) { + for ( let i = 0; i < count; i ++ ) { - // normal is assumed to be unit length + if ( type === gl.TEXTURE_3D || type === gl.TEXTURE_2D_ARRAY ) { - const x = normal.x, y = normal.y, z = normal.z; + gl.texImage3D( target, 0, gl.RGBA, 1, 1, dimensions, 0, gl.RGBA, gl.UNSIGNED_BYTE, data ); - const coeff = this.coefficients; + } else { - // band 0 - target.copy( coeff[ 0 ] ).multiplyScalar( 0.282095 ); + gl.texImage2D( target + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, data ); - // band 1 - target.addScaledVector( coeff[ 1 ], 0.488603 * y ); - target.addScaledVector( coeff[ 2 ], 0.488603 * z ); - target.addScaledVector( coeff[ 3 ], 0.488603 * x ); + } - // band 2 - target.addScaledVector( coeff[ 4 ], 1.092548 * ( x * y ) ); - target.addScaledVector( coeff[ 5 ], 1.092548 * ( y * z ) ); - target.addScaledVector( coeff[ 6 ], 0.315392 * ( 3.0 * z * z - 1.0 ) ); - target.addScaledVector( coeff[ 7 ], 1.092548 * ( x * z ) ); - target.addScaledVector( coeff[ 8 ], 0.546274 * ( x * x - y * y ) ); + } - return target; + return texture; } - // get the irradiance (radiance convolved with cosine lobe) in the direction of the normal - // target is a Vector3 - // https://graphics.stanford.edu/papers/envmap/envmap.pdf - getIrradianceAt( normal, target ) { - - // normal is assumed to be unit length - - const x = normal.x, y = normal.y, z = normal.z; - - const coeff = this.coefficients; - - // band 0 - target.copy( coeff[ 0 ] ).multiplyScalar( 0.886227 ); // π * 0.282095 - - // band 1 - target.addScaledVector( coeff[ 1 ], 2.0 * 0.511664 * y ); // ( 2 * π / 3 ) * 0.488603 - target.addScaledVector( coeff[ 2 ], 2.0 * 0.511664 * z ); - target.addScaledVector( coeff[ 3 ], 2.0 * 0.511664 * x ); - - // band 2 - target.addScaledVector( coeff[ 4 ], 2.0 * 0.429043 * x * y ); // ( π / 4 ) * 1.092548 - target.addScaledVector( coeff[ 5 ], 2.0 * 0.429043 * y * z ); - target.addScaledVector( coeff[ 6 ], 0.743125 * z * z - 0.247708 ); // ( π / 4 ) * 0.315392 * 3 - target.addScaledVector( coeff[ 7 ], 2.0 * 0.429043 * x * z ); - target.addScaledVector( coeff[ 8 ], 0.429043 * ( x * x - y * y ) ); // ( π / 4 ) * 0.546274 - - return target; - - } + const emptyTextures = {}; + emptyTextures[ gl.TEXTURE_2D ] = createTexture( gl.TEXTURE_2D, gl.TEXTURE_2D, 1 ); + emptyTextures[ gl.TEXTURE_CUBE_MAP ] = createTexture( gl.TEXTURE_CUBE_MAP, gl.TEXTURE_CUBE_MAP_POSITIVE_X, 6 ); + emptyTextures[ gl.TEXTURE_2D_ARRAY ] = createTexture( gl.TEXTURE_2D_ARRAY, gl.TEXTURE_2D_ARRAY, 1, 1 ); + emptyTextures[ gl.TEXTURE_3D ] = createTexture( gl.TEXTURE_3D, gl.TEXTURE_3D, 1, 1 ); - add( sh ) { + // init - for ( let i = 0; i < 9; i ++ ) { + colorBuffer.setClear( 0, 0, 0, 1 ); + depthBuffer.setClear( 1 ); + stencilBuffer.setClear( 0 ); - this.coefficients[ i ].add( sh.coefficients[ i ] ); + enable( gl.DEPTH_TEST ); + depthBuffer.setFunc( LessEqualDepth ); - } + setFlipSided( false ); + setCullFace( CullFaceBack ); + enable( gl.CULL_FACE ); - return this; + setBlending( NoBlending ); - } + // - addScaledSH( sh, s ) { + function enable( id ) { - for ( let i = 0; i < 9; i ++ ) { + if ( enabledCapabilities[ id ] !== true ) { - this.coefficients[ i ].addScaledVector( sh.coefficients[ i ], s ); + gl.enable( id ); + enabledCapabilities[ id ] = true; } - return this; - } - scale( s ) { + function disable( id ) { - for ( let i = 0; i < 9; i ++ ) { + if ( enabledCapabilities[ id ] !== false ) { - this.coefficients[ i ].multiplyScalar( s ); + gl.disable( id ); + enabledCapabilities[ id ] = false; } - return this; - } - lerp( sh, alpha ) { + function bindFramebuffer( target, framebuffer ) { - for ( let i = 0; i < 9; i ++ ) { + if ( currentBoundFramebuffers[ target ] !== framebuffer ) { - this.coefficients[ i ].lerp( sh.coefficients[ i ], alpha ); + gl.bindFramebuffer( target, framebuffer ); - } + currentBoundFramebuffers[ target ] = framebuffer; - return this; + // gl.DRAW_FRAMEBUFFER is equivalent to gl.FRAMEBUFFER - } + if ( target === gl.DRAW_FRAMEBUFFER ) { - equals( sh ) { + currentBoundFramebuffers[ gl.FRAMEBUFFER ] = framebuffer; - for ( let i = 0; i < 9; i ++ ) { + } - if ( ! this.coefficients[ i ].equals( sh.coefficients[ i ] ) ) { + if ( target === gl.FRAMEBUFFER ) { - return false; + currentBoundFramebuffers[ gl.DRAW_FRAMEBUFFER ] = framebuffer; } + return true; + } - return true; + return false; } - copy( sh ) { - - return this.set( sh.coefficients ); + function drawBuffers( renderTarget, framebuffer ) { - } + let drawBuffers = defaultDrawbuffers; - clone() { + let needsUpdate = false; - return new this.constructor().copy( this ); + if ( renderTarget ) { - } + drawBuffers = currentDrawbuffers.get( framebuffer ); - fromArray( array, offset = 0 ) { + if ( drawBuffers === undefined ) { - const coefficients = this.coefficients; + drawBuffers = []; + currentDrawbuffers.set( framebuffer, drawBuffers ); - for ( let i = 0; i < 9; i ++ ) { + } - coefficients[ i ].fromArray( array, offset + ( i * 3 ) ); + const textures = renderTarget.textures; - } + if ( drawBuffers.length !== textures.length || drawBuffers[ 0 ] !== gl.COLOR_ATTACHMENT0 ) { - return this; + for ( let i = 0, il = textures.length; i < il; i ++ ) { - } + drawBuffers[ i ] = gl.COLOR_ATTACHMENT0 + i; - toArray( array = [], offset = 0 ) { + } - const coefficients = this.coefficients; + drawBuffers.length = textures.length; - for ( let i = 0; i < 9; i ++ ) { + needsUpdate = true; - coefficients[ i ].toArray( array, offset + ( i * 3 ) ); + } - } + } else { - return array; + if ( drawBuffers[ 0 ] !== gl.BACK ) { - } + drawBuffers[ 0 ] = gl.BACK; - // evaluate the basis functions - // shBasis is an Array[ 9 ] - static getBasisAt( normal, shBasis ) { + needsUpdate = true; - // normal is assumed to be unit length + } - const x = normal.x, y = normal.y, z = normal.z; + } - // band 0 - shBasis[ 0 ] = 0.282095; + if ( needsUpdate ) { - // band 1 - shBasis[ 1 ] = 0.488603 * y; - shBasis[ 2 ] = 0.488603 * z; - shBasis[ 3 ] = 0.488603 * x; + gl.drawBuffers( drawBuffers ); - // band 2 - shBasis[ 4 ] = 1.092548 * x * y; - shBasis[ 5 ] = 1.092548 * y * z; - shBasis[ 6 ] = 0.315392 * ( 3 * z * z - 1 ); - shBasis[ 7 ] = 1.092548 * x * z; - shBasis[ 8 ] = 0.546274 * ( x * x - y * y ); + } } -} + function useProgram( program ) { + + if ( currentProgram !== program ) { -class LightProbe extends Light { + gl.useProgram( program ); - constructor( sh = new SphericalHarmonics3(), intensity = 1 ) { + currentProgram = program; - super( undefined, intensity ); + return true; - this.isLightProbe = true; + } - this.sh = sh; + return false; } - copy( source ) { + const equationToGL = { + [ AddEquation ]: gl.FUNC_ADD, + [ SubtractEquation ]: gl.FUNC_SUBTRACT, + [ ReverseSubtractEquation ]: gl.FUNC_REVERSE_SUBTRACT + }; - super.copy( source ); + equationToGL[ MinEquation ] = gl.MIN; + equationToGL[ MaxEquation ] = gl.MAX; - this.sh.copy( source.sh ); + const factorToGL = { + [ ZeroFactor ]: gl.ZERO, + [ OneFactor ]: gl.ONE, + [ SrcColorFactor ]: gl.SRC_COLOR, + [ SrcAlphaFactor ]: gl.SRC_ALPHA, + [ SrcAlphaSaturateFactor ]: gl.SRC_ALPHA_SATURATE, + [ DstColorFactor ]: gl.DST_COLOR, + [ DstAlphaFactor ]: gl.DST_ALPHA, + [ OneMinusSrcColorFactor ]: gl.ONE_MINUS_SRC_COLOR, + [ OneMinusSrcAlphaFactor ]: gl.ONE_MINUS_SRC_ALPHA, + [ OneMinusDstColorFactor ]: gl.ONE_MINUS_DST_COLOR, + [ OneMinusDstAlphaFactor ]: gl.ONE_MINUS_DST_ALPHA, + [ ConstantColorFactor ]: gl.CONSTANT_COLOR, + [ OneMinusConstantColorFactor ]: gl.ONE_MINUS_CONSTANT_COLOR, + [ ConstantAlphaFactor ]: gl.CONSTANT_ALPHA, + [ OneMinusConstantAlphaFactor ]: gl.ONE_MINUS_CONSTANT_ALPHA + }; - return this; + function setBlending( blending, blendEquation, blendSrc, blendDst, blendEquationAlpha, blendSrcAlpha, blendDstAlpha, blendColor, blendAlpha, premultipliedAlpha ) { - } + if ( blending === NoBlending ) { - fromJSON( json ) { + if ( currentBlendingEnabled === true ) { - this.intensity = json.intensity; // TODO: Move this bit to Light.fromJSON(); - this.sh.fromArray( json.sh ); + disable( gl.BLEND ); + currentBlendingEnabled = false; - return this; + } - } + return; - toJSON( meta ) { + } - const data = super.toJSON( meta ); + if ( currentBlendingEnabled === false ) { - data.object.sh = this.sh.toArray(); + enable( gl.BLEND ); + currentBlendingEnabled = true; - return data; + } - } + if ( blending !== CustomBlending ) { -} + if ( blending !== currentBlending || premultipliedAlpha !== currentPremultipledAlpha ) { -class MaterialLoader extends Loader { + if ( currentBlendEquation !== AddEquation || currentBlendEquationAlpha !== AddEquation ) { - constructor( manager ) { + gl.blendEquation( gl.FUNC_ADD ); - super( manager ); - this.textures = {}; + currentBlendEquation = AddEquation; + currentBlendEquationAlpha = AddEquation; - } + } - load( url, onLoad, onProgress, onError ) { + if ( premultipliedAlpha ) { - const scope = this; + switch ( blending ) { - const loader = new FileLoader( scope.manager ); - loader.setPath( scope.path ); - loader.setRequestHeader( scope.requestHeader ); - loader.setWithCredentials( scope.withCredentials ); - loader.load( url, function ( text ) { + case NormalBlending: + gl.blendFuncSeparate( gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA ); + break; - try { + case AdditiveBlending: + gl.blendFunc( gl.ONE, gl.ONE ); + break; - onLoad( scope.parse( JSON.parse( text ) ) ); + case SubtractiveBlending: + gl.blendFuncSeparate( gl.ZERO, gl.ONE_MINUS_SRC_COLOR, gl.ZERO, gl.ONE ); + break; - } catch ( e ) { + case MultiplyBlending: + gl.blendFuncSeparate( gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA, gl.ZERO, gl.ONE ); + break; - if ( onError ) { + default: + console.error( 'THREE.WebGLState: Invalid blending: ', blending ); + break; - onError( e ); + } } else { - console.error( e ); - - } + switch ( blending ) { - scope.manager.itemError( url ); + case NormalBlending: + gl.blendFuncSeparate( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA ); + break; - } + case AdditiveBlending: + gl.blendFuncSeparate( gl.SRC_ALPHA, gl.ONE, gl.ONE, gl.ONE ); + break; - }, onProgress, onError ); + case SubtractiveBlending: + console.error( 'THREE.WebGLState: SubtractiveBlending requires material.premultipliedAlpha = true' ); + break; - } + case MultiplyBlending: + console.error( 'THREE.WebGLState: MultiplyBlending requires material.premultipliedAlpha = true' ); + break; - parse( json ) { + default: + console.error( 'THREE.WebGLState: Invalid blending: ', blending ); + break; - const textures = this.textures; + } - function getTexture( name ) { + } - if ( textures[ name ] === undefined ) { + currentBlendSrc = null; + currentBlendDst = null; + currentBlendSrcAlpha = null; + currentBlendDstAlpha = null; + currentBlendColor.set( 0, 0, 0 ); + currentBlendAlpha = 0; - console.warn( 'THREE.MaterialLoader: Undefined texture', name ); + currentBlending = blending; + currentPremultipledAlpha = premultipliedAlpha; } - return textures[ name ]; + return; } - const material = MaterialLoader.createMaterialFromType( json.type ); - - if ( json.uuid !== undefined ) material.uuid = json.uuid; - if ( json.name !== undefined ) material.name = json.name; - if ( json.color !== undefined && material.color !== undefined ) material.color.setHex( json.color ); - if ( json.roughness !== undefined ) material.roughness = json.roughness; - if ( json.metalness !== undefined ) material.metalness = json.metalness; - if ( json.sheen !== undefined ) material.sheen = json.sheen; - if ( json.sheenColor !== undefined ) material.sheenColor = new Color().setHex( json.sheenColor ); - if ( json.sheenRoughness !== undefined ) material.sheenRoughness = json.sheenRoughness; - if ( json.emissive !== undefined && material.emissive !== undefined ) material.emissive.setHex( json.emissive ); - if ( json.specular !== undefined && material.specular !== undefined ) material.specular.setHex( json.specular ); - if ( json.specularIntensity !== undefined ) material.specularIntensity = json.specularIntensity; - if ( json.specularColor !== undefined && material.specularColor !== undefined ) material.specularColor.setHex( json.specularColor ); - if ( json.shininess !== undefined ) material.shininess = json.shininess; - if ( json.clearcoat !== undefined ) material.clearcoat = json.clearcoat; - if ( json.clearcoatRoughness !== undefined ) material.clearcoatRoughness = json.clearcoatRoughness; - if ( json.iridescence !== undefined ) material.iridescence = json.iridescence; - if ( json.iridescenceIOR !== undefined ) material.iridescenceIOR = json.iridescenceIOR; - if ( json.iridescenceThicknessRange !== undefined ) material.iridescenceThicknessRange = json.iridescenceThicknessRange; - if ( json.transmission !== undefined ) material.transmission = json.transmission; - if ( json.thickness !== undefined ) material.thickness = json.thickness; - if ( json.attenuationDistance !== undefined ) material.attenuationDistance = json.attenuationDistance; - if ( json.attenuationColor !== undefined && material.attenuationColor !== undefined ) material.attenuationColor.setHex( json.attenuationColor ); - if ( json.anisotropy !== undefined ) material.anisotropy = json.anisotropy; - if ( json.anisotropyRotation !== undefined ) material.anisotropyRotation = json.anisotropyRotation; - if ( json.fog !== undefined ) material.fog = json.fog; - if ( json.flatShading !== undefined ) material.flatShading = json.flatShading; - if ( json.blending !== undefined ) material.blending = json.blending; - if ( json.combine !== undefined ) material.combine = json.combine; - if ( json.side !== undefined ) material.side = json.side; - if ( json.shadowSide !== undefined ) material.shadowSide = json.shadowSide; - if ( json.opacity !== undefined ) material.opacity = json.opacity; - if ( json.transparent !== undefined ) material.transparent = json.transparent; - if ( json.alphaTest !== undefined ) material.alphaTest = json.alphaTest; - if ( json.depthTest !== undefined ) material.depthTest = json.depthTest; - if ( json.depthWrite !== undefined ) material.depthWrite = json.depthWrite; - if ( json.colorWrite !== undefined ) material.colorWrite = json.colorWrite; - - if ( json.stencilWrite !== undefined ) material.stencilWrite = json.stencilWrite; - if ( json.stencilWriteMask !== undefined ) material.stencilWriteMask = json.stencilWriteMask; - if ( json.stencilFunc !== undefined ) material.stencilFunc = json.stencilFunc; - if ( json.stencilRef !== undefined ) material.stencilRef = json.stencilRef; - if ( json.stencilFuncMask !== undefined ) material.stencilFuncMask = json.stencilFuncMask; - if ( json.stencilFail !== undefined ) material.stencilFail = json.stencilFail; - if ( json.stencilZFail !== undefined ) material.stencilZFail = json.stencilZFail; - if ( json.stencilZPass !== undefined ) material.stencilZPass = json.stencilZPass; - - if ( json.wireframe !== undefined ) material.wireframe = json.wireframe; - if ( json.wireframeLinewidth !== undefined ) material.wireframeLinewidth = json.wireframeLinewidth; - if ( json.wireframeLinecap !== undefined ) material.wireframeLinecap = json.wireframeLinecap; - if ( json.wireframeLinejoin !== undefined ) material.wireframeLinejoin = json.wireframeLinejoin; - - if ( json.rotation !== undefined ) material.rotation = json.rotation; - - if ( json.linewidth !== 1 ) material.linewidth = json.linewidth; - if ( json.dashSize !== undefined ) material.dashSize = json.dashSize; - if ( json.gapSize !== undefined ) material.gapSize = json.gapSize; - if ( json.scale !== undefined ) material.scale = json.scale; + // custom blending - if ( json.polygonOffset !== undefined ) material.polygonOffset = json.polygonOffset; - if ( json.polygonOffsetFactor !== undefined ) material.polygonOffsetFactor = json.polygonOffsetFactor; - if ( json.polygonOffsetUnits !== undefined ) material.polygonOffsetUnits = json.polygonOffsetUnits; + blendEquationAlpha = blendEquationAlpha || blendEquation; + blendSrcAlpha = blendSrcAlpha || blendSrc; + blendDstAlpha = blendDstAlpha || blendDst; - if ( json.dithering !== undefined ) material.dithering = json.dithering; + if ( blendEquation !== currentBlendEquation || blendEquationAlpha !== currentBlendEquationAlpha ) { - if ( json.alphaToCoverage !== undefined ) material.alphaToCoverage = json.alphaToCoverage; - if ( json.premultipliedAlpha !== undefined ) material.premultipliedAlpha = json.premultipliedAlpha; - if ( json.forceSinglePass !== undefined ) material.forceSinglePass = json.forceSinglePass; + gl.blendEquationSeparate( equationToGL[ blendEquation ], equationToGL[ blendEquationAlpha ] ); - if ( json.visible !== undefined ) material.visible = json.visible; + currentBlendEquation = blendEquation; + currentBlendEquationAlpha = blendEquationAlpha; - if ( json.toneMapped !== undefined ) material.toneMapped = json.toneMapped; + } - if ( json.userData !== undefined ) material.userData = json.userData; + if ( blendSrc !== currentBlendSrc || blendDst !== currentBlendDst || blendSrcAlpha !== currentBlendSrcAlpha || blendDstAlpha !== currentBlendDstAlpha ) { - if ( json.vertexColors !== undefined ) { + gl.blendFuncSeparate( factorToGL[ blendSrc ], factorToGL[ blendDst ], factorToGL[ blendSrcAlpha ], factorToGL[ blendDstAlpha ] ); - if ( typeof json.vertexColors === 'number' ) { + currentBlendSrc = blendSrc; + currentBlendDst = blendDst; + currentBlendSrcAlpha = blendSrcAlpha; + currentBlendDstAlpha = blendDstAlpha; - material.vertexColors = ( json.vertexColors > 0 ) ? true : false; + } - } else { + if ( blendColor.equals( currentBlendColor ) === false || blendAlpha !== currentBlendAlpha ) { - material.vertexColors = json.vertexColors; + gl.blendColor( blendColor.r, blendColor.g, blendColor.b, blendAlpha ); - } + currentBlendColor.copy( blendColor ); + currentBlendAlpha = blendAlpha; } - // Shader Material + currentBlending = blending; + currentPremultipledAlpha = false; - if ( json.uniforms !== undefined ) { + } - for ( const name in json.uniforms ) { + function setMaterial( material, frontFaceCW ) { - const uniform = json.uniforms[ name ]; + material.side === DoubleSide + ? disable( gl.CULL_FACE ) + : enable( gl.CULL_FACE ); - material.uniforms[ name ] = {}; + let flipSided = ( material.side === BackSide ); + if ( frontFaceCW ) flipSided = ! flipSided; - switch ( uniform.type ) { + setFlipSided( flipSided ); - case 't': - material.uniforms[ name ].value = getTexture( uniform.value ); - break; + ( material.blending === NormalBlending && material.transparent === false ) + ? setBlending( NoBlending ) + : setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst, material.blendEquationAlpha, material.blendSrcAlpha, material.blendDstAlpha, material.blendColor, material.blendAlpha, material.premultipliedAlpha ); - case 'c': - material.uniforms[ name ].value = new Color().setHex( uniform.value ); - break; + depthBuffer.setFunc( material.depthFunc ); + depthBuffer.setTest( material.depthTest ); + depthBuffer.setMask( material.depthWrite ); + colorBuffer.setMask( material.colorWrite ); - case 'v2': - material.uniforms[ name ].value = new Vector2().fromArray( uniform.value ); - break; + const stencilWrite = material.stencilWrite; + stencilBuffer.setTest( stencilWrite ); + if ( stencilWrite ) { - case 'v3': - material.uniforms[ name ].value = new Vector3().fromArray( uniform.value ); - break; + stencilBuffer.setMask( material.stencilWriteMask ); + stencilBuffer.setFunc( material.stencilFunc, material.stencilRef, material.stencilFuncMask ); + stencilBuffer.setOp( material.stencilFail, material.stencilZFail, material.stencilZPass ); - case 'v4': - material.uniforms[ name ].value = new Vector4().fromArray( uniform.value ); - break; + } - case 'm3': - material.uniforms[ name ].value = new Matrix3().fromArray( uniform.value ); - break; + setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits ); - case 'm4': - material.uniforms[ name ].value = new Matrix4().fromArray( uniform.value ); - break; + material.alphaToCoverage === true + ? enable( gl.SAMPLE_ALPHA_TO_COVERAGE ) + : disable( gl.SAMPLE_ALPHA_TO_COVERAGE ); - default: - material.uniforms[ name ].value = uniform.value; + } - } + // - } + function setFlipSided( flipSided ) { - } + if ( currentFlipSided !== flipSided ) { - if ( json.defines !== undefined ) material.defines = json.defines; - if ( json.vertexShader !== undefined ) material.vertexShader = json.vertexShader; - if ( json.fragmentShader !== undefined ) material.fragmentShader = json.fragmentShader; - if ( json.glslVersion !== undefined ) material.glslVersion = json.glslVersion; + if ( flipSided ) { - if ( json.extensions !== undefined ) { + gl.frontFace( gl.CW ); - for ( const key in json.extensions ) { + } else { - material.extensions[ key ] = json.extensions[ key ]; + gl.frontFace( gl.CCW ); } + currentFlipSided = flipSided; + } - if ( json.lights !== undefined ) material.lights = json.lights; - if ( json.clipping !== undefined ) material.clipping = json.clipping; + } - // for PointsMaterial + function setCullFace( cullFace ) { - if ( json.size !== undefined ) material.size = json.size; - if ( json.sizeAttenuation !== undefined ) material.sizeAttenuation = json.sizeAttenuation; + if ( cullFace !== CullFaceNone ) { - // maps + enable( gl.CULL_FACE ); - if ( json.map !== undefined ) material.map = getTexture( json.map ); - if ( json.matcap !== undefined ) material.matcap = getTexture( json.matcap ); + if ( cullFace !== currentCullFace ) { - if ( json.alphaMap !== undefined ) material.alphaMap = getTexture( json.alphaMap ); + if ( cullFace === CullFaceBack ) { - if ( json.bumpMap !== undefined ) material.bumpMap = getTexture( json.bumpMap ); - if ( json.bumpScale !== undefined ) material.bumpScale = json.bumpScale; + gl.cullFace( gl.BACK ); - if ( json.normalMap !== undefined ) material.normalMap = getTexture( json.normalMap ); - if ( json.normalMapType !== undefined ) material.normalMapType = json.normalMapType; - if ( json.normalScale !== undefined ) { + } else if ( cullFace === CullFaceFront ) { - let normalScale = json.normalScale; + gl.cullFace( gl.FRONT ); - if ( Array.isArray( normalScale ) === false ) { + } else { - // Blender exporter used to export a scalar. See #7459 + gl.cullFace( gl.FRONT_AND_BACK ); - normalScale = [ normalScale, normalScale ]; + } } - material.normalScale = new Vector2().fromArray( normalScale ); + } else { + + disable( gl.CULL_FACE ); } - if ( json.displacementMap !== undefined ) material.displacementMap = getTexture( json.displacementMap ); - if ( json.displacementScale !== undefined ) material.displacementScale = json.displacementScale; - if ( json.displacementBias !== undefined ) material.displacementBias = json.displacementBias; + currentCullFace = cullFace; - if ( json.roughnessMap !== undefined ) material.roughnessMap = getTexture( json.roughnessMap ); - if ( json.metalnessMap !== undefined ) material.metalnessMap = getTexture( json.metalnessMap ); + } - if ( json.emissiveMap !== undefined ) material.emissiveMap = getTexture( json.emissiveMap ); - if ( json.emissiveIntensity !== undefined ) material.emissiveIntensity = json.emissiveIntensity; + function setLineWidth( width ) { - if ( json.specularMap !== undefined ) material.specularMap = getTexture( json.specularMap ); - if ( json.specularIntensityMap !== undefined ) material.specularIntensityMap = getTexture( json.specularIntensityMap ); - if ( json.specularColorMap !== undefined ) material.specularColorMap = getTexture( json.specularColorMap ); + if ( width !== currentLineWidth ) { - if ( json.envMap !== undefined ) material.envMap = getTexture( json.envMap ); - if ( json.envMapIntensity !== undefined ) material.envMapIntensity = json.envMapIntensity; + if ( lineWidthAvailable ) gl.lineWidth( width ); - if ( json.reflectivity !== undefined ) material.reflectivity = json.reflectivity; - if ( json.refractionRatio !== undefined ) material.refractionRatio = json.refractionRatio; + currentLineWidth = width; - if ( json.lightMap !== undefined ) material.lightMap = getTexture( json.lightMap ); - if ( json.lightMapIntensity !== undefined ) material.lightMapIntensity = json.lightMapIntensity; + } - if ( json.aoMap !== undefined ) material.aoMap = getTexture( json.aoMap ); - if ( json.aoMapIntensity !== undefined ) material.aoMapIntensity = json.aoMapIntensity; + } - if ( json.gradientMap !== undefined ) material.gradientMap = getTexture( json.gradientMap ); + function setPolygonOffset( polygonOffset, factor, units ) { - if ( json.clearcoatMap !== undefined ) material.clearcoatMap = getTexture( json.clearcoatMap ); - if ( json.clearcoatRoughnessMap !== undefined ) material.clearcoatRoughnessMap = getTexture( json.clearcoatRoughnessMap ); - if ( json.clearcoatNormalMap !== undefined ) material.clearcoatNormalMap = getTexture( json.clearcoatNormalMap ); - if ( json.clearcoatNormalScale !== undefined ) material.clearcoatNormalScale = new Vector2().fromArray( json.clearcoatNormalScale ); + if ( polygonOffset ) { - if ( json.iridescenceMap !== undefined ) material.iridescenceMap = getTexture( json.iridescenceMap ); - if ( json.iridescenceThicknessMap !== undefined ) material.iridescenceThicknessMap = getTexture( json.iridescenceThicknessMap ); + enable( gl.POLYGON_OFFSET_FILL ); - if ( json.transmissionMap !== undefined ) material.transmissionMap = getTexture( json.transmissionMap ); - if ( json.thicknessMap !== undefined ) material.thicknessMap = getTexture( json.thicknessMap ); + if ( currentPolygonOffsetFactor !== factor || currentPolygonOffsetUnits !== units ) { - if ( json.anisotropyMap !== undefined ) material.anisotropyMap = getTexture( json.anisotropyMap ); + gl.polygonOffset( factor, units ); - if ( json.sheenColorMap !== undefined ) material.sheenColorMap = getTexture( json.sheenColorMap ); - if ( json.sheenRoughnessMap !== undefined ) material.sheenRoughnessMap = getTexture( json.sheenRoughnessMap ); + currentPolygonOffsetFactor = factor; + currentPolygonOffsetUnits = units; - return material; + } - } + } else { - setTextures( value ) { + disable( gl.POLYGON_OFFSET_FILL ); - this.textures = value; - return this; + } } - static createMaterialFromType( type ) { - - const materialLib = { - ShadowMaterial, - SpriteMaterial, - RawShaderMaterial, - ShaderMaterial, - PointsMaterial, - MeshPhysicalMaterial, - MeshStandardMaterial, - MeshPhongMaterial, - MeshToonMaterial, - MeshNormalMaterial, - MeshLambertMaterial, - MeshDepthMaterial, - MeshDistanceMaterial, - MeshBasicMaterial, - MeshMatcapMaterial, - LineDashedMaterial, - LineBasicMaterial, - Material - }; - - return new materialLib[ type ](); + function setScissorTest( scissorTest ) { - } + if ( scissorTest ) { -} + enable( gl.SCISSOR_TEST ); -class LoaderUtils { + } else { - static decodeText( array ) { + disable( gl.SCISSOR_TEST ); - if ( typeof TextDecoder !== 'undefined' ) { + } - return new TextDecoder().decode( array ); + } - } + // texture - // Avoid the String.fromCharCode.apply(null, array) shortcut, which - // throws a "maximum call stack size exceeded" error for large arrays. + function activeTexture( webglSlot ) { - let s = ''; + if ( webglSlot === undefined ) webglSlot = gl.TEXTURE0 + maxTextures - 1; - for ( let i = 0, il = array.length; i < il; i ++ ) { + if ( currentTextureSlot !== webglSlot ) { - // Implicitly assumes little-endian. - s += String.fromCharCode( array[ i ] ); + gl.activeTexture( webglSlot ); + currentTextureSlot = webglSlot; } - try { - - // merges multi-byte utf-8 characters. + } - return decodeURIComponent( escape( s ) ); + function bindTexture( webglType, webglTexture, webglSlot ) { - } catch ( e ) { // see #16358 + if ( webglSlot === undefined ) { - return s; + if ( currentTextureSlot === null ) { - } + webglSlot = gl.TEXTURE0 + maxTextures - 1; - } + } else { - static extractUrlBase( url ) { + webglSlot = currentTextureSlot; - const index = url.lastIndexOf( '/' ); + } - if ( index === - 1 ) return './'; + } - return url.slice( 0, index + 1 ); + let boundTexture = currentBoundTextures[ webglSlot ]; - } + if ( boundTexture === undefined ) { - static resolveURL( url, path ) { + boundTexture = { type: undefined, texture: undefined }; + currentBoundTextures[ webglSlot ] = boundTexture; - // Invalid URL - if ( typeof url !== 'string' || url === '' ) return ''; + } - // Host Relative URL - if ( /^https?:\/\//i.test( path ) && /^\//.test( url ) ) { + if ( boundTexture.type !== webglType || boundTexture.texture !== webglTexture ) { - path = path.replace( /(^https?:\/\/[^\/]+).*/i, '$1' ); + if ( currentTextureSlot !== webglSlot ) { - } + gl.activeTexture( webglSlot ); + currentTextureSlot = webglSlot; - // Absolute URL http://,https://,// - if ( /^(https?:)?\/\//i.test( url ) ) return url; + } - // Data URI - if ( /^data:.*,.*$/i.test( url ) ) return url; + gl.bindTexture( webglType, webglTexture || emptyTextures[ webglType ] ); - // Blob URL - if ( /^blob:.*$/i.test( url ) ) return url; + boundTexture.type = webglType; + boundTexture.texture = webglTexture; - // Relative URL - return path + url; + } } -} + function unbindTexture() { -class InstancedBufferGeometry extends BufferGeometry { + const boundTexture = currentBoundTextures[ currentTextureSlot ]; - constructor() { + if ( boundTexture !== undefined && boundTexture.type !== undefined ) { - super(); + gl.bindTexture( boundTexture.type, null ); - this.isInstancedBufferGeometry = true; + boundTexture.type = undefined; + boundTexture.texture = undefined; - this.type = 'InstancedBufferGeometry'; - this.instanceCount = Infinity; + } } - copy( source ) { + function compressedTexImage2D() { - super.copy( source ); + try { - this.instanceCount = source.instanceCount; + gl.compressedTexImage2D( ...arguments ); - return this; + } catch ( error ) { - } + console.error( 'THREE.WebGLState:', error ); - toJSON() { + } - const data = super.toJSON(); + } - data.instanceCount = this.instanceCount; + function compressedTexImage3D() { - data.isInstancedBufferGeometry = true; + try { - return data; + gl.compressedTexImage3D( ...arguments ); - } + } catch ( error ) { -} + console.error( 'THREE.WebGLState:', error ); -class BufferGeometryLoader extends Loader { + } - constructor( manager ) { + } - super( manager ); + function texSubImage2D() { - } + try { - load( url, onLoad, onProgress, onError ) { + gl.texSubImage2D( ...arguments ); - const scope = this; + } catch ( error ) { - const loader = new FileLoader( scope.manager ); - loader.setPath( scope.path ); - loader.setRequestHeader( scope.requestHeader ); - loader.setWithCredentials( scope.withCredentials ); - loader.load( url, function ( text ) { + console.error( 'THREE.WebGLState:', error ); - try { + } - onLoad( scope.parse( JSON.parse( text ) ) ); + } - } catch ( e ) { + function texSubImage3D() { - if ( onError ) { + try { - onError( e ); + gl.texSubImage3D( ...arguments ); - } else { + } catch ( error ) { - console.error( e ); + console.error( 'THREE.WebGLState:', error ); - } + } - scope.manager.itemError( url ); + } - } + function compressedTexSubImage2D() { - }, onProgress, onError ); + try { - } + gl.compressedTexSubImage2D( ...arguments ); - parse( json ) { + } catch ( error ) { - const interleavedBufferMap = {}; - const arrayBufferMap = {}; + console.error( 'THREE.WebGLState:', error ); - function getInterleavedBuffer( json, uuid ) { + } - if ( interleavedBufferMap[ uuid ] !== undefined ) return interleavedBufferMap[ uuid ]; + } - const interleavedBuffers = json.interleavedBuffers; - const interleavedBuffer = interleavedBuffers[ uuid ]; + function compressedTexSubImage3D() { - const buffer = getArrayBuffer( json, interleavedBuffer.buffer ); + try { - const array = getTypedArray( interleavedBuffer.type, buffer ); - const ib = new InterleavedBuffer( array, interleavedBuffer.stride ); - ib.uuid = interleavedBuffer.uuid; + gl.compressedTexSubImage3D( ...arguments ); - interleavedBufferMap[ uuid ] = ib; + } catch ( error ) { - return ib; + console.error( 'THREE.WebGLState:', error ); } - function getArrayBuffer( json, uuid ) { + } - if ( arrayBufferMap[ uuid ] !== undefined ) return arrayBufferMap[ uuid ]; + function texStorage2D() { - const arrayBuffers = json.arrayBuffers; - const arrayBuffer = arrayBuffers[ uuid ]; + try { - const ab = new Uint32Array( arrayBuffer ).buffer; + gl.texStorage2D( ...arguments ); - arrayBufferMap[ uuid ] = ab; + } catch ( error ) { - return ab; + console.error( 'THREE.WebGLState:', error ); } - const geometry = json.isInstancedBufferGeometry ? new InstancedBufferGeometry() : new BufferGeometry(); + } + + function texStorage3D() { + + try { - const index = json.data.index; + gl.texStorage3D( ...arguments ); - if ( index !== undefined ) { + } catch ( error ) { - const typedArray = getTypedArray( index.type, index.array ); - geometry.setIndex( new BufferAttribute( typedArray, 1 ) ); + console.error( 'THREE.WebGLState:', error ); } - const attributes = json.data.attributes; + } - for ( const key in attributes ) { + function texImage2D() { - const attribute = attributes[ key ]; - let bufferAttribute; + try { - if ( attribute.isInterleavedBufferAttribute ) { + gl.texImage2D( ...arguments ); - const interleavedBuffer = getInterleavedBuffer( json.data, attribute.data ); - bufferAttribute = new InterleavedBufferAttribute( interleavedBuffer, attribute.itemSize, attribute.offset, attribute.normalized ); + } catch ( error ) { - } else { + console.error( 'THREE.WebGLState:', error ); - const typedArray = getTypedArray( attribute.type, attribute.array ); - const bufferAttributeConstr = attribute.isInstancedBufferAttribute ? InstancedBufferAttribute : BufferAttribute; - bufferAttribute = new bufferAttributeConstr( typedArray, attribute.itemSize, attribute.normalized ); + } - } + } - if ( attribute.name !== undefined ) bufferAttribute.name = attribute.name; - if ( attribute.usage !== undefined ) bufferAttribute.setUsage( attribute.usage ); + function texImage3D() { - if ( attribute.updateRange !== undefined ) { + try { - bufferAttribute.updateRange.offset = attribute.updateRange.offset; - bufferAttribute.updateRange.count = attribute.updateRange.count; + gl.texImage3D( ...arguments ); - } + } catch ( error ) { - geometry.setAttribute( key, bufferAttribute ); + console.error( 'THREE.WebGLState:', error ); } - const morphAttributes = json.data.morphAttributes; + } + + // - if ( morphAttributes ) { + function scissor( scissor ) { - for ( const key in morphAttributes ) { + if ( currentScissor.equals( scissor ) === false ) { - const attributeArray = morphAttributes[ key ]; + gl.scissor( scissor.x, scissor.y, scissor.z, scissor.w ); + currentScissor.copy( scissor ); - const array = []; + } - for ( let i = 0, il = attributeArray.length; i < il; i ++ ) { + } - const attribute = attributeArray[ i ]; - let bufferAttribute; + function viewport( viewport ) { - if ( attribute.isInterleavedBufferAttribute ) { + if ( currentViewport.equals( viewport ) === false ) { - const interleavedBuffer = getInterleavedBuffer( json.data, attribute.data ); - bufferAttribute = new InterleavedBufferAttribute( interleavedBuffer, attribute.itemSize, attribute.offset, attribute.normalized ); + gl.viewport( viewport.x, viewport.y, viewport.z, viewport.w ); + currentViewport.copy( viewport ); - } else { + } - const typedArray = getTypedArray( attribute.type, attribute.array ); - bufferAttribute = new BufferAttribute( typedArray, attribute.itemSize, attribute.normalized ); + } - } + function updateUBOMapping( uniformsGroup, program ) { - if ( attribute.name !== undefined ) bufferAttribute.name = attribute.name; - array.push( bufferAttribute ); + let mapping = uboProgramMap.get( program ); - } + if ( mapping === undefined ) { - geometry.morphAttributes[ key ] = array; + mapping = new WeakMap(); - } + uboProgramMap.set( program, mapping ); } - const morphTargetsRelative = json.data.morphTargetsRelative; + let blockIndex = mapping.get( uniformsGroup ); + + if ( blockIndex === undefined ) { - if ( morphTargetsRelative ) { + blockIndex = gl.getUniformBlockIndex( program, uniformsGroup.name ); - geometry.morphTargetsRelative = true; + mapping.set( uniformsGroup, blockIndex ); } - const groups = json.data.groups || json.data.drawcalls || json.data.offsets; + } - if ( groups !== undefined ) { + function uniformBlockBinding( uniformsGroup, program ) { - for ( let i = 0, n = groups.length; i !== n; ++ i ) { + const mapping = uboProgramMap.get( program ); + const blockIndex = mapping.get( uniformsGroup ); - const group = groups[ i ]; + if ( uboBindings.get( program ) !== blockIndex ) { - geometry.addGroup( group.start, group.count, group.materialIndex ); + // bind shader specific block index to global block point + gl.uniformBlockBinding( program, blockIndex, uniformsGroup.__bindingPointIndex ); - } + uboBindings.set( program, blockIndex ); } - const boundingSphere = json.data.boundingSphere; + } - if ( boundingSphere !== undefined ) { + // - const center = new Vector3(); + function reset() { - if ( boundingSphere.center !== undefined ) { + // reset state - center.fromArray( boundingSphere.center ); + gl.disable( gl.BLEND ); + gl.disable( gl.CULL_FACE ); + gl.disable( gl.DEPTH_TEST ); + gl.disable( gl.POLYGON_OFFSET_FILL ); + gl.disable( gl.SCISSOR_TEST ); + gl.disable( gl.STENCIL_TEST ); + gl.disable( gl.SAMPLE_ALPHA_TO_COVERAGE ); - } + gl.blendEquation( gl.FUNC_ADD ); + gl.blendFunc( gl.ONE, gl.ZERO ); + gl.blendFuncSeparate( gl.ONE, gl.ZERO, gl.ONE, gl.ZERO ); + gl.blendColor( 0, 0, 0, 0 ); - geometry.boundingSphere = new Sphere( center, boundingSphere.radius ); + gl.colorMask( true, true, true, true ); + gl.clearColor( 0, 0, 0, 0 ); - } + gl.depthMask( true ); + gl.depthFunc( gl.LESS ); - if ( json.name ) geometry.name = json.name; - if ( json.userData ) geometry.userData = json.userData; + depthBuffer.setReversed( false ); - return geometry; + gl.clearDepth( 1 ); - } + gl.stencilMask( 0xffffffff ); + gl.stencilFunc( gl.ALWAYS, 0, 0xffffffff ); + gl.stencilOp( gl.KEEP, gl.KEEP, gl.KEEP ); + gl.clearStencil( 0 ); -} + gl.cullFace( gl.BACK ); + gl.frontFace( gl.CCW ); -class ObjectLoader extends Loader { + gl.polygonOffset( 0, 0 ); - constructor( manager ) { + gl.activeTexture( gl.TEXTURE0 ); - super( manager ); + gl.bindFramebuffer( gl.FRAMEBUFFER, null ); + gl.bindFramebuffer( gl.DRAW_FRAMEBUFFER, null ); + gl.bindFramebuffer( gl.READ_FRAMEBUFFER, null ); - } + gl.useProgram( null ); - load( url, onLoad, onProgress, onError ) { + gl.lineWidth( 1 ); - const scope = this; + gl.scissor( 0, 0, gl.canvas.width, gl.canvas.height ); + gl.viewport( 0, 0, gl.canvas.width, gl.canvas.height ); + + // reset internals + + enabledCapabilities = {}; - const path = ( this.path === '' ) ? LoaderUtils.extractUrlBase( url ) : this.path; - this.resourcePath = this.resourcePath || path; + currentTextureSlot = null; + currentBoundTextures = {}; - const loader = new FileLoader( this.manager ); - loader.setPath( this.path ); - loader.setRequestHeader( this.requestHeader ); - loader.setWithCredentials( this.withCredentials ); - loader.load( url, function ( text ) { + currentBoundFramebuffers = {}; + currentDrawbuffers = new WeakMap(); + defaultDrawbuffers = []; - let json = null; + currentProgram = null; - try { + currentBlendingEnabled = false; + currentBlending = null; + currentBlendEquation = null; + currentBlendSrc = null; + currentBlendDst = null; + currentBlendEquationAlpha = null; + currentBlendSrcAlpha = null; + currentBlendDstAlpha = null; + currentBlendColor = new Color( 0, 0, 0 ); + currentBlendAlpha = 0; + currentPremultipledAlpha = false; - json = JSON.parse( text ); + currentFlipSided = null; + currentCullFace = null; - } catch ( error ) { + currentLineWidth = null; - if ( onError !== undefined ) onError( error ); + currentPolygonOffsetFactor = null; + currentPolygonOffsetUnits = null; - console.error( 'THREE:ObjectLoader: Can\'t parse ' + url + '.', error.message ); + currentScissor.set( 0, 0, gl.canvas.width, gl.canvas.height ); + currentViewport.set( 0, 0, gl.canvas.width, gl.canvas.height ); - return; + colorBuffer.reset(); + depthBuffer.reset(); + stencilBuffer.reset(); - } + } - const metadata = json.metadata; + return { - if ( metadata === undefined || metadata.type === undefined || metadata.type.toLowerCase() === 'geometry' ) { + buffers: { + color: colorBuffer, + depth: depthBuffer, + stencil: stencilBuffer + }, - if ( onError !== undefined ) onError( new Error( 'THREE.ObjectLoader: Can\'t load ' + url ) ); + enable: enable, + disable: disable, - console.error( 'THREE.ObjectLoader: Can\'t load ' + url ); - return; + bindFramebuffer: bindFramebuffer, + drawBuffers: drawBuffers, - } + useProgram: useProgram, - scope.parse( json, onLoad ); + setBlending: setBlending, + setMaterial: setMaterial, - }, onProgress, onError ); + setFlipSided: setFlipSided, + setCullFace: setCullFace, - } + setLineWidth: setLineWidth, + setPolygonOffset: setPolygonOffset, - async loadAsync( url, onProgress ) { + setScissorTest: setScissorTest, - const scope = this; + activeTexture: activeTexture, + bindTexture: bindTexture, + unbindTexture: unbindTexture, + compressedTexImage2D: compressedTexImage2D, + compressedTexImage3D: compressedTexImage3D, + texImage2D: texImage2D, + texImage3D: texImage3D, - const path = ( this.path === '' ) ? LoaderUtils.extractUrlBase( url ) : this.path; - this.resourcePath = this.resourcePath || path; + updateUBOMapping: updateUBOMapping, + uniformBlockBinding: uniformBlockBinding, - const loader = new FileLoader( this.manager ); - loader.setPath( this.path ); - loader.setRequestHeader( this.requestHeader ); - loader.setWithCredentials( this.withCredentials ); + texStorage2D: texStorage2D, + texStorage3D: texStorage3D, + texSubImage2D: texSubImage2D, + texSubImage3D: texSubImage3D, + compressedTexSubImage2D: compressedTexSubImage2D, + compressedTexSubImage3D: compressedTexSubImage3D, - const text = await loader.loadAsync( url, onProgress ); + scissor: scissor, + viewport: viewport, - const json = JSON.parse( text ); + reset: reset - const metadata = json.metadata; + }; - if ( metadata === undefined || metadata.type === undefined || metadata.type.toLowerCase() === 'geometry' ) { +} - throw new Error( 'THREE.ObjectLoader: Can\'t load ' + url ); +function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, info ) { - } + const multisampledRTTExt = extensions.has( 'WEBGL_multisampled_render_to_texture' ) ? extensions.get( 'WEBGL_multisampled_render_to_texture' ) : null; + const supportsInvalidateFramebuffer = typeof navigator === 'undefined' ? false : /OculusBrowser/g.test( navigator.userAgent ); - return await scope.parseAsync( json ); + const _imageDimensions = new Vector2(); + const _videoTextures = new WeakMap(); + let _canvas; - } + const _sources = new WeakMap(); // maps WebglTexture objects to instances of Source - parse( json, onLoad ) { + // cordova iOS (as of 5.0) still uses UIWebView, which provides OffscreenCanvas, + // also OffscreenCanvas.getContext("webgl"), but not OffscreenCanvas.getContext("2d")! + // Some implementations may only implement OffscreenCanvas partially (e.g. lacking 2d). - const animations = this.parseAnimations( json.animations ); - const shapes = this.parseShapes( json.shapes ); - const geometries = this.parseGeometries( json.geometries, shapes ); + let useOffscreenCanvas = false; - const images = this.parseImages( json.images, function () { + try { - if ( onLoad !== undefined ) onLoad( object ); + useOffscreenCanvas = typeof OffscreenCanvas !== 'undefined' + // eslint-disable-next-line compat/compat + && ( new OffscreenCanvas( 1, 1 ).getContext( '2d' ) ) !== null; - } ); + } catch ( err ) { - const textures = this.parseTextures( json.textures, images ); - const materials = this.parseMaterials( json.materials, textures ); + // Ignore any errors - const object = this.parseObject( json.object, geometries, materials, textures, animations ); - const skeletons = this.parseSkeletons( json.skeletons, object ); + } - this.bindSkeletons( object, skeletons ); + function createCanvas( width, height ) { - // + // Use OffscreenCanvas when available. Specially needed in web workers - if ( onLoad !== undefined ) { + return useOffscreenCanvas ? + // eslint-disable-next-line compat/compat + new OffscreenCanvas( width, height ) : createElementNS( 'canvas' ); - let hasImages = false; + } - for ( const uuid in images ) { + function resizeImage( image, needsNewCanvas, maxSize ) { - if ( images[ uuid ].data instanceof HTMLImageElement ) { + let scale = 1; - hasImages = true; - break; + const dimensions = getDimensions( image ); - } + // handle case if texture exceeds max size - } + if ( dimensions.width > maxSize || dimensions.height > maxSize ) { - if ( hasImages === false ) onLoad( object ); + scale = maxSize / Math.max( dimensions.width, dimensions.height ); } - return object; + // only perform resize if necessary - } + if ( scale < 1 ) { - async parseAsync( json ) { + // only perform resize for certain image types + + if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || + ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) || + ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) || + ( typeof VideoFrame !== 'undefined' && image instanceof VideoFrame ) ) { - const animations = this.parseAnimations( json.animations ); - const shapes = this.parseShapes( json.shapes ); - const geometries = this.parseGeometries( json.geometries, shapes ); + const width = Math.floor( scale * dimensions.width ); + const height = Math.floor( scale * dimensions.height ); - const images = await this.parseImagesAsync( json.images ); + if ( _canvas === undefined ) _canvas = createCanvas( width, height ); - const textures = this.parseTextures( json.textures, images ); - const materials = this.parseMaterials( json.materials, textures ); + // cube textures can't reuse the same canvas - const object = this.parseObject( json.object, geometries, materials, textures, animations ); - const skeletons = this.parseSkeletons( json.skeletons, object ); + const canvas = needsNewCanvas ? createCanvas( width, height ) : _canvas; - this.bindSkeletons( object, skeletons ); + canvas.width = width; + canvas.height = height; - return object; + const context = canvas.getContext( '2d' ); + context.drawImage( image, 0, 0, width, height ); - } + console.warn( 'THREE.WebGLRenderer: Texture has been resized from (' + dimensions.width + 'x' + dimensions.height + ') to (' + width + 'x' + height + ').' ); - parseShapes( json ) { + return canvas; - const shapes = {}; + } else { - if ( json !== undefined ) { + if ( 'data' in image ) { - for ( let i = 0, l = json.length; i < l; i ++ ) { + console.warn( 'THREE.WebGLRenderer: Image in DataTexture is too big (' + dimensions.width + 'x' + dimensions.height + ').' ); - const shape = new Shape().fromJSON( json[ i ] ); + } - shapes[ shape.uuid ] = shape; + return image; } } - return shapes; + return image; } - parseSkeletons( json, object ) { - - const skeletons = {}; - const bones = {}; - - // generate bone lookup table - - object.traverse( function ( child ) { - - if ( child.isBone ) bones[ child.uuid ] = child; - - } ); - - // create skeletons + function textureNeedsGenerateMipmaps( texture ) { - if ( json !== undefined ) { + return texture.generateMipmaps; - for ( let i = 0, l = json.length; i < l; i ++ ) { + } - const skeleton = new Skeleton().fromJSON( json[ i ], bones ); + function generateMipmap( target ) { - skeletons[ skeleton.uuid ] = skeleton; + _gl.generateMipmap( target ); - } + } - } + function getTargetType( texture ) { - return skeletons; + if ( texture.isWebGLCubeRenderTarget ) return _gl.TEXTURE_CUBE_MAP; + if ( texture.isWebGL3DRenderTarget ) return _gl.TEXTURE_3D; + if ( texture.isWebGLArrayRenderTarget || texture.isCompressedArrayTexture ) return _gl.TEXTURE_2D_ARRAY; + return _gl.TEXTURE_2D; } - parseGeometries( json, shapes ) { + function getInternalFormat( internalFormatName, glFormat, glType, colorSpace, forceLinearTransfer = false ) { - const geometries = {}; + if ( internalFormatName !== null ) { - if ( json !== undefined ) { + if ( _gl[ internalFormatName ] !== undefined ) return _gl[ internalFormatName ]; - const bufferGeometryLoader = new BufferGeometryLoader(); + console.warn( 'THREE.WebGLRenderer: Attempt to use non-existing WebGL internal format \'' + internalFormatName + '\'' ); - for ( let i = 0, l = json.length; i < l; i ++ ) { + } - let geometry; - const data = json[ i ]; + let internalFormat = glFormat; - switch ( data.type ) { + if ( glFormat === _gl.RED ) { - case 'BufferGeometry': - case 'InstancedBufferGeometry': + if ( glType === _gl.FLOAT ) internalFormat = _gl.R32F; + if ( glType === _gl.HALF_FLOAT ) internalFormat = _gl.R16F; + if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = _gl.R8; - geometry = bufferGeometryLoader.parse( data ); - break; + } - default: + if ( glFormat === _gl.RED_INTEGER ) { - if ( data.type in Geometries ) { + if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = _gl.R8UI; + if ( glType === _gl.UNSIGNED_SHORT ) internalFormat = _gl.R16UI; + if ( glType === _gl.UNSIGNED_INT ) internalFormat = _gl.R32UI; + if ( glType === _gl.BYTE ) internalFormat = _gl.R8I; + if ( glType === _gl.SHORT ) internalFormat = _gl.R16I; + if ( glType === _gl.INT ) internalFormat = _gl.R32I; - geometry = Geometries[ data.type ].fromJSON( data, shapes ); + } - } else { + if ( glFormat === _gl.RG ) { - console.warn( `THREE.ObjectLoader: Unsupported geometry type "${ data.type }"` ); + if ( glType === _gl.FLOAT ) internalFormat = _gl.RG32F; + if ( glType === _gl.HALF_FLOAT ) internalFormat = _gl.RG16F; + if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = _gl.RG8; - } + } - } + if ( glFormat === _gl.RG_INTEGER ) { - geometry.uuid = data.uuid; + if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = _gl.RG8UI; + if ( glType === _gl.UNSIGNED_SHORT ) internalFormat = _gl.RG16UI; + if ( glType === _gl.UNSIGNED_INT ) internalFormat = _gl.RG32UI; + if ( glType === _gl.BYTE ) internalFormat = _gl.RG8I; + if ( glType === _gl.SHORT ) internalFormat = _gl.RG16I; + if ( glType === _gl.INT ) internalFormat = _gl.RG32I; - if ( data.name !== undefined ) geometry.name = data.name; - if ( data.userData !== undefined ) geometry.userData = data.userData; + } - geometries[ data.uuid ] = geometry; + if ( glFormat === _gl.RGB_INTEGER ) { - } + if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = _gl.RGB8UI; + if ( glType === _gl.UNSIGNED_SHORT ) internalFormat = _gl.RGB16UI; + if ( glType === _gl.UNSIGNED_INT ) internalFormat = _gl.RGB32UI; + if ( glType === _gl.BYTE ) internalFormat = _gl.RGB8I; + if ( glType === _gl.SHORT ) internalFormat = _gl.RGB16I; + if ( glType === _gl.INT ) internalFormat = _gl.RGB32I; } - return geometries; - - } + if ( glFormat === _gl.RGBA_INTEGER ) { - parseMaterials( json, textures ) { + if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = _gl.RGBA8UI; + if ( glType === _gl.UNSIGNED_SHORT ) internalFormat = _gl.RGBA16UI; + if ( glType === _gl.UNSIGNED_INT ) internalFormat = _gl.RGBA32UI; + if ( glType === _gl.BYTE ) internalFormat = _gl.RGBA8I; + if ( glType === _gl.SHORT ) internalFormat = _gl.RGBA16I; + if ( glType === _gl.INT ) internalFormat = _gl.RGBA32I; - const cache = {}; // MultiMaterial - const materials = {}; + } - if ( json !== undefined ) { + if ( glFormat === _gl.RGB ) { - const loader = new MaterialLoader(); - loader.setTextures( textures ); + if ( glType === _gl.UNSIGNED_INT_5_9_9_9_REV ) internalFormat = _gl.RGB9_E5; - for ( let i = 0, l = json.length; i < l; i ++ ) { + } - const data = json[ i ]; + if ( glFormat === _gl.RGBA ) { - if ( cache[ data.uuid ] === undefined ) { + const transfer = forceLinearTransfer ? LinearTransfer : ColorManagement.getTransfer( colorSpace ); - cache[ data.uuid ] = loader.parse( data ); + if ( glType === _gl.FLOAT ) internalFormat = _gl.RGBA32F; + if ( glType === _gl.HALF_FLOAT ) internalFormat = _gl.RGBA16F; + if ( glType === _gl.UNSIGNED_BYTE ) internalFormat = ( transfer === SRGBTransfer ) ? _gl.SRGB8_ALPHA8 : _gl.RGBA8; + if ( glType === _gl.UNSIGNED_SHORT_4_4_4_4 ) internalFormat = _gl.RGBA4; + if ( glType === _gl.UNSIGNED_SHORT_5_5_5_1 ) internalFormat = _gl.RGB5_A1; - } + } - materials[ data.uuid ] = cache[ data.uuid ]; + if ( internalFormat === _gl.R16F || internalFormat === _gl.R32F || + internalFormat === _gl.RG16F || internalFormat === _gl.RG32F || + internalFormat === _gl.RGBA16F || internalFormat === _gl.RGBA32F ) { - } + extensions.get( 'EXT_color_buffer_float' ); } - return materials; + return internalFormat; } - parseAnimations( json ) { + function getInternalDepthFormat( useStencil, depthType ) { + + let glInternalFormat; + if ( useStencil ) { - const animations = {}; + if ( depthType === null || depthType === UnsignedIntType || depthType === UnsignedInt248Type ) { - if ( json !== undefined ) { + glInternalFormat = _gl.DEPTH24_STENCIL8; - for ( let i = 0; i < json.length; i ++ ) { + } else if ( depthType === FloatType ) { - const data = json[ i ]; + glInternalFormat = _gl.DEPTH32F_STENCIL8; - const clip = AnimationClip.parse( data ); + } else if ( depthType === UnsignedShortType ) { - animations[ clip.uuid ] = clip; + glInternalFormat = _gl.DEPTH24_STENCIL8; + console.warn( 'DepthTexture: 16 bit depth attachment is not supported with stencil. Using 24-bit attachment.' ); } - } + } else { - return animations; + if ( depthType === null || depthType === UnsignedIntType || depthType === UnsignedInt248Type ) { - } + glInternalFormat = _gl.DEPTH_COMPONENT24; - parseImages( json, onLoad ) { + } else if ( depthType === FloatType ) { - const scope = this; - const images = {}; + glInternalFormat = _gl.DEPTH_COMPONENT32F; - let loader; + } else if ( depthType === UnsignedShortType ) { - function loadImage( url ) { + glInternalFormat = _gl.DEPTH_COMPONENT16; - scope.manager.itemStart( url ); + } - return loader.load( url, function () { + } - scope.manager.itemEnd( url ); + return glInternalFormat; - }, undefined, function () { + } - scope.manager.itemError( url ); - scope.manager.itemEnd( url ); + function getMipLevels( texture, image ) { - } ); + if ( textureNeedsGenerateMipmaps( texture ) === true || ( texture.isFramebufferTexture && texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter ) ) { - } + return Math.log2( Math.max( image.width, image.height ) ) + 1; - function deserializeImage( image ) { + } else if ( texture.mipmaps !== undefined && texture.mipmaps.length > 0 ) { - if ( typeof image === 'string' ) { + // user-defined mipmaps - const url = image; + return texture.mipmaps.length; - const path = /^(\/\/)|([a-z]+:(\/\/)?)/i.test( url ) ? url : scope.resourcePath + url; + } else if ( texture.isCompressedTexture && Array.isArray( texture.image ) ) { - return loadImage( path ); + return image.mipmaps.length; - } else { + } else { - if ( image.data ) { + // texture without mipmaps (only base level) - return { - data: getTypedArray( image.type, image.data ), - width: image.width, - height: image.height - }; + return 1; - } else { + } - return null; + } - } + // - } + function onTextureDispose( event ) { - } + const texture = event.target; - if ( json !== undefined && json.length > 0 ) { + texture.removeEventListener( 'dispose', onTextureDispose ); - const manager = new LoadingManager( onLoad ); + deallocateTexture( texture ); - loader = new ImageLoader( manager ); - loader.setCrossOrigin( this.crossOrigin ); + if ( texture.isVideoTexture ) { - for ( let i = 0, il = json.length; i < il; i ++ ) { + _videoTextures.delete( texture ); - const image = json[ i ]; - const url = image.url; + } - if ( Array.isArray( url ) ) { + } - // load array of images e.g CubeTexture + function onRenderTargetDispose( event ) { - const imageArray = []; + const renderTarget = event.target; - for ( let j = 0, jl = url.length; j < jl; j ++ ) { + renderTarget.removeEventListener( 'dispose', onRenderTargetDispose ); - const currentUrl = url[ j ]; + deallocateRenderTarget( renderTarget ); - const deserializedImage = deserializeImage( currentUrl ); + } - if ( deserializedImage !== null ) { + // - if ( deserializedImage instanceof HTMLImageElement ) { + function deallocateTexture( texture ) { - imageArray.push( deserializedImage ); + const textureProperties = properties.get( texture ); - } else { + if ( textureProperties.__webglInit === undefined ) return; - // special case: handle array of data textures for cube textures + // check if it's necessary to remove the WebGLTexture object - imageArray.push( new DataTexture( deserializedImage.data, deserializedImage.width, deserializedImage.height ) ); + const source = texture.source; + const webglTextures = _sources.get( source ); - } + if ( webglTextures ) { - } + const webglTexture = webglTextures[ textureProperties.__cacheKey ]; + webglTexture.usedTimes --; - } + // the WebGLTexture object is not used anymore, remove it - images[ image.uuid ] = new Source( imageArray ); + if ( webglTexture.usedTimes === 0 ) { - } else { + deleteTexture( texture ); - // load single image + } - const deserializedImage = deserializeImage( image.url ); - images[ image.uuid ] = new Source( deserializedImage ); + // remove the weak map entry if no WebGLTexture uses the source anymore + if ( Object.keys( webglTextures ).length === 0 ) { - } + _sources.delete( source ); } } - return images; + properties.remove( texture ); } - async parseImagesAsync( json ) { + function deleteTexture( texture ) { - const scope = this; - const images = {}; + const textureProperties = properties.get( texture ); + _gl.deleteTexture( textureProperties.__webglTexture ); - let loader; + const source = texture.source; + const webglTextures = _sources.get( source ); + delete webglTextures[ textureProperties.__cacheKey ]; - async function deserializeImage( image ) { + info.memory.textures --; - if ( typeof image === 'string' ) { + } - const url = image; + function deallocateRenderTarget( renderTarget ) { - const path = /^(\/\/)|([a-z]+:(\/\/)?)/i.test( url ) ? url : scope.resourcePath + url; + const renderTargetProperties = properties.get( renderTarget ); - return await loader.loadAsync( path ); + if ( renderTarget.depthTexture ) { - } else { + renderTarget.depthTexture.dispose(); - if ( image.data ) { + properties.remove( renderTarget.depthTexture ); - return { - data: getTypedArray( image.type, image.data ), - width: image.width, - height: image.height - }; + } - } else { + if ( renderTarget.isWebGLCubeRenderTarget ) { - return null; + for ( let i = 0; i < 6; i ++ ) { - } + if ( Array.isArray( renderTargetProperties.__webglFramebuffer[ i ] ) ) { - } + for ( let level = 0; level < renderTargetProperties.__webglFramebuffer[ i ].length; level ++ ) _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer[ i ][ level ] ); - } + } else { - if ( json !== undefined && json.length > 0 ) { + _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer[ i ] ); - loader = new ImageLoader( this.manager ); - loader.setCrossOrigin( this.crossOrigin ); + } - for ( let i = 0, il = json.length; i < il; i ++ ) { + if ( renderTargetProperties.__webglDepthbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthbuffer[ i ] ); - const image = json[ i ]; - const url = image.url; + } - if ( Array.isArray( url ) ) { + } else { - // load array of images e.g CubeTexture + if ( Array.isArray( renderTargetProperties.__webglFramebuffer ) ) { - const imageArray = []; + for ( let level = 0; level < renderTargetProperties.__webglFramebuffer.length; level ++ ) _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer[ level ] ); - for ( let j = 0, jl = url.length; j < jl; j ++ ) { + } else { - const currentUrl = url[ j ]; + _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer ); - const deserializedImage = await deserializeImage( currentUrl ); + } - if ( deserializedImage !== null ) { + if ( renderTargetProperties.__webglDepthbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthbuffer ); + if ( renderTargetProperties.__webglMultisampledFramebuffer ) _gl.deleteFramebuffer( renderTargetProperties.__webglMultisampledFramebuffer ); - if ( deserializedImage instanceof HTMLImageElement ) { + if ( renderTargetProperties.__webglColorRenderbuffer ) { - imageArray.push( deserializedImage ); + for ( let i = 0; i < renderTargetProperties.__webglColorRenderbuffer.length; i ++ ) { - } else { + if ( renderTargetProperties.__webglColorRenderbuffer[ i ] ) _gl.deleteRenderbuffer( renderTargetProperties.__webglColorRenderbuffer[ i ] ); - // special case: handle array of data textures for cube textures + } - imageArray.push( new DataTexture( deserializedImage.data, deserializedImage.width, deserializedImage.height ) ); + } - } + if ( renderTargetProperties.__webglDepthRenderbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthRenderbuffer ); - } + } - } + const textures = renderTarget.textures; - images[ image.uuid ] = new Source( imageArray ); + for ( let i = 0, il = textures.length; i < il; i ++ ) { - } else { + const attachmentProperties = properties.get( textures[ i ] ); - // load single image + if ( attachmentProperties.__webglTexture ) { - const deserializedImage = await deserializeImage( image.url ); - images[ image.uuid ] = new Source( deserializedImage ); + _gl.deleteTexture( attachmentProperties.__webglTexture ); - } + info.memory.textures --; } + properties.remove( textures[ i ] ); + } - return images; + properties.remove( renderTarget ); } - parseTextures( json, images ) { - - function parseConstant( value, type ) { - - if ( typeof value === 'number' ) return value; - - console.warn( 'THREE.ObjectLoader.parseTexture: Constant should be in numeric form.', value ); - - return type[ value ]; - - } - - const textures = {}; + // - if ( json !== undefined ) { + let textureUnits = 0; - for ( let i = 0, l = json.length; i < l; i ++ ) { + function resetTextureUnits() { - const data = json[ i ]; + textureUnits = 0; - if ( data.image === undefined ) { + } - console.warn( 'THREE.ObjectLoader: No "image" specified for', data.uuid ); + function allocateTextureUnit() { - } + const textureUnit = textureUnits; - if ( images[ data.image ] === undefined ) { + if ( textureUnit >= capabilities.maxTextures ) { - console.warn( 'THREE.ObjectLoader: Undefined image', data.image ); + console.warn( 'THREE.WebGLTextures: Trying to use ' + textureUnit + ' texture units while this GPU supports only ' + capabilities.maxTextures ); - } + } - const source = images[ data.image ]; - const image = source.data; + textureUnits += 1; - let texture; + return textureUnit; - if ( Array.isArray( image ) ) { + } - texture = new CubeTexture(); + function getTextureCacheKey( texture ) { - if ( image.length === 6 ) texture.needsUpdate = true; + const array = []; - } else { + array.push( texture.wrapS ); + array.push( texture.wrapT ); + array.push( texture.wrapR || 0 ); + array.push( texture.magFilter ); + array.push( texture.minFilter ); + array.push( texture.anisotropy ); + array.push( texture.internalFormat ); + array.push( texture.format ); + array.push( texture.type ); + array.push( texture.generateMipmaps ); + array.push( texture.premultiplyAlpha ); + array.push( texture.flipY ); + array.push( texture.unpackAlignment ); + array.push( texture.colorSpace ); - if ( image && image.data ) { + return array.join(); - texture = new DataTexture(); + } - } else { + // - texture = new Texture(); + function setTexture2D( texture, slot ) { - } + const textureProperties = properties.get( texture ); - if ( image ) texture.needsUpdate = true; // textures can have undefined image data + if ( texture.isVideoTexture ) updateVideoTexture( texture ); - } + if ( texture.isRenderTargetTexture === false && texture.version > 0 && textureProperties.__version !== texture.version ) { - texture.source = source; + const image = texture.image; - texture.uuid = data.uuid; + if ( image === null ) { - if ( data.name !== undefined ) texture.name = data.name; + console.warn( 'THREE.WebGLRenderer: Texture marked for update but no image data found.' ); - if ( data.mapping !== undefined ) texture.mapping = parseConstant( data.mapping, TEXTURE_MAPPING ); - if ( data.channel !== undefined ) texture.channel = data.channel; + } else if ( image.complete === false ) { - if ( data.offset !== undefined ) texture.offset.fromArray( data.offset ); - if ( data.repeat !== undefined ) texture.repeat.fromArray( data.repeat ); - if ( data.center !== undefined ) texture.center.fromArray( data.center ); - if ( data.rotation !== undefined ) texture.rotation = data.rotation; + console.warn( 'THREE.WebGLRenderer: Texture marked for update but image is incomplete' ); - if ( data.wrap !== undefined ) { + } else { - texture.wrapS = parseConstant( data.wrap[ 0 ], TEXTURE_WRAPPING ); - texture.wrapT = parseConstant( data.wrap[ 1 ], TEXTURE_WRAPPING ); + uploadTexture( textureProperties, texture, slot ); + return; - } + } - if ( data.format !== undefined ) texture.format = data.format; - if ( data.internalFormat !== undefined ) texture.internalFormat = data.internalFormat; - if ( data.type !== undefined ) texture.type = data.type; - if ( data.colorSpace !== undefined ) texture.colorSpace = data.colorSpace; - if ( data.encoding !== undefined ) texture.encoding = data.encoding; // @deprecated, r152 + } - if ( data.minFilter !== undefined ) texture.minFilter = parseConstant( data.minFilter, TEXTURE_FILTER ); - if ( data.magFilter !== undefined ) texture.magFilter = parseConstant( data.magFilter, TEXTURE_FILTER ); - if ( data.anisotropy !== undefined ) texture.anisotropy = data.anisotropy; + state.bindTexture( _gl.TEXTURE_2D, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); - if ( data.flipY !== undefined ) texture.flipY = data.flipY; + } - if ( data.generateMipmaps !== undefined ) texture.generateMipmaps = data.generateMipmaps; - if ( data.premultiplyAlpha !== undefined ) texture.premultiplyAlpha = data.premultiplyAlpha; - if ( data.unpackAlignment !== undefined ) texture.unpackAlignment = data.unpackAlignment; - if ( data.compareFunction !== undefined ) texture.compareFunction = data.compareFunction; + function setTexture2DArray( texture, slot ) { - if ( data.userData !== undefined ) texture.userData = data.userData; + const textureProperties = properties.get( texture ); - textures[ data.uuid ] = texture; + if ( texture.version > 0 && textureProperties.__version !== texture.version ) { - } + uploadTexture( textureProperties, texture, slot ); + return; } - return textures; + state.bindTexture( _gl.TEXTURE_2D_ARRAY, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); } - parseObject( data, geometries, materials, textures, animations ) { - - let object; - - function getGeometry( name ) { - - if ( geometries[ name ] === undefined ) { + function setTexture3D( texture, slot ) { - console.warn( 'THREE.ObjectLoader: Undefined geometry', name ); + const textureProperties = properties.get( texture ); - } + if ( texture.version > 0 && textureProperties.__version !== texture.version ) { - return geometries[ name ]; + uploadTexture( textureProperties, texture, slot ); + return; } - function getMaterial( name ) { - - if ( name === undefined ) return undefined; + state.bindTexture( _gl.TEXTURE_3D, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); - if ( Array.isArray( name ) ) { + } - const array = []; + function setTextureCube( texture, slot ) { - for ( let i = 0, l = name.length; i < l; i ++ ) { + const textureProperties = properties.get( texture ); - const uuid = name[ i ]; + if ( texture.version > 0 && textureProperties.__version !== texture.version ) { - if ( materials[ uuid ] === undefined ) { + uploadCubeTexture( textureProperties, texture, slot ); + return; - console.warn( 'THREE.ObjectLoader: Undefined material', uuid ); + } - } + state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); - array.push( materials[ uuid ] ); + } - } + const wrappingToGL = { + [ RepeatWrapping ]: _gl.REPEAT, + [ ClampToEdgeWrapping ]: _gl.CLAMP_TO_EDGE, + [ MirroredRepeatWrapping ]: _gl.MIRRORED_REPEAT + }; - return array; + const filterToGL = { + [ NearestFilter ]: _gl.NEAREST, + [ NearestMipmapNearestFilter ]: _gl.NEAREST_MIPMAP_NEAREST, + [ NearestMipmapLinearFilter ]: _gl.NEAREST_MIPMAP_LINEAR, - } + [ LinearFilter ]: _gl.LINEAR, + [ LinearMipmapNearestFilter ]: _gl.LINEAR_MIPMAP_NEAREST, + [ LinearMipmapLinearFilter ]: _gl.LINEAR_MIPMAP_LINEAR + }; - if ( materials[ name ] === undefined ) { + const compareToGL = { + [ NeverCompare ]: _gl.NEVER, + [ AlwaysCompare ]: _gl.ALWAYS, + [ LessCompare ]: _gl.LESS, + [ LessEqualCompare ]: _gl.LEQUAL, + [ EqualCompare ]: _gl.EQUAL, + [ GreaterEqualCompare ]: _gl.GEQUAL, + [ GreaterCompare ]: _gl.GREATER, + [ NotEqualCompare ]: _gl.NOTEQUAL + }; - console.warn( 'THREE.ObjectLoader: Undefined material', name ); + function setTextureParameters( textureType, texture ) { - } + if ( texture.type === FloatType && extensions.has( 'OES_texture_float_linear' ) === false && + ( texture.magFilter === LinearFilter || texture.magFilter === LinearMipmapNearestFilter || texture.magFilter === NearestMipmapLinearFilter || texture.magFilter === LinearMipmapLinearFilter || + texture.minFilter === LinearFilter || texture.minFilter === LinearMipmapNearestFilter || texture.minFilter === NearestMipmapLinearFilter || texture.minFilter === LinearMipmapLinearFilter ) ) { - return materials[ name ]; + console.warn( 'THREE.WebGLRenderer: Unable to use linear filtering with floating point textures. OES_texture_float_linear not supported on this device.' ); } - function getTexture( uuid ) { - - if ( textures[ uuid ] === undefined ) { + _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_S, wrappingToGL[ texture.wrapS ] ); + _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_T, wrappingToGL[ texture.wrapT ] ); - console.warn( 'THREE.ObjectLoader: Undefined texture', uuid ); + if ( textureType === _gl.TEXTURE_3D || textureType === _gl.TEXTURE_2D_ARRAY ) { - } - - return textures[ uuid ]; + _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_R, wrappingToGL[ texture.wrapR ] ); } - let geometry, material; + _gl.texParameteri( textureType, _gl.TEXTURE_MAG_FILTER, filterToGL[ texture.magFilter ] ); + _gl.texParameteri( textureType, _gl.TEXTURE_MIN_FILTER, filterToGL[ texture.minFilter ] ); - switch ( data.type ) { + if ( texture.compareFunction ) { - case 'Scene': + _gl.texParameteri( textureType, _gl.TEXTURE_COMPARE_MODE, _gl.COMPARE_REF_TO_TEXTURE ); + _gl.texParameteri( textureType, _gl.TEXTURE_COMPARE_FUNC, compareToGL[ texture.compareFunction ] ); - object = new Scene(); + } - if ( data.background !== undefined ) { + if ( extensions.has( 'EXT_texture_filter_anisotropic' ) === true ) { - if ( Number.isInteger( data.background ) ) { + if ( texture.magFilter === NearestFilter ) return; + if ( texture.minFilter !== NearestMipmapLinearFilter && texture.minFilter !== LinearMipmapLinearFilter ) return; + if ( texture.type === FloatType && extensions.has( 'OES_texture_float_linear' ) === false ) return; // verify extension - object.background = new Color( data.background ); + if ( texture.anisotropy > 1 || properties.get( texture ).__currentAnisotropy ) { - } else { + const extension = extensions.get( 'EXT_texture_filter_anisotropic' ); + _gl.texParameterf( textureType, extension.TEXTURE_MAX_ANISOTROPY_EXT, Math.min( texture.anisotropy, capabilities.getMaxAnisotropy() ) ); + properties.get( texture ).__currentAnisotropy = texture.anisotropy; - object.background = getTexture( data.background ); + } - } + } - } + } - if ( data.environment !== undefined ) { + function initTexture( textureProperties, texture ) { - object.environment = getTexture( data.environment ); + let forceUpload = false; - } + if ( textureProperties.__webglInit === undefined ) { - if ( data.fog !== undefined ) { + textureProperties.__webglInit = true; - if ( data.fog.type === 'Fog' ) { + texture.addEventListener( 'dispose', onTextureDispose ); - object.fog = new Fog( data.fog.color, data.fog.near, data.fog.far ); + } - } else if ( data.fog.type === 'FogExp2' ) { + // create Source <-> WebGLTextures mapping if necessary - object.fog = new FogExp2( data.fog.color, data.fog.density ); + const source = texture.source; + let webglTextures = _sources.get( source ); - } + if ( webglTextures === undefined ) { - } + webglTextures = {}; + _sources.set( source, webglTextures ); - if ( data.backgroundBlurriness !== undefined ) object.backgroundBlurriness = data.backgroundBlurriness; - if ( data.backgroundIntensity !== undefined ) object.backgroundIntensity = data.backgroundIntensity; + } - break; + // check if there is already a WebGLTexture object for the given texture parameters - case 'PerspectiveCamera': + const textureCacheKey = getTextureCacheKey( texture ); - object = new PerspectiveCamera( data.fov, data.aspect, data.near, data.far ); + if ( textureCacheKey !== textureProperties.__cacheKey ) { - if ( data.focus !== undefined ) object.focus = data.focus; - if ( data.zoom !== undefined ) object.zoom = data.zoom; - if ( data.filmGauge !== undefined ) object.filmGauge = data.filmGauge; - if ( data.filmOffset !== undefined ) object.filmOffset = data.filmOffset; - if ( data.view !== undefined ) object.view = Object.assign( {}, data.view ); + // if not, create a new instance of WebGLTexture - break; + if ( webglTextures[ textureCacheKey ] === undefined ) { - case 'OrthographicCamera': + // create new entry - object = new OrthographicCamera( data.left, data.right, data.top, data.bottom, data.near, data.far ); + webglTextures[ textureCacheKey ] = { + texture: _gl.createTexture(), + usedTimes: 0 + }; - if ( data.zoom !== undefined ) object.zoom = data.zoom; - if ( data.view !== undefined ) object.view = Object.assign( {}, data.view ); + info.memory.textures ++; - break; + // when a new instance of WebGLTexture was created, a texture upload is required + // even if the image contents are identical - case 'AmbientLight': + forceUpload = true; - object = new AmbientLight( data.color, data.intensity ); + } - break; + webglTextures[ textureCacheKey ].usedTimes ++; - case 'DirectionalLight': + // every time the texture cache key changes, it's necessary to check if an instance of + // WebGLTexture can be deleted in order to avoid a memory leak. - object = new DirectionalLight( data.color, data.intensity ); + const webglTexture = webglTextures[ textureProperties.__cacheKey ]; - break; + if ( webglTexture !== undefined ) { - case 'PointLight': + webglTextures[ textureProperties.__cacheKey ].usedTimes --; - object = new PointLight( data.color, data.intensity, data.distance, data.decay ); + if ( webglTexture.usedTimes === 0 ) { - break; + deleteTexture( texture ); - case 'RectAreaLight': + } - object = new RectAreaLight( data.color, data.intensity, data.width, data.height ); + } - break; + // store references to cache key and WebGLTexture object - case 'SpotLight': + textureProperties.__cacheKey = textureCacheKey; + textureProperties.__webglTexture = webglTextures[ textureCacheKey ].texture; - object = new SpotLight( data.color, data.intensity, data.distance, data.angle, data.penumbra, data.decay ); + } - break; + return forceUpload; - case 'HemisphereLight': + } - object = new HemisphereLight( data.color, data.groundColor, data.intensity ); + function getRow( index, rowLength, componentStride ) { - break; + return Math.floor( Math.floor( index / componentStride ) / rowLength ); - case 'LightProbe': + } - object = new LightProbe().fromJSON( data ); + function updateTexture( texture, image, glFormat, glType ) { - break; + const componentStride = 4; // only RGBA supported - case 'SkinnedMesh': + const updateRanges = texture.updateRanges; - geometry = getGeometry( data.geometry ); - material = getMaterial( data.material ); + if ( updateRanges.length === 0 ) { - object = new SkinnedMesh( geometry, material ); + state.texSubImage2D( _gl.TEXTURE_2D, 0, 0, 0, image.width, image.height, glFormat, glType, image.data ); - if ( data.bindMode !== undefined ) object.bindMode = data.bindMode; - if ( data.bindMatrix !== undefined ) object.bindMatrix.fromArray( data.bindMatrix ); - if ( data.skeleton !== undefined ) object.skeleton = data.skeleton; + } else { - break; + // Before applying update ranges, we merge any adjacent / overlapping + // ranges to reduce load on `gl.texSubImage2D`. Empirically, this has led + // to performance improvements for applications which make heavy use of + // update ranges. Likely due to GPU command overhead. + // + // Note that to reduce garbage collection between frames, we merge the + // update ranges in-place. This is safe because this method will clear the + // update ranges once updated. + + updateRanges.sort( ( a, b ) => a.start - b.start ); + + // To merge the update ranges in-place, we work from left to right in the + // existing updateRanges array, merging ranges. This may result in a final + // array which is smaller than the original. This index tracks the last + // index representing a merged range, any data after this index can be + // trimmed once the merge algorithm is completed. + let mergeIndex = 0; + + for ( let i = 1; i < updateRanges.length; i ++ ) { + + const previousRange = updateRanges[ mergeIndex ]; + const range = updateRanges[ i ]; + + // Only merge if in the same row and overlapping/adjacent + const previousEnd = previousRange.start + previousRange.count; + const currentRow = getRow( range.start, image.width, componentStride ); + const previousRow = getRow( previousRange.start, image.width, componentStride ); + + // We add one here to merge adjacent ranges. This is safe because ranges + // operate over positive integers. + if ( + range.start <= previousEnd + 1 && + currentRow === previousRow && + getRow( range.start + range.count - 1, image.width, componentStride ) === currentRow // ensure range doesn't spill + ) { + + previousRange.count = Math.max( + previousRange.count, + range.start + range.count - previousRange.start + ); - case 'Mesh': + } else { - geometry = getGeometry( data.geometry ); - material = getMaterial( data.material ); + ++ mergeIndex; + updateRanges[ mergeIndex ] = range; - object = new Mesh( geometry, material ); + } - break; - case 'InstancedMesh': + } - geometry = getGeometry( data.geometry ); - material = getMaterial( data.material ); - const count = data.count; - const instanceMatrix = data.instanceMatrix; - const instanceColor = data.instanceColor; + // Trim the array to only contain the merged ranges. + updateRanges.length = mergeIndex + 1; - object = new InstancedMesh( geometry, material, count ); - object.instanceMatrix = new InstancedBufferAttribute( new Float32Array( instanceMatrix.array ), 16 ); - if ( instanceColor !== undefined ) object.instanceColor = new InstancedBufferAttribute( new Float32Array( instanceColor.array ), instanceColor.itemSize ); + const currentUnpackRowLen = _gl.getParameter( _gl.UNPACK_ROW_LENGTH ); + const currentUnpackSkipPixels = _gl.getParameter( _gl.UNPACK_SKIP_PIXELS ); + const currentUnpackSkipRows = _gl.getParameter( _gl.UNPACK_SKIP_ROWS ); - break; + _gl.pixelStorei( _gl.UNPACK_ROW_LENGTH, image.width ); - case 'LOD': + for ( let i = 0, l = updateRanges.length; i < l; i ++ ) { - object = new LOD(); + const range = updateRanges[ i ]; - break; + const pixelStart = Math.floor( range.start / componentStride ); + const pixelCount = Math.ceil( range.count / componentStride ); - case 'Line': + const x = pixelStart % image.width; + const y = Math.floor( pixelStart / image.width ); - object = new Line( getGeometry( data.geometry ), getMaterial( data.material ) ); + // Assumes update ranges refer to contiguous memory + const width = pixelCount; + const height = 1; - break; + _gl.pixelStorei( _gl.UNPACK_SKIP_PIXELS, x ); + _gl.pixelStorei( _gl.UNPACK_SKIP_ROWS, y ); - case 'LineLoop': + state.texSubImage2D( _gl.TEXTURE_2D, 0, x, y, width, height, glFormat, glType, image.data ); - object = new LineLoop( getGeometry( data.geometry ), getMaterial( data.material ) ); + } - break; + texture.clearUpdateRanges(); - case 'LineSegments': + _gl.pixelStorei( _gl.UNPACK_ROW_LENGTH, currentUnpackRowLen ); + _gl.pixelStorei( _gl.UNPACK_SKIP_PIXELS, currentUnpackSkipPixels ); + _gl.pixelStorei( _gl.UNPACK_SKIP_ROWS, currentUnpackSkipRows ); - object = new LineSegments( getGeometry( data.geometry ), getMaterial( data.material ) ); + } - break; + } - case 'PointCloud': - case 'Points': + function uploadTexture( textureProperties, texture, slot ) { - object = new Points( getGeometry( data.geometry ), getMaterial( data.material ) ); + let textureType = _gl.TEXTURE_2D; - break; + if ( texture.isDataArrayTexture || texture.isCompressedArrayTexture ) textureType = _gl.TEXTURE_2D_ARRAY; + if ( texture.isData3DTexture ) textureType = _gl.TEXTURE_3D; - case 'Sprite': + const forceUpload = initTexture( textureProperties, texture ); + const source = texture.source; - object = new Sprite( getMaterial( data.material ) ); + state.bindTexture( textureType, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); - break; + const sourceProperties = properties.get( source ); - case 'Group': + if ( source.version !== sourceProperties.__version || forceUpload === true ) { - object = new Group(); + state.activeTexture( _gl.TEXTURE0 + slot ); - break; + const workingPrimaries = ColorManagement.getPrimaries( ColorManagement.workingColorSpace ); + const texturePrimaries = texture.colorSpace === NoColorSpace ? null : ColorManagement.getPrimaries( texture.colorSpace ); + const unpackConversion = texture.colorSpace === NoColorSpace || workingPrimaries === texturePrimaries ? _gl.NONE : _gl.BROWSER_DEFAULT_WEBGL; - case 'Bone': + _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY ); + _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha ); + _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, texture.unpackAlignment ); + _gl.pixelStorei( _gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, unpackConversion ); - object = new Bone(); + let image = resizeImage( texture.image, false, capabilities.maxTextureSize ); + image = verifyColorSpace( texture, image ); - break; + const glFormat = utils.convert( texture.format, texture.colorSpace ); - default: + const glType = utils.convert( texture.type ); + let glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace, texture.isVideoTexture ); - object = new Object3D(); + setTextureParameters( textureType, texture ); - } + let mipmap; + const mipmaps = texture.mipmaps; - object.uuid = data.uuid; + const useTexStorage = ( texture.isVideoTexture !== true ); + const allocateMemory = ( sourceProperties.__version === undefined ) || ( forceUpload === true ); + const dataReady = source.dataReady; + const levels = getMipLevels( texture, image ); - if ( data.name !== undefined ) object.name = data.name; + if ( texture.isDepthTexture ) { - if ( data.matrix !== undefined ) { + glInternalFormat = getInternalDepthFormat( texture.format === DepthStencilFormat, texture.type ); - object.matrix.fromArray( data.matrix ); + // - if ( data.matrixAutoUpdate !== undefined ) object.matrixAutoUpdate = data.matrixAutoUpdate; - if ( object.matrixAutoUpdate ) object.matrix.decompose( object.position, object.quaternion, object.scale ); + if ( allocateMemory ) { - } else { + if ( useTexStorage ) { - if ( data.position !== undefined ) object.position.fromArray( data.position ); - if ( data.rotation !== undefined ) object.rotation.fromArray( data.rotation ); - if ( data.quaternion !== undefined ) object.quaternion.fromArray( data.quaternion ); - if ( data.scale !== undefined ) object.scale.fromArray( data.scale ); + state.texStorage2D( _gl.TEXTURE_2D, 1, glInternalFormat, image.width, image.height ); - } + } else { - if ( data.up !== undefined ) object.up.fromArray( data.up ); + state.texImage2D( _gl.TEXTURE_2D, 0, glInternalFormat, image.width, image.height, 0, glFormat, glType, null ); - if ( data.castShadow !== undefined ) object.castShadow = data.castShadow; - if ( data.receiveShadow !== undefined ) object.receiveShadow = data.receiveShadow; + } - if ( data.shadow ) { + } - if ( data.shadow.bias !== undefined ) object.shadow.bias = data.shadow.bias; - if ( data.shadow.normalBias !== undefined ) object.shadow.normalBias = data.shadow.normalBias; - if ( data.shadow.radius !== undefined ) object.shadow.radius = data.shadow.radius; - if ( data.shadow.mapSize !== undefined ) object.shadow.mapSize.fromArray( data.shadow.mapSize ); - if ( data.shadow.camera !== undefined ) object.shadow.camera = this.parseObject( data.shadow.camera ); + } else if ( texture.isDataTexture ) { - } + // use manually created mipmaps if available + // if there are no manual mipmaps + // set 0 level mipmap and then use GL to generate other mipmap levels - if ( data.visible !== undefined ) object.visible = data.visible; - if ( data.frustumCulled !== undefined ) object.frustumCulled = data.frustumCulled; - if ( data.renderOrder !== undefined ) object.renderOrder = data.renderOrder; - if ( data.userData !== undefined ) object.userData = data.userData; - if ( data.layers !== undefined ) object.layers.mask = data.layers; + if ( mipmaps.length > 0 ) { - if ( data.children !== undefined ) { + if ( useTexStorage && allocateMemory ) { - const children = data.children; + state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, mipmaps[ 0 ].width, mipmaps[ 0 ].height ); - for ( let i = 0; i < children.length; i ++ ) { + } - object.add( this.parseObject( children[ i ], geometries, materials, textures, animations ) ); + for ( let i = 0, il = mipmaps.length; i < il; i ++ ) { - } + mipmap = mipmaps[ i ]; - } + if ( useTexStorage ) { - if ( data.animations !== undefined ) { + if ( dataReady ) { - const objectAnimations = data.animations; + state.texSubImage2D( _gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, glType, mipmap.data ); - for ( let i = 0; i < objectAnimations.length; i ++ ) { + } - const uuid = objectAnimations[ i ]; + } else { - object.animations.push( animations[ uuid ] ); + state.texImage2D( _gl.TEXTURE_2D, i, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); - } + } - } + } - if ( data.type === 'LOD' ) { + texture.generateMipmaps = false; - if ( data.autoUpdate !== undefined ) object.autoUpdate = data.autoUpdate; + } else { - const levels = data.levels; + if ( useTexStorage ) { - for ( let l = 0; l < levels.length; l ++ ) { + if ( allocateMemory ) { - const level = levels[ l ]; - const child = object.getObjectByProperty( 'uuid', level.object ); + state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, image.width, image.height ); - if ( child !== undefined ) { + } - object.addLevel( child, level.distance, level.hysteresis ); + if ( dataReady ) { - } + updateTexture( texture, image, glFormat, glType ); - } + } - } + } else { + + state.texImage2D( _gl.TEXTURE_2D, 0, glInternalFormat, image.width, image.height, 0, glFormat, glType, image.data ); - return object; + } - } + } - bindSkeletons( object, skeletons ) { + } else if ( texture.isCompressedTexture ) { - if ( Object.keys( skeletons ).length === 0 ) return; + if ( texture.isCompressedArrayTexture ) { - object.traverse( function ( child ) { + if ( useTexStorage && allocateMemory ) { - if ( child.isSkinnedMesh === true && child.skeleton !== undefined ) { + state.texStorage3D( _gl.TEXTURE_2D_ARRAY, levels, glInternalFormat, mipmaps[ 0 ].width, mipmaps[ 0 ].height, image.depth ); - const skeleton = skeletons[ child.skeleton ]; + } - if ( skeleton === undefined ) { + for ( let i = 0, il = mipmaps.length; i < il; i ++ ) { - console.warn( 'THREE.ObjectLoader: No skeleton found with UUID:', child.skeleton ); + mipmap = mipmaps[ i ]; - } else { + if ( texture.format !== RGBAFormat ) { - child.bind( skeleton, child.bindMatrix ); + if ( glFormat !== null ) { - } + if ( useTexStorage ) { - } + if ( dataReady ) { - } ); + if ( texture.layerUpdates.size > 0 ) { - } + const layerByteLength = getByteLength( mipmap.width, mipmap.height, texture.format, texture.type ); -} + for ( const layerIndex of texture.layerUpdates ) { -const TEXTURE_MAPPING = { - UVMapping: UVMapping, - CubeReflectionMapping: CubeReflectionMapping, - CubeRefractionMapping: CubeRefractionMapping, - EquirectangularReflectionMapping: EquirectangularReflectionMapping, - EquirectangularRefractionMapping: EquirectangularRefractionMapping, - CubeUVReflectionMapping: CubeUVReflectionMapping -}; + const layerData = mipmap.data.subarray( + layerIndex * layerByteLength / mipmap.data.BYTES_PER_ELEMENT, + ( layerIndex + 1 ) * layerByteLength / mipmap.data.BYTES_PER_ELEMENT + ); + state.compressedTexSubImage3D( _gl.TEXTURE_2D_ARRAY, i, 0, 0, layerIndex, mipmap.width, mipmap.height, 1, glFormat, layerData ); -const TEXTURE_WRAPPING = { - RepeatWrapping: RepeatWrapping, - ClampToEdgeWrapping: ClampToEdgeWrapping, - MirroredRepeatWrapping: MirroredRepeatWrapping -}; + } -const TEXTURE_FILTER = { - NearestFilter: NearestFilter, - NearestMipmapNearestFilter: NearestMipmapNearestFilter, - NearestMipmapLinearFilter: NearestMipmapLinearFilter, - LinearFilter: LinearFilter, - LinearMipmapNearestFilter: LinearMipmapNearestFilter, - LinearMipmapLinearFilter: LinearMipmapLinearFilter -}; + texture.clearLayerUpdates(); -class ImageBitmapLoader extends Loader { + } else { - constructor( manager ) { + state.compressedTexSubImage3D( _gl.TEXTURE_2D_ARRAY, i, 0, 0, 0, mipmap.width, mipmap.height, image.depth, glFormat, mipmap.data ); - super( manager ); + } - this.isImageBitmapLoader = true; + } - if ( typeof createImageBitmap === 'undefined' ) { + } else { - console.warn( 'THREE.ImageBitmapLoader: createImageBitmap() not supported.' ); + state.compressedTexImage3D( _gl.TEXTURE_2D_ARRAY, i, glInternalFormat, mipmap.width, mipmap.height, image.depth, 0, mipmap.data, 0, 0 ); - } + } - if ( typeof fetch === 'undefined' ) { + } else { - console.warn( 'THREE.ImageBitmapLoader: fetch() not supported.' ); + console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()' ); - } + } - this.options = { premultiplyAlpha: 'none' }; + } else { - } + if ( useTexStorage ) { - setOptions( options ) { + if ( dataReady ) { - this.options = options; + state.texSubImage3D( _gl.TEXTURE_2D_ARRAY, i, 0, 0, 0, mipmap.width, mipmap.height, image.depth, glFormat, glType, mipmap.data ); - return this; + } - } + } else { - load( url, onLoad, onProgress, onError ) { + state.texImage3D( _gl.TEXTURE_2D_ARRAY, i, glInternalFormat, mipmap.width, mipmap.height, image.depth, 0, glFormat, glType, mipmap.data ); - if ( url === undefined ) url = ''; + } - if ( this.path !== undefined ) url = this.path + url; + } - url = this.manager.resolveURL( url ); + } - const scope = this; + } else { - const cached = Cache.get( url ); + if ( useTexStorage && allocateMemory ) { - if ( cached !== undefined ) { + state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, mipmaps[ 0 ].width, mipmaps[ 0 ].height ); - scope.manager.itemStart( url ); + } - setTimeout( function () { + for ( let i = 0, il = mipmaps.length; i < il; i ++ ) { - if ( onLoad ) onLoad( cached ); + mipmap = mipmaps[ i ]; - scope.manager.itemEnd( url ); + if ( texture.format !== RGBAFormat ) { - }, 0 ); + if ( glFormat !== null ) { - return cached; + if ( useTexStorage ) { - } + if ( dataReady ) { - const fetchOptions = {}; - fetchOptions.credentials = ( this.crossOrigin === 'anonymous' ) ? 'same-origin' : 'include'; - fetchOptions.headers = this.requestHeader; + state.compressedTexSubImage2D( _gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, mipmap.data ); - fetch( url, fetchOptions ).then( function ( res ) { + } - return res.blob(); + } else { - } ).then( function ( blob ) { + state.compressedTexImage2D( _gl.TEXTURE_2D, i, glInternalFormat, mipmap.width, mipmap.height, 0, mipmap.data ); - return createImageBitmap( blob, Object.assign( scope.options, { colorSpaceConversion: 'none' } ) ); + } - } ).then( function ( imageBitmap ) { + } else { - Cache.add( url, imageBitmap ); + console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()' ); - if ( onLoad ) onLoad( imageBitmap ); + } - scope.manager.itemEnd( url ); + } else { - } ).catch( function ( e ) { + if ( useTexStorage ) { - if ( onError ) onError( e ); + if ( dataReady ) { - scope.manager.itemError( url ); - scope.manager.itemEnd( url ); + state.texSubImage2D( _gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, glType, mipmap.data ); - } ); + } - scope.manager.itemStart( url ); + } else { - } + state.texImage2D( _gl.TEXTURE_2D, i, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); -} + } -let _context; + } -class AudioContext { + } - static getContext() { + } - if ( _context === undefined ) { + } else if ( texture.isDataArrayTexture ) { - _context = new ( window.AudioContext || window.webkitAudioContext )(); + if ( useTexStorage ) { - } + if ( allocateMemory ) { - return _context; + state.texStorage3D( _gl.TEXTURE_2D_ARRAY, levels, glInternalFormat, image.width, image.height, image.depth ); - } + } - static setContext( value ) { + if ( dataReady ) { - _context = value; + if ( texture.layerUpdates.size > 0 ) { - } + const layerByteLength = getByteLength( image.width, image.height, texture.format, texture.type ); -} + for ( const layerIndex of texture.layerUpdates ) { -class AudioLoader extends Loader { + const layerData = image.data.subarray( + layerIndex * layerByteLength / image.data.BYTES_PER_ELEMENT, + ( layerIndex + 1 ) * layerByteLength / image.data.BYTES_PER_ELEMENT + ); + state.texSubImage3D( _gl.TEXTURE_2D_ARRAY, 0, 0, 0, layerIndex, image.width, image.height, 1, glFormat, glType, layerData ); - constructor( manager ) { + } - super( manager ); + texture.clearLayerUpdates(); - } + } else { - load( url, onLoad, onProgress, onError ) { + state.texSubImage3D( _gl.TEXTURE_2D_ARRAY, 0, 0, 0, 0, image.width, image.height, image.depth, glFormat, glType, image.data ); - const scope = this; + } - const loader = new FileLoader( this.manager ); - loader.setResponseType( 'arraybuffer' ); - loader.setPath( this.path ); - loader.setRequestHeader( this.requestHeader ); - loader.setWithCredentials( this.withCredentials ); - loader.load( url, function ( buffer ) { + } - try { + } else { - // Create a copy of the buffer. The `decodeAudioData` method - // detaches the buffer when complete, preventing reuse. - const bufferCopy = buffer.slice( 0 ); + state.texImage3D( _gl.TEXTURE_2D_ARRAY, 0, glInternalFormat, image.width, image.height, image.depth, 0, glFormat, glType, image.data ); - const context = AudioContext.getContext(); - context.decodeAudioData( bufferCopy, function ( audioBuffer ) { + } - onLoad( audioBuffer ); + } else if ( texture.isData3DTexture ) { - }, handleError ); + if ( useTexStorage ) { - } catch ( e ) { + if ( allocateMemory ) { - handleError( e ); + state.texStorage3D( _gl.TEXTURE_3D, levels, glInternalFormat, image.width, image.height, image.depth ); - } + } - }, onProgress, onError ); + if ( dataReady ) { - function handleError( e ) { + state.texSubImage3D( _gl.TEXTURE_3D, 0, 0, 0, 0, image.width, image.height, image.depth, glFormat, glType, image.data ); - if ( onError ) { + } - onError( e ); + } else { - } else { + state.texImage3D( _gl.TEXTURE_3D, 0, glInternalFormat, image.width, image.height, image.depth, 0, glFormat, glType, image.data ); - console.error( e ); + } - } + } else if ( texture.isFramebufferTexture ) { - scope.manager.itemError( url ); + if ( allocateMemory ) { - } + if ( useTexStorage ) { - } + state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, image.width, image.height ); -} + } else { -class HemisphereLightProbe extends LightProbe { + let width = image.width, height = image.height; - constructor( skyColor, groundColor, intensity = 1 ) { + for ( let i = 0; i < levels; i ++ ) { - super( undefined, intensity ); + state.texImage2D( _gl.TEXTURE_2D, i, glInternalFormat, width, height, 0, glFormat, glType, null ); - this.isHemisphereLightProbe = true; + width >>= 1; + height >>= 1; - const color1 = new Color().set( skyColor ); - const color2 = new Color().set( groundColor ); + } - const sky = new Vector3( color1.r, color1.g, color1.b ); - const ground = new Vector3( color2.r, color2.g, color2.b ); + } - // without extra factor of PI in the shader, should = 1 / Math.sqrt( Math.PI ); - const c0 = Math.sqrt( Math.PI ); - const c1 = c0 * Math.sqrt( 0.75 ); + } - this.sh.coefficients[ 0 ].copy( sky ).add( ground ).multiplyScalar( c0 ); - this.sh.coefficients[ 1 ].copy( sky ).sub( ground ).multiplyScalar( c1 ); + } else { - } + // regular Texture (image, video, canvas) -} + // use manually created mipmaps if available + // if there are no manual mipmaps + // set 0 level mipmap and then use GL to generate other mipmap levels -class AmbientLightProbe extends LightProbe { + if ( mipmaps.length > 0 ) { - constructor( color, intensity = 1 ) { + if ( useTexStorage && allocateMemory ) { - super( undefined, intensity ); + const dimensions = getDimensions( mipmaps[ 0 ] ); - this.isAmbientLightProbe = true; + state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, dimensions.width, dimensions.height ); - const color1 = new Color().set( color ); + } - // without extra factor of PI in the shader, would be 2 / Math.sqrt( Math.PI ); - this.sh.coefficients[ 0 ].set( color1.r, color1.g, color1.b ).multiplyScalar( 2 * Math.sqrt( Math.PI ) ); + for ( let i = 0, il = mipmaps.length; i < il; i ++ ) { - } + mipmap = mipmaps[ i ]; -} + if ( useTexStorage ) { -const _eyeRight = /*@__PURE__*/ new Matrix4(); -const _eyeLeft = /*@__PURE__*/ new Matrix4(); -const _projectionMatrix = /*@__PURE__*/ new Matrix4(); + if ( dataReady ) { -class StereoCamera { + state.texSubImage2D( _gl.TEXTURE_2D, i, 0, 0, glFormat, glType, mipmap ); - constructor() { + } - this.type = 'StereoCamera'; + } else { - this.aspect = 1; + state.texImage2D( _gl.TEXTURE_2D, i, glInternalFormat, glFormat, glType, mipmap ); - this.eyeSep = 0.064; + } - this.cameraL = new PerspectiveCamera(); - this.cameraL.layers.enable( 1 ); - this.cameraL.matrixAutoUpdate = false; + } - this.cameraR = new PerspectiveCamera(); - this.cameraR.layers.enable( 2 ); - this.cameraR.matrixAutoUpdate = false; + texture.generateMipmaps = false; - this._cache = { - focus: null, - fov: null, - aspect: null, - near: null, - far: null, - zoom: null, - eyeSep: null - }; + } else { - } + if ( useTexStorage ) { - update( camera ) { + if ( allocateMemory ) { - const cache = this._cache; + const dimensions = getDimensions( image ); - const needsUpdate = cache.focus !== camera.focus || cache.fov !== camera.fov || - cache.aspect !== camera.aspect * this.aspect || cache.near !== camera.near || - cache.far !== camera.far || cache.zoom !== camera.zoom || cache.eyeSep !== this.eyeSep; + state.texStorage2D( _gl.TEXTURE_2D, levels, glInternalFormat, dimensions.width, dimensions.height ); - if ( needsUpdate ) { + } - cache.focus = camera.focus; - cache.fov = camera.fov; - cache.aspect = camera.aspect * this.aspect; - cache.near = camera.near; - cache.far = camera.far; - cache.zoom = camera.zoom; - cache.eyeSep = this.eyeSep; + if ( dataReady ) { - // Off-axis stereoscopic effect based on - // http://paulbourke.net/stereographics/stereorender/ + state.texSubImage2D( _gl.TEXTURE_2D, 0, 0, 0, glFormat, glType, image ); - _projectionMatrix.copy( camera.projectionMatrix ); - const eyeSepHalf = cache.eyeSep / 2; - const eyeSepOnProjection = eyeSepHalf * cache.near / cache.focus; - const ymax = ( cache.near * Math.tan( DEG2RAD * cache.fov * 0.5 ) ) / cache.zoom; - let xmin, xmax; + } - // translate xOffset + } else { - _eyeLeft.elements[ 12 ] = - eyeSepHalf; - _eyeRight.elements[ 12 ] = eyeSepHalf; + state.texImage2D( _gl.TEXTURE_2D, 0, glInternalFormat, glFormat, glType, image ); - // for left eye + } - xmin = - ymax * cache.aspect + eyeSepOnProjection; - xmax = ymax * cache.aspect + eyeSepOnProjection; + } - _projectionMatrix.elements[ 0 ] = 2 * cache.near / ( xmax - xmin ); - _projectionMatrix.elements[ 8 ] = ( xmax + xmin ) / ( xmax - xmin ); + } - this.cameraL.projectionMatrix.copy( _projectionMatrix ); + if ( textureNeedsGenerateMipmaps( texture ) ) { - // for right eye + generateMipmap( textureType ); - xmin = - ymax * cache.aspect - eyeSepOnProjection; - xmax = ymax * cache.aspect - eyeSepOnProjection; + } - _projectionMatrix.elements[ 0 ] = 2 * cache.near / ( xmax - xmin ); - _projectionMatrix.elements[ 8 ] = ( xmax + xmin ) / ( xmax - xmin ); + sourceProperties.__version = source.version; - this.cameraR.projectionMatrix.copy( _projectionMatrix ); + if ( texture.onUpdate ) texture.onUpdate( texture ); } - this.cameraL.matrixWorld.copy( camera.matrixWorld ).multiply( _eyeLeft ); - this.cameraR.matrixWorld.copy( camera.matrixWorld ).multiply( _eyeRight ); + textureProperties.__version = texture.version; } -} + function uploadCubeTexture( textureProperties, texture, slot ) { -class Clock { + if ( texture.image.length !== 6 ) return; - constructor( autoStart = true ) { + const forceUpload = initTexture( textureProperties, texture ); + const source = texture.source; - this.autoStart = autoStart; + state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__webglTexture, _gl.TEXTURE0 + slot ); - this.startTime = 0; - this.oldTime = 0; - this.elapsedTime = 0; + const sourceProperties = properties.get( source ); - this.running = false; + if ( source.version !== sourceProperties.__version || forceUpload === true ) { - } + state.activeTexture( _gl.TEXTURE0 + slot ); - start() { + const workingPrimaries = ColorManagement.getPrimaries( ColorManagement.workingColorSpace ); + const texturePrimaries = texture.colorSpace === NoColorSpace ? null : ColorManagement.getPrimaries( texture.colorSpace ); + const unpackConversion = texture.colorSpace === NoColorSpace || workingPrimaries === texturePrimaries ? _gl.NONE : _gl.BROWSER_DEFAULT_WEBGL; - this.startTime = now(); + _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY ); + _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha ); + _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, texture.unpackAlignment ); + _gl.pixelStorei( _gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, unpackConversion ); - this.oldTime = this.startTime; - this.elapsedTime = 0; - this.running = true; + const isCompressed = ( texture.isCompressedTexture || texture.image[ 0 ].isCompressedTexture ); + const isDataTexture = ( texture.image[ 0 ] && texture.image[ 0 ].isDataTexture ); - } + const cubeImage = []; - stop() { + for ( let i = 0; i < 6; i ++ ) { - this.getElapsedTime(); - this.running = false; - this.autoStart = false; + if ( ! isCompressed && ! isDataTexture ) { - } + cubeImage[ i ] = resizeImage( texture.image[ i ], true, capabilities.maxCubemapSize ); - getElapsedTime() { + } else { - this.getDelta(); - return this.elapsedTime; + cubeImage[ i ] = isDataTexture ? texture.image[ i ].image : texture.image[ i ]; - } + } - getDelta() { + cubeImage[ i ] = verifyColorSpace( texture, cubeImage[ i ] ); - let diff = 0; + } - if ( this.autoStart && ! this.running ) { + const image = cubeImage[ 0 ], + glFormat = utils.convert( texture.format, texture.colorSpace ), + glType = utils.convert( texture.type ), + glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace ); - this.start(); - return 0; + const useTexStorage = ( texture.isVideoTexture !== true ); + const allocateMemory = ( sourceProperties.__version === undefined ) || ( forceUpload === true ); + const dataReady = source.dataReady; + let levels = getMipLevels( texture, image ); - } + setTextureParameters( _gl.TEXTURE_CUBE_MAP, texture ); - if ( this.running ) { + let mipmaps; - const newTime = now(); + if ( isCompressed ) { - diff = ( newTime - this.oldTime ) / 1000; - this.oldTime = newTime; + if ( useTexStorage && allocateMemory ) { - this.elapsedTime += diff; + state.texStorage2D( _gl.TEXTURE_CUBE_MAP, levels, glInternalFormat, image.width, image.height ); - } + } - return diff; + for ( let i = 0; i < 6; i ++ ) { - } + mipmaps = cubeImage[ i ].mipmaps; -} + for ( let j = 0; j < mipmaps.length; j ++ ) { -function now() { + const mipmap = mipmaps[ j ]; - return ( typeof performance === 'undefined' ? Date : performance ).now(); // see #10732 + if ( texture.format !== RGBAFormat ) { -} + if ( glFormat !== null ) { -const _position$1 = /*@__PURE__*/ new Vector3(); -const _quaternion$1 = /*@__PURE__*/ new Quaternion(); -const _scale$1 = /*@__PURE__*/ new Vector3(); -const _orientation$1 = /*@__PURE__*/ new Vector3(); + if ( useTexStorage ) { -class AudioListener extends Object3D { + if ( dataReady ) { - constructor() { + state.compressedTexSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, 0, 0, mipmap.width, mipmap.height, glFormat, mipmap.data ); - super(); + } - this.type = 'AudioListener'; + } else { - this.context = AudioContext.getContext(); + state.compressedTexImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glInternalFormat, mipmap.width, mipmap.height, 0, mipmap.data ); - this.gain = this.context.createGain(); - this.gain.connect( this.context.destination ); + } - this.filter = null; + } else { - this.timeDelta = 0; + console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .setTextureCube()' ); - // private + } - this._clock = new Clock(); + } else { - } + if ( useTexStorage ) { - getInput() { + if ( dataReady ) { - return this.gain; + state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, 0, 0, mipmap.width, mipmap.height, glFormat, glType, mipmap.data ); - } + } - removeFilter() { + } else { - if ( this.filter !== null ) { + state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); - this.gain.disconnect( this.filter ); - this.filter.disconnect( this.context.destination ); - this.gain.connect( this.context.destination ); - this.filter = null; + } - } + } - return this; + } - } + } - getFilter() { + } else { - return this.filter; + mipmaps = texture.mipmaps; - } + if ( useTexStorage && allocateMemory ) { - setFilter( value ) { + // TODO: Uniformly handle mipmap definitions + // Normal textures and compressed cube textures define base level + mips with their mipmap array + // Uncompressed cube textures use their mipmap array only for mips (no base level) - if ( this.filter !== null ) { + if ( mipmaps.length > 0 ) levels ++; - this.gain.disconnect( this.filter ); - this.filter.disconnect( this.context.destination ); + const dimensions = getDimensions( cubeImage[ 0 ] ); - } else { + state.texStorage2D( _gl.TEXTURE_CUBE_MAP, levels, glInternalFormat, dimensions.width, dimensions.height ); - this.gain.disconnect( this.context.destination ); + } - } + for ( let i = 0; i < 6; i ++ ) { - this.filter = value; - this.gain.connect( this.filter ); - this.filter.connect( this.context.destination ); + if ( isDataTexture ) { - return this; + if ( useTexStorage ) { - } + if ( dataReady ) { - getMasterVolume() { + state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, 0, 0, cubeImage[ i ].width, cubeImage[ i ].height, glFormat, glType, cubeImage[ i ].data ); - return this.gain.gain.value; + } - } + } else { - setMasterVolume( value ) { + state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, cubeImage[ i ].width, cubeImage[ i ].height, 0, glFormat, glType, cubeImage[ i ].data ); - this.gain.gain.setTargetAtTime( value, this.context.currentTime, 0.01 ); + } - return this; + for ( let j = 0; j < mipmaps.length; j ++ ) { - } + const mipmap = mipmaps[ j ]; + const mipmapImage = mipmap.image[ i ].image; - updateMatrixWorld( force ) { + if ( useTexStorage ) { - super.updateMatrixWorld( force ); + if ( dataReady ) { - const listener = this.context.listener; - const up = this.up; + state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, 0, 0, mipmapImage.width, mipmapImage.height, glFormat, glType, mipmapImage.data ); - this.timeDelta = this._clock.getDelta(); + } - this.matrixWorld.decompose( _position$1, _quaternion$1, _scale$1 ); + } else { - _orientation$1.set( 0, 0, - 1 ).applyQuaternion( _quaternion$1 ); + state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, glInternalFormat, mipmapImage.width, mipmapImage.height, 0, glFormat, glType, mipmapImage.data ); - if ( listener.positionX ) { + } - // code path for Chrome (see #14393) + } - const endTime = this.context.currentTime + this.timeDelta; + } else { - listener.positionX.linearRampToValueAtTime( _position$1.x, endTime ); - listener.positionY.linearRampToValueAtTime( _position$1.y, endTime ); - listener.positionZ.linearRampToValueAtTime( _position$1.z, endTime ); - listener.forwardX.linearRampToValueAtTime( _orientation$1.x, endTime ); - listener.forwardY.linearRampToValueAtTime( _orientation$1.y, endTime ); - listener.forwardZ.linearRampToValueAtTime( _orientation$1.z, endTime ); - listener.upX.linearRampToValueAtTime( up.x, endTime ); - listener.upY.linearRampToValueAtTime( up.y, endTime ); - listener.upZ.linearRampToValueAtTime( up.z, endTime ); + if ( useTexStorage ) { - } else { + if ( dataReady ) { - listener.setPosition( _position$1.x, _position$1.y, _position$1.z ); - listener.setOrientation( _orientation$1.x, _orientation$1.y, _orientation$1.z, up.x, up.y, up.z ); + state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, 0, 0, glFormat, glType, cubeImage[ i ] ); - } + } - } + } else { -} + state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, glFormat, glType, cubeImage[ i ] ); -class Audio extends Object3D { + } - constructor( listener ) { + for ( let j = 0; j < mipmaps.length; j ++ ) { - super(); + const mipmap = mipmaps[ j ]; - this.type = 'Audio'; + if ( useTexStorage ) { - this.listener = listener; - this.context = listener.context; + if ( dataReady ) { - this.gain = this.context.createGain(); - this.gain.connect( listener.getInput() ); + state.texSubImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, 0, 0, glFormat, glType, mipmap.image[ i ] ); - this.autoplay = false; + } - this.buffer = null; - this.detune = 0; - this.loop = false; - this.loopStart = 0; - this.loopEnd = 0; - this.offset = 0; - this.duration = undefined; - this.playbackRate = 1; - this.isPlaying = false; - this.hasPlaybackControl = true; - this.source = null; - this.sourceType = 'empty'; + } else { - this._startedAt = 0; - this._progress = 0; - this._connected = false; + state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, glInternalFormat, glFormat, glType, mipmap.image[ i ] ); - this.filters = []; + } - } + } - getOutput() { + } - return this.gain; + } - } + } - setNodeSource( audioNode ) { + if ( textureNeedsGenerateMipmaps( texture ) ) { - this.hasPlaybackControl = false; - this.sourceType = 'audioNode'; - this.source = audioNode; - this.connect(); + // We assume images for cube map have the same size. + generateMipmap( _gl.TEXTURE_CUBE_MAP ); - return this; + } - } + sourceProperties.__version = source.version; - setMediaElementSource( mediaElement ) { + if ( texture.onUpdate ) texture.onUpdate( texture ); - this.hasPlaybackControl = false; - this.sourceType = 'mediaNode'; - this.source = this.context.createMediaElementSource( mediaElement ); - this.connect(); + } - return this; + textureProperties.__version = texture.version; } - setMediaStreamSource( mediaStream ) { - - this.hasPlaybackControl = false; - this.sourceType = 'mediaStreamNode'; - this.source = this.context.createMediaStreamSource( mediaStream ); - this.connect(); + // Render targets - return this; + // Setup storage for target texture and bind it to correct framebuffer + function setupFrameBufferTexture( framebuffer, renderTarget, texture, attachment, textureTarget, level ) { - } + const glFormat = utils.convert( texture.format, texture.colorSpace ); + const glType = utils.convert( texture.type ); + const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace ); + const renderTargetProperties = properties.get( renderTarget ); + const textureProperties = properties.get( texture ); - setBuffer( audioBuffer ) { + textureProperties.__renderTarget = renderTarget; - this.buffer = audioBuffer; - this.sourceType = 'buffer'; + if ( ! renderTargetProperties.__hasExternalTextures ) { - if ( this.autoplay ) this.play(); + const width = Math.max( 1, renderTarget.width >> level ); + const height = Math.max( 1, renderTarget.height >> level ); - return this; + if ( textureTarget === _gl.TEXTURE_3D || textureTarget === _gl.TEXTURE_2D_ARRAY ) { - } + state.texImage3D( textureTarget, level, glInternalFormat, width, height, renderTarget.depth, 0, glFormat, glType, null ); - play( delay = 0 ) { + } else { - if ( this.isPlaying === true ) { + state.texImage2D( textureTarget, level, glInternalFormat, width, height, 0, glFormat, glType, null ); - console.warn( 'THREE.Audio: Audio is already playing.' ); - return; + } } - if ( this.hasPlaybackControl === false ) { - - console.warn( 'THREE.Audio: this Audio has no playback control.' ); - return; - - } + state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); - this._startedAt = this.context.currentTime + delay; + if ( useMultisampledRTT( renderTarget ) ) { - const source = this.context.createBufferSource(); - source.buffer = this.buffer; - source.loop = this.loop; - source.loopStart = this.loopStart; - source.loopEnd = this.loopEnd; - source.onended = this.onEnded.bind( this ); - source.start( this._startedAt, this._progress + this.offset, this.duration ); + multisampledRTTExt.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, attachment, textureTarget, textureProperties.__webglTexture, 0, getRenderTargetSamples( renderTarget ) ); - this.isPlaying = true; + } else if ( textureTarget === _gl.TEXTURE_2D || ( textureTarget >= _gl.TEXTURE_CUBE_MAP_POSITIVE_X && textureTarget <= _gl.TEXTURE_CUBE_MAP_NEGATIVE_Z ) ) { // see #24753 - this.source = source; + _gl.framebufferTexture2D( _gl.FRAMEBUFFER, attachment, textureTarget, textureProperties.__webglTexture, level ); - this.setDetune( this.detune ); - this.setPlaybackRate( this.playbackRate ); + } - return this.connect(); + state.bindFramebuffer( _gl.FRAMEBUFFER, null ); } - pause() { + // Setup storage for internal depth/stencil buffers and bind to correct framebuffer + function setupRenderBufferStorage( renderbuffer, renderTarget, isMultisample ) { - if ( this.hasPlaybackControl === false ) { + _gl.bindRenderbuffer( _gl.RENDERBUFFER, renderbuffer ); - console.warn( 'THREE.Audio: this Audio has no playback control.' ); - return; + if ( renderTarget.depthBuffer ) { - } + // retrieve the depth attachment types + const depthTexture = renderTarget.depthTexture; + const depthType = depthTexture && depthTexture.isDepthTexture ? depthTexture.type : null; + const glInternalFormat = getInternalDepthFormat( renderTarget.stencilBuffer, depthType ); + const glAttachmentType = renderTarget.stencilBuffer ? _gl.DEPTH_STENCIL_ATTACHMENT : _gl.DEPTH_ATTACHMENT; - if ( this.isPlaying === true ) { + // set up the attachment + const samples = getRenderTargetSamples( renderTarget ); + const isUseMultisampledRTT = useMultisampledRTT( renderTarget ); + if ( isUseMultisampledRTT ) { - // update current progress + multisampledRTTExt.renderbufferStorageMultisampleEXT( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); - this._progress += Math.max( this.context.currentTime - this._startedAt, 0 ) * this.playbackRate; + } else if ( isMultisample ) { - if ( this.loop === true ) { + _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); - // ensure _progress does not exceed duration with looped audios + } else { - this._progress = this._progress % ( this.duration || this.buffer.duration ); + _gl.renderbufferStorage( _gl.RENDERBUFFER, glInternalFormat, renderTarget.width, renderTarget.height ); } - this.source.stop(); - this.source.onended = null; + _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, glAttachmentType, _gl.RENDERBUFFER, renderbuffer ); + + } else { - this.isPlaying = false; + const textures = renderTarget.textures; - } + for ( let i = 0; i < textures.length; i ++ ) { - return this; + const texture = textures[ i ]; - } + const glFormat = utils.convert( texture.format, texture.colorSpace ); + const glType = utils.convert( texture.type ); + const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace ); + const samples = getRenderTargetSamples( renderTarget ); + + if ( isMultisample && useMultisampledRTT( renderTarget ) === false ) { - stop() { + _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); - if ( this.hasPlaybackControl === false ) { + } else if ( useMultisampledRTT( renderTarget ) ) { - console.warn( 'THREE.Audio: this Audio has no playback control.' ); - return; + multisampledRTTExt.renderbufferStorageMultisampleEXT( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); - } + } else { - this._progress = 0; + _gl.renderbufferStorage( _gl.RENDERBUFFER, glInternalFormat, renderTarget.width, renderTarget.height ); - if ( this.source !== null ) { + } - this.source.stop(); - this.source.onended = null; + } } - this.isPlaying = false; - - return this; + _gl.bindRenderbuffer( _gl.RENDERBUFFER, null ); } - connect() { + // Setup resources for a Depth Texture for a FBO (needs an extension) + function setupDepthTexture( framebuffer, renderTarget ) { - if ( this.filters.length > 0 ) { + const isCube = ( renderTarget && renderTarget.isWebGLCubeRenderTarget ); + if ( isCube ) throw new Error( 'Depth Texture with cube render targets is not supported' ); - this.source.connect( this.filters[ 0 ] ); + state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); - for ( let i = 1, l = this.filters.length; i < l; i ++ ) { + if ( ! ( renderTarget.depthTexture && renderTarget.depthTexture.isDepthTexture ) ) { - this.filters[ i - 1 ].connect( this.filters[ i ] ); + throw new Error( 'renderTarget.depthTexture must be an instance of THREE.DepthTexture' ); - } + } - this.filters[ this.filters.length - 1 ].connect( this.getOutput() ); + const textureProperties = properties.get( renderTarget.depthTexture ); + textureProperties.__renderTarget = renderTarget; - } else { + // upload an empty depth texture with framebuffer size + if ( ! textureProperties.__webglTexture || + renderTarget.depthTexture.image.width !== renderTarget.width || + renderTarget.depthTexture.image.height !== renderTarget.height ) { - this.source.connect( this.getOutput() ); + renderTarget.depthTexture.image.width = renderTarget.width; + renderTarget.depthTexture.image.height = renderTarget.height; + renderTarget.depthTexture.needsUpdate = true; } - this._connected = true; - - return this; + setTexture2D( renderTarget.depthTexture, 0 ); - } + const webglDepthTexture = textureProperties.__webglTexture; + const samples = getRenderTargetSamples( renderTarget ); - disconnect() { + if ( renderTarget.depthTexture.format === DepthFormat ) { - if ( this.filters.length > 0 ) { + if ( useMultisampledRTT( renderTarget ) ) { - this.source.disconnect( this.filters[ 0 ] ); + multisampledRTTExt.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0, samples ); - for ( let i = 1, l = this.filters.length; i < l; i ++ ) { + } else { - this.filters[ i - 1 ].disconnect( this.filters[ i ] ); + _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0 ); } - this.filters[ this.filters.length - 1 ].disconnect( this.getOutput() ); + } else if ( renderTarget.depthTexture.format === DepthStencilFormat ) { - } else { + if ( useMultisampledRTT( renderTarget ) ) { - this.source.disconnect( this.getOutput() ); + multisampledRTTExt.framebufferTexture2DMultisampleEXT( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0, samples ); - } + } else { - this._connected = false; + _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0 ); - return this; + } - } + } else { - getFilters() { + throw new Error( 'Unknown depthTexture format' ); - return this.filters; + } } - setFilters( value ) { - - if ( ! value ) value = []; + // Setup GL resources for a non-texture depth buffer + function setupDepthRenderbuffer( renderTarget ) { - if ( this._connected === true ) { + const renderTargetProperties = properties.get( renderTarget ); + const isCube = ( renderTarget.isWebGLCubeRenderTarget === true ); - this.disconnect(); - this.filters = value.slice(); - this.connect(); + // if the bound depth texture has changed + if ( renderTargetProperties.__boundDepthTexture !== renderTarget.depthTexture ) { - } else { + // fire the dispose event to get rid of stored state associated with the previously bound depth buffer + const depthTexture = renderTarget.depthTexture; + if ( renderTargetProperties.__depthDisposeCallback ) { - this.filters = value.slice(); + renderTargetProperties.__depthDisposeCallback(); - } + } - return this; + // set up dispose listeners to track when the currently attached buffer is implicitly unbound + if ( depthTexture ) { - } + const disposeEvent = () => { - setDetune( value ) { + delete renderTargetProperties.__boundDepthTexture; + delete renderTargetProperties.__depthDisposeCallback; + depthTexture.removeEventListener( 'dispose', disposeEvent ); - this.detune = value; + }; - if ( this.source.detune === undefined ) return; // only set detune when available + depthTexture.addEventListener( 'dispose', disposeEvent ); + renderTargetProperties.__depthDisposeCallback = disposeEvent; - if ( this.isPlaying === true ) { + } - this.source.detune.setTargetAtTime( this.detune, this.context.currentTime, 0.01 ); + renderTargetProperties.__boundDepthTexture = depthTexture; } - return this; + if ( renderTarget.depthTexture && ! renderTargetProperties.__autoAllocateDepthBuffer ) { - } + if ( isCube ) throw new Error( 'target.depthTexture not supported in Cube render targets' ); - getDetune() { + const mipmaps = renderTarget.texture.mipmaps; - return this.detune; + if ( mipmaps && mipmaps.length > 0 ) { - } + setupDepthTexture( renderTargetProperties.__webglFramebuffer[ 0 ], renderTarget ); - getFilter() { + } else { - return this.getFilters()[ 0 ]; + setupDepthTexture( renderTargetProperties.__webglFramebuffer, renderTarget ); - } + } - setFilter( filter ) { + } else { - return this.setFilters( filter ? [ filter ] : [] ); + if ( isCube ) { - } + renderTargetProperties.__webglDepthbuffer = []; - setPlaybackRate( value ) { + for ( let i = 0; i < 6; i ++ ) { - if ( this.hasPlaybackControl === false ) { + state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer[ i ] ); - console.warn( 'THREE.Audio: this Audio has no playback control.' ); - return; + if ( renderTargetProperties.__webglDepthbuffer[ i ] === undefined ) { - } + renderTargetProperties.__webglDepthbuffer[ i ] = _gl.createRenderbuffer(); + setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer[ i ], renderTarget, false ); - this.playbackRate = value; + } else { - if ( this.isPlaying === true ) { + // attach buffer if it's been created already + const glAttachmentType = renderTarget.stencilBuffer ? _gl.DEPTH_STENCIL_ATTACHMENT : _gl.DEPTH_ATTACHMENT; + const renderbuffer = renderTargetProperties.__webglDepthbuffer[ i ]; + _gl.bindRenderbuffer( _gl.RENDERBUFFER, renderbuffer ); + _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, glAttachmentType, _gl.RENDERBUFFER, renderbuffer ); - this.source.playbackRate.setTargetAtTime( this.playbackRate, this.context.currentTime, 0.01 ); + } - } + } - return this; + } else { - } + const mipmaps = renderTarget.texture.mipmaps; - getPlaybackRate() { + if ( mipmaps && mipmaps.length > 0 ) { - return this.playbackRate; + state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer[ 0 ] ); - } + } else { - onEnded() { + state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); + + } - this.isPlaying = false; + if ( renderTargetProperties.__webglDepthbuffer === undefined ) { - } + renderTargetProperties.__webglDepthbuffer = _gl.createRenderbuffer(); + setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer, renderTarget, false ); - getLoop() { + } else { - if ( this.hasPlaybackControl === false ) { + // attach buffer if it's been created already + const glAttachmentType = renderTarget.stencilBuffer ? _gl.DEPTH_STENCIL_ATTACHMENT : _gl.DEPTH_ATTACHMENT; + const renderbuffer = renderTargetProperties.__webglDepthbuffer; + _gl.bindRenderbuffer( _gl.RENDERBUFFER, renderbuffer ); + _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, glAttachmentType, _gl.RENDERBUFFER, renderbuffer ); - console.warn( 'THREE.Audio: this Audio has no playback control.' ); - return false; + } + + } } - return this.loop; + state.bindFramebuffer( _gl.FRAMEBUFFER, null ); } - setLoop( value ) { + // rebind framebuffer with external textures + function rebindTextures( renderTarget, colorTexture, depthTexture ) { + + const renderTargetProperties = properties.get( renderTarget ); - if ( this.hasPlaybackControl === false ) { + if ( colorTexture !== undefined ) { - console.warn( 'THREE.Audio: this Audio has no playback control.' ); - return; + setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, renderTarget.texture, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D, 0 ); } - this.loop = value; - - if ( this.isPlaying === true ) { + if ( depthTexture !== undefined ) { - this.source.loop = this.loop; + setupDepthRenderbuffer( renderTarget ); } - return this; - } - setLoopStart( value ) { + // Set up GL resources for the render target + function setupRenderTarget( renderTarget ) { - this.loopStart = value; + const texture = renderTarget.texture; - return this; + const renderTargetProperties = properties.get( renderTarget ); + const textureProperties = properties.get( texture ); - } + renderTarget.addEventListener( 'dispose', onRenderTargetDispose ); - setLoopEnd( value ) { + const textures = renderTarget.textures; - this.loopEnd = value; + const isCube = ( renderTarget.isWebGLCubeRenderTarget === true ); + const isMultipleRenderTargets = ( textures.length > 1 ); - return this; + if ( ! isMultipleRenderTargets ) { - } + if ( textureProperties.__webglTexture === undefined ) { - getVolume() { + textureProperties.__webglTexture = _gl.createTexture(); - return this.gain.gain.value; + } - } + textureProperties.__version = texture.version; + info.memory.textures ++; - setVolume( value ) { + } - this.gain.gain.setTargetAtTime( value, this.context.currentTime, 0.01 ); + // Setup framebuffer - return this; + if ( isCube ) { - } + renderTargetProperties.__webglFramebuffer = []; -} + for ( let i = 0; i < 6; i ++ ) { -const _position = /*@__PURE__*/ new Vector3(); -const _quaternion = /*@__PURE__*/ new Quaternion(); -const _scale = /*@__PURE__*/ new Vector3(); -const _orientation = /*@__PURE__*/ new Vector3(); + if ( texture.mipmaps && texture.mipmaps.length > 0 ) { -class PositionalAudio extends Audio { + renderTargetProperties.__webglFramebuffer[ i ] = []; - constructor( listener ) { + for ( let level = 0; level < texture.mipmaps.length; level ++ ) { - super( listener ); + renderTargetProperties.__webglFramebuffer[ i ][ level ] = _gl.createFramebuffer(); - this.panner = this.context.createPanner(); - this.panner.panningModel = 'HRTF'; - this.panner.connect( this.gain ); + } - } + } else { - disconnect() { + renderTargetProperties.__webglFramebuffer[ i ] = _gl.createFramebuffer(); - super.disconnect(); + } - this.panner.disconnect( this.gain ); + } - } + } else { - getOutput() { + if ( texture.mipmaps && texture.mipmaps.length > 0 ) { - return this.panner; + renderTargetProperties.__webglFramebuffer = []; - } + for ( let level = 0; level < texture.mipmaps.length; level ++ ) { - getRefDistance() { + renderTargetProperties.__webglFramebuffer[ level ] = _gl.createFramebuffer(); - return this.panner.refDistance; + } - } + } else { - setRefDistance( value ) { + renderTargetProperties.__webglFramebuffer = _gl.createFramebuffer(); - this.panner.refDistance = value; + } - return this; + if ( isMultipleRenderTargets ) { - } + for ( let i = 0, il = textures.length; i < il; i ++ ) { - getRolloffFactor() { + const attachmentProperties = properties.get( textures[ i ] ); - return this.panner.rolloffFactor; + if ( attachmentProperties.__webglTexture === undefined ) { - } + attachmentProperties.__webglTexture = _gl.createTexture(); - setRolloffFactor( value ) { + info.memory.textures ++; - this.panner.rolloffFactor = value; + } - return this; + } - } + } - getDistanceModel() { + if ( ( renderTarget.samples > 0 ) && useMultisampledRTT( renderTarget ) === false ) { - return this.panner.distanceModel; + renderTargetProperties.__webglMultisampledFramebuffer = _gl.createFramebuffer(); + renderTargetProperties.__webglColorRenderbuffer = []; - } + state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); - setDistanceModel( value ) { + for ( let i = 0; i < textures.length; i ++ ) { - this.panner.distanceModel = value; + const texture = textures[ i ]; + renderTargetProperties.__webglColorRenderbuffer[ i ] = _gl.createRenderbuffer(); - return this; + _gl.bindRenderbuffer( _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] ); - } + const glFormat = utils.convert( texture.format, texture.colorSpace ); + const glType = utils.convert( texture.type ); + const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace, renderTarget.isXRRenderTarget === true ); + const samples = getRenderTargetSamples( renderTarget ); + _gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height ); - getMaxDistance() { + _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] ); - return this.panner.maxDistance; + } - } + _gl.bindRenderbuffer( _gl.RENDERBUFFER, null ); - setMaxDistance( value ) { + if ( renderTarget.depthBuffer ) { - this.panner.maxDistance = value; + renderTargetProperties.__webglDepthRenderbuffer = _gl.createRenderbuffer(); + setupRenderBufferStorage( renderTargetProperties.__webglDepthRenderbuffer, renderTarget, true ); - return this; + } - } + state.bindFramebuffer( _gl.FRAMEBUFFER, null ); - setDirectionalCone( coneInnerAngle, coneOuterAngle, coneOuterGain ) { + } - this.panner.coneInnerAngle = coneInnerAngle; - this.panner.coneOuterAngle = coneOuterAngle; - this.panner.coneOuterGain = coneOuterGain; + } - return this; + // Setup color buffer - } + if ( isCube ) { - updateMatrixWorld( force ) { + state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__webglTexture ); + setTextureParameters( _gl.TEXTURE_CUBE_MAP, texture ); - super.updateMatrixWorld( force ); + for ( let i = 0; i < 6; i ++ ) { - if ( this.hasPlaybackControl === true && this.isPlaying === false ) return; + if ( texture.mipmaps && texture.mipmaps.length > 0 ) { - this.matrixWorld.decompose( _position, _quaternion, _scale ); + for ( let level = 0; level < texture.mipmaps.length; level ++ ) { - _orientation.set( 0, 0, 1 ).applyQuaternion( _quaternion ); + setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer[ i ][ level ], renderTarget, texture, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, level ); - const panner = this.panner; + } - if ( panner.positionX ) { + } else { - // code path for Chrome and Firefox (see #14393) + setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer[ i ], renderTarget, texture, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0 ); - const endTime = this.context.currentTime + this.listener.timeDelta; + } - panner.positionX.linearRampToValueAtTime( _position.x, endTime ); - panner.positionY.linearRampToValueAtTime( _position.y, endTime ); - panner.positionZ.linearRampToValueAtTime( _position.z, endTime ); - panner.orientationX.linearRampToValueAtTime( _orientation.x, endTime ); - panner.orientationY.linearRampToValueAtTime( _orientation.y, endTime ); - panner.orientationZ.linearRampToValueAtTime( _orientation.z, endTime ); + } - } else { + if ( textureNeedsGenerateMipmaps( texture ) ) { - panner.setPosition( _position.x, _position.y, _position.z ); - panner.setOrientation( _orientation.x, _orientation.y, _orientation.z ); + generateMipmap( _gl.TEXTURE_CUBE_MAP ); - } + } - } + state.unbindTexture(); -} + } else if ( isMultipleRenderTargets ) { -class AudioAnalyser { + for ( let i = 0, il = textures.length; i < il; i ++ ) { - constructor( audio, fftSize = 2048 ) { + const attachment = textures[ i ]; + const attachmentProperties = properties.get( attachment ); - this.analyser = audio.context.createAnalyser(); - this.analyser.fftSize = fftSize; + state.bindTexture( _gl.TEXTURE_2D, attachmentProperties.__webglTexture ); + setTextureParameters( _gl.TEXTURE_2D, attachment ); + setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, attachment, _gl.COLOR_ATTACHMENT0 + i, _gl.TEXTURE_2D, 0 ); - this.data = new Uint8Array( this.analyser.frequencyBinCount ); + if ( textureNeedsGenerateMipmaps( attachment ) ) { - audio.getOutput().connect( this.analyser ); + generateMipmap( _gl.TEXTURE_2D ); - } + } + } - getFrequencyData() { + state.unbindTexture(); - this.analyser.getByteFrequencyData( this.data ); + } else { - return this.data; + let glTextureType = _gl.TEXTURE_2D; - } + if ( renderTarget.isWebGL3DRenderTarget || renderTarget.isWebGLArrayRenderTarget ) { - getAverageFrequency() { + glTextureType = renderTarget.isWebGL3DRenderTarget ? _gl.TEXTURE_3D : _gl.TEXTURE_2D_ARRAY; - let value = 0; - const data = this.getFrequencyData(); + } - for ( let i = 0; i < data.length; i ++ ) { + state.bindTexture( glTextureType, textureProperties.__webglTexture ); + setTextureParameters( glTextureType, texture ); - value += data[ i ]; + if ( texture.mipmaps && texture.mipmaps.length > 0 ) { - } + for ( let level = 0; level < texture.mipmaps.length; level ++ ) { - return value / data.length; + setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer[ level ], renderTarget, texture, _gl.COLOR_ATTACHMENT0, glTextureType, level ); - } + } -} + } else { -class PropertyMixer { + setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, texture, _gl.COLOR_ATTACHMENT0, glTextureType, 0 ); - constructor( binding, typeName, valueSize ) { + } - this.binding = binding; - this.valueSize = valueSize; + if ( textureNeedsGenerateMipmaps( texture ) ) { - let mixFunction, - mixFunctionAdditive, - setIdentity; + generateMipmap( glTextureType ); - // buffer layout: [ incoming | accu0 | accu1 | orig | addAccu | (optional work) ] - // - // interpolators can use .buffer as their .result - // the data then goes to 'incoming' - // - // 'accu0' and 'accu1' are used frame-interleaved for - // the cumulative result and are compared to detect - // changes - // - // 'orig' stores the original state of the property - // - // 'add' is used for additive cumulative results - // - // 'work' is optional and is only present for quaternion types. It is used - // to store intermediate quaternion multiplication results + } - switch ( typeName ) { + state.unbindTexture(); - case 'quaternion': - mixFunction = this._slerp; - mixFunctionAdditive = this._slerpAdditive; - setIdentity = this._setAdditiveIdentityQuaternion; + } - this.buffer = new Float64Array( valueSize * 6 ); - this._workIndex = 5; - break; + // Setup depth and stencil buffers + + if ( renderTarget.depthBuffer ) { - case 'string': - case 'bool': - mixFunction = this._select; + setupDepthRenderbuffer( renderTarget ); - // Use the regular mix function and for additive on these types, - // additive is not relevant for non-numeric types - mixFunctionAdditive = this._select; + } - setIdentity = this._setAdditiveIdentityOther; + } - this.buffer = new Array( valueSize * 5 ); - break; + function updateRenderTargetMipmap( renderTarget ) { - default: - mixFunction = this._lerp; - mixFunctionAdditive = this._lerpAdditive; - setIdentity = this._setAdditiveIdentityNumeric; + const textures = renderTarget.textures; - this.buffer = new Float64Array( valueSize * 5 ); + for ( let i = 0, il = textures.length; i < il; i ++ ) { - } + const texture = textures[ i ]; - this._mixBufferRegion = mixFunction; - this._mixBufferRegionAdditive = mixFunctionAdditive; - this._setIdentity = setIdentity; - this._origIndex = 3; - this._addIndex = 4; + if ( textureNeedsGenerateMipmaps( texture ) ) { - this.cumulativeWeight = 0; - this.cumulativeWeightAdditive = 0; + const targetType = getTargetType( renderTarget ); + const webglTexture = properties.get( texture ).__webglTexture; - this.useCount = 0; - this.referenceCount = 0; + state.bindTexture( targetType, webglTexture ); + generateMipmap( targetType ); + state.unbindTexture(); - } + } - // accumulate data in the 'incoming' region into 'accu' - accumulate( accuIndex, weight ) { + } + + } - // note: happily accumulating nothing when weight = 0, the caller knows - // the weight and shouldn't have made the call in the first place + const invalidationArrayRead = []; + const invalidationArrayDraw = []; - const buffer = this.buffer, - stride = this.valueSize, - offset = accuIndex * stride + stride; + function updateMultisampleRenderTarget( renderTarget ) { - let currentWeight = this.cumulativeWeight; + if ( renderTarget.samples > 0 ) { - if ( currentWeight === 0 ) { + if ( useMultisampledRTT( renderTarget ) === false ) { - // accuN := incoming * weight + const textures = renderTarget.textures; + const width = renderTarget.width; + const height = renderTarget.height; + let mask = _gl.COLOR_BUFFER_BIT; + const depthStyle = renderTarget.stencilBuffer ? _gl.DEPTH_STENCIL_ATTACHMENT : _gl.DEPTH_ATTACHMENT; + const renderTargetProperties = properties.get( renderTarget ); + const isMultipleRenderTargets = ( textures.length > 1 ); - for ( let i = 0; i !== stride; ++ i ) { + // If MRT we need to remove FBO attachments + if ( isMultipleRenderTargets ) { - buffer[ offset + i ] = buffer[ i ]; + for ( let i = 0; i < textures.length; i ++ ) { - } + state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); + _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.RENDERBUFFER, null ); - currentWeight = weight; + state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); + _gl.framebufferTexture2D( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.TEXTURE_2D, null, 0 ); - } else { + } - // accuN := accuN + incoming * weight + } - currentWeight += weight; - const mix = weight / currentWeight; - this._mixBufferRegion( buffer, offset, 0, mix, stride ); + state.bindFramebuffer( _gl.READ_FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); - } + const mipmaps = renderTarget.texture.mipmaps; - this.cumulativeWeight = currentWeight; + if ( mipmaps && mipmaps.length > 0 ) { - } + state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, renderTargetProperties.__webglFramebuffer[ 0 ] ); - // accumulate data in the 'incoming' region into 'add' - accumulateAdditive( weight ) { + } else { - const buffer = this.buffer, - stride = this.valueSize, - offset = stride * this._addIndex; + state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); - if ( this.cumulativeWeightAdditive === 0 ) { + } - // add = identity + for ( let i = 0; i < textures.length; i ++ ) { - this._setIdentity(); + if ( renderTarget.resolveDepthBuffer ) { - } + if ( renderTarget.depthBuffer ) mask |= _gl.DEPTH_BUFFER_BIT; - // add := add + incoming * weight + // resolving stencil is slow with a D3D backend. disable it for all transmission render targets (see #27799) - this._mixBufferRegionAdditive( buffer, offset, 0, weight, stride ); - this.cumulativeWeightAdditive += weight; + if ( renderTarget.stencilBuffer && renderTarget.resolveStencilBuffer ) mask |= _gl.STENCIL_BUFFER_BIT; - } + } - // apply the state of 'accu' to the binding when accus differ - apply( accuIndex ) { + if ( isMultipleRenderTargets ) { - const stride = this.valueSize, - buffer = this.buffer, - offset = accuIndex * stride + stride, + _gl.framebufferRenderbuffer( _gl.READ_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] ); - weight = this.cumulativeWeight, - weightAdditive = this.cumulativeWeightAdditive, + const webglTexture = properties.get( textures[ i ] ).__webglTexture; + _gl.framebufferTexture2D( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D, webglTexture, 0 ); - binding = this.binding; + } - this.cumulativeWeight = 0; - this.cumulativeWeightAdditive = 0; + _gl.blitFramebuffer( 0, 0, width, height, 0, 0, width, height, mask, _gl.NEAREST ); - if ( weight < 1 ) { + if ( supportsInvalidateFramebuffer === true ) { - // accuN := accuN + original * ( 1 - cumulativeWeight ) + invalidationArrayRead.length = 0; + invalidationArrayDraw.length = 0; - const originalValueOffset = stride * this._origIndex; + invalidationArrayRead.push( _gl.COLOR_ATTACHMENT0 + i ); - this._mixBufferRegion( - buffer, offset, originalValueOffset, 1 - weight, stride ); + if ( renderTarget.depthBuffer && renderTarget.resolveDepthBuffer === false ) { - } + invalidationArrayRead.push( depthStyle ); + invalidationArrayDraw.push( depthStyle ); - if ( weightAdditive > 0 ) { + _gl.invalidateFramebuffer( _gl.DRAW_FRAMEBUFFER, invalidationArrayDraw ); - // accuN := accuN + additive accuN + } - this._mixBufferRegionAdditive( buffer, offset, this._addIndex * stride, 1, stride ); + _gl.invalidateFramebuffer( _gl.READ_FRAMEBUFFER, invalidationArrayRead ); - } + } - for ( let i = stride, e = stride + stride; i !== e; ++ i ) { + } - if ( buffer[ i ] !== buffer[ i + stride ] ) { + state.bindFramebuffer( _gl.READ_FRAMEBUFFER, null ); + state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, null ); - // value has changed -> update scene graph + // If MRT since pre-blit we removed the FBO we need to reconstruct the attachments + if ( isMultipleRenderTargets ) { - binding.setValue( buffer, offset ); - break; + for ( let i = 0; i < textures.length; i ++ ) { - } + state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); + _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] ); - } + const webglTexture = properties.get( textures[ i ] ).__webglTexture; - } + state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); + _gl.framebufferTexture2D( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.TEXTURE_2D, webglTexture, 0 ); - // remember the state of the bound property and copy it to both accus - saveOriginalState() { + } - const binding = this.binding; + } - const buffer = this.buffer, - stride = this.valueSize, + state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer ); - originalValueOffset = stride * this._origIndex; + } else { - binding.getValue( buffer, originalValueOffset ); + if ( renderTarget.depthBuffer && renderTarget.resolveDepthBuffer === false && supportsInvalidateFramebuffer ) { - // accu[0..1] := orig -- initially detect changes against the original - for ( let i = stride, e = originalValueOffset; i !== e; ++ i ) { + const depthStyle = renderTarget.stencilBuffer ? _gl.DEPTH_STENCIL_ATTACHMENT : _gl.DEPTH_ATTACHMENT; - buffer[ i ] = buffer[ originalValueOffset + ( i % stride ) ]; + _gl.invalidateFramebuffer( _gl.DRAW_FRAMEBUFFER, [ depthStyle ] ); - } + } - // Add to identity for additive - this._setIdentity(); + } - this.cumulativeWeight = 0; - this.cumulativeWeightAdditive = 0; + } } - // apply the state previously taken via 'saveOriginalState' to the binding - restoreOriginalState() { + function getRenderTargetSamples( renderTarget ) { - const originalValueOffset = this.valueSize * 3; - this.binding.setValue( this.buffer, originalValueOffset ); + return Math.min( capabilities.maxSamples, renderTarget.samples ); } - _setAdditiveIdentityNumeric() { + function useMultisampledRTT( renderTarget ) { - const startIndex = this._addIndex * this.valueSize; - const endIndex = startIndex + this.valueSize; + const renderTargetProperties = properties.get( renderTarget ); - for ( let i = startIndex; i < endIndex; i ++ ) { + return renderTarget.samples > 0 && extensions.has( 'WEBGL_multisampled_render_to_texture' ) === true && renderTargetProperties.__useRenderToTexture !== false; - this.buffer[ i ] = 0; + } - } + function updateVideoTexture( texture ) { - } + const frame = info.render.frame; + + // Check the last frame we updated the VideoTexture + + if ( _videoTextures.get( texture ) !== frame ) { - _setAdditiveIdentityQuaternion() { + _videoTextures.set( texture, frame ); + texture.update(); - this._setAdditiveIdentityNumeric(); - this.buffer[ this._addIndex * this.valueSize + 3 ] = 1; + } } - _setAdditiveIdentityOther() { + function verifyColorSpace( texture, image ) { - const startIndex = this._origIndex * this.valueSize; - const targetIndex = this._addIndex * this.valueSize; + const colorSpace = texture.colorSpace; + const format = texture.format; + const type = texture.type; - for ( let i = 0; i < this.valueSize; i ++ ) { + if ( texture.isCompressedTexture === true || texture.isVideoTexture === true ) return image; - this.buffer[ targetIndex + i ] = this.buffer[ startIndex + i ]; + if ( colorSpace !== LinearSRGBColorSpace && colorSpace !== NoColorSpace ) { - } + // sRGB - } + if ( ColorManagement.getTransfer( colorSpace ) === SRGBTransfer ) { + // in WebGL 2 uncompressed textures can only be sRGB encoded if they have the RGBA8 format - // mix functions + if ( format !== RGBAFormat || type !== UnsignedByteType ) { - _select( buffer, dstOffset, srcOffset, t, stride ) { + console.warn( 'THREE.WebGLTextures: sRGB encoded textures have to use RGBAFormat and UnsignedByteType.' ); - if ( t >= 0.5 ) { + } - for ( let i = 0; i !== stride; ++ i ) { + } else { - buffer[ dstOffset + i ] = buffer[ srcOffset + i ]; + console.error( 'THREE.WebGLTextures: Unsupported texture color space:', colorSpace ); } } - } - - _slerp( buffer, dstOffset, srcOffset, t ) { - - Quaternion.slerpFlat( buffer, dstOffset, buffer, dstOffset, buffer, srcOffset, t ); + return image; } - _slerpAdditive( buffer, dstOffset, srcOffset, t, stride ) { - - const workOffset = this._workIndex * stride; + function getDimensions( image ) { - // Store result in intermediate buffer offset - Quaternion.multiplyQuaternionsFlat( buffer, workOffset, buffer, dstOffset, buffer, srcOffset ); + if ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) { - // Slerp to the intermediate result - Quaternion.slerpFlat( buffer, dstOffset, buffer, dstOffset, buffer, workOffset, t ); + // if intrinsic data are not available, fallback to width/height - } - - _lerp( buffer, dstOffset, srcOffset, t, stride ) { + _imageDimensions.width = image.naturalWidth || image.width; + _imageDimensions.height = image.naturalHeight || image.height; - const s = 1 - t; + } else if ( typeof VideoFrame !== 'undefined' && image instanceof VideoFrame ) { - for ( let i = 0; i !== stride; ++ i ) { + _imageDimensions.width = image.displayWidth; + _imageDimensions.height = image.displayHeight; - const j = dstOffset + i; + } else { - buffer[ j ] = buffer[ j ] * s + buffer[ srcOffset + i ] * t; + _imageDimensions.width = image.width; + _imageDimensions.height = image.height; } + return _imageDimensions; + } - _lerpAdditive( buffer, dstOffset, srcOffset, t, stride ) { + // + + this.allocateTextureUnit = allocateTextureUnit; + this.resetTextureUnits = resetTextureUnits; - for ( let i = 0; i !== stride; ++ i ) { + this.setTexture2D = setTexture2D; + this.setTexture2DArray = setTexture2DArray; + this.setTexture3D = setTexture3D; + this.setTextureCube = setTextureCube; + this.rebindTextures = rebindTextures; + this.setupRenderTarget = setupRenderTarget; + this.updateRenderTargetMipmap = updateRenderTargetMipmap; + this.updateMultisampleRenderTarget = updateMultisampleRenderTarget; + this.setupDepthRenderbuffer = setupDepthRenderbuffer; + this.setupFrameBufferTexture = setupFrameBufferTexture; + this.useMultisampledRTT = useMultisampledRTT; - const j = dstOffset + i; +} - buffer[ j ] = buffer[ j ] + buffer[ srcOffset + i ] * t; +function WebGLUtils( gl, extensions ) { - } + function convert( p, colorSpace = NoColorSpace ) { - } + let extension; -} + const transfer = ColorManagement.getTransfer( colorSpace ); -// Characters [].:/ are reserved for track binding syntax. -const _RESERVED_CHARS_RE = '\\[\\]\\.:\\/'; -const _reservedRe = new RegExp( '[' + _RESERVED_CHARS_RE + ']', 'g' ); + if ( p === UnsignedByteType ) return gl.UNSIGNED_BYTE; + if ( p === UnsignedShort4444Type ) return gl.UNSIGNED_SHORT_4_4_4_4; + if ( p === UnsignedShort5551Type ) return gl.UNSIGNED_SHORT_5_5_5_1; + if ( p === UnsignedInt5999Type ) return gl.UNSIGNED_INT_5_9_9_9_REV; -// Attempts to allow node names from any language. ES5's `\w` regexp matches -// only latin characters, and the unicode \p{L} is not yet supported. So -// instead, we exclude reserved characters and match everything else. -const _wordChar = '[^' + _RESERVED_CHARS_RE + ']'; -const _wordCharOrDot = '[^' + _RESERVED_CHARS_RE.replace( '\\.', '' ) + ']'; + if ( p === ByteType ) return gl.BYTE; + if ( p === ShortType ) return gl.SHORT; + if ( p === UnsignedShortType ) return gl.UNSIGNED_SHORT; + if ( p === IntType ) return gl.INT; + if ( p === UnsignedIntType ) return gl.UNSIGNED_INT; + if ( p === FloatType ) return gl.FLOAT; + if ( p === HalfFloatType ) return gl.HALF_FLOAT; -// Parent directories, delimited by '/' or ':'. Currently unused, but must -// be matched to parse the rest of the track name. -const _directoryRe = /*@__PURE__*/ /((?:WC+[\/:])*)/.source.replace( 'WC', _wordChar ); + if ( p === AlphaFormat ) return gl.ALPHA; + if ( p === RGBFormat ) return gl.RGB; + if ( p === RGBAFormat ) return gl.RGBA; + if ( p === DepthFormat ) return gl.DEPTH_COMPONENT; + if ( p === DepthStencilFormat ) return gl.DEPTH_STENCIL; -// Target node. May contain word characters (a-zA-Z0-9_) and '.' or '-'. -const _nodeRe = /*@__PURE__*/ /(WCOD+)?/.source.replace( 'WCOD', _wordCharOrDot ); + // WebGL2 formats. -// Object on target node, and accessor. May not contain reserved -// characters. Accessor may contain any character except closing bracket. -const _objectRe = /*@__PURE__*/ /(?:\.(WC+)(?:\[(.+)\])?)?/.source.replace( 'WC', _wordChar ); + if ( p === RedFormat ) return gl.RED; + if ( p === RedIntegerFormat ) return gl.RED_INTEGER; + if ( p === RGFormat ) return gl.RG; + if ( p === RGIntegerFormat ) return gl.RG_INTEGER; + if ( p === RGBAIntegerFormat ) return gl.RGBA_INTEGER; -// Property and accessor. May not contain reserved characters. Accessor may -// contain any non-bracket characters. -const _propertyRe = /*@__PURE__*/ /\.(WC+)(?:\[(.+)\])?/.source.replace( 'WC', _wordChar ); + // S3TC -const _trackRe = new RegExp( '' - + '^' - + _directoryRe - + _nodeRe - + _objectRe - + _propertyRe - + '$' -); + if ( p === RGB_S3TC_DXT1_Format || p === RGBA_S3TC_DXT1_Format || p === RGBA_S3TC_DXT3_Format || p === RGBA_S3TC_DXT5_Format ) { -const _supportedObjectNames = [ 'material', 'materials', 'bones', 'map' ]; + if ( transfer === SRGBTransfer ) { -class Composite { + extension = extensions.get( 'WEBGL_compressed_texture_s3tc_srgb' ); - constructor( targetGroup, path, optionalParsedPath ) { + if ( extension !== null ) { - const parsedPath = optionalParsedPath || PropertyBinding.parseTrackName( path ); + if ( p === RGB_S3TC_DXT1_Format ) return extension.COMPRESSED_SRGB_S3TC_DXT1_EXT; + if ( p === RGBA_S3TC_DXT1_Format ) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT; + if ( p === RGBA_S3TC_DXT3_Format ) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT; + if ( p === RGBA_S3TC_DXT5_Format ) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT; - this._targetGroup = targetGroup; - this._bindings = targetGroup.subscribe_( path, parsedPath ); + } else { - } + return null; - getValue( array, offset ) { + } - this.bind(); // bind all binding + } else { - const firstValidIndex = this._targetGroup.nCachedObjects_, - binding = this._bindings[ firstValidIndex ]; + extension = extensions.get( 'WEBGL_compressed_texture_s3tc' ); - // and only call .getValue on the first - if ( binding !== undefined ) binding.getValue( array, offset ); + if ( extension !== null ) { - } + if ( p === RGB_S3TC_DXT1_Format ) return extension.COMPRESSED_RGB_S3TC_DXT1_EXT; + if ( p === RGBA_S3TC_DXT1_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT1_EXT; + if ( p === RGBA_S3TC_DXT3_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT3_EXT; + if ( p === RGBA_S3TC_DXT5_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT5_EXT; - setValue( array, offset ) { + } else { - const bindings = this._bindings; + return null; - for ( let i = this._targetGroup.nCachedObjects_, n = bindings.length; i !== n; ++ i ) { + } - bindings[ i ].setValue( array, offset ); + } } - } - - bind() { + // PVRTC - const bindings = this._bindings; + if ( p === RGB_PVRTC_4BPPV1_Format || p === RGB_PVRTC_2BPPV1_Format || p === RGBA_PVRTC_4BPPV1_Format || p === RGBA_PVRTC_2BPPV1_Format ) { - for ( let i = this._targetGroup.nCachedObjects_, n = bindings.length; i !== n; ++ i ) { + extension = extensions.get( 'WEBGL_compressed_texture_pvrtc' ); - bindings[ i ].bind(); + if ( extension !== null ) { - } + if ( p === RGB_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_4BPPV1_IMG; + if ( p === RGB_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_2BPPV1_IMG; + if ( p === RGBA_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; + if ( p === RGBA_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG; - } + } else { - unbind() { + return null; - const bindings = this._bindings; + } - for ( let i = this._targetGroup.nCachedObjects_, n = bindings.length; i !== n; ++ i ) { + } - bindings[ i ].unbind(); + // ETC - } + if ( p === RGB_ETC1_Format || p === RGB_ETC2_Format || p === RGBA_ETC2_EAC_Format ) { - } + extension = extensions.get( 'WEBGL_compressed_texture_etc' ); -} + if ( extension !== null ) { -// Note: This class uses a State pattern on a per-method basis: -// 'bind' sets 'this.getValue' / 'setValue' and shadows the -// prototype version of these methods with one that represents -// the bound state. When the property is not found, the methods -// become no-ops. -class PropertyBinding { + if ( p === RGB_ETC1_Format || p === RGB_ETC2_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ETC2 : extension.COMPRESSED_RGB8_ETC2; + if ( p === RGBA_ETC2_EAC_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ETC2_EAC : extension.COMPRESSED_RGBA8_ETC2_EAC; - constructor( rootNode, path, parsedPath ) { + } else { - this.path = path; - this.parsedPath = parsedPath || PropertyBinding.parseTrackName( path ); + return null; - this.node = PropertyBinding.findNode( rootNode, this.parsedPath.nodeName ); + } - this.rootNode = rootNode; + } - // initial state of these methods that calls 'bind' - this.getValue = this._getValue_unbound; - this.setValue = this._setValue_unbound; + // ASTC - } + if ( p === RGBA_ASTC_4x4_Format || p === RGBA_ASTC_5x4_Format || p === RGBA_ASTC_5x5_Format || + p === RGBA_ASTC_6x5_Format || p === RGBA_ASTC_6x6_Format || p === RGBA_ASTC_8x5_Format || + p === RGBA_ASTC_8x6_Format || p === RGBA_ASTC_8x8_Format || p === RGBA_ASTC_10x5_Format || + p === RGBA_ASTC_10x6_Format || p === RGBA_ASTC_10x8_Format || p === RGBA_ASTC_10x10_Format || + p === RGBA_ASTC_12x10_Format || p === RGBA_ASTC_12x12_Format ) { + extension = extensions.get( 'WEBGL_compressed_texture_astc' ); - static create( root, path, parsedPath ) { + if ( extension !== null ) { - if ( ! ( root && root.isAnimationObjectGroup ) ) { + if ( p === RGBA_ASTC_4x4_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR : extension.COMPRESSED_RGBA_ASTC_4x4_KHR; + if ( p === RGBA_ASTC_5x4_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR : extension.COMPRESSED_RGBA_ASTC_5x4_KHR; + if ( p === RGBA_ASTC_5x5_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR : extension.COMPRESSED_RGBA_ASTC_5x5_KHR; + if ( p === RGBA_ASTC_6x5_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR : extension.COMPRESSED_RGBA_ASTC_6x5_KHR; + if ( p === RGBA_ASTC_6x6_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR : extension.COMPRESSED_RGBA_ASTC_6x6_KHR; + if ( p === RGBA_ASTC_8x5_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR : extension.COMPRESSED_RGBA_ASTC_8x5_KHR; + if ( p === RGBA_ASTC_8x6_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR : extension.COMPRESSED_RGBA_ASTC_8x6_KHR; + if ( p === RGBA_ASTC_8x8_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR : extension.COMPRESSED_RGBA_ASTC_8x8_KHR; + if ( p === RGBA_ASTC_10x5_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR : extension.COMPRESSED_RGBA_ASTC_10x5_KHR; + if ( p === RGBA_ASTC_10x6_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR : extension.COMPRESSED_RGBA_ASTC_10x6_KHR; + if ( p === RGBA_ASTC_10x8_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR : extension.COMPRESSED_RGBA_ASTC_10x8_KHR; + if ( p === RGBA_ASTC_10x10_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR : extension.COMPRESSED_RGBA_ASTC_10x10_KHR; + if ( p === RGBA_ASTC_12x10_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR : extension.COMPRESSED_RGBA_ASTC_12x10_KHR; + if ( p === RGBA_ASTC_12x12_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR : extension.COMPRESSED_RGBA_ASTC_12x12_KHR; - return new PropertyBinding( root, path, parsedPath ); + } else { - } else { + return null; - return new PropertyBinding.Composite( root, path, parsedPath ); + } } - } + // BPTC - /** - * Replaces spaces with underscores and removes unsupported characters from - * node names, to ensure compatibility with parseTrackName(). - * - * @param {string} name Node name to be sanitized. - * @return {string} - */ - static sanitizeNodeName( name ) { + if ( p === RGBA_BPTC_Format || p === RGB_BPTC_SIGNED_Format || p === RGB_BPTC_UNSIGNED_Format ) { - return name.replace( /\s/g, '_' ).replace( _reservedRe, '' ); + extension = extensions.get( 'EXT_texture_compression_bptc' ); - } + if ( extension !== null ) { - static parseTrackName( trackName ) { + if ( p === RGBA_BPTC_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT : extension.COMPRESSED_RGBA_BPTC_UNORM_EXT; + if ( p === RGB_BPTC_SIGNED_Format ) return extension.COMPRESSED_RGB_BPTC_SIGNED_FLOAT_EXT; + if ( p === RGB_BPTC_UNSIGNED_Format ) return extension.COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_EXT; - const matches = _trackRe.exec( trackName ); + } else { - if ( matches === null ) { + return null; - throw new Error( 'PropertyBinding: Cannot parse trackName: ' + trackName ); + } } - const results = { - // directoryName: matches[ 1 ], // (tschw) currently unused - nodeName: matches[ 2 ], - objectName: matches[ 3 ], - objectIndex: matches[ 4 ], - propertyName: matches[ 5 ], // required - propertyIndex: matches[ 6 ] - }; + // RGTC + + if ( p === RED_RGTC1_Format || p === SIGNED_RED_RGTC1_Format || p === RED_GREEN_RGTC2_Format || p === SIGNED_RED_GREEN_RGTC2_Format ) { - const lastDot = results.nodeName && results.nodeName.lastIndexOf( '.' ); + extension = extensions.get( 'EXT_texture_compression_rgtc' ); - if ( lastDot !== undefined && lastDot !== - 1 ) { + if ( extension !== null ) { - const objectName = results.nodeName.substring( lastDot + 1 ); + if ( p === RGBA_BPTC_Format ) return extension.COMPRESSED_RED_RGTC1_EXT; + if ( p === SIGNED_RED_RGTC1_Format ) return extension.COMPRESSED_SIGNED_RED_RGTC1_EXT; + if ( p === RED_GREEN_RGTC2_Format ) return extension.COMPRESSED_RED_GREEN_RGTC2_EXT; + if ( p === SIGNED_RED_GREEN_RGTC2_Format ) return extension.COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT; - // Object names must be checked against an allowlist. Otherwise, there - // is no way to parse 'foo.bar.baz': 'baz' must be a property, but - // 'bar' could be the objectName, or part of a nodeName (which can - // include '.' characters). - if ( _supportedObjectNames.indexOf( objectName ) !== - 1 ) { + } else { - results.nodeName = results.nodeName.substring( 0, lastDot ); - results.objectName = objectName; + return null; } } - if ( results.propertyName === null || results.propertyName.length === 0 ) { + // - throw new Error( 'PropertyBinding: can not parse propertyName from trackName: ' + trackName ); + if ( p === UnsignedInt248Type ) return gl.UNSIGNED_INT_24_8; - } + // if "p" can't be resolved, assume the user defines a WebGL constant as a string (fallback/workaround for packed RGB formats) - return results; + return ( gl[ p ] !== undefined ) ? gl[ p ] : null; } - static findNode( root, nodeName ) { - - if ( nodeName === undefined || nodeName === '' || nodeName === '.' || nodeName === - 1 || nodeName === root.name || nodeName === root.uuid ) { + return { convert: convert }; - return root; +} - } +const _occlusion_vertex = ` +void main() { - // search into skeleton bones. - if ( root.skeleton ) { + gl_Position = vec4( position, 1.0 ); - const bone = root.skeleton.getBoneByName( nodeName ); +}`; - if ( bone !== undefined ) { +const _occlusion_fragment = ` +uniform sampler2DArray depthColor; +uniform float depthWidth; +uniform float depthHeight; - return bone; +void main() { - } + vec2 coord = vec2( gl_FragCoord.x / depthWidth, gl_FragCoord.y / depthHeight ); - } + if ( coord.x >= 1.0 ) { - // search into node subtree. - if ( root.children ) { + gl_FragDepth = texture( depthColor, vec3( coord.x - 1.0, coord.y, 1 ) ).r; - const searchNodeSubtree = function ( children ) { + } else { - for ( let i = 0; i < children.length; i ++ ) { + gl_FragDepth = texture( depthColor, vec3( coord.x, coord.y, 0 ) ).r; - const childNode = children[ i ]; + } - if ( childNode.name === nodeName || childNode.uuid === nodeName ) { +}`; - return childNode; +/** + * A XR module that manages the access to the Depth Sensing API. + */ +class WebXRDepthSensing { - } + /** + * Constructs a new depth sensing module. + */ + constructor() { - const result = searchNodeSubtree( childNode.children ); + /** + * A texture representing the depth of the user's environment. + * + * @type {?Texture} + */ + this.texture = null; - if ( result ) return result; + /** + * A plane mesh for visualizing the depth texture. + * + * @type {?Mesh} + */ + this.mesh = null; - } + /** + * The depth near value. + * + * @type {number} + */ + this.depthNear = 0; - return null; + /** + * The depth near far. + * + * @type {number} + */ + this.depthFar = 0; - }; + } - const subTreeNode = searchNodeSubtree( root.children ); + /** + * Inits the depth sensing module + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {XRWebGLDepthInformation} depthData - The XR depth data. + * @param {XRRenderState} renderState - The XR render state. + */ + init( renderer, depthData, renderState ) { - if ( subTreeNode ) { + if ( this.texture === null ) { - return subTreeNode; + const texture = new Texture(); - } + const texProps = renderer.properties.get( texture ); + texProps.__webglTexture = depthData.texture; - } + if ( ( depthData.depthNear !== renderState.depthNear ) || ( depthData.depthFar !== renderState.depthFar ) ) { - return null; + this.depthNear = depthData.depthNear; + this.depthFar = depthData.depthFar; - } + } - // these are used to "bind" a nonexistent property - _getValue_unavailable() {} - _setValue_unavailable() {} + this.texture = texture; - // Getters + } - _getValue_direct( buffer, offset ) { + } - buffer[ offset ] = this.targetObject[ this.propertyName ]; + /** + * Returns a plane mesh that visualizes the depth texture. + * + * @param {ArrayCamera} cameraXR - The XR camera. + * @return {?Mesh} The plane mesh. + */ + getMesh( cameraXR ) { - } + if ( this.texture !== null ) { - _getValue_array( buffer, offset ) { + if ( this.mesh === null ) { - const source = this.resolvedProperty; + const viewport = cameraXR.cameras[ 0 ].viewport; + const material = new ShaderMaterial( { + vertexShader: _occlusion_vertex, + fragmentShader: _occlusion_fragment, + uniforms: { + depthColor: { value: this.texture }, + depthWidth: { value: viewport.z }, + depthHeight: { value: viewport.w } + } + } ); - for ( let i = 0, n = source.length; i !== n; ++ i ) { + this.mesh = new Mesh( new PlaneGeometry( 20, 20 ), material ); - buffer[ offset ++ ] = source[ i ]; + } } + return this.mesh; + } - _getValue_arrayElement( buffer, offset ) { + /** + * Resets the module + */ + reset() { - buffer[ offset ] = this.resolvedProperty[ this.propertyIndex ]; + this.texture = null; + this.mesh = null; } - _getValue_toArray( buffer, offset ) { + /** + * Returns a texture representing the depth of the user's environment. + * + * @return {?Texture} The depth texture. + */ + getDepthTexture() { - this.resolvedProperty.toArray( buffer, offset ); + return this.texture; } - // Direct +} + +/** + * This class represents an abstraction of the WebXR Device API and is + * internally used by {@link WebGLRenderer}. `WebXRManager` also provides a public + * interface that allows users to enable/disable XR and perform XR related + * tasks like for instance retrieving controllers. + * + * @augments EventDispatcher + * @hideconstructor + */ +class WebXRManager extends EventDispatcher { - _setValue_direct( buffer, offset ) { + /** + * Constructs a new WebGL renderer. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGL2RenderingContext} gl - The rendering context. + */ + constructor( renderer, gl ) { - this.targetObject[ this.propertyName ] = buffer[ offset ]; + super(); - } + const scope = this; - _setValue_direct_setNeedsUpdate( buffer, offset ) { + let session = null; - this.targetObject[ this.propertyName ] = buffer[ offset ]; - this.targetObject.needsUpdate = true; + let framebufferScaleFactor = 1.0; - } + let referenceSpace = null; + let referenceSpaceType = 'local-floor'; + // Set default foveation to maximum. + let foveation = 1.0; + let customReferenceSpace = null; - _setValue_direct_setMatrixWorldNeedsUpdate( buffer, offset ) { + let pose = null; + let glBinding = null; + let glProjLayer = null; + let glBaseLayer = null; + let xrFrame = null; - this.targetObject[ this.propertyName ] = buffer[ offset ]; - this.targetObject.matrixWorldNeedsUpdate = true; + const depthSensing = new WebXRDepthSensing(); + const attributes = gl.getContextAttributes(); - } + let initialRenderTarget = null; + let newRenderTarget = null; - // EntireArray + const controllers = []; + const controllerInputSources = []; - _setValue_array( buffer, offset ) { + const currentSize = new Vector2(); + let currentPixelRatio = null; - const dest = this.resolvedProperty; + // - for ( let i = 0, n = dest.length; i !== n; ++ i ) { + const cameraL = new PerspectiveCamera(); + cameraL.viewport = new Vector4(); - dest[ i ] = buffer[ offset ++ ]; + const cameraR = new PerspectiveCamera(); + cameraR.viewport = new Vector4(); - } + const cameras = [ cameraL, cameraR ]; - } + const cameraXR = new ArrayCamera(); - _setValue_array_setNeedsUpdate( buffer, offset ) { + let _currentDepthNear = null; + let _currentDepthFar = null; - const dest = this.resolvedProperty; + // - for ( let i = 0, n = dest.length; i !== n; ++ i ) { + /** + * Whether the manager's XR camera should be automatically updated or not. + * + * @type {boolean} + * @default true + */ + this.cameraAutoUpdate = true; - dest[ i ] = buffer[ offset ++ ]; + /** + * This flag notifies the renderer to be ready for XR rendering. Set it to `true` + * if you are going to use XR in your app. + * + * @type {boolean} + * @default false + */ + this.enabled = false; - } + /** + * Whether XR presentation is active or not. + * + * @type {boolean} + * @readonly + * @default false + */ + this.isPresenting = false; - this.targetObject.needsUpdate = true; + /** + * Returns a group representing the `target ray` space of the XR controller. + * Use this space for visualizing 3D objects that support the user in pointing + * tasks like UI interaction. + * + * @param {number} index - The index of the controller. + * @return {Group} A group representing the `target ray` space. + */ + this.getController = function ( index ) { - } + let controller = controllers[ index ]; - _setValue_array_setMatrixWorldNeedsUpdate( buffer, offset ) { + if ( controller === undefined ) { - const dest = this.resolvedProperty; + controller = new WebXRController(); + controllers[ index ] = controller; - for ( let i = 0, n = dest.length; i !== n; ++ i ) { + } - dest[ i ] = buffer[ offset ++ ]; + return controller.getTargetRaySpace(); - } + }; - this.targetObject.matrixWorldNeedsUpdate = true; + /** + * Returns a group representing the `grip` space of the XR controller. + * Use this space for visualizing 3D objects that support the user in pointing + * tasks like UI interaction. + * + * Note: If you want to show something in the user's hand AND offer a + * pointing ray at the same time, you'll want to attached the handheld object + * to the group returned by `getControllerGrip()` and the ray to the + * group returned by `getController()`. The idea is to have two + * different groups in two different coordinate spaces for the same WebXR + * controller. + * + * @param {number} index - The index of the controller. + * @return {Group} A group representing the `grip` space. + */ + this.getControllerGrip = function ( index ) { - } + let controller = controllers[ index ]; - // ArrayElement + if ( controller === undefined ) { - _setValue_arrayElement( buffer, offset ) { + controller = new WebXRController(); + controllers[ index ] = controller; - this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ]; + } - } + return controller.getGripSpace(); - _setValue_arrayElement_setNeedsUpdate( buffer, offset ) { + }; - this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ]; - this.targetObject.needsUpdate = true; + /** + * Returns a group representing the `hand` space of the XR controller. + * Use this space for visualizing 3D objects that support the user in pointing + * tasks like UI interaction. + * + * @param {number} index - The index of the controller. + * @return {Group} A group representing the `hand` space. + */ + this.getHand = function ( index ) { - } + let controller = controllers[ index ]; - _setValue_arrayElement_setMatrixWorldNeedsUpdate( buffer, offset ) { + if ( controller === undefined ) { - this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ]; - this.targetObject.matrixWorldNeedsUpdate = true; + controller = new WebXRController(); + controllers[ index ] = controller; - } + } - // HasToFromArray + return controller.getHandSpace(); - _setValue_fromArray( buffer, offset ) { + }; - this.resolvedProperty.fromArray( buffer, offset ); + // - } + function onSessionEvent( event ) { - _setValue_fromArray_setNeedsUpdate( buffer, offset ) { + const controllerIndex = controllerInputSources.indexOf( event.inputSource ); - this.resolvedProperty.fromArray( buffer, offset ); - this.targetObject.needsUpdate = true; + if ( controllerIndex === -1 ) { - } + return; - _setValue_fromArray_setMatrixWorldNeedsUpdate( buffer, offset ) { + } - this.resolvedProperty.fromArray( buffer, offset ); - this.targetObject.matrixWorldNeedsUpdate = true; + const controller = controllers[ controllerIndex ]; - } + if ( controller !== undefined ) { - _getValue_unbound( targetArray, offset ) { + controller.update( event.inputSource, event.frame, customReferenceSpace || referenceSpace ); + controller.dispatchEvent( { type: event.type, data: event.inputSource } ); - this.bind(); - this.getValue( targetArray, offset ); + } - } + } - _setValue_unbound( sourceArray, offset ) { + function onSessionEnd() { - this.bind(); - this.setValue( sourceArray, offset ); + session.removeEventListener( 'select', onSessionEvent ); + session.removeEventListener( 'selectstart', onSessionEvent ); + session.removeEventListener( 'selectend', onSessionEvent ); + session.removeEventListener( 'squeeze', onSessionEvent ); + session.removeEventListener( 'squeezestart', onSessionEvent ); + session.removeEventListener( 'squeezeend', onSessionEvent ); + session.removeEventListener( 'end', onSessionEnd ); + session.removeEventListener( 'inputsourceschange', onInputSourcesChange ); - } + for ( let i = 0; i < controllers.length; i ++ ) { - // create getter / setter pair for a property in the scene graph - bind() { + const inputSource = controllerInputSources[ i ]; - let targetObject = this.node; - const parsedPath = this.parsedPath; + if ( inputSource === null ) continue; - const objectName = parsedPath.objectName; - const propertyName = parsedPath.propertyName; - let propertyIndex = parsedPath.propertyIndex; + controllerInputSources[ i ] = null; - if ( ! targetObject ) { + controllers[ i ].disconnect( inputSource ); - targetObject = PropertyBinding.findNode( this.rootNode, parsedPath.nodeName ); + } - this.node = targetObject; + _currentDepthNear = null; + _currentDepthFar = null; - } + depthSensing.reset(); - // set fail state so we can just 'return' on error - this.getValue = this._getValue_unavailable; - this.setValue = this._setValue_unavailable; + // restore framebuffer/rendering state - // ensure there is a value node - if ( ! targetObject ) { + renderer.setRenderTarget( initialRenderTarget ); - console.error( 'THREE.PropertyBinding: Trying to update node for track: ' + this.path + ' but it wasn\'t found.' ); - return; + glBaseLayer = null; + glProjLayer = null; + glBinding = null; + session = null; + newRenderTarget = null; - } + // - if ( objectName ) { + animation.stop(); - let objectIndex = parsedPath.objectIndex; + scope.isPresenting = false; - // special cases were we need to reach deeper into the hierarchy to get the face materials.... - switch ( objectName ) { + renderer.setPixelRatio( currentPixelRatio ); + renderer.setSize( currentSize.width, currentSize.height, false ); - case 'materials': + scope.dispatchEvent( { type: 'sessionend' } ); - if ( ! targetObject.material ) { + } - console.error( 'THREE.PropertyBinding: Can not bind to material as node does not have a material.', this ); - return; + /** + * Sets the framebuffer scale factor. + * + * This method can not be used during a XR session. + * + * @param {number} value - The framebuffer scale factor. + */ + this.setFramebufferScaleFactor = function ( value ) { - } + framebufferScaleFactor = value; - if ( ! targetObject.material.materials ) { + if ( scope.isPresenting === true ) { - console.error( 'THREE.PropertyBinding: Can not bind to material.materials as node.material does not have a materials array.', this ); - return; + console.warn( 'THREE.WebXRManager: Cannot change framebuffer scale while presenting.' ); - } + } - targetObject = targetObject.material.materials; + }; - break; + /** + * Sets the reference space type. Can be used to configure a spatial relationship with the user's physical + * environment. Depending on how the user moves in 3D space, setting an appropriate reference space can + * improve tracking. Default is `local-floor`. Valid values can be found here + * https://developer.mozilla.org/en-US/docs/Web/API/XRReferenceSpace#reference_space_types. + * + * This method can not be used during a XR session. + * + * @param {string} value - The reference space type. + */ + this.setReferenceSpaceType = function ( value ) { - case 'bones': + referenceSpaceType = value; - if ( ! targetObject.skeleton ) { + if ( scope.isPresenting === true ) { - console.error( 'THREE.PropertyBinding: Can not bind to bones as node does not have a skeleton.', this ); - return; + console.warn( 'THREE.WebXRManager: Cannot change reference space type while presenting.' ); - } + } - // potential future optimization: skip this if propertyIndex is already an integer - // and convert the integer string to a true integer. + }; - targetObject = targetObject.skeleton.bones; + /** + * Returns the XR reference space. + * + * @return {XRReferenceSpace} The XR reference space. + */ + this.getReferenceSpace = function () { - // support resolving morphTarget names into indices. - for ( let i = 0; i < targetObject.length; i ++ ) { + return customReferenceSpace || referenceSpace; - if ( targetObject[ i ].name === objectIndex ) { + }; - objectIndex = i; - break; + /** + * Sets a custom XR reference space. + * + * @param {XRReferenceSpace} space - The XR reference space. + */ + this.setReferenceSpace = function ( space ) { - } + customReferenceSpace = space; - } + }; - break; + /** + * Returns the current base layer. + * + * @return {?(XRWebGLLayer|XRProjectionLayer)} The XR base layer. + */ + this.getBaseLayer = function () { - case 'map': + return glProjLayer !== null ? glProjLayer : glBaseLayer; - if ( 'map' in targetObject ) { + }; - targetObject = targetObject.map; - break; + /** + * Returns the current XR binding. + * + * @return {?XRWebGLBinding} The XR binding. + */ + this.getBinding = function () { - } + return glBinding; - if ( ! targetObject.material ) { + }; - console.error( 'THREE.PropertyBinding: Can not bind to material as node does not have a material.', this ); - return; + /** + * Returns the current XR frame. + * + * @return {?XRFrame} The XR frame. Returns `null` when used outside a XR session. + */ + this.getFrame = function () { - } + return xrFrame; - if ( ! targetObject.material.map ) { + }; - console.error( 'THREE.PropertyBinding: Can not bind to material.map as node.material does not have a map.', this ); - return; + /** + * Returns the current XR session. + * + * @return {?XRSession} The XR session. Returns `null` when used outside a XR session. + */ + this.getSession = function () { - } + return session; - targetObject = targetObject.material.map; - break; + }; - default: + /** + * After a XR session has been requested usually with one of the `*Button` modules, it + * is injected into the renderer with this method. This method triggers the start of + * the actual XR rendering. + * + * @async + * @param {XRSession} value - The XR session to set. + * @return {Promise} A Promise that resolves when the session has been set. + */ + this.setSession = async function ( value ) { - if ( targetObject[ objectName ] === undefined ) { + session = value; - console.error( 'THREE.PropertyBinding: Can not bind to objectName of node undefined.', this ); - return; + if ( session !== null ) { - } + initialRenderTarget = renderer.getRenderTarget(); - targetObject = targetObject[ objectName ]; + session.addEventListener( 'select', onSessionEvent ); + session.addEventListener( 'selectstart', onSessionEvent ); + session.addEventListener( 'selectend', onSessionEvent ); + session.addEventListener( 'squeeze', onSessionEvent ); + session.addEventListener( 'squeezestart', onSessionEvent ); + session.addEventListener( 'squeezeend', onSessionEvent ); + session.addEventListener( 'end', onSessionEnd ); + session.addEventListener( 'inputsourceschange', onInputSourcesChange ); - } + if ( attributes.xrCompatible !== true ) { + await gl.makeXRCompatible(); - if ( objectIndex !== undefined ) { + } - if ( targetObject[ objectIndex ] === undefined ) { + currentPixelRatio = renderer.getPixelRatio(); + renderer.getSize( currentSize ); - console.error( 'THREE.PropertyBinding: Trying to bind to objectIndex of objectName, but is undefined.', this, targetObject ); - return; + // Check that the browser implements the necessary APIs to use an + // XRProjectionLayer rather than an XRWebGLLayer + const useLayers = typeof XRWebGLBinding !== 'undefined' && 'createProjectionLayer' in XRWebGLBinding.prototype; - } + if ( ! useLayers ) { - targetObject = targetObject[ objectIndex ]; + const layerInit = { + antialias: attributes.antialias, + alpha: true, + depth: attributes.depth, + stencil: attributes.stencil, + framebufferScaleFactor: framebufferScaleFactor + }; - } + glBaseLayer = new XRWebGLLayer( session, gl, layerInit ); - } + session.updateRenderState( { baseLayer: glBaseLayer } ); - // resolve property - const nodeProperty = targetObject[ propertyName ]; + renderer.setPixelRatio( 1 ); + renderer.setSize( glBaseLayer.framebufferWidth, glBaseLayer.framebufferHeight, false ); - if ( nodeProperty === undefined ) { + newRenderTarget = new WebGLRenderTarget( + glBaseLayer.framebufferWidth, + glBaseLayer.framebufferHeight, + { + format: RGBAFormat, + type: UnsignedByteType, + colorSpace: renderer.outputColorSpace, + stencilBuffer: attributes.stencil, + resolveDepthBuffer: ( glBaseLayer.ignoreDepthValues === false ), + resolveStencilBuffer: ( glBaseLayer.ignoreDepthValues === false ) - const nodeName = parsedPath.nodeName; + } + ); - console.error( 'THREE.PropertyBinding: Trying to update property for track: ' + nodeName + - '.' + propertyName + ' but it wasn\'t found.', targetObject ); - return; + } else { - } + let depthFormat = null; + let depthType = null; + let glDepthFormat = null; - // determine versioning scheme - let versioning = this.Versioning.None; + if ( attributes.depth ) { - this.targetObject = targetObject; + glDepthFormat = attributes.stencil ? gl.DEPTH24_STENCIL8 : gl.DEPTH_COMPONENT24; + depthFormat = attributes.stencil ? DepthStencilFormat : DepthFormat; + depthType = attributes.stencil ? UnsignedInt248Type : UnsignedIntType; - if ( targetObject.needsUpdate !== undefined ) { // material + } - versioning = this.Versioning.NeedsUpdate; + const projectionlayerInit = { + colorFormat: gl.RGBA8, + depthFormat: glDepthFormat, + scaleFactor: framebufferScaleFactor + }; - } else if ( targetObject.matrixWorldNeedsUpdate !== undefined ) { // node transform + glBinding = new XRWebGLBinding( session, gl ); - versioning = this.Versioning.MatrixWorldNeedsUpdate; + glProjLayer = glBinding.createProjectionLayer( projectionlayerInit ); - } + session.updateRenderState( { layers: [ glProjLayer ] } ); - // determine how the property gets bound - let bindingType = this.BindingType.Direct; + renderer.setPixelRatio( 1 ); + renderer.setSize( glProjLayer.textureWidth, glProjLayer.textureHeight, false ); - if ( propertyIndex !== undefined ) { + newRenderTarget = new WebGLRenderTarget( + glProjLayer.textureWidth, + glProjLayer.textureHeight, + { + format: RGBAFormat, + type: UnsignedByteType, + depthTexture: new DepthTexture( glProjLayer.textureWidth, glProjLayer.textureHeight, depthType, undefined, undefined, undefined, undefined, undefined, undefined, depthFormat ), + stencilBuffer: attributes.stencil, + colorSpace: renderer.outputColorSpace, + samples: attributes.antialias ? 4 : 0, + resolveDepthBuffer: ( glProjLayer.ignoreDepthValues === false ), + resolveStencilBuffer: ( glProjLayer.ignoreDepthValues === false ) + } ); - // access a sub element of the property array (only primitives are supported right now) + } - if ( propertyName === 'morphTargetInfluences' ) { + newRenderTarget.isXRRenderTarget = true; // TODO Remove this when possible, see #23278 - // potential optimization, skip this if propertyIndex is already an integer, and convert the integer string to a true integer. + this.setFoveation( foveation ); - // support resolving morphTarget names into indices. - if ( ! targetObject.geometry ) { + customReferenceSpace = null; + referenceSpace = await session.requestReferenceSpace( referenceSpaceType ); - console.error( 'THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.', this ); - return; + animation.setContext( session ); + animation.start(); - } + scope.isPresenting = true; - if ( ! targetObject.geometry.morphAttributes ) { + scope.dispatchEvent( { type: 'sessionstart' } ); - console.error( 'THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.morphAttributes.', this ); - return; + } - } + }; - if ( targetObject.morphTargetDictionary[ propertyIndex ] !== undefined ) { + /** + * Returns the environment blend mode from the current XR session. + * + * @return {'opaque'|'additive'|'alpha-blend'|undefined} The environment blend mode. Returns `undefined` when used outside of a XR session. + */ + this.getEnvironmentBlendMode = function () { - propertyIndex = targetObject.morphTargetDictionary[ propertyIndex ]; + if ( session !== null ) { - } + return session.environmentBlendMode; } - bindingType = this.BindingType.ArrayElement; + }; - this.resolvedProperty = nodeProperty; - this.propertyIndex = propertyIndex; + /** + * Returns the current depth texture computed via depth sensing. + * + * @return {?Texture} The depth texture. + */ + this.getDepthTexture = function () { - } else if ( nodeProperty.fromArray !== undefined && nodeProperty.toArray !== undefined ) { + return depthSensing.getDepthTexture(); - // must use copy for Object3D.Euler/Quaternion + }; - bindingType = this.BindingType.HasFromToArray; + function onInputSourcesChange( event ) { - this.resolvedProperty = nodeProperty; + // Notify disconnected - } else if ( Array.isArray( nodeProperty ) ) { + for ( let i = 0; i < event.removed.length; i ++ ) { - bindingType = this.BindingType.EntireArray; + const inputSource = event.removed[ i ]; + const index = controllerInputSources.indexOf( inputSource ); - this.resolvedProperty = nodeProperty; + if ( index >= 0 ) { - } else { + controllerInputSources[ index ] = null; + controllers[ index ].disconnect( inputSource ); - this.propertyName = propertyName; + } - } + } - // select getter / setter - this.getValue = this.GetterByBindingType[ bindingType ]; - this.setValue = this.SetterByBindingTypeAndVersioning[ bindingType ][ versioning ]; + // Notify connected - } + for ( let i = 0; i < event.added.length; i ++ ) { - unbind() { + const inputSource = event.added[ i ]; - this.node = null; + let controllerIndex = controllerInputSources.indexOf( inputSource ); - // back to the prototype version of getValue / setValue - // note: avoiding to mutate the shape of 'this' via 'delete' - this.getValue = this._getValue_unbound; - this.setValue = this._setValue_unbound; + if ( controllerIndex === -1 ) { - } + // Assign input source a controller that currently has no input source -} + for ( let i = 0; i < controllers.length; i ++ ) { -PropertyBinding.Composite = Composite; + if ( i >= controllerInputSources.length ) { -PropertyBinding.prototype.BindingType = { - Direct: 0, - EntireArray: 1, - ArrayElement: 2, - HasFromToArray: 3 -}; + controllerInputSources.push( inputSource ); + controllerIndex = i; + break; -PropertyBinding.prototype.Versioning = { - None: 0, - NeedsUpdate: 1, - MatrixWorldNeedsUpdate: 2 -}; + } else if ( controllerInputSources[ i ] === null ) { -PropertyBinding.prototype.GetterByBindingType = [ + controllerInputSources[ i ] = inputSource; + controllerIndex = i; + break; - PropertyBinding.prototype._getValue_direct, - PropertyBinding.prototype._getValue_array, - PropertyBinding.prototype._getValue_arrayElement, - PropertyBinding.prototype._getValue_toArray, + } -]; + } -PropertyBinding.prototype.SetterByBindingTypeAndVersioning = [ + // If all controllers do currently receive input we ignore new ones - [ - // Direct - PropertyBinding.prototype._setValue_direct, - PropertyBinding.prototype._setValue_direct_setNeedsUpdate, - PropertyBinding.prototype._setValue_direct_setMatrixWorldNeedsUpdate, + if ( controllerIndex === -1 ) break; - ], [ + } - // EntireArray + const controller = controllers[ controllerIndex ]; - PropertyBinding.prototype._setValue_array, - PropertyBinding.prototype._setValue_array_setNeedsUpdate, - PropertyBinding.prototype._setValue_array_setMatrixWorldNeedsUpdate, + if ( controller ) { - ], [ + controller.connect( inputSource ); - // ArrayElement - PropertyBinding.prototype._setValue_arrayElement, - PropertyBinding.prototype._setValue_arrayElement_setNeedsUpdate, - PropertyBinding.prototype._setValue_arrayElement_setMatrixWorldNeedsUpdate, + } - ], [ + } - // HasToFromArray - PropertyBinding.prototype._setValue_fromArray, - PropertyBinding.prototype._setValue_fromArray_setNeedsUpdate, - PropertyBinding.prototype._setValue_fromArray_setMatrixWorldNeedsUpdate, + } - ] + // -]; + const cameraLPos = new Vector3(); + const cameraRPos = new Vector3(); -/** - * - * A group of objects that receives a shared animation state. - * - * Usage: - * - * - Add objects you would otherwise pass as 'root' to the - * constructor or the .clipAction method of AnimationMixer. - * - * - Instead pass this object as 'root'. - * - * - You can also add and remove objects later when the mixer - * is running. - * - * Note: - * - * Objects of this class appear as one object to the mixer, - * so cache control of the individual objects must be done - * on the group. - * - * Limitation: - * - * - The animated properties must be compatible among the - * all objects in the group. - * - * - A single property can either be controlled through a - * target group or directly, but not both. - */ + /** + * Assumes 2 cameras that are parallel and share an X-axis, and that + * the cameras' projection and world matrices have already been set. + * And that near and far planes are identical for both cameras. + * Visualization of this technique: https://computergraphics.stackexchange.com/a/4765 + * + * @param {ArrayCamera} camera - The camera to update. + * @param {PerspectiveCamera} cameraL - The left camera. + * @param {PerspectiveCamera} cameraR - The right camera. + */ + function setProjectionFromUnion( camera, cameraL, cameraR ) { -class AnimationObjectGroup { + cameraLPos.setFromMatrixPosition( cameraL.matrixWorld ); + cameraRPos.setFromMatrixPosition( cameraR.matrixWorld ); - constructor() { + const ipd = cameraLPos.distanceTo( cameraRPos ); - this.isAnimationObjectGroup = true; + const projL = cameraL.projectionMatrix.elements; + const projR = cameraR.projectionMatrix.elements; - this.uuid = generateUUID(); + // VR systems will have identical far and near planes, and + // most likely identical top and bottom frustum extents. + // Use the left camera for these values. + const near = projL[ 14 ] / ( projL[ 10 ] - 1 ); + const far = projL[ 14 ] / ( projL[ 10 ] + 1 ); + const topFov = ( projL[ 9 ] + 1 ) / projL[ 5 ]; + const bottomFov = ( projL[ 9 ] - 1 ) / projL[ 5 ]; - // cached objects followed by the active ones - this._objects = Array.prototype.slice.call( arguments ); + const leftFov = ( projL[ 8 ] - 1 ) / projL[ 0 ]; + const rightFov = ( projR[ 8 ] + 1 ) / projR[ 0 ]; + const left = near * leftFov; + const right = near * rightFov; - this.nCachedObjects_ = 0; // threshold - // note: read by PropertyBinding.Composite + // Calculate the new camera's position offset from the + // left camera. xOffset should be roughly half `ipd`. + const zOffset = ipd / ( - leftFov + rightFov ); + const xOffset = zOffset * - leftFov; - const indices = {}; - this._indicesByUUID = indices; // for bookkeeping + // TODO: Better way to apply this offset? + cameraL.matrixWorld.decompose( camera.position, camera.quaternion, camera.scale ); + camera.translateX( xOffset ); + camera.translateZ( zOffset ); + camera.matrixWorld.compose( camera.position, camera.quaternion, camera.scale ); + camera.matrixWorldInverse.copy( camera.matrixWorld ).invert(); - for ( let i = 0, n = arguments.length; i !== n; ++ i ) { + // Check if the projection uses an infinite far plane. + if ( projL[ 10 ] === -1 ) { - indices[ arguments[ i ].uuid ] = i; + // Use the projection matrix from the left eye. + // The camera offset is sufficient to include the view volumes + // of both eyes (assuming symmetric projections). + camera.projectionMatrix.copy( cameraL.projectionMatrix ); + camera.projectionMatrixInverse.copy( cameraL.projectionMatrixInverse ); - } + } else { - this._paths = []; // inside: string - this._parsedPaths = []; // inside: { we don't care, here } - this._bindings = []; // inside: Array< PropertyBinding > - this._bindingsIndicesByPath = {}; // inside: indices in these arrays + // Find the union of the frustum values of the cameras and scale + // the values so that the near plane's position does not change in world space, + // although must now be relative to the new union camera. + const near2 = near + zOffset; + const far2 = far + zOffset; + const left2 = left - xOffset; + const right2 = right + ( ipd - xOffset ); + const top2 = topFov * far / far2 * near2; + const bottom2 = bottomFov * far / far2 * near2; - const scope = this; + camera.projectionMatrix.makePerspective( left2, right2, top2, bottom2, near2, far2 ); + camera.projectionMatrixInverse.copy( camera.projectionMatrix ).invert(); - this.stats = { + } - objects: { - get total() { + } - return scope._objects.length; + function updateCamera( camera, parent ) { - }, - get inUse() { + if ( parent === null ) { - return this.total - scope.nCachedObjects_; + camera.matrixWorld.copy( camera.matrix ); - } - }, - get bindingsPerObject() { + } else { - return scope._bindings.length; + camera.matrixWorld.multiplyMatrices( parent.matrixWorld, camera.matrix ); } - }; - - } + camera.matrixWorldInverse.copy( camera.matrixWorld ).invert(); - add() { + } - const objects = this._objects, - indicesByUUID = this._indicesByUUID, - paths = this._paths, - parsedPaths = this._parsedPaths, - bindings = this._bindings, - nBindings = bindings.length; + /** + * Updates the state of the XR camera. Use this method on app level if you + * set cameraAutoUpdate` to `false`. The method requires the non-XR + * camera of the scene as a parameter. The passed in camera's transformation + * is automatically adjusted to the position of the XR camera when calling + * this method. + * + * @param {Camera} camera - The camera. + */ + this.updateCamera = function ( camera ) { - let knownObject = undefined, - nObjects = objects.length, - nCachedObjects = this.nCachedObjects_; + if ( session === null ) return; - for ( let i = 0, n = arguments.length; i !== n; ++ i ) { + let depthNear = camera.near; + let depthFar = camera.far; - const object = arguments[ i ], - uuid = object.uuid; - let index = indicesByUUID[ uuid ]; + if ( depthSensing.texture !== null ) { - if ( index === undefined ) { + if ( depthSensing.depthNear > 0 ) depthNear = depthSensing.depthNear; + if ( depthSensing.depthFar > 0 ) depthFar = depthSensing.depthFar; - // unknown object -> add it to the ACTIVE region + } - index = nObjects ++; - indicesByUUID[ uuid ] = index; - objects.push( object ); + cameraXR.near = cameraR.near = cameraL.near = depthNear; + cameraXR.far = cameraR.far = cameraL.far = depthFar; - // accounting is done, now do the same for all bindings + if ( _currentDepthNear !== cameraXR.near || _currentDepthFar !== cameraXR.far ) { - for ( let j = 0, m = nBindings; j !== m; ++ j ) { + // Note that the new renderState won't apply until the next frame. See #18320 - bindings[ j ].push( new PropertyBinding( object, paths[ j ], parsedPaths[ j ] ) ); + session.updateRenderState( { + depthNear: cameraXR.near, + depthFar: cameraXR.far + } ); - } + _currentDepthNear = cameraXR.near; + _currentDepthFar = cameraXR.far; - } else if ( index < nCachedObjects ) { + } - knownObject = objects[ index ]; + cameraL.layers.mask = camera.layers.mask | 0b010; + cameraR.layers.mask = camera.layers.mask | 0b100; + cameraXR.layers.mask = cameraL.layers.mask | cameraR.layers.mask; - // move existing object to the ACTIVE region + const parent = camera.parent; + const cameras = cameraXR.cameras; - const firstActiveIndex = -- nCachedObjects, - lastCachedObject = objects[ firstActiveIndex ]; + updateCamera( cameraXR, parent ); - indicesByUUID[ lastCachedObject.uuid ] = index; - objects[ index ] = lastCachedObject; + for ( let i = 0; i < cameras.length; i ++ ) { - indicesByUUID[ uuid ] = firstActiveIndex; - objects[ firstActiveIndex ] = object; + updateCamera( cameras[ i ], parent ); - // accounting is done, now do the same for all bindings + } - for ( let j = 0, m = nBindings; j !== m; ++ j ) { + // update projection matrix for proper view frustum culling - const bindingsForPath = bindings[ j ], - lastCached = bindingsForPath[ firstActiveIndex ]; + if ( cameras.length === 2 ) { - let binding = bindingsForPath[ index ]; + setProjectionFromUnion( cameraXR, cameraL, cameraR ); - bindingsForPath[ index ] = lastCached; + } else { - if ( binding === undefined ) { + // assume single camera setup (AR) - // since we do not bother to create new bindings - // for objects that are cached, the binding may - // or may not exist + cameraXR.projectionMatrix.copy( cameraL.projectionMatrix ); - binding = new PropertyBinding( object, paths[ j ], parsedPaths[ j ] ); + } - } + // update user camera and its children - bindingsForPath[ firstActiveIndex ] = binding; + updateUserCamera( camera, cameraXR, parent ); - } + }; - } else if ( objects[ index ] !== knownObject ) { + function updateUserCamera( camera, cameraXR, parent ) { - console.error( 'THREE.AnimationObjectGroup: Different objects with the same UUID ' + - 'detected. Clean the caches or recreate your infrastructure when reloading scenes.' ); + if ( parent === null ) { - } // else the object is already where we want it to be + camera.matrix.copy( cameraXR.matrixWorld ); - } // for arguments + } else { - this.nCachedObjects_ = nCachedObjects; + camera.matrix.copy( parent.matrixWorld ); + camera.matrix.invert(); + camera.matrix.multiply( cameraXR.matrixWorld ); - } + } - remove() { + camera.matrix.decompose( camera.position, camera.quaternion, camera.scale ); + camera.updateMatrixWorld( true ); - const objects = this._objects, - indicesByUUID = this._indicesByUUID, - bindings = this._bindings, - nBindings = bindings.length; + camera.projectionMatrix.copy( cameraXR.projectionMatrix ); + camera.projectionMatrixInverse.copy( cameraXR.projectionMatrixInverse ); - let nCachedObjects = this.nCachedObjects_; + if ( camera.isPerspectiveCamera ) { - for ( let i = 0, n = arguments.length; i !== n; ++ i ) { + camera.fov = RAD2DEG * 2 * Math.atan( 1 / camera.projectionMatrix.elements[ 5 ] ); + camera.zoom = 1; - const object = arguments[ i ], - uuid = object.uuid, - index = indicesByUUID[ uuid ]; + } - if ( index !== undefined && index >= nCachedObjects ) { + } - // move existing object into the CACHED region + /** + * Returns an instance of {@link ArrayCamera} which represents the XR camera + * of the active XR session. For each view it holds a separate camera object. + * + * The camera's `fov` is currently not used and does not reflect the fov of + * the XR camera. If you need the fov on app level, you have to compute in + * manually from the XR camera's projection matrices. + * + * @return {ArrayCamera} The XR camera. + */ + this.getCamera = function () { - const lastCachedIndex = nCachedObjects ++, - firstActiveObject = objects[ lastCachedIndex ]; + return cameraXR; - indicesByUUID[ firstActiveObject.uuid ] = index; - objects[ index ] = firstActiveObject; + }; - indicesByUUID[ uuid ] = lastCachedIndex; - objects[ lastCachedIndex ] = object; + /** + * Returns the amount of foveation used by the XR compositor for the projection layer. + * + * @return {number} The amount of foveation. + */ + this.getFoveation = function () { - // accounting is done, now do the same for all bindings + if ( glProjLayer === null && glBaseLayer === null ) { - for ( let j = 0, m = nBindings; j !== m; ++ j ) { + return undefined; - const bindingsForPath = bindings[ j ], - firstActive = bindingsForPath[ lastCachedIndex ], - binding = bindingsForPath[ index ]; + } - bindingsForPath[ index ] = firstActive; - bindingsForPath[ lastCachedIndex ] = binding; + return foveation; - } + }; - } + /** + * Sets the foveation value. + * + * @param {number} value - A number in the range `[0,1]` where `0` means no foveation (full resolution) + * and `1` means maximum foveation (the edges render at lower resolution). + */ + this.setFoveation = function ( value ) { - } // for arguments + // 0 = no foveation = full resolution + // 1 = maximum foveation = the edges render at lower resolution - this.nCachedObjects_ = nCachedObjects; + foveation = value; - } + if ( glProjLayer !== null ) { - // remove & forget - uncache() { + glProjLayer.fixedFoveation = value; - const objects = this._objects, - indicesByUUID = this._indicesByUUID, - bindings = this._bindings, - nBindings = bindings.length; + } - let nCachedObjects = this.nCachedObjects_, - nObjects = objects.length; + if ( glBaseLayer !== null && glBaseLayer.fixedFoveation !== undefined ) { - for ( let i = 0, n = arguments.length; i !== n; ++ i ) { + glBaseLayer.fixedFoveation = value; - const object = arguments[ i ], - uuid = object.uuid, - index = indicesByUUID[ uuid ]; + } - if ( index !== undefined ) { + }; - delete indicesByUUID[ uuid ]; + /** + * Returns `true` if depth sensing is supported. + * + * @return {boolean} Whether depth sensing is supported or not. + */ + this.hasDepthSensing = function () { - if ( index < nCachedObjects ) { + return depthSensing.texture !== null; - // object is cached, shrink the CACHED region + }; - const firstActiveIndex = -- nCachedObjects, - lastCachedObject = objects[ firstActiveIndex ], - lastIndex = -- nObjects, - lastObject = objects[ lastIndex ]; + /** + * Returns the depth sensing mesh. + * + * @return {Mesh} The depth sensing mesh. + */ + this.getDepthSensingMesh = function () { - // last cached object takes this object's place - indicesByUUID[ lastCachedObject.uuid ] = index; - objects[ index ] = lastCachedObject; + return depthSensing.getMesh( cameraXR ); - // last object goes to the activated slot and pop - indicesByUUID[ lastObject.uuid ] = firstActiveIndex; - objects[ firstActiveIndex ] = lastObject; - objects.pop(); + }; - // accounting is done, now do the same for all bindings + // Animation Loop - for ( let j = 0, m = nBindings; j !== m; ++ j ) { + let onAnimationFrameCallback = null; - const bindingsForPath = bindings[ j ], - lastCached = bindingsForPath[ firstActiveIndex ], - last = bindingsForPath[ lastIndex ]; + function onAnimationFrame( time, frame ) { - bindingsForPath[ index ] = lastCached; - bindingsForPath[ firstActiveIndex ] = last; - bindingsForPath.pop(); + pose = frame.getViewerPose( customReferenceSpace || referenceSpace ); + xrFrame = frame; - } + if ( pose !== null ) { - } else { + const views = pose.views; - // object is active, just swap with the last and pop + if ( glBaseLayer !== null ) { - const lastIndex = -- nObjects, - lastObject = objects[ lastIndex ]; + renderer.setRenderTargetFramebuffer( newRenderTarget, glBaseLayer.framebuffer ); + renderer.setRenderTarget( newRenderTarget ); - if ( lastIndex > 0 ) { + } - indicesByUUID[ lastObject.uuid ] = index; + let cameraXRNeedsUpdate = false; - } + // check if it's necessary to rebuild cameraXR's camera list - objects[ index ] = lastObject; - objects.pop(); + if ( views.length !== cameraXR.cameras.length ) { - // accounting is done, now do the same for all bindings + cameraXR.cameras.length = 0; + cameraXRNeedsUpdate = true; - for ( let j = 0, m = nBindings; j !== m; ++ j ) { + } - const bindingsForPath = bindings[ j ]; + for ( let i = 0; i < views.length; i ++ ) { - bindingsForPath[ index ] = bindingsForPath[ lastIndex ]; - bindingsForPath.pop(); + const view = views[ i ]; - } + let viewport = null; - } // cached or active + if ( glBaseLayer !== null ) { - } // if object is known + viewport = glBaseLayer.getViewport( view ); - } // for arguments + } else { - this.nCachedObjects_ = nCachedObjects; + const glSubImage = glBinding.getViewSubImage( glProjLayer, view ); + viewport = glSubImage.viewport; - } + // For side-by-side projection, we only produce a single texture for both eyes. + if ( i === 0 ) { - // Internal interface used by befriended PropertyBinding.Composite: + renderer.setRenderTargetTextures( + newRenderTarget, + glSubImage.colorTexture, + glSubImage.depthStencilTexture ); - subscribe_( path, parsedPath ) { + renderer.setRenderTarget( newRenderTarget ); - // returns an array of bindings for the given path that is changed - // according to the contained objects in the group + } - const indicesByPath = this._bindingsIndicesByPath; - let index = indicesByPath[ path ]; - const bindings = this._bindings; + } - if ( index !== undefined ) return bindings[ index ]; + let camera = cameras[ i ]; - const paths = this._paths, - parsedPaths = this._parsedPaths, - objects = this._objects, - nObjects = objects.length, - nCachedObjects = this.nCachedObjects_, - bindingsForPath = new Array( nObjects ); + if ( camera === undefined ) { - index = bindings.length; + camera = new PerspectiveCamera(); + camera.layers.enable( i ); + camera.viewport = new Vector4(); + cameras[ i ] = camera; - indicesByPath[ path ] = index; + } - paths.push( path ); - parsedPaths.push( parsedPath ); - bindings.push( bindingsForPath ); + camera.matrix.fromArray( view.transform.matrix ); + camera.matrix.decompose( camera.position, camera.quaternion, camera.scale ); + camera.projectionMatrix.fromArray( view.projectionMatrix ); + camera.projectionMatrixInverse.copy( camera.projectionMatrix ).invert(); + camera.viewport.set( viewport.x, viewport.y, viewport.width, viewport.height ); - for ( let i = nCachedObjects, n = objects.length; i !== n; ++ i ) { + if ( i === 0 ) { - const object = objects[ i ]; - bindingsForPath[ i ] = new PropertyBinding( object, path, parsedPath ); + cameraXR.matrix.copy( camera.matrix ); + cameraXR.matrix.decompose( cameraXR.position, cameraXR.quaternion, cameraXR.scale ); - } + } - return bindingsForPath; + if ( cameraXRNeedsUpdate === true ) { - } + cameraXR.cameras.push( camera ); - unsubscribe_( path ) { + } - // tells the group to forget about a property path and no longer - // update the array previously obtained with 'subscribe_' + } - const indicesByPath = this._bindingsIndicesByPath, - index = indicesByPath[ path ]; + // - if ( index !== undefined ) { + const enabledFeatures = session.enabledFeatures; + const gpuDepthSensingEnabled = enabledFeatures && + enabledFeatures.includes( 'depth-sensing' ) && + session.depthUsage == 'gpu-optimized'; - const paths = this._paths, - parsedPaths = this._parsedPaths, - bindings = this._bindings, - lastBindingsIndex = bindings.length - 1, - lastBindings = bindings[ lastBindingsIndex ], - lastBindingsPath = path[ lastBindingsIndex ]; + if ( gpuDepthSensingEnabled && glBinding ) { - indicesByPath[ lastBindingsPath ] = index; + const depthData = glBinding.getDepthInformation( views[ 0 ] ); - bindings[ index ] = lastBindings; - bindings.pop(); + if ( depthData && depthData.isValid && depthData.texture ) { - parsedPaths[ index ] = parsedPaths[ lastBindingsIndex ]; - parsedPaths.pop(); + depthSensing.init( renderer, depthData, session.renderState ); - paths[ index ] = paths[ lastBindingsIndex ]; - paths.pop(); + } - } + } - } + } -} + // -class AnimationAction { + for ( let i = 0; i < controllers.length; i ++ ) { - constructor( mixer, clip, localRoot = null, blendMode = clip.blendMode ) { + const inputSource = controllerInputSources[ i ]; + const controller = controllers[ i ]; - this._mixer = mixer; - this._clip = clip; - this._localRoot = localRoot; - this.blendMode = blendMode; + if ( inputSource !== null && controller !== undefined ) { - const tracks = clip.tracks, - nTracks = tracks.length, - interpolants = new Array( nTracks ); + controller.update( inputSource, frame, customReferenceSpace || referenceSpace ); - const interpolantSettings = { - endingStart: ZeroCurvatureEnding, - endingEnd: ZeroCurvatureEnding - }; + } - for ( let i = 0; i !== nTracks; ++ i ) { + } - const interpolant = tracks[ i ].createInterpolant( null ); - interpolants[ i ] = interpolant; - interpolant.settings = interpolantSettings; + if ( onAnimationFrameCallback ) onAnimationFrameCallback( time, frame ); - } + if ( frame.detectedPlanes ) { - this._interpolantSettings = interpolantSettings; + scope.dispatchEvent( { type: 'planesdetected', data: frame } ); - this._interpolants = interpolants; // bound by the mixer + } - // inside: PropertyMixer (managed by the mixer) - this._propertyBindings = new Array( nTracks ); + xrFrame = null; - this._cacheIndex = null; // for the memory manager - this._byClipCacheIndex = null; // for the memory manager + } - this._timeScaleInterpolant = null; - this._weightInterpolant = null; + const animation = new WebGLAnimation(); - this.loop = LoopRepeat; - this._loopCount = - 1; + animation.setAnimationLoop( onAnimationFrame ); - // global mixer time when the action is to be started - // it's set back to 'null' upon start of the action - this._startTime = null; + this.setAnimationLoop = function ( callback ) { - // scaled local time of the action - // gets clamped or wrapped to 0..clip.duration according to loop - this.time = 0; + onAnimationFrameCallback = callback; - this.timeScale = 1; - this._effectiveTimeScale = 1; + }; - this.weight = 1; - this._effectiveWeight = 1; + this.dispose = function () {}; - this.repetitions = Infinity; // no. of repetitions when looping + } - this.paused = false; // true -> zero effective time scale - this.enabled = true; // false -> zero effective weight +} - this.clampWhenFinished = false;// keep feeding the last frame? +const _e1 = /*@__PURE__*/ new Euler(); +const _m1 = /*@__PURE__*/ new Matrix4(); - this.zeroSlopeAtStart = true;// for smooth interpolation w/o separate - this.zeroSlopeAtEnd = true;// clips for start, loop and end +function WebGLMaterials( renderer, properties ) { - } + function refreshTransformUniform( map, uniform ) { - // State & Scheduling + if ( map.matrixAutoUpdate === true ) { - play() { + map.updateMatrix(); - this._mixer._activateAction( this ); + } - return this; + uniform.value.copy( map.matrix ); } - stop() { - - this._mixer._deactivateAction( this ); + function refreshFogUniforms( uniforms, fog ) { - return this.reset(); + fog.color.getRGB( uniforms.fogColor.value, getUnlitUniformColorSpace( renderer ) ); - } + if ( fog.isFog ) { - reset() { + uniforms.fogNear.value = fog.near; + uniforms.fogFar.value = fog.far; - this.paused = false; - this.enabled = true; + } else if ( fog.isFogExp2 ) { - this.time = 0; // restart clip - this._loopCount = - 1;// forget previous loops - this._startTime = null;// forget scheduling + uniforms.fogDensity.value = fog.density; - return this.stopFading().stopWarping(); + } } - isRunning() { + function refreshMaterialUniforms( uniforms, material, pixelRatio, height, transmissionRenderTarget ) { - return this.enabled && ! this.paused && this.timeScale !== 0 && - this._startTime === null && this._mixer._isActiveAction( this ); + if ( material.isMeshBasicMaterial ) { - } + refreshUniformsCommon( uniforms, material ); - // return true when play has been called - isScheduled() { + } else if ( material.isMeshLambertMaterial ) { - return this._mixer._isActiveAction( this ); + refreshUniformsCommon( uniforms, material ); - } + } else if ( material.isMeshToonMaterial ) { - startAt( time ) { + refreshUniformsCommon( uniforms, material ); + refreshUniformsToon( uniforms, material ); - this._startTime = time; + } else if ( material.isMeshPhongMaterial ) { - return this; + refreshUniformsCommon( uniforms, material ); + refreshUniformsPhong( uniforms, material ); - } + } else if ( material.isMeshStandardMaterial ) { - setLoop( mode, repetitions ) { + refreshUniformsCommon( uniforms, material ); + refreshUniformsStandard( uniforms, material ); - this.loop = mode; - this.repetitions = repetitions; + if ( material.isMeshPhysicalMaterial ) { - return this; + refreshUniformsPhysical( uniforms, material, transmissionRenderTarget ); - } + } - // Weight + } else if ( material.isMeshMatcapMaterial ) { - // set the weight stopping any scheduled fading - // although .enabled = false yields an effective weight of zero, this - // method does *not* change .enabled, because it would be confusing - setEffectiveWeight( weight ) { + refreshUniformsCommon( uniforms, material ); + refreshUniformsMatcap( uniforms, material ); - this.weight = weight; + } else if ( material.isMeshDepthMaterial ) { - // note: same logic as when updated at runtime - this._effectiveWeight = this.enabled ? weight : 0; + refreshUniformsCommon( uniforms, material ); - return this.stopFading(); + } else if ( material.isMeshDistanceMaterial ) { - } + refreshUniformsCommon( uniforms, material ); + refreshUniformsDistance( uniforms, material ); - // return the weight considering fading and .enabled - getEffectiveWeight() { + } else if ( material.isMeshNormalMaterial ) { - return this._effectiveWeight; + refreshUniformsCommon( uniforms, material ); - } + } else if ( material.isLineBasicMaterial ) { - fadeIn( duration ) { + refreshUniformsLine( uniforms, material ); - return this._scheduleFading( duration, 0, 1 ); + if ( material.isLineDashedMaterial ) { - } + refreshUniformsDash( uniforms, material ); - fadeOut( duration ) { + } - return this._scheduleFading( duration, 1, 0 ); + } else if ( material.isPointsMaterial ) { - } + refreshUniformsPoints( uniforms, material, pixelRatio, height ); - crossFadeFrom( fadeOutAction, duration, warp ) { + } else if ( material.isSpriteMaterial ) { - fadeOutAction.fadeOut( duration ); - this.fadeIn( duration ); + refreshUniformsSprites( uniforms, material ); - if ( warp ) { + } else if ( material.isShadowMaterial ) { - const fadeInDuration = this._clip.duration, - fadeOutDuration = fadeOutAction._clip.duration, + uniforms.color.value.copy( material.color ); + uniforms.opacity.value = material.opacity; - startEndRatio = fadeOutDuration / fadeInDuration, - endStartRatio = fadeInDuration / fadeOutDuration; + } else if ( material.isShaderMaterial ) { - fadeOutAction.warp( 1.0, startEndRatio, duration ); - this.warp( endStartRatio, 1.0, duration ); + material.uniformsNeedUpdate = false; // #15581 } - return this; - } - crossFadeTo( fadeInAction, duration, warp ) { + function refreshUniformsCommon( uniforms, material ) { - return fadeInAction.crossFadeFrom( this, duration, warp ); + uniforms.opacity.value = material.opacity; - } + if ( material.color ) { - stopFading() { + uniforms.diffuse.value.copy( material.color ); - const weightInterpolant = this._weightInterpolant; + } - if ( weightInterpolant !== null ) { + if ( material.emissive ) { - this._weightInterpolant = null; - this._mixer._takeBackControlInterpolant( weightInterpolant ); + uniforms.emissive.value.copy( material.emissive ).multiplyScalar( material.emissiveIntensity ); } - return this; + if ( material.map ) { - } + uniforms.map.value = material.map; - // Time Scale Control + refreshTransformUniform( material.map, uniforms.mapTransform ); - // set the time scale stopping any scheduled warping - // although .paused = true yields an effective time scale of zero, this - // method does *not* change .paused, because it would be confusing - setEffectiveTimeScale( timeScale ) { + } - this.timeScale = timeScale; - this._effectiveTimeScale = this.paused ? 0 : timeScale; + if ( material.alphaMap ) { - return this.stopWarping(); + uniforms.alphaMap.value = material.alphaMap; - } + refreshTransformUniform( material.alphaMap, uniforms.alphaMapTransform ); - // return the time scale considering warping and .paused - getEffectiveTimeScale() { + } - return this._effectiveTimeScale; + if ( material.bumpMap ) { - } + uniforms.bumpMap.value = material.bumpMap; - setDuration( duration ) { + refreshTransformUniform( material.bumpMap, uniforms.bumpMapTransform ); - this.timeScale = this._clip.duration / duration; + uniforms.bumpScale.value = material.bumpScale; - return this.stopWarping(); + if ( material.side === BackSide ) { - } + uniforms.bumpScale.value *= -1; - syncWith( action ) { + } - this.time = action.time; - this.timeScale = action.timeScale; + } - return this.stopWarping(); + if ( material.normalMap ) { - } + uniforms.normalMap.value = material.normalMap; - halt( duration ) { + refreshTransformUniform( material.normalMap, uniforms.normalMapTransform ); - return this.warp( this._effectiveTimeScale, 0, duration ); + uniforms.normalScale.value.copy( material.normalScale ); - } + if ( material.side === BackSide ) { - warp( startTimeScale, endTimeScale, duration ) { + uniforms.normalScale.value.negate(); - const mixer = this._mixer, - now = mixer.time, - timeScale = this.timeScale; + } - let interpolant = this._timeScaleInterpolant; + } - if ( interpolant === null ) { + if ( material.displacementMap ) { - interpolant = mixer._lendControlInterpolant(); - this._timeScaleInterpolant = interpolant; + uniforms.displacementMap.value = material.displacementMap; - } + refreshTransformUniform( material.displacementMap, uniforms.displacementMapTransform ); - const times = interpolant.parameterPositions, - values = interpolant.sampleValues; + uniforms.displacementScale.value = material.displacementScale; + uniforms.displacementBias.value = material.displacementBias; - times[ 0 ] = now; - times[ 1 ] = now + duration; + } - values[ 0 ] = startTimeScale / timeScale; - values[ 1 ] = endTimeScale / timeScale; + if ( material.emissiveMap ) { - return this; + uniforms.emissiveMap.value = material.emissiveMap; - } + refreshTransformUniform( material.emissiveMap, uniforms.emissiveMapTransform ); - stopWarping() { + } - const timeScaleInterpolant = this._timeScaleInterpolant; + if ( material.specularMap ) { - if ( timeScaleInterpolant !== null ) { + uniforms.specularMap.value = material.specularMap; - this._timeScaleInterpolant = null; - this._mixer._takeBackControlInterpolant( timeScaleInterpolant ); + refreshTransformUniform( material.specularMap, uniforms.specularMapTransform ); } - return this; + if ( material.alphaTest > 0 ) { - } + uniforms.alphaTest.value = material.alphaTest; - // Object Accessors + } - getMixer() { + const materialProperties = properties.get( material ); - return this._mixer; + const envMap = materialProperties.envMap; + const envMapRotation = materialProperties.envMapRotation; - } + if ( envMap ) { - getClip() { + uniforms.envMap.value = envMap; - return this._clip; + _e1.copy( envMapRotation ); - } + // accommodate left-handed frame + _e1.x *= -1; _e1.y *= -1; _e1.z *= -1; - getRoot() { + if ( envMap.isCubeTexture && envMap.isRenderTargetTexture === false ) { - return this._localRoot || this._mixer._root; + // environment maps which are not cube render targets or PMREMs follow a different convention + _e1.y *= -1; + _e1.z *= -1; - } + } - // Interna + uniforms.envMapRotation.value.setFromMatrix4( _m1.makeRotationFromEuler( _e1 ) ); - _update( time, deltaTime, timeDirection, accuIndex ) { + uniforms.flipEnvMap.value = ( envMap.isCubeTexture && envMap.isRenderTargetTexture === false ) ? -1 : 1; - // called by the mixer + uniforms.reflectivity.value = material.reflectivity; + uniforms.ior.value = material.ior; + uniforms.refractionRatio.value = material.refractionRatio; - if ( ! this.enabled ) { + } - // call ._updateWeight() to update ._effectiveWeight + if ( material.lightMap ) { - this._updateWeight( time ); - return; + uniforms.lightMap.value = material.lightMap; + uniforms.lightMapIntensity.value = material.lightMapIntensity; + + refreshTransformUniform( material.lightMap, uniforms.lightMapTransform ); } - const startTime = this._startTime; + if ( material.aoMap ) { - if ( startTime !== null ) { + uniforms.aoMap.value = material.aoMap; + uniforms.aoMapIntensity.value = material.aoMapIntensity; - // check for scheduled start of action + refreshTransformUniform( material.aoMap, uniforms.aoMapTransform ); - const timeRunning = ( time - startTime ) * timeDirection; - if ( timeRunning < 0 || timeDirection === 0 ) { + } - deltaTime = 0; + } - } else { + function refreshUniformsLine( uniforms, material ) { + uniforms.diffuse.value.copy( material.color ); + uniforms.opacity.value = material.opacity; - this._startTime = null; // unschedule - deltaTime = timeDirection * timeRunning; + if ( material.map ) { - } + uniforms.map.value = material.map; - } + refreshTransformUniform( material.map, uniforms.mapTransform ); - // apply time scale and advance time + } - deltaTime *= this._updateTimeScale( time ); - const clipTime = this._updateTime( deltaTime ); + } - // note: _updateTime may disable the action resulting in - // an effective weight of 0 + function refreshUniformsDash( uniforms, material ) { - const weight = this._updateWeight( time ); + uniforms.dashSize.value = material.dashSize; + uniforms.totalSize.value = material.dashSize + material.gapSize; + uniforms.scale.value = material.scale; - if ( weight > 0 ) { + } - const interpolants = this._interpolants; - const propertyMixers = this._propertyBindings; + function refreshUniformsPoints( uniforms, material, pixelRatio, height ) { - switch ( this.blendMode ) { + uniforms.diffuse.value.copy( material.color ); + uniforms.opacity.value = material.opacity; + uniforms.size.value = material.size * pixelRatio; + uniforms.scale.value = height * 0.5; - case AdditiveAnimationBlendMode: + if ( material.map ) { - for ( let j = 0, m = interpolants.length; j !== m; ++ j ) { + uniforms.map.value = material.map; - interpolants[ j ].evaluate( clipTime ); - propertyMixers[ j ].accumulateAdditive( weight ); + refreshTransformUniform( material.map, uniforms.uvTransform ); - } + } - break; + if ( material.alphaMap ) { - case NormalAnimationBlendMode: - default: + uniforms.alphaMap.value = material.alphaMap; - for ( let j = 0, m = interpolants.length; j !== m; ++ j ) { + refreshTransformUniform( material.alphaMap, uniforms.alphaMapTransform ); - interpolants[ j ].evaluate( clipTime ); - propertyMixers[ j ].accumulate( accuIndex, weight ); + } - } + if ( material.alphaTest > 0 ) { - } + uniforms.alphaTest.value = material.alphaTest; } } - _updateWeight( time ) { + function refreshUniformsSprites( uniforms, material ) { - let weight = 0; + uniforms.diffuse.value.copy( material.color ); + uniforms.opacity.value = material.opacity; + uniforms.rotation.value = material.rotation; - if ( this.enabled ) { + if ( material.map ) { - weight = this.weight; - const interpolant = this._weightInterpolant; + uniforms.map.value = material.map; - if ( interpolant !== null ) { + refreshTransformUniform( material.map, uniforms.mapTransform ); - const interpolantValue = interpolant.evaluate( time )[ 0 ]; + } - weight *= interpolantValue; + if ( material.alphaMap ) { - if ( time > interpolant.parameterPositions[ 1 ] ) { + uniforms.alphaMap.value = material.alphaMap; - this.stopFading(); + refreshTransformUniform( material.alphaMap, uniforms.alphaMapTransform ); - if ( interpolantValue === 0 ) { + } - // faded out, disable - this.enabled = false; + if ( material.alphaTest > 0 ) { - } + uniforms.alphaTest.value = material.alphaTest; - } + } - } + } - } + function refreshUniformsPhong( uniforms, material ) { - this._effectiveWeight = weight; - return weight; + uniforms.specular.value.copy( material.specular ); + uniforms.shininess.value = Math.max( material.shininess, 1e-4 ); // to prevent pow( 0.0, 0.0 ) } - _updateTimeScale( time ) { - - let timeScale = 0; - - if ( ! this.paused ) { + function refreshUniformsToon( uniforms, material ) { - timeScale = this.timeScale; + if ( material.gradientMap ) { - const interpolant = this._timeScaleInterpolant; + uniforms.gradientMap.value = material.gradientMap; - if ( interpolant !== null ) { + } - const interpolantValue = interpolant.evaluate( time )[ 0 ]; + } - timeScale *= interpolantValue; + function refreshUniformsStandard( uniforms, material ) { - if ( time > interpolant.parameterPositions[ 1 ] ) { + uniforms.metalness.value = material.metalness; - this.stopWarping(); + if ( material.metalnessMap ) { - if ( timeScale === 0 ) { + uniforms.metalnessMap.value = material.metalnessMap; - // motion has halted, pause - this.paused = true; + refreshTransformUniform( material.metalnessMap, uniforms.metalnessMapTransform ); - } else { + } - // warp done - apply final time scale - this.timeScale = timeScale; + uniforms.roughness.value = material.roughness; - } + if ( material.roughnessMap ) { - } + uniforms.roughnessMap.value = material.roughnessMap; - } + refreshTransformUniform( material.roughnessMap, uniforms.roughnessMapTransform ); } - this._effectiveTimeScale = timeScale; - return timeScale; - - } + if ( material.envMap ) { - _updateTime( deltaTime ) { + //uniforms.envMap.value = material.envMap; // part of uniforms common - const duration = this._clip.duration; - const loop = this.loop; + uniforms.envMapIntensity.value = material.envMapIntensity; - let time = this.time + deltaTime; - let loopCount = this._loopCount; + } - const pingPong = ( loop === LoopPingPong ); + } - if ( deltaTime === 0 ) { + function refreshUniformsPhysical( uniforms, material, transmissionRenderTarget ) { - if ( loopCount === - 1 ) return time; + uniforms.ior.value = material.ior; // also part of uniforms common - return ( pingPong && ( loopCount & 1 ) === 1 ) ? duration - time : time; + if ( material.sheen > 0 ) { - } + uniforms.sheenColor.value.copy( material.sheenColor ).multiplyScalar( material.sheen ); - if ( loop === LoopOnce ) { + uniforms.sheenRoughness.value = material.sheenRoughness; - if ( loopCount === - 1 ) { + if ( material.sheenColorMap ) { - // just started + uniforms.sheenColorMap.value = material.sheenColorMap; - this._loopCount = 0; - this._setEndings( true, true, false ); + refreshTransformUniform( material.sheenColorMap, uniforms.sheenColorMapTransform ); } - handle_stop: { - - if ( time >= duration ) { - - time = duration; + if ( material.sheenRoughnessMap ) { - } else if ( time < 0 ) { + uniforms.sheenRoughnessMap.value = material.sheenRoughnessMap; - time = 0; + refreshTransformUniform( material.sheenRoughnessMap, uniforms.sheenRoughnessMapTransform ); - } else { + } - this.time = time; + } - break handle_stop; + if ( material.clearcoat > 0 ) { - } + uniforms.clearcoat.value = material.clearcoat; + uniforms.clearcoatRoughness.value = material.clearcoatRoughness; - if ( this.clampWhenFinished ) this.paused = true; - else this.enabled = false; + if ( material.clearcoatMap ) { - this.time = time; + uniforms.clearcoatMap.value = material.clearcoatMap; - this._mixer.dispatchEvent( { - type: 'finished', action: this, - direction: deltaTime < 0 ? - 1 : 1 - } ); + refreshTransformUniform( material.clearcoatMap, uniforms.clearcoatMapTransform ); } - } else { // repetitive Repeat or PingPong + if ( material.clearcoatRoughnessMap ) { + + uniforms.clearcoatRoughnessMap.value = material.clearcoatRoughnessMap; - if ( loopCount === - 1 ) { + refreshTransformUniform( material.clearcoatRoughnessMap, uniforms.clearcoatRoughnessMapTransform ); - // just started + } - if ( deltaTime >= 0 ) { + if ( material.clearcoatNormalMap ) { - loopCount = 0; + uniforms.clearcoatNormalMap.value = material.clearcoatNormalMap; - this._setEndings( true, this.repetitions === 0, pingPong ); + refreshTransformUniform( material.clearcoatNormalMap, uniforms.clearcoatNormalMapTransform ); - } else { + uniforms.clearcoatNormalScale.value.copy( material.clearcoatNormalScale ); - // when looping in reverse direction, the initial - // transition through zero counts as a repetition, - // so leave loopCount at -1 + if ( material.side === BackSide ) { - this._setEndings( this.repetitions === 0, true, pingPong ); + uniforms.clearcoatNormalScale.value.negate(); } } - if ( time >= duration || time < 0 ) { + } - // wrap around + if ( material.dispersion > 0 ) { - const loopDelta = Math.floor( time / duration ); // signed - time -= duration * loopDelta; + uniforms.dispersion.value = material.dispersion; - loopCount += Math.abs( loopDelta ); + } - const pending = this.repetitions - loopCount; + if ( material.iridescence > 0 ) { - if ( pending <= 0 ) { + uniforms.iridescence.value = material.iridescence; + uniforms.iridescenceIOR.value = material.iridescenceIOR; + uniforms.iridescenceThicknessMinimum.value = material.iridescenceThicknessRange[ 0 ]; + uniforms.iridescenceThicknessMaximum.value = material.iridescenceThicknessRange[ 1 ]; - // have to stop (switch state, clamp time, fire event) + if ( material.iridescenceMap ) { - if ( this.clampWhenFinished ) this.paused = true; - else this.enabled = false; + uniforms.iridescenceMap.value = material.iridescenceMap; - time = deltaTime > 0 ? duration : 0; + refreshTransformUniform( material.iridescenceMap, uniforms.iridescenceMapTransform ); - this.time = time; + } - this._mixer.dispatchEvent( { - type: 'finished', action: this, - direction: deltaTime > 0 ? 1 : - 1 - } ); + if ( material.iridescenceThicknessMap ) { - } else { + uniforms.iridescenceThicknessMap.value = material.iridescenceThicknessMap; - // keep running + refreshTransformUniform( material.iridescenceThicknessMap, uniforms.iridescenceThicknessMapTransform ); - if ( pending === 1 ) { + } - // entering the last round + } - const atStart = deltaTime < 0; - this._setEndings( atStart, ! atStart, pingPong ); + if ( material.transmission > 0 ) { - } else { + uniforms.transmission.value = material.transmission; + uniforms.transmissionSamplerMap.value = transmissionRenderTarget.texture; + uniforms.transmissionSamplerSize.value.set( transmissionRenderTarget.width, transmissionRenderTarget.height ); - this._setEndings( false, false, pingPong ); + if ( material.transmissionMap ) { - } + uniforms.transmissionMap.value = material.transmissionMap; - this._loopCount = loopCount; + refreshTransformUniform( material.transmissionMap, uniforms.transmissionMapTransform ); - this.time = time; + } - this._mixer.dispatchEvent( { - type: 'loop', action: this, loopDelta: loopDelta - } ); + uniforms.thickness.value = material.thickness; - } + if ( material.thicknessMap ) { - } else { + uniforms.thicknessMap.value = material.thicknessMap; - this.time = time; + refreshTransformUniform( material.thicknessMap, uniforms.thicknessMapTransform ); } - if ( pingPong && ( loopCount & 1 ) === 1 ) { - - // invert time for the "pong round" - - return duration - time; - - } + uniforms.attenuationDistance.value = material.attenuationDistance; + uniforms.attenuationColor.value.copy( material.attenuationColor ); } - return time; - - } - - _setEndings( atStart, atEnd, pingPong ) { - - const settings = this._interpolantSettings; + if ( material.anisotropy > 0 ) { - if ( pingPong ) { + uniforms.anisotropyVector.value.set( material.anisotropy * Math.cos( material.anisotropyRotation ), material.anisotropy * Math.sin( material.anisotropyRotation ) ); - settings.endingStart = ZeroSlopeEnding; - settings.endingEnd = ZeroSlopeEnding; + if ( material.anisotropyMap ) { - } else { + uniforms.anisotropyMap.value = material.anisotropyMap; - // assuming for LoopOnce atStart == atEnd == true + refreshTransformUniform( material.anisotropyMap, uniforms.anisotropyMapTransform ); - if ( atStart ) { + } - settings.endingStart = this.zeroSlopeAtStart ? ZeroSlopeEnding : ZeroCurvatureEnding; + } - } else { + uniforms.specularIntensity.value = material.specularIntensity; + uniforms.specularColor.value.copy( material.specularColor ); - settings.endingStart = WrapAroundEnding; + if ( material.specularColorMap ) { - } + uniforms.specularColorMap.value = material.specularColorMap; - if ( atEnd ) { + refreshTransformUniform( material.specularColorMap, uniforms.specularColorMapTransform ); - settings.endingEnd = this.zeroSlopeAtEnd ? ZeroSlopeEnding : ZeroCurvatureEnding; + } - } else { + if ( material.specularIntensityMap ) { - settings.endingEnd = WrapAroundEnding; + uniforms.specularIntensityMap.value = material.specularIntensityMap; - } + refreshTransformUniform( material.specularIntensityMap, uniforms.specularIntensityMapTransform ); } } - _scheduleFading( duration, weightNow, weightThen ) { - - const mixer = this._mixer, now = mixer.time; - let interpolant = this._weightInterpolant; + function refreshUniformsMatcap( uniforms, material ) { - if ( interpolant === null ) { + if ( material.matcap ) { - interpolant = mixer._lendControlInterpolant(); - this._weightInterpolant = interpolant; + uniforms.matcap.value = material.matcap; } - const times = interpolant.parameterPositions, - values = interpolant.sampleValues; + } + + function refreshUniformsDistance( uniforms, material ) { - times[ 0 ] = now; - values[ 0 ] = weightNow; - times[ 1 ] = now + duration; - values[ 1 ] = weightThen; + const light = properties.get( material ).light; - return this; + uniforms.referencePosition.value.setFromMatrixPosition( light.matrixWorld ); + uniforms.nearDistance.value = light.shadow.camera.near; + uniforms.farDistance.value = light.shadow.camera.far; } -} + return { + refreshFogUniforms: refreshFogUniforms, + refreshMaterialUniforms: refreshMaterialUniforms + }; -const _controlInterpolantsResultBuffer = new Float32Array( 1 ); +} +function WebGLUniformsGroups( gl, info, capabilities, state ) { -class AnimationMixer extends EventDispatcher { + let buffers = {}; + let updateList = {}; + let allocatedBindingPoints = []; - constructor( root ) { + const maxBindingPoints = gl.getParameter( gl.MAX_UNIFORM_BUFFER_BINDINGS ); // binding points are global whereas block indices are per shader program - super(); + function bind( uniformsGroup, program ) { - this._root = root; - this._initMemoryManager(); - this._accuIndex = 0; - this.time = 0; - this.timeScale = 1.0; + const webglProgram = program.program; + state.uniformBlockBinding( uniformsGroup, webglProgram ); } - _bindAction( action, prototypeAction ) { + function update( uniformsGroup, program ) { + + let buffer = buffers[ uniformsGroup.id ]; - const root = action._localRoot || this._root, - tracks = action._clip.tracks, - nTracks = tracks.length, - bindings = action._propertyBindings, - interpolants = action._interpolants, - rootUuid = root.uuid, - bindingsByRoot = this._bindingsByRootAndName; + if ( buffer === undefined ) { - let bindingsByName = bindingsByRoot[ rootUuid ]; + prepareUniformsGroup( uniformsGroup ); - if ( bindingsByName === undefined ) { + buffer = createBuffer( uniformsGroup ); + buffers[ uniformsGroup.id ] = buffer; - bindingsByName = {}; - bindingsByRoot[ rootUuid ] = bindingsByName; + uniformsGroup.addEventListener( 'dispose', onUniformsGroupsDispose ); } - for ( let i = 0; i !== nTracks; ++ i ) { + // ensure to update the binding points/block indices mapping for this program + + const webglProgram = program.program; + state.updateUBOMapping( uniformsGroup, webglProgram ); + + // update UBO once per frame - const track = tracks[ i ], - trackName = track.name; + const frame = info.render.frame; - let binding = bindingsByName[ trackName ]; + if ( updateList[ uniformsGroup.id ] !== frame ) { - if ( binding !== undefined ) { + updateBufferData( uniformsGroup ); - ++ binding.referenceCount; - bindings[ i ] = binding; + updateList[ uniformsGroup.id ] = frame; - } else { + } - binding = bindings[ i ]; + } - if ( binding !== undefined ) { + function createBuffer( uniformsGroup ) { - // existing binding, make sure the cache knows + // the setup of an UBO is independent of a particular shader program but global - if ( binding._cacheIndex === null ) { + const bindingPointIndex = allocateBindingPointIndex(); + uniformsGroup.__bindingPointIndex = bindingPointIndex; - ++ binding.referenceCount; - this._addInactiveBinding( binding, rootUuid, trackName ); + const buffer = gl.createBuffer(); + const size = uniformsGroup.__size; + const usage = uniformsGroup.usage; - } + gl.bindBuffer( gl.UNIFORM_BUFFER, buffer ); + gl.bufferData( gl.UNIFORM_BUFFER, size, usage ); + gl.bindBuffer( gl.UNIFORM_BUFFER, null ); + gl.bindBufferBase( gl.UNIFORM_BUFFER, bindingPointIndex, buffer ); - continue; + return buffer; - } + } - const path = prototypeAction && prototypeAction. - _propertyBindings[ i ].binding.parsedPath; + function allocateBindingPointIndex() { - binding = new PropertyMixer( - PropertyBinding.create( root, trackName, path ), - track.ValueTypeName, track.getValueSize() ); + for ( let i = 0; i < maxBindingPoints; i ++ ) { - ++ binding.referenceCount; - this._addInactiveBinding( binding, rootUuid, trackName ); + if ( allocatedBindingPoints.indexOf( i ) === -1 ) { - bindings[ i ] = binding; + allocatedBindingPoints.push( i ); + return i; } - interpolants[ i ].resultBuffer = binding.buffer; - } + console.error( 'THREE.WebGLRenderer: Maximum number of simultaneously usable uniforms groups reached.' ); + + return 0; + } - _activateAction( action ) { + function updateBufferData( uniformsGroup ) { - if ( ! this._isActiveAction( action ) ) { + const buffer = buffers[ uniformsGroup.id ]; + const uniforms = uniformsGroup.uniforms; + const cache = uniformsGroup.__cache; - if ( action._cacheIndex === null ) { + gl.bindBuffer( gl.UNIFORM_BUFFER, buffer ); - // this action has been forgotten by the cache, but the user - // appears to be still using it -> rebind + for ( let i = 0, il = uniforms.length; i < il; i ++ ) { - const rootUuid = ( action._localRoot || this._root ).uuid, - clipUuid = action._clip.uuid, - actionsForClip = this._actionsByClip[ clipUuid ]; + const uniformArray = Array.isArray( uniforms[ i ] ) ? uniforms[ i ] : [ uniforms[ i ] ]; - this._bindAction( action, - actionsForClip && actionsForClip.knownActions[ 0 ] ); + for ( let j = 0, jl = uniformArray.length; j < jl; j ++ ) { - this._addInactiveAction( action, clipUuid, rootUuid ); + const uniform = uniformArray[ j ]; - } + if ( hasUniformChanged( uniform, i, j, cache ) === true ) { - const bindings = action._propertyBindings; + const offset = uniform.__offset; - // increment reference counts / sort out state - for ( let i = 0, n = bindings.length; i !== n; ++ i ) { + const values = Array.isArray( uniform.value ) ? uniform.value : [ uniform.value ]; - const binding = bindings[ i ]; + let arrayOffset = 0; - if ( binding.useCount ++ === 0 ) { + for ( let k = 0; k < values.length; k ++ ) { - this._lendBinding( binding ); - binding.saveOriginalState(); + const value = values[ k ]; - } + const info = getUniformSize( value ); - } + // TODO add integer and struct support + if ( typeof value === 'number' || typeof value === 'boolean' ) { - this._lendAction( action ); + uniform.__data[ 0 ] = value; + gl.bufferSubData( gl.UNIFORM_BUFFER, offset + arrayOffset, uniform.__data ); - } + } else if ( value.isMatrix3 ) { - } + // manually converting 3x3 to 3x4 - _deactivateAction( action ) { + uniform.__data[ 0 ] = value.elements[ 0 ]; + uniform.__data[ 1 ] = value.elements[ 1 ]; + uniform.__data[ 2 ] = value.elements[ 2 ]; + uniform.__data[ 3 ] = 0; + uniform.__data[ 4 ] = value.elements[ 3 ]; + uniform.__data[ 5 ] = value.elements[ 4 ]; + uniform.__data[ 6 ] = value.elements[ 5 ]; + uniform.__data[ 7 ] = 0; + uniform.__data[ 8 ] = value.elements[ 6 ]; + uniform.__data[ 9 ] = value.elements[ 7 ]; + uniform.__data[ 10 ] = value.elements[ 8 ]; + uniform.__data[ 11 ] = 0; - if ( this._isActiveAction( action ) ) { + } else { - const bindings = action._propertyBindings; + value.toArray( uniform.__data, arrayOffset ); - // decrement reference counts / sort out state - for ( let i = 0, n = bindings.length; i !== n; ++ i ) { + arrayOffset += info.storage / Float32Array.BYTES_PER_ELEMENT; - const binding = bindings[ i ]; + } - if ( -- binding.useCount === 0 ) { + } - binding.restoreOriginalState(); - this._takeBackBinding( binding ); + gl.bufferSubData( gl.UNIFORM_BUFFER, offset, uniform.__data ); } } - this._takeBackAction( action ); - } - } - - // Memory manager - - _initMemoryManager() { + gl.bindBuffer( gl.UNIFORM_BUFFER, null ); - this._actions = []; // 'nActiveActions' followed by inactive ones - this._nActiveActions = 0; + } - this._actionsByClip = {}; - // inside: - // { - // knownActions: Array< AnimationAction > - used as prototypes - // actionByRoot: AnimationAction - lookup - // } + function hasUniformChanged( uniform, index, indexArray, cache ) { + const value = uniform.value; + const indexString = index + '_' + indexArray; - this._bindings = []; // 'nActiveBindings' followed by inactive ones - this._nActiveBindings = 0; + if ( cache[ indexString ] === undefined ) { - this._bindingsByRootAndName = {}; // inside: Map< name, PropertyMixer > + // cache entry does not exist so far + if ( typeof value === 'number' || typeof value === 'boolean' ) { - this._controlInterpolants = []; // same game as above - this._nActiveControlInterpolants = 0; + cache[ indexString ] = value; - const scope = this; + } else { - this.stats = { + cache[ indexString ] = value.clone(); - actions: { - get total() { + } - return scope._actions.length; + return true; - }, - get inUse() { + } else { - return scope._nActiveActions; + const cachedObject = cache[ indexString ]; - } - }, - bindings: { - get total() { + // compare current value with cached entry - return scope._bindings.length; + if ( typeof value === 'number' || typeof value === 'boolean' ) { - }, - get inUse() { + if ( cachedObject !== value ) { - return scope._nActiveBindings; + cache[ indexString ] = value; + return true; } - }, - controlInterpolants: { - get total() { - return scope._controlInterpolants.length; + } else { - }, - get inUse() { + if ( cachedObject.equals( value ) === false ) { - return scope._nActiveControlInterpolants; + cachedObject.copy( value ); + return true; } - } - }; + } - } + } - // Memory management for AnimationAction objects + return false; - _isActiveAction( action ) { + } - const index = action._cacheIndex; - return index !== null && index < this._nActiveActions; + function prepareUniformsGroup( uniformsGroup ) { - } + // determine total buffer size according to the STD140 layout + // Hint: STD140 is the only supported layout in WebGL 2 - _addInactiveAction( action, clipUuid, rootUuid ) { + const uniforms = uniformsGroup.uniforms; - const actions = this._actions, - actionsByClip = this._actionsByClip; + let offset = 0; // global buffer offset in bytes + const chunkSize = 16; // size of a chunk in bytes - let actionsForClip = actionsByClip[ clipUuid ]; + for ( let i = 0, l = uniforms.length; i < l; i ++ ) { - if ( actionsForClip === undefined ) { + const uniformArray = Array.isArray( uniforms[ i ] ) ? uniforms[ i ] : [ uniforms[ i ] ]; - actionsForClip = { + for ( let j = 0, jl = uniformArray.length; j < jl; j ++ ) { - knownActions: [ action ], - actionByRoot: {} + const uniform = uniformArray[ j ]; - }; + const values = Array.isArray( uniform.value ) ? uniform.value : [ uniform.value ]; - action._byClipCacheIndex = 0; + for ( let k = 0, kl = values.length; k < kl; k ++ ) { - actionsByClip[ clipUuid ] = actionsForClip; + const value = values[ k ]; - } else { + const info = getUniformSize( value ); - const knownActions = actionsForClip.knownActions; + const chunkOffset = offset % chunkSize; // offset in the current chunk + const chunkPadding = chunkOffset % info.boundary; // required padding to match boundary + const chunkStart = chunkOffset + chunkPadding; // the start position in the current chunk for the data - action._byClipCacheIndex = knownActions.length; - knownActions.push( action ); + offset += chunkPadding; - } + // Check for chunk overflow + if ( chunkStart !== 0 && ( chunkSize - chunkStart ) < info.storage ) { - action._cacheIndex = actions.length; - actions.push( action ); + // Add padding and adjust offset + offset += ( chunkSize - chunkStart ); - actionsForClip.actionByRoot[ rootUuid ] = action; + } - } + // the following two properties will be used for partial buffer updates + uniform.__data = new Float32Array( info.storage / Float32Array.BYTES_PER_ELEMENT ); + uniform.__offset = offset; - _removeInactiveAction( action ) { + // Update the global offset + offset += info.storage; - const actions = this._actions, - lastInactiveAction = actions[ actions.length - 1 ], - cacheIndex = action._cacheIndex; + } - lastInactiveAction._cacheIndex = cacheIndex; - actions[ cacheIndex ] = lastInactiveAction; - actions.pop(); + } - action._cacheIndex = null; + } + // ensure correct final padding - const clipUuid = action._clip.uuid, - actionsByClip = this._actionsByClip, - actionsForClip = actionsByClip[ clipUuid ], - knownActionsForClip = actionsForClip.knownActions, + const chunkOffset = offset % chunkSize; - lastKnownAction = - knownActionsForClip[ knownActionsForClip.length - 1 ], + if ( chunkOffset > 0 ) offset += ( chunkSize - chunkOffset ); - byClipCacheIndex = action._byClipCacheIndex; + // - lastKnownAction._byClipCacheIndex = byClipCacheIndex; - knownActionsForClip[ byClipCacheIndex ] = lastKnownAction; - knownActionsForClip.pop(); + uniformsGroup.__size = offset; + uniformsGroup.__cache = {}; - action._byClipCacheIndex = null; + return this; + } - const actionByRoot = actionsForClip.actionByRoot, - rootUuid = ( action._localRoot || this._root ).uuid; + function getUniformSize( value ) { - delete actionByRoot[ rootUuid ]; + const info = { + boundary: 0, // bytes + storage: 0 // bytes + }; - if ( knownActionsForClip.length === 0 ) { + // determine sizes according to STD140 - delete actionsByClip[ clipUuid ]; + if ( typeof value === 'number' || typeof value === 'boolean' ) { - } + // float/int/bool - this._removeInactiveBindingsForAction( action ); + info.boundary = 4; + info.storage = 4; - } + } else if ( value.isVector2 ) { - _removeInactiveBindingsForAction( action ) { + // vec2 - const bindings = action._propertyBindings; + info.boundary = 8; + info.storage = 8; - for ( let i = 0, n = bindings.length; i !== n; ++ i ) { + } else if ( value.isVector3 || value.isColor ) { - const binding = bindings[ i ]; + // vec3 - if ( -- binding.referenceCount === 0 ) { + info.boundary = 16; + info.storage = 12; // evil: vec3 must start on a 16-byte boundary but it only consumes 12 bytes - this._removeInactiveBinding( binding ); + } else if ( value.isVector4 ) { - } + // vec4 - } + info.boundary = 16; + info.storage = 16; - } + } else if ( value.isMatrix3 ) { - _lendAction( action ) { + // mat3 (in STD140 a 3x3 matrix is represented as 3x4) - // [ active actions | inactive actions ] - // [ active actions >| inactive actions ] - // s a - // <-swap-> - // a s + info.boundary = 48; + info.storage = 48; - const actions = this._actions, - prevIndex = action._cacheIndex, + } else if ( value.isMatrix4 ) { - lastActiveIndex = this._nActiveActions ++, + // mat4 - firstInactiveAction = actions[ lastActiveIndex ]; + info.boundary = 64; + info.storage = 64; - action._cacheIndex = lastActiveIndex; - actions[ lastActiveIndex ] = action; + } else if ( value.isTexture ) { - firstInactiveAction._cacheIndex = prevIndex; - actions[ prevIndex ] = firstInactiveAction; + console.warn( 'THREE.WebGLRenderer: Texture samplers can not be part of an uniforms group.' ); - } + } else { - _takeBackAction( action ) { + console.warn( 'THREE.WebGLRenderer: Unsupported uniform value type.', value ); - // [ active actions | inactive actions ] - // [ active actions |< inactive actions ] - // a s - // <-swap-> - // s a + } - const actions = this._actions, - prevIndex = action._cacheIndex, + return info; - firstInactiveIndex = -- this._nActiveActions, + } - lastActiveAction = actions[ firstInactiveIndex ]; + function onUniformsGroupsDispose( event ) { - action._cacheIndex = firstInactiveIndex; - actions[ firstInactiveIndex ] = action; + const uniformsGroup = event.target; - lastActiveAction._cacheIndex = prevIndex; - actions[ prevIndex ] = lastActiveAction; + uniformsGroup.removeEventListener( 'dispose', onUniformsGroupsDispose ); - } + const index = allocatedBindingPoints.indexOf( uniformsGroup.__bindingPointIndex ); + allocatedBindingPoints.splice( index, 1 ); - // Memory management for PropertyMixer objects + gl.deleteBuffer( buffers[ uniformsGroup.id ] ); - _addInactiveBinding( binding, rootUuid, trackName ) { + delete buffers[ uniformsGroup.id ]; + delete updateList[ uniformsGroup.id ]; - const bindingsByRoot = this._bindingsByRootAndName, - bindings = this._bindings; + } - let bindingByName = bindingsByRoot[ rootUuid ]; + function dispose() { - if ( bindingByName === undefined ) { + for ( const id in buffers ) { - bindingByName = {}; - bindingsByRoot[ rootUuid ] = bindingByName; + gl.deleteBuffer( buffers[ id ] ); } - bindingByName[ trackName ] = binding; - - binding._cacheIndex = bindings.length; - bindings.push( binding ); + allocatedBindingPoints = []; + buffers = {}; + updateList = {}; } - _removeInactiveBinding( binding ) { + return { - const bindings = this._bindings, - propBinding = binding.binding, - rootUuid = propBinding.rootNode.uuid, - trackName = propBinding.path, - bindingsByRoot = this._bindingsByRootAndName, - bindingByName = bindingsByRoot[ rootUuid ], + bind: bind, + update: update, - lastInactiveBinding = bindings[ bindings.length - 1 ], - cacheIndex = binding._cacheIndex; + dispose: dispose - lastInactiveBinding._cacheIndex = cacheIndex; - bindings[ cacheIndex ] = lastInactiveBinding; - bindings.pop(); + }; - delete bindingByName[ trackName ]; +} - if ( Object.keys( bindingByName ).length === 0 ) { +/** + * This renderer uses WebGL 2 to display scenes. + * + * WebGL 1 is not supported since `r163`. + */ +class WebGLRenderer { - delete bindingsByRoot[ rootUuid ]; + /** + * Constructs a new WebGL renderer. + * + * @param {WebGLRenderer~Options} [parameters] - The configuration parameter. + */ + constructor( parameters = {} ) { - } + const { + canvas = createCanvasElement(), + context = null, + depth = true, + stencil = false, + alpha = false, + antialias = false, + premultipliedAlpha = true, + preserveDrawingBuffer = false, + powerPreference = 'default', + failIfMajorPerformanceCaveat = false, + reverseDepthBuffer = false, + } = parameters; - } + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isWebGLRenderer = true; - _lendBinding( binding ) { + let _alpha; - const bindings = this._bindings, - prevIndex = binding._cacheIndex, + if ( context !== null ) { - lastActiveIndex = this._nActiveBindings ++, + if ( typeof WebGLRenderingContext !== 'undefined' && context instanceof WebGLRenderingContext ) { - firstInactiveBinding = bindings[ lastActiveIndex ]; + throw new Error( 'THREE.WebGLRenderer: WebGL 1 is not supported since r163.' ); - binding._cacheIndex = lastActiveIndex; - bindings[ lastActiveIndex ] = binding; + } - firstInactiveBinding._cacheIndex = prevIndex; - bindings[ prevIndex ] = firstInactiveBinding; + _alpha = context.getContextAttributes().alpha; - } + } else { - _takeBackBinding( binding ) { + _alpha = alpha; - const bindings = this._bindings, - prevIndex = binding._cacheIndex, + } - firstInactiveIndex = -- this._nActiveBindings, + const uintClearColor = new Uint32Array( 4 ); + const intClearColor = new Int32Array( 4 ); - lastActiveBinding = bindings[ firstInactiveIndex ]; + let currentRenderList = null; + let currentRenderState = null; - binding._cacheIndex = firstInactiveIndex; - bindings[ firstInactiveIndex ] = binding; + // render() can be called from within a callback triggered by another render. + // We track this so that the nested render call gets its list and state isolated from the parent render call. - lastActiveBinding._cacheIndex = prevIndex; - bindings[ prevIndex ] = lastActiveBinding; + const renderListStack = []; + const renderStateStack = []; - } + // public properties + /** + * A canvas where the renderer draws its output.This is automatically created by the renderer + * in the constructor (if not provided already); you just need to add it to your page like so: + * ```js + * document.body.appendChild( renderer.domElement ); + * ``` + * + * @type {DOMElement} + */ + this.domElement = canvas; - // Memory management of Interpolants for weight and time scale + /** + * A object with debug configuration settings. + * + * - `checkShaderErrors`: If it is `true`, defines whether material shader programs are + * checked for errors during compilation and linkage process. It may be useful to disable + * this check in production for performance gain. It is strongly recommended to keep these + * checks enabled during development. If the shader does not compile and link - it will not + * work and associated material will not render. + * - `onShaderError(gl, program, glVertexShader,glFragmentShader)`: A callback function that + * can be used for custom error reporting. The callback receives the WebGL context, an instance + * of WebGLProgram as well two instances of WebGLShader representing the vertex and fragment shader. + * Assigning a custom function disables the default error reporting. + * + * @type {Object} + */ + this.debug = { - _lendControlInterpolant() { + /** + * Enables error checking and reporting when shader programs are being compiled. + * @type {boolean} + */ + checkShaderErrors: true, + /** + * Callback for custom error reporting. + * @type {?Function} + */ + onShaderError: null + }; - const interpolants = this._controlInterpolants, - lastActiveIndex = this._nActiveControlInterpolants ++; + // clearing - let interpolant = interpolants[ lastActiveIndex ]; + /** + * Whether the renderer should automatically clear its output before rendering a frame or not. + * + * @type {boolean} + * @default true + */ + this.autoClear = true; - if ( interpolant === undefined ) { + /** + * If {@link WebGLRenderer#autoClear} set to `true`, whether the renderer should clear + * the color buffer or not. + * + * @type {boolean} + * @default true + */ + this.autoClearColor = true; - interpolant = new LinearInterpolant( - new Float32Array( 2 ), new Float32Array( 2 ), - 1, _controlInterpolantsResultBuffer ); + /** + * If {@link WebGLRenderer#autoClear} set to `true`, whether the renderer should clear + * the depth buffer or not. + * + * @type {boolean} + * @default true + */ + this.autoClearDepth = true; - interpolant.__cacheIndex = lastActiveIndex; - interpolants[ lastActiveIndex ] = interpolant; + /** + * If {@link WebGLRenderer#autoClear} set to `true`, whether the renderer should clear + * the stencil buffer or not. + * + * @type {boolean} + * @default true + */ + this.autoClearStencil = true; - } + // scene graph - return interpolant; + /** + * Whether the renderer should sort objects or not. + * + * Note: Sorting is used to attempt to properly render objects that have some + * degree of transparency. By definition, sorting objects may not work in all + * cases. Depending on the needs of application, it may be necessary to turn + * off sorting and use other methods to deal with transparency rendering e.g. + * manually determining each object's rendering order. + * + * @type {boolean} + * @default true + */ + this.sortObjects = true; - } + // user-defined clipping - _takeBackControlInterpolant( interpolant ) { + /** + * User-defined clipping planes specified in world space. These planes apply globally. + * Points in space whose dot product with the plane is negative are cut away. + * + * @type {Array} + */ + this.clippingPlanes = []; - const interpolants = this._controlInterpolants, - prevIndex = interpolant.__cacheIndex, + /** + * Whether the renderer respects object-level clipping planes or not. + * + * @type {boolean} + * @default false + */ + this.localClippingEnabled = false; - firstInactiveIndex = -- this._nActiveControlInterpolants, + // tone mapping - lastActiveInterpolant = interpolants[ firstInactiveIndex ]; + /** + * The tone mapping technique of the renderer. + * + * @type {(NoToneMapping|LinearToneMapping|ReinhardToneMapping|CineonToneMapping|ACESFilmicToneMapping|CustomToneMapping|AgXToneMapping|NeutralToneMapping)} + * @default NoToneMapping + */ + this.toneMapping = NoToneMapping; - interpolant.__cacheIndex = firstInactiveIndex; - interpolants[ firstInactiveIndex ] = interpolant; + /** + * Exposure level of tone mapping. + * + * @type {number} + * @default 1 + */ + this.toneMappingExposure = 1.0; - lastActiveInterpolant.__cacheIndex = prevIndex; - interpolants[ prevIndex ] = lastActiveInterpolant; + // transmission - } + /** + * The normalized resolution scale for the transmission render target, measured in percentage + * of viewport dimensions. Lowering this value can result in significant performance improvements + * when using {@link MeshPhysicalMaterial#transmission}. + * + * @type {number} + * @default 1 + */ + this.transmissionResolutionScale = 1.0; - // return an action for a clip optionally using a custom root target - // object (this method allocates a lot of dynamic memory in case a - // previously unknown clip/root combination is specified) - clipAction( clip, optionalRoot, blendMode ) { + // internal properties - const root = optionalRoot || this._root, - rootUuid = root.uuid; + const _this = this; - let clipObject = typeof clip === 'string' ? AnimationClip.findByName( root, clip ) : clip; + let _isContextLost = false; - const clipUuid = clipObject !== null ? clipObject.uuid : clip; + // internal state cache - const actionsForClip = this._actionsByClip[ clipUuid ]; - let prototypeAction = null; + this._outputColorSpace = SRGBColorSpace; - if ( blendMode === undefined ) { + let _currentActiveCubeFace = 0; + let _currentActiveMipmapLevel = 0; + let _currentRenderTarget = null; + let _currentMaterialId = -1; - if ( clipObject !== null ) { + let _currentCamera = null; - blendMode = clipObject.blendMode; + const _currentViewport = new Vector4(); + const _currentScissor = new Vector4(); + let _currentScissorTest = null; - } else { + const _currentClearColor = new Color( 0x000000 ); + let _currentClearAlpha = 0; - blendMode = NormalAnimationBlendMode; + // - } + let _width = canvas.width; + let _height = canvas.height; - } + let _pixelRatio = 1; + let _opaqueSort = null; + let _transparentSort = null; - if ( actionsForClip !== undefined ) { + const _viewport = new Vector4( 0, 0, _width, _height ); + const _scissor = new Vector4( 0, 0, _width, _height ); + let _scissorTest = false; - const existingAction = actionsForClip.actionByRoot[ rootUuid ]; + // frustum - if ( existingAction !== undefined && existingAction.blendMode === blendMode ) { + const _frustum = new Frustum(); - return existingAction; + // clipping - } + let _clippingEnabled = false; + let _localClippingEnabled = false; - // we know the clip, so we don't have to parse all - // the bindings again but can just copy - prototypeAction = actionsForClip.knownActions[ 0 ]; + // camera matrices cache - // also, take the clip from the prototype action - if ( clipObject === null ) - clipObject = prototypeAction._clip; + const _currentProjectionMatrix = new Matrix4(); + const _projScreenMatrix = new Matrix4(); - } + const _vector3 = new Vector3(); - // clip must be known when specified via string - if ( clipObject === null ) return null; + const _vector4 = new Vector4(); - // allocate all resources required to run it - const newAction = new AnimationAction( this, clipObject, optionalRoot, blendMode ); + const _emptyScene = { background: null, fog: null, environment: null, overrideMaterial: null, isScene: true }; - this._bindAction( newAction, prototypeAction ); + let _renderBackground = false; - // and make the action known to the memory manager - this._addInactiveAction( newAction, clipUuid, rootUuid ); + function getTargetPixelRatio() { - return newAction; + return _currentRenderTarget === null ? _pixelRatio : 1; - } + } - // get an existing action - existingAction( clip, optionalRoot ) { + // initialize - const root = optionalRoot || this._root, - rootUuid = root.uuid, + let _gl = context; - clipObject = typeof clip === 'string' ? - AnimationClip.findByName( root, clip ) : clip, + function getContext( contextName, contextAttributes ) { - clipUuid = clipObject ? clipObject.uuid : clip, + return canvas.getContext( contextName, contextAttributes ); - actionsForClip = this._actionsByClip[ clipUuid ]; + } - if ( actionsForClip !== undefined ) { + try { - return actionsForClip.actionByRoot[ rootUuid ] || null; + const contextAttributes = { + alpha: true, + depth, + stencil, + antialias, + premultipliedAlpha, + preserveDrawingBuffer, + powerPreference, + failIfMajorPerformanceCaveat, + }; - } + // OffscreenCanvas does not have setAttribute, see #22811 + if ( 'setAttribute' in canvas ) canvas.setAttribute( 'data-engine', `three.js r${REVISION}` ); - return null; + // event listeners must be registered before WebGL context is created, see #12753 + canvas.addEventListener( 'webglcontextlost', onContextLost, false ); + canvas.addEventListener( 'webglcontextrestored', onContextRestore, false ); + canvas.addEventListener( 'webglcontextcreationerror', onContextCreationError, false ); - } + if ( _gl === null ) { - // deactivates all previously scheduled actions - stopAllAction() { + const contextName = 'webgl2'; - const actions = this._actions, - nActions = this._nActiveActions; + _gl = getContext( contextName, contextAttributes ); - for ( let i = nActions - 1; i >= 0; -- i ) { + if ( _gl === null ) { - actions[ i ].stop(); + if ( getContext( contextName ) ) { - } + throw new Error( 'Error creating WebGL context with your selected attributes.' ); - return this; + } else { - } + throw new Error( 'Error creating WebGL context.' ); - // advance the time and update apply the animation - update( deltaTime ) { + } - deltaTime *= this.timeScale; + } - const actions = this._actions, - nActions = this._nActiveActions, + } - time = this.time += deltaTime, - timeDirection = Math.sign( deltaTime ), + } catch ( error ) { - accuIndex = this._accuIndex ^= 1; + console.error( 'THREE.WebGLRenderer: ' + error.message ); + throw error; - // run active actions + } - for ( let i = 0; i !== nActions; ++ i ) { + let extensions, capabilities, state, info; + let properties, textures, cubemaps, cubeuvmaps, attributes, geometries, objects; + let programCache, materials, renderLists, renderStates, clipping, shadowMap; - const action = actions[ i ]; + let background, morphtargets, bufferRenderer, indexedBufferRenderer; - action._update( time, deltaTime, timeDirection, accuIndex ); + let utils, bindingStates, uniformsGroups; - } + function initGLContext() { - // update scene graph + extensions = new WebGLExtensions( _gl ); + extensions.init(); - const bindings = this._bindings, - nBindings = this._nActiveBindings; + utils = new WebGLUtils( _gl, extensions ); - for ( let i = 0; i !== nBindings; ++ i ) { + capabilities = new WebGLCapabilities( _gl, extensions, parameters, utils ); - bindings[ i ].apply( accuIndex ); + state = new WebGLState( _gl, extensions ); - } + if ( capabilities.reverseDepthBuffer && reverseDepthBuffer ) { - return this; + state.buffers.depth.setReversed( true ); - } + } - // Allows you to seek to a specific time in an animation. - setTime( timeInSeconds ) { + info = new WebGLInfo( _gl ); + properties = new WebGLProperties(); + textures = new WebGLTextures( _gl, extensions, state, properties, capabilities, utils, info ); + cubemaps = new WebGLCubeMaps( _this ); + cubeuvmaps = new WebGLCubeUVMaps( _this ); + attributes = new WebGLAttributes( _gl ); + bindingStates = new WebGLBindingStates( _gl, attributes ); + geometries = new WebGLGeometries( _gl, attributes, info, bindingStates ); + objects = new WebGLObjects( _gl, geometries, attributes, info ); + morphtargets = new WebGLMorphtargets( _gl, capabilities, textures ); + clipping = new WebGLClipping( properties ); + programCache = new WebGLPrograms( _this, cubemaps, cubeuvmaps, extensions, capabilities, bindingStates, clipping ); + materials = new WebGLMaterials( _this, properties ); + renderLists = new WebGLRenderLists(); + renderStates = new WebGLRenderStates( extensions ); + background = new WebGLBackground( _this, cubemaps, cubeuvmaps, state, objects, _alpha, premultipliedAlpha ); + shadowMap = new WebGLShadowMap( _this, objects, capabilities ); + uniformsGroups = new WebGLUniformsGroups( _gl, info, capabilities, state ); - this.time = 0; // Zero out time attribute for AnimationMixer object; - for ( let i = 0; i < this._actions.length; i ++ ) { + bufferRenderer = new WebGLBufferRenderer( _gl, extensions, info ); + indexedBufferRenderer = new WebGLIndexedBufferRenderer( _gl, extensions, info ); - this._actions[ i ].time = 0; // Zero out time attribute for all associated AnimationAction objects. + info.programs = programCache.programs; - } + /** + * Holds details about the capabilities of the current rendering context. + * + * @name WebGLRenderer#capabilities + * @type {WebGLRenderer~Capabilities} + */ + _this.capabilities = capabilities; - return this.update( timeInSeconds ); // Update used to set exact time. Returns "this" AnimationMixer object. + /** + * Provides methods for retrieving and testing WebGL extensions. + * + * - `get(extensionName:string)`: Used to check whether a WebGL extension is supported + * and return the extension object if available. + * - `has(extensionName:string)`: returns `true` if the extension is supported. + * + * @name WebGLRenderer#extensions + * @type {Object} + */ + _this.extensions = extensions; - } + /** + * Used to track properties of other objects like native WebGL objects. + * + * @name WebGLRenderer#properties + * @type {Object} + */ + _this.properties = properties; - // return this mixer's root target object - getRoot() { + /** + * Manages the render lists of the renderer. + * + * @name WebGLRenderer#renderLists + * @type {Object} + */ + _this.renderLists = renderLists; - return this._root; - } - // free all resources specific to a particular clip - uncacheClip( clip ) { + /** + * Interface for managing shadows. + * + * @name WebGLRenderer#shadowMap + * @type {WebGLRenderer~ShadowMap} + */ + _this.shadowMap = shadowMap; - const actions = this._actions, - clipUuid = clip.uuid, - actionsByClip = this._actionsByClip, - actionsForClip = actionsByClip[ clipUuid ]; + /** + * Interface for managing the WebGL state. + * + * @name WebGLRenderer#state + * @type {Object} + */ + _this.state = state; - if ( actionsForClip !== undefined ) { + /** + * Holds a series of statistical information about the GPU memory + * and the rendering process. Useful for debugging and monitoring. + * + * By default these data are reset at each render call but when having + * multiple render passes per frame (e.g. when using post processing) it can + * be preferred to reset with a custom pattern. First, set `autoReset` to + * `false`. + * ```js + * renderer.info.autoReset = false; + * ``` + * Call `reset()` whenever you have finished to render a single frame. + * ```js + * renderer.info.reset(); + * ``` + * + * @name WebGLRenderer#info + * @type {WebGLRenderer~Info} + */ + _this.info = info; - // note: just calling _removeInactiveAction would mess up the - // iteration state and also require updating the state we can - // just throw away + } - const actionsToRemove = actionsForClip.knownActions; + initGLContext(); - for ( let i = 0, n = actionsToRemove.length; i !== n; ++ i ) { + // xr - const action = actionsToRemove[ i ]; + const xr = new WebXRManager( _this, _gl ); - this._deactivateAction( action ); + /** + * A reference to the XR manager. + * + * @type {WebXRManager} + */ + this.xr = xr; - const cacheIndex = action._cacheIndex, - lastInactiveAction = actions[ actions.length - 1 ]; + /** + * Returns the rendering context. + * + * @return {WebGL2RenderingContext} The rendering context. + */ + this.getContext = function () { - action._cacheIndex = null; - action._byClipCacheIndex = null; + return _gl; - lastInactiveAction._cacheIndex = cacheIndex; - actions[ cacheIndex ] = lastInactiveAction; - actions.pop(); + }; - this._removeInactiveBindingsForAction( action ); + /** + * Returns the rendering context attributes. + * + * @return {WebGLContextAttributes} The rendering context attributes. + */ + this.getContextAttributes = function () { - } + return _gl.getContextAttributes(); - delete actionsByClip[ clipUuid ]; + }; - } + /** + * Simulates a loss of the WebGL context. This requires support for the `WEBGL_lose_context` extension. + */ + this.forceContextLoss = function () { - } + const extension = extensions.get( 'WEBGL_lose_context' ); + if ( extension ) extension.loseContext(); - // free all resources specific to a particular root target object - uncacheRoot( root ) { + }; - const rootUuid = root.uuid, - actionsByClip = this._actionsByClip; + /** + * Simulates a restore of the WebGL context. This requires support for the `WEBGL_lose_context` extension. + */ + this.forceContextRestore = function () { - for ( const clipUuid in actionsByClip ) { + const extension = extensions.get( 'WEBGL_lose_context' ); + if ( extension ) extension.restoreContext(); - const actionByRoot = actionsByClip[ clipUuid ].actionByRoot, - action = actionByRoot[ rootUuid ]; + }; - if ( action !== undefined ) { + /** + * Returns the pixel ratio. + * + * @return {number} The pixel ratio. + */ + this.getPixelRatio = function () { - this._deactivateAction( action ); - this._removeInactiveAction( action ); + return _pixelRatio; - } + }; - } + /** + * Sets the given pixel ratio and resizes the canvas if necessary. + * + * @param {number} value - The pixel ratio. + */ + this.setPixelRatio = function ( value ) { - const bindingsByRoot = this._bindingsByRootAndName, - bindingByName = bindingsByRoot[ rootUuid ]; + if ( value === undefined ) return; - if ( bindingByName !== undefined ) { + _pixelRatio = value; - for ( const trackName in bindingByName ) { + this.setSize( _width, _height, false ); - const binding = bindingByName[ trackName ]; - binding.restoreOriginalState(); - this._removeInactiveBinding( binding ); + }; - } + /** + * Returns the renderer's size in logical pixels. This method does not honor the pixel ratio. + * + * @param {Vector2} target - The method writes the result in this target object. + * @return {Vector2} The renderer's size in logical pixels. + */ + this.getSize = function ( target ) { - } + return target.set( _width, _height ); - } + }; - // remove a targeted clip from the cache - uncacheAction( clip, optionalRoot ) { + /** + * Resizes the output canvas to (width, height) with device pixel ratio taken + * into account, and also sets the viewport to fit that size, starting in (0, + * 0). Setting `updateStyle` to false prevents any style changes to the output canvas. + * + * @param {number} width - The width in logical pixels. + * @param {number} height - The height in logical pixels. + * @param {boolean} [updateStyle=true] - Whether to update the `style` attribute of the canvas or not. + */ + this.setSize = function ( width, height, updateStyle = true ) { - const action = this.existingAction( clip, optionalRoot ); + if ( xr.isPresenting ) { - if ( action !== null ) { + console.warn( 'THREE.WebGLRenderer: Can\'t change size while VR device is presenting.' ); + return; - this._deactivateAction( action ); - this._removeInactiveAction( action ); + } - } + _width = width; + _height = height; - } + canvas.width = Math.floor( width * _pixelRatio ); + canvas.height = Math.floor( height * _pixelRatio ); -} + if ( updateStyle === true ) { -class Uniform { + canvas.style.width = width + 'px'; + canvas.style.height = height + 'px'; - constructor( value ) { + } - this.value = value; + this.setViewport( 0, 0, width, height ); - } + }; - clone() { + /** + * Returns the drawing buffer size in physical pixels. This method honors the pixel ratio. + * + * @param {Vector2} target - The method writes the result in this target object. + * @return {Vector2} The drawing buffer size. + */ + this.getDrawingBufferSize = function ( target ) { - return new Uniform( this.value.clone === undefined ? this.value : this.value.clone() ); + return target.set( _width * _pixelRatio, _height * _pixelRatio ).floor(); - } + }; -} + /** + * This method allows to define the drawing buffer size by specifying + * width, height and pixel ratio all at once. The size of the drawing + * buffer is computed with this formula: + * ```js + * size.x = width * pixelRatio; + * size.y = height * pixelRatio; + * ``` + * + * @param {number} width - The width in logical pixels. + * @param {number} height - The height in logical pixels. + * @param {number} pixelRatio - The pixel ratio. + */ + this.setDrawingBufferSize = function ( width, height, pixelRatio ) { -let id = 0; + _width = width; + _height = height; -class UniformsGroup extends EventDispatcher { + _pixelRatio = pixelRatio; - constructor() { + canvas.width = Math.floor( width * pixelRatio ); + canvas.height = Math.floor( height * pixelRatio ); - super(); + this.setViewport( 0, 0, width, height ); - this.isUniformsGroup = true; + }; - Object.defineProperty( this, 'id', { value: id ++ } ); + /** + * Returns the current viewport definition. + * + * @param {Vector2} target - The method writes the result in this target object. + * @return {Vector2} The current viewport definition. + */ + this.getCurrentViewport = function ( target ) { - this.name = ''; + return target.copy( _currentViewport ); - this.usage = StaticDrawUsage; - this.uniforms = []; + }; - } + /** + * Returns the viewport definition. + * + * @param {Vector4} target - The method writes the result in this target object. + * @return {Vector4} The viewport definition. + */ + this.getViewport = function ( target ) { - add( uniform ) { + return target.copy( _viewport ); - this.uniforms.push( uniform ); + }; - return this; + /** + * Sets the viewport to render from `(x, y)` to `(x + width, y + height)`. + * + * @param {number | Vector4} x - The horizontal coordinate for the lower left corner of the viewport origin in logical pixel unit. + * Or alternatively a four-component vector specifying all the parameters of the viewport. + * @param {number} y - The vertical coordinate for the lower left corner of the viewport origin in logical pixel unit. + * @param {number} width - The width of the viewport in logical pixel unit. + * @param {number} height - The height of the viewport in logical pixel unit. + */ + this.setViewport = function ( x, y, width, height ) { - } + if ( x.isVector4 ) { - remove( uniform ) { + _viewport.set( x.x, x.y, x.z, x.w ); - const index = this.uniforms.indexOf( uniform ); + } else { - if ( index !== - 1 ) this.uniforms.splice( index, 1 ); + _viewport.set( x, y, width, height ); - return this; + } - } + state.viewport( _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ).round() ); - setName( name ) { + }; - this.name = name; + /** + * Returns the scissor region. + * + * @param {Vector4} target - The method writes the result in this target object. + * @return {Vector4} The scissor region. + */ + this.getScissor = function ( target ) { - return this; + return target.copy( _scissor ); - } + }; - setUsage( value ) { + /** + * Sets the scissor region to render from `(x, y)` to `(x + width, y + height)`. + * + * @param {number | Vector4} x - The horizontal coordinate for the lower left corner of the scissor region origin in logical pixel unit. + * Or alternatively a four-component vector specifying all the parameters of the scissor region. + * @param {number} y - The vertical coordinate for the lower left corner of the scissor region origin in logical pixel unit. + * @param {number} width - The width of the scissor region in logical pixel unit. + * @param {number} height - The height of the scissor region in logical pixel unit. + */ + this.setScissor = function ( x, y, width, height ) { - this.usage = value; + if ( x.isVector4 ) { - return this; + _scissor.set( x.x, x.y, x.z, x.w ); - } + } else { - dispose() { + _scissor.set( x, y, width, height ); - this.dispatchEvent( { type: 'dispose' } ); + } - return this; + state.scissor( _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ).round() ); - } + }; - copy( source ) { + /** + * Returns `true` if the scissor test is enabled. + * + * @return {boolean} Whether the scissor test is enabled or not. + */ + this.getScissorTest = function () { - this.name = source.name; - this.usage = source.usage; + return _scissorTest; - const uniformsSource = source.uniforms; + }; - this.uniforms.length = 0; + /** + * Enable or disable the scissor test. When this is enabled, only the pixels + * within the defined scissor area will be affected by further renderer + * actions. + * + * @param {boolean} boolean - Whether the scissor test is enabled or not. + */ + this.setScissorTest = function ( boolean ) { - for ( let i = 0, l = uniformsSource.length; i < l; i ++ ) { + state.setScissorTest( _scissorTest = boolean ); - this.uniforms.push( uniformsSource[ i ].clone() ); + }; - } + /** + * Sets a custom opaque sort function for the render lists. Pass `null` + * to use the default `painterSortStable` function. + * + * @param {?Function} method - The opaque sort function. + */ + this.setOpaqueSort = function ( method ) { - return this; + _opaqueSort = method; - } + }; - clone() { + /** + * Sets a custom transparent sort function for the render lists. Pass `null` + * to use the default `reversePainterSortStable` function. + * + * @param {?Function} method - The opaque sort function. + */ + this.setTransparentSort = function ( method ) { - return new this.constructor().copy( this ); + _transparentSort = method; - } + }; -} + // Clearing -class InstancedInterleavedBuffer extends InterleavedBuffer { + /** + * Returns the clear color. + * + * @param {Color} target - The method writes the result in this target object. + * @return {Color} The clear color. + */ + this.getClearColor = function ( target ) { - constructor( array, stride, meshPerAttribute = 1 ) { + return target.copy( background.getClearColor() ); - super( array, stride ); + }; - this.isInstancedInterleavedBuffer = true; + /** + * Sets the clear color and alpha. + * + * @param {Color} color - The clear color. + * @param {number} [alpha=1] - The clear alpha. + */ + this.setClearColor = function () { - this.meshPerAttribute = meshPerAttribute; + background.setClearColor( ...arguments ); - } + }; - copy( source ) { + /** + * Returns the clear alpha. Ranges within `[0,1]`. + * + * @return {number} The clear alpha. + */ + this.getClearAlpha = function () { - super.copy( source ); + return background.getClearAlpha(); - this.meshPerAttribute = source.meshPerAttribute; + }; - return this; + /** + * Sets the clear alpha. + * + * @param {number} alpha - The clear alpha. + */ + this.setClearAlpha = function () { - } + background.setClearAlpha( ...arguments ); - clone( data ) { + }; - const ib = super.clone( data ); + /** + * Tells the renderer to clear its color, depth or stencil drawing buffer(s). + * This method initializes the buffers to the current clear color values. + * + * @param {boolean} [color=true] - Whether the color buffer should be cleared or not. + * @param {boolean} [depth=true] - Whether the depth buffer should be cleared or not. + * @param {boolean} [stencil=true] - Whether the stencil buffer should be cleared or not. + */ + this.clear = function ( color = true, depth = true, stencil = true ) { - ib.meshPerAttribute = this.meshPerAttribute; + let bits = 0; - return ib; + if ( color ) { - } + // check if we're trying to clear an integer target + let isIntegerFormat = false; + if ( _currentRenderTarget !== null ) { - toJSON( data ) { + const targetFormat = _currentRenderTarget.texture.format; + isIntegerFormat = targetFormat === RGBAIntegerFormat || + targetFormat === RGIntegerFormat || + targetFormat === RedIntegerFormat; - const json = super.toJSON( data ); + } - json.isInstancedInterleavedBuffer = true; - json.meshPerAttribute = this.meshPerAttribute; + // use the appropriate clear functions to clear the target if it's a signed + // or unsigned integer target + if ( isIntegerFormat ) { - return json; + const targetType = _currentRenderTarget.texture.type; + const isUnsignedType = targetType === UnsignedByteType || + targetType === UnsignedIntType || + targetType === UnsignedShortType || + targetType === UnsignedInt248Type || + targetType === UnsignedShort4444Type || + targetType === UnsignedShort5551Type; - } + const clearColor = background.getClearColor(); + const a = background.getClearAlpha(); + const r = clearColor.r; + const g = clearColor.g; + const b = clearColor.b; -} + if ( isUnsignedType ) { -class GLBufferAttribute { + uintClearColor[ 0 ] = r; + uintClearColor[ 1 ] = g; + uintClearColor[ 2 ] = b; + uintClearColor[ 3 ] = a; + _gl.clearBufferuiv( _gl.COLOR, 0, uintClearColor ); - constructor( buffer, type, itemSize, elementSize, count ) { + } else { - this.isGLBufferAttribute = true; + intClearColor[ 0 ] = r; + intClearColor[ 1 ] = g; + intClearColor[ 2 ] = b; + intClearColor[ 3 ] = a; + _gl.clearBufferiv( _gl.COLOR, 0, intClearColor ); - this.name = ''; + } - this.buffer = buffer; - this.type = type; - this.itemSize = itemSize; - this.elementSize = elementSize; - this.count = count; + } else { - this.version = 0; + bits |= _gl.COLOR_BUFFER_BIT; - } + } - set needsUpdate( value ) { + } - if ( value === true ) this.version ++; + if ( depth ) { - } + bits |= _gl.DEPTH_BUFFER_BIT; - setBuffer( buffer ) { + } - this.buffer = buffer; + if ( stencil ) { - return this; + bits |= _gl.STENCIL_BUFFER_BIT; + this.state.buffers.stencil.setMask( 0xffffffff ); - } + } - setType( type, elementSize ) { + _gl.clear( bits ); - this.type = type; - this.elementSize = elementSize; + }; - return this; + /** + * Clears the color buffer. Equivalent to calling `renderer.clear( true, false, false )`. + */ + this.clearColor = function () { - } + this.clear( true, false, false ); - setItemSize( itemSize ) { + }; - this.itemSize = itemSize; + /** + * Clears the depth buffer. Equivalent to calling `renderer.clear( false, true, false )`. + */ + this.clearDepth = function () { - return this; + this.clear( false, true, false ); - } + }; - setCount( count ) { + /** + * Clears the stencil buffer. Equivalent to calling `renderer.clear( false, false, true )`. + */ + this.clearStencil = function () { - this.count = count; + this.clear( false, false, true ); - return this; + }; - } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + this.dispose = function () { -} + canvas.removeEventListener( 'webglcontextlost', onContextLost, false ); + canvas.removeEventListener( 'webglcontextrestored', onContextRestore, false ); + canvas.removeEventListener( 'webglcontextcreationerror', onContextCreationError, false ); -class Raycaster { + background.dispose(); + renderLists.dispose(); + renderStates.dispose(); + properties.dispose(); + cubemaps.dispose(); + cubeuvmaps.dispose(); + objects.dispose(); + bindingStates.dispose(); + uniformsGroups.dispose(); + programCache.dispose(); - constructor( origin, direction, near = 0, far = Infinity ) { + xr.dispose(); - this.ray = new Ray( origin, direction ); - // direction is assumed to be normalized (for accurate distance calculations) + xr.removeEventListener( 'sessionstart', onXRSessionStart ); + xr.removeEventListener( 'sessionend', onXRSessionEnd ); - this.near = near; - this.far = far; - this.camera = null; - this.layers = new Layers(); + animation.stop(); - this.params = { - Mesh: {}, - Line: { threshold: 1 }, - LOD: {}, - Points: { threshold: 1 }, - Sprite: {} }; - } + // Events - set( origin, direction ) { + function onContextLost( event ) { - // direction is assumed to be normalized (for accurate distance calculations) + event.preventDefault(); - this.ray.set( origin, direction ); + console.log( 'THREE.WebGLRenderer: Context Lost.' ); - } + _isContextLost = true; - setFromCamera( coords, camera ) { + } - if ( camera.isPerspectiveCamera ) { + function onContextRestore( /* event */ ) { - this.ray.origin.setFromMatrixPosition( camera.matrixWorld ); - this.ray.direction.set( coords.x, coords.y, 0.5 ).unproject( camera ).sub( this.ray.origin ).normalize(); - this.camera = camera; + console.log( 'THREE.WebGLRenderer: Context Restored.' ); - } else if ( camera.isOrthographicCamera ) { + _isContextLost = false; - this.ray.origin.set( coords.x, coords.y, ( camera.near + camera.far ) / ( camera.near - camera.far ) ).unproject( camera ); // set origin in plane of camera - this.ray.direction.set( 0, 0, - 1 ).transformDirection( camera.matrixWorld ); - this.camera = camera; + const infoAutoReset = info.autoReset; + const shadowMapEnabled = shadowMap.enabled; + const shadowMapAutoUpdate = shadowMap.autoUpdate; + const shadowMapNeedsUpdate = shadowMap.needsUpdate; + const shadowMapType = shadowMap.type; - } else { + initGLContext(); - console.error( 'THREE.Raycaster: Unsupported camera type: ' + camera.type ); + info.autoReset = infoAutoReset; + shadowMap.enabled = shadowMapEnabled; + shadowMap.autoUpdate = shadowMapAutoUpdate; + shadowMap.needsUpdate = shadowMapNeedsUpdate; + shadowMap.type = shadowMapType; } - } - - intersectObject( object, recursive = true, intersects = [] ) { - - intersectObject( object, this, intersects, recursive ); + function onContextCreationError( event ) { - intersects.sort( ascSort ); + console.error( 'THREE.WebGLRenderer: A WebGL context could not be created. Reason: ', event.statusMessage ); - return intersects; + } - } + function onMaterialDispose( event ) { - intersectObjects( objects, recursive = true, intersects = [] ) { + const material = event.target; - for ( let i = 0, l = objects.length; i < l; i ++ ) { + material.removeEventListener( 'dispose', onMaterialDispose ); - intersectObject( objects[ i ], this, intersects, recursive ); + deallocateMaterial( material ); } - intersects.sort( ascSort ); - - return intersects; - - } - -} + // Buffer deallocation -function ascSort( a, b ) { + function deallocateMaterial( material ) { - return a.distance - b.distance; + releaseMaterialProgramReferences( material ); -} + properties.remove( material ); -function intersectObject( object, raycaster, intersects, recursive ) { + } - if ( object.layers.test( raycaster.layers ) ) { - object.raycast( raycaster, intersects ); + function releaseMaterialProgramReferences( material ) { - } + const programs = properties.get( material ).programs; - if ( recursive === true ) { + if ( programs !== undefined ) { - const children = object.children; + programs.forEach( function ( program ) { - for ( let i = 0, l = children.length; i < l; i ++ ) { + programCache.releaseProgram( program ); - intersectObject( children[ i ], raycaster, intersects, true ); + } ); - } + if ( material.isShaderMaterial ) { - } + programCache.releaseShaderCache( material ); -} + } -/** - * Ref: https://en.wikipedia.org/wiki/Spherical_coordinate_system - * - * The polar angle (phi) is measured from the positive y-axis. The positive y-axis is up. - * The azimuthal angle (theta) is measured from the positive z-axis. - */ + } + } -class Spherical { + // Buffer rendering - constructor( radius = 1, phi = 0, theta = 0 ) { + this.renderBufferDirect = function ( camera, scene, geometry, material, object, group ) { - this.radius = radius; - this.phi = phi; // polar angle - this.theta = theta; // azimuthal angle + if ( scene === null ) scene = _emptyScene; // renderBufferDirect second parameter used to be fog (could be null) - return this; + const frontFaceCW = ( object.isMesh && object.matrixWorld.determinant() < 0 ); - } + const program = setProgram( camera, scene, geometry, material, object ); - set( radius, phi, theta ) { + state.setMaterial( material, frontFaceCW ); - this.radius = radius; - this.phi = phi; - this.theta = theta; + // - return this; + let index = geometry.index; + let rangeFactor = 1; - } + if ( material.wireframe === true ) { - copy( other ) { + index = geometries.getWireframeAttribute( geometry ); - this.radius = other.radius; - this.phi = other.phi; - this.theta = other.theta; + if ( index === undefined ) return; - return this; + rangeFactor = 2; - } + } - // restrict phi to be between EPS and PI-EPS - makeSafe() { + // - const EPS = 0.000001; - this.phi = Math.max( EPS, Math.min( Math.PI - EPS, this.phi ) ); + const drawRange = geometry.drawRange; + const position = geometry.attributes.position; - return this; + let drawStart = drawRange.start * rangeFactor; + let drawEnd = ( drawRange.start + drawRange.count ) * rangeFactor; - } + if ( group !== null ) { - setFromVector3( v ) { + drawStart = Math.max( drawStart, group.start * rangeFactor ); + drawEnd = Math.min( drawEnd, ( group.start + group.count ) * rangeFactor ); - return this.setFromCartesianCoords( v.x, v.y, v.z ); + } - } + if ( index !== null ) { - setFromCartesianCoords( x, y, z ) { + drawStart = Math.max( drawStart, 0 ); + drawEnd = Math.min( drawEnd, index.count ); - this.radius = Math.sqrt( x * x + y * y + z * z ); + } else if ( position !== undefined && position !== null ) { - if ( this.radius === 0 ) { + drawStart = Math.max( drawStart, 0 ); + drawEnd = Math.min( drawEnd, position.count ); - this.theta = 0; - this.phi = 0; + } - } else { + const drawCount = drawEnd - drawStart; - this.theta = Math.atan2( x, z ); - this.phi = Math.acos( clamp( y / this.radius, - 1, 1 ) ); + if ( drawCount < 0 || drawCount === Infinity ) return; - } + // - return this; + bindingStates.setup( object, material, program, geometry, index ); - } + let attribute; + let renderer = bufferRenderer; - clone() { + if ( index !== null ) { - return new this.constructor().copy( this ); + attribute = attributes.get( index ); - } + renderer = indexedBufferRenderer; + renderer.setIndex( attribute ); -} + } -/** - * Ref: https://en.wikipedia.org/wiki/Cylindrical_coordinate_system - */ + // -class Cylindrical { + if ( object.isMesh ) { - constructor( radius = 1, theta = 0, y = 0 ) { + if ( material.wireframe === true ) { - this.radius = radius; // distance from the origin to a point in the x-z plane - this.theta = theta; // counterclockwise angle in the x-z plane measured in radians from the positive z-axis - this.y = y; // height above the x-z plane + state.setLineWidth( material.wireframeLinewidth * getTargetPixelRatio() ); + renderer.setMode( _gl.LINES ); - return this; + } else { - } + renderer.setMode( _gl.TRIANGLES ); - set( radius, theta, y ) { + } - this.radius = radius; - this.theta = theta; - this.y = y; + } else if ( object.isLine ) { - return this; + let lineWidth = material.linewidth; - } + if ( lineWidth === undefined ) lineWidth = 1; // Not using Line*Material - copy( other ) { + state.setLineWidth( lineWidth * getTargetPixelRatio() ); - this.radius = other.radius; - this.theta = other.theta; - this.y = other.y; + if ( object.isLineSegments ) { - return this; + renderer.setMode( _gl.LINES ); - } + } else if ( object.isLineLoop ) { - setFromVector3( v ) { + renderer.setMode( _gl.LINE_LOOP ); - return this.setFromCartesianCoords( v.x, v.y, v.z ); + } else { - } + renderer.setMode( _gl.LINE_STRIP ); - setFromCartesianCoords( x, y, z ) { + } - this.radius = Math.sqrt( x * x + z * z ); - this.theta = Math.atan2( x, z ); - this.y = y; + } else if ( object.isPoints ) { - return this; + renderer.setMode( _gl.POINTS ); - } + } else if ( object.isSprite ) { - clone() { + renderer.setMode( _gl.TRIANGLES ); - return new this.constructor().copy( this ); + } - } + if ( object.isBatchedMesh ) { -} + if ( object._multiDrawInstances !== null ) { -const _vector$4 = /*@__PURE__*/ new Vector2(); + // @deprecated, r174 + warnOnce( 'THREE.WebGLRenderer: renderMultiDrawInstances has been deprecated and will be removed in r184. Append to renderMultiDraw arguments and use indirection.' ); + renderer.renderMultiDrawInstances( object._multiDrawStarts, object._multiDrawCounts, object._multiDrawCount, object._multiDrawInstances ); -class Box2 { + } else { - constructor( min = new Vector2( + Infinity, + Infinity ), max = new Vector2( - Infinity, - Infinity ) ) { + if ( ! extensions.get( 'WEBGL_multi_draw' ) ) { - this.isBox2 = true; + const starts = object._multiDrawStarts; + const counts = object._multiDrawCounts; + const drawCount = object._multiDrawCount; + const bytesPerElement = index ? attributes.get( index ).bytesPerElement : 1; + const uniforms = properties.get( material ).currentProgram.getUniforms(); + for ( let i = 0; i < drawCount; i ++ ) { - this.min = min; - this.max = max; + uniforms.setValue( _gl, '_gl_DrawID', i ); + renderer.render( starts[ i ] / bytesPerElement, counts[ i ] ); - } + } - set( min, max ) { + } else { - this.min.copy( min ); - this.max.copy( max ); + renderer.renderMultiDraw( object._multiDrawStarts, object._multiDrawCounts, object._multiDrawCount ); - return this; + } - } + } - setFromPoints( points ) { + } else if ( object.isInstancedMesh ) { - this.makeEmpty(); + renderer.renderInstances( drawStart, drawCount, object.count ); - for ( let i = 0, il = points.length; i < il; i ++ ) { + } else if ( geometry.isInstancedBufferGeometry ) { - this.expandByPoint( points[ i ] ); + const maxInstanceCount = geometry._maxInstanceCount !== undefined ? geometry._maxInstanceCount : Infinity; + const instanceCount = Math.min( geometry.instanceCount, maxInstanceCount ); - } + renderer.renderInstances( drawStart, drawCount, instanceCount ); - return this; + } else { - } + renderer.render( drawStart, drawCount ); - setFromCenterAndSize( center, size ) { + } - const halfSize = _vector$4.copy( size ).multiplyScalar( 0.5 ); - this.min.copy( center ).sub( halfSize ); - this.max.copy( center ).add( halfSize ); + }; - return this; + // Compile - } + function prepareMaterial( material, scene, object ) { - clone() { + if ( material.transparent === true && material.side === DoubleSide && material.forceSinglePass === false ) { - return new this.constructor().copy( this ); + material.side = BackSide; + material.needsUpdate = true; + getProgram( material, scene, object ); - } + material.side = FrontSide; + material.needsUpdate = true; + getProgram( material, scene, object ); - copy( box ) { + material.side = DoubleSide; - this.min.copy( box.min ); - this.max.copy( box.max ); + } else { - return this; + getProgram( material, scene, object ); - } + } - makeEmpty() { + } - this.min.x = this.min.y = + Infinity; - this.max.x = this.max.y = - Infinity; + /** + * Compiles all materials in the scene with the camera. This is useful to precompile shaders + * before the first rendering. If you want to add a 3D object to an existing scene, use the third + * optional parameter for applying the target scene. + * + * Note that the (target) scene's lighting and environment must be configured before calling this method. + * + * @param {Object3D} scene - The scene or another type of 3D object to precompile. + * @param {Camera} camera - The camera. + * @param {?Scene} [targetScene=null] - The target scene. + * @return {Set} The precompiled materials. + */ + this.compile = function ( scene, camera, targetScene = null ) { - return this; + if ( targetScene === null ) targetScene = scene; - } + currentRenderState = renderStates.get( targetScene ); + currentRenderState.init( camera ); - isEmpty() { + renderStateStack.push( currentRenderState ); - // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes + // gather lights from both the target scene and the new object that will be added to the scene. - return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y ); + targetScene.traverseVisible( function ( object ) { - } + if ( object.isLight && object.layers.test( camera.layers ) ) { - getCenter( target ) { + currentRenderState.pushLight( object ); - return this.isEmpty() ? target.set( 0, 0 ) : target.addVectors( this.min, this.max ).multiplyScalar( 0.5 ); + if ( object.castShadow ) { - } + currentRenderState.pushShadow( object ); - getSize( target ) { + } - return this.isEmpty() ? target.set( 0, 0 ) : target.subVectors( this.max, this.min ); + } - } + } ); - expandByPoint( point ) { + if ( scene !== targetScene ) { - this.min.min( point ); - this.max.max( point ); + scene.traverseVisible( function ( object ) { - return this; + if ( object.isLight && object.layers.test( camera.layers ) ) { - } + currentRenderState.pushLight( object ); - expandByVector( vector ) { + if ( object.castShadow ) { - this.min.sub( vector ); - this.max.add( vector ); + currentRenderState.pushShadow( object ); - return this; + } - } + } - expandByScalar( scalar ) { + } ); - this.min.addScalar( - scalar ); - this.max.addScalar( scalar ); + } - return this; + currentRenderState.setupLights(); - } + // Only initialize materials in the new scene, not the targetScene. - containsPoint( point ) { + const materials = new Set(); - return point.x < this.min.x || point.x > this.max.x || - point.y < this.min.y || point.y > this.max.y ? false : true; + scene.traverse( function ( object ) { - } + if ( ! ( object.isMesh || object.isPoints || object.isLine || object.isSprite ) ) { - containsBox( box ) { + return; - return this.min.x <= box.min.x && box.max.x <= this.max.x && - this.min.y <= box.min.y && box.max.y <= this.max.y; + } - } + const material = object.material; - getParameter( point, target ) { + if ( material ) { - // This can potentially have a divide by zero if the box - // has a size dimension of 0. + if ( Array.isArray( material ) ) { - return target.set( - ( point.x - this.min.x ) / ( this.max.x - this.min.x ), - ( point.y - this.min.y ) / ( this.max.y - this.min.y ) - ); + for ( let i = 0; i < material.length; i ++ ) { - } + const material2 = material[ i ]; - intersectsBox( box ) { + prepareMaterial( material2, targetScene, object ); + materials.add( material2 ); - // using 4 splitting planes to rule out intersections + } - return box.max.x < this.min.x || box.min.x > this.max.x || - box.max.y < this.min.y || box.min.y > this.max.y ? false : true; + } else { - } + prepareMaterial( material, targetScene, object ); + materials.add( material ); - clampPoint( point, target ) { + } - return target.copy( point ).clamp( this.min, this.max ); + } - } + } ); - distanceToPoint( point ) { + currentRenderState = renderStateStack.pop(); - return this.clampPoint( point, _vector$4 ).distanceTo( point ); + return materials; - } + }; - intersect( box ) { + // compileAsync - this.min.max( box.min ); - this.max.min( box.max ); + /** + * Asynchronous version of {@link WebGLRenderer#compile}. + * + * This method makes use of the `KHR_parallel_shader_compile` WebGL extension. Hence, + * it is recommended to use this version of `compile()` whenever possible. + * + * @async + * @param {Object3D} scene - The scene or another type of 3D object to precompile. + * @param {Camera} camera - The camera. + * @param {?Scene} [targetScene=null] - The target scene. + * @return {Promise} A Promise that resolves when the given scene can be rendered without unnecessary stalling due to shader compilation. + */ + this.compileAsync = function ( scene, camera, targetScene = null ) { - if ( this.isEmpty() ) this.makeEmpty(); + const materials = this.compile( scene, camera, targetScene ); - return this; + // Wait for all the materials in the new object to indicate that they're + // ready to be used before resolving the promise. - } + return new Promise( ( resolve ) => { - union( box ) { + function checkMaterialsReady() { - this.min.min( box.min ); - this.max.max( box.max ); + materials.forEach( function ( material ) { - return this; + const materialProperties = properties.get( material ); + const program = materialProperties.currentProgram; - } + if ( program.isReady() ) { - translate( offset ) { + // remove any programs that report they're ready to use from the list + materials.delete( material ); - this.min.add( offset ); - this.max.add( offset ); + } - return this; + } ); - } + // once the list of compiling materials is empty, call the callback - equals( box ) { + if ( materials.size === 0 ) { - return box.min.equals( this.min ) && box.max.equals( this.max ); + resolve( scene ); + return; - } + } -} + // if some materials are still not ready, wait a bit and check again -const _startP = /*@__PURE__*/ new Vector3(); -const _startEnd = /*@__PURE__*/ new Vector3(); + setTimeout( checkMaterialsReady, 10 ); -class Line3 { + } - constructor( start = new Vector3(), end = new Vector3() ) { + if ( extensions.get( 'KHR_parallel_shader_compile' ) !== null ) { - this.start = start; - this.end = end; + // If we can check the compilation status of the materials without + // blocking then do so right away. - } + checkMaterialsReady(); - set( start, end ) { + } else { - this.start.copy( start ); - this.end.copy( end ); + // Otherwise start by waiting a bit to give the materials we just + // initialized a chance to finish. - return this; + setTimeout( checkMaterialsReady, 10 ); - } + } - copy( line ) { + } ); - this.start.copy( line.start ); - this.end.copy( line.end ); + }; - return this; + // Animation Loop - } + let onAnimationFrameCallback = null; - getCenter( target ) { + function onAnimationFrame( time ) { - return target.addVectors( this.start, this.end ).multiplyScalar( 0.5 ); + if ( onAnimationFrameCallback ) onAnimationFrameCallback( time ); - } + } - delta( target ) { + function onXRSessionStart() { - return target.subVectors( this.end, this.start ); + animation.stop(); - } + } - distanceSq() { + function onXRSessionEnd() { - return this.start.distanceToSquared( this.end ); + animation.start(); - } + } - distance() { + const animation = new WebGLAnimation(); + animation.setAnimationLoop( onAnimationFrame ); - return this.start.distanceTo( this.end ); + if ( typeof self !== 'undefined' ) animation.setContext( self ); - } + this.setAnimationLoop = function ( callback ) { - at( t, target ) { + onAnimationFrameCallback = callback; + xr.setAnimationLoop( callback ); - return this.delta( target ).multiplyScalar( t ).add( this.start ); + ( callback === null ) ? animation.stop() : animation.start(); - } + }; - closestPointToPointParameter( point, clampToLine ) { + xr.addEventListener( 'sessionstart', onXRSessionStart ); + xr.addEventListener( 'sessionend', onXRSessionEnd ); - _startP.subVectors( point, this.start ); - _startEnd.subVectors( this.end, this.start ); + // Rendering - const startEnd2 = _startEnd.dot( _startEnd ); - const startEnd_startP = _startEnd.dot( _startP ); + /** + * Renders the given scene (or other type of 3D object) using the given camera. + * + * The render is done to a previously specified render target set by calling {@link WebGLRenderer#setRenderTarget} + * or to the canvas as usual. + * + * By default render buffers are cleared before rendering but you can prevent + * this by setting the property `autoClear` to `false`. If you want to prevent + * only certain buffers being cleared you can `autoClearColor`, `autoClearDepth` + * or `autoClearStencil` to `false`. To force a clear, use {@link WebGLRenderer#clear}. + * + * @param {Object3D} scene - The scene to render. + * @param {Camera} camera - The camera. + */ + this.render = function ( scene, camera ) { - let t = startEnd_startP / startEnd2; + if ( camera !== undefined && camera.isCamera !== true ) { - if ( clampToLine ) { + console.error( 'THREE.WebGLRenderer.render: camera is not an instance of THREE.Camera.' ); + return; - t = clamp( t, 0, 1 ); + } - } + if ( _isContextLost === true ) return; - return t; + // update scene graph - } + if ( scene.matrixWorldAutoUpdate === true ) scene.updateMatrixWorld(); - closestPointToPoint( point, clampToLine, target ) { + // update camera matrices and frustum - const t = this.closestPointToPointParameter( point, clampToLine ); + if ( camera.parent === null && camera.matrixWorldAutoUpdate === true ) camera.updateMatrixWorld(); - return this.delta( target ).multiplyScalar( t ).add( this.start ); + if ( xr.enabled === true && xr.isPresenting === true ) { - } + if ( xr.cameraAutoUpdate === true ) xr.updateCamera( camera ); - applyMatrix4( matrix ) { + camera = xr.getCamera(); // use XR camera for rendering - this.start.applyMatrix4( matrix ); - this.end.applyMatrix4( matrix ); + } - return this; + // + if ( scene.isScene === true ) scene.onBeforeRender( _this, scene, camera, _currentRenderTarget ); - } + currentRenderState = renderStates.get( scene, renderStateStack.length ); + currentRenderState.init( camera ); - equals( line ) { + renderStateStack.push( currentRenderState ); - return line.start.equals( this.start ) && line.end.equals( this.end ); + _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); + _frustum.setFromProjectionMatrix( _projScreenMatrix ); - } + _localClippingEnabled = this.localClippingEnabled; + _clippingEnabled = clipping.init( this.clippingPlanes, _localClippingEnabled ); - clone() { + currentRenderList = renderLists.get( scene, renderListStack.length ); + currentRenderList.init(); - return new this.constructor().copy( this ); + renderListStack.push( currentRenderList ); - } + if ( xr.enabled === true && xr.isPresenting === true ) { -} + const depthSensingMesh = _this.xr.getDepthSensingMesh(); -const _vector$3 = /*@__PURE__*/ new Vector3(); + if ( depthSensingMesh !== null ) { -class SpotLightHelper extends Object3D { + projectObject( depthSensingMesh, camera, - Infinity, _this.sortObjects ); - constructor( light, color ) { + } - super(); + } - this.light = light; + projectObject( scene, camera, 0, _this.sortObjects ); - this.matrix = light.matrixWorld; - this.matrixAutoUpdate = false; + currentRenderList.finish(); - this.color = color; + if ( _this.sortObjects === true ) { - this.type = 'SpotLightHelper'; + currentRenderList.sort( _opaqueSort, _transparentSort ); - const geometry = new BufferGeometry(); + } - const positions = [ - 0, 0, 0, 0, 0, 1, - 0, 0, 0, 1, 0, 1, - 0, 0, 0, - 1, 0, 1, - 0, 0, 0, 0, 1, 1, - 0, 0, 0, 0, - 1, 1 - ]; + _renderBackground = xr.enabled === false || xr.isPresenting === false || xr.hasDepthSensing() === false; + if ( _renderBackground ) { - for ( let i = 0, j = 1, l = 32; i < l; i ++, j ++ ) { + background.addToRenderList( currentRenderList, scene ); - const p1 = ( i / l ) * Math.PI * 2; - const p2 = ( j / l ) * Math.PI * 2; + } - positions.push( - Math.cos( p1 ), Math.sin( p1 ), 1, - Math.cos( p2 ), Math.sin( p2 ), 1 - ); + // - } + this.info.render.frame ++; - geometry.setAttribute( 'position', new Float32BufferAttribute( positions, 3 ) ); + if ( _clippingEnabled === true ) clipping.beginShadows(); - const material = new LineBasicMaterial( { fog: false, toneMapped: false } ); + const shadowsArray = currentRenderState.state.shadowsArray; - this.cone = new LineSegments( geometry, material ); - this.add( this.cone ); + shadowMap.render( shadowsArray, scene, camera ); - this.update(); + if ( _clippingEnabled === true ) clipping.endShadows(); - } + // - dispose() { + if ( this.info.autoReset === true ) this.info.reset(); - this.cone.geometry.dispose(); - this.cone.material.dispose(); + // render scene - } + const opaqueObjects = currentRenderList.opaque; + const transmissiveObjects = currentRenderList.transmissive; - update() { + currentRenderState.setupLights(); - this.light.updateWorldMatrix( true, false ); - this.light.target.updateWorldMatrix( true, false ); + if ( camera.isArrayCamera ) { - const coneLength = this.light.distance ? this.light.distance : 1000; - const coneWidth = coneLength * Math.tan( this.light.angle ); + const cameras = camera.cameras; - this.cone.scale.set( coneWidth, coneWidth, coneLength ); + if ( transmissiveObjects.length > 0 ) { - _vector$3.setFromMatrixPosition( this.light.target.matrixWorld ); + for ( let i = 0, l = cameras.length; i < l; i ++ ) { - this.cone.lookAt( _vector$3 ); + const camera2 = cameras[ i ]; - if ( this.color !== undefined ) { + renderTransmissionPass( opaqueObjects, transmissiveObjects, scene, camera2 ); - this.cone.material.color.set( this.color ); + } - } else { + } - this.cone.material.color.copy( this.light.color ); + if ( _renderBackground ) background.render( scene ); - } + for ( let i = 0, l = cameras.length; i < l; i ++ ) { - } + const camera2 = cameras[ i ]; -} + renderScene( currentRenderList, scene, camera2, camera2.viewport ); -const _vector$2 = /*@__PURE__*/ new Vector3(); -const _boneMatrix = /*@__PURE__*/ new Matrix4(); -const _matrixWorldInv = /*@__PURE__*/ new Matrix4(); + } + } else { -class SkeletonHelper extends LineSegments { + if ( transmissiveObjects.length > 0 ) renderTransmissionPass( opaqueObjects, transmissiveObjects, scene, camera ); - constructor( object ) { + if ( _renderBackground ) background.render( scene ); - const bones = getBoneList( object ); + renderScene( currentRenderList, scene, camera ); - const geometry = new BufferGeometry(); + } - const vertices = []; - const colors = []; + // - const color1 = new Color( 0, 0, 1 ); - const color2 = new Color( 0, 1, 0 ); + if ( _currentRenderTarget !== null && _currentActiveMipmapLevel === 0 ) { - for ( let i = 0; i < bones.length; i ++ ) { + // resolve multisample renderbuffers to a single-sample texture if necessary - const bone = bones[ i ]; + textures.updateMultisampleRenderTarget( _currentRenderTarget ); - if ( bone.parent && bone.parent.isBone ) { + // Generate mipmap if we're using any kind of mipmap filtering - vertices.push( 0, 0, 0 ); - vertices.push( 0, 0, 0 ); - colors.push( color1.r, color1.g, color1.b ); - colors.push( color2.r, color2.g, color2.b ); + textures.updateRenderTargetMipmap( _currentRenderTarget ); } - } - - geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - geometry.setAttribute( 'color', new Float32BufferAttribute( colors, 3 ) ); - - const material = new LineBasicMaterial( { vertexColors: true, depthTest: false, depthWrite: false, toneMapped: false, transparent: true } ); - - super( geometry, material ); + // - this.isSkeletonHelper = true; + if ( scene.isScene === true ) scene.onAfterRender( _this, scene, camera ); - this.type = 'SkeletonHelper'; + // _gl.finish(); - this.root = object; - this.bones = bones; + bindingStates.resetDefaultState(); + _currentMaterialId = -1; + _currentCamera = null; - this.matrix = object.matrixWorld; - this.matrixAutoUpdate = false; + renderStateStack.pop(); - } + if ( renderStateStack.length > 0 ) { - updateMatrixWorld( force ) { + currentRenderState = renderStateStack[ renderStateStack.length - 1 ]; - const bones = this.bones; + if ( _clippingEnabled === true ) clipping.setGlobalState( _this.clippingPlanes, currentRenderState.state.camera ); - const geometry = this.geometry; - const position = geometry.getAttribute( 'position' ); + } else { - _matrixWorldInv.copy( this.root.matrixWorld ).invert(); + currentRenderState = null; - for ( let i = 0, j = 0; i < bones.length; i ++ ) { + } - const bone = bones[ i ]; + renderListStack.pop(); - if ( bone.parent && bone.parent.isBone ) { + if ( renderListStack.length > 0 ) { - _boneMatrix.multiplyMatrices( _matrixWorldInv, bone.matrixWorld ); - _vector$2.setFromMatrixPosition( _boneMatrix ); - position.setXYZ( j, _vector$2.x, _vector$2.y, _vector$2.z ); + currentRenderList = renderListStack[ renderListStack.length - 1 ]; - _boneMatrix.multiplyMatrices( _matrixWorldInv, bone.parent.matrixWorld ); - _vector$2.setFromMatrixPosition( _boneMatrix ); - position.setXYZ( j + 1, _vector$2.x, _vector$2.y, _vector$2.z ); + } else { - j += 2; + currentRenderList = null; } - } - - geometry.getAttribute( 'position' ).needsUpdate = true; + }; - super.updateMatrixWorld( force ); + function projectObject( object, camera, groupOrder, sortObjects ) { - } + if ( object.visible === false ) return; - dispose() { + const visible = object.layers.test( camera.layers ); - this.geometry.dispose(); - this.material.dispose(); + if ( visible ) { - } + if ( object.isGroup ) { -} + groupOrder = object.renderOrder; + } else if ( object.isLOD ) { -function getBoneList( object ) { + if ( object.autoUpdate === true ) object.update( camera ); - const boneList = []; + } else if ( object.isLight ) { - if ( object.isBone === true ) { + currentRenderState.pushLight( object ); - boneList.push( object ); + if ( object.castShadow ) { - } + currentRenderState.pushShadow( object ); - for ( let i = 0; i < object.children.length; i ++ ) { + } - boneList.push.apply( boneList, getBoneList( object.children[ i ] ) ); + } else if ( object.isSprite ) { - } + if ( ! object.frustumCulled || _frustum.intersectsSprite( object ) ) { - return boneList; + if ( sortObjects ) { -} + _vector4.setFromMatrixPosition( object.matrixWorld ) + .applyMatrix4( _projScreenMatrix ); -class PointLightHelper extends Mesh { + } - constructor( light, sphereSize, color ) { + const geometry = objects.update( object ); + const material = object.material; - const geometry = new SphereGeometry( sphereSize, 4, 2 ); - const material = new MeshBasicMaterial( { wireframe: true, fog: false, toneMapped: false } ); + if ( material.visible ) { - super( geometry, material ); + currentRenderList.push( object, geometry, material, groupOrder, _vector4.z, null ); - this.light = light; + } - this.color = color; + } - this.type = 'PointLightHelper'; + } else if ( object.isMesh || object.isLine || object.isPoints ) { - this.matrix = this.light.matrixWorld; - this.matrixAutoUpdate = false; + if ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) { - this.update(); + const geometry = objects.update( object ); + const material = object.material; + if ( sortObjects ) { - /* - // TODO: delete this comment? - const distanceGeometry = new THREE.IcosahedronGeometry( 1, 2 ); - const distanceMaterial = new THREE.MeshBasicMaterial( { color: hexColor, fog: false, wireframe: true, opacity: 0.1, transparent: true } ); + if ( object.boundingSphere !== undefined ) { - this.lightSphere = new THREE.Mesh( bulbGeometry, bulbMaterial ); - this.lightDistance = new THREE.Mesh( distanceGeometry, distanceMaterial ); + if ( object.boundingSphere === null ) object.computeBoundingSphere(); + _vector4.copy( object.boundingSphere.center ); - const d = light.distance; + } else { - if ( d === 0.0 ) { + if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); + _vector4.copy( geometry.boundingSphere.center ); - this.lightDistance.visible = false; + } - } else { + _vector4 + .applyMatrix4( object.matrixWorld ) + .applyMatrix4( _projScreenMatrix ); - this.lightDistance.scale.set( d, d, d ); + } - } + if ( Array.isArray( material ) ) { - this.add( this.lightDistance ); - */ + const groups = geometry.groups; - } + for ( let i = 0, l = groups.length; i < l; i ++ ) { - dispose() { + const group = groups[ i ]; + const groupMaterial = material[ group.materialIndex ]; - this.geometry.dispose(); - this.material.dispose(); + if ( groupMaterial && groupMaterial.visible ) { - } + currentRenderList.push( object, geometry, groupMaterial, groupOrder, _vector4.z, group ); - update() { + } - this.light.updateWorldMatrix( true, false ); + } - if ( this.color !== undefined ) { + } else if ( material.visible ) { - this.material.color.set( this.color ); + currentRenderList.push( object, geometry, material, groupOrder, _vector4.z, null ); - } else { + } - this.material.color.copy( this.light.color ); + } - } + } - /* - const d = this.light.distance; + } - if ( d === 0.0 ) { + const children = object.children; - this.lightDistance.visible = false; + for ( let i = 0, l = children.length; i < l; i ++ ) { - } else { + projectObject( children[ i ], camera, groupOrder, sortObjects ); - this.lightDistance.visible = true; - this.lightDistance.scale.set( d, d, d ); + } } - */ - } + function renderScene( currentRenderList, scene, camera, viewport ) { -} + const opaqueObjects = currentRenderList.opaque; + const transmissiveObjects = currentRenderList.transmissive; + const transparentObjects = currentRenderList.transparent; -const _vector$1 = /*@__PURE__*/ new Vector3(); -const _color1 = /*@__PURE__*/ new Color(); -const _color2 = /*@__PURE__*/ new Color(); + currentRenderState.setupLightsView( camera ); -class HemisphereLightHelper extends Object3D { + if ( _clippingEnabled === true ) clipping.setGlobalState( _this.clippingPlanes, camera ); - constructor( light, size, color ) { + if ( viewport ) state.viewport( _currentViewport.copy( viewport ) ); - super(); + if ( opaqueObjects.length > 0 ) renderObjects( opaqueObjects, scene, camera ); + if ( transmissiveObjects.length > 0 ) renderObjects( transmissiveObjects, scene, camera ); + if ( transparentObjects.length > 0 ) renderObjects( transparentObjects, scene, camera ); - this.light = light; + // Ensure depth buffer writing is enabled so it can be cleared on next render - this.matrix = light.matrixWorld; - this.matrixAutoUpdate = false; + state.buffers.depth.setTest( true ); + state.buffers.depth.setMask( true ); + state.buffers.color.setMask( true ); - this.color = color; + state.setPolygonOffset( false ); - this.type = 'HemisphereLightHelper'; + } - const geometry = new OctahedronGeometry( size ); - geometry.rotateY( Math.PI * 0.5 ); + function renderTransmissionPass( opaqueObjects, transmissiveObjects, scene, camera ) { - this.material = new MeshBasicMaterial( { wireframe: true, fog: false, toneMapped: false } ); - if ( this.color === undefined ) this.material.vertexColors = true; + const overrideMaterial = scene.isScene === true ? scene.overrideMaterial : null; - const position = geometry.getAttribute( 'position' ); - const colors = new Float32Array( position.count * 3 ); + if ( overrideMaterial !== null ) { - geometry.setAttribute( 'color', new BufferAttribute( colors, 3 ) ); + return; - this.add( new Mesh( geometry, this.material ) ); + } - this.update(); + if ( currentRenderState.state.transmissionRenderTarget[ camera.id ] === undefined ) { - } + currentRenderState.state.transmissionRenderTarget[ camera.id ] = new WebGLRenderTarget( 1, 1, { + generateMipmaps: true, + type: ( extensions.has( 'EXT_color_buffer_half_float' ) || extensions.has( 'EXT_color_buffer_float' ) ) ? HalfFloatType : UnsignedByteType, + minFilter: LinearMipmapLinearFilter, + samples: 4, + stencilBuffer: stencil, + resolveDepthBuffer: false, + resolveStencilBuffer: false, + colorSpace: ColorManagement.workingColorSpace, + } ); - dispose() { + // debug - this.children[ 0 ].geometry.dispose(); - this.children[ 0 ].material.dispose(); + /* + const geometry = new PlaneGeometry(); + const material = new MeshBasicMaterial( { map: _transmissionRenderTarget.texture } ); - } + const mesh = new Mesh( geometry, material ); + scene.add( mesh ); + */ - update() { + } - const mesh = this.children[ 0 ]; + const transmissionRenderTarget = currentRenderState.state.transmissionRenderTarget[ camera.id ]; - if ( this.color !== undefined ) { + const activeViewport = camera.viewport || _currentViewport; + transmissionRenderTarget.setSize( activeViewport.z * _this.transmissionResolutionScale, activeViewport.w * _this.transmissionResolutionScale ); - this.material.color.set( this.color ); + // - } else { + const currentRenderTarget = _this.getRenderTarget(); + const currentActiveCubeFace = _this.getActiveCubeFace(); + const currentActiveMipmapLevel = _this.getActiveMipmapLevel(); - const colors = mesh.geometry.getAttribute( 'color' ); + _this.setRenderTarget( transmissionRenderTarget ); - _color1.copy( this.light.color ); - _color2.copy( this.light.groundColor ); + _this.getClearColor( _currentClearColor ); + _currentClearAlpha = _this.getClearAlpha(); + if ( _currentClearAlpha < 1 ) _this.setClearColor( 0xffffff, 0.5 ); - for ( let i = 0, l = colors.count; i < l; i ++ ) { + _this.clear(); - const color = ( i < ( l / 2 ) ) ? _color1 : _color2; + if ( _renderBackground ) background.render( scene ); - colors.setXYZ( i, color.r, color.g, color.b ); + // Turn off the features which can affect the frag color for opaque objects pass. + // Otherwise they are applied twice in opaque objects pass and transmission objects pass. + const currentToneMapping = _this.toneMapping; + _this.toneMapping = NoToneMapping; - } + // Remove viewport from camera to avoid nested render calls resetting viewport to it (e.g Reflector). + // Transmission render pass requires viewport to match the transmissionRenderTarget. + const currentCameraViewport = camera.viewport; + if ( camera.viewport !== undefined ) camera.viewport = undefined; - colors.needsUpdate = true; + currentRenderState.setupLightsView( camera ); - } + if ( _clippingEnabled === true ) clipping.setGlobalState( _this.clippingPlanes, camera ); - this.light.updateWorldMatrix( true, false ); + renderObjects( opaqueObjects, scene, camera ); - mesh.lookAt( _vector$1.setFromMatrixPosition( this.light.matrixWorld ).negate() ); + textures.updateMultisampleRenderTarget( transmissionRenderTarget ); + textures.updateRenderTargetMipmap( transmissionRenderTarget ); - } + if ( extensions.has( 'WEBGL_multisampled_render_to_texture' ) === false ) { // see #28131 -} + let renderTargetNeedsUpdate = false; -class GridHelper extends LineSegments { + for ( let i = 0, l = transmissiveObjects.length; i < l; i ++ ) { - constructor( size = 10, divisions = 10, color1 = 0x444444, color2 = 0x888888 ) { + const renderItem = transmissiveObjects[ i ]; - color1 = new Color( color1 ); - color2 = new Color( color2 ); + const object = renderItem.object; + const geometry = renderItem.geometry; + const material = renderItem.material; + const group = renderItem.group; - const center = divisions / 2; - const step = size / divisions; - const halfSize = size / 2; + if ( material.side === DoubleSide && object.layers.test( camera.layers ) ) { - const vertices = [], colors = []; + const currentSide = material.side; - for ( let i = 0, j = 0, k = - halfSize; i <= divisions; i ++, k += step ) { + material.side = BackSide; + material.needsUpdate = true; - vertices.push( - halfSize, 0, k, halfSize, 0, k ); - vertices.push( k, 0, - halfSize, k, 0, halfSize ); + renderObject( object, scene, camera, geometry, material, group ); - const color = i === center ? color1 : color2; + material.side = currentSide; + material.needsUpdate = true; - color.toArray( colors, j ); j += 3; - color.toArray( colors, j ); j += 3; - color.toArray( colors, j ); j += 3; - color.toArray( colors, j ); j += 3; + renderTargetNeedsUpdate = true; - } + } - const geometry = new BufferGeometry(); - geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - geometry.setAttribute( 'color', new Float32BufferAttribute( colors, 3 ) ); + } - const material = new LineBasicMaterial( { vertexColors: true, toneMapped: false } ); + if ( renderTargetNeedsUpdate === true ) { - super( geometry, material ); + textures.updateMultisampleRenderTarget( transmissionRenderTarget ); + textures.updateRenderTargetMipmap( transmissionRenderTarget ); - this.type = 'GridHelper'; + } - } + } - dispose() { + _this.setRenderTarget( currentRenderTarget, currentActiveCubeFace, currentActiveMipmapLevel ); - this.geometry.dispose(); - this.material.dispose(); + _this.setClearColor( _currentClearColor, _currentClearAlpha ); - } + if ( currentCameraViewport !== undefined ) camera.viewport = currentCameraViewport; -} + _this.toneMapping = currentToneMapping; -class PolarGridHelper extends LineSegments { + } - constructor( radius = 10, sectors = 16, rings = 8, divisions = 64, color1 = 0x444444, color2 = 0x888888 ) { + function renderObjects( renderList, scene, camera ) { - color1 = new Color( color1 ); - color2 = new Color( color2 ); + const overrideMaterial = scene.isScene === true ? scene.overrideMaterial : null; - const vertices = []; - const colors = []; + for ( let i = 0, l = renderList.length; i < l; i ++ ) { - // create the sectors + const renderItem = renderList[ i ]; - if ( sectors > 1 ) { + const object = renderItem.object; + const geometry = renderItem.geometry; + const group = renderItem.group; + let material = renderItem.material; - for ( let i = 0; i < sectors; i ++ ) { + if ( material.allowOverride === true && overrideMaterial !== null ) { - const v = ( i / sectors ) * ( Math.PI * 2 ); + material = overrideMaterial; - const x = Math.sin( v ) * radius; - const z = Math.cos( v ) * radius; + } - vertices.push( 0, 0, 0 ); - vertices.push( x, 0, z ); + if ( object.layers.test( camera.layers ) ) { - const color = ( i & 1 ) ? color1 : color2; + renderObject( object, scene, camera, geometry, material, group ); - colors.push( color.r, color.g, color.b ); - colors.push( color.r, color.g, color.b ); + } } } - // create the rings + function renderObject( object, scene, camera, geometry, material, group ) { + + object.onBeforeRender( _this, scene, camera, geometry, material, group ); - for ( let i = 0; i < rings; i ++ ) { + object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld ); + object.normalMatrix.getNormalMatrix( object.modelViewMatrix ); - const color = ( i & 1 ) ? color1 : color2; + material.onBeforeRender( _this, scene, camera, geometry, object, group ); - const r = radius - ( radius / rings * i ); + if ( material.transparent === true && material.side === DoubleSide && material.forceSinglePass === false ) { - for ( let j = 0; j < divisions; j ++ ) { + material.side = BackSide; + material.needsUpdate = true; + _this.renderBufferDirect( camera, scene, geometry, material, object, group ); - // first vertex + material.side = FrontSide; + material.needsUpdate = true; + _this.renderBufferDirect( camera, scene, geometry, material, object, group ); - let v = ( j / divisions ) * ( Math.PI * 2 ); + material.side = DoubleSide; - let x = Math.sin( v ) * r; - let z = Math.cos( v ) * r; + } else { - vertices.push( x, 0, z ); - colors.push( color.r, color.g, color.b ); + _this.renderBufferDirect( camera, scene, geometry, material, object, group ); - // second vertex + } - v = ( ( j + 1 ) / divisions ) * ( Math.PI * 2 ); + object.onAfterRender( _this, scene, camera, geometry, material, group ); - x = Math.sin( v ) * r; - z = Math.cos( v ) * r; + } - vertices.push( x, 0, z ); - colors.push( color.r, color.g, color.b ); + function getProgram( material, scene, object ) { - } + if ( scene.isScene !== true ) scene = _emptyScene; // scene could be a Mesh, Line, Points, ... - } + const materialProperties = properties.get( material ); - const geometry = new BufferGeometry(); - geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - geometry.setAttribute( 'color', new Float32BufferAttribute( colors, 3 ) ); + const lights = currentRenderState.state.lights; + const shadowsArray = currentRenderState.state.shadowsArray; - const material = new LineBasicMaterial( { vertexColors: true, toneMapped: false } ); + const lightsStateVersion = lights.state.version; - super( geometry, material ); + const parameters = programCache.getParameters( material, lights.state, shadowsArray, scene, object ); + const programCacheKey = programCache.getProgramCacheKey( parameters ); - this.type = 'PolarGridHelper'; + let programs = materialProperties.programs; - } + // always update environment and fog - changing these trigger an getProgram call, but it's possible that the program doesn't change - dispose() { + materialProperties.environment = material.isMeshStandardMaterial ? scene.environment : null; + materialProperties.fog = scene.fog; + materialProperties.envMap = ( material.isMeshStandardMaterial ? cubeuvmaps : cubemaps ).get( material.envMap || materialProperties.environment ); + materialProperties.envMapRotation = ( materialProperties.environment !== null && material.envMap === null ) ? scene.environmentRotation : material.envMapRotation; - this.geometry.dispose(); - this.material.dispose(); + if ( programs === undefined ) { - } + // new material -} + material.addEventListener( 'dispose', onMaterialDispose ); -const _v1 = /*@__PURE__*/ new Vector3(); -const _v2 = /*@__PURE__*/ new Vector3(); -const _v3 = /*@__PURE__*/ new Vector3(); + programs = new Map(); + materialProperties.programs = programs; -class DirectionalLightHelper extends Object3D { + } - constructor( light, size, color ) { + let program = programs.get( programCacheKey ); - super(); + if ( program !== undefined ) { - this.light = light; + // early out if program and light state is identical - this.matrix = light.matrixWorld; - this.matrixAutoUpdate = false; + if ( materialProperties.currentProgram === program && materialProperties.lightsStateVersion === lightsStateVersion ) { - this.color = color; + updateCommonMaterialProperties( material, parameters ); - this.type = 'DirectionalLightHelper'; + return program; - if ( size === undefined ) size = 1; + } - let geometry = new BufferGeometry(); - geometry.setAttribute( 'position', new Float32BufferAttribute( [ - - size, size, 0, - size, size, 0, - size, - size, 0, - - size, - size, 0, - - size, size, 0 - ], 3 ) ); + } else { - const material = new LineBasicMaterial( { fog: false, toneMapped: false } ); + parameters.uniforms = programCache.getUniforms( material ); - this.lightPlane = new Line( geometry, material ); - this.add( this.lightPlane ); + material.onBeforeCompile( parameters, _this ); - geometry = new BufferGeometry(); - geometry.setAttribute( 'position', new Float32BufferAttribute( [ 0, 0, 0, 0, 0, 1 ], 3 ) ); + program = programCache.acquireProgram( parameters, programCacheKey ); + programs.set( programCacheKey, program ); - this.targetLine = new Line( geometry, material ); - this.add( this.targetLine ); + materialProperties.uniforms = parameters.uniforms; - this.update(); + } - } + const uniforms = materialProperties.uniforms; - dispose() { + if ( ( ! material.isShaderMaterial && ! material.isRawShaderMaterial ) || material.clipping === true ) { - this.lightPlane.geometry.dispose(); - this.lightPlane.material.dispose(); - this.targetLine.geometry.dispose(); - this.targetLine.material.dispose(); + uniforms.clippingPlanes = clipping.uniform; - } + } - update() { + updateCommonMaterialProperties( material, parameters ); + + // store the light setup it was created for + + materialProperties.needsLights = materialNeedsLights( material ); + materialProperties.lightsStateVersion = lightsStateVersion; - this.light.updateWorldMatrix( true, false ); - this.light.target.updateWorldMatrix( true, false ); + if ( materialProperties.needsLights ) { - _v1.setFromMatrixPosition( this.light.matrixWorld ); - _v2.setFromMatrixPosition( this.light.target.matrixWorld ); - _v3.subVectors( _v2, _v1 ); + // wire up the material to this renderer's lighting state - this.lightPlane.lookAt( _v2 ); + uniforms.ambientLightColor.value = lights.state.ambient; + uniforms.lightProbe.value = lights.state.probe; + uniforms.directionalLights.value = lights.state.directional; + uniforms.directionalLightShadows.value = lights.state.directionalShadow; + uniforms.spotLights.value = lights.state.spot; + uniforms.spotLightShadows.value = lights.state.spotShadow; + uniforms.rectAreaLights.value = lights.state.rectArea; + uniforms.ltc_1.value = lights.state.rectAreaLTC1; + uniforms.ltc_2.value = lights.state.rectAreaLTC2; + uniforms.pointLights.value = lights.state.point; + uniforms.pointLightShadows.value = lights.state.pointShadow; + uniforms.hemisphereLights.value = lights.state.hemi; - if ( this.color !== undefined ) { + uniforms.directionalShadowMap.value = lights.state.directionalShadowMap; + uniforms.directionalShadowMatrix.value = lights.state.directionalShadowMatrix; + uniforms.spotShadowMap.value = lights.state.spotShadowMap; + uniforms.spotLightMatrix.value = lights.state.spotLightMatrix; + uniforms.spotLightMap.value = lights.state.spotLightMap; + uniforms.pointShadowMap.value = lights.state.pointShadowMap; + uniforms.pointShadowMatrix.value = lights.state.pointShadowMatrix; + // TODO (abelnation): add area lights shadow info to uniforms - this.lightPlane.material.color.set( this.color ); - this.targetLine.material.color.set( this.color ); + } - } else { + materialProperties.currentProgram = program; + materialProperties.uniformsList = null; - this.lightPlane.material.color.copy( this.light.color ); - this.targetLine.material.color.copy( this.light.color ); + return program; } - this.targetLine.lookAt( _v2 ); - this.targetLine.scale.z = _v3.length(); + function getUniformList( materialProperties ) { - } + if ( materialProperties.uniformsList === null ) { -} + const progUniforms = materialProperties.currentProgram.getUniforms(); + materialProperties.uniformsList = WebGLUniforms.seqWithValue( progUniforms.seq, materialProperties.uniforms ); -const _vector = /*@__PURE__*/ new Vector3(); -const _camera = /*@__PURE__*/ new Camera(); + } -/** - * - shows frustum, line of sight and up of the camera - * - suitable for fast updates - * - based on frustum visualization in lightgl.js shadowmap example - * https://github.com/evanw/lightgl.js/blob/master/tests/shadowmap.html - */ + return materialProperties.uniformsList; + + } + + function updateCommonMaterialProperties( material, parameters ) { -class CameraHelper extends LineSegments { + const materialProperties = properties.get( material ); - constructor( camera ) { + materialProperties.outputColorSpace = parameters.outputColorSpace; + materialProperties.batching = parameters.batching; + materialProperties.batchingColor = parameters.batchingColor; + materialProperties.instancing = parameters.instancing; + materialProperties.instancingColor = parameters.instancingColor; + materialProperties.instancingMorph = parameters.instancingMorph; + materialProperties.skinning = parameters.skinning; + materialProperties.morphTargets = parameters.morphTargets; + materialProperties.morphNormals = parameters.morphNormals; + materialProperties.morphColors = parameters.morphColors; + materialProperties.morphTargetsCount = parameters.morphTargetsCount; + materialProperties.numClippingPlanes = parameters.numClippingPlanes; + materialProperties.numIntersection = parameters.numClipIntersection; + materialProperties.vertexAlphas = parameters.vertexAlphas; + materialProperties.vertexTangents = parameters.vertexTangents; + materialProperties.toneMapping = parameters.toneMapping; - const geometry = new BufferGeometry(); - const material = new LineBasicMaterial( { color: 0xffffff, vertexColors: true, toneMapped: false } ); + } - const vertices = []; - const colors = []; + function setProgram( camera, scene, geometry, material, object ) { - const pointMap = {}; + if ( scene.isScene !== true ) scene = _emptyScene; // scene could be a Mesh, Line, Points, ... - // near + textures.resetTextureUnits(); - addLine( 'n1', 'n2' ); - addLine( 'n2', 'n4' ); - addLine( 'n4', 'n3' ); - addLine( 'n3', 'n1' ); + const fog = scene.fog; + const environment = material.isMeshStandardMaterial ? scene.environment : null; + const colorSpace = ( _currentRenderTarget === null ) ? _this.outputColorSpace : ( _currentRenderTarget.isXRRenderTarget === true ? _currentRenderTarget.texture.colorSpace : LinearSRGBColorSpace ); + const envMap = ( material.isMeshStandardMaterial ? cubeuvmaps : cubemaps ).get( material.envMap || environment ); + const vertexAlphas = material.vertexColors === true && !! geometry.attributes.color && geometry.attributes.color.itemSize === 4; + const vertexTangents = !! geometry.attributes.tangent && ( !! material.normalMap || material.anisotropy > 0 ); + const morphTargets = !! geometry.morphAttributes.position; + const morphNormals = !! geometry.morphAttributes.normal; + const morphColors = !! geometry.morphAttributes.color; - // far + let toneMapping = NoToneMapping; - addLine( 'f1', 'f2' ); - addLine( 'f2', 'f4' ); - addLine( 'f4', 'f3' ); - addLine( 'f3', 'f1' ); + if ( material.toneMapped ) { - // sides + if ( _currentRenderTarget === null || _currentRenderTarget.isXRRenderTarget === true ) { - addLine( 'n1', 'f1' ); - addLine( 'n2', 'f2' ); - addLine( 'n3', 'f3' ); - addLine( 'n4', 'f4' ); + toneMapping = _this.toneMapping; - // cone + } - addLine( 'p', 'n1' ); - addLine( 'p', 'n2' ); - addLine( 'p', 'n3' ); - addLine( 'p', 'n4' ); + } - // up + const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color; + const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0; - addLine( 'u1', 'u2' ); - addLine( 'u2', 'u3' ); - addLine( 'u3', 'u1' ); + const materialProperties = properties.get( material ); + const lights = currentRenderState.state.lights; - // target + if ( _clippingEnabled === true ) { - addLine( 'c', 't' ); - addLine( 'p', 'c' ); + if ( _localClippingEnabled === true || camera !== _currentCamera ) { - // cross + const useCache = + camera === _currentCamera && + material.id === _currentMaterialId; - addLine( 'cn1', 'cn2' ); - addLine( 'cn3', 'cn4' ); + // we might want to call this function with some ClippingGroup + // object instead of the material, once it becomes feasible + // (#8465, #8379) + clipping.setState( material, camera, useCache ); - addLine( 'cf1', 'cf2' ); - addLine( 'cf3', 'cf4' ); + } - function addLine( a, b ) { + } - addPoint( a ); - addPoint( b ); + // - } + let needsProgramChange = false; - function addPoint( id ) { + if ( material.version === materialProperties.__version ) { - vertices.push( 0, 0, 0 ); - colors.push( 0, 0, 0 ); + if ( materialProperties.needsLights && ( materialProperties.lightsStateVersion !== lights.state.version ) ) { - if ( pointMap[ id ] === undefined ) { + needsProgramChange = true; - pointMap[ id ] = []; + } else if ( materialProperties.outputColorSpace !== colorSpace ) { - } + needsProgramChange = true; - pointMap[ id ].push( ( vertices.length / 3 ) - 1 ); + } else if ( object.isBatchedMesh && materialProperties.batching === false ) { - } + needsProgramChange = true; - geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - geometry.setAttribute( 'color', new Float32BufferAttribute( colors, 3 ) ); + } else if ( ! object.isBatchedMesh && materialProperties.batching === true ) { - super( geometry, material ); + needsProgramChange = true; - this.type = 'CameraHelper'; + } else if ( object.isBatchedMesh && materialProperties.batchingColor === true && object.colorTexture === null ) { - this.camera = camera; - if ( this.camera.updateProjectionMatrix ) this.camera.updateProjectionMatrix(); + needsProgramChange = true; - this.matrix = camera.matrixWorld; - this.matrixAutoUpdate = false; + } else if ( object.isBatchedMesh && materialProperties.batchingColor === false && object.colorTexture !== null ) { - this.pointMap = pointMap; + needsProgramChange = true; - this.update(); + } else if ( object.isInstancedMesh && materialProperties.instancing === false ) { - // colors + needsProgramChange = true; - const colorFrustum = new Color( 0xffaa00 ); - const colorCone = new Color( 0xff0000 ); - const colorUp = new Color( 0x00aaff ); - const colorTarget = new Color( 0xffffff ); - const colorCross = new Color( 0x333333 ); + } else if ( ! object.isInstancedMesh && materialProperties.instancing === true ) { - this.setColors( colorFrustum, colorCone, colorUp, colorTarget, colorCross ); + needsProgramChange = true; - } + } else if ( object.isSkinnedMesh && materialProperties.skinning === false ) { - setColors( frustum, cone, up, target, cross ) { + needsProgramChange = true; - const geometry = this.geometry; + } else if ( ! object.isSkinnedMesh && materialProperties.skinning === true ) { - const colorAttribute = geometry.getAttribute( 'color' ); + needsProgramChange = true; - // near + } else if ( object.isInstancedMesh && materialProperties.instancingColor === true && object.instanceColor === null ) { - colorAttribute.setXYZ( 0, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 1, frustum.r, frustum.g, frustum.b ); // n1, n2 - colorAttribute.setXYZ( 2, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 3, frustum.r, frustum.g, frustum.b ); // n2, n4 - colorAttribute.setXYZ( 4, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 5, frustum.r, frustum.g, frustum.b ); // n4, n3 - colorAttribute.setXYZ( 6, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 7, frustum.r, frustum.g, frustum.b ); // n3, n1 + needsProgramChange = true; - // far + } else if ( object.isInstancedMesh && materialProperties.instancingColor === false && object.instanceColor !== null ) { - colorAttribute.setXYZ( 8, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 9, frustum.r, frustum.g, frustum.b ); // f1, f2 - colorAttribute.setXYZ( 10, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 11, frustum.r, frustum.g, frustum.b ); // f2, f4 - colorAttribute.setXYZ( 12, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 13, frustum.r, frustum.g, frustum.b ); // f4, f3 - colorAttribute.setXYZ( 14, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 15, frustum.r, frustum.g, frustum.b ); // f3, f1 + needsProgramChange = true; - // sides + } else if ( object.isInstancedMesh && materialProperties.instancingMorph === true && object.morphTexture === null ) { - colorAttribute.setXYZ( 16, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 17, frustum.r, frustum.g, frustum.b ); // n1, f1 - colorAttribute.setXYZ( 18, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 19, frustum.r, frustum.g, frustum.b ); // n2, f2 - colorAttribute.setXYZ( 20, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 21, frustum.r, frustum.g, frustum.b ); // n3, f3 - colorAttribute.setXYZ( 22, frustum.r, frustum.g, frustum.b ); colorAttribute.setXYZ( 23, frustum.r, frustum.g, frustum.b ); // n4, f4 + needsProgramChange = true; - // cone + } else if ( object.isInstancedMesh && materialProperties.instancingMorph === false && object.morphTexture !== null ) { - colorAttribute.setXYZ( 24, cone.r, cone.g, cone.b ); colorAttribute.setXYZ( 25, cone.r, cone.g, cone.b ); // p, n1 - colorAttribute.setXYZ( 26, cone.r, cone.g, cone.b ); colorAttribute.setXYZ( 27, cone.r, cone.g, cone.b ); // p, n2 - colorAttribute.setXYZ( 28, cone.r, cone.g, cone.b ); colorAttribute.setXYZ( 29, cone.r, cone.g, cone.b ); // p, n3 - colorAttribute.setXYZ( 30, cone.r, cone.g, cone.b ); colorAttribute.setXYZ( 31, cone.r, cone.g, cone.b ); // p, n4 + needsProgramChange = true; - // up + } else if ( materialProperties.envMap !== envMap ) { - colorAttribute.setXYZ( 32, up.r, up.g, up.b ); colorAttribute.setXYZ( 33, up.r, up.g, up.b ); // u1, u2 - colorAttribute.setXYZ( 34, up.r, up.g, up.b ); colorAttribute.setXYZ( 35, up.r, up.g, up.b ); // u2, u3 - colorAttribute.setXYZ( 36, up.r, up.g, up.b ); colorAttribute.setXYZ( 37, up.r, up.g, up.b ); // u3, u1 + needsProgramChange = true; - // target + } else if ( material.fog === true && materialProperties.fog !== fog ) { - colorAttribute.setXYZ( 38, target.r, target.g, target.b ); colorAttribute.setXYZ( 39, target.r, target.g, target.b ); // c, t - colorAttribute.setXYZ( 40, cross.r, cross.g, cross.b ); colorAttribute.setXYZ( 41, cross.r, cross.g, cross.b ); // p, c + needsProgramChange = true; - // cross + } else if ( materialProperties.numClippingPlanes !== undefined && + ( materialProperties.numClippingPlanes !== clipping.numPlanes || + materialProperties.numIntersection !== clipping.numIntersection ) ) { - colorAttribute.setXYZ( 42, cross.r, cross.g, cross.b ); colorAttribute.setXYZ( 43, cross.r, cross.g, cross.b ); // cn1, cn2 - colorAttribute.setXYZ( 44, cross.r, cross.g, cross.b ); colorAttribute.setXYZ( 45, cross.r, cross.g, cross.b ); // cn3, cn4 + needsProgramChange = true; - colorAttribute.setXYZ( 46, cross.r, cross.g, cross.b ); colorAttribute.setXYZ( 47, cross.r, cross.g, cross.b ); // cf1, cf2 - colorAttribute.setXYZ( 48, cross.r, cross.g, cross.b ); colorAttribute.setXYZ( 49, cross.r, cross.g, cross.b ); // cf3, cf4 + } else if ( materialProperties.vertexAlphas !== vertexAlphas ) { - colorAttribute.needsUpdate = true; + needsProgramChange = true; - } + } else if ( materialProperties.vertexTangents !== vertexTangents ) { - update() { + needsProgramChange = true; - const geometry = this.geometry; - const pointMap = this.pointMap; + } else if ( materialProperties.morphTargets !== morphTargets ) { - const w = 1, h = 1; + needsProgramChange = true; - // we need just camera projection matrix inverse - // world matrix must be identity + } else if ( materialProperties.morphNormals !== morphNormals ) { - _camera.projectionMatrixInverse.copy( this.camera.projectionMatrixInverse ); + needsProgramChange = true; - // center / target + } else if ( materialProperties.morphColors !== morphColors ) { - setPoint( 'c', pointMap, geometry, _camera, 0, 0, - 1 ); - setPoint( 't', pointMap, geometry, _camera, 0, 0, 1 ); + needsProgramChange = true; - // near + } else if ( materialProperties.toneMapping !== toneMapping ) { - setPoint( 'n1', pointMap, geometry, _camera, - w, - h, - 1 ); - setPoint( 'n2', pointMap, geometry, _camera, w, - h, - 1 ); - setPoint( 'n3', pointMap, geometry, _camera, - w, h, - 1 ); - setPoint( 'n4', pointMap, geometry, _camera, w, h, - 1 ); + needsProgramChange = true; - // far + } else if ( materialProperties.morphTargetsCount !== morphTargetsCount ) { - setPoint( 'f1', pointMap, geometry, _camera, - w, - h, 1 ); - setPoint( 'f2', pointMap, geometry, _camera, w, - h, 1 ); - setPoint( 'f3', pointMap, geometry, _camera, - w, h, 1 ); - setPoint( 'f4', pointMap, geometry, _camera, w, h, 1 ); + needsProgramChange = true; - // up + } - setPoint( 'u1', pointMap, geometry, _camera, w * 0.7, h * 1.1, - 1 ); - setPoint( 'u2', pointMap, geometry, _camera, - w * 0.7, h * 1.1, - 1 ); - setPoint( 'u3', pointMap, geometry, _camera, 0, h * 2, - 1 ); + } else { - // cross + needsProgramChange = true; + materialProperties.__version = material.version; - setPoint( 'cf1', pointMap, geometry, _camera, - w, 0, 1 ); - setPoint( 'cf2', pointMap, geometry, _camera, w, 0, 1 ); - setPoint( 'cf3', pointMap, geometry, _camera, 0, - h, 1 ); - setPoint( 'cf4', pointMap, geometry, _camera, 0, h, 1 ); + } - setPoint( 'cn1', pointMap, geometry, _camera, - w, 0, - 1 ); - setPoint( 'cn2', pointMap, geometry, _camera, w, 0, - 1 ); - setPoint( 'cn3', pointMap, geometry, _camera, 0, - h, - 1 ); - setPoint( 'cn4', pointMap, geometry, _camera, 0, h, - 1 ); + // - geometry.getAttribute( 'position' ).needsUpdate = true; + let program = materialProperties.currentProgram; - } + if ( needsProgramChange === true ) { - dispose() { + program = getProgram( material, scene, object ); - this.geometry.dispose(); - this.material.dispose(); + } - } + let refreshProgram = false; + let refreshMaterial = false; + let refreshLights = false; -} + const p_uniforms = program.getUniforms(), + m_uniforms = materialProperties.uniforms; + if ( state.useProgram( program.program ) ) { -function setPoint( point, pointMap, geometry, camera, x, y, z ) { + refreshProgram = true; + refreshMaterial = true; + refreshLights = true; - _vector.set( x, y, z ).unproject( camera ); + } - const points = pointMap[ point ]; + if ( material.id !== _currentMaterialId ) { - if ( points !== undefined ) { + _currentMaterialId = material.id; - const position = geometry.getAttribute( 'position' ); + refreshMaterial = true; - for ( let i = 0, l = points.length; i < l; i ++ ) { + } - position.setXYZ( points[ i ], _vector.x, _vector.y, _vector.z ); + if ( refreshProgram || _currentCamera !== camera ) { - } + // common camera uniforms - } + const reverseDepthBuffer = state.buffers.depth.getReversed(); -} + if ( reverseDepthBuffer ) { -const _box = /*@__PURE__*/ new Box3(); + _currentProjectionMatrix.copy( camera.projectionMatrix ); -class BoxHelper extends LineSegments { + toNormalizedProjectionMatrix( _currentProjectionMatrix ); + toReversedProjectionMatrix( _currentProjectionMatrix ); - constructor( object, color = 0xffff00 ) { + p_uniforms.setValue( _gl, 'projectionMatrix', _currentProjectionMatrix ); - const indices = new Uint16Array( [ 0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7 ] ); - const positions = new Float32Array( 8 * 3 ); + } else { - const geometry = new BufferGeometry(); - geometry.setIndex( new BufferAttribute( indices, 1 ) ); - geometry.setAttribute( 'position', new BufferAttribute( positions, 3 ) ); + p_uniforms.setValue( _gl, 'projectionMatrix', camera.projectionMatrix ); - super( geometry, new LineBasicMaterial( { color: color, toneMapped: false } ) ); + } - this.object = object; - this.type = 'BoxHelper'; + p_uniforms.setValue( _gl, 'viewMatrix', camera.matrixWorldInverse ); - this.matrixAutoUpdate = false; + const uCamPos = p_uniforms.map.cameraPosition; - this.update(); + if ( uCamPos !== undefined ) { - } + uCamPos.setValue( _gl, _vector3.setFromMatrixPosition( camera.matrixWorld ) ); - update( object ) { + } - if ( object !== undefined ) { + if ( capabilities.logarithmicDepthBuffer ) { - console.warn( 'THREE.BoxHelper: .update() has no longer arguments.' ); + p_uniforms.setValue( _gl, 'logDepthBufFC', + 2.0 / ( Math.log( camera.far + 1.0 ) / Math.LN2 ) ); - } + } - if ( this.object !== undefined ) { + // consider moving isOrthographic to UniformLib and WebGLMaterials, see https://github.com/mrdoob/three.js/pull/26467#issuecomment-1645185067 - _box.setFromObject( this.object ); + if ( material.isMeshPhongMaterial || + material.isMeshToonMaterial || + material.isMeshLambertMaterial || + material.isMeshBasicMaterial || + material.isMeshStandardMaterial || + material.isShaderMaterial ) { - } + p_uniforms.setValue( _gl, 'isOrthographic', camera.isOrthographicCamera === true ); - if ( _box.isEmpty() ) return; + } - const min = _box.min; - const max = _box.max; + if ( _currentCamera !== camera ) { - /* - 5____4 - 1/___0/| - | 6__|_7 - 2/___3/ + _currentCamera = camera; - 0: max.x, max.y, max.z - 1: min.x, max.y, max.z - 2: min.x, min.y, max.z - 3: max.x, min.y, max.z - 4: max.x, max.y, min.z - 5: min.x, max.y, min.z - 6: min.x, min.y, min.z - 7: max.x, min.y, min.z - */ + // lighting uniforms depend on the camera so enforce an update + // now, in case this material supports lights - or later, when + // the next material that does gets activated: - const position = this.geometry.attributes.position; - const array = position.array; + refreshMaterial = true; // set to true on material change + refreshLights = true; // remains set until update done - array[ 0 ] = max.x; array[ 1 ] = max.y; array[ 2 ] = max.z; - array[ 3 ] = min.x; array[ 4 ] = max.y; array[ 5 ] = max.z; - array[ 6 ] = min.x; array[ 7 ] = min.y; array[ 8 ] = max.z; - array[ 9 ] = max.x; array[ 10 ] = min.y; array[ 11 ] = max.z; - array[ 12 ] = max.x; array[ 13 ] = max.y; array[ 14 ] = min.z; - array[ 15 ] = min.x; array[ 16 ] = max.y; array[ 17 ] = min.z; - array[ 18 ] = min.x; array[ 19 ] = min.y; array[ 20 ] = min.z; - array[ 21 ] = max.x; array[ 22 ] = min.y; array[ 23 ] = min.z; + } - position.needsUpdate = true; + } - this.geometry.computeBoundingSphere(); + // skinning and morph target uniforms must be set even if material didn't change + // auto-setting of texture unit for bone and morph texture must go before other textures + // otherwise textures used for skinning and morphing can take over texture units reserved for other material textures - } + if ( object.isSkinnedMesh ) { - setFromObject( object ) { + p_uniforms.setOptional( _gl, object, 'bindMatrix' ); + p_uniforms.setOptional( _gl, object, 'bindMatrixInverse' ); - this.object = object; - this.update(); + const skeleton = object.skeleton; - return this; + if ( skeleton ) { - } + if ( skeleton.boneTexture === null ) skeleton.computeBoneTexture(); - copy( source, recursive ) { + p_uniforms.setValue( _gl, 'boneTexture', skeleton.boneTexture, textures ); - super.copy( source, recursive ); + } - this.object = source.object; + } - return this; + if ( object.isBatchedMesh ) { - } + p_uniforms.setOptional( _gl, object, 'batchingTexture' ); + p_uniforms.setValue( _gl, 'batchingTexture', object._matricesTexture, textures ); - dispose() { + p_uniforms.setOptional( _gl, object, 'batchingIdTexture' ); + p_uniforms.setValue( _gl, 'batchingIdTexture', object._indirectTexture, textures ); - this.geometry.dispose(); - this.material.dispose(); + p_uniforms.setOptional( _gl, object, 'batchingColorTexture' ); + if ( object._colorsTexture !== null ) { - } + p_uniforms.setValue( _gl, 'batchingColorTexture', object._colorsTexture, textures ); -} + } -class Box3Helper extends LineSegments { + } - constructor( box, color = 0xffff00 ) { + const morphAttributes = geometry.morphAttributes; - const indices = new Uint16Array( [ 0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7 ] ); + if ( morphAttributes.position !== undefined || morphAttributes.normal !== undefined || ( morphAttributes.color !== undefined ) ) { - const positions = [ 1, 1, 1, - 1, 1, 1, - 1, - 1, 1, 1, - 1, 1, 1, 1, - 1, - 1, 1, - 1, - 1, - 1, - 1, 1, - 1, - 1 ]; + morphtargets.update( object, geometry, program ); - const geometry = new BufferGeometry(); + } - geometry.setIndex( new BufferAttribute( indices, 1 ) ); + if ( refreshMaterial || materialProperties.receiveShadow !== object.receiveShadow ) { - geometry.setAttribute( 'position', new Float32BufferAttribute( positions, 3 ) ); + materialProperties.receiveShadow = object.receiveShadow; + p_uniforms.setValue( _gl, 'receiveShadow', object.receiveShadow ); - super( geometry, new LineBasicMaterial( { color: color, toneMapped: false } ) ); + } - this.box = box; + // https://github.com/mrdoob/three.js/pull/24467#issuecomment-1209031512 - this.type = 'Box3Helper'; + if ( material.isMeshGouraudMaterial && material.envMap !== null ) { - this.geometry.computeBoundingSphere(); + m_uniforms.envMap.value = envMap; - } + m_uniforms.flipEnvMap.value = ( envMap.isCubeTexture && envMap.isRenderTargetTexture === false ) ? -1 : 1; - updateMatrixWorld( force ) { + } - const box = this.box; + if ( material.isMeshStandardMaterial && material.envMap === null && scene.environment !== null ) { - if ( box.isEmpty() ) return; + m_uniforms.envMapIntensity.value = scene.environmentIntensity; - box.getCenter( this.position ); + } - box.getSize( this.scale ); + if ( refreshMaterial ) { - this.scale.multiplyScalar( 0.5 ); + p_uniforms.setValue( _gl, 'toneMappingExposure', _this.toneMappingExposure ); - super.updateMatrixWorld( force ); + if ( materialProperties.needsLights ) { - } + // the current material requires lighting info - dispose() { + // note: all lighting uniforms are always set correctly + // they simply reference the renderer's state for their + // values + // + // use the current material's .needsUpdate flags to set + // the GL state when required - this.geometry.dispose(); - this.material.dispose(); + markUniformsLightsNeedsUpdate( m_uniforms, refreshLights ); - } + } -} + // refresh uniforms common to several materials -class PlaneHelper extends Line { + if ( fog && material.fog === true ) { - constructor( plane, size = 1, hex = 0xffff00 ) { + materials.refreshFogUniforms( m_uniforms, fog ); - const color = hex; + } - const positions = [ 1, - 1, 0, - 1, 1, 0, - 1, - 1, 0, 1, 1, 0, - 1, 1, 0, - 1, - 1, 0, 1, - 1, 0, 1, 1, 0 ]; + materials.refreshMaterialUniforms( m_uniforms, material, _pixelRatio, _height, currentRenderState.state.transmissionRenderTarget[ camera.id ] ); - const geometry = new BufferGeometry(); - geometry.setAttribute( 'position', new Float32BufferAttribute( positions, 3 ) ); - geometry.computeBoundingSphere(); + WebGLUniforms.upload( _gl, getUniformList( materialProperties ), m_uniforms, textures ); - super( geometry, new LineBasicMaterial( { color: color, toneMapped: false } ) ); + } - this.type = 'PlaneHelper'; + if ( material.isShaderMaterial && material.uniformsNeedUpdate === true ) { - this.plane = plane; + WebGLUniforms.upload( _gl, getUniformList( materialProperties ), m_uniforms, textures ); + material.uniformsNeedUpdate = false; - this.size = size; + } - const positions2 = [ 1, 1, 0, - 1, 1, 0, - 1, - 1, 0, 1, 1, 0, - 1, - 1, 0, 1, - 1, 0 ]; + if ( material.isSpriteMaterial ) { - const geometry2 = new BufferGeometry(); - geometry2.setAttribute( 'position', new Float32BufferAttribute( positions2, 3 ) ); - geometry2.computeBoundingSphere(); + p_uniforms.setValue( _gl, 'center', object.center ); - this.add( new Mesh( geometry2, new MeshBasicMaterial( { color: color, opacity: 0.2, transparent: true, depthWrite: false, toneMapped: false } ) ) ); + } - } + // common matrices - updateMatrixWorld( force ) { + p_uniforms.setValue( _gl, 'modelViewMatrix', object.modelViewMatrix ); + p_uniforms.setValue( _gl, 'normalMatrix', object.normalMatrix ); + p_uniforms.setValue( _gl, 'modelMatrix', object.matrixWorld ); - this.position.set( 0, 0, 0 ); + // UBOs - this.scale.set( 0.5 * this.size, 0.5 * this.size, 1 ); + if ( material.isShaderMaterial || material.isRawShaderMaterial ) { - this.lookAt( this.plane.normal ); + const groups = material.uniformsGroups; - this.translateZ( - this.plane.constant ); + for ( let i = 0, l = groups.length; i < l; i ++ ) { - super.updateMatrixWorld( force ); + const group = groups[ i ]; - } + uniformsGroups.update( group, program ); + uniformsGroups.bind( group, program ); - dispose() { + } - this.geometry.dispose(); - this.material.dispose(); - this.children[ 0 ].geometry.dispose(); - this.children[ 0 ].material.dispose(); + } - } + return program; -} + } -const _axis = /*@__PURE__*/ new Vector3(); -let _lineGeometry, _coneGeometry; + // If uniforms are marked as clean, they don't need to be loaded to the GPU. -class ArrowHelper extends Object3D { + function markUniformsLightsNeedsUpdate( uniforms, value ) { - // dir is assumed to be normalized + uniforms.ambientLightColor.needsUpdate = value; + uniforms.lightProbe.needsUpdate = value; - constructor( dir = new Vector3( 0, 0, 1 ), origin = new Vector3( 0, 0, 0 ), length = 1, color = 0xffff00, headLength = length * 0.2, headWidth = headLength * 0.2 ) { + uniforms.directionalLights.needsUpdate = value; + uniforms.directionalLightShadows.needsUpdate = value; + uniforms.pointLights.needsUpdate = value; + uniforms.pointLightShadows.needsUpdate = value; + uniforms.spotLights.needsUpdate = value; + uniforms.spotLightShadows.needsUpdate = value; + uniforms.rectAreaLights.needsUpdate = value; + uniforms.hemisphereLights.needsUpdate = value; - super(); + } - this.type = 'ArrowHelper'; + function materialNeedsLights( material ) { - if ( _lineGeometry === undefined ) { + return material.isMeshLambertMaterial || material.isMeshToonMaterial || material.isMeshPhongMaterial || + material.isMeshStandardMaterial || material.isShadowMaterial || + ( material.isShaderMaterial && material.lights === true ); - _lineGeometry = new BufferGeometry(); - _lineGeometry.setAttribute( 'position', new Float32BufferAttribute( [ 0, 0, 0, 0, 1, 0 ], 3 ) ); + } - _coneGeometry = new CylinderGeometry( 0, 0.5, 1, 5, 1 ); - _coneGeometry.translate( 0, - 0.5, 0 ); + /** + * Returns the active cube face. + * + * @return {number} The active cube face. + */ + this.getActiveCubeFace = function () { - } + return _currentActiveCubeFace; - this.position.copy( origin ); + }; - this.line = new Line( _lineGeometry, new LineBasicMaterial( { color: color, toneMapped: false } ) ); - this.line.matrixAutoUpdate = false; - this.add( this.line ); + /** + * Returns the active mipmap level. + * + * @return {number} The active mipmap level. + */ + this.getActiveMipmapLevel = function () { - this.cone = new Mesh( _coneGeometry, new MeshBasicMaterial( { color: color, toneMapped: false } ) ); - this.cone.matrixAutoUpdate = false; - this.add( this.cone ); + return _currentActiveMipmapLevel; - this.setDirection( dir ); - this.setLength( length, headLength, headWidth ); + }; - } + /** + * Returns the active render target. + * + * @return {?WebGLRenderTarget} The active render target. Returns `null` if no render target + * is currently set. + */ + this.getRenderTarget = function () { - setDirection( dir ) { + return _currentRenderTarget; - // dir is assumed to be normalized + }; - if ( dir.y > 0.99999 ) { + this.setRenderTargetTextures = function ( renderTarget, colorTexture, depthTexture ) { - this.quaternion.set( 0, 0, 0, 1 ); + const renderTargetProperties = properties.get( renderTarget ); - } else if ( dir.y < - 0.99999 ) { + renderTargetProperties.__autoAllocateDepthBuffer = renderTarget.resolveDepthBuffer === false; + if ( renderTargetProperties.__autoAllocateDepthBuffer === false ) { - this.quaternion.set( 1, 0, 0, 0 ); + // The multisample_render_to_texture extension doesn't work properly if there + // are midframe flushes and an external depth buffer. Disable use of the extension. + renderTargetProperties.__useRenderToTexture = false; - } else { + } - _axis.set( dir.z, 0, - dir.x ).normalize(); + properties.get( renderTarget.texture ).__webglTexture = colorTexture; + properties.get( renderTarget.depthTexture ).__webglTexture = renderTargetProperties.__autoAllocateDepthBuffer ? undefined : depthTexture; - const radians = Math.acos( dir.y ); + renderTargetProperties.__hasExternalTextures = true; - this.quaternion.setFromAxisAngle( _axis, radians ); + }; - } + this.setRenderTargetFramebuffer = function ( renderTarget, defaultFramebuffer ) { - } + const renderTargetProperties = properties.get( renderTarget ); + renderTargetProperties.__webglFramebuffer = defaultFramebuffer; + renderTargetProperties.__useDefaultFramebuffer = defaultFramebuffer === undefined; - setLength( length, headLength = length * 0.2, headWidth = headLength * 0.2 ) { + }; - this.line.scale.set( 1, Math.max( 0.0001, length - headLength ), 1 ); // see #17458 - this.line.updateMatrix(); + const _scratchFrameBuffer = _gl.createFramebuffer(); - this.cone.scale.set( headWidth, headLength, headWidth ); - this.cone.position.y = length; - this.cone.updateMatrix(); + /** + * Sets the active rendertarget. + * + * @param {?WebGLRenderTarget} renderTarget - The render target to set. When `null` is given, + * the canvas is set as the active render target instead. + * @param {number} [activeCubeFace=0] - The active cube face when using a cube render target. + * Indicates the z layer to render in to when using 3D or array render targets. + * @param {number} [activeMipmapLevel=0] - The active mipmap level. + */ + this.setRenderTarget = function ( renderTarget, activeCubeFace = 0, activeMipmapLevel = 0 ) { - } + _currentRenderTarget = renderTarget; + _currentActiveCubeFace = activeCubeFace; + _currentActiveMipmapLevel = activeMipmapLevel; - setColor( color ) { + let useDefaultFramebuffer = true; + let framebuffer = null; + let isCube = false; + let isRenderTarget3D = false; - this.line.material.color.set( color ); - this.cone.material.color.set( color ); + if ( renderTarget ) { - } + const renderTargetProperties = properties.get( renderTarget ); - copy( source ) { + if ( renderTargetProperties.__useDefaultFramebuffer !== undefined ) { - super.copy( source, false ); + // We need to make sure to rebind the framebuffer. + state.bindFramebuffer( _gl.FRAMEBUFFER, null ); + useDefaultFramebuffer = false; - this.line.copy( source.line ); - this.cone.copy( source.cone ); + } else if ( renderTargetProperties.__webglFramebuffer === undefined ) { - return this; + textures.setupRenderTarget( renderTarget ); - } + } else if ( renderTargetProperties.__hasExternalTextures ) { - dispose() { + // Color and depth texture must be rebound in order for the swapchain to update. + textures.rebindTextures( renderTarget, properties.get( renderTarget.texture ).__webglTexture, properties.get( renderTarget.depthTexture ).__webglTexture ); - this.line.geometry.dispose(); - this.line.material.dispose(); - this.cone.geometry.dispose(); - this.cone.material.dispose(); + } else if ( renderTarget.depthBuffer ) { - } + // check if the depth texture is already bound to the frame buffer and that it's been initialized + const depthTexture = renderTarget.depthTexture; + if ( renderTargetProperties.__boundDepthTexture !== depthTexture ) { -} + // check if the depth texture is compatible + if ( + depthTexture !== null && + properties.has( depthTexture ) && + ( renderTarget.width !== depthTexture.image.width || renderTarget.height !== depthTexture.image.height ) + ) { -class AxesHelper extends LineSegments { + throw new Error( 'WebGLRenderTarget: Attached DepthTexture is initialized to the incorrect size.' ); - constructor( size = 1 ) { + } - const vertices = [ - 0, 0, 0, size, 0, 0, - 0, 0, 0, 0, size, 0, - 0, 0, 0, 0, 0, size - ]; + // Swap the depth buffer to the currently attached one + textures.setupDepthRenderbuffer( renderTarget ); - const colors = [ - 1, 0, 0, 1, 0.6, 0, - 0, 1, 0, 0.6, 1, 0, - 0, 0, 1, 0, 0.6, 1 - ]; + } - const geometry = new BufferGeometry(); - geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - geometry.setAttribute( 'color', new Float32BufferAttribute( colors, 3 ) ); + } - const material = new LineBasicMaterial( { vertexColors: true, toneMapped: false } ); + const texture = renderTarget.texture; - super( geometry, material ); + if ( texture.isData3DTexture || texture.isDataArrayTexture || texture.isCompressedArrayTexture ) { - this.type = 'AxesHelper'; + isRenderTarget3D = true; - } + } - setColors( xAxisColor, yAxisColor, zAxisColor ) { + const __webglFramebuffer = properties.get( renderTarget ).__webglFramebuffer; - const color = new Color(); - const array = this.geometry.attributes.color.array; + if ( renderTarget.isWebGLCubeRenderTarget ) { - color.set( xAxisColor ); - color.toArray( array, 0 ); - color.toArray( array, 3 ); + if ( Array.isArray( __webglFramebuffer[ activeCubeFace ] ) ) { - color.set( yAxisColor ); - color.toArray( array, 6 ); - color.toArray( array, 9 ); + framebuffer = __webglFramebuffer[ activeCubeFace ][ activeMipmapLevel ]; - color.set( zAxisColor ); - color.toArray( array, 12 ); - color.toArray( array, 15 ); + } else { - this.geometry.attributes.color.needsUpdate = true; + framebuffer = __webglFramebuffer[ activeCubeFace ]; - return this; + } - } + isCube = true; - dispose() { + } else if ( ( renderTarget.samples > 0 ) && textures.useMultisampledRTT( renderTarget ) === false ) { - this.geometry.dispose(); - this.material.dispose(); + framebuffer = properties.get( renderTarget ).__webglMultisampledFramebuffer; - } + } else { -} + if ( Array.isArray( __webglFramebuffer ) ) { -class ShapePath { + framebuffer = __webglFramebuffer[ activeMipmapLevel ]; - constructor() { + } else { - this.type = 'ShapePath'; + framebuffer = __webglFramebuffer; - this.color = new Color(); + } - this.subPaths = []; - this.currentPath = null; + } - } + _currentViewport.copy( renderTarget.viewport ); + _currentScissor.copy( renderTarget.scissor ); + _currentScissorTest = renderTarget.scissorTest; - moveTo( x, y ) { + } else { - this.currentPath = new Path(); - this.subPaths.push( this.currentPath ); - this.currentPath.moveTo( x, y ); + _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ).floor(); + _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ).floor(); + _currentScissorTest = _scissorTest; - return this; + } - } + // Use a scratch frame buffer if rendering to a mip level to avoid depth buffers + // being bound that are different sizes. + if ( activeMipmapLevel !== 0 ) { - lineTo( x, y ) { + framebuffer = _scratchFrameBuffer; - this.currentPath.lineTo( x, y ); + } - return this; + const framebufferBound = state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); - } + if ( framebufferBound && useDefaultFramebuffer ) { - quadraticCurveTo( aCPx, aCPy, aX, aY ) { + state.drawBuffers( renderTarget, framebuffer ); - this.currentPath.quadraticCurveTo( aCPx, aCPy, aX, aY ); + } - return this; + state.viewport( _currentViewport ); + state.scissor( _currentScissor ); + state.setScissorTest( _currentScissorTest ); - } + if ( isCube ) { - bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) { + const textureProperties = properties.get( renderTarget.texture ); + _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + activeCubeFace, textureProperties.__webglTexture, activeMipmapLevel ); - this.currentPath.bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ); + } else if ( isRenderTarget3D ) { - return this; + const textureProperties = properties.get( renderTarget.texture ); + const layer = activeCubeFace; + _gl.framebufferTextureLayer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, textureProperties.__webglTexture, activeMipmapLevel, layer ); - } + } else if ( renderTarget !== null && activeMipmapLevel !== 0 ) { - splineThru( pts ) { + // Only bind the frame buffer if we are using a scratch frame buffer to render to a mipmap. + // If we rebind the texture when using a multi sample buffer then an error about inconsistent samples will be thrown. + const textureProperties = properties.get( renderTarget.texture ); + _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D, textureProperties.__webglTexture, activeMipmapLevel ); - this.currentPath.splineThru( pts ); + } - return this; + _currentMaterialId = -1; // reset current material to ensure correct uniform bindings - } + }; - toShapes( isCCW ) { + /** + * Reads the pixel data from the given render target into the given buffer. + * + * @param {WebGLRenderTarget} renderTarget - The render target to read from. + * @param {number} x - The `x` coordinate of the copy region's origin. + * @param {number} y - The `y` coordinate of the copy region's origin. + * @param {number} width - The width of the copy region. + * @param {number} height - The height of the copy region. + * @param {TypedArray} buffer - The result buffer. + * @param {number} [activeCubeFaceIndex] - The active cube face index. + * @param {number} [textureIndex=0] - The texture index of an MRT render target. + */ + this.readRenderTargetPixels = function ( renderTarget, x, y, width, height, buffer, activeCubeFaceIndex, textureIndex = 0 ) { - function toShapesNoHoles( inSubpaths ) { + if ( ! ( renderTarget && renderTarget.isWebGLRenderTarget ) ) { - const shapes = []; + console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.' ); + return; - for ( let i = 0, l = inSubpaths.length; i < l; i ++ ) { + } - const tmpPath = inSubpaths[ i ]; + let framebuffer = properties.get( renderTarget ).__webglFramebuffer; - const tmpShape = new Shape(); - tmpShape.curves = tmpPath.curves; + if ( renderTarget.isWebGLCubeRenderTarget && activeCubeFaceIndex !== undefined ) { - shapes.push( tmpShape ); + framebuffer = framebuffer[ activeCubeFaceIndex ]; } - return shapes; - - } + if ( framebuffer ) { - function isPointInsidePolygon( inPt, inPolygon ) { + state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); - const polyLen = inPolygon.length; + try { - // inPt on polygon contour => immediate success or - // toggling of inside/outside at every single! intersection point of an edge - // with the horizontal line through inPt, left of inPt - // not counting lowerY endpoints of edges and whole edges on that line - let inside = false; - for ( let p = polyLen - 1, q = 0; q < polyLen; p = q ++ ) { + const texture = renderTarget.textures[ textureIndex ]; + const textureFormat = texture.format; + const textureType = texture.type; - let edgeLowPt = inPolygon[ p ]; - let edgeHighPt = inPolygon[ q ]; + if ( ! capabilities.textureFormatReadable( textureFormat ) ) { - let edgeDx = edgeHighPt.x - edgeLowPt.x; - let edgeDy = edgeHighPt.y - edgeLowPt.y; + console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in RGBA or implementation defined format.' ); + return; - if ( Math.abs( edgeDy ) > Number.EPSILON ) { + } - // not parallel - if ( edgeDy < 0 ) { + if ( ! capabilities.textureTypeReadable( textureType ) ) { - edgeLowPt = inPolygon[ q ]; edgeDx = - edgeDx; - edgeHighPt = inPolygon[ p ]; edgeDy = - edgeDy; + console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in UnsignedByteType or implementation defined type.' ); + return; } - if ( ( inPt.y < edgeLowPt.y ) || ( inPt.y > edgeHighPt.y ) ) continue; + // the following if statement ensures valid read requests (no out-of-bounds pixels, see #8604) - if ( inPt.y === edgeLowPt.y ) { + if ( ( x >= 0 && x <= ( renderTarget.width - width ) ) && ( y >= 0 && y <= ( renderTarget.height - height ) ) ) { - if ( inPt.x === edgeLowPt.x ) return true; // inPt is on contour ? - // continue; // no intersection or edgeLowPt => doesn't count !!! + // when using MRT, select the correct color buffer for the subsequent read command - } else { + if ( renderTarget.textures.length > 1 ) _gl.readBuffer( _gl.COLOR_ATTACHMENT0 + textureIndex ); - const perpEdge = edgeDy * ( inPt.x - edgeLowPt.x ) - edgeDx * ( inPt.y - edgeLowPt.y ); - if ( perpEdge === 0 ) return true; // inPt is on contour ? - if ( perpEdge < 0 ) continue; - inside = ! inside; // true intersection left of inPt + _gl.readPixels( x, y, width, height, utils.convert( textureFormat ), utils.convert( textureType ), buffer ); } - } else { + } finally { + + // restore framebuffer of current render target if necessary - // parallel or collinear - if ( inPt.y !== edgeLowPt.y ) continue; // parallel - // edge lies on the same horizontal line as inPt - if ( ( ( edgeHighPt.x <= inPt.x ) && ( inPt.x <= edgeLowPt.x ) ) || - ( ( edgeLowPt.x <= inPt.x ) && ( inPt.x <= edgeHighPt.x ) ) ) return true; // inPt: Point on contour ! - // continue; + const framebuffer = ( _currentRenderTarget !== null ) ? properties.get( _currentRenderTarget ).__webglFramebuffer : null; + state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); } } - return inside; - - } + }; - const isClockWise = ShapeUtils.isClockWise; + /** + * Asynchronous, non-blocking version of {@link WebGLRenderer#readRenderTargetPixels}. + * + * It is recommended to use this version of `readRenderTargetPixels()` whenever possible. + * + * @async + * @param {WebGLRenderTarget} renderTarget - The render target to read from. + * @param {number} x - The `x` coordinate of the copy region's origin. + * @param {number} y - The `y` coordinate of the copy region's origin. + * @param {number} width - The width of the copy region. + * @param {number} height - The height of the copy region. + * @param {TypedArray} buffer - The result buffer. + * @param {number} [activeCubeFaceIndex] - The active cube face index. + * @param {number} [textureIndex=0] - The texture index of an MRT render target. + * @return {Promise} A Promise that resolves when the read has been finished. The resolve provides the read data as a typed array. + */ + this.readRenderTargetPixelsAsync = async function ( renderTarget, x, y, width, height, buffer, activeCubeFaceIndex, textureIndex = 0 ) { - const subPaths = this.subPaths; - if ( subPaths.length === 0 ) return []; + if ( ! ( renderTarget && renderTarget.isWebGLRenderTarget ) ) { - let solid, tmpPath, tmpShape; - const shapes = []; + throw new Error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.' ); - if ( subPaths.length === 1 ) { + } - tmpPath = subPaths[ 0 ]; - tmpShape = new Shape(); - tmpShape.curves = tmpPath.curves; - shapes.push( tmpShape ); - return shapes; + let framebuffer = properties.get( renderTarget ).__webglFramebuffer; + if ( renderTarget.isWebGLCubeRenderTarget && activeCubeFaceIndex !== undefined ) { - } + framebuffer = framebuffer[ activeCubeFaceIndex ]; - let holesFirst = ! isClockWise( subPaths[ 0 ].getPoints() ); - holesFirst = isCCW ? ! holesFirst : holesFirst; + } - // console.log("Holes first", holesFirst); + if ( framebuffer ) { - const betterShapeHoles = []; - const newShapes = []; - let newShapeHoles = []; - let mainIdx = 0; - let tmpPoints; + // the following if statement ensures valid read requests (no out-of-bounds pixels, see #8604) + if ( ( x >= 0 && x <= ( renderTarget.width - width ) ) && ( y >= 0 && y <= ( renderTarget.height - height ) ) ) { - newShapes[ mainIdx ] = undefined; - newShapeHoles[ mainIdx ] = []; + // set the active frame buffer to the one we want to read + state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); - for ( let i = 0, l = subPaths.length; i < l; i ++ ) { + const texture = renderTarget.textures[ textureIndex ]; + const textureFormat = texture.format; + const textureType = texture.type; - tmpPath = subPaths[ i ]; - tmpPoints = tmpPath.getPoints(); - solid = isClockWise( tmpPoints ); - solid = isCCW ? ! solid : solid; + if ( ! capabilities.textureFormatReadable( textureFormat ) ) { - if ( solid ) { + throw new Error( 'THREE.WebGLRenderer.readRenderTargetPixelsAsync: renderTarget is not in RGBA or implementation defined format.' ); - if ( ( ! holesFirst ) && ( newShapes[ mainIdx ] ) ) mainIdx ++; + } - newShapes[ mainIdx ] = { s: new Shape(), p: tmpPoints }; - newShapes[ mainIdx ].s.curves = tmpPath.curves; + if ( ! capabilities.textureTypeReadable( textureType ) ) { - if ( holesFirst ) mainIdx ++; - newShapeHoles[ mainIdx ] = []; + throw new Error( 'THREE.WebGLRenderer.readRenderTargetPixelsAsync: renderTarget is not in UnsignedByteType or implementation defined type.' ); - //console.log('cw', i); + } - } else { + const glBuffer = _gl.createBuffer(); + _gl.bindBuffer( _gl.PIXEL_PACK_BUFFER, glBuffer ); + _gl.bufferData( _gl.PIXEL_PACK_BUFFER, buffer.byteLength, _gl.STREAM_READ ); - newShapeHoles[ mainIdx ].push( { h: tmpPath, p: tmpPoints[ 0 ] } ); + // when using MRT, select the corect color buffer for the subsequent read command - //console.log('ccw', i); + if ( renderTarget.textures.length > 1 ) _gl.readBuffer( _gl.COLOR_ATTACHMENT0 + textureIndex ); - } + _gl.readPixels( x, y, width, height, utils.convert( textureFormat ), utils.convert( textureType ), 0 ); - } + // reset the frame buffer to the currently set buffer before waiting + const currFramebuffer = _currentRenderTarget !== null ? properties.get( _currentRenderTarget ).__webglFramebuffer : null; + state.bindFramebuffer( _gl.FRAMEBUFFER, currFramebuffer ); - // only Holes? -> probably all Shapes with wrong orientation - if ( ! newShapes[ 0 ] ) return toShapesNoHoles( subPaths ); + // check if the commands have finished every 8 ms + const sync = _gl.fenceSync( _gl.SYNC_GPU_COMMANDS_COMPLETE, 0 ); + _gl.flush(); - if ( newShapes.length > 1 ) { + await probeAsync( _gl, sync, 4 ); - let ambiguous = false; - let toChange = 0; + // read the data and delete the buffer + _gl.bindBuffer( _gl.PIXEL_PACK_BUFFER, glBuffer ); + _gl.getBufferSubData( _gl.PIXEL_PACK_BUFFER, 0, buffer ); + _gl.deleteBuffer( glBuffer ); + _gl.deleteSync( sync ); - for ( let sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) { + return buffer; - betterShapeHoles[ sIdx ] = []; + } else { - } + throw new Error( 'THREE.WebGLRenderer.readRenderTargetPixelsAsync: requested read bounds are out of range.' ); - for ( let sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) { + } - const sho = newShapeHoles[ sIdx ]; + } - for ( let hIdx = 0; hIdx < sho.length; hIdx ++ ) { + }; - const ho = sho[ hIdx ]; - let hole_unassigned = true; + /** + * Copies pixels from the current bound framebuffer into the given texture. + * + * @param {FramebufferTexture} texture - The texture. + * @param {?Vector2} [position=null] - The start position of the copy operation. + * @param {number} [level=0] - The mip level. The default represents the base mip. + */ + this.copyFramebufferToTexture = function ( texture, position = null, level = 0 ) { - for ( let s2Idx = 0; s2Idx < newShapes.length; s2Idx ++ ) { + const levelScale = Math.pow( 2, - level ); + const width = Math.floor( texture.image.width * levelScale ); + const height = Math.floor( texture.image.height * levelScale ); - if ( isPointInsidePolygon( ho.p, newShapes[ s2Idx ].p ) ) { + const x = position !== null ? position.x : 0; + const y = position !== null ? position.y : 0; - if ( sIdx !== s2Idx ) toChange ++; + textures.setTexture2D( texture, 0 ); - if ( hole_unassigned ) { + _gl.copyTexSubImage2D( _gl.TEXTURE_2D, level, 0, 0, x, y, width, height ); - hole_unassigned = false; - betterShapeHoles[ s2Idx ].push( ho ); + state.unbindTexture(); - } else { + }; - ambiguous = true; + const _srcFramebuffer = _gl.createFramebuffer(); + const _dstFramebuffer = _gl.createFramebuffer(); - } + /** + * Copies data of the given source texture into a destination texture. + * + * When using render target textures as `srcTexture` and `dstTexture`, you must make sure both render targets are initialized + * {@link WebGLRenderer#initRenderTarget}. + * + * @param {Texture} srcTexture - The source texture. + * @param {Texture} dstTexture - The destination texture. + * @param {?(Box2|Box3)} [srcRegion=null] - A bounding box which describes the source region. Can be two or three-dimensional. + * @param {?(Vector2|Vector3)} [dstPosition=null] - A vector that represents the origin of the destination region. Can be two or three-dimensional. + * @param {number} [srcLevel=0] - The source mipmap level to copy. + * @param {?number} [dstLevel=null] - The destination mipmap level. + */ + this.copyTextureToTexture = function ( srcTexture, dstTexture, srcRegion = null, dstPosition = null, srcLevel = 0, dstLevel = null ) { - } + // support the previous signature with just a single dst mipmap level + if ( dstLevel === null ) { - } + if ( srcLevel !== 0 ) { - if ( hole_unassigned ) { + // @deprecated, r171 + warnOnce( 'WebGLRenderer: copyTextureToTexture function signature has changed to support src and dst mipmap levels.' ); + dstLevel = srcLevel; + srcLevel = 0; - betterShapeHoles[ sIdx ].push( ho ); + } else { - } + dstLevel = 0; } } - if ( toChange > 0 && ambiguous === false ) { + // gather the necessary dimensions to copy + let width, height, depth, minX, minY, minZ; + let dstX, dstY, dstZ; + const image = srcTexture.isCompressedTexture ? srcTexture.mipmaps[ dstLevel ] : srcTexture.image; + if ( srcRegion !== null ) { - newShapeHoles = betterShapeHoles; + width = srcRegion.max.x - srcRegion.min.x; + height = srcRegion.max.y - srcRegion.min.y; + depth = srcRegion.isBox3 ? srcRegion.max.z - srcRegion.min.z : 1; + minX = srcRegion.min.x; + minY = srcRegion.min.y; + minZ = srcRegion.isBox3 ? srcRegion.min.z : 0; - } + } else { - } + const levelScale = Math.pow( 2, - srcLevel ); + width = Math.floor( image.width * levelScale ); + height = Math.floor( image.height * levelScale ); + if ( srcTexture.isDataArrayTexture ) { + + depth = image.depth; + + } else if ( srcTexture.isData3DTexture ) { - let tmpHoles; + depth = Math.floor( image.depth * levelScale ); - for ( let i = 0, il = newShapes.length; i < il; i ++ ) { + } else { - tmpShape = newShapes[ i ].s; - shapes.push( tmpShape ); - tmpHoles = newShapeHoles[ i ]; + depth = 1; - for ( let j = 0, jl = tmpHoles.length; j < jl; j ++ ) { + } - tmpShape.holes.push( tmpHoles[ j ].h ); + minX = 0; + minY = 0; + minZ = 0; } - } + if ( dstPosition !== null ) { - //console.log("shape", shapes); + dstX = dstPosition.x; + dstY = dstPosition.y; + dstZ = dstPosition.z; - return shapes; + } else { - } + dstX = 0; + dstY = 0; + dstZ = 0; -} + } -class BoxBufferGeometry extends BoxGeometry { // @deprecated, r144 + // Set up the destination target + const glFormat = utils.convert( dstTexture.format ); + const glType = utils.convert( dstTexture.type ); + let glTarget; - constructor( width, height, depth, widthSegments, heightSegments, depthSegments ) { + if ( dstTexture.isData3DTexture ) { - console.warn( 'THREE.BoxBufferGeometry has been renamed to THREE.BoxGeometry.' ); - super( width, height, depth, widthSegments, heightSegments, depthSegments ); + textures.setTexture3D( dstTexture, 0 ); + glTarget = _gl.TEXTURE_3D; + } else if ( dstTexture.isDataArrayTexture || dstTexture.isCompressedArrayTexture ) { - } + textures.setTexture2DArray( dstTexture, 0 ); + glTarget = _gl.TEXTURE_2D_ARRAY; -} + } else { -class CapsuleBufferGeometry extends CapsuleGeometry { // @deprecated, r144 + textures.setTexture2D( dstTexture, 0 ); + glTarget = _gl.TEXTURE_2D; - constructor( radius, length, capSegments, radialSegments ) { + } - console.warn( 'THREE.CapsuleBufferGeometry has been renamed to THREE.CapsuleGeometry.' ); - super( radius, length, capSegments, radialSegments ); + _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, dstTexture.flipY ); + _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, dstTexture.premultiplyAlpha ); + _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, dstTexture.unpackAlignment ); - } + // used for copying data from cpu + const currentUnpackRowLen = _gl.getParameter( _gl.UNPACK_ROW_LENGTH ); + const currentUnpackImageHeight = _gl.getParameter( _gl.UNPACK_IMAGE_HEIGHT ); + const currentUnpackSkipPixels = _gl.getParameter( _gl.UNPACK_SKIP_PIXELS ); + const currentUnpackSkipRows = _gl.getParameter( _gl.UNPACK_SKIP_ROWS ); + const currentUnpackSkipImages = _gl.getParameter( _gl.UNPACK_SKIP_IMAGES ); -} + _gl.pixelStorei( _gl.UNPACK_ROW_LENGTH, image.width ); + _gl.pixelStorei( _gl.UNPACK_IMAGE_HEIGHT, image.height ); + _gl.pixelStorei( _gl.UNPACK_SKIP_PIXELS, minX ); + _gl.pixelStorei( _gl.UNPACK_SKIP_ROWS, minY ); + _gl.pixelStorei( _gl.UNPACK_SKIP_IMAGES, minZ ); -class CircleBufferGeometry extends CircleGeometry { // @deprecated, r144 + // set up the src texture + const isSrc3D = srcTexture.isDataArrayTexture || srcTexture.isData3DTexture; + const isDst3D = dstTexture.isDataArrayTexture || dstTexture.isData3DTexture; + if ( srcTexture.isDepthTexture ) { - constructor( radius, segments, thetaStart, thetaLength ) { + const srcTextureProperties = properties.get( srcTexture ); + const dstTextureProperties = properties.get( dstTexture ); + const srcRenderTargetProperties = properties.get( srcTextureProperties.__renderTarget ); + const dstRenderTargetProperties = properties.get( dstTextureProperties.__renderTarget ); + state.bindFramebuffer( _gl.READ_FRAMEBUFFER, srcRenderTargetProperties.__webglFramebuffer ); + state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, dstRenderTargetProperties.__webglFramebuffer ); - console.warn( 'THREE.CircleBufferGeometry has been renamed to THREE.CircleGeometry.' ); - super( radius, segments, thetaStart, thetaLength ); + for ( let i = 0; i < depth; i ++ ) { - } + // if the source or destination are a 3d target then a layer needs to be bound + if ( isSrc3D ) { -} + _gl.framebufferTextureLayer( _gl.READ_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, properties.get( srcTexture ).__webglTexture, srcLevel, minZ + i ); + _gl.framebufferTextureLayer( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, properties.get( dstTexture ).__webglTexture, dstLevel, dstZ + i ); -class ConeBufferGeometry extends ConeGeometry { // @deprecated, r144 + } - constructor( radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ) { + _gl.blitFramebuffer( minX, minY, width, height, dstX, dstY, width, height, _gl.DEPTH_BUFFER_BIT, _gl.NEAREST ); - console.warn( 'THREE.ConeBufferGeometry has been renamed to THREE.ConeGeometry.' ); - super( radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ); + } - } + state.bindFramebuffer( _gl.READ_FRAMEBUFFER, null ); + state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, null ); -} + } else if ( srcLevel !== 0 || srcTexture.isRenderTargetTexture || properties.has( srcTexture ) ) { -class CylinderBufferGeometry extends CylinderGeometry { // @deprecated, r144 + // get the appropriate frame buffers + const srcTextureProperties = properties.get( srcTexture ); + const dstTextureProperties = properties.get( dstTexture ); - constructor( radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ) { + // bind the frame buffer targets + state.bindFramebuffer( _gl.READ_FRAMEBUFFER, _srcFramebuffer ); + state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, _dstFramebuffer ); - console.warn( 'THREE.CylinderBufferGeometry has been renamed to THREE.CylinderGeometry.' ); - super( radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ); + for ( let i = 0; i < depth; i ++ ) { - } + // assign the correct layers and mip maps to the frame buffers + if ( isSrc3D ) { -} + _gl.framebufferTextureLayer( _gl.READ_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, srcTextureProperties.__webglTexture, srcLevel, minZ + i ); -class DodecahedronBufferGeometry extends DodecahedronGeometry { // @deprecated, r144 + } else { - constructor( radius, detail ) { + _gl.framebufferTexture2D( _gl.READ_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D, srcTextureProperties.__webglTexture, srcLevel ); - console.warn( 'THREE.DodecahedronBufferGeometry has been renamed to THREE.DodecahedronGeometry.' ); - super( radius, detail ); + } - } + if ( isDst3D ) { -} + _gl.framebufferTextureLayer( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, dstTextureProperties.__webglTexture, dstLevel, dstZ + i ); -class ExtrudeBufferGeometry extends ExtrudeGeometry { // @deprecated, r144 + } else { - constructor( shapes, options ) { + _gl.framebufferTexture2D( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D, dstTextureProperties.__webglTexture, dstLevel ); - console.warn( 'THREE.ExtrudeBufferGeometry has been renamed to THREE.ExtrudeGeometry.' ); - super( shapes, options ); + } - } + // copy the data using the fastest function that can achieve the copy + if ( srcLevel !== 0 ) { -} + _gl.blitFramebuffer( minX, minY, width, height, dstX, dstY, width, height, _gl.COLOR_BUFFER_BIT, _gl.NEAREST ); -class IcosahedronBufferGeometry extends IcosahedronGeometry { // @deprecated, r144 + } else if ( isDst3D ) { - constructor( radius, detail ) { + _gl.copyTexSubImage3D( glTarget, dstLevel, dstX, dstY, dstZ + i, minX, minY, width, height ); - console.warn( 'THREE.IcosahedronBufferGeometry has been renamed to THREE.IcosahedronGeometry.' ); - super( radius, detail ); + } else { - } + _gl.copyTexSubImage2D( glTarget, dstLevel, dstX, dstY, minX, minY, width, height ); -} + } -class LatheBufferGeometry extends LatheGeometry { // @deprecated, r144 + } - constructor( points, segments, phiStart, phiLength ) { + // unbind read, draw buffers + state.bindFramebuffer( _gl.READ_FRAMEBUFFER, null ); + state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, null ); - console.warn( 'THREE.LatheBufferGeometry has been renamed to THREE.LatheGeometry.' ); - super( points, segments, phiStart, phiLength ); + } else { - } + if ( isDst3D ) { -} + // copy data into the 3d texture + if ( srcTexture.isDataTexture || srcTexture.isData3DTexture ) { -class OctahedronBufferGeometry extends OctahedronGeometry { // @deprecated, r144 + _gl.texSubImage3D( glTarget, dstLevel, dstX, dstY, dstZ, width, height, depth, glFormat, glType, image.data ); - constructor( radius, detail ) { + } else if ( dstTexture.isCompressedArrayTexture ) { - console.warn( 'THREE.OctahedronBufferGeometry has been renamed to THREE.OctahedronGeometry.' ); - super( radius, detail ); + _gl.compressedTexSubImage3D( glTarget, dstLevel, dstX, dstY, dstZ, width, height, depth, glFormat, image.data ); - } + } else { -} + _gl.texSubImage3D( glTarget, dstLevel, dstX, dstY, dstZ, width, height, depth, glFormat, glType, image ); -class PlaneBufferGeometry extends PlaneGeometry { // @deprecated, r144 + } - constructor( width, height, widthSegments, heightSegments ) { + } else { - console.warn( 'THREE.PlaneBufferGeometry has been renamed to THREE.PlaneGeometry.' ); - super( width, height, widthSegments, heightSegments ); + // copy data into the 2d texture + if ( srcTexture.isDataTexture ) { - } + _gl.texSubImage2D( _gl.TEXTURE_2D, dstLevel, dstX, dstY, width, height, glFormat, glType, image.data ); -} + } else if ( srcTexture.isCompressedTexture ) { -class PolyhedronBufferGeometry extends PolyhedronGeometry { // @deprecated, r144 + _gl.compressedTexSubImage2D( _gl.TEXTURE_2D, dstLevel, dstX, dstY, image.width, image.height, glFormat, image.data ); - constructor( vertices, indices, radius, detail ) { + } else { - console.warn( 'THREE.PolyhedronBufferGeometry has been renamed to THREE.PolyhedronGeometry.' ); - super( vertices, indices, radius, detail ); + _gl.texSubImage2D( _gl.TEXTURE_2D, dstLevel, dstX, dstY, width, height, glFormat, glType, image ); - } + } -} + } -class RingBufferGeometry extends RingGeometry { // @deprecated, r144 + } - constructor( innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength ) { + // reset values + _gl.pixelStorei( _gl.UNPACK_ROW_LENGTH, currentUnpackRowLen ); + _gl.pixelStorei( _gl.UNPACK_IMAGE_HEIGHT, currentUnpackImageHeight ); + _gl.pixelStorei( _gl.UNPACK_SKIP_PIXELS, currentUnpackSkipPixels ); + _gl.pixelStorei( _gl.UNPACK_SKIP_ROWS, currentUnpackSkipRows ); + _gl.pixelStorei( _gl.UNPACK_SKIP_IMAGES, currentUnpackSkipImages ); - console.warn( 'THREE.RingBufferGeometry has been renamed to THREE.RingGeometry.' ); - super( innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength ); + // Generate mipmaps only when copying level 0 + if ( dstLevel === 0 && dstTexture.generateMipmaps ) { - } + _gl.generateMipmap( glTarget ); -} + } -class ShapeBufferGeometry extends ShapeGeometry { // @deprecated, r144 + state.unbindTexture(); - constructor( shapes, curveSegments ) { + }; - console.warn( 'THREE.ShapeBufferGeometry has been renamed to THREE.ShapeGeometry.' ); - super( shapes, curveSegments ); + this.copyTextureToTexture3D = function ( srcTexture, dstTexture, srcRegion = null, dstPosition = null, level = 0 ) { - } + // @deprecated, r170 + warnOnce( 'WebGLRenderer: copyTextureToTexture3D function has been deprecated. Use "copyTextureToTexture" instead.' ); -} + return this.copyTextureToTexture( srcTexture, dstTexture, srcRegion, dstPosition, level ); -class SphereBufferGeometry extends SphereGeometry { // @deprecated, r144 + }; - constructor( radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength ) { + /** + * Initializes the given WebGLRenderTarget memory. Useful for initializing a render target so data + * can be copied into it using {@link WebGLRenderer#copyTextureToTexture} before it has been + * rendered to. + * + * @param {WebGLRenderTarget} target - The render target. + */ + this.initRenderTarget = function ( target ) { - console.warn( 'THREE.SphereBufferGeometry has been renamed to THREE.SphereGeometry.' ); - super( radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength ); + if ( properties.get( target ).__webglFramebuffer === undefined ) { - } + textures.setupRenderTarget( target ); -} + } -class TetrahedronBufferGeometry extends TetrahedronGeometry { // @deprecated, r144 + }; - constructor( radius, detail ) { + /** + * Initializes the given texture. Useful for preloading a texture rather than waiting until first + * render (which can cause noticeable lags due to decode and GPU upload overhead). + * + * @param {Texture} texture - The texture. + */ + this.initTexture = function ( texture ) { - console.warn( 'THREE.TetrahedronBufferGeometry has been renamed to THREE.TetrahedronGeometry.' ); - super( radius, detail ); + if ( texture.isCubeTexture ) { - } + textures.setTextureCube( texture, 0 ); -} + } else if ( texture.isData3DTexture ) { -class TorusBufferGeometry extends TorusGeometry { // @deprecated, r144 + textures.setTexture3D( texture, 0 ); - constructor( radius, tube, radialSegments, tubularSegments, arc ) { + } else if ( texture.isDataArrayTexture || texture.isCompressedArrayTexture ) { - console.warn( 'THREE.TorusBufferGeometry has been renamed to THREE.TorusGeometry.' ); - super( radius, tube, radialSegments, tubularSegments, arc ); + textures.setTexture2DArray( texture, 0 ); - } + } else { -} + textures.setTexture2D( texture, 0 ); -class TorusKnotBufferGeometry extends TorusKnotGeometry { // @deprecated, r144 + } - constructor( radius, tube, tubularSegments, radialSegments, p, q ) { + state.unbindTexture(); - console.warn( 'THREE.TorusKnotBufferGeometry has been renamed to THREE.TorusKnotGeometry.' ); - super( radius, tube, tubularSegments, radialSegments, p, q ); + }; - } + /** + * Can be used to reset the internal WebGL state. This method is mostly + * relevant for applications which share a single WebGL context across + * multiple WebGL libraries. + */ + this.resetState = function () { -} + _currentActiveCubeFace = 0; + _currentActiveMipmapLevel = 0; + _currentRenderTarget = null; + + state.reset(); + bindingStates.reset(); + + }; -class TubeBufferGeometry extends TubeGeometry { // @deprecated, r144 + if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) { - constructor( path, tubularSegments, radius, radialSegments, closed ) { + __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'observe', { detail: this } ) ); - console.warn( 'THREE.TubeBufferGeometry has been renamed to THREE.TubeGeometry.' ); - super( path, tubularSegments, radius, radialSegments, closed ); + } } -} + /** + * Defines the coordinate system of the renderer. + * + * In `WebGLRenderer`, the value is always `WebGLCoordinateSystem`. + * + * @type {WebGLCoordinateSystem|WebGPUCoordinateSystem} + * @default WebGLCoordinateSystem + * @readonly + */ + get coordinateSystem() { -if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) { + return WebGLCoordinateSystem; - __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'register', { detail: { - revision: REVISION, - } } ) ); + } -} + /** + * Defines the output color space of the renderer. + * + * @type {SRGBColorSpace|LinearSRGBColorSpace} + * @default SRGBColorSpace + */ + get outputColorSpace() { -if ( typeof window !== 'undefined' ) { + return this._outputColorSpace; - if ( window.__THREE__ ) { + } - console.warn( 'WARNING: Multiple instances of Three.js being imported.' ); + set outputColorSpace( colorSpace ) { - } else { + this._outputColorSpace = colorSpace; - window.__THREE__ = REVISION; + const gl = this.getContext(); + gl.drawingBufferColorSpace = ColorManagement._getDrawingBufferColorSpace( colorSpace ); + gl.unpackColorSpace = ColorManagement._getUnpackColorSpace(); } } -export { ACESFilmicToneMapping, AddEquation, AddOperation, AdditiveAnimationBlendMode, AdditiveBlending, AlphaFormat, AlwaysCompare, AlwaysDepth, AlwaysStencilFunc, AmbientLight, AmbientLightProbe, AnimationAction, AnimationClip, AnimationLoader, AnimationMixer, AnimationObjectGroup, AnimationUtils, ArcCurve, ArrayCamera, ArrowHelper, Audio, AudioAnalyser, AudioContext, AudioListener, AudioLoader, AxesHelper, BackSide, BasicDepthPacking, BasicShadowMap, Bone, BooleanKeyframeTrack, Box2, Box3, Box3Helper, BoxBufferGeometry, BoxGeometry, BoxHelper, BufferAttribute, BufferGeometry, BufferGeometryLoader, ByteType, Cache, Camera, CameraHelper, CanvasTexture, CapsuleBufferGeometry, CapsuleGeometry, CatmullRomCurve3, CineonToneMapping, CircleBufferGeometry, CircleGeometry, ClampToEdgeWrapping, Clock, Color, ColorKeyframeTrack, ColorManagement, CompressedArrayTexture, CompressedTexture, CompressedTextureLoader, ConeBufferGeometry, ConeGeometry, CubeCamera, CubeReflectionMapping, CubeRefractionMapping, CubeTexture, CubeTextureLoader, CubeUVReflectionMapping, CubicBezierCurve, CubicBezierCurve3, CubicInterpolant, CullFaceBack, CullFaceFront, CullFaceFrontBack, CullFaceNone, Curve, CurvePath, CustomBlending, CustomToneMapping, CylinderBufferGeometry, CylinderGeometry, Cylindrical, Data3DTexture, DataArrayTexture, DataTexture, DataTextureLoader, DataUtils, DecrementStencilOp, DecrementWrapStencilOp, DefaultLoadingManager, DepthFormat, DepthStencilFormat, DepthTexture, DirectionalLight, DirectionalLightHelper, DiscreteInterpolant, DisplayP3ColorSpace, DodecahedronBufferGeometry, DodecahedronGeometry, DoubleSide, DstAlphaFactor, DstColorFactor, DynamicCopyUsage, DynamicDrawUsage, DynamicReadUsage, EdgesGeometry, EllipseCurve, EqualCompare, EqualDepth, EqualStencilFunc, EquirectangularReflectionMapping, EquirectangularRefractionMapping, Euler, EventDispatcher, ExtrudeBufferGeometry, ExtrudeGeometry, FileLoader, Float16BufferAttribute, Float32BufferAttribute, Float64BufferAttribute, FloatType, Fog, FogExp2, FramebufferTexture, FrontSide, Frustum, GLBufferAttribute, GLSL1, GLSL3, GreaterCompare, GreaterDepth, GreaterEqualCompare, GreaterEqualDepth, GreaterEqualStencilFunc, GreaterStencilFunc, GridHelper, Group, HalfFloatType, HemisphereLight, HemisphereLightHelper, HemisphereLightProbe, IcosahedronBufferGeometry, IcosahedronGeometry, ImageBitmapLoader, ImageLoader, ImageUtils, IncrementStencilOp, IncrementWrapStencilOp, InstancedBufferAttribute, InstancedBufferGeometry, InstancedInterleavedBuffer, InstancedMesh, Int16BufferAttribute, Int32BufferAttribute, Int8BufferAttribute, IntType, InterleavedBuffer, InterleavedBufferAttribute, Interpolant, InterpolateDiscrete, InterpolateLinear, InterpolateSmooth, InvertStencilOp, KeepStencilOp, KeyframeTrack, LOD, LatheBufferGeometry, LatheGeometry, Layers, LessCompare, LessDepth, LessEqualCompare, LessEqualDepth, LessEqualStencilFunc, LessStencilFunc, Light, LightProbe, Line, Line3, LineBasicMaterial, LineCurve, LineCurve3, LineDashedMaterial, LineLoop, LineSegments, LinearEncoding, LinearFilter, LinearInterpolant, LinearMipMapLinearFilter, LinearMipMapNearestFilter, LinearMipmapLinearFilter, LinearMipmapNearestFilter, LinearSRGBColorSpace, LinearToneMapping, Loader, LoaderUtils, LoadingManager, LoopOnce, LoopPingPong, LoopRepeat, LuminanceAlphaFormat, LuminanceFormat, MOUSE, Material, MaterialLoader, MathUtils, Matrix3, Matrix4, MaxEquation, Mesh, MeshBasicMaterial, MeshDepthMaterial, MeshDistanceMaterial, MeshLambertMaterial, MeshMatcapMaterial, MeshNormalMaterial, MeshPhongMaterial, MeshPhysicalMaterial, MeshStandardMaterial, MeshToonMaterial, MinEquation, MirroredRepeatWrapping, MixOperation, MultiplyBlending, MultiplyOperation, NearestFilter, NearestMipMapLinearFilter, NearestMipMapNearestFilter, NearestMipmapLinearFilter, NearestMipmapNearestFilter, NeverCompare, NeverDepth, NeverStencilFunc, NoBlending, NoColorSpace, NoToneMapping, NormalAnimationBlendMode, NormalBlending, NotEqualCompare, NotEqualDepth, NotEqualStencilFunc, NumberKeyframeTrack, Object3D, ObjectLoader, ObjectSpaceNormalMap, OctahedronBufferGeometry, OctahedronGeometry, OneFactor, OneMinusDstAlphaFactor, OneMinusDstColorFactor, OneMinusSrcAlphaFactor, OneMinusSrcColorFactor, OrthographicCamera, PCFShadowMap, PCFSoftShadowMap, PMREMGenerator, Path, PerspectiveCamera, Plane, PlaneBufferGeometry, PlaneGeometry, PlaneHelper, PointLight, PointLightHelper, Points, PointsMaterial, PolarGridHelper, PolyhedronBufferGeometry, PolyhedronGeometry, PositionalAudio, PropertyBinding, PropertyMixer, QuadraticBezierCurve, QuadraticBezierCurve3, Quaternion, QuaternionKeyframeTrack, QuaternionLinearInterpolant, RED_GREEN_RGTC2_Format, RED_RGTC1_Format, REVISION, RGBADepthPacking, RGBAFormat, RGBAIntegerFormat, RGBA_ASTC_10x10_Format, RGBA_ASTC_10x5_Format, RGBA_ASTC_10x6_Format, RGBA_ASTC_10x8_Format, RGBA_ASTC_12x10_Format, RGBA_ASTC_12x12_Format, RGBA_ASTC_4x4_Format, RGBA_ASTC_5x4_Format, RGBA_ASTC_5x5_Format, RGBA_ASTC_6x5_Format, RGBA_ASTC_6x6_Format, RGBA_ASTC_8x5_Format, RGBA_ASTC_8x6_Format, RGBA_ASTC_8x8_Format, RGBA_BPTC_Format, RGBA_ETC2_EAC_Format, RGBA_PVRTC_2BPPV1_Format, RGBA_PVRTC_4BPPV1_Format, RGBA_S3TC_DXT1_Format, RGBA_S3TC_DXT3_Format, RGBA_S3TC_DXT5_Format, RGB_ETC1_Format, RGB_ETC2_Format, RGB_PVRTC_2BPPV1_Format, RGB_PVRTC_4BPPV1_Format, RGB_S3TC_DXT1_Format, RGFormat, RGIntegerFormat, RawShaderMaterial, Ray, Raycaster, RectAreaLight, RedFormat, RedIntegerFormat, ReinhardToneMapping, RepeatWrapping, ReplaceStencilOp, ReverseSubtractEquation, RingBufferGeometry, RingGeometry, SIGNED_RED_GREEN_RGTC2_Format, SIGNED_RED_RGTC1_Format, SRGBColorSpace, Scene, ShaderChunk, ShaderLib, ShaderMaterial, ShadowMaterial, Shape, ShapeBufferGeometry, ShapeGeometry, ShapePath, ShapeUtils, ShortType, Skeleton, SkeletonHelper, SkinnedMesh, Source, Sphere, SphereBufferGeometry, SphereGeometry, Spherical, SphericalHarmonics3, SplineCurve, SpotLight, SpotLightHelper, Sprite, SpriteMaterial, SrcAlphaFactor, SrcAlphaSaturateFactor, SrcColorFactor, StaticCopyUsage, StaticDrawUsage, StaticReadUsage, StereoCamera, StreamCopyUsage, StreamDrawUsage, StreamReadUsage, StringKeyframeTrack, SubtractEquation, SubtractiveBlending, TOUCH, TangentSpaceNormalMap, TetrahedronBufferGeometry, TetrahedronGeometry, Texture, TextureLoader, TorusBufferGeometry, TorusGeometry, TorusKnotBufferGeometry, TorusKnotGeometry, Triangle, TriangleFanDrawMode, TriangleStripDrawMode, TrianglesDrawMode, TubeBufferGeometry, TubeGeometry, TwoPassDoubleSide, UVMapping, Uint16BufferAttribute, Uint32BufferAttribute, Uint8BufferAttribute, Uint8ClampedBufferAttribute, Uniform, UniformsGroup, UniformsLib, UniformsUtils, UnsignedByteType, UnsignedInt248Type, UnsignedIntType, UnsignedShort4444Type, UnsignedShort5551Type, UnsignedShortType, VSMShadowMap, Vector2, Vector3, Vector4, VectorKeyframeTrack, VideoTexture, WebGL1Renderer, WebGL3DRenderTarget, WebGLArrayRenderTarget, WebGLCubeRenderTarget, WebGLMultipleRenderTargets, WebGLRenderTarget, WebGLRenderer, WebGLUtils, WireframeGeometry, WrapAroundEnding, ZeroCurvatureEnding, ZeroFactor, ZeroSlopeEnding, ZeroStencilOp, _SRGBAFormat, sRGBEncoding }; +export { ACESFilmicToneMapping, AddEquation, AddOperation, AdditiveBlending, AgXToneMapping, AlphaFormat, AlwaysCompare, AlwaysDepth, ArrayCamera, BackSide, BoxGeometry, BufferAttribute, BufferGeometry, ByteType, CineonToneMapping, ClampToEdgeWrapping, Color, ColorManagement, ConstantAlphaFactor, ConstantColorFactor, CubeReflectionMapping, CubeRefractionMapping, CubeTexture, CubeUVReflectionMapping, CullFaceBack, CullFaceFront, CullFaceNone, CustomBlending, CustomToneMapping, Data3DTexture, DataArrayTexture, DepthFormat, DepthStencilFormat, DepthTexture, DoubleSide, DstAlphaFactor, DstColorFactor, EqualCompare, EqualDepth, EquirectangularReflectionMapping, EquirectangularRefractionMapping, Euler, EventDispatcher, FloatType, FrontSide, Frustum, GLSL3, GreaterCompare, GreaterDepth, GreaterEqualCompare, GreaterEqualDepth, HalfFloatType, IntType, Layers, LessCompare, LessDepth, LessEqualCompare, LessEqualDepth, LinearFilter, LinearMipmapLinearFilter, LinearMipmapNearestFilter, LinearSRGBColorSpace, LinearToneMapping, LinearTransfer, Matrix3, Matrix4, MaxEquation, Mesh, MeshBasicMaterial, MeshDepthMaterial, MeshDistanceMaterial, MinEquation, MirroredRepeatWrapping, MixOperation, MultiplyBlending, MultiplyOperation, NearestFilter, NearestMipmapLinearFilter, NearestMipmapNearestFilter, NeutralToneMapping, NeverCompare, NeverDepth, NoBlending, NoColorSpace, NoToneMapping, NormalBlending, NotEqualCompare, NotEqualDepth, ObjectSpaceNormalMap, OneFactor, OneMinusConstantAlphaFactor, OneMinusConstantColorFactor, OneMinusDstAlphaFactor, OneMinusDstColorFactor, OneMinusSrcAlphaFactor, OneMinusSrcColorFactor, OrthographicCamera, PCFShadowMap, PCFSoftShadowMap, PMREMGenerator, PerspectiveCamera, Plane, PlaneGeometry, RED_GREEN_RGTC2_Format, RED_RGTC1_Format, REVISION, RGBADepthPacking, RGBAFormat, RGBAIntegerFormat, RGBA_ASTC_10x10_Format, RGBA_ASTC_10x5_Format, RGBA_ASTC_10x6_Format, RGBA_ASTC_10x8_Format, RGBA_ASTC_12x10_Format, RGBA_ASTC_12x12_Format, RGBA_ASTC_4x4_Format, RGBA_ASTC_5x4_Format, RGBA_ASTC_5x5_Format, RGBA_ASTC_6x5_Format, RGBA_ASTC_6x6_Format, RGBA_ASTC_8x5_Format, RGBA_ASTC_8x6_Format, RGBA_ASTC_8x8_Format, RGBA_BPTC_Format, RGBA_ETC2_EAC_Format, RGBA_PVRTC_2BPPV1_Format, RGBA_PVRTC_4BPPV1_Format, RGBA_S3TC_DXT1_Format, RGBA_S3TC_DXT3_Format, RGBA_S3TC_DXT5_Format, RGBFormat, RGB_BPTC_SIGNED_Format, RGB_BPTC_UNSIGNED_Format, RGB_ETC1_Format, RGB_ETC2_Format, RGB_PVRTC_2BPPV1_Format, RGB_PVRTC_4BPPV1_Format, RGB_S3TC_DXT1_Format, RGFormat, RGIntegerFormat, RedFormat, RedIntegerFormat, ReinhardToneMapping, RepeatWrapping, ReverseSubtractEquation, SIGNED_RED_GREEN_RGTC2_Format, SIGNED_RED_RGTC1_Format, SRGBColorSpace, SRGBTransfer, ShaderChunk, ShaderLib, ShaderMaterial, ShortType, SrcAlphaFactor, SrcAlphaSaturateFactor, SrcColorFactor, SubtractEquation, SubtractiveBlending, TangentSpaceNormalMap, Texture, Uint16BufferAttribute, Uint32BufferAttribute, UniformsLib, UniformsUtils, UnsignedByteType, UnsignedInt248Type, UnsignedInt5999Type, UnsignedIntType, UnsignedShort4444Type, UnsignedShort5551Type, UnsignedShortType, VSMShadowMap, Vector2, Vector3, Vector4, WebGLCoordinateSystem, WebGLCubeRenderTarget, WebGLRenderTarget, WebGLRenderer, WebGLUtils, WebXRController, ZeroFactor, createCanvasElement }; diff --git a/build/three.module.min.js b/build/three.module.min.js index a7c19aada1fedb..3c78500da68c1b 100644 --- a/build/three.module.min.js +++ b/build/three.module.min.js @@ -1,6 +1,6 @@ /** * @license - * Copyright 2010-2023 Three.js Authors + * Copyright 2010-2025 Three.js Authors * SPDX-License-Identifier: MIT */ -const t="153dev",e={LEFT:0,MIDDLE:1,RIGHT:2,ROTATE:0,DOLLY:1,PAN:2},n={ROTATE:0,PAN:1,DOLLY_PAN:2,DOLLY_ROTATE:3},i=0,r=1,s=2,a=3,o=0,l=1,c=2,h=3,u=0,d=1,p=2,m=2,f=0,g=1,v=2,_=3,y=4,x=5,M=100,S=101,b=102,E=103,T=104,w=200,A=201,R=202,C=203,P=204,L=205,I=206,U=207,N=208,D=209,O=210,F=0,B=1,z=2,H=3,k=4,V=5,G=6,W=7,X=0,j=1,q=2,Y=0,Z=1,J=2,K=3,$=4,Q=5,tt=300,et=301,nt=302,it=303,rt=304,st=306,at=1e3,ot=1001,lt=1002,ct=1003,ht=1004,ut=1004,dt=1005,pt=1005,mt=1006,ft=1007,gt=1007,vt=1008,_t=1008,yt=1009,xt=1010,Mt=1011,St=1012,bt=1013,Et=1014,Tt=1015,wt=1016,At=1017,Rt=1018,Ct=1020,Pt=1021,Lt=1023,It=1024,Ut=1025,Nt=1026,Dt=1027,Ot=1028,Ft=1029,Bt=1030,zt=1031,Ht=1033,kt=33776,Vt=33777,Gt=33778,Wt=33779,Xt=35840,jt=35841,qt=35842,Yt=35843,Zt=36196,Jt=37492,Kt=37496,$t=37808,Qt=37809,te=37810,ee=37811,ne=37812,ie=37813,re=37814,se=37815,ae=37816,oe=37817,le=37818,ce=37819,he=37820,ue=37821,de=36492,pe=36283,me=36284,fe=36285,ge=36286,ve=2200,_e=2201,ye=2202,xe=2300,Me=2301,Se=2302,be=2400,Ee=2401,Te=2402,we=2500,Ae=2501,Re=0,Ce=1,Pe=2,Le=3e3,Ie=3001,Ue=3200,Ne=3201,De=0,Oe=1,Fe="",Be="srgb",ze="srgb-linear",He="display-p3",ke=0,Ve=7680,Ge=7681,We=7682,Xe=7683,je=34055,qe=34056,Ye=5386,Ze=512,Je=513,Ke=514,$e=515,Qe=516,tn=517,en=518,nn=519,rn=512,sn=513,an=514,on=515,ln=516,cn=517,hn=518,un=519,dn=35044,pn=35048,mn=35040,fn=35045,gn=35049,vn=35041,_n=35046,yn=35050,xn=35042,Mn="100",Sn="300 es",bn=1035;class En{addEventListener(t,e){void 0===this._listeners&&(this._listeners={});const n=this._listeners;void 0===n[t]&&(n[t]=[]),-1===n[t].indexOf(e)&&n[t].push(e)}hasEventListener(t,e){if(void 0===this._listeners)return!1;const n=this._listeners;return void 0!==n[t]&&-1!==n[t].indexOf(e)}removeEventListener(t,e){if(void 0===this._listeners)return;const n=this._listeners[t];if(void 0!==n){const t=n.indexOf(e);-1!==t&&n.splice(t,1)}}dispatchEvent(t){if(void 0===this._listeners)return;const e=this._listeners[t.type];if(void 0!==e){t.target=this;const n=e.slice(0);for(let e=0,i=n.length;e>8&255]+Tn[t>>16&255]+Tn[t>>24&255]+"-"+Tn[255&e]+Tn[e>>8&255]+"-"+Tn[e>>16&15|64]+Tn[e>>24&255]+"-"+Tn[63&n|128]+Tn[n>>8&255]+"-"+Tn[n>>16&255]+Tn[n>>24&255]+Tn[255&i]+Tn[i>>8&255]+Tn[i>>16&255]+Tn[i>>24&255]).toLowerCase()}function Pn(t,e,n){return Math.max(e,Math.min(n,t))}function Ln(t,e){return(t%e+e)%e}function In(t,e,n){return(1-n)*t+n*e}function Un(t){return 0==(t&t-1)&&0!==t}function Nn(t){return Math.pow(2,Math.ceil(Math.log(t)/Math.LN2))}function Dn(t){return Math.pow(2,Math.floor(Math.log(t)/Math.LN2))}function On(t,e){switch(e.constructor){case Float32Array:return t;case Uint32Array:return t/4294967295;case Uint16Array:return t/65535;case Uint8Array:return t/255;case Int32Array:return Math.max(t/2147483647,-1);case Int16Array:return Math.max(t/32767,-1);case Int8Array:return Math.max(t/127,-1);default:throw new Error("Invalid component type.")}}function Fn(t,e){switch(e.constructor){case Float32Array:return t;case Uint32Array:return Math.round(4294967295*t);case Uint16Array:return Math.round(65535*t);case Uint8Array:return Math.round(255*t);case Int32Array:return Math.round(2147483647*t);case Int16Array:return Math.round(32767*t);case Int8Array:return Math.round(127*t);default:throw new Error("Invalid component type.")}}const Bn={DEG2RAD:An,RAD2DEG:Rn,generateUUID:Cn,clamp:Pn,euclideanModulo:Ln,mapLinear:function(t,e,n,i,r){return i+(t-e)*(r-i)/(n-e)},inverseLerp:function(t,e,n){return t!==e?(n-t)/(e-t):0},lerp:In,damp:function(t,e,n,i){return In(t,e,1-Math.exp(-n*i))},pingpong:function(t,e=1){return e-Math.abs(Ln(t,2*e)-e)},smoothstep:function(t,e,n){return t<=e?0:t>=n?1:(t=(t-e)/(n-e))*t*(3-2*t)},smootherstep:function(t,e,n){return t<=e?0:t>=n?1:(t=(t-e)/(n-e))*t*t*(t*(6*t-15)+10)},randInt:function(t,e){return t+Math.floor(Math.random()*(e-t+1))},randFloat:function(t,e){return t+Math.random()*(e-t)},randFloatSpread:function(t){return t*(.5-Math.random())},seededRandom:function(t){void 0!==t&&(wn=t);let e=wn+=1831565813;return e=Math.imul(e^e>>>15,1|e),e^=e+Math.imul(e^e>>>7,61|e),((e^e>>>14)>>>0)/4294967296},degToRad:function(t){return t*An},radToDeg:function(t){return t*Rn},isPowerOfTwo:Un,ceilPowerOfTwo:Nn,floorPowerOfTwo:Dn,setQuaternionFromProperEuler:function(t,e,n,i,r){const s=Math.cos,a=Math.sin,o=s(n/2),l=a(n/2),c=s((e+i)/2),h=a((e+i)/2),u=s((e-i)/2),d=a((e-i)/2),p=s((i-e)/2),m=a((i-e)/2);switch(r){case"XYX":t.set(o*h,l*u,l*d,o*c);break;case"YZY":t.set(l*d,o*h,l*u,o*c);break;case"ZXZ":t.set(l*u,l*d,o*h,o*c);break;case"XZX":t.set(o*h,l*m,l*p,o*c);break;case"YXY":t.set(l*p,o*h,l*m,o*c);break;case"ZYZ":t.set(l*m,l*p,o*h,o*c);break;default:console.warn("THREE.MathUtils: .setQuaternionFromProperEuler() encountered an unknown order: "+r)}},normalize:Fn,denormalize:On};class zn{constructor(t=0,e=0){zn.prototype.isVector2=!0,this.x=t,this.y=e}get width(){return this.x}set width(t){this.x=t}get height(){return this.y}set height(t){this.y=t}set(t,e){return this.x=t,this.y=e,this}setScalar(t){return this.x=t,this.y=t,this}setX(t){return this.x=t,this}setY(t){return this.y=t,this}setComponent(t,e){switch(t){case 0:this.x=e;break;case 1:this.y=e;break;default:throw new Error("index is out of range: "+t)}return this}getComponent(t){switch(t){case 0:return this.x;case 1:return this.y;default:throw new Error("index is out of range: "+t)}}clone(){return new this.constructor(this.x,this.y)}copy(t){return this.x=t.x,this.y=t.y,this}add(t){return this.x+=t.x,this.y+=t.y,this}addScalar(t){return this.x+=t,this.y+=t,this}addVectors(t,e){return this.x=t.x+e.x,this.y=t.y+e.y,this}addScaledVector(t,e){return this.x+=t.x*e,this.y+=t.y*e,this}sub(t){return this.x-=t.x,this.y-=t.y,this}subScalar(t){return this.x-=t,this.y-=t,this}subVectors(t,e){return this.x=t.x-e.x,this.y=t.y-e.y,this}multiply(t){return this.x*=t.x,this.y*=t.y,this}multiplyScalar(t){return this.x*=t,this.y*=t,this}divide(t){return this.x/=t.x,this.y/=t.y,this}divideScalar(t){return this.multiplyScalar(1/t)}applyMatrix3(t){const e=this.x,n=this.y,i=t.elements;return this.x=i[0]*e+i[3]*n+i[6],this.y=i[1]*e+i[4]*n+i[7],this}min(t){return this.x=Math.min(this.x,t.x),this.y=Math.min(this.y,t.y),this}max(t){return this.x=Math.max(this.x,t.x),this.y=Math.max(this.y,t.y),this}clamp(t,e){return this.x=Math.max(t.x,Math.min(e.x,this.x)),this.y=Math.max(t.y,Math.min(e.y,this.y)),this}clampScalar(t,e){return this.x=Math.max(t,Math.min(e,this.x)),this.y=Math.max(t,Math.min(e,this.y)),this}clampLength(t,e){const n=this.length();return this.divideScalar(n||1).multiplyScalar(Math.max(t,Math.min(e,n)))}floor(){return this.x=Math.floor(this.x),this.y=Math.floor(this.y),this}ceil(){return this.x=Math.ceil(this.x),this.y=Math.ceil(this.y),this}round(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this}roundToZero(){return this.x=this.x<0?Math.ceil(this.x):Math.floor(this.x),this.y=this.y<0?Math.ceil(this.y):Math.floor(this.y),this}negate(){return this.x=-this.x,this.y=-this.y,this}dot(t){return this.x*t.x+this.y*t.y}cross(t){return this.x*t.y-this.y*t.x}lengthSq(){return this.x*this.x+this.y*this.y}length(){return Math.sqrt(this.x*this.x+this.y*this.y)}manhattanLength(){return Math.abs(this.x)+Math.abs(this.y)}normalize(){return this.divideScalar(this.length()||1)}angle(){return Math.atan2(-this.y,-this.x)+Math.PI}angleTo(t){const e=Math.sqrt(this.lengthSq()*t.lengthSq());if(0===e)return Math.PI/2;const n=this.dot(t)/e;return Math.acos(Pn(n,-1,1))}distanceTo(t){return Math.sqrt(this.distanceToSquared(t))}distanceToSquared(t){const e=this.x-t.x,n=this.y-t.y;return e*e+n*n}manhattanDistanceTo(t){return Math.abs(this.x-t.x)+Math.abs(this.y-t.y)}setLength(t){return this.normalize().multiplyScalar(t)}lerp(t,e){return this.x+=(t.x-this.x)*e,this.y+=(t.y-this.y)*e,this}lerpVectors(t,e,n){return this.x=t.x+(e.x-t.x)*n,this.y=t.y+(e.y-t.y)*n,this}equals(t){return t.x===this.x&&t.y===this.y}fromArray(t,e=0){return this.x=t[e],this.y=t[e+1],this}toArray(t=[],e=0){return t[e]=this.x,t[e+1]=this.y,t}fromBufferAttribute(t,e){return this.x=t.getX(e),this.y=t.getY(e),this}rotateAround(t,e){const n=Math.cos(e),i=Math.sin(e),r=this.x-t.x,s=this.y-t.y;return this.x=r*n-s*i+t.x,this.y=r*i+s*n+t.y,this}random(){return this.x=Math.random(),this.y=Math.random(),this}*[Symbol.iterator](){yield this.x,yield this.y}}class Hn{constructor(t,e,n,i,r,s,a,o,l){Hn.prototype.isMatrix3=!0,this.elements=[1,0,0,0,1,0,0,0,1],void 0!==t&&this.set(t,e,n,i,r,s,a,o,l)}set(t,e,n,i,r,s,a,o,l){const c=this.elements;return c[0]=t,c[1]=i,c[2]=a,c[3]=e,c[4]=r,c[5]=o,c[6]=n,c[7]=s,c[8]=l,this}identity(){return this.set(1,0,0,0,1,0,0,0,1),this}copy(t){const e=this.elements,n=t.elements;return e[0]=n[0],e[1]=n[1],e[2]=n[2],e[3]=n[3],e[4]=n[4],e[5]=n[5],e[6]=n[6],e[7]=n[7],e[8]=n[8],this}extractBasis(t,e,n){return t.setFromMatrix3Column(this,0),e.setFromMatrix3Column(this,1),n.setFromMatrix3Column(this,2),this}setFromMatrix4(t){const e=t.elements;return this.set(e[0],e[4],e[8],e[1],e[5],e[9],e[2],e[6],e[10]),this}multiply(t){return this.multiplyMatrices(this,t)}premultiply(t){return this.multiplyMatrices(t,this)}multiplyMatrices(t,e){const n=t.elements,i=e.elements,r=this.elements,s=n[0],a=n[3],o=n[6],l=n[1],c=n[4],h=n[7],u=n[2],d=n[5],p=n[8],m=i[0],f=i[3],g=i[6],v=i[1],_=i[4],y=i[7],x=i[2],M=i[5],S=i[8];return r[0]=s*m+a*v+o*x,r[3]=s*f+a*_+o*M,r[6]=s*g+a*y+o*S,r[1]=l*m+c*v+h*x,r[4]=l*f+c*_+h*M,r[7]=l*g+c*y+h*S,r[2]=u*m+d*v+p*x,r[5]=u*f+d*_+p*M,r[8]=u*g+d*y+p*S,this}multiplyScalar(t){const e=this.elements;return e[0]*=t,e[3]*=t,e[6]*=t,e[1]*=t,e[4]*=t,e[7]*=t,e[2]*=t,e[5]*=t,e[8]*=t,this}determinant(){const t=this.elements,e=t[0],n=t[1],i=t[2],r=t[3],s=t[4],a=t[5],o=t[6],l=t[7],c=t[8];return e*s*c-e*a*l-n*r*c+n*a*o+i*r*l-i*s*o}invert(){const t=this.elements,e=t[0],n=t[1],i=t[2],r=t[3],s=t[4],a=t[5],o=t[6],l=t[7],c=t[8],h=c*s-a*l,u=a*o-c*r,d=l*r-s*o,p=e*h+n*u+i*d;if(0===p)return this.set(0,0,0,0,0,0,0,0,0);const m=1/p;return t[0]=h*m,t[1]=(i*l-c*n)*m,t[2]=(a*n-i*s)*m,t[3]=u*m,t[4]=(c*e-i*o)*m,t[5]=(i*r-a*e)*m,t[6]=d*m,t[7]=(n*o-l*e)*m,t[8]=(s*e-n*r)*m,this}transpose(){let t;const e=this.elements;return t=e[1],e[1]=e[3],e[3]=t,t=e[2],e[2]=e[6],e[6]=t,t=e[5],e[5]=e[7],e[7]=t,this}getNormalMatrix(t){return this.setFromMatrix4(t).invert().transpose()}transposeIntoArray(t){const e=this.elements;return t[0]=e[0],t[1]=e[3],t[2]=e[6],t[3]=e[1],t[4]=e[4],t[5]=e[7],t[6]=e[2],t[7]=e[5],t[8]=e[8],this}setUvTransform(t,e,n,i,r,s,a){const o=Math.cos(r),l=Math.sin(r);return this.set(n*o,n*l,-n*(o*s+l*a)+s+t,-i*l,i*o,-i*(-l*s+o*a)+a+e,0,0,1),this}scale(t,e){return this.premultiply(kn.makeScale(t,e)),this}rotate(t){return this.premultiply(kn.makeRotation(-t)),this}translate(t,e){return this.premultiply(kn.makeTranslation(t,e)),this}makeTranslation(t,e){return this.set(1,0,t,0,1,e,0,0,1),this}makeRotation(t){const e=Math.cos(t),n=Math.sin(t);return this.set(e,-n,0,n,e,0,0,0,1),this}makeScale(t,e){return this.set(t,0,0,0,e,0,0,0,1),this}equals(t){const e=this.elements,n=t.elements;for(let t=0;t<9;t++)if(e[t]!==n[t])return!1;return!0}fromArray(t,e=0){for(let n=0;n<9;n++)this.elements[n]=t[n+e];return this}toArray(t=[],e=0){const n=this.elements;return t[e]=n[0],t[e+1]=n[1],t[e+2]=n[2],t[e+3]=n[3],t[e+4]=n[4],t[e+5]=n[5],t[e+6]=n[6],t[e+7]=n[7],t[e+8]=n[8],t}clone(){return(new this.constructor).fromArray(this.elements)}}const kn=new Hn;function Vn(t){for(let e=t.length-1;e>=0;--e)if(t[e]>=65535)return!0;return!1}const Gn={Int8Array:Int8Array,Uint8Array:Uint8Array,Uint8ClampedArray:Uint8ClampedArray,Int16Array:Int16Array,Uint16Array:Uint16Array,Int32Array:Int32Array,Uint32Array:Uint32Array,Float32Array:Float32Array,Float64Array:Float64Array};function Wn(t,e){return new Gn[t](e)}function Xn(t){return document.createElementNS("http://www.w3.org/1999/xhtml",t)}const jn={};function qn(t){t in jn||(jn[t]=!0,console.warn(t))}function Yn(t){return t<.04045?.0773993808*t:Math.pow(.9478672986*t+.0521327014,2.4)}function Zn(t){return t<.0031308?12.92*t:1.055*Math.pow(t,.41666)-.055}const Jn=(new Hn).fromArray([.8224621,.0331941,.0170827,.177538,.9668058,.0723974,-1e-7,1e-7,.9105199]),Kn=(new Hn).fromArray([1.2249401,-.0420569,-.0196376,-.2249404,1.0420571,-.0786361,1e-7,0,1.0982735]);const $n={[ze]:t=>t,[Be]:t=>t.convertSRGBToLinear(),[He]:function(t){return t.convertSRGBToLinear().applyMatrix3(Kn)}},Qn={[ze]:t=>t,[Be]:t=>t.convertLinearToSRGB(),[He]:function(t){return t.applyMatrix3(Jn).convertLinearToSRGB()}},ti={enabled:!0,get legacyMode(){return console.warn("THREE.ColorManagement: .legacyMode=false renamed to .enabled=true in r150."),!this.enabled},set legacyMode(t){console.warn("THREE.ColorManagement: .legacyMode=false renamed to .enabled=true in r150."),this.enabled=!t},get workingColorSpace(){return ze},set workingColorSpace(t){console.warn("THREE.ColorManagement: .workingColorSpace is readonly.")},convert:function(t,e,n){if(!1===this.enabled||e===n||!e||!n)return t;const i=$n[e],r=Qn[n];if(void 0===i||void 0===r)throw new Error(`Unsupported color space conversion, "${e}" to "${n}".`);return r(i(t))},fromWorkingColorSpace:function(t,e){return this.convert(t,this.workingColorSpace,e)},toWorkingColorSpace:function(t,e){return this.convert(t,e,this.workingColorSpace)}};let ei;class ni{static getDataURL(t){if(/^data:/i.test(t.src))return t.src;if("undefined"==typeof HTMLCanvasElement)return t.src;let e;if(t instanceof HTMLCanvasElement)e=t;else{void 0===ei&&(ei=Xn("canvas")),ei.width=t.width,ei.height=t.height;const n=ei.getContext("2d");t instanceof ImageData?n.putImageData(t,0,0):n.drawImage(t,0,0,t.width,t.height),e=ei}return e.width>2048||e.height>2048?(console.warn("THREE.ImageUtils.getDataURL: Image converted to jpg for performance reasons",t),e.toDataURL("image/jpeg",.6)):e.toDataURL("image/png")}static sRGBToLinear(t){if("undefined"!=typeof HTMLImageElement&&t instanceof HTMLImageElement||"undefined"!=typeof HTMLCanvasElement&&t instanceof HTMLCanvasElement||"undefined"!=typeof ImageBitmap&&t instanceof ImageBitmap){const e=Xn("canvas");e.width=t.width,e.height=t.height;const n=e.getContext("2d");n.drawImage(t,0,0,t.width,t.height);const i=n.getImageData(0,0,t.width,t.height),r=i.data;for(let t=0;t0&&(n.userData=this.userData),e||(t.textures[this.uuid]=n),n}dispose(){this.dispatchEvent({type:"dispose"})}transformUv(t){if(this.mapping!==tt)return t;if(t.applyMatrix3(this.matrix),t.x<0||t.x>1)switch(this.wrapS){case at:t.x=t.x-Math.floor(t.x);break;case ot:t.x=t.x<0?0:1;break;case lt:1===Math.abs(Math.floor(t.x)%2)?t.x=Math.ceil(t.x)-t.x:t.x=t.x-Math.floor(t.x)}if(t.y<0||t.y>1)switch(this.wrapT){case at:t.y=t.y-Math.floor(t.y);break;case ot:t.y=t.y<0?0:1;break;case lt:1===Math.abs(Math.floor(t.y)%2)?t.y=Math.ceil(t.y)-t.y:t.y=t.y-Math.floor(t.y)}return this.flipY&&(t.y=1-t.y),t}set needsUpdate(t){!0===t&&(this.version++,this.source.needsUpdate=!0)}get encoding(){return qn("THREE.Texture: Property .encoding has been replaced by .colorSpace."),this.colorSpace===Be?Ie:Le}set encoding(t){qn("THREE.Texture: Property .encoding has been replaced by .colorSpace."),this.colorSpace=t===Ie?Be:Fe}}oi.DEFAULT_IMAGE=null,oi.DEFAULT_MAPPING=tt,oi.DEFAULT_ANISOTROPY=1;class li{constructor(t=0,e=0,n=0,i=1){li.prototype.isVector4=!0,this.x=t,this.y=e,this.z=n,this.w=i}get width(){return this.z}set width(t){this.z=t}get height(){return this.w}set height(t){this.w=t}set(t,e,n,i){return this.x=t,this.y=e,this.z=n,this.w=i,this}setScalar(t){return this.x=t,this.y=t,this.z=t,this.w=t,this}setX(t){return this.x=t,this}setY(t){return this.y=t,this}setZ(t){return this.z=t,this}setW(t){return this.w=t,this}setComponent(t,e){switch(t){case 0:this.x=e;break;case 1:this.y=e;break;case 2:this.z=e;break;case 3:this.w=e;break;default:throw new Error("index is out of range: "+t)}return this}getComponent(t){switch(t){case 0:return this.x;case 1:return this.y;case 2:return this.z;case 3:return this.w;default:throw new Error("index is out of range: "+t)}}clone(){return new this.constructor(this.x,this.y,this.z,this.w)}copy(t){return this.x=t.x,this.y=t.y,this.z=t.z,this.w=void 0!==t.w?t.w:1,this}add(t){return this.x+=t.x,this.y+=t.y,this.z+=t.z,this.w+=t.w,this}addScalar(t){return this.x+=t,this.y+=t,this.z+=t,this.w+=t,this}addVectors(t,e){return this.x=t.x+e.x,this.y=t.y+e.y,this.z=t.z+e.z,this.w=t.w+e.w,this}addScaledVector(t,e){return this.x+=t.x*e,this.y+=t.y*e,this.z+=t.z*e,this.w+=t.w*e,this}sub(t){return this.x-=t.x,this.y-=t.y,this.z-=t.z,this.w-=t.w,this}subScalar(t){return this.x-=t,this.y-=t,this.z-=t,this.w-=t,this}subVectors(t,e){return this.x=t.x-e.x,this.y=t.y-e.y,this.z=t.z-e.z,this.w=t.w-e.w,this}multiply(t){return this.x*=t.x,this.y*=t.y,this.z*=t.z,this.w*=t.w,this}multiplyScalar(t){return this.x*=t,this.y*=t,this.z*=t,this.w*=t,this}applyMatrix4(t){const e=this.x,n=this.y,i=this.z,r=this.w,s=t.elements;return this.x=s[0]*e+s[4]*n+s[8]*i+s[12]*r,this.y=s[1]*e+s[5]*n+s[9]*i+s[13]*r,this.z=s[2]*e+s[6]*n+s[10]*i+s[14]*r,this.w=s[3]*e+s[7]*n+s[11]*i+s[15]*r,this}divideScalar(t){return this.multiplyScalar(1/t)}setAxisAngleFromQuaternion(t){this.w=2*Math.acos(t.w);const e=Math.sqrt(1-t.w*t.w);return e<1e-4?(this.x=1,this.y=0,this.z=0):(this.x=t.x/e,this.y=t.y/e,this.z=t.z/e),this}setAxisAngleFromRotationMatrix(t){let e,n,i,r;const s=.01,a=.1,o=t.elements,l=o[0],c=o[4],h=o[8],u=o[1],d=o[5],p=o[9],m=o[2],f=o[6],g=o[10];if(Math.abs(c-u)o&&t>v?tv?o=0?1:-1,i=1-e*e;if(i>Number.EPSILON){const r=Math.sqrt(i),s=Math.atan2(r,e*n);t=Math.sin(t*s)/r,a=Math.sin(a*s)/r}const r=a*n;if(o=o*t+u*r,l=l*t+d*r,c=c*t+p*r,h=h*t+m*r,t===1-a){const t=1/Math.sqrt(o*o+l*l+c*c+h*h);o*=t,l*=t,c*=t,h*=t}}t[e]=o,t[e+1]=l,t[e+2]=c,t[e+3]=h}static multiplyQuaternionsFlat(t,e,n,i,r,s){const a=n[i],o=n[i+1],l=n[i+2],c=n[i+3],h=r[s],u=r[s+1],d=r[s+2],p=r[s+3];return t[e]=a*p+c*h+o*d-l*u,t[e+1]=o*p+c*u+l*h-a*d,t[e+2]=l*p+c*d+a*u-o*h,t[e+3]=c*p-a*h-o*u-l*d,t}get x(){return this._x}set x(t){this._x=t,this._onChangeCallback()}get y(){return this._y}set y(t){this._y=t,this._onChangeCallback()}get z(){return this._z}set z(t){this._z=t,this._onChangeCallback()}get w(){return this._w}set w(t){this._w=t,this._onChangeCallback()}set(t,e,n,i){return this._x=t,this._y=e,this._z=n,this._w=i,this._onChangeCallback(),this}clone(){return new this.constructor(this._x,this._y,this._z,this._w)}copy(t){return this._x=t.x,this._y=t.y,this._z=t.z,this._w=t.w,this._onChangeCallback(),this}setFromEuler(t,e){const n=t._x,i=t._y,r=t._z,s=t._order,a=Math.cos,o=Math.sin,l=a(n/2),c=a(i/2),h=a(r/2),u=o(n/2),d=o(i/2),p=o(r/2);switch(s){case"XYZ":this._x=u*c*h+l*d*p,this._y=l*d*h-u*c*p,this._z=l*c*p+u*d*h,this._w=l*c*h-u*d*p;break;case"YXZ":this._x=u*c*h+l*d*p,this._y=l*d*h-u*c*p,this._z=l*c*p-u*d*h,this._w=l*c*h+u*d*p;break;case"ZXY":this._x=u*c*h-l*d*p,this._y=l*d*h+u*c*p,this._z=l*c*p+u*d*h,this._w=l*c*h-u*d*p;break;case"ZYX":this._x=u*c*h-l*d*p,this._y=l*d*h+u*c*p,this._z=l*c*p-u*d*h,this._w=l*c*h+u*d*p;break;case"YZX":this._x=u*c*h+l*d*p,this._y=l*d*h+u*c*p,this._z=l*c*p-u*d*h,this._w=l*c*h-u*d*p;break;case"XZY":this._x=u*c*h-l*d*p,this._y=l*d*h-u*c*p,this._z=l*c*p+u*d*h,this._w=l*c*h+u*d*p;break;default:console.warn("THREE.Quaternion: .setFromEuler() encountered an unknown order: "+s)}return!1!==e&&this._onChangeCallback(),this}setFromAxisAngle(t,e){const n=e/2,i=Math.sin(n);return this._x=t.x*i,this._y=t.y*i,this._z=t.z*i,this._w=Math.cos(n),this._onChangeCallback(),this}setFromRotationMatrix(t){const e=t.elements,n=e[0],i=e[4],r=e[8],s=e[1],a=e[5],o=e[9],l=e[2],c=e[6],h=e[10],u=n+a+h;if(u>0){const t=.5/Math.sqrt(u+1);this._w=.25/t,this._x=(c-o)*t,this._y=(r-l)*t,this._z=(s-i)*t}else if(n>a&&n>h){const t=2*Math.sqrt(1+n-a-h);this._w=(c-o)/t,this._x=.25*t,this._y=(i+s)/t,this._z=(r+l)/t}else if(a>h){const t=2*Math.sqrt(1+a-n-h);this._w=(r-l)/t,this._x=(i+s)/t,this._y=.25*t,this._z=(o+c)/t}else{const t=2*Math.sqrt(1+h-n-a);this._w=(s-i)/t,this._x=(r+l)/t,this._y=(o+c)/t,this._z=.25*t}return this._onChangeCallback(),this}setFromUnitVectors(t,e){let n=t.dot(e)+1;return nMath.abs(t.z)?(this._x=-t.y,this._y=t.x,this._z=0,this._w=n):(this._x=0,this._y=-t.z,this._z=t.y,this._w=n)):(this._x=t.y*e.z-t.z*e.y,this._y=t.z*e.x-t.x*e.z,this._z=t.x*e.y-t.y*e.x,this._w=n),this.normalize()}angleTo(t){return 2*Math.acos(Math.abs(Pn(this.dot(t),-1,1)))}rotateTowards(t,e){const n=this.angleTo(t);if(0===n)return this;const i=Math.min(1,e/n);return this.slerp(t,i),this}identity(){return this.set(0,0,0,1)}invert(){return this.conjugate()}conjugate(){return this._x*=-1,this._y*=-1,this._z*=-1,this._onChangeCallback(),this}dot(t){return this._x*t._x+this._y*t._y+this._z*t._z+this._w*t._w}lengthSq(){return this._x*this._x+this._y*this._y+this._z*this._z+this._w*this._w}length(){return Math.sqrt(this._x*this._x+this._y*this._y+this._z*this._z+this._w*this._w)}normalize(){let t=this.length();return 0===t?(this._x=0,this._y=0,this._z=0,this._w=1):(t=1/t,this._x=this._x*t,this._y=this._y*t,this._z=this._z*t,this._w=this._w*t),this._onChangeCallback(),this}multiply(t){return this.multiplyQuaternions(this,t)}premultiply(t){return this.multiplyQuaternions(t,this)}multiplyQuaternions(t,e){const n=t._x,i=t._y,r=t._z,s=t._w,a=e._x,o=e._y,l=e._z,c=e._w;return this._x=n*c+s*a+i*l-r*o,this._y=i*c+s*o+r*a-n*l,this._z=r*c+s*l+n*o-i*a,this._w=s*c-n*a-i*o-r*l,this._onChangeCallback(),this}slerp(t,e){if(0===e)return this;if(1===e)return this.copy(t);const n=this._x,i=this._y,r=this._z,s=this._w;let a=s*t._w+n*t._x+i*t._y+r*t._z;if(a<0?(this._w=-t._w,this._x=-t._x,this._y=-t._y,this._z=-t._z,a=-a):this.copy(t),a>=1)return this._w=s,this._x=n,this._y=i,this._z=r,this;const o=1-a*a;if(o<=Number.EPSILON){const t=1-e;return this._w=t*s+e*this._w,this._x=t*n+e*this._x,this._y=t*i+e*this._y,this._z=t*r+e*this._z,this.normalize(),this._onChangeCallback(),this}const l=Math.sqrt(o),c=Math.atan2(l,a),h=Math.sin((1-e)*c)/l,u=Math.sin(e*c)/l;return this._w=s*h+this._w*u,this._x=n*h+this._x*u,this._y=i*h+this._y*u,this._z=r*h+this._z*u,this._onChangeCallback(),this}slerpQuaternions(t,e,n){return this.copy(t).slerp(e,n)}random(){const t=Math.random(),e=Math.sqrt(1-t),n=Math.sqrt(t),i=2*Math.PI*Math.random(),r=2*Math.PI*Math.random();return this.set(e*Math.cos(i),n*Math.sin(r),n*Math.cos(r),e*Math.sin(i))}equals(t){return t._x===this._x&&t._y===this._y&&t._z===this._z&&t._w===this._w}fromArray(t,e=0){return this._x=t[e],this._y=t[e+1],this._z=t[e+2],this._w=t[e+3],this._onChangeCallback(),this}toArray(t=[],e=0){return t[e]=this._x,t[e+1]=this._y,t[e+2]=this._z,t[e+3]=this._w,t}fromBufferAttribute(t,e){return this._x=t.getX(e),this._y=t.getY(e),this._z=t.getZ(e),this._w=t.getW(e),this}toJSON(){return this.toArray()}_onChange(t){return this._onChangeCallback=t,this}_onChangeCallback(){}*[Symbol.iterator](){yield this._x,yield this._y,yield this._z,yield this._w}}class gi{constructor(t=0,e=0,n=0){gi.prototype.isVector3=!0,this.x=t,this.y=e,this.z=n}set(t,e,n){return void 0===n&&(n=this.z),this.x=t,this.y=e,this.z=n,this}setScalar(t){return this.x=t,this.y=t,this.z=t,this}setX(t){return this.x=t,this}setY(t){return this.y=t,this}setZ(t){return this.z=t,this}setComponent(t,e){switch(t){case 0:this.x=e;break;case 1:this.y=e;break;case 2:this.z=e;break;default:throw new Error("index is out of range: "+t)}return this}getComponent(t){switch(t){case 0:return this.x;case 1:return this.y;case 2:return this.z;default:throw new Error("index is out of range: "+t)}}clone(){return new this.constructor(this.x,this.y,this.z)}copy(t){return this.x=t.x,this.y=t.y,this.z=t.z,this}add(t){return this.x+=t.x,this.y+=t.y,this.z+=t.z,this}addScalar(t){return this.x+=t,this.y+=t,this.z+=t,this}addVectors(t,e){return this.x=t.x+e.x,this.y=t.y+e.y,this.z=t.z+e.z,this}addScaledVector(t,e){return this.x+=t.x*e,this.y+=t.y*e,this.z+=t.z*e,this}sub(t){return this.x-=t.x,this.y-=t.y,this.z-=t.z,this}subScalar(t){return this.x-=t,this.y-=t,this.z-=t,this}subVectors(t,e){return this.x=t.x-e.x,this.y=t.y-e.y,this.z=t.z-e.z,this}multiply(t){return this.x*=t.x,this.y*=t.y,this.z*=t.z,this}multiplyScalar(t){return this.x*=t,this.y*=t,this.z*=t,this}multiplyVectors(t,e){return this.x=t.x*e.x,this.y=t.y*e.y,this.z=t.z*e.z,this}applyEuler(t){return this.applyQuaternion(_i.setFromEuler(t))}applyAxisAngle(t,e){return this.applyQuaternion(_i.setFromAxisAngle(t,e))}applyMatrix3(t){const e=this.x,n=this.y,i=this.z,r=t.elements;return this.x=r[0]*e+r[3]*n+r[6]*i,this.y=r[1]*e+r[4]*n+r[7]*i,this.z=r[2]*e+r[5]*n+r[8]*i,this}applyNormalMatrix(t){return this.applyMatrix3(t).normalize()}applyMatrix4(t){const e=this.x,n=this.y,i=this.z,r=t.elements,s=1/(r[3]*e+r[7]*n+r[11]*i+r[15]);return this.x=(r[0]*e+r[4]*n+r[8]*i+r[12])*s,this.y=(r[1]*e+r[5]*n+r[9]*i+r[13])*s,this.z=(r[2]*e+r[6]*n+r[10]*i+r[14])*s,this}applyQuaternion(t){const e=this.x,n=this.y,i=this.z,r=t.x,s=t.y,a=t.z,o=t.w,l=o*e+s*i-a*n,c=o*n+a*e-r*i,h=o*i+r*n-s*e,u=-r*e-s*n-a*i;return this.x=l*o+u*-r+c*-a-h*-s,this.y=c*o+u*-s+h*-r-l*-a,this.z=h*o+u*-a+l*-s-c*-r,this}project(t){return this.applyMatrix4(t.matrixWorldInverse).applyMatrix4(t.projectionMatrix)}unproject(t){return this.applyMatrix4(t.projectionMatrixInverse).applyMatrix4(t.matrixWorld)}transformDirection(t){const e=this.x,n=this.y,i=this.z,r=t.elements;return this.x=r[0]*e+r[4]*n+r[8]*i,this.y=r[1]*e+r[5]*n+r[9]*i,this.z=r[2]*e+r[6]*n+r[10]*i,this.normalize()}divide(t){return this.x/=t.x,this.y/=t.y,this.z/=t.z,this}divideScalar(t){return this.multiplyScalar(1/t)}min(t){return this.x=Math.min(this.x,t.x),this.y=Math.min(this.y,t.y),this.z=Math.min(this.z,t.z),this}max(t){return this.x=Math.max(this.x,t.x),this.y=Math.max(this.y,t.y),this.z=Math.max(this.z,t.z),this}clamp(t,e){return this.x=Math.max(t.x,Math.min(e.x,this.x)),this.y=Math.max(t.y,Math.min(e.y,this.y)),this.z=Math.max(t.z,Math.min(e.z,this.z)),this}clampScalar(t,e){return this.x=Math.max(t,Math.min(e,this.x)),this.y=Math.max(t,Math.min(e,this.y)),this.z=Math.max(t,Math.min(e,this.z)),this}clampLength(t,e){const n=this.length();return this.divideScalar(n||1).multiplyScalar(Math.max(t,Math.min(e,n)))}floor(){return this.x=Math.floor(this.x),this.y=Math.floor(this.y),this.z=Math.floor(this.z),this}ceil(){return this.x=Math.ceil(this.x),this.y=Math.ceil(this.y),this.z=Math.ceil(this.z),this}round(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this.z=Math.round(this.z),this}roundToZero(){return this.x=this.x<0?Math.ceil(this.x):Math.floor(this.x),this.y=this.y<0?Math.ceil(this.y):Math.floor(this.y),this.z=this.z<0?Math.ceil(this.z):Math.floor(this.z),this}negate(){return this.x=-this.x,this.y=-this.y,this.z=-this.z,this}dot(t){return this.x*t.x+this.y*t.y+this.z*t.z}lengthSq(){return this.x*this.x+this.y*this.y+this.z*this.z}length(){return Math.sqrt(this.x*this.x+this.y*this.y+this.z*this.z)}manhattanLength(){return Math.abs(this.x)+Math.abs(this.y)+Math.abs(this.z)}normalize(){return this.divideScalar(this.length()||1)}setLength(t){return this.normalize().multiplyScalar(t)}lerp(t,e){return this.x+=(t.x-this.x)*e,this.y+=(t.y-this.y)*e,this.z+=(t.z-this.z)*e,this}lerpVectors(t,e,n){return this.x=t.x+(e.x-t.x)*n,this.y=t.y+(e.y-t.y)*n,this.z=t.z+(e.z-t.z)*n,this}cross(t){return this.crossVectors(this,t)}crossVectors(t,e){const n=t.x,i=t.y,r=t.z,s=e.x,a=e.y,o=e.z;return this.x=i*o-r*a,this.y=r*s-n*o,this.z=n*a-i*s,this}projectOnVector(t){const e=t.lengthSq();if(0===e)return this.set(0,0,0);const n=t.dot(this)/e;return this.copy(t).multiplyScalar(n)}projectOnPlane(t){return vi.copy(this).projectOnVector(t),this.sub(vi)}reflect(t){return this.sub(vi.copy(t).multiplyScalar(2*this.dot(t)))}angleTo(t){const e=Math.sqrt(this.lengthSq()*t.lengthSq());if(0===e)return Math.PI/2;const n=this.dot(t)/e;return Math.acos(Pn(n,-1,1))}distanceTo(t){return Math.sqrt(this.distanceToSquared(t))}distanceToSquared(t){const e=this.x-t.x,n=this.y-t.y,i=this.z-t.z;return e*e+n*n+i*i}manhattanDistanceTo(t){return Math.abs(this.x-t.x)+Math.abs(this.y-t.y)+Math.abs(this.z-t.z)}setFromSpherical(t){return this.setFromSphericalCoords(t.radius,t.phi,t.theta)}setFromSphericalCoords(t,e,n){const i=Math.sin(e)*t;return this.x=i*Math.sin(n),this.y=Math.cos(e)*t,this.z=i*Math.cos(n),this}setFromCylindrical(t){return this.setFromCylindricalCoords(t.radius,t.theta,t.y)}setFromCylindricalCoords(t,e,n){return this.x=t*Math.sin(e),this.y=n,this.z=t*Math.cos(e),this}setFromMatrixPosition(t){const e=t.elements;return this.x=e[12],this.y=e[13],this.z=e[14],this}setFromMatrixScale(t){const e=this.setFromMatrixColumn(t,0).length(),n=this.setFromMatrixColumn(t,1).length(),i=this.setFromMatrixColumn(t,2).length();return this.x=e,this.y=n,this.z=i,this}setFromMatrixColumn(t,e){return this.fromArray(t.elements,4*e)}setFromMatrix3Column(t,e){return this.fromArray(t.elements,3*e)}setFromEuler(t){return this.x=t._x,this.y=t._y,this.z=t._z,this}setFromColor(t){return this.x=t.r,this.y=t.g,this.z=t.b,this}equals(t){return t.x===this.x&&t.y===this.y&&t.z===this.z}fromArray(t,e=0){return this.x=t[e],this.y=t[e+1],this.z=t[e+2],this}toArray(t=[],e=0){return t[e]=this.x,t[e+1]=this.y,t[e+2]=this.z,t}fromBufferAttribute(t,e){return this.x=t.getX(e),this.y=t.getY(e),this.z=t.getZ(e),this}random(){return this.x=Math.random(),this.y=Math.random(),this.z=Math.random(),this}randomDirection(){const t=2*(Math.random()-.5),e=Math.random()*Math.PI*2,n=Math.sqrt(1-t**2);return this.x=n*Math.cos(e),this.y=n*Math.sin(e),this.z=t,this}*[Symbol.iterator](){yield this.x,yield this.y,yield this.z}}const vi=new gi,_i=new fi;class yi{constructor(t=new gi(1/0,1/0,1/0),e=new gi(-1/0,-1/0,-1/0)){this.isBox3=!0,this.min=t,this.max=e}set(t,e){return this.min.copy(t),this.max.copy(e),this}setFromArray(t){this.makeEmpty();for(let e=0,n=t.length;ethis.max.x||t.ythis.max.y||t.zthis.max.z)}containsBox(t){return this.min.x<=t.min.x&&t.max.x<=this.max.x&&this.min.y<=t.min.y&&t.max.y<=this.max.y&&this.min.z<=t.min.z&&t.max.z<=this.max.z}getParameter(t,e){return e.set((t.x-this.min.x)/(this.max.x-this.min.x),(t.y-this.min.y)/(this.max.y-this.min.y),(t.z-this.min.z)/(this.max.z-this.min.z))}intersectsBox(t){return!(t.max.xthis.max.x||t.max.ythis.max.y||t.max.zthis.max.z)}intersectsSphere(t){return this.clampPoint(t.center,Mi),Mi.distanceToSquared(t.center)<=t.radius*t.radius}intersectsPlane(t){let e,n;return t.normal.x>0?(e=t.normal.x*this.min.x,n=t.normal.x*this.max.x):(e=t.normal.x*this.max.x,n=t.normal.x*this.min.x),t.normal.y>0?(e+=t.normal.y*this.min.y,n+=t.normal.y*this.max.y):(e+=t.normal.y*this.max.y,n+=t.normal.y*this.min.y),t.normal.z>0?(e+=t.normal.z*this.min.z,n+=t.normal.z*this.max.z):(e+=t.normal.z*this.max.z,n+=t.normal.z*this.min.z),e<=-t.constant&&n>=-t.constant}intersectsTriangle(t){if(this.isEmpty())return!1;this.getCenter(Ci),Pi.subVectors(this.max,Ci),bi.subVectors(t.a,Ci),Ei.subVectors(t.b,Ci),Ti.subVectors(t.c,Ci),wi.subVectors(Ei,bi),Ai.subVectors(Ti,Ei),Ri.subVectors(bi,Ti);let e=[0,-wi.z,wi.y,0,-Ai.z,Ai.y,0,-Ri.z,Ri.y,wi.z,0,-wi.x,Ai.z,0,-Ai.x,Ri.z,0,-Ri.x,-wi.y,wi.x,0,-Ai.y,Ai.x,0,-Ri.y,Ri.x,0];return!!Ui(e,bi,Ei,Ti,Pi)&&(e=[1,0,0,0,1,0,0,0,1],!!Ui(e,bi,Ei,Ti,Pi)&&(Li.crossVectors(wi,Ai),e=[Li.x,Li.y,Li.z],Ui(e,bi,Ei,Ti,Pi)))}clampPoint(t,e){return e.copy(t).clamp(this.min,this.max)}distanceToPoint(t){return this.clampPoint(t,Mi).distanceTo(t)}getBoundingSphere(t){return this.isEmpty()?t.makeEmpty():(this.getCenter(t.center),t.radius=.5*this.getSize(Mi).length()),t}intersect(t){return this.min.max(t.min),this.max.min(t.max),this.isEmpty()&&this.makeEmpty(),this}union(t){return this.min.min(t.min),this.max.max(t.max),this}applyMatrix4(t){return this.isEmpty()||(xi[0].set(this.min.x,this.min.y,this.min.z).applyMatrix4(t),xi[1].set(this.min.x,this.min.y,this.max.z).applyMatrix4(t),xi[2].set(this.min.x,this.max.y,this.min.z).applyMatrix4(t),xi[3].set(this.min.x,this.max.y,this.max.z).applyMatrix4(t),xi[4].set(this.max.x,this.min.y,this.min.z).applyMatrix4(t),xi[5].set(this.max.x,this.min.y,this.max.z).applyMatrix4(t),xi[6].set(this.max.x,this.max.y,this.min.z).applyMatrix4(t),xi[7].set(this.max.x,this.max.y,this.max.z).applyMatrix4(t),this.setFromPoints(xi)),this}translate(t){return this.min.add(t),this.max.add(t),this}equals(t){return t.min.equals(this.min)&&t.max.equals(this.max)}}const xi=[new gi,new gi,new gi,new gi,new gi,new gi,new gi,new gi],Mi=new gi,Si=new yi,bi=new gi,Ei=new gi,Ti=new gi,wi=new gi,Ai=new gi,Ri=new gi,Ci=new gi,Pi=new gi,Li=new gi,Ii=new gi;function Ui(t,e,n,i,r){for(let s=0,a=t.length-3;s<=a;s+=3){Ii.fromArray(t,s);const a=r.x*Math.abs(Ii.x)+r.y*Math.abs(Ii.y)+r.z*Math.abs(Ii.z),o=e.dot(Ii),l=n.dot(Ii),c=i.dot(Ii);if(Math.max(-Math.max(o,l,c),Math.min(o,l,c))>a)return!1}return!0}const Ni=new yi,Di=new gi,Oi=new gi;class Fi{constructor(t=new gi,e=-1){this.center=t,this.radius=e}set(t,e){return this.center.copy(t),this.radius=e,this}setFromPoints(t,e){const n=this.center;void 0!==e?n.copy(e):Ni.setFromPoints(t).getCenter(n);let i=0;for(let e=0,r=t.length;ethis.radius*this.radius&&(e.sub(this.center).normalize(),e.multiplyScalar(this.radius).add(this.center)),e}getBoundingBox(t){return this.isEmpty()?(t.makeEmpty(),t):(t.set(this.center,this.center),t.expandByScalar(this.radius),t)}applyMatrix4(t){return this.center.applyMatrix4(t),this.radius=this.radius*t.getMaxScaleOnAxis(),this}translate(t){return this.center.add(t),this}expandByPoint(t){if(this.isEmpty())return this.center.copy(t),this.radius=0,this;Di.subVectors(t,this.center);const e=Di.lengthSq();if(e>this.radius*this.radius){const t=Math.sqrt(e),n=.5*(t-this.radius);this.center.addScaledVector(Di,n/t),this.radius+=n}return this}union(t){return t.isEmpty()?this:this.isEmpty()?(this.copy(t),this):(!0===this.center.equals(t.center)?this.radius=Math.max(this.radius,t.radius):(Oi.subVectors(t.center,this.center).setLength(t.radius),this.expandByPoint(Di.copy(t.center).add(Oi)),this.expandByPoint(Di.copy(t.center).sub(Oi))),this)}equals(t){return t.center.equals(this.center)&&t.radius===this.radius}clone(){return(new this.constructor).copy(this)}}const Bi=new gi,zi=new gi,Hi=new gi,ki=new gi,Vi=new gi,Gi=new gi,Wi=new gi;class Xi{constructor(t=new gi,e=new gi(0,0,-1)){this.origin=t,this.direction=e}set(t,e){return this.origin.copy(t),this.direction.copy(e),this}copy(t){return this.origin.copy(t.origin),this.direction.copy(t.direction),this}at(t,e){return e.copy(this.origin).addScaledVector(this.direction,t)}lookAt(t){return this.direction.copy(t).sub(this.origin).normalize(),this}recast(t){return this.origin.copy(this.at(t,Bi)),this}closestPointToPoint(t,e){e.subVectors(t,this.origin);const n=e.dot(this.direction);return n<0?e.copy(this.origin):e.copy(this.origin).addScaledVector(this.direction,n)}distanceToPoint(t){return Math.sqrt(this.distanceSqToPoint(t))}distanceSqToPoint(t){const e=Bi.subVectors(t,this.origin).dot(this.direction);return e<0?this.origin.distanceToSquared(t):(Bi.copy(this.origin).addScaledVector(this.direction,e),Bi.distanceToSquared(t))}distanceSqToSegment(t,e,n,i){zi.copy(t).add(e).multiplyScalar(.5),Hi.copy(e).sub(t).normalize(),ki.copy(this.origin).sub(zi);const r=.5*t.distanceTo(e),s=-this.direction.dot(Hi),a=ki.dot(this.direction),o=-ki.dot(Hi),l=ki.lengthSq(),c=Math.abs(1-s*s);let h,u,d,p;if(c>0)if(h=s*o-a,u=s*a-o,p=r*c,h>=0)if(u>=-p)if(u<=p){const t=1/c;h*=t,u*=t,d=h*(h+s*u+2*a)+u*(s*h+u+2*o)+l}else u=r,h=Math.max(0,-(s*u+a)),d=-h*h+u*(u+2*o)+l;else u=-r,h=Math.max(0,-(s*u+a)),d=-h*h+u*(u+2*o)+l;else u<=-p?(h=Math.max(0,-(-s*r+a)),u=h>0?-r:Math.min(Math.max(-r,-o),r),d=-h*h+u*(u+2*o)+l):u<=p?(h=0,u=Math.min(Math.max(-r,-o),r),d=u*(u+2*o)+l):(h=Math.max(0,-(s*r+a)),u=h>0?r:Math.min(Math.max(-r,-o),r),d=-h*h+u*(u+2*o)+l);else u=s>0?-r:r,h=Math.max(0,-(s*u+a)),d=-h*h+u*(u+2*o)+l;return n&&n.copy(this.origin).addScaledVector(this.direction,h),i&&i.copy(zi).addScaledVector(Hi,u),d}intersectSphere(t,e){Bi.subVectors(t.center,this.origin);const n=Bi.dot(this.direction),i=Bi.dot(Bi)-n*n,r=t.radius*t.radius;if(i>r)return null;const s=Math.sqrt(r-i),a=n-s,o=n+s;return o<0?null:a<0?this.at(o,e):this.at(a,e)}intersectsSphere(t){return this.distanceSqToPoint(t.center)<=t.radius*t.radius}distanceToPlane(t){const e=t.normal.dot(this.direction);if(0===e)return 0===t.distanceToPoint(this.origin)?0:null;const n=-(this.origin.dot(t.normal)+t.constant)/e;return n>=0?n:null}intersectPlane(t,e){const n=this.distanceToPlane(t);return null===n?null:this.at(n,e)}intersectsPlane(t){const e=t.distanceToPoint(this.origin);if(0===e)return!0;return t.normal.dot(this.direction)*e<0}intersectBox(t,e){let n,i,r,s,a,o;const l=1/this.direction.x,c=1/this.direction.y,h=1/this.direction.z,u=this.origin;return l>=0?(n=(t.min.x-u.x)*l,i=(t.max.x-u.x)*l):(n=(t.max.x-u.x)*l,i=(t.min.x-u.x)*l),c>=0?(r=(t.min.y-u.y)*c,s=(t.max.y-u.y)*c):(r=(t.max.y-u.y)*c,s=(t.min.y-u.y)*c),n>s||r>i?null:((r>n||isNaN(n))&&(n=r),(s=0?(a=(t.min.z-u.z)*h,o=(t.max.z-u.z)*h):(a=(t.max.z-u.z)*h,o=(t.min.z-u.z)*h),n>o||a>i?null:((a>n||n!=n)&&(n=a),(o=0?n:i,e)))}intersectsBox(t){return null!==this.intersectBox(t,Bi)}intersectTriangle(t,e,n,i,r){Vi.subVectors(e,t),Gi.subVectors(n,t),Wi.crossVectors(Vi,Gi);let s,a=this.direction.dot(Wi);if(a>0){if(i)return null;s=1}else{if(!(a<0))return null;s=-1,a=-a}ki.subVectors(this.origin,t);const o=s*this.direction.dot(Gi.crossVectors(ki,Gi));if(o<0)return null;const l=s*this.direction.dot(Vi.cross(ki));if(l<0)return null;if(o+l>a)return null;const c=-s*ki.dot(Wi);return c<0?null:this.at(c/a,r)}applyMatrix4(t){return this.origin.applyMatrix4(t),this.direction.transformDirection(t),this}equals(t){return t.origin.equals(this.origin)&&t.direction.equals(this.direction)}clone(){return(new this.constructor).copy(this)}}class ji{constructor(t,e,n,i,r,s,a,o,l,c,h,u,d,p,m,f){ji.prototype.isMatrix4=!0,this.elements=[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],void 0!==t&&this.set(t,e,n,i,r,s,a,o,l,c,h,u,d,p,m,f)}set(t,e,n,i,r,s,a,o,l,c,h,u,d,p,m,f){const g=this.elements;return g[0]=t,g[4]=e,g[8]=n,g[12]=i,g[1]=r,g[5]=s,g[9]=a,g[13]=o,g[2]=l,g[6]=c,g[10]=h,g[14]=u,g[3]=d,g[7]=p,g[11]=m,g[15]=f,this}identity(){return this.set(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1),this}clone(){return(new ji).fromArray(this.elements)}copy(t){const e=this.elements,n=t.elements;return e[0]=n[0],e[1]=n[1],e[2]=n[2],e[3]=n[3],e[4]=n[4],e[5]=n[5],e[6]=n[6],e[7]=n[7],e[8]=n[8],e[9]=n[9],e[10]=n[10],e[11]=n[11],e[12]=n[12],e[13]=n[13],e[14]=n[14],e[15]=n[15],this}copyPosition(t){const e=this.elements,n=t.elements;return e[12]=n[12],e[13]=n[13],e[14]=n[14],this}setFromMatrix3(t){const e=t.elements;return this.set(e[0],e[3],e[6],0,e[1],e[4],e[7],0,e[2],e[5],e[8],0,0,0,0,1),this}extractBasis(t,e,n){return t.setFromMatrixColumn(this,0),e.setFromMatrixColumn(this,1),n.setFromMatrixColumn(this,2),this}makeBasis(t,e,n){return this.set(t.x,e.x,n.x,0,t.y,e.y,n.y,0,t.z,e.z,n.z,0,0,0,0,1),this}extractRotation(t){const e=this.elements,n=t.elements,i=1/qi.setFromMatrixColumn(t,0).length(),r=1/qi.setFromMatrixColumn(t,1).length(),s=1/qi.setFromMatrixColumn(t,2).length();return e[0]=n[0]*i,e[1]=n[1]*i,e[2]=n[2]*i,e[3]=0,e[4]=n[4]*r,e[5]=n[5]*r,e[6]=n[6]*r,e[7]=0,e[8]=n[8]*s,e[9]=n[9]*s,e[10]=n[10]*s,e[11]=0,e[12]=0,e[13]=0,e[14]=0,e[15]=1,this}makeRotationFromEuler(t){const e=this.elements,n=t.x,i=t.y,r=t.z,s=Math.cos(n),a=Math.sin(n),o=Math.cos(i),l=Math.sin(i),c=Math.cos(r),h=Math.sin(r);if("XYZ"===t.order){const t=s*c,n=s*h,i=a*c,r=a*h;e[0]=o*c,e[4]=-o*h,e[8]=l,e[1]=n+i*l,e[5]=t-r*l,e[9]=-a*o,e[2]=r-t*l,e[6]=i+n*l,e[10]=s*o}else if("YXZ"===t.order){const t=o*c,n=o*h,i=l*c,r=l*h;e[0]=t+r*a,e[4]=i*a-n,e[8]=s*l,e[1]=s*h,e[5]=s*c,e[9]=-a,e[2]=n*a-i,e[6]=r+t*a,e[10]=s*o}else if("ZXY"===t.order){const t=o*c,n=o*h,i=l*c,r=l*h;e[0]=t-r*a,e[4]=-s*h,e[8]=i+n*a,e[1]=n+i*a,e[5]=s*c,e[9]=r-t*a,e[2]=-s*l,e[6]=a,e[10]=s*o}else if("ZYX"===t.order){const t=s*c,n=s*h,i=a*c,r=a*h;e[0]=o*c,e[4]=i*l-n,e[8]=t*l+r,e[1]=o*h,e[5]=r*l+t,e[9]=n*l-i,e[2]=-l,e[6]=a*o,e[10]=s*o}else if("YZX"===t.order){const t=s*o,n=s*l,i=a*o,r=a*l;e[0]=o*c,e[4]=r-t*h,e[8]=i*h+n,e[1]=h,e[5]=s*c,e[9]=-a*c,e[2]=-l*c,e[6]=n*h+i,e[10]=t-r*h}else if("XZY"===t.order){const t=s*o,n=s*l,i=a*o,r=a*l;e[0]=o*c,e[4]=-h,e[8]=l*c,e[1]=t*h+r,e[5]=s*c,e[9]=n*h-i,e[2]=i*h-n,e[6]=a*c,e[10]=r*h+t}return e[3]=0,e[7]=0,e[11]=0,e[12]=0,e[13]=0,e[14]=0,e[15]=1,this}makeRotationFromQuaternion(t){return this.compose(Zi,t,Ji)}lookAt(t,e,n){const i=this.elements;return Qi.subVectors(t,e),0===Qi.lengthSq()&&(Qi.z=1),Qi.normalize(),Ki.crossVectors(n,Qi),0===Ki.lengthSq()&&(1===Math.abs(n.z)?Qi.x+=1e-4:Qi.z+=1e-4,Qi.normalize(),Ki.crossVectors(n,Qi)),Ki.normalize(),$i.crossVectors(Qi,Ki),i[0]=Ki.x,i[4]=$i.x,i[8]=Qi.x,i[1]=Ki.y,i[5]=$i.y,i[9]=Qi.y,i[2]=Ki.z,i[6]=$i.z,i[10]=Qi.z,this}multiply(t){return this.multiplyMatrices(this,t)}premultiply(t){return this.multiplyMatrices(t,this)}multiplyMatrices(t,e){const n=t.elements,i=e.elements,r=this.elements,s=n[0],a=n[4],o=n[8],l=n[12],c=n[1],h=n[5],u=n[9],d=n[13],p=n[2],m=n[6],f=n[10],g=n[14],v=n[3],_=n[7],y=n[11],x=n[15],M=i[0],S=i[4],b=i[8],E=i[12],T=i[1],w=i[5],A=i[9],R=i[13],C=i[2],P=i[6],L=i[10],I=i[14],U=i[3],N=i[7],D=i[11],O=i[15];return r[0]=s*M+a*T+o*C+l*U,r[4]=s*S+a*w+o*P+l*N,r[8]=s*b+a*A+o*L+l*D,r[12]=s*E+a*R+o*I+l*O,r[1]=c*M+h*T+u*C+d*U,r[5]=c*S+h*w+u*P+d*N,r[9]=c*b+h*A+u*L+d*D,r[13]=c*E+h*R+u*I+d*O,r[2]=p*M+m*T+f*C+g*U,r[6]=p*S+m*w+f*P+g*N,r[10]=p*b+m*A+f*L+g*D,r[14]=p*E+m*R+f*I+g*O,r[3]=v*M+_*T+y*C+x*U,r[7]=v*S+_*w+y*P+x*N,r[11]=v*b+_*A+y*L+x*D,r[15]=v*E+_*R+y*I+x*O,this}multiplyScalar(t){const e=this.elements;return e[0]*=t,e[4]*=t,e[8]*=t,e[12]*=t,e[1]*=t,e[5]*=t,e[9]*=t,e[13]*=t,e[2]*=t,e[6]*=t,e[10]*=t,e[14]*=t,e[3]*=t,e[7]*=t,e[11]*=t,e[15]*=t,this}determinant(){const t=this.elements,e=t[0],n=t[4],i=t[8],r=t[12],s=t[1],a=t[5],o=t[9],l=t[13],c=t[2],h=t[6],u=t[10],d=t[14];return t[3]*(+r*o*h-i*l*h-r*a*u+n*l*u+i*a*d-n*o*d)+t[7]*(+e*o*d-e*l*u+r*s*u-i*s*d+i*l*c-r*o*c)+t[11]*(+e*l*h-e*a*d-r*s*h+n*s*d+r*a*c-n*l*c)+t[15]*(-i*a*c-e*o*h+e*a*u+i*s*h-n*s*u+n*o*c)}transpose(){const t=this.elements;let e;return e=t[1],t[1]=t[4],t[4]=e,e=t[2],t[2]=t[8],t[8]=e,e=t[6],t[6]=t[9],t[9]=e,e=t[3],t[3]=t[12],t[12]=e,e=t[7],t[7]=t[13],t[13]=e,e=t[11],t[11]=t[14],t[14]=e,this}setPosition(t,e,n){const i=this.elements;return t.isVector3?(i[12]=t.x,i[13]=t.y,i[14]=t.z):(i[12]=t,i[13]=e,i[14]=n),this}invert(){const t=this.elements,e=t[0],n=t[1],i=t[2],r=t[3],s=t[4],a=t[5],o=t[6],l=t[7],c=t[8],h=t[9],u=t[10],d=t[11],p=t[12],m=t[13],f=t[14],g=t[15],v=h*f*l-m*u*l+m*o*d-a*f*d-h*o*g+a*u*g,_=p*u*l-c*f*l-p*o*d+s*f*d+c*o*g-s*u*g,y=c*m*l-p*h*l+p*a*d-s*m*d-c*a*g+s*h*g,x=p*h*o-c*m*o-p*a*u+s*m*u+c*a*f-s*h*f,M=e*v+n*_+i*y+r*x;if(0===M)return this.set(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);const S=1/M;return t[0]=v*S,t[1]=(m*u*r-h*f*r-m*i*d+n*f*d+h*i*g-n*u*g)*S,t[2]=(a*f*r-m*o*r+m*i*l-n*f*l-a*i*g+n*o*g)*S,t[3]=(h*o*r-a*u*r-h*i*l+n*u*l+a*i*d-n*o*d)*S,t[4]=_*S,t[5]=(c*f*r-p*u*r+p*i*d-e*f*d-c*i*g+e*u*g)*S,t[6]=(p*o*r-s*f*r-p*i*l+e*f*l+s*i*g-e*o*g)*S,t[7]=(s*u*r-c*o*r+c*i*l-e*u*l-s*i*d+e*o*d)*S,t[8]=y*S,t[9]=(p*h*r-c*m*r-p*n*d+e*m*d+c*n*g-e*h*g)*S,t[10]=(s*m*r-p*a*r+p*n*l-e*m*l-s*n*g+e*a*g)*S,t[11]=(c*a*r-s*h*r-c*n*l+e*h*l+s*n*d-e*a*d)*S,t[12]=x*S,t[13]=(c*m*i-p*h*i+p*n*u-e*m*u-c*n*f+e*h*f)*S,t[14]=(p*a*i-s*m*i-p*n*o+e*m*o+s*n*f-e*a*f)*S,t[15]=(s*h*i-c*a*i+c*n*o-e*h*o-s*n*u+e*a*u)*S,this}scale(t){const e=this.elements,n=t.x,i=t.y,r=t.z;return e[0]*=n,e[4]*=i,e[8]*=r,e[1]*=n,e[5]*=i,e[9]*=r,e[2]*=n,e[6]*=i,e[10]*=r,e[3]*=n,e[7]*=i,e[11]*=r,this}getMaxScaleOnAxis(){const t=this.elements,e=t[0]*t[0]+t[1]*t[1]+t[2]*t[2],n=t[4]*t[4]+t[5]*t[5]+t[6]*t[6],i=t[8]*t[8]+t[9]*t[9]+t[10]*t[10];return Math.sqrt(Math.max(e,n,i))}makeTranslation(t,e,n){return this.set(1,0,0,t,0,1,0,e,0,0,1,n,0,0,0,1),this}makeRotationX(t){const e=Math.cos(t),n=Math.sin(t);return this.set(1,0,0,0,0,e,-n,0,0,n,e,0,0,0,0,1),this}makeRotationY(t){const e=Math.cos(t),n=Math.sin(t);return this.set(e,0,n,0,0,1,0,0,-n,0,e,0,0,0,0,1),this}makeRotationZ(t){const e=Math.cos(t),n=Math.sin(t);return this.set(e,-n,0,0,n,e,0,0,0,0,1,0,0,0,0,1),this}makeRotationAxis(t,e){const n=Math.cos(e),i=Math.sin(e),r=1-n,s=t.x,a=t.y,o=t.z,l=r*s,c=r*a;return this.set(l*s+n,l*a-i*o,l*o+i*a,0,l*a+i*o,c*a+n,c*o-i*s,0,l*o-i*a,c*o+i*s,r*o*o+n,0,0,0,0,1),this}makeScale(t,e,n){return this.set(t,0,0,0,0,e,0,0,0,0,n,0,0,0,0,1),this}makeShear(t,e,n,i,r,s){return this.set(1,n,r,0,t,1,s,0,e,i,1,0,0,0,0,1),this}compose(t,e,n){const i=this.elements,r=e._x,s=e._y,a=e._z,o=e._w,l=r+r,c=s+s,h=a+a,u=r*l,d=r*c,p=r*h,m=s*c,f=s*h,g=a*h,v=o*l,_=o*c,y=o*h,x=n.x,M=n.y,S=n.z;return i[0]=(1-(m+g))*x,i[1]=(d+y)*x,i[2]=(p-_)*x,i[3]=0,i[4]=(d-y)*M,i[5]=(1-(u+g))*M,i[6]=(f+v)*M,i[7]=0,i[8]=(p+_)*S,i[9]=(f-v)*S,i[10]=(1-(u+m))*S,i[11]=0,i[12]=t.x,i[13]=t.y,i[14]=t.z,i[15]=1,this}decompose(t,e,n){const i=this.elements;let r=qi.set(i[0],i[1],i[2]).length();const s=qi.set(i[4],i[5],i[6]).length(),a=qi.set(i[8],i[9],i[10]).length();this.determinant()<0&&(r=-r),t.x=i[12],t.y=i[13],t.z=i[14],Yi.copy(this);const o=1/r,l=1/s,c=1/a;return Yi.elements[0]*=o,Yi.elements[1]*=o,Yi.elements[2]*=o,Yi.elements[4]*=l,Yi.elements[5]*=l,Yi.elements[6]*=l,Yi.elements[8]*=c,Yi.elements[9]*=c,Yi.elements[10]*=c,e.setFromRotationMatrix(Yi),n.x=r,n.y=s,n.z=a,this}makePerspective(t,e,n,i,r,s){const a=this.elements,o=2*r/(e-t),l=2*r/(n-i),c=(e+t)/(e-t),h=(n+i)/(n-i),u=-(s+r)/(s-r),d=-2*s*r/(s-r);return a[0]=o,a[4]=0,a[8]=c,a[12]=0,a[1]=0,a[5]=l,a[9]=h,a[13]=0,a[2]=0,a[6]=0,a[10]=u,a[14]=d,a[3]=0,a[7]=0,a[11]=-1,a[15]=0,this}makeOrthographic(t,e,n,i,r,s){const a=this.elements,o=1/(e-t),l=1/(n-i),c=1/(s-r),h=(e+t)*o,u=(n+i)*l,d=(s+r)*c;return a[0]=2*o,a[4]=0,a[8]=0,a[12]=-h,a[1]=0,a[5]=2*l,a[9]=0,a[13]=-u,a[2]=0,a[6]=0,a[10]=-2*c,a[14]=-d,a[3]=0,a[7]=0,a[11]=0,a[15]=1,this}equals(t){const e=this.elements,n=t.elements;for(let t=0;t<16;t++)if(e[t]!==n[t])return!1;return!0}fromArray(t,e=0){for(let n=0;n<16;n++)this.elements[n]=t[n+e];return this}toArray(t=[],e=0){const n=this.elements;return t[e]=n[0],t[e+1]=n[1],t[e+2]=n[2],t[e+3]=n[3],t[e+4]=n[4],t[e+5]=n[5],t[e+6]=n[6],t[e+7]=n[7],t[e+8]=n[8],t[e+9]=n[9],t[e+10]=n[10],t[e+11]=n[11],t[e+12]=n[12],t[e+13]=n[13],t[e+14]=n[14],t[e+15]=n[15],t}}const qi=new gi,Yi=new ji,Zi=new gi(0,0,0),Ji=new gi(1,1,1),Ki=new gi,$i=new gi,Qi=new gi,tr=new ji,er=new fi;class nr{constructor(t=0,e=0,n=0,i=nr.DEFAULT_ORDER){this.isEuler=!0,this._x=t,this._y=e,this._z=n,this._order=i}get x(){return this._x}set x(t){this._x=t,this._onChangeCallback()}get y(){return this._y}set y(t){this._y=t,this._onChangeCallback()}get z(){return this._z}set z(t){this._z=t,this._onChangeCallback()}get order(){return this._order}set order(t){this._order=t,this._onChangeCallback()}set(t,e,n,i=this._order){return this._x=t,this._y=e,this._z=n,this._order=i,this._onChangeCallback(),this}clone(){return new this.constructor(this._x,this._y,this._z,this._order)}copy(t){return this._x=t._x,this._y=t._y,this._z=t._z,this._order=t._order,this._onChangeCallback(),this}setFromRotationMatrix(t,e=this._order,n=!0){const i=t.elements,r=i[0],s=i[4],a=i[8],o=i[1],l=i[5],c=i[9],h=i[2],u=i[6],d=i[10];switch(e){case"XYZ":this._y=Math.asin(Pn(a,-1,1)),Math.abs(a)<.9999999?(this._x=Math.atan2(-c,d),this._z=Math.atan2(-s,r)):(this._x=Math.atan2(u,l),this._z=0);break;case"YXZ":this._x=Math.asin(-Pn(c,-1,1)),Math.abs(c)<.9999999?(this._y=Math.atan2(a,d),this._z=Math.atan2(o,l)):(this._y=Math.atan2(-h,r),this._z=0);break;case"ZXY":this._x=Math.asin(Pn(u,-1,1)),Math.abs(u)<.9999999?(this._y=Math.atan2(-h,d),this._z=Math.atan2(-s,l)):(this._y=0,this._z=Math.atan2(o,r));break;case"ZYX":this._y=Math.asin(-Pn(h,-1,1)),Math.abs(h)<.9999999?(this._x=Math.atan2(u,d),this._z=Math.atan2(o,r)):(this._x=0,this._z=Math.atan2(-s,l));break;case"YZX":this._z=Math.asin(Pn(o,-1,1)),Math.abs(o)<.9999999?(this._x=Math.atan2(-c,l),this._y=Math.atan2(-h,r)):(this._x=0,this._y=Math.atan2(a,d));break;case"XZY":this._z=Math.asin(-Pn(s,-1,1)),Math.abs(s)<.9999999?(this._x=Math.atan2(u,l),this._y=Math.atan2(a,r)):(this._x=Math.atan2(-c,d),this._y=0);break;default:console.warn("THREE.Euler: .setFromRotationMatrix() encountered an unknown order: "+e)}return this._order=e,!0===n&&this._onChangeCallback(),this}setFromQuaternion(t,e,n){return tr.makeRotationFromQuaternion(t),this.setFromRotationMatrix(tr,e,n)}setFromVector3(t,e=this._order){return this.set(t.x,t.y,t.z,e)}reorder(t){return er.setFromEuler(this),this.setFromQuaternion(er,t)}equals(t){return t._x===this._x&&t._y===this._y&&t._z===this._z&&t._order===this._order}fromArray(t){return this._x=t[0],this._y=t[1],this._z=t[2],void 0!==t[3]&&(this._order=t[3]),this._onChangeCallback(),this}toArray(t=[],e=0){return t[e]=this._x,t[e+1]=this._y,t[e+2]=this._z,t[e+3]=this._order,t}_onChange(t){return this._onChangeCallback=t,this}_onChangeCallback(){}*[Symbol.iterator](){yield this._x,yield this._y,yield this._z,yield this._order}}nr.DEFAULT_ORDER="XYZ";class ir{constructor(){this.mask=1}set(t){this.mask=(1<>>0}enable(t){this.mask|=1<1){for(let t=0;t1){for(let t=0;t0&&(n=n.concat(r))}return n}getWorldPosition(t){return this.updateWorldMatrix(!0,!1),t.setFromMatrixPosition(this.matrixWorld)}getWorldQuaternion(t){return this.updateWorldMatrix(!0,!1),this.matrixWorld.decompose(cr,t,hr),t}getWorldScale(t){return this.updateWorldMatrix(!0,!1),this.matrixWorld.decompose(cr,ur,t),t}getWorldDirection(t){this.updateWorldMatrix(!0,!1);const e=this.matrixWorld.elements;return t.set(e[8],e[9],e[10]).normalize()}raycast(){}traverse(t){t(this);const e=this.children;for(let n=0,i=e.length;n0&&(i.userData=this.userData),i.layers=this.layers.mask,i.matrix=this.matrix.toArray(),i.up=this.up.toArray(),!1===this.matrixAutoUpdate&&(i.matrixAutoUpdate=!1),this.isInstancedMesh&&(i.type="InstancedMesh",i.count=this.count,i.instanceMatrix=this.instanceMatrix.toJSON(),null!==this.instanceColor&&(i.instanceColor=this.instanceColor.toJSON())),this.isScene)this.background&&(this.background.isColor?i.background=this.background.toJSON():this.background.isTexture&&(i.background=this.background.toJSON(t).uuid)),this.environment&&this.environment.isTexture&&!0!==this.environment.isRenderTargetTexture&&(i.environment=this.environment.toJSON(t).uuid);else if(this.isMesh||this.isLine||this.isPoints){i.geometry=r(t.geometries,this.geometry);const e=this.geometry.parameters;if(void 0!==e&&void 0!==e.shapes){const n=e.shapes;if(Array.isArray(n))for(let e=0,i=n.length;e0){i.children=[];for(let e=0;e0){i.animations=[];for(let e=0;e0&&(n.geometries=e),i.length>0&&(n.materials=i),r.length>0&&(n.textures=r),a.length>0&&(n.images=a),o.length>0&&(n.shapes=o),l.length>0&&(n.skeletons=l),c.length>0&&(n.animations=c),h.length>0&&(n.nodes=h)}return n.object=i,n;function s(t){const e=[];for(const n in t){const i=t[n];delete i.metadata,e.push(i)}return e}}clone(t){return(new this.constructor).copy(this,t)}copy(t,e=!0){if(this.name=t.name,this.up.copy(t.up),this.position.copy(t.position),this.rotation.order=t.rotation.order,this.quaternion.copy(t.quaternion),this.scale.copy(t.scale),this.matrix.copy(t.matrix),this.matrixWorld.copy(t.matrixWorld),this.matrixAutoUpdate=t.matrixAutoUpdate,this.matrixWorldNeedsUpdate=t.matrixWorldNeedsUpdate,this.matrixWorldAutoUpdate=t.matrixWorldAutoUpdate,this.layers.mask=t.layers.mask,this.visible=t.visible,this.castShadow=t.castShadow,this.receiveShadow=t.receiveShadow,this.frustumCulled=t.frustumCulled,this.renderOrder=t.renderOrder,this.animations=t.animations,this.userData=JSON.parse(JSON.stringify(t.userData)),!0===e)for(let e=0;e0?i.multiplyScalar(1/Math.sqrt(r)):i.set(0,0,0)}static getBarycoord(t,e,n,i,r){_r.subVectors(i,e),yr.subVectors(n,e),xr.subVectors(t,e);const s=_r.dot(_r),a=_r.dot(yr),o=_r.dot(xr),l=yr.dot(yr),c=yr.dot(xr),h=s*l-a*a;if(0===h)return r.set(-2,-1,-1);const u=1/h,d=(l*o-a*c)*u,p=(s*c-a*o)*u;return r.set(1-d-p,p,d)}static containsPoint(t,e,n,i){return this.getBarycoord(t,e,n,i,Mr),Mr.x>=0&&Mr.y>=0&&Mr.x+Mr.y<=1}static getUV(t,e,n,i,r,s,a,o){return!1===Rr&&(console.warn("THREE.Triangle.getUV() has been renamed to THREE.Triangle.getInterpolation()."),Rr=!0),this.getInterpolation(t,e,n,i,r,s,a,o)}static getInterpolation(t,e,n,i,r,s,a,o){return this.getBarycoord(t,e,n,i,Mr),o.setScalar(0),o.addScaledVector(r,Mr.x),o.addScaledVector(s,Mr.y),o.addScaledVector(a,Mr.z),o}static isFrontFacing(t,e,n,i){return _r.subVectors(n,e),yr.subVectors(t,e),_r.cross(yr).dot(i)<0}set(t,e,n){return this.a.copy(t),this.b.copy(e),this.c.copy(n),this}setFromPointsAndIndices(t,e,n,i){return this.a.copy(t[e]),this.b.copy(t[n]),this.c.copy(t[i]),this}setFromAttributeAndIndices(t,e,n,i){return this.a.fromBufferAttribute(t,e),this.b.fromBufferAttribute(t,n),this.c.fromBufferAttribute(t,i),this}clone(){return(new this.constructor).copy(this)}copy(t){return this.a.copy(t.a),this.b.copy(t.b),this.c.copy(t.c),this}getArea(){return _r.subVectors(this.c,this.b),yr.subVectors(this.a,this.b),.5*_r.cross(yr).length()}getMidpoint(t){return t.addVectors(this.a,this.b).add(this.c).multiplyScalar(1/3)}getNormal(t){return Cr.getNormal(this.a,this.b,this.c,t)}getPlane(t){return t.setFromCoplanarPoints(this.a,this.b,this.c)}getBarycoord(t,e){return Cr.getBarycoord(t,this.a,this.b,this.c,e)}getUV(t,e,n,i,r){return!1===Rr&&(console.warn("THREE.Triangle.getUV() has been renamed to THREE.Triangle.getInterpolation()."),Rr=!0),Cr.getInterpolation(t,this.a,this.b,this.c,e,n,i,r)}getInterpolation(t,e,n,i,r){return Cr.getInterpolation(t,this.a,this.b,this.c,e,n,i,r)}containsPoint(t){return Cr.containsPoint(t,this.a,this.b,this.c)}isFrontFacing(t){return Cr.isFrontFacing(this.a,this.b,this.c,t)}intersectsBox(t){return t.intersectsTriangle(this)}closestPointToPoint(t,e){const n=this.a,i=this.b,r=this.c;let s,a;Sr.subVectors(i,n),br.subVectors(r,n),Tr.subVectors(t,n);const o=Sr.dot(Tr),l=br.dot(Tr);if(o<=0&&l<=0)return e.copy(n);wr.subVectors(t,i);const c=Sr.dot(wr),h=br.dot(wr);if(c>=0&&h<=c)return e.copy(i);const u=o*h-c*l;if(u<=0&&o>=0&&c<=0)return s=o/(o-c),e.copy(n).addScaledVector(Sr,s);Ar.subVectors(t,r);const d=Sr.dot(Ar),p=br.dot(Ar);if(p>=0&&d<=p)return e.copy(r);const m=d*l-o*p;if(m<=0&&l>=0&&p<=0)return a=l/(l-p),e.copy(n).addScaledVector(br,a);const f=c*p-d*h;if(f<=0&&h-c>=0&&d-p>=0)return Er.subVectors(r,i),a=(h-c)/(h-c+(d-p)),e.copy(i).addScaledVector(Er,a);const g=1/(f+m+u);return s=m*g,a=u*g,e.copy(n).addScaledVector(Sr,s).addScaledVector(br,a)}equals(t){return t.a.equals(this.a)&&t.b.equals(this.b)&&t.c.equals(this.c)}}let Pr=0;class Lr extends En{constructor(){super(),this.isMaterial=!0,Object.defineProperty(this,"id",{value:Pr++}),this.uuid=Cn(),this.name="",this.type="Material",this.blending=1,this.side=0,this.vertexColors=!1,this.opacity=1,this.transparent=!1,this.blendSrc=204,this.blendDst=205,this.blendEquation=M,this.blendSrcAlpha=null,this.blendDstAlpha=null,this.blendEquationAlpha=null,this.depthFunc=3,this.depthTest=!0,this.depthWrite=!0,this.stencilWriteMask=255,this.stencilFunc=519,this.stencilRef=0,this.stencilFuncMask=255,this.stencilFail=Ve,this.stencilZFail=Ve,this.stencilZPass=Ve,this.stencilWrite=!1,this.clippingPlanes=null,this.clipIntersection=!1,this.clipShadows=!1,this.shadowSide=null,this.colorWrite=!0,this.precision=null,this.polygonOffset=!1,this.polygonOffsetFactor=0,this.polygonOffsetUnits=0,this.dithering=!1,this.alphaToCoverage=!1,this.premultipliedAlpha=!1,this.forceSinglePass=!1,this.visible=!0,this.toneMapped=!0,this.userData={},this.version=0,this._alphaTest=0}get alphaTest(){return this._alphaTest}set alphaTest(t){this._alphaTest>0!=t>0&&this.version++,this._alphaTest=t}onBuild(){}onBeforeRender(){}onBeforeCompile(){}customProgramCacheKey(){return this.onBeforeCompile.toString()}setValues(t){if(void 0!==t)for(const e in t){const n=t[e];if(void 0===n){console.warn(`THREE.Material: parameter '${e}' has value of undefined.`);continue}const i=this[e];void 0!==i?i&&i.isColor?i.set(n):i&&i.isVector3&&n&&n.isVector3?i.copy(n):this[e]=n:console.warn(`THREE.Material: '${e}' is not a property of THREE.${this.type}.`)}}toJSON(t){const e=void 0===t||"string"==typeof t;e&&(t={textures:{},images:{}});const n={metadata:{version:4.6,type:"Material",generator:"Material.toJSON"}};function i(t){const e=[];for(const n in t){const i=t[n];delete i.metadata,e.push(i)}return e}if(n.uuid=this.uuid,n.type=this.type,""!==this.name&&(n.name=this.name),this.color&&this.color.isColor&&(n.color=this.color.getHex()),void 0!==this.roughness&&(n.roughness=this.roughness),void 0!==this.metalness&&(n.metalness=this.metalness),void 0!==this.sheen&&(n.sheen=this.sheen),this.sheenColor&&this.sheenColor.isColor&&(n.sheenColor=this.sheenColor.getHex()),void 0!==this.sheenRoughness&&(n.sheenRoughness=this.sheenRoughness),this.emissive&&this.emissive.isColor&&(n.emissive=this.emissive.getHex()),this.emissiveIntensity&&1!==this.emissiveIntensity&&(n.emissiveIntensity=this.emissiveIntensity),this.specular&&this.specular.isColor&&(n.specular=this.specular.getHex()),void 0!==this.specularIntensity&&(n.specularIntensity=this.specularIntensity),this.specularColor&&this.specularColor.isColor&&(n.specularColor=this.specularColor.getHex()),void 0!==this.shininess&&(n.shininess=this.shininess),void 0!==this.clearcoat&&(n.clearcoat=this.clearcoat),void 0!==this.clearcoatRoughness&&(n.clearcoatRoughness=this.clearcoatRoughness),this.clearcoatMap&&this.clearcoatMap.isTexture&&(n.clearcoatMap=this.clearcoatMap.toJSON(t).uuid),this.clearcoatRoughnessMap&&this.clearcoatRoughnessMap.isTexture&&(n.clearcoatRoughnessMap=this.clearcoatRoughnessMap.toJSON(t).uuid),this.clearcoatNormalMap&&this.clearcoatNormalMap.isTexture&&(n.clearcoatNormalMap=this.clearcoatNormalMap.toJSON(t).uuid,n.clearcoatNormalScale=this.clearcoatNormalScale.toArray()),void 0!==this.iridescence&&(n.iridescence=this.iridescence),void 0!==this.iridescenceIOR&&(n.iridescenceIOR=this.iridescenceIOR),void 0!==this.iridescenceThicknessRange&&(n.iridescenceThicknessRange=this.iridescenceThicknessRange),this.iridescenceMap&&this.iridescenceMap.isTexture&&(n.iridescenceMap=this.iridescenceMap.toJSON(t).uuid),this.iridescenceThicknessMap&&this.iridescenceThicknessMap.isTexture&&(n.iridescenceThicknessMap=this.iridescenceThicknessMap.toJSON(t).uuid),void 0!==this.anisotropy&&(n.anisotropy=this.anisotropy),void 0!==this.anisotropyRotation&&(n.anisotropyRotation=this.anisotropyRotation),this.anisotropyMap&&this.anisotropyMap.isTexture&&(n.anisotropyMap=this.anisotropyMap.toJSON(t).uuid),this.map&&this.map.isTexture&&(n.map=this.map.toJSON(t).uuid),this.matcap&&this.matcap.isTexture&&(n.matcap=this.matcap.toJSON(t).uuid),this.alphaMap&&this.alphaMap.isTexture&&(n.alphaMap=this.alphaMap.toJSON(t).uuid),this.lightMap&&this.lightMap.isTexture&&(n.lightMap=this.lightMap.toJSON(t).uuid,n.lightMapIntensity=this.lightMapIntensity),this.aoMap&&this.aoMap.isTexture&&(n.aoMap=this.aoMap.toJSON(t).uuid,n.aoMapIntensity=this.aoMapIntensity),this.bumpMap&&this.bumpMap.isTexture&&(n.bumpMap=this.bumpMap.toJSON(t).uuid,n.bumpScale=this.bumpScale),this.normalMap&&this.normalMap.isTexture&&(n.normalMap=this.normalMap.toJSON(t).uuid,n.normalMapType=this.normalMapType,n.normalScale=this.normalScale.toArray()),this.displacementMap&&this.displacementMap.isTexture&&(n.displacementMap=this.displacementMap.toJSON(t).uuid,n.displacementScale=this.displacementScale,n.displacementBias=this.displacementBias),this.roughnessMap&&this.roughnessMap.isTexture&&(n.roughnessMap=this.roughnessMap.toJSON(t).uuid),this.metalnessMap&&this.metalnessMap.isTexture&&(n.metalnessMap=this.metalnessMap.toJSON(t).uuid),this.emissiveMap&&this.emissiveMap.isTexture&&(n.emissiveMap=this.emissiveMap.toJSON(t).uuid),this.specularMap&&this.specularMap.isTexture&&(n.specularMap=this.specularMap.toJSON(t).uuid),this.specularIntensityMap&&this.specularIntensityMap.isTexture&&(n.specularIntensityMap=this.specularIntensityMap.toJSON(t).uuid),this.specularColorMap&&this.specularColorMap.isTexture&&(n.specularColorMap=this.specularColorMap.toJSON(t).uuid),this.envMap&&this.envMap.isTexture&&(n.envMap=this.envMap.toJSON(t).uuid,void 0!==this.combine&&(n.combine=this.combine)),void 0!==this.envMapIntensity&&(n.envMapIntensity=this.envMapIntensity),void 0!==this.reflectivity&&(n.reflectivity=this.reflectivity),void 0!==this.refractionRatio&&(n.refractionRatio=this.refractionRatio),this.gradientMap&&this.gradientMap.isTexture&&(n.gradientMap=this.gradientMap.toJSON(t).uuid),void 0!==this.transmission&&(n.transmission=this.transmission),this.transmissionMap&&this.transmissionMap.isTexture&&(n.transmissionMap=this.transmissionMap.toJSON(t).uuid),void 0!==this.thickness&&(n.thickness=this.thickness),this.thicknessMap&&this.thicknessMap.isTexture&&(n.thicknessMap=this.thicknessMap.toJSON(t).uuid),void 0!==this.attenuationDistance&&this.attenuationDistance!==1/0&&(n.attenuationDistance=this.attenuationDistance),void 0!==this.attenuationColor&&(n.attenuationColor=this.attenuationColor.getHex()),void 0!==this.size&&(n.size=this.size),null!==this.shadowSide&&(n.shadowSide=this.shadowSide),void 0!==this.sizeAttenuation&&(n.sizeAttenuation=this.sizeAttenuation),1!==this.blending&&(n.blending=this.blending),0!==this.side&&(n.side=this.side),this.vertexColors&&(n.vertexColors=!0),this.opacity<1&&(n.opacity=this.opacity),!0===this.transparent&&(n.transparent=this.transparent),n.depthFunc=this.depthFunc,n.depthTest=this.depthTest,n.depthWrite=this.depthWrite,n.colorWrite=this.colorWrite,n.stencilWrite=this.stencilWrite,n.stencilWriteMask=this.stencilWriteMask,n.stencilFunc=this.stencilFunc,n.stencilRef=this.stencilRef,n.stencilFuncMask=this.stencilFuncMask,n.stencilFail=this.stencilFail,n.stencilZFail=this.stencilZFail,n.stencilZPass=this.stencilZPass,void 0!==this.rotation&&0!==this.rotation&&(n.rotation=this.rotation),!0===this.polygonOffset&&(n.polygonOffset=!0),0!==this.polygonOffsetFactor&&(n.polygonOffsetFactor=this.polygonOffsetFactor),0!==this.polygonOffsetUnits&&(n.polygonOffsetUnits=this.polygonOffsetUnits),void 0!==this.linewidth&&1!==this.linewidth&&(n.linewidth=this.linewidth),void 0!==this.dashSize&&(n.dashSize=this.dashSize),void 0!==this.gapSize&&(n.gapSize=this.gapSize),void 0!==this.scale&&(n.scale=this.scale),!0===this.dithering&&(n.dithering=!0),this.alphaTest>0&&(n.alphaTest=this.alphaTest),!0===this.alphaToCoverage&&(n.alphaToCoverage=this.alphaToCoverage),!0===this.premultipliedAlpha&&(n.premultipliedAlpha=this.premultipliedAlpha),!0===this.forceSinglePass&&(n.forceSinglePass=this.forceSinglePass),!0===this.wireframe&&(n.wireframe=this.wireframe),this.wireframeLinewidth>1&&(n.wireframeLinewidth=this.wireframeLinewidth),"round"!==this.wireframeLinecap&&(n.wireframeLinecap=this.wireframeLinecap),"round"!==this.wireframeLinejoin&&(n.wireframeLinejoin=this.wireframeLinejoin),!0===this.flatShading&&(n.flatShading=this.flatShading),!1===this.visible&&(n.visible=!1),!1===this.toneMapped&&(n.toneMapped=!1),!1===this.fog&&(n.fog=!1),Object.keys(this.userData).length>0&&(n.userData=this.userData),e){const e=i(t.textures),r=i(t.images);e.length>0&&(n.textures=e),r.length>0&&(n.images=r)}return n}clone(){return(new this.constructor).copy(this)}copy(t){this.name=t.name,this.blending=t.blending,this.side=t.side,this.vertexColors=t.vertexColors,this.opacity=t.opacity,this.transparent=t.transparent,this.blendSrc=t.blendSrc,this.blendDst=t.blendDst,this.blendEquation=t.blendEquation,this.blendSrcAlpha=t.blendSrcAlpha,this.blendDstAlpha=t.blendDstAlpha,this.blendEquationAlpha=t.blendEquationAlpha,this.depthFunc=t.depthFunc,this.depthTest=t.depthTest,this.depthWrite=t.depthWrite,this.stencilWriteMask=t.stencilWriteMask,this.stencilFunc=t.stencilFunc,this.stencilRef=t.stencilRef,this.stencilFuncMask=t.stencilFuncMask,this.stencilFail=t.stencilFail,this.stencilZFail=t.stencilZFail,this.stencilZPass=t.stencilZPass,this.stencilWrite=t.stencilWrite;const e=t.clippingPlanes;let n=null;if(null!==e){const t=e.length;n=new Array(t);for(let i=0;i!==t;++i)n[i]=e[i].clone()}return this.clippingPlanes=n,this.clipIntersection=t.clipIntersection,this.clipShadows=t.clipShadows,this.shadowSide=t.shadowSide,this.colorWrite=t.colorWrite,this.precision=t.precision,this.polygonOffset=t.polygonOffset,this.polygonOffsetFactor=t.polygonOffsetFactor,this.polygonOffsetUnits=t.polygonOffsetUnits,this.dithering=t.dithering,this.alphaTest=t.alphaTest,this.alphaToCoverage=t.alphaToCoverage,this.premultipliedAlpha=t.premultipliedAlpha,this.forceSinglePass=t.forceSinglePass,this.visible=t.visible,this.toneMapped=t.toneMapped,this.userData=JSON.parse(JSON.stringify(t.userData)),this}dispose(){this.dispatchEvent({type:"dispose"})}set needsUpdate(t){!0===t&&this.version++}}const Ir={aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074},Ur={h:0,s:0,l:0},Nr={h:0,s:0,l:0};function Dr(t,e,n){return n<0&&(n+=1),n>1&&(n-=1),n<1/6?t+6*(e-t)*n:n<.5?e:n<2/3?t+6*(e-t)*(2/3-n):t}class Or{constructor(t,e,n){return this.isColor=!0,this.r=1,this.g=1,this.b=1,this.set(t,e,n)}set(t,e,n){if(void 0===e&&void 0===n){const e=t;e&&e.isColor?this.copy(e):"number"==typeof e?this.setHex(e):"string"==typeof e&&this.setStyle(e)}else this.setRGB(t,e,n);return this}setScalar(t){return this.r=t,this.g=t,this.b=t,this}setHex(t,e=Be){return t=Math.floor(t),this.r=(t>>16&255)/255,this.g=(t>>8&255)/255,this.b=(255&t)/255,ti.toWorkingColorSpace(this,e),this}setRGB(t,e,n,i=ti.workingColorSpace){return this.r=t,this.g=e,this.b=n,ti.toWorkingColorSpace(this,i),this}setHSL(t,e,n,i=ti.workingColorSpace){if(t=Ln(t,1),e=Pn(e,0,1),n=Pn(n,0,1),0===e)this.r=this.g=this.b=n;else{const i=n<=.5?n*(1+e):n+e-n*e,r=2*n-i;this.r=Dr(r,i,t+1/3),this.g=Dr(r,i,t),this.b=Dr(r,i,t-1/3)}return ti.toWorkingColorSpace(this,i),this}setStyle(t,e=Be){function n(e){void 0!==e&&parseFloat(e)<1&&console.warn("THREE.Color: Alpha component of "+t+" will be ignored.")}let i;if(i=/^(\w+)\(([^\)]*)\)/.exec(t)){let r;const s=i[1],a=i[2];switch(s){case"rgb":case"rgba":if(r=/^\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(a))return n(r[4]),this.setRGB(Math.min(255,parseInt(r[1],10))/255,Math.min(255,parseInt(r[2],10))/255,Math.min(255,parseInt(r[3],10))/255,e);if(r=/^\s*(\d+)\%\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(a))return n(r[4]),this.setRGB(Math.min(100,parseInt(r[1],10))/100,Math.min(100,parseInt(r[2],10))/100,Math.min(100,parseInt(r[3],10))/100,e);break;case"hsl":case"hsla":if(r=/^\s*(\d*\.?\d+)\s*,\s*(\d*\.?\d+)\%\s*,\s*(\d*\.?\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec(a))return n(r[4]),this.setHSL(parseFloat(r[1])/360,parseFloat(r[2])/100,parseFloat(r[3])/100,e);break;default:console.warn("THREE.Color: Unknown color model "+t)}}else if(i=/^\#([A-Fa-f\d]+)$/.exec(t)){const n=i[1],r=n.length;if(3===r)return this.setRGB(parseInt(n.charAt(0),16)/15,parseInt(n.charAt(1),16)/15,parseInt(n.charAt(2),16)/15,e);if(6===r)return this.setHex(parseInt(n,16),e);console.warn("THREE.Color: Invalid hex color "+t)}else if(t&&t.length>0)return this.setColorName(t,e);return this}setColorName(t,e=Be){const n=Ir[t.toLowerCase()];return void 0!==n?this.setHex(n,e):console.warn("THREE.Color: Unknown color "+t),this}clone(){return new this.constructor(this.r,this.g,this.b)}copy(t){return this.r=t.r,this.g=t.g,this.b=t.b,this}copySRGBToLinear(t){return this.r=Yn(t.r),this.g=Yn(t.g),this.b=Yn(t.b),this}copyLinearToSRGB(t){return this.r=Zn(t.r),this.g=Zn(t.g),this.b=Zn(t.b),this}convertSRGBToLinear(){return this.copySRGBToLinear(this),this}convertLinearToSRGB(){return this.copyLinearToSRGB(this),this}getHex(t=Be){return ti.fromWorkingColorSpace(Fr.copy(this),t),65536*Math.round(Pn(255*Fr.r,0,255))+256*Math.round(Pn(255*Fr.g,0,255))+Math.round(Pn(255*Fr.b,0,255))}getHexString(t=Be){return("000000"+this.getHex(t).toString(16)).slice(-6)}getHSL(t,e=ti.workingColorSpace){ti.fromWorkingColorSpace(Fr.copy(this),e);const n=Fr.r,i=Fr.g,r=Fr.b,s=Math.max(n,i,r),a=Math.min(n,i,r);let o,l;const c=(a+s)/2;if(a===s)o=0,l=0;else{const t=s-a;switch(l=c<=.5?t/(s+a):t/(2-s-a),s){case n:o=(i-r)/t+(i>-e-14,i[256|t]=1024>>-e-14|32768,r[t]=-e-1,r[256|t]=-e-1):e<=15?(i[t]=e+15<<10,i[256|t]=e+15<<10|32768,r[t]=13,r[256|t]=13):e<128?(i[t]=31744,i[256|t]=64512,r[t]=24,r[256|t]=24):(i[t]=31744,i[256|t]=64512,r[t]=13,r[256|t]=13)}const s=new Uint32Array(2048),a=new Uint32Array(64),o=new Uint32Array(64);for(let t=1;t<1024;++t){let e=t<<13,n=0;for(;0==(8388608&e);)e<<=1,n-=8388608;e&=-8388609,n+=947912704,s[t]=e|n}for(let t=1024;t<2048;++t)s[t]=939524096+(t-1024<<13);for(let t=1;t<31;++t)a[t]=t<<23;a[31]=1199570944,a[32]=2147483648;for(let t=33;t<63;++t)a[t]=2147483648+(t-32<<23);a[63]=3347054592;for(let t=1;t<64;++t)32!==t&&(o[t]=1024);return{floatView:e,uint32View:n,baseTable:i,shiftTable:r,mantissaTable:s,exponentTable:a,offsetTable:o}}function kr(t){Math.abs(t)>65504&&console.warn("THREE.DataUtils.toHalfFloat(): Value out of range."),t=Pn(t,-65504,65504),zr.floatView[0]=t;const e=zr.uint32View[0],n=e>>23&511;return zr.baseTable[n]+((8388607&e)>>zr.shiftTable[n])}function Vr(t){const e=t>>10;return zr.uint32View[0]=zr.mantissaTable[zr.offsetTable[e]+(1023&t)]+zr.exponentTable[e],zr.floatView[0]}const Gr={toHalfFloat:kr,fromHalfFloat:Vr},Wr=new gi,Xr=new zn;class jr{constructor(t,e,n=!1){if(Array.isArray(t))throw new TypeError("THREE.BufferAttribute: array should be a Typed Array.");this.isBufferAttribute=!0,this.name="",this.array=t,this.itemSize=e,this.count=void 0!==t?t.length/e:0,this.normalized=n,this.usage=dn,this.updateRange={offset:0,count:-1},this.version=0}onUploadCallback(){}set needsUpdate(t){!0===t&&this.version++}setUsage(t){return this.usage=t,this}copy(t){return this.name=t.name,this.array=new t.array.constructor(t.array),this.itemSize=t.itemSize,this.count=t.count,this.normalized=t.normalized,this.usage=t.usage,this}copyAt(t,e,n){t*=this.itemSize,n*=e.itemSize;for(let i=0,r=this.itemSize;i0&&(t.userData=this.userData),void 0!==this.parameters){const e=this.parameters;for(const n in e)void 0!==e[n]&&(t[n]=e[n]);return t}t.data={attributes:{}};const e=this.index;null!==e&&(t.data.index={type:e.array.constructor.name,array:Array.prototype.slice.call(e.array)});const n=this.attributes;for(const e in n){const i=n[e];t.data.attributes[e]=i.toJSON(t.data)}const i={};let r=!1;for(const e in this.morphAttributes){const n=this.morphAttributes[e],s=[];for(let e=0,i=n.length;e0&&(i[e]=s,r=!0)}r&&(t.data.morphAttributes=i,t.data.morphTargetsRelative=this.morphTargetsRelative);const s=this.groups;s.length>0&&(t.data.groups=JSON.parse(JSON.stringify(s)));const a=this.boundingSphere;return null!==a&&(t.data.boundingSphere={center:a.center.toArray(),radius:a.radius}),t}clone(){return(new this.constructor).copy(this)}copy(t){this.index=null,this.attributes={},this.morphAttributes={},this.groups=[],this.boundingBox=null,this.boundingSphere=null;const e={};this.name=t.name;const n=t.index;null!==n&&this.setIndex(n.clone(e));const i=t.attributes;for(const t in i){const n=i[t];this.setAttribute(t,n.clone(e))}const r=t.morphAttributes;for(const t in r){const n=[],i=r[t];for(let t=0,r=i.length;t0){const n=t[e[0]];if(void 0!==n){this.morphTargetInfluences=[],this.morphTargetDictionary={};for(let t=0,e=n.length;t(t.far-t.near)**2)return}us.copy(r).invert(),ds.copy(t.ray).applyMatrix4(us),null!==n.boundingBox&&!1===ds.intersectsBox(n.boundingBox)||this._computeIntersections(t,e,ds)}}_computeIntersections(t,e,n){let i;const r=this.geometry,s=this.material,a=r.index,o=r.attributes.position,l=r.attributes.uv,c=r.attributes.uv1,h=r.attributes.normal,u=r.groups,d=r.drawRange;if(null!==a)if(Array.isArray(s))for(let r=0,o=u.length;rn.far?null:{distance:c,point:As.clone(),object:t}}(t,e,n,i,fs,gs,vs,ws);if(h){r&&(xs.fromBufferAttribute(r,o),Ms.fromBufferAttribute(r,l),Ss.fromBufferAttribute(r,c),h.uv=Cr.getInterpolation(ws,fs,gs,vs,xs,Ms,Ss,new zn)),s&&(xs.fromBufferAttribute(s,o),Ms.fromBufferAttribute(s,l),Ss.fromBufferAttribute(s,c),h.uv1=Cr.getInterpolation(ws,fs,gs,vs,xs,Ms,Ss,new zn),h.uv2=h.uv1),a&&(bs.fromBufferAttribute(a,o),Es.fromBufferAttribute(a,l),Ts.fromBufferAttribute(a,c),h.normal=Cr.getInterpolation(ws,fs,gs,vs,bs,Es,Ts,new gi),h.normal.dot(i.direction)>0&&h.normal.multiplyScalar(-1));const t={a:o,b:l,c:c,normal:new gi,materialIndex:0};Cr.getNormal(fs,gs,vs,t.normal),h.face=t}return h}class Ps extends hs{constructor(t=1,e=1,n=1,i=1,r=1,s=1){super(),this.type="BoxGeometry",this.parameters={width:t,height:e,depth:n,widthSegments:i,heightSegments:r,depthSegments:s};const a=this;i=Math.floor(i),r=Math.floor(r),s=Math.floor(s);const o=[],l=[],c=[],h=[];let u=0,d=0;function p(t,e,n,i,r,s,p,m,f,g,v){const _=s/f,y=p/g,x=s/2,M=p/2,S=m/2,b=f+1,E=g+1;let T=0,w=0;const A=new gi;for(let s=0;s0?1:-1,c.push(A.x,A.y,A.z),h.push(o/f),h.push(1-s/g),T+=1}}for(let t=0;t0&&(e.defines=this.defines),e.vertexShader=this.vertexShader,e.fragmentShader=this.fragmentShader,e.lights=this.lights,e.clipping=this.clipping;const n={};for(const t in this.extensions)!0===this.extensions[t]&&(n[t]=!0);return Object.keys(n).length>0&&(e.extensions=n),e}}class Os extends vr{constructor(){super(),this.isCamera=!0,this.type="Camera",this.matrixWorldInverse=new ji,this.projectionMatrix=new ji,this.projectionMatrixInverse=new ji}copy(t,e){return super.copy(t,e),this.matrixWorldInverse.copy(t.matrixWorldInverse),this.projectionMatrix.copy(t.projectionMatrix),this.projectionMatrixInverse.copy(t.projectionMatrixInverse),this}getWorldDirection(t){this.updateWorldMatrix(!0,!1);const e=this.matrixWorld.elements;return t.set(-e[8],-e[9],-e[10]).normalize()}updateMatrixWorld(t){super.updateMatrixWorld(t),this.matrixWorldInverse.copy(this.matrixWorld).invert()}updateWorldMatrix(t,e){super.updateWorldMatrix(t,e),this.matrixWorldInverse.copy(this.matrixWorld).invert()}clone(){return(new this.constructor).copy(this)}}class Fs extends Os{constructor(t=50,e=1,n=.1,i=2e3){super(),this.isPerspectiveCamera=!0,this.type="PerspectiveCamera",this.fov=t,this.zoom=1,this.near=n,this.far=i,this.focus=10,this.aspect=e,this.view=null,this.filmGauge=35,this.filmOffset=0,this.updateProjectionMatrix()}copy(t,e){return super.copy(t,e),this.fov=t.fov,this.zoom=t.zoom,this.near=t.near,this.far=t.far,this.focus=t.focus,this.aspect=t.aspect,this.view=null===t.view?null:Object.assign({},t.view),this.filmGauge=t.filmGauge,this.filmOffset=t.filmOffset,this}setFocalLength(t){const e=.5*this.getFilmHeight()/t;this.fov=2*Rn*Math.atan(e),this.updateProjectionMatrix()}getFocalLength(){const t=Math.tan(.5*An*this.fov);return.5*this.getFilmHeight()/t}getEffectiveFOV(){return 2*Rn*Math.atan(Math.tan(.5*An*this.fov)/this.zoom)}getFilmWidth(){return this.filmGauge*Math.min(this.aspect,1)}getFilmHeight(){return this.filmGauge/Math.max(this.aspect,1)}setViewOffset(t,e,n,i,r,s){this.aspect=t/e,null===this.view&&(this.view={enabled:!0,fullWidth:1,fullHeight:1,offsetX:0,offsetY:0,width:1,height:1}),this.view.enabled=!0,this.view.fullWidth=t,this.view.fullHeight=e,this.view.offsetX=n,this.view.offsetY=i,this.view.width=r,this.view.height=s,this.updateProjectionMatrix()}clearViewOffset(){null!==this.view&&(this.view.enabled=!1),this.updateProjectionMatrix()}updateProjectionMatrix(){const t=this.near;let e=t*Math.tan(.5*An*this.fov)/this.zoom,n=2*e,i=this.aspect*n,r=-.5*i;const s=this.view;if(null!==this.view&&this.view.enabled){const t=s.fullWidth,a=s.fullHeight;r+=s.offsetX*i/t,e-=s.offsetY*n/a,i*=s.width/t,n*=s.height/a}const a=this.filmOffset;0!==a&&(r+=t*a/this.getFilmWidth()),this.projectionMatrix.makePerspective(r,r+i,e,e-n,t,this.far),this.projectionMatrixInverse.copy(this.projectionMatrix).invert()}toJSON(t){const e=super.toJSON(t);return e.object.fov=this.fov,e.object.zoom=this.zoom,e.object.near=this.near,e.object.far=this.far,e.object.focus=this.focus,e.object.aspect=this.aspect,null!==this.view&&(e.object.view=Object.assign({},this.view)),e.object.filmGauge=this.filmGauge,e.object.filmOffset=this.filmOffset,e}}const Bs=-90;class zs extends vr{constructor(t,e,n){super(),this.type="CubeCamera",this.renderTarget=n;const i=new Fs(Bs,1,t,e);i.layers=this.layers,i.up.set(0,1,0),i.lookAt(1,0,0),this.add(i);const r=new Fs(Bs,1,t,e);r.layers=this.layers,r.up.set(0,1,0),r.lookAt(-1,0,0),this.add(r);const s=new Fs(Bs,1,t,e);s.layers=this.layers,s.up.set(0,0,-1),s.lookAt(0,1,0),this.add(s);const a=new Fs(Bs,1,t,e);a.layers=this.layers,a.up.set(0,0,1),a.lookAt(0,-1,0),this.add(a);const o=new Fs(Bs,1,t,e);o.layers=this.layers,o.up.set(0,1,0),o.lookAt(0,0,1),this.add(o);const l=new Fs(Bs,1,t,e);l.layers=this.layers,l.up.set(0,1,0),l.lookAt(0,0,-1),this.add(l)}update(t,e){null===this.parent&&this.updateMatrixWorld();const n=this.renderTarget,[i,r,s,a,o,l]=this.children,c=t.getRenderTarget(),h=t.toneMapping,u=t.xr.enabled;t.toneMapping=0,t.xr.enabled=!1;const d=n.texture.generateMipmaps;n.texture.generateMipmaps=!1,t.setRenderTarget(n,0),t.render(e,i),t.setRenderTarget(n,1),t.render(e,r),t.setRenderTarget(n,2),t.render(e,s),t.setRenderTarget(n,3),t.render(e,a),t.setRenderTarget(n,4),t.render(e,o),n.texture.generateMipmaps=d,t.setRenderTarget(n,5),t.render(e,l),t.setRenderTarget(c),t.toneMapping=h,t.xr.enabled=u,n.texture.needsPMREMUpdate=!0}}class Hs extends oi{constructor(t,e,n,i,r,s,a,o,l,c){super(t=void 0!==t?t:[],e=void 0!==e?e:et,n,i,r,s,a,o,l,c),this.isCubeTexture=!0,this.flipY=!1}get images(){return this.image}set images(t){this.image=t}}class ks extends ci{constructor(t=1,e={}){super(t,t,e),this.isWebGLCubeRenderTarget=!0;const n={width:t,height:t,depth:1},i=[n,n,n,n,n,n];void 0!==e.encoding&&(qn("THREE.WebGLCubeRenderTarget: option.encoding has been replaced by option.colorSpace."),e.colorSpace=e.encoding===Ie?Be:Fe),this.texture=new Hs(i,e.mapping,e.wrapS,e.wrapT,e.magFilter,e.minFilter,e.format,e.type,e.anisotropy,e.colorSpace),this.texture.isRenderTargetTexture=!0,this.texture.generateMipmaps=void 0!==e.generateMipmaps&&e.generateMipmaps,this.texture.minFilter=void 0!==e.minFilter?e.minFilter:mt}fromEquirectangularTexture(t,e){this.texture.type=e.type,this.texture.colorSpace=e.colorSpace,this.texture.generateMipmaps=e.generateMipmaps,this.texture.minFilter=e.minFilter,this.texture.magFilter=e.magFilter;const n={uniforms:{tEquirect:{value:null}},vertexShader:"\n\n\t\t\t\tvarying vec3 vWorldDirection;\n\n\t\t\t\tvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n\n\t\t\t\t\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n\n\t\t\t\t}\n\n\t\t\t\tvoid main() {\n\n\t\t\t\t\tvWorldDirection = transformDirection( position, modelMatrix );\n\n\t\t\t\t\t#include \n\t\t\t\t\t#include \n\n\t\t\t\t}\n\t\t\t",fragmentShader:"\n\n\t\t\t\tuniform sampler2D tEquirect;\n\n\t\t\t\tvarying vec3 vWorldDirection;\n\n\t\t\t\t#include \n\n\t\t\t\tvoid main() {\n\n\t\t\t\t\tvec3 direction = normalize( vWorldDirection );\n\n\t\t\t\t\tvec2 sampleUV = equirectUv( direction );\n\n\t\t\t\t\tgl_FragColor = texture2D( tEquirect, sampleUV );\n\n\t\t\t\t}\n\t\t\t"},i=new Ps(5,5,5),r=new Ds({name:"CubemapFromEquirect",uniforms:Ls(n.uniforms),vertexShader:n.vertexShader,fragmentShader:n.fragmentShader,side:1,blending:0});r.uniforms.tEquirect.value=e;const s=new Rs(i,r),a=e.minFilter;e.minFilter===vt&&(e.minFilter=mt);return new zs(1,10,this).update(t,s),e.minFilter=a,s.geometry.dispose(),s.material.dispose(),this}clear(t,e,n,i){const r=t.getRenderTarget();for(let r=0;r<6;r++)t.setRenderTarget(this,r),t.clear(e,n,i);t.setRenderTarget(r)}}const Vs=new gi,Gs=new gi,Ws=new Hn;class Xs{constructor(t=new gi(1,0,0),e=0){this.isPlane=!0,this.normal=t,this.constant=e}set(t,e){return this.normal.copy(t),this.constant=e,this}setComponents(t,e,n,i){return this.normal.set(t,e,n),this.constant=i,this}setFromNormalAndCoplanarPoint(t,e){return this.normal.copy(t),this.constant=-e.dot(this.normal),this}setFromCoplanarPoints(t,e,n){const i=Vs.subVectors(n,e).cross(Gs.subVectors(t,e)).normalize();return this.setFromNormalAndCoplanarPoint(i,t),this}copy(t){return this.normal.copy(t.normal),this.constant=t.constant,this}normalize(){const t=1/this.normal.length();return this.normal.multiplyScalar(t),this.constant*=t,this}negate(){return this.constant*=-1,this.normal.negate(),this}distanceToPoint(t){return this.normal.dot(t)+this.constant}distanceToSphere(t){return this.distanceToPoint(t.center)-t.radius}projectPoint(t,e){return e.copy(t).addScaledVector(this.normal,-this.distanceToPoint(t))}intersectLine(t,e){const n=t.delta(Vs),i=this.normal.dot(n);if(0===i)return 0===this.distanceToPoint(t.start)?e.copy(t.start):null;const r=-(t.start.dot(this.normal)+this.constant)/i;return r<0||r>1?null:e.copy(t.start).addScaledVector(n,r)}intersectsLine(t){const e=this.distanceToPoint(t.start),n=this.distanceToPoint(t.end);return e<0&&n>0||n<0&&e>0}intersectsBox(t){return t.intersectsPlane(this)}intersectsSphere(t){return t.intersectsPlane(this)}coplanarPoint(t){return t.copy(this.normal).multiplyScalar(-this.constant)}applyMatrix4(t,e){const n=e||Ws.getNormalMatrix(t),i=this.coplanarPoint(Vs).applyMatrix4(t),r=this.normal.applyMatrix3(n).normalize();return this.constant=-i.dot(r),this}translate(t){return this.constant-=t.dot(this.normal),this}equals(t){return t.normal.equals(this.normal)&&t.constant===this.constant}clone(){return(new this.constructor).copy(this)}}const js=new Fi,qs=new gi;class Ys{constructor(t=new Xs,e=new Xs,n=new Xs,i=new Xs,r=new Xs,s=new Xs){this.planes=[t,e,n,i,r,s]}set(t,e,n,i,r,s){const a=this.planes;return a[0].copy(t),a[1].copy(e),a[2].copy(n),a[3].copy(i),a[4].copy(r),a[5].copy(s),this}copy(t){const e=this.planes;for(let n=0;n<6;n++)e[n].copy(t.planes[n]);return this}setFromProjectionMatrix(t){const e=this.planes,n=t.elements,i=n[0],r=n[1],s=n[2],a=n[3],o=n[4],l=n[5],c=n[6],h=n[7],u=n[8],d=n[9],p=n[10],m=n[11],f=n[12],g=n[13],v=n[14],_=n[15];return e[0].setComponents(a-i,h-o,m-u,_-f).normalize(),e[1].setComponents(a+i,h+o,m+u,_+f).normalize(),e[2].setComponents(a+r,h+l,m+d,_+g).normalize(),e[3].setComponents(a-r,h-l,m-d,_-g).normalize(),e[4].setComponents(a-s,h-c,m-p,_-v).normalize(),e[5].setComponents(a+s,h+c,m+p,_+v).normalize(),this}intersectsObject(t){if(void 0!==t.boundingSphere)null===t.boundingSphere&&t.computeBoundingSphere(),js.copy(t.boundingSphere).applyMatrix4(t.matrixWorld);else{const e=t.geometry;null===e.boundingSphere&&e.computeBoundingSphere(),js.copy(e.boundingSphere).applyMatrix4(t.matrixWorld)}return this.intersectsSphere(js)}intersectsSprite(t){return js.center.set(0,0,0),js.radius=.7071067811865476,js.applyMatrix4(t.matrixWorld),this.intersectsSphere(js)}intersectsSphere(t){const e=this.planes,n=t.center,i=-t.radius;for(let t=0;t<6;t++){if(e[t].distanceToPoint(n)0?t.max.x:t.min.x,qs.y=i.normal.y>0?t.max.y:t.min.y,qs.z=i.normal.z>0?t.max.z:t.min.z,i.distanceToPoint(qs)<0)return!1}return!0}containsPoint(t){const e=this.planes;for(let n=0;n<6;n++)if(e[n].distanceToPoint(t)<0)return!1;return!0}clone(){return(new this.constructor).copy(this)}}function Zs(){let t=null,e=!1,n=null,i=null;function r(e,s){n(e,s),i=t.requestAnimationFrame(r)}return{start:function(){!0!==e&&null!==n&&(i=t.requestAnimationFrame(r),e=!0)},stop:function(){t.cancelAnimationFrame(i),e=!1},setAnimationLoop:function(t){n=t},setContext:function(e){t=e}}}function Js(t,e){const n=e.isWebGL2,i=new WeakMap;return{get:function(t){return t.isInterleavedBufferAttribute&&(t=t.data),i.get(t)},remove:function(e){e.isInterleavedBufferAttribute&&(e=e.data);const n=i.get(e);n&&(t.deleteBuffer(n.buffer),i.delete(e))},update:function(e,r){if(e.isGLBufferAttribute){const t=i.get(e);return void((!t||t.version 0\n\tvec4 plane;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) {\n\t\tplane = clippingPlanes[ i ];\n\t\tif ( dot( vClipPosition, plane.xyz ) > plane.w ) discard;\n\t}\n\t#pragma unroll_loop_end\n\t#if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES\n\t\tbool clipped = true;\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) {\n\t\t\tplane = clippingPlanes[ i ];\n\t\t\tclipped = ( dot( vClipPosition, plane.xyz ) > plane.w ) && clipped;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t\tif ( clipped ) discard;\n\t#endif\n#endif",clipping_planes_pars_fragment:"#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n\tuniform vec4 clippingPlanes[ NUM_CLIPPING_PLANES ];\n#endif",clipping_planes_pars_vertex:"#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n#endif",clipping_planes_vertex:"#if NUM_CLIPPING_PLANES > 0\n\tvClipPosition = - mvPosition.xyz;\n#endif",color_fragment:"#if defined( USE_COLOR_ALPHA )\n\tdiffuseColor *= vColor;\n#elif defined( USE_COLOR )\n\tdiffuseColor.rgb *= vColor;\n#endif",color_pars_fragment:"#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR )\n\tvarying vec3 vColor;\n#endif",color_pars_vertex:"#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR )\n\tvarying vec3 vColor;\n#endif",color_vertex:"#if defined( USE_COLOR_ALPHA )\n\tvColor = vec4( 1.0 );\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR )\n\tvColor = vec3( 1.0 );\n#endif\n#ifdef USE_COLOR\n\tvColor *= color;\n#endif\n#ifdef USE_INSTANCING_COLOR\n\tvColor.xyz *= instanceColor.xyz;\n#endif",common:"#define PI 3.141592653589793\n#define PI2 6.283185307179586\n#define PI_HALF 1.5707963267948966\n#define RECIPROCAL_PI 0.3183098861837907\n#define RECIPROCAL_PI2 0.15915494309189535\n#define EPSILON 1e-6\n#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\n#define whiteComplement( a ) ( 1.0 - saturate( a ) )\nfloat pow2( const in float x ) { return x*x; }\nvec3 pow2( const in vec3 x ) { return x*x; }\nfloat pow3( const in float x ) { return x*x*x; }\nfloat pow4( const in float x ) { float x2 = x*x; return x2*x2; }\nfloat max3( const in vec3 v ) { return max( max( v.x, v.y ), v.z ); }\nfloat average( const in vec3 v ) { return dot( v, vec3( 0.3333333 ) ); }\nhighp float rand( const in vec2 uv ) {\n\tconst highp float a = 12.9898, b = 78.233, c = 43758.5453;\n\thighp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI );\n\treturn fract( sin( sn ) * c );\n}\n#ifdef HIGH_PRECISION\n\tfloat precisionSafeLength( vec3 v ) { return length( v ); }\n#else\n\tfloat precisionSafeLength( vec3 v ) {\n\t\tfloat maxComponent = max3( abs( v ) );\n\t\treturn length( v / maxComponent ) * maxComponent;\n\t}\n#endif\nstruct IncidentLight {\n\tvec3 color;\n\tvec3 direction;\n\tbool visible;\n};\nstruct ReflectedLight {\n\tvec3 directDiffuse;\n\tvec3 directSpecular;\n\tvec3 indirectDiffuse;\n\tvec3 indirectSpecular;\n};\nstruct GeometricContext {\n\tvec3 position;\n\tvec3 normal;\n\tvec3 viewDir;\n#ifdef USE_CLEARCOAT\n\tvec3 clearcoatNormal;\n#endif\n};\nvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n}\nvec3 inverseTransformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( vec4( dir, 0.0 ) * matrix ).xyz );\n}\nmat3 transposeMat3( const in mat3 m ) {\n\tmat3 tmp;\n\ttmp[ 0 ] = vec3( m[ 0 ].x, m[ 1 ].x, m[ 2 ].x );\n\ttmp[ 1 ] = vec3( m[ 0 ].y, m[ 1 ].y, m[ 2 ].y );\n\ttmp[ 2 ] = vec3( m[ 0 ].z, m[ 1 ].z, m[ 2 ].z );\n\treturn tmp;\n}\nfloat luminance( const in vec3 rgb ) {\n\tconst vec3 weights = vec3( 0.2126729, 0.7151522, 0.0721750 );\n\treturn dot( weights, rgb );\n}\nbool isPerspectiveMatrix( mat4 m ) {\n\treturn m[ 2 ][ 3 ] == - 1.0;\n}\nvec2 equirectUv( in vec3 dir ) {\n\tfloat u = atan( dir.z, dir.x ) * RECIPROCAL_PI2 + 0.5;\n\tfloat v = asin( clamp( dir.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\n\treturn vec2( u, v );\n}\nvec3 BRDF_Lambert( const in vec3 diffuseColor ) {\n\treturn RECIPROCAL_PI * diffuseColor;\n}\nvec3 F_Schlick( const in vec3 f0, const in float f90, const in float dotVH ) {\n\tfloat fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );\n\treturn f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );\n}\nfloat F_Schlick( const in float f0, const in float f90, const in float dotVH ) {\n\tfloat fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );\n\treturn f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );\n} // validated",cube_uv_reflection_fragment:"#ifdef ENVMAP_TYPE_CUBE_UV\n\t#define cubeUV_minMipLevel 4.0\n\t#define cubeUV_minTileSize 16.0\n\tfloat getFace( vec3 direction ) {\n\t\tvec3 absDirection = abs( direction );\n\t\tfloat face = - 1.0;\n\t\tif ( absDirection.x > absDirection.z ) {\n\t\t\tif ( absDirection.x > absDirection.y )\n\t\t\t\tface = direction.x > 0.0 ? 0.0 : 3.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t} else {\n\t\t\tif ( absDirection.z > absDirection.y )\n\t\t\t\tface = direction.z > 0.0 ? 2.0 : 5.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t}\n\t\treturn face;\n\t}\n\tvec2 getUV( vec3 direction, float face ) {\n\t\tvec2 uv;\n\t\tif ( face == 0.0 ) {\n\t\t\tuv = vec2( direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 1.0 ) {\n\t\t\tuv = vec2( - direction.x, - direction.z ) / abs( direction.y );\n\t\t} else if ( face == 2.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.y ) / abs( direction.z );\n\t\t} else if ( face == 3.0 ) {\n\t\t\tuv = vec2( - direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 4.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.z ) / abs( direction.y );\n\t\t} else {\n\t\t\tuv = vec2( direction.x, direction.y ) / abs( direction.z );\n\t\t}\n\t\treturn 0.5 * ( uv + 1.0 );\n\t}\n\tvec3 bilinearCubeUV( sampler2D envMap, vec3 direction, float mipInt ) {\n\t\tfloat face = getFace( direction );\n\t\tfloat filterInt = max( cubeUV_minMipLevel - mipInt, 0.0 );\n\t\tmipInt = max( mipInt, cubeUV_minMipLevel );\n\t\tfloat faceSize = exp2( mipInt );\n\t\thighp vec2 uv = getUV( direction, face ) * ( faceSize - 2.0 ) + 1.0;\n\t\tif ( face > 2.0 ) {\n\t\t\tuv.y += faceSize;\n\t\t\tface -= 3.0;\n\t\t}\n\t\tuv.x += face * faceSize;\n\t\tuv.x += filterInt * 3.0 * cubeUV_minTileSize;\n\t\tuv.y += 4.0 * ( exp2( CUBEUV_MAX_MIP ) - faceSize );\n\t\tuv.x *= CUBEUV_TEXEL_WIDTH;\n\t\tuv.y *= CUBEUV_TEXEL_HEIGHT;\n\t\t#ifdef texture2DGradEXT\n\t\t\treturn texture2DGradEXT( envMap, uv, vec2( 0.0 ), vec2( 0.0 ) ).rgb;\n\t\t#else\n\t\t\treturn texture2D( envMap, uv ).rgb;\n\t\t#endif\n\t}\n\t#define cubeUV_r0 1.0\n\t#define cubeUV_v0 0.339\n\t#define cubeUV_m0 - 2.0\n\t#define cubeUV_r1 0.8\n\t#define cubeUV_v1 0.276\n\t#define cubeUV_m1 - 1.0\n\t#define cubeUV_r4 0.4\n\t#define cubeUV_v4 0.046\n\t#define cubeUV_m4 2.0\n\t#define cubeUV_r5 0.305\n\t#define cubeUV_v5 0.016\n\t#define cubeUV_m5 3.0\n\t#define cubeUV_r6 0.21\n\t#define cubeUV_v6 0.0038\n\t#define cubeUV_m6 4.0\n\tfloat roughnessToMip( float roughness ) {\n\t\tfloat mip = 0.0;\n\t\tif ( roughness >= cubeUV_r1 ) {\n\t\t\tmip = ( cubeUV_r0 - roughness ) * ( cubeUV_m1 - cubeUV_m0 ) / ( cubeUV_r0 - cubeUV_r1 ) + cubeUV_m0;\n\t\t} else if ( roughness >= cubeUV_r4 ) {\n\t\t\tmip = ( cubeUV_r1 - roughness ) * ( cubeUV_m4 - cubeUV_m1 ) / ( cubeUV_r1 - cubeUV_r4 ) + cubeUV_m1;\n\t\t} else if ( roughness >= cubeUV_r5 ) {\n\t\t\tmip = ( cubeUV_r4 - roughness ) * ( cubeUV_m5 - cubeUV_m4 ) / ( cubeUV_r4 - cubeUV_r5 ) + cubeUV_m4;\n\t\t} else if ( roughness >= cubeUV_r6 ) {\n\t\t\tmip = ( cubeUV_r5 - roughness ) * ( cubeUV_m6 - cubeUV_m5 ) / ( cubeUV_r5 - cubeUV_r6 ) + cubeUV_m5;\n\t\t} else {\n\t\t\tmip = - 2.0 * log2( 1.16 * roughness );\t\t}\n\t\treturn mip;\n\t}\n\tvec4 textureCubeUV( sampler2D envMap, vec3 sampleDir, float roughness ) {\n\t\tfloat mip = clamp( roughnessToMip( roughness ), cubeUV_m0, CUBEUV_MAX_MIP );\n\t\tfloat mipF = fract( mip );\n\t\tfloat mipInt = floor( mip );\n\t\tvec3 color0 = bilinearCubeUV( envMap, sampleDir, mipInt );\n\t\tif ( mipF == 0.0 ) {\n\t\t\treturn vec4( color0, 1.0 );\n\t\t} else {\n\t\t\tvec3 color1 = bilinearCubeUV( envMap, sampleDir, mipInt + 1.0 );\n\t\t\treturn vec4( mix( color0, color1, mipF ), 1.0 );\n\t\t}\n\t}\n#endif",defaultnormal_vertex:"vec3 transformedNormal = objectNormal;\n#ifdef USE_INSTANCING\n\tmat3 m = mat3( instanceMatrix );\n\ttransformedNormal /= vec3( dot( m[ 0 ], m[ 0 ] ), dot( m[ 1 ], m[ 1 ] ), dot( m[ 2 ], m[ 2 ] ) );\n\ttransformedNormal = m * transformedNormal;\n#endif\ntransformedNormal = normalMatrix * transformedNormal;\n#ifdef FLIP_SIDED\n\ttransformedNormal = - transformedNormal;\n#endif\n#ifdef USE_TANGENT\n\tvec3 transformedTangent = ( modelViewMatrix * vec4( objectTangent, 0.0 ) ).xyz;\n\t#ifdef FLIP_SIDED\n\t\ttransformedTangent = - transformedTangent;\n\t#endif\n#endif",displacementmap_pars_vertex:"#ifdef USE_DISPLACEMENTMAP\n\tuniform sampler2D displacementMap;\n\tuniform float displacementScale;\n\tuniform float displacementBias;\n#endif",displacementmap_vertex:"#ifdef USE_DISPLACEMENTMAP\n\ttransformed += normalize( objectNormal ) * ( texture2D( displacementMap, vDisplacementMapUv ).x * displacementScale + displacementBias );\n#endif",emissivemap_fragment:"#ifdef USE_EMISSIVEMAP\n\tvec4 emissiveColor = texture2D( emissiveMap, vEmissiveMapUv );\n\ttotalEmissiveRadiance *= emissiveColor.rgb;\n#endif",emissivemap_pars_fragment:"#ifdef USE_EMISSIVEMAP\n\tuniform sampler2D emissiveMap;\n#endif",encodings_fragment:"gl_FragColor = linearToOutputTexel( gl_FragColor );",encodings_pars_fragment:"vec4 LinearToLinear( in vec4 value ) {\n\treturn value;\n}\nvec4 LinearTosRGB( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a );\n}",envmap_fragment:"#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvec3 cameraToFrag;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToFrag = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToFrag = normalize( vWorldPosition - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvec3 reflectVec = reflect( cameraToFrag, worldNormal );\n\t\t#else\n\t\t\tvec3 reflectVec = refract( cameraToFrag, worldNormal, refractionRatio );\n\t\t#endif\n\t#else\n\t\tvec3 reflectVec = vReflect;\n\t#endif\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 envColor = textureCube( envMap, vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );\n\t#else\n\t\tvec4 envColor = vec4( 0.0 );\n\t#endif\n\t#ifdef ENVMAP_BLENDING_MULTIPLY\n\t\toutgoingLight = mix( outgoingLight, outgoingLight * envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_MIX )\n\t\toutgoingLight = mix( outgoingLight, envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_ADD )\n\t\toutgoingLight += envColor.xyz * specularStrength * reflectivity;\n\t#endif\n#endif",envmap_common_pars_fragment:"#ifdef USE_ENVMAP\n\tuniform float envMapIntensity;\n\tuniform float flipEnvMap;\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tuniform samplerCube envMap;\n\t#else\n\t\tuniform sampler2D envMap;\n\t#endif\n\t\n#endif",envmap_pars_fragment:"#ifdef USE_ENVMAP\n\tuniform float reflectivity;\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( LAMBERT )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\tvarying vec3 vWorldPosition;\n\t\tuniform float refractionRatio;\n\t#else\n\t\tvarying vec3 vReflect;\n\t#endif\n#endif",envmap_pars_vertex:"#ifdef USE_ENVMAP\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( LAMBERT )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\t\n\t\tvarying vec3 vWorldPosition;\n\t#else\n\t\tvarying vec3 vReflect;\n\t\tuniform float refractionRatio;\n\t#endif\n#endif",envmap_physical_pars_fragment:"#ifdef USE_ENVMAP\n\tvec3 getIBLIrradiance( const in vec3 normal ) {\n\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, worldNormal, 1.0 );\n\t\t\treturn PI * envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n\tvec3 getIBLRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness ) {\n\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\tvec3 reflectVec = reflect( - viewDir, normal );\n\t\t\treflectVec = normalize( mix( reflectVec, normal, roughness * roughness) );\n\t\t\treflectVec = inverseTransformDirection( reflectVec, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, reflectVec, roughness );\n\t\t\treturn envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n\t#ifdef USE_ANISOTROPY\n\t\tvec3 getIBLAnisotropyRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness, const in vec3 bitangent, const in float anisotropy ) {\n\t\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\t\tvec3 bentNormal = cross( bitangent, viewDir );\n\t\t\t\tbentNormal = normalize( cross( bentNormal, bitangent ) );\n\t\t\t\tbentNormal = normalize( mix( bentNormal, normal, pow2( pow2( 1.0 - anisotropy * ( 1.0 - roughness ) ) ) ) );\n\t\t\t\treturn getIBLRadiance( viewDir, bentNormal, roughness );\n\t\t\t#else\n\t\t\t\treturn vec3( 0.0 );\n\t\t\t#endif\n\t\t}\n\t#endif\n#endif",envmap_vertex:"#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvWorldPosition = worldPosition.xyz;\n\t#else\n\t\tvec3 cameraToVertex;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToVertex = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToVertex = normalize( worldPosition.xyz - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvReflect = reflect( cameraToVertex, worldNormal );\n\t\t#else\n\t\t\tvReflect = refract( cameraToVertex, worldNormal, refractionRatio );\n\t\t#endif\n\t#endif\n#endif",fog_vertex:"#ifdef USE_FOG\n\tvFogDepth = - mvPosition.z;\n#endif",fog_pars_vertex:"#ifdef USE_FOG\n\tvarying float vFogDepth;\n#endif",fog_fragment:"#ifdef USE_FOG\n\t#ifdef FOG_EXP2\n\t\tfloat fogFactor = 1.0 - exp( - fogDensity * fogDensity * vFogDepth * vFogDepth );\n\t#else\n\t\tfloat fogFactor = smoothstep( fogNear, fogFar, vFogDepth );\n\t#endif\n\tgl_FragColor.rgb = mix( gl_FragColor.rgb, fogColor, fogFactor );\n#endif",fog_pars_fragment:"#ifdef USE_FOG\n\tuniform vec3 fogColor;\n\tvarying float vFogDepth;\n\t#ifdef FOG_EXP2\n\t\tuniform float fogDensity;\n\t#else\n\t\tuniform float fogNear;\n\t\tuniform float fogFar;\n\t#endif\n#endif",gradientmap_pars_fragment:"#ifdef USE_GRADIENTMAP\n\tuniform sampler2D gradientMap;\n#endif\nvec3 getGradientIrradiance( vec3 normal, vec3 lightDirection ) {\n\tfloat dotNL = dot( normal, lightDirection );\n\tvec2 coord = vec2( dotNL * 0.5 + 0.5, 0.0 );\n\t#ifdef USE_GRADIENTMAP\n\t\treturn vec3( texture2D( gradientMap, coord ).r );\n\t#else\n\t\tvec2 fw = fwidth( coord ) * 0.5;\n\t\treturn mix( vec3( 0.7 ), vec3( 1.0 ), smoothstep( 0.7 - fw.x, 0.7 + fw.x, coord.x ) );\n\t#endif\n}",lightmap_fragment:"#ifdef USE_LIGHTMAP\n\tvec4 lightMapTexel = texture2D( lightMap, vLightMapUv );\n\tvec3 lightMapIrradiance = lightMapTexel.rgb * lightMapIntensity;\n\treflectedLight.indirectDiffuse += lightMapIrradiance;\n#endif",lightmap_pars_fragment:"#ifdef USE_LIGHTMAP\n\tuniform sampler2D lightMap;\n\tuniform float lightMapIntensity;\n#endif",lights_lambert_fragment:"LambertMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularStrength = specularStrength;",lights_lambert_pars_fragment:"varying vec3 vViewPosition;\nstruct LambertMaterial {\n\tvec3 diffuseColor;\n\tfloat specularStrength;\n};\nvoid RE_Direct_Lambert( const in IncidentLight directLight, const in GeometricContext geometry, const in LambertMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Lambert( const in vec3 irradiance, const in GeometricContext geometry, const in LambertMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Lambert\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Lambert",lights_pars_begin:"uniform bool receiveShadow;\nuniform vec3 ambientLightColor;\nuniform vec3 lightProbe[ 9 ];\nvec3 shGetIrradianceAt( in vec3 normal, in vec3 shCoefficients[ 9 ] ) {\n\tfloat x = normal.x, y = normal.y, z = normal.z;\n\tvec3 result = shCoefficients[ 0 ] * 0.886227;\n\tresult += shCoefficients[ 1 ] * 2.0 * 0.511664 * y;\n\tresult += shCoefficients[ 2 ] * 2.0 * 0.511664 * z;\n\tresult += shCoefficients[ 3 ] * 2.0 * 0.511664 * x;\n\tresult += shCoefficients[ 4 ] * 2.0 * 0.429043 * x * y;\n\tresult += shCoefficients[ 5 ] * 2.0 * 0.429043 * y * z;\n\tresult += shCoefficients[ 6 ] * ( 0.743125 * z * z - 0.247708 );\n\tresult += shCoefficients[ 7 ] * 2.0 * 0.429043 * x * z;\n\tresult += shCoefficients[ 8 ] * 0.429043 * ( x * x - y * y );\n\treturn result;\n}\nvec3 getLightProbeIrradiance( const in vec3 lightProbe[ 9 ], const in vec3 normal ) {\n\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\tvec3 irradiance = shGetIrradianceAt( worldNormal, lightProbe );\n\treturn irradiance;\n}\nvec3 getAmbientLightIrradiance( const in vec3 ambientLightColor ) {\n\tvec3 irradiance = ambientLightColor;\n\treturn irradiance;\n}\nfloat getDistanceAttenuation( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) {\n\t#if defined ( LEGACY_LIGHTS )\n\t\tif ( cutoffDistance > 0.0 && decayExponent > 0.0 ) {\n\t\t\treturn pow( saturate( - lightDistance / cutoffDistance + 1.0 ), decayExponent );\n\t\t}\n\t\treturn 1.0;\n\t#else\n\t\tfloat distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );\n\t\tif ( cutoffDistance > 0.0 ) {\n\t\t\tdistanceFalloff *= pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );\n\t\t}\n\t\treturn distanceFalloff;\n\t#endif\n}\nfloat getSpotAttenuation( const in float coneCosine, const in float penumbraCosine, const in float angleCosine ) {\n\treturn smoothstep( coneCosine, penumbraCosine, angleCosine );\n}\n#if NUM_DIR_LIGHTS > 0\n\tstruct DirectionalLight {\n\t\tvec3 direction;\n\t\tvec3 color;\n\t};\n\tuniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ];\n\tvoid getDirectionalLightInfo( const in DirectionalLight directionalLight, const in GeometricContext geometry, out IncidentLight light ) {\n\t\tlight.color = directionalLight.color;\n\t\tlight.direction = directionalLight.direction;\n\t\tlight.visible = true;\n\t}\n#endif\n#if NUM_POINT_LIGHTS > 0\n\tstruct PointLight {\n\t\tvec3 position;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t};\n\tuniform PointLight pointLights[ NUM_POINT_LIGHTS ];\n\tvoid getPointLightInfo( const in PointLight pointLight, const in GeometricContext geometry, out IncidentLight light ) {\n\t\tvec3 lVector = pointLight.position - geometry.position;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat lightDistance = length( lVector );\n\t\tlight.color = pointLight.color;\n\t\tlight.color *= getDistanceAttenuation( lightDistance, pointLight.distance, pointLight.decay );\n\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t}\n#endif\n#if NUM_SPOT_LIGHTS > 0\n\tstruct SpotLight {\n\t\tvec3 position;\n\t\tvec3 direction;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t\tfloat coneCos;\n\t\tfloat penumbraCos;\n\t};\n\tuniform SpotLight spotLights[ NUM_SPOT_LIGHTS ];\n\tvoid getSpotLightInfo( const in SpotLight spotLight, const in GeometricContext geometry, out IncidentLight light ) {\n\t\tvec3 lVector = spotLight.position - geometry.position;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat angleCos = dot( light.direction, spotLight.direction );\n\t\tfloat spotAttenuation = getSpotAttenuation( spotLight.coneCos, spotLight.penumbraCos, angleCos );\n\t\tif ( spotAttenuation > 0.0 ) {\n\t\t\tfloat lightDistance = length( lVector );\n\t\t\tlight.color = spotLight.color * spotAttenuation;\n\t\t\tlight.color *= getDistanceAttenuation( lightDistance, spotLight.distance, spotLight.decay );\n\t\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t\t} else {\n\t\t\tlight.color = vec3( 0.0 );\n\t\t\tlight.visible = false;\n\t\t}\n\t}\n#endif\n#if NUM_RECT_AREA_LIGHTS > 0\n\tstruct RectAreaLight {\n\t\tvec3 color;\n\t\tvec3 position;\n\t\tvec3 halfWidth;\n\t\tvec3 halfHeight;\n\t};\n\tuniform sampler2D ltc_1;\tuniform sampler2D ltc_2;\n\tuniform RectAreaLight rectAreaLights[ NUM_RECT_AREA_LIGHTS ];\n#endif\n#if NUM_HEMI_LIGHTS > 0\n\tstruct HemisphereLight {\n\t\tvec3 direction;\n\t\tvec3 skyColor;\n\t\tvec3 groundColor;\n\t};\n\tuniform HemisphereLight hemisphereLights[ NUM_HEMI_LIGHTS ];\n\tvec3 getHemisphereLightIrradiance( const in HemisphereLight hemiLight, const in vec3 normal ) {\n\t\tfloat dotNL = dot( normal, hemiLight.direction );\n\t\tfloat hemiDiffuseWeight = 0.5 * dotNL + 0.5;\n\t\tvec3 irradiance = mix( hemiLight.groundColor, hemiLight.skyColor, hemiDiffuseWeight );\n\t\treturn irradiance;\n\t}\n#endif",lights_toon_fragment:"ToonMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;",lights_toon_pars_fragment:"varying vec3 vViewPosition;\nstruct ToonMaterial {\n\tvec3 diffuseColor;\n};\nvoid RE_Direct_Toon( const in IncidentLight directLight, const in GeometricContext geometry, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\tvec3 irradiance = getGradientIrradiance( geometry.normal, directLight.direction ) * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Toon( const in vec3 irradiance, const in GeometricContext geometry, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Toon\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Toon",lights_phong_fragment:"BlinnPhongMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularColor = specular;\nmaterial.specularShininess = shininess;\nmaterial.specularStrength = specularStrength;",lights_phong_pars_fragment:"varying vec3 vViewPosition;\nstruct BlinnPhongMaterial {\n\tvec3 diffuseColor;\n\tvec3 specularColor;\n\tfloat specularShininess;\n\tfloat specularStrength;\n};\nvoid RE_Direct_BlinnPhong( const in IncidentLight directLight, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n\treflectedLight.directSpecular += irradiance * BRDF_BlinnPhong( directLight.direction, geometry.viewDir, geometry.normal, material.specularColor, material.specularShininess ) * material.specularStrength;\n}\nvoid RE_IndirectDiffuse_BlinnPhong( const in vec3 irradiance, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_BlinnPhong\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_BlinnPhong",lights_physical_fragment:"PhysicalMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb * ( 1.0 - metalnessFactor );\nvec3 dxy = max( abs( dFdx( geometryNormal ) ), abs( dFdy( geometryNormal ) ) );\nfloat geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z );\nmaterial.roughness = max( roughnessFactor, 0.0525 );material.roughness += geometryRoughness;\nmaterial.roughness = min( material.roughness, 1.0 );\n#ifdef IOR\n\tmaterial.ior = ior;\n\t#ifdef USE_SPECULAR\n\t\tfloat specularIntensityFactor = specularIntensity;\n\t\tvec3 specularColorFactor = specularColor;\n\t\t#ifdef USE_SPECULAR_COLORMAP\n\t\t\tspecularColorFactor *= texture2D( specularColorMap, vSpecularColorMapUv ).rgb;\n\t\t#endif\n\t\t#ifdef USE_SPECULAR_INTENSITYMAP\n\t\t\tspecularIntensityFactor *= texture2D( specularIntensityMap, vSpecularIntensityMapUv ).a;\n\t\t#endif\n\t\tmaterial.specularF90 = mix( specularIntensityFactor, 1.0, metalnessFactor );\n\t#else\n\t\tfloat specularIntensityFactor = 1.0;\n\t\tvec3 specularColorFactor = vec3( 1.0 );\n\t\tmaterial.specularF90 = 1.0;\n\t#endif\n\tmaterial.specularColor = mix( min( pow2( ( material.ior - 1.0 ) / ( material.ior + 1.0 ) ) * specularColorFactor, vec3( 1.0 ) ) * specularIntensityFactor, diffuseColor.rgb, metalnessFactor );\n#else\n\tmaterial.specularColor = mix( vec3( 0.04 ), diffuseColor.rgb, metalnessFactor );\n\tmaterial.specularF90 = 1.0;\n#endif\n#ifdef USE_CLEARCOAT\n\tmaterial.clearcoat = clearcoat;\n\tmaterial.clearcoatRoughness = clearcoatRoughness;\n\tmaterial.clearcoatF0 = vec3( 0.04 );\n\tmaterial.clearcoatF90 = 1.0;\n\t#ifdef USE_CLEARCOATMAP\n\t\tmaterial.clearcoat *= texture2D( clearcoatMap, vClearcoatMapUv ).x;\n\t#endif\n\t#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\t\tmaterial.clearcoatRoughness *= texture2D( clearcoatRoughnessMap, vClearcoatRoughnessMapUv ).y;\n\t#endif\n\tmaterial.clearcoat = saturate( material.clearcoat );\tmaterial.clearcoatRoughness = max( material.clearcoatRoughness, 0.0525 );\n\tmaterial.clearcoatRoughness += geometryRoughness;\n\tmaterial.clearcoatRoughness = min( material.clearcoatRoughness, 1.0 );\n#endif\n#ifdef USE_IRIDESCENCE\n\tmaterial.iridescence = iridescence;\n\tmaterial.iridescenceIOR = iridescenceIOR;\n\t#ifdef USE_IRIDESCENCEMAP\n\t\tmaterial.iridescence *= texture2D( iridescenceMap, vIridescenceMapUv ).r;\n\t#endif\n\t#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\t\tmaterial.iridescenceThickness = (iridescenceThicknessMaximum - iridescenceThicknessMinimum) * texture2D( iridescenceThicknessMap, vIridescenceThicknessMapUv ).g + iridescenceThicknessMinimum;\n\t#else\n\t\tmaterial.iridescenceThickness = iridescenceThicknessMaximum;\n\t#endif\n#endif\n#ifdef USE_SHEEN\n\tmaterial.sheenColor = sheenColor;\n\t#ifdef USE_SHEEN_COLORMAP\n\t\tmaterial.sheenColor *= texture2D( sheenColorMap, vSheenColorMapUv ).rgb;\n\t#endif\n\tmaterial.sheenRoughness = clamp( sheenRoughness, 0.07, 1.0 );\n\t#ifdef USE_SHEEN_ROUGHNESSMAP\n\t\tmaterial.sheenRoughness *= texture2D( sheenRoughnessMap, vSheenRoughnessMapUv ).a;\n\t#endif\n#endif\n#ifdef USE_ANISOTROPY\n\t#ifdef USE_ANISOTROPYMAP\n\t\tmat2 anisotropyMat = mat2( anisotropyVector.x, anisotropyVector.y, - anisotropyVector.y, anisotropyVector.x );\n\t\tvec3 anisotropyPolar = texture2D( anisotropyMap, vAnisotropyMapUv ).rgb;\n\t\tvec2 anisotropyV = anisotropyMat * normalize( 2.0 * anisotropyPolar.rg - vec2( 1.0 ) ) * anisotropyPolar.b;\n\t#else\n\t\tvec2 anisotropyV = anisotropyVector;\n\t#endif\n\tmaterial.anisotropy = length( anisotropyV );\n\tanisotropyV /= material.anisotropy;\n\tmaterial.anisotropy = saturate( material.anisotropy );\n\tmaterial.alphaT = mix( pow2( material.roughness ), 1.0, pow2( material.anisotropy ) );\n\tmaterial.anisotropyT = tbn[ 0 ] * anisotropyV.x - tbn[ 1 ] * anisotropyV.y;\n\tmaterial.anisotropyB = tbn[ 1 ] * anisotropyV.x + tbn[ 0 ] * anisotropyV.y;\n#endif",lights_physical_pars_fragment:"struct PhysicalMaterial {\n\tvec3 diffuseColor;\n\tfloat roughness;\n\tvec3 specularColor;\n\tfloat specularF90;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat clearcoat;\n\t\tfloat clearcoatRoughness;\n\t\tvec3 clearcoatF0;\n\t\tfloat clearcoatF90;\n\t#endif\n\t#ifdef USE_IRIDESCENCE\n\t\tfloat iridescence;\n\t\tfloat iridescenceIOR;\n\t\tfloat iridescenceThickness;\n\t\tvec3 iridescenceFresnel;\n\t\tvec3 iridescenceF0;\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tvec3 sheenColor;\n\t\tfloat sheenRoughness;\n\t#endif\n\t#ifdef IOR\n\t\tfloat ior;\n\t#endif\n\t#ifdef USE_TRANSMISSION\n\t\tfloat transmission;\n\t\tfloat transmissionAlpha;\n\t\tfloat thickness;\n\t\tfloat attenuationDistance;\n\t\tvec3 attenuationColor;\n\t#endif\n\t#ifdef USE_ANISOTROPY\n\t\tfloat anisotropy;\n\t\tfloat alphaT;\n\t\tvec3 anisotropyT;\n\t\tvec3 anisotropyB;\n\t#endif\n};\nvec3 clearcoatSpecular = vec3( 0.0 );\nvec3 sheenSpecular = vec3( 0.0 );\nvec3 Schlick_to_F0( const in vec3 f, const in float f90, const in float dotVH ) {\n float x = clamp( 1.0 - dotVH, 0.0, 1.0 );\n float x2 = x * x;\n float x5 = clamp( x * x2 * x2, 0.0, 0.9999 );\n return ( f - vec3( f90 ) * x5 ) / ( 1.0 - x5 );\n}\nfloat V_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const in float dotNV ) {\n\tfloat a2 = pow2( alpha );\n\tfloat gv = dotNL * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );\n\tfloat gl = dotNV * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );\n\treturn 0.5 / max( gv + gl, EPSILON );\n}\nfloat D_GGX( const in float alpha, const in float dotNH ) {\n\tfloat a2 = pow2( alpha );\n\tfloat denom = pow2( dotNH ) * ( a2 - 1.0 ) + 1.0;\n\treturn RECIPROCAL_PI * a2 / pow2( denom );\n}\n#ifdef USE_ANISOTROPY\n\tfloat V_GGX_SmithCorrelated_Anisotropic( const in float alphaT, const in float alphaB, const in float dotTV, const in float dotBV, const in float dotTL, const in float dotBL, const in float dotNV, const in float dotNL ) {\n\t\tfloat gv = dotNL * length( vec3( alphaT * dotTV, alphaB * dotBV, dotNV ) );\n\t\tfloat gl = dotNV * length( vec3( alphaT * dotTL, alphaB * dotBL, dotNL ) );\n\t\tfloat v = 0.5 / ( gv + gl );\n\t\treturn saturate(v);\n\t}\n\tfloat D_GGX_Anisotropic( const in float alphaT, const in float alphaB, const in float dotNH, const in float dotTH, const in float dotBH ) {\n\t\tfloat a2 = alphaT * alphaB;\n\t\thighp vec3 v = vec3( alphaB * dotTH, alphaT * dotBH, a2 * dotNH );\n\t\thighp float v2 = dot( v, v );\n\t\tfloat w2 = a2 / v2;\n\t\treturn RECIPROCAL_PI * a2 * pow2 ( w2 );\n\t}\n#endif\n#ifdef USE_CLEARCOAT\n\tvec3 BRDF_GGX_Clearcoat( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in PhysicalMaterial material) {\n\t\tvec3 f0 = material.clearcoatF0;\n\t\tfloat f90 = material.clearcoatF90;\n\t\tfloat roughness = material.clearcoatRoughness;\n\t\tfloat alpha = pow2( roughness );\n\t\tvec3 halfDir = normalize( lightDir + viewDir );\n\t\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\t\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\t\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\t\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\t\tvec3 F = F_Schlick( f0, f90, dotVH );\n\t\tfloat V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\t\tfloat D = D_GGX( alpha, dotNH );\n\t\treturn F * ( V * D );\n\t}\n#endif\nvec3 BRDF_GGX( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in PhysicalMaterial material ) {\n\tvec3 f0 = material.specularColor;\n\tfloat f90 = material.specularF90;\n\tfloat roughness = material.roughness;\n\tfloat alpha = pow2( roughness );\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\tvec3 F = F_Schlick( f0, f90, dotVH );\n\t#ifdef USE_IRIDESCENCE\n\t\tF = mix( F, material.iridescenceFresnel, material.iridescence );\n\t#endif\n\t#ifdef USE_ANISOTROPY\n\t\tfloat dotTL = dot( material.anisotropyT, lightDir );\n\t\tfloat dotTV = dot( material.anisotropyT, viewDir );\n\t\tfloat dotTH = dot( material.anisotropyT, halfDir );\n\t\tfloat dotBL = dot( material.anisotropyB, lightDir );\n\t\tfloat dotBV = dot( material.anisotropyB, viewDir );\n\t\tfloat dotBH = dot( material.anisotropyB, halfDir );\n\t\tfloat V = V_GGX_SmithCorrelated_Anisotropic( material.alphaT, alpha, dotTV, dotBV, dotTL, dotBL, dotNV, dotNL );\n\t\tfloat D = D_GGX_Anisotropic( material.alphaT, alpha, dotNH, dotTH, dotBH );\n\t#else\n\t\tfloat V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\t\tfloat D = D_GGX( alpha, dotNH );\n\t#endif\n\treturn F * ( V * D );\n}\nvec2 LTC_Uv( const in vec3 N, const in vec3 V, const in float roughness ) {\n\tconst float LUT_SIZE = 64.0;\n\tconst float LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE;\n\tconst float LUT_BIAS = 0.5 / LUT_SIZE;\n\tfloat dotNV = saturate( dot( N, V ) );\n\tvec2 uv = vec2( roughness, sqrt( 1.0 - dotNV ) );\n\tuv = uv * LUT_SCALE + LUT_BIAS;\n\treturn uv;\n}\nfloat LTC_ClippedSphereFormFactor( const in vec3 f ) {\n\tfloat l = length( f );\n\treturn max( ( l * l + f.z ) / ( l + 1.0 ), 0.0 );\n}\nvec3 LTC_EdgeVectorFormFactor( const in vec3 v1, const in vec3 v2 ) {\n\tfloat x = dot( v1, v2 );\n\tfloat y = abs( x );\n\tfloat a = 0.8543985 + ( 0.4965155 + 0.0145206 * y ) * y;\n\tfloat b = 3.4175940 + ( 4.1616724 + y ) * y;\n\tfloat v = a / b;\n\tfloat theta_sintheta = ( x > 0.0 ) ? v : 0.5 * inversesqrt( max( 1.0 - x * x, 1e-7 ) ) - v;\n\treturn cross( v1, v2 ) * theta_sintheta;\n}\nvec3 LTC_Evaluate( const in vec3 N, const in vec3 V, const in vec3 P, const in mat3 mInv, const in vec3 rectCoords[ 4 ] ) {\n\tvec3 v1 = rectCoords[ 1 ] - rectCoords[ 0 ];\n\tvec3 v2 = rectCoords[ 3 ] - rectCoords[ 0 ];\n\tvec3 lightNormal = cross( v1, v2 );\n\tif( dot( lightNormal, P - rectCoords[ 0 ] ) < 0.0 ) return vec3( 0.0 );\n\tvec3 T1, T2;\n\tT1 = normalize( V - N * dot( V, N ) );\n\tT2 = - cross( N, T1 );\n\tmat3 mat = mInv * transposeMat3( mat3( T1, T2, N ) );\n\tvec3 coords[ 4 ];\n\tcoords[ 0 ] = mat * ( rectCoords[ 0 ] - P );\n\tcoords[ 1 ] = mat * ( rectCoords[ 1 ] - P );\n\tcoords[ 2 ] = mat * ( rectCoords[ 2 ] - P );\n\tcoords[ 3 ] = mat * ( rectCoords[ 3 ] - P );\n\tcoords[ 0 ] = normalize( coords[ 0 ] );\n\tcoords[ 1 ] = normalize( coords[ 1 ] );\n\tcoords[ 2 ] = normalize( coords[ 2 ] );\n\tcoords[ 3 ] = normalize( coords[ 3 ] );\n\tvec3 vectorFormFactor = vec3( 0.0 );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 0 ], coords[ 1 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 1 ], coords[ 2 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 2 ], coords[ 3 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 3 ], coords[ 0 ] );\n\tfloat result = LTC_ClippedSphereFormFactor( vectorFormFactor );\n\treturn vec3( result );\n}\n#if defined( USE_SHEEN )\nfloat D_Charlie( float roughness, float dotNH ) {\n\tfloat alpha = pow2( roughness );\n\tfloat invAlpha = 1.0 / alpha;\n\tfloat cos2h = dotNH * dotNH;\n\tfloat sin2h = max( 1.0 - cos2h, 0.0078125 );\n\treturn ( 2.0 + invAlpha ) * pow( sin2h, invAlpha * 0.5 ) / ( 2.0 * PI );\n}\nfloat V_Neubelt( float dotNV, float dotNL ) {\n\treturn saturate( 1.0 / ( 4.0 * ( dotNL + dotNV - dotNL * dotNV ) ) );\n}\nvec3 BRDF_Sheen( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, vec3 sheenColor, const in float sheenRoughness ) {\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat D = D_Charlie( sheenRoughness, dotNH );\n\tfloat V = V_Neubelt( dotNV, dotNL );\n\treturn sheenColor * ( D * V );\n}\n#endif\nfloat IBLSheenBRDF( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat r2 = roughness * roughness;\n\tfloat a = roughness < 0.25 ? -339.2 * r2 + 161.4 * roughness - 25.9 : -8.48 * r2 + 14.3 * roughness - 9.95;\n\tfloat b = roughness < 0.25 ? 44.0 * r2 - 23.7 * roughness + 3.26 : 1.97 * r2 - 3.27 * roughness + 0.72;\n\tfloat DG = exp( a * dotNV + b ) + ( roughness < 0.25 ? 0.0 : 0.1 * ( roughness - 0.25 ) );\n\treturn saturate( DG * RECIPROCAL_PI );\n}\nvec2 DFGApprox( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tconst vec4 c0 = vec4( - 1, - 0.0275, - 0.572, 0.022 );\n\tconst vec4 c1 = vec4( 1, 0.0425, 1.04, - 0.04 );\n\tvec4 r = roughness * c0 + c1;\n\tfloat a004 = min( r.x * r.x, exp2( - 9.28 * dotNV ) ) * r.x + r.y;\n\tvec2 fab = vec2( - 1.04, 1.04 ) * a004 + r.zw;\n\treturn fab;\n}\nvec3 EnvironmentBRDF( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness ) {\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\treturn specularColor * fab.x + specularF90 * fab.y;\n}\n#ifdef USE_IRIDESCENCE\nvoid computeMultiscatteringIridescence( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float iridescence, const in vec3 iridescenceF0, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#else\nvoid computeMultiscattering( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#endif\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\t#ifdef USE_IRIDESCENCE\n\t\tvec3 Fr = mix( specularColor, iridescenceF0, iridescence );\n\t#else\n\t\tvec3 Fr = specularColor;\n\t#endif\n\tvec3 FssEss = Fr * fab.x + specularF90 * fab.y;\n\tfloat Ess = fab.x + fab.y;\n\tfloat Ems = 1.0 - Ess;\n\tvec3 Favg = Fr + ( 1.0 - Fr ) * 0.047619;\tvec3 Fms = FssEss * Favg / ( 1.0 - Ems * Favg );\n\tsingleScatter += FssEss;\n\tmultiScatter += Fms * Ems;\n}\n#if NUM_RECT_AREA_LIGHTS > 0\n\tvoid RE_Direct_RectArea_Physical( const in RectAreaLight rectAreaLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\t\tvec3 normal = geometry.normal;\n\t\tvec3 viewDir = geometry.viewDir;\n\t\tvec3 position = geometry.position;\n\t\tvec3 lightPos = rectAreaLight.position;\n\t\tvec3 halfWidth = rectAreaLight.halfWidth;\n\t\tvec3 halfHeight = rectAreaLight.halfHeight;\n\t\tvec3 lightColor = rectAreaLight.color;\n\t\tfloat roughness = material.roughness;\n\t\tvec3 rectCoords[ 4 ];\n\t\trectCoords[ 0 ] = lightPos + halfWidth - halfHeight;\t\trectCoords[ 1 ] = lightPos - halfWidth - halfHeight;\n\t\trectCoords[ 2 ] = lightPos - halfWidth + halfHeight;\n\t\trectCoords[ 3 ] = lightPos + halfWidth + halfHeight;\n\t\tvec2 uv = LTC_Uv( normal, viewDir, roughness );\n\t\tvec4 t1 = texture2D( ltc_1, uv );\n\t\tvec4 t2 = texture2D( ltc_2, uv );\n\t\tmat3 mInv = mat3(\n\t\t\tvec3( t1.x, 0, t1.y ),\n\t\t\tvec3( 0, 1, 0 ),\n\t\t\tvec3( t1.z, 0, t1.w )\n\t\t);\n\t\tvec3 fresnel = ( material.specularColor * t2.x + ( vec3( 1.0 ) - material.specularColor ) * t2.y );\n\t\treflectedLight.directSpecular += lightColor * fresnel * LTC_Evaluate( normal, viewDir, position, mInv, rectCoords );\n\t\treflectedLight.directDiffuse += lightColor * material.diffuseColor * LTC_Evaluate( normal, viewDir, position, mat3( 1.0 ), rectCoords );\n\t}\n#endif\nvoid RE_Direct_Physical( const in IncidentLight directLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNLcc = saturate( dot( geometry.clearcoatNormal, directLight.direction ) );\n\t\tvec3 ccIrradiance = dotNLcc * directLight.color;\n\t\tclearcoatSpecular += ccIrradiance * BRDF_GGX_Clearcoat( directLight.direction, geometry.viewDir, geometry.clearcoatNormal, material );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tsheenSpecular += irradiance * BRDF_Sheen( directLight.direction, geometry.viewDir, geometry.normal, material.sheenColor, material.sheenRoughness );\n\t#endif\n\treflectedLight.directSpecular += irradiance * BRDF_GGX( directLight.direction, geometry.viewDir, geometry.normal, material );\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Physical( const in vec3 irradiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 irradiance, const in vec3 clearcoatRadiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatSpecular += clearcoatRadiance * EnvironmentBRDF( geometry.clearcoatNormal, geometry.viewDir, material.clearcoatF0, material.clearcoatF90, material.clearcoatRoughness );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tsheenSpecular += irradiance * material.sheenColor * IBLSheenBRDF( geometry.normal, geometry.viewDir, material.sheenRoughness );\n\t#endif\n\tvec3 singleScattering = vec3( 0.0 );\n\tvec3 multiScattering = vec3( 0.0 );\n\tvec3 cosineWeightedIrradiance = irradiance * RECIPROCAL_PI;\n\t#ifdef USE_IRIDESCENCE\n\t\tcomputeMultiscatteringIridescence( geometry.normal, geometry.viewDir, material.specularColor, material.specularF90, material.iridescence, material.iridescenceFresnel, material.roughness, singleScattering, multiScattering );\n\t#else\n\t\tcomputeMultiscattering( geometry.normal, geometry.viewDir, material.specularColor, material.specularF90, material.roughness, singleScattering, multiScattering );\n\t#endif\n\tvec3 totalScattering = singleScattering + multiScattering;\n\tvec3 diffuse = material.diffuseColor * ( 1.0 - max( max( totalScattering.r, totalScattering.g ), totalScattering.b ) );\n\treflectedLight.indirectSpecular += radiance * singleScattering;\n\treflectedLight.indirectSpecular += multiScattering * cosineWeightedIrradiance;\n\treflectedLight.indirectDiffuse += diffuse * cosineWeightedIrradiance;\n}\n#define RE_Direct\t\t\t\tRE_Direct_Physical\n#define RE_Direct_RectArea\t\tRE_Direct_RectArea_Physical\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Physical\n#define RE_IndirectSpecular\t\tRE_IndirectSpecular_Physical\nfloat computeSpecularOcclusion( const in float dotNV, const in float ambientOcclusion, const in float roughness ) {\n\treturn saturate( pow( dotNV + ambientOcclusion, exp2( - 16.0 * roughness - 1.0 ) ) - 1.0 + ambientOcclusion );\n}",lights_fragment_begin:"\nGeometricContext geometry;\ngeometry.position = - vViewPosition;\ngeometry.normal = normal;\ngeometry.viewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( vViewPosition );\n#ifdef USE_CLEARCOAT\n\tgeometry.clearcoatNormal = clearcoatNormal;\n#endif\n#ifdef USE_IRIDESCENCE\n\tfloat dotNVi = saturate( dot( normal, geometry.viewDir ) );\n\tif ( material.iridescenceThickness == 0.0 ) {\n\t\tmaterial.iridescence = 0.0;\n\t} else {\n\t\tmaterial.iridescence = saturate( material.iridescence );\n\t}\n\tif ( material.iridescence > 0.0 ) {\n\t\tmaterial.iridescenceFresnel = evalIridescence( 1.0, material.iridescenceIOR, dotNVi, material.iridescenceThickness, material.specularColor );\n\t\tmaterial.iridescenceF0 = Schlick_to_F0( material.iridescenceFresnel, 1.0, dotNVi );\n\t}\n#endif\nIncidentLight directLight;\n#if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct )\n\tPointLight pointLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tpointLight = pointLights[ i ];\n\t\tgetPointLightInfo( pointLight, geometry, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_POINT_LIGHT_SHADOWS )\n\t\tpointLightShadow = pointLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getPointShadow( pointShadowMap[ i ], pointLightShadow.shadowMapSize, pointLightShadow.shadowBias, pointLightShadow.shadowRadius, vPointShadowCoord[ i ], pointLightShadow.shadowCameraNear, pointLightShadow.shadowCameraFar ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct )\n\tSpotLight spotLight;\n\tvec4 spotColor;\n\tvec3 spotLightCoord;\n\tbool inSpotLightMap;\n\t#if defined( USE_SHADOWMAP ) && NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tspotLight = spotLights[ i ];\n\t\tgetSpotLightInfo( spotLight, geometry, directLight );\n\t\t#if ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )\n\t\t#define SPOT_LIGHT_MAP_INDEX UNROLLED_LOOP_INDEX\n\t\t#elif ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\t#define SPOT_LIGHT_MAP_INDEX NUM_SPOT_LIGHT_MAPS\n\t\t#else\n\t\t#define SPOT_LIGHT_MAP_INDEX ( UNROLLED_LOOP_INDEX - NUM_SPOT_LIGHT_SHADOWS + NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )\n\t\t#endif\n\t\t#if ( SPOT_LIGHT_MAP_INDEX < NUM_SPOT_LIGHT_MAPS )\n\t\t\tspotLightCoord = vSpotLightCoord[ i ].xyz / vSpotLightCoord[ i ].w;\n\t\t\tinSpotLightMap = all( lessThan( abs( spotLightCoord * 2. - 1. ), vec3( 1.0 ) ) );\n\t\t\tspotColor = texture2D( spotLightMap[ SPOT_LIGHT_MAP_INDEX ], spotLightCoord.xy );\n\t\t\tdirectLight.color = inSpotLightMap ? directLight.color * spotColor.rgb : directLight.color;\n\t\t#endif\n\t\t#undef SPOT_LIGHT_MAP_INDEX\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\tspotLightShadow = spotLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( spotShadowMap[ i ], spotLightShadow.shadowMapSize, spotLightShadow.shadowBias, spotLightShadow.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct )\n\tDirectionalLight directionalLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tdirectionalLight = directionalLights[ i ];\n\t\tgetDirectionalLightInfo( directionalLight, geometry, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )\n\t\tdirectionalLightShadow = directionalLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea )\n\tRectAreaLight rectAreaLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) {\n\t\trectAreaLight = rectAreaLights[ i ];\n\t\tRE_Direct_RectArea( rectAreaLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if defined( RE_IndirectDiffuse )\n\tvec3 iblIrradiance = vec3( 0.0 );\n\tvec3 irradiance = getAmbientLightIrradiance( ambientLightColor );\n\tirradiance += getLightProbeIrradiance( lightProbe, geometry.normal );\n\t#if ( NUM_HEMI_LIGHTS > 0 )\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n\t\t\tirradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry.normal );\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n#endif\n#if defined( RE_IndirectSpecular )\n\tvec3 radiance = vec3( 0.0 );\n\tvec3 clearcoatRadiance = vec3( 0.0 );\n#endif",lights_fragment_maps:"#if defined( RE_IndirectDiffuse )\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel = texture2D( lightMap, vLightMapUv );\n\t\tvec3 lightMapIrradiance = lightMapTexel.rgb * lightMapIntensity;\n\t\tirradiance += lightMapIrradiance;\n\t#endif\n\t#if defined( USE_ENVMAP ) && defined( STANDARD ) && defined( ENVMAP_TYPE_CUBE_UV )\n\t\tiblIrradiance += getIBLIrradiance( geometry.normal );\n\t#endif\n#endif\n#if defined( USE_ENVMAP ) && defined( RE_IndirectSpecular )\n\t#ifdef USE_ANISOTROPY\n\t\tradiance += getIBLAnisotropyRadiance( geometry.viewDir, geometry.normal, material.roughness, material.anisotropyB, material.anisotropy );\n\t#else\n\t\tradiance += getIBLRadiance( geometry.viewDir, geometry.normal, material.roughness );\n\t#endif\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatRadiance += getIBLRadiance( geometry.viewDir, geometry.clearcoatNormal, material.clearcoatRoughness );\n\t#endif\n#endif",lights_fragment_end:"#if defined( RE_IndirectDiffuse )\n\tRE_IndirectDiffuse( irradiance, geometry, material, reflectedLight );\n#endif\n#if defined( RE_IndirectSpecular )\n\tRE_IndirectSpecular( radiance, iblIrradiance, clearcoatRadiance, geometry, material, reflectedLight );\n#endif",logdepthbuf_fragment:"#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tgl_FragDepthEXT = vIsPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\n#endif",logdepthbuf_pars_fragment:"#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tuniform float logDepthBufFC;\n\tvarying float vFragDepth;\n\tvarying float vIsPerspective;\n#endif",logdepthbuf_pars_vertex:"#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvarying float vFragDepth;\n\t\tvarying float vIsPerspective;\n\t#else\n\t\tuniform float logDepthBufFC;\n\t#endif\n#endif",logdepthbuf_vertex:"#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvFragDepth = 1.0 + gl_Position.w;\n\t\tvIsPerspective = float( isPerspectiveMatrix( projectionMatrix ) );\n\t#else\n\t\tif ( isPerspectiveMatrix( projectionMatrix ) ) {\n\t\t\tgl_Position.z = log2( max( EPSILON, gl_Position.w + 1.0 ) ) * logDepthBufFC - 1.0;\n\t\t\tgl_Position.z *= gl_Position.w;\n\t\t}\n\t#endif\n#endif",map_fragment:"#ifdef USE_MAP\n\tdiffuseColor *= texture2D( map, vMapUv );\n#endif",map_pars_fragment:"#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif",map_particle_fragment:"#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\t#if defined( USE_POINTS_UV )\n\t\tvec2 uv = vUv;\n\t#else\n\t\tvec2 uv = ( uvTransform * vec3( gl_PointCoord.x, 1.0 - gl_PointCoord.y, 1 ) ).xy;\n\t#endif\n#endif\n#ifdef USE_MAP\n\tdiffuseColor *= texture2D( map, uv );\n#endif\n#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, uv ).g;\n#endif",map_particle_pars_fragment:"#if defined( USE_POINTS_UV )\n\tvarying vec2 vUv;\n#else\n\t#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\t\tuniform mat3 uvTransform;\n\t#endif\n#endif\n#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif\n#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif",metalnessmap_fragment:"float metalnessFactor = metalness;\n#ifdef USE_METALNESSMAP\n\tvec4 texelMetalness = texture2D( metalnessMap, vMetalnessMapUv );\n\tmetalnessFactor *= texelMetalness.b;\n#endif",metalnessmap_pars_fragment:"#ifdef USE_METALNESSMAP\n\tuniform sampler2D metalnessMap;\n#endif",morphcolor_vertex:"#if defined( USE_MORPHCOLORS ) && defined( MORPHTARGETS_TEXTURE )\n\tvColor *= morphTargetBaseInfluence;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t#if defined( USE_COLOR_ALPHA )\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ) * morphTargetInfluences[ i ];\n\t\t#elif defined( USE_COLOR )\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ).rgb * morphTargetInfluences[ i ];\n\t\t#endif\n\t}\n#endif",morphnormal_vertex:"#ifdef USE_MORPHNORMALS\n\tobjectNormal *= morphTargetBaseInfluence;\n\t#ifdef MORPHTARGETS_TEXTURE\n\t\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) objectNormal += getMorph( gl_VertexID, i, 1 ).xyz * morphTargetInfluences[ i ];\n\t\t}\n\t#else\n\t\tobjectNormal += morphNormal0 * morphTargetInfluences[ 0 ];\n\t\tobjectNormal += morphNormal1 * morphTargetInfluences[ 1 ];\n\t\tobjectNormal += morphNormal2 * morphTargetInfluences[ 2 ];\n\t\tobjectNormal += morphNormal3 * morphTargetInfluences[ 3 ];\n\t#endif\n#endif",morphtarget_pars_vertex:"#ifdef USE_MORPHTARGETS\n\tuniform float morphTargetBaseInfluence;\n\t#ifdef MORPHTARGETS_TEXTURE\n\t\tuniform float morphTargetInfluences[ MORPHTARGETS_COUNT ];\n\t\tuniform sampler2DArray morphTargetsTexture;\n\t\tuniform ivec2 morphTargetsTextureSize;\n\t\tvec4 getMorph( const in int vertexIndex, const in int morphTargetIndex, const in int offset ) {\n\t\t\tint texelIndex = vertexIndex * MORPHTARGETS_TEXTURE_STRIDE + offset;\n\t\t\tint y = texelIndex / morphTargetsTextureSize.x;\n\t\t\tint x = texelIndex - y * morphTargetsTextureSize.x;\n\t\t\tivec3 morphUV = ivec3( x, y, morphTargetIndex );\n\t\t\treturn texelFetch( morphTargetsTexture, morphUV, 0 );\n\t\t}\n\t#else\n\t\t#ifndef USE_MORPHNORMALS\n\t\t\tuniform float morphTargetInfluences[ 8 ];\n\t\t#else\n\t\t\tuniform float morphTargetInfluences[ 4 ];\n\t\t#endif\n\t#endif\n#endif",morphtarget_vertex:"#ifdef USE_MORPHTARGETS\n\ttransformed *= morphTargetBaseInfluence;\n\t#ifdef MORPHTARGETS_TEXTURE\n\t\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) transformed += getMorph( gl_VertexID, i, 0 ).xyz * morphTargetInfluences[ i ];\n\t\t}\n\t#else\n\t\ttransformed += morphTarget0 * morphTargetInfluences[ 0 ];\n\t\ttransformed += morphTarget1 * morphTargetInfluences[ 1 ];\n\t\ttransformed += morphTarget2 * morphTargetInfluences[ 2 ];\n\t\ttransformed += morphTarget3 * morphTargetInfluences[ 3 ];\n\t\t#ifndef USE_MORPHNORMALS\n\t\t\ttransformed += morphTarget4 * morphTargetInfluences[ 4 ];\n\t\t\ttransformed += morphTarget5 * morphTargetInfluences[ 5 ];\n\t\t\ttransformed += morphTarget6 * morphTargetInfluences[ 6 ];\n\t\t\ttransformed += morphTarget7 * morphTargetInfluences[ 7 ];\n\t\t#endif\n\t#endif\n#endif",normal_fragment_begin:"float faceDirection = gl_FrontFacing ? 1.0 : - 1.0;\n#ifdef FLAT_SHADED\n\tvec3 fdx = dFdx( vViewPosition );\n\tvec3 fdy = dFdy( vViewPosition );\n\tvec3 normal = normalize( cross( fdx, fdy ) );\n#else\n\tvec3 normal = normalize( vNormal );\n\t#ifdef DOUBLE_SIDED\n\t\tnormal *= faceDirection;\n\t#endif\n#endif\n#if defined( USE_NORMALMAP_TANGENTSPACE ) || defined( USE_CLEARCOAT_NORMALMAP ) || defined( USE_ANISOTROPY )\n\t#ifdef USE_TANGENT\n\t\tmat3 tbn = mat3( normalize( vTangent ), normalize( vBitangent ), normal );\n\t#else\n\t\tmat3 tbn = getTangentFrame( - vViewPosition, normal, vNormalMapUv );\n\t#endif\n\t#if defined( DOUBLE_SIDED ) && ! defined( FLAT_SHADED )\n\t\ttbn[0] *= faceDirection;\n\t\ttbn[1] *= faceDirection;\n\t#endif\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\t#ifdef USE_TANGENT\n\t\tmat3 tbn2 = mat3( normalize( vTangent ), normalize( vBitangent ), normal );\n\t#else\n\t\tmat3 tbn2 = getTangentFrame( - vViewPosition, normal, vClearcoatNormalMapUv );\n\t#endif\n\t#if defined( DOUBLE_SIDED ) && ! defined( FLAT_SHADED )\n\t\ttbn2[0] *= faceDirection;\n\t\ttbn2[1] *= faceDirection;\n\t#endif\n#endif\nvec3 geometryNormal = normal;",normal_fragment_maps:"#ifdef USE_NORMALMAP_OBJECTSPACE\n\tnormal = texture2D( normalMap, vNormalMapUv ).xyz * 2.0 - 1.0;\n\t#ifdef FLIP_SIDED\n\t\tnormal = - normal;\n\t#endif\n\t#ifdef DOUBLE_SIDED\n\t\tnormal = normal * faceDirection;\n\t#endif\n\tnormal = normalize( normalMatrix * normal );\n#elif defined( USE_NORMALMAP_TANGENTSPACE )\n\tvec3 mapN = texture2D( normalMap, vNormalMapUv ).xyz * 2.0 - 1.0;\n\tmapN.xy *= normalScale;\n\tnormal = normalize( tbn * mapN );\n#elif defined( USE_BUMPMAP )\n\tnormal = perturbNormalArb( - vViewPosition, normal, dHdxy_fwd(), faceDirection );\n#endif",normal_pars_fragment:"#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif",normal_pars_vertex:"#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif",normal_vertex:"#ifndef FLAT_SHADED\n\tvNormal = normalize( transformedNormal );\n\t#ifdef USE_TANGENT\n\t\tvTangent = normalize( transformedTangent );\n\t\tvBitangent = normalize( cross( vNormal, vTangent ) * tangent.w );\n\t#endif\n#endif",normalmap_pars_fragment:"#ifdef USE_NORMALMAP\n\tuniform sampler2D normalMap;\n\tuniform vec2 normalScale;\n#endif\n#ifdef USE_NORMALMAP_OBJECTSPACE\n\tuniform mat3 normalMatrix;\n#endif\n#if ! defined ( USE_TANGENT ) && ( defined ( USE_NORMALMAP_TANGENTSPACE ) || defined ( USE_CLEARCOAT_NORMALMAP ) || defined( USE_ANISOTROPY ) )\n\tmat3 getTangentFrame( vec3 eye_pos, vec3 surf_norm, vec2 uv ) {\n\t\tvec3 q0 = dFdx( eye_pos.xyz );\n\t\tvec3 q1 = dFdy( eye_pos.xyz );\n\t\tvec2 st0 = dFdx( uv.st );\n\t\tvec2 st1 = dFdy( uv.st );\n\t\tvec3 N = surf_norm;\n\t\tvec3 q1perp = cross( q1, N );\n\t\tvec3 q0perp = cross( N, q0 );\n\t\tvec3 T = q1perp * st0.x + q0perp * st1.x;\n\t\tvec3 B = q1perp * st0.y + q0perp * st1.y;\n\t\tfloat det = max( dot( T, T ), dot( B, B ) );\n\t\tfloat scale = ( det == 0.0 ) ? 0.0 : inversesqrt( det );\n\t\treturn mat3( T * scale, B * scale, N );\n\t}\n#endif",clearcoat_normal_fragment_begin:"#ifdef USE_CLEARCOAT\n\tvec3 clearcoatNormal = geometryNormal;\n#endif",clearcoat_normal_fragment_maps:"#ifdef USE_CLEARCOAT_NORMALMAP\n\tvec3 clearcoatMapN = texture2D( clearcoatNormalMap, vClearcoatNormalMapUv ).xyz * 2.0 - 1.0;\n\tclearcoatMapN.xy *= clearcoatNormalScale;\n\tclearcoatNormal = normalize( tbn2 * clearcoatMapN );\n#endif",clearcoat_pars_fragment:"#ifdef USE_CLEARCOATMAP\n\tuniform sampler2D clearcoatMap;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tuniform sampler2D clearcoatNormalMap;\n\tuniform vec2 clearcoatNormalScale;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tuniform sampler2D clearcoatRoughnessMap;\n#endif",iridescence_pars_fragment:"#ifdef USE_IRIDESCENCEMAP\n\tuniform sampler2D iridescenceMap;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tuniform sampler2D iridescenceThicknessMap;\n#endif",output_fragment:"#ifdef OPAQUE\ndiffuseColor.a = 1.0;\n#endif\n#ifdef USE_TRANSMISSION\ndiffuseColor.a *= material.transmissionAlpha;\n#endif\ngl_FragColor = vec4( outgoingLight, diffuseColor.a );",packing:"vec3 packNormalToRGB( const in vec3 normal ) {\n\treturn normalize( normal ) * 0.5 + 0.5;\n}\nvec3 unpackRGBToNormal( const in vec3 rgb ) {\n\treturn 2.0 * rgb.xyz - 1.0;\n}\nconst float PackUpscale = 256. / 255.;const float UnpackDownscale = 255. / 256.;\nconst vec3 PackFactors = vec3( 256. * 256. * 256., 256. * 256., 256. );\nconst vec4 UnpackFactors = UnpackDownscale / vec4( PackFactors, 1. );\nconst float ShiftRight8 = 1. / 256.;\nvec4 packDepthToRGBA( const in float v ) {\n\tvec4 r = vec4( fract( v * PackFactors ), v );\n\tr.yzw -= r.xyz * ShiftRight8;\treturn r * PackUpscale;\n}\nfloat unpackRGBAToDepth( const in vec4 v ) {\n\treturn dot( v, UnpackFactors );\n}\nvec2 packDepthToRG( in highp float v ) {\n\treturn packDepthToRGBA( v ).yx;\n}\nfloat unpackRGToDepth( const in highp vec2 v ) {\n\treturn unpackRGBAToDepth( vec4( v.xy, 0.0, 0.0 ) );\n}\nvec4 pack2HalfToRGBA( vec2 v ) {\n\tvec4 r = vec4( v.x, fract( v.x * 255.0 ), v.y, fract( v.y * 255.0 ) );\n\treturn vec4( r.x - r.y / 255.0, r.y, r.z - r.w / 255.0, r.w );\n}\nvec2 unpackRGBATo2Half( vec4 v ) {\n\treturn vec2( v.x + ( v.y / 255.0 ), v.z + ( v.w / 255.0 ) );\n}\nfloat viewZToOrthographicDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( viewZ + near ) / ( near - far );\n}\nfloat orthographicDepthToViewZ( const in float depth, const in float near, const in float far ) {\n\treturn depth * ( near - far ) - near;\n}\nfloat viewZToPerspectiveDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( ( near + viewZ ) * far ) / ( ( far - near ) * viewZ );\n}\nfloat perspectiveDepthToViewZ( const in float depth, const in float near, const in float far ) {\n\treturn ( near * far ) / ( ( far - near ) * depth - far );\n}",premultiplied_alpha_fragment:"#ifdef PREMULTIPLIED_ALPHA\n\tgl_FragColor.rgb *= gl_FragColor.a;\n#endif",project_vertex:"vec4 mvPosition = vec4( transformed, 1.0 );\n#ifdef USE_INSTANCING\n\tmvPosition = instanceMatrix * mvPosition;\n#endif\nmvPosition = modelViewMatrix * mvPosition;\ngl_Position = projectionMatrix * mvPosition;",dithering_fragment:"#ifdef DITHERING\n\tgl_FragColor.rgb = dithering( gl_FragColor.rgb );\n#endif",dithering_pars_fragment:"#ifdef DITHERING\n\tvec3 dithering( vec3 color ) {\n\t\tfloat grid_position = rand( gl_FragCoord.xy );\n\t\tvec3 dither_shift_RGB = vec3( 0.25 / 255.0, -0.25 / 255.0, 0.25 / 255.0 );\n\t\tdither_shift_RGB = mix( 2.0 * dither_shift_RGB, -2.0 * dither_shift_RGB, grid_position );\n\t\treturn color + dither_shift_RGB;\n\t}\n#endif",roughnessmap_fragment:"float roughnessFactor = roughness;\n#ifdef USE_ROUGHNESSMAP\n\tvec4 texelRoughness = texture2D( roughnessMap, vRoughnessMapUv );\n\troughnessFactor *= texelRoughness.g;\n#endif",roughnessmap_pars_fragment:"#ifdef USE_ROUGHNESSMAP\n\tuniform sampler2D roughnessMap;\n#endif",shadowmap_pars_fragment:"#if NUM_SPOT_LIGHT_COORDS > 0\n\tvarying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ];\n#endif\n#if NUM_SPOT_LIGHT_MAPS > 0\n\tuniform sampler2D spotLightMap[ NUM_SPOT_LIGHT_MAPS ];\n#endif\n#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D directionalShadowMap[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D spotShadowMap[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D pointShadowMap[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n\tfloat texture2DCompare( sampler2D depths, vec2 uv, float compare ) {\n\t\treturn step( compare, unpackRGBAToDepth( texture2D( depths, uv ) ) );\n\t}\n\tvec2 texture2DDistribution( sampler2D shadow, vec2 uv ) {\n\t\treturn unpackRGBATo2Half( texture2D( shadow, uv ) );\n\t}\n\tfloat VSMShadow (sampler2D shadow, vec2 uv, float compare ){\n\t\tfloat occlusion = 1.0;\n\t\tvec2 distribution = texture2DDistribution( shadow, uv );\n\t\tfloat hard_shadow = step( compare , distribution.x );\n\t\tif (hard_shadow != 1.0 ) {\n\t\t\tfloat distance = compare - distribution.x ;\n\t\t\tfloat variance = max( 0.00000, distribution.y * distribution.y );\n\t\t\tfloat softness_probability = variance / (variance + distance * distance );\t\t\tsoftness_probability = clamp( ( softness_probability - 0.3 ) / ( 0.95 - 0.3 ), 0.0, 1.0 );\t\t\tocclusion = clamp( max( hard_shadow, softness_probability ), 0.0, 1.0 );\n\t\t}\n\t\treturn occlusion;\n\t}\n\tfloat getShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord ) {\n\t\tfloat shadow = 1.0;\n\t\tshadowCoord.xyz /= shadowCoord.w;\n\t\tshadowCoord.z += shadowBias;\n\t\tbool inFrustum = shadowCoord.x >= 0.0 && shadowCoord.x <= 1.0 && shadowCoord.y >= 0.0 && shadowCoord.y <= 1.0;\n\t\tbool frustumTest = inFrustum && shadowCoord.z <= 1.0;\n\t\tif ( frustumTest ) {\n\t\t#if defined( SHADOWMAP_TYPE_PCF )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx0 = - texelSize.x * shadowRadius;\n\t\t\tfloat dy0 = - texelSize.y * shadowRadius;\n\t\t\tfloat dx1 = + texelSize.x * shadowRadius;\n\t\t\tfloat dy1 = + texelSize.y * shadowRadius;\n\t\t\tfloat dx2 = dx0 / 2.0;\n\t\t\tfloat dy2 = dy0 / 2.0;\n\t\t\tfloat dx3 = dx1 / 2.0;\n\t\t\tfloat dy3 = dy1 / 2.0;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy1 ), shadowCoord.z )\n\t\t\t) * ( 1.0 / 17.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_PCF_SOFT )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx = texelSize.x;\n\t\t\tfloat dy = texelSize.y;\n\t\t\tvec2 uv = shadowCoord.xy;\n\t\t\tvec2 f = fract( uv * shadowMapSize + 0.5 );\n\t\t\tuv -= f * texelSize;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, uv, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( dx, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( 0.0, dy ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + texelSize, shadowCoord.z ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, dy ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( 0.0, -dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 0.0, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( mix( texture2DCompare( shadowMap, uv + vec2( -dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t f.x ),\n\t\t\t\t\t mix( texture2DCompare( shadowMap, uv + vec2( -dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t f.x ),\n\t\t\t\t\t f.y )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_VSM )\n\t\t\tshadow = VSMShadow( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#else\n\t\t\tshadow = texture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#endif\n\t\t}\n\t\treturn shadow;\n\t}\n\tvec2 cubeToUV( vec3 v, float texelSizeY ) {\n\t\tvec3 absV = abs( v );\n\t\tfloat scaleToCube = 1.0 / max( absV.x, max( absV.y, absV.z ) );\n\t\tabsV *= scaleToCube;\n\t\tv *= scaleToCube * ( 1.0 - 2.0 * texelSizeY );\n\t\tvec2 planar = v.xy;\n\t\tfloat almostATexel = 1.5 * texelSizeY;\n\t\tfloat almostOne = 1.0 - almostATexel;\n\t\tif ( absV.z >= almostOne ) {\n\t\t\tif ( v.z > 0.0 )\n\t\t\t\tplanar.x = 4.0 - v.x;\n\t\t} else if ( absV.x >= almostOne ) {\n\t\t\tfloat signX = sign( v.x );\n\t\t\tplanar.x = v.z * signX + 2.0 * signX;\n\t\t} else if ( absV.y >= almostOne ) {\n\t\t\tfloat signY = sign( v.y );\n\t\t\tplanar.x = v.x + 2.0 * signY + 2.0;\n\t\t\tplanar.y = v.z * signY - 2.0;\n\t\t}\n\t\treturn vec2( 0.125, 0.25 ) * planar + vec2( 0.375, 0.75 );\n\t}\n\tfloat getPointShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord, float shadowCameraNear, float shadowCameraFar ) {\n\t\tvec2 texelSize = vec2( 1.0 ) / ( shadowMapSize * vec2( 4.0, 2.0 ) );\n\t\tvec3 lightToPosition = shadowCoord.xyz;\n\t\tfloat dp = ( length( lightToPosition ) - shadowCameraNear ) / ( shadowCameraFar - shadowCameraNear );\t\tdp += shadowBias;\n\t\tvec3 bd3D = normalize( lightToPosition );\n\t\t#if defined( SHADOWMAP_TYPE_PCF ) || defined( SHADOWMAP_TYPE_PCF_SOFT ) || defined( SHADOWMAP_TYPE_VSM )\n\t\t\tvec2 offset = vec2( - 1, 1 ) * shadowRadius * texelSize.y;\n\t\t\treturn (\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxx, texelSize.y ), dp )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#else\n\t\t\treturn texture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp );\n\t\t#endif\n\t}\n#endif",shadowmap_pars_vertex:"#if NUM_SPOT_LIGHT_COORDS > 0\n\tuniform mat4 spotLightMatrix[ NUM_SPOT_LIGHT_COORDS ];\n\tvarying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ];\n#endif\n#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform mat4 directionalShadowMatrix[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform mat4 pointShadowMatrix[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n#endif",shadowmap_vertex:"#if ( defined( USE_SHADOWMAP ) && ( NUM_DIR_LIGHT_SHADOWS > 0 || NUM_POINT_LIGHT_SHADOWS > 0 ) ) || ( NUM_SPOT_LIGHT_COORDS > 0 )\n\tvec3 shadowWorldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\tvec4 shadowWorldPosition;\n#endif\n#if defined( USE_SHADOWMAP )\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * directionalLightShadows[ i ].shadowNormalBias, 0 );\n\t\t\tvDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * shadowWorldPosition;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * pointLightShadows[ i ].shadowNormalBias, 0 );\n\t\t\tvPointShadowCoord[ i ] = pointShadowMatrix[ i ] * shadowWorldPosition;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n#endif\n#if NUM_SPOT_LIGHT_COORDS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_COORDS; i ++ ) {\n\t\tshadowWorldPosition = worldPosition;\n\t\t#if ( defined( USE_SHADOWMAP ) && UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\t\tshadowWorldPosition.xyz += shadowWorldNormal * spotLightShadows[ i ].shadowNormalBias;\n\t\t#endif\n\t\tvSpotLightCoord[ i ] = spotLightMatrix[ i ] * shadowWorldPosition;\n\t}\n\t#pragma unroll_loop_end\n#endif",shadowmask_pars_fragment:"float getShadowMask() {\n\tfloat shadow = 1.0;\n\t#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\tdirectionalLight = directionalLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_SHADOWS; i ++ ) {\n\t\tspotLight = spotLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowBias, spotLight.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\tpointLight = pointLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#endif\n\treturn shadow;\n}",skinbase_vertex:"#ifdef USE_SKINNING\n\tmat4 boneMatX = getBoneMatrix( skinIndex.x );\n\tmat4 boneMatY = getBoneMatrix( skinIndex.y );\n\tmat4 boneMatZ = getBoneMatrix( skinIndex.z );\n\tmat4 boneMatW = getBoneMatrix( skinIndex.w );\n#endif",skinning_pars_vertex:"#ifdef USE_SKINNING\n\tuniform mat4 bindMatrix;\n\tuniform mat4 bindMatrixInverse;\n\tuniform highp sampler2D boneTexture;\n\tuniform int boneTextureSize;\n\tmat4 getBoneMatrix( const in float i ) {\n\t\tfloat j = i * 4.0;\n\t\tfloat x = mod( j, float( boneTextureSize ) );\n\t\tfloat y = floor( j / float( boneTextureSize ) );\n\t\tfloat dx = 1.0 / float( boneTextureSize );\n\t\tfloat dy = 1.0 / float( boneTextureSize );\n\t\ty = dy * ( y + 0.5 );\n\t\tvec4 v1 = texture2D( boneTexture, vec2( dx * ( x + 0.5 ), y ) );\n\t\tvec4 v2 = texture2D( boneTexture, vec2( dx * ( x + 1.5 ), y ) );\n\t\tvec4 v3 = texture2D( boneTexture, vec2( dx * ( x + 2.5 ), y ) );\n\t\tvec4 v4 = texture2D( boneTexture, vec2( dx * ( x + 3.5 ), y ) );\n\t\tmat4 bone = mat4( v1, v2, v3, v4 );\n\t\treturn bone;\n\t}\n#endif",skinning_vertex:"#ifdef USE_SKINNING\n\tvec4 skinVertex = bindMatrix * vec4( transformed, 1.0 );\n\tvec4 skinned = vec4( 0.0 );\n\tskinned += boneMatX * skinVertex * skinWeight.x;\n\tskinned += boneMatY * skinVertex * skinWeight.y;\n\tskinned += boneMatZ * skinVertex * skinWeight.z;\n\tskinned += boneMatW * skinVertex * skinWeight.w;\n\ttransformed = ( bindMatrixInverse * skinned ).xyz;\n#endif",skinnormal_vertex:"#ifdef USE_SKINNING\n\tmat4 skinMatrix = mat4( 0.0 );\n\tskinMatrix += skinWeight.x * boneMatX;\n\tskinMatrix += skinWeight.y * boneMatY;\n\tskinMatrix += skinWeight.z * boneMatZ;\n\tskinMatrix += skinWeight.w * boneMatW;\n\tskinMatrix = bindMatrixInverse * skinMatrix * bindMatrix;\n\tobjectNormal = vec4( skinMatrix * vec4( objectNormal, 0.0 ) ).xyz;\n\t#ifdef USE_TANGENT\n\t\tobjectTangent = vec4( skinMatrix * vec4( objectTangent, 0.0 ) ).xyz;\n\t#endif\n#endif",specularmap_fragment:"float specularStrength;\n#ifdef USE_SPECULARMAP\n\tvec4 texelSpecular = texture2D( specularMap, vSpecularMapUv );\n\tspecularStrength = texelSpecular.r;\n#else\n\tspecularStrength = 1.0;\n#endif",specularmap_pars_fragment:"#ifdef USE_SPECULARMAP\n\tuniform sampler2D specularMap;\n#endif",tonemapping_fragment:"#if defined( TONE_MAPPING )\n\tgl_FragColor.rgb = toneMapping( gl_FragColor.rgb );\n#endif",tonemapping_pars_fragment:"#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\nuniform float toneMappingExposure;\nvec3 LinearToneMapping( vec3 color ) {\n\treturn toneMappingExposure * color;\n}\nvec3 ReinhardToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\treturn saturate( color / ( vec3( 1.0 ) + color ) );\n}\nvec3 OptimizedCineonToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\tcolor = max( vec3( 0.0 ), color - 0.004 );\n\treturn pow( ( color * ( 6.2 * color + 0.5 ) ) / ( color * ( 6.2 * color + 1.7 ) + 0.06 ), vec3( 2.2 ) );\n}\nvec3 RRTAndODTFit( vec3 v ) {\n\tvec3 a = v * ( v + 0.0245786 ) - 0.000090537;\n\tvec3 b = v * ( 0.983729 * v + 0.4329510 ) + 0.238081;\n\treturn a / b;\n}\nvec3 ACESFilmicToneMapping( vec3 color ) {\n\tconst mat3 ACESInputMat = mat3(\n\t\tvec3( 0.59719, 0.07600, 0.02840 ),\t\tvec3( 0.35458, 0.90834, 0.13383 ),\n\t\tvec3( 0.04823, 0.01566, 0.83777 )\n\t);\n\tconst mat3 ACESOutputMat = mat3(\n\t\tvec3( 1.60475, -0.10208, -0.00327 ),\t\tvec3( -0.53108, 1.10813, -0.07276 ),\n\t\tvec3( -0.07367, -0.00605, 1.07602 )\n\t);\n\tcolor *= toneMappingExposure / 0.6;\n\tcolor = ACESInputMat * color;\n\tcolor = RRTAndODTFit( color );\n\tcolor = ACESOutputMat * color;\n\treturn saturate( color );\n}\nvec3 CustomToneMapping( vec3 color ) { return color; }",transmission_fragment:"#ifdef USE_TRANSMISSION\n\tmaterial.transmission = transmission;\n\tmaterial.transmissionAlpha = 1.0;\n\tmaterial.thickness = thickness;\n\tmaterial.attenuationDistance = attenuationDistance;\n\tmaterial.attenuationColor = attenuationColor;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tmaterial.transmission *= texture2D( transmissionMap, vTransmissionMapUv ).r;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tmaterial.thickness *= texture2D( thicknessMap, vThicknessMapUv ).g;\n\t#endif\n\tvec3 pos = vWorldPosition;\n\tvec3 v = normalize( cameraPosition - pos );\n\tvec3 n = inverseTransformDirection( normal, viewMatrix );\n\tvec4 transmitted = getIBLVolumeRefraction(\n\t\tn, v, material.roughness, material.diffuseColor, material.specularColor, material.specularF90,\n\t\tpos, modelMatrix, viewMatrix, projectionMatrix, material.ior, material.thickness,\n\t\tmaterial.attenuationColor, material.attenuationDistance );\n\tmaterial.transmissionAlpha = mix( material.transmissionAlpha, transmitted.a, material.transmission );\n\ttotalDiffuse = mix( totalDiffuse, transmitted.rgb, material.transmission );\n#endif",transmission_pars_fragment:"#ifdef USE_TRANSMISSION\n\tuniform float transmission;\n\tuniform float thickness;\n\tuniform float attenuationDistance;\n\tuniform vec3 attenuationColor;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tuniform sampler2D transmissionMap;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tuniform sampler2D thicknessMap;\n\t#endif\n\tuniform vec2 transmissionSamplerSize;\n\tuniform sampler2D transmissionSamplerMap;\n\tuniform mat4 modelMatrix;\n\tuniform mat4 projectionMatrix;\n\tvarying vec3 vWorldPosition;\n\tfloat w0( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * ( a * ( - a + 3.0 ) - 3.0 ) + 1.0 );\n\t}\n\tfloat w1( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * a * ( 3.0 * a - 6.0 ) + 4.0 );\n\t}\n\tfloat w2( float a ){\n\t\treturn ( 1.0 / 6.0 ) * ( a * ( a * ( - 3.0 * a + 3.0 ) + 3.0 ) + 1.0 );\n\t}\n\tfloat w3( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * a * a );\n\t}\n\tfloat g0( float a ) {\n\t\treturn w0( a ) + w1( a );\n\t}\n\tfloat g1( float a ) {\n\t\treturn w2( a ) + w3( a );\n\t}\n\tfloat h0( float a ) {\n\t\treturn - 1.0 + w1( a ) / ( w0( a ) + w1( a ) );\n\t}\n\tfloat h1( float a ) {\n\t\treturn 1.0 + w3( a ) / ( w2( a ) + w3( a ) );\n\t}\n\tvec4 bicubic( sampler2D tex, vec2 uv, vec4 texelSize, float lod ) {\n\t\tuv = uv * texelSize.zw + 0.5;\n\t\tvec2 iuv = floor( uv );\n\t\tvec2 fuv = fract( uv );\n\t\tfloat g0x = g0( fuv.x );\n\t\tfloat g1x = g1( fuv.x );\n\t\tfloat h0x = h0( fuv.x );\n\t\tfloat h1x = h1( fuv.x );\n\t\tfloat h0y = h0( fuv.y );\n\t\tfloat h1y = h1( fuv.y );\n\t\tvec2 p0 = ( vec2( iuv.x + h0x, iuv.y + h0y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p1 = ( vec2( iuv.x + h1x, iuv.y + h0y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p2 = ( vec2( iuv.x + h0x, iuv.y + h1y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p3 = ( vec2( iuv.x + h1x, iuv.y + h1y ) - 0.5 ) * texelSize.xy;\n\t\treturn g0( fuv.y ) * ( g0x * textureLod( tex, p0, lod ) + g1x * textureLod( tex, p1, lod ) ) +\n\t\t\tg1( fuv.y ) * ( g0x * textureLod( tex, p2, lod ) + g1x * textureLod( tex, p3, lod ) );\n\t}\n\tvec4 textureBicubic( sampler2D sampler, vec2 uv, float lod ) {\n\t\tvec2 fLodSize = vec2( textureSize( sampler, int( lod ) ) );\n\t\tvec2 cLodSize = vec2( textureSize( sampler, int( lod + 1.0 ) ) );\n\t\tvec2 fLodSizeInv = 1.0 / fLodSize;\n\t\tvec2 cLodSizeInv = 1.0 / cLodSize;\n\t\tvec4 fSample = bicubic( sampler, uv, vec4( fLodSizeInv, fLodSize ), floor( lod ) );\n\t\tvec4 cSample = bicubic( sampler, uv, vec4( cLodSizeInv, cLodSize ), ceil( lod ) );\n\t\treturn mix( fSample, cSample, fract( lod ) );\n\t}\n\tvec3 getVolumeTransmissionRay( const in vec3 n, const in vec3 v, const in float thickness, const in float ior, const in mat4 modelMatrix ) {\n\t\tvec3 refractionVector = refract( - v, normalize( n ), 1.0 / ior );\n\t\tvec3 modelScale;\n\t\tmodelScale.x = length( vec3( modelMatrix[ 0 ].xyz ) );\n\t\tmodelScale.y = length( vec3( modelMatrix[ 1 ].xyz ) );\n\t\tmodelScale.z = length( vec3( modelMatrix[ 2 ].xyz ) );\n\t\treturn normalize( refractionVector ) * thickness * modelScale;\n\t}\n\tfloat applyIorToRoughness( const in float roughness, const in float ior ) {\n\t\treturn roughness * clamp( ior * 2.0 - 2.0, 0.0, 1.0 );\n\t}\n\tvec4 getTransmissionSample( const in vec2 fragCoord, const in float roughness, const in float ior ) {\n\t\tfloat lod = log2( transmissionSamplerSize.x ) * applyIorToRoughness( roughness, ior );\n\t\treturn textureBicubic( transmissionSamplerMap, fragCoord.xy, lod );\n\t}\n\tvec3 volumeAttenuation( const in float transmissionDistance, const in vec3 attenuationColor, const in float attenuationDistance ) {\n\t\tif ( isinf( attenuationDistance ) ) {\n\t\t\treturn vec3( 1.0 );\n\t\t} else {\n\t\t\tvec3 attenuationCoefficient = -log( attenuationColor ) / attenuationDistance;\n\t\t\tvec3 transmittance = exp( - attenuationCoefficient * transmissionDistance );\t\t\treturn transmittance;\n\t\t}\n\t}\n\tvec4 getIBLVolumeRefraction( const in vec3 n, const in vec3 v, const in float roughness, const in vec3 diffuseColor,\n\t\tconst in vec3 specularColor, const in float specularF90, const in vec3 position, const in mat4 modelMatrix,\n\t\tconst in mat4 viewMatrix, const in mat4 projMatrix, const in float ior, const in float thickness,\n\t\tconst in vec3 attenuationColor, const in float attenuationDistance ) {\n\t\tvec3 transmissionRay = getVolumeTransmissionRay( n, v, thickness, ior, modelMatrix );\n\t\tvec3 refractedRayExit = position + transmissionRay;\n\t\tvec4 ndcPos = projMatrix * viewMatrix * vec4( refractedRayExit, 1.0 );\n\t\tvec2 refractionCoords = ndcPos.xy / ndcPos.w;\n\t\trefractionCoords += 1.0;\n\t\trefractionCoords /= 2.0;\n\t\tvec4 transmittedLight = getTransmissionSample( refractionCoords, roughness, ior );\n\t\tvec3 transmittance = diffuseColor * volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance );\n\t\tvec3 attenuatedColor = transmittance * transmittedLight.rgb;\n\t\tvec3 F = EnvironmentBRDF( n, v, specularColor, specularF90, roughness );\n\t\tfloat transmittanceFactor = ( transmittance.r + transmittance.g + transmittance.b ) / 3.0;\n\t\treturn vec4( ( 1.0 - F ) * attenuatedColor, 1.0 - ( 1.0 - transmittedLight.a ) * transmittanceFactor );\n\t}\n#endif",uv_pars_fragment:"#ifdef USE_UV\n\tvarying vec2 vUv;\n#endif\n#ifdef USE_MAP\n\tvarying vec2 vMapUv;\n#endif\n#ifdef USE_ALPHAMAP\n\tvarying vec2 vAlphaMapUv;\n#endif\n#ifdef USE_LIGHTMAP\n\tvarying vec2 vLightMapUv;\n#endif\n#ifdef USE_AOMAP\n\tvarying vec2 vAoMapUv;\n#endif\n#ifdef USE_BUMPMAP\n\tvarying vec2 vBumpMapUv;\n#endif\n#ifdef USE_NORMALMAP\n\tvarying vec2 vNormalMapUv;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tvarying vec2 vEmissiveMapUv;\n#endif\n#ifdef USE_METALNESSMAP\n\tvarying vec2 vMetalnessMapUv;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tvarying vec2 vRoughnessMapUv;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tvarying vec2 vAnisotropyMapUv;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tvarying vec2 vClearcoatMapUv;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tvarying vec2 vClearcoatNormalMapUv;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tvarying vec2 vClearcoatRoughnessMapUv;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tvarying vec2 vIridescenceMapUv;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tvarying vec2 vIridescenceThicknessMapUv;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tvarying vec2 vSheenColorMapUv;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tvarying vec2 vSheenRoughnessMapUv;\n#endif\n#ifdef USE_SPECULARMAP\n\tvarying vec2 vSpecularMapUv;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tvarying vec2 vSpecularColorMapUv;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tvarying vec2 vSpecularIntensityMapUv;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tuniform mat3 transmissionMapTransform;\n\tvarying vec2 vTransmissionMapUv;\n#endif\n#ifdef USE_THICKNESSMAP\n\tuniform mat3 thicknessMapTransform;\n\tvarying vec2 vThicknessMapUv;\n#endif",uv_pars_vertex:"#ifdef USE_UV\n\tvarying vec2 vUv;\n#endif\n#ifdef USE_MAP\n\tuniform mat3 mapTransform;\n\tvarying vec2 vMapUv;\n#endif\n#ifdef USE_ALPHAMAP\n\tuniform mat3 alphaMapTransform;\n\tvarying vec2 vAlphaMapUv;\n#endif\n#ifdef USE_LIGHTMAP\n\tuniform mat3 lightMapTransform;\n\tvarying vec2 vLightMapUv;\n#endif\n#ifdef USE_AOMAP\n\tuniform mat3 aoMapTransform;\n\tvarying vec2 vAoMapUv;\n#endif\n#ifdef USE_BUMPMAP\n\tuniform mat3 bumpMapTransform;\n\tvarying vec2 vBumpMapUv;\n#endif\n#ifdef USE_NORMALMAP\n\tuniform mat3 normalMapTransform;\n\tvarying vec2 vNormalMapUv;\n#endif\n#ifdef USE_DISPLACEMENTMAP\n\tuniform mat3 displacementMapTransform;\n\tvarying vec2 vDisplacementMapUv;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tuniform mat3 emissiveMapTransform;\n\tvarying vec2 vEmissiveMapUv;\n#endif\n#ifdef USE_METALNESSMAP\n\tuniform mat3 metalnessMapTransform;\n\tvarying vec2 vMetalnessMapUv;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tuniform mat3 roughnessMapTransform;\n\tvarying vec2 vRoughnessMapUv;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tvarying vec2 vAnisotropyMapUv;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tuniform mat3 clearcoatMapTransform;\n\tvarying vec2 vClearcoatMapUv;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tuniform mat3 clearcoatNormalMapTransform;\n\tvarying vec2 vClearcoatNormalMapUv;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tuniform mat3 clearcoatRoughnessMapTransform;\n\tvarying vec2 vClearcoatRoughnessMapUv;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tuniform mat3 sheenColorMapTransform;\n\tvarying vec2 vSheenColorMapUv;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tuniform mat3 sheenRoughnessMapTransform;\n\tvarying vec2 vSheenRoughnessMapUv;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tuniform mat3 iridescenceMapTransform;\n\tvarying vec2 vIridescenceMapUv;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tuniform mat3 iridescenceThicknessMapTransform;\n\tvarying vec2 vIridescenceThicknessMapUv;\n#endif\n#ifdef USE_SPECULARMAP\n\tuniform mat3 specularMapTransform;\n\tvarying vec2 vSpecularMapUv;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tuniform mat3 specularColorMapTransform;\n\tvarying vec2 vSpecularColorMapUv;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tuniform mat3 specularIntensityMapTransform;\n\tvarying vec2 vSpecularIntensityMapUv;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tuniform mat3 transmissionMapTransform;\n\tvarying vec2 vTransmissionMapUv;\n#endif\n#ifdef USE_THICKNESSMAP\n\tuniform mat3 thicknessMapTransform;\n\tvarying vec2 vThicknessMapUv;\n#endif",uv_vertex:"#ifdef USE_UV\n\tvUv = vec3( uv, 1 ).xy;\n#endif\n#ifdef USE_MAP\n\tvMapUv = ( mapTransform * vec3( MAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ALPHAMAP\n\tvAlphaMapUv = ( alphaMapTransform * vec3( ALPHAMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_LIGHTMAP\n\tvLightMapUv = ( lightMapTransform * vec3( LIGHTMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_AOMAP\n\tvAoMapUv = ( aoMapTransform * vec3( AOMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_BUMPMAP\n\tvBumpMapUv = ( bumpMapTransform * vec3( BUMPMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_NORMALMAP\n\tvNormalMapUv = ( normalMapTransform * vec3( NORMALMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_DISPLACEMENTMAP\n\tvDisplacementMapUv = ( displacementMapTransform * vec3( DISPLACEMENTMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tvEmissiveMapUv = ( emissiveMapTransform * vec3( EMISSIVEMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_METALNESSMAP\n\tvMetalnessMapUv = ( metalnessMapTransform * vec3( METALNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tvRoughnessMapUv = ( roughnessMapTransform * vec3( ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tvAnisotropyMapUv = ( vec3( ANISOTROPYMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tvClearcoatMapUv = ( clearcoatMapTransform * vec3( CLEARCOATMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tvClearcoatNormalMapUv = ( clearcoatNormalMapTransform * vec3( CLEARCOAT_NORMALMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tvClearcoatRoughnessMapUv = ( clearcoatRoughnessMapTransform * vec3( CLEARCOAT_ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tvIridescenceMapUv = ( iridescenceMapTransform * vec3( IRIDESCENCEMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tvIridescenceThicknessMapUv = ( iridescenceThicknessMapTransform * vec3( IRIDESCENCE_THICKNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tvSheenColorMapUv = ( sheenColorMapTransform * vec3( SHEEN_COLORMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tvSheenRoughnessMapUv = ( sheenRoughnessMapTransform * vec3( SHEEN_ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULARMAP\n\tvSpecularMapUv = ( specularMapTransform * vec3( SPECULARMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tvSpecularColorMapUv = ( specularColorMapTransform * vec3( SPECULAR_COLORMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tvSpecularIntensityMapUv = ( specularIntensityMapTransform * vec3( SPECULAR_INTENSITYMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tvTransmissionMapUv = ( transmissionMapTransform * vec3( TRANSMISSIONMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_THICKNESSMAP\n\tvThicknessMapUv = ( thicknessMapTransform * vec3( THICKNESSMAP_UV, 1 ) ).xy;\n#endif",worldpos_vertex:"#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined ( USE_SHADOWMAP ) || defined ( USE_TRANSMISSION ) || NUM_SPOT_LIGHT_COORDS > 0\n\tvec4 worldPosition = vec4( transformed, 1.0 );\n\t#ifdef USE_INSTANCING\n\t\tworldPosition = instanceMatrix * worldPosition;\n\t#endif\n\tworldPosition = modelMatrix * worldPosition;\n#endif",background_vert:"varying vec2 vUv;\nuniform mat3 uvTransform;\nvoid main() {\n\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n\tgl_Position = vec4( position.xy, 1.0, 1.0 );\n}",background_frag:"uniform sampler2D t2D;\nuniform float backgroundIntensity;\nvarying vec2 vUv;\nvoid main() {\n\tvec4 texColor = texture2D( t2D, vUv );\n\ttexColor.rgb *= backgroundIntensity;\n\tgl_FragColor = texColor;\n\t#include \n\t#include \n}",backgroundCube_vert:"varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n\tgl_Position.z = gl_Position.w;\n}",backgroundCube_frag:"#ifdef ENVMAP_TYPE_CUBE\n\tuniform samplerCube envMap;\n#elif defined( ENVMAP_TYPE_CUBE_UV )\n\tuniform sampler2D envMap;\n#endif\nuniform float flipEnvMap;\nuniform float backgroundBlurriness;\nuniform float backgroundIntensity;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 texColor = textureCube( envMap, vec3( flipEnvMap * vWorldDirection.x, vWorldDirection.yz ) );\n\t#elif defined( ENVMAP_TYPE_CUBE_UV )\n\t\tvec4 texColor = textureCubeUV( envMap, vWorldDirection, backgroundBlurriness );\n\t#else\n\t\tvec4 texColor = vec4( 0.0, 0.0, 0.0, 1.0 );\n\t#endif\n\ttexColor.rgb *= backgroundIntensity;\n\tgl_FragColor = texColor;\n\t#include \n\t#include \n}",cube_vert:"varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n\tgl_Position.z = gl_Position.w;\n}",cube_frag:"uniform samplerCube tCube;\nuniform float tFlip;\nuniform float opacity;\nvarying vec3 vWorldDirection;\nvoid main() {\n\tvec4 texColor = textureCube( tCube, vec3( tFlip * vWorldDirection.x, vWorldDirection.yz ) );\n\tgl_FragColor = texColor;\n\tgl_FragColor.a *= opacity;\n\t#include \n\t#include \n}",depth_vert:"#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvHighPrecisionZW = gl_Position.zw;\n}",depth_frag:"#if DEPTH_PACKING == 3200\n\tuniform float opacity;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( 1.0 );\n\t#if DEPTH_PACKING == 3200\n\t\tdiffuseColor.a = opacity;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\tfloat fragCoordZ = 0.5 * vHighPrecisionZW[0] / vHighPrecisionZW[1] + 0.5;\n\t#if DEPTH_PACKING == 3200\n\t\tgl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity );\n\t#elif DEPTH_PACKING == 3201\n\t\tgl_FragColor = packDepthToRGBA( fragCoordZ );\n\t#endif\n}",distanceRGBA_vert:"#define DISTANCE\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvWorldPosition = worldPosition.xyz;\n}",distanceRGBA_frag:"#define DISTANCE\nuniform vec3 referencePosition;\nuniform float nearDistance;\nuniform float farDistance;\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main () {\n\t#include \n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include \n\t#include \n\t#include \n\tfloat dist = length( vWorldPosition - referencePosition );\n\tdist = ( dist - nearDistance ) / ( farDistance - nearDistance );\n\tdist = saturate( dist );\n\tgl_FragColor = packDepthToRGBA( dist );\n}",equirect_vert:"varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n}",equirect_frag:"uniform sampler2D tEquirect;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvec3 direction = normalize( vWorldDirection );\n\tvec2 sampleUV = equirectUv( direction );\n\tgl_FragColor = texture2D( tEquirect, sampleUV );\n\t#include \n\t#include \n}",linedashed_vert:"uniform float scale;\nattribute float lineDistance;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvLineDistance = scale * lineDistance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",linedashed_frag:"uniform vec3 diffuse;\nuniform float opacity;\nuniform float dashSize;\nuniform float totalSize;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tif ( mod( vLineDistance, totalSize ) > dashSize ) {\n\t\tdiscard;\n\t}\n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshbasic_vert:"#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#if defined ( USE_ENVMAP ) || defined ( USE_SKINNING )\n\t\t#include \n\t\t#include \n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshbasic_frag:"uniform vec3 diffuse;\nuniform float opacity;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel = texture2D( lightMap, vLightMapUv );\n\t\treflectedLight.indirectDiffuse += lightMapTexel.rgb * lightMapIntensity * RECIPROCAL_PI;\n\t#else\n\t\treflectedLight.indirectDiffuse += vec3( 1.0 );\n\t#endif\n\t#include \n\treflectedLight.indirectDiffuse *= diffuseColor.rgb;\n\tvec3 outgoingLight = reflectedLight.indirectDiffuse;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshlambert_vert:"#define LAMBERT\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n\t#include \n}",meshlambert_frag:"#define LAMBERT\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshmatcap_vert:"#define MATCAP\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n}",meshmatcap_frag:"#define MATCAP\nuniform vec3 diffuse;\nuniform float opacity;\nuniform sampler2D matcap;\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 viewDir = normalize( vViewPosition );\n\tvec3 x = normalize( vec3( viewDir.z, 0.0, - viewDir.x ) );\n\tvec3 y = cross( viewDir, x );\n\tvec2 uv = vec2( dot( x, normal ), dot( y, normal ) ) * 0.495 + 0.5;\n\t#ifdef USE_MATCAP\n\t\tvec4 matcapColor = texture2D( matcap, uv );\n\t#else\n\t\tvec4 matcapColor = vec4( vec3( mix( 0.2, 0.8, uv.y ) ), 1.0 );\n\t#endif\n\tvec3 outgoingLight = diffuseColor.rgb * matcapColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshnormal_vert:"#define NORMAL\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvarying vec3 vViewPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvViewPosition = - mvPosition.xyz;\n#endif\n}",meshnormal_frag:"#define NORMAL\nuniform float opacity;\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvarying vec3 vViewPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\tgl_FragColor = vec4( packNormalToRGB( normal ), opacity );\n\t#ifdef OPAQUE\n\t\tgl_FragColor.a = 1.0;\n\t#endif\n}",meshphong_vert:"#define PHONG\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n\t#include \n}",meshphong_frag:"#define PHONG\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform vec3 specular;\nuniform float shininess;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshphysical_vert:"#define STANDARD\nvarying vec3 vViewPosition;\n#ifdef USE_TRANSMISSION\n\tvarying vec3 vWorldPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n#ifdef USE_TRANSMISSION\n\tvWorldPosition = worldPosition.xyz;\n#endif\n}",meshphysical_frag:"#define STANDARD\n#ifdef PHYSICAL\n\t#define IOR\n\t#define USE_SPECULAR\n#endif\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float roughness;\nuniform float metalness;\nuniform float opacity;\n#ifdef IOR\n\tuniform float ior;\n#endif\n#ifdef USE_SPECULAR\n\tuniform float specularIntensity;\n\tuniform vec3 specularColor;\n\t#ifdef USE_SPECULAR_COLORMAP\n\t\tuniform sampler2D specularColorMap;\n\t#endif\n\t#ifdef USE_SPECULAR_INTENSITYMAP\n\t\tuniform sampler2D specularIntensityMap;\n\t#endif\n#endif\n#ifdef USE_CLEARCOAT\n\tuniform float clearcoat;\n\tuniform float clearcoatRoughness;\n#endif\n#ifdef USE_IRIDESCENCE\n\tuniform float iridescence;\n\tuniform float iridescenceIOR;\n\tuniform float iridescenceThicknessMinimum;\n\tuniform float iridescenceThicknessMaximum;\n#endif\n#ifdef USE_SHEEN\n\tuniform vec3 sheenColor;\n\tuniform float sheenRoughness;\n\t#ifdef USE_SHEEN_COLORMAP\n\t\tuniform sampler2D sheenColorMap;\n\t#endif\n\t#ifdef USE_SHEEN_ROUGHNESSMAP\n\t\tuniform sampler2D sheenRoughnessMap;\n\t#endif\n#endif\n#ifdef USE_ANISOTROPY\n\tuniform vec2 anisotropyVector;\n\t#ifdef USE_ANISOTROPYMAP\n\t\tuniform sampler2D anisotropyMap;\n\t#endif\n#endif\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 totalDiffuse = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse;\n\tvec3 totalSpecular = reflectedLight.directSpecular + reflectedLight.indirectSpecular;\n\t#include \n\tvec3 outgoingLight = totalDiffuse + totalSpecular + totalEmissiveRadiance;\n\t#ifdef USE_SHEEN\n\t\tfloat sheenEnergyComp = 1.0 - 0.157 * max3( material.sheenColor );\n\t\toutgoingLight = outgoingLight * sheenEnergyComp + sheenSpecular;\n\t#endif\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNVcc = saturate( dot( geometry.clearcoatNormal, geometry.viewDir ) );\n\t\tvec3 Fcc = F_Schlick( material.clearcoatF0, material.clearcoatF90, dotNVcc );\n\t\toutgoingLight = outgoingLight * ( 1.0 - material.clearcoat * Fcc ) + clearcoatSpecular * material.clearcoat;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshtoon_vert:"#define TOON\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n}",meshtoon_frag:"#define TOON\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",points_vert:"uniform float size;\nuniform float scale;\n#include \n#include \n#include \n#include \n#include \n#include \n#ifdef USE_POINTS_UV\n\tvarying vec2 vUv;\n\tuniform mat3 uvTransform;\n#endif\nvoid main() {\n\t#ifdef USE_POINTS_UV\n\t\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tgl_PointSize = size;\n\t#ifdef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) gl_PointSize *= ( scale / - mvPosition.z );\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n}",points_frag:"uniform vec3 diffuse;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",shadow_vert:"#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",shadow_frag:"uniform vec3 color;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tgl_FragColor = vec4( color, opacity * ( 1.0 - getShadowMask() ) );\n\t#include \n\t#include \n\t#include \n}",sprite_vert:"uniform float rotation;\nuniform vec2 center;\n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 mvPosition = modelViewMatrix * vec4( 0.0, 0.0, 0.0, 1.0 );\n\tvec2 scale;\n\tscale.x = length( vec3( modelMatrix[ 0 ].x, modelMatrix[ 0 ].y, modelMatrix[ 0 ].z ) );\n\tscale.y = length( vec3( modelMatrix[ 1 ].x, modelMatrix[ 1 ].y, modelMatrix[ 1 ].z ) );\n\t#ifndef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) scale *= - mvPosition.z;\n\t#endif\n\tvec2 alignedPosition = ( position.xy - ( center - vec2( 0.5 ) ) ) * scale;\n\tvec2 rotatedPosition;\n\trotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;\n\trotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;\n\tmvPosition.xy += rotatedPosition;\n\tgl_Position = projectionMatrix * mvPosition;\n\t#include \n\t#include \n\t#include \n}",sprite_frag:"uniform vec3 diffuse;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n}"},Qs={common:{diffuse:{value:new Or(16777215)},opacity:{value:1},map:{value:null},mapTransform:{value:new Hn},alphaMap:{value:null},alphaMapTransform:{value:new Hn},alphaTest:{value:0}},specularmap:{specularMap:{value:null},specularMapTransform:{value:new Hn}},envmap:{envMap:{value:null},flipEnvMap:{value:-1},reflectivity:{value:1},ior:{value:1.5},refractionRatio:{value:.98}},aomap:{aoMap:{value:null},aoMapIntensity:{value:1},aoMapTransform:{value:new Hn}},lightmap:{lightMap:{value:null},lightMapIntensity:{value:1},lightMapTransform:{value:new Hn}},bumpmap:{bumpMap:{value:null},bumpMapTransform:{value:new Hn},bumpScale:{value:1}},normalmap:{normalMap:{value:null},normalMapTransform:{value:new Hn},normalScale:{value:new zn(1,1)}},displacementmap:{displacementMap:{value:null},displacementMapTransform:{value:new Hn},displacementScale:{value:1},displacementBias:{value:0}},emissivemap:{emissiveMap:{value:null},emissiveMapTransform:{value:new Hn}},metalnessmap:{metalnessMap:{value:null},metalnessMapTransform:{value:new Hn}},roughnessmap:{roughnessMap:{value:null},roughnessMapTransform:{value:new Hn}},gradientmap:{gradientMap:{value:null}},fog:{fogDensity:{value:25e-5},fogNear:{value:1},fogFar:{value:2e3},fogColor:{value:new Or(16777215)}},lights:{ambientLightColor:{value:[]},lightProbe:{value:[]},directionalLights:{value:[],properties:{direction:{},color:{}}},directionalLightShadows:{value:[],properties:{shadowBias:{},shadowNormalBias:{},shadowRadius:{},shadowMapSize:{}}},directionalShadowMap:{value:[]},directionalShadowMatrix:{value:[]},spotLights:{value:[],properties:{color:{},position:{},direction:{},distance:{},coneCos:{},penumbraCos:{},decay:{}}},spotLightShadows:{value:[],properties:{shadowBias:{},shadowNormalBias:{},shadowRadius:{},shadowMapSize:{}}},spotLightMap:{value:[]},spotShadowMap:{value:[]},spotLightMatrix:{value:[]},pointLights:{value:[],properties:{color:{},position:{},decay:{},distance:{}}},pointLightShadows:{value:[],properties:{shadowBias:{},shadowNormalBias:{},shadowRadius:{},shadowMapSize:{},shadowCameraNear:{},shadowCameraFar:{}}},pointShadowMap:{value:[]},pointShadowMatrix:{value:[]},hemisphereLights:{value:[],properties:{direction:{},skyColor:{},groundColor:{}}},rectAreaLights:{value:[],properties:{color:{},position:{},width:{},height:{}}},ltc_1:{value:null},ltc_2:{value:null}},points:{diffuse:{value:new Or(16777215)},opacity:{value:1},size:{value:1},scale:{value:1},map:{value:null},alphaMap:{value:null},alphaTest:{value:0},uvTransform:{value:new Hn}},sprite:{diffuse:{value:new Or(16777215)},opacity:{value:1},center:{value:new zn(.5,.5)},rotation:{value:0},map:{value:null},mapTransform:{value:new Hn},alphaMap:{value:null},alphaTest:{value:0}}},ta={basic:{uniforms:Is([Qs.common,Qs.specularmap,Qs.envmap,Qs.aomap,Qs.lightmap,Qs.fog]),vertexShader:$s.meshbasic_vert,fragmentShader:$s.meshbasic_frag},lambert:{uniforms:Is([Qs.common,Qs.specularmap,Qs.envmap,Qs.aomap,Qs.lightmap,Qs.emissivemap,Qs.bumpmap,Qs.normalmap,Qs.displacementmap,Qs.fog,Qs.lights,{emissive:{value:new Or(0)}}]),vertexShader:$s.meshlambert_vert,fragmentShader:$s.meshlambert_frag},phong:{uniforms:Is([Qs.common,Qs.specularmap,Qs.envmap,Qs.aomap,Qs.lightmap,Qs.emissivemap,Qs.bumpmap,Qs.normalmap,Qs.displacementmap,Qs.fog,Qs.lights,{emissive:{value:new Or(0)},specular:{value:new Or(1118481)},shininess:{value:30}}]),vertexShader:$s.meshphong_vert,fragmentShader:$s.meshphong_frag},standard:{uniforms:Is([Qs.common,Qs.envmap,Qs.aomap,Qs.lightmap,Qs.emissivemap,Qs.bumpmap,Qs.normalmap,Qs.displacementmap,Qs.roughnessmap,Qs.metalnessmap,Qs.fog,Qs.lights,{emissive:{value:new Or(0)},roughness:{value:1},metalness:{value:0},envMapIntensity:{value:1}}]),vertexShader:$s.meshphysical_vert,fragmentShader:$s.meshphysical_frag},toon:{uniforms:Is([Qs.common,Qs.aomap,Qs.lightmap,Qs.emissivemap,Qs.bumpmap,Qs.normalmap,Qs.displacementmap,Qs.gradientmap,Qs.fog,Qs.lights,{emissive:{value:new Or(0)}}]),vertexShader:$s.meshtoon_vert,fragmentShader:$s.meshtoon_frag},matcap:{uniforms:Is([Qs.common,Qs.bumpmap,Qs.normalmap,Qs.displacementmap,Qs.fog,{matcap:{value:null}}]),vertexShader:$s.meshmatcap_vert,fragmentShader:$s.meshmatcap_frag},points:{uniforms:Is([Qs.points,Qs.fog]),vertexShader:$s.points_vert,fragmentShader:$s.points_frag},dashed:{uniforms:Is([Qs.common,Qs.fog,{scale:{value:1},dashSize:{value:1},totalSize:{value:2}}]),vertexShader:$s.linedashed_vert,fragmentShader:$s.linedashed_frag},depth:{uniforms:Is([Qs.common,Qs.displacementmap]),vertexShader:$s.depth_vert,fragmentShader:$s.depth_frag},normal:{uniforms:Is([Qs.common,Qs.bumpmap,Qs.normalmap,Qs.displacementmap,{opacity:{value:1}}]),vertexShader:$s.meshnormal_vert,fragmentShader:$s.meshnormal_frag},sprite:{uniforms:Is([Qs.sprite,Qs.fog]),vertexShader:$s.sprite_vert,fragmentShader:$s.sprite_frag},background:{uniforms:{uvTransform:{value:new Hn},t2D:{value:null},backgroundIntensity:{value:1}},vertexShader:$s.background_vert,fragmentShader:$s.background_frag},backgroundCube:{uniforms:{envMap:{value:null},flipEnvMap:{value:-1},backgroundBlurriness:{value:0},backgroundIntensity:{value:1}},vertexShader:$s.backgroundCube_vert,fragmentShader:$s.backgroundCube_frag},cube:{uniforms:{tCube:{value:null},tFlip:{value:-1},opacity:{value:1}},vertexShader:$s.cube_vert,fragmentShader:$s.cube_frag},equirect:{uniforms:{tEquirect:{value:null}},vertexShader:$s.equirect_vert,fragmentShader:$s.equirect_frag},distanceRGBA:{uniforms:Is([Qs.common,Qs.displacementmap,{referencePosition:{value:new gi},nearDistance:{value:1},farDistance:{value:1e3}}]),vertexShader:$s.distanceRGBA_vert,fragmentShader:$s.distanceRGBA_frag},shadow:{uniforms:Is([Qs.lights,Qs.fog,{color:{value:new Or(0)},opacity:{value:1}}]),vertexShader:$s.shadow_vert,fragmentShader:$s.shadow_frag}};ta.physical={uniforms:Is([ta.standard.uniforms,{clearcoat:{value:0},clearcoatMap:{value:null},clearcoatMapTransform:{value:new Hn},clearcoatNormalMap:{value:null},clearcoatNormalMapTransform:{value:new Hn},clearcoatNormalScale:{value:new zn(1,1)},clearcoatRoughness:{value:0},clearcoatRoughnessMap:{value:null},clearcoatRoughnessMapTransform:{value:new Hn},iridescence:{value:0},iridescenceMap:{value:null},iridescenceMapTransform:{value:new Hn},iridescenceIOR:{value:1.3},iridescenceThicknessMinimum:{value:100},iridescenceThicknessMaximum:{value:400},iridescenceThicknessMap:{value:null},iridescenceThicknessMapTransform:{value:new Hn},sheen:{value:0},sheenColor:{value:new Or(0)},sheenColorMap:{value:null},sheenColorMapTransform:{value:new Hn},sheenRoughness:{value:1},sheenRoughnessMap:{value:null},sheenRoughnessMapTransform:{value:new Hn},transmission:{value:0},transmissionMap:{value:null},transmissionMapTransform:{value:new Hn},transmissionSamplerSize:{value:new zn},transmissionSamplerMap:{value:null},thickness:{value:0},thicknessMap:{value:null},thicknessMapTransform:{value:new Hn},attenuationDistance:{value:0},attenuationColor:{value:new Or(0)},specularColor:{value:new Or(1,1,1)},specularColorMap:{value:null},specularColorMapTransform:{value:new Hn},specularIntensity:{value:1},specularIntensityMap:{value:null},specularIntensityMapTransform:{value:new Hn},anisotropyVector:{value:new zn},anisotropyMap:{value:null}}]),vertexShader:$s.meshphysical_vert,fragmentShader:$s.meshphysical_frag};const ea={r:0,b:0,g:0};function na(t,e,n,i,r,s,a){const o=new Or(0);let l,c,h=!0===s?0:1,u=null,d=0,p=null;function m(e,n){e.getRGB(ea,Us(t)),i.buffers.color.setClear(ea.r,ea.g,ea.b,n,a)}return{getClearColor:function(){return o},setClearColor:function(t,e=1){o.set(t),h=e,m(o,h)},getClearAlpha:function(){return h},setClearAlpha:function(t){h=t,m(o,h)},render:function(s,f){let g=!1,v=!0===f.isScene?f.background:null;if(v&&v.isTexture){v=(f.backgroundBlurriness>0?n:e).get(v)}switch(null===v?m(o,h):v&&v.isColor&&(m(v,1),g=!0),t.xr.getEnvironmentBlendMode()){case"opaque":g=!0;break;case"additive":i.buffers.color.setClear(0,0,0,1,a),g=!0;break;case"alpha-blend":i.buffers.color.setClear(0,0,0,0,a),g=!0}(t.autoClear||g)&&t.clear(t.autoClearColor,t.autoClearDepth,t.autoClearStencil),v&&(v.isCubeTexture||v.mapping===st)?(void 0===c&&(c=new Rs(new Ps(1,1,1),new Ds({name:"BackgroundCubeMaterial",uniforms:Ls(ta.backgroundCube.uniforms),vertexShader:ta.backgroundCube.vertexShader,fragmentShader:ta.backgroundCube.fragmentShader,side:1,depthTest:!1,depthWrite:!1,fog:!1})),c.geometry.deleteAttribute("normal"),c.geometry.deleteAttribute("uv"),c.onBeforeRender=function(t,e,n){this.matrixWorld.copyPosition(n.matrixWorld)},Object.defineProperty(c.material,"envMap",{get:function(){return this.uniforms.envMap.value}}),r.update(c)),c.material.uniforms.envMap.value=v,c.material.uniforms.flipEnvMap.value=v.isCubeTexture&&!1===v.isRenderTargetTexture?-1:1,c.material.uniforms.backgroundBlurriness.value=f.backgroundBlurriness,c.material.uniforms.backgroundIntensity.value=f.backgroundIntensity,c.material.toneMapped=v.colorSpace!==Be,u===v&&d===v.version&&p===t.toneMapping||(c.material.needsUpdate=!0,u=v,d=v.version,p=t.toneMapping),c.layers.enableAll(),s.unshift(c,c.geometry,c.material,0,0,null)):v&&v.isTexture&&(void 0===l&&(l=new Rs(new Ks(2,2),new Ds({name:"BackgroundMaterial",uniforms:Ls(ta.background.uniforms),vertexShader:ta.background.vertexShader,fragmentShader:ta.background.fragmentShader,side:0,depthTest:!1,depthWrite:!1,fog:!1})),l.geometry.deleteAttribute("normal"),Object.defineProperty(l.material,"map",{get:function(){return this.uniforms.t2D.value}}),r.update(l)),l.material.uniforms.t2D.value=v,l.material.uniforms.backgroundIntensity.value=f.backgroundIntensity,l.material.toneMapped=v.colorSpace!==Be,!0===v.matrixAutoUpdate&&v.updateMatrix(),l.material.uniforms.uvTransform.value.copy(v.matrix),u===v&&d===v.version&&p===t.toneMapping||(l.material.needsUpdate=!0,u=v,d=v.version,p=t.toneMapping),l.layers.enableAll(),s.unshift(l,l.geometry,l.material,0,0,null))}}}function ia(t,e,n,i){const r=t.getParameter(t.MAX_VERTEX_ATTRIBS),s=i.isWebGL2?null:e.get("OES_vertex_array_object"),a=i.isWebGL2||null!==s,o={},l=p(null);let c=l,h=!1;function u(e){return i.isWebGL2?t.bindVertexArray(e):s.bindVertexArrayOES(e)}function d(e){return i.isWebGL2?t.deleteVertexArray(e):s.deleteVertexArrayOES(e)}function p(t){const e=[],n=[],i=[];for(let t=0;t=0){const n=r[e];let i=s[e];if(void 0===i&&("instanceMatrix"===e&&t.instanceMatrix&&(i=t.instanceMatrix),"instanceColor"===e&&t.instanceColor&&(i=t.instanceColor)),void 0===n)return!0;if(n.attribute!==i)return!0;if(i&&n.data!==i.data)return!0;a++}}return c.attributesNum!==a||c.index!==i}(r,y,d,x),M&&function(t,e,n,i){const r={},s=e.attributes;let a=0;const o=n.getAttributes();for(const e in o){if(o[e].location>=0){let n=s[e];void 0===n&&("instanceMatrix"===e&&t.instanceMatrix&&(n=t.instanceMatrix),"instanceColor"===e&&t.instanceColor&&(n=t.instanceColor));const i={};i.attribute=n,n&&n.data&&(i.data=n.data),r[e]=i,a++}}c.attributes=r,c.attributesNum=a,c.index=i}(r,y,d,x)}else{const t=!0===l.wireframe;c.geometry===y.id&&c.program===d.id&&c.wireframe===t||(c.geometry=y.id,c.program=d.id,c.wireframe=t,M=!0)}null!==x&&n.update(x,t.ELEMENT_ARRAY_BUFFER),(M||h)&&(h=!1,function(r,s,a,o){if(!1===i.isWebGL2&&(r.isInstancedMesh||o.isInstancedBufferGeometry)&&null===e.get("ANGLE_instanced_arrays"))return;m();const l=o.attributes,c=a.getAttributes(),h=s.defaultAttributeValues;for(const e in c){const s=c[e];if(s.location>=0){let a=l[e];if(void 0===a&&("instanceMatrix"===e&&r.instanceMatrix&&(a=r.instanceMatrix),"instanceColor"===e&&r.instanceColor&&(a=r.instanceColor)),void 0!==a){const e=a.normalized,l=a.itemSize,c=n.get(a);if(void 0===c)continue;const h=c.buffer,u=c.type,d=c.bytesPerElement,p=!0===i.isWebGL2&&(u===t.INT||u===t.UNSIGNED_INT||a.gpuType===bt);if(a.isInterleavedBufferAttribute){const n=a.data,i=n.stride,c=a.offset;if(n.isInstancedInterleavedBuffer){for(let t=0;t0&&t.getShaderPrecisionFormat(t.FRAGMENT_SHADER,t.HIGH_FLOAT).precision>0)return"highp";e="mediump"}return"mediump"===e&&t.getShaderPrecisionFormat(t.VERTEX_SHADER,t.MEDIUM_FLOAT).precision>0&&t.getShaderPrecisionFormat(t.FRAGMENT_SHADER,t.MEDIUM_FLOAT).precision>0?"mediump":"lowp"}const s="undefined"!=typeof WebGL2RenderingContext&&"WebGL2RenderingContext"===t.constructor.name;let a=void 0!==n.precision?n.precision:"highp";const o=r(a);o!==a&&(console.warn("THREE.WebGLRenderer:",a,"not supported, using",o,"instead."),a=o);const l=s||e.has("WEBGL_draw_buffers"),c=!0===n.logarithmicDepthBuffer,h=t.getParameter(t.MAX_TEXTURE_IMAGE_UNITS),u=t.getParameter(t.MAX_VERTEX_TEXTURE_IMAGE_UNITS),d=t.getParameter(t.MAX_TEXTURE_SIZE),p=t.getParameter(t.MAX_CUBE_MAP_TEXTURE_SIZE),m=t.getParameter(t.MAX_VERTEX_ATTRIBS),f=t.getParameter(t.MAX_VERTEX_UNIFORM_VECTORS),g=t.getParameter(t.MAX_VARYING_VECTORS),v=t.getParameter(t.MAX_FRAGMENT_UNIFORM_VECTORS),_=u>0,y=s||e.has("OES_texture_float");return{isWebGL2:s,drawBuffers:l,getMaxAnisotropy:function(){if(void 0!==i)return i;if(!0===e.has("EXT_texture_filter_anisotropic")){const n=e.get("EXT_texture_filter_anisotropic");i=t.getParameter(n.MAX_TEXTURE_MAX_ANISOTROPY_EXT)}else i=0;return i},getMaxPrecision:r,precision:a,logarithmicDepthBuffer:c,maxTextures:h,maxVertexTextures:u,maxTextureSize:d,maxCubemapSize:p,maxAttributes:m,maxVertexUniforms:f,maxVaryings:g,maxFragmentUniforms:v,vertexTextures:_,floatFragmentTextures:y,floatVertexTextures:_&&y,maxSamples:s?t.getParameter(t.MAX_SAMPLES):0}}function aa(t){const e=this;let n=null,i=0,r=!1,s=!1;const a=new Xs,o=new Hn,l={value:null,needsUpdate:!1};function c(t,n,i,r){const s=null!==t?t.length:0;let c=null;if(0!==s){if(c=l.value,!0!==r||null===c){const e=i+4*s,r=n.matrixWorldInverse;o.getNormalMatrix(r),(null===c||c.length0);e.numPlanes=i,e.numIntersection=0}();else{const t=s?0:i,e=4*t;let r=m.clippingState||null;l.value=r,r=c(u,o,e,h);for(let t=0;t!==e;++t)r[t]=n[t];m.clippingState=r,this.numIntersection=d?this.numPlanes:0,this.numPlanes+=t}}}function oa(t){let e=new WeakMap;function n(t,e){return e===it?t.mapping=et:e===rt&&(t.mapping=nt),t}function i(t){const n=t.target;n.removeEventListener("dispose",i);const r=e.get(n);void 0!==r&&(e.delete(n),r.dispose())}return{get:function(r){if(r&&r.isTexture&&!1===r.isRenderTargetTexture){const s=r.mapping;if(s===it||s===rt){if(e.has(r)){return n(e.get(r).texture,r.mapping)}{const s=r.image;if(s&&s.height>0){const a=new ks(s.height/2);return a.fromEquirectangularTexture(t,r),e.set(r,a),r.addEventListener("dispose",i),n(a.texture,r.mapping)}return null}}}return r},dispose:function(){e=new WeakMap}}}class la extends Os{constructor(t=-1,e=1,n=1,i=-1,r=.1,s=2e3){super(),this.isOrthographicCamera=!0,this.type="OrthographicCamera",this.zoom=1,this.view=null,this.left=t,this.right=e,this.top=n,this.bottom=i,this.near=r,this.far=s,this.updateProjectionMatrix()}copy(t,e){return super.copy(t,e),this.left=t.left,this.right=t.right,this.top=t.top,this.bottom=t.bottom,this.near=t.near,this.far=t.far,this.zoom=t.zoom,this.view=null===t.view?null:Object.assign({},t.view),this}setViewOffset(t,e,n,i,r,s){null===this.view&&(this.view={enabled:!0,fullWidth:1,fullHeight:1,offsetX:0,offsetY:0,width:1,height:1}),this.view.enabled=!0,this.view.fullWidth=t,this.view.fullHeight=e,this.view.offsetX=n,this.view.offsetY=i,this.view.width=r,this.view.height=s,this.updateProjectionMatrix()}clearViewOffset(){null!==this.view&&(this.view.enabled=!1),this.updateProjectionMatrix()}updateProjectionMatrix(){const t=(this.right-this.left)/(2*this.zoom),e=(this.top-this.bottom)/(2*this.zoom),n=(this.right+this.left)/2,i=(this.top+this.bottom)/2;let r=n-t,s=n+t,a=i+e,o=i-e;if(null!==this.view&&this.view.enabled){const t=(this.right-this.left)/this.view.fullWidth/this.zoom,e=(this.top-this.bottom)/this.view.fullHeight/this.zoom;r+=t*this.view.offsetX,s=r+t*this.view.width,a-=e*this.view.offsetY,o=a-e*this.view.height}this.projectionMatrix.makeOrthographic(r,s,a,o,this.near,this.far),this.projectionMatrixInverse.copy(this.projectionMatrix).invert()}toJSON(t){const e=super.toJSON(t);return e.object.zoom=this.zoom,e.object.left=this.left,e.object.right=this.right,e.object.top=this.top,e.object.bottom=this.bottom,e.object.near=this.near,e.object.far=this.far,null!==this.view&&(e.object.view=Object.assign({},this.view)),e}}const ca=[.125,.215,.35,.446,.526,.582],ha=20,ua=new la,da=new Or;let pa=null;const ma=(1+Math.sqrt(5))/2,fa=1/ma,ga=[new gi(1,1,1),new gi(-1,1,1),new gi(1,1,-1),new gi(-1,1,-1),new gi(0,ma,fa),new gi(0,ma,-fa),new gi(fa,0,ma),new gi(-fa,0,ma),new gi(ma,fa,0),new gi(-ma,fa,0)];class va{constructor(t){this._renderer=t,this._pingPongRenderTarget=null,this._lodMax=0,this._cubeSize=0,this._lodPlanes=[],this._sizeLods=[],this._sigmas=[],this._blurMaterial=null,this._cubemapMaterial=null,this._equirectMaterial=null,this._compileMaterial(this._blurMaterial)}fromScene(t,e=0,n=.1,i=100){pa=this._renderer.getRenderTarget(),this._setSize(256);const r=this._allocateTargets();return r.depthBuffer=!0,this._sceneToCubeUV(t,n,i,r),e>0&&this._blur(r,0,0,e),this._applyPMREM(r),this._cleanup(r),r}fromEquirectangular(t,e=null){return this._fromTexture(t,e)}fromCubemap(t,e=null){return this._fromTexture(t,e)}compileCubemapShader(){null===this._cubemapMaterial&&(this._cubemapMaterial=Ma(),this._compileMaterial(this._cubemapMaterial))}compileEquirectangularShader(){null===this._equirectMaterial&&(this._equirectMaterial=xa(),this._compileMaterial(this._equirectMaterial))}dispose(){this._dispose(),null!==this._cubemapMaterial&&this._cubemapMaterial.dispose(),null!==this._equirectMaterial&&this._equirectMaterial.dispose()}_setSize(t){this._lodMax=Math.floor(Math.log2(t)),this._cubeSize=Math.pow(2,this._lodMax)}_dispose(){null!==this._blurMaterial&&this._blurMaterial.dispose(),null!==this._pingPongRenderTarget&&this._pingPongRenderTarget.dispose();for(let t=0;tt-4?o=ca[a-t+4-1]:0===a&&(o=0),i.push(o);const l=1/(s-2),c=-l,h=1+l,u=[c,c,h,c,h,h,c,c,h,h,c,h],d=6,p=6,m=3,f=2,g=1,v=new Float32Array(m*p*d),_=new Float32Array(f*p*d),y=new Float32Array(g*p*d);for(let t=0;t2?0:-1,i=[e,n,0,e+2/3,n,0,e+2/3,n+1,0,e,n,0,e+2/3,n+1,0,e,n+1,0];v.set(i,m*p*t),_.set(u,f*p*t);const r=[t,t,t,t,t,t];y.set(r,g*p*t)}const x=new hs;x.setAttribute("position",new jr(v,m)),x.setAttribute("uv",new jr(_,f)),x.setAttribute("faceIndex",new jr(y,g)),e.push(x),r>4&&r--}return{lodPlanes:e,sizeLods:n,sigmas:i}}(i)),this._blurMaterial=function(t,e,n){const i=new Float32Array(ha),r=new gi(0,1,0),s=new Ds({name:"SphericalGaussianBlur",defines:{n:ha,CUBEUV_TEXEL_WIDTH:1/e,CUBEUV_TEXEL_HEIGHT:1/n,CUBEUV_MAX_MIP:`${t}.0`},uniforms:{envMap:{value:null},samples:{value:1},weights:{value:i},latitudinal:{value:!1},dTheta:{value:0},mipInt:{value:0},poleAxis:{value:r}},vertexShader:Sa(),fragmentShader:"\n\n\t\t\tprecision mediump float;\n\t\t\tprecision mediump int;\n\n\t\t\tvarying vec3 vOutputDirection;\n\n\t\t\tuniform sampler2D envMap;\n\t\t\tuniform int samples;\n\t\t\tuniform float weights[ n ];\n\t\t\tuniform bool latitudinal;\n\t\t\tuniform float dTheta;\n\t\t\tuniform float mipInt;\n\t\t\tuniform vec3 poleAxis;\n\n\t\t\t#define ENVMAP_TYPE_CUBE_UV\n\t\t\t#include \n\n\t\t\tvec3 getSample( float theta, vec3 axis ) {\n\n\t\t\t\tfloat cosTheta = cos( theta );\n\t\t\t\t// Rodrigues' axis-angle rotation\n\t\t\t\tvec3 sampleDirection = vOutputDirection * cosTheta\n\t\t\t\t\t+ cross( axis, vOutputDirection ) * sin( theta )\n\t\t\t\t\t+ axis * dot( axis, vOutputDirection ) * ( 1.0 - cosTheta );\n\n\t\t\t\treturn bilinearCubeUV( envMap, sampleDirection, mipInt );\n\n\t\t\t}\n\n\t\t\tvoid main() {\n\n\t\t\t\tvec3 axis = latitudinal ? poleAxis : cross( poleAxis, vOutputDirection );\n\n\t\t\t\tif ( all( equal( axis, vec3( 0.0 ) ) ) ) {\n\n\t\t\t\t\taxis = vec3( vOutputDirection.z, 0.0, - vOutputDirection.x );\n\n\t\t\t\t}\n\n\t\t\t\taxis = normalize( axis );\n\n\t\t\t\tgl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 );\n\t\t\t\tgl_FragColor.rgb += weights[ 0 ] * getSample( 0.0, axis );\n\n\t\t\t\tfor ( int i = 1; i < n; i++ ) {\n\n\t\t\t\t\tif ( i >= samples ) {\n\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\t}\n\n\t\t\t\t\tfloat theta = dTheta * float( i );\n\t\t\t\t\tgl_FragColor.rgb += weights[ i ] * getSample( -1.0 * theta, axis );\n\t\t\t\t\tgl_FragColor.rgb += weights[ i ] * getSample( theta, axis );\n\n\t\t\t\t}\n\n\t\t\t}\n\t\t",blending:0,depthTest:!1,depthWrite:!1});return s}(i,t,e)}return i}_compileMaterial(t){const e=new Rs(this._lodPlanes[0],t);this._renderer.compile(e,ua)}_sceneToCubeUV(t,e,n,i){const r=new Fs(90,1,e,n),s=[1,-1,1,1,1,1],a=[1,1,1,-1,-1,-1],o=this._renderer,l=o.autoClear,c=o.toneMapping;o.getClearColor(da),o.toneMapping=0,o.autoClear=!1;const h=new Br({name:"PMREM.Background",side:1,depthWrite:!1,depthTest:!1}),u=new Rs(new Ps,h);let d=!1;const p=t.background;p?p.isColor&&(h.color.copy(p),t.background=null,d=!0):(h.color.copy(da),d=!0);for(let e=0;e<6;e++){const n=e%3;0===n?(r.up.set(0,s[e],0),r.lookAt(a[e],0,0)):1===n?(r.up.set(0,0,s[e]),r.lookAt(0,a[e],0)):(r.up.set(0,s[e],0),r.lookAt(0,0,a[e]));const l=this._cubeSize;ya(i,n*l,e>2?l:0,l,l),o.setRenderTarget(i),d&&o.render(u,r),o.render(t,r)}u.geometry.dispose(),u.material.dispose(),o.toneMapping=c,o.autoClear=l,t.background=p}_textureToCubeUV(t,e){const n=this._renderer,i=t.mapping===et||t.mapping===nt;i?(null===this._cubemapMaterial&&(this._cubemapMaterial=Ma()),this._cubemapMaterial.uniforms.flipEnvMap.value=!1===t.isRenderTargetTexture?-1:1):null===this._equirectMaterial&&(this._equirectMaterial=xa());const r=i?this._cubemapMaterial:this._equirectMaterial,s=new Rs(this._lodPlanes[0],r);r.uniforms.envMap.value=t;const a=this._cubeSize;ya(e,0,0,3*a,2*a),n.setRenderTarget(e),n.render(s,ua)}_applyPMREM(t){const e=this._renderer,n=e.autoClear;e.autoClear=!1;for(let e=1;eha&&console.warn(`sigmaRadians, ${r}, is too large and will clip, as it requested ${m} samples when the maximum is set to 20`);const f=[];let g=0;for(let t=0;tv-4?i-v+4:0),4*(this._cubeSize-_),3*_,2*_),o.setRenderTarget(e),o.render(c,ua)}}function _a(t,e,n){const i=new ci(t,e,n);return i.texture.mapping=st,i.texture.name="PMREM.cubeUv",i.scissorTest=!0,i}function ya(t,e,n,i,r){t.viewport.set(e,n,i,r),t.scissor.set(e,n,i,r)}function xa(){return new Ds({name:"EquirectangularToCubeUV",uniforms:{envMap:{value:null}},vertexShader:Sa(),fragmentShader:"\n\n\t\t\tprecision mediump float;\n\t\t\tprecision mediump int;\n\n\t\t\tvarying vec3 vOutputDirection;\n\n\t\t\tuniform sampler2D envMap;\n\n\t\t\t#include \n\n\t\t\tvoid main() {\n\n\t\t\t\tvec3 outputDirection = normalize( vOutputDirection );\n\t\t\t\tvec2 uv = equirectUv( outputDirection );\n\n\t\t\t\tgl_FragColor = vec4( texture2D ( envMap, uv ).rgb, 1.0 );\n\n\t\t\t}\n\t\t",blending:0,depthTest:!1,depthWrite:!1})}function Ma(){return new Ds({name:"CubemapToCubeUV",uniforms:{envMap:{value:null},flipEnvMap:{value:-1}},vertexShader:Sa(),fragmentShader:"\n\n\t\t\tprecision mediump float;\n\t\t\tprecision mediump int;\n\n\t\t\tuniform float flipEnvMap;\n\n\t\t\tvarying vec3 vOutputDirection;\n\n\t\t\tuniform samplerCube envMap;\n\n\t\t\tvoid main() {\n\n\t\t\t\tgl_FragColor = textureCube( envMap, vec3( flipEnvMap * vOutputDirection.x, vOutputDirection.yz ) );\n\n\t\t\t}\n\t\t",blending:0,depthTest:!1,depthWrite:!1})}function Sa(){return"\n\n\t\tprecision mediump float;\n\t\tprecision mediump int;\n\n\t\tattribute float faceIndex;\n\n\t\tvarying vec3 vOutputDirection;\n\n\t\t// RH coordinate system; PMREM face-indexing convention\n\t\tvec3 getDirection( vec2 uv, float face ) {\n\n\t\t\tuv = 2.0 * uv - 1.0;\n\n\t\t\tvec3 direction = vec3( uv, 1.0 );\n\n\t\t\tif ( face == 0.0 ) {\n\n\t\t\t\tdirection = direction.zyx; // ( 1, v, u ) pos x\n\n\t\t\t} else if ( face == 1.0 ) {\n\n\t\t\t\tdirection = direction.xzy;\n\t\t\t\tdirection.xz *= -1.0; // ( -u, 1, -v ) pos y\n\n\t\t\t} else if ( face == 2.0 ) {\n\n\t\t\t\tdirection.x *= -1.0; // ( -u, v, 1 ) pos z\n\n\t\t\t} else if ( face == 3.0 ) {\n\n\t\t\t\tdirection = direction.zyx;\n\t\t\t\tdirection.xz *= -1.0; // ( -1, v, -u ) neg x\n\n\t\t\t} else if ( face == 4.0 ) {\n\n\t\t\t\tdirection = direction.xzy;\n\t\t\t\tdirection.xy *= -1.0; // ( -u, -1, v ) neg y\n\n\t\t\t} else if ( face == 5.0 ) {\n\n\t\t\t\tdirection.z *= -1.0; // ( u, v, -1 ) neg z\n\n\t\t\t}\n\n\t\t\treturn direction;\n\n\t\t}\n\n\t\tvoid main() {\n\n\t\t\tvOutputDirection = getDirection( uv, faceIndex );\n\t\t\tgl_Position = vec4( position, 1.0 );\n\n\t\t}\n\t"}function ba(t){let e=new WeakMap,n=null;function i(t){const n=t.target;n.removeEventListener("dispose",i);const r=e.get(n);void 0!==r&&(e.delete(n),r.dispose())}return{get:function(r){if(r&&r.isTexture){const s=r.mapping,a=s===it||s===rt,o=s===et||s===nt;if(a||o){if(r.isRenderTargetTexture&&!0===r.needsPMREMUpdate){r.needsPMREMUpdate=!1;let i=e.get(r);return null===n&&(n=new va(t)),i=a?n.fromEquirectangular(r,i):n.fromCubemap(r,i),e.set(r,i),i.texture}if(e.has(r))return e.get(r).texture;{const s=r.image;if(a&&s&&s.height>0||o&&s&&function(t){let e=0;const n=6;for(let i=0;ie.maxTextureSize&&(m=Math.ceil(p/e.maxTextureSize),p=e.maxTextureSize);const f=new Float32Array(p*m*4*r),g=new hi(f,p,m,r);g.type=Tt,g.needsUpdate=!0;const v=4*d;for(let e=0;e0)return t;const r=e*n;let s=Oa[r];if(void 0===s&&(s=new Float32Array(r),Oa[r]=s),0!==e){i.toArray(s,0);for(let i=1,r=0;i!==e;++i)r+=n,t[i].toArray(s,r)}return s}function Va(t,e){if(t.length!==e.length)return!1;for(let n=0,i=t.length;n":" "} ${r}: ${n[t]}`)}return i.join("\n")}(t.getShaderSource(e),i)}return r}function Ho(t,e){const n=function(t){switch(t){case ze:return["Linear","( value )"];case Be:return["sRGB","( value )"];default:return console.warn("THREE.WebGLProgram: Unsupported color space:",t),["Linear","( value )"]}}(e);return"vec4 "+t+"( vec4 value ) { return LinearTo"+n[0]+n[1]+"; }"}function ko(t,e){let n;switch(e){case 1:n="Linear";break;case 2:n="Reinhard";break;case 3:n="OptimizedCineon";break;case 4:n="ACESFilmic";break;case 5:n="Custom";break;default:console.warn("THREE.WebGLProgram: Unsupported toneMapping:",e),n="Linear"}return"vec3 "+t+"( vec3 color ) { return "+n+"ToneMapping( color ); }"}function Vo(t){return""!==t}function Go(t,e){const n=e.numSpotLightShadows+e.numSpotLightMaps-e.numSpotLightShadowsWithMaps;return t.replace(/NUM_DIR_LIGHTS/g,e.numDirLights).replace(/NUM_SPOT_LIGHTS/g,e.numSpotLights).replace(/NUM_SPOT_LIGHT_MAPS/g,e.numSpotLightMaps).replace(/NUM_SPOT_LIGHT_COORDS/g,n).replace(/NUM_RECT_AREA_LIGHTS/g,e.numRectAreaLights).replace(/NUM_POINT_LIGHTS/g,e.numPointLights).replace(/NUM_HEMI_LIGHTS/g,e.numHemiLights).replace(/NUM_DIR_LIGHT_SHADOWS/g,e.numDirLightShadows).replace(/NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS/g,e.numSpotLightShadowsWithMaps).replace(/NUM_SPOT_LIGHT_SHADOWS/g,e.numSpotLightShadows).replace(/NUM_POINT_LIGHT_SHADOWS/g,e.numPointLightShadows)}function Wo(t,e){return t.replace(/NUM_CLIPPING_PLANES/g,e.numClippingPlanes).replace(/UNION_CLIPPING_PLANES/g,e.numClippingPlanes-e.numClipIntersection)}const Xo=/^[ \t]*#include +<([\w\d./]+)>/gm;function jo(t){return t.replace(Xo,qo)}function qo(t,e){const n=$s[e];if(void 0===n)throw new Error("Can not resolve #include <"+e+">");return jo(n)}const Yo=/#pragma unroll_loop_start\s+for\s*\(\s*int\s+i\s*=\s*(\d+)\s*;\s*i\s*<\s*(\d+)\s*;\s*i\s*\+\+\s*\)\s*{([\s\S]+?)}\s+#pragma unroll_loop_end/g;function Zo(t){return t.replace(Yo,Jo)}function Jo(t,e,n,i){let r="";for(let t=parseInt(e);t0&&(g+="\n"),v=[p,m].filter(Vo).join("\n"),v.length>0&&(v+="\n")):(g=[Ko(n),"#define SHADER_NAME "+n.shaderName,m,n.instancing?"#define USE_INSTANCING":"",n.instancingColor?"#define USE_INSTANCING_COLOR":"",n.useFog&&n.fog?"#define USE_FOG":"",n.useFog&&n.fogExp2?"#define FOG_EXP2":"",n.map?"#define USE_MAP":"",n.envMap?"#define USE_ENVMAP":"",n.envMap?"#define "+h:"",n.lightMap?"#define USE_LIGHTMAP":"",n.aoMap?"#define USE_AOMAP":"",n.bumpMap?"#define USE_BUMPMAP":"",n.normalMap?"#define USE_NORMALMAP":"",n.normalMapObjectSpace?"#define USE_NORMALMAP_OBJECTSPACE":"",n.normalMapTangentSpace?"#define USE_NORMALMAP_TANGENTSPACE":"",n.displacementMap?"#define USE_DISPLACEMENTMAP":"",n.emissiveMap?"#define USE_EMISSIVEMAP":"",n.anisotropyMap?"#define USE_ANISOTROPYMAP":"",n.clearcoatMap?"#define USE_CLEARCOATMAP":"",n.clearcoatRoughnessMap?"#define USE_CLEARCOAT_ROUGHNESSMAP":"",n.clearcoatNormalMap?"#define USE_CLEARCOAT_NORMALMAP":"",n.iridescenceMap?"#define USE_IRIDESCENCEMAP":"",n.iridescenceThicknessMap?"#define USE_IRIDESCENCE_THICKNESSMAP":"",n.specularMap?"#define USE_SPECULARMAP":"",n.specularColorMap?"#define USE_SPECULAR_COLORMAP":"",n.specularIntensityMap?"#define USE_SPECULAR_INTENSITYMAP":"",n.roughnessMap?"#define USE_ROUGHNESSMAP":"",n.metalnessMap?"#define USE_METALNESSMAP":"",n.alphaMap?"#define USE_ALPHAMAP":"",n.transmission?"#define USE_TRANSMISSION":"",n.transmissionMap?"#define USE_TRANSMISSIONMAP":"",n.thicknessMap?"#define USE_THICKNESSMAP":"",n.sheenColorMap?"#define USE_SHEEN_COLORMAP":"",n.sheenRoughnessMap?"#define USE_SHEEN_ROUGHNESSMAP":"",n.mapUv?"#define MAP_UV "+n.mapUv:"",n.alphaMapUv?"#define ALPHAMAP_UV "+n.alphaMapUv:"",n.lightMapUv?"#define LIGHTMAP_UV "+n.lightMapUv:"",n.aoMapUv?"#define AOMAP_UV "+n.aoMapUv:"",n.emissiveMapUv?"#define EMISSIVEMAP_UV "+n.emissiveMapUv:"",n.bumpMapUv?"#define BUMPMAP_UV "+n.bumpMapUv:"",n.normalMapUv?"#define NORMALMAP_UV "+n.normalMapUv:"",n.displacementMapUv?"#define DISPLACEMENTMAP_UV "+n.displacementMapUv:"",n.metalnessMapUv?"#define METALNESSMAP_UV "+n.metalnessMapUv:"",n.roughnessMapUv?"#define ROUGHNESSMAP_UV "+n.roughnessMapUv:"",n.anisotropyMapUv?"#define ANISOTROPYMAP_UV "+n.anisotropyMapUv:"",n.clearcoatMapUv?"#define CLEARCOATMAP_UV "+n.clearcoatMapUv:"",n.clearcoatNormalMapUv?"#define CLEARCOAT_NORMALMAP_UV "+n.clearcoatNormalMapUv:"",n.clearcoatRoughnessMapUv?"#define CLEARCOAT_ROUGHNESSMAP_UV "+n.clearcoatRoughnessMapUv:"",n.iridescenceMapUv?"#define IRIDESCENCEMAP_UV "+n.iridescenceMapUv:"",n.iridescenceThicknessMapUv?"#define IRIDESCENCE_THICKNESSMAP_UV "+n.iridescenceThicknessMapUv:"",n.sheenColorMapUv?"#define SHEEN_COLORMAP_UV "+n.sheenColorMapUv:"",n.sheenRoughnessMapUv?"#define SHEEN_ROUGHNESSMAP_UV "+n.sheenRoughnessMapUv:"",n.specularMapUv?"#define SPECULARMAP_UV "+n.specularMapUv:"",n.specularColorMapUv?"#define SPECULAR_COLORMAP_UV "+n.specularColorMapUv:"",n.specularIntensityMapUv?"#define SPECULAR_INTENSITYMAP_UV "+n.specularIntensityMapUv:"",n.transmissionMapUv?"#define TRANSMISSIONMAP_UV "+n.transmissionMapUv:"",n.thicknessMapUv?"#define THICKNESSMAP_UV "+n.thicknessMapUv:"",n.vertexTangents?"#define USE_TANGENT":"",n.vertexColors?"#define USE_COLOR":"",n.vertexAlphas?"#define USE_COLOR_ALPHA":"",n.vertexUv1s?"#define USE_UV1":"",n.vertexUv2s?"#define USE_UV2":"",n.vertexUv3s?"#define USE_UV3":"",n.pointsUvs?"#define USE_POINTS_UV":"",n.flatShading?"#define FLAT_SHADED":"",n.skinning?"#define USE_SKINNING":"",n.morphTargets?"#define USE_MORPHTARGETS":"",n.morphNormals&&!1===n.flatShading?"#define USE_MORPHNORMALS":"",n.morphColors&&n.isWebGL2?"#define USE_MORPHCOLORS":"",n.morphTargetsCount>0&&n.isWebGL2?"#define MORPHTARGETS_TEXTURE":"",n.morphTargetsCount>0&&n.isWebGL2?"#define MORPHTARGETS_TEXTURE_STRIDE "+n.morphTextureStride:"",n.morphTargetsCount>0&&n.isWebGL2?"#define MORPHTARGETS_COUNT "+n.morphTargetsCount:"",n.doubleSided?"#define DOUBLE_SIDED":"",n.flipSided?"#define FLIP_SIDED":"",n.shadowMapEnabled?"#define USE_SHADOWMAP":"",n.shadowMapEnabled?"#define "+l:"",n.sizeAttenuation?"#define USE_SIZEATTENUATION":"",n.logarithmicDepthBuffer?"#define USE_LOGDEPTHBUF":"",n.logarithmicDepthBuffer&&n.rendererExtensionFragDepth?"#define USE_LOGDEPTHBUF_EXT":"","uniform mat4 modelMatrix;","uniform mat4 modelViewMatrix;","uniform mat4 projectionMatrix;","uniform mat4 viewMatrix;","uniform mat3 normalMatrix;","uniform vec3 cameraPosition;","uniform bool isOrthographic;","#ifdef USE_INSTANCING","\tattribute mat4 instanceMatrix;","#endif","#ifdef USE_INSTANCING_COLOR","\tattribute vec3 instanceColor;","#endif","attribute vec3 position;","attribute vec3 normal;","attribute vec2 uv;","#ifdef USE_UV1","\tattribute vec2 uv1;","#endif","#ifdef USE_UV2","\tattribute vec2 uv2;","#endif","#ifdef USE_UV3","\tattribute vec2 uv3;","#endif","#ifdef USE_TANGENT","\tattribute vec4 tangent;","#endif","#if defined( USE_COLOR_ALPHA )","\tattribute vec4 color;","#elif defined( USE_COLOR )","\tattribute vec3 color;","#endif","#if ( defined( USE_MORPHTARGETS ) && ! defined( MORPHTARGETS_TEXTURE ) )","\tattribute vec3 morphTarget0;","\tattribute vec3 morphTarget1;","\tattribute vec3 morphTarget2;","\tattribute vec3 morphTarget3;","\t#ifdef USE_MORPHNORMALS","\t\tattribute vec3 morphNormal0;","\t\tattribute vec3 morphNormal1;","\t\tattribute vec3 morphNormal2;","\t\tattribute vec3 morphNormal3;","\t#else","\t\tattribute vec3 morphTarget4;","\t\tattribute vec3 morphTarget5;","\t\tattribute vec3 morphTarget6;","\t\tattribute vec3 morphTarget7;","\t#endif","#endif","#ifdef USE_SKINNING","\tattribute vec4 skinIndex;","\tattribute vec4 skinWeight;","#endif","\n"].filter(Vo).join("\n"),v=[p,Ko(n),"#define SHADER_NAME "+n.shaderName,m,n.useFog&&n.fog?"#define USE_FOG":"",n.useFog&&n.fogExp2?"#define FOG_EXP2":"",n.map?"#define USE_MAP":"",n.matcap?"#define USE_MATCAP":"",n.envMap?"#define USE_ENVMAP":"",n.envMap?"#define "+c:"",n.envMap?"#define "+h:"",n.envMap?"#define "+u:"",d?"#define CUBEUV_TEXEL_WIDTH "+d.texelWidth:"",d?"#define CUBEUV_TEXEL_HEIGHT "+d.texelHeight:"",d?"#define CUBEUV_MAX_MIP "+d.maxMip+".0":"",n.lightMap?"#define USE_LIGHTMAP":"",n.aoMap?"#define USE_AOMAP":"",n.bumpMap?"#define USE_BUMPMAP":"",n.normalMap?"#define USE_NORMALMAP":"",n.normalMapObjectSpace?"#define USE_NORMALMAP_OBJECTSPACE":"",n.normalMapTangentSpace?"#define USE_NORMALMAP_TANGENTSPACE":"",n.emissiveMap?"#define USE_EMISSIVEMAP":"",n.anisotropy?"#define USE_ANISOTROPY":"",n.anisotropyMap?"#define USE_ANISOTROPYMAP":"",n.clearcoat?"#define USE_CLEARCOAT":"",n.clearcoatMap?"#define USE_CLEARCOATMAP":"",n.clearcoatRoughnessMap?"#define USE_CLEARCOAT_ROUGHNESSMAP":"",n.clearcoatNormalMap?"#define USE_CLEARCOAT_NORMALMAP":"",n.iridescence?"#define USE_IRIDESCENCE":"",n.iridescenceMap?"#define USE_IRIDESCENCEMAP":"",n.iridescenceThicknessMap?"#define USE_IRIDESCENCE_THICKNESSMAP":"",n.specularMap?"#define USE_SPECULARMAP":"",n.specularColorMap?"#define USE_SPECULAR_COLORMAP":"",n.specularIntensityMap?"#define USE_SPECULAR_INTENSITYMAP":"",n.roughnessMap?"#define USE_ROUGHNESSMAP":"",n.metalnessMap?"#define USE_METALNESSMAP":"",n.alphaMap?"#define USE_ALPHAMAP":"",n.alphaTest?"#define USE_ALPHATEST":"",n.sheen?"#define USE_SHEEN":"",n.sheenColorMap?"#define USE_SHEEN_COLORMAP":"",n.sheenRoughnessMap?"#define USE_SHEEN_ROUGHNESSMAP":"",n.transmission?"#define USE_TRANSMISSION":"",n.transmissionMap?"#define USE_TRANSMISSIONMAP":"",n.thicknessMap?"#define USE_THICKNESSMAP":"",n.vertexTangents?"#define USE_TANGENT":"",n.vertexColors||n.instancingColor?"#define USE_COLOR":"",n.vertexAlphas?"#define USE_COLOR_ALPHA":"",n.vertexUv1s?"#define USE_UV1":"",n.vertexUv2s?"#define USE_UV2":"",n.vertexUv3s?"#define USE_UV3":"",n.pointsUvs?"#define USE_POINTS_UV":"",n.gradientMap?"#define USE_GRADIENTMAP":"",n.flatShading?"#define FLAT_SHADED":"",n.doubleSided?"#define DOUBLE_SIDED":"",n.flipSided?"#define FLIP_SIDED":"",n.shadowMapEnabled?"#define USE_SHADOWMAP":"",n.shadowMapEnabled?"#define "+l:"",n.premultipliedAlpha?"#define PREMULTIPLIED_ALPHA":"",n.useLegacyLights?"#define LEGACY_LIGHTS":"",n.logarithmicDepthBuffer?"#define USE_LOGDEPTHBUF":"",n.logarithmicDepthBuffer&&n.rendererExtensionFragDepth?"#define USE_LOGDEPTHBUF_EXT":"","uniform mat4 viewMatrix;","uniform vec3 cameraPosition;","uniform bool isOrthographic;",0!==n.toneMapping?"#define TONE_MAPPING":"",0!==n.toneMapping?$s.tonemapping_pars_fragment:"",0!==n.toneMapping?ko("toneMapping",n.toneMapping):"",n.dithering?"#define DITHERING":"",n.opaque?"#define OPAQUE":"",$s.encodings_pars_fragment,Ho("linearToOutputTexel",n.outputColorSpace),n.useDepthPacking?"#define DEPTH_PACKING "+n.depthPacking:"","\n"].filter(Vo).join("\n")),a=jo(a),a=Go(a,n),a=Wo(a,n),o=jo(o),o=Go(o,n),o=Wo(o,n),a=Zo(a),o=Zo(o),n.isWebGL2&&!0!==n.isRawShaderMaterial&&(_="#version 300 es\n",g=["precision mediump sampler2DArray;","#define attribute in","#define varying out","#define texture2D texture"].join("\n")+"\n"+g,v=["#define varying in",n.glslVersion===Sn?"":"layout(location = 0) out highp vec4 pc_fragColor;",n.glslVersion===Sn?"":"#define gl_FragColor pc_fragColor","#define gl_FragDepthEXT gl_FragDepth","#define texture2D texture","#define textureCube texture","#define texture2DProj textureProj","#define texture2DLodEXT textureLod","#define texture2DProjLodEXT textureProjLod","#define textureCubeLodEXT textureLod","#define texture2DGradEXT textureGrad","#define texture2DProjGradEXT textureProjGrad","#define textureCubeGradEXT textureGrad"].join("\n")+"\n"+v);const y=_+g+a,x=_+v+o,M=Fo(r,r.VERTEX_SHADER,y),S=Fo(r,r.FRAGMENT_SHADER,x);if(r.attachShader(f,M),r.attachShader(f,S),void 0!==n.index0AttributeName?r.bindAttribLocation(f,0,n.index0AttributeName):!0===n.morphTargets&&r.bindAttribLocation(f,0,"position"),r.linkProgram(f),t.debug.checkShaderErrors){const e=r.getProgramInfoLog(f).trim(),n=r.getShaderInfoLog(M).trim(),i=r.getShaderInfoLog(S).trim();let s=!0,a=!0;if(!1===r.getProgramParameter(f,r.LINK_STATUS))if(s=!1,"function"==typeof t.debug.onShaderError)t.debug.onShaderError(r,f,M,S);else{const t=zo(r,M,"vertex"),n=zo(r,S,"fragment");console.error("THREE.WebGLProgram: Shader Error "+r.getError()+" - VALIDATE_STATUS "+r.getProgramParameter(f,r.VALIDATE_STATUS)+"\n\nProgram Info Log: "+e+"\n"+t+"\n"+n)}else""!==e?console.warn("THREE.WebGLProgram: Program Info Log:",e):""!==n&&""!==i||(a=!1);a&&(this.diagnostics={runnable:s,programLog:e,vertexShader:{log:n,prefix:g},fragmentShader:{log:i,prefix:v}})}let b,E;return r.deleteShader(M),r.deleteShader(S),this.getUniforms=function(){return void 0===b&&(b=new Oo(r,f)),b},this.getAttributes=function(){return void 0===E&&(E=function(t,e){const n={},i=t.getProgramParameter(e,t.ACTIVE_ATTRIBUTES);for(let r=0;r0,X=s.clearcoat>0,j=s.iridescence>0,q=s.sheen>0,Y=s.transmission>0,Z=W&&!!s.anisotropyMap,J=X&&!!s.clearcoatMap,K=X&&!!s.clearcoatNormalMap,$=X&&!!s.clearcoatRoughnessMap,Q=j&&!!s.iridescenceMap,tt=j&&!!s.iridescenceThicknessMap,et=q&&!!s.sheenColorMap,nt=q&&!!s.sheenRoughnessMap,it=!!s.specularMap,rt=!!s.specularColorMap,at=!!s.specularIntensityMap,ot=Y&&!!s.transmissionMap,lt=Y&&!!s.thicknessMap,ct=!!s.gradientMap,ht=!!s.alphaMap,ut=s.alphaTest>0,dt=!!s.extensions,pt=!!y.attributes.uv1,mt=!!y.attributes.uv2,ft=!!y.attributes.uv3;return{isWebGL2:h,shaderID:b,shaderName:s.type,vertexShader:w,fragmentShader:A,defines:s.defines,customVertexShaderID:R,customFragmentShaderID:C,isRawShaderMaterial:!0===s.isRawShaderMaterial,glslVersion:s.glslVersion,precision:p,instancing:I,instancingColor:I&&null!==v.instanceColor,supportsVertexTextures:d,outputColorSpace:null===L?t.outputColorSpace:!0===L.isXRRenderTarget?L.texture.colorSpace:ze,map:U,matcap:N,envMap:D,envMapMode:D&&M.mapping,envMapCubeUVHeight:S,aoMap:O,lightMap:F,bumpMap:B,normalMap:z,displacementMap:d&&H,emissiveMap:k,normalMapObjectSpace:z&&1===s.normalMapType,normalMapTangentSpace:z&&0===s.normalMapType,metalnessMap:V,roughnessMap:G,anisotropy:W,anisotropyMap:Z,clearcoat:X,clearcoatMap:J,clearcoatNormalMap:K,clearcoatRoughnessMap:$,iridescence:j,iridescenceMap:Q,iridescenceThicknessMap:tt,sheen:q,sheenColorMap:et,sheenRoughnessMap:nt,specularMap:it,specularColorMap:rt,specularIntensityMap:at,transmission:Y,transmissionMap:ot,thicknessMap:lt,gradientMap:ct,opaque:!1===s.transparent&&1===s.blending,alphaMap:ht,alphaTest:ut,combine:s.combine,mapUv:U&&f(s.map.channel),aoMapUv:O&&f(s.aoMap.channel),lightMapUv:F&&f(s.lightMap.channel),bumpMapUv:B&&f(s.bumpMap.channel),normalMapUv:z&&f(s.normalMap.channel),displacementMapUv:H&&f(s.displacementMap.channel),emissiveMapUv:k&&f(s.emissiveMap.channel),metalnessMapUv:V&&f(s.metalnessMap.channel),roughnessMapUv:G&&f(s.roughnessMap.channel),anisotropyMapUv:Z&&f(s.anisotropyMap.channel),clearcoatMapUv:J&&f(s.clearcoatMap.channel),clearcoatNormalMapUv:K&&f(s.clearcoatNormalMap.channel),clearcoatRoughnessMapUv:$&&f(s.clearcoatRoughnessMap.channel),iridescenceMapUv:Q&&f(s.iridescenceMap.channel),iridescenceThicknessMapUv:tt&&f(s.iridescenceThicknessMap.channel),sheenColorMapUv:et&&f(s.sheenColorMap.channel),sheenRoughnessMapUv:nt&&f(s.sheenRoughnessMap.channel),specularMapUv:it&&f(s.specularMap.channel),specularColorMapUv:rt&&f(s.specularColorMap.channel),specularIntensityMapUv:at&&f(s.specularIntensityMap.channel),transmissionMapUv:ot&&f(s.transmissionMap.channel),thicknessMapUv:lt&&f(s.thicknessMap.channel),alphaMapUv:ht&&f(s.alphaMap.channel),vertexTangents:!!y.attributes.tangent&&(z||W),vertexColors:s.vertexColors,vertexAlphas:!0===s.vertexColors&&!!y.attributes.color&&4===y.attributes.color.itemSize,vertexUv1s:pt,vertexUv2s:mt,vertexUv3s:ft,pointsUvs:!0===v.isPoints&&!!y.attributes.uv&&(U||ht),fog:!!_,useFog:!0===s.fog,fogExp2:_&&_.isFogExp2,flatShading:!0===s.flatShading,sizeAttenuation:!0===s.sizeAttenuation,logarithmicDepthBuffer:u,skinning:!0===v.isSkinnedMesh,morphTargets:void 0!==y.morphAttributes.position,morphNormals:void 0!==y.morphAttributes.normal,morphColors:void 0!==y.morphAttributes.color,morphTargetsCount:T,morphTextureStride:P,numDirLights:o.directional.length,numPointLights:o.point.length,numSpotLights:o.spot.length,numSpotLightMaps:o.spotLightMap.length,numRectAreaLights:o.rectArea.length,numHemiLights:o.hemi.length,numDirLightShadows:o.directionalShadowMap.length,numPointLightShadows:o.pointShadowMap.length,numSpotLightShadows:o.spotShadowMap.length,numSpotLightShadowsWithMaps:o.numSpotLightShadowsWithMaps,numClippingPlanes:a.numPlanes,numClipIntersection:a.numIntersection,dithering:s.dithering,shadowMapEnabled:t.shadowMap.enabled&&c.length>0,shadowMapType:t.shadowMap.type,toneMapping:s.toneMapped?t.toneMapping:0,useLegacyLights:t.useLegacyLights,premultipliedAlpha:s.premultipliedAlpha,doubleSided:2===s.side,flipSided:1===s.side,useDepthPacking:s.depthPacking>=0,depthPacking:s.depthPacking||0,index0AttributeName:s.index0AttributeName,extensionDerivatives:dt&&!0===s.extensions.derivatives,extensionFragDepth:dt&&!0===s.extensions.fragDepth,extensionDrawBuffers:dt&&!0===s.extensions.drawBuffers,extensionShaderTextureLOD:dt&&!0===s.extensions.shaderTextureLOD,rendererExtensionFragDepth:h||i.has("EXT_frag_depth"),rendererExtensionDrawBuffers:h||i.has("WEBGL_draw_buffers"),rendererExtensionShaderTextureLod:h||i.has("EXT_shader_texture_lod"),customProgramCacheKey:s.customProgramCacheKey()}},getProgramCacheKey:function(e){const n=[];if(e.shaderID?n.push(e.shaderID):(n.push(e.customVertexShaderID),n.push(e.customFragmentShaderID)),void 0!==e.defines)for(const t in e.defines)n.push(t),n.push(e.defines[t]);return!1===e.isRawShaderMaterial&&(!function(t,e){t.push(e.precision),t.push(e.outputColorSpace),t.push(e.envMapMode),t.push(e.envMapCubeUVHeight),t.push(e.mapUv),t.push(e.alphaMapUv),t.push(e.lightMapUv),t.push(e.aoMapUv),t.push(e.bumpMapUv),t.push(e.normalMapUv),t.push(e.displacementMapUv),t.push(e.emissiveMapUv),t.push(e.metalnessMapUv),t.push(e.roughnessMapUv),t.push(e.anisotropyMapUv),t.push(e.clearcoatMapUv),t.push(e.clearcoatNormalMapUv),t.push(e.clearcoatRoughnessMapUv),t.push(e.iridescenceMapUv),t.push(e.iridescenceThicknessMapUv),t.push(e.sheenColorMapUv),t.push(e.sheenRoughnessMapUv),t.push(e.specularMapUv),t.push(e.specularColorMapUv),t.push(e.specularIntensityMapUv),t.push(e.transmissionMapUv),t.push(e.thicknessMapUv),t.push(e.combine),t.push(e.fogExp2),t.push(e.sizeAttenuation),t.push(e.morphTargetsCount),t.push(e.morphAttributeCount),t.push(e.numDirLights),t.push(e.numPointLights),t.push(e.numSpotLights),t.push(e.numSpotLightMaps),t.push(e.numHemiLights),t.push(e.numRectAreaLights),t.push(e.numDirLightShadows),t.push(e.numPointLightShadows),t.push(e.numSpotLightShadows),t.push(e.numSpotLightShadowsWithMaps),t.push(e.shadowMapType),t.push(e.toneMapping),t.push(e.numClippingPlanes),t.push(e.numClipIntersection),t.push(e.depthPacking)}(n,e),function(t,e){o.disableAll(),e.isWebGL2&&o.enable(0);e.supportsVertexTextures&&o.enable(1);e.instancing&&o.enable(2);e.instancingColor&&o.enable(3);e.matcap&&o.enable(4);e.envMap&&o.enable(5);e.normalMapObjectSpace&&o.enable(6);e.normalMapTangentSpace&&o.enable(7);e.clearcoat&&o.enable(8);e.iridescence&&o.enable(9);e.alphaTest&&o.enable(10);e.vertexColors&&o.enable(11);e.vertexAlphas&&o.enable(12);e.vertexUv1s&&o.enable(13);e.vertexUv2s&&o.enable(14);e.vertexUv3s&&o.enable(15);e.vertexTangents&&o.enable(16);e.anisotropy&&o.enable(17);t.push(o.mask),o.disableAll(),e.fog&&o.enable(0);e.useFog&&o.enable(1);e.flatShading&&o.enable(2);e.logarithmicDepthBuffer&&o.enable(3);e.skinning&&o.enable(4);e.morphTargets&&o.enable(5);e.morphNormals&&o.enable(6);e.morphColors&&o.enable(7);e.premultipliedAlpha&&o.enable(8);e.shadowMapEnabled&&o.enable(9);e.useLegacyLights&&o.enable(10);e.doubleSided&&o.enable(11);e.flipSided&&o.enable(12);e.useDepthPacking&&o.enable(13);e.dithering&&o.enable(14);e.transmission&&o.enable(15);e.sheen&&o.enable(16);e.opaque&&o.enable(17);e.pointsUvs&&o.enable(18);t.push(o.mask)}(n,e),n.push(t.outputColorSpace)),n.push(e.customProgramCacheKey),n.join()},getUniforms:function(t){const e=m[t.type];let n;if(e){const t=ta[e];n=Ns.clone(t.uniforms)}else n=t.uniforms;return n},acquireProgram:function(e,n){let i;for(let t=0,e=c.length;t0?i.push(h):!0===a.transparent?r.push(h):n.push(h)},unshift:function(t,e,a,o,l,c){const h=s(t,e,a,o,l,c);a.transmission>0?i.unshift(h):!0===a.transparent?r.unshift(h):n.unshift(h)},finish:function(){for(let n=e,i=t.length;n1&&n.sort(t||rl),i.length>1&&i.sort(e||sl),r.length>1&&r.sort(e||sl)}}}function ol(){let t=new WeakMap;return{get:function(e,n){const i=t.get(e);let r;return void 0===i?(r=new al,t.set(e,[r])):n>=i.length?(r=new al,i.push(r)):r=i[n],r},dispose:function(){t=new WeakMap}}}function ll(){const t={};return{get:function(e){if(void 0!==t[e.id])return t[e.id];let n;switch(e.type){case"DirectionalLight":n={direction:new gi,color:new Or};break;case"SpotLight":n={position:new gi,direction:new gi,color:new Or,distance:0,coneCos:0,penumbraCos:0,decay:0};break;case"PointLight":n={position:new gi,color:new Or,distance:0,decay:0};break;case"HemisphereLight":n={direction:new gi,skyColor:new Or,groundColor:new Or};break;case"RectAreaLight":n={color:new Or,position:new gi,halfWidth:new gi,halfHeight:new gi}}return t[e.id]=n,n}}}let cl=0;function hl(t,e){return(e.castShadow?2:0)-(t.castShadow?2:0)+(e.map?1:0)-(t.map?1:0)}function ul(t,e){const n=new ll,i=function(){const t={};return{get:function(e){if(void 0!==t[e.id])return t[e.id];let n;switch(e.type){case"DirectionalLight":case"SpotLight":n={shadowBias:0,shadowNormalBias:0,shadowRadius:1,shadowMapSize:new zn};break;case"PointLight":n={shadowBias:0,shadowNormalBias:0,shadowRadius:1,shadowMapSize:new zn,shadowCameraNear:1,shadowCameraFar:1e3}}return t[e.id]=n,n}}}(),r={version:0,hash:{directionalLength:-1,pointLength:-1,spotLength:-1,rectAreaLength:-1,hemiLength:-1,numDirectionalShadows:-1,numPointShadows:-1,numSpotShadows:-1,numSpotMaps:-1},ambient:[0,0,0],probe:[],directional:[],directionalShadow:[],directionalShadowMap:[],directionalShadowMatrix:[],spot:[],spotLightMap:[],spotShadow:[],spotShadowMap:[],spotLightMatrix:[],rectArea:[],rectAreaLTC1:null,rectAreaLTC2:null,point:[],pointShadow:[],pointShadowMap:[],pointShadowMatrix:[],hemi:[],numSpotLightShadowsWithMaps:0};for(let t=0;t<9;t++)r.probe.push(new gi);const s=new gi,a=new ji,o=new ji;return{setup:function(s,a){let o=0,l=0,c=0;for(let t=0;t<9;t++)r.probe[t].set(0,0,0);let h=0,u=0,d=0,p=0,m=0,f=0,g=0,v=0,_=0,y=0;s.sort(hl);const x=!0===a?Math.PI:1;for(let t=0,e=s.length;t0&&(e.isWebGL2||!0===t.has("OES_texture_float_linear")?(r.rectAreaLTC1=Qs.LTC_FLOAT_1,r.rectAreaLTC2=Qs.LTC_FLOAT_2):!0===t.has("OES_texture_half_float_linear")?(r.rectAreaLTC1=Qs.LTC_HALF_1,r.rectAreaLTC2=Qs.LTC_HALF_2):console.error("THREE.WebGLRenderer: Unable to use RectAreaLight. Missing WebGL extensions.")),r.ambient[0]=o,r.ambient[1]=l,r.ambient[2]=c;const M=r.hash;M.directionalLength===h&&M.pointLength===u&&M.spotLength===d&&M.rectAreaLength===p&&M.hemiLength===m&&M.numDirectionalShadows===f&&M.numPointShadows===g&&M.numSpotShadows===v&&M.numSpotMaps===_||(r.directional.length=h,r.spot.length=d,r.rectArea.length=p,r.point.length=u,r.hemi.length=m,r.directionalShadow.length=f,r.directionalShadowMap.length=f,r.pointShadow.length=g,r.pointShadowMap.length=g,r.spotShadow.length=v,r.spotShadowMap.length=v,r.directionalShadowMatrix.length=f,r.pointShadowMatrix.length=g,r.spotLightMatrix.length=v+_-y,r.spotLightMap.length=_,r.numSpotLightShadowsWithMaps=y,M.directionalLength=h,M.pointLength=u,M.spotLength=d,M.rectAreaLength=p,M.hemiLength=m,M.numDirectionalShadows=f,M.numPointShadows=g,M.numSpotShadows=v,M.numSpotMaps=_,r.version=cl++)},setupView:function(t,e){let n=0,i=0,l=0,c=0,h=0;const u=e.matrixWorldInverse;for(let e=0,d=t.length;e=s.length?(a=new dl(t,e),s.push(a)):a=s[r],a},dispose:function(){n=new WeakMap}}}class ml extends Lr{constructor(t){super(),this.isMeshDepthMaterial=!0,this.type="MeshDepthMaterial",this.depthPacking=3200,this.map=null,this.alphaMap=null,this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.wireframe=!1,this.wireframeLinewidth=1,this.setValues(t)}copy(t){return super.copy(t),this.depthPacking=t.depthPacking,this.map=t.map,this.alphaMap=t.alphaMap,this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this}}class fl extends Lr{constructor(t){super(),this.isMeshDistanceMaterial=!0,this.type="MeshDistanceMaterial",this.map=null,this.alphaMap=null,this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.setValues(t)}copy(t){return super.copy(t),this.map=t.map,this.alphaMap=t.alphaMap,this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this}}function gl(t,e,n){let i=new Ys;const r=new zn,s=new zn,a=new li,o=new ml({depthPacking:3201}),l=new fl,c={},h=n.maxTextureSize,u={0:1,1:0,2:2},d=new Ds({defines:{VSM_SAMPLES:8},uniforms:{shadow_pass:{value:null},resolution:{value:new zn},radius:{value:4}},vertexShader:"void main() {\n\tgl_Position = vec4( position, 1.0 );\n}",fragmentShader:"uniform sampler2D shadow_pass;\nuniform vec2 resolution;\nuniform float radius;\n#include \nvoid main() {\n\tconst float samples = float( VSM_SAMPLES );\n\tfloat mean = 0.0;\n\tfloat squared_mean = 0.0;\n\tfloat uvStride = samples <= 1.0 ? 0.0 : 2.0 / ( samples - 1.0 );\n\tfloat uvStart = samples <= 1.0 ? 0.0 : - 1.0;\n\tfor ( float i = 0.0; i < samples; i ++ ) {\n\t\tfloat uvOffset = uvStart + i * uvStride;\n\t\t#ifdef HORIZONTAL_PASS\n\t\t\tvec2 distribution = unpackRGBATo2Half( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( uvOffset, 0.0 ) * radius ) / resolution ) );\n\t\t\tmean += distribution.x;\n\t\t\tsquared_mean += distribution.y * distribution.y + distribution.x * distribution.x;\n\t\t#else\n\t\t\tfloat depth = unpackRGBAToDepth( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( 0.0, uvOffset ) * radius ) / resolution ) );\n\t\t\tmean += depth;\n\t\t\tsquared_mean += depth * depth;\n\t\t#endif\n\t}\n\tmean = mean / samples;\n\tsquared_mean = squared_mean / samples;\n\tfloat std_dev = sqrt( squared_mean - mean * mean );\n\tgl_FragColor = pack2HalfToRGBA( vec2( mean, std_dev ) );\n}"}),p=d.clone();p.defines.HORIZONTAL_PASS=1;const m=new hs;m.setAttribute("position",new jr(new Float32Array([-1,-1,.5,3,-1,.5,-1,3,.5]),3));const f=new Rs(m,d),g=this;this.enabled=!1,this.autoUpdate=!0,this.needsUpdate=!1,this.type=1;let v=this.type;function _(n,i){const s=e.update(f);d.defines.VSM_SAMPLES!==n.blurSamples&&(d.defines.VSM_SAMPLES=n.blurSamples,p.defines.VSM_SAMPLES=n.blurSamples,d.needsUpdate=!0,p.needsUpdate=!0),null===n.mapPass&&(n.mapPass=new ci(r.x,r.y)),d.uniforms.shadow_pass.value=n.map.texture,d.uniforms.resolution.value=n.mapSize,d.uniforms.radius.value=n.radius,t.setRenderTarget(n.mapPass),t.clear(),t.renderBufferDirect(i,null,s,d,f,null),p.uniforms.shadow_pass.value=n.mapPass.texture,p.uniforms.resolution.value=n.mapSize,p.uniforms.radius.value=n.radius,t.setRenderTarget(n.map),t.clear(),t.renderBufferDirect(i,null,s,p,f,null)}function y(e,n,i,r){let s=null;const a=!0===i.isPointLight?e.customDistanceMaterial:e.customDepthMaterial;if(void 0!==a)s=a;else if(s=!0===i.isPointLight?l:o,t.localClippingEnabled&&!0===n.clipShadows&&Array.isArray(n.clippingPlanes)&&0!==n.clippingPlanes.length||n.displacementMap&&0!==n.displacementScale||n.alphaMap&&n.alphaTest>0||n.map&&n.alphaTest>0){const t=s.uuid,e=n.uuid;let i=c[t];void 0===i&&(i={},c[t]=i);let r=i[e];void 0===r&&(r=s.clone(),i[e]=r),s=r}if(s.visible=n.visible,s.wireframe=n.wireframe,s.side=3===r?null!==n.shadowSide?n.shadowSide:n.side:null!==n.shadowSide?n.shadowSide:u[n.side],s.alphaMap=n.alphaMap,s.alphaTest=n.alphaTest,s.map=n.map,s.clipShadows=n.clipShadows,s.clippingPlanes=n.clippingPlanes,s.clipIntersection=n.clipIntersection,s.displacementMap=n.displacementMap,s.displacementScale=n.displacementScale,s.displacementBias=n.displacementBias,s.wireframeLinewidth=n.wireframeLinewidth,s.linewidth=n.linewidth,!0===i.isPointLight&&!0===s.isMeshDistanceMaterial){t.properties.get(s).light=i}return s}function x(n,r,s,a,o){if(!1===n.visible)return;if(n.layers.test(r.layers)&&(n.isMesh||n.isLine||n.isPoints)&&(n.castShadow||n.receiveShadow&&3===o)&&(!n.frustumCulled||i.intersectsObject(n))){n.modelViewMatrix.multiplyMatrices(s.matrixWorldInverse,n.matrixWorld);const i=e.update(n),r=n.material;if(Array.isArray(r)){const e=i.groups;for(let l=0,c=e.length;lh||r.y>h)&&(r.x>h&&(s.x=Math.floor(h/f.x),r.x=s.x*f.x,u.mapSize.x=s.x),r.y>h&&(s.y=Math.floor(h/f.y),r.y=s.y*f.y,u.mapSize.y=s.y)),null===u.map||!0===p||!0===m){const t=3!==this.type?{minFilter:ct,magFilter:ct}:{};null!==u.map&&u.map.dispose(),u.map=new ci(r.x,r.y,t),u.map.texture.name=c.name+".shadowMap",u.camera.updateProjectionMatrix()}t.setRenderTarget(u.map),t.clear();const g=u.getViewportCount();for(let t=0;t=1):-1!==I.indexOf("OpenGL ES")&&(L=parseFloat(/^OpenGL ES (\d)/.exec(I)[1]),P=L>=2);let U=null,N={};const D=t.getParameter(t.SCISSOR_BOX),O=t.getParameter(t.VIEWPORT),F=(new li).fromArray(D),B=(new li).fromArray(O);function z(e,n,r,s){const a=new Uint8Array(4),o=t.createTexture();t.bindTexture(e,o),t.texParameteri(e,t.TEXTURE_MIN_FILTER,t.NEAREST),t.texParameteri(e,t.TEXTURE_MAG_FILTER,t.NEAREST);for(let o=0;oi||t.height>i)&&(r=i/Math.max(t.width,t.height)),r<1||!0===e){if("undefined"!=typeof HTMLImageElement&&t instanceof HTMLImageElement||"undefined"!=typeof HTMLCanvasElement&&t instanceof HTMLCanvasElement||"undefined"!=typeof ImageBitmap&&t instanceof ImageBitmap){const i=e?Dn:Math.floor,s=i(r*t.width),a=i(r*t.height);void 0===f&&(f=_(s,a));const o=n?_(s,a):f;o.width=s,o.height=a;return o.getContext("2d").drawImage(t,0,0,s,a),console.warn("THREE.WebGLRenderer: Texture has been resized from ("+t.width+"x"+t.height+") to ("+s+"x"+a+")."),o}return"data"in t&&console.warn("THREE.WebGLRenderer: Image in DataTexture is too big ("+t.width+"x"+t.height+")."),t}return t}function x(t){return Un(t.width)&&Un(t.height)}function M(t,e){return t.generateMipmaps&&e&&t.minFilter!==ct&&t.minFilter!==mt}function S(e){t.generateMipmap(e)}function b(n,i,r,s,a=!1){if(!1===o)return i;if(null!==n){if(void 0!==t[n])return t[n];console.warn("THREE.WebGLRenderer: Attempt to use non-existing WebGL internal format '"+n+"'")}let l=i;return i===t.RED&&(r===t.FLOAT&&(l=t.R32F),r===t.HALF_FLOAT&&(l=t.R16F),r===t.UNSIGNED_BYTE&&(l=t.R8)),i===t.RG&&(r===t.FLOAT&&(l=t.RG32F),r===t.HALF_FLOAT&&(l=t.RG16F),r===t.UNSIGNED_BYTE&&(l=t.RG8)),i===t.RGBA&&(r===t.FLOAT&&(l=t.RGBA32F),r===t.HALF_FLOAT&&(l=t.RGBA16F),r===t.UNSIGNED_BYTE&&(l=s===Be&&!1===a?t.SRGB8_ALPHA8:t.RGBA8),r===t.UNSIGNED_SHORT_4_4_4_4&&(l=t.RGBA4),r===t.UNSIGNED_SHORT_5_5_5_1&&(l=t.RGB5_A1)),l!==t.R16F&&l!==t.R32F&&l!==t.RG16F&&l!==t.RG32F&&l!==t.RGBA16F&&l!==t.RGBA32F||e.get("EXT_color_buffer_float"),l}function E(t,e,n){return!0===M(t,n)||t.isFramebufferTexture&&t.minFilter!==ct&&t.minFilter!==mt?Math.log2(Math.max(e.width,e.height))+1:void 0!==t.mipmaps&&t.mipmaps.length>0?t.mipmaps.length:t.isCompressedTexture&&Array.isArray(t.image)?e.mipmaps.length:1}function T(e){return e===ct||e===ht||e===dt?t.NEAREST:t.LINEAR}function w(t){const e=t.target;e.removeEventListener("dispose",w),function(t){const e=i.get(t);if(void 0===e.__webglInit)return;const n=t.source,r=g.get(n);if(r){const i=r[e.__cacheKey];i.usedTimes--,0===i.usedTimes&&R(t),0===Object.keys(r).length&&g.delete(n)}i.remove(t)}(e),e.isVideoTexture&&m.delete(e)}function A(e){const n=e.target;n.removeEventListener("dispose",A),function(e){const n=e.texture,r=i.get(e),s=i.get(n);void 0!==s.__webglTexture&&(t.deleteTexture(s.__webglTexture),a.memory.textures--);e.depthTexture&&e.depthTexture.dispose();if(e.isWebGLCubeRenderTarget)for(let e=0;e<6;e++)t.deleteFramebuffer(r.__webglFramebuffer[e]),r.__webglDepthbuffer&&t.deleteRenderbuffer(r.__webglDepthbuffer[e]);else{if(t.deleteFramebuffer(r.__webglFramebuffer),r.__webglDepthbuffer&&t.deleteRenderbuffer(r.__webglDepthbuffer),r.__webglMultisampledFramebuffer&&t.deleteFramebuffer(r.__webglMultisampledFramebuffer),r.__webglColorRenderbuffer)for(let e=0;e0&&s.__version!==e.version){const t=e.image;if(null===t)console.warn("THREE.WebGLRenderer: Texture marked for update but no image data found.");else{if(!1!==t.complete)return void O(s,e,r);console.warn("THREE.WebGLRenderer: Texture marked for update but image is incomplete")}}n.bindTexture(t.TEXTURE_2D,s.__webglTexture,t.TEXTURE0+r)}const L={[at]:t.REPEAT,[ot]:t.CLAMP_TO_EDGE,[lt]:t.MIRRORED_REPEAT},I={[ct]:t.NEAREST,[ht]:t.NEAREST_MIPMAP_NEAREST,[dt]:t.NEAREST_MIPMAP_LINEAR,[mt]:t.LINEAR,[ft]:t.LINEAR_MIPMAP_NEAREST,[vt]:t.LINEAR_MIPMAP_LINEAR},U={512:t.NEVER,519:t.ALWAYS,513:t.LESS,515:t.LEQUAL,514:t.EQUAL,518:t.GEQUAL,516:t.GREATER,517:t.NOTEQUAL};function N(n,s,a){if(a?(t.texParameteri(n,t.TEXTURE_WRAP_S,L[s.wrapS]),t.texParameteri(n,t.TEXTURE_WRAP_T,L[s.wrapT]),n!==t.TEXTURE_3D&&n!==t.TEXTURE_2D_ARRAY||t.texParameteri(n,t.TEXTURE_WRAP_R,L[s.wrapR]),t.texParameteri(n,t.TEXTURE_MAG_FILTER,I[s.magFilter]),t.texParameteri(n,t.TEXTURE_MIN_FILTER,I[s.minFilter])):(t.texParameteri(n,t.TEXTURE_WRAP_S,t.CLAMP_TO_EDGE),t.texParameteri(n,t.TEXTURE_WRAP_T,t.CLAMP_TO_EDGE),n!==t.TEXTURE_3D&&n!==t.TEXTURE_2D_ARRAY||t.texParameteri(n,t.TEXTURE_WRAP_R,t.CLAMP_TO_EDGE),s.wrapS===ot&&s.wrapT===ot||console.warn("THREE.WebGLRenderer: Texture is not power of two. Texture.wrapS and Texture.wrapT should be set to THREE.ClampToEdgeWrapping."),t.texParameteri(n,t.TEXTURE_MAG_FILTER,T(s.magFilter)),t.texParameteri(n,t.TEXTURE_MIN_FILTER,T(s.minFilter)),s.minFilter!==ct&&s.minFilter!==mt&&console.warn("THREE.WebGLRenderer: Texture is not power of two. Texture.minFilter should be set to THREE.NearestFilter or THREE.LinearFilter.")),s.compareFunction&&(t.texParameteri(n,t.TEXTURE_COMPARE_MODE,t.COMPARE_REF_TO_TEXTURE),t.texParameteri(n,t.TEXTURE_COMPARE_FUNC,U[s.compareFunction])),!0===e.has("EXT_texture_filter_anisotropic")){const a=e.get("EXT_texture_filter_anisotropic");if(s.magFilter===ct)return;if(s.minFilter!==dt&&s.minFilter!==vt)return;if(s.type===Tt&&!1===e.has("OES_texture_float_linear"))return;if(!1===o&&s.type===wt&&!1===e.has("OES_texture_half_float_linear"))return;(s.anisotropy>1||i.get(s).__currentAnisotropy)&&(t.texParameterf(n,a.TEXTURE_MAX_ANISOTROPY_EXT,Math.min(s.anisotropy,r.getMaxAnisotropy())),i.get(s).__currentAnisotropy=s.anisotropy)}}function D(e,n){let i=!1;void 0===e.__webglInit&&(e.__webglInit=!0,n.addEventListener("dispose",w));const r=n.source;let s=g.get(r);void 0===s&&(s={},g.set(r,s));const o=function(t){const e=[];return e.push(t.wrapS),e.push(t.wrapT),e.push(t.wrapR||0),e.push(t.magFilter),e.push(t.minFilter),e.push(t.anisotropy),e.push(t.internalFormat),e.push(t.format),e.push(t.type),e.push(t.generateMipmaps),e.push(t.premultiplyAlpha),e.push(t.flipY),e.push(t.unpackAlignment),e.push(t.colorSpace),e.join()}(n);if(o!==e.__cacheKey){void 0===s[o]&&(s[o]={texture:t.createTexture(),usedTimes:0},a.memory.textures++,i=!0),s[o].usedTimes++;const r=s[e.__cacheKey];void 0!==r&&(s[e.__cacheKey].usedTimes--,0===r.usedTimes&&R(n)),e.__cacheKey=o,e.__webglTexture=s[o].texture}return i}function O(e,r,a){let l=t.TEXTURE_2D;(r.isDataArrayTexture||r.isCompressedArrayTexture)&&(l=t.TEXTURE_2D_ARRAY),r.isData3DTexture&&(l=t.TEXTURE_3D);const c=D(e,r),u=r.source;n.bindTexture(l,e.__webglTexture,t.TEXTURE0+a);const d=i.get(u);if(u.version!==d.__version||!0===c){n.activeTexture(t.TEXTURE0+a),t.pixelStorei(t.UNPACK_FLIP_Y_WEBGL,r.flipY),t.pixelStorei(t.UNPACK_PREMULTIPLY_ALPHA_WEBGL,r.premultiplyAlpha),t.pixelStorei(t.UNPACK_ALIGNMENT,r.unpackAlignment),t.pixelStorei(t.UNPACK_COLORSPACE_CONVERSION_WEBGL,t.NONE);const e=function(t){return!o&&(t.wrapS!==ot||t.wrapT!==ot||t.minFilter!==ct&&t.minFilter!==mt)}(r)&&!1===x(r.image);let i=y(r.image,e,!1,h);i=V(r,i);const p=x(i)||o,m=s.convert(r.format,r.colorSpace);let f,g=s.convert(r.type),v=b(r.internalFormat,m,g,r.colorSpace);N(l,r,p);const _=r.mipmaps,T=o&&!0!==r.isVideoTexture,w=void 0===d.__version||!0===c,A=E(r,i,p);if(r.isDepthTexture)v=t.DEPTH_COMPONENT,o?v=r.type===Tt?t.DEPTH_COMPONENT32F:r.type===Et?t.DEPTH_COMPONENT24:r.type===Ct?t.DEPTH24_STENCIL8:t.DEPTH_COMPONENT16:r.type===Tt&&console.error("WebGLRenderer: Floating point depth texture requires WebGL2."),r.format===Nt&&v===t.DEPTH_COMPONENT&&r.type!==St&&r.type!==Et&&(console.warn("THREE.WebGLRenderer: Use UnsignedShortType or UnsignedIntType for DepthFormat DepthTexture."),r.type=Et,g=s.convert(r.type)),r.format===Dt&&v===t.DEPTH_COMPONENT&&(v=t.DEPTH_STENCIL,r.type!==Ct&&(console.warn("THREE.WebGLRenderer: Use UnsignedInt248Type for DepthStencilFormat DepthTexture."),r.type=Ct,g=s.convert(r.type))),w&&(T?n.texStorage2D(t.TEXTURE_2D,1,v,i.width,i.height):n.texImage2D(t.TEXTURE_2D,0,v,i.width,i.height,0,m,g,null));else if(r.isDataTexture)if(_.length>0&&p){T&&w&&n.texStorage2D(t.TEXTURE_2D,A,v,_[0].width,_[0].height);for(let e=0,i=_.length;e>=1,r>>=1}}else if(_.length>0&&p){T&&w&&n.texStorage2D(t.TEXTURE_2D,A,v,_[0].width,_[0].height);for(let e=0,i=_.length;e=t.TEXTURE_CUBE_MAP_POSITIVE_X&&l<=t.TEXTURE_CUBE_MAP_NEGATIVE_Z)&&t.framebufferTexture2D(t.FRAMEBUFFER,o,l,i.get(a).__webglTexture,0),n.bindFramebuffer(t.FRAMEBUFFER,null)}function B(e,n,i){if(t.bindRenderbuffer(t.RENDERBUFFER,e),n.depthBuffer&&!n.stencilBuffer){let r=t.DEPTH_COMPONENT16;if(i||k(n)){const e=n.depthTexture;e&&e.isDepthTexture&&(e.type===Tt?r=t.DEPTH_COMPONENT32F:e.type===Et&&(r=t.DEPTH_COMPONENT24));const i=H(n);k(n)?d.renderbufferStorageMultisampleEXT(t.RENDERBUFFER,i,r,n.width,n.height):t.renderbufferStorageMultisample(t.RENDERBUFFER,i,r,n.width,n.height)}else t.renderbufferStorage(t.RENDERBUFFER,r,n.width,n.height);t.framebufferRenderbuffer(t.FRAMEBUFFER,t.DEPTH_ATTACHMENT,t.RENDERBUFFER,e)}else if(n.depthBuffer&&n.stencilBuffer){const r=H(n);i&&!1===k(n)?t.renderbufferStorageMultisample(t.RENDERBUFFER,r,t.DEPTH24_STENCIL8,n.width,n.height):k(n)?d.renderbufferStorageMultisampleEXT(t.RENDERBUFFER,r,t.DEPTH24_STENCIL8,n.width,n.height):t.renderbufferStorage(t.RENDERBUFFER,t.DEPTH_STENCIL,n.width,n.height),t.framebufferRenderbuffer(t.FRAMEBUFFER,t.DEPTH_STENCIL_ATTACHMENT,t.RENDERBUFFER,e)}else{const e=!0===n.isWebGLMultipleRenderTargets?n.texture:[n.texture];for(let r=0;r0&&!0===e.has("WEBGL_multisampled_render_to_texture")&&!1!==n.__useRenderToTexture}function V(t,n){const i=t.colorSpace,r=t.format,s=t.type;return!0===t.isCompressedTexture||t.format===bn||i!==ze&&i!==Fe&&(i===Be?!1===o?!0===e.has("EXT_sRGB")&&r===Lt?(t.format=bn,t.minFilter=mt,t.generateMipmaps=!1):n=ni.sRGBToLinear(n):r===Lt&&s===yt||console.warn("THREE.WebGLTextures: sRGB encoded textures have to use RGBAFormat and UnsignedByteType."):console.error("THREE.WebGLTextures: Unsupported texture color space:",i)),n}this.allocateTextureUnit=function(){const t=C;return t>=l&&console.warn("THREE.WebGLTextures: Trying to use "+t+" texture units while this GPU supports only "+l),C+=1,t},this.resetTextureUnits=function(){C=0},this.setTexture2D=P,this.setTexture2DArray=function(e,r){const s=i.get(e);e.version>0&&s.__version!==e.version?O(s,e,r):n.bindTexture(t.TEXTURE_2D_ARRAY,s.__webglTexture,t.TEXTURE0+r)},this.setTexture3D=function(e,r){const s=i.get(e);e.version>0&&s.__version!==e.version?O(s,e,r):n.bindTexture(t.TEXTURE_3D,s.__webglTexture,t.TEXTURE0+r)},this.setTextureCube=function(e,r){const a=i.get(e);e.version>0&&a.__version!==e.version?function(e,r,a){if(6!==r.image.length)return;const l=D(e,r),h=r.source;n.bindTexture(t.TEXTURE_CUBE_MAP,e.__webglTexture,t.TEXTURE0+a);const u=i.get(h);if(h.version!==u.__version||!0===l){n.activeTexture(t.TEXTURE0+a),t.pixelStorei(t.UNPACK_FLIP_Y_WEBGL,r.flipY),t.pixelStorei(t.UNPACK_PREMULTIPLY_ALPHA_WEBGL,r.premultiplyAlpha),t.pixelStorei(t.UNPACK_ALIGNMENT,r.unpackAlignment),t.pixelStorei(t.UNPACK_COLORSPACE_CONVERSION_WEBGL,t.NONE);const e=r.isCompressedTexture||r.image[0].isCompressedTexture,i=r.image[0]&&r.image[0].isDataTexture,d=[];for(let t=0;t<6;t++)d[t]=e||i?i?r.image[t].image:r.image[t]:y(r.image[t],!1,!0,c),d[t]=V(r,d[t]);const p=d[0],m=x(p)||o,f=s.convert(r.format,r.colorSpace),g=s.convert(r.type),v=b(r.internalFormat,f,g,r.colorSpace),_=o&&!0!==r.isVideoTexture,T=void 0===u.__version||!0===l;let w,A=E(r,p,m);if(N(t.TEXTURE_CUBE_MAP,r,m),e){_&&T&&n.texStorage2D(t.TEXTURE_CUBE_MAP,A,v,p.width,p.height);for(let e=0;e<6;e++){w=d[e].mipmaps;for(let i=0;i0&&A++,n.texStorage2D(t.TEXTURE_CUBE_MAP,A,v,d[0].width,d[0].height));for(let e=0;e<6;e++)if(i){_?n.texSubImage2D(t.TEXTURE_CUBE_MAP_POSITIVE_X+e,0,0,0,d[e].width,d[e].height,f,g,d[e].data):n.texImage2D(t.TEXTURE_CUBE_MAP_POSITIVE_X+e,0,v,d[e].width,d[e].height,0,f,g,d[e].data);for(let i=0;i0&&!1===k(e)){const i=d?l:[l];c.__webglMultisampledFramebuffer=t.createFramebuffer(),c.__webglColorRenderbuffer=[],n.bindFramebuffer(t.FRAMEBUFFER,c.__webglMultisampledFramebuffer);for(let n=0;n0&&!1===k(e)){const r=e.isWebGLMultipleRenderTargets?e.texture:[e.texture],s=e.width,a=e.height;let o=t.COLOR_BUFFER_BIT;const l=[],c=e.stencilBuffer?t.DEPTH_STENCIL_ATTACHMENT:t.DEPTH_ATTACHMENT,h=i.get(e),u=!0===e.isWebGLMultipleRenderTargets;if(u)for(let e=0;eo+c?(l.inputState.pinching=!1,this.dispatchEvent({type:"pinchend",handedness:t.handedness,target:this})):!l.inputState.pinching&&a<=o-c&&(l.inputState.pinching=!0,this.dispatchEvent({type:"pinchstart",handedness:t.handedness,target:this}))}else null!==o&&t.gripSpace&&(r=e.getPose(t.gripSpace,n),null!==r&&(o.matrix.fromArray(r.transform.matrix),o.matrix.decompose(o.position,o.rotation,o.scale),o.matrixWorldNeedsUpdate=!0,r.linearVelocity?(o.hasLinearVelocity=!0,o.linearVelocity.copy(r.linearVelocity)):o.hasLinearVelocity=!1,r.angularVelocity?(o.hasAngularVelocity=!0,o.angularVelocity.copy(r.angularVelocity)):o.hasAngularVelocity=!1));null!==a&&(i=e.getPose(t.targetRaySpace,n),null===i&&null!==r&&(i=r),null!==i&&(a.matrix.fromArray(i.transform.matrix),a.matrix.decompose(a.position,a.rotation,a.scale),a.matrixWorldNeedsUpdate=!0,i.linearVelocity?(a.hasLinearVelocity=!0,a.linearVelocity.copy(i.linearVelocity)):a.hasLinearVelocity=!1,i.angularVelocity?(a.hasAngularVelocity=!0,a.angularVelocity.copy(i.angularVelocity)):a.hasAngularVelocity=!1,this.dispatchEvent(Sl)))}return null!==a&&(a.visible=null!==i),null!==o&&(o.visible=null!==r),null!==l&&(l.visible=null!==s),this}_getHandJoint(t,e){if(void 0===t.joints[e.jointName]){const n=new Ml;n.matrixAutoUpdate=!1,n.visible=!1,t.joints[e.jointName]=n,t.add(n)}return t.joints[e.jointName]}}class El extends oi{constructor(t,e,n,i,r,s,a,o,l,c){if((c=void 0!==c?c:Nt)!==Nt&&c!==Dt)throw new Error("DepthTexture format must be either THREE.DepthFormat or THREE.DepthStencilFormat");void 0===n&&c===Nt&&(n=Et),void 0===n&&c===Dt&&(n=Ct),super(null,i,r,s,a,o,c,n,l),this.isDepthTexture=!0,this.image={width:t,height:e},this.magFilter=void 0!==a?a:ct,this.minFilter=void 0!==o?o:ct,this.flipY=!1,this.generateMipmaps=!1,this.compareFunction=null}copy(t){return super.copy(t),this.compareFunction=t.compareFunction,this}toJSON(t){const e=super.toJSON(t);return null!==this.compareFunction&&(e.compareFunction=this.compareFunction),e}}class Tl extends En{constructor(t,e){super();const n=this;let i=null,r=1,s=null,a="local-floor",o=1,l=null,c=null,h=null,u=null,d=null,p=null;const m=e.getContextAttributes();let f=null,g=null;const v=[],_=[],y=new Set,x=new Map;let M=null;const S=new Fs;S.layers.enable(1),S.viewport=new li;const b=new Fs;b.layers.enable(2),b.viewport=new li;const E=[S,b],T=new xl;T.layers.enable(1),T.layers.enable(2);let w=null,A=null;function R(t){const e=_.indexOf(t.inputSource);if(-1===e)return;const n=v[e];void 0!==n&&(n.update(t.inputSource,t.frame,l||s),n.dispatchEvent({type:t.type,data:t.inputSource}))}function C(){i.removeEventListener("select",R),i.removeEventListener("selectstart",R),i.removeEventListener("selectend",R),i.removeEventListener("squeeze",R),i.removeEventListener("squeezestart",R),i.removeEventListener("squeezeend",R),i.removeEventListener("end",C),i.removeEventListener("inputsourceschange",P);for(let t=0;t=0&&(_[i]=null,v[i].disconnect(n))}for(let e=0;e=_.length){_.push(n),i=t;break}if(null===_[t]){_[t]=n,i=t;break}}if(-1===i)break}const r=v[i];r&&r.connect(n)}}this.cameraAutoUpdate=!0,this.enabled=!1,this.isPresenting=!1,this.getCamera=function(){},this.setUserCamera=function(t){M=t},this.getController=function(t){let e=v[t];return void 0===e&&(e=new bl,v[t]=e),e.getTargetRaySpace()},this.getControllerGrip=function(t){let e=v[t];return void 0===e&&(e=new bl,v[t]=e),e.getGripSpace()},this.getHand=function(t){let e=v[t];return void 0===e&&(e=new bl,v[t]=e),e.getHandSpace()},this.setFramebufferScaleFactor=function(t){r=t,!0===n.isPresenting&&console.warn("THREE.WebXRManager: Cannot change framebuffer scale while presenting.")},this.setReferenceSpaceType=function(t){a=t,!0===n.isPresenting&&console.warn("THREE.WebXRManager: Cannot change reference space type while presenting.")},this.getReferenceSpace=function(){return l||s},this.setReferenceSpace=function(t){l=t},this.getBaseLayer=function(){return null!==u?u:d},this.getBinding=function(){return h},this.getFrame=function(){return p},this.getSession=function(){return i},this.setSession=async function(c){if(i=c,null!==i){if(f=t.getRenderTarget(),i.addEventListener("select",R),i.addEventListener("selectstart",R),i.addEventListener("selectend",R),i.addEventListener("squeeze",R),i.addEventListener("squeezestart",R),i.addEventListener("squeezeend",R),i.addEventListener("end",C),i.addEventListener("inputsourceschange",P),!0!==m.xrCompatible&&await e.makeXRCompatible(),void 0===i.renderState.layers||!1===t.capabilities.isWebGL2){const n={antialias:void 0!==i.renderState.layers||m.antialias,alpha:!0,depth:m.depth,stencil:m.stencil,framebufferScaleFactor:r};d=new XRWebGLLayer(i,e,n),i.updateRenderState({baseLayer:d}),g=new ci(d.framebufferWidth,d.framebufferHeight,{format:Lt,type:yt,colorSpace:t.outputColorSpace,stencilBuffer:m.stencil})}else{let n=null,s=null,a=null;m.depth&&(a=m.stencil?e.DEPTH24_STENCIL8:e.DEPTH_COMPONENT24,n=m.stencil?Dt:Nt,s=m.stencil?Ct:Et);const o={colorFormat:e.RGBA8,depthFormat:a,scaleFactor:r};h=new XRWebGLBinding(i,e),u=h.createProjectionLayer(o),i.updateRenderState({layers:[u]}),g=new ci(u.textureWidth,u.textureHeight,{format:Lt,type:yt,depthTexture:new El(u.textureWidth,u.textureHeight,s,void 0,void 0,void 0,void 0,void 0,void 0,n),stencilBuffer:m.stencil,colorSpace:t.outputColorSpace,samples:m.antialias?4:0});t.properties.get(g).__ignoreDepthValues=u.ignoreDepthValues}g.isXRRenderTarget=!0,this.setFoveation(o),l=null,s=await i.requestReferenceSpace(a),D.setContext(i),D.start(),n.isPresenting=!0,n.dispatchEvent({type:"sessionstart"})}},this.getEnvironmentBlendMode=function(){if(null!==i)return i.environmentBlendMode};const L=new gi,I=new gi;function U(t,e){null===e?t.matrixWorld.copy(t.matrix):t.matrixWorld.multiplyMatrices(e.matrixWorld,t.matrix),t.matrixWorldInverse.copy(t.matrixWorld).invert()}this.updateCameraXR=function(t){if(null===i)return t;M&&(t=M),T.near=b.near=S.near=t.near,T.far=b.far=S.far=t.far,w===T.near&&A===T.far||(i.updateRenderState({depthNear:T.near,depthFar:T.far}),w=T.near,A=T.far);const e=t.parent,n=T.cameras;U(T,e);for(let t=0;te&&(x.set(t,t.lastChangedTime),n.dispatchEvent({type:"planechanged",data:t}))}else y.add(t),x.set(t,i.lastChangedTime),n.dispatchEvent({type:"planeadded",data:t})}p=null})),this.setAnimationLoop=function(t){N=t},this.dispose=function(){}}}function wl(t,e){function n(t,e){!0===t.matrixAutoUpdate&&t.updateMatrix(),e.value.copy(t.matrix)}function i(i,r){i.opacity.value=r.opacity,r.color&&i.diffuse.value.copy(r.color),r.emissive&&i.emissive.value.copy(r.emissive).multiplyScalar(r.emissiveIntensity),r.map&&(i.map.value=r.map,n(r.map,i.mapTransform)),r.alphaMap&&(i.alphaMap.value=r.alphaMap,n(r.alphaMap,i.alphaMapTransform)),r.bumpMap&&(i.bumpMap.value=r.bumpMap,n(r.bumpMap,i.bumpMapTransform),i.bumpScale.value=r.bumpScale,1===r.side&&(i.bumpScale.value*=-1)),r.normalMap&&(i.normalMap.value=r.normalMap,n(r.normalMap,i.normalMapTransform),i.normalScale.value.copy(r.normalScale),1===r.side&&i.normalScale.value.negate()),r.displacementMap&&(i.displacementMap.value=r.displacementMap,n(r.displacementMap,i.displacementMapTransform),i.displacementScale.value=r.displacementScale,i.displacementBias.value=r.displacementBias),r.emissiveMap&&(i.emissiveMap.value=r.emissiveMap,n(r.emissiveMap,i.emissiveMapTransform)),r.specularMap&&(i.specularMap.value=r.specularMap,n(r.specularMap,i.specularMapTransform)),r.alphaTest>0&&(i.alphaTest.value=r.alphaTest);const s=e.get(r).envMap;if(s&&(i.envMap.value=s,i.flipEnvMap.value=s.isCubeTexture&&!1===s.isRenderTargetTexture?-1:1,i.reflectivity.value=r.reflectivity,i.ior.value=r.ior,i.refractionRatio.value=r.refractionRatio),r.lightMap){i.lightMap.value=r.lightMap;const e=!0===t.useLegacyLights?Math.PI:1;i.lightMapIntensity.value=r.lightMapIntensity*e,n(r.lightMap,i.lightMapTransform)}r.aoMap&&(i.aoMap.value=r.aoMap,i.aoMapIntensity.value=r.aoMapIntensity,n(r.aoMap,i.aoMapTransform))}return{refreshFogUniforms:function(e,n){n.color.getRGB(e.fogColor.value,Us(t)),n.isFog?(e.fogNear.value=n.near,e.fogFar.value=n.far):n.isFogExp2&&(e.fogDensity.value=n.density)},refreshMaterialUniforms:function(t,r,s,a,o){r.isMeshBasicMaterial||r.isMeshLambertMaterial?i(t,r):r.isMeshToonMaterial?(i(t,r),function(t,e){e.gradientMap&&(t.gradientMap.value=e.gradientMap)}(t,r)):r.isMeshPhongMaterial?(i(t,r),function(t,e){t.specular.value.copy(e.specular),t.shininess.value=Math.max(e.shininess,1e-4)}(t,r)):r.isMeshStandardMaterial?(i(t,r),function(t,i){t.metalness.value=i.metalness,i.metalnessMap&&(t.metalnessMap.value=i.metalnessMap,n(i.metalnessMap,t.metalnessMapTransform));t.roughness.value=i.roughness,i.roughnessMap&&(t.roughnessMap.value=i.roughnessMap,n(i.roughnessMap,t.roughnessMapTransform));const r=e.get(i).envMap;r&&(t.envMapIntensity.value=i.envMapIntensity)}(t,r),r.isMeshPhysicalMaterial&&function(t,e,i){t.ior.value=e.ior,e.sheen>0&&(t.sheenColor.value.copy(e.sheenColor).multiplyScalar(e.sheen),t.sheenRoughness.value=e.sheenRoughness,e.sheenColorMap&&(t.sheenColorMap.value=e.sheenColorMap,n(e.sheenColorMap,t.sheenColorMapTransform)),e.sheenRoughnessMap&&(t.sheenRoughnessMap.value=e.sheenRoughnessMap,n(e.sheenRoughnessMap,t.sheenRoughnessMapTransform)));e.clearcoat>0&&(t.clearcoat.value=e.clearcoat,t.clearcoatRoughness.value=e.clearcoatRoughness,e.clearcoatMap&&(t.clearcoatMap.value=e.clearcoatMap,n(e.clearcoatMap,t.clearcoatMapTransform)),e.clearcoatRoughnessMap&&(t.clearcoatRoughnessMap.value=e.clearcoatRoughnessMap,n(e.clearcoatRoughnessMap,t.clearcoatRoughnessMapTransform)),e.clearcoatNormalMap&&(t.clearcoatNormalMap.value=e.clearcoatNormalMap,n(e.clearcoatNormalMap,t.clearcoatNormalMapTransform),t.clearcoatNormalScale.value.copy(e.clearcoatNormalScale),1===e.side&&t.clearcoatNormalScale.value.negate()));e.iridescence>0&&(t.iridescence.value=e.iridescence,t.iridescenceIOR.value=e.iridescenceIOR,t.iridescenceThicknessMinimum.value=e.iridescenceThicknessRange[0],t.iridescenceThicknessMaximum.value=e.iridescenceThicknessRange[1],e.iridescenceMap&&(t.iridescenceMap.value=e.iridescenceMap,n(e.iridescenceMap,t.iridescenceMapTransform)),e.iridescenceThicknessMap&&(t.iridescenceThicknessMap.value=e.iridescenceThicknessMap,n(e.iridescenceThicknessMap,t.iridescenceThicknessMapTransform)));e.transmission>0&&(t.transmission.value=e.transmission,t.transmissionSamplerMap.value=i.texture,t.transmissionSamplerSize.value.set(i.width,i.height),e.transmissionMap&&(t.transmissionMap.value=e.transmissionMap,n(e.transmissionMap,t.transmissionMapTransform)),t.thickness.value=e.thickness,e.thicknessMap&&(t.thicknessMap.value=e.thicknessMap,n(e.thicknessMap,t.thicknessMapTransform)),t.attenuationDistance.value=e.attenuationDistance,t.attenuationColor.value.copy(e.attenuationColor));e.anisotropy>0&&(t.anisotropyVector.value.set(e.anisotropy*Math.cos(e.anisotropyRotation),e.anisotropy*Math.sin(e.anisotropyRotation)),e.anisotropyMap&&(t.anisotropyMap.value=e.anisotropyMap));t.specularIntensity.value=e.specularIntensity,t.specularColor.value.copy(e.specularColor),e.specularColorMap&&(t.specularColorMap.value=e.specularColorMap,n(e.specularColorMap,t.specularColorMapTransform));e.specularIntensityMap&&(t.specularIntensityMap.value=e.specularIntensityMap,n(e.specularIntensityMap,t.specularIntensityMapTransform))}(t,r,o)):r.isMeshMatcapMaterial?(i(t,r),function(t,e){e.matcap&&(t.matcap.value=e.matcap)}(t,r)):r.isMeshDepthMaterial?i(t,r):r.isMeshDistanceMaterial?(i(t,r),function(t,n){const i=e.get(n).light;t.referencePosition.value.setFromMatrixPosition(i.matrixWorld),t.nearDistance.value=i.shadow.camera.near,t.farDistance.value=i.shadow.camera.far}(t,r)):r.isMeshNormalMaterial?i(t,r):r.isLineBasicMaterial?(function(t,e){t.diffuse.value.copy(e.color),t.opacity.value=e.opacity,e.map&&(t.map.value=e.map,n(e.map,t.mapTransform))}(t,r),r.isLineDashedMaterial&&function(t,e){t.dashSize.value=e.dashSize,t.totalSize.value=e.dashSize+e.gapSize,t.scale.value=e.scale}(t,r)):r.isPointsMaterial?function(t,e,i,r){t.diffuse.value.copy(e.color),t.opacity.value=e.opacity,t.size.value=e.size*i,t.scale.value=.5*r,e.map&&(t.map.value=e.map,n(e.map,t.uvTransform));e.alphaMap&&(t.alphaMap.value=e.alphaMap);e.alphaTest>0&&(t.alphaTest.value=e.alphaTest)}(t,r,s,a):r.isSpriteMaterial?function(t,e){t.diffuse.value.copy(e.color),t.opacity.value=e.opacity,t.rotation.value=e.rotation,e.map&&(t.map.value=e.map,n(e.map,t.mapTransform));e.alphaMap&&(t.alphaMap.value=e.alphaMap);e.alphaTest>0&&(t.alphaTest.value=e.alphaTest)}(t,r):r.isShadowMaterial?(t.color.value.copy(r.color),t.opacity.value=r.opacity):r.isShaderMaterial&&(r.uniformsNeedUpdate=!1)}}}function Al(t,e,n,i){let r={},s={},a=[];const o=n.isWebGL2?t.getParameter(t.MAX_UNIFORM_BUFFER_BINDINGS):0;function l(t,e,n){const i=t.value;if(void 0===n[e]){if("number"==typeof i)n[e]=i;else{const t=Array.isArray(i)?i:[i],r=[];for(let e=0;e0){r=n%i;0!==r&&i-r-a.boundary<0&&(n+=i-r,s.__offset=n)}n+=a.storage}r=n%i,r>0&&(n+=i-r);t.__size=n,t.__cache={}}(n),d=function(e){const n=function(){for(let t=0;t0),u=!!n.morphAttributes.position,d=!!n.morphAttributes.normal,p=!!n.morphAttributes.color,m=i.toneMapped?y.toneMapping:0,f=n.morphAttributes.position||n.morphAttributes.normal||n.morphAttributes.color,v=void 0!==f?f.length:0,_=K.get(i),x=g.state.lights;if(!0===H&&(!0===k||t!==T)){const e=t===T&&i.id===E;lt.setState(i,t,e)}let M=!1;i.version===_.__version?_.needsLights&&_.lightsStateVersion!==x.state.version||_.outputColorSpace!==o||r.isInstancedMesh&&!1===_.instancing?M=!0:r.isInstancedMesh||!0!==_.instancing?r.isSkinnedMesh&&!1===_.skinning?M=!0:r.isSkinnedMesh||!0!==_.skinning?_.envMap!==l||!0===i.fog&&_.fog!==s?M=!0:void 0===_.numClippingPlanes||_.numClippingPlanes===lt.numPlanes&&_.numIntersection===lt.numIntersection?(_.vertexAlphas!==c||_.vertexTangents!==h||_.morphTargets!==u||_.morphNormals!==d||_.morphColors!==p||_.toneMapping!==m||!0===Y.isWebGL2&&_.morphTargetsCount!==v)&&(M=!0):M=!0:M=!0:M=!0:(M=!0,_.__version=i.version);let S=_.currentProgram;!0===M&&(S=jt(i,e,r));let w=!1,A=!1,R=!1;const C=S.getUniforms(),P=_.uniforms;Z.useProgram(S.program)&&(w=!0,A=!0,R=!0);i.id!==E&&(E=i.id,A=!0);if(w||T!==t){if(C.setValue(_t,"projectionMatrix",t.projectionMatrix),Y.logarithmicDepthBuffer&&C.setValue(_t,"logDepthBufFC",2/(Math.log(t.far+1)/Math.LN2)),T!==t&&(T=t,A=!0,R=!0),i.isShaderMaterial||i.isMeshPhongMaterial||i.isMeshToonMaterial||i.isMeshStandardMaterial||i.envMap){const e=C.map.cameraPosition;void 0!==e&&e.setValue(_t,W.setFromMatrixPosition(t.matrixWorld))}(i.isMeshPhongMaterial||i.isMeshToonMaterial||i.isMeshLambertMaterial||i.isMeshBasicMaterial||i.isMeshStandardMaterial||i.isShaderMaterial)&&C.setValue(_t,"isOrthographic",!0===t.isOrthographicCamera),(i.isMeshPhongMaterial||i.isMeshToonMaterial||i.isMeshLambertMaterial||i.isMeshBasicMaterial||i.isMeshStandardMaterial||i.isShaderMaterial||i.isShadowMaterial||r.isSkinnedMesh)&&C.setValue(_t,"viewMatrix",t.matrixWorldInverse)}if(r.isSkinnedMesh){C.setOptional(_t,r,"bindMatrix"),C.setOptional(_t,r,"bindMatrixInverse");const t=r.skeleton;t&&(Y.floatVertexTextures?(null===t.boneTexture&&t.computeBoneTexture(),C.setValue(_t,"boneTexture",t.boneTexture,$),C.setValue(_t,"boneTextureSize",t.boneTextureSize)):console.warn("THREE.WebGLRenderer: SkinnedMesh can only be used with WebGL 2. With WebGL 1 OES_texture_float and vertex textures support is required."))}const L=n.morphAttributes;(void 0!==L.position||void 0!==L.normal||void 0!==L.color&&!0===Y.isWebGL2)&&ut.update(r,n,S);(A||_.receiveShadow!==r.receiveShadow)&&(_.receiveShadow=r.receiveShadow,C.setValue(_t,"receiveShadow",r.receiveShadow));i.isMeshGouraudMaterial&&null!==i.envMap&&(P.envMap.value=l,P.flipEnvMap.value=l.isCubeTexture&&!1===l.isRenderTargetTexture?-1:1);A&&(C.setValue(_t,"toneMappingExposure",y.toneMappingExposure),_.needsLights&&(D=R,(N=P).ambientLightColor.needsUpdate=D,N.lightProbe.needsUpdate=D,N.directionalLights.needsUpdate=D,N.directionalLightShadows.needsUpdate=D,N.pointLights.needsUpdate=D,N.pointLightShadows.needsUpdate=D,N.spotLights.needsUpdate=D,N.spotLightShadows.needsUpdate=D,N.rectAreaLights.needsUpdate=D,N.hemisphereLights.needsUpdate=D),s&&!0===i.fog&&st.refreshFogUniforms(P,s),st.refreshMaterialUniforms(P,i,U,I,V),Oo.upload(_t,_.uniformsList,P,$));var N,D;i.isShaderMaterial&&!0===i.uniformsNeedUpdate&&(Oo.upload(_t,_.uniformsList,P,$),i.uniformsNeedUpdate=!1);i.isSpriteMaterial&&C.setValue(_t,"center",r.center);if(C.setValue(_t,"modelViewMatrix",r.modelViewMatrix),C.setValue(_t,"normalMatrix",r.normalMatrix),C.setValue(_t,"modelMatrix",r.matrixWorld),i.isShaderMaterial||i.isRawShaderMaterial){const t=i.uniformsGroups;for(let e=0,n=t.length;e0&&function(t,e,n,i){if(null===V){const t=Y.isWebGL2;V=new ci(1024,1024,{generateMipmaps:!0,type:q.has("EXT_color_buffer_half_float")?wt:yt,minFilter:vt,samples:t&&!0===o?4:0})}const r=y.getRenderTarget();y.setRenderTarget(V),y.getClearColor(C),P=y.getClearAlpha(),P<1&&y.setClearColor(16777215,.5);y.clear();const s=y.toneMapping;y.toneMapping=0,Wt(t,n,i),$.updateMultisampleRenderTarget(V),$.updateRenderTargetMipmap(V);let a=!1;for(let t=0,r=e.length;t0&&Wt(r,e,n),s.length>0&&Wt(s,e,n),a.length>0&&Wt(a,e,n),Z.buffers.depth.setTest(!0),Z.buffers.depth.setMask(!0),Z.buffers.color.setMask(!0),Z.setPolygonOffset(!1)}function Wt(t,e,n){const i=!0===e.isScene?e.overrideMaterial:null;for(let r=0,s=t.length;r0?_[_.length-1]:null,v.pop(),f=v.length>0?v[v.length-1]:null},this.getActiveCubeFace=function(){return M},this.getActiveMipmapLevel=function(){return S},this.getRenderTarget=function(){return b},this.setRenderTargetTextures=function(t,e,n){K.get(t.texture).__webglTexture=e,K.get(t.depthTexture).__webglTexture=n;const i=K.get(t);i.__hasExternalTextures=!0,i.__hasExternalTextures&&(i.__autoAllocateDepthBuffer=void 0===n,i.__autoAllocateDepthBuffer||!0===q.has("WEBGL_multisampled_render_to_texture")&&(console.warn("THREE.WebGLRenderer: Render-to-texture extension was disabled because an external texture was provided"),i.__useRenderToTexture=!1))},this.setRenderTargetFramebuffer=function(t,e){const n=K.get(t);n.__webglFramebuffer=e,n.__useDefaultFramebuffer=void 0===e},this.setRenderTarget=function(t,e=0,n=0){b=t,M=e,S=n;let i=!0,r=null,s=!1,a=!1;if(t){const n=K.get(t);void 0!==n.__useDefaultFramebuffer?(Z.bindFramebuffer(_t.FRAMEBUFFER,null),i=!1):void 0===n.__webglFramebuffer?$.setupRenderTarget(t):n.__hasExternalTextures&&$.rebindTextures(t,K.get(t.texture).__webglTexture,K.get(t.depthTexture).__webglTexture);const o=t.texture;(o.isData3DTexture||o.isDataArrayTexture||o.isCompressedArrayTexture)&&(a=!0);const l=K.get(t).__webglFramebuffer;t.isWebGLCubeRenderTarget?(r=l[e],s=!0):r=Y.isWebGL2&&t.samples>0&&!1===$.useMultisampledRTT(t)?K.get(t).__webglMultisampledFramebuffer:l,w.copy(t.viewport),A.copy(t.scissor),R=t.scissorTest}else w.copy(O).multiplyScalar(U).floor(),A.copy(F).multiplyScalar(U).floor(),R=B;if(Z.bindFramebuffer(_t.FRAMEBUFFER,r)&&Y.drawBuffers&&i&&Z.drawBuffers(t,r),Z.viewport(w),Z.scissor(A),Z.setScissorTest(R),s){const i=K.get(t.texture);_t.framebufferTexture2D(_t.FRAMEBUFFER,_t.COLOR_ATTACHMENT0,_t.TEXTURE_CUBE_MAP_POSITIVE_X+e,i.__webglTexture,n)}else if(a){const i=K.get(t.texture),r=e||0;_t.framebufferTextureLayer(_t.FRAMEBUFFER,_t.COLOR_ATTACHMENT0,i.__webglTexture,n||0,r)}E=-1},this.readRenderTargetPixels=function(t,e,n,i,r,s,a){if(!t||!t.isWebGLRenderTarget)return void console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.");let o=K.get(t).__webglFramebuffer;if(t.isWebGLCubeRenderTarget&&void 0!==a&&(o=o[a]),o){Z.bindFramebuffer(_t.FRAMEBUFFER,o);try{const a=t.texture,o=a.format,l=a.type;if(o!==Lt&&mt.convert(o)!==_t.getParameter(_t.IMPLEMENTATION_COLOR_READ_FORMAT))return void console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in RGBA or implementation defined format.");const c=l===wt&&(q.has("EXT_color_buffer_half_float")||Y.isWebGL2&&q.has("EXT_color_buffer_float"));if(!(l===yt||mt.convert(l)===_t.getParameter(_t.IMPLEMENTATION_COLOR_READ_TYPE)||l===Tt&&(Y.isWebGL2||q.has("OES_texture_float")||q.has("WEBGL_color_buffer_float"))||c))return void console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in UnsignedByteType or implementation defined type.");e>=0&&e<=t.width-i&&n>=0&&n<=t.height-r&&_t.readPixels(e,n,i,r,mt.convert(o),mt.convert(l),s)}finally{const t=null!==b?K.get(b).__webglFramebuffer:null;Z.bindFramebuffer(_t.FRAMEBUFFER,t)}}},this.copyFramebufferToTexture=function(t,e,n=0){const i=Math.pow(2,-n),r=Math.floor(e.image.width*i),s=Math.floor(e.image.height*i);$.setTexture2D(e,0),_t.copyTexSubImage2D(_t.TEXTURE_2D,n,0,0,t.x,t.y,r,s),Z.unbindTexture()},this.copyTextureToTexture=function(t,e,n,i=0){const r=e.image.width,s=e.image.height,a=mt.convert(n.format),o=mt.convert(n.type);$.setTexture2D(n,0),_t.pixelStorei(_t.UNPACK_FLIP_Y_WEBGL,n.flipY),_t.pixelStorei(_t.UNPACK_PREMULTIPLY_ALPHA_WEBGL,n.premultiplyAlpha),_t.pixelStorei(_t.UNPACK_ALIGNMENT,n.unpackAlignment),e.isDataTexture?_t.texSubImage2D(_t.TEXTURE_2D,i,t.x,t.y,r,s,a,o,e.image.data):e.isCompressedTexture?_t.compressedTexSubImage2D(_t.TEXTURE_2D,i,t.x,t.y,e.mipmaps[0].width,e.mipmaps[0].height,a,e.mipmaps[0].data):_t.texSubImage2D(_t.TEXTURE_2D,i,t.x,t.y,a,o,e.image),0===i&&n.generateMipmaps&&_t.generateMipmap(_t.TEXTURE_2D),Z.unbindTexture()},this.copyTextureToTexture3D=function(t,e,n,i,r=0){if(y.isWebGL1Renderer)return void console.warn("THREE.WebGLRenderer.copyTextureToTexture3D: can only be used with WebGL2.");const s=t.max.x-t.min.x+1,a=t.max.y-t.min.y+1,o=t.max.z-t.min.z+1,l=mt.convert(i.format),c=mt.convert(i.type);let h;if(i.isData3DTexture)$.setTexture3D(i,0),h=_t.TEXTURE_3D;else{if(!i.isDataArrayTexture)return void console.warn("THREE.WebGLRenderer.copyTextureToTexture3D: only supports THREE.DataTexture3D and THREE.DataTexture2DArray.");$.setTexture2DArray(i,0),h=_t.TEXTURE_2D_ARRAY}_t.pixelStorei(_t.UNPACK_FLIP_Y_WEBGL,i.flipY),_t.pixelStorei(_t.UNPACK_PREMULTIPLY_ALPHA_WEBGL,i.premultiplyAlpha),_t.pixelStorei(_t.UNPACK_ALIGNMENT,i.unpackAlignment);const u=_t.getParameter(_t.UNPACK_ROW_LENGTH),d=_t.getParameter(_t.UNPACK_IMAGE_HEIGHT),p=_t.getParameter(_t.UNPACK_SKIP_PIXELS),m=_t.getParameter(_t.UNPACK_SKIP_ROWS),f=_t.getParameter(_t.UNPACK_SKIP_IMAGES),g=n.isCompressedTexture?n.mipmaps[0]:n.image;_t.pixelStorei(_t.UNPACK_ROW_LENGTH,g.width),_t.pixelStorei(_t.UNPACK_IMAGE_HEIGHT,g.height),_t.pixelStorei(_t.UNPACK_SKIP_PIXELS,t.min.x),_t.pixelStorei(_t.UNPACK_SKIP_ROWS,t.min.y),_t.pixelStorei(_t.UNPACK_SKIP_IMAGES,t.min.z),n.isDataTexture||n.isData3DTexture?_t.texSubImage3D(h,r,e.x,e.y,e.z,s,a,o,l,c,g.data):n.isCompressedArrayTexture?(console.warn("THREE.WebGLRenderer.copyTextureToTexture3D: untested support for compressed srcTexture."),_t.compressedTexSubImage3D(h,r,e.x,e.y,e.z,s,a,o,l,g.data)):_t.texSubImage3D(h,r,e.x,e.y,e.z,s,a,o,l,c,g),_t.pixelStorei(_t.UNPACK_ROW_LENGTH,u),_t.pixelStorei(_t.UNPACK_IMAGE_HEIGHT,d),_t.pixelStorei(_t.UNPACK_SKIP_PIXELS,p),_t.pixelStorei(_t.UNPACK_SKIP_ROWS,m),_t.pixelStorei(_t.UNPACK_SKIP_IMAGES,f),0===r&&i.generateMipmaps&&_t.generateMipmap(h),Z.unbindTexture()},this.initTexture=function(t){t.isCubeTexture?$.setTextureCube(t,0):t.isData3DTexture?$.setTexture3D(t,0):t.isDataArrayTexture||t.isCompressedArrayTexture?$.setTexture2DArray(t,0):$.setTexture2D(t,0),Z.unbindTexture()},this.resetState=function(){M=0,S=0,b=null,Z.reset(),ft.reset()},"undefined"!=typeof __THREE_DEVTOOLS__&&__THREE_DEVTOOLS__.dispatchEvent(new CustomEvent("observe",{detail:this}))}get physicallyCorrectLights(){return console.warn("THREE.WebGLRenderer: the property .physicallyCorrectLights has been removed. Set renderer.useLegacyLights instead."),!this.useLegacyLights}set physicallyCorrectLights(t){console.warn("THREE.WebGLRenderer: the property .physicallyCorrectLights has been removed. Set renderer.useLegacyLights instead."),this.useLegacyLights=!t}get outputEncoding(){return console.warn("THREE.WebGLRenderer: Property .outputEncoding has been removed. Use .outputColorSpace instead."),this.outputColorSpace===Be?Ie:Le}set outputEncoding(t){console.warn("THREE.WebGLRenderer: Property .outputEncoding has been removed. Use .outputColorSpace instead."),this.outputColorSpace=t===Ie?Be:ze}}class Pl extends Cl{}Pl.prototype.isWebGL1Renderer=!0;class Ll{constructor(t,e=25e-5){this.isFogExp2=!0,this.name="",this.color=new Or(t),this.density=e}clone(){return new Ll(this.color,this.density)}toJSON(){return{type:"FogExp2",color:this.color.getHex(),density:this.density}}}class Il{constructor(t,e=1,n=1e3){this.isFog=!0,this.name="",this.color=new Or(t),this.near=e,this.far=n}clone(){return new Il(this.color,this.near,this.far)}toJSON(){return{type:"Fog",color:this.color.getHex(),near:this.near,far:this.far}}}class Ul extends vr{constructor(){super(),this.isScene=!0,this.type="Scene",this.background=null,this.environment=null,this.fog=null,this.backgroundBlurriness=0,this.backgroundIntensity=1,this.overrideMaterial=null,"undefined"!=typeof __THREE_DEVTOOLS__&&__THREE_DEVTOOLS__.dispatchEvent(new CustomEvent("observe",{detail:this}))}copy(t,e){return super.copy(t,e),null!==t.background&&(this.background=t.background.clone()),null!==t.environment&&(this.environment=t.environment.clone()),null!==t.fog&&(this.fog=t.fog.clone()),this.backgroundBlurriness=t.backgroundBlurriness,this.backgroundIntensity=t.backgroundIntensity,null!==t.overrideMaterial&&(this.overrideMaterial=t.overrideMaterial.clone()),this.matrixAutoUpdate=t.matrixAutoUpdate,this}toJSON(t){const e=super.toJSON(t);return null!==this.fog&&(e.object.fog=this.fog.toJSON()),this.backgroundBlurriness>0&&(e.object.backgroundBlurriness=this.backgroundBlurriness),1!==this.backgroundIntensity&&(e.object.backgroundIntensity=this.backgroundIntensity),e}get autoUpdate(){return console.warn("THREE.Scene: autoUpdate was renamed to matrixWorldAutoUpdate in r144."),this.matrixWorldAutoUpdate}set autoUpdate(t){console.warn("THREE.Scene: autoUpdate was renamed to matrixWorldAutoUpdate in r144."),this.matrixWorldAutoUpdate=t}}class Nl{constructor(t,e){this.isInterleavedBuffer=!0,this.array=t,this.stride=e,this.count=void 0!==t?t.length/e:0,this.usage=dn,this.updateRange={offset:0,count:-1},this.version=0,this.uuid=Cn()}onUploadCallback(){}set needsUpdate(t){!0===t&&this.version++}setUsage(t){return this.usage=t,this}copy(t){return this.array=new t.array.constructor(t.array),this.count=t.count,this.stride=t.stride,this.usage=t.usage,this}copyAt(t,e,n){t*=this.stride,n*=e.stride;for(let i=0,r=this.stride;it.far||e.push({distance:o,point:zl.clone(),uv:Cr.getInterpolation(zl,Xl,jl,ql,Yl,Zl,Jl,new zn),face:null,object:this})}copy(t,e){return super.copy(t,e),void 0!==t.center&&this.center.copy(t.center),this.material=t.material,this}}function $l(t,e,n,i,r,s){Vl.subVectors(t,n).addScalar(.5).multiply(i),void 0!==r?(Gl.x=s*Vl.x-r*Vl.y,Gl.y=r*Vl.x+s*Vl.y):Gl.copy(Vl),t.copy(e),t.x+=Gl.x,t.y+=Gl.y,t.applyMatrix4(Wl)}const Ql=new gi,tc=new gi;class ec extends vr{constructor(){super(),this._currentLevel=0,this.type="LOD",Object.defineProperties(this,{levels:{enumerable:!0,value:[]},isLOD:{value:!0}}),this.autoUpdate=!0}copy(t){super.copy(t,!1);const e=t.levels;for(let t=0,n=e.length;t0){let n,i;for(n=1,i=e.length;n0){Ql.setFromMatrixPosition(this.matrixWorld);const n=t.ray.origin.distanceTo(Ql);this.getObjectForDistance(n).raycast(t,e)}}update(t){const e=this.levels;if(e.length>1){Ql.setFromMatrixPosition(t.matrixWorld),tc.setFromMatrixPosition(this.matrixWorld);const n=Ql.distanceTo(tc)/t.zoom;let i,r;for(e[0].object.visible=!0,i=1,r=e.length;i=t))break;e[i-1].object.visible=!1,e[i].object.visible=!0}for(this._currentLevel=i-1;io)continue;u.applyMatrix4(this.matrixWorld);const s=t.ray.origin.distanceTo(u);st.far||e.push({distance:s,point:h.clone().applyMatrix4(this.matrixWorld),index:n,face:null,faceIndex:null,object:this})}}else{for(let n=Math.max(0,s.start),i=Math.min(m.count,s.start+s.count)-1;no)continue;u.applyMatrix4(this.matrixWorld);const i=t.ray.origin.distanceTo(u);it.far||e.push({distance:i,point:h.clone().applyMatrix4(this.matrixWorld),index:n,face:null,faceIndex:null,object:this})}}}updateMorphTargets(){const t=this.geometry.morphAttributes,e=Object.keys(t);if(e.length>0){const n=t[e[0]];if(void 0!==n){this.morphTargetInfluences=[],this.morphTargetDictionary={};for(let t=0,e=n.length;t0){const n=t[e[0]];if(void 0!==n){this.morphTargetInfluences=[],this.morphTargetDictionary={};for(let t=0,e=n.length;tr.far)return;s.push({distance:l,distanceToRay:Math.sqrt(o),point:n,index:e,face:null,object:a})}}class Wc extends oi{constructor(t,e,n,i,r,s,a,o,l){super(t,e,n,i,r,s,a,o,l),this.isVideoTexture=!0,this.minFilter=void 0!==s?s:mt,this.magFilter=void 0!==r?r:mt,this.generateMipmaps=!1;const c=this;"requestVideoFrameCallback"in t&&t.requestVideoFrameCallback((function e(){c.needsUpdate=!0,t.requestVideoFrameCallback(e)}))}clone(){return new this.constructor(this.image).copy(this)}update(){const t=this.image;!1==="requestVideoFrameCallback"in t&&t.readyState>=t.HAVE_CURRENT_DATA&&(this.needsUpdate=!0)}}class Xc extends oi{constructor(t,e){super({width:t,height:e}),this.isFramebufferTexture=!0,this.magFilter=ct,this.minFilter=ct,this.generateMipmaps=!1,this.needsUpdate=!0}}class jc extends oi{constructor(t,e,n,i,r,s,a,o,l,c,h,u){super(null,s,a,o,l,c,i,r,h,u),this.isCompressedTexture=!0,this.image={width:e,height:n},this.mipmaps=t,this.flipY=!1,this.generateMipmaps=!1}}class qc extends jc{constructor(t,e,n,i,r,s){super(t,e,n,r,s),this.isCompressedArrayTexture=!0,this.image.depth=i,this.wrapR=ot}}class Yc extends oi{constructor(t,e,n,i,r,s,a,o,l){super(t,e,n,i,r,s,a,o,l),this.isCanvasTexture=!0,this.needsUpdate=!0}}class Zc{constructor(){this.type="Curve",this.arcLengthDivisions=200}getPoint(){return console.warn("THREE.Curve: .getPoint() not implemented."),null}getPointAt(t,e){const n=this.getUtoTmapping(t);return this.getPoint(n,e)}getPoints(t=5){const e=[];for(let n=0;n<=t;n++)e.push(this.getPoint(n/t));return e}getSpacedPoints(t=5){const e=[];for(let n=0;n<=t;n++)e.push(this.getPointAt(n/t));return e}getLength(){const t=this.getLengths();return t[t.length-1]}getLengths(t=this.arcLengthDivisions){if(this.cacheArcLengths&&this.cacheArcLengths.length===t+1&&!this.needsUpdate)return this.cacheArcLengths;this.needsUpdate=!1;const e=[];let n,i=this.getPoint(0),r=0;e.push(0);for(let s=1;s<=t;s++)n=this.getPoint(s/t),r+=n.distanceTo(i),e.push(r),i=n;return this.cacheArcLengths=e,e}updateArcLengths(){this.needsUpdate=!0,this.getLengths()}getUtoTmapping(t,e){const n=this.getLengths();let i=0;const r=n.length;let s;s=e||t*n[r-1];let a,o=0,l=r-1;for(;o<=l;)if(i=Math.floor(o+(l-o)/2),a=n[i]-s,a<0)o=i+1;else{if(!(a>0)){l=i;break}l=i-1}if(i=l,n[i]===s)return i/(r-1);const c=n[i];return(i+(s-c)/(n[i+1]-c))/(r-1)}getTangent(t,e){const n=1e-4;let i=t-n,r=t+n;i<0&&(i=0),r>1&&(r=1);const s=this.getPoint(i),a=this.getPoint(r),o=e||(s.isVector2?new zn:new gi);return o.copy(a).sub(s).normalize(),o}getTangentAt(t,e){const n=this.getUtoTmapping(t);return this.getTangent(n,e)}computeFrenetFrames(t,e){const n=new gi,i=[],r=[],s=[],a=new gi,o=new ji;for(let e=0;e<=t;e++){const n=e/t;i[e]=this.getTangentAt(n,new gi)}r[0]=new gi,s[0]=new gi;let l=Number.MAX_VALUE;const c=Math.abs(i[0].x),h=Math.abs(i[0].y),u=Math.abs(i[0].z);c<=l&&(l=c,n.set(1,0,0)),h<=l&&(l=h,n.set(0,1,0)),u<=l&&n.set(0,0,1),a.crossVectors(i[0],n).normalize(),r[0].crossVectors(i[0],a),s[0].crossVectors(i[0],r[0]);for(let e=1;e<=t;e++){if(r[e]=r[e-1].clone(),s[e]=s[e-1].clone(),a.crossVectors(i[e-1],i[e]),a.length()>Number.EPSILON){a.normalize();const t=Math.acos(Pn(i[e-1].dot(i[e]),-1,1));r[e].applyMatrix4(o.makeRotationAxis(a,t))}s[e].crossVectors(i[e],r[e])}if(!0===e){let e=Math.acos(Pn(r[0].dot(r[t]),-1,1));e/=t,i[0].dot(a.crossVectors(r[0],r[t]))>0&&(e=-e);for(let n=1;n<=t;n++)r[n].applyMatrix4(o.makeRotationAxis(i[n],e*n)),s[n].crossVectors(i[n],r[n])}return{tangents:i,normals:r,binormals:s}}clone(){return(new this.constructor).copy(this)}copy(t){return this.arcLengthDivisions=t.arcLengthDivisions,this}toJSON(){const t={metadata:{version:4.6,type:"Curve",generator:"Curve.toJSON"}};return t.arcLengthDivisions=this.arcLengthDivisions,t.type=this.type,t}fromJSON(t){return this.arcLengthDivisions=t.arcLengthDivisions,this}}class Jc extends Zc{constructor(t=0,e=0,n=1,i=1,r=0,s=2*Math.PI,a=!1,o=0){super(),this.isEllipseCurve=!0,this.type="EllipseCurve",this.aX=t,this.aY=e,this.xRadius=n,this.yRadius=i,this.aStartAngle=r,this.aEndAngle=s,this.aClockwise=a,this.aRotation=o}getPoint(t,e){const n=e||new zn,i=2*Math.PI;let r=this.aEndAngle-this.aStartAngle;const s=Math.abs(r)i;)r-=i;r0?0:(Math.floor(Math.abs(l)/r)+1)*r:0===c&&l===r-1&&(l=r-2,c=1),this.closed||l>0?a=i[(l-1)%r]:(Qc.subVectors(i[0],i[1]).add(i[0]),a=Qc);const h=i[l%r],u=i[(l+1)%r];if(this.closed||l+2i.length-2?i.length-1:s+1],h=i[s>i.length-3?i.length-1:s+2];return n.set(rh(a,o.x,l.x,c.x,h.x),rh(a,o.y,l.y,c.y,h.y)),n}copy(t){super.copy(t),this.points=[];for(let e=0,n=t.points.length;e=n){const t=i[r]-n,s=this.curves[r],a=s.getLength(),o=0===a?0:1-t/a;return s.getPointAt(o,e)}r++}return null}getLength(){const t=this.getCurveLengths();return t[t.length-1]}updateArcLengths(){this.needsUpdate=!0,this.cacheLengths=null,this.getCurveLengths()}getCurveLengths(){if(this.cacheLengths&&this.cacheLengths.length===this.curves.length)return this.cacheLengths;const t=[];let e=0;for(let n=0,i=this.curves.length;n1&&!e[e.length-1].equals(e[0])&&e.push(e[0]),e}copy(t){super.copy(t),this.curves=[];for(let e=0,n=t.curves.length;e0){const t=l.getPoint(0);t.equals(this.currentPoint)||this.lineTo(t.x,t.y)}this.curves.push(l);const c=l.getPoint(1);return this.currentPoint.copy(c),this}copy(t){return super.copy(t),this.currentPoint.copy(t.currentPoint),this}toJSON(){const t=super.toJSON();return t.currentPoint=this.currentPoint.toArray(),t}fromJSON(t){return super.fromJSON(t),this.currentPoint.fromArray(t.currentPoint),this}}class vh extends hs{constructor(t=[new zn(0,-.5),new zn(.5,0),new zn(0,.5)],e=12,n=0,i=2*Math.PI){super(),this.type="LatheGeometry",this.parameters={points:t,segments:e,phiStart:n,phiLength:i},e=Math.floor(e),i=Pn(i,0,2*Math.PI);const r=[],s=[],a=[],o=[],l=[],c=1/e,h=new gi,u=new zn,d=new gi,p=new gi,m=new gi;let f=0,g=0;for(let e=0;e<=t.length-1;e++)switch(e){case 0:f=t[e+1].x-t[e].x,g=t[e+1].y-t[e].y,d.x=1*g,d.y=-f,d.z=0*g,m.copy(d),d.normalize(),o.push(d.x,d.y,d.z);break;case t.length-1:o.push(m.x,m.y,m.z);break;default:f=t[e+1].x-t[e].x,g=t[e+1].y-t[e].y,d.x=1*g,d.y=-f,d.z=0*g,p.copy(d),d.x+=m.x,d.y+=m.y,d.z+=m.z,d.normalize(),o.push(d.x,d.y,d.z),m.copy(p)}for(let r=0;r<=e;r++){const d=n+r*c*i,p=Math.sin(d),m=Math.cos(d);for(let n=0;n<=t.length-1;n++){h.x=t[n].x*p,h.y=t[n].y,h.z=t[n].x*m,s.push(h.x,h.y,h.z),u.x=r/e,u.y=n/(t.length-1),a.push(u.x,u.y);const i=o[3*n+0]*p,c=o[3*n+1],d=o[3*n+0]*m;l.push(i,c,d)}}for(let n=0;n0&&v(!0),e>0&&v(!1)),this.setIndex(c),this.setAttribute("position",new es(h,3)),this.setAttribute("normal",new es(u,3)),this.setAttribute("uv",new es(d,2))}copy(t){return super.copy(t),this.parameters=Object.assign({},t.parameters),this}static fromJSON(t){return new xh(t.radiusTop,t.radiusBottom,t.height,t.radialSegments,t.heightSegments,t.openEnded,t.thetaStart,t.thetaLength)}}class Mh extends xh{constructor(t=1,e=1,n=32,i=1,r=!1,s=0,a=2*Math.PI){super(0,t,e,n,i,r,s,a),this.type="ConeGeometry",this.parameters={radius:t,height:e,radialSegments:n,heightSegments:i,openEnded:r,thetaStart:s,thetaLength:a}}static fromJSON(t){return new Mh(t.radius,t.height,t.radialSegments,t.heightSegments,t.openEnded,t.thetaStart,t.thetaLength)}}class Sh extends hs{constructor(t=[],e=[],n=1,i=0){super(),this.type="PolyhedronGeometry",this.parameters={vertices:t,indices:e,radius:n,detail:i};const r=[],s=[];function a(t,e,n,i){const r=i+1,s=[];for(let i=0;i<=r;i++){s[i]=[];const a=t.clone().lerp(n,i/r),o=e.clone().lerp(n,i/r),l=r-i;for(let t=0;t<=l;t++)s[i][t]=0===t&&i===r?a:a.clone().lerp(o,t/l)}for(let t=0;t.9&&a<.1&&(e<.2&&(s[t+0]+=1),n<.2&&(s[t+2]+=1),i<.2&&(s[t+4]+=1))}}()}(),this.setAttribute("position",new es(r,3)),this.setAttribute("normal",new es(r.slice(),3)),this.setAttribute("uv",new es(s,2)),0===i?this.computeVertexNormals():this.normalizeNormals()}copy(t){return super.copy(t),this.parameters=Object.assign({},t.parameters),this}static fromJSON(t){return new Sh(t.vertices,t.indices,t.radius,t.details)}}class bh extends Sh{constructor(t=1,e=0){const n=(1+Math.sqrt(5))/2,i=1/n;super([-1,-1,-1,-1,-1,1,-1,1,-1,-1,1,1,1,-1,-1,1,-1,1,1,1,-1,1,1,1,0,-i,-n,0,-i,n,0,i,-n,0,i,n,-i,-n,0,-i,n,0,i,-n,0,i,n,0,-n,0,-i,n,0,-i,-n,0,i,n,0,i],[3,11,7,3,7,15,3,15,13,7,19,17,7,17,6,7,6,15,17,4,8,17,8,10,17,10,6,8,0,16,8,16,2,8,2,10,0,12,1,0,1,18,0,18,16,6,10,2,6,2,13,6,13,15,2,16,18,2,18,3,2,3,13,18,1,9,18,9,11,18,11,3,4,14,12,4,12,0,4,0,8,11,9,5,11,5,19,11,19,7,19,5,14,19,14,4,19,4,17,1,12,14,1,14,5,1,5,9],t,e),this.type="DodecahedronGeometry",this.parameters={radius:t,detail:e}}static fromJSON(t){return new bh(t.radius,t.detail)}}const Eh=new gi,Th=new gi,wh=new gi,Ah=new Cr;class Rh extends hs{constructor(t=null,e=1){if(super(),this.type="EdgesGeometry",this.parameters={geometry:t,thresholdAngle:e},null!==t){const n=4,i=Math.pow(10,n),r=Math.cos(An*e),s=t.getIndex(),a=t.getAttribute("position"),o=s?s.count:a.count,l=[0,0,0],c=["a","b","c"],h=new Array(3),u={},d=[];for(let t=0;t80*n){o=c=t[0],l=h=t[1];for(let e=n;ec&&(c=u),d>h&&(h=d);p=Math.max(c-o,h-l),p=0!==p?32767/p:0}return Uh(s,a,n,o,l,p,0),a};function Lh(t,e,n,i,r){let s,a;if(r===function(t,e,n,i){let r=0;for(let s=e,a=n-i;s0)for(s=e;s=e;s-=i)a=$h(s,t[s],t[s+1],a);return a&&jh(a,a.next)&&(Qh(a),a=a.next),a}function Ih(t,e){if(!t)return t;e||(e=t);let n,i=t;do{if(n=!1,i.steiner||!jh(i,i.next)&&0!==Xh(i.prev,i,i.next))i=i.next;else{if(Qh(i),i=e=i.prev,i===i.next)break;n=!0}}while(n||i!==e);return e}function Uh(t,e,n,i,r,s,a){if(!t)return;!a&&s&&function(t,e,n,i){let r=t;do{0===r.z&&(r.z=kh(r.x,r.y,e,n,i)),r.prevZ=r.prev,r.nextZ=r.next,r=r.next}while(r!==t);r.prevZ.nextZ=null,r.prevZ=null,function(t){let e,n,i,r,s,a,o,l,c=1;do{for(n=t,t=null,s=null,a=0;n;){for(a++,i=n,o=0,e=0;e0||l>0&&i;)0!==o&&(0===l||!i||n.z<=i.z)?(r=n,n=n.nextZ,o--):(r=i,i=i.nextZ,l--),s?s.nextZ=r:t=r,r.prevZ=s,s=r;n=i}s.nextZ=null,c*=2}while(a>1)}(r)}(t,i,r,s);let o,l,c=t;for(;t.prev!==t.next;)if(o=t.prev,l=t.next,s?Dh(t,i,r,s):Nh(t))e.push(o.i/n|0),e.push(t.i/n|0),e.push(l.i/n|0),Qh(t),t=l.next,c=l.next;else if((t=l)===c){a?1===a?Uh(t=Oh(Ih(t),e,n),e,n,i,r,s,2):2===a&&Fh(t,e,n,i,r,s):Uh(Ih(t),e,n,i,r,s,1);break}}function Nh(t){const e=t.prev,n=t,i=t.next;if(Xh(e,n,i)>=0)return!1;const r=e.x,s=n.x,a=i.x,o=e.y,l=n.y,c=i.y,h=rs?r>a?r:a:s>a?s:a,p=o>l?o>c?o:c:l>c?l:c;let m=i.next;for(;m!==e;){if(m.x>=h&&m.x<=d&&m.y>=u&&m.y<=p&&Gh(r,o,s,l,a,c,m.x,m.y)&&Xh(m.prev,m,m.next)>=0)return!1;m=m.next}return!0}function Dh(t,e,n,i){const r=t.prev,s=t,a=t.next;if(Xh(r,s,a)>=0)return!1;const o=r.x,l=s.x,c=a.x,h=r.y,u=s.y,d=a.y,p=ol?o>c?o:c:l>c?l:c,g=h>u?h>d?h:d:u>d?u:d,v=kh(p,m,e,n,i),_=kh(f,g,e,n,i);let y=t.prevZ,x=t.nextZ;for(;y&&y.z>=v&&x&&x.z<=_;){if(y.x>=p&&y.x<=f&&y.y>=m&&y.y<=g&&y!==r&&y!==a&&Gh(o,h,l,u,c,d,y.x,y.y)&&Xh(y.prev,y,y.next)>=0)return!1;if(y=y.prevZ,x.x>=p&&x.x<=f&&x.y>=m&&x.y<=g&&x!==r&&x!==a&&Gh(o,h,l,u,c,d,x.x,x.y)&&Xh(x.prev,x,x.next)>=0)return!1;x=x.nextZ}for(;y&&y.z>=v;){if(y.x>=p&&y.x<=f&&y.y>=m&&y.y<=g&&y!==r&&y!==a&&Gh(o,h,l,u,c,d,y.x,y.y)&&Xh(y.prev,y,y.next)>=0)return!1;y=y.prevZ}for(;x&&x.z<=_;){if(x.x>=p&&x.x<=f&&x.y>=m&&x.y<=g&&x!==r&&x!==a&&Gh(o,h,l,u,c,d,x.x,x.y)&&Xh(x.prev,x,x.next)>=0)return!1;x=x.nextZ}return!0}function Oh(t,e,n){let i=t;do{const r=i.prev,s=i.next.next;!jh(r,s)&&qh(r,i,i.next,s)&&Jh(r,s)&&Jh(s,r)&&(e.push(r.i/n|0),e.push(i.i/n|0),e.push(s.i/n|0),Qh(i),Qh(i.next),i=t=s),i=i.next}while(i!==t);return Ih(i)}function Fh(t,e,n,i,r,s){let a=t;do{let t=a.next.next;for(;t!==a.prev;){if(a.i!==t.i&&Wh(a,t)){let o=Kh(a,t);return a=Ih(a,a.next),o=Ih(o,o.next),Uh(a,e,n,i,r,s,0),void Uh(o,e,n,i,r,s,0)}t=t.next}a=a.next}while(a!==t)}function Bh(t,e){return t.x-e.x}function zh(t,e){const n=function(t,e){let n,i=e,r=-1/0;const s=t.x,a=t.y;do{if(a<=i.y&&a>=i.next.y&&i.next.y!==i.y){const t=i.x+(a-i.y)*(i.next.x-i.x)/(i.next.y-i.y);if(t<=s&&t>r&&(r=t,n=i.x=i.x&&i.x>=l&&s!==i.x&&Gh(an.x||i.x===n.x&&Hh(n,i)))&&(n=i,u=h)),i=i.next}while(i!==o);return n}(t,e);if(!n)return e;const i=Kh(n,t);return Ih(i,i.next),Ih(n,n.next)}function Hh(t,e){return Xh(t.prev,t,e.prev)<0&&Xh(e.next,t,t.next)<0}function kh(t,e,n,i,r){return(t=1431655765&((t=858993459&((t=252645135&((t=16711935&((t=(t-n)*r|0)|t<<8))|t<<4))|t<<2))|t<<1))|(e=1431655765&((e=858993459&((e=252645135&((e=16711935&((e=(e-i)*r|0)|e<<8))|e<<4))|e<<2))|e<<1))<<1}function Vh(t){let e=t,n=t;do{(e.x=(t-a)*(s-o)&&(t-a)*(i-o)>=(n-a)*(e-o)&&(n-a)*(s-o)>=(r-a)*(i-o)}function Wh(t,e){return t.next.i!==e.i&&t.prev.i!==e.i&&!function(t,e){let n=t;do{if(n.i!==t.i&&n.next.i!==t.i&&n.i!==e.i&&n.next.i!==e.i&&qh(n,n.next,t,e))return!0;n=n.next}while(n!==t);return!1}(t,e)&&(Jh(t,e)&&Jh(e,t)&&function(t,e){let n=t,i=!1;const r=(t.x+e.x)/2,s=(t.y+e.y)/2;do{n.y>s!=n.next.y>s&&n.next.y!==n.y&&r<(n.next.x-n.x)*(s-n.y)/(n.next.y-n.y)+n.x&&(i=!i),n=n.next}while(n!==t);return i}(t,e)&&(Xh(t.prev,t,e.prev)||Xh(t,e.prev,e))||jh(t,e)&&Xh(t.prev,t,t.next)>0&&Xh(e.prev,e,e.next)>0)}function Xh(t,e,n){return(e.y-t.y)*(n.x-e.x)-(e.x-t.x)*(n.y-e.y)}function jh(t,e){return t.x===e.x&&t.y===e.y}function qh(t,e,n,i){const r=Zh(Xh(t,e,n)),s=Zh(Xh(t,e,i)),a=Zh(Xh(n,i,t)),o=Zh(Xh(n,i,e));return r!==s&&a!==o||(!(0!==r||!Yh(t,n,e))||(!(0!==s||!Yh(t,i,e))||(!(0!==a||!Yh(n,t,i))||!(0!==o||!Yh(n,e,i)))))}function Yh(t,e,n){return e.x<=Math.max(t.x,n.x)&&e.x>=Math.min(t.x,n.x)&&e.y<=Math.max(t.y,n.y)&&e.y>=Math.min(t.y,n.y)}function Zh(t){return t>0?1:t<0?-1:0}function Jh(t,e){return Xh(t.prev,t,t.next)<0?Xh(t,e,t.next)>=0&&Xh(t,t.prev,e)>=0:Xh(t,e,t.prev)<0||Xh(t,t.next,e)<0}function Kh(t,e){const n=new tu(t.i,t.x,t.y),i=new tu(e.i,e.x,e.y),r=t.next,s=e.prev;return t.next=e,e.prev=t,n.next=r,r.prev=n,i.next=n,n.prev=i,s.next=i,i.prev=s,i}function $h(t,e,n,i){const r=new tu(t,e,n);return i?(r.next=i.next,r.prev=i,i.next.prev=r,i.next=r):(r.prev=r,r.next=r),r}function Qh(t){t.next.prev=t.prev,t.prev.next=t.next,t.prevZ&&(t.prevZ.nextZ=t.nextZ),t.nextZ&&(t.nextZ.prevZ=t.prevZ)}function tu(t,e,n){this.i=t,this.x=e,this.y=n,this.prev=null,this.next=null,this.z=0,this.prevZ=null,this.nextZ=null,this.steiner=!1}class eu{static area(t){const e=t.length;let n=0;for(let i=e-1,r=0;r2&&t[e-1].equals(t[0])&&t.pop()}function iu(t,e){for(let n=0;nNumber.EPSILON){const u=Math.sqrt(h),d=Math.sqrt(l*l+c*c),p=e.x-o/u,m=e.y+a/u,f=((n.x-c/d-p)*c-(n.y+l/d-m)*l)/(a*c-o*l);i=p+a*f-t.x,r=m+o*f-t.y;const g=i*i+r*r;if(g<=2)return new zn(i,r);s=Math.sqrt(g/2)}else{let t=!1;a>Number.EPSILON?l>Number.EPSILON&&(t=!0):a<-Number.EPSILON?l<-Number.EPSILON&&(t=!0):Math.sign(o)===Math.sign(c)&&(t=!0),t?(i=-o,r=a,s=Math.sqrt(h)):(i=a,r=o,s=Math.sqrt(h/2))}return new zn(i/s,r/s)}const L=[];for(let t=0,e=w.length,n=e-1,i=t+1;t=0;t--){const e=t/p,n=h*Math.cos(e*Math.PI/2),i=u*Math.sin(e*Math.PI/2)+d;for(let t=0,e=w.length;t=0;){const i=n;let r=n-1;r<0&&(r=t.length-1);for(let t=0,n=o+2*p;t0)&&d.push(e,r,l),(t!==n-1||o0!=t>0&&this.version++,this._anisotropy=t}get clearcoat(){return this._clearcoat}set clearcoat(t){this._clearcoat>0!=t>0&&this.version++,this._clearcoat=t}get iridescence(){return this._iridescence}set iridescence(t){this._iridescence>0!=t>0&&this.version++,this._iridescence=t}get sheen(){return this._sheen}set sheen(t){this._sheen>0!=t>0&&this.version++,this._sheen=t}get transmission(){return this._transmission}set transmission(t){this._transmission>0!=t>0&&this.version++,this._transmission=t}copy(t){return super.copy(t),this.defines={STANDARD:"",PHYSICAL:""},this.anisotropy=t.anisotropy,this.anisotropyRotation=t.anisotropyRotation,this.anisotropyMap=t.anisotropyMap,this.clearcoat=t.clearcoat,this.clearcoatMap=t.clearcoatMap,this.clearcoatRoughness=t.clearcoatRoughness,this.clearcoatRoughnessMap=t.clearcoatRoughnessMap,this.clearcoatNormalMap=t.clearcoatNormalMap,this.clearcoatNormalScale.copy(t.clearcoatNormalScale),this.ior=t.ior,this.iridescence=t.iridescence,this.iridescenceMap=t.iridescenceMap,this.iridescenceIOR=t.iridescenceIOR,this.iridescenceThicknessRange=[...t.iridescenceThicknessRange],this.iridescenceThicknessMap=t.iridescenceThicknessMap,this.sheen=t.sheen,this.sheenColor.copy(t.sheenColor),this.sheenColorMap=t.sheenColorMap,this.sheenRoughness=t.sheenRoughness,this.sheenRoughnessMap=t.sheenRoughnessMap,this.transmission=t.transmission,this.transmissionMap=t.transmissionMap,this.thickness=t.thickness,this.thicknessMap=t.thicknessMap,this.attenuationDistance=t.attenuationDistance,this.attenuationColor.copy(t.attenuationColor),this.specularIntensity=t.specularIntensity,this.specularIntensityMap=t.specularIntensityMap,this.specularColor.copy(t.specularColor),this.specularColorMap=t.specularColorMap,this}}class Su extends Lr{constructor(t){super(),this.isMeshPhongMaterial=!0,this.type="MeshPhongMaterial",this.color=new Or(16777215),this.specular=new Or(1118481),this.shininess=30,this.map=null,this.lightMap=null,this.lightMapIntensity=1,this.aoMap=null,this.aoMapIntensity=1,this.emissive=new Or(0),this.emissiveIntensity=1,this.emissiveMap=null,this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new zn(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.specularMap=null,this.alphaMap=null,this.envMap=null,this.combine=0,this.reflectivity=1,this.refractionRatio=.98,this.wireframe=!1,this.wireframeLinewidth=1,this.wireframeLinecap="round",this.wireframeLinejoin="round",this.flatShading=!1,this.fog=!0,this.setValues(t)}copy(t){return super.copy(t),this.color.copy(t.color),this.specular.copy(t.specular),this.shininess=t.shininess,this.map=t.map,this.lightMap=t.lightMap,this.lightMapIntensity=t.lightMapIntensity,this.aoMap=t.aoMap,this.aoMapIntensity=t.aoMapIntensity,this.emissive.copy(t.emissive),this.emissiveMap=t.emissiveMap,this.emissiveIntensity=t.emissiveIntensity,this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.specularMap=t.specularMap,this.alphaMap=t.alphaMap,this.envMap=t.envMap,this.combine=t.combine,this.reflectivity=t.reflectivity,this.refractionRatio=t.refractionRatio,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.wireframeLinecap=t.wireframeLinecap,this.wireframeLinejoin=t.wireframeLinejoin,this.flatShading=t.flatShading,this.fog=t.fog,this}}class bu extends Lr{constructor(t){super(),this.isMeshToonMaterial=!0,this.defines={TOON:""},this.type="MeshToonMaterial",this.color=new Or(16777215),this.map=null,this.gradientMap=null,this.lightMap=null,this.lightMapIntensity=1,this.aoMap=null,this.aoMapIntensity=1,this.emissive=new Or(0),this.emissiveIntensity=1,this.emissiveMap=null,this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new zn(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.alphaMap=null,this.wireframe=!1,this.wireframeLinewidth=1,this.wireframeLinecap="round",this.wireframeLinejoin="round",this.fog=!0,this.setValues(t)}copy(t){return super.copy(t),this.color.copy(t.color),this.map=t.map,this.gradientMap=t.gradientMap,this.lightMap=t.lightMap,this.lightMapIntensity=t.lightMapIntensity,this.aoMap=t.aoMap,this.aoMapIntensity=t.aoMapIntensity,this.emissive.copy(t.emissive),this.emissiveMap=t.emissiveMap,this.emissiveIntensity=t.emissiveIntensity,this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.alphaMap=t.alphaMap,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.wireframeLinecap=t.wireframeLinecap,this.wireframeLinejoin=t.wireframeLinejoin,this.fog=t.fog,this}}class Eu extends Lr{constructor(t){super(),this.isMeshNormalMaterial=!0,this.type="MeshNormalMaterial",this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new zn(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.wireframe=!1,this.wireframeLinewidth=1,this.flatShading=!1,this.setValues(t)}copy(t){return super.copy(t),this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.flatShading=t.flatShading,this}}class Tu extends Lr{constructor(t){super(),this.isMeshLambertMaterial=!0,this.type="MeshLambertMaterial",this.color=new Or(16777215),this.map=null,this.lightMap=null,this.lightMapIntensity=1,this.aoMap=null,this.aoMapIntensity=1,this.emissive=new Or(0),this.emissiveIntensity=1,this.emissiveMap=null,this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new zn(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.specularMap=null,this.alphaMap=null,this.envMap=null,this.combine=0,this.reflectivity=1,this.refractionRatio=.98,this.wireframe=!1,this.wireframeLinewidth=1,this.wireframeLinecap="round",this.wireframeLinejoin="round",this.flatShading=!1,this.fog=!0,this.setValues(t)}copy(t){return super.copy(t),this.color.copy(t.color),this.map=t.map,this.lightMap=t.lightMap,this.lightMapIntensity=t.lightMapIntensity,this.aoMap=t.aoMap,this.aoMapIntensity=t.aoMapIntensity,this.emissive.copy(t.emissive),this.emissiveMap=t.emissiveMap,this.emissiveIntensity=t.emissiveIntensity,this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.specularMap=t.specularMap,this.alphaMap=t.alphaMap,this.envMap=t.envMap,this.combine=t.combine,this.reflectivity=t.reflectivity,this.refractionRatio=t.refractionRatio,this.wireframe=t.wireframe,this.wireframeLinewidth=t.wireframeLinewidth,this.wireframeLinecap=t.wireframeLinecap,this.wireframeLinejoin=t.wireframeLinejoin,this.flatShading=t.flatShading,this.fog=t.fog,this}}class wu extends Lr{constructor(t){super(),this.isMeshMatcapMaterial=!0,this.defines={MATCAP:""},this.type="MeshMatcapMaterial",this.color=new Or(16777215),this.matcap=null,this.map=null,this.bumpMap=null,this.bumpScale=1,this.normalMap=null,this.normalMapType=0,this.normalScale=new zn(1,1),this.displacementMap=null,this.displacementScale=1,this.displacementBias=0,this.alphaMap=null,this.flatShading=!1,this.fog=!0,this.setValues(t)}copy(t){return super.copy(t),this.defines={MATCAP:""},this.color.copy(t.color),this.matcap=t.matcap,this.map=t.map,this.bumpMap=t.bumpMap,this.bumpScale=t.bumpScale,this.normalMap=t.normalMap,this.normalMapType=t.normalMapType,this.normalScale.copy(t.normalScale),this.displacementMap=t.displacementMap,this.displacementScale=t.displacementScale,this.displacementBias=t.displacementBias,this.alphaMap=t.alphaMap,this.flatShading=t.flatShading,this.fog=t.fog,this}}class Au extends wc{constructor(t){super(),this.isLineDashedMaterial=!0,this.type="LineDashedMaterial",this.scale=1,this.dashSize=3,this.gapSize=1,this.setValues(t)}copy(t){return super.copy(t),this.scale=t.scale,this.dashSize=t.dashSize,this.gapSize=t.gapSize,this}}function Ru(t,e,n){return Pu(t)?new t.constructor(t.subarray(e,void 0!==n?n:t.length)):t.slice(e,n)}function Cu(t,e,n){return!t||!n&&t.constructor===e?t:"number"==typeof e.BYTES_PER_ELEMENT?new e(t):Array.prototype.slice.call(t)}function Pu(t){return ArrayBuffer.isView(t)&&!(t instanceof DataView)}function Lu(t){const e=t.length,n=new Array(e);for(let t=0;t!==e;++t)n[t]=t;return n.sort((function(e,n){return t[e]-t[n]})),n}function Iu(t,e,n){const i=t.length,r=new t.constructor(i);for(let s=0,a=0;a!==i;++s){const i=n[s]*e;for(let n=0;n!==e;++n)r[a++]=t[i+n]}return r}function Uu(t,e,n,i){let r=1,s=t[0];for(;void 0!==s&&void 0===s[i];)s=t[r++];if(void 0===s)return;let a=s[i];if(void 0!==a)if(Array.isArray(a))do{a=s[i],void 0!==a&&(e.push(s.time),n.push.apply(n,a)),s=t[r++]}while(void 0!==s);else if(void 0!==a.toArray)do{a=s[i],void 0!==a&&(e.push(s.time),a.toArray(n,n.length)),s=t[r++]}while(void 0!==s);else do{a=s[i],void 0!==a&&(e.push(s.time),n.push(a)),s=t[r++]}while(void 0!==s)}const Nu={arraySlice:Ru,convertArray:Cu,isTypedArray:Pu,getKeyframeOrder:Lu,sortedArray:Iu,flattenJSON:Uu,subclip:function(t,e,n,i,r=30){const s=t.clone();s.name=e;const a=[];for(let t=0;t=i)){l.push(e.times[t]);for(let n=0;ns.tracks[t].times[0]&&(o=s.tracks[t].times[0]);for(let t=0;t=i.times[u]){const t=u*l+o,e=t+l-o;d=Ru(i.values,t,e)}else{const t=i.createInterpolant(),e=o,n=l-o;t.evaluate(s),d=Ru(t.resultBuffer,e,n)}if("quaternion"===r){(new fi).fromArray(d).normalize().conjugate().toArray(d)}const p=a.times.length;for(let t=0;t=r)break t;{const a=e[1];t=r)break e}s=n,n=0}}for(;n>>1;te;)--s;if(++s,0!==r||s!==i){r>=s&&(s=Math.max(s,1),r=s-1);const t=this.getValueSize();this.times=Ru(n,r,s),this.values=Ru(this.values,r*t,s*t)}return this}validate(){let t=!0;const e=this.getValueSize();e-Math.floor(e)!=0&&(console.error("THREE.KeyframeTrack: Invalid value size in track.",this),t=!1);const n=this.times,i=this.values,r=n.length;0===r&&(console.error("THREE.KeyframeTrack: Track is empty.",this),t=!1);let s=null;for(let e=0;e!==r;e++){const i=n[e];if("number"==typeof i&&isNaN(i)){console.error("THREE.KeyframeTrack: Time is not a valid number.",this,e,i),t=!1;break}if(null!==s&&s>i){console.error("THREE.KeyframeTrack: Out of order keys.",this,e,i,s),t=!1;break}s=i}if(void 0!==i&&Pu(i))for(let e=0,n=i.length;e!==n;++e){const n=i[e];if(isNaN(n)){console.error("THREE.KeyframeTrack: Value is not a valid number.",this,e,n),t=!1;break}}return t}optimize(){const t=Ru(this.times),e=Ru(this.values),n=this.getValueSize(),i=this.getInterpolation()===Se,r=t.length-1;let s=1;for(let a=1;a0){t[s]=t[r];for(let t=r*n,i=s*n,a=0;a!==n;++a)e[i+a]=e[t+a];++s}return s!==t.length?(this.times=Ru(t,0,s),this.values=Ru(e,0,s*n)):(this.times=t,this.values=e),this}clone(){const t=Ru(this.times,0),e=Ru(this.values,0),n=new(0,this.constructor)(this.name,t,e);return n.createInterpolant=this.createInterpolant,n}}zu.prototype.TimeBufferType=Float32Array,zu.prototype.ValueBufferType=Float32Array,zu.prototype.DefaultInterpolation=Me;class Hu extends zu{}Hu.prototype.ValueTypeName="bool",Hu.prototype.ValueBufferType=Array,Hu.prototype.DefaultInterpolation=xe,Hu.prototype.InterpolantFactoryMethodLinear=void 0,Hu.prototype.InterpolantFactoryMethodSmooth=void 0;class ku extends zu{}ku.prototype.ValueTypeName="color";class Vu extends zu{}Vu.prototype.ValueTypeName="number";class Gu extends Du{constructor(t,e,n,i){super(t,e,n,i)}interpolate_(t,e,n,i){const r=this.resultBuffer,s=this.sampleValues,a=this.valueSize,o=(n-e)/(i-e);let l=t*a;for(let t=l+a;l!==t;l+=4)fi.slerpFlat(r,0,s,l-a,s,l,o);return r}}class Wu extends zu{InterpolantFactoryMethodLinear(t){return new Gu(this.times,this.values,this.getValueSize(),t)}}Wu.prototype.ValueTypeName="quaternion",Wu.prototype.DefaultInterpolation=Me,Wu.prototype.InterpolantFactoryMethodSmooth=void 0;class Xu extends zu{}Xu.prototype.ValueTypeName="string",Xu.prototype.ValueBufferType=Array,Xu.prototype.DefaultInterpolation=xe,Xu.prototype.InterpolantFactoryMethodLinear=void 0,Xu.prototype.InterpolantFactoryMethodSmooth=void 0;class ju extends zu{}ju.prototype.ValueTypeName="vector";class qu{constructor(t,e=-1,n,i=2500){this.name=t,this.tracks=n,this.duration=e,this.blendMode=i,this.uuid=Cn(),this.duration<0&&this.resetDuration()}static parse(t){const e=[],n=t.tracks,i=1/(t.fps||1);for(let t=0,r=n.length;t!==r;++t)e.push(Yu(n[t]).scale(i));const r=new this(t.name,t.duration,e,t.blendMode);return r.uuid=t.uuid,r}static toJSON(t){const e=[],n=t.tracks,i={name:t.name,duration:t.duration,tracks:e,uuid:t.uuid,blendMode:t.blendMode};for(let t=0,i=n.length;t!==i;++t)e.push(zu.toJSON(n[t]));return i}static CreateFromMorphTargetSequence(t,e,n,i){const r=e.length,s=[];for(let t=0;t1){const t=s[1];let e=i[t];e||(i[t]=e=[]),e.push(n)}}const s=[];for(const t in i)s.push(this.CreateFromMorphTargetSequence(t,i[t],e,n));return s}static parseAnimation(t,e){if(!t)return console.error("THREE.AnimationClip: No animation in JSONLoader data."),null;const n=function(t,e,n,i,r){if(0!==n.length){const s=[],a=[];Uu(n,s,a,i),0!==s.length&&r.push(new t(e,s,a))}},i=[],r=t.name||"default",s=t.fps||30,a=t.blendMode;let o=t.length||-1;const l=t.hierarchy||[];for(let t=0;t{e&&e(r),this.manager.itemEnd(t)}),0),r;if(void 0!==Qu[t])return void Qu[t].push({onLoad:e,onProgress:n,onError:i});Qu[t]=[],Qu[t].push({onLoad:e,onProgress:n,onError:i});const s=new Request(t,{headers:new Headers(this.requestHeader),credentials:this.withCredentials?"include":"same-origin"}),a=this.mimeType,o=this.responseType;fetch(s).then((e=>{if(200===e.status||0===e.status){if(0===e.status&&console.warn("THREE.FileLoader: HTTP Status 0 received."),"undefined"==typeof ReadableStream||void 0===e.body||void 0===e.body.getReader)return e;const n=Qu[t],i=e.body.getReader(),r=e.headers.get("Content-Length")||e.headers.get("X-File-Size"),s=r?parseInt(r):0,a=0!==s;let o=0;const l=new ReadableStream({start(t){!function e(){i.read().then((({done:i,value:r})=>{if(i)t.close();else{o+=r.byteLength;const i=new ProgressEvent("progress",{lengthComputable:a,loaded:o,total:s});for(let t=0,e=n.length;t{switch(o){case"arraybuffer":return t.arrayBuffer();case"blob":return t.blob();case"document":return t.text().then((t=>(new DOMParser).parseFromString(t,a)));case"json":return t.json();default:if(void 0===a)return t.text();{const e=/charset="?([^;"\s]*)"?/i.exec(a),n=e&&e[1]?e[1].toLowerCase():void 0,i=new TextDecoder(n);return t.arrayBuffer().then((t=>i.decode(t)))}}})).then((e=>{Zu.add(t,e);const n=Qu[t];delete Qu[t];for(let t=0,i=n.length;t{const n=Qu[t];if(void 0===n)throw this.manager.itemError(t),e;delete Qu[t];for(let t=0,i=n.length;t{this.manager.itemEnd(t)})),this.manager.itemStart(t)}setResponseType(t){return this.responseType=t,this}setMimeType(t){return this.mimeType=t,this}}class nd extends $u{constructor(t){super(t)}load(t,e,n,i){const r=this,s=new ed(this.manager);s.setPath(this.path),s.setRequestHeader(this.requestHeader),s.setWithCredentials(this.withCredentials),s.load(t,(function(n){try{e(r.parse(JSON.parse(n)))}catch(e){i?i(e):console.error(e),r.manager.itemError(t)}}),n,i)}parse(t){const e=[];for(let n=0;n0:i.vertexColors=t.vertexColors),void 0!==t.uniforms)for(const e in t.uniforms){const r=t.uniforms[e];switch(i.uniforms[e]={},r.type){case"t":i.uniforms[e].value=n(r.value);break;case"c":i.uniforms[e].value=(new Or).setHex(r.value);break;case"v2":i.uniforms[e].value=(new zn).fromArray(r.value);break;case"v3":i.uniforms[e].value=(new gi).fromArray(r.value);break;case"v4":i.uniforms[e].value=(new li).fromArray(r.value);break;case"m3":i.uniforms[e].value=(new Hn).fromArray(r.value);break;case"m4":i.uniforms[e].value=(new ji).fromArray(r.value);break;default:i.uniforms[e].value=r.value}}if(void 0!==t.defines&&(i.defines=t.defines),void 0!==t.vertexShader&&(i.vertexShader=t.vertexShader),void 0!==t.fragmentShader&&(i.fragmentShader=t.fragmentShader),void 0!==t.glslVersion&&(i.glslVersion=t.glslVersion),void 0!==t.extensions)for(const e in t.extensions)i.extensions[e]=t.extensions[e];if(void 0!==t.lights&&(i.lights=t.lights),void 0!==t.clipping&&(i.clipping=t.clipping),void 0!==t.size&&(i.size=t.size),void 0!==t.sizeAttenuation&&(i.sizeAttenuation=t.sizeAttenuation),void 0!==t.map&&(i.map=n(t.map)),void 0!==t.matcap&&(i.matcap=n(t.matcap)),void 0!==t.alphaMap&&(i.alphaMap=n(t.alphaMap)),void 0!==t.bumpMap&&(i.bumpMap=n(t.bumpMap)),void 0!==t.bumpScale&&(i.bumpScale=t.bumpScale),void 0!==t.normalMap&&(i.normalMap=n(t.normalMap)),void 0!==t.normalMapType&&(i.normalMapType=t.normalMapType),void 0!==t.normalScale){let e=t.normalScale;!1===Array.isArray(e)&&(e=[e,e]),i.normalScale=(new zn).fromArray(e)}return void 0!==t.displacementMap&&(i.displacementMap=n(t.displacementMap)),void 0!==t.displacementScale&&(i.displacementScale=t.displacementScale),void 0!==t.displacementBias&&(i.displacementBias=t.displacementBias),void 0!==t.roughnessMap&&(i.roughnessMap=n(t.roughnessMap)),void 0!==t.metalnessMap&&(i.metalnessMap=n(t.metalnessMap)),void 0!==t.emissiveMap&&(i.emissiveMap=n(t.emissiveMap)),void 0!==t.emissiveIntensity&&(i.emissiveIntensity=t.emissiveIntensity),void 0!==t.specularMap&&(i.specularMap=n(t.specularMap)),void 0!==t.specularIntensityMap&&(i.specularIntensityMap=n(t.specularIntensityMap)),void 0!==t.specularColorMap&&(i.specularColorMap=n(t.specularColorMap)),void 0!==t.envMap&&(i.envMap=n(t.envMap)),void 0!==t.envMapIntensity&&(i.envMapIntensity=t.envMapIntensity),void 0!==t.reflectivity&&(i.reflectivity=t.reflectivity),void 0!==t.refractionRatio&&(i.refractionRatio=t.refractionRatio),void 0!==t.lightMap&&(i.lightMap=n(t.lightMap)),void 0!==t.lightMapIntensity&&(i.lightMapIntensity=t.lightMapIntensity),void 0!==t.aoMap&&(i.aoMap=n(t.aoMap)),void 0!==t.aoMapIntensity&&(i.aoMapIntensity=t.aoMapIntensity),void 0!==t.gradientMap&&(i.gradientMap=n(t.gradientMap)),void 0!==t.clearcoatMap&&(i.clearcoatMap=n(t.clearcoatMap)),void 0!==t.clearcoatRoughnessMap&&(i.clearcoatRoughnessMap=n(t.clearcoatRoughnessMap)),void 0!==t.clearcoatNormalMap&&(i.clearcoatNormalMap=n(t.clearcoatNormalMap)),void 0!==t.clearcoatNormalScale&&(i.clearcoatNormalScale=(new zn).fromArray(t.clearcoatNormalScale)),void 0!==t.iridescenceMap&&(i.iridescenceMap=n(t.iridescenceMap)),void 0!==t.iridescenceThicknessMap&&(i.iridescenceThicknessMap=n(t.iridescenceThicknessMap)),void 0!==t.transmissionMap&&(i.transmissionMap=n(t.transmissionMap)),void 0!==t.thicknessMap&&(i.thicknessMap=n(t.thicknessMap)),void 0!==t.anisotropyMap&&(i.anisotropyMap=n(t.anisotropyMap)),void 0!==t.sheenColorMap&&(i.sheenColorMap=n(t.sheenColorMap)),void 0!==t.sheenRoughnessMap&&(i.sheenRoughnessMap=n(t.sheenRoughnessMap)),i}setTextures(t){return this.textures=t,this}static createMaterialFromType(t){return new{ShadowMaterial:_u,SpriteMaterial:Fl,RawShaderMaterial:yu,ShaderMaterial:Ds,PointsMaterial:Fc,MeshPhysicalMaterial:Mu,MeshStandardMaterial:xu,MeshPhongMaterial:Su,MeshToonMaterial:bu,MeshNormalMaterial:Eu,MeshLambertMaterial:Tu,MeshDepthMaterial:ml,MeshDistanceMaterial:fl,MeshBasicMaterial:Br,MeshMatcapMaterial:wu,LineDashedMaterial:Au,LineBasicMaterial:wc,Material:Lr}[t]}}class Rd{static decodeText(t){if("undefined"!=typeof TextDecoder)return(new TextDecoder).decode(t);let e="";for(let n=0,i=t.length;n0){const n=new Ju(e);r=new rd(n),r.setCrossOrigin(this.crossOrigin);for(let e=0,n=t.length;e0){i=new rd(this.manager),i.setCrossOrigin(this.crossOrigin);for(let e=0,i=t.length;e0){this.source.connect(this.filters[0]);for(let t=1,e=this.filters.length;t0){this.source.disconnect(this.filters[0]);for(let t=1,e=this.filters.length;t0&&this._mixBufferRegionAdditive(n,i,this._addIndex*e,1,e);for(let t=e,r=e+e;t!==r;++t)if(n[t]!==n[t+e]){a.setValue(n,i);break}}saveOriginalState(){const t=this.binding,e=this.buffer,n=this.valueSize,i=n*this._origIndex;t.getValue(e,i);for(let t=n,r=i;t!==r;++t)e[t]=e[i+t%n];this._setIdentity(),this.cumulativeWeight=0,this.cumulativeWeightAdditive=0}restoreOriginalState(){const t=3*this.valueSize;this.binding.setValue(this.buffer,t)}_setAdditiveIdentityNumeric(){const t=this._addIndex*this.valueSize,e=t+this.valueSize;for(let n=t;n=.5)for(let i=0;i!==r;++i)t[e+i]=t[n+i]}_slerp(t,e,n,i){fi.slerpFlat(t,e,t,e,t,n,i)}_slerpAdditive(t,e,n,i,r){const s=this._workIndex*r;fi.multiplyQuaternionsFlat(t,s,t,e,t,n),fi.slerpFlat(t,e,t,e,t,s,i)}_lerp(t,e,n,i,r){const s=1-i;for(let a=0;a!==r;++a){const r=e+a;t[r]=t[r]*s+t[n+a]*i}}_lerpAdditive(t,e,n,i,r){for(let s=0;s!==r;++s){const r=e+s;t[r]=t[r]+t[n+s]*i}}}const ap="\\[\\]\\.:\\/",op=new RegExp("["+ap+"]","g"),lp="[^"+ap+"]",cp="[^"+ap.replace("\\.","")+"]",hp=new RegExp("^"+/((?:WC+[\/:])*)/.source.replace("WC",lp)+/(WCOD+)?/.source.replace("WCOD",cp)+/(?:\.(WC+)(?:\[(.+)\])?)?/.source.replace("WC",lp)+/\.(WC+)(?:\[(.+)\])?/.source.replace("WC",lp)+"$"),up=["material","materials","bones","map"];class dp{constructor(t,e,n){this.path=e,this.parsedPath=n||dp.parseTrackName(e),this.node=dp.findNode(t,this.parsedPath.nodeName),this.rootNode=t,this.getValue=this._getValue_unbound,this.setValue=this._setValue_unbound}static create(t,e,n){return t&&t.isAnimationObjectGroup?new dp.Composite(t,e,n):new dp(t,e,n)}static sanitizeNodeName(t){return t.replace(/\s/g,"_").replace(op,"")}static parseTrackName(t){const e=hp.exec(t);if(null===e)throw new Error("PropertyBinding: Cannot parse trackName: "+t);const n={nodeName:e[2],objectName:e[3],objectIndex:e[4],propertyName:e[5],propertyIndex:e[6]},i=n.nodeName&&n.nodeName.lastIndexOf(".");if(void 0!==i&&-1!==i){const t=n.nodeName.substring(i+1);-1!==up.indexOf(t)&&(n.nodeName=n.nodeName.substring(0,i),n.objectName=t)}if(null===n.propertyName||0===n.propertyName.length)throw new Error("PropertyBinding: can not parse propertyName from trackName: "+t);return n}static findNode(t,e){if(void 0===e||""===e||"."===e||-1===e||e===t.name||e===t.uuid)return t;if(t.skeleton){const n=t.skeleton.getBoneByName(e);if(void 0!==n)return n}if(t.children){const n=function(t){for(let i=0;i=r){const s=r++,c=t[s];e[c.uuid]=l,t[l]=c,e[o]=s,t[s]=a;for(let t=0,e=i;t!==e;++t){const e=n[t],i=e[s],r=e[l];e[l]=i,e[s]=r}}}this.nCachedObjects_=r}uncache(){const t=this._objects,e=this._indicesByUUID,n=this._bindings,i=n.length;let r=this.nCachedObjects_,s=t.length;for(let a=0,o=arguments.length;a!==o;++a){const o=arguments[a].uuid,l=e[o];if(void 0!==l)if(delete e[o],l0&&(e[a.uuid]=l),t[l]=a,t.pop();for(let t=0,e=i;t!==e;++t){const e=n[t];e[l]=e[r],e.pop()}}}this.nCachedObjects_=r}subscribe_(t,e){const n=this._bindingsIndicesByPath;let i=n[t];const r=this._bindings;if(void 0!==i)return r[i];const s=this._paths,a=this._parsedPaths,o=this._objects,l=o.length,c=this.nCachedObjects_,h=new Array(l);i=r.length,n[t]=i,s.push(t),a.push(e),r.push(h);for(let n=c,i=o.length;n!==i;++n){const i=o[n];h[n]=new dp(i,t,e)}return h}unsubscribe_(t){const e=this._bindingsIndicesByPath,n=e[t];if(void 0!==n){const i=this._paths,r=this._parsedPaths,s=this._bindings,a=s.length-1,o=s[a];e[t[a]]=n,s[n]=o,s.pop(),r[n]=r[a],r.pop(),i[n]=i[a],i.pop()}}}class mp{constructor(t,e,n=null,i=e.blendMode){this._mixer=t,this._clip=e,this._localRoot=n,this.blendMode=i;const r=e.tracks,s=r.length,a=new Array(s),o={endingStart:be,endingEnd:be};for(let t=0;t!==s;++t){const e=r[t].createInterpolant(null);a[t]=e,e.settings=o}this._interpolantSettings=o,this._interpolants=a,this._propertyBindings=new Array(s),this._cacheIndex=null,this._byClipCacheIndex=null,this._timeScaleInterpolant=null,this._weightInterpolant=null,this.loop=2201,this._loopCount=-1,this._startTime=null,this.time=0,this.timeScale=1,this._effectiveTimeScale=1,this.weight=1,this._effectiveWeight=1,this.repetitions=1/0,this.paused=!1,this.enabled=!0,this.clampWhenFinished=!1,this.zeroSlopeAtStart=!0,this.zeroSlopeAtEnd=!0}play(){return this._mixer._activateAction(this),this}stop(){return this._mixer._deactivateAction(this),this.reset()}reset(){return this.paused=!1,this.enabled=!0,this.time=0,this._loopCount=-1,this._startTime=null,this.stopFading().stopWarping()}isRunning(){return this.enabled&&!this.paused&&0!==this.timeScale&&null===this._startTime&&this._mixer._isActiveAction(this)}isScheduled(){return this._mixer._isActiveAction(this)}startAt(t){return this._startTime=t,this}setLoop(t,e){return this.loop=t,this.repetitions=e,this}setEffectiveWeight(t){return this.weight=t,this._effectiveWeight=this.enabled?t:0,this.stopFading()}getEffectiveWeight(){return this._effectiveWeight}fadeIn(t){return this._scheduleFading(t,0,1)}fadeOut(t){return this._scheduleFading(t,1,0)}crossFadeFrom(t,e,n){if(t.fadeOut(e),this.fadeIn(e),n){const n=this._clip.duration,i=t._clip.duration,r=i/n,s=n/i;t.warp(1,r,e),this.warp(s,1,e)}return this}crossFadeTo(t,e,n){return t.crossFadeFrom(this,e,n)}stopFading(){const t=this._weightInterpolant;return null!==t&&(this._weightInterpolant=null,this._mixer._takeBackControlInterpolant(t)),this}setEffectiveTimeScale(t){return this.timeScale=t,this._effectiveTimeScale=this.paused?0:t,this.stopWarping()}getEffectiveTimeScale(){return this._effectiveTimeScale}setDuration(t){return this.timeScale=this._clip.duration/t,this.stopWarping()}syncWith(t){return this.time=t.time,this.timeScale=t.timeScale,this.stopWarping()}halt(t){return this.warp(this._effectiveTimeScale,0,t)}warp(t,e,n){const i=this._mixer,r=i.time,s=this.timeScale;let a=this._timeScaleInterpolant;null===a&&(a=i._lendControlInterpolant(),this._timeScaleInterpolant=a);const o=a.parameterPositions,l=a.sampleValues;return o[0]=r,o[1]=r+n,l[0]=t/s,l[1]=e/s,this}stopWarping(){const t=this._timeScaleInterpolant;return null!==t&&(this._timeScaleInterpolant=null,this._mixer._takeBackControlInterpolant(t)),this}getMixer(){return this._mixer}getClip(){return this._clip}getRoot(){return this._localRoot||this._mixer._root}_update(t,e,n,i){if(!this.enabled)return void this._updateWeight(t);const r=this._startTime;if(null!==r){const i=(t-r)*n;i<0||0===n?e=0:(this._startTime=null,e=n*i)}e*=this._updateTimeScale(t);const s=this._updateTime(e),a=this._updateWeight(t);if(a>0){const t=this._interpolants,e=this._propertyBindings;if(this.blendMode===Ae)for(let n=0,i=t.length;n!==i;++n)t[n].evaluate(s),e[n].accumulateAdditive(a);else for(let n=0,r=t.length;n!==r;++n)t[n].evaluate(s),e[n].accumulate(i,a)}}_updateWeight(t){let e=0;if(this.enabled){e=this.weight;const n=this._weightInterpolant;if(null!==n){const i=n.evaluate(t)[0];e*=i,t>n.parameterPositions[1]&&(this.stopFading(),0===i&&(this.enabled=!1))}}return this._effectiveWeight=e,e}_updateTimeScale(t){let e=0;if(!this.paused){e=this.timeScale;const n=this._timeScaleInterpolant;if(null!==n){e*=n.evaluate(t)[0],t>n.parameterPositions[1]&&(this.stopWarping(),0===e?this.paused=!0:this.timeScale=e)}}return this._effectiveTimeScale=e,e}_updateTime(t){const e=this._clip.duration,n=this.loop;let i=this.time+t,r=this._loopCount;const s=2202===n;if(0===t)return-1===r?i:s&&1==(1&r)?e-i:i;if(2200===n){-1===r&&(this._loopCount=0,this._setEndings(!0,!0,!1));t:{if(i>=e)i=e;else{if(!(i<0)){this.time=i;break t}i=0}this.clampWhenFinished?this.paused=!0:this.enabled=!1,this.time=i,this._mixer.dispatchEvent({type:"finished",action:this,direction:t<0?-1:1})}}else{if(-1===r&&(t>=0?(r=0,this._setEndings(!0,0===this.repetitions,s)):this._setEndings(0===this.repetitions,!0,s)),i>=e||i<0){const n=Math.floor(i/e);i-=e*n,r+=Math.abs(n);const a=this.repetitions-r;if(a<=0)this.clampWhenFinished?this.paused=!0:this.enabled=!1,i=t>0?e:0,this.time=i,this._mixer.dispatchEvent({type:"finished",action:this,direction:t>0?1:-1});else{if(1===a){const e=t<0;this._setEndings(e,!e,s)}else this._setEndings(!1,!1,s);this._loopCount=r,this.time=i,this._mixer.dispatchEvent({type:"loop",action:this,loopDelta:n})}}else this.time=i;if(s&&1==(1&r))return e-i}return i}_setEndings(t,e,n){const i=this._interpolantSettings;n?(i.endingStart=Ee,i.endingEnd=Ee):(i.endingStart=t?this.zeroSlopeAtStart?Ee:be:Te,i.endingEnd=e?this.zeroSlopeAtEnd?Ee:be:Te)}_scheduleFading(t,e,n){const i=this._mixer,r=i.time;let s=this._weightInterpolant;null===s&&(s=i._lendControlInterpolant(),this._weightInterpolant=s);const a=s.parameterPositions,o=s.sampleValues;return a[0]=r,o[0]=e,a[1]=r+t,o[1]=n,this}}const fp=new Float32Array(1);class gp extends En{constructor(t){super(),this._root=t,this._initMemoryManager(),this._accuIndex=0,this.time=0,this.timeScale=1}_bindAction(t,e){const n=t._localRoot||this._root,i=t._clip.tracks,r=i.length,s=t._propertyBindings,a=t._interpolants,o=n.uuid,l=this._bindingsByRootAndName;let c=l[o];void 0===c&&(c={},l[o]=c);for(let t=0;t!==r;++t){const r=i[t],l=r.name;let h=c[l];if(void 0!==h)++h.referenceCount,s[t]=h;else{if(h=s[t],void 0!==h){null===h._cacheIndex&&(++h.referenceCount,this._addInactiveBinding(h,o,l));continue}const i=e&&e._propertyBindings[t].binding.parsedPath;h=new sp(dp.create(n,l,i),r.ValueTypeName,r.getValueSize()),++h.referenceCount,this._addInactiveBinding(h,o,l),s[t]=h}a[t].resultBuffer=h.buffer}}_activateAction(t){if(!this._isActiveAction(t)){if(null===t._cacheIndex){const e=(t._localRoot||this._root).uuid,n=t._clip.uuid,i=this._actionsByClip[n];this._bindAction(t,i&&i.knownActions[0]),this._addInactiveAction(t,n,e)}const e=t._propertyBindings;for(let t=0,n=e.length;t!==n;++t){const n=e[t];0==n.useCount++&&(this._lendBinding(n),n.saveOriginalState())}this._lendAction(t)}}_deactivateAction(t){if(this._isActiveAction(t)){const e=t._propertyBindings;for(let t=0,n=e.length;t!==n;++t){const n=e[t];0==--n.useCount&&(n.restoreOriginalState(),this._takeBackBinding(n))}this._takeBackAction(t)}}_initMemoryManager(){this._actions=[],this._nActiveActions=0,this._actionsByClip={},this._bindings=[],this._nActiveBindings=0,this._bindingsByRootAndName={},this._controlInterpolants=[],this._nActiveControlInterpolants=0;const t=this;this.stats={actions:{get total(){return t._actions.length},get inUse(){return t._nActiveActions}},bindings:{get total(){return t._bindings.length},get inUse(){return t._nActiveBindings}},controlInterpolants:{get total(){return t._controlInterpolants.length},get inUse(){return t._nActiveControlInterpolants}}}}_isActiveAction(t){const e=t._cacheIndex;return null!==e&&e=0;--e)t[e].stop();return this}update(t){t*=this.timeScale;const e=this._actions,n=this._nActiveActions,i=this.time+=t,r=Math.sign(t),s=this._accuIndex^=1;for(let a=0;a!==n;++a){e[a]._update(i,t,r,s)}const a=this._bindings,o=this._nActiveBindings;for(let t=0;t!==o;++t)a[t].apply(s);return this}setTime(t){this.time=0;for(let t=0;tthis.max.x||t.ythis.max.y)}containsBox(t){return this.min.x<=t.min.x&&t.max.x<=this.max.x&&this.min.y<=t.min.y&&t.max.y<=this.max.y}getParameter(t,e){return e.set((t.x-this.min.x)/(this.max.x-this.min.x),(t.y-this.min.y)/(this.max.y-this.min.y))}intersectsBox(t){return!(t.max.xthis.max.x||t.max.ythis.max.y)}clampPoint(t,e){return e.copy(t).clamp(this.min,this.max)}distanceToPoint(t){return this.clampPoint(t,Ap).distanceTo(t)}intersect(t){return this.min.max(t.min),this.max.min(t.max),this.isEmpty()&&this.makeEmpty(),this}union(t){return this.min.min(t.min),this.max.max(t.max),this}translate(t){return this.min.add(t),this.max.add(t),this}equals(t){return t.min.equals(this.min)&&t.max.equals(this.max)}}const Cp=new gi,Pp=new gi;class Lp{constructor(t=new gi,e=new gi){this.start=t,this.end=e}set(t,e){return this.start.copy(t),this.end.copy(e),this}copy(t){return this.start.copy(t.start),this.end.copy(t.end),this}getCenter(t){return t.addVectors(this.start,this.end).multiplyScalar(.5)}delta(t){return t.subVectors(this.end,this.start)}distanceSq(){return this.start.distanceToSquared(this.end)}distance(){return this.start.distanceTo(this.end)}at(t,e){return this.delta(e).multiplyScalar(t).add(this.start)}closestPointToPointParameter(t,e){Cp.subVectors(t,this.start),Pp.subVectors(this.end,this.start);const n=Pp.dot(Pp);let i=Pp.dot(Cp)/n;return e&&(i=Pn(i,0,1)),i}closestPointToPoint(t,e,n){const i=this.closestPointToPointParameter(t,e);return this.delta(n).multiplyScalar(i).add(this.start)}applyMatrix4(t){return this.start.applyMatrix4(t),this.end.applyMatrix4(t),this}equals(t){return t.start.equals(this.start)&&t.end.equals(this.end)}clone(){return(new this.constructor).copy(this)}}const Ip=new gi;class Up extends vr{constructor(t,e){super(),this.light=t,this.matrix=t.matrixWorld,this.matrixAutoUpdate=!1,this.color=e,this.type="SpotLightHelper";const n=new hs,i=[0,0,0,0,0,1,0,0,0,1,0,1,0,0,0,-1,0,1,0,0,0,0,1,1,0,0,0,0,-1,1];for(let t=0,e=1,n=32;t1)for(let n=0;n.99999)this.quaternion.set(0,0,0,1);else if(t.y<-.99999)this.quaternion.set(1,0,0,0);else{rm.set(t.z,0,-t.x).normalize();const e=Math.acos(t.y);this.quaternion.setFromAxisAngle(rm,e)}}setLength(t,e=.2*t,n=.2*e){this.line.scale.set(1,Math.max(1e-4,t-e),1),this.line.updateMatrix(),this.cone.scale.set(n,e,n),this.cone.position.y=t,this.cone.updateMatrix()}setColor(t){this.line.material.color.set(t),this.cone.material.color.set(t)}copy(t){return super.copy(t,!1),this.line.copy(t.line),this.cone.copy(t.cone),this}dispose(){this.line.geometry.dispose(),this.line.material.dispose(),this.cone.geometry.dispose(),this.cone.material.dispose()}}class lm extends Dc{constructor(t=1){const e=[0,0,0,t,0,0,0,0,0,0,t,0,0,0,0,0,0,t],n=new hs;n.setAttribute("position",new es(e,3)),n.setAttribute("color",new es([1,0,0,1,.6,0,0,1,0,.6,1,0,0,0,1,0,.6,1],3));super(n,new wc({vertexColors:!0,toneMapped:!1})),this.type="AxesHelper"}setColors(t,e,n){const i=new Or,r=this.geometry.attributes.color.array;return i.set(t),i.toArray(r,0),i.toArray(r,3),i.set(e),i.toArray(r,6),i.toArray(r,9),i.set(n),i.toArray(r,12),i.toArray(r,15),this.geometry.attributes.color.needsUpdate=!0,this}dispose(){this.geometry.dispose(),this.material.dispose()}}class cm{constructor(){this.type="ShapePath",this.color=new Or,this.subPaths=[],this.currentPath=null}moveTo(t,e){return this.currentPath=new gh,this.subPaths.push(this.currentPath),this.currentPath.moveTo(t,e),this}lineTo(t,e){return this.currentPath.lineTo(t,e),this}quadraticCurveTo(t,e,n,i){return this.currentPath.quadraticCurveTo(t,e,n,i),this}bezierCurveTo(t,e,n,i,r,s){return this.currentPath.bezierCurveTo(t,e,n,i,r,s),this}splineThru(t){return this.currentPath.splineThru(t),this}toShapes(t){function e(t,e){const n=e.length;let i=!1;for(let r=n-1,s=0;sNumber.EPSILON){if(l<0&&(n=e[s],o=-o,a=e[r],l=-l),t.ya.y)continue;if(t.y===n.y){if(t.x===n.x)return!0}else{const e=l*(t.x-n.x)-o*(t.y-n.y);if(0===e)return!0;if(e<0)continue;i=!i}}else{if(t.y!==n.y)continue;if(a.x<=t.x&&t.x<=n.x||n.x<=t.x&&t.x<=a.x)return!0}}return i}const n=eu.isClockWise,i=this.subPaths;if(0===i.length)return[];let r,s,a;const o=[];if(1===i.length)return s=i[0],a=new Ch,a.curves=s.curves,o.push(a),o;let l=!n(i[0].getPoints());l=t?!l:l;const c=[],h=[];let u,d,p=[],m=0;h[m]=void 0,p[m]=[];for(let e=0,a=i.length;e1){let t=!1,n=0;for(let t=0,e=h.length;t0&&!1===t&&(p=c)}for(let t=0,e=h.length;te.start-t.start);let t=0;for(let e=1;e 0\n\tvec4 plane;\n\t#ifdef ALPHA_TO_COVERAGE\n\t\tfloat distanceToPlane, distanceGradient;\n\t\tfloat clipOpacity = 1.0;\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) {\n\t\t\tplane = clippingPlanes[ i ];\n\t\t\tdistanceToPlane = - dot( vClipPosition, plane.xyz ) + plane.w;\n\t\t\tdistanceGradient = fwidth( distanceToPlane ) / 2.0;\n\t\t\tclipOpacity *= smoothstep( - distanceGradient, distanceGradient, distanceToPlane );\n\t\t\tif ( clipOpacity == 0.0 ) discard;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t\t#if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES\n\t\t\tfloat unionClipOpacity = 1.0;\n\t\t\t#pragma unroll_loop_start\n\t\t\tfor ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) {\n\t\t\t\tplane = clippingPlanes[ i ];\n\t\t\t\tdistanceToPlane = - dot( vClipPosition, plane.xyz ) + plane.w;\n\t\t\t\tdistanceGradient = fwidth( distanceToPlane ) / 2.0;\n\t\t\t\tunionClipOpacity *= 1.0 - smoothstep( - distanceGradient, distanceGradient, distanceToPlane );\n\t\t\t}\n\t\t\t#pragma unroll_loop_end\n\t\t\tclipOpacity *= 1.0 - unionClipOpacity;\n\t\t#endif\n\t\tdiffuseColor.a *= clipOpacity;\n\t\tif ( diffuseColor.a == 0.0 ) discard;\n\t#else\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) {\n\t\t\tplane = clippingPlanes[ i ];\n\t\t\tif ( dot( vClipPosition, plane.xyz ) > plane.w ) discard;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t\t#if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES\n\t\t\tbool clipped = true;\n\t\t\t#pragma unroll_loop_start\n\t\t\tfor ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) {\n\t\t\t\tplane = clippingPlanes[ i ];\n\t\t\t\tclipped = ( dot( vClipPosition, plane.xyz ) > plane.w ) && clipped;\n\t\t\t}\n\t\t\t#pragma unroll_loop_end\n\t\t\tif ( clipped ) discard;\n\t\t#endif\n\t#endif\n#endif",clipping_planes_pars_fragment:"#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n\tuniform vec4 clippingPlanes[ NUM_CLIPPING_PLANES ];\n#endif",clipping_planes_pars_vertex:"#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n#endif",clipping_planes_vertex:"#if NUM_CLIPPING_PLANES > 0\n\tvClipPosition = - mvPosition.xyz;\n#endif",color_fragment:"#if defined( USE_COLOR_ALPHA )\n\tdiffuseColor *= vColor;\n#elif defined( USE_COLOR )\n\tdiffuseColor.rgb *= vColor;\n#endif",color_pars_fragment:"#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR )\n\tvarying vec3 vColor;\n#endif",color_pars_vertex:"#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR ) || defined( USE_BATCHING_COLOR )\n\tvarying vec3 vColor;\n#endif",color_vertex:"#if defined( USE_COLOR_ALPHA )\n\tvColor = vec4( 1.0 );\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR ) || defined( USE_BATCHING_COLOR )\n\tvColor = vec3( 1.0 );\n#endif\n#ifdef USE_COLOR\n\tvColor *= color;\n#endif\n#ifdef USE_INSTANCING_COLOR\n\tvColor.xyz *= instanceColor.xyz;\n#endif\n#ifdef USE_BATCHING_COLOR\n\tvec3 batchingColor = getBatchingColor( getIndirectIndex( gl_DrawID ) );\n\tvColor.xyz *= batchingColor.xyz;\n#endif",common:"#define PI 3.141592653589793\n#define PI2 6.283185307179586\n#define PI_HALF 1.5707963267948966\n#define RECIPROCAL_PI 0.3183098861837907\n#define RECIPROCAL_PI2 0.15915494309189535\n#define EPSILON 1e-6\n#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\n#define whiteComplement( a ) ( 1.0 - saturate( a ) )\nfloat pow2( const in float x ) { return x*x; }\nvec3 pow2( const in vec3 x ) { return x*x; }\nfloat pow3( const in float x ) { return x*x*x; }\nfloat pow4( const in float x ) { float x2 = x*x; return x2*x2; }\nfloat max3( const in vec3 v ) { return max( max( v.x, v.y ), v.z ); }\nfloat average( const in vec3 v ) { return dot( v, vec3( 0.3333333 ) ); }\nhighp float rand( const in vec2 uv ) {\n\tconst highp float a = 12.9898, b = 78.233, c = 43758.5453;\n\thighp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI );\n\treturn fract( sin( sn ) * c );\n}\n#ifdef HIGH_PRECISION\n\tfloat precisionSafeLength( vec3 v ) { return length( v ); }\n#else\n\tfloat precisionSafeLength( vec3 v ) {\n\t\tfloat maxComponent = max3( abs( v ) );\n\t\treturn length( v / maxComponent ) * maxComponent;\n\t}\n#endif\nstruct IncidentLight {\n\tvec3 color;\n\tvec3 direction;\n\tbool visible;\n};\nstruct ReflectedLight {\n\tvec3 directDiffuse;\n\tvec3 directSpecular;\n\tvec3 indirectDiffuse;\n\tvec3 indirectSpecular;\n};\n#ifdef USE_ALPHAHASH\n\tvarying vec3 vPosition;\n#endif\nvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n}\nvec3 inverseTransformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( vec4( dir, 0.0 ) * matrix ).xyz );\n}\nmat3 transposeMat3( const in mat3 m ) {\n\tmat3 tmp;\n\ttmp[ 0 ] = vec3( m[ 0 ].x, m[ 1 ].x, m[ 2 ].x );\n\ttmp[ 1 ] = vec3( m[ 0 ].y, m[ 1 ].y, m[ 2 ].y );\n\ttmp[ 2 ] = vec3( m[ 0 ].z, m[ 1 ].z, m[ 2 ].z );\n\treturn tmp;\n}\nbool isPerspectiveMatrix( mat4 m ) {\n\treturn m[ 2 ][ 3 ] == - 1.0;\n}\nvec2 equirectUv( in vec3 dir ) {\n\tfloat u = atan( dir.z, dir.x ) * RECIPROCAL_PI2 + 0.5;\n\tfloat v = asin( clamp( dir.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\n\treturn vec2( u, v );\n}\nvec3 BRDF_Lambert( const in vec3 diffuseColor ) {\n\treturn RECIPROCAL_PI * diffuseColor;\n}\nvec3 F_Schlick( const in vec3 f0, const in float f90, const in float dotVH ) {\n\tfloat fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );\n\treturn f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );\n}\nfloat F_Schlick( const in float f0, const in float f90, const in float dotVH ) {\n\tfloat fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );\n\treturn f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );\n} // validated",cube_uv_reflection_fragment:"#ifdef ENVMAP_TYPE_CUBE_UV\n\t#define cubeUV_minMipLevel 4.0\n\t#define cubeUV_minTileSize 16.0\n\tfloat getFace( vec3 direction ) {\n\t\tvec3 absDirection = abs( direction );\n\t\tfloat face = - 1.0;\n\t\tif ( absDirection.x > absDirection.z ) {\n\t\t\tif ( absDirection.x > absDirection.y )\n\t\t\t\tface = direction.x > 0.0 ? 0.0 : 3.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t} else {\n\t\t\tif ( absDirection.z > absDirection.y )\n\t\t\t\tface = direction.z > 0.0 ? 2.0 : 5.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t}\n\t\treturn face;\n\t}\n\tvec2 getUV( vec3 direction, float face ) {\n\t\tvec2 uv;\n\t\tif ( face == 0.0 ) {\n\t\t\tuv = vec2( direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 1.0 ) {\n\t\t\tuv = vec2( - direction.x, - direction.z ) / abs( direction.y );\n\t\t} else if ( face == 2.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.y ) / abs( direction.z );\n\t\t} else if ( face == 3.0 ) {\n\t\t\tuv = vec2( - direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 4.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.z ) / abs( direction.y );\n\t\t} else {\n\t\t\tuv = vec2( direction.x, direction.y ) / abs( direction.z );\n\t\t}\n\t\treturn 0.5 * ( uv + 1.0 );\n\t}\n\tvec3 bilinearCubeUV( sampler2D envMap, vec3 direction, float mipInt ) {\n\t\tfloat face = getFace( direction );\n\t\tfloat filterInt = max( cubeUV_minMipLevel - mipInt, 0.0 );\n\t\tmipInt = max( mipInt, cubeUV_minMipLevel );\n\t\tfloat faceSize = exp2( mipInt );\n\t\thighp vec2 uv = getUV( direction, face ) * ( faceSize - 2.0 ) + 1.0;\n\t\tif ( face > 2.0 ) {\n\t\t\tuv.y += faceSize;\n\t\t\tface -= 3.0;\n\t\t}\n\t\tuv.x += face * faceSize;\n\t\tuv.x += filterInt * 3.0 * cubeUV_minTileSize;\n\t\tuv.y += 4.0 * ( exp2( CUBEUV_MAX_MIP ) - faceSize );\n\t\tuv.x *= CUBEUV_TEXEL_WIDTH;\n\t\tuv.y *= CUBEUV_TEXEL_HEIGHT;\n\t\t#ifdef texture2DGradEXT\n\t\t\treturn texture2DGradEXT( envMap, uv, vec2( 0.0 ), vec2( 0.0 ) ).rgb;\n\t\t#else\n\t\t\treturn texture2D( envMap, uv ).rgb;\n\t\t#endif\n\t}\n\t#define cubeUV_r0 1.0\n\t#define cubeUV_m0 - 2.0\n\t#define cubeUV_r1 0.8\n\t#define cubeUV_m1 - 1.0\n\t#define cubeUV_r4 0.4\n\t#define cubeUV_m4 2.0\n\t#define cubeUV_r5 0.305\n\t#define cubeUV_m5 3.0\n\t#define cubeUV_r6 0.21\n\t#define cubeUV_m6 4.0\n\tfloat roughnessToMip( float roughness ) {\n\t\tfloat mip = 0.0;\n\t\tif ( roughness >= cubeUV_r1 ) {\n\t\t\tmip = ( cubeUV_r0 - roughness ) * ( cubeUV_m1 - cubeUV_m0 ) / ( cubeUV_r0 - cubeUV_r1 ) + cubeUV_m0;\n\t\t} else if ( roughness >= cubeUV_r4 ) {\n\t\t\tmip = ( cubeUV_r1 - roughness ) * ( cubeUV_m4 - cubeUV_m1 ) / ( cubeUV_r1 - cubeUV_r4 ) + cubeUV_m1;\n\t\t} else if ( roughness >= cubeUV_r5 ) {\n\t\t\tmip = ( cubeUV_r4 - roughness ) * ( cubeUV_m5 - cubeUV_m4 ) / ( cubeUV_r4 - cubeUV_r5 ) + cubeUV_m4;\n\t\t} else if ( roughness >= cubeUV_r6 ) {\n\t\t\tmip = ( cubeUV_r5 - roughness ) * ( cubeUV_m6 - cubeUV_m5 ) / ( cubeUV_r5 - cubeUV_r6 ) + cubeUV_m5;\n\t\t} else {\n\t\t\tmip = - 2.0 * log2( 1.16 * roughness );\t\t}\n\t\treturn mip;\n\t}\n\tvec4 textureCubeUV( sampler2D envMap, vec3 sampleDir, float roughness ) {\n\t\tfloat mip = clamp( roughnessToMip( roughness ), cubeUV_m0, CUBEUV_MAX_MIP );\n\t\tfloat mipF = fract( mip );\n\t\tfloat mipInt = floor( mip );\n\t\tvec3 color0 = bilinearCubeUV( envMap, sampleDir, mipInt );\n\t\tif ( mipF == 0.0 ) {\n\t\t\treturn vec4( color0, 1.0 );\n\t\t} else {\n\t\t\tvec3 color1 = bilinearCubeUV( envMap, sampleDir, mipInt + 1.0 );\n\t\t\treturn vec4( mix( color0, color1, mipF ), 1.0 );\n\t\t}\n\t}\n#endif",defaultnormal_vertex:"vec3 transformedNormal = objectNormal;\n#ifdef USE_TANGENT\n\tvec3 transformedTangent = objectTangent;\n#endif\n#ifdef USE_BATCHING\n\tmat3 bm = mat3( batchingMatrix );\n\ttransformedNormal /= vec3( dot( bm[ 0 ], bm[ 0 ] ), dot( bm[ 1 ], bm[ 1 ] ), dot( bm[ 2 ], bm[ 2 ] ) );\n\ttransformedNormal = bm * transformedNormal;\n\t#ifdef USE_TANGENT\n\t\ttransformedTangent = bm * transformedTangent;\n\t#endif\n#endif\n#ifdef USE_INSTANCING\n\tmat3 im = mat3( instanceMatrix );\n\ttransformedNormal /= vec3( dot( im[ 0 ], im[ 0 ] ), dot( im[ 1 ], im[ 1 ] ), dot( im[ 2 ], im[ 2 ] ) );\n\ttransformedNormal = im * transformedNormal;\n\t#ifdef USE_TANGENT\n\t\ttransformedTangent = im * transformedTangent;\n\t#endif\n#endif\ntransformedNormal = normalMatrix * transformedNormal;\n#ifdef FLIP_SIDED\n\ttransformedNormal = - transformedNormal;\n#endif\n#ifdef USE_TANGENT\n\ttransformedTangent = ( modelViewMatrix * vec4( transformedTangent, 0.0 ) ).xyz;\n\t#ifdef FLIP_SIDED\n\t\ttransformedTangent = - transformedTangent;\n\t#endif\n#endif",displacementmap_pars_vertex:"#ifdef USE_DISPLACEMENTMAP\n\tuniform sampler2D displacementMap;\n\tuniform float displacementScale;\n\tuniform float displacementBias;\n#endif",displacementmap_vertex:"#ifdef USE_DISPLACEMENTMAP\n\ttransformed += normalize( objectNormal ) * ( texture2D( displacementMap, vDisplacementMapUv ).x * displacementScale + displacementBias );\n#endif",emissivemap_fragment:"#ifdef USE_EMISSIVEMAP\n\tvec4 emissiveColor = texture2D( emissiveMap, vEmissiveMapUv );\n\t#ifdef DECODE_VIDEO_TEXTURE_EMISSIVE\n\t\temissiveColor = sRGBTransferEOTF( emissiveColor );\n\t#endif\n\ttotalEmissiveRadiance *= emissiveColor.rgb;\n#endif",emissivemap_pars_fragment:"#ifdef USE_EMISSIVEMAP\n\tuniform sampler2D emissiveMap;\n#endif",colorspace_fragment:"gl_FragColor = linearToOutputTexel( gl_FragColor );",colorspace_pars_fragment:"vec4 LinearTransferOETF( in vec4 value ) {\n\treturn value;\n}\nvec4 sRGBTransferEOTF( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), value.rgb * 0.0773993808, vec3( lessThanEqual( value.rgb, vec3( 0.04045 ) ) ) ), value.a );\n}\nvec4 sRGBTransferOETF( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a );\n}",envmap_fragment:"#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvec3 cameraToFrag;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToFrag = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToFrag = normalize( vWorldPosition - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvec3 reflectVec = reflect( cameraToFrag, worldNormal );\n\t\t#else\n\t\t\tvec3 reflectVec = refract( cameraToFrag, worldNormal, refractionRatio );\n\t\t#endif\n\t#else\n\t\tvec3 reflectVec = vReflect;\n\t#endif\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 envColor = textureCube( envMap, envMapRotation * vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );\n\t#else\n\t\tvec4 envColor = vec4( 0.0 );\n\t#endif\n\t#ifdef ENVMAP_BLENDING_MULTIPLY\n\t\toutgoingLight = mix( outgoingLight, outgoingLight * envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_MIX )\n\t\toutgoingLight = mix( outgoingLight, envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_ADD )\n\t\toutgoingLight += envColor.xyz * specularStrength * reflectivity;\n\t#endif\n#endif",envmap_common_pars_fragment:"#ifdef USE_ENVMAP\n\tuniform float envMapIntensity;\n\tuniform float flipEnvMap;\n\tuniform mat3 envMapRotation;\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tuniform samplerCube envMap;\n\t#else\n\t\tuniform sampler2D envMap;\n\t#endif\n\t\n#endif",envmap_pars_fragment:"#ifdef USE_ENVMAP\n\tuniform float reflectivity;\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( LAMBERT )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\tvarying vec3 vWorldPosition;\n\t\tuniform float refractionRatio;\n\t#else\n\t\tvarying vec3 vReflect;\n\t#endif\n#endif",envmap_pars_vertex:"#ifdef USE_ENVMAP\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( LAMBERT )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\t\n\t\tvarying vec3 vWorldPosition;\n\t#else\n\t\tvarying vec3 vReflect;\n\t\tuniform float refractionRatio;\n\t#endif\n#endif",envmap_physical_pars_fragment:"#ifdef USE_ENVMAP\n\tvec3 getIBLIrradiance( const in vec3 normal ) {\n\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, envMapRotation * worldNormal, 1.0 );\n\t\t\treturn PI * envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n\tvec3 getIBLRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness ) {\n\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\tvec3 reflectVec = reflect( - viewDir, normal );\n\t\t\treflectVec = normalize( mix( reflectVec, normal, roughness * roughness) );\n\t\t\treflectVec = inverseTransformDirection( reflectVec, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, envMapRotation * reflectVec, roughness );\n\t\t\treturn envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n\t#ifdef USE_ANISOTROPY\n\t\tvec3 getIBLAnisotropyRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness, const in vec3 bitangent, const in float anisotropy ) {\n\t\t\t#ifdef ENVMAP_TYPE_CUBE_UV\n\t\t\t\tvec3 bentNormal = cross( bitangent, viewDir );\n\t\t\t\tbentNormal = normalize( cross( bentNormal, bitangent ) );\n\t\t\t\tbentNormal = normalize( mix( bentNormal, normal, pow2( pow2( 1.0 - anisotropy * ( 1.0 - roughness ) ) ) ) );\n\t\t\t\treturn getIBLRadiance( viewDir, bentNormal, roughness );\n\t\t\t#else\n\t\t\t\treturn vec3( 0.0 );\n\t\t\t#endif\n\t\t}\n\t#endif\n#endif",envmap_vertex:"#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvWorldPosition = worldPosition.xyz;\n\t#else\n\t\tvec3 cameraToVertex;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToVertex = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToVertex = normalize( worldPosition.xyz - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvReflect = reflect( cameraToVertex, worldNormal );\n\t\t#else\n\t\t\tvReflect = refract( cameraToVertex, worldNormal, refractionRatio );\n\t\t#endif\n\t#endif\n#endif",fog_vertex:"#ifdef USE_FOG\n\tvFogDepth = - mvPosition.z;\n#endif",fog_pars_vertex:"#ifdef USE_FOG\n\tvarying float vFogDepth;\n#endif",fog_fragment:"#ifdef USE_FOG\n\t#ifdef FOG_EXP2\n\t\tfloat fogFactor = 1.0 - exp( - fogDensity * fogDensity * vFogDepth * vFogDepth );\n\t#else\n\t\tfloat fogFactor = smoothstep( fogNear, fogFar, vFogDepth );\n\t#endif\n\tgl_FragColor.rgb = mix( gl_FragColor.rgb, fogColor, fogFactor );\n#endif",fog_pars_fragment:"#ifdef USE_FOG\n\tuniform vec3 fogColor;\n\tvarying float vFogDepth;\n\t#ifdef FOG_EXP2\n\t\tuniform float fogDensity;\n\t#else\n\t\tuniform float fogNear;\n\t\tuniform float fogFar;\n\t#endif\n#endif",gradientmap_pars_fragment:"#ifdef USE_GRADIENTMAP\n\tuniform sampler2D gradientMap;\n#endif\nvec3 getGradientIrradiance( vec3 normal, vec3 lightDirection ) {\n\tfloat dotNL = dot( normal, lightDirection );\n\tvec2 coord = vec2( dotNL * 0.5 + 0.5, 0.0 );\n\t#ifdef USE_GRADIENTMAP\n\t\treturn vec3( texture2D( gradientMap, coord ).r );\n\t#else\n\t\tvec2 fw = fwidth( coord ) * 0.5;\n\t\treturn mix( vec3( 0.7 ), vec3( 1.0 ), smoothstep( 0.7 - fw.x, 0.7 + fw.x, coord.x ) );\n\t#endif\n}",lightmap_pars_fragment:"#ifdef USE_LIGHTMAP\n\tuniform sampler2D lightMap;\n\tuniform float lightMapIntensity;\n#endif",lights_lambert_fragment:"LambertMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularStrength = specularStrength;",lights_lambert_pars_fragment:"varying vec3 vViewPosition;\nstruct LambertMaterial {\n\tvec3 diffuseColor;\n\tfloat specularStrength;\n};\nvoid RE_Direct_Lambert( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in LambertMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometryNormal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Lambert( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in LambertMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Lambert\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Lambert",lights_pars_begin:"uniform bool receiveShadow;\nuniform vec3 ambientLightColor;\n#if defined( USE_LIGHT_PROBES )\n\tuniform vec3 lightProbe[ 9 ];\n#endif\nvec3 shGetIrradianceAt( in vec3 normal, in vec3 shCoefficients[ 9 ] ) {\n\tfloat x = normal.x, y = normal.y, z = normal.z;\n\tvec3 result = shCoefficients[ 0 ] * 0.886227;\n\tresult += shCoefficients[ 1 ] * 2.0 * 0.511664 * y;\n\tresult += shCoefficients[ 2 ] * 2.0 * 0.511664 * z;\n\tresult += shCoefficients[ 3 ] * 2.0 * 0.511664 * x;\n\tresult += shCoefficients[ 4 ] * 2.0 * 0.429043 * x * y;\n\tresult += shCoefficients[ 5 ] * 2.0 * 0.429043 * y * z;\n\tresult += shCoefficients[ 6 ] * ( 0.743125 * z * z - 0.247708 );\n\tresult += shCoefficients[ 7 ] * 2.0 * 0.429043 * x * z;\n\tresult += shCoefficients[ 8 ] * 0.429043 * ( x * x - y * y );\n\treturn result;\n}\nvec3 getLightProbeIrradiance( const in vec3 lightProbe[ 9 ], const in vec3 normal ) {\n\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\tvec3 irradiance = shGetIrradianceAt( worldNormal, lightProbe );\n\treturn irradiance;\n}\nvec3 getAmbientLightIrradiance( const in vec3 ambientLightColor ) {\n\tvec3 irradiance = ambientLightColor;\n\treturn irradiance;\n}\nfloat getDistanceAttenuation( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) {\n\tfloat distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );\n\tif ( cutoffDistance > 0.0 ) {\n\t\tdistanceFalloff *= pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );\n\t}\n\treturn distanceFalloff;\n}\nfloat getSpotAttenuation( const in float coneCosine, const in float penumbraCosine, const in float angleCosine ) {\n\treturn smoothstep( coneCosine, penumbraCosine, angleCosine );\n}\n#if NUM_DIR_LIGHTS > 0\n\tstruct DirectionalLight {\n\t\tvec3 direction;\n\t\tvec3 color;\n\t};\n\tuniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ];\n\tvoid getDirectionalLightInfo( const in DirectionalLight directionalLight, out IncidentLight light ) {\n\t\tlight.color = directionalLight.color;\n\t\tlight.direction = directionalLight.direction;\n\t\tlight.visible = true;\n\t}\n#endif\n#if NUM_POINT_LIGHTS > 0\n\tstruct PointLight {\n\t\tvec3 position;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t};\n\tuniform PointLight pointLights[ NUM_POINT_LIGHTS ];\n\tvoid getPointLightInfo( const in PointLight pointLight, const in vec3 geometryPosition, out IncidentLight light ) {\n\t\tvec3 lVector = pointLight.position - geometryPosition;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat lightDistance = length( lVector );\n\t\tlight.color = pointLight.color;\n\t\tlight.color *= getDistanceAttenuation( lightDistance, pointLight.distance, pointLight.decay );\n\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t}\n#endif\n#if NUM_SPOT_LIGHTS > 0\n\tstruct SpotLight {\n\t\tvec3 position;\n\t\tvec3 direction;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t\tfloat coneCos;\n\t\tfloat penumbraCos;\n\t};\n\tuniform SpotLight spotLights[ NUM_SPOT_LIGHTS ];\n\tvoid getSpotLightInfo( const in SpotLight spotLight, const in vec3 geometryPosition, out IncidentLight light ) {\n\t\tvec3 lVector = spotLight.position - geometryPosition;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat angleCos = dot( light.direction, spotLight.direction );\n\t\tfloat spotAttenuation = getSpotAttenuation( spotLight.coneCos, spotLight.penumbraCos, angleCos );\n\t\tif ( spotAttenuation > 0.0 ) {\n\t\t\tfloat lightDistance = length( lVector );\n\t\t\tlight.color = spotLight.color * spotAttenuation;\n\t\t\tlight.color *= getDistanceAttenuation( lightDistance, spotLight.distance, spotLight.decay );\n\t\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t\t} else {\n\t\t\tlight.color = vec3( 0.0 );\n\t\t\tlight.visible = false;\n\t\t}\n\t}\n#endif\n#if NUM_RECT_AREA_LIGHTS > 0\n\tstruct RectAreaLight {\n\t\tvec3 color;\n\t\tvec3 position;\n\t\tvec3 halfWidth;\n\t\tvec3 halfHeight;\n\t};\n\tuniform sampler2D ltc_1;\tuniform sampler2D ltc_2;\n\tuniform RectAreaLight rectAreaLights[ NUM_RECT_AREA_LIGHTS ];\n#endif\n#if NUM_HEMI_LIGHTS > 0\n\tstruct HemisphereLight {\n\t\tvec3 direction;\n\t\tvec3 skyColor;\n\t\tvec3 groundColor;\n\t};\n\tuniform HemisphereLight hemisphereLights[ NUM_HEMI_LIGHTS ];\n\tvec3 getHemisphereLightIrradiance( const in HemisphereLight hemiLight, const in vec3 normal ) {\n\t\tfloat dotNL = dot( normal, hemiLight.direction );\n\t\tfloat hemiDiffuseWeight = 0.5 * dotNL + 0.5;\n\t\tvec3 irradiance = mix( hemiLight.groundColor, hemiLight.skyColor, hemiDiffuseWeight );\n\t\treturn irradiance;\n\t}\n#endif",lights_toon_fragment:"ToonMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;",lights_toon_pars_fragment:"varying vec3 vViewPosition;\nstruct ToonMaterial {\n\tvec3 diffuseColor;\n};\nvoid RE_Direct_Toon( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\tvec3 irradiance = getGradientIrradiance( geometryNormal, directLight.direction ) * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Toon( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Toon\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Toon",lights_phong_fragment:"BlinnPhongMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularColor = specular;\nmaterial.specularShininess = shininess;\nmaterial.specularStrength = specularStrength;",lights_phong_pars_fragment:"varying vec3 vViewPosition;\nstruct BlinnPhongMaterial {\n\tvec3 diffuseColor;\n\tvec3 specularColor;\n\tfloat specularShininess;\n\tfloat specularStrength;\n};\nvoid RE_Direct_BlinnPhong( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometryNormal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n\treflectedLight.directSpecular += irradiance * BRDF_BlinnPhong( directLight.direction, geometryViewDir, geometryNormal, material.specularColor, material.specularShininess ) * material.specularStrength;\n}\nvoid RE_IndirectDiffuse_BlinnPhong( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_BlinnPhong\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_BlinnPhong",lights_physical_fragment:"PhysicalMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb * ( 1.0 - metalnessFactor );\nvec3 dxy = max( abs( dFdx( nonPerturbedNormal ) ), abs( dFdy( nonPerturbedNormal ) ) );\nfloat geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z );\nmaterial.roughness = max( roughnessFactor, 0.0525 );material.roughness += geometryRoughness;\nmaterial.roughness = min( material.roughness, 1.0 );\n#ifdef IOR\n\tmaterial.ior = ior;\n\t#ifdef USE_SPECULAR\n\t\tfloat specularIntensityFactor = specularIntensity;\n\t\tvec3 specularColorFactor = specularColor;\n\t\t#ifdef USE_SPECULAR_COLORMAP\n\t\t\tspecularColorFactor *= texture2D( specularColorMap, vSpecularColorMapUv ).rgb;\n\t\t#endif\n\t\t#ifdef USE_SPECULAR_INTENSITYMAP\n\t\t\tspecularIntensityFactor *= texture2D( specularIntensityMap, vSpecularIntensityMapUv ).a;\n\t\t#endif\n\t\tmaterial.specularF90 = mix( specularIntensityFactor, 1.0, metalnessFactor );\n\t#else\n\t\tfloat specularIntensityFactor = 1.0;\n\t\tvec3 specularColorFactor = vec3( 1.0 );\n\t\tmaterial.specularF90 = 1.0;\n\t#endif\n\tmaterial.specularColor = mix( min( pow2( ( material.ior - 1.0 ) / ( material.ior + 1.0 ) ) * specularColorFactor, vec3( 1.0 ) ) * specularIntensityFactor, diffuseColor.rgb, metalnessFactor );\n#else\n\tmaterial.specularColor = mix( vec3( 0.04 ), diffuseColor.rgb, metalnessFactor );\n\tmaterial.specularF90 = 1.0;\n#endif\n#ifdef USE_CLEARCOAT\n\tmaterial.clearcoat = clearcoat;\n\tmaterial.clearcoatRoughness = clearcoatRoughness;\n\tmaterial.clearcoatF0 = vec3( 0.04 );\n\tmaterial.clearcoatF90 = 1.0;\n\t#ifdef USE_CLEARCOATMAP\n\t\tmaterial.clearcoat *= texture2D( clearcoatMap, vClearcoatMapUv ).x;\n\t#endif\n\t#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\t\tmaterial.clearcoatRoughness *= texture2D( clearcoatRoughnessMap, vClearcoatRoughnessMapUv ).y;\n\t#endif\n\tmaterial.clearcoat = saturate( material.clearcoat );\tmaterial.clearcoatRoughness = max( material.clearcoatRoughness, 0.0525 );\n\tmaterial.clearcoatRoughness += geometryRoughness;\n\tmaterial.clearcoatRoughness = min( material.clearcoatRoughness, 1.0 );\n#endif\n#ifdef USE_DISPERSION\n\tmaterial.dispersion = dispersion;\n#endif\n#ifdef USE_IRIDESCENCE\n\tmaterial.iridescence = iridescence;\n\tmaterial.iridescenceIOR = iridescenceIOR;\n\t#ifdef USE_IRIDESCENCEMAP\n\t\tmaterial.iridescence *= texture2D( iridescenceMap, vIridescenceMapUv ).r;\n\t#endif\n\t#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\t\tmaterial.iridescenceThickness = (iridescenceThicknessMaximum - iridescenceThicknessMinimum) * texture2D( iridescenceThicknessMap, vIridescenceThicknessMapUv ).g + iridescenceThicknessMinimum;\n\t#else\n\t\tmaterial.iridescenceThickness = iridescenceThicknessMaximum;\n\t#endif\n#endif\n#ifdef USE_SHEEN\n\tmaterial.sheenColor = sheenColor;\n\t#ifdef USE_SHEEN_COLORMAP\n\t\tmaterial.sheenColor *= texture2D( sheenColorMap, vSheenColorMapUv ).rgb;\n\t#endif\n\tmaterial.sheenRoughness = clamp( sheenRoughness, 0.07, 1.0 );\n\t#ifdef USE_SHEEN_ROUGHNESSMAP\n\t\tmaterial.sheenRoughness *= texture2D( sheenRoughnessMap, vSheenRoughnessMapUv ).a;\n\t#endif\n#endif\n#ifdef USE_ANISOTROPY\n\t#ifdef USE_ANISOTROPYMAP\n\t\tmat2 anisotropyMat = mat2( anisotropyVector.x, anisotropyVector.y, - anisotropyVector.y, anisotropyVector.x );\n\t\tvec3 anisotropyPolar = texture2D( anisotropyMap, vAnisotropyMapUv ).rgb;\n\t\tvec2 anisotropyV = anisotropyMat * normalize( 2.0 * anisotropyPolar.rg - vec2( 1.0 ) ) * anisotropyPolar.b;\n\t#else\n\t\tvec2 anisotropyV = anisotropyVector;\n\t#endif\n\tmaterial.anisotropy = length( anisotropyV );\n\tif( material.anisotropy == 0.0 ) {\n\t\tanisotropyV = vec2( 1.0, 0.0 );\n\t} else {\n\t\tanisotropyV /= material.anisotropy;\n\t\tmaterial.anisotropy = saturate( material.anisotropy );\n\t}\n\tmaterial.alphaT = mix( pow2( material.roughness ), 1.0, pow2( material.anisotropy ) );\n\tmaterial.anisotropyT = tbn[ 0 ] * anisotropyV.x + tbn[ 1 ] * anisotropyV.y;\n\tmaterial.anisotropyB = tbn[ 1 ] * anisotropyV.x - tbn[ 0 ] * anisotropyV.y;\n#endif",lights_physical_pars_fragment:"struct PhysicalMaterial {\n\tvec3 diffuseColor;\n\tfloat roughness;\n\tvec3 specularColor;\n\tfloat specularF90;\n\tfloat dispersion;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat clearcoat;\n\t\tfloat clearcoatRoughness;\n\t\tvec3 clearcoatF0;\n\t\tfloat clearcoatF90;\n\t#endif\n\t#ifdef USE_IRIDESCENCE\n\t\tfloat iridescence;\n\t\tfloat iridescenceIOR;\n\t\tfloat iridescenceThickness;\n\t\tvec3 iridescenceFresnel;\n\t\tvec3 iridescenceF0;\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tvec3 sheenColor;\n\t\tfloat sheenRoughness;\n\t#endif\n\t#ifdef IOR\n\t\tfloat ior;\n\t#endif\n\t#ifdef USE_TRANSMISSION\n\t\tfloat transmission;\n\t\tfloat transmissionAlpha;\n\t\tfloat thickness;\n\t\tfloat attenuationDistance;\n\t\tvec3 attenuationColor;\n\t#endif\n\t#ifdef USE_ANISOTROPY\n\t\tfloat anisotropy;\n\t\tfloat alphaT;\n\t\tvec3 anisotropyT;\n\t\tvec3 anisotropyB;\n\t#endif\n};\nvec3 clearcoatSpecularDirect = vec3( 0.0 );\nvec3 clearcoatSpecularIndirect = vec3( 0.0 );\nvec3 sheenSpecularDirect = vec3( 0.0 );\nvec3 sheenSpecularIndirect = vec3(0.0 );\nvec3 Schlick_to_F0( const in vec3 f, const in float f90, const in float dotVH ) {\n float x = clamp( 1.0 - dotVH, 0.0, 1.0 );\n float x2 = x * x;\n float x5 = clamp( x * x2 * x2, 0.0, 0.9999 );\n return ( f - vec3( f90 ) * x5 ) / ( 1.0 - x5 );\n}\nfloat V_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const in float dotNV ) {\n\tfloat a2 = pow2( alpha );\n\tfloat gv = dotNL * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );\n\tfloat gl = dotNV * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );\n\treturn 0.5 / max( gv + gl, EPSILON );\n}\nfloat D_GGX( const in float alpha, const in float dotNH ) {\n\tfloat a2 = pow2( alpha );\n\tfloat denom = pow2( dotNH ) * ( a2 - 1.0 ) + 1.0;\n\treturn RECIPROCAL_PI * a2 / pow2( denom );\n}\n#ifdef USE_ANISOTROPY\n\tfloat V_GGX_SmithCorrelated_Anisotropic( const in float alphaT, const in float alphaB, const in float dotTV, const in float dotBV, const in float dotTL, const in float dotBL, const in float dotNV, const in float dotNL ) {\n\t\tfloat gv = dotNL * length( vec3( alphaT * dotTV, alphaB * dotBV, dotNV ) );\n\t\tfloat gl = dotNV * length( vec3( alphaT * dotTL, alphaB * dotBL, dotNL ) );\n\t\tfloat v = 0.5 / ( gv + gl );\n\t\treturn saturate(v);\n\t}\n\tfloat D_GGX_Anisotropic( const in float alphaT, const in float alphaB, const in float dotNH, const in float dotTH, const in float dotBH ) {\n\t\tfloat a2 = alphaT * alphaB;\n\t\thighp vec3 v = vec3( alphaB * dotTH, alphaT * dotBH, a2 * dotNH );\n\t\thighp float v2 = dot( v, v );\n\t\tfloat w2 = a2 / v2;\n\t\treturn RECIPROCAL_PI * a2 * pow2 ( w2 );\n\t}\n#endif\n#ifdef USE_CLEARCOAT\n\tvec3 BRDF_GGX_Clearcoat( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in PhysicalMaterial material) {\n\t\tvec3 f0 = material.clearcoatF0;\n\t\tfloat f90 = material.clearcoatF90;\n\t\tfloat roughness = material.clearcoatRoughness;\n\t\tfloat alpha = pow2( roughness );\n\t\tvec3 halfDir = normalize( lightDir + viewDir );\n\t\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\t\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\t\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\t\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\t\tvec3 F = F_Schlick( f0, f90, dotVH );\n\t\tfloat V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\t\tfloat D = D_GGX( alpha, dotNH );\n\t\treturn F * ( V * D );\n\t}\n#endif\nvec3 BRDF_GGX( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in PhysicalMaterial material ) {\n\tvec3 f0 = material.specularColor;\n\tfloat f90 = material.specularF90;\n\tfloat roughness = material.roughness;\n\tfloat alpha = pow2( roughness );\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\tvec3 F = F_Schlick( f0, f90, dotVH );\n\t#ifdef USE_IRIDESCENCE\n\t\tF = mix( F, material.iridescenceFresnel, material.iridescence );\n\t#endif\n\t#ifdef USE_ANISOTROPY\n\t\tfloat dotTL = dot( material.anisotropyT, lightDir );\n\t\tfloat dotTV = dot( material.anisotropyT, viewDir );\n\t\tfloat dotTH = dot( material.anisotropyT, halfDir );\n\t\tfloat dotBL = dot( material.anisotropyB, lightDir );\n\t\tfloat dotBV = dot( material.anisotropyB, viewDir );\n\t\tfloat dotBH = dot( material.anisotropyB, halfDir );\n\t\tfloat V = V_GGX_SmithCorrelated_Anisotropic( material.alphaT, alpha, dotTV, dotBV, dotTL, dotBL, dotNV, dotNL );\n\t\tfloat D = D_GGX_Anisotropic( material.alphaT, alpha, dotNH, dotTH, dotBH );\n\t#else\n\t\tfloat V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\t\tfloat D = D_GGX( alpha, dotNH );\n\t#endif\n\treturn F * ( V * D );\n}\nvec2 LTC_Uv( const in vec3 N, const in vec3 V, const in float roughness ) {\n\tconst float LUT_SIZE = 64.0;\n\tconst float LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE;\n\tconst float LUT_BIAS = 0.5 / LUT_SIZE;\n\tfloat dotNV = saturate( dot( N, V ) );\n\tvec2 uv = vec2( roughness, sqrt( 1.0 - dotNV ) );\n\tuv = uv * LUT_SCALE + LUT_BIAS;\n\treturn uv;\n}\nfloat LTC_ClippedSphereFormFactor( const in vec3 f ) {\n\tfloat l = length( f );\n\treturn max( ( l * l + f.z ) / ( l + 1.0 ), 0.0 );\n}\nvec3 LTC_EdgeVectorFormFactor( const in vec3 v1, const in vec3 v2 ) {\n\tfloat x = dot( v1, v2 );\n\tfloat y = abs( x );\n\tfloat a = 0.8543985 + ( 0.4965155 + 0.0145206 * y ) * y;\n\tfloat b = 3.4175940 + ( 4.1616724 + y ) * y;\n\tfloat v = a / b;\n\tfloat theta_sintheta = ( x > 0.0 ) ? v : 0.5 * inversesqrt( max( 1.0 - x * x, 1e-7 ) ) - v;\n\treturn cross( v1, v2 ) * theta_sintheta;\n}\nvec3 LTC_Evaluate( const in vec3 N, const in vec3 V, const in vec3 P, const in mat3 mInv, const in vec3 rectCoords[ 4 ] ) {\n\tvec3 v1 = rectCoords[ 1 ] - rectCoords[ 0 ];\n\tvec3 v2 = rectCoords[ 3 ] - rectCoords[ 0 ];\n\tvec3 lightNormal = cross( v1, v2 );\n\tif( dot( lightNormal, P - rectCoords[ 0 ] ) < 0.0 ) return vec3( 0.0 );\n\tvec3 T1, T2;\n\tT1 = normalize( V - N * dot( V, N ) );\n\tT2 = - cross( N, T1 );\n\tmat3 mat = mInv * transposeMat3( mat3( T1, T2, N ) );\n\tvec3 coords[ 4 ];\n\tcoords[ 0 ] = mat * ( rectCoords[ 0 ] - P );\n\tcoords[ 1 ] = mat * ( rectCoords[ 1 ] - P );\n\tcoords[ 2 ] = mat * ( rectCoords[ 2 ] - P );\n\tcoords[ 3 ] = mat * ( rectCoords[ 3 ] - P );\n\tcoords[ 0 ] = normalize( coords[ 0 ] );\n\tcoords[ 1 ] = normalize( coords[ 1 ] );\n\tcoords[ 2 ] = normalize( coords[ 2 ] );\n\tcoords[ 3 ] = normalize( coords[ 3 ] );\n\tvec3 vectorFormFactor = vec3( 0.0 );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 0 ], coords[ 1 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 1 ], coords[ 2 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 2 ], coords[ 3 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 3 ], coords[ 0 ] );\n\tfloat result = LTC_ClippedSphereFormFactor( vectorFormFactor );\n\treturn vec3( result );\n}\n#if defined( USE_SHEEN )\nfloat D_Charlie( float roughness, float dotNH ) {\n\tfloat alpha = pow2( roughness );\n\tfloat invAlpha = 1.0 / alpha;\n\tfloat cos2h = dotNH * dotNH;\n\tfloat sin2h = max( 1.0 - cos2h, 0.0078125 );\n\treturn ( 2.0 + invAlpha ) * pow( sin2h, invAlpha * 0.5 ) / ( 2.0 * PI );\n}\nfloat V_Neubelt( float dotNV, float dotNL ) {\n\treturn saturate( 1.0 / ( 4.0 * ( dotNL + dotNV - dotNL * dotNV ) ) );\n}\nvec3 BRDF_Sheen( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, vec3 sheenColor, const in float sheenRoughness ) {\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat D = D_Charlie( sheenRoughness, dotNH );\n\tfloat V = V_Neubelt( dotNV, dotNL );\n\treturn sheenColor * ( D * V );\n}\n#endif\nfloat IBLSheenBRDF( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat r2 = roughness * roughness;\n\tfloat a = roughness < 0.25 ? -339.2 * r2 + 161.4 * roughness - 25.9 : -8.48 * r2 + 14.3 * roughness - 9.95;\n\tfloat b = roughness < 0.25 ? 44.0 * r2 - 23.7 * roughness + 3.26 : 1.97 * r2 - 3.27 * roughness + 0.72;\n\tfloat DG = exp( a * dotNV + b ) + ( roughness < 0.25 ? 0.0 : 0.1 * ( roughness - 0.25 ) );\n\treturn saturate( DG * RECIPROCAL_PI );\n}\nvec2 DFGApprox( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tconst vec4 c0 = vec4( - 1, - 0.0275, - 0.572, 0.022 );\n\tconst vec4 c1 = vec4( 1, 0.0425, 1.04, - 0.04 );\n\tvec4 r = roughness * c0 + c1;\n\tfloat a004 = min( r.x * r.x, exp2( - 9.28 * dotNV ) ) * r.x + r.y;\n\tvec2 fab = vec2( - 1.04, 1.04 ) * a004 + r.zw;\n\treturn fab;\n}\nvec3 EnvironmentBRDF( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness ) {\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\treturn specularColor * fab.x + specularF90 * fab.y;\n}\n#ifdef USE_IRIDESCENCE\nvoid computeMultiscatteringIridescence( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float iridescence, const in vec3 iridescenceF0, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#else\nvoid computeMultiscattering( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n#endif\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\t#ifdef USE_IRIDESCENCE\n\t\tvec3 Fr = mix( specularColor, iridescenceF0, iridescence );\n\t#else\n\t\tvec3 Fr = specularColor;\n\t#endif\n\tvec3 FssEss = Fr * fab.x + specularF90 * fab.y;\n\tfloat Ess = fab.x + fab.y;\n\tfloat Ems = 1.0 - Ess;\n\tvec3 Favg = Fr + ( 1.0 - Fr ) * 0.047619;\tvec3 Fms = FssEss * Favg / ( 1.0 - Ems * Favg );\n\tsingleScatter += FssEss;\n\tmultiScatter += Fms * Ems;\n}\n#if NUM_RECT_AREA_LIGHTS > 0\n\tvoid RE_Direct_RectArea_Physical( const in RectAreaLight rectAreaLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\t\tvec3 normal = geometryNormal;\n\t\tvec3 viewDir = geometryViewDir;\n\t\tvec3 position = geometryPosition;\n\t\tvec3 lightPos = rectAreaLight.position;\n\t\tvec3 halfWidth = rectAreaLight.halfWidth;\n\t\tvec3 halfHeight = rectAreaLight.halfHeight;\n\t\tvec3 lightColor = rectAreaLight.color;\n\t\tfloat roughness = material.roughness;\n\t\tvec3 rectCoords[ 4 ];\n\t\trectCoords[ 0 ] = lightPos + halfWidth - halfHeight;\t\trectCoords[ 1 ] = lightPos - halfWidth - halfHeight;\n\t\trectCoords[ 2 ] = lightPos - halfWidth + halfHeight;\n\t\trectCoords[ 3 ] = lightPos + halfWidth + halfHeight;\n\t\tvec2 uv = LTC_Uv( normal, viewDir, roughness );\n\t\tvec4 t1 = texture2D( ltc_1, uv );\n\t\tvec4 t2 = texture2D( ltc_2, uv );\n\t\tmat3 mInv = mat3(\n\t\t\tvec3( t1.x, 0, t1.y ),\n\t\t\tvec3( 0, 1, 0 ),\n\t\t\tvec3( t1.z, 0, t1.w )\n\t\t);\n\t\tvec3 fresnel = ( material.specularColor * t2.x + ( vec3( 1.0 ) - material.specularColor ) * t2.y );\n\t\treflectedLight.directSpecular += lightColor * fresnel * LTC_Evaluate( normal, viewDir, position, mInv, rectCoords );\n\t\treflectedLight.directDiffuse += lightColor * material.diffuseColor * LTC_Evaluate( normal, viewDir, position, mat3( 1.0 ), rectCoords );\n\t}\n#endif\nvoid RE_Direct_Physical( const in IncidentLight directLight, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometryNormal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNLcc = saturate( dot( geometryClearcoatNormal, directLight.direction ) );\n\t\tvec3 ccIrradiance = dotNLcc * directLight.color;\n\t\tclearcoatSpecularDirect += ccIrradiance * BRDF_GGX_Clearcoat( directLight.direction, geometryViewDir, geometryClearcoatNormal, material );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tsheenSpecularDirect += irradiance * BRDF_Sheen( directLight.direction, geometryViewDir, geometryNormal, material.sheenColor, material.sheenRoughness );\n\t#endif\n\treflectedLight.directSpecular += irradiance * BRDF_GGX( directLight.direction, geometryViewDir, geometryNormal, material );\n\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Physical( const in vec3 irradiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 irradiance, const in vec3 clearcoatRadiance, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatSpecularIndirect += clearcoatRadiance * EnvironmentBRDF( geometryClearcoatNormal, geometryViewDir, material.clearcoatF0, material.clearcoatF90, material.clearcoatRoughness );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tsheenSpecularIndirect += irradiance * material.sheenColor * IBLSheenBRDF( geometryNormal, geometryViewDir, material.sheenRoughness );\n\t#endif\n\tvec3 singleScattering = vec3( 0.0 );\n\tvec3 multiScattering = vec3( 0.0 );\n\tvec3 cosineWeightedIrradiance = irradiance * RECIPROCAL_PI;\n\t#ifdef USE_IRIDESCENCE\n\t\tcomputeMultiscatteringIridescence( geometryNormal, geometryViewDir, material.specularColor, material.specularF90, material.iridescence, material.iridescenceFresnel, material.roughness, singleScattering, multiScattering );\n\t#else\n\t\tcomputeMultiscattering( geometryNormal, geometryViewDir, material.specularColor, material.specularF90, material.roughness, singleScattering, multiScattering );\n\t#endif\n\tvec3 totalScattering = singleScattering + multiScattering;\n\tvec3 diffuse = material.diffuseColor * ( 1.0 - max( max( totalScattering.r, totalScattering.g ), totalScattering.b ) );\n\treflectedLight.indirectSpecular += radiance * singleScattering;\n\treflectedLight.indirectSpecular += multiScattering * cosineWeightedIrradiance;\n\treflectedLight.indirectDiffuse += diffuse * cosineWeightedIrradiance;\n}\n#define RE_Direct\t\t\t\tRE_Direct_Physical\n#define RE_Direct_RectArea\t\tRE_Direct_RectArea_Physical\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Physical\n#define RE_IndirectSpecular\t\tRE_IndirectSpecular_Physical\nfloat computeSpecularOcclusion( const in float dotNV, const in float ambientOcclusion, const in float roughness ) {\n\treturn saturate( pow( dotNV + ambientOcclusion, exp2( - 16.0 * roughness - 1.0 ) ) - 1.0 + ambientOcclusion );\n}",lights_fragment_begin:"\nvec3 geometryPosition = - vViewPosition;\nvec3 geometryNormal = normal;\nvec3 geometryViewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( vViewPosition );\nvec3 geometryClearcoatNormal = vec3( 0.0 );\n#ifdef USE_CLEARCOAT\n\tgeometryClearcoatNormal = clearcoatNormal;\n#endif\n#ifdef USE_IRIDESCENCE\n\tfloat dotNVi = saturate( dot( normal, geometryViewDir ) );\n\tif ( material.iridescenceThickness == 0.0 ) {\n\t\tmaterial.iridescence = 0.0;\n\t} else {\n\t\tmaterial.iridescence = saturate( material.iridescence );\n\t}\n\tif ( material.iridescence > 0.0 ) {\n\t\tmaterial.iridescenceFresnel = evalIridescence( 1.0, material.iridescenceIOR, dotNVi, material.iridescenceThickness, material.specularColor );\n\t\tmaterial.iridescenceF0 = Schlick_to_F0( material.iridescenceFresnel, 1.0, dotNVi );\n\t}\n#endif\nIncidentLight directLight;\n#if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct )\n\tPointLight pointLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tpointLight = pointLights[ i ];\n\t\tgetPointLightInfo( pointLight, geometryPosition, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_POINT_LIGHT_SHADOWS )\n\t\tpointLightShadow = pointLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getPointShadow( pointShadowMap[ i ], pointLightShadow.shadowMapSize, pointLightShadow.shadowIntensity, pointLightShadow.shadowBias, pointLightShadow.shadowRadius, vPointShadowCoord[ i ], pointLightShadow.shadowCameraNear, pointLightShadow.shadowCameraFar ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct )\n\tSpotLight spotLight;\n\tvec4 spotColor;\n\tvec3 spotLightCoord;\n\tbool inSpotLightMap;\n\t#if defined( USE_SHADOWMAP ) && NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tspotLight = spotLights[ i ];\n\t\tgetSpotLightInfo( spotLight, geometryPosition, directLight );\n\t\t#if ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )\n\t\t#define SPOT_LIGHT_MAP_INDEX UNROLLED_LOOP_INDEX\n\t\t#elif ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\t#define SPOT_LIGHT_MAP_INDEX NUM_SPOT_LIGHT_MAPS\n\t\t#else\n\t\t#define SPOT_LIGHT_MAP_INDEX ( UNROLLED_LOOP_INDEX - NUM_SPOT_LIGHT_SHADOWS + NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )\n\t\t#endif\n\t\t#if ( SPOT_LIGHT_MAP_INDEX < NUM_SPOT_LIGHT_MAPS )\n\t\t\tspotLightCoord = vSpotLightCoord[ i ].xyz / vSpotLightCoord[ i ].w;\n\t\t\tinSpotLightMap = all( lessThan( abs( spotLightCoord * 2. - 1. ), vec3( 1.0 ) ) );\n\t\t\tspotColor = texture2D( spotLightMap[ SPOT_LIGHT_MAP_INDEX ], spotLightCoord.xy );\n\t\t\tdirectLight.color = inSpotLightMap ? directLight.color * spotColor.rgb : directLight.color;\n\t\t#endif\n\t\t#undef SPOT_LIGHT_MAP_INDEX\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\tspotLightShadow = spotLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( spotShadowMap[ i ], spotLightShadow.shadowMapSize, spotLightShadow.shadowIntensity, spotLightShadow.shadowBias, spotLightShadow.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct )\n\tDirectionalLight directionalLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tdirectionalLight = directionalLights[ i ];\n\t\tgetDirectionalLightInfo( directionalLight, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )\n\t\tdirectionalLightShadow = directionalLightShadows[ i ];\n\t\tdirectLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowIntensity, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea )\n\tRectAreaLight rectAreaLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) {\n\t\trectAreaLight = rectAreaLights[ i ];\n\t\tRE_Direct_RectArea( rectAreaLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if defined( RE_IndirectDiffuse )\n\tvec3 iblIrradiance = vec3( 0.0 );\n\tvec3 irradiance = getAmbientLightIrradiance( ambientLightColor );\n\t#if defined( USE_LIGHT_PROBES )\n\t\tirradiance += getLightProbeIrradiance( lightProbe, geometryNormal );\n\t#endif\n\t#if ( NUM_HEMI_LIGHTS > 0 )\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n\t\t\tirradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometryNormal );\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n#endif\n#if defined( RE_IndirectSpecular )\n\tvec3 radiance = vec3( 0.0 );\n\tvec3 clearcoatRadiance = vec3( 0.0 );\n#endif",lights_fragment_maps:"#if defined( RE_IndirectDiffuse )\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel = texture2D( lightMap, vLightMapUv );\n\t\tvec3 lightMapIrradiance = lightMapTexel.rgb * lightMapIntensity;\n\t\tirradiance += lightMapIrradiance;\n\t#endif\n\t#if defined( USE_ENVMAP ) && defined( STANDARD ) && defined( ENVMAP_TYPE_CUBE_UV )\n\t\tiblIrradiance += getIBLIrradiance( geometryNormal );\n\t#endif\n#endif\n#if defined( USE_ENVMAP ) && defined( RE_IndirectSpecular )\n\t#ifdef USE_ANISOTROPY\n\t\tradiance += getIBLAnisotropyRadiance( geometryViewDir, geometryNormal, material.roughness, material.anisotropyB, material.anisotropy );\n\t#else\n\t\tradiance += getIBLRadiance( geometryViewDir, geometryNormal, material.roughness );\n\t#endif\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatRadiance += getIBLRadiance( geometryViewDir, geometryClearcoatNormal, material.clearcoatRoughness );\n\t#endif\n#endif",lights_fragment_end:"#if defined( RE_IndirectDiffuse )\n\tRE_IndirectDiffuse( irradiance, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n#endif\n#if defined( RE_IndirectSpecular )\n\tRE_IndirectSpecular( radiance, iblIrradiance, clearcoatRadiance, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n#endif",logdepthbuf_fragment:"#if defined( USE_LOGDEPTHBUF )\n\tgl_FragDepth = vIsPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\n#endif",logdepthbuf_pars_fragment:"#if defined( USE_LOGDEPTHBUF )\n\tuniform float logDepthBufFC;\n\tvarying float vFragDepth;\n\tvarying float vIsPerspective;\n#endif",logdepthbuf_pars_vertex:"#ifdef USE_LOGDEPTHBUF\n\tvarying float vFragDepth;\n\tvarying float vIsPerspective;\n#endif",logdepthbuf_vertex:"#ifdef USE_LOGDEPTHBUF\n\tvFragDepth = 1.0 + gl_Position.w;\n\tvIsPerspective = float( isPerspectiveMatrix( projectionMatrix ) );\n#endif",map_fragment:"#ifdef USE_MAP\n\tvec4 sampledDiffuseColor = texture2D( map, vMapUv );\n\t#ifdef DECODE_VIDEO_TEXTURE\n\t\tsampledDiffuseColor = sRGBTransferEOTF( sampledDiffuseColor );\n\t#endif\n\tdiffuseColor *= sampledDiffuseColor;\n#endif",map_pars_fragment:"#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif",map_particle_fragment:"#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\t#if defined( USE_POINTS_UV )\n\t\tvec2 uv = vUv;\n\t#else\n\t\tvec2 uv = ( uvTransform * vec3( gl_PointCoord.x, 1.0 - gl_PointCoord.y, 1 ) ).xy;\n\t#endif\n#endif\n#ifdef USE_MAP\n\tdiffuseColor *= texture2D( map, uv );\n#endif\n#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, uv ).g;\n#endif",map_particle_pars_fragment:"#if defined( USE_POINTS_UV )\n\tvarying vec2 vUv;\n#else\n\t#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\t\tuniform mat3 uvTransform;\n\t#endif\n#endif\n#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif\n#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif",metalnessmap_fragment:"float metalnessFactor = metalness;\n#ifdef USE_METALNESSMAP\n\tvec4 texelMetalness = texture2D( metalnessMap, vMetalnessMapUv );\n\tmetalnessFactor *= texelMetalness.b;\n#endif",metalnessmap_pars_fragment:"#ifdef USE_METALNESSMAP\n\tuniform sampler2D metalnessMap;\n#endif",morphinstance_vertex:"#ifdef USE_INSTANCING_MORPH\n\tfloat morphTargetInfluences[ MORPHTARGETS_COUNT ];\n\tfloat morphTargetBaseInfluence = texelFetch( morphTexture, ivec2( 0, gl_InstanceID ), 0 ).r;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\tmorphTargetInfluences[i] = texelFetch( morphTexture, ivec2( i + 1, gl_InstanceID ), 0 ).r;\n\t}\n#endif",morphcolor_vertex:"#if defined( USE_MORPHCOLORS )\n\tvColor *= morphTargetBaseInfluence;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t#if defined( USE_COLOR_ALPHA )\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ) * morphTargetInfluences[ i ];\n\t\t#elif defined( USE_COLOR )\n\t\t\tif ( morphTargetInfluences[ i ] != 0.0 ) vColor += getMorph( gl_VertexID, i, 2 ).rgb * morphTargetInfluences[ i ];\n\t\t#endif\n\t}\n#endif",morphnormal_vertex:"#ifdef USE_MORPHNORMALS\n\tobjectNormal *= morphTargetBaseInfluence;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\tif ( morphTargetInfluences[ i ] != 0.0 ) objectNormal += getMorph( gl_VertexID, i, 1 ).xyz * morphTargetInfluences[ i ];\n\t}\n#endif",morphtarget_pars_vertex:"#ifdef USE_MORPHTARGETS\n\t#ifndef USE_INSTANCING_MORPH\n\t\tuniform float morphTargetBaseInfluence;\n\t\tuniform float morphTargetInfluences[ MORPHTARGETS_COUNT ];\n\t#endif\n\tuniform sampler2DArray morphTargetsTexture;\n\tuniform ivec2 morphTargetsTextureSize;\n\tvec4 getMorph( const in int vertexIndex, const in int morphTargetIndex, const in int offset ) {\n\t\tint texelIndex = vertexIndex * MORPHTARGETS_TEXTURE_STRIDE + offset;\n\t\tint y = texelIndex / morphTargetsTextureSize.x;\n\t\tint x = texelIndex - y * morphTargetsTextureSize.x;\n\t\tivec3 morphUV = ivec3( x, y, morphTargetIndex );\n\t\treturn texelFetch( morphTargetsTexture, morphUV, 0 );\n\t}\n#endif",morphtarget_vertex:"#ifdef USE_MORPHTARGETS\n\ttransformed *= morphTargetBaseInfluence;\n\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\tif ( morphTargetInfluences[ i ] != 0.0 ) transformed += getMorph( gl_VertexID, i, 0 ).xyz * morphTargetInfluences[ i ];\n\t}\n#endif",normal_fragment_begin:"float faceDirection = gl_FrontFacing ? 1.0 : - 1.0;\n#ifdef FLAT_SHADED\n\tvec3 fdx = dFdx( vViewPosition );\n\tvec3 fdy = dFdy( vViewPosition );\n\tvec3 normal = normalize( cross( fdx, fdy ) );\n#else\n\tvec3 normal = normalize( vNormal );\n\t#ifdef DOUBLE_SIDED\n\t\tnormal *= faceDirection;\n\t#endif\n#endif\n#if defined( USE_NORMALMAP_TANGENTSPACE ) || defined( USE_CLEARCOAT_NORMALMAP ) || defined( USE_ANISOTROPY )\n\t#ifdef USE_TANGENT\n\t\tmat3 tbn = mat3( normalize( vTangent ), normalize( vBitangent ), normal );\n\t#else\n\t\tmat3 tbn = getTangentFrame( - vViewPosition, normal,\n\t\t#if defined( USE_NORMALMAP )\n\t\t\tvNormalMapUv\n\t\t#elif defined( USE_CLEARCOAT_NORMALMAP )\n\t\t\tvClearcoatNormalMapUv\n\t\t#else\n\t\t\tvUv\n\t\t#endif\n\t\t);\n\t#endif\n\t#if defined( DOUBLE_SIDED ) && ! defined( FLAT_SHADED )\n\t\ttbn[0] *= faceDirection;\n\t\ttbn[1] *= faceDirection;\n\t#endif\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\t#ifdef USE_TANGENT\n\t\tmat3 tbn2 = mat3( normalize( vTangent ), normalize( vBitangent ), normal );\n\t#else\n\t\tmat3 tbn2 = getTangentFrame( - vViewPosition, normal, vClearcoatNormalMapUv );\n\t#endif\n\t#if defined( DOUBLE_SIDED ) && ! defined( FLAT_SHADED )\n\t\ttbn2[0] *= faceDirection;\n\t\ttbn2[1] *= faceDirection;\n\t#endif\n#endif\nvec3 nonPerturbedNormal = normal;",normal_fragment_maps:"#ifdef USE_NORMALMAP_OBJECTSPACE\n\tnormal = texture2D( normalMap, vNormalMapUv ).xyz * 2.0 - 1.0;\n\t#ifdef FLIP_SIDED\n\t\tnormal = - normal;\n\t#endif\n\t#ifdef DOUBLE_SIDED\n\t\tnormal = normal * faceDirection;\n\t#endif\n\tnormal = normalize( normalMatrix * normal );\n#elif defined( USE_NORMALMAP_TANGENTSPACE )\n\tvec3 mapN = texture2D( normalMap, vNormalMapUv ).xyz * 2.0 - 1.0;\n\tmapN.xy *= normalScale;\n\tnormal = normalize( tbn * mapN );\n#elif defined( USE_BUMPMAP )\n\tnormal = perturbNormalArb( - vViewPosition, normal, dHdxy_fwd(), faceDirection );\n#endif",normal_pars_fragment:"#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif",normal_pars_vertex:"#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif",normal_vertex:"#ifndef FLAT_SHADED\n\tvNormal = normalize( transformedNormal );\n\t#ifdef USE_TANGENT\n\t\tvTangent = normalize( transformedTangent );\n\t\tvBitangent = normalize( cross( vNormal, vTangent ) * tangent.w );\n\t#endif\n#endif",normalmap_pars_fragment:"#ifdef USE_NORMALMAP\n\tuniform sampler2D normalMap;\n\tuniform vec2 normalScale;\n#endif\n#ifdef USE_NORMALMAP_OBJECTSPACE\n\tuniform mat3 normalMatrix;\n#endif\n#if ! defined ( USE_TANGENT ) && ( defined ( USE_NORMALMAP_TANGENTSPACE ) || defined ( USE_CLEARCOAT_NORMALMAP ) || defined( USE_ANISOTROPY ) )\n\tmat3 getTangentFrame( vec3 eye_pos, vec3 surf_norm, vec2 uv ) {\n\t\tvec3 q0 = dFdx( eye_pos.xyz );\n\t\tvec3 q1 = dFdy( eye_pos.xyz );\n\t\tvec2 st0 = dFdx( uv.st );\n\t\tvec2 st1 = dFdy( uv.st );\n\t\tvec3 N = surf_norm;\n\t\tvec3 q1perp = cross( q1, N );\n\t\tvec3 q0perp = cross( N, q0 );\n\t\tvec3 T = q1perp * st0.x + q0perp * st1.x;\n\t\tvec3 B = q1perp * st0.y + q0perp * st1.y;\n\t\tfloat det = max( dot( T, T ), dot( B, B ) );\n\t\tfloat scale = ( det == 0.0 ) ? 0.0 : inversesqrt( det );\n\t\treturn mat3( T * scale, B * scale, N );\n\t}\n#endif",clearcoat_normal_fragment_begin:"#ifdef USE_CLEARCOAT\n\tvec3 clearcoatNormal = nonPerturbedNormal;\n#endif",clearcoat_normal_fragment_maps:"#ifdef USE_CLEARCOAT_NORMALMAP\n\tvec3 clearcoatMapN = texture2D( clearcoatNormalMap, vClearcoatNormalMapUv ).xyz * 2.0 - 1.0;\n\tclearcoatMapN.xy *= clearcoatNormalScale;\n\tclearcoatNormal = normalize( tbn2 * clearcoatMapN );\n#endif",clearcoat_pars_fragment:"#ifdef USE_CLEARCOATMAP\n\tuniform sampler2D clearcoatMap;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tuniform sampler2D clearcoatNormalMap;\n\tuniform vec2 clearcoatNormalScale;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tuniform sampler2D clearcoatRoughnessMap;\n#endif",iridescence_pars_fragment:"#ifdef USE_IRIDESCENCEMAP\n\tuniform sampler2D iridescenceMap;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tuniform sampler2D iridescenceThicknessMap;\n#endif",opaque_fragment:"#ifdef OPAQUE\ndiffuseColor.a = 1.0;\n#endif\n#ifdef USE_TRANSMISSION\ndiffuseColor.a *= material.transmissionAlpha;\n#endif\ngl_FragColor = vec4( outgoingLight, diffuseColor.a );",packing:"vec3 packNormalToRGB( const in vec3 normal ) {\n\treturn normalize( normal ) * 0.5 + 0.5;\n}\nvec3 unpackRGBToNormal( const in vec3 rgb ) {\n\treturn 2.0 * rgb.xyz - 1.0;\n}\nconst float PackUpscale = 256. / 255.;const float UnpackDownscale = 255. / 256.;const float ShiftRight8 = 1. / 256.;\nconst float Inv255 = 1. / 255.;\nconst vec4 PackFactors = vec4( 1.0, 256.0, 256.0 * 256.0, 256.0 * 256.0 * 256.0 );\nconst vec2 UnpackFactors2 = vec2( UnpackDownscale, 1.0 / PackFactors.g );\nconst vec3 UnpackFactors3 = vec3( UnpackDownscale / PackFactors.rg, 1.0 / PackFactors.b );\nconst vec4 UnpackFactors4 = vec4( UnpackDownscale / PackFactors.rgb, 1.0 / PackFactors.a );\nvec4 packDepthToRGBA( const in float v ) {\n\tif( v <= 0.0 )\n\t\treturn vec4( 0., 0., 0., 0. );\n\tif( v >= 1.0 )\n\t\treturn vec4( 1., 1., 1., 1. );\n\tfloat vuf;\n\tfloat af = modf( v * PackFactors.a, vuf );\n\tfloat bf = modf( vuf * ShiftRight8, vuf );\n\tfloat gf = modf( vuf * ShiftRight8, vuf );\n\treturn vec4( vuf * Inv255, gf * PackUpscale, bf * PackUpscale, af );\n}\nvec3 packDepthToRGB( const in float v ) {\n\tif( v <= 0.0 )\n\t\treturn vec3( 0., 0., 0. );\n\tif( v >= 1.0 )\n\t\treturn vec3( 1., 1., 1. );\n\tfloat vuf;\n\tfloat bf = modf( v * PackFactors.b, vuf );\n\tfloat gf = modf( vuf * ShiftRight8, vuf );\n\treturn vec3( vuf * Inv255, gf * PackUpscale, bf );\n}\nvec2 packDepthToRG( const in float v ) {\n\tif( v <= 0.0 )\n\t\treturn vec2( 0., 0. );\n\tif( v >= 1.0 )\n\t\treturn vec2( 1., 1. );\n\tfloat vuf;\n\tfloat gf = modf( v * 256., vuf );\n\treturn vec2( vuf * Inv255, gf );\n}\nfloat unpackRGBAToDepth( const in vec4 v ) {\n\treturn dot( v, UnpackFactors4 );\n}\nfloat unpackRGBToDepth( const in vec3 v ) {\n\treturn dot( v, UnpackFactors3 );\n}\nfloat unpackRGToDepth( const in vec2 v ) {\n\treturn v.r * UnpackFactors2.r + v.g * UnpackFactors2.g;\n}\nvec4 pack2HalfToRGBA( const in vec2 v ) {\n\tvec4 r = vec4( v.x, fract( v.x * 255.0 ), v.y, fract( v.y * 255.0 ) );\n\treturn vec4( r.x - r.y / 255.0, r.y, r.z - r.w / 255.0, r.w );\n}\nvec2 unpackRGBATo2Half( const in vec4 v ) {\n\treturn vec2( v.x + ( v.y / 255.0 ), v.z + ( v.w / 255.0 ) );\n}\nfloat viewZToOrthographicDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( viewZ + near ) / ( near - far );\n}\nfloat orthographicDepthToViewZ( const in float depth, const in float near, const in float far ) {\n\treturn depth * ( near - far ) - near;\n}\nfloat viewZToPerspectiveDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( ( near + viewZ ) * far ) / ( ( far - near ) * viewZ );\n}\nfloat perspectiveDepthToViewZ( const in float depth, const in float near, const in float far ) {\n\treturn ( near * far ) / ( ( far - near ) * depth - far );\n}",premultiplied_alpha_fragment:"#ifdef PREMULTIPLIED_ALPHA\n\tgl_FragColor.rgb *= gl_FragColor.a;\n#endif",project_vertex:"vec4 mvPosition = vec4( transformed, 1.0 );\n#ifdef USE_BATCHING\n\tmvPosition = batchingMatrix * mvPosition;\n#endif\n#ifdef USE_INSTANCING\n\tmvPosition = instanceMatrix * mvPosition;\n#endif\nmvPosition = modelViewMatrix * mvPosition;\ngl_Position = projectionMatrix * mvPosition;",dithering_fragment:"#ifdef DITHERING\n\tgl_FragColor.rgb = dithering( gl_FragColor.rgb );\n#endif",dithering_pars_fragment:"#ifdef DITHERING\n\tvec3 dithering( vec3 color ) {\n\t\tfloat grid_position = rand( gl_FragCoord.xy );\n\t\tvec3 dither_shift_RGB = vec3( 0.25 / 255.0, -0.25 / 255.0, 0.25 / 255.0 );\n\t\tdither_shift_RGB = mix( 2.0 * dither_shift_RGB, -2.0 * dither_shift_RGB, grid_position );\n\t\treturn color + dither_shift_RGB;\n\t}\n#endif",roughnessmap_fragment:"float roughnessFactor = roughness;\n#ifdef USE_ROUGHNESSMAP\n\tvec4 texelRoughness = texture2D( roughnessMap, vRoughnessMapUv );\n\troughnessFactor *= texelRoughness.g;\n#endif",roughnessmap_pars_fragment:"#ifdef USE_ROUGHNESSMAP\n\tuniform sampler2D roughnessMap;\n#endif",shadowmap_pars_fragment:"#if NUM_SPOT_LIGHT_COORDS > 0\n\tvarying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ];\n#endif\n#if NUM_SPOT_LIGHT_MAPS > 0\n\tuniform sampler2D spotLightMap[ NUM_SPOT_LIGHT_MAPS ];\n#endif\n#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D directionalShadowMap[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D spotShadowMap[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D pointShadowMap[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n\tfloat texture2DCompare( sampler2D depths, vec2 uv, float compare ) {\n\t\treturn step( compare, unpackRGBAToDepth( texture2D( depths, uv ) ) );\n\t}\n\tvec2 texture2DDistribution( sampler2D shadow, vec2 uv ) {\n\t\treturn unpackRGBATo2Half( texture2D( shadow, uv ) );\n\t}\n\tfloat VSMShadow (sampler2D shadow, vec2 uv, float compare ){\n\t\tfloat occlusion = 1.0;\n\t\tvec2 distribution = texture2DDistribution( shadow, uv );\n\t\tfloat hard_shadow = step( compare , distribution.x );\n\t\tif (hard_shadow != 1.0 ) {\n\t\t\tfloat distance = compare - distribution.x ;\n\t\t\tfloat variance = max( 0.00000, distribution.y * distribution.y );\n\t\t\tfloat softness_probability = variance / (variance + distance * distance );\t\t\tsoftness_probability = clamp( ( softness_probability - 0.3 ) / ( 0.95 - 0.3 ), 0.0, 1.0 );\t\t\tocclusion = clamp( max( hard_shadow, softness_probability ), 0.0, 1.0 );\n\t\t}\n\t\treturn occlusion;\n\t}\n\tfloat getShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowIntensity, float shadowBias, float shadowRadius, vec4 shadowCoord ) {\n\t\tfloat shadow = 1.0;\n\t\tshadowCoord.xyz /= shadowCoord.w;\n\t\tshadowCoord.z += shadowBias;\n\t\tbool inFrustum = shadowCoord.x >= 0.0 && shadowCoord.x <= 1.0 && shadowCoord.y >= 0.0 && shadowCoord.y <= 1.0;\n\t\tbool frustumTest = inFrustum && shadowCoord.z <= 1.0;\n\t\tif ( frustumTest ) {\n\t\t#if defined( SHADOWMAP_TYPE_PCF )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx0 = - texelSize.x * shadowRadius;\n\t\t\tfloat dy0 = - texelSize.y * shadowRadius;\n\t\t\tfloat dx1 = + texelSize.x * shadowRadius;\n\t\t\tfloat dy1 = + texelSize.y * shadowRadius;\n\t\t\tfloat dx2 = dx0 / 2.0;\n\t\t\tfloat dy2 = dy0 / 2.0;\n\t\t\tfloat dx3 = dx1 / 2.0;\n\t\t\tfloat dy3 = dy1 / 2.0;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy1 ), shadowCoord.z )\n\t\t\t) * ( 1.0 / 17.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_PCF_SOFT )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx = texelSize.x;\n\t\t\tfloat dy = texelSize.y;\n\t\t\tvec2 uv = shadowCoord.xy;\n\t\t\tvec2 f = fract( uv * shadowMapSize + 0.5 );\n\t\t\tuv -= f * texelSize;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, uv, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( dx, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( 0.0, dy ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + texelSize, shadowCoord.z ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, dy ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( 0.0, -dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 0.0, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( mix( texture2DCompare( shadowMap, uv + vec2( -dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t f.x ),\n\t\t\t\t\t mix( texture2DCompare( shadowMap, uv + vec2( -dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t f.x ),\n\t\t\t\t\t f.y )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_VSM )\n\t\t\tshadow = VSMShadow( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#else\n\t\t\tshadow = texture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#endif\n\t\t}\n\t\treturn mix( 1.0, shadow, shadowIntensity );\n\t}\n\tvec2 cubeToUV( vec3 v, float texelSizeY ) {\n\t\tvec3 absV = abs( v );\n\t\tfloat scaleToCube = 1.0 / max( absV.x, max( absV.y, absV.z ) );\n\t\tabsV *= scaleToCube;\n\t\tv *= scaleToCube * ( 1.0 - 2.0 * texelSizeY );\n\t\tvec2 planar = v.xy;\n\t\tfloat almostATexel = 1.5 * texelSizeY;\n\t\tfloat almostOne = 1.0 - almostATexel;\n\t\tif ( absV.z >= almostOne ) {\n\t\t\tif ( v.z > 0.0 )\n\t\t\t\tplanar.x = 4.0 - v.x;\n\t\t} else if ( absV.x >= almostOne ) {\n\t\t\tfloat signX = sign( v.x );\n\t\t\tplanar.x = v.z * signX + 2.0 * signX;\n\t\t} else if ( absV.y >= almostOne ) {\n\t\t\tfloat signY = sign( v.y );\n\t\t\tplanar.x = v.x + 2.0 * signY + 2.0;\n\t\t\tplanar.y = v.z * signY - 2.0;\n\t\t}\n\t\treturn vec2( 0.125, 0.25 ) * planar + vec2( 0.375, 0.75 );\n\t}\n\tfloat getPointShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowIntensity, float shadowBias, float shadowRadius, vec4 shadowCoord, float shadowCameraNear, float shadowCameraFar ) {\n\t\tfloat shadow = 1.0;\n\t\tvec3 lightToPosition = shadowCoord.xyz;\n\t\t\n\t\tfloat lightToPositionLength = length( lightToPosition );\n\t\tif ( lightToPositionLength - shadowCameraFar <= 0.0 && lightToPositionLength - shadowCameraNear >= 0.0 ) {\n\t\t\tfloat dp = ( lightToPositionLength - shadowCameraNear ) / ( shadowCameraFar - shadowCameraNear );\t\t\tdp += shadowBias;\n\t\t\tvec3 bd3D = normalize( lightToPosition );\n\t\t\tvec2 texelSize = vec2( 1.0 ) / ( shadowMapSize * vec2( 4.0, 2.0 ) );\n\t\t\t#if defined( SHADOWMAP_TYPE_PCF ) || defined( SHADOWMAP_TYPE_PCF_SOFT ) || defined( SHADOWMAP_TYPE_VSM )\n\t\t\t\tvec2 offset = vec2( - 1, 1 ) * shadowRadius * texelSize.y;\n\t\t\t\tshadow = (\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyy, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyy, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyx, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyx, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxy, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxy, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxx, texelSize.y ), dp ) +\n\t\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxx, texelSize.y ), dp )\n\t\t\t\t) * ( 1.0 / 9.0 );\n\t\t\t#else\n\t\t\t\tshadow = texture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp );\n\t\t\t#endif\n\t\t}\n\t\treturn mix( 1.0, shadow, shadowIntensity );\n\t}\n#endif",shadowmap_pars_vertex:"#if NUM_SPOT_LIGHT_COORDS > 0\n\tuniform mat4 spotLightMatrix[ NUM_SPOT_LIGHT_COORDS ];\n\tvarying vec4 vSpotLightCoord[ NUM_SPOT_LIGHT_COORDS ];\n#endif\n#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform mat4 directionalShadowMatrix[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform mat4 pointShadowMatrix[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowIntensity;\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n#endif",shadowmap_vertex:"#if ( defined( USE_SHADOWMAP ) && ( NUM_DIR_LIGHT_SHADOWS > 0 || NUM_POINT_LIGHT_SHADOWS > 0 ) ) || ( NUM_SPOT_LIGHT_COORDS > 0 )\n\tvec3 shadowWorldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\tvec4 shadowWorldPosition;\n#endif\n#if defined( USE_SHADOWMAP )\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * directionalLightShadows[ i ].shadowNormalBias, 0 );\n\t\t\tvDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * shadowWorldPosition;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * pointLightShadows[ i ].shadowNormalBias, 0 );\n\t\t\tvPointShadowCoord[ i ] = pointShadowMatrix[ i ] * shadowWorldPosition;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n#endif\n#if NUM_SPOT_LIGHT_COORDS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_COORDS; i ++ ) {\n\t\tshadowWorldPosition = worldPosition;\n\t\t#if ( defined( USE_SHADOWMAP ) && UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\t\tshadowWorldPosition.xyz += shadowWorldNormal * spotLightShadows[ i ].shadowNormalBias;\n\t\t#endif\n\t\tvSpotLightCoord[ i ] = spotLightMatrix[ i ] * shadowWorldPosition;\n\t}\n\t#pragma unroll_loop_end\n#endif",shadowmask_pars_fragment:"float getShadowMask() {\n\tfloat shadow = 1.0;\n\t#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\tdirectionalLight = directionalLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowIntensity, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_SHADOWS; i ++ ) {\n\t\tspotLight = spotLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowIntensity, spotLight.shadowBias, spotLight.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\tpointLight = pointLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowIntensity, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#endif\n\treturn shadow;\n}",skinbase_vertex:"#ifdef USE_SKINNING\n\tmat4 boneMatX = getBoneMatrix( skinIndex.x );\n\tmat4 boneMatY = getBoneMatrix( skinIndex.y );\n\tmat4 boneMatZ = getBoneMatrix( skinIndex.z );\n\tmat4 boneMatW = getBoneMatrix( skinIndex.w );\n#endif",skinning_pars_vertex:"#ifdef USE_SKINNING\n\tuniform mat4 bindMatrix;\n\tuniform mat4 bindMatrixInverse;\n\tuniform highp sampler2D boneTexture;\n\tmat4 getBoneMatrix( const in float i ) {\n\t\tint size = textureSize( boneTexture, 0 ).x;\n\t\tint j = int( i ) * 4;\n\t\tint x = j % size;\n\t\tint y = j / size;\n\t\tvec4 v1 = texelFetch( boneTexture, ivec2( x, y ), 0 );\n\t\tvec4 v2 = texelFetch( boneTexture, ivec2( x + 1, y ), 0 );\n\t\tvec4 v3 = texelFetch( boneTexture, ivec2( x + 2, y ), 0 );\n\t\tvec4 v4 = texelFetch( boneTexture, ivec2( x + 3, y ), 0 );\n\t\treturn mat4( v1, v2, v3, v4 );\n\t}\n#endif",skinning_vertex:"#ifdef USE_SKINNING\n\tvec4 skinVertex = bindMatrix * vec4( transformed, 1.0 );\n\tvec4 skinned = vec4( 0.0 );\n\tskinned += boneMatX * skinVertex * skinWeight.x;\n\tskinned += boneMatY * skinVertex * skinWeight.y;\n\tskinned += boneMatZ * skinVertex * skinWeight.z;\n\tskinned += boneMatW * skinVertex * skinWeight.w;\n\ttransformed = ( bindMatrixInverse * skinned ).xyz;\n#endif",skinnormal_vertex:"#ifdef USE_SKINNING\n\tmat4 skinMatrix = mat4( 0.0 );\n\tskinMatrix += skinWeight.x * boneMatX;\n\tskinMatrix += skinWeight.y * boneMatY;\n\tskinMatrix += skinWeight.z * boneMatZ;\n\tskinMatrix += skinWeight.w * boneMatW;\n\tskinMatrix = bindMatrixInverse * skinMatrix * bindMatrix;\n\tobjectNormal = vec4( skinMatrix * vec4( objectNormal, 0.0 ) ).xyz;\n\t#ifdef USE_TANGENT\n\t\tobjectTangent = vec4( skinMatrix * vec4( objectTangent, 0.0 ) ).xyz;\n\t#endif\n#endif",specularmap_fragment:"float specularStrength;\n#ifdef USE_SPECULARMAP\n\tvec4 texelSpecular = texture2D( specularMap, vSpecularMapUv );\n\tspecularStrength = texelSpecular.r;\n#else\n\tspecularStrength = 1.0;\n#endif",specularmap_pars_fragment:"#ifdef USE_SPECULARMAP\n\tuniform sampler2D specularMap;\n#endif",tonemapping_fragment:"#if defined( TONE_MAPPING )\n\tgl_FragColor.rgb = toneMapping( gl_FragColor.rgb );\n#endif",tonemapping_pars_fragment:"#ifndef saturate\n#define saturate( a ) clamp( a, 0.0, 1.0 )\n#endif\nuniform float toneMappingExposure;\nvec3 LinearToneMapping( vec3 color ) {\n\treturn saturate( toneMappingExposure * color );\n}\nvec3 ReinhardToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\treturn saturate( color / ( vec3( 1.0 ) + color ) );\n}\nvec3 CineonToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\tcolor = max( vec3( 0.0 ), color - 0.004 );\n\treturn pow( ( color * ( 6.2 * color + 0.5 ) ) / ( color * ( 6.2 * color + 1.7 ) + 0.06 ), vec3( 2.2 ) );\n}\nvec3 RRTAndODTFit( vec3 v ) {\n\tvec3 a = v * ( v + 0.0245786 ) - 0.000090537;\n\tvec3 b = v * ( 0.983729 * v + 0.4329510 ) + 0.238081;\n\treturn a / b;\n}\nvec3 ACESFilmicToneMapping( vec3 color ) {\n\tconst mat3 ACESInputMat = mat3(\n\t\tvec3( 0.59719, 0.07600, 0.02840 ),\t\tvec3( 0.35458, 0.90834, 0.13383 ),\n\t\tvec3( 0.04823, 0.01566, 0.83777 )\n\t);\n\tconst mat3 ACESOutputMat = mat3(\n\t\tvec3( 1.60475, -0.10208, -0.00327 ),\t\tvec3( -0.53108, 1.10813, -0.07276 ),\n\t\tvec3( -0.07367, -0.00605, 1.07602 )\n\t);\n\tcolor *= toneMappingExposure / 0.6;\n\tcolor = ACESInputMat * color;\n\tcolor = RRTAndODTFit( color );\n\tcolor = ACESOutputMat * color;\n\treturn saturate( color );\n}\nconst mat3 LINEAR_REC2020_TO_LINEAR_SRGB = mat3(\n\tvec3( 1.6605, - 0.1246, - 0.0182 ),\n\tvec3( - 0.5876, 1.1329, - 0.1006 ),\n\tvec3( - 0.0728, - 0.0083, 1.1187 )\n);\nconst mat3 LINEAR_SRGB_TO_LINEAR_REC2020 = mat3(\n\tvec3( 0.6274, 0.0691, 0.0164 ),\n\tvec3( 0.3293, 0.9195, 0.0880 ),\n\tvec3( 0.0433, 0.0113, 0.8956 )\n);\nvec3 agxDefaultContrastApprox( vec3 x ) {\n\tvec3 x2 = x * x;\n\tvec3 x4 = x2 * x2;\n\treturn + 15.5 * x4 * x2\n\t\t- 40.14 * x4 * x\n\t\t+ 31.96 * x4\n\t\t- 6.868 * x2 * x\n\t\t+ 0.4298 * x2\n\t\t+ 0.1191 * x\n\t\t- 0.00232;\n}\nvec3 AgXToneMapping( vec3 color ) {\n\tconst mat3 AgXInsetMatrix = mat3(\n\t\tvec3( 0.856627153315983, 0.137318972929847, 0.11189821299995 ),\n\t\tvec3( 0.0951212405381588, 0.761241990602591, 0.0767994186031903 ),\n\t\tvec3( 0.0482516061458583, 0.101439036467562, 0.811302368396859 )\n\t);\n\tconst mat3 AgXOutsetMatrix = mat3(\n\t\tvec3( 1.1271005818144368, - 0.1413297634984383, - 0.14132976349843826 ),\n\t\tvec3( - 0.11060664309660323, 1.157823702216272, - 0.11060664309660294 ),\n\t\tvec3( - 0.016493938717834573, - 0.016493938717834257, 1.2519364065950405 )\n\t);\n\tconst float AgxMinEv = - 12.47393;\tconst float AgxMaxEv = 4.026069;\n\tcolor *= toneMappingExposure;\n\tcolor = LINEAR_SRGB_TO_LINEAR_REC2020 * color;\n\tcolor = AgXInsetMatrix * color;\n\tcolor = max( color, 1e-10 );\tcolor = log2( color );\n\tcolor = ( color - AgxMinEv ) / ( AgxMaxEv - AgxMinEv );\n\tcolor = clamp( color, 0.0, 1.0 );\n\tcolor = agxDefaultContrastApprox( color );\n\tcolor = AgXOutsetMatrix * color;\n\tcolor = pow( max( vec3( 0.0 ), color ), vec3( 2.2 ) );\n\tcolor = LINEAR_REC2020_TO_LINEAR_SRGB * color;\n\tcolor = clamp( color, 0.0, 1.0 );\n\treturn color;\n}\nvec3 NeutralToneMapping( vec3 color ) {\n\tconst float StartCompression = 0.8 - 0.04;\n\tconst float Desaturation = 0.15;\n\tcolor *= toneMappingExposure;\n\tfloat x = min( color.r, min( color.g, color.b ) );\n\tfloat offset = x < 0.08 ? x - 6.25 * x * x : 0.04;\n\tcolor -= offset;\n\tfloat peak = max( color.r, max( color.g, color.b ) );\n\tif ( peak < StartCompression ) return color;\n\tfloat d = 1. - StartCompression;\n\tfloat newPeak = 1. - d * d / ( peak + d - StartCompression );\n\tcolor *= newPeak / peak;\n\tfloat g = 1. - 1. / ( Desaturation * ( peak - newPeak ) + 1. );\n\treturn mix( color, vec3( newPeak ), g );\n}\nvec3 CustomToneMapping( vec3 color ) { return color; }",transmission_fragment:"#ifdef USE_TRANSMISSION\n\tmaterial.transmission = transmission;\n\tmaterial.transmissionAlpha = 1.0;\n\tmaterial.thickness = thickness;\n\tmaterial.attenuationDistance = attenuationDistance;\n\tmaterial.attenuationColor = attenuationColor;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tmaterial.transmission *= texture2D( transmissionMap, vTransmissionMapUv ).r;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tmaterial.thickness *= texture2D( thicknessMap, vThicknessMapUv ).g;\n\t#endif\n\tvec3 pos = vWorldPosition;\n\tvec3 v = normalize( cameraPosition - pos );\n\tvec3 n = inverseTransformDirection( normal, viewMatrix );\n\tvec4 transmitted = getIBLVolumeRefraction(\n\t\tn, v, material.roughness, material.diffuseColor, material.specularColor, material.specularF90,\n\t\tpos, modelMatrix, viewMatrix, projectionMatrix, material.dispersion, material.ior, material.thickness,\n\t\tmaterial.attenuationColor, material.attenuationDistance );\n\tmaterial.transmissionAlpha = mix( material.transmissionAlpha, transmitted.a, material.transmission );\n\ttotalDiffuse = mix( totalDiffuse, transmitted.rgb, material.transmission );\n#endif",transmission_pars_fragment:"#ifdef USE_TRANSMISSION\n\tuniform float transmission;\n\tuniform float thickness;\n\tuniform float attenuationDistance;\n\tuniform vec3 attenuationColor;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tuniform sampler2D transmissionMap;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tuniform sampler2D thicknessMap;\n\t#endif\n\tuniform vec2 transmissionSamplerSize;\n\tuniform sampler2D transmissionSamplerMap;\n\tuniform mat4 modelMatrix;\n\tuniform mat4 projectionMatrix;\n\tvarying vec3 vWorldPosition;\n\tfloat w0( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * ( a * ( - a + 3.0 ) - 3.0 ) + 1.0 );\n\t}\n\tfloat w1( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * a * ( 3.0 * a - 6.0 ) + 4.0 );\n\t}\n\tfloat w2( float a ){\n\t\treturn ( 1.0 / 6.0 ) * ( a * ( a * ( - 3.0 * a + 3.0 ) + 3.0 ) + 1.0 );\n\t}\n\tfloat w3( float a ) {\n\t\treturn ( 1.0 / 6.0 ) * ( a * a * a );\n\t}\n\tfloat g0( float a ) {\n\t\treturn w0( a ) + w1( a );\n\t}\n\tfloat g1( float a ) {\n\t\treturn w2( a ) + w3( a );\n\t}\n\tfloat h0( float a ) {\n\t\treturn - 1.0 + w1( a ) / ( w0( a ) + w1( a ) );\n\t}\n\tfloat h1( float a ) {\n\t\treturn 1.0 + w3( a ) / ( w2( a ) + w3( a ) );\n\t}\n\tvec4 bicubic( sampler2D tex, vec2 uv, vec4 texelSize, float lod ) {\n\t\tuv = uv * texelSize.zw + 0.5;\n\t\tvec2 iuv = floor( uv );\n\t\tvec2 fuv = fract( uv );\n\t\tfloat g0x = g0( fuv.x );\n\t\tfloat g1x = g1( fuv.x );\n\t\tfloat h0x = h0( fuv.x );\n\t\tfloat h1x = h1( fuv.x );\n\t\tfloat h0y = h0( fuv.y );\n\t\tfloat h1y = h1( fuv.y );\n\t\tvec2 p0 = ( vec2( iuv.x + h0x, iuv.y + h0y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p1 = ( vec2( iuv.x + h1x, iuv.y + h0y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p2 = ( vec2( iuv.x + h0x, iuv.y + h1y ) - 0.5 ) * texelSize.xy;\n\t\tvec2 p3 = ( vec2( iuv.x + h1x, iuv.y + h1y ) - 0.5 ) * texelSize.xy;\n\t\treturn g0( fuv.y ) * ( g0x * textureLod( tex, p0, lod ) + g1x * textureLod( tex, p1, lod ) ) +\n\t\t\tg1( fuv.y ) * ( g0x * textureLod( tex, p2, lod ) + g1x * textureLod( tex, p3, lod ) );\n\t}\n\tvec4 textureBicubic( sampler2D sampler, vec2 uv, float lod ) {\n\t\tvec2 fLodSize = vec2( textureSize( sampler, int( lod ) ) );\n\t\tvec2 cLodSize = vec2( textureSize( sampler, int( lod + 1.0 ) ) );\n\t\tvec2 fLodSizeInv = 1.0 / fLodSize;\n\t\tvec2 cLodSizeInv = 1.0 / cLodSize;\n\t\tvec4 fSample = bicubic( sampler, uv, vec4( fLodSizeInv, fLodSize ), floor( lod ) );\n\t\tvec4 cSample = bicubic( sampler, uv, vec4( cLodSizeInv, cLodSize ), ceil( lod ) );\n\t\treturn mix( fSample, cSample, fract( lod ) );\n\t}\n\tvec3 getVolumeTransmissionRay( const in vec3 n, const in vec3 v, const in float thickness, const in float ior, const in mat4 modelMatrix ) {\n\t\tvec3 refractionVector = refract( - v, normalize( n ), 1.0 / ior );\n\t\tvec3 modelScale;\n\t\tmodelScale.x = length( vec3( modelMatrix[ 0 ].xyz ) );\n\t\tmodelScale.y = length( vec3( modelMatrix[ 1 ].xyz ) );\n\t\tmodelScale.z = length( vec3( modelMatrix[ 2 ].xyz ) );\n\t\treturn normalize( refractionVector ) * thickness * modelScale;\n\t}\n\tfloat applyIorToRoughness( const in float roughness, const in float ior ) {\n\t\treturn roughness * clamp( ior * 2.0 - 2.0, 0.0, 1.0 );\n\t}\n\tvec4 getTransmissionSample( const in vec2 fragCoord, const in float roughness, const in float ior ) {\n\t\tfloat lod = log2( transmissionSamplerSize.x ) * applyIorToRoughness( roughness, ior );\n\t\treturn textureBicubic( transmissionSamplerMap, fragCoord.xy, lod );\n\t}\n\tvec3 volumeAttenuation( const in float transmissionDistance, const in vec3 attenuationColor, const in float attenuationDistance ) {\n\t\tif ( isinf( attenuationDistance ) ) {\n\t\t\treturn vec3( 1.0 );\n\t\t} else {\n\t\t\tvec3 attenuationCoefficient = -log( attenuationColor ) / attenuationDistance;\n\t\t\tvec3 transmittance = exp( - attenuationCoefficient * transmissionDistance );\t\t\treturn transmittance;\n\t\t}\n\t}\n\tvec4 getIBLVolumeRefraction( const in vec3 n, const in vec3 v, const in float roughness, const in vec3 diffuseColor,\n\t\tconst in vec3 specularColor, const in float specularF90, const in vec3 position, const in mat4 modelMatrix,\n\t\tconst in mat4 viewMatrix, const in mat4 projMatrix, const in float dispersion, const in float ior, const in float thickness,\n\t\tconst in vec3 attenuationColor, const in float attenuationDistance ) {\n\t\tvec4 transmittedLight;\n\t\tvec3 transmittance;\n\t\t#ifdef USE_DISPERSION\n\t\t\tfloat halfSpread = ( ior - 1.0 ) * 0.025 * dispersion;\n\t\t\tvec3 iors = vec3( ior - halfSpread, ior, ior + halfSpread );\n\t\t\tfor ( int i = 0; i < 3; i ++ ) {\n\t\t\t\tvec3 transmissionRay = getVolumeTransmissionRay( n, v, thickness, iors[ i ], modelMatrix );\n\t\t\t\tvec3 refractedRayExit = position + transmissionRay;\n\t\t\t\tvec4 ndcPos = projMatrix * viewMatrix * vec4( refractedRayExit, 1.0 );\n\t\t\t\tvec2 refractionCoords = ndcPos.xy / ndcPos.w;\n\t\t\t\trefractionCoords += 1.0;\n\t\t\t\trefractionCoords /= 2.0;\n\t\t\t\tvec4 transmissionSample = getTransmissionSample( refractionCoords, roughness, iors[ i ] );\n\t\t\t\ttransmittedLight[ i ] = transmissionSample[ i ];\n\t\t\t\ttransmittedLight.a += transmissionSample.a;\n\t\t\t\ttransmittance[ i ] = diffuseColor[ i ] * volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance )[ i ];\n\t\t\t}\n\t\t\ttransmittedLight.a /= 3.0;\n\t\t#else\n\t\t\tvec3 transmissionRay = getVolumeTransmissionRay( n, v, thickness, ior, modelMatrix );\n\t\t\tvec3 refractedRayExit = position + transmissionRay;\n\t\t\tvec4 ndcPos = projMatrix * viewMatrix * vec4( refractedRayExit, 1.0 );\n\t\t\tvec2 refractionCoords = ndcPos.xy / ndcPos.w;\n\t\t\trefractionCoords += 1.0;\n\t\t\trefractionCoords /= 2.0;\n\t\t\ttransmittedLight = getTransmissionSample( refractionCoords, roughness, ior );\n\t\t\ttransmittance = diffuseColor * volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance );\n\t\t#endif\n\t\tvec3 attenuatedColor = transmittance * transmittedLight.rgb;\n\t\tvec3 F = EnvironmentBRDF( n, v, specularColor, specularF90, roughness );\n\t\tfloat transmittanceFactor = ( transmittance.r + transmittance.g + transmittance.b ) / 3.0;\n\t\treturn vec4( ( 1.0 - F ) * attenuatedColor, 1.0 - ( 1.0 - transmittedLight.a ) * transmittanceFactor );\n\t}\n#endif",uv_pars_fragment:"#if defined( USE_UV ) || defined( USE_ANISOTROPY )\n\tvarying vec2 vUv;\n#endif\n#ifdef USE_MAP\n\tvarying vec2 vMapUv;\n#endif\n#ifdef USE_ALPHAMAP\n\tvarying vec2 vAlphaMapUv;\n#endif\n#ifdef USE_LIGHTMAP\n\tvarying vec2 vLightMapUv;\n#endif\n#ifdef USE_AOMAP\n\tvarying vec2 vAoMapUv;\n#endif\n#ifdef USE_BUMPMAP\n\tvarying vec2 vBumpMapUv;\n#endif\n#ifdef USE_NORMALMAP\n\tvarying vec2 vNormalMapUv;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tvarying vec2 vEmissiveMapUv;\n#endif\n#ifdef USE_METALNESSMAP\n\tvarying vec2 vMetalnessMapUv;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tvarying vec2 vRoughnessMapUv;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tvarying vec2 vAnisotropyMapUv;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tvarying vec2 vClearcoatMapUv;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tvarying vec2 vClearcoatNormalMapUv;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tvarying vec2 vClearcoatRoughnessMapUv;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tvarying vec2 vIridescenceMapUv;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tvarying vec2 vIridescenceThicknessMapUv;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tvarying vec2 vSheenColorMapUv;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tvarying vec2 vSheenRoughnessMapUv;\n#endif\n#ifdef USE_SPECULARMAP\n\tvarying vec2 vSpecularMapUv;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tvarying vec2 vSpecularColorMapUv;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tvarying vec2 vSpecularIntensityMapUv;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tuniform mat3 transmissionMapTransform;\n\tvarying vec2 vTransmissionMapUv;\n#endif\n#ifdef USE_THICKNESSMAP\n\tuniform mat3 thicknessMapTransform;\n\tvarying vec2 vThicknessMapUv;\n#endif",uv_pars_vertex:"#if defined( USE_UV ) || defined( USE_ANISOTROPY )\n\tvarying vec2 vUv;\n#endif\n#ifdef USE_MAP\n\tuniform mat3 mapTransform;\n\tvarying vec2 vMapUv;\n#endif\n#ifdef USE_ALPHAMAP\n\tuniform mat3 alphaMapTransform;\n\tvarying vec2 vAlphaMapUv;\n#endif\n#ifdef USE_LIGHTMAP\n\tuniform mat3 lightMapTransform;\n\tvarying vec2 vLightMapUv;\n#endif\n#ifdef USE_AOMAP\n\tuniform mat3 aoMapTransform;\n\tvarying vec2 vAoMapUv;\n#endif\n#ifdef USE_BUMPMAP\n\tuniform mat3 bumpMapTransform;\n\tvarying vec2 vBumpMapUv;\n#endif\n#ifdef USE_NORMALMAP\n\tuniform mat3 normalMapTransform;\n\tvarying vec2 vNormalMapUv;\n#endif\n#ifdef USE_DISPLACEMENTMAP\n\tuniform mat3 displacementMapTransform;\n\tvarying vec2 vDisplacementMapUv;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tuniform mat3 emissiveMapTransform;\n\tvarying vec2 vEmissiveMapUv;\n#endif\n#ifdef USE_METALNESSMAP\n\tuniform mat3 metalnessMapTransform;\n\tvarying vec2 vMetalnessMapUv;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tuniform mat3 roughnessMapTransform;\n\tvarying vec2 vRoughnessMapUv;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tuniform mat3 anisotropyMapTransform;\n\tvarying vec2 vAnisotropyMapUv;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tuniform mat3 clearcoatMapTransform;\n\tvarying vec2 vClearcoatMapUv;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tuniform mat3 clearcoatNormalMapTransform;\n\tvarying vec2 vClearcoatNormalMapUv;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tuniform mat3 clearcoatRoughnessMapTransform;\n\tvarying vec2 vClearcoatRoughnessMapUv;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tuniform mat3 sheenColorMapTransform;\n\tvarying vec2 vSheenColorMapUv;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tuniform mat3 sheenRoughnessMapTransform;\n\tvarying vec2 vSheenRoughnessMapUv;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tuniform mat3 iridescenceMapTransform;\n\tvarying vec2 vIridescenceMapUv;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tuniform mat3 iridescenceThicknessMapTransform;\n\tvarying vec2 vIridescenceThicknessMapUv;\n#endif\n#ifdef USE_SPECULARMAP\n\tuniform mat3 specularMapTransform;\n\tvarying vec2 vSpecularMapUv;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tuniform mat3 specularColorMapTransform;\n\tvarying vec2 vSpecularColorMapUv;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tuniform mat3 specularIntensityMapTransform;\n\tvarying vec2 vSpecularIntensityMapUv;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tuniform mat3 transmissionMapTransform;\n\tvarying vec2 vTransmissionMapUv;\n#endif\n#ifdef USE_THICKNESSMAP\n\tuniform mat3 thicknessMapTransform;\n\tvarying vec2 vThicknessMapUv;\n#endif",uv_vertex:"#if defined( USE_UV ) || defined( USE_ANISOTROPY )\n\tvUv = vec3( uv, 1 ).xy;\n#endif\n#ifdef USE_MAP\n\tvMapUv = ( mapTransform * vec3( MAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ALPHAMAP\n\tvAlphaMapUv = ( alphaMapTransform * vec3( ALPHAMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_LIGHTMAP\n\tvLightMapUv = ( lightMapTransform * vec3( LIGHTMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_AOMAP\n\tvAoMapUv = ( aoMapTransform * vec3( AOMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_BUMPMAP\n\tvBumpMapUv = ( bumpMapTransform * vec3( BUMPMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_NORMALMAP\n\tvNormalMapUv = ( normalMapTransform * vec3( NORMALMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_DISPLACEMENTMAP\n\tvDisplacementMapUv = ( displacementMapTransform * vec3( DISPLACEMENTMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_EMISSIVEMAP\n\tvEmissiveMapUv = ( emissiveMapTransform * vec3( EMISSIVEMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_METALNESSMAP\n\tvMetalnessMapUv = ( metalnessMapTransform * vec3( METALNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ROUGHNESSMAP\n\tvRoughnessMapUv = ( roughnessMapTransform * vec3( ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_ANISOTROPYMAP\n\tvAnisotropyMapUv = ( anisotropyMapTransform * vec3( ANISOTROPYMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOATMAP\n\tvClearcoatMapUv = ( clearcoatMapTransform * vec3( CLEARCOATMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tvClearcoatNormalMapUv = ( clearcoatNormalMapTransform * vec3( CLEARCOAT_NORMALMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tvClearcoatRoughnessMapUv = ( clearcoatRoughnessMapTransform * vec3( CLEARCOAT_ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_IRIDESCENCEMAP\n\tvIridescenceMapUv = ( iridescenceMapTransform * vec3( IRIDESCENCEMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_IRIDESCENCE_THICKNESSMAP\n\tvIridescenceThicknessMapUv = ( iridescenceThicknessMapTransform * vec3( IRIDESCENCE_THICKNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SHEEN_COLORMAP\n\tvSheenColorMapUv = ( sheenColorMapTransform * vec3( SHEEN_COLORMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SHEEN_ROUGHNESSMAP\n\tvSheenRoughnessMapUv = ( sheenRoughnessMapTransform * vec3( SHEEN_ROUGHNESSMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULARMAP\n\tvSpecularMapUv = ( specularMapTransform * vec3( SPECULARMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULAR_COLORMAP\n\tvSpecularColorMapUv = ( specularColorMapTransform * vec3( SPECULAR_COLORMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_SPECULAR_INTENSITYMAP\n\tvSpecularIntensityMapUv = ( specularIntensityMapTransform * vec3( SPECULAR_INTENSITYMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_TRANSMISSIONMAP\n\tvTransmissionMapUv = ( transmissionMapTransform * vec3( TRANSMISSIONMAP_UV, 1 ) ).xy;\n#endif\n#ifdef USE_THICKNESSMAP\n\tvThicknessMapUv = ( thicknessMapTransform * vec3( THICKNESSMAP_UV, 1 ) ).xy;\n#endif",worldpos_vertex:"#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined ( USE_SHADOWMAP ) || defined ( USE_TRANSMISSION ) || NUM_SPOT_LIGHT_COORDS > 0\n\tvec4 worldPosition = vec4( transformed, 1.0 );\n\t#ifdef USE_BATCHING\n\t\tworldPosition = batchingMatrix * worldPosition;\n\t#endif\n\t#ifdef USE_INSTANCING\n\t\tworldPosition = instanceMatrix * worldPosition;\n\t#endif\n\tworldPosition = modelMatrix * worldPosition;\n#endif",background_vert:"varying vec2 vUv;\nuniform mat3 uvTransform;\nvoid main() {\n\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n\tgl_Position = vec4( position.xy, 1.0, 1.0 );\n}",background_frag:"uniform sampler2D t2D;\nuniform float backgroundIntensity;\nvarying vec2 vUv;\nvoid main() {\n\tvec4 texColor = texture2D( t2D, vUv );\n\t#ifdef DECODE_VIDEO_TEXTURE\n\t\ttexColor = vec4( mix( pow( texColor.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), texColor.rgb * 0.0773993808, vec3( lessThanEqual( texColor.rgb, vec3( 0.04045 ) ) ) ), texColor.w );\n\t#endif\n\ttexColor.rgb *= backgroundIntensity;\n\tgl_FragColor = texColor;\n\t#include \n\t#include \n}",backgroundCube_vert:"varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n\tgl_Position.z = gl_Position.w;\n}",backgroundCube_frag:"#ifdef ENVMAP_TYPE_CUBE\n\tuniform samplerCube envMap;\n#elif defined( ENVMAP_TYPE_CUBE_UV )\n\tuniform sampler2D envMap;\n#endif\nuniform float flipEnvMap;\nuniform float backgroundBlurriness;\nuniform float backgroundIntensity;\nuniform mat3 backgroundRotation;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 texColor = textureCube( envMap, backgroundRotation * vec3( flipEnvMap * vWorldDirection.x, vWorldDirection.yz ) );\n\t#elif defined( ENVMAP_TYPE_CUBE_UV )\n\t\tvec4 texColor = textureCubeUV( envMap, backgroundRotation * vWorldDirection, backgroundBlurriness );\n\t#else\n\t\tvec4 texColor = vec4( 0.0, 0.0, 0.0, 1.0 );\n\t#endif\n\ttexColor.rgb *= backgroundIntensity;\n\tgl_FragColor = texColor;\n\t#include \n\t#include \n}",cube_vert:"varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n\tgl_Position.z = gl_Position.w;\n}",cube_frag:"uniform samplerCube tCube;\nuniform float tFlip;\nuniform float opacity;\nvarying vec3 vWorldDirection;\nvoid main() {\n\tvec4 texColor = textureCube( tCube, vec3( tFlip * vWorldDirection.x, vWorldDirection.yz ) );\n\tgl_FragColor = texColor;\n\tgl_FragColor.a *= opacity;\n\t#include \n\t#include \n}",depth_vert:"#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvHighPrecisionZW = gl_Position.zw;\n}",depth_frag:"#if DEPTH_PACKING == 3200\n\tuniform float opacity;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include \n\t#if DEPTH_PACKING == 3200\n\t\tdiffuseColor.a = opacity;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tfloat fragCoordZ = 0.5 * vHighPrecisionZW[0] / vHighPrecisionZW[1] + 0.5;\n\t#if DEPTH_PACKING == 3200\n\t\tgl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity );\n\t#elif DEPTH_PACKING == 3201\n\t\tgl_FragColor = packDepthToRGBA( fragCoordZ );\n\t#elif DEPTH_PACKING == 3202\n\t\tgl_FragColor = vec4( packDepthToRGB( fragCoordZ ), 1.0 );\n\t#elif DEPTH_PACKING == 3203\n\t\tgl_FragColor = vec4( packDepthToRG( fragCoordZ ), 0.0, 1.0 );\n\t#endif\n}",distanceRGBA_vert:"#define DISTANCE\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvWorldPosition = worldPosition.xyz;\n}",distanceRGBA_frag:"#define DISTANCE\nuniform vec3 referencePosition;\nuniform float nearDistance;\nuniform float farDistance;\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main () {\n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tfloat dist = length( vWorldPosition - referencePosition );\n\tdist = ( dist - nearDistance ) / ( farDistance - nearDistance );\n\tdist = saturate( dist );\n\tgl_FragColor = packDepthToRGBA( dist );\n}",equirect_vert:"varying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n}",equirect_frag:"uniform sampler2D tEquirect;\nvarying vec3 vWorldDirection;\n#include \nvoid main() {\n\tvec3 direction = normalize( vWorldDirection );\n\tvec2 sampleUV = equirectUv( direction );\n\tgl_FragColor = texture2D( tEquirect, sampleUV );\n\t#include \n\t#include \n}",linedashed_vert:"uniform float scale;\nattribute float lineDistance;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvLineDistance = scale * lineDistance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",linedashed_frag:"uniform vec3 diffuse;\nuniform float opacity;\nuniform float dashSize;\nuniform float totalSize;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tif ( mod( vLineDistance, totalSize ) > dashSize ) {\n\t\tdiscard;\n\t}\n\tvec3 outgoingLight = vec3( 0.0 );\n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshbasic_vert:"#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#if defined ( USE_ENVMAP ) || defined ( USE_SKINNING )\n\t\t#include \n\t\t#include \n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshbasic_frag:"uniform vec3 diffuse;\nuniform float opacity;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel = texture2D( lightMap, vLightMapUv );\n\t\treflectedLight.indirectDiffuse += lightMapTexel.rgb * lightMapIntensity * RECIPROCAL_PI;\n\t#else\n\t\treflectedLight.indirectDiffuse += vec3( 1.0 );\n\t#endif\n\t#include \n\treflectedLight.indirectDiffuse *= diffuseColor.rgb;\n\tvec3 outgoingLight = reflectedLight.indirectDiffuse;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshlambert_vert:"#define LAMBERT\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n\t#include \n}",meshlambert_frag:"#define LAMBERT\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshmatcap_vert:"#define MATCAP\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n}",meshmatcap_frag:"#define MATCAP\nuniform vec3 diffuse;\nuniform float opacity;\nuniform sampler2D matcap;\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 viewDir = normalize( vViewPosition );\n\tvec3 x = normalize( vec3( viewDir.z, 0.0, - viewDir.x ) );\n\tvec3 y = cross( viewDir, x );\n\tvec2 uv = vec2( dot( x, normal ), dot( y, normal ) ) * 0.495 + 0.5;\n\t#ifdef USE_MATCAP\n\t\tvec4 matcapColor = texture2D( matcap, uv );\n\t#else\n\t\tvec4 matcapColor = vec4( vec3( mix( 0.2, 0.8, uv.y ) ), 1.0 );\n\t#endif\n\tvec3 outgoingLight = diffuseColor.rgb * matcapColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshnormal_vert:"#define NORMAL\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvarying vec3 vViewPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvViewPosition = - mvPosition.xyz;\n#endif\n}",meshnormal_frag:"#define NORMAL\nuniform float opacity;\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )\n\tvarying vec3 vViewPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( 0.0, 0.0, 0.0, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\tgl_FragColor = vec4( packNormalToRGB( normal ), diffuseColor.a );\n\t#ifdef OPAQUE\n\t\tgl_FragColor.a = 1.0;\n\t#endif\n}",meshphong_vert:"#define PHONG\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n\t#include \n}",meshphong_frag:"#define PHONG\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform vec3 specular;\nuniform float shininess;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshphysical_vert:"#define STANDARD\nvarying vec3 vViewPosition;\n#ifdef USE_TRANSMISSION\n\tvarying vec3 vWorldPosition;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n#ifdef USE_TRANSMISSION\n\tvWorldPosition = worldPosition.xyz;\n#endif\n}",meshphysical_frag:"#define STANDARD\n#ifdef PHYSICAL\n\t#define IOR\n\t#define USE_SPECULAR\n#endif\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float roughness;\nuniform float metalness;\nuniform float opacity;\n#ifdef IOR\n\tuniform float ior;\n#endif\n#ifdef USE_SPECULAR\n\tuniform float specularIntensity;\n\tuniform vec3 specularColor;\n\t#ifdef USE_SPECULAR_COLORMAP\n\t\tuniform sampler2D specularColorMap;\n\t#endif\n\t#ifdef USE_SPECULAR_INTENSITYMAP\n\t\tuniform sampler2D specularIntensityMap;\n\t#endif\n#endif\n#ifdef USE_CLEARCOAT\n\tuniform float clearcoat;\n\tuniform float clearcoatRoughness;\n#endif\n#ifdef USE_DISPERSION\n\tuniform float dispersion;\n#endif\n#ifdef USE_IRIDESCENCE\n\tuniform float iridescence;\n\tuniform float iridescenceIOR;\n\tuniform float iridescenceThicknessMinimum;\n\tuniform float iridescenceThicknessMaximum;\n#endif\n#ifdef USE_SHEEN\n\tuniform vec3 sheenColor;\n\tuniform float sheenRoughness;\n\t#ifdef USE_SHEEN_COLORMAP\n\t\tuniform sampler2D sheenColorMap;\n\t#endif\n\t#ifdef USE_SHEEN_ROUGHNESSMAP\n\t\tuniform sampler2D sheenRoughnessMap;\n\t#endif\n#endif\n#ifdef USE_ANISOTROPY\n\tuniform vec2 anisotropyVector;\n\t#ifdef USE_ANISOTROPYMAP\n\t\tuniform sampler2D anisotropyMap;\n\t#endif\n#endif\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 totalDiffuse = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse;\n\tvec3 totalSpecular = reflectedLight.directSpecular + reflectedLight.indirectSpecular;\n\t#include \n\tvec3 outgoingLight = totalDiffuse + totalSpecular + totalEmissiveRadiance;\n\t#ifdef USE_SHEEN\n\t\tfloat sheenEnergyComp = 1.0 - 0.157 * max3( material.sheenColor );\n\t\toutgoingLight = outgoingLight * sheenEnergyComp + sheenSpecularDirect + sheenSpecularIndirect;\n\t#endif\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNVcc = saturate( dot( geometryClearcoatNormal, geometryViewDir ) );\n\t\tvec3 Fcc = F_Schlick( material.clearcoatF0, material.clearcoatF90, dotNVcc );\n\t\toutgoingLight = outgoingLight * ( 1.0 - material.clearcoat * Fcc ) + ( clearcoatSpecularDirect + clearcoatSpecularIndirect ) * material.clearcoat;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",meshtoon_vert:"#define TOON\nvarying vec3 vViewPosition;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n}",meshtoon_frag:"#define TOON\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",points_vert:"uniform float size;\nuniform float scale;\n#include \n#include \n#include \n#include \n#include \n#include \n#ifdef USE_POINTS_UV\n\tvarying vec2 vUv;\n\tuniform mat3 uvTransform;\n#endif\nvoid main() {\n\t#ifdef USE_POINTS_UV\n\t\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tgl_PointSize = size;\n\t#ifdef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) gl_PointSize *= ( scale / - mvPosition.z );\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n}",points_frag:"uniform vec3 diffuse;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tvec3 outgoingLight = vec3( 0.0 );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",shadow_vert:"#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}",shadow_frag:"uniform vec3 color;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tgl_FragColor = vec4( color, opacity * ( 1.0 - getShadowMask() ) );\n\t#include \n\t#include \n\t#include \n}",sprite_vert:"uniform float rotation;\nuniform vec2 center;\n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 mvPosition = modelViewMatrix[ 3 ];\n\tvec2 scale = vec2( length( modelMatrix[ 0 ].xyz ), length( modelMatrix[ 1 ].xyz ) );\n\t#ifndef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) scale *= - mvPosition.z;\n\t#endif\n\tvec2 alignedPosition = ( position.xy - ( center - vec2( 0.5 ) ) ) * scale;\n\tvec2 rotatedPosition;\n\trotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;\n\trotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;\n\tmvPosition.xy += rotatedPosition;\n\tgl_Position = projectionMatrix * mvPosition;\n\t#include \n\t#include \n\t#include \n}",sprite_frag:"uniform vec3 diffuse;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\tvec3 outgoingLight = vec3( 0.0 );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\t#include \n\t#include \n\t#include \n\t#include \n}"},Un={common:{diffuse:{value:new n(16777215)},opacity:{value:1},map:{value:null},mapTransform:{value:new e},alphaMap:{value:null},alphaMapTransform:{value:new e},alphaTest:{value:0}},specularmap:{specularMap:{value:null},specularMapTransform:{value:new e}},envmap:{envMap:{value:null},envMapRotation:{value:new e},flipEnvMap:{value:-1},reflectivity:{value:1},ior:{value:1.5},refractionRatio:{value:.98}},aomap:{aoMap:{value:null},aoMapIntensity:{value:1},aoMapTransform:{value:new e}},lightmap:{lightMap:{value:null},lightMapIntensity:{value:1},lightMapTransform:{value:new e}},bumpmap:{bumpMap:{value:null},bumpMapTransform:{value:new e},bumpScale:{value:1}},normalmap:{normalMap:{value:null},normalMapTransform:{value:new e},normalScale:{value:new t(1,1)}},displacementmap:{displacementMap:{value:null},displacementMapTransform:{value:new e},displacementScale:{value:1},displacementBias:{value:0}},emissivemap:{emissiveMap:{value:null},emissiveMapTransform:{value:new e}},metalnessmap:{metalnessMap:{value:null},metalnessMapTransform:{value:new e}},roughnessmap:{roughnessMap:{value:null},roughnessMapTransform:{value:new e}},gradientmap:{gradientMap:{value:null}},fog:{fogDensity:{value:25e-5},fogNear:{value:1},fogFar:{value:2e3},fogColor:{value:new n(16777215)}},lights:{ambientLightColor:{value:[]},lightProbe:{value:[]},directionalLights:{value:[],properties:{direction:{},color:{}}},directionalLightShadows:{value:[],properties:{shadowIntensity:1,shadowBias:{},shadowNormalBias:{},shadowRadius:{},shadowMapSize:{}}},directionalShadowMap:{value:[]},directionalShadowMatrix:{value:[]},spotLights:{value:[],properties:{color:{},position:{},direction:{},distance:{},coneCos:{},penumbraCos:{},decay:{}}},spotLightShadows:{value:[],properties:{shadowIntensity:1,shadowBias:{},shadowNormalBias:{},shadowRadius:{},shadowMapSize:{}}},spotLightMap:{value:[]},spotShadowMap:{value:[]},spotLightMatrix:{value:[]},pointLights:{value:[],properties:{color:{},position:{},decay:{},distance:{}}},pointLightShadows:{value:[],properties:{shadowIntensity:1,shadowBias:{},shadowNormalBias:{},shadowRadius:{},shadowMapSize:{},shadowCameraNear:{},shadowCameraFar:{}}},pointShadowMap:{value:[]},pointShadowMatrix:{value:[]},hemisphereLights:{value:[],properties:{direction:{},skyColor:{},groundColor:{}}},rectAreaLights:{value:[],properties:{color:{},position:{},width:{},height:{}}},ltc_1:{value:null},ltc_2:{value:null}},points:{diffuse:{value:new n(16777215)},opacity:{value:1},size:{value:1},scale:{value:1},map:{value:null},alphaMap:{value:null},alphaMapTransform:{value:new e},alphaTest:{value:0},uvTransform:{value:new e}},sprite:{diffuse:{value:new n(16777215)},opacity:{value:1},center:{value:new t(.5,.5)},rotation:{value:0},map:{value:null},mapTransform:{value:new e},alphaMap:{value:null},alphaMapTransform:{value:new e},alphaTest:{value:0}}},Dn={basic:{uniforms:r([Un.common,Un.specularmap,Un.envmap,Un.aomap,Un.lightmap,Un.fog]),vertexShader:Pn.meshbasic_vert,fragmentShader:Pn.meshbasic_frag},lambert:{uniforms:r([Un.common,Un.specularmap,Un.envmap,Un.aomap,Un.lightmap,Un.emissivemap,Un.bumpmap,Un.normalmap,Un.displacementmap,Un.fog,Un.lights,{emissive:{value:new n(0)}}]),vertexShader:Pn.meshlambert_vert,fragmentShader:Pn.meshlambert_frag},phong:{uniforms:r([Un.common,Un.specularmap,Un.envmap,Un.aomap,Un.lightmap,Un.emissivemap,Un.bumpmap,Un.normalmap,Un.displacementmap,Un.fog,Un.lights,{emissive:{value:new n(0)},specular:{value:new n(1118481)},shininess:{value:30}}]),vertexShader:Pn.meshphong_vert,fragmentShader:Pn.meshphong_frag},standard:{uniforms:r([Un.common,Un.envmap,Un.aomap,Un.lightmap,Un.emissivemap,Un.bumpmap,Un.normalmap,Un.displacementmap,Un.roughnessmap,Un.metalnessmap,Un.fog,Un.lights,{emissive:{value:new n(0)},roughness:{value:1},metalness:{value:0},envMapIntensity:{value:1}}]),vertexShader:Pn.meshphysical_vert,fragmentShader:Pn.meshphysical_frag},toon:{uniforms:r([Un.common,Un.aomap,Un.lightmap,Un.emissivemap,Un.bumpmap,Un.normalmap,Un.displacementmap,Un.gradientmap,Un.fog,Un.lights,{emissive:{value:new n(0)}}]),vertexShader:Pn.meshtoon_vert,fragmentShader:Pn.meshtoon_frag},matcap:{uniforms:r([Un.common,Un.bumpmap,Un.normalmap,Un.displacementmap,Un.fog,{matcap:{value:null}}]),vertexShader:Pn.meshmatcap_vert,fragmentShader:Pn.meshmatcap_frag},points:{uniforms:r([Un.points,Un.fog]),vertexShader:Pn.points_vert,fragmentShader:Pn.points_frag},dashed:{uniforms:r([Un.common,Un.fog,{scale:{value:1},dashSize:{value:1},totalSize:{value:2}}]),vertexShader:Pn.linedashed_vert,fragmentShader:Pn.linedashed_frag},depth:{uniforms:r([Un.common,Un.displacementmap]),vertexShader:Pn.depth_vert,fragmentShader:Pn.depth_frag},normal:{uniforms:r([Un.common,Un.bumpmap,Un.normalmap,Un.displacementmap,{opacity:{value:1}}]),vertexShader:Pn.meshnormal_vert,fragmentShader:Pn.meshnormal_frag},sprite:{uniforms:r([Un.sprite,Un.fog]),vertexShader:Pn.sprite_vert,fragmentShader:Pn.sprite_frag},background:{uniforms:{uvTransform:{value:new e},t2D:{value:null},backgroundIntensity:{value:1}},vertexShader:Pn.background_vert,fragmentShader:Pn.background_frag},backgroundCube:{uniforms:{envMap:{value:null},flipEnvMap:{value:-1},backgroundBlurriness:{value:0},backgroundIntensity:{value:1},backgroundRotation:{value:new e}},vertexShader:Pn.backgroundCube_vert,fragmentShader:Pn.backgroundCube_frag},cube:{uniforms:{tCube:{value:null},tFlip:{value:-1},opacity:{value:1}},vertexShader:Pn.cube_vert,fragmentShader:Pn.cube_frag},equirect:{uniforms:{tEquirect:{value:null}},vertexShader:Pn.equirect_vert,fragmentShader:Pn.equirect_frag},distanceRGBA:{uniforms:r([Un.common,Un.displacementmap,{referencePosition:{value:new i},nearDistance:{value:1},farDistance:{value:1e3}}]),vertexShader:Pn.distanceRGBA_vert,fragmentShader:Pn.distanceRGBA_frag},shadow:{uniforms:r([Un.lights,Un.fog,{color:{value:new n(0)},opacity:{value:1}}]),vertexShader:Pn.shadow_vert,fragmentShader:Pn.shadow_frag}};Dn.physical={uniforms:r([Dn.standard.uniforms,{clearcoat:{value:0},clearcoatMap:{value:null},clearcoatMapTransform:{value:new e},clearcoatNormalMap:{value:null},clearcoatNormalMapTransform:{value:new e},clearcoatNormalScale:{value:new t(1,1)},clearcoatRoughness:{value:0},clearcoatRoughnessMap:{value:null},clearcoatRoughnessMapTransform:{value:new e},dispersion:{value:0},iridescence:{value:0},iridescenceMap:{value:null},iridescenceMapTransform:{value:new e},iridescenceIOR:{value:1.3},iridescenceThicknessMinimum:{value:100},iridescenceThicknessMaximum:{value:400},iridescenceThicknessMap:{value:null},iridescenceThicknessMapTransform:{value:new e},sheen:{value:0},sheenColor:{value:new n(0)},sheenColorMap:{value:null},sheenColorMapTransform:{value:new e},sheenRoughness:{value:1},sheenRoughnessMap:{value:null},sheenRoughnessMapTransform:{value:new e},transmission:{value:0},transmissionMap:{value:null},transmissionMapTransform:{value:new e},transmissionSamplerSize:{value:new t},transmissionSamplerMap:{value:null},thickness:{value:0},thicknessMap:{value:null},thicknessMapTransform:{value:new e},attenuationDistance:{value:0},attenuationColor:{value:new n(0)},specularColor:{value:new n(1,1,1)},specularColorMap:{value:null},specularColorMapTransform:{value:new e},specularIntensity:{value:1},specularIntensityMap:{value:null},specularIntensityMapTransform:{value:new e},anisotropyVector:{value:new t},anisotropyMap:{value:null},anisotropyMapTransform:{value:new e}}]),vertexShader:Pn.meshphysical_vert,fragmentShader:Pn.meshphysical_frag};const wn={r:0,b:0,g:0},yn=new u,In=new f;function Nn(e,t,r,i,u,f,v){const E=new n(0);let S,T,M=!0===f?0:1,x=null,R=0,A=null;function b(e){let n=!0===e.isScene?e.background:null;if(n&&n.isTexture){n=(e.backgroundBlurriness>0?r:t).get(n)}return n}function C(t,n){t.getRGB(wn,g(e)),i.buffers.color.setClear(wn.r,wn.g,wn.b,n,v)}return{getClearColor:function(){return E},setClearColor:function(e,t=1){E.set(e),M=t,C(E,M)},getClearAlpha:function(){return M},setClearAlpha:function(e){M=e,C(E,M)},render:function(t){let n=!1;const r=b(t);null===r?C(E,M):r&&r.isColor&&(C(r,1),n=!0);const a=e.xr.getEnvironmentBlendMode();"additive"===a?i.buffers.color.setClear(0,0,0,1,v):"alpha-blend"===a&&i.buffers.color.setClear(0,0,0,0,v),(e.autoClear||n)&&(i.buffers.depth.setTest(!0),i.buffers.depth.setMask(!0),i.buffers.color.setMask(!0),e.clear(e.autoClearColor,e.autoClearDepth,e.autoClearStencil))},addToRenderList:function(t,n){const r=b(n);r&&(r.isCubeTexture||r.mapping===a)?(void 0===T&&(T=new o(new s(1,1,1),new l({name:"BackgroundCubeMaterial",uniforms:d(Dn.backgroundCube.uniforms),vertexShader:Dn.backgroundCube.vertexShader,fragmentShader:Dn.backgroundCube.fragmentShader,side:c,depthTest:!1,depthWrite:!1,fog:!1,allowOverride:!1})),T.geometry.deleteAttribute("normal"),T.geometry.deleteAttribute("uv"),T.onBeforeRender=function(e,t,n){this.matrixWorld.copyPosition(n.matrixWorld)},Object.defineProperty(T.material,"envMap",{get:function(){return this.uniforms.envMap.value}}),u.update(T)),yn.copy(n.backgroundRotation),yn.x*=-1,yn.y*=-1,yn.z*=-1,r.isCubeTexture&&!1===r.isRenderTargetTexture&&(yn.y*=-1,yn.z*=-1),T.material.uniforms.envMap.value=r,T.material.uniforms.flipEnvMap.value=r.isCubeTexture&&!1===r.isRenderTargetTexture?-1:1,T.material.uniforms.backgroundBlurriness.value=n.backgroundBlurriness,T.material.uniforms.backgroundIntensity.value=n.backgroundIntensity,T.material.uniforms.backgroundRotation.value.setFromMatrix4(In.makeRotationFromEuler(yn)),T.material.toneMapped=p.getTransfer(r.colorSpace)!==m,x===r&&R===r.version&&A===e.toneMapping||(T.material.needsUpdate=!0,x=r,R=r.version,A=e.toneMapping),T.layers.enableAll(),t.unshift(T,T.geometry,T.material,0,0,null)):r&&r.isTexture&&(void 0===S&&(S=new o(new h(2,2),new l({name:"BackgroundMaterial",uniforms:d(Dn.background.uniforms),vertexShader:Dn.background.vertexShader,fragmentShader:Dn.background.fragmentShader,side:_,depthTest:!1,depthWrite:!1,fog:!1,allowOverride:!1})),S.geometry.deleteAttribute("normal"),Object.defineProperty(S.material,"map",{get:function(){return this.uniforms.t2D.value}}),u.update(S)),S.material.uniforms.t2D.value=r,S.material.uniforms.backgroundIntensity.value=n.backgroundIntensity,S.material.toneMapped=p.getTransfer(r.colorSpace)!==m,!0===r.matrixAutoUpdate&&r.updateMatrix(),S.material.uniforms.uvTransform.value.copy(r.matrix),x===r&&R===r.version&&A===e.toneMapping||(S.material.needsUpdate=!0,x=r,R=r.version,A=e.toneMapping),S.layers.enableAll(),t.unshift(S,S.geometry,S.material,0,0,null))},dispose:function(){void 0!==T&&(T.geometry.dispose(),T.material.dispose(),T=void 0),void 0!==S&&(S.geometry.dispose(),S.material.dispose(),S=void 0)}}}function On(e,t){const n=e.getParameter(e.MAX_VERTEX_ATTRIBS),r={},i=c(null);let a=i,o=!1;function s(t){return e.bindVertexArray(t)}function l(t){return e.deleteVertexArray(t)}function c(e){const t=[],r=[],i=[];for(let e=0;e=0){const n=i[t];let r=o[t];if(void 0===r&&("instanceMatrix"===t&&e.instanceMatrix&&(r=e.instanceMatrix),"instanceColor"===t&&e.instanceColor&&(r=e.instanceColor)),void 0===n)return!0;if(n.attribute!==r)return!0;if(r&&n.data!==r.data)return!0;s++}}return a.attributesNum!==s||a.index!==r}(n,h,l,_),g&&function(e,t,n,r){const i={},o=t.attributes;let s=0;const l=n.getAttributes();for(const t in l){if(l[t].location>=0){let n=o[t];void 0===n&&("instanceMatrix"===t&&e.instanceMatrix&&(n=e.instanceMatrix),"instanceColor"===t&&e.instanceColor&&(n=e.instanceColor));const r={};r.attribute=n,n&&n.data&&(r.data=n.data),i[t]=r,s++}}a.attributes=i,a.attributesNum=s,a.index=r}(n,h,l,_),null!==_&&t.update(_,e.ELEMENT_ARRAY_BUFFER),(g||o)&&(o=!1,function(n,r,i,a){d();const o=a.attributes,s=i.getAttributes(),l=r.defaultAttributeValues;for(const r in s){const i=s[r];if(i.location>=0){let s=o[r];if(void 0===s&&("instanceMatrix"===r&&n.instanceMatrix&&(s=n.instanceMatrix),"instanceColor"===r&&n.instanceColor&&(s=n.instanceColor)),void 0!==s){const r=s.normalized,o=s.itemSize,l=t.get(s);if(void 0===l)continue;const c=l.buffer,d=l.type,p=l.bytesPerElement,h=d===e.INT||d===e.UNSIGNED_INT||s.gpuType===v;if(s.isInterleavedBufferAttribute){const t=s.data,l=t.stride,_=s.offset;if(t.isInstancedInterleavedBuffer){for(let e=0;e0&&e.getShaderPrecisionFormat(e.FRAGMENT_SHADER,e.HIGH_FLOAT).precision>0)return"highp";t="mediump"}return"mediump"===t&&e.getShaderPrecisionFormat(e.VERTEX_SHADER,e.MEDIUM_FLOAT).precision>0&&e.getShaderPrecisionFormat(e.FRAGMENT_SHADER,e.MEDIUM_FLOAT).precision>0?"mediump":"lowp"}let o=void 0!==n.precision?n.precision:"highp";const s=a(o);s!==o&&(console.warn("THREE.WebGLRenderer:",o,"not supported, using",s,"instead."),o=s);const l=!0===n.logarithmicDepthBuffer,c=!0===n.reverseDepthBuffer&&t.has("EXT_clip_control"),d=e.getParameter(e.MAX_TEXTURE_IMAGE_UNITS),u=e.getParameter(e.MAX_VERTEX_TEXTURE_IMAGE_UNITS);return{isWebGL2:!0,getMaxAnisotropy:function(){if(void 0!==i)return i;if(!0===t.has("EXT_texture_filter_anisotropic")){const n=t.get("EXT_texture_filter_anisotropic");i=e.getParameter(n.MAX_TEXTURE_MAX_ANISOTROPY_EXT)}else i=0;return i},getMaxPrecision:a,textureFormatReadable:function(t){return t===M||r.convert(t)===e.getParameter(e.IMPLEMENTATION_COLOR_READ_FORMAT)},textureTypeReadable:function(n){const i=n===E&&(t.has("EXT_color_buffer_half_float")||t.has("EXT_color_buffer_float"));return!(n!==S&&r.convert(n)!==e.getParameter(e.IMPLEMENTATION_COLOR_READ_TYPE)&&n!==T&&!i)},precision:o,logarithmicDepthBuffer:l,reverseDepthBuffer:c,maxTextures:d,maxVertexTextures:u,maxTextureSize:e.getParameter(e.MAX_TEXTURE_SIZE),maxCubemapSize:e.getParameter(e.MAX_CUBE_MAP_TEXTURE_SIZE),maxAttributes:e.getParameter(e.MAX_VERTEX_ATTRIBS),maxVertexUniforms:e.getParameter(e.MAX_VERTEX_UNIFORM_VECTORS),maxVaryings:e.getParameter(e.MAX_VARYING_VECTORS),maxFragmentUniforms:e.getParameter(e.MAX_FRAGMENT_UNIFORM_VECTORS),vertexTextures:u>0,maxSamples:e.getParameter(e.MAX_SAMPLES)}}function Hn(t){const n=this;let r=null,i=0,a=!1,o=!1;const s=new x,l=new e,c={value:null,needsUpdate:!1};function d(e,t,r,i){const a=null!==e?e.length:0;let o=null;if(0!==a){if(o=c.value,!0!==i||null===o){const n=r+4*a,i=t.matrixWorldInverse;l.getNormalMatrix(i),(null===o||o.length0);n.numPlanes=i,n.numIntersection=0}();else{const e=o?0:i,t=4*e;let n=m.clippingState||null;c.value=n,n=d(u,s,t,l);for(let e=0;e!==t;++e)n[e]=r[e];m.clippingState=n,this.numIntersection=f?this.numPlanes:0,this.numPlanes+=e}}}function Gn(e){let t=new WeakMap;function n(e,t){return t===R?e.mapping=C:t===A&&(e.mapping=L),e}function r(e){const n=e.target;n.removeEventListener("dispose",r);const i=t.get(n);void 0!==i&&(t.delete(n),i.dispose())}return{get:function(i){if(i&&i.isTexture){const a=i.mapping;if(a===R||a===A){if(t.has(i)){return n(t.get(i).texture,i.mapping)}{const a=i.image;if(a&&a.height>0){const o=new b(a.height);return o.fromEquirectangularTexture(e,i),t.set(i,o),i.addEventListener("dispose",r),n(o.texture,i.mapping)}return null}}}return i},dispose:function(){t=new WeakMap}}}const Vn=[.125,.215,.35,.446,.526,.582],zn=20,kn=new P,Wn=new n;let Xn=null,Yn=0,Kn=0,jn=!1;const qn=(1+Math.sqrt(5))/2,Zn=1/qn,$n=[new i(-qn,Zn,0),new i(qn,Zn,0),new i(-Zn,0,qn),new i(Zn,0,qn),new i(0,qn,-Zn),new i(0,qn,Zn),new i(-1,1,-1),new i(1,1,-1),new i(-1,1,1),new i(1,1,1)],Qn=new i;class Jn{constructor(e){this._renderer=e,this._pingPongRenderTarget=null,this._lodMax=0,this._cubeSize=0,this._lodPlanes=[],this._sizeLods=[],this._sigmas=[],this._blurMaterial=null,this._cubemapMaterial=null,this._equirectMaterial=null,this._compileMaterial(this._blurMaterial)}fromScene(e,t=0,n=.1,r=100,i={}){const{size:a=256,position:o=Qn}=i;Xn=this._renderer.getRenderTarget(),Yn=this._renderer.getActiveCubeFace(),Kn=this._renderer.getActiveMipmapLevel(),jn=this._renderer.xr.enabled,this._renderer.xr.enabled=!1,this._setSize(a);const s=this._allocateTargets();return s.depthBuffer=!0,this._sceneToCubeUV(e,n,r,s,o),t>0&&this._blur(s,0,0,t),this._applyPMREM(s),this._cleanup(s),s}fromEquirectangular(e,t=null){return this._fromTexture(e,t)}fromCubemap(e,t=null){return this._fromTexture(e,t)}compileCubemapShader(){null===this._cubemapMaterial&&(this._cubemapMaterial=rr(),this._compileMaterial(this._cubemapMaterial))}compileEquirectangularShader(){null===this._equirectMaterial&&(this._equirectMaterial=nr(),this._compileMaterial(this._equirectMaterial))}dispose(){this._dispose(),null!==this._cubemapMaterial&&this._cubemapMaterial.dispose(),null!==this._equirectMaterial&&this._equirectMaterial.dispose()}_setSize(e){this._lodMax=Math.floor(Math.log2(e)),this._cubeSize=Math.pow(2,this._lodMax)}_dispose(){null!==this._blurMaterial&&this._blurMaterial.dispose(),null!==this._pingPongRenderTarget&&this._pingPongRenderTarget.dispose();for(let e=0;ee-4?s=Vn[o-e+4-1]:0===o&&(s=0),r.push(s);const l=1/(a-2),c=-l,d=1+l,u=[c,c,d,c,d,d,c,c,d,d,c,d],f=6,p=6,m=3,h=2,_=1,g=new Float32Array(m*p*f),v=new Float32Array(h*p*f),E=new Float32Array(_*p*f);for(let e=0;e2?0:-1,r=[t,n,0,t+2/3,n,0,t+2/3,n+1,0,t,n,0,t+2/3,n+1,0,t,n+1,0];g.set(r,m*p*e),v.set(u,h*p*e);const i=[e,e,e,e,e,e];E.set(i,_*p*e)}const S=new N;S.setAttribute("position",new O(g,m)),S.setAttribute("uv",new O(v,h)),S.setAttribute("faceIndex",new O(E,_)),t.push(S),i>4&&i--}return{lodPlanes:t,sizeLods:n,sigmas:r}}(r)),this._blurMaterial=function(e,t,n){const r=new Float32Array(zn),a=new i(0,1,0),o=new l({name:"SphericalGaussianBlur",defines:{n:zn,CUBEUV_TEXEL_WIDTH:1/t,CUBEUV_TEXEL_HEIGHT:1/n,CUBEUV_MAX_MIP:`${e}.0`},uniforms:{envMap:{value:null},samples:{value:1},weights:{value:r},latitudinal:{value:!1},dTheta:{value:0},mipInt:{value:0},poleAxis:{value:a}},vertexShader:ir(),fragmentShader:"\n\n\t\t\tprecision mediump float;\n\t\t\tprecision mediump int;\n\n\t\t\tvarying vec3 vOutputDirection;\n\n\t\t\tuniform sampler2D envMap;\n\t\t\tuniform int samples;\n\t\t\tuniform float weights[ n ];\n\t\t\tuniform bool latitudinal;\n\t\t\tuniform float dTheta;\n\t\t\tuniform float mipInt;\n\t\t\tuniform vec3 poleAxis;\n\n\t\t\t#define ENVMAP_TYPE_CUBE_UV\n\t\t\t#include \n\n\t\t\tvec3 getSample( float theta, vec3 axis ) {\n\n\t\t\t\tfloat cosTheta = cos( theta );\n\t\t\t\t// Rodrigues' axis-angle rotation\n\t\t\t\tvec3 sampleDirection = vOutputDirection * cosTheta\n\t\t\t\t\t+ cross( axis, vOutputDirection ) * sin( theta )\n\t\t\t\t\t+ axis * dot( axis, vOutputDirection ) * ( 1.0 - cosTheta );\n\n\t\t\t\treturn bilinearCubeUV( envMap, sampleDirection, mipInt );\n\n\t\t\t}\n\n\t\t\tvoid main() {\n\n\t\t\t\tvec3 axis = latitudinal ? poleAxis : cross( poleAxis, vOutputDirection );\n\n\t\t\t\tif ( all( equal( axis, vec3( 0.0 ) ) ) ) {\n\n\t\t\t\t\taxis = vec3( vOutputDirection.z, 0.0, - vOutputDirection.x );\n\n\t\t\t\t}\n\n\t\t\t\taxis = normalize( axis );\n\n\t\t\t\tgl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 );\n\t\t\t\tgl_FragColor.rgb += weights[ 0 ] * getSample( 0.0, axis );\n\n\t\t\t\tfor ( int i = 1; i < n; i++ ) {\n\n\t\t\t\t\tif ( i >= samples ) {\n\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\t}\n\n\t\t\t\t\tfloat theta = dTheta * float( i );\n\t\t\t\t\tgl_FragColor.rgb += weights[ i ] * getSample( -1.0 * theta, axis );\n\t\t\t\t\tgl_FragColor.rgb += weights[ i ] * getSample( theta, axis );\n\n\t\t\t\t}\n\n\t\t\t}\n\t\t",blending:y,depthTest:!1,depthWrite:!1});return o}(r,e,t)}return r}_compileMaterial(e){const t=new o(this._lodPlanes[0],e);this._renderer.compile(t,kn)}_sceneToCubeUV(e,t,n,r,i){const a=new U(90,1,t,n),l=[1,-1,1,1,1,1],d=[1,1,1,-1,-1,-1],u=this._renderer,f=u.autoClear,p=u.toneMapping;u.getClearColor(Wn),u.toneMapping=D,u.autoClear=!1;const m=new w({name:"PMREM.Background",side:c,depthWrite:!1,depthTest:!1}),h=new o(new s,m);let _=!1;const g=e.background;g?g.isColor&&(m.color.copy(g),e.background=null,_=!0):(m.color.copy(Wn),_=!0);for(let t=0;t<6;t++){const n=t%3;0===n?(a.up.set(0,l[t],0),a.position.set(i.x,i.y,i.z),a.lookAt(i.x+d[t],i.y,i.z)):1===n?(a.up.set(0,0,l[t]),a.position.set(i.x,i.y,i.z),a.lookAt(i.x,i.y+d[t],i.z)):(a.up.set(0,l[t],0),a.position.set(i.x,i.y,i.z),a.lookAt(i.x,i.y,i.z+d[t]));const o=this._cubeSize;tr(r,n*o,t>2?o:0,o,o),u.setRenderTarget(r),_&&u.render(h,a),u.render(e,a)}h.geometry.dispose(),h.material.dispose(),u.toneMapping=p,u.autoClear=f,e.background=g}_textureToCubeUV(e,t){const n=this._renderer,r=e.mapping===C||e.mapping===L;r?(null===this._cubemapMaterial&&(this._cubemapMaterial=rr()),this._cubemapMaterial.uniforms.flipEnvMap.value=!1===e.isRenderTargetTexture?-1:1):null===this._equirectMaterial&&(this._equirectMaterial=nr());const i=r?this._cubemapMaterial:this._equirectMaterial,a=new o(this._lodPlanes[0],i);i.uniforms.envMap.value=e;const s=this._cubeSize;tr(t,0,0,3*s,2*s),n.setRenderTarget(t),n.render(a,kn)}_applyPMREM(e){const t=this._renderer,n=t.autoClear;t.autoClear=!1;const r=this._lodPlanes.length;for(let t=1;tzn&&console.warn(`sigmaRadians, ${i}, is too large and will clip, as it requested ${h} samples when the maximum is set to 20`);const _=[];let g=0;for(let e=0;ev-4?r-v+4:0),4*(this._cubeSize-E),3*E,2*E),l.setRenderTarget(t),l.render(d,kn)}}function er(e,t,n){const r=new I(e,t,n);return r.texture.mapping=a,r.texture.name="PMREM.cubeUv",r.scissorTest=!0,r}function tr(e,t,n,r,i){e.viewport.set(t,n,r,i),e.scissor.set(t,n,r,i)}function nr(){return new l({name:"EquirectangularToCubeUV",uniforms:{envMap:{value:null}},vertexShader:ir(),fragmentShader:"\n\n\t\t\tprecision mediump float;\n\t\t\tprecision mediump int;\n\n\t\t\tvarying vec3 vOutputDirection;\n\n\t\t\tuniform sampler2D envMap;\n\n\t\t\t#include \n\n\t\t\tvoid main() {\n\n\t\t\t\tvec3 outputDirection = normalize( vOutputDirection );\n\t\t\t\tvec2 uv = equirectUv( outputDirection );\n\n\t\t\t\tgl_FragColor = vec4( texture2D ( envMap, uv ).rgb, 1.0 );\n\n\t\t\t}\n\t\t",blending:y,depthTest:!1,depthWrite:!1})}function rr(){return new l({name:"CubemapToCubeUV",uniforms:{envMap:{value:null},flipEnvMap:{value:-1}},vertexShader:ir(),fragmentShader:"\n\n\t\t\tprecision mediump float;\n\t\t\tprecision mediump int;\n\n\t\t\tuniform float flipEnvMap;\n\n\t\t\tvarying vec3 vOutputDirection;\n\n\t\t\tuniform samplerCube envMap;\n\n\t\t\tvoid main() {\n\n\t\t\t\tgl_FragColor = textureCube( envMap, vec3( flipEnvMap * vOutputDirection.x, vOutputDirection.yz ) );\n\n\t\t\t}\n\t\t",blending:y,depthTest:!1,depthWrite:!1})}function ir(){return"\n\n\t\tprecision mediump float;\n\t\tprecision mediump int;\n\n\t\tattribute float faceIndex;\n\n\t\tvarying vec3 vOutputDirection;\n\n\t\t// RH coordinate system; PMREM face-indexing convention\n\t\tvec3 getDirection( vec2 uv, float face ) {\n\n\t\t\tuv = 2.0 * uv - 1.0;\n\n\t\t\tvec3 direction = vec3( uv, 1.0 );\n\n\t\t\tif ( face == 0.0 ) {\n\n\t\t\t\tdirection = direction.zyx; // ( 1, v, u ) pos x\n\n\t\t\t} else if ( face == 1.0 ) {\n\n\t\t\t\tdirection = direction.xzy;\n\t\t\t\tdirection.xz *= -1.0; // ( -u, 1, -v ) pos y\n\n\t\t\t} else if ( face == 2.0 ) {\n\n\t\t\t\tdirection.x *= -1.0; // ( -u, v, 1 ) pos z\n\n\t\t\t} else if ( face == 3.0 ) {\n\n\t\t\t\tdirection = direction.zyx;\n\t\t\t\tdirection.xz *= -1.0; // ( -1, v, -u ) neg x\n\n\t\t\t} else if ( face == 4.0 ) {\n\n\t\t\t\tdirection = direction.xzy;\n\t\t\t\tdirection.xy *= -1.0; // ( -u, -1, v ) neg y\n\n\t\t\t} else if ( face == 5.0 ) {\n\n\t\t\t\tdirection.z *= -1.0; // ( u, v, -1 ) neg z\n\n\t\t\t}\n\n\t\t\treturn direction;\n\n\t\t}\n\n\t\tvoid main() {\n\n\t\t\tvOutputDirection = getDirection( uv, faceIndex );\n\t\t\tgl_Position = vec4( position, 1.0 );\n\n\t\t}\n\t"}function ar(e){let t=new WeakMap,n=null;function r(e){const n=e.target;n.removeEventListener("dispose",r);const i=t.get(n);void 0!==i&&(t.delete(n),i.dispose())}return{get:function(i){if(i&&i.isTexture){const a=i.mapping,o=a===R||a===A,s=a===C||a===L;if(o||s){let a=t.get(i);const l=void 0!==a?a.texture.pmremVersion:0;if(i.isRenderTargetTexture&&i.pmremVersion!==l)return null===n&&(n=new Jn(e)),a=o?n.fromEquirectangular(i,a):n.fromCubemap(i,a),a.texture.pmremVersion=i.pmremVersion,t.set(i,a),a.texture;if(void 0!==a)return a.texture;{const l=i.image;return o&&l&&l.height>0||s&&l&&function(e){let t=0;const n=6;for(let r=0;rn.maxTextureSize&&(M=Math.ceil(S/n.maxTextureSize),S=n.maxTextureSize);const x=new Float32Array(S*M*4*u),R=new W(x,S,M,u);R.type=T,R.needsUpdate=!0;const A=4*E;for(let C=0;C0)return e;const i=t*n;let a=gr[i];if(void 0===a&&(a=new Float32Array(i),gr[i]=a),0!==t){r.toArray(a,0);for(let r=1,i=0;r!==t;++r)i+=n,e[r].toArray(a,i)}return a}function xr(e,t){if(e.length!==t.length)return!1;for(let n=0,r=e.length;n":" "} ${i}: ${n[e]}`)}return r.join("\n")}(e.getShaderSource(t),r)}return i}function Ti(e,t){const n=function(e){p._getMatrix(Ei,p.workingColorSpace,e);const t=`mat3( ${Ei.elements.map(e=>e.toFixed(4))} )`;switch(p.getTransfer(e)){case se:return[t,"LinearTransferOETF"];case m:return[t,"sRGBTransferOETF"];default:return console.warn("THREE.WebGLProgram: Unsupported color space: ",e),[t,"LinearTransferOETF"]}}(t);return[`vec4 ${e}( vec4 value ) {`,`\treturn ${n[1]}( vec4( value.rgb * ${n[0]}, value.a ) );`,"}"].join("\n")}function Mi(e,t){let n;switch(t){case oe:n="Linear";break;case ae:n="Reinhard";break;case ie:n="Cineon";break;case re:n="ACESFilmic";break;case ne:n="AgX";break;case te:n="Neutral";break;case ee:n="Custom";break;default:console.warn("THREE.WebGLProgram: Unsupported toneMapping:",t),n="Linear"}return"vec3 "+e+"( vec3 color ) { return "+n+"ToneMapping( color ); }"}const xi=new i;function Ri(){p.getLuminanceCoefficients(xi);return["float luminance( const in vec3 rgb ) {",`\tconst vec3 weights = vec3( ${xi.x.toFixed(4)}, ${xi.y.toFixed(4)}, ${xi.z.toFixed(4)} );`,"\treturn dot( weights, rgb );","}"].join("\n")}function Ai(e){return""!==e}function bi(e,t){const n=t.numSpotLightShadows+t.numSpotLightMaps-t.numSpotLightShadowsWithMaps;return e.replace(/NUM_DIR_LIGHTS/g,t.numDirLights).replace(/NUM_SPOT_LIGHTS/g,t.numSpotLights).replace(/NUM_SPOT_LIGHT_MAPS/g,t.numSpotLightMaps).replace(/NUM_SPOT_LIGHT_COORDS/g,n).replace(/NUM_RECT_AREA_LIGHTS/g,t.numRectAreaLights).replace(/NUM_POINT_LIGHTS/g,t.numPointLights).replace(/NUM_HEMI_LIGHTS/g,t.numHemiLights).replace(/NUM_DIR_LIGHT_SHADOWS/g,t.numDirLightShadows).replace(/NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS/g,t.numSpotLightShadowsWithMaps).replace(/NUM_SPOT_LIGHT_SHADOWS/g,t.numSpotLightShadows).replace(/NUM_POINT_LIGHT_SHADOWS/g,t.numPointLightShadows)}function Ci(e,t){return e.replace(/NUM_CLIPPING_PLANES/g,t.numClippingPlanes).replace(/UNION_CLIPPING_PLANES/g,t.numClippingPlanes-t.numClipIntersection)}const Li=/^[ \t]*#include +<([\w\d./]+)>/gm;function Pi(e){return e.replace(Li,Di)}const Ui=new Map;function Di(e,t){let n=Pn[t];if(void 0===n){const e=Ui.get(t);if(void 0===e)throw new Error("Can not resolve #include <"+t+">");n=Pn[e],console.warn('THREE.WebGLRenderer: Shader chunk "%s" has been deprecated. Use "%s" instead.',t,e)}return Pi(n)}const wi=/#pragma unroll_loop_start\s+for\s*\(\s*int\s+i\s*=\s*(\d+)\s*;\s*i\s*<\s*(\d+)\s*;\s*i\s*\+\+\s*\)\s*{([\s\S]+?)}\s+#pragma unroll_loop_end/g;function yi(e){return e.replace(wi,Ii)}function Ii(e,t,n,r){let i="";for(let e=parseInt(t);e0&&(g+="\n"),v=["#define SHADER_TYPE "+n.shaderType,"#define SHADER_NAME "+n.shaderName,h].filter(Ai).join("\n"),v.length>0&&(v+="\n")):(g=[Ni(n),"#define SHADER_TYPE "+n.shaderType,"#define SHADER_NAME "+n.shaderName,h,n.extensionClipCullDistance?"#define USE_CLIP_DISTANCE":"",n.batching?"#define USE_BATCHING":"",n.batchingColor?"#define USE_BATCHING_COLOR":"",n.instancing?"#define USE_INSTANCING":"",n.instancingColor?"#define USE_INSTANCING_COLOR":"",n.instancingMorph?"#define USE_INSTANCING_MORPH":"",n.useFog&&n.fog?"#define USE_FOG":"",n.useFog&&n.fogExp2?"#define FOG_EXP2":"",n.map?"#define USE_MAP":"",n.envMap?"#define USE_ENVMAP":"",n.envMap?"#define "+u:"",n.lightMap?"#define USE_LIGHTMAP":"",n.aoMap?"#define USE_AOMAP":"",n.bumpMap?"#define USE_BUMPMAP":"",n.normalMap?"#define USE_NORMALMAP":"",n.normalMapObjectSpace?"#define USE_NORMALMAP_OBJECTSPACE":"",n.normalMapTangentSpace?"#define USE_NORMALMAP_TANGENTSPACE":"",n.displacementMap?"#define USE_DISPLACEMENTMAP":"",n.emissiveMap?"#define USE_EMISSIVEMAP":"",n.anisotropy?"#define USE_ANISOTROPY":"",n.anisotropyMap?"#define USE_ANISOTROPYMAP":"",n.clearcoatMap?"#define USE_CLEARCOATMAP":"",n.clearcoatRoughnessMap?"#define USE_CLEARCOAT_ROUGHNESSMAP":"",n.clearcoatNormalMap?"#define USE_CLEARCOAT_NORMALMAP":"",n.iridescenceMap?"#define USE_IRIDESCENCEMAP":"",n.iridescenceThicknessMap?"#define USE_IRIDESCENCE_THICKNESSMAP":"",n.specularMap?"#define USE_SPECULARMAP":"",n.specularColorMap?"#define USE_SPECULAR_COLORMAP":"",n.specularIntensityMap?"#define USE_SPECULAR_INTENSITYMAP":"",n.roughnessMap?"#define USE_ROUGHNESSMAP":"",n.metalnessMap?"#define USE_METALNESSMAP":"",n.alphaMap?"#define USE_ALPHAMAP":"",n.alphaHash?"#define USE_ALPHAHASH":"",n.transmission?"#define USE_TRANSMISSION":"",n.transmissionMap?"#define USE_TRANSMISSIONMAP":"",n.thicknessMap?"#define USE_THICKNESSMAP":"",n.sheenColorMap?"#define USE_SHEEN_COLORMAP":"",n.sheenRoughnessMap?"#define USE_SHEEN_ROUGHNESSMAP":"",n.mapUv?"#define MAP_UV "+n.mapUv:"",n.alphaMapUv?"#define ALPHAMAP_UV "+n.alphaMapUv:"",n.lightMapUv?"#define LIGHTMAP_UV "+n.lightMapUv:"",n.aoMapUv?"#define AOMAP_UV "+n.aoMapUv:"",n.emissiveMapUv?"#define EMISSIVEMAP_UV "+n.emissiveMapUv:"",n.bumpMapUv?"#define BUMPMAP_UV "+n.bumpMapUv:"",n.normalMapUv?"#define NORMALMAP_UV "+n.normalMapUv:"",n.displacementMapUv?"#define DISPLACEMENTMAP_UV "+n.displacementMapUv:"",n.metalnessMapUv?"#define METALNESSMAP_UV "+n.metalnessMapUv:"",n.roughnessMapUv?"#define ROUGHNESSMAP_UV "+n.roughnessMapUv:"",n.anisotropyMapUv?"#define ANISOTROPYMAP_UV "+n.anisotropyMapUv:"",n.clearcoatMapUv?"#define CLEARCOATMAP_UV "+n.clearcoatMapUv:"",n.clearcoatNormalMapUv?"#define CLEARCOAT_NORMALMAP_UV "+n.clearcoatNormalMapUv:"",n.clearcoatRoughnessMapUv?"#define CLEARCOAT_ROUGHNESSMAP_UV "+n.clearcoatRoughnessMapUv:"",n.iridescenceMapUv?"#define IRIDESCENCEMAP_UV "+n.iridescenceMapUv:"",n.iridescenceThicknessMapUv?"#define IRIDESCENCE_THICKNESSMAP_UV "+n.iridescenceThicknessMapUv:"",n.sheenColorMapUv?"#define SHEEN_COLORMAP_UV "+n.sheenColorMapUv:"",n.sheenRoughnessMapUv?"#define SHEEN_ROUGHNESSMAP_UV "+n.sheenRoughnessMapUv:"",n.specularMapUv?"#define SPECULARMAP_UV "+n.specularMapUv:"",n.specularColorMapUv?"#define SPECULAR_COLORMAP_UV "+n.specularColorMapUv:"",n.specularIntensityMapUv?"#define SPECULAR_INTENSITYMAP_UV "+n.specularIntensityMapUv:"",n.transmissionMapUv?"#define TRANSMISSIONMAP_UV "+n.transmissionMapUv:"",n.thicknessMapUv?"#define THICKNESSMAP_UV "+n.thicknessMapUv:"",n.vertexTangents&&!1===n.flatShading?"#define USE_TANGENT":"",n.vertexColors?"#define USE_COLOR":"",n.vertexAlphas?"#define USE_COLOR_ALPHA":"",n.vertexUv1s?"#define USE_UV1":"",n.vertexUv2s?"#define USE_UV2":"",n.vertexUv3s?"#define USE_UV3":"",n.pointsUvs?"#define USE_POINTS_UV":"",n.flatShading?"#define FLAT_SHADED":"",n.skinning?"#define USE_SKINNING":"",n.morphTargets?"#define USE_MORPHTARGETS":"",n.morphNormals&&!1===n.flatShading?"#define USE_MORPHNORMALS":"",n.morphColors?"#define USE_MORPHCOLORS":"",n.morphTargetsCount>0?"#define MORPHTARGETS_TEXTURE_STRIDE "+n.morphTextureStride:"",n.morphTargetsCount>0?"#define MORPHTARGETS_COUNT "+n.morphTargetsCount:"",n.doubleSided?"#define DOUBLE_SIDED":"",n.flipSided?"#define FLIP_SIDED":"",n.shadowMapEnabled?"#define USE_SHADOWMAP":"",n.shadowMapEnabled?"#define "+c:"",n.sizeAttenuation?"#define USE_SIZEATTENUATION":"",n.numLightProbes>0?"#define USE_LIGHT_PROBES":"",n.logarithmicDepthBuffer?"#define USE_LOGDEPTHBUF":"",n.reverseDepthBuffer?"#define USE_REVERSEDEPTHBUF":"","uniform mat4 modelMatrix;","uniform mat4 modelViewMatrix;","uniform mat4 projectionMatrix;","uniform mat4 viewMatrix;","uniform mat3 normalMatrix;","uniform vec3 cameraPosition;","uniform bool isOrthographic;","#ifdef USE_INSTANCING","\tattribute mat4 instanceMatrix;","#endif","#ifdef USE_INSTANCING_COLOR","\tattribute vec3 instanceColor;","#endif","#ifdef USE_INSTANCING_MORPH","\tuniform sampler2D morphTexture;","#endif","attribute vec3 position;","attribute vec3 normal;","attribute vec2 uv;","#ifdef USE_UV1","\tattribute vec2 uv1;","#endif","#ifdef USE_UV2","\tattribute vec2 uv2;","#endif","#ifdef USE_UV3","\tattribute vec2 uv3;","#endif","#ifdef USE_TANGENT","\tattribute vec4 tangent;","#endif","#if defined( USE_COLOR_ALPHA )","\tattribute vec4 color;","#elif defined( USE_COLOR )","\tattribute vec3 color;","#endif","#ifdef USE_SKINNING","\tattribute vec4 skinIndex;","\tattribute vec4 skinWeight;","#endif","\n"].filter(Ai).join("\n"),v=[Ni(n),"#define SHADER_TYPE "+n.shaderType,"#define SHADER_NAME "+n.shaderName,h,n.useFog&&n.fog?"#define USE_FOG":"",n.useFog&&n.fogExp2?"#define FOG_EXP2":"",n.alphaToCoverage?"#define ALPHA_TO_COVERAGE":"",n.map?"#define USE_MAP":"",n.matcap?"#define USE_MATCAP":"",n.envMap?"#define USE_ENVMAP":"",n.envMap?"#define "+d:"",n.envMap?"#define "+u:"",n.envMap?"#define "+f:"",p?"#define CUBEUV_TEXEL_WIDTH "+p.texelWidth:"",p?"#define CUBEUV_TEXEL_HEIGHT "+p.texelHeight:"",p?"#define CUBEUV_MAX_MIP "+p.maxMip+".0":"",n.lightMap?"#define USE_LIGHTMAP":"",n.aoMap?"#define USE_AOMAP":"",n.bumpMap?"#define USE_BUMPMAP":"",n.normalMap?"#define USE_NORMALMAP":"",n.normalMapObjectSpace?"#define USE_NORMALMAP_OBJECTSPACE":"",n.normalMapTangentSpace?"#define USE_NORMALMAP_TANGENTSPACE":"",n.emissiveMap?"#define USE_EMISSIVEMAP":"",n.anisotropy?"#define USE_ANISOTROPY":"",n.anisotropyMap?"#define USE_ANISOTROPYMAP":"",n.clearcoat?"#define USE_CLEARCOAT":"",n.clearcoatMap?"#define USE_CLEARCOATMAP":"",n.clearcoatRoughnessMap?"#define USE_CLEARCOAT_ROUGHNESSMAP":"",n.clearcoatNormalMap?"#define USE_CLEARCOAT_NORMALMAP":"",n.dispersion?"#define USE_DISPERSION":"",n.iridescence?"#define USE_IRIDESCENCE":"",n.iridescenceMap?"#define USE_IRIDESCENCEMAP":"",n.iridescenceThicknessMap?"#define USE_IRIDESCENCE_THICKNESSMAP":"",n.specularMap?"#define USE_SPECULARMAP":"",n.specularColorMap?"#define USE_SPECULAR_COLORMAP":"",n.specularIntensityMap?"#define USE_SPECULAR_INTENSITYMAP":"",n.roughnessMap?"#define USE_ROUGHNESSMAP":"",n.metalnessMap?"#define USE_METALNESSMAP":"",n.alphaMap?"#define USE_ALPHAMAP":"",n.alphaTest?"#define USE_ALPHATEST":"",n.alphaHash?"#define USE_ALPHAHASH":"",n.sheen?"#define USE_SHEEN":"",n.sheenColorMap?"#define USE_SHEEN_COLORMAP":"",n.sheenRoughnessMap?"#define USE_SHEEN_ROUGHNESSMAP":"",n.transmission?"#define USE_TRANSMISSION":"",n.transmissionMap?"#define USE_TRANSMISSIONMAP":"",n.thicknessMap?"#define USE_THICKNESSMAP":"",n.vertexTangents&&!1===n.flatShading?"#define USE_TANGENT":"",n.vertexColors||n.instancingColor||n.batchingColor?"#define USE_COLOR":"",n.vertexAlphas?"#define USE_COLOR_ALPHA":"",n.vertexUv1s?"#define USE_UV1":"",n.vertexUv2s?"#define USE_UV2":"",n.vertexUv3s?"#define USE_UV3":"",n.pointsUvs?"#define USE_POINTS_UV":"",n.gradientMap?"#define USE_GRADIENTMAP":"",n.flatShading?"#define FLAT_SHADED":"",n.doubleSided?"#define DOUBLE_SIDED":"",n.flipSided?"#define FLIP_SIDED":"",n.shadowMapEnabled?"#define USE_SHADOWMAP":"",n.shadowMapEnabled?"#define "+c:"",n.premultipliedAlpha?"#define PREMULTIPLIED_ALPHA":"",n.numLightProbes>0?"#define USE_LIGHT_PROBES":"",n.decodeVideoTexture?"#define DECODE_VIDEO_TEXTURE":"",n.decodeVideoTextureEmissive?"#define DECODE_VIDEO_TEXTURE_EMISSIVE":"",n.logarithmicDepthBuffer?"#define USE_LOGDEPTHBUF":"",n.reverseDepthBuffer?"#define USE_REVERSEDEPTHBUF":"","uniform mat4 viewMatrix;","uniform vec3 cameraPosition;","uniform bool isOrthographic;",n.toneMapping!==D?"#define TONE_MAPPING":"",n.toneMapping!==D?Pn.tonemapping_pars_fragment:"",n.toneMapping!==D?Mi("toneMapping",n.toneMapping):"",n.dithering?"#define DITHERING":"",n.opaque?"#define OPAQUE":"",Pn.colorspace_pars_fragment,Ti("linearToOutputTexel",n.outputColorSpace),Ri(),n.useDepthPacking?"#define DEPTH_PACKING "+n.depthPacking:"","\n"].filter(Ai).join("\n")),s=Pi(s),s=bi(s,n),s=Ci(s,n),l=Pi(l),l=bi(l,n),l=Ci(l,n),s=yi(s),l=yi(l),!0!==n.isRawShaderMaterial&&(E="#version 300 es\n",g=[m,"#define attribute in","#define varying out","#define texture2D texture"].join("\n")+"\n"+g,v=["#define varying in",n.glslVersion===Z?"":"layout(location = 0) out highp vec4 pc_fragColor;",n.glslVersion===Z?"":"#define gl_FragColor pc_fragColor","#define gl_FragDepthEXT gl_FragDepth","#define texture2D texture","#define textureCube texture","#define texture2DProj textureProj","#define texture2DLodEXT textureLod","#define texture2DProjLodEXT textureProjLod","#define textureCubeLodEXT textureLod","#define texture2DGradEXT textureGrad","#define texture2DProjGradEXT textureProjGrad","#define textureCubeGradEXT textureGrad"].join("\n")+"\n"+v);const S=E+g+s,T=E+v+l,M=gi(i,i.VERTEX_SHADER,S),x=gi(i,i.FRAGMENT_SHADER,T);function R(t){if(e.debug.checkShaderErrors){const n=i.getProgramInfoLog(_).trim(),r=i.getShaderInfoLog(M).trim(),a=i.getShaderInfoLog(x).trim();let o=!0,s=!0;if(!1===i.getProgramParameter(_,i.LINK_STATUS))if(o=!1,"function"==typeof e.debug.onShaderError)e.debug.onShaderError(i,_,M,x);else{const e=Si(i,M,"vertex"),r=Si(i,x,"fragment");console.error("THREE.WebGLProgram: Shader Error "+i.getError()+" - VALIDATE_STATUS "+i.getProgramParameter(_,i.VALIDATE_STATUS)+"\n\nMaterial Name: "+t.name+"\nMaterial Type: "+t.type+"\n\nProgram Info Log: "+n+"\n"+e+"\n"+r)}else""!==n?console.warn("THREE.WebGLProgram: Program Info Log:",n):""!==r&&""!==a||(s=!1);s&&(t.diagnostics={runnable:o,programLog:n,vertexShader:{log:r,prefix:g},fragmentShader:{log:a,prefix:v}})}i.deleteShader(M),i.deleteShader(x),A=new _i(i,_),b=function(e,t){const n={},r=e.getProgramParameter(t,e.ACTIVE_ATTRIBUTES);for(let i=0;i0,J=o.clearcoat>0,ee=o.dispersion>0,te=o.iridescence>0,ne=o.sheen>0,re=o.transmission>0,ie=Q&&!!o.anisotropyMap,ae=J&&!!o.clearcoatMap,oe=J&&!!o.clearcoatNormalMap,se=J&&!!o.clearcoatRoughnessMap,le=te&&!!o.iridescenceMap,ce=te&&!!o.iridescenceThicknessMap,de=ne&&!!o.sheenColorMap,ue=ne&&!!o.sheenRoughnessMap,_e=!!o.specularMap,ge=!!o.specularColorMap,ve=!!o.specularIntensityMap,Ee=re&&!!o.transmissionMap,Se=re&&!!o.thicknessMap,Te=!!o.gradientMap,Me=!!o.alphaMap,xe=o.alphaTest>0,Re=!!o.alphaHash,Ae=!!o.extensions;let be=D;o.toneMapped&&(null!==O&&!0!==O.isXRRenderTarget||(be=e.toneMapping));const Ce={shaderID:C,shaderType:o.type,shaderName:o.name,vertexShader:U,fragmentShader:w,defines:o.defines,customVertexShaderID:y,customFragmentShaderID:I,isRawShaderMaterial:!0===o.isRawShaderMaterial,glslVersion:o.glslVersion,precision:g,batching:G,batchingColor:G&&null!==T._colorsTexture,instancing:H,instancingColor:H&&null!==T.instanceColor,instancingMorph:H&&null!==T.morphTexture,supportsVertexTextures:_,outputColorSpace:null===O?e.outputColorSpace:!0===O.isXRRenderTarget?O.texture.colorSpace:F,alphaToCoverage:!!o.alphaToCoverage,map:V,matcap:z,envMap:k,envMapMode:k&&A.mapping,envMapCubeUVHeight:b,aoMap:W,lightMap:X,bumpMap:Y,normalMap:K,displacementMap:_&&j,emissiveMap:q,normalMapObjectSpace:K&&o.normalMapType===he,normalMapTangentSpace:K&&o.normalMapType===me,metalnessMap:Z,roughnessMap:$,anisotropy:Q,anisotropyMap:ie,clearcoat:J,clearcoatMap:ae,clearcoatNormalMap:oe,clearcoatRoughnessMap:se,dispersion:ee,iridescence:te,iridescenceMap:le,iridescenceThicknessMap:ce,sheen:ne,sheenColorMap:de,sheenRoughnessMap:ue,specularMap:_e,specularColorMap:ge,specularIntensityMap:ve,transmission:re,transmissionMap:Ee,thicknessMap:Se,gradientMap:Te,opaque:!1===o.transparent&&o.blending===pe&&!1===o.alphaToCoverage,alphaMap:Me,alphaTest:xe,alphaHash:Re,combine:o.combine,mapUv:V&&E(o.map.channel),aoMapUv:W&&E(o.aoMap.channel),lightMapUv:X&&E(o.lightMap.channel),bumpMapUv:Y&&E(o.bumpMap.channel),normalMapUv:K&&E(o.normalMap.channel),displacementMapUv:j&&E(o.displacementMap.channel),emissiveMapUv:q&&E(o.emissiveMap.channel),metalnessMapUv:Z&&E(o.metalnessMap.channel),roughnessMapUv:$&&E(o.roughnessMap.channel),anisotropyMapUv:ie&&E(o.anisotropyMap.channel),clearcoatMapUv:ae&&E(o.clearcoatMap.channel),clearcoatNormalMapUv:oe&&E(o.clearcoatNormalMap.channel),clearcoatRoughnessMapUv:se&&E(o.clearcoatRoughnessMap.channel),iridescenceMapUv:le&&E(o.iridescenceMap.channel),iridescenceThicknessMapUv:ce&&E(o.iridescenceThicknessMap.channel),sheenColorMapUv:de&&E(o.sheenColorMap.channel),sheenRoughnessMapUv:ue&&E(o.sheenRoughnessMap.channel),specularMapUv:_e&&E(o.specularMap.channel),specularColorMapUv:ge&&E(o.specularColorMap.channel),specularIntensityMapUv:ve&&E(o.specularIntensityMap.channel),transmissionMapUv:Ee&&E(o.transmissionMap.channel),thicknessMapUv:Se&&E(o.thicknessMap.channel),alphaMapUv:Me&&E(o.alphaMap.channel),vertexTangents:!!x.attributes.tangent&&(K||Q),vertexColors:o.vertexColors,vertexAlphas:!0===o.vertexColors&&!!x.attributes.color&&4===x.attributes.color.itemSize,pointsUvs:!0===T.isPoints&&!!x.attributes.uv&&(V||Me),fog:!!M,useFog:!0===o.fog,fogExp2:!!M&&M.isFogExp2,flatShading:!0===o.flatShading&&!1===o.wireframe,sizeAttenuation:!0===o.sizeAttenuation,logarithmicDepthBuffer:h,reverseDepthBuffer:B,skinning:!0===T.isSkinnedMesh,morphTargets:void 0!==x.morphAttributes.position,morphNormals:void 0!==x.morphAttributes.normal,morphColors:void 0!==x.morphAttributes.color,morphTargetsCount:P,morphTextureStride:N,numDirLights:l.directional.length,numPointLights:l.point.length,numSpotLights:l.spot.length,numSpotLightMaps:l.spotLightMap.length,numRectAreaLights:l.rectArea.length,numHemiLights:l.hemi.length,numDirLightShadows:l.directionalShadowMap.length,numPointLightShadows:l.pointShadowMap.length,numSpotLightShadows:l.spotShadowMap.length,numSpotLightShadowsWithMaps:l.numSpotLightShadowsWithMaps,numLightProbes:l.numLightProbes,numClippingPlanes:s.numPlanes,numClipIntersection:s.numIntersection,dithering:o.dithering,shadowMapEnabled:e.shadowMap.enabled&&f.length>0,shadowMapType:e.shadowMap.type,toneMapping:be,decodeVideoTexture:V&&!0===o.map.isVideoTexture&&p.getTransfer(o.map.colorSpace)===m,decodeVideoTextureEmissive:q&&!0===o.emissiveMap.isVideoTexture&&p.getTransfer(o.emissiveMap.colorSpace)===m,premultipliedAlpha:o.premultipliedAlpha,doubleSided:o.side===fe,flipSided:o.side===c,useDepthPacking:o.depthPacking>=0,depthPacking:o.depthPacking||0,index0AttributeName:o.index0AttributeName,extensionClipCullDistance:Ae&&!0===o.extensions.clipCullDistance&&r.has("WEBGL_clip_cull_distance"),extensionMultiDraw:(Ae&&!0===o.extensions.multiDraw||G)&&r.has("WEBGL_multi_draw"),rendererExtensionParallelShaderCompile:r.has("KHR_parallel_shader_compile"),customProgramCacheKey:o.customProgramCacheKey()};return Ce.vertexUv1s=u.has(1),Ce.vertexUv2s=u.has(2),Ce.vertexUv3s=u.has(3),u.clear(),Ce},getProgramCacheKey:function(t){const n=[];if(t.shaderID?n.push(t.shaderID):(n.push(t.customVertexShaderID),n.push(t.customFragmentShaderID)),void 0!==t.defines)for(const e in t.defines)n.push(e),n.push(t.defines[e]);return!1===t.isRawShaderMaterial&&(!function(e,t){e.push(t.precision),e.push(t.outputColorSpace),e.push(t.envMapMode),e.push(t.envMapCubeUVHeight),e.push(t.mapUv),e.push(t.alphaMapUv),e.push(t.lightMapUv),e.push(t.aoMapUv),e.push(t.bumpMapUv),e.push(t.normalMapUv),e.push(t.displacementMapUv),e.push(t.emissiveMapUv),e.push(t.metalnessMapUv),e.push(t.roughnessMapUv),e.push(t.anisotropyMapUv),e.push(t.clearcoatMapUv),e.push(t.clearcoatNormalMapUv),e.push(t.clearcoatRoughnessMapUv),e.push(t.iridescenceMapUv),e.push(t.iridescenceThicknessMapUv),e.push(t.sheenColorMapUv),e.push(t.sheenRoughnessMapUv),e.push(t.specularMapUv),e.push(t.specularColorMapUv),e.push(t.specularIntensityMapUv),e.push(t.transmissionMapUv),e.push(t.thicknessMapUv),e.push(t.combine),e.push(t.fogExp2),e.push(t.sizeAttenuation),e.push(t.morphTargetsCount),e.push(t.morphAttributeCount),e.push(t.numDirLights),e.push(t.numPointLights),e.push(t.numSpotLights),e.push(t.numSpotLightMaps),e.push(t.numHemiLights),e.push(t.numRectAreaLights),e.push(t.numDirLightShadows),e.push(t.numPointLightShadows),e.push(t.numSpotLightShadows),e.push(t.numSpotLightShadowsWithMaps),e.push(t.numLightProbes),e.push(t.shadowMapType),e.push(t.toneMapping),e.push(t.numClippingPlanes),e.push(t.numClipIntersection),e.push(t.depthPacking)}(n,t),function(e,t){l.disableAll(),t.supportsVertexTextures&&l.enable(0);t.instancing&&l.enable(1);t.instancingColor&&l.enable(2);t.instancingMorph&&l.enable(3);t.matcap&&l.enable(4);t.envMap&&l.enable(5);t.normalMapObjectSpace&&l.enable(6);t.normalMapTangentSpace&&l.enable(7);t.clearcoat&&l.enable(8);t.iridescence&&l.enable(9);t.alphaTest&&l.enable(10);t.vertexColors&&l.enable(11);t.vertexAlphas&&l.enable(12);t.vertexUv1s&&l.enable(13);t.vertexUv2s&&l.enable(14);t.vertexUv3s&&l.enable(15);t.vertexTangents&&l.enable(16);t.anisotropy&&l.enable(17);t.alphaHash&&l.enable(18);t.batching&&l.enable(19);t.dispersion&&l.enable(20);t.batchingColor&&l.enable(21);t.gradientMap&&l.enable(22);e.push(l.mask),l.disableAll(),t.fog&&l.enable(0);t.useFog&&l.enable(1);t.flatShading&&l.enable(2);t.logarithmicDepthBuffer&&l.enable(3);t.reverseDepthBuffer&&l.enable(4);t.skinning&&l.enable(5);t.morphTargets&&l.enable(6);t.morphNormals&&l.enable(7);t.morphColors&&l.enable(8);t.premultipliedAlpha&&l.enable(9);t.shadowMapEnabled&&l.enable(10);t.doubleSided&&l.enable(11);t.flipSided&&l.enable(12);t.useDepthPacking&&l.enable(13);t.dithering&&l.enable(14);t.transmission&&l.enable(15);t.sheen&&l.enable(16);t.opaque&&l.enable(17);t.pointsUvs&&l.enable(18);t.decodeVideoTexture&&l.enable(19);t.decodeVideoTextureEmissive&&l.enable(20);t.alphaToCoverage&&l.enable(21);e.push(l.mask)}(n,t),n.push(e.outputColorSpace)),n.push(t.customProgramCacheKey),n.join()},getUniforms:function(e){const t=v[e.type];let n;if(t){const e=Dn[t];n=ue.clone(e.uniforms)}else n=e.uniforms;return n},acquireProgram:function(t,n){let r;for(let e=0,t=f.length;e0?r.push(d):!0===o.transparent?i.push(d):n.push(d)},unshift:function(e,t,o,s,l,c){const d=a(e,t,o,s,l,c);o.transmission>0?r.unshift(d):!0===o.transparent?i.unshift(d):n.unshift(d)},finish:function(){for(let n=t,r=e.length;n1&&n.sort(e||zi),r.length>1&&r.sort(t||ki),i.length>1&&i.sort(t||ki)}}}function Xi(){let e=new WeakMap;return{get:function(t,n){const r=e.get(t);let i;return void 0===r?(i=new Wi,e.set(t,[i])):n>=r.length?(i=new Wi,r.push(i)):i=r[n],i},dispose:function(){e=new WeakMap}}}function Yi(){const e={};return{get:function(t){if(void 0!==e[t.id])return e[t.id];let r;switch(t.type){case"DirectionalLight":r={direction:new i,color:new n};break;case"SpotLight":r={position:new i,direction:new i,color:new n,distance:0,coneCos:0,penumbraCos:0,decay:0};break;case"PointLight":r={position:new i,color:new n,distance:0,decay:0};break;case"HemisphereLight":r={direction:new i,skyColor:new n,groundColor:new n};break;case"RectAreaLight":r={color:new n,position:new i,halfWidth:new i,halfHeight:new i}}return e[t.id]=r,r}}}let Ki=0;function ji(e,t){return(t.castShadow?2:0)-(e.castShadow?2:0)+(t.map?1:0)-(e.map?1:0)}function qi(e){const n=new Yi,r=function(){const e={};return{get:function(n){if(void 0!==e[n.id])return e[n.id];let r;switch(n.type){case"DirectionalLight":case"SpotLight":r={shadowIntensity:1,shadowBias:0,shadowNormalBias:0,shadowRadius:1,shadowMapSize:new t};break;case"PointLight":r={shadowIntensity:1,shadowBias:0,shadowNormalBias:0,shadowRadius:1,shadowMapSize:new t,shadowCameraNear:1,shadowCameraFar:1e3}}return e[n.id]=r,r}}}(),a={version:0,hash:{directionalLength:-1,pointLength:-1,spotLength:-1,rectAreaLength:-1,hemiLength:-1,numDirectionalShadows:-1,numPointShadows:-1,numSpotShadows:-1,numSpotMaps:-1,numLightProbes:-1},ambient:[0,0,0],probe:[],directional:[],directionalShadow:[],directionalShadowMap:[],directionalShadowMatrix:[],spot:[],spotLightMap:[],spotShadow:[],spotShadowMap:[],spotLightMatrix:[],rectArea:[],rectAreaLTC1:null,rectAreaLTC2:null,point:[],pointShadow:[],pointShadowMap:[],pointShadowMatrix:[],hemi:[],numSpotLightShadowsWithMaps:0,numLightProbes:0};for(let e=0;e<9;e++)a.probe.push(new i);const o=new i,s=new f,l=new f;return{setup:function(t){let i=0,o=0,s=0;for(let e=0;e<9;e++)a.probe[e].set(0,0,0);let l=0,c=0,d=0,u=0,f=0,p=0,m=0,h=0,_=0,g=0,v=0;t.sort(ji);for(let e=0,E=t.length;e0&&(!0===e.has("OES_texture_float_linear")?(a.rectAreaLTC1=Un.LTC_FLOAT_1,a.rectAreaLTC2=Un.LTC_FLOAT_2):(a.rectAreaLTC1=Un.LTC_HALF_1,a.rectAreaLTC2=Un.LTC_HALF_2)),a.ambient[0]=i,a.ambient[1]=o,a.ambient[2]=s;const E=a.hash;E.directionalLength===l&&E.pointLength===c&&E.spotLength===d&&E.rectAreaLength===u&&E.hemiLength===f&&E.numDirectionalShadows===p&&E.numPointShadows===m&&E.numSpotShadows===h&&E.numSpotMaps===_&&E.numLightProbes===v||(a.directional.length=l,a.spot.length=d,a.rectArea.length=u,a.point.length=c,a.hemi.length=f,a.directionalShadow.length=p,a.directionalShadowMap.length=p,a.pointShadow.length=m,a.pointShadowMap.length=m,a.spotShadow.length=h,a.spotShadowMap.length=h,a.directionalShadowMatrix.length=p,a.pointShadowMatrix.length=m,a.spotLightMatrix.length=h+_-g,a.spotLightMap.length=_,a.numSpotLightShadowsWithMaps=g,a.numLightProbes=v,E.directionalLength=l,E.pointLength=c,E.spotLength=d,E.rectAreaLength=u,E.hemiLength=f,E.numDirectionalShadows=p,E.numPointShadows=m,E.numSpotShadows=h,E.numSpotMaps=_,E.numLightProbes=v,a.version=Ki++)},setupView:function(e,t){let n=0,r=0,i=0,c=0,d=0;const u=t.matrixWorldInverse;for(let t=0,f=e.length;t=i.length?(a=new Zi(e),i.push(a)):a=i[r],a},dispose:function(){t=new WeakMap}}}function Qi(e,n,r){let i=new ge;const a=new t,s=new t,d=new k,u=new ve({depthPacking:Ee}),f=new Se,p={},m=r.maxTextureSize,h={[_]:c,[c]:_,[fe]:fe},g=new l({defines:{VSM_SAMPLES:8},uniforms:{shadow_pass:{value:null},resolution:{value:new t},radius:{value:4}},vertexShader:"void main() {\n\tgl_Position = vec4( position, 1.0 );\n}",fragmentShader:"uniform sampler2D shadow_pass;\nuniform vec2 resolution;\nuniform float radius;\n#include \nvoid main() {\n\tconst float samples = float( VSM_SAMPLES );\n\tfloat mean = 0.0;\n\tfloat squared_mean = 0.0;\n\tfloat uvStride = samples <= 1.0 ? 0.0 : 2.0 / ( samples - 1.0 );\n\tfloat uvStart = samples <= 1.0 ? 0.0 : - 1.0;\n\tfor ( float i = 0.0; i < samples; i ++ ) {\n\t\tfloat uvOffset = uvStart + i * uvStride;\n\t\t#ifdef HORIZONTAL_PASS\n\t\t\tvec2 distribution = unpackRGBATo2Half( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( uvOffset, 0.0 ) * radius ) / resolution ) );\n\t\t\tmean += distribution.x;\n\t\t\tsquared_mean += distribution.y * distribution.y + distribution.x * distribution.x;\n\t\t#else\n\t\t\tfloat depth = unpackRGBAToDepth( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( 0.0, uvOffset ) * radius ) / resolution ) );\n\t\t\tmean += depth;\n\t\t\tsquared_mean += depth * depth;\n\t\t#endif\n\t}\n\tmean = mean / samples;\n\tsquared_mean = squared_mean / samples;\n\tfloat std_dev = sqrt( squared_mean - mean * mean );\n\tgl_FragColor = pack2HalfToRGBA( vec2( mean, std_dev ) );\n}"}),v=g.clone();v.defines.HORIZONTAL_PASS=1;const E=new N;E.setAttribute("position",new O(new Float32Array([-1,-1,.5,3,-1,.5,-1,3,.5]),3));const S=new o(E,g),T=this;this.enabled=!1,this.autoUpdate=!0,this.needsUpdate=!1,this.type=$;let M=this.type;function x(t,r){const i=n.update(S);g.defines.VSM_SAMPLES!==t.blurSamples&&(g.defines.VSM_SAMPLES=t.blurSamples,v.defines.VSM_SAMPLES=t.blurSamples,g.needsUpdate=!0,v.needsUpdate=!0),null===t.mapPass&&(t.mapPass=new I(a.x,a.y)),g.uniforms.shadow_pass.value=t.map.texture,g.uniforms.resolution.value=t.mapSize,g.uniforms.radius.value=t.radius,e.setRenderTarget(t.mapPass),e.clear(),e.renderBufferDirect(r,null,i,g,S,null),v.uniforms.shadow_pass.value=t.mapPass.texture,v.uniforms.resolution.value=t.mapSize,v.uniforms.radius.value=t.radius,e.setRenderTarget(t.map),e.clear(),e.renderBufferDirect(r,null,i,v,S,null)}function R(t,n,r,i){let a=null;const o=!0===r.isPointLight?t.customDistanceMaterial:t.customDepthMaterial;if(void 0!==o)a=o;else if(a=!0===r.isPointLight?f:u,e.localClippingEnabled&&!0===n.clipShadows&&Array.isArray(n.clippingPlanes)&&0!==n.clippingPlanes.length||n.displacementMap&&0!==n.displacementScale||n.alphaMap&&n.alphaTest>0||n.map&&n.alphaTest>0||!0===n.alphaToCoverage){const e=a.uuid,t=n.uuid;let r=p[e];void 0===r&&(r={},p[e]=r);let i=r[t];void 0===i&&(i=a.clone(),r[t]=i,n.addEventListener("dispose",b)),a=i}if(a.visible=n.visible,a.wireframe=n.wireframe,a.side=i===J?null!==n.shadowSide?n.shadowSide:n.side:null!==n.shadowSide?n.shadowSide:h[n.side],a.alphaMap=n.alphaMap,a.alphaTest=!0===n.alphaToCoverage?.5:n.alphaTest,a.map=n.map,a.clipShadows=n.clipShadows,a.clippingPlanes=n.clippingPlanes,a.clipIntersection=n.clipIntersection,a.displacementMap=n.displacementMap,a.displacementScale=n.displacementScale,a.displacementBias=n.displacementBias,a.wireframeLinewidth=n.wireframeLinewidth,a.linewidth=n.linewidth,!0===r.isPointLight&&!0===a.isMeshDistanceMaterial){e.properties.get(a).light=r}return a}function A(t,r,a,o,s){if(!1===t.visible)return;if(t.layers.test(r.layers)&&(t.isMesh||t.isLine||t.isPoints)&&(t.castShadow||t.receiveShadow&&s===J)&&(!t.frustumCulled||i.intersectsObject(t))){t.modelViewMatrix.multiplyMatrices(a.matrixWorldInverse,t.matrixWorld);const i=n.update(t),l=t.material;if(Array.isArray(l)){const n=i.groups;for(let c=0,d=n.length;cm||a.y>m)&&(a.x>m&&(s.x=Math.floor(m/h.x),a.x=s.x*h.x,c.mapSize.x=s.x),a.y>m&&(s.y=Math.floor(m/h.y),a.y=s.y*h.y,c.mapSize.y=s.y)),null===c.map||!0===f||!0===p){const e=this.type!==J?{minFilter:Te,magFilter:Te}:{};null!==c.map&&c.map.dispose(),c.map=new I(a.x,a.y,e),c.map.texture.name=l.name+".shadowMap",c.camera.updateProjectionMatrix()}e.setRenderTarget(c.map),e.clear();const _=c.getViewportCount();for(let e=0;e<_;e++){const t=c.getViewport(e);d.set(s.x*t.x,s.y*t.y,s.x*t.z,s.y*t.w),u.viewport(d),c.updateMatrices(l,e),i=c.getFrustum(),A(n,r,c.camera,l,this.type)}!0!==c.isPointLightShadow&&this.type===J&&x(c,r),c.needsUpdate=!1}M=this.type,T.needsUpdate=!1,e.setRenderTarget(o,l,c)}}const Ji={[Ke]:Ye,[Xe]:ze,[We]:Ve,[Me]:ke,[Ye]:Ke,[ze]:Xe,[Ve]:We,[ke]:Me};function ea(e,t){const r=new function(){let t=!1;const n=new k;let r=null;const i=new k(0,0,0,0);return{setMask:function(n){r===n||t||(e.colorMask(n,n,n,n),r=n)},setLocked:function(e){t=e},setClear:function(t,r,a,o,s){!0===s&&(t*=o,r*=o,a*=o),n.set(t,r,a,o),!1===i.equals(n)&&(e.clearColor(t,r,a,o),i.copy(n))},reset:function(){t=!1,r=null,i.set(-1,0,0,0)}}},i=new function(){let n=!1,r=!1,i=null,a=null,o=null;return{setReversed:function(e){if(r!==e){const n=t.get("EXT_clip_control");e?n.clipControlEXT(n.LOWER_LEFT_EXT,n.ZERO_TO_ONE_EXT):n.clipControlEXT(n.LOWER_LEFT_EXT,n.NEGATIVE_ONE_TO_ONE_EXT),r=e;const i=o;o=null,this.setClear(i)}},getReversed:function(){return r},setTest:function(t){t?W(e.DEPTH_TEST):X(e.DEPTH_TEST)},setMask:function(t){i===t||n||(e.depthMask(t),i=t)},setFunc:function(t){if(r&&(t=Ji[t]),a!==t){switch(t){case Ke:e.depthFunc(e.NEVER);break;case Ye:e.depthFunc(e.ALWAYS);break;case Xe:e.depthFunc(e.LESS);break;case Me:e.depthFunc(e.LEQUAL);break;case We:e.depthFunc(e.EQUAL);break;case ke:e.depthFunc(e.GEQUAL);break;case ze:e.depthFunc(e.GREATER);break;case Ve:e.depthFunc(e.NOTEQUAL);break;default:e.depthFunc(e.LEQUAL)}a=t}},setLocked:function(e){n=e},setClear:function(t){o!==t&&(r&&(t=1-t),e.clearDepth(t),o=t)},reset:function(){n=!1,i=null,a=null,o=null,r=!1}}},a=new function(){let t=!1,n=null,r=null,i=null,a=null,o=null,s=null,l=null,c=null;return{setTest:function(n){t||(n?W(e.STENCIL_TEST):X(e.STENCIL_TEST))},setMask:function(r){n===r||t||(e.stencilMask(r),n=r)},setFunc:function(t,n,o){r===t&&i===n&&a===o||(e.stencilFunc(t,n,o),r=t,i=n,a=o)},setOp:function(t,n,r){o===t&&s===n&&l===r||(e.stencilOp(t,n,r),o=t,s=n,l=r)},setLocked:function(e){t=e},setClear:function(t){c!==t&&(e.clearStencil(t),c=t)},reset:function(){t=!1,n=null,r=null,i=null,a=null,o=null,s=null,l=null,c=null}}},o=new WeakMap,s=new WeakMap;let l={},d={},u=new WeakMap,f=[],p=null,m=!1,h=null,_=null,g=null,v=null,E=null,S=null,T=null,M=new n(0,0,0),x=0,R=!1,A=null,b=null,C=null,L=null,P=null;const U=e.getParameter(e.MAX_COMBINED_TEXTURE_IMAGE_UNITS);let D=!1,w=0;const I=e.getParameter(e.VERSION);-1!==I.indexOf("WebGL")?(w=parseFloat(/^WebGL (\d)/.exec(I)[1]),D=w>=1):-1!==I.indexOf("OpenGL ES")&&(w=parseFloat(/^OpenGL ES (\d)/.exec(I)[1]),D=w>=2);let N=null,O={};const F=e.getParameter(e.SCISSOR_BOX),B=e.getParameter(e.VIEWPORT),H=(new k).fromArray(F),G=(new k).fromArray(B);function V(t,n,r,i){const a=new Uint8Array(4),o=e.createTexture();e.bindTexture(t,o),e.texParameteri(t,e.TEXTURE_MIN_FILTER,e.NEAREST),e.texParameteri(t,e.TEXTURE_MAG_FILTER,e.NEAREST);for(let o=0;on||i.height>n)&&(r=n/Math.max(i.width,i.height)),r<1){if("undefined"!=typeof HTMLImageElement&&e instanceof HTMLImageElement||"undefined"!=typeof HTMLCanvasElement&&e instanceof HTMLCanvasElement||"undefined"!=typeof ImageBitmap&&e instanceof ImageBitmap||"undefined"!=typeof VideoFrame&&e instanceof VideoFrame){const n=Math.floor(r*i.width),a=Math.floor(r*i.height);void 0===f&&(f=g(n,a));const o=t?g(n,a):f;o.width=n,o.height=a;return o.getContext("2d").drawImage(e,0,0,n,a),console.warn("THREE.WebGLRenderer: Texture has been resized from ("+i.width+"x"+i.height+") to ("+n+"x"+a+")."),o}return"data"in e&&console.warn("THREE.WebGLRenderer: Image in DataTexture is too big ("+i.width+"x"+i.height+")."),e}return e}function E(e){return e.generateMipmaps}function x(t){e.generateMipmap(t)}function R(t){return t.isWebGLCubeRenderTarget?e.TEXTURE_CUBE_MAP:t.isWebGL3DRenderTarget?e.TEXTURE_3D:t.isWebGLArrayRenderTarget||t.isCompressedArrayTexture?e.TEXTURE_2D_ARRAY:e.TEXTURE_2D}function A(t,r,i,a,o=!1){if(null!==t){if(void 0!==e[t])return e[t];console.warn("THREE.WebGLRenderer: Attempt to use non-existing WebGL internal format '"+t+"'")}let s=r;if(r===e.RED&&(i===e.FLOAT&&(s=e.R32F),i===e.HALF_FLOAT&&(s=e.R16F),i===e.UNSIGNED_BYTE&&(s=e.R8)),r===e.RED_INTEGER&&(i===e.UNSIGNED_BYTE&&(s=e.R8UI),i===e.UNSIGNED_SHORT&&(s=e.R16UI),i===e.UNSIGNED_INT&&(s=e.R32UI),i===e.BYTE&&(s=e.R8I),i===e.SHORT&&(s=e.R16I),i===e.INT&&(s=e.R32I)),r===e.RG&&(i===e.FLOAT&&(s=e.RG32F),i===e.HALF_FLOAT&&(s=e.RG16F),i===e.UNSIGNED_BYTE&&(s=e.RG8)),r===e.RG_INTEGER&&(i===e.UNSIGNED_BYTE&&(s=e.RG8UI),i===e.UNSIGNED_SHORT&&(s=e.RG16UI),i===e.UNSIGNED_INT&&(s=e.RG32UI),i===e.BYTE&&(s=e.RG8I),i===e.SHORT&&(s=e.RG16I),i===e.INT&&(s=e.RG32I)),r===e.RGB_INTEGER&&(i===e.UNSIGNED_BYTE&&(s=e.RGB8UI),i===e.UNSIGNED_SHORT&&(s=e.RGB16UI),i===e.UNSIGNED_INT&&(s=e.RGB32UI),i===e.BYTE&&(s=e.RGB8I),i===e.SHORT&&(s=e.RGB16I),i===e.INT&&(s=e.RGB32I)),r===e.RGBA_INTEGER&&(i===e.UNSIGNED_BYTE&&(s=e.RGBA8UI),i===e.UNSIGNED_SHORT&&(s=e.RGBA16UI),i===e.UNSIGNED_INT&&(s=e.RGBA32UI),i===e.BYTE&&(s=e.RGBA8I),i===e.SHORT&&(s=e.RGBA16I),i===e.INT&&(s=e.RGBA32I)),r===e.RGB&&i===e.UNSIGNED_INT_5_9_9_9_REV&&(s=e.RGB9_E5),r===e.RGBA){const t=o?se:p.getTransfer(a);i===e.FLOAT&&(s=e.RGBA32F),i===e.HALF_FLOAT&&(s=e.RGBA16F),i===e.UNSIGNED_BYTE&&(s=t===m?e.SRGB8_ALPHA8:e.RGBA8),i===e.UNSIGNED_SHORT_4_4_4_4&&(s=e.RGBA4),i===e.UNSIGNED_SHORT_5_5_5_1&&(s=e.RGB5_A1)}return s!==e.R16F&&s!==e.R32F&&s!==e.RG16F&&s!==e.RG32F&&s!==e.RGBA16F&&s!==e.RGBA32F||n.get("EXT_color_buffer_float"),s}function b(t,n){let r;return t?null===n||n===Tt||n===Mt?r=e.DEPTH24_STENCIL8:n===T?r=e.DEPTH32F_STENCIL8:n===xt&&(r=e.DEPTH24_STENCIL8,console.warn("DepthTexture: 16 bit depth attachment is not supported with stencil. Using 24-bit attachment.")):null===n||n===Tt||n===Mt?r=e.DEPTH_COMPONENT24:n===T?r=e.DEPTH_COMPONENT32F:n===xt&&(r=e.DEPTH_COMPONENT16),r}function C(e,t){return!0===E(e)||e.isFramebufferTexture&&e.minFilter!==Te&&e.minFilter!==B?Math.log2(Math.max(t.width,t.height))+1:void 0!==e.mipmaps&&e.mipmaps.length>0?e.mipmaps.length:e.isCompressedTexture&&Array.isArray(e.image)?t.mipmaps.length:1}function L(e){const t=e.target;t.removeEventListener("dispose",L),function(e){const t=i.get(e);if(void 0===t.__webglInit)return;const n=e.source,r=h.get(n);if(r){const i=r[t.__cacheKey];i.usedTimes--,0===i.usedTimes&&U(e),0===Object.keys(r).length&&h.delete(n)}i.remove(e)}(t),t.isVideoTexture&&u.delete(t)}function P(t){const n=t.target;n.removeEventListener("dispose",P),function(t){const n=i.get(t);t.depthTexture&&(t.depthTexture.dispose(),i.remove(t.depthTexture));if(t.isWebGLCubeRenderTarget)for(let t=0;t<6;t++){if(Array.isArray(n.__webglFramebuffer[t]))for(let r=0;r0&&a.__version!==t.version){const e=t.image;if(null===e)console.warn("THREE.WebGLRenderer: Texture marked for update but no image data found.");else{if(!1!==e.complete)return void V(a,t,n);console.warn("THREE.WebGLRenderer: Texture marked for update but image is incomplete")}}r.bindTexture(e.TEXTURE_2D,a.__webglTexture,e.TEXTURE0+n)}const y={[at]:e.REPEAT,[it]:e.CLAMP_TO_EDGE,[rt]:e.MIRRORED_REPEAT},I={[Te]:e.NEAREST,[ct]:e.NEAREST_MIPMAP_NEAREST,[lt]:e.NEAREST_MIPMAP_LINEAR,[B]:e.LINEAR,[st]:e.LINEAR_MIPMAP_NEAREST,[ot]:e.LINEAR_MIPMAP_LINEAR},N={[_t]:e.NEVER,[ht]:e.ALWAYS,[mt]:e.LESS,[K]:e.LEQUAL,[pt]:e.EQUAL,[ft]:e.GEQUAL,[ut]:e.GREATER,[dt]:e.NOTEQUAL};function O(t,r){if(r.type!==T||!1!==n.has("OES_texture_float_linear")||r.magFilter!==B&&r.magFilter!==st&&r.magFilter!==lt&&r.magFilter!==ot&&r.minFilter!==B&&r.minFilter!==st&&r.minFilter!==lt&&r.minFilter!==ot||console.warn("THREE.WebGLRenderer: Unable to use linear filtering with floating point textures. OES_texture_float_linear not supported on this device."),e.texParameteri(t,e.TEXTURE_WRAP_S,y[r.wrapS]),e.texParameteri(t,e.TEXTURE_WRAP_T,y[r.wrapT]),t!==e.TEXTURE_3D&&t!==e.TEXTURE_2D_ARRAY||e.texParameteri(t,e.TEXTURE_WRAP_R,y[r.wrapR]),e.texParameteri(t,e.TEXTURE_MAG_FILTER,I[r.magFilter]),e.texParameteri(t,e.TEXTURE_MIN_FILTER,I[r.minFilter]),r.compareFunction&&(e.texParameteri(t,e.TEXTURE_COMPARE_MODE,e.COMPARE_REF_TO_TEXTURE),e.texParameteri(t,e.TEXTURE_COMPARE_FUNC,N[r.compareFunction])),!0===n.has("EXT_texture_filter_anisotropic")){if(r.magFilter===Te)return;if(r.minFilter!==lt&&r.minFilter!==ot)return;if(r.type===T&&!1===n.has("OES_texture_float_linear"))return;if(r.anisotropy>1||i.get(r).__currentAnisotropy){const o=n.get("EXT_texture_filter_anisotropic");e.texParameterf(t,o.TEXTURE_MAX_ANISOTROPY_EXT,Math.min(r.anisotropy,a.getMaxAnisotropy())),i.get(r).__currentAnisotropy=r.anisotropy}}}function H(t,n){let r=!1;void 0===t.__webglInit&&(t.__webglInit=!0,n.addEventListener("dispose",L));const i=n.source;let a=h.get(i);void 0===a&&(a={},h.set(i,a));const o=function(e){const t=[];return t.push(e.wrapS),t.push(e.wrapT),t.push(e.wrapR||0),t.push(e.magFilter),t.push(e.minFilter),t.push(e.anisotropy),t.push(e.internalFormat),t.push(e.format),t.push(e.type),t.push(e.generateMipmaps),t.push(e.premultiplyAlpha),t.push(e.flipY),t.push(e.unpackAlignment),t.push(e.colorSpace),t.join()}(n);if(o!==t.__cacheKey){void 0===a[o]&&(a[o]={texture:e.createTexture(),usedTimes:0},s.memory.textures++,r=!0),a[o].usedTimes++;const i=a[t.__cacheKey];void 0!==i&&(a[t.__cacheKey].usedTimes--,0===i.usedTimes&&U(n)),t.__cacheKey=o,t.__webglTexture=a[o].texture}return r}function G(e,t,n){return Math.floor(Math.floor(e/n)/t)}function V(t,n,s){let l=e.TEXTURE_2D;(n.isDataArrayTexture||n.isCompressedArrayTexture)&&(l=e.TEXTURE_2D_ARRAY),n.isData3DTexture&&(l=e.TEXTURE_3D);const c=H(t,n),d=n.source;r.bindTexture(l,t.__webglTexture,e.TEXTURE0+s);const u=i.get(d);if(d.version!==u.__version||!0===c){r.activeTexture(e.TEXTURE0+s);const t=p.getPrimaries(p.workingColorSpace),i=n.colorSpace===gt?null:p.getPrimaries(n.colorSpace),f=n.colorSpace===gt||t===i?e.NONE:e.BROWSER_DEFAULT_WEBGL;e.pixelStorei(e.UNPACK_FLIP_Y_WEBGL,n.flipY),e.pixelStorei(e.UNPACK_PREMULTIPLY_ALPHA_WEBGL,n.premultiplyAlpha),e.pixelStorei(e.UNPACK_ALIGNMENT,n.unpackAlignment),e.pixelStorei(e.UNPACK_COLORSPACE_CONVERSION_WEBGL,f);let m=v(n.image,!1,a.maxTextureSize);m=$(n,m);const h=o.convert(n.format,n.colorSpace),_=o.convert(n.type);let g,S=A(n.internalFormat,h,_,n.colorSpace,n.isVideoTexture);O(l,n);const T=n.mipmaps,R=!0!==n.isVideoTexture,L=void 0===u.__version||!0===c,P=d.dataReady,U=C(n,m);if(n.isDepthTexture)S=b(n.format===vt,n.type),L&&(R?r.texStorage2D(e.TEXTURE_2D,1,S,m.width,m.height):r.texImage2D(e.TEXTURE_2D,0,S,m.width,m.height,0,h,_,null));else if(n.isDataTexture)if(T.length>0){R&&L&&r.texStorage2D(e.TEXTURE_2D,U,S,T[0].width,T[0].height);for(let t=0,n=T.length;te.start-t.start);let s=0;for(let e=1;e0){const i=Et(g.width,g.height,n.format,n.type);for(const a of n.layerUpdates){const n=g.data.subarray(a*i/g.data.BYTES_PER_ELEMENT,(a+1)*i/g.data.BYTES_PER_ELEMENT);r.compressedTexSubImage3D(e.TEXTURE_2D_ARRAY,t,0,0,a,g.width,g.height,1,h,n)}n.clearLayerUpdates()}else r.compressedTexSubImage3D(e.TEXTURE_2D_ARRAY,t,0,0,0,g.width,g.height,m.depth,h,g.data)}else r.compressedTexImage3D(e.TEXTURE_2D_ARRAY,t,S,g.width,g.height,m.depth,0,g.data,0,0);else console.warn("THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()");else R?P&&r.texSubImage3D(e.TEXTURE_2D_ARRAY,t,0,0,0,g.width,g.height,m.depth,h,_,g.data):r.texImage3D(e.TEXTURE_2D_ARRAY,t,S,g.width,g.height,m.depth,0,h,_,g.data)}else{R&&L&&r.texStorage2D(e.TEXTURE_2D,U,S,T[0].width,T[0].height);for(let t=0,i=T.length;t0){const t=Et(m.width,m.height,n.format,n.type);for(const i of n.layerUpdates){const n=m.data.subarray(i*t/m.data.BYTES_PER_ELEMENT,(i+1)*t/m.data.BYTES_PER_ELEMENT);r.texSubImage3D(e.TEXTURE_2D_ARRAY,0,0,0,i,m.width,m.height,1,h,_,n)}n.clearLayerUpdates()}else r.texSubImage3D(e.TEXTURE_2D_ARRAY,0,0,0,0,m.width,m.height,m.depth,h,_,m.data)}else r.texImage3D(e.TEXTURE_2D_ARRAY,0,S,m.width,m.height,m.depth,0,h,_,m.data);else if(n.isData3DTexture)R?(L&&r.texStorage3D(e.TEXTURE_3D,U,S,m.width,m.height,m.depth),P&&r.texSubImage3D(e.TEXTURE_3D,0,0,0,0,m.width,m.height,m.depth,h,_,m.data)):r.texImage3D(e.TEXTURE_3D,0,S,m.width,m.height,m.depth,0,h,_,m.data);else if(n.isFramebufferTexture){if(L)if(R)r.texStorage2D(e.TEXTURE_2D,U,S,m.width,m.height);else{let t=m.width,n=m.height;for(let i=0;i>=1,n>>=1}}else if(T.length>0){if(R&&L){const t=Q(T[0]);r.texStorage2D(e.TEXTURE_2D,U,S,t.width,t.height)}for(let t=0,n=T.length;t>d),i=Math.max(1,n.height>>d);c===e.TEXTURE_3D||c===e.TEXTURE_2D_ARRAY?r.texImage3D(c,d,p,t,i,n.depth,0,u,f,null):r.texImage2D(c,d,p,t,i,0,u,f,null)}r.bindFramebuffer(e.FRAMEBUFFER,t),Z(n)?l.framebufferTexture2DMultisampleEXT(e.FRAMEBUFFER,s,c,h.__webglTexture,0,q(n)):(c===e.TEXTURE_2D||c>=e.TEXTURE_CUBE_MAP_POSITIVE_X&&c<=e.TEXTURE_CUBE_MAP_NEGATIVE_Z)&&e.framebufferTexture2D(e.FRAMEBUFFER,s,c,h.__webglTexture,d),r.bindFramebuffer(e.FRAMEBUFFER,null)}function k(t,n,r){if(e.bindRenderbuffer(e.RENDERBUFFER,t),n.depthBuffer){const i=n.depthTexture,a=i&&i.isDepthTexture?i.type:null,o=b(n.stencilBuffer,a),s=n.stencilBuffer?e.DEPTH_STENCIL_ATTACHMENT:e.DEPTH_ATTACHMENT,c=q(n);Z(n)?l.renderbufferStorageMultisampleEXT(e.RENDERBUFFER,c,o,n.width,n.height):r?e.renderbufferStorageMultisample(e.RENDERBUFFER,c,o,n.width,n.height):e.renderbufferStorage(e.RENDERBUFFER,o,n.width,n.height),e.framebufferRenderbuffer(e.FRAMEBUFFER,s,e.RENDERBUFFER,t)}else{const t=n.textures;for(let i=0;i{delete n.__boundDepthTexture,delete n.__depthDisposeCallback,e.removeEventListener("dispose",t)};e.addEventListener("dispose",t),n.__depthDisposeCallback=t}n.__boundDepthTexture=e}if(t.depthTexture&&!n.__autoAllocateDepthBuffer){if(a)throw new Error("target.depthTexture not supported in Cube render targets");const e=t.texture.mipmaps;e&&e.length>0?W(n.__webglFramebuffer[0],t):W(n.__webglFramebuffer,t)}else if(a){n.__webglDepthbuffer=[];for(let i=0;i<6;i++)if(r.bindFramebuffer(e.FRAMEBUFFER,n.__webglFramebuffer[i]),void 0===n.__webglDepthbuffer[i])n.__webglDepthbuffer[i]=e.createRenderbuffer(),k(n.__webglDepthbuffer[i],t,!1);else{const r=t.stencilBuffer?e.DEPTH_STENCIL_ATTACHMENT:e.DEPTH_ATTACHMENT,a=n.__webglDepthbuffer[i];e.bindRenderbuffer(e.RENDERBUFFER,a),e.framebufferRenderbuffer(e.FRAMEBUFFER,r,e.RENDERBUFFER,a)}}else{const i=t.texture.mipmaps;if(i&&i.length>0?r.bindFramebuffer(e.FRAMEBUFFER,n.__webglFramebuffer[0]):r.bindFramebuffer(e.FRAMEBUFFER,n.__webglFramebuffer),void 0===n.__webglDepthbuffer)n.__webglDepthbuffer=e.createRenderbuffer(),k(n.__webglDepthbuffer,t,!1);else{const r=t.stencilBuffer?e.DEPTH_STENCIL_ATTACHMENT:e.DEPTH_ATTACHMENT,i=n.__webglDepthbuffer;e.bindRenderbuffer(e.RENDERBUFFER,i),e.framebufferRenderbuffer(e.FRAMEBUFFER,r,e.RENDERBUFFER,i)}}r.bindFramebuffer(e.FRAMEBUFFER,null)}const Y=[],j=[];function q(e){return Math.min(a.maxSamples,e.samples)}function Z(e){const t=i.get(e);return e.samples>0&&!0===n.has("WEBGL_multisampled_render_to_texture")&&!1!==t.__useRenderToTexture}function $(e,t){const n=e.colorSpace,r=e.format,i=e.type;return!0===e.isCompressedTexture||!0===e.isVideoTexture||n!==F&&n!==gt&&(p.getTransfer(n)===m?r===M&&i===S||console.warn("THREE.WebGLTextures: sRGB encoded textures have to use RGBAFormat and UnsignedByteType."):console.error("THREE.WebGLTextures: Unsupported texture color space:",n)),t}function Q(e){return"undefined"!=typeof HTMLImageElement&&e instanceof HTMLImageElement?(d.width=e.naturalWidth||e.width,d.height=e.naturalHeight||e.height):"undefined"!=typeof VideoFrame&&e instanceof VideoFrame?(d.width=e.displayWidth,d.height=e.displayHeight):(d.width=e.width,d.height=e.height),d}this.allocateTextureUnit=function(){const e=D;return e>=a.maxTextures&&console.warn("THREE.WebGLTextures: Trying to use "+e+" texture units while this GPU supports only "+a.maxTextures),D+=1,e},this.resetTextureUnits=function(){D=0},this.setTexture2D=w,this.setTexture2DArray=function(t,n){const a=i.get(t);t.version>0&&a.__version!==t.version?V(a,t,n):r.bindTexture(e.TEXTURE_2D_ARRAY,a.__webglTexture,e.TEXTURE0+n)},this.setTexture3D=function(t,n){const a=i.get(t);t.version>0&&a.__version!==t.version?V(a,t,n):r.bindTexture(e.TEXTURE_3D,a.__webglTexture,e.TEXTURE0+n)},this.setTextureCube=function(t,n){const s=i.get(t);t.version>0&&s.__version!==t.version?function(t,n,s){if(6!==n.image.length)return;const l=H(t,n),c=n.source;r.bindTexture(e.TEXTURE_CUBE_MAP,t.__webglTexture,e.TEXTURE0+s);const d=i.get(c);if(c.version!==d.__version||!0===l){r.activeTexture(e.TEXTURE0+s);const t=p.getPrimaries(p.workingColorSpace),i=n.colorSpace===gt?null:p.getPrimaries(n.colorSpace),u=n.colorSpace===gt||t===i?e.NONE:e.BROWSER_DEFAULT_WEBGL;e.pixelStorei(e.UNPACK_FLIP_Y_WEBGL,n.flipY),e.pixelStorei(e.UNPACK_PREMULTIPLY_ALPHA_WEBGL,n.premultiplyAlpha),e.pixelStorei(e.UNPACK_ALIGNMENT,n.unpackAlignment),e.pixelStorei(e.UNPACK_COLORSPACE_CONVERSION_WEBGL,u);const f=n.isCompressedTexture||n.image[0].isCompressedTexture,m=n.image[0]&&n.image[0].isDataTexture,h=[];for(let e=0;e<6;e++)h[e]=f||m?m?n.image[e].image:n.image[e]:v(n.image[e],!0,a.maxCubemapSize),h[e]=$(n,h[e]);const _=h[0],g=o.convert(n.format,n.colorSpace),S=o.convert(n.type),T=A(n.internalFormat,g,S,n.colorSpace),R=!0!==n.isVideoTexture,b=void 0===d.__version||!0===l,L=c.dataReady;let P,U=C(n,_);if(O(e.TEXTURE_CUBE_MAP,n),f){R&&b&&r.texStorage2D(e.TEXTURE_CUBE_MAP,U,T,_.width,_.height);for(let t=0;t<6;t++){P=h[t].mipmaps;for(let i=0;i0&&U++;const t=Q(h[0]);r.texStorage2D(e.TEXTURE_CUBE_MAP,U,T,t.width,t.height)}for(let t=0;t<6;t++)if(m){R?L&&r.texSubImage2D(e.TEXTURE_CUBE_MAP_POSITIVE_X+t,0,0,0,h[t].width,h[t].height,g,S,h[t].data):r.texImage2D(e.TEXTURE_CUBE_MAP_POSITIVE_X+t,0,T,h[t].width,h[t].height,0,g,S,h[t].data);for(let n=0;n1;if(u||(void 0===l.__webglTexture&&(l.__webglTexture=e.createTexture()),l.__version=n.version,s.memory.textures++),d){a.__webglFramebuffer=[];for(let t=0;t<6;t++)if(n.mipmaps&&n.mipmaps.length>0){a.__webglFramebuffer[t]=[];for(let r=0;r0){a.__webglFramebuffer=[];for(let t=0;t0&&!1===Z(t)){a.__webglMultisampledFramebuffer=e.createFramebuffer(),a.__webglColorRenderbuffer=[],r.bindFramebuffer(e.FRAMEBUFFER,a.__webglMultisampledFramebuffer);for(let n=0;n0)for(let i=0;i0)for(let r=0;r0)if(!1===Z(t)){const n=t.textures,a=t.width,o=t.height;let s=e.COLOR_BUFFER_BIT;const l=t.stencilBuffer?e.DEPTH_STENCIL_ATTACHMENT:e.DEPTH_ATTACHMENT,d=i.get(t),u=n.length>1;if(u)for(let t=0;t0?r.bindFramebuffer(e.DRAW_FRAMEBUFFER,d.__webglFramebuffer[0]):r.bindFramebuffer(e.DRAW_FRAMEBUFFER,d.__webglFramebuffer);for(let r=0;r= 1.0 ) {\n\n\t\tgl_FragDepth = texture( depthColor, vec3( coord.x - 1.0, coord.y, 1 ) ).r;\n\n\t} else {\n\n\t\tgl_FragDepth = texture( depthColor, vec3( coord.x, coord.y, 0 ) ).r;\n\n\t}\n\n}",uniforms:{depthColor:{value:this.texture},depthWidth:{value:t.z},depthHeight:{value:t.w}}});this.mesh=new o(new h(20,20),n)}return this.mesh}reset(){this.texture=null,this.mesh=null}getDepthTexture(){return this.texture}}class ia extends _n{constructor(e,n){super();const r=this;let a=null,o=1,s=null,l="local-floor",c=1,d=null,u=null,f=null,p=null,m=null,h=null;const _=new ra,g=n.getContextAttributes();let v=null,E=null;const T=[],x=[],R=new t;let A=null;const b=new U;b.viewport=new k;const C=new U;C.viewport=new k;const L=[b,C],P=new gn;let D=null,w=null;function y(e){const t=x.indexOf(e.inputSource);if(-1===t)return;const n=T[t];void 0!==n&&(n.update(e.inputSource,e.frame,d||s),n.dispatchEvent({type:e.type,data:e.inputSource}))}function N(){a.removeEventListener("select",y),a.removeEventListener("selectstart",y),a.removeEventListener("selectend",y),a.removeEventListener("squeeze",y),a.removeEventListener("squeezestart",y),a.removeEventListener("squeezeend",y),a.removeEventListener("end",N),a.removeEventListener("inputsourceschange",O);for(let e=0;e=0&&(x[r]=null,T[r].disconnect(n))}for(let t=0;t=x.length){x.push(n),r=e;break}if(null===x[e]){x[e]=n,r=e;break}}if(-1===r)break}const i=T[r];i&&i.connect(n)}}this.cameraAutoUpdate=!0,this.enabled=!1,this.isPresenting=!1,this.getController=function(e){let t=T[e];return void 0===t&&(t=new vn,T[e]=t),t.getTargetRaySpace()},this.getControllerGrip=function(e){let t=T[e];return void 0===t&&(t=new vn,T[e]=t),t.getGripSpace()},this.getHand=function(e){let t=T[e];return void 0===t&&(t=new vn,T[e]=t),t.getHandSpace()},this.setFramebufferScaleFactor=function(e){o=e,!0===r.isPresenting&&console.warn("THREE.WebXRManager: Cannot change framebuffer scale while presenting.")},this.setReferenceSpaceType=function(e){l=e,!0===r.isPresenting&&console.warn("THREE.WebXRManager: Cannot change reference space type while presenting.")},this.getReferenceSpace=function(){return d||s},this.setReferenceSpace=function(e){d=e},this.getBaseLayer=function(){return null!==p?p:m},this.getBinding=function(){return f},this.getFrame=function(){return h},this.getSession=function(){return a},this.setSession=async function(t){if(a=t,null!==a){v=e.getRenderTarget(),a.addEventListener("select",y),a.addEventListener("selectstart",y),a.addEventListener("selectend",y),a.addEventListener("squeeze",y),a.addEventListener("squeezestart",y),a.addEventListener("squeezeend",y),a.addEventListener("end",N),a.addEventListener("inputsourceschange",O),!0!==g.xrCompatible&&await n.makeXRCompatible(),A=e.getPixelRatio(),e.getSize(R);if("undefined"!=typeof XRWebGLBinding&&"createProjectionLayer"in XRWebGLBinding.prototype){let t=null,r=null,i=null;g.depth&&(i=g.stencil?n.DEPTH24_STENCIL8:n.DEPTH_COMPONENT24,t=g.stencil?vt:St,r=g.stencil?Mt:Tt);const s={colorFormat:n.RGBA8,depthFormat:i,scaleFactor:o};f=new XRWebGLBinding(a,n),p=f.createProjectionLayer(s),a.updateRenderState({layers:[p]}),e.setPixelRatio(1),e.setSize(p.textureWidth,p.textureHeight,!1),E=new I(p.textureWidth,p.textureHeight,{format:M,type:S,depthTexture:new j(p.textureWidth,p.textureHeight,r,void 0,void 0,void 0,void 0,void 0,void 0,t),stencilBuffer:g.stencil,colorSpace:e.outputColorSpace,samples:g.antialias?4:0,resolveDepthBuffer:!1===p.ignoreDepthValues,resolveStencilBuffer:!1===p.ignoreDepthValues})}else{const t={antialias:g.antialias,alpha:!0,depth:g.depth,stencil:g.stencil,framebufferScaleFactor:o};m=new XRWebGLLayer(a,n,t),a.updateRenderState({baseLayer:m}),e.setPixelRatio(1),e.setSize(m.framebufferWidth,m.framebufferHeight,!1),E=new I(m.framebufferWidth,m.framebufferHeight,{format:M,type:S,colorSpace:e.outputColorSpace,stencilBuffer:g.stencil,resolveDepthBuffer:!1===m.ignoreDepthValues,resolveStencilBuffer:!1===m.ignoreDepthValues})}E.isXRRenderTarget=!0,this.setFoveation(c),d=null,s=await a.requestReferenceSpace(l),V.setContext(a),V.start(),r.isPresenting=!0,r.dispatchEvent({type:"sessionstart"})}},this.getEnvironmentBlendMode=function(){if(null!==a)return a.environmentBlendMode},this.getDepthTexture=function(){return _.getDepthTexture()};const F=new i,B=new i;function H(e,t){null===t?e.matrixWorld.copy(e.matrix):e.matrixWorld.multiplyMatrices(t.matrixWorld,e.matrix),e.matrixWorldInverse.copy(e.matrixWorld).invert()}this.updateCamera=function(e){if(null===a)return;let t=e.near,n=e.far;null!==_.texture&&(_.depthNear>0&&(t=_.depthNear),_.depthFar>0&&(n=_.depthFar)),P.near=C.near=b.near=t,P.far=C.far=b.far=n,D===P.near&&w===P.far||(a.updateRenderState({depthNear:P.near,depthFar:P.far}),D=P.near,w=P.far),b.layers.mask=2|e.layers.mask,C.layers.mask=4|e.layers.mask,P.layers.mask=b.layers.mask|C.layers.mask;const r=e.parent,i=P.cameras;H(P,r);for(let e=0;e0&&(e.alphaTest.value=r.alphaTest);const i=t.get(r),a=i.envMap,o=i.envMapRotation;a&&(e.envMap.value=a,aa.copy(o),aa.x*=-1,aa.y*=-1,aa.z*=-1,a.isCubeTexture&&!1===a.isRenderTargetTexture&&(aa.y*=-1,aa.z*=-1),e.envMapRotation.value.setFromMatrix4(oa.makeRotationFromEuler(aa)),e.flipEnvMap.value=a.isCubeTexture&&!1===a.isRenderTargetTexture?-1:1,e.reflectivity.value=r.reflectivity,e.ior.value=r.ior,e.refractionRatio.value=r.refractionRatio),r.lightMap&&(e.lightMap.value=r.lightMap,e.lightMapIntensity.value=r.lightMapIntensity,n(r.lightMap,e.lightMapTransform)),r.aoMap&&(e.aoMap.value=r.aoMap,e.aoMapIntensity.value=r.aoMapIntensity,n(r.aoMap,e.aoMapTransform))}return{refreshFogUniforms:function(t,n){n.color.getRGB(t.fogColor.value,g(e)),n.isFog?(t.fogNear.value=n.near,t.fogFar.value=n.far):n.isFogExp2&&(t.fogDensity.value=n.density)},refreshMaterialUniforms:function(e,i,a,o,s){i.isMeshBasicMaterial||i.isMeshLambertMaterial?r(e,i):i.isMeshToonMaterial?(r(e,i),function(e,t){t.gradientMap&&(e.gradientMap.value=t.gradientMap)}(e,i)):i.isMeshPhongMaterial?(r(e,i),function(e,t){e.specular.value.copy(t.specular),e.shininess.value=Math.max(t.shininess,1e-4)}(e,i)):i.isMeshStandardMaterial?(r(e,i),function(e,t){e.metalness.value=t.metalness,t.metalnessMap&&(e.metalnessMap.value=t.metalnessMap,n(t.metalnessMap,e.metalnessMapTransform));e.roughness.value=t.roughness,t.roughnessMap&&(e.roughnessMap.value=t.roughnessMap,n(t.roughnessMap,e.roughnessMapTransform));t.envMap&&(e.envMapIntensity.value=t.envMapIntensity)}(e,i),i.isMeshPhysicalMaterial&&function(e,t,r){e.ior.value=t.ior,t.sheen>0&&(e.sheenColor.value.copy(t.sheenColor).multiplyScalar(t.sheen),e.sheenRoughness.value=t.sheenRoughness,t.sheenColorMap&&(e.sheenColorMap.value=t.sheenColorMap,n(t.sheenColorMap,e.sheenColorMapTransform)),t.sheenRoughnessMap&&(e.sheenRoughnessMap.value=t.sheenRoughnessMap,n(t.sheenRoughnessMap,e.sheenRoughnessMapTransform)));t.clearcoat>0&&(e.clearcoat.value=t.clearcoat,e.clearcoatRoughness.value=t.clearcoatRoughness,t.clearcoatMap&&(e.clearcoatMap.value=t.clearcoatMap,n(t.clearcoatMap,e.clearcoatMapTransform)),t.clearcoatRoughnessMap&&(e.clearcoatRoughnessMap.value=t.clearcoatRoughnessMap,n(t.clearcoatRoughnessMap,e.clearcoatRoughnessMapTransform)),t.clearcoatNormalMap&&(e.clearcoatNormalMap.value=t.clearcoatNormalMap,n(t.clearcoatNormalMap,e.clearcoatNormalMapTransform),e.clearcoatNormalScale.value.copy(t.clearcoatNormalScale),t.side===c&&e.clearcoatNormalScale.value.negate()));t.dispersion>0&&(e.dispersion.value=t.dispersion);t.iridescence>0&&(e.iridescence.value=t.iridescence,e.iridescenceIOR.value=t.iridescenceIOR,e.iridescenceThicknessMinimum.value=t.iridescenceThicknessRange[0],e.iridescenceThicknessMaximum.value=t.iridescenceThicknessRange[1],t.iridescenceMap&&(e.iridescenceMap.value=t.iridescenceMap,n(t.iridescenceMap,e.iridescenceMapTransform)),t.iridescenceThicknessMap&&(e.iridescenceThicknessMap.value=t.iridescenceThicknessMap,n(t.iridescenceThicknessMap,e.iridescenceThicknessMapTransform)));t.transmission>0&&(e.transmission.value=t.transmission,e.transmissionSamplerMap.value=r.texture,e.transmissionSamplerSize.value.set(r.width,r.height),t.transmissionMap&&(e.transmissionMap.value=t.transmissionMap,n(t.transmissionMap,e.transmissionMapTransform)),e.thickness.value=t.thickness,t.thicknessMap&&(e.thicknessMap.value=t.thicknessMap,n(t.thicknessMap,e.thicknessMapTransform)),e.attenuationDistance.value=t.attenuationDistance,e.attenuationColor.value.copy(t.attenuationColor));t.anisotropy>0&&(e.anisotropyVector.value.set(t.anisotropy*Math.cos(t.anisotropyRotation),t.anisotropy*Math.sin(t.anisotropyRotation)),t.anisotropyMap&&(e.anisotropyMap.value=t.anisotropyMap,n(t.anisotropyMap,e.anisotropyMapTransform)));e.specularIntensity.value=t.specularIntensity,e.specularColor.value.copy(t.specularColor),t.specularColorMap&&(e.specularColorMap.value=t.specularColorMap,n(t.specularColorMap,e.specularColorMapTransform));t.specularIntensityMap&&(e.specularIntensityMap.value=t.specularIntensityMap,n(t.specularIntensityMap,e.specularIntensityMapTransform))}(e,i,s)):i.isMeshMatcapMaterial?(r(e,i),function(e,t){t.matcap&&(e.matcap.value=t.matcap)}(e,i)):i.isMeshDepthMaterial?r(e,i):i.isMeshDistanceMaterial?(r(e,i),function(e,n){const r=t.get(n).light;e.referencePosition.value.setFromMatrixPosition(r.matrixWorld),e.nearDistance.value=r.shadow.camera.near,e.farDistance.value=r.shadow.camera.far}(e,i)):i.isMeshNormalMaterial?r(e,i):i.isLineBasicMaterial?(function(e,t){e.diffuse.value.copy(t.color),e.opacity.value=t.opacity,t.map&&(e.map.value=t.map,n(t.map,e.mapTransform))}(e,i),i.isLineDashedMaterial&&function(e,t){e.dashSize.value=t.dashSize,e.totalSize.value=t.dashSize+t.gapSize,e.scale.value=t.scale}(e,i)):i.isPointsMaterial?function(e,t,r,i){e.diffuse.value.copy(t.color),e.opacity.value=t.opacity,e.size.value=t.size*r,e.scale.value=.5*i,t.map&&(e.map.value=t.map,n(t.map,e.uvTransform));t.alphaMap&&(e.alphaMap.value=t.alphaMap,n(t.alphaMap,e.alphaMapTransform));t.alphaTest>0&&(e.alphaTest.value=t.alphaTest)}(e,i,a,o):i.isSpriteMaterial?function(e,t){e.diffuse.value.copy(t.color),e.opacity.value=t.opacity,e.rotation.value=t.rotation,t.map&&(e.map.value=t.map,n(t.map,e.mapTransform));t.alphaMap&&(e.alphaMap.value=t.alphaMap,n(t.alphaMap,e.alphaMapTransform));t.alphaTest>0&&(e.alphaTest.value=t.alphaTest)}(e,i):i.isShadowMaterial?(e.color.value.copy(i.color),e.opacity.value=i.opacity):i.isShaderMaterial&&(i.uniformsNeedUpdate=!1)}}}function la(e,t,n,r){let i={},a={},o=[];const s=e.getParameter(e.MAX_UNIFORM_BUFFER_BINDINGS);function l(e,t,n,r){const i=e.value,a=t+"_"+n;if(void 0===r[a])return r[a]="number"==typeof i||"boolean"==typeof i?i:i.clone(),!0;{const e=r[a];if("number"==typeof i||"boolean"==typeof i){if(e!==i)return r[a]=i,!0}else if(!1===e.equals(i))return e.copy(i),!0}return!1}function c(e){const t={boundary:0,storage:0};return"number"==typeof e||"boolean"==typeof e?(t.boundary=4,t.storage=4):e.isVector2?(t.boundary=8,t.storage=8):e.isVector3||e.isColor?(t.boundary=16,t.storage=12):e.isVector4?(t.boundary=16,t.storage=16):e.isMatrix3?(t.boundary=48,t.storage=48):e.isMatrix4?(t.boundary=64,t.storage=64):e.isTexture?console.warn("THREE.WebGLRenderer: Texture samplers can not be part of an uniforms group."):console.warn("THREE.WebGLRenderer: Unsupported uniform value type.",e),t}function d(t){const n=t.target;n.removeEventListener("dispose",d);const r=o.indexOf(n.__bindingPointIndex);o.splice(r,1),e.deleteBuffer(i[n.id]),delete i[n.id],delete a[n.id]}return{bind:function(e,t){const n=t.program;r.uniformBlockBinding(e,n)},update:function(n,u){let f=i[n.id];void 0===f&&(!function(e){const t=e.uniforms;let n=0;const r=16;for(let e=0,i=t.length;e0&&(n+=r-i);e.__size=n,e.__cache={}}(n),f=function(t){const n=function(){for(let e=0;e0),u=!!n.morphAttributes.position,f=!!n.morphAttributes.normal,p=!!n.morphAttributes.color;let m=D;r.toneMapped&&(null!==w&&!0!==w.isXRRenderTarget||(m=C.toneMapping));const h=n.morphAttributes.position||n.morphAttributes.normal||n.morphAttributes.color,_=void 0!==h?h.length:0,g=pe.get(r),v=R.state.lights;if(!0===J&&(!0===ee||e!==N)){const t=e===N&&r.id===y;Ae.setState(r,e,t)}let E=!1;r.version===g.__version?g.needsLights&&g.lightsStateVersion!==v.state.version||g.outputColorSpace!==s||i.isBatchedMesh&&!1===g.batching?E=!0:i.isBatchedMesh||!0!==g.batching?i.isBatchedMesh&&!0===g.batchingColor&&null===i.colorTexture||i.isBatchedMesh&&!1===g.batchingColor&&null!==i.colorTexture||i.isInstancedMesh&&!1===g.instancing?E=!0:i.isInstancedMesh||!0!==g.instancing?i.isSkinnedMesh&&!1===g.skinning?E=!0:i.isSkinnedMesh||!0!==g.skinning?i.isInstancedMesh&&!0===g.instancingColor&&null===i.instanceColor||i.isInstancedMesh&&!1===g.instancingColor&&null!==i.instanceColor||i.isInstancedMesh&&!0===g.instancingMorph&&null===i.morphTexture||i.isInstancedMesh&&!1===g.instancingMorph&&null!==i.morphTexture||g.envMap!==l||!0===r.fog&&g.fog!==a?E=!0:void 0===g.numClippingPlanes||g.numClippingPlanes===Ae.numPlanes&&g.numIntersection===Ae.numIntersection?(g.vertexAlphas!==c||g.vertexTangents!==d||g.morphTargets!==u||g.morphNormals!==f||g.morphColors!==p||g.toneMapping!==m||g.morphTargetsCount!==_)&&(E=!0):E=!0:E=!0:E=!0:E=!0:(E=!0,g.__version=r.version);let S=g.currentProgram;!0===E&&(S=Qe(r,t,i));let T=!1,M=!1,x=!1;const A=S.getUniforms(),b=g.uniforms;de.useProgram(S.program)&&(T=!0,M=!0,x=!0);r.id!==y&&(y=r.id,M=!0);if(T||N!==e){de.buffers.depth.getReversed()?(te.copy(e.projectionMatrix),xn(te),Rn(te),A.setValue(Ie,"projectionMatrix",te)):A.setValue(Ie,"projectionMatrix",e.projectionMatrix),A.setValue(Ie,"viewMatrix",e.matrixWorldInverse);const t=A.map.cameraPosition;void 0!==t&&t.setValue(Ie,re.setFromMatrixPosition(e.matrixWorld)),ce.logarithmicDepthBuffer&&A.setValue(Ie,"logDepthBufFC",2/(Math.log(e.far+1)/Math.LN2)),(r.isMeshPhongMaterial||r.isMeshToonMaterial||r.isMeshLambertMaterial||r.isMeshBasicMaterial||r.isMeshStandardMaterial||r.isShaderMaterial)&&A.setValue(Ie,"isOrthographic",!0===e.isOrthographicCamera),N!==e&&(N=e,M=!0,x=!0)}if(i.isSkinnedMesh){A.setOptional(Ie,i,"bindMatrix"),A.setOptional(Ie,i,"bindMatrixInverse");const e=i.skeleton;e&&(null===e.boneTexture&&e.computeBoneTexture(),A.setValue(Ie,"boneTexture",e.boneTexture,me))}i.isBatchedMesh&&(A.setOptional(Ie,i,"batchingTexture"),A.setValue(Ie,"batchingTexture",i._matricesTexture,me),A.setOptional(Ie,i,"batchingIdTexture"),A.setValue(Ie,"batchingIdTexture",i._indirectTexture,me),A.setOptional(Ie,i,"batchingColorTexture"),null!==i._colorsTexture&&A.setValue(Ie,"batchingColorTexture",i._colorsTexture,me));const L=n.morphAttributes;void 0===L.position&&void 0===L.normal&&void 0===L.color||Le.update(i,n,S);(M||g.receiveShadow!==i.receiveShadow)&&(g.receiveShadow=i.receiveShadow,A.setValue(Ie,"receiveShadow",i.receiveShadow));r.isMeshGouraudMaterial&&null!==r.envMap&&(b.envMap.value=l,b.flipEnvMap.value=l.isCubeTexture&&!1===l.isRenderTargetTexture?-1:1);r.isMeshStandardMaterial&&null===r.envMap&&null!==t.environment&&(b.envMapIntensity.value=t.environmentIntensity);M&&(A.setValue(Ie,"toneMappingExposure",C.toneMappingExposure),g.needsLights&&(U=x,(P=b).ambientLightColor.needsUpdate=U,P.lightProbe.needsUpdate=U,P.directionalLights.needsUpdate=U,P.directionalLightShadows.needsUpdate=U,P.pointLights.needsUpdate=U,P.pointLightShadows.needsUpdate=U,P.spotLights.needsUpdate=U,P.spotLightShadows.needsUpdate=U,P.rectAreaLights.needsUpdate=U,P.hemisphereLights.needsUpdate=U),a&&!0===r.fog&&Me.refreshFogUniforms(b,a),Me.refreshMaterialUniforms(b,r,Y,X,R.state.transmissionRenderTarget[e.id]),_i.upload(Ie,Je(g),b,me));var P,U;r.isShaderMaterial&&!0===r.uniformsNeedUpdate&&(_i.upload(Ie,Je(g),b,me),r.uniformsNeedUpdate=!1);r.isSpriteMaterial&&A.setValue(Ie,"center",i.center);if(A.setValue(Ie,"modelViewMatrix",i.modelViewMatrix),A.setValue(Ie,"normalMatrix",i.normalMatrix),A.setValue(Ie,"modelMatrix",i.matrixWorld),r.isShaderMaterial||r.isRawShaderMaterial){const e=r.uniformsGroups;for(let t=0,n=e.length;t{function n(){r.forEach(function(e){pe.get(e).currentProgram.isReady()&&r.delete(e)}),0!==r.size?setTimeout(n,10):t(e)}null!==le.get("KHR_parallel_shader_compile")?n():setTimeout(n,10)})};let ke=null;function We(){Ye.stop()}function Xe(){Ye.start()}const Ye=new Cn;function Ke(e,t,n,r){if(!1===e.visible)return;if(e.layers.test(t.layers))if(e.isGroup)n=e.renderOrder;else if(e.isLOD)!0===e.autoUpdate&&e.update(t);else if(e.isLight)R.pushLight(e),e.castShadow&&R.pushShadow(e);else if(e.isSprite){if(!e.frustumCulled||Q.intersectsSprite(e)){r&&ie.setFromMatrixPosition(e.matrixWorld).applyMatrix4(ne);const t=Se.update(e),i=e.material;i.visible&&x.push(e,t,i,n,ie.z,null)}}else if((e.isMesh||e.isLine||e.isPoints)&&(!e.frustumCulled||Q.intersectsObject(e))){const t=Se.update(e),i=e.material;if(r&&(void 0!==e.boundingSphere?(null===e.boundingSphere&&e.computeBoundingSphere(),ie.copy(e.boundingSphere.center)):(null===t.boundingSphere&&t.computeBoundingSphere(),ie.copy(t.boundingSphere.center)),ie.applyMatrix4(e.matrixWorld).applyMatrix4(ne)),Array.isArray(i)){const r=t.groups;for(let a=0,o=r.length;a0&&Ze(i,t,n),a.length>0&&Ze(a,t,n),o.length>0&&Ze(o,t,n),de.buffers.depth.setTest(!0),de.buffers.depth.setMask(!0),de.buffers.color.setMask(!0),de.setPolygonOffset(!1)}function qe(e,t,n,r){if(null!==(!0===n.isScene?n.overrideMaterial:null))return;void 0===R.state.transmissionRenderTarget[r.id]&&(R.state.transmissionRenderTarget[r.id]=new I(1,1,{generateMipmaps:!0,type:le.has("EXT_color_buffer_half_float")||le.has("EXT_color_buffer_float")?E:S,minFilter:ot,samples:4,stencilBuffer:o,resolveDepthBuffer:!1,resolveStencilBuffer:!1,colorSpace:p.workingColorSpace}));const i=R.state.transmissionRenderTarget[r.id],a=r.viewport||O;i.setSize(a.z*C.transmissionResolutionScale,a.w*C.transmissionResolutionScale);const s=C.getRenderTarget(),l=C.getActiveCubeFace(),d=C.getActiveMipmapLevel();C.setRenderTarget(i),C.getClearColor(V),z=C.getClearAlpha(),z<1&&C.setClearColor(16777215,.5),C.clear(),oe&&Ce.render(n);const u=C.toneMapping;C.toneMapping=D;const f=r.viewport;if(void 0!==r.viewport&&(r.viewport=void 0),R.setupLightsView(r),!0===J&&Ae.setGlobalState(C.clippingPlanes,r),Ze(e,n,r),me.updateMultisampleRenderTarget(i),me.updateRenderTargetMipmap(i),!1===le.has("WEBGL_multisampled_render_to_texture")){let e=!1;for(let i=0,a=t.length;i0)for(let t=0,a=n.length;t0&&qe(r,i,e,t),oe&&Ce.render(e),je(x,e,t);null!==w&&0===U&&(me.updateMultisampleRenderTarget(w),me.updateRenderTargetMipmap(w)),!0===e.isScene&&e.onAfterRender(C,e,t),we.resetDefaultState(),y=-1,N=null,b.pop(),b.length>0?(R=b[b.length-1],!0===J&&Ae.setGlobalState(C.clippingPlanes,R.state.camera)):R=null,A.pop(),x=A.length>0?A[A.length-1]:null},this.getActiveCubeFace=function(){return P},this.getActiveMipmapLevel=function(){return U},this.getRenderTarget=function(){return w},this.setRenderTargetTextures=function(e,t,n){const r=pe.get(e);r.__autoAllocateDepthBuffer=!1===e.resolveDepthBuffer,!1===r.__autoAllocateDepthBuffer&&(r.__useRenderToTexture=!1),pe.get(e.texture).__webglTexture=t,pe.get(e.depthTexture).__webglTexture=r.__autoAllocateDepthBuffer?void 0:n,r.__hasExternalTextures=!0},this.setRenderTargetFramebuffer=function(e,t){const n=pe.get(e);n.__webglFramebuffer=t,n.__useDefaultFramebuffer=void 0===t};const tt=Ie.createFramebuffer();this.setRenderTarget=function(e,t=0,n=0){w=e,P=t,U=n;let r=!0,i=null,a=!1,o=!1;if(e){const s=pe.get(e);if(void 0!==s.__useDefaultFramebuffer)de.bindFramebuffer(Ie.FRAMEBUFFER,null),r=!1;else if(void 0===s.__webglFramebuffer)me.setupRenderTarget(e);else if(s.__hasExternalTextures)me.rebindTextures(e,pe.get(e.texture).__webglTexture,pe.get(e.depthTexture).__webglTexture);else if(e.depthBuffer){const t=e.depthTexture;if(s.__boundDepthTexture!==t){if(null!==t&&pe.has(t)&&(e.width!==t.image.width||e.height!==t.image.height))throw new Error("WebGLRenderTarget: Attached DepthTexture is initialized to the incorrect size.");me.setupDepthRenderbuffer(e)}}const l=e.texture;(l.isData3DTexture||l.isDataArrayTexture||l.isCompressedArrayTexture)&&(o=!0);const c=pe.get(e).__webglFramebuffer;e.isWebGLCubeRenderTarget?(i=Array.isArray(c[t])?c[t][n]:c[t],a=!0):i=e.samples>0&&!1===me.useMultisampledRTT(e)?pe.get(e).__webglMultisampledFramebuffer:Array.isArray(c)?c[n]:c,O.copy(e.viewport),B.copy(e.scissor),G=e.scissorTest}else O.copy(q).multiplyScalar(Y).floor(),B.copy(Z).multiplyScalar(Y).floor(),G=$;0!==n&&(i=tt);if(de.bindFramebuffer(Ie.FRAMEBUFFER,i)&&r&&de.drawBuffers(e,i),de.viewport(O),de.scissor(B),de.setScissorTest(G),a){const r=pe.get(e.texture);Ie.framebufferTexture2D(Ie.FRAMEBUFFER,Ie.COLOR_ATTACHMENT0,Ie.TEXTURE_CUBE_MAP_POSITIVE_X+t,r.__webglTexture,n)}else if(o){const r=pe.get(e.texture),i=t;Ie.framebufferTextureLayer(Ie.FRAMEBUFFER,Ie.COLOR_ATTACHMENT0,r.__webglTexture,n,i)}else if(null!==e&&0!==n){const t=pe.get(e.texture);Ie.framebufferTexture2D(Ie.FRAMEBUFFER,Ie.COLOR_ATTACHMENT0,Ie.TEXTURE_2D,t.__webglTexture,n)}y=-1},this.readRenderTargetPixels=function(e,t,n,r,i,a,o,s=0){if(!e||!e.isWebGLRenderTarget)return void console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.");let l=pe.get(e).__webglFramebuffer;if(e.isWebGLCubeRenderTarget&&void 0!==o&&(l=l[o]),l){de.bindFramebuffer(Ie.FRAMEBUFFER,l);try{const o=e.textures[s],l=o.format,c=o.type;if(!ce.textureFormatReadable(l))return void console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in RGBA or implementation defined format.");if(!ce.textureTypeReadable(c))return void console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in UnsignedByteType or implementation defined type.");t>=0&&t<=e.width-r&&n>=0&&n<=e.height-i&&(e.textures.length>1&&Ie.readBuffer(Ie.COLOR_ATTACHMENT0+s),Ie.readPixels(t,n,r,i,De.convert(l),De.convert(c),a))}finally{const e=null!==w?pe.get(w).__webglFramebuffer:null;de.bindFramebuffer(Ie.FRAMEBUFFER,e)}}},this.readRenderTargetPixelsAsync=async function(e,t,n,r,i,a,o,s=0){if(!e||!e.isWebGLRenderTarget)throw new Error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.");let l=pe.get(e).__webglFramebuffer;if(e.isWebGLCubeRenderTarget&&void 0!==o&&(l=l[o]),l){if(t>=0&&t<=e.width-r&&n>=0&&n<=e.height-i){de.bindFramebuffer(Ie.FRAMEBUFFER,l);const o=e.textures[s],c=o.format,d=o.type;if(!ce.textureFormatReadable(c))throw new Error("THREE.WebGLRenderer.readRenderTargetPixelsAsync: renderTarget is not in RGBA or implementation defined format.");if(!ce.textureTypeReadable(d))throw new Error("THREE.WebGLRenderer.readRenderTargetPixelsAsync: renderTarget is not in UnsignedByteType or implementation defined type.");const u=Ie.createBuffer();Ie.bindBuffer(Ie.PIXEL_PACK_BUFFER,u),Ie.bufferData(Ie.PIXEL_PACK_BUFFER,a.byteLength,Ie.STREAM_READ),e.textures.length>1&&Ie.readBuffer(Ie.COLOR_ATTACHMENT0+s),Ie.readPixels(t,n,r,i,De.convert(c),De.convert(d),0);const f=null!==w?pe.get(w).__webglFramebuffer:null;de.bindFramebuffer(Ie.FRAMEBUFFER,f);const p=Ie.fenceSync(Ie.SYNC_GPU_COMMANDS_COMPLETE,0);return Ie.flush(),await An(Ie,p,4),Ie.bindBuffer(Ie.PIXEL_PACK_BUFFER,u),Ie.getBufferSubData(Ie.PIXEL_PACK_BUFFER,0,a),Ie.deleteBuffer(u),Ie.deleteSync(p),a}throw new Error("THREE.WebGLRenderer.readRenderTargetPixelsAsync: requested read bounds are out of range.")}},this.copyFramebufferToTexture=function(e,t=null,n=0){const r=Math.pow(2,-n),i=Math.floor(e.image.width*r),a=Math.floor(e.image.height*r),o=null!==t?t.x:0,s=null!==t?t.y:0;me.setTexture2D(e,0),Ie.copyTexSubImage2D(Ie.TEXTURE_2D,n,0,0,o,s,i,a),de.unbindTexture()};const nt=Ie.createFramebuffer(),rt=Ie.createFramebuffer();this.copyTextureToTexture=function(e,t,n=null,r=null,i=0,a=null){let o,s,l,c,d,u,f,p,m;null===a&&(0!==i?(H("WebGLRenderer: copyTextureToTexture function signature has changed to support src and dst mipmap levels."),a=i,i=0):a=0);const h=e.isCompressedTexture?e.mipmaps[a]:e.image;if(null!==n)o=n.max.x-n.min.x,s=n.max.y-n.min.y,l=n.isBox3?n.max.z-n.min.z:1,c=n.min.x,d=n.min.y,u=n.isBox3?n.min.z:0;else{const t=Math.pow(2,-i);o=Math.floor(h.width*t),s=Math.floor(h.height*t),l=e.isDataArrayTexture?h.depth:e.isData3DTexture?Math.floor(h.depth*t):1,c=0,d=0,u=0}null!==r?(f=r.x,p=r.y,m=r.z):(f=0,p=0,m=0);const _=De.convert(t.format),g=De.convert(t.type);let v;t.isData3DTexture?(me.setTexture3D(t,0),v=Ie.TEXTURE_3D):t.isDataArrayTexture||t.isCompressedArrayTexture?(me.setTexture2DArray(t,0),v=Ie.TEXTURE_2D_ARRAY):(me.setTexture2D(t,0),v=Ie.TEXTURE_2D),Ie.pixelStorei(Ie.UNPACK_FLIP_Y_WEBGL,t.flipY),Ie.pixelStorei(Ie.UNPACK_PREMULTIPLY_ALPHA_WEBGL,t.premultiplyAlpha),Ie.pixelStorei(Ie.UNPACK_ALIGNMENT,t.unpackAlignment);const E=Ie.getParameter(Ie.UNPACK_ROW_LENGTH),S=Ie.getParameter(Ie.UNPACK_IMAGE_HEIGHT),T=Ie.getParameter(Ie.UNPACK_SKIP_PIXELS),M=Ie.getParameter(Ie.UNPACK_SKIP_ROWS),x=Ie.getParameter(Ie.UNPACK_SKIP_IMAGES);Ie.pixelStorei(Ie.UNPACK_ROW_LENGTH,h.width),Ie.pixelStorei(Ie.UNPACK_IMAGE_HEIGHT,h.height),Ie.pixelStorei(Ie.UNPACK_SKIP_PIXELS,c),Ie.pixelStorei(Ie.UNPACK_SKIP_ROWS,d),Ie.pixelStorei(Ie.UNPACK_SKIP_IMAGES,u);const R=e.isDataArrayTexture||e.isData3DTexture,A=t.isDataArrayTexture||t.isData3DTexture;if(e.isDepthTexture){const n=pe.get(e),r=pe.get(t),h=pe.get(n.__renderTarget),_=pe.get(r.__renderTarget);de.bindFramebuffer(Ie.READ_FRAMEBUFFER,h.__webglFramebuffer),de.bindFramebuffer(Ie.DRAW_FRAMEBUFFER,_.__webglFramebuffer);for(let n=0;n} + */ + this.renderObjects = new WeakMap(); + + /** + * Whether the material uses node objects or not. + * + * @type {boolean} + */ + this.hasNode = this.containsNode( builder ); + + /** + * Whether the node builder's 3D object is animated or not. + * + * @type {boolean} + */ + this.hasAnimation = builder.object.isSkinnedMesh === true; + + /** + * A list of all possible material uniforms + * + * @type {Array} + */ + this.refreshUniforms = refreshUniforms; + + /** + * Holds the current render ID from the node frame. + * + * @type {number} + * @default 0 + */ + this.renderId = 0; + + } + + /** + * Returns `true` if the given render object is verified for the first time of this observer. + * + * @param {RenderObject} renderObject - The render object. + * @return {boolean} Whether the given render object is verified for the first time of this observer. + */ + firstInitialization( renderObject ) { + + const hasInitialized = this.renderObjects.has( renderObject ); + + if ( hasInitialized === false ) { + + this.getRenderObjectData( renderObject ); + + return true; + + } + + return false; + + } + + /** + * Returns `true` if the current rendering produces motion vectors. + * + * @param {Renderer} renderer - The renderer. + * @return {boolean} Whether the current rendering produces motion vectors or not. + */ + needsVelocity( renderer ) { + + const mrt = renderer.getMRT(); + + return ( mrt !== null && mrt.has( 'velocity' ) ); + + } + + /** + * Returns monitoring data for the given render object. + * + * @param {RenderObject} renderObject - The render object. + * @return {Object} The monitoring data. + */ + getRenderObjectData( renderObject ) { + + let data = this.renderObjects.get( renderObject ); + + if ( data === undefined ) { + + const { geometry, material, object } = renderObject; + + data = { + material: this.getMaterialData( material ), + geometry: { + id: geometry.id, + attributes: this.getAttributesData( geometry.attributes ), + indexVersion: geometry.index ? geometry.index.version : null, + drawRange: { start: geometry.drawRange.start, count: geometry.drawRange.count } + }, + worldMatrix: object.matrixWorld.clone() + }; + + if ( object.center ) { + + data.center = object.center.clone(); + + } + + if ( object.morphTargetInfluences ) { + + data.morphTargetInfluences = object.morphTargetInfluences.slice(); + + } + + if ( renderObject.bundle !== null ) { + + data.version = renderObject.bundle.version; + + } + + if ( data.material.transmission > 0 ) { + + const { width, height } = renderObject.context; + + data.bufferWidth = width; + data.bufferHeight = height; + + } + + this.renderObjects.set( renderObject, data ); + + } + + return data; + + } + + /** + * Returns an attribute data structure holding the attributes versions for + * monitoring. + * + * @param {Object} attributes - The geometry attributes. + * @return {Object} An object for monitoring the versions of attributes. + */ + getAttributesData( attributes ) { + + const attributesData = {}; + + for ( const name in attributes ) { + + const attribute = attributes[ name ]; + + attributesData[ name ] = { + version: attribute.version + }; + + } + + return attributesData; + + } + + /** + * Returns `true` if the node builder's material uses + * node properties. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {boolean} Whether the node builder's material uses node properties or not. + */ + containsNode( builder ) { + + const material = builder.material; + + for ( const property in material ) { + + if ( material[ property ] && material[ property ].isNode ) + return true; + + } + + if ( builder.renderer.overrideNodes.modelViewMatrix !== null || builder.renderer.overrideNodes.modelNormalViewMatrix !== null ) + return true; + + return false; + + } + + /** + * Returns a material data structure holding the material property values for + * monitoring. + * + * @param {Material} material - The material. + * @return {Object} An object for monitoring material properties. + */ + getMaterialData( material ) { + + const data = {}; + + for ( const property of this.refreshUniforms ) { + + const value = material[ property ]; + + if ( value === null || value === undefined ) continue; + + if ( typeof value === 'object' && value.clone !== undefined ) { + + if ( value.isTexture === true ) { + + data[ property ] = { id: value.id, version: value.version }; + + } else { + + data[ property ] = value.clone(); + + } + + } else { + + data[ property ] = value; + + } + + } + + return data; + + } + + /** + * Returns `true` if the given render object has not changed its state. + * + * @param {RenderObject} renderObject - The render object. + * @return {boolean} Whether the given render object has changed its state or not. + */ + equals( renderObject ) { + + const { object, material, geometry } = renderObject; + + const renderObjectData = this.getRenderObjectData( renderObject ); + + // world matrix + + if ( renderObjectData.worldMatrix.equals( object.matrixWorld ) !== true ) { + + renderObjectData.worldMatrix.copy( object.matrixWorld ); + + return false; + + } + + // material + + const materialData = renderObjectData.material; + + for ( const property in materialData ) { + + const value = materialData[ property ]; + const mtlValue = material[ property ]; + + if ( value.equals !== undefined ) { + + if ( value.equals( mtlValue ) === false ) { + + value.copy( mtlValue ); + + return false; + + } + + } else if ( mtlValue.isTexture === true ) { + + if ( value.id !== mtlValue.id || value.version !== mtlValue.version ) { + + value.id = mtlValue.id; + value.version = mtlValue.version; + + return false; + + } + + } else if ( value !== mtlValue ) { + + materialData[ property ] = mtlValue; + + return false; + + } + + } + + if ( materialData.transmission > 0 ) { + + const { width, height } = renderObject.context; + + if ( renderObjectData.bufferWidth !== width || renderObjectData.bufferHeight !== height ) { + + renderObjectData.bufferWidth = width; + renderObjectData.bufferHeight = height; + + return false; + + } + + } + + // geometry + + const storedGeometryData = renderObjectData.geometry; + const attributes = geometry.attributes; + const storedAttributes = storedGeometryData.attributes; + + const storedAttributeNames = Object.keys( storedAttributes ); + const currentAttributeNames = Object.keys( attributes ); + + if ( storedGeometryData.id !== geometry.id ) { + + storedGeometryData.id = geometry.id; + return false; + + } + + if ( storedAttributeNames.length !== currentAttributeNames.length ) { + + renderObjectData.geometry.attributes = this.getAttributesData( attributes ); + return false; + + } + + // compare each attribute + + for ( const name of storedAttributeNames ) { + + const storedAttributeData = storedAttributes[ name ]; + const attribute = attributes[ name ]; + + if ( attribute === undefined ) { + + // attribute was removed + delete storedAttributes[ name ]; + return false; + + } + + if ( storedAttributeData.version !== attribute.version ) { + + storedAttributeData.version = attribute.version; + return false; + + } + + } + + // check index + + const index = geometry.index; + const storedIndexVersion = storedGeometryData.indexVersion; + const currentIndexVersion = index ? index.version : null; + + if ( storedIndexVersion !== currentIndexVersion ) { + + storedGeometryData.indexVersion = currentIndexVersion; + return false; + + } + + // check drawRange + + if ( storedGeometryData.drawRange.start !== geometry.drawRange.start || storedGeometryData.drawRange.count !== geometry.drawRange.count ) { + + storedGeometryData.drawRange.start = geometry.drawRange.start; + storedGeometryData.drawRange.count = geometry.drawRange.count; + return false; + + } + + // morph targets + + if ( renderObjectData.morphTargetInfluences ) { + + let morphChanged = false; + + for ( let i = 0; i < renderObjectData.morphTargetInfluences.length; i ++ ) { + + if ( renderObjectData.morphTargetInfluences[ i ] !== object.morphTargetInfluences[ i ] ) { + + morphChanged = true; + + } + + } + + if ( morphChanged ) return true; + + } + + // center + + if ( renderObjectData.center ) { + + if ( renderObjectData.center.equals( object.center ) === false ) { + + renderObjectData.center.copy( object.center ); + + return true; + + } + + } + + // bundle + + if ( renderObject.bundle !== null ) { + + renderObjectData.version = renderObject.bundle.version; + + } + + return true; + + } + + /** + * Checks if the given render object requires a refresh. + * + * @param {RenderObject} renderObject - The render object. + * @param {NodeFrame} nodeFrame - The current node frame. + * @return {boolean} Whether the given render object requires a refresh or not. + */ + needsRefresh( renderObject, nodeFrame ) { + + if ( this.hasNode || this.hasAnimation || this.firstInitialization( renderObject ) || this.needsVelocity( nodeFrame.renderer ) ) + return true; + + const { renderId } = nodeFrame; + + if ( this.renderId !== renderId ) { + + this.renderId = renderId; + + return true; + + } + + const isStatic = renderObject.object.static === true; + const isBundle = renderObject.bundle !== null && renderObject.bundle.static === true && this.getRenderObjectData( renderObject ).version === renderObject.bundle.version; + + if ( isStatic || isBundle ) + return false; + + const notEqual = this.equals( renderObject ) !== true; + + return notEqual; + + } + +} + +// cyrb53 (c) 2018 bryc (github.com/bryc). License: Public domain. Attribution appreciated. +// A fast and simple 64-bit (or 53-bit) string hash function with decent collision resistance. +// Largely inspired by MurmurHash2/3, but with a focus on speed/simplicity. +// See https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript/52171480#52171480 +// https://github.com/bryc/code/blob/master/jshash/experimental/cyrb53.js +function cyrb53( value, seed = 0 ) { + + let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed; + + if ( value instanceof Array ) { + + for ( let i = 0, val; i < value.length; i ++ ) { + + val = value[ i ]; + h1 = Math.imul( h1 ^ val, 2654435761 ); + h2 = Math.imul( h2 ^ val, 1597334677 ); + + } + + } else { + + for ( let i = 0, ch; i < value.length; i ++ ) { + + ch = value.charCodeAt( i ); + h1 = Math.imul( h1 ^ ch, 2654435761 ); + h2 = Math.imul( h2 ^ ch, 1597334677 ); + + } + + } + + h1 = Math.imul( h1 ^ ( h1 >>> 16 ), 2246822507 ); + h1 ^= Math.imul( h2 ^ ( h2 >>> 13 ), 3266489909 ); + h2 = Math.imul( h2 ^ ( h2 >>> 16 ), 2246822507 ); + h2 ^= Math.imul( h1 ^ ( h1 >>> 13 ), 3266489909 ); + + return 4294967296 * ( 2097151 & h2 ) + ( h1 >>> 0 ); + +} + +/** + * Computes a hash for the given string. + * + * @method + * @param {string} str - The string to be hashed. + * @return {number} The hash. + */ +const hashString = ( str ) => cyrb53( str ); + +/** + * Computes a hash for the given array. + * + * @method + * @param {Array} array - The array to be hashed. + * @return {number} The hash. + */ +const hashArray = ( array ) => cyrb53( array ); + +/** + * Computes a hash for the given list of parameters. + * + * @method + * @param {...number} params - A list of parameters. + * @return {number} The hash. + */ +const hash$1 = ( ...params ) => cyrb53( params ); + +/** + * Computes a cache key for the given node. + * + * @method + * @param {Object|Node} object - The object to be hashed. + * @param {boolean} [force=false] - Whether to force a cache key computation or not. + * @return {number} The hash. + */ +function getCacheKey$1( object, force = false ) { + + const values = []; + + if ( object.isNode === true ) { + + values.push( object.id ); + object = object.getSelf(); + + } + + for ( const { property, childNode } of getNodeChildren( object ) ) { + + values.push( cyrb53( property.slice( 0, -4 ) ), childNode.getCacheKey( force ) ); + + } + + return cyrb53( values ); + +} + +/** + * This generator function can be used to iterate over the node children + * of the given object. + * + * @generator + * @param {Object} node - The object to be hashed. + * @param {boolean} [toJSON=false] - Whether to return JSON or not. + * @yields {Object} A result node holding the property, index (if available) and the child node. + */ +function* getNodeChildren( node, toJSON = false ) { + + for ( const property in node ) { + + // Ignore private properties. + if ( property.startsWith( '_' ) === true ) continue; + + const object = node[ property ]; + + if ( Array.isArray( object ) === true ) { + + for ( let i = 0; i < object.length; i ++ ) { + + const child = object[ i ]; + + if ( child && ( child.isNode === true || toJSON && typeof child.toJSON === 'function' ) ) { + + yield { property, index: i, childNode: child }; + + } + + } + + } else if ( object && object.isNode === true ) { + + yield { property, childNode: object }; + + } else if ( typeof object === 'object' ) { + + for ( const subProperty in object ) { + + const child = object[ subProperty ]; + + if ( child && ( child.isNode === true || toJSON && typeof child.toJSON === 'function' ) ) { + + yield { property, index: subProperty, childNode: child }; + + } + + } + + } + + } + +} + +const typeFromLength = /*@__PURE__*/ new Map( [ + [ 1, 'float' ], + [ 2, 'vec2' ], + [ 3, 'vec3' ], + [ 4, 'vec4' ], + [ 9, 'mat3' ], + [ 16, 'mat4' ] +] ); + +const dataFromObject = /*@__PURE__*/ new WeakMap(); + +/** + * Returns the data type for the given the length. + * + * @method + * @param {number} length - The length. + * @return {string} The data type. + */ +function getTypeFromLength( length ) { + + return typeFromLength.get( length ); + +} + +/** + * Returns the typed array for the given data type. + * + * @method + * @param {string} type - The data type. + * @return {TypedArray} The typed array. + */ +function getTypedArrayFromType( type ) { + + // Handle component type for vectors and matrices + if ( /[iu]?vec\d/.test( type ) ) { + + // Handle int vectors + if ( type.startsWith( 'ivec' ) ) return Int32Array; + // Handle uint vectors + if ( type.startsWith( 'uvec' ) ) return Uint32Array; + // Default to float vectors + return Float32Array; + + } + + // Handle matrices (always float) + if ( /mat\d/.test( type ) ) return Float32Array; + + // Basic types + if ( /float/.test( type ) ) return Float32Array; + if ( /uint/.test( type ) ) return Uint32Array; + if ( /int/.test( type ) ) return Int32Array; + + throw new Error( `THREE.NodeUtils: Unsupported type: ${type}` ); + +} + +/** + * Returns the length for the given data type. + * + * @method + * @param {string} type - The data type. + * @return {number} The length. + */ +function getLengthFromType( type ) { + + if ( /float|int|uint/.test( type ) ) return 1; + if ( /vec2/.test( type ) ) return 2; + if ( /vec3/.test( type ) ) return 3; + if ( /vec4/.test( type ) ) return 4; + if ( /mat2/.test( type ) ) return 4; + if ( /mat3/.test( type ) ) return 9; + if ( /mat4/.test( type ) ) return 16; + + console.error( 'THREE.TSL: Unsupported type:', type ); + +} + +/** + * Returns the gpu memory length for the given data type. + * + * @method + * @param {string} type - The data type. + * @return {number} The length. + */ +function getMemoryLengthFromType( type ) { + + if ( /float|int|uint/.test( type ) ) return 1; + if ( /vec2/.test( type ) ) return 2; + if ( /vec3/.test( type ) ) return 3; + if ( /vec4/.test( type ) ) return 4; + if ( /mat2/.test( type ) ) return 4; + if ( /mat3/.test( type ) ) return 12; + if ( /mat4/.test( type ) ) return 16; + + console.error( 'THREE.TSL: Unsupported type:', type ); + +} + +/** + * Returns the byte boundary for the given data type. + * + * @method + * @param {string} type - The data type. + * @return {number} The byte boundary. + */ +function getByteBoundaryFromType( type ) { + + if ( /float|int|uint/.test( type ) ) return 4; + if ( /vec2/.test( type ) ) return 8; + if ( /vec3/.test( type ) ) return 16; + if ( /vec4/.test( type ) ) return 16; + if ( /mat2/.test( type ) ) return 8; + if ( /mat3/.test( type ) ) return 48; + if ( /mat4/.test( type ) ) return 64; + + console.error( 'THREE.TSL: Unsupported type:', type ); + +} + +/** + * Returns the data type for the given value. + * + * @method + * @param {any} value - The value. + * @return {?string} The data type. + */ +function getValueType( value ) { + + if ( value === undefined || value === null ) return null; + + const typeOf = typeof value; + + if ( value.isNode === true ) { + + return 'node'; + + } else if ( typeOf === 'number' ) { + + return 'float'; + + } else if ( typeOf === 'boolean' ) { + + return 'bool'; + + } else if ( typeOf === 'string' ) { + + return 'string'; + + } else if ( typeOf === 'function' ) { + + return 'shader'; + + } else if ( value.isVector2 === true ) { + + return 'vec2'; + + } else if ( value.isVector3 === true ) { + + return 'vec3'; + + } else if ( value.isVector4 === true ) { + + return 'vec4'; + + } else if ( value.isMatrix2 === true ) { + + return 'mat2'; + + } else if ( value.isMatrix3 === true ) { + + return 'mat3'; + + } else if ( value.isMatrix4 === true ) { + + return 'mat4'; + + } else if ( value.isColor === true ) { + + return 'color'; + + } else if ( value instanceof ArrayBuffer ) { + + return 'ArrayBuffer'; + + } + + return null; + +} + +/** + * Returns the value/object for the given data type and parameters. + * + * @method + * @param {string} type - The given type. + * @param {...any} params - A parameter list. + * @return {any} The value/object. + */ +function getValueFromType( type, ...params ) { + + const last4 = type ? type.slice( -4 ) : undefined; + + if ( params.length === 1 ) { // ensure same behaviour as in NodeBuilder.format() + + if ( last4 === 'vec2' ) params = [ params[ 0 ], params[ 0 ] ]; + else if ( last4 === 'vec3' ) params = [ params[ 0 ], params[ 0 ], params[ 0 ] ]; + else if ( last4 === 'vec4' ) params = [ params[ 0 ], params[ 0 ], params[ 0 ], params[ 0 ] ]; + + } + + if ( type === 'color' ) { + + return new Color( ...params ); + + } else if ( last4 === 'vec2' ) { + + return new Vector2( ...params ); + + } else if ( last4 === 'vec3' ) { + + return new Vector3( ...params ); + + } else if ( last4 === 'vec4' ) { + + return new Vector4( ...params ); + + } else if ( last4 === 'mat2' ) { + + return new Matrix2( ...params ); + + } else if ( last4 === 'mat3' ) { + + return new Matrix3( ...params ); + + } else if ( last4 === 'mat4' ) { + + return new Matrix4( ...params ); + + } else if ( type === 'bool' ) { + + return params[ 0 ] || false; + + } else if ( ( type === 'float' ) || ( type === 'int' ) || ( type === 'uint' ) ) { + + return params[ 0 ] || 0; + + } else if ( type === 'string' ) { + + return params[ 0 ] || ''; + + } else if ( type === 'ArrayBuffer' ) { + + return base64ToArrayBuffer( params[ 0 ] ); + + } + + return null; + +} + +/** + * Gets the object data that can be shared between different rendering steps. + * + * @param {Object} object - The object to get the data for. + * @return {Object} The object data. + */ +function getDataFromObject( object ) { + + let data = dataFromObject.get( object ); + + if ( data === undefined ) { + + data = {}; + dataFromObject.set( object, data ); + + } + + return data; + +} + +/** + * Converts the given array buffer to a Base64 string. + * + * @method + * @param {ArrayBuffer} arrayBuffer - The array buffer. + * @return {string} The Base64 string. + */ +function arrayBufferToBase64( arrayBuffer ) { + + let chars = ''; + + const array = new Uint8Array( arrayBuffer ); + + for ( let i = 0; i < array.length; i ++ ) { + + chars += String.fromCharCode( array[ i ] ); + + } + + return btoa( chars ); + +} + +/** + * Converts the given Base64 string to an array buffer. + * + * @method + * @param {string} base64 - The Base64 string. + * @return {ArrayBuffer} The array buffer. + */ +function base64ToArrayBuffer( base64 ) { + + return Uint8Array.from( atob( base64 ), c => c.charCodeAt( 0 ) ).buffer; + +} + +var NodeUtils = /*#__PURE__*/Object.freeze({ + __proto__: null, + arrayBufferToBase64: arrayBufferToBase64, + base64ToArrayBuffer: base64ToArrayBuffer, + getByteBoundaryFromType: getByteBoundaryFromType, + getCacheKey: getCacheKey$1, + getDataFromObject: getDataFromObject, + getLengthFromType: getLengthFromType, + getMemoryLengthFromType: getMemoryLengthFromType, + getNodeChildren: getNodeChildren, + getTypeFromLength: getTypeFromLength, + getTypedArrayFromType: getTypedArrayFromType, + getValueFromType: getValueFromType, + getValueType: getValueType, + hash: hash$1, + hashArray: hashArray, + hashString: hashString +}); + +/** + * Possible shader stages. + * + * @property {string} VERTEX The vertex shader stage. + * @property {string} FRAGMENT The fragment shader stage. + */ +const NodeShaderStage = { + VERTEX: 'vertex', + FRAGMENT: 'fragment' +}; + +/** + * Update types of a node. + * + * @property {string} NONE The update method is not executed. + * @property {string} FRAME The update method is executed per frame. + * @property {string} RENDER The update method is executed per render. A frame might be produced by multiple render calls so this value allows more detailed updates than FRAME. + * @property {string} OBJECT The update method is executed per {@link Object3D} that uses the node for rendering. + */ +const NodeUpdateType = { + NONE: 'none', + FRAME: 'frame', + RENDER: 'render', + OBJECT: 'object' +}; + +/** + * Data types of a node. + * + * @property {string} BOOLEAN Boolean type. + * @property {string} INTEGER Integer type. + * @property {string} FLOAT Float type. + * @property {string} VECTOR2 Two-dimensional vector type. + * @property {string} VECTOR3 Three-dimensional vector type. + * @property {string} VECTOR4 Four-dimensional vector type. + * @property {string} MATRIX2 2x2 matrix type. + * @property {string} MATRIX3 3x3 matrix type. + * @property {string} MATRIX4 4x4 matrix type. + */ +const NodeType = { + BOOLEAN: 'bool', + INTEGER: 'int', + FLOAT: 'float', + VECTOR2: 'vec2', + VECTOR3: 'vec3', + VECTOR4: 'vec4', + MATRIX2: 'mat2', + MATRIX3: 'mat3', + MATRIX4: 'mat4' +}; + +/** + * Access types of a node. These are relevant for compute and storage usage. + * + * @property {string} READ_ONLY Read-only access + * @property {string} WRITE_ONLY Write-only access. + * @property {string} READ_WRITE Read and write access. + */ +const NodeAccess = { + READ_ONLY: 'readOnly', + WRITE_ONLY: 'writeOnly', + READ_WRITE: 'readWrite', +}; + +const defaultShaderStages = [ 'fragment', 'vertex' ]; +const defaultBuildStages = [ 'setup', 'analyze', 'generate' ]; +const shaderStages = [ ...defaultShaderStages, 'compute' ]; +const vectorComponents = [ 'x', 'y', 'z', 'w' ]; + +const _parentBuildStage = { + analyze: 'setup', + generate: 'analyze' +}; + +let _nodeId = 0; + +/** + * Base class for all nodes. + * + * @augments EventDispatcher + */ +class Node extends EventDispatcher { + + static get type() { + + return 'Node'; + + } + + /** + * Constructs a new node. + * + * @param {?string} nodeType - The node type. + */ + constructor( nodeType = null ) { + + super(); + + /** + * The node type. This represents the result type of the node (e.g. `float` or `vec3`). + * + * @type {?string} + * @default null + */ + this.nodeType = nodeType; + + /** + * The update type of the node's {@link Node#update} method. Possible values are listed in {@link NodeUpdateType}. + * + * @type {string} + * @default 'none' + */ + this.updateType = NodeUpdateType.NONE; + + /** + * The update type of the node's {@link Node#updateBefore} method. Possible values are listed in {@link NodeUpdateType}. + * + * @type {string} + * @default 'none' + */ + this.updateBeforeType = NodeUpdateType.NONE; + + /** + * The update type of the node's {@link Node#updateAfter} method. Possible values are listed in {@link NodeUpdateType}. + * + * @type {string} + * @default 'none' + */ + this.updateAfterType = NodeUpdateType.NONE; + + /** + * The UUID of the node. + * + * @type {string} + * @readonly + */ + this.uuid = MathUtils.generateUUID(); + + /** + * The version of the node. The version automatically is increased when {@link Node#needsUpdate} is set to `true`. + * + * @type {number} + * @readonly + * @default 0 + */ + this.version = 0; + + /** + * Whether this node is global or not. This property is relevant for the internal + * node caching system. All nodes which should be declared just once should + * set this flag to `true` (a typical example is {@link AttributeNode}). + * + * @type {boolean} + * @default false + */ + this.global = false; + + /** + * Create a list of parents for this node during the build process. + * + * @type {boolean} + * @default false + */ + this.parents = false; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isNode = true; + + // private + + /** + * The cache key of this node. + * + * @private + * @type {?number} + * @default null + */ + this._cacheKey = null; + + /** + * The cache key 's version. + * + * @private + * @type {number} + * @default 0 + */ + this._cacheKeyVersion = 0; + + Object.defineProperty( this, 'id', { value: _nodeId ++ } ); + + } + + /** + * Set this property to `true` when the node should be regenerated. + * + * @type {boolean} + * @default false + * @param {boolean} value + */ + set needsUpdate( value ) { + + if ( value === true ) { + + this.version ++; + + } + + } + + /** + * The type of the class. The value is usually the constructor name. + * + * @type {string} + * @readonly + */ + get type() { + + return this.constructor.type; + + } + + /** + * Convenient method for defining {@link Node#update}. + * + * @param {Function} callback - The update method. + * @param {string} updateType - The update type. + * @return {Node} A reference to this node. + */ + onUpdate( callback, updateType ) { + + this.updateType = updateType; + this.update = callback.bind( this.getSelf() ); + + return this; + + } + + /** + * Convenient method for defining {@link Node#update}. Similar to {@link Node#onUpdate}, but + * this method automatically sets the update type to `FRAME`. + * + * @param {Function} callback - The update method. + * @return {Node} A reference to this node. + */ + onFrameUpdate( callback ) { + + return this.onUpdate( callback, NodeUpdateType.FRAME ); + + } + + /** + * Convenient method for defining {@link Node#update}. Similar to {@link Node#onUpdate}, but + * this method automatically sets the update type to `RENDER`. + * + * @param {Function} callback - The update method. + * @return {Node} A reference to this node. + */ + onRenderUpdate( callback ) { + + return this.onUpdate( callback, NodeUpdateType.RENDER ); + + } + + /** + * Convenient method for defining {@link Node#update}. Similar to {@link Node#onUpdate}, but + * this method automatically sets the update type to `OBJECT`. + * + * @param {Function} callback - The update method. + * @return {Node} A reference to this node. + */ + onObjectUpdate( callback ) { + + return this.onUpdate( callback, NodeUpdateType.OBJECT ); + + } + + /** + * Convenient method for defining {@link Node#updateReference}. + * + * @param {Function} callback - The update method. + * @return {Node} A reference to this node. + */ + onReference( callback ) { + + this.updateReference = callback.bind( this.getSelf() ); + + return this; + + } + + /** + * The `this` reference might point to a Proxy so this method can be used + * to get the reference to the actual node instance. + * + * @return {Node} A reference to the node. + */ + getSelf() { + + // Returns non-node object. + + return this.self || this; + + } + + /** + * Nodes might refer to other objects like materials. This method allows to dynamically update the reference + * to such objects based on a given state (e.g. the current node frame or builder). + * + * @param {any} state - This method can be invocated in different contexts so `state` can refer to any object type. + * @return {any} The updated reference. + */ + updateReference( /*state*/ ) { + + return this; + + } + + /** + * By default this method returns the value of the {@link Node#global} flag. This method + * can be overwritten in derived classes if an analytical way is required to determine the + * global cache referring to the current shader-stage. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {boolean} Whether this node is global or not. + */ + isGlobal( /*builder*/ ) { + + return this.global; + + } + + /** + * Generator function that can be used to iterate over the child nodes. + * + * @generator + * @yields {Node} A child node. + */ + * getChildren() { + + for ( const { childNode } of getNodeChildren( this ) ) { + + yield childNode; + + } + + } + + /** + * Calling this method dispatches the `dispose` event. This event can be used + * to register event listeners for clean up tasks. + */ + dispose() { + + this.dispatchEvent( { type: 'dispose' } ); + + } + + /** + * Callback for {@link Node#traverse}. + * + * @callback traverseCallback + * @param {Node} node - The current node. + */ + + /** + * Can be used to traverse through the node's hierarchy. + * + * @param {traverseCallback} callback - A callback that is executed per node. + */ + traverse( callback ) { + + callback( this ); + + for ( const childNode of this.getChildren() ) { + + childNode.traverse( callback ); + + } + + } + + /** + * Returns the cache key for this node. + * + * @param {boolean} [force=false] - When set to `true`, a recomputation of the cache key is forced. + * @return {number} The cache key of the node. + */ + getCacheKey( force = false ) { + + force = force || this.version !== this._cacheKeyVersion; + + if ( force === true || this._cacheKey === null ) { + + this._cacheKey = hash$1( getCacheKey$1( this, force ), this.customCacheKey() ); + this._cacheKeyVersion = this.version; + + } + + return this._cacheKey; + + } + + /** + * Generate a custom cache key for this node. + * + * @return {number} The cache key of the node. + */ + customCacheKey() { + + return 0; + + } + + /** + * Returns the references to this node which is by default `this`. + * + * @return {Node} A reference to this node. + */ + getScope() { + + return this; + + } + + /** + * Returns the hash of the node which is used to identify the node. By default it's + * the {@link Node#uuid} however derived node classes might have to overwrite this method + * depending on their implementation. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The hash. + */ + getHash( /*builder*/ ) { + + return this.uuid; + + } + + /** + * Returns the update type of {@link Node#update}. + * + * @return {NodeUpdateType} The update type. + */ + getUpdateType() { + + return this.updateType; + + } + + /** + * Returns the update type of {@link Node#updateBefore}. + * + * @return {NodeUpdateType} The update type. + */ + getUpdateBeforeType() { + + return this.updateBeforeType; + + } + + /** + * Returns the update type of {@link Node#updateAfter}. + * + * @return {NodeUpdateType} The update type. + */ + getUpdateAfterType() { + + return this.updateAfterType; + + } + + /** + * Certain types are composed of multiple elements. For example a `vec3` + * is composed of three `float` values. This method returns the type of + * these elements. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The type of the node. + */ + getElementType( builder ) { + + const type = this.getNodeType( builder ); + const elementType = builder.getElementType( type ); + + return elementType; + + } + + /** + * Returns the node member type for the given name. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {string} name - The name of the member. + * @return {string} The type of the node. + */ + getMemberType( /*builder, name*/ ) { + + return 'void'; + + } + + /** + * Returns the node's type. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The type of the node. + */ + getNodeType( builder ) { + + const nodeProperties = builder.getNodeProperties( this ); + + if ( nodeProperties.outputNode ) { + + return nodeProperties.outputNode.getNodeType( builder ); + + } + + return this.nodeType; + + } + + /** + * This method is used during the build process of a node and ensures + * equal nodes are not built multiple times but just once. For example if + * `attribute( 'uv' )` is used multiple times by the user, the build + * process makes sure to process just the first node. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {Node} The shared node if possible. Otherwise `this` is returned. + */ + getShared( builder ) { + + const hash = this.getHash( builder ); + const nodeFromHash = builder.getNodeFromHash( hash ); + + return nodeFromHash || this; + + } + + /** + * Represents the setup stage which is the first step of the build process, see {@link Node#build} method. + * This method is often overwritten in derived modules to prepare the node which is used as the output/result. + * The output node must be returned in the `return` statement. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {?Node} The output node. + */ + setup( builder ) { + + const nodeProperties = builder.getNodeProperties( this ); + + let index = 0; + + for ( const childNode of this.getChildren() ) { + + nodeProperties[ 'node' + index ++ ] = childNode; + + } + + // return a outputNode if exists or null + + return nodeProperties.outputNode || null; + + } + + /** + * Represents the analyze stage which is the second step of the build process, see {@link Node#build} method. + * This stage analyzes the node hierarchy and ensures descendent nodes are built. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {?Node} output - The target output node. + */ + analyze( builder, output = null ) { + + const usageCount = builder.increaseUsage( this ); + + if ( this.parents === true ) { + + const nodeData = builder.getDataFromNode( this, 'any' ); + nodeData.stages = nodeData.stages || {}; + nodeData.stages[ builder.shaderStage ] = nodeData.stages[ builder.shaderStage ] || []; + nodeData.stages[ builder.shaderStage ].push( output ); + + } + + if ( usageCount === 1 ) { + + // node flow children + + const nodeProperties = builder.getNodeProperties( this ); + + for ( const childNode of Object.values( nodeProperties ) ) { + + if ( childNode && childNode.isNode === true ) { + + childNode.build( builder, this ); + + } + + } + + } + + } + + /** + * Represents the generate stage which is the third step of the build process, see {@link Node#build} method. + * This state builds the output node and returns the resulting shader string. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {?string} output - Can be used to define the output type. + * @return {?string} The generated shader string. + */ + generate( builder, output ) { + + const { outputNode } = builder.getNodeProperties( this ); + + if ( outputNode && outputNode.isNode === true ) { + + return outputNode.build( builder, output ); + + } + + } + + /** + * The method can be implemented to update the node's internal state before it is used to render an object. + * The {@link Node#updateBeforeType} property defines how often the update is executed. + * + * @abstract + * @param {NodeFrame} frame - A reference to the current node frame. + * @return {?boolean} An optional bool that indicates whether the implementation actually performed an update or not (e.g. due to caching). + */ + updateBefore( /*frame*/ ) { + + console.warn( 'Abstract function.' ); + + } + + /** + * The method can be implemented to update the node's internal state after it was used to render an object. + * The {@link Node#updateAfterType} property defines how often the update is executed. + * + * @abstract + * @param {NodeFrame} frame - A reference to the current node frame. + * @return {?boolean} An optional bool that indicates whether the implementation actually performed an update or not (e.g. due to caching). + */ + updateAfter( /*frame*/ ) { + + console.warn( 'Abstract function.' ); + + } + + /** + * The method can be implemented to update the node's internal state when it is used to render an object. + * The {@link Node#updateType} property defines how often the update is executed. + * + * @abstract + * @param {NodeFrame} frame - A reference to the current node frame. + * @return {?boolean} An optional bool that indicates whether the implementation actually performed an update or not (e.g. due to caching). + */ + update( /*frame*/ ) { + + console.warn( 'Abstract function.' ); + + } + + /** + * This method performs the build of a node. The behavior and return value depend on the current build stage: + * - **setup**: Prepares the node and its children for the build process. This process can also create new nodes. Returns the node itself or a variant. + * - **analyze**: Analyzes the node hierarchy for optimizations in the code generation stage. Returns `null`. + * - **generate**: Generates the shader code for the node. Returns the generated shader string. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {string|Node|null} [output=null] - Can be used to define the output type. + * @return {Node|string|null} The result of the build process, depending on the build stage. + */ + build( builder, output = null ) { + + const refNode = this.getShared( builder ); + + if ( this !== refNode ) { + + return refNode.build( builder, output ); + + } + + // + + const nodeData = builder.getDataFromNode( this ); + nodeData.buildStages = nodeData.buildStages || {}; + nodeData.buildStages[ builder.buildStage ] = true; + + const parentBuildStage = _parentBuildStage[ builder.buildStage ]; + + if ( parentBuildStage && nodeData.buildStages[ parentBuildStage ] !== true ) { + + // force parent build stage (setup or analyze) + + const previousBuildStage = builder.getBuildStage(); + + builder.setBuildStage( parentBuildStage ); + + this.build( builder ); + + builder.setBuildStage( previousBuildStage ); + + } + + // + + builder.addNode( this ); + builder.addChain( this ); + + /* Build stages expected results: + - "setup" -> Node + - "analyze" -> null + - "generate" -> String + */ + let result = null; + + const buildStage = builder.getBuildStage(); + + if ( buildStage === 'setup' ) { + + this.updateReference( builder ); + + const properties = builder.getNodeProperties( this ); + + if ( properties.initialized !== true ) { + + //const stackNodesBeforeSetup = builder.stack.nodes.length; + + properties.initialized = true; + properties.outputNode = this.setup( builder ) || properties.outputNode || null; + + /*if ( isNodeOutput && builder.stack.nodes.length !== stackNodesBeforeSetup ) { + + // !! no outputNode !! + //outputNode = builder.stack; + + }*/ + + for ( const childNode of Object.values( properties ) ) { + + if ( childNode && childNode.isNode === true ) { + + if ( childNode.parents === true ) { + + const childProperties = builder.getNodeProperties( childNode ); + childProperties.parents = childProperties.parents || []; + childProperties.parents.push( this ); + + } + + childNode.build( builder ); + + } + + } + + } + + result = properties.outputNode; + + } else if ( buildStage === 'analyze' ) { + + this.analyze( builder, output ); + + } else if ( buildStage === 'generate' ) { + + const isGenerateOnce = this.generate.length === 1; + + if ( isGenerateOnce ) { + + const type = this.getNodeType( builder ); + const nodeData = builder.getDataFromNode( this ); + + result = nodeData.snippet; + + if ( result === undefined ) { + + if ( nodeData.generated === undefined ) { + + nodeData.generated = true; + + result = this.generate( builder ) || ''; + + nodeData.snippet = result; + + } else { + + console.warn( 'THREE.Node: Recursion detected.', this ); + + result = '/* Recursion detected. */'; + + } + + } else if ( nodeData.flowCodes !== undefined && builder.context.nodeBlock !== undefined ) { + + builder.addFlowCodeHierarchy( this, builder.context.nodeBlock ); + + } + + result = builder.format( result, type, output ); + + } else { + + result = this.generate( builder, output ) || ''; + + } + + } + + builder.removeChain( this ); + builder.addSequentialNode( this ); + + return result; + + } + + /** + * Returns the child nodes as a JSON object. + * + * @return {Array} An iterable list of serialized child objects as JSON. + */ + getSerializeChildren() { + + return getNodeChildren( this ); + + } + + /** + * Serializes the node to JSON. + * + * @param {Object} json - The output JSON object. + */ + serialize( json ) { + + const nodeChildren = this.getSerializeChildren(); + + const inputNodes = {}; + + for ( const { property, index, childNode } of nodeChildren ) { + + if ( index !== undefined ) { + + if ( inputNodes[ property ] === undefined ) { + + inputNodes[ property ] = Number.isInteger( index ) ? [] : {}; + + } + + inputNodes[ property ][ index ] = childNode.toJSON( json.meta ).uuid; + + } else { + + inputNodes[ property ] = childNode.toJSON( json.meta ).uuid; + + } + + } + + if ( Object.keys( inputNodes ).length > 0 ) { + + json.inputNodes = inputNodes; + + } + + } + + /** + * Deserializes the node from the given JSON. + * + * @param {Object} json - The JSON object. + */ + deserialize( json ) { + + if ( json.inputNodes !== undefined ) { + + const nodes = json.meta.nodes; + + for ( const property in json.inputNodes ) { + + if ( Array.isArray( json.inputNodes[ property ] ) ) { + + const inputArray = []; + + for ( const uuid of json.inputNodes[ property ] ) { + + inputArray.push( nodes[ uuid ] ); + + } + + this[ property ] = inputArray; + + } else if ( typeof json.inputNodes[ property ] === 'object' ) { + + const inputObject = {}; + + for ( const subProperty in json.inputNodes[ property ] ) { + + const uuid = json.inputNodes[ property ][ subProperty ]; + + inputObject[ subProperty ] = nodes[ uuid ]; + + } + + this[ property ] = inputObject; + + } else { + + const uuid = json.inputNodes[ property ]; + + this[ property ] = nodes[ uuid ]; + + } + + } + + } + + } + + /** + * Serializes the node into the three.js JSON Object/Scene format. + * + * @param {?Object} meta - An optional JSON object that already holds serialized data from other scene objects. + * @return {Object} The serialized node. + */ + toJSON( meta ) { + + const { uuid, type } = this; + const isRoot = ( meta === undefined || typeof meta === 'string' ); + + if ( isRoot ) { + + meta = { + textures: {}, + images: {}, + nodes: {} + }; + + } + + // serialize + + let data = meta.nodes[ uuid ]; + + if ( data === undefined ) { + + data = { + uuid, + type, + meta, + metadata: { + version: 4.7, + type: 'Node', + generator: 'Node.toJSON' + } + }; + + if ( isRoot !== true ) meta.nodes[ data.uuid ] = data; + + this.serialize( data ); + + delete data.meta; + + } + + // TODO: Copied from Object3D.toJSON + + function extractFromCache( cache ) { + + const values = []; + + for ( const key in cache ) { + + const data = cache[ key ]; + delete data.metadata; + values.push( data ); + + } + + return values; + + } + + if ( isRoot ) { + + const textures = extractFromCache( meta.textures ); + const images = extractFromCache( meta.images ); + const nodes = extractFromCache( meta.nodes ); + + if ( textures.length > 0 ) data.textures = textures; + if ( images.length > 0 ) data.images = images; + if ( nodes.length > 0 ) data.nodes = nodes; + + } + + return data; + + } + +} + +/** + * Base class for representing element access on an array-like + * node data structures. + * + * @augments Node + */ +class ArrayElementNode extends Node { // @TODO: If extending from TempNode it breaks webgpu_compute + + static get type() { + + return 'ArrayElementNode'; + + } + + /** + * Constructs an array element node. + * + * @param {Node} node - The array-like node. + * @param {Node} indexNode - The index node that defines the element access. + */ + constructor( node, indexNode ) { + + super(); + + /** + * The array-like node. + * + * @type {Node} + */ + this.node = node; + + /** + * The index node that defines the element access. + * + * @type {Node} + */ + this.indexNode = indexNode; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isArrayElementNode = true; + + } + + /** + * This method is overwritten since the node type is inferred from the array-like node. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType( builder ) { + + return this.node.getElementType( builder ); + + } + + generate( builder ) { + + const indexType = this.indexNode.getNodeType( builder ); + + const nodeSnippet = this.node.build( builder ); + const indexSnippet = this.indexNode.build( builder, ! builder.isVector( indexType ) && builder.isInteger( indexType ) ? indexType : 'uint' ); + + return `${ nodeSnippet }[ ${ indexSnippet } ]`; + + } + +} + +/** + * This module is part of the TSL core and usually not used in app level code. + * It represents a convert operation during the shader generation process + * meaning it converts the data type of a node to a target data type. + * + * @augments Node + */ +class ConvertNode extends Node { + + static get type() { + + return 'ConvertNode'; + + } + + /** + * Constructs a new convert node. + * + * @param {Node} node - The node which type should be converted. + * @param {string} convertTo - The target node type. Multiple types can be defined by separating them with a `|` sign. + */ + constructor( node, convertTo ) { + + super(); + + /** + * The node which type should be converted. + * + * @type {Node} + */ + this.node = node; + + /** + * The target node type. Multiple types can be defined by separating them with a `|` sign. + * + * @type {string} + */ + this.convertTo = convertTo; + + } + + /** + * This method is overwritten since the implementation tries to infer the best + * matching type from the {@link ConvertNode#convertTo} property. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType( builder ) { + + const requestType = this.node.getNodeType( builder ); + + let convertTo = null; + + for ( const overloadingType of this.convertTo.split( '|' ) ) { + + if ( convertTo === null || builder.getTypeLength( requestType ) === builder.getTypeLength( overloadingType ) ) { + + convertTo = overloadingType; + + } + + } + + return convertTo; + + } + + serialize( data ) { + + super.serialize( data ); + + data.convertTo = this.convertTo; + + } + + deserialize( data ) { + + super.deserialize( data ); + + this.convertTo = data.convertTo; + + } + + generate( builder, output ) { + + const node = this.node; + const type = this.getNodeType( builder ); + + const snippet = node.build( builder, type ); + + return builder.format( snippet, type, output ); + + } + +} + +/** + * This module uses cache management to create temporary variables + * if the node is used more than once to prevent duplicate calculations. + * + * The class acts as a base class for many other nodes types. + * + * @augments Node + */ +class TempNode extends Node { + + static get type() { + + return 'TempNode'; + + } + + /** + * Constructs a temp node. + * + * @param {?string} nodeType - The node type. + */ + constructor( nodeType = null ) { + + super( nodeType ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isTempNode = true; + + } + + /** + * Whether this node is used more than once in context of other nodes. + * + * @param {NodeBuilder} builder - The node builder. + * @return {boolean} A flag that indicates if there is more than one dependency to other nodes. + */ + hasDependencies( builder ) { + + return builder.getDataFromNode( this ).usageCount > 1; + + } + + build( builder, output ) { + + const buildStage = builder.getBuildStage(); + + if ( buildStage === 'generate' ) { + + const type = builder.getVectorType( this.getNodeType( builder, output ) ); + const nodeData = builder.getDataFromNode( this ); + + if ( nodeData.propertyName !== undefined ) { + + return builder.format( nodeData.propertyName, type, output ); + + } else if ( type !== 'void' && output !== 'void' && this.hasDependencies( builder ) ) { + + const snippet = super.build( builder, type ); + + const nodeVar = builder.getVarFromNode( this, null, type ); + const propertyName = builder.getPropertyName( nodeVar ); + + builder.addLineFlowCode( `${ propertyName } = ${ snippet }`, this ); + + nodeData.snippet = snippet; + nodeData.propertyName = propertyName; + + return builder.format( nodeData.propertyName, type, output ); + + } + + } + + return super.build( builder, output ); + + } + +} + +/** + * This module is part of the TSL core and usually not used in app level code. + * It represents a join operation during the shader generation process. + * For example in can compose/join two single floats into a `vec2` type. + * + * @augments TempNode + */ +class JoinNode extends TempNode { + + static get type() { + + return 'JoinNode'; + + } + + /** + * Constructs a new join node. + * + * @param {Array} nodes - An array of nodes that should be joined. + * @param {?string} [nodeType=null] - The node type. + */ + constructor( nodes = [], nodeType = null ) { + + super( nodeType ); + + /** + * An array of nodes that should be joined. + * + * @type {Array} + */ + this.nodes = nodes; + + } + + /** + * This method is overwritten since the node type must be inferred from the + * joined data length if not explicitly defined. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType( builder ) { + + if ( this.nodeType !== null ) { + + return builder.getVectorType( this.nodeType ); + + } + + return builder.getTypeFromLength( this.nodes.reduce( ( count, cur ) => count + builder.getTypeLength( cur.getNodeType( builder ) ), 0 ) ); + + } + + generate( builder, output ) { + + const type = this.getNodeType( builder ); + const maxLength = builder.getTypeLength( type ); + + const nodes = this.nodes; + + const primitiveType = builder.getComponentType( type ); + + const snippetValues = []; + + let length = 0; + + for ( const input of nodes ) { + + if ( length >= maxLength ) { + + console.error( `THREE.TSL: Length of parameters exceeds maximum length of function '${ type }()' type.` ); + break; + + } + + let inputType = input.getNodeType( builder ); + let inputTypeLength = builder.getTypeLength( inputType ); + let inputSnippet; + + if ( length + inputTypeLength > maxLength ) { + + console.error( `THREE.TSL: Length of '${ type }()' data exceeds maximum length of output type.` ); + + inputTypeLength = maxLength - length; + inputType = builder.getTypeFromLength( inputTypeLength ); + + } + + length += inputTypeLength; + inputSnippet = input.build( builder, inputType ); + + const inputPrimitiveType = builder.getComponentType( inputType ); + + if ( inputPrimitiveType !== primitiveType ) { + + inputSnippet = builder.format( inputSnippet, inputPrimitiveType, primitiveType ); + + } + + snippetValues.push( inputSnippet ); + + } + + const snippet = `${ builder.getType( type ) }( ${ snippetValues.join( ', ' ) } )`; + + return builder.format( snippet, type, output ); + + } + +} + +const _stringVectorComponents = vectorComponents.join( '' ); + +/** + * This module is part of the TSL core and usually not used in app level code. + * `SplitNode` represents a property access operation which means it is + * used to implement any `.xyzw`, `.rgba` and `stpq` usage on node objects. + * For example: + * ```js + * const redValue = color.r; + * ``` + * + * @augments Node + */ +class SplitNode extends Node { + + static get type() { + + return 'SplitNode'; + + } + + /** + * Constructs a new split node. + * + * @param {Node} node - The node that should be accessed. + * @param {string} [components='x'] - The components that should be accessed. + */ + constructor( node, components = 'x' ) { + + super(); + + /** + * The node that should be accessed. + * + * @type {Node} + */ + this.node = node; + + /** + * The components that should be accessed. + * + * @type {string} + */ + this.components = components; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSplitNode = true; + + } + + /** + * Returns the vector length which is computed based on the requested components. + * + * @return {number} The vector length. + */ + getVectorLength() { + + let vectorLength = this.components.length; + + for ( const c of this.components ) { + + vectorLength = Math.max( vectorComponents.indexOf( c ) + 1, vectorLength ); + + } + + return vectorLength; + + } + + /** + * Returns the component type of the node's type. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The component type. + */ + getComponentType( builder ) { + + return builder.getComponentType( this.node.getNodeType( builder ) ); + + } + + /** + * This method is overwritten since the node type is inferred from requested components. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType( builder ) { + + return builder.getTypeFromLength( this.components.length, this.getComponentType( builder ) ); + + } + + generate( builder, output ) { + + const node = this.node; + const nodeTypeLength = builder.getTypeLength( node.getNodeType( builder ) ); + + let snippet = null; + + if ( nodeTypeLength > 1 ) { + + let type = null; + + const componentsLength = this.getVectorLength(); + + if ( componentsLength >= nodeTypeLength ) { + + // needed expand the input node + + type = builder.getTypeFromLength( this.getVectorLength(), this.getComponentType( builder ) ); + + } + + const nodeSnippet = node.build( builder, type ); + + if ( this.components.length === nodeTypeLength && this.components === _stringVectorComponents.slice( 0, this.components.length ) ) { + + // unnecessary swizzle + + snippet = builder.format( nodeSnippet, type, output ); + + } else { + + snippet = builder.format( `${nodeSnippet}.${this.components}`, this.getNodeType( builder ), output ); + + } + + } else { + + // ignore .components if .node returns float/integer + + snippet = node.build( builder, output ); + + } + + return snippet; + + } + + serialize( data ) { + + super.serialize( data ); + + data.components = this.components; + + } + + deserialize( data ) { + + super.deserialize( data ); + + this.components = data.components; + + } + +} + +/** + * This module is part of the TSL core and usually not used in app level code. + * `SetNode` represents a set operation which means it is used to implement any + * `setXYZW()`, `setRGBA()` and `setSTPQ()` method invocations on node objects. + * For example: + * ```js + * materialLine.colorNode = color( 0, 0, 0 ).setR( float( 1 ) ); + * ``` + * + * @augments TempNode + */ +class SetNode extends TempNode { + + static get type() { + + return 'SetNode'; + + } + + /** + * Constructs a new set node. + * + * @param {Node} sourceNode - The node that should be updated. + * @param {string} components - The components that should be updated. + * @param {Node} targetNode - The value node. + */ + constructor( sourceNode, components, targetNode ) { + + super(); + + /** + * The node that should be updated. + * + * @type {Node} + */ + this.sourceNode = sourceNode; + + /** + * The components that should be updated. + * + * @type {string} + */ + this.components = components; + + /** + * The value node. + * + * @type {Node} + */ + this.targetNode = targetNode; + + } + + /** + * This method is overwritten since the node type is inferred from {@link SetNode#sourceNode}. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType( builder ) { + + return this.sourceNode.getNodeType( builder ); + + } + + generate( builder ) { + + const { sourceNode, components, targetNode } = this; + + const sourceType = this.getNodeType( builder ); + + const componentType = builder.getComponentType( targetNode.getNodeType( builder ) ); + const targetType = builder.getTypeFromLength( components.length, componentType ); + + const targetSnippet = targetNode.build( builder, targetType ); + const sourceSnippet = sourceNode.build( builder, sourceType ); + + const length = builder.getTypeLength( sourceType ); + const snippetValues = []; + + for ( let i = 0; i < length; i ++ ) { + + const component = vectorComponents[ i ]; + + if ( component === components[ 0 ] ) { + + snippetValues.push( targetSnippet ); + + i += components.length - 1; + + } else { + + snippetValues.push( sourceSnippet + '.' + component ); + + } + + } + + return `${ builder.getType( sourceType ) }( ${ snippetValues.join( ', ' ) } )`; + + } + +} + +/** + * This module is part of the TSL core and usually not used in app level code. + * It represents a flip operation during the shader generation process + * meaning it flips normalized values with the following formula: + * ``` + * x = 1 - x; + * ``` + * `FlipNode` is internally used to implement any `flipXYZW()`, `flipRGBA()` and + * `flipSTPQ()` method invocations on node objects. For example: + * ```js + * uvNode = uvNode.flipY(); + * ``` + * + * @augments TempNode + */ +class FlipNode extends TempNode { + + static get type() { + + return 'FlipNode'; + + } + + /** + * Constructs a new flip node. + * + * @param {Node} sourceNode - The node which component(s) should be flipped. + * @param {string} components - The components that should be flipped e.g. `'x'` or `'xy'`. + */ + constructor( sourceNode, components ) { + + super(); + + /** + * The node which component(s) should be flipped. + * + * @type {Node} + */ + this.sourceNode = sourceNode; + + /** + * The components that should be flipped e.g. `'x'` or `'xy'`. + * + * @type {string} + */ + this.components = components; + + } + + /** + * This method is overwritten since the node type is inferred from the source node. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType( builder ) { + + return this.sourceNode.getNodeType( builder ); + + } + + generate( builder ) { + + const { components, sourceNode } = this; + + const sourceType = this.getNodeType( builder ); + const sourceSnippet = sourceNode.build( builder ); + + const sourceCache = builder.getVarFromNode( this ); + const sourceProperty = builder.getPropertyName( sourceCache ); + + builder.addLineFlowCode( sourceProperty + ' = ' + sourceSnippet, this ); + + const length = builder.getTypeLength( sourceType ); + const snippetValues = []; + + let componentIndex = 0; + + for ( let i = 0; i < length; i ++ ) { + + const component = vectorComponents[ i ]; + + if ( component === components[ componentIndex ] ) { + + snippetValues.push( '1.0 - ' + ( sourceProperty + '.' + component ) ); + + componentIndex ++; + + } else { + + snippetValues.push( sourceProperty + '.' + component ); + + } + + } + + return `${ builder.getType( sourceType ) }( ${ snippetValues.join( ', ' ) } )`; + + } + +} + +/** + * Base class for representing data input nodes. + * + * @augments Node + */ +class InputNode extends Node { + + static get type() { + + return 'InputNode'; + + } + + /** + * Constructs a new input node. + * + * @param {any} value - The value of this node. This can be any JS primitive, functions, array buffers or even three.js objects (vector, matrices, colors). + * @param {?string} nodeType - The node type. If no explicit type is defined, the node tries to derive the type from its value. + */ + constructor( value, nodeType = null ) { + + super( nodeType ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isInputNode = true; + + /** + * The value of this node. This can be any JS primitive, functions, array buffers or even three.js objects (vector, matrices, colors). + * + * @type {any} + */ + this.value = value; + + /** + * The precision of the value in the shader. + * + * @type {?('low'|'medium'|'high')} + * @default null + */ + this.precision = null; + + } + + getNodeType( /*builder*/ ) { + + if ( this.nodeType === null ) { + + return getValueType( this.value ); + + } + + return this.nodeType; + + } + + /** + * Returns the input type of the node which is by default the node type. Derived modules + * might overwrite this method and use a fixed type or compute one analytically. + * + * A typical example for different input and node types are textures. The input type of a + * normal RGBA texture is `texture` whereas its node type is `vec4`. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The input type. + */ + getInputType( builder ) { + + return this.getNodeType( builder ); + + } + + /** + * Sets the precision to the given value. The method can be + * overwritten in derived classes if the final precision must be computed + * analytically. + * + * @param {('low'|'medium'|'high')} precision - The precision of the input value in the shader. + * @return {InputNode} A reference to this node. + */ + setPrecision( precision ) { + + this.precision = precision; + + return this; + + } + + serialize( data ) { + + super.serialize( data ); + + data.value = this.value; + + if ( this.value && this.value.toArray ) data.value = this.value.toArray(); + + data.valueType = getValueType( this.value ); + data.nodeType = this.nodeType; + + if ( data.valueType === 'ArrayBuffer' ) data.value = arrayBufferToBase64( data.value ); + + data.precision = this.precision; + + } + + deserialize( data ) { + + super.deserialize( data ); + + this.nodeType = data.nodeType; + this.value = Array.isArray( data.value ) ? getValueFromType( data.valueType, ...data.value ) : data.value; + + this.precision = data.precision || null; + + if ( this.value && this.value.fromArray ) this.value = this.value.fromArray( data.value ); + + } + + generate( /*builder, output*/ ) { + + console.warn( 'Abstract function.' ); + + } + +} + +const _regNum = /float|u?int/; + +/** + * Class for representing a constant value in the shader. + * + * @augments InputNode + */ +class ConstNode extends InputNode { + + static get type() { + + return 'ConstNode'; + + } + + /** + * Constructs a new input node. + * + * @param {any} value - The value of this node. Usually a JS primitive or three.js object (vector, matrix, color). + * @param {?string} nodeType - The node type. If no explicit type is defined, the node tries to derive the type from its value. + */ + constructor( value, nodeType = null ) { + + super( value, nodeType ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isConstNode = true; + + } + + /** + * Generates the shader string of the value with the current node builder. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The generated value as a shader string. + */ + generateConst( builder ) { + + return builder.generateConst( this.getNodeType( builder ), this.value ); + + } + + generate( builder, output ) { + + const type = this.getNodeType( builder ); + + if ( _regNum.test( type ) && _regNum.test( output ) ) { + + return builder.generateConst( output, this.value ); + + } + + return builder.format( this.generateConst( builder ), type, output ); + + } + +} + +/** + * Base class for representing member access on an object-like + * node data structures. + * + * @augments Node + */ +class MemberNode extends Node { + + static get type() { + + return 'MemberNode'; + + } + + /** + * Constructs an array element node. + * + * @param {Node} node - The array-like node. + * @param {string} property - The property name. + */ + constructor( node, property ) { + + super(); + + /** + * The array-like node. + * + * @type {Node} + */ + this.node = node; + + /** + * The property name. + * + * @type {Node} + */ + this.property = property; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMemberNode = true; + + } + + getNodeType( builder ) { + + return this.node.getMemberType( builder, this.property ); + + } + + generate( builder ) { + + const propertyName = this.node.build( builder ); + + return propertyName + '.' + this.property; + + } + +} + +let currentStack = null; + +const NodeElements = new Map(); + +function addMethodChaining( name, nodeElement ) { + + if ( NodeElements.has( name ) ) { + + console.warn( `THREE.TSL: Redefinition of method chaining '${ name }'.` ); + return; + + } + + if ( typeof nodeElement !== 'function' ) throw new Error( `THREE.TSL: Node element ${ name } is not a function` ); + + NodeElements.set( name, nodeElement ); + +} + +const parseSwizzle = ( props ) => props.replace( /r|s/g, 'x' ).replace( /g|t/g, 'y' ).replace( /b|p/g, 'z' ).replace( /a|q/g, 'w' ); +const parseSwizzleAndSort = ( props ) => parseSwizzle( props ).split( '' ).sort().join( '' ); + +const shaderNodeHandler = { + + setup( NodeClosure, params ) { + + const inputs = params.shift(); + + return NodeClosure( nodeObjects( inputs ), ...params ); + + }, + + get( node, prop, nodeObj ) { + + if ( typeof prop === 'string' && node[ prop ] === undefined ) { + + if ( node.isStackNode !== true && prop === 'assign' ) { + + return ( ...params ) => { + + currentStack.assign( nodeObj, ...params ); + + return nodeObj; + + }; + + } else if ( NodeElements.has( prop ) ) { + + const nodeElement = NodeElements.get( prop ); + + return node.isStackNode ? ( ...params ) => nodeObj.add( nodeElement( ...params ) ) : ( ...params ) => nodeElement( nodeObj, ...params ); + + } else if ( prop === 'self' ) { + + return node; + + } else if ( prop.endsWith( 'Assign' ) && NodeElements.has( prop.slice( 0, prop.length - 'Assign'.length ) ) ) { + + const nodeElement = NodeElements.get( prop.slice( 0, prop.length - 'Assign'.length ) ); + + return node.isStackNode ? ( ...params ) => nodeObj.assign( params[ 0 ], nodeElement( ...params ) ) : ( ...params ) => nodeObj.assign( nodeElement( nodeObj, ...params ) ); + + } else if ( /^[xyzwrgbastpq]{1,4}$/.test( prop ) === true ) { + + // accessing properties ( swizzle ) + + prop = parseSwizzle( prop ); + + return nodeObject( new SplitNode( nodeObj, prop ) ); + + } else if ( /^set[XYZWRGBASTPQ]{1,4}$/.test( prop ) === true ) { + + // set properties ( swizzle ) and sort to xyzw sequence + + prop = parseSwizzleAndSort( prop.slice( 3 ).toLowerCase() ); + + return ( value ) => nodeObject( new SetNode( node, prop, nodeObject( value ) ) ); + + } else if ( /^flip[XYZWRGBASTPQ]{1,4}$/.test( prop ) === true ) { + + // set properties ( swizzle ) and sort to xyzw sequence + + prop = parseSwizzleAndSort( prop.slice( 4 ).toLowerCase() ); + + return () => nodeObject( new FlipNode( nodeObject( node ), prop ) ); + + } else if ( prop === 'width' || prop === 'height' || prop === 'depth' ) { + + // accessing property + + if ( prop === 'width' ) prop = 'x'; + else if ( prop === 'height' ) prop = 'y'; + else if ( prop === 'depth' ) prop = 'z'; + + return nodeObject( new SplitNode( node, prop ) ); + + } else if ( /^\d+$/.test( prop ) === true ) { + + // accessing array + + return nodeObject( new ArrayElementNode( nodeObj, new ConstNode( Number( prop ), 'uint' ) ) ); + + } else if ( /^get$/.test( prop ) === true ) { + + // accessing properties + + return ( value ) => nodeObject( new MemberNode( nodeObj, value ) ); + + } + + } + + return Reflect.get( node, prop, nodeObj ); + + }, + + set( node, prop, value, nodeObj ) { + + if ( typeof prop === 'string' && node[ prop ] === undefined ) { + + // setting properties + + if ( /^[xyzwrgbastpq]{1,4}$/.test( prop ) === true || prop === 'width' || prop === 'height' || prop === 'depth' || /^\d+$/.test( prop ) === true ) { + + nodeObj[ prop ].assign( value ); + + return true; + + } + + } + + return Reflect.set( node, prop, value, nodeObj ); + + } + +}; + +const nodeObjectsCacheMap = new WeakMap(); +const nodeBuilderFunctionsCacheMap = new WeakMap(); + +const ShaderNodeObject = function ( obj, altType = null ) { + + const type = getValueType( obj ); + + if ( type === 'node' ) { + + let nodeObject = nodeObjectsCacheMap.get( obj ); + + if ( nodeObject === undefined ) { + + nodeObject = new Proxy( obj, shaderNodeHandler ); + + nodeObjectsCacheMap.set( obj, nodeObject ); + nodeObjectsCacheMap.set( nodeObject, nodeObject ); + + } + + return nodeObject; + + } else if ( ( altType === null && ( type === 'float' || type === 'boolean' ) ) || ( type && type !== 'shader' && type !== 'string' ) ) { + + return nodeObject( getConstNode( obj, altType ) ); + + } else if ( type === 'shader' ) { + + return obj.isFn ? obj : Fn( obj ); + + } + + return obj; + +}; + +const ShaderNodeObjects = function ( objects, altType = null ) { + + for ( const name in objects ) { + + objects[ name ] = nodeObject( objects[ name ], altType ); + + } + + return objects; + +}; + +const ShaderNodeArray = function ( array, altType = null ) { + + const len = array.length; + + for ( let i = 0; i < len; i ++ ) { + + array[ i ] = nodeObject( array[ i ], altType ); + + } + + return array; + +}; + +const ShaderNodeProxy = function ( NodeClass, scope = null, factor = null, settings = null ) { + + const assignNode = ( node ) => nodeObject( settings !== null ? Object.assign( node, settings ) : node ); + + let fn, name = scope, minParams, maxParams; + + function verifyParamsLimit( params ) { + + let tslName; + + if ( name ) tslName = /[a-z]/i.test( name ) ? name + '()' : name; + else tslName = NodeClass.type; + + if ( minParams !== undefined && params.length < minParams ) { + + console.error( `THREE.TSL: "${ tslName }" parameter length is less than minimum required.` ); + + return params.concat( new Array( minParams - params.length ).fill( 0 ) ); + + } else if ( maxParams !== undefined && params.length > maxParams ) { + + console.error( `THREE.TSL: "${ tslName }" parameter length exceeds limit.` ); + + return params.slice( 0, maxParams ); + + } + + return params; + + } + + if ( scope === null ) { + + fn = ( ...params ) => { + + return assignNode( new NodeClass( ...nodeArray( verifyParamsLimit( params ) ) ) ); + + }; + + } else if ( factor !== null ) { + + factor = nodeObject( factor ); + + fn = ( ...params ) => { + + return assignNode( new NodeClass( scope, ...nodeArray( verifyParamsLimit( params ) ), factor ) ); + + }; + + } else { + + fn = ( ...params ) => { + + return assignNode( new NodeClass( scope, ...nodeArray( verifyParamsLimit( params ) ) ) ); + + }; + + } + + fn.setParameterLength = ( ...params ) => { + + if ( params.length === 1 ) minParams = maxParams = params[ 0 ]; + else if ( params.length === 2 ) [ minParams, maxParams ] = params; + + return fn; + + }; + + fn.setName = ( value ) => { + + name = value; + + return fn; + + }; + + return fn; + +}; + +const ShaderNodeImmutable = function ( NodeClass, ...params ) { + + return nodeObject( new NodeClass( ...nodeArray( params ) ) ); + +}; + +class ShaderCallNodeInternal extends Node { + + constructor( shaderNode, inputNodes ) { + + super(); + + this.shaderNode = shaderNode; + this.inputNodes = inputNodes; + + this.isShaderCallNodeInternal = true; + + } + + getNodeType( builder ) { + + return this.shaderNode.nodeType || this.getOutputNode( builder ).getNodeType( builder ); + + } + + getMemberType( builder, name ) { + + return this.getOutputNode( builder ).getMemberType( builder, name ); + + } + + call( builder ) { + + const { shaderNode, inputNodes } = this; + + const properties = builder.getNodeProperties( shaderNode ); + + const subBuild = builder.getClosestSubBuild( shaderNode.subBuilds ) || ''; + const subBuildProperty = subBuild || 'default'; + + if ( properties[ subBuildProperty ] ) { + + return properties[ subBuildProperty ]; + + } + + // + + const previousSubBuildFn = builder.subBuildFn; + + builder.subBuildFn = subBuild; + + let result = null; + + if ( shaderNode.layout ) { + + let functionNodesCacheMap = nodeBuilderFunctionsCacheMap.get( builder.constructor ); + + if ( functionNodesCacheMap === undefined ) { + + functionNodesCacheMap = new WeakMap(); + + nodeBuilderFunctionsCacheMap.set( builder.constructor, functionNodesCacheMap ); + + } + + let functionNode = functionNodesCacheMap.get( shaderNode ); + + if ( functionNode === undefined ) { + + functionNode = nodeObject( builder.buildFunctionNode( shaderNode ) ); + + functionNodesCacheMap.set( shaderNode, functionNode ); + + } + + builder.addInclude( functionNode ); + + result = nodeObject( functionNode.call( inputNodes ) ); + + } else { + + const jsFunc = shaderNode.jsFunc; + const outputNode = inputNodes !== null || jsFunc.length > 1 ? jsFunc( inputNodes || [], builder ) : jsFunc( builder ); + + result = nodeObject( outputNode ); + + } + + builder.subBuildFn = previousSubBuildFn; + + if ( shaderNode.once ) { + + properties[ subBuildProperty ] = result; + + } + + return result; + + } + + setupOutput( builder ) { + + builder.addStack(); + + builder.stack.outputNode = this.call( builder ); + + return builder.removeStack(); + + } + + getOutputNode( builder ) { + + const properties = builder.getNodeProperties( this ); + const subBuildOutput = builder.getSubBuildOutput( this ); + + properties[ subBuildOutput ] = properties[ subBuildOutput ] || this.setupOutput( builder ); + properties[ subBuildOutput ].subBuild = builder.getClosestSubBuild( this ); + + return properties[ subBuildOutput ]; + + } + + build( builder, output = null ) { + + let result = null; + + const buildStage = builder.getBuildStage(); + const properties = builder.getNodeProperties( this ); + + const subBuildOutput = builder.getSubBuildOutput( this ); + const outputNode = this.getOutputNode( builder ); + + if ( buildStage === 'setup' ) { + + const subBuildInitialized = builder.getSubBuildProperty( 'initialized', this ); + + if ( properties[ subBuildInitialized ] !== true ) { + + properties[ subBuildInitialized ] = true; + + properties[ subBuildOutput ] = this.getOutputNode( builder ); + properties[ subBuildOutput ].build( builder ); + + // If the shaderNode has subBuilds, add them to the chaining nodes + // so they can be built later in the build process. + + if ( this.shaderNode.subBuilds ) { + + for ( const node of builder.chaining ) { + + const nodeData = builder.getDataFromNode( node, 'any' ); + nodeData.subBuilds = nodeData.subBuilds || new Set(); + + for ( const subBuild of this.shaderNode.subBuilds ) { + + nodeData.subBuilds.add( subBuild ); + + } + + //builder.getDataFromNode( node ).subBuilds = nodeData.subBuilds; + + } + + } + + } + + result = properties[ subBuildOutput ]; + + } else if ( buildStage === 'analyze' ) { + + outputNode.build( builder, output ); + + } else if ( buildStage === 'generate' ) { + + result = outputNode.build( builder, output ) || ''; + + } + + return result; + + } + +} + +class ShaderNodeInternal extends Node { + + constructor( jsFunc, nodeType ) { + + super( nodeType ); + + this.jsFunc = jsFunc; + this.layout = null; + + this.global = true; + + this.once = false; + + } + + setLayout( layout ) { + + this.layout = layout; + + return this; + + } + + call( inputs = null ) { + + nodeObjects( inputs ); + + return nodeObject( new ShaderCallNodeInternal( this, inputs ) ); + + } + + setup() { + + return this.call(); + + } + +} + +const bools = [ false, true ]; +const uints = [ 0, 1, 2, 3 ]; +const ints = [ -1, -2 ]; +const floats = [ 0.5, 1.5, 1 / 3, 1e-6, 1e6, Math.PI, Math.PI * 2, 1 / Math.PI, 2 / Math.PI, 1 / ( Math.PI * 2 ), Math.PI / 2 ]; + +const boolsCacheMap = new Map(); +for ( const bool of bools ) boolsCacheMap.set( bool, new ConstNode( bool ) ); + +const uintsCacheMap = new Map(); +for ( const uint of uints ) uintsCacheMap.set( uint, new ConstNode( uint, 'uint' ) ); + +const intsCacheMap = new Map( [ ...uintsCacheMap ].map( el => new ConstNode( el.value, 'int' ) ) ); +for ( const int of ints ) intsCacheMap.set( int, new ConstNode( int, 'int' ) ); + +const floatsCacheMap = new Map( [ ...intsCacheMap ].map( el => new ConstNode( el.value ) ) ); +for ( const float of floats ) floatsCacheMap.set( float, new ConstNode( float ) ); +for ( const float of floats ) floatsCacheMap.set( - float, new ConstNode( - float ) ); + +const cacheMaps = { bool: boolsCacheMap, uint: uintsCacheMap, ints: intsCacheMap, float: floatsCacheMap }; + +const constNodesCacheMap = new Map( [ ...boolsCacheMap, ...floatsCacheMap ] ); + +const getConstNode = ( value, type ) => { + + if ( constNodesCacheMap.has( value ) ) { + + return constNodesCacheMap.get( value ); + + } else if ( value.isNode === true ) { + + return value; + + } else { + + return new ConstNode( value, type ); + + } + +}; + +const ConvertType = function ( type, cacheMap = null ) { + + return ( ...params ) => { + + if ( params.length === 0 || ( ! [ 'bool', 'float', 'int', 'uint' ].includes( type ) && params.every( param => typeof param !== 'object' ) ) ) { + + params = [ getValueFromType( type, ...params ) ]; + + } + + if ( params.length === 1 && cacheMap !== null && cacheMap.has( params[ 0 ] ) ) { + + return nodeObject( cacheMap.get( params[ 0 ] ) ); + + } + + if ( params.length === 1 ) { + + const node = getConstNode( params[ 0 ], type ); + if ( node.nodeType === type ) return nodeObject( node ); + return nodeObject( new ConvertNode( node, type ) ); + + } + + const nodes = params.map( param => getConstNode( param ) ); + return nodeObject( new JoinNode( nodes, type ) ); + + }; + +}; + +// exports + +const defined = ( v ) => typeof v === 'object' && v !== null ? v.value : v; // TODO: remove boolean conversion and defined function + +// utils + +const getConstNodeType = ( value ) => ( value !== undefined && value !== null ) ? ( value.nodeType || value.convertTo || ( typeof value === 'string' ? value : null ) ) : null; + +// shader node base + +function ShaderNode( jsFunc, nodeType ) { + + return new Proxy( new ShaderNodeInternal( jsFunc, nodeType ), shaderNodeHandler ); + +} + +const nodeObject = ( val, altType = null ) => /* new */ ShaderNodeObject( val, altType ); +const nodeObjects = ( val, altType = null ) => new ShaderNodeObjects( val, altType ); +const nodeArray = ( val, altType = null ) => new ShaderNodeArray( val, altType ); +const nodeProxy = ( ...params ) => new ShaderNodeProxy( ...params ); +const nodeImmutable = ( ...params ) => new ShaderNodeImmutable( ...params ); + +let fnId = 0; + +const Fn = ( jsFunc, layout = null ) => { + + let nodeType = null; + + if ( layout !== null ) { + + if ( typeof layout === 'object' ) { + + nodeType = layout.return; + + } else { + + if ( typeof layout === 'string' ) { + + nodeType = layout; + + } else { + + console.error( 'THREE.TSL: Invalid layout type.' ); + + } + + layout = null; + + } + + } + + const shaderNode = new ShaderNode( jsFunc, nodeType ); + + const fn = ( ...params ) => { + + let inputs; + + nodeObjects( params ); + + const isArrayAsParameter = params[ 0 ] && ( params[ 0 ].isNode || Object.getPrototypeOf( params[ 0 ] ) !== Object.prototype ); + + if ( isArrayAsParameter ) { + + inputs = [ ...params ]; + + } else { + + inputs = params[ 0 ]; + + } + + const fnCall = shaderNode.call( inputs ); + + if ( nodeType === 'void' ) fnCall.toStack(); + + return fnCall; + + }; + + fn.shaderNode = shaderNode; + fn.id = shaderNode.id; + + fn.isFn = true; + + fn.getNodeType = ( ...params ) => shaderNode.getNodeType( ...params ); + fn.getCacheKey = ( ...params ) => shaderNode.getCacheKey( ...params ); + + fn.setLayout = ( layout ) => { + + shaderNode.setLayout( layout ); + + return fn; + + }; + + fn.once = ( subBuilds = null ) => { + + shaderNode.once = true; + shaderNode.subBuilds = subBuilds; + + return fn; + + }; + + if ( layout !== null ) { + + if ( typeof layout.inputs !== 'object' ) { + + const fullLayout = { + name: 'fn' + fnId ++, + type: nodeType, + inputs: [] + }; + + for ( const name in layout ) { + + if ( name === 'return' ) continue; + + fullLayout.inputs.push( { + name: name, + type: layout[ name ] + } ); + + } + + layout = fullLayout; + + } + + fn.setLayout( layout ); + + } + + return fn; + +}; + +// + +const setCurrentStack = ( stack ) => { + + currentStack = stack; + +}; + +const getCurrentStack = () => currentStack; + +/** + * Represent a conditional node using if/else statements. + * + * ```js + * If( condition, function ) + * .ElseIf( condition, function ) + * .Else( function ) + * ``` + * @tsl + * @function + * @param {...any} params - The parameters for the conditional node. + * @returns {StackNode} The conditional node. + */ +const If = ( ...params ) => currentStack.If( ...params ); + +/** + * Represent a conditional node using switch/case statements. + * + * ```js + * Switch( value ) + * .Case( 1, function ) + * .Case( 2, 3, 4, function ) + * .Default( function ) + * ``` + * @tsl + * @function + * @param {...any} params - The parameters for the conditional node. + * @returns {StackNode} The conditional node. + */ +const Switch = ( ...params ) => currentStack.Switch( ...params ); + +/** + * Add the given node to the current stack. + * + * @param {Node} node - The node to add. + * @returns {Node} The node that was added to the stack. + */ +function Stack( node ) { + + if ( currentStack ) currentStack.add( node ); + + return node; + +} + +addMethodChaining( 'toStack', Stack ); + +// types + +const color = new ConvertType( 'color' ); + +const float = new ConvertType( 'float', cacheMaps.float ); +const int = new ConvertType( 'int', cacheMaps.ints ); +const uint = new ConvertType( 'uint', cacheMaps.uint ); +const bool = new ConvertType( 'bool', cacheMaps.bool ); + +const vec2 = new ConvertType( 'vec2' ); +const ivec2 = new ConvertType( 'ivec2' ); +const uvec2 = new ConvertType( 'uvec2' ); +const bvec2 = new ConvertType( 'bvec2' ); + +const vec3 = new ConvertType( 'vec3' ); +const ivec3 = new ConvertType( 'ivec3' ); +const uvec3 = new ConvertType( 'uvec3' ); +const bvec3 = new ConvertType( 'bvec3' ); + +const vec4 = new ConvertType( 'vec4' ); +const ivec4 = new ConvertType( 'ivec4' ); +const uvec4 = new ConvertType( 'uvec4' ); +const bvec4 = new ConvertType( 'bvec4' ); + +const mat2 = new ConvertType( 'mat2' ); +const mat3 = new ConvertType( 'mat3' ); +const mat4 = new ConvertType( 'mat4' ); + +const string = ( value = '' ) => nodeObject( new ConstNode( value, 'string' ) ); +const arrayBuffer = ( value ) => nodeObject( new ConstNode( value, 'ArrayBuffer' ) ); + +addMethodChaining( 'toColor', color ); +addMethodChaining( 'toFloat', float ); +addMethodChaining( 'toInt', int ); +addMethodChaining( 'toUint', uint ); +addMethodChaining( 'toBool', bool ); +addMethodChaining( 'toVec2', vec2 ); +addMethodChaining( 'toIVec2', ivec2 ); +addMethodChaining( 'toUVec2', uvec2 ); +addMethodChaining( 'toBVec2', bvec2 ); +addMethodChaining( 'toVec3', vec3 ); +addMethodChaining( 'toIVec3', ivec3 ); +addMethodChaining( 'toUVec3', uvec3 ); +addMethodChaining( 'toBVec3', bvec3 ); +addMethodChaining( 'toVec4', vec4 ); +addMethodChaining( 'toIVec4', ivec4 ); +addMethodChaining( 'toUVec4', uvec4 ); +addMethodChaining( 'toBVec4', bvec4 ); +addMethodChaining( 'toMat2', mat2 ); +addMethodChaining( 'toMat3', mat3 ); +addMethodChaining( 'toMat4', mat4 ); + +// basic nodes + +const element = /*@__PURE__*/ nodeProxy( ArrayElementNode ).setParameterLength( 2 ); +const convert = ( node, types ) => nodeObject( new ConvertNode( nodeObject( node ), types ) ); +const split = ( node, channels ) => nodeObject( new SplitNode( nodeObject( node ), channels ) ); + +addMethodChaining( 'element', element ); +addMethodChaining( 'convert', convert ); + +// deprecated + +/** + * @tsl + * @function + * @deprecated since r176. Use {@link Stack} instead. + * + * @param {Node} node - The node to add. + * @returns {Function} + */ +const append = ( node ) => { // @deprecated, r176 + + console.warn( 'THREE.TSL: append() has been renamed to Stack().' ); + return Stack( node ); + +}; + +addMethodChaining( 'append', ( node ) => { // @deprecated, r176 + + console.warn( 'THREE.TSL: .append() has been renamed to .toStack().' ); + return Stack( node ); + +} ); + +/** + * This class represents a shader property. It can be used + * to explicitly define a property and assign a value to it. + * + * ```js + * const threshold = property( 'float', 'threshold' ).assign( THRESHOLD ); + *``` + * `PropertyNode` is used by the engine to predefined common material properties + * for TSL code. + * + * @augments Node + */ +class PropertyNode extends Node { + + static get type() { + + return 'PropertyNode'; + + } + + /** + * Constructs a new property node. + * + * @param {string} nodeType - The type of the node. + * @param {?string} [name=null] - The name of the property in the shader. + * @param {boolean} [varying=false] - Whether this property is a varying or not. + */ + constructor( nodeType, name = null, varying = false ) { + + super( nodeType ); + + /** + * The name of the property in the shader. If no name is defined, + * the node system auto-generates one. + * + * @type {?string} + * @default null + */ + this.name = name; + + /** + * Whether this property is a varying or not. + * + * @type {boolean} + * @default false + */ + this.varying = varying; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isPropertyNode = true; + + /** + * This flag is used for global cache. + * + * @type {boolean} + * @default true + */ + this.global = true; + + } + + getHash( builder ) { + + return this.name || super.getHash( builder ); + + } + + generate( builder ) { + + let nodeVar; + + if ( this.varying === true ) { + + nodeVar = builder.getVaryingFromNode( this, this.name ); + nodeVar.needsInterpolation = true; + + } else { + + nodeVar = builder.getVarFromNode( this, this.name ); + + } + + return builder.getPropertyName( nodeVar ); + + } + +} + +/** + * TSL function for creating a property node. + * + * @tsl + * @function + * @param {string} type - The type of the node. + * @param {?string} [name=null] - The name of the property in the shader. + * @returns {PropertyNode} + */ +const property = ( type, name ) => nodeObject( new PropertyNode( type, name ) ); + +/** + * TSL function for creating a varying property node. + * + * @tsl + * @function + * @param {string} type - The type of the node. + * @param {?string} [name=null] - The name of the varying in the shader. + * @returns {PropertyNode} + */ +const varyingProperty = ( type, name ) => nodeObject( new PropertyNode( type, name, true ) ); + +/** + * TSL object that represents the shader variable `DiffuseColor`. + * + * @tsl + * @type {PropertyNode} + */ +const diffuseColor = /*@__PURE__*/ nodeImmutable( PropertyNode, 'vec4', 'DiffuseColor' ); + +/** + * TSL object that represents the shader variable `EmissiveColor`. + * + * @tsl + * @type {PropertyNode} + */ +const emissive = /*@__PURE__*/ nodeImmutable( PropertyNode, 'vec3', 'EmissiveColor' ); + +/** + * TSL object that represents the shader variable `Roughness`. + * + * @tsl + * @type {PropertyNode} + */ +const roughness = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'Roughness' ); + +/** + * TSL object that represents the shader variable `Metalness`. + * + * @tsl + * @type {PropertyNode} + */ +const metalness = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'Metalness' ); + +/** + * TSL object that represents the shader variable `Clearcoat`. + * + * @tsl + * @type {PropertyNode} + */ +const clearcoat = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'Clearcoat' ); + +/** + * TSL object that represents the shader variable `ClearcoatRoughness`. + * + * @tsl + * @type {PropertyNode} + */ +const clearcoatRoughness = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'ClearcoatRoughness' ); + +/** + * TSL object that represents the shader variable `Sheen`. + * + * @tsl + * @type {PropertyNode} + */ +const sheen = /*@__PURE__*/ nodeImmutable( PropertyNode, 'vec3', 'Sheen' ); + +/** + * TSL object that represents the shader variable `SheenRoughness`. + * + * @tsl + * @type {PropertyNode} + */ +const sheenRoughness = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'SheenRoughness' ); + +/** + * TSL object that represents the shader variable `Iridescence`. + * + * @tsl + * @type {PropertyNode} + */ +const iridescence = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'Iridescence' ); + +/** + * TSL object that represents the shader variable `IridescenceIOR`. + * + * @tsl + * @type {PropertyNode} + */ +const iridescenceIOR = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'IridescenceIOR' ); + +/** + * TSL object that represents the shader variable `IridescenceThickness`. + * + * @tsl + * @type {PropertyNode} + */ +const iridescenceThickness = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'IridescenceThickness' ); + +/** + * TSL object that represents the shader variable `AlphaT`. + * + * @tsl + * @type {PropertyNode} + */ +const alphaT = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'AlphaT' ); + +/** + * TSL object that represents the shader variable `Anisotropy`. + * + * @tsl + * @type {PropertyNode} + */ +const anisotropy = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'Anisotropy' ); + +/** + * TSL object that represents the shader variable `AnisotropyT`. + * + * @tsl + * @type {PropertyNode} + */ +const anisotropyT = /*@__PURE__*/ nodeImmutable( PropertyNode, 'vec3', 'AnisotropyT' ); + +/** + * TSL object that represents the shader variable `AnisotropyB`. + * + * @tsl + * @type {PropertyNode} + */ +const anisotropyB = /*@__PURE__*/ nodeImmutable( PropertyNode, 'vec3', 'AnisotropyB' ); + +/** + * TSL object that represents the shader variable `SpecularColor`. + * + * @tsl + * @type {PropertyNode} + */ +const specularColor = /*@__PURE__*/ nodeImmutable( PropertyNode, 'color', 'SpecularColor' ); + +/** + * TSL object that represents the shader variable `SpecularF90`. + * + * @tsl + * @type {PropertyNode} + */ +const specularF90 = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'SpecularF90' ); + +/** + * TSL object that represents the shader variable `Shininess`. + * + * @tsl + * @type {PropertyNode} + */ +const shininess = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'Shininess' ); + +/** + * TSL object that represents the shader variable `Output`. + * + * @tsl + * @type {PropertyNode} + */ +const output = /*@__PURE__*/ nodeImmutable( PropertyNode, 'vec4', 'Output' ); + +/** + * TSL object that represents the shader variable `dashSize`. + * + * @tsl + * @type {PropertyNode} + */ +const dashSize = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'dashSize' ); + +/** + * TSL object that represents the shader variable `gapSize`. + * + * @tsl + * @type {PropertyNode} + */ +const gapSize = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'gapSize' ); + +/** + * TSL object that represents the shader variable `pointWidth`. + * + * @tsl + * @type {PropertyNode} + */ +const pointWidth = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'pointWidth' ); + +/** + * TSL object that represents the shader variable `IOR`. + * + * @tsl + * @type {PropertyNode} + */ +const ior = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'IOR' ); + +/** + * TSL object that represents the shader variable `Transmission`. + * + * @tsl + * @type {PropertyNode} + */ +const transmission = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'Transmission' ); + +/** + * TSL object that represents the shader variable `Thickness`. + * + * @tsl + * @type {PropertyNode} + */ +const thickness = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'Thickness' ); + +/** + * TSL object that represents the shader variable `AttenuationDistance`. + * + * @tsl + * @type {PropertyNode} + */ +const attenuationDistance = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'AttenuationDistance' ); + +/** + * TSL object that represents the shader variable `AttenuationColor`. + * + * @tsl + * @type {PropertyNode} + */ +const attenuationColor = /*@__PURE__*/ nodeImmutable( PropertyNode, 'color', 'AttenuationColor' ); + +/** + * TSL object that represents the shader variable `Dispersion`. + * + * @tsl + * @type {PropertyNode} + */ +const dispersion = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'Dispersion' ); + +/** + * This node can be used to group single instances of {@link UniformNode} + * and manage them as a uniform buffer. + * + * In most cases, the predefined nodes `objectGroup`, `renderGroup` and `frameGroup` + * will be used when defining the {@link UniformNode#groupNode} property. + * + * - `objectGroup`: Uniform buffer per object. + * - `renderGroup`: Shared uniform buffer, updated once per render call. + * - `frameGroup`: Shared uniform buffer, updated once per frame. + * + * @augments Node + */ +class UniformGroupNode extends Node { + + static get type() { + + return 'UniformGroupNode'; + + } + + /** + * Constructs a new uniform group node. + * + * @param {string} name - The name of the uniform group node. + * @param {boolean} [shared=false] - Whether this uniform group node is shared or not. + * @param {number} [order=1] - Influences the internal sorting. + */ + constructor( name, shared = false, order = 1 ) { + + super( 'string' ); + + /** + * The name of the uniform group node. + * + * @type {string} + */ + this.name = name; + + /** + * Whether this uniform group node is shared or not. + * + * @type {boolean} + * @default false + */ + this.shared = shared; + + /** + * Influences the internal sorting. + * TODO: Add details when this property should be changed. + * + * @type {number} + * @default 1 + */ + this.order = order; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isUniformGroup = true; + + } + + serialize( data ) { + + super.serialize( data ); + + data.name = this.name; + data.version = this.version; + data.shared = this.shared; + + } + + deserialize( data ) { + + super.deserialize( data ); + + this.name = data.name; + this.version = data.version; + this.shared = data.shared; + + } + +} + +/** + * TSL function for creating a uniform group node with the given name. + * + * @tsl + * @function + * @param {string} name - The name of the uniform group node. + * @returns {UniformGroupNode} + */ +const uniformGroup = ( name ) => new UniformGroupNode( name ); + +/** + * TSL function for creating a shared uniform group node with the given name and order. + * + * @tsl + * @function + * @param {string} name - The name of the uniform group node. + * @param {number} [order=0] - Influences the internal sorting. + * @returns {UniformGroupNode} + */ +const sharedUniformGroup = ( name, order = 0 ) => new UniformGroupNode( name, true, order ); + +/** + * TSL object that represents a shared uniform group node which is updated once per frame. + * + * @tsl + * @type {UniformGroupNode} + */ +const frameGroup = /*@__PURE__*/ sharedUniformGroup( 'frame' ); + +/** + * TSL object that represents a shared uniform group node which is updated once per render. + * + * @tsl + * @type {UniformGroupNode} + */ +const renderGroup = /*@__PURE__*/ sharedUniformGroup( 'render' ); + +/** + * TSL object that represents a uniform group node which is updated once per object. + * + * @tsl + * @type {UniformGroupNode} + */ +const objectGroup = /*@__PURE__*/ uniformGroup( 'object' ); + +/** + * Class for representing a uniform. + * + * @augments InputNode + */ +class UniformNode extends InputNode { + + static get type() { + + return 'UniformNode'; + + } + + /** + * Constructs a new uniform node. + * + * @param {any} value - The value of this node. Usually a JS primitive or three.js object (vector, matrix, color, texture). + * @param {?string} nodeType - The node type. If no explicit type is defined, the node tries to derive the type from its value. + */ + constructor( value, nodeType = null ) { + + super( value, nodeType ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isUniformNode = true; + + /** + * The name or label of the uniform. + * + * @type {string} + * @default '' + */ + this.name = ''; + + /** + * The uniform group of this uniform. By default, uniforms are + * managed per object but they might belong to a shared group + * which is updated per frame or render call. + * + * @type {UniformGroupNode} + */ + this.groupNode = objectGroup; + + } + + /** + * Sets the {@link UniformNode#name} property. + * + * @param {string} name - The name of the uniform. + * @return {UniformNode} A reference to this node. + */ + label( name ) { + + this.name = name; + + return this; + + } + + /** + * Sets the {@link UniformNode#groupNode} property. + * + * @param {UniformGroupNode} group - The uniform group. + * @return {UniformNode} A reference to this node. + */ + setGroup( group ) { + + this.groupNode = group; + + return this; + + } + + /** + * Returns the {@link UniformNode#groupNode}. + * + * @return {UniformGroupNode} The uniform group. + */ + getGroup() { + + return this.groupNode; + + } + + /** + * By default, this method returns the result of {@link Node#getHash} but derived + * classes might overwrite this method with a different implementation. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The uniform hash. + */ + getUniformHash( builder ) { + + return this.getHash( builder ); + + } + + onUpdate( callback, updateType ) { + + const self = this.getSelf(); + + callback = callback.bind( self ); + + return super.onUpdate( ( frame ) => { + + const value = callback( frame, self ); + + if ( value !== undefined ) { + + this.value = value; + + } + + }, updateType ); + + } + + generate( builder, output ) { + + const type = this.getNodeType( builder ); + + const hash = this.getUniformHash( builder ); + + let sharedNode = builder.getNodeFromHash( hash ); + + if ( sharedNode === undefined ) { + + builder.setHashNode( this, hash ); + + sharedNode = this; + + } + + const sharedNodeType = sharedNode.getInputType( builder ); + + const nodeUniform = builder.getUniformFromNode( sharedNode, sharedNodeType, builder.shaderStage, this.name || builder.context.label ); + const propertyName = builder.getPropertyName( nodeUniform ); + + if ( builder.context.label !== undefined ) delete builder.context.label; + + return builder.format( propertyName, type, output ); + + } + +} + +/** + * TSL function for creating a uniform node. + * + * @tsl + * @function + * @param {any} arg1 - The value of this node. Usually a JS primitive or three.js object (vector, matrix, color, texture). + * @param {string} [arg2] - The node type. If no explicit type is defined, the node tries to derive the type from its value. + * @returns {UniformNode} + */ +const uniform = ( arg1, arg2 ) => { + + const nodeType = getConstNodeType( arg2 || arg1 ); + + // @TODO: get ConstNode from .traverse() in the future + const value = ( arg1 && arg1.isNode === true ) ? ( arg1.node && arg1.node.value ) || arg1.value : arg1; + + return nodeObject( new UniformNode( value, nodeType ) ); + +}; + +/** + * ArrayNode represents a collection of nodes, typically created using the {@link array} function. + * ```js + * const colors = array( [ + * vec3( 1, 0, 0 ), + * vec3( 0, 1, 0 ), + * vec3( 0, 0, 1 ) + * ] ); + * + * const redColor = tintColors.element( 0 ); + * + * @augments TempNode + */ +class ArrayNode extends TempNode { + + static get type() { + + return 'ArrayNode'; + + } + + /** + * Constructs a new array node. + * + * @param {?string} nodeType - The data type of the elements. + * @param {number} count - Size of the array. + * @param {?Array} [values=null] - Array default values. + */ + constructor( nodeType, count, values = null ) { + + super( nodeType ); + + /** + * Array size. + * + * @type {number} + */ + this.count = count; + + /** + * Array default values. + * + * @type {?Array} + */ + this.values = values; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isArrayNode = true; + + } + + /** + * Returns the node's type. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The type of the node. + */ + getNodeType( builder ) { + + if ( this.nodeType === null ) { + + this.nodeType = this.values[ 0 ].getNodeType( builder ); + + } + + return this.nodeType; + + } + + /** + * Returns the node's type. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The type of the node. + */ + getElementType( builder ) { + + return this.getNodeType( builder ); + + } + + /** + * This method builds the output node and returns the resulting array as a shader string. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The generated shader string. + */ + generate( builder ) { + + const type = this.getNodeType( builder ); + + return builder.generateArray( type, this.count, this.values ); + + } + +} + +/** + * TSL function for creating an array node. + * + * @tsl + * @function + * @param {string|Array} nodeTypeOrValues - A string representing the element type (e.g., 'vec3') + * or an array containing the default values (e.g., [ vec3() ]). + * @param {?number} [count] - Size of the array. + * @returns {ArrayNode} + */ +const array = ( ...params ) => { + + let node; + + if ( params.length === 1 ) { + + const values = params[ 0 ]; + + node = new ArrayNode( null, values.length, values ); + + } else { + + const nodeType = params[ 0 ]; + const count = params[ 1 ]; + + node = new ArrayNode( nodeType, count ); + + } + + return nodeObject( node ); + +}; + +addMethodChaining( 'toArray', ( node, count ) => array( Array( count ).fill( node ) ) ); + +/** + * These node represents an assign operation. Meaning a node is assigned + * to another node. + * + * @augments TempNode + */ +class AssignNode extends TempNode { + + static get type() { + + return 'AssignNode'; + + } + + /** + * Constructs a new assign node. + * + * @param {Node} targetNode - The target node. + * @param {Node} sourceNode - The source type. + */ + constructor( targetNode, sourceNode ) { + + super(); + + /** + * The target node. + * + * @type {Node} + */ + this.targetNode = targetNode; + + /** + * The source node. + * + * @type {Node} + */ + this.sourceNode = sourceNode; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isAssignNode = true; + + } + + /** + * Whether this node is used more than once in context of other nodes. This method + * is overwritten since it always returns `false` (assigns are unique). + * + * @return {boolean} A flag that indicates if there is more than one dependency to other nodes. Always `false`. + */ + hasDependencies() { + + return false; + + } + + getNodeType( builder, output ) { + + return output !== 'void' ? this.targetNode.getNodeType( builder ) : 'void'; + + } + + /** + * Whether a split is required when assigning source to target. This can happen when the component length of + * target and source data type does not match. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {boolean} Whether a split is required when assigning source to target. + */ + needsSplitAssign( builder ) { + + const { targetNode } = this; + + if ( builder.isAvailable( 'swizzleAssign' ) === false && targetNode.isSplitNode && targetNode.components.length > 1 ) { + + const targetLength = builder.getTypeLength( targetNode.node.getNodeType( builder ) ); + const assignDifferentVector = vectorComponents.join( '' ).slice( 0, targetLength ) !== targetNode.components; + + return assignDifferentVector; + + } + + return false; + + } + + setup( builder ) { + + const { targetNode, sourceNode } = this; + + const properties = builder.getNodeProperties( this ); + properties.sourceNode = sourceNode; + properties.targetNode = targetNode.context( { assign: true } ); + + } + + generate( builder, output ) { + + const { targetNode, sourceNode } = builder.getNodeProperties( this ); + + const needsSplitAssign = this.needsSplitAssign( builder ); + + const targetType = targetNode.getNodeType( builder ); + + const target = targetNode.build( builder ); + const source = sourceNode.build( builder, targetType ); + + const sourceType = sourceNode.getNodeType( builder ); + + const nodeData = builder.getDataFromNode( this ); + + // + + let snippet; + + if ( nodeData.initialized === true ) { + + if ( output !== 'void' ) { + + snippet = target; + + } + + } else if ( needsSplitAssign ) { + + const sourceVar = builder.getVarFromNode( this, null, targetType ); + const sourceProperty = builder.getPropertyName( sourceVar ); + + builder.addLineFlowCode( `${ sourceProperty } = ${ source }`, this ); + + const splitNode = targetNode.node; + const splitTargetNode = splitNode.node.context( { assign: true } ); + + const targetRoot = splitTargetNode.build( builder ); + + for ( let i = 0; i < splitNode.components.length; i ++ ) { + + const component = splitNode.components[ i ]; + + builder.addLineFlowCode( `${ targetRoot }.${ component } = ${ sourceProperty }[ ${ i } ]`, this ); + + } + + if ( output !== 'void' ) { + + snippet = target; + + } + + } else { + + snippet = `${ target } = ${ source }`; + + if ( output === 'void' || sourceType === 'void' ) { + + builder.addLineFlowCode( snippet, this ); + + if ( output !== 'void' ) { + + snippet = target; + + } + + } + + } + + nodeData.initialized = true; + + return builder.format( snippet, targetType, output ); + + } + +} + +/** + * TSL function for creating an assign node. + * + * @tsl + * @function + * @param {Node} targetNode - The target node. + * @param {Node} sourceNode - The source type. + * @returns {AssignNode} + */ +const assign = /*@__PURE__*/ nodeProxy( AssignNode ).setParameterLength( 2 ); + +addMethodChaining( 'assign', assign ); + +/** + * This module represents the call of a {@link FunctionNode}. Developers are usually not confronted + * with this module since they use the predefined TSL syntax `wgslFn` and `glslFn` which encapsulate + * this logic. + * + * @augments TempNode + */ +class FunctionCallNode extends TempNode { + + static get type() { + + return 'FunctionCallNode'; + + } + + /** + * Constructs a new function call node. + * + * @param {?FunctionNode} functionNode - The function node. + * @param {Object} [parameters={}] - The parameters for the function call. + */ + constructor( functionNode = null, parameters = {} ) { + + super(); + + /** + * The function node. + * + * @type {?FunctionNode} + * @default null + */ + this.functionNode = functionNode; + + /** + * The parameters of the function call. + * + * @type {Object} + * @default {} + */ + this.parameters = parameters; + + } + + /** + * Sets the parameters of the function call node. + * + * @param {Object} parameters - The parameters to set. + * @return {FunctionCallNode} A reference to this node. + */ + setParameters( parameters ) { + + this.parameters = parameters; + + return this; + + } + + /** + * Returns the parameters of the function call node. + * + * @return {Object} The parameters of this node. + */ + getParameters() { + + return this.parameters; + + } + + getNodeType( builder ) { + + return this.functionNode.getNodeType( builder ); + + } + + generate( builder ) { + + const params = []; + + const functionNode = this.functionNode; + + const inputs = functionNode.getInputs( builder ); + const parameters = this.parameters; + + const generateInput = ( node, inputNode ) => { + + const type = inputNode.type; + const pointer = type === 'pointer'; + + let output; + + if ( pointer ) output = '&' + node.build( builder ); + else output = node.build( builder, type ); + + return output; + + }; + + if ( Array.isArray( parameters ) ) { + + if ( parameters.length > inputs.length ) { + + console.error( 'THREE.TSL: The number of provided parameters exceeds the expected number of inputs in \'Fn()\'.' ); + + parameters.length = inputs.length; + + } else if ( parameters.length < inputs.length ) { + + console.error( 'THREE.TSL: The number of provided parameters is less than the expected number of inputs in \'Fn()\'.' ); + + while ( parameters.length < inputs.length ) { + + parameters.push( float( 0 ) ); + + } + + } + + for ( let i = 0; i < parameters.length; i ++ ) { + + params.push( generateInput( parameters[ i ], inputs[ i ] ) ); + + } + + } else { + + for ( const inputNode of inputs ) { + + const node = parameters[ inputNode.name ]; + + if ( node !== undefined ) { + + params.push( generateInput( node, inputNode ) ); + + } else { + + console.error( `THREE.TSL: Input '${ inputNode.name }' not found in \'Fn()\'.` ); + + params.push( generateInput( float( 0 ), inputNode ) ); + + } + + } + + } + + const functionName = functionNode.build( builder, 'property' ); + + return `${ functionName }( ${ params.join( ', ' ) } )`; + + } + +} + +const call = ( func, ...params ) => { + + params = params.length > 1 || ( params[ 0 ] && params[ 0 ].isNode === true ) ? nodeArray( params ) : nodeObjects( params[ 0 ] ); + + return nodeObject( new FunctionCallNode( nodeObject( func ), params ) ); + +}; + +addMethodChaining( 'call', call ); + +const _vectorOperators = { + '==': 'equal', + '!=': 'notEqual', + '<': 'lessThan', + '>': 'greaterThan', + '<=': 'lessThanEqual', + '>=': 'greaterThanEqual', + '%': 'mod' +}; + +/** + * This node represents basic mathematical and logical operations like addition, + * subtraction or comparisons (e.g. `equal()`). + * + * @augments TempNode + */ +class OperatorNode extends TempNode { + + static get type() { + + return 'OperatorNode'; + + } + + /** + * Constructs a new operator node. + * + * @param {string} op - The operator. + * @param {Node} aNode - The first input. + * @param {Node} bNode - The second input. + * @param {...Node} params - Additional input parameters. + */ + constructor( op, aNode, bNode, ...params ) { + + super(); + + if ( params.length > 0 ) { + + let finalOp = new OperatorNode( op, aNode, bNode ); + + for ( let i = 0; i < params.length - 1; i ++ ) { + + finalOp = new OperatorNode( op, finalOp, params[ i ] ); + + } + + aNode = finalOp; + bNode = params[ params.length - 1 ]; + + } + + /** + * The operator. + * + * @type {string} + */ + this.op = op; + + /** + * The first input. + * + * @type {Node} + */ + this.aNode = aNode; + + /** + * The second input. + * + * @type {Node} + */ + this.bNode = bNode; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isOperatorNode = true; + + } + + /** + * Returns the operator method name. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {string} output - The output type. + * @returns {string} The operator method name. + */ + getOperatorMethod( builder, output ) { + + return builder.getMethod( _vectorOperators[ this.op ], output ); + + } + + /** + * This method is overwritten since the node type is inferred from the operator + * and the input node types. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType( builder ) { + + const op = this.op; + + const aNode = this.aNode; + const bNode = this.bNode; + + const typeA = aNode.getNodeType( builder ); + const typeB = bNode ? bNode.getNodeType( builder ) : null; + + if ( typeA === 'void' || typeB === 'void' ) { + + return 'void'; + + } else if ( op === '%' ) { + + return typeA; + + } else if ( op === '~' || op === '&' || op === '|' || op === '^' || op === '>>' || op === '<<' ) { + + return builder.getIntegerType( typeA ); + + } else if ( op === '!' || op === '&&' || op === '||' || op === '^^' ) { + + return 'bool'; + + } else if ( op === '==' || op === '!=' || op === '<' || op === '>' || op === '<=' || op === '>=' ) { + + const typeLength = Math.max( builder.getTypeLength( typeA ), builder.getTypeLength( typeB ) ); + + return typeLength > 1 ? `bvec${ typeLength }` : 'bool'; + + } else { + + // Handle matrix operations + + if ( builder.isMatrix( typeA ) ) { + + if ( typeB === 'float' ) { + + return typeA; // matrix * scalar = matrix + + } else if ( builder.isVector( typeB ) ) { + + return builder.getVectorFromMatrix( typeA ); // matrix * vector + + } else if ( builder.isMatrix( typeB ) ) { + + return typeA; // matrix * matrix + + } + + } else if ( builder.isMatrix( typeB ) ) { + + if ( typeA === 'float' ) { + + return typeB; // scalar * matrix = matrix + + } else if ( builder.isVector( typeA ) ) { + + return builder.getVectorFromMatrix( typeB ); // vector * matrix + + } + + } + + // Handle non-matrix cases + + if ( builder.getTypeLength( typeB ) > builder.getTypeLength( typeA ) ) { + + // anytype x anytype: use the greater length vector + + return typeB; + + } + + return typeA; + + } + + } + + generate( builder, output ) { + + const op = this.op; + + const { aNode, bNode } = this; + + const type = this.getNodeType( builder ); + + let typeA = null; + let typeB = null; + + if ( type !== 'void' ) { + + typeA = aNode.getNodeType( builder ); + typeB = bNode ? bNode.getNodeType( builder ) : null; + + if ( op === '<' || op === '>' || op === '<=' || op === '>=' || op === '==' || op === '!=' ) { + + if ( builder.isVector( typeA ) ) { + + typeB = typeA; + + } else if ( builder.isVector( typeB ) ) { + + typeA = typeB; + + } else if ( typeA !== typeB ) { + + typeA = typeB = 'float'; + + } + + } else if ( op === '>>' || op === '<<' ) { + + typeA = type; + typeB = builder.changeComponentType( typeB, 'uint' ); + + } else if ( op === '%' ) { + + typeA = type; + typeB = builder.isInteger( typeA ) && builder.isInteger( typeB ) ? typeB : typeA; + + } else if ( builder.isMatrix( typeA ) ) { + + if ( typeB === 'float' ) { + + // Keep matrix type for typeA, but ensure typeB stays float + + typeB = 'float'; + + } else if ( builder.isVector( typeB ) ) { + + // matrix x vector + typeB = builder.getVectorFromMatrix( typeA ); + + } else if ( builder.isMatrix( typeB ) ) ; else { + + typeA = typeB = type; + + } + + } else if ( builder.isMatrix( typeB ) ) { + + if ( typeA === 'float' ) { + + // Keep matrix type for typeB, but ensure typeA stays float + + typeA = 'float'; + + } else if ( builder.isVector( typeA ) ) { + + // vector x matrix + + typeA = builder.getVectorFromMatrix( typeB ); + + } else { + + typeA = typeB = type; + + } + + } else { + + // anytype x anytype + + typeA = typeB = type; + + } + + } else { + + typeA = typeB = type; + + } + + const a = aNode.build( builder, typeA ); + const b = bNode ? bNode.build( builder, typeB ) : null; + + const fnOpSnippet = builder.getFunctionOperator( op ); + + if ( output !== 'void' ) { + + const isGLSL = builder.renderer.coordinateSystem === WebGLCoordinateSystem; + + if ( op === '==' || op === '!=' || op === '<' || op === '>' || op === '<=' || op === '>=' ) { + + if ( isGLSL ) { + + if ( builder.isVector( typeA ) ) { + + return builder.format( `${ this.getOperatorMethod( builder, output ) }( ${ a }, ${ b } )`, type, output ); + + } else { + + return builder.format( `( ${ a } ${ op } ${ b } )`, type, output ); + + } + + } else { + + // WGSL + + return builder.format( `( ${ a } ${ op } ${ b } )`, type, output ); + + } + + } else if ( op === '%' ) { + + if ( builder.isInteger( typeB ) ) { + + return builder.format( `( ${ a } % ${ b } )`, type, output ); + + } else { + + return builder.format( `${ this.getOperatorMethod( builder, type ) }( ${ a }, ${ b } )`, type, output ); + + } + + } else if ( op === '!' || op === '~' ) { + + return builder.format( `(${op}${a})`, typeA, output ); + + } else if ( fnOpSnippet ) { + + return builder.format( `${ fnOpSnippet }( ${ a }, ${ b } )`, type, output ); + + } else { + + // Handle matrix operations + + if ( builder.isMatrix( typeA ) && typeB === 'float' ) { + + return builder.format( `( ${ b } ${ op } ${ a } )`, type, output ); + + } else if ( typeA === 'float' && builder.isMatrix( typeB ) ) { + + return builder.format( `${ a } ${ op } ${ b }`, type, output ); + + } else { + + let snippet = `( ${ a } ${ op } ${ b } )`; + + if ( ! isGLSL && type === 'bool' && builder.isVector( typeA ) && builder.isVector( typeB ) ) { + + snippet = `all${ snippet }`; + + } + + return builder.format( snippet, type, output ); + + } + + } + + } else if ( typeA !== 'void' ) { + + if ( fnOpSnippet ) { + + return builder.format( `${ fnOpSnippet }( ${ a }, ${ b } )`, type, output ); + + } else { + + if ( builder.isMatrix( typeA ) && typeB === 'float' ) { + + return builder.format( `${ b } ${ op } ${ a }`, type, output ); + + } else { + + return builder.format( `${ a } ${ op } ${ b }`, type, output ); + + } + + } + + } + + } + + serialize( data ) { + + super.serialize( data ); + + data.op = this.op; + + } + + deserialize( data ) { + + super.deserialize( data ); + + this.op = data.op; + + } + +} + +/** + * Returns the addition of two or more value. + * + * @tsl + * @function + * @param {Node} a - The first input. + * @param {Node} b - The second input. + * @param {...Node} params - Additional input parameters. + * @returns {OperatorNode} + */ +const add = /*@__PURE__*/ nodeProxy( OperatorNode, '+' ).setParameterLength( 2, Infinity ).setName( 'add' ); + +/** + * Returns the subtraction of two or more value. + * + * @tsl + * @function + * @param {Node} a - The first input. + * @param {Node} b - The second input. + * @param {...Node} params - Additional input parameters. + * @returns {OperatorNode} + */ +const sub = /*@__PURE__*/ nodeProxy( OperatorNode, '-' ).setParameterLength( 2, Infinity ).setName( 'sub' ); + +/** + * Returns the multiplication of two or more value. + * + * @tsl + * @function + * @param {Node} a - The first input. + * @param {Node} b - The second input. + * @param {...Node} params - Additional input parameters. + * @returns {OperatorNode} + */ +const mul = /*@__PURE__*/ nodeProxy( OperatorNode, '*' ).setParameterLength( 2, Infinity ).setName( 'mul' ); + +/** + * Returns the division of two or more value. + * + * @tsl + * @function + * @param {Node} a - The first input. + * @param {Node} b - The second input. + * @param {...Node} params - Additional input parameters. + * @returns {OperatorNode} + */ +const div = /*@__PURE__*/ nodeProxy( OperatorNode, '/' ).setParameterLength( 2, Infinity ).setName( 'div' ); + +/** + * Computes the remainder of dividing the first node by the second one. + * + * @tsl + * @function + * @param {Node} a - The first input. + * @param {Node} b - The second input. + * @returns {OperatorNode} + */ +const mod = /*@__PURE__*/ nodeProxy( OperatorNode, '%' ).setParameterLength( 2 ).setName( 'mod' ); + +/** + * Checks if two nodes are equal. + * + * @tsl + * @function + * @param {Node} a - The first input. + * @param {Node} b - The second input. + * @returns {OperatorNode} + */ +const equal = /*@__PURE__*/ nodeProxy( OperatorNode, '==' ).setParameterLength( 2 ).setName( 'equal' ); + +/** + * Checks if two nodes are not equal. + * + * @tsl + * @function + * @param {Node} a - The first input. + * @param {Node} b - The second input. + * @returns {OperatorNode} + */ +const notEqual = /*@__PURE__*/ nodeProxy( OperatorNode, '!=' ).setParameterLength( 2 ).setName( 'notEqual' ); + +/** + * Checks if the first node is less than the second. + * + * @tsl + * @function + * @param {Node} a - The first input. + * @param {Node} b - The second input. + * @returns {OperatorNode} + */ +const lessThan = /*@__PURE__*/ nodeProxy( OperatorNode, '<' ).setParameterLength( 2 ).setName( 'lessThan' ); + +/** + * Checks if the first node is greater than the second. + * + * @tsl + * @function + * @param {Node} a - The first input. + * @param {Node} b - The second input. + * @returns {OperatorNode} + */ +const greaterThan = /*@__PURE__*/ nodeProxy( OperatorNode, '>' ).setParameterLength( 2 ).setName( 'greaterThan' ); + +/** + * Checks if the first node is less than or equal to the second. + * + * @tsl + * @function + * @param {Node} a - The first input. + * @param {Node} b - The second input. + * @returns {OperatorNode} + */ +const lessThanEqual = /*@__PURE__*/ nodeProxy( OperatorNode, '<=' ).setParameterLength( 2 ).setName( 'lessThanEqual' ); + +/** + * Checks if the first node is greater than or equal to the second. + * + * @tsl + * @function + * @param {Node} a - The first input. + * @param {Node} b - The second input. + * @returns {OperatorNode} + */ +const greaterThanEqual = /*@__PURE__*/ nodeProxy( OperatorNode, '>=' ).setParameterLength( 2 ).setName( 'greaterThanEqual' ); + +/** + * Performs a logical AND operation on multiple nodes. + * + * @tsl + * @function + * @param {...Node} nodes - The input nodes to be combined using AND. + * @returns {OperatorNode} + */ +const and = /*@__PURE__*/ nodeProxy( OperatorNode, '&&' ).setParameterLength( 2, Infinity ).setName( 'and' ); + +/** + * Performs a logical OR operation on multiple nodes. + * + * @tsl + * @function + * @param {...Node} nodes - The input nodes to be combined using OR. + * @returns {OperatorNode} + */ +const or = /*@__PURE__*/ nodeProxy( OperatorNode, '||' ).setParameterLength( 2, Infinity ).setName( 'or' ); + +/** + * Performs logical NOT on a node. + * + * @tsl + * @function + * @param {Node} value - The value. + * @returns {OperatorNode} + */ +const not = /*@__PURE__*/ nodeProxy( OperatorNode, '!' ).setParameterLength( 1 ).setName( 'not' ); + +/** + * Performs logical XOR on two nodes. + * + * @tsl + * @function + * @param {Node} a - The first input. + * @param {Node} b - The second input. + * @returns {OperatorNode} + */ +const xor = /*@__PURE__*/ nodeProxy( OperatorNode, '^^' ).setParameterLength( 2 ).setName( 'xor' ); + +/** + * Performs bitwise AND on two nodes. + * + * @tsl + * @function + * @param {Node} a - The first input. + * @param {Node} b - The second input. + * @returns {OperatorNode} + */ +const bitAnd = /*@__PURE__*/ nodeProxy( OperatorNode, '&' ).setParameterLength( 2 ).setName( 'bitAnd' ); + +/** + * Performs bitwise NOT on a node. + * + * @tsl + * @function + * @param {Node} a - The first input. + * @param {Node} b - The second input. + * @returns {OperatorNode} + */ +const bitNot = /*@__PURE__*/ nodeProxy( OperatorNode, '~' ).setParameterLength( 2 ).setName( 'bitNot' ); + +/** + * Performs bitwise OR on two nodes. + * + * @tsl + * @function + * @param {Node} a - The first input. + * @param {Node} b - The second input. + * @returns {OperatorNode} + */ +const bitOr = /*@__PURE__*/ nodeProxy( OperatorNode, '|' ).setParameterLength( 2 ).setName( 'bitOr' ); + +/** + * Performs bitwise XOR on two nodes. + * + * @tsl + * @function + * @param {Node} a - The first input. + * @param {Node} b - The second input. + * @returns {OperatorNode} + */ +const bitXor = /*@__PURE__*/ nodeProxy( OperatorNode, '^' ).setParameterLength( 2 ).setName( 'bitXor' ); + +/** + * Shifts a node to the left. + * + * @tsl + * @function + * @param {Node} a - The node to shift. + * @param {Node} b - The value to shift. + * @returns {OperatorNode} + */ +const shiftLeft = /*@__PURE__*/ nodeProxy( OperatorNode, '<<' ).setParameterLength( 2 ).setName( 'shiftLeft' ); + +/** + * Shifts a node to the right. + * + * @tsl + * @function + * @param {Node} a - The node to shift. + * @param {Node} b - The value to shift. + * @returns {OperatorNode} + */ +const shiftRight = /*@__PURE__*/ nodeProxy( OperatorNode, '>>' ).setParameterLength( 2 ).setName( 'shiftRight' ); + +/** + * Increments a node by 1. + * + * @tsl + * @function + * @param {Node} a - The node to increment. + * @returns {OperatorNode} + */ +const incrementBefore = Fn( ( [ a ] ) => { + + a.addAssign( 1 ); + return a; + +} ); + +/** + * Decrements a node by 1. + * + * @tsl + * @function + * @param {Node} a - The node to decrement. + * @returns {OperatorNode} + */ +const decrementBefore = Fn( ( [ a ] ) => { + + a.subAssign( 1 ); + return a; + +} ); + +/** + * Increments a node by 1 and returns the previous value. + * + * @tsl + * @function + * @param {Node} a - The node to increment. + * @returns {OperatorNode} + */ +const increment = /*@__PURE__*/ Fn( ( [ a ] ) => { + + const temp = int( a ).toConst(); + a.addAssign( 1 ); + return temp; + +} ); + +/** + * Decrements a node by 1 and returns the previous value. + * + * @tsl + * @function + * @param {Node} a - The node to decrement. + * @returns {OperatorNode} + */ +const decrement = /*@__PURE__*/ Fn( ( [ a ] ) => { + + const temp = int( a ).toConst(); + a.subAssign( 1 ); + return temp; + +} ); + +addMethodChaining( 'add', add ); +addMethodChaining( 'sub', sub ); +addMethodChaining( 'mul', mul ); +addMethodChaining( 'div', div ); +addMethodChaining( 'mod', mod ); +addMethodChaining( 'equal', equal ); +addMethodChaining( 'notEqual', notEqual ); +addMethodChaining( 'lessThan', lessThan ); +addMethodChaining( 'greaterThan', greaterThan ); +addMethodChaining( 'lessThanEqual', lessThanEqual ); +addMethodChaining( 'greaterThanEqual', greaterThanEqual ); +addMethodChaining( 'and', and ); +addMethodChaining( 'or', or ); +addMethodChaining( 'not', not ); +addMethodChaining( 'xor', xor ); +addMethodChaining( 'bitAnd', bitAnd ); +addMethodChaining( 'bitNot', bitNot ); +addMethodChaining( 'bitOr', bitOr ); +addMethodChaining( 'bitXor', bitXor ); +addMethodChaining( 'shiftLeft', shiftLeft ); +addMethodChaining( 'shiftRight', shiftRight ); + +addMethodChaining( 'incrementBefore', incrementBefore ); +addMethodChaining( 'decrementBefore', decrementBefore ); +addMethodChaining( 'increment', increment ); +addMethodChaining( 'decrement', decrement ); + +/** + * @tsl + * @function + * @deprecated since r175. Use {@link mod} instead. + * + * @param {Node} a - The first input. + * @param {Node} b - The second input. + * @returns {OperatorNode} + */ +const modInt = ( a, b ) => { // @deprecated, r175 + + console.warn( 'THREE.TSL: "modInt()" is deprecated. Use "mod( int( ... ) )" instead.' ); + return mod( int( a ), int( b ) ); + +}; + +addMethodChaining( 'modInt', modInt ); + +/** + * This node represents a variety of mathematical methods available in shaders. + * They are divided into three categories: + * + * - Methods with one input like `sin`, `cos` or `normalize`. + * - Methods with two inputs like `dot`, `cross` or `pow`. + * - Methods with three inputs like `mix`, `clamp` or `smoothstep`. + * + * @augments TempNode + */ +class MathNode extends TempNode { + + static get type() { + + return 'MathNode'; + + } + + /** + * Constructs a new math node. + * + * @param {string} method - The method name. + * @param {Node} aNode - The first input. + * @param {?Node} [bNode=null] - The second input. + * @param {?Node} [cNode=null] - The third input. + */ + constructor( method, aNode, bNode = null, cNode = null ) { + + super(); + + // Allow the max() and min() functions to take an arbitrary number of arguments. + + if ( ( method === MathNode.MAX || method === MathNode.MIN ) && arguments.length > 3 ) { + + let finalOp = new MathNode( method, aNode, bNode ); + + for ( let i = 2; i < arguments.length - 1; i ++ ) { + + finalOp = new MathNode( method, finalOp, arguments[ i ] ); + + } + + aNode = finalOp; + bNode = arguments[ arguments.length - 1 ]; + cNode = null; + + } + + /** + * The method name. + * + * @type {string} + */ + this.method = method; + + /** + * The first input. + * + * @type {Node} + */ + this.aNode = aNode; + + /** + * The second input. + * + * @type {?Node} + * @default null + */ + this.bNode = bNode; + + /** + * The third input. + * + * @type {?Node} + * @default null + */ + this.cNode = cNode; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMathNode = true; + + } + + /** + * The input type is inferred from the node types of the input nodes. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The input type. + */ + getInputType( builder ) { + + const aType = this.aNode.getNodeType( builder ); + const bType = this.bNode ? this.bNode.getNodeType( builder ) : null; + const cType = this.cNode ? this.cNode.getNodeType( builder ) : null; + + const aLen = builder.isMatrix( aType ) ? 0 : builder.getTypeLength( aType ); + const bLen = builder.isMatrix( bType ) ? 0 : builder.getTypeLength( bType ); + const cLen = builder.isMatrix( cType ) ? 0 : builder.getTypeLength( cType ); + + if ( aLen > bLen && aLen > cLen ) { + + return aType; + + } else if ( bLen > cLen ) { + + return bType; + + } else if ( cLen > aLen ) { + + return cType; + + } + + return aType; + + } + + /** + * The selected method as well as the input type determine the node type of this node. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType( builder ) { + + const method = this.method; + + if ( method === MathNode.LENGTH || method === MathNode.DISTANCE || method === MathNode.DOT ) { + + return 'float'; + + } else if ( method === MathNode.CROSS ) { + + return 'vec3'; + + } else if ( method === MathNode.ALL || method === MathNode.ANY ) { + + return 'bool'; + + } else if ( method === MathNode.EQUALS ) { + + return builder.changeComponentType( this.aNode.getNodeType( builder ), 'bool' ); + + } else { + + return this.getInputType( builder ); + + } + + } + + setup( builder ) { + + const { aNode, bNode, method } = this; + + let outputNode = null; + + if ( method === MathNode.ONE_MINUS ) { + + outputNode = sub( 1.0, aNode ); + + } else if ( method === MathNode.RECIPROCAL ) { + + outputNode = div( 1.0, aNode ); + + } else if ( method === MathNode.DIFFERENCE ) { + + outputNode = abs( sub( aNode, bNode ) ); + + } else if ( method === MathNode.TRANSFORM_DIRECTION ) { + + // dir can be either a direction vector or a normal vector + // upper-left 3x3 of matrix is assumed to be orthogonal + + let tA = aNode; + let tB = bNode; + + if ( builder.isMatrix( tA.getNodeType( builder ) ) ) { + + tB = vec4( vec3( tB ), 0.0 ); + + } else { + + tA = vec4( vec3( tA ), 0.0 ); + + } + + const mulNode = mul( tA, tB ).xyz; + + outputNode = normalize( mulNode ); + + } + + if ( outputNode !== null ) { + + return outputNode; + + } else { + + return super.setup( builder ); + + } + + } + + generate( builder, output ) { + + const properties = builder.getNodeProperties( this ); + + if ( properties.outputNode ) { + + return super.generate( builder, output ); + + } + + let method = this.method; + + const type = this.getNodeType( builder ); + const inputType = this.getInputType( builder ); + + const a = this.aNode; + const b = this.bNode; + const c = this.cNode; + + const coordinateSystem = builder.renderer.coordinateSystem; + + if ( method === MathNode.NEGATE ) { + + return builder.format( '( - ' + a.build( builder, inputType ) + ' )', type, output ); + + } else { + + const params = []; + + if ( method === MathNode.CROSS ) { + + params.push( + a.build( builder, type ), + b.build( builder, type ) + ); + + } else if ( coordinateSystem === WebGLCoordinateSystem && method === MathNode.STEP ) { + + params.push( + a.build( builder, builder.getTypeLength( a.getNodeType( builder ) ) === 1 ? 'float' : inputType ), + b.build( builder, inputType ) + ); + + } else if ( coordinateSystem === WebGLCoordinateSystem && ( method === MathNode.MIN || method === MathNode.MAX ) ) { + + params.push( + a.build( builder, inputType ), + b.build( builder, builder.getTypeLength( b.getNodeType( builder ) ) === 1 ? 'float' : inputType ) + ); + + } else if ( method === MathNode.REFRACT ) { + + params.push( + a.build( builder, inputType ), + b.build( builder, inputType ), + c.build( builder, 'float' ) + ); + + } else if ( method === MathNode.MIX ) { + + params.push( + a.build( builder, inputType ), + b.build( builder, inputType ), + c.build( builder, builder.getTypeLength( c.getNodeType( builder ) ) === 1 ? 'float' : inputType ) + ); + + } else { + + if ( coordinateSystem === WebGPUCoordinateSystem && method === MathNode.ATAN && b !== null ) { + + method = 'atan2'; + + } + + if ( builder.shaderStage !== 'fragment' && ( method === MathNode.DFDX || method === MathNode.DFDY ) ) { + + console.warn( `THREE.TSL: '${ method }' is not supported in the ${ builder.shaderStage } stage.` ); + + method = '/*' + method + '*/'; + + } + + params.push( a.build( builder, inputType ) ); + if ( b !== null ) params.push( b.build( builder, inputType ) ); + if ( c !== null ) params.push( c.build( builder, inputType ) ); + + } + + return builder.format( `${ builder.getMethod( method, type ) }( ${params.join( ', ' )} )`, type, output ); + + } + + } + + serialize( data ) { + + super.serialize( data ); + + data.method = this.method; + + } + + deserialize( data ) { + + super.deserialize( data ); + + this.method = data.method; + + } + +} + +// 1 input + +MathNode.ALL = 'all'; +MathNode.ANY = 'any'; + +MathNode.RADIANS = 'radians'; +MathNode.DEGREES = 'degrees'; +MathNode.EXP = 'exp'; +MathNode.EXP2 = 'exp2'; +MathNode.LOG = 'log'; +MathNode.LOG2 = 'log2'; +MathNode.SQRT = 'sqrt'; +MathNode.INVERSE_SQRT = 'inversesqrt'; +MathNode.FLOOR = 'floor'; +MathNode.CEIL = 'ceil'; +MathNode.NORMALIZE = 'normalize'; +MathNode.FRACT = 'fract'; +MathNode.SIN = 'sin'; +MathNode.COS = 'cos'; +MathNode.TAN = 'tan'; +MathNode.ASIN = 'asin'; +MathNode.ACOS = 'acos'; +MathNode.ATAN = 'atan'; +MathNode.ABS = 'abs'; +MathNode.SIGN = 'sign'; +MathNode.LENGTH = 'length'; +MathNode.NEGATE = 'negate'; +MathNode.ONE_MINUS = 'oneMinus'; +MathNode.DFDX = 'dFdx'; +MathNode.DFDY = 'dFdy'; +MathNode.ROUND = 'round'; +MathNode.RECIPROCAL = 'reciprocal'; +MathNode.TRUNC = 'trunc'; +MathNode.FWIDTH = 'fwidth'; +MathNode.TRANSPOSE = 'transpose'; + +// 2 inputs + +MathNode.BITCAST = 'bitcast'; +MathNode.EQUALS = 'equals'; +MathNode.MIN = 'min'; +MathNode.MAX = 'max'; +MathNode.STEP = 'step'; +MathNode.REFLECT = 'reflect'; +MathNode.DISTANCE = 'distance'; +MathNode.DIFFERENCE = 'difference'; +MathNode.DOT = 'dot'; +MathNode.CROSS = 'cross'; +MathNode.POW = 'pow'; +MathNode.TRANSFORM_DIRECTION = 'transformDirection'; + +// 3 inputs + +MathNode.MIX = 'mix'; +MathNode.CLAMP = 'clamp'; +MathNode.REFRACT = 'refract'; +MathNode.SMOOTHSTEP = 'smoothstep'; +MathNode.FACEFORWARD = 'faceforward'; + +// 1 inputs + +/** + * A small value used to handle floating-point precision errors. + * + * @tsl + * @type {Node} + */ +const EPSILON = /*@__PURE__*/ float( 1e-6 ); + +/** + * Represents infinity. + * + * @tsl + * @type {Node} + */ +const INFINITY = /*@__PURE__*/ float( 1e6 ); + +/** + * Represents PI. + * + * @tsl + * @type {Node} + */ +const PI = /*@__PURE__*/ float( Math.PI ); + +/** + * Represents PI * 2. + * + * @tsl + * @type {Node} + */ +const PI2 = /*@__PURE__*/ float( Math.PI * 2 ); + +/** + * Returns `true` if all components of `x` are `true`. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const all = /*@__PURE__*/ nodeProxy( MathNode, MathNode.ALL ).setParameterLength( 1 ); + +/** + * Returns `true` if any components of `x` are `true`. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const any = /*@__PURE__*/ nodeProxy( MathNode, MathNode.ANY ).setParameterLength( 1 ); + +/** + * Converts a quantity in degrees to radians. + * + * @tsl + * @function + * @param {Node | number} x - The input in degrees. + * @returns {Node} + */ +const radians = /*@__PURE__*/ nodeProxy( MathNode, MathNode.RADIANS ).setParameterLength( 1 ); + +/** + * Convert a quantity in radians to degrees. + * + * @tsl + * @function + * @param {Node | number} x - The input in radians. + * @returns {Node} + */ +const degrees = /*@__PURE__*/ nodeProxy( MathNode, MathNode.DEGREES ).setParameterLength( 1 ); + +/** + * Returns the natural exponentiation of the parameter. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const exp = /*@__PURE__*/ nodeProxy( MathNode, MathNode.EXP ).setParameterLength( 1 ); + +/** + * Returns 2 raised to the power of the parameter. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const exp2 = /*@__PURE__*/ nodeProxy( MathNode, MathNode.EXP2 ).setParameterLength( 1 ); + +/** + * Returns the natural logarithm of the parameter. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const log = /*@__PURE__*/ nodeProxy( MathNode, MathNode.LOG ).setParameterLength( 1 ); + +/** + * Returns the base 2 logarithm of the parameter. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const log2 = /*@__PURE__*/ nodeProxy( MathNode, MathNode.LOG2 ).setParameterLength( 1 ); + +/** + * Returns the square root of the parameter. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const sqrt = /*@__PURE__*/ nodeProxy( MathNode, MathNode.SQRT ).setParameterLength( 1 ); + +/** + * Returns the inverse of the square root of the parameter. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const inverseSqrt = /*@__PURE__*/ nodeProxy( MathNode, MathNode.INVERSE_SQRT ).setParameterLength( 1 ); + +/** + * Finds the nearest integer less than or equal to the parameter. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const floor = /*@__PURE__*/ nodeProxy( MathNode, MathNode.FLOOR ).setParameterLength( 1 ); + +/** + * Finds the nearest integer that is greater than or equal to the parameter. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const ceil = /*@__PURE__*/ nodeProxy( MathNode, MathNode.CEIL ).setParameterLength( 1 ); + +/** + * Calculates the unit vector in the same direction as the original vector. + * + * @tsl + * @function + * @param {Node} x - The input vector. + * @returns {Node} + */ +const normalize = /*@__PURE__*/ nodeProxy( MathNode, MathNode.NORMALIZE ).setParameterLength( 1 ); + +/** + * Computes the fractional part of the parameter. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const fract = /*@__PURE__*/ nodeProxy( MathNode, MathNode.FRACT ).setParameterLength( 1 ); + +/** + * Returns the sine of the parameter. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const sin = /*@__PURE__*/ nodeProxy( MathNode, MathNode.SIN ).setParameterLength( 1 ); + +/** + * Returns the cosine of the parameter. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const cos = /*@__PURE__*/ nodeProxy( MathNode, MathNode.COS ).setParameterLength( 1 ); + +/** + * Returns the tangent of the parameter. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const tan = /*@__PURE__*/ nodeProxy( MathNode, MathNode.TAN ).setParameterLength( 1 ); + +/** + * Returns the arcsine of the parameter. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const asin = /*@__PURE__*/ nodeProxy( MathNode, MathNode.ASIN ).setParameterLength( 1 ); + +/** + * Returns the arccosine of the parameter. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const acos = /*@__PURE__*/ nodeProxy( MathNode, MathNode.ACOS ).setParameterLength( 1 ); + +/** + * Returns the arc-tangent of the parameter. + * If two parameters are provided, the result is `atan2(y/x)`. + * + * @tsl + * @function + * @param {Node | number} y - The y parameter. + * @param {?(Node | number)} x - The x parameter. + * @returns {Node} + */ +const atan = /*@__PURE__*/ nodeProxy( MathNode, MathNode.ATAN ).setParameterLength( 1, 2 ); + +/** + * Returns the absolute value of the parameter. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const abs = /*@__PURE__*/ nodeProxy( MathNode, MathNode.ABS ).setParameterLength( 1 ); + +/** + * Extracts the sign of the parameter. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const sign = /*@__PURE__*/ nodeProxy( MathNode, MathNode.SIGN ).setParameterLength( 1 ); + +/** + * Calculates the length of a vector. + * + * @tsl + * @function + * @param {Node} x - The parameter. + * @returns {Node} + */ +const length = /*@__PURE__*/ nodeProxy( MathNode, MathNode.LENGTH ).setParameterLength( 1 ); + +/** + * Negates the value of the parameter (-x). + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const negate = /*@__PURE__*/ nodeProxy( MathNode, MathNode.NEGATE ).setParameterLength( 1 ); + +/** + * Return `1` minus the parameter. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const oneMinus = /*@__PURE__*/ nodeProxy( MathNode, MathNode.ONE_MINUS ).setParameterLength( 1 ); + +/** + * Returns the partial derivative of the parameter with respect to x. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const dFdx = /*@__PURE__*/ nodeProxy( MathNode, MathNode.DFDX ).setParameterLength( 1 ); + +/** + * Returns the partial derivative of the parameter with respect to y. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const dFdy = /*@__PURE__*/ nodeProxy( MathNode, MathNode.DFDY ).setParameterLength( 1 ); + +/** + * Rounds the parameter to the nearest integer. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const round = /*@__PURE__*/ nodeProxy( MathNode, MathNode.ROUND ).setParameterLength( 1 ); + +/** + * Returns the reciprocal of the parameter `(1/x)`. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const reciprocal = /*@__PURE__*/ nodeProxy( MathNode, MathNode.RECIPROCAL ).setParameterLength( 1 ); + +/** + * Truncates the parameter, removing the fractional part. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const trunc = /*@__PURE__*/ nodeProxy( MathNode, MathNode.TRUNC ).setParameterLength( 1 ); + +/** + * Returns the sum of the absolute derivatives in x and y. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const fwidth = /*@__PURE__*/ nodeProxy( MathNode, MathNode.FWIDTH ).setParameterLength( 1 ); + +/** + * Returns the transpose of a matrix. + * + * @tsl + * @function + * @param {Node} x - The parameter. + * @returns {Node} + */ +const transpose = /*@__PURE__*/ nodeProxy( MathNode, MathNode.TRANSPOSE ).setParameterLength( 1 ); + +// 2 inputs + +/** + * Reinterpret the bit representation of a value in one type as a value in another type. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @param {string} y - The new type. + * @returns {Node} + */ +const bitcast = /*@__PURE__*/ nodeProxy( MathNode, MathNode.BITCAST ).setParameterLength( 2 ); + +/** + * Returns `true` if `x` equals `y`. + * + * @tsl + * @function + * @param {Node | number} x - The first parameter. + * @param {Node | number} y - The second parameter. + * @deprecated since r175. Use {@link equal} instead. + * @returns {Node} + */ +const equals = ( x, y ) => { // @deprecated, r172 + + console.warn( 'THREE.TSL: "equals" is deprecated. Use "equal" inside a vector instead, like: "bvec*( equal( ... ) )"' ); + return equal( x, y ); + +}; + +/** + * Returns the least of the given values. + * + * @tsl + * @function + * @param {...(Node | number)} values - The values to compare. + * @returns {Node} + */ +const min$1 = /*@__PURE__*/ nodeProxy( MathNode, MathNode.MIN ).setParameterLength( 2, Infinity ); + +/** + * Returns the greatest of the given values. + * + * @tsl + * @function + * @param {...(Node | number)} values - The values to compare. + * @returns {Node} + */ +const max$1 = /*@__PURE__*/ nodeProxy( MathNode, MathNode.MAX ).setParameterLength( 2, Infinity ); + +/** + * Generate a step function by comparing two values. + * + * @tsl + * @function + * @param {Node | number} x - The y parameter. + * @param {Node | number} y - The x parameter. + * @returns {Node} + */ +const step = /*@__PURE__*/ nodeProxy( MathNode, MathNode.STEP ).setParameterLength( 2 ); + +/** + * Calculates the reflection direction for an incident vector. + * + * @tsl + * @function + * @param {Node} I - The incident vector. + * @param {Node} N - The normal vector. + * @returns {Node} + */ +const reflect = /*@__PURE__*/ nodeProxy( MathNode, MathNode.REFLECT ).setParameterLength( 2 ); + +/** + * Calculates the distance between two points. + * + * @tsl + * @function + * @param {Node} x - The first point. + * @param {Node} y - The second point. + * @returns {Node} + */ +const distance = /*@__PURE__*/ nodeProxy( MathNode, MathNode.DISTANCE ).setParameterLength( 2 ); + +/** + * Calculates the absolute difference between two values. + * + * @tsl + * @function + * @param {Node | number} x - The first parameter. + * @param {Node | number} y - The second parameter. + * @returns {Node} + */ +const difference = /*@__PURE__*/ nodeProxy( MathNode, MathNode.DIFFERENCE ).setParameterLength( 2 ); + +/** + * Calculates the dot product of two vectors. + * + * @tsl + * @function + * @param {Node} x - The first vector. + * @param {Node} y - The second vector. + * @returns {Node} + */ +const dot = /*@__PURE__*/ nodeProxy( MathNode, MathNode.DOT ).setParameterLength( 2 ); + +/** + * Calculates the cross product of two vectors. + * + * @tsl + * @function + * @param {Node} x - The first vector. + * @param {Node} y - The second vector. + * @returns {Node} + */ +const cross = /*@__PURE__*/ nodeProxy( MathNode, MathNode.CROSS ).setParameterLength( 2 ); + +/** + * Return the value of the first parameter raised to the power of the second one. + * + * @tsl + * @function + * @param {Node | number} x - The first parameter. + * @param {Node | number} y - The second parameter. + * @returns {Node} + */ +const pow = /*@__PURE__*/ nodeProxy( MathNode, MathNode.POW ).setParameterLength( 2 ); + +/** + * Returns the square of the parameter. + * + * @tsl + * @function + * @param {Node | number} x - The first parameter. + * @returns {Node} + */ +const pow2 = /*@__PURE__*/ nodeProxy( MathNode, MathNode.POW, 2 ).setParameterLength( 1 ); + +/** + * Returns the cube of the parameter. + * + * @tsl + * @function + * @param {Node | number} x - The first parameter. + * @returns {Node} + */ +const pow3 = /*@__PURE__*/ nodeProxy( MathNode, MathNode.POW, 3 ).setParameterLength( 1 ); + +/** + * Returns the fourth power of the parameter. + * + * @tsl + * @function + * @param {Node | number} x - The first parameter. + * @returns {Node} + */ +const pow4 = /*@__PURE__*/ nodeProxy( MathNode, MathNode.POW, 4 ).setParameterLength( 1 ); + +/** + * Transforms the direction of a vector by a matrix and then normalizes the result. + * + * @tsl + * @function + * @param {Node} direction - The direction vector. + * @param {Node} matrix - The transformation matrix. + * @returns {Node} + */ +const transformDirection = /*@__PURE__*/ nodeProxy( MathNode, MathNode.TRANSFORM_DIRECTION ).setParameterLength( 2 ); + +/** + * Returns the cube root of a number. + * + * @tsl + * @function + * @param {Node | number} a - The first parameter. + * @returns {Node} + */ +const cbrt = ( a ) => mul( sign( a ), pow( abs( a ), 1.0 / 3.0 ) ); + +/** + * Calculate the squared length of a vector. + * + * @tsl + * @function + * @param {Node} a - The vector. + * @returns {Node} + */ +const lengthSq = ( a ) => dot( a, a ); + +/** + * Linearly interpolates between two values. + * + * @tsl + * @function + * @param {Node | number} a - The first parameter. + * @param {Node | number} b - The second parameter. + * @param {Node | number} t - The interpolation value. + * @returns {Node} + */ +const mix = /*@__PURE__*/ nodeProxy( MathNode, MathNode.MIX ).setParameterLength( 3 ); + +/** + * Constrains a value to lie between two further values. + * + * @tsl + * @function + * @param {Node | number} value - The value to constrain. + * @param {Node | number} [low=0] - The lower bound. + * @param {Node | number} [high=1] - The upper bound. + * @returns {Node} + */ +const clamp = ( value, low = 0, high = 1 ) => nodeObject( new MathNode( MathNode.CLAMP, nodeObject( value ), nodeObject( low ), nodeObject( high ) ) ); + +/** + * Constrains a value between `0` and `1`. + * + * @tsl + * @function + * @param {Node | number} value - The value to constrain. + * @returns {Node} + */ +const saturate = ( value ) => clamp( value ); + +/** + * Calculates the refraction direction for an incident vector. + * + * @tsl + * @function + * @param {Node} I - The incident vector. + * @param {Node} N - The normal vector. + * @param {Node} eta - The ratio of indices of refraction. + * @returns {Node} + */ +const refract = /*@__PURE__*/ nodeProxy( MathNode, MathNode.REFRACT ).setParameterLength( 3 ); + +/** + * Performs a Hermite interpolation between two values. + * + * @tsl + * @function + * @param {Node | number} low - The value of the lower edge of the Hermite function. + * @param {Node | number} high - The value of the upper edge of the Hermite function. + * @param {Node | number} x - The source value for interpolation. + * @returns {Node} + */ +const smoothstep = /*@__PURE__*/ nodeProxy( MathNode, MathNode.SMOOTHSTEP ).setParameterLength( 3 ); + +/** + * Returns a vector pointing in the same direction as another. + * + * @tsl + * @function + * @param {Node} N - The vector to orient. + * @param {Node} I - The incident vector. + * @param {Node} Nref - The reference vector. + * @returns {Node} + */ +const faceForward = /*@__PURE__*/ nodeProxy( MathNode, MathNode.FACEFORWARD ).setParameterLength( 3 ); + +/** + * Returns a random value for the given uv. + * + * @tsl + * @function + * @param {Node} uv - The uv node. + * @returns {Node} + */ +const rand = /*@__PURE__*/ Fn( ( [ uv ] ) => { + + const a = 12.9898, b = 78.233, c = 43758.5453; + const dt = dot( uv.xy, vec2( a, b ) ), sn = mod( dt, PI ); + + return fract( sin( sn ).mul( c ) ); + +} ); + +/** + * Alias for `mix()` with a different parameter order. + * + * @tsl + * @function + * @param {Node | number} t - The interpolation value. + * @param {Node | number} e1 - The first parameter. + * @param {Node | number} e2 - The second parameter. + * @returns {Node} + */ +const mixElement = ( t, e1, e2 ) => mix( e1, e2, t ); + +/** + * Alias for `smoothstep()` with a different parameter order. + * + * @tsl + * @function + * @param {Node | number} x - The source value for interpolation. + * @param {Node | number} low - The value of the lower edge of the Hermite function. + * @param {Node | number} high - The value of the upper edge of the Hermite function. + * @returns {Node} + */ +const smoothstepElement = ( x, low, high ) => smoothstep( low, high, x ); + +/** + * Alias for `step()` with a different parameter order. + * + * @tsl + * @function + * @param {Node | number} x - The source value for interpolation. + * @param {Node | number} edge - The edge value. + * @returns {Node} + */ +const stepElement = ( x, edge ) => step( edge, x ); + +/** + * Returns the arc-tangent of the quotient of its parameters. + * + * @tsl + * @function + * @deprecated since r172. Use {@link atan} instead. + * + * @param {Node | number} y - The y parameter. + * @param {Node | number} x - The x parameter. + * @returns {Node} + */ +const atan2 = ( y, x ) => { // @deprecated, r172 + + console.warn( 'THREE.TSL: "atan2" is overloaded. Use "atan" instead.' ); + return atan( y, x ); + +}; + +// GLSL alias function + +const faceforward = faceForward; +const inversesqrt = inverseSqrt; + +// Method chaining + +addMethodChaining( 'all', all ); +addMethodChaining( 'any', any ); +addMethodChaining( 'equals', equals ); + +addMethodChaining( 'radians', radians ); +addMethodChaining( 'degrees', degrees ); +addMethodChaining( 'exp', exp ); +addMethodChaining( 'exp2', exp2 ); +addMethodChaining( 'log', log ); +addMethodChaining( 'log2', log2 ); +addMethodChaining( 'sqrt', sqrt ); +addMethodChaining( 'inverseSqrt', inverseSqrt ); +addMethodChaining( 'floor', floor ); +addMethodChaining( 'ceil', ceil ); +addMethodChaining( 'normalize', normalize ); +addMethodChaining( 'fract', fract ); +addMethodChaining( 'sin', sin ); +addMethodChaining( 'cos', cos ); +addMethodChaining( 'tan', tan ); +addMethodChaining( 'asin', asin ); +addMethodChaining( 'acos', acos ); +addMethodChaining( 'atan', atan ); +addMethodChaining( 'abs', abs ); +addMethodChaining( 'sign', sign ); +addMethodChaining( 'length', length ); +addMethodChaining( 'lengthSq', lengthSq ); +addMethodChaining( 'negate', negate ); +addMethodChaining( 'oneMinus', oneMinus ); +addMethodChaining( 'dFdx', dFdx ); +addMethodChaining( 'dFdy', dFdy ); +addMethodChaining( 'round', round ); +addMethodChaining( 'reciprocal', reciprocal ); +addMethodChaining( 'trunc', trunc ); +addMethodChaining( 'fwidth', fwidth ); +addMethodChaining( 'atan2', atan2 ); +addMethodChaining( 'min', min$1 ); +addMethodChaining( 'max', max$1 ); +addMethodChaining( 'step', stepElement ); +addMethodChaining( 'reflect', reflect ); +addMethodChaining( 'distance', distance ); +addMethodChaining( 'dot', dot ); +addMethodChaining( 'cross', cross ); +addMethodChaining( 'pow', pow ); +addMethodChaining( 'pow2', pow2 ); +addMethodChaining( 'pow3', pow3 ); +addMethodChaining( 'pow4', pow4 ); +addMethodChaining( 'transformDirection', transformDirection ); +addMethodChaining( 'mix', mixElement ); +addMethodChaining( 'clamp', clamp ); +addMethodChaining( 'refract', refract ); +addMethodChaining( 'smoothstep', smoothstepElement ); +addMethodChaining( 'faceForward', faceForward ); +addMethodChaining( 'difference', difference ); +addMethodChaining( 'saturate', saturate ); +addMethodChaining( 'cbrt', cbrt ); +addMethodChaining( 'transpose', transpose ); +addMethodChaining( 'rand', rand ); + +/** + * Represents a logical `if/else` statement. Can be used as an alternative + * to the `If()`/`Else()` syntax. + * + * The corresponding TSL `select()` looks like so: + * ```js + * velocity = position.greaterThanEqual( limit ).select( velocity.negate(), velocity ); + * ``` + * The `select()` method is called in a chaining fashion on a condition. The parameter nodes of `select()` + * determine the outcome of the entire statement. + * + * @augments Node + */ +class ConditionalNode extends Node { + + static get type() { + + return 'ConditionalNode'; + + } + + /** + * Constructs a new conditional node. + * + * @param {Node} condNode - The node that defines the condition. + * @param {Node} ifNode - The node that is evaluate when the condition ends up `true`. + * @param {?Node} [elseNode=null] - The node that is evaluate when the condition ends up `false`. + */ + constructor( condNode, ifNode, elseNode = null ) { + + super(); + + /** + * The node that defines the condition. + * + * @type {Node} + */ + this.condNode = condNode; + + /** + * The node that is evaluate when the condition ends up `true`. + * + * @type {Node} + */ + this.ifNode = ifNode; + + /** + * The node that is evaluate when the condition ends up `false`. + * + * @type {?Node} + * @default null + */ + this.elseNode = elseNode; + + } + + /** + * This method is overwritten since the node type is inferred from the if/else + * nodes. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType( builder ) { + + const { ifNode, elseNode } = builder.getNodeProperties( this ); + + if ( ifNode === undefined ) { + + // fallback setup + + this.setup( builder ); + + return this.getNodeType( builder ); + + } + + const ifType = ifNode.getNodeType( builder ); + + if ( elseNode !== null ) { + + const elseType = elseNode.getNodeType( builder ); + + if ( builder.getTypeLength( elseType ) > builder.getTypeLength( ifType ) ) { + + return elseType; + + } + + } + + return ifType; + + } + + setup( builder ) { + + const condNode = this.condNode.cache(); + const ifNode = this.ifNode.cache(); + const elseNode = this.elseNode ? this.elseNode.cache() : null; + + // + + const currentNodeBlock = builder.context.nodeBlock; + + builder.getDataFromNode( ifNode ).parentNodeBlock = currentNodeBlock; + if ( elseNode !== null ) builder.getDataFromNode( elseNode ).parentNodeBlock = currentNodeBlock; + + // + + const properties = builder.getNodeProperties( this ); + properties.condNode = condNode; + properties.ifNode = ifNode.context( { nodeBlock: ifNode } ); + properties.elseNode = elseNode ? elseNode.context( { nodeBlock: elseNode } ) : null; + + } + + generate( builder, output ) { + + const type = this.getNodeType( builder ); + + const nodeData = builder.getDataFromNode( this ); + + if ( nodeData.nodeProperty !== undefined ) { + + return nodeData.nodeProperty; + + } + + const { condNode, ifNode, elseNode } = builder.getNodeProperties( this ); + + const functionNode = builder.currentFunctionNode; + const needsOutput = output !== 'void'; + const nodeProperty = needsOutput ? property( type ).build( builder ) : ''; + + nodeData.nodeProperty = nodeProperty; + + const nodeSnippet = condNode.build( builder, 'bool' ); + + builder.addFlowCode( `\n${ builder.tab }if ( ${ nodeSnippet } ) {\n\n` ).addFlowTab(); + + let ifSnippet = ifNode.build( builder, type ); + + if ( ifSnippet ) { + + if ( needsOutput ) { + + ifSnippet = nodeProperty + ' = ' + ifSnippet + ';'; + + } else { + + ifSnippet = 'return ' + ifSnippet + ';'; + + if ( functionNode === null ) { + + console.warn( 'THREE.TSL: Return statement used in an inline \'Fn()\'. Define a layout struct to allow return values.' ); + + ifSnippet = '// ' + ifSnippet; + + } + + } + + } + + builder.removeFlowTab().addFlowCode( builder.tab + '\t' + ifSnippet + '\n\n' + builder.tab + '}' ); + + if ( elseNode !== null ) { + + builder.addFlowCode( ' else {\n\n' ).addFlowTab(); + + let elseSnippet = elseNode.build( builder, type ); + + if ( elseSnippet ) { + + if ( needsOutput ) { + + elseSnippet = nodeProperty + ' = ' + elseSnippet + ';'; + + } else { + + elseSnippet = 'return ' + elseSnippet + ';'; + + if ( functionNode === null ) { + + console.warn( 'THREE.TSL: Return statement used in an inline \'Fn()\'. Define a layout struct to allow return values.' ); + + elseSnippet = '// ' + elseSnippet; + + } + + } + + } + + builder.removeFlowTab().addFlowCode( builder.tab + '\t' + elseSnippet + '\n\n' + builder.tab + '}\n\n' ); + + } else { + + builder.addFlowCode( '\n\n' ); + + } + + return builder.format( nodeProperty, type, output ); + + } + +} + +/** + * TSL function for creating a conditional node. + * + * @tsl + * @function + * @param {Node} condNode - The node that defines the condition. + * @param {Node} ifNode - The node that is evaluate when the condition ends up `true`. + * @param {?Node} [elseNode=null] - The node that is evaluate when the condition ends up `false`. + * @returns {ConditionalNode} + */ +const select = /*@__PURE__*/ nodeProxy( ConditionalNode ).setParameterLength( 2, 3 ); + +addMethodChaining( 'select', select ); + +/** + * This node can be used as a context management component for another node. + * {@link NodeBuilder} performs its node building process in a specific context and + * this node allows the modify the context. A typical use case is to overwrite `getUV()` e.g.: + * + * ```js + *node.context( { getUV: () => customCoord } ); + *``` + * @augments Node + */ +class ContextNode extends Node { + + static get type() { + + return 'ContextNode'; + + } + + /** + * Constructs a new context node. + * + * @param {Node} node - The node whose context should be modified. + * @param {Object} [value={}] - The modified context data. + */ + constructor( node, value = {} ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isContextNode = true; + + /** + * The node whose context should be modified. + * + * @type {Node} + */ + this.node = node; + + /** + * The modified context data. + * + * @type {Object} + * @default {} + */ + this.value = value; + + } + + /** + * This method is overwritten to ensure it returns the reference to {@link ContextNode#node}. + * + * @return {Node} A reference to {@link ContextNode#node}. + */ + getScope() { + + return this.node.getScope(); + + } + + /** + * This method is overwritten to ensure it returns the type of {@link ContextNode#node}. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType( builder ) { + + return this.node.getNodeType( builder ); + + } + + analyze( builder ) { + + const previousContext = builder.getContext(); + + builder.setContext( { ...builder.context, ...this.value } ); + + this.node.build( builder ); + + builder.setContext( previousContext ); + + } + + setup( builder ) { + + const previousContext = builder.getContext(); + + builder.setContext( { ...builder.context, ...this.value } ); + + this.node.build( builder ); + + builder.setContext( previousContext ); + + } + + generate( builder, output ) { + + const previousContext = builder.getContext(); + + builder.setContext( { ...builder.context, ...this.value } ); + + const snippet = this.node.build( builder, output ); + + builder.setContext( previousContext ); + + return snippet; + + } + +} + +/** + * TSL function for creating a context node. + * + * @tsl + * @function + * @param {Node} node - The node whose context should be modified. + * @param {Object} [value={}] - The modified context data. + * @returns {ContextNode} + */ +const context = /*@__PURE__*/ nodeProxy( ContextNode ).setParameterLength( 1, 2 ); + +/** + * TSL function for defining a label context value for a given node. + * + * @tsl + * @function + * @param {Node} node - The node whose context should be modified. + * @param {string} name - The name/label to set. + * @returns {ContextNode} + */ +const label = ( node, name ) => context( node, { label: name } ); + +addMethodChaining( 'context', context ); +addMethodChaining( 'label', label ); + +/** + * Class for representing shader variables as nodes. Variables are created from + * existing nodes like the following: + * + * ```js + * const depth = sampleDepth( uvNode ).toVar( 'depth' ); + * ``` + * + * @augments Node + */ +class VarNode extends Node { + + static get type() { + + return 'VarNode'; + + } + + /** + * Constructs a new variable node. + * + * @param {Node} node - The node for which a variable should be created. + * @param {?string} [name=null] - The name of the variable in the shader. + * @param {boolean} [readOnly=false] - The read-only flag. + */ + constructor( node, name = null, readOnly = false ) { + + super(); + + /** + * The node for which a variable should be created. + * + * @type {Node} + */ + this.node = node; + + /** + * The name of the variable in the shader. If no name is defined, + * the node system auto-generates one. + * + * @type {?string} + * @default null + */ + this.name = name; + + /** + * `VarNode` sets this property to `true` by default. + * + * @type {boolean} + * @default true + */ + this.global = true; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isVarNode = true; + + /** + * + * The read-only flag. + * + * @type {boolean} + * @default false + */ + this.readOnly = readOnly; + + /** + * + * Add this flag to the node system to indicate that this node require parents. + * + * @type {boolean} + * @default true + */ + this.parents = true; + + } + + getMemberType( builder, name ) { + + return this.node.getMemberType( builder, name ); + + } + + getElementType( builder ) { + + return this.node.getElementType( builder ); + + } + + getNodeType( builder ) { + + return this.node.getNodeType( builder ); + + } + + generate( builder ) { + + const { node, name, readOnly } = this; + const { renderer } = builder; + + const isWebGPUBackend = renderer.backend.isWebGPUBackend === true; + + let isDeterministic = false; + let shouldTreatAsReadOnly = false; + + if ( readOnly ) { + + isDeterministic = builder.isDeterministic( node ); + + shouldTreatAsReadOnly = isWebGPUBackend ? readOnly : isDeterministic; + + } + + const vectorType = builder.getVectorType( this.getNodeType( builder ) ); + const snippet = node.build( builder, vectorType ); + + const nodeVar = builder.getVarFromNode( this, name, vectorType, undefined, shouldTreatAsReadOnly ); + + const propertyName = builder.getPropertyName( nodeVar ); + + let declarationPrefix = propertyName; + + if ( shouldTreatAsReadOnly ) { + + if ( isWebGPUBackend ) { + + declarationPrefix = isDeterministic + ? `const ${ propertyName }` + : `let ${ propertyName }`; + + } else { + + const count = builder.getArrayCount( node ); + + declarationPrefix = `const ${ builder.getVar( nodeVar.type, propertyName, count ) }`; + + } + + } + + builder.addLineFlowCode( `${ declarationPrefix } = ${ snippet }`, this ); + + return propertyName; + + } + +} + +/** + * TSL function for creating a var node. + * + * @tsl + * @function + * @param {Node} node - The node for which a variable should be created. + * @param {?string} name - The name of the variable in the shader. + * @returns {VarNode} + */ +const createVar = /*@__PURE__*/ nodeProxy( VarNode ); + +/** + * TSL function for creating a var node. + * + * @tsl + * @function + * @param {Node} node - The node for which a variable should be created. + * @param {?string} name - The name of the variable in the shader. + * @returns {VarNode} + */ +const Var = ( node, name = null ) => createVar( node, name ).toStack(); + +/** + * TSL function for creating a const node. + * + * @tsl + * @function + * @param {Node} node - The node for which a constant should be created. + * @param {?string} name - The name of the constant in the shader. + * @returns {VarNode} + */ +const Const = ( node, name = null ) => createVar( node, name, true ).toStack(); + +// Method chaining + +addMethodChaining( 'toVar', Var ); +addMethodChaining( 'toConst', Const ); + +// Deprecated + +/** + * @tsl + * @function + * @deprecated since r170. Use `Var( node )` or `node.toVar()` instead. + * + * @param {any} node + * @returns {VarNode} + */ +const temp = ( node ) => { // @deprecated, r170 + + console.warn( 'TSL: "temp( node )" is deprecated. Use "Var( node )" or "node.toVar()" instead.' ); + + return createVar( node ); + +}; + +addMethodChaining( 'temp', temp ); + +/** + * This node is used to build a sub-build in the node system. + * + * @augments Node + * @param {Node} node - The node to be built in the sub-build. + * @param {string} name - The name of the sub-build. + * @param {string|null} [nodeType=null] - The type of the node, if known. + */ +class SubBuildNode extends Node { + + static get type() { + + return 'SubBuild'; + + } + + constructor( node, name, nodeType = null ) { + + super( nodeType ); + + /** + * The node to be built in the sub-build. + * + * @type {Node} + */ + this.node = node; + + /** + * The name of the sub-build. + * + * @type {string} + */ + this.name = name; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSubBuildNode = true; + + } + + getNodeType( builder ) { + + if ( this.nodeType !== null ) return this.nodeType; + + builder.addSubBuild( this.name ); + + const nodeType = this.node.getNodeType( builder ); + + builder.removeSubBuild(); + + return nodeType; + + } + + build( builder, ...params ) { + + builder.addSubBuild( this.name ); + + const data = this.node.build( builder, ...params ); + + builder.removeSubBuild(); + + return data; + + } + +} + +/** + * Creates a new sub-build node. + * + * @tsl + * @function + * @param {Node} node - The node to be built in the sub-build. + * @param {string} name - The name of the sub-build. + * @param {string|null} [type=null] - The type of the node, if known. + * @returns {Node} A node object wrapping the SubBuildNode instance. + */ +const subBuild = ( node, name, type = null ) => nodeObject( new SubBuildNode( nodeObject( node ), name, type ) ); + +/** + * Class for representing shader varyings as nodes. Varyings are create from + * existing nodes like the following: + * + * ```js + * const positionLocal = positionGeometry.toVarying( 'vPositionLocal' ); + * ``` + * + * @augments Node + */ +class VaryingNode extends Node { + + static get type() { + + return 'VaryingNode'; + + } + + /** + * Constructs a new varying node. + * + * @param {Node} node - The node for which a varying should be created. + * @param {?string} name - The name of the varying in the shader. + */ + constructor( node, name = null ) { + + super(); + + /** + * The node for which a varying should be created. + * + * @type {Node} + */ + this.node = node; + + /** + * The name of the varying in the shader. If no name is defined, + * the node system auto-generates one. + * + * @type {?string} + * @default null + */ + this.name = name; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isVaryingNode = true; + + /** + * The interpolation type of the varying data. + * + * @type {?string} + * @default null + */ + this.interpolationType = null; + + /** + * The interpolation sampling type of varying data. + * + * @type {?string} + * @default null + */ + this.interpolationSampling = null; + + /** + * This flag is used for global cache. + * + * @type {boolean} + * @default true + */ + this.global = true; + + } + + /** + * Defines the interpolation type of the varying. + * + * @param {string} type - The interpolation type. + * @param {?string} sampling - The interpolation sampling type + * @return {VaryingNode} A reference to this node. + */ + setInterpolation( type, sampling = null ) { + + this.interpolationType = type; + this.interpolationSampling = sampling; + + return this; + + } + + getHash( builder ) { + + return this.name || super.getHash( builder ); + + } + + getNodeType( builder ) { + + // VaryingNode is auto type + + return this.node.getNodeType( builder ); + + } + + /** + * This method performs the setup of a varying node with the current node builder. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {NodeVarying} The node varying from the node builder. + */ + setupVarying( builder ) { + + const properties = builder.getNodeProperties( this ); + + let varying = properties.varying; + + if ( varying === undefined ) { + + const name = this.name; + const type = this.getNodeType( builder ); + const interpolationType = this.interpolationType; + const interpolationSampling = this.interpolationSampling; + + properties.varying = varying = builder.getVaryingFromNode( this, name, type, interpolationType, interpolationSampling ); + properties.node = subBuild( this.node, 'VERTEX' ); + + } + + // this property can be used to check if the varying can be optimized for a variable + varying.needsInterpolation || ( varying.needsInterpolation = ( builder.shaderStage === 'fragment' ) ); + + return varying; + + } + + setup( builder ) { + + this.setupVarying( builder ); + + builder.flowNodeFromShaderStage( NodeShaderStage.VERTEX, this.node ); + + } + + analyze( builder ) { + + this.setupVarying( builder ); + + builder.flowNodeFromShaderStage( NodeShaderStage.VERTEX, this.node ); + + } + + generate( builder ) { + + const propertyKey = builder.getSubBuildProperty( 'property', builder.currentStack ); + const properties = builder.getNodeProperties( this ); + const varying = this.setupVarying( builder ); + + if ( properties[ propertyKey ] === undefined ) { + + const type = this.getNodeType( builder ); + const propertyName = builder.getPropertyName( varying, NodeShaderStage.VERTEX ); + + // force node run in vertex stage + builder.flowNodeFromShaderStage( NodeShaderStage.VERTEX, properties.node, type, propertyName ); + + properties[ propertyKey ] = propertyName; + + } + + return builder.getPropertyName( varying ); + + } + +} + +/** + * TSL function for creating a varying node. + * + * @tsl + * @function + * @param {Node} node - The node for which a varying should be created. + * @param {?string} name - The name of the varying in the shader. + * @returns {VaryingNode} + */ +const varying = /*@__PURE__*/ nodeProxy( VaryingNode ).setParameterLength( 1, 2 ); + +/** + * Computes a node in the vertex stage. + * + * @tsl + * @function + * @param {Node} node - The node which should be executed in the vertex stage. + * @returns {VaryingNode} + */ +const vertexStage = ( node ) => varying( node ); + +addMethodChaining( 'toVarying', varying ); +addMethodChaining( 'toVertexStage', vertexStage ); + +// Deprecated + +addMethodChaining( 'varying', ( ...params ) => { // @deprecated, r173 + + console.warn( 'THREE.TSL: .varying() has been renamed to .toVarying().' ); + return varying( ...params ); + +} ); + +addMethodChaining( 'vertexStage', ( ...params ) => { // @deprecated, r173 + + console.warn( 'THREE.TSL: .vertexStage() has been renamed to .toVertexStage().' ); + return varying( ...params ); + +} ); + +/** + * Converts the given color value from sRGB to linear-sRGB color space. + * + * @tsl + * @function + * @param {Node} color - The sRGB color. + * @return {Node} The linear-sRGB color. + */ +const sRGBTransferEOTF = /*@__PURE__*/ Fn( ( [ color ] ) => { + + const a = color.mul( 0.9478672986 ).add( 0.0521327014 ).pow( 2.4 ); + const b = color.mul( 0.0773993808 ); + const factor = color.lessThanEqual( 0.04045 ); + + const rgbResult = mix( a, b, factor ); + + return rgbResult; + +} ).setLayout( { + name: 'sRGBTransferEOTF', + type: 'vec3', + inputs: [ + { name: 'color', type: 'vec3' } + ] +} ); + +/** + * Converts the given color value from linear-sRGB to sRGB color space. + * + * @tsl + * @function + * @param {Node} color - The linear-sRGB color. + * @return {Node} The sRGB color. + */ +const sRGBTransferOETF = /*@__PURE__*/ Fn( ( [ color ] ) => { + + const a = color.pow( 0.41666 ).mul( 1.055 ).sub( 0.055 ); + const b = color.mul( 12.92 ); + const factor = color.lessThanEqual( 0.0031308 ); + + const rgbResult = mix( a, b, factor ); + + return rgbResult; + +} ).setLayout( { + name: 'sRGBTransferOETF', + type: 'vec3', + inputs: [ + { name: 'color', type: 'vec3' } + ] +} ); + +const WORKING_COLOR_SPACE = 'WorkingColorSpace'; +const OUTPUT_COLOR_SPACE = 'OutputColorSpace'; + +/** + * This node represents a color space conversion. Meaning it converts + * a color value from a source to a target color space. + * + * @augments TempNode + */ +class ColorSpaceNode extends TempNode { + + static get type() { + + return 'ColorSpaceNode'; + + } + + /** + * Constructs a new color space node. + * + * @param {Node} colorNode - Represents the color to convert. + * @param {string} source - The source color space. + * @param {string} target - The target color space. + */ + constructor( colorNode, source, target ) { + + super( 'vec4' ); + + /** + * Represents the color to convert. + * + * @type {Node} + */ + this.colorNode = colorNode; + + /** + * The source color space. + * + * @type {string} + */ + this.source = source; + + /** + * The target color space. + * + * @type {string} + */ + this.target = target; + + } + + /** + * This method resolves the constants `WORKING_COLOR_SPACE` and + * `OUTPUT_COLOR_SPACE` based on the current configuration of the + * color management and renderer. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {string} colorSpace - The color space to resolve. + * @return {string} The resolved color space. + */ + resolveColorSpace( builder, colorSpace ) { + + if ( colorSpace === WORKING_COLOR_SPACE ) { + + return ColorManagement.workingColorSpace; + + } else if ( colorSpace === OUTPUT_COLOR_SPACE ) { + + return builder.context.outputColorSpace || builder.renderer.outputColorSpace; + + } + + return colorSpace; + + } + + setup( builder ) { + + const { colorNode } = this; + + const source = this.resolveColorSpace( builder, this.source ); + const target = this.resolveColorSpace( builder, this.target ); + + let outputNode = colorNode; + + if ( ColorManagement.enabled === false || source === target || ! source || ! target ) { + + return outputNode; + + } + + if ( ColorManagement.getTransfer( source ) === SRGBTransfer ) { + + outputNode = vec4( sRGBTransferEOTF( outputNode.rgb ), outputNode.a ); + + } + + if ( ColorManagement.getPrimaries( source ) !== ColorManagement.getPrimaries( target ) ) { + + outputNode = vec4( + mat3( ColorManagement._getMatrix( new Matrix3(), source, target ) ).mul( outputNode.rgb ), + outputNode.a + ); + + } + + if ( ColorManagement.getTransfer( target ) === SRGBTransfer ) { + + outputNode = vec4( sRGBTransferOETF( outputNode.rgb ), outputNode.a ); + + } + + return outputNode; + + } + +} + +/** + * TSL function for converting a given color node from the current working color space to the given color space. + * + * @tsl + * @function + * @param {Node} node - Represents the node to convert. + * @param {string} targetColorSpace - The target color space. + * @returns {ColorSpaceNode} + */ +const workingToColorSpace = ( node, targetColorSpace ) => nodeObject( new ColorSpaceNode( nodeObject( node ), WORKING_COLOR_SPACE, targetColorSpace ) ); + +/** + * TSL function for converting a given color node from the given color space to the current working color space. + * + * @tsl + * @function + * @param {Node} node - Represents the node to convert. + * @param {string} sourceColorSpace - The source color space. + * @returns {ColorSpaceNode} + */ +const colorSpaceToWorking = ( node, sourceColorSpace ) => nodeObject( new ColorSpaceNode( nodeObject( node ), sourceColorSpace, WORKING_COLOR_SPACE ) ); + +/** + * TSL function for converting a given color node from one color space to another one. + * + * @tsl + * @function + * @param {Node} node - Represents the node to convert. + * @param {string} sourceColorSpace - The source color space. + * @param {string} targetColorSpace - The target color space. + * @returns {ColorSpaceNode} + */ +const convertColorSpace = ( node, sourceColorSpace, targetColorSpace ) => nodeObject( new ColorSpaceNode( nodeObject( node ), sourceColorSpace, targetColorSpace ) ); + +addMethodChaining( 'workingToColorSpace', workingToColorSpace ); +addMethodChaining( 'colorSpaceToWorking', colorSpaceToWorking ); + +// TODO: Avoid duplicated code and ues only ReferenceBaseNode or ReferenceNode + +/** + * This class is only relevant if the referenced property is array-like. + * In this case, `ReferenceElementNode` allows to refer to a specific + * element inside the data structure via an index. + * + * @augments ArrayElementNode + */ +let ReferenceElementNode$1 = class ReferenceElementNode extends ArrayElementNode { + + static get type() { + + return 'ReferenceElementNode'; + + } + + /** + * Constructs a new reference element node. + * + * @param {ReferenceBaseNode} referenceNode - The reference node. + * @param {Node} indexNode - The index node that defines the element access. + */ + constructor( referenceNode, indexNode ) { + + super( referenceNode, indexNode ); + + /** + * Similar to {@link ReferenceBaseNode#reference}, an additional + * property references to the current node. + * + * @type {?ReferenceBaseNode} + * @default null + */ + this.referenceNode = referenceNode; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isReferenceElementNode = true; + + } + + /** + * This method is overwritten since the node type is inferred from + * the uniform type of the reference node. + * + * @return {string} The node type. + */ + getNodeType() { + + return this.referenceNode.uniformType; + + } + + generate( builder ) { + + const snippet = super.generate( builder ); + const arrayType = this.referenceNode.getNodeType(); + const elementType = this.getNodeType(); + + return builder.format( snippet, arrayType, elementType ); + + } + +}; + +/** + * Base class for nodes which establishes a reference to a property of another object. + * In this way, the value of the node is automatically linked to the value of + * referenced object. Reference nodes internally represent the linked value + * as a uniform. + * + * @augments Node + */ +class ReferenceBaseNode extends Node { + + static get type() { + + return 'ReferenceBaseNode'; + + } + + /** + * Constructs a new reference base node. + * + * @param {string} property - The name of the property the node refers to. + * @param {string} uniformType - The uniform type that should be used to represent the property value. + * @param {?Object} [object=null] - The object the property belongs to. + * @param {?number} [count=null] - When the linked property is an array-like, this parameter defines its length. + */ + constructor( property, uniformType, object = null, count = null ) { + + super(); + + /** + * The name of the property the node refers to. + * + * @type {string} + */ + this.property = property; + + /** + * The uniform type that should be used to represent the property value. + * + * @type {string} + */ + this.uniformType = uniformType; + + /** + * The object the property belongs to. + * + * @type {?Object} + * @default null + */ + this.object = object; + + /** + * When the linked property is an array, this parameter defines its length. + * + * @type {?number} + * @default null + */ + this.count = count; + + /** + * The property name might have dots so nested properties can be referred. + * The hierarchy of the names is stored inside this array. + * + * @type {Array} + */ + this.properties = property.split( '.' ); + + /** + * Points to the current referred object. This property exists next to {@link ReferenceNode#object} + * since the final reference might be updated from calling code. + * + * @type {?Object} + * @default null + */ + this.reference = object; + + /** + * The uniform node that holds the value of the reference node. + * + * @type {UniformNode} + * @default null + */ + this.node = null; + + /** + * The uniform group of the internal uniform. + * + * @type {UniformGroupNode} + * @default null + */ + this.group = null; + + /** + * Overwritten since reference nodes are updated per object. + * + * @type {string} + * @default 'object' + */ + this.updateType = NodeUpdateType.OBJECT; + + } + + /** + * Sets the uniform group for this reference node. + * + * @param {UniformGroupNode} group - The uniform group to set. + * @return {ReferenceBaseNode} A reference to this node. + */ + setGroup( group ) { + + this.group = group; + + return this; + + } + + /** + * When the referred property is array-like, this method can be used + * to access elements via an index node. + * + * @param {IndexNode} indexNode - indexNode. + * @return {ReferenceElementNode} A reference to an element. + */ + element( indexNode ) { + + return nodeObject( new ReferenceElementNode$1( this, nodeObject( indexNode ) ) ); + + } + + /** + * Sets the node type which automatically defines the internal + * uniform type. + * + * @param {string} uniformType - The type to set. + */ + setNodeType( uniformType ) { + + const node = uniform( null, uniformType ).getSelf(); + + if ( this.group !== null ) { + + node.setGroup( this.group ); + + } + + this.node = node; + + } + + /** + * This method is overwritten since the node type is inferred from + * the type of the reference node. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType( builder ) { + + if ( this.node === null ) { + + this.updateReference( builder ); + this.updateValue(); + + } + + return this.node.getNodeType( builder ); + + } + + /** + * Returns the property value from the given referred object. + * + * @param {Object} [object=this.reference] - The object to retrieve the property value from. + * @return {any} The value. + */ + getValueFromReference( object = this.reference ) { + + const { properties } = this; + + let value = object[ properties[ 0 ] ]; + + for ( let i = 1; i < properties.length; i ++ ) { + + value = value[ properties[ i ] ]; + + } + + return value; + + } + + /** + * Allows to update the reference based on the given state. The state is only + * evaluated {@link ReferenceBaseNode#object} is not set. + * + * @param {(NodeFrame|NodeBuilder)} state - The current state. + * @return {Object} The updated reference. + */ + updateReference( state ) { + + this.reference = this.object !== null ? this.object : state.object; + + return this.reference; + + } + + /** + * The output of the reference node is the internal uniform node. + * + * @return {UniformNode} The output node. + */ + setup() { + + this.updateValue(); + + return this.node; + + } + + /** + * Overwritten to update the internal uniform value. + * + * @param {NodeFrame} frame - A reference to the current node frame. + */ + update( /*frame*/ ) { + + this.updateValue(); + + } + + /** + * Retrieves the value from the referred object property and uses it + * to updated the internal uniform. + */ + updateValue() { + + if ( this.node === null ) this.setNodeType( this.uniformType ); + + const value = this.getValueFromReference(); + + if ( Array.isArray( value ) ) { + + this.node.array = value; + + } else { + + this.node.value = value; + + } + + } + +} + +/** + * TSL function for creating a reference base node. + * + * @tsl + * @function + * @param {string} name - The name of the property the node refers to. + * @param {string} type - The uniform type that should be used to represent the property value. + * @param {Object} object - The object the property belongs to. + * @returns {ReferenceBaseNode} + */ +const reference$1 = ( name, type, object ) => nodeObject( new ReferenceBaseNode( name, type, object ) ); + +/** + * This node is a special type of reference node which is intended + * for linking renderer properties with node values. + * ```js + * const exposureNode = rendererReference( 'toneMappingExposure', 'float', renderer ); + * ``` + * When changing `renderer.toneMappingExposure`, the node value of `exposureNode` will + * automatically be updated. + * + * @augments ReferenceBaseNode + */ +class RendererReferenceNode extends ReferenceBaseNode { + + static get type() { + + return 'RendererReferenceNode'; + + } + + /** + * Constructs a new renderer reference node. + * + * @param {string} property - The name of the property the node refers to. + * @param {string} inputType - The uniform type that should be used to represent the property value. + * @param {?Renderer} [renderer=null] - The renderer the property belongs to. When no renderer is set, + * the node refers to the renderer of the current state. + */ + constructor( property, inputType, renderer = null ) { + + super( property, inputType, renderer ); + + /** + * The renderer the property belongs to. When no renderer is set, + * the node refers to the renderer of the current state. + * + * @type {?Renderer} + * @default null + */ + this.renderer = renderer; + + this.setGroup( renderGroup ); + + } + + /** + * Updates the reference based on the given state. The state is only evaluated + * {@link RendererReferenceNode#renderer} is not set. + * + * @param {(NodeFrame|NodeBuilder)} state - The current state. + * @return {Object} The updated reference. + */ + updateReference( state ) { + + this.reference = this.renderer !== null ? this.renderer : state.renderer; + + return this.reference; + + } + +} + +/** + * TSL function for creating a renderer reference node. + * + * @tsl + * @function + * @param {string} name - The name of the property the node refers to. + * @param {string} type - The uniform type that should be used to represent the property value. + * @param {?Renderer} [renderer=null] - The renderer the property belongs to. When no renderer is set, + * the node refers to the renderer of the current state. + * @returns {RendererReferenceNode} + */ +const rendererReference = ( name, type, renderer = null ) => nodeObject( new RendererReferenceNode( name, type, renderer ) ); + +/** + * This node represents a tone mapping operation. + * + * @augments TempNode + */ +class ToneMappingNode extends TempNode { + + static get type() { + + return 'ToneMappingNode'; + + } + + /** + * Constructs a new tone mapping node. + * + * @param {number} toneMapping - The tone mapping type. + * @param {Node} exposureNode - The tone mapping exposure. + * @param {Node} [colorNode=null] - The color node to process. + */ + constructor( toneMapping, exposureNode = toneMappingExposure, colorNode = null ) { + + super( 'vec3' ); + + /** + * The tone mapping type. + * + * @type {number} + */ + this.toneMapping = toneMapping; + + /** + * The tone mapping exposure. + * + * @type {Node} + * @default null + */ + this.exposureNode = exposureNode; + + /** + * Represents the color to process. + * + * @type {?Node} + * @default null + */ + this.colorNode = colorNode; + + } + + /** + * Overwrites the default `customCacheKey()` implementation by including the tone + * mapping type into the cache key. + * + * @return {number} The hash. + */ + customCacheKey() { + + return hash$1( this.toneMapping ); + + } + + setup( builder ) { + + const colorNode = this.colorNode || builder.context.color; + const toneMapping = this.toneMapping; + + if ( toneMapping === NoToneMapping ) return colorNode; + + let outputNode = null; + + const toneMappingFn = builder.renderer.library.getToneMappingFunction( toneMapping ); + + if ( toneMappingFn !== null ) { + + outputNode = vec4( toneMappingFn( colorNode.rgb, this.exposureNode ), colorNode.a ); + + } else { + + console.error( 'ToneMappingNode: Unsupported Tone Mapping configuration.', toneMapping ); + + outputNode = colorNode; + + } + + return outputNode; + + } + +} + +/** + * TSL function for creating a tone mapping node. + * + * @tsl + * @function + * @param {number} mapping - The tone mapping type. + * @param {Node | number} exposure - The tone mapping exposure. + * @param {Node | Color} color - The color node to process. + * @returns {ToneMappingNode} + */ +const toneMapping = ( mapping, exposure, color ) => nodeObject( new ToneMappingNode( mapping, nodeObject( exposure ), nodeObject( color ) ) ); + +/** + * TSL object that represents the global tone mapping exposure of the renderer. + * + * @tsl + * @type {RendererReferenceNode} + */ +const toneMappingExposure = /*@__PURE__*/ rendererReference( 'toneMappingExposure', 'float' ); + +addMethodChaining( 'toneMapping', ( color, mapping, exposure ) => toneMapping( mapping, exposure, color ) ); + +/** + * In earlier `three.js` versions it was only possible to define attribute data + * on geometry level. With `BufferAttributeNode`, it is also possible to do this + * on the node level. + * ```js + * const geometry = new THREE.PlaneGeometry(); + * const positionAttribute = geometry.getAttribute( 'position' ); + * + * const colors = []; + * for ( let i = 0; i < position.count; i ++ ) { + * colors.push( 1, 0, 0 ); + * } + * + * material.colorNode = bufferAttribute( new THREE.Float32BufferAttribute( colors, 3 ) ); + * ``` + * This new approach is especially interesting when geometry data are generated via + * compute shaders. The below line converts a storage buffer into an attribute node. + * ```js + * material.positionNode = positionBuffer.toAttribute(); + * ``` + * @augments InputNode + */ +class BufferAttributeNode extends InputNode { + + static get type() { + + return 'BufferAttributeNode'; + + } + + /** + * Constructs a new buffer attribute node. + * + * @param {BufferAttribute|InterleavedBuffer|TypedArray} value - The attribute data. + * @param {?string} [bufferType=null] - The buffer type (e.g. `'vec3'`). + * @param {number} [bufferStride=0] - The buffer stride. + * @param {number} [bufferOffset=0] - The buffer offset. + */ + constructor( value, bufferType = null, bufferStride = 0, bufferOffset = 0 ) { + + super( value, bufferType ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isBufferNode = true; + + /** + * The buffer type (e.g. `'vec3'`). + * + * @type {?string} + * @default null + */ + this.bufferType = bufferType; + + /** + * The buffer stride. + * + * @type {number} + * @default 0 + */ + this.bufferStride = bufferStride; + + /** + * The buffer offset. + * + * @type {number} + * @default 0 + */ + this.bufferOffset = bufferOffset; + + /** + * The usage property. Set this to `THREE.DynamicDrawUsage` via `.setUsage()`, + * if you are planning to update the attribute data per frame. + * + * @type {number} + * @default StaticDrawUsage + */ + this.usage = StaticDrawUsage; + + /** + * Whether the attribute is instanced or not. + * + * @type {boolean} + * @default false + */ + this.instanced = false; + + /** + * A reference to the buffer attribute. + * + * @type {?BufferAttribute} + * @default null + */ + this.attribute = null; + + /** + * `BufferAttributeNode` sets this property to `true` by default. + * + * @type {boolean} + * @default true + */ + this.global = true; + + if ( value && value.isBufferAttribute === true ) { + + this.attribute = value; + this.usage = value.usage; + this.instanced = value.isInstancedBufferAttribute; + + } + + } + + /** + * This method is overwritten since the attribute data might be shared + * and thus the hash should be shared as well. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The hash. + */ + getHash( builder ) { + + if ( this.bufferStride === 0 && this.bufferOffset === 0 ) { + + let bufferData = builder.globalCache.getData( this.value ); + + if ( bufferData === undefined ) { + + bufferData = { + node: this + }; + + builder.globalCache.setData( this.value, bufferData ); + + } + + return bufferData.node.uuid; + + } + + return this.uuid; + + } + + /** + * This method is overwritten since the node type is inferred from + * the buffer attribute. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType( builder ) { + + if ( this.bufferType === null ) { + + this.bufferType = builder.getTypeFromAttribute( this.attribute ); + + } + + return this.bufferType; + + } + + /** + * Depending on which value was passed to the node, `setup()` behaves + * differently. If no instance of `BufferAttribute` was passed, the method + * creates an internal attribute and configures it respectively. + * + * @param {NodeBuilder} builder - The current node builder. + */ + setup( builder ) { + + if ( this.attribute !== null ) return; + + const type = this.getNodeType( builder ); + const array = this.value; + const itemSize = builder.getTypeLength( type ); + const stride = this.bufferStride || itemSize; + const offset = this.bufferOffset; + + const buffer = array.isInterleavedBuffer === true ? array : new InterleavedBuffer( array, stride ); + const bufferAttribute = new InterleavedBufferAttribute( buffer, itemSize, offset ); + + buffer.setUsage( this.usage ); + + this.attribute = bufferAttribute; + this.attribute.isInstancedBufferAttribute = this.instanced; // @TODO: Add a possible: InstancedInterleavedBufferAttribute + + } + + /** + * Generates the code snippet of the buffer attribute node. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The generated code snippet. + */ + generate( builder ) { + + const nodeType = this.getNodeType( builder ); + + const nodeAttribute = builder.getBufferAttributeFromNode( this, nodeType ); + const propertyName = builder.getPropertyName( nodeAttribute ); + + let output = null; + + if ( builder.shaderStage === 'vertex' || builder.shaderStage === 'compute' ) { + + this.name = propertyName; + + output = propertyName; + + } else { + + const nodeVarying = varying( this ); + + output = nodeVarying.build( builder, nodeType ); + + } + + return output; + + } + + /** + * Overwrites the default implementation to return a fixed value `'bufferAttribute'`. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The input type. + */ + getInputType( /*builder*/ ) { + + return 'bufferAttribute'; + + } + + /** + * Sets the `usage` property to the given value. + * + * @param {number} value - The usage to set. + * @return {BufferAttributeNode} A reference to this node. + */ + setUsage( value ) { + + this.usage = value; + + if ( this.attribute && this.attribute.isBufferAttribute === true ) { + + this.attribute.usage = value; + + } + + return this; + + } + + /** + * Sets the `instanced` property to the given value. + * + * @param {boolean} value - The value to set. + * @return {BufferAttributeNode} A reference to this node. + */ + setInstanced( value ) { + + this.instanced = value; + + return this; + + } + +} + +/** + * TSL function for creating a buffer attribute node. + * + * @tsl + * @function + * @param {BufferAttribute|InterleavedBuffer|TypedArray} array - The attribute data. + * @param {?string} [type=null] - The buffer type (e.g. `'vec3'`). + * @param {number} [stride=0] - The buffer stride. + * @param {number} [offset=0] - The buffer offset. + * @returns {BufferAttributeNode} + */ +const bufferAttribute = ( array, type = null, stride = 0, offset = 0 ) => nodeObject( new BufferAttributeNode( array, type, stride, offset ) ); + +/** + * TSL function for creating a buffer attribute node but with dynamic draw usage. + * Use this function if attribute data are updated per frame. + * + * @tsl + * @function + * @param {BufferAttribute|InterleavedBuffer|TypedArray} array - The attribute data. + * @param {?string} [type=null] - The buffer type (e.g. `'vec3'`). + * @param {number} [stride=0] - The buffer stride. + * @param {number} [offset=0] - The buffer offset. + * @returns {BufferAttributeNode} + */ +const dynamicBufferAttribute = ( array, type = null, stride = 0, offset = 0 ) => bufferAttribute( array, type, stride, offset ).setUsage( DynamicDrawUsage ); + +/** + * TSL function for creating a buffer attribute node but with enabled instancing + * + * @tsl + * @function + * @param {BufferAttribute|InterleavedBuffer|TypedArray} array - The attribute data. + * @param {?string} [type=null] - The buffer type (e.g. `'vec3'`). + * @param {number} [stride=0] - The buffer stride. + * @param {number} [offset=0] - The buffer offset. + * @returns {BufferAttributeNode} + */ +const instancedBufferAttribute = ( array, type = null, stride = 0, offset = 0 ) => bufferAttribute( array, type, stride, offset ).setInstanced( true ); + +/** + * TSL function for creating a buffer attribute node but with dynamic draw usage and enabled instancing + * + * @tsl + * @function + * @param {BufferAttribute|InterleavedBuffer|TypedArray} array - The attribute data. + * @param {?string} [type=null] - The buffer type (e.g. `'vec3'`). + * @param {number} [stride=0] - The buffer stride. + * @param {number} [offset=0] - The buffer offset. + * @returns {BufferAttributeNode} + */ +const instancedDynamicBufferAttribute = ( array, type = null, stride = 0, offset = 0 ) => dynamicBufferAttribute( array, type, stride, offset ).setInstanced( true ); + +addMethodChaining( 'toAttribute', ( bufferNode ) => bufferAttribute( bufferNode.value ) ); + +/** + * TODO + * + * @augments Node + */ +class ComputeNode extends Node { + + static get type() { + + return 'ComputeNode'; + + } + + /** + * Constructs a new compute node. + * + * @param {Node} computeNode - TODO + * @param {number} count - TODO. + * @param {Array} [workgroupSize=[64]] - TODO. + */ + constructor( computeNode, count, workgroupSize = [ 64 ] ) { + + super( 'void' ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isComputeNode = true; + + /** + * TODO + * + * @type {Node} + */ + this.computeNode = computeNode; + + /** + * TODO + * + * @type {number} + */ + this.count = count; + + /** + * TODO + * + * @type {Array} + * @default [64] + */ + this.workgroupSize = workgroupSize; + + /** + * TODO + * + * @type {number} + */ + this.dispatchCount = 0; + + /** + * TODO + * + * @type {number} + */ + this.version = 1; + + /** + * The name or label of the uniform. + * + * @type {string} + * @default '' + */ + this.name = ''; + + /** + * The `updateBeforeType` is set to `NodeUpdateType.OBJECT` since {@link ComputeNode#updateBefore} + * is executed once per object by default. + * + * @type {string} + * @default 'object' + */ + this.updateBeforeType = NodeUpdateType.OBJECT; + + /** + * TODO + * + * @type {?Function} + */ + this.onInitFunction = null; + + this.updateDispatchCount(); + + } + + /** + * Executes the `dispose` event for this node. + */ + dispose() { + + this.dispatchEvent( { type: 'dispose' } ); + + } + + /** + * Sets the {@link ComputeNode#name} property. + * + * @param {string} name - The name of the uniform. + * @return {ComputeNode} A reference to this node. + */ + label( name ) { + + this.name = name; + + return this; + + } + + /** + * TODO + */ + updateDispatchCount() { + + const { count, workgroupSize } = this; + + let size = workgroupSize[ 0 ]; + + for ( let i = 1; i < workgroupSize.length; i ++ ) + size *= workgroupSize[ i ]; + + this.dispatchCount = Math.ceil( count / size ); + + } + + /** + * TODO + * + * @param {Function} callback - TODO. + * @return {ComputeNode} A reference to this node. + */ + onInit( callback ) { + + this.onInitFunction = callback; + + return this; + + } + + /** + * The method execute the compute for this node. + * + * @param {NodeFrame} frame - A reference to the current node frame. + */ + updateBefore( { renderer } ) { + + renderer.compute( this ); + + } + + setup( builder ) { + + const result = this.computeNode.build( builder ); + + if ( result ) { + + const properties = builder.getNodeProperties( this ); + properties.outputComputeNode = result.outputNode; + + result.outputNode = null; + + } + + return result; + + } + + generate( builder, output ) { + + const { shaderStage } = builder; + + if ( shaderStage === 'compute' ) { + + const snippet = this.computeNode.build( builder, 'void' ); + + if ( snippet !== '' ) { + + builder.addLineFlowCode( snippet, this ); + + } + + } else { + + const properties = builder.getNodeProperties( this ); + const outputComputeNode = properties.outputComputeNode; + + if ( outputComputeNode ) { + + return outputComputeNode.build( builder, output ); + + } + + } + + } + +} + +/** + * TSL function for creating a compute node. + * + * @tsl + * @function + * @param {Node} node - TODO + * @param {number} count - TODO. + * @param {Array} [workgroupSize=[64]] - TODO. + * @returns {AtomicFunctionNode} + */ +const compute = ( node, count, workgroupSize ) => nodeObject( new ComputeNode( nodeObject( node ), count, workgroupSize ) ); + +addMethodChaining( 'compute', compute ); + +/** + * This node can be used as a cache management component for another node. + * Caching is in general used by default in {@link NodeBuilder} but this node + * allows the usage of a shared parent cache during the build process. + * + * @augments Node + */ +class CacheNode extends Node { + + static get type() { + + return 'CacheNode'; + + } + + /** + * Constructs a new cache node. + * + * @param {Node} node - The node that should be cached. + * @param {boolean} [parent=true] - Whether this node refers to a shared parent cache or not. + */ + constructor( node, parent = true ) { + + super(); + + /** + * The node that should be cached. + * + * @type {Node} + */ + this.node = node; + + /** + * Whether this node refers to a shared parent cache or not. + * + * @type {boolean} + * @default true + */ + this.parent = parent; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isCacheNode = true; + + } + + getNodeType( builder ) { + + const previousCache = builder.getCache(); + const cache = builder.getCacheFromNode( this, this.parent ); + + builder.setCache( cache ); + + const nodeType = this.node.getNodeType( builder ); + + builder.setCache( previousCache ); + + return nodeType; + + } + + build( builder, ...params ) { + + const previousCache = builder.getCache(); + const cache = builder.getCacheFromNode( this, this.parent ); + + builder.setCache( cache ); + + const data = this.node.build( builder, ...params ); + + builder.setCache( previousCache ); + + return data; + + } + +} + +/** + * TSL function for creating a cache node. + * + * @tsl + * @function + * @param {Node} node - The node that should be cached. + * @param {boolean} [parent] - Whether this node refers to a shared parent cache or not. + * @returns {CacheNode} + */ +const cache = ( node, parent ) => nodeObject( new CacheNode( nodeObject( node ), parent ) ); + +addMethodChaining( 'cache', cache ); + +/** + * The class generates the code of a given node but returns another node in the output. + * This can be used to call a method or node that does not return a value, i.e. + * type `void` on an input where returning a value is required. Example: + * + * ```js + * material.colorNode = myColor.bypass( runVoidFn() ) + *``` + * + * @augments Node + */ +class BypassNode extends Node { + + static get type() { + + return 'BypassNode'; + + } + + /** + * Constructs a new bypass node. + * + * @param {Node} outputNode - The output node. + * @param {Node} callNode - The call node. + */ + constructor( outputNode, callNode ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isBypassNode = true; + + /** + * The output node. + * + * @type {Node} + */ + this.outputNode = outputNode; + + /** + * The call node. + * + * @type {Node} + */ + this.callNode = callNode; + + } + + getNodeType( builder ) { + + return this.outputNode.getNodeType( builder ); + + } + + generate( builder ) { + + const snippet = this.callNode.build( builder, 'void' ); + + if ( snippet !== '' ) { + + builder.addLineFlowCode( snippet, this ); + + } + + return this.outputNode.build( builder ); + + } + +} + +/** + * TSL function for creating a bypass node. + * + * @tsl + * @function + * @param {Node} outputNode - The output node. + * @param {Node} callNode - The call node. + * @returns {BypassNode} + */ +const bypass = /*@__PURE__*/ nodeProxy( BypassNode ).setParameterLength( 2 ); + +addMethodChaining( 'bypass', bypass ); + +/** + * This node allows to remap a node value from one range into another. E.g a value of + * `0.4` in the range `[ 0.3, 0.5 ]` should be remapped into the normalized range `[ 0, 1 ]`. + * `RemapNode` takes care of that and converts the original value of `0.4` to `0.5`. + * + * @augments Node + */ +class RemapNode extends Node { + + static get type() { + + return 'RemapNode'; + + } + + /** + * Constructs a new remap node. + * + * @param {Node} node - The node that should be remapped. + * @param {Node} inLowNode - The source or current lower bound of the range. + * @param {Node} inHighNode - The source or current upper bound of the range. + * @param {Node} [outLowNode=float(0)] - The target lower bound of the range. + * @param {Node} [outHighNode=float(1)] - The target upper bound of the range. + */ + constructor( node, inLowNode, inHighNode, outLowNode = float( 0 ), outHighNode = float( 1 ) ) { + + super(); + + /** + * The node that should be remapped. + * + * @type {Node} + */ + this.node = node; + + /** + * The source or current lower bound of the range. + * + * @type {Node} + */ + this.inLowNode = inLowNode; + + /** + * The source or current upper bound of the range. + * + * @type {Node} + */ + this.inHighNode = inHighNode; + + /** + * The target lower bound of the range. + * + * @type {Node} + * @default float(0) + */ + this.outLowNode = outLowNode; + + /** + * The target upper bound of the range. + * + * @type {Node} + * @default float(1) + */ + this.outHighNode = outHighNode; + + /** + * Whether the node value should be clamped before + * remapping it to the target range. + * + * @type {boolean} + * @default true + */ + this.doClamp = true; + + } + + setup() { + + const { node, inLowNode, inHighNode, outLowNode, outHighNode, doClamp } = this; + + let t = node.sub( inLowNode ).div( inHighNode.sub( inLowNode ) ); + + if ( doClamp === true ) t = t.clamp(); + + return t.mul( outHighNode.sub( outLowNode ) ).add( outLowNode ); + + } + +} + +/** + * TSL function for creating a remap node. + * + * @tsl + * @function + * @param {Node} node - The node that should be remapped. + * @param {Node} inLowNode - The source or current lower bound of the range. + * @param {Node} inHighNode - The source or current upper bound of the range. + * @param {?Node} [outLowNode=float(0)] - The target lower bound of the range. + * @param {?Node} [outHighNode=float(1)] - The target upper bound of the range. + * @returns {RemapNode} + */ +const remap = /*@__PURE__*/ nodeProxy( RemapNode, null, null, { doClamp: false } ).setParameterLength( 3, 5 ); + +/** + * TSL function for creating a remap node, but with enabled clamping. + * + * @tsl + * @function + * @param {Node} node - The node that should be remapped. + * @param {Node} inLowNode - The source or current lower bound of the range. + * @param {Node} inHighNode - The source or current upper bound of the range. + * @param {?Node} [outLowNode=float(0)] - The target lower bound of the range. + * @param {?Node} [outHighNode=float(1)] - The target upper bound of the range. + * @returns {RemapNode} + */ +const remapClamp = /*@__PURE__*/ nodeProxy( RemapNode ).setParameterLength( 3, 5 ); + +addMethodChaining( 'remap', remap ); +addMethodChaining( 'remapClamp', remapClamp ); + +/** + * This class can be used to implement basic expressions in shader code. + * Basic examples for that are `return`, `continue` or `discard` statements. + * + * @augments Node + */ +class ExpressionNode extends Node { + + static get type() { + + return 'ExpressionNode'; + + } + + /** + * Constructs a new expression node. + * + * @param {string} [snippet=''] - The native code snippet. + * @param {string} [nodeType='void'] - The node type. + */ + constructor( snippet = '', nodeType = 'void' ) { + + super( nodeType ); + + /** + * The native code snippet. + * + * @type {string} + * @default '' + */ + this.snippet = snippet; + + } + + generate( builder, output ) { + + const type = this.getNodeType( builder ); + const snippet = this.snippet; + + if ( type === 'void' ) { + + builder.addLineFlowCode( snippet, this ); + + } else { + + return builder.format( snippet, type, output ); + + } + + } + +} + +/** + * TSL function for creating an expression node. + * + * @tsl + * @function + * @param {string} [snippet] - The native code snippet. + * @param {?string} [nodeType='void'] - The node type. + * @returns {ExpressionNode} + */ +const expression = /*@__PURE__*/ nodeProxy( ExpressionNode ).setParameterLength( 1, 2 ); + +/** + * Represents a `discard` shader operation in TSL. + * + * @tsl + * @function + * @param {?ConditionalNode} conditional - An optional conditional node. It allows to decide whether the discard should be executed or not. + * @return {Node} The `discard` expression. + */ +const Discard = ( conditional ) => ( conditional ? select( conditional, expression( 'discard' ) ) : expression( 'discard' ) ).toStack(); + +/** + * Represents a `return` shader operation in TSL. + * + * @tsl + * @function + * @return {ExpressionNode} The `return` expression. + */ +const Return = () => expression( 'return' ).toStack(); + +addMethodChaining( 'discard', Discard ); + +/** + * Normally, tone mapping and color conversion happens automatically + * before outputting pixel too the default (screen) framebuffer. In certain + * post processing setups this happens to late because certain effects + * require e.g. sRGB input. For such scenarios, `RenderOutputNode` can be used + * to apply tone mapping and color space conversion at an arbitrary point + * in the effect chain. + * + * When applying tone mapping and color space conversion manually with this node, + * you have to set {@link PostProcessing#outputColorTransform} to `false`. + * + * ```js + * const postProcessing = new PostProcessing( renderer ); + * postProcessing.outputColorTransform = false; + * + * const scenePass = pass( scene, camera ); + * const outputPass = renderOutput( scenePass ); + * + * postProcessing.outputNode = outputPass; + * ``` + * + * @augments TempNode + */ +class RenderOutputNode extends TempNode { + + static get type() { + + return 'RenderOutputNode'; + + } + + /** + * Constructs a new render output node. + * + * @param {Node} colorNode - The color node to process. + * @param {?number} toneMapping - The tone mapping type. + * @param {?string} outputColorSpace - The output color space. + */ + constructor( colorNode, toneMapping, outputColorSpace ) { + + super( 'vec4' ); + + /** + * The color node to process. + * + * @type {Node} + */ + this.colorNode = colorNode; + + /** + * The tone mapping type. + * + * @type {?number} + */ + this.toneMapping = toneMapping; + + /** + * The output color space. + * + * @type {?string} + */ + this.outputColorSpace = outputColorSpace; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isRenderOutputNode = true; + + } + + setup( { context } ) { + + let outputNode = this.colorNode || context.color; + + // tone mapping + + const toneMapping = ( this.toneMapping !== null ? this.toneMapping : context.toneMapping ) || NoToneMapping; + const outputColorSpace = ( this.outputColorSpace !== null ? this.outputColorSpace : context.outputColorSpace ) || NoColorSpace; + + if ( toneMapping !== NoToneMapping ) { + + outputNode = outputNode.toneMapping( toneMapping ); + + } + + // working to output color space + + if ( outputColorSpace !== NoColorSpace && outputColorSpace !== ColorManagement.workingColorSpace ) { + + outputNode = outputNode.workingToColorSpace( outputColorSpace ); + + } + + return outputNode; + + } + +} + +/** + * TSL function for creating a posterize node. + * + * @tsl + * @function + * @param {Node} color - The color node to process. + * @param {?number} [toneMapping=null] - The tone mapping type. + * @param {?string} [outputColorSpace=null] - The output color space. + * @returns {RenderOutputNode} + */ +const renderOutput = ( color, toneMapping = null, outputColorSpace = null ) => nodeObject( new RenderOutputNode( nodeObject( color ), toneMapping, outputColorSpace ) ); + +addMethodChaining( 'renderOutput', renderOutput ); + +class DebugNode extends TempNode { + + static get type() { + + return 'DebugNode'; + + } + + constructor( node, callback = null ) { + + super(); + + this.node = node; + this.callback = callback; + + } + + getNodeType( builder ) { + + return this.node.getNodeType( builder ); + + } + + setup( builder ) { + + return this.node.build( builder ); + + } + + analyze( builder ) { + + return this.node.build( builder ); + + } + + generate( builder ) { + + const callback = this.callback; + const snippet = this.node.build( builder ); + + const title = '--- TSL debug - ' + builder.shaderStage + ' shader ---'; + const border = '-'.repeat( title.length ); + + let code = ''; + code += '// #' + title + '#\n'; + code += builder.flow.code.replace( /^\t/mg, '' ) + '\n'; + code += '/* ... */ ' + snippet + ' /* ... */\n'; + code += '// #' + border + '#\n'; + + if ( callback !== null ) { + + callback( builder, code ); + + } else { + + console.log( code ); + + } + + return snippet; + + } + +} + +/** + * TSL function for creating a debug node. + * + * @tsl + * @function + * @param {Node} node - The node to debug. + * @param {?Function} [callback=null] - Optional callback function to handle the debug output. + * @returns {DebugNode} + */ +const debug = ( node, callback = null ) => nodeObject( new DebugNode( nodeObject( node ), callback ) ); + +addMethodChaining( 'debug', debug ); + +// Non-PURE exports list, side-effects are required here. +// TSL Base Syntax + + +function addNodeElement( name/*, nodeElement*/ ) { + + console.warn( 'THREE.TSL: AddNodeElement has been removed in favor of tree-shaking. Trying add', name ); + +} + +/** + * Base class for representing shader attributes as nodes. + * + * @augments Node + */ +class AttributeNode extends Node { + + static get type() { + + return 'AttributeNode'; + + } + + /** + * Constructs a new attribute node. + * + * @param {string} attributeName - The name of the attribute. + * @param {?string} nodeType - The node type. + */ + constructor( attributeName, nodeType = null ) { + + super( nodeType ); + + /** + * `AttributeNode` sets this property to `true` by default. + * + * @type {boolean} + * @default true + */ + this.global = true; + + this._attributeName = attributeName; + + } + + getHash( builder ) { + + return this.getAttributeName( builder ); + + } + + getNodeType( builder ) { + + let nodeType = this.nodeType; + + if ( nodeType === null ) { + + const attributeName = this.getAttributeName( builder ); + + if ( builder.hasGeometryAttribute( attributeName ) ) { + + const attribute = builder.geometry.getAttribute( attributeName ); + + nodeType = builder.getTypeFromAttribute( attribute ); + + } else { + + nodeType = 'float'; + + } + + } + + return nodeType; + + } + + /** + * Sets the attribute name to the given value. The method can be + * overwritten in derived classes if the final name must be computed + * analytically. + * + * @param {string} attributeName - The name of the attribute. + * @return {AttributeNode} A reference to this node. + */ + setAttributeName( attributeName ) { + + this._attributeName = attributeName; + + return this; + + } + + /** + * Returns the attribute name of this node. The method can be + * overwritten in derived classes if the final name must be computed + * analytically. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The attribute name. + */ + getAttributeName( /*builder*/ ) { + + return this._attributeName; + + } + + generate( builder ) { + + const attributeName = this.getAttributeName( builder ); + const nodeType = this.getNodeType( builder ); + const geometryAttribute = builder.hasGeometryAttribute( attributeName ); + + if ( geometryAttribute === true ) { + + const attribute = builder.geometry.getAttribute( attributeName ); + const attributeType = builder.getTypeFromAttribute( attribute ); + + const nodeAttribute = builder.getAttribute( attributeName, attributeType ); + + if ( builder.shaderStage === 'vertex' ) { + + return builder.format( nodeAttribute.name, attributeType, nodeType ); + + } else { + + const nodeVarying = varying( this ); + + return nodeVarying.build( builder, nodeType ); + + } + + } else { + + console.warn( `AttributeNode: Vertex attribute "${ attributeName }" not found on geometry.` ); + + return builder.generateConst( nodeType ); + + } + + } + + serialize( data ) { + + super.serialize( data ); + + data.global = this.global; + data._attributeName = this._attributeName; + + } + + deserialize( data ) { + + super.deserialize( data ); + + this.global = data.global; + this._attributeName = data._attributeName; + + } + +} + +/** + * TSL function for creating an attribute node. + * + * @tsl + * @function + * @param {string} name - The name of the attribute. + * @param {?string} [nodeType=null] - The node type. + * @returns {AttributeNode} + */ +const attribute = ( name, nodeType = null ) => nodeObject( new AttributeNode( name, nodeType ) ); + +/** + * TSL function for creating an uv attribute node with the given index. + * + * @tsl + * @function + * @param {number} [index=0] - The uv index. + * @return {AttributeNode} The uv attribute node. + */ +const uv$1 = ( index = 0 ) => attribute( 'uv' + ( index > 0 ? index : '' ), 'vec2' ); + +/** + * A node that represents the dimensions of a texture. The texture size is + * retrieved in the shader via built-in shader functions like `textureDimensions()` + * or `textureSize()`. + * + * @augments Node + */ +class TextureSizeNode extends Node { + + static get type() { + + return 'TextureSizeNode'; + + } + + /** + * Constructs a new texture size node. + * + * @param {TextureNode} textureNode - A texture node which size should be retrieved. + * @param {?Node} [levelNode=null] - A level node which defines the requested mip. + */ + constructor( textureNode, levelNode = null ) { + + super( 'uvec2' ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isTextureSizeNode = true; + + /** + * A texture node which size should be retrieved. + * + * @type {TextureNode} + */ + this.textureNode = textureNode; + + /** + * A level node which defines the requested mip. + * + * @type {Node} + * @default null + */ + this.levelNode = levelNode; + + } + + generate( builder, output ) { + + const textureProperty = this.textureNode.build( builder, 'property' ); + const level = this.levelNode === null ? '0' : this.levelNode.build( builder, 'int' ); + + return builder.format( `${ builder.getMethod( 'textureDimensions' ) }( ${ textureProperty }, ${ level } )`, this.getNodeType( builder ), output ); + + } + +} + +/** + * TSL function for creating a texture size node. + * + * @tsl + * @function + * @param {TextureNode} textureNode - A texture node which size should be retrieved. + * @param {?Node} [levelNode=null] - A level node which defines the requested mip. + * @returns {TextureSizeNode} + */ +const textureSize = /*@__PURE__*/ nodeProxy( TextureSizeNode ).setParameterLength( 1, 2 ); + +/** + * A special type of uniform node that computes the + * maximum mipmap level for a given texture node. + * + * ```js + * const level = maxMipLevel( textureNode ); + * ``` + * + * @augments UniformNode + */ +class MaxMipLevelNode extends UniformNode { + + static get type() { + + return 'MaxMipLevelNode'; + + } + + /** + * Constructs a new max mip level node. + * + * @param {TextureNode} textureNode - The texture node to compute the max mip level for. + */ + constructor( textureNode ) { + + super( 0 ); + + /** + * The texture node to compute the max mip level for. + * + * @private + * @type {TextureNode} + */ + this._textureNode = textureNode; + + /** + * The `updateType` is set to `NodeUpdateType.FRAME` since the node updates + * the texture once per frame in its {@link MaxMipLevelNode#update} method. + * + * @type {string} + * @default 'frame' + */ + this.updateType = NodeUpdateType.FRAME; + + } + + /** + * The texture node to compute the max mip level for. + * + * @readonly + * @type {TextureNode} + */ + get textureNode() { + + return this._textureNode; + + } + + /** + * The texture. + * + * @readonly + * @type {Texture} + */ + get texture() { + + return this._textureNode.value; + + } + + update() { + + const texture = this.texture; + const images = texture.images; + const image = ( images && images.length > 0 ) ? ( ( images[ 0 ] && images[ 0 ].image ) || images[ 0 ] ) : texture.image; + + if ( image && image.width !== undefined ) { + + const { width, height } = image; + + this.value = Math.log2( Math.max( width, height ) ); + + } + + } + +} + +/** + * TSL function for creating a max mip level node. + * + * @tsl + * @function + * @param {TextureNode} textureNode - The texture node to compute the max mip level for. + * @returns {MaxMipLevelNode} + */ +const maxMipLevel = /*@__PURE__*/ nodeProxy( MaxMipLevelNode ).setParameterLength( 1 ); + +const EmptyTexture$1 = /*@__PURE__*/ new Texture(); + +/** + * This type of uniform node represents a 2D texture. + * + * @augments UniformNode + */ +class TextureNode extends UniformNode { + + static get type() { + + return 'TextureNode'; + + } + + /** + * Constructs a new texture node. + * + * @param {Texture} [value=EmptyTexture] - The texture. + * @param {?Node} [uvNode=null] - The uv node. + * @param {?Node} [levelNode=null] - The level node. + * @param {?Node} [biasNode=null] - The bias node. + */ + constructor( value = EmptyTexture$1, uvNode = null, levelNode = null, biasNode = null ) { + + super( value ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isTextureNode = true; + + /** + * Represents the texture coordinates. + * + * @type {?Node} + * @default null + */ + this.uvNode = uvNode; + + /** + * Represents the mip level that should be selected. + * + * @type {?Node} + * @default null + */ + this.levelNode = levelNode; + + /** + * Represents the bias to be applied during level-of-detail computation. + * + * @type {?Node} + * @default null + */ + this.biasNode = biasNode; + + /** + * Represents a reference value a texture sample is compared to. + * + * @type {?Node} + * @default null + */ + this.compareNode = null; + + /** + * When using texture arrays, the depth node defines the layer to select. + * + * @type {?Node} + * @default null + */ + this.depthNode = null; + + /** + * When defined, a texture is sampled using explicit gradients. + * + * @type {?Array>} + * @default null + */ + this.gradNode = null; + + /** + * Whether texture values should be sampled or fetched. + * + * @type {boolean} + * @default true + */ + this.sampler = true; + + /** + * Whether the uv transformation matrix should be + * automatically updated or not. Use `setUpdateMatrix()` + * if you want to change the value of the property. + * + * @type {boolean} + * @default false + */ + this.updateMatrix = false; + + /** + * By default the `update()` method is not executed. `setUpdateMatrix()` + * sets the value to `frame` when the uv transformation matrix should + * automatically be updated. + * + * @type {string} + * @default 'none' + */ + this.updateType = NodeUpdateType.NONE; + + /** + * The reference node. + * + * @type {?Node} + * @default null + */ + this.referenceNode = null; + + /** + * The texture value is stored in a private property. + * + * @private + * @type {Texture} + */ + this._value = value; + + /** + * The uniform node that represents the uv transformation matrix. + * + * @private + * @type {?UniformNode} + */ + this._matrixUniform = null; + + this.setUpdateMatrix( uvNode === null ); + + } + + set value( value ) { + + if ( this.referenceNode ) { + + this.referenceNode.value = value; + + } else { + + this._value = value; + + } + + } + + /** + * The texture value. + * + * @type {Texture} + */ + get value() { + + return this.referenceNode ? this.referenceNode.value : this._value; + + } + + /** + * Overwritten since the uniform hash is defined by the texture's UUID. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The uniform hash. + */ + getUniformHash( /*builder*/ ) { + + return this.value.uuid; + + } + + /** + * Overwritten since the node type is inferred from the texture type. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType( /*builder*/ ) { + + if ( this.value.isDepthTexture === true ) return 'float'; + + if ( this.value.type === UnsignedIntType ) { + + return 'uvec4'; + + } else if ( this.value.type === IntType ) { + + return 'ivec4'; + + } + + return 'vec4'; + + } + + /** + * Overwrites the default implementation to return a fixed value `'texture'`. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The input type. + */ + getInputType( /*builder*/ ) { + + return 'texture'; + + } + + /** + * Returns a default uvs based on the current texture's channel. + * + * @return {AttributeNode} The default uvs. + */ + getDefaultUV() { + + return uv$1( this.value.channel ); + + } + + /** + * Overwritten to always return the texture reference of the node. + * + * @param {any} state - This method can be invocated in different contexts so `state` can refer to any object type. + * @return {Texture} The texture reference. + */ + updateReference( /*state*/ ) { + + return this.value; + + } + + /** + * Transforms the given uv node with the texture transformation matrix. + * + * @param {Node} uvNode - The uv node to transform. + * @return {Node} The transformed uv node. + */ + getTransformedUV( uvNode ) { + + if ( this._matrixUniform === null ) this._matrixUniform = uniform( this.value.matrix ); + + return this._matrixUniform.mul( vec3( uvNode, 1 ) ).xy; + + } + + /** + * Defines whether the uv transformation matrix should automatically be updated or not. + * + * @param {boolean} value - The update toggle. + * @return {TextureNode} A reference to this node. + */ + setUpdateMatrix( value ) { + + this.updateMatrix = value; + this.updateType = value ? NodeUpdateType.OBJECT : NodeUpdateType.NONE; + + return this; + + } + + /** + * Setups the uv node. Depending on the backend as well as texture's image and type, it might be necessary + * to modify the uv node for correct sampling. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {Node} uvNode - The uv node to setup. + * @return {Node} The updated uv node. + */ + setupUV( builder, uvNode ) { + + const texture = this.value; + + if ( builder.isFlipY() && ( ( texture.image instanceof ImageBitmap && texture.flipY === true ) || texture.isRenderTargetTexture === true || texture.isFramebufferTexture === true || texture.isDepthTexture === true ) ) { + + if ( this.sampler ) { + + uvNode = uvNode.flipY(); + + } else { + + uvNode = uvNode.setY( int( textureSize( this, this.levelNode ).y ).sub( uvNode.y ).sub( 1 ) ); + + } + + } + + return uvNode; + + } + + /** + * Setups texture node by preparing the internal nodes for code generation. + * + * @param {NodeBuilder} builder - The current node builder. + */ + setup( builder ) { + + const properties = builder.getNodeProperties( this ); + properties.referenceNode = this.referenceNode; + + // + + const texture = this.value; + + if ( ! texture || texture.isTexture !== true ) { + + throw new Error( 'THREE.TSL: `texture( value )` function expects a valid instance of THREE.Texture().' ); + + } + + // + + let uvNode = this.uvNode; + + if ( ( uvNode === null || builder.context.forceUVContext === true ) && builder.context.getUV ) { + + uvNode = builder.context.getUV( this, builder ); + + } + + if ( ! uvNode ) uvNode = this.getDefaultUV(); + + if ( this.updateMatrix === true ) { + + uvNode = this.getTransformedUV( uvNode ); + + } + + uvNode = this.setupUV( builder, uvNode ); + + // + + let levelNode = this.levelNode; + + if ( levelNode === null && builder.context.getTextureLevel ) { + + levelNode = builder.context.getTextureLevel( this ); + + } + + // + + properties.uvNode = uvNode; + properties.levelNode = levelNode; + properties.biasNode = this.biasNode; + properties.compareNode = this.compareNode; + properties.gradNode = this.gradNode; + properties.depthNode = this.depthNode; + + } + + /** + * Generates the uv code snippet. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {Node} uvNode - The uv node to generate code for. + * @return {string} The generated code snippet. + */ + generateUV( builder, uvNode ) { + + return uvNode.build( builder, this.sampler === true ? 'vec2' : 'ivec2' ); + + } + + /** + * Generates the snippet for the texture sampling. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {string} textureProperty - The texture property. + * @param {string} uvSnippet - The uv snippet. + * @param {?string} levelSnippet - The level snippet. + * @param {?string} biasSnippet - The bias snippet. + * @param {?string} depthSnippet - The depth snippet. + * @param {?string} compareSnippet - The compare snippet. + * @param {?Array} gradSnippet - The grad snippet. + * @return {string} The generated code snippet. + */ + generateSnippet( builder, textureProperty, uvSnippet, levelSnippet, biasSnippet, depthSnippet, compareSnippet, gradSnippet ) { + + const texture = this.value; + + let snippet; + + if ( levelSnippet ) { + + snippet = builder.generateTextureLevel( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet ); + + } else if ( biasSnippet ) { + + snippet = builder.generateTextureBias( texture, textureProperty, uvSnippet, biasSnippet, depthSnippet ); + + } else if ( gradSnippet ) { + + snippet = builder.generateTextureGrad( texture, textureProperty, uvSnippet, gradSnippet, depthSnippet ); + + } else if ( compareSnippet ) { + + snippet = builder.generateTextureCompare( texture, textureProperty, uvSnippet, compareSnippet, depthSnippet ); + + } else if ( this.sampler === false ) { + + snippet = builder.generateTextureLoad( texture, textureProperty, uvSnippet, depthSnippet ); + + } else { + + snippet = builder.generateTexture( texture, textureProperty, uvSnippet, depthSnippet ); + + } + + return snippet; + + } + + /** + * Generates the code snippet of the texture node. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {string} output - The current output. + * @return {string} The generated code snippet. + */ + generate( builder, output ) { + + const texture = this.value; + + const properties = builder.getNodeProperties( this ); + const textureProperty = super.generate( builder, 'property' ); + + if ( /^sampler/.test( output ) ) { + + return textureProperty + '_sampler'; + + } else if ( builder.isReference( output ) ) { + + return textureProperty; + + } else { + + const nodeData = builder.getDataFromNode( this ); + + let propertyName = nodeData.propertyName; + + if ( propertyName === undefined ) { + + const { uvNode, levelNode, biasNode, compareNode, depthNode, gradNode } = properties; + + const uvSnippet = this.generateUV( builder, uvNode ); + const levelSnippet = levelNode ? levelNode.build( builder, 'float' ) : null; + const biasSnippet = biasNode ? biasNode.build( builder, 'float' ) : null; + const depthSnippet = depthNode ? depthNode.build( builder, 'int' ) : null; + const compareSnippet = compareNode ? compareNode.build( builder, 'float' ) : null; + const gradSnippet = gradNode ? [ gradNode[ 0 ].build( builder, 'vec2' ), gradNode[ 1 ].build( builder, 'vec2' ) ] : null; + + const nodeVar = builder.getVarFromNode( this ); + + propertyName = builder.getPropertyName( nodeVar ); + + const snippet = this.generateSnippet( builder, textureProperty, uvSnippet, levelSnippet, biasSnippet, depthSnippet, compareSnippet, gradSnippet ); + + builder.addLineFlowCode( `${propertyName} = ${snippet}`, this ); + + nodeData.snippet = snippet; + nodeData.propertyName = propertyName; + + } + + let snippet = propertyName; + const nodeType = this.getNodeType( builder ); + + if ( builder.needsToWorkingColorSpace( texture ) ) { + + snippet = colorSpaceToWorking( expression( snippet, nodeType ), texture.colorSpace ).setup( builder ).build( builder, nodeType ); + + } + + return builder.format( snippet, nodeType, output ); + + } + + } + + /** + * Sets the sampler value. + * + * @param {boolean} value - The sampler value to set. + * @return {TextureNode} A reference to this texture node. + */ + setSampler( value ) { + + this.sampler = value; + + return this; + + } + + /** + * Returns the sampler value. + * + * @return {boolean} The sampler value. + */ + getSampler() { + + return this.sampler; + + } + + // @TODO: Move to TSL + + /** + * @function + * @deprecated since r172. Use {@link TextureNode#sample} instead. + * + * @param {Node} uvNode - The uv node. + * @return {TextureNode} A texture node representing the texture sample. + */ + uv( uvNode ) { // @deprecated, r172 + + console.warn( 'THREE.TextureNode: .uv() has been renamed. Use .sample() instead.' ); + + return this.sample( uvNode ); + + } + + /** + * Samples the texture with the given uv node. + * + * @param {Node} uvNode - The uv node. + * @return {TextureNode} A texture node representing the texture sample. + */ + sample( uvNode ) { + + const textureNode = this.clone(); + textureNode.uvNode = nodeObject( uvNode ); + textureNode.referenceNode = this.getSelf(); + + return nodeObject( textureNode ); + + } + + /** + * Samples a blurred version of the texture by defining an internal bias. + * + * @param {Node} amountNode - How blurred the texture should be. + * @return {TextureNode} A texture node representing the texture sample. + */ + blur( amountNode ) { + + const textureNode = this.clone(); + textureNode.biasNode = nodeObject( amountNode ).mul( maxMipLevel( textureNode ) ); + textureNode.referenceNode = this.getSelf(); + + const map = textureNode.value; + + if ( textureNode.generateMipmaps === false && ( map && map.generateMipmaps === false || map.minFilter === NearestFilter || map.magFilter === NearestFilter ) ) { + + console.warn( 'THREE.TSL: texture().blur() requires mipmaps and sampling. Use .generateMipmaps=true and .minFilter/.magFilter=THREE.LinearFilter in the Texture.' ); + + textureNode.biasNode = null; + + } + + return nodeObject( textureNode ); + + } + + /** + * Samples a specific mip of the texture. + * + * @param {Node} levelNode - The mip level to sample. + * @return {TextureNode} A texture node representing the texture sample. + */ + level( levelNode ) { + + const textureNode = this.clone(); + textureNode.levelNode = nodeObject( levelNode ); + textureNode.referenceNode = this.getSelf(); + + return nodeObject( textureNode ); + + } + + /** + * Returns the texture size of the requested level. + * + * @param {Node} levelNode - The level to compute the size for. + * @return {TextureSizeNode} The texture size. + */ + size( levelNode ) { + + return textureSize( this, levelNode ); + + } + + /** + * Samples the texture with the given bias. + * + * @param {Node} biasNode - The bias node. + * @return {TextureNode} A texture node representing the texture sample. + */ + bias( biasNode ) { + + const textureNode = this.clone(); + textureNode.biasNode = nodeObject( biasNode ); + textureNode.referenceNode = this.getSelf(); + + return nodeObject( textureNode ); + + } + + /** + * Samples the texture by executing a compare operation. + * + * @param {Node} compareNode - The node that defines the compare value. + * @return {TextureNode} A texture node representing the texture sample. + */ + compare( compareNode ) { + + const textureNode = this.clone(); + textureNode.compareNode = nodeObject( compareNode ); + textureNode.referenceNode = this.getSelf(); + + return nodeObject( textureNode ); + + } + + /** + * Samples the texture using an explicit gradient. + * + * @param {Node} gradNodeX - The gradX node. + * @param {Node} gradNodeY - The gradY node. + * @return {TextureNode} A texture node representing the texture sample. + */ + grad( gradNodeX, gradNodeY ) { + + const textureNode = this.clone(); + textureNode.gradNode = [ nodeObject( gradNodeX ), nodeObject( gradNodeY ) ]; + textureNode.referenceNode = this.getSelf(); + + return nodeObject( textureNode ); + + } + + /** + * Samples the texture by defining a depth node. + * + * @param {Node} depthNode - The depth node. + * @return {TextureNode} A texture node representing the texture sample. + */ + depth( depthNode ) { + + const textureNode = this.clone(); + textureNode.depthNode = nodeObject( depthNode ); + textureNode.referenceNode = this.getSelf(); + + return nodeObject( textureNode ); + + } + + // -- + + serialize( data ) { + + super.serialize( data ); + + data.value = this.value.toJSON( data.meta ).uuid; + data.sampler = this.sampler; + data.updateMatrix = this.updateMatrix; + data.updateType = this.updateType; + + } + + deserialize( data ) { + + super.deserialize( data ); + + this.value = data.meta.textures[ data.value ]; + this.sampler = data.sampler; + this.updateMatrix = data.updateMatrix; + this.updateType = data.updateType; + + } + + /** + * The update is used to implement the update of the uv transformation matrix. + */ + update() { + + const texture = this.value; + const matrixUniform = this._matrixUniform; + + if ( matrixUniform !== null ) matrixUniform.value = texture.matrix; + + if ( texture.matrixAutoUpdate === true ) { + + texture.updateMatrix(); + + } + + } + + /** + * Clones the texture node. + * + * @return {TextureNode} The cloned texture node. + */ + clone() { + + const newNode = new this.constructor( this.value, this.uvNode, this.levelNode, this.biasNode ); + newNode.sampler = this.sampler; + newNode.depthNode = this.depthNode; + newNode.compareNode = this.compareNode; + newNode.gradNode = this.gradNode; + + return newNode; + + } + +} + +/** + * TSL function for creating a texture node. + * + * @tsl + * @function + * @param {?Texture} value - The texture. + * @param {?Node} [uvNode=null] - The uv node. + * @param {?Node} [levelNode=null] - The level node. + * @param {?Node} [biasNode=null] - The bias node. + * @returns {TextureNode} + */ +const textureBase = /*@__PURE__*/ nodeProxy( TextureNode ).setParameterLength( 1, 4 ).setName( 'texture' ); + +/** + * TSL function for creating a texture node or sample a texture node already existing. + * + * @tsl + * @function + * @param {?Texture|TextureNode} [value=EmptyTexture] - The texture. + * @param {?Node} [uvNode=null] - The uv node. + * @param {?Node} [levelNode=null] - The level node. + * @param {?Node} [biasNode=null] - The bias node. + * @returns {TextureNode} + */ +const texture = ( value = EmptyTexture$1, uvNode = null, levelNode = null, biasNode = null ) => { + + let textureNode; + + if ( value && value.isTextureNode === true ) { + + textureNode = nodeObject( value.clone() ); + textureNode.referenceNode = value.getSelf(); // Ensure the reference is set to the original node + + if ( uvNode !== null ) textureNode.uvNode = nodeObject( uvNode ); + if ( levelNode !== null ) textureNode.levelNode = nodeObject( levelNode ); + if ( biasNode !== null ) textureNode.biasNode = nodeObject( biasNode ); + + } else { + + textureNode = textureBase( value, uvNode, levelNode, biasNode ); + + } + + return textureNode; + +}; + +/** + * TSL function for creating a uniform texture node. + * + * @tsl + * @function + * @param {?Texture} value - The texture. + * @returns {TextureNode} + */ +const uniformTexture = ( value = EmptyTexture$1 ) => texture( value ); + +/** + * TSL function for creating a texture node that fetches/loads texels without interpolation. + * + * @tsl + * @function + * @param {?Texture|TextureNode} [value=EmptyTexture] - The texture. + * @param {?Node} [uvNode=null] - The uv node. + * @param {?Node} [levelNode=null] - The level node. + * @param {?Node} [biasNode=null] - The bias node. + * @returns {TextureNode} + */ +const textureLoad = ( ...params ) => texture( ...params ).setSampler( false ); + +//export const textureLevel = ( value, uv, level ) => texture( value, uv ).level( level ); + +/** + * Converts a texture or texture node to a sampler. + * + * @tsl + * @function + * @param {TextureNode|Texture} value - The texture or texture node to convert. + * @returns {Node} + */ +const sampler = ( value ) => ( value.isNode === true ? value : texture( value ) ).convert( 'sampler' ); + +/** + * Converts a texture or texture node to a sampler comparison. + * + * @tsl + * @function + * @param {TextureNode|Texture} value - The texture or texture node to convert. + * @returns {Node} + */ +const samplerComparison = ( value ) => ( value.isNode === true ? value : texture( value ) ).convert( 'samplerComparison' ); + +/** + * A special type of uniform node which represents array-like data + * as uniform buffers. The access usually happens via `element()` + * which returns an instance of {@link ArrayElementNode}. For example: + * + * ```js + * const bufferNode = buffer( array, 'mat4', count ); + * const matrixNode = bufferNode.element( index ); // access a matrix from the buffer + * ``` + * In general, it is recommended to use the more managed {@link UniformArrayNode} + * since it handles more input types and automatically cares about buffer paddings. + * + * @augments UniformNode + */ +class BufferNode extends UniformNode { + + static get type() { + + return 'BufferNode'; + + } + + /** + * Constructs a new buffer node. + * + * @param {Array} value - Array-like buffer data. + * @param {string} bufferType - The data type of the buffer. + * @param {number} [bufferCount=0] - The count of buffer elements. + */ + constructor( value, bufferType, bufferCount = 0 ) { + + super( value, bufferType ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isBufferNode = true; + + /** + * The data type of the buffer. + * + * @type {string} + */ + this.bufferType = bufferType; + + /** + * The uniform node that holds the value of the reference node. + * + * @type {number} + * @default 0 + */ + this.bufferCount = bufferCount; + + } + + /** + * The data type of the buffer elements. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The element type. + */ + getElementType( builder ) { + + return this.getNodeType( builder ); + + } + + /** + * Overwrites the default implementation to return a fixed value `'buffer'`. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The input type. + */ + getInputType( /*builder*/ ) { + + return 'buffer'; + + } + +} + +/** + * TSL function for creating a buffer node. + * + * @tsl + * @function + * @param {Array} value - Array-like buffer data. + * @param {string} type - The data type of a buffer element. + * @param {number} count - The count of buffer elements. + * @returns {BufferNode} + */ +const buffer = ( value, type, count ) => nodeObject( new BufferNode( value, type, count ) ); + +/** + * Represents the element access on uniform array nodes. + * + * @augments ArrayElementNode + */ +class UniformArrayElementNode extends ArrayElementNode { + + static get type() { + + return 'UniformArrayElementNode'; + + } + + /** + * Constructs a new buffer node. + * + * @param {UniformArrayNode} uniformArrayNode - The uniform array node to access. + * @param {IndexNode} indexNode - The index data that define the position of the accessed element in the array. + */ + constructor( uniformArrayNode, indexNode ) { + + super( uniformArrayNode, indexNode ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isArrayBufferElementNode = true; + + } + + generate( builder ) { + + const snippet = super.generate( builder ); + const type = this.getNodeType(); + const paddedType = this.node.getPaddedType(); + + return builder.format( snippet, paddedType, type ); + + } + +} + +/** + * Similar to {@link BufferNode} this module represents array-like data as + * uniform buffers. Unlike {@link BufferNode}, it can handle more common + * data types in the array (e.g `three.js` primitives) and automatically + * manage buffer padding. It should be the first choice when working with + * uniforms buffers. + * ```js + * const tintColors = uniformArray( [ + * new Color( 1, 0, 0 ), + * new Color( 0, 1, 0 ), + * new Color( 0, 0, 1 ) + * ], 'color' ); + * + * const redColor = tintColors.element( 0 ); + * + * @augments BufferNode + */ +class UniformArrayNode extends BufferNode { + + static get type() { + + return 'UniformArrayNode'; + + } + + /** + * Constructs a new uniform array node. + * + * @param {Array} value - Array holding the buffer data. + * @param {?string} [elementType=null] - The data type of a buffer element. + */ + constructor( value, elementType = null ) { + + super( null ); + + /** + * Array holding the buffer data. Unlike {@link BufferNode}, the array can + * hold number primitives as well as three.js objects like vectors, matrices + * or colors. + * + * @type {Array} + */ + this.array = value; + + /** + * The data type of an array element. + * + * @type {string} + */ + this.elementType = elementType === null ? getValueType( value[ 0 ] ) : elementType; + + /** + * The padded type. Uniform buffers must conform to a certain buffer layout + * so a separate type is computed to ensure correct buffer size. + * + * @type {string} + */ + this.paddedType = this.getPaddedType(); + + /** + * Overwritten since uniform array nodes are updated per render. + * + * @type {string} + * @default 'render' + */ + this.updateType = NodeUpdateType.RENDER; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isArrayBufferNode = true; + + } + + /** + * This method is overwritten since the node type is inferred from the + * {@link UniformArrayNode#paddedType}. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType( /*builder*/ ) { + + return this.paddedType; + + } + + /** + * The data type of the array elements. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The element type. + */ + getElementType() { + + return this.elementType; + + } + + /** + * Returns the padded type based on the element type. + * + * @return {string} The padded type. + */ + getPaddedType() { + + const elementType = this.elementType; + + let paddedType = 'vec4'; + + if ( elementType === 'mat2' ) { + + paddedType = 'mat2'; + + } else if ( /mat/.test( elementType ) === true ) { + + paddedType = 'mat4'; + + } else if ( elementType.charAt( 0 ) === 'i' ) { + + paddedType = 'ivec4'; + + } else if ( elementType.charAt( 0 ) === 'u' ) { + + paddedType = 'uvec4'; + + } + + return paddedType; + + } + + /** + * The update makes sure to correctly transfer the data from the (complex) objects + * in the array to the internal, correctly padded value buffer. + * + * @param {NodeFrame} frame - A reference to the current node frame. + */ + update( /*frame*/ ) { + + const { array, value } = this; + + const elementType = this.elementType; + + if ( elementType === 'float' || elementType === 'int' || elementType === 'uint' ) { + + for ( let i = 0; i < array.length; i ++ ) { + + const index = i * 4; + + value[ index ] = array[ i ]; + + } + + } else if ( elementType === 'color' ) { + + for ( let i = 0; i < array.length; i ++ ) { + + const index = i * 4; + const vector = array[ i ]; + + value[ index ] = vector.r; + value[ index + 1 ] = vector.g; + value[ index + 2 ] = vector.b || 0; + //value[ index + 3 ] = vector.a || 0; + + } + + } else if ( elementType === 'mat2' ) { + + for ( let i = 0; i < array.length; i ++ ) { + + const index = i * 4; + const matrix = array[ i ]; + + value[ index ] = matrix.elements[ 0 ]; + value[ index + 1 ] = matrix.elements[ 1 ]; + value[ index + 2 ] = matrix.elements[ 2 ]; + value[ index + 3 ] = matrix.elements[ 3 ]; + + } + + } else if ( elementType === 'mat3' ) { + + for ( let i = 0; i < array.length; i ++ ) { + + const index = i * 16; + const matrix = array[ i ]; + + value[ index ] = matrix.elements[ 0 ]; + value[ index + 1 ] = matrix.elements[ 1 ]; + value[ index + 2 ] = matrix.elements[ 2 ]; + + value[ index + 4 ] = matrix.elements[ 3 ]; + value[ index + 5 ] = matrix.elements[ 4 ]; + value[ index + 6 ] = matrix.elements[ 5 ]; + + value[ index + 8 ] = matrix.elements[ 6 ]; + value[ index + 9 ] = matrix.elements[ 7 ]; + value[ index + 10 ] = matrix.elements[ 8 ]; + + value[ index + 15 ] = 1; + + } + + } else if ( elementType === 'mat4' ) { + + for ( let i = 0; i < array.length; i ++ ) { + + const index = i * 16; + const matrix = array[ i ]; + + for ( let i = 0; i < matrix.elements.length; i ++ ) { + + value[ index + i ] = matrix.elements[ i ]; + + } + + } + + } else { + + for ( let i = 0; i < array.length; i ++ ) { + + const index = i * 4; + const vector = array[ i ]; + + value[ index ] = vector.x; + value[ index + 1 ] = vector.y; + value[ index + 2 ] = vector.z || 0; + value[ index + 3 ] = vector.w || 0; + + } + + } + + } + + /** + * Implement the value buffer creation based on the array data. + * + * @param {NodeBuilder} builder - A reference to the current node builder. + * @return {null} + */ + setup( builder ) { + + const length = this.array.length; + const elementType = this.elementType; + + let arrayType = Float32Array; + + const paddedType = this.paddedType; + const paddedElementLength = builder.getTypeLength( paddedType ); + + if ( elementType.charAt( 0 ) === 'i' ) arrayType = Int32Array; + if ( elementType.charAt( 0 ) === 'u' ) arrayType = Uint32Array; + + this.value = new arrayType( length * paddedElementLength ); + this.bufferCount = length; + this.bufferType = paddedType; + + return super.setup( builder ); + + } + + /** + * Overwrites the default `element()` method to provide element access + * based on {@link UniformArrayNode}. + * + * @param {IndexNode} indexNode - The index node. + * @return {UniformArrayElementNode} + */ + element( indexNode ) { + + return nodeObject( new UniformArrayElementNode( this, nodeObject( indexNode ) ) ); + + } + +} + +/** + * TSL function for creating an uniform array node. + * + * @tsl + * @function + * @param {Array} values - Array-like data. + * @param {?string} [nodeType] - The data type of the array elements. + * @returns {UniformArrayNode} + */ +const uniformArray = ( values, nodeType ) => nodeObject( new UniformArrayNode( values, nodeType ) ); + +/** + * The node allows to set values for built-in shader variables. That is + * required for features like hardware-accelerated vertex clipping. + * + * @augments Node + */ +class BuiltinNode extends Node { + + /** + * Constructs a new builtin node. + * + * @param {string} name - The name of the built-in shader variable. + */ + constructor( name ) { + + super( 'float' ); + + /** + * The name of the built-in shader variable. + * + * @type {string} + */ + this.name = name; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isBuiltinNode = true; + + } + + /** + * Generates the code snippet of the builtin node. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The generated code snippet. + */ + generate( /* builder */ ) { + + return this.name; + + } + +} + +/** + * TSL function for creating a builtin node. + * + * @tsl + * @function + * @param {string} name - The name of the built-in shader variable. + * @returns {BuiltinNode} + */ +const builtin = nodeProxy( BuiltinNode ).setParameterLength( 1 ); + +/** + * TSL object that represents the current `index` value of the camera if used ArrayCamera. + * + * @tsl + * @type {UniformNode} + */ +const cameraIndex = /*@__PURE__*/ uniform( 0, 'uint' ).label( 'u_cameraIndex' ).setGroup( sharedUniformGroup( 'cameraIndex' ) ).toVarying( 'v_cameraIndex' ); + +/** + * TSL object that represents the `near` value of the camera used for the current render. + * + * @tsl + * @type {UniformNode} + */ +const cameraNear = /*@__PURE__*/ uniform( 'float' ).label( 'cameraNear' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.near ); + +/** + * TSL object that represents the `far` value of the camera used for the current render. + * + * @tsl + * @type {UniformNode} + */ +const cameraFar = /*@__PURE__*/ uniform( 'float' ).label( 'cameraFar' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.far ); + +/** + * TSL object that represents the projection matrix of the camera used for the current render. + * + * @tsl + * @type {UniformNode} + */ +const cameraProjectionMatrix = /*@__PURE__*/ ( Fn( ( { camera } ) => { + + let cameraProjectionMatrix; + + if ( camera.isArrayCamera && camera.cameras.length > 0 ) { + + const matrices = []; + + for ( const subCamera of camera.cameras ) { + + matrices.push( subCamera.projectionMatrix ); + + } + + const cameraProjectionMatrices = uniformArray( matrices ).setGroup( renderGroup ).label( 'cameraProjectionMatrices' ); + + cameraProjectionMatrix = cameraProjectionMatrices.element( camera.isMultiViewCamera ? builtin( 'gl_ViewID_OVR' ) : cameraIndex ).toVar( 'cameraProjectionMatrix' ); + + } else { + + cameraProjectionMatrix = uniform( 'mat4' ).label( 'cameraProjectionMatrix' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.projectionMatrix ); + + } + + return cameraProjectionMatrix; + +} ).once() )(); + +/** + * TSL object that represents the inverse projection matrix of the camera used for the current render. + * + * @tsl + * @type {UniformNode} + */ +const cameraProjectionMatrixInverse = /*@__PURE__*/ ( Fn( ( { camera } ) => { + + let cameraProjectionMatrixInverse; + + if ( camera.isArrayCamera && camera.cameras.length > 0 ) { + + const matrices = []; + + for ( const subCamera of camera.cameras ) { + + matrices.push( subCamera.projectionMatrixInverse ); + + } + + const cameraProjectionMatricesInverse = uniformArray( matrices ).setGroup( renderGroup ).label( 'cameraProjectionMatricesInverse' ); + + cameraProjectionMatrixInverse = cameraProjectionMatricesInverse.element( camera.isMultiViewCamera ? builtin( 'gl_ViewID_OVR' ) : cameraIndex ).toVar( 'cameraProjectionMatrixInverse' ); + + } else { + + cameraProjectionMatrixInverse = uniform( 'mat4' ).label( 'cameraProjectionMatrixInverse' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.projectionMatrixInverse ); + + } + + return cameraProjectionMatrixInverse; + +} ).once() )(); + +/** + * TSL object that represents the view matrix of the camera used for the current render. + * + * @tsl + * @type {UniformNode} + */ +const cameraViewMatrix = /*@__PURE__*/ ( Fn( ( { camera } ) => { + + let cameraViewMatrix; + + if ( camera.isArrayCamera && camera.cameras.length > 0 ) { + + const matrices = []; + + for ( const subCamera of camera.cameras ) { + + matrices.push( subCamera.matrixWorldInverse ); + + } + + const cameraViewMatrices = uniformArray( matrices ).setGroup( renderGroup ).label( 'cameraViewMatrices' ); + + cameraViewMatrix = cameraViewMatrices.element( camera.isMultiViewCamera ? builtin( 'gl_ViewID_OVR' ) : cameraIndex ).toVar( 'cameraViewMatrix' ); + + } else { + + cameraViewMatrix = uniform( 'mat4' ).label( 'cameraViewMatrix' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.matrixWorldInverse ); + + } + + return cameraViewMatrix; + +} ).once() )(); + +/** + * TSL object that represents the world matrix of the camera used for the current render. + * + * @tsl + * @type {UniformNode} + */ +const cameraWorldMatrix = /*@__PURE__*/ uniform( 'mat4' ).label( 'cameraWorldMatrix' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.matrixWorld ); + +/** + * TSL object that represents the normal matrix of the camera used for the current render. + * + * @tsl + * @type {UniformNode} + */ +const cameraNormalMatrix = /*@__PURE__*/ uniform( 'mat3' ).label( 'cameraNormalMatrix' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.normalMatrix ); + +/** + * TSL object that represents the position in world space of the camera used for the current render. + * + * @tsl + * @type {UniformNode} + */ +const cameraPosition = /*@__PURE__*/ uniform( new Vector3() ).label( 'cameraPosition' ).setGroup( renderGroup ).onRenderUpdate( ( { camera }, self ) => self.value.setFromMatrixPosition( camera.matrixWorld ) ); + +const _sphere = /*@__PURE__*/ new Sphere(); + +/** + * This node can be used to access transformation related metrics of 3D objects. + * Depending on the selected scope, a different metric is represented as a uniform + * in the shader. The following scopes are supported: + * + * - `POSITION`: The object's position in world space. + * - `VIEW_POSITION`: The object's position in view/camera space. + * - `DIRECTION`: The object's direction in world space. + * - `SCALE`: The object's scale in world space. + * - `WORLD_MATRIX`: The object's matrix in world space. + * + * @augments Node + */ +class Object3DNode extends Node { + + static get type() { + + return 'Object3DNode'; + + } + + /** + * Constructs a new object 3D node. + * + * @param {('position'|'viewPosition'|'direction'|'scale'|'worldMatrix')} scope - The node represents a different type of transformation depending on the scope. + * @param {?Object3D} [object3d=null] - The 3D object. + */ + constructor( scope, object3d = null ) { + + super(); + + /** + * The node reports a different type of transformation depending on the scope. + * + * @type {('position'|'viewPosition'|'direction'|'scale'|'worldMatrix')} + */ + this.scope = scope; + + /** + * The 3D object. + * + * @type {?Object3D} + * @default null + */ + this.object3d = object3d; + + /** + * Overwritten since this type of node is updated per object. + * + * @type {string} + * @default 'object' + */ + this.updateType = NodeUpdateType.OBJECT; + + /** + * Holds the value of the node as a uniform. + * + * @type {UniformNode} + */ + this.uniformNode = new UniformNode( null ); + + } + + /** + * Overwritten since the node type is inferred from the scope. + * + * @return {string} The node type. + */ + getNodeType() { + + const scope = this.scope; + + if ( scope === Object3DNode.WORLD_MATRIX ) { + + return 'mat4'; + + } else if ( scope === Object3DNode.POSITION || scope === Object3DNode.VIEW_POSITION || scope === Object3DNode.DIRECTION || scope === Object3DNode.SCALE ) { + + return 'vec3'; + + } else if ( scope === Object3DNode.RADIUS ) { + + return 'float'; + + } + + } + + /** + * Updates the uniform value depending on the scope. + * + * @param {NodeFrame} frame - The current node frame. + */ + update( frame ) { + + const object = this.object3d; + const uniformNode = this.uniformNode; + const scope = this.scope; + + if ( scope === Object3DNode.WORLD_MATRIX ) { + + uniformNode.value = object.matrixWorld; + + } else if ( scope === Object3DNode.POSITION ) { + + uniformNode.value = uniformNode.value || new Vector3(); + + uniformNode.value.setFromMatrixPosition( object.matrixWorld ); + + } else if ( scope === Object3DNode.SCALE ) { + + uniformNode.value = uniformNode.value || new Vector3(); + + uniformNode.value.setFromMatrixScale( object.matrixWorld ); + + } else if ( scope === Object3DNode.DIRECTION ) { + + uniformNode.value = uniformNode.value || new Vector3(); + + object.getWorldDirection( uniformNode.value ); + + } else if ( scope === Object3DNode.VIEW_POSITION ) { + + const camera = frame.camera; + + uniformNode.value = uniformNode.value || new Vector3(); + uniformNode.value.setFromMatrixPosition( object.matrixWorld ); + + uniformNode.value.applyMatrix4( camera.matrixWorldInverse ); + + } else if ( scope === Object3DNode.RADIUS ) { + + const geometry = frame.object.geometry; + + if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); + + _sphere.copy( geometry.boundingSphere ).applyMatrix4( object.matrixWorld ); + + uniformNode.value = _sphere.radius; + + } + + } + + /** + * Generates the code snippet of the uniform node. The node type of the uniform + * node also depends on the selected scope. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The generated code snippet. + */ + generate( builder ) { + + const scope = this.scope; + + if ( scope === Object3DNode.WORLD_MATRIX ) { + + this.uniformNode.nodeType = 'mat4'; + + } else if ( scope === Object3DNode.POSITION || scope === Object3DNode.VIEW_POSITION || scope === Object3DNode.DIRECTION || scope === Object3DNode.SCALE ) { + + this.uniformNode.nodeType = 'vec3'; + + } else if ( scope === Object3DNode.RADIUS ) { + + this.uniformNode.nodeType = 'float'; + + } + + return this.uniformNode.build( builder ); + + } + + serialize( data ) { + + super.serialize( data ); + + data.scope = this.scope; + + } + + deserialize( data ) { + + super.deserialize( data ); + + this.scope = data.scope; + + } + +} + +Object3DNode.WORLD_MATRIX = 'worldMatrix'; +Object3DNode.POSITION = 'position'; +Object3DNode.SCALE = 'scale'; +Object3DNode.VIEW_POSITION = 'viewPosition'; +Object3DNode.DIRECTION = 'direction'; +Object3DNode.RADIUS = 'radius'; + +/** + * TSL function for creating an object 3D node that represents the object's direction in world space. + * + * @tsl + * @function + * @param {?Object3D} [object3d] - The 3D object. + * @returns {Object3DNode} + */ +const objectDirection = /*@__PURE__*/ nodeProxy( Object3DNode, Object3DNode.DIRECTION ).setParameterLength( 1 ); + +/** + * TSL function for creating an object 3D node that represents the object's world matrix. + * + * @tsl + * @function + * @param {?Object3D} [object3d] - The 3D object. + * @returns {Object3DNode} + */ +const objectWorldMatrix = /*@__PURE__*/ nodeProxy( Object3DNode, Object3DNode.WORLD_MATRIX ).setParameterLength( 1 ); + +/** + * TSL function for creating an object 3D node that represents the object's position in world space. + * + * @tsl + * @function + * @param {?Object3D} [object3d] - The 3D object. + * @returns {Object3DNode} + */ +const objectPosition = /*@__PURE__*/ nodeProxy( Object3DNode, Object3DNode.POSITION ).setParameterLength( 1 ); + +/** + * TSL function for creating an object 3D node that represents the object's scale in world space. + * + * @tsl + * @function + * @param {?Object3D} [object3d] - The 3D object. + * @returns {Object3DNode} + */ +const objectScale = /*@__PURE__*/ nodeProxy( Object3DNode, Object3DNode.SCALE ).setParameterLength( 1 ); + +/** + * TSL function for creating an object 3D node that represents the object's position in view/camera space. + * + * @tsl + * @function + * @param {?Object3D} [object3d] - The 3D object. + * @returns {Object3DNode} + */ +const objectViewPosition = /*@__PURE__*/ nodeProxy( Object3DNode, Object3DNode.VIEW_POSITION ).setParameterLength( 1 ); + +/** + * TSL function for creating an object 3D node that represents the object's radius. + * + * @tsl + * @function + * @param {?Object3D} [object3d] - The 3D object. + * @returns {Object3DNode} + */ +const objectRadius = /*@__PURE__*/ nodeProxy( Object3DNode, Object3DNode.RADIUS ).setParameterLength( 1 ); + +/** + * This type of node is a specialized version of `Object3DNode` + * with larger set of model related metrics. Unlike `Object3DNode`, + * `ModelNode` extracts the reference to the 3D object from the + * current node frame state. + * + * @augments Object3DNode + */ +class ModelNode extends Object3DNode { + + static get type() { + + return 'ModelNode'; + + } + + /** + * Constructs a new object model node. + * + * @param {('position'|'viewPosition'|'direction'|'scale'|'worldMatrix')} scope - The node represents a different type of transformation depending on the scope. + */ + constructor( scope ) { + + super( scope ); + + } + + /** + * Extracts the model reference from the frame state and then + * updates the uniform value depending on the scope. + * + * @param {NodeFrame} frame - The current node frame. + */ + update( frame ) { + + this.object3d = frame.object; + + super.update( frame ); + + } + +} + +/** + * TSL object that represents the object's direction in world space. + * + * @tsl + * @type {ModelNode} + */ +const modelDirection = /*@__PURE__*/ nodeImmutable( ModelNode, ModelNode.DIRECTION ); + +/** + * TSL object that represents the object's world matrix. + * + * @tsl + * @type {ModelNode} + */ +const modelWorldMatrix = /*@__PURE__*/ nodeImmutable( ModelNode, ModelNode.WORLD_MATRIX ); + +/** + * TSL object that represents the object's position in world space. + * + * @tsl + * @type {ModelNode} + */ +const modelPosition = /*@__PURE__*/ nodeImmutable( ModelNode, ModelNode.POSITION ); + +/** + * TSL object that represents the object's scale in world space. + * + * @tsl + * @type {ModelNode} + */ +const modelScale = /*@__PURE__*/ nodeImmutable( ModelNode, ModelNode.SCALE ); + +/** + * TSL object that represents the object's position in view/camera space. + * + * @tsl + * @type {ModelNode} + */ +const modelViewPosition = /*@__PURE__*/ nodeImmutable( ModelNode, ModelNode.VIEW_POSITION ); + +/** + * TSL object that represents the object's radius. + * + * @tsl + * @type {ModelNode} + */ +const modelRadius = /*@__PURE__*/ nodeImmutable( ModelNode, ModelNode.RADIUS ); + +/** + * TSL object that represents the object's normal matrix. + * + * @tsl + * @type {UniformNode} + */ +const modelNormalMatrix = /*@__PURE__*/ uniform( new Matrix3() ).onObjectUpdate( ( { object }, self ) => self.value.getNormalMatrix( object.matrixWorld ) ); + +/** + * TSL object that represents the object's inverse world matrix. + * + * @tsl + * @type {UniformNode} + */ +const modelWorldMatrixInverse = /*@__PURE__*/ uniform( new Matrix4() ).onObjectUpdate( ( { object }, self ) => self.value.copy( object.matrixWorld ).invert() ); + +/** + * TSL object that represents the object's model view matrix. + * + * @tsl + * @type {Node} + */ +const modelViewMatrix = /*@__PURE__*/ ( Fn( ( builder ) => { + + return builder.renderer.overrideNodes.modelViewMatrix || mediumpModelViewMatrix; + +} ).once() )().toVar( 'modelViewMatrix' ); + +// GPU Precision + +/** + * TSL object that represents the object's model view in `mediump` precision. + * + * @tsl + * @type {Node} + */ +const mediumpModelViewMatrix = /*@__PURE__*/ cameraViewMatrix.mul( modelWorldMatrix ); + +// CPU Precision + +/** + * TSL object that represents the object's model view in `highp` precision + * which is achieved by computing the matrix in JS and not in the shader. + * + * @tsl + * @type {Node} + */ +const highpModelViewMatrix = /*@__PURE__*/ ( Fn( ( builder ) => { + + builder.context.isHighPrecisionModelViewMatrix = true; + + return uniform( 'mat4' ).onObjectUpdate( ( { object, camera } ) => { + + return object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld ); + + } ); + +} ).once() )().toVar( 'highpModelViewMatrix' ); + +/** + * TSL object that represents the object's model normal view in `highp` precision + * which is achieved by computing the matrix in JS and not in the shader. + * + * @tsl + * @type {Node} + */ +const highpModelNormalViewMatrix = /*@__PURE__*/ ( Fn( ( builder ) => { + + const isHighPrecisionModelViewMatrix = builder.context.isHighPrecisionModelViewMatrix; + + return uniform( 'mat3' ).onObjectUpdate( ( { object, camera } ) => { + + if ( isHighPrecisionModelViewMatrix !== true ) { + + object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld ); + + } + + return object.normalMatrix.getNormalMatrix( object.modelViewMatrix ); + + } ); + +} ).once() )().toVar( 'highpModelNormalViewMatrix' ); + +/** + * TSL object that represents the position attribute of the current rendered object. + * + * @tsl + * @type {AttributeNode} + */ +const positionGeometry = /*@__PURE__*/ attribute( 'position', 'vec3' ); + +/** + * TSL object that represents the vertex position in local space of the current rendered object. + * + * @tsl + * @type {AttributeNode} + */ +const positionLocal = /*@__PURE__*/ positionGeometry.toVarying( 'positionLocal' ); + +/** + * TSL object that represents the previous vertex position in local space of the current rendered object. + * Used in context of {@link VelocityNode} for rendering motion vectors. + * + * @tsl + * @type {AttributeNode} + */ +const positionPrevious = /*@__PURE__*/ positionGeometry.toVarying( 'positionPrevious' ); + +/** + * TSL object that represents the vertex position in world space of the current rendered object. + * + * @tsl + * @type {VaryingNode} + */ +const positionWorld = /*@__PURE__*/ ( Fn( ( builder ) => { + + return modelWorldMatrix.mul( positionLocal ).xyz.toVarying( builder.getSubBuildProperty( 'v_positionWorld' ) ); + +}, 'vec3' ).once( [ 'POSITION' ] ) )(); + +/** + * TSL object that represents the position world direction of the current rendered object. + * + * @tsl + * @type {Node} + */ +const positionWorldDirection = /*@__PURE__*/ ( Fn( () => { + + const vertexPWD = positionLocal.transformDirection( modelWorldMatrix ).toVarying( 'v_positionWorldDirection' ); + + return vertexPWD.normalize().toVar( 'positionWorldDirection' ); + +}, 'vec3' ).once( [ 'POSITION' ] ) )(); + +/** + * TSL object that represents the vertex position in view space of the current rendered object. + * + * @tsl + * @type {VaryingNode} + */ +const positionView = /*@__PURE__*/ ( Fn( ( builder ) => { + + return builder.context.setupPositionView().toVarying( 'v_positionView' ); + +}, 'vec3' ).once( [ 'POSITION' ] ) )(); + +/** + * TSL object that represents the position view direction of the current rendered object. + * + * @tsl + * @type {VaryingNode} + */ +const positionViewDirection = /*@__PURE__*/ positionView.negate().toVarying( 'v_positionViewDirection' ).normalize().toVar( 'positionViewDirection' ); + +/** + * This node can be used to evaluate whether a primitive is front or back facing. + * + * @augments Node + */ +class FrontFacingNode extends Node { + + static get type() { + + return 'FrontFacingNode'; + + } + + /** + * Constructs a new front facing node. + */ + constructor() { + + super( 'bool' ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isFrontFacingNode = true; + + } + + generate( builder ) { + + if ( builder.shaderStage !== 'fragment' ) return 'true'; + + // + + const { renderer, material } = builder; + + if ( renderer.coordinateSystem === WebGLCoordinateSystem ) { + + if ( material.side === BackSide ) { + + return 'false'; + + } + + } + + return builder.getFrontFacing(); + + } + +} + +/** + * TSL object that represents whether a primitive is front or back facing + * + * @tsl + * @type {FrontFacingNode} + */ +const frontFacing = /*@__PURE__*/ nodeImmutable( FrontFacingNode ); + +/** + * TSL object that represents the front facing status as a number instead of a bool. + * `1` means front facing, `-1` means back facing. + * + * @tsl + * @type {Node} + */ +const faceDirection = /*@__PURE__*/ float( frontFacing ).mul( 2.0 ).sub( 1.0 ); + +/** + * Converts a direction vector to a face direction vector based on the material's side. + * + * If the material is set to `BackSide`, the direction is inverted. + * If the material is set to `DoubleSide`, the direction is multiplied by `faceDirection`. + * + * @tsl + * @param {Node} direction - The direction vector to convert. + * @returns {Node} The converted direction vector. + */ +const directionToFaceDirection = /*@__PURE__*/ Fn( ( [ direction ], { material } ) => { + + const side = material.side; + + if ( side === BackSide ) { + + direction = direction.mul( -1 ); + + } else if ( side === DoubleSide ) { + + direction = direction.mul( faceDirection ); + + } + + return direction; + +} ); + +/** + * TSL object that represents the normal attribute of the current rendered object. + * + * @tsl + * @type {Node} + */ +const normalGeometry = /*@__PURE__*/ attribute( 'normal', 'vec3' ); + +/** + * TSL object that represents the vertex normal in local space of the current rendered object. + * + * @tsl + * @type {Node} + */ +const normalLocal = /*@__PURE__*/ ( Fn( ( builder ) => { + + if ( builder.geometry.hasAttribute( 'normal' ) === false ) { + + console.warn( 'THREE.TSL: Vertex attribute "normal" not found on geometry.' ); + + return vec3( 0, 1, 0 ); + + } + + return normalGeometry; + +}, 'vec3' ).once() )().toVar( 'normalLocal' ); + +/** + * TSL object that represents the flat vertex normal in view space of the current rendered object. + * + * @tsl + * @type {Node} + */ +const normalFlat = /*@__PURE__*/ positionView.dFdx().cross( positionView.dFdy() ).normalize().toVar( 'normalFlat' ); + +/** + * TSL object that represents the vertex normal in view space of the current rendered object. + * + * @tsl + * @type {Node} + */ +const normalViewGeometry = /*@__PURE__*/ ( Fn( ( builder ) => { + + let node; + + if ( builder.material.flatShading === true ) { + + node = normalFlat; + + } else { + + node = transformNormalToView( normalLocal ).toVarying( 'v_normalViewGeometry' ).normalize(); + + } + + return node; + +}, 'vec3' ).once() )().toVar( 'normalViewGeometry' ); + +/** + * TSL object that represents the vertex normal in world space of the current rendered object. + * + * @tsl + * @type {Node} + */ +const normalWorldGeometry = /*@__PURE__*/ ( Fn( ( builder ) => { + + let normal = normalViewGeometry.transformDirection( cameraViewMatrix ); + + if ( builder.material.flatShading !== true ) { + + normal = normal.toVarying( 'v_normalWorldGeometry' ); + + } + + return normal.normalize().toVar( 'normalWorldGeometry' ); + +}, 'vec3' ).once() )(); + +/** + * TSL object that represents the transformed vertex normal in view space of the current rendered object. + * + * @tsl + * @type {Node} + */ +const normalView = /*@__PURE__*/ ( Fn( ( { subBuildFn, material, context } ) => { + + let node; + + if ( subBuildFn === 'NORMAL' || subBuildFn === 'VERTEX' ) { + + node = normalViewGeometry; + + if ( material.flatShading !== true ) { + + node = directionToFaceDirection( node ); + + } + + } else { + + // Use getUV context to avoid side effects from nodes overwriting getUV in the context (e.g. EnvironmentNode) + + node = context.setupNormal().context( { getUV: null } ); + + } + + return node; + +}, 'vec3' ).once( [ 'NORMAL', 'VERTEX' ] ) )().toVar( 'normalView' ); + +/** + * TSL object that represents the transformed vertex normal in world space of the current rendered object. + * + * @tsl + * @type {Node} + */ +const normalWorld = /*@__PURE__*/ normalView.transformDirection( cameraViewMatrix ).toVar( 'normalWorld' ); + +/** + * TSL object that represents the transformed clearcoat vertex normal in view space of the current rendered object. + * + * @tsl + * @type {Node} + */ +const clearcoatNormalView = /*@__PURE__*/ ( Fn( ( { subBuildFn, context } ) => { + + let node; + + if ( subBuildFn === 'NORMAL' || subBuildFn === 'VERTEX' ) { + + node = normalView; + + } else { + + // Use getUV context to avoid side effects from nodes overwriting getUV in the context (e.g. EnvironmentNode) + + node = context.setupClearcoatNormal().context( { getUV: null } ); + + } + + return node; + +}, 'vec3' ).once( [ 'NORMAL', 'VERTEX' ] ) )().toVar( 'clearcoatNormalView' ); + +/** + * Transforms the normal with the given matrix. + * + * @tsl + * @function + * @param {Node} normal - The normal. + * @param {Node} [matrix=modelWorldMatrix] - The matrix. + * @return {Node} The transformed normal. + */ +const transformNormal = /*@__PURE__*/ Fn( ( [ normal, matrix = modelWorldMatrix ] ) => { + + const m = mat3( matrix ); + + const transformedNormal = normal.div( vec3( m[ 0 ].dot( m[ 0 ] ), m[ 1 ].dot( m[ 1 ] ), m[ 2 ].dot( m[ 2 ] ) ) ); + + return m.mul( transformedNormal ).xyz; + +} ); + +/** + * Transforms the given normal from local to view space. + * + * @tsl + * @function + * @param {Node} normal - The normal. + * @param {NodeBuilder} builder - The current node builder. + * @return {Node} The transformed normal. + */ +const transformNormalToView = /*@__PURE__*/ Fn( ( [ normal ], builder ) => { + + const modelNormalViewMatrix = builder.renderer.overrideNodes.modelNormalViewMatrix; + + if ( modelNormalViewMatrix !== null ) { + + return modelNormalViewMatrix.transformDirection( normal ); + + } + + // + + const transformedNormal = modelNormalMatrix.mul( normal ); + + return cameraViewMatrix.transformDirection( transformedNormal ); + +} ); + +// Deprecated + +/** + * TSL object that represents the transformed vertex normal in view space of the current rendered object. + * + * @tsl + * @type {Node} + * @deprecated since r178. Use `normalView` instead. + */ +const transformedNormalView = ( Fn( () => { // @deprecated, r177 + + console.warn( 'THREE.TSL: "transformedNormalView" is deprecated. Use "normalView" instead.' ); + return normalView; + +} ).once( [ 'NORMAL', 'VERTEX' ] ) )(); + +/** + * TSL object that represents the transformed vertex normal in world space of the current rendered object. + * + * @tsl + * @type {Node} + * @deprecated since r178. Use `normalWorld` instead. + */ +const transformedNormalWorld = ( Fn( () => { // @deprecated, r177 + + console.warn( 'THREE.TSL: "transformedNormalWorld" is deprecated. Use "normalWorld" instead.' ); + return normalWorld; + +} ).once( [ 'NORMAL', 'VERTEX' ] ) )(); + +/** + * TSL object that represents the transformed clearcoat vertex normal in view space of the current rendered object. + * + * @tsl + * @type {Node} + * @deprecated since r178. Use `clearcoatNormalView` instead. + */ +const transformedClearcoatNormalView = ( Fn( () => { // @deprecated, r177 + + console.warn( 'THREE.TSL: "transformedClearcoatNormalView" is deprecated. Use "clearcoatNormalView" instead.' ); + return clearcoatNormalView; + +} ).once( [ 'NORMAL', 'VERTEX' ] ) )(); + +const _e1$1 = /*@__PURE__*/ new Euler(); +const _m1$1 = /*@__PURE__*/ new Matrix4(); + +/** + * TSL object that represents the refraction ratio of the material used for rendering the current object. + * + * @tsl + * @type {UniformNode} + */ +const materialRefractionRatio = /*@__PURE__*/ uniform( 0 ).onReference( ( { material } ) => material ).onObjectUpdate( ( { material } ) => material.refractionRatio ); + +/** + * TSL object that represents the intensity of environment maps of PBR materials. + * When `material.envMap` is set, the value is `material.envMapIntensity` otherwise `scene.environmentIntensity`. + * + * @tsl + * @type {Node} + */ +const materialEnvIntensity = /*@__PURE__*/ uniform( 1 ).onReference( ( { material } ) => material ).onObjectUpdate( function ( { material, scene } ) { + + return material.envMap ? material.envMapIntensity : scene.environmentIntensity; + +} ); + +/** + * TSL object that represents the rotation of environment maps. + * When `material.envMap` is set, the value is `material.envMapRotation`. `scene.environmentRotation` controls the + * rotation of `scene.environment` instead. + * + * @tsl + * @type {Node} + */ +const materialEnvRotation = /*@__PURE__*/ uniform( new Matrix4() ).onReference( function ( frame ) { + + return frame.material; + +} ).onObjectUpdate( function ( { material, scene } ) { + + const rotation = ( scene.environment !== null && material.envMap === null ) ? scene.environmentRotation : material.envMapRotation; + + if ( rotation ) { + + _e1$1.copy( rotation ); + + _m1$1.makeRotationFromEuler( _e1$1 ); + + } else { + + _m1$1.identity(); + + } + + return _m1$1; + +} ); + +/** + * The reflect vector in view space. + * + * @tsl + * @type {Node} + */ +const reflectView = /*@__PURE__*/ positionViewDirection.negate().reflect( normalView ); + +/** + * The refract vector in view space. + * + * @tsl + * @type {Node} + */ +const refractView = /*@__PURE__*/ positionViewDirection.negate().refract( normalView, materialRefractionRatio ); + +/** + * Used for sampling cube maps when using cube reflection mapping. + * + * @tsl + * @type {Node} + */ +const reflectVector = /*@__PURE__*/ reflectView.transformDirection( cameraViewMatrix ).toVar( 'reflectVector' ); + +/** + * Used for sampling cube maps when using cube refraction mapping. + * + * @tsl + * @type {Node} + */ +const refractVector = /*@__PURE__*/ refractView.transformDirection( cameraViewMatrix ).toVar( 'reflectVector' ); + +const EmptyTexture = /*@__PURE__*/ new CubeTexture(); + +/** + * This type of uniform node represents a cube texture. + * + * @augments TextureNode + */ +class CubeTextureNode extends TextureNode { + + static get type() { + + return 'CubeTextureNode'; + + } + + /** + * Constructs a new cube texture node. + * + * @param {CubeTexture} value - The cube texture. + * @param {?Node} [uvNode=null] - The uv node. + * @param {?Node} [levelNode=null] - The level node. + * @param {?Node} [biasNode=null] - The bias node. + */ + constructor( value, uvNode = null, levelNode = null, biasNode = null ) { + + super( value, uvNode, levelNode, biasNode ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isCubeTextureNode = true; + + } + + /** + * Overwrites the default implementation to return a fixed value `'cubeTexture'`. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The input type. + */ + getInputType( /*builder*/ ) { + + return 'cubeTexture'; + + } + + /** + * Returns a default uvs based on the mapping type of the cube texture. + * + * @return {Node} The default uv attribute. + */ + getDefaultUV() { + + const texture = this.value; + + if ( texture.mapping === CubeReflectionMapping ) { + + return reflectVector; + + } else if ( texture.mapping === CubeRefractionMapping ) { + + return refractVector; + + } else { + + console.error( 'THREE.CubeTextureNode: Mapping "%s" not supported.', texture.mapping ); + + return vec3( 0, 0, 0 ); + + } + + } + + /** + * Overwritten with an empty implementation since the `updateMatrix` flag is ignored + * for cube textures. The uv transformation matrix is not applied to cube textures. + * + * @param {boolean} value - The update toggle. + */ + setUpdateMatrix( /*updateMatrix*/ ) { } // Ignore .updateMatrix for CubeTextureNode + + /** + * Setups the uv node. Depending on the backend as well as the texture type, it might be necessary + * to modify the uv node for correct sampling. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {Node} uvNode - The uv node to setup. + * @return {Node} The updated uv node. + */ + setupUV( builder, uvNode ) { + + const texture = this.value; + + if ( builder.renderer.coordinateSystem === WebGPUCoordinateSystem || ! texture.isRenderTargetTexture ) { + + uvNode = vec3( uvNode.x.negate(), uvNode.yz ); + + } + + return materialEnvRotation.mul( uvNode ); + + } + + /** + * Generates the uv code snippet. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {Node} cubeUV - The uv node to generate code for. + * @return {string} The generated code snippet. + */ + generateUV( builder, cubeUV ) { + + return cubeUV.build( builder, 'vec3' ); + + } + +} + +/** + * TSL function for creating a cube texture node. + * + * @tsl + * @function + * @param {CubeTexture} value - The cube texture. + * @param {?Node} [uvNode=null] - The uv node. + * @param {?Node} [levelNode=null] - The level node. + * @param {?Node} [biasNode=null] - The bias node. + * @returns {CubeTextureNode} + */ +const cubeTextureBase = /*@__PURE__*/ nodeProxy( CubeTextureNode ).setParameterLength( 1, 4 ).setName( 'cubeTexture' ); + +/** + * TSL function for creating a cube texture uniform node. + * + * @tsl + * @function + * @param {?CubeTexture|CubeTextureNode} [value=EmptyTexture] - The cube texture. + * @param {?Node} [uvNode=null] - The uv node. + * @param {?Node} [levelNode=null] - The level node. + * @param {?Node} [biasNode=null] - The bias node. + * @returns {CubeTextureNode} + */ +const cubeTexture = ( value = EmptyTexture, uvNode = null, levelNode = null, biasNode = null ) => { + + let textureNode; + + if ( value && value.isCubeTextureNode === true ) { + + textureNode = nodeObject( value.clone() ); + textureNode.referenceNode = value.getSelf(); // Ensure the reference is set to the original node + + if ( uvNode !== null ) textureNode.uvNode = nodeObject( uvNode ); + if ( levelNode !== null ) textureNode.levelNode = nodeObject( levelNode ); + if ( biasNode !== null ) textureNode.biasNode = nodeObject( biasNode ); + + } else { + + textureNode = cubeTextureBase( value, uvNode, levelNode, biasNode ); + + } + + return textureNode; + +}; + +/** + * TSL function for creating a uniform cube texture node. + * + * @tsl + * @function + * @param {?CubeTexture} [value=EmptyTexture] - The cube texture. + * @returns {CubeTextureNode} + */ +const uniformCubeTexture = ( value = EmptyTexture ) => cubeTextureBase( value ); + +// TODO: Avoid duplicated code and ues only ReferenceBaseNode or ReferenceNode + +/** + * This class is only relevant if the referenced property is array-like. + * In this case, `ReferenceElementNode` allows to refer to a specific + * element inside the data structure via an index. + * + * @augments ArrayElementNode + */ +class ReferenceElementNode extends ArrayElementNode { + + static get type() { + + return 'ReferenceElementNode'; + + } + + /** + * Constructs a new reference element node. + * + * @param {?ReferenceNode} referenceNode - The reference node. + * @param {Node} indexNode - The index node that defines the element access. + */ + constructor( referenceNode, indexNode ) { + + super( referenceNode, indexNode ); + + /** + * Similar to {@link ReferenceNode#reference}, an additional + * property references to the current node. + * + * @type {?ReferenceNode} + * @default null + */ + this.referenceNode = referenceNode; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isReferenceElementNode = true; + + } + + /** + * This method is overwritten since the node type is inferred from + * the uniform type of the reference node. + * + * @return {string} The node type. + */ + getNodeType() { + + return this.referenceNode.uniformType; + + } + + generate( builder ) { + + const snippet = super.generate( builder ); + const arrayType = this.referenceNode.getNodeType(); + const elementType = this.getNodeType(); + + return builder.format( snippet, arrayType, elementType ); + + } + +} + +/** + * This type of node establishes a reference to a property of another object. + * In this way, the value of the node is automatically linked to the value of + * referenced object. Reference nodes internally represent the linked value + * as a uniform. + * + * @augments Node + */ +class ReferenceNode extends Node { + + static get type() { + + return 'ReferenceNode'; + + } + + /** + * Constructs a new reference node. + * + * @param {string} property - The name of the property the node refers to. + * @param {string} uniformType - The uniform type that should be used to represent the property value. + * @param {?Object} [object=null] - The object the property belongs to. + * @param {?number} [count=null] - When the linked property is an array-like, this parameter defines its length. + */ + constructor( property, uniformType, object = null, count = null ) { + + super(); + + /** + * The name of the property the node refers to. + * + * @type {string} + */ + this.property = property; + + /** + * The uniform type that should be used to represent the property value. + * + * @type {string} + */ + this.uniformType = uniformType; + + /** + * The object the property belongs to. + * + * @type {?Object} + * @default null + */ + this.object = object; + + /** + * When the linked property is an array, this parameter defines its length. + * + * @type {?number} + * @default null + */ + this.count = count; + + /** + * The property name might have dots so nested properties can be referred. + * The hierarchy of the names is stored inside this array. + * + * @type {Array} + */ + this.properties = property.split( '.' ); + + /** + * Points to the current referred object. This property exists next to {@link ReferenceNode#object} + * since the final reference might be updated from calling code. + * + * @type {?Object} + * @default null + */ + this.reference = object; + + /** + * The uniform node that holds the value of the reference node. + * + * @type {UniformNode} + * @default null + */ + this.node = null; + + /** + * The uniform group of the internal uniform. + * + * @type {UniformGroupNode} + * @default null + */ + this.group = null; + + /** + * An optional label of the internal uniform node. + * + * @type {?string} + * @default null + */ + this.name = null; + + /** + * Overwritten since reference nodes are updated per object. + * + * @type {string} + * @default 'object' + */ + this.updateType = NodeUpdateType.OBJECT; + + } + + /** + * When the referred property is array-like, this method can be used + * to access elements via an index node. + * + * @param {IndexNode} indexNode - indexNode. + * @return {ReferenceElementNode} A reference to an element. + */ + element( indexNode ) { + + return nodeObject( new ReferenceElementNode( this, nodeObject( indexNode ) ) ); + + } + + /** + * Sets the uniform group for this reference node. + * + * @param {UniformGroupNode} group - The uniform group to set. + * @return {ReferenceNode} A reference to this node. + */ + setGroup( group ) { + + this.group = group; + + return this; + + } + + /** + * Sets the label for the internal uniform. + * + * @param {string} name - The label to set. + * @return {ReferenceNode} A reference to this node. + */ + label( name ) { + + this.name = name; + + return this; + + } + + /** + * Sets the node type which automatically defines the internal + * uniform type. + * + * @param {string} uniformType - The type to set. + */ + setNodeType( uniformType ) { + + let node = null; + + if ( this.count !== null ) { + + node = buffer( null, uniformType, this.count ); + + } else if ( Array.isArray( this.getValueFromReference() ) ) { + + node = uniformArray( null, uniformType ); + + } else if ( uniformType === 'texture' ) { + + node = texture( null ); + + } else if ( uniformType === 'cubeTexture' ) { + + node = cubeTexture( null ); + + } else { + + node = uniform( null, uniformType ); + + } + + if ( this.group !== null ) { + + node.setGroup( this.group ); + + } + + if ( this.name !== null ) node.label( this.name ); + + this.node = node.getSelf(); + + } + + /** + * This method is overwritten since the node type is inferred from + * the type of the reference node. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType( builder ) { + + if ( this.node === null ) { + + this.updateReference( builder ); + this.updateValue(); + + } + + return this.node.getNodeType( builder ); + + } + + /** + * Returns the property value from the given referred object. + * + * @param {Object} [object=this.reference] - The object to retrieve the property value from. + * @return {any} The value. + */ + getValueFromReference( object = this.reference ) { + + const { properties } = this; + + let value = object[ properties[ 0 ] ]; + + for ( let i = 1; i < properties.length; i ++ ) { + + value = value[ properties[ i ] ]; + + } + + return value; + + } + + /** + * Allows to update the reference based on the given state. The state is only + * evaluated {@link ReferenceNode#object} is not set. + * + * @param {(NodeFrame|NodeBuilder)} state - The current state. + * @return {Object} The updated reference. + */ + updateReference( state ) { + + this.reference = this.object !== null ? this.object : state.object; + + return this.reference; + + } + + /** + * The output of the reference node is the internal uniform node. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {UniformNode} The output node. + */ + setup( /* builder */ ) { + + this.updateValue(); + + return this.node; + + } + + /** + * Overwritten to update the internal uniform value. + * + * @param {NodeFrame} frame - A reference to the current node frame. + */ + update( /*frame*/ ) { + + this.updateValue(); + + } + + /** + * Retrieves the value from the referred object property and uses it + * to updated the internal uniform. + */ + updateValue() { + + if ( this.node === null ) this.setNodeType( this.uniformType ); + + const value = this.getValueFromReference(); + + if ( Array.isArray( value ) ) { + + this.node.array = value; + + } else { + + this.node.value = value; + + } + + } + +} + +/** + * TSL function for creating a reference node. + * + * @tsl + * @function + * @param {string} name - The name of the property the node refers to. + * @param {string} type - The uniform type that should be used to represent the property value. + * @param {?Object} [object] - The object the property belongs to. + * @returns {ReferenceNode} + */ +const reference = ( name, type, object ) => nodeObject( new ReferenceNode( name, type, object ) ); + +/** + * TSL function for creating a reference node. Use this function if you want need a reference + * to an array-like property that should be represented as a uniform buffer. + * + * @tsl + * @function + * @param {string} name - The name of the property the node refers to. + * @param {string} type - The uniform type that should be used to represent the property value. + * @param {number} count - The number of value inside the array-like object. + * @param {Object} object - An array-like object the property belongs to. + * @returns {ReferenceNode} + */ +const referenceBuffer = ( name, type, count, object ) => nodeObject( new ReferenceNode( name, type, object, count ) ); + +/** + * This node is a special type of reference node which is intended + * for linking material properties with node values. + * ```js + * const opacityNode = materialReference( 'opacity', 'float', material ); + * ``` + * When changing `material.opacity`, the node value of `opacityNode` will + * automatically be updated. + * + * @augments ReferenceNode + */ +class MaterialReferenceNode extends ReferenceNode { + + static get type() { + + return 'MaterialReferenceNode'; + + } + + /** + * Constructs a new material reference node. + * + * @param {string} property - The name of the property the node refers to. + * @param {string} inputType - The uniform type that should be used to represent the property value. + * @param {?Material} [material=null] - The material the property belongs to. When no material is set, + * the node refers to the material of the current rendered object. + */ + constructor( property, inputType, material = null ) { + + super( property, inputType, material ); + + /** + * The material the property belongs to. When no material is set, + * the node refers to the material of the current rendered object. + * + * @type {?Material} + * @default null + */ + this.material = material; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMaterialReferenceNode = true; + + } + + /** + * Updates the reference based on the given state. The state is only evaluated + * {@link MaterialReferenceNode#material} is not set. + * + * @param {(NodeFrame|NodeBuilder)} state - The current state. + * @return {Object} The updated reference. + */ + updateReference( state ) { + + this.reference = this.material !== null ? this.material : state.material; + + return this.reference; + + } + +} + +/** + * TSL function for creating a material reference node. + * + * @tsl + * @function + * @param {string} name - The name of the property the node refers to. + * @param {string} type - The uniform type that should be used to represent the property value. + * @param {?Material} [material=null] - The material the property belongs to. + * When no material is set, the node refers to the material of the current rendered object. + * @returns {MaterialReferenceNode} + */ +const materialReference = ( name, type, material = null ) => nodeObject( new MaterialReferenceNode( name, type, material ) ); + +// Normal Mapping Without Precomputed Tangents +// http://www.thetenthplanet.de/archives/1180 + +const uv = uv$1(); + +const q0 = positionView.dFdx(); +const q1 = positionView.dFdy(); +const st0 = uv.dFdx(); +const st1 = uv.dFdy(); + +const N = normalView; + +const q1perp = q1.cross( N ); +const q0perp = N.cross( q0 ); + +const T = q1perp.mul( st0.x ).add( q0perp.mul( st1.x ) ); +const B = q1perp.mul( st0.y ).add( q0perp.mul( st1.y ) ); + +const det = T.dot( T ).max( B.dot( B ) ); +const scale = det.equal( 0.0 ).select( 0.0, det.inverseSqrt() ); + +/** + * Tangent vector in view space, computed dynamically from geometry and UV derivatives. + * Useful for normal mapping without precomputed tangents. + * + * Reference: http://www.thetenthplanet.de/archives/1180 + * + * @tsl + * @type {Node} + */ +const tangentViewFrame = /*@__PURE__*/ T.mul( scale ).toVar( 'tangentViewFrame' ); + +/** + * Bitangent vector in view space, computed dynamically from geometry and UV derivatives. + * Complements the tangentViewFrame for constructing the tangent space basis. + * + * Reference: http://www.thetenthplanet.de/archives/1180 + * + * @tsl + * @type {Node} + */ +const bitangentViewFrame = /*@__PURE__*/ B.mul( scale ).toVar( 'bitangentViewFrame' ); + +/** + * TSL object that represents the tangent attribute of the current rendered object. + * + * @tsl + * @type {Node} + */ +const tangentGeometry = /*@__PURE__*/ Fn( ( builder ) => { + + if ( builder.geometry.hasAttribute( 'tangent' ) === false ) { + + builder.geometry.computeTangents(); + + } + + return attribute( 'tangent', 'vec4' ); + +} )(); + +/** + * TSL object that represents the vertex tangent in local space of the current rendered object. + * + * @tsl + * @type {Node} + */ +const tangentLocal = /*@__PURE__*/ tangentGeometry.xyz.toVar( 'tangentLocal' ); + +/** + * TSL object that represents the vertex tangent in view space of the current rendered object. + * + * @tsl + * @type {Node} + */ +const tangentView = /*@__PURE__*/ ( Fn( ( { subBuildFn, geometry, material } ) => { + + let node; + + if ( subBuildFn === 'VERTEX' || geometry.hasAttribute( 'tangent' ) ) { + + node = modelViewMatrix.mul( vec4( tangentLocal, 0 ) ).xyz.toVarying( 'v_tangentView' ).normalize(); + + } else { + + node = tangentViewFrame; + + } + + if ( material.flatShading !== true ) { + + node = directionToFaceDirection( node ); + + } + + return node; + +}, 'vec3' ).once( [ 'NORMAL', 'VERTEX' ] ) )().toVar( 'tangentView' ); + +/** + * TSL object that represents the vertex tangent in world space of the current rendered object. + * + * @tsl + * @type {Node} + */ +const tangentWorld = /*@__PURE__*/ tangentView.transformDirection( cameraViewMatrix ).toVarying( 'v_tangentWorld' ).normalize().toVar( 'tangentWorld' ); + +/** + * Returns the bitangent node and assigns it to a varying if the material is not flat shaded. + * + * @tsl + * @private + * @param {Node} crossNormalTangent - The cross product of the normal and tangent vectors. + * @param {string} varyingName - The name of the varying to assign the bitangent to. + * @returns {Node} The bitangent node. + */ +const getBitangent = /*@__PURE__*/ Fn( ( [ crossNormalTangent, varyingName ], { subBuildFn, material } ) => { + + let bitangent = crossNormalTangent.mul( tangentGeometry.w ).xyz; + + if ( subBuildFn === 'NORMAL' && material.flatShading !== true ) { + + bitangent = bitangent.toVarying( varyingName ); + + } + + return bitangent; + +} ).once( [ 'NORMAL' ] ); + +/** + * TSL object that represents the bitangent attribute of the current rendered object. + * + * @tsl + * @type {Node} + */ +const bitangentGeometry = /*@__PURE__*/ getBitangent( normalGeometry.cross( tangentGeometry ), 'v_bitangentGeometry' ).normalize().toVar( 'bitangentGeometry' ); + +/** + * TSL object that represents the vertex bitangent in local space of the current rendered object. + * + * @tsl + * @type {Node} + */ +const bitangentLocal = /*@__PURE__*/ getBitangent( normalLocal.cross( tangentLocal ), 'v_bitangentLocal' ).normalize().toVar( 'bitangentLocal' ); + +/** + * TSL object that represents the vertex bitangent in view space of the current rendered object. + * + * @tsl + * @type {Node} + */ +const bitangentView = /*@__PURE__*/ ( Fn( ( { subBuildFn, geometry, material } ) => { + + let node; + + if ( subBuildFn === 'VERTEX' || geometry.hasAttribute( 'tangent' ) ) { + + node = getBitangent( normalView.cross( tangentView ), 'v_bitangentView' ).normalize(); + + } else { + + node = bitangentViewFrame; + + } + + if ( material.flatShading !== true ) { + + node = directionToFaceDirection( node ); + + } + + return node; + +}, 'vec3' ).once( [ 'NORMAL', 'VERTEX' ] ) )().toVar( 'bitangentView' ); + +/** + * TSL object that represents the vertex bitangent in world space of the current rendered object. + * + * @tsl + * @type {Node} + */ +const bitangentWorld = /*@__PURE__*/ getBitangent( normalWorld.cross( tangentWorld ), 'v_bitangentWorld' ).normalize().toVar( 'bitangentWorld' ); + +/** + * TSL object that represents the TBN matrix in view space. + * + * @tsl + * @type {Node} + */ +const TBNViewMatrix = /*@__PURE__*/ mat3( tangentView, bitangentView, normalView ).toVar( 'TBNViewMatrix' ); + +/** + * TSL object that represents the parallax direction. + * + * @tsl + * @type {Node} + */ +const parallaxDirection = /*@__PURE__*/ positionViewDirection.mul( TBNViewMatrix )/*.normalize()*/; + +/** + * TSL function for computing parallax uv coordinates. + * + * @tsl + * @function + * @param {Node} uv - A uv node. + * @param {Node} scale - A scale node. + * @returns {Node} Parallax uv coordinates. + */ +const parallaxUV = ( uv, scale ) => uv.sub( parallaxDirection.mul( scale ) ); + +/** + * TSL function for computing bent normals. + * + * @tsl + * @function + * @returns {Node} Bent normals. + */ +const bentNormalView = /*@__PURE__*/ ( Fn( () => { + + // https://google.github.io/filament/Filament.md.html#lighting/imagebasedlights/anisotropy + + let bentNormal = anisotropyB.cross( positionViewDirection ); + bentNormal = bentNormal.cross( anisotropyB ).normalize(); + bentNormal = mix( bentNormal, normalView, anisotropy.mul( roughness.oneMinus() ).oneMinus().pow2().pow2() ).normalize(); + + return bentNormal; + +} ).once() )(); + +/** + * This class can be used for applying normals maps to materials. + * + * ```js + * material.normalNode = normalMap( texture( normalTex ) ); + * ``` + * + * @augments TempNode + */ +class NormalMapNode extends TempNode { + + static get type() { + + return 'NormalMapNode'; + + } + + /** + * Constructs a new normal map node. + * + * @param {Node} node - Represents the normal map data. + * @param {?Node} [scaleNode=null] - Controls the intensity of the effect. + */ + constructor( node, scaleNode = null ) { + + super( 'vec3' ); + + /** + * Represents the normal map data. + * + * @type {Node} + */ + this.node = node; + + /** + * Controls the intensity of the effect. + * + * @type {?Node} + * @default null + */ + this.scaleNode = scaleNode; + + /** + * The normal map type. + * + * @type {(TangentSpaceNormalMap|ObjectSpaceNormalMap)} + * @default TangentSpaceNormalMap + */ + this.normalMapType = TangentSpaceNormalMap; + + } + + setup( { material } ) { + + const { normalMapType, scaleNode } = this; + + let normalMap = this.node.mul( 2.0 ).sub( 1.0 ); + + if ( scaleNode !== null ) { + + let scale = scaleNode; + + if ( material.flatShading === true ) { + + scale = directionToFaceDirection( scale ); + + } + + normalMap = vec3( normalMap.xy.mul( scale ), normalMap.z ); + + } + + let output = null; + + if ( normalMapType === ObjectSpaceNormalMap ) { + + output = transformNormalToView( normalMap ); + + } else if ( normalMapType === TangentSpaceNormalMap ) { + + output = TBNViewMatrix.mul( normalMap ).normalize(); + + } else { + + console.error( `THREE.NodeMaterial: Unsupported normal map type: ${ normalMapType }` ); + + output = normalView; // Fallback to default normal view + + } + + return output; + + } + +} + +/** + * TSL function for creating a normal map node. + * + * @tsl + * @function + * @param {Node} node - Represents the normal map data. + * @param {?Node} [scaleNode=null] - Controls the intensity of the effect. + * @returns {NormalMapNode} + */ +const normalMap = /*@__PURE__*/ nodeProxy( NormalMapNode ).setParameterLength( 1, 2 ); + +// Bump Mapping Unparametrized Surfaces on the GPU by Morten S. Mikkelsen +// https://mmikk.github.io/papers3d/mm_sfgrad_bump.pdf + +const dHdxy_fwd = Fn( ( { textureNode, bumpScale } ) => { + + // It's used to preserve the same TextureNode instance + const sampleTexture = ( callback ) => textureNode.cache().context( { getUV: ( texNode ) => callback( texNode.uvNode || uv$1() ), forceUVContext: true } ); + + const Hll = float( sampleTexture( ( uvNode ) => uvNode ) ); + + return vec2( + float( sampleTexture( ( uvNode ) => uvNode.add( uvNode.dFdx() ) ) ).sub( Hll ), + float( sampleTexture( ( uvNode ) => uvNode.add( uvNode.dFdy() ) ) ).sub( Hll ) + ).mul( bumpScale ); + +} ); + +// Evaluate the derivative of the height w.r.t. screen-space using forward differencing (listing 2) + +const perturbNormalArb = Fn( ( inputs ) => { + + const { surf_pos, surf_norm, dHdxy } = inputs; + + // normalize is done to ensure that the bump map looks the same regardless of the texture's scale + const vSigmaX = surf_pos.dFdx().normalize(); + const vSigmaY = surf_pos.dFdy().normalize(); + const vN = surf_norm; // normalized + + const R1 = vSigmaY.cross( vN ); + const R2 = vN.cross( vSigmaX ); + + const fDet = vSigmaX.dot( R1 ).mul( faceDirection ); + + const vGrad = fDet.sign().mul( dHdxy.x.mul( R1 ).add( dHdxy.y.mul( R2 ) ) ); + + return fDet.abs().mul( surf_norm ).sub( vGrad ).normalize(); + +} ); + +/** + * This class can be used for applying bump maps to materials. + * + * ```js + * material.normalNode = bumpMap( texture( bumpTex ) ); + * ``` + * + * @augments TempNode + */ +class BumpMapNode extends TempNode { + + static get type() { + + return 'BumpMapNode'; + + } + + /** + * Constructs a new bump map node. + * + * @param {Node} textureNode - Represents the bump map data. + * @param {?Node} [scaleNode=null] - Controls the intensity of the bump effect. + */ + constructor( textureNode, scaleNode = null ) { + + super( 'vec3' ); + + /** + * Represents the bump map data. + * + * @type {Node} + */ + this.textureNode = textureNode; + + /** + * Controls the intensity of the bump effect. + * + * @type {?Node} + * @default null + */ + this.scaleNode = scaleNode; + + } + + setup() { + + const bumpScale = this.scaleNode !== null ? this.scaleNode : 1; + const dHdxy = dHdxy_fwd( { textureNode: this.textureNode, bumpScale } ); + + return perturbNormalArb( { + surf_pos: positionView, + surf_norm: normalView, + dHdxy + } ); + + } + +} + +/** + * TSL function for creating a bump map node. + * + * @tsl + * @function + * @param {Node} textureNode - Represents the bump map data. + * @param {?Node} [scaleNode=null] - Controls the intensity of the bump effect. + * @returns {BumpMapNode} + */ +const bumpMap = /*@__PURE__*/ nodeProxy( BumpMapNode ).setParameterLength( 1, 2 ); + +const _propertyCache = new Map(); + +/** + * This class should simplify the node access to material properties. + * It internal uses reference nodes to make sure changes to material + * properties are automatically reflected to predefined TSL objects + * like e.g. `materialColor`. + * + * @augments Node + */ +class MaterialNode extends Node { + + static get type() { + + return 'MaterialNode'; + + } + + /** + * Constructs a new material node. + * + * @param {string} scope - The scope defines what kind of material property is referred by the node. + */ + constructor( scope ) { + + super(); + + /** + * The scope defines what material property is referred by the node. + * + * @type {string} + */ + this.scope = scope; + + } + + /** + * Returns a cached reference node for the given property and type. + * + * @param {string} property - The name of the material property. + * @param {string} type - The uniform type of the property. + * @return {MaterialReferenceNode} A material reference node representing the property access. + */ + getCache( property, type ) { + + let node = _propertyCache.get( property ); + + if ( node === undefined ) { + + node = materialReference( property, type ); + + _propertyCache.set( property, node ); + + } + + return node; + + } + + /** + * Returns a float-typed material reference node for the given property name. + * + * @param {string} property - The name of the material property. + * @return {MaterialReferenceNode} A material reference node representing the property access. + */ + getFloat( property ) { + + return this.getCache( property, 'float' ); + + } + + /** + * Returns a color-typed material reference node for the given property name. + * + * @param {string} property - The name of the material property. + * @return {MaterialReferenceNode} A material reference node representing the property access. + */ + getColor( property ) { + + return this.getCache( property, 'color' ); + + } + + /** + * Returns a texture-typed material reference node for the given property name. + * + * @param {string} property - The name of the material property. + * @return {MaterialReferenceNode} A material reference node representing the property access. + */ + getTexture( property ) { + + return this.getCache( property === 'map' ? 'map' : property + 'Map', 'texture' ); + + } + + /** + * The node setup is done depending on the selected scope. Multiple material properties + * might be grouped into a single node composition if they logically belong together. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {Node} The node representing the selected scope. + */ + setup( builder ) { + + const material = builder.context.material; + const scope = this.scope; + + let node = null; + + if ( scope === MaterialNode.COLOR ) { + + const colorNode = material.color !== undefined ? this.getColor( scope ) : vec3(); + + if ( material.map && material.map.isTexture === true ) { + + node = colorNode.mul( this.getTexture( 'map' ) ); + + } else { + + node = colorNode; + + } + + } else if ( scope === MaterialNode.OPACITY ) { + + const opacityNode = this.getFloat( scope ); + + if ( material.alphaMap && material.alphaMap.isTexture === true ) { + + node = opacityNode.mul( this.getTexture( 'alpha' ) ); + + } else { + + node = opacityNode; + + } + + } else if ( scope === MaterialNode.SPECULAR_STRENGTH ) { + + if ( material.specularMap && material.specularMap.isTexture === true ) { + + node = this.getTexture( 'specular' ).r; + + } else { + + node = float( 1 ); + + } + + } else if ( scope === MaterialNode.SPECULAR_INTENSITY ) { + + const specularIntensityNode = this.getFloat( scope ); + + if ( material.specularIntensityMap && material.specularIntensityMap.isTexture === true ) { + + node = specularIntensityNode.mul( this.getTexture( scope ).a ); + + } else { + + node = specularIntensityNode; + + } + + } else if ( scope === MaterialNode.SPECULAR_COLOR ) { + + const specularColorNode = this.getColor( scope ); + + if ( material.specularColorMap && material.specularColorMap.isTexture === true ) { + + node = specularColorNode.mul( this.getTexture( scope ).rgb ); + + } else { + + node = specularColorNode; + + } + + } else if ( scope === MaterialNode.ROUGHNESS ) { // TODO: cleanup similar branches + + const roughnessNode = this.getFloat( scope ); + + if ( material.roughnessMap && material.roughnessMap.isTexture === true ) { + + node = roughnessNode.mul( this.getTexture( scope ).g ); + + } else { + + node = roughnessNode; + + } + + } else if ( scope === MaterialNode.METALNESS ) { + + const metalnessNode = this.getFloat( scope ); + + if ( material.metalnessMap && material.metalnessMap.isTexture === true ) { + + node = metalnessNode.mul( this.getTexture( scope ).b ); + + } else { + + node = metalnessNode; + + } + + } else if ( scope === MaterialNode.EMISSIVE ) { + + const emissiveIntensityNode = this.getFloat( 'emissiveIntensity' ); + const emissiveNode = this.getColor( scope ).mul( emissiveIntensityNode ); + + if ( material.emissiveMap && material.emissiveMap.isTexture === true ) { + + node = emissiveNode.mul( this.getTexture( scope ) ); + + } else { + + node = emissiveNode; + + } + + } else if ( scope === MaterialNode.NORMAL ) { + + if ( material.normalMap ) { + + node = normalMap( this.getTexture( 'normal' ), this.getCache( 'normalScale', 'vec2' ) ); + node.normalMapType = material.normalMapType; + + } else if ( material.bumpMap ) { + + node = bumpMap( this.getTexture( 'bump' ).r, this.getFloat( 'bumpScale' ) ); + + } else { + + node = normalView; + + } + + } else if ( scope === MaterialNode.CLEARCOAT ) { + + const clearcoatNode = this.getFloat( scope ); + + if ( material.clearcoatMap && material.clearcoatMap.isTexture === true ) { + + node = clearcoatNode.mul( this.getTexture( scope ).r ); + + } else { + + node = clearcoatNode; + + } + + } else if ( scope === MaterialNode.CLEARCOAT_ROUGHNESS ) { + + const clearcoatRoughnessNode = this.getFloat( scope ); + + if ( material.clearcoatRoughnessMap && material.clearcoatRoughnessMap.isTexture === true ) { + + node = clearcoatRoughnessNode.mul( this.getTexture( scope ).r ); + + } else { + + node = clearcoatRoughnessNode; + + } + + } else if ( scope === MaterialNode.CLEARCOAT_NORMAL ) { + + if ( material.clearcoatNormalMap ) { + + node = normalMap( this.getTexture( scope ), this.getCache( scope + 'Scale', 'vec2' ) ); + + } else { + + node = normalView; + + } + + } else if ( scope === MaterialNode.SHEEN ) { + + const sheenNode = this.getColor( 'sheenColor' ).mul( this.getFloat( 'sheen' ) ); // Move this mul() to CPU + + if ( material.sheenColorMap && material.sheenColorMap.isTexture === true ) { + + node = sheenNode.mul( this.getTexture( 'sheenColor' ).rgb ); + + } else { + + node = sheenNode; + + } + + } else if ( scope === MaterialNode.SHEEN_ROUGHNESS ) { + + const sheenRoughnessNode = this.getFloat( scope ); + + if ( material.sheenRoughnessMap && material.sheenRoughnessMap.isTexture === true ) { + + node = sheenRoughnessNode.mul( this.getTexture( scope ).a ); + + } else { + + node = sheenRoughnessNode; + + } + + node = node.clamp( 0.07, 1.0 ); + + } else if ( scope === MaterialNode.ANISOTROPY ) { + + if ( material.anisotropyMap && material.anisotropyMap.isTexture === true ) { + + const anisotropyPolar = this.getTexture( scope ); + const anisotropyMat = mat2( materialAnisotropyVector.x, materialAnisotropyVector.y, materialAnisotropyVector.y.negate(), materialAnisotropyVector.x ); + + node = anisotropyMat.mul( anisotropyPolar.rg.mul( 2.0 ).sub( vec2( 1.0 ) ).normalize().mul( anisotropyPolar.b ) ); + + } else { + + node = materialAnisotropyVector; + + } + + } else if ( scope === MaterialNode.IRIDESCENCE_THICKNESS ) { + + const iridescenceThicknessMaximum = reference( '1', 'float', material.iridescenceThicknessRange ); + + if ( material.iridescenceThicknessMap ) { + + const iridescenceThicknessMinimum = reference( '0', 'float', material.iridescenceThicknessRange ); + + node = iridescenceThicknessMaximum.sub( iridescenceThicknessMinimum ).mul( this.getTexture( scope ).g ).add( iridescenceThicknessMinimum ); + + } else { + + node = iridescenceThicknessMaximum; + + } + + } else if ( scope === MaterialNode.TRANSMISSION ) { + + const transmissionNode = this.getFloat( scope ); + + if ( material.transmissionMap ) { + + node = transmissionNode.mul( this.getTexture( scope ).r ); + + } else { + + node = transmissionNode; + + } + + } else if ( scope === MaterialNode.THICKNESS ) { + + const thicknessNode = this.getFloat( scope ); + + if ( material.thicknessMap ) { + + node = thicknessNode.mul( this.getTexture( scope ).g ); + + } else { + + node = thicknessNode; + + } + + } else if ( scope === MaterialNode.IOR ) { + + node = this.getFloat( scope ); + + } else if ( scope === MaterialNode.LIGHT_MAP ) { + + node = this.getTexture( scope ).rgb.mul( this.getFloat( 'lightMapIntensity' ) ); + + } else if ( scope === MaterialNode.AO ) { + + node = this.getTexture( scope ).r.sub( 1.0 ).mul( this.getFloat( 'aoMapIntensity' ) ).add( 1.0 ); + + } else if ( scope === MaterialNode.LINE_DASH_OFFSET ) { + + node = ( material.dashOffset ) ? this.getFloat( scope ) : float( 0 ); + + } else { + + const outputType = this.getNodeType( builder ); + + node = this.getCache( scope, outputType ); + + } + + return node; + + } + +} + +MaterialNode.ALPHA_TEST = 'alphaTest'; +MaterialNode.COLOR = 'color'; +MaterialNode.OPACITY = 'opacity'; +MaterialNode.SHININESS = 'shininess'; +MaterialNode.SPECULAR = 'specular'; +MaterialNode.SPECULAR_STRENGTH = 'specularStrength'; +MaterialNode.SPECULAR_INTENSITY = 'specularIntensity'; +MaterialNode.SPECULAR_COLOR = 'specularColor'; +MaterialNode.REFLECTIVITY = 'reflectivity'; +MaterialNode.ROUGHNESS = 'roughness'; +MaterialNode.METALNESS = 'metalness'; +MaterialNode.NORMAL = 'normal'; +MaterialNode.CLEARCOAT = 'clearcoat'; +MaterialNode.CLEARCOAT_ROUGHNESS = 'clearcoatRoughness'; +MaterialNode.CLEARCOAT_NORMAL = 'clearcoatNormal'; +MaterialNode.EMISSIVE = 'emissive'; +MaterialNode.ROTATION = 'rotation'; +MaterialNode.SHEEN = 'sheen'; +MaterialNode.SHEEN_ROUGHNESS = 'sheenRoughness'; +MaterialNode.ANISOTROPY = 'anisotropy'; +MaterialNode.IRIDESCENCE = 'iridescence'; +MaterialNode.IRIDESCENCE_IOR = 'iridescenceIOR'; +MaterialNode.IRIDESCENCE_THICKNESS = 'iridescenceThickness'; +MaterialNode.IOR = 'ior'; +MaterialNode.TRANSMISSION = 'transmission'; +MaterialNode.THICKNESS = 'thickness'; +MaterialNode.ATTENUATION_DISTANCE = 'attenuationDistance'; +MaterialNode.ATTENUATION_COLOR = 'attenuationColor'; +MaterialNode.LINE_SCALE = 'scale'; +MaterialNode.LINE_DASH_SIZE = 'dashSize'; +MaterialNode.LINE_GAP_SIZE = 'gapSize'; +MaterialNode.LINE_WIDTH = 'linewidth'; +MaterialNode.LINE_DASH_OFFSET = 'dashOffset'; +MaterialNode.POINT_SIZE = 'size'; +MaterialNode.DISPERSION = 'dispersion'; +MaterialNode.LIGHT_MAP = 'light'; +MaterialNode.AO = 'ao'; + +/** + * TSL object that represents alpha test of the current material. + * + * @tsl + * @type {Node} + */ +const materialAlphaTest = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.ALPHA_TEST ); + +/** + * TSL object that represents the diffuse color of the current material. + * The value is composed via `color` * `map`. + * + * @tsl + * @type {Node} + */ +const materialColor = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.COLOR ); + +/** + * TSL object that represents the shininess of the current material. + * + * @tsl + * @type {Node} + */ +const materialShininess = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.SHININESS ); + +/** + * TSL object that represents the emissive color of the current material. + * The value is composed via `emissive` * `emissiveIntensity` * `emissiveMap`. + * + * @tsl + * @type {Node} + */ +const materialEmissive = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.EMISSIVE ); + +/** + * TSL object that represents the opacity of the current material. + * The value is composed via `opacity` * `alphaMap`. + * + * @tsl + * @type {Node} + */ +const materialOpacity = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.OPACITY ); + +/** + * TSL object that represents the specular of the current material. + * + * @tsl + * @type {Node} + */ +const materialSpecular = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.SPECULAR ); + +/** + * TSL object that represents the specular intensity of the current material. + * The value is composed via `specularIntensity` * `specularMap.a`. + * + * @tsl + * @type {Node} + */ +const materialSpecularIntensity = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.SPECULAR_INTENSITY ); + +/** + * TSL object that represents the specular color of the current material. + * The value is composed via `specularColor` * `specularMap.rgb`. + * + * @tsl + * @type {Node} + */ +const materialSpecularColor = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.SPECULAR_COLOR ); + +/** + * TSL object that represents the specular strength of the current material. + * The value is composed via `specularMap.r`. + * + * @tsl + * @type {Node} + */ +const materialSpecularStrength = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.SPECULAR_STRENGTH ); + +/** + * TSL object that represents the reflectivity of the current material. + * + * @tsl + * @type {Node} + */ +const materialReflectivity = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.REFLECTIVITY ); + +/** + * TSL object that represents the roughness of the current material. + * The value is composed via `roughness` * `roughnessMap.g`. + * + * @tsl + * @type {Node} + */ +const materialRoughness = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.ROUGHNESS ); + +/** + * TSL object that represents the metalness of the current material. + * The value is composed via `metalness` * `metalnessMap.b`. + * + * @tsl + * @type {Node} + */ +const materialMetalness = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.METALNESS ); + +/** + * TSL object that represents the normal of the current material. + * The value will be either `normalMap` * `normalScale`, `bumpMap` * `bumpScale` or `normalView`. + * + * @tsl + * @type {Node} + */ +const materialNormal = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.NORMAL ); + +/** + * TSL object that represents the clearcoat of the current material. + * The value is composed via `clearcoat` * `clearcoatMap.r` + * + * @tsl + * @type {Node} + */ +const materialClearcoat = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.CLEARCOAT ); + +/** + * TSL object that represents the clearcoat roughness of the current material. + * The value is composed via `clearcoatRoughness` * `clearcoatRoughnessMap.r`. + * + * @tsl + * @type {Node} + */ +const materialClearcoatRoughness = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.CLEARCOAT_ROUGHNESS ); + +/** + * TSL object that represents the clearcoat normal of the current material. + * The value will be either `clearcoatNormalMap` or `normalView`. + * + * @tsl + * @type {Node} + */ +const materialClearcoatNormal = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.CLEARCOAT_NORMAL ); + +/** + * TSL object that represents the rotation of the current sprite material. + * + * @tsl + * @type {Node} + */ +const materialRotation = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.ROTATION ); + +/** + * TSL object that represents the sheen color of the current material. + * The value is composed via `sheen` * `sheenColor` * `sheenColorMap`. + * + * @tsl + * @type {Node} + */ +const materialSheen = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.SHEEN ); + +/** + * TSL object that represents the sheen roughness of the current material. + * The value is composed via `sheenRoughness` * `sheenRoughnessMap.a`. + * + * @tsl + * @type {Node} + */ +const materialSheenRoughness = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.SHEEN_ROUGHNESS ); + +/** + * TSL object that represents the anisotropy of the current material. + * + * @tsl + * @type {Node} + */ +const materialAnisotropy = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.ANISOTROPY ); + +/** + * TSL object that represents the iridescence of the current material. + * + * @tsl + * @type {Node} + */ +const materialIridescence = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.IRIDESCENCE ); + +/** + * TSL object that represents the iridescence IOR of the current material. + * + * @tsl + * @type {Node} + */ +const materialIridescenceIOR = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.IRIDESCENCE_IOR ); + +/** + * TSL object that represents the iridescence thickness of the current material. + * + * @tsl + * @type {Node} + */ +const materialIridescenceThickness = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.IRIDESCENCE_THICKNESS ); + +/** + * TSL object that represents the transmission of the current material. + * The value is composed via `transmission` * `transmissionMap.r`. + * + * @tsl + * @type {Node} + */ +const materialTransmission = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.TRANSMISSION ); + +/** + * TSL object that represents the thickness of the current material. + * The value is composed via `thickness` * `thicknessMap.g`. + * + * @tsl + * @type {Node} + */ +const materialThickness = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.THICKNESS ); + +/** + * TSL object that represents the IOR of the current material. + * + * @tsl + * @type {Node} + */ +const materialIOR = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.IOR ); + +/** + * TSL object that represents the attenuation distance of the current material. + * + * @tsl + * @type {Node} + */ +const materialAttenuationDistance = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.ATTENUATION_DISTANCE ); + +/** + * TSL object that represents the attenuation color of the current material. + * + * @tsl + * @type {Node} + */ +const materialAttenuationColor = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.ATTENUATION_COLOR ); + +/** + * TSL object that represents the scale of the current dashed line material. + * + * @tsl + * @type {Node} + */ +const materialLineScale = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.LINE_SCALE ); + +/** + * TSL object that represents the dash size of the current dashed line material. + * + * @tsl + * @type {Node} + */ +const materialLineDashSize = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.LINE_DASH_SIZE ); + +/** + * TSL object that represents the gap size of the current dashed line material. + * + * @tsl + * @type {Node} + */ +const materialLineGapSize = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.LINE_GAP_SIZE ); + +/** + * TSL object that represents the line width of the current line material. + * + * @tsl + * @type {Node} + */ +const materialLineWidth = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.LINE_WIDTH ); + +/** + * TSL object that represents the dash offset of the current line material. + * + * @tsl + * @type {Node} + */ +const materialLineDashOffset = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.LINE_DASH_OFFSET ); + +/** + * TSL object that represents the point size of the current points material. + * + * @tsl + * @type {Node} + */ +const materialPointSize = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.POINT_SIZE ); + +/** + * TSL object that represents the dispersion of the current material. + * + * @tsl + * @type {Node} + */ +const materialDispersion = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.DISPERSION ); + +/** + * TSL object that represents the light map of the current material. + * The value is composed via `lightMapIntensity` * `lightMap.rgb`. + * + * @tsl + * @type {Node} + */ +const materialLightMap = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.LIGHT_MAP ); + +/** + * TSL object that represents the ambient occlusion map of the current material. + * The value is composed via `aoMap.r` - 1 * `aoMapIntensity` + 1. + * + * @tsl + * @type {Node} + */ +const materialAO = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.AO ); + +/** + * TSL object that represents the anisotropy vector of the current material. + * + * @tsl + * @type {Node} + */ +const materialAnisotropyVector = /*@__PURE__*/ uniform( new Vector2() ).onReference( function ( frame ) { + + return frame.material; + +} ).onRenderUpdate( function ( { material } ) { + + this.value.set( material.anisotropy * Math.cos( material.anisotropyRotation ), material.anisotropy * Math.sin( material.anisotropyRotation ) ); + +} ); + +/** + * TSL object that represents the position in clip space after the model-view-projection transform of the current rendered object. + * + * @tsl + * @type {VaryingNode} + */ +const modelViewProjection = /*@__PURE__*/ ( Fn( ( builder ) => { + + return builder.context.setupModelViewProjection(); + +}, 'vec4' ).once() )().toVarying( 'v_modelViewProjection' ); + +/** + * This class represents shader indices of different types. The following predefined node + * objects cover frequent use cases: + * + * - `vertexIndex`: The index of a vertex within a mesh. + * - `instanceIndex`: The index of either a mesh instance or an invocation of a compute shader. + * - `drawIndex`: The index of a draw call. + * - `invocationLocalIndex`: The index of a compute invocation within the scope of a workgroup load. + * - `invocationSubgroupIndex`: The index of a compute invocation within the scope of a subgroup. + * - `subgroupIndex`: The index of the subgroup the current compute invocation belongs to. + * + * @augments Node + */ +class IndexNode extends Node { + + static get type() { + + return 'IndexNode'; + + } + + /** + * Constructs a new index node. + * + * @param {('vertex'|'instance'|'subgroup'|'invocationLocal'|'invocationSubgroup'|'draw')} scope - The scope of the index node. + */ + constructor( scope ) { + + super( 'uint' ); + + /** + * The scope of the index node. + * + * @type {string} + */ + this.scope = scope; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isIndexNode = true; + + } + + generate( builder ) { + + const nodeType = this.getNodeType( builder ); + const scope = this.scope; + + let propertyName; + + if ( scope === IndexNode.VERTEX ) { + + propertyName = builder.getVertexIndex(); + + } else if ( scope === IndexNode.INSTANCE ) { + + propertyName = builder.getInstanceIndex(); + + } else if ( scope === IndexNode.DRAW ) { + + propertyName = builder.getDrawIndex(); + + } else if ( scope === IndexNode.INVOCATION_LOCAL ) { + + propertyName = builder.getInvocationLocalIndex(); + + } else if ( scope === IndexNode.INVOCATION_SUBGROUP ) { + + propertyName = builder.getInvocationSubgroupIndex(); + + } else if ( scope === IndexNode.SUBGROUP ) { + + propertyName = builder.getSubgroupIndex(); + + } else { + + throw new Error( 'THREE.IndexNode: Unknown scope: ' + scope ); + + } + + let output; + + if ( builder.shaderStage === 'vertex' || builder.shaderStage === 'compute' ) { + + output = propertyName; + + } else { + + const nodeVarying = varying( this ); + + output = nodeVarying.build( builder, nodeType ); + + } + + return output; + + } + +} + +IndexNode.VERTEX = 'vertex'; +IndexNode.INSTANCE = 'instance'; +IndexNode.SUBGROUP = 'subgroup'; +IndexNode.INVOCATION_LOCAL = 'invocationLocal'; +IndexNode.INVOCATION_SUBGROUP = 'invocationSubgroup'; +IndexNode.DRAW = 'draw'; + +/** + * TSL object that represents the index of a vertex within a mesh. + * + * @tsl + * @type {IndexNode} + */ +const vertexIndex = /*@__PURE__*/ nodeImmutable( IndexNode, IndexNode.VERTEX ); + +/** + * TSL object that represents the index of either a mesh instance or an invocation of a compute shader. + * + * @tsl + * @type {IndexNode} + */ +const instanceIndex = /*@__PURE__*/ nodeImmutable( IndexNode, IndexNode.INSTANCE ); + +/** + * TSL object that represents the index of the subgroup the current compute invocation belongs to. + * + * @tsl + * @type {IndexNode} + */ +const subgroupIndex = /*@__PURE__*/ nodeImmutable( IndexNode, IndexNode.SUBGROUP ); + +/** + * TSL object that represents the index of a compute invocation within the scope of a subgroup. + * + * @tsl + * @type {IndexNode} + */ +const invocationSubgroupIndex = /*@__PURE__*/ nodeImmutable( IndexNode, IndexNode.INVOCATION_SUBGROUP ); + +/** + * TSL object that represents the index of a compute invocation within the scope of a workgroup load. + * + * @tsl + * @type {IndexNode} + */ +const invocationLocalIndex = /*@__PURE__*/ nodeImmutable( IndexNode, IndexNode.INVOCATION_LOCAL ); + +/** + * TSL object that represents the index of a draw call. + * + * @tsl + * @type {IndexNode} + */ +const drawIndex = /*@__PURE__*/ nodeImmutable( IndexNode, IndexNode.DRAW ); + +/** + * This node implements the vertex shader logic which is required + * when rendering 3D objects via instancing. The code makes sure + * vertex positions, normals and colors can be modified via instanced + * data. + * + * @augments Node + */ +class InstanceNode extends Node { + + static get type() { + + return 'InstanceNode'; + + } + + /** + * Constructs a new instance node. + * + * @param {number} count - The number of instances. + * @param {InstancedBufferAttribute} instanceMatrix - Instanced buffer attribute representing the instance transformations. + * @param {?InstancedBufferAttribute} instanceColor - Instanced buffer attribute representing the instance colors. + */ + constructor( count, instanceMatrix, instanceColor = null ) { + + super( 'void' ); + + /** + * The number of instances. + * + * @type {number} + */ + this.count = count; + + /** + * Instanced buffer attribute representing the transformation of instances. + * + * @type {InstancedBufferAttribute} + */ + this.instanceMatrix = instanceMatrix; + + /** + * Instanced buffer attribute representing the color of instances. + * + * @type {InstancedBufferAttribute} + */ + this.instanceColor = instanceColor; + + /** + * The node that represents the instance matrix data. + * + * @type {?Node} + */ + this.instanceMatrixNode = null; + + /** + * The node that represents the instance color data. + * + * @type {?Node} + * @default null + */ + this.instanceColorNode = null; + + /** + * The update type is set to `frame` since an update + * of instanced buffer data must be checked per frame. + * + * @type {string} + * @default 'frame' + */ + this.updateType = NodeUpdateType.FRAME; + + /** + * A reference to a buffer that is used by `instanceMatrixNode`. + * + * @type {?InstancedInterleavedBuffer} + */ + this.buffer = null; + + /** + * A reference to a buffer that is used by `instanceColorNode`. + * + * @type {?InstancedBufferAttribute} + */ + this.bufferColor = null; + + } + + /** + * Setups the internal buffers and nodes and assigns the transformed vertex data + * to predefined node variables for accumulation. That follows the same patterns + * like with morph and skinning nodes. + * + * @param {NodeBuilder} builder - The current node builder. + */ + setup( builder ) { + + const { count, instanceMatrix, instanceColor } = this; + + let { instanceMatrixNode, instanceColorNode } = this; + + if ( instanceMatrixNode === null ) { + + // Both WebGPU and WebGL backends have UBO max limited to 64kb. Matrix count number bigger than 1000 ( 16 * 4 * 1000 = 64kb ) will fallback to attribute. + + if ( count <= 1000 ) { + + instanceMatrixNode = buffer( instanceMatrix.array, 'mat4', Math.max( count, 1 ) ).element( instanceIndex ); + + } else { + + const buffer = new InstancedInterleavedBuffer( instanceMatrix.array, 16, 1 ); + + this.buffer = buffer; + + const bufferFn = instanceMatrix.usage === DynamicDrawUsage ? instancedDynamicBufferAttribute : instancedBufferAttribute; + + const instanceBuffers = [ + // F.Signature -> bufferAttribute( array, type, stride, offset ) + bufferFn( buffer, 'vec4', 16, 0 ), + bufferFn( buffer, 'vec4', 16, 4 ), + bufferFn( buffer, 'vec4', 16, 8 ), + bufferFn( buffer, 'vec4', 16, 12 ) + ]; + + instanceMatrixNode = mat4( ...instanceBuffers ); + + } + + this.instanceMatrixNode = instanceMatrixNode; + + } + + if ( instanceColor && instanceColorNode === null ) { + + const buffer = new InstancedBufferAttribute( instanceColor.array, 3 ); + + const bufferFn = instanceColor.usage === DynamicDrawUsage ? instancedDynamicBufferAttribute : instancedBufferAttribute; + + this.bufferColor = buffer; + + instanceColorNode = vec3( bufferFn( buffer, 'vec3', 3, 0 ) ); + + this.instanceColorNode = instanceColorNode; + + } + + // POSITION + + const instancePosition = instanceMatrixNode.mul( positionLocal ).xyz; + positionLocal.assign( instancePosition ); + + // NORMAL + + if ( builder.hasGeometryAttribute( 'normal' ) ) { + + const instanceNormal = transformNormal( normalLocal, instanceMatrixNode ); + + // ASSIGNS + + normalLocal.assign( instanceNormal ); + + } + + // COLOR + + if ( this.instanceColorNode !== null ) { + + varyingProperty( 'vec3', 'vInstanceColor' ).assign( this.instanceColorNode ); + + } + + } + + /** + * Checks if the internal buffers required an update. + * + * @param {NodeFrame} frame - The current node frame. + */ + update( /*frame*/ ) { + + if ( this.instanceMatrix.usage !== DynamicDrawUsage && this.buffer !== null && this.instanceMatrix.version !== this.buffer.version ) { + + this.buffer.version = this.instanceMatrix.version; + + } + + if ( this.instanceColor && this.instanceColor.usage !== DynamicDrawUsage && this.bufferColor !== null && this.instanceColor.version !== this.bufferColor.version ) { + + this.bufferColor.version = this.instanceColor.version; + + } + + } + +} + +/** + * TSL function for creating an instance node. + * + * @tsl + * @function + * @param {number} count - The number of instances. + * @param {InstancedBufferAttribute} instanceMatrix - Instanced buffer attribute representing the instance transformations. + * @param {?InstancedBufferAttribute} instanceColor - Instanced buffer attribute representing the instance colors. + * @returns {InstanceNode} + */ +const instance = /*@__PURE__*/ nodeProxy( InstanceNode ).setParameterLength( 2, 3 ); + +/** + * This is a special version of `InstanceNode` which requires the usage of {@link InstancedMesh}. + * It allows an easier setup of the instance node. + * + * @augments InstanceNode + */ +class InstancedMeshNode extends InstanceNode { + + static get type() { + + return 'InstancedMeshNode'; + + } + + /** + * Constructs a new instanced mesh node. + * + * @param {InstancedMesh} instancedMesh - The instanced mesh. + */ + constructor( instancedMesh ) { + + const { count, instanceMatrix, instanceColor } = instancedMesh; + + super( count, instanceMatrix, instanceColor ); + + /** + * A reference to the instanced mesh. + * + * @type {InstancedMesh} + */ + this.instancedMesh = instancedMesh; + + } + +} + +/** + * TSL function for creating an instanced mesh node. + * + * @tsl + * @function + * @param {InstancedMesh} instancedMesh - The instancedMesh. + * @returns {InstancedMeshNode} + */ +const instancedMesh = /*@__PURE__*/ nodeProxy( InstancedMeshNode ).setParameterLength( 1 ); + +/** + * This node implements the vertex shader logic which is required + * when rendering 3D objects via batching. `BatchNode` must be used + * with instances of {@link BatchedMesh}. + * + * @augments Node + */ +class BatchNode extends Node { + + static get type() { + + return 'BatchNode'; + + } + + /** + * Constructs a new batch node. + * + * @param {BatchedMesh} batchMesh - A reference to batched mesh. + */ + constructor( batchMesh ) { + + super( 'void' ); + + /** + * A reference to batched mesh. + * + * @type {BatchedMesh} + */ + this.batchMesh = batchMesh; + + /** + * The batching index node. + * + * @type {?IndexNode} + * @default null + */ + this.batchingIdNode = null; + + } + + /** + * Setups the internal buffers and nodes and assigns the transformed vertex data + * to predefined node variables for accumulation. That follows the same patterns + * like with morph and skinning nodes. + * + * @param {NodeBuilder} builder - The current node builder. + */ + setup( builder ) { + + if ( this.batchingIdNode === null ) { + + if ( builder.getDrawIndex() === null ) { + + this.batchingIdNode = instanceIndex; + + } else { + + this.batchingIdNode = drawIndex; + + } + + } + + const getIndirectIndex = Fn( ( [ id ] ) => { + + const size = int( textureSize( textureLoad( this.batchMesh._indirectTexture ), 0 ).x ); + const x = int( id ).mod( size ); + const y = int( id ).div( size ); + return textureLoad( this.batchMesh._indirectTexture, ivec2( x, y ) ).x; + + } ).setLayout( { + name: 'getIndirectIndex', + type: 'uint', + inputs: [ + { name: 'id', type: 'int' } + ] + } ); + + const indirectId = getIndirectIndex( int( this.batchingIdNode ) ); + + const matricesTexture = this.batchMesh._matricesTexture; + + const size = int( textureSize( textureLoad( matricesTexture ), 0 ).x ); + const j = float( indirectId ).mul( 4 ).toInt().toVar(); + + const x = j.mod( size ); + const y = j.div( size ); + const batchingMatrix = mat4( + textureLoad( matricesTexture, ivec2( x, y ) ), + textureLoad( matricesTexture, ivec2( x.add( 1 ), y ) ), + textureLoad( matricesTexture, ivec2( x.add( 2 ), y ) ), + textureLoad( matricesTexture, ivec2( x.add( 3 ), y ) ) + ); + + + const colorsTexture = this.batchMesh._colorsTexture; + + if ( colorsTexture !== null ) { + + const getBatchingColor = Fn( ( [ id ] ) => { + + const size = int( textureSize( textureLoad( colorsTexture ), 0 ).x ); + const j = id; + const x = j.mod( size ); + const y = j.div( size ); + return textureLoad( colorsTexture, ivec2( x, y ) ).rgb; + + } ).setLayout( { + name: 'getBatchingColor', + type: 'vec3', + inputs: [ + { name: 'id', type: 'int' } + ] + } ); + + const color = getBatchingColor( indirectId ); + + varyingProperty( 'vec3', 'vBatchColor' ).assign( color ); + + } + + const bm = mat3( batchingMatrix ); + + positionLocal.assign( batchingMatrix.mul( positionLocal ) ); + + const transformedNormal = normalLocal.div( vec3( bm[ 0 ].dot( bm[ 0 ] ), bm[ 1 ].dot( bm[ 1 ] ), bm[ 2 ].dot( bm[ 2 ] ) ) ); + + const batchingNormal = bm.mul( transformedNormal ).xyz; + + normalLocal.assign( batchingNormal ); + + if ( builder.hasGeometryAttribute( 'tangent' ) ) { + + tangentLocal.mulAssign( bm ); + + } + + } + +} + +/** + * TSL function for creating a batch node. + * + * @tsl + * @function + * @param {BatchedMesh} batchMesh - A reference to batched mesh. + * @returns {BatchNode} + */ +const batch = /*@__PURE__*/ nodeProxy( BatchNode ).setParameterLength( 1 ); + +/** + * This class enables element access on instances of {@link StorageBufferNode}. + * In most cases, it is indirectly used when accessing elements with the + * {@link StorageBufferNode#element} method. + * + * ```js + * const position = positionStorage.element( instanceIndex ); + * ``` + * + * @augments ArrayElementNode + */ +class StorageArrayElementNode extends ArrayElementNode { + + static get type() { + + return 'StorageArrayElementNode'; + + } + + /** + * Constructs storage buffer element node. + * + * @param {StorageBufferNode} storageBufferNode - The storage buffer node. + * @param {Node} indexNode - The index node that defines the element access. + */ + constructor( storageBufferNode, indexNode ) { + + super( storageBufferNode, indexNode ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isStorageArrayElementNode = true; + + } + + /** + * The storage buffer node. + * + * @param {Node} value + * @type {StorageBufferNode} + */ + set storageBufferNode( value ) { + + this.node = value; + + } + + get storageBufferNode() { + + return this.node; + + } + + getMemberType( builder, name ) { + + const structTypeNode = this.storageBufferNode.structTypeNode; + + if ( structTypeNode ) { + + return structTypeNode.getMemberType( builder, name ); + + } + + return 'void'; + + } + + setup( builder ) { + + if ( builder.isAvailable( 'storageBuffer' ) === false ) { + + if ( this.node.isPBO === true ) { + + builder.setupPBO( this.node ); + + } + + } + + return super.setup( builder ); + + } + + generate( builder, output ) { + + let snippet; + + const isAssignContext = builder.context.assign; + + // + + if ( builder.isAvailable( 'storageBuffer' ) === false ) { + + if ( this.node.isPBO === true && isAssignContext !== true && ( this.node.value.isInstancedBufferAttribute || builder.shaderStage !== 'compute' ) ) { + + snippet = builder.generatePBO( this ); + + } else { + + snippet = this.node.build( builder ); + + } + + } else { + + snippet = super.generate( builder ); + + } + + if ( isAssignContext !== true ) { + + const type = this.getNodeType( builder ); + + snippet = builder.format( snippet, type, output ); + + } + + return snippet; + + } + +} + +/** + * TSL function for creating a storage element node. + * + * @tsl + * @function + * @param {StorageBufferNode} storageBufferNode - The storage buffer node. + * @param {Node} indexNode - The index node that defines the element access. + * @returns {StorageArrayElementNode} + */ +const storageElement = /*@__PURE__*/ nodeProxy( StorageArrayElementNode ).setParameterLength( 2 ); + +/** + * This node is used in context of compute shaders and allows to define a + * storage buffer for data. A typical workflow is to create instances of + * this node with the convenience functions `attributeArray()` or `instancedArray()`, + * setup up a compute shader that writes into the buffers and then convert + * the storage buffers to attribute nodes for rendering. + * + * ```js + * const positionBuffer = instancedArray( particleCount, 'vec3' ); // the storage buffer node + * + * const computeInit = Fn( () => { // the compute shader + * + * const position = positionBuffer.element( instanceIndex ); + * + * // compute position data + * + * position.x = 1; + * position.y = 1; + * position.z = 1; + * + * } )().compute( particleCount ); + * + * const particleMaterial = new THREE.SpriteNodeMaterial(); + * particleMaterial.positionNode = positionBuffer.toAttribute(); + * + * renderer.computeAsync( computeInit ); + * + * ``` + * + * @augments BufferNode + */ +class StorageBufferNode extends BufferNode { + + static get type() { + + return 'StorageBufferNode'; + + } + + /** + * Constructs a new storage buffer node. + * + * @param {StorageBufferAttribute|StorageInstancedBufferAttribute|BufferAttribute} value - The buffer data. + * @param {?(string|Struct)} [bufferType=null] - The buffer type (e.g. `'vec3'`). + * @param {number} [bufferCount=0] - The buffer count. + */ + constructor( value, bufferType = null, bufferCount = 0 ) { + + let nodeType, structTypeNode = null; + + if ( bufferType && bufferType.isStruct ) { + + nodeType = 'struct'; + structTypeNode = bufferType.layout; + + if ( value.isStorageBufferAttribute || value.isStorageInstancedBufferAttribute ) { + + bufferCount = value.count; + + } + + } else if ( bufferType === null && ( value.isStorageBufferAttribute || value.isStorageInstancedBufferAttribute ) ) { + + nodeType = getTypeFromLength( value.itemSize ); + bufferCount = value.count; + + } else { + + nodeType = bufferType; + + } + + super( value, nodeType, bufferCount ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isStorageBufferNode = true; + + + /** + * The buffer struct type. + * + * @type {?StructTypeNode} + * @default null + */ + this.structTypeNode = structTypeNode; + + /** + * The access type of the texture node. + * + * @type {string} + * @default 'readWrite' + */ + this.access = NodeAccess.READ_WRITE; + + /** + * Whether the node is atomic or not. + * + * @type {boolean} + * @default false + */ + this.isAtomic = false; + + /** + * Whether the node represents a PBO or not. + * Only relevant for WebGL. + * + * @type {boolean} + * @default false + */ + this.isPBO = false; + + /** + * A reference to the internal buffer attribute node. + * + * @type {?BufferAttributeNode} + * @default null + */ + this._attribute = null; + + /** + * A reference to the internal varying node. + * + * @type {?VaryingNode} + * @default null + */ + this._varying = null; + + /** + * `StorageBufferNode` sets this property to `true` by default. + * + * @type {boolean} + * @default true + */ + this.global = true; + + if ( value.isStorageBufferAttribute !== true && value.isStorageInstancedBufferAttribute !== true ) { + + // TODO: Improve it, possibly adding a new property to the BufferAttribute to identify it as a storage buffer read-only attribute in Renderer + + if ( value.isInstancedBufferAttribute ) value.isStorageInstancedBufferAttribute = true; + else value.isStorageBufferAttribute = true; + + } + + } + + /** + * This method is overwritten since the buffer data might be shared + * and thus the hash should be shared as well. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The hash. + */ + getHash( builder ) { + + if ( this.bufferCount === 0 ) { + + let bufferData = builder.globalCache.getData( this.value ); + + if ( bufferData === undefined ) { + + bufferData = { + node: this + }; + + builder.globalCache.setData( this.value, bufferData ); + + } + + return bufferData.node.uuid; + + } + + return this.uuid; + + } + + /** + * Overwrites the default implementation to return a fixed value `'indirectStorageBuffer'` or `'storageBuffer'`. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The input type. + */ + getInputType( /*builder*/ ) { + + return this.value.isIndirectStorageBufferAttribute ? 'indirectStorageBuffer' : 'storageBuffer'; + + } + + /** + * Enables element access with the given index node. + * + * @param {IndexNode} indexNode - The index node. + * @return {StorageArrayElementNode} A node representing the element access. + */ + element( indexNode ) { + + return storageElement( this, indexNode ); + + } + + /** + * Defines whether this node is a PBO or not. Only relevant for WebGL. + * + * @param {boolean} value - The value so set. + * @return {StorageBufferNode} A reference to this node. + */ + setPBO( value ) { + + this.isPBO = value; + + return this; + + } + + /** + * Returns the `isPBO` value. + * + * @return {boolean} Whether the node represents a PBO or not. + */ + getPBO() { + + return this.isPBO; + + } + + /** + * Defines the node access. + * + * @param {string} value - The node access. + * @return {StorageBufferNode} A reference to this node. + */ + setAccess( value ) { + + this.access = value; + + return this; + + } + + /** + * Convenience method for configuring a read-only node access. + * + * @return {StorageBufferNode} A reference to this node. + */ + toReadOnly() { + + return this.setAccess( NodeAccess.READ_ONLY ); + + } + + /** + * Defines whether the node is atomic or not. + * + * @param {boolean} value - The atomic flag. + * @return {StorageBufferNode} A reference to this node. + */ + setAtomic( value ) { + + this.isAtomic = value; + + return this; + + } + + /** + * Convenience method for making this node atomic. + * + * @return {StorageBufferNode} A reference to this node. + */ + toAtomic() { + + return this.setAtomic( true ); + + } + + /** + * Returns attribute data for this storage buffer node. + * + * @return {{attribute: BufferAttributeNode, varying: VaryingNode}} The attribute data. + */ + getAttributeData() { + + if ( this._attribute === null ) { + + this._attribute = bufferAttribute( this.value ); + this._varying = varying( this._attribute ); + + } + + return { + attribute: this._attribute, + varying: this._varying + }; + + } + + /** + * This method is overwritten since the node type from the availability of storage buffers + * and the attribute data. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType( builder ) { + + if ( this.structTypeNode !== null ) { + + return this.structTypeNode.getNodeType( builder ); + + } + + if ( builder.isAvailable( 'storageBuffer' ) || builder.isAvailable( 'indirectStorageBuffer' ) ) { + + return super.getNodeType( builder ); + + } + + const { attribute } = this.getAttributeData(); + + return attribute.getNodeType( builder ); + + } + + /** + * Returns the type of a member of the struct. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {string} name - The name of the member. + * @return {string} The type of the member. + */ + getMemberType( builder, name ) { + + if ( this.structTypeNode !== null ) { + + return this.structTypeNode.getMemberType( builder, name ); + + } + + return 'void'; + + } + + /** + * Generates the code snippet of the storage buffer node. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The generated code snippet. + */ + generate( builder ) { + + if ( this.structTypeNode !== null ) this.structTypeNode.build( builder ); + + if ( builder.isAvailable( 'storageBuffer' ) || builder.isAvailable( 'indirectStorageBuffer' ) ) { + + return super.generate( builder ); + + } + + const { attribute, varying } = this.getAttributeData(); + + const output = varying.build( builder ); + + builder.registerTransform( output, attribute ); + + return output; + + } + +} + +/** + * TSL function for creating a storage buffer node. + * + * @tsl + * @function + * @param {StorageBufferAttribute|StorageInstancedBufferAttribute|BufferAttribute} value - The buffer data. + * @param {?(string|Struct)} [type=null] - The buffer type (e.g. `'vec3'`). + * @param {number} [count=0] - The buffer count. + * @returns {StorageBufferNode} + */ +const storage = ( value, type = null, count = 0 ) => nodeObject( new StorageBufferNode( value, type, count ) ); + +/** + * @tsl + * @function + * @deprecated since r171. Use `storage().setPBO( true )` instead. + * + * @param {StorageBufferAttribute|StorageInstancedBufferAttribute|BufferAttribute} value - The buffer data. + * @param {?string} type - The buffer type (e.g. `'vec3'`). + * @param {number} count - The buffer count. + * @returns {StorageBufferNode} + */ +const storageObject = ( value, type, count ) => { // @deprecated, r171 + + console.warn( 'THREE.TSL: "storageObject()" is deprecated. Use "storage().setPBO( true )" instead.' ); + + return storage( value, type, count ).setPBO( true ); + +}; + +const _frameId = new WeakMap(); + +/** + * This node implements the vertex transformation shader logic which is required + * for skinning/skeletal animation. + * + * @augments Node + */ +class SkinningNode extends Node { + + static get type() { + + return 'SkinningNode'; + + } + + /** + * Constructs a new skinning node. + * + * @param {SkinnedMesh} skinnedMesh - The skinned mesh. + */ + constructor( skinnedMesh ) { + + super( 'void' ); + + /** + * The skinned mesh. + * + * @type {SkinnedMesh} + */ + this.skinnedMesh = skinnedMesh; + + /** + * The update type overwritten since skinning nodes are updated per object. + * + * @type {string} + */ + this.updateType = NodeUpdateType.OBJECT; + + // + + /** + * The skin index attribute. + * + * @type {AttributeNode} + */ + this.skinIndexNode = attribute( 'skinIndex', 'uvec4' ); + + /** + * The skin weight attribute. + * + * @type {AttributeNode} + */ + this.skinWeightNode = attribute( 'skinWeight', 'vec4' ); + + /** + * The bind matrix node. + * + * @type {Node} + */ + this.bindMatrixNode = reference( 'bindMatrix', 'mat4' ); + + /** + * The bind matrix inverse node. + * + * @type {Node} + */ + this.bindMatrixInverseNode = reference( 'bindMatrixInverse', 'mat4' ); + + /** + * The bind matrices as a uniform buffer node. + * + * @type {Node} + */ + this.boneMatricesNode = referenceBuffer( 'skeleton.boneMatrices', 'mat4', skinnedMesh.skeleton.bones.length ); + + /** + * The current vertex position in local space. + * + * @type {Node} + */ + this.positionNode = positionLocal; + + /** + * The result of vertex position in local space. + * + * @type {Node} + */ + this.toPositionNode = positionLocal; + + /** + * The previous bind matrices as a uniform buffer node. + * Required for computing motion vectors. + * + * @type {?Node} + * @default null + */ + this.previousBoneMatricesNode = null; + + } + + /** + * Transforms the given vertex position via skinning. + * + * @param {Node} [boneMatrices=this.boneMatricesNode] - The bone matrices + * @param {Node} [position=this.positionNode] - The vertex position in local space. + * @return {Node} The transformed vertex position. + */ + getSkinnedPosition( boneMatrices = this.boneMatricesNode, position = this.positionNode ) { + + const { skinIndexNode, skinWeightNode, bindMatrixNode, bindMatrixInverseNode } = this; + + const boneMatX = boneMatrices.element( skinIndexNode.x ); + const boneMatY = boneMatrices.element( skinIndexNode.y ); + const boneMatZ = boneMatrices.element( skinIndexNode.z ); + const boneMatW = boneMatrices.element( skinIndexNode.w ); + + // POSITION + + const skinVertex = bindMatrixNode.mul( position ); + + const skinned = add( + boneMatX.mul( skinWeightNode.x ).mul( skinVertex ), + boneMatY.mul( skinWeightNode.y ).mul( skinVertex ), + boneMatZ.mul( skinWeightNode.z ).mul( skinVertex ), + boneMatW.mul( skinWeightNode.w ).mul( skinVertex ) + ); + + return bindMatrixInverseNode.mul( skinned ).xyz; + + } + + /** + * Transforms the given vertex normal via skinning. + * + * @param {Node} [boneMatrices=this.boneMatricesNode] - The bone matrices + * @param {Node} [normal=normalLocal] - The vertex normal in local space. + * @return {Node} The transformed vertex normal. + */ + getSkinnedNormal( boneMatrices = this.boneMatricesNode, normal = normalLocal ) { + + const { skinIndexNode, skinWeightNode, bindMatrixNode, bindMatrixInverseNode } = this; + + const boneMatX = boneMatrices.element( skinIndexNode.x ); + const boneMatY = boneMatrices.element( skinIndexNode.y ); + const boneMatZ = boneMatrices.element( skinIndexNode.z ); + const boneMatW = boneMatrices.element( skinIndexNode.w ); + + // NORMAL + + let skinMatrix = add( + skinWeightNode.x.mul( boneMatX ), + skinWeightNode.y.mul( boneMatY ), + skinWeightNode.z.mul( boneMatZ ), + skinWeightNode.w.mul( boneMatW ) + ); + + skinMatrix = bindMatrixInverseNode.mul( skinMatrix ).mul( bindMatrixNode ); + + return skinMatrix.transformDirection( normal ).xyz; + + } + + /** + * Computes the transformed/skinned vertex position of the previous frame. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {Node} The skinned position from the previous frame. + */ + getPreviousSkinnedPosition( builder ) { + + const skinnedMesh = builder.object; + + if ( this.previousBoneMatricesNode === null ) { + + skinnedMesh.skeleton.previousBoneMatrices = new Float32Array( skinnedMesh.skeleton.boneMatrices ); + + this.previousBoneMatricesNode = referenceBuffer( 'skeleton.previousBoneMatrices', 'mat4', skinnedMesh.skeleton.bones.length ); + + } + + return this.getSkinnedPosition( this.previousBoneMatricesNode, positionPrevious ); + + } + + /** + * Returns `true` if bone matrices from the previous frame are required. Relevant + * when computing motion vectors with {@link VelocityNode}. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {boolean} Whether bone matrices from the previous frame are required or not. + */ + needsPreviousBoneMatrices( builder ) { + + const mrt = builder.renderer.getMRT(); + + return ( mrt && mrt.has( 'velocity' ) ) || getDataFromObject( builder.object ).useVelocity === true; + + } + + /** + * Setups the skinning node by assigning the transformed vertex data to predefined node variables. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {Node} The transformed vertex position. + */ + setup( builder ) { + + if ( this.needsPreviousBoneMatrices( builder ) ) { + + positionPrevious.assign( this.getPreviousSkinnedPosition( builder ) ); + + } + + const skinPosition = this.getSkinnedPosition(); + + if ( this.toPositionNode ) this.toPositionNode.assign( skinPosition ); + + // + + if ( builder.hasGeometryAttribute( 'normal' ) ) { + + const skinNormal = this.getSkinnedNormal(); + + normalLocal.assign( skinNormal ); + + if ( builder.hasGeometryAttribute( 'tangent' ) ) { + + tangentLocal.assign( skinNormal ); + + } + + } + + return skinPosition; + + } + + /** + * Generates the code snippet of the skinning node. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {string} output - The current output. + * @return {string} The generated code snippet. + */ + generate( builder, output ) { + + if ( output !== 'void' ) { + + return super.generate( builder, output ); + + } + + } + + /** + * Updates the state of the skinned mesh by updating the skeleton once per frame. + * + * @param {NodeFrame} frame - The current node frame. + */ + update( frame ) { + + const skeleton = frame.object && frame.object.skeleton ? frame.object.skeleton : this.skinnedMesh.skeleton; + + if ( _frameId.get( skeleton ) === frame.frameId ) return; + + _frameId.set( skeleton, frame.frameId ); + + if ( this.previousBoneMatricesNode !== null ) skeleton.previousBoneMatrices.set( skeleton.boneMatrices ); + + skeleton.update(); + + } + +} + +/** + * TSL function for creating a skinning node. + * + * @tsl + * @function + * @param {SkinnedMesh} skinnedMesh - The skinned mesh. + * @returns {SkinningNode} + */ +const skinning = ( skinnedMesh ) => nodeObject( new SkinningNode( skinnedMesh ) ); + +/** + * TSL function for computing skinning. + * + * @tsl + * @function + * @param {SkinnedMesh} skinnedMesh - The skinned mesh. + * @param {Node} [toPosition=null] - The target position. + * @returns {SkinningNode} + */ +const computeSkinning = ( skinnedMesh, toPosition = null ) => { + + const node = new SkinningNode( skinnedMesh ); + node.positionNode = storage( new InstancedBufferAttribute( skinnedMesh.geometry.getAttribute( 'position' ).array, 3 ), 'vec3' ).setPBO( true ).toReadOnly().element( instanceIndex ).toVar(); + node.skinIndexNode = storage( new InstancedBufferAttribute( new Uint32Array( skinnedMesh.geometry.getAttribute( 'skinIndex' ).array ), 4 ), 'uvec4' ).setPBO( true ).toReadOnly().element( instanceIndex ).toVar(); + node.skinWeightNode = storage( new InstancedBufferAttribute( skinnedMesh.geometry.getAttribute( 'skinWeight' ).array, 4 ), 'vec4' ).setPBO( true ).toReadOnly().element( instanceIndex ).toVar(); + node.bindMatrixNode = uniform( skinnedMesh.bindMatrix, 'mat4' ); + node.bindMatrixInverseNode = uniform( skinnedMesh.bindMatrixInverse, 'mat4' ); + node.boneMatricesNode = buffer( skinnedMesh.skeleton.boneMatrices, 'mat4', skinnedMesh.skeleton.bones.length ); + node.toPositionNode = toPosition; + + return nodeObject( node ); + +}; + +/** + * This module offers a variety of ways to implement loops in TSL. In it's basic form it's: + * ```js + * Loop( count, ( { i } ) => { + * + * } ); + * ``` + * However, it is also possible to define a start and end ranges, data types and loop conditions: + * ```js + * Loop( { start: int( 0 ), end: int( 10 ), type: 'int', condition: '<' }, ( { i } ) => { + * + * } ); + *``` + * Nested loops can be defined in a compacted form: + * ```js + * Loop( 10, 5, ( { i, j } ) => { + * + * } ); + * ``` + * Loops that should run backwards can be defined like so: + * ```js + * Loop( { start: 10 }, () => {} ); + * ``` + * It is possible to execute with boolean values, similar to the `while` syntax. + * ```js + * const value = float( 0 ).toVar(); + * + * Loop( value.lessThan( 10 ), () => { + * + * value.addAssign( 1 ); + * + * } ); + * ``` + * The module also provides `Break()` and `Continue()` TSL expression for loop control. + * @augments Node + */ +class LoopNode extends Node { + + static get type() { + + return 'LoopNode'; + + } + + /** + * Constructs a new loop node. + * + * @param {Array} params - Depending on the loop type, array holds different parameterization values for the loop. + */ + constructor( params = [] ) { + + super(); + + this.params = params; + + } + + /** + * Returns a loop variable name based on an index. The pattern is + * `0` = `i`, `1`= `j`, `2`= `k` and so on. + * + * @param {number} index - The index. + * @return {string} The loop variable name. + */ + getVarName( index ) { + + return String.fromCharCode( 'i'.charCodeAt( 0 ) + index ); + + } + + /** + * Returns properties about this node. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {Object} The node properties. + */ + getProperties( builder ) { + + const properties = builder.getNodeProperties( this ); + + if ( properties.stackNode !== undefined ) return properties; + + // + + const inputs = {}; + + for ( let i = 0, l = this.params.length - 1; i < l; i ++ ) { + + const param = this.params[ i ]; + + const name = ( param.isNode !== true && param.name ) || this.getVarName( i ); + const type = ( param.isNode !== true && param.type ) || 'int'; + + inputs[ name ] = expression( name, type ); + + } + + const stack = builder.addStack(); // TODO: cache() it + + properties.returnsNode = this.params[ this.params.length - 1 ]( inputs, builder ); + properties.stackNode = stack; + + const baseParam = this.params[ 0 ]; + + if ( baseParam.isNode !== true && typeof baseParam.update === 'function' ) { + + properties.updateNode = Fn( this.params[ 0 ].update )( inputs ); + + } + + builder.removeStack(); + + return properties; + + } + + /** + * This method is overwritten since the node type is inferred based on the loop configuration. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType( builder ) { + + const { returnsNode } = this.getProperties( builder ); + + return returnsNode ? returnsNode.getNodeType( builder ) : 'void'; + + } + + setup( builder ) { + + // setup properties + + this.getProperties( builder ); + + } + + generate( builder ) { + + const properties = this.getProperties( builder ); + + const params = this.params; + const stackNode = properties.stackNode; + + for ( let i = 0, l = params.length - 1; i < l; i ++ ) { + + const param = params[ i ]; + + let isWhile = false, start = null, end = null, name = null, type = null, condition = null, update = null; + + if ( param.isNode ) { + + if ( param.getNodeType( builder ) === 'bool' ) { + + isWhile = true; + type = 'bool'; + end = param.build( builder, type ); + + } else { + + type = 'int'; + name = this.getVarName( i ); + start = '0'; + end = param.build( builder, type ); + condition = '<'; + + } + + } else { + + type = param.type || 'int'; + name = param.name || this.getVarName( i ); + start = param.start; + end = param.end; + condition = param.condition; + update = param.update; + + if ( typeof start === 'number' ) start = builder.generateConst( type, start ); + else if ( start && start.isNode ) start = start.build( builder, type ); + + if ( typeof end === 'number' ) end = builder.generateConst( type, end ); + else if ( end && end.isNode ) end = end.build( builder, type ); + + if ( start !== undefined && end === undefined ) { + + start = start + ' - 1'; + end = '0'; + condition = '>='; + + } else if ( end !== undefined && start === undefined ) { + + start = '0'; + condition = '<'; + + } + + if ( condition === undefined ) { + + if ( Number( start ) > Number( end ) ) { + + condition = '>='; + + } else { + + condition = '<'; + + } + + } + + } + + let loopSnippet; + + if ( isWhile ) { + + loopSnippet = `while ( ${ end } )`; + + } else { + + const internalParam = { start, end}; + + // + + const startSnippet = internalParam.start; + const endSnippet = internalParam.end; + + let updateSnippet; + + const deltaOperator = () => condition.includes( '<' ) ? '+=' : '-='; + + if ( update !== undefined && update !== null ) { + + switch ( typeof update ) { + + case 'function': + + const flow = builder.flowStagesNode( properties.updateNode, 'void' ); + const snippet = flow.code.replace( /\t|;/g, '' ); + + updateSnippet = snippet; + + break; + + case 'number': + + updateSnippet = name + ' ' + deltaOperator() + ' ' + builder.generateConst( type, update ); + + break; + + case 'string': + + updateSnippet = name + ' ' + update; + + break; + + default: + + if ( update.isNode ) { + + updateSnippet = name + ' ' + deltaOperator() + ' ' + update.build( builder ); + + } else { + + console.error( 'THREE.TSL: \'Loop( { update: ... } )\' is not a function, string or number.' ); + + updateSnippet = 'break /* invalid update */'; + + } + + } + + } else { + + if ( type === 'int' || type === 'uint' ) { + + update = condition.includes( '<' ) ? '++' : '--'; + + } else { + + update = deltaOperator() + ' 1.'; + + } + + updateSnippet = name + ' ' + update; + + } + + const declarationSnippet = builder.getVar( type, name ) + ' = ' + startSnippet; + const conditionalSnippet = name + ' ' + condition + ' ' + endSnippet; + + loopSnippet = `for ( ${ declarationSnippet }; ${ conditionalSnippet }; ${ updateSnippet } )`; + + } + + builder.addFlowCode( ( i === 0 ? '\n' : '' ) + builder.tab + loopSnippet + ' {\n\n' ).addFlowTab(); + + } + + const stackSnippet = stackNode.build( builder, 'void' ); + + const returnsSnippet = properties.returnsNode ? properties.returnsNode.build( builder ) : ''; + + builder.removeFlowTab().addFlowCode( '\n' + builder.tab + stackSnippet ); + + for ( let i = 0, l = this.params.length - 1; i < l; i ++ ) { + + builder.addFlowCode( ( i === 0 ? '' : builder.tab ) + '}\n\n' ).removeFlowTab(); + + } + + builder.addFlowTab(); + + return returnsSnippet; + + } + +} + +/** + * TSL function for creating a loop node. + * + * @tsl + * @function + * @param {...any} params - A list of parameters. + * @returns {LoopNode} + */ +const Loop = ( ...params ) => nodeObject( new LoopNode( nodeArray( params, 'int' ) ) ).toStack(); + +/** + * TSL function for creating a `Continue()` expression. + * + * @tsl + * @function + * @returns {ExpressionNode} + */ +const Continue = () => expression( 'continue' ).toStack(); + +/** + * TSL function for creating a `Break()` expression. + * + * @tsl + * @function + * @returns {ExpressionNode} + */ +const Break = () => expression( 'break' ).toStack(); + +const _morphTextures = /*@__PURE__*/ new WeakMap(); +const _morphVec4 = /*@__PURE__*/ new Vector4(); + +const getMorph = /*@__PURE__*/ Fn( ( { bufferMap, influence, stride, width, depth, offset } ) => { + + const texelIndex = int( vertexIndex ).mul( stride ).add( offset ); + + const y = texelIndex.div( width ); + const x = texelIndex.sub( y.mul( width ) ); + + const bufferAttrib = textureLoad( bufferMap, ivec2( x, y ) ).depth( depth ).xyz; + + return bufferAttrib.mul( influence ); + +} ); + +function getEntry( geometry ) { + + const hasMorphPosition = geometry.morphAttributes.position !== undefined; + const hasMorphNormals = geometry.morphAttributes.normal !== undefined; + const hasMorphColors = geometry.morphAttributes.color !== undefined; + + // instead of using attributes, the WebGL 2 code path encodes morph targets + // into an array of data textures. Each layer represents a single morph target. + + const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color; + const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0; + + let entry = _morphTextures.get( geometry ); + + if ( entry === undefined || entry.count !== morphTargetsCount ) { + + if ( entry !== undefined ) entry.texture.dispose(); + + const morphTargets = geometry.morphAttributes.position || []; + const morphNormals = geometry.morphAttributes.normal || []; + const morphColors = geometry.morphAttributes.color || []; + + let vertexDataCount = 0; + + if ( hasMorphPosition === true ) vertexDataCount = 1; + if ( hasMorphNormals === true ) vertexDataCount = 2; + if ( hasMorphColors === true ) vertexDataCount = 3; + + let width = geometry.attributes.position.count * vertexDataCount; + let height = 1; + + const maxTextureSize = 4096; // @TODO: Use 'capabilities.maxTextureSize' + + if ( width > maxTextureSize ) { + + height = Math.ceil( width / maxTextureSize ); + width = maxTextureSize; + + } + + const buffer = new Float32Array( width * height * 4 * morphTargetsCount ); + + const bufferTexture = new DataArrayTexture( buffer, width, height, morphTargetsCount ); + bufferTexture.type = FloatType; + bufferTexture.needsUpdate = true; + + // fill buffer + + const vertexDataStride = vertexDataCount * 4; + + for ( let i = 0; i < morphTargetsCount; i ++ ) { + + const morphTarget = morphTargets[ i ]; + const morphNormal = morphNormals[ i ]; + const morphColor = morphColors[ i ]; + + const offset = width * height * 4 * i; + + for ( let j = 0; j < morphTarget.count; j ++ ) { + + const stride = j * vertexDataStride; + + if ( hasMorphPosition === true ) { + + _morphVec4.fromBufferAttribute( morphTarget, j ); + + buffer[ offset + stride + 0 ] = _morphVec4.x; + buffer[ offset + stride + 1 ] = _morphVec4.y; + buffer[ offset + stride + 2 ] = _morphVec4.z; + buffer[ offset + stride + 3 ] = 0; + + } + + if ( hasMorphNormals === true ) { + + _morphVec4.fromBufferAttribute( morphNormal, j ); + + buffer[ offset + stride + 4 ] = _morphVec4.x; + buffer[ offset + stride + 5 ] = _morphVec4.y; + buffer[ offset + stride + 6 ] = _morphVec4.z; + buffer[ offset + stride + 7 ] = 0; + + } + + if ( hasMorphColors === true ) { + + _morphVec4.fromBufferAttribute( morphColor, j ); + + buffer[ offset + stride + 8 ] = _morphVec4.x; + buffer[ offset + stride + 9 ] = _morphVec4.y; + buffer[ offset + stride + 10 ] = _morphVec4.z; + buffer[ offset + stride + 11 ] = ( morphColor.itemSize === 4 ) ? _morphVec4.w : 1; + + } + + } + + } + + entry = { + count: morphTargetsCount, + texture: bufferTexture, + stride: vertexDataCount, + size: new Vector2( width, height ) + }; + + _morphTextures.set( geometry, entry ); + + function disposeTexture() { + + bufferTexture.dispose(); + + _morphTextures.delete( geometry ); + + geometry.removeEventListener( 'dispose', disposeTexture ); + + } + + geometry.addEventListener( 'dispose', disposeTexture ); + + } + + return entry; + +} + +/** + * This node implements the vertex transformation shader logic which is required + * for morph target animation. + * + * @augments Node + */ +class MorphNode extends Node { + + static get type() { + + return 'MorphNode'; + + } + + /** + * Constructs a new morph node. + * + * @param {Mesh} mesh - The mesh holding the morph targets. + */ + constructor( mesh ) { + + super( 'void' ); + + /** + * The mesh holding the morph targets. + * + * @type {Mesh} + */ + this.mesh = mesh; + + /** + * A uniform node which represents the morph base influence value. + * + * @type {UniformNode} + */ + this.morphBaseInfluence = uniform( 1 ); + + /** + * The update type overwritten since morph nodes are updated per object. + * + * @type {string} + */ + this.updateType = NodeUpdateType.OBJECT; + + } + + /** + * Setups the morph node by assigning the transformed vertex data to predefined node variables. + * + * @param {NodeBuilder} builder - The current node builder. + */ + setup( builder ) { + + const { geometry } = builder; + + const hasMorphPosition = geometry.morphAttributes.position !== undefined; + const hasMorphNormals = geometry.hasAttribute( 'normal' ) && geometry.morphAttributes.normal !== undefined; + + const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color; + const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0; + + // nodes + + const { texture: bufferMap, stride, size } = getEntry( geometry ); + + if ( hasMorphPosition === true ) positionLocal.mulAssign( this.morphBaseInfluence ); + if ( hasMorphNormals === true ) normalLocal.mulAssign( this.morphBaseInfluence ); + + const width = int( size.width ); + + Loop( morphTargetsCount, ( { i } ) => { + + const influence = float( 0 ).toVar(); + + if ( this.mesh.count > 1 && ( this.mesh.morphTexture !== null && this.mesh.morphTexture !== undefined ) ) { + + influence.assign( textureLoad( this.mesh.morphTexture, ivec2( int( i ).add( 1 ), int( instanceIndex ) ) ).r ); + + } else { + + influence.assign( reference( 'morphTargetInfluences', 'float' ).element( i ).toVar() ); + + } + + If( influence.notEqual( 0 ), () => { + + if ( hasMorphPosition === true ) { + + positionLocal.addAssign( getMorph( { + bufferMap, + influence, + stride, + width, + depth: i, + offset: int( 0 ) + } ) ); + + } + + if ( hasMorphNormals === true ) { + + normalLocal.addAssign( getMorph( { + bufferMap, + influence, + stride, + width, + depth: i, + offset: int( 1 ) + } ) ); + + } + + } ); + + } ); + + } + + /** + * Updates the state of the morphed mesh by updating the base influence. + * + * @param {NodeFrame} frame - The current node frame. + */ + update( /*frame*/ ) { + + const morphBaseInfluence = this.morphBaseInfluence; + + if ( this.mesh.geometry.morphTargetsRelative ) { + + morphBaseInfluence.value = 1; + + } else { + + morphBaseInfluence.value = 1 - this.mesh.morphTargetInfluences.reduce( ( a, b ) => a + b, 0 ); + + } + + } + +} + +/** + * TSL function for creating a morph node. + * + * @tsl + * @function + * @param {Mesh} mesh - The mesh holding the morph targets. + * @returns {MorphNode} + */ +const morphReference = /*@__PURE__*/ nodeProxy( MorphNode ).setParameterLength( 1 ); + +/** + * Base class for lighting nodes. + * + * @augments Node + */ +class LightingNode extends Node { + + static get type() { + + return 'LightingNode'; + + } + + /** + * Constructs a new lighting node. + */ + constructor() { + + super( 'vec3' ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLightingNode = true; + + } + +} + +/** + * A generic class that can be used by nodes which contribute + * ambient occlusion to the scene. E.g. an ambient occlusion map + * node can be used as input for this module. Used in {@link NodeMaterial}. + * + * @augments LightingNode + */ +class AONode extends LightingNode { + + static get type() { + + return 'AONode'; + + } + + /** + * Constructs a new AO node. + * + * @param {?Node} [aoNode=null] - The ambient occlusion node. + */ + constructor( aoNode = null ) { + + super(); + + /** + * The ambient occlusion node. + * + * @type {?Node} + * @default null + */ + this.aoNode = aoNode; + + } + + setup( builder ) { + + builder.context.ambientOcclusion.mulAssign( this.aoNode ); + + } + +} + +/** + * `LightingContextNode` represents an extension of the {@link ContextNode} module + * by adding lighting specific context data. It represents the runtime context of + * {@link LightsNode}. + * + * @augments ContextNode + */ +class LightingContextNode extends ContextNode { + + static get type() { + + return 'LightingContextNode'; + + } + + /** + * Constructs a new lighting context node. + * + * @param {LightsNode} lightsNode - The lights node. + * @param {?LightingModel} [lightingModel=null] - The current lighting model. + * @param {?Node} [backdropNode=null] - A backdrop node. + * @param {?Node} [backdropAlphaNode=null] - A backdrop alpha node. + */ + constructor( lightsNode, lightingModel = null, backdropNode = null, backdropAlphaNode = null ) { + + super( lightsNode ); + + /** + * The current lighting model. + * + * @type {?LightingModel} + * @default null + */ + this.lightingModel = lightingModel; + + /** + * A backdrop node. + * + * @type {?Node} + * @default null + */ + this.backdropNode = backdropNode; + + /** + * A backdrop alpha node. + * + * @type {?Node} + * @default null + */ + this.backdropAlphaNode = backdropAlphaNode; + + this._value = null; + + } + + /** + * Returns a lighting context object. + * + * @return {{ + * radiance: Node, + * irradiance: Node, + * iblIrradiance: Node, + * ambientOcclusion: Node, + * reflectedLight: {directDiffuse: Node, directSpecular: Node, indirectDiffuse: Node, indirectSpecular: Node}, + * backdrop: Node, + * backdropAlpha: Node + * }} The lighting context object. + */ + getContext() { + + const { backdropNode, backdropAlphaNode } = this; + + const directDiffuse = vec3().toVar( 'directDiffuse' ), + directSpecular = vec3().toVar( 'directSpecular' ), + indirectDiffuse = vec3().toVar( 'indirectDiffuse' ), + indirectSpecular = vec3().toVar( 'indirectSpecular' ); + + const reflectedLight = { + directDiffuse, + directSpecular, + indirectDiffuse, + indirectSpecular + }; + + const context = { + radiance: vec3().toVar( 'radiance' ), + irradiance: vec3().toVar( 'irradiance' ), + iblIrradiance: vec3().toVar( 'iblIrradiance' ), + ambientOcclusion: float( 1 ).toVar( 'ambientOcclusion' ), + reflectedLight, + backdrop: backdropNode, + backdropAlpha: backdropAlphaNode + }; + + return context; + + } + + setup( builder ) { + + this.value = this._value || ( this._value = this.getContext() ); + this.value.lightingModel = this.lightingModel || builder.context.lightingModel; + + return super.setup( builder ); + + } + +} + +const lightingContext = /*@__PURE__*/ nodeProxy( LightingContextNode ); + +/** + * A generic class that can be used by nodes which contribute + * irradiance to the scene. E.g. a light map node can be used + * as input for this module. Used in {@link NodeMaterial}. + * + * @augments LightingNode + */ +class IrradianceNode extends LightingNode { + + static get type() { + + return 'IrradianceNode'; + + } + + /** + * Constructs a new irradiance node. + * + * @param {Node} node - A node contributing irradiance. + */ + constructor( node ) { + + super(); + + /** + * A node contributing irradiance. + * + * @type {Node} + */ + this.node = node; + + } + + setup( builder ) { + + builder.context.irradiance.addAssign( this.node ); + + } + +} + +let screenSizeVec, viewportVec; + +/** + * This node provides a collection of screen related metrics. + * Depending on {@link ScreenNode#scope}, the nodes can represent + * resolution or viewport data as well as fragment or uv coordinates. + * + * @augments Node + */ +class ScreenNode extends Node { + + static get type() { + + return 'ScreenNode'; + + } + + /** + * Constructs a new screen node. + * + * @param {('coordinate'|'viewport'|'size'|'uv')} scope - The node's scope. + */ + constructor( scope ) { + + super(); + + /** + * The node represents different metric depending on which scope is selected. + * + * - `ScreenNode.COORDINATE`: Window-relative coordinates of the current fragment according to WebGPU standards. + * - `ScreenNode.VIEWPORT`: The current viewport defined as a four-dimensional vector. + * - `ScreenNode.SIZE`: The dimensions of the current bound framebuffer. + * - `ScreenNode.UV`: Normalized coordinates. + * + * @type {('coordinate'|'viewport'|'size'|'uv')} + */ + this.scope = scope; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isViewportNode = true; + + } + + /** + * This method is overwritten since the node type depends on the selected scope. + * + * @return {('vec2'|'vec4')} The node type. + */ + getNodeType() { + + if ( this.scope === ScreenNode.VIEWPORT ) return 'vec4'; + else return 'vec2'; + + } + + /** + * This method is overwritten since the node's update type depends on the selected scope. + * + * @return {NodeUpdateType} The update type. + */ + getUpdateType() { + + let updateType = NodeUpdateType.NONE; + + if ( this.scope === ScreenNode.SIZE || this.scope === ScreenNode.VIEWPORT ) { + + updateType = NodeUpdateType.RENDER; + + } + + this.updateType = updateType; + + return updateType; + + } + + /** + * `ScreenNode` implements {@link Node#update} to retrieve viewport and size information + * from the current renderer. + * + * @param {NodeFrame} frame - A reference to the current node frame. + */ + update( { renderer } ) { + + const renderTarget = renderer.getRenderTarget(); + + if ( this.scope === ScreenNode.VIEWPORT ) { + + if ( renderTarget !== null ) { + + viewportVec.copy( renderTarget.viewport ); + + } else { + + renderer.getViewport( viewportVec ); + + viewportVec.multiplyScalar( renderer.getPixelRatio() ); + + } + + } else { + + if ( renderTarget !== null ) { + + screenSizeVec.width = renderTarget.width; + screenSizeVec.height = renderTarget.height; + + } else { + + renderer.getDrawingBufferSize( screenSizeVec ); + + } + + } + + } + + setup( /*builder*/ ) { + + const scope = this.scope; + + let output = null; + + if ( scope === ScreenNode.SIZE ) { + + output = uniform( screenSizeVec || ( screenSizeVec = new Vector2() ) ); + + } else if ( scope === ScreenNode.VIEWPORT ) { + + output = uniform( viewportVec || ( viewportVec = new Vector4() ) ); + + } else { + + output = vec2( screenCoordinate.div( screenSize ) ); + + } + + return output; + + } + + generate( builder ) { + + if ( this.scope === ScreenNode.COORDINATE ) { + + let coord = builder.getFragCoord(); + + if ( builder.isFlipY() ) { + + // follow webgpu standards + + const size = builder.getNodeProperties( screenSize ).outputNode.build( builder ); + + coord = `${ builder.getType( 'vec2' ) }( ${ coord }.x, ${ size }.y - ${ coord }.y )`; + + } + + return coord; + + } + + return super.generate( builder ); + + } + +} + +ScreenNode.COORDINATE = 'coordinate'; +ScreenNode.VIEWPORT = 'viewport'; +ScreenNode.SIZE = 'size'; +ScreenNode.UV = 'uv'; + +// Screen + +/** + * TSL object that represents normalized screen coordinates, unitless in `[0, 1]`. + * + * @tsl + * @type {ScreenNode} + */ +const screenUV = /*@__PURE__*/ nodeImmutable( ScreenNode, ScreenNode.UV ); + +/** + * TSL object that represents the screen resolution in physical pixel units. + * + * @tsl + * @type {ScreenNode} + */ +const screenSize = /*@__PURE__*/ nodeImmutable( ScreenNode, ScreenNode.SIZE ); + +/** + * TSL object that represents the current `x`/`y` pixel position on the screen in physical pixel units. + * + * @tsl + * @type {ScreenNode} + */ +const screenCoordinate = /*@__PURE__*/ nodeImmutable( ScreenNode, ScreenNode.COORDINATE ); + +// Viewport + +/** + * TSL object that represents the viewport rectangle as `x`, `y`, `width` and `height` in physical pixel units. + * + * @tsl + * @type {ScreenNode} + */ +const viewport = /*@__PURE__*/ nodeImmutable( ScreenNode, ScreenNode.VIEWPORT ); + +/** + * TSL object that represents the viewport resolution in physical pixel units. + * + * @tsl + * @type {ScreenNode} + */ +const viewportSize = viewport.zw; + +/** + * TSL object that represents the current `x`/`y` pixel position on the viewport in physical pixel units. + * + * @tsl + * @type {ScreenNode} + */ +const viewportCoordinate = /*@__PURE__*/ screenCoordinate.sub( viewport.xy ); + +/** + * TSL object that represents normalized viewport coordinates, unitless in `[0, 1]`. + * + * @tsl + * @type {ScreenNode} + */ +const viewportUV = /*@__PURE__*/ viewportCoordinate.div( viewportSize ); + +// Deprecated + +/** + * @deprecated since r169. Use {@link screenSize} instead. + */ +const viewportResolution = /*@__PURE__*/ ( Fn( () => { // @deprecated, r169 + + console.warn( 'THREE.TSL: "viewportResolution" is deprecated. Use "screenSize" instead.' ); + + return screenSize; + +}, 'vec2' ).once() )(); + +const _size$4 = /*@__PURE__*/ new Vector2(); + +/** + * A special type of texture node which represents the data of the current viewport + * as a texture. The module extracts data from the current bound framebuffer with + * a copy operation so no extra render pass is required to produce the texture data + * (which is good for performance). `ViewportTextureNode` can be used as an input for a + * variety of effects like refractive or transmissive materials. + * + * @augments TextureNode + */ +class ViewportTextureNode extends TextureNode { + + static get type() { + + return 'ViewportTextureNode'; + + } + + /** + * Constructs a new viewport texture node. + * + * @param {Node} [uvNode=screenUV] - The uv node. + * @param {?Node} [levelNode=null] - The level node. + * @param {?Texture} [framebufferTexture=null] - A framebuffer texture holding the viewport data. If not provided, a framebuffer texture is created automatically. + */ + constructor( uvNode = screenUV, levelNode = null, framebufferTexture = null ) { + + if ( framebufferTexture === null ) { + + framebufferTexture = new FramebufferTexture(); + framebufferTexture.minFilter = LinearMipmapLinearFilter; + + } + + super( framebufferTexture, uvNode, levelNode ); + + /** + * Whether to generate mipmaps or not. + * + * @type {boolean} + * @default false + */ + this.generateMipmaps = false; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isOutputTextureNode = true; + + /** + * The `updateBeforeType` is set to `NodeUpdateType.FRAME` since the node renders the + * scene once per frame in its {@link ViewportTextureNode#updateBefore} method. + * + * @type {string} + * @default 'frame' + */ + this.updateBeforeType = NodeUpdateType.FRAME; + + } + + updateBefore( frame ) { + + const renderer = frame.renderer; + renderer.getDrawingBufferSize( _size$4 ); + + // + + const framebufferTexture = this.value; + + if ( framebufferTexture.image.width !== _size$4.width || framebufferTexture.image.height !== _size$4.height ) { + + framebufferTexture.image.width = _size$4.width; + framebufferTexture.image.height = _size$4.height; + framebufferTexture.needsUpdate = true; + + } + + // + + const currentGenerateMipmaps = framebufferTexture.generateMipmaps; + framebufferTexture.generateMipmaps = this.generateMipmaps; + + renderer.copyFramebufferToTexture( framebufferTexture ); + + framebufferTexture.generateMipmaps = currentGenerateMipmaps; + + } + + clone() { + + const viewportTextureNode = new this.constructor( this.uvNode, this.levelNode, this.value ); + viewportTextureNode.generateMipmaps = this.generateMipmaps; + + return viewportTextureNode; + + } + +} + +/** + * TSL function for creating a viewport texture node. + * + * @tsl + * @function + * @param {?Node} [uvNode=screenUV] - The uv node. + * @param {?Node} [levelNode=null] - The level node. + * @param {?Texture} [framebufferTexture=null] - A framebuffer texture holding the viewport data. If not provided, a framebuffer texture is created automatically. + * @returns {ViewportTextureNode} + */ +const viewportTexture = /*@__PURE__*/ nodeProxy( ViewportTextureNode ).setParameterLength( 0, 3 ); + +/** + * TSL function for creating a viewport texture node with enabled mipmap generation. + * + * @tsl + * @function + * @param {?Node} [uvNode=screenUV] - The uv node. + * @param {?Node} [levelNode=null] - The level node. + * @param {?Texture} [framebufferTexture=null] - A framebuffer texture holding the viewport data. If not provided, a framebuffer texture is created automatically. + * @returns {ViewportTextureNode} + */ +const viewportMipTexture = /*@__PURE__*/ nodeProxy( ViewportTextureNode, null, null, { generateMipmaps: true } ).setParameterLength( 0, 3 ); + +let sharedDepthbuffer = null; + +/** + * Represents the depth of the current viewport as a texture. This module + * can be used in combination with viewport texture to achieve effects + * that require depth evaluation. + * + * @augments ViewportTextureNode + */ +class ViewportDepthTextureNode extends ViewportTextureNode { + + static get type() { + + return 'ViewportDepthTextureNode'; + + } + + /** + * Constructs a new viewport depth texture node. + * + * @param {Node} [uvNode=screenUV] - The uv node. + * @param {?Node} [levelNode=null] - The level node. + */ + constructor( uvNode = screenUV, levelNode = null ) { + + if ( sharedDepthbuffer === null ) { + + sharedDepthbuffer = new DepthTexture(); + + } + + super( uvNode, levelNode, sharedDepthbuffer ); + + } + +} + +/** + * TSL function for a viewport depth texture node. + * + * @tsl + * @function + * @param {?Node} [uvNode=screenUV] - The uv node. + * @param {?Node} [levelNode=null] - The level node. + * @returns {ViewportDepthTextureNode} + */ +const viewportDepthTexture = /*@__PURE__*/ nodeProxy( ViewportDepthTextureNode ).setParameterLength( 0, 2 ); + +/** + * This node offers a collection of features in context of the depth logic in the fragment shader. + * Depending on {@link ViewportDepthNode#scope}, it can be used to define a depth value for the current + * fragment or for depth evaluation purposes. + * + * @augments Node + */ +class ViewportDepthNode extends Node { + + static get type() { + + return 'ViewportDepthNode'; + + } + + /** + * Constructs a new viewport depth node. + * + * @param {('depth'|'depthBase'|'linearDepth')} scope - The node's scope. + * @param {?Node} [valueNode=null] - The value node. + */ + constructor( scope, valueNode = null ) { + + super( 'float' ); + + /** + * The node behaves differently depending on which scope is selected. + * + * - `ViewportDepthNode.DEPTH_BASE`: Allows to define a value for the current fragment's depth. + * - `ViewportDepthNode.DEPTH`: Represents the depth value for the current fragment (`valueNode` is ignored). + * - `ViewportDepthNode.LINEAR_DEPTH`: Represents the linear (orthographic) depth value of the current fragment. + * If a `valueNode` is set, the scope can be used to convert perspective depth data to linear data. + * + * @type {('depth'|'depthBase'|'linearDepth')} + */ + this.scope = scope; + + /** + * Can be used to define a custom depth value. + * The property is ignored in the `ViewportDepthNode.DEPTH` scope. + * + * @type {?Node} + * @default null + */ + this.valueNode = valueNode; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isViewportDepthNode = true; + + } + + generate( builder ) { + + const { scope } = this; + + if ( scope === ViewportDepthNode.DEPTH_BASE ) { + + return builder.getFragDepth(); + + } + + return super.generate( builder ); + + } + + setup( { camera } ) { + + const { scope } = this; + const value = this.valueNode; + + let node = null; + + if ( scope === ViewportDepthNode.DEPTH_BASE ) { + + if ( value !== null ) { + + node = depthBase().assign( value ); + + } + + } else if ( scope === ViewportDepthNode.DEPTH ) { + + if ( camera.isPerspectiveCamera ) { + + node = viewZToPerspectiveDepth( positionView.z, cameraNear, cameraFar ); + + } else { + + node = viewZToOrthographicDepth( positionView.z, cameraNear, cameraFar ); + + } + + } else if ( scope === ViewportDepthNode.LINEAR_DEPTH ) { + + if ( value !== null ) { + + if ( camera.isPerspectiveCamera ) { + + const viewZ = perspectiveDepthToViewZ( value, cameraNear, cameraFar ); + + node = viewZToOrthographicDepth( viewZ, cameraNear, cameraFar ); + + } else { + + node = value; + + } + + } else { + + node = viewZToOrthographicDepth( positionView.z, cameraNear, cameraFar ); + + } + + } + + return node; + + } + +} + +ViewportDepthNode.DEPTH_BASE = 'depthBase'; +ViewportDepthNode.DEPTH = 'depth'; +ViewportDepthNode.LINEAR_DEPTH = 'linearDepth'; + +// NOTE: viewZ, the z-coordinate in camera space, is negative for points in front of the camera + +/** + * TSL function for converting a viewZ value to an orthographic depth value. + * + * @tsl + * @function + * @param {Node} viewZ - The viewZ node. + * @param {Node} near - The camera's near value. + * @param {Node} far - The camera's far value. + * @returns {Node} + */ +const viewZToOrthographicDepth = ( viewZ, near, far ) => viewZ.add( near ).div( near.sub( far ) ); + +/** + * TSL function for converting an orthographic depth value to a viewZ value. + * + * @tsl + * @function + * @param {Node} depth - The orthographic depth. + * @param {Node} near - The camera's near value. + * @param {Node} far - The camera's far value. + * @returns {Node} + */ +const orthographicDepthToViewZ = ( depth, near, far ) => near.sub( far ).mul( depth ).sub( near ); + +/** + * TSL function for converting a viewZ value to a perspective depth value. + * + * Note: {link https://twitter.com/gonnavis/status/1377183786949959682}. + * + * @tsl + * @function + * @param {Node} viewZ - The viewZ node. + * @param {Node} near - The camera's near value. + * @param {Node} far - The camera's far value. + * @returns {Node} + */ +const viewZToPerspectiveDepth = ( viewZ, near, far ) => near.add( viewZ ).mul( far ).div( far.sub( near ).mul( viewZ ) ); + +/** + * TSL function for converting a perspective depth value to a viewZ value. + * + * @tsl + * @function + * @param {Node} depth - The perspective depth. + * @param {Node} near - The camera's near value. + * @param {Node} far - The camera's far value. + * @returns {Node} + */ +const perspectiveDepthToViewZ = ( depth, near, far ) => near.mul( far ).div( far.sub( near ).mul( depth ).sub( far ) ); + +/** + * TSL function for converting a viewZ value to a logarithmic depth value. + * + * @tsl + * @function + * @param {Node} viewZ - The viewZ node. + * @param {Node} near - The camera's near value. + * @param {Node} far - The camera's far value. + * @returns {Node} + */ +const viewZToLogarithmicDepth = ( viewZ, near, far ) => { + + // NOTE: viewZ must be negative--see explanation at the end of this comment block. + // The final logarithmic depth formula used here is adapted from one described in an + // article by Thatcher Ulrich (see http://tulrich.com/geekstuff/log_depth_buffer.txt), + // which was an improvement upon an earlier formula one described in an + // Outerra article (https://outerra.blogspot.com/2009/08/logarithmic-z-buffer.html). + // Ulrich's formula is the following: + // z = K * log( w / cameraNear ) / log( cameraFar / cameraNear ) + // where K = 2^k - 1, and k is the number of bits in the depth buffer. + // The Outerra variant ignored the camera near plane (it assumed it was 0) and instead + // opted for a "C-constant" for resolution adjustment of objects near the camera. + // Outerra states: "Notice that the 'C' variant doesn’t use a near plane distance, it has it + // set at 0" (quote from https://outerra.blogspot.com/2012/11/maximizing-depth-buffer-range-and.html). + // Ulrich's variant has the benefit of constant relative precision over the whole near-far range. + // It was debated here whether Outerra's "C-constant" or Ulrich's "near plane" variant should + // be used, and ultimately Ulrich's "near plane" version was chosen. + // Outerra eventually made another improvement to their original "C-constant" variant, + // but it still does not incorporate the camera near plane (for this version, + // see https://outerra.blogspot.com/2013/07/logarithmic-depth-buffer-optimizations.html). + // Here we make 4 changes to Ulrich's formula: + // 1. Clamp the camera near plane so we don't divide by 0. + // 2. Use log2 instead of log to avoid an extra multiply (shaders implement log using log2). + // 3. Assume K is 1 (K = maximum value in depth buffer; see Ulrich's formula above). + // 4. To maintain consistency with the functions "viewZToOrthographicDepth" and "viewZToPerspectiveDepth", + // we modify the formula here to use 'viewZ' instead of 'w'. The other functions expect a negative viewZ, + // so we do the same here, hence the 'viewZ.negate()' call. + // For visual representation of this depth curve, see https://www.desmos.com/calculator/uyqk0vex1u + near = near.max( 1e-6 ).toVar(); + const numerator = log2( viewZ.negate().div( near ) ); + const denominator = log2( far.div( near ) ); + return numerator.div( denominator ); + +}; + +/** + * TSL function for converting a logarithmic depth value to a viewZ value. + * + * @tsl + * @function + * @param {Node} depth - The logarithmic depth. + * @param {Node} near - The camera's near value. + * @param {Node} far - The camera's far value. + * @returns {Node} + */ +const logarithmicDepthToViewZ = ( depth, near, far ) => { + + // NOTE: we add a 'negate()' call to the return value here to maintain consistency with + // the functions "orthographicDepthToViewZ" and "perspectiveDepthToViewZ" (they return + // a negative viewZ). + const exponent = depth.mul( log( far.div( near ) ) ); + return float( Math.E ).pow( exponent ).mul( near ).negate(); + +}; + +/** + * TSL function for defining a value for the current fragment's depth. + * + * @tsl + * @function + * @param {Node} value - The depth value to set. + * @returns {ViewportDepthNode} + */ +const depthBase = /*@__PURE__*/ nodeProxy( ViewportDepthNode, ViewportDepthNode.DEPTH_BASE ); + +/** + * TSL object that represents the depth value for the current fragment. + * + * @tsl + * @type {ViewportDepthNode} + */ +const depth = /*@__PURE__*/ nodeImmutable( ViewportDepthNode, ViewportDepthNode.DEPTH ); + +/** + * TSL function for converting a perspective depth value to linear depth. + * + * @tsl + * @function + * @param {?Node} [value=null] - The perspective depth. If `null` is provided, the current fragment's depth is used. + * @returns {ViewportDepthNode} + */ +const linearDepth = /*@__PURE__*/ nodeProxy( ViewportDepthNode, ViewportDepthNode.LINEAR_DEPTH ).setParameterLength( 0, 1 ); + +/** + * TSL object that represents the linear (orthographic) depth value of the current fragment + * + * @tsl + * @type {ViewportDepthNode} + */ +const viewportLinearDepth = /*@__PURE__*/ linearDepth( viewportDepthTexture() ); + +depth.assign = ( value ) => depthBase( value ); + +/** + * This node is used in {@link NodeMaterial} to setup the clipping + * which can happen hardware-accelerated (if supported) and optionally + * use alpha-to-coverage for anti-aliasing clipped edges. + * + * @augments Node + */ +class ClippingNode extends Node { + + static get type() { + + return 'ClippingNode'; + + } + + /** + * Constructs a new clipping node. + * + * @param {('default'|'hardware'|'alphaToCoverage')} [scope='default'] - The node's scope. Similar to other nodes, + * the selected scope influences the behavior of the node and what type of code is generated. + */ + constructor( scope = ClippingNode.DEFAULT ) { + + super(); + + /** + * The node's scope. Similar to other nodes, the selected scope influences + * the behavior of the node and what type of code is generated. + * + * @type {('default'|'hardware'|'alphaToCoverage')} + */ + this.scope = scope; + + } + + /** + * Setups the node depending on the selected scope. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {Node} The result node. + */ + setup( builder ) { + + super.setup( builder ); + + const clippingContext = builder.clippingContext; + const { intersectionPlanes, unionPlanes } = clippingContext; + + this.hardwareClipping = builder.material.hardwareClipping; + + if ( this.scope === ClippingNode.ALPHA_TO_COVERAGE ) { + + return this.setupAlphaToCoverage( intersectionPlanes, unionPlanes ); + + } else if ( this.scope === ClippingNode.HARDWARE ) { + + return this.setupHardwareClipping( unionPlanes, builder ); + + } else { + + return this.setupDefault( intersectionPlanes, unionPlanes ); + + } + + } + + /** + * Setups alpha to coverage. + * + * @param {Array} intersectionPlanes - The intersection planes. + * @param {Array} unionPlanes - The union planes. + * @return {Node} The result node. + */ + setupAlphaToCoverage( intersectionPlanes, unionPlanes ) { + + return Fn( () => { + + const distanceToPlane = float().toVar( 'distanceToPlane' ); + const distanceGradient = float().toVar( 'distanceToGradient' ); + + const clipOpacity = float( 1 ).toVar( 'clipOpacity' ); + + const numUnionPlanes = unionPlanes.length; + + if ( this.hardwareClipping === false && numUnionPlanes > 0 ) { + + const clippingPlanes = uniformArray( unionPlanes ); + + Loop( numUnionPlanes, ( { i } ) => { + + const plane = clippingPlanes.element( i ); + + distanceToPlane.assign( positionView.dot( plane.xyz ).negate().add( plane.w ) ); + distanceGradient.assign( distanceToPlane.fwidth().div( 2.0 ) ); + + clipOpacity.mulAssign( smoothstep( distanceGradient.negate(), distanceGradient, distanceToPlane ) ); + + } ); + + } + + const numIntersectionPlanes = intersectionPlanes.length; + + if ( numIntersectionPlanes > 0 ) { + + const clippingPlanes = uniformArray( intersectionPlanes ); + const intersectionClipOpacity = float( 1 ).toVar( 'intersectionClipOpacity' ); + + Loop( numIntersectionPlanes, ( { i } ) => { + + const plane = clippingPlanes.element( i ); + + distanceToPlane.assign( positionView.dot( plane.xyz ).negate().add( plane.w ) ); + distanceGradient.assign( distanceToPlane.fwidth().div( 2.0 ) ); + + intersectionClipOpacity.mulAssign( smoothstep( distanceGradient.negate(), distanceGradient, distanceToPlane ).oneMinus() ); + + } ); + + clipOpacity.mulAssign( intersectionClipOpacity.oneMinus() ); + + } + + diffuseColor.a.mulAssign( clipOpacity ); + + diffuseColor.a.equal( 0.0 ).discard(); + + } )(); + + } + + /** + * Setups the default clipping. + * + * @param {Array} intersectionPlanes - The intersection planes. + * @param {Array} unionPlanes - The union planes. + * @return {Node} The result node. + */ + setupDefault( intersectionPlanes, unionPlanes ) { + + return Fn( () => { + + const numUnionPlanes = unionPlanes.length; + + if ( this.hardwareClipping === false && numUnionPlanes > 0 ) { + + const clippingPlanes = uniformArray( unionPlanes ); + + Loop( numUnionPlanes, ( { i } ) => { + + const plane = clippingPlanes.element( i ); + positionView.dot( plane.xyz ).greaterThan( plane.w ).discard(); + + } ); + + } + + const numIntersectionPlanes = intersectionPlanes.length; + + if ( numIntersectionPlanes > 0 ) { + + const clippingPlanes = uniformArray( intersectionPlanes ); + const clipped = bool( true ).toVar( 'clipped' ); + + Loop( numIntersectionPlanes, ( { i } ) => { + + const plane = clippingPlanes.element( i ); + clipped.assign( positionView.dot( plane.xyz ).greaterThan( plane.w ).and( clipped ) ); + + } ); + + clipped.discard(); + + } + + } )(); + + } + + /** + * Setups hardware clipping. + * + * @param {Array} unionPlanes - The union planes. + * @param {NodeBuilder} builder - The current node builder. + * @return {Node} The result node. + */ + setupHardwareClipping( unionPlanes, builder ) { + + const numUnionPlanes = unionPlanes.length; + + builder.enableHardwareClipping( numUnionPlanes ); + + return Fn( () => { + + const clippingPlanes = uniformArray( unionPlanes ); + const hw_clip_distances = builtin( builder.getClipDistance() ); + + Loop( numUnionPlanes, ( { i } ) => { + + const plane = clippingPlanes.element( i ); + + const distance = positionView.dot( plane.xyz ).sub( plane.w ).negate(); + hw_clip_distances.element( i ).assign( distance ); + + } ); + + } )(); + + } + +} + +ClippingNode.ALPHA_TO_COVERAGE = 'alphaToCoverage'; +ClippingNode.DEFAULT = 'default'; +ClippingNode.HARDWARE = 'hardware'; + +/** + * TSL function for setting up the default clipping logic. + * + * @tsl + * @function + * @returns {ClippingNode} + */ +const clipping = () => nodeObject( new ClippingNode() ); + +/** + * TSL function for setting up alpha to coverage. + * + * @tsl + * @function + * @returns {ClippingNode} + */ +const clippingAlpha = () => nodeObject( new ClippingNode( ClippingNode.ALPHA_TO_COVERAGE ) ); + +/** + * TSL function for setting up hardware-based clipping. + * + * @tsl + * @function + * @returns {ClippingNode} + */ +const hardwareClipping = () => nodeObject( new ClippingNode( ClippingNode.HARDWARE ) ); + +// See: https://casual-effects.com/research/Wyman2017Hashed/index.html + +const ALPHA_HASH_SCALE = 0.05; // Derived from trials only, and may be changed. + +const hash2D = /*@__PURE__*/ Fn( ( [ value ] ) => { + + return fract( mul( 1.0e4, sin( mul( 17.0, value.x ).add( mul( 0.1, value.y ) ) ) ).mul( add( 0.1, abs( sin( mul( 13.0, value.y ).add( value.x ) ) ) ) ) ); + +} ); + +const hash3D = /*@__PURE__*/ Fn( ( [ value ] ) => { + + return hash2D( vec2( hash2D( value.xy ), value.z ) ); + +} ); + +const getAlphaHashThreshold = /*@__PURE__*/ Fn( ( [ position ] ) => { + + // Find the discretized derivatives of our coordinates + const maxDeriv = max$1( + length( dFdx( position.xyz ) ), + length( dFdy( position.xyz ) ) + ); + + const pixScale = float( 1 ).div( float( ALPHA_HASH_SCALE ).mul( maxDeriv ) ).toVar( 'pixScale' ); + + // Find two nearest log-discretized noise scales + const pixScales = vec2( + exp2( floor( log2( pixScale ) ) ), + exp2( ceil( log2( pixScale ) ) ) + ); + + // Compute alpha thresholds at our two noise scales + const alpha = vec2( + hash3D( floor( pixScales.x.mul( position.xyz ) ) ), + hash3D( floor( pixScales.y.mul( position.xyz ) ) ), + ); + + // Factor to interpolate lerp with + const lerpFactor = fract( log2( pixScale ) ); + + // Interpolate alpha threshold from noise at two scales + const x = add( mul( lerpFactor.oneMinus(), alpha.x ), mul( lerpFactor, alpha.y ) ); + + // Pass into CDF to compute uniformly distrib threshold + const a = min$1( lerpFactor, lerpFactor.oneMinus() ); + const cases = vec3( + x.mul( x ).div( mul( 2.0, a ).mul( sub( 1.0, a ) ) ), + x.sub( mul( 0.5, a ) ).div( sub( 1.0, a ) ), + sub( 1.0, sub( 1.0, x ).mul( sub( 1.0, x ) ).div( mul( 2.0, a ).mul( sub( 1.0, a ) ) ) ) ); + + // Find our final, uniformly distributed alpha threshold (ατ) + const threshold = x.lessThan( a.oneMinus() ).select( x.lessThan( a ).select( cases.x, cases.y ), cases.z ); + + // Avoids ατ == 0. Could also do ατ =1-ατ + return clamp( threshold, 1.0e-6, 1.0 ); + +} ).setLayout( { + name: 'getAlphaHashThreshold', + type: 'float', + inputs: [ + { name: 'position', type: 'vec3' } + ] +} ); + +/** + * An attribute node for representing vertex colors. + * + * @augments AttributeNode + */ +class VertexColorNode extends AttributeNode { + + static get type() { + + return 'VertexColorNode'; + + } + + /** + * Constructs a new vertex color node. + * + * @param {number} index - The attribute index. + */ + constructor( index ) { + + super( null, 'vec4' ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isVertexColorNode = true; + + /** + * The attribute index to enable more than one sets of vertex colors. + * + * @type {number} + * @default 0 + */ + this.index = index; + + } + + /** + * Overwrites the default implementation by honoring the attribute index. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The attribute name. + */ + getAttributeName( /*builder*/ ) { + + const index = this.index; + + return 'color' + ( index > 0 ? index : '' ); + + } + + generate( builder ) { + + const attributeName = this.getAttributeName( builder ); + const geometryAttribute = builder.hasGeometryAttribute( attributeName ); + + let result; + + if ( geometryAttribute === true ) { + + result = super.generate( builder ); + + } else { + + // Vertex color fallback should be white + result = builder.generateConst( this.nodeType, new Vector4( 1, 1, 1, 1 ) ); + + } + + return result; + + } + + serialize( data ) { + + super.serialize( data ); + + data.index = this.index; + + } + + deserialize( data ) { + + super.deserialize( data ); + + this.index = data.index; + + } + +} + +/** + * TSL function for creating a reference node. + * + * @tsl + * @function + * @param {number} [index=0] - The attribute index. + * @returns {VertexColorNode} + */ +const vertexColor = ( index = 0 ) => nodeObject( new VertexColorNode( index ) ); + +/** + * Represents a "Color Burn" blend mode. + * + * It's designed to darken the base layer's colors based on the color of the blend layer. + * It significantly increases the contrast of the base layer, making the colors more vibrant and saturated. + * The darker the color in the blend layer, the stronger the darkening and contrast effect on the base layer. + * + * @tsl + * @function + * @param {Node} base - The base color. + * @param {Node} blend - The blend color. A white (#ffffff) blend color does not alter the base color. + * @return {Node} The result. + */ +const blendBurn = /*@__PURE__*/ Fn( ( [ base, blend ] ) => { + + return min$1( 1.0, base.oneMinus().div( blend ) ).oneMinus(); + +} ).setLayout( { + name: 'blendBurn', + type: 'vec3', + inputs: [ + { name: 'base', type: 'vec3' }, + { name: 'blend', type: 'vec3' } + ] +} ); + +/** + * Represents a "Color Dodge" blend mode. + * + * It's designed to lighten the base layer's colors based on the color of the blend layer. + * It significantly increases the brightness of the base layer, making the colors lighter and more vibrant. + * The brighter the color in the blend layer, the stronger the lightening and contrast effect on the base layer. + * + * @tsl + * @function + * @param {Node} base - The base color. + * @param {Node} blend - The blend color. A black (#000000) blend color does not alter the base color. + * @return {Node} The result. + */ +const blendDodge = /*@__PURE__*/ Fn( ( [ base, blend ] ) => { + + return min$1( base.div( blend.oneMinus() ), 1.0 ); + +} ).setLayout( { + name: 'blendDodge', + type: 'vec3', + inputs: [ + { name: 'base', type: 'vec3' }, + { name: 'blend', type: 'vec3' } + ] +} ); + +/** + * Represents a "Screen" blend mode. + * + * Similar to `blendDodge()`, this mode also lightens the base layer's colors based on the color of the blend layer. + * The "Screen" blend mode is better for general brightening whereas the "Dodge" results in more subtle and nuanced + * effects. + * + * @tsl + * @function + * @param {Node} base - The base color. + * @param {Node} blend - The blend color. A black (#000000) blend color does not alter the base color. + * @return {Node} The result. + */ +const blendScreen = /*@__PURE__*/ Fn( ( [ base, blend ] ) => { + + return base.oneMinus().mul( blend.oneMinus() ).oneMinus(); + +} ).setLayout( { + name: 'blendScreen', + type: 'vec3', + inputs: [ + { name: 'base', type: 'vec3' }, + { name: 'blend', type: 'vec3' } + ] +} ); + +/** + * Represents a "Overlay" blend mode. + * + * It's designed to increase the contrast of the base layer based on the color of the blend layer. + * It amplifies the existing colors and contrast in the base layer, making lighter areas lighter and darker areas darker. + * The color of the blend layer significantly influences the resulting contrast and color shift in the base layer. + * + * @tsl + * @function + * @param {Node} base - The base color. + * @param {Node} blend - The blend color + * @return {Node} The result. + */ +const blendOverlay = /*@__PURE__*/ Fn( ( [ base, blend ] ) => { + + return mix( base.mul( 2.0 ).mul( blend ), base.oneMinus().mul( 2.0 ).mul( blend.oneMinus() ).oneMinus(), step( 0.5, base ) ); + +} ).setLayout( { + name: 'blendOverlay', + type: 'vec3', + inputs: [ + { name: 'base', type: 'vec3' }, + { name: 'blend', type: 'vec3' } + ] +} ); + +/** + * This function blends two color based on their alpha values by replicating the behavior of `THREE.NormalBlending`. + * It assumes both input colors have non-premultiplied alpha. + * + * @tsl + * @function + * @param {Node} base - The base color. + * @param {Node} blend - The blend color + * @return {Node} The result. + */ +const blendColor = /*@__PURE__*/ Fn( ( [ base, blend ] ) => { + + const outAlpha = blend.a.add( base.a.mul( blend.a.oneMinus() ) ); + + return vec4( blend.rgb.mul( blend.a ).add( base.rgb.mul( base.a ).mul( blend.a.oneMinus() ) ).div( outAlpha ), outAlpha ); + +} ).setLayout( { + name: 'blendColor', + type: 'vec4', + inputs: [ + { name: 'base', type: 'vec4' }, + { name: 'blend', type: 'vec4' } + ] +} ); + +/** + * Premultiplies the RGB channels of a color by its alpha channel. + * + * This function is useful for converting a non-premultiplied alpha color + * into a premultiplied alpha format, where the RGB values are scaled + * by the alpha value. Premultiplied alpha is often used in graphics + * rendering for certain operations, such as compositing and image processing. + * + * @tsl + * @function + * @param {Node} color - The input color with non-premultiplied alpha. + * @return {Node} The color with premultiplied alpha. + */ +const premultiplyAlpha = /*@__PURE__*/ Fn( ( [ color ] ) => { + + return vec4( color.rgb.mul( color.a ), color.a ); + +}, { color: 'vec4', return: 'vec4' } ); + +/** + * Unpremultiplies the RGB channels of a color by its alpha channel. + * + * This function is useful for converting a premultiplied alpha color + * back into a non-premultiplied alpha format, where the RGB values are + * divided by the alpha value. Unpremultiplied alpha is often used in graphics + * rendering for certain operations, such as compositing and image processing. + * + * @tsl + * @function + * @param {Node} color - The input color with premultiplied alpha. + * @return {Node} The color with non-premultiplied alpha. + */ +const unpremultiplyAlpha = /*@__PURE__*/ Fn( ( [ color ] ) => { + + If( color.a.equal( 0.0 ), () => vec4( 0.0 ) ); + + return vec4( color.rgb.div( color.a ), color.a ); + +}, { color: 'vec4', return: 'vec4' } ); + + +// Deprecated + +/** + * @tsl + * @function + * @deprecated since r171. Use {@link blendBurn} instead. + * + * @param {...any} params + * @returns {Function} + */ +const burn = ( ...params ) => { // @deprecated, r171 + + console.warn( 'THREE.TSL: "burn" has been renamed. Use "blendBurn" instead.' ); + return blendBurn( params ); + +}; + +/** + * @tsl + * @function + * @deprecated since r171. Use {@link blendDodge} instead. + * + * @param {...any} params + * @returns {Function} + */ +const dodge = ( ...params ) => { // @deprecated, r171 + + console.warn( 'THREE.TSL: "dodge" has been renamed. Use "blendDodge" instead.' ); + return blendDodge( params ); + +}; + +/** + * @tsl + * @function + * @deprecated since r171. Use {@link blendScreen} instead. + * + * @param {...any} params + * @returns {Function} + */ +const screen = ( ...params ) => { // @deprecated, r171 + + console.warn( 'THREE.TSL: "screen" has been renamed. Use "blendScreen" instead.' ); + return blendScreen( params ); + +}; + +/** + * @tsl + * @function + * @deprecated since r171. Use {@link blendOverlay} instead. + * + * @param {...any} params + * @returns {Function} + */ +const overlay = ( ...params ) => { // @deprecated, r171 + + console.warn( 'THREE.TSL: "overlay" has been renamed. Use "blendOverlay" instead.' ); + return blendOverlay( params ); + +}; + +/** + * Base class for all node materials. + * + * @augments Material + */ +class NodeMaterial extends Material { + + static get type() { + + return 'NodeMaterial'; + + } + + /** + * Represents the type of the node material. + * + * @type {string} + */ + get type() { + + return this.constructor.type; + + } + + set type( _value ) { /* */ } + + /** + * Constructs a new node material. + */ + constructor() { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isNodeMaterial = true; + + /** + * Whether this material is affected by fog or not. + * + * @type {boolean} + * @default true + */ + this.fog = true; + + /** + * Whether this material is affected by lights or not. + * + * @type {boolean} + * @default false + */ + this.lights = false; + + /** + * Whether this material uses hardware clipping or not. + * This property is managed by the engine and should not be + * modified by apps. + * + * @type {boolean} + * @default false + */ + this.hardwareClipping = false; + + /** + * Node materials which set their `lights` property to `true` + * are affected by all lights of the scene. Sometimes selective + * lighting is wanted which means only _some_ lights in the scene + * affect a material. This can be achieved by creating an instance + * of {@link LightsNode} with a list of selective + * lights and assign the node to this property. + * + * ```js + * const customLightsNode = lights( [ light1, light2 ] ); + * material.lightsNode = customLightsNode; + * ``` + * + * @type {?LightsNode} + * @default null + */ + this.lightsNode = null; + + /** + * The environment of node materials can be defined by an environment + * map assigned to the `envMap` property or by `Scene.environment` + * if the node material is a PBR material. This node property allows to overwrite + * the default behavior and define the environment with a custom node. + * + * ```js + * material.envNode = pmremTexture( renderTarget.texture ); + * ``` + * + * @type {?Node} + * @default null + */ + this.envNode = null; + + /** + * The lighting of node materials might be influenced by ambient occlusion. + * The default AO is inferred from an ambient occlusion map assigned to `aoMap` + * and the respective `aoMapIntensity`. This node property allows to overwrite + * the default and define the ambient occlusion with a custom node instead. + * + * If you don't want to overwrite the diffuse color but modify the existing + * values instead, use {@link materialAO}. + * + * @type {?Node} + * @default null + */ + this.aoNode = null; + + /** + * The diffuse color of node materials is by default inferred from the + * `color` and `map` properties. This node property allows to overwrite the default + * and define the diffuse color with a node instead. + * + * ```js + * material.colorNode = color( 0xff0000 ); // define red color + * ``` + * + * If you don't want to overwrite the diffuse color but modify the existing + * values instead, use {@link materialColor}. + * + * ```js + * material.colorNode = materialColor.mul( color( 0xff0000 ) ); // give diffuse colors a red tint + * ``` + * + * @type {?Node} + * @default null + */ + this.colorNode = null; + + /** + * The normals of node materials are by default inferred from the `normalMap`/`normalScale` + * or `bumpMap`/`bumpScale` properties. This node property allows to overwrite the default + * and define the normals with a node instead. + * + * If you don't want to overwrite the normals but modify the existing values instead, + * use {@link materialNormal}. + * + * @type {?Node} + * @default null + */ + this.normalNode = null; + + /** + * The opacity of node materials is by default inferred from the `opacity` + * and `alphaMap` properties. This node property allows to overwrite the default + * and define the opacity with a node instead. + * + * If you don't want to overwrite the normals but modify the existing + * value instead, use {@link materialOpacity}. + * + * @type {?Node} + * @default null + */ + this.opacityNode = null; + + /** + * This node can be used to implement a variety of filter-like effects. The idea is + * to store the current rendering into a texture e.g. via `viewportSharedTexture()`, use it + * to create an arbitrary effect and then assign the node composition to this property. + * Everything behind the object using this material will now be affected by a filter. + * + * ```js + * const material = new NodeMaterial() + * material.transparent = true; + * + * // everything behind the object will be monochromatic + * material.backdropNode = saturation( viewportSharedTexture().rgb, 0 ); + * ``` + * + * Backdrop computations are part of the lighting so only lit materials can use this property. + * + * @type {?Node} + * @default null + */ + this.backdropNode = null; + + /** + * This node allows to modulate the influence of `backdropNode` to the outgoing light. + * + * @type {?Node} + * @default null + */ + this.backdropAlphaNode = null; + + /** + * The alpha test of node materials is by default inferred from the `alphaTest` + * property. This node property allows to overwrite the default and define the + * alpha test with a node instead. + * + * If you don't want to overwrite the alpha test but modify the existing + * value instead, use {@link materialAlphaTest}. + * + * @type {?Node} + * @default null + */ + this.alphaTestNode = null; + + + /** + * Discards the fragment if the mask value is `false`. + * + * @type {?Node} + * @default null + */ + this.maskNode = null; + + /** + * The local vertex positions are computed based on multiple factors like the + * attribute data, morphing or skinning. This node property allows to overwrite + * the default and define local vertex positions with nodes instead. + * + * If you don't want to overwrite the vertex positions but modify the existing + * values instead, use {@link positionLocal}. + * + *```js + * material.positionNode = positionLocal.add( displace ); + * ``` + * + * @type {?Node} + * @default null + */ + this.positionNode = null; + + /** + * This node property is intended for logic which modifies geometry data once or per animation step. + * Apps usually place such logic randomly in initialization routines or in the animation loop. + * `geometryNode` is intended as a dedicated API so there is an intended spot where geometry modifications + * can be implemented. + * + * The idea is to assign a `Fn` definition that holds the geometry modification logic. A typical example + * would be a GPU based particle system that provides a node material for usage on app level. The particle + * simulation would be implemented as compute shaders and managed inside a `Fn` function. This function is + * eventually assigned to `geometryNode`. + * + * @type {?Function} + * @default null + */ + this.geometryNode = null; + + /** + * Allows to overwrite depth values in the fragment shader. + * + * @type {?Node} + * @default null + */ + this.depthNode = null; + + /** + * Allows to overwrite the position used for shadow map rendering which + * is by default {@link positionWorld}, the vertex position + * in world space. + * + * @type {?Node} + * @default null + */ + this.receivedShadowPositionNode = null; + + /** + * Allows to overwrite the geometry position used for shadow map projection which + * is by default {@link positionLocal}, the vertex position in local space. + * + * @type {?Node} + * @default null + */ + this.castShadowPositionNode = null; + + /** + * This node can be used to influence how an object using this node material + * receive shadows. + * + * ```js + * const totalShadows = float( 1 ).toVar(); + * material.receivedShadowNode = Fn( ( [ shadow ] ) => { + * totalShadows.mulAssign( shadow ); + * //return float( 1 ); // bypass received shadows + * return shadow.mix( color( 0xff0000 ), 1 ); // modify shadow color + * } ); + * + * @type {?(Function|FunctionNode)} + * @default null + */ + this.receivedShadowNode = null; + + /** + * This node can be used to influence how an object using this node material + * casts shadows. To apply a color to shadows, you can simply do: + * + * ```js + * material.castShadowNode = vec4( 1, 0, 0, 1 ); + * ``` + * + * Which can be nice to fake colored shadows of semi-transparent objects. It + * is also common to use the property with `Fn` function so checks are performed + * per fragment. + * + * ```js + * materialCustomShadow.castShadowNode = Fn( () => { + * hash( vertexIndex ).greaterThan( 0.5 ).discard(); + * return materialColor; + * } )(); + * ``` + * + * @type {?Node} + * @default null + */ + this.castShadowNode = null; + + /** + * This node can be used to define the final output of the material. + * + * TODO: Explain the differences to `fragmentNode`. + * + * @type {?Node} + * @default null + */ + this.outputNode = null; + + /** + * MRT configuration is done on renderer or pass level. This node allows to + * overwrite what values are written into MRT targets on material level. This + * can be useful for implementing selective FX features that should only affect + * specific objects. + * + * @type {?MRTNode} + * @default null + */ + this.mrtNode = null; + + /** + * This node property can be used if you need complete freedom in implementing + * the fragment shader. Assigning a node will replace the built-in material + * logic used in the fragment stage. + * + * @type {?Node} + * @default null + */ + this.fragmentNode = null; + + /** + * This node property can be used if you need complete freedom in implementing + * the vertex shader. Assigning a node will replace the built-in material logic + * used in the vertex stage. + * + * @type {?Node} + * @default null + */ + this.vertexNode = null; + + // Deprecated properties + + Object.defineProperty( this, 'shadowPositionNode', { // @deprecated, r176 + + get: () => { + + return this.receivedShadowPositionNode; + + }, + + set: ( value ) => { + + console.warn( 'THREE.NodeMaterial: ".shadowPositionNode" was renamed to ".receivedShadowPositionNode".' ); + + this.receivedShadowPositionNode = value; + + } + + } ); + + } + + /** + * Allows to define a custom cache key that influence the material key computation + * for render objects. + * + * @return {string} The custom cache key. + */ + customProgramCacheKey() { + + return this.type + getCacheKey$1( this ); + + } + + /** + * Builds this material with the given node builder. + * + * @param {NodeBuilder} builder - The current node builder. + */ + build( builder ) { + + this.setup( builder ); + + } + + /** + * Setups a node material observer with the given builder. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {NodeMaterialObserver} The node material observer. + */ + setupObserver( builder ) { + + return new NodeMaterialObserver( builder ); + + } + + /** + * Setups the vertex and fragment stage of this node material. + * + * @param {NodeBuilder} builder - The current node builder. + */ + setup( builder ) { + + builder.context.setupNormal = () => subBuild( this.setupNormal( builder ), 'NORMAL', 'vec3' ); + builder.context.setupPositionView = () => this.setupPositionView( builder ); + builder.context.setupModelViewProjection = () => this.setupModelViewProjection( builder ); + + const renderer = builder.renderer; + const renderTarget = renderer.getRenderTarget(); + + // < VERTEX STAGE > + + builder.addStack(); + + const mvp = subBuild( this.setupVertex( builder ), 'VERTEX' ); + + const vertexNode = this.vertexNode || mvp; + + builder.stack.outputNode = vertexNode; + + this.setupHardwareClipping( builder ); + + if ( this.geometryNode !== null ) { + + builder.stack.outputNode = builder.stack.outputNode.bypass( this.geometryNode ); + + } + + builder.addFlow( 'vertex', builder.removeStack() ); + + // < FRAGMENT STAGE > + + builder.addStack(); + + let resultNode; + + const clippingNode = this.setupClipping( builder ); + + if ( this.depthWrite === true || this.depthTest === true ) { + + // only write depth if depth buffer is configured + + if ( renderTarget !== null ) { + + if ( renderTarget.depthBuffer === true ) this.setupDepth( builder ); + + } else { + + if ( renderer.depth === true ) this.setupDepth( builder ); + + } + + } + + if ( this.fragmentNode === null ) { + + this.setupDiffuseColor( builder ); + this.setupVariants( builder ); + + const outgoingLightNode = this.setupLighting( builder ); + + if ( clippingNode !== null ) builder.stack.add( clippingNode ); + + // force unsigned floats - useful for RenderTargets + + const basicOutput = vec4( outgoingLightNode, diffuseColor.a ).max( 0 ); + + resultNode = this.setupOutput( builder, basicOutput ); + + // OUTPUT NODE + + output.assign( resultNode ); + + // + + const isCustomOutput = this.outputNode !== null; + + if ( isCustomOutput ) resultNode = this.outputNode; + + // MRT + + if ( renderTarget !== null ) { + + const mrt = renderer.getMRT(); + const materialMRT = this.mrtNode; + + if ( mrt !== null ) { + + if ( isCustomOutput ) output.assign( resultNode ); + + resultNode = mrt; + + if ( materialMRT !== null ) { + + resultNode = mrt.merge( materialMRT ); + + } + + } else if ( materialMRT !== null ) { + + resultNode = materialMRT; + + } + + } + + } else { + + let fragmentNode = this.fragmentNode; + + if ( fragmentNode.isOutputStructNode !== true ) { + + fragmentNode = vec4( fragmentNode ); + + } + + resultNode = this.setupOutput( builder, fragmentNode ); + + } + + builder.stack.outputNode = resultNode; + + builder.addFlow( 'fragment', builder.removeStack() ); + + // < OBSERVER > + + builder.observer = this.setupObserver( builder ); + + } + + /** + * Setups the clipping node. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {ClippingNode} The clipping node. + */ + setupClipping( builder ) { + + if ( builder.clippingContext === null ) return null; + + const { unionPlanes, intersectionPlanes } = builder.clippingContext; + + let result = null; + + if ( unionPlanes.length > 0 || intersectionPlanes.length > 0 ) { + + const samples = builder.renderer.samples; + + if ( this.alphaToCoverage && samples > 1 ) { + + // to be added to flow when the color/alpha value has been determined + result = clippingAlpha(); + + } else { + + builder.stack.add( clipping() ); + + } + + } + + return result; + + } + + /** + * Setups the hardware clipping if available on the current device. + * + * @param {NodeBuilder} builder - The current node builder. + */ + setupHardwareClipping( builder ) { + + this.hardwareClipping = false; + + if ( builder.clippingContext === null ) return; + + const candidateCount = builder.clippingContext.unionPlanes.length; + + // 8 planes supported by WebGL ANGLE_clip_cull_distance and WebGPU clip-distances + + if ( candidateCount > 0 && candidateCount <= 8 && builder.isAvailable( 'clipDistance' ) ) { + + builder.stack.add( hardwareClipping() ); + + this.hardwareClipping = true; + + } + + return; + + } + + /** + * Setups the depth of this material. + * + * @param {NodeBuilder} builder - The current node builder. + */ + setupDepth( builder ) { + + const { renderer, camera } = builder; + + // Depth + + let depthNode = this.depthNode; + + if ( depthNode === null ) { + + const mrt = renderer.getMRT(); + + if ( mrt && mrt.has( 'depth' ) ) { + + depthNode = mrt.get( 'depth' ); + + } else if ( renderer.logarithmicDepthBuffer === true ) { + + if ( camera.isPerspectiveCamera ) { + + depthNode = viewZToLogarithmicDepth( positionView.z, cameraNear, cameraFar ); + + } else { + + depthNode = viewZToOrthographicDepth( positionView.z, cameraNear, cameraFar ); + + } + + } + + } + + if ( depthNode !== null ) { + + depth.assign( depthNode ).toStack(); + + } + + } + + /** + * Setups the position node in view space. This method exists + * so derived node materials can modify the implementation e.g. sprite materials. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {Node} The position in view space. + */ + setupPositionView( /*builder*/ ) { + + return modelViewMatrix.mul( positionLocal ).xyz; + + } + + /** + * Setups the position in clip space. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {Node} The position in view space. + */ + setupModelViewProjection( /*builder*/ ) { + + return cameraProjectionMatrix.mul( positionView ); + + } + + /** + * Setups the logic for the vertex stage. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {Node} The position in clip space. + */ + setupVertex( builder ) { + + builder.addStack(); + + this.setupPosition( builder ); + + builder.context.vertex = builder.removeStack(); + + return modelViewProjection; + + } + + /** + * Setups the computation of the position in local space. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {Node} The position in local space. + */ + setupPosition( builder ) { + + const { object, geometry } = builder; + + if ( geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color ) { + + morphReference( object ).toStack(); + + } + + if ( object.isSkinnedMesh === true ) { + + skinning( object ).toStack(); + + } + + if ( this.displacementMap ) { + + const displacementMap = materialReference( 'displacementMap', 'texture' ); + const displacementScale = materialReference( 'displacementScale', 'float' ); + const displacementBias = materialReference( 'displacementBias', 'float' ); + + positionLocal.addAssign( normalLocal.normalize().mul( ( displacementMap.x.mul( displacementScale ).add( displacementBias ) ) ) ); + + } + + if ( object.isBatchedMesh ) { + + batch( object ).toStack(); + + } + + if ( ( object.isInstancedMesh && object.instanceMatrix && object.instanceMatrix.isInstancedBufferAttribute === true ) ) { + + instancedMesh( object ).toStack(); + + } + + if ( this.positionNode !== null ) { + + positionLocal.assign( subBuild( this.positionNode, 'POSITION', 'vec3' ) ); + + } + + return positionLocal; + + } + + /** + * Setups the computation of the material's diffuse color. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {BufferGeometry} geometry - The geometry. + */ + setupDiffuseColor( { object, geometry } ) { + + // MASK + + if ( this.maskNode !== null ) { + + // Discard if the mask is `false` + + bool( this.maskNode ).not().discard(); + + } + + // COLOR + + let colorNode = this.colorNode ? vec4( this.colorNode ) : materialColor; + + // VERTEX COLORS + + if ( this.vertexColors === true && geometry.hasAttribute( 'color' ) ) { + + colorNode = colorNode.mul( vertexColor() ); + + } + + // INSTANCED COLORS + + if ( object.instanceColor ) { + + const instanceColor = varyingProperty( 'vec3', 'vInstanceColor' ); + + colorNode = instanceColor.mul( colorNode ); + + } + + if ( object.isBatchedMesh && object._colorsTexture ) { + + const batchColor = varyingProperty( 'vec3', 'vBatchColor' ); + + colorNode = batchColor.mul( colorNode ); + + } + + // DIFFUSE COLOR + + diffuseColor.assign( colorNode ); + + // OPACITY + + const opacityNode = this.opacityNode ? float( this.opacityNode ) : materialOpacity; + diffuseColor.a.assign( diffuseColor.a.mul( opacityNode ) ); + + // ALPHA TEST + + let alphaTestNode = null; + + if ( this.alphaTestNode !== null || this.alphaTest > 0 ) { + + alphaTestNode = this.alphaTestNode !== null ? float( this.alphaTestNode ) : materialAlphaTest; + + diffuseColor.a.lessThanEqual( alphaTestNode ).discard(); + + } + + // ALPHA HASH + + if ( this.alphaHash === true ) { + + diffuseColor.a.lessThan( getAlphaHashThreshold( positionLocal ) ).discard(); + + } + + // OPAQUE + + const isOpaque = this.transparent === false && this.blending === NormalBlending && this.alphaToCoverage === false; + + if ( isOpaque ) { + + diffuseColor.a.assign( 1.0 ); + + } else if ( alphaTestNode === null ) { + + diffuseColor.a.lessThanEqual( 0 ).discard(); + + } + + } + + /** + * Abstract interface method that can be implemented by derived materials + * to setup material-specific node variables. + * + * @abstract + * @param {NodeBuilder} builder - The current node builder. + */ + setupVariants( /*builder*/ ) { + + // Interface function. + + } + + /** + * Setups the outgoing light node variable + * + * @return {Node} The outgoing light node. + */ + setupOutgoingLight() { + + return ( this.lights === true ) ? vec3( 0 ) : diffuseColor.rgb; + + } + + /** + * Setups the normal node from the material. + * + * @return {Node} The normal node. + */ + setupNormal() { + + return this.normalNode ? vec3( this.normalNode ) : materialNormal; + + } + + /** + * Setups the environment node from the material. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {Node} The environment node. + */ + setupEnvironment( /*builder*/ ) { + + let node = null; + + if ( this.envNode ) { + + node = this.envNode; + + } else if ( this.envMap ) { + + node = this.envMap.isCubeTexture ? materialReference( 'envMap', 'cubeTexture' ) : materialReference( 'envMap', 'texture' ); + + } + + return node; + + } + + /** + * Setups the light map node from the material. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {Node} The light map node. + */ + setupLightMap( builder ) { + + let node = null; + + if ( builder.material.lightMap ) { + + node = new IrradianceNode( materialLightMap ); + + } + + return node; + + } + + /** + * Setups the lights node based on the scene, environment and material. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {LightsNode} The lights node. + */ + setupLights( builder ) { + + const materialLightsNode = []; + + // + + const envNode = this.setupEnvironment( builder ); + + if ( envNode && envNode.isLightingNode ) { + + materialLightsNode.push( envNode ); + + } + + const lightMapNode = this.setupLightMap( builder ); + + if ( lightMapNode && lightMapNode.isLightingNode ) { + + materialLightsNode.push( lightMapNode ); + + } + + if ( this.aoNode !== null || builder.material.aoMap ) { + + const aoNode = this.aoNode !== null ? this.aoNode : materialAO; + + materialLightsNode.push( new AONode( aoNode ) ); + + } + + let lightsN = this.lightsNode || builder.lightsNode; + + if ( materialLightsNode.length > 0 ) { + + lightsN = builder.renderer.lighting.createNode( [ ...lightsN.getLights(), ...materialLightsNode ] ); + + } + + return lightsN; + + } + + /** + * This method should be implemented by most derived materials + * since it defines the material's lighting model. + * + * @abstract + * @param {NodeBuilder} builder - The current node builder. + * @return {LightingModel} The lighting model. + */ + setupLightingModel( /*builder*/ ) { + + // Interface function. + + } + + /** + * Setups the outgoing light node. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {Node} The outgoing light node. + */ + setupLighting( builder ) { + + const { material } = builder; + const { backdropNode, backdropAlphaNode, emissiveNode } = this; + + // OUTGOING LIGHT + + const lights = this.lights === true || this.lightsNode !== null; + + const lightsNode = lights ? this.setupLights( builder ) : null; + + let outgoingLightNode = this.setupOutgoingLight( builder ); + + if ( lightsNode && lightsNode.getScope().hasLights ) { + + const lightingModel = this.setupLightingModel( builder ) || null; + + outgoingLightNode = lightingContext( lightsNode, lightingModel, backdropNode, backdropAlphaNode ); + + } else if ( backdropNode !== null ) { + + outgoingLightNode = vec3( backdropAlphaNode !== null ? mix( outgoingLightNode, backdropNode, backdropAlphaNode ) : backdropNode ); + + } + + // EMISSIVE + + if ( ( emissiveNode && emissiveNode.isNode === true ) || ( material.emissive && material.emissive.isColor === true ) ) { + + emissive.assign( vec3( emissiveNode ? emissiveNode : materialEmissive ) ); + + outgoingLightNode = outgoingLightNode.add( emissive ); + + } + + return outgoingLightNode; + + } + + /** + * Setup the fog. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {Node} outputNode - The existing output node. + * @return {Node} The output node. + */ + setupFog( builder, outputNode ) { + + const fogNode = builder.fogNode; + + if ( fogNode ) { + + output.assign( outputNode ); + + outputNode = vec4( fogNode ); + + } + + return outputNode; + + } + + /** + * Setups premultiplied alpha. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {Node} outputNode - The existing output node. + * @return {Node} The output node. + */ + setupPremultipliedAlpha( builder, outputNode ) { + + return premultiplyAlpha( outputNode ); + + } + + /** + * Setups the output node. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {Node} outputNode - The existing output node. + * @return {Node} The output node. + */ + setupOutput( builder, outputNode ) { + + // FOG + + if ( this.fog === true ) { + + outputNode = this.setupFog( builder, outputNode ); + + } + + // PREMULTIPLIED ALPHA + + if ( this.premultipliedAlpha === true ) { + + outputNode = this.setupPremultipliedAlpha( builder, outputNode ); + + } + + return outputNode; + + } + + /** + * Most classic material types have a node pendant e.g. for `MeshBasicMaterial` + * there is `MeshBasicNodeMaterial`. This utility method is intended for + * defining all material properties of the classic type in the node type. + * + * @param {Material} material - The material to copy properties with their values to this node material. + */ + setDefaultValues( material ) { + + // This approach is to reuse the native refreshUniforms* + // and turn available the use of features like transmission and environment in core + + for ( const property in material ) { + + const value = material[ property ]; + + if ( this[ property ] === undefined ) { + + this[ property ] = value; + + if ( value && value.clone ) this[ property ] = value.clone(); + + } + + } + + const descriptors = Object.getOwnPropertyDescriptors( material.constructor.prototype ); + + for ( const key in descriptors ) { + + if ( Object.getOwnPropertyDescriptor( this.constructor.prototype, key ) === undefined && + descriptors[ key ].get !== undefined ) { + + Object.defineProperty( this.constructor.prototype, key, descriptors[ key ] ); + + } + + } + + } + + /** + * Serializes this material to JSON. + * + * @param {?(Object|string)} meta - The meta information for serialization. + * @return {Object} The serialized node. + */ + toJSON( meta ) { + + const isRoot = ( meta === undefined || typeof meta === 'string' ); + + if ( isRoot ) { + + meta = { + textures: {}, + images: {}, + nodes: {} + }; + + } + + const data = Material.prototype.toJSON.call( this, meta ); + const nodeChildren = getNodeChildren( this ); + + data.inputNodes = {}; + + for ( const { property, childNode } of nodeChildren ) { + + data.inputNodes[ property ] = childNode.toJSON( meta ).uuid; + + } + + // TODO: Copied from Object3D.toJSON + + function extractFromCache( cache ) { + + const values = []; + + for ( const key in cache ) { + + const data = cache[ key ]; + delete data.metadata; + values.push( data ); + + } + + return values; + + } + + if ( isRoot ) { + + const textures = extractFromCache( meta.textures ); + const images = extractFromCache( meta.images ); + const nodes = extractFromCache( meta.nodes ); + + if ( textures.length > 0 ) data.textures = textures; + if ( images.length > 0 ) data.images = images; + if ( nodes.length > 0 ) data.nodes = nodes; + + } + + return data; + + } + + /** + * Copies the properties of the given node material to this instance. + * + * @param {NodeMaterial} source - The material to copy. + * @return {NodeMaterial} A reference to this node material. + */ + copy( source ) { + + this.lightsNode = source.lightsNode; + this.envNode = source.envNode; + + this.colorNode = source.colorNode; + this.normalNode = source.normalNode; + this.opacityNode = source.opacityNode; + this.backdropNode = source.backdropNode; + this.backdropAlphaNode = source.backdropAlphaNode; + this.alphaTestNode = source.alphaTestNode; + this.maskNode = source.maskNode; + + this.positionNode = source.positionNode; + this.geometryNode = source.geometryNode; + + this.depthNode = source.depthNode; + this.receivedShadowPositionNode = source.receivedShadowPositionNode; + this.castShadowPositionNode = source.castShadowPositionNode; + this.receivedShadowNode = source.receivedShadowNode; + this.castShadowNode = source.castShadowNode; + + this.outputNode = source.outputNode; + this.mrtNode = source.mrtNode; + + this.fragmentNode = source.fragmentNode; + this.vertexNode = source.vertexNode; + + return super.copy( source ); + + } + +} + +const _defaultValues$d = /*@__PURE__*/ new LineBasicMaterial(); + +/** + * Node material version of {@link LineBasicMaterial}. + * + * @augments NodeMaterial + */ +class LineBasicNodeMaterial extends NodeMaterial { + + static get type() { + + return 'LineBasicNodeMaterial'; + + } + + /** + * Constructs a new line basic node material. + * + * @param {Object} [parameters] - The configuration parameter. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLineBasicNodeMaterial = true; + + this.setDefaultValues( _defaultValues$d ); + + this.setValues( parameters ); + + } + +} + +const _defaultValues$c = /*@__PURE__*/ new LineDashedMaterial(); + +/** + * Node material version of {@link LineDashedMaterial}. + * + * @augments NodeMaterial + */ +class LineDashedNodeMaterial extends NodeMaterial { + + static get type() { + + return 'LineDashedNodeMaterial'; + + } + + /** + * Constructs a new line dashed node material. + * + * @param {Object} [parameters] - The configuration parameter. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLineDashedNodeMaterial = true; + + this.setDefaultValues( _defaultValues$c ); + + /** + * The dash offset. + * + * @type {number} + * @default 0 + */ + this.dashOffset = 0; + + /** + * The offset of dash materials is by default inferred from the `dashOffset` + * property. This node property allows to overwrite the default + * and define the offset with a node instead. + * + * If you don't want to overwrite the offset but modify the existing + * value instead, use {@link materialLineDashOffset}. + * + * @type {?Node} + * @default null + */ + this.offsetNode = null; + + /** + * The scale of dash materials is by default inferred from the `scale` + * property. This node property allows to overwrite the default + * and define the scale with a node instead. + * + * If you don't want to overwrite the scale but modify the existing + * value instead, use {@link materialLineScale}. + * + * @type {?Node} + * @default null + */ + this.dashScaleNode = null; + + /** + * The dash size of dash materials is by default inferred from the `dashSize` + * property. This node property allows to overwrite the default + * and define the dash size with a node instead. + * + * If you don't want to overwrite the dash size but modify the existing + * value instead, use {@link materialLineDashSize}. + * + * @type {?Node} + * @default null + */ + this.dashSizeNode = null; + + /** + * The gap size of dash materials is by default inferred from the `gapSize` + * property. This node property allows to overwrite the default + * and define the gap size with a node instead. + * + * If you don't want to overwrite the gap size but modify the existing + * value instead, use {@link materialLineGapSize}. + * + * @type {?Node} + * @default null + */ + this.gapSizeNode = null; + + this.setValues( parameters ); + + } + + /** + * Setups the dash specific node variables. + * + * @param {NodeBuilder} builder - The current node builder. + */ + setupVariants( /* builder */ ) { + + const offsetNode = this.offsetNode ? float( this.offsetNode ) : materialLineDashOffset; + const dashScaleNode = this.dashScaleNode ? float( this.dashScaleNode ) : materialLineScale; + const dashSizeNode = this.dashSizeNode ? float( this.dashSizeNode ) : materialLineDashSize; + const gapSizeNode = this.gapSizeNode ? float( this.gapSizeNode ) : materialLineGapSize; + + dashSize.assign( dashSizeNode ); + gapSize.assign( gapSizeNode ); + + const vLineDistance = varying( attribute( 'lineDistance' ).mul( dashScaleNode ) ); + const vLineDistanceOffset = offsetNode ? vLineDistance.add( offsetNode ) : vLineDistance; + + vLineDistanceOffset.mod( dashSize.add( gapSize ) ).greaterThan( dashSize ).discard(); + + } + +} + +let _sharedFramebuffer = null; + +/** + * `ViewportTextureNode` creates an internal texture for each node instance. This module + * shares a texture across all instances of `ViewportSharedTextureNode`. It should + * be the first choice when using data of the default/screen framebuffer for performance reasons. + * + * @augments ViewportTextureNode + */ +class ViewportSharedTextureNode extends ViewportTextureNode { + + static get type() { + + return 'ViewportSharedTextureNode'; + + } + + /** + * Constructs a new viewport shared texture node. + * + * @param {Node} [uvNode=screenUV] - The uv node. + * @param {?Node} [levelNode=null] - The level node. + */ + constructor( uvNode = screenUV, levelNode = null ) { + + if ( _sharedFramebuffer === null ) { + + _sharedFramebuffer = new FramebufferTexture(); + + } + + super( uvNode, levelNode, _sharedFramebuffer ); + + } + + updateReference() { + + return this; + + } + +} + +/** + * TSL function for creating a shared viewport texture node. + * + * @tsl + * @function + * @param {?Node} [uvNode=screenUV] - The uv node. + * @param {?Node} [levelNode=null] - The level node. + * @returns {ViewportSharedTextureNode} + */ +const viewportSharedTexture = /*@__PURE__*/ nodeProxy( ViewportSharedTextureNode ).setParameterLength( 0, 2 ); + +const _defaultValues$b = /*@__PURE__*/ new LineDashedMaterial(); + +/** + * This node material can be used to render lines with a size larger than one + * by representing them as instanced meshes. + * + * @augments NodeMaterial + */ +class Line2NodeMaterial extends NodeMaterial { + + static get type() { + + return 'Line2NodeMaterial'; + + } + + /** + * Constructs a new node material for wide line rendering. + * + * @param {Object} [parameters={}] - The configuration parameter. + */ + constructor( parameters = {} ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLine2NodeMaterial = true; + + this.setDefaultValues( _defaultValues$b ); + + /** + * Whether vertex colors should be used or not. + * + * @type {boolean} + * @default false + */ + this.useColor = parameters.vertexColors; + + /** + * The dash offset. + * + * @type {number} + * @default 0 + */ + this.dashOffset = 0; + + /** + * The line width. + * + * @type {number} + * @default 0 + */ + this.lineWidth = 1; + + /** + * Defines the lines color. + * + * @type {?Node} + * @default null + */ + this.lineColorNode = null; + + /** + * Defines the offset. + * + * @type {?Node} + * @default null + */ + this.offsetNode = null; + + /** + * Defines the dash scale. + * + * @type {?Node} + * @default null + */ + this.dashScaleNode = null; + + /** + * Defines the dash size. + * + * @type {?Node} + * @default null + */ + this.dashSizeNode = null; + + /** + * Defines the gap size. + * + * @type {?Node} + * @default null + */ + this.gapSizeNode = null; + + /** + * Blending is set to `NoBlending` since transparency + * is not supported, yet. + * + * @type {number} + * @default 0 + */ + this.blending = NoBlending; + + this._useDash = parameters.dashed; + this._useAlphaToCoverage = true; + this._useWorldUnits = false; + + this.setValues( parameters ); + + } + + /** + * Setups the vertex and fragment stage of this node material. + * + * @param {NodeBuilder} builder - The current node builder. + */ + setup( builder ) { + + const { renderer } = builder; + + const useAlphaToCoverage = this._useAlphaToCoverage; + const useColor = this.useColor; + const useDash = this._useDash; + const useWorldUnits = this._useWorldUnits; + + const trimSegment = Fn( ( { start, end } ) => { + + const a = cameraProjectionMatrix.element( 2 ).element( 2 ); // 3nd entry in 3th column + const b = cameraProjectionMatrix.element( 3 ).element( 2 ); // 3nd entry in 4th column + const nearEstimate = b.mul( -0.5 ).div( a ); + + const alpha = nearEstimate.sub( start.z ).div( end.z.sub( start.z ) ); + + return vec4( mix( start.xyz, end.xyz, alpha ), end.w ); + + } ).setLayout( { + name: 'trimSegment', + type: 'vec4', + inputs: [ + { name: 'start', type: 'vec4' }, + { name: 'end', type: 'vec4' } + ] + } ); + + this.vertexNode = Fn( () => { + + const instanceStart = attribute( 'instanceStart' ); + const instanceEnd = attribute( 'instanceEnd' ); + + // camera space + + const start = vec4( modelViewMatrix.mul( vec4( instanceStart, 1.0 ) ) ).toVar( 'start' ); + const end = vec4( modelViewMatrix.mul( vec4( instanceEnd, 1.0 ) ) ).toVar( 'end' ); + + if ( useDash ) { + + const dashScaleNode = this.dashScaleNode ? float( this.dashScaleNode ) : materialLineScale; + const offsetNode = this.offsetNode ? float( this.offsetNode ) : materialLineDashOffset; + + const instanceDistanceStart = attribute( 'instanceDistanceStart' ); + const instanceDistanceEnd = attribute( 'instanceDistanceEnd' ); + + let lineDistance = positionGeometry.y.lessThan( 0.5 ).select( dashScaleNode.mul( instanceDistanceStart ), dashScaleNode.mul( instanceDistanceEnd ) ); + lineDistance = lineDistance.add( offsetNode ); + + varyingProperty( 'float', 'lineDistance' ).assign( lineDistance ); + + } + + if ( useWorldUnits ) { + + varyingProperty( 'vec3', 'worldStart' ).assign( start.xyz ); + varyingProperty( 'vec3', 'worldEnd' ).assign( end.xyz ); + + } + + const aspect = viewport.z.div( viewport.w ); + + // special case for perspective projection, and segments that terminate either in, or behind, the camera plane + // clearly the gpu firmware has a way of addressing this issue when projecting into ndc space + // but we need to perform ndc-space calculations in the shader, so we must address this issue directly + // perhaps there is a more elegant solution -- WestLangley + + const perspective = cameraProjectionMatrix.element( 2 ).element( 3 ).equal( -1 ); // 4th entry in the 3rd column + + If( perspective, () => { + + If( start.z.lessThan( 0.0 ).and( end.z.greaterThan( 0.0 ) ), () => { + + end.assign( trimSegment( { start: start, end: end } ) ); + + } ).ElseIf( end.z.lessThan( 0.0 ).and( start.z.greaterThanEqual( 0.0 ) ), () => { + + start.assign( trimSegment( { start: end, end: start } ) ); + + } ); + + } ); + + // clip space + const clipStart = cameraProjectionMatrix.mul( start ); + const clipEnd = cameraProjectionMatrix.mul( end ); + + // ndc space + const ndcStart = clipStart.xyz.div( clipStart.w ); + const ndcEnd = clipEnd.xyz.div( clipEnd.w ); + + // direction + const dir = ndcEnd.xy.sub( ndcStart.xy ).toVar(); + + // account for clip-space aspect ratio + dir.x.assign( dir.x.mul( aspect ) ); + dir.assign( dir.normalize() ); + + const clip = vec4().toVar(); + + if ( useWorldUnits ) { + + // get the offset direction as perpendicular to the view vector + + const worldDir = end.xyz.sub( start.xyz ).normalize(); + const tmpFwd = mix( start.xyz, end.xyz, 0.5 ).normalize(); + const worldUp = worldDir.cross( tmpFwd ).normalize(); + const worldFwd = worldDir.cross( worldUp ); + + const worldPos = varyingProperty( 'vec4', 'worldPos' ); + + worldPos.assign( positionGeometry.y.lessThan( 0.5 ).select( start, end ) ); + + // height offset + const hw = materialLineWidth.mul( 0.5 ); + worldPos.addAssign( vec4( positionGeometry.x.lessThan( 0.0 ).select( worldUp.mul( hw ), worldUp.mul( hw ).negate() ), 0 ) ); + + // don't extend the line if we're rendering dashes because we + // won't be rendering the endcaps + if ( ! useDash ) { + + // cap extension + worldPos.addAssign( vec4( positionGeometry.y.lessThan( 0.5 ).select( worldDir.mul( hw ).negate(), worldDir.mul( hw ) ), 0 ) ); + + // add width to the box + worldPos.addAssign( vec4( worldFwd.mul( hw ), 0 ) ); + + // endcaps + If( positionGeometry.y.greaterThan( 1.0 ).or( positionGeometry.y.lessThan( 0.0 ) ), () => { + + worldPos.subAssign( vec4( worldFwd.mul( 2.0 ).mul( hw ), 0 ) ); + + } ); + + } + + // project the worldpos + clip.assign( cameraProjectionMatrix.mul( worldPos ) ); + + // shift the depth of the projected points so the line + // segments overlap neatly + const clipPose = vec3().toVar(); + + clipPose.assign( positionGeometry.y.lessThan( 0.5 ).select( ndcStart, ndcEnd ) ); + clip.z.assign( clipPose.z.mul( clip.w ) ); + + } else { + + const offset = vec2( dir.y, dir.x.negate() ).toVar( 'offset' ); + + // undo aspect ratio adjustment + dir.x.assign( dir.x.div( aspect ) ); + offset.x.assign( offset.x.div( aspect ) ); + + // sign flip + offset.assign( positionGeometry.x.lessThan( 0.0 ).select( offset.negate(), offset ) ); + + // endcaps + If( positionGeometry.y.lessThan( 0.0 ), () => { + + offset.assign( offset.sub( dir ) ); + + } ).ElseIf( positionGeometry.y.greaterThan( 1.0 ), () => { + + offset.assign( offset.add( dir ) ); + + } ); + + // adjust for linewidth + offset.assign( offset.mul( materialLineWidth ) ); + + // adjust for clip-space to screen-space conversion // maybe resolution should be based on viewport ... + offset.assign( offset.div( viewport.w ) ); + + // select end + clip.assign( positionGeometry.y.lessThan( 0.5 ).select( clipStart, clipEnd ) ); + + // back to clip space + offset.assign( offset.mul( clip.w ) ); + + clip.assign( clip.add( vec4( offset, 0, 0 ) ) ); + + } + + return clip; + + } )(); + + const closestLineToLine = Fn( ( { p1, p2, p3, p4 } ) => { + + const p13 = p1.sub( p3 ); + const p43 = p4.sub( p3 ); + + const p21 = p2.sub( p1 ); + + const d1343 = p13.dot( p43 ); + const d4321 = p43.dot( p21 ); + const d1321 = p13.dot( p21 ); + const d4343 = p43.dot( p43 ); + const d2121 = p21.dot( p21 ); + + const denom = d2121.mul( d4343 ).sub( d4321.mul( d4321 ) ); + const numer = d1343.mul( d4321 ).sub( d1321.mul( d4343 ) ); + + const mua = numer.div( denom ).clamp(); + const mub = d1343.add( d4321.mul( mua ) ).div( d4343 ).clamp(); + + return vec2( mua, mub ); + + } ); + + this.colorNode = Fn( () => { + + const vUv = uv$1(); + + if ( useDash ) { + + const dashSizeNode = this.dashSizeNode ? float( this.dashSizeNode ) : materialLineDashSize; + const gapSizeNode = this.gapSizeNode ? float( this.gapSizeNode ) : materialLineGapSize; + + dashSize.assign( dashSizeNode ); + gapSize.assign( gapSizeNode ); + + const vLineDistance = varyingProperty( 'float', 'lineDistance' ); + + vUv.y.lessThan( -1 ).or( vUv.y.greaterThan( 1.0 ) ).discard(); // discard endcaps + vLineDistance.mod( dashSize.add( gapSize ) ).greaterThan( dashSize ).discard(); // todo - FIX + + } + + const alpha = float( 1 ).toVar( 'alpha' ); + + if ( useWorldUnits ) { + + const worldStart = varyingProperty( 'vec3', 'worldStart' ); + const worldEnd = varyingProperty( 'vec3', 'worldEnd' ); + + // Find the closest points on the view ray and the line segment + const rayEnd = varyingProperty( 'vec4', 'worldPos' ).xyz.normalize().mul( 1e5 ); + const lineDir = worldEnd.sub( worldStart ); + const params = closestLineToLine( { p1: worldStart, p2: worldEnd, p3: vec3( 0.0, 0.0, 0.0 ), p4: rayEnd } ); + + const p1 = worldStart.add( lineDir.mul( params.x ) ); + const p2 = rayEnd.mul( params.y ); + const delta = p1.sub( p2 ); + const len = delta.length(); + const norm = len.div( materialLineWidth ); + + if ( ! useDash ) { + + if ( useAlphaToCoverage && renderer.samples > 1 ) { + + const dnorm = norm.fwidth(); + alpha.assign( smoothstep( dnorm.negate().add( 0.5 ), dnorm.add( 0.5 ), norm ).oneMinus() ); + + } else { + + norm.greaterThan( 0.5 ).discard(); + + } + + } + + } else { + + // round endcaps + + if ( useAlphaToCoverage && renderer.samples > 1 ) { + + const a = vUv.x; + const b = vUv.y.greaterThan( 0.0 ).select( vUv.y.sub( 1.0 ), vUv.y.add( 1.0 ) ); + + const len2 = a.mul( a ).add( b.mul( b ) ); + + const dlen = float( len2.fwidth() ).toVar( 'dlen' ); + + If( vUv.y.abs().greaterThan( 1.0 ), () => { + + alpha.assign( smoothstep( dlen.oneMinus(), dlen.add( 1 ), len2 ).oneMinus() ); + + } ); + + } else { + + If( vUv.y.abs().greaterThan( 1.0 ), () => { + + const a = vUv.x; + const b = vUv.y.greaterThan( 0.0 ).select( vUv.y.sub( 1.0 ), vUv.y.add( 1.0 ) ); + const len2 = a.mul( a ).add( b.mul( b ) ); + + len2.greaterThan( 1.0 ).discard(); + + } ); + + } + + } + + let lineColorNode; + + if ( this.lineColorNode ) { + + lineColorNode = this.lineColorNode; + + } else { + + if ( useColor ) { + + const instanceColorStart = attribute( 'instanceColorStart' ); + const instanceColorEnd = attribute( 'instanceColorEnd' ); + + const instanceColor = positionGeometry.y.lessThan( 0.5 ).select( instanceColorStart, instanceColorEnd ); + + lineColorNode = instanceColor.mul( materialColor ); + + } else { + + lineColorNode = materialColor; + + } + + } + + return vec4( lineColorNode, alpha ); + + } )(); + + if ( this.transparent ) { + + const opacityNode = this.opacityNode ? float( this.opacityNode ) : materialOpacity; + + this.outputNode = vec4( this.colorNode.rgb.mul( opacityNode ).add( viewportSharedTexture().rgb.mul( opacityNode.oneMinus() ) ), this.colorNode.a ); + + } + + super.setup( builder ); + + } + + /** + * Whether the lines should sized in world units or not. + * When set to `false` the unit is pixel. + * + * @type {boolean} + * @default false + */ + get worldUnits() { + + return this._useWorldUnits; + + } + + set worldUnits( value ) { + + if ( this._useWorldUnits !== value ) { + + this._useWorldUnits = value; + this.needsUpdate = true; + + } + + } + + /** + * Whether the lines should be dashed or not. + * + * @type {boolean} + * @default false + */ + get dashed() { + + return this._useDash; + + } + + set dashed( value ) { + + if ( this._useDash !== value ) { + + this._useDash = value; + this.needsUpdate = true; + + } + + } + + /** + * Whether alpha to coverage should be used or not. + * + * @type {boolean} + * @default true + */ + get alphaToCoverage() { + + return this._useAlphaToCoverage; + + } + + set alphaToCoverage( value ) { + + if ( this._useAlphaToCoverage !== value ) { + + this._useAlphaToCoverage = value; + this.needsUpdate = true; + + } + + } + +} + +/** + * Packs a direction vector into a color value. + * + * @tsl + * @function + * @param {Node} node - The direction to pack. + * @return {Node} The color. + */ +const directionToColor = ( node ) => nodeObject( node ).mul( 0.5 ).add( 0.5 ); + +/** + * Unpacks a color value into a direction vector. + * + * @tsl + * @function + * @param {Node} node - The color to unpack. + * @return {Node} The direction. + */ +const colorToDirection = ( node ) => nodeObject( node ).mul( 2.0 ).sub( 1 ); + +const _defaultValues$a = /*@__PURE__*/ new MeshNormalMaterial(); + +/** + * Node material version of {@link MeshNormalMaterial}. + * + * @augments NodeMaterial + */ +class MeshNormalNodeMaterial extends NodeMaterial { + + static get type() { + + return 'MeshNormalNodeMaterial'; + + } + + /** + * Constructs a new mesh normal node material. + * + * @param {Object} [parameters] - The configuration parameter. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshNormalNodeMaterial = true; + + this.setDefaultValues( _defaultValues$a ); + + this.setValues( parameters ); + + } + + /** + * Overwrites the default implementation by computing the diffuse color + * based on the normal data. + */ + setupDiffuseColor() { + + const opacityNode = this.opacityNode ? float( this.opacityNode ) : materialOpacity; + + // By convention, a normal packed to RGB is in sRGB color space. Convert it to working color space. + + diffuseColor.assign( colorSpaceToWorking( vec4( directionToColor( normalView ), opacityNode ), SRGBColorSpace ) ); + + } + +} + +/** + * TSL function for creating an equirect uv node. + * + * Can be used to compute texture coordinates for projecting an + * equirectangular texture onto a mesh for using it as the scene's + * background. + * + * ```js + * scene.backgroundNode = texture( equirectTexture, equirectUV() ); + * ``` + * + * @tsl + * @function + * @param {?Node} [dirNode=positionWorldDirection] - A direction vector for sampling which is by default `positionWorldDirection`. + * @returns {Node} + */ +const equirectUV = /*@__PURE__*/ Fn( ( [ dir = positionWorldDirection ] ) => { + + const u = dir.z.atan( dir.x ).mul( 1 / ( Math.PI * 2 ) ).add( 0.5 ); + const v = dir.y.clamp( -1, 1.0 ).asin().mul( 1 / Math.PI ).add( 0.5 ); + + return vec2( u, v ); + +} ); + +// @TODO: Consider rename WebGLCubeRenderTarget to just CubeRenderTarget + +/** + * This class represents a cube render target. It is a special version + * of `WebGLCubeRenderTarget` which is compatible with `WebGPURenderer`. + * + * @augments WebGLCubeRenderTarget + */ +class CubeRenderTarget extends WebGLCubeRenderTarget { + + /** + * Constructs a new cube render target. + * + * @param {number} [size=1] - The size of the render target. + * @param {RenderTarget~Options} [options] - The configuration object. + */ + constructor( size = 1, options = {} ) { + + super( size, options ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isCubeRenderTarget = true; + + } + + /** + * Converts the given equirectangular texture to a cube map. + * + * @param {Renderer} renderer - The renderer. + * @param {Texture} texture - The equirectangular texture. + * @return {CubeRenderTarget} A reference to this cube render target. + */ + fromEquirectangularTexture( renderer, texture$1 ) { + + const currentMinFilter = texture$1.minFilter; + const currentGenerateMipmaps = texture$1.generateMipmaps; + + texture$1.generateMipmaps = true; + + this.texture.type = texture$1.type; + this.texture.colorSpace = texture$1.colorSpace; + + this.texture.generateMipmaps = texture$1.generateMipmaps; + this.texture.minFilter = texture$1.minFilter; + this.texture.magFilter = texture$1.magFilter; + + const geometry = new BoxGeometry( 5, 5, 5 ); + + const uvNode = equirectUV( positionWorldDirection ); + + const material = new NodeMaterial(); + material.colorNode = texture( texture$1, uvNode, 0 ); + material.side = BackSide; + material.blending = NoBlending; + + const mesh = new Mesh( geometry, material ); + + const scene = new Scene(); + scene.add( mesh ); + + // Avoid blurred poles + if ( texture$1.minFilter === LinearMipmapLinearFilter ) texture$1.minFilter = LinearFilter; + + const camera = new CubeCamera( 1, 10, this ); + + const currentMRT = renderer.getMRT(); + renderer.setMRT( null ); + + camera.update( renderer, scene ); + + renderer.setMRT( currentMRT ); + + texture$1.minFilter = currentMinFilter; + texture$1.currentGenerateMipmaps = currentGenerateMipmaps; + + mesh.geometry.dispose(); + mesh.material.dispose(); + + return this; + + } + +} + +const _cache$1 = new WeakMap(); + +/** + * This node can be used to automatically convert environment maps in the + * equirectangular format into the cube map format. + * + * @augments TempNode + */ +class CubeMapNode extends TempNode { + + static get type() { + + return 'CubeMapNode'; + + } + + /** + * Constructs a new cube map node. + * + * @param {Node} envNode - The node representing the environment map. + */ + constructor( envNode ) { + + super( 'vec3' ); + + /** + * The node representing the environment map. + * + * @type {Node} + */ + this.envNode = envNode; + + /** + * A reference to the internal cube texture. + * + * @private + * @type {?CubeTexture} + * @default null + */ + this._cubeTexture = null; + + /** + * A reference to the internal cube texture node. + * + * @private + * @type {CubeTextureNode} + */ + this._cubeTextureNode = cubeTexture( null ); + + const defaultTexture = new CubeTexture(); + defaultTexture.isRenderTargetTexture = true; + + /** + * A default cube texture that acts as a placeholder. + * It is used when the conversion from equirectangular to cube + * map has not finished yet for a given texture. + * + * @private + * @type {CubeTexture} + */ + this._defaultTexture = defaultTexture; + + /** + * The `updateBeforeType` is set to `NodeUpdateType.RENDER` since the node updates + * the texture once per render in its {@link CubeMapNode#updateBefore} method. + * + * @type {string} + * @default 'render' + */ + this.updateBeforeType = NodeUpdateType.RENDER; + + } + + updateBefore( frame ) { + + const { renderer, material } = frame; + + const envNode = this.envNode; + + if ( envNode.isTextureNode || envNode.isMaterialReferenceNode ) { + + const texture = ( envNode.isTextureNode ) ? envNode.value : material[ envNode.property ]; + + if ( texture && texture.isTexture ) { + + const mapping = texture.mapping; + + if ( mapping === EquirectangularReflectionMapping || mapping === EquirectangularRefractionMapping ) { + + // check for converted cubemap map + + if ( _cache$1.has( texture ) ) { + + const cubeMap = _cache$1.get( texture ); + + mapTextureMapping( cubeMap, texture.mapping ); + this._cubeTexture = cubeMap; + + } else { + + // create cube map from equirectangular map + + const image = texture.image; + + if ( isEquirectangularMapReady$1( image ) ) { + + const renderTarget = new CubeRenderTarget( image.height ); + renderTarget.fromEquirectangularTexture( renderer, texture ); + + mapTextureMapping( renderTarget.texture, texture.mapping ); + this._cubeTexture = renderTarget.texture; + + _cache$1.set( texture, renderTarget.texture ); + + texture.addEventListener( 'dispose', onTextureDispose ); + + } else { + + // default cube texture as fallback when equirectangular texture is not yet loaded + + this._cubeTexture = this._defaultTexture; + + } + + } + + // + + this._cubeTextureNode.value = this._cubeTexture; + + } else { + + // envNode already refers to a cube map + + this._cubeTextureNode = this.envNode; + + } + + } + + } + + } + + setup( builder ) { + + this.updateBefore( builder ); + + return this._cubeTextureNode; + + } + +} + +/** + * Returns true if the given equirectangular image has been fully loaded + * and is ready for further processing. + * + * @private + * @param {Image} image - The equirectangular image to check. + * @return {boolean} Whether the image is ready or not. + */ +function isEquirectangularMapReady$1( image ) { + + if ( image === null || image === undefined ) return false; + + return image.height > 0; + +} + +/** + * This function is executed when `dispose()` is called on the equirectangular + * texture. In this case, the generated cube map with its render target + * is deleted as well. + * + * @private + * @param {Object} event - The event object. + */ +function onTextureDispose( event ) { + + const texture = event.target; + + texture.removeEventListener( 'dispose', onTextureDispose ); + + const renderTarget = _cache$1.get( texture ); + + if ( renderTarget !== undefined ) { + + _cache$1.delete( texture ); + + renderTarget.dispose(); + + } + +} + +/** + * This function makes sure the generated cube map uses the correct + * texture mapping that corresponds to the equirectangular original. + * + * @private + * @param {Texture} texture - The cube texture. + * @param {number} mapping - The original texture mapping. + */ +function mapTextureMapping( texture, mapping ) { + + if ( mapping === EquirectangularReflectionMapping ) { + + texture.mapping = CubeReflectionMapping; + + } else if ( mapping === EquirectangularRefractionMapping ) { + + texture.mapping = CubeRefractionMapping; + + } + +} + +/** + * TSL function for creating a cube map node. + * + * @tsl + * @function + * @param {Node} envNode - The node representing the environment map. + * @returns {CubeMapNode} + */ +const cubeMapNode = /*@__PURE__*/ nodeProxy( CubeMapNode ).setParameterLength( 1 ); + +/** + * Represents a basic model for Image-based lighting (IBL). The environment + * is defined via environment maps in the equirectangular or cube map format. + * `BasicEnvironmentNode` is intended for non-PBR materials like {@link MeshBasicNodeMaterial} + * or {@link MeshPhongNodeMaterial}. + * + * @augments LightingNode + */ +class BasicEnvironmentNode extends LightingNode { + + static get type() { + + return 'BasicEnvironmentNode'; + + } + + /** + * Constructs a new basic environment node. + * + * @param {Node} [envNode=null] - A node representing the environment. + */ + constructor( envNode = null ) { + + super(); + + /** + * A node representing the environment. + * + * @type {Node} + * @default null + */ + this.envNode = envNode; + + } + + setup( builder ) { + + // environment property is used in the finish() method of BasicLightingModel + + builder.context.environment = cubeMapNode( this.envNode ); + + } + +} + +/** + * A specific version of {@link IrradianceNode} that is only relevant + * for {@link MeshBasicNodeMaterial}. Since the material is unlit, it + * requires a special scaling factor for the light map. + * + * @augments LightingNode + */ +class BasicLightMapNode extends LightingNode { + + static get type() { + + return 'BasicLightMapNode'; + + } + + /** + * Constructs a new basic light map node. + * + * @param {?Node} [lightMapNode=null] - The light map node. + */ + constructor( lightMapNode = null ) { + + super(); + + /** + * The light map node. + * + * @type {?Node} + */ + this.lightMapNode = lightMapNode; + + } + + setup( builder ) { + + // irradianceLightMap property is used in the indirectDiffuse() method of BasicLightingModel + + const RECIPROCAL_PI = float( 1 / Math.PI ); + + builder.context.irradianceLightMap = this.lightMapNode.mul( RECIPROCAL_PI ); + + } + +} + +/** + * Abstract class for implementing lighting models. The module defines + * multiple methods that concrete lighting models can implement. These + * methods are executed at different points during the light evaluation + * process. + */ +class LightingModel { + + /** + * This method is intended for setting up lighting model and context data + * which are later used in the evaluation process. + * + * @abstract + * @param {NodeBuilder} builder - The current node builder. + */ + start( builder ) { + + // lights ( direct ) + + builder.lightsNode.setupLights( builder, builder.lightsNode.getLightNodes( builder ) ); + + // indirect + + this.indirect( builder ); + + } + + /** + * This method is intended for executing final tasks like final updates + * to the outgoing light. + * + * @abstract + * @param {NodeBuilder} builder - The current node builder. + */ + finish( /*builder*/ ) { } + + /** + * This method is intended for implementing the direct light term and + * executed during the build process of directional, point and spot light nodes. + * + * @abstract + * @param {Object} lightData - The light data. + * @param {NodeBuilder} builder - The current node builder. + */ + direct( /*lightData, builder*/ ) { } + + /** + * This method is intended for implementing the direct light term for + * rect area light nodes. + * + * @abstract + * @param {Object} lightData - The light data. + * @param {NodeBuilder} builder - The current node builder. + */ + directRectArea( /*lightData, builder*/ ) {} + + /** + * This method is intended for implementing the indirect light term. + * + * @abstract + * @param {NodeBuilder} builder - The current node builder. + */ + indirect( /*builder*/ ) { } + + /** + * This method is intended for implementing the ambient occlusion term. + * Unlike other methods, this method must be called manually by the lighting + * model in its indirect term. + * + * @abstract + * @param {NodeBuilder} builder - The current node builder. + */ + ambientOcclusion( /*input, stack, builder*/ ) { } + +} + +/** + * Represents the lighting model for unlit materials. The only light contribution + * is baked indirect lighting modulated with ambient occlusion and the material's + * diffuse color. Environment mapping is supported. Used in {@link MeshBasicNodeMaterial}. + * + * @augments LightingModel + */ +class BasicLightingModel extends LightingModel { + + /** + * Constructs a new basic lighting model. + */ + constructor() { + + super(); + + } + + /** + * Implements the baked indirect lighting with its modulation. + * + * @param {NodeBuilder} builder - The current node builder. + */ + indirect( { context } ) { + + const ambientOcclusion = context.ambientOcclusion; + const reflectedLight = context.reflectedLight; + const irradianceLightMap = context.irradianceLightMap; + + reflectedLight.indirectDiffuse.assign( vec4( 0.0 ) ); + + // accumulation (baked indirect lighting only) + + if ( irradianceLightMap ) { + + reflectedLight.indirectDiffuse.addAssign( irradianceLightMap ); + + } else { + + reflectedLight.indirectDiffuse.addAssign( vec4( 1.0, 1.0, 1.0, 0.0 ) ); + + } + + // modulation + + reflectedLight.indirectDiffuse.mulAssign( ambientOcclusion ); + + reflectedLight.indirectDiffuse.mulAssign( diffuseColor.rgb ); + + } + + /** + * Implements the environment mapping. + * + * @param {NodeBuilder} builder - The current node builder. + */ + finish( builder ) { + + const { material, context } = builder; + + const outgoingLight = context.outgoingLight; + const envNode = builder.context.environment; + + if ( envNode ) { + + switch ( material.combine ) { + + case MultiplyOperation: + outgoingLight.rgb.assign( mix( outgoingLight.rgb, outgoingLight.rgb.mul( envNode.rgb ), materialSpecularStrength.mul( materialReflectivity ) ) ); + break; + + case MixOperation: + outgoingLight.rgb.assign( mix( outgoingLight.rgb, envNode.rgb, materialSpecularStrength.mul( materialReflectivity ) ) ); + break; + + case AddOperation: + outgoingLight.rgb.addAssign( envNode.rgb.mul( materialSpecularStrength.mul( materialReflectivity ) ) ); + break; + + default: + console.warn( 'THREE.BasicLightingModel: Unsupported .combine value:', material.combine ); + break; + + } + + } + + } + +} + +const _defaultValues$9 = /*@__PURE__*/ new MeshBasicMaterial(); + +/** + * Node material version of {@link MeshBasicMaterial}. + * + * @augments NodeMaterial + */ +class MeshBasicNodeMaterial extends NodeMaterial { + + static get type() { + + return 'MeshBasicNodeMaterial'; + + } + + /** + * Constructs a new mesh basic node material. + * + * @param {Object} [parameters] - The configuration parameter. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshBasicNodeMaterial = true; + + /** + * Although the basic material is by definition unlit, we set + * this property to `true` since we use a lighting model to compute + * the outgoing light of the fragment shader. + * + * @type {boolean} + * @default true + */ + this.lights = true; + + this.setDefaultValues( _defaultValues$9 ); + + this.setValues( parameters ); + + } + + /** + * Basic materials are not affected by normal and bump maps so we + * return by default {@link normalViewGeometry}. + * + * @return {Node} The normal node. + */ + setupNormal() { + + return directionToFaceDirection( normalViewGeometry ); // see #28839 + + } + + /** + * Overwritten since this type of material uses {@link BasicEnvironmentNode} + * to implement the default environment mapping. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {?BasicEnvironmentNode} The environment node. + */ + setupEnvironment( builder ) { + + const envNode = super.setupEnvironment( builder ); + + return envNode ? new BasicEnvironmentNode( envNode ) : null; + + } + + /** + * This method must be overwritten since light maps are evaluated + * with a special scaling factor for basic materials. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {?BasicLightMapNode} The light map node. + */ + setupLightMap( builder ) { + + let node = null; + + if ( builder.material.lightMap ) { + + node = new BasicLightMapNode( materialLightMap ); + + } + + return node; + + } + + /** + * The material overwrites this method because `lights` is set to `true` but + * we still want to return the diffuse color as the outgoing light. + * + * @return {Node} The outgoing light node. + */ + setupOutgoingLight() { + + return diffuseColor.rgb; + + } + + /** + * Setups the lighting model. + * + * @return {BasicLightingModel} The lighting model. + */ + setupLightingModel() { + + return new BasicLightingModel(); + + } + +} + +const F_Schlick = /*@__PURE__*/ Fn( ( { f0, f90, dotVH } ) => { + + // Original approximation by Christophe Schlick '94 + // float fresnel = pow( 1.0 - dotVH, 5.0 ); + + // Optimized variant (presented by Epic at SIGGRAPH '13) + // https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf + const fresnel = dotVH.mul( -5.55473 ).sub( 6.98316 ).mul( dotVH ).exp2(); + + return f0.mul( fresnel.oneMinus() ).add( f90.mul( fresnel ) ); + +} ); // validated + +const BRDF_Lambert = /*@__PURE__*/ Fn( ( inputs ) => { + + return inputs.diffuseColor.mul( 1 / Math.PI ); // punctual light + +} ); // validated + +const G_BlinnPhong_Implicit = () => float( 0.25 ); + +const D_BlinnPhong = /*@__PURE__*/ Fn( ( { dotNH } ) => { + + return shininess.mul( float( 0.5 ) ).add( 1.0 ).mul( float( 1 / Math.PI ) ).mul( dotNH.pow( shininess ) ); + +} ); + +const BRDF_BlinnPhong = /*@__PURE__*/ Fn( ( { lightDirection } ) => { + + const halfDir = lightDirection.add( positionViewDirection ).normalize(); + + const dotNH = normalView.dot( halfDir ).clamp(); + const dotVH = positionViewDirection.dot( halfDir ).clamp(); + + const F = F_Schlick( { f0: specularColor, f90: 1.0, dotVH } ); + const G = G_BlinnPhong_Implicit(); + const D = D_BlinnPhong( { dotNH } ); + + return F.mul( G ).mul( D ); + +} ); + +/** + * Represents the lighting model for a phong material. Used in {@link MeshPhongNodeMaterial}. + * + * @augments BasicLightingModel + */ +class PhongLightingModel extends BasicLightingModel { + + /** + * Constructs a new phong lighting model. + * + * @param {boolean} [specular=true] - Whether specular is supported or not. + */ + constructor( specular = true ) { + + super(); + + /** + * Whether specular is supported or not. Set this to `false` if you are + * looking for a Lambert-like material meaning a material for non-shiny + * surfaces, without specular highlights. + * + * @type {boolean} + * @default true + */ + this.specular = specular; + + } + + /** + * Implements the direct lighting. The specular portion is optional an can be controlled + * with the {@link PhongLightingModel#specular} flag. + * + * @param {Object} lightData - The light data. + */ + direct( { lightDirection, lightColor, reflectedLight } ) { + + const dotNL = normalView.dot( lightDirection ).clamp(); + const irradiance = dotNL.mul( lightColor ); + + reflectedLight.directDiffuse.addAssign( irradiance.mul( BRDF_Lambert( { diffuseColor: diffuseColor.rgb } ) ) ); + + if ( this.specular === true ) { + + reflectedLight.directSpecular.addAssign( irradiance.mul( BRDF_BlinnPhong( { lightDirection } ) ).mul( materialSpecularStrength ) ); + + } + + } + + /** + * Implements the indirect lighting. + * + * @param {NodeBuilder} builder - The current node builder. + */ + indirect( builder ) { + + const { ambientOcclusion, irradiance, reflectedLight } = builder.context; + + reflectedLight.indirectDiffuse.addAssign( irradiance.mul( BRDF_Lambert( { diffuseColor } ) ) ); + + reflectedLight.indirectDiffuse.mulAssign( ambientOcclusion ); + + } + +} + +const _defaultValues$8 = /*@__PURE__*/ new MeshLambertMaterial(); + +/** + * Node material version of {@link MeshLambertMaterial}. + * + * @augments NodeMaterial + */ +class MeshLambertNodeMaterial extends NodeMaterial { + + static get type() { + + return 'MeshLambertNodeMaterial'; + + } + + /** + * Constructs a new mesh lambert node material. + * + * @param {Object} [parameters] - The configuration parameter. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshLambertNodeMaterial = true; + + /** + * Set to `true` because lambert materials react on lights. + * + * @type {boolean} + * @default true + */ + this.lights = true; + + this.setDefaultValues( _defaultValues$8 ); + + this.setValues( parameters ); + + } + + /** + * Overwritten since this type of material uses {@link BasicEnvironmentNode} + * to implement the default environment mapping. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {?BasicEnvironmentNode} The environment node. + */ + setupEnvironment( builder ) { + + const envNode = super.setupEnvironment( builder ); + + return envNode ? new BasicEnvironmentNode( envNode ) : null; + + } + + /** + * Setups the lighting model. + * + * @return {PhongLightingModel} The lighting model. + */ + setupLightingModel( /*builder*/ ) { + + return new PhongLightingModel( false ); // ( specular ) -> force lambert + + } + +} + +const _defaultValues$7 = /*@__PURE__*/ new MeshPhongMaterial(); + +/** + * Node material version of {@link MeshPhongMaterial}. + * + * @augments NodeMaterial + */ +class MeshPhongNodeMaterial extends NodeMaterial { + + static get type() { + + return 'MeshPhongNodeMaterial'; + + } + + /** + * Constructs a new mesh lambert node material. + * + * @param {Object} [parameters] - The configuration parameter. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshPhongNodeMaterial = true; + + /** + * Set to `true` because phong materials react on lights. + * + * @type {boolean} + * @default true + */ + this.lights = true; + + /** + * The shininess of phong materials is by default inferred from the `shininess` + * property. This node property allows to overwrite the default + * and define the shininess with a node instead. + * + * If you don't want to overwrite the shininess but modify the existing + * value instead, use {@link materialShininess}. + * + * @type {?Node} + * @default null + */ + this.shininessNode = null; + + /** + * The specular color of phong materials is by default inferred from the + * `specular` property. This node property allows to overwrite the default + * and define the specular color with a node instead. + * + * If you don't want to overwrite the specular color but modify the existing + * value instead, use {@link materialSpecular}. + * + * @type {?Node} + * @default null + */ + this.specularNode = null; + + this.setDefaultValues( _defaultValues$7 ); + + this.setValues( parameters ); + + } + + /** + * Overwritten since this type of material uses {@link BasicEnvironmentNode} + * to implement the default environment mapping. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {?BasicEnvironmentNode} The environment node. + */ + setupEnvironment( builder ) { + + const envNode = super.setupEnvironment( builder ); + + return envNode ? new BasicEnvironmentNode( envNode ) : null; + + } + + /** + * Setups the lighting model. + * + * @return {PhongLightingModel} The lighting model. + */ + setupLightingModel( /*builder*/ ) { + + return new PhongLightingModel(); + + } + + /** + * Setups the phong specific node variables. + * + * @param {NodeBuilder} builder - The current node builder. + */ + setupVariants( /*builder*/ ) { + + // SHININESS + + const shininessNode = ( this.shininessNode ? float( this.shininessNode ) : materialShininess ).max( 1e-4 ); // to prevent pow( 0.0, 0.0 ) + + shininess.assign( shininessNode ); + + // SPECULAR COLOR + + const specularNode = this.specularNode || materialSpecular; + + specularColor.assign( specularNode ); + + } + + copy( source ) { + + this.shininessNode = source.shininessNode; + this.specularNode = source.specularNode; + + return super.copy( source ); + + } + +} + +const getGeometryRoughness = /*@__PURE__*/ Fn( ( builder ) => { + + if ( builder.geometry.hasAttribute( 'normal' ) === false ) { + + return float( 0 ); + + } + + const dxy = normalViewGeometry.dFdx().abs().max( normalViewGeometry.dFdy().abs() ); + const geometryRoughness = dxy.x.max( dxy.y ).max( dxy.z ); + + return geometryRoughness; + +} ); + +const getRoughness = /*@__PURE__*/ Fn( ( inputs ) => { + + const { roughness } = inputs; + + const geometryRoughness = getGeometryRoughness(); + + let roughnessFactor = roughness.max( 0.0525 ); // 0.0525 corresponds to the base mip of a 256 cubemap. + roughnessFactor = roughnessFactor.add( geometryRoughness ); + roughnessFactor = roughnessFactor.min( 1.0 ); + + return roughnessFactor; + +} ); + +// Moving Frostbite to Physically Based Rendering 3.0 - page 12, listing 2 +// https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf +const V_GGX_SmithCorrelated = /*@__PURE__*/ Fn( ( { alpha, dotNL, dotNV } ) => { + + const a2 = alpha.pow2(); + + const gv = dotNL.mul( a2.add( a2.oneMinus().mul( dotNV.pow2() ) ).sqrt() ); + const gl = dotNV.mul( a2.add( a2.oneMinus().mul( dotNL.pow2() ) ).sqrt() ); + + return div( 0.5, gv.add( gl ).max( EPSILON ) ); + +} ).setLayout( { + name: 'V_GGX_SmithCorrelated', + type: 'float', + inputs: [ + { name: 'alpha', type: 'float' }, + { name: 'dotNL', type: 'float' }, + { name: 'dotNV', type: 'float' } + ] +} ); // validated + +// https://google.github.io/filament/Filament.md.html#materialsystem/anisotropicmodel/anisotropicspecularbrdf + +const V_GGX_SmithCorrelated_Anisotropic = /*@__PURE__*/ Fn( ( { alphaT, alphaB, dotTV, dotBV, dotTL, dotBL, dotNV, dotNL } ) => { + + const gv = dotNL.mul( vec3( alphaT.mul( dotTV ), alphaB.mul( dotBV ), dotNV ).length() ); + const gl = dotNV.mul( vec3( alphaT.mul( dotTL ), alphaB.mul( dotBL ), dotNL ).length() ); + const v = div( 0.5, gv.add( gl ) ); + + return v.saturate(); + +} ).setLayout( { + name: 'V_GGX_SmithCorrelated_Anisotropic', + type: 'float', + inputs: [ + { name: 'alphaT', type: 'float', qualifier: 'in' }, + { name: 'alphaB', type: 'float', qualifier: 'in' }, + { name: 'dotTV', type: 'float', qualifier: 'in' }, + { name: 'dotBV', type: 'float', qualifier: 'in' }, + { name: 'dotTL', type: 'float', qualifier: 'in' }, + { name: 'dotBL', type: 'float', qualifier: 'in' }, + { name: 'dotNV', type: 'float', qualifier: 'in' }, + { name: 'dotNL', type: 'float', qualifier: 'in' } + ] +} ); + +// Microfacet Models for Refraction through Rough Surfaces - equation (33) +// http://graphicrants.blogspot.com/2013/08/specular-brdf-reference.html +// alpha is "roughness squared" in Disney’s reparameterization +const D_GGX = /*@__PURE__*/ Fn( ( { alpha, dotNH } ) => { + + const a2 = alpha.pow2(); + + const denom = dotNH.pow2().mul( a2.oneMinus() ).oneMinus(); // avoid alpha = 0 with dotNH = 1 + + return a2.div( denom.pow2() ).mul( 1 / Math.PI ); + +} ).setLayout( { + name: 'D_GGX', + type: 'float', + inputs: [ + { name: 'alpha', type: 'float' }, + { name: 'dotNH', type: 'float' } + ] +} ); // validated + +const RECIPROCAL_PI = /*@__PURE__*/ float( 1 / Math.PI ); + +// https://google.github.io/filament/Filament.md.html#materialsystem/anisotropicmodel/anisotropicspecularbrdf + +const D_GGX_Anisotropic = /*@__PURE__*/ Fn( ( { alphaT, alphaB, dotNH, dotTH, dotBH } ) => { + + const a2 = alphaT.mul( alphaB ); + const v = vec3( alphaB.mul( dotTH ), alphaT.mul( dotBH ), a2.mul( dotNH ) ); + const v2 = v.dot( v ); + const w2 = a2.div( v2 ); + + return RECIPROCAL_PI.mul( a2.mul( w2.pow2() ) ); + +} ).setLayout( { + name: 'D_GGX_Anisotropic', + type: 'float', + inputs: [ + { name: 'alphaT', type: 'float', qualifier: 'in' }, + { name: 'alphaB', type: 'float', qualifier: 'in' }, + { name: 'dotNH', type: 'float', qualifier: 'in' }, + { name: 'dotTH', type: 'float', qualifier: 'in' }, + { name: 'dotBH', type: 'float', qualifier: 'in' } + ] +} ); + +// GGX Distribution, Schlick Fresnel, GGX_SmithCorrelated Visibility +const BRDF_GGX = /*@__PURE__*/ Fn( ( { lightDirection, f0, f90, roughness, f, normalView: normalView$1 = normalView, USE_IRIDESCENCE, USE_ANISOTROPY } ) => { + + const alpha = roughness.pow2(); // UE4's roughness + + const halfDir = lightDirection.add( positionViewDirection ).normalize(); + + const dotNL = normalView$1.dot( lightDirection ).clamp(); + const dotNV = normalView$1.dot( positionViewDirection ).clamp(); // @ TODO: Move to core dotNV + const dotNH = normalView$1.dot( halfDir ).clamp(); + const dotVH = positionViewDirection.dot( halfDir ).clamp(); + + let F = F_Schlick( { f0, f90, dotVH } ); + let V, D; + + if ( defined( USE_IRIDESCENCE ) ) { + + F = iridescence.mix( F, f ); + + } + + if ( defined( USE_ANISOTROPY ) ) { + + const dotTL = anisotropyT.dot( lightDirection ); + const dotTV = anisotropyT.dot( positionViewDirection ); + const dotTH = anisotropyT.dot( halfDir ); + const dotBL = anisotropyB.dot( lightDirection ); + const dotBV = anisotropyB.dot( positionViewDirection ); + const dotBH = anisotropyB.dot( halfDir ); + + V = V_GGX_SmithCorrelated_Anisotropic( { alphaT, alphaB: alpha, dotTV, dotBV, dotTL, dotBL, dotNV, dotNL } ); + D = D_GGX_Anisotropic( { alphaT, alphaB: alpha, dotNH, dotTH, dotBH } ); + + } else { + + V = V_GGX_SmithCorrelated( { alpha, dotNL, dotNV } ); + D = D_GGX( { alpha, dotNH } ); + + } + + return F.mul( V ).mul( D ); + +} ); // validated + +// Analytical approximation of the DFG LUT, one half of the +// split-sum approximation used in indirect specular lighting. +// via 'environmentBRDF' from "Physically Based Shading on Mobile" +// https://www.unrealengine.com/blog/physically-based-shading-on-mobile +const DFGApprox = /*@__PURE__*/ Fn( ( { roughness, dotNV } ) => { + + const c0 = vec4( -1, -0.0275, -0.572, 0.022 ); + + const c1 = vec4( 1, 0.0425, 1.04, -0.04 ); + + const r = roughness.mul( c0 ).add( c1 ); + + const a004 = r.x.mul( r.x ).min( dotNV.mul( -9.28 ).exp2() ).mul( r.x ).add( r.y ); + + const fab = vec2( -1.04, 1.04 ).mul( a004 ).add( r.zw ); + + return fab; + +} ).setLayout( { + name: 'DFGApprox', + type: 'vec2', + inputs: [ + { name: 'roughness', type: 'float' }, + { name: 'dotNV', type: 'vec3' } + ] +} ); + +const EnvironmentBRDF = /*@__PURE__*/ Fn( ( inputs ) => { + + const { dotNV, specularColor, specularF90, roughness } = inputs; + + const fab = DFGApprox( { dotNV, roughness } ); + return specularColor.mul( fab.x ).add( specularF90.mul( fab.y ) ); + +} ); + +const Schlick_to_F0 = /*@__PURE__*/ Fn( ( { f, f90, dotVH } ) => { + + const x = dotVH.oneMinus().saturate(); + const x2 = x.mul( x ); + const x5 = x.mul( x2, x2 ).clamp( 0, .9999 ); + + return f.sub( vec3( f90 ).mul( x5 ) ).div( x5.oneMinus() ); + +} ).setLayout( { + name: 'Schlick_to_F0', + type: 'vec3', + inputs: [ + { name: 'f', type: 'vec3' }, + { name: 'f90', type: 'float' }, + { name: 'dotVH', type: 'float' } + ] +} ); + +// https://github.com/google/filament/blob/master/shaders/src/brdf.fs +const D_Charlie = /*@__PURE__*/ Fn( ( { roughness, dotNH } ) => { + + const alpha = roughness.pow2(); + + // Estevez and Kulla 2017, "Production Friendly Microfacet Sheen BRDF" + const invAlpha = float( 1.0 ).div( alpha ); + const cos2h = dotNH.pow2(); + const sin2h = cos2h.oneMinus().max( 0.0078125 ); // 2^(-14/2), so sin2h^2 > 0 in fp16 + + return float( 2.0 ).add( invAlpha ).mul( sin2h.pow( invAlpha.mul( 0.5 ) ) ).div( 2.0 * Math.PI ); + +} ).setLayout( { + name: 'D_Charlie', + type: 'float', + inputs: [ + { name: 'roughness', type: 'float' }, + { name: 'dotNH', type: 'float' } + ] +} ); + +// https://github.com/google/filament/blob/master/shaders/src/brdf.fs +const V_Neubelt = /*@__PURE__*/ Fn( ( { dotNV, dotNL } ) => { + + // Neubelt and Pettineo 2013, "Crafting a Next-gen Material Pipeline for The Order: 1886" + return float( 1.0 ).div( float( 4.0 ).mul( dotNL.add( dotNV ).sub( dotNL.mul( dotNV ) ) ) ); + +} ).setLayout( { + name: 'V_Neubelt', + type: 'float', + inputs: [ + { name: 'dotNV', type: 'float' }, + { name: 'dotNL', type: 'float' } + ] +} ); + +const BRDF_Sheen = /*@__PURE__*/ Fn( ( { lightDirection } ) => { + + const halfDir = lightDirection.add( positionViewDirection ).normalize(); + + const dotNL = normalView.dot( lightDirection ).clamp(); + const dotNV = normalView.dot( positionViewDirection ).clamp(); + const dotNH = normalView.dot( halfDir ).clamp(); + + const D = D_Charlie( { roughness: sheenRoughness, dotNH } ); + const V = V_Neubelt( { dotNV, dotNL } ); + + return sheen.mul( D ).mul( V ); + +} ); + +// Rect Area Light + +// Real-Time Polygonal-Light Shading with Linearly Transformed Cosines +// by Eric Heitz, Jonathan Dupuy, Stephen Hill and David Neubelt +// code: https://github.com/selfshadow/ltc_code/ + +const LTC_Uv = /*@__PURE__*/ Fn( ( { N, V, roughness } ) => { + + const LUT_SIZE = 64.0; + const LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE; + const LUT_BIAS = 0.5 / LUT_SIZE; + + const dotNV = N.dot( V ).saturate(); + + // texture parameterized by sqrt( GGX alpha ) and sqrt( 1 - cos( theta ) ) + const uv = vec2( roughness, dotNV.oneMinus().sqrt() ); + + uv.assign( uv.mul( LUT_SCALE ).add( LUT_BIAS ) ); + + return uv; + +} ).setLayout( { + name: 'LTC_Uv', + type: 'vec2', + inputs: [ + { name: 'N', type: 'vec3' }, + { name: 'V', type: 'vec3' }, + { name: 'roughness', type: 'float' } + ] +} ); + +const LTC_ClippedSphereFormFactor = /*@__PURE__*/ Fn( ( { f } ) => { + + // Real-Time Area Lighting: a Journey from Research to Production (p.102) + // An approximation of the form factor of a horizon-clipped rectangle. + + const l = f.length(); + + return max$1( l.mul( l ).add( f.z ).div( l.add( 1.0 ) ), 0 ); + +} ).setLayout( { + name: 'LTC_ClippedSphereFormFactor', + type: 'float', + inputs: [ + { name: 'f', type: 'vec3' } + ] +} ); + +const LTC_EdgeVectorFormFactor = /*@__PURE__*/ Fn( ( { v1, v2 } ) => { + + const x = v1.dot( v2 ); + const y = x.abs().toVar(); + + // rational polynomial approximation to theta / sin( theta ) / 2PI + const a = y.mul( 0.0145206 ).add( 0.4965155 ).mul( y ).add( 0.8543985 ).toVar(); + const b = y.add( 4.1616724 ).mul( y ).add( 3.4175940 ).toVar(); + const v = a.div( b ); + + const theta_sintheta = x.greaterThan( 0.0 ).select( v, max$1( x.mul( x ).oneMinus(), 1e-7 ).inverseSqrt().mul( 0.5 ).sub( v ) ); + + return v1.cross( v2 ).mul( theta_sintheta ); + +} ).setLayout( { + name: 'LTC_EdgeVectorFormFactor', + type: 'vec3', + inputs: [ + { name: 'v1', type: 'vec3' }, + { name: 'v2', type: 'vec3' } + ] +} ); + +const LTC_Evaluate = /*@__PURE__*/ Fn( ( { N, V, P, mInv, p0, p1, p2, p3 } ) => { + + // bail if point is on back side of plane of light + // assumes ccw winding order of light vertices + const v1 = p1.sub( p0 ).toVar(); + const v2 = p3.sub( p0 ).toVar(); + + const lightNormal = v1.cross( v2 ); + const result = vec3().toVar(); + + If( lightNormal.dot( P.sub( p0 ) ).greaterThanEqual( 0.0 ), () => { + + // construct orthonormal basis around N + const T1 = V.sub( N.mul( V.dot( N ) ) ).normalize(); + const T2 = N.cross( T1 ).negate(); // negated from paper; possibly due to a different handedness of world coordinate system + + // compute transform + const mat = mInv.mul( mat3( T1, T2, N ).transpose() ).toVar(); + + // transform rect + // & project rect onto sphere + const coords0 = mat.mul( p0.sub( P ) ).normalize().toVar(); + const coords1 = mat.mul( p1.sub( P ) ).normalize().toVar(); + const coords2 = mat.mul( p2.sub( P ) ).normalize().toVar(); + const coords3 = mat.mul( p3.sub( P ) ).normalize().toVar(); + + // calculate vector form factor + const vectorFormFactor = vec3( 0 ).toVar(); + vectorFormFactor.addAssign( LTC_EdgeVectorFormFactor( { v1: coords0, v2: coords1 } ) ); + vectorFormFactor.addAssign( LTC_EdgeVectorFormFactor( { v1: coords1, v2: coords2 } ) ); + vectorFormFactor.addAssign( LTC_EdgeVectorFormFactor( { v1: coords2, v2: coords3 } ) ); + vectorFormFactor.addAssign( LTC_EdgeVectorFormFactor( { v1: coords3, v2: coords0 } ) ); + + // adjust for horizon clipping + result.assign( vec3( LTC_ClippedSphereFormFactor( { f: vectorFormFactor } ) ) ); + + } ); + + return result; + +} ).setLayout( { + name: 'LTC_Evaluate', + type: 'vec3', + inputs: [ + { name: 'N', type: 'vec3' }, + { name: 'V', type: 'vec3' }, + { name: 'P', type: 'vec3' }, + { name: 'mInv', type: 'mat3' }, + { name: 'p0', type: 'vec3' }, + { name: 'p1', type: 'vec3' }, + { name: 'p2', type: 'vec3' }, + { name: 'p3', type: 'vec3' } + ] +} ); + +const LTC_Evaluate_Volume = /*@__PURE__*/ Fn( ( { P, p0, p1, p2, p3 } ) => { + + // bail if point is on back side of plane of light + // assumes ccw winding order of light vertices + const v1 = p1.sub( p0 ).toVar(); + const v2 = p3.sub( p0 ).toVar(); + + const lightNormal = v1.cross( v2 ); + const result = vec3().toVar(); + + If( lightNormal.dot( P.sub( p0 ) ).greaterThanEqual( 0.0 ), () => { + + // transform rect + // & project rect onto sphere + const coords0 = p0.sub( P ).normalize().toVar(); + const coords1 = p1.sub( P ).normalize().toVar(); + const coords2 = p2.sub( P ).normalize().toVar(); + const coords3 = p3.sub( P ).normalize().toVar(); + + // calculate vector form factor + const vectorFormFactor = vec3( 0 ).toVar(); + vectorFormFactor.addAssign( LTC_EdgeVectorFormFactor( { v1: coords0, v2: coords1 } ) ); + vectorFormFactor.addAssign( LTC_EdgeVectorFormFactor( { v1: coords1, v2: coords2 } ) ); + vectorFormFactor.addAssign( LTC_EdgeVectorFormFactor( { v1: coords2, v2: coords3 } ) ); + vectorFormFactor.addAssign( LTC_EdgeVectorFormFactor( { v1: coords3, v2: coords0 } ) ); + + // adjust for horizon clipping + result.assign( vec3( LTC_ClippedSphereFormFactor( { f: vectorFormFactor.abs() } ) ) ); + + } ); + + return result; + +} ).setLayout( { + name: 'LTC_Evaluate', + type: 'vec3', + inputs: [ + { name: 'P', type: 'vec3' }, + { name: 'p0', type: 'vec3' }, + { name: 'p1', type: 'vec3' }, + { name: 'p2', type: 'vec3' }, + { name: 'p3', type: 'vec3' } + ] +} ); + +// Mipped Bicubic Texture Filtering by N8 +// https://www.shadertoy.com/view/Dl2SDW + +const bC = 1.0 / 6.0; + +const w0 = ( a ) => mul( bC, mul( a, mul( a, a.negate().add( 3.0 ) ).sub( 3.0 ) ).add( 1.0 ) ); + +const w1 = ( a ) => mul( bC, mul( a, mul( a, mul( 3.0, a ).sub( 6.0 ) ) ).add( 4.0 ) ); + +const w2 = ( a ) => mul( bC, mul( a, mul( a, mul( -3, a ).add( 3.0 ) ).add( 3.0 ) ).add( 1.0 ) ); + +const w3 = ( a ) => mul( bC, pow( a, 3 ) ); + +const g0 = ( a ) => w0( a ).add( w1( a ) ); + +const g1 = ( a ) => w2( a ).add( w3( a ) ); + +// h0 and h1 are the two offset functions +const h0 = ( a ) => add( -1, w1( a ).div( w0( a ).add( w1( a ) ) ) ); + +const h1 = ( a ) => add( 1.0, w3( a ).div( w2( a ).add( w3( a ) ) ) ); + +const bicubic = ( textureNode, texelSize, lod ) => { + + const uv = textureNode.uvNode; + const uvScaled = mul( uv, texelSize.zw ).add( 0.5 ); + + const iuv = floor( uvScaled ); + const fuv = fract( uvScaled ); + + const g0x = g0( fuv.x ); + const g1x = g1( fuv.x ); + const h0x = h0( fuv.x ); + const h1x = h1( fuv.x ); + const h0y = h0( fuv.y ); + const h1y = h1( fuv.y ); + + const p0 = vec2( iuv.x.add( h0x ), iuv.y.add( h0y ) ).sub( 0.5 ).mul( texelSize.xy ); + const p1 = vec2( iuv.x.add( h1x ), iuv.y.add( h0y ) ).sub( 0.5 ).mul( texelSize.xy ); + const p2 = vec2( iuv.x.add( h0x ), iuv.y.add( h1y ) ).sub( 0.5 ).mul( texelSize.xy ); + const p3 = vec2( iuv.x.add( h1x ), iuv.y.add( h1y ) ).sub( 0.5 ).mul( texelSize.xy ); + + const a = g0( fuv.y ).mul( add( g0x.mul( textureNode.sample( p0 ).level( lod ) ), g1x.mul( textureNode.sample( p1 ).level( lod ) ) ) ); + const b = g1( fuv.y ).mul( add( g0x.mul( textureNode.sample( p2 ).level( lod ) ), g1x.mul( textureNode.sample( p3 ).level( lod ) ) ) ); + + return a.add( b ); + +}; + +/** + * Applies mipped bicubic texture filtering to the given texture node. + * + * @tsl + * @function + * @param {TextureNode} textureNode - The texture node that should be filtered. + * @param {Node} lodNode - Defines the LOD to sample from. + * @return {Node} The filtered texture sample. + */ +const textureBicubicLevel = /*@__PURE__*/ Fn( ( [ textureNode, lodNode ] ) => { + + const fLodSize = vec2( textureNode.size( int( lodNode ) ) ); + const cLodSize = vec2( textureNode.size( int( lodNode.add( 1.0 ) ) ) ); + const fLodSizeInv = div( 1.0, fLodSize ); + const cLodSizeInv = div( 1.0, cLodSize ); + const fSample = bicubic( textureNode, vec4( fLodSizeInv, fLodSize ), floor( lodNode ) ); + const cSample = bicubic( textureNode, vec4( cLodSizeInv, cLodSize ), ceil( lodNode ) ); + + return fract( lodNode ).mix( fSample, cSample ); + +} ); + +/** + * Applies mipped bicubic texture filtering to the given texture node. + * + * @tsl + * @function + * @param {TextureNode} textureNode - The texture node that should be filtered. + * @param {Node} [strength] - Defines the strength of the bicubic filtering. + * @return {Node} The filtered texture sample. + */ +const textureBicubic = /*@__PURE__*/ Fn( ( [ textureNode, strength ] ) => { + + const lod = strength.mul( maxMipLevel( textureNode ) ); + + return textureBicubicLevel( textureNode, lod ); + +} ); + +// +// Transmission +// + +const getVolumeTransmissionRay = /*@__PURE__*/ Fn( ( [ n, v, thickness, ior, modelMatrix ] ) => { + + // Direction of refracted light. + const refractionVector = vec3( refract( v.negate(), normalize( n ), div( 1.0, ior ) ) ); + + // Compute rotation-independent scaling of the model matrix. + const modelScale = vec3( + length( modelMatrix[ 0 ].xyz ), + length( modelMatrix[ 1 ].xyz ), + length( modelMatrix[ 2 ].xyz ) + ); + + // The thickness is specified in local space. + return normalize( refractionVector ).mul( thickness.mul( modelScale ) ); + +} ).setLayout( { + name: 'getVolumeTransmissionRay', + type: 'vec3', + inputs: [ + { name: 'n', type: 'vec3' }, + { name: 'v', type: 'vec3' }, + { name: 'thickness', type: 'float' }, + { name: 'ior', type: 'float' }, + { name: 'modelMatrix', type: 'mat4' } + ] +} ); + +const applyIorToRoughness = /*@__PURE__*/ Fn( ( [ roughness, ior ] ) => { + + // Scale roughness with IOR so that an IOR of 1.0 results in no microfacet refraction and + // an IOR of 1.5 results in the default amount of microfacet refraction. + return roughness.mul( clamp( ior.mul( 2.0 ).sub( 2.0 ), 0.0, 1.0 ) ); + +} ).setLayout( { + name: 'applyIorToRoughness', + type: 'float', + inputs: [ + { name: 'roughness', type: 'float' }, + { name: 'ior', type: 'float' } + ] +} ); + +const viewportBackSideTexture = /*@__PURE__*/ viewportMipTexture(); +const viewportFrontSideTexture = /*@__PURE__*/ viewportMipTexture(); + +const getTransmissionSample = /*@__PURE__*/ Fn( ( [ fragCoord, roughness, ior ], { material } ) => { + + const vTexture = material.side === BackSide ? viewportBackSideTexture : viewportFrontSideTexture; + + const transmissionSample = vTexture.sample( fragCoord ); + //const transmissionSample = viewportMipTexture( fragCoord ); + + const lod = log2( screenSize.x ).mul( applyIorToRoughness( roughness, ior ) ); + + return textureBicubicLevel( transmissionSample, lod ); + +} ); + +const volumeAttenuation = /*@__PURE__*/ Fn( ( [ transmissionDistance, attenuationColor, attenuationDistance ] ) => { + + If( attenuationDistance.notEqual( 0 ), () => { + + // Compute light attenuation using Beer's law. + const attenuationCoefficient = log( attenuationColor ).negate().div( attenuationDistance ); + const transmittance = exp( attenuationCoefficient.negate().mul( transmissionDistance ) ); + + return transmittance; + + } ); + + // Attenuation distance is +∞, i.e. the transmitted color is not attenuated at all. + return vec3( 1.0 ); + +} ).setLayout( { + name: 'volumeAttenuation', + type: 'vec3', + inputs: [ + { name: 'transmissionDistance', type: 'float' }, + { name: 'attenuationColor', type: 'vec3' }, + { name: 'attenuationDistance', type: 'float' } + ] +} ); + +const getIBLVolumeRefraction = /*@__PURE__*/ Fn( ( [ n, v, roughness, diffuseColor, specularColor, specularF90, position, modelMatrix, viewMatrix, projMatrix, ior, thickness, attenuationColor, attenuationDistance, dispersion ] ) => { + + let transmittedLight, transmittance; + + if ( dispersion ) { + + transmittedLight = vec4().toVar(); + transmittance = vec3().toVar(); + + const halfSpread = ior.sub( 1.0 ).mul( dispersion.mul( 0.025 ) ); + const iors = vec3( ior.sub( halfSpread ), ior, ior.add( halfSpread ) ); + + Loop( { start: 0, end: 3 }, ( { i } ) => { + + const ior = iors.element( i ); + + const transmissionRay = getVolumeTransmissionRay( n, v, thickness, ior, modelMatrix ); + const refractedRayExit = position.add( transmissionRay ); + + // Project refracted vector on the framebuffer, while mapping to normalized device coordinates. + const ndcPos = projMatrix.mul( viewMatrix.mul( vec4( refractedRayExit, 1.0 ) ) ); + const refractionCoords = vec2( ndcPos.xy.div( ndcPos.w ) ).toVar(); + refractionCoords.addAssign( 1.0 ); + refractionCoords.divAssign( 2.0 ); + refractionCoords.assign( vec2( refractionCoords.x, refractionCoords.y.oneMinus() ) ); // webgpu + + // Sample framebuffer to get pixel the refracted ray hits. + const transmissionSample = getTransmissionSample( refractionCoords, roughness, ior ); + + transmittedLight.element( i ).assign( transmissionSample.element( i ) ); + transmittedLight.a.addAssign( transmissionSample.a ); + + transmittance.element( i ).assign( diffuseColor.element( i ).mul( volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance ).element( i ) ) ); + + } ); + + transmittedLight.a.divAssign( 3.0 ); + + } else { + + const transmissionRay = getVolumeTransmissionRay( n, v, thickness, ior, modelMatrix ); + const refractedRayExit = position.add( transmissionRay ); + + // Project refracted vector on the framebuffer, while mapping to normalized device coordinates. + const ndcPos = projMatrix.mul( viewMatrix.mul( vec4( refractedRayExit, 1.0 ) ) ); + const refractionCoords = vec2( ndcPos.xy.div( ndcPos.w ) ).toVar(); + refractionCoords.addAssign( 1.0 ); + refractionCoords.divAssign( 2.0 ); + refractionCoords.assign( vec2( refractionCoords.x, refractionCoords.y.oneMinus() ) ); // webgpu + + // Sample framebuffer to get pixel the refracted ray hits. + transmittedLight = getTransmissionSample( refractionCoords, roughness, ior ); + transmittance = diffuseColor.mul( volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance ) ); + + } + + const attenuatedColor = transmittance.rgb.mul( transmittedLight.rgb ); + const dotNV = n.dot( v ).clamp(); + + // Get the specular component. + const F = vec3( EnvironmentBRDF( { // n, v, specularColor, specularF90, roughness + dotNV, + specularColor, + specularF90, + roughness + } ) ); + + // As less light is transmitted, the opacity should be increased. This simple approximation does a decent job + // of modulating a CSS background, and has no effect when the buffer is opaque, due to a solid object or clear color. + const transmittanceFactor = transmittance.r.add( transmittance.g, transmittance.b ).div( 3.0 ); + + return vec4( F.oneMinus().mul( attenuatedColor ), transmittedLight.a.oneMinus().mul( transmittanceFactor ).oneMinus() ); + +} ); + +// +// Iridescence +// + +// XYZ to linear-sRGB color space +const XYZ_TO_REC709 = /*@__PURE__*/ mat3( + 3.2404542, -0.969266, 0.0556434, + -1.5371385, 1.8760108, -0.2040259, + -0.4985314, 0.0415560, 1.0572252 +); + +// Assume air interface for top +// Note: We don't handle the case fresnel0 == 1 +const Fresnel0ToIor = ( fresnel0 ) => { + + const sqrtF0 = fresnel0.sqrt(); + return vec3( 1.0 ).add( sqrtF0 ).div( vec3( 1.0 ).sub( sqrtF0 ) ); + +}; + +// ior is a value between 1.0 and 3.0. 1.0 is air interface +const IorToFresnel0 = ( transmittedIor, incidentIor ) => { + + return transmittedIor.sub( incidentIor ).div( transmittedIor.add( incidentIor ) ).pow2(); + +}; + +// Fresnel equations for dielectric/dielectric interfaces. +// Ref: https://belcour.github.io/blog/research/2017/05/01/brdf-thin-film.html +// Evaluation XYZ sensitivity curves in Fourier space +const evalSensitivity = ( OPD, shift ) => { + + const phase = OPD.mul( 2.0 * Math.PI * 1.0e-9 ); + const val = vec3( 5.4856e-13, 4.4201e-13, 5.2481e-13 ); + const pos = vec3( 1.6810e+06, 1.7953e+06, 2.2084e+06 ); + const VAR = vec3( 4.3278e+09, 9.3046e+09, 6.6121e+09 ); + + const x = float( 9.7470e-14 * Math.sqrt( 2.0 * Math.PI * 4.5282e+09 ) ).mul( phase.mul( 2.2399e+06 ).add( shift.x ).cos() ).mul( phase.pow2().mul( -45282e5 ).exp() ); + + let xyz = val.mul( VAR.mul( 2.0 * Math.PI ).sqrt() ).mul( pos.mul( phase ).add( shift ).cos() ).mul( phase.pow2().negate().mul( VAR ).exp() ); + xyz = vec3( xyz.x.add( x ), xyz.y, xyz.z ).div( 1.0685e-7 ); + + const rgb = XYZ_TO_REC709.mul( xyz ); + + return rgb; + +}; + +const evalIridescence = /*@__PURE__*/ Fn( ( { outsideIOR, eta2, cosTheta1, thinFilmThickness, baseF0 } ) => { + + // Force iridescenceIOR -> outsideIOR when thinFilmThickness -> 0.0 + const iridescenceIOR = mix( outsideIOR, eta2, smoothstep( 0.0, 0.03, thinFilmThickness ) ); + // Evaluate the cosTheta on the base layer (Snell law) + const sinTheta2Sq = outsideIOR.div( iridescenceIOR ).pow2().mul( cosTheta1.pow2().oneMinus() ); + + // Handle TIR: + const cosTheta2Sq = sinTheta2Sq.oneMinus(); + + If( cosTheta2Sq.lessThan( 0 ), () => { + + return vec3( 1.0 ); + + } ); + + const cosTheta2 = cosTheta2Sq.sqrt(); + + // First interface + const R0 = IorToFresnel0( iridescenceIOR, outsideIOR ); + const R12 = F_Schlick( { f0: R0, f90: 1.0, dotVH: cosTheta1 } ); + //const R21 = R12; + const T121 = R12.oneMinus(); + const phi12 = iridescenceIOR.lessThan( outsideIOR ).select( Math.PI, 0.0 ); + const phi21 = float( Math.PI ).sub( phi12 ); + + // Second interface + const baseIOR = Fresnel0ToIor( baseF0.clamp( 0.0, 0.9999 ) ); // guard against 1.0 + const R1 = IorToFresnel0( baseIOR, iridescenceIOR.toVec3() ); + const R23 = F_Schlick( { f0: R1, f90: 1.0, dotVH: cosTheta2 } ); + const phi23 = vec3( + baseIOR.x.lessThan( iridescenceIOR ).select( Math.PI, 0.0 ), + baseIOR.y.lessThan( iridescenceIOR ).select( Math.PI, 0.0 ), + baseIOR.z.lessThan( iridescenceIOR ).select( Math.PI, 0.0 ) + ); + + // Phase shift + const OPD = iridescenceIOR.mul( thinFilmThickness, cosTheta2, 2.0 ); + const phi = vec3( phi21 ).add( phi23 ); + + // Compound terms + const R123 = R12.mul( R23 ).clamp( 1e-5, 0.9999 ); + const r123 = R123.sqrt(); + const Rs = T121.pow2().mul( R23 ).div( vec3( 1.0 ).sub( R123 ) ); + + // Reflectance term for m = 0 (DC term amplitude) + const C0 = R12.add( Rs ); + const I = C0.toVar(); + + // Reflectance term for m > 0 (pairs of diracs) + const Cm = Rs.sub( T121 ).toVar(); + + Loop( { start: 1, end: 2, condition: '<=', name: 'm' }, ( { m } ) => { + + Cm.mulAssign( r123 ); + const Sm = evalSensitivity( float( m ).mul( OPD ), float( m ).mul( phi ) ).mul( 2.0 ); + I.addAssign( Cm.mul( Sm ) ); + + } ); + + // Since out of gamut colors might be produced, negative color values are clamped to 0. + return I.max( vec3( 0.0 ) ); + +} ).setLayout( { + name: 'evalIridescence', + type: 'vec3', + inputs: [ + { name: 'outsideIOR', type: 'float' }, + { name: 'eta2', type: 'float' }, + { name: 'cosTheta1', type: 'float' }, + { name: 'thinFilmThickness', type: 'float' }, + { name: 'baseF0', type: 'vec3' } + ] +} ); + +// +// Sheen +// + +// This is a curve-fit approximation to the "Charlie sheen" BRDF integrated over the hemisphere from +// Estevez and Kulla 2017, "Production Friendly Microfacet Sheen BRDF". The analysis can be found +// in the Sheen section of https://drive.google.com/file/d/1T0D1VSyR4AllqIJTQAraEIzjlb5h4FKH/view?usp=sharing +const IBLSheenBRDF = /*@__PURE__*/ Fn( ( { normal, viewDir, roughness } ) => { + + const dotNV = normal.dot( viewDir ).saturate(); + + const r2 = roughness.pow2(); + + const a = select( + roughness.lessThan( 0.25 ), + float( -339.2 ).mul( r2 ).add( float( 161.4 ).mul( roughness ) ).sub( 25.9 ), + float( -8.48 ).mul( r2 ).add( float( 14.3 ).mul( roughness ) ).sub( 9.95 ) + ); + + const b = select( + roughness.lessThan( 0.25 ), + float( 44.0 ).mul( r2 ).sub( float( 23.7 ).mul( roughness ) ).add( 3.26 ), + float( 1.97 ).mul( r2 ).sub( float( 3.27 ).mul( roughness ) ).add( 0.72 ) + ); + + const DG = select( roughness.lessThan( 0.25 ), 0.0, float( 0.1 ).mul( roughness ).sub( 0.025 ) ).add( a.mul( dotNV ).add( b ).exp() ); + + return DG.mul( 1.0 / Math.PI ).saturate(); + +} ); + +const clearcoatF0 = vec3( 0.04 ); +const clearcoatF90 = float( 1 ); + + +/** + * Represents the lighting model for a PBR material. + * + * @augments LightingModel + */ +class PhysicalLightingModel extends LightingModel { + + /** + * Constructs a new physical lighting model. + * + * @param {boolean} [clearcoat=false] - Whether clearcoat is supported or not. + * @param {boolean} [sheen=false] - Whether sheen is supported or not. + * @param {boolean} [iridescence=false] - Whether iridescence is supported or not. + * @param {boolean} [anisotropy=false] - Whether anisotropy is supported or not. + * @param {boolean} [transmission=false] - Whether transmission is supported or not. + * @param {boolean} [dispersion=false] - Whether dispersion is supported or not. + */ + constructor( clearcoat = false, sheen = false, iridescence = false, anisotropy = false, transmission = false, dispersion = false ) { + + super(); + + /** + * Whether clearcoat is supported or not. + * + * @type {boolean} + * @default false + */ + this.clearcoat = clearcoat; + + /** + * Whether sheen is supported or not. + * + * @type {boolean} + * @default false + */ + this.sheen = sheen; + + /** + * Whether iridescence is supported or not. + * + * @type {boolean} + * @default false + */ + this.iridescence = iridescence; + + /** + * Whether anisotropy is supported or not. + * + * @type {boolean} + * @default false + */ + this.anisotropy = anisotropy; + + /** + * Whether transmission is supported or not. + * + * @type {boolean} + * @default false + */ + this.transmission = transmission; + + /** + * Whether dispersion is supported or not. + * + * @type {boolean} + * @default false + */ + this.dispersion = dispersion; + + /** + * The clear coat radiance. + * + * @type {?Node} + * @default null + */ + this.clearcoatRadiance = null; + + /** + * The clear coat specular direct. + * + * @type {?Node} + * @default null + */ + this.clearcoatSpecularDirect = null; + + /** + * The clear coat specular indirect. + * + * @type {?Node} + * @default null + */ + this.clearcoatSpecularIndirect = null; + + /** + * The sheen specular direct. + * + * @type {?Node} + * @default null + */ + this.sheenSpecularDirect = null; + + /** + * The sheen specular indirect. + * + * @type {?Node} + * @default null + */ + this.sheenSpecularIndirect = null; + + /** + * The iridescence Fresnel. + * + * @type {?Node} + * @default null + */ + this.iridescenceFresnel = null; + + /** + * The iridescence F0. + * + * @type {?Node} + * @default null + */ + this.iridescenceF0 = null; + + } + + /** + * Depending on what features are requested, the method prepares certain node variables + * which are later used for lighting computations. + * + * @param {NodeBuilder} builder - The current node builder. + */ + start( builder ) { + + if ( this.clearcoat === true ) { + + this.clearcoatRadiance = vec3().toVar( 'clearcoatRadiance' ); + this.clearcoatSpecularDirect = vec3().toVar( 'clearcoatSpecularDirect' ); + this.clearcoatSpecularIndirect = vec3().toVar( 'clearcoatSpecularIndirect' ); + + } + + if ( this.sheen === true ) { + + this.sheenSpecularDirect = vec3().toVar( 'sheenSpecularDirect' ); + this.sheenSpecularIndirect = vec3().toVar( 'sheenSpecularIndirect' ); + + } + + if ( this.iridescence === true ) { + + const dotNVi = normalView.dot( positionViewDirection ).clamp(); + + this.iridescenceFresnel = evalIridescence( { + outsideIOR: float( 1.0 ), + eta2: iridescenceIOR, + cosTheta1: dotNVi, + thinFilmThickness: iridescenceThickness, + baseF0: specularColor + } ); + + this.iridescenceF0 = Schlick_to_F0( { f: this.iridescenceFresnel, f90: 1.0, dotVH: dotNVi } ); + + } + + if ( this.transmission === true ) { + + const position = positionWorld; + const v = cameraPosition.sub( positionWorld ).normalize(); // TODO: Create Node for this, same issue in MaterialX + const n = normalWorld; + + const context = builder.context; + + context.backdrop = getIBLVolumeRefraction( + n, + v, + roughness, + diffuseColor, + specularColor, + specularF90, // specularF90 + position, // positionWorld + modelWorldMatrix, // modelMatrix + cameraViewMatrix, // viewMatrix + cameraProjectionMatrix, // projMatrix + ior, + thickness, + attenuationColor, + attenuationDistance, + this.dispersion ? dispersion : null + ); + + context.backdropAlpha = transmission; + + diffuseColor.a.mulAssign( mix( 1, context.backdrop.a, transmission ) ); + + } + + super.start( builder ); + + } + + // Fdez-Agüera's "Multiple-Scattering Microfacet Model for Real-Time Image Based Lighting" + // Approximates multi-scattering in order to preserve energy. + // http://www.jcgt.org/published/0008/01/03/ + + computeMultiscattering( singleScatter, multiScatter, specularF90 ) { + + const dotNV = normalView.dot( positionViewDirection ).clamp(); // @ TODO: Move to core dotNV + + const fab = DFGApprox( { roughness, dotNV } ); + + const Fr = this.iridescenceF0 ? iridescence.mix( specularColor, this.iridescenceF0 ) : specularColor; + + const FssEss = Fr.mul( fab.x ).add( specularF90.mul( fab.y ) ); + + const Ess = fab.x.add( fab.y ); + const Ems = Ess.oneMinus(); + + const Favg = specularColor.add( specularColor.oneMinus().mul( 0.047619 ) ); // 1/21 + const Fms = FssEss.mul( Favg ).div( Ems.mul( Favg ).oneMinus() ); + + singleScatter.addAssign( FssEss ); + multiScatter.addAssign( Fms.mul( Ems ) ); + + } + + /** + * Implements the direct light. + * + * @param {Object} lightData - The light data. + * @param {NodeBuilder} builder - The current node builder. + */ + direct( { lightDirection, lightColor, reflectedLight } ) { + + const dotNL = normalView.dot( lightDirection ).clamp(); + const irradiance = dotNL.mul( lightColor ); + + if ( this.sheen === true ) { + + this.sheenSpecularDirect.addAssign( irradiance.mul( BRDF_Sheen( { lightDirection } ) ) ); + + } + + if ( this.clearcoat === true ) { + + const dotNLcc = clearcoatNormalView.dot( lightDirection ).clamp(); + const ccIrradiance = dotNLcc.mul( lightColor ); + + this.clearcoatSpecularDirect.addAssign( ccIrradiance.mul( BRDF_GGX( { lightDirection, f0: clearcoatF0, f90: clearcoatF90, roughness: clearcoatRoughness, normalView: clearcoatNormalView } ) ) ); + + } + + reflectedLight.directDiffuse.addAssign( irradiance.mul( BRDF_Lambert( { diffuseColor: diffuseColor.rgb } ) ) ); + + reflectedLight.directSpecular.addAssign( irradiance.mul( BRDF_GGX( { lightDirection, f0: specularColor, f90: 1, roughness, iridescence: this.iridescence, f: this.iridescenceFresnel, USE_IRIDESCENCE: this.iridescence, USE_ANISOTROPY: this.anisotropy } ) ) ); + + } + + /** + * This method is intended for implementing the direct light term for + * rect area light nodes. + * + * @param {Object} input - The input data. + * @param {NodeBuilder} builder - The current node builder. + */ + directRectArea( { lightColor, lightPosition, halfWidth, halfHeight, reflectedLight, ltc_1, ltc_2 } ) { + + const p0 = lightPosition.add( halfWidth ).sub( halfHeight ); // counterclockwise; light shines in local neg z direction + const p1 = lightPosition.sub( halfWidth ).sub( halfHeight ); + const p2 = lightPosition.sub( halfWidth ).add( halfHeight ); + const p3 = lightPosition.add( halfWidth ).add( halfHeight ); + + const N = normalView; + const V = positionViewDirection; + const P = positionView.toVar(); + + const uv = LTC_Uv( { N, V, roughness } ); + + const t1 = ltc_1.sample( uv ).toVar(); + const t2 = ltc_2.sample( uv ).toVar(); + + const mInv = mat3( + vec3( t1.x, 0, t1.y ), + vec3( 0, 1, 0 ), + vec3( t1.z, 0, t1.w ) + ).toVar(); + + // LTC Fresnel Approximation by Stephen Hill + // http://blog.selfshadow.com/publications/s2016-advances/s2016_ltc_fresnel.pdf + const fresnel = specularColor.mul( t2.x ).add( specularColor.oneMinus().mul( t2.y ) ).toVar(); + + reflectedLight.directSpecular.addAssign( lightColor.mul( fresnel ).mul( LTC_Evaluate( { N, V, P, mInv, p0, p1, p2, p3 } ) ) ); + + reflectedLight.directDiffuse.addAssign( lightColor.mul( diffuseColor ).mul( LTC_Evaluate( { N, V, P, mInv: mat3( 1, 0, 0, 0, 1, 0, 0, 0, 1 ), p0, p1, p2, p3 } ) ) ); + + } + + /** + * Implements the indirect lighting. + * + * @param {NodeBuilder} builder - The current node builder. + */ + indirect( builder ) { + + this.indirectDiffuse( builder ); + this.indirectSpecular( builder ); + this.ambientOcclusion( builder ); + + } + + /** + * Implements the indirect diffuse term. + * + * @param {NodeBuilder} builder - The current node builder. + */ + indirectDiffuse( builder ) { + + const { irradiance, reflectedLight } = builder.context; + + reflectedLight.indirectDiffuse.addAssign( irradiance.mul( BRDF_Lambert( { diffuseColor } ) ) ); + + } + + /** + * Implements the indirect specular term. + * + * @param {NodeBuilder} builder - The current node builder. + */ + indirectSpecular( builder ) { + + const { radiance, iblIrradiance, reflectedLight } = builder.context; + + if ( this.sheen === true ) { + + this.sheenSpecularIndirect.addAssign( iblIrradiance.mul( + sheen, + IBLSheenBRDF( { + normal: normalView, + viewDir: positionViewDirection, + roughness: sheenRoughness + } ) + ) ); + + } + + if ( this.clearcoat === true ) { + + const dotNVcc = clearcoatNormalView.dot( positionViewDirection ).clamp(); + + const clearcoatEnv = EnvironmentBRDF( { + dotNV: dotNVcc, + specularColor: clearcoatF0, + specularF90: clearcoatF90, + roughness: clearcoatRoughness + } ); + + this.clearcoatSpecularIndirect.addAssign( this.clearcoatRadiance.mul( clearcoatEnv ) ); + + } + + // Both indirect specular and indirect diffuse light accumulate here + + const singleScattering = vec3().toVar( 'singleScattering' ); + const multiScattering = vec3().toVar( 'multiScattering' ); + const cosineWeightedIrradiance = iblIrradiance.mul( 1 / Math.PI ); + + this.computeMultiscattering( singleScattering, multiScattering, specularF90 ); + + const totalScattering = singleScattering.add( multiScattering ); + + const diffuse = diffuseColor.mul( totalScattering.r.max( totalScattering.g ).max( totalScattering.b ).oneMinus() ); + + reflectedLight.indirectSpecular.addAssign( radiance.mul( singleScattering ) ); + reflectedLight.indirectSpecular.addAssign( multiScattering.mul( cosineWeightedIrradiance ) ); + + reflectedLight.indirectDiffuse.addAssign( diffuse.mul( cosineWeightedIrradiance ) ); + + } + + /** + * Implements the ambient occlusion term. + * + * @param {NodeBuilder} builder - The current node builder. + */ + ambientOcclusion( builder ) { + + const { ambientOcclusion, reflectedLight } = builder.context; + + const dotNV = normalView.dot( positionViewDirection ).clamp(); // @ TODO: Move to core dotNV + + const aoNV = dotNV.add( ambientOcclusion ); + const aoExp = roughness.mul( -16 ).oneMinus().negate().exp2(); + + const aoNode = ambientOcclusion.sub( aoNV.pow( aoExp ).oneMinus() ).clamp(); + + if ( this.clearcoat === true ) { + + this.clearcoatSpecularIndirect.mulAssign( ambientOcclusion ); + + } + + if ( this.sheen === true ) { + + this.sheenSpecularIndirect.mulAssign( ambientOcclusion ); + + } + + reflectedLight.indirectDiffuse.mulAssign( ambientOcclusion ); + reflectedLight.indirectSpecular.mulAssign( aoNode ); + + } + + /** + * Used for final lighting accumulations depending on the requested features. + * + * @param {NodeBuilder} builder - The current node builder. + */ + finish( { context } ) { + + const { outgoingLight } = context; + + if ( this.clearcoat === true ) { + + const dotNVcc = clearcoatNormalView.dot( positionViewDirection ).clamp(); + + const Fcc = F_Schlick( { + dotVH: dotNVcc, + f0: clearcoatF0, + f90: clearcoatF90 + } ); + + const clearcoatLight = outgoingLight.mul( clearcoat.mul( Fcc ).oneMinus() ).add( this.clearcoatSpecularDirect.add( this.clearcoatSpecularIndirect ).mul( clearcoat ) ); + + outgoingLight.assign( clearcoatLight ); + + } + + if ( this.sheen === true ) { + + const sheenEnergyComp = sheen.r.max( sheen.g ).max( sheen.b ).mul( 0.157 ).oneMinus(); + const sheenLight = outgoingLight.mul( sheenEnergyComp ).add( this.sheenSpecularDirect, this.sheenSpecularIndirect ); + + outgoingLight.assign( sheenLight ); + + } + + } + +} + +// These defines must match with PMREMGenerator + +const cubeUV_r0 = /*@__PURE__*/ float( 1.0 ); +const cubeUV_m0 = /*@__PURE__*/ float( -2 ); +const cubeUV_r1 = /*@__PURE__*/ float( 0.8 ); +const cubeUV_m1 = /*@__PURE__*/ float( -1 ); +const cubeUV_r4 = /*@__PURE__*/ float( 0.4 ); +const cubeUV_m4 = /*@__PURE__*/ float( 2.0 ); +const cubeUV_r5 = /*@__PURE__*/ float( 0.305 ); +const cubeUV_m5 = /*@__PURE__*/ float( 3.0 ); +const cubeUV_r6 = /*@__PURE__*/ float( 0.21 ); +const cubeUV_m6 = /*@__PURE__*/ float( 4.0 ); + +const cubeUV_minMipLevel = /*@__PURE__*/ float( 4.0 ); +const cubeUV_minTileSize = /*@__PURE__*/ float( 16.0 ); + +// These shader functions convert between the UV coordinates of a single face of +// a cubemap, the 0-5 integer index of a cube face, and the direction vector for +// sampling a textureCube (not generally normalized ). + +const getFace = /*@__PURE__*/ Fn( ( [ direction ] ) => { + + const absDirection = vec3( abs( direction ) ).toVar(); + const face = float( -1 ).toVar(); + + If( absDirection.x.greaterThan( absDirection.z ), () => { + + If( absDirection.x.greaterThan( absDirection.y ), () => { + + face.assign( select( direction.x.greaterThan( 0.0 ), 0.0, 3.0 ) ); + + } ).Else( () => { + + face.assign( select( direction.y.greaterThan( 0.0 ), 1.0, 4.0 ) ); + + } ); + + } ).Else( () => { + + If( absDirection.z.greaterThan( absDirection.y ), () => { + + face.assign( select( direction.z.greaterThan( 0.0 ), 2.0, 5.0 ) ); + + } ).Else( () => { + + face.assign( select( direction.y.greaterThan( 0.0 ), 1.0, 4.0 ) ); + + } ); + + } ); + + return face; + +} ).setLayout( { + name: 'getFace', + type: 'float', + inputs: [ + { name: 'direction', type: 'vec3' } + ] +} ); + +// RH coordinate system; PMREM face-indexing convention +const getUV = /*@__PURE__*/ Fn( ( [ direction, face ] ) => { + + const uv = vec2().toVar(); + + If( face.equal( 0.0 ), () => { + + uv.assign( vec2( direction.z, direction.y ).div( abs( direction.x ) ) ); // pos x + + } ).ElseIf( face.equal( 1.0 ), () => { + + uv.assign( vec2( direction.x.negate(), direction.z.negate() ).div( abs( direction.y ) ) ); // pos y + + } ).ElseIf( face.equal( 2.0 ), () => { + + uv.assign( vec2( direction.x.negate(), direction.y ).div( abs( direction.z ) ) ); // pos z + + } ).ElseIf( face.equal( 3.0 ), () => { + + uv.assign( vec2( direction.z.negate(), direction.y ).div( abs( direction.x ) ) ); // neg x + + } ).ElseIf( face.equal( 4.0 ), () => { + + uv.assign( vec2( direction.x.negate(), direction.z ).div( abs( direction.y ) ) ); // neg y + + } ).Else( () => { + + uv.assign( vec2( direction.x, direction.y ).div( abs( direction.z ) ) ); // neg z + + } ); + + return mul( 0.5, uv.add( 1.0 ) ); + +} ).setLayout( { + name: 'getUV', + type: 'vec2', + inputs: [ + { name: 'direction', type: 'vec3' }, + { name: 'face', type: 'float' } + ] +} ); + +const roughnessToMip = /*@__PURE__*/ Fn( ( [ roughness ] ) => { + + const mip = float( 0.0 ).toVar(); + + If( roughness.greaterThanEqual( cubeUV_r1 ), () => { + + mip.assign( cubeUV_r0.sub( roughness ).mul( cubeUV_m1.sub( cubeUV_m0 ) ).div( cubeUV_r0.sub( cubeUV_r1 ) ).add( cubeUV_m0 ) ); + + } ).ElseIf( roughness.greaterThanEqual( cubeUV_r4 ), () => { + + mip.assign( cubeUV_r1.sub( roughness ).mul( cubeUV_m4.sub( cubeUV_m1 ) ).div( cubeUV_r1.sub( cubeUV_r4 ) ).add( cubeUV_m1 ) ); + + } ).ElseIf( roughness.greaterThanEqual( cubeUV_r5 ), () => { + + mip.assign( cubeUV_r4.sub( roughness ).mul( cubeUV_m5.sub( cubeUV_m4 ) ).div( cubeUV_r4.sub( cubeUV_r5 ) ).add( cubeUV_m4 ) ); + + } ).ElseIf( roughness.greaterThanEqual( cubeUV_r6 ), () => { + + mip.assign( cubeUV_r5.sub( roughness ).mul( cubeUV_m6.sub( cubeUV_m5 ) ).div( cubeUV_r5.sub( cubeUV_r6 ) ).add( cubeUV_m5 ) ); + + } ).Else( () => { + + mip.assign( float( -2 ).mul( log2( mul( 1.16, roughness ) ) ) ); // 1.16 = 1.79^0.25 + + } ); + + return mip; + +} ).setLayout( { + name: 'roughnessToMip', + type: 'float', + inputs: [ + { name: 'roughness', type: 'float' } + ] +} ); + +// RH coordinate system; PMREM face-indexing convention +const getDirection = /*@__PURE__*/ Fn( ( [ uv_immutable, face ] ) => { + + const uv = uv_immutable.toVar(); + uv.assign( mul( 2.0, uv ).sub( 1.0 ) ); + const direction = vec3( uv, 1.0 ).toVar(); + + If( face.equal( 0.0 ), () => { + + direction.assign( direction.zyx ); // ( 1, v, u ) pos x + + } ).ElseIf( face.equal( 1.0 ), () => { + + direction.assign( direction.xzy ); + direction.xz.mulAssign( -1 ); // ( -u, 1, -v ) pos y + + } ).ElseIf( face.equal( 2.0 ), () => { + + direction.x.mulAssign( -1 ); // ( -u, v, 1 ) pos z + + } ).ElseIf( face.equal( 3.0 ), () => { + + direction.assign( direction.zyx ); + direction.xz.mulAssign( -1 ); // ( -1, v, -u ) neg x + + } ).ElseIf( face.equal( 4.0 ), () => { + + direction.assign( direction.xzy ); + direction.xy.mulAssign( -1 ); // ( -u, -1, v ) neg y + + } ).ElseIf( face.equal( 5.0 ), () => { + + direction.z.mulAssign( -1 ); // ( u, v, -1 ) neg zS + + } ); + + return direction; + +} ).setLayout( { + name: 'getDirection', + type: 'vec3', + inputs: [ + { name: 'uv', type: 'vec2' }, + { name: 'face', type: 'float' } + ] +} ); + +// + +const textureCubeUV = /*@__PURE__*/ Fn( ( [ envMap, sampleDir_immutable, roughness_immutable, CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP ] ) => { + + const roughness = float( roughness_immutable ); + const sampleDir = vec3( sampleDir_immutable ); + + const mip = clamp( roughnessToMip( roughness ), cubeUV_m0, CUBEUV_MAX_MIP ); + const mipF = fract( mip ); + const mipInt = floor( mip ); + const color0 = vec3( bilinearCubeUV( envMap, sampleDir, mipInt, CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP ) ).toVar(); + + If( mipF.notEqual( 0.0 ), () => { + + const color1 = vec3( bilinearCubeUV( envMap, sampleDir, mipInt.add( 1.0 ), CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP ) ).toVar(); + + color0.assign( mix( color0, color1, mipF ) ); + + } ); + + return color0; + +} ); + +const bilinearCubeUV = /*@__PURE__*/ Fn( ( [ envMap, direction_immutable, mipInt_immutable, CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP ] ) => { + + const mipInt = float( mipInt_immutable ).toVar(); + const direction = vec3( direction_immutable ); + const face = float( getFace( direction ) ).toVar(); + const filterInt = float( max$1( cubeUV_minMipLevel.sub( mipInt ), 0.0 ) ).toVar(); + mipInt.assign( max$1( mipInt, cubeUV_minMipLevel ) ); + const faceSize = float( exp2( mipInt ) ).toVar(); + const uv = vec2( getUV( direction, face ).mul( faceSize.sub( 2.0 ) ).add( 1.0 ) ).toVar(); + + If( face.greaterThan( 2.0 ), () => { + + uv.y.addAssign( faceSize ); + face.subAssign( 3.0 ); + + } ); + + uv.x.addAssign( face.mul( faceSize ) ); + uv.x.addAssign( filterInt.mul( mul( 3.0, cubeUV_minTileSize ) ) ); + uv.y.addAssign( mul( 4.0, exp2( CUBEUV_MAX_MIP ).sub( faceSize ) ) ); + uv.x.mulAssign( CUBEUV_TEXEL_WIDTH ); + uv.y.mulAssign( CUBEUV_TEXEL_HEIGHT ); + + return envMap.sample( uv ).grad( vec2(), vec2() ); // disable anisotropic filtering + +} ); + +const getSample = /*@__PURE__*/ Fn( ( { envMap, mipInt, outputDirection, theta, axis, CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP } ) => { + + const cosTheta = cos( theta ); + + // Rodrigues' axis-angle rotation + const sampleDirection = outputDirection.mul( cosTheta ) + .add( axis.cross( outputDirection ).mul( sin( theta ) ) ) + .add( axis.mul( axis.dot( outputDirection ).mul( cosTheta.oneMinus() ) ) ); + + return bilinearCubeUV( envMap, sampleDirection, mipInt, CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP ); + +} ); + +const blur = /*@__PURE__*/ Fn( ( { n, latitudinal, poleAxis, outputDirection, weights, samples, dTheta, mipInt, envMap, CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP } ) => { + + const axis = vec3( select( latitudinal, poleAxis, cross( poleAxis, outputDirection ) ) ).toVar(); + + If( axis.equal( vec3( 0.0 ) ), () => { + + axis.assign( vec3( outputDirection.z, 0.0, outputDirection.x.negate() ) ); + + } ); + + axis.assign( normalize( axis ) ); + + const gl_FragColor = vec3().toVar(); + gl_FragColor.addAssign( weights.element( 0 ).mul( getSample( { theta: 0.0, axis, outputDirection, mipInt, envMap, CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP } ) ) ); + + Loop( { start: int( 1 ), end: n }, ( { i } ) => { + + If( i.greaterThanEqual( samples ), () => { + + Break(); + + } ); + + const theta = float( dTheta.mul( float( i ) ) ).toVar(); + gl_FragColor.addAssign( weights.element( i ).mul( getSample( { theta: theta.mul( -1 ), axis, outputDirection, mipInt, envMap, CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP } ) ) ); + gl_FragColor.addAssign( weights.element( i ).mul( getSample( { theta, axis, outputDirection, mipInt, envMap, CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP } ) ) ); + + } ); + + return vec4( gl_FragColor, 1 ); + +} ); + +const LOD_MIN = 4; + +// The standard deviations (radians) associated with the extra mips. These are +// chosen to approximate a Trowbridge-Reitz distribution function times the +// geometric shadowing function. These sigma values squared must match the +// variance #defines in cube_uv_reflection_fragment.glsl.js. +const EXTRA_LOD_SIGMA = [ 0.125, 0.215, 0.35, 0.446, 0.526, 0.582 ]; + +// The maximum length of the blur for loop. Smaller sigmas will use fewer +// samples and exit early, but not recompile the shader. +const MAX_SAMPLES = 20; + +const _flatCamera = /*@__PURE__*/ new OrthographicCamera( -1, 1, 1, -1, 0, 1 ); +const _cubeCamera = /*@__PURE__*/ new PerspectiveCamera( 90, 1 ); +const _clearColor$2 = /*@__PURE__*/ new Color(); +let _oldTarget = null; +let _oldActiveCubeFace = 0; +let _oldActiveMipmapLevel = 0; + +// Golden Ratio +const PHI = ( 1 + Math.sqrt( 5 ) ) / 2; +const INV_PHI = 1 / PHI; + +// Vertices of a dodecahedron (except the opposites, which represent the +// same axis), used as axis directions evenly spread on a sphere. +const _axisDirections = [ + /*@__PURE__*/ new Vector3( - PHI, INV_PHI, 0 ), + /*@__PURE__*/ new Vector3( PHI, INV_PHI, 0 ), + /*@__PURE__*/ new Vector3( - INV_PHI, 0, PHI ), + /*@__PURE__*/ new Vector3( INV_PHI, 0, PHI ), + /*@__PURE__*/ new Vector3( 0, PHI, - INV_PHI ), + /*@__PURE__*/ new Vector3( 0, PHI, INV_PHI ), + /*@__PURE__*/ new Vector3( -1, 1, -1 ), + /*@__PURE__*/ new Vector3( 1, 1, -1 ), + /*@__PURE__*/ new Vector3( -1, 1, 1 ), + /*@__PURE__*/ new Vector3( 1, 1, 1 ) +]; + +const _origin = /*@__PURE__*/ new Vector3(); + +// maps blur materials to their uniforms dictionary + +const _uniformsMap = new WeakMap(); + +// WebGPU Face indices +const _faceLib = [ + 3, 1, 5, + 0, 4, 2 +]; + +const _direction = /*@__PURE__*/ getDirection( uv$1(), attribute( 'faceIndex' ) ).normalize(); +const _outputDirection = /*@__PURE__*/ vec3( _direction.x, _direction.y, _direction.z ); + +/** + * This class generates a Prefiltered, Mipmapped Radiance Environment Map + * (PMREM) from a cubeMap environment texture. This allows different levels of + * blur to be quickly accessed based on material roughness. It is packed into a + * special CubeUV format that allows us to perform custom interpolation so that + * we can support nonlinear formats such as RGBE. Unlike a traditional mipmap + * chain, it only goes down to the LOD_MIN level (above), and then creates extra + * even more filtered 'mips' at the same LOD_MIN resolution, associated with + * higher roughness levels. In this way we maintain resolution to smoothly + * interpolate diffuse lighting while limiting sampling computation. + * + * Paper: Fast, Accurate Image-Based Lighting: + * {@link https://drive.google.com/file/d/15y8r_UpKlU9SvV4ILb0C3qCPecS8pvLz/view} +*/ +class PMREMGenerator { + + /** + * Constructs a new PMREM generator. + * + * @param {Renderer} renderer - The renderer. + */ + constructor( renderer ) { + + this._renderer = renderer; + this._pingPongRenderTarget = null; + + this._lodMax = 0; + this._cubeSize = 0; + this._lodPlanes = []; + this._sizeLods = []; + this._sigmas = []; + this._lodMeshes = []; + + this._blurMaterial = null; + this._cubemapMaterial = null; + this._equirectMaterial = null; + this._backgroundBox = null; + + } + + get _hasInitialized() { + + return this._renderer.hasInitialized(); + + } + + /** + * Generates a PMREM from a supplied Scene, which can be faster than using an + * image if networking bandwidth is low. Optional sigma specifies a blur radius + * in radians to be applied to the scene before PMREM generation. Optional near + * and far planes ensure the scene is rendered in its entirety. + * + * @param {Scene} scene - The scene to be captured. + * @param {number} [sigma=0] - The blur radius in radians. + * @param {number} [near=0.1] - The near plane distance. + * @param {number} [far=100] - The far plane distance. + * @param {Object} [options={}] - The configuration options. + * @param {number} [options.size=256] - The texture size of the PMREM. + * @param {Vector3} [options.renderTarget=origin] - The position of the internal cube camera that renders the scene. + * @param {?RenderTarget} [options.renderTarget=null] - The render target to use. + * @return {RenderTarget} The resulting PMREM. + * @see {@link PMREMGenerator#fromSceneAsync} + */ + fromScene( scene, sigma = 0, near = 0.1, far = 100, options = {} ) { + + const { + size = 256, + position = _origin, + renderTarget = null, + } = options; + + this._setSize( size ); + + if ( this._hasInitialized === false ) { + + console.warn( 'THREE.PMREMGenerator: .fromScene() called before the backend is initialized. Try using .fromSceneAsync() instead.' ); + + const cubeUVRenderTarget = renderTarget || this._allocateTarget(); + + options.renderTarget = cubeUVRenderTarget; + + this.fromSceneAsync( scene, sigma, near, far, options ); + + return cubeUVRenderTarget; + + } + + _oldTarget = this._renderer.getRenderTarget(); + _oldActiveCubeFace = this._renderer.getActiveCubeFace(); + _oldActiveMipmapLevel = this._renderer.getActiveMipmapLevel(); + + const cubeUVRenderTarget = renderTarget || this._allocateTarget(); + cubeUVRenderTarget.depthBuffer = true; + + this._init( cubeUVRenderTarget ); + + this._sceneToCubeUV( scene, near, far, cubeUVRenderTarget, position ); + + if ( sigma > 0 ) { + + this._blur( cubeUVRenderTarget, 0, 0, sigma ); + + } + + this._applyPMREM( cubeUVRenderTarget ); + + this._cleanup( cubeUVRenderTarget ); + + return cubeUVRenderTarget; + + } + + /** + * Generates a PMREM from a supplied Scene, which can be faster than using an + * image if networking bandwidth is low. Optional sigma specifies a blur radius + * in radians to be applied to the scene before PMREM generation. Optional near + * and far planes ensure the scene is rendered in its entirety (the cubeCamera + * is placed at the origin). + * + * @param {Scene} scene - The scene to be captured. + * @param {number} [sigma=0] - The blur radius in radians. + * @param {number} [near=0.1] - The near plane distance. + * @param {number} [far=100] - The far plane distance. + * @param {Object} [options={}] - The configuration options. + * @param {number} [options.size=256] - The texture size of the PMREM. + * @param {Vector3} [options.position=origin] - The position of the internal cube camera that renders the scene. + * @param {?RenderTarget} [options.renderTarget=null] - The render target to use. + * @return {Promise} A Promise that resolve with the PMREM when the generation has been finished. + * @see {@link PMREMGenerator#fromScene} + */ + async fromSceneAsync( scene, sigma = 0, near = 0.1, far = 100, options = {} ) { + + if ( this._hasInitialized === false ) await this._renderer.init(); + + return this.fromScene( scene, sigma, near, far, options ); + + } + + /** + * Generates a PMREM from an equirectangular texture, which can be either LDR + * or HDR. The ideal input image size is 1k (1024 x 512), + * as this matches best with the 256 x 256 cubemap output. + * + * @param {Texture} equirectangular - The equirectangular texture to be converted. + * @param {?RenderTarget} [renderTarget=null] - The render target to use. + * @return {RenderTarget} The resulting PMREM. + * @see {@link PMREMGenerator#fromEquirectangularAsync} + */ + fromEquirectangular( equirectangular, renderTarget = null ) { + + if ( this._hasInitialized === false ) { + + console.warn( 'THREE.PMREMGenerator: .fromEquirectangular() called before the backend is initialized. Try using .fromEquirectangularAsync() instead.' ); + + this._setSizeFromTexture( equirectangular ); + + const cubeUVRenderTarget = renderTarget || this._allocateTarget(); + + this.fromEquirectangularAsync( equirectangular, cubeUVRenderTarget ); + + return cubeUVRenderTarget; + + } + + return this._fromTexture( equirectangular, renderTarget ); + + } + + /** + * Generates a PMREM from an equirectangular texture, which can be either LDR + * or HDR. The ideal input image size is 1k (1024 x 512), + * as this matches best with the 256 x 256 cubemap output. + * + * @param {Texture} equirectangular - The equirectangular texture to be converted. + * @param {?RenderTarget} [renderTarget=null] - The render target to use. + * @return {Promise} The resulting PMREM. + * @see {@link PMREMGenerator#fromEquirectangular} + */ + async fromEquirectangularAsync( equirectangular, renderTarget = null ) { + + if ( this._hasInitialized === false ) await this._renderer.init(); + + return this._fromTexture( equirectangular, renderTarget ); + + } + + /** + * Generates a PMREM from an cubemap texture, which can be either LDR + * or HDR. The ideal input cube size is 256 x 256, + * as this matches best with the 256 x 256 cubemap output. + * + * @param {Texture} cubemap - The cubemap texture to be converted. + * @param {?RenderTarget} [renderTarget=null] - The render target to use. + * @return {RenderTarget} The resulting PMREM. + * @see {@link PMREMGenerator#fromCubemapAsync} + */ + fromCubemap( cubemap, renderTarget = null ) { + + if ( this._hasInitialized === false ) { + + console.warn( 'THREE.PMREMGenerator: .fromCubemap() called before the backend is initialized. Try using .fromCubemapAsync() instead.' ); + + this._setSizeFromTexture( cubemap ); + + const cubeUVRenderTarget = renderTarget || this._allocateTarget(); + + this.fromCubemapAsync( cubemap, renderTarget ); + + return cubeUVRenderTarget; + + } + + return this._fromTexture( cubemap, renderTarget ); + + } + + /** + * Generates a PMREM from an cubemap texture, which can be either LDR + * or HDR. The ideal input cube size is 256 x 256, + * with the 256 x 256 cubemap output. + * + * @param {Texture} cubemap - The cubemap texture to be converted. + * @param {?RenderTarget} [renderTarget=null] - The render target to use. + * @return {Promise} The resulting PMREM. + * @see {@link PMREMGenerator#fromCubemap} + */ + async fromCubemapAsync( cubemap, renderTarget = null ) { + + if ( this._hasInitialized === false ) await this._renderer.init(); + + return this._fromTexture( cubemap, renderTarget ); + + } + + /** + * Pre-compiles the cubemap shader. You can get faster start-up by invoking this method during + * your texture's network fetch for increased concurrency. + * + * @returns {Promise} + */ + async compileCubemapShader() { + + if ( this._cubemapMaterial === null ) { + + this._cubemapMaterial = _getCubemapMaterial(); + await this._compileMaterial( this._cubemapMaterial ); + + } + + } + + /** + * Pre-compiles the equirectangular shader. You can get faster start-up by invoking this method during + * your texture's network fetch for increased concurrency. + * + * @returns {Promise} + */ + async compileEquirectangularShader() { + + if ( this._equirectMaterial === null ) { + + this._equirectMaterial = _getEquirectMaterial(); + await this._compileMaterial( this._equirectMaterial ); + + } + + } + + /** + * Disposes of the PMREMGenerator's internal memory. Note that PMREMGenerator is a static class, + * so you should not need more than one PMREMGenerator object. If you do, calling dispose() on + * one of them will cause any others to also become unusable. + */ + dispose() { + + this._dispose(); + + if ( this._cubemapMaterial !== null ) this._cubemapMaterial.dispose(); + if ( this._equirectMaterial !== null ) this._equirectMaterial.dispose(); + if ( this._backgroundBox !== null ) { + + this._backgroundBox.geometry.dispose(); + this._backgroundBox.material.dispose(); + + } + + } + + // private interface + + _setSizeFromTexture( texture ) { + + if ( texture.mapping === CubeReflectionMapping || texture.mapping === CubeRefractionMapping ) { + + this._setSize( texture.image.length === 0 ? 16 : ( texture.image[ 0 ].width || texture.image[ 0 ].image.width ) ); + + } else { // Equirectangular + + this._setSize( texture.image.width / 4 ); + + } + + } + + _setSize( cubeSize ) { + + this._lodMax = Math.floor( Math.log2( cubeSize ) ); + this._cubeSize = Math.pow( 2, this._lodMax ); + + } + + _dispose() { + + if ( this._blurMaterial !== null ) this._blurMaterial.dispose(); + + if ( this._pingPongRenderTarget !== null ) this._pingPongRenderTarget.dispose(); + + for ( let i = 0; i < this._lodPlanes.length; i ++ ) { + + this._lodPlanes[ i ].dispose(); + + } + + } + + _cleanup( outputTarget ) { + + this._renderer.setRenderTarget( _oldTarget, _oldActiveCubeFace, _oldActiveMipmapLevel ); + outputTarget.scissorTest = false; + _setViewport( outputTarget, 0, 0, outputTarget.width, outputTarget.height ); + + } + + _fromTexture( texture, renderTarget ) { + + this._setSizeFromTexture( texture ); + + _oldTarget = this._renderer.getRenderTarget(); + _oldActiveCubeFace = this._renderer.getActiveCubeFace(); + _oldActiveMipmapLevel = this._renderer.getActiveMipmapLevel(); + + const cubeUVRenderTarget = renderTarget || this._allocateTarget(); + this._init( cubeUVRenderTarget ); + this._textureToCubeUV( texture, cubeUVRenderTarget ); + this._applyPMREM( cubeUVRenderTarget ); + this._cleanup( cubeUVRenderTarget ); + + return cubeUVRenderTarget; + + } + + _allocateTarget() { + + const width = 3 * Math.max( this._cubeSize, 16 * 7 ); + const height = 4 * this._cubeSize; + + const cubeUVRenderTarget = _createRenderTarget( width, height ); + + return cubeUVRenderTarget; + + } + + _init( renderTarget ) { + + if ( this._pingPongRenderTarget === null || this._pingPongRenderTarget.width !== renderTarget.width || this._pingPongRenderTarget.height !== renderTarget.height ) { + + if ( this._pingPongRenderTarget !== null ) { + + this._dispose(); + + } + + this._pingPongRenderTarget = _createRenderTarget( renderTarget.width, renderTarget.height ); + + const { _lodMax } = this; + ( { sizeLods: this._sizeLods, lodPlanes: this._lodPlanes, sigmas: this._sigmas, lodMeshes: this._lodMeshes } = _createPlanes( _lodMax ) ); + + this._blurMaterial = _getBlurShader( _lodMax, renderTarget.width, renderTarget.height ); + + } + + } + + async _compileMaterial( material ) { + + const tmpMesh = new Mesh( this._lodPlanes[ 0 ], material ); + await this._renderer.compile( tmpMesh, _flatCamera ); + + } + + _sceneToCubeUV( scene, near, far, cubeUVRenderTarget, position ) { + + const cubeCamera = _cubeCamera; + cubeCamera.near = near; + cubeCamera.far = far; + + // px, py, pz, nx, ny, nz + const upSign = [ 1, 1, 1, 1, -1, 1 ]; + const forwardSign = [ 1, -1, 1, -1, 1, -1 ]; + + const renderer = this._renderer; + + const originalAutoClear = renderer.autoClear; + + renderer.getClearColor( _clearColor$2 ); + + renderer.autoClear = false; + + let backgroundBox = this._backgroundBox; + + if ( backgroundBox === null ) { + + const backgroundMaterial = new MeshBasicMaterial( { + name: 'PMREM.Background', + side: BackSide, + depthWrite: false, + depthTest: false + } ); + + backgroundBox = new Mesh( new BoxGeometry(), backgroundMaterial ); + + } + + let useSolidColor = false; + const background = scene.background; + + if ( background ) { + + if ( background.isColor ) { + + backgroundBox.material.color.copy( background ); + scene.background = null; + useSolidColor = true; + + } + + } else { + + backgroundBox.material.color.copy( _clearColor$2 ); + useSolidColor = true; + + } + + renderer.setRenderTarget( cubeUVRenderTarget ); + + renderer.clear(); + + if ( useSolidColor ) { + + renderer.render( backgroundBox, cubeCamera ); + + } + + for ( let i = 0; i < 6; i ++ ) { + + const col = i % 3; + + if ( col === 0 ) { + + cubeCamera.up.set( 0, upSign[ i ], 0 ); + cubeCamera.position.set( position.x, position.y, position.z ); + cubeCamera.lookAt( position.x + forwardSign[ i ], position.y, position.z ); + + } else if ( col === 1 ) { + + cubeCamera.up.set( 0, 0, upSign[ i ] ); + cubeCamera.position.set( position.x, position.y, position.z ); + cubeCamera.lookAt( position.x, position.y + forwardSign[ i ], position.z ); + + + } else { + + cubeCamera.up.set( 0, upSign[ i ], 0 ); + cubeCamera.position.set( position.x, position.y, position.z ); + cubeCamera.lookAt( position.x, position.y, position.z + forwardSign[ i ] ); + + + } + + const size = this._cubeSize; + + _setViewport( cubeUVRenderTarget, col * size, i > 2 ? size : 0, size, size ); + + renderer.render( scene, cubeCamera ); + + } + + renderer.autoClear = originalAutoClear; + scene.background = background; + + } + + _textureToCubeUV( texture, cubeUVRenderTarget ) { + + const renderer = this._renderer; + + const isCubeTexture = ( texture.mapping === CubeReflectionMapping || texture.mapping === CubeRefractionMapping ); + + if ( isCubeTexture ) { + + if ( this._cubemapMaterial === null ) { + + this._cubemapMaterial = _getCubemapMaterial( texture ); + + } + + } else { + + if ( this._equirectMaterial === null ) { + + this._equirectMaterial = _getEquirectMaterial( texture ); + + } + + } + + const material = isCubeTexture ? this._cubemapMaterial : this._equirectMaterial; + material.fragmentNode.value = texture; + + const mesh = this._lodMeshes[ 0 ]; + mesh.material = material; + + const size = this._cubeSize; + + _setViewport( cubeUVRenderTarget, 0, 0, 3 * size, 2 * size ); + + renderer.setRenderTarget( cubeUVRenderTarget ); + renderer.render( mesh, _flatCamera ); + + } + + _applyPMREM( cubeUVRenderTarget ) { + + const renderer = this._renderer; + const autoClear = renderer.autoClear; + renderer.autoClear = false; + const n = this._lodPlanes.length; + + for ( let i = 1; i < n; i ++ ) { + + const sigma = Math.sqrt( this._sigmas[ i ] * this._sigmas[ i ] - this._sigmas[ i - 1 ] * this._sigmas[ i - 1 ] ); + + const poleAxis = _axisDirections[ ( n - i - 1 ) % _axisDirections.length ]; + + this._blur( cubeUVRenderTarget, i - 1, i, sigma, poleAxis ); + + } + + renderer.autoClear = autoClear; + + } + + /** + * This is a two-pass Gaussian blur for a cubemap. Normally this is done + * vertically and horizontally, but this breaks down on a cube. Here we apply + * the blur latitudinally (around the poles), and then longitudinally (towards + * the poles) to approximate the orthogonally-separable blur. It is least + * accurate at the poles, but still does a decent job. + * + * @private + * @param {RenderTarget} cubeUVRenderTarget - The cubemap render target. + * @param {number} lodIn - The input level-of-detail. + * @param {number} lodOut - The output level-of-detail. + * @param {number} sigma - The blur radius in radians. + * @param {Vector3} [poleAxis] - The pole axis. + */ + _blur( cubeUVRenderTarget, lodIn, lodOut, sigma, poleAxis ) { + + const pingPongRenderTarget = this._pingPongRenderTarget; + + this._halfBlur( + cubeUVRenderTarget, + pingPongRenderTarget, + lodIn, + lodOut, + sigma, + 'latitudinal', + poleAxis ); + + this._halfBlur( + pingPongRenderTarget, + cubeUVRenderTarget, + lodOut, + lodOut, + sigma, + 'longitudinal', + poleAxis ); + + } + + _halfBlur( targetIn, targetOut, lodIn, lodOut, sigmaRadians, direction, poleAxis ) { + + const renderer = this._renderer; + const blurMaterial = this._blurMaterial; + + if ( direction !== 'latitudinal' && direction !== 'longitudinal' ) { + + console.error( 'blur direction must be either latitudinal or longitudinal!' ); + + } + + // Number of standard deviations at which to cut off the discrete approximation. + const STANDARD_DEVIATIONS = 3; + + const blurMesh = this._lodMeshes[ lodOut ]; + blurMesh.material = blurMaterial; + + const blurUniforms = _uniformsMap.get( blurMaterial ); + + const pixels = this._sizeLods[ lodIn ] - 1; + const radiansPerPixel = isFinite( sigmaRadians ) ? Math.PI / ( 2 * pixels ) : 2 * Math.PI / ( 2 * MAX_SAMPLES - 1 ); + const sigmaPixels = sigmaRadians / radiansPerPixel; + const samples = isFinite( sigmaRadians ) ? 1 + Math.floor( STANDARD_DEVIATIONS * sigmaPixels ) : MAX_SAMPLES; + + if ( samples > MAX_SAMPLES ) { + + console.warn( `sigmaRadians, ${ + sigmaRadians}, is too large and will clip, as it requested ${ + samples} samples when the maximum is set to ${MAX_SAMPLES}` ); + + } + + const weights = []; + let sum = 0; + + for ( let i = 0; i < MAX_SAMPLES; ++ i ) { + + const x = i / sigmaPixels; + const weight = Math.exp( - x * x / 2 ); + weights.push( weight ); + + if ( i === 0 ) { + + sum += weight; + + } else if ( i < samples ) { + + sum += 2 * weight; + + } + + } + + for ( let i = 0; i < weights.length; i ++ ) { + + weights[ i ] = weights[ i ] / sum; + + } + + targetIn.texture.frame = ( targetIn.texture.frame || 0 ) + 1; + + blurUniforms.envMap.value = targetIn.texture; + blurUniforms.samples.value = samples; + blurUniforms.weights.array = weights; + blurUniforms.latitudinal.value = direction === 'latitudinal' ? 1 : 0; + + if ( poleAxis ) { + + blurUniforms.poleAxis.value = poleAxis; + + } + + const { _lodMax } = this; + blurUniforms.dTheta.value = radiansPerPixel; + blurUniforms.mipInt.value = _lodMax - lodIn; + + const outputSize = this._sizeLods[ lodOut ]; + const x = 3 * outputSize * ( lodOut > _lodMax - LOD_MIN ? lodOut - _lodMax + LOD_MIN : 0 ); + const y = 4 * ( this._cubeSize - outputSize ); + + _setViewport( targetOut, x, y, 3 * outputSize, 2 * outputSize ); + renderer.setRenderTarget( targetOut ); + renderer.render( blurMesh, _flatCamera ); + + } + +} + +function _createPlanes( lodMax ) { + + const lodPlanes = []; + const sizeLods = []; + const sigmas = []; + const lodMeshes = []; + + let lod = lodMax; + + const totalLods = lodMax - LOD_MIN + 1 + EXTRA_LOD_SIGMA.length; + + for ( let i = 0; i < totalLods; i ++ ) { + + const sizeLod = Math.pow( 2, lod ); + sizeLods.push( sizeLod ); + let sigma = 1.0 / sizeLod; + + if ( i > lodMax - LOD_MIN ) { + + sigma = EXTRA_LOD_SIGMA[ i - lodMax + LOD_MIN - 1 ]; + + } else if ( i === 0 ) { + + sigma = 0; + + } + + sigmas.push( sigma ); + + const texelSize = 1.0 / ( sizeLod - 2 ); + const min = - texelSize; + const max = 1 + texelSize; + const uv1 = [ min, min, max, min, max, max, min, min, max, max, min, max ]; + + const cubeFaces = 6; + const vertices = 6; + const positionSize = 3; + const uvSize = 2; + const faceIndexSize = 1; + + const position = new Float32Array( positionSize * vertices * cubeFaces ); + const uv = new Float32Array( uvSize * vertices * cubeFaces ); + const faceIndex = new Float32Array( faceIndexSize * vertices * cubeFaces ); + + for ( let face = 0; face < cubeFaces; face ++ ) { + + const x = ( face % 3 ) * 2 / 3 - 1; + const y = face > 2 ? 0 : -1; + const coordinates = [ + x, y, 0, + x + 2 / 3, y, 0, + x + 2 / 3, y + 1, 0, + x, y, 0, + x + 2 / 3, y + 1, 0, + x, y + 1, 0 + ]; + + const faceIdx = _faceLib[ face ]; + position.set( coordinates, positionSize * vertices * faceIdx ); + uv.set( uv1, uvSize * vertices * faceIdx ); + const fill = [ faceIdx, faceIdx, faceIdx, faceIdx, faceIdx, faceIdx ]; + faceIndex.set( fill, faceIndexSize * vertices * faceIdx ); + + } + + const planes = new BufferGeometry(); + planes.setAttribute( 'position', new BufferAttribute( position, positionSize ) ); + planes.setAttribute( 'uv', new BufferAttribute( uv, uvSize ) ); + planes.setAttribute( 'faceIndex', new BufferAttribute( faceIndex, faceIndexSize ) ); + lodPlanes.push( planes ); + lodMeshes.push( new Mesh( planes, null ) ); + + if ( lod > LOD_MIN ) { + + lod --; + + } + + } + + return { lodPlanes, sizeLods, sigmas, lodMeshes }; + +} + +function _createRenderTarget( width, height ) { + + const params = { + magFilter: LinearFilter, + minFilter: LinearFilter, + generateMipmaps: false, + type: HalfFloatType, + format: RGBAFormat, + colorSpace: LinearSRGBColorSpace, + //depthBuffer: false + }; + + const cubeUVRenderTarget = new RenderTarget( width, height, params ); + cubeUVRenderTarget.texture.mapping = CubeUVReflectionMapping; + cubeUVRenderTarget.texture.name = 'PMREM.cubeUv'; + cubeUVRenderTarget.texture.isPMREMTexture = true; + cubeUVRenderTarget.scissorTest = true; + return cubeUVRenderTarget; + +} + +function _setViewport( target, x, y, width, height ) { + + target.viewport.set( x, y, width, height ); + target.scissor.set( x, y, width, height ); + +} + +function _getMaterial( type ) { + + const material = new NodeMaterial(); + material.depthTest = false; + material.depthWrite = false; + material.blending = NoBlending; + material.name = `PMREM_${ type }`; + + return material; + +} + +function _getBlurShader( lodMax, width, height ) { + + const weights = uniformArray( new Array( MAX_SAMPLES ).fill( 0 ) ); + const poleAxis = uniform( new Vector3( 0, 1, 0 ) ); + const dTheta = uniform( 0 ); + const n = float( MAX_SAMPLES ); + const latitudinal = uniform( 0 ); // false, bool + const samples = uniform( 1 ); // int + const envMap = texture( null ); + const mipInt = uniform( 0 ); // int + const CUBEUV_TEXEL_WIDTH = float( 1 / width ); + const CUBEUV_TEXEL_HEIGHT = float( 1 / height ); + const CUBEUV_MAX_MIP = float( lodMax ); + + const materialUniforms = { + n, + latitudinal, + weights, + poleAxis, + outputDirection: _outputDirection, + dTheta, + samples, + envMap, + mipInt, + CUBEUV_TEXEL_WIDTH, + CUBEUV_TEXEL_HEIGHT, + CUBEUV_MAX_MIP + }; + + const material = _getMaterial( 'blur' ); + material.fragmentNode = blur( { ...materialUniforms, latitudinal: latitudinal.equal( 1 ) } ); + + _uniformsMap.set( material, materialUniforms ); + + return material; + +} + +function _getCubemapMaterial( envTexture ) { + + const material = _getMaterial( 'cubemap' ); + material.fragmentNode = cubeTexture( envTexture, _outputDirection ); + + return material; + +} + +function _getEquirectMaterial( envTexture ) { + + const material = _getMaterial( 'equirect' ); + material.fragmentNode = texture( envTexture, equirectUV( _outputDirection ), 0 ); + + return material; + +} + +const _cache = new WeakMap(); + +/** + * Generates the cubeUV size based on the given image height. + * + * @private + * @param {number} imageHeight - The image height. + * @return {{texelWidth: number,texelHeight: number, maxMip: number}} The result object. + */ +function _generateCubeUVSize( imageHeight ) { + + const maxMip = Math.log2( imageHeight ) - 2; + + const texelHeight = 1.0 / imageHeight; + + const texelWidth = 1.0 / ( 3 * Math.max( Math.pow( 2, maxMip ), 7 * 16 ) ); + + return { texelWidth, texelHeight, maxMip }; + +} + +/** + * Generates a PMREM from the given texture. + * + * @private + * @param {Texture} texture - The texture to create the PMREM for. + * @param {Renderer} renderer - The renderer. + * @param {PMREMGenerator} generator - The PMREM generator. + * @return {?Texture} The PMREM. + */ +function _getPMREMFromTexture( texture, renderer, generator ) { + + const cache = _getCache( renderer ); + + let cacheTexture = cache.get( texture ); + + const pmremVersion = cacheTexture !== undefined ? cacheTexture.pmremVersion : -1; + + if ( pmremVersion !== texture.pmremVersion ) { + + const image = texture.image; + + if ( texture.isCubeTexture ) { + + if ( isCubeMapReady( image ) ) { + + cacheTexture = generator.fromCubemap( texture, cacheTexture ); + + } else { + + return null; + + } + + + } else { + + if ( isEquirectangularMapReady( image ) ) { + + cacheTexture = generator.fromEquirectangular( texture, cacheTexture ); + + } else { + + return null; + + } + + } + + cacheTexture.pmremVersion = texture.pmremVersion; + + cache.set( texture, cacheTexture ); + + } + + return cacheTexture.texture; + +} + +/** + * Returns a cache that stores generated PMREMs for the respective textures. + * A cache must be maintained per renderer since PMREMs are render target textures + * which can't be shared across render contexts. + * + * @private + * @param {Renderer} renderer - The renderer. + * @return {WeakMap} The PMREM cache. + */ +function _getCache( renderer ) { + + let rendererCache = _cache.get( renderer ); + + if ( rendererCache === undefined ) { + + rendererCache = new WeakMap(); + _cache.set( renderer, rendererCache ); + + } + + return rendererCache; + +} + +/** + * This node represents a PMREM which is a special type of preprocessed + * environment map intended for PBR materials. + * + * ```js + * const material = new MeshStandardNodeMaterial(); + * material.envNode = pmremTexture( envMap ); + * ``` + * + * @augments TempNode + */ +class PMREMNode extends TempNode { + + static get type() { + + return 'PMREMNode'; + + } + + /** + * Constructs a new function overloading node. + * + * @param {Texture} value - The input texture. + * @param {Node} [uvNode=null] - The uv node. + * @param {Node} [levelNode=null] - The level node. + */ + constructor( value, uvNode = null, levelNode = null ) { + + super( 'vec3' ); + + /** + * Reference to the input texture. + * + * @private + * @type {Texture} + */ + this._value = value; + + /** + * Reference to the generated PMREM. + * + * @private + * @type {Texture | null} + * @default null + */ + this._pmrem = null; + + /** + * The uv node. + * + * @type {Node} + */ + this.uvNode = uvNode; + + /** + * The level node. + * + * @type {Node} + */ + this.levelNode = levelNode; + + /** + * Reference to a PMREM generator. + * + * @private + * @type {?PMREMGenerator} + * @default null + */ + this._generator = null; + + const defaultTexture = new Texture(); + defaultTexture.isRenderTargetTexture = true; + + /** + * The texture node holding the generated PMREM. + * + * @private + * @type {TextureNode} + */ + this._texture = texture( defaultTexture ); + + /** + * A uniform representing the PMREM's width. + * + * @private + * @type {UniformNode} + */ + this._width = uniform( 0 ); + + /** + * A uniform representing the PMREM's height. + * + * @private + * @type {UniformNode} + */ + this._height = uniform( 0 ); + + /** + * A uniform representing the PMREM's max Mip. + * + * @private + * @type {UniformNode} + */ + this._maxMip = uniform( 0 ); + + /** + * The `updateBeforeType` is set to `NodeUpdateType.RENDER`. + * + * @type {string} + * @default 'render' + */ + this.updateBeforeType = NodeUpdateType.RENDER; + + } + + set value( value ) { + + this._value = value; + this._pmrem = null; + + } + + /** + * The node's texture value. + * + * @type {Texture} + */ + get value() { + + return this._value; + + } + + /** + * Uses the given PMREM texture to update internal values. + * + * @param {Texture} texture - The PMREM texture. + */ + updateFromTexture( texture ) { + + const cubeUVSize = _generateCubeUVSize( texture.image.height ); + + this._texture.value = texture; + this._width.value = cubeUVSize.texelWidth; + this._height.value = cubeUVSize.texelHeight; + this._maxMip.value = cubeUVSize.maxMip; + + } + + updateBefore( frame ) { + + let pmrem = this._pmrem; + + const pmremVersion = pmrem ? pmrem.pmremVersion : -1; + const texture = this._value; + + if ( pmremVersion !== texture.pmremVersion ) { + + if ( texture.isPMREMTexture === true ) { + + pmrem = texture; + + } else { + + pmrem = _getPMREMFromTexture( texture, frame.renderer, this._generator ); + + } + + if ( pmrem !== null ) { + + this._pmrem = pmrem; + + this.updateFromTexture( pmrem ); + + } + + } + + } + + setup( builder ) { + + if ( this._generator === null ) { + + this._generator = new PMREMGenerator( builder.renderer ); + + } + + this.updateBefore( builder ); + + // + + let uvNode = this.uvNode; + + if ( uvNode === null && builder.context.getUV ) { + + uvNode = builder.context.getUV( this ); + + } + + // + + uvNode = materialEnvRotation.mul( vec3( uvNode.x, uvNode.y.negate(), uvNode.z ) ); + + // + + let levelNode = this.levelNode; + + if ( levelNode === null && builder.context.getTextureLevel ) { + + levelNode = builder.context.getTextureLevel( this ); + + } + + // + + return textureCubeUV( this._texture, uvNode, levelNode, this._width, this._height, this._maxMip ); + + } + + dispose() { + + super.dispose(); + + if ( this._generator !== null ) this._generator.dispose(); + + } + +} + +/** + * Returns `true` if the given cube map image has been fully loaded. + * + * @private + * @param {?Array<(Image|Object)>} [image] - The cube map image. + * @return {boolean} Whether the given cube map is ready or not. + */ +function isCubeMapReady( image ) { + + if ( image === null || image === undefined ) return false; + + let count = 0; + const length = 6; + + for ( let i = 0; i < length; i ++ ) { + + if ( image[ i ] !== undefined ) count ++; + + } + + return count === length; + + +} + +/** + * Returns `true` if the given equirectangular image has been fully loaded. + * + * @private + * @param {(Image|Object)} image - The equirectangular image. + * @return {boolean} Whether the given cube map is ready or not. + */ +function isEquirectangularMapReady( image ) { + + if ( image === null || image === undefined ) return false; + + return image.height > 0; + +} + +/** + * TSL function for creating a PMREM node. + * + * @tsl + * @function + * @param {Texture} value - The input texture. + * @param {?Node} [uvNode=null] - The uv node. + * @param {?Node} [levelNode=null] - The level node. + * @returns {PMREMNode} + */ +const pmremTexture = /*@__PURE__*/ nodeProxy( PMREMNode ).setParameterLength( 1, 3 ); + +const _envNodeCache = new WeakMap(); + +/** + * Represents a physical model for Image-based lighting (IBL). The environment + * is defined via environment maps in the equirectangular, cube map or cubeUV (PMREM) format. + * `EnvironmentNode` is intended for PBR materials like {@link MeshStandardNodeMaterial}. + * + * @augments LightingNode + */ +class EnvironmentNode extends LightingNode { + + static get type() { + + return 'EnvironmentNode'; + + } + + /** + * Constructs a new environment node. + * + * @param {Node} [envNode=null] - A node representing the environment. + */ + constructor( envNode = null ) { + + super(); + + /** + * A node representing the environment. + * + * @type {?Node} + * @default null + */ + this.envNode = envNode; + + } + + setup( builder ) { + + const { material } = builder; + + let envNode = this.envNode; + + if ( envNode.isTextureNode || envNode.isMaterialReferenceNode ) { + + const value = ( envNode.isTextureNode ) ? envNode.value : material[ envNode.property ]; + + let cacheEnvNode = _envNodeCache.get( value ); + + if ( cacheEnvNode === undefined ) { + + cacheEnvNode = pmremTexture( value ); + + _envNodeCache.set( value, cacheEnvNode ); + + } + + envNode = cacheEnvNode; + + } + + // + + const useAnisotropy = material.useAnisotropy === true || material.anisotropy > 0; + const radianceNormalView = useAnisotropy ? bentNormalView : normalView; + + const radiance = envNode.context( createRadianceContext( roughness, radianceNormalView ) ).mul( materialEnvIntensity ); + const irradiance = envNode.context( createIrradianceContext( normalWorld ) ).mul( Math.PI ).mul( materialEnvIntensity ); + + const isolateRadiance = cache( radiance ); + const isolateIrradiance = cache( irradiance ); + + // + + builder.context.radiance.addAssign( isolateRadiance ); + + builder.context.iblIrradiance.addAssign( isolateIrradiance ); + + // + + const clearcoatRadiance = builder.context.lightingModel.clearcoatRadiance; + + if ( clearcoatRadiance ) { + + const clearcoatRadianceContext = envNode.context( createRadianceContext( clearcoatRoughness, clearcoatNormalView ) ).mul( materialEnvIntensity ); + const isolateClearcoatRadiance = cache( clearcoatRadianceContext ); + + clearcoatRadiance.addAssign( isolateClearcoatRadiance ); + + } + + } + +} + +const createRadianceContext = ( roughnessNode, normalViewNode ) => { + + let reflectVec = null; + + return { + getUV: () => { + + if ( reflectVec === null ) { + + reflectVec = positionViewDirection.negate().reflect( normalViewNode ); + + // Mixing the reflection with the normal is more accurate and keeps rough objects from gathering light from behind their tangent plane. + reflectVec = roughnessNode.mul( roughnessNode ).mix( reflectVec, normalViewNode ).normalize(); + + reflectVec = reflectVec.transformDirection( cameraViewMatrix ); + + } + + return reflectVec; + + }, + getTextureLevel: () => { + + return roughnessNode; + + } + }; + +}; + +const createIrradianceContext = ( normalWorldNode ) => { + + return { + getUV: () => { + + return normalWorldNode; + + }, + getTextureLevel: () => { + + return float( 1.0 ); + + } + }; + +}; + +const _defaultValues$6 = /*@__PURE__*/ new MeshStandardMaterial(); + +/** + * Node material version of {@link MeshStandardMaterial}. + * + * @augments NodeMaterial + */ +class MeshStandardNodeMaterial extends NodeMaterial { + + static get type() { + + return 'MeshStandardNodeMaterial'; + + } + + /** + * Constructs a new mesh standard node material. + * + * @param {Object} [parameters] - The configuration parameter. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshStandardNodeMaterial = true; + + /** + * Set to `true` because standard materials react on lights. + * + * @type {boolean} + * @default true + */ + this.lights = true; + + /** + * The emissive color of standard materials is by default inferred from the `emissive`, + * `emissiveIntensity` and `emissiveMap` properties. This node property allows to + * overwrite the default and define the emissive color with a node instead. + * + * If you don't want to overwrite the emissive color but modify the existing + * value instead, use {@link materialEmissive}. + * + * @type {?Node} + * @default null + */ + this.emissiveNode = null; + + /** + * The metalness of standard materials is by default inferred from the `metalness`, + * and `metalnessMap` properties. This node property allows to + * overwrite the default and define the metalness with a node instead. + * + * If you don't want to overwrite the metalness but modify the existing + * value instead, use {@link materialMetalness}. + * + * @type {?Node} + * @default null + */ + this.metalnessNode = null; + + /** + * The roughness of standard materials is by default inferred from the `roughness`, + * and `roughnessMap` properties. This node property allows to + * overwrite the default and define the roughness with a node instead. + * + * If you don't want to overwrite the roughness but modify the existing + * value instead, use {@link materialRoughness}. + * + * @type {?Node} + * @default null + */ + this.roughnessNode = null; + + this.setDefaultValues( _defaultValues$6 ); + + this.setValues( parameters ); + + } + + /** + * Overwritten since this type of material uses {@link EnvironmentNode} + * to implement the PBR (PMREM based) environment mapping. Besides, the + * method honors `Scene.environment`. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {?EnvironmentNode} The environment node. + */ + setupEnvironment( builder ) { + + let envNode = super.setupEnvironment( builder ); + + if ( envNode === null && builder.environmentNode ) { + + envNode = builder.environmentNode; + + } + + return envNode ? new EnvironmentNode( envNode ) : null; + + } + + /** + * Setups the lighting model. + * + * @return {PhysicalLightingModel} The lighting model. + */ + setupLightingModel( /*builder*/ ) { + + return new PhysicalLightingModel(); + + } + + /** + * Setups the specular related node variables. + */ + setupSpecular() { + + const specularColorNode = mix( vec3( 0.04 ), diffuseColor.rgb, metalness ); + + specularColor.assign( specularColorNode ); + specularF90.assign( 1.0 ); + + } + + /** + * Setups the standard specific node variables. + * + * @param {NodeBuilder} builder - The current node builder. + */ + setupVariants() { + + // METALNESS + + const metalnessNode = this.metalnessNode ? float( this.metalnessNode ) : materialMetalness; + + metalness.assign( metalnessNode ); + + // ROUGHNESS + + let roughnessNode = this.roughnessNode ? float( this.roughnessNode ) : materialRoughness; + roughnessNode = getRoughness( { roughness: roughnessNode } ); + + roughness.assign( roughnessNode ); + + // SPECULAR COLOR + + this.setupSpecular(); + + // DIFFUSE COLOR + + diffuseColor.assign( vec4( diffuseColor.rgb.mul( metalnessNode.oneMinus() ), diffuseColor.a ) ); + + } + + copy( source ) { + + this.emissiveNode = source.emissiveNode; + + this.metalnessNode = source.metalnessNode; + this.roughnessNode = source.roughnessNode; + + return super.copy( source ); + + } + +} + +const _defaultValues$5 = /*@__PURE__*/ new MeshPhysicalMaterial(); + +/** + * Node material version of {@link MeshPhysicalMaterial}. + * + * @augments MeshStandardNodeMaterial + */ +class MeshPhysicalNodeMaterial extends MeshStandardNodeMaterial { + + static get type() { + + return 'MeshPhysicalNodeMaterial'; + + } + + /** + * Constructs a new mesh physical node material. + * + * @param {Object} [parameters] - The configuration parameter. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshPhysicalNodeMaterial = true; + + /** + * The clearcoat of physical materials is by default inferred from the `clearcoat` + * and `clearcoatMap` properties. This node property allows to overwrite the default + * and define the clearcoat with a node instead. + * + * If you don't want to overwrite the clearcoat but modify the existing + * value instead, use {@link materialClearcoat}. + * + * @type {?Node} + * @default null + */ + this.clearcoatNode = null; + + /** + * The clearcoat roughness of physical materials is by default inferred from the `clearcoatRoughness` + * and `clearcoatRoughnessMap` properties. This node property allows to overwrite the default + * and define the clearcoat roughness with a node instead. + * + * If you don't want to overwrite the clearcoat roughness but modify the existing + * value instead, use {@link materialClearcoatRoughness}. + * + * @type {?Node} + * @default null + */ + this.clearcoatRoughnessNode = null; + + /** + * The clearcoat normal of physical materials is by default inferred from the `clearcoatNormalMap` + * property. This node property allows to overwrite the default + * and define the clearcoat normal with a node instead. + * + * If you don't want to overwrite the clearcoat normal but modify the existing + * value instead, use {@link materialClearcoatNormal}. + * + * @type {?Node} + * @default null + */ + this.clearcoatNormalNode = null; + + /** + * The sheen of physical materials is by default inferred from the `sheen`, `sheenColor` + * and `sheenColorMap` properties. This node property allows to overwrite the default + * and define the sheen with a node instead. + * + * If you don't want to overwrite the sheen but modify the existing + * value instead, use {@link materialSheen}. + * + * @type {?Node} + * @default null + */ + this.sheenNode = null; + + /** + * The sheen roughness of physical materials is by default inferred from the `sheenRoughness` and + * `sheenRoughnessMap` properties. This node property allows to overwrite the default + * and define the sheen roughness with a node instead. + * + * If you don't want to overwrite the sheen roughness but modify the existing + * value instead, use {@link materialSheenRoughness}. + * + * @type {?Node} + * @default null + */ + this.sheenRoughnessNode = null; + + /** + * The iridescence of physical materials is by default inferred from the `iridescence` + * property. This node property allows to overwrite the default + * and define the iridescence with a node instead. + * + * If you don't want to overwrite the iridescence but modify the existing + * value instead, use {@link materialIridescence}. + * + * @type {?Node} + * @default null + */ + this.iridescenceNode = null; + + /** + * The iridescence IOR of physical materials is by default inferred from the `iridescenceIOR` + * property. This node property allows to overwrite the default + * and define the iridescence IOR with a node instead. + * + * If you don't want to overwrite the iridescence IOR but modify the existing + * value instead, use {@link materialIridescenceIOR}. + * + * @type {?Node} + * @default null + */ + this.iridescenceIORNode = null; + + /** + * The iridescence thickness of physical materials is by default inferred from the `iridescenceThicknessRange` + * and `iridescenceThicknessMap` properties. This node property allows to overwrite the default + * and define the iridescence thickness with a node instead. + * + * If you don't want to overwrite the iridescence thickness but modify the existing + * value instead, use {@link materialIridescenceThickness}. + * + * @type {?Node} + * @default null + */ + this.iridescenceThicknessNode = null; + + /** + * The specular intensity of physical materials is by default inferred from the `specularIntensity` + * and `specularIntensityMap` properties. This node property allows to overwrite the default + * and define the specular intensity with a node instead. + * + * If you don't want to overwrite the specular intensity but modify the existing + * value instead, use {@link materialSpecularIntensity}. + * + * @type {?Node} + * @default null + */ + this.specularIntensityNode = null; + + /** + * The specular color of physical materials is by default inferred from the `specularColor` + * and `specularColorMap` properties. This node property allows to overwrite the default + * and define the specular color with a node instead. + * + * If you don't want to overwrite the specular color but modify the existing + * value instead, use {@link materialSpecularColor}. + * + * @type {?Node} + * @default null + */ + this.specularColorNode = null; + + /** + * The ior of physical materials is by default inferred from the `ior` + * property. This node property allows to overwrite the default + * and define the ior with a node instead. + * + * If you don't want to overwrite the ior but modify the existing + * value instead, use {@link materialIOR}. + * + * @type {?Node} + * @default null + */ + this.iorNode = null; + + /** + * The transmission of physical materials is by default inferred from the `transmission` and + * `transmissionMap` properties. This node property allows to overwrite the default + * and define the transmission with a node instead. + * + * If you don't want to overwrite the transmission but modify the existing + * value instead, use {@link materialTransmission}. + * + * @type {?Node} + * @default null + */ + this.transmissionNode = null; + + /** + * The thickness of physical materials is by default inferred from the `thickness` and + * `thicknessMap` properties. This node property allows to overwrite the default + * and define the thickness with a node instead. + * + * If you don't want to overwrite the thickness but modify the existing + * value instead, use {@link materialThickness}. + * + * @type {?Node} + * @default null + */ + this.thicknessNode = null; + + /** + * The attenuation distance of physical materials is by default inferred from the + * `attenuationDistance` property. This node property allows to overwrite the default + * and define the attenuation distance with a node instead. + * + * If you don't want to overwrite the attenuation distance but modify the existing + * value instead, use {@link materialAttenuationDistance}. + * + * @type {?Node} + * @default null + */ + this.attenuationDistanceNode = null; + + /** + * The attenuation color of physical materials is by default inferred from the + * `attenuationColor` property. This node property allows to overwrite the default + * and define the attenuation color with a node instead. + * + * If you don't want to overwrite the attenuation color but modify the existing + * value instead, use {@link materialAttenuationColor}. + * + * @type {?Node} + * @default null + */ + this.attenuationColorNode = null; + + /** + * The dispersion of physical materials is by default inferred from the + * `dispersion` property. This node property allows to overwrite the default + * and define the dispersion with a node instead. + * + * If you don't want to overwrite the dispersion but modify the existing + * value instead, use {@link materialDispersion}. + * + * @type {?Node} + * @default null + */ + this.dispersionNode = null; + + /** + * The anisotropy of physical materials is by default inferred from the + * `anisotropy` property. This node property allows to overwrite the default + * and define the anisotropy with a node instead. + * + * If you don't want to overwrite the anisotropy but modify the existing + * value instead, use {@link materialAnisotropy}. + * + * @type {?Node} + * @default null + */ + this.anisotropyNode = null; + + this.setDefaultValues( _defaultValues$5 ); + + this.setValues( parameters ); + + } + + /** + * Whether the lighting model should use clearcoat or not. + * + * @type {boolean} + * @default true + */ + get useClearcoat() { + + return this.clearcoat > 0 || this.clearcoatNode !== null; + + } + + /** + * Whether the lighting model should use iridescence or not. + * + * @type {boolean} + * @default true + */ + get useIridescence() { + + return this.iridescence > 0 || this.iridescenceNode !== null; + + } + + /** + * Whether the lighting model should use sheen or not. + * + * @type {boolean} + * @default true + */ + get useSheen() { + + return this.sheen > 0 || this.sheenNode !== null; + + } + + /** + * Whether the lighting model should use anisotropy or not. + * + * @type {boolean} + * @default true + */ + get useAnisotropy() { + + return this.anisotropy > 0 || this.anisotropyNode !== null; + + } + + /** + * Whether the lighting model should use transmission or not. + * + * @type {boolean} + * @default true + */ + get useTransmission() { + + return this.transmission > 0 || this.transmissionNode !== null; + + } + + /** + * Whether the lighting model should use dispersion or not. + * + * @type {boolean} + * @default true + */ + get useDispersion() { + + return this.dispersion > 0 || this.dispersionNode !== null; + + } + + /** + * Setups the specular related node variables. + */ + setupSpecular() { + + const iorNode = this.iorNode ? float( this.iorNode ) : materialIOR; + + ior.assign( iorNode ); + specularColor.assign( mix( min$1( pow2( ior.sub( 1.0 ).div( ior.add( 1.0 ) ) ).mul( materialSpecularColor ), vec3( 1.0 ) ).mul( materialSpecularIntensity ), diffuseColor.rgb, metalness ) ); + specularF90.assign( mix( materialSpecularIntensity, 1.0, metalness ) ); + + } + + /** + * Setups the lighting model. + * + * @return {PhysicalLightingModel} The lighting model. + */ + setupLightingModel( /*builder*/ ) { + + return new PhysicalLightingModel( this.useClearcoat, this.useSheen, this.useIridescence, this.useAnisotropy, this.useTransmission, this.useDispersion ); + + } + + /** + * Setups the physical specific node variables. + * + * @param {NodeBuilder} builder - The current node builder. + */ + setupVariants( builder ) { + + super.setupVariants( builder ); + + // CLEARCOAT + + if ( this.useClearcoat ) { + + const clearcoatNode = this.clearcoatNode ? float( this.clearcoatNode ) : materialClearcoat; + const clearcoatRoughnessNode = this.clearcoatRoughnessNode ? float( this.clearcoatRoughnessNode ) : materialClearcoatRoughness; + + clearcoat.assign( clearcoatNode ); + clearcoatRoughness.assign( getRoughness( { roughness: clearcoatRoughnessNode } ) ); + + } + + // SHEEN + + if ( this.useSheen ) { + + const sheenNode = this.sheenNode ? vec3( this.sheenNode ) : materialSheen; + const sheenRoughnessNode = this.sheenRoughnessNode ? float( this.sheenRoughnessNode ) : materialSheenRoughness; + + sheen.assign( sheenNode ); + sheenRoughness.assign( sheenRoughnessNode ); + + } + + // IRIDESCENCE + + if ( this.useIridescence ) { + + const iridescenceNode = this.iridescenceNode ? float( this.iridescenceNode ) : materialIridescence; + const iridescenceIORNode = this.iridescenceIORNode ? float( this.iridescenceIORNode ) : materialIridescenceIOR; + const iridescenceThicknessNode = this.iridescenceThicknessNode ? float( this.iridescenceThicknessNode ) : materialIridescenceThickness; + + iridescence.assign( iridescenceNode ); + iridescenceIOR.assign( iridescenceIORNode ); + iridescenceThickness.assign( iridescenceThicknessNode ); + + } + + // ANISOTROPY + + if ( this.useAnisotropy ) { + + const anisotropyV = ( this.anisotropyNode ? vec2( this.anisotropyNode ) : materialAnisotropy ).toVar(); + + anisotropy.assign( anisotropyV.length() ); + + If( anisotropy.equal( 0.0 ), () => { + + anisotropyV.assign( vec2( 1.0, 0.0 ) ); + + } ).Else( () => { + + anisotropyV.divAssign( vec2( anisotropy ) ); + anisotropy.assign( anisotropy.saturate() ); + + } ); + + // Roughness along the anisotropy bitangent is the material roughness, while the tangent roughness increases with anisotropy. + alphaT.assign( anisotropy.pow2().mix( roughness.pow2(), 1.0 ) ); + + anisotropyT.assign( TBNViewMatrix[ 0 ].mul( anisotropyV.x ).add( TBNViewMatrix[ 1 ].mul( anisotropyV.y ) ) ); + anisotropyB.assign( TBNViewMatrix[ 1 ].mul( anisotropyV.x ).sub( TBNViewMatrix[ 0 ].mul( anisotropyV.y ) ) ); + + } + + // TRANSMISSION + + if ( this.useTransmission ) { + + const transmissionNode = this.transmissionNode ? float( this.transmissionNode ) : materialTransmission; + const thicknessNode = this.thicknessNode ? float( this.thicknessNode ) : materialThickness; + const attenuationDistanceNode = this.attenuationDistanceNode ? float( this.attenuationDistanceNode ) : materialAttenuationDistance; + const attenuationColorNode = this.attenuationColorNode ? vec3( this.attenuationColorNode ) : materialAttenuationColor; + + transmission.assign( transmissionNode ); + thickness.assign( thicknessNode ); + attenuationDistance.assign( attenuationDistanceNode ); + attenuationColor.assign( attenuationColorNode ); + + if ( this.useDispersion ) { + + const dispersionNode = this.dispersionNode ? float( this.dispersionNode ) : materialDispersion; + + dispersion.assign( dispersionNode ); + + } + + } + + } + + /** + * Setups the clearcoat normal node. + * + * @return {Node} The clearcoat normal. + */ + setupClearcoatNormal() { + + return this.clearcoatNormalNode ? vec3( this.clearcoatNormalNode ) : materialClearcoatNormal; + + } + + setup( builder ) { + + builder.context.setupClearcoatNormal = () => subBuild( this.setupClearcoatNormal( builder ), 'NORMAL', 'vec3' ); + + super.setup( builder ); + + } + + copy( source ) { + + this.clearcoatNode = source.clearcoatNode; + this.clearcoatRoughnessNode = source.clearcoatRoughnessNode; + this.clearcoatNormalNode = source.clearcoatNormalNode; + + this.sheenNode = source.sheenNode; + this.sheenRoughnessNode = source.sheenRoughnessNode; + + this.iridescenceNode = source.iridescenceNode; + this.iridescenceIORNode = source.iridescenceIORNode; + this.iridescenceThicknessNode = source.iridescenceThicknessNode; + + this.specularIntensityNode = source.specularIntensityNode; + this.specularColorNode = source.specularColorNode; + + this.transmissionNode = source.transmissionNode; + this.thicknessNode = source.thicknessNode; + this.attenuationDistanceNode = source.attenuationDistanceNode; + this.attenuationColorNode = source.attenuationColorNode; + this.dispersionNode = source.dispersionNode; + + this.anisotropyNode = source.anisotropyNode; + + return super.copy( source ); + + } + +} + +/** + * Represents the lighting model for {@link MeshSSSNodeMaterial}. + * + * @augments PhysicalLightingModel + */ +class SSSLightingModel extends PhysicalLightingModel { + + /** + * Constructs a new physical lighting model. + * + * @param {boolean} [clearcoat=false] - Whether clearcoat is supported or not. + * @param {boolean} [sheen=false] - Whether sheen is supported or not. + * @param {boolean} [iridescence=false] - Whether iridescence is supported or not. + * @param {boolean} [anisotropy=false] - Whether anisotropy is supported or not. + * @param {boolean} [transmission=false] - Whether transmission is supported or not. + * @param {boolean} [dispersion=false] - Whether dispersion is supported or not. + * @param {boolean} [sss=false] - Whether SSS is supported or not. + */ + constructor( clearcoat = false, sheen = false, iridescence = false, anisotropy = false, transmission = false, dispersion = false, sss = false ) { + + super( clearcoat, sheen, iridescence, anisotropy, transmission, dispersion ); + + /** + * Whether the lighting model should use SSS or not. + * + * @type {boolean} + * @default false + */ + this.useSSS = sss; + + } + + /** + * Extends the default implementation with a SSS term. + * + * Reference: [Approximating Translucency for a Fast, Cheap and Convincing Subsurface Scattering Look]{@link https://colinbarrebrisebois.com/2011/03/07/gdc-2011-approximating-translucency-for-a-fast-cheap-and-convincing-subsurface-scattering-look/} + * + * @param {Object} input - The input data. + * @param {NodeBuilder} builder - The current node builder. + */ + direct( { lightDirection, lightColor, reflectedLight }, builder ) { + + if ( this.useSSS === true ) { + + const material = builder.material; + + const { thicknessColorNode, thicknessDistortionNode, thicknessAmbientNode, thicknessAttenuationNode, thicknessPowerNode, thicknessScaleNode } = material; + + const scatteringHalf = lightDirection.add( normalView.mul( thicknessDistortionNode ) ).normalize(); + const scatteringDot = float( positionViewDirection.dot( scatteringHalf.negate() ).saturate().pow( thicknessPowerNode ).mul( thicknessScaleNode ) ); + const scatteringIllu = vec3( scatteringDot.add( thicknessAmbientNode ).mul( thicknessColorNode ) ); + + reflectedLight.directDiffuse.addAssign( scatteringIllu.mul( thicknessAttenuationNode.mul( lightColor ) ) ); + + } + + super.direct( { lightDirection, lightColor, reflectedLight }, builder ); + + } + +} + +/** + * This node material is an experimental extension of {@link MeshPhysicalNodeMaterial} + * that implements a Subsurface scattering (SSS) term. + * + * @augments MeshPhysicalNodeMaterial + */ +class MeshSSSNodeMaterial extends MeshPhysicalNodeMaterial { + + static get type() { + + return 'MeshSSSNodeMaterial'; + + } + + /** + * Constructs a new mesh SSS node material. + * + * @param {Object} [parameters] - The configuration parameter. + */ + constructor( parameters ) { + + super( parameters ); + + /** + * Represents the thickness color. + * + * @type {?Node} + * @default null + */ + this.thicknessColorNode = null; + + /** + * Represents the distortion factor. + * + * @type {?Node} + */ + this.thicknessDistortionNode = float( 0.1 ); + + /** + * Represents the thickness ambient factor. + * + * @type {?Node} + */ + this.thicknessAmbientNode = float( 0.0 ); + + /** + * Represents the thickness attenuation. + * + * @type {?Node} + */ + this.thicknessAttenuationNode = float( .1 ); + + /** + * Represents the thickness power. + * + * @type {?Node} + */ + this.thicknessPowerNode = float( 2.0 ); + + /** + * Represents the thickness scale. + * + * @type {?Node} + */ + this.thicknessScaleNode = float( 10.0 ); + + } + + /** + * Whether the lighting model should use SSS or not. + * + * @type {boolean} + * @default true + */ + get useSSS() { + + return this.thicknessColorNode !== null; + + } + + /** + * Setups the lighting model. + * + * @return {SSSLightingModel} The lighting model. + */ + setupLightingModel( /*builder*/ ) { + + return new SSSLightingModel( this.useClearcoat, this.useSheen, this.useIridescence, this.useAnisotropy, this.useTransmission, this.useDispersion, this.useSSS ); + + } + + copy( source ) { + + this.thicknessColorNode = source.thicknessColorNode; + this.thicknessDistortionNode = source.thicknessDistortionNode; + this.thicknessAmbientNode = source.thicknessAmbientNode; + this.thicknessAttenuationNode = source.thicknessAttenuationNode; + this.thicknessPowerNode = source.thicknessPowerNode; + this.thicknessScaleNode = source.thicknessScaleNode; + + return super.copy( source ); + + } + +} + +const getGradientIrradiance = /*@__PURE__*/ Fn( ( { normal, lightDirection, builder } ) => { + + // dotNL will be from -1.0 to 1.0 + const dotNL = normal.dot( lightDirection ); + const coord = vec2( dotNL.mul( 0.5 ).add( 0.5 ), 0.0 ); + + if ( builder.material.gradientMap ) { + + const gradientMap = materialReference( 'gradientMap', 'texture' ).context( { getUV: () => coord } ); + + return vec3( gradientMap.r ); + + } else { + + const fw = coord.fwidth().mul( 0.5 ); + + return mix( vec3( 0.7 ), vec3( 1.0 ), smoothstep( float( 0.7 ).sub( fw.x ), float( 0.7 ).add( fw.x ), coord.x ) ); + + } + +} ); + +/** + * Represents the lighting model for a toon material. Used in {@link MeshToonNodeMaterial}. + * + * @augments LightingModel + */ +class ToonLightingModel extends LightingModel { + + /** + * Implements the direct lighting. Instead of using a conventional smooth irradiance, the irradiance is + * reduced to a small number of discrete shades to create a comic-like, flat look. + * + * @param {Object} lightData - The light data. + * @param {NodeBuilder} builder - The current node builder. + */ + direct( { lightDirection, lightColor, reflectedLight }, builder ) { + + const irradiance = getGradientIrradiance( { normal: normalGeometry, lightDirection, builder } ).mul( lightColor ); + + reflectedLight.directDiffuse.addAssign( irradiance.mul( BRDF_Lambert( { diffuseColor: diffuseColor.rgb } ) ) ); + + } + + /** + * Implements the indirect lighting. + * + * @param {NodeBuilder} builder - The current node builder. + */ + indirect( builder ) { + + const { ambientOcclusion, irradiance, reflectedLight } = builder.context; + + reflectedLight.indirectDiffuse.addAssign( irradiance.mul( BRDF_Lambert( { diffuseColor } ) ) ); + + reflectedLight.indirectDiffuse.mulAssign( ambientOcclusion ); + + } + +} + +const _defaultValues$4 = /*@__PURE__*/ new MeshToonMaterial(); + +/** + * Node material version of {@link MeshToonMaterial}. + * + * @augments NodeMaterial + */ +class MeshToonNodeMaterial extends NodeMaterial { + + static get type() { + + return 'MeshToonNodeMaterial'; + + } + + /** + * Constructs a new mesh toon node material. + * + * @param {Object} [parameters] - The configuration parameter. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshToonNodeMaterial = true; + + /** + * Set to `true` because toon materials react on lights. + * + * @type {boolean} + * @default true + */ + this.lights = true; + + this.setDefaultValues( _defaultValues$4 ); + + this.setValues( parameters ); + + } + + /** + * Setups the lighting model. + * + * @return {ToonLightingModel} The lighting model. + */ + setupLightingModel( /*builder*/ ) { + + return new ToonLightingModel(); + + } + +} + +/** + * TSL function for creating a matcap uv node. + * + * Can be used to compute texture coordinates for projecting a + * matcap onto a mesh. Used by {@link MeshMatcapNodeMaterial}. + * + * @tsl + * @function + * @returns {Node} The matcap UV coordinates. + */ +const matcapUV = /*@__PURE__*/ Fn( () => { + + const x = vec3( positionViewDirection.z, 0, positionViewDirection.x.negate() ).normalize(); + const y = positionViewDirection.cross( x ); + + return vec2( x.dot( normalView ), y.dot( normalView ) ).mul( 0.495 ).add( 0.5 ); // 0.495 to remove artifacts caused by undersized matcap disks + +} ).once( [ 'NORMAL', 'VERTEX' ] )().toVar( 'matcapUV' ); + +const _defaultValues$3 = /*@__PURE__*/ new MeshMatcapMaterial(); + +/** + * Node material version of {@link MeshMatcapMaterial}. + * + * @augments NodeMaterial + */ +class MeshMatcapNodeMaterial extends NodeMaterial { + + static get type() { + + return 'MeshMatcapNodeMaterial'; + + } + + /** + * Constructs a new mesh normal node material. + * + * @param {Object} [parameters] - The configuration parameter. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshMatcapNodeMaterial = true; + + this.setDefaultValues( _defaultValues$3 ); + + this.setValues( parameters ); + + } + + /** + * Setups the matcap specific node variables. + * + * @param {NodeBuilder} builder - The current node builder. + */ + setupVariants( builder ) { + + const uv = matcapUV; + + let matcapColor; + + if ( builder.material.matcap ) { + + matcapColor = materialReference( 'matcap', 'texture' ).context( { getUV: () => uv } ); + + } else { + + matcapColor = vec3( mix( 0.2, 0.8, uv.y ) ); // default if matcap is missing + + } + + diffuseColor.rgb.mulAssign( matcapColor.rgb ); + + } + +} + +/** + * Applies a rotation to the given position node. + * + * @augments TempNode + */ +class RotateNode extends TempNode { + + static get type() { + + return 'RotateNode'; + + } + + /** + * Constructs a new rotate node. + * + * @param {Node} positionNode - The position node. + * @param {Node} rotationNode - Represents the rotation that is applied to the position node. Depending + * on whether the position data are 2D or 3D, the rotation is expressed a single float value or an Euler value. + */ + constructor( positionNode, rotationNode ) { + + super(); + + /** + * The position node. + * + * @type {Node} + */ + this.positionNode = positionNode; + + /** + * Represents the rotation that is applied to the position node. + * Depending on whether the position data are 2D or 3D, the rotation is expressed a single float value or an Euler value. + * + * @type {Node} + */ + this.rotationNode = rotationNode; + + } + + /** + * The type of the {@link RotateNode#positionNode} defines the node's type. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node's type. + */ + getNodeType( builder ) { + + return this.positionNode.getNodeType( builder ); + + } + + setup( builder ) { + + const { rotationNode, positionNode } = this; + + const nodeType = this.getNodeType( builder ); + + if ( nodeType === 'vec2' ) { + + const cosAngle = rotationNode.cos(); + const sinAngle = rotationNode.sin(); + + const rotationMatrix = mat2( + cosAngle, sinAngle, + sinAngle.negate(), cosAngle + ); + + return rotationMatrix.mul( positionNode ); + + } else { + + const rotation = rotationNode; + const rotationXMatrix = mat4( vec4( 1.0, 0.0, 0.0, 0.0 ), vec4( 0.0, cos( rotation.x ), sin( rotation.x ).negate(), 0.0 ), vec4( 0.0, sin( rotation.x ), cos( rotation.x ), 0.0 ), vec4( 0.0, 0.0, 0.0, 1.0 ) ); + const rotationYMatrix = mat4( vec4( cos( rotation.y ), 0.0, sin( rotation.y ), 0.0 ), vec4( 0.0, 1.0, 0.0, 0.0 ), vec4( sin( rotation.y ).negate(), 0.0, cos( rotation.y ), 0.0 ), vec4( 0.0, 0.0, 0.0, 1.0 ) ); + const rotationZMatrix = mat4( vec4( cos( rotation.z ), sin( rotation.z ).negate(), 0.0, 0.0 ), vec4( sin( rotation.z ), cos( rotation.z ), 0.0, 0.0 ), vec4( 0.0, 0.0, 1.0, 0.0 ), vec4( 0.0, 0.0, 0.0, 1.0 ) ); + + return rotationXMatrix.mul( rotationYMatrix ).mul( rotationZMatrix ).mul( vec4( positionNode, 1.0 ) ).xyz; + + } + + } + +} + +/** + * TSL function for creating a rotate node. + * + * @tsl + * @function + * @param {Node} positionNode - The position node. + * @param {Node} rotationNode - Represents the rotation that is applied to the position node. Depending + * on whether the position data are 2D or 3D, the rotation is expressed a single float value or an Euler value. + * @returns {RotateNode} + */ +const rotate = /*@__PURE__*/ nodeProxy( RotateNode ).setParameterLength( 2 ); + +const _defaultValues$2 = /*@__PURE__*/ new SpriteMaterial(); + +/** + * Node material version of {@link SpriteMaterial}. + * + * @augments NodeMaterial + */ +class SpriteNodeMaterial extends NodeMaterial { + + static get type() { + + return 'SpriteNodeMaterial'; + + } + + /** + * Constructs a new sprite node material. + * + * @param {Object} [parameters] - The configuration parameter. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSpriteNodeMaterial = true; + + this._useSizeAttenuation = true; + + /** + * This property makes it possible to define the position of the sprite with a + * node. That can be useful when the material is used with instanced rendering + * and node data are defined with an instanced attribute node: + * ```js + * const positionAttribute = new InstancedBufferAttribute( new Float32Array( positions ), 3 ); + * material.positionNode = instancedBufferAttribute( positionAttribute ); + * ``` + * Another possibility is to compute the instanced data with a compute shader: + * ```js + * const positionBuffer = instancedArray( particleCount, 'vec3' ); + * particleMaterial.positionNode = positionBuffer.toAttribute(); + * ``` + * + * @type {?Node} + * @default null + */ + this.positionNode = null; + + /** + * The rotation of sprite materials is by default inferred from the `rotation`, + * property. This node property allows to overwrite the default and define + * the rotation with a node instead. + * + * If you don't want to overwrite the rotation but modify the existing + * value instead, use {@link materialRotation}. + * + * @type {?Node} + * @default null + */ + this.rotationNode = null; + + /** + * This node property provides an additional way to scale sprites next to + * `Object3D.scale`. The scale transformation based in `Object3D.scale` + * is multiplied with the scale value of this node in the vertex shader. + * + * @type {?Node} + * @default null + */ + this.scaleNode = null; + + /** + * In Sprites, the transparent property is enabled by default. + * + * @type {boolean} + * @default true + */ + this.transparent = true; + + this.setDefaultValues( _defaultValues$2 ); + + this.setValues( parameters ); + + } + + /** + * Setups the position node in view space. This method implements + * the sprite specific vertex shader. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {Node} The position in view space. + */ + setupPositionView( builder ) { + + const { object, camera } = builder; + + const sizeAttenuation = this.sizeAttenuation; + + const { positionNode, rotationNode, scaleNode } = this; + + const mvPosition = modelViewMatrix.mul( vec3( positionNode || 0 ) ); + + let scale = vec2( modelWorldMatrix[ 0 ].xyz.length(), modelWorldMatrix[ 1 ].xyz.length() ); + + if ( scaleNode !== null ) { + + scale = scale.mul( vec2( scaleNode ) ); + + } + + if ( sizeAttenuation === false ) { + + if ( camera.isPerspectiveCamera ) { + + scale = scale.mul( mvPosition.z.negate() ); + + } else { + + const orthoScale = float( 2.0 ).div( cameraProjectionMatrix.element( 1 ).element( 1 ) ); + scale = scale.mul( orthoScale.mul( 2 ) ); + + } + + } + + let alignedPosition = positionGeometry.xy; + + if ( object.center && object.center.isVector2 === true ) { + + const center = reference$1( 'center', 'vec2', object ); + + alignedPosition = alignedPosition.sub( center.sub( 0.5 ) ); + + } + + alignedPosition = alignedPosition.mul( scale ); + + const rotation = float( rotationNode || materialRotation ); + + const rotatedPosition = rotate( alignedPosition, rotation ); + + return vec4( mvPosition.xy.add( rotatedPosition ), mvPosition.zw ); + + } + + copy( source ) { + + this.positionNode = source.positionNode; + this.rotationNode = source.rotationNode; + this.scaleNode = source.scaleNode; + + return super.copy( source ); + + } + + /** + * Whether to use size attenuation or not. + * + * @type {boolean} + * @default true + */ + get sizeAttenuation() { + + return this._useSizeAttenuation; + + } + + set sizeAttenuation( value ) { + + if ( this._useSizeAttenuation !== value ) { + + this._useSizeAttenuation = value; + this.needsUpdate = true; + + } + + } + +} + +const _defaultValues$1 = /*@__PURE__*/ new PointsMaterial(); + +/** + * Node material version of {@link PointsMaterial}. + * + * @augments SpriteNodeMaterial + */ +class PointsNodeMaterial extends SpriteNodeMaterial { + + static get type() { + + return 'PointsNodeMaterial'; + + } + + /** + * Constructs a new points node material. + * + * @param {Object} [parameters] - The configuration parameter. + */ + constructor( parameters ) { + + super(); + + /** + * This node property provides an additional way to set the point size. + * + * @type {?Node} + * @default null + */ + this.sizeNode = null; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isPointsNodeMaterial = true; + + this.setDefaultValues( _defaultValues$1 ); + + this.setValues( parameters ); + + } + + setupPositionView() { + + const { positionNode } = this; + + return modelViewMatrix.mul( vec3( positionNode || positionLocal ) ).xyz; + + } + + setupVertex( builder ) { + + const mvp = super.setupVertex( builder ); + + // skip further processing if the material is not a node material + + if ( builder.material.isNodeMaterial !== true ) { + + return mvp; + + } + + // ndc space + + const { rotationNode, scaleNode, sizeNode } = this; + + const alignedPosition = positionGeometry.xy.toVar(); + const aspect = viewport.z.div( viewport.w ); + + // rotation + + if ( rotationNode && rotationNode.isNode ) { + + const rotation = float( rotationNode ); + + alignedPosition.assign( rotate( alignedPosition, rotation ) ); + + } + + // point size + + let pointSize = sizeNode !== null ? vec2( sizeNode ) : materialPointSize; + + if ( this.sizeAttenuation === true ) { + + pointSize = pointSize.mul( pointSize.div( positionView.z.negate() ) ); + + } + + // scale + + if ( scaleNode && scaleNode.isNode ) { + + pointSize = pointSize.mul( vec2( scaleNode ) ); + + } + + alignedPosition.mulAssign( pointSize.mul( 2 ) ); + + alignedPosition.assign( alignedPosition.div( viewport.z ) ); + alignedPosition.y.assign( alignedPosition.y.mul( aspect ) ); + + // back to clip space + alignedPosition.assign( alignedPosition.mul( mvp.w ) ); + + //clipPos.xy += offset; + mvp.addAssign( vec4( alignedPosition, 0, 0 ) ); + + return mvp; + + } + + /** + * Whether alpha to coverage should be used or not. + * + * @type {boolean} + * @default true + */ + get alphaToCoverage() { + + return this._useAlphaToCoverage; + + } + + set alphaToCoverage( value ) { + + if ( this._useAlphaToCoverage !== value ) { + + this._useAlphaToCoverage = value; + this.needsUpdate = true; + + } + + } + +} + +/** + * Represents lighting model for a shadow material. Used in {@link ShadowNodeMaterial}. + * + * @augments LightingModel + */ +class ShadowMaskModel extends LightingModel { + + /** + * Constructs a new shadow mask model. + */ + constructor() { + + super(); + + /** + * The shadow mask node. + * + * @type {Node} + */ + this.shadowNode = float( 1 ).toVar( 'shadowMask' ); + + } + + /** + * Only used to save the shadow mask. + * + * @param {Object} input - The input data. + */ + direct( { lightNode } ) { + + if ( lightNode.shadowNode !== null ) { + + this.shadowNode.mulAssign( lightNode.shadowNode ); + + } + + } + + /** + * Uses the shadow mask to produce the final color. + * + * @param {NodeBuilder} builder - The current node builder. + */ + finish( { context } ) { + + diffuseColor.a.mulAssign( this.shadowNode.oneMinus() ); + + context.outgoingLight.rgb.assign( diffuseColor.rgb ); // TODO: Optimize LightsNode to avoid this assignment + + } + +} + +const _defaultValues = /*@__PURE__*/ new ShadowMaterial(); + +/** + * Node material version of {@link ShadowMaterial}. + * + * @augments NodeMaterial + */ +class ShadowNodeMaterial extends NodeMaterial { + + static get type() { + + return 'ShadowNodeMaterial'; + + } + + /** + * Constructs a new shadow node material. + * + * @param {Object} [parameters] - The configuration parameter. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isShadowNodeMaterial = true; + + /** + * Set to `true` because so it's possible to implement + * the shadow mask effect. + * + * @type {boolean} + * @default true + */ + this.lights = true; + + /** + * Overwritten since shadow materials are transparent + * by default. + * + * @type {boolean} + * @default true + */ + this.transparent = true; + + this.setDefaultValues( _defaultValues ); + + this.setValues( parameters ); + + } + + /** + * Setups the lighting model. + * + * @return {ShadowMaskModel} The lighting model. + */ + setupLightingModel( /*builder*/ ) { + + return new ShadowMaskModel(); + + } + +} + +const scatteringDensity = property( 'vec3' ); +const linearDepthRay = property( 'vec3' ); +const outgoingRayLight = property( 'vec3' ); + +/** + * VolumetricLightingModel class extends the LightingModel to implement volumetric lighting effects. + * This model calculates the scattering and transmittance of light through a volumetric medium. + * It dynamically adjusts the direction of the ray based on the camera and object positions. + * The model supports custom scattering and depth nodes to enhance the lighting effects. + * + * @augments LightingModel + */ +class VolumetricLightingModel extends LightingModel { + + constructor() { + + super(); + + } + + start( builder ) { + + const { material, context } = builder; + + const startPos = property( 'vec3' ); + const endPos = property( 'vec3' ); + + // This approach dynamically changes the direction of the ray, + // prioritizing the ray from the camera to the object if it is inside the mesh, and from the object to the camera if it is far away. + + If( cameraPosition.sub( positionWorld ).length().greaterThan( modelRadius.mul( 2 ) ), () => { + + startPos.assign( cameraPosition ); + endPos.assign( positionWorld ); + + } ).Else( () => { + + startPos.assign( positionWorld ); + endPos.assign( cameraPosition ); + + } ); + + // + + const viewVector = endPos.sub( startPos ); + + const steps = uniform( 'int' ).onRenderUpdate( ( { material } ) => material.steps ); + const stepSize = viewVector.length().div( steps ).toVar(); + + const rayDir = viewVector.normalize().toVar(); // TODO: toVar() should be automatic here ( in loop ) + + const distTravelled = float( 0.0 ).toVar(); + const transmittance = vec3( 1 ).toVar(); + + if ( material.offsetNode ) { + + // reduce banding + + distTravelled.addAssign( material.offsetNode.mul( stepSize ) ); + + } + + Loop( steps, () => { + + const positionRay = startPos.add( rayDir.mul( distTravelled ) ); + const positionViewRay = cameraViewMatrix.mul( vec4( positionRay, 1 ) ).xyz; + + if ( material.depthNode !== null ) { + + linearDepthRay.assign( linearDepth( viewZToPerspectiveDepth( positionViewRay.z, cameraNear, cameraFar ) ) ); + + context.sceneDepthNode = linearDepth( material.depthNode ).toVar(); + + } + + context.positionWorld = positionRay; + context.shadowPositionWorld = positionRay; + context.positionView = positionViewRay; + + scatteringDensity.assign( 0 ); + + let scatteringNode; + + if ( material.scatteringNode ) { + + scatteringNode = material.scatteringNode( { + positionRay + } ); + + } + + super.start( builder ); + + if ( scatteringNode ) { + + scatteringDensity.mulAssign( scatteringNode ); + + } + + // beer's law + + const falloff = scatteringDensity.mul( .01 ).negate().mul( stepSize ).exp(); + transmittance.mulAssign( falloff ); + + // move along the ray + + distTravelled.addAssign( stepSize ); + + } ); + + outgoingRayLight.addAssign( transmittance.saturate().oneMinus() ); + + } + + scatteringLight( lightColor, builder ) { + + const sceneDepthNode = builder.context.sceneDepthNode; + + if ( sceneDepthNode ) { + + If( sceneDepthNode.greaterThanEqual( linearDepthRay ), () => { + + scatteringDensity.addAssign( lightColor ); + + } ); + + } else { + + scatteringDensity.addAssign( lightColor ); + + } + + } + + direct( { lightNode, lightColor }, builder ) { + + // Ignore lights with infinite distance + + if ( lightNode.light.distance === undefined ) return; + + // TODO: We need a viewportOpaque*() ( output, depth ) to fit with modern rendering approaches + + const directLight = lightColor.xyz.toVar(); + directLight.mulAssign( lightNode.shadowNode ); // it no should be necessary if used in the same render pass + + this.scatteringLight( directLight, builder ); + + } + + directRectArea( { lightColor, lightPosition, halfWidth, halfHeight }, builder ) { + + const p0 = lightPosition.add( halfWidth ).sub( halfHeight ); // counterclockwise; light shines in local neg z direction + const p1 = lightPosition.sub( halfWidth ).sub( halfHeight ); + const p2 = lightPosition.sub( halfWidth ).add( halfHeight ); + const p3 = lightPosition.add( halfWidth ).add( halfHeight ); + + const P = builder.context.positionView; + + const directLight = lightColor.xyz.mul( LTC_Evaluate_Volume( { P, p0, p1, p2, p3 } ) ).pow( 1.5 ); + + this.scatteringLight( directLight, builder ); + + } + + finish( builder ) { + + builder.context.outgoingLight.assign( outgoingRayLight ); + + } + +} + +/** + * Volume node material. + * + * @augments NodeMaterial + */ +class VolumeNodeMaterial extends NodeMaterial { + + static get type() { + + return 'VolumeNodeMaterial'; + + } + + /** + * Constructs a new volume node material. + * + * @param {Object} [parameters] - The configuration parameter. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isVolumeNodeMaterial = true; + + /** + * Number of steps used for raymarching. + * + * @type {number} + * @default 25 + */ + this.steps = 25; + + /** + * Offsets the distance a ray has been traveled through a volume. + * Can be used to implement dithering to reduce banding. + * + * @type {Node} + * @default null + */ + this.offsetNode = null; + + /** + * Node used for scattering calculations. + * + * @type {Function|FunctionNode} + * @default null + */ + this.scatteringNode = null; + + this.lights = true; + + this.transparent = true; + this.side = BackSide; + + this.depthTest = false; + this.depthWrite = false; + + this.setValues( parameters ); + + } + + setupLightingModel() { + + return new VolumetricLightingModel(); + + } + +} + +/** + * This module manages the internal animation loop of the renderer. + * + * @private + */ +class Animation { + + /** + * Constructs a new animation loop management component. + * + * @param {Nodes} nodes - Renderer component for managing nodes related logic. + * @param {Info} info - Renderer component for managing metrics and monitoring data. + */ + constructor( nodes, info ) { + + /** + * Renderer component for managing nodes related logic. + * + * @type {Nodes} + */ + this.nodes = nodes; + + /** + * Renderer component for managing metrics and monitoring data. + * + * @type {Info} + */ + this.info = info; + + /** + * A reference to the context from `requestAnimationFrame()` can + * be called (usually `window`). + * + * @type {?(Window|XRSession)} + */ + this._context = typeof self !== 'undefined' ? self : null; + + /** + * The user-defined animation loop. + * + * @type {?Function} + * @default null + */ + this._animationLoop = null; + + /** + * The requestId which is returned from the `requestAnimationFrame()` call. + * Can be used to cancel the stop the animation loop. + * + * @type {?number} + * @default null + */ + this._requestId = null; + + } + + /** + * Starts the internal animation loop. + */ + start() { + + const update = ( time, xrFrame ) => { + + this._requestId = this._context.requestAnimationFrame( update ); + + if ( this.info.autoReset === true ) this.info.reset(); + + this.nodes.nodeFrame.update(); + + this.info.frame = this.nodes.nodeFrame.frameId; + + if ( this._animationLoop !== null ) this._animationLoop( time, xrFrame ); + + }; + + update(); + + } + + /** + * Stops the internal animation loop. + */ + stop() { + + this._context.cancelAnimationFrame( this._requestId ); + + this._requestId = null; + + } + + /** + * Returns the user-level animation loop. + * + * @return {?Function} The animation loop. + */ + getAnimationLoop() { + + return this._animationLoop; + + } + + /** + * Defines the user-level animation loop. + * + * @param {?Function} callback - The animation loop. + */ + setAnimationLoop( callback ) { + + this._animationLoop = callback; + + } + + /** + * Returns the animation context. + * + * @return {Window|XRSession} The animation context. + */ + getContext() { + + return this._context; + + } + + /** + * Defines the context in which `requestAnimationFrame()` is executed. + * + * @param {Window|XRSession} context - The context to set. + */ + setContext( context ) { + + this._context = context; + + } + + /** + * Frees all internal resources and stops the animation loop. + */ + dispose() { + + this.stop(); + + } + +} + +/** + * Data structure for the renderer. It allows defining values + * with chained, hierarchical keys. Keys are meant to be + * objects since the module internally works with Weak Maps + * for performance reasons. + * + * @private + */ +class ChainMap { + + /** + * Constructs a new Chain Map. + */ + constructor() { + + /** + * The root Weak Map. + * + * @type {WeakMap} + */ + this.weakMap = new WeakMap(); + + } + + /** + * Returns the value for the given array of keys. + * + * @param {Array} keys - List of keys. + * @return {any} The value. Returns `undefined` if no value was found. + */ + get( keys ) { + + let map = this.weakMap; + + for ( let i = 0; i < keys.length - 1; i ++ ) { + + map = map.get( keys[ i ] ); + + if ( map === undefined ) return undefined; + + } + + return map.get( keys[ keys.length - 1 ] ); + + } + + /** + * Sets the value for the given keys. + * + * @param {Array} keys - List of keys. + * @param {any} value - The value to set. + * @return {ChainMap} A reference to this Chain Map. + */ + set( keys, value ) { + + let map = this.weakMap; + + for ( let i = 0; i < keys.length - 1; i ++ ) { + + const key = keys[ i ]; + + if ( map.has( key ) === false ) map.set( key, new WeakMap() ); + + map = map.get( key ); + + } + + map.set( keys[ keys.length - 1 ], value ); + + return this; + + } + + /** + * Deletes a value for the given keys. + * + * @param {Array} keys - The keys. + * @return {boolean} Returns `true` if the value has been removed successfully and `false` if the value has not be found. + */ + delete( keys ) { + + let map = this.weakMap; + + for ( let i = 0; i < keys.length - 1; i ++ ) { + + map = map.get( keys[ i ] ); + + if ( map === undefined ) return false; + + } + + return map.delete( keys[ keys.length - 1 ] ); + + } + +} + +let _id$9 = 0; + +function getKeys( obj ) { + + const keys = Object.keys( obj ); + + let proto = Object.getPrototypeOf( obj ); + + while ( proto ) { + + const descriptors = Object.getOwnPropertyDescriptors( proto ); + + for ( const key in descriptors ) { + + if ( descriptors[ key ] !== undefined ) { + + const descriptor = descriptors[ key ]; + + if ( descriptor && typeof descriptor.get === 'function' ) { + + keys.push( key ); + + } + + } + + } + + proto = Object.getPrototypeOf( proto ); + + } + + return keys; + +} + +/** + * A render object is the renderer's representation of single entity that gets drawn + * with a draw command. There is no unique mapping of render objects to 3D objects in the + * scene since render objects also depend from the used material, the current render context + * and the current scene's lighting. + * + * In general, the basic process of the renderer is: + * + * - Analyze the 3D objects in the scene and generate render lists containing render items. + * - Process the render lists by calling one or more render commands for each render item. + * - For each render command, request a render object and perform the draw. + * + * The module provides an interface to get data required for the draw command like the actual + * draw parameters or vertex buffers. It also holds a series of caching related methods since + * creating render objects should only be done when necessary. + * + * @private + */ +class RenderObject { + + /** + * Constructs a new render object. + * + * @param {Nodes} nodes - Renderer component for managing nodes related logic. + * @param {Geometries} geometries - Renderer component for managing geometries. + * @param {Renderer} renderer - The renderer. + * @param {Object3D} object - The 3D object. + * @param {Material} material - The 3D object's material. + * @param {Scene} scene - The scene the 3D object belongs to. + * @param {Camera} camera - The camera the object should be rendered with. + * @param {LightsNode} lightsNode - The lights node. + * @param {RenderContext} renderContext - The render context. + * @param {ClippingContext} clippingContext - The clipping context. + */ + constructor( nodes, geometries, renderer, object, material, scene, camera, lightsNode, renderContext, clippingContext ) { + + this.id = _id$9 ++; + + /** + * Renderer component for managing nodes related logic. + * + * @type {Nodes} + * @private + */ + this._nodes = nodes; + + /** + * Renderer component for managing geometries. + * + * @type {Geometries} + * @private + */ + this._geometries = geometries; + + /** + * The renderer. + * + * @type {Renderer} + */ + this.renderer = renderer; + + /** + * The 3D object. + * + * @type {Object3D} + */ + this.object = object; + + /** + * The 3D object's material. + * + * @type {Material} + */ + this.material = material; + + /** + * The scene the 3D object belongs to. + * + * @type {Scene} + */ + this.scene = scene; + + /** + * The camera the 3D object should be rendered with. + * + * @type {Camera} + */ + this.camera = camera; + + /** + * The lights node. + * + * @type {LightsNode} + */ + this.lightsNode = lightsNode; + + /** + * The render context. + * + * @type {RenderContext} + */ + this.context = renderContext; + + /** + * The 3D object's geometry. + * + * @type {BufferGeometry} + */ + this.geometry = object.geometry; + + /** + * The render object's version. + * + * @type {number} + */ + this.version = material.version; + + /** + * The draw range of the geometry. + * + * @type {?Object} + * @default null + */ + this.drawRange = null; + + /** + * An array holding the buffer attributes + * of the render object. This entails attribute + * definitions on geometry and node level. + * + * @type {?Array} + * @default null + */ + this.attributes = null; + + /** + * An object holding the version of the + * attributes. The keys are the attribute names + * and the values are the attribute versions. + * + * @type {?Object} + * @default null + */ + this.attributesId = null; + + /** + * A reference to a render pipeline the render + * object is processed with. + * + * @type {RenderPipeline} + * @default null + */ + this.pipeline = null; + + /** + * Only relevant for objects using + * multiple materials. This represents a group entry + * from the respective `BufferGeometry`. + * + * @type {?{start: number, count: number}} + * @default null + */ + this.group = null; + + /** + * An array holding the vertex buffers which can + * be buffer attributes but also interleaved buffers. + * + * @type {?Array} + * @default null + */ + this.vertexBuffers = null; + + /** + * The parameters for the draw command. + * + * @type {?Object} + * @default null + */ + this.drawParams = null; + + /** + * If this render object is used inside a render bundle, + * this property points to the respective bundle group. + * + * @type {?BundleGroup} + * @default null + */ + this.bundle = null; + + /** + * The clipping context. + * + * @type {ClippingContext} + */ + this.clippingContext = clippingContext; + + /** + * The clipping context's cache key. + * + * @type {string} + */ + this.clippingContextCacheKey = clippingContext !== null ? clippingContext.cacheKey : ''; + + /** + * The initial node cache key. + * + * @type {number} + */ + this.initialNodesCacheKey = this.getDynamicCacheKey(); + + /** + * The initial cache key. + * + * @type {number} + */ + this.initialCacheKey = this.getCacheKey(); + + /** + * The node builder state. + * + * @type {?NodeBuilderState} + * @private + * @default null + */ + this._nodeBuilderState = null; + + /** + * An array of bindings. + * + * @type {?Array} + * @private + * @default null + */ + this._bindings = null; + + /** + * Reference to the node material observer. + * + * @type {?NodeMaterialObserver} + * @private + * @default null + */ + this._monitor = null; + + /** + * An event listener which is defined by `RenderObjects`. It performs + * clean up tasks when `dispose()` on this render object. + * + * @method + */ + this.onDispose = null; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isRenderObject = true; + + /** + * An event listener which is executed when `dispose()` is called on + * the material of this render object. + * + * @method + */ + this.onMaterialDispose = () => { + + this.dispose(); + + }; + + /** + * An event listener which is executed when `dispose()` is called on + * the geometry of this render object. + * + * @method + */ + this.onGeometryDispose = () => { + + // clear geometry cache attributes + + this.attributes = null; + this.attributesId = null; + + }; + + this.material.addEventListener( 'dispose', this.onMaterialDispose ); + this.geometry.addEventListener( 'dispose', this.onGeometryDispose ); + + } + + /** + * Updates the clipping context. + * + * @param {ClippingContext} context - The clipping context to set. + */ + updateClipping( context ) { + + this.clippingContext = context; + + } + + /** + * Whether the clipping requires an update or not. + * + * @type {boolean} + * @readonly + */ + get clippingNeedsUpdate() { + + if ( this.clippingContext === null || this.clippingContext.cacheKey === this.clippingContextCacheKey ) return false; + + this.clippingContextCacheKey = this.clippingContext.cacheKey; + + return true; + + } + + /** + * The number of clipping planes defined in context of hardware clipping. + * + * @type {number} + * @readonly + */ + get hardwareClippingPlanes() { + + return this.material.hardwareClipping === true ? this.clippingContext.unionClippingCount : 0; + + } + + /** + * Returns the node builder state of this render object. + * + * @return {NodeBuilderState} The node builder state. + */ + getNodeBuilderState() { + + return this._nodeBuilderState || ( this._nodeBuilderState = this._nodes.getForRender( this ) ); + + } + + /** + * Returns the node material observer of this render object. + * + * @return {NodeMaterialObserver} The node material observer. + */ + getMonitor() { + + return this._monitor || ( this._monitor = this.getNodeBuilderState().observer ); + + } + + /** + * Returns an array of bind groups of this render object. + * + * @return {Array} The bindings. + */ + getBindings() { + + return this._bindings || ( this._bindings = this.getNodeBuilderState().createBindings() ); + + } + + /** + * Returns a binding group by group name of this render object. + * + * @param {string} name - The name of the binding group. + * @return {?BindGroup} The bindings. + */ + getBindingGroup( name ) { + + for ( const bindingGroup of this.getBindings() ) { + + if ( bindingGroup.name === name ) { + + return bindingGroup; + + } + + } + + } + + /** + * Returns the index of the render object's geometry. + * + * @return {?BufferAttribute} The index. Returns `null` for non-indexed geometries. + */ + getIndex() { + + return this._geometries.getIndex( this ); + + } + + /** + * Returns the indirect buffer attribute. + * + * @return {?BufferAttribute} The indirect attribute. `null` if no indirect drawing is used. + */ + getIndirect() { + + return this._geometries.getIndirect( this ); + + } + + /** + * Returns an array that acts as a key for identifying the render object in a chain map. + * + * @return {Array} An array with object references. + */ + getChainArray() { + + return [ this.object, this.material, this.context, this.lightsNode ]; + + } + + /** + * This method is used when the geometry of a 3D object has been exchanged and the + * respective render object now requires an update. + * + * @param {BufferGeometry} geometry - The geometry to set. + */ + setGeometry( geometry ) { + + this.geometry = geometry; + this.attributes = null; + this.attributesId = null; + + } + + /** + * Returns the buffer attributes of the render object. The returned array holds + * attribute definitions on geometry and node level. + * + * @return {Array} An array with buffer attributes. + */ + getAttributes() { + + if ( this.attributes !== null ) return this.attributes; + + const nodeAttributes = this.getNodeBuilderState().nodeAttributes; + const geometry = this.geometry; + + const attributes = []; + const vertexBuffers = new Set(); + + const attributesId = {}; + + for ( const nodeAttribute of nodeAttributes ) { + + let attribute; + + if ( nodeAttribute.node && nodeAttribute.node.attribute ) { + + // node attribute + attribute = nodeAttribute.node.attribute; + + } else { + + // geometry attribute + attribute = geometry.getAttribute( nodeAttribute.name ); + + attributesId[ nodeAttribute.name ] = attribute.version; + + } + + if ( attribute === undefined ) continue; + + attributes.push( attribute ); + + const bufferAttribute = attribute.isInterleavedBufferAttribute ? attribute.data : attribute; + vertexBuffers.add( bufferAttribute ); + + } + + this.attributes = attributes; + this.attributesId = attributesId; + this.vertexBuffers = Array.from( vertexBuffers.values() ); + + return attributes; + + } + + /** + * Returns the vertex buffers of the render object. + * + * @return {Array} An array with buffer attribute or interleaved buffers. + */ + getVertexBuffers() { + + if ( this.vertexBuffers === null ) this.getAttributes(); + + return this.vertexBuffers; + + } + + /** + * Returns the draw parameters for the render object. + * + * @return {?{vertexCount: number, firstVertex: number, instanceCount: number, firstInstance: number}} The draw parameters. + */ + getDrawParameters() { + + const { object, material, geometry, group, drawRange } = this; + + const drawParams = this.drawParams || ( this.drawParams = { + vertexCount: 0, + firstVertex: 0, + instanceCount: 0, + firstInstance: 0 + } ); + + const index = this.getIndex(); + const hasIndex = ( index !== null ); + + let instanceCount = 1; + + if ( geometry.isInstancedBufferGeometry === true ) { + + instanceCount = geometry.instanceCount; + + } else if ( object.count !== undefined ) { + + instanceCount = Math.max( 0, object.count ); + + } + + if ( instanceCount === 0 ) return null; + + drawParams.instanceCount = instanceCount; + + if ( object.isBatchedMesh === true ) return drawParams; + + let rangeFactor = 1; + + if ( material.wireframe === true && ! object.isPoints && ! object.isLineSegments && ! object.isLine && ! object.isLineLoop ) { + + rangeFactor = 2; + + } + + let firstVertex = drawRange.start * rangeFactor; + let lastVertex = ( drawRange.start + drawRange.count ) * rangeFactor; + + if ( group !== null ) { + + firstVertex = Math.max( firstVertex, group.start * rangeFactor ); + lastVertex = Math.min( lastVertex, ( group.start + group.count ) * rangeFactor ); + + } + + const position = geometry.attributes.position; + let itemCount = Infinity; + + if ( hasIndex ) { + + itemCount = index.count; + + } else if ( position !== undefined && position !== null ) { + + itemCount = position.count; + + } + + firstVertex = Math.max( firstVertex, 0 ); + lastVertex = Math.min( lastVertex, itemCount ); + + const count = lastVertex - firstVertex; + + if ( count < 0 || count === Infinity ) return null; + + drawParams.vertexCount = count; + drawParams.firstVertex = firstVertex; + + return drawParams; + + } + + /** + * Returns the render object's geometry cache key. + * + * The geometry cache key is part of the material cache key. + * + * @return {string} The geometry cache key. + */ + getGeometryCacheKey() { + + const { geometry } = this; + + let cacheKey = ''; + + for ( const name of Object.keys( geometry.attributes ).sort() ) { + + const attribute = geometry.attributes[ name ]; + + cacheKey += name + ','; + + if ( attribute.data ) cacheKey += attribute.data.stride + ','; + if ( attribute.offset ) cacheKey += attribute.offset + ','; + if ( attribute.itemSize ) cacheKey += attribute.itemSize + ','; + if ( attribute.normalized ) cacheKey += 'n,'; + + } + + // structural equality isn't sufficient for morph targets since the + // data are maintained in textures. only if the targets are all equal + // the texture and thus the instance of `MorphNode` can be shared. + + for ( const name of Object.keys( geometry.morphAttributes ).sort() ) { + + const targets = geometry.morphAttributes[ name ]; + + cacheKey += 'morph-' + name + ','; + + for ( let i = 0, l = targets.length; i < l; i ++ ) { + + const attribute = targets[ i ]; + + cacheKey += attribute.id + ','; + + } + + } + + if ( geometry.index ) { + + cacheKey += 'index,'; + + } + + return cacheKey; + + } + + /** + * Returns the render object's material cache key. + * + * The material cache key is part of the render object cache key. + * + * @return {number} The material cache key. + */ + getMaterialCacheKey() { + + const { object, material } = this; + + let cacheKey = material.customProgramCacheKey(); + + for ( const property of getKeys( material ) ) { + + if ( /^(is[A-Z]|_)|^(visible|version|uuid|name|opacity|userData)$/.test( property ) ) continue; + + const value = material[ property ]; + + let valueKey; + + if ( value !== null ) { + + // some material values require a formatting + + const type = typeof value; + + if ( type === 'number' ) { + + valueKey = value !== 0 ? '1' : '0'; // Convert to on/off, important for clearcoat, transmission, etc + + } else if ( type === 'object' ) { + + valueKey = '{'; + + if ( value.isTexture ) { + + valueKey += value.mapping; + + } + + valueKey += '}'; + + } else { + + valueKey = String( value ); + + } + + } else { + + valueKey = String( value ); + + } + + cacheKey += /*property + ':' +*/ valueKey + ','; + + } + + cacheKey += this.clippingContextCacheKey + ','; + + if ( object.geometry ) { + + cacheKey += this.getGeometryCacheKey(); + + } + + if ( object.skeleton ) { + + cacheKey += object.skeleton.bones.length + ','; + + } + + if ( object.isBatchedMesh ) { + + cacheKey += object._matricesTexture.uuid + ','; + + if ( object._colorsTexture !== null ) { + + cacheKey += object._colorsTexture.uuid + ','; + + } + + } + + if ( object.count > 1 ) { + + // TODO: https://github.com/mrdoob/three.js/pull/29066#issuecomment-2269400850 + + cacheKey += object.uuid + ','; + + } + + cacheKey += object.receiveShadow + ','; + + return hashString( cacheKey ); + + } + + /** + * Whether the geometry requires an update or not. + * + * @type {boolean} + * @readonly + */ + get needsGeometryUpdate() { + + if ( this.geometry.id !== this.object.geometry.id ) return true; + + if ( this.attributes !== null ) { + + const attributesId = this.attributesId; + + for ( const name in attributesId ) { + + const attribute = this.geometry.getAttribute( name ); + + if ( attribute === undefined || attributesId[ name ] !== attribute.id ) { + + return true; + + } + + } + + } + + return false; + + } + + /** + * Whether the render object requires an update or not. + * + * Note: There are two distinct places where render objects are checked for an update. + * + * 1. In `RenderObjects.get()` which is executed when the render object is request. This + * method checks the `needsUpdate` flag and recreates the render object if necessary. + * 2. In `Renderer._renderObjectDirect()` right after getting the render object via + * `RenderObjects.get()`. The render object's NodeMaterialObserver is then used to detect + * a need for a refresh due to material, geometry or object related value changes. + * + * TODO: Investigate if it's possible to merge both steps so there is only a single place + * that performs the 'needsUpdate' check. + * + * @type {boolean} + * @readonly + */ + get needsUpdate() { + + return /*this.object.static !== true &&*/ ( this.initialNodesCacheKey !== this.getDynamicCacheKey() || this.clippingNeedsUpdate ); + + } + + /** + * Returns the dynamic cache key which represents a key that is computed per draw command. + * + * @return {number} The cache key. + */ + getDynamicCacheKey() { + + let cacheKey = 0; + + // `Nodes.getCacheKey()` returns an environment cache key which is not relevant when + // the renderer is inside a shadow pass. + + if ( this.material.isShadowPassMaterial !== true ) { + + cacheKey = this._nodes.getCacheKey( this.scene, this.lightsNode ); + + } + + if ( this.camera.isArrayCamera ) { + + cacheKey = hash$1( cacheKey, this.camera.cameras.length ); + + } + + if ( this.object.receiveShadow ) { + + cacheKey = hash$1( cacheKey, 1 ); + + } + + return cacheKey; + + } + + /** + * Returns the render object's cache key. + * + * @return {number} The cache key. + */ + getCacheKey() { + + return this.getMaterialCacheKey() + this.getDynamicCacheKey(); + + } + + /** + * Frees internal resources. + */ + dispose() { + + this.material.removeEventListener( 'dispose', this.onMaterialDispose ); + this.geometry.removeEventListener( 'dispose', this.onGeometryDispose ); + + this.onDispose(); + + } + +} + +const _chainKeys$5 = []; + +/** + * This module manages the render objects of the renderer. + * + * @private + */ +class RenderObjects { + + /** + * Constructs a new render object management component. + * + * @param {Renderer} renderer - The renderer. + * @param {Nodes} nodes - Renderer component for managing nodes related logic. + * @param {Geometries} geometries - Renderer component for managing geometries. + * @param {Pipelines} pipelines - Renderer component for managing pipelines. + * @param {Bindings} bindings - Renderer component for managing bindings. + * @param {Info} info - Renderer component for managing metrics and monitoring data. + */ + constructor( renderer, nodes, geometries, pipelines, bindings, info ) { + + /** + * The renderer. + * + * @type {Renderer} + */ + this.renderer = renderer; + + /** + * Renderer component for managing nodes related logic. + * + * @type {Nodes} + */ + this.nodes = nodes; + + /** + * Renderer component for managing geometries. + * + * @type {Geometries} + */ + this.geometries = geometries; + + /** + * Renderer component for managing pipelines. + * + * @type {Pipelines} + */ + this.pipelines = pipelines; + + /** + * Renderer component for managing bindings. + * + * @type {Bindings} + */ + this.bindings = bindings; + + /** + * Renderer component for managing metrics and monitoring data. + * + * @type {Info} + */ + this.info = info; + + /** + * A dictionary that manages render contexts in chain maps + * for each pass ID. + * + * @type {Object} + */ + this.chainMaps = {}; + + } + + /** + * Returns a render object for the given object and state data. + * + * @param {Object3D} object - The 3D object. + * @param {Material} material - The 3D object's material. + * @param {Scene} scene - The scene the 3D object belongs to. + * @param {Camera} camera - The camera the 3D object should be rendered with. + * @param {LightsNode} lightsNode - The lights node. + * @param {RenderContext} renderContext - The render context. + * @param {ClippingContext} clippingContext - The clipping context. + * @param {string} [passId] - An optional ID for identifying the pass. + * @return {RenderObject} The render object. + */ + get( object, material, scene, camera, lightsNode, renderContext, clippingContext, passId ) { + + const chainMap = this.getChainMap( passId ); + + // reuse chainArray + _chainKeys$5[ 0 ] = object; + _chainKeys$5[ 1 ] = material; + _chainKeys$5[ 2 ] = renderContext; + _chainKeys$5[ 3 ] = lightsNode; + + let renderObject = chainMap.get( _chainKeys$5 ); + + if ( renderObject === undefined ) { + + renderObject = this.createRenderObject( this.nodes, this.geometries, this.renderer, object, material, scene, camera, lightsNode, renderContext, clippingContext, passId ); + + chainMap.set( _chainKeys$5, renderObject ); + + } else { + + renderObject.updateClipping( clippingContext ); + + if ( renderObject.needsGeometryUpdate ) { + + renderObject.setGeometry( object.geometry ); + + } + + if ( renderObject.version !== material.version || renderObject.needsUpdate ) { + + if ( renderObject.initialCacheKey !== renderObject.getCacheKey() ) { + + renderObject.dispose(); + + renderObject = this.get( object, material, scene, camera, lightsNode, renderContext, clippingContext, passId ); + + } else { + + renderObject.version = material.version; + + } + + } + + } + + _chainKeys$5.length = 0; + + return renderObject; + + } + + /** + * Returns a chain map for the given pass ID. + * + * @param {string} [passId='default'] - The pass ID. + * @return {ChainMap} The chain map. + */ + getChainMap( passId = 'default' ) { + + return this.chainMaps[ passId ] || ( this.chainMaps[ passId ] = new ChainMap() ); + + } + + /** + * Frees internal resources. + */ + dispose() { + + this.chainMaps = {}; + + } + + /** + * Factory method for creating render objects with the given list of parameters. + * + * @param {Nodes} nodes - Renderer component for managing nodes related logic. + * @param {Geometries} geometries - Renderer component for managing geometries. + * @param {Renderer} renderer - The renderer. + * @param {Object3D} object - The 3D object. + * @param {Material} material - The object's material. + * @param {Scene} scene - The scene the 3D object belongs to. + * @param {Camera} camera - The camera the object should be rendered with. + * @param {LightsNode} lightsNode - The lights node. + * @param {RenderContext} renderContext - The render context. + * @param {ClippingContext} clippingContext - The clipping context. + * @param {string} [passId] - An optional ID for identifying the pass. + * @return {RenderObject} The render object. + */ + createRenderObject( nodes, geometries, renderer, object, material, scene, camera, lightsNode, renderContext, clippingContext, passId ) { + + const chainMap = this.getChainMap( passId ); + + const renderObject = new RenderObject( nodes, geometries, renderer, object, material, scene, camera, lightsNode, renderContext, clippingContext ); + + renderObject.onDispose = () => { + + this.pipelines.delete( renderObject ); + this.bindings.delete( renderObject ); + this.nodes.delete( renderObject ); + + chainMap.delete( renderObject.getChainArray() ); + + }; + + return renderObject; + + } + + +} + +/** + * Data structure for the renderer. It is intended to manage + * data of objects in dictionaries. + * + * @private + */ +class DataMap { + + /** + * Constructs a new data map. + */ + constructor() { + + /** + * `DataMap` internally uses a weak map + * to manage its data. + * + * @type {WeakMap} + */ + this.data = new WeakMap(); + + } + + /** + * Returns the dictionary for the given object. + * + * @param {Object} object - The object. + * @return {Object} The dictionary. + */ + get( object ) { + + let map = this.data.get( object ); + + if ( map === undefined ) { + + map = {}; + this.data.set( object, map ); + + } + + return map; + + } + + /** + * Deletes the dictionary for the given object. + * + * @param {Object} object - The object. + * @return {?Object} The deleted dictionary. + */ + delete( object ) { + + let map = null; + + if ( this.data.has( object ) ) { + + map = this.data.get( object ); + + this.data.delete( object ); + + } + + return map; + + } + + /** + * Returns `true` if the given object has a dictionary defined. + * + * @param {Object} object - The object to test. + * @return {boolean} Whether a dictionary is defined or not. + */ + has( object ) { + + return this.data.has( object ); + + } + + /** + * Frees internal resources. + */ + dispose() { + + this.data = new WeakMap(); + + } + +} + +const AttributeType = { + VERTEX: 1, + INDEX: 2, + STORAGE: 3, + INDIRECT: 4 +}; + +// size of a chunk in bytes (STD140 layout) + +const GPU_CHUNK_BYTES = 16; + +// @TODO: Move to src/constants.js + +const BlendColorFactor = 211; +const OneMinusBlendColorFactor = 212; + +/** + * This renderer module manages geometry attributes. + * + * @private + * @augments DataMap + */ +class Attributes extends DataMap { + + /** + * Constructs a new attribute management component. + * + * @param {Backend} backend - The renderer's backend. + */ + constructor( backend ) { + + super(); + + /** + * The renderer's backend. + * + * @type {Backend} + */ + this.backend = backend; + + } + + /** + * Deletes the data for the given attribute. + * + * @param {BufferAttribute} attribute - The attribute. + * @return {Object|null} The deleted attribute data. + */ + delete( attribute ) { + + const attributeData = super.delete( attribute ); + + if ( attributeData !== null ) { + + this.backend.destroyAttribute( attribute ); + + } + + return attributeData; + + } + + /** + * Updates the given attribute. This method creates attribute buffers + * for new attributes and updates data for existing ones. + * + * @param {BufferAttribute} attribute - The attribute to update. + * @param {number} type - The attribute type. + */ + update( attribute, type ) { + + const data = this.get( attribute ); + + if ( data.version === undefined ) { + + if ( type === AttributeType.VERTEX ) { + + this.backend.createAttribute( attribute ); + + } else if ( type === AttributeType.INDEX ) { + + this.backend.createIndexAttribute( attribute ); + + } else if ( type === AttributeType.STORAGE ) { + + this.backend.createStorageAttribute( attribute ); + + } else if ( type === AttributeType.INDIRECT ) { + + this.backend.createIndirectStorageAttribute( attribute ); + + } + + data.version = this._getBufferAttribute( attribute ).version; + + } else { + + const bufferAttribute = this._getBufferAttribute( attribute ); + + if ( data.version < bufferAttribute.version || bufferAttribute.usage === DynamicDrawUsage ) { + + this.backend.updateAttribute( attribute ); + + data.version = bufferAttribute.version; + + } + + } + + } + + /** + * Utility method for handling interleaved buffer attributes correctly. + * To process them, their `InterleavedBuffer` is returned. + * + * @param {BufferAttribute} attribute - The attribute. + * @return {BufferAttribute|InterleavedBuffer} + */ + _getBufferAttribute( attribute ) { + + if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; + + return attribute; + + } + +} + +/** + * Returns the wireframe version for the given geometry. + * + * @private + * @function + * @param {BufferGeometry} geometry - The geometry. + * @return {number} The version. + */ +function getWireframeVersion( geometry ) { + + return ( geometry.index !== null ) ? geometry.index.version : geometry.attributes.position.version; + +} + +/** + * Returns a wireframe index attribute for the given geometry. + * + * @private + * @function + * @param {BufferGeometry} geometry - The geometry. + * @return {BufferAttribute} The wireframe index attribute. + */ +function getWireframeIndex( geometry ) { + + const indices = []; + + const geometryIndex = geometry.index; + const geometryPosition = geometry.attributes.position; + + if ( geometryIndex !== null ) { + + const array = geometryIndex.array; + + for ( let i = 0, l = array.length; i < l; i += 3 ) { + + const a = array[ i + 0 ]; + const b = array[ i + 1 ]; + const c = array[ i + 2 ]; + + indices.push( a, b, b, c, c, a ); + + } + + } else { + + const array = geometryPosition.array; + + for ( let i = 0, l = ( array.length / 3 ) - 1; i < l; i += 3 ) { + + const a = i + 0; + const b = i + 1; + const c = i + 2; + + indices.push( a, b, b, c, c, a ); + + } + + } + + const attribute = new ( arrayNeedsUint32( indices ) ? Uint32BufferAttribute : Uint16BufferAttribute )( indices, 1 ); + attribute.version = getWireframeVersion( geometry ); + + return attribute; + +} + +/** + * This renderer module manages geometries. + * + * @private + * @augments DataMap + */ +class Geometries extends DataMap { + + /** + * Constructs a new geometry management component. + * + * @param {Attributes} attributes - Renderer component for managing attributes. + * @param {Info} info - Renderer component for managing metrics and monitoring data. + */ + constructor( attributes, info ) { + + super(); + + /** + * Renderer component for managing attributes. + * + * @type {Attributes} + */ + this.attributes = attributes; + + /** + * Renderer component for managing metrics and monitoring data. + * + * @type {Info} + */ + this.info = info; + + /** + * Weak Map for managing attributes for wireframe rendering. + * + * @type {WeakMap} + */ + this.wireframes = new WeakMap(); + + /** + * This Weak Map is used to make sure buffer attributes are + * updated only once per render call. + * + * @type {WeakMap} + */ + this.attributeCall = new WeakMap(); + + } + + /** + * Returns `true` if the given render object has an initialized geometry. + * + * @param {RenderObject} renderObject - The render object. + * @return {boolean} Whether if the given render object has an initialized geometry or not. + */ + has( renderObject ) { + + const geometry = renderObject.geometry; + + return super.has( geometry ) && this.get( geometry ).initialized === true; + + } + + /** + * Prepares the geometry of the given render object for rendering. + * + * @param {RenderObject} renderObject - The render object. + */ + updateForRender( renderObject ) { + + if ( this.has( renderObject ) === false ) this.initGeometry( renderObject ); + + this.updateAttributes( renderObject ); + + } + + /** + * Initializes the geometry of the given render object. + * + * @param {RenderObject} renderObject - The render object. + */ + initGeometry( renderObject ) { + + const geometry = renderObject.geometry; + const geometryData = this.get( geometry ); + + geometryData.initialized = true; + + this.info.memory.geometries ++; + + const onDispose = () => { + + this.info.memory.geometries --; + + const index = geometry.index; + const geometryAttributes = renderObject.getAttributes(); + + if ( index !== null ) { + + this.attributes.delete( index ); + + } + + for ( const geometryAttribute of geometryAttributes ) { + + this.attributes.delete( geometryAttribute ); + + } + + const wireframeAttribute = this.wireframes.get( geometry ); + + if ( wireframeAttribute !== undefined ) { + + this.attributes.delete( wireframeAttribute ); + + } + + geometry.removeEventListener( 'dispose', onDispose ); + + }; + + geometry.addEventListener( 'dispose', onDispose ); + + } + + /** + * Updates the geometry attributes of the given render object. + * + * @param {RenderObject} renderObject - The render object. + */ + updateAttributes( renderObject ) { + + // attributes + + const attributes = renderObject.getAttributes(); + + for ( const attribute of attributes ) { + + if ( attribute.isStorageBufferAttribute || attribute.isStorageInstancedBufferAttribute ) { + + this.updateAttribute( attribute, AttributeType.STORAGE ); + + } else { + + this.updateAttribute( attribute, AttributeType.VERTEX ); + + } + + } + + // indexes + + const index = this.getIndex( renderObject ); + + if ( index !== null ) { + + this.updateAttribute( index, AttributeType.INDEX ); + + } + + // indirect + + const indirect = renderObject.geometry.indirect; + + if ( indirect !== null ) { + + this.updateAttribute( indirect, AttributeType.INDIRECT ); + + } + + } + + /** + * Updates the given attribute. + * + * @param {BufferAttribute} attribute - The attribute to update. + * @param {number} type - The attribute type. + */ + updateAttribute( attribute, type ) { + + const callId = this.info.render.calls; + + if ( ! attribute.isInterleavedBufferAttribute ) { + + if ( this.attributeCall.get( attribute ) !== callId ) { + + this.attributes.update( attribute, type ); + + this.attributeCall.set( attribute, callId ); + + } + + } else { + + if ( this.attributeCall.get( attribute ) === undefined ) { + + this.attributes.update( attribute, type ); + + this.attributeCall.set( attribute, callId ); + + } else if ( this.attributeCall.get( attribute.data ) !== callId ) { + + this.attributes.update( attribute, type ); + + this.attributeCall.set( attribute.data, callId ); + + this.attributeCall.set( attribute, callId ); + + } + + } + + } + + /** + * Returns the indirect buffer attribute of the given render object. + * + * @param {RenderObject} renderObject - The render object. + * @return {?BufferAttribute} The indirect attribute. `null` if no indirect drawing is used. + */ + getIndirect( renderObject ) { + + return renderObject.geometry.indirect; + + } + + /** + * Returns the index of the given render object's geometry. This is implemented + * in a method to return a wireframe index if necessary. + * + * @param {RenderObject} renderObject - The render object. + * @return {?BufferAttribute} The index. Returns `null` for non-indexed geometries. + */ + getIndex( renderObject ) { + + const { geometry, material } = renderObject; + + let index = geometry.index; + + if ( material.wireframe === true ) { + + const wireframes = this.wireframes; + + let wireframeAttribute = wireframes.get( geometry ); + + if ( wireframeAttribute === undefined ) { + + wireframeAttribute = getWireframeIndex( geometry ); + + wireframes.set( geometry, wireframeAttribute ); + + } else if ( wireframeAttribute.version !== getWireframeVersion( geometry ) ) { + + this.attributes.delete( wireframeAttribute ); + + wireframeAttribute = getWireframeIndex( geometry ); + + wireframes.set( geometry, wireframeAttribute ); + + } + + index = wireframeAttribute; + + } + + return index; + + } + +} + +/** + * This renderer module provides a series of statistical information + * about the GPU memory and the rendering process. Useful for debugging + * and monitoring. + */ +class Info { + + /** + * Constructs a new info component. + */ + constructor() { + + /** + * Whether frame related metrics should automatically + * be resetted or not. This property should be set to `false` + * by apps which manage their own animation loop. They must + * then call `renderer.info.reset()` once per frame manually. + * + * @type {boolean} + * @default true + */ + this.autoReset = true; + + /** + * The current frame ID. This ID is managed + * by `NodeFrame`. + * + * @type {number} + * @readonly + * @default 0 + */ + this.frame = 0; + + /** + * The number of render calls since the + * app has been started. + * + * @type {number} + * @readonly + * @default 0 + */ + this.calls = 0; + + /** + * Render related metrics. + * + * @type {Object} + * @readonly + * @property {number} calls - The number of render calls since the app has been started. + * @property {number} frameCalls - The number of render calls of the current frame. + * @property {number} drawCalls - The number of draw calls of the current frame. + * @property {number} triangles - The number of rendered triangle primitives of the current frame. + * @property {number} points - The number of rendered point primitives of the current frame. + * @property {number} lines - The number of rendered line primitives of the current frame. + * @property {number} timestamp - The timestamp of the frame when using `renderer.renderAsync()`. + */ + this.render = { + calls: 0, + frameCalls: 0, + drawCalls: 0, + triangles: 0, + points: 0, + lines: 0, + timestamp: 0, + }; + + /** + * Compute related metrics. + * + * @type {Object} + * @readonly + * @property {number} calls - The number of compute calls since the app has been started. + * @property {number} frameCalls - The number of compute calls of the current frame. + * @property {number} timestamp - The timestamp of the frame when using `renderer.computeAsync()`. + */ + this.compute = { + calls: 0, + frameCalls: 0, + timestamp: 0 + }; + + /** + * Memory related metrics. + * + * @type {Object} + * @readonly + * @property {number} geometries - The number of active geometries. + * @property {number} frameCalls - The number of active textures. + */ + this.memory = { + geometries: 0, + textures: 0 + }; + + } + + /** + * This method should be executed per draw call and updates the corresponding metrics. + * + * @param {Object3D} object - The 3D object that is going to be rendered. + * @param {number} count - The vertex or index count. + * @param {number} instanceCount - The instance count. + */ + update( object, count, instanceCount ) { + + this.render.drawCalls ++; + + if ( object.isMesh || object.isSprite ) { + + this.render.triangles += instanceCount * ( count / 3 ); + + } else if ( object.isPoints ) { + + this.render.points += instanceCount * count; + + } else if ( object.isLineSegments ) { + + this.render.lines += instanceCount * ( count / 2 ); + + } else if ( object.isLine ) { + + this.render.lines += instanceCount * ( count - 1 ); + + } else { + + console.error( 'THREE.WebGPUInfo: Unknown object type.' ); + + } + + } + + /** + * Resets frame related metrics. + */ + reset() { + + this.render.drawCalls = 0; + this.render.frameCalls = 0; + this.compute.frameCalls = 0; + + this.render.triangles = 0; + this.render.points = 0; + this.render.lines = 0; + + + } + + /** + * Performs a complete reset of the object. + */ + dispose() { + + this.reset(); + + this.calls = 0; + + this.render.calls = 0; + this.compute.calls = 0; + + this.render.timestamp = 0; + this.compute.timestamp = 0; + this.memory.geometries = 0; + this.memory.textures = 0; + + } + +} + +/** + * Abstract class for representing pipelines. + * + * @private + * @abstract + */ +class Pipeline { + + /** + * Constructs a new pipeline. + * + * @param {string} cacheKey - The pipeline's cache key. + */ + constructor( cacheKey ) { + + /** + * The pipeline's cache key. + * + * @type {string} + */ + this.cacheKey = cacheKey; + + /** + * How often the pipeline is currently in use. + * + * @type {number} + * @default 0 + */ + this.usedTimes = 0; + + } + +} + +/** + * Class for representing render pipelines. + * + * @private + * @augments Pipeline + */ +class RenderPipeline extends Pipeline { + + /** + * Constructs a new render pipeline. + * + * @param {string} cacheKey - The pipeline's cache key. + * @param {ProgrammableStage} vertexProgram - The pipeline's vertex shader. + * @param {ProgrammableStage} fragmentProgram - The pipeline's fragment shader. + */ + constructor( cacheKey, vertexProgram, fragmentProgram ) { + + super( cacheKey ); + + /** + * The pipeline's vertex shader. + * + * @type {ProgrammableStage} + */ + this.vertexProgram = vertexProgram; + + /** + * The pipeline's fragment shader. + * + * @type {ProgrammableStage} + */ + this.fragmentProgram = fragmentProgram; + + } + +} + +/** + * Class for representing compute pipelines. + * + * @private + * @augments Pipeline + */ +class ComputePipeline extends Pipeline { + + /** + * Constructs a new render pipeline. + * + * @param {string} cacheKey - The pipeline's cache key. + * @param {ProgrammableStage} computeProgram - The pipeline's compute shader. + */ + constructor( cacheKey, computeProgram ) { + + super( cacheKey ); + + /** + * The pipeline's compute shader. + * + * @type {ProgrammableStage} + */ + this.computeProgram = computeProgram; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isComputePipeline = true; + + } + +} + +let _id$8 = 0; + +/** + * Class for representing programmable stages which are vertex, + * fragment or compute shaders. Unlike fixed-function states (like blending), + * they represent the programmable part of a pipeline. + * + * @private + */ +class ProgrammableStage { + + /** + * Constructs a new programmable stage. + * + * @param {string} code - The shader code. + * @param {('vertex'|'fragment'|'compute')} stage - The type of stage. + * @param {string} name - The name of the shader. + * @param {?Array} [transforms=null] - The transforms (only relevant for compute stages with WebGL 2 which uses Transform Feedback). + * @param {?Array} [attributes=null] - The attributes (only relevant for compute stages with WebGL 2 which uses Transform Feedback). + */ + constructor( code, stage, name, transforms = null, attributes = null ) { + + /** + * The id of the programmable stage. + * + * @type {number} + */ + this.id = _id$8 ++; + + /** + * The shader code. + * + * @type {string} + */ + this.code = code; + + /** + * The type of stage. + * + * @type {string} + */ + this.stage = stage; + + /** + * The name of the stage. + * This is used for debugging purposes. + * + * @type {string} + */ + this.name = name; + + /** + * The transforms (only relevant for compute stages with WebGL 2 which uses Transform Feedback). + * + * @type {?Array} + */ + this.transforms = transforms; + + /** + * The attributes (only relevant for compute stages with WebGL 2 which uses Transform Feedback). + * + * @type {?Array} + */ + this.attributes = attributes; + + /** + * How often the programmable stage is currently in use. + * + * @type {number} + * @default 0 + */ + this.usedTimes = 0; + + } + +} + +/** + * This renderer module manages the pipelines of the renderer. + * + * @private + * @augments DataMap + */ +class Pipelines extends DataMap { + + /** + * Constructs a new pipeline management component. + * + * @param {Backend} backend - The renderer's backend. + * @param {Nodes} nodes - Renderer component for managing nodes related logic. + */ + constructor( backend, nodes ) { + + super(); + + /** + * The renderer's backend. + * + * @type {Backend} + */ + this.backend = backend; + + /** + * Renderer component for managing nodes related logic. + * + * @type {Nodes} + */ + this.nodes = nodes; + + /** + * A references to the bindings management component. + * This reference will be set inside the `Bindings` + * constructor. + * + * @type {?Bindings} + * @default null + */ + this.bindings = null; + + /** + * Internal cache for maintaining pipelines. + * The key of the map is a cache key, the value the pipeline. + * + * @type {Map} + */ + this.caches = new Map(); + + /** + * This dictionary maintains for each shader stage type (vertex, + * fragment and compute) the programmable stage objects which + * represent the actual shader code. + * + * @type {Object} + */ + this.programs = { + vertex: new Map(), + fragment: new Map(), + compute: new Map() + }; + + } + + /** + * Returns a compute pipeline for the given compute node. + * + * @param {Node} computeNode - The compute node. + * @param {Array} bindings - The bindings. + * @return {ComputePipeline} The compute pipeline. + */ + getForCompute( computeNode, bindings ) { + + const { backend } = this; + + const data = this.get( computeNode ); + + if ( this._needsComputeUpdate( computeNode ) ) { + + const previousPipeline = data.pipeline; + + if ( previousPipeline ) { + + previousPipeline.usedTimes --; + previousPipeline.computeProgram.usedTimes --; + + } + + // get shader + + const nodeBuilderState = this.nodes.getForCompute( computeNode ); + + // programmable stage + + let stageCompute = this.programs.compute.get( nodeBuilderState.computeShader ); + + if ( stageCompute === undefined ) { + + if ( previousPipeline && previousPipeline.computeProgram.usedTimes === 0 ) this._releaseProgram( previousPipeline.computeProgram ); + + stageCompute = new ProgrammableStage( nodeBuilderState.computeShader, 'compute', computeNode.name, nodeBuilderState.transforms, nodeBuilderState.nodeAttributes ); + this.programs.compute.set( nodeBuilderState.computeShader, stageCompute ); + + backend.createProgram( stageCompute ); + + } + + // determine compute pipeline + + const cacheKey = this._getComputeCacheKey( computeNode, stageCompute ); + + let pipeline = this.caches.get( cacheKey ); + + if ( pipeline === undefined ) { + + if ( previousPipeline && previousPipeline.usedTimes === 0 ) this._releasePipeline( previousPipeline ); + + pipeline = this._getComputePipeline( computeNode, stageCompute, cacheKey, bindings ); + + } + + // keep track of all used times + + pipeline.usedTimes ++; + stageCompute.usedTimes ++; + + // + + data.version = computeNode.version; + data.pipeline = pipeline; + + } + + return data.pipeline; + + } + + /** + * Returns a render pipeline for the given render object. + * + * @param {RenderObject} renderObject - The render object. + * @param {?Array} [promises=null] - An array of compilation promises which is only relevant in context of `Renderer.compileAsync()`. + * @return {RenderPipeline} The render pipeline. + */ + getForRender( renderObject, promises = null ) { + + const { backend } = this; + + const data = this.get( renderObject ); + + if ( this._needsRenderUpdate( renderObject ) ) { + + const previousPipeline = data.pipeline; + + if ( previousPipeline ) { + + previousPipeline.usedTimes --; + previousPipeline.vertexProgram.usedTimes --; + previousPipeline.fragmentProgram.usedTimes --; + + } + + // get shader + + const nodeBuilderState = renderObject.getNodeBuilderState(); + + const name = renderObject.material ? renderObject.material.name : ''; + + // programmable stages + + let stageVertex = this.programs.vertex.get( nodeBuilderState.vertexShader ); + + if ( stageVertex === undefined ) { + + if ( previousPipeline && previousPipeline.vertexProgram.usedTimes === 0 ) this._releaseProgram( previousPipeline.vertexProgram ); + + stageVertex = new ProgrammableStage( nodeBuilderState.vertexShader, 'vertex', name ); + this.programs.vertex.set( nodeBuilderState.vertexShader, stageVertex ); + + backend.createProgram( stageVertex ); + + } + + let stageFragment = this.programs.fragment.get( nodeBuilderState.fragmentShader ); + + if ( stageFragment === undefined ) { + + if ( previousPipeline && previousPipeline.fragmentProgram.usedTimes === 0 ) this._releaseProgram( previousPipeline.fragmentProgram ); + + stageFragment = new ProgrammableStage( nodeBuilderState.fragmentShader, 'fragment', name ); + this.programs.fragment.set( nodeBuilderState.fragmentShader, stageFragment ); + + backend.createProgram( stageFragment ); + + } + + // determine render pipeline + + const cacheKey = this._getRenderCacheKey( renderObject, stageVertex, stageFragment ); + + let pipeline = this.caches.get( cacheKey ); + + if ( pipeline === undefined ) { + + if ( previousPipeline && previousPipeline.usedTimes === 0 ) this._releasePipeline( previousPipeline ); + + pipeline = this._getRenderPipeline( renderObject, stageVertex, stageFragment, cacheKey, promises ); + + } else { + + renderObject.pipeline = pipeline; + + } + + // keep track of all used times + + pipeline.usedTimes ++; + stageVertex.usedTimes ++; + stageFragment.usedTimes ++; + + // + + data.pipeline = pipeline; + + } + + return data.pipeline; + + } + + /** + * Deletes the pipeline for the given render object. + * + * @param {RenderObject} object - The render object. + * @return {?Object} The deleted dictionary. + */ + delete( object ) { + + const pipeline = this.get( object ).pipeline; + + if ( pipeline ) { + + // pipeline + + pipeline.usedTimes --; + + if ( pipeline.usedTimes === 0 ) this._releasePipeline( pipeline ); + + // programs + + if ( pipeline.isComputePipeline ) { + + pipeline.computeProgram.usedTimes --; + + if ( pipeline.computeProgram.usedTimes === 0 ) this._releaseProgram( pipeline.computeProgram ); + + } else { + + pipeline.fragmentProgram.usedTimes --; + pipeline.vertexProgram.usedTimes --; + + if ( pipeline.vertexProgram.usedTimes === 0 ) this._releaseProgram( pipeline.vertexProgram ); + if ( pipeline.fragmentProgram.usedTimes === 0 ) this._releaseProgram( pipeline.fragmentProgram ); + + } + + } + + return super.delete( object ); + + } + + /** + * Frees internal resources. + */ + dispose() { + + super.dispose(); + + this.caches = new Map(); + this.programs = { + vertex: new Map(), + fragment: new Map(), + compute: new Map() + }; + + } + + /** + * Updates the pipeline for the given render object. + * + * @param {RenderObject} renderObject - The render object. + */ + updateForRender( renderObject ) { + + this.getForRender( renderObject ); + + } + + /** + * Returns a compute pipeline for the given parameters. + * + * @private + * @param {Node} computeNode - The compute node. + * @param {ProgrammableStage} stageCompute - The programmable stage representing the compute shader. + * @param {string} cacheKey - The cache key. + * @param {Array} bindings - The bindings. + * @return {ComputePipeline} The compute pipeline. + */ + _getComputePipeline( computeNode, stageCompute, cacheKey, bindings ) { + + // check for existing pipeline + + cacheKey = cacheKey || this._getComputeCacheKey( computeNode, stageCompute ); + + let pipeline = this.caches.get( cacheKey ); + + if ( pipeline === undefined ) { + + pipeline = new ComputePipeline( cacheKey, stageCompute ); + + this.caches.set( cacheKey, pipeline ); + + this.backend.createComputePipeline( pipeline, bindings ); + + } + + return pipeline; + + } + + /** + * Returns a render pipeline for the given parameters. + * + * @private + * @param {RenderObject} renderObject - The render object. + * @param {ProgrammableStage} stageVertex - The programmable stage representing the vertex shader. + * @param {ProgrammableStage} stageFragment - The programmable stage representing the fragment shader. + * @param {string} cacheKey - The cache key. + * @param {?Array} promises - An array of compilation promises which is only relevant in context of `Renderer.compileAsync()`. + * @return {ComputePipeline} The compute pipeline. + */ + _getRenderPipeline( renderObject, stageVertex, stageFragment, cacheKey, promises ) { + + // check for existing pipeline + + cacheKey = cacheKey || this._getRenderCacheKey( renderObject, stageVertex, stageFragment ); + + let pipeline = this.caches.get( cacheKey ); + + if ( pipeline === undefined ) { + + pipeline = new RenderPipeline( cacheKey, stageVertex, stageFragment ); + + this.caches.set( cacheKey, pipeline ); + + renderObject.pipeline = pipeline; + + // The `promises` array is `null` by default and only set to an empty array when + // `Renderer.compileAsync()` is used. The next call actually fills the array with + // pending promises that resolve when the render pipelines are ready for rendering. + + this.backend.createRenderPipeline( renderObject, promises ); + + } + + return pipeline; + + } + + /** + * Computes a cache key representing a compute pipeline. + * + * @private + * @param {Node} computeNode - The compute node. + * @param {ProgrammableStage} stageCompute - The programmable stage representing the compute shader. + * @return {string} The cache key. + */ + _getComputeCacheKey( computeNode, stageCompute ) { + + return computeNode.id + ',' + stageCompute.id; + + } + + /** + * Computes a cache key representing a render pipeline. + * + * @private + * @param {RenderObject} renderObject - The render object. + * @param {ProgrammableStage} stageVertex - The programmable stage representing the vertex shader. + * @param {ProgrammableStage} stageFragment - The programmable stage representing the fragment shader. + * @return {string} The cache key. + */ + _getRenderCacheKey( renderObject, stageVertex, stageFragment ) { + + return stageVertex.id + ',' + stageFragment.id + ',' + this.backend.getRenderCacheKey( renderObject ); + + } + + /** + * Releases the given pipeline. + * + * @private + * @param {Pipeline} pipeline - The pipeline to release. + */ + _releasePipeline( pipeline ) { + + this.caches.delete( pipeline.cacheKey ); + + } + + /** + * Releases the shader program. + * + * @private + * @param {Object} program - The shader program to release. + */ + _releaseProgram( program ) { + + const code = program.code; + const stage = program.stage; + + this.programs[ stage ].delete( code ); + + } + + /** + * Returns `true` if the compute pipeline for the given compute node requires an update. + * + * @private + * @param {Node} computeNode - The compute node. + * @return {boolean} Whether the compute pipeline for the given compute node requires an update or not. + */ + _needsComputeUpdate( computeNode ) { + + const data = this.get( computeNode ); + + return data.pipeline === undefined || data.version !== computeNode.version; + + } + + /** + * Returns `true` if the render pipeline for the given render object requires an update. + * + * @private + * @param {RenderObject} renderObject - The render object. + * @return {boolean} Whether the render object for the given render object requires an update or not. + */ + _needsRenderUpdate( renderObject ) { + + const data = this.get( renderObject ); + + return data.pipeline === undefined || this.backend.needsRenderUpdate( renderObject ); + + } + +} + +/** + * This renderer module manages the bindings of the renderer. + * + * @private + * @augments DataMap + */ +class Bindings extends DataMap { + + /** + * Constructs a new bindings management component. + * + * @param {Backend} backend - The renderer's backend. + * @param {Nodes} nodes - Renderer component for managing nodes related logic. + * @param {Textures} textures - Renderer component for managing textures. + * @param {Attributes} attributes - Renderer component for managing attributes. + * @param {Pipelines} pipelines - Renderer component for managing pipelines. + * @param {Info} info - Renderer component for managing metrics and monitoring data. + */ + constructor( backend, nodes, textures, attributes, pipelines, info ) { + + super(); + + /** + * The renderer's backend. + * + * @type {Backend} + */ + this.backend = backend; + + /** + * Renderer component for managing textures. + * + * @type {Textures} + */ + this.textures = textures; + + /** + * Renderer component for managing pipelines. + * + * @type {Pipelines} + */ + this.pipelines = pipelines; + + /** + * Renderer component for managing attributes. + * + * @type {Attributes} + */ + this.attributes = attributes; + + /** + * Renderer component for managing nodes related logic. + * + * @type {Nodes} + */ + this.nodes = nodes; + + /** + * Renderer component for managing metrics and monitoring data. + * + * @type {Info} + */ + this.info = info; + + this.pipelines.bindings = this; // assign bindings to pipelines + + } + + /** + * Returns the bind groups for the given render object. + * + * @param {RenderObject} renderObject - The render object. + * @return {Array} The bind groups. + */ + getForRender( renderObject ) { + + const bindings = renderObject.getBindings(); + + for ( const bindGroup of bindings ) { + + const groupData = this.get( bindGroup ); + + if ( groupData.bindGroup === undefined ) { + + // each object defines an array of bindings (ubos, textures, samplers etc.) + + this._init( bindGroup ); + + this.backend.createBindings( bindGroup, bindings, 0 ); + + groupData.bindGroup = bindGroup; + + } + + } + + return bindings; + + } + + /** + * Returns the bind groups for the given compute node. + * + * @param {Node} computeNode - The compute node. + * @return {Array} The bind groups. + */ + getForCompute( computeNode ) { + + const bindings = this.nodes.getForCompute( computeNode ).bindings; + + for ( const bindGroup of bindings ) { + + const groupData = this.get( bindGroup ); + + if ( groupData.bindGroup === undefined ) { + + this._init( bindGroup ); + + this.backend.createBindings( bindGroup, bindings, 0 ); + + groupData.bindGroup = bindGroup; + + } + + } + + return bindings; + + } + + /** + * Updates the bindings for the given compute node. + * + * @param {Node} computeNode - The compute node. + */ + updateForCompute( computeNode ) { + + this._updateBindings( this.getForCompute( computeNode ) ); + + } + + /** + * Updates the bindings for the given render object. + * + * @param {RenderObject} renderObject - The render object. + */ + updateForRender( renderObject ) { + + this._updateBindings( this.getForRender( renderObject ) ); + + } + + /** + * Updates the given array of bindings. + * + * @param {Array} bindings - The bind groups. + */ + _updateBindings( bindings ) { + + for ( const bindGroup of bindings ) { + + this._update( bindGroup, bindings ); + + } + + } + + /** + * Initializes the given bind group. + * + * @param {BindGroup} bindGroup - The bind group to initialize. + */ + _init( bindGroup ) { + + for ( const binding of bindGroup.bindings ) { + + if ( binding.isSampledTexture ) { + + this.textures.updateTexture( binding.texture ); + + } else if ( binding.isStorageBuffer ) { + + const attribute = binding.attribute; + const attributeType = attribute.isIndirectStorageBufferAttribute ? AttributeType.INDIRECT : AttributeType.STORAGE; + + this.attributes.update( attribute, attributeType ); + + } + + } + + } + + /** + * Updates the given bind group. + * + * @param {BindGroup} bindGroup - The bind group to update. + * @param {Array} bindings - The bind groups. + */ + _update( bindGroup, bindings ) { + + const { backend } = this; + + let needsBindingsUpdate = false; + let cacheBindings = true; + let cacheIndex = 0; + let version = 0; + + // iterate over all bindings and check if buffer updates or a new binding group is required + + for ( const binding of bindGroup.bindings ) { + + if ( binding.isNodeUniformsGroup ) { + + const updated = this.nodes.updateGroup( binding ); + + // every uniforms group is a uniform buffer. So if no update is required, + // we move one with the next binding. Otherwise the next if block will update the group. + + if ( updated === false ) continue; + + } + + if ( binding.isStorageBuffer ) { + + const attribute = binding.attribute; + const attributeType = attribute.isIndirectStorageBufferAttribute ? AttributeType.INDIRECT : AttributeType.STORAGE; + + this.attributes.update( attribute, attributeType ); + + + } + + if ( binding.isUniformBuffer ) { + + const updated = binding.update(); + + if ( updated ) { + + backend.updateBinding( binding ); + + } + + } else if ( binding.isSampler ) { + + binding.update(); + + } else if ( binding.isSampledTexture ) { + + const texturesTextureData = this.textures.get( binding.texture ); + + if ( binding.needsBindingsUpdate( texturesTextureData.generation ) ) needsBindingsUpdate = true; + + const updated = binding.update(); + + const texture = binding.texture; + + if ( updated ) { + + this.textures.updateTexture( texture ); + + } + + const textureData = backend.get( texture ); + + if ( textureData.externalTexture !== undefined || texturesTextureData.isDefaultTexture ) { + + cacheBindings = false; + + } else { + + cacheIndex = cacheIndex * 10 + texture.id; + version += texture.version; + + } + + if ( backend.isWebGPUBackend === true && textureData.texture === undefined && textureData.externalTexture === undefined ) { + + // TODO: Remove this once we found why updated === false isn't bound to a texture in the WebGPU backend + console.error( 'Bindings._update: binding should be available:', binding, updated, texture, binding.textureNode.value, needsBindingsUpdate ); + + this.textures.updateTexture( texture ); + needsBindingsUpdate = true; + + } + + if ( texture.isStorageTexture === true ) { + + const textureData = this.get( texture ); + + if ( binding.store === true ) { + + textureData.needsMipmap = true; + + } else if ( this.textures.needsMipmaps( texture ) && textureData.needsMipmap === true ) { + + this.backend.generateMipmaps( texture ); + + textureData.needsMipmap = false; + + } + + } + + } + + } + + if ( needsBindingsUpdate === true ) { + + this.backend.updateBindings( bindGroup, bindings, cacheBindings ? cacheIndex : 0, version ); + + } + + } + +} + +/** + * Default sorting function for opaque render items. + * + * @private + * @function + * @param {Object} a - The first render item. + * @param {Object} b - The second render item. + * @return {number} A numeric value which defines the sort order. + */ +function painterSortStable( a, b ) { + + if ( a.groupOrder !== b.groupOrder ) { + + return a.groupOrder - b.groupOrder; + + } else if ( a.renderOrder !== b.renderOrder ) { + + return a.renderOrder - b.renderOrder; + + } else if ( a.z !== b.z ) { + + return a.z - b.z; + + } else { + + return a.id - b.id; + + } + +} + +/** + * Default sorting function for transparent render items. + * + * @private + * @function + * @param {Object} a - The first render item. + * @param {Object} b - The second render item. + * @return {number} A numeric value which defines the sort order. + */ +function reversePainterSortStable( a, b ) { + + if ( a.groupOrder !== b.groupOrder ) { + + return a.groupOrder - b.groupOrder; + + } else if ( a.renderOrder !== b.renderOrder ) { + + return a.renderOrder - b.renderOrder; + + } else if ( a.z !== b.z ) { + + return b.z - a.z; + + } else { + + return a.id - b.id; + + } + +} + +/** + * Returns `true` if the given transparent material requires a double pass. + * + * @private + * @function + * @param {Material} material - The transparent material. + * @return {boolean} Whether the given material requires a double pass or not. + */ +function needsDoublePass( material ) { + + const hasTransmission = material.transmission > 0 || material.transmissionNode; + + return hasTransmission && material.side === DoubleSide && material.forceSinglePass === false; + +} + +/** + * When the renderer analyzes the scene at the beginning of a render call, + * it stores 3D object for further processing in render lists. Depending on the + * properties of a 3D objects (like their transformation or material state), the + * objects are maintained in ordered lists for the actual rendering. + * + * Render lists are unique per scene and camera combination. + * + * @private + * @augments Pipeline + */ +class RenderList { + + /** + * Constructs a render list. + * + * @param {Lighting} lighting - The lighting management component. + * @param {Scene} scene - The scene. + * @param {Camera} camera - The camera the scene is rendered with. + */ + constructor( lighting, scene, camera ) { + + /** + * 3D objects are transformed into render items and stored in this array. + * + * @type {Array} + */ + this.renderItems = []; + + /** + * The current render items index. + * + * @type {number} + * @default 0 + */ + this.renderItemsIndex = 0; + + /** + * A list with opaque render items. + * + * @type {Array} + */ + this.opaque = []; + + /** + * A list with transparent render items which require + * double pass rendering (e.g. transmissive objects). + * + * @type {Array} + */ + this.transparentDoublePass = []; + + /** + * A list with transparent render items. + * + * @type {Array} + */ + this.transparent = []; + + /** + * A list with transparent render bundle data. + * + * @type {Array} + */ + this.bundles = []; + + /** + * The render list's lights node. This node is later + * relevant for the actual analytical light nodes which + * compute the scene's lighting in the shader. + * + * @type {LightsNode} + */ + this.lightsNode = lighting.getNode( scene, camera ); + + /** + * The scene's lights stored in an array. This array + * is used to setup the lights node. + * + * @type {Array} + */ + this.lightsArray = []; + + /** + * The scene. + * + * @type {Scene} + */ + this.scene = scene; + + /** + * The camera the scene is rendered with. + * + * @type {Camera} + */ + this.camera = camera; + + /** + * How many objects perform occlusion query tests. + * + * @type {number} + * @default 0 + */ + this.occlusionQueryCount = 0; + + } + + /** + * This method is called right at the beginning of a render call + * before the scene is analyzed. It prepares the internal data + * structures for the upcoming render lists generation. + * + * @return {RenderList} A reference to this render list. + */ + begin() { + + this.renderItemsIndex = 0; + + this.opaque.length = 0; + this.transparentDoublePass.length = 0; + this.transparent.length = 0; + this.bundles.length = 0; + + this.lightsArray.length = 0; + + this.occlusionQueryCount = 0; + + return this; + + } + + /** + * Returns a render item for the giving render item state. The state is defined + * by a series of object-related parameters. + * + * The method avoids object creation by holding render items and reusing them in + * subsequent render calls (just with different property values). + * + * @param {Object3D} object - The 3D object. + * @param {BufferGeometry} geometry - The 3D object's geometry. + * @param {Material} material - The 3D object's material. + * @param {number} groupOrder - The current group order. + * @param {number} z - Th 3D object's depth value (z value in clip space). + * @param {?number} group - {?Object} group - Only relevant for objects using multiple materials. This represents a group entry from the respective `BufferGeometry`. + * @param {ClippingContext} clippingContext - The current clipping context. + * @return {Object} The render item. + */ + getNextRenderItem( object, geometry, material, groupOrder, z, group, clippingContext ) { + + let renderItem = this.renderItems[ this.renderItemsIndex ]; + + if ( renderItem === undefined ) { + + renderItem = { + id: object.id, + object: object, + geometry: geometry, + material: material, + groupOrder: groupOrder, + renderOrder: object.renderOrder, + z: z, + group: group, + clippingContext: clippingContext + }; + + this.renderItems[ this.renderItemsIndex ] = renderItem; + + } else { + + renderItem.id = object.id; + renderItem.object = object; + renderItem.geometry = geometry; + renderItem.material = material; + renderItem.groupOrder = groupOrder; + renderItem.renderOrder = object.renderOrder; + renderItem.z = z; + renderItem.group = group; + renderItem.clippingContext = clippingContext; + + } + + this.renderItemsIndex ++; + + return renderItem; + + } + + /** + * Pushes the given object as a render item to the internal render lists. + * The selected lists depend on the object properties. + * + * @param {Object3D} object - The 3D object. + * @param {BufferGeometry} geometry - The 3D object's geometry. + * @param {Material} material - The 3D object's material. + * @param {number} groupOrder - The current group order. + * @param {number} z - Th 3D object's depth value (z value in clip space). + * @param {?number} group - {?Object} group - Only relevant for objects using multiple materials. This represents a group entry from the respective `BufferGeometry`. + * @param {ClippingContext} clippingContext - The current clipping context. + */ + push( object, geometry, material, groupOrder, z, group, clippingContext ) { + + const renderItem = this.getNextRenderItem( object, geometry, material, groupOrder, z, group, clippingContext ); + + if ( object.occlusionTest === true ) this.occlusionQueryCount ++; + + if ( material.transparent === true || material.transmission > 0 ) { + + if ( needsDoublePass( material ) ) this.transparentDoublePass.push( renderItem ); + + this.transparent.push( renderItem ); + + } else { + + this.opaque.push( renderItem ); + + } + + } + + /** + * Inserts the given object as a render item at the start of the internal render lists. + * The selected lists depend on the object properties. + * + * @param {Object3D} object - The 3D object. + * @param {BufferGeometry} geometry - The 3D object's geometry. + * @param {Material} material - The 3D object's material. + * @param {number} groupOrder - The current group order. + * @param {number} z - Th 3D object's depth value (z value in clip space). + * @param {?number} group - {?Object} group - Only relevant for objects using multiple materials. This represents a group entry from the respective `BufferGeometry`. + * @param {ClippingContext} clippingContext - The current clipping context. + */ + unshift( object, geometry, material, groupOrder, z, group, clippingContext ) { + + const renderItem = this.getNextRenderItem( object, geometry, material, groupOrder, z, group, clippingContext ); + + if ( material.transparent === true || material.transmission > 0 ) { + + if ( needsDoublePass( material ) ) this.transparentDoublePass.unshift( renderItem ); + + this.transparent.unshift( renderItem ); + + } else { + + this.opaque.unshift( renderItem ); + + } + + } + + /** + * Pushes render bundle group data into the render list. + * + * @param {Object} group - Bundle group data. + */ + pushBundle( group ) { + + this.bundles.push( group ); + + } + + /** + * Pushes a light into the render list. + * + * @param {Light} light - The light. + */ + pushLight( light ) { + + this.lightsArray.push( light ); + + } + + /** + * Sorts the internal render lists. + * + * @param {?function(any, any): number} customOpaqueSort - A custom sort function for opaque objects. + * @param {?function(any, any): number} customTransparentSort - A custom sort function for transparent objects. + */ + sort( customOpaqueSort, customTransparentSort ) { + + if ( this.opaque.length > 1 ) this.opaque.sort( customOpaqueSort || painterSortStable ); + if ( this.transparentDoublePass.length > 1 ) this.transparentDoublePass.sort( customTransparentSort || reversePainterSortStable ); + if ( this.transparent.length > 1 ) this.transparent.sort( customTransparentSort || reversePainterSortStable ); + + } + + /** + * This method performs finalizing tasks right after the render lists + * have been generated. + */ + finish() { + + // update lights + + this.lightsNode.setLights( this.lightsArray ); + + // Clear references from inactive renderItems in the list + + for ( let i = this.renderItemsIndex, il = this.renderItems.length; i < il; i ++ ) { + + const renderItem = this.renderItems[ i ]; + + if ( renderItem.id === null ) break; + + renderItem.id = null; + renderItem.object = null; + renderItem.geometry = null; + renderItem.material = null; + renderItem.groupOrder = null; + renderItem.renderOrder = null; + renderItem.z = null; + renderItem.group = null; + renderItem.clippingContext = null; + + } + + } + +} + +const _chainKeys$4 = []; + +/** + * This renderer module manages the render lists which are unique + * per scene and camera combination. + * + * @private + */ +class RenderLists { + + /** + * Constructs a render lists management component. + * + * @param {Lighting} lighting - The lighting management component. + */ + constructor( lighting ) { + + /** + * The lighting management component. + * + * @type {Lighting} + */ + this.lighting = lighting; + + /** + * The internal chain map which holds the render lists. + * + * @type {ChainMap} + */ + this.lists = new ChainMap(); + + } + + /** + * Returns a render list for the given scene and camera. + * + * @param {Scene} scene - The scene. + * @param {Camera} camera - The camera. + * @return {RenderList} The render list. + */ + get( scene, camera ) { + + const lists = this.lists; + + _chainKeys$4[ 0 ] = scene; + _chainKeys$4[ 1 ] = camera; + + let list = lists.get( _chainKeys$4 ); + + if ( list === undefined ) { + + list = new RenderList( this.lighting, scene, camera ); + lists.set( _chainKeys$4, list ); + + } + + _chainKeys$4.length = 0; + + return list; + + } + + /** + * Frees all internal resources. + */ + dispose() { + + this.lists = new ChainMap(); + + } + +} + +let _id$7 = 0; + +/** + * Any render or compute command is executed in a specific context that defines + * the state of the renderer and its backend. Typical examples for such context + * data are the current clear values or data from the active framebuffer. This + * module is used to represent these contexts as objects. + * + * @private + */ +class RenderContext { + + /** + * Constructs a new render context. + */ + constructor() { + + /** + * The context's ID. + * + * @type {number} + */ + this.id = _id$7 ++; + + /** + * Whether the current active framebuffer has a color attachment. + * + * @type {boolean} + * @default true + */ + this.color = true; + + /** + * Whether the color attachment should be cleared or not. + * + * @type {boolean} + * @default true + */ + this.clearColor = true; + + /** + * The clear color value. + * + * @type {Object} + * @default true + */ + this.clearColorValue = { r: 0, g: 0, b: 0, a: 1 }; + + /** + * Whether the current active framebuffer has a depth attachment. + * + * @type {boolean} + * @default true + */ + this.depth = true; + + /** + * Whether the depth attachment should be cleared or not. + * + * @type {boolean} + * @default true + */ + this.clearDepth = true; + + /** + * The clear depth value. + * + * @type {number} + * @default 1 + */ + this.clearDepthValue = 1; + + /** + * Whether the current active framebuffer has a stencil attachment. + * + * @type {boolean} + * @default false + */ + this.stencil = false; + + /** + * Whether the stencil attachment should be cleared or not. + * + * @type {boolean} + * @default true + */ + this.clearStencil = true; + + /** + * The clear stencil value. + * + * @type {number} + * @default 1 + */ + this.clearStencilValue = 1; + + /** + * By default the viewport encloses the entire framebuffer If a smaller + * viewport is manually defined, this property is to `true` by the renderer. + * + * @type {boolean} + * @default false + */ + this.viewport = false; + + /** + * The viewport value. This value is in physical pixels meaning it incorporates + * the renderer's pixel ratio. The viewport property of render targets or + * the renderer is in logical pixels. + * + * @type {Vector4} + */ + this.viewportValue = new Vector4(); + + /** + * When the scissor test is active and scissor rectangle smaller than the + * framebuffers dimensions, this property is to `true` by the renderer. + * + * @type {boolean} + * @default false + */ + this.scissor = false; + + /** + * The scissor rectangle. + * + * @type {Vector4} + */ + this.scissorValue = new Vector4(); + + /** + * The active render target. + * + * @type {?RenderTarget} + * @default null + */ + this.renderTarget = null; + + /** + * The textures of the active render target. + * `null` when no render target is set. + * + * @type {?Array} + * @default null + */ + this.textures = null; + + /** + * The depth texture of the active render target. + * `null` when no render target is set. + * + * @type {?DepthTexture} + * @default null + */ + this.depthTexture = null; + + /** + * The active cube face. + * + * @type {number} + * @default 0 + */ + this.activeCubeFace = 0; + + /** + * The active mipmap level. + * + * @type {number} + * @default 0 + */ + this.activeMipmapLevel = 0; + + /** + * The number of MSAA samples. This value is always `1` when + * MSAA isn't used. + * + * @type {number} + * @default 1 + */ + this.sampleCount = 1; + + /** + * The active render target's width in physical pixels. + * + * @type {number} + * @default 0 + */ + this.width = 0; + + /** + * The active render target's height in physical pixels. + * + * @type {number} + * @default 0 + */ + this.height = 0; + + /** + * The occlusion query count. + * + * @type {number} + * @default 0 + */ + this.occlusionQueryCount = 0; + + /** + * The current clipping context. + * + * @type {?ClippingContext} + * @default null + */ + this.clippingContext = null; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isRenderContext = true; + + } + + /** + * Returns the cache key of this render context. + * + * @return {number} The cache key. + */ + getCacheKey() { + + return getCacheKey( this ); + + } + +} + +/** + * Computes a cache key for the given render context. This key + * should identify the render target state so it is possible to + * configure the correct attachments in the respective backend. + * + * @param {RenderContext} renderContext - The render context. + * @return {number} The cache key. + */ +function getCacheKey( renderContext ) { + + const { textures, activeCubeFace } = renderContext; + + const values = [ activeCubeFace ]; + + for ( const texture of textures ) { + + values.push( texture.id ); + + } + + return hashArray( values ); + +} + +const _chainKeys$3 = []; +const _defaultScene = /*@__PURE__*/ new Scene(); +const _defaultCamera = /*@__PURE__*/ new Camera(); + +/** + * This module manages the render contexts of the renderer. + * + * @private + */ +class RenderContexts { + + /** + * Constructs a new render context management component. + */ + constructor() { + + /** + * A dictionary that manages render contexts in chain maps + * for each attachment state. + * + * @type {Object} + */ + this.chainMaps = {}; + + } + + /** + * Returns a render context for the given scene, camera and render target. + * + * @param {Scene} scene - The scene. + * @param {Camera} camera - The camera that is used to render the scene. + * @param {?RenderTarget} [renderTarget=null] - The active render target. + * @return {RenderContext} The render context. + */ + get( scene, camera, renderTarget = null ) { + + _chainKeys$3[ 0 ] = scene; + _chainKeys$3[ 1 ] = camera; + + let attachmentState; + + if ( renderTarget === null ) { + + attachmentState = 'default'; + + } else { + + const format = renderTarget.texture.format; + const count = renderTarget.textures.length; + + attachmentState = `${ count }:${ format }:${ renderTarget.samples }:${ renderTarget.depthBuffer }:${ renderTarget.stencilBuffer }`; + + } + + const chainMap = this._getChainMap( attachmentState ); + + let renderState = chainMap.get( _chainKeys$3 ); + + if ( renderState === undefined ) { + + renderState = new RenderContext(); + + chainMap.set( _chainKeys$3, renderState ); + + } + + _chainKeys$3.length = 0; + + if ( renderTarget !== null ) renderState.sampleCount = renderTarget.samples === 0 ? 1 : renderTarget.samples; + + return renderState; + + } + + /** + * Returns a render context intended for clear operations. + * + * @param {?RenderTarget} [renderTarget=null] - The active render target. + * @return {RenderContext} The render context. + */ + getForClear( renderTarget = null ) { + + return this.get( _defaultScene, _defaultCamera, renderTarget ); + + } + + /** + * Returns a chain map for the given attachment state. + * + * @private + * @param {string} attachmentState - The attachment state. + * @return {ChainMap} The chain map. + */ + _getChainMap( attachmentState ) { + + return this.chainMaps[ attachmentState ] || ( this.chainMaps[ attachmentState ] = new ChainMap() ); + + } + + /** + * Frees internal resources. + */ + dispose() { + + this.chainMaps = {}; + + } + +} + +const _size$3 = /*@__PURE__*/ new Vector3(); + +/** + * This module manages the textures of the renderer. + * + * @private + * @augments DataMap + */ +class Textures extends DataMap { + + /** + * Constructs a new texture management component. + * + * @param {Renderer} renderer - The renderer. + * @param {Backend} backend - The renderer's backend. + * @param {Info} info - Renderer component for managing metrics and monitoring data. + */ + constructor( renderer, backend, info ) { + + super(); + + /** + * The renderer. + * + * @type {Renderer} + */ + this.renderer = renderer; + + /** + * The backend. + * + * @type {Backend} + */ + this.backend = backend; + + /** + * Renderer component for managing metrics and monitoring data. + * + * @type {Info} + */ + this.info = info; + + } + + /** + * Updates the given render target. Based on the given render target configuration, + * it updates the texture states representing the attachments of the framebuffer. + * + * @param {RenderTarget} renderTarget - The render target to update. + * @param {number} [activeMipmapLevel=0] - The active mipmap level. + */ + updateRenderTarget( renderTarget, activeMipmapLevel = 0 ) { + + const renderTargetData = this.get( renderTarget ); + + const sampleCount = renderTarget.samples === 0 ? 1 : renderTarget.samples; + const depthTextureMips = renderTargetData.depthTextureMips || ( renderTargetData.depthTextureMips = {} ); + + const textures = renderTarget.textures; + + const size = this.getSize( textures[ 0 ] ); + + const mipWidth = size.width >> activeMipmapLevel; + const mipHeight = size.height >> activeMipmapLevel; + + let depthTexture = renderTarget.depthTexture || depthTextureMips[ activeMipmapLevel ]; + const useDepthTexture = renderTarget.depthBuffer === true || renderTarget.stencilBuffer === true; + + let textureNeedsUpdate = false; + + if ( depthTexture === undefined && useDepthTexture ) { + + depthTexture = new DepthTexture(); + + depthTexture.format = renderTarget.stencilBuffer ? DepthStencilFormat : DepthFormat; + depthTexture.type = renderTarget.stencilBuffer ? UnsignedInt248Type : UnsignedIntType; // FloatType + depthTexture.image.width = mipWidth; + depthTexture.image.height = mipHeight; + depthTexture.image.depth = size.depth; + depthTexture.isArrayTexture = renderTarget.multiview === true && size.depth > 1; + + depthTextureMips[ activeMipmapLevel ] = depthTexture; + + } + + if ( renderTargetData.width !== size.width || size.height !== renderTargetData.height ) { + + textureNeedsUpdate = true; + + if ( depthTexture ) { + + depthTexture.needsUpdate = true; + depthTexture.image.width = mipWidth; + depthTexture.image.height = mipHeight; + depthTexture.image.depth = depthTexture.isArrayTexture ? depthTexture.image.depth : 1; + + } + + } + + renderTargetData.width = size.width; + renderTargetData.height = size.height; + renderTargetData.textures = textures; + renderTargetData.depthTexture = depthTexture || null; + renderTargetData.depth = renderTarget.depthBuffer; + renderTargetData.stencil = renderTarget.stencilBuffer; + renderTargetData.renderTarget = renderTarget; + + if ( renderTargetData.sampleCount !== sampleCount ) { + + textureNeedsUpdate = true; + + if ( depthTexture ) { + + depthTexture.needsUpdate = true; + + } + + renderTargetData.sampleCount = sampleCount; + + } + + // + + + const options = { sampleCount }; + + // XR render targets require no texture updates + + if ( renderTarget.isXRRenderTarget !== true ) { + + for ( let i = 0; i < textures.length; i ++ ) { + + const texture = textures[ i ]; + + if ( textureNeedsUpdate ) texture.needsUpdate = true; + + this.updateTexture( texture, options ); + + } + + if ( depthTexture ) { + + this.updateTexture( depthTexture, options ); + + } + + } + + // dispose handler + + if ( renderTargetData.initialized !== true ) { + + renderTargetData.initialized = true; + + // dispose + + const onDispose = () => { + + renderTarget.removeEventListener( 'dispose', onDispose ); + + for ( let i = 0; i < textures.length; i ++ ) { + + this._destroyTexture( textures[ i ] ); + + } + + if ( depthTexture ) { + + this._destroyTexture( depthTexture ); + + } + + this.delete( renderTarget ); + + }; + + renderTarget.addEventListener( 'dispose', onDispose ); + + } + + } + + /** + * Updates the given texture. Depending on the texture state, this method + * triggers the upload of texture data to the GPU memory. If the texture data are + * not yet ready for the upload, it uses default texture data for as a placeholder. + * + * @param {Texture} texture - The texture to update. + * @param {Object} [options={}] - The options. + */ + updateTexture( texture, options = {} ) { + + const textureData = this.get( texture ); + if ( textureData.initialized === true && textureData.version === texture.version ) return; + + const isRenderTarget = texture.isRenderTargetTexture || texture.isDepthTexture || texture.isFramebufferTexture; + const backend = this.backend; + + if ( isRenderTarget && textureData.initialized === true ) { + + // it's an update + + backend.destroySampler( texture ); + backend.destroyTexture( texture ); + + } + + // + + if ( texture.isFramebufferTexture ) { + + const renderTarget = this.renderer.getRenderTarget(); + + if ( renderTarget ) { + + texture.type = renderTarget.texture.type; + + } else { + + texture.type = UnsignedByteType; + + } + + } + + // + + const { width, height, depth } = this.getSize( texture ); + + options.width = width; + options.height = height; + options.depth = depth; + options.needsMipmaps = this.needsMipmaps( texture ); + options.levels = options.needsMipmaps ? this.getMipLevels( texture, width, height ) : 1; + + // + + if ( isRenderTarget || texture.isStorageTexture === true ) { + + backend.createSampler( texture ); + backend.createTexture( texture, options ); + + textureData.generation = texture.version; + + } else { + + const needsCreate = textureData.initialized !== true; + + if ( needsCreate ) backend.createSampler( texture ); + + if ( texture.version > 0 ) { + + const image = texture.image; + + if ( image === undefined ) { + + console.warn( 'THREE.Renderer: Texture marked for update but image is undefined.' ); + + } else if ( image.complete === false ) { + + console.warn( 'THREE.Renderer: Texture marked for update but image is incomplete.' ); + + } else { + + if ( texture.images ) { + + const images = []; + + for ( const image of texture.images ) { + + images.push( image ); + + } + + options.images = images; + + } else { + + options.image = image; + + } + + if ( textureData.isDefaultTexture === undefined || textureData.isDefaultTexture === true ) { + + backend.createTexture( texture, options ); + + textureData.isDefaultTexture = false; + textureData.generation = texture.version; + + } + + if ( texture.source.dataReady === true ) backend.updateTexture( texture, options ); + + if ( options.needsMipmaps && texture.mipmaps.length === 0 ) backend.generateMipmaps( texture ); + + } + + } else { + + // async update + + backend.createDefaultTexture( texture ); + + textureData.isDefaultTexture = true; + textureData.generation = texture.version; + + } + + } + + // dispose handler + + if ( textureData.initialized !== true ) { + + textureData.initialized = true; + textureData.generation = texture.version; + + // + + this.info.memory.textures ++; + + // dispose + + const onDispose = () => { + + texture.removeEventListener( 'dispose', onDispose ); + + this._destroyTexture( texture ); + + }; + + texture.addEventListener( 'dispose', onDispose ); + + } + + // + + textureData.version = texture.version; + + } + + /** + * Computes the size of the given texture and writes the result + * into the target vector. This vector is also returned by the + * method. + * + * If no texture data are available for the compute yet, the method + * returns default size values. + * + * @param {Texture} texture - The texture to compute the size for. + * @param {Vector3} target - The target vector. + * @return {Vector3} The target vector. + */ + getSize( texture, target = _size$3 ) { + + let image = texture.images ? texture.images[ 0 ] : texture.image; + + if ( image ) { + + if ( image.image !== undefined ) image = image.image; + + target.width = image.width || 1; + target.height = image.height || 1; + target.depth = texture.isCubeTexture ? 6 : ( image.depth || 1 ); + + } else { + + target.width = target.height = target.depth = 1; + + } + + return target; + + } + + /** + * Computes the number of mipmap levels for the given texture. + * + * @param {Texture} texture - The texture. + * @param {number} width - The texture's width. + * @param {number} height - The texture's height. + * @return {number} The number of mipmap levels. + */ + getMipLevels( texture, width, height ) { + + let mipLevelCount; + + if ( texture.isCompressedTexture ) { + + if ( texture.mipmaps ) { + + mipLevelCount = texture.mipmaps.length; + + } else { + + mipLevelCount = 1; + + } + + } else { + + mipLevelCount = Math.floor( Math.log2( Math.max( width, height ) ) ) + 1; + + } + + return mipLevelCount; + + } + + /** + * Returns `true` if the given texture requires mipmaps. + * + * @param {Texture} texture - The texture. + * @return {boolean} Whether mipmaps are required or not. + */ + needsMipmaps( texture ) { + + return texture.isCompressedTexture === true || texture.generateMipmaps; + + } + + /** + * Frees internal resource when the given texture isn't + * required anymore. + * + * @param {Texture} texture - The texture to destroy. + */ + _destroyTexture( texture ) { + + if ( this.has( texture ) === true ) { + + this.backend.destroySampler( texture ); + this.backend.destroyTexture( texture ); + + this.delete( texture ); + + this.info.memory.textures --; + + } + + } + +} + +/** + * A four-component version of {@link Color} which is internally + * used by the renderer to represents clear color with alpha as + * one object. + * + * @private + * @augments Color + */ +class Color4 extends Color { + + /** + * Constructs a new four-component color. + * You can also pass a single THREE.Color, hex or + * string argument to this constructor. + * + * @param {number|string} [r=1] - The red value. + * @param {number} [g=1] - The green value. + * @param {number} [b=1] - The blue value. + * @param {number} [a=1] - The alpha value. + */ + constructor( r, g, b, a = 1 ) { + + super( r, g, b ); + + this.a = a; + + } + + /** + * Overwrites the default to honor alpha. + * You can also pass a single THREE.Color, hex or + * string argument to this method. + * + * @param {number|string|Color} r - The red value. + * @param {number} g - The green value. + * @param {number} b - The blue value. + * @param {number} [a=1] - The alpha value. + * @return {Color4} A reference to this object. + */ + set( r, g, b, a = 1 ) { + + this.a = a; + + return super.set( r, g, b ); + + } + + /** + * Overwrites the default to honor alpha. + * + * @param {Color4} color - The color to copy. + * @return {Color4} A reference to this object. + */ + copy( color ) { + + if ( color.a !== undefined ) this.a = color.a; + + return super.copy( color ); + + } + + /** + * Overwrites the default to honor alpha. + * + * @return {Color4} The cloned color. + */ + clone() { + + return new this.constructor( this.r, this.g, this.b, this.a ); + + } + +} + +/** + * Special version of {@link PropertyNode} which is used for parameters. + * + * @augments PropertyNode + */ +class ParameterNode extends PropertyNode { + + static get type() { + + return 'ParameterNode'; + + } + + /** + * Constructs a new parameter node. + * + * @param {string} nodeType - The type of the node. + * @param {?string} [name=null] - The name of the parameter in the shader. + */ + constructor( nodeType, name = null ) { + + super( nodeType, name ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isParameterNode = true; + + } + + getHash() { + + return this.uuid; + + } + + generate() { + + return this.name; + + } + +} + +/** + * TSL function for creating a parameter node. + * + * @tsl + * @function + * @param {string} type - The type of the node. + * @param {?string} name - The name of the parameter in the shader. + * @returns {ParameterNode} + */ +const parameter = ( type, name ) => nodeObject( new ParameterNode( type, name ) ); + +/** + * Stack is a helper for Nodes that need to produce stack-based code instead of continuous flow. + * They are usually needed in cases like `If`, `Else`. + * + * @augments Node + */ +class StackNode extends Node { + + static get type() { + + return 'StackNode'; + + } + + /** + * Constructs a new stack node. + * + * @param {?StackNode} [parent=null] - The parent stack node. + */ + constructor( parent = null ) { + + super(); + + /** + * List of nodes. + * + * @type {Array} + */ + this.nodes = []; + + /** + * The output node. + * + * @type {?Node} + * @default null + */ + this.outputNode = null; + + /** + * The parent stack node. + * + * @type {?StackNode} + * @default null + */ + this.parent = parent; + + /** + * The current conditional node. + * + * @private + * @type {ConditionalNode} + * @default null + */ + this._currentCond = null; + + /** + * The expression node. Only + * relevant for Switch/Case. + * + * @private + * @type {Node} + * @default null + */ + this._expressionNode = null; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isStackNode = true; + + } + + getNodeType( builder ) { + + return this.outputNode ? this.outputNode.getNodeType( builder ) : 'void'; + + } + + getMemberType( builder, name ) { + + return this.outputNode ? this.outputNode.getMemberType( builder, name ) : 'void'; + + } + + /** + * Adds a node to this stack. + * + * @param {Node} node - The node to add. + * @return {StackNode} A reference to this stack node. + */ + add( node ) { + + this.nodes.push( node ); + + return this; + + } + + /** + * Represent an `if` statement in TSL. + * + * @param {Node} boolNode - Represents the condition. + * @param {Function} method - TSL code which is executed if the condition evaluates to `true`. + * @return {StackNode} A reference to this stack node. + */ + If( boolNode, method ) { + + const methodNode = new ShaderNode( method ); + this._currentCond = select( boolNode, methodNode ); + + return this.add( this._currentCond ); + + } + + /** + * Represent an `elseif` statement in TSL. + * + * @param {Node} boolNode - Represents the condition. + * @param {Function} method - TSL code which is executed if the condition evaluates to `true`. + * @return {StackNode} A reference to this stack node. + */ + ElseIf( boolNode, method ) { + + const methodNode = new ShaderNode( method ); + const ifNode = select( boolNode, methodNode ); + + this._currentCond.elseNode = ifNode; + this._currentCond = ifNode; + + return this; + + } + + /** + * Represent an `else` statement in TSL. + * + * @param {Function} method - TSL code which is executed in the `else` case. + * @return {StackNode} A reference to this stack node. + */ + Else( method ) { + + this._currentCond.elseNode = new ShaderNode( method ); + + return this; + + } + + /** + * Represents a `switch` statement in TSL. + * + * @param {any} expression - Represents the expression. + * @param {Function} method - TSL code which is executed if the condition evaluates to `true`. + * @return {StackNode} A reference to this stack node. + */ + Switch( expression ) { + + this._expressionNode = nodeObject( expression ); + + return this; + + } + + /** + * Represents a `case` statement in TSL. The TSL version accepts an arbitrary numbers of values. + * The last parameter must be the callback method that should be executed in the `true` case. + * + * @param {...any} params - The values of the `Case()` statement as well as the callback method. + * @return {StackNode} A reference to this stack node. + */ + Case( ...params ) { + + const caseNodes = []; + + // extract case nodes from the parameter list + + if ( params.length >= 2 ) { + + for ( let i = 0; i < params.length - 1; i ++ ) { + + caseNodes.push( this._expressionNode.equal( nodeObject( params[ i ] ) ) ); + + } + + } else { + + throw new Error( 'TSL: Invalid parameter length. Case() requires at least two parameters.' ); + + } + + // extract method + + const method = params[ params.length - 1 ]; + const methodNode = new ShaderNode( method ); + + // chain multiple cases when using Case( 1, 2, 3, () => {} ) + + let caseNode = caseNodes[ 0 ]; + + for ( let i = 1; i < caseNodes.length; i ++ ) { + + caseNode = caseNode.or( caseNodes[ i ] ); + + } + + // build condition + + const condNode = select( caseNode, methodNode ); + + if ( this._currentCond === null ) { + + this._currentCond = condNode; + + return this.add( this._currentCond ); + + } else { + + this._currentCond.elseNode = condNode; + this._currentCond = condNode; + + return this; + + } + + } + + /** + * Represents the default code block of a Switch/Case statement. + * + * @param {Function} method - TSL code which is executed in the `else` case. + * @return {StackNode} A reference to this stack node. + */ + Default( method ) { + + this.Else( method ); + + return this; + + } + + build( builder, ...params ) { + + const previousBuildStack = builder.currentStack; + const previousStack = getCurrentStack(); + + setCurrentStack( this ); + + builder.currentStack = this; + + const buildStage = builder.buildStage; + + for ( const node of this.nodes ) { + + if ( buildStage === 'setup' ) { + + node.build( builder ); + + } else if ( buildStage === 'analyze' ) { + + node.build( builder, this ); + + } else if ( buildStage === 'generate' ) { + + const stages = builder.getDataFromNode( node, 'any' ).stages; + const parents = stages && stages[ builder.shaderStage ]; + + if ( node.isVarNode && parents && parents.length === 1 && parents[ 0 ] && parents[ 0 ].isStackNode ) { + + continue; // skip var nodes that are only used in .toVarying() + + } + + node.build( builder, 'void' ); + + } + + } + + const result = this.outputNode ? this.outputNode.build( builder, ...params ) : super.build( builder, ...params ); + + setCurrentStack( previousStack ); + + builder.currentStack = previousBuildStack; + + return result; + + } + +} + +/** + * TSL function for creating a stack node. + * + * @tsl + * @function + * @param {?StackNode} [parent=null] - The parent stack node. + * @returns {StackNode} + */ +const stack = /*@__PURE__*/ nodeProxy( StackNode ).setParameterLength( 0, 1 ); + +/** + * Generates a layout for struct members. + * This function takes an object representing struct members and returns an array of member layouts. + * Each member layout includes the member's name, type, and whether it is atomic. + * + * @param {Object.} members - An object where keys are member names and values are either types (as strings) or objects with type and atomic properties. + * @returns {Array.<{name: string, type: string, atomic: boolean}>} An array of member layouts. + */ +function getMembersLayout( members ) { + + return Object.entries( members ).map( ( [ name, value ] ) => { + + if ( typeof value === 'string' ) { + + return { name, type: value, atomic: false }; + + } + + return { name, type: value.type, atomic: value.atomic || false }; + + } ); + +} + +/** + * Represents a struct type node in the node-based system. + * This class is used to define and manage the layout and types of struct members. + * It extends the base Node class and provides methods to get the length of the struct, + * retrieve member types, and generate the struct type for a builder. + * + * @augments Node + */ +class StructTypeNode extends Node { + + static get type() { + + return 'StructTypeNode'; + + } + + /** + * Creates an instance of StructTypeNode. + * + * @param {Object} membersLayout - The layout of the members for the struct. + * @param {?string} [name=null] - The optional name of the struct. + */ + constructor( membersLayout, name = null ) { + + super( 'struct' ); + + /** + * The layout of the members for the struct + * + * @type {Array.<{name: string, type: string, atomic: boolean}>} + */ + this.membersLayout = getMembersLayout( membersLayout ); + + /** + * The name of the struct. + * + * @type {?string} + * @default null + */ + this.name = name; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isStructLayoutNode = true; + + } + + /** + * Returns the length of the struct. + * The length is calculated by summing the lengths of the struct's members. + * + * @returns {number} The length of the struct. + */ + getLength() { + + const GPU_CHUNK_BYTES = 8; + const BYTES_PER_ELEMENT = Float32Array.BYTES_PER_ELEMENT; + + let offset = 0; // global buffer offset in bytes + + for ( const member of this.membersLayout ) { + + const type = member.type; + + const itemSize = getMemoryLengthFromType( type ) * BYTES_PER_ELEMENT; + const boundary = getByteBoundaryFromType( type ); + + const chunkOffset = offset % GPU_CHUNK_BYTES; // offset in the current chunk + const chunkPadding = chunkOffset % boundary; // required padding to match boundary + const chunkStart = chunkOffset + chunkPadding; // start position in the current chunk for the data + + offset += chunkPadding; + + // Check for chunk overflow + if ( chunkStart !== 0 && ( GPU_CHUNK_BYTES - chunkStart ) < itemSize ) { + + // Add padding to the end of the chunk + offset += ( GPU_CHUNK_BYTES - chunkStart ); + + } + + offset += itemSize; + + } + + return ( Math.ceil( offset / GPU_CHUNK_BYTES ) * GPU_CHUNK_BYTES ) / BYTES_PER_ELEMENT; + + } + + getMemberType( builder, name ) { + + const member = this.membersLayout.find( m => m.name === name ); + + return member ? member.type : 'void'; + + } + + getNodeType( builder ) { + + const structType = builder.getStructTypeFromNode( this, this.membersLayout, this.name ); + + return structType.name; + + } + + setup( builder ) { + + builder.addInclude( this ); + + } + + generate( builder ) { + + return this.getNodeType( builder ); + + } + +} + +/** + * StructNode allows to create custom structures with multiple members. + * This can also be used to define structures in attribute and uniform data. + * + * ```js + * // Define a custom struct + * const BoundingBox = struct( { min: 'vec3', max: 'vec3' } ); + * + * // Create a new instance of the struct + * const bb = BoundingBox( vec3( 0 ), vec3( 1 ) ); // style 1 + * const bb = BoundingBox( { min: vec3( 0 ), max: vec3( 1 ) } ); // style 2 + * + * // Access the struct members + * const min = bb.get( 'min' ); + * + * // Assign a new value to a member + * min.assign( vec3() ); + * ``` + * @augments Node + */ +class StructNode extends Node { + + static get type() { + + return 'StructNode'; + + } + + constructor( structLayoutNode, values ) { + + super( 'vec3' ); + + this.structLayoutNode = structLayoutNode; + this.values = values; + + this.isStructNode = true; + + } + + getNodeType( builder ) { + + return this.structLayoutNode.getNodeType( builder ); + + } + + getMemberType( builder, name ) { + + return this.structLayoutNode.getMemberType( builder, name ); + + } + + generate( builder ) { + + const nodeVar = builder.getVarFromNode( this ); + const structType = nodeVar.type; + const propertyName = builder.getPropertyName( nodeVar ); + + builder.addLineFlowCode( `${ propertyName } = ${ builder.generateStruct( structType, this.structLayoutNode.membersLayout, this.values ) }`, this ); + + return nodeVar.name; + + } + +} + +/** + * TSL function for creating a struct node. + * + * @tsl + * @function + * @param {Object} membersLayout - The layout of the struct members. + * @param {?string} [name=null] - The name of the struct. + * @returns {Function} The struct function. + */ +const struct = ( membersLayout, name = null ) => { + + const structLayout = new StructTypeNode( membersLayout, name ); + + const struct = ( ...params ) => { + + let values = null; + + if ( params.length > 0 ) { + + if ( params[ 0 ].isNode ) { + + values = {}; + + const names = Object.keys( membersLayout ); + + for ( let i = 0; i < params.length; i ++ ) { + + values[ names[ i ] ] = params[ i ]; + + } + + } else { + + values = params[ 0 ]; + + } + + } + + return nodeObject( new StructNode( structLayout, values ) ); + + }; + + struct.layout = structLayout; + struct.isStruct = true; + + return struct; + +}; + +/** + * This node can be used to define multiple outputs in a shader programs. + * + * @augments Node + */ +class OutputStructNode extends Node { + + static get type() { + + return 'OutputStructNode'; + + } + + /** + * Constructs a new output struct node. The constructor can be invoked with an + * arbitrary number of nodes representing the members. + * + * @param {...Node} members - A parameter list of nodes. + */ + constructor( ...members ) { + + super(); + + /** + * An array of nodes which defines the output. + * + * @type {Array} + */ + this.members = members; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isOutputStructNode = true; + + } + + getNodeType( builder ) { + + const properties = builder.getNodeProperties( this ); + + if ( properties.membersLayout === undefined ) { + + const members = this.members; + const membersLayout = []; + + for ( let i = 0; i < members.length; i ++ ) { + + const name = 'm' + i; + const type = members[ i ].getNodeType( builder ); + + membersLayout.push( { name, type, index: i } ); + + } + + properties.membersLayout = membersLayout; + properties.structType = builder.getOutputStructTypeFromNode( this, properties.membersLayout ); + + } + + return properties.structType.name; + + } + + generate( builder ) { + + const propertyName = builder.getOutputStructName(); + const members = this.members; + + const structPrefix = propertyName !== '' ? propertyName + '.' : ''; + + for ( let i = 0; i < members.length; i ++ ) { + + const snippet = members[ i ].build( builder ); + + builder.addLineFlowCode( `${ structPrefix }m${ i } = ${ snippet }`, this ); + + } + + return propertyName; + + } + +} + +/** + * TSL function for creating an output struct node. + * + * @tsl + * @function + * @param {...Node} members - A parameter list of nodes. + * @returns {OutputStructNode} + */ +const outputStruct = /*@__PURE__*/ nodeProxy( OutputStructNode ); + +/** + * Returns the MRT texture index for the given name. + * + * @param {Array} textures - The textures of a MRT-configured render target. + * @param {string} name - The name of the MRT texture which index is requested. + * @return {number} The texture index. + */ +function getTextureIndex( textures, name ) { + + for ( let i = 0; i < textures.length; i ++ ) { + + if ( textures[ i ].name === name ) { + + return i; + + } + + } + + return -1; + +} + +/** + * This node can be used setup a MRT context for rendering. A typical MRT setup for + * post-processing is shown below: + * ```js + * const mrtNode = mrt( { + * output: output, + * normal: normalView + * } ) ); + * ``` + * The MRT output is defined as a dictionary. + * + * @augments OutputStructNode + */ +class MRTNode extends OutputStructNode { + + static get type() { + + return 'MRTNode'; + + } + + /** + * Constructs a new output struct node. + * + * @param {Object} outputNodes - The MRT outputs. + */ + constructor( outputNodes ) { + + super(); + + /** + * A dictionary representing the MRT outputs. The key + * is the name of the output, the value the node which produces + * the output result. + * + * @type {Object} + */ + this.outputNodes = outputNodes; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMRTNode = true; + + } + + /** + * Returns `true` if the MRT node has an output with the given name. + * + * @param {string} name - The name of the output. + * @return {NodeBuilder} Whether the MRT node has an output for the given name or not. + */ + has( name ) { + + return this.outputNodes[ name ] !== undefined; + + } + + /** + * Returns the output node for the given name. + * + * @param {string} name - The name of the output. + * @return {Node} The output node. + */ + get( name ) { + + return this.outputNodes[ name ]; + + } + + /** + * Merges the outputs of the given MRT node with the outputs of this node. + * + * @param {MRTNode} mrtNode - The MRT to merge. + * @return {MRTNode} A new MRT node with merged outputs.. + */ + merge( mrtNode ) { + + const outputs = { ...this.outputNodes, ...mrtNode.outputNodes }; + + return mrt( outputs ); + + } + + setup( builder ) { + + const outputNodes = this.outputNodes; + const mrt = builder.renderer.getRenderTarget(); + + const members = []; + + const textures = mrt.textures; + + for ( const name in outputNodes ) { + + const index = getTextureIndex( textures, name ); + + members[ index ] = vec4( outputNodes[ name ] ); + + } + + this.members = members; + + return super.setup( builder ); + + } + +} + +/** + * TSL function for creating a MRT node. + * + * @tsl + * @function + * @param {Object} outputNodes - The MRT outputs. + * @returns {MRTNode} + */ +const mrt = /*@__PURE__*/ nodeProxy( MRTNode ); + +/** + * Generates a hash value in the range `[0, 1]` from the given seed. + * + * @tsl + * @function + * @param {Node} seed - The seed. + * @return {Node} The hash value. + */ +const hash = /*@__PURE__*/ Fn( ( [ seed ] ) => { + + // Taken from https://www.shadertoy.com/view/XlGcRh, originally from pcg-random.org + + const state = seed.toUint().mul( 747796405 ).add( 2891336453 ); + const word = state.shiftRight( state.shiftRight( 28 ).add( 4 ) ).bitXor( state ).mul( 277803737 ); + const result = word.shiftRight( 22 ).bitXor( word ); + + return result.toFloat().mul( 1 / 2 ** 32 ); // Convert to range [0, 1) + +} ); + +/** + * A function that remaps the `[0,1]` interval into the `[0,1]` interval. + * The corners are mapped to `0` and the center to `1`. + * Reference: {@link https://iquilezles.org/articles/functions/}. + * + * @tsl + * @function + * @param {Node} x - The value to remap. + * @param {Node} k - Allows to control the remapping functions shape by rising the parabola to a power `k`. + * @return {Node} The remapped value. + */ +const parabola = ( x, k ) => pow( mul( 4.0, x.mul( sub( 1.0, x ) ) ), k ); + +/** + * A function that remaps the `[0,1]` interval into the `[0,1]` interval. + * Expands the sides and compresses the center, and keeps `0.5` mapped to `0.5`. + * Reference: {@link https://iquilezles.org/articles/functions/}. + * + * @tsl + * @function + * @param {Node} x - The value to remap. + * @param {Node} k - `k=1` is the identity curve,`k<1` produces the classic `gain()` shape, and `k>1` produces "s" shaped curves. + * @return {Node} The remapped value. + */ +const gain = ( x, k ) => x.lessThan( 0.5 ) ? parabola( x.mul( 2.0 ), k ).div( 2.0 ) : sub( 1.0, parabola( mul( sub( 1.0, x ), 2.0 ), k ).div( 2.0 ) ); + +/** + * A function that remaps the `[0,1]` interval into the `[0,1]` interval. + * A generalization of the `parabola()`. Keeps the corners mapped to 0 but allows the control of the shape one either side of the curve. + * Reference: {@link https://iquilezles.org/articles/functions/}. + * + * @tsl + * @function + * @param {Node} x - The value to remap. + * @param {Node} a - First control parameter. + * @param {Node} b - Second control parameter. + * @return {Node} The remapped value. + */ +const pcurve = ( x, a, b ) => pow( div( pow( x, a ), add( pow( x, a ), pow( sub( 1.0, x ), b ) ) ), 1.0 / a ); + +/** + * A phase shifted sinus curve that starts at zero and ends at zero, with bouncing behavior. + * Reference: {@link https://iquilezles.org/articles/functions/}. + * + * @tsl + * @function + * @param {Node} x - The value to compute the sin for. + * @param {Node} k - Controls the amount of bounces. + * @return {Node} The result value. + */ +const sinc = ( x, k ) => sin( PI.mul( k.mul( x ).sub( 1.0 ) ) ).div( PI.mul( k.mul( x ).sub( 1.0 ) ) ); + +// https://github.com/cabbibo/glsl-tri-noise-3d + + +const tri = /*@__PURE__*/ Fn( ( [ x ] ) => { + + return x.fract().sub( .5 ).abs(); + +} ).setLayout( { + name: 'tri', + type: 'float', + inputs: [ + { name: 'x', type: 'float' } + ] +} ); + +const tri3 = /*@__PURE__*/ Fn( ( [ p ] ) => { + + return vec3( tri( p.z.add( tri( p.y.mul( 1. ) ) ) ), tri( p.z.add( tri( p.x.mul( 1. ) ) ) ), tri( p.y.add( tri( p.x.mul( 1. ) ) ) ) ); + +} ).setLayout( { + name: 'tri3', + type: 'vec3', + inputs: [ + { name: 'p', type: 'vec3' } + ] +} ); + +/** + * Generates a noise value from the given position, speed and time parameters. + * + * @tsl + * @function + * @param {Node} position - The position. + * @param {Node} speed - The speed. + * @param {Node} time - The time. + * @return {Node} The generated noise. + */ +const triNoise3D = /*@__PURE__*/ Fn( ( [ position, speed, time ] ) => { + + const p = vec3( position ).toVar(); + const z = float( 1.4 ).toVar(); + const rz = float( 0.0 ).toVar(); + const bp = vec3( p ).toVar(); + + Loop( { start: float( 0.0 ), end: float( 3.0 ), type: 'float', condition: '<=' }, () => { + + const dg = vec3( tri3( bp.mul( 2.0 ) ) ).toVar(); + p.addAssign( dg.add( time.mul( float( 0.1 ).mul( speed ) ) ) ); + bp.mulAssign( 1.8 ); + z.mulAssign( 1.5 ); + p.mulAssign( 1.2 ); + + const t = float( tri( p.z.add( tri( p.x.add( tri( p.y ) ) ) ) ) ).toVar(); + rz.addAssign( t.div( z ) ); + bp.addAssign( 0.14 ); + + } ); + + return rz; + +} ).setLayout( { + name: 'triNoise3D', + type: 'float', + inputs: [ + { name: 'position', type: 'vec3' }, + { name: 'speed', type: 'float' }, + { name: 'time', type: 'float' } + ] +} ); + +/** + * This class allows to define multiple overloaded versions + * of the same function. Depending on the parameters of the function + * call, the node picks the best-fit overloaded version. + * + * @augments Node + */ +class FunctionOverloadingNode extends Node { + + static get type() { + + return 'FunctionOverloadingNode'; + + } + + /** + * Constructs a new function overloading node. + * + * @param {Array} functionNodes - Array of `Fn` function definitions. + * @param {...Node} parametersNodes - A list of parameter nodes. + */ + constructor( functionNodes = [], ...parametersNodes ) { + + super(); + + /** + * Array of `Fn` function definitions. + * + * @type {Array} + */ + this.functionNodes = functionNodes; + + /** + * A list of parameter nodes. + * + * @type {Array} + */ + this.parametersNodes = parametersNodes; + + /** + * The selected overloaded function call. + * + * @private + * @type {ShaderCallNodeInternal} + */ + this._candidateFnCall = null; + + /** + * This node is marked as global. + * + * @type {boolean} + * @default true + */ + this.global = true; + + } + + /** + * This method is overwritten since the node type is inferred from + * the function's return type. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType() { + + return this.functionNodes[ 0 ].shaderNode.layout.type; + + } + + setup( builder ) { + + const params = this.parametersNodes; + + let candidateFnCall = this._candidateFnCall; + + if ( candidateFnCall === null ) { + + let candidateFn = null; + let candidateScore = -1; + + for ( const functionNode of this.functionNodes ) { + + const shaderNode = functionNode.shaderNode; + const layout = shaderNode.layout; + + if ( layout === null ) { + + throw new Error( 'FunctionOverloadingNode: FunctionNode must be a layout.' ); + + } + + const inputs = layout.inputs; + + if ( params.length === inputs.length ) { + + let score = 0; + + for ( let i = 0; i < params.length; i ++ ) { + + const param = params[ i ]; + const input = inputs[ i ]; + + if ( param.getNodeType( builder ) === input.type ) { + + score ++; + + } else { + + score = 0; + + } + + } + + if ( score > candidateScore ) { + + candidateFn = functionNode; + candidateScore = score; + + } + + } + + } + + this._candidateFnCall = candidateFnCall = candidateFn( ...params ); + + } + + return candidateFnCall; + + } + +} + +const overloadingBaseFn = /*@__PURE__*/ nodeProxy( FunctionOverloadingNode ); + +/** + * TSL function for creating a function overloading node. + * + * @tsl + * @function + * @param {Array} functionNodes - Array of `Fn` function definitions. + * @returns {FunctionOverloadingNode} + */ +const overloadingFn = ( functionNodes ) => ( ...params ) => overloadingBaseFn( functionNodes, ...params ); + +/** + * Represents the elapsed time in seconds. + * + * @tsl + * @type {UniformNode} + */ +const time = /*@__PURE__*/ uniform( 0 ).setGroup( renderGroup ).onRenderUpdate( ( frame ) => frame.time ); + +/** + * Represents the delta time in seconds. + * + * @tsl + * @type {UniformNode} + */ +const deltaTime = /*@__PURE__*/ uniform( 0 ).setGroup( renderGroup ).onRenderUpdate( ( frame ) => frame.deltaTime ); + +/** + * Represents the current frame ID. + * + * @tsl + * @type {UniformNode} + */ +const frameId = /*@__PURE__*/ uniform( 0, 'uint' ).setGroup( renderGroup ).onRenderUpdate( ( frame ) => frame.frameId ); + +// Deprecated + +/** + * @tsl + * @function + * @deprecated since r170. Use {@link time} instead. + * + * @param {number} [timeScale=1] - The time scale. + * @returns {UniformNode} + */ +const timerLocal = ( timeScale = 1 ) => { // @deprecated, r170 + + console.warn( 'TSL: timerLocal() is deprecated. Use "time" instead.' ); + return time.mul( timeScale ); + +}; + +/** + * @tsl + * @function + * @deprecated since r170. Use {@link time} instead. + * + * @param {number} [timeScale=1] - The time scale. + * @returns {UniformNode} + */ +const timerGlobal = ( timeScale = 1 ) => { // @deprecated, r170 + + console.warn( 'TSL: timerGlobal() is deprecated. Use "time" instead.' ); + return time.mul( timeScale ); + +}; + +/** + * @tsl + * @function + * @deprecated since r170. Use {@link deltaTime} instead. + * + * @param {number} [timeScale=1] - The time scale. + * @returns {UniformNode} + */ +const timerDelta = ( timeScale = 1 ) => { // @deprecated, r170 + + console.warn( 'TSL: timerDelta() is deprecated. Use "deltaTime" instead.' ); + return deltaTime.mul( timeScale ); + +}; + +/** + * Generates a sine wave oscillation based on a timer. + * + * @tsl + * @function + * @param {Node} t - The timer to generate the oscillation with. + * @return {Node} The oscillation node. + */ +const oscSine = ( t = time ) => t.add( 0.75 ).mul( Math.PI * 2 ).sin().mul( 0.5 ).add( 0.5 ); + +/** + * Generates a square wave oscillation based on a timer. + * + * @tsl + * @function + * @param {Node} t - The timer to generate the oscillation with. + * @return {Node} The oscillation node. + */ +const oscSquare = ( t = time ) => t.fract().round(); + +/** + * Generates a triangle wave oscillation based on a timer. + * + * @tsl + * @function + * @param {Node} t - The timer to generate the oscillation with. + * @return {Node} The oscillation node. + */ +const oscTriangle = ( t = time ) => t.add( 0.5 ).fract().mul( 2 ).sub( 1 ).abs(); + +/** + * Generates a sawtooth wave oscillation based on a timer. + * + * @tsl + * @function + * @param {Node} t - The timer to generate the oscillation with. + * @return {Node} The oscillation node. + */ +const oscSawtooth = ( t = time ) => t.fract(); + +/** + * Rotates the given uv coordinates around a center point + * + * @tsl + * @function + * @param {Node} uv - The uv coordinates. + * @param {Node} rotation - The rotation defined in radians. + * @param {Node} center - The center of rotation + * @return {Node} The rotated uv coordinates. + */ +const rotateUV = /*@__PURE__*/ Fn( ( [ uv, rotation, center = vec2( 0.5 ) ] ) => { + + return rotate( uv.sub( center ), rotation ).add( center ); + +} ); + +/** + * Applies a spherical warping effect to the given uv coordinates. + * + * @tsl + * @function + * @param {Node} uv - The uv coordinates. + * @param {Node} strength - The strength of the effect. + * @param {Node} center - The center point + * @return {Node} The updated uv coordinates. + */ +const spherizeUV = /*@__PURE__*/ Fn( ( [ uv, strength, center = vec2( 0.5 ) ] ) => { + + const delta = uv.sub( center ); + const delta2 = delta.dot( delta ); + const delta4 = delta2.mul( delta2 ); + const deltaOffset = delta4.mul( strength ); + + return uv.add( delta.mul( deltaOffset ) ); + +} ); + +/** + * This can be used to achieve a billboarding behavior for flat meshes. That means they are + * oriented always towards the camera. + * + * ```js + * material.vertexNode = billboarding(); + * ``` + * + * @tsl + * @function + * @param {Object} config - The configuration object. + * @param {?Node} [config.position=null] - Can be used to define the vertex positions in world space. + * @param {boolean} [config.horizontal=true] - Whether to follow the camera rotation horizontally or not. + * @param {boolean} [config.vertical=false] - Whether to follow the camera rotation vertically or not. + * @return {Node} The updated vertex position in clip space. + */ +const billboarding = /*@__PURE__*/ Fn( ( { position = null, horizontal = true, vertical = false } ) => { + + let worldMatrix; + + if ( position !== null ) { + + worldMatrix = modelWorldMatrix.toVar(); + worldMatrix[ 3 ][ 0 ] = position.x; + worldMatrix[ 3 ][ 1 ] = position.y; + worldMatrix[ 3 ][ 2 ] = position.z; + + } else { + + worldMatrix = modelWorldMatrix; + + } + + const modelViewMatrix = cameraViewMatrix.mul( worldMatrix ); + + if ( defined( horizontal ) ) { + + modelViewMatrix[ 0 ][ 0 ] = modelWorldMatrix[ 0 ].length(); + modelViewMatrix[ 0 ][ 1 ] = 0; + modelViewMatrix[ 0 ][ 2 ] = 0; + + } + + if ( defined( vertical ) ) { + + modelViewMatrix[ 1 ][ 0 ] = 0; + modelViewMatrix[ 1 ][ 1 ] = modelWorldMatrix[ 1 ].length(); + modelViewMatrix[ 1 ][ 2 ] = 0; + + } + + modelViewMatrix[ 2 ][ 0 ] = 0; + modelViewMatrix[ 2 ][ 1 ] = 0; + modelViewMatrix[ 2 ][ 2 ] = 1; + + return cameraProjectionMatrix.mul( modelViewMatrix ).mul( positionLocal ); + +} ); + +/** + * A special version of a screen uv function that involves a depth comparison + * when computing the final uvs. The function mitigates visual errors when + * using viewport texture nodes for refraction purposes. Without this function + * objects in front of a refractive surface might appear on the refractive surface + * which is incorrect. + * + * @tsl + * @function + * @param {?Node} uv - Optional uv coordinates. By default `screenUV` is used. + * @return {Node} The update uv coordinates. + */ +const viewportSafeUV = /*@__PURE__*/ Fn( ( [ uv = null ] ) => { + + const depth = linearDepth(); + const depthDiff = linearDepth( viewportDepthTexture( uv ) ).sub( depth ); + const finalUV = depthDiff.lessThan( 0 ).select( screenUV, uv ); + + return finalUV; + +} ); + +/** + * Can be used to compute texture coordinates for animated sprite sheets. + * + * ```js + * const uvNode = spritesheetUV( vec2( 6, 6 ), uv(), time.mul( animationSpeed ) ); + * + * material.colorNode = texture( spriteSheet, uvNode ); + * ``` + * + * @augments Node + */ +class SpriteSheetUVNode extends Node { + + static get type() { + + return 'SpriteSheetUVNode'; + + } + + /** + * Constructs a new sprite sheet uv node. + * + * @param {Node} countNode - The node that defines the number of sprites in the x and y direction (e.g 6x6). + * @param {Node} [uvNode=uv()] - The uv node. + * @param {Node} [frameNode=float()] - The node that defines the current frame/sprite. + */ + constructor( countNode, uvNode = uv$1(), frameNode = float( 0 ) ) { + + super( 'vec2' ); + + /** + * The node that defines the number of sprites in the x and y direction (e.g 6x6). + * + * @type {Node} + */ + this.countNode = countNode; + + /** + * The uv node. + * + * @type {Node} + */ + this.uvNode = uvNode; + + /** + * The node that defines the current frame/sprite. + * + * @type {Node} + */ + this.frameNode = frameNode; + + } + + setup() { + + const { frameNode, uvNode, countNode } = this; + + const { width, height } = countNode; + + const frameNum = frameNode.mod( width.mul( height ) ).floor(); + + const column = frameNum.mod( width ); + const row = height.sub( frameNum.add( 1 ).div( width ).ceil() ); + + const scale = countNode.reciprocal(); + const uvFrameOffset = vec2( column, row ); + + return uvNode.add( uvFrameOffset ).mul( scale ); + + } + +} + +/** + * TSL function for creating a sprite sheet uv node. + * + * @tsl + * @function + * @param {Node} countNode - The node that defines the number of sprites in the x and y direction (e.g 6x6). + * @param {?Node} [uvNode=uv()] - The uv node. + * @param {?Node} [frameNode=float()] - The node that defines the current frame/sprite. + * @returns {SpriteSheetUVNode} + */ +const spritesheetUV = /*@__PURE__*/ nodeProxy( SpriteSheetUVNode ).setParameterLength( 3 ); + +/** + * TSL function for creating a triplanar textures node. + * + * Can be used for triplanar texture mapping. + * + * ```js + * material.colorNode = triplanarTexture( texture( diffuseMap ) ); + * ``` + * + * @tsl + * @function + * @param {Node} textureXNode - First texture node. + * @param {?Node} [textureYNode=null] - Second texture node. When not set, the shader will sample from `textureXNode` instead. + * @param {?Node} [textureZNode=null] - Third texture node. When not set, the shader will sample from `textureXNode` instead. + * @param {?Node} [scaleNode=float(1)] - The scale node. + * @param {?Node} [positionNode=positionLocal] - Vertex positions in local space. + * @param {?Node} [normalNode=normalLocal] - Normals in local space. + * @returns {Node} + */ +const triplanarTextures = /*@__PURE__*/ Fn( ( [ textureXNode, textureYNode = null, textureZNode = null, scaleNode = float( 1 ), positionNode = positionLocal, normalNode = normalLocal ] ) => { + + // Reference: https://github.com/keijiro/StandardTriplanar + + // Blending factor of triplanar mapping + let bf = normalNode.abs().normalize(); + bf = bf.div( bf.dot( vec3( 1.0 ) ) ); + + // Triplanar mapping + const tx = positionNode.yz.mul( scaleNode ); + const ty = positionNode.zx.mul( scaleNode ); + const tz = positionNode.xy.mul( scaleNode ); + + // Base color + const textureX = textureXNode.value; + const textureY = textureYNode !== null ? textureYNode.value : textureX; + const textureZ = textureZNode !== null ? textureZNode.value : textureX; + + const cx = texture( textureX, tx ).mul( bf.x ); + const cy = texture( textureY, ty ).mul( bf.y ); + const cz = texture( textureZ, tz ).mul( bf.z ); + + return add( cx, cy, cz ); + +} ); + +/** + * TSL function for creating a triplanar textures node. + * + * @tsl + * @function + * @param {Node} textureXNode - First texture node. + * @param {?Node} [textureYNode=null] - Second texture node. When not set, the shader will sample from `textureXNode` instead. + * @param {?Node} [textureZNode=null] - Third texture node. When not set, the shader will sample from `textureXNode` instead. + * @param {?Node} [scaleNode=float(1)] - The scale node. + * @param {?Node} [positionNode=positionLocal] - Vertex positions in local space. + * @param {?Node} [normalNode=normalLocal] - Normals in local space. + * @returns {Node} + */ +const triplanarTexture = ( ...params ) => triplanarTextures( ...params ); + +const _reflectorPlane = new Plane(); +const _normal = new Vector3(); +const _reflectorWorldPosition = new Vector3(); +const _cameraWorldPosition = new Vector3(); +const _rotationMatrix = new Matrix4(); +const _lookAtPosition = new Vector3( 0, 0, -1 ); +const clipPlane = new Vector4(); + +const _view = new Vector3(); +const _target = new Vector3(); +const _q = new Vector4(); + +const _size$2 = new Vector2(); + +const _defaultRT = new RenderTarget(); +const _defaultUV = screenUV.flipX(); + +_defaultRT.depthTexture = new DepthTexture( 1, 1 ); + +let _inReflector = false; + +/** + * This node can be used to implement mirror-like flat reflective surfaces. + * + * ```js + * const groundReflector = reflector(); + * material.colorNode = groundReflector; + * + * const plane = new Mesh( geometry, material ); + * plane.add( groundReflector.target ); + * ``` + * + * @augments TextureNode + */ +class ReflectorNode extends TextureNode { + + static get type() { + + return 'ReflectorNode'; + + } + + /** + * Constructs a new reflector node. + * + * @param {Object} [parameters={}] - An object holding configuration parameters. + * @param {Object3D} [parameters.target=new Object3D()] - The 3D object the reflector is linked to. + * @param {number} [parameters.resolution=1] - The resolution scale. + * @param {boolean} [parameters.generateMipmaps=false] - Whether mipmaps should be generated or not. + * @param {boolean} [parameters.bounces=true] - Whether reflectors can render other reflector nodes or not. + * @param {boolean} [parameters.depth=false] - Whether depth data should be generated or not. + * @param {TextureNode} [parameters.defaultTexture] - The default texture node. + * @param {ReflectorBaseNode} [parameters.reflector] - The reflector base node. + */ + constructor( parameters = {} ) { + + super( parameters.defaultTexture || _defaultRT.texture, _defaultUV ); + + /** + * A reference to the internal reflector base node which holds the actual implementation. + * + * @private + * @type {ReflectorBaseNode} + * @default ReflectorBaseNode + */ + this._reflectorBaseNode = parameters.reflector || new ReflectorBaseNode( this, parameters ); + + /** + * A reference to the internal depth node. + * + * @private + * @type {?Node} + * @default null + */ + this._depthNode = null; + + this.setUpdateMatrix( false ); + + } + + /** + * A reference to the internal reflector node. + * + * @type {ReflectorBaseNode} + */ + get reflector() { + + return this._reflectorBaseNode; + + } + + /** + * A reference to 3D object the reflector is linked to. + * + * @type {Object3D} + */ + get target() { + + return this._reflectorBaseNode.target; + + } + + /** + * Returns a node representing the mirror's depth. That can be used + * to implement more advanced reflection effects like distance attenuation. + * + * @return {Node} The depth node. + */ + getDepthNode() { + + if ( this._depthNode === null ) { + + if ( this._reflectorBaseNode.depth !== true ) { + + throw new Error( 'THREE.ReflectorNode: Depth node can only be requested when the reflector is created with { depth: true }. ' ); + + } + + this._depthNode = nodeObject( new ReflectorNode( { + defaultTexture: _defaultRT.depthTexture, + reflector: this._reflectorBaseNode + } ) ); + + } + + return this._depthNode; + + } + + setup( builder ) { + + // ignore if used in post-processing + if ( ! builder.object.isQuadMesh ) this._reflectorBaseNode.build( builder ); + + return super.setup( builder ); + + } + + clone() { + + const newNode = new this.constructor( this.reflectorNode ); + newNode.uvNode = this.uvNode; + newNode.levelNode = this.levelNode; + newNode.biasNode = this.biasNode; + newNode.sampler = this.sampler; + newNode.depthNode = this.depthNode; + newNode.compareNode = this.compareNode; + newNode.gradNode = this.gradNode; + newNode._reflectorBaseNode = this._reflectorBaseNode; + + return newNode; + + } + + /** + * Frees internal resources. Should be called when the node is no longer in use. + */ + dispose() { + + super.dispose(); + + this._reflectorBaseNode.dispose(); + + } + +} + +/** + * Holds the actual implementation of the reflector. + * + * TODO: Explain why `ReflectorBaseNode`. Originally the entire logic was implemented + * in `ReflectorNode`, see #29619. + * + * @private + * @augments Node + */ +class ReflectorBaseNode extends Node { + + static get type() { + + return 'ReflectorBaseNode'; + + } + + /** + * Constructs a new reflector base node. + * + * @param {TextureNode} textureNode - Represents the rendered reflections as a texture node. + * @param {Object} [parameters={}] - An object holding configuration parameters. + * @param {Object3D} [parameters.target=new Object3D()] - The 3D object the reflector is linked to. + * @param {number} [parameters.resolution=1] - The resolution scale. + * @param {boolean} [parameters.generateMipmaps=false] - Whether mipmaps should be generated or not. + * @param {boolean} [parameters.bounces=true] - Whether reflectors can render other reflector nodes or not. + * @param {boolean} [parameters.depth=false] - Whether depth data should be generated or not. + */ + constructor( textureNode, parameters = {} ) { + + super(); + + const { + target = new Object3D(), + resolution = 1, + generateMipmaps = false, + bounces = true, + depth = false + } = parameters; + + /** + * Represents the rendered reflections as a texture node. + * + * @type {TextureNode} + */ + this.textureNode = textureNode; + + /** + * The 3D object the reflector is linked to. + * + * @type {Object3D} + * @default {new Object3D()} + */ + this.target = target; + + /** + * The resolution scale. + * + * @type {number} + * @default {1} + */ + this.resolution = resolution; + + /** + * Whether mipmaps should be generated or not. + * + * @type {boolean} + * @default {false} + */ + this.generateMipmaps = generateMipmaps; + + /** + * Whether reflectors can render other reflector nodes or not. + * + * @type {boolean} + * @default {true} + */ + this.bounces = bounces; + + /** + * Whether depth data should be generated or not. + * + * @type {boolean} + * @default {false} + */ + this.depth = depth; + + /** + * The `updateBeforeType` is set to `NodeUpdateType.RENDER` when {@link ReflectorBaseNode#bounces} + * is `true`. Otherwise it's `NodeUpdateType.FRAME`. + * + * @type {string} + * @default 'render' + */ + this.updateBeforeType = bounces ? NodeUpdateType.RENDER : NodeUpdateType.FRAME; + + /** + * Weak map for managing virtual cameras. + * + * @type {WeakMap} + */ + this.virtualCameras = new WeakMap(); + + /** + * Weak map for managing render targets. + * + * @type {Map} + */ + this.renderTargets = new Map(); + + /** + * Force render even if reflector is facing away from camera. + * + * @type {boolean} + * @default {false} + */ + this.forceUpdate = false; + + /** + * Whether the reflector has been rendered or not. + * + * When the reflector is facing away from the camera, + * this flag is set to `false` and the texture will be empty(black). + * + * @type {boolean} + * @default {false} + */ + this.hasOutput = false; + + } + + /** + * Updates the resolution of the internal render target. + * + * @private + * @param {RenderTarget} renderTarget - The render target to resize. + * @param {Renderer} renderer - The renderer that is used to determine the new size. + */ + _updateResolution( renderTarget, renderer ) { + + const resolution = this.resolution; + + renderer.getDrawingBufferSize( _size$2 ); + + renderTarget.setSize( Math.round( _size$2.width * resolution ), Math.round( _size$2.height * resolution ) ); + + } + + setup( builder ) { + + this._updateResolution( _defaultRT, builder.renderer ); + + return super.setup( builder ); + + } + + /** + * Frees internal resources. Should be called when the node is no longer in use. + */ + dispose() { + + super.dispose(); + + for ( const renderTarget of this.renderTargets.values() ) { + + renderTarget.dispose(); + + } + + } + + /** + * Returns a virtual camera for the given camera. The virtual camera is used to + * render the scene from the reflector's view so correct reflections can be produced. + * + * @param {Camera} camera - The scene's camera. + * @return {Camera} The corresponding virtual camera. + */ + getVirtualCamera( camera ) { + + let virtualCamera = this.virtualCameras.get( camera ); + + if ( virtualCamera === undefined ) { + + virtualCamera = camera.clone(); + + this.virtualCameras.set( camera, virtualCamera ); + + } + + return virtualCamera; + + } + + /** + * Returns a render target for the given camera. The reflections are rendered + * into this render target. + * + * @param {Camera} camera - The scene's camera. + * @return {RenderTarget} The render target. + */ + getRenderTarget( camera ) { + + let renderTarget = this.renderTargets.get( camera ); + + if ( renderTarget === undefined ) { + + renderTarget = new RenderTarget( 0, 0, { type: HalfFloatType } ); + + if ( this.generateMipmaps === true ) { + + renderTarget.texture.minFilter = LinearMipMapLinearFilter; + renderTarget.texture.generateMipmaps = true; + + } + + if ( this.depth === true ) { + + renderTarget.depthTexture = new DepthTexture(); + + } + + this.renderTargets.set( camera, renderTarget ); + + } + + return renderTarget; + + } + + updateBefore( frame ) { + + if ( this.bounces === false && _inReflector ) return false; + + _inReflector = true; + + const { scene, camera, renderer, material } = frame; + const { target } = this; + + const virtualCamera = this.getVirtualCamera( camera ); + const renderTarget = this.getRenderTarget( virtualCamera ); + + renderer.getDrawingBufferSize( _size$2 ); + + this._updateResolution( renderTarget, renderer ); + + // + + _reflectorWorldPosition.setFromMatrixPosition( target.matrixWorld ); + _cameraWorldPosition.setFromMatrixPosition( camera.matrixWorld ); + + _rotationMatrix.extractRotation( target.matrixWorld ); + + _normal.set( 0, 0, 1 ); + _normal.applyMatrix4( _rotationMatrix ); + + _view.subVectors( _reflectorWorldPosition, _cameraWorldPosition ); + + // Avoid rendering when reflector is facing away unless forcing an update + const isFacingAway = _view.dot( _normal ) > 0; + + let needsClear = false; + + if ( isFacingAway === true && this.forceUpdate === false ) { + + if ( this.hasOutput === false ) { + + _inReflector = false; + + return; + + } + + needsClear = true; + + } + + _view.reflect( _normal ).negate(); + _view.add( _reflectorWorldPosition ); + + _rotationMatrix.extractRotation( camera.matrixWorld ); + + _lookAtPosition.set( 0, 0, -1 ); + _lookAtPosition.applyMatrix4( _rotationMatrix ); + _lookAtPosition.add( _cameraWorldPosition ); + + _target.subVectors( _reflectorWorldPosition, _lookAtPosition ); + _target.reflect( _normal ).negate(); + _target.add( _reflectorWorldPosition ); + + // + + virtualCamera.coordinateSystem = camera.coordinateSystem; + virtualCamera.position.copy( _view ); + virtualCamera.up.set( 0, 1, 0 ); + virtualCamera.up.applyMatrix4( _rotationMatrix ); + virtualCamera.up.reflect( _normal ); + virtualCamera.lookAt( _target ); + + virtualCamera.near = camera.near; + virtualCamera.far = camera.far; + + virtualCamera.updateMatrixWorld(); + virtualCamera.projectionMatrix.copy( camera.projectionMatrix ); + + // Now update projection matrix with new clip plane, implementing code from: http://www.terathon.com/code/oblique.html + // Paper explaining this technique: http://www.terathon.com/lengyel/Lengyel-Oblique.pdf + _reflectorPlane.setFromNormalAndCoplanarPoint( _normal, _reflectorWorldPosition ); + _reflectorPlane.applyMatrix4( virtualCamera.matrixWorldInverse ); + + clipPlane.set( _reflectorPlane.normal.x, _reflectorPlane.normal.y, _reflectorPlane.normal.z, _reflectorPlane.constant ); + + const projectionMatrix = virtualCamera.projectionMatrix; + + _q.x = ( Math.sign( clipPlane.x ) + projectionMatrix.elements[ 8 ] ) / projectionMatrix.elements[ 0 ]; + _q.y = ( Math.sign( clipPlane.y ) + projectionMatrix.elements[ 9 ] ) / projectionMatrix.elements[ 5 ]; + _q.z = -1; + _q.w = ( 1.0 + projectionMatrix.elements[ 10 ] ) / projectionMatrix.elements[ 14 ]; + + // Calculate the scaled plane vector + clipPlane.multiplyScalar( 1.0 / clipPlane.dot( _q ) ); + + const clipBias = 0; + + // Replacing the third row of the projection matrix + projectionMatrix.elements[ 2 ] = clipPlane.x; + projectionMatrix.elements[ 6 ] = clipPlane.y; + projectionMatrix.elements[ 10 ] = ( renderer.coordinateSystem === WebGPUCoordinateSystem ) ? ( clipPlane.z - clipBias ) : ( clipPlane.z + 1.0 - clipBias ); + projectionMatrix.elements[ 14 ] = clipPlane.w; + + // + + this.textureNode.value = renderTarget.texture; + + if ( this.depth === true ) { + + this.textureNode.getDepthNode().value = renderTarget.depthTexture; + + } + + material.visible = false; + + const currentRenderTarget = renderer.getRenderTarget(); + const currentMRT = renderer.getMRT(); + const currentAutoClear = renderer.autoClear; + + renderer.setMRT( null ); + renderer.setRenderTarget( renderTarget ); + renderer.autoClear = true; + + if ( needsClear ) { + + renderer.clear(); + + this.hasOutput = false; + + } else { + + renderer.render( scene, virtualCamera ); + + this.hasOutput = true; + + } + + renderer.setMRT( currentMRT ); + renderer.setRenderTarget( currentRenderTarget ); + renderer.autoClear = currentAutoClear; + + material.visible = true; + + _inReflector = false; + + this.forceUpdate = false; + + } + +} + +/** + * TSL function for creating a reflector node. + * + * @tsl + * @function + * @param {Object} [parameters={}] - An object holding configuration parameters. + * @param {Object3D} [parameters.target=new Object3D()] - The 3D object the reflector is linked to. + * @param {number} [parameters.resolution=1] - The resolution scale. + * @param {boolean} [parameters.generateMipmaps=false] - Whether mipmaps should be generated or not. + * @param {boolean} [parameters.bounces=true] - Whether reflectors can render other reflector nodes or not. + * @param {boolean} [parameters.depth=false] - Whether depth data should be generated or not. + * @param {TextureNode} [parameters.defaultTexture] - The default texture node. + * @param {ReflectorBaseNode} [parameters.reflector] - The reflector base node. + * @returns {ReflectorNode} + */ +const reflector = ( parameters ) => nodeObject( new ReflectorNode( parameters ) ); + +const _camera = /*@__PURE__*/ new OrthographicCamera( -1, 1, 1, -1, 0, 1 ); + +/** + * The purpose of this special geometry is to fill the entire viewport with a single triangle. + * + * Reference: {@link https://github.com/mrdoob/three.js/pull/21358} + * + * @private + * @augments BufferGeometry + */ +class QuadGeometry extends BufferGeometry { + + /** + * Constructs a new quad geometry. + * + * @param {boolean} [flipY=false] - Whether the uv coordinates should be flipped along the vertical axis or not. + */ + constructor( flipY = false ) { + + super(); + + const uv = flipY === false ? [ 0, -1, 0, 1, 2, 1 ] : [ 0, 2, 0, 0, 2, 0 ]; + + this.setAttribute( 'position', new Float32BufferAttribute( [ -1, 3, 0, -1, -1, 0, 3, -1, 0 ], 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uv, 2 ) ); + + } + +} + +const _geometry = /*@__PURE__*/ new QuadGeometry(); + + +/** + * This module is a helper for passes which need to render a full + * screen effect which is quite common in context of post processing. + * + * The intended usage is to reuse a single quad mesh for rendering + * subsequent passes by just reassigning the `material` reference. + * + * Note: This module can only be used with `WebGPURenderer`. + * + * @augments Mesh + */ +class QuadMesh extends Mesh { + + /** + * Constructs a new quad mesh. + * + * @param {?Material} [material=null] - The material to render the quad mesh with. + */ + constructor( material = null ) { + + super( _geometry, material ); + + /** + * The camera to render the quad mesh with. + * + * @type {OrthographicCamera} + * @readonly + */ + this.camera = _camera; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isQuadMesh = true; + + } + + /** + * Async version of `render()`. + * + * @async + * @param {Renderer} renderer - The renderer. + * @return {Promise} A Promise that resolves when the render has been finished. + */ + async renderAsync( renderer ) { + + return renderer.renderAsync( this, _camera ); + + } + + /** + * Renders the quad mesh + * + * @param {Renderer} renderer - The renderer. + */ + render( renderer ) { + + renderer.render( this, _camera ); + + } + +} + +const _size$1 = /*@__PURE__*/ new Vector2(); + +/** + * `RTTNode` takes another node and uses it with a `QuadMesh` to render into a texture (RTT). + * This module is especially relevant in context of post processing where certain nodes require + * texture input for their effects. With the helper function `convertToTexture()` which is based + * on this module, the node system can automatically ensure texture input if required. + * + * @augments TextureNode + */ +class RTTNode extends TextureNode { + + static get type() { + + return 'RTTNode'; + + } + + /** + * Constructs a new RTT node. + * + * @param {Node} node - The node to render a texture with. + * @param {?number} [width=null] - The width of the internal render target. If not width is applied, the render target is automatically resized. + * @param {?number} [height=null] - The height of the internal render target. + * @param {Object} [options={type:HalfFloatType}] - The options for the internal render target. + */ + constructor( node, width = null, height = null, options = { type: HalfFloatType } ) { + + const renderTarget = new RenderTarget( width, height, options ); + + super( renderTarget.texture, uv$1() ); + + /** + * The node to render a texture with. + * + * @type {Node} + */ + this.node = node; + + /** + * The width of the internal render target. + * If not width is applied, the render target is automatically resized. + * + * @type {?number} + * @default null + */ + this.width = width; + + /** + * The height of the internal render target. + * + * @type {?number} + * @default null + */ + this.height = height; + + /** + * The pixel ratio + * + * @type {number} + * @default 1 + */ + this.pixelRatio = 1; + + /** + * The render target + * + * @type {RenderTarget} + */ + this.renderTarget = renderTarget; + + /** + * Whether the texture requires an update or not. + * + * @type {boolean} + * @default true + */ + this.textureNeedsUpdate = true; + + /** + * Whether the texture should automatically be updated or not. + * + * @type {boolean} + * @default true + */ + this.autoUpdate = true; + + /** + * The node which is used with the quad mesh for RTT. + * + * @private + * @type {Node} + * @default null + */ + this._rttNode = null; + + /** + * The internal quad mesh for RTT. + * + * @private + * @type {QuadMesh} + */ + this._quadMesh = new QuadMesh( new NodeMaterial() ); + + /** + * The `updateBeforeType` is set to `NodeUpdateType.RENDER` since the node updates + * the texture once per render in its {@link RTTNode#updateBefore} method. + * + * @type {string} + * @default 'render' + */ + this.updateBeforeType = NodeUpdateType.RENDER; + + } + + /** + * Whether the internal render target should automatically be resized or not. + * + * @type {boolean} + * @readonly + * @default true + */ + get autoResize() { + + return this.width === null; + + } + + setup( builder ) { + + this._rttNode = this.node.context( builder.getSharedContext() ); + this._quadMesh.material.name = 'RTT'; + this._quadMesh.material.needsUpdate = true; + + return super.setup( builder ); + + } + + /** + * Sets the size of the internal render target + * + * @param {number} width - The width to set. + * @param {number} height - The width to set. + */ + setSize( width, height ) { + + this.width = width; + this.height = height; + + const effectiveWidth = width * this.pixelRatio; + const effectiveHeight = height * this.pixelRatio; + + this.renderTarget.setSize( effectiveWidth, effectiveHeight ); + + this.textureNeedsUpdate = true; + + } + + /** + * Sets the pixel ratio. This will also resize the render target. + * + * @param {number} pixelRatio - The pixel ratio to set. + */ + setPixelRatio( pixelRatio ) { + + this.pixelRatio = pixelRatio; + + this.setSize( this.width, this.height ); + + } + + updateBefore( { renderer } ) { + + if ( this.textureNeedsUpdate === false && this.autoUpdate === false ) return; + + this.textureNeedsUpdate = false; + + // + + if ( this.autoResize === true ) { + + const pixelRatio = renderer.getPixelRatio(); + const size = renderer.getSize( _size$1 ); + + const effectiveWidth = size.width * pixelRatio; + const effectiveHeight = size.height * pixelRatio; + + if ( effectiveWidth !== this.renderTarget.width || effectiveHeight !== this.renderTarget.height ) { + + this.renderTarget.setSize( effectiveWidth, effectiveHeight ); + + this.textureNeedsUpdate = true; + + } + + } + + // + + this._quadMesh.material.fragmentNode = this._rttNode; + + // + + const currentRenderTarget = renderer.getRenderTarget(); + + renderer.setRenderTarget( this.renderTarget ); + + this._quadMesh.render( renderer ); + + renderer.setRenderTarget( currentRenderTarget ); + + } + + clone() { + + const newNode = new TextureNode( this.value, this.uvNode, this.levelNode ); + newNode.sampler = this.sampler; + newNode.referenceNode = this; + + return newNode; + + } + +} + +/** + * TSL function for creating a RTT node. + * + * @tsl + * @function + * @param {Node} node - The node to render a texture with. + * @param {?number} [width=null] - The width of the internal render target. If not width is applied, the render target is automatically resized. + * @param {?number} [height=null] - The height of the internal render target. + * @param {Object} [options={type:HalfFloatType}] - The options for the internal render target. + * @returns {RTTNode} + */ +const rtt = ( node, ...params ) => nodeObject( new RTTNode( nodeObject( node ), ...params ) ); + +/** + * TSL function for converting nodes to textures nodes. + * + * @tsl + * @function + * @param {Node} node - The node to render a texture with. + * @param {?number} [width=null] - The width of the internal render target. If not width is applied, the render target is automatically resized. + * @param {?number} [height=null] - The height of the internal render target. + * @param {Object} [options={type:HalfFloatType}] - The options for the internal render target. + * @returns {RTTNode} + */ +const convertToTexture = ( node, ...params ) => { + + if ( node.isTextureNode ) return node; + if ( node.isPassNode ) return node.getTextureNode(); + + return rtt( node, ...params ); + +}; + +/** + * Computes a position in view space based on a fragment's screen position expressed as uv coordinates, the fragments + * depth value and the camera's inverse projection matrix. + * + * @tsl + * @function + * @param {Node} screenPosition - The fragment's screen position expressed as uv coordinates. + * @param {Node} depth - The fragment's depth value. + * @param {Node} projectionMatrixInverse - The camera's inverse projection matrix. + * @return {Node} The fragments position in view space. + */ +const getViewPosition = /*@__PURE__*/ Fn( ( [ screenPosition, depth, projectionMatrixInverse ], builder ) => { + + let clipSpacePosition; + + if ( builder.renderer.coordinateSystem === WebGPUCoordinateSystem ) { + + screenPosition = vec2( screenPosition.x, screenPosition.y.oneMinus() ).mul( 2.0 ).sub( 1.0 ); + clipSpacePosition = vec4( vec3( screenPosition, depth ), 1.0 ); + + } else { + + clipSpacePosition = vec4( vec3( screenPosition.x, screenPosition.y.oneMinus(), depth ).mul( 2.0 ).sub( 1.0 ), 1.0 ); + + } + + const viewSpacePosition = vec4( projectionMatrixInverse.mul( clipSpacePosition ) ); + + return viewSpacePosition.xyz.div( viewSpacePosition.w ); + +} ); + +/** + * Computes a screen position expressed as uv coordinates based on a fragment's position in view space + * and the camera's projection matrix + * + * @tsl + * @function + * @param {Node} viewPosition - The fragments position in view space. + * @param {Node} projectionMatrix - The camera's projection matrix. + * @return {Node} The fragment's screen position expressed as uv coordinates. + */ +const getScreenPosition = /*@__PURE__*/ Fn( ( [ viewPosition, projectionMatrix ] ) => { + + const sampleClipPos = projectionMatrix.mul( vec4( viewPosition, 1.0 ) ); + const sampleUv = sampleClipPos.xy.div( sampleClipPos.w ).mul( 0.5 ).add( 0.5 ).toVar(); + return vec2( sampleUv.x, sampleUv.y.oneMinus() ); + +} ); + +/** + * Computes a normal vector based on depth data. Can be used as a fallback when no normal render + * target is available or if flat surface normals are required. + * + * @tsl + * @function + * @param {Node} uv - The texture coordinate. + * @param {DepthTexture} depthTexture - The depth texture. + * @param {Node} projectionMatrixInverse - The camera's inverse projection matrix. + * @return {Node} The computed normal vector. + */ +const getNormalFromDepth = /*@__PURE__*/ Fn( ( [ uv, depthTexture, projectionMatrixInverse ] ) => { + + const size = textureSize( textureLoad( depthTexture ) ); + const p = ivec2( uv.mul( size ) ).toVar(); + + const c0 = textureLoad( depthTexture, p ).toVar(); + + const l2 = textureLoad( depthTexture, p.sub( ivec2( 2, 0 ) ) ).toVar(); + const l1 = textureLoad( depthTexture, p.sub( ivec2( 1, 0 ) ) ).toVar(); + const r1 = textureLoad( depthTexture, p.add( ivec2( 1, 0 ) ) ).toVar(); + const r2 = textureLoad( depthTexture, p.add( ivec2( 2, 0 ) ) ).toVar(); + const b2 = textureLoad( depthTexture, p.add( ivec2( 0, 2 ) ) ).toVar(); + const b1 = textureLoad( depthTexture, p.add( ivec2( 0, 1 ) ) ).toVar(); + const t1 = textureLoad( depthTexture, p.sub( ivec2( 0, 1 ) ) ).toVar(); + const t2 = textureLoad( depthTexture, p.sub( ivec2( 0, 2 ) ) ).toVar(); + + const dl = abs( sub( float( 2 ).mul( l1 ).sub( l2 ), c0 ) ).toVar(); + const dr = abs( sub( float( 2 ).mul( r1 ).sub( r2 ), c0 ) ).toVar(); + const db = abs( sub( float( 2 ).mul( b1 ).sub( b2 ), c0 ) ).toVar(); + const dt = abs( sub( float( 2 ).mul( t1 ).sub( t2 ), c0 ) ).toVar(); + + const ce = getViewPosition( uv, c0, projectionMatrixInverse ).toVar(); + + const dpdx = dl.lessThan( dr ).select( ce.sub( getViewPosition( uv.sub( vec2( float( 1 ).div( size.x ), 0 ) ), l1, projectionMatrixInverse ) ), ce.negate().add( getViewPosition( uv.add( vec2( float( 1 ).div( size.x ), 0 ) ), r1, projectionMatrixInverse ) ) ); + const dpdy = db.lessThan( dt ).select( ce.sub( getViewPosition( uv.add( vec2( 0, float( 1 ).div( size.y ) ) ), b1, projectionMatrixInverse ) ), ce.negate().add( getViewPosition( uv.sub( vec2( 0, float( 1 ).div( size.y ) ) ), t1, projectionMatrixInverse ) ) ); + + return normalize( cross( dpdx, dpdy ) ); + +} ); + +/** + * Class representing a node that samples a value using a provided callback function. + * + * @extends Node + */ +class SampleNode extends Node { + + /** + * Returns the type of the node. + * + * @type {string} + * @readonly + * @static + */ + static get type() { + + return 'SampleNode'; + + } + + /** + * Creates an instance of SampleNode. + * + * @param {Function} callback - The function to be called when sampling. Should accept a UV node and return a value. + */ + constructor( callback ) { + + super(); + + this.callback = callback; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSampleNode = true; + + } + + /** + * Sets up the node by sampling with the default UV accessor. + * + * @returns {Node} The result of the callback function when called with the UV node. + */ + setup() { + + return this.sample( uv$1() ); + + } + + /** + * Calls the callback function with the provided UV node. + * + * @param {Node} uv - The UV node or value to be passed to the callback. + * @returns {Node} The result of the callback function. + */ + sample( uv ) { + + return this.callback( uv ); + + } + +} + +/** + * Helper function to create a SampleNode wrapped as a node object. + * + * @function + * @param {Function} callback - The function to be called when sampling. Should accept a UV node and return a value. + * @returns {SampleNode} The created SampleNode instance wrapped as a node object. + */ +const sample = ( callback ) => nodeObject( new SampleNode( callback ) ); + +/** + * This special type of instanced buffer attribute is intended for compute shaders. + * In earlier three.js versions it was only possible to update attribute data + * on the CPU via JavaScript and then upload the data to the GPU. With the + * new material system and renderer it is now possible to use compute shaders + * to compute the data for an attribute more efficiently on the GPU. + * + * The idea is to create an instance of this class and provide it as an input + * to {@link StorageBufferNode}. + * + * Note: This type of buffer attribute can only be used with `WebGPURenderer`. + * + * @augments InstancedBufferAttribute + */ +class StorageInstancedBufferAttribute extends InstancedBufferAttribute { + + /** + * Constructs a new storage instanced buffer attribute. + * + * @param {number|TypedArray} count - The item count. It is also valid to pass a typed array as an argument. + * The subsequent parameters are then obsolete. + * @param {number} itemSize - The item size. + * @param {TypedArray.constructor} [typeClass=Float32Array] - A typed array constructor. + */ + constructor( count, itemSize, typeClass = Float32Array ) { + + const array = ArrayBuffer.isView( count ) ? count : new typeClass( count * itemSize ); + + super( array, itemSize ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isStorageInstancedBufferAttribute = true; + + } + +} + +/** + * This special type of buffer attribute is intended for compute shaders. + * In earlier three.js versions it was only possible to update attribute data + * on the CPU via JavaScript and then upload the data to the GPU. With the + * new material system and renderer it is now possible to use compute shaders + * to compute the data for an attribute more efficiently on the GPU. + * + * The idea is to create an instance of this class and provide it as an input + * to {@link StorageBufferNode}. + * + * Note: This type of buffer attribute can only be used with `WebGPURenderer`. + * + * @augments BufferAttribute + */ +class StorageBufferAttribute extends BufferAttribute { + + /** + * Constructs a new storage buffer attribute. + * + * @param {number|TypedArray} count - The item count. It is also valid to pass a typed array as an argument. + * The subsequent parameters are then obsolete. + * @param {number} itemSize - The item size. + * @param {TypedArray.constructor} [typeClass=Float32Array] - A typed array constructor. + */ + constructor( count, itemSize, typeClass = Float32Array ) { + + const array = ArrayBuffer.isView( count ) ? count : new typeClass( count * itemSize ); + + super( array, itemSize ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isStorageBufferAttribute = true; + + } + +} + +/** + * TSL function for creating a storage buffer node with a configured `StorageBufferAttribute`. + * + * @tsl + * @function + * @param {number|TypedArray} count - The data count. It is also valid to pass a typed array as an argument. + * @param {string|Struct} [type='float'] - The data type. + * @returns {StorageBufferNode} + */ +const attributeArray = ( count, type = 'float' ) => { + + let itemSize, typedArray; + + if ( type.isStruct === true ) { + + itemSize = type.layout.getLength(); + typedArray = getTypedArrayFromType( 'float' ); + + } else { + + itemSize = getLengthFromType( type ); + typedArray = getTypedArrayFromType( type ); + + } + + const buffer = new StorageBufferAttribute( count, itemSize, typedArray ); + const node = storage( buffer, type, count ); + + return node; + +}; + +/** + * TSL function for creating a storage buffer node with a configured `StorageInstancedBufferAttribute`. + * + * @tsl + * @function + * @param {number|TypedArray} count - The data count. It is also valid to pass a typed array as an argument. + * @param {string|Struct} [type='float'] - The data type. + * @returns {StorageBufferNode} + */ +const instancedArray = ( count, type = 'float' ) => { + + let itemSize, typedArray; + + if ( type.isStruct === true ) { + + itemSize = type.layout.getLength(); + typedArray = getTypedArrayFromType( 'float' ); + + } else { + + itemSize = getLengthFromType( type ); + typedArray = getTypedArrayFromType( type ); + + } + + const buffer = new StorageInstancedBufferAttribute( count, itemSize, typedArray ); + const node = storage( buffer, type, count ); + + return node; + +}; + +/** + * A node for representing the uv coordinates of points. + * + * Can only be used with a WebGL backend. In WebGPU, point + * primitives always have the size of one pixel and can thus + * can't be used as sprite-like objects that display textures. + * + * @augments Node + */ +class PointUVNode extends Node { + + static get type() { + + return 'PointUVNode'; + + } + + /** + * Constructs a new point uv node. + */ + constructor() { + + super( 'vec2' ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isPointUVNode = true; + + } + + generate( /*builder*/ ) { + + return 'vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y )'; + + } + +} + +/** + * TSL object that represents the uv coordinates of points. + * + * @tsl + * @type {PointUVNode} + */ +const pointUV = /*@__PURE__*/ nodeImmutable( PointUVNode ); + +const _e1 = /*@__PURE__*/ new Euler(); +const _m1 = /*@__PURE__*/ new Matrix4(); + +/** + * This module allows access to a collection of scene properties. The following predefined TSL objects + * are available for easier use: + * + * - `backgroundBlurriness`: A node that represents the scene's background blurriness. + * - `backgroundIntensity`: A node that represents the scene's background intensity. + * - `backgroundRotation`: A node that represents the scene's background rotation. + * + * @augments Node + */ +class SceneNode extends Node { + + static get type() { + + return 'SceneNode'; + + } + + /** + * Constructs a new scene node. + * + * @param {('backgroundBlurriness'|'backgroundIntensity'|'backgroundRotation')} scope - The scope defines the type of scene property that is accessed. + * @param {?Scene} [scene=null] - A reference to the scene. + */ + constructor( scope = SceneNode.BACKGROUND_BLURRINESS, scene = null ) { + + super(); + + /** + * The scope defines the type of scene property that is accessed. + * + * @type {('backgroundBlurriness'|'backgroundIntensity'|'backgroundRotation')} + */ + this.scope = scope; + + /** + * A reference to the scene that is going to be accessed. + * + * @type {?Scene} + * @default null + */ + this.scene = scene; + + } + + /** + * Depending on the scope, the method returns a different type of node that represents + * the respective scene property. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {Node} The output node. + */ + setup( builder ) { + + const scope = this.scope; + const scene = this.scene !== null ? this.scene : builder.scene; + + let output; + + if ( scope === SceneNode.BACKGROUND_BLURRINESS ) { + + output = reference( 'backgroundBlurriness', 'float', scene ); + + } else if ( scope === SceneNode.BACKGROUND_INTENSITY ) { + + output = reference( 'backgroundIntensity', 'float', scene ); + + } else if ( scope === SceneNode.BACKGROUND_ROTATION ) { + + output = uniform( 'mat4' ).label( 'backgroundRotation' ).setGroup( renderGroup ).onRenderUpdate( () => { + + const background = scene.background; + + if ( background !== null && background.isTexture && background.mapping !== UVMapping ) { + + _e1.copy( scene.backgroundRotation ); + + // accommodate left-handed frame + _e1.x *= -1; _e1.y *= -1; _e1.z *= -1; + + _m1.makeRotationFromEuler( _e1 ); + + } else { + + _m1.identity(); + + } + + return _m1; + + } ); + + } else { + + console.error( 'THREE.SceneNode: Unknown scope:', scope ); + + } + + return output; + + } + +} + +SceneNode.BACKGROUND_BLURRINESS = 'backgroundBlurriness'; +SceneNode.BACKGROUND_INTENSITY = 'backgroundIntensity'; +SceneNode.BACKGROUND_ROTATION = 'backgroundRotation'; + +/** + * TSL object that represents the scene's background blurriness. + * + * @tsl + * @type {SceneNode} + */ +const backgroundBlurriness = /*@__PURE__*/ nodeImmutable( SceneNode, SceneNode.BACKGROUND_BLURRINESS ); + +/** + * TSL object that represents the scene's background intensity. + * + * @tsl + * @type {SceneNode} + */ +const backgroundIntensity = /*@__PURE__*/ nodeImmutable( SceneNode, SceneNode.BACKGROUND_INTENSITY ); + +/** + * TSL object that represents the scene's background rotation. + * + * @tsl + * @type {SceneNode} + */ +const backgroundRotation = /*@__PURE__*/ nodeImmutable( SceneNode, SceneNode.BACKGROUND_ROTATION ); + +/** + * This special version of a texture node can be used to + * write data into a storage texture with a compute shader. + * + * ```js + * const storageTexture = new THREE.StorageTexture( width, height ); + * + * const computeTexture = Fn( ( { storageTexture } ) => { + * + * const posX = instanceIndex.mod( width ); + * const posY = instanceIndex.div( width ); + * const indexUV = uvec2( posX, posY ); + * + * // generate RGB values + * + * const r = 1; + * const g = 1; + * const b = 1; + * + * textureStore( storageTexture, indexUV, vec4( r, g, b, 1 ) ).toWriteOnly(); + * + * } ); + * + * const computeNode = computeTexture( { storageTexture } ).compute( width * height ); + * renderer.computeAsync( computeNode ); + * ``` + * + * This node can only be used with a WebGPU backend. + * + * @augments TextureNode + */ +class StorageTextureNode extends TextureNode { + + static get type() { + + return 'StorageTextureNode'; + + } + + /** + * Constructs a new storage texture node. + * + * @param {StorageTexture} value - The storage texture. + * @param {Node} uvNode - The uv node. + * @param {?Node} [storeNode=null] - The value node that should be stored in the texture. + */ + constructor( value, uvNode, storeNode = null ) { + + super( value, uvNode ); + + /** + * The value node that should be stored in the texture. + * + * @type {?Node} + * @default null + */ + this.storeNode = storeNode; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isStorageTextureNode = true; + + /** + * The access type of the texture node. + * + * @type {string} + * @default 'writeOnly' + */ + this.access = NodeAccess.WRITE_ONLY; + + } + + /** + * Overwrites the default implementation to return a fixed value `'storageTexture'`. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The input type. + */ + getInputType( /*builder*/ ) { + + return 'storageTexture'; + + } + + setup( builder ) { + + super.setup( builder ); + + const properties = builder.getNodeProperties( this ); + properties.storeNode = this.storeNode; + + return properties; + + } + + /** + * Defines the node access. + * + * @param {string} value - The node access. + * @return {StorageTextureNode} A reference to this node. + */ + setAccess( value ) { + + this.access = value; + return this; + + } + + /** + * Generates the code snippet of the storage node. If no `storeNode` + * is defined, the texture node is generated as normal texture. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {string} output - The current output. + * @return {string} The generated code snippet. + */ + generate( builder, output ) { + + let snippet; + + if ( this.storeNode !== null ) { + + snippet = this.generateStore( builder ); + + } else { + + snippet = super.generate( builder, output ); + + } + + return snippet; + + } + + /** + * Convenience method for configuring a read/write node access. + * + * @return {StorageTextureNode} A reference to this node. + */ + toReadWrite() { + + return this.setAccess( NodeAccess.READ_WRITE ); + + } + + /** + * Convenience method for configuring a read-only node access. + * + * @return {StorageTextureNode} A reference to this node. + */ + toReadOnly() { + + return this.setAccess( NodeAccess.READ_ONLY ); + + } + + /** + * Convenience method for configuring a write-only node access. + * + * @return {StorageTextureNode} A reference to this node. + */ + toWriteOnly() { + + return this.setAccess( NodeAccess.WRITE_ONLY ); + + } + + /** + * Generates the code snippet of the storage texture node. + * + * @param {NodeBuilder} builder - The current node builder. + */ + generateStore( builder ) { + + const properties = builder.getNodeProperties( this ); + + const { uvNode, storeNode, depthNode } = properties; + + const textureProperty = super.generate( builder, 'property' ); + const uvSnippet = uvNode.build( builder, 'uvec2' ); + const storeSnippet = storeNode.build( builder, 'vec4' ); + const depthSnippet = depthNode ? depthNode.build( builder, 'int' ) : null; + + const snippet = builder.generateTextureStore( builder, textureProperty, uvSnippet, depthSnippet, storeSnippet ); + + builder.addLineFlowCode( snippet, this ); + + } + + clone() { + + const newNode = super.clone(); + newNode.storeNode = this.storeNode; + return newNode; + + } + +} + +/** + * TSL function for creating a storage texture node. + * + * @tsl + * @function + * @param {StorageTexture} value - The storage texture. + * @param {?Node} uvNode - The uv node. + * @param {?Node} [storeNode=null] - The value node that should be stored in the texture. + * @returns {StorageTextureNode} + */ +const storageTexture = /*@__PURE__*/ nodeProxy( StorageTextureNode ).setParameterLength( 1, 3 ); + + +/** + * TODO: Explain difference to `storageTexture()`. + * + * @tsl + * @function + * @param {StorageTexture} value - The storage texture. + * @param {Node} uvNode - The uv node. + * @param {?Node} [storeNode=null] - The value node that should be stored in the texture. + * @returns {StorageTextureNode} + */ +const textureStore = ( value, uvNode, storeNode ) => { + + const node = storageTexture( value, uvNode, storeNode ); + + if ( storeNode !== null ) node.toStack(); + + return node; + +}; + +const normal = Fn( ( { texture, uv } ) => { + + const epsilon = 0.0001; + + const ret = vec3().toVar(); + + If( uv.x.lessThan( epsilon ), () => { + + ret.assign( vec3( 1, 0, 0 ) ); + + } ).ElseIf( uv.y.lessThan( epsilon ), () => { + + ret.assign( vec3( 0, 1, 0 ) ); + + } ).ElseIf( uv.z.lessThan( epsilon ), () => { + + ret.assign( vec3( 0, 0, 1 ) ); + + } ).ElseIf( uv.x.greaterThan( 1 - epsilon ), () => { + + ret.assign( vec3( -1, 0, 0 ) ); + + } ).ElseIf( uv.y.greaterThan( 1 - epsilon ), () => { + + ret.assign( vec3( 0, -1, 0 ) ); + + } ).ElseIf( uv.z.greaterThan( 1 - epsilon ), () => { + + ret.assign( vec3( 0, 0, -1 ) ); + + } ).Else( () => { + + const step = 0.01; + + const x = texture.sample( uv.add( vec3( - step, 0.0, 0.0 ) ) ).r.sub( texture.sample( uv.add( vec3( step, 0.0, 0.0 ) ) ).r ); + const y = texture.sample( uv.add( vec3( 0.0, - step, 0.0 ) ) ).r.sub( texture.sample( uv.add( vec3( 0.0, step, 0.0 ) ) ).r ); + const z = texture.sample( uv.add( vec3( 0.0, 0.0, - step ) ) ).r.sub( texture.sample( uv.add( vec3( 0.0, 0.0, step ) ) ).r ); + + ret.assign( vec3( x, y, z ) ); + + } ); + + return ret.normalize(); + +} ); + +/** + * This type of uniform node represents a 3D texture. + * + * @augments TextureNode + */ +class Texture3DNode extends TextureNode { + + static get type() { + + return 'Texture3DNode'; + + } + + /** + * Constructs a new 3D texture node. + * + * @param {Data3DTexture} value - The 3D texture. + * @param {?Node} [uvNode=null] - The uv node. + * @param {?Node} [levelNode=null] - The level node. + */ + constructor( value, uvNode = null, levelNode = null ) { + + super( value, uvNode, levelNode ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isTexture3DNode = true; + + } + + /** + * Overwrites the default implementation to return a fixed value `'texture3D'`. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The input type. + */ + getInputType( /*builder*/ ) { + + return 'texture3D'; + + } + + /** + * Returns a default uv node which is in context of 3D textures a three-dimensional + * uv node. + * + * @return {Node} The default uv node. + */ + getDefaultUV() { + + return vec3( 0.5, 0.5, 0.5 ); + + } + + /** + * Overwritten with an empty implementation since the `updateMatrix` flag is ignored + * for 3D textures. The uv transformation matrix is not applied to 3D textures. + * + * @param {boolean} value - The update toggle. + */ + setUpdateMatrix( /*value*/ ) { } // Ignore .updateMatrix for 3d TextureNode + + /** + * Overwrites the default implementation to return the unmodified uv node. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {Node} uvNode - The uv node to setup. + * @return {Node} The unmodified uv node. + */ + setupUV( builder, uvNode ) { + + const texture = this.value; + + if ( builder.isFlipY() && ( texture.isRenderTargetTexture === true || texture.isFramebufferTexture === true ) ) { + + if ( this.sampler ) { + + uvNode = uvNode.flipY(); + + } else { + + uvNode = uvNode.setY( int( textureSize( this, this.levelNode ).y ).sub( uvNode.y ).sub( 1 ) ); + + } + + } + + return uvNode; + + } + + /** + * Generates the uv code snippet. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {Node} uvNode - The uv node to generate code for. + * @return {string} The generated code snippet. + */ + generateUV( builder, uvNode ) { + + return uvNode.build( builder, 'vec3' ); + + } + + /** + * TODO. + * + * @param {Node} uvNode - The uv node . + * @return {Node} TODO. + */ + normal( uvNode ) { + + return normal( { texture: this, uv: uvNode } ); + + } + +} + +/** + * TSL function for creating a 3D texture node. + * + * @tsl + * @function + * @param {Data3DTexture} value - The 3D texture. + * @param {?Node} [uvNode=null] - The uv node. + * @param {?Node} [levelNode=null] - The level node. + * @returns {Texture3DNode} + */ +const texture3D = /*@__PURE__*/ nodeProxy( Texture3DNode ).setParameterLength( 1, 3 ); + +/** + * A special type of reference node that allows to link values in + * `userData` fields to node objects. + * ```js + * sprite.userData.rotation = 1; // stores individual rotation per sprite + * + * const material = new THREE.SpriteNodeMaterial(); + * material.rotationNode = userData( 'rotation', 'float' ); + * ``` + * Since `UserDataNode` is extended from {@link ReferenceNode}, the node value + * will automatically be updated when the `rotation` user data field changes. + * + * @augments ReferenceNode + */ +class UserDataNode extends ReferenceNode { + + static get type() { + + return 'UserDataNode'; + + } + + /** + * Constructs a new user data node. + * + * @param {string} property - The property name that should be referenced by the node. + * @param {string} inputType - The node data type of the reference. + * @param {?Object} [userData=null] - A reference to the `userData` object. If not provided, the `userData` property of the 3D object that uses the node material is evaluated. + */ + constructor( property, inputType, userData = null ) { + + super( property, inputType, userData ); + + /** + * A reference to the `userData` object. If not provided, the `userData` + * property of the 3D object that uses the node material is evaluated. + * + * @type {?Object} + * @default null + */ + this.userData = userData; + + } + + /** + * Overwritten to make sure {@link ReferenceNode#reference} points to the correct + * `userData` field. + * + * @param {(NodeFrame|NodeBuilder)} state - The current state to evaluate. + * @return {Object} A reference to the `userData` field. + */ + updateReference( state ) { + + this.reference = this.userData !== null ? this.userData : state.object.userData; + + return this.reference; + + } + +} + +/** + * TSL function for creating a user data node. + * + * @tsl + * @function + * @param {string} name - The property name that should be referenced by the node. + * @param {string} inputType - The node data type of the reference. + * @param {?Object} userData - A reference to the `userData` object. If not provided, the `userData` property of the 3D object that uses the node material is evaluated. + * @returns {UserDataNode} + */ +const userData = ( name, inputType, userData ) => nodeObject( new UserDataNode( name, inputType, userData ) ); + +const _objectData = new WeakMap(); + +/** + * A node for representing motion or velocity vectors. Foundation + * for advanced post processing effects like motion blur or TRAA. + * + * The node keeps track of the model, view and projection matrices + * of the previous frame and uses them to compute offsets in NDC space. + * These offsets represent the final velocity. + * + * @augments TempNode + */ +class VelocityNode extends TempNode { + + static get type() { + + return 'VelocityNode'; + + } + + /** + * Constructs a new vertex color node. + */ + constructor() { + + super( 'vec2' ); + + /** + * The current projection matrix. + * + * @type {?Matrix4} + * @default null + */ + this.projectionMatrix = null; + + /** + * Overwritten since velocity nodes are updated per object. + * + * @type {string} + * @default 'object' + */ + this.updateType = NodeUpdateType.OBJECT; + + /** + * Overwritten since velocity nodes save data after the update. + * + * @type {string} + * @default 'object' + */ + this.updateAfterType = NodeUpdateType.OBJECT; + + /** + * Uniform node representing the previous model matrix in world space. + * + * @type {UniformNode} + * @default null + */ + this.previousModelWorldMatrix = uniform( new Matrix4() ); + + /** + * Uniform node representing the previous projection matrix. + * + * @type {UniformNode} + * @default null + */ + this.previousProjectionMatrix = uniform( new Matrix4() ).setGroup( renderGroup ); + + /** + * Uniform node representing the previous view matrix. + * + * @type {UniformNode} + * @default null + */ + this.previousCameraViewMatrix = uniform( new Matrix4() ); + + } + + /** + * Sets the given projection matrix. + * + * @param {Matrix4} projectionMatrix - The projection matrix to set. + */ + setProjectionMatrix( projectionMatrix ) { + + this.projectionMatrix = projectionMatrix; + + } + + /** + * Updates velocity specific uniforms. + * + * @param {NodeFrame} frame - A reference to the current node frame. + */ + update( { frameId, camera, object } ) { + + const previousModelMatrix = getPreviousMatrix( object ); + + this.previousModelWorldMatrix.value.copy( previousModelMatrix ); + + // + + const cameraData = getData( camera ); + + if ( cameraData.frameId !== frameId ) { + + cameraData.frameId = frameId; + + if ( cameraData.previousProjectionMatrix === undefined ) { + + cameraData.previousProjectionMatrix = new Matrix4(); + cameraData.previousCameraViewMatrix = new Matrix4(); + + cameraData.currentProjectionMatrix = new Matrix4(); + cameraData.currentCameraViewMatrix = new Matrix4(); + + cameraData.previousProjectionMatrix.copy( this.projectionMatrix || camera.projectionMatrix ); + cameraData.previousCameraViewMatrix.copy( camera.matrixWorldInverse ); + + } else { + + cameraData.previousProjectionMatrix.copy( cameraData.currentProjectionMatrix ); + cameraData.previousCameraViewMatrix.copy( cameraData.currentCameraViewMatrix ); + + } + + cameraData.currentProjectionMatrix.copy( this.projectionMatrix || camera.projectionMatrix ); + cameraData.currentCameraViewMatrix.copy( camera.matrixWorldInverse ); + + this.previousProjectionMatrix.value.copy( cameraData.previousProjectionMatrix ); + this.previousCameraViewMatrix.value.copy( cameraData.previousCameraViewMatrix ); + + } + + } + + /** + * Overwritten to updated velocity specific uniforms. + * + * @param {NodeFrame} frame - A reference to the current node frame. + */ + updateAfter( { object } ) { + + getPreviousMatrix( object ).copy( object.matrixWorld ); + + } + + /** + * Implements the velocity computation based on the previous and current vertex data. + * + * @param {NodeBuilder} builder - A reference to the current node builder. + * @return {Node} The motion vector. + */ + setup( /*builder*/ ) { + + const projectionMatrix = ( this.projectionMatrix === null ) ? cameraProjectionMatrix : uniform( this.projectionMatrix ); + + const previousModelViewMatrix = this.previousCameraViewMatrix.mul( this.previousModelWorldMatrix ); + + const clipPositionCurrent = projectionMatrix.mul( modelViewMatrix ).mul( positionLocal ); + const clipPositionPrevious = this.previousProjectionMatrix.mul( previousModelViewMatrix ).mul( positionPrevious ); + + const ndcPositionCurrent = clipPositionCurrent.xy.div( clipPositionCurrent.w ); + const ndcPositionPrevious = clipPositionPrevious.xy.div( clipPositionPrevious.w ); + + const velocity = sub( ndcPositionCurrent, ndcPositionPrevious ); + + return velocity; + + } + +} + +function getData( object ) { + + let objectData = _objectData.get( object ); + + if ( objectData === undefined ) { + + objectData = {}; + _objectData.set( object, objectData ); + + } + + return objectData; + +} + +function getPreviousMatrix( object, index = 0 ) { + + const objectData = getData( object ); + + let matrix = objectData[ index ]; + + if ( matrix === undefined ) { + + objectData[ index ] = matrix = new Matrix4(); + objectData[ index ].copy( object.matrixWorld ); + + } + + return matrix; + +} + +/** + * TSL object that represents the velocity of a render pass. + * + * @tsl + * @type {VelocityNode} + */ +const velocity = /*@__PURE__*/ nodeImmutable( VelocityNode ); + +/** + * Computes a grayscale value for the given RGB color value. + * + * @tsl + * @function + * @param {Node} color - The color value to compute the grayscale for. + * @return {Node} The grayscale color. + */ +const grayscale = /*@__PURE__*/ Fn( ( [ color ] ) => { + + return luminance( color.rgb ); + +} ); + +/** + * Super-saturates or desaturates the given RGB color. + * + * @tsl + * @function + * @param {Node} color - The input color. + * @param {Node} [adjustment=1] - Specifies the amount of the conversion. A value under `1` desaturates the color, a value over `1` super-saturates it. + * @return {Node} The saturated color. + */ +const saturation = /*@__PURE__*/ Fn( ( [ color, adjustment = float( 1 ) ] ) => { + + return adjustment.mix( luminance( color.rgb ), color.rgb ); + +} ); + +/** + * Selectively enhance the intensity of less saturated RGB colors. Can result + * in a more natural and visually appealing image with enhanced color depth + * compared to {@link ColorAdjustment#saturation}. + * + * @tsl + * @function + * @param {Node} color - The input color. + * @param {Node} [adjustment=1] - Controls the intensity of the vibrance effect. + * @return {Node} The updated color. + */ +const vibrance = /*@__PURE__*/ Fn( ( [ color, adjustment = float( 1 ) ] ) => { + + const average = add( color.r, color.g, color.b ).div( 3.0 ); + + const mx = color.r.max( color.g.max( color.b ) ); + const amt = mx.sub( average ).mul( adjustment ).mul( -3 ); + + return mix( color.rgb, mx, amt ); + +} ); + +/** + * Updates the hue component of the given RGB color while preserving its luminance and saturation. + * + * @tsl + * @function + * @param {Node} color - The input color. + * @param {Node} [adjustment=1] - Defines the degree of hue rotation in radians. A positive value rotates the hue clockwise, while a negative value rotates it counterclockwise. + * @return {Node} The updated color. + */ +const hue = /*@__PURE__*/ Fn( ( [ color, adjustment = float( 1 ) ] ) => { + + const k = vec3( 0.57735, 0.57735, 0.57735 ); + + const cosAngle = adjustment.cos(); + + return vec3( color.rgb.mul( cosAngle ).add( k.cross( color.rgb ).mul( adjustment.sin() ).add( k.mul( dot( k, color.rgb ).mul( cosAngle.oneMinus() ) ) ) ) ); + +} ); + +/** + * Computes the luminance for the given RGB color value. + * + * @tsl + * @function + * @param {Node} color - The color value to compute the luminance for. + * @param {?Node} luminanceCoefficients - The luminance coefficients. By default predefined values of the current working color space are used. + * @return {Node} The luminance. + */ +const luminance = ( + color, + luminanceCoefficients = vec3( ColorManagement.getLuminanceCoefficients( new Vector3() ) ) +) => dot( color, luminanceCoefficients ); + +/** + * Color Decision List (CDL) v1.2 + * + * Compact representation of color grading information, defined by slope, offset, power, and + * saturation. The CDL should be typically be given input in a log space (such as LogC, ACEScc, + * or AgX Log), and will return output in the same space. Output may require clamping >=0. + * + * @tsl + * @function + * @param {Node} color Input (-Infinity < input < +Infinity) + * @param {Node} slope Slope (0 ≤ slope < +Infinity) + * @param {Node} offset Offset (-Infinity < offset < +Infinity; typically -1 < offset < 1) + * @param {Node} power Power (0 < power < +Infinity) + * @param {Node} saturation Saturation (0 ≤ saturation < +Infinity; typically 0 ≤ saturation < 4) + * @param {Node} luminanceCoefficients Luminance coefficients for saturation term, typically Rec. 709 + * @return {Node} Output, -Infinity < output < +Infinity + * + * References: + * - ASC CDL v1.2 + * - {@link https://blender.stackexchange.com/a/55239/43930} + * - {@link https://docs.acescentral.com/specifications/acescc/} + */ +const cdl = /*@__PURE__*/ Fn( ( [ + color, + slope = vec3( 1 ), + offset = vec3( 0 ), + power = vec3( 1 ), + saturation = float( 1 ), + // ASC CDL v1.2 explicitly requires Rec. 709 luminance coefficients. + luminanceCoefficients = vec3( ColorManagement.getLuminanceCoefficients( new Vector3(), LinearSRGBColorSpace ) ) +] ) => { + + // NOTE: The ASC CDL v1.2 defines a [0, 1] clamp on the slope+offset term, and another on the + // saturation term. Per the ACEScc specification and Filament, limits may be omitted to support + // values outside [0, 1], requiring a workaround for negative values in the power expression. + + const luma = color.rgb.dot( vec3( luminanceCoefficients ) ); + + const v = max$1( color.rgb.mul( slope ).add( offset ), 0.0 ).toVar(); + const pv = v.pow( power ).toVar(); + + If( v.r.greaterThan( 0.0 ), () => { v.r.assign( pv.r ); } ); // eslint-disable-line + If( v.g.greaterThan( 0.0 ), () => { v.g.assign( pv.g ); } ); // eslint-disable-line + If( v.b.greaterThan( 0.0 ), () => { v.b.assign( pv.b ); } ); // eslint-disable-line + + v.assign( luma.add( v.sub( luma ).mul( saturation ) ) ); + + return vec4( v.rgb, color.a ); + +} ); + +/** + * Represents a posterize effect which reduces the number of colors + * in an image, resulting in a more blocky and stylized appearance. + * + * @augments TempNode + */ +class PosterizeNode extends TempNode { + + static get type() { + + return 'PosterizeNode'; + + } + + /** + * Constructs a new posterize node. + * + * @param {Node} sourceNode - The input color. + * @param {Node} stepsNode - Controls the intensity of the posterization effect. A lower number results in a more blocky appearance. + */ + constructor( sourceNode, stepsNode ) { + + super(); + + /** + * The input color. + * + * @type {Node} + */ + this.sourceNode = sourceNode; + + /** + * Controls the intensity of the posterization effect. A lower number results in a more blocky appearance. + * + * @type {Node} + */ + this.stepsNode = stepsNode; + + } + + setup() { + + const { sourceNode, stepsNode } = this; + + return sourceNode.mul( stepsNode ).floor().div( stepsNode ); + + } + +} + +/** + * TSL function for creating a posterize node. + * + * @tsl + * @function + * @param {Node} sourceNode - The input color. + * @param {Node} stepsNode - Controls the intensity of the posterization effect. A lower number results in a more blocky appearance. + * @returns {PosterizeNode} + */ +const posterize = /*@__PURE__*/ nodeProxy( PosterizeNode ).setParameterLength( 2 ); + +const _size = /*@__PURE__*/ new Vector2(); + +/** + * Represents the texture of a pass node. + * + * @augments TextureNode + */ +class PassTextureNode extends TextureNode { + + static get type() { + + return 'PassTextureNode'; + + } + + /** + * Constructs a new pass texture node. + * + * @param {PassNode} passNode - The pass node. + * @param {Texture} texture - The output texture. + */ + constructor( passNode, texture ) { + + super( texture ); + + /** + * A reference to the pass node. + * + * @type {PassNode} + */ + this.passNode = passNode; + + this.setUpdateMatrix( false ); + + } + + setup( builder ) { + + if ( builder.object.isQuadMesh ) this.passNode.build( builder ); + + return super.setup( builder ); + + } + + clone() { + + return new this.constructor( this.passNode, this.value ); + + } + +} + +/** + * An extension of `PassTextureNode` which allows to manage more than one + * internal texture. Relevant for the `getPreviousTexture()` related API. + * + * @augments PassTextureNode + */ +class PassMultipleTextureNode extends PassTextureNode { + + static get type() { + + return 'PassMultipleTextureNode'; + + } + + /** + * Constructs a new pass texture node. + * + * @param {PassNode} passNode - The pass node. + * @param {string} textureName - The output texture name. + * @param {boolean} [previousTexture=false] - Whether previous frame data should be used or not. + */ + constructor( passNode, textureName, previousTexture = false ) { + + // null is passed to the super call since this class does not + // use an external texture for rendering pass data into. Instead + // the texture is managed by the pass node itself + + super( passNode, null ); + + /** + * The output texture name. + * + * @type {string} + */ + this.textureName = textureName; + + /** + * Whether previous frame data should be used or not. + * + * @type {boolean} + */ + this.previousTexture = previousTexture; + + } + + /** + * Updates the texture reference of this node. + */ + updateTexture() { + + this.value = this.previousTexture ? this.passNode.getPreviousTexture( this.textureName ) : this.passNode.getTexture( this.textureName ); + + } + + setup( builder ) { + + this.updateTexture(); + + return super.setup( builder ); + + } + + clone() { + + const newNode = new this.constructor( this.passNode, this.textureName, this.previousTexture ); + newNode.uvNode = this.uvNode; + newNode.levelNode = this.levelNode; + newNode.biasNode = this.biasNode; + newNode.sampler = this.sampler; + newNode.depthNode = this.depthNode; + newNode.compareNode = this.compareNode; + newNode.gradNode = this.gradNode; + + return newNode; + + } + +} + +/** + * Represents a render pass (sometimes called beauty pass) in context of post processing. + * This pass produces a render for the given scene and camera and can provide multiple outputs + * via MRT for further processing. + * + * ```js + * const postProcessing = new PostProcessing( renderer ); + * + * const scenePass = pass( scene, camera ); + * + * postProcessing.outputNode = scenePass; + * ``` + * + * @augments TempNode + */ +class PassNode extends TempNode { + + static get type() { + + return 'PassNode'; + + } + + /** + * Constructs a new pass node. + * + * @param {('color'|'depth')} scope - The scope of the pass. The scope determines whether the node outputs color or depth. + * @param {Scene} scene - A reference to the scene. + * @param {Camera} camera - A reference to the camera. + * @param {Object} options - Options for the internal render target. + */ + constructor( scope, scene, camera, options = {} ) { + + super( 'vec4' ); + + /** + * The scope of the pass. The scope determines whether the node outputs color or depth. + * + * @type {('color'|'depth')} + */ + this.scope = scope; + + /** + * A reference to the scene. + * + * @type {Scene} + */ + this.scene = scene; + + /** + * A reference to the camera. + * + * @type {Camera} + */ + this.camera = camera; + + /** + * Options for the internal render target. + * + * @type {Object} + */ + this.options = options; + + /** + * The pass's pixel ratio. Will be kept automatically kept in sync with the renderer's pixel ratio. + * + * @private + * @type {number} + * @default 1 + */ + this._pixelRatio = 1; + + /** + * The pass's pixel width. Will be kept automatically kept in sync with the renderer's width. + * @private + * @type {number} + * @default 1 + */ + this._width = 1; + + /** + * The pass's pixel height. Will be kept automatically kept in sync with the renderer's height. + * @private + * @type {number} + * @default 1 + */ + this._height = 1; + + const depthTexture = new DepthTexture(); + depthTexture.isRenderTargetTexture = true; + //depthTexture.type = FloatType; + depthTexture.name = 'depth'; + + const renderTarget = new RenderTarget( this._width * this._pixelRatio, this._height * this._pixelRatio, { type: HalfFloatType, ...options, } ); + renderTarget.texture.name = 'output'; + renderTarget.depthTexture = depthTexture; + + /** + * The pass's render target. + * + * @type {RenderTarget} + */ + this.renderTarget = renderTarget; + + /** + * A dictionary holding the internal result textures. + * + * @private + * @type {Object} + */ + this._textures = { + output: renderTarget.texture, + depth: depthTexture + }; + + /** + * A dictionary holding the internal texture nodes. + * + * @private + * @type {Object} + */ + this._textureNodes = {}; + + /** + * A dictionary holding the internal depth nodes. + * + * @private + * @type {Object} + */ + this._linearDepthNodes = {}; + + /** + * A dictionary holding the internal viewZ nodes. + * + * @private + * @type {Object} + */ + this._viewZNodes = {}; + + /** + * A dictionary holding the texture data of the previous frame. + * Used for computing velocity/motion vectors. + * + * @private + * @type {Object} + */ + this._previousTextures = {}; + + /** + * A dictionary holding the texture nodes of the previous frame. + * Used for computing velocity/motion vectors. + * + * @private + * @type {Object} + */ + this._previousTextureNodes = {}; + + /** + * The `near` property of the camera as a uniform. + * + * @private + * @type {UniformNode} + */ + this._cameraNear = uniform( 0 ); + + /** + * The `far` property of the camera as a uniform. + * + * @private + * @type {UniformNode} + */ + this._cameraFar = uniform( 0 ); + + /** + * A MRT node configuring the MRT settings. + * + * @private + * @type {?MRTNode} + * @default null + */ + this._mrt = null; + + this._layers = null; + + this._resolution = 1; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isPassNode = true; + + /** + * The `updateBeforeType` is set to `NodeUpdateType.FRAME` since the node renders the + * scene once per frame in its {@link PassNode#updateBefore} method. + * + * @type {string} + * @default 'frame' + */ + this.updateBeforeType = NodeUpdateType.FRAME; + + /** + * This flag is used for global cache. + * + * @type {boolean} + * @default true + */ + this.global = true; + + } + + /** + * Sets the resolution for the pass. + * The resolution is a factor that is multiplied with the renderer's width and height. + * + * @param {number} resolution - The resolution to set. A value of `1` means full resolution. + * @return {PassNode} A reference to this pass. + */ + setResolution( resolution ) { + + this._resolution = resolution; + + return this; + + } + + /** + * Gets the current resolution of the pass. + * + * @return {number} The current resolution. A value of `1` means full resolution. + * @default 1 + */ + getResolution() { + + return this._resolution; + + } + + setLayers( layers ) { + + this._layers = layers; + + return this; + + } + + getLayers() { + + return this._layers; + + } + + /** + * Sets the given MRT node to setup MRT for this pass. + * + * @param {MRTNode} mrt - The MRT object. + * @return {PassNode} A reference to this pass. + */ + setMRT( mrt ) { + + this._mrt = mrt; + + return this; + + } + + /** + * Returns the current MRT node. + * + * @return {MRTNode} The current MRT node. + */ + getMRT() { + + return this._mrt; + + } + + /** + * Returns the texture for the given output name. + * + * @param {string} name - The output name to get the texture for. + * @return {Texture} The texture. + */ + getTexture( name ) { + + let texture = this._textures[ name ]; + + if ( texture === undefined ) { + + const refTexture = this.renderTarget.texture; + + texture = refTexture.clone(); + texture.name = name; + + this._textures[ name ] = texture; + + this.renderTarget.textures.push( texture ); + + } + + return texture; + + } + + /** + * Returns the texture holding the data of the previous frame for the given output name. + * + * @param {string} name - The output name to get the texture for. + * @return {Texture} The texture holding the data of the previous frame. + */ + getPreviousTexture( name ) { + + let texture = this._previousTextures[ name ]; + + if ( texture === undefined ) { + + texture = this.getTexture( name ).clone(); + + this._previousTextures[ name ] = texture; + + } + + return texture; + + } + + /** + * Switches current and previous textures for the given output name. + * + * @param {string} name - The output name. + */ + toggleTexture( name ) { + + const prevTexture = this._previousTextures[ name ]; + + if ( prevTexture !== undefined ) { + + const texture = this._textures[ name ]; + + const index = this.renderTarget.textures.indexOf( texture ); + this.renderTarget.textures[ index ] = prevTexture; + + this._textures[ name ] = prevTexture; + this._previousTextures[ name ] = texture; + + this._textureNodes[ name ].updateTexture(); + this._previousTextureNodes[ name ].updateTexture(); + + } + + } + + /** + * Returns the texture node for the given output name. + * + * @param {string} [name='output'] - The output name to get the texture node for. + * @return {TextureNode} The texture node. + */ + getTextureNode( name = 'output' ) { + + let textureNode = this._textureNodes[ name ]; + + if ( textureNode === undefined ) { + + textureNode = nodeObject( new PassMultipleTextureNode( this, name ) ); + textureNode.updateTexture(); + this._textureNodes[ name ] = textureNode; + + } + + return textureNode; + + } + + /** + * Returns the previous texture node for the given output name. + * + * @param {string} [name='output'] - The output name to get the previous texture node for. + * @return {TextureNode} The previous texture node. + */ + getPreviousTextureNode( name = 'output' ) { + + let textureNode = this._previousTextureNodes[ name ]; + + if ( textureNode === undefined ) { + + if ( this._textureNodes[ name ] === undefined ) this.getTextureNode( name ); + + textureNode = nodeObject( new PassMultipleTextureNode( this, name, true ) ); + textureNode.updateTexture(); + this._previousTextureNodes[ name ] = textureNode; + + } + + return textureNode; + + } + + /** + * Returns a viewZ node of this pass. + * + * @param {string} [name='depth'] - The output name to get the viewZ node for. In most cases the default `'depth'` can be used however the parameter exists for custom depth outputs. + * @return {Node} The viewZ node. + */ + getViewZNode( name = 'depth' ) { + + let viewZNode = this._viewZNodes[ name ]; + + if ( viewZNode === undefined ) { + + const cameraNear = this._cameraNear; + const cameraFar = this._cameraFar; + + this._viewZNodes[ name ] = viewZNode = perspectiveDepthToViewZ( this.getTextureNode( name ), cameraNear, cameraFar ); + + } + + return viewZNode; + + } + + /** + * Returns a linear depth node of this pass. + * + * @param {string} [name='depth'] - The output name to get the linear depth node for. In most cases the default `'depth'` can be used however the parameter exists for custom depth outputs. + * @return {Node} The linear depth node. + */ + getLinearDepthNode( name = 'depth' ) { + + let linearDepthNode = this._linearDepthNodes[ name ]; + + if ( linearDepthNode === undefined ) { + + const cameraNear = this._cameraNear; + const cameraFar = this._cameraFar; + const viewZNode = this.getViewZNode( name ); + + // TODO: just if ( builder.camera.isPerspectiveCamera ) + + this._linearDepthNodes[ name ] = linearDepthNode = viewZToOrthographicDepth( viewZNode, cameraNear, cameraFar ); + + } + + return linearDepthNode; + + } + + setup( { renderer } ) { + + this.renderTarget.samples = this.options.samples === undefined ? renderer.samples : this.options.samples; + + this.renderTarget.texture.type = renderer.getColorBufferType(); + + return this.scope === PassNode.COLOR ? this.getTextureNode() : this.getLinearDepthNode(); + + } + + updateBefore( frame ) { + + const { renderer } = frame; + const { scene } = this; + + let camera; + let pixelRatio; + + const outputRenderTarget = renderer.getOutputRenderTarget(); + + if ( outputRenderTarget && outputRenderTarget.isXRRenderTarget === true ) { + + pixelRatio = 1; + camera = renderer.xr.getCamera(); + + renderer.xr.updateCamera( camera ); + + _size.set( outputRenderTarget.width, outputRenderTarget.height ); + + } else { + + camera = this.camera; + pixelRatio = renderer.getPixelRatio(); + + renderer.getSize( _size ); + + } + + this._pixelRatio = pixelRatio; + + this.setSize( _size.width, _size.height ); + + const currentRenderTarget = renderer.getRenderTarget(); + const currentMRT = renderer.getMRT(); + const currentMask = camera.layers.mask; + + this._cameraNear.value = camera.near; + this._cameraFar.value = camera.far; + + if ( this._layers !== null ) { + + camera.layers.mask = this._layers.mask; + + } + + for ( const name in this._previousTextures ) { + + this.toggleTexture( name ); + + } + + renderer.setRenderTarget( this.renderTarget ); + renderer.setMRT( this._mrt ); + + renderer.render( scene, camera ); + + renderer.setRenderTarget( currentRenderTarget ); + renderer.setMRT( currentMRT ); + + camera.layers.mask = currentMask; + + } + + /** + * Sets the size of the pass's render target. Honors the pixel ratio. + * + * @param {number} width - The width to set. + * @param {number} height - The height to set. + */ + setSize( width, height ) { + + this._width = width; + this._height = height; + + const effectiveWidth = this._width * this._pixelRatio * this._resolution; + const effectiveHeight = this._height * this._pixelRatio * this._resolution; + + this.renderTarget.setSize( effectiveWidth, effectiveHeight ); + + } + + /** + * Sets the pixel ratio the pass's render target and updates the size. + * + * @param {number} pixelRatio - The pixel ratio to set. + */ + setPixelRatio( pixelRatio ) { + + this._pixelRatio = pixelRatio; + + this.setSize( this._width, this._height ); + + } + + /** + * Frees internal resources. Should be called when the node is no longer in use. + */ + dispose() { + + this.renderTarget.dispose(); + + } + + +} + +/** + * @static + * @type {'color'} + * @default 'color' + */ +PassNode.COLOR = 'color'; + +/** + * @static + * @type {'depth'} + * @default 'depth' + */ +PassNode.DEPTH = 'depth'; + +/** + * TSL function for creating a pass node. + * + * @tsl + * @function + * @param {Scene} scene - A reference to the scene. + * @param {Camera} camera - A reference to the camera. + * @param {Object} options - Options for the internal render target. + * @returns {PassNode} + */ +const pass = ( scene, camera, options ) => nodeObject( new PassNode( PassNode.COLOR, scene, camera, options ) ); + +/** + * TSL function for creating a pass texture node. + * + * @tsl + * @function + * @param {PassNode} pass - The pass node. + * @param {Texture} texture - The output texture. + * @returns {PassTextureNode} + */ +const passTexture = ( pass, texture ) => nodeObject( new PassTextureNode( pass, texture ) ); + +/** + * TSL function for creating a depth pass node. + * + * @tsl + * @function + * @param {Scene} scene - A reference to the scene. + * @param {Camera} camera - A reference to the camera. + * @param {Object} options - Options for the internal render target. + * @returns {PassNode} + */ +const depthPass = ( scene, camera, options ) => nodeObject( new PassNode( PassNode.DEPTH, scene, camera, options ) ); + +/** + * Represents a render pass for producing a toon outline effect on compatible objects. + * Only 3D objects with materials of type `MeshToonMaterial` and `MeshToonNodeMaterial` + * will receive the outline. + * + * ```js + * const postProcessing = new PostProcessing( renderer ); + * + * const scenePass = toonOutlinePass( scene, camera ); + * + * postProcessing.outputNode = scenePass; + * ``` + * @augments PassNode + */ +class ToonOutlinePassNode extends PassNode { + + static get type() { + + return 'ToonOutlinePassNode'; + + } + + /** + * Constructs a new outline pass node. + * + * @param {Scene} scene - A reference to the scene. + * @param {Camera} camera - A reference to the camera. + * @param {Node} colorNode - Defines the outline's color. + * @param {Node} thicknessNode - Defines the outline's thickness. + * @param {Node} alphaNode - Defines the outline's alpha. + */ + constructor( scene, camera, colorNode, thicknessNode, alphaNode ) { + + super( PassNode.COLOR, scene, camera ); + + /** + * Defines the outline's color. + * + * @type {Node} + */ + this.colorNode = colorNode; + + /** + * Defines the outline's thickness. + * + * @type {Node} + */ + this.thicknessNode = thicknessNode; + + /** + * Defines the outline's alpha. + * + * @type {Node} + */ + this.alphaNode = alphaNode; + + /** + * An internal material cache. + * + * @private + * @type {WeakMap} + */ + this._materialCache = new WeakMap(); + + } + + updateBefore( frame ) { + + const { renderer } = frame; + + const currentRenderObjectFunction = renderer.getRenderObjectFunction(); + + renderer.setRenderObjectFunction( ( object, scene, camera, geometry, material, group, lightsNode, clippingContext ) => { + + // only render outline for supported materials + + if ( material.isMeshToonMaterial || material.isMeshToonNodeMaterial ) { + + if ( material.wireframe === false ) { + + const outlineMaterial = this._getOutlineMaterial( material ); + renderer.renderObject( object, scene, camera, geometry, outlineMaterial, group, lightsNode, clippingContext ); + + } + + } + + // default + + renderer.renderObject( object, scene, camera, geometry, material, group, lightsNode, clippingContext ); + + } ); + + super.updateBefore( frame ); + + renderer.setRenderObjectFunction( currentRenderObjectFunction ); + + } + + /** + * Creates the material used for outline rendering. + * + * @private + * @return {NodeMaterial} The outline material. + */ + _createMaterial() { + + const material = new NodeMaterial(); + material.isMeshToonOutlineMaterial = true; + material.name = 'Toon_Outline'; + material.side = BackSide; + + // vertex node + + const outlineNormal = normalLocal.negate(); + const mvp = cameraProjectionMatrix.mul( modelViewMatrix ); + + const ratio = float( 1.0 ); // TODO: support outline thickness ratio for each vertex + const pos = mvp.mul( vec4( positionLocal, 1.0 ) ); + const pos2 = mvp.mul( vec4( positionLocal.add( outlineNormal ), 1.0 ) ); + const norm = normalize( pos.sub( pos2 ) ); // NOTE: subtract pos2 from pos because BackSide objectNormal is negative + + material.vertexNode = pos.add( norm.mul( this.thicknessNode ).mul( pos.w ).mul( ratio ) ); + + // color node + + material.colorNode = vec4( this.colorNode, this.alphaNode ); + + return material; + + } + + /** + * For the given toon material, this method returns a corresponding + * outline material. + * + * @private + * @param {(MeshToonMaterial|MeshToonNodeMaterial)} originalMaterial - The toon material. + * @return {NodeMaterial} The outline material. + */ + _getOutlineMaterial( originalMaterial ) { + + let outlineMaterial = this._materialCache.get( originalMaterial ); + + if ( outlineMaterial === undefined ) { + + outlineMaterial = this._createMaterial(); + + this._materialCache.set( originalMaterial, outlineMaterial ); + + } + + return outlineMaterial; + + } + +} + +/** + * TSL function for creating a toon outline pass node. + * + * @tsl + * @function + * @param {Scene} scene - A reference to the scene. + * @param {Camera} camera - A reference to the camera. + * @param {Color} color - Defines the outline's color. + * @param {number} [thickness=0.003] - Defines the outline's thickness. + * @param {number} [alpha=1] - Defines the outline's alpha. + * @returns {ToonOutlinePassNode} + */ +const toonOutlinePass = ( scene, camera, color = new Color( 0, 0, 0 ), thickness = 0.003, alpha = 1 ) => nodeObject( new ToonOutlinePassNode( scene, camera, nodeObject( color ), nodeObject( thickness ), nodeObject( alpha ) ) ); + +/** + * Linear tone mapping, exposure only. + * + * @tsl + * @function + * @param {Node} color - The color that should be tone mapped. + * @param {Node} exposure - The exposure. + * @return {Node} The tone mapped color. + */ +const linearToneMapping = /*@__PURE__*/ Fn( ( [ color, exposure ] ) => { + + return color.mul( exposure ).clamp(); + +} ).setLayout( { + name: 'linearToneMapping', + type: 'vec3', + inputs: [ + { name: 'color', type: 'vec3' }, + { name: 'exposure', type: 'float' } + ] +} ); + +/** + * Reinhard tone mapping. + * + * Reference: {@link https://www.cs.utah.edu/docs/techreports/2002/pdf/UUCS-02-001.pdf} + * + * @tsl + * @function + * @param {Node} color - The color that should be tone mapped. + * @param {Node} exposure - The exposure. + * @return {Node} The tone mapped color. + */ +const reinhardToneMapping = /*@__PURE__*/ Fn( ( [ color, exposure ] ) => { + + color = color.mul( exposure ); + + return color.div( color.add( 1.0 ) ).clamp(); + +} ).setLayout( { + name: 'reinhardToneMapping', + type: 'vec3', + inputs: [ + { name: 'color', type: 'vec3' }, + { name: 'exposure', type: 'float' } + ] +} ); + +/** + * Cineon tone mapping. + * + * Reference: {@link http://filmicworlds.com/blog/filmic-tonemapping-operators/} + * + * @tsl + * @function + * @param {Node} color - The color that should be tone mapped. + * @param {Node} exposure - The exposure. + * @return {Node} The tone mapped color. + */ +const cineonToneMapping = /*@__PURE__*/ Fn( ( [ color, exposure ] ) => { + + // filmic operator by Jim Hejl and Richard Burgess-Dawson + color = color.mul( exposure ); + color = color.sub( 0.004 ).max( 0.0 ); + + const a = color.mul( color.mul( 6.2 ).add( 0.5 ) ); + const b = color.mul( color.mul( 6.2 ).add( 1.7 ) ).add( 0.06 ); + + return a.div( b ).pow( 2.2 ); + +} ).setLayout( { + name: 'cineonToneMapping', + type: 'vec3', + inputs: [ + { name: 'color', type: 'vec3' }, + { name: 'exposure', type: 'float' } + ] +} ); + +// source: https://github.com/selfshadow/ltc_code/blob/master/webgl/shaders/ltc/ltc_blit.fs + +const RRTAndODTFit = /*@__PURE__*/ Fn( ( [ color ] ) => { + + const a = color.mul( color.add( 0.0245786 ) ).sub( 0.000090537 ); + const b = color.mul( color.add( 0.4329510 ).mul( 0.983729 ) ).add( 0.238081 ); + + return a.div( b ); + +} ); + +/** + * ACESFilmic tone mapping. + * + * Reference: {@link https://github.com/selfshadow/ltc_code/blob/master/webgl/shaders/ltc/ltc_blit.fs} + * + * @tsl + * @function + * @param {Node} color - The color that should be tone mapped. + * @param {Node} exposure - The exposure. + * @return {Node} The tone mapped color. + */ +const acesFilmicToneMapping = /*@__PURE__*/ Fn( ( [ color, exposure ] ) => { + + // sRGB => XYZ => D65_2_D60 => AP1 => RRT_SAT + const ACESInputMat = mat3( + 0.59719, 0.35458, 0.04823, + 0.07600, 0.90834, 0.01566, + 0.02840, 0.13383, 0.83777 + ); + + // ODT_SAT => XYZ => D60_2_D65 => sRGB + const ACESOutputMat = mat3( + 1.60475, -0.53108, -0.07367, + -0.10208, 1.10813, -605e-5, + -327e-5, -0.07276, 1.07602 + ); + + color = color.mul( exposure ).div( 0.6 ); + + color = ACESInputMat.mul( color ); + + // Apply RRT and ODT + color = RRTAndODTFit( color ); + + color = ACESOutputMat.mul( color ); + + // Clamp to [0, 1] + return color.clamp(); + +} ).setLayout( { + name: 'acesFilmicToneMapping', + type: 'vec3', + inputs: [ + { name: 'color', type: 'vec3' }, + { name: 'exposure', type: 'float' } + ] +} ); + +const LINEAR_REC2020_TO_LINEAR_SRGB = /*@__PURE__*/ mat3( vec3( 1.6605, -0.1246, -0.0182 ), vec3( -0.5876, 1.1329, -0.1006 ), vec3( -0.0728, -83e-4, 1.1187 ) ); +const LINEAR_SRGB_TO_LINEAR_REC2020 = /*@__PURE__*/ mat3( vec3( 0.6274, 0.0691, 0.0164 ), vec3( 0.3293, 0.9195, 0.0880 ), vec3( 0.0433, 0.0113, 0.8956 ) ); + +const agxDefaultContrastApprox = /*@__PURE__*/ Fn( ( [ x_immutable ] ) => { + + const x = vec3( x_immutable ).toVar(); + const x2 = vec3( x.mul( x ) ).toVar(); + const x4 = vec3( x2.mul( x2 ) ).toVar(); + + return float( 15.5 ).mul( x4.mul( x2 ) ).sub( mul( 40.14, x4.mul( x ) ) ).add( mul( 31.96, x4 ).sub( mul( 6.868, x2.mul( x ) ) ).add( mul( 0.4298, x2 ).add( mul( 0.1191, x ).sub( 0.00232 ) ) ) ); + +} ); + +/** + * AgX tone mapping. + * + * @tsl + * @function + * @param {Node} color - The color that should be tone mapped. + * @param {Node} exposure - The exposure. + * @return {Node} The tone mapped color. + */ +const agxToneMapping = /*@__PURE__*/ Fn( ( [ color, exposure ] ) => { + + const colortone = vec3( color ).toVar(); + const AgXInsetMatrix = mat3( vec3( 0.856627153315983, 0.137318972929847, 0.11189821299995 ), vec3( 0.0951212405381588, 0.761241990602591, 0.0767994186031903 ), vec3( 0.0482516061458583, 0.101439036467562, 0.811302368396859 ) ); + const AgXOutsetMatrix = mat3( vec3( 1.1271005818144368, -0.1413297634984383, -0.14132976349843826 ), vec3( -0.11060664309660323, 1.157823702216272, -0.11060664309660294 ), vec3( -0.016493938717834573, -0.016493938717834257, 1.2519364065950405 ) ); + const AgxMinEv = float( -12.47393 ); + const AgxMaxEv = float( 4.026069 ); + colortone.mulAssign( exposure ); + colortone.assign( LINEAR_SRGB_TO_LINEAR_REC2020.mul( colortone ) ); + colortone.assign( AgXInsetMatrix.mul( colortone ) ); + colortone.assign( max$1( colortone, 1e-10 ) ); + colortone.assign( log2( colortone ) ); + colortone.assign( colortone.sub( AgxMinEv ).div( AgxMaxEv.sub( AgxMinEv ) ) ); + colortone.assign( clamp( colortone, 0.0, 1.0 ) ); + colortone.assign( agxDefaultContrastApprox( colortone ) ); + colortone.assign( AgXOutsetMatrix.mul( colortone ) ); + colortone.assign( pow( max$1( vec3( 0.0 ), colortone ), vec3( 2.2 ) ) ); + colortone.assign( LINEAR_REC2020_TO_LINEAR_SRGB.mul( colortone ) ); + colortone.assign( clamp( colortone, 0.0, 1.0 ) ); + + return colortone; + +} ).setLayout( { + name: 'agxToneMapping', + type: 'vec3', + inputs: [ + { name: 'color', type: 'vec3' }, + { name: 'exposure', type: 'float' } + ] +} ); + +/** + * Neutral tone mapping. + * + * Reference: {@link https://modelviewer.dev/examples/tone-mapping} + * + * @tsl + * @function + * @param {Node} color - The color that should be tone mapped. + * @param {Node} exposure - The exposure. + * @return {Node} The tone mapped color. + */ +const neutralToneMapping = /*@__PURE__*/ Fn( ( [ color, exposure ] ) => { + + const StartCompression = float( 0.8 - 0.04 ); + const Desaturation = float( 0.15 ); + + color = color.mul( exposure ); + + const x = min$1( color.r, min$1( color.g, color.b ) ); + const offset = select( x.lessThan( 0.08 ), x.sub( mul( 6.25, x.mul( x ) ) ), 0.04 ); + + color.subAssign( offset ); + + const peak = max$1( color.r, max$1( color.g, color.b ) ); + + If( peak.lessThan( StartCompression ), () => { + + return color; + + } ); + + const d = sub( 1, StartCompression ); + const newPeak = sub( 1, d.mul( d ).div( peak.add( d.sub( StartCompression ) ) ) ); + color.mulAssign( newPeak.div( peak ) ); + const g = sub( 1, div( 1, Desaturation.mul( peak.sub( newPeak ) ).add( 1 ) ) ); + + return mix( color, vec3( newPeak ), g ); + +} ).setLayout( { + name: 'neutralToneMapping', + type: 'vec3', + inputs: [ + { name: 'color', type: 'vec3' }, + { name: 'exposure', type: 'float' } + ] +} ); + +/** + * This class represents native code sections. It is the base + * class for modules like {@link FunctionNode} which allows to implement + * functions with native shader languages. + * + * @augments Node + */ +class CodeNode extends Node { + + static get type() { + + return 'CodeNode'; + + } + + /** + * Constructs a new code node. + * + * @param {string} [code=''] - The native code. + * @param {Array} [includes=[]] - An array of includes. + * @param {('js'|'wgsl'|'glsl')} [language=''] - The used language. + */ + constructor( code = '', includes = [], language = '' ) { + + super( 'code' ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isCodeNode = true; + + /** + * This flag is used for global cache. + * + * @type {boolean} + * @default true + */ + this.global = true; + + /** + * The native code. + * + * @type {string} + * @default '' + */ + this.code = code; + + /** + * An array of includes + * + * @type {Array} + * @default [] + */ + this.includes = includes; + + /** + * The used language. + * + * @type {('js'|'wgsl'|'glsl')} + * @default '' + */ + this.language = language; + + } + + /** + * Sets the includes of this code node. + * + * @param {Array} includes - The includes to set. + * @return {CodeNode} A reference to this node. + */ + setIncludes( includes ) { + + this.includes = includes; + + return this; + + } + + /** + * Returns the includes of this code node. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {Array} The includes. + */ + getIncludes( /*builder*/ ) { + + return this.includes; + + } + + generate( builder ) { + + const includes = this.getIncludes( builder ); + + for ( const include of includes ) { + + include.build( builder ); + + } + + const nodeCode = builder.getCodeFromNode( this, this.getNodeType( builder ) ); + nodeCode.code = this.code; + + return nodeCode.code; + + } + + serialize( data ) { + + super.serialize( data ); + + data.code = this.code; + data.language = this.language; + + } + + deserialize( data ) { + + super.deserialize( data ); + + this.code = data.code; + this.language = data.language; + + } + +} + +/** + * TSL function for creating a code node. + * + * @tsl + * @function + * @param {string} [code] - The native code. + * @param {?Array} [includes=[]] - An array of includes. + * @param {?('js'|'wgsl'|'glsl')} [language=''] - The used language. + * @returns {CodeNode} + */ +const code = /*@__PURE__*/ nodeProxy( CodeNode ).setParameterLength( 1, 3 ); + +/** + * TSL function for creating a JS code node. + * + * @tsl + * @function + * @param {string} src - The native code. + * @param {Array} includes - An array of includes. + * @returns {CodeNode} + */ +const js = ( src, includes ) => code( src, includes, 'js' ); + +/** + * TSL function for creating a WGSL code node. + * + * @tsl + * @function + * @param {string} src - The native code. + * @param {Array} includes - An array of includes. + * @returns {CodeNode} + */ +const wgsl = ( src, includes ) => code( src, includes, 'wgsl' ); + +/** + * TSL function for creating a GLSL code node. + * + * @tsl + * @function + * @param {string} src - The native code. + * @param {Array} includes - An array of includes. + * @returns {CodeNode} + */ +const glsl = ( src, includes ) => code( src, includes, 'glsl' ); + +/** + * This class represents a native shader function. It can be used to implement + * certain aspects of a node material with native shader code. There are two predefined + * TSL functions for easier usage. + * + * - `wgslFn`: Creates a WGSL function node. + * - `glslFn`: Creates a GLSL function node. + * + * A basic example with one include looks like so: + * + * ```js + * const desaturateWGSLFn = wgslFn( ` + * fn desaturate( color:vec3 ) -> vec3 { + * let lum = vec3( 0.299, 0.587, 0.114 ); + * return vec3( dot( lum, color ) ); + * }` + *); + * const someWGSLFn = wgslFn( ` + * fn someFn( color:vec3 ) -> vec3 { + * return desaturate( color ); + * } + * `, [ desaturateWGSLFn ] ); + * material.colorNode = someWGSLFn( { color: texture( map ) } ); + *``` + * @augments CodeNode + */ +class FunctionNode extends CodeNode { + + static get type() { + + return 'FunctionNode'; + + } + + /** + * Constructs a new function node. + * + * @param {string} [code=''] - The native code. + * @param {Array} [includes=[]] - An array of includes. + * @param {('js'|'wgsl'|'glsl')} [language=''] - The used language. + */ + constructor( code = '', includes = [], language = '' ) { + + super( code, includes, language ); + + } + + getNodeType( builder ) { + + return this.getNodeFunction( builder ).type; + + } + + /** + * Returns the inputs of this function node. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {Array} The inputs. + */ + getInputs( builder ) { + + return this.getNodeFunction( builder ).inputs; + + } + + /** + * Returns the node function for this function node. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {NodeFunction} The node function. + */ + getNodeFunction( builder ) { + + const nodeData = builder.getDataFromNode( this ); + + let nodeFunction = nodeData.nodeFunction; + + if ( nodeFunction === undefined ) { + + nodeFunction = builder.parser.parseFunction( this.code ); + + nodeData.nodeFunction = nodeFunction; + + } + + return nodeFunction; + + } + + generate( builder, output ) { + + super.generate( builder ); + + const nodeFunction = this.getNodeFunction( builder ); + + const name = nodeFunction.name; + const type = nodeFunction.type; + + const nodeCode = builder.getCodeFromNode( this, type ); + + if ( name !== '' ) { + + // use a custom property name + + nodeCode.name = name; + + } + + const propertyName = builder.getPropertyName( nodeCode ); + + const code = this.getNodeFunction( builder ).getCode( propertyName ); + + nodeCode.code = code + '\n'; + + if ( output === 'property' ) { + + return propertyName; + + } else { + + return builder.format( `${ propertyName }()`, type, output ); + + } + + } + +} + +const nativeFn = ( code, includes = [], language = '' ) => { + + for ( let i = 0; i < includes.length; i ++ ) { + + const include = includes[ i ]; + + // TSL Function: glslFn, wgslFn + + if ( typeof include === 'function' ) { + + includes[ i ] = include.functionNode; + + } + + } + + const functionNode = nodeObject( new FunctionNode( code, includes, language ) ); + + const fn = ( ...params ) => functionNode.call( ...params ); + fn.functionNode = functionNode; + + return fn; + +}; + +const glslFn = ( code, includes ) => nativeFn( code, includes, 'glsl' ); +const wgslFn = ( code, includes ) => nativeFn( code, includes, 'wgsl' ); + +/** + * `ScriptableNode` uses this class to manage script inputs and outputs. + * + * @augments Node + */ +class ScriptableValueNode extends Node { + + static get type() { + + return 'ScriptableValueNode'; + + } + + /** + * Constructs a new scriptable node. + * + * @param {any} [value=null] - The value. + */ + constructor( value = null ) { + + super(); + + /** + * A reference to the value. + * + * @private + * @default null + */ + this._value = value; + + /** + * Depending on the type of `_value`, this property might cache parsed data. + * + * @private + * @default null + */ + this._cache = null; + + /** + * If this node represents an input, this property represents the input type. + * + * @type {?string} + * @default null + */ + this.inputType = null; + + /** + * If this node represents an output, this property represents the output type. + * + * @type {?string} + * @default null + */ + this.outputType = null; + + /** + * An event dispatcher for managing events. + * + * @type {EventDispatcher} + */ + this.events = new EventDispatcher(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isScriptableValueNode = true; + + } + + /** + * Whether this node represents an output or not. + * + * @type {boolean} + * @readonly + * @default true + */ + get isScriptableOutputNode() { + + return this.outputType !== null; + + } + + set value( val ) { + + if ( this._value === val ) return; + + if ( this._cache && this.inputType === 'URL' && this.value.value instanceof ArrayBuffer ) { + + URL.revokeObjectURL( this._cache ); + + this._cache = null; + + } + + this._value = val; + + this.events.dispatchEvent( { type: 'change' } ); + + this.refresh(); + + } + + /** + * The node's value. + * + * @type {any} + */ + get value() { + + return this._value; + + } + + /** + * Dispatches the `refresh` event. + */ + refresh() { + + this.events.dispatchEvent( { type: 'refresh' } ); + + } + + /** + * The `value` property usually represents a node or even binary data in form of array buffers. + * In this case, this method tries to return the actual value behind the complex type. + * + * @return {any} The value. + */ + getValue() { + + const value = this.value; + + if ( value && this._cache === null && this.inputType === 'URL' && value.value instanceof ArrayBuffer ) { + + this._cache = URL.createObjectURL( new Blob( [ value.value ] ) ); + + } else if ( value && value.value !== null && value.value !== undefined && ( + ( ( this.inputType === 'URL' || this.inputType === 'String' ) && typeof value.value === 'string' ) || + ( this.inputType === 'Number' && typeof value.value === 'number' ) || + ( this.inputType === 'Vector2' && value.value.isVector2 ) || + ( this.inputType === 'Vector3' && value.value.isVector3 ) || + ( this.inputType === 'Vector4' && value.value.isVector4 ) || + ( this.inputType === 'Color' && value.value.isColor ) || + ( this.inputType === 'Matrix3' && value.value.isMatrix3 ) || + ( this.inputType === 'Matrix4' && value.value.isMatrix4 ) + ) ) { + + return value.value; + + } + + return this._cache || value; + + } + + /** + * Overwritten since the node type is inferred from the value. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType( builder ) { + + return this.value && this.value.isNode ? this.value.getNodeType( builder ) : 'float'; + + } + + setup() { + + return this.value && this.value.isNode ? this.value : float(); + + } + + serialize( data ) { + + super.serialize( data ); + + if ( this.value !== null ) { + + if ( this.inputType === 'ArrayBuffer' ) { + + data.value = arrayBufferToBase64( this.value ); + + } else { + + data.value = this.value ? this.value.toJSON( data.meta ).uuid : null; + + } + + } else { + + data.value = null; + + } + + data.inputType = this.inputType; + data.outputType = this.outputType; + + } + + deserialize( data ) { + + super.deserialize( data ); + + let value = null; + + if ( data.value !== null ) { + + if ( data.inputType === 'ArrayBuffer' ) { + + value = base64ToArrayBuffer( data.value ); + + } else if ( data.inputType === 'Texture' ) { + + value = data.meta.textures[ data.value ]; + + } else { + + value = data.meta.nodes[ data.value ] || null; + + } + + } + + this.value = value; + + this.inputType = data.inputType; + this.outputType = data.outputType; + + } + +} + +/** + * TSL function for creating a scriptable value node. + * + * @tsl + * @function + * @param {any} [value] - The value. + * @returns {ScriptableValueNode} + */ +const scriptableValue = /*@__PURE__*/ nodeProxy( ScriptableValueNode ).setParameterLength( 1 ); + +/** + * A Map-like data structure for managing resources of scriptable nodes. + * + * @augments Map + */ +class Resources extends Map { + + get( key, callback = null, ...params ) { + + if ( this.has( key ) ) return super.get( key ); + + if ( callback !== null ) { + + const value = callback( ...params ); + this.set( key, value ); + return value; + + } + + } + +} + +class Parameters { + + constructor( scriptableNode ) { + + this.scriptableNode = scriptableNode; + + } + + get parameters() { + + return this.scriptableNode.parameters; + + } + + get layout() { + + return this.scriptableNode.getLayout(); + + } + + getInputLayout( id ) { + + return this.scriptableNode.getInputLayout( id ); + + } + + get( name ) { + + const param = this.parameters[ name ]; + const value = param ? param.getValue() : null; + + return value; + + } + +} + +/** + * Defines the resources (e.g. namespaces) of scriptable nodes. + * + * @type {Resources} + */ +const ScriptableNodeResources = new Resources(); + +/** + * This type of node allows to implement nodes with custom scripts. The script + * section is represented as an instance of `CodeNode` written with JavaScript. + * The script itself must adhere to a specific structure. + * + * - main(): Executed once by default and every time `node.needsUpdate` is set. + * - layout: The layout object defines the script's interface (inputs and outputs). + * + * ```js + * ScriptableNodeResources.set( 'TSL', TSL ); + * + * const scriptableNode = scriptable( js( ` + * layout = { + * outputType: 'node', + * elements: [ + * { name: 'source', inputType: 'node' }, + * ] + * }; + * + * const { mul, oscSine } = TSL; + * + * function main() { + * const source = parameters.get( 'source' ) || float(); + * return mul( source, oscSine() ) ); + * } + * + * ` ) ); + * + * scriptableNode.setParameter( 'source', color( 1, 0, 0 ) ); + * + * const material = new THREE.MeshBasicNodeMaterial(); + * material.colorNode = scriptableNode; + * ``` + * + * @augments Node + */ +class ScriptableNode extends Node { + + static get type() { + + return 'ScriptableNode'; + + } + + /** + * Constructs a new scriptable node. + * + * @param {?CodeNode} [codeNode=null] - The code node. + * @param {Object} [parameters={}] - The parameters definition. + */ + constructor( codeNode = null, parameters = {} ) { + + super(); + + /** + * The code node. + * + * @type {?CodeNode} + * @default null + */ + this.codeNode = codeNode; + + /** + * The parameters definition. + * + * @type {Object} + * @default {} + */ + this.parameters = parameters; + + this._local = new Resources(); + this._output = scriptableValue( null ); + this._outputs = {}; + this._source = this.source; + this._method = null; + this._object = null; + this._value = null; + this._needsOutputUpdate = true; + + this.onRefresh = this.onRefresh.bind( this ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isScriptableNode = true; + + } + + /** + * The source code of the scriptable node. + * + * @type {string} + */ + get source() { + + return this.codeNode ? this.codeNode.code : ''; + + } + + /** + * Sets the reference of a local script variable. + * + * @param {string} name - The variable name. + * @param {Object} value - The reference to set. + * @return {Resources} The resource map + */ + setLocal( name, value ) { + + return this._local.set( name, value ); + + } + + /** + * Gets the value of a local script variable. + * + * @param {string} name - The variable name. + * @return {Object} The value. + */ + getLocal( name ) { + + return this._local.get( name ); + + } + + /** + * Event listener for the `refresh` event. + */ + onRefresh() { + + this._refresh(); + + } + + /** + * Returns an input from the layout with the given id/name. + * + * @param {string} id - The id/name of the input. + * @return {Object} The element entry. + */ + getInputLayout( id ) { + + for ( const element of this.getLayout() ) { + + if ( element.inputType && ( element.id === id || element.name === id ) ) { + + return element; + + } + + } + + } + + /** + * Returns an output from the layout with the given id/name. + * + * @param {string} id - The id/name of the output. + * @return {Object} The element entry. + */ + getOutputLayout( id ) { + + for ( const element of this.getLayout() ) { + + if ( element.outputType && ( element.id === id || element.name === id ) ) { + + return element; + + } + + } + + } + + /** + * Defines a script output for the given name and value. + * + * @param {string} name - The name of the output. + * @param {Node} value - The node value. + * @return {ScriptableNode} A reference to this node. + */ + setOutput( name, value ) { + + const outputs = this._outputs; + + if ( outputs[ name ] === undefined ) { + + outputs[ name ] = scriptableValue( value ); + + } else { + + outputs[ name ].value = value; + + } + + return this; + + } + + /** + * Returns a script output for the given name. + * + * @param {string} name - The name of the output. + * @return {ScriptableValueNode} The node value. + */ + getOutput( name ) { + + return this._outputs[ name ]; + + } + + /** + * Returns a parameter for the given name + * + * @param {string} name - The name of the parameter. + * @return {ScriptableValueNode} The node value. + */ + getParameter( name ) { + + return this.parameters[ name ]; + + } + + /** + * Sets a value for the given parameter name. + * + * @param {string} name - The parameter name. + * @param {any} value - The parameter value. + * @return {ScriptableNode} A reference to this node. + */ + setParameter( name, value ) { + + const parameters = this.parameters; + + if ( value && value.isScriptableNode ) { + + this.deleteParameter( name ); + + parameters[ name ] = value; + parameters[ name ].getDefaultOutput().events.addEventListener( 'refresh', this.onRefresh ); + + } else if ( value && value.isScriptableValueNode ) { + + this.deleteParameter( name ); + + parameters[ name ] = value; + parameters[ name ].events.addEventListener( 'refresh', this.onRefresh ); + + } else if ( parameters[ name ] === undefined ) { + + parameters[ name ] = scriptableValue( value ); + parameters[ name ].events.addEventListener( 'refresh', this.onRefresh ); + + } else { + + parameters[ name ].value = value; + + } + + return this; + + } + + /** + * Returns the value of this node which is the value of + * the default output. + * + * @return {Node} The value. + */ + getValue() { + + return this.getDefaultOutput().getValue(); + + } + + /** + * Deletes a parameter from the script. + * + * @param {string} name - The parameter to remove. + * @return {ScriptableNode} A reference to this node. + */ + deleteParameter( name ) { + + let valueNode = this.parameters[ name ]; + + if ( valueNode ) { + + if ( valueNode.isScriptableNode ) valueNode = valueNode.getDefaultOutput(); + + valueNode.events.removeEventListener( 'refresh', this.onRefresh ); + + } + + return this; + + } + + /** + * Deletes all parameters from the script. + * + * @return {ScriptableNode} A reference to this node. + */ + clearParameters() { + + for ( const name of Object.keys( this.parameters ) ) { + + this.deleteParameter( name ); + + } + + this.needsUpdate = true; + + return this; + + } + + /** + * Calls a function from the script. + * + * @param {string} name - The function name. + * @param {...any} params - A list of parameters. + * @return {any} The result of the function call. + */ + call( name, ...params ) { + + const object = this.getObject(); + const method = object[ name ]; + + if ( typeof method === 'function' ) { + + return method( ...params ); + + } + + } + + /** + * Asynchronously calls a function from the script. + * + * @param {string} name - The function name. + * @param {...any} params - A list of parameters. + * @return {Promise} The result of the function call. + */ + async callAsync( name, ...params ) { + + const object = this.getObject(); + const method = object[ name ]; + + if ( typeof method === 'function' ) { + + return method.constructor.name === 'AsyncFunction' ? await method( ...params ) : method( ...params ); + + } + + } + + /** + * Overwritten since the node types is inferred from the script's output. + * + * @param {NodeBuilder} builder - The current node builder + * @return {string} The node type. + */ + getNodeType( builder ) { + + return this.getDefaultOutputNode().getNodeType( builder ); + + } + + /** + * Refreshes the script node. + * + * @param {?string} [output=null] - An optional output. + */ + refresh( output = null ) { + + if ( output !== null ) { + + this.getOutput( output ).refresh(); + + } else { + + this._refresh(); + + } + + } + + /** + * Returns an object representation of the script. + * + * @return {Object} The result object. + */ + getObject() { + + if ( this.needsUpdate ) this.dispose(); + if ( this._object !== null ) return this._object; + + // + + const refresh = () => this.refresh(); + const setOutput = ( id, value ) => this.setOutput( id, value ); + + const parameters = new Parameters( this ); + + const THREE = ScriptableNodeResources.get( 'THREE' ); + const TSL = ScriptableNodeResources.get( 'TSL' ); + + const method = this.getMethod(); + const params = [ parameters, this._local, ScriptableNodeResources, refresh, setOutput, THREE, TSL ]; + + this._object = method( ...params ); + + const layout = this._object.layout; + + if ( layout ) { + + if ( layout.cache === false ) { + + this._local.clear(); + + } + + // default output + this._output.outputType = layout.outputType || null; + + if ( Array.isArray( layout.elements ) ) { + + for ( const element of layout.elements ) { + + const id = element.id || element.name; + + if ( element.inputType ) { + + if ( this.getParameter( id ) === undefined ) this.setParameter( id, null ); + + this.getParameter( id ).inputType = element.inputType; + + } + + if ( element.outputType ) { + + if ( this.getOutput( id ) === undefined ) this.setOutput( id, null ); + + this.getOutput( id ).outputType = element.outputType; + + } + + } + + } + + } + + return this._object; + + } + + deserialize( data ) { + + super.deserialize( data ); + + for ( const name in this.parameters ) { + + let valueNode = this.parameters[ name ]; + + if ( valueNode.isScriptableNode ) valueNode = valueNode.getDefaultOutput(); + + valueNode.events.addEventListener( 'refresh', this.onRefresh ); + + } + + } + + /** + * Returns the layout of the script. + * + * @return {Object} The script's layout. + */ + getLayout() { + + return this.getObject().layout; + + } + + /** + * Returns default node output of the script. + * + * @return {Node} The default node output. + */ + getDefaultOutputNode() { + + const output = this.getDefaultOutput().value; + + if ( output && output.isNode ) { + + return output; + + } + + return float(); + + } + + /** + * Returns default output of the script. + * + * @return {ScriptableValueNode} The default output. + */ + getDefaultOutput() { + + return this._exec()._output; + + } + + /** + * Returns a function created from the node's script. + * + * @return {Function} The function representing the node's code. + */ + getMethod() { + + if ( this.needsUpdate ) this.dispose(); + if ( this._method !== null ) return this._method; + + // + + const parametersProps = [ 'parameters', 'local', 'global', 'refresh', 'setOutput', 'THREE', 'TSL' ]; + const interfaceProps = [ 'layout', 'init', 'main', 'dispose' ]; + + const properties = interfaceProps.join( ', ' ); + const declarations = 'var ' + properties + '; var output = {};\n'; + const returns = '\nreturn { ...output, ' + properties + ' };'; + + const code = declarations + this.codeNode.code + returns; + + // + + this._method = new Function( ...parametersProps, code ); + + return this._method; + + } + + /** + * Frees all internal resources. + */ + dispose() { + + if ( this._method === null ) return; + + if ( this._object && typeof this._object.dispose === 'function' ) { + + this._object.dispose(); + + } + + this._method = null; + this._object = null; + this._source = null; + this._value = null; + this._needsOutputUpdate = true; + this._output.value = null; + this._outputs = {}; + + } + + setup() { + + return this.getDefaultOutputNode(); + + } + + getCacheKey( force ) { + + const values = [ hashString( this.source ), this.getDefaultOutputNode().getCacheKey( force ) ]; + + for ( const param in this.parameters ) { + + values.push( this.parameters[ param ].getCacheKey( force ) ); + + } + + return hashArray( values ); + + } + + set needsUpdate( value ) { + + if ( value === true ) this.dispose(); + + } + + get needsUpdate() { + + return this.source !== this._source; + + } + + /** + * Executes the `main` function of the script. + * + * @private + * @return {ScriptableNode} A reference to this node. + */ + _exec() { + + if ( this.codeNode === null ) return this; + + if ( this._needsOutputUpdate === true ) { + + this._value = this.call( 'main' ); + + this._needsOutputUpdate = false; + + } + + this._output.value = this._value; + + return this; + + } + + /** + * Executes the refresh. + * + * @private + */ + _refresh() { + + this.needsUpdate = true; + + this._exec(); + + this._output.refresh(); + + } + +} + +/** + * TSL function for creating a scriptable node. + * + * @tsl + * @function + * @param {CodeNode} [codeNode] - The code node. + * @param {?Object} [parameters={}] - The parameters definition. + * @returns {ScriptableNode} + */ +const scriptable = /*@__PURE__*/ nodeProxy( ScriptableNode ).setParameterLength( 1, 2 ); + +/** + * Returns a node that represents the `z` coordinate in view space + * for the current fragment. It's a different representation of the + * default depth value. + * + * This value can be part of a computation that defines how the fog + * density increases when moving away from the camera. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {Node} The viewZ node. + */ +function getViewZNode( builder ) { + + let viewZ; + + const getViewZ = builder.context.getViewZ; + + if ( getViewZ !== undefined ) { + + viewZ = getViewZ( this ); + + } + + return ( viewZ || positionView.z ).negate(); + +} + +/** + * Constructs a new range factor node. + * + * @tsl + * @function + * @param {Node} near - Defines the near value. + * @param {Node} far - Defines the far value. + */ +const rangeFogFactor = Fn( ( [ near, far ], builder ) => { + + const viewZ = getViewZNode( builder ); + + return smoothstep( near, far, viewZ ); + +} ); + +/** + * Represents an exponential squared fog. This type of fog gives + * a clear view near the camera and a faster than exponentially + * densening fog farther from the camera. + * + * @tsl + * @function + * @param {Node} density - Defines the fog density. + */ +const densityFogFactor = Fn( ( [ density ], builder ) => { + + const viewZ = getViewZNode( builder ); + + return density.mul( density, viewZ, viewZ ).negate().exp().oneMinus(); + +} ); + +/** + * This class can be used to configure a fog for the scene. + * Nodes of this type are assigned to `Scene.fogNode`. + * + * @tsl + * @function + * @param {Node} color - Defines the color of the fog. + * @param {Node} factor - Defines how the fog is factored in the scene. + */ +const fog = Fn( ( [ color, factor ] ) => { + + return vec4( factor.toFloat().mix( output.rgb, color.toVec3() ), output.a ); + +} ); + +// Deprecated + +/** + * @tsl + * @function + * @deprecated since r171. Use `fog( color, rangeFogFactor( near, far ) )` instead. + * + * @param {Node} color + * @param {Node} near + * @param {Node} far + * @returns {Function} + */ +function rangeFog( color, near, far ) { // @deprecated, r171 + + console.warn( 'THREE.TSL: "rangeFog( color, near, far )" is deprecated. Use "fog( color, rangeFogFactor( near, far ) )" instead.' ); + return fog( color, rangeFogFactor( near, far ) ); + +} + +/** + * @tsl + * @function + * @deprecated since r171. Use `fog( color, densityFogFactor( density ) )` instead. + * + * @param {Node} color + * @param {Node} density + * @returns {Function} + */ +function densityFog( color, density ) { // @deprecated, r171 + + console.warn( 'THREE.TSL: "densityFog( color, density )" is deprecated. Use "fog( color, densityFogFactor( density ) )" instead.' ); + return fog( color, densityFogFactor( density ) ); + +} + +let min = null; +let max = null; + +/** + * `RangeNode` generates random instanced attribute data in a defined range. + * An exemplary use case for this utility node is to generate random per-instance + * colors: + * ```js + * const material = new MeshBasicNodeMaterial(); + * material.colorNode = range( new Color( 0x000000 ), new Color( 0xFFFFFF ) ); + * const mesh = new InstancedMesh( geometry, material, count ); + * ``` + * @augments Node + */ +class RangeNode extends Node { + + static get type() { + + return 'RangeNode'; + + } + + /** + * Constructs a new range node. + * + * @param {Node} [minNode=float()] - A node defining the lower bound of the range. + * @param {Node} [maxNode=float()] - A node defining the upper bound of the range. + */ + constructor( minNode = float(), maxNode = float() ) { + + super(); + + /** + * A node defining the lower bound of the range. + * + * @type {Node} + * @default float() + */ + this.minNode = minNode; + + /** + * A node defining the upper bound of the range. + * + * @type {Node} + * @default float() + */ + this.maxNode = maxNode; + + } + + /** + * Returns the vector length which is computed based on the range definition. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {number} The vector length. + */ + getVectorLength( builder ) { + + const minLength = builder.getTypeLength( getValueType( this.minNode.value ) ); + const maxLength = builder.getTypeLength( getValueType( this.maxNode.value ) ); + + return minLength > maxLength ? minLength : maxLength; + + } + + /** + * This method is overwritten since the node type is inferred from range definition. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType( builder ) { + + return builder.object.count > 1 ? builder.getTypeFromLength( this.getVectorLength( builder ) ) : 'float'; + + } + + setup( builder ) { + + const object = builder.object; + + let output = null; + + if ( object.count > 1 ) { + + const minValue = this.minNode.value; + const maxValue = this.maxNode.value; + + const minLength = builder.getTypeLength( getValueType( minValue ) ); + const maxLength = builder.getTypeLength( getValueType( maxValue ) ); + + min = min || new Vector4(); + max = max || new Vector4(); + + min.setScalar( 0 ); + max.setScalar( 0 ); + + if ( minLength === 1 ) min.setScalar( minValue ); + else if ( minValue.isColor ) min.set( minValue.r, minValue.g, minValue.b, 1 ); + else min.set( minValue.x, minValue.y, minValue.z || 0, minValue.w || 0 ); + + if ( maxLength === 1 ) max.setScalar( maxValue ); + else if ( maxValue.isColor ) max.set( maxValue.r, maxValue.g, maxValue.b, 1 ); + else max.set( maxValue.x, maxValue.y, maxValue.z || 0, maxValue.w || 0 ); + + const stride = 4; + + const length = stride * object.count; + const array = new Float32Array( length ); + + for ( let i = 0; i < length; i ++ ) { + + const index = i % stride; + + const minElementValue = min.getComponent( index ); + const maxElementValue = max.getComponent( index ); + + array[ i ] = MathUtils.lerp( minElementValue, maxElementValue, Math.random() ); + + } + + const nodeType = this.getNodeType( builder ); + + if ( object.count <= 4096 ) { + + output = buffer( array, 'vec4', object.count ).element( instanceIndex ).convert( nodeType ); + + } else { + + // TODO: Improve anonymous buffer attribute creation removing this part + const bufferAttribute = new InstancedBufferAttribute( array, 4 ); + builder.geometry.setAttribute( '__range' + this.id, bufferAttribute ); + + output = instancedBufferAttribute( bufferAttribute ).convert( nodeType ); + + } + + } else { + + output = float( 0 ); + + } + + return output; + + } + +} + +/** + * TSL function for creating a range node. + * + * @tsl + * @function + * @param {Node} [minNode=float()] - A node defining the lower bound of the range. + * @param {Node} [maxNode=float()] - A node defining the upper bound of the range. + * @returns {RangeNode} + */ +const range = /*@__PURE__*/ nodeProxy( RangeNode ).setParameterLength( 2 ); + +/** + * `ComputeBuiltinNode` represents a compute-scope builtin value that expose information + * about the currently running dispatch and/or the device it is running on. + * + * This node can only be used with a WebGPU backend. + * + * @augments Node + */ +class ComputeBuiltinNode extends Node { + + static get type() { + + return 'ComputeBuiltinNode'; + + } + + /** + * Constructs a new compute builtin node. + * + * @param {string} builtinName - The built-in name. + * @param {string} nodeType - The node type. + */ + constructor( builtinName, nodeType ) { + + super( nodeType ); + + /** + * The built-in name. + * + * @private + * @type {string} + */ + this._builtinName = builtinName; + + } + + /** + * This method is overwritten since hash is derived from the built-in name. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The hash. + */ + getHash( builder ) { + + return this.getBuiltinName( builder ); + + } + + /** + * This method is overwritten since the node type is simply derived from `nodeType`.. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType( /*builder*/ ) { + + return this.nodeType; + + } + + /** + * Sets the builtin name. + * + * @param {string} builtinName - The built-in name. + * @return {ComputeBuiltinNode} A reference to this node. + */ + setBuiltinName( builtinName ) { + + this._builtinName = builtinName; + + return this; + + } + + /** + * Returns the builtin name. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The builtin name. + */ + getBuiltinName( /*builder*/ ) { + + return this._builtinName; + + } + + /** + * Whether the current node builder has the builtin or not. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {boolean} Whether the builder has the builtin or not. + */ + hasBuiltin( builder ) { + + return builder.hasBuiltin( this._builtinName ); + + } + + generate( builder, output ) { + + const builtinName = this.getBuiltinName( builder ); + const nodeType = this.getNodeType( builder ); + + if ( builder.shaderStage === 'compute' ) { + + return builder.format( builtinName, nodeType, output ); + + } else { + + console.warn( `ComputeBuiltinNode: Compute built-in value ${builtinName} can not be accessed in the ${builder.shaderStage} stage` ); + return builder.generateConst( nodeType ); + + } + + } + + serialize( data ) { + + super.serialize( data ); + + data.global = this.global; + data._builtinName = this._builtinName; + + } + + deserialize( data ) { + + super.deserialize( data ); + + this.global = data.global; + this._builtinName = data._builtinName; + + } + +} + +/** + * TSL function for creating a compute builtin node. + * + * @tsl + * @function + * @param {string} name - The built-in name. + * @param {string} nodeType - The node type. + * @returns {ComputeBuiltinNode} + */ +const computeBuiltin = ( name, nodeType ) => nodeObject( new ComputeBuiltinNode( name, nodeType ) ); + +/** + * Represents the number of workgroups dispatched by the compute shader. + * ```js + * // Run 512 invocations/threads with a workgroup size of 128. + * const computeFn = Fn(() => { + * + * // numWorkgroups.x = 4 + * storageBuffer.element(0).assign(numWorkgroups.x) + * + * })().compute(512, [128]); + * + * // Run 512 invocations/threads with the default workgroup size of 64. + * const computeFn = Fn(() => { + * + * // numWorkgroups.x = 8 + * storageBuffer.element(0).assign(numWorkgroups.x) + * + * })().compute(512); + * ``` + * + * @tsl + * @type {ComputeBuiltinNode} + */ +const numWorkgroups = /*@__PURE__*/ computeBuiltin( 'numWorkgroups', 'uvec3' ); + +/** + * Represents the 3-dimensional index of the workgroup the current compute invocation belongs to. + * ```js + * // Execute 12 compute threads with a workgroup size of 3. + * const computeFn = Fn( () => { + * + * If( workgroupId.x.mod( 2 ).equal( 0 ), () => { + * + * storageBuffer.element( instanceIndex ).assign( instanceIndex ); + * + * } ).Else( () => { + * + * storageBuffer.element( instanceIndex ).assign( 0 ); + * + * } ); + * + * } )().compute( 12, [ 3 ] ); + * + * // workgroupId.x = [0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3]; + * // Buffer Output = [0, 1, 2, 0, 0, 0, 6, 7, 8, 0, 0, 0]; + * ``` + * + * @tsl + * @type {ComputeBuiltinNode} + */ +const workgroupId = /*@__PURE__*/ computeBuiltin( 'workgroupId', 'uvec3' ); + +/** + * A non-linearized 3-dimensional representation of the current invocation's position within a 3D global grid. + * + * @tsl + * @type {ComputeBuiltinNode} + */ +const globalId = /*@__PURE__*/ computeBuiltin( 'globalId', 'uvec3' ); +/** + * A non-linearized 3-dimensional representation of the current invocation's position within a 3D workgroup grid. + * + * @tsl + * @type {ComputeBuiltinNode} + */ +const localId = /*@__PURE__*/ computeBuiltin( 'localId', 'uvec3' ); + +/** + * A device dependent variable that exposes the size of the current invocation's subgroup. + * + * @tsl + * @type {ComputeBuiltinNode} + */ +const subgroupSize = /*@__PURE__*/ computeBuiltin( 'subgroupSize', 'uint' ); + +/** + * Represents a GPU control barrier that synchronizes compute operations within a given scope. + * + * This node can only be used with a WebGPU backend. + * + * @augments Node + */ +class BarrierNode extends Node { + + /** + * Constructs a new barrier node. + * + * @param {string} scope - The scope defines the behavior of the node. + */ + constructor( scope ) { + + super(); + + this.scope = scope; + + } + + generate( builder ) { + + const { scope } = this; + const { renderer } = builder; + + if ( renderer.backend.isWebGLBackend === true ) { + + builder.addFlowCode( `\t// ${scope}Barrier \n` ); + + } else { + + builder.addLineFlowCode( `${scope}Barrier()`, this ); + + } + + } + +} + +/** + * TSL function for creating a barrier node. + * + * @tsl + * @function + * @param {string} scope - The scope defines the behavior of the node.. + * @returns {BarrierNode} + */ +const barrier = nodeProxy( BarrierNode ); + +/** + * TSL function for creating a workgroup barrier. All compute shader + * invocations must wait for each invocation within a workgroup to + * complete before the barrier can be surpassed. + * + * @tsl + * @function + * @returns {BarrierNode} + */ +const workgroupBarrier = () => barrier( 'workgroup' ).toStack(); + +/** + * TSL function for creating a storage barrier. All invocations must + * wait for each access to variables within the 'storage' address space + * to complete before the barrier can be passed. + * + * @tsl + * @function + * @returns {BarrierNode} + */ +const storageBarrier = () => barrier( 'storage' ).toStack(); + +/** + * TSL function for creating a texture barrier. All invocations must + * wait for each access to variables within the 'texture' address space + * to complete before the barrier can be passed. + * + * @tsl + * @function + * @returns {BarrierNode} + */ +const textureBarrier = () => barrier( 'texture' ).toStack(); + +/** + * Represents an element of a 'workgroup' scoped buffer. + * + * @augments ArrayElementNode + */ +class WorkgroupInfoElementNode extends ArrayElementNode { + + /** + * Constructs a new workgroup info element node. + * + * @param {Node} workgroupInfoNode - The workgroup info node. + * @param {Node} indexNode - The index node that defines the element access. + */ + constructor( workgroupInfoNode, indexNode ) { + + super( workgroupInfoNode, indexNode ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isWorkgroupInfoElementNode = true; + + } + + generate( builder, output ) { + + let snippet; + + const isAssignContext = builder.context.assign; + snippet = super.generate( builder ); + + if ( isAssignContext !== true ) { + + const type = this.getNodeType( builder ); + + snippet = builder.format( snippet, type, output ); + + } + + // TODO: Possibly activate clip distance index on index access rather than from clipping context + + return snippet; + + } + +} + +/** + * A node allowing the user to create a 'workgroup' scoped buffer within the + * context of a compute shader. Typically, workgroup scoped buffers are + * created to hold data that is transferred from a global storage scope into + * a local workgroup scope. For invocations within a workgroup, data + * access speeds on 'workgroup' scoped buffers can be significantly faster + * than similar access operations on globally accessible storage buffers. + * + * This node can only be used with a WebGPU backend. + * + * @augments Node + */ +class WorkgroupInfoNode extends Node { + + /** + * Constructs a new buffer scoped to type scope. + * + * @param {string} scope - TODO. + * @param {string} bufferType - The data type of a 'workgroup' scoped buffer element. + * @param {number} [bufferCount=0] - The number of elements in the buffer. + */ + constructor( scope, bufferType, bufferCount = 0 ) { + + super( bufferType ); + + /** + * The buffer type. + * + * @type {string} + */ + this.bufferType = bufferType; + + /** + * The buffer count. + * + * @type {number} + * @default 0 + */ + this.bufferCount = bufferCount; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isWorkgroupInfoNode = true; + + /** + * The data type of the array buffer. + * + * @type {string} + */ + this.elementType = bufferType; + + /** + * TODO. + * + * @type {string} + */ + this.scope = scope; + + } + + /** + * Sets the name/label of this node. + * + * @param {string} name - The name to set. + * @return {WorkgroupInfoNode} A reference to this node. + */ + label( name ) { + + this.name = name; + + return this; + + } + + /** + * Sets the scope of this node. + * + * @param {string} scope - The scope to set. + * @return {WorkgroupInfoNode} A reference to this node. + */ + setScope( scope ) { + + this.scope = scope; + + return this; + + } + + + /** + * The data type of the array buffer. + * + * @return {string} The element type. + */ + getElementType() { + + return this.elementType; + + } + + /** + * Overwrites the default implementation since the input type + * is inferred from the scope. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The input type. + */ + getInputType( /*builder*/ ) { + + return `${this.scope}Array`; + + } + + /** + * This method can be used to access elements via an index node. + * + * @param {IndexNode} indexNode - indexNode. + * @return {WorkgroupInfoElementNode} A reference to an element. + */ + element( indexNode ) { + + return nodeObject( new WorkgroupInfoElementNode( this, indexNode ) ); + + } + + generate( builder ) { + + return builder.getScopedArray( this.name || `${this.scope}Array_${this.id}`, this.scope.toLowerCase(), this.bufferType, this.bufferCount ); + + } + +} + +/** + * TSL function for creating a workgroup info node. + * Creates a new 'workgroup' scoped array buffer. + * + * @tsl + * @function + * @param {string} type - The data type of a 'workgroup' scoped buffer element. + * @param {number} [count=0] - The number of elements in the buffer. + * @returns {WorkgroupInfoNode} + */ +const workgroupArray = ( type, count ) => nodeObject( new WorkgroupInfoNode( 'Workgroup', type, count ) ); + +/** + * `AtomicFunctionNode` represents any function that can operate on atomic variable types + * within a shader. In an atomic function, any modification to an atomic variable will + * occur as an indivisible step with a defined order relative to other modifications. + * Accordingly, even if multiple atomic functions are modifying an atomic variable at once + * atomic operations will not interfere with each other. + * + * This node can only be used with a WebGPU backend. + * + * @augments Node + */ +class AtomicFunctionNode extends Node { + + static get type() { + + return 'AtomicFunctionNode'; + + } + + /** + * Constructs a new atomic function node. + * + * @param {string} method - The signature of the atomic function to construct. + * @param {Node} pointerNode - An atomic variable or element of an atomic buffer. + * @param {Node} valueNode - The value that mutates the atomic variable. + */ + constructor( method, pointerNode, valueNode ) { + + super( 'uint' ); + + /** + * The signature of the atomic function to construct. + * + * @type {string} + */ + this.method = method; + + /** + * An atomic variable or element of an atomic buffer. + * + * @type {Node} + */ + this.pointerNode = pointerNode; + + /** + * A value that modifies the atomic variable. + * + * @type {Node} + */ + this.valueNode = valueNode; + + /** + * Creates a list of the parents for this node for detecting if the node needs to return a value. + * + * @type {boolean} + * @default true + */ + this.parents = true; + + } + + /** + * Overwrites the default implementation to return the type of + * the pointer node. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The input type. + */ + getInputType( builder ) { + + return this.pointerNode.getNodeType( builder ); + + } + + /** + * Overwritten since the node type is inferred from the input type. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType( builder ) { + + return this.getInputType( builder ); + + } + + generate( builder ) { + + const properties = builder.getNodeProperties( this ); + const parents = properties.parents; + + const method = this.method; + + const type = this.getNodeType( builder ); + const inputType = this.getInputType( builder ); + + const a = this.pointerNode; + const b = this.valueNode; + + const params = []; + + params.push( `&${ a.build( builder, inputType ) }` ); + + if ( b !== null ) { + + params.push( b.build( builder, inputType ) ); + + + } + + const methodSnippet = `${ builder.getMethod( method, type ) }( ${ params.join( ', ' ) } )`; + const isVoid = parents.length === 1 && parents[ 0 ].isStackNode === true; + + if ( isVoid ) { + + builder.addLineFlowCode( methodSnippet, this ); + + } else { + + if ( properties.constNode === undefined ) { + + properties.constNode = expression( methodSnippet, type ).toConst(); + + } + + return properties.constNode.build( builder ); + + } + + } + +} + +AtomicFunctionNode.ATOMIC_LOAD = 'atomicLoad'; +AtomicFunctionNode.ATOMIC_STORE = 'atomicStore'; +AtomicFunctionNode.ATOMIC_ADD = 'atomicAdd'; +AtomicFunctionNode.ATOMIC_SUB = 'atomicSub'; +AtomicFunctionNode.ATOMIC_MAX = 'atomicMax'; +AtomicFunctionNode.ATOMIC_MIN = 'atomicMin'; +AtomicFunctionNode.ATOMIC_AND = 'atomicAnd'; +AtomicFunctionNode.ATOMIC_OR = 'atomicOr'; +AtomicFunctionNode.ATOMIC_XOR = 'atomicXor'; + +/** + * TSL function for creating an atomic function node. + * + * @tsl + * @function + * @param {string} method - The signature of the atomic function to construct. + * @param {Node} pointerNode - An atomic variable or element of an atomic buffer. + * @param {Node} valueNode - The value that mutates the atomic variable. + * @returns {AtomicFunctionNode} + */ +const atomicNode = nodeProxy( AtomicFunctionNode ); + +/** + * TSL function for appending an atomic function call into the programmatic flow of a compute shader. + * + * @tsl + * @function + * @param {string} method - The signature of the atomic function to construct. + * @param {Node} pointerNode - An atomic variable or element of an atomic buffer. + * @param {Node} valueNode - The value that mutates the atomic variable. + * @returns {AtomicFunctionNode} + */ +const atomicFunc = ( method, pointerNode, valueNode ) => { + + return atomicNode( method, pointerNode, valueNode ).toStack(); + +}; + +/** + * Loads the value stored in the atomic variable. + * + * @tsl + * @function + * @param {Node} pointerNode - An atomic variable or element of an atomic buffer. + * @returns {AtomicFunctionNode} + */ +const atomicLoad = ( pointerNode ) => atomicFunc( AtomicFunctionNode.ATOMIC_LOAD, pointerNode, null ); + +/** + * Stores a value in the atomic variable. + * + * @tsl + * @function + * @param {Node} pointerNode - An atomic variable or element of an atomic buffer. + * @param {Node} valueNode - The value that mutates the atomic variable. + * @returns {AtomicFunctionNode} + */ +const atomicStore = ( pointerNode, valueNode ) => atomicFunc( AtomicFunctionNode.ATOMIC_STORE, pointerNode, valueNode ); + +/** + * Increments the value stored in the atomic variable. + * + * @tsl + * @function + * @param {Node} pointerNode - An atomic variable or element of an atomic buffer. + * @param {Node} valueNode - The value that mutates the atomic variable. + * @returns {AtomicFunctionNode} + */ +const atomicAdd = ( pointerNode, valueNode ) => atomicFunc( AtomicFunctionNode.ATOMIC_ADD, pointerNode, valueNode ); + +/** + * Decrements the value stored in the atomic variable. + * + * @tsl + * @function + * @param {Node} pointerNode - An atomic variable or element of an atomic buffer. + * @param {Node} valueNode - The value that mutates the atomic variable. + * @returns {AtomicFunctionNode} + */ +const atomicSub = ( pointerNode, valueNode ) => atomicFunc( AtomicFunctionNode.ATOMIC_SUB, pointerNode, valueNode ); + +/** + * Stores in an atomic variable the maximum between its current value and a parameter. + * + * @tsl + * @function + * @param {Node} pointerNode - An atomic variable or element of an atomic buffer. + * @param {Node} valueNode - The value that mutates the atomic variable. + * @returns {AtomicFunctionNode} + */ +const atomicMax = ( pointerNode, valueNode ) => atomicFunc( AtomicFunctionNode.ATOMIC_MAX, pointerNode, valueNode ); + +/** + * Stores in an atomic variable the minimum between its current value and a parameter. + * + * @tsl + * @function + * @param {Node} pointerNode - An atomic variable or element of an atomic buffer. + * @param {Node} valueNode - The value that mutates the atomic variable. + * @returns {AtomicFunctionNode} + */ +const atomicMin = ( pointerNode, valueNode ) => atomicFunc( AtomicFunctionNode.ATOMIC_MIN, pointerNode, valueNode ); + +/** + * Stores in an atomic variable the bitwise AND of its value with a parameter. + * + * @tsl + * @function + * @param {Node} pointerNode - An atomic variable or element of an atomic buffer. + * @param {Node} valueNode - The value that mutates the atomic variable. + * @returns {AtomicFunctionNode} + */ +const atomicAnd = ( pointerNode, valueNode ) => atomicFunc( AtomicFunctionNode.ATOMIC_AND, pointerNode, valueNode ); + +/** + * Stores in an atomic variable the bitwise OR of its value with a parameter. + * + * @tsl + * @function + * @param {Node} pointerNode - An atomic variable or element of an atomic buffer. + * @param {Node} valueNode - The value that mutates the atomic variable. + * @returns {AtomicFunctionNode} + */ +const atomicOr = ( pointerNode, valueNode ) => atomicFunc( AtomicFunctionNode.ATOMIC_OR, pointerNode, valueNode ); + +/** + * Stores in an atomic variable the bitwise XOR of its value with a parameter. + * + * @tsl + * @function + * @param {Node} pointerNode - An atomic variable or element of an atomic buffer. + * @param {Node} valueNode - The value that mutates the atomic variable. + * @returns {AtomicFunctionNode} + */ +const atomicXor = ( pointerNode, valueNode ) => atomicFunc( AtomicFunctionNode.ATOMIC_XOR, pointerNode, valueNode ); + +let uniformsLib; + +function getLightData( light ) { + + uniformsLib = uniformsLib || new WeakMap(); + + let uniforms = uniformsLib.get( light ); + + if ( uniforms === undefined ) uniformsLib.set( light, uniforms = {} ); + + return uniforms; + +} + +/** + * TSL function for getting a shadow matrix uniform node for the given light. + * + * @tsl + * @function + * @param {Light} light -The light source. + * @returns {UniformNode} The shadow matrix uniform node. + */ +function lightShadowMatrix( light ) { + + const data = getLightData( light ); + + return data.shadowMatrix || ( data.shadowMatrix = uniform( 'mat4' ).setGroup( renderGroup ).onRenderUpdate( ( frame ) => { + + if ( light.castShadow !== true || frame.renderer.shadowMap.enabled === false ) { + + light.shadow.updateMatrices( light ); + + } + + return light.shadow.matrix; + + } ) ); + +} + +/** + * TSL function for getting projected uv coordinates for the given light. + * Relevant when using maps with spot lights. + * + * @tsl + * @function + * @param {Light} light -The light source. + * @param {Node} [position=positionWorld] -The position to project. + * @returns {Node} The projected uvs. + */ +function lightProjectionUV( light, position = positionWorld ) { + + const spotLightCoord = lightShadowMatrix( light ).mul( position ); + const projectionUV = spotLightCoord.xyz.div( spotLightCoord.w ); + + return projectionUV; + +} + +/** + * TSL function for getting the position in world space for the given light. + * + * @tsl + * @function + * @param {Light} light -The light source. + * @returns {UniformNode} The light's position in world space. + */ +function lightPosition( light ) { + + const data = getLightData( light ); + + return data.position || ( data.position = uniform( new Vector3() ).setGroup( renderGroup ).onRenderUpdate( ( _, self ) => self.value.setFromMatrixPosition( light.matrixWorld ) ) ); + +} + +/** + * TSL function for getting the light target position in world space for the given light. + * + * @tsl + * @function + * @param {Light} light -The light source. + * @returns {UniformNode} The light target position in world space. + */ +function lightTargetPosition( light ) { + + const data = getLightData( light ); + + return data.targetPosition || ( data.targetPosition = uniform( new Vector3() ).setGroup( renderGroup ).onRenderUpdate( ( _, self ) => self.value.setFromMatrixPosition( light.target.matrixWorld ) ) ); + +} + +/** + * TSL function for getting the position in view space for the given light. + * + * @tsl + * @function + * @param {Light} light - The light source. + * @returns {UniformNode} The light's position in view space. + */ +function lightViewPosition( light ) { + + const data = getLightData( light ); + + return data.viewPosition || ( data.viewPosition = uniform( new Vector3() ).setGroup( renderGroup ).onRenderUpdate( ( { camera }, self ) => { + + self.value = self.value || new Vector3(); + self.value.setFromMatrixPosition( light.matrixWorld ); + + self.value.applyMatrix4( camera.matrixWorldInverse ); + + } ) ); + +} + +/** + * TSL function for getting the light target direction for the given light. + * + * @tsl + * @function + * @param {Light} light -The light source. + * @returns {Node} The light's target direction. + */ +const lightTargetDirection = ( light ) => cameraViewMatrix.transformDirection( lightPosition( light ).sub( lightTargetPosition( light ) ) ); + +const sortLights = ( lights ) => { + + return lights.sort( ( a, b ) => a.id - b.id ); + +}; + +const getLightNodeById = ( id, lightNodes ) => { + + for ( const lightNode of lightNodes ) { + + if ( lightNode.isAnalyticLightNode && lightNode.light.id === id ) { + + return lightNode; + + } + + } + + return null; + +}; + +const _lightsNodeRef = /*@__PURE__*/ new WeakMap(); +const _hashData = []; + +/** + * This node represents the scene's lighting and manages the lighting model's life cycle + * for the current build 3D object. It is responsible for computing the total outgoing + * light in a given lighting context. + * + * @augments Node + */ +class LightsNode extends Node { + + static get type() { + + return 'LightsNode'; + + } + + /** + * Constructs a new lights node. + */ + constructor() { + + super( 'vec3' ); + + /** + * A node representing the total diffuse light. + * + * @type {Node} + */ + this.totalDiffuseNode = property( 'vec3', 'totalDiffuse' ); + + /** + * A node representing the total specular light. + * + * @type {Node} + */ + this.totalSpecularNode = property( 'vec3', 'totalSpecular' ); + + /** + * A node representing the outgoing light. + * + * @type {Node} + */ + this.outgoingLightNode = property( 'vec3', 'outgoingLight' ); + + /** + * An array representing the lights in the scene. + * + * @private + * @type {Array} + */ + this._lights = []; + + /** + * For each light in the scene, this node will create a + * corresponding light node. + * + * @private + * @type {?Array} + * @default null + */ + this._lightNodes = null; + + /** + * A hash for identifying the current light nodes setup. + * + * @private + * @type {?string} + * @default null + */ + this._lightNodesHash = null; + + /** + * `LightsNode` sets this property to `true` by default. + * + * @type {boolean} + * @default true + */ + this.global = true; + + } + + /** + * Overwrites the default {@link Node#customCacheKey} implementation by including + * light data into the cache key. + * + * @return {number} The custom cache key. + */ + customCacheKey() { + + const lights = this._lights; + + for ( let i = 0; i < lights.length; i ++ ) { + + const light = lights[ i ]; + + _hashData.push( light.id ); + _hashData.push( light.castShadow ? 1 : 0 ); + + if ( light.isSpotLight === true ) { + + const hashMap = ( light.map !== null ) ? light.map.id : -1; + const hashColorNode = ( light.colorNode ) ? light.colorNode.getCacheKey() : -1; + + _hashData.push( hashMap, hashColorNode ); + + } + + } + + const cacheKey = hashArray( _hashData ); + + _hashData.length = 0; + + return cacheKey; + + } + + /** + * Computes a hash value for identifying the current light nodes setup. + * + * @param {NodeBuilder} builder - A reference to the current node builder. + * @return {string} The computed hash. + */ + getHash( builder ) { + + if ( this._lightNodesHash === null ) { + + if ( this._lightNodes === null ) this.setupLightsNode( builder ); + + const hash = []; + + for ( const lightNode of this._lightNodes ) { + + hash.push( lightNode.getSelf().getHash() ); + + } + + this._lightNodesHash = 'lights-' + hash.join( ',' ); + + } + + return this._lightNodesHash; + + } + + analyze( builder ) { + + const properties = builder.getNodeProperties( this ); + + for ( const node of properties.nodes ) { + + node.build( builder ); + + } + + properties.outputNode.build( builder ); + + } + + /** + * Creates lighting nodes for each scene light. This makes it possible to further + * process lights in the node system. + * + * @param {NodeBuilder} builder - A reference to the current node builder. + */ + setupLightsNode( builder ) { + + const lightNodes = []; + + const previousLightNodes = this._lightNodes; + + const lights = sortLights( this._lights ); + const nodeLibrary = builder.renderer.library; + + for ( const light of lights ) { + + if ( light.isNode ) { + + lightNodes.push( nodeObject( light ) ); + + } else { + + let lightNode = null; + + if ( previousLightNodes !== null ) { + + lightNode = getLightNodeById( light.id, previousLightNodes ); // reuse existing light node + + } + + if ( lightNode === null ) { + + // find the corresponding node type for a given light + + const lightNodeClass = nodeLibrary.getLightNodeClass( light.constructor ); + + if ( lightNodeClass === null ) { + + console.warn( `LightsNode.setupNodeLights: Light node not found for ${ light.constructor.name }` ); + continue; + + } + + let lightNode = null; + + if ( ! _lightsNodeRef.has( light ) ) { + + lightNode = nodeObject( new lightNodeClass( light ) ); + _lightsNodeRef.set( light, lightNode ); + + } else { + + lightNode = _lightsNodeRef.get( light ); + + } + + lightNodes.push( lightNode ); + + } + + } + + } + + this._lightNodes = lightNodes; + + } + + /** + * Sets up a direct light in the lighting model. + * + * @param {Object} builder - The builder object containing the context and stack. + * @param {Object} lightNode - The light node. + * @param {Object} lightData - The light object containing color and direction properties. + */ + setupDirectLight( builder, lightNode, lightData ) { + + const { lightingModel, reflectedLight } = builder.context; + + lightingModel.direct( { + ...lightData, + lightNode, + reflectedLight + }, builder ); + + } + + setupDirectRectAreaLight( builder, lightNode, lightData ) { + + const { lightingModel, reflectedLight } = builder.context; + + lightingModel.directRectArea( { + ...lightData, + lightNode, + reflectedLight + }, builder ); + + } + + /** + * Setups the internal lights by building all respective + * light nodes. + * + * @param {NodeBuilder} builder - A reference to the current node builder. + * @param {Array} lightNodes - An array of lighting nodes. + */ + setupLights( builder, lightNodes ) { + + for ( const lightNode of lightNodes ) { + + lightNode.build( builder ); + + } + + } + + getLightNodes( builder ) { + + if ( this._lightNodes === null ) this.setupLightsNode( builder ); + + return this._lightNodes; + + } + + /** + * The implementation makes sure that for each light in the scene + * there is a corresponding light node. By building the light nodes + * and evaluating the lighting model the outgoing light is computed. + * + * @param {NodeBuilder} builder - A reference to the current node builder. + * @return {Node} A node representing the outgoing light. + */ + setup( builder ) { + + const currentLightsNode = builder.lightsNode; + + builder.lightsNode = this; + + // + + let outgoingLightNode = this.outgoingLightNode; + + const context = builder.context; + const lightingModel = context.lightingModel; + + const properties = builder.getNodeProperties( this ); + + if ( lightingModel ) { + + const { totalDiffuseNode, totalSpecularNode } = this; + + context.outgoingLight = outgoingLightNode; + + const stack = builder.addStack(); + + // + + properties.nodes = stack.nodes; + + // + + lightingModel.start( builder ); + + // + + const { backdrop, backdropAlpha } = context; + const { directDiffuse, directSpecular, indirectDiffuse, indirectSpecular } = context.reflectedLight; + + let totalDiffuse = directDiffuse.add( indirectDiffuse ); + + if ( backdrop !== null ) { + + if ( backdropAlpha !== null ) { + + totalDiffuse = vec3( backdropAlpha.mix( totalDiffuse, backdrop ) ); + + } else { + + totalDiffuse = vec3( backdrop ); + + } + + context.material.transparent = true; + + } + + totalDiffuseNode.assign( totalDiffuse ); + totalSpecularNode.assign( directSpecular.add( indirectSpecular ) ); + + outgoingLightNode.assign( totalDiffuseNode.add( totalSpecularNode ) ); + + // + + lightingModel.finish( builder ); + + // + + outgoingLightNode = outgoingLightNode.bypass( builder.removeStack() ); + + } else { + + properties.nodes = []; + + } + + // + + builder.lightsNode = currentLightsNode; + + return outgoingLightNode; + + } + + /** + * Configures this node with an array of lights. + * + * @param {Array} lights - An array of lights. + * @return {LightsNode} A reference to this node. + */ + setLights( lights ) { + + this._lights = lights; + + this._lightNodes = null; + this._lightNodesHash = null; + + return this; + + } + + /** + * Returns an array of the scene's lights. + * + * @return {Array} The scene's lights. + */ + getLights() { + + return this._lights; + + } + + /** + * Whether the scene has lights or not. + * + * @type {boolean} + */ + get hasLights() { + + return this._lights.length > 0; + + } + +} + +/** + * TSL function for creating an instance of `LightsNode` and configuring + * it with the given array of lights. + * + * @tsl + * @function + * @param {Array} lights - An array of lights. + * @return {LightsNode} The created lights node. + */ +const lights = ( lights = [] ) => nodeObject( new LightsNode() ).setLights( lights ); + +/** + * Base class for all shadow nodes. + * + * Shadow nodes encapsulate shadow related logic and are always coupled to lighting nodes. + * Lighting nodes might share the same shadow node type or use specific ones depending on + * their requirements. + * + * @augments Node + */ +class ShadowBaseNode extends Node { + + static get type() { + + return 'ShadowBaseNode'; + + } + + /** + * Constructs a new shadow base node. + * + * @param {Light} light - The shadow casting light. + */ + constructor( light ) { + + super(); + + /** + * The shadow casting light. + * + * @type {Light} + */ + this.light = light; + + /** + * Overwritten since shadows are updated by default per render. + * + * @type {string} + * @default 'render' + */ + this.updateBeforeType = NodeUpdateType.RENDER; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isShadowBaseNode = true; + + } + + /** + * Setups the shadow position node which is by default the predefined TSL node object `shadowPositionWorld`. + * + * @param {NodeBuilder} object - A configuration object that must at least hold a material reference. + */ + setupShadowPosition( { context, material } ) { + + // Use assign inside an Fn() + + shadowPositionWorld.assign( material.receivedShadowPositionNode || context.shadowPositionWorld || positionWorld ); + + } + +} + +/** + * TSL object that represents the vertex position in world space during the shadow pass. + * + * @tsl + * @type {Node} + */ +const shadowPositionWorld = /*@__PURE__*/ property( 'vec3', 'shadowPositionWorld' ); + +/** + * Saves the state of the given renderer and stores it into the given state object. + * + * If not state object is provided, the function creates one. + * + * @function + * @param {Renderer} renderer - The renderer. + * @param {Object} [state={}] - The state. + * @return {Object} The state. + */ +function saveRendererState( renderer, state = {} ) { + + state.toneMapping = renderer.toneMapping; + state.toneMappingExposure = renderer.toneMappingExposure; + state.outputColorSpace = renderer.outputColorSpace; + state.renderTarget = renderer.getRenderTarget(); + state.activeCubeFace = renderer.getActiveCubeFace(); + state.activeMipmapLevel = renderer.getActiveMipmapLevel(); + state.renderObjectFunction = renderer.getRenderObjectFunction(); + state.pixelRatio = renderer.getPixelRatio(); + state.mrt = renderer.getMRT(); + state.clearColor = renderer.getClearColor( state.clearColor || new Color() ); + state.clearAlpha = renderer.getClearAlpha(); + state.autoClear = renderer.autoClear; + state.scissorTest = renderer.getScissorTest(); + + return state; + +} + +/** + * Saves the state of the given renderer and stores it into the given state object. + * Besides, the function also resets the state of the renderer to its default values. + * + * If not state object is provided, the function creates one. + * + * @function + * @param {Renderer} renderer - The renderer. + * @param {Object} [state={}] - The state. + * @return {Object} The state. + */ +function resetRendererState( renderer, state ) { + + state = saveRendererState( renderer, state ); + + renderer.setMRT( null ); + renderer.setRenderObjectFunction( null ); + renderer.setClearColor( 0x000000, 1 ); + renderer.autoClear = true; + + return state; + +} + +/** + * Restores the state of the given renderer from the given state object. + * + * @function + * @param {Renderer} renderer - The renderer. + * @param {Object} state - The state to restore. + */ +function restoreRendererState( renderer, state ) { + + renderer.toneMapping = state.toneMapping; + renderer.toneMappingExposure = state.toneMappingExposure; + renderer.outputColorSpace = state.outputColorSpace; + renderer.setRenderTarget( state.renderTarget, state.activeCubeFace, state.activeMipmapLevel ); + renderer.setRenderObjectFunction( state.renderObjectFunction ); + renderer.setPixelRatio( state.pixelRatio ); + renderer.setMRT( state.mrt ); + renderer.setClearColor( state.clearColor, state.clearAlpha ); + renderer.autoClear = state.autoClear; + renderer.setScissorTest( state.scissorTest ); + +} + +/** + * Saves the state of the given scene and stores it into the given state object. + * + * If not state object is provided, the function creates one. + * + * @function + * @param {Scene} scene - The scene. + * @param {Object} [state={}] - The state. + * @return {Object} The state. + */ +function saveSceneState( scene, state = {} ) { + + state.background = scene.background; + state.backgroundNode = scene.backgroundNode; + state.overrideMaterial = scene.overrideMaterial; + + return state; + +} + +/** + * Saves the state of the given scene and stores it into the given state object. + * Besides, the function also resets the state of the scene to its default values. + * + * If not state object is provided, the function creates one. + * + * @function + * @param {Scene} scene - The scene. + * @param {Object} [state={}] - The state. + * @return {Object} The state. + */ +function resetSceneState( scene, state ) { + + state = saveSceneState( scene, state ); + + scene.background = null; + scene.backgroundNode = null; + scene.overrideMaterial = null; + + return state; + +} + +/** + * Restores the state of the given scene from the given state object. + * + * @function + * @param {Scene} scene - The scene. + * @param {Object} state - The state to restore. + */ +function restoreSceneState( scene, state ) { + + scene.background = state.background; + scene.backgroundNode = state.backgroundNode; + scene.overrideMaterial = state.overrideMaterial; + +} + +/** + * Saves the state of the given renderer and scene and stores it into the given state object. + * + * If not state object is provided, the function creates one. + * + * @function + * @param {Renderer} renderer - The renderer. + * @param {Scene} scene - The scene. + * @param {Object} [state={}] - The state. + * @return {Object} The state. + */ +function saveRendererAndSceneState( renderer, scene, state = {} ) { + + state = saveRendererState( renderer, state ); + state = saveSceneState( scene, state ); + + return state; + +} + +/** + * Saves the state of the given renderer and scene and stores it into the given state object. + * Besides, the function also resets the state of the renderer and scene to its default values. + * + * If not state object is provided, the function creates one. + * + * @function + * @param {Renderer} renderer - The renderer. + * @param {Scene} scene - The scene. + * @param {Object} [state={}] - The state. + * @return {Object} The state. + */ +function resetRendererAndSceneState( renderer, scene, state ) { + + state = resetRendererState( renderer, state ); + state = resetSceneState( scene, state ); + + return state; + +} + +/** + * Restores the state of the given renderer and scene from the given state object. + * + * @function + * @param {Renderer} renderer - The renderer. + * @param {Scene} scene - The scene. + * @param {Object} state - The state to restore. + */ +function restoreRendererAndSceneState( renderer, scene, state ) { + + restoreRendererState( renderer, state ); + restoreSceneState( scene, state ); + +} + +var RendererUtils = /*#__PURE__*/Object.freeze({ + __proto__: null, + resetRendererAndSceneState: resetRendererAndSceneState, + resetRendererState: resetRendererState, + resetSceneState: resetSceneState, + restoreRendererAndSceneState: restoreRendererAndSceneState, + restoreRendererState: restoreRendererState, + restoreSceneState: restoreSceneState, + saveRendererAndSceneState: saveRendererAndSceneState, + saveRendererState: saveRendererState, + saveSceneState: saveSceneState +}); + +const shadowMaterialLib = /*@__PURE__*/ new WeakMap(); + +/** + * A shadow filtering function performing basic filtering. This is in fact an unfiltered version of the shadow map + * with a binary `[0,1]` result. + * + * @method + * @param {Object} inputs - The input parameter object. + * @param {DepthTexture} inputs.depthTexture - A reference to the shadow map's texture data. + * @param {Node} inputs.shadowCoord - The shadow coordinates. + * @return {Node} The filtering result. + */ +const BasicShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, depthLayer } ) => { + + let basic = texture( depthTexture, shadowCoord.xy ).label( 't_basic' ); + + if ( depthTexture.isArrayTexture ) { + + basic = basic.depth( depthLayer ); + + } + + return basic.compare( shadowCoord.z ); + +} ); + +/** + * A shadow filtering function performing PCF filtering. + * + * @method + * @param {Object} inputs - The input parameter object. + * @param {DepthTexture} inputs.depthTexture - A reference to the shadow map's texture data. + * @param {Node} inputs.shadowCoord - The shadow coordinates. + * @param {LightShadow} inputs.shadow - The light shadow. + * @return {Node} The filtering result. + */ +const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, depthLayer } ) => { + + const depthCompare = ( uv, compare ) => { + + let depth = texture( depthTexture, uv ); + + if ( depthTexture.isArrayTexture ) { + + depth = depth.depth( depthLayer ); + + } + + return depth.compare( compare ); + + }; + + const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); + const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup ); + + const texelSize = vec2( 1 ).div( mapSize ); + const dx0 = texelSize.x.negate().mul( radius ); + const dy0 = texelSize.y.negate().mul( radius ); + const dx1 = texelSize.x.mul( radius ); + const dy1 = texelSize.y.mul( radius ); + const dx2 = dx0.div( 2 ); + const dy2 = dy0.div( 2 ); + const dx3 = dx1.div( 2 ); + const dy3 = dy1.div( 2 ); + + return add( + depthCompare( shadowCoord.xy.add( vec2( dx0, dy0 ) ), shadowCoord.z ), + depthCompare( shadowCoord.xy.add( vec2( 0, dy0 ) ), shadowCoord.z ), + depthCompare( shadowCoord.xy.add( vec2( dx1, dy0 ) ), shadowCoord.z ), + depthCompare( shadowCoord.xy.add( vec2( dx2, dy2 ) ), shadowCoord.z ), + depthCompare( shadowCoord.xy.add( vec2( 0, dy2 ) ), shadowCoord.z ), + depthCompare( shadowCoord.xy.add( vec2( dx3, dy2 ) ), shadowCoord.z ), + depthCompare( shadowCoord.xy.add( vec2( dx0, 0 ) ), shadowCoord.z ), + depthCompare( shadowCoord.xy.add( vec2( dx2, 0 ) ), shadowCoord.z ), + depthCompare( shadowCoord.xy, shadowCoord.z ), + depthCompare( shadowCoord.xy.add( vec2( dx3, 0 ) ), shadowCoord.z ), + depthCompare( shadowCoord.xy.add( vec2( dx1, 0 ) ), shadowCoord.z ), + depthCompare( shadowCoord.xy.add( vec2( dx2, dy3 ) ), shadowCoord.z ), + depthCompare( shadowCoord.xy.add( vec2( 0, dy3 ) ), shadowCoord.z ), + depthCompare( shadowCoord.xy.add( vec2( dx3, dy3 ) ), shadowCoord.z ), + depthCompare( shadowCoord.xy.add( vec2( dx0, dy1 ) ), shadowCoord.z ), + depthCompare( shadowCoord.xy.add( vec2( 0, dy1 ) ), shadowCoord.z ), + depthCompare( shadowCoord.xy.add( vec2( dx1, dy1 ) ), shadowCoord.z ) + ).mul( 1 / 17 ); + +} ); + +/** + * A shadow filtering function performing PCF soft filtering. + * + * @method + * @param {Object} inputs - The input parameter object. + * @param {DepthTexture} inputs.depthTexture - A reference to the shadow map's texture data. + * @param {Node} inputs.shadowCoord - The shadow coordinates. + * @param {LightShadow} inputs.shadow - The light shadow. + * @return {Node} The filtering result. + */ +const PCFSoftShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, depthLayer } ) => { + + const depthCompare = ( uv, compare ) => { + + let depth = texture( depthTexture, uv ); + + if ( depthTexture.isArrayTexture ) { + + depth = depth.depth( depthLayer ); + + } + + return depth.compare( compare ); + + }; + + + const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); + + const texelSize = vec2( 1 ).div( mapSize ); + const dx = texelSize.x; + const dy = texelSize.y; + + const uv = shadowCoord.xy; + const f = fract( uv.mul( mapSize ).add( 0.5 ) ); + uv.subAssign( f.mul( texelSize ) ); + + return add( + depthCompare( uv, shadowCoord.z ), + depthCompare( uv.add( vec2( dx, 0 ) ), shadowCoord.z ), + depthCompare( uv.add( vec2( 0, dy ) ), shadowCoord.z ), + depthCompare( uv.add( texelSize ), shadowCoord.z ), + mix( + depthCompare( uv.add( vec2( dx.negate(), 0 ) ), shadowCoord.z ), + depthCompare( uv.add( vec2( dx.mul( 2 ), 0 ) ), shadowCoord.z ), + f.x + ), + mix( + depthCompare( uv.add( vec2( dx.negate(), dy ) ), shadowCoord.z ), + depthCompare( uv.add( vec2( dx.mul( 2 ), dy ) ), shadowCoord.z ), + f.x + ), + mix( + depthCompare( uv.add( vec2( 0, dy.negate() ) ), shadowCoord.z ), + depthCompare( uv.add( vec2( 0, dy.mul( 2 ) ) ), shadowCoord.z ), + f.y + ), + mix( + depthCompare( uv.add( vec2( dx, dy.negate() ) ), shadowCoord.z ), + depthCompare( uv.add( vec2( dx, dy.mul( 2 ) ) ), shadowCoord.z ), + f.y + ), + mix( + mix( + depthCompare( uv.add( vec2( dx.negate(), dy.negate() ) ), shadowCoord.z ), + depthCompare( uv.add( vec2( dx.mul( 2 ), dy.negate() ) ), shadowCoord.z ), + f.x + ), + mix( + depthCompare( uv.add( vec2( dx.negate(), dy.mul( 2 ) ) ), shadowCoord.z ), + depthCompare( uv.add( vec2( dx.mul( 2 ), dy.mul( 2 ) ) ), shadowCoord.z ), + f.x + ), + f.y + ) + ).mul( 1 / 9 ); + +} ); + +/** + * A shadow filtering function performing VSM filtering. + * + * @method + * @param {Object} inputs - The input parameter object. + * @param {DepthTexture} inputs.depthTexture - A reference to the shadow map's texture data. + * @param {Node} inputs.shadowCoord - The shadow coordinates. + * @return {Node} The filtering result. + */ +const VSMShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, depthLayer } ) => { + + const occlusion = float( 1 ).toVar(); + + let distribution = texture( depthTexture ).sample( shadowCoord.xy ); + + if ( depthTexture.isArrayTexture ) { + + distribution = distribution.depth( depthLayer ); + + } + + distribution = distribution.rg; + + const hardShadow = step( shadowCoord.z, distribution.x ); + + If( hardShadow.notEqual( float( 1.0 ) ), () => { + + const distance = shadowCoord.z.sub( distribution.x ); + const variance = max$1( 0, distribution.y.mul( distribution.y ) ); + let softnessProbability = variance.div( variance.add( distance.mul( distance ) ) ); // Chebeyshevs inequality + softnessProbability = clamp( sub( softnessProbability, 0.3 ).div( 0.95 - 0.3 ) ); + occlusion.assign( clamp( max$1( hardShadow, softnessProbability ) ) ); + + } ); + + return occlusion; + +} ); + +// + +const linearDistance = /*@__PURE__*/ Fn( ( [ position, cameraNear, cameraFar ] ) => { + + let dist = positionWorld.sub( position ).length(); + dist = dist.sub( cameraNear ).div( cameraFar.sub( cameraNear ) ); + dist = dist.saturate(); // clamp to [ 0, 1 ] + + return dist; + +} ); + +const linearShadowDistance = ( light ) => { + + const camera = light.shadow.camera; + + const nearDistance = reference( 'near', 'float', camera ).setGroup( renderGroup ); + const farDistance = reference( 'far', 'float', camera ).setGroup( renderGroup ); + + const referencePosition = objectPosition( light ); + + return linearDistance( referencePosition, nearDistance, farDistance ); + +}; + +/** + * Retrieves or creates a shadow material for the given light source. + * + * This function checks if a shadow material already exists for the provided light. + * If not, it creates a new `NodeMaterial` configured for shadow rendering and stores it + * in the `shadowMaterialLib` for future use. + * + * @param {Light} light - The light source for which the shadow material is needed. + * If the light is a point light, a depth node is calculated + * using the linear shadow distance. + * @returns {NodeMaterial} The shadow material associated with the given light. + */ +const getShadowMaterial = ( light ) => { + + let material = shadowMaterialLib.get( light ); + + if ( material === undefined ) { + + const depthNode = light.isPointLight ? linearShadowDistance( light ) : null; + + material = new NodeMaterial(); + material.colorNode = vec4( 0, 0, 0, 1 ); + material.depthNode = depthNode; + material.isShadowPassMaterial = true; // Use to avoid other overrideMaterial override material.colorNode unintentionally when using material.shadowNode + material.name = 'ShadowMaterial'; + material.fog = false; + + shadowMaterialLib.set( light, material ); + + } + + return material; + +}; + +// + +const _shadowRenderObjectLibrary = /*@__PURE__*/ new ChainMap(); +const _shadowRenderObjectKeys = []; + +/** + * Creates a function to render shadow objects in a scene. + * + * @param {Renderer} renderer - The renderer. + * @param {LightShadow} shadow - The light shadow object containing shadow properties. + * @param {number} shadowType - The type of shadow map (e.g., BasicShadowMap). + * @param {boolean} useVelocity - Whether to use velocity data for rendering. + * @return {Function} A function that renders shadow objects. + * + * The returned function has the following parameters: + * @param {Object3D} object - The 3D object to render. + * @param {Scene} scene - The scene containing the object. + * @param {Camera} _camera - The camera used for rendering. + * @param {BufferGeometry} geometry - The geometry of the object. + * @param {Material} material - The material of the object. + * @param {Group} group - The group the object belongs to. + * @param {...any} params - Additional parameters for rendering. + */ +const getShadowRenderObjectFunction = ( renderer, shadow, shadowType, useVelocity ) => { + + _shadowRenderObjectKeys[ 0 ] = renderer; + _shadowRenderObjectKeys[ 1 ] = shadow; + + let renderObjectFunction = _shadowRenderObjectLibrary.get( _shadowRenderObjectKeys ); + + if ( renderObjectFunction === undefined || ( renderObjectFunction.shadowType !== shadowType || renderObjectFunction.useVelocity !== useVelocity ) ) { + + renderObjectFunction = ( object, scene, _camera, geometry, material, group, ...params ) => { + + if ( object.castShadow === true || ( object.receiveShadow && shadowType === VSMShadowMap ) ) { + + if ( useVelocity ) { + + getDataFromObject( object ).useVelocity = true; + + } + + object.onBeforeShadow( renderer, object, _camera, shadow.camera, geometry, scene.overrideMaterial, group ); + + renderer.renderObject( object, scene, _camera, geometry, material, group, ...params ); + + object.onAfterShadow( renderer, object, _camera, shadow.camera, geometry, scene.overrideMaterial, group ); + + } + + }; + + renderObjectFunction.shadowType = shadowType; + renderObjectFunction.useVelocity = useVelocity; + + _shadowRenderObjectLibrary.set( _shadowRenderObjectKeys, renderObjectFunction ); + + } + + _shadowRenderObjectKeys[ 0 ] = null; + _shadowRenderObjectKeys[ 1 ] = null; + + return renderObjectFunction; + +}; + +/** + * Represents the shader code for the first VSM render pass. + * + * @method + * @param {Object} inputs - The input parameter object. + * @param {Node} inputs.samples - The number of samples + * @param {Node} inputs.radius - The radius. + * @param {Node} inputs.size - The size. + * @param {TextureNode} inputs.shadowPass - A reference to the render target's depth data. + * @return {Node} The VSM output. + */ +const VSMPassVertical = /*@__PURE__*/ Fn( ( { samples, radius, size, shadowPass, depthLayer } ) => { + + const mean = float( 0 ).toVar( 'meanVertical' ); + const squaredMean = float( 0 ).toVar( 'squareMeanVertical' ); + + const uvStride = samples.lessThanEqual( float( 1 ) ).select( float( 0 ), float( 2 ).div( samples.sub( 1 ) ) ); + const uvStart = samples.lessThanEqual( float( 1 ) ).select( float( 0 ), float( -1 ) ); + + Loop( { start: int( 0 ), end: int( samples ), type: 'int', condition: '<' }, ( { i } ) => { + + const uvOffset = uvStart.add( float( i ).mul( uvStride ) ); + + let depth = shadowPass.sample( add( screenCoordinate.xy, vec2( 0, uvOffset ).mul( radius ) ).div( size ) ); + + if ( shadowPass.value.isArrayTexture ) { + + depth = depth.depth( depthLayer ); + + } + + depth = depth.x; + + mean.addAssign( depth ); + squaredMean.addAssign( depth.mul( depth ) ); + + } ); + + mean.divAssign( samples ); + squaredMean.divAssign( samples ); + + const std_dev = sqrt( squaredMean.sub( mean.mul( mean ) ) ); + return vec2( mean, std_dev ); + +} ); + +/** + * Represents the shader code for the second VSM render pass. + * + * @method + * @param {Object} inputs - The input parameter object. + * @param {Node} inputs.samples - The number of samples + * @param {Node} inputs.radius - The radius. + * @param {Node} inputs.size - The size. + * @param {TextureNode} inputs.shadowPass - The result of the first VSM render pass. + * @return {Node} The VSM output. + */ +const VSMPassHorizontal = /*@__PURE__*/ Fn( ( { samples, radius, size, shadowPass, depthLayer } ) => { + + const mean = float( 0 ).toVar( 'meanHorizontal' ); + const squaredMean = float( 0 ).toVar( 'squareMeanHorizontal' ); + + const uvStride = samples.lessThanEqual( float( 1 ) ).select( float( 0 ), float( 2 ).div( samples.sub( 1 ) ) ); + const uvStart = samples.lessThanEqual( float( 1 ) ).select( float( 0 ), float( -1 ) ); + + Loop( { start: int( 0 ), end: int( samples ), type: 'int', condition: '<' }, ( { i } ) => { + + const uvOffset = uvStart.add( float( i ).mul( uvStride ) ); + + let distribution = shadowPass.sample( add( screenCoordinate.xy, vec2( uvOffset, 0 ).mul( radius ) ).div( size ) ); + + if ( shadowPass.value.isArrayTexture ) { + + distribution = distribution.depth( depthLayer ); + + } + + mean.addAssign( distribution.x ); + squaredMean.addAssign( add( distribution.y.mul( distribution.y ), distribution.x.mul( distribution.x ) ) ); + + } ); + + mean.divAssign( samples ); + squaredMean.divAssign( samples ); + + const std_dev = sqrt( squaredMean.sub( mean.mul( mean ) ) ); + return vec2( mean, std_dev ); + +} ); + +const _shadowFilterLib = [ BasicShadowFilter, PCFShadowFilter, PCFSoftShadowFilter, VSMShadowFilter ]; + +// + +let _rendererState; +const _quadMesh = /*@__PURE__*/ new QuadMesh(); + +/** + * Represents the default shadow implementation for lighting nodes. + * + * @augments ShadowBaseNode + */ +class ShadowNode extends ShadowBaseNode { + + static get type() { + + return 'ShadowNode'; + + } + + /** + * Constructs a new shadow node. + * + * @param {Light} light - The shadow casting light. + * @param {?LightShadow} [shadow=null] - An optional light shadow. + */ + constructor( light, shadow = null ) { + + super( light ); + + /** + * The light shadow which defines the properties light's + * shadow. + * + * @type {?LightShadow} + * @default null + */ + this.shadow = shadow || light.shadow; + + /** + * A reference to the shadow map which is a render target. + * + * @type {?RenderTarget} + * @default null + */ + this.shadowMap = null; + + /** + * Only relevant for VSM shadows. Render target for the + * first VSM render pass. + * + * @type {?RenderTarget} + * @default null + */ + this.vsmShadowMapVertical = null; + + /** + * Only relevant for VSM shadows. Render target for the + * second VSM render pass. + * + * @type {?RenderTarget} + * @default null + */ + this.vsmShadowMapHorizontal = null; + + /** + * Only relevant for VSM shadows. Node material which + * is used to render the first VSM pass. + * + * @type {?NodeMaterial} + * @default null + */ + this.vsmMaterialVertical = null; + + /** + * Only relevant for VSM shadows. Node material which + * is used to render the second VSM pass. + * + * @type {?NodeMaterial} + * @default null + */ + this.vsmMaterialHorizontal = null; + + /** + * A reference to the output node which defines the + * final result of this shadow node. + * + * @type {?Node} + * @private + * @default null + */ + this._node = null; + + this._cameraFrameId = new WeakMap(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isShadowNode = true; + + /** + * This index can be used when overriding setupRenderTarget with a RenderTarget Array to specify the depth layer. + * + * @type {number} + * @readonly + * @default true + */ + this.depthLayer = 0; + + } + + /** + * Setups the shadow filtering. + * + * @param {NodeBuilder} builder - A reference to the current node builder. + * @param {Object} inputs - A configuration object that defines the shadow filtering. + * @param {Function} inputs.filterFn - This function defines the filtering type of the shadow map e.g. PCF. + * @param {DepthTexture} inputs.depthTexture - A reference to the shadow map's texture data. + * @param {Node} inputs.shadowCoord - Shadow coordinates which are used to sample from the shadow map. + * @param {LightShadow} inputs.shadow - The light shadow. + * @return {Node} The result node of the shadow filtering. + */ + setupShadowFilter( builder, { filterFn, depthTexture, shadowCoord, shadow, depthLayer } ) { + + const frustumTest = shadowCoord.x.greaterThanEqual( 0 ) + .and( shadowCoord.x.lessThanEqual( 1 ) ) + .and( shadowCoord.y.greaterThanEqual( 0 ) ) + .and( shadowCoord.y.lessThanEqual( 1 ) ) + .and( shadowCoord.z.lessThanEqual( 1 ) ); + + const shadowNode = filterFn( { depthTexture, shadowCoord, shadow, depthLayer } ); + + return frustumTest.select( shadowNode, float( 1 ) ); + + } + + /** + * Setups the shadow coordinates. + * + * @param {NodeBuilder} builder - A reference to the current node builder. + * @param {Node} shadowPosition - A node representing the shadow position. + * @return {Node} The shadow coordinates. + */ + setupShadowCoord( builder, shadowPosition ) { + + const { shadow } = this; + const { renderer } = builder; + + const bias = reference( 'bias', 'float', shadow ).setGroup( renderGroup ); + + let shadowCoord = shadowPosition; + let coordZ; + + if ( shadow.camera.isOrthographicCamera || renderer.logarithmicDepthBuffer !== true ) { + + shadowCoord = shadowCoord.xyz.div( shadowCoord.w ); + + coordZ = shadowCoord.z; + + if ( renderer.coordinateSystem === WebGPUCoordinateSystem ) { + + coordZ = coordZ.mul( 2 ).sub( 1 ); // WebGPU: Conversion [ 0, 1 ] to [ - 1, 1 ] + + } + + } else { + + const w = shadowCoord.w; + shadowCoord = shadowCoord.xy.div( w ); // <-- Only divide X/Y coords since we don't need Z + + // The normally available "cameraNear" and "cameraFar" nodes cannot be used here because they do not get + // updated to use the shadow camera. So, we have to declare our own "local" ones here. + // TODO: How do we get the cameraNear/cameraFar nodes to use the shadow camera so we don't have to declare local ones here? + const cameraNearLocal = reference( 'near', 'float', shadow.camera ).setGroup( renderGroup ); + const cameraFarLocal = reference( 'far', 'float', shadow.camera ).setGroup( renderGroup ); + + coordZ = viewZToLogarithmicDepth( w.negate(), cameraNearLocal, cameraFarLocal ); + + } + + shadowCoord = vec3( + shadowCoord.x, + shadowCoord.y.oneMinus(), // follow webgpu standards + coordZ.add( bias ) + ); + + return shadowCoord; + + } + + /** + * Returns the shadow filtering function for the given shadow type. + * + * @param {number} type - The shadow type. + * @return {Function} The filtering function. + */ + getShadowFilterFn( type ) { + + return _shadowFilterLib[ type ]; + + } + + + setupRenderTarget( shadow, builder ) { + + const depthTexture = new DepthTexture( shadow.mapSize.width, shadow.mapSize.height ); + depthTexture.name = 'ShadowDepthTexture'; + depthTexture.compareFunction = LessCompare; + + const shadowMap = builder.createRenderTarget( shadow.mapSize.width, shadow.mapSize.height ); + shadowMap.texture.name = 'ShadowMap'; + shadowMap.texture.type = shadow.mapType; + shadowMap.depthTexture = depthTexture; + + return { shadowMap, depthTexture }; + + } + + /** + * Setups the shadow output node. + * + * @param {NodeBuilder} builder - A reference to the current node builder. + * @return {Node} The shadow output node. + */ + setupShadow( builder ) { + + const { renderer } = builder; + + const { light, shadow } = this; + + const shadowMapType = renderer.shadowMap.type; + + const { depthTexture, shadowMap } = this.setupRenderTarget( shadow, builder ); + + shadow.camera.updateProjectionMatrix(); + + // VSM + + if ( shadowMapType === VSMShadowMap && shadow.isPointLightShadow !== true ) { + + depthTexture.compareFunction = null; // VSM does not use textureSampleCompare()/texture2DCompare() + + if ( shadowMap.depth > 1 ) { + + if ( ! shadowMap._vsmShadowMapVertical ) { + + shadowMap._vsmShadowMapVertical = builder.createRenderTarget( shadow.mapSize.width, shadow.mapSize.height, { format: RGFormat, type: HalfFloatType, depth: shadowMap.depth, depthBuffer: false } ); + shadowMap._vsmShadowMapVertical.texture.name = 'VSMVertical'; + + } + + this.vsmShadowMapVertical = shadowMap._vsmShadowMapVertical; + + if ( ! shadowMap._vsmShadowMapHorizontal ) { + + shadowMap._vsmShadowMapHorizontal = builder.createRenderTarget( shadow.mapSize.width, shadow.mapSize.height, { format: RGFormat, type: HalfFloatType, depth: shadowMap.depth, depthBuffer: false } ); + shadowMap._vsmShadowMapHorizontal.texture.name = 'VSMHorizontal'; + + } + + this.vsmShadowMapHorizontal = shadowMap._vsmShadowMapHorizontal; + + } else { + + this.vsmShadowMapVertical = builder.createRenderTarget( shadow.mapSize.width, shadow.mapSize.height, { format: RGFormat, type: HalfFloatType, depthBuffer: false } ); + this.vsmShadowMapHorizontal = builder.createRenderTarget( shadow.mapSize.width, shadow.mapSize.height, { format: RGFormat, type: HalfFloatType, depthBuffer: false } ); + + } + + + let shadowPassVertical = texture( depthTexture ); + + if ( depthTexture.isArrayTexture ) { + + shadowPassVertical = shadowPassVertical.depth( this.depthLayer ); + + } + + let shadowPassHorizontal = texture( this.vsmShadowMapVertical.texture ); + + if ( depthTexture.isArrayTexture ) { + + shadowPassHorizontal = shadowPassHorizontal.depth( this.depthLayer ); + + } + + const samples = reference( 'blurSamples', 'float', shadow ).setGroup( renderGroup ); + const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup ); + const size = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); + + let material = this.vsmMaterialVertical || ( this.vsmMaterialVertical = new NodeMaterial() ); + material.fragmentNode = VSMPassVertical( { samples, radius, size, shadowPass: shadowPassVertical, depthLayer: this.depthLayer } ).context( builder.getSharedContext() ); + material.name = 'VSMVertical'; + + material = this.vsmMaterialHorizontal || ( this.vsmMaterialHorizontal = new NodeMaterial() ); + material.fragmentNode = VSMPassHorizontal( { samples, radius, size, shadowPass: shadowPassHorizontal, depthLayer: this.depthLayer } ).context( builder.getSharedContext() ); + material.name = 'VSMHorizontal'; + + } + + // + + const shadowIntensity = reference( 'intensity', 'float', shadow ).setGroup( renderGroup ); + const normalBias = reference( 'normalBias', 'float', shadow ).setGroup( renderGroup ); + + const shadowPosition = lightShadowMatrix( light ).mul( shadowPositionWorld.add( normalWorld.mul( normalBias ) ) ); + const shadowCoord = this.setupShadowCoord( builder, shadowPosition ); + + // + + const filterFn = shadow.filterNode || this.getShadowFilterFn( renderer.shadowMap.type ) || null; + + if ( filterFn === null ) { + + throw new Error( 'THREE.WebGPURenderer: Shadow map type not supported yet.' ); + + } + + const shadowDepthTexture = ( shadowMapType === VSMShadowMap && shadow.isPointLightShadow !== true ) ? this.vsmShadowMapHorizontal.texture : depthTexture; + + const shadowNode = this.setupShadowFilter( builder, { filterFn, shadowTexture: shadowMap.texture, depthTexture: shadowDepthTexture, shadowCoord, shadow, depthLayer: this.depthLayer } ); + + let shadowColor = texture( shadowMap.texture, shadowCoord ); + + if ( depthTexture.isArrayTexture ) { + + shadowColor = shadowColor.depth( this.depthLayer ); + + } + + const shadowOutput = mix( 1, shadowNode.rgb.mix( shadowColor, 1 ), shadowIntensity.mul( shadowColor.a ) ).toVar(); + + this.shadowMap = shadowMap; + this.shadow.map = shadowMap; + + return shadowOutput; + + } + + /** + * The implementation performs the setup of the output node. An output is only + * produces if shadow mapping is globally enabled in the renderer. + * + * @param {NodeBuilder} builder - A reference to the current node builder. + * @return {ShaderCallNodeInternal} The output node. + */ + setup( builder ) { + + if ( builder.renderer.shadowMap.enabled === false ) return; + + return Fn( () => { + + let node = this._node; + + this.setupShadowPosition( builder ); + + if ( node === null ) { + + this._node = node = this.setupShadow( builder ); + + } + + if ( builder.material.shadowNode ) { // @deprecated, r171 + + console.warn( 'THREE.NodeMaterial: ".shadowNode" is deprecated. Use ".castShadowNode" instead.' ); + + } + + if ( builder.material.receivedShadowNode ) { + + node = builder.material.receivedShadowNode( node ); + + } + + return node; + + } )(); + + } + + /** + * Renders the shadow. The logic of this function could be included + * into {@link ShadowNode#updateShadow} however more specialized shadow + * nodes might require a custom shadow map rendering. By having a + * dedicated method, it's easier to overwrite the default behavior. + * + * @param {NodeFrame} frame - A reference to the current node frame. + */ + renderShadow( frame ) { + + const { shadow, shadowMap, light } = this; + const { renderer, scene } = frame; + + shadow.updateMatrices( light ); + + shadowMap.setSize( shadow.mapSize.width, shadow.mapSize.height, shadowMap.depth ); + + renderer.render( scene, shadow.camera ); + + } + + /** + * Updates the shadow. + * + * @param {NodeFrame} frame - A reference to the current node frame. + */ + updateShadow( frame ) { + + const { shadowMap, light, shadow } = this; + const { renderer, scene, camera } = frame; + + const shadowType = renderer.shadowMap.type; + + const depthVersion = shadowMap.depthTexture.version; + this._depthVersionCached = depthVersion; + + const _shadowCameraLayer = shadow.camera.layers.mask; + + if ( ( shadow.camera.layers.mask & 0xFFFFFFFE ) === 0 ) { + + shadow.camera.layers.mask = camera.layers.mask; + + } + + const currentRenderObjectFunction = renderer.getRenderObjectFunction(); + + const currentMRT = renderer.getMRT(); + const useVelocity = currentMRT ? currentMRT.has( 'velocity' ) : false; + + _rendererState = resetRendererAndSceneState( renderer, scene, _rendererState ); + + scene.overrideMaterial = getShadowMaterial( light ); + + renderer.setRenderObjectFunction( getShadowRenderObjectFunction( renderer, shadow, shadowType, useVelocity ) ); + + renderer.setClearColor( 0x000000, 0 ); + + renderer.setRenderTarget( shadowMap ); + + this.renderShadow( frame ); + + renderer.setRenderObjectFunction( currentRenderObjectFunction ); + + // vsm blur pass + + if ( shadowType === VSMShadowMap && shadow.isPointLightShadow !== true ) { + + this.vsmPass( renderer ); + + } + + shadow.camera.layers.mask = _shadowCameraLayer; + + restoreRendererAndSceneState( renderer, scene, _rendererState ); + + } + + /** + * For VSM additional render passes are required. + * + * @param {Renderer} renderer - A reference to the current renderer. + */ + vsmPass( renderer ) { + + const { shadow } = this; + + const depth = this.shadowMap.depth; + this.vsmShadowMapVertical.setSize( shadow.mapSize.width, shadow.mapSize.height, depth ); + this.vsmShadowMapHorizontal.setSize( shadow.mapSize.width, shadow.mapSize.height, depth ); + + renderer.setRenderTarget( this.vsmShadowMapVertical ); + _quadMesh.material = this.vsmMaterialVertical; + _quadMesh.render( renderer ); + + renderer.setRenderTarget( this.vsmShadowMapHorizontal ); + _quadMesh.material = this.vsmMaterialHorizontal; + _quadMesh.render( renderer ); + + } + + /** + * Frees the internal resources of this shadow node. + */ + dispose() { + + this.shadowMap.dispose(); + this.shadowMap = null; + + if ( this.vsmShadowMapVertical !== null ) { + + this.vsmShadowMapVertical.dispose(); + this.vsmShadowMapVertical = null; + + this.vsmMaterialVertical.dispose(); + this.vsmMaterialVertical = null; + + } + + if ( this.vsmShadowMapHorizontal !== null ) { + + this.vsmShadowMapHorizontal.dispose(); + this.vsmShadowMapHorizontal = null; + + this.vsmMaterialHorizontal.dispose(); + this.vsmMaterialHorizontal = null; + + } + + super.dispose(); + + } + + /** + * The implementation performs the update of the shadow map if necessary. + * + * @param {NodeFrame} frame - A reference to the current node frame. + */ + updateBefore( frame ) { + + const { shadow } = this; + + let needsUpdate = shadow.needsUpdate || shadow.autoUpdate; + + if ( needsUpdate ) { + + if ( this._cameraFrameId[ frame.camera ] === frame.frameId ) { + + needsUpdate = false; + + } + + this._cameraFrameId[ frame.camera ] = frame.frameId; + + } + + if ( needsUpdate ) { + + this.updateShadow( frame ); + + if ( this.shadowMap.depthTexture.version === this._depthVersionCached ) { + + shadow.needsUpdate = false; + + } + + } + + } + +} + +/** + * TSL function for creating an instance of `ShadowNode`. + * + * @tsl + * @function + * @param {Light} light - The shadow casting light. + * @param {?LightShadow} [shadow] - The light shadow. + * @return {ShadowNode} The created shadow node. + */ +const shadow = ( light, shadow ) => nodeObject( new ShadowNode( light, shadow ) ); + +const _clearColor$1 = /*@__PURE__*/ new Color(); + +// cubeToUV() maps a 3D direction vector suitable for cube texture mapping to a 2D +// vector suitable for 2D texture mapping. This code uses the following layout for the +// 2D texture: +// +// xzXZ +// y Y +// +// Y - Positive y direction +// y - Negative y direction +// X - Positive x direction +// x - Negative x direction +// Z - Positive z direction +// z - Negative z direction +// +// Source and test bed: +// https://gist.github.com/tschw/da10c43c467ce8afd0c4 + +const cubeToUV = /*@__PURE__*/ Fn( ( [ pos, texelSizeY ] ) => { + + const v = pos.toVar(); + + // Number of texels to avoid at the edge of each square + + const absV = abs( v ); + + // Intersect unit cube + + const scaleToCube = div( 1.0, max$1( absV.x, max$1( absV.y, absV.z ) ) ); + absV.mulAssign( scaleToCube ); + + // Apply scale to avoid seams + + // two texels less per square (one texel will do for NEAREST) + v.mulAssign( scaleToCube.mul( texelSizeY.mul( 2 ).oneMinus() ) ); + + // Unwrap + + // space: -1 ... 1 range for each square + // + // #X## dim := ( 4 , 2 ) + // # # center := ( 1 , 1 ) + + const planar = vec2( v.xy ).toVar(); + + const almostATexel = texelSizeY.mul( 1.5 ); + const almostOne = almostATexel.oneMinus(); + + If( absV.z.greaterThanEqual( almostOne ), () => { + + If( v.z.greaterThan( 0.0 ), () => { + + planar.x.assign( sub( 4.0, v.x ) ); + + } ); + + } ).ElseIf( absV.x.greaterThanEqual( almostOne ), () => { + + const signX = sign( v.x ); + planar.x.assign( v.z.mul( signX ).add( signX.mul( 2.0 ) ) ); + + } ).ElseIf( absV.y.greaterThanEqual( almostOne ), () => { + + const signY = sign( v.y ); + planar.x.assign( v.x.add( signY.mul( 2.0 ) ).add( 2.0 ) ); + planar.y.assign( v.z.mul( signY ).sub( 2.0 ) ); + + } ); + + // Transform to UV space + + // scale := 0.5 / dim + // translate := ( center + 0.5 ) / dim + return vec2( 0.125, 0.25 ).mul( planar ).add( vec2( 0.375, 0.75 ) ).flipY(); + +} ).setLayout( { + name: 'cubeToUV', + type: 'vec2', + inputs: [ + { name: 'pos', type: 'vec3' }, + { name: 'texelSizeY', type: 'float' } + ] +} ); + +const BasicPointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp, texelSize } ) => { + + return texture( depthTexture, cubeToUV( bd3D, texelSize.y ) ).compare( dp ); + +} ); + +const PointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp, texelSize, shadow } ) => { + + const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup ); + const offset = vec2( -1, 1.0 ).mul( radius ).mul( texelSize.y ); + + return texture( depthTexture, cubeToUV( bd3D.add( offset.xyy ), texelSize.y ) ).compare( dp ) + .add( texture( depthTexture, cubeToUV( bd3D.add( offset.yyy ), texelSize.y ) ).compare( dp ) ) + .add( texture( depthTexture, cubeToUV( bd3D.add( offset.xyx ), texelSize.y ) ).compare( dp ) ) + .add( texture( depthTexture, cubeToUV( bd3D.add( offset.yyx ), texelSize.y ) ).compare( dp ) ) + .add( texture( depthTexture, cubeToUV( bd3D, texelSize.y ) ).compare( dp ) ) + .add( texture( depthTexture, cubeToUV( bd3D.add( offset.xxy ), texelSize.y ) ).compare( dp ) ) + .add( texture( depthTexture, cubeToUV( bd3D.add( offset.yxy ), texelSize.y ) ).compare( dp ) ) + .add( texture( depthTexture, cubeToUV( bd3D.add( offset.xxx ), texelSize.y ) ).compare( dp ) ) + .add( texture( depthTexture, cubeToUV( bd3D.add( offset.yxx ), texelSize.y ) ).compare( dp ) ) + .mul( 1.0 / 9.0 ); + +} ); + +const pointShadowFilter = /*@__PURE__*/ Fn( ( { filterFn, depthTexture, shadowCoord, shadow } ) => { + + // for point lights, the uniform @vShadowCoord is re-purposed to hold + // the vector from the light to the world-space position of the fragment. + const lightToPosition = shadowCoord.xyz.toVar(); + const lightToPositionLength = lightToPosition.length(); + + const cameraNearLocal = uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.near ); + const cameraFarLocal = uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.far ); + const bias = reference( 'bias', 'float', shadow ).setGroup( renderGroup ); + const mapSize = uniform( shadow.mapSize ).setGroup( renderGroup ); + + const result = float( 1.0 ).toVar(); + + If( lightToPositionLength.sub( cameraFarLocal ).lessThanEqual( 0.0 ).and( lightToPositionLength.sub( cameraNearLocal ).greaterThanEqual( 0.0 ) ), () => { + + // dp = normalized distance from light to fragment position + const dp = lightToPositionLength.sub( cameraNearLocal ).div( cameraFarLocal.sub( cameraNearLocal ) ).toVar(); // need to clamp? + dp.addAssign( bias ); + + // bd3D = base direction 3D + const bd3D = lightToPosition.normalize(); + const texelSize = vec2( 1.0 ).div( mapSize.mul( vec2( 4.0, 2.0 ) ) ); + + // percentage-closer filtering + result.assign( filterFn( { depthTexture, bd3D, dp, texelSize, shadow } ) ); + + } ); + + return result; + +} ); + +const _viewport = /*@__PURE__*/ new Vector4(); +const _viewportSize = /*@__PURE__*/ new Vector2(); +const _shadowMapSize = /*@__PURE__*/ new Vector2(); + + +/** + * Represents the shadow implementation for point light nodes. + * + * @augments ShadowNode + */ +class PointShadowNode extends ShadowNode { + + static get type() { + + return 'PointShadowNode'; + + } + + /** + * Constructs a new point shadow node. + * + * @param {PointLight} light - The shadow casting point light. + * @param {?PointLightShadow} [shadow=null] - An optional point light shadow. + */ + constructor( light, shadow = null ) { + + super( light, shadow ); + + } + + /** + * Overwrites the default implementation to return point light shadow specific + * filtering functions. + * + * @param {number} type - The shadow type. + * @return {Function} The filtering function. + */ + getShadowFilterFn( type ) { + + return type === BasicShadowMap ? BasicPointShadowFilter : PointShadowFilter; + + } + + /** + * Overwrites the default implementation so the unaltered shadow position is used. + * + * @param {NodeBuilder} builder - A reference to the current node builder. + * @param {Node} shadowPosition - A node representing the shadow position. + * @return {Node} The shadow coordinates. + */ + setupShadowCoord( builder, shadowPosition ) { + + return shadowPosition; + + } + + /** + * Overwrites the default implementation to only use point light specific + * shadow filter functions. + * + * @param {NodeBuilder} builder - A reference to the current node builder. + * @param {Object} inputs - A configuration object that defines the shadow filtering. + * @param {Function} inputs.filterFn - This function defines the filtering type of the shadow map e.g. PCF. + * @param {Texture} inputs.shadowTexture - A reference to the shadow map's texture. + * @param {DepthTexture} inputs.depthTexture - A reference to the shadow map's texture data. + * @param {Node} inputs.shadowCoord - Shadow coordinates which are used to sample from the shadow map. + * @param {LightShadow} inputs.shadow - The light shadow. + * @return {Node} The result node of the shadow filtering. + */ + setupShadowFilter( builder, { filterFn, shadowTexture, depthTexture, shadowCoord, shadow } ) { + + return pointShadowFilter( { filterFn, shadowTexture, depthTexture, shadowCoord, shadow } ); + + } + + /** + * Overwrites the default implementation with point light specific + * rendering code. + * + * @param {NodeFrame} frame - A reference to the current node frame. + */ + renderShadow( frame ) { + + const { shadow, shadowMap, light } = this; + const { renderer, scene } = frame; + + const shadowFrameExtents = shadow.getFrameExtents(); + + _shadowMapSize.copy( shadow.mapSize ); + _shadowMapSize.multiply( shadowFrameExtents ); + + shadowMap.setSize( _shadowMapSize.width, _shadowMapSize.height ); + + _viewportSize.copy( shadow.mapSize ); + + // + + const previousAutoClear = renderer.autoClear; + + const previousClearColor = renderer.getClearColor( _clearColor$1 ); + const previousClearAlpha = renderer.getClearAlpha(); + + renderer.autoClear = false; + renderer.setClearColor( shadow.clearColor, shadow.clearAlpha ); + renderer.clear(); + + const viewportCount = shadow.getViewportCount(); + + for ( let vp = 0; vp < viewportCount; vp ++ ) { + + const viewport = shadow.getViewport( vp ); + + const x = _viewportSize.x * viewport.x; + const y = _shadowMapSize.y - _viewportSize.y - ( _viewportSize.y * viewport.y ); + + _viewport.set( + x, + y, + _viewportSize.x * viewport.z, + _viewportSize.y * viewport.w + ); + + shadowMap.viewport.copy( _viewport ); + + shadow.updateMatrices( light, vp ); + + renderer.render( scene, shadow.camera ); + + } + + // + + renderer.autoClear = previousAutoClear; + renderer.setClearColor( previousClearColor, previousClearAlpha ); + + } + +} + +/** + * TSL function for creating an instance of `PointShadowNode`. + * + * @tsl + * @function + * @param {PointLight} light - The shadow casting point light. + * @param {?PointLightShadow} [shadow=null] - An optional point light shadow. + * @return {PointShadowNode} The created point shadow node. + */ +const pointShadow = ( light, shadow ) => nodeObject( new PointShadowNode( light, shadow ) ); + +/** + * Base class for analytic light nodes. + * + * @augments LightingNode + */ +class AnalyticLightNode extends LightingNode { + + static get type() { + + return 'AnalyticLightNode'; + + } + + /** + * Constructs a new analytic light node. + * + * @param {?Light} [light=null] - The light source. + */ + constructor( light = null ) { + + super(); + + /** + * The light source. + * + * @type {?Light} + * @default null + */ + this.light = light; + + /** + * The light's color value. + * + * @type {Color} + */ + this.color = new Color(); + + /** + * The light's color node. Points to `colorNode` of the light source, if set. Otherwise + * it creates a uniform node based on {@link AnalyticLightNode#color}. + * + * @type {Node} + */ + this.colorNode = ( light && light.colorNode ) || uniform( this.color ).setGroup( renderGroup ); + + /** + * This property is used to retain a reference to the original value of {@link AnalyticLightNode#colorNode}. + * The final color node is represented by a different node when using shadows. + * + * @type {?Node} + * @default null + */ + this.baseColorNode = null; + + /** + * Represents the light's shadow. + * + * @type {?ShadowNode} + * @default null + */ + this.shadowNode = null; + + /** + * Represents the light's shadow color. + * + * @type {?Node} + * @default null + */ + this.shadowColorNode = null; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isAnalyticLightNode = true; + + /** + * Overwritten since analytic light nodes are updated + * once per frame. + * + * @type {string} + * @default 'frame' + */ + this.updateType = NodeUpdateType.FRAME; + + } + + getHash() { + + return this.light.uuid; + + } + + /** + * Returns a node representing a direction vector which points from the current + * position in view space to the light's position in view space. + * + * @param {NodeBuilder} builder - The builder object used for setting up the light. + * @return {Node} The light vector node. + */ + getLightVector( builder ) { + + return lightViewPosition( this.light ).sub( builder.context.positionView || positionView ); + + } + + /** + * Sets up the direct lighting for the analytic light node. + * + * @abstract + * @param {NodeBuilder} builder - The builder object used for setting up the light. + * @return {Object|undefined} The direct light data (color and direction). + */ + setupDirect( /*builder*/ ) { } + + /** + * Sets up the direct rect area lighting for the analytic light node. + * + * @abstract + * @param {NodeBuilder} builder - The builder object used for setting up the light. + * @return {Object|undefined} The direct rect area light data. + */ + setupDirectRectArea( /*builder*/ ) { } + + /** + * Setups the shadow node for this light. The method exists so concrete light classes + * can setup different types of shadow nodes. + * + * @return {ShadowNode} The created shadow node. + */ + setupShadowNode() { + + return shadow( this.light ); + + } + + /** + * Setups the shadow for this light. This method is only executed if the light + * cast shadows and the current build object receives shadows. It incorporates + * shadows into the lighting computation. + * + * @param {NodeBuilder} builder - The current node builder. + */ + setupShadow( builder ) { + + const { renderer } = builder; + + if ( renderer.shadowMap.enabled === false ) return; + + let shadowColorNode = this.shadowColorNode; + + if ( shadowColorNode === null ) { + + const customShadowNode = this.light.shadow.shadowNode; + + let shadowNode; + + if ( customShadowNode !== undefined ) { + + shadowNode = nodeObject( customShadowNode ); + + } else { + + shadowNode = this.setupShadowNode(); + + } + + this.shadowNode = shadowNode; + + this.shadowColorNode = shadowColorNode = this.colorNode.mul( shadowNode ); + + this.baseColorNode = this.colorNode; + + } + + // + + this.colorNode = shadowColorNode; + + } + + /** + * Unlike most other nodes, lighting nodes do not return a output node in {@link Node#setup}. + * The main purpose of lighting nodes is to configure the current {@link LightingModel} and/or + * invocate the respective interface methods. + * + * @param {NodeBuilder} builder - The current node builder. + */ + setup( builder ) { + + this.colorNode = this.baseColorNode || this.colorNode; + + if ( this.light.castShadow ) { + + if ( builder.object.receiveShadow ) { + + this.setupShadow( builder ); + + } + + } else if ( this.shadowNode !== null ) { + + this.shadowNode.dispose(); + this.shadowNode = null; + this.shadowColorNode = null; + + } + + const directLightData = this.setupDirect( builder ); + const directRectAreaLightData = this.setupDirectRectArea( builder ); + + if ( directLightData ) { + + builder.lightsNode.setupDirectLight( builder, this, directLightData ); + + } + + if ( directRectAreaLightData ) { + + builder.lightsNode.setupDirectRectAreaLight( builder, this, directRectAreaLightData ); + + } + + } + + /** + * The update method is used to update light uniforms per frame. + * Potentially overwritten in concrete light nodes to update light + * specific uniforms. + * + * @param {NodeFrame} frame - A reference to the current node frame. + */ + update( /*frame*/ ) { + + const { light } = this; + + this.color.copy( light.color ).multiplyScalar( light.intensity ); + + } + +} + +/** + * Represents a `discard` shader operation in TSL. + * + * @method + * @param {Object} inputs - The input parameter object. + * @param {Node} inputs.lightDistance - The distance of the light's position to the current fragment position. + * @param {Node} inputs.cutoffDistance - The light's cutoff distance. + * @param {Node} inputs.decayExponent - The light's decay exponent. + * @return {Node} The distance falloff. + */ +const getDistanceAttenuation = /*@__PURE__*/ Fn( ( { lightDistance, cutoffDistance, decayExponent } ) => { + + // based upon Frostbite 3 Moving to Physically-based Rendering + // page 32, equation 26: E[window1] + // https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf + const distanceFalloff = lightDistance.pow( decayExponent ).max( 0.01 ).reciprocal(); + + return cutoffDistance.greaterThan( 0 ).select( + distanceFalloff.mul( lightDistance.div( cutoffDistance ).pow4().oneMinus().clamp().pow2() ), + distanceFalloff + ); + +} ); // validated + +const directPointLight = ( { color, lightVector, cutoffDistance, decayExponent } ) => { + + const lightDirection = lightVector.normalize(); + const lightDistance = lightVector.length(); + + const attenuation = getDistanceAttenuation( { + lightDistance, + cutoffDistance, + decayExponent + } ); + + const lightColor = color.mul( attenuation ); + + return { lightDirection, lightColor }; + +}; + +/** + * Module for representing point lights as nodes. + * + * @augments AnalyticLightNode + */ +class PointLightNode extends AnalyticLightNode { + + static get type() { + + return 'PointLightNode'; + + } + + /** + * Constructs a new point light node. + * + * @param {?PointLight} [light=null] - The point light source. + */ + constructor( light = null ) { + + super( light ); + + /** + * Uniform node representing the cutoff distance. + * + * @type {UniformNode} + */ + this.cutoffDistanceNode = uniform( 0 ).setGroup( renderGroup ); + + /** + * Uniform node representing the decay exponent. + * + * @type {UniformNode} + */ + this.decayExponentNode = uniform( 2 ).setGroup( renderGroup ); + + } + + /** + * Overwritten to updated point light specific uniforms. + * + * @param {NodeFrame} frame - A reference to the current node frame. + */ + update( frame ) { + + const { light } = this; + + super.update( frame ); + + this.cutoffDistanceNode.value = light.distance; + this.decayExponentNode.value = light.decay; + + } + + /** + * Overwritten to setup point light specific shadow. + * + * @return {PointShadowNode} + */ + setupShadowNode() { + + return pointShadow( this.light ); + + } + + setupDirect( builder ) { + + return directPointLight( { + color: this.colorNode, + lightVector: this.getLightVector( builder ), + cutoffDistance: this.cutoffDistanceNode, + decayExponent: this.decayExponentNode + } ); + + } + +} + +/** + * Creates a 2x2 checkerboard pattern that can be used as procedural texture data. + * + * @tsl + * @function + * @param {Node} coord - The uv coordinates. + * @return {Node} The result data. + */ +const checker = /*@__PURE__*/ Fn( ( [ coord = uv$1() ] ) => { + + const uv = coord.mul( 2.0 ); + + const cx = uv.x.floor(); + const cy = uv.y.floor(); + const result = cx.add( cy ).mod( 2.0 ); + + return result.sign(); + +} ); + +/** + * Generates a circle based on the uv coordinates. + * + * @tsl + * @function + * @param {Node} coord - The uv to generate the circle. + * @return {Node} The circle shape. + */ +const shapeCircle = Fn( ( [ coord = uv$1() ], { renderer, material } ) => { + + const len2 = lengthSq( coord.mul( 2 ).sub( 1 ) ); + + let alpha; + + if ( material.alphaToCoverage && renderer.samples > 1 ) { + + const dlen = float( len2.fwidth() ).toVar(); + + alpha = smoothstep( dlen.oneMinus(), dlen.add( 1 ), len2 ).oneMinus(); + + } else { + + alpha = select( len2.greaterThan( 1.0 ), 0, 1 ); + + } + + return alpha; + +} ); + +// Three.js Transpiler +// https://raw.githubusercontent.com/AcademySoftwareFoundation/MaterialX/main/libraries/stdlib/genglsl/lib/mx_noise.glsl + + + +const mx_select = /*@__PURE__*/ Fn( ( [ b_immutable, t_immutable, f_immutable ] ) => { + + const f = float( f_immutable ).toVar(); + const t = float( t_immutable ).toVar(); + const b = bool( b_immutable ).toVar(); + + return select( b, t, f ); + +} ).setLayout( { + name: 'mx_select', + type: 'float', + inputs: [ + { name: 'b', type: 'bool' }, + { name: 't', type: 'float' }, + { name: 'f', type: 'float' } + ] +} ); + +const mx_negate_if = /*@__PURE__*/ Fn( ( [ val_immutable, b_immutable ] ) => { + + const b = bool( b_immutable ).toVar(); + const val = float( val_immutable ).toVar(); + + return select( b, val.negate(), val ); + +} ).setLayout( { + name: 'mx_negate_if', + type: 'float', + inputs: [ + { name: 'val', type: 'float' }, + { name: 'b', type: 'bool' } + ] +} ); + +const mx_floor = /*@__PURE__*/ Fn( ( [ x_immutable ] ) => { + + const x = float( x_immutable ).toVar(); + + return int( floor( x ) ); + +} ).setLayout( { + name: 'mx_floor', + type: 'int', + inputs: [ + { name: 'x', type: 'float' } + ] +} ); + +const mx_floorfrac = /*@__PURE__*/ Fn( ( [ x_immutable, i ] ) => { + + const x = float( x_immutable ).toVar(); + i.assign( mx_floor( x ) ); + + return x.sub( float( i ) ); + +} ); + +const mx_bilerp_0 = /*@__PURE__*/ Fn( ( [ v0_immutable, v1_immutable, v2_immutable, v3_immutable, s_immutable, t_immutable ] ) => { + + const t = float( t_immutable ).toVar(); + const s = float( s_immutable ).toVar(); + const v3 = float( v3_immutable ).toVar(); + const v2 = float( v2_immutable ).toVar(); + const v1 = float( v1_immutable ).toVar(); + const v0 = float( v0_immutable ).toVar(); + const s1 = float( sub( 1.0, s ) ).toVar(); + + return sub( 1.0, t ).mul( v0.mul( s1 ).add( v1.mul( s ) ) ).add( t.mul( v2.mul( s1 ).add( v3.mul( s ) ) ) ); + +} ).setLayout( { + name: 'mx_bilerp_0', + type: 'float', + inputs: [ + { name: 'v0', type: 'float' }, + { name: 'v1', type: 'float' }, + { name: 'v2', type: 'float' }, + { name: 'v3', type: 'float' }, + { name: 's', type: 'float' }, + { name: 't', type: 'float' } + ] +} ); + +const mx_bilerp_1 = /*@__PURE__*/ Fn( ( [ v0_immutable, v1_immutable, v2_immutable, v3_immutable, s_immutable, t_immutable ] ) => { + + const t = float( t_immutable ).toVar(); + const s = float( s_immutable ).toVar(); + const v3 = vec3( v3_immutable ).toVar(); + const v2 = vec3( v2_immutable ).toVar(); + const v1 = vec3( v1_immutable ).toVar(); + const v0 = vec3( v0_immutable ).toVar(); + const s1 = float( sub( 1.0, s ) ).toVar(); + + return sub( 1.0, t ).mul( v0.mul( s1 ).add( v1.mul( s ) ) ).add( t.mul( v2.mul( s1 ).add( v3.mul( s ) ) ) ); + +} ).setLayout( { + name: 'mx_bilerp_1', + type: 'vec3', + inputs: [ + { name: 'v0', type: 'vec3' }, + { name: 'v1', type: 'vec3' }, + { name: 'v2', type: 'vec3' }, + { name: 'v3', type: 'vec3' }, + { name: 's', type: 'float' }, + { name: 't', type: 'float' } + ] +} ); + +const mx_bilerp = /*@__PURE__*/ overloadingFn( [ mx_bilerp_0, mx_bilerp_1 ] ); + +const mx_trilerp_0 = /*@__PURE__*/ Fn( ( [ v0_immutable, v1_immutable, v2_immutable, v3_immutable, v4_immutable, v5_immutable, v6_immutable, v7_immutable, s_immutable, t_immutable, r_immutable ] ) => { + + const r = float( r_immutable ).toVar(); + const t = float( t_immutable ).toVar(); + const s = float( s_immutable ).toVar(); + const v7 = float( v7_immutable ).toVar(); + const v6 = float( v6_immutable ).toVar(); + const v5 = float( v5_immutable ).toVar(); + const v4 = float( v4_immutable ).toVar(); + const v3 = float( v3_immutable ).toVar(); + const v2 = float( v2_immutable ).toVar(); + const v1 = float( v1_immutable ).toVar(); + const v0 = float( v0_immutable ).toVar(); + const s1 = float( sub( 1.0, s ) ).toVar(); + const t1 = float( sub( 1.0, t ) ).toVar(); + const r1 = float( sub( 1.0, r ) ).toVar(); + + return r1.mul( t1.mul( v0.mul( s1 ).add( v1.mul( s ) ) ).add( t.mul( v2.mul( s1 ).add( v3.mul( s ) ) ) ) ).add( r.mul( t1.mul( v4.mul( s1 ).add( v5.mul( s ) ) ).add( t.mul( v6.mul( s1 ).add( v7.mul( s ) ) ) ) ) ); + +} ).setLayout( { + name: 'mx_trilerp_0', + type: 'float', + inputs: [ + { name: 'v0', type: 'float' }, + { name: 'v1', type: 'float' }, + { name: 'v2', type: 'float' }, + { name: 'v3', type: 'float' }, + { name: 'v4', type: 'float' }, + { name: 'v5', type: 'float' }, + { name: 'v6', type: 'float' }, + { name: 'v7', type: 'float' }, + { name: 's', type: 'float' }, + { name: 't', type: 'float' }, + { name: 'r', type: 'float' } + ] +} ); + +const mx_trilerp_1 = /*@__PURE__*/ Fn( ( [ v0_immutable, v1_immutable, v2_immutable, v3_immutable, v4_immutable, v5_immutable, v6_immutable, v7_immutable, s_immutable, t_immutable, r_immutable ] ) => { + + const r = float( r_immutable ).toVar(); + const t = float( t_immutable ).toVar(); + const s = float( s_immutable ).toVar(); + const v7 = vec3( v7_immutable ).toVar(); + const v6 = vec3( v6_immutable ).toVar(); + const v5 = vec3( v5_immutable ).toVar(); + const v4 = vec3( v4_immutable ).toVar(); + const v3 = vec3( v3_immutable ).toVar(); + const v2 = vec3( v2_immutable ).toVar(); + const v1 = vec3( v1_immutable ).toVar(); + const v0 = vec3( v0_immutable ).toVar(); + const s1 = float( sub( 1.0, s ) ).toVar(); + const t1 = float( sub( 1.0, t ) ).toVar(); + const r1 = float( sub( 1.0, r ) ).toVar(); + + return r1.mul( t1.mul( v0.mul( s1 ).add( v1.mul( s ) ) ).add( t.mul( v2.mul( s1 ).add( v3.mul( s ) ) ) ) ).add( r.mul( t1.mul( v4.mul( s1 ).add( v5.mul( s ) ) ).add( t.mul( v6.mul( s1 ).add( v7.mul( s ) ) ) ) ) ); + +} ).setLayout( { + name: 'mx_trilerp_1', + type: 'vec3', + inputs: [ + { name: 'v0', type: 'vec3' }, + { name: 'v1', type: 'vec3' }, + { name: 'v2', type: 'vec3' }, + { name: 'v3', type: 'vec3' }, + { name: 'v4', type: 'vec3' }, + { name: 'v5', type: 'vec3' }, + { name: 'v6', type: 'vec3' }, + { name: 'v7', type: 'vec3' }, + { name: 's', type: 'float' }, + { name: 't', type: 'float' }, + { name: 'r', type: 'float' } + ] +} ); + +const mx_trilerp = /*@__PURE__*/ overloadingFn( [ mx_trilerp_0, mx_trilerp_1 ] ); + +const mx_gradient_float_0 = /*@__PURE__*/ Fn( ( [ hash_immutable, x_immutable, y_immutable ] ) => { + + const y = float( y_immutable ).toVar(); + const x = float( x_immutable ).toVar(); + const hash = uint( hash_immutable ).toVar(); + const h = uint( hash.bitAnd( uint( 7 ) ) ).toVar(); + const u = float( mx_select( h.lessThan( uint( 4 ) ), x, y ) ).toVar(); + const v = float( mul( 2.0, mx_select( h.lessThan( uint( 4 ) ), y, x ) ) ).toVar(); + + return mx_negate_if( u, bool( h.bitAnd( uint( 1 ) ) ) ).add( mx_negate_if( v, bool( h.bitAnd( uint( 2 ) ) ) ) ); + +} ).setLayout( { + name: 'mx_gradient_float_0', + type: 'float', + inputs: [ + { name: 'hash', type: 'uint' }, + { name: 'x', type: 'float' }, + { name: 'y', type: 'float' } + ] +} ); + +const mx_gradient_float_1 = /*@__PURE__*/ Fn( ( [ hash_immutable, x_immutable, y_immutable, z_immutable ] ) => { + + const z = float( z_immutable ).toVar(); + const y = float( y_immutable ).toVar(); + const x = float( x_immutable ).toVar(); + const hash = uint( hash_immutable ).toVar(); + const h = uint( hash.bitAnd( uint( 15 ) ) ).toVar(); + const u = float( mx_select( h.lessThan( uint( 8 ) ), x, y ) ).toVar(); + const v = float( mx_select( h.lessThan( uint( 4 ) ), y, mx_select( h.equal( uint( 12 ) ).or( h.equal( uint( 14 ) ) ), x, z ) ) ).toVar(); + + return mx_negate_if( u, bool( h.bitAnd( uint( 1 ) ) ) ).add( mx_negate_if( v, bool( h.bitAnd( uint( 2 ) ) ) ) ); + +} ).setLayout( { + name: 'mx_gradient_float_1', + type: 'float', + inputs: [ + { name: 'hash', type: 'uint' }, + { name: 'x', type: 'float' }, + { name: 'y', type: 'float' }, + { name: 'z', type: 'float' } + ] +} ); + +const mx_gradient_float = /*@__PURE__*/ overloadingFn( [ mx_gradient_float_0, mx_gradient_float_1 ] ); + +const mx_gradient_vec3_0 = /*@__PURE__*/ Fn( ( [ hash_immutable, x_immutable, y_immutable ] ) => { + + const y = float( y_immutable ).toVar(); + const x = float( x_immutable ).toVar(); + const hash = uvec3( hash_immutable ).toVar(); + + return vec3( mx_gradient_float( hash.x, x, y ), mx_gradient_float( hash.y, x, y ), mx_gradient_float( hash.z, x, y ) ); + +} ).setLayout( { + name: 'mx_gradient_vec3_0', + type: 'vec3', + inputs: [ + { name: 'hash', type: 'uvec3' }, + { name: 'x', type: 'float' }, + { name: 'y', type: 'float' } + ] +} ); + +const mx_gradient_vec3_1 = /*@__PURE__*/ Fn( ( [ hash_immutable, x_immutable, y_immutable, z_immutable ] ) => { + + const z = float( z_immutable ).toVar(); + const y = float( y_immutable ).toVar(); + const x = float( x_immutable ).toVar(); + const hash = uvec3( hash_immutable ).toVar(); + + return vec3( mx_gradient_float( hash.x, x, y, z ), mx_gradient_float( hash.y, x, y, z ), mx_gradient_float( hash.z, x, y, z ) ); + +} ).setLayout( { + name: 'mx_gradient_vec3_1', + type: 'vec3', + inputs: [ + { name: 'hash', type: 'uvec3' }, + { name: 'x', type: 'float' }, + { name: 'y', type: 'float' }, + { name: 'z', type: 'float' } + ] +} ); + +const mx_gradient_vec3 = /*@__PURE__*/ overloadingFn( [ mx_gradient_vec3_0, mx_gradient_vec3_1 ] ); + +const mx_gradient_scale2d_0 = /*@__PURE__*/ Fn( ( [ v_immutable ] ) => { + + const v = float( v_immutable ).toVar(); + + return mul( 0.6616, v ); + +} ).setLayout( { + name: 'mx_gradient_scale2d_0', + type: 'float', + inputs: [ + { name: 'v', type: 'float' } + ] +} ); + +const mx_gradient_scale3d_0 = /*@__PURE__*/ Fn( ( [ v_immutable ] ) => { + + const v = float( v_immutable ).toVar(); + + return mul( 0.9820, v ); + +} ).setLayout( { + name: 'mx_gradient_scale3d_0', + type: 'float', + inputs: [ + { name: 'v', type: 'float' } + ] +} ); + +const mx_gradient_scale2d_1 = /*@__PURE__*/ Fn( ( [ v_immutable ] ) => { + + const v = vec3( v_immutable ).toVar(); + + return mul( 0.6616, v ); + +} ).setLayout( { + name: 'mx_gradient_scale2d_1', + type: 'vec3', + inputs: [ + { name: 'v', type: 'vec3' } + ] +} ); + +const mx_gradient_scale2d = /*@__PURE__*/ overloadingFn( [ mx_gradient_scale2d_0, mx_gradient_scale2d_1 ] ); + +const mx_gradient_scale3d_1 = /*@__PURE__*/ Fn( ( [ v_immutable ] ) => { + + const v = vec3( v_immutable ).toVar(); + + return mul( 0.9820, v ); + +} ).setLayout( { + name: 'mx_gradient_scale3d_1', + type: 'vec3', + inputs: [ + { name: 'v', type: 'vec3' } + ] +} ); + +const mx_gradient_scale3d = /*@__PURE__*/ overloadingFn( [ mx_gradient_scale3d_0, mx_gradient_scale3d_1 ] ); + +const mx_rotl32 = /*@__PURE__*/ Fn( ( [ x_immutable, k_immutable ] ) => { + + const k = int( k_immutable ).toVar(); + const x = uint( x_immutable ).toVar(); + + return x.shiftLeft( k ).bitOr( x.shiftRight( int( 32 ).sub( k ) ) ); + +} ).setLayout( { + name: 'mx_rotl32', + type: 'uint', + inputs: [ + { name: 'x', type: 'uint' }, + { name: 'k', type: 'int' } + ] +} ); + +const mx_bjmix = /*@__PURE__*/ Fn( ( [ a, b, c ] ) => { + + a.subAssign( c ); + a.bitXorAssign( mx_rotl32( c, int( 4 ) ) ); + c.addAssign( b ); + b.subAssign( a ); + b.bitXorAssign( mx_rotl32( a, int( 6 ) ) ); + a.addAssign( c ); + c.subAssign( b ); + c.bitXorAssign( mx_rotl32( b, int( 8 ) ) ); + b.addAssign( a ); + a.subAssign( c ); + a.bitXorAssign( mx_rotl32( c, int( 16 ) ) ); + c.addAssign( b ); + b.subAssign( a ); + b.bitXorAssign( mx_rotl32( a, int( 19 ) ) ); + a.addAssign( c ); + c.subAssign( b ); + c.bitXorAssign( mx_rotl32( b, int( 4 ) ) ); + b.addAssign( a ); + +} ); + +const mx_bjfinal = /*@__PURE__*/ Fn( ( [ a_immutable, b_immutable, c_immutable ] ) => { + + const c = uint( c_immutable ).toVar(); + const b = uint( b_immutable ).toVar(); + const a = uint( a_immutable ).toVar(); + c.bitXorAssign( b ); + c.subAssign( mx_rotl32( b, int( 14 ) ) ); + a.bitXorAssign( c ); + a.subAssign( mx_rotl32( c, int( 11 ) ) ); + b.bitXorAssign( a ); + b.subAssign( mx_rotl32( a, int( 25 ) ) ); + c.bitXorAssign( b ); + c.subAssign( mx_rotl32( b, int( 16 ) ) ); + a.bitXorAssign( c ); + a.subAssign( mx_rotl32( c, int( 4 ) ) ); + b.bitXorAssign( a ); + b.subAssign( mx_rotl32( a, int( 14 ) ) ); + c.bitXorAssign( b ); + c.subAssign( mx_rotl32( b, int( 24 ) ) ); + + return c; + +} ).setLayout( { + name: 'mx_bjfinal', + type: 'uint', + inputs: [ + { name: 'a', type: 'uint' }, + { name: 'b', type: 'uint' }, + { name: 'c', type: 'uint' } + ] +} ); + +const mx_bits_to_01 = /*@__PURE__*/ Fn( ( [ bits_immutable ] ) => { + + const bits = uint( bits_immutable ).toVar(); + + return float( bits ).div( float( uint( int( 0xffffffff ) ) ) ); + +} ).setLayout( { + name: 'mx_bits_to_01', + type: 'float', + inputs: [ + { name: 'bits', type: 'uint' } + ] +} ); + +const mx_fade = /*@__PURE__*/ Fn( ( [ t_immutable ] ) => { + + const t = float( t_immutable ).toVar(); + + return t.mul( t ).mul( t ).mul( t.mul( t.mul( 6.0 ).sub( 15.0 ) ).add( 10.0 ) ); + +} ).setLayout( { + name: 'mx_fade', + type: 'float', + inputs: [ + { name: 't', type: 'float' } + ] +} ); + +const mx_hash_int_0 = /*@__PURE__*/ Fn( ( [ x_immutable ] ) => { + + const x = int( x_immutable ).toVar(); + const len = uint( uint( 1 ) ).toVar(); + const seed = uint( uint( int( 0xdeadbeef ) ).add( len.shiftLeft( uint( 2 ) ) ).add( uint( 13 ) ) ).toVar(); + + return mx_bjfinal( seed.add( uint( x ) ), seed, seed ); + +} ).setLayout( { + name: 'mx_hash_int_0', + type: 'uint', + inputs: [ + { name: 'x', type: 'int' } + ] +} ); + +const mx_hash_int_1 = /*@__PURE__*/ Fn( ( [ x_immutable, y_immutable ] ) => { + + const y = int( y_immutable ).toVar(); + const x = int( x_immutable ).toVar(); + const len = uint( uint( 2 ) ).toVar(); + const a = uint().toVar(), b = uint().toVar(), c = uint().toVar(); + a.assign( b.assign( c.assign( uint( int( 0xdeadbeef ) ).add( len.shiftLeft( uint( 2 ) ) ).add( uint( 13 ) ) ) ) ); + a.addAssign( uint( x ) ); + b.addAssign( uint( y ) ); + + return mx_bjfinal( a, b, c ); + +} ).setLayout( { + name: 'mx_hash_int_1', + type: 'uint', + inputs: [ + { name: 'x', type: 'int' }, + { name: 'y', type: 'int' } + ] +} ); + +const mx_hash_int_2 = /*@__PURE__*/ Fn( ( [ x_immutable, y_immutable, z_immutable ] ) => { + + const z = int( z_immutable ).toVar(); + const y = int( y_immutable ).toVar(); + const x = int( x_immutable ).toVar(); + const len = uint( uint( 3 ) ).toVar(); + const a = uint().toVar(), b = uint().toVar(), c = uint().toVar(); + a.assign( b.assign( c.assign( uint( int( 0xdeadbeef ) ).add( len.shiftLeft( uint( 2 ) ) ).add( uint( 13 ) ) ) ) ); + a.addAssign( uint( x ) ); + b.addAssign( uint( y ) ); + c.addAssign( uint( z ) ); + + return mx_bjfinal( a, b, c ); + +} ).setLayout( { + name: 'mx_hash_int_2', + type: 'uint', + inputs: [ + { name: 'x', type: 'int' }, + { name: 'y', type: 'int' }, + { name: 'z', type: 'int' } + ] +} ); + +const mx_hash_int_3 = /*@__PURE__*/ Fn( ( [ x_immutable, y_immutable, z_immutable, xx_immutable ] ) => { + + const xx = int( xx_immutable ).toVar(); + const z = int( z_immutable ).toVar(); + const y = int( y_immutable ).toVar(); + const x = int( x_immutable ).toVar(); + const len = uint( uint( 4 ) ).toVar(); + const a = uint().toVar(), b = uint().toVar(), c = uint().toVar(); + a.assign( b.assign( c.assign( uint( int( 0xdeadbeef ) ).add( len.shiftLeft( uint( 2 ) ) ).add( uint( 13 ) ) ) ) ); + a.addAssign( uint( x ) ); + b.addAssign( uint( y ) ); + c.addAssign( uint( z ) ); + mx_bjmix( a, b, c ); + a.addAssign( uint( xx ) ); + + return mx_bjfinal( a, b, c ); + +} ).setLayout( { + name: 'mx_hash_int_3', + type: 'uint', + inputs: [ + { name: 'x', type: 'int' }, + { name: 'y', type: 'int' }, + { name: 'z', type: 'int' }, + { name: 'xx', type: 'int' } + ] +} ); + +const mx_hash_int_4 = /*@__PURE__*/ Fn( ( [ x_immutable, y_immutable, z_immutable, xx_immutable, yy_immutable ] ) => { + + const yy = int( yy_immutable ).toVar(); + const xx = int( xx_immutable ).toVar(); + const z = int( z_immutable ).toVar(); + const y = int( y_immutable ).toVar(); + const x = int( x_immutable ).toVar(); + const len = uint( uint( 5 ) ).toVar(); + const a = uint().toVar(), b = uint().toVar(), c = uint().toVar(); + a.assign( b.assign( c.assign( uint( int( 0xdeadbeef ) ).add( len.shiftLeft( uint( 2 ) ) ).add( uint( 13 ) ) ) ) ); + a.addAssign( uint( x ) ); + b.addAssign( uint( y ) ); + c.addAssign( uint( z ) ); + mx_bjmix( a, b, c ); + a.addAssign( uint( xx ) ); + b.addAssign( uint( yy ) ); + + return mx_bjfinal( a, b, c ); + +} ).setLayout( { + name: 'mx_hash_int_4', + type: 'uint', + inputs: [ + { name: 'x', type: 'int' }, + { name: 'y', type: 'int' }, + { name: 'z', type: 'int' }, + { name: 'xx', type: 'int' }, + { name: 'yy', type: 'int' } + ] +} ); + +const mx_hash_int = /*@__PURE__*/ overloadingFn( [ mx_hash_int_0, mx_hash_int_1, mx_hash_int_2, mx_hash_int_3, mx_hash_int_4 ] ); + +const mx_hash_vec3_0 = /*@__PURE__*/ Fn( ( [ x_immutable, y_immutable ] ) => { + + const y = int( y_immutable ).toVar(); + const x = int( x_immutable ).toVar(); + const h = uint( mx_hash_int( x, y ) ).toVar(); + const result = uvec3().toVar(); + result.x.assign( h.bitAnd( int( 0xFF ) ) ); + result.y.assign( h.shiftRight( int( 8 ) ).bitAnd( int( 0xFF ) ) ); + result.z.assign( h.shiftRight( int( 16 ) ).bitAnd( int( 0xFF ) ) ); + + return result; + +} ).setLayout( { + name: 'mx_hash_vec3_0', + type: 'uvec3', + inputs: [ + { name: 'x', type: 'int' }, + { name: 'y', type: 'int' } + ] +} ); + +const mx_hash_vec3_1 = /*@__PURE__*/ Fn( ( [ x_immutable, y_immutable, z_immutable ] ) => { + + const z = int( z_immutable ).toVar(); + const y = int( y_immutable ).toVar(); + const x = int( x_immutable ).toVar(); + const h = uint( mx_hash_int( x, y, z ) ).toVar(); + const result = uvec3().toVar(); + result.x.assign( h.bitAnd( int( 0xFF ) ) ); + result.y.assign( h.shiftRight( int( 8 ) ).bitAnd( int( 0xFF ) ) ); + result.z.assign( h.shiftRight( int( 16 ) ).bitAnd( int( 0xFF ) ) ); + + return result; + +} ).setLayout( { + name: 'mx_hash_vec3_1', + type: 'uvec3', + inputs: [ + { name: 'x', type: 'int' }, + { name: 'y', type: 'int' }, + { name: 'z', type: 'int' } + ] +} ); + +const mx_hash_vec3 = /*@__PURE__*/ overloadingFn( [ mx_hash_vec3_0, mx_hash_vec3_1 ] ); + +const mx_perlin_noise_float_0 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => { + + const p = vec2( p_immutable ).toVar(); + const X = int().toVar(), Y = int().toVar(); + const fx = float( mx_floorfrac( p.x, X ) ).toVar(); + const fy = float( mx_floorfrac( p.y, Y ) ).toVar(); + const u = float( mx_fade( fx ) ).toVar(); + const v = float( mx_fade( fy ) ).toVar(); + const result = float( mx_bilerp( mx_gradient_float( mx_hash_int( X, Y ), fx, fy ), mx_gradient_float( mx_hash_int( X.add( int( 1 ) ), Y ), fx.sub( 1.0 ), fy ), mx_gradient_float( mx_hash_int( X, Y.add( int( 1 ) ) ), fx, fy.sub( 1.0 ) ), mx_gradient_float( mx_hash_int( X.add( int( 1 ) ), Y.add( int( 1 ) ) ), fx.sub( 1.0 ), fy.sub( 1.0 ) ), u, v ) ).toVar(); + + return mx_gradient_scale2d( result ); + +} ).setLayout( { + name: 'mx_perlin_noise_float_0', + type: 'float', + inputs: [ + { name: 'p', type: 'vec2' } + ] +} ); + +const mx_perlin_noise_float_1 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => { + + const p = vec3( p_immutable ).toVar(); + const X = int().toVar(), Y = int().toVar(), Z = int().toVar(); + const fx = float( mx_floorfrac( p.x, X ) ).toVar(); + const fy = float( mx_floorfrac( p.y, Y ) ).toVar(); + const fz = float( mx_floorfrac( p.z, Z ) ).toVar(); + const u = float( mx_fade( fx ) ).toVar(); + const v = float( mx_fade( fy ) ).toVar(); + const w = float( mx_fade( fz ) ).toVar(); + const result = float( mx_trilerp( mx_gradient_float( mx_hash_int( X, Y, Z ), fx, fy, fz ), mx_gradient_float( mx_hash_int( X.add( int( 1 ) ), Y, Z ), fx.sub( 1.0 ), fy, fz ), mx_gradient_float( mx_hash_int( X, Y.add( int( 1 ) ), Z ), fx, fy.sub( 1.0 ), fz ), mx_gradient_float( mx_hash_int( X.add( int( 1 ) ), Y.add( int( 1 ) ), Z ), fx.sub( 1.0 ), fy.sub( 1.0 ), fz ), mx_gradient_float( mx_hash_int( X, Y, Z.add( int( 1 ) ) ), fx, fy, fz.sub( 1.0 ) ), mx_gradient_float( mx_hash_int( X.add( int( 1 ) ), Y, Z.add( int( 1 ) ) ), fx.sub( 1.0 ), fy, fz.sub( 1.0 ) ), mx_gradient_float( mx_hash_int( X, Y.add( int( 1 ) ), Z.add( int( 1 ) ) ), fx, fy.sub( 1.0 ), fz.sub( 1.0 ) ), mx_gradient_float( mx_hash_int( X.add( int( 1 ) ), Y.add( int( 1 ) ), Z.add( int( 1 ) ) ), fx.sub( 1.0 ), fy.sub( 1.0 ), fz.sub( 1.0 ) ), u, v, w ) ).toVar(); + + return mx_gradient_scale3d( result ); + +} ).setLayout( { + name: 'mx_perlin_noise_float_1', + type: 'float', + inputs: [ + { name: 'p', type: 'vec3' } + ] +} ); + +const mx_perlin_noise_float = /*@__PURE__*/ overloadingFn( [ mx_perlin_noise_float_0, mx_perlin_noise_float_1 ] ); + +const mx_perlin_noise_vec3_0 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => { + + const p = vec2( p_immutable ).toVar(); + const X = int().toVar(), Y = int().toVar(); + const fx = float( mx_floorfrac( p.x, X ) ).toVar(); + const fy = float( mx_floorfrac( p.y, Y ) ).toVar(); + const u = float( mx_fade( fx ) ).toVar(); + const v = float( mx_fade( fy ) ).toVar(); + const result = vec3( mx_bilerp( mx_gradient_vec3( mx_hash_vec3( X, Y ), fx, fy ), mx_gradient_vec3( mx_hash_vec3( X.add( int( 1 ) ), Y ), fx.sub( 1.0 ), fy ), mx_gradient_vec3( mx_hash_vec3( X, Y.add( int( 1 ) ) ), fx, fy.sub( 1.0 ) ), mx_gradient_vec3( mx_hash_vec3( X.add( int( 1 ) ), Y.add( int( 1 ) ) ), fx.sub( 1.0 ), fy.sub( 1.0 ) ), u, v ) ).toVar(); + + return mx_gradient_scale2d( result ); + +} ).setLayout( { + name: 'mx_perlin_noise_vec3_0', + type: 'vec3', + inputs: [ + { name: 'p', type: 'vec2' } + ] +} ); + +const mx_perlin_noise_vec3_1 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => { + + const p = vec3( p_immutable ).toVar(); + const X = int().toVar(), Y = int().toVar(), Z = int().toVar(); + const fx = float( mx_floorfrac( p.x, X ) ).toVar(); + const fy = float( mx_floorfrac( p.y, Y ) ).toVar(); + const fz = float( mx_floorfrac( p.z, Z ) ).toVar(); + const u = float( mx_fade( fx ) ).toVar(); + const v = float( mx_fade( fy ) ).toVar(); + const w = float( mx_fade( fz ) ).toVar(); + const result = vec3( mx_trilerp( mx_gradient_vec3( mx_hash_vec3( X, Y, Z ), fx, fy, fz ), mx_gradient_vec3( mx_hash_vec3( X.add( int( 1 ) ), Y, Z ), fx.sub( 1.0 ), fy, fz ), mx_gradient_vec3( mx_hash_vec3( X, Y.add( int( 1 ) ), Z ), fx, fy.sub( 1.0 ), fz ), mx_gradient_vec3( mx_hash_vec3( X.add( int( 1 ) ), Y.add( int( 1 ) ), Z ), fx.sub( 1.0 ), fy.sub( 1.0 ), fz ), mx_gradient_vec3( mx_hash_vec3( X, Y, Z.add( int( 1 ) ) ), fx, fy, fz.sub( 1.0 ) ), mx_gradient_vec3( mx_hash_vec3( X.add( int( 1 ) ), Y, Z.add( int( 1 ) ) ), fx.sub( 1.0 ), fy, fz.sub( 1.0 ) ), mx_gradient_vec3( mx_hash_vec3( X, Y.add( int( 1 ) ), Z.add( int( 1 ) ) ), fx, fy.sub( 1.0 ), fz.sub( 1.0 ) ), mx_gradient_vec3( mx_hash_vec3( X.add( int( 1 ) ), Y.add( int( 1 ) ), Z.add( int( 1 ) ) ), fx.sub( 1.0 ), fy.sub( 1.0 ), fz.sub( 1.0 ) ), u, v, w ) ).toVar(); + + return mx_gradient_scale3d( result ); + +} ).setLayout( { + name: 'mx_perlin_noise_vec3_1', + type: 'vec3', + inputs: [ + { name: 'p', type: 'vec3' } + ] +} ); + +const mx_perlin_noise_vec3 = /*@__PURE__*/ overloadingFn( [ mx_perlin_noise_vec3_0, mx_perlin_noise_vec3_1 ] ); + +const mx_cell_noise_float_0 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => { + + const p = float( p_immutable ).toVar(); + const ix = int( mx_floor( p ) ).toVar(); + + return mx_bits_to_01( mx_hash_int( ix ) ); + +} ).setLayout( { + name: 'mx_cell_noise_float_0', + type: 'float', + inputs: [ + { name: 'p', type: 'float' } + ] +} ); + +const mx_cell_noise_float_1 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => { + + const p = vec2( p_immutable ).toVar(); + const ix = int( mx_floor( p.x ) ).toVar(); + const iy = int( mx_floor( p.y ) ).toVar(); + + return mx_bits_to_01( mx_hash_int( ix, iy ) ); + +} ).setLayout( { + name: 'mx_cell_noise_float_1', + type: 'float', + inputs: [ + { name: 'p', type: 'vec2' } + ] +} ); + +const mx_cell_noise_float_2 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => { + + const p = vec3( p_immutable ).toVar(); + const ix = int( mx_floor( p.x ) ).toVar(); + const iy = int( mx_floor( p.y ) ).toVar(); + const iz = int( mx_floor( p.z ) ).toVar(); + + return mx_bits_to_01( mx_hash_int( ix, iy, iz ) ); + +} ).setLayout( { + name: 'mx_cell_noise_float_2', + type: 'float', + inputs: [ + { name: 'p', type: 'vec3' } + ] +} ); + +const mx_cell_noise_float_3 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => { + + const p = vec4( p_immutable ).toVar(); + const ix = int( mx_floor( p.x ) ).toVar(); + const iy = int( mx_floor( p.y ) ).toVar(); + const iz = int( mx_floor( p.z ) ).toVar(); + const iw = int( mx_floor( p.w ) ).toVar(); + + return mx_bits_to_01( mx_hash_int( ix, iy, iz, iw ) ); + +} ).setLayout( { + name: 'mx_cell_noise_float_3', + type: 'float', + inputs: [ + { name: 'p', type: 'vec4' } + ] +} ); + +const mx_cell_noise_float$1 = /*@__PURE__*/ overloadingFn( [ mx_cell_noise_float_0, mx_cell_noise_float_1, mx_cell_noise_float_2, mx_cell_noise_float_3 ] ); + +const mx_cell_noise_vec3_0 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => { + + const p = float( p_immutable ).toVar(); + const ix = int( mx_floor( p ) ).toVar(); + + return vec3( mx_bits_to_01( mx_hash_int( ix, int( 0 ) ) ), mx_bits_to_01( mx_hash_int( ix, int( 1 ) ) ), mx_bits_to_01( mx_hash_int( ix, int( 2 ) ) ) ); + +} ).setLayout( { + name: 'mx_cell_noise_vec3_0', + type: 'vec3', + inputs: [ + { name: 'p', type: 'float' } + ] +} ); + +const mx_cell_noise_vec3_1 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => { + + const p = vec2( p_immutable ).toVar(); + const ix = int( mx_floor( p.x ) ).toVar(); + const iy = int( mx_floor( p.y ) ).toVar(); + + return vec3( mx_bits_to_01( mx_hash_int( ix, iy, int( 0 ) ) ), mx_bits_to_01( mx_hash_int( ix, iy, int( 1 ) ) ), mx_bits_to_01( mx_hash_int( ix, iy, int( 2 ) ) ) ); + +} ).setLayout( { + name: 'mx_cell_noise_vec3_1', + type: 'vec3', + inputs: [ + { name: 'p', type: 'vec2' } + ] +} ); + +const mx_cell_noise_vec3_2 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => { + + const p = vec3( p_immutable ).toVar(); + const ix = int( mx_floor( p.x ) ).toVar(); + const iy = int( mx_floor( p.y ) ).toVar(); + const iz = int( mx_floor( p.z ) ).toVar(); + + return vec3( mx_bits_to_01( mx_hash_int( ix, iy, iz, int( 0 ) ) ), mx_bits_to_01( mx_hash_int( ix, iy, iz, int( 1 ) ) ), mx_bits_to_01( mx_hash_int( ix, iy, iz, int( 2 ) ) ) ); + +} ).setLayout( { + name: 'mx_cell_noise_vec3_2', + type: 'vec3', + inputs: [ + { name: 'p', type: 'vec3' } + ] +} ); + +const mx_cell_noise_vec3_3 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => { + + const p = vec4( p_immutable ).toVar(); + const ix = int( mx_floor( p.x ) ).toVar(); + const iy = int( mx_floor( p.y ) ).toVar(); + const iz = int( mx_floor( p.z ) ).toVar(); + const iw = int( mx_floor( p.w ) ).toVar(); + + return vec3( mx_bits_to_01( mx_hash_int( ix, iy, iz, iw, int( 0 ) ) ), mx_bits_to_01( mx_hash_int( ix, iy, iz, iw, int( 1 ) ) ), mx_bits_to_01( mx_hash_int( ix, iy, iz, iw, int( 2 ) ) ) ); + +} ).setLayout( { + name: 'mx_cell_noise_vec3_3', + type: 'vec3', + inputs: [ + { name: 'p', type: 'vec4' } + ] +} ); + +const mx_cell_noise_vec3 = /*@__PURE__*/ overloadingFn( [ mx_cell_noise_vec3_0, mx_cell_noise_vec3_1, mx_cell_noise_vec3_2, mx_cell_noise_vec3_3 ] ); + +const mx_fractal_noise_float$1 = /*@__PURE__*/ Fn( ( [ p_immutable, octaves_immutable, lacunarity_immutable, diminish_immutable ] ) => { + + const diminish = float( diminish_immutable ).toVar(); + const lacunarity = float( lacunarity_immutable ).toVar(); + const octaves = int( octaves_immutable ).toVar(); + const p = vec3( p_immutable ).toVar(); + const result = float( 0.0 ).toVar(); + const amplitude = float( 1.0 ).toVar(); + + Loop( octaves, () => { + + result.addAssign( amplitude.mul( mx_perlin_noise_float( p ) ) ); + amplitude.mulAssign( diminish ); + p.mulAssign( lacunarity ); + + } ); + + return result; + +} ).setLayout( { + name: 'mx_fractal_noise_float', + type: 'float', + inputs: [ + { name: 'p', type: 'vec3' }, + { name: 'octaves', type: 'int' }, + { name: 'lacunarity', type: 'float' }, + { name: 'diminish', type: 'float' } + ] +} ); + +const mx_fractal_noise_vec3$1 = /*@__PURE__*/ Fn( ( [ p_immutable, octaves_immutable, lacunarity_immutable, diminish_immutable ] ) => { + + const diminish = float( diminish_immutable ).toVar(); + const lacunarity = float( lacunarity_immutable ).toVar(); + const octaves = int( octaves_immutable ).toVar(); + const p = vec3( p_immutable ).toVar(); + const result = vec3( 0.0 ).toVar(); + const amplitude = float( 1.0 ).toVar(); + + Loop( octaves, () => { + + result.addAssign( amplitude.mul( mx_perlin_noise_vec3( p ) ) ); + amplitude.mulAssign( diminish ); + p.mulAssign( lacunarity ); + + } ); + + return result; + +} ).setLayout( { + name: 'mx_fractal_noise_vec3', + type: 'vec3', + inputs: [ + { name: 'p', type: 'vec3' }, + { name: 'octaves', type: 'int' }, + { name: 'lacunarity', type: 'float' }, + { name: 'diminish', type: 'float' } + ] +} ); + +const mx_fractal_noise_vec2$1 = /*@__PURE__*/ Fn( ( [ p_immutable, octaves_immutable, lacunarity_immutable, diminish_immutable ] ) => { + + const diminish = float( diminish_immutable ).toVar(); + const lacunarity = float( lacunarity_immutable ).toVar(); + const octaves = int( octaves_immutable ).toVar(); + const p = vec3( p_immutable ).toVar(); + + return vec2( mx_fractal_noise_float$1( p, octaves, lacunarity, diminish ), mx_fractal_noise_float$1( p.add( vec3( int( 19 ), int( 193 ), int( 17 ) ) ), octaves, lacunarity, diminish ) ); + +} ).setLayout( { + name: 'mx_fractal_noise_vec2', + type: 'vec2', + inputs: [ + { name: 'p', type: 'vec3' }, + { name: 'octaves', type: 'int' }, + { name: 'lacunarity', type: 'float' }, + { name: 'diminish', type: 'float' } + ] +} ); + +const mx_fractal_noise_vec4$1 = /*@__PURE__*/ Fn( ( [ p_immutable, octaves_immutable, lacunarity_immutable, diminish_immutable ] ) => { + + const diminish = float( diminish_immutable ).toVar(); + const lacunarity = float( lacunarity_immutable ).toVar(); + const octaves = int( octaves_immutable ).toVar(); + const p = vec3( p_immutable ).toVar(); + const c = vec3( mx_fractal_noise_vec3$1( p, octaves, lacunarity, diminish ) ).toVar(); + const f = float( mx_fractal_noise_float$1( p.add( vec3( int( 19 ), int( 193 ), int( 17 ) ) ), octaves, lacunarity, diminish ) ).toVar(); + + return vec4( c, f ); + +} ).setLayout( { + name: 'mx_fractal_noise_vec4', + type: 'vec4', + inputs: [ + { name: 'p', type: 'vec3' }, + { name: 'octaves', type: 'int' }, + { name: 'lacunarity', type: 'float' }, + { name: 'diminish', type: 'float' } + ] +} ); + +const mx_worley_distance_0 = /*@__PURE__*/ Fn( ( [ p_immutable, x_immutable, y_immutable, xoff_immutable, yoff_immutable, jitter_immutable, metric_immutable ] ) => { + + const metric = int( metric_immutable ).toVar(); + const jitter = float( jitter_immutable ).toVar(); + const yoff = int( yoff_immutable ).toVar(); + const xoff = int( xoff_immutable ).toVar(); + const y = int( y_immutable ).toVar(); + const x = int( x_immutable ).toVar(); + const p = vec2( p_immutable ).toVar(); + const tmp = vec3( mx_cell_noise_vec3( vec2( x.add( xoff ), y.add( yoff ) ) ) ).toVar(); + const off = vec2( tmp.x, tmp.y ).toVar(); + off.subAssign( 0.5 ); + off.mulAssign( jitter ); + off.addAssign( 0.5 ); + const cellpos = vec2( vec2( float( x ), float( y ) ).add( off ) ).toVar(); + const diff = vec2( cellpos.sub( p ) ).toVar(); + + If( metric.equal( int( 2 ) ), () => { + + return abs( diff.x ).add( abs( diff.y ) ); + + } ); + + If( metric.equal( int( 3 ) ), () => { + + return max$1( abs( diff.x ), abs( diff.y ) ); + + } ); + + return dot( diff, diff ); + +} ).setLayout( { + name: 'mx_worley_distance_0', + type: 'float', + inputs: [ + { name: 'p', type: 'vec2' }, + { name: 'x', type: 'int' }, + { name: 'y', type: 'int' }, + { name: 'xoff', type: 'int' }, + { name: 'yoff', type: 'int' }, + { name: 'jitter', type: 'float' }, + { name: 'metric', type: 'int' } + ] +} ); + +const mx_worley_distance_1 = /*@__PURE__*/ Fn( ( [ p_immutable, x_immutable, y_immutable, z_immutable, xoff_immutable, yoff_immutable, zoff_immutable, jitter_immutable, metric_immutable ] ) => { + + const metric = int( metric_immutable ).toVar(); + const jitter = float( jitter_immutable ).toVar(); + const zoff = int( zoff_immutable ).toVar(); + const yoff = int( yoff_immutable ).toVar(); + const xoff = int( xoff_immutable ).toVar(); + const z = int( z_immutable ).toVar(); + const y = int( y_immutable ).toVar(); + const x = int( x_immutable ).toVar(); + const p = vec3( p_immutable ).toVar(); + const off = vec3( mx_cell_noise_vec3( vec3( x.add( xoff ), y.add( yoff ), z.add( zoff ) ) ) ).toVar(); + off.subAssign( 0.5 ); + off.mulAssign( jitter ); + off.addAssign( 0.5 ); + const cellpos = vec3( vec3( float( x ), float( y ), float( z ) ).add( off ) ).toVar(); + const diff = vec3( cellpos.sub( p ) ).toVar(); + + If( metric.equal( int( 2 ) ), () => { + + return abs( diff.x ).add( abs( diff.y ) ).add( abs( diff.z ) ); + + } ); + + If( metric.equal( int( 3 ) ), () => { + + return max$1( abs( diff.x ), abs( diff.y ), abs( diff.z ) ); + + } ); + + return dot( diff, diff ); + +} ).setLayout( { + name: 'mx_worley_distance_1', + type: 'float', + inputs: [ + { name: 'p', type: 'vec3' }, + { name: 'x', type: 'int' }, + { name: 'y', type: 'int' }, + { name: 'z', type: 'int' }, + { name: 'xoff', type: 'int' }, + { name: 'yoff', type: 'int' }, + { name: 'zoff', type: 'int' }, + { name: 'jitter', type: 'float' }, + { name: 'metric', type: 'int' } + ] +} ); + +const mx_worley_distance = /*@__PURE__*/ overloadingFn( [ mx_worley_distance_0, mx_worley_distance_1 ] ); + +const mx_worley_noise_float_0 = /*@__PURE__*/ Fn( ( [ p_immutable, jitter_immutable, metric_immutable ] ) => { + + const metric = int( metric_immutable ).toVar(); + const jitter = float( jitter_immutable ).toVar(); + const p = vec2( p_immutable ).toVar(); + const X = int().toVar(), Y = int().toVar(); + const localpos = vec2( mx_floorfrac( p.x, X ), mx_floorfrac( p.y, Y ) ).toVar(); + const sqdist = float( 1e6 ).toVar(); + + Loop( { start: -1, end: int( 1 ), name: 'x', condition: '<=' }, ( { x } ) => { + + Loop( { start: -1, end: int( 1 ), name: 'y', condition: '<=' }, ( { y } ) => { + + const dist = float( mx_worley_distance( localpos, x, y, X, Y, jitter, metric ) ).toVar(); + sqdist.assign( min$1( sqdist, dist ) ); + + } ); + + } ); + + If( metric.equal( int( 0 ) ), () => { + + sqdist.assign( sqrt( sqdist ) ); + + } ); + + return sqdist; + +} ).setLayout( { + name: 'mx_worley_noise_float_0', + type: 'float', + inputs: [ + { name: 'p', type: 'vec2' }, + { name: 'jitter', type: 'float' }, + { name: 'metric', type: 'int' } + ] +} ); + +const mx_worley_noise_vec2_0 = /*@__PURE__*/ Fn( ( [ p_immutable, jitter_immutable, metric_immutable ] ) => { + + const metric = int( metric_immutable ).toVar(); + const jitter = float( jitter_immutable ).toVar(); + const p = vec2( p_immutable ).toVar(); + const X = int().toVar(), Y = int().toVar(); + const localpos = vec2( mx_floorfrac( p.x, X ), mx_floorfrac( p.y, Y ) ).toVar(); + const sqdist = vec2( 1e6, 1e6 ).toVar(); + + Loop( { start: -1, end: int( 1 ), name: 'x', condition: '<=' }, ( { x } ) => { + + Loop( { start: -1, end: int( 1 ), name: 'y', condition: '<=' }, ( { y } ) => { + + const dist = float( mx_worley_distance( localpos, x, y, X, Y, jitter, metric ) ).toVar(); + + If( dist.lessThan( sqdist.x ), () => { + + sqdist.y.assign( sqdist.x ); + sqdist.x.assign( dist ); + + } ).ElseIf( dist.lessThan( sqdist.y ), () => { + + sqdist.y.assign( dist ); + + } ); + + } ); + + } ); + + If( metric.equal( int( 0 ) ), () => { + + sqdist.assign( sqrt( sqdist ) ); + + } ); + + return sqdist; + +} ).setLayout( { + name: 'mx_worley_noise_vec2_0', + type: 'vec2', + inputs: [ + { name: 'p', type: 'vec2' }, + { name: 'jitter', type: 'float' }, + { name: 'metric', type: 'int' } + ] +} ); + +const mx_worley_noise_vec3_0 = /*@__PURE__*/ Fn( ( [ p_immutable, jitter_immutable, metric_immutable ] ) => { + + const metric = int( metric_immutable ).toVar(); + const jitter = float( jitter_immutable ).toVar(); + const p = vec2( p_immutable ).toVar(); + const X = int().toVar(), Y = int().toVar(); + const localpos = vec2( mx_floorfrac( p.x, X ), mx_floorfrac( p.y, Y ) ).toVar(); + const sqdist = vec3( 1e6, 1e6, 1e6 ).toVar(); + + Loop( { start: -1, end: int( 1 ), name: 'x', condition: '<=' }, ( { x } ) => { + + Loop( { start: -1, end: int( 1 ), name: 'y', condition: '<=' }, ( { y } ) => { + + const dist = float( mx_worley_distance( localpos, x, y, X, Y, jitter, metric ) ).toVar(); + + If( dist.lessThan( sqdist.x ), () => { + + sqdist.z.assign( sqdist.y ); + sqdist.y.assign( sqdist.x ); + sqdist.x.assign( dist ); + + } ).ElseIf( dist.lessThan( sqdist.y ), () => { + + sqdist.z.assign( sqdist.y ); + sqdist.y.assign( dist ); + + } ).ElseIf( dist.lessThan( sqdist.z ), () => { + + sqdist.z.assign( dist ); + + } ); + + } ); + + } ); + + If( metric.equal( int( 0 ) ), () => { + + sqdist.assign( sqrt( sqdist ) ); + + } ); + + return sqdist; + +} ).setLayout( { + name: 'mx_worley_noise_vec3_0', + type: 'vec3', + inputs: [ + { name: 'p', type: 'vec2' }, + { name: 'jitter', type: 'float' }, + { name: 'metric', type: 'int' } + ] +} ); + +const mx_worley_noise_float_1 = /*@__PURE__*/ Fn( ( [ p_immutable, jitter_immutable, metric_immutable ] ) => { + + const metric = int( metric_immutable ).toVar(); + const jitter = float( jitter_immutable ).toVar(); + const p = vec3( p_immutable ).toVar(); + const X = int().toVar(), Y = int().toVar(), Z = int().toVar(); + const localpos = vec3( mx_floorfrac( p.x, X ), mx_floorfrac( p.y, Y ), mx_floorfrac( p.z, Z ) ).toVar(); + const sqdist = float( 1e6 ).toVar(); + + Loop( { start: -1, end: int( 1 ), name: 'x', condition: '<=' }, ( { x } ) => { + + Loop( { start: -1, end: int( 1 ), name: 'y', condition: '<=' }, ( { y } ) => { + + Loop( { start: -1, end: int( 1 ), name: 'z', condition: '<=' }, ( { z } ) => { + + const dist = float( mx_worley_distance( localpos, x, y, z, X, Y, Z, jitter, metric ) ).toVar(); + sqdist.assign( min$1( sqdist, dist ) ); + + } ); + + } ); + + } ); + + If( metric.equal( int( 0 ) ), () => { + + sqdist.assign( sqrt( sqdist ) ); + + } ); + + return sqdist; + +} ).setLayout( { + name: 'mx_worley_noise_float_1', + type: 'float', + inputs: [ + { name: 'p', type: 'vec3' }, + { name: 'jitter', type: 'float' }, + { name: 'metric', type: 'int' } + ] +} ); + +const mx_worley_noise_float$1 = /*@__PURE__*/ overloadingFn( [ mx_worley_noise_float_0, mx_worley_noise_float_1 ] ); + +const mx_worley_noise_vec2_1 = /*@__PURE__*/ Fn( ( [ p_immutable, jitter_immutable, metric_immutable ] ) => { + + const metric = int( metric_immutable ).toVar(); + const jitter = float( jitter_immutable ).toVar(); + const p = vec3( p_immutable ).toVar(); + const X = int().toVar(), Y = int().toVar(), Z = int().toVar(); + const localpos = vec3( mx_floorfrac( p.x, X ), mx_floorfrac( p.y, Y ), mx_floorfrac( p.z, Z ) ).toVar(); + const sqdist = vec2( 1e6, 1e6 ).toVar(); + + Loop( { start: -1, end: int( 1 ), name: 'x', condition: '<=' }, ( { x } ) => { + + Loop( { start: -1, end: int( 1 ), name: 'y', condition: '<=' }, ( { y } ) => { + + Loop( { start: -1, end: int( 1 ), name: 'z', condition: '<=' }, ( { z } ) => { + + const dist = float( mx_worley_distance( localpos, x, y, z, X, Y, Z, jitter, metric ) ).toVar(); + + If( dist.lessThan( sqdist.x ), () => { + + sqdist.y.assign( sqdist.x ); + sqdist.x.assign( dist ); + + } ).ElseIf( dist.lessThan( sqdist.y ), () => { + + sqdist.y.assign( dist ); + + } ); + + } ); + + } ); + + } ); + + If( metric.equal( int( 0 ) ), () => { + + sqdist.assign( sqrt( sqdist ) ); + + } ); + + return sqdist; + +} ).setLayout( { + name: 'mx_worley_noise_vec2_1', + type: 'vec2', + inputs: [ + { name: 'p', type: 'vec3' }, + { name: 'jitter', type: 'float' }, + { name: 'metric', type: 'int' } + ] +} ); + +const mx_worley_noise_vec2$1 = /*@__PURE__*/ overloadingFn( [ mx_worley_noise_vec2_0, mx_worley_noise_vec2_1 ] ); + +const mx_worley_noise_vec3_1 = /*@__PURE__*/ Fn( ( [ p_immutable, jitter_immutable, metric_immutable ] ) => { + + const metric = int( metric_immutable ).toVar(); + const jitter = float( jitter_immutable ).toVar(); + const p = vec3( p_immutable ).toVar(); + const X = int().toVar(), Y = int().toVar(), Z = int().toVar(); + const localpos = vec3( mx_floorfrac( p.x, X ), mx_floorfrac( p.y, Y ), mx_floorfrac( p.z, Z ) ).toVar(); + const sqdist = vec3( 1e6, 1e6, 1e6 ).toVar(); + + Loop( { start: -1, end: int( 1 ), name: 'x', condition: '<=' }, ( { x } ) => { + + Loop( { start: -1, end: int( 1 ), name: 'y', condition: '<=' }, ( { y } ) => { + + Loop( { start: -1, end: int( 1 ), name: 'z', condition: '<=' }, ( { z } ) => { + + const dist = float( mx_worley_distance( localpos, x, y, z, X, Y, Z, jitter, metric ) ).toVar(); + + If( dist.lessThan( sqdist.x ), () => { + + sqdist.z.assign( sqdist.y ); + sqdist.y.assign( sqdist.x ); + sqdist.x.assign( dist ); + + } ).ElseIf( dist.lessThan( sqdist.y ), () => { + + sqdist.z.assign( sqdist.y ); + sqdist.y.assign( dist ); + + } ).ElseIf( dist.lessThan( sqdist.z ), () => { + + sqdist.z.assign( dist ); + + } ); + + } ); + + } ); + + } ); + + If( metric.equal( int( 0 ) ), () => { + + sqdist.assign( sqrt( sqdist ) ); + + } ); + + return sqdist; + +} ).setLayout( { + name: 'mx_worley_noise_vec3_1', + type: 'vec3', + inputs: [ + { name: 'p', type: 'vec3' }, + { name: 'jitter', type: 'float' }, + { name: 'metric', type: 'int' } + ] +} ); + +const mx_worley_noise_vec3$1 = /*@__PURE__*/ overloadingFn( [ mx_worley_noise_vec3_0, mx_worley_noise_vec3_1 ] ); + +// Three.js Transpiler +// https://github.com/AcademySoftwareFoundation/MaterialX/blob/main/libraries/stdlib/genglsl/lib/mx_hsv.glsl + + +const mx_hsvtorgb = /*@__PURE__*/ Fn( ( [ hsv ] ) => { + + const s = hsv.y; + const v = hsv.z; + + const result = vec3().toVar(); + + If( s.lessThan( 0.0001 ), () => { + + result.assign( vec3( v, v, v ) ); + + } ).Else( () => { + + let h = hsv.x; + h = h.sub( floor( h ) ).mul( 6.0 ).toVar(); // TODO: check what .toVar() is needed in node system cache + const hi = int( trunc( h ) ); + const f = h.sub( float( hi ) ); + const p = v.mul( s.oneMinus() ); + const q = v.mul( s.mul( f ).oneMinus() ); + const t = v.mul( s.mul( f.oneMinus() ).oneMinus() ); + + If( hi.equal( int( 0 ) ), () => { + + result.assign( vec3( v, t, p ) ); + + } ).ElseIf( hi.equal( int( 1 ) ), () => { + + result.assign( vec3( q, v, p ) ); + + } ).ElseIf( hi.equal( int( 2 ) ), () => { + + result.assign( vec3( p, v, t ) ); + + } ).ElseIf( hi.equal( int( 3 ) ), () => { + + result.assign( vec3( p, q, v ) ); + + } ).ElseIf( hi.equal( int( 4 ) ), () => { + + result.assign( vec3( t, p, v ) ); + + } ).Else( () => { + + result.assign( vec3( v, p, q ) ); + + } ); + + } ); + + return result; + +} ).setLayout( { + name: 'mx_hsvtorgb', + type: 'vec3', + inputs: [ + { name: 'hsv', type: 'vec3' } + ] +} ); + +const mx_rgbtohsv = /*@__PURE__*/ Fn( ( [ c_immutable ] ) => { + + const c = vec3( c_immutable ).toVar(); + const r = float( c.x ).toVar(); + const g = float( c.y ).toVar(); + const b = float( c.z ).toVar(); + const mincomp = float( min$1( r, min$1( g, b ) ) ).toVar(); + const maxcomp = float( max$1( r, max$1( g, b ) ) ).toVar(); + const delta = float( maxcomp.sub( mincomp ) ).toVar(); + const h = float().toVar(), s = float().toVar(), v = float().toVar(); + v.assign( maxcomp ); + + If( maxcomp.greaterThan( 0.0 ), () => { + + s.assign( delta.div( maxcomp ) ); + + } ).Else( () => { + + s.assign( 0.0 ); + + } ); + + If( s.lessThanEqual( 0.0 ), () => { + + h.assign( 0.0 ); + + } ).Else( () => { + + If( r.greaterThanEqual( maxcomp ), () => { + + h.assign( g.sub( b ).div( delta ) ); + + } ).ElseIf( g.greaterThanEqual( maxcomp ), () => { + + h.assign( add( 2.0, b.sub( r ).div( delta ) ) ); + + } ).Else( () => { + + h.assign( add( 4.0, r.sub( g ).div( delta ) ) ); + + } ); + + h.mulAssign( 1.0 / 6.0 ); + + If( h.lessThan( 0.0 ), () => { + + h.addAssign( 1.0 ); + + } ); + + } ); + + return vec3( h, s, v ); + +} ).setLayout( { + name: 'mx_rgbtohsv', + type: 'vec3', + inputs: [ + { name: 'c', type: 'vec3' } + ] +} ); + +// Three.js Transpiler +// https://github.com/AcademySoftwareFoundation/MaterialX/blob/main/libraries/stdlib/genglsl/lib/mx_transform_color.glsl + + +const mx_srgb_texture_to_lin_rec709 = /*@__PURE__*/ Fn( ( [ color_immutable ] ) => { + + const color = vec3( color_immutable ).toVar(); + const isAbove = bvec3( greaterThan( color, vec3( 0.04045 ) ) ).toVar(); + const linSeg = vec3( color.div( 12.92 ) ).toVar(); + const powSeg = vec3( pow( max$1( color.add( vec3( 0.055 ) ), vec3( 0.0 ) ).div( 1.055 ), vec3( 2.4 ) ) ).toVar(); + + return mix( linSeg, powSeg, isAbove ); + +} ).setLayout( { + name: 'mx_srgb_texture_to_lin_rec709', + type: 'vec3', + inputs: [ + { name: 'color', type: 'vec3' } + ] +} ); + +const mx_aastep = ( threshold, value ) => { + + threshold = float( threshold ); + value = float( value ); + + const afwidth = vec2( value.dFdx(), value.dFdy() ).length().mul( 0.70710678118654757 ); + + return smoothstep( threshold.sub( afwidth ), threshold.add( afwidth ), value ); + +}; + +const _ramp = ( a, b, uv, p ) => mix( a, b, uv[ p ].clamp() ); +const mx_ramplr = ( valuel, valuer, texcoord = uv$1() ) => _ramp( valuel, valuer, texcoord, 'x' ); +const mx_ramptb = ( valuet, valueb, texcoord = uv$1() ) => _ramp( valuet, valueb, texcoord, 'y' ); + +const _split = ( a, b, center, uv, p ) => mix( a, b, mx_aastep( center, uv[ p ] ) ); +const mx_splitlr = ( valuel, valuer, center, texcoord = uv$1() ) => _split( valuel, valuer, center, texcoord, 'x' ); +const mx_splittb = ( valuet, valueb, center, texcoord = uv$1() ) => _split( valuet, valueb, center, texcoord, 'y' ); + +const mx_transform_uv = ( uv_scale = 1, uv_offset = 0, uv_geo = uv$1() ) => uv_geo.mul( uv_scale ).add( uv_offset ); + +const mx_safepower = ( in1, in2 = 1 ) => { + + in1 = float( in1 ); + + return in1.abs().pow( in2 ).mul( in1.sign() ); + +}; + +const mx_contrast = ( input, amount = 1, pivot = .5 ) => float( input ).sub( pivot ).mul( amount ).add( pivot ); + +const mx_noise_float = ( texcoord = uv$1(), amplitude = 1, pivot = 0 ) => mx_perlin_noise_float( texcoord.convert( 'vec2|vec3' ) ).mul( amplitude ).add( pivot ); +//export const mx_noise_vec2 = ( texcoord = uv(), amplitude = 1, pivot = 0 ) => mx_perlin_noise_vec3( texcoord.convert( 'vec2|vec3' ) ).mul( amplitude ).add( pivot ); +const mx_noise_vec3 = ( texcoord = uv$1(), amplitude = 1, pivot = 0 ) => mx_perlin_noise_vec3( texcoord.convert( 'vec2|vec3' ) ).mul( amplitude ).add( pivot ); +const mx_noise_vec4 = ( texcoord = uv$1(), amplitude = 1, pivot = 0 ) => { + + texcoord = texcoord.convert( 'vec2|vec3' ); // overloading type + + const noise_vec4 = vec4( mx_perlin_noise_vec3( texcoord ), mx_perlin_noise_float( texcoord.add( vec2( 19, 73 ) ) ) ); + + return noise_vec4.mul( amplitude ).add( pivot ); + +}; + +const mx_worley_noise_float = ( texcoord = uv$1(), jitter = 1 ) => mx_worley_noise_float$1( texcoord.convert( 'vec2|vec3' ), jitter, int( 1 ) ); +const mx_worley_noise_vec2 = ( texcoord = uv$1(), jitter = 1 ) => mx_worley_noise_vec2$1( texcoord.convert( 'vec2|vec3' ), jitter, int( 1 ) ); +const mx_worley_noise_vec3 = ( texcoord = uv$1(), jitter = 1 ) => mx_worley_noise_vec3$1( texcoord.convert( 'vec2|vec3' ), jitter, int( 1 ) ); + +const mx_cell_noise_float = ( texcoord = uv$1() ) => mx_cell_noise_float$1( texcoord.convert( 'vec2|vec3' ) ); + +const mx_fractal_noise_float = ( position = uv$1(), octaves = 3, lacunarity = 2, diminish = .5, amplitude = 1 ) => mx_fractal_noise_float$1( position, int( octaves ), lacunarity, diminish ).mul( amplitude ); +const mx_fractal_noise_vec2 = ( position = uv$1(), octaves = 3, lacunarity = 2, diminish = .5, amplitude = 1 ) => mx_fractal_noise_vec2$1( position, int( octaves ), lacunarity, diminish ).mul( amplitude ); +const mx_fractal_noise_vec3 = ( position = uv$1(), octaves = 3, lacunarity = 2, diminish = .5, amplitude = 1 ) => mx_fractal_noise_vec3$1( position, int( octaves ), lacunarity, diminish ).mul( amplitude ); +const mx_fractal_noise_vec4 = ( position = uv$1(), octaves = 3, lacunarity = 2, diminish = .5, amplitude = 1 ) => mx_fractal_noise_vec4$1( position, int( octaves ), lacunarity, diminish ).mul( amplitude ); + +/** + * This computes a parallax corrected normal which is used for box-projected cube mapping (BPCEM). + * + * Reference: {@link https://devlog-martinsh.blogspot.com/2011/09/box-projected-cube-environment-mapping.html} + * + * ```js + * const uvNode = getParallaxCorrectNormal( reflectVector, vec3( 200, 100, 100 ), vec3( 0, - 50, 0 ) ); + * material.envNode = pmremTexture( renderTarget.texture, uvNode ); + * ``` + * + * @tsl + * @function + * @param {Node} normal - The normal to correct. + * @param {Node} cubeSize - The cube size should reflect the size of the environment (BPCEM is usually applied in closed environments like rooms). + * @param {Node} cubePos - The cube position. + * @return {Node} The parallax corrected normal. + */ +const getParallaxCorrectNormal = /*@__PURE__*/ Fn( ( [ normal, cubeSize, cubePos ] ) => { + + const nDir = normalize( normal ).toVar(); + const rbmax = sub( float( 0.5 ).mul( cubeSize.sub( cubePos ) ), positionWorld ).div( nDir ).toVar(); + const rbmin = sub( float( -0.5 ).mul( cubeSize.sub( cubePos ) ), positionWorld ).div( nDir ).toVar(); + const rbminmax = vec3().toVar(); + rbminmax.x = nDir.x.greaterThan( float( 0 ) ).select( rbmax.x, rbmin.x ); + rbminmax.y = nDir.y.greaterThan( float( 0 ) ).select( rbmax.y, rbmin.y ); + rbminmax.z = nDir.z.greaterThan( float( 0 ) ).select( rbmax.z, rbmin.z ); + + const correction = min$1( rbminmax.x, rbminmax.y, rbminmax.z ).toVar(); + const boxIntersection = positionWorld.add( nDir.mul( correction ) ).toVar(); + return boxIntersection.sub( cubePos ); + +} ); + +const getShIrradianceAt = /*@__PURE__*/ Fn( ( [ normal, shCoefficients ] ) => { + + // normal is assumed to have unit length + + const x = normal.x, y = normal.y, z = normal.z; + + // band 0 + let result = shCoefficients.element( 0 ).mul( 0.886227 ); + + // band 1 + result = result.add( shCoefficients.element( 1 ).mul( 2.0 * 0.511664 ).mul( y ) ); + result = result.add( shCoefficients.element( 2 ).mul( 2.0 * 0.511664 ).mul( z ) ); + result = result.add( shCoefficients.element( 3 ).mul( 2.0 * 0.511664 ).mul( x ) ); + + // band 2 + result = result.add( shCoefficients.element( 4 ).mul( 2.0 * 0.429043 ).mul( x ).mul( y ) ); + result = result.add( shCoefficients.element( 5 ).mul( 2.0 * 0.429043 ).mul( y ).mul( z ) ); + result = result.add( shCoefficients.element( 6 ).mul( z.mul( z ).mul( 0.743125 ).sub( 0.247708 ) ) ); + result = result.add( shCoefficients.element( 7 ).mul( 2.0 * 0.429043 ).mul( x ).mul( z ) ); + result = result.add( shCoefficients.element( 8 ).mul( 0.429043 ).mul( mul( x, x ).sub( mul( y, y ) ) ) ); + + return result; + +} ); + +// constants + +var TSL = /*#__PURE__*/Object.freeze({ + __proto__: null, + BRDF_GGX: BRDF_GGX, + BRDF_Lambert: BRDF_Lambert, + BasicPointShadowFilter: BasicPointShadowFilter, + BasicShadowFilter: BasicShadowFilter, + Break: Break, + Const: Const, + Continue: Continue, + DFGApprox: DFGApprox, + D_GGX: D_GGX, + Discard: Discard, + EPSILON: EPSILON, + F_Schlick: F_Schlick, + Fn: Fn, + INFINITY: INFINITY, + If: If, + Loop: Loop, + NodeAccess: NodeAccess, + NodeShaderStage: NodeShaderStage, + NodeType: NodeType, + NodeUpdateType: NodeUpdateType, + PCFShadowFilter: PCFShadowFilter, + PCFSoftShadowFilter: PCFSoftShadowFilter, + PI: PI, + PI2: PI2, + PointShadowFilter: PointShadowFilter, + Return: Return, + Schlick_to_F0: Schlick_to_F0, + ScriptableNodeResources: ScriptableNodeResources, + ShaderNode: ShaderNode, + Stack: Stack, + Switch: Switch, + TBNViewMatrix: TBNViewMatrix, + VSMShadowFilter: VSMShadowFilter, + V_GGX_SmithCorrelated: V_GGX_SmithCorrelated, + Var: Var, + abs: abs, + acesFilmicToneMapping: acesFilmicToneMapping, + acos: acos, + add: add, + addMethodChaining: addMethodChaining, + addNodeElement: addNodeElement, + agxToneMapping: agxToneMapping, + all: all, + alphaT: alphaT, + and: and, + anisotropy: anisotropy, + anisotropyB: anisotropyB, + anisotropyT: anisotropyT, + any: any, + append: append, + array: array, + arrayBuffer: arrayBuffer, + asin: asin, + assign: assign, + atan: atan, + atan2: atan2, + atomicAdd: atomicAdd, + atomicAnd: atomicAnd, + atomicFunc: atomicFunc, + atomicLoad: atomicLoad, + atomicMax: atomicMax, + atomicMin: atomicMin, + atomicOr: atomicOr, + atomicStore: atomicStore, + atomicSub: atomicSub, + atomicXor: atomicXor, + attenuationColor: attenuationColor, + attenuationDistance: attenuationDistance, + attribute: attribute, + attributeArray: attributeArray, + backgroundBlurriness: backgroundBlurriness, + backgroundIntensity: backgroundIntensity, + backgroundRotation: backgroundRotation, + batch: batch, + bentNormalView: bentNormalView, + billboarding: billboarding, + bitAnd: bitAnd, + bitNot: bitNot, + bitOr: bitOr, + bitXor: bitXor, + bitangentGeometry: bitangentGeometry, + bitangentLocal: bitangentLocal, + bitangentView: bitangentView, + bitangentWorld: bitangentWorld, + bitcast: bitcast, + blendBurn: blendBurn, + blendColor: blendColor, + blendDodge: blendDodge, + blendOverlay: blendOverlay, + blendScreen: blendScreen, + blur: blur, + bool: bool, + buffer: buffer, + bufferAttribute: bufferAttribute, + bumpMap: bumpMap, + burn: burn, + bvec2: bvec2, + bvec3: bvec3, + bvec4: bvec4, + bypass: bypass, + cache: cache, + call: call, + cameraFar: cameraFar, + cameraIndex: cameraIndex, + cameraNear: cameraNear, + cameraNormalMatrix: cameraNormalMatrix, + cameraPosition: cameraPosition, + cameraProjectionMatrix: cameraProjectionMatrix, + cameraProjectionMatrixInverse: cameraProjectionMatrixInverse, + cameraViewMatrix: cameraViewMatrix, + cameraWorldMatrix: cameraWorldMatrix, + cbrt: cbrt, + cdl: cdl, + ceil: ceil, + checker: checker, + cineonToneMapping: cineonToneMapping, + clamp: clamp, + clearcoat: clearcoat, + clearcoatNormalView: clearcoatNormalView, + clearcoatRoughness: clearcoatRoughness, + code: code, + color: color, + colorSpaceToWorking: colorSpaceToWorking, + colorToDirection: colorToDirection, + compute: compute, + computeSkinning: computeSkinning, + context: context, + convert: convert, + convertColorSpace: convertColorSpace, + convertToTexture: convertToTexture, + cos: cos, + cross: cross, + cubeTexture: cubeTexture, + cubeTextureBase: cubeTextureBase, + cubeToUV: cubeToUV, + dFdx: dFdx, + dFdy: dFdy, + dashSize: dashSize, + debug: debug, + decrement: decrement, + decrementBefore: decrementBefore, + defaultBuildStages: defaultBuildStages, + defaultShaderStages: defaultShaderStages, + defined: defined, + degrees: degrees, + deltaTime: deltaTime, + densityFog: densityFog, + densityFogFactor: densityFogFactor, + depth: depth, + depthPass: depthPass, + difference: difference, + diffuseColor: diffuseColor, + directPointLight: directPointLight, + directionToColor: directionToColor, + directionToFaceDirection: directionToFaceDirection, + dispersion: dispersion, + distance: distance, + div: div, + dodge: dodge, + dot: dot, + drawIndex: drawIndex, + dynamicBufferAttribute: dynamicBufferAttribute, + element: element, + emissive: emissive, + equal: equal, + equals: equals, + equirectUV: equirectUV, + exp: exp, + exp2: exp2, + expression: expression, + faceDirection: faceDirection, + faceForward: faceForward, + faceforward: faceforward, + float: float, + floor: floor, + fog: fog, + fract: fract, + frameGroup: frameGroup, + frameId: frameId, + frontFacing: frontFacing, + fwidth: fwidth, + gain: gain, + gapSize: gapSize, + getConstNodeType: getConstNodeType, + getCurrentStack: getCurrentStack, + getDirection: getDirection, + getDistanceAttenuation: getDistanceAttenuation, + getGeometryRoughness: getGeometryRoughness, + getNormalFromDepth: getNormalFromDepth, + getParallaxCorrectNormal: getParallaxCorrectNormal, + getRoughness: getRoughness, + getScreenPosition: getScreenPosition, + getShIrradianceAt: getShIrradianceAt, + getShadowMaterial: getShadowMaterial, + getShadowRenderObjectFunction: getShadowRenderObjectFunction, + getTextureIndex: getTextureIndex, + getViewPosition: getViewPosition, + globalId: globalId, + glsl: glsl, + glslFn: glslFn, + grayscale: grayscale, + greaterThan: greaterThan, + greaterThanEqual: greaterThanEqual, + hash: hash, + highpModelNormalViewMatrix: highpModelNormalViewMatrix, + highpModelViewMatrix: highpModelViewMatrix, + hue: hue, + increment: increment, + incrementBefore: incrementBefore, + instance: instance, + instanceIndex: instanceIndex, + instancedArray: instancedArray, + instancedBufferAttribute: instancedBufferAttribute, + instancedDynamicBufferAttribute: instancedDynamicBufferAttribute, + instancedMesh: instancedMesh, + int: int, + inverseSqrt: inverseSqrt, + inversesqrt: inversesqrt, + invocationLocalIndex: invocationLocalIndex, + invocationSubgroupIndex: invocationSubgroupIndex, + ior: ior, + iridescence: iridescence, + iridescenceIOR: iridescenceIOR, + iridescenceThickness: iridescenceThickness, + ivec2: ivec2, + ivec3: ivec3, + ivec4: ivec4, + js: js, + label: label, + length: length, + lengthSq: lengthSq, + lessThan: lessThan, + lessThanEqual: lessThanEqual, + lightPosition: lightPosition, + lightProjectionUV: lightProjectionUV, + lightShadowMatrix: lightShadowMatrix, + lightTargetDirection: lightTargetDirection, + lightTargetPosition: lightTargetPosition, + lightViewPosition: lightViewPosition, + lightingContext: lightingContext, + lights: lights, + linearDepth: linearDepth, + linearToneMapping: linearToneMapping, + localId: localId, + log: log, + log2: log2, + logarithmicDepthToViewZ: logarithmicDepthToViewZ, + luminance: luminance, + mat2: mat2, + mat3: mat3, + mat4: mat4, + matcapUV: matcapUV, + materialAO: materialAO, + materialAlphaTest: materialAlphaTest, + materialAnisotropy: materialAnisotropy, + materialAnisotropyVector: materialAnisotropyVector, + materialAttenuationColor: materialAttenuationColor, + materialAttenuationDistance: materialAttenuationDistance, + materialClearcoat: materialClearcoat, + materialClearcoatNormal: materialClearcoatNormal, + materialClearcoatRoughness: materialClearcoatRoughness, + materialColor: materialColor, + materialDispersion: materialDispersion, + materialEmissive: materialEmissive, + materialEnvIntensity: materialEnvIntensity, + materialEnvRotation: materialEnvRotation, + materialIOR: materialIOR, + materialIridescence: materialIridescence, + materialIridescenceIOR: materialIridescenceIOR, + materialIridescenceThickness: materialIridescenceThickness, + materialLightMap: materialLightMap, + materialLineDashOffset: materialLineDashOffset, + materialLineDashSize: materialLineDashSize, + materialLineGapSize: materialLineGapSize, + materialLineScale: materialLineScale, + materialLineWidth: materialLineWidth, + materialMetalness: materialMetalness, + materialNormal: materialNormal, + materialOpacity: materialOpacity, + materialPointSize: materialPointSize, + materialReference: materialReference, + materialReflectivity: materialReflectivity, + materialRefractionRatio: materialRefractionRatio, + materialRotation: materialRotation, + materialRoughness: materialRoughness, + materialSheen: materialSheen, + materialSheenRoughness: materialSheenRoughness, + materialShininess: materialShininess, + materialSpecular: materialSpecular, + materialSpecularColor: materialSpecularColor, + materialSpecularIntensity: materialSpecularIntensity, + materialSpecularStrength: materialSpecularStrength, + materialThickness: materialThickness, + materialTransmission: materialTransmission, + max: max$1, + maxMipLevel: maxMipLevel, + mediumpModelViewMatrix: mediumpModelViewMatrix, + metalness: metalness, + min: min$1, + mix: mix, + mixElement: mixElement, + mod: mod, + modInt: modInt, + modelDirection: modelDirection, + modelNormalMatrix: modelNormalMatrix, + modelPosition: modelPosition, + modelRadius: modelRadius, + modelScale: modelScale, + modelViewMatrix: modelViewMatrix, + modelViewPosition: modelViewPosition, + modelViewProjection: modelViewProjection, + modelWorldMatrix: modelWorldMatrix, + modelWorldMatrixInverse: modelWorldMatrixInverse, + morphReference: morphReference, + mrt: mrt, + mul: mul, + mx_aastep: mx_aastep, + mx_cell_noise_float: mx_cell_noise_float, + mx_contrast: mx_contrast, + mx_fractal_noise_float: mx_fractal_noise_float, + mx_fractal_noise_vec2: mx_fractal_noise_vec2, + mx_fractal_noise_vec3: mx_fractal_noise_vec3, + mx_fractal_noise_vec4: mx_fractal_noise_vec4, + mx_hsvtorgb: mx_hsvtorgb, + mx_noise_float: mx_noise_float, + mx_noise_vec3: mx_noise_vec3, + mx_noise_vec4: mx_noise_vec4, + mx_ramplr: mx_ramplr, + mx_ramptb: mx_ramptb, + mx_rgbtohsv: mx_rgbtohsv, + mx_safepower: mx_safepower, + mx_splitlr: mx_splitlr, + mx_splittb: mx_splittb, + mx_srgb_texture_to_lin_rec709: mx_srgb_texture_to_lin_rec709, + mx_transform_uv: mx_transform_uv, + mx_worley_noise_float: mx_worley_noise_float, + mx_worley_noise_vec2: mx_worley_noise_vec2, + mx_worley_noise_vec3: mx_worley_noise_vec3, + negate: negate, + neutralToneMapping: neutralToneMapping, + nodeArray: nodeArray, + nodeImmutable: nodeImmutable, + nodeObject: nodeObject, + nodeObjects: nodeObjects, + nodeProxy: nodeProxy, + normalFlat: normalFlat, + normalGeometry: normalGeometry, + normalLocal: normalLocal, + normalMap: normalMap, + normalView: normalView, + normalViewGeometry: normalViewGeometry, + normalWorld: normalWorld, + normalWorldGeometry: normalWorldGeometry, + normalize: normalize, + not: not, + notEqual: notEqual, + numWorkgroups: numWorkgroups, + objectDirection: objectDirection, + objectGroup: objectGroup, + objectPosition: objectPosition, + objectRadius: objectRadius, + objectScale: objectScale, + objectViewPosition: objectViewPosition, + objectWorldMatrix: objectWorldMatrix, + oneMinus: oneMinus, + or: or, + orthographicDepthToViewZ: orthographicDepthToViewZ, + oscSawtooth: oscSawtooth, + oscSine: oscSine, + oscSquare: oscSquare, + oscTriangle: oscTriangle, + output: output, + outputStruct: outputStruct, + overlay: overlay, + overloadingFn: overloadingFn, + parabola: parabola, + parallaxDirection: parallaxDirection, + parallaxUV: parallaxUV, + parameter: parameter, + pass: pass, + passTexture: passTexture, + pcurve: pcurve, + perspectiveDepthToViewZ: perspectiveDepthToViewZ, + pmremTexture: pmremTexture, + pointShadow: pointShadow, + pointUV: pointUV, + pointWidth: pointWidth, + positionGeometry: positionGeometry, + positionLocal: positionLocal, + positionPrevious: positionPrevious, + positionView: positionView, + positionViewDirection: positionViewDirection, + positionWorld: positionWorld, + positionWorldDirection: positionWorldDirection, + posterize: posterize, + pow: pow, + pow2: pow2, + pow3: pow3, + pow4: pow4, + premultiplyAlpha: premultiplyAlpha, + property: property, + radians: radians, + rand: rand, + range: range, + rangeFog: rangeFog, + rangeFogFactor: rangeFogFactor, + reciprocal: reciprocal, + reference: reference, + referenceBuffer: referenceBuffer, + reflect: reflect, + reflectVector: reflectVector, + reflectView: reflectView, + reflector: reflector, + refract: refract, + refractVector: refractVector, + refractView: refractView, + reinhardToneMapping: reinhardToneMapping, + remap: remap, + remapClamp: remapClamp, + renderGroup: renderGroup, + renderOutput: renderOutput, + rendererReference: rendererReference, + rotate: rotate, + rotateUV: rotateUV, + roughness: roughness, + round: round, + rtt: rtt, + sRGBTransferEOTF: sRGBTransferEOTF, + sRGBTransferOETF: sRGBTransferOETF, + sample: sample, + sampler: sampler, + samplerComparison: samplerComparison, + saturate: saturate, + saturation: saturation, + screen: screen, + screenCoordinate: screenCoordinate, + screenSize: screenSize, + screenUV: screenUV, + scriptable: scriptable, + scriptableValue: scriptableValue, + select: select, + setCurrentStack: setCurrentStack, + shaderStages: shaderStages, + shadow: shadow, + shadowPositionWorld: shadowPositionWorld, + shapeCircle: shapeCircle, + sharedUniformGroup: sharedUniformGroup, + sheen: sheen, + sheenRoughness: sheenRoughness, + shiftLeft: shiftLeft, + shiftRight: shiftRight, + shininess: shininess, + sign: sign, + sin: sin, + sinc: sinc, + skinning: skinning, + smoothstep: smoothstep, + smoothstepElement: smoothstepElement, + specularColor: specularColor, + specularF90: specularF90, + spherizeUV: spherizeUV, + split: split, + spritesheetUV: spritesheetUV, + sqrt: sqrt, + stack: stack, + step: step, + stepElement: stepElement, + storage: storage, + storageBarrier: storageBarrier, + storageObject: storageObject, + storageTexture: storageTexture, + string: string, + struct: struct, + sub: sub, + subBuild: subBuild, + subgroupIndex: subgroupIndex, + subgroupSize: subgroupSize, + tan: tan, + tangentGeometry: tangentGeometry, + tangentLocal: tangentLocal, + tangentView: tangentView, + tangentWorld: tangentWorld, + temp: temp, + texture: texture, + texture3D: texture3D, + textureBarrier: textureBarrier, + textureBicubic: textureBicubic, + textureBicubicLevel: textureBicubicLevel, + textureCubeUV: textureCubeUV, + textureLoad: textureLoad, + textureSize: textureSize, + textureStore: textureStore, + thickness: thickness, + time: time, + timerDelta: timerDelta, + timerGlobal: timerGlobal, + timerLocal: timerLocal, + toneMapping: toneMapping, + toneMappingExposure: toneMappingExposure, + toonOutlinePass: toonOutlinePass, + transformDirection: transformDirection, + transformNormal: transformNormal, + transformNormalToView: transformNormalToView, + transformedClearcoatNormalView: transformedClearcoatNormalView, + transformedNormalView: transformedNormalView, + transformedNormalWorld: transformedNormalWorld, + transmission: transmission, + transpose: transpose, + triNoise3D: triNoise3D, + triplanarTexture: triplanarTexture, + triplanarTextures: triplanarTextures, + trunc: trunc, + uint: uint, + uniform: uniform, + uniformArray: uniformArray, + uniformCubeTexture: uniformCubeTexture, + uniformGroup: uniformGroup, + uniformTexture: uniformTexture, + unpremultiplyAlpha: unpremultiplyAlpha, + userData: userData, + uv: uv$1, + uvec2: uvec2, + uvec3: uvec3, + uvec4: uvec4, + varying: varying, + varyingProperty: varyingProperty, + vec2: vec2, + vec3: vec3, + vec4: vec4, + vectorComponents: vectorComponents, + velocity: velocity, + vertexColor: vertexColor, + vertexIndex: vertexIndex, + vertexStage: vertexStage, + vibrance: vibrance, + viewZToLogarithmicDepth: viewZToLogarithmicDepth, + viewZToOrthographicDepth: viewZToOrthographicDepth, + viewZToPerspectiveDepth: viewZToPerspectiveDepth, + viewport: viewport, + viewportCoordinate: viewportCoordinate, + viewportDepthTexture: viewportDepthTexture, + viewportLinearDepth: viewportLinearDepth, + viewportMipTexture: viewportMipTexture, + viewportResolution: viewportResolution, + viewportSafeUV: viewportSafeUV, + viewportSharedTexture: viewportSharedTexture, + viewportSize: viewportSize, + viewportTexture: viewportTexture, + viewportUV: viewportUV, + wgsl: wgsl, + wgslFn: wgslFn, + workgroupArray: workgroupArray, + workgroupBarrier: workgroupBarrier, + workgroupId: workgroupId, + workingToColorSpace: workingToColorSpace, + xor: xor +}); + +const _clearColor = /*@__PURE__*/ new Color4(); + +/** + * This renderer module manages the background. + * + * @private + * @augments DataMap + */ +class Background extends DataMap { + + /** + * Constructs a new background management component. + * + * @param {Renderer} renderer - The renderer. + * @param {Nodes} nodes - Renderer component for managing nodes related logic. + */ + constructor( renderer, nodes ) { + + super(); + + /** + * The renderer. + * + * @type {Renderer} + */ + this.renderer = renderer; + + /** + * Renderer component for managing nodes related logic. + * + * @type {Nodes} + */ + this.nodes = nodes; + + } + + /** + * Updates the background for the given scene. Depending on how `Scene.background` + * or `Scene.backgroundNode` are configured, this method might configure a simple clear + * or add a mesh to the render list for rendering the background as a textured plane + * or skybox. + * + * @param {Scene} scene - The scene. + * @param {RenderList} renderList - The current render list. + * @param {RenderContext} renderContext - The current render context. + */ + update( scene, renderList, renderContext ) { + + const renderer = this.renderer; + const background = this.nodes.getBackgroundNode( scene ) || scene.background; + + let forceClear = false; + + if ( background === null ) { + + // no background settings, use clear color configuration from the renderer + + renderer._clearColor.getRGB( _clearColor ); + _clearColor.a = renderer._clearColor.a; + + } else if ( background.isColor === true ) { + + // background is an opaque color + + background.getRGB( _clearColor ); + _clearColor.a = 1; + + forceClear = true; + + } else if ( background.isNode === true ) { + + const sceneData = this.get( scene ); + const backgroundNode = background; + + _clearColor.copy( renderer._clearColor ); + + let backgroundMesh = sceneData.backgroundMesh; + + if ( backgroundMesh === undefined ) { + + const backgroundMeshNode = context( vec4( backgroundNode ).mul( backgroundIntensity ), { + // @TODO: Add Texture2D support using node context + getUV: () => backgroundRotation.mul( normalWorldGeometry ), + getTextureLevel: () => backgroundBlurriness + } ); + + let viewProj = modelViewProjection; + viewProj = viewProj.setZ( viewProj.w ); + + const nodeMaterial = new NodeMaterial(); + nodeMaterial.name = 'Background.material'; + nodeMaterial.side = BackSide; + nodeMaterial.depthTest = false; + nodeMaterial.depthWrite = false; + nodeMaterial.allowOverride = false; + nodeMaterial.fog = false; + nodeMaterial.lights = false; + nodeMaterial.vertexNode = viewProj; + nodeMaterial.colorNode = backgroundMeshNode; + + sceneData.backgroundMeshNode = backgroundMeshNode; + sceneData.backgroundMesh = backgroundMesh = new Mesh( new SphereGeometry( 1, 32, 32 ), nodeMaterial ); + backgroundMesh.frustumCulled = false; + backgroundMesh.name = 'Background.mesh'; + + backgroundMesh.onBeforeRender = function ( renderer, scene, camera ) { + + this.matrixWorld.copyPosition( camera.matrixWorld ); + + }; + + function onBackgroundDispose() { + + background.removeEventListener( 'dispose', onBackgroundDispose ); + + backgroundMesh.material.dispose(); + backgroundMesh.geometry.dispose(); + + } + + background.addEventListener( 'dispose', onBackgroundDispose ); + + } + + const backgroundCacheKey = backgroundNode.getCacheKey(); + + if ( sceneData.backgroundCacheKey !== backgroundCacheKey ) { + + sceneData.backgroundMeshNode.node = vec4( backgroundNode ).mul( backgroundIntensity ); + sceneData.backgroundMeshNode.needsUpdate = true; + + backgroundMesh.material.needsUpdate = true; + + sceneData.backgroundCacheKey = backgroundCacheKey; + + } + + renderList.unshift( backgroundMesh, backgroundMesh.geometry, backgroundMesh.material, 0, 0, null, null ); + + } else { + + console.error( 'THREE.Renderer: Unsupported background configuration.', background ); + + } + + // + + const environmentBlendMode = renderer.xr.getEnvironmentBlendMode(); + + if ( environmentBlendMode === 'additive' ) { + + _clearColor.set( 0, 0, 0, 1 ); + + } else if ( environmentBlendMode === 'alpha-blend' ) { + + _clearColor.set( 0, 0, 0, 0 ); + + } + + // + + if ( renderer.autoClear === true || forceClear === true ) { + + const clearColorValue = renderContext.clearColorValue; + + clearColorValue.r = _clearColor.r; + clearColorValue.g = _clearColor.g; + clearColorValue.b = _clearColor.b; + clearColorValue.a = _clearColor.a; + + // premultiply alpha + + if ( renderer.backend.isWebGLBackend === true || renderer.alpha === true ) { + + clearColorValue.r *= clearColorValue.a; + clearColorValue.g *= clearColorValue.a; + clearColorValue.b *= clearColorValue.a; + + } + + // + + renderContext.depthClearValue = renderer._clearDepth; + renderContext.stencilClearValue = renderer._clearStencil; + + renderContext.clearColor = renderer.autoClearColor === true; + renderContext.clearDepth = renderer.autoClearDepth === true; + renderContext.clearStencil = renderer.autoClearStencil === true; + + } else { + + renderContext.clearColor = false; + renderContext.clearDepth = false; + renderContext.clearStencil = false; + + } + + } + +} + +let _id$6 = 0; + +/** + * A bind group represents a collection of bindings and thus a collection + * or resources. Bind groups are assigned to pipelines to provide them + * with the required resources (like uniform buffers or textures). + * + * @private + */ +class BindGroup { + + /** + * Constructs a new bind group. + * + * @param {string} name - The bind group's name. + * @param {Array} bindings - An array of bindings. + * @param {number} index - The group index. + * @param {Array} bindingsReference - An array of reference bindings. + */ + constructor( name = '', bindings = [], index = 0, bindingsReference = [] ) { + + /** + * The bind group's name. + * + * @type {string} + */ + this.name = name; + + /** + * An array of bindings. + * + * @type {Array} + */ + this.bindings = bindings; + + /** + * The group index. + * + * @type {number} + */ + this.index = index; + + /** + * An array of reference bindings. + * + * @type {Array} + */ + this.bindingsReference = bindingsReference; + + /** + * The group's ID. + * + * @type {number} + */ + this.id = _id$6 ++; + + } + +} + +/** + * This module represents the state of a node builder after it was + * used to build the nodes for a render object. The state holds the + * results of the build for further processing in the renderer. + * + * Render objects with identical cache keys share the same node builder state. + * + * @private + */ +class NodeBuilderState { + + /** + * Constructs a new node builder state. + * + * @param {string} vertexShader - The native vertex shader code. + * @param {string} fragmentShader - The native fragment shader code. + * @param {string} computeShader - The native compute shader code. + * @param {Array} nodeAttributes - An array of node attributes. + * @param {Array} bindings - An array of bind groups. + * @param {Array} updateNodes - An array of nodes that implement their `update()` method. + * @param {Array} updateBeforeNodes - An array of nodes that implement their `updateBefore()` method. + * @param {Array} updateAfterNodes - An array of nodes that implement their `updateAfter()` method. + * @param {NodeMaterialObserver} observer - A node material observer. + * @param {Array} transforms - An array with transform attribute objects. Only relevant when using compute shaders with WebGL 2. + */ + constructor( vertexShader, fragmentShader, computeShader, nodeAttributes, bindings, updateNodes, updateBeforeNodes, updateAfterNodes, observer, transforms = [] ) { + + /** + * The native vertex shader code. + * + * @type {string} + */ + this.vertexShader = vertexShader; + + /** + * The native fragment shader code. + * + * @type {string} + */ + this.fragmentShader = fragmentShader; + + /** + * The native compute shader code. + * + * @type {string} + */ + this.computeShader = computeShader; + + /** + * An array with transform attribute objects. + * Only relevant when using compute shaders with WebGL 2. + * + * @type {Array} + */ + this.transforms = transforms; + + /** + * An array of node attributes representing + * the attributes of the shaders. + * + * @type {Array} + */ + this.nodeAttributes = nodeAttributes; + + /** + * An array of bind groups representing the uniform or storage + * buffers, texture or samplers of the shader. + * + * @type {Array} + */ + this.bindings = bindings; + + /** + * An array of nodes that implement their `update()` method. + * + * @type {Array} + */ + this.updateNodes = updateNodes; + + /** + * An array of nodes that implement their `updateBefore()` method. + * + * @type {Array} + */ + this.updateBeforeNodes = updateBeforeNodes; + + /** + * An array of nodes that implement their `updateAfter()` method. + * + * @type {Array} + */ + this.updateAfterNodes = updateAfterNodes; + + /** + * A node material observer. + * + * @type {NodeMaterialObserver} + */ + this.observer = observer; + + /** + * How often this state is used by render objects. + * + * @type {number} + */ + this.usedTimes = 0; + + } + + /** + * This method is used to create a array of bind groups based + * on the existing bind groups of this state. Shared groups are + * not cloned. + * + * @return {Array} A array of bind groups. + */ + createBindings() { + + const bindings = []; + + for ( const instanceGroup of this.bindings ) { + + const shared = instanceGroup.bindings[ 0 ].groupNode.shared; // All bindings in the group must have the same groupNode. + + if ( shared !== true ) { + + const bindingsGroup = new BindGroup( instanceGroup.name, [], instanceGroup.index, instanceGroup ); + bindings.push( bindingsGroup ); + + for ( const instanceBinding of instanceGroup.bindings ) { + + bindingsGroup.bindings.push( instanceBinding.clone() ); + + } + + } else { + + bindings.push( instanceGroup ); + + } + + } + + return bindings; + + } + +} + +/** + * {@link NodeBuilder} is going to create instances of this class during the build process + * of nodes. They represent the final shader attributes that are going to be generated + * by the builder. Arrays of node attributes is maintained in {@link NodeBuilder#attributes} + * and {@link NodeBuilder#bufferAttributes} for this purpose. + */ +class NodeAttribute { + + /** + * Constructs a new node attribute. + * + * @param {string} name - The name of the attribute. + * @param {string} type - The type of the attribute. + * @param {?Node} node - An optional reference to the node. + */ + constructor( name, type, node = null ) { + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isNodeAttribute = true; + + /** + * The name of the attribute. + * + * @type {string} + */ + this.name = name; + + /** + * The type of the attribute. + * + * @type {string} + */ + this.type = type; + + /** + * An optional reference to the node. + * + * @type {?Node} + * @default null + */ + this.node = node; + + } + +} + +/** + * {@link NodeBuilder} is going to create instances of this class during the build process + * of nodes. They represent the final shader uniforms that are going to be generated + * by the builder. A dictionary of node uniforms is maintained in {@link NodeBuilder#uniforms} + * for this purpose. + */ +class NodeUniform { + + /** + * Constructs a new node uniform. + * + * @param {string} name - The name of the uniform. + * @param {string} type - The type of the uniform. + * @param {UniformNode} node - An reference to the node. + */ + constructor( name, type, node ) { + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isNodeUniform = true; + + /** + * The name of the uniform. + * + * @type {string} + */ + this.name = name; + + /** + * The type of the uniform. + * + * @type {string} + */ + this.type = type; + + /** + * An reference to the node. + * + * @type {UniformNode} + */ + this.node = node.getSelf(); + + } + + /** + * The value of the uniform node. + * + * @type {any} + */ + get value() { + + return this.node.value; + + } + + set value( val ) { + + this.node.value = val; + + } + + /** + * The id of the uniform node. + * + * @type {number} + */ + get id() { + + return this.node.id; + + } + + /** + * The uniform node's group. + * + * @type {UniformGroupNode} + */ + get groupNode() { + + return this.node.groupNode; + + } + +} + +/** + * {@link NodeBuilder} is going to create instances of this class during the build process + * of nodes. They represent the final shader variables that are going to be generated + * by the builder. A dictionary of node variables is maintained in {@link NodeBuilder#vars} for + * this purpose. + */ +class NodeVar { + + /** + * Constructs a new node variable. + * + * @param {string} name - The name of the variable. + * @param {string} type - The type of the variable. + * @param {boolean} [readOnly=false] - The read-only flag. + * @param {?number} [count=null] - The size. + */ + constructor( name, type, readOnly = false, count = null ) { + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isNodeVar = true; + + /** + * The name of the variable. + * + * @type {string} + */ + this.name = name; + + /** + * The type of the variable. + * + * @type {string} + */ + this.type = type; + + /** + * The read-only flag. + * + * @type {boolean} + */ + this.readOnly = readOnly; + + /** + * The size. + * + * @type {?number} + */ + this.count = count; + + } + +} + +/** + * {@link NodeBuilder} is going to create instances of this class during the build process + * of nodes. They represent the final shader varyings that are going to be generated + * by the builder. An array of node varyings is maintained in {@link NodeBuilder#varyings} for + * this purpose. + * + * @augments NodeVar + */ +class NodeVarying extends NodeVar { + + /** + * Constructs a new node varying. + * + * @param {string} name - The name of the varying. + * @param {string} type - The type of the varying. + * @param {?string} interpolationType - The interpolation type of the varying. + * @param {?string} interpolationSampling - The interpolation sampling type of the varying. + */ + constructor( name, type, interpolationType = null, interpolationSampling = null ) { + + super( name, type ); + + /** + * Whether this varying requires interpolation or not. This property can be used + * to check if the varying can be optimized for a variable. + * + * @type {boolean} + * @default false + */ + this.needsInterpolation = false; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isNodeVarying = true; + + /** + * The interpolation type of the varying data. + * + * @type {?string} + * @default null + */ + this.interpolationType = interpolationType; + + /** + * The interpolation sampling type of varying data. + * + * @type {?string} + * @default null + */ + this.interpolationSampling = interpolationSampling; + + } + +} + +/** + * {@link NodeBuilder} is going to create instances of this class during the build process + * of nodes. They represent user-defined, native shader code portions that are going to be + * injected by the builder. A dictionary of node codes is maintained in {@link NodeBuilder#codes} + * for this purpose. + */ +class NodeCode { + + /** + * Constructs a new code node. + * + * @param {string} name - The name of the code. + * @param {string} type - The node type. + * @param {string} [code=''] - The native shader code. + */ + constructor( name, type, code = '' ) { + + /** + * The name of the code. + * + * @type {string} + */ + this.name = name; + + /** + * The node type. + * + * @type {string} + */ + this.type = type; + + /** + * The native shader code. + * + * @type {string} + * @default '' + */ + this.code = code; + + Object.defineProperty( this, 'isNodeCode', { value: true } ); + + } + +} + +let _id$5 = 0; + +/** + * This utility class is used in {@link NodeBuilder} as an internal + * cache data structure for node data. + */ +class NodeCache { + + /** + * Constructs a new node cache. + * + * @param {?NodeCache} parent - A reference to a parent cache. + */ + constructor( parent = null ) { + + /** + * The id of the cache. + * + * @type {number} + * @readonly + */ + this.id = _id$5 ++; + + /** + * A weak map for managing node data. + * + * @type {WeakMap} + */ + this.nodesData = new WeakMap(); + + /** + * Reference to a parent node cache. + * + * @type {?NodeCache} + * @default null + */ + this.parent = parent; + + } + + /** + * Returns the data for the given node. + * + * @param {Node} node - The node. + * @return {?Object} The data for the node. + */ + getData( node ) { + + let data = this.nodesData.get( node ); + + if ( data === undefined && this.parent !== null ) { + + data = this.parent.getData( node ); + + } + + return data; + + } + + /** + * Sets the data for a given node. + * + * @param {Node} node - The node. + * @param {Object} data - The data that should be cached. + */ + setData( node, data ) { + + this.nodesData.set( node, data ); + + } + +} + +class StructType { + + constructor( name, members ) { + + this.name = name; + this.members = members; + this.output = false; + + } + +} + +/** + * Abstract base class for uniforms. + * + * @abstract + * @private + */ +class Uniform { + + /** + * Constructs a new uniform. + * + * @param {string} name - The uniform's name. + * @param {any} value - The uniform's value. + */ + constructor( name, value ) { + + /** + * The uniform's name. + * + * @type {string} + */ + this.name = name; + + /** + * The uniform's value. + * + * @type {any} + */ + this.value = value; + + /** + * Used to build the uniform buffer according to the STD140 layout. + * Derived uniforms will set this property to a data type specific + * value. + * + * @type {number} + */ + this.boundary = 0; + + /** + * The item size. Derived uniforms will set this property to a data + * type specific value. + * + * @type {number} + */ + this.itemSize = 0; + + /** + * This property is set by {@link UniformsGroup} and marks + * the start position in the uniform buffer. + * + * @type {number} + */ + this.offset = 0; + + } + + /** + * Sets the uniform's value. + * + * @param {any} value - The value to set. + */ + setValue( value ) { + + this.value = value; + + } + + /** + * Returns the uniform's value. + * + * @return {any} The value. + */ + getValue() { + + return this.value; + + } + +} + +/** + * Represents a Number uniform. + * + * @private + * @augments Uniform + */ +class NumberUniform extends Uniform { + + /** + * Constructs a new Number uniform. + * + * @param {string} name - The uniform's name. + * @param {number} value - The uniform's value. + */ + constructor( name, value = 0 ) { + + super( name, value ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isNumberUniform = true; + + this.boundary = 4; + this.itemSize = 1; + + } + +} + +/** + * Represents a Vector2 uniform. + * + * @private + * @augments Uniform + */ +class Vector2Uniform extends Uniform { + + /** + * Constructs a new Number uniform. + * + * @param {string} name - The uniform's name. + * @param {Vector2} value - The uniform's value. + */ + constructor( name, value = new Vector2() ) { + + super( name, value ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isVector2Uniform = true; + + this.boundary = 8; + this.itemSize = 2; + + } + +} + +/** + * Represents a Vector3 uniform. + * + * @private + * @augments Uniform + */ +class Vector3Uniform extends Uniform { + + /** + * Constructs a new Number uniform. + * + * @param {string} name - The uniform's name. + * @param {Vector3} value - The uniform's value. + */ + constructor( name, value = new Vector3() ) { + + super( name, value ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isVector3Uniform = true; + + this.boundary = 16; + this.itemSize = 3; + + } + +} + +/** + * Represents a Vector4 uniform. + * + * @private + * @augments Uniform + */ +class Vector4Uniform extends Uniform { + + /** + * Constructs a new Number uniform. + * + * @param {string} name - The uniform's name. + * @param {Vector4} value - The uniform's value. + */ + constructor( name, value = new Vector4() ) { + + super( name, value ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isVector4Uniform = true; + + this.boundary = 16; + this.itemSize = 4; + + } + +} + +/** + * Represents a Color uniform. + * + * @private + * @augments Uniform + */ +class ColorUniform extends Uniform { + + /** + * Constructs a new Number uniform. + * + * @param {string} name - The uniform's name. + * @param {Color} value - The uniform's value. + */ + constructor( name, value = new Color() ) { + + super( name, value ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isColorUniform = true; + + this.boundary = 16; + this.itemSize = 3; + + } + +} + +/** + * Represents a Matrix2 uniform. + * + * @private + * @augments Uniform + */ +class Matrix2Uniform extends Uniform { + + /** + * Constructs a new Number uniform. + * + * @param {string} name - The uniform's name. + * @param {Matrix2} value - The uniform's value. + */ + constructor( name, value = new Matrix2() ) { + + super( name, value ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMatrix2Uniform = true; + + this.boundary = 8; + this.itemSize = 4; + + } + +} + + +/** + * Represents a Matrix3 uniform. + * + * @private + * @augments Uniform + */ +class Matrix3Uniform extends Uniform { + + /** + * Constructs a new Number uniform. + * + * @param {string} name - The uniform's name. + * @param {Matrix3} value - The uniform's value. + */ + constructor( name, value = new Matrix3() ) { + + super( name, value ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMatrix3Uniform = true; + + this.boundary = 48; + this.itemSize = 12; + + } + +} + +/** + * Represents a Matrix4 uniform. + * + * @private + * @augments Uniform + */ +class Matrix4Uniform extends Uniform { + + /** + * Constructs a new Number uniform. + * + * @param {string} name - The uniform's name. + * @param {Matrix4} value - The uniform's value. + */ + constructor( name, value = new Matrix4() ) { + + super( name, value ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMatrix4Uniform = true; + + this.boundary = 64; + this.itemSize = 16; + + } + +} + +/** + * A special form of Number uniform binding type. + * It's value is managed by a node object. + * + * @private + * @augments NumberUniform + */ +class NumberNodeUniform extends NumberUniform { + + /** + * Constructs a new node-based Number uniform. + * + * @param {NodeUniform} nodeUniform - The node uniform. + */ + constructor( nodeUniform ) { + + super( nodeUniform.name, nodeUniform.value ); + + /** + * The node uniform. + * + * @type {NodeUniform} + */ + this.nodeUniform = nodeUniform; + + } + + /** + * Overwritten to return the value of the node uniform. + * + * @return {number} The value. + */ + getValue() { + + return this.nodeUniform.value; + + } + + /** + * Returns the node uniform data type. + * + * @return {string} The data type. + */ + getType() { + + return this.nodeUniform.type; + + } + +} + +/** + * A special form of Vector2 uniform binding type. + * It's value is managed by a node object. + * + * @private + * @augments Vector2Uniform + */ +class Vector2NodeUniform extends Vector2Uniform { + + /** + * Constructs a new node-based Vector2 uniform. + * + * @param {NodeUniform} nodeUniform - The node uniform. + */ + constructor( nodeUniform ) { + + super( nodeUniform.name, nodeUniform.value ); + + /** + * The node uniform. + * + * @type {NodeUniform} + */ + this.nodeUniform = nodeUniform; + + } + + /** + * Overwritten to return the value of the node uniform. + * + * @return {Vector2} The value. + */ + getValue() { + + return this.nodeUniform.value; + + } + + /** + * Returns the node uniform data type. + * + * @return {string} The data type. + */ + getType() { + + return this.nodeUniform.type; + + } + +} + +/** + * A special form of Vector3 uniform binding type. + * It's value is managed by a node object. + * + * @private + * @augments Vector3Uniform + */ +class Vector3NodeUniform extends Vector3Uniform { + + /** + * Constructs a new node-based Vector3 uniform. + * + * @param {NodeUniform} nodeUniform - The node uniform. + */ + constructor( nodeUniform ) { + + super( nodeUniform.name, nodeUniform.value ); + + /** + * The node uniform. + * + * @type {NodeUniform} + */ + this.nodeUniform = nodeUniform; + + } + + /** + * Overwritten to return the value of the node uniform. + * + * @return {Vector3} The value. + */ + getValue() { + + return this.nodeUniform.value; + + } + + /** + * Returns the node uniform data type. + * + * @return {string} The data type. + */ + getType() { + + return this.nodeUniform.type; + + } + +} + +/** + * A special form of Vector4 uniform binding type. + * It's value is managed by a node object. + * + * @private + * @augments Vector4Uniform + */ +class Vector4NodeUniform extends Vector4Uniform { + + /** + * Constructs a new node-based Vector4 uniform. + * + * @param {NodeUniform} nodeUniform - The node uniform. + */ + constructor( nodeUniform ) { + + super( nodeUniform.name, nodeUniform.value ); + + /** + * The node uniform. + * + * @type {NodeUniform} + */ + this.nodeUniform = nodeUniform; + + } + + /** + * Overwritten to return the value of the node uniform. + * + * @return {Vector4} The value. + */ + getValue() { + + return this.nodeUniform.value; + + } + + /** + * Returns the node uniform data type. + * + * @return {string} The data type. + */ + getType() { + + return this.nodeUniform.type; + + } + +} + +/** + * A special form of Color uniform binding type. + * It's value is managed by a node object. + * + * @private + * @augments ColorUniform + */ +class ColorNodeUniform extends ColorUniform { + + /** + * Constructs a new node-based Color uniform. + * + * @param {NodeUniform} nodeUniform - The node uniform. + */ + constructor( nodeUniform ) { + + super( nodeUniform.name, nodeUniform.value ); + + /** + * The node uniform. + * + * @type {NodeUniform} + */ + this.nodeUniform = nodeUniform; + + } + + /** + * Overwritten to return the value of the node uniform. + * + * @return {Color} The value. + */ + getValue() { + + return this.nodeUniform.value; + + } + + /** + * Returns the node uniform data type. + * + * @return {string} The data type. + */ + getType() { + + return this.nodeUniform.type; + + } + +} + + +/** + * A special form of Matrix2 uniform binding type. + * It's value is managed by a node object. + * + * @private + * @augments Matrix2Uniform + */ +class Matrix2NodeUniform extends Matrix2Uniform { + + /** + * Constructs a new node-based Matrix2 uniform. + * + * @param {NodeUniform} nodeUniform - The node uniform. + */ + constructor( nodeUniform ) { + + super( nodeUniform.name, nodeUniform.value ); + + /** + * The node uniform. + * + * @type {NodeUniform} + */ + this.nodeUniform = nodeUniform; + + } + + /** + * Overwritten to return the value of the node uniform. + * + * @return {Matrix2} The value. + */ + getValue() { + + return this.nodeUniform.value; + + } + + /** + * Returns the node uniform data type. + * + * @return {string} The data type. + */ + getType() { + + return this.nodeUniform.type; + + } + +} + +/** + * A special form of Matrix3 uniform binding type. + * It's value is managed by a node object. + * + * @private + * @augments Matrix3Uniform + */ +class Matrix3NodeUniform extends Matrix3Uniform { + + /** + * Constructs a new node-based Matrix3 uniform. + * + * @param {NodeUniform} nodeUniform - The node uniform. + */ + constructor( nodeUniform ) { + + super( nodeUniform.name, nodeUniform.value ); + + /** + * The node uniform. + * + * @type {NodeUniform} + */ + this.nodeUniform = nodeUniform; + + } + + /** + * Overwritten to return the value of the node uniform. + * + * @return {Matrix3} The value. + */ + getValue() { + + return this.nodeUniform.value; + + } + + /** + * Returns the node uniform data type. + * + * @return {string} The data type. + */ + getType() { + + return this.nodeUniform.type; + + } + +} + +/** + * A special form of Matrix4 uniform binding type. + * It's value is managed by a node object. + * + * @private + * @augments Matrix4Uniform + */ +class Matrix4NodeUniform extends Matrix4Uniform { + + /** + * Constructs a new node-based Matrix4 uniform. + * + * @param {NodeUniform} nodeUniform - The node uniform. + */ + constructor( nodeUniform ) { + + super( nodeUniform.name, nodeUniform.value ); + + /** + * The node uniform. + * + * @type {NodeUniform} + */ + this.nodeUniform = nodeUniform; + + } + + /** + * Overwritten to return the value of the node uniform. + * + * @return {Matrix4} The value. + */ + getValue() { + + return this.nodeUniform.value; + + } + + /** + * Returns the node uniform data type. + * + * @return {string} The data type. + */ + getType() { + + return this.nodeUniform.type; + + } + +} + +const rendererCache = new WeakMap(); + +const typeFromArray = new Map( [ + [ Int8Array, 'int' ], + [ Int16Array, 'int' ], + [ Int32Array, 'int' ], + [ Uint8Array, 'uint' ], + [ Uint16Array, 'uint' ], + [ Uint32Array, 'uint' ], + [ Float32Array, 'float' ] +] ); + +const toFloat = ( value ) => { + + if ( /e/g.test( value ) ) { + + return String( value ).replace( /\+/g, '' ); + + } else { + + value = Number( value ); + + return value + ( value % 1 ? '' : '.0' ); + + } + +}; + +/** + * Base class for builders which generate a shader program based + * on a 3D object and its node material definition. + */ +class NodeBuilder { + + /** + * Constructs a new node builder. + * + * @param {Object3D} object - The 3D object. + * @param {Renderer} renderer - The current renderer. + * @param {NodeParser} parser - A reference to a node parser. + */ + constructor( object, renderer, parser ) { + + /** + * The 3D object. + * + * @type {Object3D} + */ + this.object = object; + + /** + * The material of the 3D object. + * + * @type {?Material} + */ + this.material = ( object && object.material ) || null; + + /** + * The geometry of the 3D object. + * + * @type {?BufferGeometry} + */ + this.geometry = ( object && object.geometry ) || null; + + /** + * The current renderer. + * + * @type {Renderer} + */ + this.renderer = renderer; + + /** + * A reference to a node parser. + * + * @type {NodeParser} + */ + this.parser = parser; + + /** + * The scene the 3D object belongs to. + * + * @type {?Scene} + * @default null + */ + this.scene = null; + + /** + * The camera the 3D object is rendered with. + * + * @type {?Camera} + * @default null + */ + this.camera = null; + + /** + * A list of all nodes the builder is processing + * for this 3D object. + * + * @type {Array} + */ + this.nodes = []; + + /** + * A list of all sequential nodes. + * + * @type {Array} + */ + this.sequentialNodes = []; + + /** + * A list of all nodes which {@link Node#update} method should be executed. + * + * @type {Array} + */ + this.updateNodes = []; + + /** + * A list of all nodes which {@link Node#updateBefore} method should be executed. + * + * @type {Array} + */ + this.updateBeforeNodes = []; + + /** + * A list of all nodes which {@link Node#updateAfter} method should be executed. + * + * @type {Array} + */ + this.updateAfterNodes = []; + + /** + * A dictionary that assigns each node to a unique hash. + * + * @type {Object} + */ + this.hashNodes = {}; + + /** + * A reference to a node material observer. + * + * @type {?NodeMaterialObserver} + * @default null + */ + this.observer = null; + + /** + * A reference to the current lights node. + * + * @type {?LightsNode} + * @default null + */ + this.lightsNode = null; + + /** + * A reference to the current environment node. + * + * @type {?Node} + * @default null + */ + this.environmentNode = null; + + /** + * A reference to the current fog node. + * + * @type {?FogNode} + * @default null + */ + this.fogNode = null; + + /** + * The current clipping context. + * + * @type {?ClippingContext} + */ + this.clippingContext = null; + + /** + * The generated vertex shader. + * + * @type {?string} + */ + this.vertexShader = null; + + /** + * The generated fragment shader. + * + * @type {?string} + */ + this.fragmentShader = null; + + /** + * The generated compute shader. + * + * @type {?string} + */ + this.computeShader = null; + + /** + * Nodes used in the primary flow of code generation. + * + * @type {Object>} + */ + this.flowNodes = { vertex: [], fragment: [], compute: [] }; + + /** + * Nodes code from `.flowNodes`. + * + * @type {Object} + */ + this.flowCode = { vertex: '', fragment: '', compute: '' }; + + /** + * This dictionary holds the node uniforms of the builder. + * The uniforms are maintained in an array for each shader stage. + * + * @type {Object} + */ + this.uniforms = { vertex: [], fragment: [], compute: [], index: 0 }; + + /** + * This dictionary holds the output structs of the builder. + * The structs are maintained in an array for each shader stage. + * + * @type {Object} + */ + this.structs = { vertex: [], fragment: [], compute: [], index: 0 }; + + /** + * This dictionary holds the bindings for each shader stage. + * + * @type {Object} + */ + this.bindings = { vertex: {}, fragment: {}, compute: {} }; + + /** + * This dictionary maintains the binding indices per bind group. + * + * @type {Object} + */ + this.bindingsIndexes = {}; + + /** + * Reference to the array of bind groups. + * + * @type {?Array} + */ + this.bindGroups = null; + + /** + * This array holds the node attributes of this builder + * created via {@link AttributeNode}. + * + * @type {Array} + */ + this.attributes = []; + + /** + * This array holds the node attributes of this builder + * created via {@link BufferAttributeNode}. + * + * @type {Array} + */ + this.bufferAttributes = []; + + /** + * This array holds the node varyings of this builder. + * + * @type {Array} + */ + this.varyings = []; + + /** + * This dictionary holds the (native) node codes of this builder. + * The codes are maintained in an array for each shader stage. + * + * @type {Object>} + */ + this.codes = {}; + + /** + * This dictionary holds the node variables of this builder. + * The variables are maintained in an array for each shader stage. + * This dictionary is also used to count the number of variables + * according to their type (const, vars). + * + * @type {Object|number>} + */ + this.vars = {}; + + /** + * This dictionary holds the declarations for each shader stage. + * + * @type {Object} + */ + this.declarations = {}; + + /** + * Current code flow. + * All code generated in this stack will be stored in `.flow`. + * + * @type {{code: string}} + */ + this.flow = { code: '' }; + + /** + * A chain of nodes. + * Used to check recursive calls in node-graph. + * + * @type {Array} + */ + this.chaining = []; + + /** + * The current stack. + * This reflects the current process in the code block hierarchy, + * it is useful to know if the current process is inside a conditional for example. + * + * @type {StackNode} + */ + this.stack = stack(); + + /** + * List of stack nodes. + * The current stack hierarchy is stored in an array. + * + * @type {Array} + */ + this.stacks = []; + + /** + * A tab value. Used for shader string generation. + * + * @type {string} + * @default '\t' + */ + this.tab = '\t'; + + /** + * Reference to the current function node. + * + * @type {?FunctionNode} + * @default null + */ + this.currentFunctionNode = null; + + /** + * The builder's context. + * + * @type {Object} + */ + this.context = { + material: this.material + }; + + /** + * The builder's cache. + * + * @type {NodeCache} + */ + this.cache = new NodeCache(); + + /** + * Since the {@link NodeBuilder#cache} might be temporarily + * overwritten by other caches, this member retains the reference + * to the builder's own cache. + * + * @type {NodeCache} + * @default this.cache + */ + this.globalCache = this.cache; + + this.flowsData = new WeakMap(); + + /** + * The current shader stage. + * + * @type {?('vertex'|'fragment'|'compute'|'any')} + */ + this.shaderStage = null; + + /** + * The current build stage. + * + * @type {?('setup'|'analyze'|'generate')} + */ + this.buildStage = null; + + /** + * The sub-build layers. + * + * @type {Array} + * @default [] + */ + this.subBuildLayers = []; + + /** + * The current stack of nodes. + * + * @type {?StackNode} + * @default null + */ + this.currentStack = null; + + /** + * The current sub-build TSL function(Fn). + * + * @type {?string} + * @default null + */ + this.subBuildFn = null; + + } + + /** + * Returns the bind groups of the current renderer. + * + * @return {ChainMap} The cache. + */ + getBindGroupsCache() { + + let bindGroupsCache = rendererCache.get( this.renderer ); + + if ( bindGroupsCache === undefined ) { + + bindGroupsCache = new ChainMap(); + + rendererCache.set( this.renderer, bindGroupsCache ); + + } + + return bindGroupsCache; + + } + + /** + * Factory method for creating an instance of {@link RenderTarget} with the given + * dimensions and options. + * + * @param {number} width - The width of the render target. + * @param {number} height - The height of the render target. + * @param {Object} options - The options of the render target. + * @return {RenderTarget} The render target. + */ + createRenderTarget( width, height, options ) { + + return new RenderTarget( width, height, options ); + + } + + /** + * Factory method for creating an instance of {@link CubeRenderTarget} with the given + * dimensions and options. + * + * @param {number} size - The size of the cube render target. + * @param {Object} options - The options of the cube render target. + * @return {CubeRenderTarget} The cube render target. + */ + createCubeRenderTarget( size, options ) { + + return new CubeRenderTarget( size, options ); + + } + + /** + * Whether the given node is included in the internal array of nodes or not. + * + * @param {Node} node - The node to test. + * @return {boolean} Whether the given node is included in the internal array of nodes or not. + */ + includes( node ) { + + return this.nodes.includes( node ); + + } + + /** + * Returns the output struct name which is required by + * {@link OutputStructNode}. + * + * @abstract + * @return {string} The name of the output struct. + */ + getOutputStructName() {} + + /** + * Returns a bind group for the given group name and binding. + * + * @private + * @param {string} groupName - The group name. + * @param {Array} bindings - List of bindings. + * @return {BindGroup} The bind group + */ + _getBindGroup( groupName, bindings ) { + + const bindGroupsCache = this.getBindGroupsCache(); + + // + + const bindingsArray = []; + + let sharedGroup = true; + + for ( const binding of bindings ) { + + bindingsArray.push( binding ); + + sharedGroup = sharedGroup && binding.groupNode.shared !== true; + + } + + // + + let bindGroup; + + if ( sharedGroup ) { + + bindGroup = bindGroupsCache.get( bindingsArray ); + + if ( bindGroup === undefined ) { + + bindGroup = new BindGroup( groupName, bindingsArray, this.bindingsIndexes[ groupName ].group, bindingsArray ); + + bindGroupsCache.set( bindingsArray, bindGroup ); + + } + + } else { + + bindGroup = new BindGroup( groupName, bindingsArray, this.bindingsIndexes[ groupName ].group, bindingsArray ); + + } + + return bindGroup; + + } + + /** + * Returns an array of node uniform groups for the given group name and shader stage. + * + * @param {string} groupName - The group name. + * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage. + * @return {Array} The array of node uniform groups. + */ + getBindGroupArray( groupName, shaderStage ) { + + const bindings = this.bindings[ shaderStage ]; + + let bindGroup = bindings[ groupName ]; + + if ( bindGroup === undefined ) { + + if ( this.bindingsIndexes[ groupName ] === undefined ) { + + this.bindingsIndexes[ groupName ] = { binding: 0, group: Object.keys( this.bindingsIndexes ).length }; + + } + + bindings[ groupName ] = bindGroup = []; + + } + + return bindGroup; + + } + + /** + * Returns a list bindings of all shader stages separated by groups. + * + * @return {Array} The list of bindings. + */ + getBindings() { + + let bindingsGroups = this.bindGroups; + + if ( bindingsGroups === null ) { + + const groups = {}; + const bindings = this.bindings; + + for ( const shaderStage of shaderStages ) { + + for ( const groupName in bindings[ shaderStage ] ) { + + const uniforms = bindings[ shaderStage ][ groupName ]; + + const groupUniforms = groups[ groupName ] || ( groups[ groupName ] = [] ); + groupUniforms.push( ...uniforms ); + + } + + } + + bindingsGroups = []; + + for ( const groupName in groups ) { + + const group = groups[ groupName ]; + + const bindingsGroup = this._getBindGroup( groupName, group ); + + bindingsGroups.push( bindingsGroup ); + + } + + this.bindGroups = bindingsGroups; + + } + + return bindingsGroups; + + } + + /** + * Sorts the bind groups and updates {@link NodeBuilder#bindingsIndexes}. + */ + sortBindingGroups() { + + const bindingsGroups = this.getBindings(); + + bindingsGroups.sort( ( a, b ) => ( a.bindings[ 0 ].groupNode.order - b.bindings[ 0 ].groupNode.order ) ); + + for ( let i = 0; i < bindingsGroups.length; i ++ ) { + + const bindingGroup = bindingsGroups[ i ]; + this.bindingsIndexes[ bindingGroup.name ].group = i; + + bindingGroup.index = i; + + } + + } + + /** + * The builder maintains each node in a hash-based dictionary. + * This method sets the given node (value) with the given hash (key) into this dictionary. + * + * @param {Node} node - The node to add. + * @param {number} hash - The hash of the node. + */ + setHashNode( node, hash ) { + + this.hashNodes[ hash ] = node; + + } + + /** + * Adds a node to this builder. + * + * @param {Node} node - The node to add. + */ + addNode( node ) { + + if ( this.nodes.includes( node ) === false ) { + + this.nodes.push( node ); + + this.setHashNode( node, node.getHash( this ) ); + + } + + } + + /** + * It is used to add Nodes that will be used as FRAME and RENDER events, + * and need to follow a certain sequence in the calls to work correctly. + * This function should be called after 'setup()' in the 'build()' process to ensure that the child nodes are processed first. + * + * @param {Node} node - The node to add. + */ + addSequentialNode( node ) { + + if ( this.sequentialNodes.includes( node ) === false ) { + + this.sequentialNodes.push( node ); + + } + + } + + /** + * Checks the update types of nodes + */ + buildUpdateNodes() { + + for ( const node of this.nodes ) { + + const updateType = node.getUpdateType(); + + if ( updateType !== NodeUpdateType.NONE ) { + + this.updateNodes.push( node.getSelf() ); + + } + + } + + for ( const node of this.sequentialNodes ) { + + const updateBeforeType = node.getUpdateBeforeType(); + const updateAfterType = node.getUpdateAfterType(); + + if ( updateBeforeType !== NodeUpdateType.NONE ) { + + this.updateBeforeNodes.push( node.getSelf() ); + + } + + if ( updateAfterType !== NodeUpdateType.NONE ) { + + this.updateAfterNodes.push( node.getSelf() ); + + } + + } + + } + + /** + * A reference the current node which is the + * last node in the chain of nodes. + * + * @type {Node} + */ + get currentNode() { + + return this.chaining[ this.chaining.length - 1 ]; + + } + + /** + * Whether the given texture is filtered or not. + * + * @param {Texture} texture - The texture to check. + * @return {boolean} Whether the given texture is filtered or not. + */ + isFilteredTexture( texture ) { + + return ( texture.magFilter === LinearFilter || texture.magFilter === LinearMipmapNearestFilter || texture.magFilter === NearestMipmapLinearFilter || texture.magFilter === LinearMipmapLinearFilter || + texture.minFilter === LinearFilter || texture.minFilter === LinearMipmapNearestFilter || texture.minFilter === NearestMipmapLinearFilter || texture.minFilter === LinearMipmapLinearFilter ); + + } + + /** + * Adds the given node to the internal node chain. + * This is used to check recursive calls in node-graph. + * + * @param {Node} node - The node to add. + */ + addChain( node ) { + + /* + if ( this.chaining.indexOf( node ) !== - 1 ) { + + console.warn( 'Recursive node: ', node ); + + } + */ + + this.chaining.push( node ); + + } + + /** + * Removes the given node from the internal node chain. + * + * @param {Node} node - The node to remove. + */ + removeChain( node ) { + + const lastChain = this.chaining.pop(); + + if ( lastChain !== node ) { + + throw new Error( 'NodeBuilder: Invalid node chaining!' ); + + } + + } + + /** + * Returns the native shader method name for a given generic name. E.g. + * the method name `textureDimensions` matches the WGSL name but must be + * resolved to `textureSize` in GLSL. + * + * @abstract + * @param {string} method - The method name to resolve. + * @return {string} The resolved method name. + */ + getMethod( method ) { + + return method; + + } + + /** + * Returns a node for the given hash, see {@link NodeBuilder#setHashNode}. + * + * @param {number} hash - The hash of the node. + * @return {Node} The found node. + */ + getNodeFromHash( hash ) { + + return this.hashNodes[ hash ]; + + } + + /** + * Adds the Node to a target flow so that it can generate code in the 'generate' process. + * + * @param {('vertex'|'fragment'|'compute')} shaderStage - The shader stage. + * @param {Node} node - The node to add. + * @return {Node} The node. + */ + addFlow( shaderStage, node ) { + + this.flowNodes[ shaderStage ].push( node ); + + return node; + + } + + /** + * Sets builder's context. + * + * @param {Object} context - The context to set. + */ + setContext( context ) { + + this.context = context; + + } + + /** + * Returns the builder's current context. + * + * @return {Object} The builder's current context. + */ + getContext() { + + return this.context; + + } + + /** + * Gets a context used in shader construction that can be shared across different materials. + * This is necessary since the renderer cache can reuse shaders generated in one material and use them in another. + * + * @return {Object} The builder's current context without material. + */ + getSharedContext() { + + ({ ...this.context }); + + return this.context; + + } + + /** + * Sets builder's cache. + * + * @param {NodeCache} cache - The cache to set. + */ + setCache( cache ) { + + this.cache = cache; + + } + + /** + * Returns the builder's current cache. + * + * @return {NodeCache} The builder's current cache. + */ + getCache() { + + return this.cache; + + } + + /** + * Returns a cache for the given node. + * + * @param {Node} node - The node. + * @param {boolean} [parent=true] - Whether this node refers to a shared parent cache or not. + * @return {NodeCache} The cache. + */ + getCacheFromNode( node, parent = true ) { + + const data = this.getDataFromNode( node ); + if ( data.cache === undefined ) data.cache = new NodeCache( parent ? this.getCache() : null ); + + return data.cache; + + } + + /** + * Whether the requested feature is available or not. + * + * @abstract + * @param {string} name - The requested feature. + * @return {boolean} Whether the requested feature is supported or not. + */ + isAvailable( /*name*/ ) { + + return false; + + } + + /** + * Returns the vertexIndex input variable as a native shader string. + * + * @abstract + * @return {string} The instanceIndex shader string. + */ + getVertexIndex() { + + console.warn( 'Abstract function.' ); + + } + + /** + * Returns the instanceIndex input variable as a native shader string. + * + * @abstract + * @return {string} The instanceIndex shader string. + */ + getInstanceIndex() { + + console.warn( 'Abstract function.' ); + + } + + /** + * Returns the drawIndex input variable as a native shader string. + * Only relevant for WebGL and its `WEBGL_multi_draw` extension. + * + * @abstract + * @return {?string} The drawIndex shader string. + */ + getDrawIndex() { + + console.warn( 'Abstract function.' ); + + } + + /** + * Returns the frontFacing input variable as a native shader string. + * + * @abstract + * @return {string} The frontFacing shader string. + */ + getFrontFacing() { + + console.warn( 'Abstract function.' ); + + } + + /** + * Returns the fragCoord input variable as a native shader string. + * + * @abstract + * @return {string} The fragCoord shader string. + */ + getFragCoord() { + + console.warn( 'Abstract function.' ); + + } + + /** + * Whether to flip texture data along its vertical axis or not. WebGL needs + * this method evaluate to `true`, WebGPU to `false`. + * + * @abstract + * @return {boolean} Whether to flip texture data along its vertical axis or not. + */ + isFlipY() { + + return false; + + } + + /** + * Calling this method increases the usage count for the given node by one. + * + * @param {Node} node - The node to increase the usage count for. + * @return {number} The updated usage count. + */ + increaseUsage( node ) { + + const nodeData = this.getDataFromNode( node ); + nodeData.usageCount = nodeData.usageCount === undefined ? 1 : nodeData.usageCount + 1; + + return nodeData.usageCount; + + } + + /** + * Generates a texture sample shader string for the given texture data. + * + * @abstract + * @param {Texture} texture - The texture. + * @param {string} textureProperty - The texture property name. + * @param {string} uvSnippet - Snippet defining the texture coordinates. + * @return {string} The generated shader string. + */ + generateTexture( /* texture, textureProperty, uvSnippet */ ) { + + console.warn( 'Abstract function.' ); + + } + + /** + * Generates a texture LOD shader string for the given texture data. + * + * @abstract + * @param {Texture} texture - The texture. + * @param {string} textureProperty - The texture property name. + * @param {string} uvSnippet - Snippet defining the texture coordinates. + * @param {?string} depthSnippet - Snippet defining the 0-based texture array index to sample. + * @param {string} levelSnippet - Snippet defining the mip level. + * @return {string} The generated shader string. + */ + generateTextureLod( /* texture, textureProperty, uvSnippet, depthSnippet, levelSnippet */ ) { + + console.warn( 'Abstract function.' ); + + } + + /** + * Generates the array declaration string. + * + * @param {string} type - The type. + * @param {?number} [count] - The count. + * @return {string} The generated value as a shader string. + */ + generateArrayDeclaration( type, count ) { + + return this.getType( type ) + '[ ' + count + ' ]'; + + } + + /** + * Generates the array shader string for the given type and value. + * + * @param {string} type - The type. + * @param {?number} [count] - The count. + * @param {?Array} [values=null] - The default values. + * @return {string} The generated value as a shader string. + */ + generateArray( type, count, values = null ) { + + let snippet = this.generateArrayDeclaration( type, count ) + '( '; + + for ( let i = 0; i < count; i ++ ) { + + const value = values ? values[ i ] : null; + + if ( value !== null ) { + + snippet += value.build( this, type ); + + } else { + + snippet += this.generateConst( type ); + + } + + if ( i < count - 1 ) snippet += ', '; + + } + + snippet += ' )'; + + return snippet; + + } + + /** + * Generates the struct shader string. + * + * @param {string} type - The type. + * @param {Array} [membersLayout] - The count. + * @param {?Array} [values=null] - The default values. + * @return {string} The generated value as a shader string. + */ + generateStruct( type, membersLayout, values = null ) { + + const snippets = []; + + for ( const member of membersLayout ) { + + const { name, type } = member; + + if ( values && values[ name ] && values[ name ].isNode ) { + + snippets.push( values[ name ].build( this, type ) ); + + } else { + + snippets.push( this.generateConst( type ) ); + + } + + } + + return type + '( ' + snippets.join( ', ' ) + ' )'; + + } + + + /** + * Generates the shader string for the given type and value. + * + * @param {string} type - The type. + * @param {?any} [value=null] - The value. + * @return {string} The generated value as a shader string. + */ + generateConst( type, value = null ) { + + if ( value === null ) { + + if ( type === 'float' || type === 'int' || type === 'uint' ) value = 0; + else if ( type === 'bool' ) value = false; + else if ( type === 'color' ) value = new Color(); + else if ( type === 'vec2' ) value = new Vector2(); + else if ( type === 'vec3' ) value = new Vector3(); + else if ( type === 'vec4' ) value = new Vector4(); + + } + + if ( type === 'float' ) return toFloat( value ); + if ( type === 'int' ) return `${ Math.round( value ) }`; + if ( type === 'uint' ) return value >= 0 ? `${ Math.round( value ) }u` : '0u'; + if ( type === 'bool' ) return value ? 'true' : 'false'; + if ( type === 'color' ) return `${ this.getType( 'vec3' ) }( ${ toFloat( value.r ) }, ${ toFloat( value.g ) }, ${ toFloat( value.b ) } )`; + + const typeLength = this.getTypeLength( type ); + + const componentType = this.getComponentType( type ); + + const generateConst = value => this.generateConst( componentType, value ); + + if ( typeLength === 2 ) { + + return `${ this.getType( type ) }( ${ generateConst( value.x ) }, ${ generateConst( value.y ) } )`; + + } else if ( typeLength === 3 ) { + + return `${ this.getType( type ) }( ${ generateConst( value.x ) }, ${ generateConst( value.y ) }, ${ generateConst( value.z ) } )`; + + } else if ( typeLength === 4 && type !== 'mat2' ) { + + return `${ this.getType( type ) }( ${ generateConst( value.x ) }, ${ generateConst( value.y ) }, ${ generateConst( value.z ) }, ${ generateConst( value.w ) } )`; + + } else if ( typeLength >= 4 && value && ( value.isMatrix2 || value.isMatrix3 || value.isMatrix4 ) ) { + + return `${ this.getType( type ) }( ${ value.elements.map( generateConst ).join( ', ' ) } )`; + + } else if ( typeLength > 4 ) { + + return `${ this.getType( type ) }()`; + + } + + throw new Error( `NodeBuilder: Type '${type}' not found in generate constant attempt.` ); + + } + + /** + * It might be necessary to convert certain data types to different ones + * so this method can be used to hide the conversion. + * + * @param {string} type - The type. + * @return {string} The updated type. + */ + getType( type ) { + + if ( type === 'color' ) return 'vec3'; + + return type; + + } + + /** + * Whether the given attribute name is defined in the geometry or not. + * + * @param {string} name - The attribute name. + * @return {boolean} Whether the given attribute name is defined in the geometry. + */ + hasGeometryAttribute( name ) { + + return this.geometry && this.geometry.getAttribute( name ) !== undefined; + + } + + /** + * Returns a node attribute for the given name and type. + * + * @param {string} name - The attribute's name. + * @param {string} type - The attribute's type. + * @return {NodeAttribute} The node attribute. + */ + getAttribute( name, type ) { + + const attributes = this.attributes; + + // find attribute + + for ( const attribute of attributes ) { + + if ( attribute.name === name ) { + + return attribute; + + } + + } + + // create a new if no exist + + const attribute = new NodeAttribute( name, type ); + + this.registerDeclaration( attribute ); + + attributes.push( attribute ); + + return attribute; + + } + + /** + * Returns for the given node and shader stage the property name for the shader. + * + * @param {Node} node - The node. + * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage. + * @return {string} The property name. + */ + getPropertyName( node/*, shaderStage*/ ) { + + return node.name; + + } + + /** + * Whether the given type is a vector type or not. + * + * @param {string} type - The type to check. + * @return {boolean} Whether the given type is a vector type or not. + */ + isVector( type ) { + + return /vec\d/.test( type ); + + } + + /** + * Whether the given type is a matrix type or not. + * + * @param {string} type - The type to check. + * @return {boolean} Whether the given type is a matrix type or not. + */ + isMatrix( type ) { + + return /mat\d/.test( type ); + + } + + /** + * Whether the given type is a reference type or not. + * + * @param {string} type - The type to check. + * @return {boolean} Whether the given type is a reference type or not. + */ + isReference( type ) { + + return type === 'void' || type === 'property' || type === 'sampler' || type === 'samplerComparison' || type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' || type === 'depthTexture' || type === 'texture3D'; + + } + + /** + * Checks if the given texture requires a manual conversion to the working color space. + * + * @abstract + * @param {Texture} texture - The texture to check. + * @return {boolean} Whether the given texture requires a conversion to working color space or not. + */ + needsToWorkingColorSpace( /*texture*/ ) { + + return false; + + } + + /** + * Returns the component type of a given texture. + * + * @param {Texture} texture - The texture. + * @return {string} The component type. + */ + getComponentTypeFromTexture( texture ) { + + const type = texture.type; + + if ( texture.isDataTexture ) { + + if ( type === IntType ) return 'int'; + if ( type === UnsignedIntType ) return 'uint'; + + } + + return 'float'; + + } + + /** + * Returns the element type for a given type. + * + * @param {string} type - The type. + * @return {string} The element type. + */ + getElementType( type ) { + + if ( type === 'mat2' ) return 'vec2'; + if ( type === 'mat3' ) return 'vec3'; + if ( type === 'mat4' ) return 'vec4'; + + return this.getComponentType( type ); + + } + + /** + * Returns the component type for a given type. + * + * @param {string} type - The type. + * @return {string} The component type. + */ + getComponentType( type ) { + + type = this.getVectorType( type ); + + if ( type === 'float' || type === 'bool' || type === 'int' || type === 'uint' ) return type; + + const componentType = /(b|i|u|)(vec|mat)([2-4])/.exec( type ); + + if ( componentType === null ) return null; + + if ( componentType[ 1 ] === 'b' ) return 'bool'; + if ( componentType[ 1 ] === 'i' ) return 'int'; + if ( componentType[ 1 ] === 'u' ) return 'uint'; + + return 'float'; + + } + + /** + * Returns the vector type for a given type. + * + * @param {string} type - The type. + * @return {string} The vector type. + */ + getVectorType( type ) { + + if ( type === 'color' ) return 'vec3'; + if ( type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' || type === 'texture3D' ) return 'vec4'; + + return type; + + } + + /** + * Returns the data type for the given the length and component type. + * + * @param {number} length - The length. + * @param {string} [componentType='float'] - The component type. + * @return {string} The type. + */ + getTypeFromLength( length, componentType = 'float' ) { + + if ( length === 1 ) return componentType; + + let baseType = getTypeFromLength( length ); + const prefix = componentType === 'float' ? '' : componentType[ 0 ]; + + // fix edge case for mat2x2 being same size as vec4 + if ( /mat2/.test( componentType ) === true ) { + + baseType = baseType.replace( 'vec', 'mat' ); + + } + + return prefix + baseType; + + } + + /** + * Returns the type for a given typed array. + * + * @param {TypedArray} array - The typed array. + * @return {string} The type. + */ + getTypeFromArray( array ) { + + return typeFromArray.get( array.constructor ); + + } + + /** + * Returns the type is an integer type. + * + * @param {string} type - The type. + * @return {boolean} Whether the type is an integer type or not. + */ + isInteger( type ) { + + return /int|uint|(i|u)vec/.test( type ); + + } + + /** + * Returns the type for a given buffer attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute. + * @return {string} The type. + */ + getTypeFromAttribute( attribute ) { + + let dataAttribute = attribute; + + if ( attribute.isInterleavedBufferAttribute ) dataAttribute = attribute.data; + + const array = dataAttribute.array; + const itemSize = attribute.itemSize; + const normalized = attribute.normalized; + + let arrayType; + + if ( ! ( attribute instanceof Float16BufferAttribute ) && normalized !== true ) { + + arrayType = this.getTypeFromArray( array ); + + } + + return this.getTypeFromLength( itemSize, arrayType ); + + } + + /** + * Returns the length for the given data type. + * + * @param {string} type - The data type. + * @return {number} The length. + */ + getTypeLength( type ) { + + const vecType = this.getVectorType( type ); + const vecNum = /vec([2-4])/.exec( vecType ); + + if ( vecNum !== null ) return Number( vecNum[ 1 ] ); + if ( vecType === 'float' || vecType === 'bool' || vecType === 'int' || vecType === 'uint' ) return 1; + if ( /mat2/.test( type ) === true ) return 4; + if ( /mat3/.test( type ) === true ) return 9; + if ( /mat4/.test( type ) === true ) return 16; + + return 0; + + } + + /** + * Returns the vector type for a given matrix type. + * + * @param {string} type - The matrix type. + * @return {string} The vector type. + */ + getVectorFromMatrix( type ) { + + return type.replace( 'mat', 'vec' ); + + } + + /** + * For a given type this method changes the component type to the + * given value. E.g. `vec4` should be changed to the new component type + * `uint` which results in `uvec4`. + * + * @param {string} type - The type. + * @param {string} newComponentType - The new component type. + * @return {string} The new type. + */ + changeComponentType( type, newComponentType ) { + + return this.getTypeFromLength( this.getTypeLength( type ), newComponentType ); + + } + + /** + * Returns the integer type pendant for the given type. + * + * @param {string} type - The type. + * @return {string} The integer type. + */ + getIntegerType( type ) { + + const componentType = this.getComponentType( type ); + + if ( componentType === 'int' || componentType === 'uint' ) return type; + + return this.changeComponentType( type, 'int' ); + + } + + /** + * Adds a stack node to the internal stack. + * + * @return {StackNode} The added stack node. + */ + addStack() { + + this.stack = stack( this.stack ); + + this.stacks.push( getCurrentStack() || this.stack ); + setCurrentStack( this.stack ); + + return this.stack; + + } + + /** + * Removes the last stack node from the internal stack. + * + * @return {StackNode} The removed stack node. + */ + removeStack() { + + const lastStack = this.stack; + this.stack = lastStack.parent; + + setCurrentStack( this.stacks.pop() ); + + return lastStack; + + } + + /** + * The builder maintains (cached) data for each node during the building process. This method + * can be used to get these data for a specific shader stage and cache. + * + * @param {Node} node - The node to get the data for. + * @param {('vertex'|'fragment'|'compute'|'any')} [shaderStage=this.shaderStage] - The shader stage. + * @param {?NodeCache} cache - An optional cache. + * @return {Object} The node data. + */ + getDataFromNode( node, shaderStage = this.shaderStage, cache = null ) { + + cache = cache === null ? ( node.isGlobal( this ) ? this.globalCache : this.cache ) : cache; + + let nodeData = cache.getData( node ); + + if ( nodeData === undefined ) { + + nodeData = {}; + + cache.setData( node, nodeData ); + + } + + if ( nodeData[ shaderStage ] === undefined ) nodeData[ shaderStage ] = {}; + + // + + let data = nodeData[ shaderStage ]; + + const subBuilds = nodeData.any ? nodeData.any.subBuilds : null; + const subBuild = this.getClosestSubBuild( subBuilds ); + + if ( subBuild ) { + + if ( data.subBuildsCache === undefined ) data.subBuildsCache = {}; + + data = data.subBuildsCache[ subBuild ] || ( data.subBuildsCache[ subBuild ] = {} ); + data.subBuilds = subBuilds; + + } + + return data; + + } + + /** + * Returns the properties for the given node and shader stage. + * + * @param {Node} node - The node to get the properties for. + * @param {('vertex'|'fragment'|'compute'|'any')} [shaderStage='any'] - The shader stage. + * @return {Object} The node properties. + */ + getNodeProperties( node, shaderStage = 'any' ) { + + const nodeData = this.getDataFromNode( node, shaderStage ); + + return nodeData.properties || ( nodeData.properties = { outputNode: null } ); + + } + + /** + * Returns an instance of {@link NodeAttribute} for the given buffer attribute node. + * + * @param {BufferAttributeNode} node - The buffer attribute node. + * @param {string} type - The node type. + * @return {NodeAttribute} The node attribute. + */ + getBufferAttributeFromNode( node, type ) { + + const nodeData = this.getDataFromNode( node ); + + let bufferAttribute = nodeData.bufferAttribute; + + if ( bufferAttribute === undefined ) { + + const index = this.uniforms.index ++; + + bufferAttribute = new NodeAttribute( 'nodeAttribute' + index, type, node ); + + this.bufferAttributes.push( bufferAttribute ); + + nodeData.bufferAttribute = bufferAttribute; + + } + + return bufferAttribute; + + } + + /** + * Returns an instance of {@link StructType} for the given output struct node. + * + * @param {OutputStructNode} node - The output struct node. + * @param {Array} membersLayout - The output struct types. + * @param {?string} [name=null] - The name of the struct. + * @param {('vertex'|'fragment'|'compute'|'any')} [shaderStage=this.shaderStage] - The shader stage. + * @return {StructType} The struct type attribute. + */ + getStructTypeFromNode( node, membersLayout, name = null, shaderStage = this.shaderStage ) { + + const nodeData = this.getDataFromNode( node, shaderStage, this.globalCache ); + + let structType = nodeData.structType; + + if ( structType === undefined ) { + + const index = this.structs.index ++; + + if ( name === null ) name = 'StructType' + index; + + structType = new StructType( name, membersLayout ); + + this.structs[ shaderStage ].push( structType ); + + nodeData.structType = structType; + + } + + return structType; + + } + + /** + * Returns an instance of {@link StructType} for the given output struct node. + * + * @param {OutputStructNode} node - The output struct node. + * @param {Array} membersLayout - The output struct types. + * @return {StructType} The struct type attribute. + */ + getOutputStructTypeFromNode( node, membersLayout ) { + + const structType = this.getStructTypeFromNode( node, membersLayout, 'OutputType', 'fragment' ); + structType.output = true; + + return structType; + + } + + /** + * Returns an instance of {@link NodeUniform} for the given uniform node. + * + * @param {UniformNode} node - The uniform node. + * @param {string} type - The uniform type. + * @param {('vertex'|'fragment'|'compute'|'any')} [shaderStage=this.shaderStage] - The shader stage. + * @param {?string} name - The name of the uniform. + * @return {NodeUniform} The node uniform. + */ + getUniformFromNode( node, type, shaderStage = this.shaderStage, name = null ) { + + const nodeData = this.getDataFromNode( node, shaderStage, this.globalCache ); + + let nodeUniform = nodeData.uniform; + + if ( nodeUniform === undefined ) { + + const index = this.uniforms.index ++; + + nodeUniform = new NodeUniform( name || ( 'nodeUniform' + index ), type, node ); + + this.uniforms[ shaderStage ].push( nodeUniform ); + + this.registerDeclaration( nodeUniform ); + + nodeData.uniform = nodeUniform; + + } + + return nodeUniform; + + } + + /** + * Returns the array length. + * + * @param {Node} node - The node. + * @return {?number} The array length. + */ + getArrayCount( node ) { + + let count = null; + + if ( node.isArrayNode ) count = node.count; + else if ( node.isVarNode && node.node.isArrayNode ) count = node.node.count; + + return count; + + } + + /** + * Returns an instance of {@link NodeVar} for the given variable node. + * + * @param {VarNode} node - The variable node. + * @param {?string} name - The variable's name. + * @param {string} [type=node.getNodeType( this )] - The variable's type. + * @param {('vertex'|'fragment'|'compute'|'any')} [shaderStage=this.shaderStage] - The shader stage. + * @param {boolean} [readOnly=false] - Whether the variable is read-only or not. + * + * @return {NodeVar} The node variable. + */ + getVarFromNode( node, name = null, type = node.getNodeType( this ), shaderStage = this.shaderStage, readOnly = false ) { + + const nodeData = this.getDataFromNode( node, shaderStage ); + const subBuildVariable = this.getSubBuildProperty( 'variable', nodeData.subBuilds ); + + let nodeVar = nodeData[ subBuildVariable ]; + + if ( nodeVar === undefined ) { + + const idNS = readOnly ? '_const' : '_var'; + + const vars = this.vars[ shaderStage ] || ( this.vars[ shaderStage ] = [] ); + const id = this.vars[ idNS ] || ( this.vars[ idNS ] = 0 ); + + if ( name === null ) { + + name = ( readOnly ? 'nodeConst' : 'nodeVar' ) + id; + + this.vars[ idNS ] ++; + + } + + // + + if ( subBuildVariable !== 'variable' ) { + + name = this.getSubBuildProperty( name, nodeData.subBuilds ); + + } + + // + + const count = this.getArrayCount( node ); + + nodeVar = new NodeVar( name, type, readOnly, count ); + + if ( ! readOnly ) { + + vars.push( nodeVar ); + + } + + this.registerDeclaration( nodeVar ); + + nodeData[ subBuildVariable ] = nodeVar; + + } + + return nodeVar; + + } + + /** + * Returns whether a Node or its flow is deterministic, useful for use in `const`. + * + * @param {Node} node - The varying node. + * @return {boolean} Returns true if deterministic. + */ + isDeterministic( node ) { + + if ( node.isMathNode ) { + + return this.isDeterministic( node.aNode ) && + ( node.bNode ? this.isDeterministic( node.bNode ) : true ) && + ( node.cNode ? this.isDeterministic( node.cNode ) : true ); + + } else if ( node.isOperatorNode ) { + + return this.isDeterministic( node.aNode ) && + ( node.bNode ? this.isDeterministic( node.bNode ) : true ); + + } else if ( node.isArrayNode ) { + + if ( node.values !== null ) { + + for ( const n of node.values ) { + + if ( ! this.isDeterministic( n ) ) { + + return false; + + } + + } + + } + + return true; + + } else if ( node.isConstNode ) { + + return true; + + } + + return false; + + } + + /** + * Returns an instance of {@link NodeVarying} for the given varying node. + * + * @param {(VaryingNode|PropertyNode)} node - The varying node. + * @param {?string} name - The varying's name. + * @param {string} [type=node.getNodeType( this )] - The varying's type. + * @param {?string} interpolationType - The interpolation type of the varying. + * @param {?string} interpolationSampling - The interpolation sampling type of the varying. + * @return {NodeVar} The node varying. + */ + getVaryingFromNode( node, name = null, type = node.getNodeType( this ), interpolationType = null, interpolationSampling = null ) { + + const nodeData = this.getDataFromNode( node, 'any' ); + const subBuildVarying = this.getSubBuildProperty( 'varying', nodeData.subBuilds ); + + let nodeVarying = nodeData[ subBuildVarying ]; + + if ( nodeVarying === undefined ) { + + const varyings = this.varyings; + const index = varyings.length; + + if ( name === null ) name = 'nodeVarying' + index; + + // + + if ( subBuildVarying !== 'varying' ) { + + name = this.getSubBuildProperty( name, nodeData.subBuilds ); + + } + + // + + nodeVarying = new NodeVarying( name, type, interpolationType, interpolationSampling ); + + varyings.push( nodeVarying ); + + this.registerDeclaration( nodeVarying ); + + nodeData[ subBuildVarying ] = nodeVarying; + + } + + return nodeVarying; + + } + + /** + * Registers a node declaration in the current shader stage. + * + * @param {Object} node - The node to be registered. + */ + registerDeclaration( node ) { + + const shaderStage = this.shaderStage; + const declarations = this.declarations[ shaderStage ] || ( this.declarations[ shaderStage ] = {} ); + + const property = this.getPropertyName( node ); + + let index = 1; + let name = property; + + // Automatically renames the property if the name is already in use. + + while ( declarations[ name ] !== undefined ) { + + name = property + '_' + index ++; + + } + + if ( index > 1 ) { + + node.name = name; + + console.warn( `THREE.TSL: Declaration name '${ property }' of '${ node.type }' already in use. Renamed to '${ name }'.` ); + + } + + declarations[ name ] = node; + + } + + /** + * Returns an instance of {@link NodeCode} for the given code node. + * + * @param {CodeNode} node - The code node. + * @param {string} type - The node type. + * @param {('vertex'|'fragment'|'compute'|'any')} [shaderStage=this.shaderStage] - The shader stage. + * @return {NodeCode} The node code. + */ + getCodeFromNode( node, type, shaderStage = this.shaderStage ) { + + const nodeData = this.getDataFromNode( node ); + + let nodeCode = nodeData.code; + + if ( nodeCode === undefined ) { + + const codes = this.codes[ shaderStage ] || ( this.codes[ shaderStage ] = [] ); + const index = codes.length; + + nodeCode = new NodeCode( 'nodeCode' + index, type ); + + codes.push( nodeCode ); + + nodeData.code = nodeCode; + + } + + return nodeCode; + + } + + /** + * Adds a code flow based on the code-block hierarchy. + + * This is used so that code-blocks like If,Else create their variables locally if the Node + * is only used inside one of these conditionals in the current shader stage. + * + * @param {Node} node - The node to add. + * @param {Node} nodeBlock - Node-based code-block. Usually 'ConditionalNode'. + */ + addFlowCodeHierarchy( node, nodeBlock ) { + + const { flowCodes, flowCodeBlock } = this.getDataFromNode( node ); + + let needsFlowCode = true; + let nodeBlockHierarchy = nodeBlock; + + while ( nodeBlockHierarchy ) { + + if ( flowCodeBlock.get( nodeBlockHierarchy ) === true ) { + + needsFlowCode = false; + break; + + } + + nodeBlockHierarchy = this.getDataFromNode( nodeBlockHierarchy ).parentNodeBlock; + + } + + if ( needsFlowCode ) { + + for ( const flowCode of flowCodes ) { + + this.addLineFlowCode( flowCode ); + + } + + } + + } + + /** + * Add a inline-code to the current flow code-block. + * + * @param {Node} node - The node to add. + * @param {string} code - The code to add. + * @param {Node} nodeBlock - Current ConditionalNode + */ + addLineFlowCodeBlock( node, code, nodeBlock ) { + + const nodeData = this.getDataFromNode( node ); + const flowCodes = nodeData.flowCodes || ( nodeData.flowCodes = [] ); + const codeBlock = nodeData.flowCodeBlock || ( nodeData.flowCodeBlock = new WeakMap() ); + + flowCodes.push( code ); + codeBlock.set( nodeBlock, true ); + + } + + /** + * Add a inline-code to the current flow. + * + * @param {string} code - The code to add. + * @param {?Node} [node= null] - Optional Node, can help the system understand if the Node is part of a code-block. + * @return {NodeBuilder} A reference to this node builder. + */ + addLineFlowCode( code, node = null ) { + + if ( code === '' ) return this; + + if ( node !== null && this.context.nodeBlock ) { + + this.addLineFlowCodeBlock( node, code, this.context.nodeBlock ); + + } + + code = this.tab + code; + + if ( ! /;\s*$/.test( code ) ) { + + code = code + ';\n'; + + } + + this.flow.code += code; + + return this; + + } + + /** + * Adds a code to the current code flow. + * + * @param {string} code - Shader code. + * @return {NodeBuilder} A reference to this node builder. + */ + addFlowCode( code ) { + + this.flow.code += code; + + return this; + + } + + /** + * Add tab in the code that will be generated so that other snippets respect the current tabulation. + * Typically used in codes with If,Else. + * + * @return {NodeBuilder} A reference to this node builder. + */ + addFlowTab() { + + this.tab += '\t'; + + return this; + + } + + /** + * Removes a tab. + * + * @return {NodeBuilder} A reference to this node builder. + */ + removeFlowTab() { + + this.tab = this.tab.slice( 0, -1 ); + + return this; + + } + + /** + * Gets the current flow data based on a Node. + * + * @param {Node} node - Node that the flow was started. + * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage. + * @return {Object} The flow data. + */ + getFlowData( node/*, shaderStage*/ ) { + + return this.flowsData.get( node ); + + } + + /** + * Executes the node flow based on a root node to generate the final shader code. + * + * @param {Node} node - The node to execute. + * @return {Object} The code flow. + */ + flowNode( node ) { + + const output = node.getNodeType( this ); + + const flowData = this.flowChildNode( node, output ); + + this.flowsData.set( node, flowData ); + + return flowData; + + } + + /** + * Includes a node in the current function node. + * + * @param {Node} node - The node to include. + * @returns {void} + */ + addInclude( node ) { + + if ( this.currentFunctionNode !== null ) { + + this.currentFunctionNode.includes.push( node ); + + } + + } + + /** + * Returns the native shader operator name for a given generic name. + * It is a similar type of method like {@link NodeBuilder#getMethod}. + * + * @param {ShaderNodeInternal} shaderNode - The shader node to build the function node with. + * @return {FunctionNode} The build function node. + */ + buildFunctionNode( shaderNode ) { + + const fn = new FunctionNode(); + + const previous = this.currentFunctionNode; + + this.currentFunctionNode = fn; + + fn.code = this.buildFunctionCode( shaderNode ); + + this.currentFunctionNode = previous; + + return fn; + + } + + /** + * Generates a code flow based on a TSL function: Fn(). + * + * @param {ShaderNodeInternal} shaderNode - A function code will be generated based on the input. + * @return {Object} + */ + flowShaderNode( shaderNode ) { + + const layout = shaderNode.layout; + + const inputs = { + [ Symbol.iterator ]() { + + let index = 0; + const values = Object.values( this ); + return { + next: () => ( { + value: values[ index ], + done: index ++ >= values.length + } ) + }; + + } + }; + + for ( const input of layout.inputs ) { + + inputs[ input.name ] = new ParameterNode( input.type, input.name ); + + } + + // + + shaderNode.layout = null; + + const callNode = shaderNode.call( inputs ); + const flowData = this.flowStagesNode( callNode, layout.type ); + + shaderNode.layout = layout; + + return flowData; + + } + + /** + * Runs the node flow through all the steps of creation, 'setup', 'analyze', 'generate'. + * + * @param {Node} node - The node to execute. + * @param {?string} output - Expected output type. For example 'vec3'. + * @return {Object} + */ + flowStagesNode( node, output = null ) { + + const previousFlow = this.flow; + const previousVars = this.vars; + const previousDeclarations = this.declarations; + const previousCache = this.cache; + const previousBuildStage = this.buildStage; + const previousStack = this.stack; + + const flow = { + code: '' + }; + + this.flow = flow; + this.vars = {}; + this.declarations = {}; + this.cache = new NodeCache(); + this.stack = stack(); + + for ( const buildStage of defaultBuildStages ) { + + this.setBuildStage( buildStage ); + + flow.result = node.build( this, output ); + + } + + flow.vars = this.getVars( this.shaderStage ); + + this.flow = previousFlow; + this.vars = previousVars; + this.declarations = previousDeclarations; + this.cache = previousCache; + this.stack = previousStack; + + this.setBuildStage( previousBuildStage ); + + return flow; + + } + + /** + * Returns the native shader operator name for a given generic name. + * It is a similar type of method like {@link NodeBuilder#getMethod}. + * + * @abstract + * @param {string} op - The operator name to resolve. + * @return {?string} The resolved operator name. + */ + getFunctionOperator( /* op */ ) { + + return null; + + } + + /** + * Builds the given shader node. + * + * @abstract + * @param {ShaderNodeInternal} shaderNode - The shader node. + * @return {string} The function code. + */ + buildFunctionCode( /* shaderNode */ ) { + + console.warn( 'Abstract function.' ); + + } + + /** + * Generates a code flow based on a child Node. + * + * @param {Node} node - The node to execute. + * @param {?string} output - Expected output type. For example 'vec3'. + * @return {Object} The code flow. + */ + flowChildNode( node, output = null ) { + + const previousFlow = this.flow; + + const flow = { + code: '' + }; + + this.flow = flow; + + flow.result = node.build( this, output ); + + this.flow = previousFlow; + + return flow; + + } + + /** + * Executes a flow of code in a different stage. + * + * Some nodes like `varying()` have the ability to compute code in vertex-stage and + * return the value in fragment-stage even if it is being executed in an input fragment. + * + * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage. + * @param {Node} node - The node to execute. + * @param {?string} output - Expected output type. For example 'vec3'. + * @param {?string} propertyName - The property name to assign the result. + * @return {Object|Node|null} The code flow or node.build() result. + */ + flowNodeFromShaderStage( shaderStage, node, output = null, propertyName = null ) { + + const previousTab = this.tab; + const previousCache = this.cache; + const previousShaderStage = this.shaderStage; + const previousContext = this.context; + + this.setShaderStage( shaderStage ); + + const context = { ...this.context }; + delete context.nodeBlock; + + this.cache = this.globalCache; + this.tab = '\t'; + this.context = context; + + let result = null; + + if ( this.buildStage === 'generate' ) { + + const flowData = this.flowChildNode( node, output ); + + if ( propertyName !== null ) { + + flowData.code += `${ this.tab + propertyName } = ${ flowData.result };\n`; + + } + + this.flowCode[ shaderStage ] = this.flowCode[ shaderStage ] + flowData.code; + + result = flowData; + + } else { + + result = node.build( this ); + + } + + this.setShaderStage( previousShaderStage ); + + this.cache = previousCache; + this.tab = previousTab; + this.context = previousContext; + + return result; + + } + + /** + * Returns an array holding all node attributes of this node builder. + * + * @return {Array} The node attributes of this builder. + */ + getAttributesArray() { + + return this.attributes.concat( this.bufferAttributes ); + + } + + /** + * Returns the attribute definitions as a shader string for the given shader stage. + * + * @abstract + * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage. + * @return {string} The attribute code section. + */ + getAttributes( /*shaderStage*/ ) { + + console.warn( 'Abstract function.' ); + + } + + /** + * Returns the varying definitions as a shader string for the given shader stage. + * + * @abstract + * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage. + * @return {string} The varying code section. + */ + getVaryings( /*shaderStage*/ ) { + + console.warn( 'Abstract function.' ); + + } + + /** + * Returns a single variable definition as a shader string for the given variable type and name. + * + * @param {string} type - The variable's type. + * @param {string} name - The variable's name. + * @param {?number} [count=null] - The array length. + * @return {string} The shader string. + */ + getVar( type, name, count = null ) { + + return `${ count !== null ? this.generateArrayDeclaration( type, count ) : this.getType( type ) } ${ name }`; + + } + + /** + * Returns the variable definitions as a shader string for the given shader stage. + * + * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage. + * @return {string} The variable code section. + */ + getVars( shaderStage ) { + + let snippet = ''; + + const vars = this.vars[ shaderStage ]; + + if ( vars !== undefined ) { + + for ( const variable of vars ) { + + snippet += `${ this.getVar( variable.type, variable.name ) }; `; + + } + + } + + return snippet; + + } + + /** + * Returns the uniform definitions as a shader string for the given shader stage. + * + * @abstract + * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage. + * @return {string} The uniform code section. + */ + getUniforms( /*shaderStage*/ ) { + + console.warn( 'Abstract function.' ); + + } + + /** + * Returns the native code definitions as a shader string for the given shader stage. + * + * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage. + * @return {string} The native code section. + */ + getCodes( shaderStage ) { + + const codes = this.codes[ shaderStage ]; + + let code = ''; + + if ( codes !== undefined ) { + + for ( const nodeCode of codes ) { + + code += nodeCode.code + '\n'; + + } + + } + + return code; + + } + + /** + * Returns the hash of this node builder. + * + * @return {string} The hash. + */ + getHash() { + + return this.vertexShader + this.fragmentShader + this.computeShader; + + } + + /** + * Sets the current shader stage. + * + * @param {?('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage to set. + */ + setShaderStage( shaderStage ) { + + this.shaderStage = shaderStage; + + } + + /** + * Returns the current shader stage. + * + * @return {?('vertex'|'fragment'|'compute'|'any')} The current shader stage. + */ + getShaderStage() { + + return this.shaderStage; + + } + + /** + * Sets the current build stage. + * + * @param {?('setup'|'analyze'|'generate')} buildStage - The build stage to set. + */ + setBuildStage( buildStage ) { + + this.buildStage = buildStage; + + } + + /** + * Returns the current build stage. + * + * @return {?('setup'|'analyze'|'generate')} The current build stage. + */ + getBuildStage() { + + return this.buildStage; + + } + + /** + * Controls the code build of the shader stages. + * + * @abstract + */ + buildCode() { + + console.warn( 'Abstract function.' ); + + } + + /** + * Returns the current sub-build layer. + * + * @return {SubBuildNode} The current sub-build layers. + */ + get subBuild() { + + return this.subBuildLayers[ this.subBuildLayers.length - 1 ] || null; + + } + + /** + * Adds a sub-build layer to the node builder. + * + * @param {SubBuildNode} subBuild - The sub-build layer to add. + */ + addSubBuild( subBuild ) { + + this.subBuildLayers.push( subBuild ); + + } + + /** + * Removes the last sub-build layer from the node builder. + * + * @return {SubBuildNode} The removed sub-build layer. + */ + removeSubBuild() { + + return this.subBuildLayers.pop(); + + } + + /** + * Returns the closest sub-build layer for the given data. + * + * @param {Node|Set|Array} data - The data to get the closest sub-build layer from. + * @return {?string} The closest sub-build name or null if none found. + */ + getClosestSubBuild( data ) { + + let subBuilds; + + if ( data && data.isNode ) { + + if ( data.isShaderCallNodeInternal ) { + + subBuilds = data.shaderNode.subBuilds; + + } else if ( data.isStackNode ) { + + subBuilds = [ data.subBuild ]; + + } else { + + subBuilds = this.getDataFromNode( data, 'any' ).subBuilds; + + } + + } else if ( data instanceof Set ) { + + subBuilds = [ ...data ]; + + } else { + + subBuilds = data; + + } + + if ( ! subBuilds ) return null; + + const subBuildLayers = this.subBuildLayers; + + for ( let i = subBuilds.length - 1; i >= 0; i -- ) { + + const subBuild = subBuilds[ i ]; + + if ( subBuildLayers.includes( subBuild ) ) { + + return subBuild; + + } + + } + + return null; + + } + + + /** + * Returns the output node of a sub-build layer. + * + * @param {Node} node - The node to get the output from. + * @return {string} The output node name. + */ + getSubBuildOutput( node ) { + + return this.getSubBuildProperty( 'outputNode', node ); + + } + + /** + * Returns the sub-build property name for the given property and node. + * + * @param {string} [property=''] - The property name. + * @param {?Node} [node=null] - The node to get the sub-build from. + * @return {string} The sub-build property name. + */ + getSubBuildProperty( property = '', node = null ) { + + let subBuild; + + if ( node !== null ) { + + subBuild = this.getClosestSubBuild( node ); + + } else { + + subBuild = this.subBuildFn; + + } + + let result; + + if ( subBuild ) { + + result = property ? ( subBuild + '_' + property ) : subBuild; + + } else { + + result = property; + + } + + return result; + + } + + /** + * Central build method which controls the build for the given object. + * + * @return {NodeBuilder} A reference to this node builder. + */ + build() { + + const { object, material, renderer } = this; + + if ( material !== null ) { + + let nodeMaterial = renderer.library.fromMaterial( material ); + + if ( nodeMaterial === null ) { + + console.error( `NodeMaterial: Material "${ material.type }" is not compatible.` ); + + nodeMaterial = new NodeMaterial(); + + } + + nodeMaterial.build( this ); + + } else { + + this.addFlow( 'compute', object ); + + } + + // setup() -> stage 1: create possible new nodes and returns an output reference node + // analyze() -> stage 2: analyze nodes to possible optimization and validation + // generate() -> stage 3: generate shader + + for ( const buildStage of defaultBuildStages ) { + + this.setBuildStage( buildStage ); + + if ( this.context.vertex && this.context.vertex.isNode ) { + + this.flowNodeFromShaderStage( 'vertex', this.context.vertex ); + + } + + for ( const shaderStage of shaderStages ) { + + this.setShaderStage( shaderStage ); + + const flowNodes = this.flowNodes[ shaderStage ]; + + for ( const node of flowNodes ) { + + if ( buildStage === 'generate' ) { + + this.flowNode( node ); + + } else { + + node.build( this ); + + } + + } + + } + + } + + this.setBuildStage( null ); + this.setShaderStage( null ); + + // stage 4: build code for a specific output + + this.buildCode(); + this.buildUpdateNodes(); + + return this; + + } + + /** + * Returns a uniform representation which is later used for UBO generation and rendering. + * + * @param {NodeUniform} uniformNode - The uniform node. + * @param {string} type - The requested type. + * @return {Uniform} The uniform. + */ + getNodeUniform( uniformNode, type ) { + + if ( type === 'float' || type === 'int' || type === 'uint' ) return new NumberNodeUniform( uniformNode ); + if ( type === 'vec2' || type === 'ivec2' || type === 'uvec2' ) return new Vector2NodeUniform( uniformNode ); + if ( type === 'vec3' || type === 'ivec3' || type === 'uvec3' ) return new Vector3NodeUniform( uniformNode ); + if ( type === 'vec4' || type === 'ivec4' || type === 'uvec4' ) return new Vector4NodeUniform( uniformNode ); + if ( type === 'color' ) return new ColorNodeUniform( uniformNode ); + if ( type === 'mat2' ) return new Matrix2NodeUniform( uniformNode ); + if ( type === 'mat3' ) return new Matrix3NodeUniform( uniformNode ); + if ( type === 'mat4' ) return new Matrix4NodeUniform( uniformNode ); + + throw new Error( `Uniform "${type}" not declared.` ); + + } + + /** + * Formats the given shader snippet from a given type into another one. E.g. + * this method might be used to convert a simple float string `"1.0"` into a + * `vec3` representation: `"vec3( 1.0 )"`. + * + * @param {string} snippet - The shader snippet. + * @param {string} fromType - The source type. + * @param {string} toType - The target type. + * @return {string} The updated shader string. + */ + format( snippet, fromType, toType ) { + + fromType = this.getVectorType( fromType ); + toType = this.getVectorType( toType ); + + if ( fromType === toType || toType === null || this.isReference( toType ) ) { + + return snippet; + + } + + const fromTypeLength = this.getTypeLength( fromType ); + const toTypeLength = this.getTypeLength( toType ); + + if ( fromTypeLength === 16 && toTypeLength === 9 ) { + + return `${ this.getType( toType ) }( ${ snippet }[ 0 ].xyz, ${ snippet }[ 1 ].xyz, ${ snippet }[ 2 ].xyz )`; + + } + + if ( fromTypeLength === 9 && toTypeLength === 4 ) { + + return `${ this.getType( toType ) }( ${ snippet }[ 0 ].xy, ${ snippet }[ 1 ].xy )`; + + } + + + if ( fromTypeLength > 4 ) { // fromType is matrix-like + + // @TODO: ignore for now + + return snippet; + + } + + if ( toTypeLength > 4 || toTypeLength === 0 ) { // toType is matrix-like or unknown + + // @TODO: ignore for now + + return snippet; + + } + + if ( fromTypeLength === toTypeLength ) { + + return `${ this.getType( toType ) }( ${ snippet } )`; + + } + + if ( fromTypeLength > toTypeLength ) { + + snippet = toType === 'bool' ? `all( ${ snippet } )` : `${ snippet }.${ 'xyz'.slice( 0, toTypeLength ) }`; + + return this.format( snippet, this.getTypeFromLength( toTypeLength, this.getComponentType( fromType ) ), toType ); + + } + + if ( toTypeLength === 4 && fromTypeLength > 1 ) { // toType is vec4-like + + return `${ this.getType( toType ) }( ${ this.format( snippet, fromType, 'vec3' ) }, 1.0 )`; + + } + + if ( fromTypeLength === 2 ) { // fromType is vec2-like and toType is vec3-like + + return `${ this.getType( toType ) }( ${ this.format( snippet, fromType, 'vec2' ) }, 0.0 )`; + + } + + if ( fromTypeLength === 1 && toTypeLength > 1 && fromType !== this.getComponentType( toType ) ) { // fromType is float-like + + // convert a number value to vector type, e.g: + // vec3( 1u ) -> vec3( float( 1u ) ) + + snippet = `${ this.getType( this.getComponentType( toType ) ) }( ${ snippet } )`; + + } + + return `${ this.getType( toType ) }( ${ snippet } )`; // fromType is float-like + + } + + /** + * Returns a signature with the engine's current revision. + * + * @return {string} The signature. + */ + getSignature() { + + return `// Three.js r${ REVISION } - Node System\n`; + + } + + /** + * Prevents the node builder from being used as an iterable in TSL.Fn(), avoiding potential runtime errors. + */ + *[ Symbol.iterator ]() { } + +} + +/** + * Management class for updating nodes. The module tracks metrics like + * the elapsed time, delta time, the render and frame ID to correctly + * call the node update methods {@link Node#updateBefore}, {@link Node#update} + * and {@link Node#updateAfter} depending on the node's configuration. + */ +class NodeFrame { + + /** + * Constructs a new node fame. + */ + constructor() { + + /** + * The elapsed time in seconds. + * + * @type {number} + * @default 0 + */ + this.time = 0; + + /** + * The delta time in seconds. + * + * @type {number} + * @default 0 + */ + this.deltaTime = 0; + + /** + * The frame ID. + * + * @type {number} + * @default 0 + */ + this.frameId = 0; + + /** + * The render ID. + * + * @type {number} + * @default 0 + */ + this.renderId = 0; + + /** + * Used to control the {@link Node#update} call. + * + * @type {WeakMap} + */ + this.updateMap = new WeakMap(); + + /** + * Used to control the {@link Node#updateBefore} call. + * + * @type {WeakMap} + */ + this.updateBeforeMap = new WeakMap(); + + /** + * Used to control the {@link Node#updateAfter} call. + * + * @type {WeakMap} + */ + this.updateAfterMap = new WeakMap(); + + /** + * A reference to the current renderer. + * + * @type {?Renderer} + * @default null + */ + this.renderer = null; + + /** + * A reference to the current material. + * + * @type {?Material} + * @default null + */ + this.material = null; + + /** + * A reference to the current camera. + * + * @type {?Camera} + * @default null + */ + this.camera = null; + + /** + * A reference to the current 3D object. + * + * @type {?Object3D} + * @default null + */ + this.object = null; + + /** + * A reference to the current scene. + * + * @type {?Scene} + * @default null + */ + this.scene = null; + + } + + /** + * Returns a dictionary for a given node and update map which + * is used to correctly call node update methods per frame or render. + * + * @private + * @param {WeakMap} referenceMap - The reference weak map. + * @param {Node} nodeRef - The reference to the current node. + * @return {Object} The dictionary. + */ + _getMaps( referenceMap, nodeRef ) { + + let maps = referenceMap.get( nodeRef ); + + if ( maps === undefined ) { + + maps = { + renderMap: new WeakMap(), + frameMap: new WeakMap() + }; + + referenceMap.set( nodeRef, maps ); + + } + + return maps; + + } + + /** + * This method executes the {@link Node#updateBefore} for the given node. + * It makes sure {@link Node#updateBeforeType} is honored meaning the update + * is only executed once per frame, render or object depending on the update + * type. + * + * @param {Node} node - The node that should be updated. + */ + updateBeforeNode( node ) { + + const updateType = node.getUpdateBeforeType(); + const reference = node.updateReference( this ); + + if ( updateType === NodeUpdateType.FRAME ) { + + const { frameMap } = this._getMaps( this.updateBeforeMap, reference ); + + if ( frameMap.get( reference ) !== this.frameId ) { + + if ( node.updateBefore( this ) !== false ) { + + frameMap.set( reference, this.frameId ); + + } + + } + + } else if ( updateType === NodeUpdateType.RENDER ) { + + const { renderMap } = this._getMaps( this.updateBeforeMap, reference ); + + if ( renderMap.get( reference ) !== this.renderId ) { + + if ( node.updateBefore( this ) !== false ) { + + renderMap.set( reference, this.renderId ); + + } + + } + + } else if ( updateType === NodeUpdateType.OBJECT ) { + + node.updateBefore( this ); + + } + + } + + /** + * This method executes the {@link Node#updateAfter} for the given node. + * It makes sure {@link Node#updateAfterType} is honored meaning the update + * is only executed once per frame, render or object depending on the update + * type. + * + * @param {Node} node - The node that should be updated. + */ + updateAfterNode( node ) { + + const updateType = node.getUpdateAfterType(); + const reference = node.updateReference( this ); + + if ( updateType === NodeUpdateType.FRAME ) { + + const { frameMap } = this._getMaps( this.updateAfterMap, reference ); + + if ( frameMap.get( reference ) !== this.frameId ) { + + if ( node.updateAfter( this ) !== false ) { + + frameMap.set( reference, this.frameId ); + + } + + } + + } else if ( updateType === NodeUpdateType.RENDER ) { + + const { renderMap } = this._getMaps( this.updateAfterMap, reference ); + + if ( renderMap.get( reference ) !== this.renderId ) { + + if ( node.updateAfter( this ) !== false ) { + + renderMap.set( reference, this.renderId ); + + } + + } + + } else if ( updateType === NodeUpdateType.OBJECT ) { + + node.updateAfter( this ); + + } + + } + + /** + * This method executes the {@link Node#update} for the given node. + * It makes sure {@link Node#updateType} is honored meaning the update + * is only executed once per frame, render or object depending on the update + * type. + * + * @param {Node} node - The node that should be updated. + */ + updateNode( node ) { + + const updateType = node.getUpdateType(); + const reference = node.updateReference( this ); + + if ( updateType === NodeUpdateType.FRAME ) { + + const { frameMap } = this._getMaps( this.updateMap, reference ); + + if ( frameMap.get( reference ) !== this.frameId ) { + + if ( node.update( this ) !== false ) { + + frameMap.set( reference, this.frameId ); + + } + + } + + } else if ( updateType === NodeUpdateType.RENDER ) { + + const { renderMap } = this._getMaps( this.updateMap, reference ); + + if ( renderMap.get( reference ) !== this.renderId ) { + + if ( node.update( this ) !== false ) { + + renderMap.set( reference, this.renderId ); + + } + + } + + } else if ( updateType === NodeUpdateType.OBJECT ) { + + node.update( this ); + + } + + } + + /** + * Updates the internal state of the node frame. This method is + * called by the renderer in its internal animation loop. + */ + update() { + + this.frameId ++; + + if ( this.lastTime === undefined ) this.lastTime = performance.now(); + + this.deltaTime = ( performance.now() - this.lastTime ) / 1000; + + this.lastTime = performance.now(); + + this.time += this.deltaTime; + + } + +} + +/** + * Describes the input of a {@link NodeFunction}. + */ +class NodeFunctionInput { + + /** + * Constructs a new node function input. + * + * @param {string} type - The input type. + * @param {string} name - The input name. + * @param {?number} [count=null] - If the input is an Array, count will be the length. + * @param {('in'|'out'|'inout')} [qualifier=''] - The parameter qualifier (only relevant for GLSL). + * @param {boolean} [isConst=false] - Whether the input uses a const qualifier or not (only relevant for GLSL). + */ + constructor( type, name, count = null, qualifier = '', isConst = false ) { + + /** + * The input type. + * + * @type {string} + */ + this.type = type; + + /** + * The input name. + * + * @type {string} + */ + this.name = name; + + /** + * If the input is an Array, count will be the length. + * + * @type {?number} + * @default null + */ + this.count = count; + + /** + *The parameter qualifier (only relevant for GLSL). + * + * @type {('in'|'out'|'inout')} + * @default '' + */ + this.qualifier = qualifier; + + /** + * Whether the input uses a const qualifier or not (only relevant for GLSL). + * + * @type {boolean} + * @default false + */ + this.isConst = isConst; + + } + +} + +NodeFunctionInput.isNodeFunctionInput = true; + +/** + * Module for representing directional lights as nodes. + * + * @augments AnalyticLightNode + */ +class DirectionalLightNode extends AnalyticLightNode { + + static get type() { + + return 'DirectionalLightNode'; + + } + + /** + * Constructs a new directional light node. + * + * @param {?DirectionalLight} [light=null] - The directional light source. + */ + constructor( light = null ) { + + super( light ); + + } + + setupDirect() { + + const lightColor = this.colorNode; + const lightDirection = lightTargetDirection( this.light ); + + return { lightDirection, lightColor }; + + } + +} + +const _matrix41 = /*@__PURE__*/ new Matrix4(); +const _matrix42 = /*@__PURE__*/ new Matrix4(); + +let _ltcLib = null; + +/** + * Module for representing rect area lights as nodes. + * + * @augments AnalyticLightNode + */ +class RectAreaLightNode extends AnalyticLightNode { + + static get type() { + + return 'RectAreaLightNode'; + + } + + /** + * Constructs a new rect area light node. + * + * @param {?RectAreaLight} [light=null] - The rect area light source. + */ + constructor( light = null ) { + + super( light ); + + /** + * Uniform node representing the half height of the are light. + * + * @type {UniformNode} + */ + this.halfHeight = uniform( new Vector3() ).setGroup( renderGroup ); + + /** + * Uniform node representing the half width of the are light. + * + * @type {UniformNode} + */ + this.halfWidth = uniform( new Vector3() ).setGroup( renderGroup ); + + /** + * The `updateType` is set to `NodeUpdateType.RENDER` since the light + * relies on `viewMatrix` which might vary per render call. + * + * @type {string} + * @default 'render' + */ + this.updateType = NodeUpdateType.RENDER; + + } + + /** + * Overwritten to updated rect area light specific uniforms. + * + * @param {NodeFrame} frame - A reference to the current node frame. + */ + update( frame ) { + + super.update( frame ); + + const { light } = this; + + const viewMatrix = frame.camera.matrixWorldInverse; + + _matrix42.identity(); + _matrix41.copy( light.matrixWorld ); + _matrix41.premultiply( viewMatrix ); + _matrix42.extractRotation( _matrix41 ); + + this.halfWidth.value.set( light.width * 0.5, 0.0, 0.0 ); + this.halfHeight.value.set( 0.0, light.height * 0.5, 0.0 ); + + this.halfWidth.value.applyMatrix4( _matrix42 ); + this.halfHeight.value.applyMatrix4( _matrix42 ); + + } + + setupDirectRectArea( builder ) { + + let ltc_1, ltc_2; + + if ( builder.isAvailable( 'float32Filterable' ) ) { + + ltc_1 = texture( _ltcLib.LTC_FLOAT_1 ); + ltc_2 = texture( _ltcLib.LTC_FLOAT_2 ); + + } else { + + ltc_1 = texture( _ltcLib.LTC_HALF_1 ); + ltc_2 = texture( _ltcLib.LTC_HALF_2 ); + + } + + const { colorNode, light } = this; + + const lightPosition = lightViewPosition( light ); + + return { + lightColor: colorNode, + lightPosition, + halfWidth: this.halfWidth, + halfHeight: this.halfHeight, + ltc_1, + ltc_2 + }; + + } + + /** + * Used to configure the internal BRDF approximation texture data. + * + * @param {RectAreaLightTexturesLib} ltc - The BRDF approximation texture data. + */ + static setLTC( ltc ) { + + _ltcLib = ltc; + + } + +} + +/** + * Module for representing spot lights as nodes. + * + * @augments AnalyticLightNode + */ +class SpotLightNode extends AnalyticLightNode { + + static get type() { + + return 'SpotLightNode'; + + } + + /** + * Constructs a new spot light node. + * + * @param {?SpotLight} [light=null] - The spot light source. + */ + constructor( light = null ) { + + super( light ); + + /** + * Uniform node representing the cone cosine. + * + * @type {UniformNode} + */ + this.coneCosNode = uniform( 0 ).setGroup( renderGroup ); + + /** + * Uniform node representing the penumbra cosine. + * + * @type {UniformNode} + */ + this.penumbraCosNode = uniform( 0 ).setGroup( renderGroup ); + + /** + * Uniform node representing the cutoff distance. + * + * @type {UniformNode} + */ + this.cutoffDistanceNode = uniform( 0 ).setGroup( renderGroup ); + + /** + * Uniform node representing the decay exponent. + * + * @type {UniformNode} + */ + this.decayExponentNode = uniform( 0 ).setGroup( renderGroup ); + + /** + * Uniform node representing the light color. + * + * @type {UniformNode} + */ + this.colorNode = uniform( this.color ).setGroup( renderGroup ); + + } + + /** + * Overwritten to updated spot light specific uniforms. + * + * @param {NodeFrame} frame - A reference to the current node frame. + */ + update( frame ) { + + super.update( frame ); + + const { light } = this; + + this.coneCosNode.value = Math.cos( light.angle ); + this.penumbraCosNode.value = Math.cos( light.angle * ( 1 - light.penumbra ) ); + + this.cutoffDistanceNode.value = light.distance; + this.decayExponentNode.value = light.decay; + + } + + /** + * Computes the spot attenuation for the given angle. + * + * @param {NodeBuilder} builder - The node builder. + * @param {Node} angleCosine - The angle to compute the spot attenuation for. + * @return {Node} The spot attenuation. + */ + getSpotAttenuation( builder, angleCosine ) { + + const { coneCosNode, penumbraCosNode } = this; + + return smoothstep( coneCosNode, penumbraCosNode, angleCosine ); + + } + + getLightCoord( builder ) { + + const properties = builder.getNodeProperties( this ); + let projectionUV = properties.projectionUV; + + if ( projectionUV === undefined ) { + + projectionUV = lightProjectionUV( this.light, builder.context.positionWorld ); + + properties.projectionUV = projectionUV; + + } + + return projectionUV; + + } + + setupDirect( builder ) { + + const { colorNode, cutoffDistanceNode, decayExponentNode, light } = this; + + const lightVector = this.getLightVector( builder ); + + const lightDirection = lightVector.normalize(); + const angleCos = lightDirection.dot( lightTargetDirection( light ) ); + + const spotAttenuation = this.getSpotAttenuation( builder, angleCos ); + + const lightDistance = lightVector.length(); + + const lightAttenuation = getDistanceAttenuation( { + lightDistance, + cutoffDistance: cutoffDistanceNode, + decayExponent: decayExponentNode + } ); + + let lightColor = colorNode.mul( spotAttenuation ).mul( lightAttenuation ); + + let projected, lightCoord; + + if ( light.colorNode ) { + + lightCoord = this.getLightCoord( builder ); + projected = light.colorNode( lightCoord ); + + } else if ( light.map ) { + + lightCoord = this.getLightCoord( builder ); + projected = texture( light.map, lightCoord.xy ).onRenderUpdate( () => light.map ); + + } + + if ( projected ) { + + const inSpotLightMap = lightCoord.mul( 2. ).sub( 1. ).abs().lessThan( 1. ).all(); + + lightColor = inSpotLightMap.select( lightColor.mul( projected ), lightColor ); + + } + + return { lightColor, lightDirection }; + + } + +} + +/** + * An IES version of the default spot light node. + * + * @augments SpotLightNode + */ +class IESSpotLightNode extends SpotLightNode { + + static get type() { + + return 'IESSpotLightNode'; + + } + + /** + * Overwrites the default implementation to compute an IES conform spot attenuation. + * + * @param {NodeBuilder} builder - The node builder. + * @param {Node} angleCosine - The angle to compute the spot attenuation for. + * @return {Node} The spot attenuation. + */ + getSpotAttenuation( builder, angleCosine ) { + + const iesMap = this.light.iesMap; + + let spotAttenuation = null; + + if ( iesMap && iesMap.isTexture === true ) { + + const angle = angleCosine.acos().mul( 1.0 / Math.PI ); + + spotAttenuation = texture( iesMap, vec2( angle, 0 ), 0 ).r; + + } else { + + spotAttenuation = super.getSpotAttenuation( angleCosine ); + + } + + return spotAttenuation; + + } + +} + +const sdBox = /*@__PURE__*/ Fn( ( [ p, b ] ) => { + + const d = p.abs().sub( b ); + + return length( max$1( d, 0.0 ) ).add( min$1( max$1( d.x, d.y ), 0.0 ) ); + +} ); + +/** + * An implementation of a projector light node. + * + * @augments SpotLightNode + */ +class ProjectorLightNode extends SpotLightNode { + + static get type() { + + return 'ProjectorLightNode'; + + } + + update( frame ) { + + super.update( frame ); + + const light = this.light; + + this.penumbraCosNode.value = Math.min( Math.cos( light.angle * ( 1 - light.penumbra ) ), .99999 ); + + if ( light.aspect === null ) { + + let aspect = 1; + + if ( light.map !== null ) { + + aspect = light.map.width / light.map.height; + + } + + light.shadow.aspect = aspect; + + } else { + + light.shadow.aspect = light.aspect; + + } + + } + + /** + * Overwrites the default implementation to compute projection attenuation. + * + * @param {NodeBuilder} builder - The node builder. + * @return {Node} The spot attenuation. + */ + getSpotAttenuation( builder ) { + + const penumbraCos = this.penumbraCosNode; + const spotLightCoord = this.getLightCoord( builder ); + const coord = spotLightCoord.xyz.div( spotLightCoord.w ); + + const boxDist = sdBox( coord.xy.sub( vec2( 0.5 ) ), vec2( 0.5 ) ); + const angleFactor = div( -1, sub( 1.0, acos( penumbraCos ) ).sub( 1.0 ) ); + const attenuation = saturate( boxDist.mul( -2 ).mul( angleFactor ) ); + + return attenuation; + + } + +} + +/** + * Module for representing ambient lights as nodes. + * + * @augments AnalyticLightNode + */ +class AmbientLightNode extends AnalyticLightNode { + + static get type() { + + return 'AmbientLightNode'; + + } + + /** + * Constructs a new ambient light node. + * + * @param {?AmbientLight} [light=null] - The ambient light source. + */ + constructor( light = null ) { + + super( light ); + + } + + setup( { context } ) { + + context.irradiance.addAssign( this.colorNode ); + + } + +} + +/** + * Module for representing hemisphere lights as nodes. + * + * @augments AnalyticLightNode + */ +class HemisphereLightNode extends AnalyticLightNode { + + static get type() { + + return 'HemisphereLightNode'; + + } + + /** + * Constructs a new hemisphere light node. + * + * @param {?HemisphereLight} [light=null] - The hemisphere light source. + */ + constructor( light = null ) { + + super( light ); + + /** + * Uniform node representing the light's position. + * + * @type {UniformNode} + */ + this.lightPositionNode = lightPosition( light ); + + /** + * A node representing the light's direction. + * + * @type {Node} + */ + this.lightDirectionNode = this.lightPositionNode.normalize(); + + /** + * Uniform node representing the light's ground color. + * + * @type {UniformNode} + */ + this.groundColorNode = uniform( new Color() ).setGroup( renderGroup ); + + } + + /** + * Overwritten to updated hemisphere light specific uniforms. + * + * @param {NodeFrame} frame - A reference to the current node frame. + */ + update( frame ) { + + const { light } = this; + + super.update( frame ); + + this.lightPositionNode.object3d = light; + + this.groundColorNode.value.copy( light.groundColor ).multiplyScalar( light.intensity ); + + } + + setup( builder ) { + + const { colorNode, groundColorNode, lightDirectionNode } = this; + + const dotNL = normalWorld.dot( lightDirectionNode ); + const hemiDiffuseWeight = dotNL.mul( 0.5 ).add( 0.5 ); + + const irradiance = mix( groundColorNode, colorNode, hemiDiffuseWeight ); + + builder.context.irradiance.addAssign( irradiance ); + + } + +} + +/** + * Module for representing light probes as nodes. + * + * @augments AnalyticLightNode + */ +class LightProbeNode extends AnalyticLightNode { + + static get type() { + + return 'LightProbeNode'; + + } + + /** + * Constructs a new light probe node. + * + * @param {?LightProbe} [light=null] - The light probe. + */ + constructor( light = null ) { + + super( light ); + + const array = []; + + for ( let i = 0; i < 9; i ++ ) array.push( new Vector3() ); + + /** + * Light probe represented as a uniform of spherical harmonics. + * + * @type {UniformArrayNode} + */ + this.lightProbe = uniformArray( array ); + + } + + /** + * Overwritten to updated light probe specific uniforms. + * + * @param {NodeFrame} frame - A reference to the current node frame. + */ + update( frame ) { + + const { light } = this; + + super.update( frame ); + + // + + for ( let i = 0; i < 9; i ++ ) { + + this.lightProbe.array[ i ].copy( light.sh.coefficients[ i ] ).multiplyScalar( light.intensity ); + + } + + } + + setup( builder ) { + + const irradiance = getShIrradianceAt( normalWorld, this.lightProbe ); + + builder.context.irradiance.addAssign( irradiance ); + + } + +} + +/** + * Base class for node parsers. A derived parser must be implemented + * for each supported native shader language. + */ +class NodeParser { + + /** + * The method parses the given native code an returns a node function. + * + * @abstract + * @param {string} source - The native shader code. + * @return {NodeFunction} A node function. + */ + parseFunction( /*source*/ ) { + + console.warn( 'Abstract function.' ); + + } + +} + +/** + * Base class for node functions. A derived module must be implemented + * for each supported native shader language. Similar to other `Node*` modules, + * this class is only relevant during the building process and not used + * in user-level code. + */ +class NodeFunction { + + /** + * Constructs a new node function. + * + * @param {string} type - The node type. This type is the return type of the node function. + * @param {Array} inputs - The function's inputs. + * @param {string} [name=''] - The function's name. + * @param {string} [precision=''] - The precision qualifier. + */ + constructor( type, inputs, name = '', precision = '' ) { + + /** + * The node type. This type is the return type of the node function. + * + * @type {string} + */ + this.type = type; + + /** + * The function's inputs. + * + * @type {Array} + */ + this.inputs = inputs; + + /** + * The name of the uniform. + * + * @type {string} + * @default '' + */ + this.name = name; + + /** + * The precision qualifier. + * + * @type {string} + * @default '' + */ + this.precision = precision; + + } + + /** + * This method returns the native code of the node function. + * + * @abstract + * @param {string} name - The function's name. + * @return {string} A shader code. + */ + getCode( /*name = this.name*/ ) { + + console.warn( 'Abstract function.' ); + + } + +} + +NodeFunction.isNodeFunction = true; + +const declarationRegexp$1 = /^\s*(highp|mediump|lowp)?\s*([a-z_0-9]+)\s*([a-z_0-9]+)?\s*\(([\s\S]*?)\)/i; +const propertiesRegexp$1 = /[a-z_0-9]+/ig; + +const pragmaMain = '#pragma main'; + +const parse$1 = ( source ) => { + + source = source.trim(); + + const pragmaMainIndex = source.indexOf( pragmaMain ); + + const mainCode = pragmaMainIndex !== -1 ? source.slice( pragmaMainIndex + pragmaMain.length ) : source; + + const declaration = mainCode.match( declarationRegexp$1 ); + + if ( declaration !== null && declaration.length === 5 ) { + + // tokenizer + + const inputsCode = declaration[ 4 ]; + const propsMatches = []; + + let nameMatch = null; + + while ( ( nameMatch = propertiesRegexp$1.exec( inputsCode ) ) !== null ) { + + propsMatches.push( nameMatch ); + + } + + // parser + + const inputs = []; + + let i = 0; + + while ( i < propsMatches.length ) { + + const isConst = propsMatches[ i ][ 0 ] === 'const'; + + if ( isConst === true ) { + + i ++; + + } + + let qualifier = propsMatches[ i ][ 0 ]; + + if ( qualifier === 'in' || qualifier === 'out' || qualifier === 'inout' ) { + + i ++; + + } else { + + qualifier = ''; + + } + + const type = propsMatches[ i ++ ][ 0 ]; + + let count = Number.parseInt( propsMatches[ i ][ 0 ] ); + + if ( Number.isNaN( count ) === false ) i ++; + else count = null; + + const name = propsMatches[ i ++ ][ 0 ]; + + inputs.push( new NodeFunctionInput( type, name, count, qualifier, isConst ) ); + + } + + // + + const blockCode = mainCode.substring( declaration[ 0 ].length ); + + const name = declaration[ 3 ] !== undefined ? declaration[ 3 ] : ''; + const type = declaration[ 2 ]; + + const precision = declaration[ 1 ] !== undefined ? declaration[ 1 ] : ''; + + const headerCode = pragmaMainIndex !== -1 ? source.slice( 0, pragmaMainIndex ) : ''; + + return { + type, + inputs, + name, + precision, + inputsCode, + blockCode, + headerCode + }; + + } else { + + throw new Error( 'FunctionNode: Function is not a GLSL code.' ); + + } + +}; + +/** + * This class represents a GLSL node function. + * + * @augments NodeFunction + */ +class GLSLNodeFunction extends NodeFunction { + + /** + * Constructs a new GLSL node function. + * + * @param {string} source - The GLSL source. + */ + constructor( source ) { + + const { type, inputs, name, precision, inputsCode, blockCode, headerCode } = parse$1( source ); + + super( type, inputs, name, precision ); + + this.inputsCode = inputsCode; + this.blockCode = blockCode; + this.headerCode = headerCode; + + } + + /** + * This method returns the GLSL code of the node function. + * + * @param {string} [name=this.name] - The function's name. + * @return {string} The shader code. + */ + getCode( name = this.name ) { + + let code; + + const blockCode = this.blockCode; + + if ( blockCode !== '' ) { + + const { type, inputsCode, headerCode, precision } = this; + + let declarationCode = `${ type } ${ name } ( ${ inputsCode.trim() } )`; + + if ( precision !== '' ) { + + declarationCode = `${ precision } ${ declarationCode }`; + + } + + code = headerCode + declarationCode + blockCode; + + } else { + + // interface function + + code = ''; + + } + + return code; + + } + +} + +/** + * A GLSL node parser. + * + * @augments NodeParser + */ +class GLSLNodeParser extends NodeParser { + + /** + * The method parses the given GLSL code an returns a node function. + * + * @param {string} source - The GLSL code. + * @return {GLSLNodeFunction} A node function. + */ + parseFunction( source ) { + + return new GLSLNodeFunction( source ); + + } + +} + +const _outputNodeMap = new WeakMap(); +const _chainKeys$2 = []; +const _cacheKeyValues = []; + +/** + * This renderer module manages node-related objects and is the + * primary interface between the renderer and the node system. + * + * @private + * @augments DataMap + */ +class Nodes extends DataMap { + + /** + * Constructs a new nodes management component. + * + * @param {Renderer} renderer - The renderer. + * @param {Backend} backend - The renderer's backend. + */ + constructor( renderer, backend ) { + + super(); + + /** + * The renderer. + * + * @type {Renderer} + */ + this.renderer = renderer; + + /** + * The renderer's backend. + * + * @type {Backend} + */ + this.backend = backend; + + /** + * The node frame. + * + * @type {Renderer} + */ + this.nodeFrame = new NodeFrame(); + + /** + * A cache for managing node builder states. + * + * @type {Map} + */ + this.nodeBuilderCache = new Map(); + + /** + * A cache for managing data cache key data. + * + * @type {ChainMap} + */ + this.callHashCache = new ChainMap(); + + /** + * A cache for managing node uniforms group data. + * + * @type {ChainMap} + */ + this.groupsData = new ChainMap(); + + /** + * A cache for managing node objects of + * scene properties like fog or environments. + * + * @type {Object} + */ + this.cacheLib = {}; + + } + + /** + * Returns `true` if the given node uniforms group must be updated or not. + * + * @param {NodeUniformsGroup} nodeUniformsGroup - The node uniforms group. + * @return {boolean} Whether the node uniforms group requires an update or not. + */ + updateGroup( nodeUniformsGroup ) { + + const groupNode = nodeUniformsGroup.groupNode; + const name = groupNode.name; + + // objectGroup is always updated + + if ( name === objectGroup.name ) return true; + + // renderGroup is updated once per render/compute call + + if ( name === renderGroup.name ) { + + const uniformsGroupData = this.get( nodeUniformsGroup ); + const renderId = this.nodeFrame.renderId; + + if ( uniformsGroupData.renderId !== renderId ) { + + uniformsGroupData.renderId = renderId; + + return true; + + } + + return false; + + } + + // frameGroup is updated once per frame + + if ( name === frameGroup.name ) { + + const uniformsGroupData = this.get( nodeUniformsGroup ); + const frameId = this.nodeFrame.frameId; + + if ( uniformsGroupData.frameId !== frameId ) { + + uniformsGroupData.frameId = frameId; + + return true; + + } + + return false; + + } + + // other groups are updated just when groupNode.needsUpdate is true + + _chainKeys$2[ 0 ] = groupNode; + _chainKeys$2[ 1 ] = nodeUniformsGroup; + + let groupData = this.groupsData.get( _chainKeys$2 ); + if ( groupData === undefined ) this.groupsData.set( _chainKeys$2, groupData = {} ); + + _chainKeys$2.length = 0; + + if ( groupData.version !== groupNode.version ) { + + groupData.version = groupNode.version; + + return true; + + } + + return false; + + } + + /** + * Returns the cache key for the given render object. + * + * @param {RenderObject} renderObject - The render object. + * @return {number} The cache key. + */ + getForRenderCacheKey( renderObject ) { + + return renderObject.initialCacheKey; + + } + + /** + * Returns a node builder state for the given render object. + * + * @param {RenderObject} renderObject - The render object. + * @return {NodeBuilderState} The node builder state. + */ + getForRender( renderObject ) { + + const renderObjectData = this.get( renderObject ); + + let nodeBuilderState = renderObjectData.nodeBuilderState; + + if ( nodeBuilderState === undefined ) { + + const { nodeBuilderCache } = this; + + const cacheKey = this.getForRenderCacheKey( renderObject ); + + nodeBuilderState = nodeBuilderCache.get( cacheKey ); + + if ( nodeBuilderState === undefined ) { + + const nodeBuilder = this.backend.createNodeBuilder( renderObject.object, this.renderer ); + nodeBuilder.scene = renderObject.scene; + nodeBuilder.material = renderObject.material; + nodeBuilder.camera = renderObject.camera; + nodeBuilder.context.material = renderObject.material; + nodeBuilder.lightsNode = renderObject.lightsNode; + nodeBuilder.environmentNode = this.getEnvironmentNode( renderObject.scene ); + nodeBuilder.fogNode = this.getFogNode( renderObject.scene ); + nodeBuilder.clippingContext = renderObject.clippingContext; + if ( this.renderer.getOutputRenderTarget() ? this.renderer.getOutputRenderTarget().multiview : false ) { + + nodeBuilder.enableMultiview(); + + } + + nodeBuilder.build(); + + nodeBuilderState = this._createNodeBuilderState( nodeBuilder ); + + nodeBuilderCache.set( cacheKey, nodeBuilderState ); + + } + + nodeBuilderState.usedTimes ++; + + renderObjectData.nodeBuilderState = nodeBuilderState; + + } + + return nodeBuilderState; + + } + + /** + * Deletes the given object from the internal data map + * + * @param {any} object - The object to delete. + * @return {?Object} The deleted dictionary. + */ + delete( object ) { + + if ( object.isRenderObject ) { + + const nodeBuilderState = this.get( object ).nodeBuilderState; + nodeBuilderState.usedTimes --; + + if ( nodeBuilderState.usedTimes === 0 ) { + + this.nodeBuilderCache.delete( this.getForRenderCacheKey( object ) ); + + } + + } + + return super.delete( object ); + + } + + /** + * Returns a node builder state for the given compute node. + * + * @param {Node} computeNode - The compute node. + * @return {NodeBuilderState} The node builder state. + */ + getForCompute( computeNode ) { + + const computeData = this.get( computeNode ); + + let nodeBuilderState = computeData.nodeBuilderState; + + if ( nodeBuilderState === undefined ) { + + const nodeBuilder = this.backend.createNodeBuilder( computeNode, this.renderer ); + nodeBuilder.build(); + + nodeBuilderState = this._createNodeBuilderState( nodeBuilder ); + + computeData.nodeBuilderState = nodeBuilderState; + + } + + return nodeBuilderState; + + } + + /** + * Creates a node builder state for the given node builder. + * + * @private + * @param {NodeBuilder} nodeBuilder - The node builder. + * @return {NodeBuilderState} The node builder state. + */ + _createNodeBuilderState( nodeBuilder ) { + + return new NodeBuilderState( + nodeBuilder.vertexShader, + nodeBuilder.fragmentShader, + nodeBuilder.computeShader, + nodeBuilder.getAttributesArray(), + nodeBuilder.getBindings(), + nodeBuilder.updateNodes, + nodeBuilder.updateBeforeNodes, + nodeBuilder.updateAfterNodes, + nodeBuilder.observer, + nodeBuilder.transforms + ); + + } + + /** + * Returns an environment node for the current configured + * scene environment. + * + * @param {Scene} scene - The scene. + * @return {Node} A node representing the current scene environment. + */ + getEnvironmentNode( scene ) { + + this.updateEnvironment( scene ); + + let environmentNode = null; + + if ( scene.environmentNode && scene.environmentNode.isNode ) { + + environmentNode = scene.environmentNode; + + } else { + + const sceneData = this.get( scene ); + + if ( sceneData.environmentNode ) { + + environmentNode = sceneData.environmentNode; + + } + + } + + return environmentNode; + + } + + /** + * Returns a background node for the current configured + * scene background. + * + * @param {Scene} scene - The scene. + * @return {Node} A node representing the current scene background. + */ + getBackgroundNode( scene ) { + + this.updateBackground( scene ); + + let backgroundNode = null; + + if ( scene.backgroundNode && scene.backgroundNode.isNode ) { + + backgroundNode = scene.backgroundNode; + + } else { + + const sceneData = this.get( scene ); + + if ( sceneData.backgroundNode ) { + + backgroundNode = sceneData.backgroundNode; + + } + + } + + return backgroundNode; + + } + + /** + * Returns a fog node for the current configured scene fog. + * + * @param {Scene} scene - The scene. + * @return {Node} A node representing the current scene fog. + */ + getFogNode( scene ) { + + this.updateFog( scene ); + + return scene.fogNode || this.get( scene ).fogNode || null; + + } + + /** + * Returns a cache key for the given scene and lights node. + * This key is used by `RenderObject` as a part of the dynamic + * cache key (a key that must be checked every time the render + * objects is drawn). + * + * @param {Scene} scene - The scene. + * @param {LightsNode} lightsNode - The lights node. + * @return {number} The cache key. + */ + getCacheKey( scene, lightsNode ) { + + _chainKeys$2[ 0 ] = scene; + _chainKeys$2[ 1 ] = lightsNode; + + const callId = this.renderer.info.calls; + + const cacheKeyData = this.callHashCache.get( _chainKeys$2 ) || {}; + + if ( cacheKeyData.callId !== callId ) { + + const environmentNode = this.getEnvironmentNode( scene ); + const fogNode = this.getFogNode( scene ); + + if ( lightsNode ) _cacheKeyValues.push( lightsNode.getCacheKey( true ) ); + if ( environmentNode ) _cacheKeyValues.push( environmentNode.getCacheKey() ); + if ( fogNode ) _cacheKeyValues.push( fogNode.getCacheKey() ); + + _cacheKeyValues.push( this.renderer.getOutputRenderTarget() && this.renderer.getOutputRenderTarget().multiview ? 1 : 0 ); + _cacheKeyValues.push( this.renderer.shadowMap.enabled ? 1 : 0 ); + + cacheKeyData.callId = callId; + cacheKeyData.cacheKey = hashArray( _cacheKeyValues ); + + this.callHashCache.set( _chainKeys$2, cacheKeyData ); + + _cacheKeyValues.length = 0; + + } + + _chainKeys$2.length = 0; + + return cacheKeyData.cacheKey; + + } + + /** + * A boolean that indicates whether tone mapping should be enabled + * or not. + * + * @type {boolean} + */ + get isToneMappingState() { + + return this.renderer.getRenderTarget() ? false : true; + + } + + /** + * If a scene background is configured, this method makes sure to + * represent the background with a corresponding node-based implementation. + * + * @param {Scene} scene - The scene. + */ + updateBackground( scene ) { + + const sceneData = this.get( scene ); + const background = scene.background; + + if ( background ) { + + const forceUpdate = ( scene.backgroundBlurriness === 0 && sceneData.backgroundBlurriness > 0 ) || ( scene.backgroundBlurriness > 0 && sceneData.backgroundBlurriness === 0 ); + + if ( sceneData.background !== background || forceUpdate ) { + + const backgroundNode = this.getCacheNode( 'background', background, () => { + + if ( background.isCubeTexture === true || ( background.mapping === EquirectangularReflectionMapping || background.mapping === EquirectangularRefractionMapping || background.mapping === CubeUVReflectionMapping ) ) { + + if ( scene.backgroundBlurriness > 0 || background.mapping === CubeUVReflectionMapping ) { + + return pmremTexture( background ); + + } else { + + let envMap; + + if ( background.isCubeTexture === true ) { + + envMap = cubeTexture( background ); + + } else { + + envMap = texture( background ); + + } + + return cubeMapNode( envMap ); + + } + + } else if ( background.isTexture === true ) { + + return texture( background, screenUV.flipY() ).setUpdateMatrix( true ); + + } else if ( background.isColor !== true ) { + + console.error( 'WebGPUNodes: Unsupported background configuration.', background ); + + } + + }, forceUpdate ); + + sceneData.backgroundNode = backgroundNode; + sceneData.background = background; + sceneData.backgroundBlurriness = scene.backgroundBlurriness; + + } + + } else if ( sceneData.backgroundNode ) { + + delete sceneData.backgroundNode; + delete sceneData.background; + + } + + } + + /** + * This method is part of the caching of nodes which are used to represents the + * scene's background, fog or environment. + * + * @param {string} type - The type of object to cache. + * @param {Object} object - The object. + * @param {Function} callback - A callback that produces a node representation for the given object. + * @param {boolean} [forceUpdate=false] - Whether an update should be enforced or not. + * @return {Node} The node representation. + */ + getCacheNode( type, object, callback, forceUpdate = false ) { + + const nodeCache = this.cacheLib[ type ] || ( this.cacheLib[ type ] = new WeakMap() ); + + let node = nodeCache.get( object ); + + if ( node === undefined || forceUpdate ) { + + node = callback(); + nodeCache.set( object, node ); + + } + + return node; + + } + + /** + * If a scene fog is configured, this method makes sure to + * represent the fog with a corresponding node-based implementation. + * + * @param {Scene} scene - The scene. + */ + updateFog( scene ) { + + const sceneData = this.get( scene ); + const sceneFog = scene.fog; + + if ( sceneFog ) { + + if ( sceneData.fog !== sceneFog ) { + + const fogNode = this.getCacheNode( 'fog', sceneFog, () => { + + if ( sceneFog.isFogExp2 ) { + + const color = reference( 'color', 'color', sceneFog ).setGroup( renderGroup ); + const density = reference( 'density', 'float', sceneFog ).setGroup( renderGroup ); + + return fog( color, densityFogFactor( density ) ); + + } else if ( sceneFog.isFog ) { + + const color = reference( 'color', 'color', sceneFog ).setGroup( renderGroup ); + const near = reference( 'near', 'float', sceneFog ).setGroup( renderGroup ); + const far = reference( 'far', 'float', sceneFog ).setGroup( renderGroup ); + + return fog( color, rangeFogFactor( near, far ) ); + + } else { + + console.error( 'THREE.Renderer: Unsupported fog configuration.', sceneFog ); + + } + + } ); + + sceneData.fogNode = fogNode; + sceneData.fog = sceneFog; + + } + + } else { + + delete sceneData.fogNode; + delete sceneData.fog; + + } + + } + + /** + * If a scene environment is configured, this method makes sure to + * represent the environment with a corresponding node-based implementation. + * + * @param {Scene} scene - The scene. + */ + updateEnvironment( scene ) { + + const sceneData = this.get( scene ); + const environment = scene.environment; + + if ( environment ) { + + if ( sceneData.environment !== environment ) { + + const environmentNode = this.getCacheNode( 'environment', environment, () => { + + if ( environment.isCubeTexture === true ) { + + return cubeTexture( environment ); + + } else if ( environment.isTexture === true ) { + + return texture( environment ); + + } else { + + console.error( 'Nodes: Unsupported environment configuration.', environment ); + + } + + } ); + + sceneData.environmentNode = environmentNode; + sceneData.environment = environment; + + } + + } else if ( sceneData.environmentNode ) { + + delete sceneData.environmentNode; + delete sceneData.environment; + + } + + } + + getNodeFrame( renderer = this.renderer, scene = null, object = null, camera = null, material = null ) { + + const nodeFrame = this.nodeFrame; + nodeFrame.renderer = renderer; + nodeFrame.scene = scene; + nodeFrame.object = object; + nodeFrame.camera = camera; + nodeFrame.material = material; + + return nodeFrame; + + } + + getNodeFrameForRender( renderObject ) { + + return this.getNodeFrame( renderObject.renderer, renderObject.scene, renderObject.object, renderObject.camera, renderObject.material ); + + } + + /** + * Returns the current output cache key. + * + * @return {string} The output cache key. + */ + getOutputCacheKey() { + + const renderer = this.renderer; + + return renderer.toneMapping + ',' + renderer.currentColorSpace + ',' + renderer.xr.isPresenting; + + } + + /** + * Checks if the output configuration (tone mapping and color space) for + * the given target has changed. + * + * @param {Texture} outputTarget - The output target. + * @return {boolean} Whether the output configuration has changed or not. + */ + hasOutputChange( outputTarget ) { + + const cacheKey = _outputNodeMap.get( outputTarget ); + + return cacheKey !== this.getOutputCacheKey(); + + } + + /** + * Returns a node that represents the output configuration (tone mapping and + * color space) for the current target. + * + * @param {Texture} outputTarget - The output target. + * @return {Node} The output node. + */ + getOutputNode( outputTarget ) { + + const renderer = this.renderer; + const cacheKey = this.getOutputCacheKey(); + + const output = outputTarget.isArrayTexture ? + texture3D( outputTarget, vec3( screenUV, builtin( 'gl_ViewID_OVR' ) ) ).renderOutput( renderer.toneMapping, renderer.currentColorSpace ) : + texture( outputTarget, screenUV ).renderOutput( renderer.toneMapping, renderer.currentColorSpace ); + + _outputNodeMap.set( outputTarget, cacheKey ); + + return output; + + } + + /** + * Triggers the call of `updateBefore()` methods + * for all nodes of the given render object. + * + * @param {RenderObject} renderObject - The render object. + */ + updateBefore( renderObject ) { + + const nodeBuilder = renderObject.getNodeBuilderState(); + + for ( const node of nodeBuilder.updateBeforeNodes ) { + + // update frame state for each node + + this.getNodeFrameForRender( renderObject ).updateBeforeNode( node ); + + } + + } + + /** + * Triggers the call of `updateAfter()` methods + * for all nodes of the given render object. + * + * @param {RenderObject} renderObject - The render object. + */ + updateAfter( renderObject ) { + + const nodeBuilder = renderObject.getNodeBuilderState(); + + for ( const node of nodeBuilder.updateAfterNodes ) { + + // update frame state for each node + + this.getNodeFrameForRender( renderObject ).updateAfterNode( node ); + + } + + } + + /** + * Triggers the call of `update()` methods + * for all nodes of the given compute node. + * + * @param {Node} computeNode - The compute node. + */ + updateForCompute( computeNode ) { + + const nodeFrame = this.getNodeFrame(); + const nodeBuilder = this.getForCompute( computeNode ); + + for ( const node of nodeBuilder.updateNodes ) { + + nodeFrame.updateNode( node ); + + } + + } + + /** + * Triggers the call of `update()` methods + * for all nodes of the given compute node. + * + * @param {RenderObject} renderObject - The render object. + */ + updateForRender( renderObject ) { + + const nodeFrame = this.getNodeFrameForRender( renderObject ); + const nodeBuilder = renderObject.getNodeBuilderState(); + + for ( const node of nodeBuilder.updateNodes ) { + + nodeFrame.updateNode( node ); + + } + + } + + /** + * Returns `true` if the given render object requires a refresh. + * + * @param {RenderObject} renderObject - The render object. + * @return {boolean} Whether the given render object requires a refresh or not. + */ + needsRefresh( renderObject ) { + + const nodeFrame = this.getNodeFrameForRender( renderObject ); + const monitor = renderObject.getMonitor(); + + return monitor.needsRefresh( renderObject, nodeFrame ); + + } + + /** + * Frees the internal resources. + */ + dispose() { + + super.dispose(); + + this.nodeFrame = new NodeFrame(); + this.nodeBuilderCache = new Map(); + this.cacheLib = {}; + + } + +} + +const _plane = /*@__PURE__*/ new Plane(); + +/** + * Represents the state that is used to perform clipping via clipping planes. + * There is a default clipping context for each render context. When the + * scene holds instances of `ClippingGroup`, there will be a context for each + * group. + * + * @private + */ +class ClippingContext { + + /** + * Constructs a new clipping context. + * + * @param {?ClippingContext} [parentContext=null] - A reference to the parent clipping context. + */ + constructor( parentContext = null ) { + + /** + * The clipping context's version. + * + * @type {number} + * @readonly + */ + this.version = 0; + + /** + * Whether the intersection of the clipping planes is used to clip objects, rather than their union. + * + * @type {?boolean} + * @default null + */ + this.clipIntersection = null; + + /** + * The clipping context's cache key. + * + * @type {string} + */ + this.cacheKey = ''; + + /** + * Whether the shadow pass is active or not. + * + * @type {boolean} + * @default false + */ + this.shadowPass = false; + + /** + * The view normal matrix. + * + * @type {Matrix3} + */ + this.viewNormalMatrix = new Matrix3(); + + /** + * Internal cache for maintaining clipping contexts. + * + * @type {WeakMap} + */ + this.clippingGroupContexts = new WeakMap(); + + /** + * The intersection planes. + * + * @type {Array} + */ + this.intersectionPlanes = []; + + /** + * The intersection planes. + * + * @type {Array} + */ + this.unionPlanes = []; + + /** + * The version of the clipping context's parent context. + * + * @type {?number} + * @readonly + */ + this.parentVersion = null; + + if ( parentContext !== null ) { + + this.viewNormalMatrix = parentContext.viewNormalMatrix; + this.clippingGroupContexts = parentContext.clippingGroupContexts; + + this.shadowPass = parentContext.shadowPass; + this.viewMatrix = parentContext.viewMatrix; + + } + + } + + /** + * Projects the given source clipping planes and writes the result into the + * destination array. + * + * @param {Array} source - The source clipping planes. + * @param {Array} destination - The destination. + * @param {number} offset - The offset. + */ + projectPlanes( source, destination, offset ) { + + const l = source.length; + + for ( let i = 0; i < l; i ++ ) { + + _plane.copy( source[ i ] ).applyMatrix4( this.viewMatrix, this.viewNormalMatrix ); + + const v = destination[ offset + i ]; + const normal = _plane.normal; + + v.x = - normal.x; + v.y = - normal.y; + v.z = - normal.z; + v.w = _plane.constant; + + } + + } + + /** + * Updates the root clipping context of a scene. + * + * @param {Scene} scene - The scene. + * @param {Camera} camera - The camera that is used to render the scene. + */ + updateGlobal( scene, camera ) { + + this.shadowPass = ( scene.overrideMaterial !== null && scene.overrideMaterial.isShadowPassMaterial ); + this.viewMatrix = camera.matrixWorldInverse; + + this.viewNormalMatrix.getNormalMatrix( this.viewMatrix ); + + } + + /** + * Updates the clipping context. + * + * @param {ClippingContext} parentContext - The parent context. + * @param {ClippingGroup} clippingGroup - The clipping group this context belongs to. + */ + update( parentContext, clippingGroup ) { + + let update = false; + + if ( parentContext.version !== this.parentVersion ) { + + this.intersectionPlanes = Array.from( parentContext.intersectionPlanes ); + this.unionPlanes = Array.from( parentContext.unionPlanes ); + this.parentVersion = parentContext.version; + + } + + if ( this.clipIntersection !== clippingGroup.clipIntersection ) { + + this.clipIntersection = clippingGroup.clipIntersection; + + if ( this.clipIntersection ) { + + this.unionPlanes.length = parentContext.unionPlanes.length; + + } else { + + this.intersectionPlanes.length = parentContext.intersectionPlanes.length; + + } + + } + + const srcClippingPlanes = clippingGroup.clippingPlanes; + const l = srcClippingPlanes.length; + + let dstClippingPlanes; + let offset; + + if ( this.clipIntersection ) { + + dstClippingPlanes = this.intersectionPlanes; + offset = parentContext.intersectionPlanes.length; + + } else { + + dstClippingPlanes = this.unionPlanes; + offset = parentContext.unionPlanes.length; + + } + + if ( dstClippingPlanes.length !== offset + l ) { + + dstClippingPlanes.length = offset + l; + + for ( let i = 0; i < l; i ++ ) { + + dstClippingPlanes[ offset + i ] = new Vector4(); + + } + + update = true; + + } + + this.projectPlanes( srcClippingPlanes, dstClippingPlanes, offset ); + + if ( update ) { + + this.version ++; + this.cacheKey = `${ this.intersectionPlanes.length }:${ this.unionPlanes.length }`; + + } + + } + + /** + * Returns a clipping context for the given clipping group. + * + * @param {ClippingGroup} clippingGroup - The clipping group. + * @return {ClippingContext} The clipping context. + */ + getGroupContext( clippingGroup ) { + + if ( this.shadowPass && ! clippingGroup.clipShadows ) return this; + + let context = this.clippingGroupContexts.get( clippingGroup ); + + if ( context === undefined ) { + + context = new ClippingContext( this ); + this.clippingGroupContexts.set( clippingGroup, context ); + + } + + context.update( this, clippingGroup ); + + return context; + + } + + /** + * The count of union clipping planes. + * + * @type {number} + * @readonly + */ + get unionClippingCount() { + + return this.unionPlanes.length; + + } + +} + +/** + * This module is used to represent render bundles inside the renderer + * for further processing. + * + * @private + */ +class RenderBundle { + + /** + * Constructs a new bundle group. + * + * @param {BundleGroup} bundleGroup - The bundle group. + * @param {Camera} camera - The camera the bundle group is rendered with. + */ + constructor( bundleGroup, camera ) { + + this.bundleGroup = bundleGroup; + this.camera = camera; + + } + +} + +const _chainKeys$1 = []; + +/** + * This renderer module manages render bundles. + * + * @private + */ +class RenderBundles { + + /** + * Constructs a new render bundle management component. + */ + constructor() { + + /** + * A chain map for maintaining the render bundles. + * + * @type {ChainMap} + */ + this.bundles = new ChainMap(); + + } + + /** + * Returns a render bundle for the given bundle group and camera. + * + * @param {BundleGroup} bundleGroup - The bundle group. + * @param {Camera} camera - The camera the bundle group is rendered with. + * @return {RenderBundle} The render bundle. + */ + get( bundleGroup, camera ) { + + const bundles = this.bundles; + + _chainKeys$1[ 0 ] = bundleGroup; + _chainKeys$1[ 1 ] = camera; + + let bundle = bundles.get( _chainKeys$1 ); + + if ( bundle === undefined ) { + + bundle = new RenderBundle( bundleGroup, camera ); + bundles.set( _chainKeys$1, bundle ); + + } + + _chainKeys$1.length = 0; + + return bundle; + + } + + /** + * Frees all internal resources. + */ + dispose() { + + this.bundles = new ChainMap(); + + } + +} + +/** + * The purpose of a node library is to assign node implementations + * to existing library features. In `WebGPURenderer` lights, materials + * which are not based on `NodeMaterial` as well as tone mapping techniques + * are implemented with node-based modules. + * + * @private + */ +class NodeLibrary { + + /** + * Constructs a new node library. + */ + constructor() { + + /** + * A weak map that maps lights to light nodes. + * + * @type {WeakMap} + */ + this.lightNodes = new WeakMap(); + + /** + * A map that maps materials to node materials. + * + * @type {Map} + */ + this.materialNodes = new Map(); + + /** + * A map that maps tone mapping techniques (constants) + * to tone mapping node functions. + * + * @type {Map} + */ + this.toneMappingNodes = new Map(); + + } + + /** + * Returns a matching node material instance for the given material object. + * + * This method also assigns/copies the properties of the given material object + * to the node material. This is done to make sure the current material + * configuration carries over to the node version. + * + * @param {Material} material - A material. + * @return {NodeMaterial} The corresponding node material. + */ + fromMaterial( material ) { + + if ( material.isNodeMaterial ) return material; + + let nodeMaterial = null; + + const nodeMaterialClass = this.getMaterialNodeClass( material.type ); + + if ( nodeMaterialClass !== null ) { + + nodeMaterial = new nodeMaterialClass(); + + for ( const key in material ) { + + nodeMaterial[ key ] = material[ key ]; + + } + + } + + return nodeMaterial; + + } + + /** + * Adds a tone mapping node function for a tone mapping technique (constant). + * + * @param {Function} toneMappingNode - The tone mapping node function. + * @param {number} toneMapping - The tone mapping. + */ + addToneMapping( toneMappingNode, toneMapping ) { + + this.addType( toneMappingNode, toneMapping, this.toneMappingNodes ); + + } + + /** + * Returns a tone mapping node function for a tone mapping technique (constant). + * + * @param {number} toneMapping - The tone mapping. + * @return {?Function} The tone mapping node function. Returns `null` if no node function is found. + */ + getToneMappingFunction( toneMapping ) { + + return this.toneMappingNodes.get( toneMapping ) || null; + + } + + /** + * Returns a node material class definition for a material type. + * + * @param {string} materialType - The material type. + * @return {?NodeMaterial.constructor} The node material class definition. Returns `null` if no node material is found. + */ + getMaterialNodeClass( materialType ) { + + return this.materialNodes.get( materialType ) || null; + + } + + /** + * Adds a node material class definition for a given material type. + * + * @param {NodeMaterial.constructor} materialNodeClass - The node material class definition. + * @param {string} materialClassType - The material type. + */ + addMaterial( materialNodeClass, materialClassType ) { + + this.addType( materialNodeClass, materialClassType, this.materialNodes ); + + } + + /** + * Returns a light node class definition for a light class definition. + * + * @param {Light.constructor} light - The light class definition. + * @return {?AnalyticLightNode.constructor} The light node class definition. Returns `null` if no light node is found. + */ + getLightNodeClass( light ) { + + return this.lightNodes.get( light ) || null; + + } + + /** + * Adds a light node class definition for a given light class definition. + * + * @param {AnalyticLightNode.constructor} lightNodeClass - The light node class definition. + * @param {Light.constructor} lightClass - The light class definition. + */ + addLight( lightNodeClass, lightClass ) { + + this.addClass( lightNodeClass, lightClass, this.lightNodes ); + + } + + /** + * Adds a node class definition for the given type to the provided type library. + * + * @param {any} nodeClass - The node class definition. + * @param {number|string} type - The object type. + * @param {Map} library - The type library. + */ + addType( nodeClass, type, library ) { + + if ( library.has( type ) ) { + + console.warn( `Redefinition of node ${ type }` ); + return; + + } + + if ( typeof nodeClass !== 'function' ) throw new Error( `Node class ${ nodeClass.name } is not a class.` ); + if ( typeof type === 'function' || typeof type === 'object' ) throw new Error( `Base class ${ type } is not a class.` ); + + library.set( type, nodeClass ); + + } + + /** + * Adds a node class definition for the given class definition to the provided type library. + * + * @param {any} nodeClass - The node class definition. + * @param {any} baseClass - The class definition. + * @param {WeakMap} library - The type library. + */ + addClass( nodeClass, baseClass, library ) { + + if ( library.has( baseClass ) ) { + + console.warn( `Redefinition of node ${ baseClass.name }` ); + return; + + } + + if ( typeof nodeClass !== 'function' ) throw new Error( `Node class ${ nodeClass.name } is not a class.` ); + if ( typeof baseClass !== 'function' ) throw new Error( `Base class ${ baseClass.name } is not a class.` ); + + library.set( baseClass, nodeClass ); + + } + +} + +const _defaultLights = /*@__PURE__*/ new LightsNode(); +const _chainKeys = []; + +/** + * This renderer module manages the lights nodes which are unique + * per scene and camera combination. + * + * The lights node itself is later configured in the render list + * with the actual lights from the scene. + * + * @private + * @augments ChainMap + */ +class Lighting extends ChainMap { + + /** + * Constructs a lighting management component. + */ + constructor() { + + super(); + + } + + /** + * Creates a new lights node for the given array of lights. + * + * @param {Array} lights - The render object. + * @return {LightsNode} The lights node. + */ + createNode( lights = [] ) { + + return new LightsNode().setLights( lights ); + + } + + /** + * Returns a lights node for the given scene and camera. + * + * @param {Scene} scene - The scene. + * @param {Camera} camera - The camera. + * @return {LightsNode} The lights node. + */ + getNode( scene, camera ) { + + // ignore post-processing + + if ( scene.isQuadMesh ) return _defaultLights; + + _chainKeys[ 0 ] = scene; + _chainKeys[ 1 ] = camera; + + let node = this.get( _chainKeys ); + + if ( node === undefined ) { + + node = this.createNode(); + this.set( _chainKeys, node ); + + } + + _chainKeys.length = 0; + + return node; + + } + +} + +/** + * A special type of render target that is used when rendering + * with the WebXR Device API. + * + * @private + * @augments RenderTarget + */ +class XRRenderTarget extends RenderTarget { + + /** + * Constructs a new XR render target. + * + * @param {number} [width=1] - The width of the render target. + * @param {number} [height=1] - The height of the render target. + * @param {Object} [options={}] - The configuration options. + */ + constructor( width = 1, height = 1, options = {} ) { + + super( width, height, options ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isXRRenderTarget = true; + + /** + * Whether the attachments of the render target + * are defined by external textures. This flag is + * set to `true` when using the WebXR Layers API. + * + * @private + * @type {boolean} + * @default false + */ + this._hasExternalTextures = false; + + /** + * Whether a depth buffer should automatically be allocated + * for this XR render target or not. + * + * Allocating a depth buffer is the default behavior of XR render + * targets. However, when using the WebXR Layers API, this flag + * must be set to `false` when the `ignoreDepthValues` property of + * the projection layers evaluates to `false`. + * + * Reference: {@link https://www.w3.org/TR/webxrlayers-1/#dom-xrprojectionlayer-ignoredepthvalues}. + * + * @private + * @type {boolean} + * @default true + */ + this._autoAllocateDepthBuffer = true; + + /** + * Whether this render target is associated with a XRWebGLLayer. + * + * A XRWebGLLayer points to an opaque framebuffer. Basically, + * this means that you don't have access to its bound color, + * stencil and depth buffers. We need to handle this framebuffer + * differently since its textures are always bound. + * + * @private + * @type {boolean} + * @default false + * */ + this._isOpaqueFramebuffer = false; + + } + + copy( source ) { + + super.copy( source ); + + this._hasExternalTextures = source._hasExternalTextures; + this._autoAllocateDepthBuffer = source._autoAllocateDepthBuffer; + this._isOpaqueFramebuffer = source._isOpaqueFramebuffer; + + return this; + + } + + +} + +const _cameraLPos = /*@__PURE__*/ new Vector3(); +const _cameraRPos = /*@__PURE__*/ new Vector3(); + +/** + * The XR manager is built on top of the WebXR Device API to + * manage XR sessions with `WebGPURenderer`. + * + * XR is currently only supported with a WebGL 2 backend. + * + * @augments EventDispatcher + */ +class XRManager extends EventDispatcher { + + /** + * Constructs a new XR manager. + * + * @param {Renderer} renderer - The renderer. + * @param {boolean} [multiview=false] - Enables multiview if the device supports it. + */ + constructor( renderer, multiview = false ) { + + super(); + + /** + * This flag globally enables XR rendering. + * + * @type {boolean} + * @default false + */ + this.enabled = false; + + /** + * Whether the XR device is currently presenting or not. + * + * @type {boolean} + * @default false + * @readonly + */ + this.isPresenting = false; + + /** + * Whether the XR camera should automatically be updated or not. + * + * @type {boolean} + * @default true + */ + this.cameraAutoUpdate = true; + + /** + * The renderer. + * + * @private + * @type {Renderer} + */ + this._renderer = renderer; + + // camera + + /** + * Represents the camera for the left eye. + * + * @private + * @type {PerspectiveCamera} + */ + this._cameraL = new PerspectiveCamera(); + this._cameraL.viewport = new Vector4(); + + /** + * Represents the camera for the right eye. + * + * @private + * @type {PerspectiveCamera} + */ + this._cameraR = new PerspectiveCamera(); + this._cameraR.viewport = new Vector4(); + + /** + * A list of cameras used for rendering the XR views. + * + * @private + * @type {Array} + */ + this._cameras = [ this._cameraL, this._cameraR ]; + + /** + * The main XR camera. + * + * @private + * @type {ArrayCamera} + */ + this._cameraXR = new ArrayCamera(); + + /** + * The current near value of the XR camera. + * + * @private + * @type {?number} + * @default null + */ + this._currentDepthNear = null; + + /** + * The current far value of the XR camera. + * + * @private + * @type {?number} + * @default null + */ + this._currentDepthFar = null; + + /** + * A list of WebXR controllers requested by the application. + * + * @private + * @type {Array} + */ + this._controllers = []; + + /** + * A list of XR input source. Each input source belongs to + * an instance of WebXRController. + * + * @private + * @type {Array} + */ + this._controllerInputSources = []; + + /** + * The XR render target that represents the rendering destination + * during an active XR session. + * + * @private + * @type {?RenderTarget} + * @default null + */ + this._xrRenderTarget = null; + + /** + * An array holding all the non-projection layers + * + * @private + * @type {Array} + * @default [] + */ + this._layers = []; + + /** + * Whether the device has support for all layer types. + * + * @type {boolean} + * @default false + */ + this._supportsLayers = false; + + this._frameBufferTargets = null; + + /** + * Helper function to create native WebXR Layer. + * + * @private + * @type {Function} + */ + this._createXRLayer = createXRLayer.bind( this ); + + /** + * The current WebGL context. + * + * @private + * @type {?WebGL2RenderingContext} + * @default null + */ + this._gl = null; + + /** + * The current animation context. + * + * @private + * @type {?Window} + * @default null + */ + this._currentAnimationContext = null; + + /** + * The current animation loop. + * + * @private + * @type {?Function} + * @default null + */ + this._currentAnimationLoop = null; + + /** + * The current pixel ratio. + * + * @private + * @type {?number} + * @default null + */ + this._currentPixelRatio = null; + + /** + * The current size of the renderer's canvas + * in logical pixel unit. + * + * @private + * @type {Vector2} + */ + this._currentSize = new Vector2(); + + /** + * The default event listener for handling events inside a XR session. + * + * @private + * @type {Function} + */ + this._onSessionEvent = onSessionEvent.bind( this ); + + /** + * The event listener for handling the end of a XR session. + * + * @private + * @type {Function} + */ + this._onSessionEnd = onSessionEnd.bind( this ); + + /** + * The event listener for handling the `inputsourceschange` event. + * + * @private + * @type {Function} + */ + this._onInputSourcesChange = onInputSourcesChange.bind( this ); + + /** + * The animation loop which is used as a replacement for the default + * animation loop of the application. It is only used when a XR session + * is active. + * + * @private + * @type {Function} + */ + this._onAnimationFrame = onAnimationFrame.bind( this ); + + /** + * The current XR reference space. + * + * @private + * @type {?XRReferenceSpace} + * @default null + */ + this._referenceSpace = null; + + /** + * The current XR reference space type. + * + * @private + * @type {XRReferenceSpaceType} + * @default 'local-floor' + */ + this._referenceSpaceType = 'local-floor'; + + /** + * A custom reference space defined by the application. + * + * @private + * @type {?XRReferenceSpace} + * @default null + */ + this._customReferenceSpace = null; + + /** + * The framebuffer scale factor. + * + * @private + * @type {number} + * @default 1 + */ + this._framebufferScaleFactor = 1; + + /** + * The foveation factor. + * + * @private + * @type {number} + * @default 1 + */ + this._foveation = 1.0; + + /** + * A reference to the current XR session. + * + * @private + * @type {?XRSession} + * @default null + */ + this._session = null; + + /** + * A reference to the current XR base layer. + * + * @private + * @type {?XRWebGLLayer} + * @default null + */ + this._glBaseLayer = null; + + /** + * A reference to the current XR binding. + * + * @private + * @type {?XRWebGLBinding} + * @default null + */ + this._glBinding = null; + + /** + * A reference to the current XR projection layer. + * + * @private + * @type {?XRProjectionLayer} + * @default null + */ + this._glProjLayer = null; + + /** + * A reference to the current XR frame. + * + * @private + * @type {?XRFrame} + * @default null + */ + this._xrFrame = null; + + /** + * Whether to use the WebXR Layers API or not. + * + * @private + * @type {boolean} + * @readonly + */ + this._useLayers = ( typeof XRWebGLBinding !== 'undefined' && 'createProjectionLayer' in XRWebGLBinding.prototype ); // eslint-disable-line compat/compat + + /** + * Whether the usage of multiview has been requested by the application or not. + * + * @private + * @type {boolean} + * @default false + * @readonly + */ + this._useMultiviewIfPossible = multiview; + + /** + * Whether the usage of multiview is actually enabled. This flag only evaluates to `true` + * if multiview has been requested by the application and the `OVR_multiview2` is available. + * + * @private + * @type {boolean} + * @readonly + */ + this._useMultiview = false; + + } + + /** + * Returns an instance of `THREE.Group` that represents the transformation + * of a XR controller in target ray space. The requested controller is defined + * by the given index. + * + * @param {number} index - The index of the XR controller. + * @return {Group} A group that represents the controller's transformation. + */ + getController( index ) { + + const controller = this._getController( index ); + + return controller.getTargetRaySpace(); + + } + + /** + * Returns an instance of `THREE.Group` that represents the transformation + * of a XR controller in grip space. The requested controller is defined + * by the given index. + * + * @param {number} index - The index of the XR controller. + * @return {Group} A group that represents the controller's transformation. + */ + getControllerGrip( index ) { + + const controller = this._getController( index ); + + return controller.getGripSpace(); + + } + + /** + * Returns an instance of `THREE.Group` that represents the transformation + * of a XR controller in hand space. The requested controller is defined + * by the given index. + * + * @param {number} index - The index of the XR controller. + * @return {Group} A group that represents the controller's transformation. + */ + getHand( index ) { + + const controller = this._getController( index ); + + return controller.getHandSpace(); + + } + + /** + * Returns the foveation value. + * + * @return {number|undefined} The foveation value. Returns `undefined` if no base or projection layer is defined. + */ + getFoveation() { + + if ( this._glProjLayer === null && this._glBaseLayer === null ) { + + return undefined; + + } + + return this._foveation; + + } + + /** + * Sets the foveation value. + * + * @param {number} foveation - A number in the range `[0,1]` where `0` means no foveation (full resolution) + * and `1` means maximum foveation (the edges render at lower resolution). + */ + setFoveation( foveation ) { + + this._foveation = foveation; + + if ( this._glProjLayer !== null ) { + + this._glProjLayer.fixedFoveation = foveation; + + } + + if ( this._glBaseLayer !== null && this._glBaseLayer.fixedFoveation !== undefined ) { + + this._glBaseLayer.fixedFoveation = foveation; + + } + + } + + /** + * Returns the framebuffer scale factor. + * + * @return {number} The framebuffer scale factor. + */ + getFramebufferScaleFactor() { + + return this._framebufferScaleFactor; + + } + + /** + * Sets the framebuffer scale factor. + * + * This method can not be used during a XR session. + * + * @param {number} factor - The framebuffer scale factor. + */ + setFramebufferScaleFactor( factor ) { + + this._framebufferScaleFactor = factor; + + if ( this.isPresenting === true ) { + + console.warn( 'THREE.XRManager: Cannot change framebuffer scale while presenting.' ); + + } + + } + + /** + * Returns the reference space type. + * + * @return {XRReferenceSpaceType} The reference space type. + */ + getReferenceSpaceType() { + + return this._referenceSpaceType; + + } + + /** + * Sets the reference space type. + * + * This method can not be used during a XR session. + * + * @param {XRReferenceSpaceType} type - The reference space type. + */ + setReferenceSpaceType( type ) { + + this._referenceSpaceType = type; + + if ( this.isPresenting === true ) { + + console.warn( 'THREE.XRManager: Cannot change reference space type while presenting.' ); + + } + + } + + /** + * Returns the XR reference space. + * + * @return {XRReferenceSpace} The XR reference space. + */ + getReferenceSpace() { + + return this._customReferenceSpace || this._referenceSpace; + + } + + /** + * Sets a custom XR reference space. + * + * @param {XRReferenceSpace} space - The XR reference space. + */ + setReferenceSpace( space ) { + + this._customReferenceSpace = space; + + } + + /** + * Returns the XR camera. + * + * @return {ArrayCamera} The XR camera. + */ + getCamera() { + + return this._cameraXR; + + } + + /** + * Returns the environment blend mode from the current XR session. + * + * @return {'opaque'|'additive'|'alpha-blend'|undefined} The environment blend mode. Returns `undefined` when used outside of a XR session. + */ + getEnvironmentBlendMode() { + + if ( this._session !== null ) { + + return this._session.environmentBlendMode; + + } + + } + + /** + * Returns the current XR frame. + * + * @return {?XRFrame} The XR frame. Returns `null` when used outside a XR session. + */ + getFrame() { + + return this._xrFrame; + + } + + /** + * Returns `true` if the engine renders to a multiview target. + * + * @return {boolean} Whether the engine renders to a multiview render target or not. + */ + useMultiview() { + + return this._useMultiview; + + } + + /** + * This method can be used in XR applications to create a quadratic layer that presents a separate + * rendered scene. + * + * @param {number} width - The width of the layer plane in world units. + * @param {number} height - The height of the layer plane in world units. + * @param {Vector3} translation - The position/translation of the layer plane in world units. + * @param {Quaternion} quaternion - The orientation of the layer plane expressed as a quaternion. + * @param {number} pixelwidth - The width of the layer's render target in pixels. + * @param {number} pixelheight - The height of the layer's render target in pixels. + * @param {Function} rendercall - A callback function that renders the layer. Similar to code in + * the default animation loop, this method can be used to update/transform 3D object in the layer's scene. + * @param {Object} [attributes={}] - Allows to configure the layer's render target. + * @return {Mesh} A mesh representing the quadratic XR layer. This mesh should be added to the XR scene. + */ + createQuadLayer( width, height, translation, quaternion, pixelwidth, pixelheight, rendercall, attributes = {} ) { + + const geometry = new PlaneGeometry( width, height ); + const renderTarget = new XRRenderTarget( + pixelwidth, + pixelheight, + { + format: RGBAFormat, + type: UnsignedByteType, + depthTexture: new DepthTexture( + pixelwidth, + pixelheight, + attributes.stencil ? UnsignedInt248Type : UnsignedIntType, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + attributes.stencil ? DepthStencilFormat : DepthFormat + ), + stencilBuffer: attributes.stencil, + resolveDepthBuffer: false, + resolveStencilBuffer: false + } ); + + renderTarget._autoAllocateDepthBuffer = true; + + const material = new MeshBasicMaterial( { color: 0xffffff, side: FrontSide } ); + material.map = renderTarget.texture; + material.map.offset.y = 1; + material.map.repeat.y = -1; + const plane = new Mesh( geometry, material ); + plane.position.copy( translation ); + plane.quaternion.copy( quaternion ); + + const layer = { + type: 'quad', + width: width, + height: height, + translation: translation, + quaternion: quaternion, + pixelwidth: pixelwidth, + pixelheight: pixelheight, + plane: plane, + material: material, + rendercall: rendercall, + renderTarget: renderTarget }; + + this._layers.push( layer ); + + if ( this._session !== null ) { + + layer.plane.material = new MeshBasicMaterial( { color: 0xffffff, side: FrontSide } ); + layer.plane.material.blending = CustomBlending; + layer.plane.material.blendEquation = AddEquation; + layer.plane.material.blendSrc = ZeroFactor; + layer.plane.material.blendDst = ZeroFactor; + + layer.xrlayer = this._createXRLayer( layer ); + + const xrlayers = this._session.renderState.layers; + xrlayers.unshift( layer.xrlayer ); + this._session.updateRenderState( { layers: xrlayers } ); + + } else { + + renderTarget.isXRRenderTarget = false; + + } + + return plane; + + } + + /** + * This method can be used in XR applications to create a cylindrical layer that presents a separate + * rendered scene. + * + * @param {number} radius - The radius of the cylinder in world units. + * @param {number} centralAngle - The central angle of the cylinder in radians. + * @param {number} aspectratio - The aspect ratio. + * @param {Vector3} translation - The position/translation of the layer plane in world units. + * @param {Quaternion} quaternion - The orientation of the layer plane expressed as a quaternion. + * @param {number} pixelwidth - The width of the layer's render target in pixels. + * @param {number} pixelheight - The height of the layer's render target in pixels. + * @param {Function} rendercall - A callback function that renders the layer. Similar to code in + * the default animation loop, this method can be used to update/transform 3D object in the layer's scene. + * @param {Object} [attributes={}] - Allows to configure the layer's render target. + * @return {Mesh} A mesh representing the cylindrical XR layer. This mesh should be added to the XR scene. + */ + createCylinderLayer( radius, centralAngle, aspectratio, translation, quaternion, pixelwidth, pixelheight, rendercall, attributes = {} ) { + + const geometry = new CylinderGeometry( radius, radius, radius * centralAngle / aspectratio, 64, 64, true, Math.PI - centralAngle / 2, centralAngle ); + const renderTarget = new XRRenderTarget( + pixelwidth, + pixelheight, + { + format: RGBAFormat, + type: UnsignedByteType, + depthTexture: new DepthTexture( + pixelwidth, + pixelheight, + attributes.stencil ? UnsignedInt248Type : UnsignedIntType, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + attributes.stencil ? DepthStencilFormat : DepthFormat + ), + stencilBuffer: attributes.stencil, + resolveDepthBuffer: false, + resolveStencilBuffer: false + } ); + + renderTarget._autoAllocateDepthBuffer = true; + + const material = new MeshBasicMaterial( { color: 0xffffff, side: BackSide } ); + material.map = renderTarget.texture; + material.map.offset.y = 1; + material.map.repeat.y = -1; + const plane = new Mesh( geometry, material ); + plane.position.copy( translation ); + plane.quaternion.copy( quaternion ); + + const layer = { + type: 'cylinder', + radius: radius, + centralAngle: centralAngle, + aspectratio: aspectratio, + translation: translation, + quaternion: quaternion, + pixelwidth: pixelwidth, + pixelheight: pixelheight, + plane: plane, + material: material, + rendercall: rendercall, + renderTarget: renderTarget }; + + this._layers.push( layer ); + + if ( this._session !== null ) { + + layer.plane.material = new MeshBasicMaterial( { color: 0xffffff, side: BackSide } ); + layer.plane.material.blending = CustomBlending; + layer.plane.material.blendEquation = AddEquation; + layer.plane.material.blendSrc = ZeroFactor; + layer.plane.material.blendDst = ZeroFactor; + + layer.xrlayer = this._createXRLayer( layer ); + + const xrlayers = this._session.renderState.layers; + xrlayers.unshift( layer.xrlayer ); + this._session.updateRenderState( { layers: xrlayers } ); + + } else { + + renderTarget.isXRRenderTarget = false; + + } + + return plane; + + } + + /** + * Renders the XR layers that have been previously added to the scene. + * + * This method is usually called in your animation loop before rendering + * the actual scene via `renderer.render( scene, camera );`. + */ + renderLayers( ) { + + const translationObject = new Vector3(); + const quaternionObject = new Quaternion(); + const renderer = this._renderer; + + const wasPresenting = this.isPresenting; + const rendererOutputTarget = renderer.getOutputRenderTarget(); + const rendererFramebufferTarget = renderer._frameBufferTarget; + this.isPresenting = false; + + const rendererSize = new Vector2(); + renderer.getSize( rendererSize ); + const rendererQuad = renderer._quad; + + for ( const layer of this._layers ) { + + layer.renderTarget.isXRRenderTarget = this._session !== null; + layer.renderTarget._hasExternalTextures = layer.renderTarget.isXRRenderTarget; + + if ( layer.renderTarget.isXRRenderTarget && this._supportsLayers ) { + + layer.xrlayer.transform = new XRRigidTransform( layer.plane.getWorldPosition( translationObject ), layer.plane.getWorldQuaternion( quaternionObject ) ); + + const glSubImage = this._glBinding.getSubImage( layer.xrlayer, this._xrFrame ); + renderer.backend.setXRRenderTargetTextures( + layer.renderTarget, + glSubImage.colorTexture, + undefined ); + + renderer._setXRLayerSize( layer.renderTarget.width, layer.renderTarget.height ); + renderer.setOutputRenderTarget( layer.renderTarget ); + renderer.setRenderTarget( null ); + renderer._frameBufferTarget = null; + + this._frameBufferTargets || ( this._frameBufferTargets = new WeakMap() ); + const { frameBufferTarget, quad } = this._frameBufferTargets.get( layer.renderTarget ) || { frameBufferTarget: null, quad: null }; + if ( ! frameBufferTarget ) { + + renderer._quad = new QuadMesh( new NodeMaterial() ); + this._frameBufferTargets.set( layer.renderTarget, { frameBufferTarget: renderer._getFrameBufferTarget(), quad: renderer._quad } ); + + } else { + + renderer._frameBufferTarget = frameBufferTarget; + renderer._quad = quad; + + } + + layer.rendercall(); + + renderer._frameBufferTarget = null; + + } else { + + renderer.setRenderTarget( layer.renderTarget ); + layer.rendercall(); + + } + + } + + renderer.setRenderTarget( null ); + renderer.setOutputRenderTarget( rendererOutputTarget ); + renderer._frameBufferTarget = rendererFramebufferTarget; + renderer._setXRLayerSize( rendererSize.x, rendererSize.y ); + renderer._quad = rendererQuad; + this.isPresenting = wasPresenting; + + } + + + /** + * Returns the current XR session. + * + * @return {?XRSession} The XR session. Returns `null` when used outside a XR session. + */ + getSession() { + + return this._session; + + } + + /** + * After a XR session has been requested usually with one of the `*Button` modules, it + * is injected into the renderer with this method. This method triggers the start of + * the actual XR rendering. + * + * @async + * @param {XRSession} session - The XR session to set. + * @return {Promise} A Promise that resolves when the session has been set. + */ + async setSession( session ) { + + const renderer = this._renderer; + const backend = renderer.backend; + + this._gl = renderer.getContext(); + const gl = this._gl; + const attributes = gl.getContextAttributes(); + + this._session = session; + + if ( session !== null ) { + + if ( backend.isWebGPUBackend === true ) throw new Error( 'THREE.XRManager: XR is currently not supported with a WebGPU backend. Use WebGL by passing "{ forceWebGL: true }" to the constructor of the renderer.' ); + + session.addEventListener( 'select', this._onSessionEvent ); + session.addEventListener( 'selectstart', this._onSessionEvent ); + session.addEventListener( 'selectend', this._onSessionEvent ); + session.addEventListener( 'squeeze', this._onSessionEvent ); + session.addEventListener( 'squeezestart', this._onSessionEvent ); + session.addEventListener( 'squeezeend', this._onSessionEvent ); + session.addEventListener( 'end', this._onSessionEnd ); + session.addEventListener( 'inputsourceschange', this._onInputSourcesChange ); + + await backend.makeXRCompatible(); + + this._currentPixelRatio = renderer.getPixelRatio(); + renderer.getSize( this._currentSize ); + + this._currentAnimationContext = renderer._animation.getContext(); + this._currentAnimationLoop = renderer._animation.getAnimationLoop(); + renderer._animation.stop(); + + // + + if ( this._useLayers === true ) { + + // default path using XRWebGLBinding/XRProjectionLayer + + let depthFormat = null; + let depthType = null; + let glDepthFormat = null; + + if ( renderer.depth ) { + + glDepthFormat = renderer.stencil ? gl.DEPTH24_STENCIL8 : gl.DEPTH_COMPONENT24; + depthFormat = renderer.stencil ? DepthStencilFormat : DepthFormat; + depthType = renderer.stencil ? UnsignedInt248Type : UnsignedIntType; + + } + + const projectionlayerInit = { + colorFormat: gl.RGBA8, + depthFormat: glDepthFormat, + scaleFactor: this._framebufferScaleFactor, + clearOnAccess: false + }; + + if ( this._useMultiviewIfPossible && renderer.hasFeature( 'OVR_multiview2' ) ) { + + projectionlayerInit.textureType = 'texture-array'; + this._useMultiview = true; + + } + + const glBinding = new XRWebGLBinding( session, gl ); + const glProjLayer = glBinding.createProjectionLayer( projectionlayerInit ); + const layersArray = [ glProjLayer ]; + + this._glBinding = glBinding; + this._glProjLayer = glProjLayer; + + renderer.setPixelRatio( 1 ); + renderer._setXRLayerSize( glProjLayer.textureWidth, glProjLayer.textureHeight ); + + const depth = this._useMultiview ? 2 : 1; + const depthTexture = new DepthTexture( glProjLayer.textureWidth, glProjLayer.textureHeight, depthType, undefined, undefined, undefined, undefined, undefined, undefined, depthFormat, depth ); + + this._xrRenderTarget = new XRRenderTarget( + glProjLayer.textureWidth, + glProjLayer.textureHeight, + { + format: RGBAFormat, + type: UnsignedByteType, + colorSpace: renderer.outputColorSpace, + depthTexture: depthTexture, + stencilBuffer: renderer.stencil, + samples: attributes.antialias ? 4 : 0, + resolveDepthBuffer: ( glProjLayer.ignoreDepthValues === false ), + resolveStencilBuffer: ( glProjLayer.ignoreDepthValues === false ), + depth: this._useMultiview ? 2 : 1, + multiview: this._useMultiview + } ); + + this._xrRenderTarget._hasExternalTextures = true; + this._xrRenderTarget.depth = this._useMultiview ? 2 : 1; + + this._supportsLayers = session.enabledFeatures.includes( 'layers' ); + + this._referenceSpace = await session.requestReferenceSpace( this.getReferenceSpaceType() ); + + if ( this._supportsLayers ) { + + // switch layers to native + for ( const layer of this._layers ) { + + // change material so it "punches" out a hole to show the XR Layer. + layer.plane.material = new MeshBasicMaterial( { color: 0xffffff, side: layer.type === 'cylinder' ? BackSide : FrontSide } ); + layer.plane.material.blending = CustomBlending; + layer.plane.material.blendEquation = AddEquation; + layer.plane.material.blendSrc = ZeroFactor; + layer.plane.material.blendDst = ZeroFactor; + + layer.xrlayer = this._createXRLayer( layer ); + + layersArray.unshift( layer.xrlayer ); + + } + + } + + session.updateRenderState( { layers: layersArray } ); + + } else { + + // fallback to XRWebGLLayer + + const layerInit = { + antialias: renderer.samples > 0, + alpha: true, + depth: renderer.depth, + stencil: renderer.stencil, + framebufferScaleFactor: this.getFramebufferScaleFactor() + }; + + const glBaseLayer = new XRWebGLLayer( session, gl, layerInit ); + this._glBaseLayer = glBaseLayer; + + session.updateRenderState( { baseLayer: glBaseLayer } ); + + renderer.setPixelRatio( 1 ); + renderer._setXRLayerSize( glBaseLayer.framebufferWidth, glBaseLayer.framebufferHeight ); + + this._xrRenderTarget = new XRRenderTarget( + glBaseLayer.framebufferWidth, + glBaseLayer.framebufferHeight, + { + format: RGBAFormat, + type: UnsignedByteType, + colorSpace: renderer.outputColorSpace, + stencilBuffer: renderer.stencil, + resolveDepthBuffer: ( glBaseLayer.ignoreDepthValues === false ), + resolveStencilBuffer: ( glBaseLayer.ignoreDepthValues === false ), + } + ); + + this._xrRenderTarget._isOpaqueFramebuffer = true; + this._referenceSpace = await session.requestReferenceSpace( this.getReferenceSpaceType() ); + + } + + // + + this.setFoveation( this.getFoveation() ); + + renderer._animation.setAnimationLoop( this._onAnimationFrame ); + renderer._animation.setContext( session ); + renderer._animation.start(); + + this.isPresenting = true; + + this.dispatchEvent( { type: 'sessionstart' } ); + + } + + } + + /** + * This method is called by the renderer per frame and updates the XR camera + * and it sub cameras based on the given camera. The given camera is the "user" + * camera created on application level and used for non-XR rendering. + * + * @param {PerspectiveCamera} camera - The camera. + */ + updateCamera( camera ) { + + const session = this._session; + + if ( session === null ) return; + + const depthNear = camera.near; + const depthFar = camera.far; + + const cameraXR = this._cameraXR; + const cameraL = this._cameraL; + const cameraR = this._cameraR; + + cameraXR.near = cameraR.near = cameraL.near = depthNear; + cameraXR.far = cameraR.far = cameraL.far = depthFar; + cameraXR.isMultiViewCamera = this._useMultiview; + + if ( this._currentDepthNear !== cameraXR.near || this._currentDepthFar !== cameraXR.far ) { + + // Note that the new renderState won't apply until the next frame. See #18320 + + session.updateRenderState( { + depthNear: cameraXR.near, + depthFar: cameraXR.far + } ); + + this._currentDepthNear = cameraXR.near; + this._currentDepthFar = cameraXR.far; + + } + + cameraL.layers.mask = camera.layers.mask | 0b010; + cameraR.layers.mask = camera.layers.mask | 0b100; + cameraXR.layers.mask = cameraL.layers.mask | cameraR.layers.mask; + + const parent = camera.parent; + const cameras = cameraXR.cameras; + + updateCamera( cameraXR, parent ); + + for ( let i = 0; i < cameras.length; i ++ ) { + + updateCamera( cameras[ i ], parent ); + + } + + // update projection matrix for proper view frustum culling + + if ( cameras.length === 2 ) { + + setProjectionFromUnion( cameraXR, cameraL, cameraR ); + + } else { + + // assume single camera setup (AR) + + cameraXR.projectionMatrix.copy( cameraL.projectionMatrix ); + + } + + // update user camera and its children + + updateUserCamera( camera, cameraXR, parent ); + + + } + + /** + * Returns a WebXR controller for the given controller index. + * + * @private + * @param {number} index - The controller index. + * @return {WebXRController} The XR controller. + */ + _getController( index ) { + + let controller = this._controllers[ index ]; + + if ( controller === undefined ) { + + controller = new WebXRController(); + this._controllers[ index ] = controller; + + } + + return controller; + + } + +} + +/** + * Assumes 2 cameras that are parallel and share an X-axis, and that + * the cameras' projection and world matrices have already been set. + * And that near and far planes are identical for both cameras. + * Visualization of this technique: https://computergraphics.stackexchange.com/a/4765 + * + * @param {ArrayCamera} camera - The camera to update. + * @param {PerspectiveCamera} cameraL - The left camera. + * @param {PerspectiveCamera} cameraR - The right camera. + */ +function setProjectionFromUnion( camera, cameraL, cameraR ) { + + _cameraLPos.setFromMatrixPosition( cameraL.matrixWorld ); + _cameraRPos.setFromMatrixPosition( cameraR.matrixWorld ); + + const ipd = _cameraLPos.distanceTo( _cameraRPos ); + + const projL = cameraL.projectionMatrix.elements; + const projR = cameraR.projectionMatrix.elements; + + // VR systems will have identical far and near planes, and + // most likely identical top and bottom frustum extents. + // Use the left camera for these values. + const near = projL[ 14 ] / ( projL[ 10 ] - 1 ); + const far = projL[ 14 ] / ( projL[ 10 ] + 1 ); + const topFov = ( projL[ 9 ] + 1 ) / projL[ 5 ]; + const bottomFov = ( projL[ 9 ] - 1 ) / projL[ 5 ]; + + const leftFov = ( projL[ 8 ] - 1 ) / projL[ 0 ]; + const rightFov = ( projR[ 8 ] + 1 ) / projR[ 0 ]; + const left = near * leftFov; + const right = near * rightFov; + + // Calculate the new camera's position offset from the + // left camera. xOffset should be roughly half `ipd`. + const zOffset = ipd / ( - leftFov + rightFov ); + const xOffset = zOffset * - leftFov; + + // TODO: Better way to apply this offset? + cameraL.matrixWorld.decompose( camera.position, camera.quaternion, camera.scale ); + camera.translateX( xOffset ); + camera.translateZ( zOffset ); + camera.matrixWorld.compose( camera.position, camera.quaternion, camera.scale ); + camera.matrixWorldInverse.copy( camera.matrixWorld ).invert(); + + // Check if the projection uses an infinite far plane. + if ( projL[ 10 ] === -1 ) { + + // Use the projection matrix from the left eye. + // The camera offset is sufficient to include the view volumes + // of both eyes (assuming symmetric projections). + camera.projectionMatrix.copy( cameraL.projectionMatrix ); + camera.projectionMatrixInverse.copy( cameraL.projectionMatrixInverse ); + + } else { + + // Find the union of the frustum values of the cameras and scale + // the values so that the near plane's position does not change in world space, + // although must now be relative to the new union camera. + const near2 = near + zOffset; + const far2 = far + zOffset; + const left2 = left - xOffset; + const right2 = right + ( ipd - xOffset ); + const top2 = topFov * far / far2 * near2; + const bottom2 = bottomFov * far / far2 * near2; + + camera.projectionMatrix.makePerspective( left2, right2, top2, bottom2, near2, far2 ); + camera.projectionMatrixInverse.copy( camera.projectionMatrix ).invert(); + + } + +} + +/** + * Updates the world matrices for the given camera based on the parent 3D object. + * + * @inner + * @param {Camera} camera - The camera to update. + * @param {Object3D} parent - The parent 3D object. + */ +function updateCamera( camera, parent ) { + + if ( parent === null ) { + + camera.matrixWorld.copy( camera.matrix ); + + } else { + + camera.matrixWorld.multiplyMatrices( parent.matrixWorld, camera.matrix ); + + } + + camera.matrixWorldInverse.copy( camera.matrixWorld ).invert(); + +} + +/** + * Updates the given camera with the transformation of the XR camera and parent object. + * + * @inner + * @param {Camera} camera - The camera to update. + * @param {ArrayCamera} cameraXR - The XR camera. + * @param {Object3D} parent - The parent 3D object. + */ +function updateUserCamera( camera, cameraXR, parent ) { + + if ( parent === null ) { + + camera.matrix.copy( cameraXR.matrixWorld ); + + } else { + + camera.matrix.copy( parent.matrixWorld ); + camera.matrix.invert(); + camera.matrix.multiply( cameraXR.matrixWorld ); + + } + + camera.matrix.decompose( camera.position, camera.quaternion, camera.scale ); + camera.updateMatrixWorld( true ); + + camera.projectionMatrix.copy( cameraXR.projectionMatrix ); + camera.projectionMatrixInverse.copy( cameraXR.projectionMatrixInverse ); + + if ( camera.isPerspectiveCamera ) { + + camera.fov = RAD2DEG * 2 * Math.atan( 1 / camera.projectionMatrix.elements[ 5 ] ); + camera.zoom = 1; + + } + +} + +function onSessionEvent( event ) { + + const controllerIndex = this._controllerInputSources.indexOf( event.inputSource ); + + if ( controllerIndex === -1 ) { + + return; + + } + + const controller = this._controllers[ controllerIndex ]; + + if ( controller !== undefined ) { + + const referenceSpace = this.getReferenceSpace(); + + controller.update( event.inputSource, event.frame, referenceSpace ); + controller.dispatchEvent( { type: event.type, data: event.inputSource } ); + + } + +} + +function onSessionEnd() { + + const session = this._session; + const renderer = this._renderer; + + session.removeEventListener( 'select', this._onSessionEvent ); + session.removeEventListener( 'selectstart', this._onSessionEvent ); + session.removeEventListener( 'selectend', this._onSessionEvent ); + session.removeEventListener( 'squeeze', this._onSessionEvent ); + session.removeEventListener( 'squeezestart', this._onSessionEvent ); + session.removeEventListener( 'squeezeend', this._onSessionEvent ); + session.removeEventListener( 'end', this._onSessionEnd ); + session.removeEventListener( 'inputsourceschange', this._onInputSourcesChange ); + + for ( let i = 0; i < this._controllers.length; i ++ ) { + + const inputSource = this._controllerInputSources[ i ]; + + if ( inputSource === null ) continue; + + this._controllerInputSources[ i ] = null; + + this._controllers[ i ].disconnect( inputSource ); + + } + + this._currentDepthNear = null; + this._currentDepthFar = null; + + // restore framebuffer/rendering state + + renderer._resetXRState(); + + this._session = null; + this._xrRenderTarget = null; + + // switch layers back to emulated + if ( this._supportsLayers === true ) { + + for ( const layer of this._layers ) { + + // Recreate layer render target to reset state + layer.renderTarget = new XRRenderTarget( + layer.pixelwidth, + layer.pixelheight, + { + format: RGBAFormat, + type: UnsignedByteType, + depthTexture: new DepthTexture( + layer.pixelwidth, + layer.pixelheight, + layer.stencilBuffer ? UnsignedInt248Type : UnsignedIntType, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + layer.stencilBuffer ? DepthStencilFormat : DepthFormat + ), + stencilBuffer: layer.stencilBuffer, + resolveDepthBuffer: false, + resolveStencilBuffer: false + } ); + + layer.renderTarget.isXRRenderTarget = false; + + layer.plane.material = layer.material; + layer.material.map = layer.renderTarget.texture; + layer.material.map.offset.y = 1; + layer.material.map.repeat.y = -1; + delete layer.xrlayer; + + } + + } + + // + + this.isPresenting = false; + this._useMultiview = false; + + renderer._animation.stop(); + renderer._animation.setAnimationLoop( this._currentAnimationLoop ); + renderer._animation.setContext( this._currentAnimationContext ); + renderer._animation.start(); + + renderer.setPixelRatio( this._currentPixelRatio ); + renderer.setSize( this._currentSize.width, this._currentSize.height, false ); + + this.dispatchEvent( { type: 'sessionend' } ); + +} + +function onInputSourcesChange( event ) { + + const controllers = this._controllers; + const controllerInputSources = this._controllerInputSources; + + // Notify disconnected + + for ( let i = 0; i < event.removed.length; i ++ ) { + + const inputSource = event.removed[ i ]; + const index = controllerInputSources.indexOf( inputSource ); + + if ( index >= 0 ) { + + controllerInputSources[ index ] = null; + controllers[ index ].disconnect( inputSource ); + + } + + } + + // Notify connected + + for ( let i = 0; i < event.added.length; i ++ ) { + + const inputSource = event.added[ i ]; + + let controllerIndex = controllerInputSources.indexOf( inputSource ); + + if ( controllerIndex === -1 ) { + + // Assign input source a controller that currently has no input source + + for ( let i = 0; i < controllers.length; i ++ ) { + + if ( i >= controllerInputSources.length ) { + + controllerInputSources.push( inputSource ); + controllerIndex = i; + break; + + } else if ( controllerInputSources[ i ] === null ) { + + controllerInputSources[ i ] = inputSource; + controllerIndex = i; + break; + + } + + } + + // If all controllers do currently receive input we ignore new ones + + if ( controllerIndex === -1 ) break; + + } + + const controller = controllers[ controllerIndex ]; + + if ( controller ) { + + controller.connect( inputSource ); + + } + + } + +} + +// Creation method for native WebXR layers +function createXRLayer( layer ) { + + if ( layer.type === 'quad' ) { + + return this._glBinding.createQuadLayer( { + transform: new XRRigidTransform( layer.translation, layer.quaternion ), + width: layer.width / 2, + height: layer.height / 2, + space: this._referenceSpace, + viewPixelWidth: layer.pixelwidth, + viewPixelHeight: layer.pixelheight, + clearOnAccess: false + } ); + + } else { + + return this._glBinding.createCylinderLayer( { + transform: new XRRigidTransform( layer.translation, layer.quaternion ), + radius: layer.radius, + centralAngle: layer.centralAngle, + aspectRatio: layer.aspectRatio, + space: this._referenceSpace, + viewPixelWidth: layer.pixelwidth, + viewPixelHeight: layer.pixelheight, + clearOnAccess: false + } ); + + } + +} + +// Animation Loop + +function onAnimationFrame( time, frame ) { + + if ( frame === undefined ) return; + + const cameraXR = this._cameraXR; + const renderer = this._renderer; + const backend = renderer.backend; + + const glBaseLayer = this._glBaseLayer; + + const referenceSpace = this.getReferenceSpace(); + const pose = frame.getViewerPose( referenceSpace ); + + this._xrFrame = frame; + + if ( pose !== null ) { + + const views = pose.views; + + if ( this._glBaseLayer !== null ) { + + backend.setXRTarget( glBaseLayer.framebuffer ); + + } + + let cameraXRNeedsUpdate = false; + + // check if it's necessary to rebuild cameraXR's camera list + + if ( views.length !== cameraXR.cameras.length ) { + + cameraXR.cameras.length = 0; + cameraXRNeedsUpdate = true; + + } + + for ( let i = 0; i < views.length; i ++ ) { + + const view = views[ i ]; + + let viewport; + + if ( this._useLayers === true ) { + + const glSubImage = this._glBinding.getViewSubImage( this._glProjLayer, view ); + viewport = glSubImage.viewport; + + // For side-by-side projection, we only produce a single texture for both eyes. + if ( i === 0 ) { + + backend.setXRRenderTargetTextures( + this._xrRenderTarget, + glSubImage.colorTexture, + ( this._glProjLayer.ignoreDepthValues && ! this._useMultiview ) ? undefined : glSubImage.depthStencilTexture + ); + + } + + } else { + + viewport = glBaseLayer.getViewport( view ); + + } + + let camera = this._cameras[ i ]; + + if ( camera === undefined ) { + + camera = new PerspectiveCamera(); + camera.layers.enable( i ); + camera.viewport = new Vector4(); + this._cameras[ i ] = camera; + + } + + camera.matrix.fromArray( view.transform.matrix ); + camera.matrix.decompose( camera.position, camera.quaternion, camera.scale ); + camera.projectionMatrix.fromArray( view.projectionMatrix ); + camera.projectionMatrixInverse.copy( camera.projectionMatrix ).invert(); + camera.viewport.set( viewport.x, viewport.y, viewport.width, viewport.height ); + + if ( i === 0 ) { + + cameraXR.matrix.copy( camera.matrix ); + cameraXR.matrix.decompose( cameraXR.position, cameraXR.quaternion, cameraXR.scale ); + + } + + if ( cameraXRNeedsUpdate === true ) { + + cameraXR.cameras.push( camera ); + + } + + } + + renderer.setOutputRenderTarget( this._xrRenderTarget ); + + } + + // + + for ( let i = 0; i < this._controllers.length; i ++ ) { + + const inputSource = this._controllerInputSources[ i ]; + const controller = this._controllers[ i ]; + + if ( inputSource !== null && controller !== undefined ) { + + controller.update( inputSource, frame, referenceSpace ); + + } + + } + + if ( this._currentAnimationLoop ) this._currentAnimationLoop( time, frame ); + + if ( frame.detectedPlanes ) { + + this.dispatchEvent( { type: 'planesdetected', data: frame } ); + + } + + this._xrFrame = null; + +} + +const _scene = /*@__PURE__*/ new Scene(); +const _drawingBufferSize$1 = /*@__PURE__*/ new Vector2(); +const _screen = /*@__PURE__*/ new Vector4(); +const _frustum = /*@__PURE__*/ new Frustum(); +const _frustumArray = /*@__PURE__*/ new FrustumArray(); + +const _projScreenMatrix = /*@__PURE__*/ new Matrix4(); +const _vector4 = /*@__PURE__*/ new Vector4(); + +/** + * Base class for renderers. + */ +class Renderer { + + /** + * Renderer options. + * + * @typedef {Object} Renderer~Options + * @property {boolean} [logarithmicDepthBuffer=false] - Whether logarithmic depth buffer is enabled or not. + * @property {boolean} [alpha=true] - Whether the default framebuffer (which represents the final contents of the canvas) should be transparent or opaque. + * @property {boolean} [depth=true] - Whether the default framebuffer should have a depth buffer or not. + * @property {boolean} [stencil=false] - Whether the default framebuffer should have a stencil buffer or not. + * @property {boolean} [antialias=false] - Whether MSAA as the default anti-aliasing should be enabled or not. + * @property {number} [samples=0] - When `antialias` is `true`, `4` samples are used by default. This parameter can set to any other integer value than 0 + * to overwrite the default. + * @property {?Function} [getFallback=null] - This callback function can be used to provide a fallback backend, if the primary backend can't be targeted. + * @property {number} [colorBufferType=HalfFloatType] - Defines the type of color buffers. The default `HalfFloatType` is recommend for best + * quality. To save memory and bandwidth, `UnsignedByteType` might be used. This will reduce rendering quality though. + * @property {boolean} [multiview=false] - If set to `true`, the renderer will use multiview during WebXR rendering if supported. + */ + + /** + * Constructs a new renderer. + * + * @param {Backend} backend - The backend the renderer is targeting (e.g. WebGPU or WebGL 2). + * @param {Renderer~Options} [parameters] - The configuration parameter. + + */ + constructor( backend, parameters = {} ) { + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isRenderer = true; + + // + + const { + logarithmicDepthBuffer = false, + alpha = true, + depth = true, + stencil = false, + antialias = false, + samples = 0, + getFallback = null, + colorBufferType = HalfFloatType, + multiview = false + } = parameters; + + /** + * A reference to the canvas element the renderer is drawing to. + * This value of this property will automatically be created by + * the renderer. + * + * @type {HTMLCanvasElement|OffscreenCanvas} + */ + this.domElement = backend.getDomElement(); + + /** + * A reference to the current backend. + * + * @type {Backend} + */ + this.backend = backend; + + /** + * The number of MSAA samples. + * + * @type {number} + * @default 0 + */ + this.samples = samples || ( antialias === true ) ? 4 : 0; + + /** + * Whether the renderer should automatically clear the current rendering target + * before execute a `render()` call. The target can be the canvas (default framebuffer) + * or the current bound render target (custom framebuffer). + * + * @type {boolean} + * @default true + */ + this.autoClear = true; + + /** + * When `autoClear` is set to `true`, this property defines whether the renderer + * should clear the color buffer. + * + * @type {boolean} + * @default true + */ + this.autoClearColor = true; + + /** + * When `autoClear` is set to `true`, this property defines whether the renderer + * should clear the depth buffer. + * + * @type {boolean} + * @default true + */ + this.autoClearDepth = true; + + /** + * When `autoClear` is set to `true`, this property defines whether the renderer + * should clear the stencil buffer. + * + * @type {boolean} + * @default true + */ + this.autoClearStencil = true; + + /** + * Whether the default framebuffer should be transparent or opaque. + * + * @type {boolean} + * @default true + */ + this.alpha = alpha; + + /** + * Whether logarithmic depth buffer is enabled or not. + * + * @type {boolean} + * @default false + */ + this.logarithmicDepthBuffer = logarithmicDepthBuffer; + + /** + * Defines the output color space of the renderer. + * + * @type {string} + * @default SRGBColorSpace + */ + this.outputColorSpace = SRGBColorSpace; + + /** + * Defines the tone mapping of the renderer. + * + * @type {number} + * @default NoToneMapping + */ + this.toneMapping = NoToneMapping; + + /** + * Defines the tone mapping exposure. + * + * @type {number} + * @default 1 + */ + this.toneMappingExposure = 1.0; + + /** + * Whether the renderer should sort its render lists or not. + * + * Note: Sorting is used to attempt to properly render objects that have some degree of transparency. + * By definition, sorting objects may not work in all cases. Depending on the needs of application, + * it may be necessary to turn off sorting and use other methods to deal with transparency rendering + * e.g. manually determining each object's rendering order. + * + * @type {boolean} + * @default true + */ + this.sortObjects = true; + + /** + * Whether the default framebuffer should have a depth buffer or not. + * + * @type {boolean} + * @default true + */ + this.depth = depth; + + /** + * Whether the default framebuffer should have a stencil buffer or not. + * + * @type {boolean} + * @default false + */ + this.stencil = stencil; + + /** + * Holds a series of statistical information about the GPU memory + * and the rendering process. Useful for debugging and monitoring. + * + * @type {Info} + */ + this.info = new Info(); + + /** + * Stores override nodes for specific transformations or calculations. + * These nodes can be used to replace default behavior in the rendering pipeline. + * + * @type {Object} + * @property {?Node} modelViewMatrix - An override node for the model-view matrix. + * @property {?Node} modelNormalViewMatrix - An override node for the model normal view matrix. + */ + this.overrideNodes = { + modelViewMatrix: null, + modelNormalViewMatrix: null + }; + + /** + * The node library defines how certain library objects like materials, lights + * or tone mapping functions are mapped to node types. This is required since + * although instances of classes like `MeshBasicMaterial` or `PointLight` can + * be part of the scene graph, they are internally represented as nodes for + * further processing. + * + * @type {NodeLibrary} + */ + this.library = new NodeLibrary(); + + /** + * A map-like data structure for managing lights. + * + * @type {Lighting} + */ + this.lighting = new Lighting(); + + // internals + + /** + * This callback function can be used to provide a fallback backend, if the primary backend can't be targeted. + * + * @private + * @type {?Function} + */ + this._getFallback = getFallback; + + /** + * The renderer's pixel ratio. + * + * @private + * @type {number} + * @default 1 + */ + this._pixelRatio = 1; + + /** + * The width of the renderer's default framebuffer in logical pixel unit. + * + * @private + * @type {number} + */ + this._width = this.domElement.width; + + /** + * The height of the renderer's default framebuffer in logical pixel unit. + * + * @private + * @type {number} + */ + this._height = this.domElement.height; + + /** + * The viewport of the renderer in logical pixel unit. + * + * @private + * @type {Vector4} + */ + this._viewport = new Vector4( 0, 0, this._width, this._height ); + + /** + * The scissor rectangle of the renderer in logical pixel unit. + * + * @private + * @type {Vector4} + */ + this._scissor = new Vector4( 0, 0, this._width, this._height ); + + /** + * Whether the scissor test should be enabled or not. + * + * @private + * @type {boolean} + */ + this._scissorTest = false; + + /** + * A reference to a renderer module for managing shader attributes. + * + * @private + * @type {?Attributes} + * @default null + */ + this._attributes = null; + + /** + * A reference to a renderer module for managing geometries. + * + * @private + * @type {?Geometries} + * @default null + */ + this._geometries = null; + + /** + * A reference to a renderer module for managing node related logic. + * + * @private + * @type {?Nodes} + * @default null + */ + this._nodes = null; + + /** + * A reference to a renderer module for managing the internal animation loop. + * + * @private + * @type {?Animation} + * @default null + */ + this._animation = null; + + /** + * A reference to a renderer module for managing shader program bindings. + * + * @private + * @type {?Bindings} + * @default null + */ + this._bindings = null; + + /** + * A reference to a renderer module for managing render objects. + * + * @private + * @type {?RenderObjects} + * @default null + */ + this._objects = null; + + /** + * A reference to a renderer module for managing render and compute pipelines. + * + * @private + * @type {?Pipelines} + * @default null + */ + this._pipelines = null; + + /** + * A reference to a renderer module for managing render bundles. + * + * @private + * @type {?RenderBundles} + * @default null + */ + this._bundles = null; + + /** + * A reference to a renderer module for managing render lists. + * + * @private + * @type {?RenderLists} + * @default null + */ + this._renderLists = null; + + /** + * A reference to a renderer module for managing render contexts. + * + * @private + * @type {?RenderContexts} + * @default null + */ + this._renderContexts = null; + + /** + * A reference to a renderer module for managing textures. + * + * @private + * @type {?Textures} + * @default null + */ + this._textures = null; + + /** + * A reference to a renderer module for backgrounds. + * + * @private + * @type {?Background} + * @default null + */ + this._background = null; + + /** + * This fullscreen quad is used for internal render passes + * like the tone mapping and color space output pass. + * + * @private + * @type {QuadMesh} + */ + this._quad = new QuadMesh( new NodeMaterial() ); + this._quad.material.name = 'Renderer_output'; + + /** + * A reference to the current render context. + * + * @private + * @type {?RenderContext} + * @default null + */ + this._currentRenderContext = null; + + /** + * A custom sort function for the opaque render list. + * + * @private + * @type {?Function} + * @default null + */ + this._opaqueSort = null; + + /** + * A custom sort function for the transparent render list. + * + * @private + * @type {?Function} + * @default null + */ + this._transparentSort = null; + + /** + * The framebuffer target. + * + * @private + * @type {?RenderTarget} + * @default null + */ + this._frameBufferTarget = null; + + const alphaClear = this.alpha === true ? 0 : 1; + + /** + * The clear color value. + * + * @private + * @type {Color4} + */ + this._clearColor = new Color4( 0, 0, 0, alphaClear ); + + /** + * The clear depth value. + * + * @private + * @type {number} + * @default 1 + */ + this._clearDepth = 1; + + /** + * The clear stencil value. + * + * @private + * @type {number} + * @default 0 + */ + this._clearStencil = 0; + + /** + * The current render target. + * + * @private + * @type {?RenderTarget} + * @default null + */ + this._renderTarget = null; + + /** + * The active cube face. + * + * @private + * @type {number} + * @default 0 + */ + this._activeCubeFace = 0; + + /** + * The active mipmap level. + * + * @private + * @type {number} + * @default 0 + */ + this._activeMipmapLevel = 0; + + /** + * The current output render target. + * + * @private + * @type {?RenderTarget} + * @default null + */ + this._outputRenderTarget = null; + + /** + * The MRT setting. + * + * @private + * @type {?MRTNode} + * @default null + */ + this._mrt = null; + + /** + * This function defines how a render object is going + * to be rendered. + * + * @private + * @type {?Function} + * @default null + */ + this._renderObjectFunction = null; + + /** + * Used to keep track of the current render object function. + * + * @private + * @type {?Function} + * @default null + */ + this._currentRenderObjectFunction = null; + + /** + * Used to keep track of the current render bundle. + * + * @private + * @type {?RenderBundle} + * @default null + */ + this._currentRenderBundle = null; + + /** + * Next to `_renderObjectFunction()`, this function provides another hook + * for influencing the render process of a render object. It is meant for internal + * use and only relevant for `compileAsync()` right now. Instead of using + * the default logic of `_renderObjectDirect()` which actually draws the render object, + * a different function might be used which performs no draw but just the node + * and pipeline updates. + * + * @private + * @type {?Function} + * @default null + */ + this._handleObjectFunction = this._renderObjectDirect; + + /** + * Indicates whether the device has been lost or not. In WebGL terms, the device + * lost is considered as a context lost. When this is set to `true`, rendering + * isn't possible anymore. + * + * @private + * @type {boolean} + * @default false + */ + this._isDeviceLost = false; + + /** + * A callback function that defines what should happen when a device/context lost occurs. + * + * @type {Function} + */ + this.onDeviceLost = this._onDeviceLost; + + /** + * Defines the type of color buffers. The default `HalfFloatType` is recommend for + * best quality. To save memory and bandwidth, `UnsignedByteType` might be used. + * This will reduce rendering quality though. + * + * @private + * @type {number} + * @default HalfFloatType + */ + this._colorBufferType = colorBufferType; + + /** + * Whether the renderer has been initialized or not. + * + * @private + * @type {boolean} + * @default false + */ + this._initialized = false; + + /** + * A reference to the promise which initializes the renderer. + * + * @private + * @type {?Promise} + * @default null + */ + this._initPromise = null; + + /** + * An array of compilation promises which are used in `compileAsync()`. + * + * @private + * @type {?Array} + * @default null + */ + this._compilationPromises = null; + + /** + * Whether the renderer should render transparent render objects or not. + * + * @type {boolean} + * @default true + */ + this.transparent = true; + + /** + * Whether the renderer should render opaque render objects or not. + * + * @type {boolean} + * @default true + */ + this.opaque = true; + + /** + * Shadow map configuration + * @typedef {Object} ShadowMapConfig + * @property {boolean} enabled - Whether to globally enable shadows or not. + * @property {number} type - The shadow map type. + */ + + /** + * The renderer's shadow configuration. + * + * @type {ShadowMapConfig} + */ + this.shadowMap = { + enabled: false, + type: PCFShadowMap + }; + + /** + * XR configuration. + * @typedef {Object} XRConfig + * @property {boolean} enabled - Whether to globally enable XR or not. + */ + + /** + * The renderer's XR manager. + * + * @type {XRManager} + */ + this.xr = new XRManager( this, multiview ); + + /** + * Debug configuration. + * @typedef {Object} DebugConfig + * @property {boolean} checkShaderErrors - Whether shader errors should be checked or not. + * @property {?Function} onShaderError - A callback function that is executed when a shader error happens. Only supported with WebGL 2 right now. + * @property {Function} getShaderAsync - Allows the get the raw shader code for the given scene, camera and 3D object. + */ + + /** + * The renderer's debug configuration. + * + * @type {DebugConfig} + */ + this.debug = { + checkShaderErrors: true, + onShaderError: null, + getShaderAsync: async ( scene, camera, object ) => { + + await this.compileAsync( scene, camera ); + + const renderList = this._renderLists.get( scene, camera ); + const renderContext = this._renderContexts.get( scene, camera, this._renderTarget ); + + const material = scene.overrideMaterial || object.material; + + const renderObject = this._objects.get( object, material, scene, camera, renderList.lightsNode, renderContext, renderContext.clippingContext ); + + const { fragmentShader, vertexShader } = renderObject.getNodeBuilderState(); + + return { fragmentShader, vertexShader }; + + } + }; + + } + + /** + * Initializes the renderer so it is ready for usage. + * + * @async + * @return {Promise} A Promise that resolves when the renderer has been initialized. + */ + async init() { + + if ( this._initialized ) { + + throw new Error( 'Renderer: Backend has already been initialized.' ); + + } + + if ( this._initPromise !== null ) { + + return this._initPromise; + + } + + this._initPromise = new Promise( async ( resolve, reject ) => { + + let backend = this.backend; + + try { + + await backend.init( this ); + + } catch ( error ) { + + if ( this._getFallback !== null ) { + + // try the fallback + + try { + + this.backend = backend = this._getFallback( error ); + await backend.init( this ); + + } catch ( error ) { + + reject( error ); + return; + + } + + } else { + + reject( error ); + return; + + } + + } + + this._nodes = new Nodes( this, backend ); + this._animation = new Animation( this._nodes, this.info ); + this._attributes = new Attributes( backend ); + this._background = new Background( this, this._nodes ); + this._geometries = new Geometries( this._attributes, this.info ); + this._textures = new Textures( this, backend, this.info ); + this._pipelines = new Pipelines( backend, this._nodes ); + this._bindings = new Bindings( backend, this._nodes, this._textures, this._attributes, this._pipelines, this.info ); + this._objects = new RenderObjects( this, this._nodes, this._geometries, this._pipelines, this._bindings, this.info ); + this._renderLists = new RenderLists( this.lighting ); + this._bundles = new RenderBundles(); + this._renderContexts = new RenderContexts(); + + // + + this._animation.start(); + this._initialized = true; + + resolve( this ); + + } ); + + return this._initPromise; + + } + + /** + * The coordinate system of the renderer. The value of this property + * depends on the selected backend. Either `THREE.WebGLCoordinateSystem` or + * `THREE.WebGPUCoordinateSystem`. + * + * @readonly + * @type {number} + */ + get coordinateSystem() { + + return this.backend.coordinateSystem; + + } + + /** + * Compiles all materials in the given scene. This can be useful to avoid a + * phenomenon which is called "shader compilation stutter", which occurs when + * rendering an object with a new shader for the first time. + * + * If you want to add a 3D object to an existing scene, use the third optional + * parameter for applying the target scene. Note that the (target) scene's lighting + * and environment must be configured before calling this method. + * + * @async + * @param {Object3D} scene - The scene or 3D object to precompile. + * @param {Camera} camera - The camera that is used to render the scene. + * @param {?Scene} targetScene - If the first argument is a 3D object, this parameter must represent the scene the 3D object is going to be added. + * @return {Promise} A Promise that resolves when the compile has been finished. + */ + async compileAsync( scene, camera, targetScene = null ) { + + if ( this._isDeviceLost === true ) return; + + if ( this._initialized === false ) await this.init(); + + // preserve render tree + + const nodeFrame = this._nodes.nodeFrame; + + const previousRenderId = nodeFrame.renderId; + const previousRenderContext = this._currentRenderContext; + const previousRenderObjectFunction = this._currentRenderObjectFunction; + const previousCompilationPromises = this._compilationPromises; + + // + + const sceneRef = ( scene.isScene === true ) ? scene : _scene; + + if ( targetScene === null ) targetScene = scene; + + const renderTarget = this._renderTarget; + const renderContext = this._renderContexts.get( targetScene, camera, renderTarget ); + const activeMipmapLevel = this._activeMipmapLevel; + + const compilationPromises = []; + + this._currentRenderContext = renderContext; + this._currentRenderObjectFunction = this.renderObject; + + this._handleObjectFunction = this._createObjectPipeline; + + this._compilationPromises = compilationPromises; + + nodeFrame.renderId ++; + + // + + nodeFrame.update(); + + // + + renderContext.depth = this.depth; + renderContext.stencil = this.stencil; + + if ( ! renderContext.clippingContext ) renderContext.clippingContext = new ClippingContext(); + renderContext.clippingContext.updateGlobal( sceneRef, camera ); + + // + + sceneRef.onBeforeRender( this, scene, camera, renderTarget ); + + // + + const renderList = this._renderLists.get( scene, camera ); + renderList.begin(); + + this._projectObject( scene, camera, 0, renderList, renderContext.clippingContext ); + + // include lights from target scene + if ( targetScene !== scene ) { + + targetScene.traverseVisible( function ( object ) { + + if ( object.isLight && object.layers.test( camera.layers ) ) { + + renderList.pushLight( object ); + + } + + } ); + + } + + renderList.finish(); + + // + + if ( renderTarget !== null ) { + + this._textures.updateRenderTarget( renderTarget, activeMipmapLevel ); + + const renderTargetData = this._textures.get( renderTarget ); + + renderContext.textures = renderTargetData.textures; + renderContext.depthTexture = renderTargetData.depthTexture; + + } else { + + renderContext.textures = null; + renderContext.depthTexture = null; + + } + + // + + this._background.update( sceneRef, renderList, renderContext ); + + // process render lists + + const opaqueObjects = renderList.opaque; + const transparentObjects = renderList.transparent; + const transparentDoublePassObjects = renderList.transparentDoublePass; + const lightsNode = renderList.lightsNode; + + if ( this.opaque === true && opaqueObjects.length > 0 ) this._renderObjects( opaqueObjects, camera, sceneRef, lightsNode ); + if ( this.transparent === true && transparentObjects.length > 0 ) this._renderTransparents( transparentObjects, transparentDoublePassObjects, camera, sceneRef, lightsNode ); + + // restore render tree + + nodeFrame.renderId = previousRenderId; + + this._currentRenderContext = previousRenderContext; + this._currentRenderObjectFunction = previousRenderObjectFunction; + this._compilationPromises = previousCompilationPromises; + + this._handleObjectFunction = this._renderObjectDirect; + + // wait for all promises setup by backends awaiting compilation/linking/pipeline creation to complete + + await Promise.all( compilationPromises ); + + } + + /** + * Renders the scene in an async fashion. + * + * @async + * @param {Object3D} scene - The scene or 3D object to render. + * @param {Camera} camera - The camera. + * @return {Promise} A Promise that resolves when the render has been finished. + */ + async renderAsync( scene, camera ) { + + if ( this._initialized === false ) await this.init(); + + this._renderScene( scene, camera ); + + } + + /** + * Can be used to synchronize CPU operations with GPU tasks. So when this method is called, + * the CPU waits for the GPU to complete its operation (e.g. a compute task). + * + * @async + * @return {Promise} A Promise that resolves when synchronization has been finished. + */ + async waitForGPU() { + + await this.backend.waitForGPU(); + + } + + /** + * Enables or disables high precision for model-view and normal-view matrices. + * When enabled, will use CPU 64-bit precision for higher precision instead of GPU 32-bit for higher performance. + * + * NOTE: 64-bit precision is not compatible with `InstancedMesh` and `SkinnedMesh`. + * + * @param {boolean} value - Whether to enable or disable high precision. + * @type {boolean} + */ + set highPrecision( value ) { + + if ( value === true ) { + + this.overrideNodes.modelViewMatrix = highpModelViewMatrix; + this.overrideNodes.modelNormalViewMatrix = highpModelNormalViewMatrix; + + } else if ( this.highPrecision ) { + + this.overrideNodes.modelViewMatrix = null; + this.overrideNodes.modelNormalViewMatrix = null; + + } + + } + + /** + * Returns whether high precision is enabled or not. + * + * @return {boolean} Whether high precision is enabled or not. + * @type {boolean} + */ + get highPrecision() { + + return this.overrideNodes.modelViewMatrix === highpModelViewMatrix && this.overrideNodes.modelNormalViewMatrix === highpModelNormalViewMatrix; + + } + + /** + * Sets the given MRT configuration. + * + * @param {MRTNode} mrt - The MRT node to set. + * @return {Renderer} A reference to this renderer. + */ + setMRT( mrt ) { + + this._mrt = mrt; + + return this; + + } + + /** + * Returns the MRT configuration. + * + * @return {MRTNode} The MRT configuration. + */ + getMRT() { + + return this._mrt; + + } + + /** + * Returns the color buffer type. + * + * @return {number} The color buffer type. + */ + getColorBufferType() { + + return this._colorBufferType; + + } + + /** + * Default implementation of the device lost callback. + * + * @private + * @param {Object} info - Information about the context lost. + */ + _onDeviceLost( info ) { + + let errorMessage = `THREE.WebGPURenderer: ${info.api} Device Lost:\n\nMessage: ${info.message}`; + + if ( info.reason ) { + + errorMessage += `\nReason: ${info.reason}`; + + } + + console.error( errorMessage ); + + this._isDeviceLost = true; + + } + + /** + * Renders the given render bundle. + * + * @private + * @param {Object} bundle - Render bundle data. + * @param {Scene} sceneRef - The scene the render bundle belongs to. + * @param {LightsNode} lightsNode - The lights node. + */ + _renderBundle( bundle, sceneRef, lightsNode ) { + + const { bundleGroup, camera, renderList } = bundle; + + const renderContext = this._currentRenderContext; + + // + + const renderBundle = this._bundles.get( bundleGroup, camera ); + const renderBundleData = this.backend.get( renderBundle ); + + if ( renderBundleData.renderContexts === undefined ) renderBundleData.renderContexts = new Set(); + + // + + const needsUpdate = bundleGroup.version !== renderBundleData.version; + const renderBundleNeedsUpdate = renderBundleData.renderContexts.has( renderContext ) === false || needsUpdate; + + renderBundleData.renderContexts.add( renderContext ); + + if ( renderBundleNeedsUpdate ) { + + this.backend.beginBundle( renderContext ); + + if ( renderBundleData.renderObjects === undefined || needsUpdate ) { + + renderBundleData.renderObjects = []; + + } + + this._currentRenderBundle = renderBundle; + + const { + transparentDoublePass: transparentDoublePassObjects, + transparent: transparentObjects, + opaque: opaqueObjects + } = renderList; + + if ( this.opaque === true && opaqueObjects.length > 0 ) this._renderObjects( opaqueObjects, camera, sceneRef, lightsNode ); + if ( this.transparent === true && transparentObjects.length > 0 ) this._renderTransparents( transparentObjects, transparentDoublePassObjects, camera, sceneRef, lightsNode ); + + this._currentRenderBundle = null; + + // + + this.backend.finishBundle( renderContext, renderBundle ); + + renderBundleData.version = bundleGroup.version; + + } else { + + const { renderObjects } = renderBundleData; + + for ( let i = 0, l = renderObjects.length; i < l; i ++ ) { + + const renderObject = renderObjects[ i ]; + + if ( this._nodes.needsRefresh( renderObject ) ) { + + this._nodes.updateBefore( renderObject ); + + this._nodes.updateForRender( renderObject ); + this._bindings.updateForRender( renderObject ); + + this._nodes.updateAfter( renderObject ); + + } + + } + + } + + this.backend.addBundle( renderContext, renderBundle ); + + } + + /** + * Renders the scene or 3D object with the given camera. This method can only be called + * if the renderer has been initialized. + * + * The target of the method is the default framebuffer (meaning the canvas) + * or alternatively a render target when specified via `setRenderTarget()`. + * + * @param {Object3D} scene - The scene or 3D object to render. + * @param {Camera} camera - The camera to render the scene with. + * @return {?Promise} A Promise that resolve when the scene has been rendered. + * Only returned when the renderer has not been initialized. + */ + render( scene, camera ) { + + if ( this._initialized === false ) { + + console.warn( 'THREE.Renderer: .render() called before the backend is initialized. Try using .renderAsync() instead.' ); + + return this.renderAsync( scene, camera ); + + } + + this._renderScene( scene, camera ); + + } + + /** + * Returns an internal render target which is used when computing the output tone mapping + * and color space conversion. Unlike in `WebGLRenderer`, this is done in a separate render + * pass and not inline to achieve more correct results. + * + * @private + * @return {?RenderTarget} The render target. The method returns `null` if no output conversion should be applied. + */ + _getFrameBufferTarget() { + + const { currentToneMapping, currentColorSpace } = this; + + const useToneMapping = currentToneMapping !== NoToneMapping; + const useColorSpace = currentColorSpace !== LinearSRGBColorSpace; + + if ( useToneMapping === false && useColorSpace === false ) return null; + + const { width, height } = this.getDrawingBufferSize( _drawingBufferSize$1 ); + const { depth, stencil } = this; + + let frameBufferTarget = this._frameBufferTarget; + + if ( frameBufferTarget === null ) { + + frameBufferTarget = new RenderTarget( width, height, { + depthBuffer: depth, + stencilBuffer: stencil, + type: this._colorBufferType, + format: RGBAFormat, + colorSpace: LinearSRGBColorSpace, + generateMipmaps: false, + minFilter: LinearFilter, + magFilter: LinearFilter, + samples: this.samples + } ); + + frameBufferTarget.isPostProcessingRenderTarget = true; + + this._frameBufferTarget = frameBufferTarget; + + } + + const outputRenderTarget = this.getOutputRenderTarget(); + + frameBufferTarget.depthBuffer = depth; + frameBufferTarget.stencilBuffer = stencil; + if ( outputRenderTarget !== null ) { + + frameBufferTarget.setSize( outputRenderTarget.width, outputRenderTarget.height, outputRenderTarget.depth ); + + } else { + + frameBufferTarget.setSize( width, height, 1 ); + + } + + frameBufferTarget.viewport.copy( this._viewport ); + frameBufferTarget.scissor.copy( this._scissor ); + frameBufferTarget.viewport.multiplyScalar( this._pixelRatio ); + frameBufferTarget.scissor.multiplyScalar( this._pixelRatio ); + frameBufferTarget.scissorTest = this._scissorTest; + frameBufferTarget.multiview = outputRenderTarget !== null ? outputRenderTarget.multiview : false; + frameBufferTarget.resolveDepthBuffer = outputRenderTarget !== null ? outputRenderTarget.resolveDepthBuffer : true; + frameBufferTarget._autoAllocateDepthBuffer = outputRenderTarget !== null ? outputRenderTarget._autoAllocateDepthBuffer : false; + + return frameBufferTarget; + + } + + /** + * Renders the scene or 3D object with the given camera. + * + * @private + * @param {Object3D} scene - The scene or 3D object to render. + * @param {Camera} camera - The camera to render the scene with. + * @param {boolean} [useFrameBufferTarget=true] - Whether to use a framebuffer target or not. + * @return {RenderContext} The current render context. + */ + _renderScene( scene, camera, useFrameBufferTarget = true ) { + + if ( this._isDeviceLost === true ) return; + + const frameBufferTarget = useFrameBufferTarget ? this._getFrameBufferTarget() : null; + + // preserve render tree + + const nodeFrame = this._nodes.nodeFrame; + + const previousRenderId = nodeFrame.renderId; + const previousRenderContext = this._currentRenderContext; + const previousRenderObjectFunction = this._currentRenderObjectFunction; + + // + + const sceneRef = ( scene.isScene === true ) ? scene : _scene; + + const outputRenderTarget = this._renderTarget || this._outputRenderTarget; + + const activeCubeFace = this._activeCubeFace; + const activeMipmapLevel = this._activeMipmapLevel; + + // + + let renderTarget; + + if ( frameBufferTarget !== null ) { + + renderTarget = frameBufferTarget; + + this.setRenderTarget( renderTarget ); + + } else { + + renderTarget = outputRenderTarget; + + } + + // + + const renderContext = this._renderContexts.get( scene, camera, renderTarget ); + + this._currentRenderContext = renderContext; + this._currentRenderObjectFunction = this._renderObjectFunction || this.renderObject; + + // + + this.info.calls ++; + this.info.render.calls ++; + this.info.render.frameCalls ++; + + nodeFrame.renderId = this.info.calls; + + // + + const coordinateSystem = this.coordinateSystem; + const xr = this.xr; + + if ( camera.coordinateSystem !== coordinateSystem && xr.isPresenting === false ) { + + camera.coordinateSystem = coordinateSystem; + camera.updateProjectionMatrix(); + + if ( camera.isArrayCamera ) { + + for ( const subCamera of camera.cameras ) { + + subCamera.coordinateSystem = coordinateSystem; + subCamera.updateProjectionMatrix(); + + } + + } + + } + + // + + if ( scene.matrixWorldAutoUpdate === true ) scene.updateMatrixWorld(); + + if ( camera.parent === null && camera.matrixWorldAutoUpdate === true ) camera.updateMatrixWorld(); + + if ( xr.enabled === true && xr.isPresenting === true ) { + + if ( xr.cameraAutoUpdate === true ) xr.updateCamera( camera ); + camera = xr.getCamera(); // use XR camera for rendering + + } + + // + + let viewport = this._viewport; + let scissor = this._scissor; + let pixelRatio = this._pixelRatio; + + if ( renderTarget !== null ) { + + viewport = renderTarget.viewport; + scissor = renderTarget.scissor; + pixelRatio = 1; + + } + + this.getDrawingBufferSize( _drawingBufferSize$1 ); + + _screen.set( 0, 0, _drawingBufferSize$1.width, _drawingBufferSize$1.height ); + + const minDepth = ( viewport.minDepth === undefined ) ? 0 : viewport.minDepth; + const maxDepth = ( viewport.maxDepth === undefined ) ? 1 : viewport.maxDepth; + + renderContext.viewportValue.copy( viewport ).multiplyScalar( pixelRatio ).floor(); + renderContext.viewportValue.width >>= activeMipmapLevel; + renderContext.viewportValue.height >>= activeMipmapLevel; + renderContext.viewportValue.minDepth = minDepth; + renderContext.viewportValue.maxDepth = maxDepth; + renderContext.viewport = renderContext.viewportValue.equals( _screen ) === false; + + renderContext.scissorValue.copy( scissor ).multiplyScalar( pixelRatio ).floor(); + renderContext.scissor = this._scissorTest && renderContext.scissorValue.equals( _screen ) === false; + renderContext.scissorValue.width >>= activeMipmapLevel; + renderContext.scissorValue.height >>= activeMipmapLevel; + + if ( ! renderContext.clippingContext ) renderContext.clippingContext = new ClippingContext(); + renderContext.clippingContext.updateGlobal( sceneRef, camera ); + + // + + sceneRef.onBeforeRender( this, scene, camera, renderTarget ); + + // + + const frustum = camera.isArrayCamera ? _frustumArray : _frustum; + + if ( ! camera.isArrayCamera ) { + + _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); + frustum.setFromProjectionMatrix( _projScreenMatrix, coordinateSystem ); + + } + + const renderList = this._renderLists.get( scene, camera ); + renderList.begin(); + + this._projectObject( scene, camera, 0, renderList, renderContext.clippingContext ); + + renderList.finish(); + + if ( this.sortObjects === true ) { + + renderList.sort( this._opaqueSort, this._transparentSort ); + + } + + // + + if ( renderTarget !== null ) { + + this._textures.updateRenderTarget( renderTarget, activeMipmapLevel ); + + const renderTargetData = this._textures.get( renderTarget ); + + renderContext.textures = renderTargetData.textures; + renderContext.depthTexture = renderTargetData.depthTexture; + renderContext.width = renderTargetData.width; + renderContext.height = renderTargetData.height; + renderContext.renderTarget = renderTarget; + renderContext.depth = renderTarget.depthBuffer; + renderContext.stencil = renderTarget.stencilBuffer; + + } else { + + renderContext.textures = null; + renderContext.depthTexture = null; + renderContext.width = this.domElement.width; + renderContext.height = this.domElement.height; + renderContext.depth = this.depth; + renderContext.stencil = this.stencil; + + } + + renderContext.width >>= activeMipmapLevel; + renderContext.height >>= activeMipmapLevel; + renderContext.activeCubeFace = activeCubeFace; + renderContext.activeMipmapLevel = activeMipmapLevel; + renderContext.occlusionQueryCount = renderList.occlusionQueryCount; + + // + + this._background.update( sceneRef, renderList, renderContext ); + + // + + renderContext.camera = camera; + this.backend.beginRender( renderContext ); + + // process render lists + + const { + bundles, + lightsNode, + transparentDoublePass: transparentDoublePassObjects, + transparent: transparentObjects, + opaque: opaqueObjects + } = renderList; + + if ( bundles.length > 0 ) this._renderBundles( bundles, sceneRef, lightsNode ); + if ( this.opaque === true && opaqueObjects.length > 0 ) this._renderObjects( opaqueObjects, camera, sceneRef, lightsNode ); + if ( this.transparent === true && transparentObjects.length > 0 ) this._renderTransparents( transparentObjects, transparentDoublePassObjects, camera, sceneRef, lightsNode ); + + // finish render pass + + this.backend.finishRender( renderContext ); + + // restore render tree + + nodeFrame.renderId = previousRenderId; + + this._currentRenderContext = previousRenderContext; + this._currentRenderObjectFunction = previousRenderObjectFunction; + + // + + if ( frameBufferTarget !== null ) { + + this.setRenderTarget( outputRenderTarget, activeCubeFace, activeMipmapLevel ); + + this._renderOutput( renderTarget ); + + } + + // + + sceneRef.onAfterRender( this, scene, camera, renderTarget ); + + // + + return renderContext; + + } + + _setXRLayerSize( width, height ) { + + this._width = width; + this._height = height; + + this.setViewport( 0, 0, width, height ); + + } + + /** + * The output pass performs tone mapping and color space conversion. + * + * @private + * @param {RenderTarget} renderTarget - The current render target. + */ + _renderOutput( renderTarget ) { + + const quad = this._quad; + + if ( this._nodes.hasOutputChange( renderTarget.texture ) ) { + + quad.material.fragmentNode = this._nodes.getOutputNode( renderTarget.texture ); + quad.material.needsUpdate = true; + + } + + // a clear operation clears the intermediate renderTarget texture, but should not update the screen canvas. + + const currentAutoClear = this.autoClear; + const currentXR = this.xr.enabled; + + this.autoClear = false; + this.xr.enabled = false; + + this._renderScene( quad, quad.camera, false ); + + this.autoClear = currentAutoClear; + this.xr.enabled = currentXR; + + + } + + /** + * Returns the maximum available anisotropy for texture filtering. + * + * @return {number} The maximum available anisotropy. + */ + getMaxAnisotropy() { + + return this.backend.getMaxAnisotropy(); + + } + + /** + * Returns the active cube face. + * + * @return {number} The active cube face. + */ + getActiveCubeFace() { + + return this._activeCubeFace; + + } + + /** + * Returns the active mipmap level. + * + * @return {number} The active mipmap level. + */ + getActiveMipmapLevel() { + + return this._activeMipmapLevel; + + } + + /** + * Applications are advised to always define the animation loop + * with this method and not manually with `requestAnimationFrame()` + * for best compatibility. + * + * @async + * @param {?Function} callback - The application's animation loop. + * @return {Promise} A Promise that resolves when the set has been executed. + */ + async setAnimationLoop( callback ) { + + if ( this._initialized === false ) await this.init(); + + this._animation.setAnimationLoop( callback ); + + } + + /** + * Can be used to transfer buffer data from a storage buffer attribute + * from the GPU to the CPU in context of compute shaders. + * + * @async + * @param {StorageBufferAttribute} attribute - The storage buffer attribute. + * @return {Promise} A promise that resolves with the buffer data when the data are ready. + */ + async getArrayBufferAsync( attribute ) { + + return await this.backend.getArrayBufferAsync( attribute ); + + } + + /** + * Returns the rendering context. + * + * @return {GPUCanvasContext|WebGL2RenderingContext} The rendering context. + */ + getContext() { + + return this.backend.getContext(); + + } + + /** + * Returns the pixel ratio. + * + * @return {number} The pixel ratio. + */ + getPixelRatio() { + + return this._pixelRatio; + + } + + /** + * Returns the drawing buffer size in physical pixels. This method honors the pixel ratio. + * + * @param {Vector2} target - The method writes the result in this target object. + * @return {Vector2} The drawing buffer size. + */ + getDrawingBufferSize( target ) { + + return target.set( this._width * this._pixelRatio, this._height * this._pixelRatio ).floor(); + + } + + /** + * Returns the renderer's size in logical pixels. This method does not honor the pixel ratio. + * + * @param {Vector2} target - The method writes the result in this target object. + * @return {Vector2} The renderer's size in logical pixels. + */ + getSize( target ) { + + return target.set( this._width, this._height ); + + } + + /** + * Sets the given pixel ratio and resizes the canvas if necessary. + * + * @param {number} [value=1] - The pixel ratio. + */ + setPixelRatio( value = 1 ) { + + if ( this._pixelRatio === value ) return; + + this._pixelRatio = value; + + this.setSize( this._width, this._height, false ); + + } + + /** + * This method allows to define the drawing buffer size by specifying + * width, height and pixel ratio all at once. The size of the drawing + * buffer is computed with this formula: + * ```js + * size.x = width * pixelRatio; + * size.y = height * pixelRatio; + * ``` + * + * @param {number} width - The width in logical pixels. + * @param {number} height - The height in logical pixels. + * @param {number} pixelRatio - The pixel ratio. + */ + setDrawingBufferSize( width, height, pixelRatio ) { + + // Renderer can't be resized while presenting in XR. + if ( this.xr && this.xr.isPresenting ) return; + + this._width = width; + this._height = height; + + this._pixelRatio = pixelRatio; + + this.domElement.width = Math.floor( width * pixelRatio ); + this.domElement.height = Math.floor( height * pixelRatio ); + + this.setViewport( 0, 0, width, height ); + + if ( this._initialized ) this.backend.updateSize(); + + } + + /** + * Sets the size of the renderer. + * + * @param {number} width - The width in logical pixels. + * @param {number} height - The height in logical pixels. + * @param {boolean} [updateStyle=true] - Whether to update the `style` attribute of the canvas or not. + */ + setSize( width, height, updateStyle = true ) { + + // Renderer can't be resized while presenting in XR. + if ( this.xr && this.xr.isPresenting ) return; + + this._width = width; + this._height = height; + + this.domElement.width = Math.floor( width * this._pixelRatio ); + this.domElement.height = Math.floor( height * this._pixelRatio ); + + if ( updateStyle === true ) { + + this.domElement.style.width = width + 'px'; + this.domElement.style.height = height + 'px'; + + } + + this.setViewport( 0, 0, width, height ); + + if ( this._initialized ) this.backend.updateSize(); + + } + + /** + * Defines a manual sort function for the opaque render list. + * Pass `null` to use the default sort. + * + * @param {Function} method - The sort function. + */ + setOpaqueSort( method ) { + + this._opaqueSort = method; + + } + + /** + * Defines a manual sort function for the transparent render list. + * Pass `null` to use the default sort. + * + * @param {Function} method - The sort function. + */ + setTransparentSort( method ) { + + this._transparentSort = method; + + } + + /** + * Returns the scissor rectangle. + * + * @param {Vector4} target - The method writes the result in this target object. + * @return {Vector4} The scissor rectangle. + */ + getScissor( target ) { + + const scissor = this._scissor; + + target.x = scissor.x; + target.y = scissor.y; + target.width = scissor.width; + target.height = scissor.height; + + return target; + + } + + /** + * Defines the scissor rectangle. + * + * @param {number | Vector4} x - The horizontal coordinate for the lower left corner of the box in logical pixel unit. + * Instead of passing four arguments, the method also works with a single four-dimensional vector. + * @param {number} y - The vertical coordinate for the lower left corner of the box in logical pixel unit. + * @param {number} width - The width of the scissor box in logical pixel unit. + * @param {number} height - The height of the scissor box in logical pixel unit. + */ + setScissor( x, y, width, height ) { + + const scissor = this._scissor; + + if ( x.isVector4 ) { + + scissor.copy( x ); + + } else { + + scissor.set( x, y, width, height ); + + } + + } + + /** + * Returns the scissor test value. + * + * @return {boolean} Whether the scissor test should be enabled or not. + */ + getScissorTest() { + + return this._scissorTest; + + } + + /** + * Defines the scissor test. + * + * @param {boolean} boolean - Whether the scissor test should be enabled or not. + */ + setScissorTest( boolean ) { + + this._scissorTest = boolean; + + this.backend.setScissorTest( boolean ); + + } + + /** + * Returns the viewport definition. + * + * @param {Vector4} target - The method writes the result in this target object. + * @return {Vector4} The viewport definition. + */ + getViewport( target ) { + + return target.copy( this._viewport ); + + } + + /** + * Defines the viewport. + * + * @param {number | Vector4} x - The horizontal coordinate for the lower left corner of the viewport origin in logical pixel unit. + * @param {number} y - The vertical coordinate for the lower left corner of the viewport origin in logical pixel unit. + * @param {number} width - The width of the viewport in logical pixel unit. + * @param {number} height - The height of the viewport in logical pixel unit. + * @param {number} minDepth - The minimum depth value of the viewport. WebGPU only. + * @param {number} maxDepth - The maximum depth value of the viewport. WebGPU only. + */ + setViewport( x, y, width, height, minDepth = 0, maxDepth = 1 ) { + + const viewport = this._viewport; + + if ( x.isVector4 ) { + + viewport.copy( x ); + + } else { + + viewport.set( x, y, width, height ); + + } + + viewport.minDepth = minDepth; + viewport.maxDepth = maxDepth; + + } + + /** + * Returns the clear color. + * + * @param {Color} target - The method writes the result in this target object. + * @return {Color} The clear color. + */ + getClearColor( target ) { + + return target.copy( this._clearColor ); + + } + + /** + * Defines the clear color and optionally the clear alpha. + * + * @param {Color} color - The clear color. + * @param {number} [alpha=1] - The clear alpha. + */ + setClearColor( color, alpha = 1 ) { + + this._clearColor.set( color ); + this._clearColor.a = alpha; + + } + + /** + * Returns the clear alpha. + * + * @return {number} The clear alpha. + */ + getClearAlpha() { + + return this._clearColor.a; + + } + + /** + * Defines the clear alpha. + * + * @param {number} alpha - The clear alpha. + */ + setClearAlpha( alpha ) { + + this._clearColor.a = alpha; + + } + + /** + * Returns the clear depth. + * + * @return {number} The clear depth. + */ + getClearDepth() { + + return this._clearDepth; + + } + + /** + * Defines the clear depth. + * + * @param {number} depth - The clear depth. + */ + setClearDepth( depth ) { + + this._clearDepth = depth; + + } + + /** + * Returns the clear stencil. + * + * @return {number} The clear stencil. + */ + getClearStencil() { + + return this._clearStencil; + + } + + /** + * Defines the clear stencil. + * + * @param {number} stencil - The clear stencil. + */ + setClearStencil( stencil ) { + + this._clearStencil = stencil; + + } + + /** + * This method performs an occlusion query for the given 3D object. + * It returns `true` if the given 3D object is fully occluded by other + * 3D objects in the scene. + * + * @param {Object3D} object - The 3D object to test. + * @return {boolean} Whether the 3D object is fully occluded or not. + */ + isOccluded( object ) { + + const renderContext = this._currentRenderContext; + + return renderContext && this.backend.isOccluded( renderContext, object ); + + } + + /** + * Performs a manual clear operation. This method ignores `autoClear` properties. + * + * @param {boolean} [color=true] - Whether the color buffer should be cleared or not. + * @param {boolean} [depth=true] - Whether the depth buffer should be cleared or not. + * @param {boolean} [stencil=true] - Whether the stencil buffer should be cleared or not. + * @return {Promise} A Promise that resolves when the clear operation has been executed. + * Only returned when the renderer has not been initialized. + */ + clear( color = true, depth = true, stencil = true ) { + + if ( this._initialized === false ) { + + console.warn( 'THREE.Renderer: .clear() called before the backend is initialized. Try using .clearAsync() instead.' ); + + return this.clearAsync( color, depth, stencil ); + + } + + const renderTarget = this._renderTarget || this._getFrameBufferTarget(); + + let renderContext = null; + + if ( renderTarget !== null ) { + + this._textures.updateRenderTarget( renderTarget ); + + const renderTargetData = this._textures.get( renderTarget ); + + renderContext = this._renderContexts.getForClear( renderTarget ); + renderContext.textures = renderTargetData.textures; + renderContext.depthTexture = renderTargetData.depthTexture; + renderContext.width = renderTargetData.width; + renderContext.height = renderTargetData.height; + renderContext.renderTarget = renderTarget; + renderContext.depth = renderTarget.depthBuffer; + renderContext.stencil = renderTarget.stencilBuffer; + // #30329 + renderContext.clearColorValue = this.backend.getClearColor(); + renderContext.activeCubeFace = this.getActiveCubeFace(); + renderContext.activeMipmapLevel = this.getActiveMipmapLevel(); + + } + + this.backend.clear( color, depth, stencil, renderContext ); + + if ( renderTarget !== null && this._renderTarget === null ) { + + this._renderOutput( renderTarget ); + + } + + } + + /** + * Performs a manual clear operation of the color buffer. This method ignores `autoClear` properties. + * + * @return {Promise} A Promise that resolves when the clear operation has been executed. + * Only returned when the renderer has not been initialized. + */ + clearColor() { + + return this.clear( true, false, false ); + + } + + /** + * Performs a manual clear operation of the depth buffer. This method ignores `autoClear` properties. + * + * @return {Promise} A Promise that resolves when the clear operation has been executed. + * Only returned when the renderer has not been initialized. + */ + clearDepth() { + + return this.clear( false, true, false ); + + } + + /** + * Performs a manual clear operation of the stencil buffer. This method ignores `autoClear` properties. + * + * @return {Promise} A Promise that resolves when the clear operation has been executed. + * Only returned when the renderer has not been initialized. + */ + clearStencil() { + + return this.clear( false, false, true ); + + } + + /** + * Async version of {@link Renderer#clear}. + * + * @async + * @param {boolean} [color=true] - Whether the color buffer should be cleared or not. + * @param {boolean} [depth=true] - Whether the depth buffer should be cleared or not. + * @param {boolean} [stencil=true] - Whether the stencil buffer should be cleared or not. + * @return {Promise} A Promise that resolves when the clear operation has been executed. + */ + async clearAsync( color = true, depth = true, stencil = true ) { + + if ( this._initialized === false ) await this.init(); + + this.clear( color, depth, stencil ); + + } + + /** + * Async version of {@link Renderer#clearColor}. + * + * @async + * @return {Promise} A Promise that resolves when the clear operation has been executed. + */ + async clearColorAsync() { + + this.clearAsync( true, false, false ); + + } + + /** + * Async version of {@link Renderer#clearDepth}. + * + * @async + * @return {Promise} A Promise that resolves when the clear operation has been executed. + */ + async clearDepthAsync() { + + this.clearAsync( false, true, false ); + + } + + /** + * Async version of {@link Renderer#clearStencil}. + * + * @async + * @return {Promise} A Promise that resolves when the clear operation has been executed. + */ + async clearStencilAsync() { + + this.clearAsync( false, false, true ); + + } + + /** + * The current output tone mapping of the renderer. When a render target is set, + * the output tone mapping is always `NoToneMapping`. + * + * @type {number} + */ + get currentToneMapping() { + + return this.isOutputTarget ? this.toneMapping : NoToneMapping; + + } + + /** + * The current output color space of the renderer. When a render target is set, + * the output color space is always `LinearSRGBColorSpace`. + * + * @type {string} + */ + get currentColorSpace() { + + return this.isOutputTarget ? this.outputColorSpace : LinearSRGBColorSpace; + + } + + /** + * Returns `true` if the rendering settings are set to screen output. + * + * @returns {boolean} True if the current render target is the same of output render target or `null`, otherwise false. + */ + get isOutputTarget() { + + return this._renderTarget === this._outputRenderTarget || this._renderTarget === null; + + } + + /** + * Frees all internal resources of the renderer. Call this method if the renderer + * is no longer in use by your app. + */ + dispose() { + + this.info.dispose(); + this.backend.dispose(); + + this._animation.dispose(); + this._objects.dispose(); + this._pipelines.dispose(); + this._nodes.dispose(); + this._bindings.dispose(); + this._renderLists.dispose(); + this._renderContexts.dispose(); + this._textures.dispose(); + + if ( this._frameBufferTarget !== null ) this._frameBufferTarget.dispose(); + + Object.values( this.backend.timestampQueryPool ).forEach( queryPool => { + + if ( queryPool !== null ) queryPool.dispose(); + + } ); + + this.setRenderTarget( null ); + this.setAnimationLoop( null ); + + } + + /** + * Sets the given render target. Calling this method means the renderer does not + * target the default framebuffer (meaning the canvas) anymore but a custom framebuffer. + * Use `null` as the first argument to reset the state. + * + * @param {?RenderTarget} renderTarget - The render target to set. + * @param {number} [activeCubeFace=0] - The active cube face. + * @param {number} [activeMipmapLevel=0] - The active mipmap level. + */ + setRenderTarget( renderTarget, activeCubeFace = 0, activeMipmapLevel = 0 ) { + + this._renderTarget = renderTarget; + this._activeCubeFace = activeCubeFace; + this._activeMipmapLevel = activeMipmapLevel; + + } + + /** + * Returns the current render target. + * + * @return {?RenderTarget} The render target. Returns `null` if no render target is set. + */ + getRenderTarget() { + + return this._renderTarget; + + } + + /** + * Sets the output render target for the renderer. + * + * @param {Object} renderTarget - The render target to set as the output target. + */ + setOutputRenderTarget( renderTarget ) { + + this._outputRenderTarget = renderTarget; + + } + + /** + * Returns the current output target. + * + * @return {?RenderTarget} The current output render target. Returns `null` if no output target is set. + */ + getOutputRenderTarget() { + + return this._outputRenderTarget; + + } + + /** + * Resets the renderer to the initial state before WebXR started. + * + */ + _resetXRState() { + + this.backend.setXRTarget( null ); + this.setOutputRenderTarget( null ); + this.setRenderTarget( null ); + + this._frameBufferTarget.dispose(); + this._frameBufferTarget = null; + + } + + /** + * Callback for {@link Renderer#setRenderObjectFunction}. + * + * @callback renderObjectFunction + * @param {Object3D} object - The 3D object. + * @param {Scene} scene - The scene the 3D object belongs to. + * @param {Camera} camera - The camera the object should be rendered with. + * @param {BufferGeometry} geometry - The object's geometry. + * @param {Material} material - The object's material. + * @param {?Object} group - Only relevant for objects using multiple materials. This represents a group entry from the respective `BufferGeometry`. + * @param {LightsNode} lightsNode - The current lights node. + * @param {ClippingContext} clippingContext - The clipping context. + * @param {?string} [passId=null] - An optional ID for identifying the pass. + */ + + /** + * Sets the given render object function. Calling this method overwrites the default implementation + * which is {@link Renderer#renderObject}. Defining a custom function can be useful + * if you want to modify the way objects are rendered. For example you can define things like "every + * object that has material of a certain type should perform a pre-pass with a special overwrite material". + * The custom function must always call `renderObject()` in its implementation. + * + * Use `null` as the first argument to reset the state. + * + * @param {?renderObjectFunction} renderObjectFunction - The render object function. + */ + setRenderObjectFunction( renderObjectFunction ) { + + this._renderObjectFunction = renderObjectFunction; + + } + + /** + * Returns the current render object function. + * + * @return {?Function} The current render object function. Returns `null` if no function is set. + */ + getRenderObjectFunction() { + + return this._renderObjectFunction; + + } + + /** + * Execute a single or an array of compute nodes. This method can only be called + * if the renderer has been initialized. + * + * @param {Node|Array} computeNodes - The compute node(s). + * @return {Promise|undefined} A Promise that resolve when the compute has finished. Only returned when the renderer has not been initialized. + */ + compute( computeNodes ) { + + if ( this._isDeviceLost === true ) return; + + if ( this._initialized === false ) { + + console.warn( 'THREE.Renderer: .compute() called before the backend is initialized. Try using .computeAsync() instead.' ); + + return this.computeAsync( computeNodes ); + + } + + // + + const nodeFrame = this._nodes.nodeFrame; + + const previousRenderId = nodeFrame.renderId; + + // + + this.info.calls ++; + this.info.compute.calls ++; + this.info.compute.frameCalls ++; + + nodeFrame.renderId = this.info.calls; + + // + + const backend = this.backend; + const pipelines = this._pipelines; + const bindings = this._bindings; + const nodes = this._nodes; + + const computeList = Array.isArray( computeNodes ) ? computeNodes : [ computeNodes ]; + + if ( computeList[ 0 ] === undefined || computeList[ 0 ].isComputeNode !== true ) { + + throw new Error( 'THREE.Renderer: .compute() expects a ComputeNode.' ); + + } + + backend.beginCompute( computeNodes ); + + for ( const computeNode of computeList ) { + + // onInit + + if ( pipelines.has( computeNode ) === false ) { + + const dispose = () => { + + computeNode.removeEventListener( 'dispose', dispose ); + + pipelines.delete( computeNode ); + bindings.delete( computeNode ); + nodes.delete( computeNode ); + + }; + + computeNode.addEventListener( 'dispose', dispose ); + + // + + const onInitFn = computeNode.onInitFunction; + + if ( onInitFn !== null ) { + + onInitFn.call( computeNode, { renderer: this } ); + + } + + } + + nodes.updateForCompute( computeNode ); + bindings.updateForCompute( computeNode ); + + const computeBindings = bindings.getForCompute( computeNode ); + const computePipeline = pipelines.getForCompute( computeNode, computeBindings ); + + backend.compute( computeNodes, computeNode, computeBindings, computePipeline ); + + } + + backend.finishCompute( computeNodes ); + + // + + nodeFrame.renderId = previousRenderId; + + } + + /** + * Execute a single or an array of compute nodes. + * + * @async + * @param {Node|Array} computeNodes - The compute node(s). + * @return {Promise} A Promise that resolve when the compute has finished. + */ + async computeAsync( computeNodes ) { + + if ( this._initialized === false ) await this.init(); + + this.compute( computeNodes ); + + } + + /** + * Checks if the given feature is supported by the selected backend. + * + * @async + * @param {string} name - The feature's name. + * @return {Promise} A Promise that resolves with a bool that indicates whether the feature is supported or not. + */ + async hasFeatureAsync( name ) { + + if ( this._initialized === false ) await this.init(); + + return this.backend.hasFeature( name ); + + } + + async resolveTimestampsAsync( type = 'render' ) { + + if ( this._initialized === false ) await this.init(); + + return this.backend.resolveTimestampsAsync( type ); + + } + + /** + * Checks if the given feature is supported by the selected backend. If the + * renderer has not been initialized, this method always returns `false`. + * + * @param {string} name - The feature's name. + * @return {boolean} Whether the feature is supported or not. + */ + hasFeature( name ) { + + if ( this._initialized === false ) { + + console.warn( 'THREE.Renderer: .hasFeature() called before the backend is initialized. Try using .hasFeatureAsync() instead.' ); + + return false; + + } + + return this.backend.hasFeature( name ); + + } + + /** + * Returns `true` when the renderer has been initialized. + * + * @return {boolean} Whether the renderer has been initialized or not. + */ + hasInitialized() { + + return this._initialized; + + } + + /** + * Initializes the given textures. Useful for preloading a texture rather than waiting until first render + * (which can cause noticeable lags due to decode and GPU upload overhead). + * + * @async + * @param {Texture} texture - The texture. + * @return {Promise} A Promise that resolves when the texture has been initialized. + */ + async initTextureAsync( texture ) { + + if ( this._initialized === false ) await this.init(); + + this._textures.updateTexture( texture ); + + } + + /** + * Initializes the given texture. Useful for preloading a texture rather than waiting until first render + * (which can cause noticeable lags due to decode and GPU upload overhead). + * + * This method can only be used if the renderer has been initialized. + * + * @param {Texture} texture - The texture. + */ + initTexture( texture ) { + + if ( this._initialized === false ) { + + console.warn( 'THREE.Renderer: .initTexture() called before the backend is initialized. Try using .initTextureAsync() instead.' ); + + } + + this._textures.updateTexture( texture ); + + } + + /** + * Copies the current bound framebuffer into the given texture. + * + * @param {FramebufferTexture} framebufferTexture - The texture. + * @param {?Vector2|Vector4} [rectangle=null] - A two or four dimensional vector that defines the rectangular portion of the framebuffer that should be copied. + */ + copyFramebufferToTexture( framebufferTexture, rectangle = null ) { + + if ( rectangle !== null ) { + + if ( rectangle.isVector2 ) { + + rectangle = _vector4.set( rectangle.x, rectangle.y, framebufferTexture.image.width, framebufferTexture.image.height ).floor(); + + } else if ( rectangle.isVector4 ) { + + rectangle = _vector4.copy( rectangle ).floor(); + + } else { + + console.error( 'THREE.Renderer.copyFramebufferToTexture: Invalid rectangle.' ); + + return; + + } + + } else { + + rectangle = _vector4.set( 0, 0, framebufferTexture.image.width, framebufferTexture.image.height ); + + } + + // + + let renderContext = this._currentRenderContext; + let renderTarget; + + if ( renderContext !== null ) { + + renderTarget = renderContext.renderTarget; + + } else { + + renderTarget = this._renderTarget || this._getFrameBufferTarget(); + + if ( renderTarget !== null ) { + + this._textures.updateRenderTarget( renderTarget ); + + renderContext = this._textures.get( renderTarget ); + + } + + } + + // + + this._textures.updateTexture( framebufferTexture, { renderTarget } ); + + this.backend.copyFramebufferToTexture( framebufferTexture, renderContext, rectangle ); + + } + + /** + * Copies data of the given source texture into a destination texture. + * + * @param {Texture} srcTexture - The source texture. + * @param {Texture} dstTexture - The destination texture. + * @param {Box2|Box3} [srcRegion=null] - A bounding box which describes the source region. Can be two or three-dimensional. + * @param {Vector2|Vector3} [dstPosition=null] - A vector that represents the origin of the destination region. Can be two or three-dimensional. + * @param {number} [srcLevel=0] - The source mip level to copy from. + * @param {number} [dstLevel=0] - The destination mip level to copy to. + */ + copyTextureToTexture( srcTexture, dstTexture, srcRegion = null, dstPosition = null, srcLevel = 0, dstLevel = 0 ) { + + this._textures.updateTexture( srcTexture ); + this._textures.updateTexture( dstTexture ); + + this.backend.copyTextureToTexture( srcTexture, dstTexture, srcRegion, dstPosition, srcLevel, dstLevel ); + + } + + /** + * Reads pixel data from the given render target. + * + * @async + * @param {RenderTarget} renderTarget - The render target to read from. + * @param {number} x - The `x` coordinate of the copy region's origin. + * @param {number} y - The `y` coordinate of the copy region's origin. + * @param {number} width - The width of the copy region. + * @param {number} height - The height of the copy region. + * @param {number} [textureIndex=0] - The texture index of a MRT render target. + * @param {number} [faceIndex=0] - The active cube face index. + * @return {Promise} A Promise that resolves when the read has been finished. The resolve provides the read data as a typed array. + */ + async readRenderTargetPixelsAsync( renderTarget, x, y, width, height, textureIndex = 0, faceIndex = 0 ) { + + return this.backend.copyTextureToBuffer( renderTarget.textures[ textureIndex ], x, y, width, height, faceIndex ); + + } + + /** + * Analyzes the given 3D object's hierarchy and builds render lists from the + * processed hierarchy. + * + * @param {Object3D} object - The 3D object to process (usually a scene). + * @param {Camera} camera - The camera the object is rendered with. + * @param {number} groupOrder - The group order is derived from the `renderOrder` of groups and is used to group 3D objects within groups. + * @param {RenderList} renderList - The current render list. + * @param {ClippingContext} clippingContext - The current clipping context. + */ + _projectObject( object, camera, groupOrder, renderList, clippingContext ) { + + if ( object.visible === false ) return; + + const visible = object.layers.test( camera.layers ); + + if ( visible ) { + + if ( object.isGroup ) { + + groupOrder = object.renderOrder; + + if ( object.isClippingGroup && object.enabled ) clippingContext = clippingContext.getGroupContext( object ); + + } else if ( object.isLOD ) { + + if ( object.autoUpdate === true ) object.update( camera ); + + } else if ( object.isLight ) { + + renderList.pushLight( object ); + + } else if ( object.isSprite ) { + + const frustum = camera.isArrayCamera ? _frustumArray : _frustum; + + if ( ! object.frustumCulled || frustum.intersectsSprite( object, camera ) ) { + + if ( this.sortObjects === true ) { + + _vector4.setFromMatrixPosition( object.matrixWorld ).applyMatrix4( _projScreenMatrix ); + + } + + const { geometry, material } = object; + + if ( material.visible ) { + + renderList.push( object, geometry, material, groupOrder, _vector4.z, null, clippingContext ); + + } + + } + + } else if ( object.isLineLoop ) { + + console.error( 'THREE.Renderer: Objects of type THREE.LineLoop are not supported. Please use THREE.Line or THREE.LineSegments.' ); + + } else if ( object.isMesh || object.isLine || object.isPoints ) { + + const frustum = camera.isArrayCamera ? _frustumArray : _frustum; + + if ( ! object.frustumCulled || frustum.intersectsObject( object, camera ) ) { + + const { geometry, material } = object; + + if ( this.sortObjects === true ) { + + if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); + + _vector4 + .copy( geometry.boundingSphere.center ) + .applyMatrix4( object.matrixWorld ) + .applyMatrix4( _projScreenMatrix ); + + } + + if ( Array.isArray( material ) ) { + + const groups = geometry.groups; + + for ( let i = 0, l = groups.length; i < l; i ++ ) { + + const group = groups[ i ]; + const groupMaterial = material[ group.materialIndex ]; + + if ( groupMaterial && groupMaterial.visible ) { + + renderList.push( object, geometry, groupMaterial, groupOrder, _vector4.z, group, clippingContext ); + + } + + } + + } else if ( material.visible ) { + + renderList.push( object, geometry, material, groupOrder, _vector4.z, null, clippingContext ); + + } + + } + + } + + } + + if ( object.isBundleGroup === true && this.backend.beginBundle !== undefined ) { + + const baseRenderList = renderList; + + // replace render list + renderList = this._renderLists.get( object, camera ); + + renderList.begin(); + + baseRenderList.pushBundle( { + bundleGroup: object, + camera, + renderList, + } ); + + renderList.finish(); + + } + + const children = object.children; + + for ( let i = 0, l = children.length; i < l; i ++ ) { + + this._projectObject( children[ i ], camera, groupOrder, renderList, clippingContext ); + + } + + } + + /** + * Renders the given render bundles. + * + * @private + * @param {Array} bundles - Array with render bundle data. + * @param {Scene} sceneRef - The scene the render bundles belong to. + * @param {LightsNode} lightsNode - The current lights node. + */ + _renderBundles( bundles, sceneRef, lightsNode ) { + + for ( const bundle of bundles ) { + + this._renderBundle( bundle, sceneRef, lightsNode ); + + } + + } + + /** + * Renders the transparent objects from the given render lists. + * + * @private + * @param {Array} renderList - The transparent render list. + * @param {Array} doublePassList - The list of transparent objects which require a double pass (e.g. because of transmission). + * @param {Camera} camera - The camera the render list should be rendered with. + * @param {Scene} scene - The scene the render list belongs to. + * @param {LightsNode} lightsNode - The current lights node. + */ + _renderTransparents( renderList, doublePassList, camera, scene, lightsNode ) { + + if ( doublePassList.length > 0 ) { + + // render back side + + for ( const { material } of doublePassList ) { + + material.side = BackSide; + + } + + this._renderObjects( doublePassList, camera, scene, lightsNode, 'backSide' ); + + // render front side + + for ( const { material } of doublePassList ) { + + material.side = FrontSide; + + } + + this._renderObjects( renderList, camera, scene, lightsNode ); + + // restore + + for ( const { material } of doublePassList ) { + + material.side = DoubleSide; + + } + + } else { + + this._renderObjects( renderList, camera, scene, lightsNode ); + + } + + } + + /** + * Renders the objects from the given render list. + * + * @private + * @param {Array} renderList - The render list. + * @param {Camera} camera - The camera the render list should be rendered with. + * @param {Scene} scene - The scene the render list belongs to. + * @param {LightsNode} lightsNode - The current lights node. + * @param {?string} [passId=null] - An optional ID for identifying the pass. + */ + _renderObjects( renderList, camera, scene, lightsNode, passId = null ) { + + for ( let i = 0, il = renderList.length; i < il; i ++ ) { + + const { object, geometry, material, group, clippingContext } = renderList[ i ]; + + this._currentRenderObjectFunction( object, scene, camera, geometry, material, group, lightsNode, clippingContext, passId ); + + } + + } + + /** + * This method represents the default render object function that manages the render lifecycle + * of the object. + * + * @param {Object3D} object - The 3D object. + * @param {Scene} scene - The scene the 3D object belongs to. + * @param {Camera} camera - The camera the object should be rendered with. + * @param {BufferGeometry} geometry - The object's geometry. + * @param {Material} material - The object's material. + * @param {?Object} group - Only relevant for objects using multiple materials. This represents a group entry from the respective `BufferGeometry`. + * @param {LightsNode} lightsNode - The current lights node. + * @param {?ClippingContext} clippingContext - The clipping context. + * @param {?string} [passId=null] - An optional ID for identifying the pass. + */ + renderObject( object, scene, camera, geometry, material, group, lightsNode, clippingContext = null, passId = null ) { + + let overridePositionNode; + let overrideColorNode; + let overrideDepthNode; + + // + + object.onBeforeRender( this, scene, camera, geometry, material, group ); + + // + + if ( material.allowOverride === true && scene.overrideMaterial !== null ) { + + const overrideMaterial = scene.overrideMaterial; + + if ( material.positionNode && material.positionNode.isNode ) { + + overridePositionNode = overrideMaterial.positionNode; + overrideMaterial.positionNode = material.positionNode; + + } + + overrideMaterial.alphaTest = material.alphaTest; + overrideMaterial.alphaMap = material.alphaMap; + overrideMaterial.transparent = material.transparent || material.transmission > 0; + + if ( overrideMaterial.isShadowPassMaterial ) { + + overrideMaterial.side = material.shadowSide === null ? material.side : material.shadowSide; + + if ( material.depthNode && material.depthNode.isNode ) { + + overrideDepthNode = overrideMaterial.depthNode; + overrideMaterial.depthNode = material.depthNode; + + } + + if ( material.castShadowNode && material.castShadowNode.isNode ) { + + overrideColorNode = overrideMaterial.colorNode; + overrideMaterial.colorNode = material.castShadowNode; + + } + + if ( material.castShadowPositionNode && material.castShadowPositionNode.isNode ) { + + overridePositionNode = overrideMaterial.positionNode; + overrideMaterial.positionNode = material.castShadowPositionNode; + + } + + } + + material = overrideMaterial; + + } + + // + + if ( material.transparent === true && material.side === DoubleSide && material.forceSinglePass === false ) { + + material.side = BackSide; + this._handleObjectFunction( object, material, scene, camera, lightsNode, group, clippingContext, 'backSide' ); // create backSide pass id + + material.side = FrontSide; + this._handleObjectFunction( object, material, scene, camera, lightsNode, group, clippingContext, passId ); // use default pass id + + material.side = DoubleSide; + + } else { + + this._handleObjectFunction( object, material, scene, camera, lightsNode, group, clippingContext, passId ); + + } + + // + + if ( overridePositionNode !== undefined ) { + + scene.overrideMaterial.positionNode = overridePositionNode; + + } + + if ( overrideDepthNode !== undefined ) { + + scene.overrideMaterial.depthNode = overrideDepthNode; + + } + + if ( overrideColorNode !== undefined ) { + + scene.overrideMaterial.colorNode = overrideColorNode; + + } + + // + + object.onAfterRender( this, scene, camera, geometry, material, group ); + + } + + /** + * This method represents the default `_handleObjectFunction` implementation which creates + * a render object from the given data and performs the draw command with the selected backend. + * + * @private + * @param {Object3D} object - The 3D object. + * @param {Material} material - The object's material. + * @param {Scene} scene - The scene the 3D object belongs to. + * @param {Camera} camera - The camera the object should be rendered with. + * @param {LightsNode} lightsNode - The current lights node. + * @param {?{start: number, count: number}} group - Only relevant for objects using multiple materials. This represents a group entry from the respective `BufferGeometry`. + * @param {ClippingContext} clippingContext - The clipping context. + * @param {?string} [passId=null] - An optional ID for identifying the pass. + */ + _renderObjectDirect( object, material, scene, camera, lightsNode, group, clippingContext, passId ) { + + const renderObject = this._objects.get( object, material, scene, camera, lightsNode, this._currentRenderContext, clippingContext, passId ); + renderObject.drawRange = object.geometry.drawRange; + renderObject.group = group; + + // + + const needsRefresh = this._nodes.needsRefresh( renderObject ); + + if ( needsRefresh ) { + + this._nodes.updateBefore( renderObject ); + + this._geometries.updateForRender( renderObject ); + + this._nodes.updateForRender( renderObject ); + this._bindings.updateForRender( renderObject ); + + } + + this._pipelines.updateForRender( renderObject ); + + // + + if ( this._currentRenderBundle !== null ) { + + const renderBundleData = this.backend.get( this._currentRenderBundle ); + + renderBundleData.renderObjects.push( renderObject ); + + renderObject.bundle = this._currentRenderBundle.bundleGroup; + + } + + this.backend.draw( renderObject, this.info ); + + if ( needsRefresh ) this._nodes.updateAfter( renderObject ); + + } + + /** + * A different implementation for `_handleObjectFunction` which only makes sure the object is ready for rendering. + * Used in `compileAsync()`. + * + * @private + * @param {Object3D} object - The 3D object. + * @param {Material} material - The object's material. + * @param {Scene} scene - The scene the 3D object belongs to. + * @param {Camera} camera - The camera the object should be rendered with. + * @param {LightsNode} lightsNode - The current lights node. + * @param {?{start: number, count: number}} group - Only relevant for objects using multiple materials. This represents a group entry from the respective `BufferGeometry`. + * @param {ClippingContext} clippingContext - The clipping context. + * @param {?string} [passId=null] - An optional ID for identifying the pass. + */ + _createObjectPipeline( object, material, scene, camera, lightsNode, group, clippingContext, passId ) { + + const renderObject = this._objects.get( object, material, scene, camera, lightsNode, this._currentRenderContext, clippingContext, passId ); + renderObject.drawRange = object.geometry.drawRange; + renderObject.group = group; + + // + + this._nodes.updateBefore( renderObject ); + + this._geometries.updateForRender( renderObject ); + + this._nodes.updateForRender( renderObject ); + this._bindings.updateForRender( renderObject ); + + this._pipelines.getForRender( renderObject, this._compilationPromises ); + + this._nodes.updateAfter( renderObject ); + + } + + /** + * Alias for `compileAsync()`. + * + * @method + * @param {Object3D} scene - The scene or 3D object to precompile. + * @param {Camera} camera - The camera that is used to render the scene. + * @param {Scene} targetScene - If the first argument is a 3D object, this parameter must represent the scene the 3D object is going to be added. + * @return {function(Object3D, Camera, ?Scene): Promise|undefined} A Promise that resolves when the compile has been finished. + */ + get compile() { + + return this.compileAsync; + + } + +} + +/** + * A binding represents the connection between a resource (like a texture, sampler + * or uniform buffer) and the resource definition in a shader stage. + * + * This module is an abstract base class for all concrete bindings types. + * + * @abstract + * @private + */ +class Binding { + + /** + * Constructs a new binding. + * + * @param {string} [name=''] - The binding's name. + */ + constructor( name = '' ) { + + /** + * The binding's name. + * + * @type {string} + */ + this.name = name; + + /** + * A bitmask that defines in what shader stages the + * binding's resource is accessible. + * + * @type {number} + */ + this.visibility = 0; + + } + + /** + * Makes sure binding's resource is visible for the given shader stage. + * + * @param {number} visibility - The shader stage. + */ + setVisibility( visibility ) { + + this.visibility |= visibility; + + } + + /** + * Clones the binding. + * + * @return {Binding} The cloned binding. + */ + clone() { + + return Object.assign( new this.constructor(), this ); + + } + +} + +/** + * This function is usually called with the length in bytes of an array buffer. + * It returns an padded value which ensure chunk size alignment according to STD140 layout. + * + * @function + * @param {number} floatLength - The buffer length. + * @return {number} The padded length. + */ +function getFloatLength( floatLength ) { + + // ensure chunk size alignment (STD140 layout) + + return floatLength + ( ( GPU_CHUNK_BYTES - ( floatLength % GPU_CHUNK_BYTES ) ) % GPU_CHUNK_BYTES ); + +} + +/** + * Represents a buffer binding type. + * + * @private + * @abstract + * @augments Binding + */ +class Buffer extends Binding { + + /** + * Constructs a new buffer. + * + * @param {string} name - The buffer's name. + * @param {TypedArray} [buffer=null] - The buffer. + */ + constructor( name, buffer = null ) { + + super( name ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isBuffer = true; + + /** + * The bytes per element. + * + * @type {number} + */ + this.bytesPerElement = Float32Array.BYTES_PER_ELEMENT; + + /** + * A reference to the internal buffer. + * + * @private + * @type {TypedArray} + */ + this._buffer = buffer; + + } + + /** + * The buffer's byte length. + * + * @type {number} + * @readonly + */ + get byteLength() { + + return getFloatLength( this._buffer.byteLength ); + + } + + /** + * A reference to the internal buffer. + * + * @type {Float32Array} + * @readonly + */ + get buffer() { + + return this._buffer; + + } + + /** + * Updates the binding. + * + * @return {boolean} Whether the buffer has been updated and must be + * uploaded to the GPU. + */ + update() { + + return true; + + } + +} + +/** + * Represents a uniform buffer binding type. + * + * @private + * @augments Buffer + */ +class UniformBuffer extends Buffer { + + /** + * Constructs a new uniform buffer. + * + * @param {string} name - The buffer's name. + * @param {TypedArray} [buffer=null] - The buffer. + */ + constructor( name, buffer = null ) { + + super( name, buffer ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isUniformBuffer = true; + + } + +} + +let _id$4 = 0; + +/** + * A special form of uniform buffer binding type. + * It's buffer value is managed by a node object. + * + * @private + * @augments UniformBuffer + */ +class NodeUniformBuffer extends UniformBuffer { + + /** + * Constructs a new node-based uniform buffer. + * + * @param {BufferNode} nodeUniform - The uniform buffer node. + * @param {UniformGroupNode} groupNode - The uniform group node. + */ + constructor( nodeUniform, groupNode ) { + + super( 'UniformBuffer_' + _id$4 ++, nodeUniform ? nodeUniform.value : null ); + + /** + * The uniform buffer node. + * + * @type {BufferNode} + */ + this.nodeUniform = nodeUniform; + + /** + * The uniform group node. + * + * @type {UniformGroupNode} + */ + this.groupNode = groupNode; + + } + + /** + * The uniform buffer. + * + * @type {Float32Array} + */ + get buffer() { + + return this.nodeUniform.value; + + } + +} + +/** + * This class represents a uniform buffer binding but with + * an API that allows to maintain individual uniform objects. + * + * @private + * @augments UniformBuffer + */ +class UniformsGroup extends UniformBuffer { + + /** + * Constructs a new uniforms group. + * + * @param {string} name - The group's name. + */ + constructor( name ) { + + super( name ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isUniformsGroup = true; + + /** + * An array with the raw uniform values. + * + * @private + * @type {?Array} + * @default null + */ + this._values = null; + + /** + * An array of uniform objects. + * + * The order of uniforms in this array must match the order of uniforms in the shader. + * + * @type {Array} + */ + this.uniforms = []; + + } + + /** + * Adds a uniform to this group. + * + * @param {Uniform} uniform - The uniform to add. + * @return {UniformsGroup} A reference to this group. + */ + addUniform( uniform ) { + + this.uniforms.push( uniform ); + + return this; + + } + + /** + * Removes a uniform from this group. + * + * @param {Uniform} uniform - The uniform to remove. + * @return {UniformsGroup} A reference to this group. + */ + removeUniform( uniform ) { + + const index = this.uniforms.indexOf( uniform ); + + if ( index !== -1 ) { + + this.uniforms.splice( index, 1 ); + + } + + return this; + + } + + /** + * An array with the raw uniform values. + * + * @type {Array} + */ + get values() { + + if ( this._values === null ) { + + this._values = Array.from( this.buffer ); + + } + + return this._values; + + } + + /** + * A Float32 array buffer with the uniform values. + * + * @type {Float32Array} + */ + get buffer() { + + let buffer = this._buffer; + + if ( buffer === null ) { + + const byteLength = this.byteLength; + + buffer = new Float32Array( new ArrayBuffer( byteLength ) ); + + this._buffer = buffer; + + } + + return buffer; + + } + + /** + * The byte length of the buffer with correct buffer alignment. + * + * @type {number} + */ + get byteLength() { + + const bytesPerElement = this.bytesPerElement; + + let offset = 0; // global buffer offset in bytes + + for ( let i = 0, l = this.uniforms.length; i < l; i ++ ) { + + const uniform = this.uniforms[ i ]; + + const boundary = uniform.boundary; + const itemSize = uniform.itemSize * bytesPerElement; // size of the uniform in bytes + + const chunkOffset = offset % GPU_CHUNK_BYTES; // offset in the current chunk + const chunkPadding = chunkOffset % boundary; // required padding to match boundary + const chunkStart = chunkOffset + chunkPadding; // start position in the current chunk for the data + + offset += chunkPadding; + + // Check for chunk overflow + if ( chunkStart !== 0 && ( GPU_CHUNK_BYTES - chunkStart ) < itemSize ) { + + // Add padding to the end of the chunk + offset += ( GPU_CHUNK_BYTES - chunkStart ); + + } + + uniform.offset = offset / bytesPerElement; + + offset += itemSize; + + } + + return Math.ceil( offset / GPU_CHUNK_BYTES ) * GPU_CHUNK_BYTES; + + } + + /** + * Updates this group by updating each uniform object of + * the internal uniform list. The uniform objects check if their + * values has actually changed so this method only returns + * `true` if there is a real value change. + * + * @return {boolean} Whether the uniforms have been updated and + * must be uploaded to the GPU. + */ + update() { + + let updated = false; + + for ( const uniform of this.uniforms ) { + + if ( this.updateByType( uniform ) === true ) { + + updated = true; + + } + + } + + return updated; + + } + + /** + * Updates a given uniform by calling an update method matching + * the uniforms type. + * + * @param {Uniform} uniform - The uniform to update. + * @return {boolean} Whether the uniform has been updated or not. + */ + updateByType( uniform ) { + + if ( uniform.isNumberUniform ) return this.updateNumber( uniform ); + if ( uniform.isVector2Uniform ) return this.updateVector2( uniform ); + if ( uniform.isVector3Uniform ) return this.updateVector3( uniform ); + if ( uniform.isVector4Uniform ) return this.updateVector4( uniform ); + if ( uniform.isColorUniform ) return this.updateColor( uniform ); + if ( uniform.isMatrix3Uniform ) return this.updateMatrix3( uniform ); + if ( uniform.isMatrix4Uniform ) return this.updateMatrix4( uniform ); + + console.error( 'THREE.WebGPUUniformsGroup: Unsupported uniform type.', uniform ); + + } + + /** + * Updates a given Number uniform. + * + * @param {NumberUniform} uniform - The Number uniform. + * @return {boolean} Whether the uniform has been updated or not. + */ + updateNumber( uniform ) { + + let updated = false; + + const a = this.values; + const v = uniform.getValue(); + const offset = uniform.offset; + const type = uniform.getType(); + + if ( a[ offset ] !== v ) { + + const b = this._getBufferForType( type ); + + b[ offset ] = a[ offset ] = v; + updated = true; + + } + + return updated; + + } + + /** + * Updates a given Vector2 uniform. + * + * @param {Vector2Uniform} uniform - The Vector2 uniform. + * @return {boolean} Whether the uniform has been updated or not. + */ + updateVector2( uniform ) { + + let updated = false; + + const a = this.values; + const v = uniform.getValue(); + const offset = uniform.offset; + const type = uniform.getType(); + + if ( a[ offset + 0 ] !== v.x || a[ offset + 1 ] !== v.y ) { + + const b = this._getBufferForType( type ); + + b[ offset + 0 ] = a[ offset + 0 ] = v.x; + b[ offset + 1 ] = a[ offset + 1 ] = v.y; + + updated = true; + + } + + return updated; + + } + + /** + * Updates a given Vector3 uniform. + * + * @param {Vector3Uniform} uniform - The Vector3 uniform. + * @return {boolean} Whether the uniform has been updated or not. + */ + updateVector3( uniform ) { + + let updated = false; + + const a = this.values; + const v = uniform.getValue(); + const offset = uniform.offset; + const type = uniform.getType(); + + if ( a[ offset + 0 ] !== v.x || a[ offset + 1 ] !== v.y || a[ offset + 2 ] !== v.z ) { + + const b = this._getBufferForType( type ); + + b[ offset + 0 ] = a[ offset + 0 ] = v.x; + b[ offset + 1 ] = a[ offset + 1 ] = v.y; + b[ offset + 2 ] = a[ offset + 2 ] = v.z; + + updated = true; + + } + + return updated; + + } + + /** + * Updates a given Vector4 uniform. + * + * @param {Vector4Uniform} uniform - The Vector4 uniform. + * @return {boolean} Whether the uniform has been updated or not. + */ + updateVector4( uniform ) { + + let updated = false; + + const a = this.values; + const v = uniform.getValue(); + const offset = uniform.offset; + const type = uniform.getType(); + + if ( a[ offset + 0 ] !== v.x || a[ offset + 1 ] !== v.y || a[ offset + 2 ] !== v.z || a[ offset + 4 ] !== v.w ) { + + const b = this._getBufferForType( type ); + + b[ offset + 0 ] = a[ offset + 0 ] = v.x; + b[ offset + 1 ] = a[ offset + 1 ] = v.y; + b[ offset + 2 ] = a[ offset + 2 ] = v.z; + b[ offset + 3 ] = a[ offset + 3 ] = v.w; + + updated = true; + + } + + return updated; + + } + + /** + * Updates a given Color uniform. + * + * @param {ColorUniform} uniform - The Color uniform. + * @return {boolean} Whether the uniform has been updated or not. + */ + updateColor( uniform ) { + + let updated = false; + + const a = this.values; + const c = uniform.getValue(); + const offset = uniform.offset; + + if ( a[ offset + 0 ] !== c.r || a[ offset + 1 ] !== c.g || a[ offset + 2 ] !== c.b ) { + + const b = this.buffer; + + b[ offset + 0 ] = a[ offset + 0 ] = c.r; + b[ offset + 1 ] = a[ offset + 1 ] = c.g; + b[ offset + 2 ] = a[ offset + 2 ] = c.b; + + updated = true; + + } + + return updated; + + } + + /** + * Updates a given Matrix3 uniform. + * + * @param {Matrix3Uniform} uniform - The Matrix3 uniform. + * @return {boolean} Whether the uniform has been updated or not. + */ + updateMatrix3( uniform ) { + + let updated = false; + + const a = this.values; + const e = uniform.getValue().elements; + const offset = uniform.offset; + + if ( a[ offset + 0 ] !== e[ 0 ] || a[ offset + 1 ] !== e[ 1 ] || a[ offset + 2 ] !== e[ 2 ] || + a[ offset + 4 ] !== e[ 3 ] || a[ offset + 5 ] !== e[ 4 ] || a[ offset + 6 ] !== e[ 5 ] || + a[ offset + 8 ] !== e[ 6 ] || a[ offset + 9 ] !== e[ 7 ] || a[ offset + 10 ] !== e[ 8 ] ) { + + const b = this.buffer; + + b[ offset + 0 ] = a[ offset + 0 ] = e[ 0 ]; + b[ offset + 1 ] = a[ offset + 1 ] = e[ 1 ]; + b[ offset + 2 ] = a[ offset + 2 ] = e[ 2 ]; + b[ offset + 4 ] = a[ offset + 4 ] = e[ 3 ]; + b[ offset + 5 ] = a[ offset + 5 ] = e[ 4 ]; + b[ offset + 6 ] = a[ offset + 6 ] = e[ 5 ]; + b[ offset + 8 ] = a[ offset + 8 ] = e[ 6 ]; + b[ offset + 9 ] = a[ offset + 9 ] = e[ 7 ]; + b[ offset + 10 ] = a[ offset + 10 ] = e[ 8 ]; + + updated = true; + + } + + return updated; + + } + + /** + * Updates a given Matrix4 uniform. + * + * @param {Matrix4Uniform} uniform - The Matrix4 uniform. + * @return {boolean} Whether the uniform has been updated or not. + */ + updateMatrix4( uniform ) { + + let updated = false; + + const a = this.values; + const e = uniform.getValue().elements; + const offset = uniform.offset; + + if ( arraysEqual( a, e, offset ) === false ) { + + const b = this.buffer; + b.set( e, offset ); + setArray( a, e, offset ); + updated = true; + + } + + return updated; + + } + + /** + * Returns a typed array that matches the given data type. + * + * @param {string} type - The data type. + * @return {TypedArray} The typed array. + */ + _getBufferForType( type ) { + + if ( type === 'int' || type === 'ivec2' || type === 'ivec3' || type === 'ivec4' ) return new Int32Array( this.buffer.buffer ); + if ( type === 'uint' || type === 'uvec2' || type === 'uvec3' || type === 'uvec4' ) return new Uint32Array( this.buffer.buffer ); + return this.buffer; + + } + +} + +/** + * Sets the values of the second array to the first array. + * + * @private + * @param {TypedArray} a - The first array. + * @param {TypedArray} b - The second array. + * @param {number} offset - An index offset for the first array. + */ +function setArray( a, b, offset ) { + + for ( let i = 0, l = b.length; i < l; i ++ ) { + + a[ offset + i ] = b[ i ]; + + } + +} + +/** + * Returns `true` if the given arrays are equal. + * + * @private + * @param {TypedArray} a - The first array. + * @param {TypedArray} b - The second array. + * @param {number} offset - An index offset for the first array. + * @return {boolean} Whether the given arrays are equal or not. + */ +function arraysEqual( a, b, offset ) { + + for ( let i = 0, l = b.length; i < l; i ++ ) { + + if ( a[ offset + i ] !== b[ i ] ) return false; + + } + + return true; + +} + +let _id$3 = 0; + +/** + * A special form of uniforms group that represents + * the individual uniforms as node-based uniforms. + * + * @private + * @augments UniformsGroup + */ +class NodeUniformsGroup extends UniformsGroup { + + /** + * Constructs a new node-based uniforms group. + * + * @param {string} name - The group's name. + * @param {UniformGroupNode} groupNode - The uniform group node. + */ + constructor( name, groupNode ) { + + super( name ); + + /** + * The group's ID. + * + * @type {number} + */ + this.id = _id$3 ++; + + /** + * The uniform group node. + * + * @type {UniformGroupNode} + */ + this.groupNode = groupNode; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isNodeUniformsGroup = true; + + } + +} + +let _id$2 = 0; + +/** + * Represents a sampled texture binding type. + * + * @private + * @augments Binding + */ +class SampledTexture extends Binding { + + /** + * Constructs a new sampled texture. + * + * @param {string} name - The sampled texture's name. + * @param {?Texture} texture - The texture this binding is referring to. + */ + constructor( name, texture ) { + + super( name ); + + /** + * This identifier. + * + * @type {number} + */ + this.id = _id$2 ++; + + /** + * The texture this binding is referring to. + * + * @type {?Texture} + */ + this.texture = texture; + + /** + * The binding's version. + * + * @type {number} + */ + this.version = texture ? texture.version : 0; + + /** + * Whether the texture is a storage texture or not. + * + * @type {boolean} + * @default false + */ + this.store = false; + + /** + * The binding's generation which is an additional version + * qualifier. + * + * @type {?number} + * @default null + */ + this.generation = null; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSampledTexture = true; + + } + + /** + * Returns `true` whether this binding requires an update for the + * given generation. + * + * @param {number} generation - The generation. + * @return {boolean} Whether an update is required or not. + */ + needsBindingsUpdate( generation ) { + + const { texture } = this; + + if ( generation !== this.generation ) { + + this.generation = generation; + + return true; + + } + + return texture.isVideoTexture; + + } + + /** + * Updates the binding. + * + * @return {boolean} Whether the texture has been updated and must be + * uploaded to the GPU. + */ + update() { + + const { texture, version } = this; + + if ( version !== texture.version ) { + + this.version = texture.version; + + return true; + + } + + return false; + + } + +} + +/** + * A special form of sampled texture binding type. + * It's texture value is managed by a node object. + * + * @private + * @augments SampledTexture + */ +class NodeSampledTexture extends SampledTexture { + + /** + * Constructs a new node-based sampled texture. + * + * @param {string} name - The textures's name. + * @param {TextureNode} textureNode - The texture node. + * @param {UniformGroupNode} groupNode - The uniform group node. + * @param {?string} [access=null] - The access type. + */ + constructor( name, textureNode, groupNode, access = null ) { + + super( name, textureNode ? textureNode.value : null ); + + /** + * The texture node. + * + * @type {TextureNode} + */ + this.textureNode = textureNode; + + /** + * The uniform group node. + * + * @type {UniformGroupNode} + */ + this.groupNode = groupNode; + + /** + * The access type. + * + * @type {?string} + * @default null + */ + this.access = access; + + } + + /** + * Overwrites the default to additionally check if the node value has changed. + * + * @param {number} generation - The generation. + * @return {boolean} Whether an update is required or not. + */ + needsBindingsUpdate( generation ) { + + return this.textureNode.value !== this.texture || super.needsBindingsUpdate( generation ); + + } + + /** + * Updates the binding. + * + * @return {boolean} Whether the texture has been updated and must be + * uploaded to the GPU. + */ + update() { + + const { textureNode } = this; + + if ( this.texture !== textureNode.value ) { + + this.texture = textureNode.value; + + return true; + + } + + return super.update(); + + } + +} + +/** + * A special form of sampled cube texture binding type. + * It's texture value is managed by a node object. + * + * @private + * @augments NodeSampledTexture + */ +class NodeSampledCubeTexture extends NodeSampledTexture { + + /** + * Constructs a new node-based sampled cube texture. + * + * @param {string} name - The textures's name. + * @param {TextureNode} textureNode - The texture node. + * @param {UniformGroupNode} groupNode - The uniform group node. + * @param {?string} [access=null] - The access type. + */ + constructor( name, textureNode, groupNode, access = null ) { + + super( name, textureNode, groupNode, access ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSampledCubeTexture = true; + + } + +} + +/** + * A special form of sampled 3D texture binding type. + * It's texture value is managed by a node object. + * + * @private + * @augments NodeSampledTexture + */ +class NodeSampledTexture3D extends NodeSampledTexture { + + /** + * Constructs a new node-based sampled 3D texture. + * + * @param {string} name - The textures's name. + * @param {TextureNode} textureNode - The texture node. + * @param {UniformGroupNode} groupNode - The uniform group node. + * @param {?string} [access=null] - The access type. + */ + constructor( name, textureNode, groupNode, access = null ) { + + super( name, textureNode, groupNode, access ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSampledTexture3D = true; + + } + +} + +const glslMethods = { + textureDimensions: 'textureSize', + equals: 'equal' +}; + +const precisionLib = { + low: 'lowp', + medium: 'mediump', + high: 'highp' +}; + +const supports$1 = { + swizzleAssign: true, + storageBuffer: false +}; + +const interpolationTypeMap = { + perspective: 'smooth', + linear: 'noperspective' +}; + +const interpolationModeMap = { + 'centroid': 'centroid' +}; + +const defaultPrecisions = ` +precision highp float; +precision highp int; +precision highp sampler2D; +precision highp sampler3D; +precision highp samplerCube; +precision highp sampler2DArray; + +precision highp usampler2D; +precision highp usampler3D; +precision highp usamplerCube; +precision highp usampler2DArray; + +precision highp isampler2D; +precision highp isampler3D; +precision highp isamplerCube; +precision highp isampler2DArray; + +precision lowp sampler2DShadow; +precision lowp sampler2DArrayShadow; +precision lowp samplerCubeShadow; +`; + +/** + * A node builder targeting GLSL. + * + * This module generates GLSL shader code from node materials and also + * generates the respective bindings and vertex buffer definitions. These + * data are later used by the renderer to create render and compute pipelines + * for render objects. + * + * @augments NodeBuilder + */ +class GLSLNodeBuilder extends NodeBuilder { + + /** + * Constructs a new GLSL node builder renderer. + * + * @param {Object3D} object - The 3D object. + * @param {Renderer} renderer - The renderer. + */ + constructor( object, renderer ) { + + super( object, renderer, new GLSLNodeParser() ); + + /** + * A dictionary holds for each shader stage ('vertex', 'fragment', 'compute') + * another dictionary which manages UBOs per group ('render','frame','object'). + * + * @type {Object>} + */ + this.uniformGroups = {}; + + /** + * An array that holds objects defining the varying and attribute data in + * context of Transform Feedback. + * + * @type {Array>} + */ + this.transforms = []; + + /** + * A dictionary that holds for each shader stage a Map of used extensions. + * + * @type {Object>} + */ + this.extensions = {}; + + /** + * A dictionary that holds for each shader stage an Array of used builtins. + * + * @type {Object>} + */ + this.builtins = { vertex: [], fragment: [], compute: [] }; + + } + + /** + * Checks if the given texture requires a manual conversion to the working color space. + * + * @param {Texture} texture - The texture to check. + * @return {boolean} Whether the given texture requires a conversion to working color space or not. + */ + needsToWorkingColorSpace( texture ) { + + return texture.isVideoTexture === true && texture.colorSpace !== NoColorSpace; + + } + + /** + * Returns the native shader method name for a given generic name. + * + * @param {string} method - The method name to resolve. + * @return {string} The resolved GLSL method name. + */ + getMethod( method ) { + + return glslMethods[ method ] || method; + + } + + /** + * Returns the output struct name. Not relevant for GLSL. + * + * @return {string} + */ + getOutputStructName() { + + return ''; + + } + + /** + * Builds the given shader node. + * + * @param {ShaderNodeInternal} shaderNode - The shader node. + * @return {string} The GLSL function code. + */ + buildFunctionCode( shaderNode ) { + + const layout = shaderNode.layout; + const flowData = this.flowShaderNode( shaderNode ); + + const parameters = []; + + for ( const input of layout.inputs ) { + + parameters.push( this.getType( input.type ) + ' ' + input.name ); + + } + + // + + const code = `${ this.getType( layout.type ) } ${ layout.name }( ${ parameters.join( ', ' ) } ) { + + ${ flowData.vars } + +${ flowData.code } + return ${ flowData.result }; + +}`; + + // + + return code; + + } + + /** + * Setups the Pixel Buffer Object (PBO) for the given storage + * buffer node. + * + * @param {StorageBufferNode} storageBufferNode - The storage buffer node. + */ + setupPBO( storageBufferNode ) { + + const attribute = storageBufferNode.value; + + if ( attribute.pbo === undefined ) { + + const originalArray = attribute.array; + const numElements = attribute.count * attribute.itemSize; + + const { itemSize } = attribute; + + const isInteger = attribute.array.constructor.name.toLowerCase().includes( 'int' ); + + let format = isInteger ? RedIntegerFormat : RedFormat; + + if ( itemSize === 2 ) { + + format = isInteger ? RGIntegerFormat : RGFormat; + + } else if ( itemSize === 3 ) { + + format = isInteger ? RGBIntegerFormat : RGBFormat; + + } else if ( itemSize === 4 ) { + + format = isInteger ? RGBAIntegerFormat : RGBAFormat; + + } + + const typeMap = { + Float32Array: FloatType, + Uint8Array: UnsignedByteType, + Uint16Array: UnsignedShortType, + Uint32Array: UnsignedIntType, + Int8Array: ByteType, + Int16Array: ShortType, + Int32Array: IntType, + Uint8ClampedArray: UnsignedByteType, + }; + + const width = Math.pow( 2, Math.ceil( Math.log2( Math.sqrt( numElements / itemSize ) ) ) ); + let height = Math.ceil( ( numElements / itemSize ) / width ); + if ( width * height * itemSize < numElements ) height ++; // Ensure enough space + + const newSize = width * height * itemSize; + + const newArray = new originalArray.constructor( newSize ); + + newArray.set( originalArray, 0 ); + + attribute.array = newArray; + + const pboTexture = new DataTexture( attribute.array, width, height, format, typeMap[ attribute.array.constructor.name ] || FloatType ); + pboTexture.needsUpdate = true; + pboTexture.isPBOTexture = true; + + const pbo = new TextureNode( pboTexture, null, null ); + pbo.setPrecision( 'high' ); + + attribute.pboNode = pbo; + attribute.pbo = pbo.value; + + this.getUniformFromNode( attribute.pboNode, 'texture', this.shaderStage, this.context.label ); + + } + + } + + /** + * Returns a GLSL snippet that represents the property name of the given node. + * + * @param {Node} node - The node. + * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for. + * @return {string} The property name. + */ + getPropertyName( node, shaderStage = this.shaderStage ) { + + if ( node.isNodeUniform && node.node.isTextureNode !== true && node.node.isBufferNode !== true ) { + + return shaderStage.charAt( 0 ) + '_' + node.name; + + } + + return super.getPropertyName( node, shaderStage ); + + } + + /** + * Setups the Pixel Buffer Object (PBO) for the given storage + * buffer node. + * + * @param {StorageArrayElementNode} storageArrayElementNode - The storage array element node. + * @return {string} The property name. + */ + generatePBO( storageArrayElementNode ) { + + const { node, indexNode } = storageArrayElementNode; + const attribute = node.value; + + if ( this.renderer.backend.has( attribute ) ) { + + const attributeData = this.renderer.backend.get( attribute ); + attributeData.pbo = attribute.pbo; + + } + + const nodeUniform = this.getUniformFromNode( attribute.pboNode, 'texture', this.shaderStage, this.context.label ); + const textureName = this.getPropertyName( nodeUniform ); + + this.increaseUsage( indexNode ); // force cache generate to be used as index in x,y + const indexSnippet = indexNode.build( this, 'uint' ); + + const elementNodeData = this.getDataFromNode( storageArrayElementNode ); + + let propertyName = elementNodeData.propertyName; + + if ( propertyName === undefined ) { + + // property element + + const nodeVar = this.getVarFromNode( storageArrayElementNode ); + + propertyName = this.getPropertyName( nodeVar ); + + // property size + + const bufferNodeData = this.getDataFromNode( node ); + + let propertySizeName = bufferNodeData.propertySizeName; + + if ( propertySizeName === undefined ) { + + propertySizeName = propertyName + 'Size'; + + this.getVarFromNode( node, propertySizeName, 'uint' ); + + this.addLineFlowCode( `${ propertySizeName } = uint( textureSize( ${ textureName }, 0 ).x )`, storageArrayElementNode ); + + bufferNodeData.propertySizeName = propertySizeName; + + } + + // + + const { itemSize } = attribute; + + const channel = '.' + vectorComponents.join( '' ).slice( 0, itemSize ); + const uvSnippet = `ivec2(${indexSnippet} % ${ propertySizeName }, ${indexSnippet} / ${ propertySizeName })`; + + const snippet = this.generateTextureLoad( null, textureName, uvSnippet, null, '0' ); + + // + + + let prefix = 'vec4'; + + if ( attribute.pbo.type === UnsignedIntType ) { + + prefix = 'uvec4'; + + } else if ( attribute.pbo.type === IntType ) { + + prefix = 'ivec4'; + + } + + this.addLineFlowCode( `${ propertyName } = ${prefix}(${ snippet })${channel}`, storageArrayElementNode ); + + elementNodeData.propertyName = propertyName; + + } + + return propertyName; + + } + + /** + * Generates the GLSL snippet that reads a single texel from a texture without sampling or filtering. + * + * @param {Texture} texture - The texture. + * @param {string} textureProperty - The name of the texture uniform in the shader. + * @param {string} uvIndexSnippet - A GLSL snippet that represents texture coordinates used for sampling. + * @param {?string} depthSnippet - A GLSL snippet that represents the 0-based texture array index to sample. + * @param {string} [levelSnippet='0u'] - A GLSL snippet that represents the mip level, with level 0 containing a full size version of the texture. + * @return {string} The GLSL snippet. + */ + generateTextureLoad( texture, textureProperty, uvIndexSnippet, depthSnippet, levelSnippet = '0' ) { + + if ( depthSnippet ) { + + return `texelFetch( ${ textureProperty }, ivec3( ${ uvIndexSnippet }, ${ depthSnippet } ), ${ levelSnippet } )`; + + } else { + + return `texelFetch( ${ textureProperty }, ${ uvIndexSnippet }, ${ levelSnippet } )`; + + } + + } + + /** + * Generates the GLSL snippet for sampling/loading the given texture. + * + * @param {Texture} texture - The texture. + * @param {string} textureProperty - The name of the texture uniform in the shader. + * @param {string} uvSnippet - A GLSL snippet that represents texture coordinates used for sampling. + * @param {?string} depthSnippet - A GLSL snippet that represents the 0-based texture array index to sample. + * @return {string} The GLSL snippet. + */ + generateTexture( texture, textureProperty, uvSnippet, depthSnippet ) { + + if ( texture.isDepthTexture ) { + + if ( depthSnippet ) uvSnippet = `vec4( ${ uvSnippet }, ${ depthSnippet } )`; + + return `texture( ${ textureProperty }, ${ uvSnippet } ).x`; + + } else { + + if ( depthSnippet ) uvSnippet = `vec3( ${ uvSnippet }, ${ depthSnippet } )`; + + return `texture( ${ textureProperty }, ${ uvSnippet } )`; + + } + + } + + /** + * Generates the GLSL snippet when sampling textures with explicit mip level. + * + * @param {Texture} texture - The texture. + * @param {string} textureProperty - The name of the texture uniform in the shader. + * @param {string} uvSnippet - A GLSL snippet that represents texture coordinates used for sampling. + * @param {string} levelSnippet - A GLSL snippet that represents the mip level, with level 0 containing a full size version of the texture. + * @return {string} The GLSL snippet. + */ + generateTextureLevel( texture, textureProperty, uvSnippet, levelSnippet ) { + + return `textureLod( ${ textureProperty }, ${ uvSnippet }, ${ levelSnippet } )`; + + } + + /** + * Generates the GLSL snippet when sampling textures with a bias to the mip level. + * + * @param {Texture} texture - The texture. + * @param {string} textureProperty - The name of the texture uniform in the shader. + * @param {string} uvSnippet - A GLSL snippet that represents texture coordinates used for sampling. + * @param {string} biasSnippet - A GLSL snippet that represents the bias to apply to the mip level before sampling. + * @return {string} The GLSL snippet. + */ + generateTextureBias( texture, textureProperty, uvSnippet, biasSnippet ) { + + return `texture( ${ textureProperty }, ${ uvSnippet }, ${ biasSnippet } )`; + + } + + /** + * Generates the GLSL snippet for sampling/loading the given texture using explicit gradients. + * + * @param {Texture} texture - The texture. + * @param {string} textureProperty - The name of the texture uniform in the shader. + * @param {string} uvSnippet - A GLSL snippet that represents texture coordinates used for sampling. + * @param {Array} gradSnippet - An array holding both gradient GLSL snippets. + * @return {string} The GLSL snippet. + */ + generateTextureGrad( texture, textureProperty, uvSnippet, gradSnippet ) { + + return `textureGrad( ${ textureProperty }, ${ uvSnippet }, ${ gradSnippet[ 0 ] }, ${ gradSnippet[ 1 ] } )`; + + } + + /** + * Generates the GLSL snippet for sampling a depth texture and comparing the sampled depth values + * against a reference value. + * + * @param {Texture} texture - The texture. + * @param {string} textureProperty - The name of the texture uniform in the shader. + * @param {string} uvSnippet - A GLSL snippet that represents texture coordinates used for sampling. + * @param {string} compareSnippet - A GLSL snippet that represents the reference value. + * @param {?string} depthSnippet - A GLSL snippet that represents 0-based texture array index to sample. + * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for. + * @return {string} The GLSL snippet. + */ + generateTextureCompare( texture, textureProperty, uvSnippet, compareSnippet, depthSnippet, shaderStage = this.shaderStage ) { + + if ( shaderStage === 'fragment' ) { + + if ( depthSnippet ) { + + return `texture( ${ textureProperty }, vec4( ${ uvSnippet }, ${ depthSnippet }, ${ compareSnippet } ) )`; + + } + + return `texture( ${ textureProperty }, vec3( ${ uvSnippet }, ${ compareSnippet } ) )`; + + } else { + + console.error( `WebGPURenderer: THREE.DepthTexture.compareFunction() does not support ${ shaderStage } shader.` ); + + } + + } + + /** + * Returns the variables of the given shader stage as a GLSL string. + * + * @param {string} shaderStage - The shader stage. + * @return {string} The GLSL snippet that defines the variables. + */ + getVars( shaderStage ) { + + const snippets = []; + + const vars = this.vars[ shaderStage ]; + + if ( vars !== undefined ) { + + for ( const variable of vars ) { + + snippets.push( `${ this.getVar( variable.type, variable.name, variable.count ) };` ); + + } + + } + + return snippets.join( '\n\t' ); + + } + + /** + * Returns the uniforms of the given shader stage as a GLSL string. + * + * @param {string} shaderStage - The shader stage. + * @return {string} The GLSL snippet that defines the uniforms. + */ + getUniforms( shaderStage ) { + + const uniforms = this.uniforms[ shaderStage ]; + + const bindingSnippets = []; + const uniformGroups = {}; + + for ( const uniform of uniforms ) { + + let snippet = null; + let group = false; + + if ( uniform.type === 'texture' || uniform.type === 'texture3D' ) { + + const texture = uniform.node.value; + + let typePrefix = ''; + + if ( texture.isDataTexture === true || texture.isData3DTexture === true ) { + + if ( texture.type === UnsignedIntType ) { + + typePrefix = 'u'; + + } else if ( texture.type === IntType ) { + + typePrefix = 'i'; + + } + + } + + if ( uniform.type === 'texture3D' && texture.isArrayTexture === false ) { + + snippet = `${typePrefix}sampler3D ${ uniform.name };`; + + } else if ( texture.compareFunction ) { + + if ( texture.isArrayTexture === true ) { + + snippet = `sampler2DArrayShadow ${ uniform.name };`; + + } else { + + snippet = `sampler2DShadow ${ uniform.name };`; + + } + + } else if ( texture.isArrayTexture === true || texture.isDataArrayTexture === true || texture.isCompressedArrayTexture === true ) { + + snippet = `${typePrefix}sampler2DArray ${ uniform.name };`; + + } else { + + snippet = `${typePrefix}sampler2D ${ uniform.name };`; + + } + + } else if ( uniform.type === 'cubeTexture' ) { + + snippet = `samplerCube ${ uniform.name };`; + + } else if ( uniform.type === 'buffer' ) { + + const bufferNode = uniform.node; + const bufferType = this.getType( bufferNode.bufferType ); + const bufferCount = bufferNode.bufferCount; + + const bufferCountSnippet = bufferCount > 0 ? bufferCount : ''; + snippet = `${bufferNode.name} {\n\t${ bufferType } ${ uniform.name }[${ bufferCountSnippet }];\n};\n`; + + } else { + + const vectorType = this.getVectorType( uniform.type ); + + snippet = `${ vectorType } ${ this.getPropertyName( uniform, shaderStage ) };`; + + group = true; + + } + + const precision = uniform.node.precision; + + if ( precision !== null ) { + + snippet = precisionLib[ precision ] + ' ' + snippet; + + } + + if ( group ) { + + snippet = '\t' + snippet; + + const groupName = uniform.groupNode.name; + const groupSnippets = uniformGroups[ groupName ] || ( uniformGroups[ groupName ] = [] ); + + groupSnippets.push( snippet ); + + } else { + + snippet = 'uniform ' + snippet; + + bindingSnippets.push( snippet ); + + } + + } + + let output = ''; + + for ( const name in uniformGroups ) { + + const groupSnippets = uniformGroups[ name ]; + + output += this._getGLSLUniformStruct( shaderStage + '_' + name, groupSnippets.join( '\n' ) ) + '\n'; + + } + + output += bindingSnippets.join( '\n' ); + + return output; + + } + + /** + * Returns the type for a given buffer attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute. + * @return {string} The type. + */ + getTypeFromAttribute( attribute ) { + + let nodeType = super.getTypeFromAttribute( attribute ); + + if ( /^[iu]/.test( nodeType ) && attribute.gpuType !== IntType ) { + + let dataAttribute = attribute; + + if ( attribute.isInterleavedBufferAttribute ) dataAttribute = attribute.data; + + const array = dataAttribute.array; + + if ( ( array instanceof Uint32Array || array instanceof Int32Array ) === false ) { + + nodeType = nodeType.slice( 1 ); + + } + + } + + return nodeType; + + } + + /** + * Returns the shader attributes of the given shader stage as a GLSL string. + * + * @param {string} shaderStage - The shader stage. + * @return {string} The GLSL snippet that defines the shader attributes. + */ + getAttributes( shaderStage ) { + + let snippet = ''; + + if ( shaderStage === 'vertex' || shaderStage === 'compute' ) { + + const attributes = this.getAttributesArray(); + + let location = 0; + + for ( const attribute of attributes ) { + + snippet += `layout( location = ${ location ++ } ) in ${ attribute.type } ${ attribute.name };\n`; + + } + + } + + return snippet; + + } + + /** + * Returns the members of the given struct type node as a GLSL string. + * + * @param {StructTypeNode} struct - The struct type node. + * @return {string} The GLSL snippet that defines the struct members. + */ + getStructMembers( struct ) { + + const snippets = []; + + for ( const member of struct.members ) { + + snippets.push( `\t${ member.type } ${ member.name };` ); + + } + + return snippets.join( '\n' ); + + } + + /** + * Returns the structs of the given shader stage as a GLSL string. + * + * @param {string} shaderStage - The shader stage. + * @return {string} The GLSL snippet that defines the structs. + */ + getStructs( shaderStage ) { + + const snippets = []; + const structs = this.structs[ shaderStage ]; + + const outputSnippet = []; + + for ( const struct of structs ) { + + if ( struct.output ) { + + for ( const member of struct.members ) { + + outputSnippet.push( `layout( location = ${ member.index } ) out ${ member.type } ${ member.name };` ); + + } + + } else { + + let snippet = 'struct ' + struct.name + ' {\n'; + snippet += this.getStructMembers( struct ); + snippet += '\n};\n'; + + snippets.push( snippet ); + + } + + } + + if ( outputSnippet.length === 0 ) { + + outputSnippet.push( 'layout( location = 0 ) out vec4 fragColor;' ); + + } + + return '\n' + outputSnippet.join( '\n' ) + '\n\n' + snippets.join( '\n' ); + + } + + /** + * Returns the varyings of the given shader stage as a GLSL string. + * + * @param {string} shaderStage - The shader stage. + * @return {string} The GLSL snippet that defines the varyings. + */ + getVaryings( shaderStage ) { + + let snippet = ''; + + const varyings = this.varyings; + + if ( shaderStage === 'vertex' || shaderStage === 'compute' ) { + + for ( const varying of varyings ) { + + if ( shaderStage === 'compute' ) varying.needsInterpolation = true; + + const type = this.getType( varying.type ); + + if ( varying.needsInterpolation ) { + + if ( varying.interpolationType ) { + + const interpolationType = interpolationTypeMap[ varying.interpolationType ] || varying.interpolationType; + const sampling = interpolationModeMap[ varying.interpolationSampling ] || ''; + + snippet += `${ interpolationType } ${ sampling } out ${ type } ${ varying.name };\n`; + + } else { + + const flat = type.includes( 'int' ) || type.includes( 'uv' ) || type.includes( 'iv' ) ? 'flat ' : ''; + + snippet += `${ flat }out ${ type } ${ varying.name };\n`; + + } + + } else { + + snippet += `${type} ${varying.name};\n`; // generate variable (no varying required) + + } + + } + + } else if ( shaderStage === 'fragment' ) { + + for ( const varying of varyings ) { + + if ( varying.needsInterpolation ) { + + const type = this.getType( varying.type ); + + if ( varying.interpolationType ) { + + const interpolationType = interpolationTypeMap[ varying.interpolationType ] || varying.interpolationType; + const sampling = interpolationModeMap[ varying.interpolationSampling ] || ''; + + snippet += `${ interpolationType } ${ sampling } in ${ type } ${ varying.name };\n`; + + + } else { + + const flat = type.includes( 'int' ) || type.includes( 'uv' ) || type.includes( 'iv' ) ? 'flat ' : ''; + + snippet += `${ flat }in ${ type } ${ varying.name };\n`; + + } + + } + + } + + } + + for ( const builtin of this.builtins[ shaderStage ] ) { + + snippet += `${builtin};\n`; + + } + + return snippet; + + } + + /** + * Returns the vertex index builtin. + * + * @return {string} The vertex index. + */ + getVertexIndex() { + + return 'uint( gl_VertexID )'; + + } + + /** + * Returns the instance index builtin. + * + * @return {string} The instance index. + */ + getInstanceIndex() { + + return 'uint( gl_InstanceID )'; + + } + + /** + * Returns the invocation local index builtin. + * + * @return {string} The invocation local index. + */ + getInvocationLocalIndex() { + + const workgroupSize = this.object.workgroupSize; + + const size = workgroupSize.reduce( ( acc, curr ) => acc * curr, 1 ); + + return `uint( gl_InstanceID ) % ${size}u`; + + } + + /** + * Returns the draw index builtin. + * + * @return {?string} The drawIndex shader string. Returns `null` if `WEBGL_multi_draw` isn't supported by the device. + */ + getDrawIndex() { + + const extensions = this.renderer.backend.extensions; + + if ( extensions.has( 'WEBGL_multi_draw' ) ) { + + return 'uint( gl_DrawID )'; + + } + + return null; + + } + + /** + * Returns the front facing builtin. + * + * @return {string} The front facing builtin. + */ + getFrontFacing() { + + return 'gl_FrontFacing'; + + } + + /** + * Returns the frag coord builtin. + * + * @return {string} The frag coord builtin. + */ + getFragCoord() { + + return 'gl_FragCoord.xy'; + + } + + /** + * Returns the frag depth builtin. + * + * @return {string} The frag depth builtin. + */ + getFragDepth() { + + return 'gl_FragDepth'; + + } + + /** + * Enables the given extension. + * + * @param {string} name - The extension name. + * @param {string} behavior - The extension behavior. + * @param {string} [shaderStage=this.shaderStage] - The shader stage. + */ + enableExtension( name, behavior, shaderStage = this.shaderStage ) { + + const map = this.extensions[ shaderStage ] || ( this.extensions[ shaderStage ] = new Map() ); + + if ( map.has( name ) === false ) { + + map.set( name, { + name, + behavior + } ); + + } + + } + + /** + * Returns the enabled extensions of the given shader stage as a GLSL string. + * + * @param {string} shaderStage - The shader stage. + * @return {string} The GLSL snippet that defines the enabled extensions. + */ + getExtensions( shaderStage ) { + + const snippets = []; + + if ( shaderStage === 'vertex' ) { + + const ext = this.renderer.backend.extensions; + const isBatchedMesh = this.object.isBatchedMesh; + + if ( isBatchedMesh && ext.has( 'WEBGL_multi_draw' ) ) { + + this.enableExtension( 'GL_ANGLE_multi_draw', 'require', shaderStage ); + + } + + } + + const extensions = this.extensions[ shaderStage ]; + + if ( extensions !== undefined ) { + + for ( const { name, behavior } of extensions.values() ) { + + snippets.push( `#extension ${name} : ${behavior}` ); + + } + + } + + return snippets.join( '\n' ); + + } + + /** + * Returns the clip distances builtin. + * + * @return {string} The clip distances builtin. + */ + getClipDistance() { + + return 'gl_ClipDistance'; + + } + + /** + * Whether the requested feature is available or not. + * + * @param {string} name - The requested feature. + * @return {boolean} Whether the requested feature is supported or not. + */ + isAvailable( name ) { + + let result = supports$1[ name ]; + + if ( result === undefined ) { + + let extensionName; + + result = false; + + switch ( name ) { + + case 'float32Filterable': + extensionName = 'OES_texture_float_linear'; + break; + + case 'clipDistance': + extensionName = 'WEBGL_clip_cull_distance'; + break; + + } + + if ( extensionName !== undefined ) { + + const extensions = this.renderer.backend.extensions; + + if ( extensions.has( extensionName ) ) { + + extensions.get( extensionName ); + result = true; + + } + + } + + supports$1[ name ] = result; + + } + + return result; + + } + + /** + * Whether to flip texture data along its vertical axis or not. + * + * @return {boolean} Returns always `true` in context of GLSL. + */ + isFlipY() { + + return true; + + } + + /** + * Enables hardware clipping. + * + * @param {string} planeCount - The clipping plane count. + */ + enableHardwareClipping( planeCount ) { + + this.enableExtension( 'GL_ANGLE_clip_cull_distance', 'require' ); + + this.builtins[ 'vertex' ].push( `out float gl_ClipDistance[ ${ planeCount } ]` ); + + } + + /** + * Enables multiview. + */ + enableMultiview() { + + this.enableExtension( 'GL_OVR_multiview2', 'require', 'fragment' ); + this.enableExtension( 'GL_OVR_multiview2', 'require', 'vertex' ); + + this.builtins[ 'vertex' ].push( 'layout(num_views = 2) in' ); + + } + + /** + * Registers a transform in context of Transform Feedback. + * + * @param {string} varyingName - The varying name. + * @param {AttributeNode} attributeNode - The attribute node. + */ + registerTransform( varyingName, attributeNode ) { + + this.transforms.push( { varyingName, attributeNode } ); + + } + + /** + * Returns the transforms of the given shader stage as a GLSL string. + * + * @param {string} shaderStage - The shader stage. + * @return {string} The GLSL snippet that defines the transforms. + */ + getTransforms( /* shaderStage */ ) { + + const transforms = this.transforms; + + let snippet = ''; + + for ( let i = 0; i < transforms.length; i ++ ) { + + const transform = transforms[ i ]; + const attributeName = this.getPropertyName( transform.attributeNode ); + + if ( attributeName ) snippet += `${ transform.varyingName } = ${ attributeName };\n\t`; + + } + + return snippet; + + } + + /** + * Returns a GLSL struct based on the given name and variables. + * + * @private + * @param {string} name - The struct name. + * @param {string} vars - The struct variables. + * @return {string} The GLSL snippet representing a struct. + */ + _getGLSLUniformStruct( name, vars ) { + + return ` +layout( std140 ) uniform ${name} { +${vars} +};`; + + } + + /** + * Returns a GLSL vertex shader based on the given shader data. + * + * @private + * @param {Object} shaderData - The shader data. + * @return {string} The vertex shader. + */ + _getGLSLVertexCode( shaderData ) { + + return `#version 300 es + +${ this.getSignature() } + +// extensions +${shaderData.extensions} + +// precision +${ defaultPrecisions } + +// uniforms +${shaderData.uniforms} + +// varyings +${shaderData.varyings} + +// attributes +${shaderData.attributes} + +// codes +${shaderData.codes} + +void main() { + + // vars + ${shaderData.vars} + + // transforms + ${shaderData.transforms} + + // flow + ${shaderData.flow} + + gl_PointSize = 1.0; + +} +`; + + } + + /** + * Returns a GLSL fragment shader based on the given shader data. + * + * @private + * @param {Object} shaderData - The shader data. + * @return {string} The vertex shader. + */ + _getGLSLFragmentCode( shaderData ) { + + return `#version 300 es + +${ this.getSignature() } + +// extensions +${shaderData.extensions} + +// precision +${ defaultPrecisions } + +// uniforms +${shaderData.uniforms} + +// varyings +${shaderData.varyings} + +// codes +${shaderData.codes} + +// structs +${shaderData.structs} + +void main() { + + // vars + ${shaderData.vars} + + // flow + ${shaderData.flow} + +} +`; + + } + + /** + * Controls the code build of the shader stages. + */ + buildCode() { + + const shadersData = this.material !== null ? { fragment: {}, vertex: {} } : { compute: {} }; + + this.sortBindingGroups(); + + for ( const shaderStage in shadersData ) { + + let flow = '// code\n\n'; + flow += this.flowCode[ shaderStage ]; + + const flowNodes = this.flowNodes[ shaderStage ]; + const mainNode = flowNodes[ flowNodes.length - 1 ]; + + for ( const node of flowNodes ) { + + const flowSlotData = this.getFlowData( node/*, shaderStage*/ ); + const slotName = node.name; + + if ( slotName ) { + + if ( flow.length > 0 ) flow += '\n'; + + flow += `\t// flow -> ${ slotName }\n\t`; + + } + + flow += `${ flowSlotData.code }\n\t`; + + if ( node === mainNode && shaderStage !== 'compute' ) { + + flow += '// result\n\t'; + + if ( shaderStage === 'vertex' ) { + + flow += 'gl_Position = '; + flow += `${ flowSlotData.result };`; + + } else if ( shaderStage === 'fragment' ) { + + if ( ! node.outputNode.isOutputStructNode ) { + + flow += 'fragColor = '; + flow += `${ flowSlotData.result };`; + + } + + } + + } + + } + + const stageData = shadersData[ shaderStage ]; + + stageData.extensions = this.getExtensions( shaderStage ); + stageData.uniforms = this.getUniforms( shaderStage ); + stageData.attributes = this.getAttributes( shaderStage ); + stageData.varyings = this.getVaryings( shaderStage ); + stageData.vars = this.getVars( shaderStage ); + stageData.structs = this.getStructs( shaderStage ); + stageData.codes = this.getCodes( shaderStage ); + stageData.transforms = this.getTransforms( shaderStage ); + stageData.flow = flow; + + } + + if ( this.material !== null ) { + + this.vertexShader = this._getGLSLVertexCode( shadersData.vertex ); + this.fragmentShader = this._getGLSLFragmentCode( shadersData.fragment ); + + } else { + + this.computeShader = this._getGLSLVertexCode( shadersData.compute ); + + } + + } + + /** + * This method is one of the more important ones since it's responsible + * for generating a matching binding instance for the given uniform node. + * + * These bindings are later used in the renderer to create bind groups + * and layouts. + * + * @param {UniformNode} node - The uniform node. + * @param {string} type - The node data type. + * @param {string} shaderStage - The shader stage. + * @param {?string} [name=null] - An optional uniform name. + * @return {NodeUniform} The node uniform object. + */ + getUniformFromNode( node, type, shaderStage, name = null ) { + + const uniformNode = super.getUniformFromNode( node, type, shaderStage, name ); + const nodeData = this.getDataFromNode( node, shaderStage, this.globalCache ); + + let uniformGPU = nodeData.uniformGPU; + + if ( uniformGPU === undefined ) { + + const group = node.groupNode; + const groupName = group.name; + + const bindings = this.getBindGroupArray( groupName, shaderStage ); + + if ( type === 'texture' ) { + + uniformGPU = new NodeSampledTexture( uniformNode.name, uniformNode.node, group ); + bindings.push( uniformGPU ); + + } else if ( type === 'cubeTexture' ) { + + uniformGPU = new NodeSampledCubeTexture( uniformNode.name, uniformNode.node, group ); + bindings.push( uniformGPU ); + + } else if ( type === 'texture3D' ) { + + uniformGPU = new NodeSampledTexture3D( uniformNode.name, uniformNode.node, group ); + bindings.push( uniformGPU ); + + } else if ( type === 'buffer' ) { + + node.name = `NodeBuffer_${ node.id }`; + uniformNode.name = `buffer${ node.id }`; + + const buffer = new NodeUniformBuffer( node, group ); + buffer.name = node.name; + + bindings.push( buffer ); + + uniformGPU = buffer; + + } else { + + const uniformsStage = this.uniformGroups[ shaderStage ] || ( this.uniformGroups[ shaderStage ] = {} ); + + let uniformsGroup = uniformsStage[ groupName ]; + + if ( uniformsGroup === undefined ) { + + uniformsGroup = new NodeUniformsGroup( shaderStage + '_' + groupName, group ); + //uniformsGroup.setVisibility( gpuShaderStageLib[ shaderStage ] ); + + uniformsStage[ groupName ] = uniformsGroup; + + bindings.push( uniformsGroup ); + + } + + uniformGPU = this.getNodeUniform( uniformNode, type ); + + uniformsGroup.addUniform( uniformGPU ); + + } + + nodeData.uniformGPU = uniformGPU; + + } + + return uniformNode; + + } + +} + +let _vector2 = null; +let _color4 = null; + +/** + * Most of the rendering related logic is implemented in the + * {@link Renderer} module and related management components. + * Sometimes it is required though to execute commands which are + * specific to the current 3D backend (which is WebGPU or WebGL 2). + * This abstract base class defines an interface that encapsulates + * all backend-related logic. Derived classes for each backend must + * implement the interface. + * + * @abstract + * @private + */ +class Backend { + + /** + * Constructs a new backend. + * + * @param {Object} parameters - An object holding parameters for the backend. + */ + constructor( parameters = {} ) { + + /** + * The parameters of the backend. + * + * @type {Object} + */ + this.parameters = Object.assign( {}, parameters ); + + /** + * This weak map holds backend-specific data of objects + * like textures, attributes or render targets. + * + * @type {WeakMap} + */ + this.data = new WeakMap(); + + /** + * A reference to the renderer. + * + * @type {?Renderer} + * @default null + */ + this.renderer = null; + + /** + * A reference to the canvas element the renderer is drawing to. + * + * @type {?(HTMLCanvasElement|OffscreenCanvas)} + * @default null + */ + this.domElement = null; + + /** + * A reference to the timestamp query pool. + * + * @type {{render: ?TimestampQueryPool, compute: ?TimestampQueryPool}} + */ + this.timestampQueryPool = { + 'render': null, + 'compute': null + }; + + /** + * Whether to track timestamps with a Timestamp Query API or not. + * + * @type {boolean} + * @default false + */ + this.trackTimestamp = ( parameters.trackTimestamp === true ); + + } + + /** + * Initializes the backend so it is ready for usage. Concrete backends + * are supposed to implement their rendering context creation and related + * operations in this method. + * + * @async + * @param {Renderer} renderer - The renderer. + * @return {Promise} A Promise that resolves when the backend has been initialized. + */ + async init( renderer ) { + + this.renderer = renderer; + + } + + /** + * The coordinate system of the backend. + * + * @abstract + * @type {number} + * @readonly + */ + get coordinateSystem() {} + + // render context + + /** + * This method is executed at the beginning of a render call and + * can be used by the backend to prepare the state for upcoming + * draw calls. + * + * @abstract + * @param {RenderContext} renderContext - The render context. + */ + beginRender( /*renderContext*/ ) {} + + /** + * This method is executed at the end of a render call and + * can be used by the backend to finalize work after draw + * calls. + * + * @abstract + * @param {RenderContext} renderContext - The render context. + */ + finishRender( /*renderContext*/ ) {} + + /** + * This method is executed at the beginning of a compute call and + * can be used by the backend to prepare the state for upcoming + * compute tasks. + * + * @abstract + * @param {Node|Array} computeGroup - The compute node(s). + */ + beginCompute( /*computeGroup*/ ) {} + + /** + * This method is executed at the end of a compute call and + * can be used by the backend to finalize work after compute + * tasks. + * + * @abstract + * @param {Node|Array} computeGroup - The compute node(s). + */ + finishCompute( /*computeGroup*/ ) {} + + // render object + + /** + * Executes a draw command for the given render object. + * + * @abstract + * @param {RenderObject} renderObject - The render object to draw. + * @param {Info} info - Holds a series of statistical information about the GPU memory and the rendering process. + */ + draw( /*renderObject, info*/ ) { } + + // compute node + + /** + * Executes a compute command for the given compute node. + * + * @abstract + * @param {Node|Array} computeGroup - The group of compute nodes of a compute call. Can be a single compute node. + * @param {Node} computeNode - The compute node. + * @param {Array} bindings - The bindings. + * @param {ComputePipeline} computePipeline - The compute pipeline. + */ + compute( /*computeGroup, computeNode, computeBindings, computePipeline*/ ) { } + + // program + + /** + * Creates a shader program from the given programmable stage. + * + * @abstract + * @param {ProgrammableStage} program - The programmable stage. + */ + createProgram( /*program*/ ) { } + + /** + * Destroys the shader program of the given programmable stage. + * + * @abstract + * @param {ProgrammableStage} program - The programmable stage. + */ + destroyProgram( /*program*/ ) { } + + // bindings + + /** + * Creates bindings from the given bind group definition. + * + * @abstract + * @param {BindGroup} bindGroup - The bind group. + * @param {Array} bindings - Array of bind groups. + * @param {number} cacheIndex - The cache index. + * @param {number} version - The version. + */ + createBindings( /*bindGroup, bindings, cacheIndex, version*/ ) { } + + /** + * Updates the given bind group definition. + * + * @abstract + * @param {BindGroup} bindGroup - The bind group. + * @param {Array} bindings - Array of bind groups. + * @param {number} cacheIndex - The cache index. + * @param {number} version - The version. + */ + updateBindings( /*bindGroup, bindings, cacheIndex, version*/ ) { } + + /** + * Updates a buffer binding. + * + * @abstract + * @param {Buffer} binding - The buffer binding to update. + */ + updateBinding( /*binding*/ ) { } + + // pipeline + + /** + * Creates a render pipeline for the given render object. + * + * @abstract + * @param {RenderObject} renderObject - The render object. + * @param {Array} promises - An array of compilation promises which are used in `compileAsync()`. + */ + createRenderPipeline( /*renderObject, promises*/ ) { } + + /** + * Creates a compute pipeline for the given compute node. + * + * @abstract + * @param {ComputePipeline} computePipeline - The compute pipeline. + * @param {Array} bindings - The bindings. + */ + createComputePipeline( /*computePipeline, bindings*/ ) { } + + // cache key + + /** + * Returns `true` if the render pipeline requires an update. + * + * @abstract + * @param {RenderObject} renderObject - The render object. + * @return {boolean} Whether the render pipeline requires an update or not. + */ + needsRenderUpdate( /*renderObject*/ ) { } + + /** + * Returns a cache key that is used to identify render pipelines. + * + * @abstract + * @param {RenderObject} renderObject - The render object. + * @return {string} The cache key. + */ + getRenderCacheKey( /*renderObject*/ ) { } + + // node builder + + /** + * Returns a node builder for the given render object. + * + * @abstract + * @param {RenderObject} renderObject - The render object. + * @param {Renderer} renderer - The renderer. + * @return {NodeBuilder} The node builder. + */ + createNodeBuilder( /*renderObject, renderer*/ ) { } + + // textures + + /** + * Creates a GPU sampler for the given texture. + * + * @abstract + * @param {Texture} texture - The texture to create the sampler for. + */ + createSampler( /*texture*/ ) { } + + /** + * Destroys the GPU sampler for the given texture. + * + * @abstract + * @param {Texture} texture - The texture to destroy the sampler for. + */ + destroySampler( /*texture*/ ) {} + + /** + * Creates a default texture for the given texture that can be used + * as a placeholder until the actual texture is ready for usage. + * + * @abstract + * @param {Texture} texture - The texture to create a default texture for. + */ + createDefaultTexture( /*texture*/ ) { } + + /** + * Defines a texture on the GPU for the given texture object. + * + * @abstract + * @param {Texture} texture - The texture. + * @param {Object} [options={}] - Optional configuration parameter. + */ + createTexture( /*texture, options={}*/ ) { } + + /** + * Uploads the updated texture data to the GPU. + * + * @abstract + * @param {Texture} texture - The texture. + * @param {Object} [options={}] - Optional configuration parameter. + */ + updateTexture( /*texture, options = {}*/ ) { } + + /** + * Generates mipmaps for the given texture. + * + * @abstract + * @param {Texture} texture - The texture. + */ + generateMipmaps( /*texture*/ ) { } + + /** + * Destroys the GPU data for the given texture object. + * + * @abstract + * @param {Texture} texture - The texture. + */ + destroyTexture( /*texture*/ ) { } + + /** + * Returns texture data as a typed array. + * + * @abstract + * @async + * @param {Texture} texture - The texture to copy. + * @param {number} x - The x coordinate of the copy origin. + * @param {number} y - The y coordinate of the copy origin. + * @param {number} width - The width of the copy. + * @param {number} height - The height of the copy. + * @param {number} faceIndex - The face index. + * @return {Promise} A Promise that resolves with a typed array when the copy operation has finished. + */ + async copyTextureToBuffer( /*texture, x, y, width, height, faceIndex*/ ) {} + + /** + * Copies data of the given source texture to the given destination texture. + * + * @abstract + * @param {Texture} srcTexture - The source texture. + * @param {Texture} dstTexture - The destination texture. + * @param {?(Box3|Box2)} [srcRegion=null] - The region of the source texture to copy. + * @param {?(Vector2|Vector3)} [dstPosition=null] - The destination position of the copy. + * @param {number} [srcLevel=0] - The source mip level to copy from. + * @param {number} [dstLevel=0] - The destination mip level to copy to. + */ + copyTextureToTexture( /*srcTexture, dstTexture, srcRegion = null, dstPosition = null, srcLevel = 0, dstLevel = 0*/ ) {} + + /** + * Copies the current bound framebuffer to the given texture. + * + * @abstract + * @param {Texture} texture - The destination texture. + * @param {RenderContext} renderContext - The render context. + * @param {Vector4} rectangle - A four dimensional vector defining the origin and dimension of the copy. + */ + copyFramebufferToTexture( /*texture, renderContext, rectangle*/ ) {} + + // attributes + + /** + * Creates the GPU buffer of a shader attribute. + * + * @abstract + * @param {BufferAttribute} attribute - The buffer attribute. + */ + createAttribute( /*attribute*/ ) { } + + /** + * Creates the GPU buffer of an indexed shader attribute. + * + * @abstract + * @param {BufferAttribute} attribute - The indexed buffer attribute. + */ + createIndexAttribute( /*attribute*/ ) { } + + /** + * Creates the GPU buffer of a storage attribute. + * + * @abstract + * @param {BufferAttribute} attribute - The buffer attribute. + */ + createStorageAttribute( /*attribute*/ ) { } + + /** + * Updates the GPU buffer of a shader attribute. + * + * @abstract + * @param {BufferAttribute} attribute - The buffer attribute to update. + */ + updateAttribute( /*attribute*/ ) { } + + /** + * Destroys the GPU buffer of a shader attribute. + * + * @abstract + * @param {BufferAttribute} attribute - The buffer attribute to destroy. + */ + destroyAttribute( /*attribute*/ ) { } + + // canvas + + /** + * Returns the backend's rendering context. + * + * @abstract + * @return {Object} The rendering context. + */ + getContext() { } + + /** + * Backends can use this method if they have to run + * logic when the renderer gets resized. + * + * @abstract + */ + updateSize() { } + + /** + * Updates the viewport with the values from the given render context. + * + * @abstract + * @param {RenderContext} renderContext - The render context. + */ + updateViewport( /*renderContext*/ ) {} + + // utils + + /** + * Returns `true` if the given 3D object is fully occluded by other + * 3D objects in the scene. Backends must implement this method by using + * a Occlusion Query API. + * + * @abstract + * @param {RenderContext} renderContext - The render context. + * @param {Object3D} object - The 3D object to test. + * @return {boolean} Whether the 3D object is fully occluded or not. + */ + isOccluded( /*renderContext, object*/ ) {} + + /** + * Resolves the time stamp for the given render context and type. + * + * @async + * @abstract + * @param {string} [type='render'] - The type of the time stamp. + * @return {Promise} A Promise that resolves with the time stamp. + */ + async resolveTimestampsAsync( type = 'render' ) { + + if ( ! this.trackTimestamp ) { + + warnOnce( 'WebGPURenderer: Timestamp tracking is disabled.' ); + return; + + } + + const queryPool = this.timestampQueryPool[ type ]; + if ( ! queryPool ) { + + warnOnce( `WebGPURenderer: No timestamp query pool for type '${type}' found.` ); + return; + + } + + const duration = await queryPool.resolveQueriesAsync(); + + this.renderer.info[ type ].timestamp = duration; + + return duration; + + } + + /** + * Can be used to synchronize CPU operations with GPU tasks. So when this method is called, + * the CPU waits for the GPU to complete its operation (e.g. a compute task). + * + * @async + * @abstract + * @return {Promise} A Promise that resolves when synchronization has been finished. + */ + async waitForGPU() {} + + /** + * This method performs a readback operation by moving buffer data from + * a storage buffer attribute from the GPU to the CPU. + * + * @async + * @param {StorageBufferAttribute} attribute - The storage buffer attribute. + * @return {Promise} A promise that resolves with the buffer data when the data are ready. + */ + async getArrayBufferAsync( /* attribute */ ) {} + + /** + * Checks if the given feature is supported by the backend. + * + * @async + * @abstract + * @param {string} name - The feature's name. + * @return {Promise} A Promise that resolves with a bool that indicates whether the feature is supported or not. + */ + async hasFeatureAsync( /*name*/ ) { } + + /** + * Checks if the given feature is supported by the backend. + * + * @abstract + * @param {string} name - The feature's name. + * @return {boolean} Whether the feature is supported or not. + */ + hasFeature( /*name*/ ) {} + + /** + * Returns the maximum anisotropy texture filtering value. + * + * @abstract + * @return {number} The maximum anisotropy texture filtering value. + */ + getMaxAnisotropy() {} + + /** + * Returns the drawing buffer size. + * + * @return {Vector2} The drawing buffer size. + */ + getDrawingBufferSize() { + + _vector2 = _vector2 || new Vector2(); + + return this.renderer.getDrawingBufferSize( _vector2 ); + + } + + /** + * Defines the scissor test. + * + * @abstract + * @param {boolean} boolean - Whether the scissor test should be enabled or not. + */ + setScissorTest( /*boolean*/ ) { } + + /** + * Returns the clear color and alpha into a single + * color object. + * + * @return {Color4} The clear color. + */ + getClearColor() { + + const renderer = this.renderer; + + _color4 = _color4 || new Color4(); + + renderer.getClearColor( _color4 ); + + _color4.getRGB( _color4 ); + + return _color4; + + } + + /** + * Returns the DOM element. If no DOM element exists, the backend + * creates a new one. + * + * @return {HTMLCanvasElement} The DOM element. + */ + getDomElement() { + + let domElement = this.domElement; + + if ( domElement === null ) { + + domElement = ( this.parameters.canvas !== undefined ) ? this.parameters.canvas : createCanvasElement(); + + // OffscreenCanvas does not have setAttribute, see #22811 + if ( 'setAttribute' in domElement ) domElement.setAttribute( 'data-engine', `three.js r${REVISION} webgpu` ); + + this.domElement = domElement; + + } + + return domElement; + + } + + /** + * Sets a dictionary for the given object into the + * internal data structure. + * + * @param {Object} object - The object. + * @param {Object} value - The dictionary to set. + */ + set( object, value ) { + + this.data.set( object, value ); + + } + + /** + * Returns the dictionary for the given object. + * + * @param {Object} object - The object. + * @return {Object} The object's dictionary. + */ + get( object ) { + + let map = this.data.get( object ); + + if ( map === undefined ) { + + map = {}; + this.data.set( object, map ); + + } + + return map; + + } + + /** + * Checks if the given object has a dictionary + * with data defined. + * + * @param {Object} object - The object. + * @return {boolean} Whether a dictionary for the given object as been defined or not. + */ + has( object ) { + + return this.data.has( object ); + + } + + /** + * Deletes an object from the internal data structure. + * + * @param {Object} object - The object to delete. + */ + delete( object ) { + + this.data.delete( object ); + + } + + /** + * Frees internal resources. + * + * @abstract + */ + dispose() { } + +} + +let _id$1 = 0; + +/** + * This module is internally used in context of compute shaders. + * This type of shader is not natively supported in WebGL 2 and + * thus implemented via Transform Feedback. `DualAttributeData` + * manages the related data. + * + * @private + */ +class DualAttributeData { + + constructor( attributeData, dualBuffer ) { + + this.buffers = [ attributeData.bufferGPU, dualBuffer ]; + this.type = attributeData.type; + this.bufferType = attributeData.bufferType; + this.pbo = attributeData.pbo; + this.byteLength = attributeData.byteLength; + this.bytesPerElement = attributeData.BYTES_PER_ELEMENT; + this.version = attributeData.version; + this.isInteger = attributeData.isInteger; + this.activeBufferIndex = 0; + this.baseId = attributeData.id; + + } + + + get id() { + + return `${ this.baseId }|${ this.activeBufferIndex }`; + + } + + get bufferGPU() { + + return this.buffers[ this.activeBufferIndex ]; + + } + + get transformBuffer() { + + return this.buffers[ this.activeBufferIndex ^ 1 ]; + + } + + switchBuffers() { + + this.activeBufferIndex ^= 1; + + } + +} + +/** + * A WebGL 2 backend utility module for managing shader attributes. + * + * @private + */ +class WebGLAttributeUtils { + + /** + * Constructs a new utility object. + * + * @param {WebGLBackend} backend - The WebGL 2 backend. + */ + constructor( backend ) { + + /** + * A reference to the WebGL 2 backend. + * + * @type {WebGLBackend} + */ + this.backend = backend; + + } + + /** + * Creates the GPU buffer for the given buffer attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute. + * @param {GLenum } bufferType - A flag that indicates the buffer type and thus binding point target. + */ + createAttribute( attribute, bufferType ) { + + const backend = this.backend; + const { gl } = backend; + + const array = attribute.array; + const usage = attribute.usage || gl.STATIC_DRAW; + + const bufferAttribute = attribute.isInterleavedBufferAttribute ? attribute.data : attribute; + const bufferData = backend.get( bufferAttribute ); + + let bufferGPU = bufferData.bufferGPU; + + if ( bufferGPU === undefined ) { + + bufferGPU = this._createBuffer( gl, bufferType, array, usage ); + + bufferData.bufferGPU = bufferGPU; + bufferData.bufferType = bufferType; + bufferData.version = bufferAttribute.version; + + } + + //attribute.onUploadCallback(); + + let type; + + if ( array instanceof Float32Array ) { + + type = gl.FLOAT; + + } else if ( typeof Float16Array !== 'undefined' && array instanceof Float16Array ) { + + type = gl.HALF_FLOAT; + + } else if ( array instanceof Uint16Array ) { + + if ( attribute.isFloat16BufferAttribute ) { + + type = gl.HALF_FLOAT; + + } else { + + type = gl.UNSIGNED_SHORT; + + } + + } else if ( array instanceof Int16Array ) { + + type = gl.SHORT; + + } else if ( array instanceof Uint32Array ) { + + type = gl.UNSIGNED_INT; + + } else if ( array instanceof Int32Array ) { + + type = gl.INT; + + } else if ( array instanceof Int8Array ) { + + type = gl.BYTE; + + } else if ( array instanceof Uint8Array ) { + + type = gl.UNSIGNED_BYTE; + + } else if ( array instanceof Uint8ClampedArray ) { + + type = gl.UNSIGNED_BYTE; + + } else { + + throw new Error( 'THREE.WebGLBackend: Unsupported buffer data format: ' + array ); + + } + + let attributeData = { + bufferGPU, + bufferType, + type, + byteLength: array.byteLength, + bytesPerElement: array.BYTES_PER_ELEMENT, + version: attribute.version, + pbo: attribute.pbo, + isInteger: type === gl.INT || type === gl.UNSIGNED_INT || attribute.gpuType === IntType, + id: _id$1 ++ + }; + + if ( attribute.isStorageBufferAttribute || attribute.isStorageInstancedBufferAttribute ) { + + // create buffer for transform feedback use + const bufferGPUDual = this._createBuffer( gl, bufferType, array, usage ); + attributeData = new DualAttributeData( attributeData, bufferGPUDual ); + + } + + backend.set( attribute, attributeData ); + + } + + /** + * Updates the GPU buffer of the given buffer attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute. + */ + updateAttribute( attribute ) { + + const backend = this.backend; + const { gl } = backend; + + const array = attribute.array; + const bufferAttribute = attribute.isInterleavedBufferAttribute ? attribute.data : attribute; + const bufferData = backend.get( bufferAttribute ); + const bufferType = bufferData.bufferType; + const updateRanges = attribute.isInterleavedBufferAttribute ? attribute.data.updateRanges : attribute.updateRanges; + + gl.bindBuffer( bufferType, bufferData.bufferGPU ); + + if ( updateRanges.length === 0 ) { + + // Not using update ranges + + gl.bufferSubData( bufferType, 0, array ); + + } else { + + for ( let i = 0, l = updateRanges.length; i < l; i ++ ) { + + const range = updateRanges[ i ]; + gl.bufferSubData( bufferType, range.start * array.BYTES_PER_ELEMENT, + array, range.start, range.count ); + + } + + bufferAttribute.clearUpdateRanges(); + + } + + gl.bindBuffer( bufferType, null ); + + bufferData.version = bufferAttribute.version; + + } + + /** + * Destroys the GPU buffer of the given buffer attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute. + */ + destroyAttribute( attribute ) { + + const backend = this.backend; + const { gl } = backend; + + if ( attribute.isInterleavedBufferAttribute ) { + + backend.delete( attribute.data ); + + } + + const attributeData = backend.get( attribute ); + + gl.deleteBuffer( attributeData.bufferGPU ); + + backend.delete( attribute ); + + } + + /** + * This method performs a readback operation by moving buffer data from + * a storage buffer attribute from the GPU to the CPU. + * + * @async + * @param {StorageBufferAttribute} attribute - The storage buffer attribute. + * @return {Promise} A promise that resolves with the buffer data when the data are ready. + */ + async getArrayBufferAsync( attribute ) { + + const backend = this.backend; + const { gl } = backend; + + const bufferAttribute = attribute.isInterleavedBufferAttribute ? attribute.data : attribute; + const { bufferGPU } = backend.get( bufferAttribute ); + + const array = attribute.array; + const byteLength = array.byteLength; + + gl.bindBuffer( gl.COPY_READ_BUFFER, bufferGPU ); + + const writeBuffer = gl.createBuffer(); + + gl.bindBuffer( gl.COPY_WRITE_BUFFER, writeBuffer ); + gl.bufferData( gl.COPY_WRITE_BUFFER, byteLength, gl.STREAM_READ ); + + gl.copyBufferSubData( gl.COPY_READ_BUFFER, gl.COPY_WRITE_BUFFER, 0, 0, byteLength ); + + await backend.utils._clientWaitAsync(); + + const dstBuffer = new attribute.array.constructor( array.length ); + + // Ensure the buffer is bound before reading + gl.bindBuffer( gl.COPY_WRITE_BUFFER, writeBuffer ); + + gl.getBufferSubData( gl.COPY_WRITE_BUFFER, 0, dstBuffer ); + + gl.deleteBuffer( writeBuffer ); + + gl.bindBuffer( gl.COPY_READ_BUFFER, null ); + gl.bindBuffer( gl.COPY_WRITE_BUFFER, null ); + + return dstBuffer.buffer; + + } + + /** + * Creates a WebGL buffer with the given data. + * + * @private + * @param {WebGL2RenderingContext} gl - The rendering context. + * @param {GLenum } bufferType - A flag that indicates the buffer type and thus binding point target. + * @param {TypedArray} array - The array of the buffer attribute. + * @param {GLenum} usage - The usage. + * @return {WebGLBuffer} The WebGL buffer. + */ + _createBuffer( gl, bufferType, array, usage ) { + + const bufferGPU = gl.createBuffer(); + + gl.bindBuffer( bufferType, bufferGPU ); + gl.bufferData( bufferType, array, usage ); + gl.bindBuffer( bufferType, null ); + + return bufferGPU; + + } + +} + +let equationToGL, factorToGL; + +/** + * A WebGL 2 backend utility module for managing the WebGL state. + * + * The major goal of this module is to reduce the number of state changes + * by caching the WEbGL state with a series of variables. In this way, the + * renderer only executes state change commands when necessary which + * improves the overall performance. + * + * @private + */ +class WebGLState { + + /** + * Constructs a new utility object. + * + * @param {WebGLBackend} backend - The WebGL 2 backend. + */ + constructor( backend ) { + + /** + * A reference to the WebGL 2 backend. + * + * @type {WebGLBackend} + */ + this.backend = backend; + + /** + * A reference to the rendering context. + * + * @type {WebGL2RenderingContext} + */ + this.gl = this.backend.gl; + + // Below properties are intended to cache + // the WebGL state and are not explicitly + // documented for convenience reasons. + + this.enabled = {}; + this.currentFlipSided = null; + this.currentCullFace = null; + this.currentProgram = null; + this.currentBlendingEnabled = false; + this.currentBlending = null; + this.currentBlendSrc = null; + this.currentBlendDst = null; + this.currentBlendSrcAlpha = null; + this.currentBlendDstAlpha = null; + this.currentPremultipledAlpha = null; + this.currentPolygonOffsetFactor = null; + this.currentPolygonOffsetUnits = null; + this.currentColorMask = null; + this.currentDepthFunc = null; + this.currentDepthMask = null; + this.currentStencilFunc = null; + this.currentStencilRef = null; + this.currentStencilFuncMask = null; + this.currentStencilFail = null; + this.currentStencilZFail = null; + this.currentStencilZPass = null; + this.currentStencilMask = null; + this.currentLineWidth = null; + this.currentClippingPlanes = 0; + + this.currentVAO = null; + this.currentIndex = null; + + this.currentBoundFramebuffers = {}; + this.currentDrawbuffers = new WeakMap(); + + this.maxTextures = this.gl.getParameter( this.gl.MAX_TEXTURE_IMAGE_UNITS ); + this.currentTextureSlot = null; + this.currentBoundTextures = {}; + this.currentBoundBufferBases = {}; + + + this._init(); + + } + + /** + * Inits the state of the utility. + * + * @private + */ + _init() { + + const gl = this.gl; + + // Store only WebGL constants here. + + equationToGL = { + [ AddEquation ]: gl.FUNC_ADD, + [ SubtractEquation ]: gl.FUNC_SUBTRACT, + [ ReverseSubtractEquation ]: gl.FUNC_REVERSE_SUBTRACT + }; + + factorToGL = { + [ ZeroFactor ]: gl.ZERO, + [ OneFactor ]: gl.ONE, + [ SrcColorFactor ]: gl.SRC_COLOR, + [ SrcAlphaFactor ]: gl.SRC_ALPHA, + [ SrcAlphaSaturateFactor ]: gl.SRC_ALPHA_SATURATE, + [ DstColorFactor ]: gl.DST_COLOR, + [ DstAlphaFactor ]: gl.DST_ALPHA, + [ OneMinusSrcColorFactor ]: gl.ONE_MINUS_SRC_COLOR, + [ OneMinusSrcAlphaFactor ]: gl.ONE_MINUS_SRC_ALPHA, + [ OneMinusDstColorFactor ]: gl.ONE_MINUS_DST_COLOR, + [ OneMinusDstAlphaFactor ]: gl.ONE_MINUS_DST_ALPHA + }; + + const scissorParam = gl.getParameter( gl.SCISSOR_BOX ); + const viewportParam = gl.getParameter( gl.VIEWPORT ); + + this.currentScissor = new Vector4().fromArray( scissorParam ); + this.currentViewport = new Vector4().fromArray( viewportParam ); + + this._tempVec4 = new Vector4(); + + } + + /** + * Enables the given WebGL capability. + * + * This method caches the capability state so + * `gl.enable()` is only called when necessary. + * + * @param {GLenum} id - The capability to enable. + */ + enable( id ) { + + const { enabled } = this; + + if ( enabled[ id ] !== true ) { + + this.gl.enable( id ); + enabled[ id ] = true; + + } + + } + + /** + * Disables the given WebGL capability. + * + * This method caches the capability state so + * `gl.disable()` is only called when necessary. + * + * @param {GLenum} id - The capability to enable. + */ + disable( id ) { + + const { enabled } = this; + + if ( enabled[ id ] !== false ) { + + this.gl.disable( id ); + enabled[ id ] = false; + + } + + } + + /** + * Specifies whether polygons are front- or back-facing + * by setting the winding orientation. + * + * This method caches the state so `gl.frontFace()` is only + * called when necessary. + * + * @param {boolean} flipSided - Whether triangles flipped their sides or not. + */ + setFlipSided( flipSided ) { + + if ( this.currentFlipSided !== flipSided ) { + + const { gl } = this; + + if ( flipSided ) { + + gl.frontFace( gl.CW ); + + } else { + + gl.frontFace( gl.CCW ); + + } + + this.currentFlipSided = flipSided; + + } + + } + + /** + * Specifies whether or not front- and/or back-facing + * polygons can be culled. + * + * This method caches the state so `gl.cullFace()` is only + * called when necessary. + * + * @param {number} cullFace - Defines which polygons are candidates for culling. + */ + setCullFace( cullFace ) { + + const { gl } = this; + + if ( cullFace !== CullFaceNone ) { + + this.enable( gl.CULL_FACE ); + + if ( cullFace !== this.currentCullFace ) { + + if ( cullFace === CullFaceBack ) { + + gl.cullFace( gl.BACK ); + + } else if ( cullFace === CullFaceFront ) { + + gl.cullFace( gl.FRONT ); + + } else { + + gl.cullFace( gl.FRONT_AND_BACK ); + + } + + } + + } else { + + this.disable( gl.CULL_FACE ); + + } + + this.currentCullFace = cullFace; + + } + + /** + * Specifies the width of line primitives. + * + * This method caches the state so `gl.lineWidth()` is only + * called when necessary. + * + * @param {number} width - The line width. + */ + setLineWidth( width ) { + + const { currentLineWidth, gl } = this; + + if ( width !== currentLineWidth ) { + + gl.lineWidth( width ); + + this.currentLineWidth = width; + + } + + } + + /** + * Defines the blending. + * + * This method caches the state so `gl.blendEquation()`, `gl.blendEquationSeparate()`, + * `gl.blendFunc()` and `gl.blendFuncSeparate()` are only called when necessary. + * + * @param {number} blending - The blending type. + * @param {number} blendEquation - The blending equation. + * @param {number} blendSrc - Only relevant for custom blending. The RGB source blending factor. + * @param {number} blendDst - Only relevant for custom blending. The RGB destination blending factor. + * @param {number} blendEquationAlpha - Only relevant for custom blending. The blending equation for alpha. + * @param {number} blendSrcAlpha - Only relevant for custom blending. The alpha source blending factor. + * @param {number} blendDstAlpha - Only relevant for custom blending. The alpha destination blending factor. + * @param {boolean} premultipliedAlpha - Whether premultiplied alpha is enabled or not. + */ + setBlending( blending, blendEquation, blendSrc, blendDst, blendEquationAlpha, blendSrcAlpha, blendDstAlpha, premultipliedAlpha ) { + + const { gl } = this; + + if ( blending === NoBlending ) { + + if ( this.currentBlendingEnabled === true ) { + + this.disable( gl.BLEND ); + this.currentBlendingEnabled = false; + + } + + return; + + } + + if ( this.currentBlendingEnabled === false ) { + + this.enable( gl.BLEND ); + this.currentBlendingEnabled = true; + + } + + if ( blending !== CustomBlending ) { + + if ( blending !== this.currentBlending || premultipliedAlpha !== this.currentPremultipledAlpha ) { + + if ( this.currentBlendEquation !== AddEquation || this.currentBlendEquationAlpha !== AddEquation ) { + + gl.blendEquation( gl.FUNC_ADD ); + + this.currentBlendEquation = AddEquation; + this.currentBlendEquationAlpha = AddEquation; + + } + + if ( premultipliedAlpha ) { + + switch ( blending ) { + + case NormalBlending: + gl.blendFuncSeparate( gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA ); + break; + + case AdditiveBlending: + gl.blendFunc( gl.ONE, gl.ONE ); + break; + + case SubtractiveBlending: + gl.blendFuncSeparate( gl.ZERO, gl.ONE_MINUS_SRC_COLOR, gl.ZERO, gl.ONE ); + break; + + case MultiplyBlending: + gl.blendFuncSeparate( gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA, gl.ZERO, gl.ONE ); + break; + + default: + console.error( 'THREE.WebGLState: Invalid blending: ', blending ); + break; + + } + + } else { + + switch ( blending ) { + + case NormalBlending: + gl.blendFuncSeparate( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA ); + break; + + case AdditiveBlending: + gl.blendFuncSeparate( gl.SRC_ALPHA, gl.ONE, gl.ONE, gl.ONE ); + break; + + case SubtractiveBlending: + console.error( 'THREE.WebGLState: SubtractiveBlending requires material.premultipliedAlpha = true' ); + break; + + case MultiplyBlending: + console.error( 'THREE.WebGLState: MultiplyBlending requires material.premultipliedAlpha = true' ); + break; + + default: + console.error( 'THREE.WebGLState: Invalid blending: ', blending ); + break; + + } + + } + + this.currentBlendSrc = null; + this.currentBlendDst = null; + this.currentBlendSrcAlpha = null; + this.currentBlendDstAlpha = null; + + this.currentBlending = blending; + this.currentPremultipledAlpha = premultipliedAlpha; + + } + + return; + + } + + // custom blending + + blendEquationAlpha = blendEquationAlpha || blendEquation; + blendSrcAlpha = blendSrcAlpha || blendSrc; + blendDstAlpha = blendDstAlpha || blendDst; + + if ( blendEquation !== this.currentBlendEquation || blendEquationAlpha !== this.currentBlendEquationAlpha ) { + + gl.blendEquationSeparate( equationToGL[ blendEquation ], equationToGL[ blendEquationAlpha ] ); + + this.currentBlendEquation = blendEquation; + this.currentBlendEquationAlpha = blendEquationAlpha; + + } + + if ( blendSrc !== this.currentBlendSrc || blendDst !== this.currentBlendDst || blendSrcAlpha !== this.currentBlendSrcAlpha || blendDstAlpha !== this.currentBlendDstAlpha ) { + + gl.blendFuncSeparate( factorToGL[ blendSrc ], factorToGL[ blendDst ], factorToGL[ blendSrcAlpha ], factorToGL[ blendDstAlpha ] ); + + this.currentBlendSrc = blendSrc; + this.currentBlendDst = blendDst; + this.currentBlendSrcAlpha = blendSrcAlpha; + this.currentBlendDstAlpha = blendDstAlpha; + + } + + this.currentBlending = blending; + this.currentPremultipledAlpha = false; + + } + + /** + * Specifies whether colors can be written when rendering + * into a framebuffer or not. + * + * This method caches the state so `gl.colorMask()` is only + * called when necessary. + * + * @param {boolean} colorMask - The color mask. + */ + setColorMask( colorMask ) { + + if ( this.currentColorMask !== colorMask ) { + + this.gl.colorMask( colorMask, colorMask, colorMask, colorMask ); + this.currentColorMask = colorMask; + + } + + } + + /** + * Specifies whether the depth test is enabled or not. + * + * @param {boolean} depthTest - Whether the depth test is enabled or not. + */ + setDepthTest( depthTest ) { + + const { gl } = this; + + if ( depthTest ) { + + this.enable( gl.DEPTH_TEST ); + + } else { + + this.disable( gl.DEPTH_TEST ); + + } + + } + + /** + * Specifies whether depth values can be written when rendering + * into a framebuffer or not. + * + * This method caches the state so `gl.depthMask()` is only + * called when necessary. + * + * @param {boolean} depthMask - The depth mask. + */ + setDepthMask( depthMask ) { + + if ( this.currentDepthMask !== depthMask ) { + + this.gl.depthMask( depthMask ); + this.currentDepthMask = depthMask; + + } + + } + + /** + * Specifies the depth compare function. + * + * This method caches the state so `gl.depthFunc()` is only + * called when necessary. + * + * @param {number} depthFunc - The depth compare function. + */ + setDepthFunc( depthFunc ) { + + if ( this.currentDepthFunc !== depthFunc ) { + + const { gl } = this; + + switch ( depthFunc ) { + + case NeverDepth: + + gl.depthFunc( gl.NEVER ); + break; + + case AlwaysDepth: + + gl.depthFunc( gl.ALWAYS ); + break; + + case LessDepth: + + gl.depthFunc( gl.LESS ); + break; + + case LessEqualDepth: + + gl.depthFunc( gl.LEQUAL ); + break; + + case EqualDepth: + + gl.depthFunc( gl.EQUAL ); + break; + + case GreaterEqualDepth: + + gl.depthFunc( gl.GEQUAL ); + break; + + case GreaterDepth: + + gl.depthFunc( gl.GREATER ); + break; + + case NotEqualDepth: + + gl.depthFunc( gl.NOTEQUAL ); + break; + + default: + + gl.depthFunc( gl.LEQUAL ); + + } + + this.currentDepthFunc = depthFunc; + + } + + } + + /** + * Specifies the scissor box. + * + * @param {number} x - The x-coordinate of the lower left corner of the viewport. + * @param {number} y - The y-coordinate of the lower left corner of the viewport. + * @param {number} width - The width of the viewport. + * @param {number} height - The height of the viewport. + * + */ + scissor( x, y, width, height ) { + + const scissor = this._tempVec4.set( x, y, width, height ); + + if ( this.currentScissor.equals( scissor ) === false ) { + + const { gl } = this; + + gl.scissor( scissor.x, scissor.y, scissor.z, scissor.w ); + this.currentScissor.copy( scissor ); + + } + + } + + /** + * Specifies the viewport. + * + * @param {number} x - The x-coordinate of the lower left corner of the viewport. + * @param {number} y - The y-coordinate of the lower left corner of the viewport. + * @param {number} width - The width of the viewport. + * @param {number} height - The height of the viewport. + * + */ + viewport( x, y, width, height ) { + + const viewport = this._tempVec4.set( x, y, width, height ); + + if ( this.currentViewport.equals( viewport ) === false ) { + + const { gl } = this; + + gl.viewport( viewport.x, viewport.y, viewport.z, viewport.w ); + this.currentViewport.copy( viewport ); + + } + + } + + /** + * Defines the scissor test. + * + * @param {boolean} boolean - Whether the scissor test should be enabled or not. + */ + setScissorTest( boolean ) { + + const gl = this.gl; + + if ( boolean ) { + + gl.enable( gl.SCISSOR_TEST ); + + } else { + + gl.disable( gl.SCISSOR_TEST ); + + } + + } + + /** + * Specifies whether the stencil test is enabled or not. + * + * @param {boolean} stencilTest - Whether the stencil test is enabled or not. + */ + setStencilTest( stencilTest ) { + + const { gl } = this; + + if ( stencilTest ) { + + this.enable( gl.STENCIL_TEST ); + + } else { + + this.disable( gl.STENCIL_TEST ); + + } + + } + + /** + * Specifies whether stencil values can be written when rendering + * into a framebuffer or not. + * + * This method caches the state so `gl.stencilMask()` is only + * called when necessary. + * + * @param {boolean} stencilMask - The stencil mask. + */ + setStencilMask( stencilMask ) { + + if ( this.currentStencilMask !== stencilMask ) { + + this.gl.stencilMask( stencilMask ); + this.currentStencilMask = stencilMask; + + } + + } + + /** + * Specifies whether the stencil test functions. + * + * This method caches the state so `gl.stencilFunc()` is only + * called when necessary. + * + * @param {number} stencilFunc - The stencil compare function. + * @param {number} stencilRef - The reference value for the stencil test. + * @param {number} stencilMask - A bit-wise mask that is used to AND the reference value and the stored stencil value when the test is done. + */ + setStencilFunc( stencilFunc, stencilRef, stencilMask ) { + + if ( this.currentStencilFunc !== stencilFunc || + this.currentStencilRef !== stencilRef || + this.currentStencilFuncMask !== stencilMask ) { + + this.gl.stencilFunc( stencilFunc, stencilRef, stencilMask ); + + this.currentStencilFunc = stencilFunc; + this.currentStencilRef = stencilRef; + this.currentStencilFuncMask = stencilMask; + + } + + } + + /** + * Specifies whether the stencil test operation. + * + * This method caches the state so `gl.stencilOp()` is only + * called when necessary. + * + * @param {number} stencilFail - The function to use when the stencil test fails. + * @param {number} stencilZFail - The function to use when the stencil test passes, but the depth test fail. + * @param {number} stencilZPass - The function to use when both the stencil test and the depth test pass, + * or when the stencil test passes and there is no depth buffer or depth testing is disabled. + */ + setStencilOp( stencilFail, stencilZFail, stencilZPass ) { + + if ( this.currentStencilFail !== stencilFail || + this.currentStencilZFail !== stencilZFail || + this.currentStencilZPass !== stencilZPass ) { + + this.gl.stencilOp( stencilFail, stencilZFail, stencilZPass ); + + this.currentStencilFail = stencilFail; + this.currentStencilZFail = stencilZFail; + this.currentStencilZPass = stencilZPass; + + } + + } + + /** + * Configures the WebGL state for the given material. + * + * @param {Material} material - The material to configure the state for. + * @param {number} frontFaceCW - Whether the front faces are counter-clockwise or not. + * @param {number} hardwareClippingPlanes - The number of hardware clipping planes. + */ + setMaterial( material, frontFaceCW, hardwareClippingPlanes ) { + + const { gl } = this; + + material.side === DoubleSide + ? this.disable( gl.CULL_FACE ) + : this.enable( gl.CULL_FACE ); + + let flipSided = ( material.side === BackSide ); + if ( frontFaceCW ) flipSided = ! flipSided; + + this.setFlipSided( flipSided ); + + ( material.blending === NormalBlending && material.transparent === false ) + ? this.setBlending( NoBlending ) + : this.setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst, material.blendEquationAlpha, material.blendSrcAlpha, material.blendDstAlpha, material.premultipliedAlpha ); + + this.setDepthFunc( material.depthFunc ); + this.setDepthTest( material.depthTest ); + this.setDepthMask( material.depthWrite ); + this.setColorMask( material.colorWrite ); + + const stencilWrite = material.stencilWrite; + this.setStencilTest( stencilWrite ); + if ( stencilWrite ) { + + this.setStencilMask( material.stencilWriteMask ); + this.setStencilFunc( material.stencilFunc, material.stencilRef, material.stencilFuncMask ); + this.setStencilOp( material.stencilFail, material.stencilZFail, material.stencilZPass ); + + } + + this.setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits ); + + material.alphaToCoverage === true && this.backend.renderer.samples > 1 + ? this.enable( gl.SAMPLE_ALPHA_TO_COVERAGE ) + : this.disable( gl.SAMPLE_ALPHA_TO_COVERAGE ); + + if ( hardwareClippingPlanes > 0 ) { + + if ( this.currentClippingPlanes !== hardwareClippingPlanes ) { + + const CLIP_DISTANCE0_WEBGL = 0x3000; + + for ( let i = 0; i < 8; i ++ ) { + + if ( i < hardwareClippingPlanes ) { + + this.enable( CLIP_DISTANCE0_WEBGL + i ); + + } else { + + this.disable( CLIP_DISTANCE0_WEBGL + i ); + + } + + } + + } + + } + + } + + /** + * Specifies the polygon offset. + * + * This method caches the state so `gl.polygonOffset()` is only + * called when necessary. + * + * @param {boolean} polygonOffset - Whether polygon offset is enabled or not. + * @param {number} factor - The scale factor for the variable depth offset for each polygon. + * @param {number} units - The multiplier by which an implementation-specific value is multiplied with to create a constant depth offset. + */ + setPolygonOffset( polygonOffset, factor, units ) { + + const { gl } = this; + + if ( polygonOffset ) { + + this.enable( gl.POLYGON_OFFSET_FILL ); + + if ( this.currentPolygonOffsetFactor !== factor || this.currentPolygonOffsetUnits !== units ) { + + gl.polygonOffset( factor, units ); + + this.currentPolygonOffsetFactor = factor; + this.currentPolygonOffsetUnits = units; + + } + + } else { + + this.disable( gl.POLYGON_OFFSET_FILL ); + + } + + } + + /** + * Defines the usage of the given WebGL program. + * + * This method caches the state so `gl.useProgram()` is only + * called when necessary. + * + * @param {WebGLProgram} program - The WebGL program to use. + * @return {boolean} Whether a program change has been executed or not. + */ + useProgram( program ) { + + if ( this.currentProgram !== program ) { + + this.gl.useProgram( program ); + + this.currentProgram = program; + + return true; + + } + + return false; + + } + + /** + * Sets the vertex state by binding the given VAO and element buffer. + * + * @param {WebGLVertexArrayObject} vao - The VAO. + * @param {WebGLBuffer} indexBuffer - The index buffer. + * @return {boolean} Whether a vertex state has been changed or not. + */ + setVertexState( vao, indexBuffer = null ) { + + const gl = this.gl; + + if ( this.currentVAO !== vao || this.currentIndex !== indexBuffer ) { + + gl.bindVertexArray( vao ); + + if ( indexBuffer !== null ) { + + gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, indexBuffer ); + + } + + this.currentVAO = vao; + this.currentIndex = indexBuffer; + + return true; + + } + + return false; + + } + + /** + * Resets the vertex array state by resetting the VAO and element buffer. + */ + resetVertexState() { + + const gl = this.gl; + + gl.bindVertexArray( null ); + gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, null ); + + this.currentVAO = null; + this.currentIndex = null; + + } + + // framebuffer + + + /** + * Binds the given framebuffer. + * + * This method caches the state so `gl.bindFramebuffer()` is only + * called when necessary. + * + * @param {number} target - The binding point (target). + * @param {WebGLFramebuffer} framebuffer - The WebGL framebuffer to bind. + * @return {boolean} Whether a bind has been executed or not. + */ + bindFramebuffer( target, framebuffer ) { + + const { gl, currentBoundFramebuffers } = this; + + if ( currentBoundFramebuffers[ target ] !== framebuffer ) { + + gl.bindFramebuffer( target, framebuffer ); + + currentBoundFramebuffers[ target ] = framebuffer; + + // gl.DRAW_FRAMEBUFFER is equivalent to gl.FRAMEBUFFER + + if ( target === gl.DRAW_FRAMEBUFFER ) { + + currentBoundFramebuffers[ gl.FRAMEBUFFER ] = framebuffer; + + } + + if ( target === gl.FRAMEBUFFER ) { + + currentBoundFramebuffers[ gl.DRAW_FRAMEBUFFER ] = framebuffer; + + } + + return true; + + } + + return false; + + } + + /** + * Defines draw buffers to which fragment colors are written into. + * Configures the MRT setup of custom framebuffers. + * + * This method caches the state so `gl.drawBuffers()` is only + * called when necessary. + * + * @param {RenderContext} renderContext - The render context. + * @param {WebGLFramebuffer} framebuffer - The WebGL framebuffer. + */ + drawBuffers( renderContext, framebuffer ) { + + const { gl } = this; + + let drawBuffers = []; + + let needsUpdate = false; + + if ( renderContext.textures !== null ) { + + drawBuffers = this.currentDrawbuffers.get( framebuffer ); + + if ( drawBuffers === undefined ) { + + drawBuffers = []; + this.currentDrawbuffers.set( framebuffer, drawBuffers ); + + } + + + const textures = renderContext.textures; + + if ( drawBuffers.length !== textures.length || drawBuffers[ 0 ] !== gl.COLOR_ATTACHMENT0 ) { + + for ( let i = 0, il = textures.length; i < il; i ++ ) { + + drawBuffers[ i ] = gl.COLOR_ATTACHMENT0 + i; + + } + + drawBuffers.length = textures.length; + + needsUpdate = true; + + } + + + } else { + + if ( drawBuffers[ 0 ] !== gl.BACK ) { + + drawBuffers[ 0 ] = gl.BACK; + + needsUpdate = true; + + } + + } + + if ( needsUpdate ) { + + gl.drawBuffers( drawBuffers ); + + } + + } + + + // texture + + /** + * Makes the given texture unit active. + * + * This method caches the state so `gl.activeTexture()` is only + * called when necessary. + * + * @param {number} webglSlot - The texture unit to make active. + */ + activeTexture( webglSlot ) { + + const { gl, currentTextureSlot, maxTextures } = this; + + if ( webglSlot === undefined ) webglSlot = gl.TEXTURE0 + maxTextures - 1; + + if ( currentTextureSlot !== webglSlot ) { + + gl.activeTexture( webglSlot ); + this.currentTextureSlot = webglSlot; + + } + + } + + /** + * Binds the given WebGL texture to a target. + * + * This method caches the state so `gl.bindTexture()` is only + * called when necessary. + * + * @param {number} webglType - The binding point (target). + * @param {WebGLTexture} webglTexture - The WebGL texture to bind. + * @param {number} webglSlot - The texture. + */ + bindTexture( webglType, webglTexture, webglSlot ) { + + const { gl, currentTextureSlot, currentBoundTextures, maxTextures } = this; + + if ( webglSlot === undefined ) { + + if ( currentTextureSlot === null ) { + + webglSlot = gl.TEXTURE0 + maxTextures - 1; + + } else { + + webglSlot = currentTextureSlot; + + } + + } + + let boundTexture = currentBoundTextures[ webglSlot ]; + + if ( boundTexture === undefined ) { + + boundTexture = { type: undefined, texture: undefined }; + currentBoundTextures[ webglSlot ] = boundTexture; + + } + + if ( boundTexture.type !== webglType || boundTexture.texture !== webglTexture ) { + + if ( currentTextureSlot !== webglSlot ) { + + gl.activeTexture( webglSlot ); + this.currentTextureSlot = webglSlot; + + } + + gl.bindTexture( webglType, webglTexture ); + + boundTexture.type = webglType; + boundTexture.texture = webglTexture; + + } + + } + + /** + * Binds a given WebGL buffer to a given binding point (target) at a given index. + * + * This method caches the state so `gl.bindBufferBase()` is only + * called when necessary. + * + * @param {number} target - The target for the bind operation. + * @param {number} index - The index of the target. + * @param {WebGLBuffer} buffer - The WebGL buffer. + * @return {boolean} Whether a bind has been executed or not. + */ + bindBufferBase( target, index, buffer ) { + + const { gl } = this; + + const key = `${target}-${index}`; + + if ( this.currentBoundBufferBases[ key ] !== buffer ) { + + gl.bindBufferBase( target, index, buffer ); + this.currentBoundBufferBases[ key ] = buffer; + + return true; + + } + + return false; + + } + + + /** + * Unbinds the current bound texture. + * + * This method caches the state so `gl.bindTexture()` is only + * called when necessary. + */ + unbindTexture() { + + const { gl, currentTextureSlot, currentBoundTextures } = this; + + const boundTexture = currentBoundTextures[ currentTextureSlot ]; + + if ( boundTexture !== undefined && boundTexture.type !== undefined ) { + + gl.bindTexture( boundTexture.type, null ); + + boundTexture.type = undefined; + boundTexture.texture = undefined; + + } + + } + +} + +/** + * A WebGL 2 backend utility module with common helpers. + * + * @private + */ +class WebGLUtils { + + /** + * Constructs a new utility object. + * + * @param {WebGLBackend} backend - The WebGL 2 backend. + */ + constructor( backend ) { + + /** + * A reference to the WebGL 2 backend. + * + * @type {WebGLBackend} + */ + this.backend = backend; + + /** + * A reference to the rendering context. + * + * @type {WebGL2RenderingContext} + */ + this.gl = this.backend.gl; + + /** + * A reference to a backend module holding extension-related + * utility functions. + * + * @type {WebGLExtensions} + */ + this.extensions = backend.extensions; + + } + + /** + * Converts the given three.js constant into a WebGL constant. + * The method currently supports the conversion of texture formats + * and types. + * + * @param {number} p - The three.js constant. + * @param {string} [colorSpace=NoColorSpace] - The color space. + * @return {?number} The corresponding WebGL constant. + */ + convert( p, colorSpace = NoColorSpace ) { + + const { gl, extensions } = this; + + let extension; + + const transfer = ColorManagement.getTransfer( colorSpace ); + + if ( p === UnsignedByteType ) return gl.UNSIGNED_BYTE; + if ( p === UnsignedShort4444Type ) return gl.UNSIGNED_SHORT_4_4_4_4; + if ( p === UnsignedShort5551Type ) return gl.UNSIGNED_SHORT_5_5_5_1; + if ( p === UnsignedInt5999Type ) return gl.UNSIGNED_INT_5_9_9_9_REV; + + if ( p === ByteType ) return gl.BYTE; + if ( p === ShortType ) return gl.SHORT; + if ( p === UnsignedShortType ) return gl.UNSIGNED_SHORT; + if ( p === IntType ) return gl.INT; + if ( p === UnsignedIntType ) return gl.UNSIGNED_INT; + if ( p === FloatType ) return gl.FLOAT; + + if ( p === HalfFloatType ) { + + return gl.HALF_FLOAT; + + } + + if ( p === AlphaFormat ) return gl.ALPHA; + if ( p === RGBFormat ) return gl.RGB; + if ( p === RGBAFormat ) return gl.RGBA; + if ( p === DepthFormat ) return gl.DEPTH_COMPONENT; + if ( p === DepthStencilFormat ) return gl.DEPTH_STENCIL; + + // WebGL2 formats. + + if ( p === RedFormat ) return gl.RED; + if ( p === RedIntegerFormat ) return gl.RED_INTEGER; + if ( p === RGFormat ) return gl.RG; + if ( p === RGIntegerFormat ) return gl.RG_INTEGER; + if ( p === RGBAIntegerFormat ) return gl.RGBA_INTEGER; + + // S3TC + + if ( p === RGB_S3TC_DXT1_Format || p === RGBA_S3TC_DXT1_Format || p === RGBA_S3TC_DXT3_Format || p === RGBA_S3TC_DXT5_Format ) { + + if ( transfer === SRGBTransfer ) { + + extension = extensions.get( 'WEBGL_compressed_texture_s3tc_srgb' ); + + if ( extension !== null ) { + + if ( p === RGB_S3TC_DXT1_Format ) return extension.COMPRESSED_SRGB_S3TC_DXT1_EXT; + if ( p === RGBA_S3TC_DXT1_Format ) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT; + if ( p === RGBA_S3TC_DXT3_Format ) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT; + if ( p === RGBA_S3TC_DXT5_Format ) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT; + + } else { + + return null; + + } + + } else { + + extension = extensions.get( 'WEBGL_compressed_texture_s3tc' ); + + if ( extension !== null ) { + + if ( p === RGB_S3TC_DXT1_Format ) return extension.COMPRESSED_RGB_S3TC_DXT1_EXT; + if ( p === RGBA_S3TC_DXT1_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT1_EXT; + if ( p === RGBA_S3TC_DXT3_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT3_EXT; + if ( p === RGBA_S3TC_DXT5_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT5_EXT; + + } else { + + return null; + + } + + } + + } + + // PVRTC + + if ( p === RGB_PVRTC_4BPPV1_Format || p === RGB_PVRTC_2BPPV1_Format || p === RGBA_PVRTC_4BPPV1_Format || p === RGBA_PVRTC_2BPPV1_Format ) { + + extension = extensions.get( 'WEBGL_compressed_texture_pvrtc' ); + + if ( extension !== null ) { + + if ( p === RGB_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_4BPPV1_IMG; + if ( p === RGB_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_2BPPV1_IMG; + if ( p === RGBA_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; + if ( p === RGBA_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG; + + } else { + + return null; + + } + + } + + // ETC + + if ( p === RGB_ETC1_Format || p === RGB_ETC2_Format || p === RGBA_ETC2_EAC_Format ) { + + extension = extensions.get( 'WEBGL_compressed_texture_etc' ); + + if ( extension !== null ) { + + if ( p === RGB_ETC1_Format || p === RGB_ETC2_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ETC2 : extension.COMPRESSED_RGB8_ETC2; + if ( p === RGBA_ETC2_EAC_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ETC2_EAC : extension.COMPRESSED_RGBA8_ETC2_EAC; + + } else { + + return null; + + } + + } + + // ASTC + + if ( p === RGBA_ASTC_4x4_Format || p === RGBA_ASTC_5x4_Format || p === RGBA_ASTC_5x5_Format || + p === RGBA_ASTC_6x5_Format || p === RGBA_ASTC_6x6_Format || p === RGBA_ASTC_8x5_Format || + p === RGBA_ASTC_8x6_Format || p === RGBA_ASTC_8x8_Format || p === RGBA_ASTC_10x5_Format || + p === RGBA_ASTC_10x6_Format || p === RGBA_ASTC_10x8_Format || p === RGBA_ASTC_10x10_Format || + p === RGBA_ASTC_12x10_Format || p === RGBA_ASTC_12x12_Format ) { + + extension = extensions.get( 'WEBGL_compressed_texture_astc' ); + + if ( extension !== null ) { + + if ( p === RGBA_ASTC_4x4_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR : extension.COMPRESSED_RGBA_ASTC_4x4_KHR; + if ( p === RGBA_ASTC_5x4_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR : extension.COMPRESSED_RGBA_ASTC_5x4_KHR; + if ( p === RGBA_ASTC_5x5_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR : extension.COMPRESSED_RGBA_ASTC_5x5_KHR; + if ( p === RGBA_ASTC_6x5_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR : extension.COMPRESSED_RGBA_ASTC_6x5_KHR; + if ( p === RGBA_ASTC_6x6_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR : extension.COMPRESSED_RGBA_ASTC_6x6_KHR; + if ( p === RGBA_ASTC_8x5_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR : extension.COMPRESSED_RGBA_ASTC_8x5_KHR; + if ( p === RGBA_ASTC_8x6_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR : extension.COMPRESSED_RGBA_ASTC_8x6_KHR; + if ( p === RGBA_ASTC_8x8_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR : extension.COMPRESSED_RGBA_ASTC_8x8_KHR; + if ( p === RGBA_ASTC_10x5_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR : extension.COMPRESSED_RGBA_ASTC_10x5_KHR; + if ( p === RGBA_ASTC_10x6_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR : extension.COMPRESSED_RGBA_ASTC_10x6_KHR; + if ( p === RGBA_ASTC_10x8_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR : extension.COMPRESSED_RGBA_ASTC_10x8_KHR; + if ( p === RGBA_ASTC_10x10_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR : extension.COMPRESSED_RGBA_ASTC_10x10_KHR; + if ( p === RGBA_ASTC_12x10_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR : extension.COMPRESSED_RGBA_ASTC_12x10_KHR; + if ( p === RGBA_ASTC_12x12_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR : extension.COMPRESSED_RGBA_ASTC_12x12_KHR; + + } else { + + return null; + + } + + } + + // BPTC + + if ( p === RGBA_BPTC_Format ) { + + extension = extensions.get( 'EXT_texture_compression_bptc' ); + + if ( extension !== null ) { + + if ( p === RGBA_BPTC_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT : extension.COMPRESSED_RGBA_BPTC_UNORM_EXT; + + } else { + + return null; + + } + + } + + // RGTC + + if ( p === RED_RGTC1_Format || p === SIGNED_RED_RGTC1_Format || p === RED_GREEN_RGTC2_Format || p === SIGNED_RED_GREEN_RGTC2_Format ) { + + extension = extensions.get( 'EXT_texture_compression_rgtc' ); + + if ( extension !== null ) { + + if ( p === RGBA_BPTC_Format ) return extension.COMPRESSED_RED_RGTC1_EXT; + if ( p === SIGNED_RED_RGTC1_Format ) return extension.COMPRESSED_SIGNED_RED_RGTC1_EXT; + if ( p === RED_GREEN_RGTC2_Format ) return extension.COMPRESSED_RED_GREEN_RGTC2_EXT; + if ( p === SIGNED_RED_GREEN_RGTC2_Format ) return extension.COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT; + + } else { + + return null; + + } + + } + + // + + if ( p === UnsignedInt248Type ) { + + return gl.UNSIGNED_INT_24_8; + + } + + // if "p" can't be resolved, assume the user defines a WebGL constant as a string (fallback/workaround for packed RGB formats) + + return ( gl[ p ] !== undefined ) ? gl[ p ] : null; + + } + + /** + * This method can be used to synchronize the CPU with the GPU by waiting until + * ongoing GPU commands have been completed. + * + * @private + * @return {Promise} A promise that resolves when all ongoing GPU commands have been completed. + */ + _clientWaitAsync() { + + const { gl } = this; + + const sync = gl.fenceSync( gl.SYNC_GPU_COMMANDS_COMPLETE, 0 ); + + gl.flush(); + + return new Promise( ( resolve, reject ) => { + + function test() { + + const res = gl.clientWaitSync( sync, gl.SYNC_FLUSH_COMMANDS_BIT, 0 ); + + if ( res === gl.WAIT_FAILED ) { + + gl.deleteSync( sync ); + + reject(); + return; + + } + + if ( res === gl.TIMEOUT_EXPIRED ) { + + requestAnimationFrame( test ); + return; + + } + + gl.deleteSync( sync ); + + resolve(); + + } + + test(); + + } ); + + } + +} + +let initialized = false, wrappingToGL, filterToGL, compareToGL; + +/** + * A WebGL 2 backend utility module for managing textures. + * + * @private + */ +class WebGLTextureUtils { + + /** + * Constructs a new utility object. + * + * @param {WebGLBackend} backend - The WebGL 2 backend. + */ + constructor( backend ) { + + /** + * A reference to the WebGL 2 backend. + * + * @type {WebGLBackend} + */ + this.backend = backend; + + /** + * A reference to the rendering context. + * + * @type {WebGL2RenderingContext} + */ + this.gl = backend.gl; + + /** + * A reference to a backend module holding extension-related + * utility functions. + * + * @type {WebGLExtensions} + */ + this.extensions = backend.extensions; + + /** + * A dictionary for managing default textures. The key + * is the binding point (target), the value the WEbGL texture object. + * + * @type {Object} + */ + this.defaultTextures = {}; + + if ( initialized === false ) { + + this._init(); + + initialized = true; + + } + + } + + /** + * Inits the state of the utility. + * + * @private + */ + _init() { + + const gl = this.gl; + + // Store only WebGL constants here. + + wrappingToGL = { + [ RepeatWrapping ]: gl.REPEAT, + [ ClampToEdgeWrapping ]: gl.CLAMP_TO_EDGE, + [ MirroredRepeatWrapping ]: gl.MIRRORED_REPEAT + }; + + filterToGL = { + [ NearestFilter ]: gl.NEAREST, + [ NearestMipmapNearestFilter ]: gl.NEAREST_MIPMAP_NEAREST, + [ NearestMipmapLinearFilter ]: gl.NEAREST_MIPMAP_LINEAR, + + [ LinearFilter ]: gl.LINEAR, + [ LinearMipmapNearestFilter ]: gl.LINEAR_MIPMAP_NEAREST, + [ LinearMipmapLinearFilter ]: gl.LINEAR_MIPMAP_LINEAR + }; + + compareToGL = { + [ NeverCompare ]: gl.NEVER, + [ AlwaysCompare ]: gl.ALWAYS, + [ LessCompare ]: gl.LESS, + [ LessEqualCompare ]: gl.LEQUAL, + [ EqualCompare ]: gl.EQUAL, + [ GreaterEqualCompare ]: gl.GEQUAL, + [ GreaterCompare ]: gl.GREATER, + [ NotEqualCompare ]: gl.NOTEQUAL + }; + + } + + /** + * Returns the native texture type for the given texture. + * + * @param {Texture} texture - The texture. + * @return {GLenum} The native texture type. + */ + getGLTextureType( texture ) { + + const { gl } = this; + + let glTextureType; + + if ( texture.isCubeTexture === true ) { + + glTextureType = gl.TEXTURE_CUBE_MAP; + + } else if ( texture.isArrayTexture === true || texture.isDataArrayTexture === true || texture.isCompressedArrayTexture === true ) { + + glTextureType = gl.TEXTURE_2D_ARRAY; + + } else if ( texture.isData3DTexture === true ) { // TODO: isCompressed3DTexture, wait for #26642 + + glTextureType = gl.TEXTURE_3D; + + } else { + + glTextureType = gl.TEXTURE_2D; + + + } + + return glTextureType; + + } + + /** + * Returns the native texture type for the given texture. + * + * @param {?string} internalFormatName - The internal format name. When `null`, the internal format is derived from the subsequent parameters. + * @param {GLenum} glFormat - The WebGL format. + * @param {GLenum} glType - The WebGL type. + * @param {string} colorSpace - The texture's color space. + * @param {boolean} [forceLinearTransfer=false] - Whether to force a linear transfer or not. + * @return {GLenum} The internal format. + */ + getInternalFormat( internalFormatName, glFormat, glType, colorSpace, forceLinearTransfer = false ) { + + const { gl, extensions } = this; + + if ( internalFormatName !== null ) { + + if ( gl[ internalFormatName ] !== undefined ) return gl[ internalFormatName ]; + + console.warn( 'THREE.WebGLRenderer: Attempt to use non-existing WebGL internal format \'' + internalFormatName + '\'' ); + + } + + let internalFormat = glFormat; + + if ( glFormat === gl.RED ) { + + if ( glType === gl.FLOAT ) internalFormat = gl.R32F; + if ( glType === gl.HALF_FLOAT ) internalFormat = gl.R16F; + if ( glType === gl.UNSIGNED_BYTE ) internalFormat = gl.R8; + if ( glType === gl.UNSIGNED_SHORT ) internalFormat = gl.R16; + if ( glType === gl.UNSIGNED_INT ) internalFormat = gl.R32UI; + if ( glType === gl.BYTE ) internalFormat = gl.R8I; + if ( glType === gl.SHORT ) internalFormat = gl.R16I; + if ( glType === gl.INT ) internalFormat = gl.R32I; + + } + + if ( glFormat === gl.RED_INTEGER ) { + + if ( glType === gl.UNSIGNED_BYTE ) internalFormat = gl.R8UI; + if ( glType === gl.UNSIGNED_SHORT ) internalFormat = gl.R16UI; + if ( glType === gl.UNSIGNED_INT ) internalFormat = gl.R32UI; + if ( glType === gl.BYTE ) internalFormat = gl.R8I; + if ( glType === gl.SHORT ) internalFormat = gl.R16I; + if ( glType === gl.INT ) internalFormat = gl.R32I; + + } + + if ( glFormat === gl.RG ) { + + if ( glType === gl.FLOAT ) internalFormat = gl.RG32F; + if ( glType === gl.HALF_FLOAT ) internalFormat = gl.RG16F; + if ( glType === gl.UNSIGNED_BYTE ) internalFormat = gl.RG8; + if ( glType === gl.UNSIGNED_SHORT ) internalFormat = gl.RG16; + if ( glType === gl.UNSIGNED_INT ) internalFormat = gl.RG32UI; + if ( glType === gl.BYTE ) internalFormat = gl.RG8I; + if ( glType === gl.SHORT ) internalFormat = gl.RG16I; + if ( glType === gl.INT ) internalFormat = gl.RG32I; + + } + + if ( glFormat === gl.RG_INTEGER ) { + + if ( glType === gl.UNSIGNED_BYTE ) internalFormat = gl.RG8UI; + if ( glType === gl.UNSIGNED_SHORT ) internalFormat = gl.RG16UI; + if ( glType === gl.UNSIGNED_INT ) internalFormat = gl.RG32UI; + if ( glType === gl.BYTE ) internalFormat = gl.RG8I; + if ( glType === gl.SHORT ) internalFormat = gl.RG16I; + if ( glType === gl.INT ) internalFormat = gl.RG32I; + + } + + if ( glFormat === gl.RGB ) { + + const transfer = forceLinearTransfer ? LinearTransfer : ColorManagement.getTransfer( colorSpace ); + + if ( glType === gl.FLOAT ) internalFormat = gl.RGB32F; + if ( glType === gl.HALF_FLOAT ) internalFormat = gl.RGB16F; + if ( glType === gl.UNSIGNED_BYTE ) internalFormat = gl.RGB8; + if ( glType === gl.UNSIGNED_SHORT ) internalFormat = gl.RGB16; + if ( glType === gl.UNSIGNED_INT ) internalFormat = gl.RGB32UI; + if ( glType === gl.BYTE ) internalFormat = gl.RGB8I; + if ( glType === gl.SHORT ) internalFormat = gl.RGB16I; + if ( glType === gl.INT ) internalFormat = gl.RGB32I; + if ( glType === gl.UNSIGNED_BYTE ) internalFormat = ( transfer === SRGBTransfer ) ? gl.SRGB8 : gl.RGB8; + if ( glType === gl.UNSIGNED_SHORT_5_6_5 ) internalFormat = gl.RGB565; + if ( glType === gl.UNSIGNED_SHORT_5_5_5_1 ) internalFormat = gl.RGB5_A1; + if ( glType === gl.UNSIGNED_SHORT_4_4_4_4 ) internalFormat = gl.RGB4; + if ( glType === gl.UNSIGNED_INT_5_9_9_9_REV ) internalFormat = gl.RGB9_E5; + + } + + if ( glFormat === gl.RGB_INTEGER ) { + + if ( glType === gl.UNSIGNED_BYTE ) internalFormat = gl.RGB8UI; + if ( glType === gl.UNSIGNED_SHORT ) internalFormat = gl.RGB16UI; + if ( glType === gl.UNSIGNED_INT ) internalFormat = gl.RGB32UI; + if ( glType === gl.BYTE ) internalFormat = gl.RGB8I; + if ( glType === gl.SHORT ) internalFormat = gl.RGB16I; + if ( glType === gl.INT ) internalFormat = gl.RGB32I; + + } + + if ( glFormat === gl.RGBA ) { + + const transfer = forceLinearTransfer ? LinearTransfer : ColorManagement.getTransfer( colorSpace ); + + if ( glType === gl.FLOAT ) internalFormat = gl.RGBA32F; + if ( glType === gl.HALF_FLOAT ) internalFormat = gl.RGBA16F; + if ( glType === gl.UNSIGNED_BYTE ) internalFormat = gl.RGBA8; + if ( glType === gl.UNSIGNED_SHORT ) internalFormat = gl.RGBA16; + if ( glType === gl.UNSIGNED_INT ) internalFormat = gl.RGBA32UI; + if ( glType === gl.BYTE ) internalFormat = gl.RGBA8I; + if ( glType === gl.SHORT ) internalFormat = gl.RGBA16I; + if ( glType === gl.INT ) internalFormat = gl.RGBA32I; + if ( glType === gl.UNSIGNED_BYTE ) internalFormat = ( transfer === SRGBTransfer ) ? gl.SRGB8_ALPHA8 : gl.RGBA8; + if ( glType === gl.UNSIGNED_SHORT_4_4_4_4 ) internalFormat = gl.RGBA4; + if ( glType === gl.UNSIGNED_SHORT_5_5_5_1 ) internalFormat = gl.RGB5_A1; + + } + + if ( glFormat === gl.RGBA_INTEGER ) { + + if ( glType === gl.UNSIGNED_BYTE ) internalFormat = gl.RGBA8UI; + if ( glType === gl.UNSIGNED_SHORT ) internalFormat = gl.RGBA16UI; + if ( glType === gl.UNSIGNED_INT ) internalFormat = gl.RGBA32UI; + if ( glType === gl.BYTE ) internalFormat = gl.RGBA8I; + if ( glType === gl.SHORT ) internalFormat = gl.RGBA16I; + if ( glType === gl.INT ) internalFormat = gl.RGBA32I; + + } + + if ( glFormat === gl.DEPTH_COMPONENT ) { + + if ( glType === gl.UNSIGNED_SHORT ) internalFormat = gl.DEPTH_COMPONENT16; + if ( glType === gl.UNSIGNED_INT ) internalFormat = gl.DEPTH_COMPONENT24; + if ( glType === gl.FLOAT ) internalFormat = gl.DEPTH_COMPONENT32F; + + } + + if ( glFormat === gl.DEPTH_STENCIL ) { + + if ( glType === gl.UNSIGNED_INT_24_8 ) internalFormat = gl.DEPTH24_STENCIL8; + + } + + if ( internalFormat === gl.R16F || internalFormat === gl.R32F || + internalFormat === gl.RG16F || internalFormat === gl.RG32F || + internalFormat === gl.RGBA16F || internalFormat === gl.RGBA32F ) { + + extensions.get( 'EXT_color_buffer_float' ); + + } + + return internalFormat; + + } + + /** + * Sets the texture parameters for the given texture. + * + * @param {GLenum} textureType - The texture type. + * @param {Texture} texture - The texture. + */ + setTextureParameters( textureType, texture ) { + + const { gl, extensions, backend } = this; + + const workingPrimaries = ColorManagement.getPrimaries( ColorManagement.workingColorSpace ); + const texturePrimaries = texture.colorSpace === NoColorSpace ? null : ColorManagement.getPrimaries( texture.colorSpace ); + const unpackConversion = texture.colorSpace === NoColorSpace || workingPrimaries === texturePrimaries ? gl.NONE : gl.BROWSER_DEFAULT_WEBGL; + + gl.pixelStorei( gl.UNPACK_FLIP_Y_WEBGL, texture.flipY ); + gl.pixelStorei( gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha ); + gl.pixelStorei( gl.UNPACK_ALIGNMENT, texture.unpackAlignment ); + gl.pixelStorei( gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, unpackConversion ); + + gl.texParameteri( textureType, gl.TEXTURE_WRAP_S, wrappingToGL[ texture.wrapS ] ); + gl.texParameteri( textureType, gl.TEXTURE_WRAP_T, wrappingToGL[ texture.wrapT ] ); + + if ( textureType === gl.TEXTURE_3D || textureType === gl.TEXTURE_2D_ARRAY ) { + + // WebGL 2 does not support wrapping for depth 2D array textures + if ( ! texture.isArrayTexture ) { + + gl.texParameteri( textureType, gl.TEXTURE_WRAP_R, wrappingToGL[ texture.wrapR ] ); + + } + + } + + gl.texParameteri( textureType, gl.TEXTURE_MAG_FILTER, filterToGL[ texture.magFilter ] ); + + + const hasMipmaps = texture.mipmaps !== undefined && texture.mipmaps.length > 0; + + // follow WebGPU backend mapping for texture filtering + const minFilter = texture.minFilter === LinearFilter && hasMipmaps ? LinearMipmapLinearFilter : texture.minFilter; + + gl.texParameteri( textureType, gl.TEXTURE_MIN_FILTER, filterToGL[ minFilter ] ); + + if ( texture.compareFunction ) { + + gl.texParameteri( textureType, gl.TEXTURE_COMPARE_MODE, gl.COMPARE_REF_TO_TEXTURE ); + gl.texParameteri( textureType, gl.TEXTURE_COMPARE_FUNC, compareToGL[ texture.compareFunction ] ); + + } + + if ( extensions.has( 'EXT_texture_filter_anisotropic' ) === true ) { + + if ( texture.magFilter === NearestFilter ) return; + if ( texture.minFilter !== NearestMipmapLinearFilter && texture.minFilter !== LinearMipmapLinearFilter ) return; + if ( texture.type === FloatType && extensions.has( 'OES_texture_float_linear' ) === false ) return; // verify extension for WebGL 1 and WebGL 2 + + if ( texture.anisotropy > 1 ) { + + const extension = extensions.get( 'EXT_texture_filter_anisotropic' ); + gl.texParameterf( textureType, extension.TEXTURE_MAX_ANISOTROPY_EXT, Math.min( texture.anisotropy, backend.getMaxAnisotropy() ) ); + + } + + } + + } + + /** + * Creates a default texture for the given texture that can be used + * as a placeholder until the actual texture is ready for usage. + * + * @param {Texture} texture - The texture to create a default texture for. + */ + createDefaultTexture( texture ) { + + const { gl, backend, defaultTextures } = this; + + + const glTextureType = this.getGLTextureType( texture ); + + let textureGPU = defaultTextures[ glTextureType ]; + + if ( textureGPU === undefined ) { + + textureGPU = gl.createTexture(); + + backend.state.bindTexture( glTextureType, textureGPU ); + gl.texParameteri( glTextureType, gl.TEXTURE_MIN_FILTER, gl.NEAREST ); + gl.texParameteri( glTextureType, gl.TEXTURE_MAG_FILTER, gl.NEAREST ); + + // gl.texImage2D( glTextureType, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, data ); + + defaultTextures[ glTextureType ] = textureGPU; + + } + + backend.set( texture, { + textureGPU, + glTextureType, + isDefault: true + } ); + + } + + /** + * Defines a texture on the GPU for the given texture object. + * + * @param {Texture} texture - The texture. + * @param {Object} [options={}] - Optional configuration parameter. + * @return {undefined} + */ + createTexture( texture, options ) { + + const { gl, backend } = this; + const { levels, width, height, depth } = options; + + const glFormat = backend.utils.convert( texture.format, texture.colorSpace ); + const glType = backend.utils.convert( texture.type ); + const glInternalFormat = this.getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace, texture.isVideoTexture ); + + const textureGPU = gl.createTexture(); + const glTextureType = this.getGLTextureType( texture ); + + backend.state.bindTexture( glTextureType, textureGPU ); + + this.setTextureParameters( glTextureType, texture ); + + if ( texture.isArrayTexture || texture.isDataArrayTexture || texture.isCompressedArrayTexture ) { + + gl.texStorage3D( gl.TEXTURE_2D_ARRAY, levels, glInternalFormat, width, height, depth ); + + } else if ( texture.isData3DTexture ) { + + gl.texStorage3D( gl.TEXTURE_3D, levels, glInternalFormat, width, height, depth ); + + } else if ( ! texture.isVideoTexture ) { + + gl.texStorage2D( glTextureType, levels, glInternalFormat, width, height ); + + } + + backend.set( texture, { + textureGPU, + glTextureType, + glFormat, + glType, + glInternalFormat + } ); + + } + + /** + * Uploads texture buffer data to the GPU memory. + * + * @param {WebGLBuffer} buffer - The buffer data. + * @param {Texture} texture - The texture, + */ + copyBufferToTexture( buffer, texture ) { + + const { gl, backend } = this; + + const { textureGPU, glTextureType, glFormat, glType } = backend.get( texture ); + + const { width, height } = texture.source.data; + + gl.bindBuffer( gl.PIXEL_UNPACK_BUFFER, buffer ); + + backend.state.bindTexture( glTextureType, textureGPU ); + + gl.pixelStorei( gl.UNPACK_FLIP_Y_WEBGL, false ); + gl.pixelStorei( gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false ); + gl.texSubImage2D( glTextureType, 0, 0, 0, width, height, glFormat, glType, 0 ); + + gl.bindBuffer( gl.PIXEL_UNPACK_BUFFER, null ); + + backend.state.unbindTexture(); + // debug + // const framebuffer = gl.createFramebuffer(); + // gl.bindFramebuffer( gl.FRAMEBUFFER, framebuffer ); + // gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, glTextureType, textureGPU, 0 ); + + // const readout = new Float32Array( width * height * 4 ); + + // const altFormat = gl.getParameter( gl.IMPLEMENTATION_COLOR_READ_FORMAT ); + // const altType = gl.getParameter( gl.IMPLEMENTATION_COLOR_READ_TYPE ); + + // gl.readPixels( 0, 0, width, height, altFormat, altType, readout ); + // gl.bindFramebuffer( gl.FRAMEBUFFER, null ); + // console.log( readout ); + + } + + /** + * Uploads the updated texture data to the GPU. + * + * @param {Texture} texture - The texture. + * @param {Object} [options={}] - Optional configuration parameter. + */ + updateTexture( texture, options ) { + + const { gl } = this; + const { width, height } = options; + const { textureGPU, glTextureType, glFormat, glType, glInternalFormat } = this.backend.get( texture ); + + if ( texture.isRenderTargetTexture || ( textureGPU === undefined /* unsupported texture format */ ) ) + return; + + this.backend.state.bindTexture( glTextureType, textureGPU ); + + this.setTextureParameters( glTextureType, texture ); + + if ( texture.isCompressedTexture ) { + + const mipmaps = texture.mipmaps; + const image = options.image; + + for ( let i = 0; i < mipmaps.length; i ++ ) { + + const mipmap = mipmaps[ i ]; + + if ( texture.isCompressedArrayTexture ) { + + + if ( texture.format !== gl.RGBA ) { + + if ( glFormat !== null ) { + + gl.compressedTexSubImage3D( gl.TEXTURE_2D_ARRAY, i, 0, 0, 0, mipmap.width, mipmap.height, image.depth, glFormat, mipmap.data ); + + } else { + + console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()' ); + + } + + } else { + + gl.texSubImage3D( gl.TEXTURE_2D_ARRAY, i, 0, 0, 0, mipmap.width, mipmap.height, image.depth, glFormat, glType, mipmap.data ); + + } + + } else { + + if ( glFormat !== null ) { + + gl.compressedTexSubImage2D( gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, mipmap.data ); + + } else { + + console.warn( 'Unsupported compressed texture format' ); + + } + + } + + } + + + } else if ( texture.isCubeTexture ) { + + const images = options.images; + + for ( let i = 0; i < 6; i ++ ) { + + const image = getImage( images[ i ] ); + + gl.texSubImage2D( gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, 0, 0, width, height, glFormat, glType, image ); + + } + + } else if ( texture.isDataArrayTexture || texture.isArrayTexture ) { + + const image = options.image; + + gl.texSubImage3D( gl.TEXTURE_2D_ARRAY, 0, 0, 0, 0, image.width, image.height, image.depth, glFormat, glType, image.data ); + + } else if ( texture.isData3DTexture ) { + + const image = options.image; + + gl.texSubImage3D( gl.TEXTURE_3D, 0, 0, 0, 0, image.width, image.height, image.depth, glFormat, glType, image.data ); + + } else if ( texture.isVideoTexture ) { + + texture.update(); + + gl.texImage2D( glTextureType, 0, glInternalFormat, glFormat, glType, options.image ); + + + } else { + + const image = getImage( options.image ); + + gl.texSubImage2D( glTextureType, 0, 0, 0, width, height, glFormat, glType, image ); + + } + + } + + /** + * Generates mipmaps for the given texture. + * + * @param {Texture} texture - The texture. + */ + generateMipmaps( texture ) { + + const { gl, backend } = this; + const { textureGPU, glTextureType } = backend.get( texture ); + + backend.state.bindTexture( glTextureType, textureGPU ); + gl.generateMipmap( glTextureType ); + + } + + /** + * Deallocates the render buffers of the given render target. + * + * @param {RenderTarget} renderTarget - The render target. + */ + deallocateRenderBuffers( renderTarget ) { + + const { gl, backend } = this; + + // remove framebuffer reference + if ( renderTarget ) { + + const renderContextData = backend.get( renderTarget ); + + renderContextData.renderBufferStorageSetup = undefined; + + if ( renderContextData.framebuffers ) { + + for ( const cacheKey in renderContextData.framebuffers ) { + + gl.deleteFramebuffer( renderContextData.framebuffers[ cacheKey ] ); + + } + + delete renderContextData.framebuffers; + + } + + if ( renderContextData.depthRenderbuffer ) { + + gl.deleteRenderbuffer( renderContextData.depthRenderbuffer ); + delete renderContextData.depthRenderbuffer; + + } + + if ( renderContextData.stencilRenderbuffer ) { + + gl.deleteRenderbuffer( renderContextData.stencilRenderbuffer ); + delete renderContextData.stencilRenderbuffer; + + } + + if ( renderContextData.msaaFrameBuffer ) { + + gl.deleteFramebuffer( renderContextData.msaaFrameBuffer ); + delete renderContextData.msaaFrameBuffer; + + } + + if ( renderContextData.msaaRenderbuffers ) { + + for ( let i = 0; i < renderContextData.msaaRenderbuffers.length; i ++ ) { + + gl.deleteRenderbuffer( renderContextData.msaaRenderbuffers[ i ] ); + + } + + delete renderContextData.msaaRenderbuffers; + + } + + } + + } + + /** + * Destroys the GPU data for the given texture object. + * + * @param {Texture} texture - The texture. + */ + destroyTexture( texture ) { + + const { gl, backend } = this; + const { textureGPU, renderTarget } = backend.get( texture ); + + this.deallocateRenderBuffers( renderTarget ); + gl.deleteTexture( textureGPU ); + + backend.delete( texture ); + + } + + /** + * Copies data of the given source texture to the given destination texture. + * + * @param {Texture} srcTexture - The source texture. + * @param {Texture} dstTexture - The destination texture. + * @param {?(Box3|Box2)} [srcRegion=null] - The region of the source texture to copy. + * @param {?(Vector2|Vector3)} [dstPosition=null] - The destination position of the copy. + * @param {number} [srcLevel=0] - The source mip level to copy from. + * @param {number} [dstLevel=0] - The destination mip level to copy to. + */ + copyTextureToTexture( srcTexture, dstTexture, srcRegion = null, dstPosition = null, srcLevel = 0, dstLevel = 0 ) { + + const { gl, backend } = this; + const { state } = this.backend; + + const { textureGPU: dstTextureGPU, glTextureType, glType, glFormat } = backend.get( dstTexture ); + + state.bindTexture( glTextureType, dstTextureGPU ); + + // gather the necessary dimensions to copy + let width, height, depth, minX, minY, minZ; + let dstX, dstY, dstZ; + const image = srcTexture.isCompressedTexture ? srcTexture.mipmaps[ dstLevel ] : srcTexture.image; + + if ( srcRegion !== null ) { + + width = srcRegion.max.x - srcRegion.min.x; + height = srcRegion.max.y - srcRegion.min.y; + depth = srcRegion.isBox3 ? srcRegion.max.z - srcRegion.min.z : 1; + minX = srcRegion.min.x; + minY = srcRegion.min.y; + minZ = srcRegion.isBox3 ? srcRegion.min.z : 0; + + } else { + + const levelScale = Math.pow( 2, - srcLevel ); + width = Math.floor( image.width * levelScale ); + height = Math.floor( image.height * levelScale ); + + if ( srcTexture.isDataArrayTexture || srcTexture.isArrayTexture ) { + + depth = image.depth; + + } else if ( srcTexture.isData3DTexture ) { + + depth = Math.floor( image.depth * levelScale ); + + } else { + + depth = 1; + + } + + minX = 0; + minY = 0; + minZ = 0; + + } + + if ( dstPosition !== null ) { + + dstX = dstPosition.x; + dstY = dstPosition.y; + dstZ = dstPosition.z; + + } else { + + dstX = 0; + dstY = 0; + dstZ = 0; + + } + + + gl.pixelStorei( gl.UNPACK_FLIP_Y_WEBGL, dstTexture.flipY ); + gl.pixelStorei( gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, dstTexture.premultiplyAlpha ); + gl.pixelStorei( gl.UNPACK_ALIGNMENT, dstTexture.unpackAlignment ); + + // used for copying data from cpu + const currentUnpackRowLen = gl.getParameter( gl.UNPACK_ROW_LENGTH ); + const currentUnpackImageHeight = gl.getParameter( gl.UNPACK_IMAGE_HEIGHT ); + const currentUnpackSkipPixels = gl.getParameter( gl.UNPACK_SKIP_PIXELS ); + const currentUnpackSkipRows = gl.getParameter( gl.UNPACK_SKIP_ROWS ); + const currentUnpackSkipImages = gl.getParameter( gl.UNPACK_SKIP_IMAGES ); + + gl.pixelStorei( gl.UNPACK_ROW_LENGTH, image.width ); + gl.pixelStorei( gl.UNPACK_IMAGE_HEIGHT, image.height ); + gl.pixelStorei( gl.UNPACK_SKIP_PIXELS, minX ); + gl.pixelStorei( gl.UNPACK_SKIP_ROWS, minY ); + gl.pixelStorei( gl.UNPACK_SKIP_IMAGES, minZ ); + + // set up the src texture + const isDst3D = dstTexture.isDataArrayTexture || dstTexture.isData3DTexture || dstTexture.isArrayTexture; + if ( srcTexture.isRenderTargetTexture || srcTexture.isDepthTexture ) { + + const srcTextureData = backend.get( srcTexture ); + const dstTextureData = backend.get( dstTexture ); + + const srcRenderContextData = backend.get( srcTextureData.renderTarget ); + const dstRenderContextData = backend.get( dstTextureData.renderTarget ); + + const srcFramebuffer = srcRenderContextData.framebuffers[ srcTextureData.cacheKey ]; + const dstFramebuffer = dstRenderContextData.framebuffers[ dstTextureData.cacheKey ]; + + state.bindFramebuffer( gl.READ_FRAMEBUFFER, srcFramebuffer ); + state.bindFramebuffer( gl.DRAW_FRAMEBUFFER, dstFramebuffer ); + + let mask = gl.COLOR_BUFFER_BIT; + + if ( srcTexture.isDepthTexture ) mask = gl.DEPTH_BUFFER_BIT; + + gl.blitFramebuffer( minX, minY, width, height, dstX, dstY, width, height, mask, gl.NEAREST ); + + state.bindFramebuffer( gl.READ_FRAMEBUFFER, null ); + state.bindFramebuffer( gl.DRAW_FRAMEBUFFER, null ); + + } else { + + if ( isDst3D ) { + + // copy data into the 3d texture + if ( srcTexture.isDataTexture || srcTexture.isData3DTexture ) { + + gl.texSubImage3D( glTextureType, dstLevel, dstX, dstY, dstZ, width, height, depth, glFormat, glType, image.data ); + + } else if ( dstTexture.isCompressedArrayTexture ) { + + gl.compressedTexSubImage3D( glTextureType, dstLevel, dstX, dstY, dstZ, width, height, depth, glFormat, image.data ); + + } else { + + gl.texSubImage3D( glTextureType, dstLevel, dstX, dstY, dstZ, width, height, depth, glFormat, glType, image ); + + } + + } else { + + // copy data into the 2d texture + if ( srcTexture.isDataTexture ) { + + gl.texSubImage2D( glTextureType, dstLevel, dstX, dstY, width, height, glFormat, glType, image.data ); + + } else if ( srcTexture.isCompressedTexture ) { + + gl.compressedTexSubImage2D( glTextureType, dstLevel, dstX, dstY, image.width, image.height, glFormat, image.data ); + + } else { + + gl.texSubImage2D( glTextureType, dstLevel, dstX, dstY, width, height, glFormat, glType, image ); + + } + + } + + } + + // reset values + gl.pixelStorei( gl.UNPACK_ROW_LENGTH, currentUnpackRowLen ); + gl.pixelStorei( gl.UNPACK_IMAGE_HEIGHT, currentUnpackImageHeight ); + gl.pixelStorei( gl.UNPACK_SKIP_PIXELS, currentUnpackSkipPixels ); + gl.pixelStorei( gl.UNPACK_SKIP_ROWS, currentUnpackSkipRows ); + gl.pixelStorei( gl.UNPACK_SKIP_IMAGES, currentUnpackSkipImages ); + + // Generate mipmaps only when copying level 0 + if ( dstLevel === 0 && dstTexture.generateMipmaps ) { + + gl.generateMipmap( glTextureType ); + + } + + state.unbindTexture(); + + } + + + /** + * Copies the current bound framebuffer to the given texture. + * + * @param {Texture} texture - The destination texture. + * @param {RenderContext} renderContext - The render context. + * @param {Vector4} rectangle - A four dimensional vector defining the origin and dimension of the copy. + */ + copyFramebufferToTexture( texture, renderContext, rectangle ) { + + const { gl } = this; + const { state } = this.backend; + + const { textureGPU } = this.backend.get( texture ); + + const { x, y, z: width, w: height } = rectangle; + + const requireDrawFrameBuffer = texture.isDepthTexture === true || ( renderContext.renderTarget && renderContext.renderTarget.samples > 0 ); + + const srcHeight = renderContext.renderTarget ? renderContext.renderTarget.height : this.backend.getDrawingBufferSize().y; + + if ( requireDrawFrameBuffer ) { + + const partial = ( x !== 0 || y !== 0 ); + let mask; + let attachment; + + if ( texture.isDepthTexture === true ) { + + mask = gl.DEPTH_BUFFER_BIT; + attachment = gl.DEPTH_ATTACHMENT; + + if ( renderContext.stencil ) { + + mask |= gl.STENCIL_BUFFER_BIT; + + } + + } else { + + mask = gl.COLOR_BUFFER_BIT; + attachment = gl.COLOR_ATTACHMENT0; + + } + + if ( partial ) { + + const renderTargetContextData = this.backend.get( renderContext.renderTarget ); + + const fb = renderTargetContextData.framebuffers[ renderContext.getCacheKey() ]; + const msaaFrameBuffer = renderTargetContextData.msaaFrameBuffer; + + state.bindFramebuffer( gl.DRAW_FRAMEBUFFER, fb ); + state.bindFramebuffer( gl.READ_FRAMEBUFFER, msaaFrameBuffer ); + + const flippedY = srcHeight - y - height; + + gl.blitFramebuffer( x, flippedY, x + width, flippedY + height, x, flippedY, x + width, flippedY + height, mask, gl.NEAREST ); + + state.bindFramebuffer( gl.READ_FRAMEBUFFER, fb ); + + state.bindTexture( gl.TEXTURE_2D, textureGPU ); + + gl.copyTexSubImage2D( gl.TEXTURE_2D, 0, 0, 0, x, flippedY, width, height ); + + state.unbindTexture(); + + } else { + + const fb = gl.createFramebuffer(); + + state.bindFramebuffer( gl.DRAW_FRAMEBUFFER, fb ); + + gl.framebufferTexture2D( gl.DRAW_FRAMEBUFFER, attachment, gl.TEXTURE_2D, textureGPU, 0 ); + gl.blitFramebuffer( 0, 0, width, height, 0, 0, width, height, mask, gl.NEAREST ); + + gl.deleteFramebuffer( fb ); + + } + + } else { + + state.bindTexture( gl.TEXTURE_2D, textureGPU ); + gl.copyTexSubImage2D( gl.TEXTURE_2D, 0, 0, 0, x, srcHeight - height - y, width, height ); + + state.unbindTexture(); + + } + + if ( texture.generateMipmaps ) this.generateMipmaps( texture ); + + this.backend._setFramebuffer( renderContext ); + + } + + /** + * SetupS storage for internal depth/stencil buffers and bind to correct framebuffer. + * + * @param {WebGLRenderbuffer} renderbuffer - The render buffer. + * @param {RenderContext} renderContext - The render context. + * @param {number} samples - The MSAA sample count. + * @param {boolean} [useMultisampledRTT=false] - Whether to use WEBGL_multisampled_render_to_texture or not. + */ + setupRenderBufferStorage( renderbuffer, renderContext, samples, useMultisampledRTT = false ) { + + const { gl } = this; + const renderTarget = renderContext.renderTarget; + + const { depthTexture, depthBuffer, stencilBuffer, width, height } = renderTarget; + + gl.bindRenderbuffer( gl.RENDERBUFFER, renderbuffer ); + + if ( depthBuffer && ! stencilBuffer ) { + + let glInternalFormat = gl.DEPTH_COMPONENT24; + + if ( useMultisampledRTT === true ) { + + const multisampledRTTExt = this.extensions.get( 'WEBGL_multisampled_render_to_texture' ); + + multisampledRTTExt.renderbufferStorageMultisampleEXT( gl.RENDERBUFFER, renderTarget.samples, glInternalFormat, width, height ); + + } else if ( samples > 0 ) { + + if ( depthTexture && depthTexture.isDepthTexture ) { + + if ( depthTexture.type === gl.FLOAT ) { + + glInternalFormat = gl.DEPTH_COMPONENT32F; + + } + + } + + gl.renderbufferStorageMultisample( gl.RENDERBUFFER, samples, glInternalFormat, width, height ); + + } else { + + gl.renderbufferStorage( gl.RENDERBUFFER, glInternalFormat, width, height ); + + } + + gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, renderbuffer ); + + } else if ( depthBuffer && stencilBuffer ) { + + if ( samples > 0 ) { + + gl.renderbufferStorageMultisample( gl.RENDERBUFFER, samples, gl.DEPTH24_STENCIL8, width, height ); + + } else { + + gl.renderbufferStorage( gl.RENDERBUFFER, gl.DEPTH_STENCIL, width, height ); + + } + + + gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, renderbuffer ); + + } + + gl.bindRenderbuffer( gl.RENDERBUFFER, null ); + + } + + /** + * Returns texture data as a typed array. + * + * @async + * @param {Texture} texture - The texture to copy. + * @param {number} x - The x coordinate of the copy origin. + * @param {number} y - The y coordinate of the copy origin. + * @param {number} width - The width of the copy. + * @param {number} height - The height of the copy. + * @param {number} faceIndex - The face index. + * @return {Promise} A Promise that resolves with a typed array when the copy operation has finished. + */ + async copyTextureToBuffer( texture, x, y, width, height, faceIndex ) { + + const { backend, gl } = this; + + const { textureGPU, glFormat, glType } = this.backend.get( texture ); + + const fb = gl.createFramebuffer(); + + gl.bindFramebuffer( gl.READ_FRAMEBUFFER, fb ); + + const target = texture.isCubeTexture ? gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex : gl.TEXTURE_2D; + + gl.framebufferTexture2D( gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, target, textureGPU, 0 ); + + const typedArrayType = this._getTypedArrayType( glType ); + const bytesPerTexel = this._getBytesPerTexel( glType, glFormat ); + + const elementCount = width * height; + const byteLength = elementCount * bytesPerTexel; + + const buffer = gl.createBuffer(); + + gl.bindBuffer( gl.PIXEL_PACK_BUFFER, buffer ); + gl.bufferData( gl.PIXEL_PACK_BUFFER, byteLength, gl.STREAM_READ ); + gl.readPixels( x, y, width, height, glFormat, glType, 0 ); + gl.bindBuffer( gl.PIXEL_PACK_BUFFER, null ); + + await backend.utils._clientWaitAsync(); + + const dstBuffer = new typedArrayType( byteLength / typedArrayType.BYTES_PER_ELEMENT ); + + gl.bindBuffer( gl.PIXEL_PACK_BUFFER, buffer ); + gl.getBufferSubData( gl.PIXEL_PACK_BUFFER, 0, dstBuffer ); + gl.bindBuffer( gl.PIXEL_PACK_BUFFER, null ); + + gl.deleteFramebuffer( fb ); + + return dstBuffer; + + } + + /** + * Returns the corresponding typed array type for the given WebGL data type. + * + * @private + * @param {GLenum} glType - The WebGL data type. + * @return {TypedArray.constructor} The typed array type. + */ + _getTypedArrayType( glType ) { + + const { gl } = this; + + if ( glType === gl.UNSIGNED_BYTE ) return Uint8Array; + + if ( glType === gl.UNSIGNED_SHORT_4_4_4_4 ) return Uint16Array; + if ( glType === gl.UNSIGNED_SHORT_5_5_5_1 ) return Uint16Array; + if ( glType === gl.UNSIGNED_SHORT_5_6_5 ) return Uint16Array; + if ( glType === gl.UNSIGNED_SHORT ) return Uint16Array; + if ( glType === gl.UNSIGNED_INT ) return Uint32Array; + + if ( glType === gl.HALF_FLOAT ) return Uint16Array; + if ( glType === gl.FLOAT ) return Float32Array; + + throw new Error( `Unsupported WebGL type: ${glType}` ); + + } + + /** + * Returns the bytes-per-texel value for the given WebGL data type and texture format. + * + * @private + * @param {GLenum} glType - The WebGL data type. + * @param {GLenum} glFormat - The WebGL texture format. + * @return {number} The bytes-per-texel. + */ + _getBytesPerTexel( glType, glFormat ) { + + const { gl } = this; + + let bytesPerComponent = 0; + + if ( glType === gl.UNSIGNED_BYTE ) bytesPerComponent = 1; + + if ( glType === gl.UNSIGNED_SHORT_4_4_4_4 || + glType === gl.UNSIGNED_SHORT_5_5_5_1 || + glType === gl.UNSIGNED_SHORT_5_6_5 || + glType === gl.UNSIGNED_SHORT || + glType === gl.HALF_FLOAT ) bytesPerComponent = 2; + + if ( glType === gl.UNSIGNED_INT || + glType === gl.FLOAT ) bytesPerComponent = 4; + + if ( glFormat === gl.RGBA ) return bytesPerComponent * 4; + if ( glFormat === gl.RGB ) return bytesPerComponent * 3; + if ( glFormat === gl.ALPHA ) return bytesPerComponent; + + } + +} + +function getImage( source ) { + + if ( source.isDataTexture ) { + + return source.image.data; + + } else if ( ( typeof HTMLImageElement !== 'undefined' && source instanceof HTMLImageElement ) || + ( typeof HTMLCanvasElement !== 'undefined' && source instanceof HTMLCanvasElement ) || + ( typeof ImageBitmap !== 'undefined' && source instanceof ImageBitmap ) || + ( typeof OffscreenCanvas !== 'undefined' && source instanceof OffscreenCanvas ) ) { + + return source; + + } + + return source.data; + +} + +/** + * A WebGL 2 backend utility module for managing extensions. + * + * @private + */ +class WebGLExtensions { + + /** + * Constructs a new utility object. + * + * @param {WebGLBackend} backend - The WebGL 2 backend. + */ + constructor( backend ) { + + /** + * A reference to the WebGL 2 backend. + * + * @type {WebGLBackend} + */ + this.backend = backend; + + /** + * A reference to the rendering context. + * + * @type {WebGL2RenderingContext} + */ + this.gl = this.backend.gl; + + /** + * A list with all the supported WebGL extensions. + * + * @type {Array} + */ + this.availableExtensions = this.gl.getSupportedExtensions(); + + /** + * A dictionary with requested WebGL extensions. + * The key is the name of the extension, the value + * the requested extension object. + * + * @type {Object} + */ + this.extensions = {}; + + } + + /** + * Returns the extension object for the given extension name. + * + * @param {string} name - The extension name. + * @return {Object} The extension object. + */ + get( name ) { + + let extension = this.extensions[ name ]; + + if ( extension === undefined ) { + + extension = this.gl.getExtension( name ); + + this.extensions[ name ] = extension; + + } + + return extension; + + } + + /** + * Returns `true` if the requested extension is available. + * + * @param {string} name - The extension name. + * @return {boolean} Whether the given extension is available or not. + */ + has( name ) { + + return this.availableExtensions.includes( name ); + + } + +} + +/** + * A WebGL 2 backend utility module for managing the device's capabilities. + * + * @private + */ +class WebGLCapabilities { + + /** + * Constructs a new utility object. + * + * @param {WebGLBackend} backend - The WebGL 2 backend. + */ + constructor( backend ) { + + /** + * A reference to the WebGL 2 backend. + * + * @type {WebGLBackend} + */ + this.backend = backend; + + /** + * This value holds the cached max anisotropy value. + * + * @type {?number} + * @default null + */ + this.maxAnisotropy = null; + + } + + /** + * Returns the maximum anisotropy texture filtering value. This value + * depends on the device and is reported by the `EXT_texture_filter_anisotropic` + * WebGL extension. + * + * @return {number} The maximum anisotropy texture filtering value. + */ + getMaxAnisotropy() { + + if ( this.maxAnisotropy !== null ) return this.maxAnisotropy; + + const gl = this.backend.gl; + const extensions = this.backend.extensions; + + if ( extensions.has( 'EXT_texture_filter_anisotropic' ) === true ) { + + const extension = extensions.get( 'EXT_texture_filter_anisotropic' ); + + this.maxAnisotropy = gl.getParameter( extension.MAX_TEXTURE_MAX_ANISOTROPY_EXT ); + + } else { + + this.maxAnisotropy = 0; + + } + + return this.maxAnisotropy; + + } + +} + +const GLFeatureName = { + + 'WEBGL_multi_draw': 'WEBGL_multi_draw', + 'WEBGL_compressed_texture_astc': 'texture-compression-astc', + 'WEBGL_compressed_texture_etc': 'texture-compression-etc2', + 'WEBGL_compressed_texture_etc1': 'texture-compression-etc1', + 'WEBGL_compressed_texture_pvrtc': 'texture-compression-pvrtc', + 'WEBKIT_WEBGL_compressed_texture_pvrtc': 'texture-compression-pvrtc', + 'WEBGL_compressed_texture_s3tc': 'texture-compression-bc', + 'EXT_texture_compression_bptc': 'texture-compression-bptc', + 'EXT_disjoint_timer_query_webgl2': 'timestamp-query', + 'OVR_multiview2': 'OVR_multiview2' + +}; + +class WebGLBufferRenderer { + + constructor( backend ) { + + this.gl = backend.gl; + this.extensions = backend.extensions; + this.info = backend.renderer.info; + this.mode = null; + this.index = 0; + this.type = null; + this.object = null; + + } + + render( start, count ) { + + const { gl, mode, object, type, info, index } = this; + + if ( index !== 0 ) { + + gl.drawElements( mode, count, type, start ); + + } else { + + gl.drawArrays( mode, start, count ); + + } + + info.update( object, count, 1 ); + + } + + renderInstances( start, count, primcount ) { + + const { gl, mode, type, index, object, info } = this; + + if ( primcount === 0 ) return; + + if ( index !== 0 ) { + + gl.drawElementsInstanced( mode, count, type, start, primcount ); + + } else { + + gl.drawArraysInstanced( mode, start, count, primcount ); + + } + + info.update( object, count, primcount ); + + } + + renderMultiDraw( starts, counts, drawCount ) { + + const { extensions, mode, object, info } = this; + + if ( drawCount === 0 ) return; + + const extension = extensions.get( 'WEBGL_multi_draw' ); + + if ( extension === null ) { + + for ( let i = 0; i < drawCount; i ++ ) { + + this.render( starts[ i ], counts[ i ] ); + + } + + } else { + + if ( this.index !== 0 ) { + + extension.multiDrawElementsWEBGL( mode, counts, 0, this.type, starts, 0, drawCount ); + + } else { + + extension.multiDrawArraysWEBGL( mode, starts, 0, counts, 0, drawCount ); + + } + + let elementCount = 0; + for ( let i = 0; i < drawCount; i ++ ) { + + elementCount += counts[ i ]; + + } + + info.update( object, elementCount, 1 ); + + } + + } + + renderMultiDrawInstances( starts, counts, drawCount, primcount ) { + + const { extensions, mode, object, info } = this; + + if ( drawCount === 0 ) return; + + const extension = extensions.get( 'WEBGL_multi_draw' ); + + if ( extension === null ) { + + for ( let i = 0; i < drawCount; i ++ ) { + + this.renderInstances( starts[ i ], counts[ i ], primcount[ i ] ); + + } + + } else { + + if ( this.index !== 0 ) { + + extension.multiDrawElementsInstancedWEBGL( mode, counts, 0, this.type, starts, 0, primcount, 0, drawCount ); + + } else { + + extension.multiDrawArraysInstancedWEBGL( mode, starts, 0, counts, 0, primcount, 0, drawCount ); + + } + + let elementCount = 0; + for ( let i = 0; i < drawCount; i ++ ) { + + elementCount += counts[ i ] * primcount[ i ]; + + } + + info.update( object, elementCount, 1 ); + + } + + } + + // + +} + +/** + * Abstract base class of a timestamp query pool. + * + * @abstract + */ +class TimestampQueryPool { + + /** + * Creates a new timestamp query pool. + * + * @param {number} [maxQueries=256] - Maximum number of queries this pool can hold. + */ + constructor( maxQueries = 256 ) { + + /** + * Whether to track timestamps or not. + * + * @type {boolean} + * @default true + */ + this.trackTimestamp = true; + + /** + * Maximum number of queries this pool can hold. + * + * @type {number} + * @default 256 + */ + this.maxQueries = maxQueries; + + /** + * How many queries allocated so far. + * + * @type {number} + * @default 0 + */ + this.currentQueryIndex = 0; + + /** + * Tracks offsets for different contexts. + * + * @type {Map} + */ + this.queryOffsets = new Map(); + + /** + * Whether the pool has been disposed or not. + * + * @type {boolean} + * @default false + */ + this.isDisposed = false; + + /** + * TODO + * + * @type {number} + * @default 0 + */ + this.lastValue = 0; + + /** + * TODO + * + * @type {boolean} + * @default false + */ + this.pendingResolve = false; + + } + + /** + * Allocate queries for a specific renderContext. + * + * @abstract + * @param {Object} renderContext - The render context to allocate queries for. + * @returns {?number} + */ + allocateQueriesForContext( /* renderContext */ ) {} + + /** + * Resolve all timestamps and return data (or process them). + * + * @abstract + * @async + * @returns {Promise|number} The resolved timestamp value. + */ + async resolveQueriesAsync() {} + + /** + * Dispose of the query pool. + * + * @abstract + */ + dispose() {} + +} + +/** + * Manages a pool of WebGL timestamp queries for performance measurement. + * Handles creation, execution, and resolution of timer queries using WebGL extensions. + * + * @augments TimestampQueryPool + */ +class WebGLTimestampQueryPool extends TimestampQueryPool { + + /** + * Creates a new WebGL timestamp query pool. + * + * @param {WebGLRenderingContext|WebGL2RenderingContext} gl - The WebGL context. + * @param {string} type - The type identifier for this query pool. + * @param {number} [maxQueries=2048] - Maximum number of queries this pool can hold. + */ + constructor( gl, type, maxQueries = 2048 ) { + + super( maxQueries ); + + this.gl = gl; + this.type = type; + + // Check for timer query extensions + this.ext = gl.getExtension( 'EXT_disjoint_timer_query_webgl2' ) || + gl.getExtension( 'EXT_disjoint_timer_query' ); + + if ( ! this.ext ) { + + console.warn( 'EXT_disjoint_timer_query not supported; timestamps will be disabled.' ); + this.trackTimestamp = false; + return; + + } + + // Create query objects + this.queries = []; + for ( let i = 0; i < this.maxQueries; i ++ ) { + + this.queries.push( gl.createQuery() ); + + } + + this.activeQuery = null; + this.queryStates = new Map(); // Track state of each query: 'inactive', 'started', 'ended' + + } + + /** + * Allocates a pair of queries for a given render context. + * + * @param {Object} renderContext - The render context to allocate queries for. + * @returns {?number} The base offset for the allocated queries, or null if allocation failed. + */ + allocateQueriesForContext( renderContext ) { + + if ( ! this.trackTimestamp ) return null; + + // Check if we have enough space for a new query pair + if ( this.currentQueryIndex + 2 > this.maxQueries ) { + + warnOnce( `WebGPUTimestampQueryPool [${ this.type }]: Maximum number of queries exceeded, when using trackTimestamp it is necessary to resolves the queries via renderer.resolveTimestampsAsync( THREE.TimestampQuery.${ this.type.toUpperCase() } ).` ); + return null; + + } + + const baseOffset = this.currentQueryIndex; + this.currentQueryIndex += 2; + + // Initialize query states + this.queryStates.set( baseOffset, 'inactive' ); + this.queryOffsets.set( renderContext.id, baseOffset ); + + return baseOffset; + + } + + /** + * Begins a timestamp query for the specified render context. + * + * @param {Object} renderContext - The render context to begin timing for. + */ + beginQuery( renderContext ) { + + if ( ! this.trackTimestamp || this.isDisposed ) { + + return; + + } + + const baseOffset = this.queryOffsets.get( renderContext.id ); + if ( baseOffset == null ) { + + return; + + } + + // Don't start a new query if there's an active one + if ( this.activeQuery !== null ) { + + return; + + } + + const query = this.queries[ baseOffset ]; + if ( ! query ) { + + return; + + } + + try { + + // Only begin if query is inactive + if ( this.queryStates.get( baseOffset ) === 'inactive' ) { + + this.gl.beginQuery( this.ext.TIME_ELAPSED_EXT, query ); + this.activeQuery = baseOffset; + this.queryStates.set( baseOffset, 'started' ); + + } + + } catch ( error ) { + + console.error( 'Error in beginQuery:', error ); + this.activeQuery = null; + this.queryStates.set( baseOffset, 'inactive' ); + + } + + } + + /** + * Ends the active timestamp query for the specified render context. + * + * @param {Object} renderContext - The render context to end timing for. + * @param {string} renderContext.id - Unique identifier for the render context. + */ + endQuery( renderContext ) { + + if ( ! this.trackTimestamp || this.isDisposed ) { + + return; + + } + + const baseOffset = this.queryOffsets.get( renderContext.id ); + if ( baseOffset == null ) { + + return; + + } + + // Only end if this is the active query + if ( this.activeQuery !== baseOffset ) { + + return; + + } + + try { + + this.gl.endQuery( this.ext.TIME_ELAPSED_EXT ); + this.queryStates.set( baseOffset, 'ended' ); + this.activeQuery = null; + + } catch ( error ) { + + console.error( 'Error in endQuery:', error ); + // Reset state on error + this.queryStates.set( baseOffset, 'inactive' ); + this.activeQuery = null; + + } + + } + + /** + * Asynchronously resolves all completed queries and returns the total duration. + * + * @async + * @returns {Promise} The total duration in milliseconds, or the last valid value if resolution fails. + */ + async resolveQueriesAsync() { + + if ( ! this.trackTimestamp || this.pendingResolve ) { + + return this.lastValue; + + } + + this.pendingResolve = true; + + try { + + // Wait for all ended queries to complete + const resolvePromises = []; + + for ( const [ baseOffset, state ] of this.queryStates ) { + + if ( state === 'ended' ) { + + const query = this.queries[ baseOffset ]; + resolvePromises.push( this.resolveQuery( query ) ); + + } + + } + + if ( resolvePromises.length === 0 ) { + + return this.lastValue; + + } + + const results = await Promise.all( resolvePromises ); + const totalDuration = results.reduce( ( acc, val ) => acc + val, 0 ); + + // Store the last valid result + this.lastValue = totalDuration; + + // Reset states + this.currentQueryIndex = 0; + this.queryOffsets.clear(); + this.queryStates.clear(); + this.activeQuery = null; + + return totalDuration; + + } catch ( error ) { + + console.error( 'Error resolving queries:', error ); + return this.lastValue; + + } finally { + + this.pendingResolve = false; + + } + + } + + /** + * Resolves a single query, checking for completion and disjoint operation. + * + * @async + * @param {WebGLQuery} query - The query object to resolve. + * @returns {Promise} The elapsed time in milliseconds. + */ + async resolveQuery( query ) { + + return new Promise( ( resolve ) => { + + if ( this.isDisposed ) { + + resolve( this.lastValue ); + return; + + } + + let timeoutId; + let isResolved = false; + + const cleanup = () => { + + if ( timeoutId ) { + + clearTimeout( timeoutId ); + timeoutId = null; + + } + + }; + + const finalizeResolution = ( value ) => { + + if ( ! isResolved ) { + + isResolved = true; + cleanup(); + resolve( value ); + + } + + }; + + const checkQuery = () => { + + if ( this.isDisposed ) { + + finalizeResolution( this.lastValue ); + return; + + } + + try { + + // Check if the GPU timer was disjoint (i.e., timing was unreliable) + const disjoint = this.gl.getParameter( this.ext.GPU_DISJOINT_EXT ); + if ( disjoint ) { + + finalizeResolution( this.lastValue ); + return; + + } + + const available = this.gl.getQueryParameter( query, this.gl.QUERY_RESULT_AVAILABLE ); + if ( ! available ) { + + timeoutId = setTimeout( checkQuery, 1 ); + return; + + } + + const elapsed = this.gl.getQueryParameter( query, this.gl.QUERY_RESULT ); + resolve( Number( elapsed ) / 1e6 ); // Convert nanoseconds to milliseconds + + } catch ( error ) { + + console.error( 'Error checking query:', error ); + resolve( this.lastValue ); + + } + + }; + + checkQuery(); + + } ); + + } + + /** + * Releases all resources held by this query pool. + * This includes deleting all query objects and clearing internal state. + */ + dispose() { + + if ( this.isDisposed ) { + + return; + + } + + this.isDisposed = true; + + if ( ! this.trackTimestamp ) return; + + for ( const query of this.queries ) { + + this.gl.deleteQuery( query ); + + } + + this.queries = []; + this.queryStates.clear(); + this.queryOffsets.clear(); + this.lastValue = 0; + this.activeQuery = null; + + } + +} + +const _drawingBufferSize = /*@__PURE__*/ new Vector2(); + +/** + * A backend implementation targeting WebGL 2. + * + * @private + * @augments Backend + */ +class WebGLBackend extends Backend { + + /** + * WebGLBackend options. + * + * @typedef {Object} WebGLBackend~Options + * @property {boolean} [logarithmicDepthBuffer=false] - Whether logarithmic depth buffer is enabled or not. + * @property {boolean} [alpha=true] - Whether the default framebuffer (which represents the final contents of the canvas) should be transparent or opaque. + * @property {boolean} [depth=true] - Whether the default framebuffer should have a depth buffer or not. + * @property {boolean} [stencil=false] - Whether the default framebuffer should have a stencil buffer or not. + * @property {boolean} [antialias=false] - Whether MSAA as the default anti-aliasing should be enabled or not. + * @property {number} [samples=0] - When `antialias` is `true`, `4` samples are used by default. Set this parameter to any other integer value than 0 to overwrite the default. + * @property {boolean} [forceWebGL=false] - If set to `true`, the renderer uses a WebGL 2 backend no matter if WebGPU is supported or not. + * @property {WebGL2RenderingContext} [context=undefined] - A WebGL 2 rendering context. + */ + + /** + * Constructs a new WebGPU backend. + * + * @param {WebGLBackend~Options} [parameters] - The configuration parameter. + */ + constructor( parameters = {} ) { + + super( parameters ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isWebGLBackend = true; + + /** + * A reference to a backend module holding shader attribute-related + * utility functions. + * + * @type {?WebGLAttributeUtils} + * @default null + */ + this.attributeUtils = null; + + /** + * A reference to a backend module holding extension-related + * utility functions. + * + * @type {?WebGLExtensions} + * @default null + */ + this.extensions = null; + + /** + * A reference to a backend module holding capability-related + * utility functions. + * + * @type {?WebGLCapabilities} + * @default null + */ + this.capabilities = null; + + /** + * A reference to a backend module holding texture-related + * utility functions. + * + * @type {?WebGLTextureUtils} + * @default null + */ + this.textureUtils = null; + + /** + * A reference to a backend module holding renderer-related + * utility functions. + * + * @type {?WebGLBufferRenderer} + * @default null + */ + this.bufferRenderer = null; + + /** + * A reference to the rendering context. + * + * @type {?WebGL2RenderingContext} + * @default null + */ + this.gl = null; + + /** + * A reference to a backend module holding state-related + * utility functions. + * + * @type {?WebGLState} + * @default null + */ + this.state = null; + + /** + * A reference to a backend module holding common + * utility functions. + * + * @type {?WebGLUtils} + * @default null + */ + this.utils = null; + + /** + * Dictionary for caching VAOs. + * + * @type {Object} + */ + this.vaoCache = {}; + + /** + * Dictionary for caching transform feedback objects. + * + * @type {Object} + */ + this.transformFeedbackCache = {}; + + /** + * Controls if `gl.RASTERIZER_DISCARD` should be enabled or not. + * Only relevant when using compute shaders. + * + * @type {boolean} + * @default false + */ + this.discard = false; + + /** + * A reference to the `EXT_disjoint_timer_query_webgl2` extension. `null` if the + * device does not support the extension. + * + * @type {?EXTDisjointTimerQueryWebGL2} + * @default null + */ + this.disjoint = null; + + /** + * A reference to the `KHR_parallel_shader_compile` extension. `null` if the + * device does not support the extension. + * + * @type {?KHRParallelShaderCompile} + * @default null + */ + this.parallel = null; + + /** + * A reference to the current render context. + * + * @private + * @type {RenderContext} + * @default null + */ + this._currentContext = null; + + /** + * A unique collection of bindings. + * + * @private + * @type {WeakSet} + */ + this._knownBindings = new WeakSet(); + + + /** + * Whether the device supports framebuffers invalidation or not. + * + * @private + * @type {boolean} + */ + this._supportsInvalidateFramebuffer = typeof navigator === 'undefined' ? false : /OculusBrowser/g.test( navigator.userAgent ); + + /** + * The target framebuffer when rendering with + * the WebXR device API. + * + * @private + * @type {WebGLFramebuffer} + * @default null + */ + this._xrFramebuffer = null; + + } + + /** + * Initializes the backend so it is ready for usage. + * + * @param {Renderer} renderer - The renderer. + */ + init( renderer ) { + + super.init( renderer ); + + // + + const parameters = this.parameters; + + const contextAttributes = { + antialias: renderer.samples > 0, + alpha: true, // always true for performance reasons + depth: renderer.depth, + stencil: renderer.stencil + }; + + const glContext = ( parameters.context !== undefined ) ? parameters.context : renderer.domElement.getContext( 'webgl2', contextAttributes ); + + function onContextLost( event ) { + + event.preventDefault(); + + const contextLossInfo = { + api: 'WebGL', + message: event.statusMessage || 'Unknown reason', + reason: null, + originalEvent: event + }; + + renderer.onDeviceLost( contextLossInfo ); + + } + + this._onContextLost = onContextLost; + + renderer.domElement.addEventListener( 'webglcontextlost', onContextLost, false ); + + this.gl = glContext; + + this.extensions = new WebGLExtensions( this ); + this.capabilities = new WebGLCapabilities( this ); + this.attributeUtils = new WebGLAttributeUtils( this ); + this.textureUtils = new WebGLTextureUtils( this ); + this.bufferRenderer = new WebGLBufferRenderer( this ); + + this.state = new WebGLState( this ); + this.utils = new WebGLUtils( this ); + + this.extensions.get( 'EXT_color_buffer_float' ); + this.extensions.get( 'WEBGL_clip_cull_distance' ); + this.extensions.get( 'OES_texture_float_linear' ); + this.extensions.get( 'EXT_color_buffer_half_float' ); + this.extensions.get( 'WEBGL_multisampled_render_to_texture' ); + this.extensions.get( 'WEBGL_render_shared_exponent' ); + this.extensions.get( 'WEBGL_multi_draw' ); + this.extensions.get( 'OVR_multiview2' ); + + this.disjoint = this.extensions.get( 'EXT_disjoint_timer_query_webgl2' ); + this.parallel = this.extensions.get( 'KHR_parallel_shader_compile' ); + + } + + /** + * The coordinate system of the backend. + * + * @type {number} + * @readonly + */ + get coordinateSystem() { + + return WebGLCoordinateSystem; + + } + + /** + * This method performs a readback operation by moving buffer data from + * a storage buffer attribute from the GPU to the CPU. + * + * @async + * @param {StorageBufferAttribute} attribute - The storage buffer attribute. + * @return {Promise} A promise that resolves with the buffer data when the data are ready. + */ + async getArrayBufferAsync( attribute ) { + + return await this.attributeUtils.getArrayBufferAsync( attribute ); + + } + + /** + * Can be used to synchronize CPU operations with GPU tasks. So when this method is called, + * the CPU waits for the GPU to complete its operation (e.g. a compute task). + * + * @async + * @return {Promise} A Promise that resolves when synchronization has been finished. + */ + async waitForGPU() { + + await this.utils._clientWaitAsync(); + + } + + /** + * Ensures the backend is XR compatible. + * + * @async + * @return {Promise} A Promise that resolve when the renderer is XR compatible. + */ + async makeXRCompatible() { + + const attributes = this.gl.getContextAttributes(); + + if ( attributes.xrCompatible !== true ) { + + await this.gl.makeXRCompatible(); + + } + + } + /** + * Sets the XR rendering destination. + * + * @param {WebGLFramebuffer} xrFramebuffer - The XR framebuffer. + */ + setXRTarget( xrFramebuffer ) { + + this._xrFramebuffer = xrFramebuffer; + + } + + /** + * Configures the given XR render target with external textures. + * + * This method is only relevant when using the WebXR Layers API. + * + * @param {XRRenderTarget} renderTarget - The XR render target. + * @param {WebGLTexture} colorTexture - A native color texture. + * @param {?WebGLTexture} [depthTexture=null] - A native depth texture. + */ + setXRRenderTargetTextures( renderTarget, colorTexture, depthTexture = null ) { + + const gl = this.gl; + + this.set( renderTarget.texture, { textureGPU: colorTexture, glInternalFormat: gl.RGBA8 } ); // see #24698 why RGBA8 and not SRGB8_ALPHA8 is used + + if ( depthTexture !== null ) { + + const glInternalFormat = renderTarget.stencilBuffer ? gl.DEPTH24_STENCIL8 : gl.DEPTH_COMPONENT24; + + this.set( renderTarget.depthTexture, { textureGPU: depthTexture, glInternalFormat: glInternalFormat } ); + + // The multisample_render_to_texture extension doesn't work properly if there + // are midframe flushes and an external depth texture. + if ( ( this.extensions.has( 'WEBGL_multisampled_render_to_texture' ) === true ) && renderTarget._autoAllocateDepthBuffer === true && renderTarget.multiview === false ) { + + console.warn( 'THREE.WebGLBackend: Render-to-texture extension was disabled because an external texture was provided' ); + + } + + renderTarget._autoAllocateDepthBuffer = false; + + } + + } + + /** + * Inits a time stamp query for the given render context. + * + * @param {RenderContext} renderContext - The render context. + */ + initTimestampQuery( renderContext ) { + + if ( ! this.disjoint || ! this.trackTimestamp ) return; + + const type = renderContext.isComputeNode ? 'compute' : 'render'; + + if ( ! this.timestampQueryPool[ type ] ) { + + // TODO: Variable maxQueries? + this.timestampQueryPool[ type ] = new WebGLTimestampQueryPool( this.gl, type, 2048 ); + + } + + const timestampQueryPool = this.timestampQueryPool[ type ]; + + const baseOffset = timestampQueryPool.allocateQueriesForContext( renderContext ); + + if ( baseOffset !== null ) { + + timestampQueryPool.beginQuery( renderContext ); + + } + + } + + // timestamp utils + + /** + * Prepares the timestamp buffer. + * + * @param {RenderContext} renderContext - The render context. + */ + prepareTimestampBuffer( renderContext ) { + + if ( ! this.disjoint || ! this.trackTimestamp ) return; + + const type = renderContext.isComputeNode ? 'compute' : 'render'; + const timestampQueryPool = this.timestampQueryPool[ type ]; + + timestampQueryPool.endQuery( renderContext ); + + } + + + /** + * Returns the backend's rendering context. + * + * @return {WebGL2RenderingContext} The rendering context. + */ + getContext() { + + return this.gl; + + } + + /** + * This method is executed at the beginning of a render call and prepares + * the WebGL state for upcoming render calls + * + * @param {RenderContext} renderContext - The render context. + */ + beginRender( renderContext ) { + + const { state } = this; + const renderContextData = this.get( renderContext ); + + // + + if ( renderContext.viewport ) { + + this.updateViewport( renderContext ); + + } else { + + const { width, height } = this.getDrawingBufferSize( _drawingBufferSize ); + state.viewport( 0, 0, width, height ); + + } + + if ( renderContext.scissor ) { + + const { x, y, width, height } = renderContext.scissorValue; + + state.scissor( x, renderContext.height - height - y, width, height ); + + } + + // + + this.initTimestampQuery( renderContext ); + + renderContextData.previousContext = this._currentContext; + this._currentContext = renderContext; + + this._setFramebuffer( renderContext ); + this.clear( renderContext.clearColor, renderContext.clearDepth, renderContext.clearStencil, renderContext, false ); + + const occlusionQueryCount = renderContext.occlusionQueryCount; + + if ( occlusionQueryCount > 0 ) { + + // Get a reference to the array of objects with queries. The renderContextData property + // can be changed by another render pass before the async reading of all previous queries complete + renderContextData.currentOcclusionQueries = renderContextData.occlusionQueries; + renderContextData.currentOcclusionQueryObjects = renderContextData.occlusionQueryObjects; + + renderContextData.lastOcclusionObject = null; + renderContextData.occlusionQueries = new Array( occlusionQueryCount ); + renderContextData.occlusionQueryObjects = new Array( occlusionQueryCount ); + renderContextData.occlusionQueryIndex = 0; + + } + + } + + /** + * This method is executed at the end of a render call and finalizes work + * after draw calls. + * + * @param {RenderContext} renderContext - The render context. + */ + finishRender( renderContext ) { + + const { gl, state } = this; + const renderContextData = this.get( renderContext ); + const previousContext = renderContextData.previousContext; + + state.resetVertexState(); + + const occlusionQueryCount = renderContext.occlusionQueryCount; + + if ( occlusionQueryCount > 0 ) { + + if ( occlusionQueryCount > renderContextData.occlusionQueryIndex ) { + + gl.endQuery( gl.ANY_SAMPLES_PASSED ); + + } + + this.resolveOccludedAsync( renderContext ); + + } + + const textures = renderContext.textures; + + if ( textures !== null ) { + + for ( let i = 0; i < textures.length; i ++ ) { + + const texture = textures[ i ]; + + if ( texture.generateMipmaps ) { + + this.generateMipmaps( texture ); + + } + + } + + } + + this._currentContext = previousContext; + const renderTarget = renderContext.renderTarget; + + if ( renderContext.textures !== null && renderTarget ) { + + const renderTargetContextData = this.get( renderTarget ); + + if ( renderTarget.samples > 0 && this._useMultisampledExtension( renderTarget ) === false ) { + + const fb = renderTargetContextData.framebuffers[ renderContext.getCacheKey() ]; + + let mask = gl.COLOR_BUFFER_BIT; + + if ( renderTarget.resolveDepthBuffer ) { + + if ( renderTarget.depthBuffer ) mask |= gl.DEPTH_BUFFER_BIT; + if ( renderTarget.stencilBuffer && renderTarget.resolveStencilBuffer ) mask |= gl.STENCIL_BUFFER_BIT; + + } + + const msaaFrameBuffer = renderTargetContextData.msaaFrameBuffer; + const msaaRenderbuffers = renderTargetContextData.msaaRenderbuffers; + + const textures = renderContext.textures; + const isMRT = textures.length > 1; + + state.bindFramebuffer( gl.READ_FRAMEBUFFER, msaaFrameBuffer ); + state.bindFramebuffer( gl.DRAW_FRAMEBUFFER, fb ); + + if ( isMRT ) { + + // blitFramebuffer() can only copy/resolve the first color attachment of a framebuffer. When using MRT, + // the engine temporarily removes all attachments and then configures each attachment for the resolve. + + for ( let i = 0; i < textures.length; i ++ ) { + + gl.framebufferRenderbuffer( gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.RENDERBUFFER, null ); + gl.framebufferTexture2D( gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.TEXTURE_2D, null, 0 ); + + } + + } + + for ( let i = 0; i < textures.length; i ++ ) { + + if ( isMRT ) { + + // configure attachment for resolve + + const { textureGPU } = this.get( textures[ i ] ); + + gl.framebufferRenderbuffer( gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, msaaRenderbuffers[ i ] ); + gl.framebufferTexture2D( gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, textureGPU, 0 ); + + } + + if ( renderContext.scissor ) { + + const { x, y, width, height } = renderContext.scissorValue; + + const viewY = renderContext.height - height - y; + + gl.blitFramebuffer( x, viewY, x + width, viewY + height, x, viewY, x + width, viewY + height, mask, gl.NEAREST ); + + } else { + + gl.blitFramebuffer( 0, 0, renderContext.width, renderContext.height, 0, 0, renderContext.width, renderContext.height, mask, gl.NEAREST ); + + } + + } + + if ( isMRT ) { + + // restore attachments + + for ( let i = 0; i < textures.length; i ++ ) { + + const { textureGPU } = this.get( textures[ i ] ); + + gl.framebufferRenderbuffer( gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.RENDERBUFFER, msaaRenderbuffers[ i ] ); + gl.framebufferTexture2D( gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.TEXTURE_2D, textureGPU, 0 ); + + } + + } + + if ( this._supportsInvalidateFramebuffer === true ) { + + gl.invalidateFramebuffer( gl.READ_FRAMEBUFFER, renderTargetContextData.invalidationArray ); + + } + + } else if ( renderTarget.resolveDepthBuffer === false && renderTargetContextData.framebuffers ) { + + const fb = renderTargetContextData.framebuffers[ renderContext.getCacheKey() ]; + state.bindFramebuffer( gl.DRAW_FRAMEBUFFER, fb ); + gl.invalidateFramebuffer( gl.DRAW_FRAMEBUFFER, renderTargetContextData.depthInvalidationArray ); + + } + + } + + if ( previousContext !== null ) { + + this._setFramebuffer( previousContext ); + + if ( previousContext.viewport ) { + + this.updateViewport( previousContext ); + + } else { + + const { width, height } = this.getDrawingBufferSize( _drawingBufferSize ); + state.viewport( 0, 0, width, height ); + + } + + } + + this.prepareTimestampBuffer( renderContext ); + + } + + /** + * This method processes the result of occlusion queries and writes it + * into render context data. + * + * @async + * @param {RenderContext} renderContext - The render context. + */ + resolveOccludedAsync( renderContext ) { + + const renderContextData = this.get( renderContext ); + + // handle occlusion query results + + const { currentOcclusionQueries, currentOcclusionQueryObjects } = renderContextData; + + if ( currentOcclusionQueries && currentOcclusionQueryObjects ) { + + const occluded = new WeakSet(); + const { gl } = this; + + renderContextData.currentOcclusionQueryObjects = null; + renderContextData.currentOcclusionQueries = null; + + const check = () => { + + let completed = 0; + + // check all queries and requeue as appropriate + for ( let i = 0; i < currentOcclusionQueries.length; i ++ ) { + + const query = currentOcclusionQueries[ i ]; + + if ( query === null ) continue; + + if ( gl.getQueryParameter( query, gl.QUERY_RESULT_AVAILABLE ) ) { + + if ( gl.getQueryParameter( query, gl.QUERY_RESULT ) === 0 ) occluded.add( currentOcclusionQueryObjects[ i ] ); + + currentOcclusionQueries[ i ] = null; + gl.deleteQuery( query ); + + completed ++; + + } + + } + + if ( completed < currentOcclusionQueries.length ) { + + requestAnimationFrame( check ); + + } else { + + renderContextData.occluded = occluded; + + } + + }; + + check(); + + } + + } + + /** + * Returns `true` if the given 3D object is fully occluded by other + * 3D objects in the scene. + * + * @param {RenderContext} renderContext - The render context. + * @param {Object3D} object - The 3D object to test. + * @return {boolean} Whether the 3D object is fully occluded or not. + */ + isOccluded( renderContext, object ) { + + const renderContextData = this.get( renderContext ); + + return renderContextData.occluded && renderContextData.occluded.has( object ); + + } + + /** + * Updates the viewport with the values from the given render context. + * + * @param {RenderContext} renderContext - The render context. + */ + updateViewport( renderContext ) { + + const { state } = this; + const { x, y, width, height } = renderContext.viewportValue; + + state.viewport( x, renderContext.height - height - y, width, height ); + + } + + /** + * Defines the scissor test. + * + * @param {boolean} boolean - Whether the scissor test should be enabled or not. + */ + setScissorTest( boolean ) { + + const state = this.state; + + state.setScissorTest( boolean ); + + } + + /** + * Returns the clear color and alpha into a single + * color object. + * + * @return {Color4} The clear color. + */ + getClearColor() { + + const clearColor = super.getClearColor(); + + // Since the canvas is always created with alpha: true, + // WebGL must always premultiply the clear color. + + clearColor.r *= clearColor.a; + clearColor.g *= clearColor.a; + clearColor.b *= clearColor.a; + + return clearColor; + + } + + /** + * Performs a clear operation. + * + * @param {boolean} color - Whether the color buffer should be cleared or not. + * @param {boolean} depth - Whether the depth buffer should be cleared or not. + * @param {boolean} stencil - Whether the stencil buffer should be cleared or not. + * @param {?Object} [descriptor=null] - The render context of the current set render target. + * @param {boolean} [setFrameBuffer=true] - TODO. + */ + clear( color, depth, stencil, descriptor = null, setFrameBuffer = true ) { + + const { gl, renderer } = this; + + if ( descriptor === null ) { + + const clearColor = this.getClearColor(); + + descriptor = { + textures: null, + clearColorValue: clearColor + }; + + } + + // + + let clear = 0; + + if ( color ) clear |= gl.COLOR_BUFFER_BIT; + if ( depth ) clear |= gl.DEPTH_BUFFER_BIT; + if ( stencil ) clear |= gl.STENCIL_BUFFER_BIT; + + if ( clear !== 0 ) { + + let clearColor; + + if ( descriptor.clearColorValue ) { + + clearColor = descriptor.clearColorValue; + + } else { + + clearColor = this.getClearColor(); + + } + + const clearDepth = renderer.getClearDepth(); + const clearStencil = renderer.getClearStencil(); + + if ( depth ) this.state.setDepthMask( true ); + + if ( descriptor.textures === null ) { + + gl.clearColor( clearColor.r, clearColor.g, clearColor.b, clearColor.a ); + gl.clear( clear ); + + } else { + + if ( setFrameBuffer ) this._setFramebuffer( descriptor ); + + if ( color ) { + + for ( let i = 0; i < descriptor.textures.length; i ++ ) { + + if ( i === 0 ) { + + gl.clearBufferfv( gl.COLOR, i, [ clearColor.r, clearColor.g, clearColor.b, clearColor.a ] ); + + } else { + + gl.clearBufferfv( gl.COLOR, i, [ 0, 0, 0, 1 ] ); + + } + + } + + } + + if ( depth && stencil ) { + + gl.clearBufferfi( gl.DEPTH_STENCIL, 0, clearDepth, clearStencil ); + + } else if ( depth ) { + + gl.clearBufferfv( gl.DEPTH, 0, [ clearDepth ] ); + + } else if ( stencil ) { + + gl.clearBufferiv( gl.STENCIL, 0, [ clearStencil ] ); + + } + + } + + } + + } + + /** + * This method is executed at the beginning of a compute call and + * prepares the state for upcoming compute tasks. + * + * @param {Node|Array} computeGroup - The compute node(s). + */ + beginCompute( computeGroup ) { + + const { state, gl } = this; + + state.bindFramebuffer( gl.FRAMEBUFFER, null ); + this.initTimestampQuery( computeGroup ); + + } + + /** + * Executes a compute command for the given compute node. + * + * @param {Node|Array} computeGroup - The group of compute nodes of a compute call. Can be a single compute node. + * @param {Node} computeNode - The compute node. + * @param {Array} bindings - The bindings. + * @param {ComputePipeline} pipeline - The compute pipeline. + */ + compute( computeGroup, computeNode, bindings, pipeline ) { + + const { state, gl } = this; + + if ( this.discard === false ) { + + // required here to handle async behaviour of render.compute() + gl.enable( gl.RASTERIZER_DISCARD ); + this.discard = true; + + } + + const { programGPU, transformBuffers, attributes } = this.get( pipeline ); + + const vaoKey = this._getVaoKey( attributes ); + + const vaoGPU = this.vaoCache[ vaoKey ]; + + if ( vaoGPU === undefined ) { + + this.vaoCache[ vaoKey ] = this._createVao( attributes ); + + } else { + + state.setVertexState( vaoGPU ); + + } + + state.useProgram( programGPU ); + + this._bindUniforms( bindings ); + + const transformFeedbackGPU = this._getTransformFeedback( transformBuffers ); + + gl.bindTransformFeedback( gl.TRANSFORM_FEEDBACK, transformFeedbackGPU ); + gl.beginTransformFeedback( gl.POINTS ); + + if ( attributes[ 0 ].isStorageInstancedBufferAttribute ) { + + gl.drawArraysInstanced( gl.POINTS, 0, 1, computeNode.count ); + + } else { + + gl.drawArrays( gl.POINTS, 0, computeNode.count ); + + } + + gl.endTransformFeedback(); + gl.bindTransformFeedback( gl.TRANSFORM_FEEDBACK, null ); + + // switch active buffers + + for ( let i = 0; i < transformBuffers.length; i ++ ) { + + const dualAttributeData = transformBuffers[ i ]; + + if ( dualAttributeData.pbo && this.has( dualAttributeData.pbo ) ) { + + this.textureUtils.copyBufferToTexture( dualAttributeData.transformBuffer, dualAttributeData.pbo ); + + } + + dualAttributeData.switchBuffers(); + + + } + + } + + /** + * This method is executed at the end of a compute call and + * finalizes work after compute tasks. + * + * @param {Node|Array} computeGroup - The compute node(s). + */ + finishCompute( computeGroup ) { + + const gl = this.gl; + + this.discard = false; + + gl.disable( gl.RASTERIZER_DISCARD ); + + this.prepareTimestampBuffer( computeGroup ); + + if ( this._currentContext ) { + + this._setFramebuffer( this._currentContext ); + + } + + } + + /** + * Internal to determine if the current render target is a render target array with depth 2D array texture. + * + * @param {RenderContext} renderContext - The render context. + * @return {boolean} Whether the render target is a render target array with depth 2D array texture. + * + * @private + */ + _isRenderCameraDepthArray( renderContext ) { + + return renderContext.depthTexture && renderContext.depthTexture.isArrayTexture && renderContext.camera.isArrayCamera; + + } + + /** + * Executes a draw command for the given render object. + * + * @param {RenderObject} renderObject - The render object to draw. + * @param {Info} info - Holds a series of statistical information about the GPU memory and the rendering process. + */ + draw( renderObject/*, info*/ ) { + + const { object, pipeline, material, context, hardwareClippingPlanes } = renderObject; + const { programGPU } = this.get( pipeline ); + + const { gl, state } = this; + + const contextData = this.get( context ); + + const drawParams = renderObject.getDrawParameters(); + + if ( drawParams === null ) return; + + // + + this._bindUniforms( renderObject.getBindings() ); + + const frontFaceCW = ( object.isMesh && object.matrixWorld.determinant() < 0 ); + + state.setMaterial( material, frontFaceCW, hardwareClippingPlanes ); + + state.useProgram( programGPU ); + + // vertex state + + const attributes = renderObject.getAttributes(); + const attributesData = this.get( attributes ); + + let vaoGPU = attributesData.vaoGPU; + + if ( vaoGPU === undefined ) { + + const vaoKey = this._getVaoKey( attributes ); + + vaoGPU = this.vaoCache[ vaoKey ]; + + if ( vaoGPU === undefined ) { + + vaoGPU = this._createVao( attributes ); + + this.vaoCache[ vaoKey ] = vaoGPU; + attributesData.vaoGPU = vaoGPU; + + } + + } + + const index = renderObject.getIndex(); + const indexGPU = ( index !== null ) ? this.get( index ).bufferGPU : null; + + state.setVertexState( vaoGPU, indexGPU ); + + // + + const lastObject = contextData.lastOcclusionObject; + + if ( lastObject !== object && lastObject !== undefined ) { + + if ( lastObject !== null && lastObject.occlusionTest === true ) { + + gl.endQuery( gl.ANY_SAMPLES_PASSED ); + + contextData.occlusionQueryIndex ++; + + } + + if ( object.occlusionTest === true ) { + + const query = gl.createQuery(); + + gl.beginQuery( gl.ANY_SAMPLES_PASSED, query ); + + contextData.occlusionQueries[ contextData.occlusionQueryIndex ] = query; + contextData.occlusionQueryObjects[ contextData.occlusionQueryIndex ] = object; + + } + + contextData.lastOcclusionObject = object; + + } + + // + const renderer = this.bufferRenderer; + + if ( object.isPoints ) renderer.mode = gl.POINTS; + else if ( object.isLineSegments ) renderer.mode = gl.LINES; + else if ( object.isLine ) renderer.mode = gl.LINE_STRIP; + else if ( object.isLineLoop ) renderer.mode = gl.LINE_LOOP; + else { + + if ( material.wireframe === true ) { + + state.setLineWidth( material.wireframeLinewidth * this.renderer.getPixelRatio() ); + renderer.mode = gl.LINES; + + } else { + + renderer.mode = gl.TRIANGLES; + + } + + } + + // + + const { vertexCount, instanceCount } = drawParams; + let { firstVertex } = drawParams; + + renderer.object = object; + + if ( index !== null ) { + + firstVertex *= index.array.BYTES_PER_ELEMENT; + + const indexData = this.get( index ); + + renderer.index = index.count; + renderer.type = indexData.type; + + } else { + + renderer.index = 0; + + } + + const draw = () => { + + if ( object.isBatchedMesh ) { + + if ( object._multiDrawInstances !== null ) { + + // @deprecated, r174 + warnOnce( 'THREE.WebGLBackend: renderMultiDrawInstances has been deprecated and will be removed in r184. Append to renderMultiDraw arguments and use indirection.' ); + renderer.renderMultiDrawInstances( object._multiDrawStarts, object._multiDrawCounts, object._multiDrawCount, object._multiDrawInstances ); + + } else if ( ! this.hasFeature( 'WEBGL_multi_draw' ) ) { + + warnOnce( 'THREE.WebGLRenderer: WEBGL_multi_draw not supported.' ); + + } else { + + renderer.renderMultiDraw( object._multiDrawStarts, object._multiDrawCounts, object._multiDrawCount ); + + } + + } else if ( instanceCount > 1 ) { + + renderer.renderInstances( firstVertex, vertexCount, instanceCount ); + + } else { + + renderer.render( firstVertex, vertexCount ); + + } + + }; + + if ( renderObject.camera.isArrayCamera === true && renderObject.camera.cameras.length > 0 && renderObject.camera.isMultiViewCamera === false ) { + + const cameraData = this.get( renderObject.camera ); + const cameras = renderObject.camera.cameras; + const cameraIndex = renderObject.getBindingGroup( 'cameraIndex' ).bindings[ 0 ]; + + if ( cameraData.indexesGPU === undefined || cameraData.indexesGPU.length !== cameras.length ) { + + const data = new Uint32Array( [ 0, 0, 0, 0 ] ); + const indexesGPU = []; + + for ( let i = 0, len = cameras.length; i < len; i ++ ) { + + const bufferGPU = gl.createBuffer(); + + data[ 0 ] = i; + + gl.bindBuffer( gl.UNIFORM_BUFFER, bufferGPU ); + gl.bufferData( gl.UNIFORM_BUFFER, data, gl.STATIC_DRAW ); + + indexesGPU.push( bufferGPU ); + + } + + cameraData.indexesGPU = indexesGPU; // TODO: Create a global library for this + + } + + const cameraIndexData = this.get( cameraIndex ); + const pixelRatio = this.renderer.getPixelRatio(); + + const renderTarget = this._currentContext.renderTarget; + const isRenderCameraDepthArray = this._isRenderCameraDepthArray( this._currentContext ); + const prevActiveCubeFace = this._currentContext.activeCubeFace; + + if ( isRenderCameraDepthArray ) { + + // Clear the depth texture + const textureData = this.get( renderTarget.depthTexture ); + + if ( textureData.clearedRenderId !== this.renderer._nodes.nodeFrame.renderId ) { + + textureData.clearedRenderId = this.renderer._nodes.nodeFrame.renderId; + + const { stencilBuffer } = renderTarget; + + for ( let i = 0, len = cameras.length; i < len; i ++ ) { + + this.renderer._activeCubeFace = i; + this._currentContext.activeCubeFace = i; + + this._setFramebuffer( this._currentContext ); + this.clear( false, true, stencilBuffer, this._currentContext, false ); + + } + + this.renderer._activeCubeFace = prevActiveCubeFace; + this._currentContext.activeCubeFace = prevActiveCubeFace; + + } + + } + + for ( let i = 0, len = cameras.length; i < len; i ++ ) { + + const subCamera = cameras[ i ]; + + if ( object.layers.test( subCamera.layers ) ) { + + if ( isRenderCameraDepthArray ) { + + // Update the active layer + this.renderer._activeCubeFace = i; + this._currentContext.activeCubeFace = i; + + this._setFramebuffer( this._currentContext ); + + } + + const vp = subCamera.viewport; + + if ( vp !== undefined ) { + + const x = vp.x * pixelRatio; + const y = vp.y * pixelRatio; + const width = vp.width * pixelRatio; + const height = vp.height * pixelRatio; + + state.viewport( + Math.floor( x ), + Math.floor( renderObject.context.height - height - y ), + Math.floor( width ), + Math.floor( height ) + ); + + } + + state.bindBufferBase( gl.UNIFORM_BUFFER, cameraIndexData.index, cameraData.indexesGPU[ i ] ); + + draw(); + + } + + this._currentContext.activeCubeFace = prevActiveCubeFace; + this.renderer._activeCubeFace = prevActiveCubeFace; + + } + + } else { + + draw(); + + } + + } + + /** + * Explain why always null is returned. + * + * @param {RenderObject} renderObject - The render object. + * @return {boolean} Whether the render pipeline requires an update or not. + */ + needsRenderUpdate( /*renderObject*/ ) { + + return false; + + } + + /** + * Explain why no cache key is computed. + * + * @param {RenderObject} renderObject - The render object. + * @return {string} The cache key. + */ + getRenderCacheKey( /*renderObject*/ ) { + + return ''; + + } + + // textures + + /** + * Creates a default texture for the given texture that can be used + * as a placeholder until the actual texture is ready for usage. + * + * @param {Texture} texture - The texture to create a default texture for. + */ + createDefaultTexture( texture ) { + + this.textureUtils.createDefaultTexture( texture ); + + } + + /** + * Defines a texture on the GPU for the given texture object. + * + * @param {Texture} texture - The texture. + * @param {Object} [options={}] - Optional configuration parameter. + */ + createTexture( texture, options ) { + + this.textureUtils.createTexture( texture, options ); + + } + + /** + * Uploads the updated texture data to the GPU. + * + * @param {Texture} texture - The texture. + * @param {Object} [options={}] - Optional configuration parameter. + */ + updateTexture( texture, options ) { + + this.textureUtils.updateTexture( texture, options ); + + } + + /** + * Generates mipmaps for the given texture. + * + * @param {Texture} texture - The texture. + */ + generateMipmaps( texture ) { + + this.textureUtils.generateMipmaps( texture ); + + } + + /** + * Destroys the GPU data for the given texture object. + * + * @param {Texture} texture - The texture. + */ + destroyTexture( texture ) { + + this.textureUtils.destroyTexture( texture ); + + } + + /** + * Returns texture data as a typed array. + * + * @async + * @param {Texture} texture - The texture to copy. + * @param {number} x - The x coordinate of the copy origin. + * @param {number} y - The y coordinate of the copy origin. + * @param {number} width - The width of the copy. + * @param {number} height - The height of the copy. + * @param {number} faceIndex - The face index. + * @return {Promise} A Promise that resolves with a typed array when the copy operation has finished. + */ + async copyTextureToBuffer( texture, x, y, width, height, faceIndex ) { + + return this.textureUtils.copyTextureToBuffer( texture, x, y, width, height, faceIndex ); + + } + + /** + * This method does nothing since WebGL 2 has no concept of samplers. + * + * @param {Texture} texture - The texture to create the sampler for. + */ + createSampler( /*texture*/ ) { + + //console.warn( 'Abstract class.' ); + + } + + /** + * This method does nothing since WebGL 2 has no concept of samplers. + * + * @param {Texture} texture - The texture to destroy the sampler for. + */ + destroySampler( /*texture*/ ) {} + + // node builder + + /** + * Returns a node builder for the given render object. + * + * @param {RenderObject} object - The render object. + * @param {Renderer} renderer - The renderer. + * @return {GLSLNodeBuilder} The node builder. + */ + createNodeBuilder( object, renderer ) { + + return new GLSLNodeBuilder( object, renderer ); + + } + + // program + + /** + * Creates a shader program from the given programmable stage. + * + * @param {ProgrammableStage} program - The programmable stage. + */ + createProgram( program ) { + + const gl = this.gl; + const { stage, code } = program; + + const shader = stage === 'fragment' ? gl.createShader( gl.FRAGMENT_SHADER ) : gl.createShader( gl.VERTEX_SHADER ); + + gl.shaderSource( shader, code ); + gl.compileShader( shader ); + + this.set( program, { + shaderGPU: shader + } ); + + } + + /** + * Destroys the shader program of the given programmable stage. + * + * @param {ProgrammableStage} program - The programmable stage. + */ + destroyProgram( program ) { + + this.delete( program ); + + } + + /** + * Creates a render pipeline for the given render object. + * + * @param {RenderObject} renderObject - The render object. + * @param {Array} promises - An array of compilation promises which are used in `compileAsync()`. + */ + createRenderPipeline( renderObject, promises ) { + + const gl = this.gl; + const pipeline = renderObject.pipeline; + + // Program + + const { fragmentProgram, vertexProgram } = pipeline; + + const programGPU = gl.createProgram(); + + const fragmentShader = this.get( fragmentProgram ).shaderGPU; + const vertexShader = this.get( vertexProgram ).shaderGPU; + + gl.attachShader( programGPU, fragmentShader ); + gl.attachShader( programGPU, vertexShader ); + gl.linkProgram( programGPU ); + + this.set( pipeline, { + programGPU, + fragmentShader, + vertexShader + } ); + + if ( promises !== null && this.parallel ) { + + const p = new Promise( ( resolve /*, reject*/ ) => { + + const parallel = this.parallel; + const checkStatus = () => { + + if ( gl.getProgramParameter( programGPU, parallel.COMPLETION_STATUS_KHR ) ) { + + this._completeCompile( renderObject, pipeline ); + resolve(); + + } else { + + requestAnimationFrame( checkStatus ); + + } + + }; + + checkStatus(); + + } ); + + promises.push( p ); + + return; + + } + + this._completeCompile( renderObject, pipeline ); + + } + + /** + * Formats the source code of error messages. + * + * @private + * @param {string} string - The code. + * @param {number} errorLine - The error line. + * @return {string} The formatted code. + */ + _handleSource( string, errorLine ) { + + const lines = string.split( '\n' ); + const lines2 = []; + + const from = Math.max( errorLine - 6, 0 ); + const to = Math.min( errorLine + 6, lines.length ); + + for ( let i = from; i < to; i ++ ) { + + const line = i + 1; + lines2.push( `${line === errorLine ? '>' : ' '} ${line}: ${lines[ i ]}` ); + + } + + return lines2.join( '\n' ); + + } + + /** + * Gets the shader compilation errors from the info log. + * + * @private + * @param {WebGL2RenderingContext} gl - The rendering context. + * @param {WebGLShader} shader - The WebGL shader object. + * @param {string} type - The shader type. + * @return {string} The shader errors. + */ + _getShaderErrors( gl, shader, type ) { + + const status = gl.getShaderParameter( shader, gl.COMPILE_STATUS ); + const errors = gl.getShaderInfoLog( shader ).trim(); + + if ( status && errors === '' ) return ''; + + const errorMatches = /ERROR: 0:(\d+)/.exec( errors ); + if ( errorMatches ) { + + const errorLine = parseInt( errorMatches[ 1 ] ); + return type.toUpperCase() + '\n\n' + errors + '\n\n' + this._handleSource( gl.getShaderSource( shader ), errorLine ); + + } else { + + return errors; + + } + + } + + /** + * Logs shader compilation errors. + * + * @private + * @param {WebGLProgram} programGPU - The WebGL program. + * @param {WebGLShader} glFragmentShader - The fragment shader as a native WebGL shader object. + * @param {WebGLShader} glVertexShader - The vertex shader as a native WebGL shader object. + */ + _logProgramError( programGPU, glFragmentShader, glVertexShader ) { + + if ( this.renderer.debug.checkShaderErrors ) { + + const gl = this.gl; + + const programLog = gl.getProgramInfoLog( programGPU ).trim(); + + if ( gl.getProgramParameter( programGPU, gl.LINK_STATUS ) === false ) { + + + if ( typeof this.renderer.debug.onShaderError === 'function' ) { + + this.renderer.debug.onShaderError( gl, programGPU, glVertexShader, glFragmentShader ); + + } else { + + // default error reporting + + const vertexErrors = this._getShaderErrors( gl, glVertexShader, 'vertex' ); + const fragmentErrors = this._getShaderErrors( gl, glFragmentShader, 'fragment' ); + + console.error( + 'THREE.WebGLProgram: Shader Error ' + gl.getError() + ' - ' + + 'VALIDATE_STATUS ' + gl.getProgramParameter( programGPU, gl.VALIDATE_STATUS ) + '\n\n' + + 'Program Info Log: ' + programLog + '\n' + + vertexErrors + '\n' + + fragmentErrors + ); + + } + + } else if ( programLog !== '' ) { + + console.warn( 'THREE.WebGLProgram: Program Info Log:', programLog ); + + } + + } + + } + + /** + * Completes the shader program setup for the given render object. + * + * @private + * @param {RenderObject} renderObject - The render object. + * @param {RenderPipeline} pipeline - The render pipeline. + */ + _completeCompile( renderObject, pipeline ) { + + const { state, gl } = this; + const pipelineData = this.get( pipeline ); + const { programGPU, fragmentShader, vertexShader } = pipelineData; + + if ( gl.getProgramParameter( programGPU, gl.LINK_STATUS ) === false ) { + + this._logProgramError( programGPU, fragmentShader, vertexShader ); + + } + + state.useProgram( programGPU ); + + // Bindings + + const bindings = renderObject.getBindings(); + + this._setupBindings( bindings, programGPU ); + + // + + this.set( pipeline, { + programGPU + } ); + + } + + /** + * Creates a compute pipeline for the given compute node. + * + * @param {ComputePipeline} computePipeline - The compute pipeline. + * @param {Array} bindings - The bindings. + */ + createComputePipeline( computePipeline, bindings ) { + + const { state, gl } = this; + + // Program + + const fragmentProgram = { + stage: 'fragment', + code: '#version 300 es\nprecision highp float;\nvoid main() {}' + }; + + this.createProgram( fragmentProgram ); + + const { computeProgram } = computePipeline; + + const programGPU = gl.createProgram(); + + const fragmentShader = this.get( fragmentProgram ).shaderGPU; + const vertexShader = this.get( computeProgram ).shaderGPU; + + const transforms = computeProgram.transforms; + + const transformVaryingNames = []; + const transformAttributeNodes = []; + + for ( let i = 0; i < transforms.length; i ++ ) { + + const transform = transforms[ i ]; + + transformVaryingNames.push( transform.varyingName ); + transformAttributeNodes.push( transform.attributeNode ); + + } + + gl.attachShader( programGPU, fragmentShader ); + gl.attachShader( programGPU, vertexShader ); + + gl.transformFeedbackVaryings( + programGPU, + transformVaryingNames, + gl.SEPARATE_ATTRIBS + ); + + gl.linkProgram( programGPU ); + + if ( gl.getProgramParameter( programGPU, gl.LINK_STATUS ) === false ) { + + this._logProgramError( programGPU, fragmentShader, vertexShader ); + + + } + + state.useProgram( programGPU ); + + // Bindings + + this._setupBindings( bindings, programGPU ); + + const attributeNodes = computeProgram.attributes; + const attributes = []; + const transformBuffers = []; + + for ( let i = 0; i < attributeNodes.length; i ++ ) { + + const attribute = attributeNodes[ i ].node.attribute; + + attributes.push( attribute ); + + if ( ! this.has( attribute ) ) this.attributeUtils.createAttribute( attribute, gl.ARRAY_BUFFER ); + + } + + for ( let i = 0; i < transformAttributeNodes.length; i ++ ) { + + const attribute = transformAttributeNodes[ i ].attribute; + + if ( ! this.has( attribute ) ) this.attributeUtils.createAttribute( attribute, gl.ARRAY_BUFFER ); + + const attributeData = this.get( attribute ); + + transformBuffers.push( attributeData ); + + } + + // + + this.set( computePipeline, { + programGPU, + transformBuffers, + attributes + } ); + + } + + /** + * Creates bindings from the given bind group definition. + * + * @param {BindGroup} bindGroup - The bind group. + * @param {Array} bindings - Array of bind groups. + * @param {number} cacheIndex - The cache index. + * @param {number} version - The version. + */ + createBindings( bindGroup, bindings /*, cacheIndex, version*/ ) { + + if ( this._knownBindings.has( bindings ) === false ) { + + this._knownBindings.add( bindings ); + + let uniformBuffers = 0; + let textures = 0; + + for ( const bindGroup of bindings ) { + + this.set( bindGroup, { + textures: textures, + uniformBuffers: uniformBuffers + } ); + + for ( const binding of bindGroup.bindings ) { + + if ( binding.isUniformBuffer ) uniformBuffers ++; + if ( binding.isSampledTexture ) textures ++; + + } + + } + + } + + this.updateBindings( bindGroup, bindings ); + + } + + /** + * Updates the given bind group definition. + * + * @param {BindGroup} bindGroup - The bind group. + * @param {Array} bindings - Array of bind groups. + * @param {number} cacheIndex - The cache index. + * @param {number} version - The version. + */ + updateBindings( bindGroup /*, bindings, cacheIndex, version*/ ) { + + const { gl } = this; + + const bindGroupData = this.get( bindGroup ); + + let i = bindGroupData.uniformBuffers; + let t = bindGroupData.textures; + + for ( const binding of bindGroup.bindings ) { + + if ( binding.isUniformsGroup || binding.isUniformBuffer ) { + + const data = binding.buffer; + const bufferGPU = gl.createBuffer(); + + gl.bindBuffer( gl.UNIFORM_BUFFER, bufferGPU ); + gl.bufferData( gl.UNIFORM_BUFFER, data, gl.DYNAMIC_DRAW ); + + this.set( binding, { + index: i ++, + bufferGPU + } ); + + } else if ( binding.isSampledTexture ) { + + const { textureGPU, glTextureType } = this.get( binding.texture ); + + this.set( binding, { + index: t ++, + textureGPU, + glTextureType + } ); + + } + + } + + } + + /** + * Updates a buffer binding. + * + * @param {Buffer} binding - The buffer binding to update. + */ + updateBinding( binding ) { + + const gl = this.gl; + + if ( binding.isUniformsGroup || binding.isUniformBuffer ) { + + const bindingData = this.get( binding ); + const bufferGPU = bindingData.bufferGPU; + const data = binding.buffer; + + gl.bindBuffer( gl.UNIFORM_BUFFER, bufferGPU ); + gl.bufferData( gl.UNIFORM_BUFFER, data, gl.DYNAMIC_DRAW ); + + } + + } + + // attributes + + /** + * Creates the GPU buffer of an indexed shader attribute. + * + * @param {BufferAttribute} attribute - The indexed buffer attribute. + */ + createIndexAttribute( attribute ) { + + const gl = this.gl; + + this.attributeUtils.createAttribute( attribute, gl.ELEMENT_ARRAY_BUFFER ); + + } + + /** + * Creates the GPU buffer of a shader attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute. + */ + createAttribute( attribute ) { + + if ( this.has( attribute ) ) return; + + const gl = this.gl; + + this.attributeUtils.createAttribute( attribute, gl.ARRAY_BUFFER ); + + } + + /** + * Creates the GPU buffer of a storage attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute. + */ + createStorageAttribute( attribute ) { + + if ( this.has( attribute ) ) return; + + const gl = this.gl; + + this.attributeUtils.createAttribute( attribute, gl.ARRAY_BUFFER ); + + } + + /** + * Updates the GPU buffer of a shader attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute to update. + */ + updateAttribute( attribute ) { + + this.attributeUtils.updateAttribute( attribute ); + + } + + /** + * Destroys the GPU buffer of a shader attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute to destroy. + */ + destroyAttribute( attribute ) { + + this.attributeUtils.destroyAttribute( attribute ); + + } + + /** + * Checks if the given feature is supported by the backend. + * + * @param {string} name - The feature's name. + * @return {boolean} Whether the feature is supported or not. + */ + hasFeature( name ) { + + const keysMatching = Object.keys( GLFeatureName ).filter( key => GLFeatureName[ key ] === name ); + + const extensions = this.extensions; + + for ( let i = 0; i < keysMatching.length; i ++ ) { + + if ( extensions.has( keysMatching[ i ] ) ) return true; + + } + + return false; + + } + + /** + * Returns the maximum anisotropy texture filtering value. + * + * @return {number} The maximum anisotropy texture filtering value. + */ + getMaxAnisotropy() { + + return this.capabilities.getMaxAnisotropy(); + + } + + /** + * Copies data of the given source texture to the given destination texture. + * + * @param {Texture} srcTexture - The source texture. + * @param {Texture} dstTexture - The destination texture. + * @param {?(Box3|Box2)} [srcRegion=null] - The region of the source texture to copy. + * @param {?(Vector2|Vector3)} [dstPosition=null] - The destination position of the copy. + * @param {number} [srcLevel=0] - The source mip level to copy from. + * @param {number} [dstLevel=0] - The destination mip level to copy to. + */ + copyTextureToTexture( srcTexture, dstTexture, srcRegion = null, dstPosition = null, srcLevel = 0, dstLevel = 0 ) { + + this.textureUtils.copyTextureToTexture( srcTexture, dstTexture, srcRegion, dstPosition, srcLevel, dstLevel ); + + } + + /** + * Copies the current bound framebuffer to the given texture. + * + * @param {Texture} texture - The destination texture. + * @param {RenderContext} renderContext - The render context. + * @param {Vector4} rectangle - A four dimensional vector defining the origin and dimension of the copy. + */ + copyFramebufferToTexture( texture, renderContext, rectangle ) { + + this.textureUtils.copyFramebufferToTexture( texture, renderContext, rectangle ); + + } + + /** + * Configures the active framebuffer from the given render context. + * + * @private + * @param {RenderContext} descriptor - The render context. + */ + _setFramebuffer( descriptor ) { + + const { gl, state } = this; + + let currentFrameBuffer = null; + + if ( descriptor.textures !== null ) { + + const renderTarget = descriptor.renderTarget; + const renderTargetContextData = this.get( renderTarget ); + const { samples, depthBuffer, stencilBuffer } = renderTarget; + + const isCube = renderTarget.isWebGLCubeRenderTarget === true; + const isRenderTarget3D = renderTarget.isRenderTarget3D === true; + const isRenderTargetArray = renderTarget.depth > 1; + const isXRRenderTarget = renderTarget.isXRRenderTarget === true; + const _hasExternalTextures = ( isXRRenderTarget === true && renderTarget._hasExternalTextures === true ); + + let msaaFb = renderTargetContextData.msaaFrameBuffer; + let depthRenderbuffer = renderTargetContextData.depthRenderbuffer; + const multisampledRTTExt = this.extensions.get( 'WEBGL_multisampled_render_to_texture' ); + const multiviewExt = this.extensions.get( 'OVR_multiview2' ); + const useMultisampledRTT = this._useMultisampledExtension( renderTarget ); + const cacheKey = getCacheKey( descriptor ); + + let fb; + + if ( isCube ) { + + renderTargetContextData.cubeFramebuffers || ( renderTargetContextData.cubeFramebuffers = {} ); + + fb = renderTargetContextData.cubeFramebuffers[ cacheKey ]; + + } else if ( isXRRenderTarget && _hasExternalTextures === false ) { + + fb = this._xrFramebuffer; + + } else { + + renderTargetContextData.framebuffers || ( renderTargetContextData.framebuffers = {} ); + + fb = renderTargetContextData.framebuffers[ cacheKey ]; + + } + + if ( fb === undefined ) { + + fb = gl.createFramebuffer(); + + state.bindFramebuffer( gl.FRAMEBUFFER, fb ); + + const textures = descriptor.textures; + const depthInvalidationArray = []; + + if ( isCube ) { + + renderTargetContextData.cubeFramebuffers[ cacheKey ] = fb; + + const { textureGPU } = this.get( textures[ 0 ] ); + + const cubeFace = this.renderer._activeCubeFace; + + gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X + cubeFace, textureGPU, 0 ); + + } else { + + renderTargetContextData.framebuffers[ cacheKey ] = fb; + + for ( let i = 0; i < textures.length; i ++ ) { + + const texture = textures[ i ]; + const textureData = this.get( texture ); + textureData.renderTarget = descriptor.renderTarget; + textureData.cacheKey = cacheKey; // required for copyTextureToTexture() + + const attachment = gl.COLOR_ATTACHMENT0 + i; + + if ( renderTarget.multiview ) { + + multiviewExt.framebufferTextureMultisampleMultiviewOVR( gl.FRAMEBUFFER, attachment, textureData.textureGPU, 0, samples, 0, 2 ); + + } else if ( isRenderTarget3D || isRenderTargetArray ) { + + const layer = this.renderer._activeCubeFace; + + gl.framebufferTextureLayer( gl.FRAMEBUFFER, attachment, textureData.textureGPU, 0, layer ); + + } else { + + if ( useMultisampledRTT ) { + + multisampledRTTExt.framebufferTexture2DMultisampleEXT( gl.FRAMEBUFFER, attachment, gl.TEXTURE_2D, textureData.textureGPU, 0, samples ); + + } else { + + gl.framebufferTexture2D( gl.FRAMEBUFFER, attachment, gl.TEXTURE_2D, textureData.textureGPU, 0 ); + + } + + } + + } + + } + + const depthStyle = stencilBuffer ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT; + + if ( renderTarget._autoAllocateDepthBuffer === true ) { + + const renderbuffer = gl.createRenderbuffer(); + this.textureUtils.setupRenderBufferStorage( renderbuffer, descriptor, 0, useMultisampledRTT ); + renderTargetContextData.xrDepthRenderbuffer = renderbuffer; + depthInvalidationArray.push( stencilBuffer ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT ); + + gl.bindRenderbuffer( gl.RENDERBUFFER, renderbuffer ); + gl.framebufferRenderbuffer( gl.FRAMEBUFFER, depthStyle, gl.RENDERBUFFER, renderbuffer ); + + + } else { + + if ( descriptor.depthTexture !== null ) { + + depthInvalidationArray.push( stencilBuffer ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT ); + + const textureData = this.get( descriptor.depthTexture ); + textureData.renderTarget = descriptor.renderTarget; + textureData.cacheKey = cacheKey; // required for copyTextureToTexture() + + if ( renderTarget.multiview ) { + + multiviewExt.framebufferTextureMultisampleMultiviewOVR( gl.FRAMEBUFFER, depthStyle, textureData.textureGPU, 0, samples, 0, 2 ); + + } else if ( _hasExternalTextures && useMultisampledRTT ) { + + multisampledRTTExt.framebufferTexture2DMultisampleEXT( gl.FRAMEBUFFER, depthStyle, gl.TEXTURE_2D, textureData.textureGPU, 0, samples ); + + } else { + + if ( descriptor.depthTexture.isArrayTexture ) { + + const layer = this.renderer._activeCubeFace; + + gl.framebufferTextureLayer( gl.FRAMEBUFFER, depthStyle, textureData.textureGPU, 0, layer ); + + } else { + + gl.framebufferTexture2D( gl.FRAMEBUFFER, depthStyle, gl.TEXTURE_2D, textureData.textureGPU, 0 ); + + } + + } + + } + + } + + renderTargetContextData.depthInvalidationArray = depthInvalidationArray; + + + } else { + + const isRenderCameraDepthArray = this._isRenderCameraDepthArray( descriptor ); + + if ( isRenderCameraDepthArray ) { + + state.bindFramebuffer( gl.FRAMEBUFFER, fb ); + + const layer = this.renderer._activeCubeFace; + + const depthData = this.get( descriptor.depthTexture ); + const depthStyle = stencilBuffer ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT; + gl.framebufferTextureLayer( + gl.FRAMEBUFFER, + depthStyle, + depthData.textureGPU, + 0, + layer + ); + + } + + // rebind external XR textures + + if ( ( isXRRenderTarget || useMultisampledRTT || renderTarget.multiview ) && ( renderTarget._isOpaqueFramebuffer !== true ) ) { + + state.bindFramebuffer( gl.FRAMEBUFFER, fb ); + + // rebind color + + const textureData = this.get( descriptor.textures[ 0 ] ); + + if ( renderTarget.multiview ) { + + multiviewExt.framebufferTextureMultisampleMultiviewOVR( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, textureData.textureGPU, 0, samples, 0, 2 ); + + } else if ( useMultisampledRTT ) { + + multisampledRTTExt.framebufferTexture2DMultisampleEXT( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, textureData.textureGPU, 0, samples ); + + } else { + + gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, textureData.textureGPU, 0 ); + + } + + // rebind depth + + const depthStyle = stencilBuffer ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT; + + if ( renderTarget._autoAllocateDepthBuffer === true ) { + + const renderbuffer = renderTargetContextData.xrDepthRenderbuffer; + gl.bindRenderbuffer( gl.RENDERBUFFER, renderbuffer ); + gl.framebufferRenderbuffer( gl.FRAMEBUFFER, depthStyle, gl.RENDERBUFFER, renderbuffer ); + + } else { + + const textureData = this.get( descriptor.depthTexture ); + + if ( renderTarget.multiview ) { + + multiviewExt.framebufferTextureMultisampleMultiviewOVR( gl.FRAMEBUFFER, depthStyle, textureData.textureGPU, 0, samples, 0, 2 ); + + } else if ( useMultisampledRTT ) { + + multisampledRTTExt.framebufferTexture2DMultisampleEXT( gl.FRAMEBUFFER, depthStyle, gl.TEXTURE_2D, textureData.textureGPU, 0, samples ); + + } else { + + gl.framebufferTexture2D( gl.FRAMEBUFFER, depthStyle, gl.TEXTURE_2D, textureData.textureGPU, 0 ); + + } + + } + + } + + } + + if ( samples > 0 && useMultisampledRTT === false && ! renderTarget.multiview ) { + + if ( msaaFb === undefined ) { + + const invalidationArray = []; + + msaaFb = gl.createFramebuffer(); + + state.bindFramebuffer( gl.FRAMEBUFFER, msaaFb ); + + const msaaRenderbuffers = []; + + const textures = descriptor.textures; + + for ( let i = 0; i < textures.length; i ++ ) { + + msaaRenderbuffers[ i ] = gl.createRenderbuffer(); + + gl.bindRenderbuffer( gl.RENDERBUFFER, msaaRenderbuffers[ i ] ); + + invalidationArray.push( gl.COLOR_ATTACHMENT0 + i ); + + const texture = descriptor.textures[ i ]; + const textureData = this.get( texture ); + + gl.renderbufferStorageMultisample( gl.RENDERBUFFER, samples, textureData.glInternalFormat, descriptor.width, descriptor.height ); + gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.RENDERBUFFER, msaaRenderbuffers[ i ] ); + + + } + + gl.bindRenderbuffer( gl.RENDERBUFFER, null ); + + renderTargetContextData.msaaFrameBuffer = msaaFb; + renderTargetContextData.msaaRenderbuffers = msaaRenderbuffers; + + if ( depthBuffer && depthRenderbuffer === undefined ) { + + depthRenderbuffer = gl.createRenderbuffer(); + this.textureUtils.setupRenderBufferStorage( depthRenderbuffer, descriptor, samples ); + + renderTargetContextData.depthRenderbuffer = depthRenderbuffer; + + const depthStyle = stencilBuffer ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT; + invalidationArray.push( depthStyle ); + + } + + renderTargetContextData.invalidationArray = invalidationArray; + + } + + currentFrameBuffer = renderTargetContextData.msaaFrameBuffer; + + } else { + + currentFrameBuffer = fb; + + } + + state.drawBuffers( descriptor, fb ); + + } + + state.bindFramebuffer( gl.FRAMEBUFFER, currentFrameBuffer ); + + } + + /** + * Computes the VAO key for the given index and attributes. + * + * @private + * @param {Array} attributes - An array of buffer attributes. + * @return {string} The VAO key. + */ + _getVaoKey( attributes ) { + + let key = ''; + + for ( let i = 0; i < attributes.length; i ++ ) { + + const attributeData = this.get( attributes[ i ] ); + + key += ':' + attributeData.id; + + } + + return key; + + } + + /** + * Creates a VAO from the index and attributes. + * + * @private + * @param {Array} attributes - An array of buffer attributes. + * @return {Object} The VAO data. + */ + _createVao( attributes ) { + + const { gl } = this; + + const vaoGPU = gl.createVertexArray(); + + gl.bindVertexArray( vaoGPU ); + + for ( let i = 0; i < attributes.length; i ++ ) { + + const attribute = attributes[ i ]; + const attributeData = this.get( attribute ); + + gl.bindBuffer( gl.ARRAY_BUFFER, attributeData.bufferGPU ); + gl.enableVertexAttribArray( i ); + + let stride, offset; + + if ( attribute.isInterleavedBufferAttribute === true ) { + + stride = attribute.data.stride * attributeData.bytesPerElement; + offset = attribute.offset * attributeData.bytesPerElement; + + } else { + + stride = 0; + offset = 0; + + } + + if ( attributeData.isInteger ) { + + gl.vertexAttribIPointer( i, attribute.itemSize, attributeData.type, stride, offset ); + + } else { + + gl.vertexAttribPointer( i, attribute.itemSize, attributeData.type, attribute.normalized, stride, offset ); + + } + + if ( attribute.isInstancedBufferAttribute && ! attribute.isInterleavedBufferAttribute ) { + + gl.vertexAttribDivisor( i, attribute.meshPerAttribute ); + + } else if ( attribute.isInterleavedBufferAttribute && attribute.data.isInstancedInterleavedBuffer ) { + + gl.vertexAttribDivisor( i, attribute.data.meshPerAttribute ); + + } + + } + + gl.bindBuffer( gl.ARRAY_BUFFER, null ); + + return vaoGPU; + + } + + /** + * Creates a transform feedback from the given transform buffers. + * + * @private + * @param {Array} transformBuffers - The transform buffers. + * @return {WebGLTransformFeedback} The transform feedback. + */ + _getTransformFeedback( transformBuffers ) { + + let key = ''; + + for ( let i = 0; i < transformBuffers.length; i ++ ) { + + key += ':' + transformBuffers[ i ].id; + + } + + let transformFeedbackGPU = this.transformFeedbackCache[ key ]; + + if ( transformFeedbackGPU !== undefined ) { + + return transformFeedbackGPU; + + } + + const { gl } = this; + + transformFeedbackGPU = gl.createTransformFeedback(); + + gl.bindTransformFeedback( gl.TRANSFORM_FEEDBACK, transformFeedbackGPU ); + + for ( let i = 0; i < transformBuffers.length; i ++ ) { + + const attributeData = transformBuffers[ i ]; + + gl.bindBufferBase( gl.TRANSFORM_FEEDBACK_BUFFER, i, attributeData.transformBuffer ); + + } + + gl.bindTransformFeedback( gl.TRANSFORM_FEEDBACK, null ); + + this.transformFeedbackCache[ key ] = transformFeedbackGPU; + + return transformFeedbackGPU; + + } + + /** + * Setups the given bindings. + * + * @private + * @param {Array} bindings - The bindings. + * @param {WebGLProgram} programGPU - The WebGL program. + */ + _setupBindings( bindings, programGPU ) { + + const gl = this.gl; + + for ( const bindGroup of bindings ) { + + for ( const binding of bindGroup.bindings ) { + + const bindingData = this.get( binding ); + const index = bindingData.index; + + if ( binding.isUniformsGroup || binding.isUniformBuffer ) { + + const location = gl.getUniformBlockIndex( programGPU, binding.name ); + gl.uniformBlockBinding( programGPU, location, index ); + + } else if ( binding.isSampledTexture ) { + + const location = gl.getUniformLocation( programGPU, binding.name ); + gl.uniform1i( location, index ); + + } + + } + + } + + } + + /** + * Binds the given uniforms. + * + * @private + * @param {Array} bindings - The bindings. + */ + _bindUniforms( bindings ) { + + const { gl, state } = this; + + for ( const bindGroup of bindings ) { + + for ( const binding of bindGroup.bindings ) { + + const bindingData = this.get( binding ); + const index = bindingData.index; + + if ( binding.isUniformsGroup || binding.isUniformBuffer ) { + + // TODO USE bindBufferRange to group multiple uniform buffers + state.bindBufferBase( gl.UNIFORM_BUFFER, index, bindingData.bufferGPU ); + + } else if ( binding.isSampledTexture ) { + + state.bindTexture( bindingData.glTextureType, bindingData.textureGPU, gl.TEXTURE0 + index ); + + } + + } + + } + + } + + /** + * Returns `true` if the `WEBGL_multisampled_render_to_texture` extension + * should be used when MSAA is enabled. + * + * @private + * @param {RenderTarget} renderTarget - The render target that should be multisampled. + * @return {boolean} Whether to use the `WEBGL_multisampled_render_to_texture` extension for MSAA or not. + */ + _useMultisampledExtension( renderTarget ) { + + if ( renderTarget.multiview === true ) { + + return true; + + } + + return renderTarget.samples > 0 && this.extensions.has( 'WEBGL_multisampled_render_to_texture' ) === true && renderTarget._autoAllocateDepthBuffer !== false; + + } + + /** + * Frees internal resources. + */ + dispose() { + + const extension = this.extensions.get( 'WEBGL_lose_context' ); + if ( extension ) extension.loseContext(); + + this.renderer.domElement.removeEventListener( 'webglcontextlost', this._onContextLost ); + + } + +} + +const GPUPrimitiveTopology = { + PointList: 'point-list', + LineList: 'line-list', + LineStrip: 'line-strip', + TriangleList: 'triangle-list', + TriangleStrip: 'triangle-strip', +}; + +const GPUCompareFunction = { + Never: 'never', + Less: 'less', + Equal: 'equal', + LessEqual: 'less-equal', + Greater: 'greater', + NotEqual: 'not-equal', + GreaterEqual: 'greater-equal', + Always: 'always' +}; + +const GPUStoreOp = { + Store: 'store'}; + +const GPULoadOp = { + Load: 'load', + Clear: 'clear' +}; + +const GPUFrontFace = { + CCW: 'ccw'}; + +const GPUCullMode = { + None: 'none', + Front: 'front', + Back: 'back' +}; + +const GPUIndexFormat = { + Uint16: 'uint16', + Uint32: 'uint32' +}; + +const GPUTextureFormat = { + + // 8-bit formats + + R8Unorm: 'r8unorm', + R8Snorm: 'r8snorm', + R8Uint: 'r8uint', + R8Sint: 'r8sint', + + // 16-bit formats + + R16Uint: 'r16uint', + R16Sint: 'r16sint', + R16Float: 'r16float', + RG8Unorm: 'rg8unorm', + RG8Snorm: 'rg8snorm', + RG8Uint: 'rg8uint', + RG8Sint: 'rg8sint', + + // 32-bit formats + + R32Uint: 'r32uint', + R32Sint: 'r32sint', + R32Float: 'r32float', + RG16Uint: 'rg16uint', + RG16Sint: 'rg16sint', + RG16Float: 'rg16float', + RGBA8Unorm: 'rgba8unorm', + RGBA8UnormSRGB: 'rgba8unorm-srgb', + RGBA8Snorm: 'rgba8snorm', + RGBA8Uint: 'rgba8uint', + RGBA8Sint: 'rgba8sint', + BGRA8Unorm: 'bgra8unorm', + BGRA8UnormSRGB: 'bgra8unorm-srgb', + // Packed 32-bit formats + RGB9E5UFloat: 'rgb9e5ufloat', + RGB10A2Unorm: 'rgb10a2unorm', + RG11B10UFloat: 'rgb10a2unorm', + + // 64-bit formats + + RG32Uint: 'rg32uint', + RG32Sint: 'rg32sint', + RG32Float: 'rg32float', + RGBA16Uint: 'rgba16uint', + RGBA16Sint: 'rgba16sint', + RGBA16Float: 'rgba16float', + + // 128-bit formats + + RGBA32Uint: 'rgba32uint', + RGBA32Sint: 'rgba32sint', + RGBA32Float: 'rgba32float', + + Depth16Unorm: 'depth16unorm', + Depth24Plus: 'depth24plus', + Depth24PlusStencil8: 'depth24plus-stencil8', + Depth32Float: 'depth32float', + + // 'depth32float-stencil8' extension + + Depth32FloatStencil8: 'depth32float-stencil8', + + // BC compressed formats usable if 'texture-compression-bc' is both + // supported by the device/user agent and enabled in requestDevice. + + BC1RGBAUnorm: 'bc1-rgba-unorm', + BC1RGBAUnormSRGB: 'bc1-rgba-unorm-srgb', + BC2RGBAUnorm: 'bc2-rgba-unorm', + BC2RGBAUnormSRGB: 'bc2-rgba-unorm-srgb', + BC3RGBAUnorm: 'bc3-rgba-unorm', + BC3RGBAUnormSRGB: 'bc3-rgba-unorm-srgb', + BC4RUnorm: 'bc4-r-unorm', + BC4RSnorm: 'bc4-r-snorm', + BC5RGUnorm: 'bc5-rg-unorm', + BC5RGSnorm: 'bc5-rg-snorm', + BC6HRGBUFloat: 'bc6h-rgb-ufloat', + BC6HRGBFloat: 'bc6h-rgb-float', + BC7RGBAUnorm: 'bc7-rgba-unorm', + BC7RGBAUnormSRGB: 'bc7-rgba-srgb', + + // ETC2 compressed formats usable if 'texture-compression-etc2' is both + // supported by the device/user agent and enabled in requestDevice. + + ETC2RGB8Unorm: 'etc2-rgb8unorm', + ETC2RGB8UnormSRGB: 'etc2-rgb8unorm-srgb', + ETC2RGB8A1Unorm: 'etc2-rgb8a1unorm', + ETC2RGB8A1UnormSRGB: 'etc2-rgb8a1unorm-srgb', + ETC2RGBA8Unorm: 'etc2-rgba8unorm', + ETC2RGBA8UnormSRGB: 'etc2-rgba8unorm-srgb', + EACR11Unorm: 'eac-r11unorm', + EACR11Snorm: 'eac-r11snorm', + EACRG11Unorm: 'eac-rg11unorm', + EACRG11Snorm: 'eac-rg11snorm', + + // ASTC compressed formats usable if 'texture-compression-astc' is both + // supported by the device/user agent and enabled in requestDevice. + + ASTC4x4Unorm: 'astc-4x4-unorm', + ASTC4x4UnormSRGB: 'astc-4x4-unorm-srgb', + ASTC5x4Unorm: 'astc-5x4-unorm', + ASTC5x4UnormSRGB: 'astc-5x4-unorm-srgb', + ASTC5x5Unorm: 'astc-5x5-unorm', + ASTC5x5UnormSRGB: 'astc-5x5-unorm-srgb', + ASTC6x5Unorm: 'astc-6x5-unorm', + ASTC6x5UnormSRGB: 'astc-6x5-unorm-srgb', + ASTC6x6Unorm: 'astc-6x6-unorm', + ASTC6x6UnormSRGB: 'astc-6x6-unorm-srgb', + ASTC8x5Unorm: 'astc-8x5-unorm', + ASTC8x5UnormSRGB: 'astc-8x5-unorm-srgb', + ASTC8x6Unorm: 'astc-8x6-unorm', + ASTC8x6UnormSRGB: 'astc-8x6-unorm-srgb', + ASTC8x8Unorm: 'astc-8x8-unorm', + ASTC8x8UnormSRGB: 'astc-8x8-unorm-srgb', + ASTC10x5Unorm: 'astc-10x5-unorm', + ASTC10x5UnormSRGB: 'astc-10x5-unorm-srgb', + ASTC10x6Unorm: 'astc-10x6-unorm', + ASTC10x6UnormSRGB: 'astc-10x6-unorm-srgb', + ASTC10x8Unorm: 'astc-10x8-unorm', + ASTC10x8UnormSRGB: 'astc-10x8-unorm-srgb', + ASTC10x10Unorm: 'astc-10x10-unorm', + ASTC10x10UnormSRGB: 'astc-10x10-unorm-srgb', + ASTC12x10Unorm: 'astc-12x10-unorm', + ASTC12x10UnormSRGB: 'astc-12x10-unorm-srgb', + ASTC12x12Unorm: 'astc-12x12-unorm', + ASTC12x12UnormSRGB: 'astc-12x12-unorm-srgb', + +}; + +const GPUAddressMode = { + ClampToEdge: 'clamp-to-edge', + Repeat: 'repeat', + MirrorRepeat: 'mirror-repeat' +}; + +const GPUFilterMode = { + Linear: 'linear', + Nearest: 'nearest' +}; + +const GPUBlendFactor = { + Zero: 'zero', + One: 'one', + Src: 'src', + OneMinusSrc: 'one-minus-src', + SrcAlpha: 'src-alpha', + OneMinusSrcAlpha: 'one-minus-src-alpha', + Dst: 'dst', + OneMinusDst: 'one-minus-dst', + DstAlpha: 'dst-alpha', + OneMinusDstAlpha: 'one-minus-dst-alpha', + SrcAlphaSaturated: 'src-alpha-saturated', + Constant: 'constant', + OneMinusConstant: 'one-minus-constant' +}; + +const GPUBlendOperation = { + Add: 'add', + Subtract: 'subtract', + ReverseSubtract: 'reverse-subtract', + Min: 'min', + Max: 'max' +}; + +const GPUColorWriteFlags = { + None: 0, + All: 0xF +}; + +const GPUStencilOperation = { + Keep: 'keep', + Zero: 'zero', + Replace: 'replace', + Invert: 'invert', + IncrementClamp: 'increment-clamp', + DecrementClamp: 'decrement-clamp', + IncrementWrap: 'increment-wrap', + DecrementWrap: 'decrement-wrap' +}; + +const GPUBufferBindingType = { + Storage: 'storage', + ReadOnlyStorage: 'read-only-storage' +}; + +const GPUStorageTextureAccess = { + WriteOnly: 'write-only', + ReadOnly: 'read-only', + ReadWrite: 'read-write', +}; + +const GPUSamplerBindingType = { + NonFiltering: 'non-filtering', + Comparison: 'comparison' +}; + +const GPUTextureSampleType = { + Float: 'float', + UnfilterableFloat: 'unfilterable-float', + Depth: 'depth', + SInt: 'sint', + UInt: 'uint' +}; + +const GPUTextureDimension = { + TwoD: '2d', + ThreeD: '3d' +}; + +const GPUTextureViewDimension = { + TwoD: '2d', + TwoDArray: '2d-array', + Cube: 'cube', + ThreeD: '3d' +}; + +const GPUTextureAspect = { + All: 'all'}; + +const GPUInputStepMode = { + Vertex: 'vertex', + Instance: 'instance' +}; + +const GPUFeatureName = { + CoreFeaturesAndLimits: 'core-features-and-limits', + DepthClipControl: 'depth-clip-control', + Depth32FloatStencil8: 'depth32float-stencil8', + TextureCompressionBC: 'texture-compression-bc', + TextureCompressionBCSliced3D: 'texture-compression-bc-sliced-3d', + TextureCompressionETC2: 'texture-compression-etc2', + TextureCompressionASTC: 'texture-compression-astc', + TextureCompressionASTCSliced3D: 'texture-compression-astc-sliced-3d', + TimestampQuery: 'timestamp-query', + IndirectFirstInstance: 'indirect-first-instance', + ShaderF16: 'shader-f16', + RG11B10UFloat: 'rg11b10ufloat-renderable', + BGRA8UNormStorage: 'bgra8unorm-storage', + Float32Filterable: 'float32-filterable', + Float32Blendable: 'float32-blendable', + ClipDistances: 'clip-distances', + DualSourceBlending: 'dual-source-blending', + Subgroups: 'subgroups', + TextureFormatsTier1: 'texture-formats-tier1', + TextureFormatsTier2: 'texture-formats-tier2' +}; + +/** + * Represents a sampler binding type. + * + * @private + * @augments Binding + */ +class Sampler extends Binding { + + /** + * Constructs a new sampler. + * + * @param {string} name - The samplers's name. + * @param {?Texture} texture - The texture this binding is referring to. + */ + constructor( name, texture ) { + + super( name ); + + /** + * The texture the sampler is referring to. + * + * @type {?Texture} + */ + this.texture = texture; + + /** + * The binding's version. + * + * @type {number} + */ + this.version = texture ? texture.version : 0; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSampler = true; + + } + +} + +/** + * A special form of sampler binding type. + * It's texture value is managed by a node object. + * + * @private + * @augments Sampler + */ +class NodeSampler extends Sampler { + + /** + * Constructs a new node-based sampler. + * + * @param {string} name - The samplers's name. + * @param {TextureNode} textureNode - The texture node. + * @param {UniformGroupNode} groupNode - The uniform group node. + */ + constructor( name, textureNode, groupNode ) { + + super( name, textureNode ? textureNode.value : null ); + + /** + * The texture node. + * + * @type {TextureNode} + */ + this.textureNode = textureNode; + + /** + * The uniform group node. + * + * @type {UniformGroupNode} + */ + this.groupNode = groupNode; + + } + + /** + * Updates the texture value of this sampler. + */ + update() { + + this.texture = this.textureNode.value; + + } + +} + +/** + * Represents a storage buffer binding type. + * + * @private + * @augments Buffer + */ +class StorageBuffer extends Buffer { + + /** + * Constructs a new uniform buffer. + * + * @param {string} name - The buffer's name. + * @param {BufferAttribute} attribute - The buffer attribute. + */ + constructor( name, attribute ) { + + super( name, attribute ? attribute.array : null ); + + /** + * This flag can be used for type testing. + * + * @type {BufferAttribute} + */ + this.attribute = attribute; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isStorageBuffer = true; + + } + +} + +let _id = 0; + +/** + * A special form of storage buffer binding type. + * It's buffer value is managed by a node object. + * + * @private + * @augments StorageBuffer + */ +class NodeStorageBuffer extends StorageBuffer { + + /** + * Constructs a new node-based storage buffer. + * + * @param {StorageBufferNode} nodeUniform - The storage buffer node. + * @param {UniformGroupNode} groupNode - The uniform group node. + */ + constructor( nodeUniform, groupNode ) { + + super( 'StorageBuffer_' + _id ++, nodeUniform ? nodeUniform.value : null ); + + /** + * The node uniform. + * + * @type {StorageBufferNode} + */ + this.nodeUniform = nodeUniform; + + /** + * The access type. + * + * @type {string} + */ + this.access = nodeUniform ? nodeUniform.access : NodeAccess.READ_WRITE; + + /** + * The uniform group node. + * + * @type {UniformGroupNode} + */ + this.groupNode = groupNode; + + } + + /** + * The storage buffer. + * + * @type {BufferAttribute} + */ + get buffer() { + + return this.nodeUniform.value; + + } + +} + +/** + * A WebGPU backend utility module used by {@link WebGPUTextureUtils}. + * + * @private + */ +class WebGPUTexturePassUtils extends DataMap { + + /** + * Constructs a new utility object. + * + * @param {GPUDevice} device - The WebGPU device. + */ + constructor( device ) { + + super(); + + /** + * The WebGPU device. + * + * @type {GPUDevice} + */ + this.device = device; + + const mipmapVertexSource = ` +struct VarysStruct { + @builtin( position ) Position: vec4, + @location( 0 ) vTex : vec2 +}; + +@vertex +fn main( @builtin( vertex_index ) vertexIndex : u32 ) -> VarysStruct { + + var Varys : VarysStruct; + + var pos = array< vec2, 4 >( + vec2( -1.0, 1.0 ), + vec2( 1.0, 1.0 ), + vec2( -1.0, -1.0 ), + vec2( 1.0, -1.0 ) + ); + + var tex = array< vec2, 4 >( + vec2( 0.0, 0.0 ), + vec2( 1.0, 0.0 ), + vec2( 0.0, 1.0 ), + vec2( 1.0, 1.0 ) + ); + + Varys.vTex = tex[ vertexIndex ]; + Varys.Position = vec4( pos[ vertexIndex ], 0.0, 1.0 ); + + return Varys; + +} +`; + + const mipmapFragmentSource = ` +@group( 0 ) @binding( 0 ) +var imgSampler : sampler; + +@group( 0 ) @binding( 1 ) +var img : texture_2d; + +@fragment +fn main( @location( 0 ) vTex : vec2 ) -> @location( 0 ) vec4 { + + return textureSample( img, imgSampler, vTex ); + +} +`; + + const flipYFragmentSource = ` +@group( 0 ) @binding( 0 ) +var imgSampler : sampler; + +@group( 0 ) @binding( 1 ) +var img : texture_2d; + +@fragment +fn main( @location( 0 ) vTex : vec2 ) -> @location( 0 ) vec4 { + + return textureSample( img, imgSampler, vec2( vTex.x, 1.0 - vTex.y ) ); + +} +`; + + /** + * The mipmap GPU sampler. + * + * @type {GPUSampler} + */ + this.mipmapSampler = device.createSampler( { minFilter: GPUFilterMode.Linear } ); + + /** + * The flipY GPU sampler. + * + * @type {GPUSampler} + */ + this.flipYSampler = device.createSampler( { minFilter: GPUFilterMode.Nearest } ); //@TODO?: Consider using textureLoad() + + /** + * A cache for GPU render pipelines used for copy/transfer passes. + * Every texture format requires a unique pipeline. + * + * @type {Object} + */ + this.transferPipelines = {}; + + /** + * A cache for GPU render pipelines used for flipY passes. + * Every texture format requires a unique pipeline. + * + * @type {Object} + */ + this.flipYPipelines = {}; + + /** + * The mipmap vertex shader module. + * + * @type {GPUShaderModule} + */ + this.mipmapVertexShaderModule = device.createShaderModule( { + label: 'mipmapVertex', + code: mipmapVertexSource + } ); + + /** + * The mipmap fragment shader module. + * + * @type {GPUShaderModule} + */ + this.mipmapFragmentShaderModule = device.createShaderModule( { + label: 'mipmapFragment', + code: mipmapFragmentSource + } ); + + /** + * The flipY fragment shader module. + * + * @type {GPUShaderModule} + */ + this.flipYFragmentShaderModule = device.createShaderModule( { + label: 'flipYFragment', + code: flipYFragmentSource + } ); + + } + + /** + * Returns a render pipeline for the internal copy render pass. The pass + * requires a unique render pipeline for each texture format. + * + * @param {string} format - The GPU texture format + * @return {GPURenderPipeline} The GPU render pipeline. + */ + getTransferPipeline( format ) { + + let pipeline = this.transferPipelines[ format ]; + + if ( pipeline === undefined ) { + + pipeline = this.device.createRenderPipeline( { + label: `mipmap-${ format }`, + vertex: { + module: this.mipmapVertexShaderModule, + entryPoint: 'main' + }, + fragment: { + module: this.mipmapFragmentShaderModule, + entryPoint: 'main', + targets: [ { format } ] + }, + primitive: { + topology: GPUPrimitiveTopology.TriangleStrip, + stripIndexFormat: GPUIndexFormat.Uint32 + }, + layout: 'auto' + } ); + + this.transferPipelines[ format ] = pipeline; + + } + + return pipeline; + + } + + /** + * Returns a render pipeline for the flipY render pass. The pass + * requires a unique render pipeline for each texture format. + * + * @param {string} format - The GPU texture format + * @return {GPURenderPipeline} The GPU render pipeline. + */ + getFlipYPipeline( format ) { + + let pipeline = this.flipYPipelines[ format ]; + + if ( pipeline === undefined ) { + + pipeline = this.device.createRenderPipeline( { + label: `flipY-${ format }`, + vertex: { + module: this.mipmapVertexShaderModule, + entryPoint: 'main' + }, + fragment: { + module: this.flipYFragmentShaderModule, + entryPoint: 'main', + targets: [ { format } ] + }, + primitive: { + topology: GPUPrimitiveTopology.TriangleStrip, + stripIndexFormat: GPUIndexFormat.Uint32 + }, + layout: 'auto' + } ); + + this.flipYPipelines[ format ] = pipeline; + + } + + return pipeline; + + } + + /** + * Flip the contents of the given GPU texture along its vertical axis. + * + * @param {GPUTexture} textureGPU - The GPU texture object. + * @param {Object} textureGPUDescriptor - The texture descriptor. + * @param {number} [baseArrayLayer=0] - The index of the first array layer accessible to the texture view. + */ + flipY( textureGPU, textureGPUDescriptor, baseArrayLayer = 0 ) { + + const format = textureGPUDescriptor.format; + const { width, height } = textureGPUDescriptor.size; + + const transferPipeline = this.getTransferPipeline( format ); + const flipYPipeline = this.getFlipYPipeline( format ); + + const tempTexture = this.device.createTexture( { + size: { width, height, depthOrArrayLayers: 1 }, + format, + usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING + } ); + + const srcView = textureGPU.createView( { + baseMipLevel: 0, + mipLevelCount: 1, + dimension: GPUTextureViewDimension.TwoD, + baseArrayLayer + } ); + + const dstView = tempTexture.createView( { + baseMipLevel: 0, + mipLevelCount: 1, + dimension: GPUTextureViewDimension.TwoD, + baseArrayLayer: 0 + } ); + + const commandEncoder = this.device.createCommandEncoder( {} ); + + const pass = ( pipeline, sourceView, destinationView ) => { + + const bindGroupLayout = pipeline.getBindGroupLayout( 0 ); // @TODO: Consider making this static. + + const bindGroup = this.device.createBindGroup( { + layout: bindGroupLayout, + entries: [ { + binding: 0, + resource: this.flipYSampler + }, { + binding: 1, + resource: sourceView + } ] + } ); + + const passEncoder = commandEncoder.beginRenderPass( { + colorAttachments: [ { + view: destinationView, + loadOp: GPULoadOp.Clear, + storeOp: GPUStoreOp.Store, + clearValue: [ 0, 0, 0, 0 ] + } ] + } ); + + passEncoder.setPipeline( pipeline ); + passEncoder.setBindGroup( 0, bindGroup ); + passEncoder.draw( 4, 1, 0, 0 ); + passEncoder.end(); + + }; + + pass( transferPipeline, srcView, dstView ); + pass( flipYPipeline, dstView, srcView ); + + this.device.queue.submit( [ commandEncoder.finish() ] ); + + tempTexture.destroy(); + + } + + /** + * Generates mipmaps for the given GPU texture. + * + * @param {GPUTexture} textureGPU - The GPU texture object. + * @param {Object} textureGPUDescriptor - The texture descriptor. + * @param {number} [baseArrayLayer=0] - The index of the first array layer accessible to the texture view. + */ + generateMipmaps( textureGPU, textureGPUDescriptor, baseArrayLayer = 0 ) { + + const textureData = this.get( textureGPU ); + + if ( textureData.useCount === undefined ) { + + textureData.useCount = 0; + textureData.layers = []; + + } + + const passes = textureData.layers[ baseArrayLayer ] || this._mipmapCreateBundles( textureGPU, textureGPUDescriptor, baseArrayLayer ); + + const commandEncoder = this.device.createCommandEncoder( {} ); + + this._mipmapRunBundles( commandEncoder, passes ); + + this.device.queue.submit( [ commandEncoder.finish() ] ); + + if ( textureData.useCount !== 0 ) textureData.layers[ baseArrayLayer ] = passes; + + textureData.useCount ++; + + } + + /** + * Since multiple copy render passes are required to generate mipmaps, the passes + * are managed as render bundles to improve performance. + * + * @param {GPUTexture} textureGPU - The GPU texture object. + * @param {Object} textureGPUDescriptor - The texture descriptor. + * @param {number} baseArrayLayer - The index of the first array layer accessible to the texture view. + * @return {Array} An array of render bundles. + */ + _mipmapCreateBundles( textureGPU, textureGPUDescriptor, baseArrayLayer ) { + + const pipeline = this.getTransferPipeline( textureGPUDescriptor.format ); + + const bindGroupLayout = pipeline.getBindGroupLayout( 0 ); // @TODO: Consider making this static. + + let srcView = textureGPU.createView( { + baseMipLevel: 0, + mipLevelCount: 1, + dimension: GPUTextureViewDimension.TwoD, + baseArrayLayer + } ); + + const passes = []; + + for ( let i = 1; i < textureGPUDescriptor.mipLevelCount; i ++ ) { + + const bindGroup = this.device.createBindGroup( { + layout: bindGroupLayout, + entries: [ { + binding: 0, + resource: this.mipmapSampler + }, { + binding: 1, + resource: srcView + } ] + } ); + + const dstView = textureGPU.createView( { + baseMipLevel: i, + mipLevelCount: 1, + dimension: GPUTextureViewDimension.TwoD, + baseArrayLayer + } ); + + const passDescriptor = { + colorAttachments: [ { + view: dstView, + loadOp: GPULoadOp.Clear, + storeOp: GPUStoreOp.Store, + clearValue: [ 0, 0, 0, 0 ] + } ] + }; + + const passEncoder = this.device.createRenderBundleEncoder( { + colorFormats: [ textureGPUDescriptor.format ] + } ); + + passEncoder.setPipeline( pipeline ); + passEncoder.setBindGroup( 0, bindGroup ); + passEncoder.draw( 4, 1, 0, 0 ); + + passes.push( { + renderBundles: [ passEncoder.finish() ], + passDescriptor + } ); + + srcView = dstView; + + } + + return passes; + + } + + /** + * Executes the render bundles. + * + * @param {GPUCommandEncoder} commandEncoder - The GPU command encoder. + * @param {Array} passes - An array of render bundles. + */ + _mipmapRunBundles( commandEncoder, passes ) { + + const levels = passes.length; + + for ( let i = 0; i < levels; i ++ ) { + + const pass = passes[ i ]; + + const passEncoder = commandEncoder.beginRenderPass( pass.passDescriptor ); + + passEncoder.executeBundles( pass.renderBundles ); + + passEncoder.end(); + + } + + } + +} + +const _compareToWebGPU = { + [ NeverCompare ]: 'never', + [ LessCompare ]: 'less', + [ EqualCompare ]: 'equal', + [ LessEqualCompare ]: 'less-equal', + [ GreaterCompare ]: 'greater', + [ GreaterEqualCompare ]: 'greater-equal', + [ AlwaysCompare ]: 'always', + [ NotEqualCompare ]: 'not-equal' +}; + +const _flipMap = [ 0, 1, 3, 2, 4, 5 ]; + +/** + * A WebGPU backend utility module for managing textures. + * + * @private + */ +class WebGPUTextureUtils { + + /** + * Constructs a new utility object. + * + * @param {WebGPUBackend} backend - The WebGPU backend. + */ + constructor( backend ) { + + /** + * A reference to the WebGPU backend. + * + * @type {WebGPUBackend} + */ + this.backend = backend; + + /** + * A reference to the pass utils. + * + * @type {?WebGPUTexturePassUtils} + * @default null + */ + this._passUtils = null; + + /** + * A dictionary for managing default textures. The key + * is the texture format, the value the texture object. + * + * @type {Object} + */ + this.defaultTexture = {}; + + /** + * A dictionary for managing default cube textures. The key + * is the texture format, the value the texture object. + * + * @type {Object} + */ + this.defaultCubeTexture = {}; + + /** + * A default video frame. + * + * @type {?VideoFrame} + * @default null + */ + this.defaultVideoFrame = null; + + /** + * Represents the color attachment of the default framebuffer. + * + * @type {?GPUTexture} + * @default null + */ + this.colorBuffer = null; + + /** + * Represents the depth attachment of the default framebuffer. + * + * @type {DepthTexture} + */ + this.depthTexture = new DepthTexture(); + this.depthTexture.name = 'depthBuffer'; + + } + + /** + * Creates a GPU sampler for the given texture. + * + * @param {Texture} texture - The texture to create the sampler for. + */ + createSampler( texture ) { + + const backend = this.backend; + const device = backend.device; + + const textureGPU = backend.get( texture ); + + const samplerDescriptorGPU = { + addressModeU: this._convertAddressMode( texture.wrapS ), + addressModeV: this._convertAddressMode( texture.wrapT ), + addressModeW: this._convertAddressMode( texture.wrapR ), + magFilter: this._convertFilterMode( texture.magFilter ), + minFilter: this._convertFilterMode( texture.minFilter ), + mipmapFilter: this._convertFilterMode( texture.minFilter ), + maxAnisotropy: 1 + }; + + // anisotropy can only be used when all filter modes are set to linear. + + if ( samplerDescriptorGPU.magFilter === GPUFilterMode.Linear && samplerDescriptorGPU.minFilter === GPUFilterMode.Linear && samplerDescriptorGPU.mipmapFilter === GPUFilterMode.Linear ) { + + samplerDescriptorGPU.maxAnisotropy = texture.anisotropy; + + } + + if ( texture.isDepthTexture && texture.compareFunction !== null ) { + + samplerDescriptorGPU.compare = _compareToWebGPU[ texture.compareFunction ]; + + } + + textureGPU.sampler = device.createSampler( samplerDescriptorGPU ); + + } + + /** + * Creates a default texture for the given texture that can be used + * as a placeholder until the actual texture is ready for usage. + * + * @param {Texture} texture - The texture to create a default texture for. + */ + createDefaultTexture( texture ) { + + let textureGPU; + + const format = getFormat( texture ); + + if ( texture.isCubeTexture ) { + + textureGPU = this._getDefaultCubeTextureGPU( format ); + + } else if ( texture.isVideoTexture ) { + + this.backend.get( texture ).externalTexture = this._getDefaultVideoFrame(); + + } else { + + textureGPU = this._getDefaultTextureGPU( format ); + + } + + this.backend.get( texture ).texture = textureGPU; + + } + + /** + * Defines a texture on the GPU for the given texture object. + * + * @param {Texture} texture - The texture. + * @param {Object} [options={}] - Optional configuration parameter. + */ + createTexture( texture, options = {} ) { + + const backend = this.backend; + const textureData = backend.get( texture ); + + if ( textureData.initialized ) { + + throw new Error( 'WebGPUTextureUtils: Texture already initialized.' ); + + } + + if ( options.needsMipmaps === undefined ) options.needsMipmaps = false; + if ( options.levels === undefined ) options.levels = 1; + if ( options.depth === undefined ) options.depth = 1; + + const { width, height, depth, levels } = options; + + if ( texture.isFramebufferTexture ) { + + if ( options.renderTarget ) { + + options.format = this.backend.utils.getCurrentColorFormat( options.renderTarget ); + + } else { + + options.format = this.backend.utils.getPreferredCanvasFormat(); + + } + + } + + const dimension = this._getDimension( texture ); + const format = texture.internalFormat || options.format || getFormat( texture, backend.device ); + + textureData.format = format; + + const { samples, primarySamples, isMSAA } = backend.utils.getTextureSampleData( texture ); + + let usage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC; + + if ( texture.isStorageTexture === true ) { + + usage |= GPUTextureUsage.STORAGE_BINDING; + + } + + if ( texture.isCompressedTexture !== true && texture.isCompressedArrayTexture !== true ) { + + usage |= GPUTextureUsage.RENDER_ATTACHMENT; + + } + + const textureDescriptorGPU = { + label: texture.name, + size: { + width: width, + height: height, + depthOrArrayLayers: depth, + }, + mipLevelCount: levels, + sampleCount: primarySamples, + dimension: dimension, + format: format, + usage: usage + }; + + // texture creation + + if ( texture.isVideoTexture ) { + + const video = texture.source.data; + const videoFrame = new VideoFrame( video ); + + textureDescriptorGPU.size.width = videoFrame.displayWidth; + textureDescriptorGPU.size.height = videoFrame.displayHeight; + + videoFrame.close(); + + textureData.externalTexture = video; + + } else { + + if ( format === undefined ) { + + console.warn( 'WebGPURenderer: Texture format not supported.' ); + + this.createDefaultTexture( texture ); + return; + + } + + if ( texture.isCubeTexture ) { + + textureDescriptorGPU.textureBindingViewDimension = GPUTextureViewDimension.Cube; + + } + + textureData.texture = backend.device.createTexture( textureDescriptorGPU ); + + } + + if ( isMSAA ) { + + const msaaTextureDescriptorGPU = Object.assign( {}, textureDescriptorGPU ); + + msaaTextureDescriptorGPU.label = msaaTextureDescriptorGPU.label + '-msaa'; + msaaTextureDescriptorGPU.sampleCount = samples; + + textureData.msaaTexture = backend.device.createTexture( msaaTextureDescriptorGPU ); + + } + + textureData.initialized = true; + + textureData.textureDescriptorGPU = textureDescriptorGPU; + + } + + /** + * Destroys the GPU data for the given texture object. + * + * @param {Texture} texture - The texture. + */ + destroyTexture( texture ) { + + const backend = this.backend; + const textureData = backend.get( texture ); + + if ( textureData.texture !== undefined ) textureData.texture.destroy(); + + if ( textureData.msaaTexture !== undefined ) textureData.msaaTexture.destroy(); + + backend.delete( texture ); + + } + + /** + * Destroys the GPU sampler for the given texture. + * + * @param {Texture} texture - The texture to destroy the sampler for. + */ + destroySampler( texture ) { + + const backend = this.backend; + const textureData = backend.get( texture ); + + delete textureData.sampler; + + } + + /** + * Generates mipmaps for the given texture. + * + * @param {Texture} texture - The texture. + */ + generateMipmaps( texture ) { + + const textureData = this.backend.get( texture ); + + if ( texture.isCubeTexture ) { + + for ( let i = 0; i < 6; i ++ ) { + + this._generateMipmaps( textureData.texture, textureData.textureDescriptorGPU, i ); + + } + + } else { + + const depth = texture.image.depth || 1; + + for ( let i = 0; i < depth; i ++ ) { + + this._generateMipmaps( textureData.texture, textureData.textureDescriptorGPU, i ); + + } + + } + + } + + /** + * Returns the color buffer representing the color + * attachment of the default framebuffer. + * + * @return {GPUTexture} The color buffer. + */ + getColorBuffer() { + + if ( this.colorBuffer ) this.colorBuffer.destroy(); + + const backend = this.backend; + const { width, height } = backend.getDrawingBufferSize(); + + this.colorBuffer = backend.device.createTexture( { + label: 'colorBuffer', + size: { + width: width, + height: height, + depthOrArrayLayers: 1 + }, + sampleCount: backend.utils.getSampleCount( backend.renderer.samples ), + format: backend.utils.getPreferredCanvasFormat(), + usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC + } ); + + return this.colorBuffer; + + } + + /** + * Returns the depth buffer representing the depth + * attachment of the default framebuffer. + * + * @param {boolean} [depth=true] - Whether depth is enabled or not. + * @param {boolean} [stencil=false] - Whether stencil is enabled or not. + * @return {GPUTexture} The depth buffer. + */ + getDepthBuffer( depth = true, stencil = false ) { + + const backend = this.backend; + const { width, height } = backend.getDrawingBufferSize(); + + const depthTexture = this.depthTexture; + const depthTextureGPU = backend.get( depthTexture ).texture; + + let format, type; + + if ( stencil ) { + + format = DepthStencilFormat; + type = UnsignedInt248Type; + + } else if ( depth ) { + + format = DepthFormat; + type = UnsignedIntType; + + } + + if ( depthTextureGPU !== undefined ) { + + if ( depthTexture.image.width === width && depthTexture.image.height === height && depthTexture.format === format && depthTexture.type === type ) { + + return depthTextureGPU; + + } + + this.destroyTexture( depthTexture ); + + } + + depthTexture.name = 'depthBuffer'; + depthTexture.format = format; + depthTexture.type = type; + depthTexture.image.width = width; + depthTexture.image.height = height; + + this.createTexture( depthTexture, { width, height } ); + + return backend.get( depthTexture ).texture; + + } + + /** + * Uploads the updated texture data to the GPU. + * + * @param {Texture} texture - The texture. + * @param {Object} [options={}] - Optional configuration parameter. + */ + updateTexture( texture, options ) { + + const textureData = this.backend.get( texture ); + + const { textureDescriptorGPU } = textureData; + + if ( texture.isRenderTargetTexture || ( textureDescriptorGPU === undefined /* unsupported texture format */ ) ) + return; + + // transfer texture data + + if ( texture.isDataTexture ) { + + this._copyBufferToTexture( options.image, textureData.texture, textureDescriptorGPU, 0, texture.flipY ); + + } else if ( texture.isArrayTexture || texture.isDataArrayTexture || texture.isData3DTexture ) { + + for ( let i = 0; i < options.image.depth; i ++ ) { + + this._copyBufferToTexture( options.image, textureData.texture, textureDescriptorGPU, i, texture.flipY, i ); + + } + + } else if ( texture.isCompressedTexture || texture.isCompressedArrayTexture ) { + + this._copyCompressedBufferToTexture( texture.mipmaps, textureData.texture, textureDescriptorGPU ); + + } else if ( texture.isCubeTexture ) { + + this._copyCubeMapToTexture( options.images, textureData.texture, textureDescriptorGPU, texture.flipY, texture.premultiplyAlpha ); + + } else if ( texture.isVideoTexture ) { + + const video = texture.source.data; + + textureData.externalTexture = video; + + } else { + + this._copyImageToTexture( options.image, textureData.texture, textureDescriptorGPU, 0, texture.flipY, texture.premultiplyAlpha ); + + } + + // + + textureData.version = texture.version; + + if ( texture.onUpdate ) texture.onUpdate( texture ); + + } + + /** + * Returns texture data as a typed array. + * + * @async + * @param {Texture} texture - The texture to copy. + * @param {number} x - The x coordinate of the copy origin. + * @param {number} y - The y coordinate of the copy origin. + * @param {number} width - The width of the copy. + * @param {number} height - The height of the copy. + * @param {number} faceIndex - The face index. + * @return {Promise} A Promise that resolves with a typed array when the copy operation has finished. + */ + async copyTextureToBuffer( texture, x, y, width, height, faceIndex ) { + + const device = this.backend.device; + + const textureData = this.backend.get( texture ); + const textureGPU = textureData.texture; + const format = textureData.textureDescriptorGPU.format; + const bytesPerTexel = this._getBytesPerTexel( format ); + + let bytesPerRow = width * bytesPerTexel; + bytesPerRow = Math.ceil( bytesPerRow / 256 ) * 256; // Align to 256 bytes + + const readBuffer = device.createBuffer( + { + size: width * height * bytesPerTexel, + usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ + } + ); + + const encoder = device.createCommandEncoder(); + + encoder.copyTextureToBuffer( + { + texture: textureGPU, + origin: { x, y, z: faceIndex }, + }, + { + buffer: readBuffer, + bytesPerRow: bytesPerRow + }, + { + width: width, + height: height + } + + ); + + const typedArrayType = this._getTypedArrayType( format ); + + device.queue.submit( [ encoder.finish() ] ); + + await readBuffer.mapAsync( GPUMapMode.READ ); + + const buffer = readBuffer.getMappedRange(); + + return new typedArrayType( buffer ); + + } + + /** + * Returns the default GPU texture for the given format. + * + * @private + * @param {string} format - The GPU format. + * @return {GPUTexture} The GPU texture. + */ + _getDefaultTextureGPU( format ) { + + let defaultTexture = this.defaultTexture[ format ]; + + if ( defaultTexture === undefined ) { + + const texture = new Texture(); + texture.minFilter = NearestFilter; + texture.magFilter = NearestFilter; + + this.createTexture( texture, { width: 1, height: 1, format } ); + + this.defaultTexture[ format ] = defaultTexture = texture; + + } + + return this.backend.get( defaultTexture ).texture; + + } + + /** + * Returns the default GPU cube texture for the given format. + * + * @private + * @param {string} format - The GPU format. + * @return {GPUTexture} The GPU texture. + */ + _getDefaultCubeTextureGPU( format ) { + + let defaultCubeTexture = this.defaultTexture[ format ]; + + if ( defaultCubeTexture === undefined ) { + + const texture = new CubeTexture(); + texture.minFilter = NearestFilter; + texture.magFilter = NearestFilter; + + this.createTexture( texture, { width: 1, height: 1, depth: 6 } ); + + this.defaultCubeTexture[ format ] = defaultCubeTexture = texture; + + } + + return this.backend.get( defaultCubeTexture ).texture; + + } + + /** + * Returns the default video frame used as default data in context of video textures. + * + * @private + * @return {VideoFrame} The video frame. + */ + _getDefaultVideoFrame() { + + let defaultVideoFrame = this.defaultVideoFrame; + + if ( defaultVideoFrame === null ) { + + const init = { + timestamp: 0, + codedWidth: 1, + codedHeight: 1, + format: 'RGBA', + }; + + this.defaultVideoFrame = defaultVideoFrame = new VideoFrame( new Uint8Array( [ 0, 0, 0, 0xff ] ), init ); + + } + + return defaultVideoFrame; + + } + + /** + * Uploads cube texture image data to the GPU memory. + * + * @private + * @param {Array} images - The cube image data. + * @param {GPUTexture} textureGPU - The GPU texture. + * @param {Object} textureDescriptorGPU - The GPU texture descriptor. + * @param {boolean} flipY - Whether to flip texture data along their vertical axis or not. + * @param {boolean} premultiplyAlpha - Whether the texture should have its RGB channels premultiplied by the alpha channel or not. + */ + _copyCubeMapToTexture( images, textureGPU, textureDescriptorGPU, flipY, premultiplyAlpha ) { + + for ( let i = 0; i < 6; i ++ ) { + + const image = images[ i ]; + + const flipIndex = flipY === true ? _flipMap[ i ] : i; + + if ( image.isDataTexture ) { + + this._copyBufferToTexture( image.image, textureGPU, textureDescriptorGPU, flipIndex, flipY ); + + } else { + + this._copyImageToTexture( image, textureGPU, textureDescriptorGPU, flipIndex, flipY, premultiplyAlpha ); + + } + + } + + } + + /** + * Uploads texture image data to the GPU memory. + * + * @private + * @param {HTMLImageElement|ImageBitmap|HTMLCanvasElement} image - The image data. + * @param {GPUTexture} textureGPU - The GPU texture. + * @param {Object} textureDescriptorGPU - The GPU texture descriptor. + * @param {number} originDepth - The origin depth. + * @param {boolean} flipY - Whether to flip texture data along their vertical axis or not. + * @param {boolean} premultiplyAlpha - Whether the texture should have its RGB channels premultiplied by the alpha channel or not. + */ + _copyImageToTexture( image, textureGPU, textureDescriptorGPU, originDepth, flipY, premultiplyAlpha ) { + + const device = this.backend.device; + + device.queue.copyExternalImageToTexture( + { + source: image, + flipY: flipY + }, { + texture: textureGPU, + mipLevel: 0, + origin: { x: 0, y: 0, z: originDepth }, + premultipliedAlpha: premultiplyAlpha + }, { + width: image.width, + height: image.height, + depthOrArrayLayers: 1 + } + ); + + } + + /** + * Returns the pass utils singleton. + * + * @private + * @return {WebGPUTexturePassUtils} The utils instance. + */ + _getPassUtils() { + + let passUtils = this._passUtils; + + if ( passUtils === null ) { + + this._passUtils = passUtils = new WebGPUTexturePassUtils( this.backend.device ); + + } + + return passUtils; + + } + + /** + * Generates mipmaps for the given GPU texture. + * + * @private + * @param {GPUTexture} textureGPU - The GPU texture object. + * @param {Object} textureDescriptorGPU - The texture descriptor. + * @param {number} [baseArrayLayer=0] - The index of the first array layer accessible to the texture view. + */ + _generateMipmaps( textureGPU, textureDescriptorGPU, baseArrayLayer = 0 ) { + + this._getPassUtils().generateMipmaps( textureGPU, textureDescriptorGPU, baseArrayLayer ); + + } + + /** + * Flip the contents of the given GPU texture along its vertical axis. + * + * @private + * @param {GPUTexture} textureGPU - The GPU texture object. + * @param {Object} textureDescriptorGPU - The texture descriptor. + * @param {number} [originDepth=0] - The origin depth. + */ + _flipY( textureGPU, textureDescriptorGPU, originDepth = 0 ) { + + this._getPassUtils().flipY( textureGPU, textureDescriptorGPU, originDepth ); + + } + + /** + * Uploads texture buffer data to the GPU memory. + * + * @private + * @param {Object} image - An object defining the image buffer data. + * @param {GPUTexture} textureGPU - The GPU texture. + * @param {Object} textureDescriptorGPU - The GPU texture descriptor. + * @param {number} originDepth - The origin depth. + * @param {boolean} flipY - Whether to flip texture data along their vertical axis or not. + * @param {number} [depth=0] - TODO. + */ + _copyBufferToTexture( image, textureGPU, textureDescriptorGPU, originDepth, flipY, depth = 0 ) { + + // @TODO: Consider to use GPUCommandEncoder.copyBufferToTexture() + // @TODO: Consider to support valid buffer layouts with other formats like RGB + + const device = this.backend.device; + + const data = image.data; + + const bytesPerTexel = this._getBytesPerTexel( textureDescriptorGPU.format ); + const bytesPerRow = image.width * bytesPerTexel; + + device.queue.writeTexture( + { + texture: textureGPU, + mipLevel: 0, + origin: { x: 0, y: 0, z: originDepth } + }, + data, + { + offset: image.width * image.height * bytesPerTexel * depth, + bytesPerRow + }, + { + width: image.width, + height: image.height, + depthOrArrayLayers: 1 + } ); + + if ( flipY === true ) { + + this._flipY( textureGPU, textureDescriptorGPU, originDepth ); + + } + + } + + /** + * Uploads compressed texture data to the GPU memory. + * + * @private + * @param {Array} mipmaps - An array with mipmap data. + * @param {GPUTexture} textureGPU - The GPU texture. + * @param {Object} textureDescriptorGPU - The GPU texture descriptor. + */ + _copyCompressedBufferToTexture( mipmaps, textureGPU, textureDescriptorGPU ) { + + // @TODO: Consider to use GPUCommandEncoder.copyBufferToTexture() + + const device = this.backend.device; + + const blockData = this._getBlockData( textureDescriptorGPU.format ); + const isArrayTexture = textureDescriptorGPU.size.depthOrArrayLayers > 1; + + for ( let i = 0; i < mipmaps.length; i ++ ) { + + const mipmap = mipmaps[ i ]; + + const width = mipmap.width; + const height = mipmap.height; + const depth = isArrayTexture ? textureDescriptorGPU.size.depthOrArrayLayers : 1; + + const bytesPerRow = Math.ceil( width / blockData.width ) * blockData.byteLength; + const bytesPerImage = bytesPerRow * Math.ceil( height / blockData.height ); + + for ( let j = 0; j < depth; j ++ ) { + + device.queue.writeTexture( + { + texture: textureGPU, + mipLevel: i, + origin: { x: 0, y: 0, z: j } + }, + mipmap.data, + { + offset: j * bytesPerImage, + bytesPerRow, + rowsPerImage: Math.ceil( height / blockData.height ) + }, + { + width: Math.ceil( width / blockData.width ) * blockData.width, + height: Math.ceil( height / blockData.height ) * blockData.height, + depthOrArrayLayers: 1 + } + ); + + } + + } + + } + + /** + * This method is only relevant for compressed texture formats. It returns a block + * data descriptor for the given GPU compressed texture format. + * + * @private + * @param {string} format - The GPU compressed texture format. + * @return {Object} The block data descriptor. + */ + _getBlockData( format ) { + + if ( format === GPUTextureFormat.BC1RGBAUnorm || format === GPUTextureFormat.BC1RGBAUnormSRGB ) return { byteLength: 8, width: 4, height: 4 }; // DXT1 + if ( format === GPUTextureFormat.BC2RGBAUnorm || format === GPUTextureFormat.BC2RGBAUnormSRGB ) return { byteLength: 16, width: 4, height: 4 }; // DXT3 + if ( format === GPUTextureFormat.BC3RGBAUnorm || format === GPUTextureFormat.BC3RGBAUnormSRGB ) return { byteLength: 16, width: 4, height: 4 }; // DXT5 + if ( format === GPUTextureFormat.BC4RUnorm || format === GPUTextureFormat.BC4RSnorm ) return { byteLength: 8, width: 4, height: 4 }; // RGTC1 + if ( format === GPUTextureFormat.BC5RGUnorm || format === GPUTextureFormat.BC5RGSnorm ) return { byteLength: 16, width: 4, height: 4 }; // RGTC2 + if ( format === GPUTextureFormat.BC6HRGBUFloat || format === GPUTextureFormat.BC6HRGBFloat ) return { byteLength: 16, width: 4, height: 4 }; // BPTC (float) + if ( format === GPUTextureFormat.BC7RGBAUnorm || format === GPUTextureFormat.BC7RGBAUnormSRGB ) return { byteLength: 16, width: 4, height: 4 }; // BPTC (unorm) + + if ( format === GPUTextureFormat.ETC2RGB8Unorm || format === GPUTextureFormat.ETC2RGB8UnormSRGB ) return { byteLength: 8, width: 4, height: 4 }; + if ( format === GPUTextureFormat.ETC2RGB8A1Unorm || format === GPUTextureFormat.ETC2RGB8A1UnormSRGB ) return { byteLength: 8, width: 4, height: 4 }; + if ( format === GPUTextureFormat.ETC2RGBA8Unorm || format === GPUTextureFormat.ETC2RGBA8UnormSRGB ) return { byteLength: 16, width: 4, height: 4 }; + if ( format === GPUTextureFormat.EACR11Unorm ) return { byteLength: 8, width: 4, height: 4 }; + if ( format === GPUTextureFormat.EACR11Snorm ) return { byteLength: 8, width: 4, height: 4 }; + if ( format === GPUTextureFormat.EACRG11Unorm ) return { byteLength: 16, width: 4, height: 4 }; + if ( format === GPUTextureFormat.EACRG11Snorm ) return { byteLength: 16, width: 4, height: 4 }; + + if ( format === GPUTextureFormat.ASTC4x4Unorm || format === GPUTextureFormat.ASTC4x4UnormSRGB ) return { byteLength: 16, width: 4, height: 4 }; + if ( format === GPUTextureFormat.ASTC5x4Unorm || format === GPUTextureFormat.ASTC5x4UnormSRGB ) return { byteLength: 16, width: 5, height: 4 }; + if ( format === GPUTextureFormat.ASTC5x5Unorm || format === GPUTextureFormat.ASTC5x5UnormSRGB ) return { byteLength: 16, width: 5, height: 5 }; + if ( format === GPUTextureFormat.ASTC6x5Unorm || format === GPUTextureFormat.ASTC6x5UnormSRGB ) return { byteLength: 16, width: 6, height: 5 }; + if ( format === GPUTextureFormat.ASTC6x6Unorm || format === GPUTextureFormat.ASTC6x6UnormSRGB ) return { byteLength: 16, width: 6, height: 6 }; + if ( format === GPUTextureFormat.ASTC8x5Unorm || format === GPUTextureFormat.ASTC8x5UnormSRGB ) return { byteLength: 16, width: 8, height: 5 }; + if ( format === GPUTextureFormat.ASTC8x6Unorm || format === GPUTextureFormat.ASTC8x6UnormSRGB ) return { byteLength: 16, width: 8, height: 6 }; + if ( format === GPUTextureFormat.ASTC8x8Unorm || format === GPUTextureFormat.ASTC8x8UnormSRGB ) return { byteLength: 16, width: 8, height: 8 }; + if ( format === GPUTextureFormat.ASTC10x5Unorm || format === GPUTextureFormat.ASTC10x5UnormSRGB ) return { byteLength: 16, width: 10, height: 5 }; + if ( format === GPUTextureFormat.ASTC10x6Unorm || format === GPUTextureFormat.ASTC10x6UnormSRGB ) return { byteLength: 16, width: 10, height: 6 }; + if ( format === GPUTextureFormat.ASTC10x8Unorm || format === GPUTextureFormat.ASTC10x8UnormSRGB ) return { byteLength: 16, width: 10, height: 8 }; + if ( format === GPUTextureFormat.ASTC10x10Unorm || format === GPUTextureFormat.ASTC10x10UnormSRGB ) return { byteLength: 16, width: 10, height: 10 }; + if ( format === GPUTextureFormat.ASTC12x10Unorm || format === GPUTextureFormat.ASTC12x10UnormSRGB ) return { byteLength: 16, width: 12, height: 10 }; + if ( format === GPUTextureFormat.ASTC12x12Unorm || format === GPUTextureFormat.ASTC12x12UnormSRGB ) return { byteLength: 16, width: 12, height: 12 }; + + } + + /** + * Converts the three.js uv wrapping constants to GPU address mode constants. + * + * @private + * @param {number} value - The three.js constant defining a uv wrapping mode. + * @return {string} The GPU address mode. + */ + _convertAddressMode( value ) { + + let addressMode = GPUAddressMode.ClampToEdge; + + if ( value === RepeatWrapping ) { + + addressMode = GPUAddressMode.Repeat; + + } else if ( value === MirroredRepeatWrapping ) { + + addressMode = GPUAddressMode.MirrorRepeat; + + } + + return addressMode; + + } + + /** + * Converts the three.js filter constants to GPU filter constants. + * + * @private + * @param {number} value - The three.js constant defining a filter mode. + * @return {string} The GPU filter mode. + */ + _convertFilterMode( value ) { + + let filterMode = GPUFilterMode.Linear; + + if ( value === NearestFilter || value === NearestMipmapNearestFilter || value === NearestMipmapLinearFilter ) { + + filterMode = GPUFilterMode.Nearest; + + } + + return filterMode; + + } + + /** + * Returns the bytes-per-texel value for the given GPU texture format. + * + * @private + * @param {string} format - The GPU texture format. + * @return {number} The bytes-per-texel. + */ + _getBytesPerTexel( format ) { + + // 8-bit formats + if ( format === GPUTextureFormat.R8Unorm || + format === GPUTextureFormat.R8Snorm || + format === GPUTextureFormat.R8Uint || + format === GPUTextureFormat.R8Sint ) return 1; + + // 16-bit formats + if ( format === GPUTextureFormat.R16Uint || + format === GPUTextureFormat.R16Sint || + format === GPUTextureFormat.R16Float || + format === GPUTextureFormat.RG8Unorm || + format === GPUTextureFormat.RG8Snorm || + format === GPUTextureFormat.RG8Uint || + format === GPUTextureFormat.RG8Sint ) return 2; + + // 32-bit formats + if ( format === GPUTextureFormat.R32Uint || + format === GPUTextureFormat.R32Sint || + format === GPUTextureFormat.R32Float || + format === GPUTextureFormat.RG16Uint || + format === GPUTextureFormat.RG16Sint || + format === GPUTextureFormat.RG16Float || + format === GPUTextureFormat.RGBA8Unorm || + format === GPUTextureFormat.RGBA8UnormSRGB || + format === GPUTextureFormat.RGBA8Snorm || + format === GPUTextureFormat.RGBA8Uint || + format === GPUTextureFormat.RGBA8Sint || + format === GPUTextureFormat.BGRA8Unorm || + format === GPUTextureFormat.BGRA8UnormSRGB || + // Packed 32-bit formats + format === GPUTextureFormat.RGB9E5UFloat || + format === GPUTextureFormat.RGB10A2Unorm || + format === GPUTextureFormat.RG11B10UFloat || + format === GPUTextureFormat.Depth32Float || + format === GPUTextureFormat.Depth24Plus || + format === GPUTextureFormat.Depth24PlusStencil8 || + format === GPUTextureFormat.Depth32FloatStencil8 ) return 4; + + // 64-bit formats + if ( format === GPUTextureFormat.RG32Uint || + format === GPUTextureFormat.RG32Sint || + format === GPUTextureFormat.RG32Float || + format === GPUTextureFormat.RGBA16Uint || + format === GPUTextureFormat.RGBA16Sint || + format === GPUTextureFormat.RGBA16Float ) return 8; + + // 128-bit formats + if ( format === GPUTextureFormat.RGBA32Uint || + format === GPUTextureFormat.RGBA32Sint || + format === GPUTextureFormat.RGBA32Float ) return 16; + + + } + + /** + * Returns the corresponding typed array type for the given GPU texture format. + * + * @private + * @param {string} format - The GPU texture format. + * @return {TypedArray.constructor} The typed array type. + */ + _getTypedArrayType( format ) { + + if ( format === GPUTextureFormat.R8Uint ) return Uint8Array; + if ( format === GPUTextureFormat.R8Sint ) return Int8Array; + if ( format === GPUTextureFormat.R8Unorm ) return Uint8Array; + if ( format === GPUTextureFormat.R8Snorm ) return Int8Array; + if ( format === GPUTextureFormat.RG8Uint ) return Uint8Array; + if ( format === GPUTextureFormat.RG8Sint ) return Int8Array; + if ( format === GPUTextureFormat.RG8Unorm ) return Uint8Array; + if ( format === GPUTextureFormat.RG8Snorm ) return Int8Array; + if ( format === GPUTextureFormat.RGBA8Uint ) return Uint8Array; + if ( format === GPUTextureFormat.RGBA8Sint ) return Int8Array; + if ( format === GPUTextureFormat.RGBA8Unorm ) return Uint8Array; + if ( format === GPUTextureFormat.RGBA8Snorm ) return Int8Array; + + + if ( format === GPUTextureFormat.R16Uint ) return Uint16Array; + if ( format === GPUTextureFormat.R16Sint ) return Int16Array; + if ( format === GPUTextureFormat.RG16Uint ) return Uint16Array; + if ( format === GPUTextureFormat.RG16Sint ) return Int16Array; + if ( format === GPUTextureFormat.RGBA16Uint ) return Uint16Array; + if ( format === GPUTextureFormat.RGBA16Sint ) return Int16Array; + if ( format === GPUTextureFormat.R16Float ) return Uint16Array; + if ( format === GPUTextureFormat.RG16Float ) return Uint16Array; + if ( format === GPUTextureFormat.RGBA16Float ) return Uint16Array; + + + if ( format === GPUTextureFormat.R32Uint ) return Uint32Array; + if ( format === GPUTextureFormat.R32Sint ) return Int32Array; + if ( format === GPUTextureFormat.R32Float ) return Float32Array; + if ( format === GPUTextureFormat.RG32Uint ) return Uint32Array; + if ( format === GPUTextureFormat.RG32Sint ) return Int32Array; + if ( format === GPUTextureFormat.RG32Float ) return Float32Array; + if ( format === GPUTextureFormat.RGBA32Uint ) return Uint32Array; + if ( format === GPUTextureFormat.RGBA32Sint ) return Int32Array; + if ( format === GPUTextureFormat.RGBA32Float ) return Float32Array; + + if ( format === GPUTextureFormat.BGRA8Unorm ) return Uint8Array; + if ( format === GPUTextureFormat.BGRA8UnormSRGB ) return Uint8Array; + if ( format === GPUTextureFormat.RGB10A2Unorm ) return Uint32Array; + if ( format === GPUTextureFormat.RGB9E5UFloat ) return Uint32Array; + if ( format === GPUTextureFormat.RG11B10UFloat ) return Uint32Array; + + if ( format === GPUTextureFormat.Depth32Float ) return Float32Array; + if ( format === GPUTextureFormat.Depth24Plus ) return Uint32Array; + if ( format === GPUTextureFormat.Depth24PlusStencil8 ) return Uint32Array; + if ( format === GPUTextureFormat.Depth32FloatStencil8 ) return Float32Array; + + } + + /** + * Returns the GPU dimensions for the given texture. + * + * @private + * @param {Texture} texture - The texture. + * @return {string} The GPU dimension. + */ + _getDimension( texture ) { + + let dimension; + + if ( texture.is3DTexture || texture.isData3DTexture ) { + + dimension = GPUTextureDimension.ThreeD; + + } else { + + dimension = GPUTextureDimension.TwoD; + + } + + return dimension; + + } + +} + +/** + * Returns the GPU format for the given texture. + * + * @param {Texture} texture - The texture. + * @param {?GPUDevice} [device=null] - The GPU device which is used for feature detection. + * It is not necessary to apply the device for most formats. + * @return {string} The GPU format. + */ +function getFormat( texture, device = null ) { + + const format = texture.format; + const type = texture.type; + const colorSpace = texture.colorSpace; + const transfer = ColorManagement.getTransfer( colorSpace ); + + let formatGPU; + + if ( texture.isCompressedTexture === true || texture.isCompressedArrayTexture === true ) { + + switch ( format ) { + + case RGBA_S3TC_DXT1_Format: + formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.BC1RGBAUnormSRGB : GPUTextureFormat.BC1RGBAUnorm; + break; + + case RGBA_S3TC_DXT3_Format: + formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.BC2RGBAUnormSRGB : GPUTextureFormat.BC2RGBAUnorm; + break; + + case RGBA_S3TC_DXT5_Format: + formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.BC3RGBAUnormSRGB : GPUTextureFormat.BC3RGBAUnorm; + break; + + case RGB_ETC2_Format: + formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ETC2RGB8UnormSRGB : GPUTextureFormat.ETC2RGB8Unorm; + break; + + case RGBA_ETC2_EAC_Format: + formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ETC2RGBA8UnormSRGB : GPUTextureFormat.ETC2RGBA8Unorm; + break; + + case RGBA_ASTC_4x4_Format: + formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC4x4UnormSRGB : GPUTextureFormat.ASTC4x4Unorm; + break; + + case RGBA_ASTC_5x4_Format: + formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC5x4UnormSRGB : GPUTextureFormat.ASTC5x4Unorm; + break; + + case RGBA_ASTC_5x5_Format: + formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC5x5UnormSRGB : GPUTextureFormat.ASTC5x5Unorm; + break; + + case RGBA_ASTC_6x5_Format: + formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC6x5UnormSRGB : GPUTextureFormat.ASTC6x5Unorm; + break; + + case RGBA_ASTC_6x6_Format: + formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC6x6UnormSRGB : GPUTextureFormat.ASTC6x6Unorm; + break; + + case RGBA_ASTC_8x5_Format: + formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC8x5UnormSRGB : GPUTextureFormat.ASTC8x5Unorm; + break; + + case RGBA_ASTC_8x6_Format: + formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC8x6UnormSRGB : GPUTextureFormat.ASTC8x6Unorm; + break; + + case RGBA_ASTC_8x8_Format: + formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC8x8UnormSRGB : GPUTextureFormat.ASTC8x8Unorm; + break; + + case RGBA_ASTC_10x5_Format: + formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC10x5UnormSRGB : GPUTextureFormat.ASTC10x5Unorm; + break; + + case RGBA_ASTC_10x6_Format: + formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC10x6UnormSRGB : GPUTextureFormat.ASTC10x6Unorm; + break; + + case RGBA_ASTC_10x8_Format: + formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC10x8UnormSRGB : GPUTextureFormat.ASTC10x8Unorm; + break; + + case RGBA_ASTC_10x10_Format: + formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC10x10UnormSRGB : GPUTextureFormat.ASTC10x10Unorm; + break; + + case RGBA_ASTC_12x10_Format: + formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC12x10UnormSRGB : GPUTextureFormat.ASTC12x10Unorm; + break; + + case RGBA_ASTC_12x12_Format: + formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC12x12UnormSRGB : GPUTextureFormat.ASTC12x12Unorm; + break; + + case RGBAFormat: + formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.RGBA8UnormSRGB : GPUTextureFormat.RGBA8Unorm; + break; + + default: + console.error( 'WebGPURenderer: Unsupported texture format.', format ); + + } + + } else { + + switch ( format ) { + + case RGBAFormat: + + switch ( type ) { + + case ByteType: + formatGPU = GPUTextureFormat.RGBA8Snorm; + break; + + case ShortType: + formatGPU = GPUTextureFormat.RGBA16Sint; + break; + + case UnsignedShortType: + formatGPU = GPUTextureFormat.RGBA16Uint; + break; + case UnsignedIntType: + formatGPU = GPUTextureFormat.RGBA32Uint; + break; + + case IntType: + formatGPU = GPUTextureFormat.RGBA32Sint; + break; + + case UnsignedByteType: + formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.RGBA8UnormSRGB : GPUTextureFormat.RGBA8Unorm; + break; + + case HalfFloatType: + formatGPU = GPUTextureFormat.RGBA16Float; + break; + + case FloatType: + formatGPU = GPUTextureFormat.RGBA32Float; + break; + + default: + console.error( 'WebGPURenderer: Unsupported texture type with RGBAFormat.', type ); + + } + + break; + + case RGBFormat: + + switch ( type ) { + + case UnsignedInt5999Type: + formatGPU = GPUTextureFormat.RGB9E5UFloat; + break; + + default: + console.error( 'WebGPURenderer: Unsupported texture type with RGBFormat.', type ); + + } + + break; + + case RedFormat: + + switch ( type ) { + + case ByteType: + formatGPU = GPUTextureFormat.R8Snorm; + break; + + case ShortType: + formatGPU = GPUTextureFormat.R16Sint; + break; + + case UnsignedShortType: + formatGPU = GPUTextureFormat.R16Uint; + break; + + case UnsignedIntType: + formatGPU = GPUTextureFormat.R32Uint; + break; + + case IntType: + formatGPU = GPUTextureFormat.R32Sint; + break; + + case UnsignedByteType: + formatGPU = GPUTextureFormat.R8Unorm; + break; + + case HalfFloatType: + formatGPU = GPUTextureFormat.R16Float; + break; + + case FloatType: + formatGPU = GPUTextureFormat.R32Float; + break; + + default: + console.error( 'WebGPURenderer: Unsupported texture type with RedFormat.', type ); + + } + + break; + + case RGFormat: + + switch ( type ) { + + case ByteType: + formatGPU = GPUTextureFormat.RG8Snorm; + break; + + case ShortType: + formatGPU = GPUTextureFormat.RG16Sint; + break; + + case UnsignedShortType: + formatGPU = GPUTextureFormat.RG16Uint; + break; + + case UnsignedIntType: + formatGPU = GPUTextureFormat.RG32Uint; + break; + + case IntType: + formatGPU = GPUTextureFormat.RG32Sint; + break; + + case UnsignedByteType: + formatGPU = GPUTextureFormat.RG8Unorm; + break; + + case HalfFloatType: + formatGPU = GPUTextureFormat.RG16Float; + break; + + case FloatType: + formatGPU = GPUTextureFormat.RG32Float; + break; + + default: + console.error( 'WebGPURenderer: Unsupported texture type with RGFormat.', type ); + + } + + break; + + case DepthFormat: + + switch ( type ) { + + case UnsignedShortType: + formatGPU = GPUTextureFormat.Depth16Unorm; + break; + + case UnsignedIntType: + formatGPU = GPUTextureFormat.Depth24Plus; + break; + + case FloatType: + formatGPU = GPUTextureFormat.Depth32Float; + break; + + default: + console.error( 'WebGPURenderer: Unsupported texture type with DepthFormat.', type ); + + } + + break; + + case DepthStencilFormat: + + switch ( type ) { + + case UnsignedInt248Type: + formatGPU = GPUTextureFormat.Depth24PlusStencil8; + break; + + case FloatType: + + if ( device && device.features.has( GPUFeatureName.Depth32FloatStencil8 ) === false ) { + + console.error( 'WebGPURenderer: Depth textures with DepthStencilFormat + FloatType can only be used with the "depth32float-stencil8" GPU feature.' ); + + } + + formatGPU = GPUTextureFormat.Depth32FloatStencil8; + + break; + + default: + console.error( 'WebGPURenderer: Unsupported texture type with DepthStencilFormat.', type ); + + } + + break; + + case RedIntegerFormat: + + switch ( type ) { + + case IntType: + formatGPU = GPUTextureFormat.R32Sint; + break; + + case UnsignedIntType: + formatGPU = GPUTextureFormat.R32Uint; + break; + + default: + console.error( 'WebGPURenderer: Unsupported texture type with RedIntegerFormat.', type ); + + } + + break; + + case RGIntegerFormat: + + switch ( type ) { + + case IntType: + formatGPU = GPUTextureFormat.RG32Sint; + break; + + case UnsignedIntType: + formatGPU = GPUTextureFormat.RG32Uint; + break; + + default: + console.error( 'WebGPURenderer: Unsupported texture type with RGIntegerFormat.', type ); + + } + + break; + + case RGBAIntegerFormat: + + switch ( type ) { + + case IntType: + formatGPU = GPUTextureFormat.RGBA32Sint; + break; + + case UnsignedIntType: + formatGPU = GPUTextureFormat.RGBA32Uint; + break; + + default: + console.error( 'WebGPURenderer: Unsupported texture type with RGBAIntegerFormat.', type ); + + } + + break; + + default: + console.error( 'WebGPURenderer: Unsupported texture format.', format ); + + } + + } + + return formatGPU; + +} + +const declarationRegexp = /^[fn]*\s*([a-z_0-9]+)?\s*\(([\s\S]*?)\)\s*[\-\>]*\s*([a-z_0-9]+(?:<[\s\S]+?>)?)/i; +const propertiesRegexp = /([a-z_0-9]+)\s*:\s*([a-z_0-9]+(?:<[\s\S]+?>)?)/ig; + +const wgslTypeLib$1 = { + 'f32': 'float', + 'i32': 'int', + 'u32': 'uint', + 'bool': 'bool', + + 'vec2': 'vec2', + 'vec2': 'ivec2', + 'vec2': 'uvec2', + 'vec2': 'bvec2', + + 'vec2f': 'vec2', + 'vec2i': 'ivec2', + 'vec2u': 'uvec2', + 'vec2b': 'bvec2', + + 'vec3': 'vec3', + 'vec3': 'ivec3', + 'vec3': 'uvec3', + 'vec3': 'bvec3', + + 'vec3f': 'vec3', + 'vec3i': 'ivec3', + 'vec3u': 'uvec3', + 'vec3b': 'bvec3', + + 'vec4': 'vec4', + 'vec4': 'ivec4', + 'vec4': 'uvec4', + 'vec4': 'bvec4', + + 'vec4f': 'vec4', + 'vec4i': 'ivec4', + 'vec4u': 'uvec4', + 'vec4b': 'bvec4', + + 'mat2x2': 'mat2', + 'mat2x2f': 'mat2', + + 'mat3x3': 'mat3', + 'mat3x3f': 'mat3', + + 'mat4x4': 'mat4', + 'mat4x4f': 'mat4', + + 'sampler': 'sampler', + + 'texture_1d': 'texture', + + 'texture_2d': 'texture', + 'texture_2d_array': 'texture', + 'texture_multisampled_2d': 'cubeTexture', + + 'texture_depth_2d': 'depthTexture', + 'texture_depth_2d_array': 'depthTexture', + 'texture_depth_multisampled_2d': 'depthTexture', + 'texture_depth_cube': 'depthTexture', + 'texture_depth_cube_array': 'depthTexture', + + 'texture_3d': 'texture3D', + + 'texture_cube': 'cubeTexture', + 'texture_cube_array': 'cubeTexture', + + 'texture_storage_1d': 'storageTexture', + 'texture_storage_2d': 'storageTexture', + 'texture_storage_2d_array': 'storageTexture', + 'texture_storage_3d': 'storageTexture' + +}; + +const parse = ( source ) => { + + source = source.trim(); + + const declaration = source.match( declarationRegexp ); + + if ( declaration !== null && declaration.length === 4 ) { + + const inputsCode = declaration[ 2 ]; + const propsMatches = []; + let match = null; + + while ( ( match = propertiesRegexp.exec( inputsCode ) ) !== null ) { + + propsMatches.push( { name: match[ 1 ], type: match[ 2 ] } ); + + } + + // Process matches to correctly pair names and types + const inputs = []; + for ( let i = 0; i < propsMatches.length; i ++ ) { + + const { name, type } = propsMatches[ i ]; + + let resolvedType = type; + + if ( resolvedType.startsWith( 'ptr' ) ) { + + resolvedType = 'pointer'; + + } else { + + if ( resolvedType.startsWith( 'texture' ) ) { + + resolvedType = type.split( '<' )[ 0 ]; + + } + + resolvedType = wgslTypeLib$1[ resolvedType ]; + + } + + inputs.push( new NodeFunctionInput( resolvedType, name ) ); + + } + + const blockCode = source.substring( declaration[ 0 ].length ); + const outputType = declaration[ 3 ] || 'void'; + + const name = declaration[ 1 ] !== undefined ? declaration[ 1 ] : ''; + const type = wgslTypeLib$1[ outputType ] || outputType; + + return { + type, + inputs, + name, + inputsCode, + blockCode, + outputType + }; + + } else { + + throw new Error( 'FunctionNode: Function is not a WGSL code.' ); + + } + +}; + +/** + * This class represents a WSL node function. + * + * @augments NodeFunction + */ +class WGSLNodeFunction extends NodeFunction { + + /** + * Constructs a new WGSL node function. + * + * @param {string} source - The WGSL source. + */ + constructor( source ) { + + const { type, inputs, name, inputsCode, blockCode, outputType } = parse( source ); + + super( type, inputs, name ); + + this.inputsCode = inputsCode; + this.blockCode = blockCode; + this.outputType = outputType; + + } + + /** + * This method returns the WGSL code of the node function. + * + * @param {string} [name=this.name] - The function's name. + * @return {string} The shader code. + */ + getCode( name = this.name ) { + + const outputType = this.outputType !== 'void' ? '-> ' + this.outputType : ''; + + return `fn ${ name } ( ${ this.inputsCode.trim() } ) ${ outputType }` + this.blockCode; + + } + +} + +/** + * A WGSL node parser. + * + * @augments NodeParser + */ +class WGSLNodeParser extends NodeParser { + + /** + * The method parses the given WGSL code an returns a node function. + * + * @param {string} source - The WGSL code. + * @return {WGSLNodeFunction} A node function. + */ + parseFunction( source ) { + + return new WGSLNodeFunction( source ); + + } + +} + +// GPUShaderStage is not defined in browsers not supporting WebGPU +const GPUShaderStage = ( typeof self !== 'undefined' ) ? self.GPUShaderStage : { VERTEX: 1, FRAGMENT: 2, COMPUTE: 4 }; + +const accessNames = { + [ NodeAccess.READ_ONLY ]: 'read', + [ NodeAccess.WRITE_ONLY ]: 'write', + [ NodeAccess.READ_WRITE ]: 'read_write' +}; + +const wrapNames = { + [ RepeatWrapping ]: 'repeat', + [ ClampToEdgeWrapping ]: 'clamp', + [ MirroredRepeatWrapping ]: 'mirror' +}; + +const gpuShaderStageLib = { + 'vertex': GPUShaderStage ? GPUShaderStage.VERTEX : 1, + 'fragment': GPUShaderStage ? GPUShaderStage.FRAGMENT : 2, + 'compute': GPUShaderStage ? GPUShaderStage.COMPUTE : 4 +}; + +const supports = { + instance: true, + swizzleAssign: false, + storageBuffer: true +}; + +const wgslFnOpLib = { + '^^': 'tsl_xor' +}; + +const wgslTypeLib = { + float: 'f32', + int: 'i32', + uint: 'u32', + bool: 'bool', + color: 'vec3', + + vec2: 'vec2', + ivec2: 'vec2', + uvec2: 'vec2', + bvec2: 'vec2', + + vec3: 'vec3', + ivec3: 'vec3', + uvec3: 'vec3', + bvec3: 'vec3', + + vec4: 'vec4', + ivec4: 'vec4', + uvec4: 'vec4', + bvec4: 'vec4', + + mat2: 'mat2x2', + mat3: 'mat3x3', + mat4: 'mat4x4' +}; + +const wgslCodeCache = {}; + +const wgslPolyfill = { + tsl_xor: new CodeNode( 'fn tsl_xor( a : bool, b : bool ) -> bool { return ( a || b ) && !( a && b ); }' ), + mod_float: new CodeNode( 'fn tsl_mod_float( x : f32, y : f32 ) -> f32 { return x - y * floor( x / y ); }' ), + mod_vec2: new CodeNode( 'fn tsl_mod_vec2( x : vec2f, y : vec2f ) -> vec2f { return x - y * floor( x / y ); }' ), + mod_vec3: new CodeNode( 'fn tsl_mod_vec3( x : vec3f, y : vec3f ) -> vec3f { return x - y * floor( x / y ); }' ), + mod_vec4: new CodeNode( 'fn tsl_mod_vec4( x : vec4f, y : vec4f ) -> vec4f { return x - y * floor( x / y ); }' ), + equals_bool: new CodeNode( 'fn tsl_equals_bool( a : bool, b : bool ) -> bool { return a == b; }' ), + equals_bvec2: new CodeNode( 'fn tsl_equals_bvec2( a : vec2f, b : vec2f ) -> vec2 { return vec2( a.x == b.x, a.y == b.y ); }' ), + equals_bvec3: new CodeNode( 'fn tsl_equals_bvec3( a : vec3f, b : vec3f ) -> vec3 { return vec3( a.x == b.x, a.y == b.y, a.z == b.z ); }' ), + equals_bvec4: new CodeNode( 'fn tsl_equals_bvec4( a : vec4f, b : vec4f ) -> vec4 { return vec4( a.x == b.x, a.y == b.y, a.z == b.z, a.w == b.w ); }' ), + repeatWrapping_float: new CodeNode( 'fn tsl_repeatWrapping_float( coord: f32 ) -> f32 { return fract( coord ); }' ), + mirrorWrapping_float: new CodeNode( 'fn tsl_mirrorWrapping_float( coord: f32 ) -> f32 { let mirrored = fract( coord * 0.5 ) * 2.0; return 1.0 - abs( 1.0 - mirrored ); }' ), + clampWrapping_float: new CodeNode( 'fn tsl_clampWrapping_float( coord: f32 ) -> f32 { return clamp( coord, 0.0, 1.0 ); }' ), + biquadraticTexture: new CodeNode( /* wgsl */` +fn tsl_biquadraticTexture( map : texture_2d, coord : vec2f, iRes : vec2u, level : u32 ) -> vec4f { + + let res = vec2f( iRes ); + + let uvScaled = coord * res; + let uvWrapping = ( ( uvScaled % res ) + res ) % res; + + // https://www.shadertoy.com/view/WtyXRy + + let uv = uvWrapping - 0.5; + let iuv = floor( uv ); + let f = fract( uv ); + + let rg1 = textureLoad( map, vec2u( iuv + vec2( 0.5, 0.5 ) ) % iRes, level ); + let rg2 = textureLoad( map, vec2u( iuv + vec2( 1.5, 0.5 ) ) % iRes, level ); + let rg3 = textureLoad( map, vec2u( iuv + vec2( 0.5, 1.5 ) ) % iRes, level ); + let rg4 = textureLoad( map, vec2u( iuv + vec2( 1.5, 1.5 ) ) % iRes, level ); + + return mix( mix( rg1, rg2, f.x ), mix( rg3, rg4, f.x ), f.y ); + +} +` ) +}; + +const wgslMethods = { + dFdx: 'dpdx', + dFdy: '- dpdy', + mod_float: 'tsl_mod_float', + mod_vec2: 'tsl_mod_vec2', + mod_vec3: 'tsl_mod_vec3', + mod_vec4: 'tsl_mod_vec4', + equals_bool: 'tsl_equals_bool', + equals_bvec2: 'tsl_equals_bvec2', + equals_bvec3: 'tsl_equals_bvec3', + equals_bvec4: 'tsl_equals_bvec4', + inversesqrt: 'inverseSqrt', + bitcast: 'bitcast' +}; + +// WebGPU issue: does not support pow() with negative base on Windows + +if ( typeof navigator !== 'undefined' && /Windows/g.test( navigator.userAgent ) ) { + + wgslPolyfill.pow_float = new CodeNode( 'fn tsl_pow_float( a : f32, b : f32 ) -> f32 { return select( -pow( -a, b ), pow( a, b ), a > 0.0 ); }' ); + wgslPolyfill.pow_vec2 = new CodeNode( 'fn tsl_pow_vec2( a : vec2f, b : vec2f ) -> vec2f { return vec2f( tsl_pow_float( a.x, b.x ), tsl_pow_float( a.y, b.y ) ); }', [ wgslPolyfill.pow_float ] ); + wgslPolyfill.pow_vec3 = new CodeNode( 'fn tsl_pow_vec3( a : vec3f, b : vec3f ) -> vec3f { return vec3f( tsl_pow_float( a.x, b.x ), tsl_pow_float( a.y, b.y ), tsl_pow_float( a.z, b.z ) ); }', [ wgslPolyfill.pow_float ] ); + wgslPolyfill.pow_vec4 = new CodeNode( 'fn tsl_pow_vec4( a : vec4f, b : vec4f ) -> vec4f { return vec4f( tsl_pow_float( a.x, b.x ), tsl_pow_float( a.y, b.y ), tsl_pow_float( a.z, b.z ), tsl_pow_float( a.w, b.w ) ); }', [ wgslPolyfill.pow_float ] ); + + wgslMethods.pow_float = 'tsl_pow_float'; + wgslMethods.pow_vec2 = 'tsl_pow_vec2'; + wgslMethods.pow_vec3 = 'tsl_pow_vec3'; + wgslMethods.pow_vec4 = 'tsl_pow_vec4'; + +} + +// + +let diagnostics = ''; + +if ( ( typeof navigator !== 'undefined' && /Firefox|Deno/g.test( navigator.userAgent ) ) !== true ) { + + diagnostics += 'diagnostic( off, derivative_uniformity );\n'; + +} + +/** + * A node builder targeting WGSL. + * + * This module generates WGSL shader code from node materials and also + * generates the respective bindings and vertex buffer definitions. These + * data are later used by the renderer to create render and compute pipelines + * for render objects. + * + * @augments NodeBuilder + */ +class WGSLNodeBuilder extends NodeBuilder { + + /** + * Constructs a new WGSL node builder renderer. + * + * @param {Object3D} object - The 3D object. + * @param {Renderer} renderer - The renderer. + */ + constructor( object, renderer ) { + + super( object, renderer, new WGSLNodeParser() ); + + /** + * A dictionary that holds for each shader stage ('vertex', 'fragment', 'compute') + * another dictionary which manages UBOs per group ('render','frame','object'). + * + * @type {Object>} + */ + this.uniformGroups = {}; + + /** + * A dictionary that holds for each shader stage a Map of builtins. + * + * @type {Object>} + */ + this.builtins = {}; + + /** + * A dictionary that holds for each shader stage a Set of directives. + * + * @type {Object>} + */ + this.directives = {}; + + /** + * A map for managing scope arrays. Only relevant for when using + * {@link WorkgroupInfoNode} in context of compute shaders. + * + * @type {Map} + */ + this.scopedArrays = new Map(); + + } + + /** + * Checks if the given texture requires a manual conversion to the working color space. + * + * @param {Texture} texture - The texture to check. + * @return {boolean} Whether the given texture requires a conversion to working color space or not. + */ + needsToWorkingColorSpace( texture ) { + + return texture.isVideoTexture === true && texture.colorSpace !== NoColorSpace; + + } + + /** + * Generates the WGSL snippet for sampled textures. + * + * @private + * @param {Texture} texture - The texture. + * @param {string} textureProperty - The name of the texture uniform in the shader. + * @param {string} uvSnippet - A WGSL snippet that represents texture coordinates used for sampling. + * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample. + * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for. + * @return {string} The WGSL snippet. + */ + _generateTextureSample( texture, textureProperty, uvSnippet, depthSnippet, shaderStage = this.shaderStage ) { + + if ( shaderStage === 'fragment' ) { + + if ( depthSnippet ) { + + return `textureSample( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ depthSnippet } )`; + + } else { + + return `textureSample( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet } )`; + + } + + } else { + + return this._generateTextureSampleLevel( texture, textureProperty, uvSnippet, '0', depthSnippet ); + + } + + } + + /** + * Generates the WGSL snippet when sampling video textures. + * + * @private + * @param {string} textureProperty - The name of the video texture uniform in the shader. + * @param {string} uvSnippet - A WGSL snippet that represents texture coordinates used for sampling. + * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for. + * @return {string} The WGSL snippet. + */ + _generateVideoSample( textureProperty, uvSnippet, shaderStage = this.shaderStage ) { + + if ( shaderStage === 'fragment' ) { + + return `textureSampleBaseClampToEdge( ${ textureProperty }, ${ textureProperty }_sampler, vec2( ${ uvSnippet }.x, 1.0 - ${ uvSnippet }.y ) )`; + + } else { + + console.error( `WebGPURenderer: THREE.VideoTexture does not support ${ shaderStage } shader.` ); + + } + + } + + /** + * Generates the WGSL snippet when sampling textures with explicit mip level. + * + * @private + * @param {Texture} texture - The texture. + * @param {string} textureProperty - The name of the texture uniform in the shader. + * @param {string} uvSnippet - A WGSL snippet that represents texture coordinates used for sampling. + * @param {string} levelSnippet - A WGSL snippet that represents the mip level, with level 0 containing a full size version of the texture. + * @param {string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample. + * @return {string} The WGSL snippet. + */ + _generateTextureSampleLevel( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet ) { + + if ( this.isUnfilterable( texture ) === false ) { + + return `textureSampleLevel( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ levelSnippet } )`; + + } else if ( this.isFilteredTexture( texture ) ) { + + return this.generateFilteredTexture( texture, textureProperty, uvSnippet, levelSnippet ); + + } else { + + return this.generateTextureLod( texture, textureProperty, uvSnippet, depthSnippet, levelSnippet ); + + } + + } + + /** + * Generates a wrap function used in context of textures. + * + * @param {Texture} texture - The texture to generate the function for. + * @return {string} The name of the generated function. + */ + generateWrapFunction( texture ) { + + const functionName = `tsl_coord_${ wrapNames[ texture.wrapS ] }S_${ wrapNames[ texture.wrapT ] }_${ texture.isData3DTexture ? '3d' : '2d' }T`; + + let nodeCode = wgslCodeCache[ functionName ]; + + if ( nodeCode === undefined ) { + + const includes = []; + + // For 3D textures, use vec3f; for texture arrays, keep vec2f since array index is separate + const coordType = texture.isData3DTexture ? 'vec3f' : 'vec2f'; + let code = `fn ${ functionName }( coord : ${ coordType } ) -> ${ coordType } {\n\n\treturn ${ coordType }(\n`; + + const addWrapSnippet = ( wrap, axis ) => { + + if ( wrap === RepeatWrapping ) { + + includes.push( wgslPolyfill.repeatWrapping_float ); + + code += `\t\ttsl_repeatWrapping_float( coord.${ axis } )`; + + } else if ( wrap === ClampToEdgeWrapping ) { + + includes.push( wgslPolyfill.clampWrapping_float ); + + code += `\t\ttsl_clampWrapping_float( coord.${ axis } )`; + + } else if ( wrap === MirroredRepeatWrapping ) { + + includes.push( wgslPolyfill.mirrorWrapping_float ); + + code += `\t\ttsl_mirrorWrapping_float( coord.${ axis } )`; + + } else { + + code += `\t\tcoord.${ axis }`; + + console.warn( `WebGPURenderer: Unsupported texture wrap type "${ wrap }" for vertex shader.` ); + + } + + }; + + addWrapSnippet( texture.wrapS, 'x' ); + + code += ',\n'; + + addWrapSnippet( texture.wrapT, 'y' ); + + if ( texture.isData3DTexture ) { + + code += ',\n'; + addWrapSnippet( texture.wrapR, 'z' ); + + } + + code += '\n\t);\n\n}\n'; + + wgslCodeCache[ functionName ] = nodeCode = new CodeNode( code, includes ); + + } + + nodeCode.build( this ); + + return functionName; + + } + + /** + * Generates the array declaration string. + * + * @param {string} type - The type. + * @param {?number} [count] - The count. + * @return {string} The generated value as a shader string. + */ + generateArrayDeclaration( type, count ) { + + return `array< ${ this.getType( type ) }, ${ count } >`; + + } + + /** + * Generates a WGSL variable that holds the texture dimension of the given texture. + * It also returns information about the number of layers (elements) of an arrayed + * texture as well as the cube face count of cube textures. + * + * @param {Texture} texture - The texture to generate the function for. + * @param {string} textureProperty - The name of the video texture uniform in the shader. + * @param {string} levelSnippet - A WGSL snippet that represents the mip level, with level 0 containing a full size version of the texture. + * @return {string} The name of the dimension variable. + */ + generateTextureDimension( texture, textureProperty, levelSnippet ) { + + const textureData = this.getDataFromNode( texture, this.shaderStage, this.globalCache ); + + if ( textureData.dimensionsSnippet === undefined ) textureData.dimensionsSnippet = {}; + + let textureDimensionNode = textureData.dimensionsSnippet[ levelSnippet ]; + + if ( textureData.dimensionsSnippet[ levelSnippet ] === undefined ) { + + let textureDimensionsParams; + let dimensionType; + + const { primarySamples } = this.renderer.backend.utils.getTextureSampleData( texture ); + const isMultisampled = primarySamples > 1; + + if ( texture.isData3DTexture ) { + + dimensionType = 'vec3'; + + } else { + + // Regular 2D textures, depth textures, etc. + dimensionType = 'vec2'; + + } + + // Build parameters string based on texture type and multisampling + if ( isMultisampled || texture.isVideoTexture || texture.isStorageTexture ) { + + textureDimensionsParams = textureProperty; + + } else { + + textureDimensionsParams = `${textureProperty}${levelSnippet ? `, u32( ${ levelSnippet } )` : ''}`; + + } + + textureDimensionNode = new VarNode( new ExpressionNode( `textureDimensions( ${ textureDimensionsParams } )`, dimensionType ) ); + + textureData.dimensionsSnippet[ levelSnippet ] = textureDimensionNode; + + if ( texture.isArrayTexture || texture.isDataArrayTexture || texture.isData3DTexture ) { + + textureData.arrayLayerCount = new VarNode( + new ExpressionNode( + `textureNumLayers(${textureProperty})`, + 'u32' + ) + ); + + } + + // For cube textures, we know it's always 6 faces + if ( texture.isTextureCube ) { + + textureData.cubeFaceCount = new VarNode( + new ExpressionNode( '6u', 'u32' ) + ); + + } + + } + + return textureDimensionNode.build( this ); + + } + + /** + * Generates the WGSL snippet for a manual filtered texture. + * + * @param {Texture} texture - The texture. + * @param {string} textureProperty - The name of the texture uniform in the shader. + * @param {string} uvSnippet - A WGSL snippet that represents texture coordinates used for sampling. + * @param {string} levelSnippet - A WGSL snippet that represents the mip level, with level 0 containing a full size version of the texture. + * @return {string} The WGSL snippet. + */ + generateFilteredTexture( texture, textureProperty, uvSnippet, levelSnippet = '0u' ) { + + this._include( 'biquadraticTexture' ); + + const wrapFunction = this.generateWrapFunction( texture ); + const textureDimension = this.generateTextureDimension( texture, textureProperty, levelSnippet ); + + return `tsl_biquadraticTexture( ${ textureProperty }, ${ wrapFunction }( ${ uvSnippet } ), ${ textureDimension }, u32( ${ levelSnippet } ) )`; + + } + + /** + * Generates the WGSL snippet for a texture lookup with explicit level-of-detail. + * Since it's a lookup, no sampling or filtering is applied. + * + * @param {Texture} texture - The texture. + * @param {string} textureProperty - The name of the texture uniform in the shader. + * @param {string} uvSnippet - A WGSL snippet that represents texture coordinates used for sampling. + * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample. + * @param {string} [levelSnippet='0u'] - A WGSL snippet that represents the mip level, with level 0 containing a full size version of the texture. + * @return {string} The WGSL snippet. + */ + generateTextureLod( texture, textureProperty, uvSnippet, depthSnippet, levelSnippet = '0u' ) { + + const wrapFunction = this.generateWrapFunction( texture ); + const textureDimension = this.generateTextureDimension( texture, textureProperty, levelSnippet ); + + const vecType = texture.isData3DTexture ? 'vec3' : 'vec2'; + const coordSnippet = `${ vecType }( ${ wrapFunction }( ${ uvSnippet } ) * ${ vecType }( ${ textureDimension } ) )`; + + return this.generateTextureLoad( texture, textureProperty, coordSnippet, depthSnippet, levelSnippet ); + + } + + /** + * Generates the WGSL snippet that reads a single texel from a texture without sampling or filtering. + * + * @param {Texture} texture - The texture. + * @param {string} textureProperty - The name of the texture uniform in the shader. + * @param {string} uvIndexSnippet - A WGSL snippet that represents texture coordinates used for sampling. + * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample. + * @param {string} [levelSnippet='0u'] - A WGSL snippet that represents the mip level, with level 0 containing a full size version of the texture. + * @return {string} The WGSL snippet. + */ + generateTextureLoad( texture, textureProperty, uvIndexSnippet, depthSnippet, levelSnippet = '0u' ) { + + let snippet; + + if ( texture.isVideoTexture === true ) { + + snippet = `textureLoad( ${ textureProperty }, ${ uvIndexSnippet } )`; + + } else if ( depthSnippet ) { + + snippet = `textureLoad( ${ textureProperty }, ${ uvIndexSnippet }, ${ depthSnippet }, u32( ${ levelSnippet } ) )`; + + } else { + + snippet = `textureLoad( ${ textureProperty }, ${ uvIndexSnippet }, u32( ${ levelSnippet } ) )`; + + if ( this.renderer.backend.compatibilityMode && texture.isDepthTexture ) { + + snippet += '.x'; + + } + + } + + return snippet; + + } + + /** + * Generates the WGSL snippet that writes a single texel to a texture. + * + * @param {Texture} texture - The texture. + * @param {string} textureProperty - The name of the texture uniform in the shader. + * @param {string} uvIndexSnippet - A WGSL snippet that represents texture coordinates used for sampling. + * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample. + * @param {string} valueSnippet - A WGSL snippet that represent the new texel value. + * @return {string} The WGSL snippet. + */ + generateTextureStore( texture, textureProperty, uvIndexSnippet, depthSnippet, valueSnippet ) { + + let snippet; + + if ( depthSnippet ) { + + snippet = `textureStore( ${ textureProperty }, ${ uvIndexSnippet }, ${ depthSnippet }, ${ valueSnippet } )`; + + } else { + + snippet = `textureStore( ${ textureProperty }, ${ uvIndexSnippet }, ${ valueSnippet } )`; + + } + + return snippet; + + } + + /** + * Returns `true` if the sampled values of the given texture should be compared against a reference value. + * + * @param {Texture} texture - The texture. + * @return {boolean} Whether the sampled values of the given texture should be compared against a reference value or not. + */ + isSampleCompare( texture ) { + + return texture.isDepthTexture === true && texture.compareFunction !== null; + + } + + /** + * Returns `true` if the given texture is unfilterable. + * + * @param {Texture} texture - The texture. + * @return {boolean} Whether the given texture is unfilterable or not. + */ + isUnfilterable( texture ) { + + return this.getComponentTypeFromTexture( texture ) !== 'float' || + ( ! this.isAvailable( 'float32Filterable' ) && texture.isDataTexture === true && texture.type === FloatType ) || + ( this.isSampleCompare( texture ) === false && texture.minFilter === NearestFilter && texture.magFilter === NearestFilter ) || + this.renderer.backend.utils.getTextureSampleData( texture ).primarySamples > 1; + + } + + /** + * Generates the WGSL snippet for sampling/loading the given texture. + * + * @param {Texture} texture - The texture. + * @param {string} textureProperty - The name of the texture uniform in the shader. + * @param {string} uvSnippet - A WGSL snippet that represents texture coordinates used for sampling. + * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample. + * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for. + * @return {string} The WGSL snippet. + */ + generateTexture( texture, textureProperty, uvSnippet, depthSnippet, shaderStage = this.shaderStage ) { + + let snippet = null; + + if ( texture.isVideoTexture === true ) { + + snippet = this._generateVideoSample( textureProperty, uvSnippet, shaderStage ); + + } else if ( this.isUnfilterable( texture ) ) { + + snippet = this.generateTextureLod( texture, textureProperty, uvSnippet, depthSnippet, '0', shaderStage ); + + } else { + + snippet = this._generateTextureSample( texture, textureProperty, uvSnippet, depthSnippet, shaderStage ); + + } + + return snippet; + + } + + /** + * Generates the WGSL snippet for sampling/loading the given texture using explicit gradients. + * + * @param {Texture} texture - The texture. + * @param {string} textureProperty - The name of the texture uniform in the shader. + * @param {string} uvSnippet - A WGSL snippet that represents texture coordinates used for sampling. + * @param {Array} gradSnippet - An array holding both gradient WGSL snippets. + * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample. + * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for. + * @return {string} The WGSL snippet. + */ + generateTextureGrad( texture, textureProperty, uvSnippet, gradSnippet, depthSnippet, shaderStage = this.shaderStage ) { + + if ( shaderStage === 'fragment' ) { + + // TODO handle i32 or u32 --> uvSnippet, array_index: A, ddx, ddy + return `textureSampleGrad( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ gradSnippet[ 0 ] }, ${ gradSnippet[ 1 ] } )`; + + } else { + + console.error( `WebGPURenderer: THREE.TextureNode.gradient() does not support ${ shaderStage } shader.` ); + + } + + } + + /** + * Generates the WGSL snippet for sampling a depth texture and comparing the sampled depth values + * against a reference value. + * + * @param {Texture} texture - The texture. + * @param {string} textureProperty - The name of the texture uniform in the shader. + * @param {string} uvSnippet - A WGSL snippet that represents texture coordinates used for sampling. + * @param {string} compareSnippet - A WGSL snippet that represents the reference value. + * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample. + * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for. + * @return {string} The WGSL snippet. + */ + generateTextureCompare( texture, textureProperty, uvSnippet, compareSnippet, depthSnippet, shaderStage = this.shaderStage ) { + + if ( shaderStage === 'fragment' ) { + + if ( texture.isDepthTexture === true && texture.isArrayTexture === true ) { + + return `textureSampleCompare( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ depthSnippet }, ${ compareSnippet } )`; + + } + + return `textureSampleCompare( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ compareSnippet } )`; + + } else { + + console.error( `WebGPURenderer: THREE.DepthTexture.compareFunction() does not support ${ shaderStage } shader.` ); + + } + + } + + /** + * Generates the WGSL snippet when sampling textures with explicit mip level. + * + * @param {Texture} texture - The texture. + * @param {string} textureProperty - The name of the texture uniform in the shader. + * @param {string} uvSnippet - A WGSL snippet that represents texture coordinates used for sampling. + * @param {string} levelSnippet - A WGSL snippet that represents the mip level, with level 0 containing a full size version of the texture. + * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample. + * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for. + * @return {string} The WGSL snippet. + */ + generateTextureLevel( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, shaderStage = this.shaderStage ) { + + let snippet = null; + + if ( texture.isVideoTexture === true ) { + + snippet = this._generateVideoSample( textureProperty, uvSnippet, shaderStage ); + + } else { + + snippet = this._generateTextureSampleLevel( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet ); + + } + + return snippet; + + } + + /** + * Generates the WGSL snippet when sampling textures with a bias to the mip level. + * + * @param {Texture} texture - The texture. + * @param {string} textureProperty - The name of the texture uniform in the shader. + * @param {string} uvSnippet - A WGSL snippet that represents texture coordinates used for sampling. + * @param {string} biasSnippet - A WGSL snippet that represents the bias to apply to the mip level before sampling. + * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample. + * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for. + * @return {string} The WGSL snippet. + */ + generateTextureBias( texture, textureProperty, uvSnippet, biasSnippet, depthSnippet, shaderStage = this.shaderStage ) { + + if ( shaderStage === 'fragment' ) { + + return `textureSampleBias( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ biasSnippet } )`; + + } else { + + console.error( `WebGPURenderer: THREE.TextureNode.biasNode does not support ${ shaderStage } shader.` ); + + } + + } + + /** + * Returns a WGSL snippet that represents the property name of the given node. + * + * @param {Node} node - The node. + * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for. + * @return {string} The property name. + */ + getPropertyName( node, shaderStage = this.shaderStage ) { + + if ( node.isNodeVarying === true && node.needsInterpolation === true ) { + + if ( shaderStage === 'vertex' ) { + + return `varyings.${ node.name }`; + + } + + } else if ( node.isNodeUniform === true ) { + + const name = node.name; + const type = node.type; + + if ( type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' || type === 'texture3D' ) { + + return name; + + } else if ( type === 'buffer' || type === 'storageBuffer' || type === 'indirectStorageBuffer' ) { + + if ( this.isCustomStruct( node ) ) { + + return name; + + } + + return name + '.value'; + + } else { + + return node.groupNode.name + '.' + name; + + } + + } + + return super.getPropertyName( node ); + + } + + /** + * Returns the output struct name. + * + * @return {string} The name of the output struct. + */ + getOutputStructName() { + + return 'output'; + + } + + /** + * Returns the native shader operator name for a given generic name. + * + * @param {string} op - The operator name to resolve. + * @return {?string} The resolved operator name. + */ + getFunctionOperator( op ) { + + const fnOp = wgslFnOpLib[ op ]; + + if ( fnOp !== undefined ) { + + this._include( fnOp ); + + return fnOp; + + } + + return null; + + } + + /** + * Returns the node access for the given node and shader stage. + * + * @param {StorageTextureNode|StorageBufferNode} node - The storage node. + * @param {string} shaderStage - The shader stage. + * @return {string} The node access. + */ + getNodeAccess( node, shaderStage ) { + + if ( shaderStage !== 'compute' ) + return NodeAccess.READ_ONLY; + + return node.access; + + } + + /** + * Returns A WGSL snippet representing the storage access. + * + * @param {StorageTextureNode|StorageBufferNode} node - The storage node. + * @param {string} shaderStage - The shader stage. + * @return {string} The WGSL snippet representing the storage access. + */ + getStorageAccess( node, shaderStage ) { + + return accessNames[ this.getNodeAccess( node, shaderStage ) ]; + + } + + /** + * This method is one of the more important ones since it's responsible + * for generating a matching binding instance for the given uniform node. + * + * These bindings are later used in the renderer to create bind groups + * and layouts. + * + * @param {UniformNode} node - The uniform node. + * @param {string} type - The node data type. + * @param {string} shaderStage - The shader stage. + * @param {?string} [name=null] - An optional uniform name. + * @return {NodeUniform} The node uniform object. + */ + getUniformFromNode( node, type, shaderStage, name = null ) { + + const uniformNode = super.getUniformFromNode( node, type, shaderStage, name ); + const nodeData = this.getDataFromNode( node, shaderStage, this.globalCache ); + + if ( nodeData.uniformGPU === undefined ) { + + let uniformGPU; + + const group = node.groupNode; + const groupName = group.name; + + const bindings = this.getBindGroupArray( groupName, shaderStage ); + + if ( type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' || type === 'texture3D' ) { + + let texture = null; + + const access = this.getNodeAccess( node, shaderStage ); + + if ( type === 'texture' || type === 'storageTexture' ) { + + texture = new NodeSampledTexture( uniformNode.name, uniformNode.node, group, access ); + + } else if ( type === 'cubeTexture' ) { + + texture = new NodeSampledCubeTexture( uniformNode.name, uniformNode.node, group, access ); + + } else if ( type === 'texture3D' ) { + + texture = new NodeSampledTexture3D( uniformNode.name, uniformNode.node, group, access ); + + } + + texture.store = node.isStorageTextureNode === true; + texture.setVisibility( gpuShaderStageLib[ shaderStage ] ); + + if ( this.isUnfilterable( node.value ) === false && texture.store === false ) { + + const sampler = new NodeSampler( `${ uniformNode.name }_sampler`, uniformNode.node, group ); + sampler.setVisibility( gpuShaderStageLib[ shaderStage ] ); + + bindings.push( sampler, texture ); + + uniformGPU = [ sampler, texture ]; + + } else { + + bindings.push( texture ); + + uniformGPU = [ texture ]; + + } + + } else if ( type === 'buffer' || type === 'storageBuffer' || type === 'indirectStorageBuffer' ) { + + const bufferClass = type === 'buffer' ? NodeUniformBuffer : NodeStorageBuffer; + + const buffer = new bufferClass( node, group ); + buffer.setVisibility( gpuShaderStageLib[ shaderStage ] ); + + bindings.push( buffer ); + + uniformGPU = buffer; + + uniformNode.name = name ? name : 'NodeBuffer_' + uniformNode.id; + + } else { + + const uniformsStage = this.uniformGroups[ shaderStage ] || ( this.uniformGroups[ shaderStage ] = {} ); + + let uniformsGroup = uniformsStage[ groupName ]; + + if ( uniformsGroup === undefined ) { + + uniformsGroup = new NodeUniformsGroup( groupName, group ); + uniformsGroup.setVisibility( gpuShaderStageLib[ shaderStage ] ); + + uniformsStage[ groupName ] = uniformsGroup; + + bindings.push( uniformsGroup ); + + } + + uniformGPU = this.getNodeUniform( uniformNode, type ); + + uniformsGroup.addUniform( uniformGPU ); + + } + + nodeData.uniformGPU = uniformGPU; + + } + + return uniformNode; + + } + + /** + * This method should be used whenever builtins are required in nodes. + * The internal builtins data structure will make sure builtins are + * defined in the WGSL source. + * + * @param {string} name - The builtin name. + * @param {string} property - The property name. + * @param {string} type - The node data type. + * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for. + * @return {string} The property name. + */ + getBuiltin( name, property, type, shaderStage = this.shaderStage ) { + + const map = this.builtins[ shaderStage ] || ( this.builtins[ shaderStage ] = new Map() ); + + if ( map.has( name ) === false ) { + + map.set( name, { + name, + property, + type + } ); + + } + + return property; + + } + + /** + * Returns `true` if the given builtin is defined in the given shader stage. + * + * @param {string} name - The builtin name. + * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for. + * @return {boolean} Whether the given builtin is defined in the given shader stage or not. + */ + hasBuiltin( name, shaderStage = this.shaderStage ) { + + return ( this.builtins[ shaderStage ] !== undefined && this.builtins[ shaderStage ].has( name ) ); + + } + + /** + * Returns the vertex index builtin. + * + * @return {string} The vertex index. + */ + getVertexIndex() { + + if ( this.shaderStage === 'vertex' ) { + + return this.getBuiltin( 'vertex_index', 'vertexIndex', 'u32', 'attribute' ); + + } + + return 'vertexIndex'; + + } + + /** + * Builds the given shader node. + * + * @param {ShaderNodeInternal} shaderNode - The shader node. + * @return {string} The WGSL function code. + */ + buildFunctionCode( shaderNode ) { + + const layout = shaderNode.layout; + const flowData = this.flowShaderNode( shaderNode ); + + const parameters = []; + + for ( const input of layout.inputs ) { + + parameters.push( input.name + ' : ' + this.getType( input.type ) ); + + } + + // + + let code = `fn ${ layout.name }( ${ parameters.join( ', ' ) } ) -> ${ this.getType( layout.type ) } { +${ flowData.vars } +${ flowData.code } +`; + + if ( flowData.result ) { + + code += `\treturn ${ flowData.result };\n`; + + } + + code += '\n}\n'; + + // + + return code; + + } + + /** + * Returns the instance index builtin. + * + * @return {string} The instance index. + */ + getInstanceIndex() { + + if ( this.shaderStage === 'vertex' ) { + + return this.getBuiltin( 'instance_index', 'instanceIndex', 'u32', 'attribute' ); + + } + + return 'instanceIndex'; + + } + + /** + * Returns the invocation local index builtin. + * + * @return {string} The invocation local index. + */ + getInvocationLocalIndex() { + + return this.getBuiltin( 'local_invocation_index', 'invocationLocalIndex', 'u32', 'attribute' ); + + } + + /** + * Returns the subgroup size builtin. + * + * @return {string} The subgroup size. + */ + getSubgroupSize() { + + this.enableSubGroups(); + + return this.getBuiltin( 'subgroup_size', 'subgroupSize', 'u32', 'attribute' ); + + } + + /** + * Returns the invocation subgroup index builtin. + * + * @return {string} The invocation subgroup index. + */ + getInvocationSubgroupIndex() { + + this.enableSubGroups(); + + return this.getBuiltin( 'subgroup_invocation_id', 'invocationSubgroupIndex', 'u32', 'attribute' ); + + } + + /** + * Returns the subgroup index builtin. + * + * @return {string} The subgroup index. + */ + getSubgroupIndex() { + + this.enableSubGroups(); + + return this.getBuiltin( 'subgroup_id', 'subgroupIndex', 'u32', 'attribute' ); + + } + + /** + * Overwritten as a NOP since this method is intended for the WebGL 2 backend. + * + * @return {null} Null. + */ + getDrawIndex() { + + return null; + + } + + /** + * Returns the front facing builtin. + * + * @return {string} The front facing builtin. + */ + getFrontFacing() { + + return this.getBuiltin( 'front_facing', 'isFront', 'bool' ); + + } + + /** + * Returns the frag coord builtin. + * + * @return {string} The frag coord builtin. + */ + getFragCoord() { + + return this.getBuiltin( 'position', 'fragCoord', 'vec4' ) + '.xy'; + + } + + /** + * Returns the frag depth builtin. + * + * @return {string} The frag depth builtin. + */ + getFragDepth() { + + return 'output.' + this.getBuiltin( 'frag_depth', 'depth', 'f32', 'output' ); + + } + + /** + * Returns the clip distances builtin. + * + * @return {string} The clip distances builtin. + */ + getClipDistance() { + + return 'varyings.hw_clip_distances'; + + } + + /** + * Whether to flip texture data along its vertical axis or not. + * + * @return {boolean} Returns always `false` in context of WGSL. + */ + isFlipY() { + + return false; + + } + + /** + * Enables the given directive for the given shader stage. + * + * @param {string} name - The directive name. + * @param {string} [shaderStage=this.shaderStage] - The shader stage to enable the directive for. + */ + enableDirective( name, shaderStage = this.shaderStage ) { + + const stage = this.directives[ shaderStage ] || ( this.directives[ shaderStage ] = new Set() ); + stage.add( name ); + + } + + /** + * Returns the directives of the given shader stage as a WGSL string. + * + * @param {string} shaderStage - The shader stage. + * @return {string} A WGSL snippet that enables the directives of the given stage. + */ + getDirectives( shaderStage ) { + + const snippets = []; + const directives = this.directives[ shaderStage ]; + + if ( directives !== undefined ) { + + for ( const directive of directives ) { + + snippets.push( `enable ${directive};` ); + + } + + } + + return snippets.join( '\n' ); + + } + + /** + * Enables the 'subgroups' directive. + */ + enableSubGroups() { + + this.enableDirective( 'subgroups' ); + + } + + /** + * Enables the 'subgroups-f16' directive. + */ + enableSubgroupsF16() { + + this.enableDirective( 'subgroups-f16' ); + + } + + /** + * Enables the 'clip_distances' directive. + */ + enableClipDistances() { + + this.enableDirective( 'clip_distances' ); + + } + + /** + * Enables the 'f16' directive. + */ + enableShaderF16() { + + this.enableDirective( 'f16' ); + + } + + /** + * Enables the 'dual_source_blending' directive. + */ + enableDualSourceBlending() { + + this.enableDirective( 'dual_source_blending' ); + + } + + /** + * Enables hardware clipping. + * + * @param {string} planeCount - The clipping plane count. + */ + enableHardwareClipping( planeCount ) { + + this.enableClipDistances(); + this.getBuiltin( 'clip_distances', 'hw_clip_distances', `array`, 'vertex' ); + + } + + /** + * Returns the builtins of the given shader stage as a WGSL string. + * + * @param {string} shaderStage - The shader stage. + * @return {string} A WGSL snippet that represents the builtins of the given stage. + */ + getBuiltins( shaderStage ) { + + const snippets = []; + const builtins = this.builtins[ shaderStage ]; + + if ( builtins !== undefined ) { + + for ( const { name, property, type } of builtins.values() ) { + + snippets.push( `@builtin( ${name} ) ${property} : ${type}` ); + + } + + } + + return snippets.join( ',\n\t' ); + + } + + /** + * This method should be used when a new scoped buffer is used in context of + * compute shaders. It adds the array to the internal data structure which is + * later used to generate the respective WGSL. + * + * @param {string} name - The array name. + * @param {string} scope - The scope. + * @param {string} bufferType - The buffer type. + * @param {string} bufferCount - The buffer count. + * @return {string} The array name. + */ + getScopedArray( name, scope, bufferType, bufferCount ) { + + if ( this.scopedArrays.has( name ) === false ) { + + this.scopedArrays.set( name, { + name, + scope, + bufferType, + bufferCount + } ); + + } + + return name; + + } + + /** + * Returns the scoped arrays of the given shader stage as a WGSL string. + * + * @param {string} shaderStage - The shader stage. + * @return {string|undefined} The WGSL snippet that defines the scoped arrays. + * Returns `undefined` when used in the vertex or fragment stage. + */ + getScopedArrays( shaderStage ) { + + if ( shaderStage !== 'compute' ) { + + return; + + } + + const snippets = []; + + for ( const { name, scope, bufferType, bufferCount } of this.scopedArrays.values() ) { + + const type = this.getType( bufferType ); + + snippets.push( `var<${scope}> ${name}: array< ${type}, ${bufferCount} >;` ); + + } + + return snippets.join( '\n' ); + + } + + /** + * Returns the shader attributes of the given shader stage as a WGSL string. + * + * @param {string} shaderStage - The shader stage. + * @return {string} The WGSL snippet that defines the shader attributes. + */ + getAttributes( shaderStage ) { + + const snippets = []; + + if ( shaderStage === 'compute' ) { + + this.getBuiltin( 'global_invocation_id', 'globalId', 'vec3', 'attribute' ); + this.getBuiltin( 'workgroup_id', 'workgroupId', 'vec3', 'attribute' ); + this.getBuiltin( 'local_invocation_id', 'localId', 'vec3', 'attribute' ); + this.getBuiltin( 'num_workgroups', 'numWorkgroups', 'vec3', 'attribute' ); + + if ( this.renderer.hasFeature( 'subgroups' ) ) { + + this.enableDirective( 'subgroups', shaderStage ); + this.getBuiltin( 'subgroup_size', 'subgroupSize', 'u32', 'attribute' ); + + } + + } + + if ( shaderStage === 'vertex' || shaderStage === 'compute' ) { + + const builtins = this.getBuiltins( 'attribute' ); + + if ( builtins ) snippets.push( builtins ); + + const attributes = this.getAttributesArray(); + + for ( let index = 0, length = attributes.length; index < length; index ++ ) { + + const attribute = attributes[ index ]; + const name = attribute.name; + const type = this.getType( attribute.type ); + + snippets.push( `@location( ${index} ) ${ name } : ${ type }` ); + + } + + } + + return snippets.join( ',\n\t' ); + + } + + /** + * Returns the members of the given struct type node as a WGSL string. + * + * @param {StructTypeNode} struct - The struct type node. + * @return {string} The WGSL snippet that defines the struct members. + */ + getStructMembers( struct ) { + + const snippets = []; + + for ( const member of struct.members ) { + + const prefix = struct.output ? '@location( ' + member.index + ' ) ' : ''; + + let type = this.getType( member.type ); + + if ( member.atomic ) { + + type = 'atomic< ' + type + ' >'; + + } + + snippets.push( `\t${ prefix + member.name } : ${ type }` ); + + } + + if ( struct.output ) { + + snippets.push( `\t${ this.getBuiltins( 'output' ) }` ); + + } + + return snippets.join( ',\n' ); + + } + + /** + * Returns the structs of the given shader stage as a WGSL string. + * + * @param {string} shaderStage - The shader stage. + * @return {string} The WGSL snippet that defines the structs. + */ + getStructs( shaderStage ) { + + let result = ''; + + const structs = this.structs[ shaderStage ]; + + if ( structs.length > 0 ) { + + const snippets = []; + + for ( const struct of structs ) { + + let snippet = `struct ${ struct.name } {\n`; + snippet += this.getStructMembers( struct ); + snippet += '\n};'; + + snippets.push( snippet ); + + } + + result = '\n' + snippets.join( '\n\n' ) + '\n'; + + } + + return result; + + } + + /** + * Returns a WGSL string representing a variable. + * + * @param {string} type - The variable's type. + * @param {string} name - The variable's name. + * @param {?number} [count=null] - The array length. + * @return {string} The WGSL snippet that defines a variable. + */ + getVar( type, name, count = null ) { + + let snippet = `var ${ name } : `; + + if ( count !== null ) { + + snippet += this.generateArrayDeclaration( type, count ); + + } else { + + snippet += this.getType( type ); + + } + + return snippet; + + } + + /** + * Returns the variables of the given shader stage as a WGSL string. + * + * @param {string} shaderStage - The shader stage. + * @return {string} The WGSL snippet that defines the variables. + */ + getVars( shaderStage ) { + + const snippets = []; + const vars = this.vars[ shaderStage ]; + + if ( vars !== undefined ) { + + for ( const variable of vars ) { + + snippets.push( `\t${ this.getVar( variable.type, variable.name, variable.count ) };` ); + + } + + } + + return `\n${ snippets.join( '\n' ) }\n`; + + } + + /** + * Returns the varyings of the given shader stage as a WGSL string. + * + * @param {string} shaderStage - The shader stage. + * @return {string} The WGSL snippet that defines the varyings. + */ + getVaryings( shaderStage ) { + + const snippets = []; + + if ( shaderStage === 'vertex' ) { + + this.getBuiltin( 'position', 'Vertex', 'vec4', 'vertex' ); + + } + + if ( shaderStage === 'vertex' || shaderStage === 'fragment' ) { + + const varyings = this.varyings; + const vars = this.vars[ shaderStage ]; + + for ( let index = 0; index < varyings.length; index ++ ) { + + const varying = varyings[ index ]; + + if ( varying.needsInterpolation ) { + + let attributesSnippet = `@location( ${index} )`; + + if ( varying.interpolationType ) { + + const samplingSnippet = varying.interpolationSampling !== null ? `, ${ varying.interpolationSampling } )` : ' )'; + + attributesSnippet += ` @interpolate( ${ varying.interpolationType }${ samplingSnippet }`; + + // Otherwise, optimize interpolation when sensible + + } else if ( /^(int|uint|ivec|uvec)/.test( varying.type ) ) { + + attributesSnippet += ` @interpolate( ${ this.renderer.backend.compatibilityMode ? 'flat, either' : 'flat' } )`; + + } + + snippets.push( `${ attributesSnippet } ${ varying.name } : ${ this.getType( varying.type ) }` ); + + } else if ( shaderStage === 'vertex' && vars.includes( varying ) === false ) { + + vars.push( varying ); + + } + + } + + } + + const builtins = this.getBuiltins( shaderStage ); + + if ( builtins ) snippets.push( builtins ); + + const code = snippets.join( ',\n\t' ); + + return shaderStage === 'vertex' ? this._getWGSLStruct( 'VaryingsStruct', '\t' + code ) : code; + + } + + isCustomStruct( nodeUniform ) { + + const attribute = nodeUniform.value; + const bufferNode = nodeUniform.node; + + const isAttributeStructType = ( attribute.isBufferAttribute || attribute.isInstancedBufferAttribute ) && bufferNode.structTypeNode !== null; + + const isStructArray = + ( bufferNode.value && bufferNode.value.array ) && + ( typeof bufferNode.value.itemSize === 'number' && bufferNode.value.array.length > bufferNode.value.itemSize ); + + return isAttributeStructType && ! isStructArray; + + } + + /** + * Returns the uniforms of the given shader stage as a WGSL string. + * + * @param {string} shaderStage - The shader stage. + * @return {string} The WGSL snippet that defines the uniforms. + */ + getUniforms( shaderStage ) { + + const uniforms = this.uniforms[ shaderStage ]; + + const bindingSnippets = []; + const bufferSnippets = []; + const structSnippets = []; + const uniformGroups = {}; + + for ( const uniform of uniforms ) { + + const groupName = uniform.groupNode.name; + const uniformIndexes = this.bindingsIndexes[ groupName ]; + + if ( uniform.type === 'texture' || uniform.type === 'cubeTexture' || uniform.type === 'storageTexture' || uniform.type === 'texture3D' ) { + + const texture = uniform.node.value; + + if ( this.isUnfilterable( texture ) === false && uniform.node.isStorageTextureNode !== true ) { + + if ( this.isSampleCompare( texture ) ) { + + bindingSnippets.push( `@binding( ${ uniformIndexes.binding ++ } ) @group( ${ uniformIndexes.group } ) var ${ uniform.name }_sampler : sampler_comparison;` ); + + } else { + + bindingSnippets.push( `@binding( ${ uniformIndexes.binding ++ } ) @group( ${ uniformIndexes.group } ) var ${ uniform.name }_sampler : sampler;` ); + + } + + } + + let textureType; + + let multisampled = ''; + + const { primarySamples } = this.renderer.backend.utils.getTextureSampleData( texture ); + + if ( primarySamples > 1 ) { + + multisampled = '_multisampled'; + + } + + if ( texture.isCubeTexture === true ) { + + textureType = 'texture_cube'; + + } else if ( texture.isDepthTexture === true ) { + + if ( this.renderer.backend.compatibilityMode && texture.compareFunction === null ) { + + textureType = `texture${ multisampled }_2d`; + + } else { + + textureType = `texture_depth${ multisampled }_2d${ texture.isArrayTexture === true ? '_array' : '' }`; + + } + + } else if ( uniform.node.isStorageTextureNode === true ) { + + const format = getFormat( texture ); + const access = this.getStorageAccess( uniform.node, shaderStage ); + + const is3D = uniform.node.value.is3DTexture; + const isArrayTexture = uniform.node.value.isArrayTexture; + + const dimension = is3D ? '3d' : `2d${ isArrayTexture ? '_array' : '' }`; + + textureType = `texture_storage_${ dimension }<${ format }, ${ access }>`; + + } else if ( texture.isArrayTexture === true || texture.isDataArrayTexture === true || texture.isCompressedArrayTexture === true ) { + + textureType = 'texture_2d_array'; + + } else if ( texture.is3DTexture === true || texture.isData3DTexture === true ) { + + textureType = 'texture_3d'; + + } else if ( texture.isVideoTexture === true ) { + + textureType = 'texture_external'; + + } else { + + const componentPrefix = this.getComponentTypeFromTexture( texture ).charAt( 0 ); + + textureType = `texture${ multisampled }_2d<${ componentPrefix }32>`; + + } + + bindingSnippets.push( `@binding( ${ uniformIndexes.binding ++ } ) @group( ${ uniformIndexes.group } ) var ${ uniform.name } : ${ textureType };` ); + + } else if ( uniform.type === 'buffer' || uniform.type === 'storageBuffer' || uniform.type === 'indirectStorageBuffer' ) { + + const bufferNode = uniform.node; + const bufferType = this.getType( bufferNode.getNodeType( this ) ); + const bufferCount = bufferNode.bufferCount; + const bufferCountSnippet = bufferCount > 0 && uniform.type === 'buffer' ? ', ' + bufferCount : ''; + const bufferAccessMode = bufferNode.isStorageBufferNode ? `storage, ${ this.getStorageAccess( bufferNode, shaderStage ) }` : 'uniform'; + + if ( this.isCustomStruct( uniform ) ) { + + bufferSnippets.push( `@binding( ${ uniformIndexes.binding ++ } ) @group( ${ uniformIndexes.group } ) var<${ bufferAccessMode }> ${ uniform.name } : ${ bufferType };` ); + + } else { + + const bufferTypeSnippet = bufferNode.isAtomic ? `atomic<${ bufferType }>` : `${ bufferType }`; + const bufferSnippet = `\tvalue : array< ${ bufferTypeSnippet }${ bufferCountSnippet } >`; + + bufferSnippets.push( this._getWGSLStructBinding( uniform.name, bufferSnippet, bufferAccessMode, uniformIndexes.binding ++, uniformIndexes.group ) ); + + } + + } else { + + const vectorType = this.getType( this.getVectorType( uniform.type ) ); + const groupName = uniform.groupNode.name; + + const group = uniformGroups[ groupName ] || ( uniformGroups[ groupName ] = { + index: uniformIndexes.binding ++, + id: uniformIndexes.group, + snippets: [] + } ); + + group.snippets.push( `\t${ uniform.name } : ${ vectorType }` ); + + } + + } + + for ( const name in uniformGroups ) { + + const group = uniformGroups[ name ]; + + structSnippets.push( this._getWGSLStructBinding( name, group.snippets.join( ',\n' ), 'uniform', group.index, group.id ) ); + + } + + let code = bindingSnippets.join( '\n' ); + code += bufferSnippets.join( '\n' ); + code += structSnippets.join( '\n' ); + + return code; + + } + + /** + * Controls the code build of the shader stages. + */ + buildCode() { + + const shadersData = this.material !== null ? { fragment: {}, vertex: {} } : { compute: {} }; + + this.sortBindingGroups(); + + for ( const shaderStage in shadersData ) { + + this.shaderStage = shaderStage; + + const stageData = shadersData[ shaderStage ]; + stageData.uniforms = this.getUniforms( shaderStage ); + stageData.attributes = this.getAttributes( shaderStage ); + stageData.varyings = this.getVaryings( shaderStage ); + stageData.structs = this.getStructs( shaderStage ); + stageData.vars = this.getVars( shaderStage ); + stageData.codes = this.getCodes( shaderStage ); + stageData.directives = this.getDirectives( shaderStage ); + stageData.scopedArrays = this.getScopedArrays( shaderStage ); + + // + + let flow = '// code\n\n'; + flow += this.flowCode[ shaderStage ]; + + const flowNodes = this.flowNodes[ shaderStage ]; + const mainNode = flowNodes[ flowNodes.length - 1 ]; + + const outputNode = mainNode.outputNode; + const isOutputStruct = ( outputNode !== undefined && outputNode.isOutputStructNode === true ); + + for ( const node of flowNodes ) { + + const flowSlotData = this.getFlowData( node/*, shaderStage*/ ); + const slotName = node.name; + + if ( slotName ) { + + if ( flow.length > 0 ) flow += '\n'; + + flow += `\t// flow -> ${ slotName }\n`; + + } + + flow += `${ flowSlotData.code }\n\t`; + + if ( node === mainNode && shaderStage !== 'compute' ) { + + flow += '// result\n\n\t'; + + if ( shaderStage === 'vertex' ) { + + flow += `varyings.Vertex = ${ flowSlotData.result };`; + + } else if ( shaderStage === 'fragment' ) { + + if ( isOutputStruct ) { + + stageData.returnType = outputNode.getNodeType( this ); + stageData.structs += 'var output : ' + stageData.returnType + ';'; + + flow += `return ${ flowSlotData.result };`; + + } else { + + let structSnippet = '\t@location(0) color: vec4'; + + const builtins = this.getBuiltins( 'output' ); + + if ( builtins ) structSnippet += ',\n\t' + builtins; + + stageData.returnType = 'OutputStruct'; + stageData.structs += this._getWGSLStruct( 'OutputStruct', structSnippet ); + stageData.structs += '\nvar output : OutputStruct;'; + + flow += `output.color = ${ flowSlotData.result };\n\n\treturn output;`; + + } + + } + + } + + } + + stageData.flow = flow; + + } + + this.shaderStage = null; + + if ( this.material !== null ) { + + this.vertexShader = this._getWGSLVertexCode( shadersData.vertex ); + this.fragmentShader = this._getWGSLFragmentCode( shadersData.fragment ); + + } else { + + this.computeShader = this._getWGSLComputeCode( shadersData.compute, ( this.object.workgroupSize || [ 64 ] ).join( ', ' ) ); + + } + + } + + /** + * Returns the native shader method name for a given generic name. + * + * @param {string} method - The method name to resolve. + * @param {?string} [output=null] - An optional output. + * @return {string} The resolved WGSL method name. + */ + getMethod( method, output = null ) { + + let wgslMethod; + + if ( output !== null ) { + + wgslMethod = this._getWGSLMethod( method + '_' + output ); + + } + + if ( wgslMethod === undefined ) { + + wgslMethod = this._getWGSLMethod( method ); + + } + + return wgslMethod || method; + + } + + /** + * Returns the WGSL type of the given node data type. + * + * @param {string} type - The node data type. + * @return {string} The WGSL type. + */ + getType( type ) { + + return wgslTypeLib[ type ] || type; + + } + + /** + * Whether the requested feature is available or not. + * + * @param {string} name - The requested feature. + * @return {boolean} Whether the requested feature is supported or not. + */ + isAvailable( name ) { + + let result = supports[ name ]; + + if ( result === undefined ) { + + if ( name === 'float32Filterable' ) { + + result = this.renderer.hasFeature( 'float32-filterable' ); + + } else if ( name === 'clipDistance' ) { + + result = this.renderer.hasFeature( 'clip-distances' ); + + } + + supports[ name ] = result; + + } + + return result; + + } + + /** + * Returns the native shader method name for a given generic name. + * + * @private + * @param {string} method - The method name to resolve. + * @return {string} The resolved WGSL method name. + */ + _getWGSLMethod( method ) { + + if ( wgslPolyfill[ method ] !== undefined ) { + + this._include( method ); + + } + + return wgslMethods[ method ]; + + } + + /** + * Includes the given method name into the current + * function node. + * + * @private + * @param {string} name - The method name to include. + * @return {CodeNode} The respective code node. + */ + _include( name ) { + + const codeNode = wgslPolyfill[ name ]; + codeNode.build( this ); + + if ( this.currentFunctionNode !== null ) { + + this.currentFunctionNode.includes.push( codeNode ); + + } + + return codeNode; + + } + + /** + * Returns a WGSL vertex shader based on the given shader data. + * + * @private + * @param {Object} shaderData - The shader data. + * @return {string} The vertex shader. + */ + _getWGSLVertexCode( shaderData ) { + + return `${ this.getSignature() } +// directives +${shaderData.directives} + +// structs +${shaderData.structs} + +// uniforms +${shaderData.uniforms} + +// varyings +${shaderData.varyings} +var varyings : VaryingsStruct; + +// codes +${shaderData.codes} + +@vertex +fn main( ${shaderData.attributes} ) -> VaryingsStruct { + + // vars + ${shaderData.vars} + + // flow + ${shaderData.flow} + + return varyings; + +} +`; + + } + + /** + * Returns a WGSL fragment shader based on the given shader data. + * + * @private + * @param {Object} shaderData - The shader data. + * @return {string} The vertex shader. + */ + _getWGSLFragmentCode( shaderData ) { + + return `${ this.getSignature() } +// global +${ diagnostics } + +// structs +${shaderData.structs} + +// uniforms +${shaderData.uniforms} + +// codes +${shaderData.codes} + +@fragment +fn main( ${shaderData.varyings} ) -> ${shaderData.returnType} { + + // vars + ${shaderData.vars} + + // flow + ${shaderData.flow} + +} +`; + + } + + /** + * Returns a WGSL compute shader based on the given shader data. + * + * @private + * @param {Object} shaderData - The shader data. + * @param {string} workgroupSize - The workgroup size. + * @return {string} The vertex shader. + */ + _getWGSLComputeCode( shaderData, workgroupSize ) { + + return `${ this.getSignature() } +// directives +${shaderData.directives} + +// system +var instanceIndex : u32; + +// locals +${shaderData.scopedArrays} + +// structs +${shaderData.structs} + +// uniforms +${shaderData.uniforms} + +// codes +${shaderData.codes} + +@compute @workgroup_size( ${workgroupSize} ) +fn main( ${shaderData.attributes} ) { + + // system + instanceIndex = globalId.x + globalId.y * numWorkgroups.x * u32(${workgroupSize}) + globalId.z * numWorkgroups.x * numWorkgroups.y * u32(${workgroupSize}); + + // vars + ${shaderData.vars} + + // flow + ${shaderData.flow} + +} +`; + + } + + /** + * Returns a WGSL struct based on the given name and variables. + * + * @private + * @param {string} name - The struct name. + * @param {string} vars - The struct variables. + * @return {string} The WGSL snippet representing a struct. + */ + _getWGSLStruct( name, vars ) { + + return ` +struct ${name} { +${vars} +};`; + + } + + /** + * Returns a WGSL struct binding. + * + * @private + * @param {string} name - The struct name. + * @param {string} vars - The struct variables. + * @param {string} access - The access. + * @param {number} [binding=0] - The binding index. + * @param {number} [group=0] - The group index. + * @return {string} The WGSL snippet representing a struct binding. + */ + _getWGSLStructBinding( name, vars, access, binding = 0, group = 0 ) { + + const structName = name + 'Struct'; + const structSnippet = this._getWGSLStruct( structName, vars ); + + return `${structSnippet} +@binding( ${ binding } ) @group( ${ group } ) +var<${access}> ${ name } : ${ structName };`; + + } + +} + +/** + * A WebGPU backend utility module with common helpers. + * + * @private + */ +class WebGPUUtils { + + /** + * Constructs a new utility object. + * + * @param {WebGPUBackend} backend - The WebGPU backend. + */ + constructor( backend ) { + + /** + * A reference to the WebGPU backend. + * + * @type {WebGPUBackend} + */ + this.backend = backend; + + } + + /** + * Returns the depth/stencil GPU format for the given render context. + * + * @param {RenderContext} renderContext - The render context. + * @return {string} The depth/stencil GPU texture format. + */ + getCurrentDepthStencilFormat( renderContext ) { + + let format; + + if ( renderContext.depthTexture !== null ) { + + format = this.getTextureFormatGPU( renderContext.depthTexture ); + + } else if ( renderContext.depth && renderContext.stencil ) { + + format = GPUTextureFormat.Depth24PlusStencil8; + + } else if ( renderContext.depth ) { + + format = GPUTextureFormat.Depth24Plus; + + } + + return format; + + } + + /** + * Returns the GPU format for the given texture. + * + * @param {Texture} texture - The texture. + * @return {string} The GPU texture format. + */ + getTextureFormatGPU( texture ) { + + return this.backend.get( texture ).format; + + } + + /** + * Returns an object that defines the multi-sampling state of the given texture. + * + * @param {Texture} texture - The texture. + * @return {Object} The multi-sampling state. + */ + getTextureSampleData( texture ) { + + let samples; + + if ( texture.isFramebufferTexture ) { + + samples = 1; + + } else if ( texture.isDepthTexture && ! texture.renderTarget ) { + + const renderer = this.backend.renderer; + const renderTarget = renderer.getRenderTarget(); + + samples = renderTarget ? renderTarget.samples : renderer.samples; + + } else if ( texture.renderTarget ) { + + samples = texture.renderTarget.samples; + + } + + samples = samples || 1; + + const isMSAA = samples > 1 && texture.renderTarget !== null && ( texture.isDepthTexture !== true && texture.isFramebufferTexture !== true ); + const primarySamples = isMSAA ? 1 : samples; + + return { samples, primarySamples, isMSAA }; + + } + + /** + * Returns the default color attachment's GPU format of the current render context. + * + * @param {RenderContext} renderContext - The render context. + * @return {string} The GPU texture format of the default color attachment. + */ + getCurrentColorFormat( renderContext ) { + + let format; + + if ( renderContext.textures !== null ) { + + format = this.getTextureFormatGPU( renderContext.textures[ 0 ] ); + + } else { + + format = this.getPreferredCanvasFormat(); // default context format + + } + + return format; + + } + + /** + * Returns the output color space of the current render context. + * + * @param {RenderContext} renderContext - The render context. + * @return {string} The output color space. + */ + getCurrentColorSpace( renderContext ) { + + if ( renderContext.textures !== null ) { + + return renderContext.textures[ 0 ].colorSpace; + + } + + return this.backend.renderer.outputColorSpace; + + } + + /** + * Returns GPU primitive topology for the given object and material. + * + * @param {Object3D} object - The 3D object. + * @param {Material} material - The material. + * @return {string} The GPU primitive topology. + */ + getPrimitiveTopology( object, material ) { + + if ( object.isPoints ) return GPUPrimitiveTopology.PointList; + else if ( object.isLineSegments || ( object.isMesh && material.wireframe === true ) ) return GPUPrimitiveTopology.LineList; + else if ( object.isLine ) return GPUPrimitiveTopology.LineStrip; + else if ( object.isMesh ) return GPUPrimitiveTopology.TriangleList; + + } + + /** + * Returns a modified sample count from the given sample count value. + * + * That is required since WebGPU does not support arbitrary sample counts. + * + * @param {number} sampleCount - The input sample count. + * @return {number} The (potentially updated) output sample count. + */ + getSampleCount( sampleCount ) { + + let count = 1; + + if ( sampleCount > 1 ) { + + // WebGPU only supports power-of-two sample counts and 2 is not a valid value + count = Math.pow( 2, Math.floor( Math.log2( sampleCount ) ) ); + + if ( count === 2 ) { + + count = 4; + + } + + } + + return count; + + } + + /** + * Returns the sample count of the given render context. + * + * @param {RenderContext} renderContext - The render context. + * @return {number} The sample count. + */ + getSampleCountRenderContext( renderContext ) { + + if ( renderContext.textures !== null ) { + + return this.getSampleCount( renderContext.sampleCount ); + + } + + return this.getSampleCount( this.backend.renderer.samples ); + + } + + /** + * Returns the preferred canvas format. + * + * There is a separate method for this so it's possible to + * honor edge cases for specific devices. + * + * @return {string} The GPU texture format of the canvas. + */ + getPreferredCanvasFormat() { + + const outputType = this.backend.parameters.outputType; + + if ( outputType === undefined ) { + + return navigator.gpu.getPreferredCanvasFormat(); + + } else if ( outputType === UnsignedByteType ) { + + return GPUTextureFormat.BGRA8Unorm; + + } else if ( outputType === HalfFloatType ) { + + return GPUTextureFormat.RGBA16Float; + + } else { + + throw new Error( 'Unsupported outputType' ); + + } + + } + +} + +const typedArraysToVertexFormatPrefix = new Map( [ + [ Int8Array, [ 'sint8', 'snorm8' ]], + [ Uint8Array, [ 'uint8', 'unorm8' ]], + [ Int16Array, [ 'sint16', 'snorm16' ]], + [ Uint16Array, [ 'uint16', 'unorm16' ]], + [ Int32Array, [ 'sint32', 'snorm32' ]], + [ Uint32Array, [ 'uint32', 'unorm32' ]], + [ Float32Array, [ 'float32', ]], +] ); + +if ( typeof Float16Array !== 'undefined' ) { + + typedArraysToVertexFormatPrefix.set( Float16Array, [ 'float16' ] ); + +} + +const typedAttributeToVertexFormatPrefix = new Map( [ + [ Float16BufferAttribute, [ 'float16', ]], +] ); + +const typeArraysToVertexFormatPrefixForItemSize1 = new Map( [ + [ Int32Array, 'sint32' ], + [ Int16Array, 'sint32' ], // patch for INT16 + [ Uint32Array, 'uint32' ], + [ Uint16Array, 'uint32' ], // patch for UINT16 + [ Float32Array, 'float32' ] +] ); + +/** + * A WebGPU backend utility module for managing shader attributes. + * + * @private + */ +class WebGPUAttributeUtils { + + /** + * Constructs a new utility object. + * + * @param {WebGPUBackend} backend - The WebGPU backend. + */ + constructor( backend ) { + + /** + * A reference to the WebGPU backend. + * + * @type {WebGPUBackend} + */ + this.backend = backend; + + } + + /** + * Creates the GPU buffer for the given buffer attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute. + * @param {GPUBufferUsage} usage - A flag that indicates how the buffer may be used after its creation. + */ + createAttribute( attribute, usage ) { + + const bufferAttribute = this._getBufferAttribute( attribute ); + + const backend = this.backend; + const bufferData = backend.get( bufferAttribute ); + + let buffer = bufferData.buffer; + + if ( buffer === undefined ) { + + const device = backend.device; + + let array = bufferAttribute.array; + + // patch for INT16 and UINT16 + if ( attribute.normalized === false ) { + + if ( array.constructor === Int16Array || array.constructor === Int8Array ) { + + array = new Int32Array( array ); + + } else if ( array.constructor === Uint16Array || array.constructor === Uint8Array ) { + + array = new Uint32Array( array ); + + if ( usage & GPUBufferUsage.INDEX ) { + + for ( let i = 0; i < array.length; i ++ ) { + + if ( array[ i ] === 0xffff ) array[ i ] = 0xffffffff; // use correct primitive restart index + + } + + } + + } + + } + + bufferAttribute.array = array; + + if ( ( bufferAttribute.isStorageBufferAttribute || bufferAttribute.isStorageInstancedBufferAttribute ) && bufferAttribute.itemSize === 3 ) { + + array = new array.constructor( bufferAttribute.count * 4 ); + + for ( let i = 0; i < bufferAttribute.count; i ++ ) { + + array.set( bufferAttribute.array.subarray( i * 3, i * 3 + 3 ), i * 4 ); + + } + + // Update BufferAttribute + bufferAttribute.itemSize = 4; + bufferAttribute.array = array; + + bufferData._force3to4BytesAlignment = true; + + } + + // ensure 4 byte alignment + const byteLength = array.byteLength; + const size = byteLength + ( ( 4 - ( byteLength % 4 ) ) % 4 ); + + buffer = device.createBuffer( { + label: bufferAttribute.name, + size: size, + usage: usage, + mappedAtCreation: true + } ); + + new array.constructor( buffer.getMappedRange() ).set( array ); + + buffer.unmap(); + + bufferData.buffer = buffer; + + } + + } + + /** + * Updates the GPU buffer of the given buffer attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute. + */ + updateAttribute( attribute ) { + + const bufferAttribute = this._getBufferAttribute( attribute ); + + const backend = this.backend; + const device = backend.device; + + const bufferData = backend.get( bufferAttribute ); + const buffer = backend.get( bufferAttribute ).buffer; + + let array = bufferAttribute.array; + + // if storage buffer ensure 4 byte alignment + if ( bufferData._force3to4BytesAlignment === true ) { + + array = new array.constructor( bufferAttribute.count * 4 ); + + for ( let i = 0; i < bufferAttribute.count; i ++ ) { + + array.set( bufferAttribute.array.subarray( i * 3, i * 3 + 3 ), i * 4 ); + + } + + bufferAttribute.array = array; + + } + + + const isTypedArray = this._isTypedArray( array ); + const updateRanges = bufferAttribute.updateRanges; + + if ( updateRanges.length === 0 ) { + + // Not using update ranges + + device.queue.writeBuffer( + buffer, + 0, + array, + 0 + ); + + } else { + + const byteOffsetFactor = isTypedArray ? 1 : array.BYTES_PER_ELEMENT; + + for ( let i = 0, l = updateRanges.length; i < l; i ++ ) { + + const range = updateRanges[ i ]; + let dataOffset, size; + + if ( bufferData._force3to4BytesAlignment === true ) { + + const vertexStart = Math.floor( range.start / 3 ); + const vertexCount = Math.ceil( range.count / 3 ); + dataOffset = vertexStart * 4 * byteOffsetFactor; + size = vertexCount * 4 * byteOffsetFactor; + + } else { + + dataOffset = range.start * byteOffsetFactor; + size = range.count * byteOffsetFactor; + + } + + const bufferOffset = dataOffset * ( isTypedArray ? array.BYTES_PER_ELEMENT : 1 ); // bufferOffset is always in bytes + + device.queue.writeBuffer( + buffer, + bufferOffset, + array, + dataOffset, + size + ); + + } + + bufferAttribute.clearUpdateRanges(); + + } + + } + + /** + * This method creates the vertex buffer layout data which are + * require when creating a render pipeline for the given render object. + * + * @param {RenderObject} renderObject - The render object. + * @return {Array} An array holding objects which describe the vertex buffer layout. + */ + createShaderVertexBuffers( renderObject ) { + + const attributes = renderObject.getAttributes(); + const vertexBuffers = new Map(); + + for ( let slot = 0; slot < attributes.length; slot ++ ) { + + const geometryAttribute = attributes[ slot ]; + const bytesPerElement = geometryAttribute.array.BYTES_PER_ELEMENT; + const bufferAttribute = this._getBufferAttribute( geometryAttribute ); + + let vertexBufferLayout = vertexBuffers.get( bufferAttribute ); + + if ( vertexBufferLayout === undefined ) { + + let arrayStride, stepMode; + + if ( geometryAttribute.isInterleavedBufferAttribute === true ) { + + arrayStride = geometryAttribute.data.stride * bytesPerElement; + stepMode = geometryAttribute.data.isInstancedInterleavedBuffer ? GPUInputStepMode.Instance : GPUInputStepMode.Vertex; + + } else { + + arrayStride = geometryAttribute.itemSize * bytesPerElement; + stepMode = geometryAttribute.isInstancedBufferAttribute ? GPUInputStepMode.Instance : GPUInputStepMode.Vertex; + + } + + // patch for INT16 and UINT16 + if ( geometryAttribute.normalized === false && ( geometryAttribute.array.constructor === Int16Array || geometryAttribute.array.constructor === Uint16Array ) ) { + + arrayStride = 4; + + } + + vertexBufferLayout = { + arrayStride, + attributes: [], + stepMode + }; + + vertexBuffers.set( bufferAttribute, vertexBufferLayout ); + + } + + const format = this._getVertexFormat( geometryAttribute ); + const offset = ( geometryAttribute.isInterleavedBufferAttribute === true ) ? geometryAttribute.offset * bytesPerElement : 0; + + vertexBufferLayout.attributes.push( { + shaderLocation: slot, + offset, + format + } ); + + } + + return Array.from( vertexBuffers.values() ); + + } + + /** + * Destroys the GPU buffer of the given buffer attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute. + */ + destroyAttribute( attribute ) { + + const backend = this.backend; + const data = backend.get( this._getBufferAttribute( attribute ) ); + + data.buffer.destroy(); + + backend.delete( attribute ); + + } + + /** + * This method performs a readback operation by moving buffer data from + * a storage buffer attribute from the GPU to the CPU. + * + * @async + * @param {StorageBufferAttribute} attribute - The storage buffer attribute. + * @return {Promise} A promise that resolves with the buffer data when the data are ready. + */ + async getArrayBufferAsync( attribute ) { + + const backend = this.backend; + const device = backend.device; + + const data = backend.get( this._getBufferAttribute( attribute ) ); + const bufferGPU = data.buffer; + const size = bufferGPU.size; + + const readBufferGPU = device.createBuffer( { + label: `${ attribute.name }_readback`, + size, + usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ + } ); + + const cmdEncoder = device.createCommandEncoder( { + label: `readback_encoder_${ attribute.name }` + } ); + + cmdEncoder.copyBufferToBuffer( + bufferGPU, + 0, + readBufferGPU, + 0, + size + ); + + const gpuCommands = cmdEncoder.finish(); + device.queue.submit( [ gpuCommands ] ); + + await readBufferGPU.mapAsync( GPUMapMode.READ ); + + const arrayBuffer = readBufferGPU.getMappedRange(); + + const dstBuffer = new attribute.array.constructor( arrayBuffer.slice( 0 ) ); + + readBufferGPU.unmap(); + + return dstBuffer.buffer; + + } + + /** + * Returns the vertex format of the given buffer attribute. + * + * @private + * @param {BufferAttribute} geometryAttribute - The buffer attribute. + * @return {string|undefined} The vertex format (e.g. 'float32x3'). + */ + _getVertexFormat( geometryAttribute ) { + + const { itemSize, normalized } = geometryAttribute; + const ArrayType = geometryAttribute.array.constructor; + const AttributeType = geometryAttribute.constructor; + + let format; + + if ( itemSize === 1 ) { + + format = typeArraysToVertexFormatPrefixForItemSize1.get( ArrayType ); + + } else { + + const prefixOptions = typedAttributeToVertexFormatPrefix.get( AttributeType ) || typedArraysToVertexFormatPrefix.get( ArrayType ); + const prefix = prefixOptions[ normalized ? 1 : 0 ]; + + if ( prefix ) { + + const bytesPerUnit = ArrayType.BYTES_PER_ELEMENT * itemSize; + const paddedBytesPerUnit = Math.floor( ( bytesPerUnit + 3 ) / 4 ) * 4; + const paddedItemSize = paddedBytesPerUnit / ArrayType.BYTES_PER_ELEMENT; + + if ( paddedItemSize % 1 ) { + + throw new Error( 'THREE.WebGPUAttributeUtils: Bad vertex format item size.' ); + + } + + format = `${prefix}x${paddedItemSize}`; + + } + + } + + if ( ! format ) { + + console.error( 'THREE.WebGPUAttributeUtils: Vertex format not supported yet.' ); + + } + + return format; + + } + + /** + * Returns `true` if the given array is a typed array. + * + * @private + * @param {any} array - The array. + * @return {boolean} Whether the given array is a typed array or not. + */ + _isTypedArray( array ) { + + return ArrayBuffer.isView( array ) && ! ( array instanceof DataView ); + + } + + /** + * Utility method for handling interleaved buffer attributes correctly. + * To process them, their `InterleavedBuffer` is returned. + * + * @private + * @param {BufferAttribute} attribute - The attribute. + * @return {BufferAttribute|InterleavedBuffer} + */ + _getBufferAttribute( attribute ) { + + if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; + + return attribute; + + } + +} + +/** + * A WebGPU backend utility module for managing bindings. + * + * When reading the documentation it's helpful to keep in mind that + * all class definitions starting with 'GPU*' are modules from the + * WebGPU API. So for example `BindGroup` is a class from the engine + * whereas `GPUBindGroup` is a class from WebGPU. + * + * @private + */ +class WebGPUBindingUtils { + + /** + * Constructs a new utility object. + * + * @param {WebGPUBackend} backend - The WebGPU backend. + */ + constructor( backend ) { + + /** + * A reference to the WebGPU backend. + * + * @type {WebGPUBackend} + */ + this.backend = backend; + + /** + * A cache for managing bind group layouts. + * + * @type {WeakMap,GPUBindGroupLayout>} + */ + this.bindGroupLayoutCache = new WeakMap(); + + } + + /** + * Creates a GPU bind group layout for the given bind group. + * + * @param {BindGroup} bindGroup - The bind group. + * @return {GPUBindGroupLayout} The GPU bind group layout. + */ + createBindingsLayout( bindGroup ) { + + const backend = this.backend; + const device = backend.device; + + const entries = []; + + let index = 0; + + for ( const binding of bindGroup.bindings ) { + + const bindingGPU = { + binding: index ++, + visibility: binding.visibility + }; + + if ( binding.isUniformBuffer || binding.isStorageBuffer ) { + + const buffer = {}; // GPUBufferBindingLayout + + if ( binding.isStorageBuffer ) { + + if ( binding.visibility & 4 ) { + + // compute + + if ( binding.access === NodeAccess.READ_WRITE || binding.access === NodeAccess.WRITE_ONLY ) { + + buffer.type = GPUBufferBindingType.Storage; + + } else { + + buffer.type = GPUBufferBindingType.ReadOnlyStorage; + + } + + } else { + + buffer.type = GPUBufferBindingType.ReadOnlyStorage; + + } + + } + + bindingGPU.buffer = buffer; + + } else if ( binding.isSampler ) { + + const sampler = {}; // GPUSamplerBindingLayout + + if ( binding.texture.isDepthTexture ) { + + if ( binding.texture.compareFunction !== null ) { + + sampler.type = GPUSamplerBindingType.Comparison; + + } else if ( backend.compatibilityMode ) { + + sampler.type = GPUSamplerBindingType.NonFiltering; + + } + + } + + bindingGPU.sampler = sampler; + + } else if ( binding.isSampledTexture && binding.texture.isVideoTexture ) { + + bindingGPU.externalTexture = {}; // GPUExternalTextureBindingLayout + + } else if ( binding.isSampledTexture && binding.store ) { + + const storageTexture = {}; // GPUStorageTextureBindingLayout + storageTexture.format = this.backend.get( binding.texture ).texture.format; + + const access = binding.access; + + if ( access === NodeAccess.READ_WRITE ) { + + storageTexture.access = GPUStorageTextureAccess.ReadWrite; + + } else if ( access === NodeAccess.WRITE_ONLY ) { + + storageTexture.access = GPUStorageTextureAccess.WriteOnly; + + } else { + + storageTexture.access = GPUStorageTextureAccess.ReadOnly; + + } + + if ( binding.texture.isArrayTexture ) { + + storageTexture.viewDimension = GPUTextureViewDimension.TwoDArray; + + } else if ( binding.texture.is3DTexture ) { + + storageTexture.viewDimension = GPUTextureViewDimension.ThreeD; + + } + + bindingGPU.storageTexture = storageTexture; + + } else if ( binding.isSampledTexture ) { + + const texture = {}; // GPUTextureBindingLayout + + const { primarySamples } = backend.utils.getTextureSampleData( binding.texture ); + + if ( primarySamples > 1 ) { + + texture.multisampled = true; + + if ( ! binding.texture.isDepthTexture ) { + + texture.sampleType = GPUTextureSampleType.UnfilterableFloat; + + } + + } + + if ( binding.texture.isDepthTexture ) { + + if ( backend.compatibilityMode && binding.texture.compareFunction === null ) { + + texture.sampleType = GPUTextureSampleType.UnfilterableFloat; + + } else { + + texture.sampleType = GPUTextureSampleType.Depth; + + } + + } else if ( binding.texture.isDataTexture || binding.texture.isDataArrayTexture || binding.texture.isData3DTexture ) { + + const type = binding.texture.type; + + if ( type === IntType ) { + + texture.sampleType = GPUTextureSampleType.SInt; + + } else if ( type === UnsignedIntType ) { + + texture.sampleType = GPUTextureSampleType.UInt; + + } else if ( type === FloatType ) { + + if ( this.backend.hasFeature( 'float32-filterable' ) ) { + + texture.sampleType = GPUTextureSampleType.Float; + + } else { + + texture.sampleType = GPUTextureSampleType.UnfilterableFloat; + + } + + } + + } + + if ( binding.isSampledCubeTexture ) { + + texture.viewDimension = GPUTextureViewDimension.Cube; + + } else if ( binding.texture.isArrayTexture || binding.texture.isDataArrayTexture || binding.texture.isCompressedArrayTexture ) { + + texture.viewDimension = GPUTextureViewDimension.TwoDArray; + + } else if ( binding.isSampledTexture3D ) { + + texture.viewDimension = GPUTextureViewDimension.ThreeD; + + } + + bindingGPU.texture = texture; + + } else { + + console.error( `WebGPUBindingUtils: Unsupported binding "${ binding }".` ); + + } + + entries.push( bindingGPU ); + + } + + return device.createBindGroupLayout( { entries } ); + + } + + /** + * Creates bindings from the given bind group definition. + * + * @param {BindGroup} bindGroup - The bind group. + * @param {Array} bindings - Array of bind groups. + * @param {number} cacheIndex - The cache index. + * @param {number} version - The version. + */ + createBindings( bindGroup, bindings, cacheIndex, version = 0 ) { + + const { backend, bindGroupLayoutCache } = this; + const bindingsData = backend.get( bindGroup ); + + // setup (static) binding layout and (dynamic) binding group + + let bindLayoutGPU = bindGroupLayoutCache.get( bindGroup.bindingsReference ); + + if ( bindLayoutGPU === undefined ) { + + bindLayoutGPU = this.createBindingsLayout( bindGroup ); + bindGroupLayoutCache.set( bindGroup.bindingsReference, bindLayoutGPU ); + + } + + let bindGroupGPU; + + if ( cacheIndex > 0 ) { + + if ( bindingsData.groups === undefined ) { + + bindingsData.groups = []; + bindingsData.versions = []; + + } + + if ( bindingsData.versions[ cacheIndex ] === version ) { + + bindGroupGPU = bindingsData.groups[ cacheIndex ]; + + } + + } + + if ( bindGroupGPU === undefined ) { + + bindGroupGPU = this.createBindGroup( bindGroup, bindLayoutGPU ); + + if ( cacheIndex > 0 ) { + + bindingsData.groups[ cacheIndex ] = bindGroupGPU; + bindingsData.versions[ cacheIndex ] = version; + + } + + } + + bindingsData.group = bindGroupGPU; + bindingsData.layout = bindLayoutGPU; + + } + + /** + * Updates a buffer binding. + * + * @param {Buffer} binding - The buffer binding to update. + */ + updateBinding( binding ) { + + const backend = this.backend; + const device = backend.device; + + const buffer = binding.buffer; + const bufferGPU = backend.get( binding ).buffer; + + device.queue.writeBuffer( bufferGPU, 0, buffer, 0 ); + + } + + /** + * Creates a GPU bind group for the camera index. + * + * @param {Uint32Array} data - The index data. + * @param {GPUBindGroupLayout} layout - The GPU bind group layout. + * @return {GPUBindGroup} The GPU bind group. + */ + createBindGroupIndex( data, layout ) { + + const backend = this.backend; + const device = backend.device; + + const usage = GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST; + const index = data[ 0 ]; + + const buffer = device.createBuffer( { + label: 'bindingCameraIndex_' + index, + size: 16, // uint(4) * 4 + usage: usage + } ); + + device.queue.writeBuffer( buffer, 0, data, 0 ); + + const entries = [ { binding: 0, resource: { buffer } } ]; + + return device.createBindGroup( { + label: 'bindGroupCameraIndex_' + index, + layout, + entries + } ); + + } + + /** + * Creates a GPU bind group for the given bind group and GPU layout. + * + * @param {BindGroup} bindGroup - The bind group. + * @param {GPUBindGroupLayout} layoutGPU - The GPU bind group layout. + * @return {GPUBindGroup} The GPU bind group. + */ + createBindGroup( bindGroup, layoutGPU ) { + + const backend = this.backend; + const device = backend.device; + + let bindingPoint = 0; + const entriesGPU = []; + + for ( const binding of bindGroup.bindings ) { + + if ( binding.isUniformBuffer ) { + + const bindingData = backend.get( binding ); + + if ( bindingData.buffer === undefined ) { + + const byteLength = binding.byteLength; + + const usage = GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST; + + const bufferGPU = device.createBuffer( { + label: 'bindingBuffer_' + binding.name, + size: byteLength, + usage: usage + } ); + + bindingData.buffer = bufferGPU; + + } + + entriesGPU.push( { binding: bindingPoint, resource: { buffer: bindingData.buffer } } ); + + } else if ( binding.isStorageBuffer ) { + + const bindingData = backend.get( binding ); + + if ( bindingData.buffer === undefined ) { + + const attribute = binding.attribute; + //const usage = GPUBufferUsage.STORAGE | GPUBufferUsage.VERTEX | /*GPUBufferUsage.COPY_SRC |*/ GPUBufferUsage.COPY_DST; + + //backend.attributeUtils.createAttribute( attribute, usage ); // @TODO: Move it to universal renderer + + bindingData.buffer = backend.get( attribute ).buffer; + + } + + entriesGPU.push( { binding: bindingPoint, resource: { buffer: bindingData.buffer } } ); + + } else if ( binding.isSampler ) { + + const textureGPU = backend.get( binding.texture ); + + entriesGPU.push( { binding: bindingPoint, resource: textureGPU.sampler } ); + + } else if ( binding.isSampledTexture ) { + + const textureData = backend.get( binding.texture ); + + let resourceGPU; + + if ( textureData.externalTexture !== undefined ) { + + resourceGPU = device.importExternalTexture( { source: textureData.externalTexture } ); + + } else { + + const mipLevelCount = binding.store ? 1 : textureData.texture.mipLevelCount; + const propertyName = `view-${ textureData.texture.width }-${ textureData.texture.height }-${ mipLevelCount }`; + + resourceGPU = textureData[ propertyName ]; + + if ( resourceGPU === undefined ) { + + const aspectGPU = GPUTextureAspect.All; + + let dimensionViewGPU; + + if ( binding.isSampledCubeTexture ) { + + dimensionViewGPU = GPUTextureViewDimension.Cube; + + } else if ( binding.isSampledTexture3D ) { + + dimensionViewGPU = GPUTextureViewDimension.ThreeD; + + } else if ( binding.texture.isArrayTexture || binding.texture.isDataArrayTexture || binding.texture.isCompressedArrayTexture ) { + + dimensionViewGPU = GPUTextureViewDimension.TwoDArray; + + } else { + + dimensionViewGPU = GPUTextureViewDimension.TwoD; + + } + + resourceGPU = textureData[ propertyName ] = textureData.texture.createView( { aspect: aspectGPU, dimension: dimensionViewGPU, mipLevelCount } ); + + } + + } + + entriesGPU.push( { binding: bindingPoint, resource: resourceGPU } ); + + } + + bindingPoint ++; + + } + + return device.createBindGroup( { + label: 'bindGroup_' + bindGroup.name, + layout: layoutGPU, + entries: entriesGPU + } ); + + } + +} + +/** + * A WebGPU backend utility module for managing pipelines. + * + * @private + */ +class WebGPUPipelineUtils { + + /** + * Constructs a new utility object. + * + * @param {WebGPUBackend} backend - The WebGPU backend. + */ + constructor( backend ) { + + /** + * A reference to the WebGPU backend. + * + * @type {WebGPUBackend} + */ + this.backend = backend; + + /** + * A Weak Map that tracks the active pipeline for render or compute passes. + * + * @private + * @type {WeakMap<(GPURenderPassEncoder|GPUComputePassEncoder),(GPURenderPipeline|GPUComputePipeline)>} + */ + this._activePipelines = new WeakMap(); + + } + + /** + * Sets the given pipeline for the given pass. The method makes sure to only set the + * pipeline when necessary. + * + * @param {(GPURenderPassEncoder|GPUComputePassEncoder)} pass - The pass encoder. + * @param {(GPURenderPipeline|GPUComputePipeline)} pipeline - The pipeline. + */ + setPipeline( pass, pipeline ) { + + const currentPipeline = this._activePipelines.get( pass ); + + if ( currentPipeline !== pipeline ) { + + pass.setPipeline( pipeline ); + + this._activePipelines.set( pass, pipeline ); + + } + + } + + /** + * Returns the sample count derived from the given render context. + * + * @private + * @param {RenderContext} renderContext - The render context. + * @return {number} The sample count. + */ + _getSampleCount( renderContext ) { + + return this.backend.utils.getSampleCountRenderContext( renderContext ); + + } + + /** + * Creates a render pipeline for the given render object. + * + * @param {RenderObject} renderObject - The render object. + * @param {Array} promises - An array of compilation promises which are used in `compileAsync()`. + */ + createRenderPipeline( renderObject, promises ) { + + const { object, material, geometry, pipeline } = renderObject; + const { vertexProgram, fragmentProgram } = pipeline; + + const backend = this.backend; + const device = backend.device; + const utils = backend.utils; + + const pipelineData = backend.get( pipeline ); + + // bind group layouts + + const bindGroupLayouts = []; + + for ( const bindGroup of renderObject.getBindings() ) { + + const bindingsData = backend.get( bindGroup ); + + bindGroupLayouts.push( bindingsData.layout ); + + } + + // vertex buffers + + const vertexBuffers = backend.attributeUtils.createShaderVertexBuffers( renderObject ); + + // blending + + let blending; + + if ( material.blending !== NoBlending && ( material.blending !== NormalBlending || material.transparent !== false ) ) { + + blending = this._getBlending( material ); + + } + + // stencil + + let stencilFront = {}; + + if ( material.stencilWrite === true ) { + + stencilFront = { + compare: this._getStencilCompare( material ), + failOp: this._getStencilOperation( material.stencilFail ), + depthFailOp: this._getStencilOperation( material.stencilZFail ), + passOp: this._getStencilOperation( material.stencilZPass ) + }; + + } + + const colorWriteMask = this._getColorWriteMask( material ); + + const targets = []; + + if ( renderObject.context.textures !== null ) { + + const textures = renderObject.context.textures; + + for ( let i = 0; i < textures.length; i ++ ) { + + const colorFormat = utils.getTextureFormatGPU( textures[ i ] ); + + targets.push( { + format: colorFormat, + blend: blending, + writeMask: colorWriteMask + } ); + + } + + } else { + + const colorFormat = utils.getCurrentColorFormat( renderObject.context ); + + targets.push( { + format: colorFormat, + blend: blending, + writeMask: colorWriteMask + } ); + + } + + const vertexModule = backend.get( vertexProgram ).module; + const fragmentModule = backend.get( fragmentProgram ).module; + + const primitiveState = this._getPrimitiveState( object, geometry, material ); + const depthCompare = this._getDepthCompare( material ); + const depthStencilFormat = utils.getCurrentDepthStencilFormat( renderObject.context ); + + const sampleCount = this._getSampleCount( renderObject.context ); + + const pipelineDescriptor = { + label: `renderPipeline_${ material.name || material.type }_${ material.id }`, + vertex: Object.assign( {}, vertexModule, { buffers: vertexBuffers } ), + fragment: Object.assign( {}, fragmentModule, { targets } ), + primitive: primitiveState, + multisample: { + count: sampleCount, + alphaToCoverageEnabled: material.alphaToCoverage && sampleCount > 1 + }, + layout: device.createPipelineLayout( { + bindGroupLayouts + } ) + }; + + + const depthStencil = {}; + const renderDepth = renderObject.context.depth; + const renderStencil = renderObject.context.stencil; + + if ( renderDepth === true || renderStencil === true ) { + + if ( renderDepth === true ) { + + depthStencil.format = depthStencilFormat; + depthStencil.depthWriteEnabled = material.depthWrite; + depthStencil.depthCompare = depthCompare; + + } + + if ( renderStencil === true ) { + + depthStencil.stencilFront = stencilFront; + depthStencil.stencilBack = {}; // three.js does not provide an API to configure the back function (gl.stencilFuncSeparate() was never used) + depthStencil.stencilReadMask = material.stencilFuncMask; + depthStencil.stencilWriteMask = material.stencilWriteMask; + + } + + if ( material.polygonOffset === true ) { + + depthStencil.depthBias = material.polygonOffsetUnits; + depthStencil.depthBiasSlopeScale = material.polygonOffsetFactor; + depthStencil.depthBiasClamp = 0; // three.js does not provide an API to configure this value + + } + + pipelineDescriptor.depthStencil = depthStencil; + + } + + + if ( promises === null ) { + + pipelineData.pipeline = device.createRenderPipeline( pipelineDescriptor ); + + } else { + + const p = new Promise( ( resolve /*, reject*/ ) => { + + device.createRenderPipelineAsync( pipelineDescriptor ).then( pipeline => { + + pipelineData.pipeline = pipeline; + resolve(); + + } ); + + } ); + + promises.push( p ); + + } + + } + + /** + * Creates GPU render bundle encoder for the given render context. + * + * @param {RenderContext} renderContext - The render context. + * @param {?string} [label='renderBundleEncoder'] - The label. + * @return {GPURenderBundleEncoder} The GPU render bundle encoder. + */ + createBundleEncoder( renderContext, label = 'renderBundleEncoder' ) { + + const backend = this.backend; + const { utils, device } = backend; + + const depthStencilFormat = utils.getCurrentDepthStencilFormat( renderContext ); + const colorFormat = utils.getCurrentColorFormat( renderContext ); + const sampleCount = this._getSampleCount( renderContext ); + + const descriptor = { + label: label, + colorFormats: [ colorFormat ], + depthStencilFormat, + sampleCount + }; + + return device.createRenderBundleEncoder( descriptor ); + + } + + /** + * Creates a compute pipeline for the given compute node. + * + * @param {ComputePipeline} pipeline - The compute pipeline. + * @param {Array} bindings - The bindings. + */ + createComputePipeline( pipeline, bindings ) { + + const backend = this.backend; + const device = backend.device; + + const computeProgram = backend.get( pipeline.computeProgram ).module; + + const pipelineGPU = backend.get( pipeline ); + + // bind group layouts + + const bindGroupLayouts = []; + + for ( const bindingsGroup of bindings ) { + + const bindingsData = backend.get( bindingsGroup ); + + bindGroupLayouts.push( bindingsData.layout ); + + } + + pipelineGPU.pipeline = device.createComputePipeline( { + compute: computeProgram, + layout: device.createPipelineLayout( { + bindGroupLayouts + } ) + } ); + + } + + /** + * Returns the blending state as a descriptor object required + * for the pipeline creation. + * + * @private + * @param {Material} material - The material. + * @return {Object} The blending state. + */ + _getBlending( material ) { + + let color, alpha; + + const blending = material.blending; + const blendSrc = material.blendSrc; + const blendDst = material.blendDst; + const blendEquation = material.blendEquation; + + + if ( blending === CustomBlending ) { + + const blendSrcAlpha = material.blendSrcAlpha !== null ? material.blendSrcAlpha : blendSrc; + const blendDstAlpha = material.blendDstAlpha !== null ? material.blendDstAlpha : blendDst; + const blendEquationAlpha = material.blendEquationAlpha !== null ? material.blendEquationAlpha : blendEquation; + + color = { + srcFactor: this._getBlendFactor( blendSrc ), + dstFactor: this._getBlendFactor( blendDst ), + operation: this._getBlendOperation( blendEquation ) + }; + + alpha = { + srcFactor: this._getBlendFactor( blendSrcAlpha ), + dstFactor: this._getBlendFactor( blendDstAlpha ), + operation: this._getBlendOperation( blendEquationAlpha ) + }; + + } else { + + const premultipliedAlpha = material.premultipliedAlpha; + + const setBlend = ( srcRGB, dstRGB, srcAlpha, dstAlpha ) => { + + color = { + srcFactor: srcRGB, + dstFactor: dstRGB, + operation: GPUBlendOperation.Add + }; + + alpha = { + srcFactor: srcAlpha, + dstFactor: dstAlpha, + operation: GPUBlendOperation.Add + }; + + }; + + if ( premultipliedAlpha ) { + + switch ( blending ) { + + case NormalBlending: + setBlend( GPUBlendFactor.One, GPUBlendFactor.OneMinusSrcAlpha, GPUBlendFactor.One, GPUBlendFactor.OneMinusSrcAlpha ); + break; + + case AdditiveBlending: + setBlend( GPUBlendFactor.One, GPUBlendFactor.One, GPUBlendFactor.One, GPUBlendFactor.One ); + break; + + case SubtractiveBlending: + setBlend( GPUBlendFactor.Zero, GPUBlendFactor.OneMinusSrc, GPUBlendFactor.Zero, GPUBlendFactor.One ); + break; + + case MultiplyBlending: + setBlend( GPUBlendFactor.Dst, GPUBlendFactor.OneMinusSrcAlpha, GPUBlendFactor.Zero, GPUBlendFactor.One ); + break; + + } + + } else { + + switch ( blending ) { + + case NormalBlending: + setBlend( GPUBlendFactor.SrcAlpha, GPUBlendFactor.OneMinusSrcAlpha, GPUBlendFactor.One, GPUBlendFactor.OneMinusSrcAlpha ); + break; + + case AdditiveBlending: + setBlend( GPUBlendFactor.SrcAlpha, GPUBlendFactor.One, GPUBlendFactor.One, GPUBlendFactor.One ); + break; + + case SubtractiveBlending: + console.error( 'THREE.WebGPURenderer: SubtractiveBlending requires material.premultipliedAlpha = true' ); + break; + + case MultiplyBlending: + console.error( 'THREE.WebGPURenderer: MultiplyBlending requires material.premultipliedAlpha = true' ); + break; + + } + + } + + } + + if ( color !== undefined && alpha !== undefined ) { + + return { color, alpha }; + + } else { + + console.error( 'THREE.WebGPURenderer: Invalid blending: ', blending ); + + } + + } + /** + * Returns the GPU blend factor which is required for the pipeline creation. + * + * @private + * @param {number} blend - The blend factor as a three.js constant. + * @return {string} The GPU blend factor. + */ + _getBlendFactor( blend ) { + + let blendFactor; + + switch ( blend ) { + + case ZeroFactor: + blendFactor = GPUBlendFactor.Zero; + break; + + case OneFactor: + blendFactor = GPUBlendFactor.One; + break; + + case SrcColorFactor: + blendFactor = GPUBlendFactor.Src; + break; + + case OneMinusSrcColorFactor: + blendFactor = GPUBlendFactor.OneMinusSrc; + break; + + case SrcAlphaFactor: + blendFactor = GPUBlendFactor.SrcAlpha; + break; + + case OneMinusSrcAlphaFactor: + blendFactor = GPUBlendFactor.OneMinusSrcAlpha; + break; + + case DstColorFactor: + blendFactor = GPUBlendFactor.Dst; + break; + + case OneMinusDstColorFactor: + blendFactor = GPUBlendFactor.OneMinusDst; + break; + + case DstAlphaFactor: + blendFactor = GPUBlendFactor.DstAlpha; + break; + + case OneMinusDstAlphaFactor: + blendFactor = GPUBlendFactor.OneMinusDstAlpha; + break; + + case SrcAlphaSaturateFactor: + blendFactor = GPUBlendFactor.SrcAlphaSaturated; + break; + + case BlendColorFactor: + blendFactor = GPUBlendFactor.Constant; + break; + + case OneMinusBlendColorFactor: + blendFactor = GPUBlendFactor.OneMinusConstant; + break; + + default: + console.error( 'THREE.WebGPURenderer: Blend factor not supported.', blend ); + + } + + return blendFactor; + + } + + /** + * Returns the GPU stencil compare function which is required for the pipeline creation. + * + * @private + * @param {Material} material - The material. + * @return {string} The GPU stencil compare function. + */ + _getStencilCompare( material ) { + + let stencilCompare; + + const stencilFunc = material.stencilFunc; + + switch ( stencilFunc ) { + + case NeverStencilFunc: + stencilCompare = GPUCompareFunction.Never; + break; + + case AlwaysStencilFunc: + stencilCompare = GPUCompareFunction.Always; + break; + + case LessStencilFunc: + stencilCompare = GPUCompareFunction.Less; + break; + + case LessEqualStencilFunc: + stencilCompare = GPUCompareFunction.LessEqual; + break; + + case EqualStencilFunc: + stencilCompare = GPUCompareFunction.Equal; + break; + + case GreaterEqualStencilFunc: + stencilCompare = GPUCompareFunction.GreaterEqual; + break; + + case GreaterStencilFunc: + stencilCompare = GPUCompareFunction.Greater; + break; + + case NotEqualStencilFunc: + stencilCompare = GPUCompareFunction.NotEqual; + break; + + default: + console.error( 'THREE.WebGPURenderer: Invalid stencil function.', stencilFunc ); + + } + + return stencilCompare; + + } + + /** + * Returns the GPU stencil operation which is required for the pipeline creation. + * + * @private + * @param {number} op - A three.js constant defining the stencil operation. + * @return {string} The GPU stencil operation. + */ + _getStencilOperation( op ) { + + let stencilOperation; + + switch ( op ) { + + case KeepStencilOp: + stencilOperation = GPUStencilOperation.Keep; + break; + + case ZeroStencilOp: + stencilOperation = GPUStencilOperation.Zero; + break; + + case ReplaceStencilOp: + stencilOperation = GPUStencilOperation.Replace; + break; + + case InvertStencilOp: + stencilOperation = GPUStencilOperation.Invert; + break; + + case IncrementStencilOp: + stencilOperation = GPUStencilOperation.IncrementClamp; + break; + + case DecrementStencilOp: + stencilOperation = GPUStencilOperation.DecrementClamp; + break; + + case IncrementWrapStencilOp: + stencilOperation = GPUStencilOperation.IncrementWrap; + break; + + case DecrementWrapStencilOp: + stencilOperation = GPUStencilOperation.DecrementWrap; + break; + + default: + console.error( 'THREE.WebGPURenderer: Invalid stencil operation.', stencilOperation ); + + } + + return stencilOperation; + + } + + /** + * Returns the GPU blend operation which is required for the pipeline creation. + * + * @private + * @param {number} blendEquation - A three.js constant defining the blend equation. + * @return {string} The GPU blend operation. + */ + _getBlendOperation( blendEquation ) { + + let blendOperation; + + switch ( blendEquation ) { + + case AddEquation: + blendOperation = GPUBlendOperation.Add; + break; + + case SubtractEquation: + blendOperation = GPUBlendOperation.Subtract; + break; + + case ReverseSubtractEquation: + blendOperation = GPUBlendOperation.ReverseSubtract; + break; + + case MinEquation: + blendOperation = GPUBlendOperation.Min; + break; + + case MaxEquation: + blendOperation = GPUBlendOperation.Max; + break; + + default: + console.error( 'THREE.WebGPUPipelineUtils: Blend equation not supported.', blendEquation ); + + } + + return blendOperation; + + } + + /** + * Returns the primitive state as a descriptor object required + * for the pipeline creation. + * + * @private + * @param {Object3D} object - The 3D object. + * @param {BufferGeometry} geometry - The geometry. + * @param {Material} material - The material. + * @return {Object} The primitive state. + */ + _getPrimitiveState( object, geometry, material ) { + + const descriptor = {}; + const utils = this.backend.utils; + + descriptor.topology = utils.getPrimitiveTopology( object, material ); + + if ( geometry.index !== null && object.isLine === true && object.isLineSegments !== true ) { + + descriptor.stripIndexFormat = ( geometry.index.array instanceof Uint16Array ) ? GPUIndexFormat.Uint16 : GPUIndexFormat.Uint32; + + } + + switch ( material.side ) { + + case FrontSide: + descriptor.frontFace = GPUFrontFace.CCW; + descriptor.cullMode = GPUCullMode.Back; + break; + + case BackSide: + descriptor.frontFace = GPUFrontFace.CCW; + descriptor.cullMode = GPUCullMode.Front; + break; + + case DoubleSide: + descriptor.frontFace = GPUFrontFace.CCW; + descriptor.cullMode = GPUCullMode.None; + break; + + default: + console.error( 'THREE.WebGPUPipelineUtils: Unknown material.side value.', material.side ); + break; + + } + + return descriptor; + + } + + /** + * Returns the GPU color write mask which is required for the pipeline creation. + * + * @private + * @param {Material} material - The material. + * @return {string} The GPU color write mask. + */ + _getColorWriteMask( material ) { + + return ( material.colorWrite === true ) ? GPUColorWriteFlags.All : GPUColorWriteFlags.None; + + } + + /** + * Returns the GPU depth compare function which is required for the pipeline creation. + * + * @private + * @param {Material} material - The material. + * @return {string} The GPU depth compare function. + */ + _getDepthCompare( material ) { + + let depthCompare; + + if ( material.depthTest === false ) { + + depthCompare = GPUCompareFunction.Always; + + } else { + + const depthFunc = material.depthFunc; + + switch ( depthFunc ) { + + case NeverDepth: + depthCompare = GPUCompareFunction.Never; + break; + + case AlwaysDepth: + depthCompare = GPUCompareFunction.Always; + break; + + case LessDepth: + depthCompare = GPUCompareFunction.Less; + break; + + case LessEqualDepth: + depthCompare = GPUCompareFunction.LessEqual; + break; + + case EqualDepth: + depthCompare = GPUCompareFunction.Equal; + break; + + case GreaterEqualDepth: + depthCompare = GPUCompareFunction.GreaterEqual; + break; + + case GreaterDepth: + depthCompare = GPUCompareFunction.Greater; + break; + + case NotEqualDepth: + depthCompare = GPUCompareFunction.NotEqual; + break; + + default: + console.error( 'THREE.WebGPUPipelineUtils: Invalid depth function.', depthFunc ); + + } + + } + + return depthCompare; + + } + +} + +/** + * Manages a pool of WebGPU timestamp queries for performance measurement. + * Extends the base TimestampQueryPool to provide WebGPU-specific implementation. + * + * @augments TimestampQueryPool + */ +class WebGPUTimestampQueryPool extends TimestampQueryPool { + + /** + * Creates a new WebGPU timestamp query pool. + * + * @param {GPUDevice} device - The WebGPU device to create queries on. + * @param {string} type - The type identifier for this query pool. + * @param {number} [maxQueries=2048] - Maximum number of queries this pool can hold. + */ + constructor( device, type, maxQueries = 2048 ) { + + super( maxQueries ); + this.device = device; + this.type = type; + + this.querySet = this.device.createQuerySet( { + type: 'timestamp', + count: this.maxQueries, + label: `queryset_global_timestamp_${type}` + } ); + + const bufferSize = this.maxQueries * 8; + this.resolveBuffer = this.device.createBuffer( { + label: `buffer_timestamp_resolve_${type}`, + size: bufferSize, + usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC + } ); + + this.resultBuffer = this.device.createBuffer( { + label: `buffer_timestamp_result_${type}`, + size: bufferSize, + usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ + } ); + + } + + /** + * Allocates a pair of queries for a given render context. + * + * @param {Object} renderContext - The render context to allocate queries for. + * @returns {?number} The base offset for the allocated queries, or null if allocation failed. + */ + allocateQueriesForContext( renderContext ) { + + if ( ! this.trackTimestamp || this.isDisposed ) return null; + + if ( this.currentQueryIndex + 2 > this.maxQueries ) { + + warnOnce( `WebGPUTimestampQueryPool [${ this.type }]: Maximum number of queries exceeded, when using trackTimestamp it is necessary to resolves the queries via renderer.resolveTimestampsAsync( THREE.TimestampQuery.${ this.type.toUpperCase() } ).` ); + return null; + + } + + const baseOffset = this.currentQueryIndex; + this.currentQueryIndex += 2; + + this.queryOffsets.set( renderContext.id, baseOffset ); + return baseOffset; + + } + + /** + * Asynchronously resolves all pending queries and returns the total duration. + * If there's already a pending resolve operation, returns that promise instead. + * + * @async + * @returns {Promise} The total duration in milliseconds, or the last valid value if resolution fails. + */ + async resolveQueriesAsync() { + + if ( ! this.trackTimestamp || this.currentQueryIndex === 0 || this.isDisposed ) { + + return this.lastValue; + + } + + if ( this.pendingResolve ) { + + return this.pendingResolve; + + } + + this.pendingResolve = this._resolveQueries(); + + try { + + const result = await this.pendingResolve; + return result; + + } finally { + + this.pendingResolve = null; + + } + + } + + /** + * Internal method to resolve queries and calculate total duration. + * + * @async + * @private + * @returns {Promise} The total duration in milliseconds. + */ + async _resolveQueries() { + + if ( this.isDisposed ) { + + return this.lastValue; + + } + + try { + + if ( this.resultBuffer.mapState !== 'unmapped' ) { + + return this.lastValue; + + } + + const currentOffsets = new Map( this.queryOffsets ); + const queryCount = this.currentQueryIndex; + const bytesUsed = queryCount * 8; + + // Reset state before GPU work + this.currentQueryIndex = 0; + this.queryOffsets.clear(); + + const commandEncoder = this.device.createCommandEncoder(); + + commandEncoder.resolveQuerySet( + this.querySet, + 0, + queryCount, + this.resolveBuffer, + 0 + ); + + commandEncoder.copyBufferToBuffer( + this.resolveBuffer, + 0, + this.resultBuffer, + 0, + bytesUsed + ); + + const commandBuffer = commandEncoder.finish(); + this.device.queue.submit( [ commandBuffer ] ); + + if ( this.resultBuffer.mapState !== 'unmapped' ) { + + return this.lastValue; + + } + + // Create and track the mapping operation + await this.resultBuffer.mapAsync( GPUMapMode.READ, 0, bytesUsed ); + + if ( this.isDisposed ) { + + if ( this.resultBuffer.mapState === 'mapped' ) { + + this.resultBuffer.unmap(); + + } + + return this.lastValue; + + } + + const times = new BigUint64Array( this.resultBuffer.getMappedRange( 0, bytesUsed ) ); + let totalDuration = 0; + + for ( const [ , baseOffset ] of currentOffsets ) { + + const startTime = times[ baseOffset ]; + const endTime = times[ baseOffset + 1 ]; + const duration = Number( endTime - startTime ) / 1e6; + totalDuration += duration; + + } + + this.resultBuffer.unmap(); + this.lastValue = totalDuration; + + return totalDuration; + + } catch ( error ) { + + console.error( 'Error resolving queries:', error ); + if ( this.resultBuffer.mapState === 'mapped' ) { + + this.resultBuffer.unmap(); + + } + + return this.lastValue; + + } + + } + + /** + * Dispose of the query pool. + * + * @async + * @returns {Promise} A Promise that resolves when the dispose has been executed. + */ + async dispose() { + + if ( this.isDisposed ) { + + return; + + } + + this.isDisposed = true; + + // Wait for pending resolve operation + if ( this.pendingResolve ) { + + try { + + await this.pendingResolve; + + } catch ( error ) { + + console.error( 'Error waiting for pending resolve:', error ); + + } + + } + + // Ensure buffer is unmapped before destroying + if ( this.resultBuffer && this.resultBuffer.mapState === 'mapped' ) { + + try { + + this.resultBuffer.unmap(); + + } catch ( error ) { + + console.error( 'Error unmapping buffer:', error ); + + } + + } + + // Destroy resources + if ( this.querySet ) { + + this.querySet.destroy(); + this.querySet = null; + + } + + if ( this.resolveBuffer ) { + + this.resolveBuffer.destroy(); + this.resolveBuffer = null; + + } + + if ( this.resultBuffer ) { + + this.resultBuffer.destroy(); + this.resultBuffer = null; + + } + + this.queryOffsets.clear(); + this.pendingResolve = null; + + } + +} + +/*// debugger tools +import 'https://greggman.github.io/webgpu-avoid-redundant-state-setting/webgpu-check-redundant-state-setting.js'; +//*/ + + +/** + * A backend implementation targeting WebGPU. + * + * @private + * @augments Backend + */ +class WebGPUBackend extends Backend { + + /** + * WebGPUBackend options. + * + * @typedef {Object} WebGPUBackend~Options + * @property {boolean} [logarithmicDepthBuffer=false] - Whether logarithmic depth buffer is enabled or not. + * @property {boolean} [alpha=true] - Whether the default framebuffer (which represents the final contents of the canvas) should be transparent or opaque. + * @property {boolean} [compatibilityMode=false] - Whether the backend should be in compatibility mode or not. + * @property {boolean} [depth=true] - Whether the default framebuffer should have a depth buffer or not. + * @property {boolean} [stencil=false] - Whether the default framebuffer should have a stencil buffer or not. + * @property {boolean} [antialias=false] - Whether MSAA as the default anti-aliasing should be enabled or not. + * @property {number} [samples=0] - When `antialias` is `true`, `4` samples are used by default. Set this parameter to any other integer value than 0 to overwrite the default. + * @property {boolean} [forceWebGL=false] - If set to `true`, the renderer uses a WebGL 2 backend no matter if WebGPU is supported or not. + * @property {boolean} [trackTimestamp=false] - Whether to track timestamps with a Timestamp Query API or not. + * @property {string} [powerPreference=undefined] - The power preference. + * @property {Object} [requiredLimits=undefined] - Specifies the limits that are required by the device request. The request will fail if the adapter cannot provide these limits. + * @property {GPUDevice} [device=undefined] - If there is an existing GPU device on app level, it can be passed to the renderer as a parameter. + * @property {number} [outputType=undefined] - Texture type for output to canvas. By default, device's preferred format is used; other formats may incur overhead. + */ + + /** + * Constructs a new WebGPU backend. + * + * @param {WebGPUBackend~Options} [parameters] - The configuration parameter. + */ + constructor( parameters = {} ) { + + super( parameters ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isWebGPUBackend = true; + + // some parameters require default values other than "undefined" + this.parameters.alpha = ( parameters.alpha === undefined ) ? true : parameters.alpha; + this.parameters.compatibilityMode = ( parameters.compatibilityMode === undefined ) ? false : parameters.compatibilityMode; + + this.parameters.requiredLimits = ( parameters.requiredLimits === undefined ) ? {} : parameters.requiredLimits; + + /** + * Indicates whether the backend is in compatibility mode or not. + * @type {boolean} + * @default false + */ + this.compatibilityMode = this.parameters.compatibilityMode; + + /** + * A reference to the device. + * + * @type {?GPUDevice} + * @default null + */ + this.device = null; + + /** + * A reference to the context. + * + * @type {?GPUCanvasContext} + * @default null + */ + this.context = null; + + /** + * A reference to the color attachment of the default framebuffer. + * + * @type {?GPUTexture} + * @default null + */ + this.colorBuffer = null; + + /** + * A reference to the default render pass descriptor. + * + * @type {?Object} + * @default null + */ + this.defaultRenderPassdescriptor = null; + + /** + * A reference to a backend module holding common utility functions. + * + * @type {WebGPUUtils} + */ + this.utils = new WebGPUUtils( this ); + + /** + * A reference to a backend module holding shader attribute-related + * utility functions. + * + * @type {WebGPUAttributeUtils} + */ + this.attributeUtils = new WebGPUAttributeUtils( this ); + + /** + * A reference to a backend module holding shader binding-related + * utility functions. + * + * @type {WebGPUBindingUtils} + */ + this.bindingUtils = new WebGPUBindingUtils( this ); + + /** + * A reference to a backend module holding shader pipeline-related + * utility functions. + * + * @type {WebGPUPipelineUtils} + */ + this.pipelineUtils = new WebGPUPipelineUtils( this ); + + /** + * A reference to a backend module holding shader texture-related + * utility functions. + * + * @type {WebGPUTextureUtils} + */ + this.textureUtils = new WebGPUTextureUtils( this ); + + /** + * A map that manages the resolve buffers for occlusion queries. + * + * @type {Map} + */ + this.occludedResolveCache = new Map(); + + } + + /** + * Initializes the backend so it is ready for usage. + * + * @async + * @param {Renderer} renderer - The renderer. + * @return {Promise} A Promise that resolves when the backend has been initialized. + */ + async init( renderer ) { + + await super.init( renderer ); + + // + + const parameters = this.parameters; + + // create the device if it is not passed with parameters + + let device; + + if ( parameters.device === undefined ) { + + const adapterOptions = { + powerPreference: parameters.powerPreference, + featureLevel: parameters.compatibilityMode ? 'compatibility' : undefined + }; + + const adapter = ( typeof navigator !== 'undefined' ) ? await navigator.gpu.requestAdapter( adapterOptions ) : null; + + if ( adapter === null ) { + + throw new Error( 'WebGPUBackend: Unable to create WebGPU adapter.' ); + + } + + // feature support + + const features = Object.values( GPUFeatureName ); + + const supportedFeatures = []; + + for ( const name of features ) { + + if ( adapter.features.has( name ) ) { + + supportedFeatures.push( name ); + + } + + } + + const deviceDescriptor = { + requiredFeatures: supportedFeatures, + requiredLimits: parameters.requiredLimits + }; + + device = await adapter.requestDevice( deviceDescriptor ); + + } else { + + device = parameters.device; + + } + + device.lost.then( ( info ) => { + + const deviceLossInfo = { + api: 'WebGPU', + message: info.message || 'Unknown reason', + reason: info.reason || null, + originalEvent: info + }; + + renderer.onDeviceLost( deviceLossInfo ); + + } ); + + const context = ( parameters.context !== undefined ) ? parameters.context : renderer.domElement.getContext( 'webgpu' ); + + this.device = device; + this.context = context; + + const alphaMode = parameters.alpha ? 'premultiplied' : 'opaque'; + + this.trackTimestamp = this.trackTimestamp && this.hasFeature( GPUFeatureName.TimestampQuery ); + + this.context.configure( { + device: this.device, + format: this.utils.getPreferredCanvasFormat(), + usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC, + alphaMode: alphaMode + } ); + + this.updateSize(); + + } + + /** + * The coordinate system of the backend. + * + * @type {number} + * @readonly + */ + get coordinateSystem() { + + return WebGPUCoordinateSystem; + + } + + /** + * This method performs a readback operation by moving buffer data from + * a storage buffer attribute from the GPU to the CPU. + * + * @async + * @param {StorageBufferAttribute} attribute - The storage buffer attribute. + * @return {Promise} A promise that resolves with the buffer data when the data are ready. + */ + async getArrayBufferAsync( attribute ) { + + return await this.attributeUtils.getArrayBufferAsync( attribute ); + + } + + /** + * Returns the backend's rendering context. + * + * @return {GPUCanvasContext} The rendering context. + */ + getContext() { + + return this.context; + + } + + /** + * Returns the default render pass descriptor. + * + * In WebGPU, the default framebuffer must be configured + * like custom framebuffers so the backend needs a render + * pass descriptor even when rendering directly to screen. + * + * @private + * @return {Object} The render pass descriptor. + */ + _getDefaultRenderPassDescriptor() { + + let descriptor = this.defaultRenderPassdescriptor; + + if ( descriptor === null ) { + + const renderer = this.renderer; + + descriptor = { + colorAttachments: [ { + view: null + } ], + }; + + if ( this.renderer.depth === true || this.renderer.stencil === true ) { + + descriptor.depthStencilAttachment = { + view: this.textureUtils.getDepthBuffer( renderer.depth, renderer.stencil ).createView() + }; + + } + + const colorAttachment = descriptor.colorAttachments[ 0 ]; + + if ( this.renderer.samples > 0 ) { + + colorAttachment.view = this.colorBuffer.createView(); + + } else { + + colorAttachment.resolveTarget = undefined; + + } + + this.defaultRenderPassdescriptor = descriptor; + + } + + const colorAttachment = descriptor.colorAttachments[ 0 ]; + + if ( this.renderer.samples > 0 ) { + + colorAttachment.resolveTarget = this.context.getCurrentTexture().createView(); + + } else { + + colorAttachment.view = this.context.getCurrentTexture().createView(); + + } + + return descriptor; + + } + + /** + * Internal to determine if the current render target is a render target array with depth 2D array texture. + * + * @param {RenderContext} renderContext - The render context. + * @return {boolean} Whether the render target is a render target array with depth 2D array texture. + * + * @private + */ + _isRenderCameraDepthArray( renderContext ) { + + return renderContext.depthTexture && renderContext.depthTexture.image.depth > 1 && renderContext.camera.isArrayCamera; + + } + + /** + * Returns the render pass descriptor for the given render context. + * + * @private + * @param {RenderContext} renderContext - The render context. + * @param {Object} colorAttachmentsConfig - Configuration object for the color attachments. + * @return {Object} The render pass descriptor. + */ + _getRenderPassDescriptor( renderContext, colorAttachmentsConfig = {} ) { + + const renderTarget = renderContext.renderTarget; + const renderTargetData = this.get( renderTarget ); + + let descriptors = renderTargetData.descriptors; + + if ( descriptors === undefined || + renderTargetData.width !== renderTarget.width || + renderTargetData.height !== renderTarget.height || + renderTargetData.dimensions !== renderTarget.dimensions || + renderTargetData.activeMipmapLevel !== renderContext.activeMipmapLevel || + renderTargetData.activeCubeFace !== renderContext.activeCubeFace || + renderTargetData.samples !== renderTarget.samples + ) { + + descriptors = {}; + + renderTargetData.descriptors = descriptors; + + // dispose + + const onDispose = () => { + + renderTarget.removeEventListener( 'dispose', onDispose ); + this.delete( renderTarget ); + + }; + + if ( renderTarget.hasEventListener( 'dispose', onDispose ) === false ) { + + renderTarget.addEventListener( 'dispose', onDispose ); + + } + + } + + const cacheKey = renderContext.getCacheKey(); + let descriptorBase = descriptors[ cacheKey ]; + + if ( descriptorBase === undefined ) { + + const textures = renderContext.textures; + const textureViews = []; + + let sliceIndex; + + const isRenderCameraDepthArray = this._isRenderCameraDepthArray( renderContext ); + + for ( let i = 0; i < textures.length; i ++ ) { + + const textureData = this.get( textures[ i ] ); + + const viewDescriptor = { + label: `colorAttachment_${ i }`, + baseMipLevel: renderContext.activeMipmapLevel, + mipLevelCount: 1, + baseArrayLayer: renderContext.activeCubeFace, + arrayLayerCount: 1, + dimension: GPUTextureViewDimension.TwoD + }; + + if ( renderTarget.isRenderTarget3D ) { + + sliceIndex = renderContext.activeCubeFace; + + viewDescriptor.baseArrayLayer = 0; + viewDescriptor.dimension = GPUTextureViewDimension.ThreeD; + viewDescriptor.depthOrArrayLayers = textures[ i ].image.depth; + + } else if ( renderTarget.isRenderTarget && textures[ i ].image.depth > 1 ) { + + if ( isRenderCameraDepthArray === true ) { + + const cameras = renderContext.camera.cameras; + for ( let layer = 0; layer < cameras.length; layer ++ ) { + + const layerViewDescriptor = { + ...viewDescriptor, + baseArrayLayer: layer, + arrayLayerCount: 1, + dimension: GPUTextureViewDimension.TwoD + }; + const textureView = textureData.texture.createView( layerViewDescriptor ); + textureViews.push( { + view: textureView, + resolveTarget: undefined, + depthSlice: undefined + } ); + + } + + } else { + + viewDescriptor.dimension = GPUTextureViewDimension.TwoDArray; + viewDescriptor.depthOrArrayLayers = textures[ i ].image.depth; + + } + + } + + if ( isRenderCameraDepthArray !== true ) { + + const textureView = textureData.texture.createView( viewDescriptor ); + + let view, resolveTarget; + + if ( textureData.msaaTexture !== undefined ) { + + view = textureData.msaaTexture.createView(); + resolveTarget = textureView; + + } else { + + view = textureView; + resolveTarget = undefined; + + } + + textureViews.push( { + view, + resolveTarget, + depthSlice: sliceIndex + } ); + + } + + } + + descriptorBase = { textureViews }; + + if ( renderContext.depth ) { + + const depthTextureData = this.get( renderContext.depthTexture ); + const options = {}; + if ( renderContext.depthTexture.isArrayTexture ) { + + options.dimension = GPUTextureViewDimension.TwoD; + options.arrayLayerCount = 1; + options.baseArrayLayer = renderContext.activeCubeFace; + + } + + descriptorBase.depthStencilView = depthTextureData.texture.createView( options ); + + } + + descriptors[ cacheKey ] = descriptorBase; + + renderTargetData.width = renderTarget.width; + renderTargetData.height = renderTarget.height; + renderTargetData.samples = renderTarget.samples; + renderTargetData.activeMipmapLevel = renderContext.activeMipmapLevel; + renderTargetData.activeCubeFace = renderContext.activeCubeFace; + renderTargetData.dimensions = renderTarget.dimensions; + + } + + const descriptor = { + colorAttachments: [] + }; + + // Apply dynamic properties to cached views + for ( let i = 0; i < descriptorBase.textureViews.length; i ++ ) { + + const viewInfo = descriptorBase.textureViews[ i ]; + + let clearValue = { r: 0, g: 0, b: 0, a: 1 }; + if ( i === 0 && colorAttachmentsConfig.clearValue ) { + + clearValue = colorAttachmentsConfig.clearValue; + + } + + descriptor.colorAttachments.push( { + view: viewInfo.view, + depthSlice: viewInfo.depthSlice, + resolveTarget: viewInfo.resolveTarget, + loadOp: colorAttachmentsConfig.loadOp || GPULoadOp.Load, + storeOp: colorAttachmentsConfig.storeOp || GPUStoreOp.Store, + clearValue: clearValue + } ); + + } + + if ( descriptorBase.depthStencilView ) { + + descriptor.depthStencilAttachment = { + view: descriptorBase.depthStencilView + }; + + } + + return descriptor; + + } + + /** + * This method is executed at the beginning of a render call and prepares + * the WebGPU state for upcoming render calls + * + * @param {RenderContext} renderContext - The render context. + */ + beginRender( renderContext ) { + + const renderContextData = this.get( renderContext ); + + const device = this.device; + const occlusionQueryCount = renderContext.occlusionQueryCount; + + let occlusionQuerySet; + + if ( occlusionQueryCount > 0 ) { + + if ( renderContextData.currentOcclusionQuerySet ) renderContextData.currentOcclusionQuerySet.destroy(); + if ( renderContextData.currentOcclusionQueryBuffer ) renderContextData.currentOcclusionQueryBuffer.destroy(); + + // Get a reference to the array of objects with queries. The renderContextData property + // can be changed by another render pass before the buffer.mapAsyc() completes. + renderContextData.currentOcclusionQuerySet = renderContextData.occlusionQuerySet; + renderContextData.currentOcclusionQueryBuffer = renderContextData.occlusionQueryBuffer; + renderContextData.currentOcclusionQueryObjects = renderContextData.occlusionQueryObjects; + + // + + occlusionQuerySet = device.createQuerySet( { type: 'occlusion', count: occlusionQueryCount, label: `occlusionQuerySet_${ renderContext.id }` } ); + + renderContextData.occlusionQuerySet = occlusionQuerySet; + renderContextData.occlusionQueryIndex = 0; + renderContextData.occlusionQueryObjects = new Array( occlusionQueryCount ); + + renderContextData.lastOcclusionObject = null; + + } + + let descriptor; + + if ( renderContext.textures === null ) { + + descriptor = this._getDefaultRenderPassDescriptor(); + + } else { + + descriptor = this._getRenderPassDescriptor( renderContext, { loadOp: GPULoadOp.Load } ); + + } + + this.initTimestampQuery( renderContext, descriptor ); + + descriptor.occlusionQuerySet = occlusionQuerySet; + + const depthStencilAttachment = descriptor.depthStencilAttachment; + + if ( renderContext.textures !== null ) { + + const colorAttachments = descriptor.colorAttachments; + + for ( let i = 0; i < colorAttachments.length; i ++ ) { + + const colorAttachment = colorAttachments[ i ]; + + if ( renderContext.clearColor ) { + + colorAttachment.clearValue = i === 0 ? renderContext.clearColorValue : { r: 0, g: 0, b: 0, a: 1 }; + colorAttachment.loadOp = GPULoadOp.Clear; + + } else { + + colorAttachment.loadOp = GPULoadOp.Load; + + } + + colorAttachment.storeOp = GPUStoreOp.Store; + + } + + } else { + + const colorAttachment = descriptor.colorAttachments[ 0 ]; + + if ( renderContext.clearColor ) { + + colorAttachment.clearValue = renderContext.clearColorValue; + colorAttachment.loadOp = GPULoadOp.Clear; + + } else { + + colorAttachment.loadOp = GPULoadOp.Load; + + } + + colorAttachment.storeOp = GPUStoreOp.Store; + + } + + // + + if ( renderContext.depth ) { + + if ( renderContext.clearDepth ) { + + depthStencilAttachment.depthClearValue = renderContext.clearDepthValue; + depthStencilAttachment.depthLoadOp = GPULoadOp.Clear; + + } else { + + depthStencilAttachment.depthLoadOp = GPULoadOp.Load; + + } + + depthStencilAttachment.depthStoreOp = GPUStoreOp.Store; + + } + + if ( renderContext.stencil ) { + + if ( renderContext.clearStencil ) { + + depthStencilAttachment.stencilClearValue = renderContext.clearStencilValue; + depthStencilAttachment.stencilLoadOp = GPULoadOp.Clear; + + } else { + + depthStencilAttachment.stencilLoadOp = GPULoadOp.Load; + + } + + depthStencilAttachment.stencilStoreOp = GPUStoreOp.Store; + + } + + // + + const encoder = device.createCommandEncoder( { label: 'renderContext_' + renderContext.id } ); + + // shadow arrays - prepare bundle encoders for each camera in an array camera + + if ( this._isRenderCameraDepthArray( renderContext ) === true ) { + + const cameras = renderContext.camera.cameras; + + if ( ! renderContextData.layerDescriptors || renderContextData.layerDescriptors.length !== cameras.length ) { + + this._createDepthLayerDescriptors( renderContext, renderContextData, descriptor, cameras ); + + } else { + + this._updateDepthLayerDescriptors( renderContext, renderContextData, cameras ); + + } + + // Create bundle encoders for each layer + renderContextData.bundleEncoders = []; + renderContextData.bundleSets = []; + + // Create separate bundle encoders for each camera in the array + for ( let i = 0; i < cameras.length; i ++ ) { + + const bundleEncoder = this.pipelineUtils.createBundleEncoder( + renderContext, + 'renderBundleArrayCamera_' + i + ); + + // Initialize state tracking for this bundle + const bundleSets = { + attributes: {}, + bindingGroups: [], + pipeline: null, + index: null + }; + + renderContextData.bundleEncoders.push( bundleEncoder ); + renderContextData.bundleSets.push( bundleSets ); + + } + + // We'll complete the bundles in finishRender + renderContextData.currentPass = null; + + } else { + + const currentPass = encoder.beginRenderPass( descriptor ); + renderContextData.currentPass = currentPass; + + if ( renderContext.viewport ) { + + this.updateViewport( renderContext ); + + } + + if ( renderContext.scissor ) { + + const { x, y, width, height } = renderContext.scissorValue; + currentPass.setScissorRect( x, y, width, height ); + + } + + } + + // + + renderContextData.descriptor = descriptor; + renderContextData.encoder = encoder; + renderContextData.currentSets = { attributes: {}, bindingGroups: [], pipeline: null, index: null }; + renderContextData.renderBundles = []; + + } + + /** + * This method creates layer descriptors for each camera in an array camera + * to prepare for rendering to a depth array texture. + * + * @param {RenderContext} renderContext - The render context. + * @param {Object} renderContextData - The render context data. + * @param {Object} descriptor - The render pass descriptor. + * @param {ArrayCamera} cameras - The array camera. + * + * @private + */ + _createDepthLayerDescriptors( renderContext, renderContextData, descriptor, cameras ) { + + const depthStencilAttachment = descriptor.depthStencilAttachment; + renderContextData.layerDescriptors = []; + + const depthTextureData = this.get( renderContext.depthTexture ); + if ( ! depthTextureData.viewCache ) { + + depthTextureData.viewCache = []; + + } + + for ( let i = 0; i < cameras.length; i ++ ) { + + const layerDescriptor = { + ...descriptor, + colorAttachments: [ { + ...descriptor.colorAttachments[ 0 ], + view: descriptor.colorAttachments[ i ].view + } ] + }; + + if ( descriptor.depthStencilAttachment ) { + + const layerIndex = i; + + if ( ! depthTextureData.viewCache[ layerIndex ] ) { + + depthTextureData.viewCache[ layerIndex ] = depthTextureData.texture.createView( { + dimension: GPUTextureViewDimension.TwoD, + baseArrayLayer: i, + arrayLayerCount: 1 + } ); + + } + + layerDescriptor.depthStencilAttachment = { + view: depthTextureData.viewCache[ layerIndex ], + depthLoadOp: depthStencilAttachment.depthLoadOp || GPULoadOp.Clear, + depthStoreOp: depthStencilAttachment.depthStoreOp || GPUStoreOp.Store, + depthClearValue: depthStencilAttachment.depthClearValue || 1.0 + }; + + if ( renderContext.stencil ) { + + layerDescriptor.depthStencilAttachment.stencilLoadOp = depthStencilAttachment.stencilLoadOp; + layerDescriptor.depthStencilAttachment.stencilStoreOp = depthStencilAttachment.stencilStoreOp; + layerDescriptor.depthStencilAttachment.stencilClearValue = depthStencilAttachment.stencilClearValue; + + } + + } else { + + layerDescriptor.depthStencilAttachment = { ...depthStencilAttachment }; + + } + + renderContextData.layerDescriptors.push( layerDescriptor ); + + } + + } + + /** + * This method updates the layer descriptors for each camera in an array camera + * to prepare for rendering to a depth array texture. + * + * @param {RenderContext} renderContext - The render context. + * @param {Object} renderContextData - The render context data. + * @param {ArrayCamera} cameras - The array camera. + * + */ + _updateDepthLayerDescriptors( renderContext, renderContextData, cameras ) { + + for ( let i = 0; i < cameras.length; i ++ ) { + + const layerDescriptor = renderContextData.layerDescriptors[ i ]; + + if ( layerDescriptor.depthStencilAttachment ) { + + const depthAttachment = layerDescriptor.depthStencilAttachment; + + if ( renderContext.depth ) { + + if ( renderContext.clearDepth ) { + + depthAttachment.depthClearValue = renderContext.clearDepthValue; + depthAttachment.depthLoadOp = GPULoadOp.Clear; + + } else { + + depthAttachment.depthLoadOp = GPULoadOp.Load; + + } + + } + + if ( renderContext.stencil ) { + + if ( renderContext.clearStencil ) { + + depthAttachment.stencilClearValue = renderContext.clearStencilValue; + depthAttachment.stencilLoadOp = GPULoadOp.Clear; + + } else { + + depthAttachment.stencilLoadOp = GPULoadOp.Load; + + } + + } + + } + + } + + } + + /** + * This method is executed at the end of a render call and finalizes work + * after draw calls. + * + * @param {RenderContext} renderContext - The render context. + */ + finishRender( renderContext ) { + + const renderContextData = this.get( renderContext ); + const occlusionQueryCount = renderContext.occlusionQueryCount; + + if ( renderContextData.renderBundles.length > 0 ) { + + renderContextData.currentPass.executeBundles( renderContextData.renderBundles ); + + } + + if ( occlusionQueryCount > renderContextData.occlusionQueryIndex ) { + + renderContextData.currentPass.endOcclusionQuery(); + + } + + // shadow arrays - Execute bundles for each layer + + const encoder = renderContextData.encoder; + + if ( this._isRenderCameraDepthArray( renderContext ) === true ) { + + const bundles = []; + + for ( let i = 0; i < renderContextData.bundleEncoders.length; i ++ ) { + + const bundleEncoder = renderContextData.bundleEncoders[ i ]; + bundles.push( bundleEncoder.finish() ); + + } + + for ( let i = 0; i < renderContextData.layerDescriptors.length; i ++ ) { + + if ( i < bundles.length ) { + + const layerDescriptor = renderContextData.layerDescriptors[ i ]; + const renderPass = encoder.beginRenderPass( layerDescriptor ); + + if ( renderContext.viewport ) { + + const { x, y, width, height, minDepth, maxDepth } = renderContext.viewportValue; + renderPass.setViewport( x, y, width, height, minDepth, maxDepth ); + + } + + if ( renderContext.scissor ) { + + const { x, y, width, height } = renderContext.scissorValue; + renderPass.setScissorRect( x, y, width, height ); + + } + + renderPass.executeBundles( [ bundles[ i ] ] ); + + renderPass.end(); + + } + + } + + } else if ( renderContextData.currentPass ) { + + renderContextData.currentPass.end(); + + } + + if ( occlusionQueryCount > 0 ) { + + const bufferSize = occlusionQueryCount * 8; // 8 byte entries for query results + + // + + let queryResolveBuffer = this.occludedResolveCache.get( bufferSize ); + + if ( queryResolveBuffer === undefined ) { + + queryResolveBuffer = this.device.createBuffer( + { + size: bufferSize, + usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC + } + ); + + this.occludedResolveCache.set( bufferSize, queryResolveBuffer ); + + } + + // + + const readBuffer = this.device.createBuffer( + { + size: bufferSize, + usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ + } + ); + + // two buffers required here - WebGPU doesn't allow usage of QUERY_RESOLVE & MAP_READ to be combined + renderContextData.encoder.resolveQuerySet( renderContextData.occlusionQuerySet, 0, occlusionQueryCount, queryResolveBuffer, 0 ); + renderContextData.encoder.copyBufferToBuffer( queryResolveBuffer, 0, readBuffer, 0, bufferSize ); + + renderContextData.occlusionQueryBuffer = readBuffer; + + // + + this.resolveOccludedAsync( renderContext ); + + } + + this.device.queue.submit( [ renderContextData.encoder.finish() ] ); + + + // + + if ( renderContext.textures !== null ) { + + const textures = renderContext.textures; + + for ( let i = 0; i < textures.length; i ++ ) { + + const texture = textures[ i ]; + + if ( texture.generateMipmaps === true ) { + + this.textureUtils.generateMipmaps( texture ); + + } + + } + + } + + } + + /** + * Returns `true` if the given 3D object is fully occluded by other + * 3D objects in the scene. + * + * @param {RenderContext} renderContext - The render context. + * @param {Object3D} object - The 3D object to test. + * @return {boolean} Whether the 3D object is fully occluded or not. + */ + isOccluded( renderContext, object ) { + + const renderContextData = this.get( renderContext ); + + return renderContextData.occluded && renderContextData.occluded.has( object ); + + } + + /** + * This method processes the result of occlusion queries and writes it + * into render context data. + * + * @async + * @param {RenderContext} renderContext - The render context. + * @return {Promise} A Promise that resolves when the occlusion query results have been processed. + */ + async resolveOccludedAsync( renderContext ) { + + const renderContextData = this.get( renderContext ); + + // handle occlusion query results + + const { currentOcclusionQueryBuffer, currentOcclusionQueryObjects } = renderContextData; + + if ( currentOcclusionQueryBuffer && currentOcclusionQueryObjects ) { + + const occluded = new WeakSet(); + + renderContextData.currentOcclusionQueryObjects = null; + renderContextData.currentOcclusionQueryBuffer = null; + + await currentOcclusionQueryBuffer.mapAsync( GPUMapMode.READ ); + + const buffer = currentOcclusionQueryBuffer.getMappedRange(); + const results = new BigUint64Array( buffer ); + + for ( let i = 0; i < currentOcclusionQueryObjects.length; i ++ ) { + + if ( results[ i ] === BigInt( 0 ) ) { + + occluded.add( currentOcclusionQueryObjects[ i ] ); + + } + + } + + currentOcclusionQueryBuffer.destroy(); + + renderContextData.occluded = occluded; + + } + + } + + /** + * Updates the viewport with the values from the given render context. + * + * @param {RenderContext} renderContext - The render context. + */ + updateViewport( renderContext ) { + + const { currentPass } = this.get( renderContext ); + const { x, y, width, height, minDepth, maxDepth } = renderContext.viewportValue; + + currentPass.setViewport( x, y, width, height, minDepth, maxDepth ); + + } + + /** + * Returns the clear color and alpha into a single + * color object. + * + * @return {Color4} The clear color. + */ + getClearColor() { + + const clearColor = super.getClearColor(); + + // only premultiply alpha when alphaMode is "premultiplied" + + if ( this.renderer.alpha === true ) { + + clearColor.r *= clearColor.a; + clearColor.g *= clearColor.a; + clearColor.b *= clearColor.a; + + } + + return clearColor; + + } + + /** + * Performs a clear operation. + * + * @param {boolean} color - Whether the color buffer should be cleared or not. + * @param {boolean} depth - Whether the depth buffer should be cleared or not. + * @param {boolean} stencil - Whether the stencil buffer should be cleared or not. + * @param {?RenderContext} [renderTargetContext=null] - The render context of the current set render target. + */ + clear( color, depth, stencil, renderTargetContext = null ) { + + const device = this.device; + const renderer = this.renderer; + + let colorAttachments = []; + let depthStencilAttachment; + let clearValue; + + let supportsDepth; + let supportsStencil; + + if ( color ) { + + const clearColor = this.getClearColor(); + clearValue = { r: clearColor.r, g: clearColor.g, b: clearColor.b, a: clearColor.a }; + + } + + if ( renderTargetContext === null ) { + + supportsDepth = renderer.depth; + supportsStencil = renderer.stencil; + + const descriptor = this._getDefaultRenderPassDescriptor(); + + if ( color ) { + + colorAttachments = descriptor.colorAttachments; + + const colorAttachment = colorAttachments[ 0 ]; + + colorAttachment.clearValue = clearValue; + colorAttachment.loadOp = GPULoadOp.Clear; + colorAttachment.storeOp = GPUStoreOp.Store; + + } + + if ( supportsDepth || supportsStencil ) { + + depthStencilAttachment = descriptor.depthStencilAttachment; + + } + + } else { + + supportsDepth = renderTargetContext.depth; + supportsStencil = renderTargetContext.stencil; + + const clearConfig = { + loadOp: color ? GPULoadOp.Clear : GPULoadOp.Load, + clearValue: color ? clearValue : undefined + }; + + if ( supportsDepth ) { + + clearConfig.depthLoadOp = depth ? GPULoadOp.Clear : GPULoadOp.Load; + clearConfig.depthClearValue = depth ? renderer.getClearDepth() : undefined; + clearConfig.depthStoreOp = GPUStoreOp.Store; + + } + + if ( supportsStencil ) { + + clearConfig.stencilLoadOp = stencil ? GPULoadOp.Clear : GPULoadOp.Load; + clearConfig.stencilClearValue = stencil ? renderer.getClearStencil() : undefined; + clearConfig.stencilStoreOp = GPUStoreOp.Store; + + } + + const descriptor = this._getRenderPassDescriptor( renderTargetContext, clearConfig ); + + colorAttachments = descriptor.colorAttachments; + depthStencilAttachment = descriptor.depthStencilAttachment; + + } + + if ( supportsDepth && depthStencilAttachment && depthStencilAttachment.depthLoadOp === undefined ) { + + if ( depth ) { + + depthStencilAttachment.depthLoadOp = GPULoadOp.Clear; + depthStencilAttachment.depthClearValue = renderer.getClearDepth(); + depthStencilAttachment.depthStoreOp = GPUStoreOp.Store; + + } else { + + depthStencilAttachment.depthLoadOp = GPULoadOp.Load; + depthStencilAttachment.depthStoreOp = GPUStoreOp.Store; + + } + + } + + // + + if ( supportsStencil && depthStencilAttachment && depthStencilAttachment.stencilLoadOp === undefined ) { + + if ( stencil ) { + + depthStencilAttachment.stencilLoadOp = GPULoadOp.Clear; + depthStencilAttachment.stencilClearValue = renderer.getClearStencil(); + depthStencilAttachment.stencilStoreOp = GPUStoreOp.Store; + + } else { + + depthStencilAttachment.stencilLoadOp = GPULoadOp.Load; + depthStencilAttachment.stencilStoreOp = GPUStoreOp.Store; + + } + + } + + // + + const encoder = device.createCommandEncoder( { label: 'clear' } ); + const currentPass = encoder.beginRenderPass( { + colorAttachments, + depthStencilAttachment + } ); + + currentPass.end(); + + device.queue.submit( [ encoder.finish() ] ); + + } + + // compute + + /** + * This method is executed at the beginning of a compute call and + * prepares the state for upcoming compute tasks. + * + * @param {Node|Array} computeGroup - The compute node(s). + */ + beginCompute( computeGroup ) { + + const groupGPU = this.get( computeGroup ); + + + const descriptor = { + label: 'computeGroup_' + computeGroup.id + }; + + this.initTimestampQuery( computeGroup, descriptor ); + + groupGPU.cmdEncoderGPU = this.device.createCommandEncoder( { label: 'computeGroup_' + computeGroup.id } ); + + groupGPU.passEncoderGPU = groupGPU.cmdEncoderGPU.beginComputePass( descriptor ); + + } + + /** + * Executes a compute command for the given compute node. + * + * @param {Node|Array} computeGroup - The group of compute nodes of a compute call. Can be a single compute node. + * @param {Node} computeNode - The compute node. + * @param {Array} bindings - The bindings. + * @param {ComputePipeline} pipeline - The compute pipeline. + */ + compute( computeGroup, computeNode, bindings, pipeline ) { + + const { passEncoderGPU } = this.get( computeGroup ); + + // pipeline + + const pipelineGPU = this.get( pipeline ).pipeline; + + this.pipelineUtils.setPipeline( passEncoderGPU, pipelineGPU ); + + // bind groups + + for ( let i = 0, l = bindings.length; i < l; i ++ ) { + + const bindGroup = bindings[ i ]; + const bindingsData = this.get( bindGroup ); + + passEncoderGPU.setBindGroup( i, bindingsData.group ); + + } + + const maxComputeWorkgroupsPerDimension = this.device.limits.maxComputeWorkgroupsPerDimension; + + const computeNodeData = this.get( computeNode ); + + if ( computeNodeData.dispatchSize === undefined ) computeNodeData.dispatchSize = { x: 0, y: 1, z: 1 }; + + const { dispatchSize } = computeNodeData; + + if ( computeNode.dispatchCount > maxComputeWorkgroupsPerDimension ) { + + dispatchSize.x = Math.min( computeNode.dispatchCount, maxComputeWorkgroupsPerDimension ); + dispatchSize.y = Math.ceil( computeNode.dispatchCount / maxComputeWorkgroupsPerDimension ); + + } else { + + dispatchSize.x = computeNode.dispatchCount; + + } + + passEncoderGPU.dispatchWorkgroups( + dispatchSize.x, + dispatchSize.y, + dispatchSize.z + ); + + } + + /** + * This method is executed at the end of a compute call and + * finalizes work after compute tasks. + * + * @param {Node|Array} computeGroup - The compute node(s). + */ + finishCompute( computeGroup ) { + + const groupData = this.get( computeGroup ); + + groupData.passEncoderGPU.end(); + + this.device.queue.submit( [ groupData.cmdEncoderGPU.finish() ] ); + + } + + /** + * Can be used to synchronize CPU operations with GPU tasks. So when this method is called, + * the CPU waits for the GPU to complete its operation (e.g. a compute task). + * + * @async + * @return {Promise} A Promise that resolves when synchronization has been finished. + */ + async waitForGPU() { + + await this.device.queue.onSubmittedWorkDone(); + + } + + // render object + + /** + * Executes a draw command for the given render object. + * + * @param {RenderObject} renderObject - The render object to draw. + * @param {Info} info - Holds a series of statistical information about the GPU memory and the rendering process. + */ + draw( renderObject, info ) { + + const { object, material, context, pipeline } = renderObject; + const bindings = renderObject.getBindings(); + const renderContextData = this.get( context ); + const pipelineGPU = this.get( pipeline ).pipeline; + + const index = renderObject.getIndex(); + const hasIndex = ( index !== null ); + + + const drawParams = renderObject.getDrawParameters(); + if ( drawParams === null ) return; + + // pipeline + + const setPipelineAndBindings = ( passEncoderGPU, currentSets ) => { + + // pipeline + this.pipelineUtils.setPipeline( passEncoderGPU, pipelineGPU ); + currentSets.pipeline = pipelineGPU; + + // bind groups + const currentBindingGroups = currentSets.bindingGroups; + for ( let i = 0, l = bindings.length; i < l; i ++ ) { + + const bindGroup = bindings[ i ]; + const bindingsData = this.get( bindGroup ); + if ( currentBindingGroups[ bindGroup.index ] !== bindGroup.id ) { + + passEncoderGPU.setBindGroup( bindGroup.index, bindingsData.group ); + currentBindingGroups[ bindGroup.index ] = bindGroup.id; + + } + + } + + // attributes + + // index + + if ( hasIndex === true ) { + + if ( currentSets.index !== index ) { + + const buffer = this.get( index ).buffer; + const indexFormat = ( index.array instanceof Uint16Array ) ? GPUIndexFormat.Uint16 : GPUIndexFormat.Uint32; + + passEncoderGPU.setIndexBuffer( buffer, indexFormat ); + + currentSets.index = index; + + } + + } + // vertex buffers + + const vertexBuffers = renderObject.getVertexBuffers(); + + for ( let i = 0, l = vertexBuffers.length; i < l; i ++ ) { + + const vertexBuffer = vertexBuffers[ i ]; + + if ( currentSets.attributes[ i ] !== vertexBuffer ) { + + const buffer = this.get( vertexBuffer ).buffer; + passEncoderGPU.setVertexBuffer( i, buffer ); + + currentSets.attributes[ i ] = vertexBuffer; + + } + + } + // stencil + + if ( context.stencil === true && material.stencilWrite === true && renderContextData.currentStencilRef !== material.stencilRef ) { + + passEncoderGPU.setStencilReference( material.stencilRef ); + renderContextData.currentStencilRef = material.stencilRef; + + } + + + }; + + // Define draw function + const draw = ( passEncoderGPU, currentSets ) => { + + setPipelineAndBindings( passEncoderGPU, currentSets ); + + if ( object.isBatchedMesh === true ) { + + const starts = object._multiDrawStarts; + const counts = object._multiDrawCounts; + const drawCount = object._multiDrawCount; + const drawInstances = object._multiDrawInstances; + + if ( drawInstances !== null ) { + + // @deprecated, r174 + warnOnce( 'THREE.WebGPUBackend: renderMultiDrawInstances has been deprecated and will be removed in r184. Append to renderMultiDraw arguments and use indirection.' ); + + } + + for ( let i = 0; i < drawCount; i ++ ) { + + const count = drawInstances ? drawInstances[ i ] : 1; + const firstInstance = count > 1 ? 0 : i; + + if ( hasIndex === true ) { + + passEncoderGPU.drawIndexed( counts[ i ], count, starts[ i ] / index.array.BYTES_PER_ELEMENT, 0, firstInstance ); + + } else { + + passEncoderGPU.draw( counts[ i ], count, starts[ i ], firstInstance ); + + } + + info.update( object, counts[ i ], count ); + + } + + } else if ( hasIndex === true ) { + + const { vertexCount: indexCount, instanceCount, firstVertex: firstIndex } = drawParams; + + const indirect = renderObject.getIndirect(); + + if ( indirect !== null ) { + + const buffer = this.get( indirect ).buffer; + + passEncoderGPU.drawIndexedIndirect( buffer, 0 ); + + } else { + + passEncoderGPU.drawIndexed( indexCount, instanceCount, firstIndex, 0, 0 ); + + } + + info.update( object, indexCount, instanceCount ); + + } else { + + const { vertexCount, instanceCount, firstVertex } = drawParams; + + const indirect = renderObject.getIndirect(); + + if ( indirect !== null ) { + + const buffer = this.get( indirect ).buffer; + + passEncoderGPU.drawIndirect( buffer, 0 ); + + } else { + + passEncoderGPU.draw( vertexCount, instanceCount, firstVertex, 0 ); + + } + + info.update( object, vertexCount, instanceCount ); + + } + + }; + + if ( renderObject.camera.isArrayCamera && renderObject.camera.cameras.length > 0 ) { + + const cameraData = this.get( renderObject.camera ); + const cameras = renderObject.camera.cameras; + const cameraIndex = renderObject.getBindingGroup( 'cameraIndex' ); + + if ( cameraData.indexesGPU === undefined || cameraData.indexesGPU.length !== cameras.length ) { + + const bindingsData = this.get( cameraIndex ); + const indexesGPU = []; + + const data = new Uint32Array( [ 0, 0, 0, 0 ] ); + + for ( let i = 0, len = cameras.length; i < len; i ++ ) { + + data[ 0 ] = i; + + const bindGroupIndex = this.bindingUtils.createBindGroupIndex( data, bindingsData.layout ); + + indexesGPU.push( bindGroupIndex ); + + } + + cameraData.indexesGPU = indexesGPU; // TODO: Create a global library for this + + } + + const pixelRatio = this.renderer.getPixelRatio(); + + for ( let i = 0, len = cameras.length; i < len; i ++ ) { + + const subCamera = cameras[ i ]; + + if ( object.layers.test( subCamera.layers ) ) { + + const vp = subCamera.viewport; + + + + let pass = renderContextData.currentPass; + let sets = renderContextData.currentSets; + if ( renderContextData.bundleEncoders ) { + + const bundleEncoder = renderContextData.bundleEncoders[ i ]; + const bundleSets = renderContextData.bundleSets[ i ]; + pass = bundleEncoder; + sets = bundleSets; + + } + + + + if ( vp ) { + + pass.setViewport( + Math.floor( vp.x * pixelRatio ), + Math.floor( vp.y * pixelRatio ), + Math.floor( vp.width * pixelRatio ), + Math.floor( vp.height * pixelRatio ), + context.viewportValue.minDepth, + context.viewportValue.maxDepth + ); + + } + + + // Set camera index binding for this layer + if ( cameraIndex && cameraData.indexesGPU ) { + + pass.setBindGroup( cameraIndex.index, cameraData.indexesGPU[ i ] ); + sets.bindingGroups[ cameraIndex.index ] = cameraIndex.id; + + } + + draw( pass, sets ); + + + } + + } + + } else { + + // Regular single camera rendering + if ( renderContextData.currentPass ) { + + // Handle occlusion queries + if ( renderContextData.occlusionQuerySet !== undefined ) { + + const lastObject = renderContextData.lastOcclusionObject; + if ( lastObject !== object ) { + + if ( lastObject !== null && lastObject.occlusionTest === true ) { + + renderContextData.currentPass.endOcclusionQuery(); + renderContextData.occlusionQueryIndex ++; + + } + + if ( object.occlusionTest === true ) { + + renderContextData.currentPass.beginOcclusionQuery( renderContextData.occlusionQueryIndex ); + renderContextData.occlusionQueryObjects[ renderContextData.occlusionQueryIndex ] = object; + + } + + renderContextData.lastOcclusionObject = object; + + } + + } + + draw( renderContextData.currentPass, renderContextData.currentSets ); + + } + + } + + } + + // cache key + + /** + * Returns `true` if the render pipeline requires an update. + * + * @param {RenderObject} renderObject - The render object. + * @return {boolean} Whether the render pipeline requires an update or not. + */ + needsRenderUpdate( renderObject ) { + + const data = this.get( renderObject ); + + const { object, material } = renderObject; + + const utils = this.utils; + + const sampleCount = utils.getSampleCountRenderContext( renderObject.context ); + const colorSpace = utils.getCurrentColorSpace( renderObject.context ); + const colorFormat = utils.getCurrentColorFormat( renderObject.context ); + const depthStencilFormat = utils.getCurrentDepthStencilFormat( renderObject.context ); + const primitiveTopology = utils.getPrimitiveTopology( object, material ); + + let needsUpdate = false; + + if ( data.material !== material || data.materialVersion !== material.version || + data.transparent !== material.transparent || data.blending !== material.blending || data.premultipliedAlpha !== material.premultipliedAlpha || + data.blendSrc !== material.blendSrc || data.blendDst !== material.blendDst || data.blendEquation !== material.blendEquation || + data.blendSrcAlpha !== material.blendSrcAlpha || data.blendDstAlpha !== material.blendDstAlpha || data.blendEquationAlpha !== material.blendEquationAlpha || + data.colorWrite !== material.colorWrite || data.depthWrite !== material.depthWrite || data.depthTest !== material.depthTest || data.depthFunc !== material.depthFunc || + data.stencilWrite !== material.stencilWrite || data.stencilFunc !== material.stencilFunc || + data.stencilFail !== material.stencilFail || data.stencilZFail !== material.stencilZFail || data.stencilZPass !== material.stencilZPass || + data.stencilFuncMask !== material.stencilFuncMask || data.stencilWriteMask !== material.stencilWriteMask || + data.side !== material.side || data.alphaToCoverage !== material.alphaToCoverage || + data.sampleCount !== sampleCount || data.colorSpace !== colorSpace || + data.colorFormat !== colorFormat || data.depthStencilFormat !== depthStencilFormat || + data.primitiveTopology !== primitiveTopology || + data.clippingContextCacheKey !== renderObject.clippingContextCacheKey + ) { + + data.material = material; data.materialVersion = material.version; + data.transparent = material.transparent; data.blending = material.blending; data.premultipliedAlpha = material.premultipliedAlpha; + data.blendSrc = material.blendSrc; data.blendDst = material.blendDst; data.blendEquation = material.blendEquation; + data.blendSrcAlpha = material.blendSrcAlpha; data.blendDstAlpha = material.blendDstAlpha; data.blendEquationAlpha = material.blendEquationAlpha; + data.colorWrite = material.colorWrite; + data.depthWrite = material.depthWrite; data.depthTest = material.depthTest; data.depthFunc = material.depthFunc; + data.stencilWrite = material.stencilWrite; data.stencilFunc = material.stencilFunc; + data.stencilFail = material.stencilFail; data.stencilZFail = material.stencilZFail; data.stencilZPass = material.stencilZPass; + data.stencilFuncMask = material.stencilFuncMask; data.stencilWriteMask = material.stencilWriteMask; + data.side = material.side; data.alphaToCoverage = material.alphaToCoverage; + data.sampleCount = sampleCount; + data.colorSpace = colorSpace; + data.colorFormat = colorFormat; + data.depthStencilFormat = depthStencilFormat; + data.primitiveTopology = primitiveTopology; + data.clippingContextCacheKey = renderObject.clippingContextCacheKey; + + needsUpdate = true; + + } + + return needsUpdate; + + } + + /** + * Returns a cache key that is used to identify render pipelines. + * + * @param {RenderObject} renderObject - The render object. + * @return {string} The cache key. + */ + getRenderCacheKey( renderObject ) { + + const { object, material } = renderObject; + + const utils = this.utils; + const renderContext = renderObject.context; + + return [ + material.transparent, material.blending, material.premultipliedAlpha, + material.blendSrc, material.blendDst, material.blendEquation, + material.blendSrcAlpha, material.blendDstAlpha, material.blendEquationAlpha, + material.colorWrite, + material.depthWrite, material.depthTest, material.depthFunc, + material.stencilWrite, material.stencilFunc, + material.stencilFail, material.stencilZFail, material.stencilZPass, + material.stencilFuncMask, material.stencilWriteMask, + material.side, + utils.getSampleCountRenderContext( renderContext ), + utils.getCurrentColorSpace( renderContext ), utils.getCurrentColorFormat( renderContext ), utils.getCurrentDepthStencilFormat( renderContext ), + utils.getPrimitiveTopology( object, material ), + renderObject.getGeometryCacheKey(), + renderObject.clippingContextCacheKey + ].join(); + + } + + // textures + + /** + * Creates a GPU sampler for the given texture. + * + * @param {Texture} texture - The texture to create the sampler for. + */ + createSampler( texture ) { + + this.textureUtils.createSampler( texture ); + + } + + /** + * Destroys the GPU sampler for the given texture. + * + * @param {Texture} texture - The texture to destroy the sampler for. + */ + destroySampler( texture ) { + + this.textureUtils.destroySampler( texture ); + + } + + /** + * Creates a default texture for the given texture that can be used + * as a placeholder until the actual texture is ready for usage. + * + * @param {Texture} texture - The texture to create a default texture for. + */ + createDefaultTexture( texture ) { + + this.textureUtils.createDefaultTexture( texture ); + + } + + /** + * Defines a texture on the GPU for the given texture object. + * + * @param {Texture} texture - The texture. + * @param {Object} [options={}] - Optional configuration parameter. + */ + createTexture( texture, options ) { + + this.textureUtils.createTexture( texture, options ); + + } + + /** + * Uploads the updated texture data to the GPU. + * + * @param {Texture} texture - The texture. + * @param {Object} [options={}] - Optional configuration parameter. + */ + updateTexture( texture, options ) { + + this.textureUtils.updateTexture( texture, options ); + + } + + /** + * Generates mipmaps for the given texture. + * + * @param {Texture} texture - The texture. + */ + generateMipmaps( texture ) { + + this.textureUtils.generateMipmaps( texture ); + + } + + /** + * Destroys the GPU data for the given texture object. + * + * @param {Texture} texture - The texture. + */ + destroyTexture( texture ) { + + this.textureUtils.destroyTexture( texture ); + + } + + /** + * Returns texture data as a typed array. + * + * @async + * @param {Texture} texture - The texture to copy. + * @param {number} x - The x coordinate of the copy origin. + * @param {number} y - The y coordinate of the copy origin. + * @param {number} width - The width of the copy. + * @param {number} height - The height of the copy. + * @param {number} faceIndex - The face index. + * @return {Promise} A Promise that resolves with a typed array when the copy operation has finished. + */ + async copyTextureToBuffer( texture, x, y, width, height, faceIndex ) { + + return this.textureUtils.copyTextureToBuffer( texture, x, y, width, height, faceIndex ); + + } + + /** + * Inits a time stamp query for the given render context. + * + * @param {RenderContext} renderContext - The render context. + * @param {Object} descriptor - The query descriptor. + */ + initTimestampQuery( renderContext, descriptor ) { + + if ( ! this.trackTimestamp ) return; + + const type = renderContext.isComputeNode ? 'compute' : 'render'; + + if ( ! this.timestampQueryPool[ type ] ) { + + // TODO: Variable maxQueries? + this.timestampQueryPool[ type ] = new WebGPUTimestampQueryPool( this.device, type, 2048 ); + + } + + const timestampQueryPool = this.timestampQueryPool[ type ]; + + const baseOffset = timestampQueryPool.allocateQueriesForContext( renderContext ); + + descriptor.timestampWrites = { + querySet: timestampQueryPool.querySet, + beginningOfPassWriteIndex: baseOffset, + endOfPassWriteIndex: baseOffset + 1, + }; + + } + + + // node builder + + /** + * Returns a node builder for the given render object. + * + * @param {RenderObject} object - The render object. + * @param {Renderer} renderer - The renderer. + * @return {WGSLNodeBuilder} The node builder. + */ + createNodeBuilder( object, renderer ) { + + return new WGSLNodeBuilder( object, renderer ); + + } + + // program + + /** + * Creates a shader program from the given programmable stage. + * + * @param {ProgrammableStage} program - The programmable stage. + */ + createProgram( program ) { + + const programGPU = this.get( program ); + + programGPU.module = { + module: this.device.createShaderModule( { code: program.code, label: program.stage + ( program.name !== '' ? `_${ program.name }` : '' ) } ), + entryPoint: 'main' + }; + + } + + /** + * Destroys the shader program of the given programmable stage. + * + * @param {ProgrammableStage} program - The programmable stage. + */ + destroyProgram( program ) { + + this.delete( program ); + + } + + // pipelines + + /** + * Creates a render pipeline for the given render object. + * + * @param {RenderObject} renderObject - The render object. + * @param {Array} promises - An array of compilation promises which are used in `compileAsync()`. + */ + createRenderPipeline( renderObject, promises ) { + + this.pipelineUtils.createRenderPipeline( renderObject, promises ); + + } + + /** + * Creates a compute pipeline for the given compute node. + * + * @param {ComputePipeline} computePipeline - The compute pipeline. + * @param {Array} bindings - The bindings. + */ + createComputePipeline( computePipeline, bindings ) { + + this.pipelineUtils.createComputePipeline( computePipeline, bindings ); + + } + + /** + * Prepares the state for encoding render bundles. + * + * @param {RenderContext} renderContext - The render context. + */ + beginBundle( renderContext ) { + + const renderContextData = this.get( renderContext ); + + renderContextData._currentPass = renderContextData.currentPass; + renderContextData._currentSets = renderContextData.currentSets; + + renderContextData.currentSets = { attributes: {}, bindingGroups: [], pipeline: null, index: null }; + renderContextData.currentPass = this.pipelineUtils.createBundleEncoder( renderContext ); + + } + + /** + * After processing render bundles this method finalizes related work. + * + * @param {RenderContext} renderContext - The render context. + * @param {RenderBundle} bundle - The render bundle. + */ + finishBundle( renderContext, bundle ) { + + const renderContextData = this.get( renderContext ); + + const bundleEncoder = renderContextData.currentPass; + const bundleGPU = bundleEncoder.finish(); + + this.get( bundle ).bundleGPU = bundleGPU; + + // restore render pass state + + renderContextData.currentSets = renderContextData._currentSets; + renderContextData.currentPass = renderContextData._currentPass; + + } + + /** + * Adds a render bundle to the render context data. + * + * @param {RenderContext} renderContext - The render context. + * @param {RenderBundle} bundle - The render bundle to add. + */ + addBundle( renderContext, bundle ) { + + const renderContextData = this.get( renderContext ); + + renderContextData.renderBundles.push( this.get( bundle ).bundleGPU ); + + } + + // bindings + + /** + * Creates bindings from the given bind group definition. + * + * @param {BindGroup} bindGroup - The bind group. + * @param {Array} bindings - Array of bind groups. + * @param {number} cacheIndex - The cache index. + * @param {number} version - The version. + */ + createBindings( bindGroup, bindings, cacheIndex, version ) { + + this.bindingUtils.createBindings( bindGroup, bindings, cacheIndex, version ); + + } + + /** + * Updates the given bind group definition. + * + * @param {BindGroup} bindGroup - The bind group. + * @param {Array} bindings - Array of bind groups. + * @param {number} cacheIndex - The cache index. + * @param {number} version - The version. + */ + updateBindings( bindGroup, bindings, cacheIndex, version ) { + + this.bindingUtils.createBindings( bindGroup, bindings, cacheIndex, version ); + + } + + /** + * Updates a buffer binding. + * + * @param {Buffer} binding - The buffer binding to update. + */ + updateBinding( binding ) { + + this.bindingUtils.updateBinding( binding ); + + } + + // attributes + + /** + * Creates the buffer of an indexed shader attribute. + * + * @param {BufferAttribute} attribute - The indexed buffer attribute. + */ + createIndexAttribute( attribute ) { + + let usage = GPUBufferUsage.INDEX | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST; + + if ( attribute.isStorageBufferAttribute || attribute.isStorageInstancedBufferAttribute ) { + + usage |= GPUBufferUsage.STORAGE; + + } + + this.attributeUtils.createAttribute( attribute, usage ); + + } + + /** + * Creates the GPU buffer of a shader attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute. + */ + createAttribute( attribute ) { + + this.attributeUtils.createAttribute( attribute, GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST ); + + } + + /** + * Creates the GPU buffer of a storage attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute. + */ + createStorageAttribute( attribute ) { + + this.attributeUtils.createAttribute( attribute, GPUBufferUsage.STORAGE | GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST ); + + } + + /** + * Creates the GPU buffer of an indirect storage attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute. + */ + createIndirectStorageAttribute( attribute ) { + + this.attributeUtils.createAttribute( attribute, GPUBufferUsage.STORAGE | GPUBufferUsage.INDIRECT | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST ); + + } + + /** + * Updates the GPU buffer of a shader attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute to update. + */ + updateAttribute( attribute ) { + + this.attributeUtils.updateAttribute( attribute ); + + } + + /** + * Destroys the GPU buffer of a shader attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute to destroy. + */ + destroyAttribute( attribute ) { + + this.attributeUtils.destroyAttribute( attribute ); + + } + + // canvas + + /** + * Triggers an update of the default render pass descriptor. + */ + updateSize() { + + this.colorBuffer = this.textureUtils.getColorBuffer(); + this.defaultRenderPassdescriptor = null; + + } + + // utils public + + /** + * Returns the maximum anisotropy texture filtering value. + * + * @return {number} The maximum anisotropy texture filtering value. + */ + getMaxAnisotropy() { + + return 16; + + } + + /** + * Checks if the given feature is supported by the backend. + * + * @param {string} name - The feature's name. + * @return {boolean} Whether the feature is supported or not. + */ + hasFeature( name ) { + + return this.device.features.has( name ); + + } + + /** + * Copies data of the given source texture to the given destination texture. + * + * @param {Texture} srcTexture - The source texture. + * @param {Texture} dstTexture - The destination texture. + * @param {?(Box3|Box2)} [srcRegion=null] - The region of the source texture to copy. + * @param {?(Vector2|Vector3)} [dstPosition=null] - The destination position of the copy. + * @param {number} [srcLevel=0] - The mipmap level to copy. + * @param {number} [dstLevel=0] - The destination mip level to copy to. + */ + copyTextureToTexture( srcTexture, dstTexture, srcRegion = null, dstPosition = null, srcLevel = 0, dstLevel = 0 ) { + + let dstX = 0; + let dstY = 0; + let dstZ = 0; + + let srcX = 0; + let srcY = 0; + let srcZ = 0; + + let srcWidth = srcTexture.image.width; + let srcHeight = srcTexture.image.height; + let srcDepth = 1; + + + if ( srcRegion !== null ) { + + if ( srcRegion.isBox3 === true ) { + + srcX = srcRegion.min.x; + srcY = srcRegion.min.y; + srcZ = srcRegion.min.z; + srcWidth = srcRegion.max.x - srcRegion.min.x; + srcHeight = srcRegion.max.y - srcRegion.min.y; + srcDepth = srcRegion.max.z - srcRegion.min.z; + + } else { + + // Assume it's a Box2 + srcX = srcRegion.min.x; + srcY = srcRegion.min.y; + srcWidth = srcRegion.max.x - srcRegion.min.x; + srcHeight = srcRegion.max.y - srcRegion.min.y; + srcDepth = 1; + + } + + } + + + if ( dstPosition !== null ) { + + dstX = dstPosition.x; + dstY = dstPosition.y; + dstZ = dstPosition.z || 0; + + } + + const encoder = this.device.createCommandEncoder( { label: 'copyTextureToTexture_' + srcTexture.id + '_' + dstTexture.id } ); + + const sourceGPU = this.get( srcTexture ).texture; + const destinationGPU = this.get( dstTexture ).texture; + + encoder.copyTextureToTexture( + { + texture: sourceGPU, + mipLevel: srcLevel, + origin: { x: srcX, y: srcY, z: srcZ } + }, + { + texture: destinationGPU, + mipLevel: dstLevel, + origin: { x: dstX, y: dstY, z: dstZ } + }, + [ + srcWidth, + srcHeight, + srcDepth + ] + ); + + this.device.queue.submit( [ encoder.finish() ] ); + + if ( dstLevel === 0 && dstTexture.generateMipmaps ) { + + this.textureUtils.generateMipmaps( dstTexture ); + + } + + } + + /** + * Copies the current bound framebuffer to the given texture. + * + * @param {Texture} texture - The destination texture. + * @param {RenderContext} renderContext - The render context. + * @param {Vector4} rectangle - A four dimensional vector defining the origin and dimension of the copy. + */ + copyFramebufferToTexture( texture, renderContext, rectangle ) { + + const renderContextData = this.get( renderContext ); + + let sourceGPU = null; + + if ( renderContext.renderTarget ) { + + if ( texture.isDepthTexture ) { + + sourceGPU = this.get( renderContext.depthTexture ).texture; + + } else { + + sourceGPU = this.get( renderContext.textures[ 0 ] ).texture; + + } + + } else { + + if ( texture.isDepthTexture ) { + + sourceGPU = this.textureUtils.getDepthBuffer( renderContext.depth, renderContext.stencil ); + + } else { + + sourceGPU = this.context.getCurrentTexture(); + + } + + } + + const destinationGPU = this.get( texture ).texture; + + if ( sourceGPU.format !== destinationGPU.format ) { + + console.error( 'WebGPUBackend: copyFramebufferToTexture: Source and destination formats do not match.', sourceGPU.format, destinationGPU.format ); + + return; + + } + + let encoder; + + if ( renderContextData.currentPass ) { + + renderContextData.currentPass.end(); + + encoder = renderContextData.encoder; + + } else { + + encoder = this.device.createCommandEncoder( { label: 'copyFramebufferToTexture_' + texture.id } ); + + } + + encoder.copyTextureToTexture( + { + texture: sourceGPU, + origin: [ rectangle.x, rectangle.y, 0 ], + }, + { + texture: destinationGPU + }, + [ + rectangle.z, + rectangle.w + ] + ); + + if ( renderContextData.currentPass ) { + + const { descriptor } = renderContextData; + + for ( let i = 0; i < descriptor.colorAttachments.length; i ++ ) { + + descriptor.colorAttachments[ i ].loadOp = GPULoadOp.Load; + + } + + if ( renderContext.depth ) descriptor.depthStencilAttachment.depthLoadOp = GPULoadOp.Load; + if ( renderContext.stencil ) descriptor.depthStencilAttachment.stencilLoadOp = GPULoadOp.Load; + + renderContextData.currentPass = encoder.beginRenderPass( descriptor ); + renderContextData.currentSets = { attributes: {}, bindingGroups: [], pipeline: null, index: null }; + + if ( renderContext.viewport ) { + + this.updateViewport( renderContext ); + + } + + if ( renderContext.scissor ) { + + const { x, y, width, height } = renderContext.scissorValue; + + renderContextData.currentPass.setScissorRect( x, y, width, height ); + + } + + } else { + + this.device.queue.submit( [ encoder.finish() ] ); + + } + + if ( texture.generateMipmaps ) { + + this.textureUtils.generateMipmaps( texture ); + + } + + } + +} + +/** + * A IES version of {@link SpotLight}. Can only be used with {@link WebGPURenderer}. + * + * @augments SpotLight + */ +class IESSpotLight extends SpotLight { + + /** + * Constructs a new IES spot light. + * + * @param {(number|Color|string)} [color=0xffffff] - The light's color. + * @param {number} [intensity=1] - The light's strength/intensity measured in candela (cd). + * @param {number} [distance=0] - Maximum range of the light. `0` means no limit. + * @param {number} [angle=Math.PI/3] - Maximum angle of light dispersion from its direction whose upper bound is `Math.PI/2`. + * @param {number} [penumbra=0] - Percent of the spotlight cone that is attenuated due to penumbra. Value range is `[0,1]`. + * @param {number} [decay=2] - The amount the light dims along the distance of the light. + */ + constructor( color, intensity, distance, angle, penumbra, decay ) { + + super( color, intensity, distance, angle, penumbra, decay ); + + /** + * TODO + * + * @type {?Texture} + * @default null + */ + this.iesMap = null; + + } + + copy( source, recursive ) { + + super.copy( source, recursive ); + + this.iesMap = source.iesMap; + + return this; + + } + +} + +/** + * A projector light version of {@link SpotLight}. Can only be used with {@link WebGPURenderer}. + * + * @augments SpotLight + */ +class ProjectorLight extends SpotLight { + + /** + * Constructs a new projector light. + * + * @param {(number|Color|string)} [color=0xffffff] - The light's color. + * @param {number} [intensity=1] - The light's strength/intensity measured in candela (cd). + * @param {number} [distance=0] - Maximum range of the light. `0` means no limit. + * @param {number} [angle=Math.PI/3] - Maximum angle of light dispersion from its direction whose upper bound is `Math.PI/2`. + * @param {number} [penumbra=0] - Percent of the spotlight cone that is attenuated due to penumbra. Value range is `[0,1]`. + * @param {number} [decay=2] - The amount the light dims along the distance of the light. + */ + constructor( color, intensity, distance, angle, penumbra, decay ) { + + super( color, intensity, distance, angle, penumbra, decay ); + + /** + * Aspect ratio of the light. Set to `null` to use the texture aspect ratio. + * + * @type {number} + * @default null + */ + this.aspect = null; + + } + + copy( source, recursive ) { + + super.copy( source, recursive ); + + this.aspect = source.aspect; + + return this; + + } + +} + +/** + * This version of a node library represents the standard version + * used in {@link WebGPURenderer}. It maps lights, tone mapping + * techniques and materials to node-based implementations. + * + * @private + * @augments NodeLibrary + */ +class StandardNodeLibrary extends NodeLibrary { + + /** + * Constructs a new standard node library. + */ + constructor() { + + super(); + + this.addMaterial( MeshPhongNodeMaterial, 'MeshPhongMaterial' ); + this.addMaterial( MeshStandardNodeMaterial, 'MeshStandardMaterial' ); + this.addMaterial( MeshPhysicalNodeMaterial, 'MeshPhysicalMaterial' ); + this.addMaterial( MeshToonNodeMaterial, 'MeshToonMaterial' ); + this.addMaterial( MeshBasicNodeMaterial, 'MeshBasicMaterial' ); + this.addMaterial( MeshLambertNodeMaterial, 'MeshLambertMaterial' ); + this.addMaterial( MeshNormalNodeMaterial, 'MeshNormalMaterial' ); + this.addMaterial( MeshMatcapNodeMaterial, 'MeshMatcapMaterial' ); + this.addMaterial( LineBasicNodeMaterial, 'LineBasicMaterial' ); + this.addMaterial( LineDashedNodeMaterial, 'LineDashedMaterial' ); + this.addMaterial( PointsNodeMaterial, 'PointsMaterial' ); + this.addMaterial( SpriteNodeMaterial, 'SpriteMaterial' ); + this.addMaterial( ShadowNodeMaterial, 'ShadowMaterial' ); + + this.addLight( PointLightNode, PointLight ); + this.addLight( DirectionalLightNode, DirectionalLight ); + this.addLight( RectAreaLightNode, RectAreaLight ); + this.addLight( SpotLightNode, SpotLight ); + this.addLight( AmbientLightNode, AmbientLight ); + this.addLight( HemisphereLightNode, HemisphereLight ); + this.addLight( LightProbeNode, LightProbe ); + this.addLight( IESSpotLightNode, IESSpotLight ); + this.addLight( ProjectorLightNode, ProjectorLight ); + + this.addToneMapping( linearToneMapping, LinearToneMapping ); + this.addToneMapping( reinhardToneMapping, ReinhardToneMapping ); + this.addToneMapping( cineonToneMapping, CineonToneMapping ); + this.addToneMapping( acesFilmicToneMapping, ACESFilmicToneMapping ); + this.addToneMapping( agxToneMapping, AgXToneMapping ); + this.addToneMapping( neutralToneMapping, NeutralToneMapping ); + + } + +} + +/* +const debugHandler = { + + get: function ( target, name ) { + + // Add |update + if ( /^(create|destroy)/.test( name ) ) console.log( 'WebGPUBackend.' + name ); + + return target[ name ]; + + } + +}; +*/ + +/** + * This renderer is the new alternative of `WebGLRenderer`. `WebGPURenderer` has the ability + * to target different backends. By default, the renderer tries to use a WebGPU backend if the + * browser supports WebGPU. If not, `WebGPURenderer` falls backs to a WebGL 2 backend. + * + * @augments Renderer + */ +class WebGPURenderer extends Renderer { + + /** + * WebGPURenderer options. + * + * @typedef {Object} WebGPURenderer~Options + * @property {boolean} [logarithmicDepthBuffer=false] - Whether logarithmic depth buffer is enabled or not. + * @property {boolean} [alpha=true] - Whether the default framebuffer (which represents the final contents of the canvas) should be transparent or opaque. + * @property {boolean} [depth=true] - Whether the default framebuffer should have a depth buffer or not. + * @property {boolean} [stencil=false] - Whether the default framebuffer should have a stencil buffer or not. + * @property {boolean} [antialias=false] - Whether MSAA as the default anti-aliasing should be enabled or not. + * @property {number} [samples=0] - When `antialias` is `true`, `4` samples are used by default. Set this parameter to any other integer value than 0 to overwrite the default. + * @property {boolean} [forceWebGL=false] - If set to `true`, the renderer uses a WebGL 2 backend no matter if WebGPU is supported or not. + * @property {boolean} [multiview=false] - If set to `true`, the renderer will use multiview during WebXR rendering if supported. + * @property {number} [outputType=undefined] - Texture type for output to canvas. By default, device's preferred format is used; other formats may incur overhead. + * @property {number} [colorBufferType=HalfFloatType] - Defines the type of color buffers. The default `HalfFloatType` is recommend for best + * quality. To save memory and bandwidth, `UnsignedByteType` might be used. This will reduce rendering quality though. + */ + + /** + * Constructs a new WebGPU renderer. + * + * @param {WebGPURenderer~Options} [parameters] - The configuration parameter. + */ + constructor( parameters = {} ) { + + let BackendClass; + + if ( parameters.forceWebGL ) { + + BackendClass = WebGLBackend; + + } else { + + BackendClass = WebGPUBackend; + + parameters.getFallback = () => { + + console.warn( 'THREE.WebGPURenderer: WebGPU is not available, running under WebGL2 backend.' ); + + return new WebGLBackend( parameters ); + + }; + + } + + const backend = new BackendClass( parameters ); + + //super( new Proxy( backend, debugHandler ) ); + super( backend, parameters ); + + /** + * The generic default value is overwritten with the + * standard node library for type mapping. + * + * @type {StandardNodeLibrary} + */ + this.library = new StandardNodeLibrary(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isWebGPURenderer = true; + + if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) { + + __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'observe', { detail: this } ) ); + + } + + } + +} + +/** + * A specialized group which enables applications access to the + * Render Bundle API of WebGPU. The group with all its descendant nodes + * are considered as one render bundle and processed as such by + * the renderer. + * + * This module is only fully supported by `WebGPURenderer` with a WebGPU backend. + * With a WebGL backend, the group can technically be rendered but without + * any performance improvements. + * + * @augments Group + */ +class BundleGroup extends Group { + + /** + * Constructs a new bundle group. + */ + constructor() { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isBundleGroup = true; + + /** + * This property is only relevant for detecting types + * during serialization/deserialization. It should always + * match the class name. + * + * @type {string} + * @readonly + * @default 'BundleGroup' + */ + this.type = 'BundleGroup'; + + /** + * Whether the bundle is static or not. When set to `true`, the structure + * is assumed to be static and does not change. E.g. no new objects are + * added to the group + * + * If a change is required, an update can still be forced by setting the + * `needsUpdate` flag to `true`. + * + * @type {boolean} + * @default true + */ + this.static = true; + + /** + * The bundle group's version. + * + * @type {number} + * @readonly + * @default 0 + */ + this.version = 0; + + } + + /** + * Set this property to `true` when the bundle group has changed. + * + * @type {boolean} + * @default false + * @param {boolean} value + */ + set needsUpdate( value ) { + + if ( value === true ) this.version ++; + + } + +} + +/** + * This module is responsible to manage the post processing setups in apps. + * You usually create a single instance of this class and use it to define + * the output of your post processing effect chain. + * ```js + * const postProcessing = new PostProcessing( renderer ); + * + * const scenePass = pass( scene, camera ); + * + * postProcessing.outputNode = scenePass; + * ``` + * + * Note: This module can only be used with `WebGPURenderer`. + */ +class PostProcessing { + + /** + * Constructs a new post processing management module. + * + * @param {Renderer} renderer - A reference to the renderer. + * @param {Node} outputNode - An optional output node. + */ + constructor( renderer, outputNode = vec4( 0, 0, 1, 1 ) ) { + + /** + * A reference to the renderer. + * + * @type {Renderer} + */ + this.renderer = renderer; + + /** + * A node which defines the final output of the post + * processing. This is usually the last node in a chain + * of effect nodes. + * + * @type {Node} + */ + this.outputNode = outputNode; + + /** + * Whether the default output tone mapping and color + * space transformation should be enabled or not. + * + * It is enabled by default by it must be disabled when + * effects must be executed after tone mapping and color + * space conversion. A typical example is FXAA which + * requires sRGB input. + * + * When set to `false`, the app must control the output + * transformation with `RenderOutputNode`. + * + * ```js + * const outputPass = renderOutput( scenePass ); + * ``` + * + * @type {boolean} + */ + this.outputColorTransform = true; + + /** + * Must be set to `true` when the output node changes. + * + * @type {Node} + */ + this.needsUpdate = true; + + const material = new NodeMaterial(); + material.name = 'PostProcessing'; + + /** + * The full screen quad that is used to render + * the effects. + * + * @private + * @type {QuadMesh} + */ + this._quadMesh = new QuadMesh( material ); + + } + + /** + * When `PostProcessing` is used to apply post processing effects, + * the application must use this version of `render()` inside + * its animation loop (not the one from the renderer). + */ + render() { + + this._update(); + + const renderer = this.renderer; + + const toneMapping = renderer.toneMapping; + const outputColorSpace = renderer.outputColorSpace; + + renderer.toneMapping = NoToneMapping; + renderer.outputColorSpace = LinearSRGBColorSpace; + + // + + const currentXR = renderer.xr.enabled; + renderer.xr.enabled = false; + + this._quadMesh.render( renderer ); + + renderer.xr.enabled = currentXR; + + // + + renderer.toneMapping = toneMapping; + renderer.outputColorSpace = outputColorSpace; + + } + + /** + * Frees internal resources. + */ + dispose() { + + this._quadMesh.material.dispose(); + + } + + /** + * Updates the state of the module. + * + * @private + */ + _update() { + + if ( this.needsUpdate === true ) { + + const renderer = this.renderer; + + const toneMapping = renderer.toneMapping; + const outputColorSpace = renderer.outputColorSpace; + + this._quadMesh.material.fragmentNode = this.outputColorTransform === true ? renderOutput( this.outputNode, toneMapping, outputColorSpace ) : this.outputNode.context( { toneMapping, outputColorSpace } ); + this._quadMesh.material.needsUpdate = true; + + this.needsUpdate = false; + + } + + } + + /** + * When `PostProcessing` is used to apply post processing effects, + * the application must use this version of `renderAsync()` inside + * its animation loop (not the one from the renderer). + * + * @async + * @return {Promise} A Promise that resolves when the render has been finished. + */ + async renderAsync() { + + this._update(); + + const renderer = this.renderer; + + const toneMapping = renderer.toneMapping; + const outputColorSpace = renderer.outputColorSpace; + + renderer.toneMapping = NoToneMapping; + renderer.outputColorSpace = LinearSRGBColorSpace; + + // + + const currentXR = renderer.xr.enabled; + renderer.xr.enabled = false; + + await this._quadMesh.renderAsync( renderer ); + + renderer.xr.enabled = currentXR; + + // + + renderer.toneMapping = toneMapping; + renderer.outputColorSpace = outputColorSpace; + + } + +} + +/** + * This special type of texture is intended for compute shaders. + * It can be used to compute the data of a texture with a compute shader. + * + * Note: This type of texture can only be used with `WebGPURenderer` + * and a WebGPU backend. + * + * @augments Texture + */ +class StorageTexture extends Texture { + + /** + * Constructs a new storage texture. + * + * @param {number} [width=1] - The storage texture's width. + * @param {number} [height=1] - The storage texture's height. + */ + constructor( width = 1, height = 1 ) { + + super(); + + /** + * The image object which just represents the texture's dimension. + * + * @type {{width: number, height: number}} + */ + this.image = { width, height }; + + /** + * The default `magFilter` for storage textures is `THREE.LinearFilter`. + * + * @type {number} + */ + this.magFilter = LinearFilter; + + /** + * The default `minFilter` for storage textures is `THREE.LinearFilter`. + * + * @type {number} + */ + this.minFilter = LinearFilter; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isStorageTexture = true; + + } + +} + +/** + * This special type of texture is intended for compute shaders. + * It can be used to compute the data of a texture with a compute shader. + * + * Note: This type of texture can only be used with `WebGPURenderer` + * and a WebGPU backend. + * + * @augments Texture + */ +class Storage3DTexture extends Texture { + + /** + * Constructs a new storage texture. + * + * @param {number} [width=1] - The storage texture's width. + * @param {number} [height=1] - The storage texture's height. + * @param {number} [depth=1] - The storage texture's depth. + */ + constructor( width = 1, height = 1, depth = 1 ) { + + super(); + + //inherited from texture. Must be false for 3DTexture + this.isArrayTexture = false; + + /** + * The image object which just represents the texture's dimension. + * + * @type {{width: number, height: number, depth: number}} + */ + this.image = { width, height, depth }; + + /** + * The default `magFilter` for storage textures is `THREE.LinearFilter`. + * + * @type {number} + */ + this.magFilter = LinearFilter; + + /** + * The default `minFilter` for storage textures is `THREE.LinearFilter`. + * + * @type {number} + */ + this.minFilter = LinearFilter; + + /** + * This defines how the texture is wrapped in the depth direction and corresponds to + * *W* in UVW mapping. + * + * @type {number} + */ + this.wrapR = ClampToEdgeWrapping; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isStorageTexture = true; + + /** + * Indicates whether this texture is a 3D texture. + * + * @type {boolean} + * + */ + this.is3DTexture = true; + + } + +} + +/** + * This special type of texture is intended for compute shaders. + * It can be used to compute the data of a texture with a compute shader. + * + * Note: This type of texture can only be used with `WebGPURenderer` + * and a WebGPU backend. + * + * @augments Texture + */ +class StorageArrayTexture extends Texture { + + /** + * Constructs a new storage texture. + * + * @param {number} [width=1] - The storage texture's width. + * @param {number} [height=1] - The storage texture's height. + * @param {number} [depth=1] - The storage texture's depth. + */ + constructor( width = 1, height = 1, depth = 1 ) { + + super(); + + //inherited from texture + this.isArrayTexture = true; + + /** + * The image object which just represents the texture's dimension. + * + * @type {{width: number, height: number, depth: number}} + */ + this.image = { width, height, depth }; + + /** + * The default `magFilter` for storage textures is `THREE.LinearFilter`. + * + * @type {number} + */ + this.magFilter = LinearFilter; + + /** + * The default `minFilter` for storage textures is `THREE.LinearFilter`. + * + * @type {number} + */ + this.minFilter = LinearFilter; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isStorageTexture = true; + + } + +} + +/** + * This special type of buffer attribute is intended for compute shaders. + * It can be used to encode draw parameters for indirect draw calls. + * + * Note: This type of buffer attribute can only be used with `WebGPURenderer` + * and a WebGPU backend. + * + * @augments StorageBufferAttribute + */ +class IndirectStorageBufferAttribute extends StorageBufferAttribute { + + /** + * Constructs a new storage buffer attribute. + * + * @param {number|Uint32Array} count - The item count. It is also valid to pass a `Uint32Array` as an argument. + * The subsequent parameter is then obsolete. + * @param {number} itemSize - The item size. + */ + constructor( count, itemSize ) { + + super( count, itemSize, Uint32Array ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isIndirectStorageBufferAttribute = true; + + } + +} + +/** + * A loader for loading node objects in the three.js JSON Object/Scene format. + * + * @augments Loader + */ +class NodeLoader extends Loader { + + /** + * Constructs a new node loader. + * + * @param {LoadingManager} [manager] - A reference to a loading manager. + */ + constructor( manager ) { + + super( manager ); + + /** + * Represents a dictionary of textures. + * + * @type {Object} + */ + this.textures = {}; + + /** + * Represents a dictionary of node types. + * + * @type {Object} + */ + this.nodes = {}; + + } + + /** + * Loads the node definitions from the given URL. + * + * @param {string} url - The path/URL of the file to be loaded. + * @param {Function} onLoad - Will be called when load completes. + * @param {Function} onProgress - Will be called while load progresses. + * @param {Function} onError - Will be called when errors are thrown during the loading process. + */ + load( url, onLoad, onProgress, onError ) { + + const loader = new FileLoader( this.manager ); + loader.setPath( this.path ); + loader.setRequestHeader( this.requestHeader ); + loader.setWithCredentials( this.withCredentials ); + loader.load( url, ( text ) => { + + try { + + onLoad( this.parse( JSON.parse( text ) ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + this.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + /** + * Parse the node dependencies for the loaded node. + * + * @param {Array} [json] - The JSON definition + * @return {Object} A dictionary with node dependencies. + */ + parseNodes( json ) { + + const nodes = {}; + + if ( json !== undefined ) { + + for ( const nodeJSON of json ) { + + const { uuid, type } = nodeJSON; + + nodes[ uuid ] = this.createNodeFromType( type ); + nodes[ uuid ].uuid = uuid; + + } + + const meta = { nodes, textures: this.textures }; + + for ( const nodeJSON of json ) { + + nodeJSON.meta = meta; + + const node = nodes[ nodeJSON.uuid ]; + node.deserialize( nodeJSON ); + + delete nodeJSON.meta; + + } + + } + + return nodes; + + } + + /** + * Parses the node from the given JSON. + * + * @param {Object} json - The JSON definition + * @param {string} json.type - The node type. + * @param {string} json.uuid - The node UUID. + * @param {Array} [json.nodes] - The node dependencies. + * @param {Object} [json.meta] - The meta data. + * @return {Node} The parsed node. + */ + parse( json ) { + + const node = this.createNodeFromType( json.type ); + node.uuid = json.uuid; + + const nodes = this.parseNodes( json.nodes ); + const meta = { nodes, textures: this.textures }; + + json.meta = meta; + + node.deserialize( json ); + + delete json.meta; + + return node; + + } + + /** + * Defines the dictionary of textures. + * + * @param {Object} value - The texture library defines as ``. + * @return {NodeLoader} A reference to this loader. + */ + setTextures( value ) { + + this.textures = value; + return this; + + } + + /** + * Defines the dictionary of node types. + * + * @param {Object} value - The node library defined as ``. + * @return {NodeLoader} A reference to this loader. + */ + setNodes( value ) { + + this.nodes = value; + return this; + + } + + /** + * Creates a node object from the given type. + * + * @param {string} type - The node type. + * @return {Node} The created node instance. + */ + createNodeFromType( type ) { + + if ( this.nodes[ type ] === undefined ) { + + console.error( 'THREE.NodeLoader: Node type not found:', type ); + return float(); + + } + + return nodeObject( new this.nodes[ type ]() ); + + } + +} + +/** + * A special type of material loader for loading node materials. + * + * @augments MaterialLoader + */ +class NodeMaterialLoader extends MaterialLoader { + + /** + * Constructs a new node material loader. + * + * @param {LoadingManager} [manager] - A reference to a loading manager. + */ + constructor( manager ) { + + super( manager ); + + /** + * Represents a dictionary of node types. + * + * @type {Object} + */ + this.nodes = {}; + + /** + * Represents a dictionary of node material types. + * + * @type {Object} + */ + this.nodeMaterials = {}; + + } + + /** + * Parses the node material from the given JSON. + * + * @param {Object} json - The JSON definition + * @return {NodeMaterial}. The parsed material. + */ + parse( json ) { + + const material = super.parse( json ); + + const nodes = this.nodes; + const inputNodes = json.inputNodes; + + for ( const property in inputNodes ) { + + const uuid = inputNodes[ property ]; + + material[ property ] = nodes[ uuid ]; + + } + + return material; + + } + + /** + * Defines the dictionary of node types. + * + * @param {Object} value - The node library defined as ``. + * @return {NodeLoader} A reference to this loader. + */ + setNodes( value ) { + + this.nodes = value; + return this; + + } + + /** + * Defines the dictionary of node material types. + * + * @param {Object} value - The node material library defined as ``. + * @return {NodeLoader} A reference to this loader. + */ + setNodeMaterials( value ) { + + this.nodeMaterials = value; + return this; + + } + + /** + * Creates a node material from the given type. + * + * @param {string} type - The node material type. + * @return {Node} The created node material instance. + */ + createMaterialFromType( type ) { + + const materialClass = this.nodeMaterials[ type ]; + + if ( materialClass !== undefined ) { + + return new materialClass(); + + } + + return super.createMaterialFromType( type ); + + } + +} + +/** + * A special type of object loader for loading 3D objects using + * node materials. + * + * @augments ObjectLoader + */ +class NodeObjectLoader extends ObjectLoader { + + /** + * Constructs a new node object loader. + * + * @param {LoadingManager} [manager] - A reference to a loading manager. + */ + constructor( manager ) { + + super( manager ); + + /** + * Represents a dictionary of node types. + * + * @type {Object} + */ + this.nodes = {}; + + /** + * Represents a dictionary of node material types. + * + * @type {Object} + */ + this.nodeMaterials = {}; + + /** + * A reference to hold the `nodes` JSON property. + * + * @private + * @type {?Object[]} + */ + this._nodesJSON = null; + + } + + /** + * Defines the dictionary of node types. + * + * @param {Object} value - The node library defined as ``. + * @return {NodeObjectLoader} A reference to this loader. + */ + setNodes( value ) { + + this.nodes = value; + return this; + + } + + /** + * Defines the dictionary of node material types. + * + * @param {Object} value - The node material library defined as ``. + * @return {NodeObjectLoader} A reference to this loader. + */ + setNodeMaterials( value ) { + + this.nodeMaterials = value; + return this; + + } + + /** + * Parses the node objects from the given JSON. + * + * @param {Object} json - The JSON definition + * @param {Function} onLoad - The onLoad callback function. + * @return {Object3D}. The parsed 3D object. + */ + parse( json, onLoad ) { + + this._nodesJSON = json.nodes; + + const data = super.parse( json, onLoad ); + + this._nodesJSON = null; // dispose + + return data; + + } + + /** + * Parses the node objects from the given JSON and textures. + * + * @param {Object[]} json - The JSON definition + * @param {Object} textures - The texture library. + * @return {Object}. The parsed nodes. + */ + parseNodes( json, textures ) { + + if ( json !== undefined ) { + + const loader = new NodeLoader(); + loader.setNodes( this.nodes ); + loader.setTextures( textures ); + + return loader.parseNodes( json ); + + } + + return {}; + + } + + /** + * Parses the node objects from the given JSON and textures. + * + * @param {Object} json - The JSON definition + * @param {Object} textures - The texture library. + * @return {Object}. The parsed materials. + */ + parseMaterials( json, textures ) { + + const materials = {}; + + if ( json !== undefined ) { + + const nodes = this.parseNodes( this._nodesJSON, textures ); + + const loader = new NodeMaterialLoader(); + loader.setTextures( textures ); + loader.setNodes( nodes ); + loader.setNodeMaterials( this.nodeMaterials ); + + for ( let i = 0, l = json.length; i < l; i ++ ) { + + const data = json[ i ]; + + materials[ data.uuid ] = loader.parse( data ); + + } + + } + + return materials; + + } + +} + +/** + * In earlier three.js versions, clipping was defined globally + * on the renderer or on material level. This special version of + * `THREE.Group` allows to encode the clipping state into the scene + * graph. Meaning if you create an instance of this group, all + * descendant 3D objects will be affected by the respective clipping + * planes. + * + * Note: `ClippingGroup` can only be used with `WebGPURenderer`. + * + * @augments Group + */ +class ClippingGroup extends Group { + + /** + * Constructs a new clipping group. + */ + constructor() { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isClippingGroup = true; + + /** + * An array with clipping planes. + * + * @type {Array} + */ + this.clippingPlanes = []; + + /** + * Whether clipping should be enabled or not. + * + * @type {boolean} + * @default true + */ + this.enabled = true; + + /** + * Whether the intersection of the clipping planes is used to clip objects, rather than their union. + * + * @type {boolean} + * @default false + */ + this.clipIntersection = false; + + /** + * Whether shadows should be clipped or not. + * + * @type {boolean} + * @default false + */ + this.clipShadows = false; + + } + +} + +export { ACESFilmicToneMapping, AONode, AddEquation, AddOperation, AdditiveBlending, AgXToneMapping, AlphaFormat, AlwaysCompare, AlwaysDepth, AlwaysStencilFunc, AmbientLight, AmbientLightNode, AnalyticLightNode, ArrayCamera, ArrayElementNode, ArrayNode, AssignNode, AttributeNode, BackSide, BasicEnvironmentNode, BasicShadowMap, BatchNode, BoxGeometry, BufferAttribute, BufferAttributeNode, BufferGeometry, BufferNode, BumpMapNode, BundleGroup, BypassNode, ByteType, CacheNode, Camera, CineonToneMapping, ClampToEdgeWrapping, ClippingGroup, CodeNode, Color, ColorManagement, ColorSpaceNode, ComputeNode, ConstNode, ContextNode, ConvertNode, CubeCamera, CubeReflectionMapping, CubeRefractionMapping, CubeTexture, CubeTextureNode, CubeUVReflectionMapping, CullFaceBack, CullFaceFront, CullFaceNone, CustomBlending, CylinderGeometry, DataArrayTexture, DataTexture, DebugNode, DecrementStencilOp, DecrementWrapStencilOp, DepthFormat, DepthStencilFormat, DepthTexture, DirectionalLight, DirectionalLightNode, DoubleSide, DstAlphaFactor, DstColorFactor, DynamicDrawUsage, EnvironmentNode, EqualCompare, EqualDepth, EqualStencilFunc, EquirectangularReflectionMapping, EquirectangularRefractionMapping, Euler, EventDispatcher, ExpressionNode, FileLoader, Float16BufferAttribute, Float32BufferAttribute, FloatType, FramebufferTexture, FrontFacingNode, FrontSide, Frustum, FrustumArray, FunctionCallNode, FunctionNode, FunctionOverloadingNode, GLSLNodeParser, GreaterCompare, GreaterDepth, GreaterEqualCompare, GreaterEqualDepth, GreaterEqualStencilFunc, GreaterStencilFunc, Group, HalfFloatType, HemisphereLight, HemisphereLightNode, IESSpotLight, IESSpotLightNode, IncrementStencilOp, IncrementWrapStencilOp, IndexNode, IndirectStorageBufferAttribute, InstanceNode, InstancedBufferAttribute, InstancedInterleavedBuffer, InstancedMeshNode, IntType, InterleavedBuffer, InterleavedBufferAttribute, InvertStencilOp, IrradianceNode, JoinNode, KeepStencilOp, LessCompare, LessDepth, LessEqualCompare, LessEqualDepth, LessEqualStencilFunc, LessStencilFunc, LightProbe, LightProbeNode, Lighting, LightingContextNode, LightingModel, LightingNode, LightsNode, Line2NodeMaterial, LineBasicMaterial, LineBasicNodeMaterial, LineDashedMaterial, LineDashedNodeMaterial, LinearFilter, LinearMipMapLinearFilter, LinearMipmapLinearFilter, LinearMipmapNearestFilter, LinearSRGBColorSpace, LinearToneMapping, LinearTransfer, Loader, LoopNode, MRTNode, Material, MaterialLoader, MaterialNode, MaterialReferenceNode, MathUtils, Matrix2, Matrix3, Matrix4, MaxEquation, MaxMipLevelNode, MemberNode, Mesh, MeshBasicMaterial, MeshBasicNodeMaterial, MeshLambertMaterial, MeshLambertNodeMaterial, MeshMatcapMaterial, MeshMatcapNodeMaterial, MeshNormalMaterial, MeshNormalNodeMaterial, MeshPhongMaterial, MeshPhongNodeMaterial, MeshPhysicalMaterial, MeshPhysicalNodeMaterial, MeshSSSNodeMaterial, MeshStandardMaterial, MeshStandardNodeMaterial, MeshToonMaterial, MeshToonNodeMaterial, MinEquation, MirroredRepeatWrapping, MixOperation, ModelNode, MorphNode, MultiplyBlending, MultiplyOperation, NearestFilter, NearestMipmapLinearFilter, NearestMipmapNearestFilter, NeutralToneMapping, NeverCompare, NeverDepth, NeverStencilFunc, NoBlending, NoColorSpace, NoToneMapping, Node, NodeAccess, NodeAttribute, NodeBuilder, NodeCache, NodeCode, NodeFrame, NodeFunctionInput, NodeLoader, NodeMaterial, NodeMaterialLoader, NodeMaterialObserver, NodeObjectLoader, NodeShaderStage, NodeType, NodeUniform, NodeUpdateType, NodeUtils, NodeVar, NodeVarying, NormalBlending, NormalMapNode, NotEqualCompare, NotEqualDepth, NotEqualStencilFunc, Object3D, Object3DNode, ObjectLoader, ObjectSpaceNormalMap, OneFactor, OneMinusDstAlphaFactor, OneMinusDstColorFactor, OneMinusSrcAlphaFactor, OneMinusSrcColorFactor, OrthographicCamera, OutputStructNode, PCFShadowMap, PMREMGenerator, PMREMNode, ParameterNode, PassNode, PerspectiveCamera, PhongLightingModel, PhysicalLightingModel, Plane, PlaneGeometry, PointLight, PointLightNode, PointUVNode, PointsMaterial, PointsNodeMaterial, PostProcessing, PosterizeNode, ProjectorLight, ProjectorLightNode, PropertyNode, QuadMesh, Quaternion, RED_GREEN_RGTC2_Format, RED_RGTC1_Format, REVISION, RGBAFormat, RGBAIntegerFormat, RGBA_ASTC_10x10_Format, RGBA_ASTC_10x5_Format, RGBA_ASTC_10x6_Format, RGBA_ASTC_10x8_Format, RGBA_ASTC_12x10_Format, RGBA_ASTC_12x12_Format, RGBA_ASTC_4x4_Format, RGBA_ASTC_5x4_Format, RGBA_ASTC_5x5_Format, RGBA_ASTC_6x5_Format, RGBA_ASTC_6x6_Format, RGBA_ASTC_8x5_Format, RGBA_ASTC_8x6_Format, RGBA_ASTC_8x8_Format, RGBA_BPTC_Format, RGBA_ETC2_EAC_Format, RGBA_PVRTC_2BPPV1_Format, RGBA_PVRTC_4BPPV1_Format, RGBA_S3TC_DXT1_Format, RGBA_S3TC_DXT3_Format, RGBA_S3TC_DXT5_Format, RGBFormat, RGBIntegerFormat, RGB_ETC1_Format, RGB_ETC2_Format, RGB_PVRTC_2BPPV1_Format, RGB_PVRTC_4BPPV1_Format, RGB_S3TC_DXT1_Format, RGFormat, RGIntegerFormat, RTTNode, RangeNode, RectAreaLight, RectAreaLightNode, RedFormat, RedIntegerFormat, ReferenceNode, ReflectorNode, ReinhardToneMapping, RemapNode, RenderOutputNode, RenderTarget, RendererReferenceNode, RendererUtils, RepeatWrapping, ReplaceStencilOp, ReverseSubtractEquation, RotateNode, SIGNED_RED_GREEN_RGTC2_Format, SIGNED_RED_RGTC1_Format, SRGBColorSpace, SRGBTransfer, Scene, SceneNode, ScreenNode, ScriptableNode, ScriptableValueNode, SetNode, ShadowBaseNode, ShadowMaterial, ShadowNode, ShadowNodeMaterial, ShortType, SkinningNode, Sphere, SphereGeometry, SplitNode, SpotLight, SpotLightNode, SpriteMaterial, SpriteNodeMaterial, SpriteSheetUVNode, SrcAlphaFactor, SrcAlphaSaturateFactor, SrcColorFactor, StackNode, StaticDrawUsage, Storage3DTexture, StorageArrayElementNode, StorageArrayTexture, StorageBufferAttribute, StorageBufferNode, StorageInstancedBufferAttribute, StorageTexture, StorageTextureNode, StructNode, StructTypeNode, SubBuildNode, SubtractEquation, SubtractiveBlending, TSL, TangentSpaceNormalMap, TempNode, Texture, Texture3DNode, TextureNode, TextureSizeNode, ToneMappingNode, ToonOutlinePassNode, UVMapping, Uint16BufferAttribute, Uint32BufferAttribute, UniformArrayNode, UniformGroupNode, UniformNode, UnsignedByteType, UnsignedInt248Type, UnsignedInt5999Type, UnsignedIntType, UnsignedShort4444Type, UnsignedShort5551Type, UnsignedShortType, UserDataNode, VSMShadowMap, VarNode, VaryingNode, Vector2, Vector3, Vector4, VertexColorNode, ViewportDepthNode, ViewportDepthTextureNode, ViewportSharedTextureNode, ViewportTextureNode, VolumeNodeMaterial, WebGLCoordinateSystem, WebGLCubeRenderTarget, WebGPUCoordinateSystem, WebGPURenderer, WebXRController, ZeroFactor, ZeroStencilOp, createCanvasElement, defaultBuildStages, defaultShaderStages, shaderStages, vectorComponents }; diff --git a/build/three.webgpu.min.js b/build/three.webgpu.min.js new file mode 100644 index 00000000000000..e721db62f86ac7 --- /dev/null +++ b/build/three.webgpu.min.js @@ -0,0 +1,6 @@ +/** + * @license + * Copyright 2010-2025 Three.js Authors + * SPDX-License-Identifier: MIT + */ +import{Color as e,Vector2 as t,Vector3 as r,Vector4 as s,Matrix2 as i,Matrix3 as n,Matrix4 as a,EventDispatcher as o,MathUtils as u,WebGLCoordinateSystem as l,WebGPUCoordinateSystem as d,ColorManagement as c,SRGBTransfer as h,NoToneMapping as p,StaticDrawUsage as g,InterleavedBuffer as m,InterleavedBufferAttribute as f,DynamicDrawUsage as y,NoColorSpace as b,Texture as x,UnsignedIntType as T,IntType as _,NearestFilter as v,Sphere as N,BackSide as S,DoubleSide as E,Euler as w,CubeTexture as A,CubeReflectionMapping as R,CubeRefractionMapping as C,TangentSpaceNormalMap as M,ObjectSpaceNormalMap as P,InstancedInterleavedBuffer as B,InstancedBufferAttribute as F,DataArrayTexture as L,FloatType as D,FramebufferTexture as I,LinearMipmapLinearFilter as V,DepthTexture as U,Material as O,NormalBlending as k,LineBasicMaterial as G,LineDashedMaterial as z,NoBlending as H,MeshNormalMaterial as $,SRGBColorSpace as W,WebGLCubeRenderTarget as j,BoxGeometry as q,Mesh as X,Scene as K,LinearFilter as Y,CubeCamera as Q,EquirectangularReflectionMapping as Z,EquirectangularRefractionMapping as J,AddOperation as ee,MixOperation as te,MultiplyOperation as re,MeshBasicMaterial as se,MeshLambertMaterial as ie,MeshPhongMaterial as ne,OrthographicCamera as ae,PerspectiveCamera as oe,RenderTarget as ue,LinearSRGBColorSpace as le,RGBAFormat as de,HalfFloatType as ce,CubeUVReflectionMapping as he,BufferGeometry as pe,BufferAttribute as ge,MeshStandardMaterial as me,MeshPhysicalMaterial as fe,MeshToonMaterial as ye,MeshMatcapMaterial as be,SpriteMaterial as xe,PointsMaterial as Te,ShadowMaterial as _e,Uint32BufferAttribute as ve,Uint16BufferAttribute as Ne,arrayNeedsUint32 as Se,Camera as Ee,DepthStencilFormat as we,DepthFormat as Ae,UnsignedInt248Type as Re,UnsignedByteType as Ce,Plane as Me,Object3D as Pe,LinearMipMapLinearFilter as Be,Float32BufferAttribute as Fe,UVMapping as Le,VSMShadowMap as De,LessCompare as Ie,RGFormat as Ve,BasicShadowMap as Ue,SphereGeometry as Oe,LinearMipmapNearestFilter as ke,NearestMipmapLinearFilter as Ge,Float16BufferAttribute as ze,REVISION as He,ArrayCamera as $e,PlaneGeometry as We,FrontSide as je,CustomBlending as qe,AddEquation as Xe,ZeroFactor as Ke,CylinderGeometry as Ye,Quaternion as Qe,WebXRController as Ze,RAD2DEG as Je,PCFShadowMap as et,FrustumArray as tt,Frustum as rt,DataTexture as st,RedIntegerFormat as it,RedFormat as nt,ShortType as at,ByteType as ot,UnsignedShortType as ut,RGIntegerFormat as lt,RGBIntegerFormat as dt,RGBFormat as ct,RGBAIntegerFormat as ht,warnOnce as pt,createCanvasElement as gt,ReverseSubtractEquation as mt,SubtractEquation as ft,OneMinusDstAlphaFactor as yt,OneMinusDstColorFactor as bt,OneMinusSrcAlphaFactor as xt,OneMinusSrcColorFactor as Tt,DstAlphaFactor as _t,DstColorFactor as vt,SrcAlphaSaturateFactor as Nt,SrcAlphaFactor as St,SrcColorFactor as Et,OneFactor as wt,CullFaceNone as At,CullFaceBack as Rt,CullFaceFront as Ct,MultiplyBlending as Mt,SubtractiveBlending as Pt,AdditiveBlending as Bt,NotEqualDepth as Ft,GreaterDepth as Lt,GreaterEqualDepth as Dt,EqualDepth as It,LessEqualDepth as Vt,LessDepth as Ut,AlwaysDepth as Ot,NeverDepth as kt,UnsignedShort4444Type as Gt,UnsignedShort5551Type as zt,UnsignedInt5999Type as Ht,AlphaFormat as $t,RGB_S3TC_DXT1_Format as Wt,RGBA_S3TC_DXT1_Format as jt,RGBA_S3TC_DXT3_Format as qt,RGBA_S3TC_DXT5_Format as Xt,RGB_PVRTC_4BPPV1_Format as Kt,RGB_PVRTC_2BPPV1_Format as Yt,RGBA_PVRTC_4BPPV1_Format as Qt,RGBA_PVRTC_2BPPV1_Format as Zt,RGB_ETC1_Format as Jt,RGB_ETC2_Format as er,RGBA_ETC2_EAC_Format as tr,RGBA_ASTC_4x4_Format as rr,RGBA_ASTC_5x4_Format as sr,RGBA_ASTC_5x5_Format as ir,RGBA_ASTC_6x5_Format as nr,RGBA_ASTC_6x6_Format as ar,RGBA_ASTC_8x5_Format as or,RGBA_ASTC_8x6_Format as ur,RGBA_ASTC_8x8_Format as lr,RGBA_ASTC_10x5_Format as dr,RGBA_ASTC_10x6_Format as cr,RGBA_ASTC_10x8_Format as hr,RGBA_ASTC_10x10_Format as pr,RGBA_ASTC_12x10_Format as gr,RGBA_ASTC_12x12_Format as mr,RGBA_BPTC_Format as fr,RED_RGTC1_Format as yr,SIGNED_RED_RGTC1_Format as br,RED_GREEN_RGTC2_Format as xr,SIGNED_RED_GREEN_RGTC2_Format as Tr,MirroredRepeatWrapping as _r,ClampToEdgeWrapping as vr,RepeatWrapping as Nr,NearestMipmapNearestFilter as Sr,NotEqualCompare as Er,GreaterCompare as wr,GreaterEqualCompare as Ar,EqualCompare as Rr,LessEqualCompare as Cr,AlwaysCompare as Mr,NeverCompare as Pr,LinearTransfer as Br,NotEqualStencilFunc as Fr,GreaterStencilFunc as Lr,GreaterEqualStencilFunc as Dr,EqualStencilFunc as Ir,LessEqualStencilFunc as Vr,LessStencilFunc as Ur,AlwaysStencilFunc as Or,NeverStencilFunc as kr,DecrementWrapStencilOp as Gr,IncrementWrapStencilOp as zr,DecrementStencilOp as Hr,IncrementStencilOp as $r,InvertStencilOp as Wr,ReplaceStencilOp as jr,ZeroStencilOp as qr,KeepStencilOp as Xr,MaxEquation as Kr,MinEquation as Yr,SpotLight as Qr,PointLight as Zr,DirectionalLight as Jr,RectAreaLight as es,AmbientLight as ts,HemisphereLight as rs,LightProbe as ss,LinearToneMapping as is,ReinhardToneMapping as ns,CineonToneMapping as as,ACESFilmicToneMapping as os,AgXToneMapping as us,NeutralToneMapping as ls,Group as ds,Loader as cs,FileLoader as hs,MaterialLoader as ps,ObjectLoader as gs}from"./three.core.min.js";export{AdditiveAnimationBlendMode,AnimationAction,AnimationClip,AnimationLoader,AnimationMixer,AnimationObjectGroup,AnimationUtils,ArcCurve,ArrowHelper,AttachedBindMode,Audio,AudioAnalyser,AudioContext,AudioListener,AudioLoader,AxesHelper,BasicDepthPacking,BatchedMesh,Bone,BooleanKeyframeTrack,Box2,Box3,Box3Helper,BoxHelper,BufferGeometryLoader,Cache,CameraHelper,CanvasTexture,CapsuleGeometry,CatmullRomCurve3,CircleGeometry,Clock,ColorKeyframeTrack,CompressedArrayTexture,CompressedCubeTexture,CompressedTexture,CompressedTextureLoader,ConeGeometry,ConstantAlphaFactor,ConstantColorFactor,Controls,CubeTextureLoader,CubicBezierCurve,CubicBezierCurve3,CubicInterpolant,CullFaceFrontBack,Curve,CurvePath,CustomToneMapping,Cylindrical,Data3DTexture,DataTextureLoader,DataUtils,DefaultLoadingManager,DetachedBindMode,DirectionalLightHelper,DiscreteInterpolant,DodecahedronGeometry,DynamicCopyUsage,DynamicReadUsage,EdgesGeometry,EllipseCurve,ExtrudeGeometry,Fog,FogExp2,GLBufferAttribute,GLSL1,GLSL3,GridHelper,HemisphereLightHelper,IcosahedronGeometry,ImageBitmapLoader,ImageLoader,ImageUtils,InstancedBufferGeometry,InstancedMesh,Int16BufferAttribute,Int32BufferAttribute,Int8BufferAttribute,Interpolant,InterpolateDiscrete,InterpolateLinear,InterpolateSmooth,InterpolationSamplingMode,InterpolationSamplingType,KeyframeTrack,LOD,LatheGeometry,Layers,Light,Line,Line3,LineCurve,LineCurve3,LineLoop,LineSegments,LinearInterpolant,LinearMipMapNearestFilter,LoaderUtils,LoadingManager,LoopOnce,LoopPingPong,LoopRepeat,MOUSE,MeshDepthMaterial,MeshDistanceMaterial,NearestMipMapLinearFilter,NearestMipMapNearestFilter,NormalAnimationBlendMode,NumberKeyframeTrack,OctahedronGeometry,OneMinusConstantAlphaFactor,OneMinusConstantColorFactor,PCFSoftShadowMap,Path,PlaneHelper,PointLightHelper,Points,PolarGridHelper,PolyhedronGeometry,PositionalAudio,PropertyBinding,PropertyMixer,QuadraticBezierCurve,QuadraticBezierCurve3,QuaternionKeyframeTrack,QuaternionLinearInterpolant,RGBADepthPacking,RGBDepthPacking,RGB_BPTC_SIGNED_Format,RGB_BPTC_UNSIGNED_Format,RGDepthPacking,RawShaderMaterial,Ray,Raycaster,RenderTarget3D,RingGeometry,ShaderMaterial,Shape,ShapeGeometry,ShapePath,ShapeUtils,Skeleton,SkeletonHelper,SkinnedMesh,Source,Spherical,SphericalHarmonics3,SplineCurve,SpotLightHelper,Sprite,StaticCopyUsage,StaticReadUsage,StereoCamera,StreamCopyUsage,StreamDrawUsage,StreamReadUsage,StringKeyframeTrack,TOUCH,TetrahedronGeometry,TextureLoader,TextureUtils,TimestampQuery,TorusGeometry,TorusKnotGeometry,Triangle,TriangleFanDrawMode,TriangleStripDrawMode,TrianglesDrawMode,TubeGeometry,Uint8BufferAttribute,Uint8ClampedBufferAttribute,Uniform,UniformsGroup,VectorKeyframeTrack,VideoFrameTexture,VideoTexture,WebGL3DRenderTarget,WebGLArrayRenderTarget,WebGLRenderTarget,WireframeGeometry,WrapAroundEnding,ZeroCurvatureEnding,ZeroSlopeEnding}from"./three.core.min.js";const ms=["alphaMap","alphaTest","anisotropy","anisotropyMap","anisotropyRotation","aoMap","aoMapIntensity","attenuationColor","attenuationDistance","bumpMap","clearcoat","clearcoatMap","clearcoatNormalMap","clearcoatNormalScale","clearcoatRoughness","color","dispersion","displacementMap","emissive","emissiveIntensity","emissiveMap","envMap","envMapIntensity","gradientMap","ior","iridescence","iridescenceIOR","iridescenceMap","iridescenceThicknessMap","lightMap","lightMapIntensity","map","matcap","metalness","metalnessMap","normalMap","normalScale","opacity","roughness","roughnessMap","sheen","sheenColor","sheenColorMap","sheenRoughnessMap","shininess","specular","specularColor","specularColorMap","specularIntensity","specularIntensityMap","specularMap","thickness","transmission","transmissionMap"];class fs{constructor(e){this.renderObjects=new WeakMap,this.hasNode=this.containsNode(e),this.hasAnimation=!0===e.object.isSkinnedMesh,this.refreshUniforms=ms,this.renderId=0}firstInitialization(e){return!1===this.renderObjects.has(e)&&(this.getRenderObjectData(e),!0)}needsVelocity(e){const t=e.getMRT();return null!==t&&t.has("velocity")}getRenderObjectData(e){let t=this.renderObjects.get(e);if(void 0===t){const{geometry:r,material:s,object:i}=e;if(t={material:this.getMaterialData(s),geometry:{id:r.id,attributes:this.getAttributesData(r.attributes),indexVersion:r.index?r.index.version:null,drawRange:{start:r.drawRange.start,count:r.drawRange.count}},worldMatrix:i.matrixWorld.clone()},i.center&&(t.center=i.center.clone()),i.morphTargetInfluences&&(t.morphTargetInfluences=i.morphTargetInfluences.slice()),null!==e.bundle&&(t.version=e.bundle.version),t.material.transmission>0){const{width:r,height:s}=e.context;t.bufferWidth=r,t.bufferHeight=s}this.renderObjects.set(e,t)}return t}getAttributesData(e){const t={};for(const r in e){const s=e[r];t[r]={version:s.version}}return t}containsNode(e){const t=e.material;for(const e in t)if(t[e]&&t[e].isNode)return!0;return null!==e.renderer.overrideNodes.modelViewMatrix||null!==e.renderer.overrideNodes.modelNormalViewMatrix}getMaterialData(e){const t={};for(const r of this.refreshUniforms){const s=e[r];null!=s&&("object"==typeof s&&void 0!==s.clone?!0===s.isTexture?t[r]={id:s.id,version:s.version}:t[r]=s.clone():t[r]=s)}return t}equals(e){const{object:t,material:r,geometry:s}=e,i=this.getRenderObjectData(e);if(!0!==i.worldMatrix.equals(t.matrixWorld))return i.worldMatrix.copy(t.matrixWorld),!1;const n=i.material;for(const e in n){const t=n[e],s=r[e];if(void 0!==t.equals){if(!1===t.equals(s))return t.copy(s),!1}else if(!0===s.isTexture){if(t.id!==s.id||t.version!==s.version)return t.id=s.id,t.version=s.version,!1}else if(t!==s)return n[e]=s,!1}if(n.transmission>0){const{width:t,height:r}=e.context;if(i.bufferWidth!==t||i.bufferHeight!==r)return i.bufferWidth=t,i.bufferHeight=r,!1}const a=i.geometry,o=s.attributes,u=a.attributes,l=Object.keys(u),d=Object.keys(o);if(a.id!==s.id)return a.id=s.id,!1;if(l.length!==d.length)return i.geometry.attributes=this.getAttributesData(o),!1;for(const e of l){const t=u[e],r=o[e];if(void 0===r)return delete u[e],!1;if(t.version!==r.version)return t.version=r.version,!1}const c=s.index,h=a.indexVersion,p=c?c.version:null;if(h!==p)return a.indexVersion=p,!1;if(a.drawRange.start!==s.drawRange.start||a.drawRange.count!==s.drawRange.count)return a.drawRange.start=s.drawRange.start,a.drawRange.count=s.drawRange.count,!1;if(i.morphTargetInfluences){let e=!1;for(let r=0;r>>16,2246822507),r^=Math.imul(s^s>>>13,3266489909),s=Math.imul(s^s>>>16,2246822507),s^=Math.imul(r^r>>>13,3266489909),4294967296*(2097151&s)+(r>>>0)}const bs=e=>ys(e),xs=e=>ys(e),Ts=(...e)=>ys(e);function _s(e,t=!1){const r=[];!0===e.isNode&&(r.push(e.id),e=e.getSelf());for(const{property:s,childNode:i}of vs(e))r.push(ys(s.slice(0,-4)),i.getCacheKey(t));return ys(r)}function*vs(e,t=!1){for(const r in e){if(!0===r.startsWith("_"))continue;const s=e[r];if(!0===Array.isArray(s))for(let e=0;ee.charCodeAt(0)).buffer}var Ds=Object.freeze({__proto__:null,arrayBufferToBase64:Fs,base64ToArrayBuffer:Ls,getByteBoundaryFromType:Cs,getCacheKey:_s,getDataFromObject:Bs,getLengthFromType:As,getMemoryLengthFromType:Rs,getNodeChildren:vs,getTypeFromLength:Es,getTypedArrayFromType:ws,getValueFromType:Ps,getValueType:Ms,hash:Ts,hashArray:xs,hashString:bs});const Is={VERTEX:"vertex",FRAGMENT:"fragment"},Vs={NONE:"none",FRAME:"frame",RENDER:"render",OBJECT:"object"},Us={BOOLEAN:"bool",INTEGER:"int",FLOAT:"float",VECTOR2:"vec2",VECTOR3:"vec3",VECTOR4:"vec4",MATRIX2:"mat2",MATRIX3:"mat3",MATRIX4:"mat4"},Os={READ_ONLY:"readOnly",WRITE_ONLY:"writeOnly",READ_WRITE:"readWrite"},ks=["fragment","vertex"],Gs=["setup","analyze","generate"],zs=[...ks,"compute"],Hs=["x","y","z","w"],$s={analyze:"setup",generate:"analyze"};let Ws=0;class js extends o{static get type(){return"Node"}constructor(e=null){super(),this.nodeType=e,this.updateType=Vs.NONE,this.updateBeforeType=Vs.NONE,this.updateAfterType=Vs.NONE,this.uuid=u.generateUUID(),this.version=0,this.global=!1,this.parents=!1,this.isNode=!0,this._cacheKey=null,this._cacheKeyVersion=0,Object.defineProperty(this,"id",{value:Ws++})}set needsUpdate(e){!0===e&&this.version++}get type(){return this.constructor.type}onUpdate(e,t){return this.updateType=t,this.update=e.bind(this.getSelf()),this}onFrameUpdate(e){return this.onUpdate(e,Vs.FRAME)}onRenderUpdate(e){return this.onUpdate(e,Vs.RENDER)}onObjectUpdate(e){return this.onUpdate(e,Vs.OBJECT)}onReference(e){return this.updateReference=e.bind(this.getSelf()),this}getSelf(){return this.self||this}updateReference(){return this}isGlobal(){return this.global}*getChildren(){for(const{childNode:e}of vs(this))yield e}dispose(){this.dispatchEvent({type:"dispose"})}traverse(e){e(this);for(const t of this.getChildren())t.traverse(e)}getCacheKey(e=!1){return!0!==(e=e||this.version!==this._cacheKeyVersion)&&null!==this._cacheKey||(this._cacheKey=Ts(_s(this,e),this.customCacheKey()),this._cacheKeyVersion=this.version),this._cacheKey}customCacheKey(){return 0}getScope(){return this}getHash(){return this.uuid}getUpdateType(){return this.updateType}getUpdateBeforeType(){return this.updateBeforeType}getUpdateAfterType(){return this.updateAfterType}getElementType(e){const t=this.getNodeType(e);return e.getElementType(t)}getMemberType(){return"void"}getNodeType(e){const t=e.getNodeProperties(this);return t.outputNode?t.outputNode.getNodeType(e):this.nodeType}getShared(e){const t=this.getHash(e);return e.getNodeFromHash(t)||this}setup(e){const t=e.getNodeProperties(this);let r=0;for(const e of this.getChildren())t["node"+r++]=e;return t.outputNode||null}analyze(e,t=null){const r=e.increaseUsage(this);if(!0===this.parents){const r=e.getDataFromNode(this,"any");r.stages=r.stages||{},r.stages[e.shaderStage]=r.stages[e.shaderStage]||[],r.stages[e.shaderStage].push(t)}if(1===r){const t=e.getNodeProperties(this);for(const r of Object.values(t))r&&!0===r.isNode&&r.build(e,this)}}generate(e,t){const{outputNode:r}=e.getNodeProperties(this);if(r&&!0===r.isNode)return r.build(e,t)}updateBefore(){console.warn("Abstract function.")}updateAfter(){console.warn("Abstract function.")}update(){console.warn("Abstract function.")}build(e,t=null){const r=this.getShared(e);if(this!==r)return r.build(e,t);const s=e.getDataFromNode(this);s.buildStages=s.buildStages||{},s.buildStages[e.buildStage]=!0;const i=$s[e.buildStage];if(i&&!0!==s.buildStages[i]){const t=e.getBuildStage();e.setBuildStage(i),this.build(e),e.setBuildStage(t)}e.addNode(this),e.addChain(this);let n=null;const a=e.getBuildStage();if("setup"===a){this.updateReference(e);const t=e.getNodeProperties(this);if(!0!==t.initialized){t.initialized=!0,t.outputNode=this.setup(e)||t.outputNode||null;for(const r of Object.values(t))if(r&&!0===r.isNode){if(!0===r.parents){const t=e.getNodeProperties(r);t.parents=t.parents||[],t.parents.push(this)}r.build(e)}}n=t.outputNode}else if("analyze"===a)this.analyze(e,t);else if("generate"===a){if(1===this.generate.length){const r=this.getNodeType(e),s=e.getDataFromNode(this);n=s.snippet,void 0===n?void 0===s.generated?(s.generated=!0,n=this.generate(e)||"",s.snippet=n):(console.warn("THREE.Node: Recursion detected.",this),n="/* Recursion detected. */"):void 0!==s.flowCodes&&void 0!==e.context.nodeBlock&&e.addFlowCodeHierarchy(this,e.context.nodeBlock),n=e.format(n,r,t)}else n=this.generate(e,t)||""}return e.removeChain(this),e.addSequentialNode(this),n}getSerializeChildren(){return vs(this)}serialize(e){const t=this.getSerializeChildren(),r={};for(const{property:s,index:i,childNode:n}of t)void 0!==i?(void 0===r[s]&&(r[s]=Number.isInteger(i)?[]:{}),r[s][i]=n.toJSON(e.meta).uuid):r[s]=n.toJSON(e.meta).uuid;Object.keys(r).length>0&&(e.inputNodes=r)}deserialize(e){if(void 0!==e.inputNodes){const t=e.meta.nodes;for(const r in e.inputNodes)if(Array.isArray(e.inputNodes[r])){const s=[];for(const i of e.inputNodes[r])s.push(t[i]);this[r]=s}else if("object"==typeof e.inputNodes[r]){const s={};for(const i in e.inputNodes[r]){const n=e.inputNodes[r][i];s[i]=t[n]}this[r]=s}else{const s=e.inputNodes[r];this[r]=t[s]}}}toJSON(e){const{uuid:t,type:r}=this,s=void 0===e||"string"==typeof e;s&&(e={textures:{},images:{},nodes:{}});let i=e.nodes[t];function n(e){const t=[];for(const r in e){const s=e[r];delete s.metadata,t.push(s)}return t}if(void 0===i&&(i={uuid:t,type:r,meta:e,metadata:{version:4.7,type:"Node",generator:"Node.toJSON"}},!0!==s&&(e.nodes[i.uuid]=i),this.serialize(i),delete i.meta),s){const t=n(e.textures),r=n(e.images),s=n(e.nodes);t.length>0&&(i.textures=t),r.length>0&&(i.images=r),s.length>0&&(i.nodes=s)}return i}}class qs extends js{static get type(){return"ArrayElementNode"}constructor(e,t){super(),this.node=e,this.indexNode=t,this.isArrayElementNode=!0}getNodeType(e){return this.node.getElementType(e)}generate(e){const t=this.indexNode.getNodeType(e);return`${this.node.build(e)}[ ${this.indexNode.build(e,!e.isVector(t)&&e.isInteger(t)?t:"uint")} ]`}}class Xs extends js{static get type(){return"ConvertNode"}constructor(e,t){super(),this.node=e,this.convertTo=t}getNodeType(e){const t=this.node.getNodeType(e);let r=null;for(const s of this.convertTo.split("|"))null!==r&&e.getTypeLength(t)!==e.getTypeLength(s)||(r=s);return r}serialize(e){super.serialize(e),e.convertTo=this.convertTo}deserialize(e){super.deserialize(e),this.convertTo=e.convertTo}generate(e,t){const r=this.node,s=this.getNodeType(e),i=r.build(e,s);return e.format(i,s,t)}}class Ks extends js{static get type(){return"TempNode"}constructor(e=null){super(e),this.isTempNode=!0}hasDependencies(e){return e.getDataFromNode(this).usageCount>1}build(e,t){if("generate"===e.getBuildStage()){const r=e.getVectorType(this.getNodeType(e,t)),s=e.getDataFromNode(this);if(void 0!==s.propertyName)return e.format(s.propertyName,r,t);if("void"!==r&&"void"!==t&&this.hasDependencies(e)){const i=super.build(e,r),n=e.getVarFromNode(this,null,r),a=e.getPropertyName(n);return e.addLineFlowCode(`${a} = ${i}`,this),s.snippet=i,s.propertyName=a,e.format(s.propertyName,r,t)}}return super.build(e,t)}}class Ys extends Ks{static get type(){return"JoinNode"}constructor(e=[],t=null){super(t),this.nodes=e}getNodeType(e){return null!==this.nodeType?e.getVectorType(this.nodeType):e.getTypeFromLength(this.nodes.reduce((t,r)=>t+e.getTypeLength(r.getNodeType(e)),0))}generate(e,t){const r=this.getNodeType(e),s=e.getTypeLength(r),i=this.nodes,n=e.getComponentType(r),a=[];let o=0;for(const t of i){if(o>=s){console.error(`THREE.TSL: Length of parameters exceeds maximum length of function '${r}()' type.`);break}let i,u=t.getNodeType(e),l=e.getTypeLength(u);o+l>s&&(console.error(`THREE.TSL: Length of '${r}()' data exceeds maximum length of output type.`),l=s-o,u=e.getTypeFromLength(l)),o+=l,i=t.build(e,u);const d=e.getComponentType(u);d!==n&&(i=e.format(i,d,n)),a.push(i)}const u=`${e.getType(r)}( ${a.join(", ")} )`;return e.format(u,r,t)}}const Qs=Hs.join("");class Zs extends js{static get type(){return"SplitNode"}constructor(e,t="x"){super(),this.node=e,this.components=t,this.isSplitNode=!0}getVectorLength(){let e=this.components.length;for(const t of this.components)e=Math.max(Hs.indexOf(t)+1,e);return e}getComponentType(e){return e.getComponentType(this.node.getNodeType(e))}getNodeType(e){return e.getTypeFromLength(this.components.length,this.getComponentType(e))}generate(e,t){const r=this.node,s=e.getTypeLength(r.getNodeType(e));let i=null;if(s>1){let n=null;this.getVectorLength()>=s&&(n=e.getTypeFromLength(this.getVectorLength(),this.getComponentType(e)));const a=r.build(e,n);i=this.components.length===s&&this.components===Qs.slice(0,this.components.length)?e.format(a,n,t):e.format(`${a}.${this.components}`,this.getNodeType(e),t)}else i=r.build(e,t);return i}serialize(e){super.serialize(e),e.components=this.components}deserialize(e){super.deserialize(e),this.components=e.components}}class Js extends Ks{static get type(){return"SetNode"}constructor(e,t,r){super(),this.sourceNode=e,this.components=t,this.targetNode=r}getNodeType(e){return this.sourceNode.getNodeType(e)}generate(e){const{sourceNode:t,components:r,targetNode:s}=this,i=this.getNodeType(e),n=e.getComponentType(s.getNodeType(e)),a=e.getTypeFromLength(r.length,n),o=s.build(e,a),u=t.build(e,i),l=e.getTypeLength(i),d=[];for(let e=0;ee.replace(/r|s/g,"x").replace(/g|t/g,"y").replace(/b|p/g,"z").replace(/a|q/g,"w"),li=e=>ui(e).split("").sort().join(""),di={setup(e,t){const r=t.shift();return e(Di(r),...t)},get(e,t,r){if("string"==typeof t&&void 0===e[t]){if(!0!==e.isStackNode&&"assign"===t)return(...e)=>(ni.assign(r,...e),r);if(ai.has(t)){const s=ai.get(t);return e.isStackNode?(...e)=>r.add(s(...e)):(...e)=>s(r,...e)}if("self"===t)return e;if(t.endsWith("Assign")&&ai.has(t.slice(0,t.length-6))){const s=ai.get(t.slice(0,t.length-6));return e.isStackNode?(...e)=>r.assign(e[0],s(...e)):(...e)=>r.assign(s(r,...e))}if(!0===/^[xyzwrgbastpq]{1,4}$/.test(t))return t=ui(t),Li(new Zs(r,t));if(!0===/^set[XYZWRGBASTPQ]{1,4}$/.test(t))return t=li(t.slice(3).toLowerCase()),r=>Li(new Js(e,t,Li(r)));if(!0===/^flip[XYZWRGBASTPQ]{1,4}$/.test(t))return t=li(t.slice(4).toLowerCase()),()=>Li(new ei(Li(e),t));if("width"===t||"height"===t||"depth"===t)return"width"===t?t="x":"height"===t?t="y":"depth"===t&&(t="z"),Li(new Zs(e,t));if(!0===/^\d+$/.test(t))return Li(new qs(r,new si(Number(t),"uint")));if(!0===/^get$/.test(t))return e=>Li(new ii(r,e))}return Reflect.get(e,t,r)},set:(e,t,r,s)=>"string"!=typeof t||void 0!==e[t]||!0!==/^[xyzwrgbastpq]{1,4}$/.test(t)&&"width"!==t&&"height"!==t&&"depth"!==t&&!0!==/^\d+$/.test(t)?Reflect.set(e,t,r,s):(s[t].assign(r),!0)},ci=new WeakMap,hi=new WeakMap,pi=function(e,t=null){for(const r in e)e[r]=Li(e[r],t);return e},gi=function(e,t=null){const r=e.length;for(let s=0;sLi(null!==s?Object.assign(e,s):e);let n,a,o,u=t;function l(t){let r;return r=u?/[a-z]/i.test(u)?u+"()":u:e.type,void 0!==a&&t.lengtho?(console.error(`THREE.TSL: "${r}" parameter length exceeds limit.`),t.slice(0,o)):t}return null===t?n=(...t)=>i(new e(...Ii(l(t)))):null!==r?(r=Li(r),n=(...s)=>i(new e(t,...Ii(l(s)),r))):n=(...r)=>i(new e(t,...Ii(l(r)))),n.setParameterLength=(...e)=>(1===e.length?a=o=e[0]:2===e.length&&([a,o]=e),n),n.setName=e=>(u=e,n),n},fi=function(e,...t){return Li(new e(...Ii(t)))};class yi extends js{constructor(e,t){super(),this.shaderNode=e,this.inputNodes=t,this.isShaderCallNodeInternal=!0}getNodeType(e){return this.shaderNode.nodeType||this.getOutputNode(e).getNodeType(e)}getMemberType(e,t){return this.getOutputNode(e).getMemberType(e,t)}call(e){const{shaderNode:t,inputNodes:r}=this,s=e.getNodeProperties(t),i=e.getClosestSubBuild(t.subBuilds)||"",n=i||"default";if(s[n])return s[n];const a=e.subBuildFn;e.subBuildFn=i;let o=null;if(t.layout){let s=hi.get(e.constructor);void 0===s&&(s=new WeakMap,hi.set(e.constructor,s));let i=s.get(t);void 0===i&&(i=Li(e.buildFunctionNode(t)),s.set(t,i)),e.addInclude(i),o=Li(i.call(r))}else{const s=t.jsFunc,i=null!==r||s.length>1?s(r||[],e):s(e);o=Li(i)}return e.subBuildFn=a,t.once&&(s[n]=o),o}setupOutput(e){return e.addStack(),e.stack.outputNode=this.call(e),e.removeStack()}getOutputNode(e){const t=e.getNodeProperties(this),r=e.getSubBuildOutput(this);return t[r]=t[r]||this.setupOutput(e),t[r].subBuild=e.getClosestSubBuild(this),t[r]}build(e,t=null){let r=null;const s=e.getBuildStage(),i=e.getNodeProperties(this),n=e.getSubBuildOutput(this),a=this.getOutputNode(e);if("setup"===s){const t=e.getSubBuildProperty("initialized",this);if(!0!==i[t]&&(i[t]=!0,i[n]=this.getOutputNode(e),i[n].build(e),this.shaderNode.subBuilds))for(const t of e.chaining){const r=e.getDataFromNode(t,"any");r.subBuilds=r.subBuilds||new Set;for(const e of this.shaderNode.subBuilds)r.subBuilds.add(e)}r=i[n]}else"analyze"===s?a.build(e,t):"generate"===s&&(r=a.build(e,t)||"");return r}}class bi extends js{constructor(e,t){super(t),this.jsFunc=e,this.layout=null,this.global=!0,this.once=!1}setLayout(e){return this.layout=e,this}call(e=null){return Di(e),Li(new yi(this,e))}setup(){return this.call()}}const xi=[!1,!0],Ti=[0,1,2,3],_i=[-1,-2],vi=[.5,1.5,1/3,1e-6,1e6,Math.PI,2*Math.PI,1/Math.PI,2/Math.PI,1/(2*Math.PI),Math.PI/2],Ni=new Map;for(const e of xi)Ni.set(e,new si(e));const Si=new Map;for(const e of Ti)Si.set(e,new si(e,"uint"));const Ei=new Map([...Si].map(e=>new si(e.value,"int")));for(const e of _i)Ei.set(e,new si(e,"int"));const wi=new Map([...Ei].map(e=>new si(e.value)));for(const e of vi)wi.set(e,new si(e));for(const e of vi)wi.set(-e,new si(-e));const Ai={bool:Ni,uint:Si,ints:Ei,float:wi},Ri=new Map([...Ni,...wi]),Ci=(e,t)=>Ri.has(e)?Ri.get(e):!0===e.isNode?e:new si(e,t),Mi=function(e,t=null){return(...r)=>{if((0===r.length||!["bool","float","int","uint"].includes(e)&&r.every(e=>"object"!=typeof e))&&(r=[Ps(e,...r)]),1===r.length&&null!==t&&t.has(r[0]))return Li(t.get(r[0]));if(1===r.length){const t=Ci(r[0],e);return t.nodeType===e?Li(t):Li(new Xs(t,e))}const s=r.map(e=>Ci(e));return Li(new Ys(s,e))}},Pi=e=>"object"==typeof e&&null!==e?e.value:e,Bi=e=>null!=e?e.nodeType||e.convertTo||("string"==typeof e?e:null):null;function Fi(e,t){return new Proxy(new bi(e,t),di)}const Li=(e,t=null)=>function(e,t=null){const r=Ms(e);if("node"===r){let t=ci.get(e);return void 0===t&&(t=new Proxy(e,di),ci.set(e,t),ci.set(t,t)),t}return null===t&&("float"===r||"boolean"===r)||r&&"shader"!==r&&"string"!==r?Li(Ci(e,t)):"shader"===r?e.isFn?e:ki(e):e}(e,t),Di=(e,t=null)=>new pi(e,t),Ii=(e,t=null)=>new gi(e,t),Vi=(...e)=>new mi(...e),Ui=(...e)=>new fi(...e);let Oi=0;const ki=(e,t=null)=>{let r=null;null!==t&&("object"==typeof t?r=t.return:("string"==typeof t?r=t:console.error("THREE.TSL: Invalid layout type."),t=null));const s=new Fi(e,r),i=(...e)=>{let t;Di(e);t=e[0]&&(e[0].isNode||Object.getPrototypeOf(e[0])!==Object.prototype)?[...e]:e[0];const i=s.call(t);return"void"===r&&i.toStack(),i};if(i.shaderNode=s,i.id=s.id,i.isFn=!0,i.getNodeType=(...e)=>s.getNodeType(...e),i.getCacheKey=(...e)=>s.getCacheKey(...e),i.setLayout=e=>(s.setLayout(e),i),i.once=(e=null)=>(s.once=!0,s.subBuilds=e,i),null!==t){if("object"!=typeof t.inputs){const e={name:"fn"+Oi++,type:r,inputs:[]};for(const r in t)"return"!==r&&e.inputs.push({name:r,type:t[r]});t=e}i.setLayout(t)}return i},Gi=e=>{ni=e},zi=()=>ni,Hi=(...e)=>ni.If(...e);function $i(e){return ni&&ni.add(e),e}oi("toStack",$i);const Wi=new Mi("color"),ji=new Mi("float",Ai.float),qi=new Mi("int",Ai.ints),Xi=new Mi("uint",Ai.uint),Ki=new Mi("bool",Ai.bool),Yi=new Mi("vec2"),Qi=new Mi("ivec2"),Zi=new Mi("uvec2"),Ji=new Mi("bvec2"),en=new Mi("vec3"),tn=new Mi("ivec3"),rn=new Mi("uvec3"),sn=new Mi("bvec3"),nn=new Mi("vec4"),an=new Mi("ivec4"),on=new Mi("uvec4"),un=new Mi("bvec4"),ln=new Mi("mat2"),dn=new Mi("mat3"),cn=new Mi("mat4");oi("toColor",Wi),oi("toFloat",ji),oi("toInt",qi),oi("toUint",Xi),oi("toBool",Ki),oi("toVec2",Yi),oi("toIVec2",Qi),oi("toUVec2",Zi),oi("toBVec2",Ji),oi("toVec3",en),oi("toIVec3",tn),oi("toUVec3",rn),oi("toBVec3",sn),oi("toVec4",nn),oi("toIVec4",an),oi("toUVec4",on),oi("toBVec4",un),oi("toMat2",ln),oi("toMat3",dn),oi("toMat4",cn);const hn=Vi(qs).setParameterLength(2),pn=(e,t)=>Li(new Xs(Li(e),t));oi("element",hn),oi("convert",pn);oi("append",e=>(console.warn("THREE.TSL: .append() has been renamed to .toStack()."),$i(e)));class gn extends js{static get type(){return"PropertyNode"}constructor(e,t=null,r=!1){super(e),this.name=t,this.varying=r,this.isPropertyNode=!0,this.global=!0}getHash(e){return this.name||super.getHash(e)}generate(e){let t;return!0===this.varying?(t=e.getVaryingFromNode(this,this.name),t.needsInterpolation=!0):t=e.getVarFromNode(this,this.name),e.getPropertyName(t)}}const mn=(e,t)=>Li(new gn(e,t)),fn=(e,t)=>Li(new gn(e,t,!0)),yn=Ui(gn,"vec4","DiffuseColor"),bn=Ui(gn,"vec3","EmissiveColor"),xn=Ui(gn,"float","Roughness"),Tn=Ui(gn,"float","Metalness"),_n=Ui(gn,"float","Clearcoat"),vn=Ui(gn,"float","ClearcoatRoughness"),Nn=Ui(gn,"vec3","Sheen"),Sn=Ui(gn,"float","SheenRoughness"),En=Ui(gn,"float","Iridescence"),wn=Ui(gn,"float","IridescenceIOR"),An=Ui(gn,"float","IridescenceThickness"),Rn=Ui(gn,"float","AlphaT"),Cn=Ui(gn,"float","Anisotropy"),Mn=Ui(gn,"vec3","AnisotropyT"),Pn=Ui(gn,"vec3","AnisotropyB"),Bn=Ui(gn,"color","SpecularColor"),Fn=Ui(gn,"float","SpecularF90"),Ln=Ui(gn,"float","Shininess"),Dn=Ui(gn,"vec4","Output"),In=Ui(gn,"float","dashSize"),Vn=Ui(gn,"float","gapSize"),Un=Ui(gn,"float","pointWidth"),On=Ui(gn,"float","IOR"),kn=Ui(gn,"float","Transmission"),Gn=Ui(gn,"float","Thickness"),zn=Ui(gn,"float","AttenuationDistance"),Hn=Ui(gn,"color","AttenuationColor"),$n=Ui(gn,"float","Dispersion");class Wn extends js{static get type(){return"UniformGroupNode"}constructor(e,t=!1,r=1){super("string"),this.name=e,this.shared=t,this.order=r,this.isUniformGroup=!0}serialize(e){super.serialize(e),e.name=this.name,e.version=this.version,e.shared=this.shared}deserialize(e){super.deserialize(e),this.name=e.name,this.version=e.version,this.shared=e.shared}}const jn=e=>new Wn(e),qn=(e,t=0)=>new Wn(e,!0,t),Xn=qn("frame"),Kn=qn("render"),Yn=jn("object");class Qn extends ti{static get type(){return"UniformNode"}constructor(e,t=null){super(e,t),this.isUniformNode=!0,this.name="",this.groupNode=Yn}label(e){return this.name=e,this}setGroup(e){return this.groupNode=e,this}getGroup(){return this.groupNode}getUniformHash(e){return this.getHash(e)}onUpdate(e,t){const r=this.getSelf();return e=e.bind(r),super.onUpdate(t=>{const s=e(t,r);void 0!==s&&(this.value=s)},t)}generate(e,t){const r=this.getNodeType(e),s=this.getUniformHash(e);let i=e.getNodeFromHash(s);void 0===i&&(e.setHashNode(this,s),i=this);const n=i.getInputType(e),a=e.getUniformFromNode(i,n,e.shaderStage,this.name||e.context.label),o=e.getPropertyName(a);return void 0!==e.context.label&&delete e.context.label,e.format(o,r,t)}}const Zn=(e,t)=>{const r=Bi(t||e),s=e&&!0===e.isNode?e.node&&e.node.value||e.value:e;return Li(new Qn(s,r))};class Jn extends Ks{static get type(){return"ArrayNode"}constructor(e,t,r=null){super(e),this.count=t,this.values=r,this.isArrayNode=!0}getNodeType(e){return null===this.nodeType&&(this.nodeType=this.values[0].getNodeType(e)),this.nodeType}getElementType(e){return this.getNodeType(e)}generate(e){const t=this.getNodeType(e);return e.generateArray(t,this.count,this.values)}}const ea=(...e)=>{let t;if(1===e.length){const r=e[0];t=new Jn(null,r.length,r)}else{const r=e[0],s=e[1];t=new Jn(r,s)}return Li(t)};oi("toArray",(e,t)=>ea(Array(t).fill(e)));class ta extends Ks{static get type(){return"AssignNode"}constructor(e,t){super(),this.targetNode=e,this.sourceNode=t,this.isAssignNode=!0}hasDependencies(){return!1}getNodeType(e,t){return"void"!==t?this.targetNode.getNodeType(e):"void"}needsSplitAssign(e){const{targetNode:t}=this;if(!1===e.isAvailable("swizzleAssign")&&t.isSplitNode&&t.components.length>1){const r=e.getTypeLength(t.node.getNodeType(e));return Hs.join("").slice(0,r)!==t.components}return!1}setup(e){const{targetNode:t,sourceNode:r}=this,s=e.getNodeProperties(this);s.sourceNode=r,s.targetNode=t.context({assign:!0})}generate(e,t){const{targetNode:r,sourceNode:s}=e.getNodeProperties(this),i=this.needsSplitAssign(e),n=r.getNodeType(e),a=r.build(e),o=s.build(e,n),u=s.getNodeType(e),l=e.getDataFromNode(this);let d;if(!0===l.initialized)"void"!==t&&(d=a);else if(i){const s=e.getVarFromNode(this,null,n),i=e.getPropertyName(s);e.addLineFlowCode(`${i} = ${o}`,this);const u=r.node,l=u.node.context({assign:!0}).build(e);for(let t=0;t{const s=r.type;let i;return i="pointer"===s?"&"+t.build(e):t.build(e,s),i};if(Array.isArray(i)){if(i.length>s.length)console.error("THREE.TSL: The number of provided parameters exceeds the expected number of inputs in 'Fn()'."),i.length=s.length;else if(i.length(t=t.length>1||t[0]&&!0===t[0].isNode?Ii(t):Di(t[0]),Li(new sa(Li(e),t)));oi("call",ia);const na={"==":"equal","!=":"notEqual","<":"lessThan",">":"greaterThan","<=":"lessThanEqual",">=":"greaterThanEqual","%":"mod"};class aa extends Ks{static get type(){return"OperatorNode"}constructor(e,t,r,...s){if(super(),s.length>0){let i=new aa(e,t,r);for(let t=0;t>"===t||"<<"===t)return e.getIntegerType(i);if("!"===t||"&&"===t||"||"===t||"^^"===t)return"bool";if("=="===t||"!="===t||"<"===t||">"===t||"<="===t||">="===t){const t=Math.max(e.getTypeLength(i),e.getTypeLength(n));return t>1?`bvec${t}`:"bool"}if(e.isMatrix(i)){if("float"===n)return i;if(e.isVector(n))return e.getVectorFromMatrix(i);if(e.isMatrix(n))return i}else if(e.isMatrix(n)){if("float"===i)return n;if(e.isVector(i))return e.getVectorFromMatrix(n)}return e.getTypeLength(n)>e.getTypeLength(i)?n:i}generate(e,t){const r=this.op,{aNode:s,bNode:i}=this,n=this.getNodeType(e);let a=null,o=null;"void"!==n?(a=s.getNodeType(e),o=i?i.getNodeType(e):null,"<"===r||">"===r||"<="===r||">="===r||"=="===r||"!="===r?e.isVector(a)?o=a:e.isVector(o)?a=o:a!==o&&(a=o="float"):">>"===r||"<<"===r?(a=n,o=e.changeComponentType(o,"uint")):"%"===r?(a=n,o=e.isInteger(a)&&e.isInteger(o)?o:a):e.isMatrix(a)?"float"===o?o="float":e.isVector(o)?o=e.getVectorFromMatrix(a):e.isMatrix(o)||(a=o=n):a=e.isMatrix(o)?"float"===a?"float":e.isVector(a)?e.getVectorFromMatrix(o):o=n:o=n):a=o=n;const u=s.build(e,a),d=i?i.build(e,o):null,c=e.getFunctionOperator(r);if("void"!==t){const s=e.renderer.coordinateSystem===l;if("=="===r||"!="===r||"<"===r||">"===r||"<="===r||">="===r)return s&&e.isVector(a)?e.format(`${this.getOperatorMethod(e,t)}( ${u}, ${d} )`,n,t):e.format(`( ${u} ${r} ${d} )`,n,t);if("%"===r)return e.isInteger(o)?e.format(`( ${u} % ${d} )`,n,t):e.format(`${this.getOperatorMethod(e,n)}( ${u}, ${d} )`,n,t);if("!"===r||"~"===r)return e.format(`(${r}${u})`,a,t);if(c)return e.format(`${c}( ${u}, ${d} )`,n,t);if(e.isMatrix(a)&&"float"===o)return e.format(`( ${d} ${r} ${u} )`,n,t);if("float"===a&&e.isMatrix(o))return e.format(`${u} ${r} ${d}`,n,t);{let i=`( ${u} ${r} ${d} )`;return!s&&"bool"===n&&e.isVector(a)&&e.isVector(o)&&(i=`all${i}`),e.format(i,n,t)}}if("void"!==a)return c?e.format(`${c}( ${u}, ${d} )`,n,t):e.isMatrix(a)&&"float"===o?e.format(`${d} ${r} ${u}`,n,t):e.format(`${u} ${r} ${d}`,n,t)}serialize(e){super.serialize(e),e.op=this.op}deserialize(e){super.deserialize(e),this.op=e.op}}const oa=Vi(aa,"+").setParameterLength(2,1/0).setName("add"),ua=Vi(aa,"-").setParameterLength(2,1/0).setName("sub"),la=Vi(aa,"*").setParameterLength(2,1/0).setName("mul"),da=Vi(aa,"/").setParameterLength(2,1/0).setName("div"),ca=Vi(aa,"%").setParameterLength(2).setName("mod"),ha=Vi(aa,"==").setParameterLength(2).setName("equal"),pa=Vi(aa,"!=").setParameterLength(2).setName("notEqual"),ga=Vi(aa,"<").setParameterLength(2).setName("lessThan"),ma=Vi(aa,">").setParameterLength(2).setName("greaterThan"),fa=Vi(aa,"<=").setParameterLength(2).setName("lessThanEqual"),ya=Vi(aa,">=").setParameterLength(2).setName("greaterThanEqual"),ba=Vi(aa,"&&").setParameterLength(2,1/0).setName("and"),xa=Vi(aa,"||").setParameterLength(2,1/0).setName("or"),Ta=Vi(aa,"!").setParameterLength(1).setName("not"),_a=Vi(aa,"^^").setParameterLength(2).setName("xor"),va=Vi(aa,"&").setParameterLength(2).setName("bitAnd"),Na=Vi(aa,"~").setParameterLength(2).setName("bitNot"),Sa=Vi(aa,"|").setParameterLength(2).setName("bitOr"),Ea=Vi(aa,"^").setParameterLength(2).setName("bitXor"),wa=Vi(aa,"<<").setParameterLength(2).setName("shiftLeft"),Aa=Vi(aa,">>").setParameterLength(2).setName("shiftRight"),Ra=ki(([e])=>(e.addAssign(1),e)),Ca=ki(([e])=>(e.subAssign(1),e)),Ma=ki(([e])=>{const t=qi(e).toConst();return e.addAssign(1),t}),Pa=ki(([e])=>{const t=qi(e).toConst();return e.subAssign(1),t});oi("add",oa),oi("sub",ua),oi("mul",la),oi("div",da),oi("mod",ca),oi("equal",ha),oi("notEqual",pa),oi("lessThan",ga),oi("greaterThan",ma),oi("lessThanEqual",fa),oi("greaterThanEqual",ya),oi("and",ba),oi("or",xa),oi("not",Ta),oi("xor",_a),oi("bitAnd",va),oi("bitNot",Na),oi("bitOr",Sa),oi("bitXor",Ea),oi("shiftLeft",wa),oi("shiftRight",Aa),oi("incrementBefore",Ra),oi("decrementBefore",Ca),oi("increment",Ma),oi("decrement",Pa);const Ba=(e,t)=>(console.warn('THREE.TSL: "modInt()" is deprecated. Use "mod( int( ... ) )" instead.'),ca(qi(e),qi(t)));oi("modInt",Ba);class Fa extends Ks{static get type(){return"MathNode"}constructor(e,t,r=null,s=null){if(super(),(e===Fa.MAX||e===Fa.MIN)&&arguments.length>3){let i=new Fa(e,t,r);for(let t=2;tn&&i>a?t:n>a?r:a>i?s:t}getNodeType(e){const t=this.method;return t===Fa.LENGTH||t===Fa.DISTANCE||t===Fa.DOT?"float":t===Fa.CROSS?"vec3":t===Fa.ALL||t===Fa.ANY?"bool":t===Fa.EQUALS?e.changeComponentType(this.aNode.getNodeType(e),"bool"):this.getInputType(e)}setup(e){const{aNode:t,bNode:r,method:s}=this;let i=null;if(s===Fa.ONE_MINUS)i=ua(1,t);else if(s===Fa.RECIPROCAL)i=da(1,t);else if(s===Fa.DIFFERENCE)i=io(ua(t,r));else if(s===Fa.TRANSFORM_DIRECTION){let s=t,n=r;e.isMatrix(s.getNodeType(e))?n=nn(en(n),0):s=nn(en(s),0);const a=la(s,n).xyz;i=Ya(a)}return null!==i?i:super.setup(e)}generate(e,t){if(e.getNodeProperties(this).outputNode)return super.generate(e,t);let r=this.method;const s=this.getNodeType(e),i=this.getInputType(e),n=this.aNode,a=this.bNode,o=this.cNode,u=e.renderer.coordinateSystem;if(r===Fa.NEGATE)return e.format("( - "+n.build(e,i)+" )",s,t);{const c=[];return r===Fa.CROSS?c.push(n.build(e,s),a.build(e,s)):u===l&&r===Fa.STEP?c.push(n.build(e,1===e.getTypeLength(n.getNodeType(e))?"float":i),a.build(e,i)):u!==l||r!==Fa.MIN&&r!==Fa.MAX?r===Fa.REFRACT?c.push(n.build(e,i),a.build(e,i),o.build(e,"float")):r===Fa.MIX?c.push(n.build(e,i),a.build(e,i),o.build(e,1===e.getTypeLength(o.getNodeType(e))?"float":i)):(u===d&&r===Fa.ATAN&&null!==a&&(r="atan2"),"fragment"===e.shaderStage||r!==Fa.DFDX&&r!==Fa.DFDY||(console.warn(`THREE.TSL: '${r}' is not supported in the ${e.shaderStage} stage.`),r="/*"+r+"*/"),c.push(n.build(e,i)),null!==a&&c.push(a.build(e,i)),null!==o&&c.push(o.build(e,i))):c.push(n.build(e,i),a.build(e,1===e.getTypeLength(a.getNodeType(e))?"float":i)),e.format(`${e.getMethod(r,s)}( ${c.join(", ")} )`,s,t)}}serialize(e){super.serialize(e),e.method=this.method}deserialize(e){super.deserialize(e),this.method=e.method}}Fa.ALL="all",Fa.ANY="any",Fa.RADIANS="radians",Fa.DEGREES="degrees",Fa.EXP="exp",Fa.EXP2="exp2",Fa.LOG="log",Fa.LOG2="log2",Fa.SQRT="sqrt",Fa.INVERSE_SQRT="inversesqrt",Fa.FLOOR="floor",Fa.CEIL="ceil",Fa.NORMALIZE="normalize",Fa.FRACT="fract",Fa.SIN="sin",Fa.COS="cos",Fa.TAN="tan",Fa.ASIN="asin",Fa.ACOS="acos",Fa.ATAN="atan",Fa.ABS="abs",Fa.SIGN="sign",Fa.LENGTH="length",Fa.NEGATE="negate",Fa.ONE_MINUS="oneMinus",Fa.DFDX="dFdx",Fa.DFDY="dFdy",Fa.ROUND="round",Fa.RECIPROCAL="reciprocal",Fa.TRUNC="trunc",Fa.FWIDTH="fwidth",Fa.TRANSPOSE="transpose",Fa.BITCAST="bitcast",Fa.EQUALS="equals",Fa.MIN="min",Fa.MAX="max",Fa.STEP="step",Fa.REFLECT="reflect",Fa.DISTANCE="distance",Fa.DIFFERENCE="difference",Fa.DOT="dot",Fa.CROSS="cross",Fa.POW="pow",Fa.TRANSFORM_DIRECTION="transformDirection",Fa.MIX="mix",Fa.CLAMP="clamp",Fa.REFRACT="refract",Fa.SMOOTHSTEP="smoothstep",Fa.FACEFORWARD="faceforward";const La=ji(1e-6),Da=ji(1e6),Ia=ji(Math.PI),Va=ji(2*Math.PI),Ua=Vi(Fa,Fa.ALL).setParameterLength(1),Oa=Vi(Fa,Fa.ANY).setParameterLength(1),ka=Vi(Fa,Fa.RADIANS).setParameterLength(1),Ga=Vi(Fa,Fa.DEGREES).setParameterLength(1),za=Vi(Fa,Fa.EXP).setParameterLength(1),Ha=Vi(Fa,Fa.EXP2).setParameterLength(1),$a=Vi(Fa,Fa.LOG).setParameterLength(1),Wa=Vi(Fa,Fa.LOG2).setParameterLength(1),ja=Vi(Fa,Fa.SQRT).setParameterLength(1),qa=Vi(Fa,Fa.INVERSE_SQRT).setParameterLength(1),Xa=Vi(Fa,Fa.FLOOR).setParameterLength(1),Ka=Vi(Fa,Fa.CEIL).setParameterLength(1),Ya=Vi(Fa,Fa.NORMALIZE).setParameterLength(1),Qa=Vi(Fa,Fa.FRACT).setParameterLength(1),Za=Vi(Fa,Fa.SIN).setParameterLength(1),Ja=Vi(Fa,Fa.COS).setParameterLength(1),eo=Vi(Fa,Fa.TAN).setParameterLength(1),to=Vi(Fa,Fa.ASIN).setParameterLength(1),ro=Vi(Fa,Fa.ACOS).setParameterLength(1),so=Vi(Fa,Fa.ATAN).setParameterLength(1,2),io=Vi(Fa,Fa.ABS).setParameterLength(1),no=Vi(Fa,Fa.SIGN).setParameterLength(1),ao=Vi(Fa,Fa.LENGTH).setParameterLength(1),oo=Vi(Fa,Fa.NEGATE).setParameterLength(1),uo=Vi(Fa,Fa.ONE_MINUS).setParameterLength(1),lo=Vi(Fa,Fa.DFDX).setParameterLength(1),co=Vi(Fa,Fa.DFDY).setParameterLength(1),ho=Vi(Fa,Fa.ROUND).setParameterLength(1),po=Vi(Fa,Fa.RECIPROCAL).setParameterLength(1),go=Vi(Fa,Fa.TRUNC).setParameterLength(1),mo=Vi(Fa,Fa.FWIDTH).setParameterLength(1),fo=Vi(Fa,Fa.TRANSPOSE).setParameterLength(1),yo=Vi(Fa,Fa.BITCAST).setParameterLength(2),bo=(e,t)=>(console.warn('THREE.TSL: "equals" is deprecated. Use "equal" inside a vector instead, like: "bvec*( equal( ... ) )"'),ha(e,t)),xo=Vi(Fa,Fa.MIN).setParameterLength(2,1/0),To=Vi(Fa,Fa.MAX).setParameterLength(2,1/0),_o=Vi(Fa,Fa.STEP).setParameterLength(2),vo=Vi(Fa,Fa.REFLECT).setParameterLength(2),No=Vi(Fa,Fa.DISTANCE).setParameterLength(2),So=Vi(Fa,Fa.DIFFERENCE).setParameterLength(2),Eo=Vi(Fa,Fa.DOT).setParameterLength(2),wo=Vi(Fa,Fa.CROSS).setParameterLength(2),Ao=Vi(Fa,Fa.POW).setParameterLength(2),Ro=Vi(Fa,Fa.POW,2).setParameterLength(1),Co=Vi(Fa,Fa.POW,3).setParameterLength(1),Mo=Vi(Fa,Fa.POW,4).setParameterLength(1),Po=Vi(Fa,Fa.TRANSFORM_DIRECTION).setParameterLength(2),Bo=e=>la(no(e),Ao(io(e),1/3)),Fo=e=>Eo(e,e),Lo=Vi(Fa,Fa.MIX).setParameterLength(3),Do=(e,t=0,r=1)=>Li(new Fa(Fa.CLAMP,Li(e),Li(t),Li(r))),Io=e=>Do(e),Vo=Vi(Fa,Fa.REFRACT).setParameterLength(3),Uo=Vi(Fa,Fa.SMOOTHSTEP).setParameterLength(3),Oo=Vi(Fa,Fa.FACEFORWARD).setParameterLength(3),ko=ki(([e])=>{const t=Eo(e.xy,Yi(12.9898,78.233)),r=ca(t,Ia);return Qa(Za(r).mul(43758.5453))}),Go=(e,t,r)=>Lo(t,r,e),zo=(e,t,r)=>Uo(t,r,e),Ho=(e,t)=>_o(t,e),$o=(e,t)=>(console.warn('THREE.TSL: "atan2" is overloaded. Use "atan" instead.'),so(e,t)),Wo=Oo,jo=qa;oi("all",Ua),oi("any",Oa),oi("equals",bo),oi("radians",ka),oi("degrees",Ga),oi("exp",za),oi("exp2",Ha),oi("log",$a),oi("log2",Wa),oi("sqrt",ja),oi("inverseSqrt",qa),oi("floor",Xa),oi("ceil",Ka),oi("normalize",Ya),oi("fract",Qa),oi("sin",Za),oi("cos",Ja),oi("tan",eo),oi("asin",to),oi("acos",ro),oi("atan",so),oi("abs",io),oi("sign",no),oi("length",ao),oi("lengthSq",Fo),oi("negate",oo),oi("oneMinus",uo),oi("dFdx",lo),oi("dFdy",co),oi("round",ho),oi("reciprocal",po),oi("trunc",go),oi("fwidth",mo),oi("atan2",$o),oi("min",xo),oi("max",To),oi("step",Ho),oi("reflect",vo),oi("distance",No),oi("dot",Eo),oi("cross",wo),oi("pow",Ao),oi("pow2",Ro),oi("pow3",Co),oi("pow4",Mo),oi("transformDirection",Po),oi("mix",Go),oi("clamp",Do),oi("refract",Vo),oi("smoothstep",zo),oi("faceForward",Oo),oi("difference",So),oi("saturate",Io),oi("cbrt",Bo),oi("transpose",fo),oi("rand",ko);class qo extends js{static get type(){return"ConditionalNode"}constructor(e,t,r=null){super(),this.condNode=e,this.ifNode=t,this.elseNode=r}getNodeType(e){const{ifNode:t,elseNode:r}=e.getNodeProperties(this);if(void 0===t)return this.setup(e),this.getNodeType(e);const s=t.getNodeType(e);if(null!==r){const t=r.getNodeType(e);if(e.getTypeLength(t)>e.getTypeLength(s))return t}return s}setup(e){const t=this.condNode.cache(),r=this.ifNode.cache(),s=this.elseNode?this.elseNode.cache():null,i=e.context.nodeBlock;e.getDataFromNode(r).parentNodeBlock=i,null!==s&&(e.getDataFromNode(s).parentNodeBlock=i);const n=e.getNodeProperties(this);n.condNode=t,n.ifNode=r.context({nodeBlock:r}),n.elseNode=s?s.context({nodeBlock:s}):null}generate(e,t){const r=this.getNodeType(e),s=e.getDataFromNode(this);if(void 0!==s.nodeProperty)return s.nodeProperty;const{condNode:i,ifNode:n,elseNode:a}=e.getNodeProperties(this),o=e.currentFunctionNode,u="void"!==t,l=u?mn(r).build(e):"";s.nodeProperty=l;const d=i.build(e,"bool");e.addFlowCode(`\n${e.tab}if ( ${d} ) {\n\n`).addFlowTab();let c=n.build(e,r);if(c&&(u?c=l+" = "+c+";":(c="return "+c+";",null===o&&(console.warn("THREE.TSL: Return statement used in an inline 'Fn()'. Define a layout struct to allow return values."),c="// "+c))),e.removeFlowTab().addFlowCode(e.tab+"\t"+c+"\n\n"+e.tab+"}"),null!==a){e.addFlowCode(" else {\n\n").addFlowTab();let t=a.build(e,r);t&&(u?t=l+" = "+t+";":(t="return "+t+";",null===o&&(console.warn("THREE.TSL: Return statement used in an inline 'Fn()'. Define a layout struct to allow return values."),t="// "+t))),e.removeFlowTab().addFlowCode(e.tab+"\t"+t+"\n\n"+e.tab+"}\n\n")}else e.addFlowCode("\n\n");return e.format(l,r,t)}}const Xo=Vi(qo).setParameterLength(2,3);oi("select",Xo);class Ko extends js{static get type(){return"ContextNode"}constructor(e,t={}){super(),this.isContextNode=!0,this.node=e,this.value=t}getScope(){return this.node.getScope()}getNodeType(e){return this.node.getNodeType(e)}analyze(e){const t=e.getContext();e.setContext({...e.context,...this.value}),this.node.build(e),e.setContext(t)}setup(e){const t=e.getContext();e.setContext({...e.context,...this.value}),this.node.build(e),e.setContext(t)}generate(e,t){const r=e.getContext();e.setContext({...e.context,...this.value});const s=this.node.build(e,t);return e.setContext(r),s}}const Yo=Vi(Ko).setParameterLength(1,2),Qo=(e,t)=>Yo(e,{label:t});oi("context",Yo),oi("label",Qo);class Zo extends js{static get type(){return"VarNode"}constructor(e,t=null,r=!1){super(),this.node=e,this.name=t,this.global=!0,this.isVarNode=!0,this.readOnly=r,this.parents=!0}getMemberType(e,t){return this.node.getMemberType(e,t)}getElementType(e){return this.node.getElementType(e)}getNodeType(e){return this.node.getNodeType(e)}generate(e){const{node:t,name:r,readOnly:s}=this,{renderer:i}=e,n=!0===i.backend.isWebGPUBackend;let a=!1,o=!1;s&&(a=e.isDeterministic(t),o=n?s:a);const u=e.getVectorType(this.getNodeType(e)),l=t.build(e,u),d=e.getVarFromNode(this,r,u,void 0,o),c=e.getPropertyName(d);let h=c;if(o)if(n)h=a?`const ${c}`:`let ${c}`;else{const r=e.getArrayCount(t);h=`const ${e.getVar(d.type,c,r)}`}return e.addLineFlowCode(`${h} = ${l}`,this),c}}const Jo=Vi(Zo),eu=(e,t=null)=>Jo(e,t).toStack(),tu=(e,t=null)=>Jo(e,t,!0).toStack();oi("toVar",eu),oi("toConst",tu);const ru=e=>(console.warn('TSL: "temp( node )" is deprecated. Use "Var( node )" or "node.toVar()" instead.'),Jo(e));oi("temp",ru);class su extends js{static get type(){return"SubBuild"}constructor(e,t,r=null){super(r),this.node=e,this.name=t,this.isSubBuildNode=!0}getNodeType(e){if(null!==this.nodeType)return this.nodeType;e.addSubBuild(this.name);const t=this.node.getNodeType(e);return e.removeSubBuild(),t}build(e,...t){e.addSubBuild(this.name);const r=this.node.build(e,...t);return e.removeSubBuild(),r}}const iu=(e,t,r=null)=>Li(new su(Li(e),t,r));class nu extends js{static get type(){return"VaryingNode"}constructor(e,t=null){super(),this.node=e,this.name=t,this.isVaryingNode=!0,this.interpolationType=null,this.interpolationSampling=null,this.global=!0}setInterpolation(e,t=null){return this.interpolationType=e,this.interpolationSampling=t,this}getHash(e){return this.name||super.getHash(e)}getNodeType(e){return this.node.getNodeType(e)}setupVarying(e){const t=e.getNodeProperties(this);let r=t.varying;if(void 0===r){const s=this.name,i=this.getNodeType(e),n=this.interpolationType,a=this.interpolationSampling;t.varying=r=e.getVaryingFromNode(this,s,i,n,a),t.node=iu(this.node,"VERTEX")}return r.needsInterpolation||(r.needsInterpolation="fragment"===e.shaderStage),r}setup(e){this.setupVarying(e),e.flowNodeFromShaderStage(Is.VERTEX,this.node)}analyze(e){this.setupVarying(e),e.flowNodeFromShaderStage(Is.VERTEX,this.node)}generate(e){const t=e.getSubBuildProperty("property",e.currentStack),r=e.getNodeProperties(this),s=this.setupVarying(e);if(void 0===r[t]){const i=this.getNodeType(e),n=e.getPropertyName(s,Is.VERTEX);e.flowNodeFromShaderStage(Is.VERTEX,r.node,i,n),r[t]=n}return e.getPropertyName(s)}}const au=Vi(nu).setParameterLength(1,2),ou=e=>au(e);oi("toVarying",au),oi("toVertexStage",ou),oi("varying",(...e)=>(console.warn("THREE.TSL: .varying() has been renamed to .toVarying()."),au(...e))),oi("vertexStage",(...e)=>(console.warn("THREE.TSL: .vertexStage() has been renamed to .toVertexStage()."),au(...e)));const uu=ki(([e])=>{const t=e.mul(.9478672986).add(.0521327014).pow(2.4),r=e.mul(.0773993808),s=e.lessThanEqual(.04045);return Lo(t,r,s)}).setLayout({name:"sRGBTransferEOTF",type:"vec3",inputs:[{name:"color",type:"vec3"}]}),lu=ki(([e])=>{const t=e.pow(.41666).mul(1.055).sub(.055),r=e.mul(12.92),s=e.lessThanEqual(.0031308);return Lo(t,r,s)}).setLayout({name:"sRGBTransferOETF",type:"vec3",inputs:[{name:"color",type:"vec3"}]}),du="WorkingColorSpace";class cu extends Ks{static get type(){return"ColorSpaceNode"}constructor(e,t,r){super("vec4"),this.colorNode=e,this.source=t,this.target=r}resolveColorSpace(e,t){return t===du?c.workingColorSpace:"OutputColorSpace"===t?e.context.outputColorSpace||e.renderer.outputColorSpace:t}setup(e){const{colorNode:t}=this,r=this.resolveColorSpace(e,this.source),s=this.resolveColorSpace(e,this.target);let i=t;return!1!==c.enabled&&r!==s&&r&&s?(c.getTransfer(r)===h&&(i=nn(uu(i.rgb),i.a)),c.getPrimaries(r)!==c.getPrimaries(s)&&(i=nn(dn(c._getMatrix(new n,r,s)).mul(i.rgb),i.a)),c.getTransfer(s)===h&&(i=nn(lu(i.rgb),i.a)),i):i}}const hu=(e,t)=>Li(new cu(Li(e),du,t)),pu=(e,t)=>Li(new cu(Li(e),t,du));oi("workingToColorSpace",hu),oi("colorSpaceToWorking",pu);let gu=class extends qs{static get type(){return"ReferenceElementNode"}constructor(e,t){super(e,t),this.referenceNode=e,this.isReferenceElementNode=!0}getNodeType(){return this.referenceNode.uniformType}generate(e){const t=super.generate(e),r=this.referenceNode.getNodeType(),s=this.getNodeType();return e.format(t,r,s)}};class mu extends js{static get type(){return"ReferenceBaseNode"}constructor(e,t,r=null,s=null){super(),this.property=e,this.uniformType=t,this.object=r,this.count=s,this.properties=e.split("."),this.reference=r,this.node=null,this.group=null,this.updateType=Vs.OBJECT}setGroup(e){return this.group=e,this}element(e){return Li(new gu(this,Li(e)))}setNodeType(e){const t=Zn(null,e).getSelf();null!==this.group&&t.setGroup(this.group),this.node=t}getNodeType(e){return null===this.node&&(this.updateReference(e),this.updateValue()),this.node.getNodeType(e)}getValueFromReference(e=this.reference){const{properties:t}=this;let r=e[t[0]];for(let e=1;eLi(new fu(e,t,r));class bu extends Ks{static get type(){return"ToneMappingNode"}constructor(e,t=Tu,r=null){super("vec3"),this.toneMapping=e,this.exposureNode=t,this.colorNode=r}customCacheKey(){return Ts(this.toneMapping)}setup(e){const t=this.colorNode||e.context.color,r=this.toneMapping;if(r===p)return t;let s=null;const i=e.renderer.library.getToneMappingFunction(r);return null!==i?s=nn(i(t.rgb,this.exposureNode),t.a):(console.error("ToneMappingNode: Unsupported Tone Mapping configuration.",r),s=t),s}}const xu=(e,t,r)=>Li(new bu(e,Li(t),Li(r))),Tu=yu("toneMappingExposure","float");oi("toneMapping",(e,t,r)=>xu(t,r,e));class _u extends ti{static get type(){return"BufferAttributeNode"}constructor(e,t=null,r=0,s=0){super(e,t),this.isBufferNode=!0,this.bufferType=t,this.bufferStride=r,this.bufferOffset=s,this.usage=g,this.instanced=!1,this.attribute=null,this.global=!0,e&&!0===e.isBufferAttribute&&(this.attribute=e,this.usage=e.usage,this.instanced=e.isInstancedBufferAttribute)}getHash(e){if(0===this.bufferStride&&0===this.bufferOffset){let t=e.globalCache.getData(this.value);return void 0===t&&(t={node:this},e.globalCache.setData(this.value,t)),t.node.uuid}return this.uuid}getNodeType(e){return null===this.bufferType&&(this.bufferType=e.getTypeFromAttribute(this.attribute)),this.bufferType}setup(e){if(null!==this.attribute)return;const t=this.getNodeType(e),r=this.value,s=e.getTypeLength(t),i=this.bufferStride||s,n=this.bufferOffset,a=!0===r.isInterleavedBuffer?r:new m(r,i),o=new f(a,s,n);a.setUsage(this.usage),this.attribute=o,this.attribute.isInstancedBufferAttribute=this.instanced}generate(e){const t=this.getNodeType(e),r=e.getBufferAttributeFromNode(this,t),s=e.getPropertyName(r);let i=null;if("vertex"===e.shaderStage||"compute"===e.shaderStage)this.name=s,i=s;else{i=au(this).build(e,t)}return i}getInputType(){return"bufferAttribute"}setUsage(e){return this.usage=e,this.attribute&&!0===this.attribute.isBufferAttribute&&(this.attribute.usage=e),this}setInstanced(e){return this.instanced=e,this}}const vu=(e,t=null,r=0,s=0)=>Li(new _u(e,t,r,s)),Nu=(e,t=null,r=0,s=0)=>vu(e,t,r,s).setUsage(y),Su=(e,t=null,r=0,s=0)=>vu(e,t,r,s).setInstanced(!0),Eu=(e,t=null,r=0,s=0)=>Nu(e,t,r,s).setInstanced(!0);oi("toAttribute",e=>vu(e.value));class wu extends js{static get type(){return"ComputeNode"}constructor(e,t,r=[64]){super("void"),this.isComputeNode=!0,this.computeNode=e,this.count=t,this.workgroupSize=r,this.dispatchCount=0,this.version=1,this.name="",this.updateBeforeType=Vs.OBJECT,this.onInitFunction=null,this.updateDispatchCount()}dispose(){this.dispatchEvent({type:"dispose"})}label(e){return this.name=e,this}updateDispatchCount(){const{count:e,workgroupSize:t}=this;let r=t[0];for(let e=1;eLi(new wu(Li(e),t,r));oi("compute",Au);class Ru extends js{static get type(){return"CacheNode"}constructor(e,t=!0){super(),this.node=e,this.parent=t,this.isCacheNode=!0}getNodeType(e){const t=e.getCache(),r=e.getCacheFromNode(this,this.parent);e.setCache(r);const s=this.node.getNodeType(e);return e.setCache(t),s}build(e,...t){const r=e.getCache(),s=e.getCacheFromNode(this,this.parent);e.setCache(s);const i=this.node.build(e,...t);return e.setCache(r),i}}const Cu=(e,t)=>Li(new Ru(Li(e),t));oi("cache",Cu);class Mu extends js{static get type(){return"BypassNode"}constructor(e,t){super(),this.isBypassNode=!0,this.outputNode=e,this.callNode=t}getNodeType(e){return this.outputNode.getNodeType(e)}generate(e){const t=this.callNode.build(e,"void");return""!==t&&e.addLineFlowCode(t,this),this.outputNode.build(e)}}const Pu=Vi(Mu).setParameterLength(2);oi("bypass",Pu);class Bu extends js{static get type(){return"RemapNode"}constructor(e,t,r,s=ji(0),i=ji(1)){super(),this.node=e,this.inLowNode=t,this.inHighNode=r,this.outLowNode=s,this.outHighNode=i,this.doClamp=!0}setup(){const{node:e,inLowNode:t,inHighNode:r,outLowNode:s,outHighNode:i,doClamp:n}=this;let a=e.sub(t).div(r.sub(t));return!0===n&&(a=a.clamp()),a.mul(i.sub(s)).add(s)}}const Fu=Vi(Bu,null,null,{doClamp:!1}).setParameterLength(3,5),Lu=Vi(Bu).setParameterLength(3,5);oi("remap",Fu),oi("remapClamp",Lu);class Du extends js{static get type(){return"ExpressionNode"}constructor(e="",t="void"){super(t),this.snippet=e}generate(e,t){const r=this.getNodeType(e),s=this.snippet;if("void"!==r)return e.format(s,r,t);e.addLineFlowCode(s,this)}}const Iu=Vi(Du).setParameterLength(1,2),Vu=e=>(e?Xo(e,Iu("discard")):Iu("discard")).toStack();oi("discard",Vu);class Uu extends Ks{static get type(){return"RenderOutputNode"}constructor(e,t,r){super("vec4"),this.colorNode=e,this.toneMapping=t,this.outputColorSpace=r,this.isRenderOutputNode=!0}setup({context:e}){let t=this.colorNode||e.color;const r=(null!==this.toneMapping?this.toneMapping:e.toneMapping)||p,s=(null!==this.outputColorSpace?this.outputColorSpace:e.outputColorSpace)||b;return r!==p&&(t=t.toneMapping(r)),s!==b&&s!==c.workingColorSpace&&(t=t.workingToColorSpace(s)),t}}const Ou=(e,t=null,r=null)=>Li(new Uu(Li(e),t,r));oi("renderOutput",Ou);class ku extends Ks{static get type(){return"DebugNode"}constructor(e,t=null){super(),this.node=e,this.callback=t}getNodeType(e){return this.node.getNodeType(e)}setup(e){return this.node.build(e)}analyze(e){return this.node.build(e)}generate(e){const t=this.callback,r=this.node.build(e),s="--- TSL debug - "+e.shaderStage+" shader ---",i="-".repeat(s.length);let n="";return n+="// #"+s+"#\n",n+=e.flow.code.replace(/^\t/gm,"")+"\n",n+="/* ... */ "+r+" /* ... */\n",n+="// #"+i+"#\n",null!==t?t(e,n):console.log(n),r}}const Gu=(e,t=null)=>Li(new ku(Li(e),t));oi("debug",Gu);class zu extends js{static get type(){return"AttributeNode"}constructor(e,t=null){super(t),this.global=!0,this._attributeName=e}getHash(e){return this.getAttributeName(e)}getNodeType(e){let t=this.nodeType;if(null===t){const r=this.getAttributeName(e);if(e.hasGeometryAttribute(r)){const s=e.geometry.getAttribute(r);t=e.getTypeFromAttribute(s)}else t="float"}return t}setAttributeName(e){return this._attributeName=e,this}getAttributeName(){return this._attributeName}generate(e){const t=this.getAttributeName(e),r=this.getNodeType(e);if(!0===e.hasGeometryAttribute(t)){const s=e.geometry.getAttribute(t),i=e.getTypeFromAttribute(s),n=e.getAttribute(t,i);if("vertex"===e.shaderStage)return e.format(n.name,i,r);return au(this).build(e,r)}return console.warn(`AttributeNode: Vertex attribute "${t}" not found on geometry.`),e.generateConst(r)}serialize(e){super.serialize(e),e.global=this.global,e._attributeName=this._attributeName}deserialize(e){super.deserialize(e),this.global=e.global,this._attributeName=e._attributeName}}const Hu=(e,t=null)=>Li(new zu(e,t)),$u=(e=0)=>Hu("uv"+(e>0?e:""),"vec2");class Wu extends js{static get type(){return"TextureSizeNode"}constructor(e,t=null){super("uvec2"),this.isTextureSizeNode=!0,this.textureNode=e,this.levelNode=t}generate(e,t){const r=this.textureNode.build(e,"property"),s=null===this.levelNode?"0":this.levelNode.build(e,"int");return e.format(`${e.getMethod("textureDimensions")}( ${r}, ${s} )`,this.getNodeType(e),t)}}const ju=Vi(Wu).setParameterLength(1,2);class qu extends Qn{static get type(){return"MaxMipLevelNode"}constructor(e){super(0),this._textureNode=e,this.updateType=Vs.FRAME}get textureNode(){return this._textureNode}get texture(){return this._textureNode.value}update(){const e=this.texture,t=e.images,r=t&&t.length>0?t[0]&&t[0].image||t[0]:e.image;if(r&&void 0!==r.width){const{width:e,height:t}=r;this.value=Math.log2(Math.max(e,t))}}}const Xu=Vi(qu).setParameterLength(1),Ku=new x;class Yu extends Qn{static get type(){return"TextureNode"}constructor(e=Ku,t=null,r=null,s=null){super(e),this.isTextureNode=!0,this.uvNode=t,this.levelNode=r,this.biasNode=s,this.compareNode=null,this.depthNode=null,this.gradNode=null,this.sampler=!0,this.updateMatrix=!1,this.updateType=Vs.NONE,this.referenceNode=null,this._value=e,this._matrixUniform=null,this.setUpdateMatrix(null===t)}set value(e){this.referenceNode?this.referenceNode.value=e:this._value=e}get value(){return this.referenceNode?this.referenceNode.value:this._value}getUniformHash(){return this.value.uuid}getNodeType(){return!0===this.value.isDepthTexture?"float":this.value.type===T?"uvec4":this.value.type===_?"ivec4":"vec4"}getInputType(){return"texture"}getDefaultUV(){return $u(this.value.channel)}updateReference(){return this.value}getTransformedUV(e){return null===this._matrixUniform&&(this._matrixUniform=Zn(this.value.matrix)),this._matrixUniform.mul(en(e,1)).xy}setUpdateMatrix(e){return this.updateMatrix=e,this.updateType=e?Vs.OBJECT:Vs.NONE,this}setupUV(e,t){const r=this.value;return e.isFlipY()&&(r.image instanceof ImageBitmap&&!0===r.flipY||!0===r.isRenderTargetTexture||!0===r.isFramebufferTexture||!0===r.isDepthTexture)&&(t=this.sampler?t.flipY():t.setY(qi(ju(this,this.levelNode).y).sub(t.y).sub(1))),t}setup(e){const t=e.getNodeProperties(this);t.referenceNode=this.referenceNode;const r=this.value;if(!r||!0!==r.isTexture)throw new Error("THREE.TSL: `texture( value )` function expects a valid instance of THREE.Texture().");let s=this.uvNode;null!==s&&!0!==e.context.forceUVContext||!e.context.getUV||(s=e.context.getUV(this,e)),s||(s=this.getDefaultUV()),!0===this.updateMatrix&&(s=this.getTransformedUV(s)),s=this.setupUV(e,s);let i=this.levelNode;null===i&&e.context.getTextureLevel&&(i=e.context.getTextureLevel(this)),t.uvNode=s,t.levelNode=i,t.biasNode=this.biasNode,t.compareNode=this.compareNode,t.gradNode=this.gradNode,t.depthNode=this.depthNode}generateUV(e,t){return t.build(e,!0===this.sampler?"vec2":"ivec2")}generateSnippet(e,t,r,s,i,n,a,o){const u=this.value;let l;return l=s?e.generateTextureLevel(u,t,r,s,n):i?e.generateTextureBias(u,t,r,i,n):o?e.generateTextureGrad(u,t,r,o,n):a?e.generateTextureCompare(u,t,r,a,n):!1===this.sampler?e.generateTextureLoad(u,t,r,n):e.generateTexture(u,t,r,n),l}generate(e,t){const r=this.value,s=e.getNodeProperties(this),i=super.generate(e,"property");if(/^sampler/.test(t))return i+"_sampler";if(e.isReference(t))return i;{const n=e.getDataFromNode(this);let a=n.propertyName;if(void 0===a){const{uvNode:t,levelNode:r,biasNode:o,compareNode:u,depthNode:l,gradNode:d}=s,c=this.generateUV(e,t),h=r?r.build(e,"float"):null,p=o?o.build(e,"float"):null,g=l?l.build(e,"int"):null,m=u?u.build(e,"float"):null,f=d?[d[0].build(e,"vec2"),d[1].build(e,"vec2")]:null,y=e.getVarFromNode(this);a=e.getPropertyName(y);const b=this.generateSnippet(e,i,c,h,p,g,m,f);e.addLineFlowCode(`${a} = ${b}`,this),n.snippet=b,n.propertyName=a}let o=a;const u=this.getNodeType(e);return e.needsToWorkingColorSpace(r)&&(o=pu(Iu(o,u),r.colorSpace).setup(e).build(e,u)),e.format(o,u,t)}}setSampler(e){return this.sampler=e,this}getSampler(){return this.sampler}uv(e){return console.warn("THREE.TextureNode: .uv() has been renamed. Use .sample() instead."),this.sample(e)}sample(e){const t=this.clone();return t.uvNode=Li(e),t.referenceNode=this.getSelf(),Li(t)}blur(e){const t=this.clone();t.biasNode=Li(e).mul(Xu(t)),t.referenceNode=this.getSelf();const r=t.value;return!1===t.generateMipmaps&&(r&&!1===r.generateMipmaps||r.minFilter===v||r.magFilter===v)&&(console.warn("THREE.TSL: texture().blur() requires mipmaps and sampling. Use .generateMipmaps=true and .minFilter/.magFilter=THREE.LinearFilter in the Texture."),t.biasNode=null),Li(t)}level(e){const t=this.clone();return t.levelNode=Li(e),t.referenceNode=this.getSelf(),Li(t)}size(e){return ju(this,e)}bias(e){const t=this.clone();return t.biasNode=Li(e),t.referenceNode=this.getSelf(),Li(t)}compare(e){const t=this.clone();return t.compareNode=Li(e),t.referenceNode=this.getSelf(),Li(t)}grad(e,t){const r=this.clone();return r.gradNode=[Li(e),Li(t)],r.referenceNode=this.getSelf(),Li(r)}depth(e){const t=this.clone();return t.depthNode=Li(e),t.referenceNode=this.getSelf(),Li(t)}serialize(e){super.serialize(e),e.value=this.value.toJSON(e.meta).uuid,e.sampler=this.sampler,e.updateMatrix=this.updateMatrix,e.updateType=this.updateType}deserialize(e){super.deserialize(e),this.value=e.meta.textures[e.value],this.sampler=e.sampler,this.updateMatrix=e.updateMatrix,this.updateType=e.updateType}update(){const e=this.value,t=this._matrixUniform;null!==t&&(t.value=e.matrix),!0===e.matrixAutoUpdate&&e.updateMatrix()}clone(){const e=new this.constructor(this.value,this.uvNode,this.levelNode,this.biasNode);return e.sampler=this.sampler,e.depthNode=this.depthNode,e.compareNode=this.compareNode,e.gradNode=this.gradNode,e}}const Qu=Vi(Yu).setParameterLength(1,4).setName("texture"),Zu=(e=Ku,t=null,r=null,s=null)=>{let i;return e&&!0===e.isTextureNode?(i=Li(e.clone()),i.referenceNode=e.getSelf(),null!==t&&(i.uvNode=Li(t)),null!==r&&(i.levelNode=Li(r)),null!==s&&(i.biasNode=Li(s))):i=Qu(e,t,r,s),i},Ju=(...e)=>Zu(...e).setSampler(!1);class el extends Qn{static get type(){return"BufferNode"}constructor(e,t,r=0){super(e,t),this.isBufferNode=!0,this.bufferType=t,this.bufferCount=r}getElementType(e){return this.getNodeType(e)}getInputType(){return"buffer"}}const tl=(e,t,r)=>Li(new el(e,t,r));class rl extends qs{static get type(){return"UniformArrayElementNode"}constructor(e,t){super(e,t),this.isArrayBufferElementNode=!0}generate(e){const t=super.generate(e),r=this.getNodeType(),s=this.node.getPaddedType();return e.format(t,s,r)}}class sl extends el{static get type(){return"UniformArrayNode"}constructor(e,t=null){super(null),this.array=e,this.elementType=null===t?Ms(e[0]):t,this.paddedType=this.getPaddedType(),this.updateType=Vs.RENDER,this.isArrayBufferNode=!0}getNodeType(){return this.paddedType}getElementType(){return this.elementType}getPaddedType(){const e=this.elementType;let t="vec4";return"mat2"===e?t="mat2":!0===/mat/.test(e)?t="mat4":"i"===e.charAt(0)?t="ivec4":"u"===e.charAt(0)&&(t="uvec4"),t}update(){const{array:e,value:t}=this,r=this.elementType;if("float"===r||"int"===r||"uint"===r)for(let r=0;rLi(new sl(e,t));const nl=Vi(class extends js{constructor(e){super("float"),this.name=e,this.isBuiltinNode=!0}generate(){return this.name}}).setParameterLength(1),al=Zn(0,"uint").label("u_cameraIndex").setGroup(qn("cameraIndex")).toVarying("v_cameraIndex"),ol=Zn("float").label("cameraNear").setGroup(Kn).onRenderUpdate(({camera:e})=>e.near),ul=Zn("float").label("cameraFar").setGroup(Kn).onRenderUpdate(({camera:e})=>e.far),ll=ki(({camera:e})=>{let t;if(e.isArrayCamera&&e.cameras.length>0){const r=[];for(const t of e.cameras)r.push(t.projectionMatrix);t=il(r).setGroup(Kn).label("cameraProjectionMatrices").element(e.isMultiViewCamera?nl("gl_ViewID_OVR"):al).toVar("cameraProjectionMatrix")}else t=Zn("mat4").label("cameraProjectionMatrix").setGroup(Kn).onRenderUpdate(({camera:e})=>e.projectionMatrix);return t}).once()(),dl=ki(({camera:e})=>{let t;if(e.isArrayCamera&&e.cameras.length>0){const r=[];for(const t of e.cameras)r.push(t.projectionMatrixInverse);t=il(r).setGroup(Kn).label("cameraProjectionMatricesInverse").element(e.isMultiViewCamera?nl("gl_ViewID_OVR"):al).toVar("cameraProjectionMatrixInverse")}else t=Zn("mat4").label("cameraProjectionMatrixInverse").setGroup(Kn).onRenderUpdate(({camera:e})=>e.projectionMatrixInverse);return t}).once()(),cl=ki(({camera:e})=>{let t;if(e.isArrayCamera&&e.cameras.length>0){const r=[];for(const t of e.cameras)r.push(t.matrixWorldInverse);t=il(r).setGroup(Kn).label("cameraViewMatrices").element(e.isMultiViewCamera?nl("gl_ViewID_OVR"):al).toVar("cameraViewMatrix")}else t=Zn("mat4").label("cameraViewMatrix").setGroup(Kn).onRenderUpdate(({camera:e})=>e.matrixWorldInverse);return t}).once()(),hl=Zn("mat4").label("cameraWorldMatrix").setGroup(Kn).onRenderUpdate(({camera:e})=>e.matrixWorld),pl=Zn("mat3").label("cameraNormalMatrix").setGroup(Kn).onRenderUpdate(({camera:e})=>e.normalMatrix),gl=Zn(new r).label("cameraPosition").setGroup(Kn).onRenderUpdate(({camera:e},t)=>t.value.setFromMatrixPosition(e.matrixWorld)),ml=new N;class fl extends js{static get type(){return"Object3DNode"}constructor(e,t=null){super(),this.scope=e,this.object3d=t,this.updateType=Vs.OBJECT,this.uniformNode=new Qn(null)}getNodeType(){const e=this.scope;return e===fl.WORLD_MATRIX?"mat4":e===fl.POSITION||e===fl.VIEW_POSITION||e===fl.DIRECTION||e===fl.SCALE?"vec3":e===fl.RADIUS?"float":void 0}update(e){const t=this.object3d,s=this.uniformNode,i=this.scope;if(i===fl.WORLD_MATRIX)s.value=t.matrixWorld;else if(i===fl.POSITION)s.value=s.value||new r,s.value.setFromMatrixPosition(t.matrixWorld);else if(i===fl.SCALE)s.value=s.value||new r,s.value.setFromMatrixScale(t.matrixWorld);else if(i===fl.DIRECTION)s.value=s.value||new r,t.getWorldDirection(s.value);else if(i===fl.VIEW_POSITION){const i=e.camera;s.value=s.value||new r,s.value.setFromMatrixPosition(t.matrixWorld),s.value.applyMatrix4(i.matrixWorldInverse)}else if(i===fl.RADIUS){const r=e.object.geometry;null===r.boundingSphere&&r.computeBoundingSphere(),ml.copy(r.boundingSphere).applyMatrix4(t.matrixWorld),s.value=ml.radius}}generate(e){const t=this.scope;return t===fl.WORLD_MATRIX?this.uniformNode.nodeType="mat4":t===fl.POSITION||t===fl.VIEW_POSITION||t===fl.DIRECTION||t===fl.SCALE?this.uniformNode.nodeType="vec3":t===fl.RADIUS&&(this.uniformNode.nodeType="float"),this.uniformNode.build(e)}serialize(e){super.serialize(e),e.scope=this.scope}deserialize(e){super.deserialize(e),this.scope=e.scope}}fl.WORLD_MATRIX="worldMatrix",fl.POSITION="position",fl.SCALE="scale",fl.VIEW_POSITION="viewPosition",fl.DIRECTION="direction",fl.RADIUS="radius";const yl=Vi(fl,fl.DIRECTION).setParameterLength(1),bl=Vi(fl,fl.WORLD_MATRIX).setParameterLength(1),xl=Vi(fl,fl.POSITION).setParameterLength(1),Tl=Vi(fl,fl.SCALE).setParameterLength(1),_l=Vi(fl,fl.VIEW_POSITION).setParameterLength(1),vl=Vi(fl,fl.RADIUS).setParameterLength(1);class Nl extends fl{static get type(){return"ModelNode"}constructor(e){super(e)}update(e){this.object3d=e.object,super.update(e)}}const Sl=Ui(Nl,Nl.DIRECTION),El=Ui(Nl,Nl.WORLD_MATRIX),wl=Ui(Nl,Nl.POSITION),Al=Ui(Nl,Nl.SCALE),Rl=Ui(Nl,Nl.VIEW_POSITION),Cl=Ui(Nl,Nl.RADIUS),Ml=Zn(new n).onObjectUpdate(({object:e},t)=>t.value.getNormalMatrix(e.matrixWorld)),Pl=Zn(new a).onObjectUpdate(({object:e},t)=>t.value.copy(e.matrixWorld).invert()),Bl=ki(e=>e.renderer.overrideNodes.modelViewMatrix||Fl).once()().toVar("modelViewMatrix"),Fl=cl.mul(El),Ll=ki(e=>(e.context.isHighPrecisionModelViewMatrix=!0,Zn("mat4").onObjectUpdate(({object:e,camera:t})=>e.modelViewMatrix.multiplyMatrices(t.matrixWorldInverse,e.matrixWorld)))).once()().toVar("highpModelViewMatrix"),Dl=ki(e=>{const t=e.context.isHighPrecisionModelViewMatrix;return Zn("mat3").onObjectUpdate(({object:e,camera:r})=>(!0!==t&&e.modelViewMatrix.multiplyMatrices(r.matrixWorldInverse,e.matrixWorld),e.normalMatrix.getNormalMatrix(e.modelViewMatrix)))}).once()().toVar("highpModelNormalViewMatrix"),Il=Hu("position","vec3"),Vl=Il.toVarying("positionLocal"),Ul=Il.toVarying("positionPrevious"),Ol=ki(e=>El.mul(Vl).xyz.toVarying(e.getSubBuildProperty("v_positionWorld")),"vec3").once(["POSITION"])(),kl=ki(()=>Vl.transformDirection(El).toVarying("v_positionWorldDirection").normalize().toVar("positionWorldDirection"),"vec3").once(["POSITION"])(),Gl=ki(e=>e.context.setupPositionView().toVarying("v_positionView"),"vec3").once(["POSITION"])(),zl=Gl.negate().toVarying("v_positionViewDirection").normalize().toVar("positionViewDirection");class Hl extends js{static get type(){return"FrontFacingNode"}constructor(){super("bool"),this.isFrontFacingNode=!0}generate(e){if("fragment"!==e.shaderStage)return"true";const{renderer:t,material:r}=e;return t.coordinateSystem===l&&r.side===S?"false":e.getFrontFacing()}}const $l=Ui(Hl),Wl=ji($l).mul(2).sub(1),jl=ki(([e],{material:t})=>{const r=t.side;return r===S?e=e.mul(-1):r===E&&(e=e.mul(Wl)),e}),ql=Hu("normal","vec3"),Xl=ki(e=>!1===e.geometry.hasAttribute("normal")?(console.warn('THREE.TSL: Vertex attribute "normal" not found on geometry.'),en(0,1,0)):ql,"vec3").once()().toVar("normalLocal"),Kl=Gl.dFdx().cross(Gl.dFdy()).normalize().toVar("normalFlat"),Yl=ki(e=>{let t;return t=!0===e.material.flatShading?Kl:rd(Xl).toVarying("v_normalViewGeometry").normalize(),t},"vec3").once()().toVar("normalViewGeometry"),Ql=ki(e=>{let t=Yl.transformDirection(cl);return!0!==e.material.flatShading&&(t=t.toVarying("v_normalWorldGeometry")),t.normalize().toVar("normalWorldGeometry")},"vec3").once()(),Zl=ki(({subBuildFn:e,material:t,context:r})=>{let s;return"NORMAL"===e||"VERTEX"===e?(s=Yl,!0!==t.flatShading&&(s=jl(s))):s=r.setupNormal().context({getUV:null}),s},"vec3").once(["NORMAL","VERTEX"])().toVar("normalView"),Jl=Zl.transformDirection(cl).toVar("normalWorld"),ed=ki(({subBuildFn:e,context:t})=>{let r;return r="NORMAL"===e||"VERTEX"===e?Zl:t.setupClearcoatNormal().context({getUV:null}),r},"vec3").once(["NORMAL","VERTEX"])().toVar("clearcoatNormalView"),td=ki(([e,t=El])=>{const r=dn(t),s=e.div(en(r[0].dot(r[0]),r[1].dot(r[1]),r[2].dot(r[2])));return r.mul(s).xyz}),rd=ki(([e],t)=>{const r=t.renderer.overrideNodes.modelNormalViewMatrix;if(null!==r)return r.transformDirection(e);const s=Ml.mul(e);return cl.transformDirection(s)}),sd=ki(()=>(console.warn('THREE.TSL: "transformedNormalView" is deprecated. Use "normalView" instead.'),Zl)).once(["NORMAL","VERTEX"])(),id=ki(()=>(console.warn('THREE.TSL: "transformedNormalWorld" is deprecated. Use "normalWorld" instead.'),Jl)).once(["NORMAL","VERTEX"])(),nd=ki(()=>(console.warn('THREE.TSL: "transformedClearcoatNormalView" is deprecated. Use "clearcoatNormalView" instead.'),ed)).once(["NORMAL","VERTEX"])(),ad=new w,od=new a,ud=Zn(0).onReference(({material:e})=>e).onObjectUpdate(({material:e})=>e.refractionRatio),ld=Zn(1).onReference(({material:e})=>e).onObjectUpdate(function({material:e,scene:t}){return e.envMap?e.envMapIntensity:t.environmentIntensity}),dd=Zn(new a).onReference(function(e){return e.material}).onObjectUpdate(function({material:e,scene:t}){const r=null!==t.environment&&null===e.envMap?t.environmentRotation:e.envMapRotation;return r?(ad.copy(r),od.makeRotationFromEuler(ad)):od.identity(),od}),cd=zl.negate().reflect(Zl),hd=zl.negate().refract(Zl,ud),pd=cd.transformDirection(cl).toVar("reflectVector"),gd=hd.transformDirection(cl).toVar("reflectVector"),md=new A;class fd extends Yu{static get type(){return"CubeTextureNode"}constructor(e,t=null,r=null,s=null){super(e,t,r,s),this.isCubeTextureNode=!0}getInputType(){return"cubeTexture"}getDefaultUV(){const e=this.value;return e.mapping===R?pd:e.mapping===C?gd:(console.error('THREE.CubeTextureNode: Mapping "%s" not supported.',e.mapping),en(0,0,0))}setUpdateMatrix(){}setupUV(e,t){const r=this.value;return e.renderer.coordinateSystem!==d&&r.isRenderTargetTexture||(t=en(t.x.negate(),t.yz)),dd.mul(t)}generateUV(e,t){return t.build(e,"vec3")}}const yd=Vi(fd).setParameterLength(1,4).setName("cubeTexture"),bd=(e=md,t=null,r=null,s=null)=>{let i;return e&&!0===e.isCubeTextureNode?(i=Li(e.clone()),i.referenceNode=e.getSelf(),null!==t&&(i.uvNode=Li(t)),null!==r&&(i.levelNode=Li(r)),null!==s&&(i.biasNode=Li(s))):i=yd(e,t,r,s),i};class xd extends qs{static get type(){return"ReferenceElementNode"}constructor(e,t){super(e,t),this.referenceNode=e,this.isReferenceElementNode=!0}getNodeType(){return this.referenceNode.uniformType}generate(e){const t=super.generate(e),r=this.referenceNode.getNodeType(),s=this.getNodeType();return e.format(t,r,s)}}class Td extends js{static get type(){return"ReferenceNode"}constructor(e,t,r=null,s=null){super(),this.property=e,this.uniformType=t,this.object=r,this.count=s,this.properties=e.split("."),this.reference=r,this.node=null,this.group=null,this.name=null,this.updateType=Vs.OBJECT}element(e){return Li(new xd(this,Li(e)))}setGroup(e){return this.group=e,this}label(e){return this.name=e,this}setNodeType(e){let t=null;t=null!==this.count?tl(null,e,this.count):Array.isArray(this.getValueFromReference())?il(null,e):"texture"===e?Zu(null):"cubeTexture"===e?bd(null):Zn(null,e),null!==this.group&&t.setGroup(this.group),null!==this.name&&t.label(this.name),this.node=t.getSelf()}getNodeType(e){return null===this.node&&(this.updateReference(e),this.updateValue()),this.node.getNodeType(e)}getValueFromReference(e=this.reference){const{properties:t}=this;let r=e[t[0]];for(let e=1;eLi(new Td(e,t,r)),vd=(e,t,r,s)=>Li(new Td(e,t,s,r));class Nd extends Td{static get type(){return"MaterialReferenceNode"}constructor(e,t,r=null){super(e,t,r),this.material=r,this.isMaterialReferenceNode=!0}updateReference(e){return this.reference=null!==this.material?this.material:e.material,this.reference}}const Sd=(e,t,r=null)=>Li(new Nd(e,t,r)),Ed=$u(),wd=Gl.dFdx(),Ad=Gl.dFdy(),Rd=Ed.dFdx(),Cd=Ed.dFdy(),Md=Zl,Pd=Ad.cross(Md),Bd=Md.cross(wd),Fd=Pd.mul(Rd.x).add(Bd.mul(Cd.x)),Ld=Pd.mul(Rd.y).add(Bd.mul(Cd.y)),Dd=Fd.dot(Fd).max(Ld.dot(Ld)),Id=Dd.equal(0).select(0,Dd.inverseSqrt()),Vd=Fd.mul(Id).toVar("tangentViewFrame"),Ud=Ld.mul(Id).toVar("bitangentViewFrame"),Od=ki(e=>(!1===e.geometry.hasAttribute("tangent")&&e.geometry.computeTangents(),Hu("tangent","vec4")))(),kd=Od.xyz.toVar("tangentLocal"),Gd=ki(({subBuildFn:e,geometry:t,material:r})=>{let s;return s="VERTEX"===e||t.hasAttribute("tangent")?Bl.mul(nn(kd,0)).xyz.toVarying("v_tangentView").normalize():Vd,!0!==r.flatShading&&(s=jl(s)),s},"vec3").once(["NORMAL","VERTEX"])().toVar("tangentView"),zd=Gd.transformDirection(cl).toVarying("v_tangentWorld").normalize().toVar("tangentWorld"),Hd=ki(([e,t],{subBuildFn:r,material:s})=>{let i=e.mul(Od.w).xyz;return"NORMAL"===r&&!0!==s.flatShading&&(i=i.toVarying(t)),i}).once(["NORMAL"]),$d=Hd(ql.cross(Od),"v_bitangentGeometry").normalize().toVar("bitangentGeometry"),Wd=Hd(Xl.cross(kd),"v_bitangentLocal").normalize().toVar("bitangentLocal"),jd=ki(({subBuildFn:e,geometry:t,material:r})=>{let s;return s="VERTEX"===e||t.hasAttribute("tangent")?Hd(Zl.cross(Gd),"v_bitangentView").normalize():Ud,!0!==r.flatShading&&(s=jl(s)),s},"vec3").once(["NORMAL","VERTEX"])().toVar("bitangentView"),qd=Hd(Jl.cross(zd),"v_bitangentWorld").normalize().toVar("bitangentWorld"),Xd=dn(Gd,jd,Zl).toVar("TBNViewMatrix"),Kd=zl.mul(Xd),Yd=ki(()=>{let e=Pn.cross(zl);return e=e.cross(Pn).normalize(),e=Lo(e,Zl,Cn.mul(xn.oneMinus()).oneMinus().pow2().pow2()).normalize(),e}).once()();class Qd extends Ks{static get type(){return"NormalMapNode"}constructor(e,t=null){super("vec3"),this.node=e,this.scaleNode=t,this.normalMapType=M}setup({material:e}){const{normalMapType:t,scaleNode:r}=this;let s=this.node.mul(2).sub(1);if(null!==r){let t=r;!0===e.flatShading&&(t=jl(t)),s=en(s.xy.mul(t),s.z)}let i=null;return t===P?i=rd(s):t===M?i=Xd.mul(s).normalize():(console.error(`THREE.NodeMaterial: Unsupported normal map type: ${t}`),i=Zl),i}}const Zd=Vi(Qd).setParameterLength(1,2),Jd=ki(({textureNode:e,bumpScale:t})=>{const r=t=>e.cache().context({getUV:e=>t(e.uvNode||$u()),forceUVContext:!0}),s=ji(r(e=>e));return Yi(ji(r(e=>e.add(e.dFdx()))).sub(s),ji(r(e=>e.add(e.dFdy()))).sub(s)).mul(t)}),ec=ki(e=>{const{surf_pos:t,surf_norm:r,dHdxy:s}=e,i=t.dFdx().normalize(),n=r,a=t.dFdy().normalize().cross(n),o=n.cross(i),u=i.dot(a).mul(Wl),l=u.sign().mul(s.x.mul(a).add(s.y.mul(o)));return u.abs().mul(r).sub(l).normalize()});class tc extends Ks{static get type(){return"BumpMapNode"}constructor(e,t=null){super("vec3"),this.textureNode=e,this.scaleNode=t}setup(){const e=null!==this.scaleNode?this.scaleNode:1,t=Jd({textureNode:this.textureNode,bumpScale:e});return ec({surf_pos:Gl,surf_norm:Zl,dHdxy:t})}}const rc=Vi(tc).setParameterLength(1,2),sc=new Map;class ic extends js{static get type(){return"MaterialNode"}constructor(e){super(),this.scope=e}getCache(e,t){let r=sc.get(e);return void 0===r&&(r=Sd(e,t),sc.set(e,r)),r}getFloat(e){return this.getCache(e,"float")}getColor(e){return this.getCache(e,"color")}getTexture(e){return this.getCache("map"===e?"map":e+"Map","texture")}setup(e){const t=e.context.material,r=this.scope;let s=null;if(r===ic.COLOR){const e=void 0!==t.color?this.getColor(r):en();s=t.map&&!0===t.map.isTexture?e.mul(this.getTexture("map")):e}else if(r===ic.OPACITY){const e=this.getFloat(r);s=t.alphaMap&&!0===t.alphaMap.isTexture?e.mul(this.getTexture("alpha")):e}else if(r===ic.SPECULAR_STRENGTH)s=t.specularMap&&!0===t.specularMap.isTexture?this.getTexture("specular").r:ji(1);else if(r===ic.SPECULAR_INTENSITY){const e=this.getFloat(r);s=t.specularIntensityMap&&!0===t.specularIntensityMap.isTexture?e.mul(this.getTexture(r).a):e}else if(r===ic.SPECULAR_COLOR){const e=this.getColor(r);s=t.specularColorMap&&!0===t.specularColorMap.isTexture?e.mul(this.getTexture(r).rgb):e}else if(r===ic.ROUGHNESS){const e=this.getFloat(r);s=t.roughnessMap&&!0===t.roughnessMap.isTexture?e.mul(this.getTexture(r).g):e}else if(r===ic.METALNESS){const e=this.getFloat(r);s=t.metalnessMap&&!0===t.metalnessMap.isTexture?e.mul(this.getTexture(r).b):e}else if(r===ic.EMISSIVE){const e=this.getFloat("emissiveIntensity"),i=this.getColor(r).mul(e);s=t.emissiveMap&&!0===t.emissiveMap.isTexture?i.mul(this.getTexture(r)):i}else if(r===ic.NORMAL)t.normalMap?(s=Zd(this.getTexture("normal"),this.getCache("normalScale","vec2")),s.normalMapType=t.normalMapType):s=t.bumpMap?rc(this.getTexture("bump").r,this.getFloat("bumpScale")):Zl;else if(r===ic.CLEARCOAT){const e=this.getFloat(r);s=t.clearcoatMap&&!0===t.clearcoatMap.isTexture?e.mul(this.getTexture(r).r):e}else if(r===ic.CLEARCOAT_ROUGHNESS){const e=this.getFloat(r);s=t.clearcoatRoughnessMap&&!0===t.clearcoatRoughnessMap.isTexture?e.mul(this.getTexture(r).r):e}else if(r===ic.CLEARCOAT_NORMAL)s=t.clearcoatNormalMap?Zd(this.getTexture(r),this.getCache(r+"Scale","vec2")):Zl;else if(r===ic.SHEEN){const e=this.getColor("sheenColor").mul(this.getFloat("sheen"));s=t.sheenColorMap&&!0===t.sheenColorMap.isTexture?e.mul(this.getTexture("sheenColor").rgb):e}else if(r===ic.SHEEN_ROUGHNESS){const e=this.getFloat(r);s=t.sheenRoughnessMap&&!0===t.sheenRoughnessMap.isTexture?e.mul(this.getTexture(r).a):e,s=s.clamp(.07,1)}else if(r===ic.ANISOTROPY)if(t.anisotropyMap&&!0===t.anisotropyMap.isTexture){const e=this.getTexture(r);s=ln(zc.x,zc.y,zc.y.negate(),zc.x).mul(e.rg.mul(2).sub(Yi(1)).normalize().mul(e.b))}else s=zc;else if(r===ic.IRIDESCENCE_THICKNESS){const e=_d("1","float",t.iridescenceThicknessRange);if(t.iridescenceThicknessMap){const i=_d("0","float",t.iridescenceThicknessRange);s=e.sub(i).mul(this.getTexture(r).g).add(i)}else s=e}else if(r===ic.TRANSMISSION){const e=this.getFloat(r);s=t.transmissionMap?e.mul(this.getTexture(r).r):e}else if(r===ic.THICKNESS){const e=this.getFloat(r);s=t.thicknessMap?e.mul(this.getTexture(r).g):e}else if(r===ic.IOR)s=this.getFloat(r);else if(r===ic.LIGHT_MAP)s=this.getTexture(r).rgb.mul(this.getFloat("lightMapIntensity"));else if(r===ic.AO)s=this.getTexture(r).r.sub(1).mul(this.getFloat("aoMapIntensity")).add(1);else if(r===ic.LINE_DASH_OFFSET)s=t.dashOffset?this.getFloat(r):ji(0);else{const t=this.getNodeType(e);s=this.getCache(r,t)}return s}}ic.ALPHA_TEST="alphaTest",ic.COLOR="color",ic.OPACITY="opacity",ic.SHININESS="shininess",ic.SPECULAR="specular",ic.SPECULAR_STRENGTH="specularStrength",ic.SPECULAR_INTENSITY="specularIntensity",ic.SPECULAR_COLOR="specularColor",ic.REFLECTIVITY="reflectivity",ic.ROUGHNESS="roughness",ic.METALNESS="metalness",ic.NORMAL="normal",ic.CLEARCOAT="clearcoat",ic.CLEARCOAT_ROUGHNESS="clearcoatRoughness",ic.CLEARCOAT_NORMAL="clearcoatNormal",ic.EMISSIVE="emissive",ic.ROTATION="rotation",ic.SHEEN="sheen",ic.SHEEN_ROUGHNESS="sheenRoughness",ic.ANISOTROPY="anisotropy",ic.IRIDESCENCE="iridescence",ic.IRIDESCENCE_IOR="iridescenceIOR",ic.IRIDESCENCE_THICKNESS="iridescenceThickness",ic.IOR="ior",ic.TRANSMISSION="transmission",ic.THICKNESS="thickness",ic.ATTENUATION_DISTANCE="attenuationDistance",ic.ATTENUATION_COLOR="attenuationColor",ic.LINE_SCALE="scale",ic.LINE_DASH_SIZE="dashSize",ic.LINE_GAP_SIZE="gapSize",ic.LINE_WIDTH="linewidth",ic.LINE_DASH_OFFSET="dashOffset",ic.POINT_SIZE="size",ic.DISPERSION="dispersion",ic.LIGHT_MAP="light",ic.AO="ao";const nc=Ui(ic,ic.ALPHA_TEST),ac=Ui(ic,ic.COLOR),oc=Ui(ic,ic.SHININESS),uc=Ui(ic,ic.EMISSIVE),lc=Ui(ic,ic.OPACITY),dc=Ui(ic,ic.SPECULAR),cc=Ui(ic,ic.SPECULAR_INTENSITY),hc=Ui(ic,ic.SPECULAR_COLOR),pc=Ui(ic,ic.SPECULAR_STRENGTH),gc=Ui(ic,ic.REFLECTIVITY),mc=Ui(ic,ic.ROUGHNESS),fc=Ui(ic,ic.METALNESS),yc=Ui(ic,ic.NORMAL),bc=Ui(ic,ic.CLEARCOAT),xc=Ui(ic,ic.CLEARCOAT_ROUGHNESS),Tc=Ui(ic,ic.CLEARCOAT_NORMAL),_c=Ui(ic,ic.ROTATION),vc=Ui(ic,ic.SHEEN),Nc=Ui(ic,ic.SHEEN_ROUGHNESS),Sc=Ui(ic,ic.ANISOTROPY),Ec=Ui(ic,ic.IRIDESCENCE),wc=Ui(ic,ic.IRIDESCENCE_IOR),Ac=Ui(ic,ic.IRIDESCENCE_THICKNESS),Rc=Ui(ic,ic.TRANSMISSION),Cc=Ui(ic,ic.THICKNESS),Mc=Ui(ic,ic.IOR),Pc=Ui(ic,ic.ATTENUATION_DISTANCE),Bc=Ui(ic,ic.ATTENUATION_COLOR),Fc=Ui(ic,ic.LINE_SCALE),Lc=Ui(ic,ic.LINE_DASH_SIZE),Dc=Ui(ic,ic.LINE_GAP_SIZE),Ic=Ui(ic,ic.LINE_WIDTH),Vc=Ui(ic,ic.LINE_DASH_OFFSET),Uc=Ui(ic,ic.POINT_SIZE),Oc=Ui(ic,ic.DISPERSION),kc=Ui(ic,ic.LIGHT_MAP),Gc=Ui(ic,ic.AO),zc=Zn(new t).onReference(function(e){return e.material}).onRenderUpdate(function({material:e}){this.value.set(e.anisotropy*Math.cos(e.anisotropyRotation),e.anisotropy*Math.sin(e.anisotropyRotation))}),Hc=ki(e=>e.context.setupModelViewProjection(),"vec4").once()().toVarying("v_modelViewProjection");class $c extends js{static get type(){return"IndexNode"}constructor(e){super("uint"),this.scope=e,this.isIndexNode=!0}generate(e){const t=this.getNodeType(e),r=this.scope;let s,i;if(r===$c.VERTEX)s=e.getVertexIndex();else if(r===$c.INSTANCE)s=e.getInstanceIndex();else if(r===$c.DRAW)s=e.getDrawIndex();else if(r===$c.INVOCATION_LOCAL)s=e.getInvocationLocalIndex();else if(r===$c.INVOCATION_SUBGROUP)s=e.getInvocationSubgroupIndex();else{if(r!==$c.SUBGROUP)throw new Error("THREE.IndexNode: Unknown scope: "+r);s=e.getSubgroupIndex()}if("vertex"===e.shaderStage||"compute"===e.shaderStage)i=s;else{i=au(this).build(e,t)}return i}}$c.VERTEX="vertex",$c.INSTANCE="instance",$c.SUBGROUP="subgroup",$c.INVOCATION_LOCAL="invocationLocal",$c.INVOCATION_SUBGROUP="invocationSubgroup",$c.DRAW="draw";const Wc=Ui($c,$c.VERTEX),jc=Ui($c,$c.INSTANCE),qc=Ui($c,$c.SUBGROUP),Xc=Ui($c,$c.INVOCATION_SUBGROUP),Kc=Ui($c,$c.INVOCATION_LOCAL),Yc=Ui($c,$c.DRAW);class Qc extends js{static get type(){return"InstanceNode"}constructor(e,t,r=null){super("void"),this.count=e,this.instanceMatrix=t,this.instanceColor=r,this.instanceMatrixNode=null,this.instanceColorNode=null,this.updateType=Vs.FRAME,this.buffer=null,this.bufferColor=null}setup(e){const{count:t,instanceMatrix:r,instanceColor:s}=this;let{instanceMatrixNode:i,instanceColorNode:n}=this;if(null===i){if(t<=1e3)i=tl(r.array,"mat4",Math.max(t,1)).element(jc);else{const e=new B(r.array,16,1);this.buffer=e;const t=r.usage===y?Eu:Su,s=[t(e,"vec4",16,0),t(e,"vec4",16,4),t(e,"vec4",16,8),t(e,"vec4",16,12)];i=cn(...s)}this.instanceMatrixNode=i}if(s&&null===n){const e=new F(s.array,3),t=s.usage===y?Eu:Su;this.bufferColor=e,n=en(t(e,"vec3",3,0)),this.instanceColorNode=n}const a=i.mul(Vl).xyz;if(Vl.assign(a),e.hasGeometryAttribute("normal")){const e=td(Xl,i);Xl.assign(e)}null!==this.instanceColorNode&&fn("vec3","vInstanceColor").assign(this.instanceColorNode)}update(){this.instanceMatrix.usage!==y&&null!==this.buffer&&this.instanceMatrix.version!==this.buffer.version&&(this.buffer.version=this.instanceMatrix.version),this.instanceColor&&this.instanceColor.usage!==y&&null!==this.bufferColor&&this.instanceColor.version!==this.bufferColor.version&&(this.bufferColor.version=this.instanceColor.version)}}const Zc=Vi(Qc).setParameterLength(2,3);class Jc extends Qc{static get type(){return"InstancedMeshNode"}constructor(e){const{count:t,instanceMatrix:r,instanceColor:s}=e;super(t,r,s),this.instancedMesh=e}}const eh=Vi(Jc).setParameterLength(1);class th extends js{static get type(){return"BatchNode"}constructor(e){super("void"),this.batchMesh=e,this.batchingIdNode=null}setup(e){null===this.batchingIdNode&&(null===e.getDrawIndex()?this.batchingIdNode=jc:this.batchingIdNode=Yc);const t=ki(([e])=>{const t=qi(ju(Ju(this.batchMesh._indirectTexture),0).x),r=qi(e).mod(t),s=qi(e).div(t);return Ju(this.batchMesh._indirectTexture,Qi(r,s)).x}).setLayout({name:"getIndirectIndex",type:"uint",inputs:[{name:"id",type:"int"}]}),r=t(qi(this.batchingIdNode)),s=this.batchMesh._matricesTexture,i=qi(ju(Ju(s),0).x),n=ji(r).mul(4).toInt().toVar(),a=n.mod(i),o=n.div(i),u=cn(Ju(s,Qi(a,o)),Ju(s,Qi(a.add(1),o)),Ju(s,Qi(a.add(2),o)),Ju(s,Qi(a.add(3),o))),l=this.batchMesh._colorsTexture;if(null!==l){const e=ki(([e])=>{const t=qi(ju(Ju(l),0).x),r=e,s=r.mod(t),i=r.div(t);return Ju(l,Qi(s,i)).rgb}).setLayout({name:"getBatchingColor",type:"vec3",inputs:[{name:"id",type:"int"}]}),t=e(r);fn("vec3","vBatchColor").assign(t)}const d=dn(u);Vl.assign(u.mul(Vl));const c=Xl.div(en(d[0].dot(d[0]),d[1].dot(d[1]),d[2].dot(d[2]))),h=d.mul(c).xyz;Xl.assign(h),e.hasGeometryAttribute("tangent")&&kd.mulAssign(d)}}const rh=Vi(th).setParameterLength(1);class sh extends qs{static get type(){return"StorageArrayElementNode"}constructor(e,t){super(e,t),this.isStorageArrayElementNode=!0}set storageBufferNode(e){this.node=e}get storageBufferNode(){return this.node}getMemberType(e,t){const r=this.storageBufferNode.structTypeNode;return r?r.getMemberType(e,t):"void"}setup(e){return!1===e.isAvailable("storageBuffer")&&!0===this.node.isPBO&&e.setupPBO(this.node),super.setup(e)}generate(e,t){let r;const s=e.context.assign;if(r=!1===e.isAvailable("storageBuffer")?!0!==this.node.isPBO||!0===s||!this.node.value.isInstancedBufferAttribute&&"compute"===e.shaderStage?this.node.build(e):e.generatePBO(this):super.generate(e),!0!==s){const s=this.getNodeType(e);r=e.format(r,s,t)}return r}}const ih=Vi(sh).setParameterLength(2);class nh extends el{static get type(){return"StorageBufferNode"}constructor(e,t=null,r=0){let s,i=null;t&&t.isStruct?(s="struct",i=t.layout,(e.isStorageBufferAttribute||e.isStorageInstancedBufferAttribute)&&(r=e.count)):null===t&&(e.isStorageBufferAttribute||e.isStorageInstancedBufferAttribute)?(s=Es(e.itemSize),r=e.count):s=t,super(e,s,r),this.isStorageBufferNode=!0,this.structTypeNode=i,this.access=Os.READ_WRITE,this.isAtomic=!1,this.isPBO=!1,this._attribute=null,this._varying=null,this.global=!0,!0!==e.isStorageBufferAttribute&&!0!==e.isStorageInstancedBufferAttribute&&(e.isInstancedBufferAttribute?e.isStorageInstancedBufferAttribute=!0:e.isStorageBufferAttribute=!0)}getHash(e){if(0===this.bufferCount){let t=e.globalCache.getData(this.value);return void 0===t&&(t={node:this},e.globalCache.setData(this.value,t)),t.node.uuid}return this.uuid}getInputType(){return this.value.isIndirectStorageBufferAttribute?"indirectStorageBuffer":"storageBuffer"}element(e){return ih(this,e)}setPBO(e){return this.isPBO=e,this}getPBO(){return this.isPBO}setAccess(e){return this.access=e,this}toReadOnly(){return this.setAccess(Os.READ_ONLY)}setAtomic(e){return this.isAtomic=e,this}toAtomic(){return this.setAtomic(!0)}getAttributeData(){return null===this._attribute&&(this._attribute=vu(this.value),this._varying=au(this._attribute)),{attribute:this._attribute,varying:this._varying}}getNodeType(e){if(null!==this.structTypeNode)return this.structTypeNode.getNodeType(e);if(e.isAvailable("storageBuffer")||e.isAvailable("indirectStorageBuffer"))return super.getNodeType(e);const{attribute:t}=this.getAttributeData();return t.getNodeType(e)}getMemberType(e,t){return null!==this.structTypeNode?this.structTypeNode.getMemberType(e,t):"void"}generate(e){if(null!==this.structTypeNode&&this.structTypeNode.build(e),e.isAvailable("storageBuffer")||e.isAvailable("indirectStorageBuffer"))return super.generate(e);const{attribute:t,varying:r}=this.getAttributeData(),s=r.build(e);return e.registerTransform(s,t),s}}const ah=(e,t=null,r=0)=>Li(new nh(e,t,r)),oh=new WeakMap;class uh extends js{static get type(){return"SkinningNode"}constructor(e){super("void"),this.skinnedMesh=e,this.updateType=Vs.OBJECT,this.skinIndexNode=Hu("skinIndex","uvec4"),this.skinWeightNode=Hu("skinWeight","vec4"),this.bindMatrixNode=_d("bindMatrix","mat4"),this.bindMatrixInverseNode=_d("bindMatrixInverse","mat4"),this.boneMatricesNode=vd("skeleton.boneMatrices","mat4",e.skeleton.bones.length),this.positionNode=Vl,this.toPositionNode=Vl,this.previousBoneMatricesNode=null}getSkinnedPosition(e=this.boneMatricesNode,t=this.positionNode){const{skinIndexNode:r,skinWeightNode:s,bindMatrixNode:i,bindMatrixInverseNode:n}=this,a=e.element(r.x),o=e.element(r.y),u=e.element(r.z),l=e.element(r.w),d=i.mul(t),c=oa(a.mul(s.x).mul(d),o.mul(s.y).mul(d),u.mul(s.z).mul(d),l.mul(s.w).mul(d));return n.mul(c).xyz}getSkinnedNormal(e=this.boneMatricesNode,t=Xl){const{skinIndexNode:r,skinWeightNode:s,bindMatrixNode:i,bindMatrixInverseNode:n}=this,a=e.element(r.x),o=e.element(r.y),u=e.element(r.z),l=e.element(r.w);let d=oa(s.x.mul(a),s.y.mul(o),s.z.mul(u),s.w.mul(l));return d=n.mul(d).mul(i),d.transformDirection(t).xyz}getPreviousSkinnedPosition(e){const t=e.object;return null===this.previousBoneMatricesNode&&(t.skeleton.previousBoneMatrices=new Float32Array(t.skeleton.boneMatrices),this.previousBoneMatricesNode=vd("skeleton.previousBoneMatrices","mat4",t.skeleton.bones.length)),this.getSkinnedPosition(this.previousBoneMatricesNode,Ul)}needsPreviousBoneMatrices(e){const t=e.renderer.getMRT();return t&&t.has("velocity")||!0===Bs(e.object).useVelocity}setup(e){this.needsPreviousBoneMatrices(e)&&Ul.assign(this.getPreviousSkinnedPosition(e));const t=this.getSkinnedPosition();if(this.toPositionNode&&this.toPositionNode.assign(t),e.hasGeometryAttribute("normal")){const t=this.getSkinnedNormal();Xl.assign(t),e.hasGeometryAttribute("tangent")&&kd.assign(t)}return t}generate(e,t){if("void"!==t)return super.generate(e,t)}update(e){const t=e.object&&e.object.skeleton?e.object.skeleton:this.skinnedMesh.skeleton;oh.get(t)!==e.frameId&&(oh.set(t,e.frameId),null!==this.previousBoneMatricesNode&&t.previousBoneMatrices.set(t.boneMatrices),t.update())}}const lh=e=>Li(new uh(e));class dh extends js{static get type(){return"LoopNode"}constructor(e=[]){super(),this.params=e}getVarName(e){return String.fromCharCode("i".charCodeAt(0)+e)}getProperties(e){const t=e.getNodeProperties(this);if(void 0!==t.stackNode)return t;const r={};for(let e=0,t=this.params.length-1;eNumber(u)?">=":"<")),a)n=`while ( ${u} )`;else{const r={start:o,end:u},s=r.start,i=r.end;let a;const p=()=>c.includes("<")?"+=":"-=";if(null!=h)switch(typeof h){case"function":a=e.flowStagesNode(t.updateNode,"void").code.replace(/\t|;/g,"");break;case"number":a=l+" "+p()+" "+e.generateConst(d,h);break;case"string":a=l+" "+h;break;default:h.isNode?a=l+" "+p()+" "+h.build(e):(console.error("THREE.TSL: 'Loop( { update: ... } )' is not a function, string or number."),a="break /* invalid update */")}else h="int"===d||"uint"===d?c.includes("<")?"++":"--":p()+" 1.",a=l+" "+h;n=`for ( ${e.getVar(d,l)+" = "+s}; ${l+" "+c+" "+i}; ${a} )`}e.addFlowCode((0===s?"\n":"")+e.tab+n+" {\n\n").addFlowTab()}const i=s.build(e,"void"),n=t.returnsNode?t.returnsNode.build(e):"";e.removeFlowTab().addFlowCode("\n"+e.tab+i);for(let t=0,r=this.params.length-1;tLi(new dh(Ii(e,"int"))).toStack(),hh=()=>Iu("break").toStack(),ph=new WeakMap,gh=new s,mh=ki(({bufferMap:e,influence:t,stride:r,width:s,depth:i,offset:n})=>{const a=qi(Wc).mul(r).add(n),o=a.div(s),u=a.sub(o.mul(s));return Ju(e,Qi(u,o)).depth(i).xyz.mul(t)});class fh extends js{static get type(){return"MorphNode"}constructor(e){super("void"),this.mesh=e,this.morphBaseInfluence=Zn(1),this.updateType=Vs.OBJECT}setup(e){const{geometry:r}=e,s=void 0!==r.morphAttributes.position,i=r.hasAttribute("normal")&&void 0!==r.morphAttributes.normal,n=r.morphAttributes.position||r.morphAttributes.normal||r.morphAttributes.color,a=void 0!==n?n.length:0,{texture:o,stride:u,size:l}=function(e){const r=void 0!==e.morphAttributes.position,s=void 0!==e.morphAttributes.normal,i=void 0!==e.morphAttributes.color,n=e.morphAttributes.position||e.morphAttributes.normal||e.morphAttributes.color,a=void 0!==n?n.length:0;let o=ph.get(e);if(void 0===o||o.count!==a){void 0!==o&&o.texture.dispose();const u=e.morphAttributes.position||[],l=e.morphAttributes.normal||[],d=e.morphAttributes.color||[];let c=0;!0===r&&(c=1),!0===s&&(c=2),!0===i&&(c=3);let h=e.attributes.position.count*c,p=1;const g=4096;h>g&&(p=Math.ceil(h/g),h=g);const m=new Float32Array(h*p*4*a),f=new L(m,h,p,a);f.type=D,f.needsUpdate=!0;const y=4*c;for(let x=0;x{const t=ji(0).toVar();this.mesh.count>1&&null!==this.mesh.morphTexture&&void 0!==this.mesh.morphTexture?t.assign(Ju(this.mesh.morphTexture,Qi(qi(e).add(1),qi(jc))).r):t.assign(_d("morphTargetInfluences","float").element(e).toVar()),Hi(t.notEqual(0),()=>{!0===s&&Vl.addAssign(mh({bufferMap:o,influence:t,stride:u,width:d,depth:e,offset:qi(0)})),!0===i&&Xl.addAssign(mh({bufferMap:o,influence:t,stride:u,width:d,depth:e,offset:qi(1)}))})})}update(){const e=this.morphBaseInfluence;this.mesh.geometry.morphTargetsRelative?e.value=1:e.value=1-this.mesh.morphTargetInfluences.reduce((e,t)=>e+t,0)}}const yh=Vi(fh).setParameterLength(1);class bh extends js{static get type(){return"LightingNode"}constructor(){super("vec3"),this.isLightingNode=!0}}class xh extends bh{static get type(){return"AONode"}constructor(e=null){super(),this.aoNode=e}setup(e){e.context.ambientOcclusion.mulAssign(this.aoNode)}}class Th extends Ko{static get type(){return"LightingContextNode"}constructor(e,t=null,r=null,s=null){super(e),this.lightingModel=t,this.backdropNode=r,this.backdropAlphaNode=s,this._value=null}getContext(){const{backdropNode:e,backdropAlphaNode:t}=this,r={directDiffuse:en().toVar("directDiffuse"),directSpecular:en().toVar("directSpecular"),indirectDiffuse:en().toVar("indirectDiffuse"),indirectSpecular:en().toVar("indirectSpecular")};return{radiance:en().toVar("radiance"),irradiance:en().toVar("irradiance"),iblIrradiance:en().toVar("iblIrradiance"),ambientOcclusion:ji(1).toVar("ambientOcclusion"),reflectedLight:r,backdrop:e,backdropAlpha:t}}setup(e){return this.value=this._value||(this._value=this.getContext()),this.value.lightingModel=this.lightingModel||e.context.lightingModel,super.setup(e)}}const _h=Vi(Th);class vh extends bh{static get type(){return"IrradianceNode"}constructor(e){super(),this.node=e}setup(e){e.context.irradiance.addAssign(this.node)}}let Nh,Sh;class Eh extends js{static get type(){return"ScreenNode"}constructor(e){super(),this.scope=e,this.isViewportNode=!0}getNodeType(){return this.scope===Eh.VIEWPORT?"vec4":"vec2"}getUpdateType(){let e=Vs.NONE;return this.scope!==Eh.SIZE&&this.scope!==Eh.VIEWPORT||(e=Vs.RENDER),this.updateType=e,e}update({renderer:e}){const t=e.getRenderTarget();this.scope===Eh.VIEWPORT?null!==t?Sh.copy(t.viewport):(e.getViewport(Sh),Sh.multiplyScalar(e.getPixelRatio())):null!==t?(Nh.width=t.width,Nh.height=t.height):e.getDrawingBufferSize(Nh)}setup(){const e=this.scope;let r=null;return r=e===Eh.SIZE?Zn(Nh||(Nh=new t)):e===Eh.VIEWPORT?Zn(Sh||(Sh=new s)):Yi(Rh.div(Ah)),r}generate(e){if(this.scope===Eh.COORDINATE){let t=e.getFragCoord();if(e.isFlipY()){const r=e.getNodeProperties(Ah).outputNode.build(e);t=`${e.getType("vec2")}( ${t}.x, ${r}.y - ${t}.y )`}return t}return super.generate(e)}}Eh.COORDINATE="coordinate",Eh.VIEWPORT="viewport",Eh.SIZE="size",Eh.UV="uv";const wh=Ui(Eh,Eh.UV),Ah=Ui(Eh,Eh.SIZE),Rh=Ui(Eh,Eh.COORDINATE),Ch=Ui(Eh,Eh.VIEWPORT),Mh=Ch.zw,Ph=Rh.sub(Ch.xy),Bh=Ph.div(Mh),Fh=ki(()=>(console.warn('THREE.TSL: "viewportResolution" is deprecated. Use "screenSize" instead.'),Ah),"vec2").once()(),Lh=new t;class Dh extends Yu{static get type(){return"ViewportTextureNode"}constructor(e=wh,t=null,r=null){null===r&&((r=new I).minFilter=V),super(r,e,t),this.generateMipmaps=!1,this.isOutputTextureNode=!0,this.updateBeforeType=Vs.FRAME}updateBefore(e){const t=e.renderer;t.getDrawingBufferSize(Lh);const r=this.value;r.image.width===Lh.width&&r.image.height===Lh.height||(r.image.width=Lh.width,r.image.height=Lh.height,r.needsUpdate=!0);const s=r.generateMipmaps;r.generateMipmaps=this.generateMipmaps,t.copyFramebufferToTexture(r),r.generateMipmaps=s}clone(){const e=new this.constructor(this.uvNode,this.levelNode,this.value);return e.generateMipmaps=this.generateMipmaps,e}}const Ih=Vi(Dh).setParameterLength(0,3),Vh=Vi(Dh,null,null,{generateMipmaps:!0}).setParameterLength(0,3);let Uh=null;class Oh extends Dh{static get type(){return"ViewportDepthTextureNode"}constructor(e=wh,t=null){null===Uh&&(Uh=new U),super(e,t,Uh)}}const kh=Vi(Oh).setParameterLength(0,2);class Gh extends js{static get type(){return"ViewportDepthNode"}constructor(e,t=null){super("float"),this.scope=e,this.valueNode=t,this.isViewportDepthNode=!0}generate(e){const{scope:t}=this;return t===Gh.DEPTH_BASE?e.getFragDepth():super.generate(e)}setup({camera:e}){const{scope:t}=this,r=this.valueNode;let s=null;if(t===Gh.DEPTH_BASE)null!==r&&(s=jh().assign(r));else if(t===Gh.DEPTH)s=e.isPerspectiveCamera?Hh(Gl.z,ol,ul):zh(Gl.z,ol,ul);else if(t===Gh.LINEAR_DEPTH)if(null!==r)if(e.isPerspectiveCamera){const e=$h(r,ol,ul);s=zh(e,ol,ul)}else s=r;else s=zh(Gl.z,ol,ul);return s}}Gh.DEPTH_BASE="depthBase",Gh.DEPTH="depth",Gh.LINEAR_DEPTH="linearDepth";const zh=(e,t,r)=>e.add(t).div(t.sub(r)),Hh=(e,t,r)=>t.add(e).mul(r).div(r.sub(t).mul(e)),$h=(e,t,r)=>t.mul(r).div(r.sub(t).mul(e).sub(r)),Wh=(e,t,r)=>{t=t.max(1e-6).toVar();const s=Wa(e.negate().div(t)),i=Wa(r.div(t));return s.div(i)},jh=Vi(Gh,Gh.DEPTH_BASE),qh=Ui(Gh,Gh.DEPTH),Xh=Vi(Gh,Gh.LINEAR_DEPTH).setParameterLength(0,1),Kh=Xh(kh());qh.assign=e=>jh(e);class Yh extends js{static get type(){return"ClippingNode"}constructor(e=Yh.DEFAULT){super(),this.scope=e}setup(e){super.setup(e);const t=e.clippingContext,{intersectionPlanes:r,unionPlanes:s}=t;return this.hardwareClipping=e.material.hardwareClipping,this.scope===Yh.ALPHA_TO_COVERAGE?this.setupAlphaToCoverage(r,s):this.scope===Yh.HARDWARE?this.setupHardwareClipping(s,e):this.setupDefault(r,s)}setupAlphaToCoverage(e,t){return ki(()=>{const r=ji().toVar("distanceToPlane"),s=ji().toVar("distanceToGradient"),i=ji(1).toVar("clipOpacity"),n=t.length;if(!1===this.hardwareClipping&&n>0){const e=il(t);ch(n,({i:t})=>{const n=e.element(t);r.assign(Gl.dot(n.xyz).negate().add(n.w)),s.assign(r.fwidth().div(2)),i.mulAssign(Uo(s.negate(),s,r))})}const a=e.length;if(a>0){const t=il(e),n=ji(1).toVar("intersectionClipOpacity");ch(a,({i:e})=>{const i=t.element(e);r.assign(Gl.dot(i.xyz).negate().add(i.w)),s.assign(r.fwidth().div(2)),n.mulAssign(Uo(s.negate(),s,r).oneMinus())}),i.mulAssign(n.oneMinus())}yn.a.mulAssign(i),yn.a.equal(0).discard()})()}setupDefault(e,t){return ki(()=>{const r=t.length;if(!1===this.hardwareClipping&&r>0){const e=il(t);ch(r,({i:t})=>{const r=e.element(t);Gl.dot(r.xyz).greaterThan(r.w).discard()})}const s=e.length;if(s>0){const t=il(e),r=Ki(!0).toVar("clipped");ch(s,({i:e})=>{const s=t.element(e);r.assign(Gl.dot(s.xyz).greaterThan(s.w).and(r))}),r.discard()}})()}setupHardwareClipping(e,t){const r=e.length;return t.enableHardwareClipping(r),ki(()=>{const s=il(e),i=nl(t.getClipDistance());ch(r,({i:e})=>{const t=s.element(e),r=Gl.dot(t.xyz).sub(t.w).negate();i.element(e).assign(r)})})()}}Yh.ALPHA_TO_COVERAGE="alphaToCoverage",Yh.DEFAULT="default",Yh.HARDWARE="hardware";const Qh=ki(([e])=>Qa(la(1e4,Za(la(17,e.x).add(la(.1,e.y)))).mul(oa(.1,io(Za(la(13,e.y).add(e.x))))))),Zh=ki(([e])=>Qh(Yi(Qh(e.xy),e.z))),Jh=ki(([e])=>{const t=To(ao(lo(e.xyz)),ao(co(e.xyz))),r=ji(1).div(ji(.05).mul(t)).toVar("pixScale"),s=Yi(Ha(Xa(Wa(r))),Ha(Ka(Wa(r)))),i=Yi(Zh(Xa(s.x.mul(e.xyz))),Zh(Xa(s.y.mul(e.xyz)))),n=Qa(Wa(r)),a=oa(la(n.oneMinus(),i.x),la(n,i.y)),o=xo(n,n.oneMinus()),u=en(a.mul(a).div(la(2,o).mul(ua(1,o))),a.sub(la(.5,o)).div(ua(1,o)),ua(1,ua(1,a).mul(ua(1,a)).div(la(2,o).mul(ua(1,o))))),l=a.lessThan(o.oneMinus()).select(a.lessThan(o).select(u.x,u.y),u.z);return Do(l,1e-6,1)}).setLayout({name:"getAlphaHashThreshold",type:"float",inputs:[{name:"position",type:"vec3"}]});class ep extends zu{static get type(){return"VertexColorNode"}constructor(e){super(null,"vec4"),this.isVertexColorNode=!0,this.index=e}getAttributeName(){const e=this.index;return"color"+(e>0?e:"")}generate(e){const t=this.getAttributeName(e);let r;return r=!0===e.hasGeometryAttribute(t)?super.generate(e):e.generateConst(this.nodeType,new s(1,1,1,1)),r}serialize(e){super.serialize(e),e.index=this.index}deserialize(e){super.deserialize(e),this.index=e.index}}const tp=(e=0)=>Li(new ep(e)),rp=ki(([e,t])=>xo(1,e.oneMinus().div(t)).oneMinus()).setLayout({name:"blendBurn",type:"vec3",inputs:[{name:"base",type:"vec3"},{name:"blend",type:"vec3"}]}),sp=ki(([e,t])=>xo(e.div(t.oneMinus()),1)).setLayout({name:"blendDodge",type:"vec3",inputs:[{name:"base",type:"vec3"},{name:"blend",type:"vec3"}]}),ip=ki(([e,t])=>e.oneMinus().mul(t.oneMinus()).oneMinus()).setLayout({name:"blendScreen",type:"vec3",inputs:[{name:"base",type:"vec3"},{name:"blend",type:"vec3"}]}),np=ki(([e,t])=>Lo(e.mul(2).mul(t),e.oneMinus().mul(2).mul(t.oneMinus()).oneMinus(),_o(.5,e))).setLayout({name:"blendOverlay",type:"vec3",inputs:[{name:"base",type:"vec3"},{name:"blend",type:"vec3"}]}),ap=ki(([e,t])=>{const r=t.a.add(e.a.mul(t.a.oneMinus()));return nn(t.rgb.mul(t.a).add(e.rgb.mul(e.a).mul(t.a.oneMinus())).div(r),r)}).setLayout({name:"blendColor",type:"vec4",inputs:[{name:"base",type:"vec4"},{name:"blend",type:"vec4"}]}),op=ki(([e])=>nn(e.rgb.mul(e.a),e.a),{color:"vec4",return:"vec4"}),up=ki(([e])=>(Hi(e.a.equal(0),()=>nn(0)),nn(e.rgb.div(e.a),e.a)),{color:"vec4",return:"vec4"});class lp extends O{static get type(){return"NodeMaterial"}get type(){return this.constructor.type}set type(e){}constructor(){super(),this.isNodeMaterial=!0,this.fog=!0,this.lights=!1,this.hardwareClipping=!1,this.lightsNode=null,this.envNode=null,this.aoNode=null,this.colorNode=null,this.normalNode=null,this.opacityNode=null,this.backdropNode=null,this.backdropAlphaNode=null,this.alphaTestNode=null,this.maskNode=null,this.positionNode=null,this.geometryNode=null,this.depthNode=null,this.receivedShadowPositionNode=null,this.castShadowPositionNode=null,this.receivedShadowNode=null,this.castShadowNode=null,this.outputNode=null,this.mrtNode=null,this.fragmentNode=null,this.vertexNode=null,Object.defineProperty(this,"shadowPositionNode",{get:()=>this.receivedShadowPositionNode,set:e=>{console.warn('THREE.NodeMaterial: ".shadowPositionNode" was renamed to ".receivedShadowPositionNode".'),this.receivedShadowPositionNode=e}})}customProgramCacheKey(){return this.type+_s(this)}build(e){this.setup(e)}setupObserver(e){return new fs(e)}setup(e){e.context.setupNormal=()=>iu(this.setupNormal(e),"NORMAL","vec3"),e.context.setupPositionView=()=>this.setupPositionView(e),e.context.setupModelViewProjection=()=>this.setupModelViewProjection(e);const t=e.renderer,r=t.getRenderTarget();e.addStack();const s=iu(this.setupVertex(e),"VERTEX"),i=this.vertexNode||s;let n;e.stack.outputNode=i,this.setupHardwareClipping(e),null!==this.geometryNode&&(e.stack.outputNode=e.stack.outputNode.bypass(this.geometryNode)),e.addFlow("vertex",e.removeStack()),e.addStack();const a=this.setupClipping(e);if(!0!==this.depthWrite&&!0!==this.depthTest||(null!==r?!0===r.depthBuffer&&this.setupDepth(e):!0===t.depth&&this.setupDepth(e)),null===this.fragmentNode){this.setupDiffuseColor(e),this.setupVariants(e);const s=this.setupLighting(e);null!==a&&e.stack.add(a);const i=nn(s,yn.a).max(0);n=this.setupOutput(e,i),Dn.assign(n);const o=null!==this.outputNode;if(o&&(n=this.outputNode),null!==r){const e=t.getMRT(),r=this.mrtNode;null!==e?(o&&Dn.assign(n),n=e,null!==r&&(n=e.merge(r))):null!==r&&(n=r)}}else{let t=this.fragmentNode;!0!==t.isOutputStructNode&&(t=nn(t)),n=this.setupOutput(e,t)}e.stack.outputNode=n,e.addFlow("fragment",e.removeStack()),e.observer=this.setupObserver(e)}setupClipping(e){if(null===e.clippingContext)return null;const{unionPlanes:t,intersectionPlanes:r}=e.clippingContext;let s=null;if(t.length>0||r.length>0){const t=e.renderer.samples;this.alphaToCoverage&&t>1?s=Li(new Yh(Yh.ALPHA_TO_COVERAGE)):e.stack.add(Li(new Yh))}return s}setupHardwareClipping(e){if(this.hardwareClipping=!1,null===e.clippingContext)return;const t=e.clippingContext.unionPlanes.length;t>0&&t<=8&&e.isAvailable("clipDistance")&&(e.stack.add(Li(new Yh(Yh.HARDWARE))),this.hardwareClipping=!0)}setupDepth(e){const{renderer:t,camera:r}=e;let s=this.depthNode;if(null===s){const e=t.getMRT();e&&e.has("depth")?s=e.get("depth"):!0===t.logarithmicDepthBuffer&&(s=r.isPerspectiveCamera?Wh(Gl.z,ol,ul):zh(Gl.z,ol,ul))}null!==s&&qh.assign(s).toStack()}setupPositionView(){return Bl.mul(Vl).xyz}setupModelViewProjection(){return ll.mul(Gl)}setupVertex(e){return e.addStack(),this.setupPosition(e),e.context.vertex=e.removeStack(),Hc}setupPosition(e){const{object:t,geometry:r}=e;if((r.morphAttributes.position||r.morphAttributes.normal||r.morphAttributes.color)&&yh(t).toStack(),!0===t.isSkinnedMesh&&lh(t).toStack(),this.displacementMap){const e=Sd("displacementMap","texture"),t=Sd("displacementScale","float"),r=Sd("displacementBias","float");Vl.addAssign(Xl.normalize().mul(e.x.mul(t).add(r)))}return t.isBatchedMesh&&rh(t).toStack(),t.isInstancedMesh&&t.instanceMatrix&&!0===t.instanceMatrix.isInstancedBufferAttribute&&eh(t).toStack(),null!==this.positionNode&&Vl.assign(iu(this.positionNode,"POSITION","vec3")),Vl}setupDiffuseColor({object:e,geometry:t}){null!==this.maskNode&&Ki(this.maskNode).not().discard();let r=this.colorNode?nn(this.colorNode):ac;if(!0===this.vertexColors&&t.hasAttribute("color")&&(r=r.mul(tp())),e.instanceColor){r=fn("vec3","vInstanceColor").mul(r)}if(e.isBatchedMesh&&e._colorsTexture){r=fn("vec3","vBatchColor").mul(r)}yn.assign(r);const s=this.opacityNode?ji(this.opacityNode):lc;yn.a.assign(yn.a.mul(s));let i=null;(null!==this.alphaTestNode||this.alphaTest>0)&&(i=null!==this.alphaTestNode?ji(this.alphaTestNode):nc,yn.a.lessThanEqual(i).discard()),!0===this.alphaHash&&yn.a.lessThan(Jh(Vl)).discard();!1===this.transparent&&this.blending===k&&!1===this.alphaToCoverage?yn.a.assign(1):null===i&&yn.a.lessThanEqual(0).discard()}setupVariants(){}setupOutgoingLight(){return!0===this.lights?en(0):yn.rgb}setupNormal(){return this.normalNode?en(this.normalNode):yc}setupEnvironment(){let e=null;return this.envNode?e=this.envNode:this.envMap&&(e=this.envMap.isCubeTexture?Sd("envMap","cubeTexture"):Sd("envMap","texture")),e}setupLightMap(e){let t=null;return e.material.lightMap&&(t=new vh(kc)),t}setupLights(e){const t=[],r=this.setupEnvironment(e);r&&r.isLightingNode&&t.push(r);const s=this.setupLightMap(e);if(s&&s.isLightingNode&&t.push(s),null!==this.aoNode||e.material.aoMap){const e=null!==this.aoNode?this.aoNode:Gc;t.push(new xh(e))}let i=this.lightsNode||e.lightsNode;return t.length>0&&(i=e.renderer.lighting.createNode([...i.getLights(),...t])),i}setupLightingModel(){}setupLighting(e){const{material:t}=e,{backdropNode:r,backdropAlphaNode:s,emissiveNode:i}=this,n=!0===this.lights||null!==this.lightsNode?this.setupLights(e):null;let a=this.setupOutgoingLight(e);if(n&&n.getScope().hasLights){const t=this.setupLightingModel(e)||null;a=_h(n,t,r,s)}else null!==r&&(a=en(null!==s?Lo(a,r,s):r));return(i&&!0===i.isNode||t.emissive&&!0===t.emissive.isColor)&&(bn.assign(en(i||uc)),a=a.add(bn)),a}setupFog(e,t){const r=e.fogNode;return r&&(Dn.assign(t),t=nn(r)),t}setupPremultipliedAlpha(e,t){return op(t)}setupOutput(e,t){return!0===this.fog&&(t=this.setupFog(e,t)),!0===this.premultipliedAlpha&&(t=this.setupPremultipliedAlpha(e,t)),t}setDefaultValues(e){for(const t in e){const r=e[t];void 0===this[t]&&(this[t]=r,r&&r.clone&&(this[t]=r.clone()))}const t=Object.getOwnPropertyDescriptors(e.constructor.prototype);for(const e in t)void 0===Object.getOwnPropertyDescriptor(this.constructor.prototype,e)&&void 0!==t[e].get&&Object.defineProperty(this.constructor.prototype,e,t[e])}toJSON(e){const t=void 0===e||"string"==typeof e;t&&(e={textures:{},images:{},nodes:{}});const r=O.prototype.toJSON.call(this,e),s=vs(this);r.inputNodes={};for(const{property:t,childNode:i}of s)r.inputNodes[t]=i.toJSON(e).uuid;function i(e){const t=[];for(const r in e){const s=e[r];delete s.metadata,t.push(s)}return t}if(t){const t=i(e.textures),s=i(e.images),n=i(e.nodes);t.length>0&&(r.textures=t),s.length>0&&(r.images=s),n.length>0&&(r.nodes=n)}return r}copy(e){return this.lightsNode=e.lightsNode,this.envNode=e.envNode,this.colorNode=e.colorNode,this.normalNode=e.normalNode,this.opacityNode=e.opacityNode,this.backdropNode=e.backdropNode,this.backdropAlphaNode=e.backdropAlphaNode,this.alphaTestNode=e.alphaTestNode,this.maskNode=e.maskNode,this.positionNode=e.positionNode,this.geometryNode=e.geometryNode,this.depthNode=e.depthNode,this.receivedShadowPositionNode=e.receivedShadowPositionNode,this.castShadowPositionNode=e.castShadowPositionNode,this.receivedShadowNode=e.receivedShadowNode,this.castShadowNode=e.castShadowNode,this.outputNode=e.outputNode,this.mrtNode=e.mrtNode,this.fragmentNode=e.fragmentNode,this.vertexNode=e.vertexNode,super.copy(e)}}const dp=new G;class cp extends lp{static get type(){return"LineBasicNodeMaterial"}constructor(e){super(),this.isLineBasicNodeMaterial=!0,this.setDefaultValues(dp),this.setValues(e)}}const hp=new z;class pp extends lp{static get type(){return"LineDashedNodeMaterial"}constructor(e){super(),this.isLineDashedNodeMaterial=!0,this.setDefaultValues(hp),this.dashOffset=0,this.offsetNode=null,this.dashScaleNode=null,this.dashSizeNode=null,this.gapSizeNode=null,this.setValues(e)}setupVariants(){const e=this.offsetNode?ji(this.offsetNode):Vc,t=this.dashScaleNode?ji(this.dashScaleNode):Fc,r=this.dashSizeNode?ji(this.dashSizeNode):Lc,s=this.gapSizeNode?ji(this.gapSizeNode):Dc;In.assign(r),Vn.assign(s);const i=au(Hu("lineDistance").mul(t));(e?i.add(e):i).mod(In.add(Vn)).greaterThan(In).discard()}}let gp=null;class mp extends Dh{static get type(){return"ViewportSharedTextureNode"}constructor(e=wh,t=null){null===gp&&(gp=new I),super(e,t,gp)}updateReference(){return this}}const fp=Vi(mp).setParameterLength(0,2),yp=new z;class bp extends lp{static get type(){return"Line2NodeMaterial"}constructor(e={}){super(),this.isLine2NodeMaterial=!0,this.setDefaultValues(yp),this.useColor=e.vertexColors,this.dashOffset=0,this.lineWidth=1,this.lineColorNode=null,this.offsetNode=null,this.dashScaleNode=null,this.dashSizeNode=null,this.gapSizeNode=null,this.blending=H,this._useDash=e.dashed,this._useAlphaToCoverage=!0,this._useWorldUnits=!1,this.setValues(e)}setup(e){const{renderer:t}=e,r=this._useAlphaToCoverage,s=this.useColor,i=this._useDash,n=this._useWorldUnits,a=ki(({start:e,end:t})=>{const r=ll.element(2).element(2),s=ll.element(3).element(2).mul(-.5).div(r).sub(e.z).div(t.z.sub(e.z));return nn(Lo(e.xyz,t.xyz,s),t.w)}).setLayout({name:"trimSegment",type:"vec4",inputs:[{name:"start",type:"vec4"},{name:"end",type:"vec4"}]});this.vertexNode=ki(()=>{const e=Hu("instanceStart"),t=Hu("instanceEnd"),r=nn(Bl.mul(nn(e,1))).toVar("start"),s=nn(Bl.mul(nn(t,1))).toVar("end");if(i){const e=this.dashScaleNode?ji(this.dashScaleNode):Fc,t=this.offsetNode?ji(this.offsetNode):Vc,r=Hu("instanceDistanceStart"),s=Hu("instanceDistanceEnd");let i=Il.y.lessThan(.5).select(e.mul(r),e.mul(s));i=i.add(t),fn("float","lineDistance").assign(i)}n&&(fn("vec3","worldStart").assign(r.xyz),fn("vec3","worldEnd").assign(s.xyz));const o=Ch.z.div(Ch.w),u=ll.element(2).element(3).equal(-1);Hi(u,()=>{Hi(r.z.lessThan(0).and(s.z.greaterThan(0)),()=>{s.assign(a({start:r,end:s}))}).ElseIf(s.z.lessThan(0).and(r.z.greaterThanEqual(0)),()=>{r.assign(a({start:s,end:r}))})});const l=ll.mul(r),d=ll.mul(s),c=l.xyz.div(l.w),h=d.xyz.div(d.w),p=h.xy.sub(c.xy).toVar();p.x.assign(p.x.mul(o)),p.assign(p.normalize());const g=nn().toVar();if(n){const e=s.xyz.sub(r.xyz).normalize(),t=Lo(r.xyz,s.xyz,.5).normalize(),n=e.cross(t).normalize(),a=e.cross(n),o=fn("vec4","worldPos");o.assign(Il.y.lessThan(.5).select(r,s));const u=Ic.mul(.5);o.addAssign(nn(Il.x.lessThan(0).select(n.mul(u),n.mul(u).negate()),0)),i||(o.addAssign(nn(Il.y.lessThan(.5).select(e.mul(u).negate(),e.mul(u)),0)),o.addAssign(nn(a.mul(u),0)),Hi(Il.y.greaterThan(1).or(Il.y.lessThan(0)),()=>{o.subAssign(nn(a.mul(2).mul(u),0))})),g.assign(ll.mul(o));const l=en().toVar();l.assign(Il.y.lessThan(.5).select(c,h)),g.z.assign(l.z.mul(g.w))}else{const e=Yi(p.y,p.x.negate()).toVar("offset");p.x.assign(p.x.div(o)),e.x.assign(e.x.div(o)),e.assign(Il.x.lessThan(0).select(e.negate(),e)),Hi(Il.y.lessThan(0),()=>{e.assign(e.sub(p))}).ElseIf(Il.y.greaterThan(1),()=>{e.assign(e.add(p))}),e.assign(e.mul(Ic)),e.assign(e.div(Ch.w)),g.assign(Il.y.lessThan(.5).select(l,d)),e.assign(e.mul(g.w)),g.assign(g.add(nn(e,0,0)))}return g})();const o=ki(({p1:e,p2:t,p3:r,p4:s})=>{const i=e.sub(r),n=s.sub(r),a=t.sub(e),o=i.dot(n),u=n.dot(a),l=i.dot(a),d=n.dot(n),c=a.dot(a).mul(d).sub(u.mul(u)),h=o.mul(u).sub(l.mul(d)).div(c).clamp(),p=o.add(u.mul(h)).div(d).clamp();return Yi(h,p)});if(this.colorNode=ki(()=>{const e=$u();if(i){const t=this.dashSizeNode?ji(this.dashSizeNode):Lc,r=this.gapSizeNode?ji(this.gapSizeNode):Dc;In.assign(t),Vn.assign(r);const s=fn("float","lineDistance");e.y.lessThan(-1).or(e.y.greaterThan(1)).discard(),s.mod(In.add(Vn)).greaterThan(In).discard()}const a=ji(1).toVar("alpha");if(n){const e=fn("vec3","worldStart"),s=fn("vec3","worldEnd"),n=fn("vec4","worldPos").xyz.normalize().mul(1e5),u=s.sub(e),l=o({p1:e,p2:s,p3:en(0,0,0),p4:n}),d=e.add(u.mul(l.x)),c=n.mul(l.y),h=d.sub(c).length().div(Ic);if(!i)if(r&&t.samples>1){const e=h.fwidth();a.assign(Uo(e.negate().add(.5),e.add(.5),h).oneMinus())}else h.greaterThan(.5).discard()}else if(r&&t.samples>1){const t=e.x,r=e.y.greaterThan(0).select(e.y.sub(1),e.y.add(1)),s=t.mul(t).add(r.mul(r)),i=ji(s.fwidth()).toVar("dlen");Hi(e.y.abs().greaterThan(1),()=>{a.assign(Uo(i.oneMinus(),i.add(1),s).oneMinus())})}else Hi(e.y.abs().greaterThan(1),()=>{const t=e.x,r=e.y.greaterThan(0).select(e.y.sub(1),e.y.add(1));t.mul(t).add(r.mul(r)).greaterThan(1).discard()});let u;if(this.lineColorNode)u=this.lineColorNode;else if(s){const e=Hu("instanceColorStart"),t=Hu("instanceColorEnd");u=Il.y.lessThan(.5).select(e,t).mul(ac)}else u=ac;return nn(u,a)})(),this.transparent){const e=this.opacityNode?ji(this.opacityNode):lc;this.outputNode=nn(this.colorNode.rgb.mul(e).add(fp().rgb.mul(e.oneMinus())),this.colorNode.a)}super.setup(e)}get worldUnits(){return this._useWorldUnits}set worldUnits(e){this._useWorldUnits!==e&&(this._useWorldUnits=e,this.needsUpdate=!0)}get dashed(){return this._useDash}set dashed(e){this._useDash!==e&&(this._useDash=e,this.needsUpdate=!0)}get alphaToCoverage(){return this._useAlphaToCoverage}set alphaToCoverage(e){this._useAlphaToCoverage!==e&&(this._useAlphaToCoverage=e,this.needsUpdate=!0)}}const xp=e=>Li(e).mul(.5).add(.5),Tp=new $;class _p extends lp{static get type(){return"MeshNormalNodeMaterial"}constructor(e){super(),this.isMeshNormalNodeMaterial=!0,this.setDefaultValues(Tp),this.setValues(e)}setupDiffuseColor(){const e=this.opacityNode?ji(this.opacityNode):lc;yn.assign(pu(nn(xp(Zl),e),W))}}const vp=ki(([e=kl])=>{const t=e.z.atan(e.x).mul(1/(2*Math.PI)).add(.5),r=e.y.clamp(-1,1).asin().mul(1/Math.PI).add(.5);return Yi(t,r)});class Np extends j{constructor(e=1,t={}){super(e,t),this.isCubeRenderTarget=!0}fromEquirectangularTexture(e,t){const r=t.minFilter,s=t.generateMipmaps;t.generateMipmaps=!0,this.texture.type=t.type,this.texture.colorSpace=t.colorSpace,this.texture.generateMipmaps=t.generateMipmaps,this.texture.minFilter=t.minFilter,this.texture.magFilter=t.magFilter;const i=new q(5,5,5),n=vp(kl),a=new lp;a.colorNode=Zu(t,n,0),a.side=S,a.blending=H;const o=new X(i,a),u=new K;u.add(o),t.minFilter===V&&(t.minFilter=Y);const l=new Q(1,10,this),d=e.getMRT();return e.setMRT(null),l.update(e,u),e.setMRT(d),t.minFilter=r,t.currentGenerateMipmaps=s,o.geometry.dispose(),o.material.dispose(),this}}const Sp=new WeakMap;class Ep extends Ks{static get type(){return"CubeMapNode"}constructor(e){super("vec3"),this.envNode=e,this._cubeTexture=null,this._cubeTextureNode=bd(null);const t=new A;t.isRenderTargetTexture=!0,this._defaultTexture=t,this.updateBeforeType=Vs.RENDER}updateBefore(e){const{renderer:t,material:r}=e,s=this.envNode;if(s.isTextureNode||s.isMaterialReferenceNode){const e=s.isTextureNode?s.value:r[s.property];if(e&&e.isTexture){const r=e.mapping;if(r===Z||r===J){if(Sp.has(e)){const t=Sp.get(e);Ap(t,e.mapping),this._cubeTexture=t}else{const r=e.image;if(function(e){return null!=e&&e.height>0}(r)){const s=new Np(r.height);s.fromEquirectangularTexture(t,e),Ap(s.texture,e.mapping),this._cubeTexture=s.texture,Sp.set(e,s.texture),e.addEventListener("dispose",wp)}else this._cubeTexture=this._defaultTexture}this._cubeTextureNode.value=this._cubeTexture}else this._cubeTextureNode=this.envNode}}}setup(e){return this.updateBefore(e),this._cubeTextureNode}}function wp(e){const t=e.target;t.removeEventListener("dispose",wp);const r=Sp.get(t);void 0!==r&&(Sp.delete(t),r.dispose())}function Ap(e,t){t===Z?e.mapping=R:t===J&&(e.mapping=C)}const Rp=Vi(Ep).setParameterLength(1);class Cp extends bh{static get type(){return"BasicEnvironmentNode"}constructor(e=null){super(),this.envNode=e}setup(e){e.context.environment=Rp(this.envNode)}}class Mp extends bh{static get type(){return"BasicLightMapNode"}constructor(e=null){super(),this.lightMapNode=e}setup(e){const t=ji(1/Math.PI);e.context.irradianceLightMap=this.lightMapNode.mul(t)}}class Pp{start(e){e.lightsNode.setupLights(e,e.lightsNode.getLightNodes(e)),this.indirect(e)}finish(){}direct(){}directRectArea(){}indirect(){}ambientOcclusion(){}}class Bp extends Pp{constructor(){super()}indirect({context:e}){const t=e.ambientOcclusion,r=e.reflectedLight,s=e.irradianceLightMap;r.indirectDiffuse.assign(nn(0)),s?r.indirectDiffuse.addAssign(s):r.indirectDiffuse.addAssign(nn(1,1,1,0)),r.indirectDiffuse.mulAssign(t),r.indirectDiffuse.mulAssign(yn.rgb)}finish(e){const{material:t,context:r}=e,s=r.outgoingLight,i=e.context.environment;if(i)switch(t.combine){case re:s.rgb.assign(Lo(s.rgb,s.rgb.mul(i.rgb),pc.mul(gc)));break;case te:s.rgb.assign(Lo(s.rgb,i.rgb,pc.mul(gc)));break;case ee:s.rgb.addAssign(i.rgb.mul(pc.mul(gc)));break;default:console.warn("THREE.BasicLightingModel: Unsupported .combine value:",t.combine)}}}const Fp=new se;class Lp extends lp{static get type(){return"MeshBasicNodeMaterial"}constructor(e){super(),this.isMeshBasicNodeMaterial=!0,this.lights=!0,this.setDefaultValues(Fp),this.setValues(e)}setupNormal(){return jl(Yl)}setupEnvironment(e){const t=super.setupEnvironment(e);return t?new Cp(t):null}setupLightMap(e){let t=null;return e.material.lightMap&&(t=new Mp(kc)),t}setupOutgoingLight(){return yn.rgb}setupLightingModel(){return new Bp}}const Dp=ki(({f0:e,f90:t,dotVH:r})=>{const s=r.mul(-5.55473).sub(6.98316).mul(r).exp2();return e.mul(s.oneMinus()).add(t.mul(s))}),Ip=ki(e=>e.diffuseColor.mul(1/Math.PI)),Vp=ki(({dotNH:e})=>Ln.mul(ji(.5)).add(1).mul(ji(1/Math.PI)).mul(e.pow(Ln))),Up=ki(({lightDirection:e})=>{const t=e.add(zl).normalize(),r=Zl.dot(t).clamp(),s=zl.dot(t).clamp(),i=Dp({f0:Bn,f90:1,dotVH:s}),n=ji(.25),a=Vp({dotNH:r});return i.mul(n).mul(a)});class Op extends Bp{constructor(e=!0){super(),this.specular=e}direct({lightDirection:e,lightColor:t,reflectedLight:r}){const s=Zl.dot(e).clamp().mul(t);r.directDiffuse.addAssign(s.mul(Ip({diffuseColor:yn.rgb}))),!0===this.specular&&r.directSpecular.addAssign(s.mul(Up({lightDirection:e})).mul(pc))}indirect(e){const{ambientOcclusion:t,irradiance:r,reflectedLight:s}=e.context;s.indirectDiffuse.addAssign(r.mul(Ip({diffuseColor:yn}))),s.indirectDiffuse.mulAssign(t)}}const kp=new ie;class Gp extends lp{static get type(){return"MeshLambertNodeMaterial"}constructor(e){super(),this.isMeshLambertNodeMaterial=!0,this.lights=!0,this.setDefaultValues(kp),this.setValues(e)}setupEnvironment(e){const t=super.setupEnvironment(e);return t?new Cp(t):null}setupLightingModel(){return new Op(!1)}}const zp=new ne;class Hp extends lp{static get type(){return"MeshPhongNodeMaterial"}constructor(e){super(),this.isMeshPhongNodeMaterial=!0,this.lights=!0,this.shininessNode=null,this.specularNode=null,this.setDefaultValues(zp),this.setValues(e)}setupEnvironment(e){const t=super.setupEnvironment(e);return t?new Cp(t):null}setupLightingModel(){return new Op}setupVariants(){const e=(this.shininessNode?ji(this.shininessNode):oc).max(1e-4);Ln.assign(e);const t=this.specularNode||dc;Bn.assign(t)}copy(e){return this.shininessNode=e.shininessNode,this.specularNode=e.specularNode,super.copy(e)}}const $p=ki(e=>{if(!1===e.geometry.hasAttribute("normal"))return ji(0);const t=Yl.dFdx().abs().max(Yl.dFdy().abs());return t.x.max(t.y).max(t.z)}),Wp=ki(e=>{const{roughness:t}=e,r=$p();let s=t.max(.0525);return s=s.add(r),s=s.min(1),s}),jp=ki(({alpha:e,dotNL:t,dotNV:r})=>{const s=e.pow2(),i=t.mul(s.add(s.oneMinus().mul(r.pow2())).sqrt()),n=r.mul(s.add(s.oneMinus().mul(t.pow2())).sqrt());return da(.5,i.add(n).max(La))}).setLayout({name:"V_GGX_SmithCorrelated",type:"float",inputs:[{name:"alpha",type:"float"},{name:"dotNL",type:"float"},{name:"dotNV",type:"float"}]}),qp=ki(({alphaT:e,alphaB:t,dotTV:r,dotBV:s,dotTL:i,dotBL:n,dotNV:a,dotNL:o})=>{const u=o.mul(en(e.mul(r),t.mul(s),a).length()),l=a.mul(en(e.mul(i),t.mul(n),o).length());return da(.5,u.add(l)).saturate()}).setLayout({name:"V_GGX_SmithCorrelated_Anisotropic",type:"float",inputs:[{name:"alphaT",type:"float",qualifier:"in"},{name:"alphaB",type:"float",qualifier:"in"},{name:"dotTV",type:"float",qualifier:"in"},{name:"dotBV",type:"float",qualifier:"in"},{name:"dotTL",type:"float",qualifier:"in"},{name:"dotBL",type:"float",qualifier:"in"},{name:"dotNV",type:"float",qualifier:"in"},{name:"dotNL",type:"float",qualifier:"in"}]}),Xp=ki(({alpha:e,dotNH:t})=>{const r=e.pow2(),s=t.pow2().mul(r.oneMinus()).oneMinus();return r.div(s.pow2()).mul(1/Math.PI)}).setLayout({name:"D_GGX",type:"float",inputs:[{name:"alpha",type:"float"},{name:"dotNH",type:"float"}]}),Kp=ji(1/Math.PI),Yp=ki(({alphaT:e,alphaB:t,dotNH:r,dotTH:s,dotBH:i})=>{const n=e.mul(t),a=en(t.mul(s),e.mul(i),n.mul(r)),o=a.dot(a),u=n.div(o);return Kp.mul(n.mul(u.pow2()))}).setLayout({name:"D_GGX_Anisotropic",type:"float",inputs:[{name:"alphaT",type:"float",qualifier:"in"},{name:"alphaB",type:"float",qualifier:"in"},{name:"dotNH",type:"float",qualifier:"in"},{name:"dotTH",type:"float",qualifier:"in"},{name:"dotBH",type:"float",qualifier:"in"}]}),Qp=ki(({lightDirection:e,f0:t,f90:r,roughness:s,f:i,normalView:n=Zl,USE_IRIDESCENCE:a,USE_ANISOTROPY:o})=>{const u=s.pow2(),l=e.add(zl).normalize(),d=n.dot(e).clamp(),c=n.dot(zl).clamp(),h=n.dot(l).clamp(),p=zl.dot(l).clamp();let g,m,f=Dp({f0:t,f90:r,dotVH:p});if(Pi(a)&&(f=En.mix(f,i)),Pi(o)){const t=Mn.dot(e),r=Mn.dot(zl),s=Mn.dot(l),i=Pn.dot(e),n=Pn.dot(zl),a=Pn.dot(l);g=qp({alphaT:Rn,alphaB:u,dotTV:r,dotBV:n,dotTL:t,dotBL:i,dotNV:c,dotNL:d}),m=Yp({alphaT:Rn,alphaB:u,dotNH:h,dotTH:s,dotBH:a})}else g=jp({alpha:u,dotNL:d,dotNV:c}),m=Xp({alpha:u,dotNH:h});return f.mul(g).mul(m)}),Zp=ki(({roughness:e,dotNV:t})=>{const r=nn(-1,-.0275,-.572,.022),s=nn(1,.0425,1.04,-.04),i=e.mul(r).add(s),n=i.x.mul(i.x).min(t.mul(-9.28).exp2()).mul(i.x).add(i.y);return Yi(-1.04,1.04).mul(n).add(i.zw)}).setLayout({name:"DFGApprox",type:"vec2",inputs:[{name:"roughness",type:"float"},{name:"dotNV",type:"vec3"}]}),Jp=ki(e=>{const{dotNV:t,specularColor:r,specularF90:s,roughness:i}=e,n=Zp({dotNV:t,roughness:i});return r.mul(n.x).add(s.mul(n.y))}),eg=ki(({f:e,f90:t,dotVH:r})=>{const s=r.oneMinus().saturate(),i=s.mul(s),n=s.mul(i,i).clamp(0,.9999);return e.sub(en(t).mul(n)).div(n.oneMinus())}).setLayout({name:"Schlick_to_F0",type:"vec3",inputs:[{name:"f",type:"vec3"},{name:"f90",type:"float"},{name:"dotVH",type:"float"}]}),tg=ki(({roughness:e,dotNH:t})=>{const r=e.pow2(),s=ji(1).div(r),i=t.pow2().oneMinus().max(.0078125);return ji(2).add(s).mul(i.pow(s.mul(.5))).div(2*Math.PI)}).setLayout({name:"D_Charlie",type:"float",inputs:[{name:"roughness",type:"float"},{name:"dotNH",type:"float"}]}),rg=ki(({dotNV:e,dotNL:t})=>ji(1).div(ji(4).mul(t.add(e).sub(t.mul(e))))).setLayout({name:"V_Neubelt",type:"float",inputs:[{name:"dotNV",type:"float"},{name:"dotNL",type:"float"}]}),sg=ki(({lightDirection:e})=>{const t=e.add(zl).normalize(),r=Zl.dot(e).clamp(),s=Zl.dot(zl).clamp(),i=Zl.dot(t).clamp(),n=tg({roughness:Sn,dotNH:i}),a=rg({dotNV:s,dotNL:r});return Nn.mul(n).mul(a)}),ig=ki(({N:e,V:t,roughness:r})=>{const s=e.dot(t).saturate(),i=Yi(r,s.oneMinus().sqrt());return i.assign(i.mul(.984375).add(.0078125)),i}).setLayout({name:"LTC_Uv",type:"vec2",inputs:[{name:"N",type:"vec3"},{name:"V",type:"vec3"},{name:"roughness",type:"float"}]}),ng=ki(({f:e})=>{const t=e.length();return To(t.mul(t).add(e.z).div(t.add(1)),0)}).setLayout({name:"LTC_ClippedSphereFormFactor",type:"float",inputs:[{name:"f",type:"vec3"}]}),ag=ki(({v1:e,v2:t})=>{const r=e.dot(t),s=r.abs().toVar(),i=s.mul(.0145206).add(.4965155).mul(s).add(.8543985).toVar(),n=s.add(4.1616724).mul(s).add(3.417594).toVar(),a=i.div(n),o=r.greaterThan(0).select(a,To(r.mul(r).oneMinus(),1e-7).inverseSqrt().mul(.5).sub(a));return e.cross(t).mul(o)}).setLayout({name:"LTC_EdgeVectorFormFactor",type:"vec3",inputs:[{name:"v1",type:"vec3"},{name:"v2",type:"vec3"}]}),og=ki(({N:e,V:t,P:r,mInv:s,p0:i,p1:n,p2:a,p3:o})=>{const u=n.sub(i).toVar(),l=o.sub(i).toVar(),d=u.cross(l),c=en().toVar();return Hi(d.dot(r.sub(i)).greaterThanEqual(0),()=>{const u=t.sub(e.mul(t.dot(e))).normalize(),l=e.cross(u).negate(),d=s.mul(dn(u,l,e).transpose()).toVar(),h=d.mul(i.sub(r)).normalize().toVar(),p=d.mul(n.sub(r)).normalize().toVar(),g=d.mul(a.sub(r)).normalize().toVar(),m=d.mul(o.sub(r)).normalize().toVar(),f=en(0).toVar();f.addAssign(ag({v1:h,v2:p})),f.addAssign(ag({v1:p,v2:g})),f.addAssign(ag({v1:g,v2:m})),f.addAssign(ag({v1:m,v2:h})),c.assign(en(ng({f:f})))}),c}).setLayout({name:"LTC_Evaluate",type:"vec3",inputs:[{name:"N",type:"vec3"},{name:"V",type:"vec3"},{name:"P",type:"vec3"},{name:"mInv",type:"mat3"},{name:"p0",type:"vec3"},{name:"p1",type:"vec3"},{name:"p2",type:"vec3"},{name:"p3",type:"vec3"}]}),ug=ki(({P:e,p0:t,p1:r,p2:s,p3:i})=>{const n=r.sub(t).toVar(),a=i.sub(t).toVar(),o=n.cross(a),u=en().toVar();return Hi(o.dot(e.sub(t)).greaterThanEqual(0),()=>{const n=t.sub(e).normalize().toVar(),a=r.sub(e).normalize().toVar(),o=s.sub(e).normalize().toVar(),l=i.sub(e).normalize().toVar(),d=en(0).toVar();d.addAssign(ag({v1:n,v2:a})),d.addAssign(ag({v1:a,v2:o})),d.addAssign(ag({v1:o,v2:l})),d.addAssign(ag({v1:l,v2:n})),u.assign(en(ng({f:d.abs()})))}),u}).setLayout({name:"LTC_Evaluate",type:"vec3",inputs:[{name:"P",type:"vec3"},{name:"p0",type:"vec3"},{name:"p1",type:"vec3"},{name:"p2",type:"vec3"},{name:"p3",type:"vec3"}]}),lg=1/6,dg=e=>la(lg,la(e,la(e,e.negate().add(3)).sub(3)).add(1)),cg=e=>la(lg,la(e,la(e,la(3,e).sub(6))).add(4)),hg=e=>la(lg,la(e,la(e,la(-3,e).add(3)).add(3)).add(1)),pg=e=>la(lg,Ao(e,3)),gg=e=>dg(e).add(cg(e)),mg=e=>hg(e).add(pg(e)),fg=e=>oa(-1,cg(e).div(dg(e).add(cg(e)))),yg=e=>oa(1,pg(e).div(hg(e).add(pg(e)))),bg=(e,t,r)=>{const s=e.uvNode,i=la(s,t.zw).add(.5),n=Xa(i),a=Qa(i),o=gg(a.x),u=mg(a.x),l=fg(a.x),d=yg(a.x),c=fg(a.y),h=yg(a.y),p=Yi(n.x.add(l),n.y.add(c)).sub(.5).mul(t.xy),g=Yi(n.x.add(d),n.y.add(c)).sub(.5).mul(t.xy),m=Yi(n.x.add(l),n.y.add(h)).sub(.5).mul(t.xy),f=Yi(n.x.add(d),n.y.add(h)).sub(.5).mul(t.xy),y=gg(a.y).mul(oa(o.mul(e.sample(p).level(r)),u.mul(e.sample(g).level(r)))),b=mg(a.y).mul(oa(o.mul(e.sample(m).level(r)),u.mul(e.sample(f).level(r))));return y.add(b)},xg=ki(([e,t])=>{const r=Yi(e.size(qi(t))),s=Yi(e.size(qi(t.add(1)))),i=da(1,r),n=da(1,s),a=bg(e,nn(i,r),Xa(t)),o=bg(e,nn(n,s),Ka(t));return Qa(t).mix(a,o)}),Tg=ki(([e,t])=>{const r=t.mul(Xu(e));return xg(e,r)}),_g=ki(([e,t,r,s,i])=>{const n=en(Vo(t.negate(),Ya(e),da(1,s))),a=en(ao(i[0].xyz),ao(i[1].xyz),ao(i[2].xyz));return Ya(n).mul(r.mul(a))}).setLayout({name:"getVolumeTransmissionRay",type:"vec3",inputs:[{name:"n",type:"vec3"},{name:"v",type:"vec3"},{name:"thickness",type:"float"},{name:"ior",type:"float"},{name:"modelMatrix",type:"mat4"}]}),vg=ki(([e,t])=>e.mul(Do(t.mul(2).sub(2),0,1))).setLayout({name:"applyIorToRoughness",type:"float",inputs:[{name:"roughness",type:"float"},{name:"ior",type:"float"}]}),Ng=Vh(),Sg=Vh(),Eg=ki(([e,t,r],{material:s})=>{const i=(s.side===S?Ng:Sg).sample(e),n=Wa(Ah.x).mul(vg(t,r));return xg(i,n)}),wg=ki(([e,t,r])=>(Hi(r.notEqual(0),()=>{const s=$a(t).negate().div(r);return za(s.negate().mul(e))}),en(1))).setLayout({name:"volumeAttenuation",type:"vec3",inputs:[{name:"transmissionDistance",type:"float"},{name:"attenuationColor",type:"vec3"},{name:"attenuationDistance",type:"float"}]}),Ag=ki(([e,t,r,s,i,n,a,o,u,l,d,c,h,p,g])=>{let m,f;if(g){m=nn().toVar(),f=en().toVar();const i=d.sub(1).mul(g.mul(.025)),n=en(d.sub(i),d,d.add(i));ch({start:0,end:3},({i:i})=>{const d=n.element(i),g=_g(e,t,c,d,o),y=a.add(g),b=l.mul(u.mul(nn(y,1))),x=Yi(b.xy.div(b.w)).toVar();x.addAssign(1),x.divAssign(2),x.assign(Yi(x.x,x.y.oneMinus()));const T=Eg(x,r,d);m.element(i).assign(T.element(i)),m.a.addAssign(T.a),f.element(i).assign(s.element(i).mul(wg(ao(g),h,p).element(i)))}),m.a.divAssign(3)}else{const i=_g(e,t,c,d,o),n=a.add(i),g=l.mul(u.mul(nn(n,1))),y=Yi(g.xy.div(g.w)).toVar();y.addAssign(1),y.divAssign(2),y.assign(Yi(y.x,y.y.oneMinus())),m=Eg(y,r,d),f=s.mul(wg(ao(i),h,p))}const y=f.rgb.mul(m.rgb),b=e.dot(t).clamp(),x=en(Jp({dotNV:b,specularColor:i,specularF90:n,roughness:r})),T=f.r.add(f.g,f.b).div(3);return nn(x.oneMinus().mul(y),m.a.oneMinus().mul(T).oneMinus())}),Rg=dn(3.2404542,-.969266,.0556434,-1.5371385,1.8760108,-.2040259,-.4985314,.041556,1.0572252),Cg=(e,t)=>e.sub(t).div(e.add(t)).pow2(),Mg=ki(({outsideIOR:e,eta2:t,cosTheta1:r,thinFilmThickness:s,baseF0:i})=>{const n=Lo(e,t,Uo(0,.03,s)),a=e.div(n).pow2().mul(r.pow2().oneMinus()).oneMinus();Hi(a.lessThan(0),()=>en(1));const o=a.sqrt(),u=Cg(n,e),l=Dp({f0:u,f90:1,dotVH:r}),d=l.oneMinus(),c=n.lessThan(e).select(Math.PI,0),h=ji(Math.PI).sub(c),p=(e=>{const t=e.sqrt();return en(1).add(t).div(en(1).sub(t))})(i.clamp(0,.9999)),g=Cg(p,n.toVec3()),m=Dp({f0:g,f90:1,dotVH:o}),f=en(p.x.lessThan(n).select(Math.PI,0),p.y.lessThan(n).select(Math.PI,0),p.z.lessThan(n).select(Math.PI,0)),y=n.mul(s,o,2),b=en(h).add(f),x=l.mul(m).clamp(1e-5,.9999),T=x.sqrt(),_=d.pow2().mul(m).div(en(1).sub(x)),v=l.add(_).toVar(),N=_.sub(d).toVar();return ch({start:1,end:2,condition:"<=",name:"m"},({m:e})=>{N.mulAssign(T);const t=((e,t)=>{const r=e.mul(2*Math.PI*1e-9),s=en(54856e-17,44201e-17,52481e-17),i=en(1681e3,1795300,2208400),n=en(43278e5,93046e5,66121e5),a=ji(9747e-17*Math.sqrt(2*Math.PI*45282e5)).mul(r.mul(2239900).add(t.x).cos()).mul(r.pow2().mul(-45282e5).exp());let o=s.mul(n.mul(2*Math.PI).sqrt()).mul(i.mul(r).add(t).cos()).mul(r.pow2().negate().mul(n).exp());return o=en(o.x.add(a),o.y,o.z).div(1.0685e-7),Rg.mul(o)})(ji(e).mul(y),ji(e).mul(b)).mul(2);v.addAssign(N.mul(t))}),v.max(en(0))}).setLayout({name:"evalIridescence",type:"vec3",inputs:[{name:"outsideIOR",type:"float"},{name:"eta2",type:"float"},{name:"cosTheta1",type:"float"},{name:"thinFilmThickness",type:"float"},{name:"baseF0",type:"vec3"}]}),Pg=ki(({normal:e,viewDir:t,roughness:r})=>{const s=e.dot(t).saturate(),i=r.pow2(),n=Xo(r.lessThan(.25),ji(-339.2).mul(i).add(ji(161.4).mul(r)).sub(25.9),ji(-8.48).mul(i).add(ji(14.3).mul(r)).sub(9.95)),a=Xo(r.lessThan(.25),ji(44).mul(i).sub(ji(23.7).mul(r)).add(3.26),ji(1.97).mul(i).sub(ji(3.27).mul(r)).add(.72));return Xo(r.lessThan(.25),0,ji(.1).mul(r).sub(.025)).add(n.mul(s).add(a).exp()).mul(1/Math.PI).saturate()}),Bg=en(.04),Fg=ji(1);class Lg extends Pp{constructor(e=!1,t=!1,r=!1,s=!1,i=!1,n=!1){super(),this.clearcoat=e,this.sheen=t,this.iridescence=r,this.anisotropy=s,this.transmission=i,this.dispersion=n,this.clearcoatRadiance=null,this.clearcoatSpecularDirect=null,this.clearcoatSpecularIndirect=null,this.sheenSpecularDirect=null,this.sheenSpecularIndirect=null,this.iridescenceFresnel=null,this.iridescenceF0=null}start(e){if(!0===this.clearcoat&&(this.clearcoatRadiance=en().toVar("clearcoatRadiance"),this.clearcoatSpecularDirect=en().toVar("clearcoatSpecularDirect"),this.clearcoatSpecularIndirect=en().toVar("clearcoatSpecularIndirect")),!0===this.sheen&&(this.sheenSpecularDirect=en().toVar("sheenSpecularDirect"),this.sheenSpecularIndirect=en().toVar("sheenSpecularIndirect")),!0===this.iridescence){const e=Zl.dot(zl).clamp();this.iridescenceFresnel=Mg({outsideIOR:ji(1),eta2:wn,cosTheta1:e,thinFilmThickness:An,baseF0:Bn}),this.iridescenceF0=eg({f:this.iridescenceFresnel,f90:1,dotVH:e})}if(!0===this.transmission){const t=Ol,r=gl.sub(Ol).normalize(),s=Jl,i=e.context;i.backdrop=Ag(s,r,xn,yn,Bn,Fn,t,El,cl,ll,On,Gn,Hn,zn,this.dispersion?$n:null),i.backdropAlpha=kn,yn.a.mulAssign(Lo(1,i.backdrop.a,kn))}super.start(e)}computeMultiscattering(e,t,r){const s=Zl.dot(zl).clamp(),i=Zp({roughness:xn,dotNV:s}),n=(this.iridescenceF0?En.mix(Bn,this.iridescenceF0):Bn).mul(i.x).add(r.mul(i.y)),a=i.x.add(i.y).oneMinus(),o=Bn.add(Bn.oneMinus().mul(.047619)),u=n.mul(o).div(a.mul(o).oneMinus());e.addAssign(n),t.addAssign(u.mul(a))}direct({lightDirection:e,lightColor:t,reflectedLight:r}){const s=Zl.dot(e).clamp().mul(t);if(!0===this.sheen&&this.sheenSpecularDirect.addAssign(s.mul(sg({lightDirection:e}))),!0===this.clearcoat){const r=ed.dot(e).clamp().mul(t);this.clearcoatSpecularDirect.addAssign(r.mul(Qp({lightDirection:e,f0:Bg,f90:Fg,roughness:vn,normalView:ed})))}r.directDiffuse.addAssign(s.mul(Ip({diffuseColor:yn.rgb}))),r.directSpecular.addAssign(s.mul(Qp({lightDirection:e,f0:Bn,f90:1,roughness:xn,iridescence:this.iridescence,f:this.iridescenceFresnel,USE_IRIDESCENCE:this.iridescence,USE_ANISOTROPY:this.anisotropy})))}directRectArea({lightColor:e,lightPosition:t,halfWidth:r,halfHeight:s,reflectedLight:i,ltc_1:n,ltc_2:a}){const o=t.add(r).sub(s),u=t.sub(r).sub(s),l=t.sub(r).add(s),d=t.add(r).add(s),c=Zl,h=zl,p=Gl.toVar(),g=ig({N:c,V:h,roughness:xn}),m=n.sample(g).toVar(),f=a.sample(g).toVar(),y=dn(en(m.x,0,m.y),en(0,1,0),en(m.z,0,m.w)).toVar(),b=Bn.mul(f.x).add(Bn.oneMinus().mul(f.y)).toVar();i.directSpecular.addAssign(e.mul(b).mul(og({N:c,V:h,P:p,mInv:y,p0:o,p1:u,p2:l,p3:d}))),i.directDiffuse.addAssign(e.mul(yn).mul(og({N:c,V:h,P:p,mInv:dn(1,0,0,0,1,0,0,0,1),p0:o,p1:u,p2:l,p3:d})))}indirect(e){this.indirectDiffuse(e),this.indirectSpecular(e),this.ambientOcclusion(e)}indirectDiffuse(e){const{irradiance:t,reflectedLight:r}=e.context;r.indirectDiffuse.addAssign(t.mul(Ip({diffuseColor:yn})))}indirectSpecular(e){const{radiance:t,iblIrradiance:r,reflectedLight:s}=e.context;if(!0===this.sheen&&this.sheenSpecularIndirect.addAssign(r.mul(Nn,Pg({normal:Zl,viewDir:zl,roughness:Sn}))),!0===this.clearcoat){const e=ed.dot(zl).clamp(),t=Jp({dotNV:e,specularColor:Bg,specularF90:Fg,roughness:vn});this.clearcoatSpecularIndirect.addAssign(this.clearcoatRadiance.mul(t))}const i=en().toVar("singleScattering"),n=en().toVar("multiScattering"),a=r.mul(1/Math.PI);this.computeMultiscattering(i,n,Fn);const o=i.add(n),u=yn.mul(o.r.max(o.g).max(o.b).oneMinus());s.indirectSpecular.addAssign(t.mul(i)),s.indirectSpecular.addAssign(n.mul(a)),s.indirectDiffuse.addAssign(u.mul(a))}ambientOcclusion(e){const{ambientOcclusion:t,reflectedLight:r}=e.context,s=Zl.dot(zl).clamp().add(t),i=xn.mul(-16).oneMinus().negate().exp2(),n=t.sub(s.pow(i).oneMinus()).clamp();!0===this.clearcoat&&this.clearcoatSpecularIndirect.mulAssign(t),!0===this.sheen&&this.sheenSpecularIndirect.mulAssign(t),r.indirectDiffuse.mulAssign(t),r.indirectSpecular.mulAssign(n)}finish({context:e}){const{outgoingLight:t}=e;if(!0===this.clearcoat){const e=ed.dot(zl).clamp(),r=Dp({dotVH:e,f0:Bg,f90:Fg}),s=t.mul(_n.mul(r).oneMinus()).add(this.clearcoatSpecularDirect.add(this.clearcoatSpecularIndirect).mul(_n));t.assign(s)}if(!0===this.sheen){const e=Nn.r.max(Nn.g).max(Nn.b).mul(.157).oneMinus(),r=t.mul(e).add(this.sheenSpecularDirect,this.sheenSpecularIndirect);t.assign(r)}}}const Dg=ji(1),Ig=ji(-2),Vg=ji(.8),Ug=ji(-1),Og=ji(.4),kg=ji(2),Gg=ji(.305),zg=ji(3),Hg=ji(.21),$g=ji(4),Wg=ji(4),jg=ji(16),qg=ki(([e])=>{const t=en(io(e)).toVar(),r=ji(-1).toVar();return Hi(t.x.greaterThan(t.z),()=>{Hi(t.x.greaterThan(t.y),()=>{r.assign(Xo(e.x.greaterThan(0),0,3))}).Else(()=>{r.assign(Xo(e.y.greaterThan(0),1,4))})}).Else(()=>{Hi(t.z.greaterThan(t.y),()=>{r.assign(Xo(e.z.greaterThan(0),2,5))}).Else(()=>{r.assign(Xo(e.y.greaterThan(0),1,4))})}),r}).setLayout({name:"getFace",type:"float",inputs:[{name:"direction",type:"vec3"}]}),Xg=ki(([e,t])=>{const r=Yi().toVar();return Hi(t.equal(0),()=>{r.assign(Yi(e.z,e.y).div(io(e.x)))}).ElseIf(t.equal(1),()=>{r.assign(Yi(e.x.negate(),e.z.negate()).div(io(e.y)))}).ElseIf(t.equal(2),()=>{r.assign(Yi(e.x.negate(),e.y).div(io(e.z)))}).ElseIf(t.equal(3),()=>{r.assign(Yi(e.z.negate(),e.y).div(io(e.x)))}).ElseIf(t.equal(4),()=>{r.assign(Yi(e.x.negate(),e.z).div(io(e.y)))}).Else(()=>{r.assign(Yi(e.x,e.y).div(io(e.z)))}),la(.5,r.add(1))}).setLayout({name:"getUV",type:"vec2",inputs:[{name:"direction",type:"vec3"},{name:"face",type:"float"}]}),Kg=ki(([e])=>{const t=ji(0).toVar();return Hi(e.greaterThanEqual(Vg),()=>{t.assign(Dg.sub(e).mul(Ug.sub(Ig)).div(Dg.sub(Vg)).add(Ig))}).ElseIf(e.greaterThanEqual(Og),()=>{t.assign(Vg.sub(e).mul(kg.sub(Ug)).div(Vg.sub(Og)).add(Ug))}).ElseIf(e.greaterThanEqual(Gg),()=>{t.assign(Og.sub(e).mul(zg.sub(kg)).div(Og.sub(Gg)).add(kg))}).ElseIf(e.greaterThanEqual(Hg),()=>{t.assign(Gg.sub(e).mul($g.sub(zg)).div(Gg.sub(Hg)).add(zg))}).Else(()=>{t.assign(ji(-2).mul(Wa(la(1.16,e))))}),t}).setLayout({name:"roughnessToMip",type:"float",inputs:[{name:"roughness",type:"float"}]}),Yg=ki(([e,t])=>{const r=e.toVar();r.assign(la(2,r).sub(1));const s=en(r,1).toVar();return Hi(t.equal(0),()=>{s.assign(s.zyx)}).ElseIf(t.equal(1),()=>{s.assign(s.xzy),s.xz.mulAssign(-1)}).ElseIf(t.equal(2),()=>{s.x.mulAssign(-1)}).ElseIf(t.equal(3),()=>{s.assign(s.zyx),s.xz.mulAssign(-1)}).ElseIf(t.equal(4),()=>{s.assign(s.xzy),s.xy.mulAssign(-1)}).ElseIf(t.equal(5),()=>{s.z.mulAssign(-1)}),s}).setLayout({name:"getDirection",type:"vec3",inputs:[{name:"uv",type:"vec2"},{name:"face",type:"float"}]}),Qg=ki(([e,t,r,s,i,n])=>{const a=ji(r),o=en(t),u=Do(Kg(a),Ig,n),l=Qa(u),d=Xa(u),c=en(Zg(e,o,d,s,i,n)).toVar();return Hi(l.notEqual(0),()=>{const t=en(Zg(e,o,d.add(1),s,i,n)).toVar();c.assign(Lo(c,t,l))}),c}),Zg=ki(([e,t,r,s,i,n])=>{const a=ji(r).toVar(),o=en(t),u=ji(qg(o)).toVar(),l=ji(To(Wg.sub(a),0)).toVar();a.assign(To(a,Wg));const d=ji(Ha(a)).toVar(),c=Yi(Xg(o,u).mul(d.sub(2)).add(1)).toVar();return Hi(u.greaterThan(2),()=>{c.y.addAssign(d),u.subAssign(3)}),c.x.addAssign(u.mul(d)),c.x.addAssign(l.mul(la(3,jg))),c.y.addAssign(la(4,Ha(n).sub(d))),c.x.mulAssign(s),c.y.mulAssign(i),e.sample(c).grad(Yi(),Yi())}),Jg=ki(({envMap:e,mipInt:t,outputDirection:r,theta:s,axis:i,CUBEUV_TEXEL_WIDTH:n,CUBEUV_TEXEL_HEIGHT:a,CUBEUV_MAX_MIP:o})=>{const u=Ja(s),l=r.mul(u).add(i.cross(r).mul(Za(s))).add(i.mul(i.dot(r).mul(u.oneMinus())));return Zg(e,l,t,n,a,o)}),em=ki(({n:e,latitudinal:t,poleAxis:r,outputDirection:s,weights:i,samples:n,dTheta:a,mipInt:o,envMap:u,CUBEUV_TEXEL_WIDTH:l,CUBEUV_TEXEL_HEIGHT:d,CUBEUV_MAX_MIP:c})=>{const h=en(Xo(t,r,wo(r,s))).toVar();Hi(h.equal(en(0)),()=>{h.assign(en(s.z,0,s.x.negate()))}),h.assign(Ya(h));const p=en().toVar();return p.addAssign(i.element(0).mul(Jg({theta:0,axis:h,outputDirection:s,mipInt:o,envMap:u,CUBEUV_TEXEL_WIDTH:l,CUBEUV_TEXEL_HEIGHT:d,CUBEUV_MAX_MIP:c}))),ch({start:qi(1),end:e},({i:e})=>{Hi(e.greaterThanEqual(n),()=>{hh()});const t=ji(a.mul(ji(e))).toVar();p.addAssign(i.element(e).mul(Jg({theta:t.mul(-1),axis:h,outputDirection:s,mipInt:o,envMap:u,CUBEUV_TEXEL_WIDTH:l,CUBEUV_TEXEL_HEIGHT:d,CUBEUV_MAX_MIP:c}))),p.addAssign(i.element(e).mul(Jg({theta:t,axis:h,outputDirection:s,mipInt:o,envMap:u,CUBEUV_TEXEL_WIDTH:l,CUBEUV_TEXEL_HEIGHT:d,CUBEUV_MAX_MIP:c})))}),nn(p,1)}),tm=[.125,.215,.35,.446,.526,.582],rm=20,sm=new ae(-1,1,1,-1,0,1),im=new oe(90,1),nm=new e;let am=null,om=0,um=0;const lm=(1+Math.sqrt(5))/2,dm=1/lm,cm=[new r(-lm,dm,0),new r(lm,dm,0),new r(-dm,0,lm),new r(dm,0,lm),new r(0,lm,-dm),new r(0,lm,dm),new r(-1,1,-1),new r(1,1,-1),new r(-1,1,1),new r(1,1,1)],hm=new r,pm=new WeakMap,gm=[3,1,5,0,4,2],mm=Yg($u(),Hu("faceIndex")).normalize(),fm=en(mm.x,mm.y,mm.z);class ym{constructor(e){this._renderer=e,this._pingPongRenderTarget=null,this._lodMax=0,this._cubeSize=0,this._lodPlanes=[],this._sizeLods=[],this._sigmas=[],this._lodMeshes=[],this._blurMaterial=null,this._cubemapMaterial=null,this._equirectMaterial=null,this._backgroundBox=null}get _hasInitialized(){return this._renderer.hasInitialized()}fromScene(e,t=0,r=.1,s=100,i={}){const{size:n=256,position:a=hm,renderTarget:o=null}=i;if(this._setSize(n),!1===this._hasInitialized){console.warn("THREE.PMREMGenerator: .fromScene() called before the backend is initialized. Try using .fromSceneAsync() instead.");const n=o||this._allocateTarget();return i.renderTarget=n,this.fromSceneAsync(e,t,r,s,i),n}am=this._renderer.getRenderTarget(),om=this._renderer.getActiveCubeFace(),um=this._renderer.getActiveMipmapLevel();const u=o||this._allocateTarget();return u.depthBuffer=!0,this._init(u),this._sceneToCubeUV(e,r,s,u,a),t>0&&this._blur(u,0,0,t),this._applyPMREM(u),this._cleanup(u),u}async fromSceneAsync(e,t=0,r=.1,s=100,i={}){return!1===this._hasInitialized&&await this._renderer.init(),this.fromScene(e,t,r,s,i)}fromEquirectangular(e,t=null){if(!1===this._hasInitialized){console.warn("THREE.PMREMGenerator: .fromEquirectangular() called before the backend is initialized. Try using .fromEquirectangularAsync() instead."),this._setSizeFromTexture(e);const r=t||this._allocateTarget();return this.fromEquirectangularAsync(e,r),r}return this._fromTexture(e,t)}async fromEquirectangularAsync(e,t=null){return!1===this._hasInitialized&&await this._renderer.init(),this._fromTexture(e,t)}fromCubemap(e,t=null){if(!1===this._hasInitialized){console.warn("THREE.PMREMGenerator: .fromCubemap() called before the backend is initialized. Try using .fromCubemapAsync() instead."),this._setSizeFromTexture(e);const r=t||this._allocateTarget();return this.fromCubemapAsync(e,t),r}return this._fromTexture(e,t)}async fromCubemapAsync(e,t=null){return!1===this._hasInitialized&&await this._renderer.init(),this._fromTexture(e,t)}async compileCubemapShader(){null===this._cubemapMaterial&&(this._cubemapMaterial=_m(),await this._compileMaterial(this._cubemapMaterial))}async compileEquirectangularShader(){null===this._equirectMaterial&&(this._equirectMaterial=vm(),await this._compileMaterial(this._equirectMaterial))}dispose(){this._dispose(),null!==this._cubemapMaterial&&this._cubemapMaterial.dispose(),null!==this._equirectMaterial&&this._equirectMaterial.dispose(),null!==this._backgroundBox&&(this._backgroundBox.geometry.dispose(),this._backgroundBox.material.dispose())}_setSizeFromTexture(e){e.mapping===R||e.mapping===C?this._setSize(0===e.image.length?16:e.image[0].width||e.image[0].image.width):this._setSize(e.image.width/4)}_setSize(e){this._lodMax=Math.floor(Math.log2(e)),this._cubeSize=Math.pow(2,this._lodMax)}_dispose(){null!==this._blurMaterial&&this._blurMaterial.dispose(),null!==this._pingPongRenderTarget&&this._pingPongRenderTarget.dispose();for(let e=0;ee-4?u=tm[o-e+4-1]:0===o&&(u=0),s.push(u);const l=1/(a-2),d=-l,c=1+l,h=[d,d,c,d,c,c,d,d,c,c,d,c],p=6,g=6,m=3,f=2,y=1,b=new Float32Array(m*g*p),x=new Float32Array(f*g*p),T=new Float32Array(y*g*p);for(let e=0;e2?0:-1,s=[t,r,0,t+2/3,r,0,t+2/3,r+1,0,t,r,0,t+2/3,r+1,0,t,r+1,0],i=gm[e];b.set(s,m*g*i),x.set(h,f*g*i);const n=[i,i,i,i,i,i];T.set(n,y*g*i)}const _=new pe;_.setAttribute("position",new ge(b,m)),_.setAttribute("uv",new ge(x,f)),_.setAttribute("faceIndex",new ge(T,y)),t.push(_),i.push(new X(_,null)),n>4&&n--}return{lodPlanes:t,sizeLods:r,sigmas:s,lodMeshes:i}}(t)),this._blurMaterial=function(e,t,s){const i=il(new Array(rm).fill(0)),n=Zn(new r(0,1,0)),a=Zn(0),o=ji(rm),u=Zn(0),l=Zn(1),d=Zu(null),c=Zn(0),h=ji(1/t),p=ji(1/s),g=ji(e),m={n:o,latitudinal:u,weights:i,poleAxis:n,outputDirection:fm,dTheta:a,samples:l,envMap:d,mipInt:c,CUBEUV_TEXEL_WIDTH:h,CUBEUV_TEXEL_HEIGHT:p,CUBEUV_MAX_MIP:g},f=Tm("blur");return f.fragmentNode=em({...m,latitudinal:u.equal(1)}),pm.set(f,m),f}(t,e.width,e.height)}}async _compileMaterial(e){const t=new X(this._lodPlanes[0],e);await this._renderer.compile(t,sm)}_sceneToCubeUV(e,t,r,s,i){const n=im;n.near=t,n.far=r;const a=[1,1,1,1,-1,1],o=[1,-1,1,-1,1,-1],u=this._renderer,l=u.autoClear;u.getClearColor(nm),u.autoClear=!1;let d=this._backgroundBox;if(null===d){const e=new se({name:"PMREM.Background",side:S,depthWrite:!1,depthTest:!1});d=new X(new q,e)}let c=!1;const h=e.background;h?h.isColor&&(d.material.color.copy(h),e.background=null,c=!0):(d.material.color.copy(nm),c=!0),u.setRenderTarget(s),u.clear(),c&&u.render(d,n);for(let t=0;t<6;t++){const r=t%3;0===r?(n.up.set(0,a[t],0),n.position.set(i.x,i.y,i.z),n.lookAt(i.x+o[t],i.y,i.z)):1===r?(n.up.set(0,0,a[t]),n.position.set(i.x,i.y,i.z),n.lookAt(i.x,i.y+o[t],i.z)):(n.up.set(0,a[t],0),n.position.set(i.x,i.y,i.z),n.lookAt(i.x,i.y,i.z+o[t]));const l=this._cubeSize;xm(s,r*l,t>2?l:0,l,l),u.render(e,n)}u.autoClear=l,e.background=h}_textureToCubeUV(e,t){const r=this._renderer,s=e.mapping===R||e.mapping===C;s?null===this._cubemapMaterial&&(this._cubemapMaterial=_m(e)):null===this._equirectMaterial&&(this._equirectMaterial=vm(e));const i=s?this._cubemapMaterial:this._equirectMaterial;i.fragmentNode.value=e;const n=this._lodMeshes[0];n.material=i;const a=this._cubeSize;xm(t,0,0,3*a,2*a),r.setRenderTarget(t),r.render(n,sm)}_applyPMREM(e){const t=this._renderer,r=t.autoClear;t.autoClear=!1;const s=this._lodPlanes.length;for(let t=1;trm&&console.warn(`sigmaRadians, ${i}, is too large and will clip, as it requested ${g} samples when the maximum is set to 20`);const m=[];let f=0;for(let e=0;ey-4?s-y+4:0),4*(this._cubeSize-b),3*b,2*b),o.setRenderTarget(t),o.render(l,sm)}}function bm(e,t){const r=new ue(e,t,{magFilter:Y,minFilter:Y,generateMipmaps:!1,type:ce,format:de,colorSpace:le});return r.texture.mapping=he,r.texture.name="PMREM.cubeUv",r.texture.isPMREMTexture=!0,r.scissorTest=!0,r}function xm(e,t,r,s,i){e.viewport.set(t,r,s,i),e.scissor.set(t,r,s,i)}function Tm(e){const t=new lp;return t.depthTest=!1,t.depthWrite=!1,t.blending=H,t.name=`PMREM_${e}`,t}function _m(e){const t=Tm("cubemap");return t.fragmentNode=bd(e,fm),t}function vm(e){const t=Tm("equirect");return t.fragmentNode=Zu(e,vp(fm),0),t}const Nm=new WeakMap;function Sm(e,t,r){const s=function(e){let t=Nm.get(e);void 0===t&&(t=new WeakMap,Nm.set(e,t));return t}(t);let i=s.get(e);if((void 0!==i?i.pmremVersion:-1)!==e.pmremVersion){const t=e.image;if(e.isCubeTexture){if(!function(e){if(null==e)return!1;let t=0;const r=6;for(let s=0;s0}(t))return null;i=r.fromEquirectangular(e,i)}i.pmremVersion=e.pmremVersion,s.set(e,i)}return i.texture}class Em extends Ks{static get type(){return"PMREMNode"}constructor(e,t=null,r=null){super("vec3"),this._value=e,this._pmrem=null,this.uvNode=t,this.levelNode=r,this._generator=null;const s=new x;s.isRenderTargetTexture=!0,this._texture=Zu(s),this._width=Zn(0),this._height=Zn(0),this._maxMip=Zn(0),this.updateBeforeType=Vs.RENDER}set value(e){this._value=e,this._pmrem=null}get value(){return this._value}updateFromTexture(e){const t=function(e){const t=Math.log2(e)-2,r=1/e;return{texelWidth:1/(3*Math.max(Math.pow(2,t),112)),texelHeight:r,maxMip:t}}(e.image.height);this._texture.value=e,this._width.value=t.texelWidth,this._height.value=t.texelHeight,this._maxMip.value=t.maxMip}updateBefore(e){let t=this._pmrem;const r=t?t.pmremVersion:-1,s=this._value;r!==s.pmremVersion&&(t=!0===s.isPMREMTexture?s:Sm(s,e.renderer,this._generator),null!==t&&(this._pmrem=t,this.updateFromTexture(t)))}setup(e){null===this._generator&&(this._generator=new ym(e.renderer)),this.updateBefore(e);let t=this.uvNode;null===t&&e.context.getUV&&(t=e.context.getUV(this)),t=dd.mul(en(t.x,t.y.negate(),t.z));let r=this.levelNode;return null===r&&e.context.getTextureLevel&&(r=e.context.getTextureLevel(this)),Qg(this._texture,t,r,this._width,this._height,this._maxMip)}dispose(){super.dispose(),null!==this._generator&&this._generator.dispose()}}const wm=Vi(Em).setParameterLength(1,3),Am=new WeakMap;class Rm extends bh{static get type(){return"EnvironmentNode"}constructor(e=null){super(),this.envNode=e}setup(e){const{material:t}=e;let r=this.envNode;if(r.isTextureNode||r.isMaterialReferenceNode){const e=r.isTextureNode?r.value:t[r.property];let s=Am.get(e);void 0===s&&(s=wm(e),Am.set(e,s)),r=s}const s=!0===t.useAnisotropy||t.anisotropy>0?Yd:Zl,i=r.context(Cm(xn,s)).mul(ld),n=r.context(Mm(Jl)).mul(Math.PI).mul(ld),a=Cu(i),o=Cu(n);e.context.radiance.addAssign(a),e.context.iblIrradiance.addAssign(o);const u=e.context.lightingModel.clearcoatRadiance;if(u){const e=r.context(Cm(vn,ed)).mul(ld),t=Cu(e);u.addAssign(t)}}}const Cm=(e,t)=>{let r=null;return{getUV:()=>(null===r&&(r=zl.negate().reflect(t),r=e.mul(e).mix(r,t).normalize(),r=r.transformDirection(cl)),r),getTextureLevel:()=>e}},Mm=e=>({getUV:()=>e,getTextureLevel:()=>ji(1)}),Pm=new me;class Bm extends lp{static get type(){return"MeshStandardNodeMaterial"}constructor(e){super(),this.isMeshStandardNodeMaterial=!0,this.lights=!0,this.emissiveNode=null,this.metalnessNode=null,this.roughnessNode=null,this.setDefaultValues(Pm),this.setValues(e)}setupEnvironment(e){let t=super.setupEnvironment(e);return null===t&&e.environmentNode&&(t=e.environmentNode),t?new Rm(t):null}setupLightingModel(){return new Lg}setupSpecular(){const e=Lo(en(.04),yn.rgb,Tn);Bn.assign(e),Fn.assign(1)}setupVariants(){const e=this.metalnessNode?ji(this.metalnessNode):fc;Tn.assign(e);let t=this.roughnessNode?ji(this.roughnessNode):mc;t=Wp({roughness:t}),xn.assign(t),this.setupSpecular(),yn.assign(nn(yn.rgb.mul(e.oneMinus()),yn.a))}copy(e){return this.emissiveNode=e.emissiveNode,this.metalnessNode=e.metalnessNode,this.roughnessNode=e.roughnessNode,super.copy(e)}}const Fm=new fe;class Lm extends Bm{static get type(){return"MeshPhysicalNodeMaterial"}constructor(e){super(),this.isMeshPhysicalNodeMaterial=!0,this.clearcoatNode=null,this.clearcoatRoughnessNode=null,this.clearcoatNormalNode=null,this.sheenNode=null,this.sheenRoughnessNode=null,this.iridescenceNode=null,this.iridescenceIORNode=null,this.iridescenceThicknessNode=null,this.specularIntensityNode=null,this.specularColorNode=null,this.iorNode=null,this.transmissionNode=null,this.thicknessNode=null,this.attenuationDistanceNode=null,this.attenuationColorNode=null,this.dispersionNode=null,this.anisotropyNode=null,this.setDefaultValues(Fm),this.setValues(e)}get useClearcoat(){return this.clearcoat>0||null!==this.clearcoatNode}get useIridescence(){return this.iridescence>0||null!==this.iridescenceNode}get useSheen(){return this.sheen>0||null!==this.sheenNode}get useAnisotropy(){return this.anisotropy>0||null!==this.anisotropyNode}get useTransmission(){return this.transmission>0||null!==this.transmissionNode}get useDispersion(){return this.dispersion>0||null!==this.dispersionNode}setupSpecular(){const e=this.iorNode?ji(this.iorNode):Mc;On.assign(e),Bn.assign(Lo(xo(Ro(On.sub(1).div(On.add(1))).mul(hc),en(1)).mul(cc),yn.rgb,Tn)),Fn.assign(Lo(cc,1,Tn))}setupLightingModel(){return new Lg(this.useClearcoat,this.useSheen,this.useIridescence,this.useAnisotropy,this.useTransmission,this.useDispersion)}setupVariants(e){if(super.setupVariants(e),this.useClearcoat){const e=this.clearcoatNode?ji(this.clearcoatNode):bc,t=this.clearcoatRoughnessNode?ji(this.clearcoatRoughnessNode):xc;_n.assign(e),vn.assign(Wp({roughness:t}))}if(this.useSheen){const e=this.sheenNode?en(this.sheenNode):vc,t=this.sheenRoughnessNode?ji(this.sheenRoughnessNode):Nc;Nn.assign(e),Sn.assign(t)}if(this.useIridescence){const e=this.iridescenceNode?ji(this.iridescenceNode):Ec,t=this.iridescenceIORNode?ji(this.iridescenceIORNode):wc,r=this.iridescenceThicknessNode?ji(this.iridescenceThicknessNode):Ac;En.assign(e),wn.assign(t),An.assign(r)}if(this.useAnisotropy){const e=(this.anisotropyNode?Yi(this.anisotropyNode):Sc).toVar();Cn.assign(e.length()),Hi(Cn.equal(0),()=>{e.assign(Yi(1,0))}).Else(()=>{e.divAssign(Yi(Cn)),Cn.assign(Cn.saturate())}),Rn.assign(Cn.pow2().mix(xn.pow2(),1)),Mn.assign(Xd[0].mul(e.x).add(Xd[1].mul(e.y))),Pn.assign(Xd[1].mul(e.x).sub(Xd[0].mul(e.y)))}if(this.useTransmission){const e=this.transmissionNode?ji(this.transmissionNode):Rc,t=this.thicknessNode?ji(this.thicknessNode):Cc,r=this.attenuationDistanceNode?ji(this.attenuationDistanceNode):Pc,s=this.attenuationColorNode?en(this.attenuationColorNode):Bc;if(kn.assign(e),Gn.assign(t),zn.assign(r),Hn.assign(s),this.useDispersion){const e=this.dispersionNode?ji(this.dispersionNode):Oc;$n.assign(e)}}}setupClearcoatNormal(){return this.clearcoatNormalNode?en(this.clearcoatNormalNode):Tc}setup(e){e.context.setupClearcoatNormal=()=>iu(this.setupClearcoatNormal(e),"NORMAL","vec3"),super.setup(e)}copy(e){return this.clearcoatNode=e.clearcoatNode,this.clearcoatRoughnessNode=e.clearcoatRoughnessNode,this.clearcoatNormalNode=e.clearcoatNormalNode,this.sheenNode=e.sheenNode,this.sheenRoughnessNode=e.sheenRoughnessNode,this.iridescenceNode=e.iridescenceNode,this.iridescenceIORNode=e.iridescenceIORNode,this.iridescenceThicknessNode=e.iridescenceThicknessNode,this.specularIntensityNode=e.specularIntensityNode,this.specularColorNode=e.specularColorNode,this.transmissionNode=e.transmissionNode,this.thicknessNode=e.thicknessNode,this.attenuationDistanceNode=e.attenuationDistanceNode,this.attenuationColorNode=e.attenuationColorNode,this.dispersionNode=e.dispersionNode,this.anisotropyNode=e.anisotropyNode,super.copy(e)}}class Dm extends Lg{constructor(e=!1,t=!1,r=!1,s=!1,i=!1,n=!1,a=!1){super(e,t,r,s,i,n),this.useSSS=a}direct({lightDirection:e,lightColor:t,reflectedLight:r},s){if(!0===this.useSSS){const i=s.material,{thicknessColorNode:n,thicknessDistortionNode:a,thicknessAmbientNode:o,thicknessAttenuationNode:u,thicknessPowerNode:l,thicknessScaleNode:d}=i,c=e.add(Zl.mul(a)).normalize(),h=ji(zl.dot(c.negate()).saturate().pow(l).mul(d)),p=en(h.add(o).mul(n));r.directDiffuse.addAssign(p.mul(u.mul(t)))}super.direct({lightDirection:e,lightColor:t,reflectedLight:r},s)}}class Im extends Lm{static get type(){return"MeshSSSNodeMaterial"}constructor(e){super(e),this.thicknessColorNode=null,this.thicknessDistortionNode=ji(.1),this.thicknessAmbientNode=ji(0),this.thicknessAttenuationNode=ji(.1),this.thicknessPowerNode=ji(2),this.thicknessScaleNode=ji(10)}get useSSS(){return null!==this.thicknessColorNode}setupLightingModel(){return new Dm(this.useClearcoat,this.useSheen,this.useIridescence,this.useAnisotropy,this.useTransmission,this.useDispersion,this.useSSS)}copy(e){return this.thicknessColorNode=e.thicknessColorNode,this.thicknessDistortionNode=e.thicknessDistortionNode,this.thicknessAmbientNode=e.thicknessAmbientNode,this.thicknessAttenuationNode=e.thicknessAttenuationNode,this.thicknessPowerNode=e.thicknessPowerNode,this.thicknessScaleNode=e.thicknessScaleNode,super.copy(e)}}const Vm=ki(({normal:e,lightDirection:t,builder:r})=>{const s=e.dot(t),i=Yi(s.mul(.5).add(.5),0);if(r.material.gradientMap){const e=Sd("gradientMap","texture").context({getUV:()=>i});return en(e.r)}{const e=i.fwidth().mul(.5);return Lo(en(.7),en(1),Uo(ji(.7).sub(e.x),ji(.7).add(e.x),i.x))}});class Um extends Pp{direct({lightDirection:e,lightColor:t,reflectedLight:r},s){const i=Vm({normal:ql,lightDirection:e,builder:s}).mul(t);r.directDiffuse.addAssign(i.mul(Ip({diffuseColor:yn.rgb})))}indirect(e){const{ambientOcclusion:t,irradiance:r,reflectedLight:s}=e.context;s.indirectDiffuse.addAssign(r.mul(Ip({diffuseColor:yn}))),s.indirectDiffuse.mulAssign(t)}}const Om=new ye;class km extends lp{static get type(){return"MeshToonNodeMaterial"}constructor(e){super(),this.isMeshToonNodeMaterial=!0,this.lights=!0,this.setDefaultValues(Om),this.setValues(e)}setupLightingModel(){return new Um}}const Gm=ki(()=>{const e=en(zl.z,0,zl.x.negate()).normalize(),t=zl.cross(e);return Yi(e.dot(Zl),t.dot(Zl)).mul(.495).add(.5)}).once(["NORMAL","VERTEX"])().toVar("matcapUV"),zm=new be;class Hm extends lp{static get type(){return"MeshMatcapNodeMaterial"}constructor(e){super(),this.isMeshMatcapNodeMaterial=!0,this.setDefaultValues(zm),this.setValues(e)}setupVariants(e){const t=Gm;let r;r=e.material.matcap?Sd("matcap","texture").context({getUV:()=>t}):en(Lo(.2,.8,t.y)),yn.rgb.mulAssign(r.rgb)}}class $m extends Ks{static get type(){return"RotateNode"}constructor(e,t){super(),this.positionNode=e,this.rotationNode=t}getNodeType(e){return this.positionNode.getNodeType(e)}setup(e){const{rotationNode:t,positionNode:r}=this;if("vec2"===this.getNodeType(e)){const e=t.cos(),s=t.sin();return ln(e,s,s.negate(),e).mul(r)}{const e=t,s=cn(nn(1,0,0,0),nn(0,Ja(e.x),Za(e.x).negate(),0),nn(0,Za(e.x),Ja(e.x),0),nn(0,0,0,1)),i=cn(nn(Ja(e.y),0,Za(e.y),0),nn(0,1,0,0),nn(Za(e.y).negate(),0,Ja(e.y),0),nn(0,0,0,1)),n=cn(nn(Ja(e.z),Za(e.z).negate(),0,0),nn(Za(e.z),Ja(e.z),0,0),nn(0,0,1,0),nn(0,0,0,1));return s.mul(i).mul(n).mul(nn(r,1)).xyz}}}const Wm=Vi($m).setParameterLength(2),jm=new xe;class qm extends lp{static get type(){return"SpriteNodeMaterial"}constructor(e){super(),this.isSpriteNodeMaterial=!0,this._useSizeAttenuation=!0,this.positionNode=null,this.rotationNode=null,this.scaleNode=null,this.transparent=!0,this.setDefaultValues(jm),this.setValues(e)}setupPositionView(e){const{object:t,camera:r}=e,s=this.sizeAttenuation,{positionNode:i,rotationNode:n,scaleNode:a}=this,o=Bl.mul(en(i||0));let u=Yi(El[0].xyz.length(),El[1].xyz.length());if(null!==a&&(u=u.mul(Yi(a))),!1===s)if(r.isPerspectiveCamera)u=u.mul(o.z.negate());else{const e=ji(2).div(ll.element(1).element(1));u=u.mul(e.mul(2))}let l=Il.xy;if(t.center&&!0===t.center.isVector2){const e=((e,t,r)=>Li(new mu(e,t,r)))("center","vec2",t);l=l.sub(e.sub(.5))}l=l.mul(u);const d=ji(n||_c),c=Wm(l,d);return nn(o.xy.add(c),o.zw)}copy(e){return this.positionNode=e.positionNode,this.rotationNode=e.rotationNode,this.scaleNode=e.scaleNode,super.copy(e)}get sizeAttenuation(){return this._useSizeAttenuation}set sizeAttenuation(e){this._useSizeAttenuation!==e&&(this._useSizeAttenuation=e,this.needsUpdate=!0)}}const Xm=new Te;class Km extends qm{static get type(){return"PointsNodeMaterial"}constructor(e){super(),this.sizeNode=null,this.isPointsNodeMaterial=!0,this.setDefaultValues(Xm),this.setValues(e)}setupPositionView(){const{positionNode:e}=this;return Bl.mul(en(e||Vl)).xyz}setupVertex(e){const t=super.setupVertex(e);if(!0!==e.material.isNodeMaterial)return t;const{rotationNode:r,scaleNode:s,sizeNode:i}=this,n=Il.xy.toVar(),a=Ch.z.div(Ch.w);if(r&&r.isNode){const e=ji(r);n.assign(Wm(n,e))}let o=null!==i?Yi(i):Uc;return!0===this.sizeAttenuation&&(o=o.mul(o.div(Gl.z.negate()))),s&&s.isNode&&(o=o.mul(Yi(s))),n.mulAssign(o.mul(2)),n.assign(n.div(Ch.z)),n.y.assign(n.y.mul(a)),n.assign(n.mul(t.w)),t.addAssign(nn(n,0,0)),t}get alphaToCoverage(){return this._useAlphaToCoverage}set alphaToCoverage(e){this._useAlphaToCoverage!==e&&(this._useAlphaToCoverage=e,this.needsUpdate=!0)}}class Ym extends Pp{constructor(){super(),this.shadowNode=ji(1).toVar("shadowMask")}direct({lightNode:e}){null!==e.shadowNode&&this.shadowNode.mulAssign(e.shadowNode)}finish({context:e}){yn.a.mulAssign(this.shadowNode.oneMinus()),e.outgoingLight.rgb.assign(yn.rgb)}}const Qm=new _e;class Zm extends lp{static get type(){return"ShadowNodeMaterial"}constructor(e){super(),this.isShadowNodeMaterial=!0,this.lights=!0,this.transparent=!0,this.setDefaultValues(Qm),this.setValues(e)}setupLightingModel(){return new Ym}}const Jm=mn("vec3"),ef=mn("vec3"),tf=mn("vec3");class rf extends Pp{constructor(){super()}start(e){const{material:t,context:r}=e,s=mn("vec3"),i=mn("vec3");Hi(gl.sub(Ol).length().greaterThan(Cl.mul(2)),()=>{s.assign(gl),i.assign(Ol)}).Else(()=>{s.assign(Ol),i.assign(gl)});const n=i.sub(s),a=Zn("int").onRenderUpdate(({material:e})=>e.steps),o=n.length().div(a).toVar(),u=n.normalize().toVar(),l=ji(0).toVar(),d=en(1).toVar();t.offsetNode&&l.addAssign(t.offsetNode.mul(o)),ch(a,()=>{const i=s.add(u.mul(l)),n=cl.mul(nn(i,1)).xyz;let a;null!==t.depthNode&&(ef.assign(Xh(Hh(n.z,ol,ul))),r.sceneDepthNode=Xh(t.depthNode).toVar()),r.positionWorld=i,r.shadowPositionWorld=i,r.positionView=n,Jm.assign(0),t.scatteringNode&&(a=t.scatteringNode({positionRay:i})),super.start(e),a&&Jm.mulAssign(a);const c=Jm.mul(.01).negate().mul(o).exp();d.mulAssign(c),l.addAssign(o)}),tf.addAssign(d.saturate().oneMinus())}scatteringLight(e,t){const r=t.context.sceneDepthNode;r?Hi(r.greaterThanEqual(ef),()=>{Jm.addAssign(e)}):Jm.addAssign(e)}direct({lightNode:e,lightColor:t},r){if(void 0===e.light.distance)return;const s=t.xyz.toVar();s.mulAssign(e.shadowNode),this.scatteringLight(s,r)}directRectArea({lightColor:e,lightPosition:t,halfWidth:r,halfHeight:s},i){const n=t.add(r).sub(s),a=t.sub(r).sub(s),o=t.sub(r).add(s),u=t.add(r).add(s),l=i.context.positionView,d=e.xyz.mul(ug({P:l,p0:n,p1:a,p2:o,p3:u})).pow(1.5);this.scatteringLight(d,i)}finish(e){e.context.outgoingLight.assign(tf)}}class sf extends lp{static get type(){return"VolumeNodeMaterial"}constructor(e){super(),this.isVolumeNodeMaterial=!0,this.steps=25,this.offsetNode=null,this.scatteringNode=null,this.lights=!0,this.transparent=!0,this.side=S,this.depthTest=!1,this.depthWrite=!1,this.setValues(e)}setupLightingModel(){return new rf}}class nf{constructor(e,t){this.nodes=e,this.info=t,this._context="undefined"!=typeof self?self:null,this._animationLoop=null,this._requestId=null}start(){const e=(t,r)=>{this._requestId=this._context.requestAnimationFrame(e),!0===this.info.autoReset&&this.info.reset(),this.nodes.nodeFrame.update(),this.info.frame=this.nodes.nodeFrame.frameId,null!==this._animationLoop&&this._animationLoop(t,r)};e()}stop(){this._context.cancelAnimationFrame(this._requestId),this._requestId=null}getAnimationLoop(){return this._animationLoop}setAnimationLoop(e){this._animationLoop=e}getContext(){return this._context}setContext(e){this._context=e}dispose(){this.stop()}}class af{constructor(){this.weakMap=new WeakMap}get(e){let t=this.weakMap;for(let r=0;r{this.dispose()},this.onGeometryDispose=()=>{this.attributes=null,this.attributesId=null},this.material.addEventListener("dispose",this.onMaterialDispose),this.geometry.addEventListener("dispose",this.onGeometryDispose)}updateClipping(e){this.clippingContext=e}get clippingNeedsUpdate(){return null!==this.clippingContext&&this.clippingContext.cacheKey!==this.clippingContextCacheKey&&(this.clippingContextCacheKey=this.clippingContext.cacheKey,!0)}get hardwareClippingPlanes(){return!0===this.material.hardwareClipping?this.clippingContext.unionClippingCount:0}getNodeBuilderState(){return this._nodeBuilderState||(this._nodeBuilderState=this._nodes.getForRender(this))}getMonitor(){return this._monitor||(this._monitor=this.getNodeBuilderState().observer)}getBindings(){return this._bindings||(this._bindings=this.getNodeBuilderState().createBindings())}getBindingGroup(e){for(const t of this.getBindings())if(t.name===e)return t}getIndex(){return this._geometries.getIndex(this)}getIndirect(){return this._geometries.getIndirect(this)}getChainArray(){return[this.object,this.material,this.context,this.lightsNode]}setGeometry(e){this.geometry=e,this.attributes=null,this.attributesId=null}getAttributes(){if(null!==this.attributes)return this.attributes;const e=this.getNodeBuilderState().nodeAttributes,t=this.geometry,r=[],s=new Set,i={};for(const n of e){let e;if(n.node&&n.node.attribute?e=n.node.attribute:(e=t.getAttribute(n.name),i[n.name]=e.version),void 0===e)continue;r.push(e);const a=e.isInterleavedBufferAttribute?e.data:e;s.add(a)}return this.attributes=r,this.attributesId=i,this.vertexBuffers=Array.from(s.values()),r}getVertexBuffers(){return null===this.vertexBuffers&&this.getAttributes(),this.vertexBuffers}getDrawParameters(){const{object:e,material:t,geometry:r,group:s,drawRange:i}=this,n=this.drawParams||(this.drawParams={vertexCount:0,firstVertex:0,instanceCount:0,firstInstance:0}),a=this.getIndex(),o=null!==a;let u=1;if(!0===r.isInstancedBufferGeometry?u=r.instanceCount:void 0!==e.count&&(u=Math.max(0,e.count)),0===u)return null;if(n.instanceCount=u,!0===e.isBatchedMesh)return n;let l=1;!0!==t.wireframe||e.isPoints||e.isLineSegments||e.isLine||e.isLineLoop||(l=2);let d=i.start*l,c=(i.start+i.count)*l;null!==s&&(d=Math.max(d,s.start*l),c=Math.min(c,(s.start+s.count)*l));const h=r.attributes.position;let p=1/0;o?p=a.count:null!=h&&(p=h.count),d=Math.max(d,0),c=Math.min(c,p);const g=c-d;return g<0||g===1/0?null:(n.vertexCount=g,n.firstVertex=d,n)}getGeometryCacheKey(){const{geometry:e}=this;let t="";for(const r of Object.keys(e.attributes).sort()){const s=e.attributes[r];t+=r+",",s.data&&(t+=s.data.stride+","),s.offset&&(t+=s.offset+","),s.itemSize&&(t+=s.itemSize+","),s.normalized&&(t+="n,")}for(const r of Object.keys(e.morphAttributes).sort()){const s=e.morphAttributes[r];t+="morph-"+r+",";for(let e=0,r=s.length;e1&&(r+=e.uuid+","),r+=e.receiveShadow+",",bs(r)}get needsGeometryUpdate(){if(this.geometry.id!==this.object.geometry.id)return!0;if(null!==this.attributes){const e=this.attributesId;for(const t in e){const r=this.geometry.getAttribute(t);if(void 0===r||e[t]!==r.id)return!0}}return!1}get needsUpdate(){return this.initialNodesCacheKey!==this.getDynamicCacheKey()||this.clippingNeedsUpdate}getDynamicCacheKey(){let e=0;return!0!==this.material.isShadowPassMaterial&&(e=this._nodes.getCacheKey(this.scene,this.lightsNode)),this.camera.isArrayCamera&&(e=Ts(e,this.camera.cameras.length)),this.object.receiveShadow&&(e=Ts(e,1)),e}getCacheKey(){return this.getMaterialCacheKey()+this.getDynamicCacheKey()}dispose(){this.material.removeEventListener("dispose",this.onMaterialDispose),this.geometry.removeEventListener("dispose",this.onGeometryDispose),this.onDispose()}}const lf=[];class df{constructor(e,t,r,s,i,n){this.renderer=e,this.nodes=t,this.geometries=r,this.pipelines=s,this.bindings=i,this.info=n,this.chainMaps={}}get(e,t,r,s,i,n,a,o){const u=this.getChainMap(o);lf[0]=e,lf[1]=t,lf[2]=n,lf[3]=i;let l=u.get(lf);return void 0===l?(l=this.createRenderObject(this.nodes,this.geometries,this.renderer,e,t,r,s,i,n,a,o),u.set(lf,l)):(l.updateClipping(a),l.needsGeometryUpdate&&l.setGeometry(e.geometry),(l.version!==t.version||l.needsUpdate)&&(l.initialCacheKey!==l.getCacheKey()?(l.dispose(),l=this.get(e,t,r,s,i,n,a,o)):l.version=t.version)),lf.length=0,l}getChainMap(e="default"){return this.chainMaps[e]||(this.chainMaps[e]=new af)}dispose(){this.chainMaps={}}createRenderObject(e,t,r,s,i,n,a,o,u,l,d){const c=this.getChainMap(d),h=new uf(e,t,r,s,i,n,a,o,u,l);return h.onDispose=()=>{this.pipelines.delete(h),this.bindings.delete(h),this.nodes.delete(h),c.delete(h.getChainArray())},h}}class cf{constructor(){this.data=new WeakMap}get(e){let t=this.data.get(e);return void 0===t&&(t={},this.data.set(e,t)),t}delete(e){let t=null;return this.data.has(e)&&(t=this.data.get(e),this.data.delete(e)),t}has(e){return this.data.has(e)}dispose(){this.data=new WeakMap}}const hf=1,pf=2,gf=3,mf=4,ff=16;class yf extends cf{constructor(e){super(),this.backend=e}delete(e){const t=super.delete(e);return null!==t&&this.backend.destroyAttribute(e),t}update(e,t){const r=this.get(e);if(void 0===r.version)t===hf?this.backend.createAttribute(e):t===pf?this.backend.createIndexAttribute(e):t===gf?this.backend.createStorageAttribute(e):t===mf&&this.backend.createIndirectStorageAttribute(e),r.version=this._getBufferAttribute(e).version;else{const t=this._getBufferAttribute(e);(r.version{this.info.memory.geometries--;const s=t.index,i=e.getAttributes();null!==s&&this.attributes.delete(s);for(const e of i)this.attributes.delete(e);const n=this.wireframes.get(t);void 0!==n&&this.attributes.delete(n),t.removeEventListener("dispose",r)};t.addEventListener("dispose",r)}updateAttributes(e){const t=e.getAttributes();for(const e of t)e.isStorageBufferAttribute||e.isStorageInstancedBufferAttribute?this.updateAttribute(e,gf):this.updateAttribute(e,hf);const r=this.getIndex(e);null!==r&&this.updateAttribute(r,pf);const s=e.geometry.indirect;null!==s&&this.updateAttribute(s,mf)}updateAttribute(e,t){const r=this.info.render.calls;e.isInterleavedBufferAttribute?void 0===this.attributeCall.get(e)?(this.attributes.update(e,t),this.attributeCall.set(e,r)):this.attributeCall.get(e.data)!==r&&(this.attributes.update(e,t),this.attributeCall.set(e.data,r),this.attributeCall.set(e,r)):this.attributeCall.get(e)!==r&&(this.attributes.update(e,t),this.attributeCall.set(e,r))}getIndirect(e){return e.geometry.indirect}getIndex(e){const{geometry:t,material:r}=e;let s=t.index;if(!0===r.wireframe){const e=this.wireframes;let r=e.get(t);void 0===r?(r=xf(t),e.set(t,r)):r.version!==bf(t)&&(this.attributes.delete(r),r=xf(t),e.set(t,r)),s=r}return s}}class _f{constructor(){this.autoReset=!0,this.frame=0,this.calls=0,this.render={calls:0,frameCalls:0,drawCalls:0,triangles:0,points:0,lines:0,timestamp:0},this.compute={calls:0,frameCalls:0,timestamp:0},this.memory={geometries:0,textures:0}}update(e,t,r){this.render.drawCalls++,e.isMesh||e.isSprite?this.render.triangles+=r*(t/3):e.isPoints?this.render.points+=r*t:e.isLineSegments?this.render.lines+=r*(t/2):e.isLine?this.render.lines+=r*(t-1):console.error("THREE.WebGPUInfo: Unknown object type.")}reset(){this.render.drawCalls=0,this.render.frameCalls=0,this.compute.frameCalls=0,this.render.triangles=0,this.render.points=0,this.render.lines=0}dispose(){this.reset(),this.calls=0,this.render.calls=0,this.compute.calls=0,this.render.timestamp=0,this.compute.timestamp=0,this.memory.geometries=0,this.memory.textures=0}}class vf{constructor(e){this.cacheKey=e,this.usedTimes=0}}class Nf extends vf{constructor(e,t,r){super(e),this.vertexProgram=t,this.fragmentProgram=r}}class Sf extends vf{constructor(e,t){super(e),this.computeProgram=t,this.isComputePipeline=!0}}let Ef=0;class wf{constructor(e,t,r,s=null,i=null){this.id=Ef++,this.code=e,this.stage=t,this.name=r,this.transforms=s,this.attributes=i,this.usedTimes=0}}class Af extends cf{constructor(e,t){super(),this.backend=e,this.nodes=t,this.bindings=null,this.caches=new Map,this.programs={vertex:new Map,fragment:new Map,compute:new Map}}getForCompute(e,t){const{backend:r}=this,s=this.get(e);if(this._needsComputeUpdate(e)){const i=s.pipeline;i&&(i.usedTimes--,i.computeProgram.usedTimes--);const n=this.nodes.getForCompute(e);let a=this.programs.compute.get(n.computeShader);void 0===a&&(i&&0===i.computeProgram.usedTimes&&this._releaseProgram(i.computeProgram),a=new wf(n.computeShader,"compute",e.name,n.transforms,n.nodeAttributes),this.programs.compute.set(n.computeShader,a),r.createProgram(a));const o=this._getComputeCacheKey(e,a);let u=this.caches.get(o);void 0===u&&(i&&0===i.usedTimes&&this._releasePipeline(i),u=this._getComputePipeline(e,a,o,t)),u.usedTimes++,a.usedTimes++,s.version=e.version,s.pipeline=u}return s.pipeline}getForRender(e,t=null){const{backend:r}=this,s=this.get(e);if(this._needsRenderUpdate(e)){const i=s.pipeline;i&&(i.usedTimes--,i.vertexProgram.usedTimes--,i.fragmentProgram.usedTimes--);const n=e.getNodeBuilderState(),a=e.material?e.material.name:"";let o=this.programs.vertex.get(n.vertexShader);void 0===o&&(i&&0===i.vertexProgram.usedTimes&&this._releaseProgram(i.vertexProgram),o=new wf(n.vertexShader,"vertex",a),this.programs.vertex.set(n.vertexShader,o),r.createProgram(o));let u=this.programs.fragment.get(n.fragmentShader);void 0===u&&(i&&0===i.fragmentProgram.usedTimes&&this._releaseProgram(i.fragmentProgram),u=new wf(n.fragmentShader,"fragment",a),this.programs.fragment.set(n.fragmentShader,u),r.createProgram(u));const l=this._getRenderCacheKey(e,o,u);let d=this.caches.get(l);void 0===d?(i&&0===i.usedTimes&&this._releasePipeline(i),d=this._getRenderPipeline(e,o,u,l,t)):e.pipeline=d,d.usedTimes++,o.usedTimes++,u.usedTimes++,s.pipeline=d}return s.pipeline}delete(e){const t=this.get(e).pipeline;return t&&(t.usedTimes--,0===t.usedTimes&&this._releasePipeline(t),t.isComputePipeline?(t.computeProgram.usedTimes--,0===t.computeProgram.usedTimes&&this._releaseProgram(t.computeProgram)):(t.fragmentProgram.usedTimes--,t.vertexProgram.usedTimes--,0===t.vertexProgram.usedTimes&&this._releaseProgram(t.vertexProgram),0===t.fragmentProgram.usedTimes&&this._releaseProgram(t.fragmentProgram))),super.delete(e)}dispose(){super.dispose(),this.caches=new Map,this.programs={vertex:new Map,fragment:new Map,compute:new Map}}updateForRender(e){this.getForRender(e)}_getComputePipeline(e,t,r,s){r=r||this._getComputeCacheKey(e,t);let i=this.caches.get(r);return void 0===i&&(i=new Sf(r,t),this.caches.set(r,i),this.backend.createComputePipeline(i,s)),i}_getRenderPipeline(e,t,r,s,i){s=s||this._getRenderCacheKey(e,t,r);let n=this.caches.get(s);return void 0===n&&(n=new Nf(s,t,r),this.caches.set(s,n),e.pipeline=n,this.backend.createRenderPipeline(e,i)),n}_getComputeCacheKey(e,t){return e.id+","+t.id}_getRenderCacheKey(e,t,r){return t.id+","+r.id+","+this.backend.getRenderCacheKey(e)}_releasePipeline(e){this.caches.delete(e.cacheKey)}_releaseProgram(e){const t=e.code,r=e.stage;this.programs[r].delete(t)}_needsComputeUpdate(e){const t=this.get(e);return void 0===t.pipeline||t.version!==e.version}_needsRenderUpdate(e){return void 0===this.get(e).pipeline||this.backend.needsRenderUpdate(e)}}class Rf extends cf{constructor(e,t,r,s,i,n){super(),this.backend=e,this.textures=r,this.pipelines=i,this.attributes=s,this.nodes=t,this.info=n,this.pipelines.bindings=this}getForRender(e){const t=e.getBindings();for(const e of t){const r=this.get(e);void 0===r.bindGroup&&(this._init(e),this.backend.createBindings(e,t,0),r.bindGroup=e)}return t}getForCompute(e){const t=this.nodes.getForCompute(e).bindings;for(const e of t){const r=this.get(e);void 0===r.bindGroup&&(this._init(e),this.backend.createBindings(e,t,0),r.bindGroup=e)}return t}updateForCompute(e){this._updateBindings(this.getForCompute(e))}updateForRender(e){this._updateBindings(this.getForRender(e))}_updateBindings(e){for(const t of e)this._update(t,e)}_init(e){for(const t of e.bindings)if(t.isSampledTexture)this.textures.updateTexture(t.texture);else if(t.isStorageBuffer){const e=t.attribute,r=e.isIndirectStorageBufferAttribute?mf:gf;this.attributes.update(e,r)}}_update(e,t){const{backend:r}=this;let s=!1,i=!0,n=0,a=0;for(const t of e.bindings){if(t.isNodeUniformsGroup){if(!1===this.nodes.updateGroup(t))continue}if(t.isStorageBuffer){const e=t.attribute,r=e.isIndirectStorageBufferAttribute?mf:gf;this.attributes.update(e,r)}if(t.isUniformBuffer){t.update()&&r.updateBinding(t)}else if(t.isSampler)t.update();else if(t.isSampledTexture){const e=this.textures.get(t.texture);t.needsBindingsUpdate(e.generation)&&(s=!0);const o=t.update(),u=t.texture;o&&this.textures.updateTexture(u);const l=r.get(u);if(void 0!==l.externalTexture||e.isDefaultTexture?i=!1:(n=10*n+u.id,a+=u.version),!0===r.isWebGPUBackend&&void 0===l.texture&&void 0===l.externalTexture&&(console.error("Bindings._update: binding should be available:",t,o,u,t.textureNode.value,s),this.textures.updateTexture(u),s=!0),!0===u.isStorageTexture){const e=this.get(u);!0===t.store?e.needsMipmap=!0:this.textures.needsMipmaps(u)&&!0===e.needsMipmap&&(this.backend.generateMipmaps(u),e.needsMipmap=!1)}}}!0===s&&this.backend.updateBindings(e,t,i?n:0,a)}}function Cf(e,t){return e.groupOrder!==t.groupOrder?e.groupOrder-t.groupOrder:e.renderOrder!==t.renderOrder?e.renderOrder-t.renderOrder:e.z!==t.z?e.z-t.z:e.id-t.id}function Mf(e,t){return e.groupOrder!==t.groupOrder?e.groupOrder-t.groupOrder:e.renderOrder!==t.renderOrder?e.renderOrder-t.renderOrder:e.z!==t.z?t.z-e.z:e.id-t.id}function Pf(e){return(e.transmission>0||e.transmissionNode)&&e.side===E&&!1===e.forceSinglePass}class Bf{constructor(e,t,r){this.renderItems=[],this.renderItemsIndex=0,this.opaque=[],this.transparentDoublePass=[],this.transparent=[],this.bundles=[],this.lightsNode=e.getNode(t,r),this.lightsArray=[],this.scene=t,this.camera=r,this.occlusionQueryCount=0}begin(){return this.renderItemsIndex=0,this.opaque.length=0,this.transparentDoublePass.length=0,this.transparent.length=0,this.bundles.length=0,this.lightsArray.length=0,this.occlusionQueryCount=0,this}getNextRenderItem(e,t,r,s,i,n,a){let o=this.renderItems[this.renderItemsIndex];return void 0===o?(o={id:e.id,object:e,geometry:t,material:r,groupOrder:s,renderOrder:e.renderOrder,z:i,group:n,clippingContext:a},this.renderItems[this.renderItemsIndex]=o):(o.id=e.id,o.object=e,o.geometry=t,o.material=r,o.groupOrder=s,o.renderOrder=e.renderOrder,o.z=i,o.group=n,o.clippingContext=a),this.renderItemsIndex++,o}push(e,t,r,s,i,n,a){const o=this.getNextRenderItem(e,t,r,s,i,n,a);!0===e.occlusionTest&&this.occlusionQueryCount++,!0===r.transparent||r.transmission>0?(Pf(r)&&this.transparentDoublePass.push(o),this.transparent.push(o)):this.opaque.push(o)}unshift(e,t,r,s,i,n,a){const o=this.getNextRenderItem(e,t,r,s,i,n,a);!0===r.transparent||r.transmission>0?(Pf(r)&&this.transparentDoublePass.unshift(o),this.transparent.unshift(o)):this.opaque.unshift(o)}pushBundle(e){this.bundles.push(e)}pushLight(e){this.lightsArray.push(e)}sort(e,t){this.opaque.length>1&&this.opaque.sort(e||Cf),this.transparentDoublePass.length>1&&this.transparentDoublePass.sort(t||Mf),this.transparent.length>1&&this.transparent.sort(t||Mf)}finish(){this.lightsNode.setLights(this.lightsArray);for(let e=this.renderItemsIndex,t=this.renderItems.length;e>t,u=a.height>>t;let l=e.depthTexture||i[t];const d=!0===e.depthBuffer||!0===e.stencilBuffer;let c=!1;void 0===l&&d&&(l=new U,l.format=e.stencilBuffer?we:Ae,l.type=e.stencilBuffer?Re:T,l.image.width=o,l.image.height=u,l.image.depth=a.depth,l.isArrayTexture=!0===e.multiview&&a.depth>1,i[t]=l),r.width===a.width&&a.height===r.height||(c=!0,l&&(l.needsUpdate=!0,l.image.width=o,l.image.height=u,l.image.depth=l.isArrayTexture?l.image.depth:1)),r.width=a.width,r.height=a.height,r.textures=n,r.depthTexture=l||null,r.depth=e.depthBuffer,r.stencil=e.stencilBuffer,r.renderTarget=e,r.sampleCount!==s&&(c=!0,l&&(l.needsUpdate=!0),r.sampleCount=s);const h={sampleCount:s};if(!0!==e.isXRRenderTarget){for(let e=0;e{e.removeEventListener("dispose",t);for(let e=0;e0){const s=e.image;if(void 0===s)console.warn("THREE.Renderer: Texture marked for update but image is undefined.");else if(!1===s.complete)console.warn("THREE.Renderer: Texture marked for update but image is incomplete.");else{if(e.images){const r=[];for(const t of e.images)r.push(t);t.images=r}else t.image=s;void 0!==r.isDefaultTexture&&!0!==r.isDefaultTexture||(i.createTexture(e,t),r.isDefaultTexture=!1,r.generation=e.version),!0===e.source.dataReady&&i.updateTexture(e,t),t.needsMipmaps&&0===e.mipmaps.length&&i.generateMipmaps(e)}}else i.createDefaultTexture(e),r.isDefaultTexture=!0,r.generation=e.version}if(!0!==r.initialized){r.initialized=!0,r.generation=e.version,this.info.memory.textures++;const t=()=>{e.removeEventListener("dispose",t),this._destroyTexture(e)};e.addEventListener("dispose",t)}r.version=e.version}getSize(e,t=zf){let r=e.images?e.images[0]:e.image;return r?(void 0!==r.image&&(r=r.image),t.width=r.width||1,t.height=r.height||1,t.depth=e.isCubeTexture?6:r.depth||1):t.width=t.height=t.depth=1,t}getMipLevels(e,t,r){let s;return s=e.isCompressedTexture?e.mipmaps?e.mipmaps.length:1:Math.floor(Math.log2(Math.max(t,r)))+1,s}needsMipmaps(e){return!0===e.isCompressedTexture||e.generateMipmaps}_destroyTexture(e){!0===this.has(e)&&(this.backend.destroySampler(e),this.backend.destroyTexture(e),this.delete(e),this.info.memory.textures--)}}class $f extends e{constructor(e,t,r,s=1){super(e,t,r),this.a=s}set(e,t,r,s=1){return this.a=s,super.set(e,t,r)}copy(e){return void 0!==e.a&&(this.a=e.a),super.copy(e)}clone(){return new this.constructor(this.r,this.g,this.b,this.a)}}class Wf extends gn{static get type(){return"ParameterNode"}constructor(e,t=null){super(e,t),this.isParameterNode=!0}getHash(){return this.uuid}generate(){return this.name}}class jf extends js{static get type(){return"StackNode"}constructor(e=null){super(),this.nodes=[],this.outputNode=null,this.parent=e,this._currentCond=null,this._expressionNode=null,this.isStackNode=!0}getNodeType(e){return this.outputNode?this.outputNode.getNodeType(e):"void"}getMemberType(e,t){return this.outputNode?this.outputNode.getMemberType(e,t):"void"}add(e){return this.nodes.push(e),this}If(e,t){const r=new Fi(t);return this._currentCond=Xo(e,r),this.add(this._currentCond)}ElseIf(e,t){const r=new Fi(t),s=Xo(e,r);return this._currentCond.elseNode=s,this._currentCond=s,this}Else(e){return this._currentCond.elseNode=new Fi(e),this}Switch(e){return this._expressionNode=Li(e),this}Case(...e){const t=[];if(!(e.length>=2))throw new Error("TSL: Invalid parameter length. Case() requires at least two parameters.");for(let r=0;r"string"==typeof t?{name:e,type:t,atomic:!1}:{name:e,type:t.type,atomic:t.atomic||!1})),this.name=t,this.isStructLayoutNode=!0}getLength(){const e=Float32Array.BYTES_PER_ELEMENT;let t=0;for(const r of this.membersLayout){const s=r.type,i=Rs(s)*e,n=t%8,a=n%Cs(s),o=n+a;t+=a,0!==o&&8-oe.name===t);return r?r.type:"void"}getNodeType(e){return e.getStructTypeFromNode(this,this.membersLayout,this.name).name}setup(e){e.addInclude(this)}generate(e){return this.getNodeType(e)}}class Kf extends js{static get type(){return"StructNode"}constructor(e,t){super("vec3"),this.structLayoutNode=e,this.values=t,this.isStructNode=!0}getNodeType(e){return this.structLayoutNode.getNodeType(e)}getMemberType(e,t){return this.structLayoutNode.getMemberType(e,t)}generate(e){const t=e.getVarFromNode(this),r=t.type,s=e.getPropertyName(t);return e.addLineFlowCode(`${s} = ${e.generateStruct(r,this.structLayoutNode.membersLayout,this.values)}`,this),t.name}}class Yf extends js{static get type(){return"OutputStructNode"}constructor(...e){super(),this.members=e,this.isOutputStructNode=!0}getNodeType(e){const t=e.getNodeProperties(this);if(void 0===t.membersLayout){const r=this.members,s=[];for(let t=0;t{const t=e.toUint().mul(747796405).add(2891336453),r=t.shiftRight(t.shiftRight(28).add(4)).bitXor(t).mul(277803737);return r.shiftRight(22).bitXor(r).toFloat().mul(1/2**32)}),ry=(e,t)=>Ao(la(4,e.mul(ua(1,e))),t),sy=ki(([e])=>e.fract().sub(.5).abs()).setLayout({name:"tri",type:"float",inputs:[{name:"x",type:"float"}]}),iy=ki(([e])=>en(sy(e.z.add(sy(e.y.mul(1)))),sy(e.z.add(sy(e.x.mul(1)))),sy(e.y.add(sy(e.x.mul(1)))))).setLayout({name:"tri3",type:"vec3",inputs:[{name:"p",type:"vec3"}]}),ny=ki(([e,t,r])=>{const s=en(e).toVar(),i=ji(1.4).toVar(),n=ji(0).toVar(),a=en(s).toVar();return ch({start:ji(0),end:ji(3),type:"float",condition:"<="},()=>{const e=en(iy(a.mul(2))).toVar();s.addAssign(e.add(r.mul(ji(.1).mul(t)))),a.mulAssign(1.8),i.mulAssign(1.5),s.mulAssign(1.2);const o=ji(sy(s.z.add(sy(s.x.add(sy(s.y)))))).toVar();n.addAssign(o.div(i)),a.addAssign(.14)}),n}).setLayout({name:"triNoise3D",type:"float",inputs:[{name:"position",type:"vec3"},{name:"speed",type:"float"},{name:"time",type:"float"}]});class ay extends js{static get type(){return"FunctionOverloadingNode"}constructor(e=[],...t){super(),this.functionNodes=e,this.parametersNodes=t,this._candidateFnCall=null,this.global=!0}getNodeType(){return this.functionNodes[0].shaderNode.layout.type}setup(e){const t=this.parametersNodes;let r=this._candidateFnCall;if(null===r){let s=null,i=-1;for(const r of this.functionNodes){const n=r.shaderNode.layout;if(null===n)throw new Error("FunctionOverloadingNode: FunctionNode must be a layout.");const a=n.inputs;if(t.length===a.length){let n=0;for(let r=0;ri&&(s=r,i=n)}}this._candidateFnCall=r=s(...t)}return r}}const oy=Vi(ay),uy=e=>(...t)=>oy(e,...t),ly=Zn(0).setGroup(Kn).onRenderUpdate(e=>e.time),dy=Zn(0).setGroup(Kn).onRenderUpdate(e=>e.deltaTime),cy=Zn(0,"uint").setGroup(Kn).onRenderUpdate(e=>e.frameId),hy=ki(([e,t,r=Yi(.5)])=>Wm(e.sub(r),t).add(r)),py=ki(([e,t,r=Yi(.5)])=>{const s=e.sub(r),i=s.dot(s),n=i.mul(i).mul(t);return e.add(s.mul(n))}),gy=ki(({position:e=null,horizontal:t=!0,vertical:r=!1})=>{let s;null!==e?(s=El.toVar(),s[3][0]=e.x,s[3][1]=e.y,s[3][2]=e.z):s=El;const i=cl.mul(s);return Pi(t)&&(i[0][0]=El[0].length(),i[0][1]=0,i[0][2]=0),Pi(r)&&(i[1][0]=0,i[1][1]=El[1].length(),i[1][2]=0),i[2][0]=0,i[2][1]=0,i[2][2]=1,ll.mul(i).mul(Vl)}),my=ki(([e=null])=>{const t=Xh();return Xh(kh(e)).sub(t).lessThan(0).select(wh,e)});class fy extends js{static get type(){return"SpriteSheetUVNode"}constructor(e,t=$u(),r=ji(0)){super("vec2"),this.countNode=e,this.uvNode=t,this.frameNode=r}setup(){const{frameNode:e,uvNode:t,countNode:r}=this,{width:s,height:i}=r,n=e.mod(s.mul(i)).floor(),a=n.mod(s),o=i.sub(n.add(1).div(s).ceil()),u=r.reciprocal(),l=Yi(a,o);return t.add(l).mul(u)}}const yy=Vi(fy).setParameterLength(3),by=ki(([e,t=null,r=null,s=ji(1),i=Vl,n=Xl])=>{let a=n.abs().normalize();a=a.div(a.dot(en(1)));const o=i.yz.mul(s),u=i.zx.mul(s),l=i.xy.mul(s),d=e.value,c=null!==t?t.value:d,h=null!==r?r.value:d,p=Zu(d,o).mul(a.x),g=Zu(c,u).mul(a.y),m=Zu(h,l).mul(a.z);return oa(p,g,m)}),xy=new Me,Ty=new r,_y=new r,vy=new r,Ny=new a,Sy=new r(0,0,-1),Ey=new s,wy=new r,Ay=new r,Ry=new s,Cy=new t,My=new ue,Py=wh.flipX();My.depthTexture=new U(1,1);let By=!1;class Fy extends Yu{static get type(){return"ReflectorNode"}constructor(e={}){super(e.defaultTexture||My.texture,Py),this._reflectorBaseNode=e.reflector||new Ly(this,e),this._depthNode=null,this.setUpdateMatrix(!1)}get reflector(){return this._reflectorBaseNode}get target(){return this._reflectorBaseNode.target}getDepthNode(){if(null===this._depthNode){if(!0!==this._reflectorBaseNode.depth)throw new Error("THREE.ReflectorNode: Depth node can only be requested when the reflector is created with { depth: true }. ");this._depthNode=Li(new Fy({defaultTexture:My.depthTexture,reflector:this._reflectorBaseNode}))}return this._depthNode}setup(e){return e.object.isQuadMesh||this._reflectorBaseNode.build(e),super.setup(e)}clone(){const e=new this.constructor(this.reflectorNode);return e.uvNode=this.uvNode,e.levelNode=this.levelNode,e.biasNode=this.biasNode,e.sampler=this.sampler,e.depthNode=this.depthNode,e.compareNode=this.compareNode,e.gradNode=this.gradNode,e._reflectorBaseNode=this._reflectorBaseNode,e}dispose(){super.dispose(),this._reflectorBaseNode.dispose()}}class Ly extends js{static get type(){return"ReflectorBaseNode"}constructor(e,t={}){super();const{target:r=new Pe,resolution:s=1,generateMipmaps:i=!1,bounces:n=!0,depth:a=!1}=t;this.textureNode=e,this.target=r,this.resolution=s,this.generateMipmaps=i,this.bounces=n,this.depth=a,this.updateBeforeType=n?Vs.RENDER:Vs.FRAME,this.virtualCameras=new WeakMap,this.renderTargets=new Map,this.forceUpdate=!1,this.hasOutput=!1}_updateResolution(e,t){const r=this.resolution;t.getDrawingBufferSize(Cy),e.setSize(Math.round(Cy.width*r),Math.round(Cy.height*r))}setup(e){return this._updateResolution(My,e.renderer),super.setup(e)}dispose(){super.dispose();for(const e of this.renderTargets.values())e.dispose()}getVirtualCamera(e){let t=this.virtualCameras.get(e);return void 0===t&&(t=e.clone(),this.virtualCameras.set(e,t)),t}getRenderTarget(e){let t=this.renderTargets.get(e);return void 0===t&&(t=new ue(0,0,{type:ce}),!0===this.generateMipmaps&&(t.texture.minFilter=Be,t.texture.generateMipmaps=!0),!0===this.depth&&(t.depthTexture=new U),this.renderTargets.set(e,t)),t}updateBefore(e){if(!1===this.bounces&&By)return!1;By=!0;const{scene:t,camera:r,renderer:s,material:i}=e,{target:n}=this,a=this.getVirtualCamera(r),o=this.getRenderTarget(a);s.getDrawingBufferSize(Cy),this._updateResolution(o,s),_y.setFromMatrixPosition(n.matrixWorld),vy.setFromMatrixPosition(r.matrixWorld),Ny.extractRotation(n.matrixWorld),Ty.set(0,0,1),Ty.applyMatrix4(Ny),wy.subVectors(_y,vy);let u=!1;if(!0===wy.dot(Ty)>0&&!1===this.forceUpdate){if(!1===this.hasOutput)return void(By=!1);u=!0}wy.reflect(Ty).negate(),wy.add(_y),Ny.extractRotation(r.matrixWorld),Sy.set(0,0,-1),Sy.applyMatrix4(Ny),Sy.add(vy),Ay.subVectors(_y,Sy),Ay.reflect(Ty).negate(),Ay.add(_y),a.coordinateSystem=r.coordinateSystem,a.position.copy(wy),a.up.set(0,1,0),a.up.applyMatrix4(Ny),a.up.reflect(Ty),a.lookAt(Ay),a.near=r.near,a.far=r.far,a.updateMatrixWorld(),a.projectionMatrix.copy(r.projectionMatrix),xy.setFromNormalAndCoplanarPoint(Ty,_y),xy.applyMatrix4(a.matrixWorldInverse),Ey.set(xy.normal.x,xy.normal.y,xy.normal.z,xy.constant);const l=a.projectionMatrix;Ry.x=(Math.sign(Ey.x)+l.elements[8])/l.elements[0],Ry.y=(Math.sign(Ey.y)+l.elements[9])/l.elements[5],Ry.z=-1,Ry.w=(1+l.elements[10])/l.elements[14],Ey.multiplyScalar(1/Ey.dot(Ry));l.elements[2]=Ey.x,l.elements[6]=Ey.y,l.elements[10]=s.coordinateSystem===d?Ey.z-0:Ey.z+1-0,l.elements[14]=Ey.w,this.textureNode.value=o.texture,!0===this.depth&&(this.textureNode.getDepthNode().value=o.depthTexture),i.visible=!1;const c=s.getRenderTarget(),h=s.getMRT(),p=s.autoClear;s.setMRT(null),s.setRenderTarget(o),s.autoClear=!0,u?(s.clear(),this.hasOutput=!1):(s.render(t,a),this.hasOutput=!0),s.setMRT(h),s.setRenderTarget(c),s.autoClear=p,i.visible=!0,By=!1,this.forceUpdate=!1}}const Dy=new ae(-1,1,1,-1,0,1);class Iy extends pe{constructor(e=!1){super();const t=!1===e?[0,-1,0,1,2,1]:[0,2,0,0,2,0];this.setAttribute("position",new Fe([-1,3,0,-1,-1,0,3,-1,0],3)),this.setAttribute("uv",new Fe(t,2))}}const Vy=new Iy;class Uy extends X{constructor(e=null){super(Vy,e),this.camera=Dy,this.isQuadMesh=!0}async renderAsync(e){return e.renderAsync(this,Dy)}render(e){e.render(this,Dy)}}const Oy=new t;class ky extends Yu{static get type(){return"RTTNode"}constructor(e,t=null,r=null,s={type:ce}){const i=new ue(t,r,s);super(i.texture,$u()),this.node=e,this.width=t,this.height=r,this.pixelRatio=1,this.renderTarget=i,this.textureNeedsUpdate=!0,this.autoUpdate=!0,this._rttNode=null,this._quadMesh=new Uy(new lp),this.updateBeforeType=Vs.RENDER}get autoResize(){return null===this.width}setup(e){return this._rttNode=this.node.context(e.getSharedContext()),this._quadMesh.material.name="RTT",this._quadMesh.material.needsUpdate=!0,super.setup(e)}setSize(e,t){this.width=e,this.height=t;const r=e*this.pixelRatio,s=t*this.pixelRatio;this.renderTarget.setSize(r,s),this.textureNeedsUpdate=!0}setPixelRatio(e){this.pixelRatio=e,this.setSize(this.width,this.height)}updateBefore({renderer:e}){if(!1===this.textureNeedsUpdate&&!1===this.autoUpdate)return;if(this.textureNeedsUpdate=!1,!0===this.autoResize){const t=e.getPixelRatio(),r=e.getSize(Oy),s=r.width*t,i=r.height*t;s===this.renderTarget.width&&i===this.renderTarget.height||(this.renderTarget.setSize(s,i),this.textureNeedsUpdate=!0)}this._quadMesh.material.fragmentNode=this._rttNode;const t=e.getRenderTarget();e.setRenderTarget(this.renderTarget),this._quadMesh.render(e),e.setRenderTarget(t)}clone(){const e=new Yu(this.value,this.uvNode,this.levelNode);return e.sampler=this.sampler,e.referenceNode=this,e}}const Gy=(e,...t)=>Li(new ky(Li(e),...t)),zy=ki(([e,t,r],s)=>{let i;s.renderer.coordinateSystem===d?(e=Yi(e.x,e.y.oneMinus()).mul(2).sub(1),i=nn(en(e,t),1)):i=nn(en(e.x,e.y.oneMinus(),t).mul(2).sub(1),1);const n=nn(r.mul(i));return n.xyz.div(n.w)}),Hy=ki(([e,t])=>{const r=t.mul(nn(e,1)),s=r.xy.div(r.w).mul(.5).add(.5).toVar();return Yi(s.x,s.y.oneMinus())}),$y=ki(([e,t,r])=>{const s=ju(Ju(t)),i=Qi(e.mul(s)).toVar(),n=Ju(t,i).toVar(),a=Ju(t,i.sub(Qi(2,0))).toVar(),o=Ju(t,i.sub(Qi(1,0))).toVar(),u=Ju(t,i.add(Qi(1,0))).toVar(),l=Ju(t,i.add(Qi(2,0))).toVar(),d=Ju(t,i.add(Qi(0,2))).toVar(),c=Ju(t,i.add(Qi(0,1))).toVar(),h=Ju(t,i.sub(Qi(0,1))).toVar(),p=Ju(t,i.sub(Qi(0,2))).toVar(),g=io(ua(ji(2).mul(o).sub(a),n)).toVar(),m=io(ua(ji(2).mul(u).sub(l),n)).toVar(),f=io(ua(ji(2).mul(c).sub(d),n)).toVar(),y=io(ua(ji(2).mul(h).sub(p),n)).toVar(),b=zy(e,n,r).toVar(),x=g.lessThan(m).select(b.sub(zy(e.sub(Yi(ji(1).div(s.x),0)),o,r)),b.negate().add(zy(e.add(Yi(ji(1).div(s.x),0)),u,r))),T=f.lessThan(y).select(b.sub(zy(e.add(Yi(0,ji(1).div(s.y))),c,r)),b.negate().add(zy(e.sub(Yi(0,ji(1).div(s.y))),h,r)));return Ya(wo(x,T))});class Wy extends js{static get type(){return"SampleNode"}constructor(e){super(),this.callback=e,this.isSampleNode=!0}setup(){return this.sample($u())}sample(e){return this.callback(e)}}class jy extends F{constructor(e,t,r=Float32Array){super(ArrayBuffer.isView(e)?e:new r(e*t),t),this.isStorageInstancedBufferAttribute=!0}}class qy extends ge{constructor(e,t,r=Float32Array){super(ArrayBuffer.isView(e)?e:new r(e*t),t),this.isStorageBufferAttribute=!0}}class Xy extends js{static get type(){return"PointUVNode"}constructor(){super("vec2"),this.isPointUVNode=!0}generate(){return"vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y )"}}const Ky=Ui(Xy),Yy=new w,Qy=new a;class Zy extends js{static get type(){return"SceneNode"}constructor(e=Zy.BACKGROUND_BLURRINESS,t=null){super(),this.scope=e,this.scene=t}setup(e){const t=this.scope,r=null!==this.scene?this.scene:e.scene;let s;return t===Zy.BACKGROUND_BLURRINESS?s=_d("backgroundBlurriness","float",r):t===Zy.BACKGROUND_INTENSITY?s=_d("backgroundIntensity","float",r):t===Zy.BACKGROUND_ROTATION?s=Zn("mat4").label("backgroundRotation").setGroup(Kn).onRenderUpdate(()=>{const e=r.background;return null!==e&&e.isTexture&&e.mapping!==Le?(Yy.copy(r.backgroundRotation),Yy.x*=-1,Yy.y*=-1,Yy.z*=-1,Qy.makeRotationFromEuler(Yy)):Qy.identity(),Qy}):console.error("THREE.SceneNode: Unknown scope:",t),s}}Zy.BACKGROUND_BLURRINESS="backgroundBlurriness",Zy.BACKGROUND_INTENSITY="backgroundIntensity",Zy.BACKGROUND_ROTATION="backgroundRotation";const Jy=Ui(Zy,Zy.BACKGROUND_BLURRINESS),eb=Ui(Zy,Zy.BACKGROUND_INTENSITY),tb=Ui(Zy,Zy.BACKGROUND_ROTATION);class rb extends Yu{static get type(){return"StorageTextureNode"}constructor(e,t,r=null){super(e,t),this.storeNode=r,this.isStorageTextureNode=!0,this.access=Os.WRITE_ONLY}getInputType(){return"storageTexture"}setup(e){super.setup(e);const t=e.getNodeProperties(this);return t.storeNode=this.storeNode,t}setAccess(e){return this.access=e,this}generate(e,t){let r;return r=null!==this.storeNode?this.generateStore(e):super.generate(e,t),r}toReadWrite(){return this.setAccess(Os.READ_WRITE)}toReadOnly(){return this.setAccess(Os.READ_ONLY)}toWriteOnly(){return this.setAccess(Os.WRITE_ONLY)}generateStore(e){const t=e.getNodeProperties(this),{uvNode:r,storeNode:s,depthNode:i}=t,n=super.generate(e,"property"),a=r.build(e,"uvec2"),o=s.build(e,"vec4"),u=i?i.build(e,"int"):null,l=e.generateTextureStore(e,n,a,u,o);e.addLineFlowCode(l,this)}clone(){const e=super.clone();return e.storeNode=this.storeNode,e}}const sb=Vi(rb).setParameterLength(1,3),ib=ki(({texture:e,uv:t})=>{const r=1e-4,s=en().toVar();return Hi(t.x.lessThan(r),()=>{s.assign(en(1,0,0))}).ElseIf(t.y.lessThan(r),()=>{s.assign(en(0,1,0))}).ElseIf(t.z.lessThan(r),()=>{s.assign(en(0,0,1))}).ElseIf(t.x.greaterThan(.9999),()=>{s.assign(en(-1,0,0))}).ElseIf(t.y.greaterThan(.9999),()=>{s.assign(en(0,-1,0))}).ElseIf(t.z.greaterThan(.9999),()=>{s.assign(en(0,0,-1))}).Else(()=>{const r=.01,i=e.sample(t.add(en(-.01,0,0))).r.sub(e.sample(t.add(en(r,0,0))).r),n=e.sample(t.add(en(0,-.01,0))).r.sub(e.sample(t.add(en(0,r,0))).r),a=e.sample(t.add(en(0,0,-.01))).r.sub(e.sample(t.add(en(0,0,r))).r);s.assign(en(i,n,a))}),s.normalize()});class nb extends Yu{static get type(){return"Texture3DNode"}constructor(e,t=null,r=null){super(e,t,r),this.isTexture3DNode=!0}getInputType(){return"texture3D"}getDefaultUV(){return en(.5,.5,.5)}setUpdateMatrix(){}setupUV(e,t){const r=this.value;return!e.isFlipY()||!0!==r.isRenderTargetTexture&&!0!==r.isFramebufferTexture||(t=this.sampler?t.flipY():t.setY(qi(ju(this,this.levelNode).y).sub(t.y).sub(1))),t}generateUV(e,t){return t.build(e,"vec3")}normal(e){return ib({texture:this,uv:e})}}const ab=Vi(nb).setParameterLength(1,3);class ob extends Td{static get type(){return"UserDataNode"}constructor(e,t,r=null){super(e,t,r),this.userData=r}updateReference(e){return this.reference=null!==this.userData?this.userData:e.object.userData,this.reference}}const ub=new WeakMap;class lb extends Ks{static get type(){return"VelocityNode"}constructor(){super("vec2"),this.projectionMatrix=null,this.updateType=Vs.OBJECT,this.updateAfterType=Vs.OBJECT,this.previousModelWorldMatrix=Zn(new a),this.previousProjectionMatrix=Zn(new a).setGroup(Kn),this.previousCameraViewMatrix=Zn(new a)}setProjectionMatrix(e){this.projectionMatrix=e}update({frameId:e,camera:t,object:r}){const s=cb(r);this.previousModelWorldMatrix.value.copy(s);const i=db(t);i.frameId!==e&&(i.frameId=e,void 0===i.previousProjectionMatrix?(i.previousProjectionMatrix=new a,i.previousCameraViewMatrix=new a,i.currentProjectionMatrix=new a,i.currentCameraViewMatrix=new a,i.previousProjectionMatrix.copy(this.projectionMatrix||t.projectionMatrix),i.previousCameraViewMatrix.copy(t.matrixWorldInverse)):(i.previousProjectionMatrix.copy(i.currentProjectionMatrix),i.previousCameraViewMatrix.copy(i.currentCameraViewMatrix)),i.currentProjectionMatrix.copy(this.projectionMatrix||t.projectionMatrix),i.currentCameraViewMatrix.copy(t.matrixWorldInverse),this.previousProjectionMatrix.value.copy(i.previousProjectionMatrix),this.previousCameraViewMatrix.value.copy(i.previousCameraViewMatrix))}updateAfter({object:e}){cb(e).copy(e.matrixWorld)}setup(){const e=null===this.projectionMatrix?ll:Zn(this.projectionMatrix),t=this.previousCameraViewMatrix.mul(this.previousModelWorldMatrix),r=e.mul(Bl).mul(Vl),s=this.previousProjectionMatrix.mul(t).mul(Ul),i=r.xy.div(r.w),n=s.xy.div(s.w);return ua(i,n)}}function db(e){let t=ub.get(e);return void 0===t&&(t={},ub.set(e,t)),t}function cb(e,t=0){const r=db(e);let s=r[t];return void 0===s&&(r[t]=s=new a,r[t].copy(e.matrixWorld)),s}const hb=Ui(lb),pb=ki(([e])=>yb(e.rgb)),gb=ki(([e,t=ji(1)])=>t.mix(yb(e.rgb),e.rgb)),mb=ki(([e,t=ji(1)])=>{const r=oa(e.r,e.g,e.b).div(3),s=e.r.max(e.g.max(e.b)),i=s.sub(r).mul(t).mul(-3);return Lo(e.rgb,s,i)}),fb=ki(([e,t=ji(1)])=>{const r=en(.57735,.57735,.57735),s=t.cos();return en(e.rgb.mul(s).add(r.cross(e.rgb).mul(t.sin()).add(r.mul(Eo(r,e.rgb).mul(s.oneMinus())))))}),yb=(e,t=en(c.getLuminanceCoefficients(new r)))=>Eo(e,t),bb=ki(([e,t=en(1),s=en(0),i=en(1),n=ji(1),a=en(c.getLuminanceCoefficients(new r,le))])=>{const o=e.rgb.dot(en(a)),u=To(e.rgb.mul(t).add(s),0).toVar(),l=u.pow(i).toVar();return Hi(u.r.greaterThan(0),()=>{u.r.assign(l.r)}),Hi(u.g.greaterThan(0),()=>{u.g.assign(l.g)}),Hi(u.b.greaterThan(0),()=>{u.b.assign(l.b)}),u.assign(o.add(u.sub(o).mul(n))),nn(u.rgb,e.a)});class xb extends Ks{static get type(){return"PosterizeNode"}constructor(e,t){super(),this.sourceNode=e,this.stepsNode=t}setup(){const{sourceNode:e,stepsNode:t}=this;return e.mul(t).floor().div(t)}}const Tb=Vi(xb).setParameterLength(2),_b=new t;class vb extends Yu{static get type(){return"PassTextureNode"}constructor(e,t){super(t),this.passNode=e,this.setUpdateMatrix(!1)}setup(e){return e.object.isQuadMesh&&this.passNode.build(e),super.setup(e)}clone(){return new this.constructor(this.passNode,this.value)}}class Nb extends vb{static get type(){return"PassMultipleTextureNode"}constructor(e,t,r=!1){super(e,null),this.textureName=t,this.previousTexture=r}updateTexture(){this.value=this.previousTexture?this.passNode.getPreviousTexture(this.textureName):this.passNode.getTexture(this.textureName)}setup(e){return this.updateTexture(),super.setup(e)}clone(){const e=new this.constructor(this.passNode,this.textureName,this.previousTexture);return e.uvNode=this.uvNode,e.levelNode=this.levelNode,e.biasNode=this.biasNode,e.sampler=this.sampler,e.depthNode=this.depthNode,e.compareNode=this.compareNode,e.gradNode=this.gradNode,e}}class Sb extends Ks{static get type(){return"PassNode"}constructor(e,t,r,s={}){super("vec4"),this.scope=e,this.scene=t,this.camera=r,this.options=s,this._pixelRatio=1,this._width=1,this._height=1;const i=new U;i.isRenderTargetTexture=!0,i.name="depth";const n=new ue(this._width*this._pixelRatio,this._height*this._pixelRatio,{type:ce,...s});n.texture.name="output",n.depthTexture=i,this.renderTarget=n,this._textures={output:n.texture,depth:i},this._textureNodes={},this._linearDepthNodes={},this._viewZNodes={},this._previousTextures={},this._previousTextureNodes={},this._cameraNear=Zn(0),this._cameraFar=Zn(0),this._mrt=null,this._layers=null,this._resolution=1,this.isPassNode=!0,this.updateBeforeType=Vs.FRAME,this.global=!0}setResolution(e){return this._resolution=e,this}getResolution(){return this._resolution}setLayers(e){return this._layers=e,this}getLayers(){return this._layers}setMRT(e){return this._mrt=e,this}getMRT(){return this._mrt}getTexture(e){let t=this._textures[e];if(void 0===t){t=this.renderTarget.texture.clone(),t.name=e,this._textures[e]=t,this.renderTarget.textures.push(t)}return t}getPreviousTexture(e){let t=this._previousTextures[e];return void 0===t&&(t=this.getTexture(e).clone(),this._previousTextures[e]=t),t}toggleTexture(e){const t=this._previousTextures[e];if(void 0!==t){const r=this._textures[e],s=this.renderTarget.textures.indexOf(r);this.renderTarget.textures[s]=t,this._textures[e]=t,this._previousTextures[e]=r,this._textureNodes[e].updateTexture(),this._previousTextureNodes[e].updateTexture()}}getTextureNode(e="output"){let t=this._textureNodes[e];return void 0===t&&(t=Li(new Nb(this,e)),t.updateTexture(),this._textureNodes[e]=t),t}getPreviousTextureNode(e="output"){let t=this._previousTextureNodes[e];return void 0===t&&(void 0===this._textureNodes[e]&&this.getTextureNode(e),t=Li(new Nb(this,e,!0)),t.updateTexture(),this._previousTextureNodes[e]=t),t}getViewZNode(e="depth"){let t=this._viewZNodes[e];if(void 0===t){const r=this._cameraNear,s=this._cameraFar;this._viewZNodes[e]=t=$h(this.getTextureNode(e),r,s)}return t}getLinearDepthNode(e="depth"){let t=this._linearDepthNodes[e];if(void 0===t){const r=this._cameraNear,s=this._cameraFar,i=this.getViewZNode(e);this._linearDepthNodes[e]=t=zh(i,r,s)}return t}setup({renderer:e}){return this.renderTarget.samples=void 0===this.options.samples?e.samples:this.options.samples,this.renderTarget.texture.type=e.getColorBufferType(),this.scope===Sb.COLOR?this.getTextureNode():this.getLinearDepthNode()}updateBefore(e){const{renderer:t}=e,{scene:r}=this;let s,i;const n=t.getOutputRenderTarget();n&&!0===n.isXRRenderTarget?(i=1,s=t.xr.getCamera(),t.xr.updateCamera(s),_b.set(n.width,n.height)):(s=this.camera,i=t.getPixelRatio(),t.getSize(_b)),this._pixelRatio=i,this.setSize(_b.width,_b.height);const a=t.getRenderTarget(),o=t.getMRT(),u=s.layers.mask;this._cameraNear.value=s.near,this._cameraFar.value=s.far,null!==this._layers&&(s.layers.mask=this._layers.mask);for(const e in this._previousTextures)this.toggleTexture(e);t.setRenderTarget(this.renderTarget),t.setMRT(this._mrt),t.render(r,s),t.setRenderTarget(a),t.setMRT(o),s.layers.mask=u}setSize(e,t){this._width=e,this._height=t;const r=this._width*this._pixelRatio*this._resolution,s=this._height*this._pixelRatio*this._resolution;this.renderTarget.setSize(r,s)}setPixelRatio(e){this._pixelRatio=e,this.setSize(this._width,this._height)}dispose(){this.renderTarget.dispose()}}Sb.COLOR="color",Sb.DEPTH="depth";class Eb extends Sb{static get type(){return"ToonOutlinePassNode"}constructor(e,t,r,s,i){super(Sb.COLOR,e,t),this.colorNode=r,this.thicknessNode=s,this.alphaNode=i,this._materialCache=new WeakMap}updateBefore(e){const{renderer:t}=e,r=t.getRenderObjectFunction();t.setRenderObjectFunction((e,r,s,i,n,a,o,u)=>{if((n.isMeshToonMaterial||n.isMeshToonNodeMaterial)&&!1===n.wireframe){const l=this._getOutlineMaterial(n);t.renderObject(e,r,s,i,l,a,o,u)}t.renderObject(e,r,s,i,n,a,o,u)}),super.updateBefore(e),t.setRenderObjectFunction(r)}_createMaterial(){const e=new lp;e.isMeshToonOutlineMaterial=!0,e.name="Toon_Outline",e.side=S;const t=Xl.negate(),r=ll.mul(Bl),s=ji(1),i=r.mul(nn(Vl,1)),n=r.mul(nn(Vl.add(t),1)),a=Ya(i.sub(n));return e.vertexNode=i.add(a.mul(this.thicknessNode).mul(i.w).mul(s)),e.colorNode=nn(this.colorNode,this.alphaNode),e}_getOutlineMaterial(e){let t=this._materialCache.get(e);return void 0===t&&(t=this._createMaterial(),this._materialCache.set(e,t)),t}}const wb=ki(([e,t])=>e.mul(t).clamp()).setLayout({name:"linearToneMapping",type:"vec3",inputs:[{name:"color",type:"vec3"},{name:"exposure",type:"float"}]}),Ab=ki(([e,t])=>(e=e.mul(t)).div(e.add(1)).clamp()).setLayout({name:"reinhardToneMapping",type:"vec3",inputs:[{name:"color",type:"vec3"},{name:"exposure",type:"float"}]}),Rb=ki(([e,t])=>{const r=(e=(e=e.mul(t)).sub(.004).max(0)).mul(e.mul(6.2).add(.5)),s=e.mul(e.mul(6.2).add(1.7)).add(.06);return r.div(s).pow(2.2)}).setLayout({name:"cineonToneMapping",type:"vec3",inputs:[{name:"color",type:"vec3"},{name:"exposure",type:"float"}]}),Cb=ki(([e])=>{const t=e.mul(e.add(.0245786)).sub(90537e-9),r=e.mul(e.add(.432951).mul(.983729)).add(.238081);return t.div(r)}),Mb=ki(([e,t])=>{const r=dn(.59719,.35458,.04823,.076,.90834,.01566,.0284,.13383,.83777),s=dn(1.60475,-.53108,-.07367,-.10208,1.10813,-.00605,-.00327,-.07276,1.07602);return e=e.mul(t).div(.6),e=r.mul(e),e=Cb(e),(e=s.mul(e)).clamp()}).setLayout({name:"acesFilmicToneMapping",type:"vec3",inputs:[{name:"color",type:"vec3"},{name:"exposure",type:"float"}]}),Pb=dn(en(1.6605,-.1246,-.0182),en(-.5876,1.1329,-.1006),en(-.0728,-.0083,1.1187)),Bb=dn(en(.6274,.0691,.0164),en(.3293,.9195,.088),en(.0433,.0113,.8956)),Fb=ki(([e])=>{const t=en(e).toVar(),r=en(t.mul(t)).toVar(),s=en(r.mul(r)).toVar();return ji(15.5).mul(s.mul(r)).sub(la(40.14,s.mul(t))).add(la(31.96,s).sub(la(6.868,r.mul(t))).add(la(.4298,r).add(la(.1191,t).sub(.00232))))}),Lb=ki(([e,t])=>{const r=en(e).toVar(),s=dn(en(.856627153315983,.137318972929847,.11189821299995),en(.0951212405381588,.761241990602591,.0767994186031903),en(.0482516061458583,.101439036467562,.811302368396859)),i=dn(en(1.1271005818144368,-.1413297634984383,-.14132976349843826),en(-.11060664309660323,1.157823702216272,-.11060664309660294),en(-.016493938717834573,-.016493938717834257,1.2519364065950405)),n=ji(-12.47393),a=ji(4.026069);return r.mulAssign(t),r.assign(Bb.mul(r)),r.assign(s.mul(r)),r.assign(To(r,1e-10)),r.assign(Wa(r)),r.assign(r.sub(n).div(a.sub(n))),r.assign(Do(r,0,1)),r.assign(Fb(r)),r.assign(i.mul(r)),r.assign(Ao(To(en(0),r),en(2.2))),r.assign(Pb.mul(r)),r.assign(Do(r,0,1)),r}).setLayout({name:"agxToneMapping",type:"vec3",inputs:[{name:"color",type:"vec3"},{name:"exposure",type:"float"}]}),Db=ki(([e,t])=>{const r=ji(.76),s=ji(.15);e=e.mul(t);const i=xo(e.r,xo(e.g,e.b)),n=Xo(i.lessThan(.08),i.sub(la(6.25,i.mul(i))),.04);e.subAssign(n);const a=To(e.r,To(e.g,e.b));Hi(a.lessThan(r),()=>e);const o=ua(1,r),u=ua(1,o.mul(o).div(a.add(o.sub(r))));e.mulAssign(u.div(a));const l=ua(1,da(1,s.mul(a.sub(u)).add(1)));return Lo(e,en(u),l)}).setLayout({name:"neutralToneMapping",type:"vec3",inputs:[{name:"color",type:"vec3"},{name:"exposure",type:"float"}]});class Ib extends js{static get type(){return"CodeNode"}constructor(e="",t=[],r=""){super("code"),this.isCodeNode=!0,this.global=!0,this.code=e,this.includes=t,this.language=r}setIncludes(e){return this.includes=e,this}getIncludes(){return this.includes}generate(e){const t=this.getIncludes(e);for(const r of t)r.build(e);const r=e.getCodeFromNode(this,this.getNodeType(e));return r.code=this.code,r.code}serialize(e){super.serialize(e),e.code=this.code,e.language=this.language}deserialize(e){super.deserialize(e),this.code=e.code,this.language=e.language}}const Vb=Vi(Ib).setParameterLength(1,3);class Ub extends Ib{static get type(){return"FunctionNode"}constructor(e="",t=[],r=""){super(e,t,r)}getNodeType(e){return this.getNodeFunction(e).type}getInputs(e){return this.getNodeFunction(e).inputs}getNodeFunction(e){const t=e.getDataFromNode(this);let r=t.nodeFunction;return void 0===r&&(r=e.parser.parseFunction(this.code),t.nodeFunction=r),r}generate(e,t){super.generate(e);const r=this.getNodeFunction(e),s=r.name,i=r.type,n=e.getCodeFromNode(this,i);""!==s&&(n.name=s);const a=e.getPropertyName(n),o=this.getNodeFunction(e).getCode(a);return n.code=o+"\n","property"===t?a:e.format(`${a}()`,i,t)}}const Ob=(e,t=[],r="")=>{for(let e=0;es.call(...e);return i.functionNode=s,i};class kb extends js{static get type(){return"ScriptableValueNode"}constructor(e=null){super(),this._value=e,this._cache=null,this.inputType=null,this.outputType=null,this.events=new o,this.isScriptableValueNode=!0}get isScriptableOutputNode(){return null!==this.outputType}set value(e){this._value!==e&&(this._cache&&"URL"===this.inputType&&this.value.value instanceof ArrayBuffer&&(URL.revokeObjectURL(this._cache),this._cache=null),this._value=e,this.events.dispatchEvent({type:"change"}),this.refresh())}get value(){return this._value}refresh(){this.events.dispatchEvent({type:"refresh"})}getValue(){const e=this.value;if(e&&null===this._cache&&"URL"===this.inputType&&e.value instanceof ArrayBuffer)this._cache=URL.createObjectURL(new Blob([e.value]));else if(e&&null!==e.value&&void 0!==e.value&&(("URL"===this.inputType||"String"===this.inputType)&&"string"==typeof e.value||"Number"===this.inputType&&"number"==typeof e.value||"Vector2"===this.inputType&&e.value.isVector2||"Vector3"===this.inputType&&e.value.isVector3||"Vector4"===this.inputType&&e.value.isVector4||"Color"===this.inputType&&e.value.isColor||"Matrix3"===this.inputType&&e.value.isMatrix3||"Matrix4"===this.inputType&&e.value.isMatrix4))return e.value;return this._cache||e}getNodeType(e){return this.value&&this.value.isNode?this.value.getNodeType(e):"float"}setup(){return this.value&&this.value.isNode?this.value:ji()}serialize(e){super.serialize(e),null!==this.value?"ArrayBuffer"===this.inputType?e.value=Fs(this.value):e.value=this.value?this.value.toJSON(e.meta).uuid:null:e.value=null,e.inputType=this.inputType,e.outputType=this.outputType}deserialize(e){super.deserialize(e);let t=null;null!==e.value&&(t="ArrayBuffer"===e.inputType?Ls(e.value):"Texture"===e.inputType?e.meta.textures[e.value]:e.meta.nodes[e.value]||null),this.value=t,this.inputType=e.inputType,this.outputType=e.outputType}}const Gb=Vi(kb).setParameterLength(1);class zb extends Map{get(e,t=null,...r){if(this.has(e))return super.get(e);if(null!==t){const s=t(...r);return this.set(e,s),s}}}class Hb{constructor(e){this.scriptableNode=e}get parameters(){return this.scriptableNode.parameters}get layout(){return this.scriptableNode.getLayout()}getInputLayout(e){return this.scriptableNode.getInputLayout(e)}get(e){const t=this.parameters[e];return t?t.getValue():null}}const $b=new zb;class Wb extends js{static get type(){return"ScriptableNode"}constructor(e=null,t={}){super(),this.codeNode=e,this.parameters=t,this._local=new zb,this._output=Gb(null),this._outputs={},this._source=this.source,this._method=null,this._object=null,this._value=null,this._needsOutputUpdate=!0,this.onRefresh=this.onRefresh.bind(this),this.isScriptableNode=!0}get source(){return this.codeNode?this.codeNode.code:""}setLocal(e,t){return this._local.set(e,t)}getLocal(e){return this._local.get(e)}onRefresh(){this._refresh()}getInputLayout(e){for(const t of this.getLayout())if(t.inputType&&(t.id===e||t.name===e))return t}getOutputLayout(e){for(const t of this.getLayout())if(t.outputType&&(t.id===e||t.name===e))return t}setOutput(e,t){const r=this._outputs;return void 0===r[e]?r[e]=Gb(t):r[e].value=t,this}getOutput(e){return this._outputs[e]}getParameter(e){return this.parameters[e]}setParameter(e,t){const r=this.parameters;return t&&t.isScriptableNode?(this.deleteParameter(e),r[e]=t,r[e].getDefaultOutput().events.addEventListener("refresh",this.onRefresh)):t&&t.isScriptableValueNode?(this.deleteParameter(e),r[e]=t,r[e].events.addEventListener("refresh",this.onRefresh)):void 0===r[e]?(r[e]=Gb(t),r[e].events.addEventListener("refresh",this.onRefresh)):r[e].value=t,this}getValue(){return this.getDefaultOutput().getValue()}deleteParameter(e){let t=this.parameters[e];return t&&(t.isScriptableNode&&(t=t.getDefaultOutput()),t.events.removeEventListener("refresh",this.onRefresh)),this}clearParameters(){for(const e of Object.keys(this.parameters))this.deleteParameter(e);return this.needsUpdate=!0,this}call(e,...t){const r=this.getObject()[e];if("function"==typeof r)return r(...t)}async callAsync(e,...t){const r=this.getObject()[e];if("function"==typeof r)return"AsyncFunction"===r.constructor.name?await r(...t):r(...t)}getNodeType(e){return this.getDefaultOutputNode().getNodeType(e)}refresh(e=null){null!==e?this.getOutput(e).refresh():this._refresh()}getObject(){if(this.needsUpdate&&this.dispose(),null!==this._object)return this._object;const e=new Hb(this),t=$b.get("THREE"),r=$b.get("TSL"),s=this.getMethod(),i=[e,this._local,$b,()=>this.refresh(),(e,t)=>this.setOutput(e,t),t,r];this._object=s(...i);const n=this._object.layout;if(n&&(!1===n.cache&&this._local.clear(),this._output.outputType=n.outputType||null,Array.isArray(n.elements)))for(const e of n.elements){const t=e.id||e.name;e.inputType&&(void 0===this.getParameter(t)&&this.setParameter(t,null),this.getParameter(t).inputType=e.inputType),e.outputType&&(void 0===this.getOutput(t)&&this.setOutput(t,null),this.getOutput(t).outputType=e.outputType)}return this._object}deserialize(e){super.deserialize(e);for(const e in this.parameters){let t=this.parameters[e];t.isScriptableNode&&(t=t.getDefaultOutput()),t.events.addEventListener("refresh",this.onRefresh)}}getLayout(){return this.getObject().layout}getDefaultOutputNode(){const e=this.getDefaultOutput().value;return e&&e.isNode?e:ji()}getDefaultOutput(){return this._exec()._output}getMethod(){if(this.needsUpdate&&this.dispose(),null!==this._method)return this._method;const e=["layout","init","main","dispose"].join(", "),t="\nreturn { ...output, "+e+" };",r="var "+e+"; var output = {};\n"+this.codeNode.code+t;return this._method=new Function(...["parameters","local","global","refresh","setOutput","THREE","TSL"],r),this._method}dispose(){null!==this._method&&(this._object&&"function"==typeof this._object.dispose&&this._object.dispose(),this._method=null,this._object=null,this._source=null,this._value=null,this._needsOutputUpdate=!0,this._output.value=null,this._outputs={})}setup(){return this.getDefaultOutputNode()}getCacheKey(e){const t=[bs(this.source),this.getDefaultOutputNode().getCacheKey(e)];for(const r in this.parameters)t.push(this.parameters[r].getCacheKey(e));return xs(t)}set needsUpdate(e){!0===e&&this.dispose()}get needsUpdate(){return this.source!==this._source}_exec(){return null===this.codeNode||(!0===this._needsOutputUpdate&&(this._value=this.call("main"),this._needsOutputUpdate=!1),this._output.value=this._value),this}_refresh(){this.needsUpdate=!0,this._exec(),this._output.refresh()}}const jb=Vi(Wb).setParameterLength(1,2);function qb(e){let t;const r=e.context.getViewZ;return void 0!==r&&(t=r(this)),(t||Gl.z).negate()}const Xb=ki(([e,t],r)=>{const s=qb(r);return Uo(e,t,s)}),Kb=ki(([e],t)=>{const r=qb(t);return e.mul(e,r,r).negate().exp().oneMinus()}),Yb=ki(([e,t])=>nn(t.toFloat().mix(Dn.rgb,e.toVec3()),Dn.a));let Qb=null,Zb=null;class Jb extends js{static get type(){return"RangeNode"}constructor(e=ji(),t=ji()){super(),this.minNode=e,this.maxNode=t}getVectorLength(e){const t=e.getTypeLength(Ms(this.minNode.value)),r=e.getTypeLength(Ms(this.maxNode.value));return t>r?t:r}getNodeType(e){return e.object.count>1?e.getTypeFromLength(this.getVectorLength(e)):"float"}setup(e){const t=e.object;let r=null;if(t.count>1){const i=this.minNode.value,n=this.maxNode.value,a=e.getTypeLength(Ms(i)),o=e.getTypeLength(Ms(n));Qb=Qb||new s,Zb=Zb||new s,Qb.setScalar(0),Zb.setScalar(0),1===a?Qb.setScalar(i):i.isColor?Qb.set(i.r,i.g,i.b,1):Qb.set(i.x,i.y,i.z||0,i.w||0),1===o?Zb.setScalar(n):n.isColor?Zb.set(n.r,n.g,n.b,1):Zb.set(n.x,n.y,n.z||0,n.w||0);const l=4,d=l*t.count,c=new Float32Array(d);for(let e=0;eLi(new tx(e,t)),sx=rx("numWorkgroups","uvec3"),ix=rx("workgroupId","uvec3"),nx=rx("globalId","uvec3"),ax=rx("localId","uvec3"),ox=rx("subgroupSize","uint");const ux=Vi(class extends js{constructor(e){super(),this.scope=e}generate(e){const{scope:t}=this,{renderer:r}=e;!0===r.backend.isWebGLBackend?e.addFlowCode(`\t// ${t}Barrier \n`):e.addLineFlowCode(`${t}Barrier()`,this)}});class lx extends qs{constructor(e,t){super(e,t),this.isWorkgroupInfoElementNode=!0}generate(e,t){let r;const s=e.context.assign;if(r=super.generate(e),!0!==s){const s=this.getNodeType(e);r=e.format(r,s,t)}return r}}class dx extends js{constructor(e,t,r=0){super(t),this.bufferType=t,this.bufferCount=r,this.isWorkgroupInfoNode=!0,this.elementType=t,this.scope=e}label(e){return this.name=e,this}setScope(e){return this.scope=e,this}getElementType(){return this.elementType}getInputType(){return`${this.scope}Array`}element(e){return Li(new lx(this,e))}generate(e){return e.getScopedArray(this.name||`${this.scope}Array_${this.id}`,this.scope.toLowerCase(),this.bufferType,this.bufferCount)}}class cx extends js{static get type(){return"AtomicFunctionNode"}constructor(e,t,r){super("uint"),this.method=e,this.pointerNode=t,this.valueNode=r,this.parents=!0}getInputType(e){return this.pointerNode.getNodeType(e)}getNodeType(e){return this.getInputType(e)}generate(e){const t=e.getNodeProperties(this),r=t.parents,s=this.method,i=this.getNodeType(e),n=this.getInputType(e),a=this.pointerNode,o=this.valueNode,u=[];u.push(`&${a.build(e,n)}`),null!==o&&u.push(o.build(e,n));const l=`${e.getMethod(s,i)}( ${u.join(", ")} )`;if(!(1===r.length&&!0===r[0].isStackNode))return void 0===t.constNode&&(t.constNode=Iu(l,i).toConst()),t.constNode.build(e);e.addLineFlowCode(l,this)}}cx.ATOMIC_LOAD="atomicLoad",cx.ATOMIC_STORE="atomicStore",cx.ATOMIC_ADD="atomicAdd",cx.ATOMIC_SUB="atomicSub",cx.ATOMIC_MAX="atomicMax",cx.ATOMIC_MIN="atomicMin",cx.ATOMIC_AND="atomicAnd",cx.ATOMIC_OR="atomicOr",cx.ATOMIC_XOR="atomicXor";const hx=Vi(cx),px=(e,t,r)=>hx(e,t,r).toStack();let gx;function mx(e){gx=gx||new WeakMap;let t=gx.get(e);return void 0===t&&gx.set(e,t={}),t}function fx(e){const t=mx(e);return t.shadowMatrix||(t.shadowMatrix=Zn("mat4").setGroup(Kn).onRenderUpdate(t=>(!0===e.castShadow&&!1!==t.renderer.shadowMap.enabled||e.shadow.updateMatrices(e),e.shadow.matrix)))}function yx(e,t=Ol){const r=fx(e).mul(t);return r.xyz.div(r.w)}function bx(e){const t=mx(e);return t.position||(t.position=Zn(new r).setGroup(Kn).onRenderUpdate((t,r)=>r.value.setFromMatrixPosition(e.matrixWorld)))}function xx(e){const t=mx(e);return t.targetPosition||(t.targetPosition=Zn(new r).setGroup(Kn).onRenderUpdate((t,r)=>r.value.setFromMatrixPosition(e.target.matrixWorld)))}function Tx(e){const t=mx(e);return t.viewPosition||(t.viewPosition=Zn(new r).setGroup(Kn).onRenderUpdate(({camera:t},s)=>{s.value=s.value||new r,s.value.setFromMatrixPosition(e.matrixWorld),s.value.applyMatrix4(t.matrixWorldInverse)}))}const _x=e=>cl.transformDirection(bx(e).sub(xx(e))),vx=(e,t)=>{for(const r of t)if(r.isAnalyticLightNode&&r.light.id===e)return r;return null},Nx=new WeakMap,Sx=[];class Ex extends js{static get type(){return"LightsNode"}constructor(){super("vec3"),this.totalDiffuseNode=mn("vec3","totalDiffuse"),this.totalSpecularNode=mn("vec3","totalSpecular"),this.outgoingLightNode=mn("vec3","outgoingLight"),this._lights=[],this._lightNodes=null,this._lightNodesHash=null,this.global=!0}customCacheKey(){const e=this._lights;for(let t=0;te.sort((e,t)=>e.id-t.id))(this._lights),i=e.renderer.library;for(const e of s)if(e.isNode)t.push(Li(e));else{let s=null;if(null!==r&&(s=vx(e.id,r)),null===s){const r=i.getLightNodeClass(e.constructor);if(null===r){console.warn(`LightsNode.setupNodeLights: Light node not found for ${e.constructor.name}`);continue}let s=null;Nx.has(e)?s=Nx.get(e):(s=Li(new r(e)),Nx.set(e,s)),t.push(s)}}this._lightNodes=t}setupDirectLight(e,t,r){const{lightingModel:s,reflectedLight:i}=e.context;s.direct({...r,lightNode:t,reflectedLight:i},e)}setupDirectRectAreaLight(e,t,r){const{lightingModel:s,reflectedLight:i}=e.context;s.directRectArea({...r,lightNode:t,reflectedLight:i},e)}setupLights(e,t){for(const r of t)r.build(e)}getLightNodes(e){return null===this._lightNodes&&this.setupLightsNode(e),this._lightNodes}setup(e){const t=e.lightsNode;e.lightsNode=this;let r=this.outgoingLightNode;const s=e.context,i=s.lightingModel,n=e.getNodeProperties(this);if(i){const{totalDiffuseNode:t,totalSpecularNode:a}=this;s.outgoingLight=r;const o=e.addStack();n.nodes=o.nodes,i.start(e);const{backdrop:u,backdropAlpha:l}=s,{directDiffuse:d,directSpecular:c,indirectDiffuse:h,indirectSpecular:p}=s.reflectedLight;let g=d.add(h);null!==u&&(g=en(null!==l?l.mix(g,u):u),s.material.transparent=!0),t.assign(g),a.assign(c.add(p)),r.assign(t.add(a)),i.finish(e),r=r.bypass(e.removeStack())}else n.nodes=[];return e.lightsNode=t,r}setLights(e){return this._lights=e,this._lightNodes=null,this._lightNodesHash=null,this}getLights(){return this._lights}get hasLights(){return this._lights.length>0}}class wx extends js{static get type(){return"ShadowBaseNode"}constructor(e){super(),this.light=e,this.updateBeforeType=Vs.RENDER,this.isShadowBaseNode=!0}setupShadowPosition({context:e,material:t}){Ax.assign(t.receivedShadowPositionNode||e.shadowPositionWorld||Ol)}}const Ax=mn("vec3","shadowPositionWorld");function Rx(t,r={}){return r.toneMapping=t.toneMapping,r.toneMappingExposure=t.toneMappingExposure,r.outputColorSpace=t.outputColorSpace,r.renderTarget=t.getRenderTarget(),r.activeCubeFace=t.getActiveCubeFace(),r.activeMipmapLevel=t.getActiveMipmapLevel(),r.renderObjectFunction=t.getRenderObjectFunction(),r.pixelRatio=t.getPixelRatio(),r.mrt=t.getMRT(),r.clearColor=t.getClearColor(r.clearColor||new e),r.clearAlpha=t.getClearAlpha(),r.autoClear=t.autoClear,r.scissorTest=t.getScissorTest(),r}function Cx(e,t){return t=Rx(e,t),e.setMRT(null),e.setRenderObjectFunction(null),e.setClearColor(0,1),e.autoClear=!0,t}function Mx(e,t){e.toneMapping=t.toneMapping,e.toneMappingExposure=t.toneMappingExposure,e.outputColorSpace=t.outputColorSpace,e.setRenderTarget(t.renderTarget,t.activeCubeFace,t.activeMipmapLevel),e.setRenderObjectFunction(t.renderObjectFunction),e.setPixelRatio(t.pixelRatio),e.setMRT(t.mrt),e.setClearColor(t.clearColor,t.clearAlpha),e.autoClear=t.autoClear,e.setScissorTest(t.scissorTest)}function Px(e,t={}){return t.background=e.background,t.backgroundNode=e.backgroundNode,t.overrideMaterial=e.overrideMaterial,t}function Bx(e,t){return t=Px(e,t),e.background=null,e.backgroundNode=null,e.overrideMaterial=null,t}function Fx(e,t){e.background=t.background,e.backgroundNode=t.backgroundNode,e.overrideMaterial=t.overrideMaterial}function Lx(e,t,r){return r=Bx(t,r=Cx(e,r))}function Dx(e,t,r){Mx(e,r),Fx(t,r)}var Ix=Object.freeze({__proto__:null,resetRendererAndSceneState:Lx,resetRendererState:Cx,resetSceneState:Bx,restoreRendererAndSceneState:Dx,restoreRendererState:Mx,restoreSceneState:Fx,saveRendererAndSceneState:function(e,t,r={}){return r=Px(t,r=Rx(e,r))},saveRendererState:Rx,saveSceneState:Px});const Vx=new WeakMap,Ux=ki(({depthTexture:e,shadowCoord:t,depthLayer:r})=>{let s=Zu(e,t.xy).label("t_basic");return e.isArrayTexture&&(s=s.depth(r)),s.compare(t.z)}),Ox=ki(({depthTexture:e,shadowCoord:t,shadow:r,depthLayer:s})=>{const i=(t,r)=>{let i=Zu(e,t);return e.isArrayTexture&&(i=i.depth(s)),i.compare(r)},n=_d("mapSize","vec2",r).setGroup(Kn),a=_d("radius","float",r).setGroup(Kn),o=Yi(1).div(n),u=o.x.negate().mul(a),l=o.y.negate().mul(a),d=o.x.mul(a),c=o.y.mul(a),h=u.div(2),p=l.div(2),g=d.div(2),m=c.div(2);return oa(i(t.xy.add(Yi(u,l)),t.z),i(t.xy.add(Yi(0,l)),t.z),i(t.xy.add(Yi(d,l)),t.z),i(t.xy.add(Yi(h,p)),t.z),i(t.xy.add(Yi(0,p)),t.z),i(t.xy.add(Yi(g,p)),t.z),i(t.xy.add(Yi(u,0)),t.z),i(t.xy.add(Yi(h,0)),t.z),i(t.xy,t.z),i(t.xy.add(Yi(g,0)),t.z),i(t.xy.add(Yi(d,0)),t.z),i(t.xy.add(Yi(h,m)),t.z),i(t.xy.add(Yi(0,m)),t.z),i(t.xy.add(Yi(g,m)),t.z),i(t.xy.add(Yi(u,c)),t.z),i(t.xy.add(Yi(0,c)),t.z),i(t.xy.add(Yi(d,c)),t.z)).mul(1/17)}),kx=ki(({depthTexture:e,shadowCoord:t,shadow:r,depthLayer:s})=>{const i=(t,r)=>{let i=Zu(e,t);return e.isArrayTexture&&(i=i.depth(s)),i.compare(r)},n=_d("mapSize","vec2",r).setGroup(Kn),a=Yi(1).div(n),o=a.x,u=a.y,l=t.xy,d=Qa(l.mul(n).add(.5));return l.subAssign(d.mul(a)),oa(i(l,t.z),i(l.add(Yi(o,0)),t.z),i(l.add(Yi(0,u)),t.z),i(l.add(a),t.z),Lo(i(l.add(Yi(o.negate(),0)),t.z),i(l.add(Yi(o.mul(2),0)),t.z),d.x),Lo(i(l.add(Yi(o.negate(),u)),t.z),i(l.add(Yi(o.mul(2),u)),t.z),d.x),Lo(i(l.add(Yi(0,u.negate())),t.z),i(l.add(Yi(0,u.mul(2))),t.z),d.y),Lo(i(l.add(Yi(o,u.negate())),t.z),i(l.add(Yi(o,u.mul(2))),t.z),d.y),Lo(Lo(i(l.add(Yi(o.negate(),u.negate())),t.z),i(l.add(Yi(o.mul(2),u.negate())),t.z),d.x),Lo(i(l.add(Yi(o.negate(),u.mul(2))),t.z),i(l.add(Yi(o.mul(2),u.mul(2))),t.z),d.x),d.y)).mul(1/9)}),Gx=ki(({depthTexture:e,shadowCoord:t,depthLayer:r})=>{const s=ji(1).toVar();let i=Zu(e).sample(t.xy);e.isArrayTexture&&(i=i.depth(r)),i=i.rg;const n=_o(t.z,i.x);return Hi(n.notEqual(ji(1)),()=>{const e=t.z.sub(i.x),r=To(0,i.y.mul(i.y));let a=r.div(r.add(e.mul(e)));a=Do(ua(a,.3).div(.95-.3)),s.assign(Do(To(n,a)))}),s}),zx=ki(([e,t,r])=>{let s=Ol.sub(e).length();return s=s.sub(t).div(r.sub(t)),s=s.saturate(),s}),Hx=e=>{let t=Vx.get(e);if(void 0===t){const r=e.isPointLight?(e=>{const t=e.shadow.camera,r=_d("near","float",t).setGroup(Kn),s=_d("far","float",t).setGroup(Kn),i=xl(e);return zx(i,r,s)})(e):null;t=new lp,t.colorNode=nn(0,0,0,1),t.depthNode=r,t.isShadowPassMaterial=!0,t.name="ShadowMaterial",t.fog=!1,Vx.set(e,t)}return t},$x=new af,Wx=[],jx=(e,t,r,s)=>{Wx[0]=e,Wx[1]=t;let i=$x.get(Wx);return void 0!==i&&i.shadowType===r&&i.useVelocity===s||(i=(i,n,a,o,u,l,...d)=>{(!0===i.castShadow||i.receiveShadow&&r===De)&&(s&&(Bs(i).useVelocity=!0),i.onBeforeShadow(e,i,a,t.camera,o,n.overrideMaterial,l),e.renderObject(i,n,a,o,u,l,...d),i.onAfterShadow(e,i,a,t.camera,o,n.overrideMaterial,l))},i.shadowType=r,i.useVelocity=s,$x.set(Wx,i)),Wx[0]=null,Wx[1]=null,i},qx=ki(({samples:e,radius:t,size:r,shadowPass:s,depthLayer:i})=>{const n=ji(0).toVar("meanVertical"),a=ji(0).toVar("squareMeanVertical"),o=e.lessThanEqual(ji(1)).select(ji(0),ji(2).div(e.sub(1))),u=e.lessThanEqual(ji(1)).select(ji(0),ji(-1));ch({start:qi(0),end:qi(e),type:"int",condition:"<"},({i:e})=>{const l=u.add(ji(e).mul(o));let d=s.sample(oa(Rh.xy,Yi(0,l).mul(t)).div(r));s.value.isArrayTexture&&(d=d.depth(i)),d=d.x,n.addAssign(d),a.addAssign(d.mul(d))}),n.divAssign(e),a.divAssign(e);const l=ja(a.sub(n.mul(n)));return Yi(n,l)}),Xx=ki(({samples:e,radius:t,size:r,shadowPass:s,depthLayer:i})=>{const n=ji(0).toVar("meanHorizontal"),a=ji(0).toVar("squareMeanHorizontal"),o=e.lessThanEqual(ji(1)).select(ji(0),ji(2).div(e.sub(1))),u=e.lessThanEqual(ji(1)).select(ji(0),ji(-1));ch({start:qi(0),end:qi(e),type:"int",condition:"<"},({i:e})=>{const l=u.add(ji(e).mul(o));let d=s.sample(oa(Rh.xy,Yi(l,0).mul(t)).div(r));s.value.isArrayTexture&&(d=d.depth(i)),n.addAssign(d.x),a.addAssign(oa(d.y.mul(d.y),d.x.mul(d.x)))}),n.divAssign(e),a.divAssign(e);const l=ja(a.sub(n.mul(n)));return Yi(n,l)}),Kx=[Ux,Ox,kx,Gx];let Yx;const Qx=new Uy;class Zx extends wx{static get type(){return"ShadowNode"}constructor(e,t=null){super(e),this.shadow=t||e.shadow,this.shadowMap=null,this.vsmShadowMapVertical=null,this.vsmShadowMapHorizontal=null,this.vsmMaterialVertical=null,this.vsmMaterialHorizontal=null,this._node=null,this._cameraFrameId=new WeakMap,this.isShadowNode=!0,this.depthLayer=0}setupShadowFilter(e,{filterFn:t,depthTexture:r,shadowCoord:s,shadow:i,depthLayer:n}){const a=s.x.greaterThanEqual(0).and(s.x.lessThanEqual(1)).and(s.y.greaterThanEqual(0)).and(s.y.lessThanEqual(1)).and(s.z.lessThanEqual(1)),o=t({depthTexture:r,shadowCoord:s,shadow:i,depthLayer:n});return a.select(o,ji(1))}setupShadowCoord(e,t){const{shadow:r}=this,{renderer:s}=e,i=_d("bias","float",r).setGroup(Kn);let n,a=t;if(r.camera.isOrthographicCamera||!0!==s.logarithmicDepthBuffer)a=a.xyz.div(a.w),n=a.z,s.coordinateSystem===d&&(n=n.mul(2).sub(1));else{const e=a.w;a=a.xy.div(e);const t=_d("near","float",r.camera).setGroup(Kn),s=_d("far","float",r.camera).setGroup(Kn);n=Wh(e.negate(),t,s)}return a=en(a.x,a.y.oneMinus(),n.add(i)),a}getShadowFilterFn(e){return Kx[e]}setupRenderTarget(e,t){const r=new U(e.mapSize.width,e.mapSize.height);r.name="ShadowDepthTexture",r.compareFunction=Ie;const s=t.createRenderTarget(e.mapSize.width,e.mapSize.height);return s.texture.name="ShadowMap",s.texture.type=e.mapType,s.depthTexture=r,{shadowMap:s,depthTexture:r}}setupShadow(e){const{renderer:t}=e,{light:r,shadow:s}=this,i=t.shadowMap.type,{depthTexture:n,shadowMap:a}=this.setupRenderTarget(s,e);if(s.camera.updateProjectionMatrix(),i===De&&!0!==s.isPointLightShadow){n.compareFunction=null,a.depth>1?(a._vsmShadowMapVertical||(a._vsmShadowMapVertical=e.createRenderTarget(s.mapSize.width,s.mapSize.height,{format:Ve,type:ce,depth:a.depth,depthBuffer:!1}),a._vsmShadowMapVertical.texture.name="VSMVertical"),this.vsmShadowMapVertical=a._vsmShadowMapVertical,a._vsmShadowMapHorizontal||(a._vsmShadowMapHorizontal=e.createRenderTarget(s.mapSize.width,s.mapSize.height,{format:Ve,type:ce,depth:a.depth,depthBuffer:!1}),a._vsmShadowMapHorizontal.texture.name="VSMHorizontal"),this.vsmShadowMapHorizontal=a._vsmShadowMapHorizontal):(this.vsmShadowMapVertical=e.createRenderTarget(s.mapSize.width,s.mapSize.height,{format:Ve,type:ce,depthBuffer:!1}),this.vsmShadowMapHorizontal=e.createRenderTarget(s.mapSize.width,s.mapSize.height,{format:Ve,type:ce,depthBuffer:!1}));let t=Zu(n);n.isArrayTexture&&(t=t.depth(this.depthLayer));let r=Zu(this.vsmShadowMapVertical.texture);n.isArrayTexture&&(r=r.depth(this.depthLayer));const i=_d("blurSamples","float",s).setGroup(Kn),o=_d("radius","float",s).setGroup(Kn),u=_d("mapSize","vec2",s).setGroup(Kn);let l=this.vsmMaterialVertical||(this.vsmMaterialVertical=new lp);l.fragmentNode=qx({samples:i,radius:o,size:u,shadowPass:t,depthLayer:this.depthLayer}).context(e.getSharedContext()),l.name="VSMVertical",l=this.vsmMaterialHorizontal||(this.vsmMaterialHorizontal=new lp),l.fragmentNode=Xx({samples:i,radius:o,size:u,shadowPass:r,depthLayer:this.depthLayer}).context(e.getSharedContext()),l.name="VSMHorizontal"}const o=_d("intensity","float",s).setGroup(Kn),u=_d("normalBias","float",s).setGroup(Kn),l=fx(r).mul(Ax.add(Jl.mul(u))),d=this.setupShadowCoord(e,l),c=s.filterNode||this.getShadowFilterFn(t.shadowMap.type)||null;if(null===c)throw new Error("THREE.WebGPURenderer: Shadow map type not supported yet.");const h=i===De&&!0!==s.isPointLightShadow?this.vsmShadowMapHorizontal.texture:n,p=this.setupShadowFilter(e,{filterFn:c,shadowTexture:a.texture,depthTexture:h,shadowCoord:d,shadow:s,depthLayer:this.depthLayer});let g=Zu(a.texture,d);n.isArrayTexture&&(g=g.depth(this.depthLayer));const m=Lo(1,p.rgb.mix(g,1),o.mul(g.a)).toVar();return this.shadowMap=a,this.shadow.map=a,m}setup(e){if(!1!==e.renderer.shadowMap.enabled)return ki(()=>{let t=this._node;return this.setupShadowPosition(e),null===t&&(this._node=t=this.setupShadow(e)),e.material.shadowNode&&console.warn('THREE.NodeMaterial: ".shadowNode" is deprecated. Use ".castShadowNode" instead.'),e.material.receivedShadowNode&&(t=e.material.receivedShadowNode(t)),t})()}renderShadow(e){const{shadow:t,shadowMap:r,light:s}=this,{renderer:i,scene:n}=e;t.updateMatrices(s),r.setSize(t.mapSize.width,t.mapSize.height,r.depth),i.render(n,t.camera)}updateShadow(e){const{shadowMap:t,light:r,shadow:s}=this,{renderer:i,scene:n,camera:a}=e,o=i.shadowMap.type,u=t.depthTexture.version;this._depthVersionCached=u;const l=s.camera.layers.mask;4294967294&s.camera.layers.mask||(s.camera.layers.mask=a.layers.mask);const d=i.getRenderObjectFunction(),c=i.getMRT(),h=!!c&&c.has("velocity");Yx=Lx(i,n,Yx),n.overrideMaterial=Hx(r),i.setRenderObjectFunction(jx(i,s,o,h)),i.setClearColor(0,0),i.setRenderTarget(t),this.renderShadow(e),i.setRenderObjectFunction(d),o===De&&!0!==s.isPointLightShadow&&this.vsmPass(i),s.camera.layers.mask=l,Dx(i,n,Yx)}vsmPass(e){const{shadow:t}=this,r=this.shadowMap.depth;this.vsmShadowMapVertical.setSize(t.mapSize.width,t.mapSize.height,r),this.vsmShadowMapHorizontal.setSize(t.mapSize.width,t.mapSize.height,r),e.setRenderTarget(this.vsmShadowMapVertical),Qx.material=this.vsmMaterialVertical,Qx.render(e),e.setRenderTarget(this.vsmShadowMapHorizontal),Qx.material=this.vsmMaterialHorizontal,Qx.render(e)}dispose(){this.shadowMap.dispose(),this.shadowMap=null,null!==this.vsmShadowMapVertical&&(this.vsmShadowMapVertical.dispose(),this.vsmShadowMapVertical=null,this.vsmMaterialVertical.dispose(),this.vsmMaterialVertical=null),null!==this.vsmShadowMapHorizontal&&(this.vsmShadowMapHorizontal.dispose(),this.vsmShadowMapHorizontal=null,this.vsmMaterialHorizontal.dispose(),this.vsmMaterialHorizontal=null),super.dispose()}updateBefore(e){const{shadow:t}=this;let r=t.needsUpdate||t.autoUpdate;r&&(this._cameraFrameId[e.camera]===e.frameId&&(r=!1),this._cameraFrameId[e.camera]=e.frameId),r&&(this.updateShadow(e),this.shadowMap.depthTexture.version===this._depthVersionCached&&(t.needsUpdate=!1))}}const Jx=(e,t)=>Li(new Zx(e,t)),eT=new e,tT=ki(([e,t])=>{const r=e.toVar(),s=io(r),i=da(1,To(s.x,To(s.y,s.z)));s.mulAssign(i),r.mulAssign(i.mul(t.mul(2).oneMinus()));const n=Yi(r.xy).toVar(),a=t.mul(1.5).oneMinus();return Hi(s.z.greaterThanEqual(a),()=>{Hi(r.z.greaterThan(0),()=>{n.x.assign(ua(4,r.x))})}).ElseIf(s.x.greaterThanEqual(a),()=>{const e=no(r.x);n.x.assign(r.z.mul(e).add(e.mul(2)))}).ElseIf(s.y.greaterThanEqual(a),()=>{const e=no(r.y);n.x.assign(r.x.add(e.mul(2)).add(2)),n.y.assign(r.z.mul(e).sub(2))}),Yi(.125,.25).mul(n).add(Yi(.375,.75)).flipY()}).setLayout({name:"cubeToUV",type:"vec2",inputs:[{name:"pos",type:"vec3"},{name:"texelSizeY",type:"float"}]}),rT=ki(({depthTexture:e,bd3D:t,dp:r,texelSize:s})=>Zu(e,tT(t,s.y)).compare(r)),sT=ki(({depthTexture:e,bd3D:t,dp:r,texelSize:s,shadow:i})=>{const n=_d("radius","float",i).setGroup(Kn),a=Yi(-1,1).mul(n).mul(s.y);return Zu(e,tT(t.add(a.xyy),s.y)).compare(r).add(Zu(e,tT(t.add(a.yyy),s.y)).compare(r)).add(Zu(e,tT(t.add(a.xyx),s.y)).compare(r)).add(Zu(e,tT(t.add(a.yyx),s.y)).compare(r)).add(Zu(e,tT(t,s.y)).compare(r)).add(Zu(e,tT(t.add(a.xxy),s.y)).compare(r)).add(Zu(e,tT(t.add(a.yxy),s.y)).compare(r)).add(Zu(e,tT(t.add(a.xxx),s.y)).compare(r)).add(Zu(e,tT(t.add(a.yxx),s.y)).compare(r)).mul(1/9)}),iT=ki(({filterFn:e,depthTexture:t,shadowCoord:r,shadow:s})=>{const i=r.xyz.toVar(),n=i.length(),a=Zn("float").setGroup(Kn).onRenderUpdate(()=>s.camera.near),o=Zn("float").setGroup(Kn).onRenderUpdate(()=>s.camera.far),u=_d("bias","float",s).setGroup(Kn),l=Zn(s.mapSize).setGroup(Kn),d=ji(1).toVar();return Hi(n.sub(o).lessThanEqual(0).and(n.sub(a).greaterThanEqual(0)),()=>{const r=n.sub(a).div(o.sub(a)).toVar();r.addAssign(u);const c=i.normalize(),h=Yi(1).div(l.mul(Yi(4,2)));d.assign(e({depthTexture:t,bd3D:c,dp:r,texelSize:h,shadow:s}))}),d}),nT=new s,aT=new t,oT=new t;class uT extends Zx{static get type(){return"PointShadowNode"}constructor(e,t=null){super(e,t)}getShadowFilterFn(e){return e===Ue?rT:sT}setupShadowCoord(e,t){return t}setupShadowFilter(e,{filterFn:t,shadowTexture:r,depthTexture:s,shadowCoord:i,shadow:n}){return iT({filterFn:t,shadowTexture:r,depthTexture:s,shadowCoord:i,shadow:n})}renderShadow(e){const{shadow:t,shadowMap:r,light:s}=this,{renderer:i,scene:n}=e,a=t.getFrameExtents();oT.copy(t.mapSize),oT.multiply(a),r.setSize(oT.width,oT.height),aT.copy(t.mapSize);const o=i.autoClear,u=i.getClearColor(eT),l=i.getClearAlpha();i.autoClear=!1,i.setClearColor(t.clearColor,t.clearAlpha),i.clear();const d=t.getViewportCount();for(let e=0;eLi(new uT(e,t));class dT extends bh{static get type(){return"AnalyticLightNode"}constructor(t=null){super(),this.light=t,this.color=new e,this.colorNode=t&&t.colorNode||Zn(this.color).setGroup(Kn),this.baseColorNode=null,this.shadowNode=null,this.shadowColorNode=null,this.isAnalyticLightNode=!0,this.updateType=Vs.FRAME}getHash(){return this.light.uuid}getLightVector(e){return Tx(this.light).sub(e.context.positionView||Gl)}setupDirect(){}setupDirectRectArea(){}setupShadowNode(){return Jx(this.light)}setupShadow(e){const{renderer:t}=e;if(!1===t.shadowMap.enabled)return;let r=this.shadowColorNode;if(null===r){const e=this.light.shadow.shadowNode;let t;t=void 0!==e?Li(e):this.setupShadowNode(),this.shadowNode=t,this.shadowColorNode=r=this.colorNode.mul(t),this.baseColorNode=this.colorNode}this.colorNode=r}setup(e){this.colorNode=this.baseColorNode||this.colorNode,this.light.castShadow?e.object.receiveShadow&&this.setupShadow(e):null!==this.shadowNode&&(this.shadowNode.dispose(),this.shadowNode=null,this.shadowColorNode=null);const t=this.setupDirect(e),r=this.setupDirectRectArea(e);t&&e.lightsNode.setupDirectLight(e,this,t),r&&e.lightsNode.setupDirectRectAreaLight(e,this,r)}update(){const{light:e}=this;this.color.copy(e.color).multiplyScalar(e.intensity)}}const cT=ki(({lightDistance:e,cutoffDistance:t,decayExponent:r})=>{const s=e.pow(r).max(.01).reciprocal();return t.greaterThan(0).select(s.mul(e.div(t).pow4().oneMinus().clamp().pow2()),s)}),hT=({color:e,lightVector:t,cutoffDistance:r,decayExponent:s})=>{const i=t.normalize(),n=t.length(),a=cT({lightDistance:n,cutoffDistance:r,decayExponent:s});return{lightDirection:i,lightColor:e.mul(a)}};class pT extends dT{static get type(){return"PointLightNode"}constructor(e=null){super(e),this.cutoffDistanceNode=Zn(0).setGroup(Kn),this.decayExponentNode=Zn(2).setGroup(Kn)}update(e){const{light:t}=this;super.update(e),this.cutoffDistanceNode.value=t.distance,this.decayExponentNode.value=t.decay}setupShadowNode(){return lT(this.light)}setupDirect(e){return hT({color:this.colorNode,lightVector:this.getLightVector(e),cutoffDistance:this.cutoffDistanceNode,decayExponent:this.decayExponentNode})}}const gT=ki(([e=$u()])=>{const t=e.mul(2),r=t.x.floor(),s=t.y.floor();return r.add(s).mod(2).sign()}),mT=ki(([e=$u()],{renderer:t,material:r})=>{const s=Fo(e.mul(2).sub(1));let i;if(r.alphaToCoverage&&t.samples>1){const e=ji(s.fwidth()).toVar();i=Uo(e.oneMinus(),e.add(1),s).oneMinus()}else i=Xo(s.greaterThan(1),0,1);return i}),fT=ki(([e,t,r])=>{const s=ji(r).toVar(),i=ji(t).toVar(),n=Ki(e).toVar();return Xo(n,i,s)}).setLayout({name:"mx_select",type:"float",inputs:[{name:"b",type:"bool"},{name:"t",type:"float"},{name:"f",type:"float"}]}),yT=ki(([e,t])=>{const r=Ki(t).toVar(),s=ji(e).toVar();return Xo(r,s.negate(),s)}).setLayout({name:"mx_negate_if",type:"float",inputs:[{name:"val",type:"float"},{name:"b",type:"bool"}]}),bT=ki(([e])=>{const t=ji(e).toVar();return qi(Xa(t))}).setLayout({name:"mx_floor",type:"int",inputs:[{name:"x",type:"float"}]}),xT=ki(([e,t])=>{const r=ji(e).toVar();return t.assign(bT(r)),r.sub(ji(t))}),TT=uy([ki(([e,t,r,s,i,n])=>{const a=ji(n).toVar(),o=ji(i).toVar(),u=ji(s).toVar(),l=ji(r).toVar(),d=ji(t).toVar(),c=ji(e).toVar(),h=ji(ua(1,o)).toVar();return ua(1,a).mul(c.mul(h).add(d.mul(o))).add(a.mul(l.mul(h).add(u.mul(o))))}).setLayout({name:"mx_bilerp_0",type:"float",inputs:[{name:"v0",type:"float"},{name:"v1",type:"float"},{name:"v2",type:"float"},{name:"v3",type:"float"},{name:"s",type:"float"},{name:"t",type:"float"}]}),ki(([e,t,r,s,i,n])=>{const a=ji(n).toVar(),o=ji(i).toVar(),u=en(s).toVar(),l=en(r).toVar(),d=en(t).toVar(),c=en(e).toVar(),h=ji(ua(1,o)).toVar();return ua(1,a).mul(c.mul(h).add(d.mul(o))).add(a.mul(l.mul(h).add(u.mul(o))))}).setLayout({name:"mx_bilerp_1",type:"vec3",inputs:[{name:"v0",type:"vec3"},{name:"v1",type:"vec3"},{name:"v2",type:"vec3"},{name:"v3",type:"vec3"},{name:"s",type:"float"},{name:"t",type:"float"}]})]),_T=uy([ki(([e,t,r,s,i,n,a,o,u,l,d])=>{const c=ji(d).toVar(),h=ji(l).toVar(),p=ji(u).toVar(),g=ji(o).toVar(),m=ji(a).toVar(),f=ji(n).toVar(),y=ji(i).toVar(),b=ji(s).toVar(),x=ji(r).toVar(),T=ji(t).toVar(),_=ji(e).toVar(),v=ji(ua(1,p)).toVar(),N=ji(ua(1,h)).toVar();return ji(ua(1,c)).toVar().mul(N.mul(_.mul(v).add(T.mul(p))).add(h.mul(x.mul(v).add(b.mul(p))))).add(c.mul(N.mul(y.mul(v).add(f.mul(p))).add(h.mul(m.mul(v).add(g.mul(p))))))}).setLayout({name:"mx_trilerp_0",type:"float",inputs:[{name:"v0",type:"float"},{name:"v1",type:"float"},{name:"v2",type:"float"},{name:"v3",type:"float"},{name:"v4",type:"float"},{name:"v5",type:"float"},{name:"v6",type:"float"},{name:"v7",type:"float"},{name:"s",type:"float"},{name:"t",type:"float"},{name:"r",type:"float"}]}),ki(([e,t,r,s,i,n,a,o,u,l,d])=>{const c=ji(d).toVar(),h=ji(l).toVar(),p=ji(u).toVar(),g=en(o).toVar(),m=en(a).toVar(),f=en(n).toVar(),y=en(i).toVar(),b=en(s).toVar(),x=en(r).toVar(),T=en(t).toVar(),_=en(e).toVar(),v=ji(ua(1,p)).toVar(),N=ji(ua(1,h)).toVar();return ji(ua(1,c)).toVar().mul(N.mul(_.mul(v).add(T.mul(p))).add(h.mul(x.mul(v).add(b.mul(p))))).add(c.mul(N.mul(y.mul(v).add(f.mul(p))).add(h.mul(m.mul(v).add(g.mul(p))))))}).setLayout({name:"mx_trilerp_1",type:"vec3",inputs:[{name:"v0",type:"vec3"},{name:"v1",type:"vec3"},{name:"v2",type:"vec3"},{name:"v3",type:"vec3"},{name:"v4",type:"vec3"},{name:"v5",type:"vec3"},{name:"v6",type:"vec3"},{name:"v7",type:"vec3"},{name:"s",type:"float"},{name:"t",type:"float"},{name:"r",type:"float"}]})]),vT=ki(([e,t,r])=>{const s=ji(r).toVar(),i=ji(t).toVar(),n=Xi(e).toVar(),a=Xi(n.bitAnd(Xi(7))).toVar(),o=ji(fT(a.lessThan(Xi(4)),i,s)).toVar(),u=ji(la(2,fT(a.lessThan(Xi(4)),s,i))).toVar();return yT(o,Ki(a.bitAnd(Xi(1)))).add(yT(u,Ki(a.bitAnd(Xi(2)))))}).setLayout({name:"mx_gradient_float_0",type:"float",inputs:[{name:"hash",type:"uint"},{name:"x",type:"float"},{name:"y",type:"float"}]}),NT=ki(([e,t,r,s])=>{const i=ji(s).toVar(),n=ji(r).toVar(),a=ji(t).toVar(),o=Xi(e).toVar(),u=Xi(o.bitAnd(Xi(15))).toVar(),l=ji(fT(u.lessThan(Xi(8)),a,n)).toVar(),d=ji(fT(u.lessThan(Xi(4)),n,fT(u.equal(Xi(12)).or(u.equal(Xi(14))),a,i))).toVar();return yT(l,Ki(u.bitAnd(Xi(1)))).add(yT(d,Ki(u.bitAnd(Xi(2)))))}).setLayout({name:"mx_gradient_float_1",type:"float",inputs:[{name:"hash",type:"uint"},{name:"x",type:"float"},{name:"y",type:"float"},{name:"z",type:"float"}]}),ST=uy([vT,NT]),ET=ki(([e,t,r])=>{const s=ji(r).toVar(),i=ji(t).toVar(),n=rn(e).toVar();return en(ST(n.x,i,s),ST(n.y,i,s),ST(n.z,i,s))}).setLayout({name:"mx_gradient_vec3_0",type:"vec3",inputs:[{name:"hash",type:"uvec3"},{name:"x",type:"float"},{name:"y",type:"float"}]}),wT=ki(([e,t,r,s])=>{const i=ji(s).toVar(),n=ji(r).toVar(),a=ji(t).toVar(),o=rn(e).toVar();return en(ST(o.x,a,n,i),ST(o.y,a,n,i),ST(o.z,a,n,i))}).setLayout({name:"mx_gradient_vec3_1",type:"vec3",inputs:[{name:"hash",type:"uvec3"},{name:"x",type:"float"},{name:"y",type:"float"},{name:"z",type:"float"}]}),AT=uy([ET,wT]),RT=ki(([e])=>{const t=ji(e).toVar();return la(.6616,t)}).setLayout({name:"mx_gradient_scale2d_0",type:"float",inputs:[{name:"v",type:"float"}]}),CT=ki(([e])=>{const t=ji(e).toVar();return la(.982,t)}).setLayout({name:"mx_gradient_scale3d_0",type:"float",inputs:[{name:"v",type:"float"}]}),MT=uy([RT,ki(([e])=>{const t=en(e).toVar();return la(.6616,t)}).setLayout({name:"mx_gradient_scale2d_1",type:"vec3",inputs:[{name:"v",type:"vec3"}]})]),PT=uy([CT,ki(([e])=>{const t=en(e).toVar();return la(.982,t)}).setLayout({name:"mx_gradient_scale3d_1",type:"vec3",inputs:[{name:"v",type:"vec3"}]})]),BT=ki(([e,t])=>{const r=qi(t).toVar(),s=Xi(e).toVar();return s.shiftLeft(r).bitOr(s.shiftRight(qi(32).sub(r)))}).setLayout({name:"mx_rotl32",type:"uint",inputs:[{name:"x",type:"uint"},{name:"k",type:"int"}]}),FT=ki(([e,t,r])=>{e.subAssign(r),e.bitXorAssign(BT(r,qi(4))),r.addAssign(t),t.subAssign(e),t.bitXorAssign(BT(e,qi(6))),e.addAssign(r),r.subAssign(t),r.bitXorAssign(BT(t,qi(8))),t.addAssign(e),e.subAssign(r),e.bitXorAssign(BT(r,qi(16))),r.addAssign(t),t.subAssign(e),t.bitXorAssign(BT(e,qi(19))),e.addAssign(r),r.subAssign(t),r.bitXorAssign(BT(t,qi(4))),t.addAssign(e)}),LT=ki(([e,t,r])=>{const s=Xi(r).toVar(),i=Xi(t).toVar(),n=Xi(e).toVar();return s.bitXorAssign(i),s.subAssign(BT(i,qi(14))),n.bitXorAssign(s),n.subAssign(BT(s,qi(11))),i.bitXorAssign(n),i.subAssign(BT(n,qi(25))),s.bitXorAssign(i),s.subAssign(BT(i,qi(16))),n.bitXorAssign(s),n.subAssign(BT(s,qi(4))),i.bitXorAssign(n),i.subAssign(BT(n,qi(14))),s.bitXorAssign(i),s.subAssign(BT(i,qi(24))),s}).setLayout({name:"mx_bjfinal",type:"uint",inputs:[{name:"a",type:"uint"},{name:"b",type:"uint"},{name:"c",type:"uint"}]}),DT=ki(([e])=>{const t=Xi(e).toVar();return ji(t).div(ji(Xi(qi(4294967295))))}).setLayout({name:"mx_bits_to_01",type:"float",inputs:[{name:"bits",type:"uint"}]}),IT=ki(([e])=>{const t=ji(e).toVar();return t.mul(t).mul(t).mul(t.mul(t.mul(6).sub(15)).add(10))}).setLayout({name:"mx_fade",type:"float",inputs:[{name:"t",type:"float"}]}),VT=uy([ki(([e])=>{const t=qi(e).toVar(),r=Xi(Xi(1)).toVar(),s=Xi(Xi(qi(3735928559)).add(r.shiftLeft(Xi(2))).add(Xi(13))).toVar();return LT(s.add(Xi(t)),s,s)}).setLayout({name:"mx_hash_int_0",type:"uint",inputs:[{name:"x",type:"int"}]}),ki(([e,t])=>{const r=qi(t).toVar(),s=qi(e).toVar(),i=Xi(Xi(2)).toVar(),n=Xi().toVar(),a=Xi().toVar(),o=Xi().toVar();return n.assign(a.assign(o.assign(Xi(qi(3735928559)).add(i.shiftLeft(Xi(2))).add(Xi(13))))),n.addAssign(Xi(s)),a.addAssign(Xi(r)),LT(n,a,o)}).setLayout({name:"mx_hash_int_1",type:"uint",inputs:[{name:"x",type:"int"},{name:"y",type:"int"}]}),ki(([e,t,r])=>{const s=qi(r).toVar(),i=qi(t).toVar(),n=qi(e).toVar(),a=Xi(Xi(3)).toVar(),o=Xi().toVar(),u=Xi().toVar(),l=Xi().toVar();return o.assign(u.assign(l.assign(Xi(qi(3735928559)).add(a.shiftLeft(Xi(2))).add(Xi(13))))),o.addAssign(Xi(n)),u.addAssign(Xi(i)),l.addAssign(Xi(s)),LT(o,u,l)}).setLayout({name:"mx_hash_int_2",type:"uint",inputs:[{name:"x",type:"int"},{name:"y",type:"int"},{name:"z",type:"int"}]}),ki(([e,t,r,s])=>{const i=qi(s).toVar(),n=qi(r).toVar(),a=qi(t).toVar(),o=qi(e).toVar(),u=Xi(Xi(4)).toVar(),l=Xi().toVar(),d=Xi().toVar(),c=Xi().toVar();return l.assign(d.assign(c.assign(Xi(qi(3735928559)).add(u.shiftLeft(Xi(2))).add(Xi(13))))),l.addAssign(Xi(o)),d.addAssign(Xi(a)),c.addAssign(Xi(n)),FT(l,d,c),l.addAssign(Xi(i)),LT(l,d,c)}).setLayout({name:"mx_hash_int_3",type:"uint",inputs:[{name:"x",type:"int"},{name:"y",type:"int"},{name:"z",type:"int"},{name:"xx",type:"int"}]}),ki(([e,t,r,s,i])=>{const n=qi(i).toVar(),a=qi(s).toVar(),o=qi(r).toVar(),u=qi(t).toVar(),l=qi(e).toVar(),d=Xi(Xi(5)).toVar(),c=Xi().toVar(),h=Xi().toVar(),p=Xi().toVar();return c.assign(h.assign(p.assign(Xi(qi(3735928559)).add(d.shiftLeft(Xi(2))).add(Xi(13))))),c.addAssign(Xi(l)),h.addAssign(Xi(u)),p.addAssign(Xi(o)),FT(c,h,p),c.addAssign(Xi(a)),h.addAssign(Xi(n)),LT(c,h,p)}).setLayout({name:"mx_hash_int_4",type:"uint",inputs:[{name:"x",type:"int"},{name:"y",type:"int"},{name:"z",type:"int"},{name:"xx",type:"int"},{name:"yy",type:"int"}]})]),UT=uy([ki(([e,t])=>{const r=qi(t).toVar(),s=qi(e).toVar(),i=Xi(VT(s,r)).toVar(),n=rn().toVar();return n.x.assign(i.bitAnd(qi(255))),n.y.assign(i.shiftRight(qi(8)).bitAnd(qi(255))),n.z.assign(i.shiftRight(qi(16)).bitAnd(qi(255))),n}).setLayout({name:"mx_hash_vec3_0",type:"uvec3",inputs:[{name:"x",type:"int"},{name:"y",type:"int"}]}),ki(([e,t,r])=>{const s=qi(r).toVar(),i=qi(t).toVar(),n=qi(e).toVar(),a=Xi(VT(n,i,s)).toVar(),o=rn().toVar();return o.x.assign(a.bitAnd(qi(255))),o.y.assign(a.shiftRight(qi(8)).bitAnd(qi(255))),o.z.assign(a.shiftRight(qi(16)).bitAnd(qi(255))),o}).setLayout({name:"mx_hash_vec3_1",type:"uvec3",inputs:[{name:"x",type:"int"},{name:"y",type:"int"},{name:"z",type:"int"}]})]),OT=uy([ki(([e])=>{const t=Yi(e).toVar(),r=qi().toVar(),s=qi().toVar(),i=ji(xT(t.x,r)).toVar(),n=ji(xT(t.y,s)).toVar(),a=ji(IT(i)).toVar(),o=ji(IT(n)).toVar(),u=ji(TT(ST(VT(r,s),i,n),ST(VT(r.add(qi(1)),s),i.sub(1),n),ST(VT(r,s.add(qi(1))),i,n.sub(1)),ST(VT(r.add(qi(1)),s.add(qi(1))),i.sub(1),n.sub(1)),a,o)).toVar();return MT(u)}).setLayout({name:"mx_perlin_noise_float_0",type:"float",inputs:[{name:"p",type:"vec2"}]}),ki(([e])=>{const t=en(e).toVar(),r=qi().toVar(),s=qi().toVar(),i=qi().toVar(),n=ji(xT(t.x,r)).toVar(),a=ji(xT(t.y,s)).toVar(),o=ji(xT(t.z,i)).toVar(),u=ji(IT(n)).toVar(),l=ji(IT(a)).toVar(),d=ji(IT(o)).toVar(),c=ji(_T(ST(VT(r,s,i),n,a,o),ST(VT(r.add(qi(1)),s,i),n.sub(1),a,o),ST(VT(r,s.add(qi(1)),i),n,a.sub(1),o),ST(VT(r.add(qi(1)),s.add(qi(1)),i),n.sub(1),a.sub(1),o),ST(VT(r,s,i.add(qi(1))),n,a,o.sub(1)),ST(VT(r.add(qi(1)),s,i.add(qi(1))),n.sub(1),a,o.sub(1)),ST(VT(r,s.add(qi(1)),i.add(qi(1))),n,a.sub(1),o.sub(1)),ST(VT(r.add(qi(1)),s.add(qi(1)),i.add(qi(1))),n.sub(1),a.sub(1),o.sub(1)),u,l,d)).toVar();return PT(c)}).setLayout({name:"mx_perlin_noise_float_1",type:"float",inputs:[{name:"p",type:"vec3"}]})]),kT=uy([ki(([e])=>{const t=Yi(e).toVar(),r=qi().toVar(),s=qi().toVar(),i=ji(xT(t.x,r)).toVar(),n=ji(xT(t.y,s)).toVar(),a=ji(IT(i)).toVar(),o=ji(IT(n)).toVar(),u=en(TT(AT(UT(r,s),i,n),AT(UT(r.add(qi(1)),s),i.sub(1),n),AT(UT(r,s.add(qi(1))),i,n.sub(1)),AT(UT(r.add(qi(1)),s.add(qi(1))),i.sub(1),n.sub(1)),a,o)).toVar();return MT(u)}).setLayout({name:"mx_perlin_noise_vec3_0",type:"vec3",inputs:[{name:"p",type:"vec2"}]}),ki(([e])=>{const t=en(e).toVar(),r=qi().toVar(),s=qi().toVar(),i=qi().toVar(),n=ji(xT(t.x,r)).toVar(),a=ji(xT(t.y,s)).toVar(),o=ji(xT(t.z,i)).toVar(),u=ji(IT(n)).toVar(),l=ji(IT(a)).toVar(),d=ji(IT(o)).toVar(),c=en(_T(AT(UT(r,s,i),n,a,o),AT(UT(r.add(qi(1)),s,i),n.sub(1),a,o),AT(UT(r,s.add(qi(1)),i),n,a.sub(1),o),AT(UT(r.add(qi(1)),s.add(qi(1)),i),n.sub(1),a.sub(1),o),AT(UT(r,s,i.add(qi(1))),n,a,o.sub(1)),AT(UT(r.add(qi(1)),s,i.add(qi(1))),n.sub(1),a,o.sub(1)),AT(UT(r,s.add(qi(1)),i.add(qi(1))),n,a.sub(1),o.sub(1)),AT(UT(r.add(qi(1)),s.add(qi(1)),i.add(qi(1))),n.sub(1),a.sub(1),o.sub(1)),u,l,d)).toVar();return PT(c)}).setLayout({name:"mx_perlin_noise_vec3_1",type:"vec3",inputs:[{name:"p",type:"vec3"}]})]),GT=uy([ki(([e])=>{const t=ji(e).toVar(),r=qi(bT(t)).toVar();return DT(VT(r))}).setLayout({name:"mx_cell_noise_float_0",type:"float",inputs:[{name:"p",type:"float"}]}),ki(([e])=>{const t=Yi(e).toVar(),r=qi(bT(t.x)).toVar(),s=qi(bT(t.y)).toVar();return DT(VT(r,s))}).setLayout({name:"mx_cell_noise_float_1",type:"float",inputs:[{name:"p",type:"vec2"}]}),ki(([e])=>{const t=en(e).toVar(),r=qi(bT(t.x)).toVar(),s=qi(bT(t.y)).toVar(),i=qi(bT(t.z)).toVar();return DT(VT(r,s,i))}).setLayout({name:"mx_cell_noise_float_2",type:"float",inputs:[{name:"p",type:"vec3"}]}),ki(([e])=>{const t=nn(e).toVar(),r=qi(bT(t.x)).toVar(),s=qi(bT(t.y)).toVar(),i=qi(bT(t.z)).toVar(),n=qi(bT(t.w)).toVar();return DT(VT(r,s,i,n))}).setLayout({name:"mx_cell_noise_float_3",type:"float",inputs:[{name:"p",type:"vec4"}]})]),zT=uy([ki(([e])=>{const t=ji(e).toVar(),r=qi(bT(t)).toVar();return en(DT(VT(r,qi(0))),DT(VT(r,qi(1))),DT(VT(r,qi(2))))}).setLayout({name:"mx_cell_noise_vec3_0",type:"vec3",inputs:[{name:"p",type:"float"}]}),ki(([e])=>{const t=Yi(e).toVar(),r=qi(bT(t.x)).toVar(),s=qi(bT(t.y)).toVar();return en(DT(VT(r,s,qi(0))),DT(VT(r,s,qi(1))),DT(VT(r,s,qi(2))))}).setLayout({name:"mx_cell_noise_vec3_1",type:"vec3",inputs:[{name:"p",type:"vec2"}]}),ki(([e])=>{const t=en(e).toVar(),r=qi(bT(t.x)).toVar(),s=qi(bT(t.y)).toVar(),i=qi(bT(t.z)).toVar();return en(DT(VT(r,s,i,qi(0))),DT(VT(r,s,i,qi(1))),DT(VT(r,s,i,qi(2))))}).setLayout({name:"mx_cell_noise_vec3_2",type:"vec3",inputs:[{name:"p",type:"vec3"}]}),ki(([e])=>{const t=nn(e).toVar(),r=qi(bT(t.x)).toVar(),s=qi(bT(t.y)).toVar(),i=qi(bT(t.z)).toVar(),n=qi(bT(t.w)).toVar();return en(DT(VT(r,s,i,n,qi(0))),DT(VT(r,s,i,n,qi(1))),DT(VT(r,s,i,n,qi(2))))}).setLayout({name:"mx_cell_noise_vec3_3",type:"vec3",inputs:[{name:"p",type:"vec4"}]})]),HT=ki(([e,t,r,s])=>{const i=ji(s).toVar(),n=ji(r).toVar(),a=qi(t).toVar(),o=en(e).toVar(),u=ji(0).toVar(),l=ji(1).toVar();return ch(a,()=>{u.addAssign(l.mul(OT(o))),l.mulAssign(i),o.mulAssign(n)}),u}).setLayout({name:"mx_fractal_noise_float",type:"float",inputs:[{name:"p",type:"vec3"},{name:"octaves",type:"int"},{name:"lacunarity",type:"float"},{name:"diminish",type:"float"}]}),$T=ki(([e,t,r,s])=>{const i=ji(s).toVar(),n=ji(r).toVar(),a=qi(t).toVar(),o=en(e).toVar(),u=en(0).toVar(),l=ji(1).toVar();return ch(a,()=>{u.addAssign(l.mul(kT(o))),l.mulAssign(i),o.mulAssign(n)}),u}).setLayout({name:"mx_fractal_noise_vec3",type:"vec3",inputs:[{name:"p",type:"vec3"},{name:"octaves",type:"int"},{name:"lacunarity",type:"float"},{name:"diminish",type:"float"}]}),WT=ki(([e,t,r,s])=>{const i=ji(s).toVar(),n=ji(r).toVar(),a=qi(t).toVar(),o=en(e).toVar();return Yi(HT(o,a,n,i),HT(o.add(en(qi(19),qi(193),qi(17))),a,n,i))}).setLayout({name:"mx_fractal_noise_vec2",type:"vec2",inputs:[{name:"p",type:"vec3"},{name:"octaves",type:"int"},{name:"lacunarity",type:"float"},{name:"diminish",type:"float"}]}),jT=ki(([e,t,r,s])=>{const i=ji(s).toVar(),n=ji(r).toVar(),a=qi(t).toVar(),o=en(e).toVar(),u=en($T(o,a,n,i)).toVar(),l=ji(HT(o.add(en(qi(19),qi(193),qi(17))),a,n,i)).toVar();return nn(u,l)}).setLayout({name:"mx_fractal_noise_vec4",type:"vec4",inputs:[{name:"p",type:"vec3"},{name:"octaves",type:"int"},{name:"lacunarity",type:"float"},{name:"diminish",type:"float"}]}),qT=uy([ki(([e,t,r,s,i,n,a])=>{const o=qi(a).toVar(),u=ji(n).toVar(),l=qi(i).toVar(),d=qi(s).toVar(),c=qi(r).toVar(),h=qi(t).toVar(),p=Yi(e).toVar(),g=en(zT(Yi(h.add(d),c.add(l)))).toVar(),m=Yi(g.x,g.y).toVar();m.subAssign(.5),m.mulAssign(u),m.addAssign(.5);const f=Yi(Yi(ji(h),ji(c)).add(m)).toVar(),y=Yi(f.sub(p)).toVar();return Hi(o.equal(qi(2)),()=>io(y.x).add(io(y.y))),Hi(o.equal(qi(3)),()=>To(io(y.x),io(y.y))),Eo(y,y)}).setLayout({name:"mx_worley_distance_0",type:"float",inputs:[{name:"p",type:"vec2"},{name:"x",type:"int"},{name:"y",type:"int"},{name:"xoff",type:"int"},{name:"yoff",type:"int"},{name:"jitter",type:"float"},{name:"metric",type:"int"}]}),ki(([e,t,r,s,i,n,a,o,u])=>{const l=qi(u).toVar(),d=ji(o).toVar(),c=qi(a).toVar(),h=qi(n).toVar(),p=qi(i).toVar(),g=qi(s).toVar(),m=qi(r).toVar(),f=qi(t).toVar(),y=en(e).toVar(),b=en(zT(en(f.add(p),m.add(h),g.add(c)))).toVar();b.subAssign(.5),b.mulAssign(d),b.addAssign(.5);const x=en(en(ji(f),ji(m),ji(g)).add(b)).toVar(),T=en(x.sub(y)).toVar();return Hi(l.equal(qi(2)),()=>io(T.x).add(io(T.y)).add(io(T.z))),Hi(l.equal(qi(3)),()=>To(io(T.x),io(T.y),io(T.z))),Eo(T,T)}).setLayout({name:"mx_worley_distance_1",type:"float",inputs:[{name:"p",type:"vec3"},{name:"x",type:"int"},{name:"y",type:"int"},{name:"z",type:"int"},{name:"xoff",type:"int"},{name:"yoff",type:"int"},{name:"zoff",type:"int"},{name:"jitter",type:"float"},{name:"metric",type:"int"}]})]),XT=ki(([e,t,r])=>{const s=qi(r).toVar(),i=ji(t).toVar(),n=Yi(e).toVar(),a=qi().toVar(),o=qi().toVar(),u=Yi(xT(n.x,a),xT(n.y,o)).toVar(),l=ji(1e6).toVar();return ch({start:-1,end:qi(1),name:"x",condition:"<="},({x:e})=>{ch({start:-1,end:qi(1),name:"y",condition:"<="},({y:t})=>{const r=ji(qT(u,e,t,a,o,i,s)).toVar();l.assign(xo(l,r))})}),Hi(s.equal(qi(0)),()=>{l.assign(ja(l))}),l}).setLayout({name:"mx_worley_noise_float_0",type:"float",inputs:[{name:"p",type:"vec2"},{name:"jitter",type:"float"},{name:"metric",type:"int"}]}),KT=ki(([e,t,r])=>{const s=qi(r).toVar(),i=ji(t).toVar(),n=Yi(e).toVar(),a=qi().toVar(),o=qi().toVar(),u=Yi(xT(n.x,a),xT(n.y,o)).toVar(),l=Yi(1e6,1e6).toVar();return ch({start:-1,end:qi(1),name:"x",condition:"<="},({x:e})=>{ch({start:-1,end:qi(1),name:"y",condition:"<="},({y:t})=>{const r=ji(qT(u,e,t,a,o,i,s)).toVar();Hi(r.lessThan(l.x),()=>{l.y.assign(l.x),l.x.assign(r)}).ElseIf(r.lessThan(l.y),()=>{l.y.assign(r)})})}),Hi(s.equal(qi(0)),()=>{l.assign(ja(l))}),l}).setLayout({name:"mx_worley_noise_vec2_0",type:"vec2",inputs:[{name:"p",type:"vec2"},{name:"jitter",type:"float"},{name:"metric",type:"int"}]}),YT=ki(([e,t,r])=>{const s=qi(r).toVar(),i=ji(t).toVar(),n=Yi(e).toVar(),a=qi().toVar(),o=qi().toVar(),u=Yi(xT(n.x,a),xT(n.y,o)).toVar(),l=en(1e6,1e6,1e6).toVar();return ch({start:-1,end:qi(1),name:"x",condition:"<="},({x:e})=>{ch({start:-1,end:qi(1),name:"y",condition:"<="},({y:t})=>{const r=ji(qT(u,e,t,a,o,i,s)).toVar();Hi(r.lessThan(l.x),()=>{l.z.assign(l.y),l.y.assign(l.x),l.x.assign(r)}).ElseIf(r.lessThan(l.y),()=>{l.z.assign(l.y),l.y.assign(r)}).ElseIf(r.lessThan(l.z),()=>{l.z.assign(r)})})}),Hi(s.equal(qi(0)),()=>{l.assign(ja(l))}),l}).setLayout({name:"mx_worley_noise_vec3_0",type:"vec3",inputs:[{name:"p",type:"vec2"},{name:"jitter",type:"float"},{name:"metric",type:"int"}]}),QT=uy([XT,ki(([e,t,r])=>{const s=qi(r).toVar(),i=ji(t).toVar(),n=en(e).toVar(),a=qi().toVar(),o=qi().toVar(),u=qi().toVar(),l=en(xT(n.x,a),xT(n.y,o),xT(n.z,u)).toVar(),d=ji(1e6).toVar();return ch({start:-1,end:qi(1),name:"x",condition:"<="},({x:e})=>{ch({start:-1,end:qi(1),name:"y",condition:"<="},({y:t})=>{ch({start:-1,end:qi(1),name:"z",condition:"<="},({z:r})=>{const n=ji(qT(l,e,t,r,a,o,u,i,s)).toVar();d.assign(xo(d,n))})})}),Hi(s.equal(qi(0)),()=>{d.assign(ja(d))}),d}).setLayout({name:"mx_worley_noise_float_1",type:"float",inputs:[{name:"p",type:"vec3"},{name:"jitter",type:"float"},{name:"metric",type:"int"}]})]),ZT=uy([KT,ki(([e,t,r])=>{const s=qi(r).toVar(),i=ji(t).toVar(),n=en(e).toVar(),a=qi().toVar(),o=qi().toVar(),u=qi().toVar(),l=en(xT(n.x,a),xT(n.y,o),xT(n.z,u)).toVar(),d=Yi(1e6,1e6).toVar();return ch({start:-1,end:qi(1),name:"x",condition:"<="},({x:e})=>{ch({start:-1,end:qi(1),name:"y",condition:"<="},({y:t})=>{ch({start:-1,end:qi(1),name:"z",condition:"<="},({z:r})=>{const n=ji(qT(l,e,t,r,a,o,u,i,s)).toVar();Hi(n.lessThan(d.x),()=>{d.y.assign(d.x),d.x.assign(n)}).ElseIf(n.lessThan(d.y),()=>{d.y.assign(n)})})})}),Hi(s.equal(qi(0)),()=>{d.assign(ja(d))}),d}).setLayout({name:"mx_worley_noise_vec2_1",type:"vec2",inputs:[{name:"p",type:"vec3"},{name:"jitter",type:"float"},{name:"metric",type:"int"}]})]),JT=uy([YT,ki(([e,t,r])=>{const s=qi(r).toVar(),i=ji(t).toVar(),n=en(e).toVar(),a=qi().toVar(),o=qi().toVar(),u=qi().toVar(),l=en(xT(n.x,a),xT(n.y,o),xT(n.z,u)).toVar(),d=en(1e6,1e6,1e6).toVar();return ch({start:-1,end:qi(1),name:"x",condition:"<="},({x:e})=>{ch({start:-1,end:qi(1),name:"y",condition:"<="},({y:t})=>{ch({start:-1,end:qi(1),name:"z",condition:"<="},({z:r})=>{const n=ji(qT(l,e,t,r,a,o,u,i,s)).toVar();Hi(n.lessThan(d.x),()=>{d.z.assign(d.y),d.y.assign(d.x),d.x.assign(n)}).ElseIf(n.lessThan(d.y),()=>{d.z.assign(d.y),d.y.assign(n)}).ElseIf(n.lessThan(d.z),()=>{d.z.assign(n)})})})}),Hi(s.equal(qi(0)),()=>{d.assign(ja(d))}),d}).setLayout({name:"mx_worley_noise_vec3_1",type:"vec3",inputs:[{name:"p",type:"vec3"},{name:"jitter",type:"float"},{name:"metric",type:"int"}]})]),e_=ki(([e])=>{const t=e.y,r=e.z,s=en().toVar();return Hi(t.lessThan(1e-4),()=>{s.assign(en(r,r,r))}).Else(()=>{let i=e.x;i=i.sub(Xa(i)).mul(6).toVar();const n=qi(go(i)),a=i.sub(ji(n)),o=r.mul(t.oneMinus()),u=r.mul(t.mul(a).oneMinus()),l=r.mul(t.mul(a.oneMinus()).oneMinus());Hi(n.equal(qi(0)),()=>{s.assign(en(r,l,o))}).ElseIf(n.equal(qi(1)),()=>{s.assign(en(u,r,o))}).ElseIf(n.equal(qi(2)),()=>{s.assign(en(o,r,l))}).ElseIf(n.equal(qi(3)),()=>{s.assign(en(o,u,r))}).ElseIf(n.equal(qi(4)),()=>{s.assign(en(l,o,r))}).Else(()=>{s.assign(en(r,o,u))})}),s}).setLayout({name:"mx_hsvtorgb",type:"vec3",inputs:[{name:"hsv",type:"vec3"}]}),t_=ki(([e])=>{const t=en(e).toVar(),r=ji(t.x).toVar(),s=ji(t.y).toVar(),i=ji(t.z).toVar(),n=ji(xo(r,xo(s,i))).toVar(),a=ji(To(r,To(s,i))).toVar(),o=ji(a.sub(n)).toVar(),u=ji().toVar(),l=ji().toVar(),d=ji().toVar();return d.assign(a),Hi(a.greaterThan(0),()=>{l.assign(o.div(a))}).Else(()=>{l.assign(0)}),Hi(l.lessThanEqual(0),()=>{u.assign(0)}).Else(()=>{Hi(r.greaterThanEqual(a),()=>{u.assign(s.sub(i).div(o))}).ElseIf(s.greaterThanEqual(a),()=>{u.assign(oa(2,i.sub(r).div(o)))}).Else(()=>{u.assign(oa(4,r.sub(s).div(o)))}),u.mulAssign(1/6),Hi(u.lessThan(0),()=>{u.addAssign(1)})}),en(u,l,d)}).setLayout({name:"mx_rgbtohsv",type:"vec3",inputs:[{name:"c",type:"vec3"}]}),r_=ki(([e])=>{const t=en(e).toVar(),r=sn(ma(t,en(.04045))).toVar(),s=en(t.div(12.92)).toVar(),i=en(Ao(To(t.add(en(.055)),en(0)).div(1.055),en(2.4))).toVar();return Lo(s,i,r)}).setLayout({name:"mx_srgb_texture_to_lin_rec709",type:"vec3",inputs:[{name:"color",type:"vec3"}]}),s_=(e,t)=>{e=ji(e),t=ji(t);const r=Yi(t.dFdx(),t.dFdy()).length().mul(.7071067811865476);return Uo(e.sub(r),e.add(r),t)},i_=(e,t,r,s)=>Lo(e,t,r[s].clamp()),n_=(e,t,r,s,i)=>Lo(e,t,s_(r,s[i])),a_=ki(([e,t,r])=>{const s=Ya(e).toVar(),i=ua(ji(.5).mul(t.sub(r)),Ol).div(s).toVar(),n=ua(ji(-.5).mul(t.sub(r)),Ol).div(s).toVar(),a=en().toVar();a.x=s.x.greaterThan(ji(0)).select(i.x,n.x),a.y=s.y.greaterThan(ji(0)).select(i.y,n.y),a.z=s.z.greaterThan(ji(0)).select(i.z,n.z);const o=xo(a.x,a.y,a.z).toVar();return Ol.add(s.mul(o)).toVar().sub(r)}),o_=ki(([e,t])=>{const r=e.x,s=e.y,i=e.z;let n=t.element(0).mul(.886227);return n=n.add(t.element(1).mul(1.023328).mul(s)),n=n.add(t.element(2).mul(1.023328).mul(i)),n=n.add(t.element(3).mul(1.023328).mul(r)),n=n.add(t.element(4).mul(.858086).mul(r).mul(s)),n=n.add(t.element(5).mul(.858086).mul(s).mul(i)),n=n.add(t.element(6).mul(i.mul(i).mul(.743125).sub(.247708))),n=n.add(t.element(7).mul(.858086).mul(r).mul(i)),n=n.add(t.element(8).mul(.429043).mul(la(r,r).sub(la(s,s)))),n});var u_=Object.freeze({__proto__:null,BRDF_GGX:Qp,BRDF_Lambert:Ip,BasicPointShadowFilter:rT,BasicShadowFilter:Ux,Break:hh,Const:tu,Continue:()=>Iu("continue").toStack(),DFGApprox:Zp,D_GGX:Xp,Discard:Vu,EPSILON:La,F_Schlick:Dp,Fn:ki,INFINITY:Da,If:Hi,Loop:ch,NodeAccess:Os,NodeShaderStage:Is,NodeType:Us,NodeUpdateType:Vs,PCFShadowFilter:Ox,PCFSoftShadowFilter:kx,PI:Ia,PI2:Va,PointShadowFilter:sT,Return:()=>Iu("return").toStack(),Schlick_to_F0:eg,ScriptableNodeResources:$b,ShaderNode:Fi,Stack:$i,Switch:(...e)=>ni.Switch(...e),TBNViewMatrix:Xd,VSMShadowFilter:Gx,V_GGX_SmithCorrelated:jp,Var:eu,abs:io,acesFilmicToneMapping:Mb,acos:ro,add:oa,addMethodChaining:oi,addNodeElement:function(e){console.warn("THREE.TSL: AddNodeElement has been removed in favor of tree-shaking. Trying add",e)},agxToneMapping:Lb,all:Ua,alphaT:Rn,and:ba,anisotropy:Cn,anisotropyB:Pn,anisotropyT:Mn,any:Oa,append:e=>(console.warn("THREE.TSL: append() has been renamed to Stack()."),$i(e)),array:ea,arrayBuffer:e=>Li(new si(e,"ArrayBuffer")),asin:to,assign:ra,atan:so,atan2:$o,atomicAdd:(e,t)=>px(cx.ATOMIC_ADD,e,t),atomicAnd:(e,t)=>px(cx.ATOMIC_AND,e,t),atomicFunc:px,atomicLoad:e=>px(cx.ATOMIC_LOAD,e,null),atomicMax:(e,t)=>px(cx.ATOMIC_MAX,e,t),atomicMin:(e,t)=>px(cx.ATOMIC_MIN,e,t),atomicOr:(e,t)=>px(cx.ATOMIC_OR,e,t),atomicStore:(e,t)=>px(cx.ATOMIC_STORE,e,t),atomicSub:(e,t)=>px(cx.ATOMIC_SUB,e,t),atomicXor:(e,t)=>px(cx.ATOMIC_XOR,e,t),attenuationColor:Hn,attenuationDistance:zn,attribute:Hu,attributeArray:(e,t="float")=>{let r,s;!0===t.isStruct?(r=t.layout.getLength(),s=ws("float")):(r=As(t),s=ws(t));const i=new qy(e,r,s);return ah(i,t,e)},backgroundBlurriness:Jy,backgroundIntensity:eb,backgroundRotation:tb,batch:rh,bentNormalView:Yd,billboarding:gy,bitAnd:va,bitNot:Na,bitOr:Sa,bitXor:Ea,bitangentGeometry:$d,bitangentLocal:Wd,bitangentView:jd,bitangentWorld:qd,bitcast:yo,blendBurn:rp,blendColor:ap,blendDodge:sp,blendOverlay:np,blendScreen:ip,blur:em,bool:Ki,buffer:tl,bufferAttribute:vu,bumpMap:rc,burn:(...e)=>(console.warn('THREE.TSL: "burn" has been renamed. Use "blendBurn" instead.'),rp(e)),bvec2:Ji,bvec3:sn,bvec4:un,bypass:Pu,cache:Cu,call:ia,cameraFar:ul,cameraIndex:al,cameraNear:ol,cameraNormalMatrix:pl,cameraPosition:gl,cameraProjectionMatrix:ll,cameraProjectionMatrixInverse:dl,cameraViewMatrix:cl,cameraWorldMatrix:hl,cbrt:Bo,cdl:bb,ceil:Ka,checker:gT,cineonToneMapping:Rb,clamp:Do,clearcoat:_n,clearcoatNormalView:ed,clearcoatRoughness:vn,code:Vb,color:Wi,colorSpaceToWorking:pu,colorToDirection:e=>Li(e).mul(2).sub(1),compute:Au,computeSkinning:(e,t=null)=>{const r=new uh(e);return r.positionNode=ah(new F(e.geometry.getAttribute("position").array,3),"vec3").setPBO(!0).toReadOnly().element(jc).toVar(),r.skinIndexNode=ah(new F(new Uint32Array(e.geometry.getAttribute("skinIndex").array),4),"uvec4").setPBO(!0).toReadOnly().element(jc).toVar(),r.skinWeightNode=ah(new F(e.geometry.getAttribute("skinWeight").array,4),"vec4").setPBO(!0).toReadOnly().element(jc).toVar(),r.bindMatrixNode=Zn(e.bindMatrix,"mat4"),r.bindMatrixInverseNode=Zn(e.bindMatrixInverse,"mat4"),r.boneMatricesNode=tl(e.skeleton.boneMatrices,"mat4",e.skeleton.bones.length),r.toPositionNode=t,Li(r)},context:Yo,convert:pn,convertColorSpace:(e,t,r)=>Li(new cu(Li(e),t,r)),convertToTexture:(e,...t)=>e.isTextureNode?e:e.isPassNode?e.getTextureNode():Gy(e,...t),cos:Ja,cross:wo,cubeTexture:bd,cubeTextureBase:yd,cubeToUV:tT,dFdx:lo,dFdy:co,dashSize:In,debug:Gu,decrement:Pa,decrementBefore:Ca,defaultBuildStages:Gs,defaultShaderStages:ks,defined:Pi,degrees:Ga,deltaTime:dy,densityFog:function(e,t){return console.warn('THREE.TSL: "densityFog( color, density )" is deprecated. Use "fog( color, densityFogFactor( density ) )" instead.'),Yb(e,Kb(t))},densityFogFactor:Kb,depth:qh,depthPass:(e,t,r)=>Li(new Sb(Sb.DEPTH,e,t,r)),difference:So,diffuseColor:yn,directPointLight:hT,directionToColor:xp,directionToFaceDirection:jl,dispersion:$n,distance:No,div:da,dodge:(...e)=>(console.warn('THREE.TSL: "dodge" has been renamed. Use "blendDodge" instead.'),sp(e)),dot:Eo,drawIndex:Yc,dynamicBufferAttribute:Nu,element:hn,emissive:bn,equal:ha,equals:bo,equirectUV:vp,exp:za,exp2:Ha,expression:Iu,faceDirection:Wl,faceForward:Oo,faceforward:Wo,float:ji,floor:Xa,fog:Yb,fract:Qa,frameGroup:Xn,frameId:cy,frontFacing:$l,fwidth:mo,gain:(e,t)=>e.lessThan(.5)?ry(e.mul(2),t).div(2):ua(1,ry(la(ua(1,e),2),t).div(2)),gapSize:Vn,getConstNodeType:Bi,getCurrentStack:zi,getDirection:Yg,getDistanceAttenuation:cT,getGeometryRoughness:$p,getNormalFromDepth:$y,getParallaxCorrectNormal:a_,getRoughness:Wp,getScreenPosition:Hy,getShIrradianceAt:o_,getShadowMaterial:Hx,getShadowRenderObjectFunction:jx,getTextureIndex:Zf,getViewPosition:zy,globalId:nx,glsl:(e,t)=>Vb(e,t,"glsl"),glslFn:(e,t)=>Ob(e,t,"glsl"),grayscale:pb,greaterThan:ma,greaterThanEqual:ya,hash:ty,highpModelNormalViewMatrix:Dl,highpModelViewMatrix:Ll,hue:fb,increment:Ma,incrementBefore:Ra,instance:Zc,instanceIndex:jc,instancedArray:(e,t="float")=>{let r,s;!0===t.isStruct?(r=t.layout.getLength(),s=ws("float")):(r=As(t),s=ws(t));const i=new jy(e,r,s);return ah(i,t,e)},instancedBufferAttribute:Su,instancedDynamicBufferAttribute:Eu,instancedMesh:eh,int:qi,inverseSqrt:qa,inversesqrt:jo,invocationLocalIndex:Kc,invocationSubgroupIndex:Xc,ior:On,iridescence:En,iridescenceIOR:wn,iridescenceThickness:An,ivec2:Qi,ivec3:tn,ivec4:an,js:(e,t)=>Vb(e,t,"js"),label:Qo,length:ao,lengthSq:Fo,lessThan:ga,lessThanEqual:fa,lightPosition:bx,lightProjectionUV:yx,lightShadowMatrix:fx,lightTargetDirection:_x,lightTargetPosition:xx,lightViewPosition:Tx,lightingContext:_h,lights:(e=[])=>Li(new Ex).setLights(e),linearDepth:Xh,linearToneMapping:wb,localId:ax,log:$a,log2:Wa,logarithmicDepthToViewZ:(e,t,r)=>{const s=e.mul($a(r.div(t)));return ji(Math.E).pow(s).mul(t).negate()},luminance:yb,mat2:ln,mat3:dn,mat4:cn,matcapUV:Gm,materialAO:Gc,materialAlphaTest:nc,materialAnisotropy:Sc,materialAnisotropyVector:zc,materialAttenuationColor:Bc,materialAttenuationDistance:Pc,materialClearcoat:bc,materialClearcoatNormal:Tc,materialClearcoatRoughness:xc,materialColor:ac,materialDispersion:Oc,materialEmissive:uc,materialEnvIntensity:ld,materialEnvRotation:dd,materialIOR:Mc,materialIridescence:Ec,materialIridescenceIOR:wc,materialIridescenceThickness:Ac,materialLightMap:kc,materialLineDashOffset:Vc,materialLineDashSize:Lc,materialLineGapSize:Dc,materialLineScale:Fc,materialLineWidth:Ic,materialMetalness:fc,materialNormal:yc,materialOpacity:lc,materialPointSize:Uc,materialReference:Sd,materialReflectivity:gc,materialRefractionRatio:ud,materialRotation:_c,materialRoughness:mc,materialSheen:vc,materialSheenRoughness:Nc,materialShininess:oc,materialSpecular:dc,materialSpecularColor:hc,materialSpecularIntensity:cc,materialSpecularStrength:pc,materialThickness:Cc,materialTransmission:Rc,max:To,maxMipLevel:Xu,mediumpModelViewMatrix:Fl,metalness:Tn,min:xo,mix:Lo,mixElement:Go,mod:ca,modInt:Ba,modelDirection:Sl,modelNormalMatrix:Ml,modelPosition:wl,modelRadius:Cl,modelScale:Al,modelViewMatrix:Bl,modelViewPosition:Rl,modelViewProjection:Hc,modelWorldMatrix:El,modelWorldMatrixInverse:Pl,morphReference:yh,mrt:ey,mul:la,mx_aastep:s_,mx_cell_noise_float:(e=$u())=>GT(e.convert("vec2|vec3")),mx_contrast:(e,t=1,r=.5)=>ji(e).sub(r).mul(t).add(r),mx_fractal_noise_float:(e=$u(),t=3,r=2,s=.5,i=1)=>HT(e,qi(t),r,s).mul(i),mx_fractal_noise_vec2:(e=$u(),t=3,r=2,s=.5,i=1)=>WT(e,qi(t),r,s).mul(i),mx_fractal_noise_vec3:(e=$u(),t=3,r=2,s=.5,i=1)=>$T(e,qi(t),r,s).mul(i),mx_fractal_noise_vec4:(e=$u(),t=3,r=2,s=.5,i=1)=>jT(e,qi(t),r,s).mul(i),mx_hsvtorgb:e_,mx_noise_float:(e=$u(),t=1,r=0)=>OT(e.convert("vec2|vec3")).mul(t).add(r),mx_noise_vec3:(e=$u(),t=1,r=0)=>kT(e.convert("vec2|vec3")).mul(t).add(r),mx_noise_vec4:(e=$u(),t=1,r=0)=>{e=e.convert("vec2|vec3");return nn(kT(e),OT(e.add(Yi(19,73)))).mul(t).add(r)},mx_ramplr:(e,t,r=$u())=>i_(e,t,r,"x"),mx_ramptb:(e,t,r=$u())=>i_(e,t,r,"y"),mx_rgbtohsv:t_,mx_safepower:(e,t=1)=>(e=ji(e)).abs().pow(t).mul(e.sign()),mx_splitlr:(e,t,r,s=$u())=>n_(e,t,r,s,"x"),mx_splittb:(e,t,r,s=$u())=>n_(e,t,r,s,"y"),mx_srgb_texture_to_lin_rec709:r_,mx_transform_uv:(e=1,t=0,r=$u())=>r.mul(e).add(t),mx_worley_noise_float:(e=$u(),t=1)=>QT(e.convert("vec2|vec3"),t,qi(1)),mx_worley_noise_vec2:(e=$u(),t=1)=>ZT(e.convert("vec2|vec3"),t,qi(1)),mx_worley_noise_vec3:(e=$u(),t=1)=>JT(e.convert("vec2|vec3"),t,qi(1)),negate:oo,neutralToneMapping:Db,nodeArray:Ii,nodeImmutable:Ui,nodeObject:Li,nodeObjects:Di,nodeProxy:Vi,normalFlat:Kl,normalGeometry:ql,normalLocal:Xl,normalMap:Zd,normalView:Zl,normalViewGeometry:Yl,normalWorld:Jl,normalWorldGeometry:Ql,normalize:Ya,not:Ta,notEqual:pa,numWorkgroups:sx,objectDirection:yl,objectGroup:Yn,objectPosition:xl,objectRadius:vl,objectScale:Tl,objectViewPosition:_l,objectWorldMatrix:bl,oneMinus:uo,or:xa,orthographicDepthToViewZ:(e,t,r)=>t.sub(r).mul(e).sub(t),oscSawtooth:(e=ly)=>e.fract(),oscSine:(e=ly)=>e.add(.75).mul(2*Math.PI).sin().mul(.5).add(.5),oscSquare:(e=ly)=>e.fract().round(),oscTriangle:(e=ly)=>e.add(.5).fract().mul(2).sub(1).abs(),output:Dn,outputStruct:Qf,overlay:(...e)=>(console.warn('THREE.TSL: "overlay" has been renamed. Use "blendOverlay" instead.'),np(e)),overloadingFn:uy,parabola:ry,parallaxDirection:Kd,parallaxUV:(e,t)=>e.sub(Kd.mul(t)),parameter:(e,t)=>Li(new Wf(e,t)),pass:(e,t,r)=>Li(new Sb(Sb.COLOR,e,t,r)),passTexture:(e,t)=>Li(new vb(e,t)),pcurve:(e,t,r)=>Ao(da(Ao(e,t),oa(Ao(e,t),Ao(ua(1,e),r))),1/t),perspectiveDepthToViewZ:$h,pmremTexture:wm,pointShadow:lT,pointUV:Ky,pointWidth:Un,positionGeometry:Il,positionLocal:Vl,positionPrevious:Ul,positionView:Gl,positionViewDirection:zl,positionWorld:Ol,positionWorldDirection:kl,posterize:Tb,pow:Ao,pow2:Ro,pow3:Co,pow4:Mo,premultiplyAlpha:op,property:mn,radians:ka,rand:ko,range:ex,rangeFog:function(e,t,r){return console.warn('THREE.TSL: "rangeFog( color, near, far )" is deprecated. Use "fog( color, rangeFogFactor( near, far ) )" instead.'),Yb(e,Xb(t,r))},rangeFogFactor:Xb,reciprocal:po,reference:_d,referenceBuffer:vd,reflect:vo,reflectVector:pd,reflectView:cd,reflector:e=>Li(new Fy(e)),refract:Vo,refractVector:gd,refractView:hd,reinhardToneMapping:Ab,remap:Fu,remapClamp:Lu,renderGroup:Kn,renderOutput:Ou,rendererReference:yu,rotate:Wm,rotateUV:hy,roughness:xn,round:ho,rtt:Gy,sRGBTransferEOTF:uu,sRGBTransferOETF:lu,sample:e=>Li(new Wy(e)),sampler:e=>(!0===e.isNode?e:Zu(e)).convert("sampler"),samplerComparison:e=>(!0===e.isNode?e:Zu(e)).convert("samplerComparison"),saturate:Io,saturation:gb,screen:(...e)=>(console.warn('THREE.TSL: "screen" has been renamed. Use "blendScreen" instead.'),ip(e)),screenCoordinate:Rh,screenSize:Ah,screenUV:wh,scriptable:jb,scriptableValue:Gb,select:Xo,setCurrentStack:Gi,shaderStages:zs,shadow:Jx,shadowPositionWorld:Ax,shapeCircle:mT,sharedUniformGroup:qn,sheen:Nn,sheenRoughness:Sn,shiftLeft:wa,shiftRight:Aa,shininess:Ln,sign:no,sin:Za,sinc:(e,t)=>Za(Ia.mul(t.mul(e).sub(1))).div(Ia.mul(t.mul(e).sub(1))),skinning:lh,smoothstep:Uo,smoothstepElement:zo,specularColor:Bn,specularF90:Fn,spherizeUV:py,split:(e,t)=>Li(new Zs(Li(e),t)),spritesheetUV:yy,sqrt:ja,stack:qf,step:_o,stepElement:Ho,storage:ah,storageBarrier:()=>ux("storage").toStack(),storageObject:(e,t,r)=>(console.warn('THREE.TSL: "storageObject()" is deprecated. Use "storage().setPBO( true )" instead.'),ah(e,t,r).setPBO(!0)),storageTexture:sb,string:(e="")=>Li(new si(e,"string")),struct:(e,t=null)=>{const r=new Xf(e,t),s=(...t)=>{let s=null;if(t.length>0)if(t[0].isNode){s={};const r=Object.keys(e);for(let e=0;eux("texture").toStack(),textureBicubic:Tg,textureBicubicLevel:xg,textureCubeUV:Qg,textureLoad:Ju,textureSize:ju,textureStore:(e,t,r)=>{const s=sb(e,t,r);return null!==r&&s.toStack(),s},thickness:Gn,time:ly,timerDelta:(e=1)=>(console.warn('TSL: timerDelta() is deprecated. Use "deltaTime" instead.'),dy.mul(e)),timerGlobal:(e=1)=>(console.warn('TSL: timerGlobal() is deprecated. Use "time" instead.'),ly.mul(e)),timerLocal:(e=1)=>(console.warn('TSL: timerLocal() is deprecated. Use "time" instead.'),ly.mul(e)),toneMapping:xu,toneMappingExposure:Tu,toonOutlinePass:(t,r,s=new e(0,0,0),i=.003,n=1)=>Li(new Eb(t,r,Li(s),Li(i),Li(n))),transformDirection:Po,transformNormal:td,transformNormalToView:rd,transformedClearcoatNormalView:nd,transformedNormalView:sd,transformedNormalWorld:id,transmission:kn,transpose:fo,triNoise3D:ny,triplanarTexture:(...e)=>by(...e),triplanarTextures:by,trunc:go,uint:Xi,uniform:Zn,uniformArray:il,uniformCubeTexture:(e=md)=>yd(e),uniformGroup:jn,uniformTexture:(e=Ku)=>Zu(e),unpremultiplyAlpha:up,userData:(e,t,r)=>Li(new ob(e,t,r)),uv:$u,uvec2:Zi,uvec3:rn,uvec4:on,varying:au,varyingProperty:fn,vec2:Yi,vec3:en,vec4:nn,vectorComponents:Hs,velocity:hb,vertexColor:tp,vertexIndex:Wc,vertexStage:ou,vibrance:mb,viewZToLogarithmicDepth:Wh,viewZToOrthographicDepth:zh,viewZToPerspectiveDepth:Hh,viewport:Ch,viewportCoordinate:Ph,viewportDepthTexture:kh,viewportLinearDepth:Kh,viewportMipTexture:Vh,viewportResolution:Fh,viewportSafeUV:my,viewportSharedTexture:fp,viewportSize:Mh,viewportTexture:Ih,viewportUV:Bh,wgsl:(e,t)=>Vb(e,t,"wgsl"),wgslFn:(e,t)=>Ob(e,t,"wgsl"),workgroupArray:(e,t)=>Li(new dx("Workgroup",e,t)),workgroupBarrier:()=>ux("workgroup").toStack(),workgroupId:ix,workingToColorSpace:hu,xor:_a});const l_=new $f;class d_ extends cf{constructor(e,t){super(),this.renderer=e,this.nodes=t}update(e,t,r){const s=this.renderer,i=this.nodes.getBackgroundNode(e)||e.background;let n=!1;if(null===i)s._clearColor.getRGB(l_),l_.a=s._clearColor.a;else if(!0===i.isColor)i.getRGB(l_),l_.a=1,n=!0;else if(!0===i.isNode){const o=this.get(e),u=i;l_.copy(s._clearColor);let l=o.backgroundMesh;if(void 0===l){const c=Yo(nn(u).mul(eb),{getUV:()=>tb.mul(Ql),getTextureLevel:()=>Jy});let h=Hc;h=h.setZ(h.w);const p=new lp;function g(){i.removeEventListener("dispose",g),l.material.dispose(),l.geometry.dispose()}p.name="Background.material",p.side=S,p.depthTest=!1,p.depthWrite=!1,p.allowOverride=!1,p.fog=!1,p.lights=!1,p.vertexNode=h,p.colorNode=c,o.backgroundMeshNode=c,o.backgroundMesh=l=new X(new Oe(1,32,32),p),l.frustumCulled=!1,l.name="Background.mesh",l.onBeforeRender=function(e,t,r){this.matrixWorld.copyPosition(r.matrixWorld)},i.addEventListener("dispose",g)}const d=u.getCacheKey();o.backgroundCacheKey!==d&&(o.backgroundMeshNode.node=nn(u).mul(eb),o.backgroundMeshNode.needsUpdate=!0,l.material.needsUpdate=!0,o.backgroundCacheKey=d),t.unshift(l,l.geometry,l.material,0,0,null,null)}else console.error("THREE.Renderer: Unsupported background configuration.",i);const a=s.xr.getEnvironmentBlendMode();if("additive"===a?l_.set(0,0,0,1):"alpha-blend"===a&&l_.set(0,0,0,0),!0===s.autoClear||!0===n){const m=r.clearColorValue;m.r=l_.r,m.g=l_.g,m.b=l_.b,m.a=l_.a,!0!==s.backend.isWebGLBackend&&!0!==s.alpha||(m.r*=m.a,m.g*=m.a,m.b*=m.a),r.depthClearValue=s._clearDepth,r.stencilClearValue=s._clearStencil,r.clearColor=!0===s.autoClearColor,r.clearDepth=!0===s.autoClearDepth,r.clearStencil=!0===s.autoClearStencil}else r.clearColor=!1,r.clearDepth=!1,r.clearStencil=!1}}let c_=0;class h_{constructor(e="",t=[],r=0,s=[]){this.name=e,this.bindings=t,this.index=r,this.bindingsReference=s,this.id=c_++}}class p_{constructor(e,t,r,s,i,n,a,o,u,l=[]){this.vertexShader=e,this.fragmentShader=t,this.computeShader=r,this.transforms=l,this.nodeAttributes=s,this.bindings=i,this.updateNodes=n,this.updateBeforeNodes=a,this.updateAfterNodes=o,this.observer=u,this.usedTimes=0}createBindings(){const e=[];for(const t of this.bindings){if(!0!==t.bindings[0].groupNode.shared){const r=new h_(t.name,[],t.index,t);e.push(r);for(const e of t.bindings)r.bindings.push(e.clone())}else e.push(t)}return e}}class g_{constructor(e,t,r=null){this.isNodeAttribute=!0,this.name=e,this.type=t,this.node=r}}class m_{constructor(e,t,r){this.isNodeUniform=!0,this.name=e,this.type=t,this.node=r.getSelf()}get value(){return this.node.value}set value(e){this.node.value=e}get id(){return this.node.id}get groupNode(){return this.node.groupNode}}class f_{constructor(e,t,r=!1,s=null){this.isNodeVar=!0,this.name=e,this.type=t,this.readOnly=r,this.count=s}}class y_ extends f_{constructor(e,t,r=null,s=null){super(e,t),this.needsInterpolation=!1,this.isNodeVarying=!0,this.interpolationType=r,this.interpolationSampling=s}}class b_{constructor(e,t,r=""){this.name=e,this.type=t,this.code=r,Object.defineProperty(this,"isNodeCode",{value:!0})}}let x_=0;class T_{constructor(e=null){this.id=x_++,this.nodesData=new WeakMap,this.parent=e}getData(e){let t=this.nodesData.get(e);return void 0===t&&null!==this.parent&&(t=this.parent.getData(e)),t}setData(e,t){this.nodesData.set(e,t)}}class __{constructor(e,t){this.name=e,this.members=t,this.output=!1}}class v_{constructor(e,t){this.name=e,this.value=t,this.boundary=0,this.itemSize=0,this.offset=0}setValue(e){this.value=e}getValue(){return this.value}}class N_ extends v_{constructor(e,t=0){super(e,t),this.isNumberUniform=!0,this.boundary=4,this.itemSize=1}}class S_ extends v_{constructor(e,r=new t){super(e,r),this.isVector2Uniform=!0,this.boundary=8,this.itemSize=2}}class E_ extends v_{constructor(e,t=new r){super(e,t),this.isVector3Uniform=!0,this.boundary=16,this.itemSize=3}}class w_ extends v_{constructor(e,t=new s){super(e,t),this.isVector4Uniform=!0,this.boundary=16,this.itemSize=4}}class A_ extends v_{constructor(t,r=new e){super(t,r),this.isColorUniform=!0,this.boundary=16,this.itemSize=3}}class R_ extends v_{constructor(e,t=new i){super(e,t),this.isMatrix2Uniform=!0,this.boundary=8,this.itemSize=4}}class C_ extends v_{constructor(e,t=new n){super(e,t),this.isMatrix3Uniform=!0,this.boundary=48,this.itemSize=12}}class M_ extends v_{constructor(e,t=new a){super(e,t),this.isMatrix4Uniform=!0,this.boundary=64,this.itemSize=16}}class P_ extends N_{constructor(e){super(e.name,e.value),this.nodeUniform=e}getValue(){return this.nodeUniform.value}getType(){return this.nodeUniform.type}}class B_ extends S_{constructor(e){super(e.name,e.value),this.nodeUniform=e}getValue(){return this.nodeUniform.value}getType(){return this.nodeUniform.type}}class F_ extends E_{constructor(e){super(e.name,e.value),this.nodeUniform=e}getValue(){return this.nodeUniform.value}getType(){return this.nodeUniform.type}}class L_ extends w_{constructor(e){super(e.name,e.value),this.nodeUniform=e}getValue(){return this.nodeUniform.value}getType(){return this.nodeUniform.type}}class D_ extends A_{constructor(e){super(e.name,e.value),this.nodeUniform=e}getValue(){return this.nodeUniform.value}getType(){return this.nodeUniform.type}}class I_ extends R_{constructor(e){super(e.name,e.value),this.nodeUniform=e}getValue(){return this.nodeUniform.value}getType(){return this.nodeUniform.type}}class V_ extends C_{constructor(e){super(e.name,e.value),this.nodeUniform=e}getValue(){return this.nodeUniform.value}getType(){return this.nodeUniform.type}}class U_ extends M_{constructor(e){super(e.name,e.value),this.nodeUniform=e}getValue(){return this.nodeUniform.value}getType(){return this.nodeUniform.type}}const O_=new WeakMap,k_=new Map([[Int8Array,"int"],[Int16Array,"int"],[Int32Array,"int"],[Uint8Array,"uint"],[Uint16Array,"uint"],[Uint32Array,"uint"],[Float32Array,"float"]]),G_=e=>/e/g.test(e)?String(e).replace(/\+/g,""):(e=Number(e))+(e%1?"":".0");class z_{constructor(e,t,r){this.object=e,this.material=e&&e.material||null,this.geometry=e&&e.geometry||null,this.renderer=t,this.parser=r,this.scene=null,this.camera=null,this.nodes=[],this.sequentialNodes=[],this.updateNodes=[],this.updateBeforeNodes=[],this.updateAfterNodes=[],this.hashNodes={},this.observer=null,this.lightsNode=null,this.environmentNode=null,this.fogNode=null,this.clippingContext=null,this.vertexShader=null,this.fragmentShader=null,this.computeShader=null,this.flowNodes={vertex:[],fragment:[],compute:[]},this.flowCode={vertex:"",fragment:"",compute:""},this.uniforms={vertex:[],fragment:[],compute:[],index:0},this.structs={vertex:[],fragment:[],compute:[],index:0},this.bindings={vertex:{},fragment:{},compute:{}},this.bindingsIndexes={},this.bindGroups=null,this.attributes=[],this.bufferAttributes=[],this.varyings=[],this.codes={},this.vars={},this.declarations={},this.flow={code:""},this.chaining=[],this.stack=qf(),this.stacks=[],this.tab="\t",this.currentFunctionNode=null,this.context={material:this.material},this.cache=new T_,this.globalCache=this.cache,this.flowsData=new WeakMap,this.shaderStage=null,this.buildStage=null,this.subBuildLayers=[],this.currentStack=null,this.subBuildFn=null}getBindGroupsCache(){let e=O_.get(this.renderer);return void 0===e&&(e=new af,O_.set(this.renderer,e)),e}createRenderTarget(e,t,r){return new ue(e,t,r)}createCubeRenderTarget(e,t){return new Np(e,t)}includes(e){return this.nodes.includes(e)}getOutputStructName(){}_getBindGroup(e,t){const r=this.getBindGroupsCache(),s=[];let i,n=!0;for(const e of t)s.push(e),n=n&&!0!==e.groupNode.shared;return n?(i=r.get(s),void 0===i&&(i=new h_(e,s,this.bindingsIndexes[e].group,s),r.set(s,i))):i=new h_(e,s,this.bindingsIndexes[e].group,s),i}getBindGroupArray(e,t){const r=this.bindings[t];let s=r[e];return void 0===s&&(void 0===this.bindingsIndexes[e]&&(this.bindingsIndexes[e]={binding:0,group:Object.keys(this.bindingsIndexes).length}),r[e]=s=[]),s}getBindings(){let e=this.bindGroups;if(null===e){const t={},r=this.bindings;for(const e of zs)for(const s in r[e]){const i=r[e][s];(t[s]||(t[s]=[])).push(...i)}e=[];for(const r in t){const s=t[r],i=this._getBindGroup(r,s);e.push(i)}this.bindGroups=e}return e}sortBindingGroups(){const e=this.getBindings();e.sort((e,t)=>e.bindings[0].groupNode.order-t.bindings[0].groupNode.order);for(let t=0;t=0?`${Math.round(n)}u`:"0u";if("bool"===i)return n?"true":"false";if("color"===i)return`${this.getType("vec3")}( ${G_(n.r)}, ${G_(n.g)}, ${G_(n.b)} )`;const a=this.getTypeLength(i),o=this.getComponentType(i),u=e=>this.generateConst(o,e);if(2===a)return`${this.getType(i)}( ${u(n.x)}, ${u(n.y)} )`;if(3===a)return`${this.getType(i)}( ${u(n.x)}, ${u(n.y)}, ${u(n.z)} )`;if(4===a&&"mat2"!==i)return`${this.getType(i)}( ${u(n.x)}, ${u(n.y)}, ${u(n.z)}, ${u(n.w)} )`;if(a>=4&&n&&(n.isMatrix2||n.isMatrix3||n.isMatrix4))return`${this.getType(i)}( ${n.elements.map(u).join(", ")} )`;if(a>4)return`${this.getType(i)}()`;throw new Error(`NodeBuilder: Type '${i}' not found in generate constant attempt.`)}getType(e){return"color"===e?"vec3":e}hasGeometryAttribute(e){return this.geometry&&void 0!==this.geometry.getAttribute(e)}getAttribute(e,t){const r=this.attributes;for(const t of r)if(t.name===e)return t;const s=new g_(e,t);return this.registerDeclaration(s),r.push(s),s}getPropertyName(e){return e.name}isVector(e){return/vec\d/.test(e)}isMatrix(e){return/mat\d/.test(e)}isReference(e){return"void"===e||"property"===e||"sampler"===e||"samplerComparison"===e||"texture"===e||"cubeTexture"===e||"storageTexture"===e||"depthTexture"===e||"texture3D"===e}needsToWorkingColorSpace(){return!1}getComponentTypeFromTexture(e){const t=e.type;if(e.isDataTexture){if(t===_)return"int";if(t===T)return"uint"}return"float"}getElementType(e){return"mat2"===e?"vec2":"mat3"===e?"vec3":"mat4"===e?"vec4":this.getComponentType(e)}getComponentType(e){if("float"===(e=this.getVectorType(e))||"bool"===e||"int"===e||"uint"===e)return e;const t=/(b|i|u|)(vec|mat)([2-4])/.exec(e);return null===t?null:"b"===t[1]?"bool":"i"===t[1]?"int":"u"===t[1]?"uint":"float"}getVectorType(e){return"color"===e?"vec3":"texture"===e||"cubeTexture"===e||"storageTexture"===e||"texture3D"===e?"vec4":e}getTypeFromLength(e,t="float"){if(1===e)return t;let r=Es(e);const s="float"===t?"":t[0];return!0===/mat2/.test(t)&&(r=r.replace("vec","mat")),s+r}getTypeFromArray(e){return k_.get(e.constructor)}isInteger(e){return/int|uint|(i|u)vec/.test(e)}getTypeFromAttribute(e){let t=e;e.isInterleavedBufferAttribute&&(t=e.data);const r=t.array,s=e.itemSize,i=e.normalized;let n;return e instanceof ze||!0===i||(n=this.getTypeFromArray(r)),this.getTypeFromLength(s,n)}getTypeLength(e){const t=this.getVectorType(e),r=/vec([2-4])/.exec(t);return null!==r?Number(r[1]):"float"===t||"bool"===t||"int"===t||"uint"===t?1:!0===/mat2/.test(e)?4:!0===/mat3/.test(e)?9:!0===/mat4/.test(e)?16:0}getVectorFromMatrix(e){return e.replace("mat","vec")}changeComponentType(e,t){return this.getTypeFromLength(this.getTypeLength(e),t)}getIntegerType(e){const t=this.getComponentType(e);return"int"===t||"uint"===t?e:this.changeComponentType(e,"int")}addStack(){return this.stack=qf(this.stack),this.stacks.push(zi()||this.stack),Gi(this.stack),this.stack}removeStack(){const e=this.stack;return this.stack=e.parent,Gi(this.stacks.pop()),e}getDataFromNode(e,t=this.shaderStage,r=null){let s=(r=null===r?e.isGlobal(this)?this.globalCache:this.cache:r).getData(e);void 0===s&&(s={},r.setData(e,s)),void 0===s[t]&&(s[t]={});let i=s[t];const n=s.any?s.any.subBuilds:null,a=this.getClosestSubBuild(n);return a&&(void 0===i.subBuildsCache&&(i.subBuildsCache={}),i=i.subBuildsCache[a]||(i.subBuildsCache[a]={}),i.subBuilds=n),i}getNodeProperties(e,t="any"){const r=this.getDataFromNode(e,t);return r.properties||(r.properties={outputNode:null})}getBufferAttributeFromNode(e,t){const r=this.getDataFromNode(e);let s=r.bufferAttribute;if(void 0===s){const i=this.uniforms.index++;s=new g_("nodeAttribute"+i,t,e),this.bufferAttributes.push(s),r.bufferAttribute=s}return s}getStructTypeFromNode(e,t,r=null,s=this.shaderStage){const i=this.getDataFromNode(e,s,this.globalCache);let n=i.structType;if(void 0===n){const e=this.structs.index++;null===r&&(r="StructType"+e),n=new __(r,t),this.structs[s].push(n),i.structType=n}return n}getOutputStructTypeFromNode(e,t){const r=this.getStructTypeFromNode(e,t,"OutputType","fragment");return r.output=!0,r}getUniformFromNode(e,t,r=this.shaderStage,s=null){const i=this.getDataFromNode(e,r,this.globalCache);let n=i.uniform;if(void 0===n){const a=this.uniforms.index++;n=new m_(s||"nodeUniform"+a,t,e),this.uniforms[r].push(n),this.registerDeclaration(n),i.uniform=n}return n}getArrayCount(e){let t=null;return e.isArrayNode?t=e.count:e.isVarNode&&e.node.isArrayNode&&(t=e.node.count),t}getVarFromNode(e,t=null,r=e.getNodeType(this),s=this.shaderStage,i=!1){const n=this.getDataFromNode(e,s),a=this.getSubBuildProperty("variable",n.subBuilds);let o=n[a];if(void 0===o){const u=i?"_const":"_var",l=this.vars[s]||(this.vars[s]=[]),d=this.vars[u]||(this.vars[u]=0);null===t&&(t=(i?"nodeConst":"nodeVar")+d,this.vars[u]++),"variable"!==a&&(t=this.getSubBuildProperty(t,n.subBuilds));const c=this.getArrayCount(e);o=new f_(t,r,i,c),i||l.push(o),this.registerDeclaration(o),n[a]=o}return o}isDeterministic(e){if(e.isMathNode)return this.isDeterministic(e.aNode)&&(!e.bNode||this.isDeterministic(e.bNode))&&(!e.cNode||this.isDeterministic(e.cNode));if(e.isOperatorNode)return this.isDeterministic(e.aNode)&&(!e.bNode||this.isDeterministic(e.bNode));if(e.isArrayNode){if(null!==e.values)for(const t of e.values)if(!this.isDeterministic(t))return!1;return!0}return!!e.isConstNode}getVaryingFromNode(e,t=null,r=e.getNodeType(this),s=null,i=null){const n=this.getDataFromNode(e,"any"),a=this.getSubBuildProperty("varying",n.subBuilds);let o=n[a];if(void 0===o){const e=this.varyings,u=e.length;null===t&&(t="nodeVarying"+u),"varying"!==a&&(t=this.getSubBuildProperty(t,n.subBuilds)),o=new y_(t,r,s,i),e.push(o),this.registerDeclaration(o),n[a]=o}return o}registerDeclaration(e){const t=this.shaderStage,r=this.declarations[t]||(this.declarations[t]={}),s=this.getPropertyName(e);let i=1,n=s;for(;void 0!==r[n];)n=s+"_"+i++;i>1&&(e.name=n,console.warn(`THREE.TSL: Declaration name '${s}' of '${e.type}' already in use. Renamed to '${n}'.`)),r[n]=e}getCodeFromNode(e,t,r=this.shaderStage){const s=this.getDataFromNode(e);let i=s.code;if(void 0===i){const e=this.codes[r]||(this.codes[r]=[]),n=e.length;i=new b_("nodeCode"+n,t),e.push(i),s.code=i}return i}addFlowCodeHierarchy(e,t){const{flowCodes:r,flowCodeBlock:s}=this.getDataFromNode(e);let i=!0,n=t;for(;n;){if(!0===s.get(n)){i=!1;break}n=this.getDataFromNode(n).parentNodeBlock}if(i)for(const e of r)this.addLineFlowCode(e)}addLineFlowCodeBlock(e,t,r){const s=this.getDataFromNode(e),i=s.flowCodes||(s.flowCodes=[]),n=s.flowCodeBlock||(s.flowCodeBlock=new WeakMap);i.push(t),n.set(r,!0)}addLineFlowCode(e,t=null){return""===e||(null!==t&&this.context.nodeBlock&&this.addLineFlowCodeBlock(t,e,this.context.nodeBlock),e=this.tab+e,/;\s*$/.test(e)||(e+=";\n"),this.flow.code+=e),this}addFlowCode(e){return this.flow.code+=e,this}addFlowTab(){return this.tab+="\t",this}removeFlowTab(){return this.tab=this.tab.slice(0,-1),this}getFlowData(e){return this.flowsData.get(e)}flowNode(e){const t=e.getNodeType(this),r=this.flowChildNode(e,t);return this.flowsData.set(e,r),r}addInclude(e){null!==this.currentFunctionNode&&this.currentFunctionNode.includes.push(e)}buildFunctionNode(e){const t=new Ub,r=this.currentFunctionNode;return this.currentFunctionNode=t,t.code=this.buildFunctionCode(e),this.currentFunctionNode=r,t}flowShaderNode(e){const t=e.layout,r={[Symbol.iterator](){let e=0;const t=Object.values(this);return{next:()=>({value:t[e],done:e++>=t.length})}}};for(const e of t.inputs)r[e.name]=new Wf(e.type,e.name);e.layout=null;const s=e.call(r),i=this.flowStagesNode(s,t.type);return e.layout=t,i}flowStagesNode(e,t=null){const r=this.flow,s=this.vars,i=this.declarations,n=this.cache,a=this.buildStage,o=this.stack,u={code:""};this.flow=u,this.vars={},this.declarations={},this.cache=new T_,this.stack=qf();for(const r of Gs)this.setBuildStage(r),u.result=e.build(this,t);return u.vars=this.getVars(this.shaderStage),this.flow=r,this.vars=s,this.declarations=i,this.cache=n,this.stack=o,this.setBuildStage(a),u}getFunctionOperator(){return null}buildFunctionCode(){console.warn("Abstract function.")}flowChildNode(e,t=null){const r=this.flow,s={code:""};return this.flow=s,s.result=e.build(this,t),this.flow=r,s}flowNodeFromShaderStage(e,t,r=null,s=null){const i=this.tab,n=this.cache,a=this.shaderStage,o=this.context;this.setShaderStage(e);const u={...this.context};delete u.nodeBlock,this.cache=this.globalCache,this.tab="\t",this.context=u;let l=null;if("generate"===this.buildStage){const i=this.flowChildNode(t,r);null!==s&&(i.code+=`${this.tab+s} = ${i.result};\n`),this.flowCode[e]=this.flowCode[e]+i.code,l=i}else l=t.build(this);return this.setShaderStage(a),this.cache=n,this.tab=i,this.context=o,l}getAttributesArray(){return this.attributes.concat(this.bufferAttributes)}getAttributes(){console.warn("Abstract function.")}getVaryings(){console.warn("Abstract function.")}getVar(e,t,r=null){return`${null!==r?this.generateArrayDeclaration(e,r):this.getType(e)} ${t}`}getVars(e){let t="";const r=this.vars[e];if(void 0!==r)for(const e of r)t+=`${this.getVar(e.type,e.name)}; `;return t}getUniforms(){console.warn("Abstract function.")}getCodes(e){const t=this.codes[e];let r="";if(void 0!==t)for(const e of t)r+=e.code+"\n";return r}getHash(){return this.vertexShader+this.fragmentShader+this.computeShader}setShaderStage(e){this.shaderStage=e}getShaderStage(){return this.shaderStage}setBuildStage(e){this.buildStage=e}getBuildStage(){return this.buildStage}buildCode(){console.warn("Abstract function.")}get subBuild(){return this.subBuildLayers[this.subBuildLayers.length-1]||null}addSubBuild(e){this.subBuildLayers.push(e)}removeSubBuild(){return this.subBuildLayers.pop()}getClosestSubBuild(e){let t;if(t=e&&e.isNode?e.isShaderCallNodeInternal?e.shaderNode.subBuilds:e.isStackNode?[e.subBuild]:this.getDataFromNode(e,"any").subBuilds:e instanceof Set?[...e]:e,!t)return null;const r=this.subBuildLayers;for(let e=t.length-1;e>=0;e--){const s=t[e];if(r.includes(s))return s}return null}getSubBuildOutput(e){return this.getSubBuildProperty("outputNode",e)}getSubBuildProperty(e="",t=null){let r,s;return r=null!==t?this.getClosestSubBuild(t):this.subBuildFn,s=r?e?r+"_"+e:r:e,s}build(){const{object:e,material:t,renderer:r}=this;if(null!==t){let e=r.library.fromMaterial(t);null===e&&(console.error(`NodeMaterial: Material "${t.type}" is not compatible.`),e=new lp),e.build(this)}else this.addFlow("compute",e);for(const e of Gs){this.setBuildStage(e),this.context.vertex&&this.context.vertex.isNode&&this.flowNodeFromShaderStage("vertex",this.context.vertex);for(const t of zs){this.setShaderStage(t);const r=this.flowNodes[t];for(const t of r)"generate"===e?this.flowNode(t):t.build(this)}}return this.setBuildStage(null),this.setShaderStage(null),this.buildCode(),this.buildUpdateNodes(),this}getNodeUniform(e,t){if("float"===t||"int"===t||"uint"===t)return new P_(e);if("vec2"===t||"ivec2"===t||"uvec2"===t)return new B_(e);if("vec3"===t||"ivec3"===t||"uvec3"===t)return new F_(e);if("vec4"===t||"ivec4"===t||"uvec4"===t)return new L_(e);if("color"===t)return new D_(e);if("mat2"===t)return new I_(e);if("mat3"===t)return new V_(e);if("mat4"===t)return new U_(e);throw new Error(`Uniform "${t}" not declared.`)}format(e,t,r){if((t=this.getVectorType(t))===(r=this.getVectorType(r))||null===r||this.isReference(r))return e;const s=this.getTypeLength(t),i=this.getTypeLength(r);return 16===s&&9===i?`${this.getType(r)}( ${e}[ 0 ].xyz, ${e}[ 1 ].xyz, ${e}[ 2 ].xyz )`:9===s&&4===i?`${this.getType(r)}( ${e}[ 0 ].xy, ${e}[ 1 ].xy )`:s>4||i>4||0===i?e:s===i?`${this.getType(r)}( ${e} )`:s>i?(e="bool"===r?`all( ${e} )`:`${e}.${"xyz".slice(0,i)}`,this.format(e,this.getTypeFromLength(i,this.getComponentType(t)),r)):4===i&&s>1?`${this.getType(r)}( ${this.format(e,t,"vec3")}, 1.0 )`:2===s?`${this.getType(r)}( ${this.format(e,t,"vec2")}, 0.0 )`:(1===s&&i>1&&t!==this.getComponentType(r)&&(e=`${this.getType(this.getComponentType(r))}( ${e} )`),`${this.getType(r)}( ${e} )`)}getSignature(){return`// Three.js r${He} - Node System\n`}*[Symbol.iterator](){}}class H_{constructor(){this.time=0,this.deltaTime=0,this.frameId=0,this.renderId=0,this.updateMap=new WeakMap,this.updateBeforeMap=new WeakMap,this.updateAfterMap=new WeakMap,this.renderer=null,this.material=null,this.camera=null,this.object=null,this.scene=null}_getMaps(e,t){let r=e.get(t);return void 0===r&&(r={renderMap:new WeakMap,frameMap:new WeakMap},e.set(t,r)),r}updateBeforeNode(e){const t=e.getUpdateBeforeType(),r=e.updateReference(this);if(t===Vs.FRAME){const{frameMap:t}=this._getMaps(this.updateBeforeMap,r);t.get(r)!==this.frameId&&!1!==e.updateBefore(this)&&t.set(r,this.frameId)}else if(t===Vs.RENDER){const{renderMap:t}=this._getMaps(this.updateBeforeMap,r);t.get(r)!==this.renderId&&!1!==e.updateBefore(this)&&t.set(r,this.renderId)}else t===Vs.OBJECT&&e.updateBefore(this)}updateAfterNode(e){const t=e.getUpdateAfterType(),r=e.updateReference(this);if(t===Vs.FRAME){const{frameMap:t}=this._getMaps(this.updateAfterMap,r);t.get(r)!==this.frameId&&!1!==e.updateAfter(this)&&t.set(r,this.frameId)}else if(t===Vs.RENDER){const{renderMap:t}=this._getMaps(this.updateAfterMap,r);t.get(r)!==this.renderId&&!1!==e.updateAfter(this)&&t.set(r,this.renderId)}else t===Vs.OBJECT&&e.updateAfter(this)}updateNode(e){const t=e.getUpdateType(),r=e.updateReference(this);if(t===Vs.FRAME){const{frameMap:t}=this._getMaps(this.updateMap,r);t.get(r)!==this.frameId&&!1!==e.update(this)&&t.set(r,this.frameId)}else if(t===Vs.RENDER){const{renderMap:t}=this._getMaps(this.updateMap,r);t.get(r)!==this.renderId&&!1!==e.update(this)&&t.set(r,this.renderId)}else t===Vs.OBJECT&&e.update(this)}update(){this.frameId++,void 0===this.lastTime&&(this.lastTime=performance.now()),this.deltaTime=(performance.now()-this.lastTime)/1e3,this.lastTime=performance.now(),this.time+=this.deltaTime}}class $_{constructor(e,t,r=null,s="",i=!1){this.type=e,this.name=t,this.count=r,this.qualifier=s,this.isConst=i}}$_.isNodeFunctionInput=!0;class W_ extends dT{static get type(){return"DirectionalLightNode"}constructor(e=null){super(e)}setupDirect(){const e=this.colorNode;return{lightDirection:_x(this.light),lightColor:e}}}const j_=new a,q_=new a;let X_=null;class K_ extends dT{static get type(){return"RectAreaLightNode"}constructor(e=null){super(e),this.halfHeight=Zn(new r).setGroup(Kn),this.halfWidth=Zn(new r).setGroup(Kn),this.updateType=Vs.RENDER}update(e){super.update(e);const{light:t}=this,r=e.camera.matrixWorldInverse;q_.identity(),j_.copy(t.matrixWorld),j_.premultiply(r),q_.extractRotation(j_),this.halfWidth.value.set(.5*t.width,0,0),this.halfHeight.value.set(0,.5*t.height,0),this.halfWidth.value.applyMatrix4(q_),this.halfHeight.value.applyMatrix4(q_)}setupDirectRectArea(e){let t,r;e.isAvailable("float32Filterable")?(t=Zu(X_.LTC_FLOAT_1),r=Zu(X_.LTC_FLOAT_2)):(t=Zu(X_.LTC_HALF_1),r=Zu(X_.LTC_HALF_2));const{colorNode:s,light:i}=this;return{lightColor:s,lightPosition:Tx(i),halfWidth:this.halfWidth,halfHeight:this.halfHeight,ltc_1:t,ltc_2:r}}static setLTC(e){X_=e}}class Y_ extends dT{static get type(){return"SpotLightNode"}constructor(e=null){super(e),this.coneCosNode=Zn(0).setGroup(Kn),this.penumbraCosNode=Zn(0).setGroup(Kn),this.cutoffDistanceNode=Zn(0).setGroup(Kn),this.decayExponentNode=Zn(0).setGroup(Kn),this.colorNode=Zn(this.color).setGroup(Kn)}update(e){super.update(e);const{light:t}=this;this.coneCosNode.value=Math.cos(t.angle),this.penumbraCosNode.value=Math.cos(t.angle*(1-t.penumbra)),this.cutoffDistanceNode.value=t.distance,this.decayExponentNode.value=t.decay}getSpotAttenuation(e,t){const{coneCosNode:r,penumbraCosNode:s}=this;return Uo(r,s,t)}getLightCoord(e){const t=e.getNodeProperties(this);let r=t.projectionUV;return void 0===r&&(r=yx(this.light,e.context.positionWorld),t.projectionUV=r),r}setupDirect(e){const{colorNode:t,cutoffDistanceNode:r,decayExponentNode:s,light:i}=this,n=this.getLightVector(e),a=n.normalize(),o=a.dot(_x(i)),u=this.getSpotAttenuation(e,o),l=n.length(),d=cT({lightDistance:l,cutoffDistance:r,decayExponent:s});let c,h,p=t.mul(u).mul(d);if(i.colorNode?(h=this.getLightCoord(e),c=i.colorNode(h)):i.map&&(h=this.getLightCoord(e),c=Zu(i.map,h.xy).onRenderUpdate(()=>i.map)),c){p=h.mul(2).sub(1).abs().lessThan(1).all().select(p.mul(c),p)}return{lightColor:p,lightDirection:a}}}class Q_ extends Y_{static get type(){return"IESSpotLightNode"}getSpotAttenuation(e,t){const r=this.light.iesMap;let s=null;if(r&&!0===r.isTexture){const e=t.acos().mul(1/Math.PI);s=Zu(r,Yi(e,0),0).r}else s=super.getSpotAttenuation(t);return s}}const Z_=ki(([e,t])=>{const r=e.abs().sub(t);return ao(To(r,0)).add(xo(To(r.x,r.y),0))});class J_ extends Y_{static get type(){return"ProjectorLightNode"}update(e){super.update(e);const t=this.light;if(this.penumbraCosNode.value=Math.min(Math.cos(t.angle*(1-t.penumbra)),.99999),null===t.aspect){let e=1;null!==t.map&&(e=t.map.width/t.map.height),t.shadow.aspect=e}else t.shadow.aspect=t.aspect}getSpotAttenuation(e){const t=this.penumbraCosNode,r=this.getLightCoord(e),s=r.xyz.div(r.w),i=Z_(s.xy.sub(Yi(.5)),Yi(.5)),n=da(-1,ua(1,ro(t)).sub(1));return Io(i.mul(-2).mul(n))}}class ev extends dT{static get type(){return"AmbientLightNode"}constructor(e=null){super(e)}setup({context:e}){e.irradiance.addAssign(this.colorNode)}}class tv extends dT{static get type(){return"HemisphereLightNode"}constructor(t=null){super(t),this.lightPositionNode=bx(t),this.lightDirectionNode=this.lightPositionNode.normalize(),this.groundColorNode=Zn(new e).setGroup(Kn)}update(e){const{light:t}=this;super.update(e),this.lightPositionNode.object3d=t,this.groundColorNode.value.copy(t.groundColor).multiplyScalar(t.intensity)}setup(e){const{colorNode:t,groundColorNode:r,lightDirectionNode:s}=this,i=Jl.dot(s).mul(.5).add(.5),n=Lo(r,t,i);e.context.irradiance.addAssign(n)}}class rv extends dT{static get type(){return"LightProbeNode"}constructor(e=null){super(e);const t=[];for(let e=0;e<9;e++)t.push(new r);this.lightProbe=il(t)}update(e){const{light:t}=this;super.update(e);for(let e=0;e<9;e++)this.lightProbe.array[e].copy(t.sh.coefficients[e]).multiplyScalar(t.intensity)}setup(e){const t=o_(Jl,this.lightProbe);e.context.irradiance.addAssign(t)}}class sv{parseFunction(){console.warn("Abstract function.")}}class iv{constructor(e,t,r="",s=""){this.type=e,this.inputs=t,this.name=r,this.precision=s}getCode(){console.warn("Abstract function.")}}iv.isNodeFunction=!0;const nv=/^\s*(highp|mediump|lowp)?\s*([a-z_0-9]+)\s*([a-z_0-9]+)?\s*\(([\s\S]*?)\)/i,av=/[a-z_0-9]+/gi,ov="#pragma main";class uv extends iv{constructor(e){const{type:t,inputs:r,name:s,precision:i,inputsCode:n,blockCode:a,headerCode:o}=(e=>{const t=(e=e.trim()).indexOf(ov),r=-1!==t?e.slice(t+12):e,s=r.match(nv);if(null!==s&&5===s.length){const i=s[4],n=[];let a=null;for(;null!==(a=av.exec(i));)n.push(a);const o=[];let u=0;for(;u0||e.backgroundBlurriness>0&&0===t.backgroundBlurriness;if(t.background!==r||s){const i=this.getCacheNode("background",r,()=>{if(!0===r.isCubeTexture||r.mapping===Z||r.mapping===J||r.mapping===he){if(e.backgroundBlurriness>0||r.mapping===he)return wm(r);{let e;return e=!0===r.isCubeTexture?bd(r):Zu(r),Rp(e)}}if(!0===r.isTexture)return Zu(r,wh.flipY()).setUpdateMatrix(!0);!0!==r.isColor&&console.error("WebGPUNodes: Unsupported background configuration.",r)},s);t.backgroundNode=i,t.background=r,t.backgroundBlurriness=e.backgroundBlurriness}}else t.backgroundNode&&(delete t.backgroundNode,delete t.background)}getCacheNode(e,t,r,s=!1){const i=this.cacheLib[e]||(this.cacheLib[e]=new WeakMap);let n=i.get(t);return(void 0===n||s)&&(n=r(),i.set(t,n)),n}updateFog(e){const t=this.get(e),r=e.fog;if(r){if(t.fog!==r){const e=this.getCacheNode("fog",r,()=>{if(r.isFogExp2){const e=_d("color","color",r).setGroup(Kn),t=_d("density","float",r).setGroup(Kn);return Yb(e,Kb(t))}if(r.isFog){const e=_d("color","color",r).setGroup(Kn),t=_d("near","float",r).setGroup(Kn),s=_d("far","float",r).setGroup(Kn);return Yb(e,Xb(t,s))}console.error("THREE.Renderer: Unsupported fog configuration.",r)});t.fogNode=e,t.fog=r}}else delete t.fogNode,delete t.fog}updateEnvironment(e){const t=this.get(e),r=e.environment;if(r){if(t.environment!==r){const e=this.getCacheNode("environment",r,()=>!0===r.isCubeTexture?bd(r):!0===r.isTexture?Zu(r):void console.error("Nodes: Unsupported environment configuration.",r));t.environmentNode=e,t.environment=r}}else t.environmentNode&&(delete t.environmentNode,delete t.environment)}getNodeFrame(e=this.renderer,t=null,r=null,s=null,i=null){const n=this.nodeFrame;return n.renderer=e,n.scene=t,n.object=r,n.camera=s,n.material=i,n}getNodeFrameForRender(e){return this.getNodeFrame(e.renderer,e.scene,e.object,e.camera,e.material)}getOutputCacheKey(){const e=this.renderer;return e.toneMapping+","+e.currentColorSpace+","+e.xr.isPresenting}hasOutputChange(e){return dv.get(e)!==this.getOutputCacheKey()}getOutputNode(e){const t=this.renderer,r=this.getOutputCacheKey(),s=e.isArrayTexture?ab(e,en(wh,nl("gl_ViewID_OVR"))).renderOutput(t.toneMapping,t.currentColorSpace):Zu(e,wh).renderOutput(t.toneMapping,t.currentColorSpace);return dv.set(e,r),s}updateBefore(e){const t=e.getNodeBuilderState();for(const r of t.updateBeforeNodes)this.getNodeFrameForRender(e).updateBeforeNode(r)}updateAfter(e){const t=e.getNodeBuilderState();for(const r of t.updateAfterNodes)this.getNodeFrameForRender(e).updateAfterNode(r)}updateForCompute(e){const t=this.getNodeFrame(),r=this.getForCompute(e);for(const e of r.updateNodes)t.updateNode(e)}updateForRender(e){const t=this.getNodeFrameForRender(e),r=e.getNodeBuilderState();for(const e of r.updateNodes)t.updateNode(e)}needsRefresh(e){const t=this.getNodeFrameForRender(e);return e.getMonitor().needsRefresh(e,t)}dispose(){super.dispose(),this.nodeFrame=new H_,this.nodeBuilderCache=new Map,this.cacheLib={}}}const gv=new Me;class mv{constructor(e=null){this.version=0,this.clipIntersection=null,this.cacheKey="",this.shadowPass=!1,this.viewNormalMatrix=new n,this.clippingGroupContexts=new WeakMap,this.intersectionPlanes=[],this.unionPlanes=[],this.parentVersion=null,null!==e&&(this.viewNormalMatrix=e.viewNormalMatrix,this.clippingGroupContexts=e.clippingGroupContexts,this.shadowPass=e.shadowPass,this.viewMatrix=e.viewMatrix)}projectPlanes(e,t,r){const s=e.length;for(let i=0;i0,alpha:!0,depth:t.depth,stencil:t.stencil,framebufferScaleFactor:this.getFramebufferScaleFactor()},i=new XRWebGLLayer(e,s,r);this._glBaseLayer=i,e.updateRenderState({baseLayer:i}),t.setPixelRatio(1),t._setXRLayerSize(i.framebufferWidth,i.framebufferHeight),this._xrRenderTarget=new Nv(i.framebufferWidth,i.framebufferHeight,{format:de,type:Ce,colorSpace:t.outputColorSpace,stencilBuffer:t.stencil,resolveDepthBuffer:!1===i.ignoreDepthValues,resolveStencilBuffer:!1===i.ignoreDepthValues}),this._xrRenderTarget._isOpaqueFramebuffer=!0,this._referenceSpace=await e.requestReferenceSpace(this.getReferenceSpaceType())}this.setFoveation(this.getFoveation()),t._animation.setAnimationLoop(this._onAnimationFrame),t._animation.setContext(e),t._animation.start(),this.isPresenting=!0,this.dispatchEvent({type:"sessionstart"})}}updateCamera(e){const t=this._session;if(null===t)return;const r=e.near,s=e.far,i=this._cameraXR,n=this._cameraL,a=this._cameraR;i.near=a.near=n.near=r,i.far=a.far=n.far=s,i.isMultiViewCamera=this._useMultiview,this._currentDepthNear===i.near&&this._currentDepthFar===i.far||(t.updateRenderState({depthNear:i.near,depthFar:i.far}),this._currentDepthNear=i.near,this._currentDepthFar=i.far),n.layers.mask=2|e.layers.mask,a.layers.mask=4|e.layers.mask,i.layers.mask=n.layers.mask|a.layers.mask;const o=e.parent,u=i.cameras;Av(i,o);for(let e=0;e=0&&(r[n]=null,t[n].disconnect(i))}for(let s=0;s=r.length){r.push(i),n=e;break}if(null===r[e]){r[e]=i,n=e;break}}if(-1===n)break}const a=t[n];a&&a.connect(i)}}function Pv(e){return"quad"===e.type?this._glBinding.createQuadLayer({transform:new XRRigidTransform(e.translation,e.quaternion),width:e.width/2,height:e.height/2,space:this._referenceSpace,viewPixelWidth:e.pixelwidth,viewPixelHeight:e.pixelheight,clearOnAccess:!1}):this._glBinding.createCylinderLayer({transform:new XRRigidTransform(e.translation,e.quaternion),radius:e.radius,centralAngle:e.centralAngle,aspectRatio:e.aspectRatio,space:this._referenceSpace,viewPixelWidth:e.pixelwidth,viewPixelHeight:e.pixelheight,clearOnAccess:!1})}function Bv(e,t){if(void 0===t)return;const r=this._cameraXR,i=this._renderer,n=i.backend,a=this._glBaseLayer,o=this.getReferenceSpace(),u=t.getViewerPose(o);if(this._xrFrame=t,null!==u){const e=u.views;null!==this._glBaseLayer&&n.setXRTarget(a.framebuffer);let t=!1;e.length!==r.cameras.length&&(r.cameras.length=0,t=!0);for(let i=0;i{await this.compileAsync(e,t);const s=this._renderLists.get(e,t),i=this._renderContexts.get(e,t,this._renderTarget),n=e.overrideMaterial||r.material,a=this._objects.get(r,n,e,t,s.lightsNode,i,i.clippingContext),{fragmentShader:o,vertexShader:u}=a.getNodeBuilderState();return{fragmentShader:o,vertexShader:u}}}}async init(){if(this._initialized)throw new Error("Renderer: Backend has already been initialized.");return null!==this._initPromise||(this._initPromise=new Promise(async(e,t)=>{let r=this.backend;try{await r.init(this)}catch(e){if(null===this._getFallback)return void t(e);try{this.backend=r=this._getFallback(e),await r.init(this)}catch(e){return void t(e)}}this._nodes=new pv(this,r),this._animation=new nf(this._nodes,this.info),this._attributes=new yf(r),this._background=new d_(this,this._nodes),this._geometries=new Tf(this._attributes,this.info),this._textures=new Hf(this,r,this.info),this._pipelines=new Af(r,this._nodes),this._bindings=new Rf(r,this._nodes,this._textures,this._attributes,this._pipelines,this.info),this._objects=new df(this,this._nodes,this._geometries,this._pipelines,this._bindings,this.info),this._renderLists=new Lf(this.lighting),this._bundles=new bv,this._renderContexts=new Gf,this._animation.start(),this._initialized=!0,e(this)})),this._initPromise}get coordinateSystem(){return this.backend.coordinateSystem}async compileAsync(e,t,r=null){if(!0===this._isDeviceLost)return;!1===this._initialized&&await this.init();const s=this._nodes.nodeFrame,i=s.renderId,n=this._currentRenderContext,a=this._currentRenderObjectFunction,o=this._compilationPromises,u=!0===e.isScene?e:Fv;null===r&&(r=e);const l=this._renderTarget,d=this._renderContexts.get(r,t,l),c=this._activeMipmapLevel,h=[];this._currentRenderContext=d,this._currentRenderObjectFunction=this.renderObject,this._handleObjectFunction=this._createObjectPipeline,this._compilationPromises=h,s.renderId++,s.update(),d.depth=this.depth,d.stencil=this.stencil,d.clippingContext||(d.clippingContext=new mv),d.clippingContext.updateGlobal(u,t),u.onBeforeRender(this,e,t,l);const p=this._renderLists.get(e,t);if(p.begin(),this._projectObject(e,t,0,p,d.clippingContext),r!==e&&r.traverseVisible(function(e){e.isLight&&e.layers.test(t.layers)&&p.pushLight(e)}),p.finish(),null!==l){this._textures.updateRenderTarget(l,c);const e=this._textures.get(l);d.textures=e.textures,d.depthTexture=e.depthTexture}else d.textures=null,d.depthTexture=null;this._background.update(u,p,d);const g=p.opaque,m=p.transparent,f=p.transparentDoublePass,y=p.lightsNode;!0===this.opaque&&g.length>0&&this._renderObjects(g,t,u,y),!0===this.transparent&&m.length>0&&this._renderTransparents(m,f,t,u,y),s.renderId=i,this._currentRenderContext=n,this._currentRenderObjectFunction=a,this._compilationPromises=o,this._handleObjectFunction=this._renderObjectDirect,await Promise.all(h)}async renderAsync(e,t){!1===this._initialized&&await this.init(),this._renderScene(e,t)}async waitForGPU(){await this.backend.waitForGPU()}set highPrecision(e){!0===e?(this.overrideNodes.modelViewMatrix=Ll,this.overrideNodes.modelNormalViewMatrix=Dl):this.highPrecision&&(this.overrideNodes.modelViewMatrix=null,this.overrideNodes.modelNormalViewMatrix=null)}get highPrecision(){return this.overrideNodes.modelViewMatrix===Ll&&this.overrideNodes.modelNormalViewMatrix===Dl}setMRT(e){return this._mrt=e,this}getMRT(){return this._mrt}getColorBufferType(){return this._colorBufferType}_onDeviceLost(e){let t=`THREE.WebGPURenderer: ${e.api} Device Lost:\n\nMessage: ${e.message}`;e.reason&&(t+=`\nReason: ${e.reason}`),console.error(t),this._isDeviceLost=!0}_renderBundle(e,t,r){const{bundleGroup:s,camera:i,renderList:n}=e,a=this._currentRenderContext,o=this._bundles.get(s,i),u=this.backend.get(o);void 0===u.renderContexts&&(u.renderContexts=new Set);const l=s.version!==u.version,d=!1===u.renderContexts.has(a)||l;if(u.renderContexts.add(a),d){this.backend.beginBundle(a),(void 0===u.renderObjects||l)&&(u.renderObjects=[]),this._currentRenderBundle=o;const{transparentDoublePass:e,transparent:d,opaque:c}=n;!0===this.opaque&&c.length>0&&this._renderObjects(c,i,t,r),!0===this.transparent&&d.length>0&&this._renderTransparents(d,e,i,t,r),this._currentRenderBundle=null,this.backend.finishBundle(a,o),u.version=s.version}else{const{renderObjects:e}=u;for(let t=0,r=e.length;t>=c,p.viewportValue.height>>=c,p.viewportValue.minDepth=x,p.viewportValue.maxDepth=T,p.viewport=!1===p.viewportValue.equals(Dv),p.scissorValue.copy(y).multiplyScalar(b).floor(),p.scissor=this._scissorTest&&!1===p.scissorValue.equals(Dv),p.scissorValue.width>>=c,p.scissorValue.height>>=c,p.clippingContext||(p.clippingContext=new mv),p.clippingContext.updateGlobal(u,t),u.onBeforeRender(this,e,t,h);const _=t.isArrayCamera?Vv:Iv;t.isArrayCamera||(Uv.multiplyMatrices(t.projectionMatrix,t.matrixWorldInverse),_.setFromProjectionMatrix(Uv,g));const v=this._renderLists.get(e,t);if(v.begin(),this._projectObject(e,t,0,v,p.clippingContext),v.finish(),!0===this.sortObjects&&v.sort(this._opaqueSort,this._transparentSort),null!==h){this._textures.updateRenderTarget(h,c);const e=this._textures.get(h);p.textures=e.textures,p.depthTexture=e.depthTexture,p.width=e.width,p.height=e.height,p.renderTarget=h,p.depth=h.depthBuffer,p.stencil=h.stencilBuffer}else p.textures=null,p.depthTexture=null,p.width=this.domElement.width,p.height=this.domElement.height,p.depth=this.depth,p.stencil=this.stencil;p.width>>=c,p.height>>=c,p.activeCubeFace=d,p.activeMipmapLevel=c,p.occlusionQueryCount=v.occlusionQueryCount,this._background.update(u,v,p),p.camera=t,this.backend.beginRender(p);const{bundles:N,lightsNode:S,transparentDoublePass:E,transparent:w,opaque:A}=v;return N.length>0&&this._renderBundles(N,u,S),!0===this.opaque&&A.length>0&&this._renderObjects(A,t,u,S),!0===this.transparent&&w.length>0&&this._renderTransparents(w,E,t,u,S),this.backend.finishRender(p),i.renderId=n,this._currentRenderContext=a,this._currentRenderObjectFunction=o,null!==s&&(this.setRenderTarget(l,d,c),this._renderOutput(h)),u.onAfterRender(this,e,t,h),p}_setXRLayerSize(e,t){this._width=e,this._height=t,this.setViewport(0,0,e,t)}_renderOutput(e){const t=this._quad;this._nodes.hasOutputChange(e.texture)&&(t.material.fragmentNode=this._nodes.getOutputNode(e.texture),t.material.needsUpdate=!0);const r=this.autoClear,s=this.xr.enabled;this.autoClear=!1,this.xr.enabled=!1,this._renderScene(t,t.camera,!1),this.autoClear=r,this.xr.enabled=s}getMaxAnisotropy(){return this.backend.getMaxAnisotropy()}getActiveCubeFace(){return this._activeCubeFace}getActiveMipmapLevel(){return this._activeMipmapLevel}async setAnimationLoop(e){!1===this._initialized&&await this.init(),this._animation.setAnimationLoop(e)}async getArrayBufferAsync(e){return await this.backend.getArrayBufferAsync(e)}getContext(){return this.backend.getContext()}getPixelRatio(){return this._pixelRatio}getDrawingBufferSize(e){return e.set(this._width*this._pixelRatio,this._height*this._pixelRatio).floor()}getSize(e){return e.set(this._width,this._height)}setPixelRatio(e=1){this._pixelRatio!==e&&(this._pixelRatio=e,this.setSize(this._width,this._height,!1))}setDrawingBufferSize(e,t,r){this.xr&&this.xr.isPresenting||(this._width=e,this._height=t,this._pixelRatio=r,this.domElement.width=Math.floor(e*r),this.domElement.height=Math.floor(t*r),this.setViewport(0,0,e,t),this._initialized&&this.backend.updateSize())}setSize(e,t,r=!0){this.xr&&this.xr.isPresenting||(this._width=e,this._height=t,this.domElement.width=Math.floor(e*this._pixelRatio),this.domElement.height=Math.floor(t*this._pixelRatio),!0===r&&(this.domElement.style.width=e+"px",this.domElement.style.height=t+"px"),this.setViewport(0,0,e,t),this._initialized&&this.backend.updateSize())}setOpaqueSort(e){this._opaqueSort=e}setTransparentSort(e){this._transparentSort=e}getScissor(e){const t=this._scissor;return e.x=t.x,e.y=t.y,e.width=t.width,e.height=t.height,e}setScissor(e,t,r,s){const i=this._scissor;e.isVector4?i.copy(e):i.set(e,t,r,s)}getScissorTest(){return this._scissorTest}setScissorTest(e){this._scissorTest=e,this.backend.setScissorTest(e)}getViewport(e){return e.copy(this._viewport)}setViewport(e,t,r,s,i=0,n=1){const a=this._viewport;e.isVector4?a.copy(e):a.set(e,t,r,s),a.minDepth=i,a.maxDepth=n}getClearColor(e){return e.copy(this._clearColor)}setClearColor(e,t=1){this._clearColor.set(e),this._clearColor.a=t}getClearAlpha(){return this._clearColor.a}setClearAlpha(e){this._clearColor.a=e}getClearDepth(){return this._clearDepth}setClearDepth(e){this._clearDepth=e}getClearStencil(){return this._clearStencil}setClearStencil(e){this._clearStencil=e}isOccluded(e){const t=this._currentRenderContext;return t&&this.backend.isOccluded(t,e)}clear(e=!0,t=!0,r=!0){if(!1===this._initialized)return console.warn("THREE.Renderer: .clear() called before the backend is initialized. Try using .clearAsync() instead."),this.clearAsync(e,t,r);const s=this._renderTarget||this._getFrameBufferTarget();let i=null;if(null!==s){this._textures.updateRenderTarget(s);const e=this._textures.get(s);i=this._renderContexts.getForClear(s),i.textures=e.textures,i.depthTexture=e.depthTexture,i.width=e.width,i.height=e.height,i.renderTarget=s,i.depth=s.depthBuffer,i.stencil=s.stencilBuffer,i.clearColorValue=this.backend.getClearColor(),i.activeCubeFace=this.getActiveCubeFace(),i.activeMipmapLevel=this.getActiveMipmapLevel()}this.backend.clear(e,t,r,i),null!==s&&null===this._renderTarget&&this._renderOutput(s)}clearColor(){return this.clear(!0,!1,!1)}clearDepth(){return this.clear(!1,!0,!1)}clearStencil(){return this.clear(!1,!1,!0)}async clearAsync(e=!0,t=!0,r=!0){!1===this._initialized&&await this.init(),this.clear(e,t,r)}async clearColorAsync(){this.clearAsync(!0,!1,!1)}async clearDepthAsync(){this.clearAsync(!1,!0,!1)}async clearStencilAsync(){this.clearAsync(!1,!1,!0)}get currentToneMapping(){return this.isOutputTarget?this.toneMapping:p}get currentColorSpace(){return this.isOutputTarget?this.outputColorSpace:le}get isOutputTarget(){return this._renderTarget===this._outputRenderTarget||null===this._renderTarget}dispose(){this.info.dispose(),this.backend.dispose(),this._animation.dispose(),this._objects.dispose(),this._pipelines.dispose(),this._nodes.dispose(),this._bindings.dispose(),this._renderLists.dispose(),this._renderContexts.dispose(),this._textures.dispose(),null!==this._frameBufferTarget&&this._frameBufferTarget.dispose(),Object.values(this.backend.timestampQueryPool).forEach(e=>{null!==e&&e.dispose()}),this.setRenderTarget(null),this.setAnimationLoop(null)}setRenderTarget(e,t=0,r=0){this._renderTarget=e,this._activeCubeFace=t,this._activeMipmapLevel=r}getRenderTarget(){return this._renderTarget}setOutputRenderTarget(e){this._outputRenderTarget=e}getOutputRenderTarget(){return this._outputRenderTarget}_resetXRState(){this.backend.setXRTarget(null),this.setOutputRenderTarget(null),this.setRenderTarget(null),this._frameBufferTarget.dispose(),this._frameBufferTarget=null}setRenderObjectFunction(e){this._renderObjectFunction=e}getRenderObjectFunction(){return this._renderObjectFunction}compute(e){if(!0===this._isDeviceLost)return;if(!1===this._initialized)return console.warn("THREE.Renderer: .compute() called before the backend is initialized. Try using .computeAsync() instead."),this.computeAsync(e);const t=this._nodes.nodeFrame,r=t.renderId;this.info.calls++,this.info.compute.calls++,this.info.compute.frameCalls++,t.renderId=this.info.calls;const s=this.backend,i=this._pipelines,n=this._bindings,a=this._nodes,o=Array.isArray(e)?e:[e];if(void 0===o[0]||!0!==o[0].isComputeNode)throw new Error("THREE.Renderer: .compute() expects a ComputeNode.");s.beginCompute(e);for(const t of o){if(!1===i.has(t)){const e=()=>{t.removeEventListener("dispose",e),i.delete(t),n.delete(t),a.delete(t)};t.addEventListener("dispose",e);const r=t.onInitFunction;null!==r&&r.call(t,{renderer:this})}a.updateForCompute(t),n.updateForCompute(t);const r=n.getForCompute(t),o=i.getForCompute(t,r);s.compute(e,t,r,o)}s.finishCompute(e),t.renderId=r}async computeAsync(e){!1===this._initialized&&await this.init(),this.compute(e)}async hasFeatureAsync(e){return!1===this._initialized&&await this.init(),this.backend.hasFeature(e)}async resolveTimestampsAsync(e="render"){return!1===this._initialized&&await this.init(),this.backend.resolveTimestampsAsync(e)}hasFeature(e){return!1===this._initialized?(console.warn("THREE.Renderer: .hasFeature() called before the backend is initialized. Try using .hasFeatureAsync() instead."),!1):this.backend.hasFeature(e)}hasInitialized(){return this._initialized}async initTextureAsync(e){!1===this._initialized&&await this.init(),this._textures.updateTexture(e)}initTexture(e){!1===this._initialized&&console.warn("THREE.Renderer: .initTexture() called before the backend is initialized. Try using .initTextureAsync() instead."),this._textures.updateTexture(e)}copyFramebufferToTexture(e,t=null){if(null!==t)if(t.isVector2)t=Ov.set(t.x,t.y,e.image.width,e.image.height).floor();else{if(!t.isVector4)return void console.error("THREE.Renderer.copyFramebufferToTexture: Invalid rectangle.");t=Ov.copy(t).floor()}else t=Ov.set(0,0,e.image.width,e.image.height);let r,s=this._currentRenderContext;null!==s?r=s.renderTarget:(r=this._renderTarget||this._getFrameBufferTarget(),null!==r&&(this._textures.updateRenderTarget(r),s=this._textures.get(r))),this._textures.updateTexture(e,{renderTarget:r}),this.backend.copyFramebufferToTexture(e,s,t)}copyTextureToTexture(e,t,r=null,s=null,i=0,n=0){this._textures.updateTexture(e),this._textures.updateTexture(t),this.backend.copyTextureToTexture(e,t,r,s,i,n)}async readRenderTargetPixelsAsync(e,t,r,s,i,n=0,a=0){return this.backend.copyTextureToBuffer(e.textures[n],t,r,s,i,a)}_projectObject(e,t,r,s,i){if(!1===e.visible)return;if(e.layers.test(t.layers))if(e.isGroup)r=e.renderOrder,e.isClippingGroup&&e.enabled&&(i=i.getGroupContext(e));else if(e.isLOD)!0===e.autoUpdate&&e.update(t);else if(e.isLight)s.pushLight(e);else if(e.isSprite){const n=t.isArrayCamera?Vv:Iv;if(!e.frustumCulled||n.intersectsSprite(e,t)){!0===this.sortObjects&&Ov.setFromMatrixPosition(e.matrixWorld).applyMatrix4(Uv);const{geometry:t,material:n}=e;n.visible&&s.push(e,t,n,r,Ov.z,null,i)}}else if(e.isLineLoop)console.error("THREE.Renderer: Objects of type THREE.LineLoop are not supported. Please use THREE.Line or THREE.LineSegments.");else if(e.isMesh||e.isLine||e.isPoints){const n=t.isArrayCamera?Vv:Iv;if(!e.frustumCulled||n.intersectsObject(e,t)){const{geometry:t,material:n}=e;if(!0===this.sortObjects&&(null===t.boundingSphere&&t.computeBoundingSphere(),Ov.copy(t.boundingSphere.center).applyMatrix4(e.matrixWorld).applyMatrix4(Uv)),Array.isArray(n)){const a=t.groups;for(let o=0,u=a.length;o0){for(const{material:e}of t)e.side=S;this._renderObjects(t,r,s,i,"backSide");for(const{material:e}of t)e.side=je;this._renderObjects(e,r,s,i);for(const{material:e}of t)e.side=E}else this._renderObjects(e,r,s,i)}_renderObjects(e,t,r,s,i=null){for(let n=0,a=e.length;n0,e.isShadowPassMaterial&&(e.side=null===i.shadowSide?i.side:i.shadowSide,i.depthNode&&i.depthNode.isNode&&(c=e.depthNode,e.depthNode=i.depthNode),i.castShadowNode&&i.castShadowNode.isNode&&(d=e.colorNode,e.colorNode=i.castShadowNode),i.castShadowPositionNode&&i.castShadowPositionNode.isNode&&(l=e.positionNode,e.positionNode=i.castShadowPositionNode)),i=e}!0===i.transparent&&i.side===E&&!1===i.forceSinglePass?(i.side=S,this._handleObjectFunction(e,i,t,r,a,n,o,"backSide"),i.side=je,this._handleObjectFunction(e,i,t,r,a,n,o,u),i.side=E):this._handleObjectFunction(e,i,t,r,a,n,o,u),void 0!==l&&(t.overrideMaterial.positionNode=l),void 0!==c&&(t.overrideMaterial.depthNode=c),void 0!==d&&(t.overrideMaterial.colorNode=d),e.onAfterRender(this,t,r,s,i,n)}_renderObjectDirect(e,t,r,s,i,n,a,o){const u=this._objects.get(e,t,r,s,i,this._currentRenderContext,a,o);u.drawRange=e.geometry.drawRange,u.group=n;const l=this._nodes.needsRefresh(u);if(l&&(this._nodes.updateBefore(u),this._geometries.updateForRender(u),this._nodes.updateForRender(u),this._bindings.updateForRender(u)),this._pipelines.updateForRender(u),null!==this._currentRenderBundle){this.backend.get(this._currentRenderBundle).renderObjects.push(u),u.bundle=this._currentRenderBundle.bundleGroup}this.backend.draw(u,this.info),l&&this._nodes.updateAfter(u)}_createObjectPipeline(e,t,r,s,i,n,a,o){const u=this._objects.get(e,t,r,s,i,this._currentRenderContext,a,o);u.drawRange=e.geometry.drawRange,u.group=n,this._nodes.updateBefore(u),this._geometries.updateForRender(u),this._nodes.updateForRender(u),this._bindings.updateForRender(u),this._pipelines.getForRender(u,this._compilationPromises),this._nodes.updateAfter(u)}get compile(){return this.compileAsync}}class Gv{constructor(e=""){this.name=e,this.visibility=0}setVisibility(e){this.visibility|=e}clone(){return Object.assign(new this.constructor,this)}}class zv extends Gv{constructor(e,t=null){super(e),this.isBuffer=!0,this.bytesPerElement=Float32Array.BYTES_PER_ELEMENT,this._buffer=t}get byteLength(){return(e=this._buffer.byteLength)+(ff-e%ff)%ff;var e}get buffer(){return this._buffer}update(){return!0}}class Hv extends zv{constructor(e,t=null){super(e,t),this.isUniformBuffer=!0}}let $v=0;class Wv extends Hv{constructor(e,t){super("UniformBuffer_"+$v++,e?e.value:null),this.nodeUniform=e,this.groupNode=t}get buffer(){return this.nodeUniform.value}}class jv extends Hv{constructor(e){super(e),this.isUniformsGroup=!0,this._values=null,this.uniforms=[]}addUniform(e){return this.uniforms.push(e),this}removeUniform(e){const t=this.uniforms.indexOf(e);return-1!==t&&this.uniforms.splice(t,1),this}get values(){return null===this._values&&(this._values=Array.from(this.buffer)),this._values}get buffer(){let e=this._buffer;if(null===e){const t=this.byteLength;e=new Float32Array(new ArrayBuffer(t)),this._buffer=e}return e}get byteLength(){const e=this.bytesPerElement;let t=0;for(let r=0,s=this.uniforms.length;r0?s:"";t=`${e.name} {\n\t${r} ${i.name}[${n}];\n};\n`}else{t=`${this.getVectorType(i.type)} ${this.getPropertyName(i,e)};`,n=!0}const a=i.node.precision;if(null!==a&&(t=tN[a]+" "+t),n){t="\t"+t;const e=i.groupNode.name;(s[e]||(s[e]=[])).push(t)}else t="uniform "+t,r.push(t)}let i="";for(const t in s){const r=s[t];i+=this._getGLSLUniformStruct(e+"_"+t,r.join("\n"))+"\n"}return i+=r.join("\n"),i}getTypeFromAttribute(e){let t=super.getTypeFromAttribute(e);if(/^[iu]/.test(t)&&e.gpuType!==_){let r=e;e.isInterleavedBufferAttribute&&(r=e.data);const s=r.array;!1==(s instanceof Uint32Array||s instanceof Int32Array)&&(t=t.slice(1))}return t}getAttributes(e){let t="";if("vertex"===e||"compute"===e){const e=this.getAttributesArray();let r=0;for(const s of e)t+=`layout( location = ${r++} ) in ${s.type} ${s.name};\n`}return t}getStructMembers(e){const t=[];for(const r of e.members)t.push(`\t${r.type} ${r.name};`);return t.join("\n")}getStructs(e){const t=[],r=this.structs[e],s=[];for(const e of r)if(e.output)for(const t of e.members)s.push(`layout( location = ${t.index} ) out ${t.type} ${t.name};`);else{let r="struct "+e.name+" {\n";r+=this.getStructMembers(e),r+="\n};\n",t.push(r)}return 0===s.length&&s.push("layout( location = 0 ) out vec4 fragColor;"),"\n"+s.join("\n")+"\n\n"+t.join("\n")}getVaryings(e){let t="";const r=this.varyings;if("vertex"===e||"compute"===e)for(const s of r){"compute"===e&&(s.needsInterpolation=!0);const r=this.getType(s.type);if(s.needsInterpolation)if(s.interpolationType){t+=`${sN[s.interpolationType]||s.interpolationType} ${iN[s.interpolationSampling]||""} out ${r} ${s.name};\n`}else{t+=`${r.includes("int")||r.includes("uv")||r.includes("iv")?"flat ":""}out ${r} ${s.name};\n`}else t+=`${r} ${s.name};\n`}else if("fragment"===e)for(const e of r)if(e.needsInterpolation){const r=this.getType(e.type);if(e.interpolationType){t+=`${sN[e.interpolationType]||e.interpolationType} ${iN[e.interpolationSampling]||""} in ${r} ${e.name};\n`}else{t+=`${r.includes("int")||r.includes("uv")||r.includes("iv")?"flat ":""}in ${r} ${e.name};\n`}}for(const r of this.builtins[e])t+=`${r};\n`;return t}getVertexIndex(){return"uint( gl_VertexID )"}getInstanceIndex(){return"uint( gl_InstanceID )"}getInvocationLocalIndex(){return`uint( gl_InstanceID ) % ${this.object.workgroupSize.reduce((e,t)=>e*t,1)}u`}getDrawIndex(){return this.renderer.backend.extensions.has("WEBGL_multi_draw")?"uint( gl_DrawID )":null}getFrontFacing(){return"gl_FrontFacing"}getFragCoord(){return"gl_FragCoord.xy"}getFragDepth(){return"gl_FragDepth"}enableExtension(e,t,r=this.shaderStage){const s=this.extensions[r]||(this.extensions[r]=new Map);!1===s.has(e)&&s.set(e,{name:e,behavior:t})}getExtensions(e){const t=[];if("vertex"===e){const t=this.renderer.backend.extensions;this.object.isBatchedMesh&&t.has("WEBGL_multi_draw")&&this.enableExtension("GL_ANGLE_multi_draw","require",e)}const r=this.extensions[e];if(void 0!==r)for(const{name:e,behavior:s}of r.values())t.push(`#extension ${e} : ${s}`);return t.join("\n")}getClipDistance(){return"gl_ClipDistance"}isAvailable(e){let t=rN[e];if(void 0===t){let r;switch(t=!1,e){case"float32Filterable":r="OES_texture_float_linear";break;case"clipDistance":r="WEBGL_clip_cull_distance"}if(void 0!==r){const e=this.renderer.backend.extensions;e.has(r)&&(e.get(r),t=!0)}rN[e]=t}return t}isFlipY(){return!0}enableHardwareClipping(e){this.enableExtension("GL_ANGLE_clip_cull_distance","require"),this.builtins.vertex.push(`out float gl_ClipDistance[ ${e} ]`)}enableMultiview(){this.enableExtension("GL_OVR_multiview2","require","fragment"),this.enableExtension("GL_OVR_multiview2","require","vertex"),this.builtins.vertex.push("layout(num_views = 2) in")}registerTransform(e,t){this.transforms.push({varyingName:e,attributeNode:t})}getTransforms(){const e=this.transforms;let t="";for(let r=0;r0&&(r+="\n"),r+=`\t// flow -> ${n}\n\t`),r+=`${s.code}\n\t`,e===i&&"compute"!==t&&(r+="// result\n\t","vertex"===t?(r+="gl_Position = ",r+=`${s.result};`):"fragment"===t&&(e.outputNode.isOutputStructNode||(r+="fragColor = ",r+=`${s.result};`)))}const n=e[t];n.extensions=this.getExtensions(t),n.uniforms=this.getUniforms(t),n.attributes=this.getAttributes(t),n.varyings=this.getVaryings(t),n.vars=this.getVars(t),n.structs=this.getStructs(t),n.codes=this.getCodes(t),n.transforms=this.getTransforms(t),n.flow=r}null!==this.material?(this.vertexShader=this._getGLSLVertexCode(e.vertex),this.fragmentShader=this._getGLSLFragmentCode(e.fragment)):this.computeShader=this._getGLSLVertexCode(e.compute)}getUniformFromNode(e,t,r,s=null){const i=super.getUniformFromNode(e,t,r,s),n=this.getDataFromNode(e,r,this.globalCache);let a=n.uniformGPU;if(void 0===a){const s=e.groupNode,o=s.name,u=this.getBindGroupArray(o,r);if("texture"===t)a=new Qv(i.name,i.node,s),u.push(a);else if("cubeTexture"===t)a=new Zv(i.name,i.node,s),u.push(a);else if("texture3D"===t)a=new Jv(i.name,i.node,s),u.push(a);else if("buffer"===t){e.name=`NodeBuffer_${e.id}`,i.name=`buffer${e.id}`;const t=new Wv(e,s);t.name=e.name,u.push(t),a=t}else{const e=this.uniformGroups[r]||(this.uniformGroups[r]={});let n=e[o];void 0===n&&(n=new Xv(r+"_"+o,s),e[o]=n,u.push(n)),a=this.getNodeUniform(i,t),n.addUniform(a)}n.uniformGPU=a}return i}}let oN=null,uN=null;class lN{constructor(e={}){this.parameters=Object.assign({},e),this.data=new WeakMap,this.renderer=null,this.domElement=null,this.timestampQueryPool={render:null,compute:null},this.trackTimestamp=!0===e.trackTimestamp}async init(e){this.renderer=e}get coordinateSystem(){}beginRender(){}finishRender(){}beginCompute(){}finishCompute(){}draw(){}compute(){}createProgram(){}destroyProgram(){}createBindings(){}updateBindings(){}updateBinding(){}createRenderPipeline(){}createComputePipeline(){}needsRenderUpdate(){}getRenderCacheKey(){}createNodeBuilder(){}createSampler(){}destroySampler(){}createDefaultTexture(){}createTexture(){}updateTexture(){}generateMipmaps(){}destroyTexture(){}async copyTextureToBuffer(){}copyTextureToTexture(){}copyFramebufferToTexture(){}createAttribute(){}createIndexAttribute(){}createStorageAttribute(){}updateAttribute(){}destroyAttribute(){}getContext(){}updateSize(){}updateViewport(){}isOccluded(){}async resolveTimestampsAsync(e="render"){if(!this.trackTimestamp)return void pt("WebGPURenderer: Timestamp tracking is disabled.");const t=this.timestampQueryPool[e];if(!t)return void pt(`WebGPURenderer: No timestamp query pool for type '${e}' found.`);const r=await t.resolveQueriesAsync();return this.renderer.info[e].timestamp=r,r}async waitForGPU(){}async getArrayBufferAsync(){}async hasFeatureAsync(){}hasFeature(){}getMaxAnisotropy(){}getDrawingBufferSize(){return oN=oN||new t,this.renderer.getDrawingBufferSize(oN)}setScissorTest(){}getClearColor(){const e=this.renderer;return uN=uN||new $f,e.getClearColor(uN),uN.getRGB(uN),uN}getDomElement(){let e=this.domElement;return null===e&&(e=void 0!==this.parameters.canvas?this.parameters.canvas:gt(),"setAttribute"in e&&e.setAttribute("data-engine",`three.js r${He} webgpu`),this.domElement=e),e}set(e,t){this.data.set(e,t)}get(e){let t=this.data.get(e);return void 0===t&&(t={},this.data.set(e,t)),t}has(e){return this.data.has(e)}delete(e){this.data.delete(e)}dispose(){}}let dN,cN,hN=0;class pN{constructor(e,t){this.buffers=[e.bufferGPU,t],this.type=e.type,this.bufferType=e.bufferType,this.pbo=e.pbo,this.byteLength=e.byteLength,this.bytesPerElement=e.BYTES_PER_ELEMENT,this.version=e.version,this.isInteger=e.isInteger,this.activeBufferIndex=0,this.baseId=e.id}get id(){return`${this.baseId}|${this.activeBufferIndex}`}get bufferGPU(){return this.buffers[this.activeBufferIndex]}get transformBuffer(){return this.buffers[1^this.activeBufferIndex]}switchBuffers(){this.activeBufferIndex^=1}}class gN{constructor(e){this.backend=e}createAttribute(e,t){const r=this.backend,{gl:s}=r,i=e.array,n=e.usage||s.STATIC_DRAW,a=e.isInterleavedBufferAttribute?e.data:e,o=r.get(a);let u,l=o.bufferGPU;if(void 0===l&&(l=this._createBuffer(s,t,i,n),o.bufferGPU=l,o.bufferType=t,o.version=a.version),i instanceof Float32Array)u=s.FLOAT;else if("undefined"!=typeof Float16Array&&i instanceof Float16Array)u=s.HALF_FLOAT;else if(i instanceof Uint16Array)u=e.isFloat16BufferAttribute?s.HALF_FLOAT:s.UNSIGNED_SHORT;else if(i instanceof Int16Array)u=s.SHORT;else if(i instanceof Uint32Array)u=s.UNSIGNED_INT;else if(i instanceof Int32Array)u=s.INT;else if(i instanceof Int8Array)u=s.BYTE;else if(i instanceof Uint8Array)u=s.UNSIGNED_BYTE;else{if(!(i instanceof Uint8ClampedArray))throw new Error("THREE.WebGLBackend: Unsupported buffer data format: "+i);u=s.UNSIGNED_BYTE}let d={bufferGPU:l,bufferType:t,type:u,byteLength:i.byteLength,bytesPerElement:i.BYTES_PER_ELEMENT,version:e.version,pbo:e.pbo,isInteger:u===s.INT||u===s.UNSIGNED_INT||e.gpuType===_,id:hN++};if(e.isStorageBufferAttribute||e.isStorageInstancedBufferAttribute){const e=this._createBuffer(s,t,i,n);d=new pN(d,e)}r.set(e,d)}updateAttribute(e){const t=this.backend,{gl:r}=t,s=e.array,i=e.isInterleavedBufferAttribute?e.data:e,n=t.get(i),a=n.bufferType,o=e.isInterleavedBufferAttribute?e.data.updateRanges:e.updateRanges;if(r.bindBuffer(a,n.bufferGPU),0===o.length)r.bufferSubData(a,0,s);else{for(let e=0,t=o.length;e1?this.enable(s.SAMPLE_ALPHA_TO_COVERAGE):this.disable(s.SAMPLE_ALPHA_TO_COVERAGE),r>0&&this.currentClippingPlanes!==r){const e=12288;for(let t=0;t<8;t++)t{!function i(){const n=e.clientWaitSync(t,e.SYNC_FLUSH_COMMANDS_BIT,0);if(n===e.WAIT_FAILED)return e.deleteSync(t),void s();n!==e.TIMEOUT_EXPIRED?(e.deleteSync(t),r()):requestAnimationFrame(i)}()})}}let yN,bN,xN,TN=!1;class _N{constructor(e){this.backend=e,this.gl=e.gl,this.extensions=e.extensions,this.defaultTextures={},!1===TN&&(this._init(),TN=!0)}_init(){const e=this.gl;yN={[Nr]:e.REPEAT,[vr]:e.CLAMP_TO_EDGE,[_r]:e.MIRRORED_REPEAT},bN={[v]:e.NEAREST,[Sr]:e.NEAREST_MIPMAP_NEAREST,[Ge]:e.NEAREST_MIPMAP_LINEAR,[Y]:e.LINEAR,[ke]:e.LINEAR_MIPMAP_NEAREST,[V]:e.LINEAR_MIPMAP_LINEAR},xN={[Pr]:e.NEVER,[Mr]:e.ALWAYS,[Ie]:e.LESS,[Cr]:e.LEQUAL,[Rr]:e.EQUAL,[Ar]:e.GEQUAL,[wr]:e.GREATER,[Er]:e.NOTEQUAL}}getGLTextureType(e){const{gl:t}=this;let r;return r=!0===e.isCubeTexture?t.TEXTURE_CUBE_MAP:!0===e.isArrayTexture||!0===e.isDataArrayTexture||!0===e.isCompressedArrayTexture?t.TEXTURE_2D_ARRAY:!0===e.isData3DTexture?t.TEXTURE_3D:t.TEXTURE_2D,r}getInternalFormat(e,t,r,s,i=!1){const{gl:n,extensions:a}=this;if(null!==e){if(void 0!==n[e])return n[e];console.warn("THREE.WebGLRenderer: Attempt to use non-existing WebGL internal format '"+e+"'")}let o=t;if(t===n.RED&&(r===n.FLOAT&&(o=n.R32F),r===n.HALF_FLOAT&&(o=n.R16F),r===n.UNSIGNED_BYTE&&(o=n.R8),r===n.UNSIGNED_SHORT&&(o=n.R16),r===n.UNSIGNED_INT&&(o=n.R32UI),r===n.BYTE&&(o=n.R8I),r===n.SHORT&&(o=n.R16I),r===n.INT&&(o=n.R32I)),t===n.RED_INTEGER&&(r===n.UNSIGNED_BYTE&&(o=n.R8UI),r===n.UNSIGNED_SHORT&&(o=n.R16UI),r===n.UNSIGNED_INT&&(o=n.R32UI),r===n.BYTE&&(o=n.R8I),r===n.SHORT&&(o=n.R16I),r===n.INT&&(o=n.R32I)),t===n.RG&&(r===n.FLOAT&&(o=n.RG32F),r===n.HALF_FLOAT&&(o=n.RG16F),r===n.UNSIGNED_BYTE&&(o=n.RG8),r===n.UNSIGNED_SHORT&&(o=n.RG16),r===n.UNSIGNED_INT&&(o=n.RG32UI),r===n.BYTE&&(o=n.RG8I),r===n.SHORT&&(o=n.RG16I),r===n.INT&&(o=n.RG32I)),t===n.RG_INTEGER&&(r===n.UNSIGNED_BYTE&&(o=n.RG8UI),r===n.UNSIGNED_SHORT&&(o=n.RG16UI),r===n.UNSIGNED_INT&&(o=n.RG32UI),r===n.BYTE&&(o=n.RG8I),r===n.SHORT&&(o=n.RG16I),r===n.INT&&(o=n.RG32I)),t===n.RGB){const e=i?Br:c.getTransfer(s);r===n.FLOAT&&(o=n.RGB32F),r===n.HALF_FLOAT&&(o=n.RGB16F),r===n.UNSIGNED_BYTE&&(o=n.RGB8),r===n.UNSIGNED_SHORT&&(o=n.RGB16),r===n.UNSIGNED_INT&&(o=n.RGB32UI),r===n.BYTE&&(o=n.RGB8I),r===n.SHORT&&(o=n.RGB16I),r===n.INT&&(o=n.RGB32I),r===n.UNSIGNED_BYTE&&(o=e===h?n.SRGB8:n.RGB8),r===n.UNSIGNED_SHORT_5_6_5&&(o=n.RGB565),r===n.UNSIGNED_SHORT_5_5_5_1&&(o=n.RGB5_A1),r===n.UNSIGNED_SHORT_4_4_4_4&&(o=n.RGB4),r===n.UNSIGNED_INT_5_9_9_9_REV&&(o=n.RGB9_E5)}if(t===n.RGB_INTEGER&&(r===n.UNSIGNED_BYTE&&(o=n.RGB8UI),r===n.UNSIGNED_SHORT&&(o=n.RGB16UI),r===n.UNSIGNED_INT&&(o=n.RGB32UI),r===n.BYTE&&(o=n.RGB8I),r===n.SHORT&&(o=n.RGB16I),r===n.INT&&(o=n.RGB32I)),t===n.RGBA){const e=i?Br:c.getTransfer(s);r===n.FLOAT&&(o=n.RGBA32F),r===n.HALF_FLOAT&&(o=n.RGBA16F),r===n.UNSIGNED_BYTE&&(o=n.RGBA8),r===n.UNSIGNED_SHORT&&(o=n.RGBA16),r===n.UNSIGNED_INT&&(o=n.RGBA32UI),r===n.BYTE&&(o=n.RGBA8I),r===n.SHORT&&(o=n.RGBA16I),r===n.INT&&(o=n.RGBA32I),r===n.UNSIGNED_BYTE&&(o=e===h?n.SRGB8_ALPHA8:n.RGBA8),r===n.UNSIGNED_SHORT_4_4_4_4&&(o=n.RGBA4),r===n.UNSIGNED_SHORT_5_5_5_1&&(o=n.RGB5_A1)}return t===n.RGBA_INTEGER&&(r===n.UNSIGNED_BYTE&&(o=n.RGBA8UI),r===n.UNSIGNED_SHORT&&(o=n.RGBA16UI),r===n.UNSIGNED_INT&&(o=n.RGBA32UI),r===n.BYTE&&(o=n.RGBA8I),r===n.SHORT&&(o=n.RGBA16I),r===n.INT&&(o=n.RGBA32I)),t===n.DEPTH_COMPONENT&&(r===n.UNSIGNED_SHORT&&(o=n.DEPTH_COMPONENT16),r===n.UNSIGNED_INT&&(o=n.DEPTH_COMPONENT24),r===n.FLOAT&&(o=n.DEPTH_COMPONENT32F)),t===n.DEPTH_STENCIL&&r===n.UNSIGNED_INT_24_8&&(o=n.DEPTH24_STENCIL8),o!==n.R16F&&o!==n.R32F&&o!==n.RG16F&&o!==n.RG32F&&o!==n.RGBA16F&&o!==n.RGBA32F||a.get("EXT_color_buffer_float"),o}setTextureParameters(e,t){const{gl:r,extensions:s,backend:i}=this,n=c.getPrimaries(c.workingColorSpace),a=t.colorSpace===b?null:c.getPrimaries(t.colorSpace),o=t.colorSpace===b||n===a?r.NONE:r.BROWSER_DEFAULT_WEBGL;r.pixelStorei(r.UNPACK_FLIP_Y_WEBGL,t.flipY),r.pixelStorei(r.UNPACK_PREMULTIPLY_ALPHA_WEBGL,t.premultiplyAlpha),r.pixelStorei(r.UNPACK_ALIGNMENT,t.unpackAlignment),r.pixelStorei(r.UNPACK_COLORSPACE_CONVERSION_WEBGL,o),r.texParameteri(e,r.TEXTURE_WRAP_S,yN[t.wrapS]),r.texParameteri(e,r.TEXTURE_WRAP_T,yN[t.wrapT]),e!==r.TEXTURE_3D&&e!==r.TEXTURE_2D_ARRAY||t.isArrayTexture||r.texParameteri(e,r.TEXTURE_WRAP_R,yN[t.wrapR]),r.texParameteri(e,r.TEXTURE_MAG_FILTER,bN[t.magFilter]);const u=void 0!==t.mipmaps&&t.mipmaps.length>0,l=t.minFilter===Y&&u?V:t.minFilter;if(r.texParameteri(e,r.TEXTURE_MIN_FILTER,bN[l]),t.compareFunction&&(r.texParameteri(e,r.TEXTURE_COMPARE_MODE,r.COMPARE_REF_TO_TEXTURE),r.texParameteri(e,r.TEXTURE_COMPARE_FUNC,xN[t.compareFunction])),!0===s.has("EXT_texture_filter_anisotropic")){if(t.magFilter===v)return;if(t.minFilter!==Ge&&t.minFilter!==V)return;if(t.type===D&&!1===s.has("OES_texture_float_linear"))return;if(t.anisotropy>1){const n=s.get("EXT_texture_filter_anisotropic");r.texParameterf(e,n.TEXTURE_MAX_ANISOTROPY_EXT,Math.min(t.anisotropy,i.getMaxAnisotropy()))}}}createDefaultTexture(e){const{gl:t,backend:r,defaultTextures:s}=this,i=this.getGLTextureType(e);let n=s[i];void 0===n&&(n=t.createTexture(),r.state.bindTexture(i,n),t.texParameteri(i,t.TEXTURE_MIN_FILTER,t.NEAREST),t.texParameteri(i,t.TEXTURE_MAG_FILTER,t.NEAREST),s[i]=n),r.set(e,{textureGPU:n,glTextureType:i,isDefault:!0})}createTexture(e,t){const{gl:r,backend:s}=this,{levels:i,width:n,height:a,depth:o}=t,u=s.utils.convert(e.format,e.colorSpace),l=s.utils.convert(e.type),d=this.getInternalFormat(e.internalFormat,u,l,e.colorSpace,e.isVideoTexture),c=r.createTexture(),h=this.getGLTextureType(e);s.state.bindTexture(h,c),this.setTextureParameters(h,e),e.isArrayTexture||e.isDataArrayTexture||e.isCompressedArrayTexture?r.texStorage3D(r.TEXTURE_2D_ARRAY,i,d,n,a,o):e.isData3DTexture?r.texStorage3D(r.TEXTURE_3D,i,d,n,a,o):e.isVideoTexture||r.texStorage2D(h,i,d,n,a),s.set(e,{textureGPU:c,glTextureType:h,glFormat:u,glType:l,glInternalFormat:d})}copyBufferToTexture(e,t){const{gl:r,backend:s}=this,{textureGPU:i,glTextureType:n,glFormat:a,glType:o}=s.get(t),{width:u,height:l}=t.source.data;r.bindBuffer(r.PIXEL_UNPACK_BUFFER,e),s.state.bindTexture(n,i),r.pixelStorei(r.UNPACK_FLIP_Y_WEBGL,!1),r.pixelStorei(r.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!1),r.texSubImage2D(n,0,0,0,u,l,a,o,0),r.bindBuffer(r.PIXEL_UNPACK_BUFFER,null),s.state.unbindTexture()}updateTexture(e,t){const{gl:r}=this,{width:s,height:i}=t,{textureGPU:n,glTextureType:a,glFormat:o,glType:u,glInternalFormat:l}=this.backend.get(e);if(!e.isRenderTargetTexture&&void 0!==n)if(this.backend.state.bindTexture(a,n),this.setTextureParameters(a,e),e.isCompressedTexture){const s=e.mipmaps,i=t.image;for(let t=0;t0,c=t.renderTarget?t.renderTarget.height:this.backend.getDrawingBufferSize().y;if(d){const r=0!==a||0!==o;let d,h;if(!0===e.isDepthTexture?(d=s.DEPTH_BUFFER_BIT,h=s.DEPTH_ATTACHMENT,t.stencil&&(d|=s.STENCIL_BUFFER_BIT)):(d=s.COLOR_BUFFER_BIT,h=s.COLOR_ATTACHMENT0),r){const e=this.backend.get(t.renderTarget),r=e.framebuffers[t.getCacheKey()],h=e.msaaFrameBuffer;i.bindFramebuffer(s.DRAW_FRAMEBUFFER,r),i.bindFramebuffer(s.READ_FRAMEBUFFER,h);const p=c-o-l;s.blitFramebuffer(a,p,a+u,p+l,a,p,a+u,p+l,d,s.NEAREST),i.bindFramebuffer(s.READ_FRAMEBUFFER,r),i.bindTexture(s.TEXTURE_2D,n),s.copyTexSubImage2D(s.TEXTURE_2D,0,0,0,a,p,u,l),i.unbindTexture()}else{const e=s.createFramebuffer();i.bindFramebuffer(s.DRAW_FRAMEBUFFER,e),s.framebufferTexture2D(s.DRAW_FRAMEBUFFER,h,s.TEXTURE_2D,n,0),s.blitFramebuffer(0,0,u,l,0,0,u,l,d,s.NEAREST),s.deleteFramebuffer(e)}}else i.bindTexture(s.TEXTURE_2D,n),s.copyTexSubImage2D(s.TEXTURE_2D,0,0,0,a,c-l-o,u,l),i.unbindTexture();e.generateMipmaps&&this.generateMipmaps(e),this.backend._setFramebuffer(t)}setupRenderBufferStorage(e,t,r,s=!1){const{gl:i}=this,n=t.renderTarget,{depthTexture:a,depthBuffer:o,stencilBuffer:u,width:l,height:d}=n;if(i.bindRenderbuffer(i.RENDERBUFFER,e),o&&!u){let t=i.DEPTH_COMPONENT24;if(!0===s){this.extensions.get("WEBGL_multisampled_render_to_texture").renderbufferStorageMultisampleEXT(i.RENDERBUFFER,n.samples,t,l,d)}else r>0?(a&&a.isDepthTexture&&a.type===i.FLOAT&&(t=i.DEPTH_COMPONENT32F),i.renderbufferStorageMultisample(i.RENDERBUFFER,r,t,l,d)):i.renderbufferStorage(i.RENDERBUFFER,t,l,d);i.framebufferRenderbuffer(i.FRAMEBUFFER,i.DEPTH_ATTACHMENT,i.RENDERBUFFER,e)}else o&&u&&(r>0?i.renderbufferStorageMultisample(i.RENDERBUFFER,r,i.DEPTH24_STENCIL8,l,d):i.renderbufferStorage(i.RENDERBUFFER,i.DEPTH_STENCIL,l,d),i.framebufferRenderbuffer(i.FRAMEBUFFER,i.DEPTH_STENCIL_ATTACHMENT,i.RENDERBUFFER,e));i.bindRenderbuffer(i.RENDERBUFFER,null)}async copyTextureToBuffer(e,t,r,s,i,n){const{backend:a,gl:o}=this,{textureGPU:u,glFormat:l,glType:d}=this.backend.get(e),c=o.createFramebuffer();o.bindFramebuffer(o.READ_FRAMEBUFFER,c);const h=e.isCubeTexture?o.TEXTURE_CUBE_MAP_POSITIVE_X+n:o.TEXTURE_2D;o.framebufferTexture2D(o.READ_FRAMEBUFFER,o.COLOR_ATTACHMENT0,h,u,0);const p=this._getTypedArrayType(d),g=s*i*this._getBytesPerTexel(d,l),m=o.createBuffer();o.bindBuffer(o.PIXEL_PACK_BUFFER,m),o.bufferData(o.PIXEL_PACK_BUFFER,g,o.STREAM_READ),o.readPixels(t,r,s,i,l,d,0),o.bindBuffer(o.PIXEL_PACK_BUFFER,null),await a.utils._clientWaitAsync();const f=new p(g/p.BYTES_PER_ELEMENT);return o.bindBuffer(o.PIXEL_PACK_BUFFER,m),o.getBufferSubData(o.PIXEL_PACK_BUFFER,0,f),o.bindBuffer(o.PIXEL_PACK_BUFFER,null),o.deleteFramebuffer(c),f}_getTypedArrayType(e){const{gl:t}=this;if(e===t.UNSIGNED_BYTE)return Uint8Array;if(e===t.UNSIGNED_SHORT_4_4_4_4)return Uint16Array;if(e===t.UNSIGNED_SHORT_5_5_5_1)return Uint16Array;if(e===t.UNSIGNED_SHORT_5_6_5)return Uint16Array;if(e===t.UNSIGNED_SHORT)return Uint16Array;if(e===t.UNSIGNED_INT)return Uint32Array;if(e===t.HALF_FLOAT)return Uint16Array;if(e===t.FLOAT)return Float32Array;throw new Error(`Unsupported WebGL type: ${e}`)}_getBytesPerTexel(e,t){const{gl:r}=this;let s=0;return e===r.UNSIGNED_BYTE&&(s=1),e!==r.UNSIGNED_SHORT_4_4_4_4&&e!==r.UNSIGNED_SHORT_5_5_5_1&&e!==r.UNSIGNED_SHORT_5_6_5&&e!==r.UNSIGNED_SHORT&&e!==r.HALF_FLOAT||(s=2),e!==r.UNSIGNED_INT&&e!==r.FLOAT||(s=4),t===r.RGBA?4*s:t===r.RGB?3*s:t===r.ALPHA?s:void 0}}function vN(e){return e.isDataTexture?e.image.data:"undefined"!=typeof HTMLImageElement&&e instanceof HTMLImageElement||"undefined"!=typeof HTMLCanvasElement&&e instanceof HTMLCanvasElement||"undefined"!=typeof ImageBitmap&&e instanceof ImageBitmap||"undefined"!=typeof OffscreenCanvas&&e instanceof OffscreenCanvas?e:e.data}class NN{constructor(e){this.backend=e,this.gl=this.backend.gl,this.availableExtensions=this.gl.getSupportedExtensions(),this.extensions={}}get(e){let t=this.extensions[e];return void 0===t&&(t=this.gl.getExtension(e),this.extensions[e]=t),t}has(e){return this.availableExtensions.includes(e)}}class SN{constructor(e){this.backend=e,this.maxAnisotropy=null}getMaxAnisotropy(){if(null!==this.maxAnisotropy)return this.maxAnisotropy;const e=this.backend.gl,t=this.backend.extensions;if(!0===t.has("EXT_texture_filter_anisotropic")){const r=t.get("EXT_texture_filter_anisotropic");this.maxAnisotropy=e.getParameter(r.MAX_TEXTURE_MAX_ANISOTROPY_EXT)}else this.maxAnisotropy=0;return this.maxAnisotropy}}const EN={WEBGL_multi_draw:"WEBGL_multi_draw",WEBGL_compressed_texture_astc:"texture-compression-astc",WEBGL_compressed_texture_etc:"texture-compression-etc2",WEBGL_compressed_texture_etc1:"texture-compression-etc1",WEBGL_compressed_texture_pvrtc:"texture-compression-pvrtc",WEBKIT_WEBGL_compressed_texture_pvrtc:"texture-compression-pvrtc",WEBGL_compressed_texture_s3tc:"texture-compression-bc",EXT_texture_compression_bptc:"texture-compression-bptc",EXT_disjoint_timer_query_webgl2:"timestamp-query",OVR_multiview2:"OVR_multiview2"};class wN{constructor(e){this.gl=e.gl,this.extensions=e.extensions,this.info=e.renderer.info,this.mode=null,this.index=0,this.type=null,this.object=null}render(e,t){const{gl:r,mode:s,object:i,type:n,info:a,index:o}=this;0!==o?r.drawElements(s,t,n,e):r.drawArrays(s,e,t),a.update(i,t,1)}renderInstances(e,t,r){const{gl:s,mode:i,type:n,index:a,object:o,info:u}=this;0!==r&&(0!==a?s.drawElementsInstanced(i,t,n,e,r):s.drawArraysInstanced(i,e,t,r),u.update(o,t,r))}renderMultiDraw(e,t,r){const{extensions:s,mode:i,object:n,info:a}=this;if(0===r)return;const o=s.get("WEBGL_multi_draw");if(null===o)for(let s=0;sthis.maxQueries)return pt(`WebGPUTimestampQueryPool [${this.type}]: Maximum number of queries exceeded, when using trackTimestamp it is necessary to resolves the queries via renderer.resolveTimestampsAsync( THREE.TimestampQuery.${this.type.toUpperCase()} ).`),null;const t=this.currentQueryIndex;return this.currentQueryIndex+=2,this.queryStates.set(t,"inactive"),this.queryOffsets.set(e.id,t),t}beginQuery(e){if(!this.trackTimestamp||this.isDisposed)return;const t=this.queryOffsets.get(e.id);if(null==t)return;if(null!==this.activeQuery)return;const r=this.queries[t];if(r)try{"inactive"===this.queryStates.get(t)&&(this.gl.beginQuery(this.ext.TIME_ELAPSED_EXT,r),this.activeQuery=t,this.queryStates.set(t,"started"))}catch(e){console.error("Error in beginQuery:",e),this.activeQuery=null,this.queryStates.set(t,"inactive")}}endQuery(e){if(!this.trackTimestamp||this.isDisposed)return;const t=this.queryOffsets.get(e.id);if(null!=t&&this.activeQuery===t)try{this.gl.endQuery(this.ext.TIME_ELAPSED_EXT),this.queryStates.set(t,"ended"),this.activeQuery=null}catch(e){console.error("Error in endQuery:",e),this.queryStates.set(t,"inactive"),this.activeQuery=null}}async resolveQueriesAsync(){if(!this.trackTimestamp||this.pendingResolve)return this.lastValue;this.pendingResolve=!0;try{const e=[];for(const[t,r]of this.queryStates)if("ended"===r){const r=this.queries[t];e.push(this.resolveQuery(r))}if(0===e.length)return this.lastValue;const t=(await Promise.all(e)).reduce((e,t)=>e+t,0);return this.lastValue=t,this.currentQueryIndex=0,this.queryOffsets.clear(),this.queryStates.clear(),this.activeQuery=null,t}catch(e){return console.error("Error resolving queries:",e),this.lastValue}finally{this.pendingResolve=!1}}async resolveQuery(e){return new Promise(t=>{if(this.isDisposed)return void t(this.lastValue);let r,s=!1;const i=e=>{s||(s=!0,r&&(clearTimeout(r),r=null),t(e))},n=()=>{if(this.isDisposed)i(this.lastValue);else try{if(this.gl.getParameter(this.ext.GPU_DISJOINT_EXT))return void i(this.lastValue);if(!this.gl.getQueryParameter(e,this.gl.QUERY_RESULT_AVAILABLE))return void(r=setTimeout(n,1));const s=this.gl.getQueryParameter(e,this.gl.QUERY_RESULT);t(Number(s)/1e6)}catch(e){console.error("Error checking query:",e),t(this.lastValue)}};n()})}dispose(){if(!this.isDisposed&&(this.isDisposed=!0,this.trackTimestamp)){for(const e of this.queries)this.gl.deleteQuery(e);this.queries=[],this.queryStates.clear(),this.queryOffsets.clear(),this.lastValue=0,this.activeQuery=null}}}const CN=new t;class MN extends lN{constructor(e={}){super(e),this.isWebGLBackend=!0,this.attributeUtils=null,this.extensions=null,this.capabilities=null,this.textureUtils=null,this.bufferRenderer=null,this.gl=null,this.state=null,this.utils=null,this.vaoCache={},this.transformFeedbackCache={},this.discard=!1,this.disjoint=null,this.parallel=null,this._currentContext=null,this._knownBindings=new WeakSet,this._supportsInvalidateFramebuffer="undefined"!=typeof navigator&&/OculusBrowser/g.test(navigator.userAgent),this._xrFramebuffer=null}init(e){super.init(e);const t=this.parameters,r={antialias:e.samples>0,alpha:!0,depth:e.depth,stencil:e.stencil},s=void 0!==t.context?t.context:e.domElement.getContext("webgl2",r);function i(t){t.preventDefault();const r={api:"WebGL",message:t.statusMessage||"Unknown reason",reason:null,originalEvent:t};e.onDeviceLost(r)}this._onContextLost=i,e.domElement.addEventListener("webglcontextlost",i,!1),this.gl=s,this.extensions=new NN(this),this.capabilities=new SN(this),this.attributeUtils=new gN(this),this.textureUtils=new _N(this),this.bufferRenderer=new wN(this),this.state=new mN(this),this.utils=new fN(this),this.extensions.get("EXT_color_buffer_float"),this.extensions.get("WEBGL_clip_cull_distance"),this.extensions.get("OES_texture_float_linear"),this.extensions.get("EXT_color_buffer_half_float"),this.extensions.get("WEBGL_multisampled_render_to_texture"),this.extensions.get("WEBGL_render_shared_exponent"),this.extensions.get("WEBGL_multi_draw"),this.extensions.get("OVR_multiview2"),this.disjoint=this.extensions.get("EXT_disjoint_timer_query_webgl2"),this.parallel=this.extensions.get("KHR_parallel_shader_compile")}get coordinateSystem(){return l}async getArrayBufferAsync(e){return await this.attributeUtils.getArrayBufferAsync(e)}async waitForGPU(){await this.utils._clientWaitAsync()}async makeXRCompatible(){!0!==this.gl.getContextAttributes().xrCompatible&&await this.gl.makeXRCompatible()}setXRTarget(e){this._xrFramebuffer=e}setXRRenderTargetTextures(e,t,r=null){const s=this.gl;if(this.set(e.texture,{textureGPU:t,glInternalFormat:s.RGBA8}),null!==r){const t=e.stencilBuffer?s.DEPTH24_STENCIL8:s.DEPTH_COMPONENT24;this.set(e.depthTexture,{textureGPU:r,glInternalFormat:t}),!0===this.extensions.has("WEBGL_multisampled_render_to_texture")&&!0===e._autoAllocateDepthBuffer&&!1===e.multiview&&console.warn("THREE.WebGLBackend: Render-to-texture extension was disabled because an external texture was provided"),e._autoAllocateDepthBuffer=!1}}initTimestampQuery(e){if(!this.disjoint||!this.trackTimestamp)return;const t=e.isComputeNode?"compute":"render";this.timestampQueryPool[t]||(this.timestampQueryPool[t]=new RN(this.gl,t,2048));const r=this.timestampQueryPool[t];null!==r.allocateQueriesForContext(e)&&r.beginQuery(e)}prepareTimestampBuffer(e){if(!this.disjoint||!this.trackTimestamp)return;const t=e.isComputeNode?"compute":"render";this.timestampQueryPool[t].endQuery(e)}getContext(){return this.gl}beginRender(e){const{state:t}=this,r=this.get(e);if(e.viewport)this.updateViewport(e);else{const{width:e,height:r}=this.getDrawingBufferSize(CN);t.viewport(0,0,e,r)}if(e.scissor){const{x:r,y:s,width:i,height:n}=e.scissorValue;t.scissor(r,e.height-n-s,i,n)}this.initTimestampQuery(e),r.previousContext=this._currentContext,this._currentContext=e,this._setFramebuffer(e),this.clear(e.clearColor,e.clearDepth,e.clearStencil,e,!1);const s=e.occlusionQueryCount;s>0&&(r.currentOcclusionQueries=r.occlusionQueries,r.currentOcclusionQueryObjects=r.occlusionQueryObjects,r.lastOcclusionObject=null,r.occlusionQueries=new Array(s),r.occlusionQueryObjects=new Array(s),r.occlusionQueryIndex=0)}finishRender(e){const{gl:t,state:r}=this,s=this.get(e),i=s.previousContext;r.resetVertexState();const n=e.occlusionQueryCount;n>0&&(n>s.occlusionQueryIndex&&t.endQuery(t.ANY_SAMPLES_PASSED),this.resolveOccludedAsync(e));const a=e.textures;if(null!==a)for(let e=0;e0&&!1===this._useMultisampledExtension(o)){const i=s.framebuffers[e.getCacheKey()];let n=t.COLOR_BUFFER_BIT;o.resolveDepthBuffer&&(o.depthBuffer&&(n|=t.DEPTH_BUFFER_BIT),o.stencilBuffer&&o.resolveStencilBuffer&&(n|=t.STENCIL_BUFFER_BIT));const a=s.msaaFrameBuffer,u=s.msaaRenderbuffers,l=e.textures,d=l.length>1;if(r.bindFramebuffer(t.READ_FRAMEBUFFER,a),r.bindFramebuffer(t.DRAW_FRAMEBUFFER,i),d)for(let e=0;e{let a=0;for(let t=0;t{t.isBatchedMesh?null!==t._multiDrawInstances?(pt("THREE.WebGLBackend: renderMultiDrawInstances has been deprecated and will be removed in r184. Append to renderMultiDraw arguments and use indirection."),b.renderMultiDrawInstances(t._multiDrawStarts,t._multiDrawCounts,t._multiDrawCount,t._multiDrawInstances)):this.hasFeature("WEBGL_multi_draw")?b.renderMultiDraw(t._multiDrawStarts,t._multiDrawCounts,t._multiDrawCount):pt("THREE.WebGLRenderer: WEBGL_multi_draw not supported."):T>1?b.renderInstances(_,x,T):b.render(_,x)};if(!0===e.camera.isArrayCamera&&e.camera.cameras.length>0&&!1===e.camera.isMultiViewCamera){const r=this.get(e.camera),s=e.camera.cameras,i=e.getBindingGroup("cameraIndex").bindings[0];if(void 0===r.indexesGPU||r.indexesGPU.length!==s.length){const e=new Uint32Array([0,0,0,0]),t=[];for(let r=0,i=s.length;r{const i=this.parallel,n=()=>{r.getProgramParameter(a,i.COMPLETION_STATUS_KHR)?(this._completeCompile(e,s),t()):requestAnimationFrame(n)};n()});return void t.push(i)}this._completeCompile(e,s)}_handleSource(e,t){const r=e.split("\n"),s=[],i=Math.max(t-6,0),n=Math.min(t+6,r.length);for(let e=i;e":" "} ${i}: ${r[e]}`)}return s.join("\n")}_getShaderErrors(e,t,r){const s=e.getShaderParameter(t,e.COMPILE_STATUS),i=e.getShaderInfoLog(t).trim();if(s&&""===i)return"";const n=/ERROR: 0:(\d+)/.exec(i);if(n){const s=parseInt(n[1]);return r.toUpperCase()+"\n\n"+i+"\n\n"+this._handleSource(e.getShaderSource(t),s)}return i}_logProgramError(e,t,r){if(this.renderer.debug.checkShaderErrors){const s=this.gl,i=s.getProgramInfoLog(e).trim();if(!1===s.getProgramParameter(e,s.LINK_STATUS))if("function"==typeof this.renderer.debug.onShaderError)this.renderer.debug.onShaderError(s,e,r,t);else{const n=this._getShaderErrors(s,r,"vertex"),a=this._getShaderErrors(s,t,"fragment");console.error("THREE.WebGLProgram: Shader Error "+s.getError()+" - VALIDATE_STATUS "+s.getProgramParameter(e,s.VALIDATE_STATUS)+"\n\nProgram Info Log: "+i+"\n"+n+"\n"+a)}else""!==i&&console.warn("THREE.WebGLProgram: Program Info Log:",i)}}_completeCompile(e,t){const{state:r,gl:s}=this,i=this.get(t),{programGPU:n,fragmentShader:a,vertexShader:o}=i;!1===s.getProgramParameter(n,s.LINK_STATUS)&&this._logProgramError(n,a,o),r.useProgram(n);const u=e.getBindings();this._setupBindings(u,n),this.set(t,{programGPU:n})}createComputePipeline(e,t){const{state:r,gl:s}=this,i={stage:"fragment",code:"#version 300 es\nprecision highp float;\nvoid main() {}"};this.createProgram(i);const{computeProgram:n}=e,a=s.createProgram(),o=this.get(i).shaderGPU,u=this.get(n).shaderGPU,l=n.transforms,d=[],c=[];for(let e=0;eEN[t]===e),r=this.extensions;for(let e=0;e1,h=!0===i.isXRRenderTarget,p=!0===h&&!0===i._hasExternalTextures;let g=n.msaaFrameBuffer,m=n.depthRenderbuffer;const f=this.extensions.get("WEBGL_multisampled_render_to_texture"),y=this.extensions.get("OVR_multiview2"),b=this._useMultisampledExtension(i),x=Vf(e);let T;if(l?(n.cubeFramebuffers||(n.cubeFramebuffers={}),T=n.cubeFramebuffers[x]):h&&!1===p?T=this._xrFramebuffer:(n.framebuffers||(n.framebuffers={}),T=n.framebuffers[x]),void 0===T){T=t.createFramebuffer(),r.bindFramebuffer(t.FRAMEBUFFER,T);const s=e.textures,o=[];if(l){n.cubeFramebuffers[x]=T;const{textureGPU:e}=this.get(s[0]),r=this.renderer._activeCubeFace;t.framebufferTexture2D(t.FRAMEBUFFER,t.COLOR_ATTACHMENT0,t.TEXTURE_CUBE_MAP_POSITIVE_X+r,e,0)}else{n.framebuffers[x]=T;for(let r=0;r0&&!1===b&&!i.multiview){if(void 0===g){const s=[];g=t.createFramebuffer(),r.bindFramebuffer(t.FRAMEBUFFER,g);const i=[],l=e.textures;for(let r=0;r0&&!0===this.extensions.has("WEBGL_multisampled_render_to_texture")&&!1!==e._autoAllocateDepthBuffer}dispose(){const e=this.extensions.get("WEBGL_lose_context");e&&e.loseContext(),this.renderer.domElement.removeEventListener("webglcontextlost",this._onContextLost)}}const PN="point-list",BN="line-list",FN="line-strip",LN="triangle-list",DN="triangle-strip",IN="never",VN="less",UN="equal",ON="less-equal",kN="greater",GN="not-equal",zN="greater-equal",HN="always",$N="store",WN="load",jN="clear",qN="ccw",XN="none",KN="front",YN="back",QN="uint16",ZN="uint32",JN="r8unorm",eS="r8snorm",tS="r8uint",rS="r8sint",sS="r16uint",iS="r16sint",nS="r16float",aS="rg8unorm",oS="rg8snorm",uS="rg8uint",lS="rg8sint",dS="r32uint",cS="r32sint",hS="r32float",pS="rg16uint",gS="rg16sint",mS="rg16float",fS="rgba8unorm",yS="rgba8unorm-srgb",bS="rgba8snorm",xS="rgba8uint",TS="rgba8sint",_S="bgra8unorm",vS="bgra8unorm-srgb",NS="rgb9e5ufloat",SS="rgb10a2unorm",ES="rgb10a2unorm",wS="rg32uint",AS="rg32sint",RS="rg32float",CS="rgba16uint",MS="rgba16sint",PS="rgba16float",BS="rgba32uint",FS="rgba32sint",LS="rgba32float",DS="depth16unorm",IS="depth24plus",VS="depth24plus-stencil8",US="depth32float",OS="depth32float-stencil8",kS="bc1-rgba-unorm",GS="bc1-rgba-unorm-srgb",zS="bc2-rgba-unorm",HS="bc2-rgba-unorm-srgb",$S="bc3-rgba-unorm",WS="bc3-rgba-unorm-srgb",jS="bc4-r-unorm",qS="bc4-r-snorm",XS="bc5-rg-unorm",KS="bc5-rg-snorm",YS="bc6h-rgb-ufloat",QS="bc6h-rgb-float",ZS="bc7-rgba-unorm",JS="bc7-rgba-srgb",eE="etc2-rgb8unorm",tE="etc2-rgb8unorm-srgb",rE="etc2-rgb8a1unorm",sE="etc2-rgb8a1unorm-srgb",iE="etc2-rgba8unorm",nE="etc2-rgba8unorm-srgb",aE="eac-r11unorm",oE="eac-r11snorm",uE="eac-rg11unorm",lE="eac-rg11snorm",dE="astc-4x4-unorm",cE="astc-4x4-unorm-srgb",hE="astc-5x4-unorm",pE="astc-5x4-unorm-srgb",gE="astc-5x5-unorm",mE="astc-5x5-unorm-srgb",fE="astc-6x5-unorm",yE="astc-6x5-unorm-srgb",bE="astc-6x6-unorm",xE="astc-6x6-unorm-srgb",TE="astc-8x5-unorm",_E="astc-8x5-unorm-srgb",vE="astc-8x6-unorm",NE="astc-8x6-unorm-srgb",SE="astc-8x8-unorm",EE="astc-8x8-unorm-srgb",wE="astc-10x5-unorm",AE="astc-10x5-unorm-srgb",RE="astc-10x6-unorm",CE="astc-10x6-unorm-srgb",ME="astc-10x8-unorm",PE="astc-10x8-unorm-srgb",BE="astc-10x10-unorm",FE="astc-10x10-unorm-srgb",LE="astc-12x10-unorm",DE="astc-12x10-unorm-srgb",IE="astc-12x12-unorm",VE="astc-12x12-unorm-srgb",UE="clamp-to-edge",OE="repeat",kE="mirror-repeat",GE="linear",zE="nearest",HE="zero",$E="one",WE="src",jE="one-minus-src",qE="src-alpha",XE="one-minus-src-alpha",KE="dst",YE="one-minus-dst",QE="dst-alpha",ZE="one-minus-dst-alpha",JE="src-alpha-saturated",ew="constant",tw="one-minus-constant",rw="add",sw="subtract",iw="reverse-subtract",nw="min",aw="max",ow=0,uw=15,lw="keep",dw="zero",cw="replace",hw="invert",pw="increment-clamp",gw="decrement-clamp",mw="increment-wrap",fw="decrement-wrap",yw="storage",bw="read-only-storage",xw="write-only",Tw="read-only",_w="read-write",vw="non-filtering",Nw="comparison",Sw="float",Ew="unfilterable-float",ww="depth",Aw="sint",Rw="uint",Cw="2d",Mw="3d",Pw="2d",Bw="2d-array",Fw="cube",Lw="3d",Dw="all",Iw="vertex",Vw="instance",Uw={CoreFeaturesAndLimits:"core-features-and-limits",DepthClipControl:"depth-clip-control",Depth32FloatStencil8:"depth32float-stencil8",TextureCompressionBC:"texture-compression-bc",TextureCompressionBCSliced3D:"texture-compression-bc-sliced-3d",TextureCompressionETC2:"texture-compression-etc2",TextureCompressionASTC:"texture-compression-astc",TextureCompressionASTCSliced3D:"texture-compression-astc-sliced-3d",TimestampQuery:"timestamp-query",IndirectFirstInstance:"indirect-first-instance",ShaderF16:"shader-f16",RG11B10UFloat:"rg11b10ufloat-renderable",BGRA8UNormStorage:"bgra8unorm-storage",Float32Filterable:"float32-filterable",Float32Blendable:"float32-blendable",ClipDistances:"clip-distances",DualSourceBlending:"dual-source-blending",Subgroups:"subgroups",TextureFormatsTier1:"texture-formats-tier1",TextureFormatsTier2:"texture-formats-tier2"};class Ow extends Gv{constructor(e,t){super(e),this.texture=t,this.version=t?t.version:0,this.isSampler=!0}}class kw extends Ow{constructor(e,t,r){super(e,t?t.value:null),this.textureNode=t,this.groupNode=r}update(){this.texture=this.textureNode.value}}class Gw extends zv{constructor(e,t){super(e,t?t.array:null),this.attribute=t,this.isStorageBuffer=!0}}let zw=0;class Hw extends Gw{constructor(e,t){super("StorageBuffer_"+zw++,e?e.value:null),this.nodeUniform=e,this.access=e?e.access:Os.READ_WRITE,this.groupNode=t}get buffer(){return this.nodeUniform.value}}class $w extends cf{constructor(e){super(),this.device=e;this.mipmapSampler=e.createSampler({minFilter:GE}),this.flipYSampler=e.createSampler({minFilter:zE}),this.transferPipelines={},this.flipYPipelines={},this.mipmapVertexShaderModule=e.createShaderModule({label:"mipmapVertex",code:"\nstruct VarysStruct {\n\t@builtin( position ) Position: vec4,\n\t@location( 0 ) vTex : vec2\n};\n\n@vertex\nfn main( @builtin( vertex_index ) vertexIndex : u32 ) -> VarysStruct {\n\n\tvar Varys : VarysStruct;\n\n\tvar pos = array< vec2, 4 >(\n\t\tvec2( -1.0, 1.0 ),\n\t\tvec2( 1.0, 1.0 ),\n\t\tvec2( -1.0, -1.0 ),\n\t\tvec2( 1.0, -1.0 )\n\t);\n\n\tvar tex = array< vec2, 4 >(\n\t\tvec2( 0.0, 0.0 ),\n\t\tvec2( 1.0, 0.0 ),\n\t\tvec2( 0.0, 1.0 ),\n\t\tvec2( 1.0, 1.0 )\n\t);\n\n\tVarys.vTex = tex[ vertexIndex ];\n\tVarys.Position = vec4( pos[ vertexIndex ], 0.0, 1.0 );\n\n\treturn Varys;\n\n}\n"}),this.mipmapFragmentShaderModule=e.createShaderModule({label:"mipmapFragment",code:"\n@group( 0 ) @binding( 0 )\nvar imgSampler : sampler;\n\n@group( 0 ) @binding( 1 )\nvar img : texture_2d;\n\n@fragment\nfn main( @location( 0 ) vTex : vec2 ) -> @location( 0 ) vec4 {\n\n\treturn textureSample( img, imgSampler, vTex );\n\n}\n"}),this.flipYFragmentShaderModule=e.createShaderModule({label:"flipYFragment",code:"\n@group( 0 ) @binding( 0 )\nvar imgSampler : sampler;\n\n@group( 0 ) @binding( 1 )\nvar img : texture_2d;\n\n@fragment\nfn main( @location( 0 ) vTex : vec2 ) -> @location( 0 ) vec4 {\n\n\treturn textureSample( img, imgSampler, vec2( vTex.x, 1.0 - vTex.y ) );\n\n}\n"})}getTransferPipeline(e){let t=this.transferPipelines[e];return void 0===t&&(t=this.device.createRenderPipeline({label:`mipmap-${e}`,vertex:{module:this.mipmapVertexShaderModule,entryPoint:"main"},fragment:{module:this.mipmapFragmentShaderModule,entryPoint:"main",targets:[{format:e}]},primitive:{topology:DN,stripIndexFormat:ZN},layout:"auto"}),this.transferPipelines[e]=t),t}getFlipYPipeline(e){let t=this.flipYPipelines[e];return void 0===t&&(t=this.device.createRenderPipeline({label:`flipY-${e}`,vertex:{module:this.mipmapVertexShaderModule,entryPoint:"main"},fragment:{module:this.flipYFragmentShaderModule,entryPoint:"main",targets:[{format:e}]},primitive:{topology:DN,stripIndexFormat:ZN},layout:"auto"}),this.flipYPipelines[e]=t),t}flipY(e,t,r=0){const s=t.format,{width:i,height:n}=t.size,a=this.getTransferPipeline(s),o=this.getFlipYPipeline(s),u=this.device.createTexture({size:{width:i,height:n,depthOrArrayLayers:1},format:s,usage:GPUTextureUsage.RENDER_ATTACHMENT|GPUTextureUsage.TEXTURE_BINDING}),l=e.createView({baseMipLevel:0,mipLevelCount:1,dimension:Pw,baseArrayLayer:r}),d=u.createView({baseMipLevel:0,mipLevelCount:1,dimension:Pw,baseArrayLayer:0}),c=this.device.createCommandEncoder({}),h=(e,t,r)=>{const s=e.getBindGroupLayout(0),i=this.device.createBindGroup({layout:s,entries:[{binding:0,resource:this.flipYSampler},{binding:1,resource:t}]}),n=c.beginRenderPass({colorAttachments:[{view:r,loadOp:jN,storeOp:$N,clearValue:[0,0,0,0]}]});n.setPipeline(e),n.setBindGroup(0,i),n.draw(4,1,0,0),n.end()};h(a,l,d),h(o,d,l),this.device.queue.submit([c.finish()]),u.destroy()}generateMipmaps(e,t,r=0){const s=this.get(e);void 0===s.useCount&&(s.useCount=0,s.layers=[]);const i=s.layers[r]||this._mipmapCreateBundles(e,t,r),n=this.device.createCommandEncoder({});this._mipmapRunBundles(n,i),this.device.queue.submit([n.finish()]),0!==s.useCount&&(s.layers[r]=i),s.useCount++}_mipmapCreateBundles(e,t,r){const s=this.getTransferPipeline(t.format),i=s.getBindGroupLayout(0);let n=e.createView({baseMipLevel:0,mipLevelCount:1,dimension:Pw,baseArrayLayer:r});const a=[];for(let o=1;o1;for(let a=0;a]*\s*([a-z_0-9]+(?:<[\s\S]+?>)?)/i,Yw=/([a-z_0-9]+)\s*:\s*([a-z_0-9]+(?:<[\s\S]+?>)?)/gi,Qw={f32:"float",i32:"int",u32:"uint",bool:"bool","vec2":"vec2","vec2":"ivec2","vec2":"uvec2","vec2":"bvec2",vec2f:"vec2",vec2i:"ivec2",vec2u:"uvec2",vec2b:"bvec2","vec3":"vec3","vec3":"ivec3","vec3":"uvec3","vec3":"bvec3",vec3f:"vec3",vec3i:"ivec3",vec3u:"uvec3",vec3b:"bvec3","vec4":"vec4","vec4":"ivec4","vec4":"uvec4","vec4":"bvec4",vec4f:"vec4",vec4i:"ivec4",vec4u:"uvec4",vec4b:"bvec4","mat2x2":"mat2",mat2x2f:"mat2","mat3x3":"mat3",mat3x3f:"mat3","mat4x4":"mat4",mat4x4f:"mat4",sampler:"sampler",texture_1d:"texture",texture_2d:"texture",texture_2d_array:"texture",texture_multisampled_2d:"cubeTexture",texture_depth_2d:"depthTexture",texture_depth_2d_array:"depthTexture",texture_depth_multisampled_2d:"depthTexture",texture_depth_cube:"depthTexture",texture_depth_cube_array:"depthTexture",texture_3d:"texture3D",texture_cube:"cubeTexture",texture_cube_array:"cubeTexture",texture_storage_1d:"storageTexture",texture_storage_2d:"storageTexture",texture_storage_2d_array:"storageTexture",texture_storage_3d:"storageTexture"};class Zw extends iv{constructor(e){const{type:t,inputs:r,name:s,inputsCode:i,blockCode:n,outputType:a}=(e=>{const t=(e=e.trim()).match(Kw);if(null!==t&&4===t.length){const r=t[2],s=[];let i=null;for(;null!==(i=Yw.exec(r));)s.push({name:i[1],type:i[2]});const n=[];for(let e=0;e "+this.outputType:"";return`fn ${e} ( ${this.inputsCode.trim()} ) ${t}`+this.blockCode}}class Jw extends sv{parseFunction(e){return new Zw(e)}}const eA="undefined"!=typeof self?self.GPUShaderStage:{VERTEX:1,FRAGMENT:2,COMPUTE:4},tA={[Os.READ_ONLY]:"read",[Os.WRITE_ONLY]:"write",[Os.READ_WRITE]:"read_write"},rA={[Nr]:"repeat",[vr]:"clamp",[_r]:"mirror"},sA={vertex:eA?eA.VERTEX:1,fragment:eA?eA.FRAGMENT:2,compute:eA?eA.COMPUTE:4},iA={instance:!0,swizzleAssign:!1,storageBuffer:!0},nA={"^^":"tsl_xor"},aA={float:"f32",int:"i32",uint:"u32",bool:"bool",color:"vec3",vec2:"vec2",ivec2:"vec2",uvec2:"vec2",bvec2:"vec2",vec3:"vec3",ivec3:"vec3",uvec3:"vec3",bvec3:"vec3",vec4:"vec4",ivec4:"vec4",uvec4:"vec4",bvec4:"vec4",mat2:"mat2x2",mat3:"mat3x3",mat4:"mat4x4"},oA={},uA={tsl_xor:new Ib("fn tsl_xor( a : bool, b : bool ) -> bool { return ( a || b ) && !( a && b ); }"),mod_float:new Ib("fn tsl_mod_float( x : f32, y : f32 ) -> f32 { return x - y * floor( x / y ); }"),mod_vec2:new Ib("fn tsl_mod_vec2( x : vec2f, y : vec2f ) -> vec2f { return x - y * floor( x / y ); }"),mod_vec3:new Ib("fn tsl_mod_vec3( x : vec3f, y : vec3f ) -> vec3f { return x - y * floor( x / y ); }"),mod_vec4:new Ib("fn tsl_mod_vec4( x : vec4f, y : vec4f ) -> vec4f { return x - y * floor( x / y ); }"),equals_bool:new Ib("fn tsl_equals_bool( a : bool, b : bool ) -> bool { return a == b; }"),equals_bvec2:new Ib("fn tsl_equals_bvec2( a : vec2f, b : vec2f ) -> vec2 { return vec2( a.x == b.x, a.y == b.y ); }"),equals_bvec3:new Ib("fn tsl_equals_bvec3( a : vec3f, b : vec3f ) -> vec3 { return vec3( a.x == b.x, a.y == b.y, a.z == b.z ); }"),equals_bvec4:new Ib("fn tsl_equals_bvec4( a : vec4f, b : vec4f ) -> vec4 { return vec4( a.x == b.x, a.y == b.y, a.z == b.z, a.w == b.w ); }"),repeatWrapping_float:new Ib("fn tsl_repeatWrapping_float( coord: f32 ) -> f32 { return fract( coord ); }"),mirrorWrapping_float:new Ib("fn tsl_mirrorWrapping_float( coord: f32 ) -> f32 { let mirrored = fract( coord * 0.5 ) * 2.0; return 1.0 - abs( 1.0 - mirrored ); }"),clampWrapping_float:new Ib("fn tsl_clampWrapping_float( coord: f32 ) -> f32 { return clamp( coord, 0.0, 1.0 ); }"),biquadraticTexture:new Ib("\nfn tsl_biquadraticTexture( map : texture_2d, coord : vec2f, iRes : vec2u, level : u32 ) -> vec4f {\n\n\tlet res = vec2f( iRes );\n\n\tlet uvScaled = coord * res;\n\tlet uvWrapping = ( ( uvScaled % res ) + res ) % res;\n\n\t// https://www.shadertoy.com/view/WtyXRy\n\n\tlet uv = uvWrapping - 0.5;\n\tlet iuv = floor( uv );\n\tlet f = fract( uv );\n\n\tlet rg1 = textureLoad( map, vec2u( iuv + vec2( 0.5, 0.5 ) ) % iRes, level );\n\tlet rg2 = textureLoad( map, vec2u( iuv + vec2( 1.5, 0.5 ) ) % iRes, level );\n\tlet rg3 = textureLoad( map, vec2u( iuv + vec2( 0.5, 1.5 ) ) % iRes, level );\n\tlet rg4 = textureLoad( map, vec2u( iuv + vec2( 1.5, 1.5 ) ) % iRes, level );\n\n\treturn mix( mix( rg1, rg2, f.x ), mix( rg3, rg4, f.x ), f.y );\n\n}\n")},lA={dFdx:"dpdx",dFdy:"- dpdy",mod_float:"tsl_mod_float",mod_vec2:"tsl_mod_vec2",mod_vec3:"tsl_mod_vec3",mod_vec4:"tsl_mod_vec4",equals_bool:"tsl_equals_bool",equals_bvec2:"tsl_equals_bvec2",equals_bvec3:"tsl_equals_bvec3",equals_bvec4:"tsl_equals_bvec4",inversesqrt:"inverseSqrt",bitcast:"bitcast"};"undefined"!=typeof navigator&&/Windows/g.test(navigator.userAgent)&&(uA.pow_float=new Ib("fn tsl_pow_float( a : f32, b : f32 ) -> f32 { return select( -pow( -a, b ), pow( a, b ), a > 0.0 ); }"),uA.pow_vec2=new Ib("fn tsl_pow_vec2( a : vec2f, b : vec2f ) -> vec2f { return vec2f( tsl_pow_float( a.x, b.x ), tsl_pow_float( a.y, b.y ) ); }",[uA.pow_float]),uA.pow_vec3=new Ib("fn tsl_pow_vec3( a : vec3f, b : vec3f ) -> vec3f { return vec3f( tsl_pow_float( a.x, b.x ), tsl_pow_float( a.y, b.y ), tsl_pow_float( a.z, b.z ) ); }",[uA.pow_float]),uA.pow_vec4=new Ib("fn tsl_pow_vec4( a : vec4f, b : vec4f ) -> vec4f { return vec4f( tsl_pow_float( a.x, b.x ), tsl_pow_float( a.y, b.y ), tsl_pow_float( a.z, b.z ), tsl_pow_float( a.w, b.w ) ); }",[uA.pow_float]),lA.pow_float="tsl_pow_float",lA.pow_vec2="tsl_pow_vec2",lA.pow_vec3="tsl_pow_vec3",lA.pow_vec4="tsl_pow_vec4");let dA="";!0!==("undefined"!=typeof navigator&&/Firefox|Deno/g.test(navigator.userAgent))&&(dA+="diagnostic( off, derivative_uniformity );\n");class cA extends z_{constructor(e,t){super(e,t,new Jw),this.uniformGroups={},this.builtins={},this.directives={},this.scopedArrays=new Map}needsToWorkingColorSpace(e){return!0===e.isVideoTexture&&e.colorSpace!==b}_generateTextureSample(e,t,r,s,i=this.shaderStage){return"fragment"===i?s?`textureSample( ${t}, ${t}_sampler, ${r}, ${s} )`:`textureSample( ${t}, ${t}_sampler, ${r} )`:this._generateTextureSampleLevel(e,t,r,"0",s)}_generateVideoSample(e,t,r=this.shaderStage){if("fragment"===r)return`textureSampleBaseClampToEdge( ${e}, ${e}_sampler, vec2( ${t}.x, 1.0 - ${t}.y ) )`;console.error(`WebGPURenderer: THREE.VideoTexture does not support ${r} shader.`)}_generateTextureSampleLevel(e,t,r,s,i){return!1===this.isUnfilterable(e)?`textureSampleLevel( ${t}, ${t}_sampler, ${r}, ${s} )`:this.isFilteredTexture(e)?this.generateFilteredTexture(e,t,r,s):this.generateTextureLod(e,t,r,i,s)}generateWrapFunction(e){const t=`tsl_coord_${rA[e.wrapS]}S_${rA[e.wrapT]}_${e.isData3DTexture?"3d":"2d"}T`;let r=oA[t];if(void 0===r){const s=[],i=e.isData3DTexture?"vec3f":"vec2f";let n=`fn ${t}( coord : ${i} ) -> ${i} {\n\n\treturn ${i}(\n`;const a=(e,t)=>{e===Nr?(s.push(uA.repeatWrapping_float),n+=`\t\ttsl_repeatWrapping_float( coord.${t} )`):e===vr?(s.push(uA.clampWrapping_float),n+=`\t\ttsl_clampWrapping_float( coord.${t} )`):e===_r?(s.push(uA.mirrorWrapping_float),n+=`\t\ttsl_mirrorWrapping_float( coord.${t} )`):(n+=`\t\tcoord.${t}`,console.warn(`WebGPURenderer: Unsupported texture wrap type "${e}" for vertex shader.`))};a(e.wrapS,"x"),n+=",\n",a(e.wrapT,"y"),e.isData3DTexture&&(n+=",\n",a(e.wrapR,"z")),n+="\n\t);\n\n}\n",oA[t]=r=new Ib(n,s)}return r.build(this),t}generateArrayDeclaration(e,t){return`array< ${this.getType(e)}, ${t} >`}generateTextureDimension(e,t,r){const s=this.getDataFromNode(e,this.shaderStage,this.globalCache);void 0===s.dimensionsSnippet&&(s.dimensionsSnippet={});let i=s.dimensionsSnippet[r];if(void 0===s.dimensionsSnippet[r]){let n,a;const{primarySamples:o}=this.renderer.backend.utils.getTextureSampleData(e),u=o>1;a=e.isData3DTexture?"vec3":"vec2",n=u||e.isVideoTexture||e.isStorageTexture?t:`${t}${r?`, u32( ${r} )`:""}`,i=new Zo(new Du(`textureDimensions( ${n} )`,a)),s.dimensionsSnippet[r]=i,(e.isArrayTexture||e.isDataArrayTexture||e.isData3DTexture)&&(s.arrayLayerCount=new Zo(new Du(`textureNumLayers(${t})`,"u32"))),e.isTextureCube&&(s.cubeFaceCount=new Zo(new Du("6u","u32")))}return i.build(this)}generateFilteredTexture(e,t,r,s="0u"){this._include("biquadraticTexture");return`tsl_biquadraticTexture( ${t}, ${this.generateWrapFunction(e)}( ${r} ), ${this.generateTextureDimension(e,t,s)}, u32( ${s} ) )`}generateTextureLod(e,t,r,s,i="0u"){const n=this.generateWrapFunction(e),a=this.generateTextureDimension(e,t,i),o=e.isData3DTexture?"vec3":"vec2",u=`${o}( ${n}( ${r} ) * ${o}( ${a} ) )`;return this.generateTextureLoad(e,t,u,s,i)}generateTextureLoad(e,t,r,s,i="0u"){let n;return!0===e.isVideoTexture?n=`textureLoad( ${t}, ${r} )`:s?n=`textureLoad( ${t}, ${r}, ${s}, u32( ${i} ) )`:(n=`textureLoad( ${t}, ${r}, u32( ${i} ) )`,this.renderer.backend.compatibilityMode&&e.isDepthTexture&&(n+=".x")),n}generateTextureStore(e,t,r,s,i){let n;return n=s?`textureStore( ${t}, ${r}, ${s}, ${i} )`:`textureStore( ${t}, ${r}, ${i} )`,n}isSampleCompare(e){return!0===e.isDepthTexture&&null!==e.compareFunction}isUnfilterable(e){return"float"!==this.getComponentTypeFromTexture(e)||!this.isAvailable("float32Filterable")&&!0===e.isDataTexture&&e.type===D||!1===this.isSampleCompare(e)&&e.minFilter===v&&e.magFilter===v||this.renderer.backend.utils.getTextureSampleData(e).primarySamples>1}generateTexture(e,t,r,s,i=this.shaderStage){let n=null;return n=!0===e.isVideoTexture?this._generateVideoSample(t,r,i):this.isUnfilterable(e)?this.generateTextureLod(e,t,r,s,"0",i):this._generateTextureSample(e,t,r,s,i),n}generateTextureGrad(e,t,r,s,i,n=this.shaderStage){if("fragment"===n)return`textureSampleGrad( ${t}, ${t}_sampler, ${r}, ${s[0]}, ${s[1]} )`;console.error(`WebGPURenderer: THREE.TextureNode.gradient() does not support ${n} shader.`)}generateTextureCompare(e,t,r,s,i,n=this.shaderStage){if("fragment"===n)return!0===e.isDepthTexture&&!0===e.isArrayTexture?`textureSampleCompare( ${t}, ${t}_sampler, ${r}, ${i}, ${s} )`:`textureSampleCompare( ${t}, ${t}_sampler, ${r}, ${s} )`;console.error(`WebGPURenderer: THREE.DepthTexture.compareFunction() does not support ${n} shader.`)}generateTextureLevel(e,t,r,s,i,n=this.shaderStage){let a=null;return a=!0===e.isVideoTexture?this._generateVideoSample(t,r,n):this._generateTextureSampleLevel(e,t,r,s,i),a}generateTextureBias(e,t,r,s,i,n=this.shaderStage){if("fragment"===n)return`textureSampleBias( ${t}, ${t}_sampler, ${r}, ${s} )`;console.error(`WebGPURenderer: THREE.TextureNode.biasNode does not support ${n} shader.`)}getPropertyName(e,t=this.shaderStage){if(!0===e.isNodeVarying&&!0===e.needsInterpolation){if("vertex"===t)return`varyings.${e.name}`}else if(!0===e.isNodeUniform){const t=e.name,r=e.type;return"texture"===r||"cubeTexture"===r||"storageTexture"===r||"texture3D"===r?t:"buffer"===r||"storageBuffer"===r||"indirectStorageBuffer"===r?this.isCustomStruct(e)?t:t+".value":e.groupNode.name+"."+t}return super.getPropertyName(e)}getOutputStructName(){return"output"}getFunctionOperator(e){const t=nA[e];return void 0!==t?(this._include(t),t):null}getNodeAccess(e,t){return"compute"!==t?Os.READ_ONLY:e.access}getStorageAccess(e,t){return tA[this.getNodeAccess(e,t)]}getUniformFromNode(e,t,r,s=null){const i=super.getUniformFromNode(e,t,r,s),n=this.getDataFromNode(e,r,this.globalCache);if(void 0===n.uniformGPU){let a;const o=e.groupNode,u=o.name,l=this.getBindGroupArray(u,r);if("texture"===t||"cubeTexture"===t||"storageTexture"===t||"texture3D"===t){let s=null;const n=this.getNodeAccess(e,r);if("texture"===t||"storageTexture"===t?s=new Qv(i.name,i.node,o,n):"cubeTexture"===t?s=new Zv(i.name,i.node,o,n):"texture3D"===t&&(s=new Jv(i.name,i.node,o,n)),s.store=!0===e.isStorageTextureNode,s.setVisibility(sA[r]),!1===this.isUnfilterable(e.value)&&!1===s.store){const e=new kw(`${i.name}_sampler`,i.node,o);e.setVisibility(sA[r]),l.push(e,s),a=[e,s]}else l.push(s),a=[s]}else if("buffer"===t||"storageBuffer"===t||"indirectStorageBuffer"===t){const n=new("buffer"===t?Wv:Hw)(e,o);n.setVisibility(sA[r]),l.push(n),a=n,i.name=s||"NodeBuffer_"+i.id}else{const e=this.uniformGroups[r]||(this.uniformGroups[r]={});let s=e[u];void 0===s&&(s=new Xv(u,o),s.setVisibility(sA[r]),e[u]=s,l.push(s)),a=this.getNodeUniform(i,t),s.addUniform(a)}n.uniformGPU=a}return i}getBuiltin(e,t,r,s=this.shaderStage){const i=this.builtins[s]||(this.builtins[s]=new Map);return!1===i.has(e)&&i.set(e,{name:e,property:t,type:r}),t}hasBuiltin(e,t=this.shaderStage){return void 0!==this.builtins[t]&&this.builtins[t].has(e)}getVertexIndex(){return"vertex"===this.shaderStage?this.getBuiltin("vertex_index","vertexIndex","u32","attribute"):"vertexIndex"}buildFunctionCode(e){const t=e.layout,r=this.flowShaderNode(e),s=[];for(const e of t.inputs)s.push(e.name+" : "+this.getType(e.type));let i=`fn ${t.name}( ${s.join(", ")} ) -> ${this.getType(t.type)} {\n${r.vars}\n${r.code}\n`;return r.result&&(i+=`\treturn ${r.result};\n`),i+="\n}\n",i}getInstanceIndex(){return"vertex"===this.shaderStage?this.getBuiltin("instance_index","instanceIndex","u32","attribute"):"instanceIndex"}getInvocationLocalIndex(){return this.getBuiltin("local_invocation_index","invocationLocalIndex","u32","attribute")}getSubgroupSize(){return this.enableSubGroups(),this.getBuiltin("subgroup_size","subgroupSize","u32","attribute")}getInvocationSubgroupIndex(){return this.enableSubGroups(),this.getBuiltin("subgroup_invocation_id","invocationSubgroupIndex","u32","attribute")}getSubgroupIndex(){return this.enableSubGroups(),this.getBuiltin("subgroup_id","subgroupIndex","u32","attribute")}getDrawIndex(){return null}getFrontFacing(){return this.getBuiltin("front_facing","isFront","bool")}getFragCoord(){return this.getBuiltin("position","fragCoord","vec4")+".xy"}getFragDepth(){return"output."+this.getBuiltin("frag_depth","depth","f32","output")}getClipDistance(){return"varyings.hw_clip_distances"}isFlipY(){return!1}enableDirective(e,t=this.shaderStage){(this.directives[t]||(this.directives[t]=new Set)).add(e)}getDirectives(e){const t=[],r=this.directives[e];if(void 0!==r)for(const e of r)t.push(`enable ${e};`);return t.join("\n")}enableSubGroups(){this.enableDirective("subgroups")}enableSubgroupsF16(){this.enableDirective("subgroups-f16")}enableClipDistances(){this.enableDirective("clip_distances")}enableShaderF16(){this.enableDirective("f16")}enableDualSourceBlending(){this.enableDirective("dual_source_blending")}enableHardwareClipping(e){this.enableClipDistances(),this.getBuiltin("clip_distances","hw_clip_distances",`array`,"vertex")}getBuiltins(e){const t=[],r=this.builtins[e];if(void 0!==r)for(const{name:e,property:s,type:i}of r.values())t.push(`@builtin( ${e} ) ${s} : ${i}`);return t.join(",\n\t")}getScopedArray(e,t,r,s){return!1===this.scopedArrays.has(e)&&this.scopedArrays.set(e,{name:e,scope:t,bufferType:r,bufferCount:s}),e}getScopedArrays(e){if("compute"!==e)return;const t=[];for(const{name:e,scope:r,bufferType:s,bufferCount:i}of this.scopedArrays.values()){const n=this.getType(s);t.push(`var<${r}> ${e}: array< ${n}, ${i} >;`)}return t.join("\n")}getAttributes(e){const t=[];if("compute"===e&&(this.getBuiltin("global_invocation_id","globalId","vec3","attribute"),this.getBuiltin("workgroup_id","workgroupId","vec3","attribute"),this.getBuiltin("local_invocation_id","localId","vec3","attribute"),this.getBuiltin("num_workgroups","numWorkgroups","vec3","attribute"),this.renderer.hasFeature("subgroups")&&(this.enableDirective("subgroups",e),this.getBuiltin("subgroup_size","subgroupSize","u32","attribute"))),"vertex"===e||"compute"===e){const e=this.getBuiltins("attribute");e&&t.push(e);const r=this.getAttributesArray();for(let e=0,s=r.length;e"),t.push(`\t${s+r.name} : ${i}`)}return e.output&&t.push(`\t${this.getBuiltins("output")}`),t.join(",\n")}getStructs(e){let t="";const r=this.structs[e];if(r.length>0){const e=[];for(const t of r){let r=`struct ${t.name} {\n`;r+=this.getStructMembers(t),r+="\n};",e.push(r)}t="\n"+e.join("\n\n")+"\n"}return t}getVar(e,t,r=null){let s=`var ${t} : `;return s+=null!==r?this.generateArrayDeclaration(e,r):this.getType(e),s}getVars(e){const t=[],r=this.vars[e];if(void 0!==r)for(const e of r)t.push(`\t${this.getVar(e.type,e.name,e.count)};`);return`\n${t.join("\n")}\n`}getVaryings(e){const t=[];if("vertex"===e&&this.getBuiltin("position","Vertex","vec4","vertex"),"vertex"===e||"fragment"===e){const r=this.varyings,s=this.vars[e];for(let i=0;ir.value.itemSize;return s&&!i}getUniforms(e){const t=this.uniforms[e],r=[],s=[],i=[],n={};for(const i of t){const t=i.groupNode.name,a=this.bindingsIndexes[t];if("texture"===i.type||"cubeTexture"===i.type||"storageTexture"===i.type||"texture3D"===i.type){const t=i.node.value;let s;!1===this.isUnfilterable(t)&&!0!==i.node.isStorageTextureNode&&(this.isSampleCompare(t)?r.push(`@binding( ${a.binding++} ) @group( ${a.group} ) var ${i.name}_sampler : sampler_comparison;`):r.push(`@binding( ${a.binding++} ) @group( ${a.group} ) var ${i.name}_sampler : sampler;`));let n="";const{primarySamples:o}=this.renderer.backend.utils.getTextureSampleData(t);if(o>1&&(n="_multisampled"),!0===t.isCubeTexture)s="texture_cube";else if(!0===t.isDepthTexture)s=this.renderer.backend.compatibilityMode&&null===t.compareFunction?`texture${n}_2d`:`texture_depth${n}_2d${!0===t.isArrayTexture?"_array":""}`;else if(!0===i.node.isStorageTextureNode){const r=Xw(t),n=this.getStorageAccess(i.node,e),a=i.node.value.is3DTexture,o=i.node.value.isArrayTexture;s=`texture_storage_${a?"3d":"2d"+(o?"_array":"")}<${r}, ${n}>`}else if(!0===t.isArrayTexture||!0===t.isDataArrayTexture||!0===t.isCompressedArrayTexture)s="texture_2d_array";else if(!0===t.is3DTexture||!0===t.isData3DTexture)s="texture_3d";else if(!0===t.isVideoTexture)s="texture_external";else{s=`texture${n}_2d<${this.getComponentTypeFromTexture(t).charAt(0)}32>`}r.push(`@binding( ${a.binding++} ) @group( ${a.group} ) var ${i.name} : ${s};`)}else if("buffer"===i.type||"storageBuffer"===i.type||"indirectStorageBuffer"===i.type){const t=i.node,r=this.getType(t.getNodeType(this)),n=t.bufferCount,o=n>0&&"buffer"===i.type?", "+n:"",u=t.isStorageBufferNode?`storage, ${this.getStorageAccess(t,e)}`:"uniform";if(this.isCustomStruct(i))s.push(`@binding( ${a.binding++} ) @group( ${a.group} ) var<${u}> ${i.name} : ${r};`);else{const e=`\tvalue : array< ${t.isAtomic?`atomic<${r}>`:`${r}`}${o} >`;s.push(this._getWGSLStructBinding(i.name,e,u,a.binding++,a.group))}}else{const e=this.getType(this.getVectorType(i.type)),t=i.groupNode.name;(n[t]||(n[t]={index:a.binding++,id:a.group,snippets:[]})).snippets.push(`\t${i.name} : ${e}`)}}for(const e in n){const t=n[e];i.push(this._getWGSLStructBinding(e,t.snippets.join(",\n"),"uniform",t.index,t.id))}let a=r.join("\n");return a+=s.join("\n"),a+=i.join("\n"),a}buildCode(){const e=null!==this.material?{fragment:{},vertex:{}}:{compute:{}};this.sortBindingGroups();for(const t in e){this.shaderStage=t;const r=e[t];r.uniforms=this.getUniforms(t),r.attributes=this.getAttributes(t),r.varyings=this.getVaryings(t),r.structs=this.getStructs(t),r.vars=this.getVars(t),r.codes=this.getCodes(t),r.directives=this.getDirectives(t),r.scopedArrays=this.getScopedArrays(t);let s="// code\n\n";s+=this.flowCode[t];const i=this.flowNodes[t],n=i[i.length-1],a=n.outputNode,o=void 0!==a&&!0===a.isOutputStructNode;for(const e of i){const i=this.getFlowData(e),u=e.name;if(u&&(s.length>0&&(s+="\n"),s+=`\t// flow -> ${u}\n`),s+=`${i.code}\n\t`,e===n&&"compute"!==t)if(s+="// result\n\n\t","vertex"===t)s+=`varyings.Vertex = ${i.result};`;else if("fragment"===t)if(o)r.returnType=a.getNodeType(this),r.structs+="var output : "+r.returnType+";",s+=`return ${i.result};`;else{let e="\t@location(0) color: vec4";const t=this.getBuiltins("output");t&&(e+=",\n\t"+t),r.returnType="OutputStruct",r.structs+=this._getWGSLStruct("OutputStruct",e),r.structs+="\nvar output : OutputStruct;",s+=`output.color = ${i.result};\n\n\treturn output;`}}r.flow=s}this.shaderStage=null,null!==this.material?(this.vertexShader=this._getWGSLVertexCode(e.vertex),this.fragmentShader=this._getWGSLFragmentCode(e.fragment)):this.computeShader=this._getWGSLComputeCode(e.compute,(this.object.workgroupSize||[64]).join(", "))}getMethod(e,t=null){let r;return null!==t&&(r=this._getWGSLMethod(e+"_"+t)),void 0===r&&(r=this._getWGSLMethod(e)),r||e}getType(e){return aA[e]||e}isAvailable(e){let t=iA[e];return void 0===t&&("float32Filterable"===e?t=this.renderer.hasFeature("float32-filterable"):"clipDistance"===e&&(t=this.renderer.hasFeature("clip-distances")),iA[e]=t),t}_getWGSLMethod(e){return void 0!==uA[e]&&this._include(e),lA[e]}_include(e){const t=uA[e];return t.build(this),null!==this.currentFunctionNode&&this.currentFunctionNode.includes.push(t),t}_getWGSLVertexCode(e){return`${this.getSignature()}\n// directives\n${e.directives}\n\n// structs\n${e.structs}\n\n// uniforms\n${e.uniforms}\n\n// varyings\n${e.varyings}\nvar varyings : VaryingsStruct;\n\n// codes\n${e.codes}\n\n@vertex\nfn main( ${e.attributes} ) -> VaryingsStruct {\n\n\t// vars\n\t${e.vars}\n\n\t// flow\n\t${e.flow}\n\n\treturn varyings;\n\n}\n`}_getWGSLFragmentCode(e){return`${this.getSignature()}\n// global\n${dA}\n\n// structs\n${e.structs}\n\n// uniforms\n${e.uniforms}\n\n// codes\n${e.codes}\n\n@fragment\nfn main( ${e.varyings} ) -> ${e.returnType} {\n\n\t// vars\n\t${e.vars}\n\n\t// flow\n\t${e.flow}\n\n}\n`}_getWGSLComputeCode(e,t){return`${this.getSignature()}\n// directives\n${e.directives}\n\n// system\nvar instanceIndex : u32;\n\n// locals\n${e.scopedArrays}\n\n// structs\n${e.structs}\n\n// uniforms\n${e.uniforms}\n\n// codes\n${e.codes}\n\n@compute @workgroup_size( ${t} )\nfn main( ${e.attributes} ) {\n\n\t// system\n\tinstanceIndex = globalId.x + globalId.y * numWorkgroups.x * u32(${t}) + globalId.z * numWorkgroups.x * numWorkgroups.y * u32(${t});\n\n\t// vars\n\t${e.vars}\n\n\t// flow\n\t${e.flow}\n\n}\n`}_getWGSLStruct(e,t){return`\nstruct ${e} {\n${t}\n};`}_getWGSLStructBinding(e,t,r,s=0,i=0){const n=e+"Struct";return`${this._getWGSLStruct(n,t)}\n@binding( ${s} ) @group( ${i} )\nvar<${r}> ${e} : ${n};`}}class hA{constructor(e){this.backend=e}getCurrentDepthStencilFormat(e){let t;return null!==e.depthTexture?t=this.getTextureFormatGPU(e.depthTexture):e.depth&&e.stencil?t=VS:e.depth&&(t=IS),t}getTextureFormatGPU(e){return this.backend.get(e).format}getTextureSampleData(e){let t;if(e.isFramebufferTexture)t=1;else if(e.isDepthTexture&&!e.renderTarget){const e=this.backend.renderer,r=e.getRenderTarget();t=r?r.samples:e.samples}else e.renderTarget&&(t=e.renderTarget.samples);t=t||1;const r=t>1&&null!==e.renderTarget&&!0!==e.isDepthTexture&&!0!==e.isFramebufferTexture;return{samples:t,primarySamples:r?1:t,isMSAA:r}}getCurrentColorFormat(e){let t;return t=null!==e.textures?this.getTextureFormatGPU(e.textures[0]):this.getPreferredCanvasFormat(),t}getCurrentColorSpace(e){return null!==e.textures?e.textures[0].colorSpace:this.backend.renderer.outputColorSpace}getPrimitiveTopology(e,t){return e.isPoints?PN:e.isLineSegments||e.isMesh&&!0===t.wireframe?BN:e.isLine?FN:e.isMesh?LN:void 0}getSampleCount(e){let t=1;return e>1&&(t=Math.pow(2,Math.floor(Math.log2(e))),2===t&&(t=4)),t}getSampleCountRenderContext(e){return null!==e.textures?this.getSampleCount(e.sampleCount):this.getSampleCount(this.backend.renderer.samples)}getPreferredCanvasFormat(){const e=this.backend.parameters.outputType;if(void 0===e)return navigator.gpu.getPreferredCanvasFormat();if(e===Ce)return _S;if(e===ce)return PS;throw new Error("Unsupported outputType")}}const pA=new Map([[Int8Array,["sint8","snorm8"]],[Uint8Array,["uint8","unorm8"]],[Int16Array,["sint16","snorm16"]],[Uint16Array,["uint16","unorm16"]],[Int32Array,["sint32","snorm32"]],[Uint32Array,["uint32","unorm32"]],[Float32Array,["float32"]]]);"undefined"!=typeof Float16Array&&pA.set(Float16Array,["float16"]);const gA=new Map([[ze,["float16"]]]),mA=new Map([[Int32Array,"sint32"],[Int16Array,"sint32"],[Uint32Array,"uint32"],[Uint16Array,"uint32"],[Float32Array,"float32"]]);class fA{constructor(e){this.backend=e}createAttribute(e,t){const r=this._getBufferAttribute(e),s=this.backend,i=s.get(r);let n=i.buffer;if(void 0===n){const a=s.device;let o=r.array;if(!1===e.normalized)if(o.constructor===Int16Array||o.constructor===Int8Array)o=new Int32Array(o);else if((o.constructor===Uint16Array||o.constructor===Uint8Array)&&(o=new Uint32Array(o),t&GPUBufferUsage.INDEX))for(let e=0;e1&&(s.multisampled=!0,r.texture.isDepthTexture||(s.sampleType=Ew)),r.texture.isDepthTexture)t.compatibilityMode&&null===r.texture.compareFunction?s.sampleType=Ew:s.sampleType=ww;else if(r.texture.isDataTexture||r.texture.isDataArrayTexture||r.texture.isData3DTexture){const e=r.texture.type;e===_?s.sampleType=Aw:e===T?s.sampleType=Rw:e===D&&(this.backend.hasFeature("float32-filterable")?s.sampleType=Sw:s.sampleType=Ew)}r.isSampledCubeTexture?s.viewDimension=Fw:r.texture.isArrayTexture||r.texture.isDataArrayTexture||r.texture.isCompressedArrayTexture?s.viewDimension=Bw:r.isSampledTexture3D&&(s.viewDimension=Lw),e.texture=s}else console.error(`WebGPUBindingUtils: Unsupported binding "${r}".`);s.push(e)}return r.createBindGroupLayout({entries:s})}createBindings(e,t,r,s=0){const{backend:i,bindGroupLayoutCache:n}=this,a=i.get(e);let o,u=n.get(e.bindingsReference);void 0===u&&(u=this.createBindingsLayout(e),n.set(e.bindingsReference,u)),r>0&&(void 0===a.groups&&(a.groups=[],a.versions=[]),a.versions[r]===s&&(o=a.groups[r])),void 0===o&&(o=this.createBindGroup(e,u),r>0&&(a.groups[r]=o,a.versions[r]=s)),a.group=o,a.layout=u}updateBinding(e){const t=this.backend,r=t.device,s=e.buffer,i=t.get(e).buffer;r.queue.writeBuffer(i,0,s,0)}createBindGroupIndex(e,t){const r=this.backend.device,s=GPUBufferUsage.UNIFORM|GPUBufferUsage.COPY_DST,i=e[0],n=r.createBuffer({label:"bindingCameraIndex_"+i,size:16,usage:s});r.queue.writeBuffer(n,0,e,0);const a=[{binding:0,resource:{buffer:n}}];return r.createBindGroup({label:"bindGroupCameraIndex_"+i,layout:t,entries:a})}createBindGroup(e,t){const r=this.backend,s=r.device;let i=0;const n=[];for(const t of e.bindings){if(t.isUniformBuffer){const e=r.get(t);if(void 0===e.buffer){const r=t.byteLength,i=GPUBufferUsage.UNIFORM|GPUBufferUsage.COPY_DST,n=s.createBuffer({label:"bindingBuffer_"+t.name,size:r,usage:i});e.buffer=n}n.push({binding:i,resource:{buffer:e.buffer}})}else if(t.isStorageBuffer){const e=r.get(t);if(void 0===e.buffer){const s=t.attribute;e.buffer=r.get(s).buffer}n.push({binding:i,resource:{buffer:e.buffer}})}else if(t.isSampler){const e=r.get(t.texture);n.push({binding:i,resource:e.sampler})}else if(t.isSampledTexture){const e=r.get(t.texture);let a;if(void 0!==e.externalTexture)a=s.importExternalTexture({source:e.externalTexture});else{const r=t.store?1:e.texture.mipLevelCount,s=`view-${e.texture.width}-${e.texture.height}-${r}`;if(a=e[s],void 0===a){const i=Dw;let n;n=t.isSampledCubeTexture?Fw:t.isSampledTexture3D?Lw:t.texture.isArrayTexture||t.texture.isDataArrayTexture||t.texture.isCompressedArrayTexture?Bw:Pw,a=e[s]=e.texture.createView({aspect:i,dimension:n,mipLevelCount:r})}}n.push({binding:i,resource:a})}i++}return s.createBindGroup({label:"bindGroup_"+e.name,layout:t,entries:n})}}class bA{constructor(e){this.backend=e,this._activePipelines=new WeakMap}setPipeline(e,t){this._activePipelines.get(e)!==t&&(e.setPipeline(t),this._activePipelines.set(e,t))}_getSampleCount(e){return this.backend.utils.getSampleCountRenderContext(e)}createRenderPipeline(e,t){const{object:r,material:s,geometry:i,pipeline:n}=e,{vertexProgram:a,fragmentProgram:o}=n,u=this.backend,l=u.device,d=u.utils,c=u.get(n),h=[];for(const t of e.getBindings()){const e=u.get(t);h.push(e.layout)}const p=u.attributeUtils.createShaderVertexBuffers(e);let g;s.blending===H||s.blending===k&&!1===s.transparent||(g=this._getBlending(s));let m={};!0===s.stencilWrite&&(m={compare:this._getStencilCompare(s),failOp:this._getStencilOperation(s.stencilFail),depthFailOp:this._getStencilOperation(s.stencilZFail),passOp:this._getStencilOperation(s.stencilZPass)});const f=this._getColorWriteMask(s),y=[];if(null!==e.context.textures){const t=e.context.textures;for(let e=0;e1},layout:l.createPipelineLayout({bindGroupLayouts:h})},E={},w=e.context.depth,A=e.context.stencil;if(!0!==w&&!0!==A||(!0===w&&(E.format=v,E.depthWriteEnabled=s.depthWrite,E.depthCompare=_),!0===A&&(E.stencilFront=m,E.stencilBack={},E.stencilReadMask=s.stencilFuncMask,E.stencilWriteMask=s.stencilWriteMask),!0===s.polygonOffset&&(E.depthBias=s.polygonOffsetUnits,E.depthBiasSlopeScale=s.polygonOffsetFactor,E.depthBiasClamp=0),S.depthStencil=E),null===t)c.pipeline=l.createRenderPipeline(S);else{const e=new Promise(e=>{l.createRenderPipelineAsync(S).then(t=>{c.pipeline=t,e()})});t.push(e)}}createBundleEncoder(e,t="renderBundleEncoder"){const r=this.backend,{utils:s,device:i}=r,n=s.getCurrentDepthStencilFormat(e),a={label:t,colorFormats:[s.getCurrentColorFormat(e)],depthStencilFormat:n,sampleCount:this._getSampleCount(e)};return i.createRenderBundleEncoder(a)}createComputePipeline(e,t){const r=this.backend,s=r.device,i=r.get(e.computeProgram).module,n=r.get(e),a=[];for(const e of t){const t=r.get(e);a.push(t.layout)}n.pipeline=s.createComputePipeline({compute:i,layout:s.createPipelineLayout({bindGroupLayouts:a})})}_getBlending(e){let t,r;const s=e.blending,i=e.blendSrc,n=e.blendDst,a=e.blendEquation;if(s===qe){const s=null!==e.blendSrcAlpha?e.blendSrcAlpha:i,o=null!==e.blendDstAlpha?e.blendDstAlpha:n,u=null!==e.blendEquationAlpha?e.blendEquationAlpha:a;t={srcFactor:this._getBlendFactor(i),dstFactor:this._getBlendFactor(n),operation:this._getBlendOperation(a)},r={srcFactor:this._getBlendFactor(s),dstFactor:this._getBlendFactor(o),operation:this._getBlendOperation(u)}}else{const i=(e,s,i,n)=>{t={srcFactor:e,dstFactor:s,operation:rw},r={srcFactor:i,dstFactor:n,operation:rw}};if(e.premultipliedAlpha)switch(s){case k:i($E,XE,$E,XE);break;case Bt:i($E,$E,$E,$E);break;case Pt:i(HE,jE,HE,$E);break;case Mt:i(KE,XE,HE,$E)}else switch(s){case k:i(qE,XE,$E,XE);break;case Bt:i(qE,$E,$E,$E);break;case Pt:console.error("THREE.WebGPURenderer: SubtractiveBlending requires material.premultipliedAlpha = true");break;case Mt:console.error("THREE.WebGPURenderer: MultiplyBlending requires material.premultipliedAlpha = true")}}if(void 0!==t&&void 0!==r)return{color:t,alpha:r};console.error("THREE.WebGPURenderer: Invalid blending: ",s)}_getBlendFactor(e){let t;switch(e){case Ke:t=HE;break;case wt:t=$E;break;case Et:t=WE;break;case Tt:t=jE;break;case St:t=qE;break;case xt:t=XE;break;case vt:t=KE;break;case bt:t=YE;break;case _t:t=QE;break;case yt:t=ZE;break;case Nt:t=JE;break;case 211:t=ew;break;case 212:t=tw;break;default:console.error("THREE.WebGPURenderer: Blend factor not supported.",e)}return t}_getStencilCompare(e){let t;const r=e.stencilFunc;switch(r){case kr:t=IN;break;case Or:t=HN;break;case Ur:t=VN;break;case Vr:t=ON;break;case Ir:t=UN;break;case Dr:t=zN;break;case Lr:t=kN;break;case Fr:t=GN;break;default:console.error("THREE.WebGPURenderer: Invalid stencil function.",r)}return t}_getStencilOperation(e){let t;switch(e){case Xr:t=lw;break;case qr:t=dw;break;case jr:t=cw;break;case Wr:t=hw;break;case $r:t=pw;break;case Hr:t=gw;break;case zr:t=mw;break;case Gr:t=fw;break;default:console.error("THREE.WebGPURenderer: Invalid stencil operation.",t)}return t}_getBlendOperation(e){let t;switch(e){case Xe:t=rw;break;case ft:t=sw;break;case mt:t=iw;break;case Yr:t=nw;break;case Kr:t=aw;break;default:console.error("THREE.WebGPUPipelineUtils: Blend equation not supported.",e)}return t}_getPrimitiveState(e,t,r){const s={},i=this.backend.utils;switch(s.topology=i.getPrimitiveTopology(e,r),null!==t.index&&!0===e.isLine&&!0!==e.isLineSegments&&(s.stripIndexFormat=t.index.array instanceof Uint16Array?QN:ZN),r.side){case je:s.frontFace=qN,s.cullMode=YN;break;case S:s.frontFace=qN,s.cullMode=KN;break;case E:s.frontFace=qN,s.cullMode=XN;break;default:console.error("THREE.WebGPUPipelineUtils: Unknown material.side value.",r.side)}return s}_getColorWriteMask(e){return!0===e.colorWrite?uw:ow}_getDepthCompare(e){let t;if(!1===e.depthTest)t=HN;else{const r=e.depthFunc;switch(r){case kt:t=IN;break;case Ot:t=HN;break;case Ut:t=VN;break;case Vt:t=ON;break;case It:t=UN;break;case Dt:t=zN;break;case Lt:t=kN;break;case Ft:t=GN;break;default:console.error("THREE.WebGPUPipelineUtils: Invalid depth function.",r)}}return t}}class xA extends AN{constructor(e,t,r=2048){super(r),this.device=e,this.type=t,this.querySet=this.device.createQuerySet({type:"timestamp",count:this.maxQueries,label:`queryset_global_timestamp_${t}`});const s=8*this.maxQueries;this.resolveBuffer=this.device.createBuffer({label:`buffer_timestamp_resolve_${t}`,size:s,usage:GPUBufferUsage.QUERY_RESOLVE|GPUBufferUsage.COPY_SRC}),this.resultBuffer=this.device.createBuffer({label:`buffer_timestamp_result_${t}`,size:s,usage:GPUBufferUsage.COPY_DST|GPUBufferUsage.MAP_READ})}allocateQueriesForContext(e){if(!this.trackTimestamp||this.isDisposed)return null;if(this.currentQueryIndex+2>this.maxQueries)return pt(`WebGPUTimestampQueryPool [${this.type}]: Maximum number of queries exceeded, when using trackTimestamp it is necessary to resolves the queries via renderer.resolveTimestampsAsync( THREE.TimestampQuery.${this.type.toUpperCase()} ).`),null;const t=this.currentQueryIndex;return this.currentQueryIndex+=2,this.queryOffsets.set(e.id,t),t}async resolveQueriesAsync(){if(!this.trackTimestamp||0===this.currentQueryIndex||this.isDisposed)return this.lastValue;if(this.pendingResolve)return this.pendingResolve;this.pendingResolve=this._resolveQueries();try{return await this.pendingResolve}finally{this.pendingResolve=null}}async _resolveQueries(){if(this.isDisposed)return this.lastValue;try{if("unmapped"!==this.resultBuffer.mapState)return this.lastValue;const e=new Map(this.queryOffsets),t=this.currentQueryIndex,r=8*t;this.currentQueryIndex=0,this.queryOffsets.clear();const s=this.device.createCommandEncoder();s.resolveQuerySet(this.querySet,0,t,this.resolveBuffer,0),s.copyBufferToBuffer(this.resolveBuffer,0,this.resultBuffer,0,r);const i=s.finish();if(this.device.queue.submit([i]),"unmapped"!==this.resultBuffer.mapState)return this.lastValue;if(await this.resultBuffer.mapAsync(GPUMapMode.READ,0,r),this.isDisposed)return"mapped"===this.resultBuffer.mapState&&this.resultBuffer.unmap(),this.lastValue;const n=new BigUint64Array(this.resultBuffer.getMappedRange(0,r));let a=0;for(const[,t]of e){const e=n[t],r=n[t+1];a+=Number(r-e)/1e6}return this.resultBuffer.unmap(),this.lastValue=a,a}catch(e){return console.error("Error resolving queries:",e),"mapped"===this.resultBuffer.mapState&&this.resultBuffer.unmap(),this.lastValue}}async dispose(){if(!this.isDisposed){if(this.isDisposed=!0,this.pendingResolve)try{await this.pendingResolve}catch(e){console.error("Error waiting for pending resolve:",e)}if(this.resultBuffer&&"mapped"===this.resultBuffer.mapState)try{this.resultBuffer.unmap()}catch(e){console.error("Error unmapping buffer:",e)}this.querySet&&(this.querySet.destroy(),this.querySet=null),this.resolveBuffer&&(this.resolveBuffer.destroy(),this.resolveBuffer=null),this.resultBuffer&&(this.resultBuffer.destroy(),this.resultBuffer=null),this.queryOffsets.clear(),this.pendingResolve=null}}}class TA extends lN{constructor(e={}){super(e),this.isWebGPUBackend=!0,this.parameters.alpha=void 0===e.alpha||e.alpha,this.parameters.compatibilityMode=void 0!==e.compatibilityMode&&e.compatibilityMode,this.parameters.requiredLimits=void 0===e.requiredLimits?{}:e.requiredLimits,this.compatibilityMode=this.parameters.compatibilityMode,this.device=null,this.context=null,this.colorBuffer=null,this.defaultRenderPassdescriptor=null,this.utils=new hA(this),this.attributeUtils=new fA(this),this.bindingUtils=new yA(this),this.pipelineUtils=new bA(this),this.textureUtils=new qw(this),this.occludedResolveCache=new Map}async init(e){await super.init(e);const t=this.parameters;let r;if(void 0===t.device){const e={powerPreference:t.powerPreference,featureLevel:t.compatibilityMode?"compatibility":void 0},s="undefined"!=typeof navigator?await navigator.gpu.requestAdapter(e):null;if(null===s)throw new Error("WebGPUBackend: Unable to create WebGPU adapter.");const i=Object.values(Uw),n=[];for(const e of i)s.features.has(e)&&n.push(e);const a={requiredFeatures:n,requiredLimits:t.requiredLimits};r=await s.requestDevice(a)}else r=t.device;r.lost.then(t=>{const r={api:"WebGPU",message:t.message||"Unknown reason",reason:t.reason||null,originalEvent:t};e.onDeviceLost(r)});const s=void 0!==t.context?t.context:e.domElement.getContext("webgpu");this.device=r,this.context=s;const i=t.alpha?"premultiplied":"opaque";this.trackTimestamp=this.trackTimestamp&&this.hasFeature(Uw.TimestampQuery),this.context.configure({device:this.device,format:this.utils.getPreferredCanvasFormat(),usage:GPUTextureUsage.RENDER_ATTACHMENT|GPUTextureUsage.COPY_SRC,alphaMode:i}),this.updateSize()}get coordinateSystem(){return d}async getArrayBufferAsync(e){return await this.attributeUtils.getArrayBufferAsync(e)}getContext(){return this.context}_getDefaultRenderPassDescriptor(){let e=this.defaultRenderPassdescriptor;if(null===e){const t=this.renderer;e={colorAttachments:[{view:null}]},!0!==this.renderer.depth&&!0!==this.renderer.stencil||(e.depthStencilAttachment={view:this.textureUtils.getDepthBuffer(t.depth,t.stencil).createView()});const r=e.colorAttachments[0];this.renderer.samples>0?r.view=this.colorBuffer.createView():r.resolveTarget=void 0,this.defaultRenderPassdescriptor=e}const t=e.colorAttachments[0];return this.renderer.samples>0?t.resolveTarget=this.context.getCurrentTexture().createView():t.view=this.context.getCurrentTexture().createView(),e}_isRenderCameraDepthArray(e){return e.depthTexture&&e.depthTexture.image.depth>1&&e.camera.isArrayCamera}_getRenderPassDescriptor(e,t={}){const r=e.renderTarget,s=this.get(r);let i=s.descriptors;if(void 0===i||s.width!==r.width||s.height!==r.height||s.dimensions!==r.dimensions||s.activeMipmapLevel!==e.activeMipmapLevel||s.activeCubeFace!==e.activeCubeFace||s.samples!==r.samples){i={},s.descriptors=i;const e=()=>{r.removeEventListener("dispose",e),this.delete(r)};!1===r.hasEventListener("dispose",e)&&r.addEventListener("dispose",e)}const n=e.getCacheKey();let a=i[n];if(void 0===a){const t=e.textures,o=[];let u;const l=this._isRenderCameraDepthArray(e);for(let s=0;s1)if(!0===l){const t=e.camera.cameras;for(let e=0;e0&&(t.currentOcclusionQuerySet&&t.currentOcclusionQuerySet.destroy(),t.currentOcclusionQueryBuffer&&t.currentOcclusionQueryBuffer.destroy(),t.currentOcclusionQuerySet=t.occlusionQuerySet,t.currentOcclusionQueryBuffer=t.occlusionQueryBuffer,t.currentOcclusionQueryObjects=t.occlusionQueryObjects,i=r.createQuerySet({type:"occlusion",count:s,label:`occlusionQuerySet_${e.id}`}),t.occlusionQuerySet=i,t.occlusionQueryIndex=0,t.occlusionQueryObjects=new Array(s),t.lastOcclusionObject=null),n=null===e.textures?this._getDefaultRenderPassDescriptor():this._getRenderPassDescriptor(e,{loadOp:WN}),this.initTimestampQuery(e,n),n.occlusionQuerySet=i;const a=n.depthStencilAttachment;if(null!==e.textures){const t=n.colorAttachments;for(let r=0;r0&&t.currentPass.executeBundles(t.renderBundles),r>t.occlusionQueryIndex&&t.currentPass.endOcclusionQuery();const s=t.encoder;if(!0===this._isRenderCameraDepthArray(e)){const r=[];for(let e=0;e0){const s=8*r;let i=this.occludedResolveCache.get(s);void 0===i&&(i=this.device.createBuffer({size:s,usage:GPUBufferUsage.QUERY_RESOLVE|GPUBufferUsage.COPY_SRC}),this.occludedResolveCache.set(s,i));const n=this.device.createBuffer({size:s,usage:GPUBufferUsage.COPY_DST|GPUBufferUsage.MAP_READ});t.encoder.resolveQuerySet(t.occlusionQuerySet,0,r,i,0),t.encoder.copyBufferToBuffer(i,0,n,0,s),t.occlusionQueryBuffer=n,this.resolveOccludedAsync(e)}if(this.device.queue.submit([t.encoder.finish()]),null!==e.textures){const t=e.textures;for(let e=0;ea?(u.x=Math.min(t.dispatchCount,a),u.y=Math.ceil(t.dispatchCount/a)):u.x=t.dispatchCount,i.dispatchWorkgroups(u.x,u.y,u.z)}finishCompute(e){const t=this.get(e);t.passEncoderGPU.end(),this.device.queue.submit([t.cmdEncoderGPU.finish()])}async waitForGPU(){await this.device.queue.onSubmittedWorkDone()}draw(e,t){const{object:r,material:s,context:i,pipeline:n}=e,a=e.getBindings(),o=this.get(i),u=this.get(n).pipeline,l=e.getIndex(),d=null!==l,c=e.getDrawParameters();if(null===c)return;const h=(t,r)=>{this.pipelineUtils.setPipeline(t,u),r.pipeline=u;const n=r.bindingGroups;for(let e=0,r=a.length;e{if(h(s,i),!0===r.isBatchedMesh){const e=r._multiDrawStarts,i=r._multiDrawCounts,n=r._multiDrawCount,a=r._multiDrawInstances;null!==a&&pt("THREE.WebGPUBackend: renderMultiDrawInstances has been deprecated and will be removed in r184. Append to renderMultiDraw arguments and use indirection.");for(let o=0;o1?0:o;!0===d?s.drawIndexed(i[o],n,e[o]/l.array.BYTES_PER_ELEMENT,0,u):s.draw(i[o],n,e[o],u),t.update(r,i[o],n)}}else if(!0===d){const{vertexCount:i,instanceCount:n,firstVertex:a}=c,o=e.getIndirect();if(null!==o){const e=this.get(o).buffer;s.drawIndexedIndirect(e,0)}else s.drawIndexed(i,n,a,0,0);t.update(r,i,n)}else{const{vertexCount:i,instanceCount:n,firstVertex:a}=c,o=e.getIndirect();if(null!==o){const e=this.get(o).buffer;s.drawIndirect(e,0)}else s.draw(i,n,a,0);t.update(r,i,n)}};if(e.camera.isArrayCamera&&e.camera.cameras.length>0){const t=this.get(e.camera),s=e.camera.cameras,n=e.getBindingGroup("cameraIndex");if(void 0===t.indexesGPU||t.indexesGPU.length!==s.length){const e=this.get(n),r=[],i=new Uint32Array([0,0,0,0]);for(let t=0,n=s.length;t(console.warn("THREE.WebGPURenderer: WebGPU is not available, running under WebGL2 backend."),new MN(e)));super(new t(e),e),this.library=new NA,this.isWebGPURenderer=!0,"undefined"!=typeof __THREE_DEVTOOLS__&&__THREE_DEVTOOLS__.dispatchEvent(new CustomEvent("observe",{detail:this}))}}class EA extends ds{constructor(){super(),this.isBundleGroup=!0,this.type="BundleGroup",this.static=!0,this.version=0}set needsUpdate(e){!0===e&&this.version++}}class wA{constructor(e,t=nn(0,0,1,1)){this.renderer=e,this.outputNode=t,this.outputColorTransform=!0,this.needsUpdate=!0;const r=new lp;r.name="PostProcessing",this._quadMesh=new Uy(r)}render(){this._update();const e=this.renderer,t=e.toneMapping,r=e.outputColorSpace;e.toneMapping=p,e.outputColorSpace=le;const s=e.xr.enabled;e.xr.enabled=!1,this._quadMesh.render(e),e.xr.enabled=s,e.toneMapping=t,e.outputColorSpace=r}dispose(){this._quadMesh.material.dispose()}_update(){if(!0===this.needsUpdate){const e=this.renderer,t=e.toneMapping,r=e.outputColorSpace;this._quadMesh.material.fragmentNode=!0===this.outputColorTransform?Ou(this.outputNode,t,r):this.outputNode.context({toneMapping:t,outputColorSpace:r}),this._quadMesh.material.needsUpdate=!0,this.needsUpdate=!1}}async renderAsync(){this._update();const e=this.renderer,t=e.toneMapping,r=e.outputColorSpace;e.toneMapping=p,e.outputColorSpace=le;const s=e.xr.enabled;e.xr.enabled=!1,await this._quadMesh.renderAsync(e),e.xr.enabled=s,e.toneMapping=t,e.outputColorSpace=r}}class AA extends x{constructor(e=1,t=1){super(),this.image={width:e,height:t},this.magFilter=Y,this.minFilter=Y,this.isStorageTexture=!0}}class RA extends x{constructor(e=1,t=1,r=1){super(),this.isArrayTexture=!1,this.image={width:e,height:t,depth:r},this.magFilter=Y,this.minFilter=Y,this.wrapR=vr,this.isStorageTexture=!0,this.is3DTexture=!0}}class CA extends x{constructor(e=1,t=1,r=1){super(),this.isArrayTexture=!0,this.image={width:e,height:t,depth:r},this.magFilter=Y,this.minFilter=Y,this.isStorageTexture=!0}}class MA extends qy{constructor(e,t){super(e,t,Uint32Array),this.isIndirectStorageBufferAttribute=!0}}class PA extends cs{constructor(e){super(e),this.textures={},this.nodes={}}load(e,t,r,s){const i=new hs(this.manager);i.setPath(this.path),i.setRequestHeader(this.requestHeader),i.setWithCredentials(this.withCredentials),i.load(e,r=>{try{t(this.parse(JSON.parse(r)))}catch(t){s?s(t):console.error(t),this.manager.itemError(e)}},r,s)}parseNodes(e){const t={};if(void 0!==e){for(const r of e){const{uuid:e,type:s}=r;t[e]=this.createNodeFromType(s),t[e].uuid=e}const r={nodes:t,textures:this.textures};for(const s of e){s.meta=r;t[s.uuid].deserialize(s),delete s.meta}}return t}parse(e){const t=this.createNodeFromType(e.type);t.uuid=e.uuid;const r={nodes:this.parseNodes(e.nodes),textures:this.textures};return e.meta=r,t.deserialize(e),delete e.meta,t}setTextures(e){return this.textures=e,this}setNodes(e){return this.nodes=e,this}createNodeFromType(e){return void 0===this.nodes[e]?(console.error("THREE.NodeLoader: Node type not found:",e),ji()):Li(new this.nodes[e])}}class BA extends ps{constructor(e){super(e),this.nodes={},this.nodeMaterials={}}parse(e){const t=super.parse(e),r=this.nodes,s=e.inputNodes;for(const e in s){const i=s[e];t[e]=r[i]}return t}setNodes(e){return this.nodes=e,this}setNodeMaterials(e){return this.nodeMaterials=e,this}createMaterialFromType(e){const t=this.nodeMaterials[e];return void 0!==t?new t:super.createMaterialFromType(e)}}class FA extends gs{constructor(e){super(e),this.nodes={},this.nodeMaterials={},this._nodesJSON=null}setNodes(e){return this.nodes=e,this}setNodeMaterials(e){return this.nodeMaterials=e,this}parse(e,t){this._nodesJSON=e.nodes;const r=super.parse(e,t);return this._nodesJSON=null,r}parseNodes(e,t){if(void 0!==e){const r=new PA;return r.setNodes(this.nodes),r.setTextures(t),r.parseNodes(e)}return{}}parseMaterials(e,t){const r={};if(void 0!==e){const s=this.parseNodes(this._nodesJSON,t),i=new BA;i.setTextures(t),i.setNodes(s),i.setNodeMaterials(this.nodeMaterials);for(let t=0,s=e.length;t} + */ + this.renderObjects = new WeakMap(); + + /** + * Whether the material uses node objects or not. + * + * @type {boolean} + */ + this.hasNode = this.containsNode( builder ); + + /** + * Whether the node builder's 3D object is animated or not. + * + * @type {boolean} + */ + this.hasAnimation = builder.object.isSkinnedMesh === true; + + /** + * A list of all possible material uniforms + * + * @type {Array} + */ + this.refreshUniforms = refreshUniforms; + + /** + * Holds the current render ID from the node frame. + * + * @type {number} + * @default 0 + */ + this.renderId = 0; + + } + + /** + * Returns `true` if the given render object is verified for the first time of this observer. + * + * @param {RenderObject} renderObject - The render object. + * @return {boolean} Whether the given render object is verified for the first time of this observer. + */ + firstInitialization( renderObject ) { + + const hasInitialized = this.renderObjects.has( renderObject ); + + if ( hasInitialized === false ) { + + this.getRenderObjectData( renderObject ); + + return true; + + } + + return false; + + } + + /** + * Returns `true` if the current rendering produces motion vectors. + * + * @param {Renderer} renderer - The renderer. + * @return {boolean} Whether the current rendering produces motion vectors or not. + */ + needsVelocity( renderer ) { + + const mrt = renderer.getMRT(); + + return ( mrt !== null && mrt.has( 'velocity' ) ); + + } + + /** + * Returns monitoring data for the given render object. + * + * @param {RenderObject} renderObject - The render object. + * @return {Object} The monitoring data. + */ + getRenderObjectData( renderObject ) { + + let data = this.renderObjects.get( renderObject ); + + if ( data === undefined ) { + + const { geometry, material, object } = renderObject; + + data = { + material: this.getMaterialData( material ), + geometry: { + id: geometry.id, + attributes: this.getAttributesData( geometry.attributes ), + indexVersion: geometry.index ? geometry.index.version : null, + drawRange: { start: geometry.drawRange.start, count: geometry.drawRange.count } + }, + worldMatrix: object.matrixWorld.clone() + }; + + if ( object.center ) { + + data.center = object.center.clone(); + + } + + if ( object.morphTargetInfluences ) { + + data.morphTargetInfluences = object.morphTargetInfluences.slice(); + + } + + if ( renderObject.bundle !== null ) { + + data.version = renderObject.bundle.version; + + } + + if ( data.material.transmission > 0 ) { + + const { width, height } = renderObject.context; + + data.bufferWidth = width; + data.bufferHeight = height; + + } + + this.renderObjects.set( renderObject, data ); + + } + + return data; + + } + + /** + * Returns an attribute data structure holding the attributes versions for + * monitoring. + * + * @param {Object} attributes - The geometry attributes. + * @return {Object} An object for monitoring the versions of attributes. + */ + getAttributesData( attributes ) { + + const attributesData = {}; + + for ( const name in attributes ) { + + const attribute = attributes[ name ]; + + attributesData[ name ] = { + version: attribute.version + }; + + } + + return attributesData; + + } + + /** + * Returns `true` if the node builder's material uses + * node properties. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {boolean} Whether the node builder's material uses node properties or not. + */ + containsNode( builder ) { + + const material = builder.material; + + for ( const property in material ) { + + if ( material[ property ] && material[ property ].isNode ) + return true; + + } + + if ( builder.renderer.overrideNodes.modelViewMatrix !== null || builder.renderer.overrideNodes.modelNormalViewMatrix !== null ) + return true; + + return false; + + } + + /** + * Returns a material data structure holding the material property values for + * monitoring. + * + * @param {Material} material - The material. + * @return {Object} An object for monitoring material properties. + */ + getMaterialData( material ) { + + const data = {}; + + for ( const property of this.refreshUniforms ) { + + const value = material[ property ]; + + if ( value === null || value === undefined ) continue; + + if ( typeof value === 'object' && value.clone !== undefined ) { + + if ( value.isTexture === true ) { + + data[ property ] = { id: value.id, version: value.version }; + + } else { + + data[ property ] = value.clone(); + + } + + } else { + + data[ property ] = value; + + } + + } + + return data; + + } + + /** + * Returns `true` if the given render object has not changed its state. + * + * @param {RenderObject} renderObject - The render object. + * @return {boolean} Whether the given render object has changed its state or not. + */ + equals( renderObject ) { + + const { object, material, geometry } = renderObject; + + const renderObjectData = this.getRenderObjectData( renderObject ); + + // world matrix + + if ( renderObjectData.worldMatrix.equals( object.matrixWorld ) !== true ) { + + renderObjectData.worldMatrix.copy( object.matrixWorld ); + + return false; + + } + + // material + + const materialData = renderObjectData.material; + + for ( const property in materialData ) { + + const value = materialData[ property ]; + const mtlValue = material[ property ]; + + if ( value.equals !== undefined ) { + + if ( value.equals( mtlValue ) === false ) { + + value.copy( mtlValue ); + + return false; + + } + + } else if ( mtlValue.isTexture === true ) { + + if ( value.id !== mtlValue.id || value.version !== mtlValue.version ) { + + value.id = mtlValue.id; + value.version = mtlValue.version; + + return false; + + } + + } else if ( value !== mtlValue ) { + + materialData[ property ] = mtlValue; + + return false; + + } + + } + + if ( materialData.transmission > 0 ) { + + const { width, height } = renderObject.context; + + if ( renderObjectData.bufferWidth !== width || renderObjectData.bufferHeight !== height ) { + + renderObjectData.bufferWidth = width; + renderObjectData.bufferHeight = height; + + return false; + + } + + } + + // geometry + + const storedGeometryData = renderObjectData.geometry; + const attributes = geometry.attributes; + const storedAttributes = storedGeometryData.attributes; + + const storedAttributeNames = Object.keys( storedAttributes ); + const currentAttributeNames = Object.keys( attributes ); + + if ( storedGeometryData.id !== geometry.id ) { + + storedGeometryData.id = geometry.id; + return false; + + } + + if ( storedAttributeNames.length !== currentAttributeNames.length ) { + + renderObjectData.geometry.attributes = this.getAttributesData( attributes ); + return false; + + } + + // compare each attribute + + for ( const name of storedAttributeNames ) { + + const storedAttributeData = storedAttributes[ name ]; + const attribute = attributes[ name ]; + + if ( attribute === undefined ) { + + // attribute was removed + delete storedAttributes[ name ]; + return false; + + } + + if ( storedAttributeData.version !== attribute.version ) { + + storedAttributeData.version = attribute.version; + return false; + + } + + } + + // check index + + const index = geometry.index; + const storedIndexVersion = storedGeometryData.indexVersion; + const currentIndexVersion = index ? index.version : null; + + if ( storedIndexVersion !== currentIndexVersion ) { + + storedGeometryData.indexVersion = currentIndexVersion; + return false; + + } + + // check drawRange + + if ( storedGeometryData.drawRange.start !== geometry.drawRange.start || storedGeometryData.drawRange.count !== geometry.drawRange.count ) { + + storedGeometryData.drawRange.start = geometry.drawRange.start; + storedGeometryData.drawRange.count = geometry.drawRange.count; + return false; + + } + + // morph targets + + if ( renderObjectData.morphTargetInfluences ) { + + let morphChanged = false; + + for ( let i = 0; i < renderObjectData.morphTargetInfluences.length; i ++ ) { + + if ( renderObjectData.morphTargetInfluences[ i ] !== object.morphTargetInfluences[ i ] ) { + + morphChanged = true; + + } + + } + + if ( morphChanged ) return true; + + } + + // center + + if ( renderObjectData.center ) { + + if ( renderObjectData.center.equals( object.center ) === false ) { + + renderObjectData.center.copy( object.center ); + + return true; + + } + + } + + // bundle + + if ( renderObject.bundle !== null ) { + + renderObjectData.version = renderObject.bundle.version; + + } + + return true; + + } + + /** + * Checks if the given render object requires a refresh. + * + * @param {RenderObject} renderObject - The render object. + * @param {NodeFrame} nodeFrame - The current node frame. + * @return {boolean} Whether the given render object requires a refresh or not. + */ + needsRefresh( renderObject, nodeFrame ) { + + if ( this.hasNode || this.hasAnimation || this.firstInitialization( renderObject ) || this.needsVelocity( nodeFrame.renderer ) ) + return true; + + const { renderId } = nodeFrame; + + if ( this.renderId !== renderId ) { + + this.renderId = renderId; + + return true; + + } + + const isStatic = renderObject.object.static === true; + const isBundle = renderObject.bundle !== null && renderObject.bundle.static === true && this.getRenderObjectData( renderObject ).version === renderObject.bundle.version; + + if ( isStatic || isBundle ) + return false; + + const notEqual = this.equals( renderObject ) !== true; + + return notEqual; + + } + +} + +// cyrb53 (c) 2018 bryc (github.com/bryc). License: Public domain. Attribution appreciated. +// A fast and simple 64-bit (or 53-bit) string hash function with decent collision resistance. +// Largely inspired by MurmurHash2/3, but with a focus on speed/simplicity. +// See https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript/52171480#52171480 +// https://github.com/bryc/code/blob/master/jshash/experimental/cyrb53.js +function cyrb53( value, seed = 0 ) { + + let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed; + + if ( value instanceof Array ) { + + for ( let i = 0, val; i < value.length; i ++ ) { + + val = value[ i ]; + h1 = Math.imul( h1 ^ val, 2654435761 ); + h2 = Math.imul( h2 ^ val, 1597334677 ); + + } + + } else { + + for ( let i = 0, ch; i < value.length; i ++ ) { + + ch = value.charCodeAt( i ); + h1 = Math.imul( h1 ^ ch, 2654435761 ); + h2 = Math.imul( h2 ^ ch, 1597334677 ); + + } + + } + + h1 = Math.imul( h1 ^ ( h1 >>> 16 ), 2246822507 ); + h1 ^= Math.imul( h2 ^ ( h2 >>> 13 ), 3266489909 ); + h2 = Math.imul( h2 ^ ( h2 >>> 16 ), 2246822507 ); + h2 ^= Math.imul( h1 ^ ( h1 >>> 13 ), 3266489909 ); + + return 4294967296 * ( 2097151 & h2 ) + ( h1 >>> 0 ); + +} + +/** + * Computes a hash for the given string. + * + * @method + * @param {string} str - The string to be hashed. + * @return {number} The hash. + */ +const hashString = ( str ) => cyrb53( str ); + +/** + * Computes a hash for the given array. + * + * @method + * @param {Array} array - The array to be hashed. + * @return {number} The hash. + */ +const hashArray = ( array ) => cyrb53( array ); + +/** + * Computes a hash for the given list of parameters. + * + * @method + * @param {...number} params - A list of parameters. + * @return {number} The hash. + */ +const hash$1 = ( ...params ) => cyrb53( params ); + +/** + * Computes a cache key for the given node. + * + * @method + * @param {Object|Node} object - The object to be hashed. + * @param {boolean} [force=false] - Whether to force a cache key computation or not. + * @return {number} The hash. + */ +function getCacheKey$1( object, force = false ) { + + const values = []; + + if ( object.isNode === true ) { + + values.push( object.id ); + object = object.getSelf(); + + } + + for ( const { property, childNode } of getNodeChildren( object ) ) { + + values.push( cyrb53( property.slice( 0, -4 ) ), childNode.getCacheKey( force ) ); + + } + + return cyrb53( values ); + +} + +/** + * This generator function can be used to iterate over the node children + * of the given object. + * + * @generator + * @param {Object} node - The object to be hashed. + * @param {boolean} [toJSON=false] - Whether to return JSON or not. + * @yields {Object} A result node holding the property, index (if available) and the child node. + */ +function* getNodeChildren( node, toJSON = false ) { + + for ( const property in node ) { + + // Ignore private properties. + if ( property.startsWith( '_' ) === true ) continue; + + const object = node[ property ]; + + if ( Array.isArray( object ) === true ) { + + for ( let i = 0; i < object.length; i ++ ) { + + const child = object[ i ]; + + if ( child && ( child.isNode === true || toJSON && typeof child.toJSON === 'function' ) ) { + + yield { property, index: i, childNode: child }; + + } + + } + + } else if ( object && object.isNode === true ) { + + yield { property, childNode: object }; + + } else if ( typeof object === 'object' ) { + + for ( const subProperty in object ) { + + const child = object[ subProperty ]; + + if ( child && ( child.isNode === true || toJSON && typeof child.toJSON === 'function' ) ) { + + yield { property, index: subProperty, childNode: child }; + + } + + } + + } + + } + +} + +const typeFromLength = /*@__PURE__*/ new Map( [ + [ 1, 'float' ], + [ 2, 'vec2' ], + [ 3, 'vec3' ], + [ 4, 'vec4' ], + [ 9, 'mat3' ], + [ 16, 'mat4' ] +] ); + +const dataFromObject = /*@__PURE__*/ new WeakMap(); + +/** + * Returns the data type for the given the length. + * + * @method + * @param {number} length - The length. + * @return {string} The data type. + */ +function getTypeFromLength( length ) { + + return typeFromLength.get( length ); + +} + +/** + * Returns the typed array for the given data type. + * + * @method + * @param {string} type - The data type. + * @return {TypedArray} The typed array. + */ +function getTypedArrayFromType( type ) { + + // Handle component type for vectors and matrices + if ( /[iu]?vec\d/.test( type ) ) { + + // Handle int vectors + if ( type.startsWith( 'ivec' ) ) return Int32Array; + // Handle uint vectors + if ( type.startsWith( 'uvec' ) ) return Uint32Array; + // Default to float vectors + return Float32Array; + + } + + // Handle matrices (always float) + if ( /mat\d/.test( type ) ) return Float32Array; + + // Basic types + if ( /float/.test( type ) ) return Float32Array; + if ( /uint/.test( type ) ) return Uint32Array; + if ( /int/.test( type ) ) return Int32Array; + + throw new Error( `THREE.NodeUtils: Unsupported type: ${type}` ); + +} + +/** + * Returns the length for the given data type. + * + * @method + * @param {string} type - The data type. + * @return {number} The length. + */ +function getLengthFromType( type ) { + + if ( /float|int|uint/.test( type ) ) return 1; + if ( /vec2/.test( type ) ) return 2; + if ( /vec3/.test( type ) ) return 3; + if ( /vec4/.test( type ) ) return 4; + if ( /mat2/.test( type ) ) return 4; + if ( /mat3/.test( type ) ) return 9; + if ( /mat4/.test( type ) ) return 16; + + console.error( 'THREE.TSL: Unsupported type:', type ); + +} + +/** + * Returns the gpu memory length for the given data type. + * + * @method + * @param {string} type - The data type. + * @return {number} The length. + */ +function getMemoryLengthFromType( type ) { + + if ( /float|int|uint/.test( type ) ) return 1; + if ( /vec2/.test( type ) ) return 2; + if ( /vec3/.test( type ) ) return 3; + if ( /vec4/.test( type ) ) return 4; + if ( /mat2/.test( type ) ) return 4; + if ( /mat3/.test( type ) ) return 12; + if ( /mat4/.test( type ) ) return 16; + + console.error( 'THREE.TSL: Unsupported type:', type ); + +} + +/** + * Returns the byte boundary for the given data type. + * + * @method + * @param {string} type - The data type. + * @return {number} The byte boundary. + */ +function getByteBoundaryFromType( type ) { + + if ( /float|int|uint/.test( type ) ) return 4; + if ( /vec2/.test( type ) ) return 8; + if ( /vec3/.test( type ) ) return 16; + if ( /vec4/.test( type ) ) return 16; + if ( /mat2/.test( type ) ) return 8; + if ( /mat3/.test( type ) ) return 48; + if ( /mat4/.test( type ) ) return 64; + + console.error( 'THREE.TSL: Unsupported type:', type ); + +} + +/** + * Returns the data type for the given value. + * + * @method + * @param {any} value - The value. + * @return {?string} The data type. + */ +function getValueType( value ) { + + if ( value === undefined || value === null ) return null; + + const typeOf = typeof value; + + if ( value.isNode === true ) { + + return 'node'; + + } else if ( typeOf === 'number' ) { + + return 'float'; + + } else if ( typeOf === 'boolean' ) { + + return 'bool'; + + } else if ( typeOf === 'string' ) { + + return 'string'; + + } else if ( typeOf === 'function' ) { + + return 'shader'; + + } else if ( value.isVector2 === true ) { + + return 'vec2'; + + } else if ( value.isVector3 === true ) { + + return 'vec3'; + + } else if ( value.isVector4 === true ) { + + return 'vec4'; + + } else if ( value.isMatrix2 === true ) { + + return 'mat2'; + + } else if ( value.isMatrix3 === true ) { + + return 'mat3'; + + } else if ( value.isMatrix4 === true ) { + + return 'mat4'; + + } else if ( value.isColor === true ) { + + return 'color'; + + } else if ( value instanceof ArrayBuffer ) { + + return 'ArrayBuffer'; + + } + + return null; + +} + +/** + * Returns the value/object for the given data type and parameters. + * + * @method + * @param {string} type - The given type. + * @param {...any} params - A parameter list. + * @return {any} The value/object. + */ +function getValueFromType( type, ...params ) { + + const last4 = type ? type.slice( -4 ) : undefined; + + if ( params.length === 1 ) { // ensure same behaviour as in NodeBuilder.format() + + if ( last4 === 'vec2' ) params = [ params[ 0 ], params[ 0 ] ]; + else if ( last4 === 'vec3' ) params = [ params[ 0 ], params[ 0 ], params[ 0 ] ]; + else if ( last4 === 'vec4' ) params = [ params[ 0 ], params[ 0 ], params[ 0 ], params[ 0 ] ]; + + } + + if ( type === 'color' ) { + + return new Color( ...params ); + + } else if ( last4 === 'vec2' ) { + + return new Vector2( ...params ); + + } else if ( last4 === 'vec3' ) { + + return new Vector3( ...params ); + + } else if ( last4 === 'vec4' ) { + + return new Vector4( ...params ); + + } else if ( last4 === 'mat2' ) { + + return new Matrix2( ...params ); + + } else if ( last4 === 'mat3' ) { + + return new Matrix3( ...params ); + + } else if ( last4 === 'mat4' ) { + + return new Matrix4( ...params ); + + } else if ( type === 'bool' ) { + + return params[ 0 ] || false; + + } else if ( ( type === 'float' ) || ( type === 'int' ) || ( type === 'uint' ) ) { + + return params[ 0 ] || 0; + + } else if ( type === 'string' ) { + + return params[ 0 ] || ''; + + } else if ( type === 'ArrayBuffer' ) { + + return base64ToArrayBuffer( params[ 0 ] ); + + } + + return null; + +} + +/** + * Gets the object data that can be shared between different rendering steps. + * + * @param {Object} object - The object to get the data for. + * @return {Object} The object data. + */ +function getDataFromObject( object ) { + + let data = dataFromObject.get( object ); + + if ( data === undefined ) { + + data = {}; + dataFromObject.set( object, data ); + + } + + return data; + +} + +/** + * Converts the given array buffer to a Base64 string. + * + * @method + * @param {ArrayBuffer} arrayBuffer - The array buffer. + * @return {string} The Base64 string. + */ +function arrayBufferToBase64( arrayBuffer ) { + + let chars = ''; + + const array = new Uint8Array( arrayBuffer ); + + for ( let i = 0; i < array.length; i ++ ) { + + chars += String.fromCharCode( array[ i ] ); + + } + + return btoa( chars ); + +} + +/** + * Converts the given Base64 string to an array buffer. + * + * @method + * @param {string} base64 - The Base64 string. + * @return {ArrayBuffer} The array buffer. + */ +function base64ToArrayBuffer( base64 ) { + + return Uint8Array.from( atob( base64 ), c => c.charCodeAt( 0 ) ).buffer; + +} + +var NodeUtils = /*#__PURE__*/Object.freeze({ + __proto__: null, + arrayBufferToBase64: arrayBufferToBase64, + base64ToArrayBuffer: base64ToArrayBuffer, + getByteBoundaryFromType: getByteBoundaryFromType, + getCacheKey: getCacheKey$1, + getDataFromObject: getDataFromObject, + getLengthFromType: getLengthFromType, + getMemoryLengthFromType: getMemoryLengthFromType, + getNodeChildren: getNodeChildren, + getTypeFromLength: getTypeFromLength, + getTypedArrayFromType: getTypedArrayFromType, + getValueFromType: getValueFromType, + getValueType: getValueType, + hash: hash$1, + hashArray: hashArray, + hashString: hashString +}); + +/** + * Possible shader stages. + * + * @property {string} VERTEX The vertex shader stage. + * @property {string} FRAGMENT The fragment shader stage. + */ +const NodeShaderStage = { + VERTEX: 'vertex', + FRAGMENT: 'fragment' +}; + +/** + * Update types of a node. + * + * @property {string} NONE The update method is not executed. + * @property {string} FRAME The update method is executed per frame. + * @property {string} RENDER The update method is executed per render. A frame might be produced by multiple render calls so this value allows more detailed updates than FRAME. + * @property {string} OBJECT The update method is executed per {@link Object3D} that uses the node for rendering. + */ +const NodeUpdateType = { + NONE: 'none', + FRAME: 'frame', + RENDER: 'render', + OBJECT: 'object' +}; + +/** + * Data types of a node. + * + * @property {string} BOOLEAN Boolean type. + * @property {string} INTEGER Integer type. + * @property {string} FLOAT Float type. + * @property {string} VECTOR2 Two-dimensional vector type. + * @property {string} VECTOR3 Three-dimensional vector type. + * @property {string} VECTOR4 Four-dimensional vector type. + * @property {string} MATRIX2 2x2 matrix type. + * @property {string} MATRIX3 3x3 matrix type. + * @property {string} MATRIX4 4x4 matrix type. + */ +const NodeType = { + BOOLEAN: 'bool', + INTEGER: 'int', + FLOAT: 'float', + VECTOR2: 'vec2', + VECTOR3: 'vec3', + VECTOR4: 'vec4', + MATRIX2: 'mat2', + MATRIX3: 'mat3', + MATRIX4: 'mat4' +}; + +/** + * Access types of a node. These are relevant for compute and storage usage. + * + * @property {string} READ_ONLY Read-only access + * @property {string} WRITE_ONLY Write-only access. + * @property {string} READ_WRITE Read and write access. + */ +const NodeAccess = { + READ_ONLY: 'readOnly', + WRITE_ONLY: 'writeOnly', + READ_WRITE: 'readWrite', +}; + +const defaultShaderStages = [ 'fragment', 'vertex' ]; +const defaultBuildStages = [ 'setup', 'analyze', 'generate' ]; +const shaderStages = [ ...defaultShaderStages, 'compute' ]; +const vectorComponents = [ 'x', 'y', 'z', 'w' ]; + +const _parentBuildStage = { + analyze: 'setup', + generate: 'analyze' +}; + +let _nodeId = 0; + +/** + * Base class for all nodes. + * + * @augments EventDispatcher + */ +class Node extends EventDispatcher { + + static get type() { + + return 'Node'; + + } + + /** + * Constructs a new node. + * + * @param {?string} nodeType - The node type. + */ + constructor( nodeType = null ) { + + super(); + + /** + * The node type. This represents the result type of the node (e.g. `float` or `vec3`). + * + * @type {?string} + * @default null + */ + this.nodeType = nodeType; + + /** + * The update type of the node's {@link Node#update} method. Possible values are listed in {@link NodeUpdateType}. + * + * @type {string} + * @default 'none' + */ + this.updateType = NodeUpdateType.NONE; + + /** + * The update type of the node's {@link Node#updateBefore} method. Possible values are listed in {@link NodeUpdateType}. + * + * @type {string} + * @default 'none' + */ + this.updateBeforeType = NodeUpdateType.NONE; + + /** + * The update type of the node's {@link Node#updateAfter} method. Possible values are listed in {@link NodeUpdateType}. + * + * @type {string} + * @default 'none' + */ + this.updateAfterType = NodeUpdateType.NONE; + + /** + * The UUID of the node. + * + * @type {string} + * @readonly + */ + this.uuid = MathUtils.generateUUID(); + + /** + * The version of the node. The version automatically is increased when {@link Node#needsUpdate} is set to `true`. + * + * @type {number} + * @readonly + * @default 0 + */ + this.version = 0; + + /** + * Whether this node is global or not. This property is relevant for the internal + * node caching system. All nodes which should be declared just once should + * set this flag to `true` (a typical example is {@link AttributeNode}). + * + * @type {boolean} + * @default false + */ + this.global = false; + + /** + * Create a list of parents for this node during the build process. + * + * @type {boolean} + * @default false + */ + this.parents = false; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isNode = true; + + // private + + /** + * The cache key of this node. + * + * @private + * @type {?number} + * @default null + */ + this._cacheKey = null; + + /** + * The cache key 's version. + * + * @private + * @type {number} + * @default 0 + */ + this._cacheKeyVersion = 0; + + Object.defineProperty( this, 'id', { value: _nodeId ++ } ); + + } + + /** + * Set this property to `true` when the node should be regenerated. + * + * @type {boolean} + * @default false + * @param {boolean} value + */ + set needsUpdate( value ) { + + if ( value === true ) { + + this.version ++; + + } + + } + + /** + * The type of the class. The value is usually the constructor name. + * + * @type {string} + * @readonly + */ + get type() { + + return this.constructor.type; + + } + + /** + * Convenient method for defining {@link Node#update}. + * + * @param {Function} callback - The update method. + * @param {string} updateType - The update type. + * @return {Node} A reference to this node. + */ + onUpdate( callback, updateType ) { + + this.updateType = updateType; + this.update = callback.bind( this.getSelf() ); + + return this; + + } + + /** + * Convenient method for defining {@link Node#update}. Similar to {@link Node#onUpdate}, but + * this method automatically sets the update type to `FRAME`. + * + * @param {Function} callback - The update method. + * @return {Node} A reference to this node. + */ + onFrameUpdate( callback ) { + + return this.onUpdate( callback, NodeUpdateType.FRAME ); + + } + + /** + * Convenient method for defining {@link Node#update}. Similar to {@link Node#onUpdate}, but + * this method automatically sets the update type to `RENDER`. + * + * @param {Function} callback - The update method. + * @return {Node} A reference to this node. + */ + onRenderUpdate( callback ) { + + return this.onUpdate( callback, NodeUpdateType.RENDER ); + + } + + /** + * Convenient method for defining {@link Node#update}. Similar to {@link Node#onUpdate}, but + * this method automatically sets the update type to `OBJECT`. + * + * @param {Function} callback - The update method. + * @return {Node} A reference to this node. + */ + onObjectUpdate( callback ) { + + return this.onUpdate( callback, NodeUpdateType.OBJECT ); + + } + + /** + * Convenient method for defining {@link Node#updateReference}. + * + * @param {Function} callback - The update method. + * @return {Node} A reference to this node. + */ + onReference( callback ) { + + this.updateReference = callback.bind( this.getSelf() ); + + return this; + + } + + /** + * The `this` reference might point to a Proxy so this method can be used + * to get the reference to the actual node instance. + * + * @return {Node} A reference to the node. + */ + getSelf() { + + // Returns non-node object. + + return this.self || this; + + } + + /** + * Nodes might refer to other objects like materials. This method allows to dynamically update the reference + * to such objects based on a given state (e.g. the current node frame or builder). + * + * @param {any} state - This method can be invocated in different contexts so `state` can refer to any object type. + * @return {any} The updated reference. + */ + updateReference( /*state*/ ) { + + return this; + + } + + /** + * By default this method returns the value of the {@link Node#global} flag. This method + * can be overwritten in derived classes if an analytical way is required to determine the + * global cache referring to the current shader-stage. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {boolean} Whether this node is global or not. + */ + isGlobal( /*builder*/ ) { + + return this.global; + + } + + /** + * Generator function that can be used to iterate over the child nodes. + * + * @generator + * @yields {Node} A child node. + */ + * getChildren() { + + for ( const { childNode } of getNodeChildren( this ) ) { + + yield childNode; + + } + + } + + /** + * Calling this method dispatches the `dispose` event. This event can be used + * to register event listeners for clean up tasks. + */ + dispose() { + + this.dispatchEvent( { type: 'dispose' } ); + + } + + /** + * Callback for {@link Node#traverse}. + * + * @callback traverseCallback + * @param {Node} node - The current node. + */ + + /** + * Can be used to traverse through the node's hierarchy. + * + * @param {traverseCallback} callback - A callback that is executed per node. + */ + traverse( callback ) { + + callback( this ); + + for ( const childNode of this.getChildren() ) { + + childNode.traverse( callback ); + + } + + } + + /** + * Returns the cache key for this node. + * + * @param {boolean} [force=false] - When set to `true`, a recomputation of the cache key is forced. + * @return {number} The cache key of the node. + */ + getCacheKey( force = false ) { + + force = force || this.version !== this._cacheKeyVersion; + + if ( force === true || this._cacheKey === null ) { + + this._cacheKey = hash$1( getCacheKey$1( this, force ), this.customCacheKey() ); + this._cacheKeyVersion = this.version; + + } + + return this._cacheKey; + + } + + /** + * Generate a custom cache key for this node. + * + * @return {number} The cache key of the node. + */ + customCacheKey() { + + return 0; + + } + + /** + * Returns the references to this node which is by default `this`. + * + * @return {Node} A reference to this node. + */ + getScope() { + + return this; + + } + + /** + * Returns the hash of the node which is used to identify the node. By default it's + * the {@link Node#uuid} however derived node classes might have to overwrite this method + * depending on their implementation. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The hash. + */ + getHash( /*builder*/ ) { + + return this.uuid; + + } + + /** + * Returns the update type of {@link Node#update}. + * + * @return {NodeUpdateType} The update type. + */ + getUpdateType() { + + return this.updateType; + + } + + /** + * Returns the update type of {@link Node#updateBefore}. + * + * @return {NodeUpdateType} The update type. + */ + getUpdateBeforeType() { + + return this.updateBeforeType; + + } + + /** + * Returns the update type of {@link Node#updateAfter}. + * + * @return {NodeUpdateType} The update type. + */ + getUpdateAfterType() { + + return this.updateAfterType; + + } + + /** + * Certain types are composed of multiple elements. For example a `vec3` + * is composed of three `float` values. This method returns the type of + * these elements. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The type of the node. + */ + getElementType( builder ) { + + const type = this.getNodeType( builder ); + const elementType = builder.getElementType( type ); + + return elementType; + + } + + /** + * Returns the node member type for the given name. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {string} name - The name of the member. + * @return {string} The type of the node. + */ + getMemberType( /*builder, name*/ ) { + + return 'void'; + + } + + /** + * Returns the node's type. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The type of the node. + */ + getNodeType( builder ) { + + const nodeProperties = builder.getNodeProperties( this ); + + if ( nodeProperties.outputNode ) { + + return nodeProperties.outputNode.getNodeType( builder ); + + } + + return this.nodeType; + + } + + /** + * This method is used during the build process of a node and ensures + * equal nodes are not built multiple times but just once. For example if + * `attribute( 'uv' )` is used multiple times by the user, the build + * process makes sure to process just the first node. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {Node} The shared node if possible. Otherwise `this` is returned. + */ + getShared( builder ) { + + const hash = this.getHash( builder ); + const nodeFromHash = builder.getNodeFromHash( hash ); + + return nodeFromHash || this; + + } + + /** + * Represents the setup stage which is the first step of the build process, see {@link Node#build} method. + * This method is often overwritten in derived modules to prepare the node which is used as the output/result. + * The output node must be returned in the `return` statement. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {?Node} The output node. + */ + setup( builder ) { + + const nodeProperties = builder.getNodeProperties( this ); + + let index = 0; + + for ( const childNode of this.getChildren() ) { + + nodeProperties[ 'node' + index ++ ] = childNode; + + } + + // return a outputNode if exists or null + + return nodeProperties.outputNode || null; + + } + + /** + * Represents the analyze stage which is the second step of the build process, see {@link Node#build} method. + * This stage analyzes the node hierarchy and ensures descendent nodes are built. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {?Node} output - The target output node. + */ + analyze( builder, output = null ) { + + const usageCount = builder.increaseUsage( this ); + + if ( this.parents === true ) { + + const nodeData = builder.getDataFromNode( this, 'any' ); + nodeData.stages = nodeData.stages || {}; + nodeData.stages[ builder.shaderStage ] = nodeData.stages[ builder.shaderStage ] || []; + nodeData.stages[ builder.shaderStage ].push( output ); + + } + + if ( usageCount === 1 ) { + + // node flow children + + const nodeProperties = builder.getNodeProperties( this ); + + for ( const childNode of Object.values( nodeProperties ) ) { + + if ( childNode && childNode.isNode === true ) { + + childNode.build( builder, this ); + + } + + } + + } + + } + + /** + * Represents the generate stage which is the third step of the build process, see {@link Node#build} method. + * This state builds the output node and returns the resulting shader string. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {?string} output - Can be used to define the output type. + * @return {?string} The generated shader string. + */ + generate( builder, output ) { + + const { outputNode } = builder.getNodeProperties( this ); + + if ( outputNode && outputNode.isNode === true ) { + + return outputNode.build( builder, output ); + + } + + } + + /** + * The method can be implemented to update the node's internal state before it is used to render an object. + * The {@link Node#updateBeforeType} property defines how often the update is executed. + * + * @abstract + * @param {NodeFrame} frame - A reference to the current node frame. + * @return {?boolean} An optional bool that indicates whether the implementation actually performed an update or not (e.g. due to caching). + */ + updateBefore( /*frame*/ ) { + + console.warn( 'Abstract function.' ); + + } + + /** + * The method can be implemented to update the node's internal state after it was used to render an object. + * The {@link Node#updateAfterType} property defines how often the update is executed. + * + * @abstract + * @param {NodeFrame} frame - A reference to the current node frame. + * @return {?boolean} An optional bool that indicates whether the implementation actually performed an update or not (e.g. due to caching). + */ + updateAfter( /*frame*/ ) { + + console.warn( 'Abstract function.' ); + + } + + /** + * The method can be implemented to update the node's internal state when it is used to render an object. + * The {@link Node#updateType} property defines how often the update is executed. + * + * @abstract + * @param {NodeFrame} frame - A reference to the current node frame. + * @return {?boolean} An optional bool that indicates whether the implementation actually performed an update or not (e.g. due to caching). + */ + update( /*frame*/ ) { + + console.warn( 'Abstract function.' ); + + } + + /** + * This method performs the build of a node. The behavior and return value depend on the current build stage: + * - **setup**: Prepares the node and its children for the build process. This process can also create new nodes. Returns the node itself or a variant. + * - **analyze**: Analyzes the node hierarchy for optimizations in the code generation stage. Returns `null`. + * - **generate**: Generates the shader code for the node. Returns the generated shader string. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {string|Node|null} [output=null] - Can be used to define the output type. + * @return {Node|string|null} The result of the build process, depending on the build stage. + */ + build( builder, output = null ) { + + const refNode = this.getShared( builder ); + + if ( this !== refNode ) { + + return refNode.build( builder, output ); + + } + + // + + const nodeData = builder.getDataFromNode( this ); + nodeData.buildStages = nodeData.buildStages || {}; + nodeData.buildStages[ builder.buildStage ] = true; + + const parentBuildStage = _parentBuildStage[ builder.buildStage ]; + + if ( parentBuildStage && nodeData.buildStages[ parentBuildStage ] !== true ) { + + // force parent build stage (setup or analyze) + + const previousBuildStage = builder.getBuildStage(); + + builder.setBuildStage( parentBuildStage ); + + this.build( builder ); + + builder.setBuildStage( previousBuildStage ); + + } + + // + + builder.addNode( this ); + builder.addChain( this ); + + /* Build stages expected results: + - "setup" -> Node + - "analyze" -> null + - "generate" -> String + */ + let result = null; + + const buildStage = builder.getBuildStage(); + + if ( buildStage === 'setup' ) { + + this.updateReference( builder ); + + const properties = builder.getNodeProperties( this ); + + if ( properties.initialized !== true ) { + + //const stackNodesBeforeSetup = builder.stack.nodes.length; + + properties.initialized = true; + properties.outputNode = this.setup( builder ) || properties.outputNode || null; + + /*if ( isNodeOutput && builder.stack.nodes.length !== stackNodesBeforeSetup ) { + + // !! no outputNode !! + //outputNode = builder.stack; + + }*/ + + for ( const childNode of Object.values( properties ) ) { + + if ( childNode && childNode.isNode === true ) { + + if ( childNode.parents === true ) { + + const childProperties = builder.getNodeProperties( childNode ); + childProperties.parents = childProperties.parents || []; + childProperties.parents.push( this ); + + } + + childNode.build( builder ); + + } + + } + + } + + result = properties.outputNode; + + } else if ( buildStage === 'analyze' ) { + + this.analyze( builder, output ); + + } else if ( buildStage === 'generate' ) { + + const isGenerateOnce = this.generate.length === 1; + + if ( isGenerateOnce ) { + + const type = this.getNodeType( builder ); + const nodeData = builder.getDataFromNode( this ); + + result = nodeData.snippet; + + if ( result === undefined ) { + + if ( nodeData.generated === undefined ) { + + nodeData.generated = true; + + result = this.generate( builder ) || ''; + + nodeData.snippet = result; + + } else { + + console.warn( 'THREE.Node: Recursion detected.', this ); + + result = '/* Recursion detected. */'; + + } + + } else if ( nodeData.flowCodes !== undefined && builder.context.nodeBlock !== undefined ) { + + builder.addFlowCodeHierarchy( this, builder.context.nodeBlock ); + + } + + result = builder.format( result, type, output ); + + } else { + + result = this.generate( builder, output ) || ''; + + } + + } + + builder.removeChain( this ); + builder.addSequentialNode( this ); + + return result; + + } + + /** + * Returns the child nodes as a JSON object. + * + * @return {Array} An iterable list of serialized child objects as JSON. + */ + getSerializeChildren() { + + return getNodeChildren( this ); + + } + + /** + * Serializes the node to JSON. + * + * @param {Object} json - The output JSON object. + */ + serialize( json ) { + + const nodeChildren = this.getSerializeChildren(); + + const inputNodes = {}; + + for ( const { property, index, childNode } of nodeChildren ) { + + if ( index !== undefined ) { + + if ( inputNodes[ property ] === undefined ) { + + inputNodes[ property ] = Number.isInteger( index ) ? [] : {}; + + } + + inputNodes[ property ][ index ] = childNode.toJSON( json.meta ).uuid; + + } else { + + inputNodes[ property ] = childNode.toJSON( json.meta ).uuid; + + } + + } + + if ( Object.keys( inputNodes ).length > 0 ) { + + json.inputNodes = inputNodes; + + } + + } + + /** + * Deserializes the node from the given JSON. + * + * @param {Object} json - The JSON object. + */ + deserialize( json ) { + + if ( json.inputNodes !== undefined ) { + + const nodes = json.meta.nodes; + + for ( const property in json.inputNodes ) { + + if ( Array.isArray( json.inputNodes[ property ] ) ) { + + const inputArray = []; + + for ( const uuid of json.inputNodes[ property ] ) { + + inputArray.push( nodes[ uuid ] ); + + } + + this[ property ] = inputArray; + + } else if ( typeof json.inputNodes[ property ] === 'object' ) { + + const inputObject = {}; + + for ( const subProperty in json.inputNodes[ property ] ) { + + const uuid = json.inputNodes[ property ][ subProperty ]; + + inputObject[ subProperty ] = nodes[ uuid ]; + + } + + this[ property ] = inputObject; + + } else { + + const uuid = json.inputNodes[ property ]; + + this[ property ] = nodes[ uuid ]; + + } + + } + + } + + } + + /** + * Serializes the node into the three.js JSON Object/Scene format. + * + * @param {?Object} meta - An optional JSON object that already holds serialized data from other scene objects. + * @return {Object} The serialized node. + */ + toJSON( meta ) { + + const { uuid, type } = this; + const isRoot = ( meta === undefined || typeof meta === 'string' ); + + if ( isRoot ) { + + meta = { + textures: {}, + images: {}, + nodes: {} + }; + + } + + // serialize + + let data = meta.nodes[ uuid ]; + + if ( data === undefined ) { + + data = { + uuid, + type, + meta, + metadata: { + version: 4.7, + type: 'Node', + generator: 'Node.toJSON' + } + }; + + if ( isRoot !== true ) meta.nodes[ data.uuid ] = data; + + this.serialize( data ); + + delete data.meta; + + } + + // TODO: Copied from Object3D.toJSON + + function extractFromCache( cache ) { + + const values = []; + + for ( const key in cache ) { + + const data = cache[ key ]; + delete data.metadata; + values.push( data ); + + } + + return values; + + } + + if ( isRoot ) { + + const textures = extractFromCache( meta.textures ); + const images = extractFromCache( meta.images ); + const nodes = extractFromCache( meta.nodes ); + + if ( textures.length > 0 ) data.textures = textures; + if ( images.length > 0 ) data.images = images; + if ( nodes.length > 0 ) data.nodes = nodes; + + } + + return data; + + } + +} + +/** + * Base class for representing element access on an array-like + * node data structures. + * + * @augments Node + */ +class ArrayElementNode extends Node { // @TODO: If extending from TempNode it breaks webgpu_compute + + static get type() { + + return 'ArrayElementNode'; + + } + + /** + * Constructs an array element node. + * + * @param {Node} node - The array-like node. + * @param {Node} indexNode - The index node that defines the element access. + */ + constructor( node, indexNode ) { + + super(); + + /** + * The array-like node. + * + * @type {Node} + */ + this.node = node; + + /** + * The index node that defines the element access. + * + * @type {Node} + */ + this.indexNode = indexNode; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isArrayElementNode = true; + + } + + /** + * This method is overwritten since the node type is inferred from the array-like node. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType( builder ) { + + return this.node.getElementType( builder ); + + } + + generate( builder ) { + + const indexType = this.indexNode.getNodeType( builder ); + + const nodeSnippet = this.node.build( builder ); + const indexSnippet = this.indexNode.build( builder, ! builder.isVector( indexType ) && builder.isInteger( indexType ) ? indexType : 'uint' ); + + return `${ nodeSnippet }[ ${ indexSnippet } ]`; + + } + +} + +/** + * This module is part of the TSL core and usually not used in app level code. + * It represents a convert operation during the shader generation process + * meaning it converts the data type of a node to a target data type. + * + * @augments Node + */ +class ConvertNode extends Node { + + static get type() { + + return 'ConvertNode'; + + } + + /** + * Constructs a new convert node. + * + * @param {Node} node - The node which type should be converted. + * @param {string} convertTo - The target node type. Multiple types can be defined by separating them with a `|` sign. + */ + constructor( node, convertTo ) { + + super(); + + /** + * The node which type should be converted. + * + * @type {Node} + */ + this.node = node; + + /** + * The target node type. Multiple types can be defined by separating them with a `|` sign. + * + * @type {string} + */ + this.convertTo = convertTo; + + } + + /** + * This method is overwritten since the implementation tries to infer the best + * matching type from the {@link ConvertNode#convertTo} property. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType( builder ) { + + const requestType = this.node.getNodeType( builder ); + + let convertTo = null; + + for ( const overloadingType of this.convertTo.split( '|' ) ) { + + if ( convertTo === null || builder.getTypeLength( requestType ) === builder.getTypeLength( overloadingType ) ) { + + convertTo = overloadingType; + + } + + } + + return convertTo; + + } + + serialize( data ) { + + super.serialize( data ); + + data.convertTo = this.convertTo; + + } + + deserialize( data ) { + + super.deserialize( data ); + + this.convertTo = data.convertTo; + + } + + generate( builder, output ) { + + const node = this.node; + const type = this.getNodeType( builder ); + + const snippet = node.build( builder, type ); + + return builder.format( snippet, type, output ); + + } + +} + +/** + * This module uses cache management to create temporary variables + * if the node is used more than once to prevent duplicate calculations. + * + * The class acts as a base class for many other nodes types. + * + * @augments Node + */ +class TempNode extends Node { + + static get type() { + + return 'TempNode'; + + } + + /** + * Constructs a temp node. + * + * @param {?string} nodeType - The node type. + */ + constructor( nodeType = null ) { + + super( nodeType ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isTempNode = true; + + } + + /** + * Whether this node is used more than once in context of other nodes. + * + * @param {NodeBuilder} builder - The node builder. + * @return {boolean} A flag that indicates if there is more than one dependency to other nodes. + */ + hasDependencies( builder ) { + + return builder.getDataFromNode( this ).usageCount > 1; + + } + + build( builder, output ) { + + const buildStage = builder.getBuildStage(); + + if ( buildStage === 'generate' ) { + + const type = builder.getVectorType( this.getNodeType( builder, output ) ); + const nodeData = builder.getDataFromNode( this ); + + if ( nodeData.propertyName !== undefined ) { + + return builder.format( nodeData.propertyName, type, output ); + + } else if ( type !== 'void' && output !== 'void' && this.hasDependencies( builder ) ) { + + const snippet = super.build( builder, type ); + + const nodeVar = builder.getVarFromNode( this, null, type ); + const propertyName = builder.getPropertyName( nodeVar ); + + builder.addLineFlowCode( `${ propertyName } = ${ snippet }`, this ); + + nodeData.snippet = snippet; + nodeData.propertyName = propertyName; + + return builder.format( nodeData.propertyName, type, output ); + + } + + } + + return super.build( builder, output ); + + } + +} + +/** + * This module is part of the TSL core and usually not used in app level code. + * It represents a join operation during the shader generation process. + * For example in can compose/join two single floats into a `vec2` type. + * + * @augments TempNode + */ +class JoinNode extends TempNode { + + static get type() { + + return 'JoinNode'; + + } + + /** + * Constructs a new join node. + * + * @param {Array} nodes - An array of nodes that should be joined. + * @param {?string} [nodeType=null] - The node type. + */ + constructor( nodes = [], nodeType = null ) { + + super( nodeType ); + + /** + * An array of nodes that should be joined. + * + * @type {Array} + */ + this.nodes = nodes; + + } + + /** + * This method is overwritten since the node type must be inferred from the + * joined data length if not explicitly defined. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType( builder ) { + + if ( this.nodeType !== null ) { + + return builder.getVectorType( this.nodeType ); + + } + + return builder.getTypeFromLength( this.nodes.reduce( ( count, cur ) => count + builder.getTypeLength( cur.getNodeType( builder ) ), 0 ) ); + + } + + generate( builder, output ) { + + const type = this.getNodeType( builder ); + const maxLength = builder.getTypeLength( type ); + + const nodes = this.nodes; + + const primitiveType = builder.getComponentType( type ); + + const snippetValues = []; + + let length = 0; + + for ( const input of nodes ) { + + if ( length >= maxLength ) { + + console.error( `THREE.TSL: Length of parameters exceeds maximum length of function '${ type }()' type.` ); + break; + + } + + let inputType = input.getNodeType( builder ); + let inputTypeLength = builder.getTypeLength( inputType ); + let inputSnippet; + + if ( length + inputTypeLength > maxLength ) { + + console.error( `THREE.TSL: Length of '${ type }()' data exceeds maximum length of output type.` ); + + inputTypeLength = maxLength - length; + inputType = builder.getTypeFromLength( inputTypeLength ); + + } + + length += inputTypeLength; + inputSnippet = input.build( builder, inputType ); + + const inputPrimitiveType = builder.getComponentType( inputType ); + + if ( inputPrimitiveType !== primitiveType ) { + + inputSnippet = builder.format( inputSnippet, inputPrimitiveType, primitiveType ); + + } + + snippetValues.push( inputSnippet ); + + } + + const snippet = `${ builder.getType( type ) }( ${ snippetValues.join( ', ' ) } )`; + + return builder.format( snippet, type, output ); + + } + +} + +const _stringVectorComponents = vectorComponents.join( '' ); + +/** + * This module is part of the TSL core and usually not used in app level code. + * `SplitNode` represents a property access operation which means it is + * used to implement any `.xyzw`, `.rgba` and `stpq` usage on node objects. + * For example: + * ```js + * const redValue = color.r; + * ``` + * + * @augments Node + */ +class SplitNode extends Node { + + static get type() { + + return 'SplitNode'; + + } + + /** + * Constructs a new split node. + * + * @param {Node} node - The node that should be accessed. + * @param {string} [components='x'] - The components that should be accessed. + */ + constructor( node, components = 'x' ) { + + super(); + + /** + * The node that should be accessed. + * + * @type {Node} + */ + this.node = node; + + /** + * The components that should be accessed. + * + * @type {string} + */ + this.components = components; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSplitNode = true; + + } + + /** + * Returns the vector length which is computed based on the requested components. + * + * @return {number} The vector length. + */ + getVectorLength() { + + let vectorLength = this.components.length; + + for ( const c of this.components ) { + + vectorLength = Math.max( vectorComponents.indexOf( c ) + 1, vectorLength ); + + } + + return vectorLength; + + } + + /** + * Returns the component type of the node's type. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The component type. + */ + getComponentType( builder ) { + + return builder.getComponentType( this.node.getNodeType( builder ) ); + + } + + /** + * This method is overwritten since the node type is inferred from requested components. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType( builder ) { + + return builder.getTypeFromLength( this.components.length, this.getComponentType( builder ) ); + + } + + generate( builder, output ) { + + const node = this.node; + const nodeTypeLength = builder.getTypeLength( node.getNodeType( builder ) ); + + let snippet = null; + + if ( nodeTypeLength > 1 ) { + + let type = null; + + const componentsLength = this.getVectorLength(); + + if ( componentsLength >= nodeTypeLength ) { + + // needed expand the input node + + type = builder.getTypeFromLength( this.getVectorLength(), this.getComponentType( builder ) ); + + } + + const nodeSnippet = node.build( builder, type ); + + if ( this.components.length === nodeTypeLength && this.components === _stringVectorComponents.slice( 0, this.components.length ) ) { + + // unnecessary swizzle + + snippet = builder.format( nodeSnippet, type, output ); + + } else { + + snippet = builder.format( `${nodeSnippet}.${this.components}`, this.getNodeType( builder ), output ); + + } + + } else { + + // ignore .components if .node returns float/integer + + snippet = node.build( builder, output ); + + } + + return snippet; + + } + + serialize( data ) { + + super.serialize( data ); + + data.components = this.components; + + } + + deserialize( data ) { + + super.deserialize( data ); + + this.components = data.components; + + } + +} + +/** + * This module is part of the TSL core and usually not used in app level code. + * `SetNode` represents a set operation which means it is used to implement any + * `setXYZW()`, `setRGBA()` and `setSTPQ()` method invocations on node objects. + * For example: + * ```js + * materialLine.colorNode = color( 0, 0, 0 ).setR( float( 1 ) ); + * ``` + * + * @augments TempNode + */ +class SetNode extends TempNode { + + static get type() { + + return 'SetNode'; + + } + + /** + * Constructs a new set node. + * + * @param {Node} sourceNode - The node that should be updated. + * @param {string} components - The components that should be updated. + * @param {Node} targetNode - The value node. + */ + constructor( sourceNode, components, targetNode ) { + + super(); + + /** + * The node that should be updated. + * + * @type {Node} + */ + this.sourceNode = sourceNode; + + /** + * The components that should be updated. + * + * @type {string} + */ + this.components = components; + + /** + * The value node. + * + * @type {Node} + */ + this.targetNode = targetNode; + + } + + /** + * This method is overwritten since the node type is inferred from {@link SetNode#sourceNode}. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType( builder ) { + + return this.sourceNode.getNodeType( builder ); + + } + + generate( builder ) { + + const { sourceNode, components, targetNode } = this; + + const sourceType = this.getNodeType( builder ); + + const componentType = builder.getComponentType( targetNode.getNodeType( builder ) ); + const targetType = builder.getTypeFromLength( components.length, componentType ); + + const targetSnippet = targetNode.build( builder, targetType ); + const sourceSnippet = sourceNode.build( builder, sourceType ); + + const length = builder.getTypeLength( sourceType ); + const snippetValues = []; + + for ( let i = 0; i < length; i ++ ) { + + const component = vectorComponents[ i ]; + + if ( component === components[ 0 ] ) { + + snippetValues.push( targetSnippet ); + + i += components.length - 1; + + } else { + + snippetValues.push( sourceSnippet + '.' + component ); + + } + + } + + return `${ builder.getType( sourceType ) }( ${ snippetValues.join( ', ' ) } )`; + + } + +} + +/** + * This module is part of the TSL core and usually not used in app level code. + * It represents a flip operation during the shader generation process + * meaning it flips normalized values with the following formula: + * ``` + * x = 1 - x; + * ``` + * `FlipNode` is internally used to implement any `flipXYZW()`, `flipRGBA()` and + * `flipSTPQ()` method invocations on node objects. For example: + * ```js + * uvNode = uvNode.flipY(); + * ``` + * + * @augments TempNode + */ +class FlipNode extends TempNode { + + static get type() { + + return 'FlipNode'; + + } + + /** + * Constructs a new flip node. + * + * @param {Node} sourceNode - The node which component(s) should be flipped. + * @param {string} components - The components that should be flipped e.g. `'x'` or `'xy'`. + */ + constructor( sourceNode, components ) { + + super(); + + /** + * The node which component(s) should be flipped. + * + * @type {Node} + */ + this.sourceNode = sourceNode; + + /** + * The components that should be flipped e.g. `'x'` or `'xy'`. + * + * @type {string} + */ + this.components = components; + + } + + /** + * This method is overwritten since the node type is inferred from the source node. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType( builder ) { + + return this.sourceNode.getNodeType( builder ); + + } + + generate( builder ) { + + const { components, sourceNode } = this; + + const sourceType = this.getNodeType( builder ); + const sourceSnippet = sourceNode.build( builder ); + + const sourceCache = builder.getVarFromNode( this ); + const sourceProperty = builder.getPropertyName( sourceCache ); + + builder.addLineFlowCode( sourceProperty + ' = ' + sourceSnippet, this ); + + const length = builder.getTypeLength( sourceType ); + const snippetValues = []; + + let componentIndex = 0; + + for ( let i = 0; i < length; i ++ ) { + + const component = vectorComponents[ i ]; + + if ( component === components[ componentIndex ] ) { + + snippetValues.push( '1.0 - ' + ( sourceProperty + '.' + component ) ); + + componentIndex ++; + + } else { + + snippetValues.push( sourceProperty + '.' + component ); + + } + + } + + return `${ builder.getType( sourceType ) }( ${ snippetValues.join( ', ' ) } )`; + + } + +} + +/** + * Base class for representing data input nodes. + * + * @augments Node + */ +class InputNode extends Node { + + static get type() { + + return 'InputNode'; + + } + + /** + * Constructs a new input node. + * + * @param {any} value - The value of this node. This can be any JS primitive, functions, array buffers or even three.js objects (vector, matrices, colors). + * @param {?string} nodeType - The node type. If no explicit type is defined, the node tries to derive the type from its value. + */ + constructor( value, nodeType = null ) { + + super( nodeType ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isInputNode = true; + + /** + * The value of this node. This can be any JS primitive, functions, array buffers or even three.js objects (vector, matrices, colors). + * + * @type {any} + */ + this.value = value; + + /** + * The precision of the value in the shader. + * + * @type {?('low'|'medium'|'high')} + * @default null + */ + this.precision = null; + + } + + getNodeType( /*builder*/ ) { + + if ( this.nodeType === null ) { + + return getValueType( this.value ); + + } + + return this.nodeType; + + } + + /** + * Returns the input type of the node which is by default the node type. Derived modules + * might overwrite this method and use a fixed type or compute one analytically. + * + * A typical example for different input and node types are textures. The input type of a + * normal RGBA texture is `texture` whereas its node type is `vec4`. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The input type. + */ + getInputType( builder ) { + + return this.getNodeType( builder ); + + } + + /** + * Sets the precision to the given value. The method can be + * overwritten in derived classes if the final precision must be computed + * analytically. + * + * @param {('low'|'medium'|'high')} precision - The precision of the input value in the shader. + * @return {InputNode} A reference to this node. + */ + setPrecision( precision ) { + + this.precision = precision; + + return this; + + } + + serialize( data ) { + + super.serialize( data ); + + data.value = this.value; + + if ( this.value && this.value.toArray ) data.value = this.value.toArray(); + + data.valueType = getValueType( this.value ); + data.nodeType = this.nodeType; + + if ( data.valueType === 'ArrayBuffer' ) data.value = arrayBufferToBase64( data.value ); + + data.precision = this.precision; + + } + + deserialize( data ) { + + super.deserialize( data ); + + this.nodeType = data.nodeType; + this.value = Array.isArray( data.value ) ? getValueFromType( data.valueType, ...data.value ) : data.value; + + this.precision = data.precision || null; + + if ( this.value && this.value.fromArray ) this.value = this.value.fromArray( data.value ); + + } + + generate( /*builder, output*/ ) { + + console.warn( 'Abstract function.' ); + + } + +} + +const _regNum = /float|u?int/; + +/** + * Class for representing a constant value in the shader. + * + * @augments InputNode + */ +class ConstNode extends InputNode { + + static get type() { + + return 'ConstNode'; + + } + + /** + * Constructs a new input node. + * + * @param {any} value - The value of this node. Usually a JS primitive or three.js object (vector, matrix, color). + * @param {?string} nodeType - The node type. If no explicit type is defined, the node tries to derive the type from its value. + */ + constructor( value, nodeType = null ) { + + super( value, nodeType ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isConstNode = true; + + } + + /** + * Generates the shader string of the value with the current node builder. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The generated value as a shader string. + */ + generateConst( builder ) { + + return builder.generateConst( this.getNodeType( builder ), this.value ); + + } + + generate( builder, output ) { + + const type = this.getNodeType( builder ); + + if ( _regNum.test( type ) && _regNum.test( output ) ) { + + return builder.generateConst( output, this.value ); + + } + + return builder.format( this.generateConst( builder ), type, output ); + + } + +} + +/** + * Base class for representing member access on an object-like + * node data structures. + * + * @augments Node + */ +class MemberNode extends Node { + + static get type() { + + return 'MemberNode'; + + } + + /** + * Constructs an array element node. + * + * @param {Node} node - The array-like node. + * @param {string} property - The property name. + */ + constructor( node, property ) { + + super(); + + /** + * The array-like node. + * + * @type {Node} + */ + this.node = node; + + /** + * The property name. + * + * @type {Node} + */ + this.property = property; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMemberNode = true; + + } + + getNodeType( builder ) { + + return this.node.getMemberType( builder, this.property ); + + } + + generate( builder ) { + + const propertyName = this.node.build( builder ); + + return propertyName + '.' + this.property; + + } + +} + +let currentStack = null; + +const NodeElements = new Map(); + +function addMethodChaining( name, nodeElement ) { + + if ( NodeElements.has( name ) ) { + + console.warn( `THREE.TSL: Redefinition of method chaining '${ name }'.` ); + return; + + } + + if ( typeof nodeElement !== 'function' ) throw new Error( `THREE.TSL: Node element ${ name } is not a function` ); + + NodeElements.set( name, nodeElement ); + +} + +const parseSwizzle = ( props ) => props.replace( /r|s/g, 'x' ).replace( /g|t/g, 'y' ).replace( /b|p/g, 'z' ).replace( /a|q/g, 'w' ); +const parseSwizzleAndSort = ( props ) => parseSwizzle( props ).split( '' ).sort().join( '' ); + +const shaderNodeHandler = { + + setup( NodeClosure, params ) { + + const inputs = params.shift(); + + return NodeClosure( nodeObjects( inputs ), ...params ); + + }, + + get( node, prop, nodeObj ) { + + if ( typeof prop === 'string' && node[ prop ] === undefined ) { + + if ( node.isStackNode !== true && prop === 'assign' ) { + + return ( ...params ) => { + + currentStack.assign( nodeObj, ...params ); + + return nodeObj; + + }; + + } else if ( NodeElements.has( prop ) ) { + + const nodeElement = NodeElements.get( prop ); + + return node.isStackNode ? ( ...params ) => nodeObj.add( nodeElement( ...params ) ) : ( ...params ) => nodeElement( nodeObj, ...params ); + + } else if ( prop === 'self' ) { + + return node; + + } else if ( prop.endsWith( 'Assign' ) && NodeElements.has( prop.slice( 0, prop.length - 'Assign'.length ) ) ) { + + const nodeElement = NodeElements.get( prop.slice( 0, prop.length - 'Assign'.length ) ); + + return node.isStackNode ? ( ...params ) => nodeObj.assign( params[ 0 ], nodeElement( ...params ) ) : ( ...params ) => nodeObj.assign( nodeElement( nodeObj, ...params ) ); + + } else if ( /^[xyzwrgbastpq]{1,4}$/.test( prop ) === true ) { + + // accessing properties ( swizzle ) + + prop = parseSwizzle( prop ); + + return nodeObject( new SplitNode( nodeObj, prop ) ); + + } else if ( /^set[XYZWRGBASTPQ]{1,4}$/.test( prop ) === true ) { + + // set properties ( swizzle ) and sort to xyzw sequence + + prop = parseSwizzleAndSort( prop.slice( 3 ).toLowerCase() ); + + return ( value ) => nodeObject( new SetNode( node, prop, nodeObject( value ) ) ); + + } else if ( /^flip[XYZWRGBASTPQ]{1,4}$/.test( prop ) === true ) { + + // set properties ( swizzle ) and sort to xyzw sequence + + prop = parseSwizzleAndSort( prop.slice( 4 ).toLowerCase() ); + + return () => nodeObject( new FlipNode( nodeObject( node ), prop ) ); + + } else if ( prop === 'width' || prop === 'height' || prop === 'depth' ) { + + // accessing property + + if ( prop === 'width' ) prop = 'x'; + else if ( prop === 'height' ) prop = 'y'; + else if ( prop === 'depth' ) prop = 'z'; + + return nodeObject( new SplitNode( node, prop ) ); + + } else if ( /^\d+$/.test( prop ) === true ) { + + // accessing array + + return nodeObject( new ArrayElementNode( nodeObj, new ConstNode( Number( prop ), 'uint' ) ) ); + + } else if ( /^get$/.test( prop ) === true ) { + + // accessing properties + + return ( value ) => nodeObject( new MemberNode( nodeObj, value ) ); + + } + + } + + return Reflect.get( node, prop, nodeObj ); + + }, + + set( node, prop, value, nodeObj ) { + + if ( typeof prop === 'string' && node[ prop ] === undefined ) { + + // setting properties + + if ( /^[xyzwrgbastpq]{1,4}$/.test( prop ) === true || prop === 'width' || prop === 'height' || prop === 'depth' || /^\d+$/.test( prop ) === true ) { + + nodeObj[ prop ].assign( value ); + + return true; + + } + + } + + return Reflect.set( node, prop, value, nodeObj ); + + } + +}; + +const nodeObjectsCacheMap = new WeakMap(); +const nodeBuilderFunctionsCacheMap = new WeakMap(); + +const ShaderNodeObject = function ( obj, altType = null ) { + + const type = getValueType( obj ); + + if ( type === 'node' ) { + + let nodeObject = nodeObjectsCacheMap.get( obj ); + + if ( nodeObject === undefined ) { + + nodeObject = new Proxy( obj, shaderNodeHandler ); + + nodeObjectsCacheMap.set( obj, nodeObject ); + nodeObjectsCacheMap.set( nodeObject, nodeObject ); + + } + + return nodeObject; + + } else if ( ( altType === null && ( type === 'float' || type === 'boolean' ) ) || ( type && type !== 'shader' && type !== 'string' ) ) { + + return nodeObject( getConstNode( obj, altType ) ); + + } else if ( type === 'shader' ) { + + return obj.isFn ? obj : Fn( obj ); + + } + + return obj; + +}; + +const ShaderNodeObjects = function ( objects, altType = null ) { + + for ( const name in objects ) { + + objects[ name ] = nodeObject( objects[ name ], altType ); + + } + + return objects; + +}; + +const ShaderNodeArray = function ( array, altType = null ) { + + const len = array.length; + + for ( let i = 0; i < len; i ++ ) { + + array[ i ] = nodeObject( array[ i ], altType ); + + } + + return array; + +}; + +const ShaderNodeProxy = function ( NodeClass, scope = null, factor = null, settings = null ) { + + const assignNode = ( node ) => nodeObject( settings !== null ? Object.assign( node, settings ) : node ); + + let fn, name = scope, minParams, maxParams; + + function verifyParamsLimit( params ) { + + let tslName; + + if ( name ) tslName = /[a-z]/i.test( name ) ? name + '()' : name; + else tslName = NodeClass.type; + + if ( minParams !== undefined && params.length < minParams ) { + + console.error( `THREE.TSL: "${ tslName }" parameter length is less than minimum required.` ); + + return params.concat( new Array( minParams - params.length ).fill( 0 ) ); + + } else if ( maxParams !== undefined && params.length > maxParams ) { + + console.error( `THREE.TSL: "${ tslName }" parameter length exceeds limit.` ); + + return params.slice( 0, maxParams ); + + } + + return params; + + } + + if ( scope === null ) { + + fn = ( ...params ) => { + + return assignNode( new NodeClass( ...nodeArray( verifyParamsLimit( params ) ) ) ); + + }; + + } else if ( factor !== null ) { + + factor = nodeObject( factor ); + + fn = ( ...params ) => { + + return assignNode( new NodeClass( scope, ...nodeArray( verifyParamsLimit( params ) ), factor ) ); + + }; + + } else { + + fn = ( ...params ) => { + + return assignNode( new NodeClass( scope, ...nodeArray( verifyParamsLimit( params ) ) ) ); + + }; + + } + + fn.setParameterLength = ( ...params ) => { + + if ( params.length === 1 ) minParams = maxParams = params[ 0 ]; + else if ( params.length === 2 ) [ minParams, maxParams ] = params; + + return fn; + + }; + + fn.setName = ( value ) => { + + name = value; + + return fn; + + }; + + return fn; + +}; + +const ShaderNodeImmutable = function ( NodeClass, ...params ) { + + return nodeObject( new NodeClass( ...nodeArray( params ) ) ); + +}; + +class ShaderCallNodeInternal extends Node { + + constructor( shaderNode, inputNodes ) { + + super(); + + this.shaderNode = shaderNode; + this.inputNodes = inputNodes; + + this.isShaderCallNodeInternal = true; + + } + + getNodeType( builder ) { + + return this.shaderNode.nodeType || this.getOutputNode( builder ).getNodeType( builder ); + + } + + getMemberType( builder, name ) { + + return this.getOutputNode( builder ).getMemberType( builder, name ); + + } + + call( builder ) { + + const { shaderNode, inputNodes } = this; + + const properties = builder.getNodeProperties( shaderNode ); + + const subBuild = builder.getClosestSubBuild( shaderNode.subBuilds ) || ''; + const subBuildProperty = subBuild || 'default'; + + if ( properties[ subBuildProperty ] ) { + + return properties[ subBuildProperty ]; + + } + + // + + const previousSubBuildFn = builder.subBuildFn; + + builder.subBuildFn = subBuild; + + let result = null; + + if ( shaderNode.layout ) { + + let functionNodesCacheMap = nodeBuilderFunctionsCacheMap.get( builder.constructor ); + + if ( functionNodesCacheMap === undefined ) { + + functionNodesCacheMap = new WeakMap(); + + nodeBuilderFunctionsCacheMap.set( builder.constructor, functionNodesCacheMap ); + + } + + let functionNode = functionNodesCacheMap.get( shaderNode ); + + if ( functionNode === undefined ) { + + functionNode = nodeObject( builder.buildFunctionNode( shaderNode ) ); + + functionNodesCacheMap.set( shaderNode, functionNode ); + + } + + builder.addInclude( functionNode ); + + result = nodeObject( functionNode.call( inputNodes ) ); + + } else { + + const jsFunc = shaderNode.jsFunc; + const outputNode = inputNodes !== null || jsFunc.length > 1 ? jsFunc( inputNodes || [], builder ) : jsFunc( builder ); + + result = nodeObject( outputNode ); + + } + + builder.subBuildFn = previousSubBuildFn; + + if ( shaderNode.once ) { + + properties[ subBuildProperty ] = result; + + } + + return result; + + } + + setupOutput( builder ) { + + builder.addStack(); + + builder.stack.outputNode = this.call( builder ); + + return builder.removeStack(); + + } + + getOutputNode( builder ) { + + const properties = builder.getNodeProperties( this ); + const subBuildOutput = builder.getSubBuildOutput( this ); + + properties[ subBuildOutput ] = properties[ subBuildOutput ] || this.setupOutput( builder ); + properties[ subBuildOutput ].subBuild = builder.getClosestSubBuild( this ); + + return properties[ subBuildOutput ]; + + } + + build( builder, output = null ) { + + let result = null; + + const buildStage = builder.getBuildStage(); + const properties = builder.getNodeProperties( this ); + + const subBuildOutput = builder.getSubBuildOutput( this ); + const outputNode = this.getOutputNode( builder ); + + if ( buildStage === 'setup' ) { + + const subBuildInitialized = builder.getSubBuildProperty( 'initialized', this ); + + if ( properties[ subBuildInitialized ] !== true ) { + + properties[ subBuildInitialized ] = true; + + properties[ subBuildOutput ] = this.getOutputNode( builder ); + properties[ subBuildOutput ].build( builder ); + + // If the shaderNode has subBuilds, add them to the chaining nodes + // so they can be built later in the build process. + + if ( this.shaderNode.subBuilds ) { + + for ( const node of builder.chaining ) { + + const nodeData = builder.getDataFromNode( node, 'any' ); + nodeData.subBuilds = nodeData.subBuilds || new Set(); + + for ( const subBuild of this.shaderNode.subBuilds ) { + + nodeData.subBuilds.add( subBuild ); + + } + + //builder.getDataFromNode( node ).subBuilds = nodeData.subBuilds; + + } + + } + + } + + result = properties[ subBuildOutput ]; + + } else if ( buildStage === 'analyze' ) { + + outputNode.build( builder, output ); + + } else if ( buildStage === 'generate' ) { + + result = outputNode.build( builder, output ) || ''; + + } + + return result; + + } + +} + +class ShaderNodeInternal extends Node { + + constructor( jsFunc, nodeType ) { + + super( nodeType ); + + this.jsFunc = jsFunc; + this.layout = null; + + this.global = true; + + this.once = false; + + } + + setLayout( layout ) { + + this.layout = layout; + + return this; + + } + + call( inputs = null ) { + + nodeObjects( inputs ); + + return nodeObject( new ShaderCallNodeInternal( this, inputs ) ); + + } + + setup() { + + return this.call(); + + } + +} + +const bools = [ false, true ]; +const uints = [ 0, 1, 2, 3 ]; +const ints = [ -1, -2 ]; +const floats = [ 0.5, 1.5, 1 / 3, 1e-6, 1e6, Math.PI, Math.PI * 2, 1 / Math.PI, 2 / Math.PI, 1 / ( Math.PI * 2 ), Math.PI / 2 ]; + +const boolsCacheMap = new Map(); +for ( const bool of bools ) boolsCacheMap.set( bool, new ConstNode( bool ) ); + +const uintsCacheMap = new Map(); +for ( const uint of uints ) uintsCacheMap.set( uint, new ConstNode( uint, 'uint' ) ); + +const intsCacheMap = new Map( [ ...uintsCacheMap ].map( el => new ConstNode( el.value, 'int' ) ) ); +for ( const int of ints ) intsCacheMap.set( int, new ConstNode( int, 'int' ) ); + +const floatsCacheMap = new Map( [ ...intsCacheMap ].map( el => new ConstNode( el.value ) ) ); +for ( const float of floats ) floatsCacheMap.set( float, new ConstNode( float ) ); +for ( const float of floats ) floatsCacheMap.set( - float, new ConstNode( - float ) ); + +const cacheMaps = { bool: boolsCacheMap, uint: uintsCacheMap, ints: intsCacheMap, float: floatsCacheMap }; + +const constNodesCacheMap = new Map( [ ...boolsCacheMap, ...floatsCacheMap ] ); + +const getConstNode = ( value, type ) => { + + if ( constNodesCacheMap.has( value ) ) { + + return constNodesCacheMap.get( value ); + + } else if ( value.isNode === true ) { + + return value; + + } else { + + return new ConstNode( value, type ); + + } + +}; + +const ConvertType = function ( type, cacheMap = null ) { + + return ( ...params ) => { + + if ( params.length === 0 || ( ! [ 'bool', 'float', 'int', 'uint' ].includes( type ) && params.every( param => typeof param !== 'object' ) ) ) { + + params = [ getValueFromType( type, ...params ) ]; + + } + + if ( params.length === 1 && cacheMap !== null && cacheMap.has( params[ 0 ] ) ) { + + return nodeObject( cacheMap.get( params[ 0 ] ) ); + + } + + if ( params.length === 1 ) { + + const node = getConstNode( params[ 0 ], type ); + if ( node.nodeType === type ) return nodeObject( node ); + return nodeObject( new ConvertNode( node, type ) ); + + } + + const nodes = params.map( param => getConstNode( param ) ); + return nodeObject( new JoinNode( nodes, type ) ); + + }; + +}; + +// exports + +const defined = ( v ) => typeof v === 'object' && v !== null ? v.value : v; // TODO: remove boolean conversion and defined function + +// utils + +const getConstNodeType = ( value ) => ( value !== undefined && value !== null ) ? ( value.nodeType || value.convertTo || ( typeof value === 'string' ? value : null ) ) : null; + +// shader node base + +function ShaderNode( jsFunc, nodeType ) { + + return new Proxy( new ShaderNodeInternal( jsFunc, nodeType ), shaderNodeHandler ); + +} + +const nodeObject = ( val, altType = null ) => /* new */ ShaderNodeObject( val, altType ); +const nodeObjects = ( val, altType = null ) => new ShaderNodeObjects( val, altType ); +const nodeArray = ( val, altType = null ) => new ShaderNodeArray( val, altType ); +const nodeProxy = ( ...params ) => new ShaderNodeProxy( ...params ); +const nodeImmutable = ( ...params ) => new ShaderNodeImmutable( ...params ); + +let fnId = 0; + +const Fn = ( jsFunc, layout = null ) => { + + let nodeType = null; + + if ( layout !== null ) { + + if ( typeof layout === 'object' ) { + + nodeType = layout.return; + + } else { + + if ( typeof layout === 'string' ) { + + nodeType = layout; + + } else { + + console.error( 'THREE.TSL: Invalid layout type.' ); + + } + + layout = null; + + } + + } + + const shaderNode = new ShaderNode( jsFunc, nodeType ); + + const fn = ( ...params ) => { + + let inputs; + + nodeObjects( params ); + + const isArrayAsParameter = params[ 0 ] && ( params[ 0 ].isNode || Object.getPrototypeOf( params[ 0 ] ) !== Object.prototype ); + + if ( isArrayAsParameter ) { + + inputs = [ ...params ]; + + } else { + + inputs = params[ 0 ]; + + } + + const fnCall = shaderNode.call( inputs ); + + if ( nodeType === 'void' ) fnCall.toStack(); + + return fnCall; + + }; + + fn.shaderNode = shaderNode; + fn.id = shaderNode.id; + + fn.isFn = true; + + fn.getNodeType = ( ...params ) => shaderNode.getNodeType( ...params ); + fn.getCacheKey = ( ...params ) => shaderNode.getCacheKey( ...params ); + + fn.setLayout = ( layout ) => { + + shaderNode.setLayout( layout ); + + return fn; + + }; + + fn.once = ( subBuilds = null ) => { + + shaderNode.once = true; + shaderNode.subBuilds = subBuilds; + + return fn; + + }; + + if ( layout !== null ) { + + if ( typeof layout.inputs !== 'object' ) { + + const fullLayout = { + name: 'fn' + fnId ++, + type: nodeType, + inputs: [] + }; + + for ( const name in layout ) { + + if ( name === 'return' ) continue; + + fullLayout.inputs.push( { + name: name, + type: layout[ name ] + } ); + + } + + layout = fullLayout; + + } + + fn.setLayout( layout ); + + } + + return fn; + +}; + +// + +const setCurrentStack = ( stack ) => { + + currentStack = stack; + +}; + +const getCurrentStack = () => currentStack; + +/** + * Represent a conditional node using if/else statements. + * + * ```js + * If( condition, function ) + * .ElseIf( condition, function ) + * .Else( function ) + * ``` + * @tsl + * @function + * @param {...any} params - The parameters for the conditional node. + * @returns {StackNode} The conditional node. + */ +const If = ( ...params ) => currentStack.If( ...params ); + +/** + * Represent a conditional node using switch/case statements. + * + * ```js + * Switch( value ) + * .Case( 1, function ) + * .Case( 2, 3, 4, function ) + * .Default( function ) + * ``` + * @tsl + * @function + * @param {...any} params - The parameters for the conditional node. + * @returns {StackNode} The conditional node. + */ +const Switch = ( ...params ) => currentStack.Switch( ...params ); + +/** + * Add the given node to the current stack. + * + * @param {Node} node - The node to add. + * @returns {Node} The node that was added to the stack. + */ +function Stack( node ) { + + if ( currentStack ) currentStack.add( node ); + + return node; + +} + +addMethodChaining( 'toStack', Stack ); + +// types + +const color = new ConvertType( 'color' ); + +const float = new ConvertType( 'float', cacheMaps.float ); +const int = new ConvertType( 'int', cacheMaps.ints ); +const uint = new ConvertType( 'uint', cacheMaps.uint ); +const bool = new ConvertType( 'bool', cacheMaps.bool ); + +const vec2 = new ConvertType( 'vec2' ); +const ivec2 = new ConvertType( 'ivec2' ); +const uvec2 = new ConvertType( 'uvec2' ); +const bvec2 = new ConvertType( 'bvec2' ); + +const vec3 = new ConvertType( 'vec3' ); +const ivec3 = new ConvertType( 'ivec3' ); +const uvec3 = new ConvertType( 'uvec3' ); +const bvec3 = new ConvertType( 'bvec3' ); + +const vec4 = new ConvertType( 'vec4' ); +const ivec4 = new ConvertType( 'ivec4' ); +const uvec4 = new ConvertType( 'uvec4' ); +const bvec4 = new ConvertType( 'bvec4' ); + +const mat2 = new ConvertType( 'mat2' ); +const mat3 = new ConvertType( 'mat3' ); +const mat4 = new ConvertType( 'mat4' ); + +const string = ( value = '' ) => nodeObject( new ConstNode( value, 'string' ) ); +const arrayBuffer = ( value ) => nodeObject( new ConstNode( value, 'ArrayBuffer' ) ); + +addMethodChaining( 'toColor', color ); +addMethodChaining( 'toFloat', float ); +addMethodChaining( 'toInt', int ); +addMethodChaining( 'toUint', uint ); +addMethodChaining( 'toBool', bool ); +addMethodChaining( 'toVec2', vec2 ); +addMethodChaining( 'toIVec2', ivec2 ); +addMethodChaining( 'toUVec2', uvec2 ); +addMethodChaining( 'toBVec2', bvec2 ); +addMethodChaining( 'toVec3', vec3 ); +addMethodChaining( 'toIVec3', ivec3 ); +addMethodChaining( 'toUVec3', uvec3 ); +addMethodChaining( 'toBVec3', bvec3 ); +addMethodChaining( 'toVec4', vec4 ); +addMethodChaining( 'toIVec4', ivec4 ); +addMethodChaining( 'toUVec4', uvec4 ); +addMethodChaining( 'toBVec4', bvec4 ); +addMethodChaining( 'toMat2', mat2 ); +addMethodChaining( 'toMat3', mat3 ); +addMethodChaining( 'toMat4', mat4 ); + +// basic nodes + +const element = /*@__PURE__*/ nodeProxy( ArrayElementNode ).setParameterLength( 2 ); +const convert = ( node, types ) => nodeObject( new ConvertNode( nodeObject( node ), types ) ); +const split = ( node, channels ) => nodeObject( new SplitNode( nodeObject( node ), channels ) ); + +addMethodChaining( 'element', element ); +addMethodChaining( 'convert', convert ); + +// deprecated + +/** + * @tsl + * @function + * @deprecated since r176. Use {@link Stack} instead. + * + * @param {Node} node - The node to add. + * @returns {Function} + */ +const append = ( node ) => { // @deprecated, r176 + + console.warn( 'THREE.TSL: append() has been renamed to Stack().' ); + return Stack( node ); + +}; + +addMethodChaining( 'append', ( node ) => { // @deprecated, r176 + + console.warn( 'THREE.TSL: .append() has been renamed to .toStack().' ); + return Stack( node ); + +} ); + +/** + * This class represents a shader property. It can be used + * to explicitly define a property and assign a value to it. + * + * ```js + * const threshold = property( 'float', 'threshold' ).assign( THRESHOLD ); + *``` + * `PropertyNode` is used by the engine to predefined common material properties + * for TSL code. + * + * @augments Node + */ +class PropertyNode extends Node { + + static get type() { + + return 'PropertyNode'; + + } + + /** + * Constructs a new property node. + * + * @param {string} nodeType - The type of the node. + * @param {?string} [name=null] - The name of the property in the shader. + * @param {boolean} [varying=false] - Whether this property is a varying or not. + */ + constructor( nodeType, name = null, varying = false ) { + + super( nodeType ); + + /** + * The name of the property in the shader. If no name is defined, + * the node system auto-generates one. + * + * @type {?string} + * @default null + */ + this.name = name; + + /** + * Whether this property is a varying or not. + * + * @type {boolean} + * @default false + */ + this.varying = varying; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isPropertyNode = true; + + /** + * This flag is used for global cache. + * + * @type {boolean} + * @default true + */ + this.global = true; + + } + + getHash( builder ) { + + return this.name || super.getHash( builder ); + + } + + generate( builder ) { + + let nodeVar; + + if ( this.varying === true ) { + + nodeVar = builder.getVaryingFromNode( this, this.name ); + nodeVar.needsInterpolation = true; + + } else { + + nodeVar = builder.getVarFromNode( this, this.name ); + + } + + return builder.getPropertyName( nodeVar ); + + } + +} + +/** + * TSL function for creating a property node. + * + * @tsl + * @function + * @param {string} type - The type of the node. + * @param {?string} [name=null] - The name of the property in the shader. + * @returns {PropertyNode} + */ +const property = ( type, name ) => nodeObject( new PropertyNode( type, name ) ); + +/** + * TSL function for creating a varying property node. + * + * @tsl + * @function + * @param {string} type - The type of the node. + * @param {?string} [name=null] - The name of the varying in the shader. + * @returns {PropertyNode} + */ +const varyingProperty = ( type, name ) => nodeObject( new PropertyNode( type, name, true ) ); + +/** + * TSL object that represents the shader variable `DiffuseColor`. + * + * @tsl + * @type {PropertyNode} + */ +const diffuseColor = /*@__PURE__*/ nodeImmutable( PropertyNode, 'vec4', 'DiffuseColor' ); + +/** + * TSL object that represents the shader variable `EmissiveColor`. + * + * @tsl + * @type {PropertyNode} + */ +const emissive = /*@__PURE__*/ nodeImmutable( PropertyNode, 'vec3', 'EmissiveColor' ); + +/** + * TSL object that represents the shader variable `Roughness`. + * + * @tsl + * @type {PropertyNode} + */ +const roughness = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'Roughness' ); + +/** + * TSL object that represents the shader variable `Metalness`. + * + * @tsl + * @type {PropertyNode} + */ +const metalness = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'Metalness' ); + +/** + * TSL object that represents the shader variable `Clearcoat`. + * + * @tsl + * @type {PropertyNode} + */ +const clearcoat = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'Clearcoat' ); + +/** + * TSL object that represents the shader variable `ClearcoatRoughness`. + * + * @tsl + * @type {PropertyNode} + */ +const clearcoatRoughness = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'ClearcoatRoughness' ); + +/** + * TSL object that represents the shader variable `Sheen`. + * + * @tsl + * @type {PropertyNode} + */ +const sheen = /*@__PURE__*/ nodeImmutable( PropertyNode, 'vec3', 'Sheen' ); + +/** + * TSL object that represents the shader variable `SheenRoughness`. + * + * @tsl + * @type {PropertyNode} + */ +const sheenRoughness = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'SheenRoughness' ); + +/** + * TSL object that represents the shader variable `Iridescence`. + * + * @tsl + * @type {PropertyNode} + */ +const iridescence = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'Iridescence' ); + +/** + * TSL object that represents the shader variable `IridescenceIOR`. + * + * @tsl + * @type {PropertyNode} + */ +const iridescenceIOR = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'IridescenceIOR' ); + +/** + * TSL object that represents the shader variable `IridescenceThickness`. + * + * @tsl + * @type {PropertyNode} + */ +const iridescenceThickness = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'IridescenceThickness' ); + +/** + * TSL object that represents the shader variable `AlphaT`. + * + * @tsl + * @type {PropertyNode} + */ +const alphaT = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'AlphaT' ); + +/** + * TSL object that represents the shader variable `Anisotropy`. + * + * @tsl + * @type {PropertyNode} + */ +const anisotropy = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'Anisotropy' ); + +/** + * TSL object that represents the shader variable `AnisotropyT`. + * + * @tsl + * @type {PropertyNode} + */ +const anisotropyT = /*@__PURE__*/ nodeImmutable( PropertyNode, 'vec3', 'AnisotropyT' ); + +/** + * TSL object that represents the shader variable `AnisotropyB`. + * + * @tsl + * @type {PropertyNode} + */ +const anisotropyB = /*@__PURE__*/ nodeImmutable( PropertyNode, 'vec3', 'AnisotropyB' ); + +/** + * TSL object that represents the shader variable `SpecularColor`. + * + * @tsl + * @type {PropertyNode} + */ +const specularColor = /*@__PURE__*/ nodeImmutable( PropertyNode, 'color', 'SpecularColor' ); + +/** + * TSL object that represents the shader variable `SpecularF90`. + * + * @tsl + * @type {PropertyNode} + */ +const specularF90 = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'SpecularF90' ); + +/** + * TSL object that represents the shader variable `Shininess`. + * + * @tsl + * @type {PropertyNode} + */ +const shininess = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'Shininess' ); + +/** + * TSL object that represents the shader variable `Output`. + * + * @tsl + * @type {PropertyNode} + */ +const output = /*@__PURE__*/ nodeImmutable( PropertyNode, 'vec4', 'Output' ); + +/** + * TSL object that represents the shader variable `dashSize`. + * + * @tsl + * @type {PropertyNode} + */ +const dashSize = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'dashSize' ); + +/** + * TSL object that represents the shader variable `gapSize`. + * + * @tsl + * @type {PropertyNode} + */ +const gapSize = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'gapSize' ); + +/** + * TSL object that represents the shader variable `pointWidth`. + * + * @tsl + * @type {PropertyNode} + */ +const pointWidth = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'pointWidth' ); + +/** + * TSL object that represents the shader variable `IOR`. + * + * @tsl + * @type {PropertyNode} + */ +const ior = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'IOR' ); + +/** + * TSL object that represents the shader variable `Transmission`. + * + * @tsl + * @type {PropertyNode} + */ +const transmission = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'Transmission' ); + +/** + * TSL object that represents the shader variable `Thickness`. + * + * @tsl + * @type {PropertyNode} + */ +const thickness = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'Thickness' ); + +/** + * TSL object that represents the shader variable `AttenuationDistance`. + * + * @tsl + * @type {PropertyNode} + */ +const attenuationDistance = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'AttenuationDistance' ); + +/** + * TSL object that represents the shader variable `AttenuationColor`. + * + * @tsl + * @type {PropertyNode} + */ +const attenuationColor = /*@__PURE__*/ nodeImmutable( PropertyNode, 'color', 'AttenuationColor' ); + +/** + * TSL object that represents the shader variable `Dispersion`. + * + * @tsl + * @type {PropertyNode} + */ +const dispersion = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'Dispersion' ); + +/** + * This node can be used to group single instances of {@link UniformNode} + * and manage them as a uniform buffer. + * + * In most cases, the predefined nodes `objectGroup`, `renderGroup` and `frameGroup` + * will be used when defining the {@link UniformNode#groupNode} property. + * + * - `objectGroup`: Uniform buffer per object. + * - `renderGroup`: Shared uniform buffer, updated once per render call. + * - `frameGroup`: Shared uniform buffer, updated once per frame. + * + * @augments Node + */ +class UniformGroupNode extends Node { + + static get type() { + + return 'UniformGroupNode'; + + } + + /** + * Constructs a new uniform group node. + * + * @param {string} name - The name of the uniform group node. + * @param {boolean} [shared=false] - Whether this uniform group node is shared or not. + * @param {number} [order=1] - Influences the internal sorting. + */ + constructor( name, shared = false, order = 1 ) { + + super( 'string' ); + + /** + * The name of the uniform group node. + * + * @type {string} + */ + this.name = name; + + /** + * Whether this uniform group node is shared or not. + * + * @type {boolean} + * @default false + */ + this.shared = shared; + + /** + * Influences the internal sorting. + * TODO: Add details when this property should be changed. + * + * @type {number} + * @default 1 + */ + this.order = order; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isUniformGroup = true; + + } + + serialize( data ) { + + super.serialize( data ); + + data.name = this.name; + data.version = this.version; + data.shared = this.shared; + + } + + deserialize( data ) { + + super.deserialize( data ); + + this.name = data.name; + this.version = data.version; + this.shared = data.shared; + + } + +} + +/** + * TSL function for creating a uniform group node with the given name. + * + * @tsl + * @function + * @param {string} name - The name of the uniform group node. + * @returns {UniformGroupNode} + */ +const uniformGroup = ( name ) => new UniformGroupNode( name ); + +/** + * TSL function for creating a shared uniform group node with the given name and order. + * + * @tsl + * @function + * @param {string} name - The name of the uniform group node. + * @param {number} [order=0] - Influences the internal sorting. + * @returns {UniformGroupNode} + */ +const sharedUniformGroup = ( name, order = 0 ) => new UniformGroupNode( name, true, order ); + +/** + * TSL object that represents a shared uniform group node which is updated once per frame. + * + * @tsl + * @type {UniformGroupNode} + */ +const frameGroup = /*@__PURE__*/ sharedUniformGroup( 'frame' ); + +/** + * TSL object that represents a shared uniform group node which is updated once per render. + * + * @tsl + * @type {UniformGroupNode} + */ +const renderGroup = /*@__PURE__*/ sharedUniformGroup( 'render' ); + +/** + * TSL object that represents a uniform group node which is updated once per object. + * + * @tsl + * @type {UniformGroupNode} + */ +const objectGroup = /*@__PURE__*/ uniformGroup( 'object' ); + +/** + * Class for representing a uniform. + * + * @augments InputNode + */ +class UniformNode extends InputNode { + + static get type() { + + return 'UniformNode'; + + } + + /** + * Constructs a new uniform node. + * + * @param {any} value - The value of this node. Usually a JS primitive or three.js object (vector, matrix, color, texture). + * @param {?string} nodeType - The node type. If no explicit type is defined, the node tries to derive the type from its value. + */ + constructor( value, nodeType = null ) { + + super( value, nodeType ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isUniformNode = true; + + /** + * The name or label of the uniform. + * + * @type {string} + * @default '' + */ + this.name = ''; + + /** + * The uniform group of this uniform. By default, uniforms are + * managed per object but they might belong to a shared group + * which is updated per frame or render call. + * + * @type {UniformGroupNode} + */ + this.groupNode = objectGroup; + + } + + /** + * Sets the {@link UniformNode#name} property. + * + * @param {string} name - The name of the uniform. + * @return {UniformNode} A reference to this node. + */ + label( name ) { + + this.name = name; + + return this; + + } + + /** + * Sets the {@link UniformNode#groupNode} property. + * + * @param {UniformGroupNode} group - The uniform group. + * @return {UniformNode} A reference to this node. + */ + setGroup( group ) { + + this.groupNode = group; + + return this; + + } + + /** + * Returns the {@link UniformNode#groupNode}. + * + * @return {UniformGroupNode} The uniform group. + */ + getGroup() { + + return this.groupNode; + + } + + /** + * By default, this method returns the result of {@link Node#getHash} but derived + * classes might overwrite this method with a different implementation. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The uniform hash. + */ + getUniformHash( builder ) { + + return this.getHash( builder ); + + } + + onUpdate( callback, updateType ) { + + const self = this.getSelf(); + + callback = callback.bind( self ); + + return super.onUpdate( ( frame ) => { + + const value = callback( frame, self ); + + if ( value !== undefined ) { + + this.value = value; + + } + + }, updateType ); + + } + + generate( builder, output ) { + + const type = this.getNodeType( builder ); + + const hash = this.getUniformHash( builder ); + + let sharedNode = builder.getNodeFromHash( hash ); + + if ( sharedNode === undefined ) { + + builder.setHashNode( this, hash ); + + sharedNode = this; + + } + + const sharedNodeType = sharedNode.getInputType( builder ); + + const nodeUniform = builder.getUniformFromNode( sharedNode, sharedNodeType, builder.shaderStage, this.name || builder.context.label ); + const propertyName = builder.getPropertyName( nodeUniform ); + + if ( builder.context.label !== undefined ) delete builder.context.label; + + return builder.format( propertyName, type, output ); + + } + +} + +/** + * TSL function for creating a uniform node. + * + * @tsl + * @function + * @param {any} arg1 - The value of this node. Usually a JS primitive or three.js object (vector, matrix, color, texture). + * @param {string} [arg2] - The node type. If no explicit type is defined, the node tries to derive the type from its value. + * @returns {UniformNode} + */ +const uniform = ( arg1, arg2 ) => { + + const nodeType = getConstNodeType( arg2 || arg1 ); + + // @TODO: get ConstNode from .traverse() in the future + const value = ( arg1 && arg1.isNode === true ) ? ( arg1.node && arg1.node.value ) || arg1.value : arg1; + + return nodeObject( new UniformNode( value, nodeType ) ); + +}; + +/** + * ArrayNode represents a collection of nodes, typically created using the {@link array} function. + * ```js + * const colors = array( [ + * vec3( 1, 0, 0 ), + * vec3( 0, 1, 0 ), + * vec3( 0, 0, 1 ) + * ] ); + * + * const redColor = tintColors.element( 0 ); + * + * @augments TempNode + */ +class ArrayNode extends TempNode { + + static get type() { + + return 'ArrayNode'; + + } + + /** + * Constructs a new array node. + * + * @param {?string} nodeType - The data type of the elements. + * @param {number} count - Size of the array. + * @param {?Array} [values=null] - Array default values. + */ + constructor( nodeType, count, values = null ) { + + super( nodeType ); + + /** + * Array size. + * + * @type {number} + */ + this.count = count; + + /** + * Array default values. + * + * @type {?Array} + */ + this.values = values; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isArrayNode = true; + + } + + /** + * Returns the node's type. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The type of the node. + */ + getNodeType( builder ) { + + if ( this.nodeType === null ) { + + this.nodeType = this.values[ 0 ].getNodeType( builder ); + + } + + return this.nodeType; + + } + + /** + * Returns the node's type. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The type of the node. + */ + getElementType( builder ) { + + return this.getNodeType( builder ); + + } + + /** + * This method builds the output node and returns the resulting array as a shader string. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The generated shader string. + */ + generate( builder ) { + + const type = this.getNodeType( builder ); + + return builder.generateArray( type, this.count, this.values ); + + } + +} + +/** + * TSL function for creating an array node. + * + * @tsl + * @function + * @param {string|Array} nodeTypeOrValues - A string representing the element type (e.g., 'vec3') + * or an array containing the default values (e.g., [ vec3() ]). + * @param {?number} [count] - Size of the array. + * @returns {ArrayNode} + */ +const array = ( ...params ) => { + + let node; + + if ( params.length === 1 ) { + + const values = params[ 0 ]; + + node = new ArrayNode( null, values.length, values ); + + } else { + + const nodeType = params[ 0 ]; + const count = params[ 1 ]; + + node = new ArrayNode( nodeType, count ); + + } + + return nodeObject( node ); + +}; + +addMethodChaining( 'toArray', ( node, count ) => array( Array( count ).fill( node ) ) ); + +/** + * These node represents an assign operation. Meaning a node is assigned + * to another node. + * + * @augments TempNode + */ +class AssignNode extends TempNode { + + static get type() { + + return 'AssignNode'; + + } + + /** + * Constructs a new assign node. + * + * @param {Node} targetNode - The target node. + * @param {Node} sourceNode - The source type. + */ + constructor( targetNode, sourceNode ) { + + super(); + + /** + * The target node. + * + * @type {Node} + */ + this.targetNode = targetNode; + + /** + * The source node. + * + * @type {Node} + */ + this.sourceNode = sourceNode; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isAssignNode = true; + + } + + /** + * Whether this node is used more than once in context of other nodes. This method + * is overwritten since it always returns `false` (assigns are unique). + * + * @return {boolean} A flag that indicates if there is more than one dependency to other nodes. Always `false`. + */ + hasDependencies() { + + return false; + + } + + getNodeType( builder, output ) { + + return output !== 'void' ? this.targetNode.getNodeType( builder ) : 'void'; + + } + + /** + * Whether a split is required when assigning source to target. This can happen when the component length of + * target and source data type does not match. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {boolean} Whether a split is required when assigning source to target. + */ + needsSplitAssign( builder ) { + + const { targetNode } = this; + + if ( builder.isAvailable( 'swizzleAssign' ) === false && targetNode.isSplitNode && targetNode.components.length > 1 ) { + + const targetLength = builder.getTypeLength( targetNode.node.getNodeType( builder ) ); + const assignDifferentVector = vectorComponents.join( '' ).slice( 0, targetLength ) !== targetNode.components; + + return assignDifferentVector; + + } + + return false; + + } + + setup( builder ) { + + const { targetNode, sourceNode } = this; + + const properties = builder.getNodeProperties( this ); + properties.sourceNode = sourceNode; + properties.targetNode = targetNode.context( { assign: true } ); + + } + + generate( builder, output ) { + + const { targetNode, sourceNode } = builder.getNodeProperties( this ); + + const needsSplitAssign = this.needsSplitAssign( builder ); + + const targetType = targetNode.getNodeType( builder ); + + const target = targetNode.build( builder ); + const source = sourceNode.build( builder, targetType ); + + const sourceType = sourceNode.getNodeType( builder ); + + const nodeData = builder.getDataFromNode( this ); + + // + + let snippet; + + if ( nodeData.initialized === true ) { + + if ( output !== 'void' ) { + + snippet = target; + + } + + } else if ( needsSplitAssign ) { + + const sourceVar = builder.getVarFromNode( this, null, targetType ); + const sourceProperty = builder.getPropertyName( sourceVar ); + + builder.addLineFlowCode( `${ sourceProperty } = ${ source }`, this ); + + const splitNode = targetNode.node; + const splitTargetNode = splitNode.node.context( { assign: true } ); + + const targetRoot = splitTargetNode.build( builder ); + + for ( let i = 0; i < splitNode.components.length; i ++ ) { + + const component = splitNode.components[ i ]; + + builder.addLineFlowCode( `${ targetRoot }.${ component } = ${ sourceProperty }[ ${ i } ]`, this ); + + } + + if ( output !== 'void' ) { + + snippet = target; + + } + + } else { + + snippet = `${ target } = ${ source }`; + + if ( output === 'void' || sourceType === 'void' ) { + + builder.addLineFlowCode( snippet, this ); + + if ( output !== 'void' ) { + + snippet = target; + + } + + } + + } + + nodeData.initialized = true; + + return builder.format( snippet, targetType, output ); + + } + +} + +/** + * TSL function for creating an assign node. + * + * @tsl + * @function + * @param {Node} targetNode - The target node. + * @param {Node} sourceNode - The source type. + * @returns {AssignNode} + */ +const assign = /*@__PURE__*/ nodeProxy( AssignNode ).setParameterLength( 2 ); + +addMethodChaining( 'assign', assign ); + +/** + * This module represents the call of a {@link FunctionNode}. Developers are usually not confronted + * with this module since they use the predefined TSL syntax `wgslFn` and `glslFn` which encapsulate + * this logic. + * + * @augments TempNode + */ +class FunctionCallNode extends TempNode { + + static get type() { + + return 'FunctionCallNode'; + + } + + /** + * Constructs a new function call node. + * + * @param {?FunctionNode} functionNode - The function node. + * @param {Object} [parameters={}] - The parameters for the function call. + */ + constructor( functionNode = null, parameters = {} ) { + + super(); + + /** + * The function node. + * + * @type {?FunctionNode} + * @default null + */ + this.functionNode = functionNode; + + /** + * The parameters of the function call. + * + * @type {Object} + * @default {} + */ + this.parameters = parameters; + + } + + /** + * Sets the parameters of the function call node. + * + * @param {Object} parameters - The parameters to set. + * @return {FunctionCallNode} A reference to this node. + */ + setParameters( parameters ) { + + this.parameters = parameters; + + return this; + + } + + /** + * Returns the parameters of the function call node. + * + * @return {Object} The parameters of this node. + */ + getParameters() { + + return this.parameters; + + } + + getNodeType( builder ) { + + return this.functionNode.getNodeType( builder ); + + } + + generate( builder ) { + + const params = []; + + const functionNode = this.functionNode; + + const inputs = functionNode.getInputs( builder ); + const parameters = this.parameters; + + const generateInput = ( node, inputNode ) => { + + const type = inputNode.type; + const pointer = type === 'pointer'; + + let output; + + if ( pointer ) output = '&' + node.build( builder ); + else output = node.build( builder, type ); + + return output; + + }; + + if ( Array.isArray( parameters ) ) { + + if ( parameters.length > inputs.length ) { + + console.error( 'THREE.TSL: The number of provided parameters exceeds the expected number of inputs in \'Fn()\'.' ); + + parameters.length = inputs.length; + + } else if ( parameters.length < inputs.length ) { + + console.error( 'THREE.TSL: The number of provided parameters is less than the expected number of inputs in \'Fn()\'.' ); + + while ( parameters.length < inputs.length ) { + + parameters.push( float( 0 ) ); + + } + + } + + for ( let i = 0; i < parameters.length; i ++ ) { + + params.push( generateInput( parameters[ i ], inputs[ i ] ) ); + + } + + } else { + + for ( const inputNode of inputs ) { + + const node = parameters[ inputNode.name ]; + + if ( node !== undefined ) { + + params.push( generateInput( node, inputNode ) ); + + } else { + + console.error( `THREE.TSL: Input '${ inputNode.name }' not found in \'Fn()\'.` ); + + params.push( generateInput( float( 0 ), inputNode ) ); + + } + + } + + } + + const functionName = functionNode.build( builder, 'property' ); + + return `${ functionName }( ${ params.join( ', ' ) } )`; + + } + +} + +const call = ( func, ...params ) => { + + params = params.length > 1 || ( params[ 0 ] && params[ 0 ].isNode === true ) ? nodeArray( params ) : nodeObjects( params[ 0 ] ); + + return nodeObject( new FunctionCallNode( nodeObject( func ), params ) ); + +}; + +addMethodChaining( 'call', call ); + +const _vectorOperators = { + '==': 'equal', + '!=': 'notEqual', + '<': 'lessThan', + '>': 'greaterThan', + '<=': 'lessThanEqual', + '>=': 'greaterThanEqual', + '%': 'mod' +}; + +/** + * This node represents basic mathematical and logical operations like addition, + * subtraction or comparisons (e.g. `equal()`). + * + * @augments TempNode + */ +class OperatorNode extends TempNode { + + static get type() { + + return 'OperatorNode'; + + } + + /** + * Constructs a new operator node. + * + * @param {string} op - The operator. + * @param {Node} aNode - The first input. + * @param {Node} bNode - The second input. + * @param {...Node} params - Additional input parameters. + */ + constructor( op, aNode, bNode, ...params ) { + + super(); + + if ( params.length > 0 ) { + + let finalOp = new OperatorNode( op, aNode, bNode ); + + for ( let i = 0; i < params.length - 1; i ++ ) { + + finalOp = new OperatorNode( op, finalOp, params[ i ] ); + + } + + aNode = finalOp; + bNode = params[ params.length - 1 ]; + + } + + /** + * The operator. + * + * @type {string} + */ + this.op = op; + + /** + * The first input. + * + * @type {Node} + */ + this.aNode = aNode; + + /** + * The second input. + * + * @type {Node} + */ + this.bNode = bNode; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isOperatorNode = true; + + } + + /** + * Returns the operator method name. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {string} output - The output type. + * @returns {string} The operator method name. + */ + getOperatorMethod( builder, output ) { + + return builder.getMethod( _vectorOperators[ this.op ], output ); + + } + + /** + * This method is overwritten since the node type is inferred from the operator + * and the input node types. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType( builder ) { + + const op = this.op; + + const aNode = this.aNode; + const bNode = this.bNode; + + const typeA = aNode.getNodeType( builder ); + const typeB = bNode ? bNode.getNodeType( builder ) : null; + + if ( typeA === 'void' || typeB === 'void' ) { + + return 'void'; + + } else if ( op === '%' ) { + + return typeA; + + } else if ( op === '~' || op === '&' || op === '|' || op === '^' || op === '>>' || op === '<<' ) { + + return builder.getIntegerType( typeA ); + + } else if ( op === '!' || op === '&&' || op === '||' || op === '^^' ) { + + return 'bool'; + + } else if ( op === '==' || op === '!=' || op === '<' || op === '>' || op === '<=' || op === '>=' ) { + + const typeLength = Math.max( builder.getTypeLength( typeA ), builder.getTypeLength( typeB ) ); + + return typeLength > 1 ? `bvec${ typeLength }` : 'bool'; + + } else { + + // Handle matrix operations + + if ( builder.isMatrix( typeA ) ) { + + if ( typeB === 'float' ) { + + return typeA; // matrix * scalar = matrix + + } else if ( builder.isVector( typeB ) ) { + + return builder.getVectorFromMatrix( typeA ); // matrix * vector + + } else if ( builder.isMatrix( typeB ) ) { + + return typeA; // matrix * matrix + + } + + } else if ( builder.isMatrix( typeB ) ) { + + if ( typeA === 'float' ) { + + return typeB; // scalar * matrix = matrix + + } else if ( builder.isVector( typeA ) ) { + + return builder.getVectorFromMatrix( typeB ); // vector * matrix + + } + + } + + // Handle non-matrix cases + + if ( builder.getTypeLength( typeB ) > builder.getTypeLength( typeA ) ) { + + // anytype x anytype: use the greater length vector + + return typeB; + + } + + return typeA; + + } + + } + + generate( builder, output ) { + + const op = this.op; + + const { aNode, bNode } = this; + + const type = this.getNodeType( builder ); + + let typeA = null; + let typeB = null; + + if ( type !== 'void' ) { + + typeA = aNode.getNodeType( builder ); + typeB = bNode ? bNode.getNodeType( builder ) : null; + + if ( op === '<' || op === '>' || op === '<=' || op === '>=' || op === '==' || op === '!=' ) { + + if ( builder.isVector( typeA ) ) { + + typeB = typeA; + + } else if ( builder.isVector( typeB ) ) { + + typeA = typeB; + + } else if ( typeA !== typeB ) { + + typeA = typeB = 'float'; + + } + + } else if ( op === '>>' || op === '<<' ) { + + typeA = type; + typeB = builder.changeComponentType( typeB, 'uint' ); + + } else if ( op === '%' ) { + + typeA = type; + typeB = builder.isInteger( typeA ) && builder.isInteger( typeB ) ? typeB : typeA; + + } else if ( builder.isMatrix( typeA ) ) { + + if ( typeB === 'float' ) { + + // Keep matrix type for typeA, but ensure typeB stays float + + typeB = 'float'; + + } else if ( builder.isVector( typeB ) ) { + + // matrix x vector + typeB = builder.getVectorFromMatrix( typeA ); + + } else if ( builder.isMatrix( typeB ) ) ; else { + + typeA = typeB = type; + + } + + } else if ( builder.isMatrix( typeB ) ) { + + if ( typeA === 'float' ) { + + // Keep matrix type for typeB, but ensure typeA stays float + + typeA = 'float'; + + } else if ( builder.isVector( typeA ) ) { + + // vector x matrix + + typeA = builder.getVectorFromMatrix( typeB ); + + } else { + + typeA = typeB = type; + + } + + } else { + + // anytype x anytype + + typeA = typeB = type; + + } + + } else { + + typeA = typeB = type; + + } + + const a = aNode.build( builder, typeA ); + const b = bNode ? bNode.build( builder, typeB ) : null; + + const fnOpSnippet = builder.getFunctionOperator( op ); + + if ( output !== 'void' ) { + + const isGLSL = builder.renderer.coordinateSystem === WebGLCoordinateSystem; + + if ( op === '==' || op === '!=' || op === '<' || op === '>' || op === '<=' || op === '>=' ) { + + if ( isGLSL ) { + + if ( builder.isVector( typeA ) ) { + + return builder.format( `${ this.getOperatorMethod( builder, output ) }( ${ a }, ${ b } )`, type, output ); + + } else { + + return builder.format( `( ${ a } ${ op } ${ b } )`, type, output ); + + } + + } else { + + // WGSL + + return builder.format( `( ${ a } ${ op } ${ b } )`, type, output ); + + } + + } else if ( op === '%' ) { + + if ( builder.isInteger( typeB ) ) { + + return builder.format( `( ${ a } % ${ b } )`, type, output ); + + } else { + + return builder.format( `${ this.getOperatorMethod( builder, type ) }( ${ a }, ${ b } )`, type, output ); + + } + + } else if ( op === '!' || op === '~' ) { + + return builder.format( `(${op}${a})`, typeA, output ); + + } else if ( fnOpSnippet ) { + + return builder.format( `${ fnOpSnippet }( ${ a }, ${ b } )`, type, output ); + + } else { + + // Handle matrix operations + + if ( builder.isMatrix( typeA ) && typeB === 'float' ) { + + return builder.format( `( ${ b } ${ op } ${ a } )`, type, output ); + + } else if ( typeA === 'float' && builder.isMatrix( typeB ) ) { + + return builder.format( `${ a } ${ op } ${ b }`, type, output ); + + } else { + + let snippet = `( ${ a } ${ op } ${ b } )`; + + if ( ! isGLSL && type === 'bool' && builder.isVector( typeA ) && builder.isVector( typeB ) ) { + + snippet = `all${ snippet }`; + + } + + return builder.format( snippet, type, output ); + + } + + } + + } else if ( typeA !== 'void' ) { + + if ( fnOpSnippet ) { + + return builder.format( `${ fnOpSnippet }( ${ a }, ${ b } )`, type, output ); + + } else { + + if ( builder.isMatrix( typeA ) && typeB === 'float' ) { + + return builder.format( `${ b } ${ op } ${ a }`, type, output ); + + } else { + + return builder.format( `${ a } ${ op } ${ b }`, type, output ); + + } + + } + + } + + } + + serialize( data ) { + + super.serialize( data ); + + data.op = this.op; + + } + + deserialize( data ) { + + super.deserialize( data ); + + this.op = data.op; + + } + +} + +/** + * Returns the addition of two or more value. + * + * @tsl + * @function + * @param {Node} a - The first input. + * @param {Node} b - The second input. + * @param {...Node} params - Additional input parameters. + * @returns {OperatorNode} + */ +const add = /*@__PURE__*/ nodeProxy( OperatorNode, '+' ).setParameterLength( 2, Infinity ).setName( 'add' ); + +/** + * Returns the subtraction of two or more value. + * + * @tsl + * @function + * @param {Node} a - The first input. + * @param {Node} b - The second input. + * @param {...Node} params - Additional input parameters. + * @returns {OperatorNode} + */ +const sub = /*@__PURE__*/ nodeProxy( OperatorNode, '-' ).setParameterLength( 2, Infinity ).setName( 'sub' ); + +/** + * Returns the multiplication of two or more value. + * + * @tsl + * @function + * @param {Node} a - The first input. + * @param {Node} b - The second input. + * @param {...Node} params - Additional input parameters. + * @returns {OperatorNode} + */ +const mul = /*@__PURE__*/ nodeProxy( OperatorNode, '*' ).setParameterLength( 2, Infinity ).setName( 'mul' ); + +/** + * Returns the division of two or more value. + * + * @tsl + * @function + * @param {Node} a - The first input. + * @param {Node} b - The second input. + * @param {...Node} params - Additional input parameters. + * @returns {OperatorNode} + */ +const div = /*@__PURE__*/ nodeProxy( OperatorNode, '/' ).setParameterLength( 2, Infinity ).setName( 'div' ); + +/** + * Computes the remainder of dividing the first node by the second one. + * + * @tsl + * @function + * @param {Node} a - The first input. + * @param {Node} b - The second input. + * @returns {OperatorNode} + */ +const mod = /*@__PURE__*/ nodeProxy( OperatorNode, '%' ).setParameterLength( 2 ).setName( 'mod' ); + +/** + * Checks if two nodes are equal. + * + * @tsl + * @function + * @param {Node} a - The first input. + * @param {Node} b - The second input. + * @returns {OperatorNode} + */ +const equal = /*@__PURE__*/ nodeProxy( OperatorNode, '==' ).setParameterLength( 2 ).setName( 'equal' ); + +/** + * Checks if two nodes are not equal. + * + * @tsl + * @function + * @param {Node} a - The first input. + * @param {Node} b - The second input. + * @returns {OperatorNode} + */ +const notEqual = /*@__PURE__*/ nodeProxy( OperatorNode, '!=' ).setParameterLength( 2 ).setName( 'notEqual' ); + +/** + * Checks if the first node is less than the second. + * + * @tsl + * @function + * @param {Node} a - The first input. + * @param {Node} b - The second input. + * @returns {OperatorNode} + */ +const lessThan = /*@__PURE__*/ nodeProxy( OperatorNode, '<' ).setParameterLength( 2 ).setName( 'lessThan' ); + +/** + * Checks if the first node is greater than the second. + * + * @tsl + * @function + * @param {Node} a - The first input. + * @param {Node} b - The second input. + * @returns {OperatorNode} + */ +const greaterThan = /*@__PURE__*/ nodeProxy( OperatorNode, '>' ).setParameterLength( 2 ).setName( 'greaterThan' ); + +/** + * Checks if the first node is less than or equal to the second. + * + * @tsl + * @function + * @param {Node} a - The first input. + * @param {Node} b - The second input. + * @returns {OperatorNode} + */ +const lessThanEqual = /*@__PURE__*/ nodeProxy( OperatorNode, '<=' ).setParameterLength( 2 ).setName( 'lessThanEqual' ); + +/** + * Checks if the first node is greater than or equal to the second. + * + * @tsl + * @function + * @param {Node} a - The first input. + * @param {Node} b - The second input. + * @returns {OperatorNode} + */ +const greaterThanEqual = /*@__PURE__*/ nodeProxy( OperatorNode, '>=' ).setParameterLength( 2 ).setName( 'greaterThanEqual' ); + +/** + * Performs a logical AND operation on multiple nodes. + * + * @tsl + * @function + * @param {...Node} nodes - The input nodes to be combined using AND. + * @returns {OperatorNode} + */ +const and = /*@__PURE__*/ nodeProxy( OperatorNode, '&&' ).setParameterLength( 2, Infinity ).setName( 'and' ); + +/** + * Performs a logical OR operation on multiple nodes. + * + * @tsl + * @function + * @param {...Node} nodes - The input nodes to be combined using OR. + * @returns {OperatorNode} + */ +const or = /*@__PURE__*/ nodeProxy( OperatorNode, '||' ).setParameterLength( 2, Infinity ).setName( 'or' ); + +/** + * Performs logical NOT on a node. + * + * @tsl + * @function + * @param {Node} value - The value. + * @returns {OperatorNode} + */ +const not = /*@__PURE__*/ nodeProxy( OperatorNode, '!' ).setParameterLength( 1 ).setName( 'not' ); + +/** + * Performs logical XOR on two nodes. + * + * @tsl + * @function + * @param {Node} a - The first input. + * @param {Node} b - The second input. + * @returns {OperatorNode} + */ +const xor = /*@__PURE__*/ nodeProxy( OperatorNode, '^^' ).setParameterLength( 2 ).setName( 'xor' ); + +/** + * Performs bitwise AND on two nodes. + * + * @tsl + * @function + * @param {Node} a - The first input. + * @param {Node} b - The second input. + * @returns {OperatorNode} + */ +const bitAnd = /*@__PURE__*/ nodeProxy( OperatorNode, '&' ).setParameterLength( 2 ).setName( 'bitAnd' ); + +/** + * Performs bitwise NOT on a node. + * + * @tsl + * @function + * @param {Node} a - The first input. + * @param {Node} b - The second input. + * @returns {OperatorNode} + */ +const bitNot = /*@__PURE__*/ nodeProxy( OperatorNode, '~' ).setParameterLength( 2 ).setName( 'bitNot' ); + +/** + * Performs bitwise OR on two nodes. + * + * @tsl + * @function + * @param {Node} a - The first input. + * @param {Node} b - The second input. + * @returns {OperatorNode} + */ +const bitOr = /*@__PURE__*/ nodeProxy( OperatorNode, '|' ).setParameterLength( 2 ).setName( 'bitOr' ); + +/** + * Performs bitwise XOR on two nodes. + * + * @tsl + * @function + * @param {Node} a - The first input. + * @param {Node} b - The second input. + * @returns {OperatorNode} + */ +const bitXor = /*@__PURE__*/ nodeProxy( OperatorNode, '^' ).setParameterLength( 2 ).setName( 'bitXor' ); + +/** + * Shifts a node to the left. + * + * @tsl + * @function + * @param {Node} a - The node to shift. + * @param {Node} b - The value to shift. + * @returns {OperatorNode} + */ +const shiftLeft = /*@__PURE__*/ nodeProxy( OperatorNode, '<<' ).setParameterLength( 2 ).setName( 'shiftLeft' ); + +/** + * Shifts a node to the right. + * + * @tsl + * @function + * @param {Node} a - The node to shift. + * @param {Node} b - The value to shift. + * @returns {OperatorNode} + */ +const shiftRight = /*@__PURE__*/ nodeProxy( OperatorNode, '>>' ).setParameterLength( 2 ).setName( 'shiftRight' ); + +/** + * Increments a node by 1. + * + * @tsl + * @function + * @param {Node} a - The node to increment. + * @returns {OperatorNode} + */ +const incrementBefore = Fn( ( [ a ] ) => { + + a.addAssign( 1 ); + return a; + +} ); + +/** + * Decrements a node by 1. + * + * @tsl + * @function + * @param {Node} a - The node to decrement. + * @returns {OperatorNode} + */ +const decrementBefore = Fn( ( [ a ] ) => { + + a.subAssign( 1 ); + return a; + +} ); + +/** + * Increments a node by 1 and returns the previous value. + * + * @tsl + * @function + * @param {Node} a - The node to increment. + * @returns {OperatorNode} + */ +const increment = /*@__PURE__*/ Fn( ( [ a ] ) => { + + const temp = int( a ).toConst(); + a.addAssign( 1 ); + return temp; + +} ); + +/** + * Decrements a node by 1 and returns the previous value. + * + * @tsl + * @function + * @param {Node} a - The node to decrement. + * @returns {OperatorNode} + */ +const decrement = /*@__PURE__*/ Fn( ( [ a ] ) => { + + const temp = int( a ).toConst(); + a.subAssign( 1 ); + return temp; + +} ); + +addMethodChaining( 'add', add ); +addMethodChaining( 'sub', sub ); +addMethodChaining( 'mul', mul ); +addMethodChaining( 'div', div ); +addMethodChaining( 'mod', mod ); +addMethodChaining( 'equal', equal ); +addMethodChaining( 'notEqual', notEqual ); +addMethodChaining( 'lessThan', lessThan ); +addMethodChaining( 'greaterThan', greaterThan ); +addMethodChaining( 'lessThanEqual', lessThanEqual ); +addMethodChaining( 'greaterThanEqual', greaterThanEqual ); +addMethodChaining( 'and', and ); +addMethodChaining( 'or', or ); +addMethodChaining( 'not', not ); +addMethodChaining( 'xor', xor ); +addMethodChaining( 'bitAnd', bitAnd ); +addMethodChaining( 'bitNot', bitNot ); +addMethodChaining( 'bitOr', bitOr ); +addMethodChaining( 'bitXor', bitXor ); +addMethodChaining( 'shiftLeft', shiftLeft ); +addMethodChaining( 'shiftRight', shiftRight ); + +addMethodChaining( 'incrementBefore', incrementBefore ); +addMethodChaining( 'decrementBefore', decrementBefore ); +addMethodChaining( 'increment', increment ); +addMethodChaining( 'decrement', decrement ); + +/** + * @tsl + * @function + * @deprecated since r175. Use {@link mod} instead. + * + * @param {Node} a - The first input. + * @param {Node} b - The second input. + * @returns {OperatorNode} + */ +const modInt = ( a, b ) => { // @deprecated, r175 + + console.warn( 'THREE.TSL: "modInt()" is deprecated. Use "mod( int( ... ) )" instead.' ); + return mod( int( a ), int( b ) ); + +}; + +addMethodChaining( 'modInt', modInt ); + +/** + * This node represents a variety of mathematical methods available in shaders. + * They are divided into three categories: + * + * - Methods with one input like `sin`, `cos` or `normalize`. + * - Methods with two inputs like `dot`, `cross` or `pow`. + * - Methods with three inputs like `mix`, `clamp` or `smoothstep`. + * + * @augments TempNode + */ +class MathNode extends TempNode { + + static get type() { + + return 'MathNode'; + + } + + /** + * Constructs a new math node. + * + * @param {string} method - The method name. + * @param {Node} aNode - The first input. + * @param {?Node} [bNode=null] - The second input. + * @param {?Node} [cNode=null] - The third input. + */ + constructor( method, aNode, bNode = null, cNode = null ) { + + super(); + + // Allow the max() and min() functions to take an arbitrary number of arguments. + + if ( ( method === MathNode.MAX || method === MathNode.MIN ) && arguments.length > 3 ) { + + let finalOp = new MathNode( method, aNode, bNode ); + + for ( let i = 2; i < arguments.length - 1; i ++ ) { + + finalOp = new MathNode( method, finalOp, arguments[ i ] ); + + } + + aNode = finalOp; + bNode = arguments[ arguments.length - 1 ]; + cNode = null; + + } + + /** + * The method name. + * + * @type {string} + */ + this.method = method; + + /** + * The first input. + * + * @type {Node} + */ + this.aNode = aNode; + + /** + * The second input. + * + * @type {?Node} + * @default null + */ + this.bNode = bNode; + + /** + * The third input. + * + * @type {?Node} + * @default null + */ + this.cNode = cNode; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMathNode = true; + + } + + /** + * The input type is inferred from the node types of the input nodes. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The input type. + */ + getInputType( builder ) { + + const aType = this.aNode.getNodeType( builder ); + const bType = this.bNode ? this.bNode.getNodeType( builder ) : null; + const cType = this.cNode ? this.cNode.getNodeType( builder ) : null; + + const aLen = builder.isMatrix( aType ) ? 0 : builder.getTypeLength( aType ); + const bLen = builder.isMatrix( bType ) ? 0 : builder.getTypeLength( bType ); + const cLen = builder.isMatrix( cType ) ? 0 : builder.getTypeLength( cType ); + + if ( aLen > bLen && aLen > cLen ) { + + return aType; + + } else if ( bLen > cLen ) { + + return bType; + + } else if ( cLen > aLen ) { + + return cType; + + } + + return aType; + + } + + /** + * The selected method as well as the input type determine the node type of this node. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType( builder ) { + + const method = this.method; + + if ( method === MathNode.LENGTH || method === MathNode.DISTANCE || method === MathNode.DOT ) { + + return 'float'; + + } else if ( method === MathNode.CROSS ) { + + return 'vec3'; + + } else if ( method === MathNode.ALL || method === MathNode.ANY ) { + + return 'bool'; + + } else if ( method === MathNode.EQUALS ) { + + return builder.changeComponentType( this.aNode.getNodeType( builder ), 'bool' ); + + } else { + + return this.getInputType( builder ); + + } + + } + + setup( builder ) { + + const { aNode, bNode, method } = this; + + let outputNode = null; + + if ( method === MathNode.ONE_MINUS ) { + + outputNode = sub( 1.0, aNode ); + + } else if ( method === MathNode.RECIPROCAL ) { + + outputNode = div( 1.0, aNode ); + + } else if ( method === MathNode.DIFFERENCE ) { + + outputNode = abs( sub( aNode, bNode ) ); + + } else if ( method === MathNode.TRANSFORM_DIRECTION ) { + + // dir can be either a direction vector or a normal vector + // upper-left 3x3 of matrix is assumed to be orthogonal + + let tA = aNode; + let tB = bNode; + + if ( builder.isMatrix( tA.getNodeType( builder ) ) ) { + + tB = vec4( vec3( tB ), 0.0 ); + + } else { + + tA = vec4( vec3( tA ), 0.0 ); + + } + + const mulNode = mul( tA, tB ).xyz; + + outputNode = normalize( mulNode ); + + } + + if ( outputNode !== null ) { + + return outputNode; + + } else { + + return super.setup( builder ); + + } + + } + + generate( builder, output ) { + + const properties = builder.getNodeProperties( this ); + + if ( properties.outputNode ) { + + return super.generate( builder, output ); + + } + + let method = this.method; + + const type = this.getNodeType( builder ); + const inputType = this.getInputType( builder ); + + const a = this.aNode; + const b = this.bNode; + const c = this.cNode; + + const coordinateSystem = builder.renderer.coordinateSystem; + + if ( method === MathNode.NEGATE ) { + + return builder.format( '( - ' + a.build( builder, inputType ) + ' )', type, output ); + + } else { + + const params = []; + + if ( method === MathNode.CROSS ) { + + params.push( + a.build( builder, type ), + b.build( builder, type ) + ); + + } else if ( coordinateSystem === WebGLCoordinateSystem && method === MathNode.STEP ) { + + params.push( + a.build( builder, builder.getTypeLength( a.getNodeType( builder ) ) === 1 ? 'float' : inputType ), + b.build( builder, inputType ) + ); + + } else if ( coordinateSystem === WebGLCoordinateSystem && ( method === MathNode.MIN || method === MathNode.MAX ) ) { + + params.push( + a.build( builder, inputType ), + b.build( builder, builder.getTypeLength( b.getNodeType( builder ) ) === 1 ? 'float' : inputType ) + ); + + } else if ( method === MathNode.REFRACT ) { + + params.push( + a.build( builder, inputType ), + b.build( builder, inputType ), + c.build( builder, 'float' ) + ); + + } else if ( method === MathNode.MIX ) { + + params.push( + a.build( builder, inputType ), + b.build( builder, inputType ), + c.build( builder, builder.getTypeLength( c.getNodeType( builder ) ) === 1 ? 'float' : inputType ) + ); + + } else { + + if ( coordinateSystem === WebGPUCoordinateSystem && method === MathNode.ATAN && b !== null ) { + + method = 'atan2'; + + } + + if ( builder.shaderStage !== 'fragment' && ( method === MathNode.DFDX || method === MathNode.DFDY ) ) { + + console.warn( `THREE.TSL: '${ method }' is not supported in the ${ builder.shaderStage } stage.` ); + + method = '/*' + method + '*/'; + + } + + params.push( a.build( builder, inputType ) ); + if ( b !== null ) params.push( b.build( builder, inputType ) ); + if ( c !== null ) params.push( c.build( builder, inputType ) ); + + } + + return builder.format( `${ builder.getMethod( method, type ) }( ${params.join( ', ' )} )`, type, output ); + + } + + } + + serialize( data ) { + + super.serialize( data ); + + data.method = this.method; + + } + + deserialize( data ) { + + super.deserialize( data ); + + this.method = data.method; + + } + +} + +// 1 input + +MathNode.ALL = 'all'; +MathNode.ANY = 'any'; + +MathNode.RADIANS = 'radians'; +MathNode.DEGREES = 'degrees'; +MathNode.EXP = 'exp'; +MathNode.EXP2 = 'exp2'; +MathNode.LOG = 'log'; +MathNode.LOG2 = 'log2'; +MathNode.SQRT = 'sqrt'; +MathNode.INVERSE_SQRT = 'inversesqrt'; +MathNode.FLOOR = 'floor'; +MathNode.CEIL = 'ceil'; +MathNode.NORMALIZE = 'normalize'; +MathNode.FRACT = 'fract'; +MathNode.SIN = 'sin'; +MathNode.COS = 'cos'; +MathNode.TAN = 'tan'; +MathNode.ASIN = 'asin'; +MathNode.ACOS = 'acos'; +MathNode.ATAN = 'atan'; +MathNode.ABS = 'abs'; +MathNode.SIGN = 'sign'; +MathNode.LENGTH = 'length'; +MathNode.NEGATE = 'negate'; +MathNode.ONE_MINUS = 'oneMinus'; +MathNode.DFDX = 'dFdx'; +MathNode.DFDY = 'dFdy'; +MathNode.ROUND = 'round'; +MathNode.RECIPROCAL = 'reciprocal'; +MathNode.TRUNC = 'trunc'; +MathNode.FWIDTH = 'fwidth'; +MathNode.TRANSPOSE = 'transpose'; + +// 2 inputs + +MathNode.BITCAST = 'bitcast'; +MathNode.EQUALS = 'equals'; +MathNode.MIN = 'min'; +MathNode.MAX = 'max'; +MathNode.STEP = 'step'; +MathNode.REFLECT = 'reflect'; +MathNode.DISTANCE = 'distance'; +MathNode.DIFFERENCE = 'difference'; +MathNode.DOT = 'dot'; +MathNode.CROSS = 'cross'; +MathNode.POW = 'pow'; +MathNode.TRANSFORM_DIRECTION = 'transformDirection'; + +// 3 inputs + +MathNode.MIX = 'mix'; +MathNode.CLAMP = 'clamp'; +MathNode.REFRACT = 'refract'; +MathNode.SMOOTHSTEP = 'smoothstep'; +MathNode.FACEFORWARD = 'faceforward'; + +// 1 inputs + +/** + * A small value used to handle floating-point precision errors. + * + * @tsl + * @type {Node} + */ +const EPSILON = /*@__PURE__*/ float( 1e-6 ); + +/** + * Represents infinity. + * + * @tsl + * @type {Node} + */ +const INFINITY = /*@__PURE__*/ float( 1e6 ); + +/** + * Represents PI. + * + * @tsl + * @type {Node} + */ +const PI = /*@__PURE__*/ float( Math.PI ); + +/** + * Represents PI * 2. + * + * @tsl + * @type {Node} + */ +const PI2 = /*@__PURE__*/ float( Math.PI * 2 ); + +/** + * Returns `true` if all components of `x` are `true`. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const all = /*@__PURE__*/ nodeProxy( MathNode, MathNode.ALL ).setParameterLength( 1 ); + +/** + * Returns `true` if any components of `x` are `true`. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const any = /*@__PURE__*/ nodeProxy( MathNode, MathNode.ANY ).setParameterLength( 1 ); + +/** + * Converts a quantity in degrees to radians. + * + * @tsl + * @function + * @param {Node | number} x - The input in degrees. + * @returns {Node} + */ +const radians = /*@__PURE__*/ nodeProxy( MathNode, MathNode.RADIANS ).setParameterLength( 1 ); + +/** + * Convert a quantity in radians to degrees. + * + * @tsl + * @function + * @param {Node | number} x - The input in radians. + * @returns {Node} + */ +const degrees = /*@__PURE__*/ nodeProxy( MathNode, MathNode.DEGREES ).setParameterLength( 1 ); + +/** + * Returns the natural exponentiation of the parameter. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const exp = /*@__PURE__*/ nodeProxy( MathNode, MathNode.EXP ).setParameterLength( 1 ); + +/** + * Returns 2 raised to the power of the parameter. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const exp2 = /*@__PURE__*/ nodeProxy( MathNode, MathNode.EXP2 ).setParameterLength( 1 ); + +/** + * Returns the natural logarithm of the parameter. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const log = /*@__PURE__*/ nodeProxy( MathNode, MathNode.LOG ).setParameterLength( 1 ); + +/** + * Returns the base 2 logarithm of the parameter. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const log2 = /*@__PURE__*/ nodeProxy( MathNode, MathNode.LOG2 ).setParameterLength( 1 ); + +/** + * Returns the square root of the parameter. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const sqrt = /*@__PURE__*/ nodeProxy( MathNode, MathNode.SQRT ).setParameterLength( 1 ); + +/** + * Returns the inverse of the square root of the parameter. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const inverseSqrt = /*@__PURE__*/ nodeProxy( MathNode, MathNode.INVERSE_SQRT ).setParameterLength( 1 ); + +/** + * Finds the nearest integer less than or equal to the parameter. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const floor = /*@__PURE__*/ nodeProxy( MathNode, MathNode.FLOOR ).setParameterLength( 1 ); + +/** + * Finds the nearest integer that is greater than or equal to the parameter. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const ceil = /*@__PURE__*/ nodeProxy( MathNode, MathNode.CEIL ).setParameterLength( 1 ); + +/** + * Calculates the unit vector in the same direction as the original vector. + * + * @tsl + * @function + * @param {Node} x - The input vector. + * @returns {Node} + */ +const normalize = /*@__PURE__*/ nodeProxy( MathNode, MathNode.NORMALIZE ).setParameterLength( 1 ); + +/** + * Computes the fractional part of the parameter. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const fract = /*@__PURE__*/ nodeProxy( MathNode, MathNode.FRACT ).setParameterLength( 1 ); + +/** + * Returns the sine of the parameter. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const sin = /*@__PURE__*/ nodeProxy( MathNode, MathNode.SIN ).setParameterLength( 1 ); + +/** + * Returns the cosine of the parameter. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const cos = /*@__PURE__*/ nodeProxy( MathNode, MathNode.COS ).setParameterLength( 1 ); + +/** + * Returns the tangent of the parameter. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const tan = /*@__PURE__*/ nodeProxy( MathNode, MathNode.TAN ).setParameterLength( 1 ); + +/** + * Returns the arcsine of the parameter. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const asin = /*@__PURE__*/ nodeProxy( MathNode, MathNode.ASIN ).setParameterLength( 1 ); + +/** + * Returns the arccosine of the parameter. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const acos = /*@__PURE__*/ nodeProxy( MathNode, MathNode.ACOS ).setParameterLength( 1 ); + +/** + * Returns the arc-tangent of the parameter. + * If two parameters are provided, the result is `atan2(y/x)`. + * + * @tsl + * @function + * @param {Node | number} y - The y parameter. + * @param {?(Node | number)} x - The x parameter. + * @returns {Node} + */ +const atan = /*@__PURE__*/ nodeProxy( MathNode, MathNode.ATAN ).setParameterLength( 1, 2 ); + +/** + * Returns the absolute value of the parameter. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const abs = /*@__PURE__*/ nodeProxy( MathNode, MathNode.ABS ).setParameterLength( 1 ); + +/** + * Extracts the sign of the parameter. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const sign = /*@__PURE__*/ nodeProxy( MathNode, MathNode.SIGN ).setParameterLength( 1 ); + +/** + * Calculates the length of a vector. + * + * @tsl + * @function + * @param {Node} x - The parameter. + * @returns {Node} + */ +const length = /*@__PURE__*/ nodeProxy( MathNode, MathNode.LENGTH ).setParameterLength( 1 ); + +/** + * Negates the value of the parameter (-x). + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const negate = /*@__PURE__*/ nodeProxy( MathNode, MathNode.NEGATE ).setParameterLength( 1 ); + +/** + * Return `1` minus the parameter. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const oneMinus = /*@__PURE__*/ nodeProxy( MathNode, MathNode.ONE_MINUS ).setParameterLength( 1 ); + +/** + * Returns the partial derivative of the parameter with respect to x. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const dFdx = /*@__PURE__*/ nodeProxy( MathNode, MathNode.DFDX ).setParameterLength( 1 ); + +/** + * Returns the partial derivative of the parameter with respect to y. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const dFdy = /*@__PURE__*/ nodeProxy( MathNode, MathNode.DFDY ).setParameterLength( 1 ); + +/** + * Rounds the parameter to the nearest integer. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const round = /*@__PURE__*/ nodeProxy( MathNode, MathNode.ROUND ).setParameterLength( 1 ); + +/** + * Returns the reciprocal of the parameter `(1/x)`. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const reciprocal = /*@__PURE__*/ nodeProxy( MathNode, MathNode.RECIPROCAL ).setParameterLength( 1 ); + +/** + * Truncates the parameter, removing the fractional part. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const trunc = /*@__PURE__*/ nodeProxy( MathNode, MathNode.TRUNC ).setParameterLength( 1 ); + +/** + * Returns the sum of the absolute derivatives in x and y. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @returns {Node} + */ +const fwidth = /*@__PURE__*/ nodeProxy( MathNode, MathNode.FWIDTH ).setParameterLength( 1 ); + +/** + * Returns the transpose of a matrix. + * + * @tsl + * @function + * @param {Node} x - The parameter. + * @returns {Node} + */ +const transpose = /*@__PURE__*/ nodeProxy( MathNode, MathNode.TRANSPOSE ).setParameterLength( 1 ); + +// 2 inputs + +/** + * Reinterpret the bit representation of a value in one type as a value in another type. + * + * @tsl + * @function + * @param {Node | number} x - The parameter. + * @param {string} y - The new type. + * @returns {Node} + */ +const bitcast = /*@__PURE__*/ nodeProxy( MathNode, MathNode.BITCAST ).setParameterLength( 2 ); + +/** + * Returns `true` if `x` equals `y`. + * + * @tsl + * @function + * @param {Node | number} x - The first parameter. + * @param {Node | number} y - The second parameter. + * @deprecated since r175. Use {@link equal} instead. + * @returns {Node} + */ +const equals = ( x, y ) => { // @deprecated, r172 + + console.warn( 'THREE.TSL: "equals" is deprecated. Use "equal" inside a vector instead, like: "bvec*( equal( ... ) )"' ); + return equal( x, y ); + +}; + +/** + * Returns the least of the given values. + * + * @tsl + * @function + * @param {...(Node | number)} values - The values to compare. + * @returns {Node} + */ +const min$1 = /*@__PURE__*/ nodeProxy( MathNode, MathNode.MIN ).setParameterLength( 2, Infinity ); + +/** + * Returns the greatest of the given values. + * + * @tsl + * @function + * @param {...(Node | number)} values - The values to compare. + * @returns {Node} + */ +const max$1 = /*@__PURE__*/ nodeProxy( MathNode, MathNode.MAX ).setParameterLength( 2, Infinity ); + +/** + * Generate a step function by comparing two values. + * + * @tsl + * @function + * @param {Node | number} x - The y parameter. + * @param {Node | number} y - The x parameter. + * @returns {Node} + */ +const step = /*@__PURE__*/ nodeProxy( MathNode, MathNode.STEP ).setParameterLength( 2 ); + +/** + * Calculates the reflection direction for an incident vector. + * + * @tsl + * @function + * @param {Node} I - The incident vector. + * @param {Node} N - The normal vector. + * @returns {Node} + */ +const reflect = /*@__PURE__*/ nodeProxy( MathNode, MathNode.REFLECT ).setParameterLength( 2 ); + +/** + * Calculates the distance between two points. + * + * @tsl + * @function + * @param {Node} x - The first point. + * @param {Node} y - The second point. + * @returns {Node} + */ +const distance = /*@__PURE__*/ nodeProxy( MathNode, MathNode.DISTANCE ).setParameterLength( 2 ); + +/** + * Calculates the absolute difference between two values. + * + * @tsl + * @function + * @param {Node | number} x - The first parameter. + * @param {Node | number} y - The second parameter. + * @returns {Node} + */ +const difference = /*@__PURE__*/ nodeProxy( MathNode, MathNode.DIFFERENCE ).setParameterLength( 2 ); + +/** + * Calculates the dot product of two vectors. + * + * @tsl + * @function + * @param {Node} x - The first vector. + * @param {Node} y - The second vector. + * @returns {Node} + */ +const dot = /*@__PURE__*/ nodeProxy( MathNode, MathNode.DOT ).setParameterLength( 2 ); + +/** + * Calculates the cross product of two vectors. + * + * @tsl + * @function + * @param {Node} x - The first vector. + * @param {Node} y - The second vector. + * @returns {Node} + */ +const cross = /*@__PURE__*/ nodeProxy( MathNode, MathNode.CROSS ).setParameterLength( 2 ); + +/** + * Return the value of the first parameter raised to the power of the second one. + * + * @tsl + * @function + * @param {Node | number} x - The first parameter. + * @param {Node | number} y - The second parameter. + * @returns {Node} + */ +const pow = /*@__PURE__*/ nodeProxy( MathNode, MathNode.POW ).setParameterLength( 2 ); + +/** + * Returns the square of the parameter. + * + * @tsl + * @function + * @param {Node | number} x - The first parameter. + * @returns {Node} + */ +const pow2 = /*@__PURE__*/ nodeProxy( MathNode, MathNode.POW, 2 ).setParameterLength( 1 ); + +/** + * Returns the cube of the parameter. + * + * @tsl + * @function + * @param {Node | number} x - The first parameter. + * @returns {Node} + */ +const pow3 = /*@__PURE__*/ nodeProxy( MathNode, MathNode.POW, 3 ).setParameterLength( 1 ); + +/** + * Returns the fourth power of the parameter. + * + * @tsl + * @function + * @param {Node | number} x - The first parameter. + * @returns {Node} + */ +const pow4 = /*@__PURE__*/ nodeProxy( MathNode, MathNode.POW, 4 ).setParameterLength( 1 ); + +/** + * Transforms the direction of a vector by a matrix and then normalizes the result. + * + * @tsl + * @function + * @param {Node} direction - The direction vector. + * @param {Node} matrix - The transformation matrix. + * @returns {Node} + */ +const transformDirection = /*@__PURE__*/ nodeProxy( MathNode, MathNode.TRANSFORM_DIRECTION ).setParameterLength( 2 ); + +/** + * Returns the cube root of a number. + * + * @tsl + * @function + * @param {Node | number} a - The first parameter. + * @returns {Node} + */ +const cbrt = ( a ) => mul( sign( a ), pow( abs( a ), 1.0 / 3.0 ) ); + +/** + * Calculate the squared length of a vector. + * + * @tsl + * @function + * @param {Node} a - The vector. + * @returns {Node} + */ +const lengthSq = ( a ) => dot( a, a ); + +/** + * Linearly interpolates between two values. + * + * @tsl + * @function + * @param {Node | number} a - The first parameter. + * @param {Node | number} b - The second parameter. + * @param {Node | number} t - The interpolation value. + * @returns {Node} + */ +const mix = /*@__PURE__*/ nodeProxy( MathNode, MathNode.MIX ).setParameterLength( 3 ); + +/** + * Constrains a value to lie between two further values. + * + * @tsl + * @function + * @param {Node | number} value - The value to constrain. + * @param {Node | number} [low=0] - The lower bound. + * @param {Node | number} [high=1] - The upper bound. + * @returns {Node} + */ +const clamp = ( value, low = 0, high = 1 ) => nodeObject( new MathNode( MathNode.CLAMP, nodeObject( value ), nodeObject( low ), nodeObject( high ) ) ); + +/** + * Constrains a value between `0` and `1`. + * + * @tsl + * @function + * @param {Node | number} value - The value to constrain. + * @returns {Node} + */ +const saturate = ( value ) => clamp( value ); + +/** + * Calculates the refraction direction for an incident vector. + * + * @tsl + * @function + * @param {Node} I - The incident vector. + * @param {Node} N - The normal vector. + * @param {Node} eta - The ratio of indices of refraction. + * @returns {Node} + */ +const refract = /*@__PURE__*/ nodeProxy( MathNode, MathNode.REFRACT ).setParameterLength( 3 ); + +/** + * Performs a Hermite interpolation between two values. + * + * @tsl + * @function + * @param {Node | number} low - The value of the lower edge of the Hermite function. + * @param {Node | number} high - The value of the upper edge of the Hermite function. + * @param {Node | number} x - The source value for interpolation. + * @returns {Node} + */ +const smoothstep = /*@__PURE__*/ nodeProxy( MathNode, MathNode.SMOOTHSTEP ).setParameterLength( 3 ); + +/** + * Returns a vector pointing in the same direction as another. + * + * @tsl + * @function + * @param {Node} N - The vector to orient. + * @param {Node} I - The incident vector. + * @param {Node} Nref - The reference vector. + * @returns {Node} + */ +const faceForward = /*@__PURE__*/ nodeProxy( MathNode, MathNode.FACEFORWARD ).setParameterLength( 3 ); + +/** + * Returns a random value for the given uv. + * + * @tsl + * @function + * @param {Node} uv - The uv node. + * @returns {Node} + */ +const rand = /*@__PURE__*/ Fn( ( [ uv ] ) => { + + const a = 12.9898, b = 78.233, c = 43758.5453; + const dt = dot( uv.xy, vec2( a, b ) ), sn = mod( dt, PI ); + + return fract( sin( sn ).mul( c ) ); + +} ); + +/** + * Alias for `mix()` with a different parameter order. + * + * @tsl + * @function + * @param {Node | number} t - The interpolation value. + * @param {Node | number} e1 - The first parameter. + * @param {Node | number} e2 - The second parameter. + * @returns {Node} + */ +const mixElement = ( t, e1, e2 ) => mix( e1, e2, t ); + +/** + * Alias for `smoothstep()` with a different parameter order. + * + * @tsl + * @function + * @param {Node | number} x - The source value for interpolation. + * @param {Node | number} low - The value of the lower edge of the Hermite function. + * @param {Node | number} high - The value of the upper edge of the Hermite function. + * @returns {Node} + */ +const smoothstepElement = ( x, low, high ) => smoothstep( low, high, x ); + +/** + * Alias for `step()` with a different parameter order. + * + * @tsl + * @function + * @param {Node | number} x - The source value for interpolation. + * @param {Node | number} edge - The edge value. + * @returns {Node} + */ +const stepElement = ( x, edge ) => step( edge, x ); + +/** + * Returns the arc-tangent of the quotient of its parameters. + * + * @tsl + * @function + * @deprecated since r172. Use {@link atan} instead. + * + * @param {Node | number} y - The y parameter. + * @param {Node | number} x - The x parameter. + * @returns {Node} + */ +const atan2 = ( y, x ) => { // @deprecated, r172 + + console.warn( 'THREE.TSL: "atan2" is overloaded. Use "atan" instead.' ); + return atan( y, x ); + +}; + +// GLSL alias function + +const faceforward = faceForward; +const inversesqrt = inverseSqrt; + +// Method chaining + +addMethodChaining( 'all', all ); +addMethodChaining( 'any', any ); +addMethodChaining( 'equals', equals ); + +addMethodChaining( 'radians', radians ); +addMethodChaining( 'degrees', degrees ); +addMethodChaining( 'exp', exp ); +addMethodChaining( 'exp2', exp2 ); +addMethodChaining( 'log', log ); +addMethodChaining( 'log2', log2 ); +addMethodChaining( 'sqrt', sqrt ); +addMethodChaining( 'inverseSqrt', inverseSqrt ); +addMethodChaining( 'floor', floor ); +addMethodChaining( 'ceil', ceil ); +addMethodChaining( 'normalize', normalize ); +addMethodChaining( 'fract', fract ); +addMethodChaining( 'sin', sin ); +addMethodChaining( 'cos', cos ); +addMethodChaining( 'tan', tan ); +addMethodChaining( 'asin', asin ); +addMethodChaining( 'acos', acos ); +addMethodChaining( 'atan', atan ); +addMethodChaining( 'abs', abs ); +addMethodChaining( 'sign', sign ); +addMethodChaining( 'length', length ); +addMethodChaining( 'lengthSq', lengthSq ); +addMethodChaining( 'negate', negate ); +addMethodChaining( 'oneMinus', oneMinus ); +addMethodChaining( 'dFdx', dFdx ); +addMethodChaining( 'dFdy', dFdy ); +addMethodChaining( 'round', round ); +addMethodChaining( 'reciprocal', reciprocal ); +addMethodChaining( 'trunc', trunc ); +addMethodChaining( 'fwidth', fwidth ); +addMethodChaining( 'atan2', atan2 ); +addMethodChaining( 'min', min$1 ); +addMethodChaining( 'max', max$1 ); +addMethodChaining( 'step', stepElement ); +addMethodChaining( 'reflect', reflect ); +addMethodChaining( 'distance', distance ); +addMethodChaining( 'dot', dot ); +addMethodChaining( 'cross', cross ); +addMethodChaining( 'pow', pow ); +addMethodChaining( 'pow2', pow2 ); +addMethodChaining( 'pow3', pow3 ); +addMethodChaining( 'pow4', pow4 ); +addMethodChaining( 'transformDirection', transformDirection ); +addMethodChaining( 'mix', mixElement ); +addMethodChaining( 'clamp', clamp ); +addMethodChaining( 'refract', refract ); +addMethodChaining( 'smoothstep', smoothstepElement ); +addMethodChaining( 'faceForward', faceForward ); +addMethodChaining( 'difference', difference ); +addMethodChaining( 'saturate', saturate ); +addMethodChaining( 'cbrt', cbrt ); +addMethodChaining( 'transpose', transpose ); +addMethodChaining( 'rand', rand ); + +/** + * Represents a logical `if/else` statement. Can be used as an alternative + * to the `If()`/`Else()` syntax. + * + * The corresponding TSL `select()` looks like so: + * ```js + * velocity = position.greaterThanEqual( limit ).select( velocity.negate(), velocity ); + * ``` + * The `select()` method is called in a chaining fashion on a condition. The parameter nodes of `select()` + * determine the outcome of the entire statement. + * + * @augments Node + */ +class ConditionalNode extends Node { + + static get type() { + + return 'ConditionalNode'; + + } + + /** + * Constructs a new conditional node. + * + * @param {Node} condNode - The node that defines the condition. + * @param {Node} ifNode - The node that is evaluate when the condition ends up `true`. + * @param {?Node} [elseNode=null] - The node that is evaluate when the condition ends up `false`. + */ + constructor( condNode, ifNode, elseNode = null ) { + + super(); + + /** + * The node that defines the condition. + * + * @type {Node} + */ + this.condNode = condNode; + + /** + * The node that is evaluate when the condition ends up `true`. + * + * @type {Node} + */ + this.ifNode = ifNode; + + /** + * The node that is evaluate when the condition ends up `false`. + * + * @type {?Node} + * @default null + */ + this.elseNode = elseNode; + + } + + /** + * This method is overwritten since the node type is inferred from the if/else + * nodes. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType( builder ) { + + const { ifNode, elseNode } = builder.getNodeProperties( this ); + + if ( ifNode === undefined ) { + + // fallback setup + + this.setup( builder ); + + return this.getNodeType( builder ); + + } + + const ifType = ifNode.getNodeType( builder ); + + if ( elseNode !== null ) { + + const elseType = elseNode.getNodeType( builder ); + + if ( builder.getTypeLength( elseType ) > builder.getTypeLength( ifType ) ) { + + return elseType; + + } + + } + + return ifType; + + } + + setup( builder ) { + + const condNode = this.condNode.cache(); + const ifNode = this.ifNode.cache(); + const elseNode = this.elseNode ? this.elseNode.cache() : null; + + // + + const currentNodeBlock = builder.context.nodeBlock; + + builder.getDataFromNode( ifNode ).parentNodeBlock = currentNodeBlock; + if ( elseNode !== null ) builder.getDataFromNode( elseNode ).parentNodeBlock = currentNodeBlock; + + // + + const properties = builder.getNodeProperties( this ); + properties.condNode = condNode; + properties.ifNode = ifNode.context( { nodeBlock: ifNode } ); + properties.elseNode = elseNode ? elseNode.context( { nodeBlock: elseNode } ) : null; + + } + + generate( builder, output ) { + + const type = this.getNodeType( builder ); + + const nodeData = builder.getDataFromNode( this ); + + if ( nodeData.nodeProperty !== undefined ) { + + return nodeData.nodeProperty; + + } + + const { condNode, ifNode, elseNode } = builder.getNodeProperties( this ); + + const functionNode = builder.currentFunctionNode; + const needsOutput = output !== 'void'; + const nodeProperty = needsOutput ? property( type ).build( builder ) : ''; + + nodeData.nodeProperty = nodeProperty; + + const nodeSnippet = condNode.build( builder, 'bool' ); + + builder.addFlowCode( `\n${ builder.tab }if ( ${ nodeSnippet } ) {\n\n` ).addFlowTab(); + + let ifSnippet = ifNode.build( builder, type ); + + if ( ifSnippet ) { + + if ( needsOutput ) { + + ifSnippet = nodeProperty + ' = ' + ifSnippet + ';'; + + } else { + + ifSnippet = 'return ' + ifSnippet + ';'; + + if ( functionNode === null ) { + + console.warn( 'THREE.TSL: Return statement used in an inline \'Fn()\'. Define a layout struct to allow return values.' ); + + ifSnippet = '// ' + ifSnippet; + + } + + } + + } + + builder.removeFlowTab().addFlowCode( builder.tab + '\t' + ifSnippet + '\n\n' + builder.tab + '}' ); + + if ( elseNode !== null ) { + + builder.addFlowCode( ' else {\n\n' ).addFlowTab(); + + let elseSnippet = elseNode.build( builder, type ); + + if ( elseSnippet ) { + + if ( needsOutput ) { + + elseSnippet = nodeProperty + ' = ' + elseSnippet + ';'; + + } else { + + elseSnippet = 'return ' + elseSnippet + ';'; + + if ( functionNode === null ) { + + console.warn( 'THREE.TSL: Return statement used in an inline \'Fn()\'. Define a layout struct to allow return values.' ); + + elseSnippet = '// ' + elseSnippet; + + } + + } + + } + + builder.removeFlowTab().addFlowCode( builder.tab + '\t' + elseSnippet + '\n\n' + builder.tab + '}\n\n' ); + + } else { + + builder.addFlowCode( '\n\n' ); + + } + + return builder.format( nodeProperty, type, output ); + + } + +} + +/** + * TSL function for creating a conditional node. + * + * @tsl + * @function + * @param {Node} condNode - The node that defines the condition. + * @param {Node} ifNode - The node that is evaluate when the condition ends up `true`. + * @param {?Node} [elseNode=null] - The node that is evaluate when the condition ends up `false`. + * @returns {ConditionalNode} + */ +const select = /*@__PURE__*/ nodeProxy( ConditionalNode ).setParameterLength( 2, 3 ); + +addMethodChaining( 'select', select ); + +/** + * This node can be used as a context management component for another node. + * {@link NodeBuilder} performs its node building process in a specific context and + * this node allows the modify the context. A typical use case is to overwrite `getUV()` e.g.: + * + * ```js + *node.context( { getUV: () => customCoord } ); + *``` + * @augments Node + */ +class ContextNode extends Node { + + static get type() { + + return 'ContextNode'; + + } + + /** + * Constructs a new context node. + * + * @param {Node} node - The node whose context should be modified. + * @param {Object} [value={}] - The modified context data. + */ + constructor( node, value = {} ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isContextNode = true; + + /** + * The node whose context should be modified. + * + * @type {Node} + */ + this.node = node; + + /** + * The modified context data. + * + * @type {Object} + * @default {} + */ + this.value = value; + + } + + /** + * This method is overwritten to ensure it returns the reference to {@link ContextNode#node}. + * + * @return {Node} A reference to {@link ContextNode#node}. + */ + getScope() { + + return this.node.getScope(); + + } + + /** + * This method is overwritten to ensure it returns the type of {@link ContextNode#node}. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType( builder ) { + + return this.node.getNodeType( builder ); + + } + + analyze( builder ) { + + const previousContext = builder.getContext(); + + builder.setContext( { ...builder.context, ...this.value } ); + + this.node.build( builder ); + + builder.setContext( previousContext ); + + } + + setup( builder ) { + + const previousContext = builder.getContext(); + + builder.setContext( { ...builder.context, ...this.value } ); + + this.node.build( builder ); + + builder.setContext( previousContext ); + + } + + generate( builder, output ) { + + const previousContext = builder.getContext(); + + builder.setContext( { ...builder.context, ...this.value } ); + + const snippet = this.node.build( builder, output ); + + builder.setContext( previousContext ); + + return snippet; + + } + +} + +/** + * TSL function for creating a context node. + * + * @tsl + * @function + * @param {Node} node - The node whose context should be modified. + * @param {Object} [value={}] - The modified context data. + * @returns {ContextNode} + */ +const context = /*@__PURE__*/ nodeProxy( ContextNode ).setParameterLength( 1, 2 ); + +/** + * TSL function for defining a label context value for a given node. + * + * @tsl + * @function + * @param {Node} node - The node whose context should be modified. + * @param {string} name - The name/label to set. + * @returns {ContextNode} + */ +const label = ( node, name ) => context( node, { label: name } ); + +addMethodChaining( 'context', context ); +addMethodChaining( 'label', label ); + +/** + * Class for representing shader variables as nodes. Variables are created from + * existing nodes like the following: + * + * ```js + * const depth = sampleDepth( uvNode ).toVar( 'depth' ); + * ``` + * + * @augments Node + */ +class VarNode extends Node { + + static get type() { + + return 'VarNode'; + + } + + /** + * Constructs a new variable node. + * + * @param {Node} node - The node for which a variable should be created. + * @param {?string} [name=null] - The name of the variable in the shader. + * @param {boolean} [readOnly=false] - The read-only flag. + */ + constructor( node, name = null, readOnly = false ) { + + super(); + + /** + * The node for which a variable should be created. + * + * @type {Node} + */ + this.node = node; + + /** + * The name of the variable in the shader. If no name is defined, + * the node system auto-generates one. + * + * @type {?string} + * @default null + */ + this.name = name; + + /** + * `VarNode` sets this property to `true` by default. + * + * @type {boolean} + * @default true + */ + this.global = true; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isVarNode = true; + + /** + * + * The read-only flag. + * + * @type {boolean} + * @default false + */ + this.readOnly = readOnly; + + /** + * + * Add this flag to the node system to indicate that this node require parents. + * + * @type {boolean} + * @default true + */ + this.parents = true; + + } + + getMemberType( builder, name ) { + + return this.node.getMemberType( builder, name ); + + } + + getElementType( builder ) { + + return this.node.getElementType( builder ); + + } + + getNodeType( builder ) { + + return this.node.getNodeType( builder ); + + } + + generate( builder ) { + + const { node, name, readOnly } = this; + const { renderer } = builder; + + const isWebGPUBackend = renderer.backend.isWebGPUBackend === true; + + let isDeterministic = false; + let shouldTreatAsReadOnly = false; + + if ( readOnly ) { + + isDeterministic = builder.isDeterministic( node ); + + shouldTreatAsReadOnly = isWebGPUBackend ? readOnly : isDeterministic; + + } + + const vectorType = builder.getVectorType( this.getNodeType( builder ) ); + const snippet = node.build( builder, vectorType ); + + const nodeVar = builder.getVarFromNode( this, name, vectorType, undefined, shouldTreatAsReadOnly ); + + const propertyName = builder.getPropertyName( nodeVar ); + + let declarationPrefix = propertyName; + + if ( shouldTreatAsReadOnly ) { + + if ( isWebGPUBackend ) { + + declarationPrefix = isDeterministic + ? `const ${ propertyName }` + : `let ${ propertyName }`; + + } else { + + const count = builder.getArrayCount( node ); + + declarationPrefix = `const ${ builder.getVar( nodeVar.type, propertyName, count ) }`; + + } + + } + + builder.addLineFlowCode( `${ declarationPrefix } = ${ snippet }`, this ); + + return propertyName; + + } + +} + +/** + * TSL function for creating a var node. + * + * @tsl + * @function + * @param {Node} node - The node for which a variable should be created. + * @param {?string} name - The name of the variable in the shader. + * @returns {VarNode} + */ +const createVar = /*@__PURE__*/ nodeProxy( VarNode ); + +/** + * TSL function for creating a var node. + * + * @tsl + * @function + * @param {Node} node - The node for which a variable should be created. + * @param {?string} name - The name of the variable in the shader. + * @returns {VarNode} + */ +const Var = ( node, name = null ) => createVar( node, name ).toStack(); + +/** + * TSL function for creating a const node. + * + * @tsl + * @function + * @param {Node} node - The node for which a constant should be created. + * @param {?string} name - The name of the constant in the shader. + * @returns {VarNode} + */ +const Const = ( node, name = null ) => createVar( node, name, true ).toStack(); + +// Method chaining + +addMethodChaining( 'toVar', Var ); +addMethodChaining( 'toConst', Const ); + +// Deprecated + +/** + * @tsl + * @function + * @deprecated since r170. Use `Var( node )` or `node.toVar()` instead. + * + * @param {any} node + * @returns {VarNode} + */ +const temp = ( node ) => { // @deprecated, r170 + + console.warn( 'TSL: "temp( node )" is deprecated. Use "Var( node )" or "node.toVar()" instead.' ); + + return createVar( node ); + +}; + +addMethodChaining( 'temp', temp ); + +/** + * This node is used to build a sub-build in the node system. + * + * @augments Node + * @param {Node} node - The node to be built in the sub-build. + * @param {string} name - The name of the sub-build. + * @param {string|null} [nodeType=null] - The type of the node, if known. + */ +class SubBuildNode extends Node { + + static get type() { + + return 'SubBuild'; + + } + + constructor( node, name, nodeType = null ) { + + super( nodeType ); + + /** + * The node to be built in the sub-build. + * + * @type {Node} + */ + this.node = node; + + /** + * The name of the sub-build. + * + * @type {string} + */ + this.name = name; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSubBuildNode = true; + + } + + getNodeType( builder ) { + + if ( this.nodeType !== null ) return this.nodeType; + + builder.addSubBuild( this.name ); + + const nodeType = this.node.getNodeType( builder ); + + builder.removeSubBuild(); + + return nodeType; + + } + + build( builder, ...params ) { + + builder.addSubBuild( this.name ); + + const data = this.node.build( builder, ...params ); + + builder.removeSubBuild(); + + return data; + + } + +} + +/** + * Creates a new sub-build node. + * + * @tsl + * @function + * @param {Node} node - The node to be built in the sub-build. + * @param {string} name - The name of the sub-build. + * @param {string|null} [type=null] - The type of the node, if known. + * @returns {Node} A node object wrapping the SubBuildNode instance. + */ +const subBuild = ( node, name, type = null ) => nodeObject( new SubBuildNode( nodeObject( node ), name, type ) ); + +/** + * Class for representing shader varyings as nodes. Varyings are create from + * existing nodes like the following: + * + * ```js + * const positionLocal = positionGeometry.toVarying( 'vPositionLocal' ); + * ``` + * + * @augments Node + */ +class VaryingNode extends Node { + + static get type() { + + return 'VaryingNode'; + + } + + /** + * Constructs a new varying node. + * + * @param {Node} node - The node for which a varying should be created. + * @param {?string} name - The name of the varying in the shader. + */ + constructor( node, name = null ) { + + super(); + + /** + * The node for which a varying should be created. + * + * @type {Node} + */ + this.node = node; + + /** + * The name of the varying in the shader. If no name is defined, + * the node system auto-generates one. + * + * @type {?string} + * @default null + */ + this.name = name; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isVaryingNode = true; + + /** + * The interpolation type of the varying data. + * + * @type {?string} + * @default null + */ + this.interpolationType = null; + + /** + * The interpolation sampling type of varying data. + * + * @type {?string} + * @default null + */ + this.interpolationSampling = null; + + /** + * This flag is used for global cache. + * + * @type {boolean} + * @default true + */ + this.global = true; + + } + + /** + * Defines the interpolation type of the varying. + * + * @param {string} type - The interpolation type. + * @param {?string} sampling - The interpolation sampling type + * @return {VaryingNode} A reference to this node. + */ + setInterpolation( type, sampling = null ) { + + this.interpolationType = type; + this.interpolationSampling = sampling; + + return this; + + } + + getHash( builder ) { + + return this.name || super.getHash( builder ); + + } + + getNodeType( builder ) { + + // VaryingNode is auto type + + return this.node.getNodeType( builder ); + + } + + /** + * This method performs the setup of a varying node with the current node builder. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {NodeVarying} The node varying from the node builder. + */ + setupVarying( builder ) { + + const properties = builder.getNodeProperties( this ); + + let varying = properties.varying; + + if ( varying === undefined ) { + + const name = this.name; + const type = this.getNodeType( builder ); + const interpolationType = this.interpolationType; + const interpolationSampling = this.interpolationSampling; + + properties.varying = varying = builder.getVaryingFromNode( this, name, type, interpolationType, interpolationSampling ); + properties.node = subBuild( this.node, 'VERTEX' ); + + } + + // this property can be used to check if the varying can be optimized for a variable + varying.needsInterpolation || ( varying.needsInterpolation = ( builder.shaderStage === 'fragment' ) ); + + return varying; + + } + + setup( builder ) { + + this.setupVarying( builder ); + + builder.flowNodeFromShaderStage( NodeShaderStage.VERTEX, this.node ); + + } + + analyze( builder ) { + + this.setupVarying( builder ); + + builder.flowNodeFromShaderStage( NodeShaderStage.VERTEX, this.node ); + + } + + generate( builder ) { + + const propertyKey = builder.getSubBuildProperty( 'property', builder.currentStack ); + const properties = builder.getNodeProperties( this ); + const varying = this.setupVarying( builder ); + + if ( properties[ propertyKey ] === undefined ) { + + const type = this.getNodeType( builder ); + const propertyName = builder.getPropertyName( varying, NodeShaderStage.VERTEX ); + + // force node run in vertex stage + builder.flowNodeFromShaderStage( NodeShaderStage.VERTEX, properties.node, type, propertyName ); + + properties[ propertyKey ] = propertyName; + + } + + return builder.getPropertyName( varying ); + + } + +} + +/** + * TSL function for creating a varying node. + * + * @tsl + * @function + * @param {Node} node - The node for which a varying should be created. + * @param {?string} name - The name of the varying in the shader. + * @returns {VaryingNode} + */ +const varying = /*@__PURE__*/ nodeProxy( VaryingNode ).setParameterLength( 1, 2 ); + +/** + * Computes a node in the vertex stage. + * + * @tsl + * @function + * @param {Node} node - The node which should be executed in the vertex stage. + * @returns {VaryingNode} + */ +const vertexStage = ( node ) => varying( node ); + +addMethodChaining( 'toVarying', varying ); +addMethodChaining( 'toVertexStage', vertexStage ); + +// Deprecated + +addMethodChaining( 'varying', ( ...params ) => { // @deprecated, r173 + + console.warn( 'THREE.TSL: .varying() has been renamed to .toVarying().' ); + return varying( ...params ); + +} ); + +addMethodChaining( 'vertexStage', ( ...params ) => { // @deprecated, r173 + + console.warn( 'THREE.TSL: .vertexStage() has been renamed to .toVertexStage().' ); + return varying( ...params ); + +} ); + +/** + * Converts the given color value from sRGB to linear-sRGB color space. + * + * @tsl + * @function + * @param {Node} color - The sRGB color. + * @return {Node} The linear-sRGB color. + */ +const sRGBTransferEOTF = /*@__PURE__*/ Fn( ( [ color ] ) => { + + const a = color.mul( 0.9478672986 ).add( 0.0521327014 ).pow( 2.4 ); + const b = color.mul( 0.0773993808 ); + const factor = color.lessThanEqual( 0.04045 ); + + const rgbResult = mix( a, b, factor ); + + return rgbResult; + +} ).setLayout( { + name: 'sRGBTransferEOTF', + type: 'vec3', + inputs: [ + { name: 'color', type: 'vec3' } + ] +} ); + +/** + * Converts the given color value from linear-sRGB to sRGB color space. + * + * @tsl + * @function + * @param {Node} color - The linear-sRGB color. + * @return {Node} The sRGB color. + */ +const sRGBTransferOETF = /*@__PURE__*/ Fn( ( [ color ] ) => { + + const a = color.pow( 0.41666 ).mul( 1.055 ).sub( 0.055 ); + const b = color.mul( 12.92 ); + const factor = color.lessThanEqual( 0.0031308 ); + + const rgbResult = mix( a, b, factor ); + + return rgbResult; + +} ).setLayout( { + name: 'sRGBTransferOETF', + type: 'vec3', + inputs: [ + { name: 'color', type: 'vec3' } + ] +} ); + +const WORKING_COLOR_SPACE = 'WorkingColorSpace'; +const OUTPUT_COLOR_SPACE = 'OutputColorSpace'; + +/** + * This node represents a color space conversion. Meaning it converts + * a color value from a source to a target color space. + * + * @augments TempNode + */ +class ColorSpaceNode extends TempNode { + + static get type() { + + return 'ColorSpaceNode'; + + } + + /** + * Constructs a new color space node. + * + * @param {Node} colorNode - Represents the color to convert. + * @param {string} source - The source color space. + * @param {string} target - The target color space. + */ + constructor( colorNode, source, target ) { + + super( 'vec4' ); + + /** + * Represents the color to convert. + * + * @type {Node} + */ + this.colorNode = colorNode; + + /** + * The source color space. + * + * @type {string} + */ + this.source = source; + + /** + * The target color space. + * + * @type {string} + */ + this.target = target; + + } + + /** + * This method resolves the constants `WORKING_COLOR_SPACE` and + * `OUTPUT_COLOR_SPACE` based on the current configuration of the + * color management and renderer. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {string} colorSpace - The color space to resolve. + * @return {string} The resolved color space. + */ + resolveColorSpace( builder, colorSpace ) { + + if ( colorSpace === WORKING_COLOR_SPACE ) { + + return ColorManagement.workingColorSpace; + + } else if ( colorSpace === OUTPUT_COLOR_SPACE ) { + + return builder.context.outputColorSpace || builder.renderer.outputColorSpace; + + } + + return colorSpace; + + } + + setup( builder ) { + + const { colorNode } = this; + + const source = this.resolveColorSpace( builder, this.source ); + const target = this.resolveColorSpace( builder, this.target ); + + let outputNode = colorNode; + + if ( ColorManagement.enabled === false || source === target || ! source || ! target ) { + + return outputNode; + + } + + if ( ColorManagement.getTransfer( source ) === SRGBTransfer ) { + + outputNode = vec4( sRGBTransferEOTF( outputNode.rgb ), outputNode.a ); + + } + + if ( ColorManagement.getPrimaries( source ) !== ColorManagement.getPrimaries( target ) ) { + + outputNode = vec4( + mat3( ColorManagement._getMatrix( new Matrix3(), source, target ) ).mul( outputNode.rgb ), + outputNode.a + ); + + } + + if ( ColorManagement.getTransfer( target ) === SRGBTransfer ) { + + outputNode = vec4( sRGBTransferOETF( outputNode.rgb ), outputNode.a ); + + } + + return outputNode; + + } + +} + +/** + * TSL function for converting a given color node from the current working color space to the given color space. + * + * @tsl + * @function + * @param {Node} node - Represents the node to convert. + * @param {string} targetColorSpace - The target color space. + * @returns {ColorSpaceNode} + */ +const workingToColorSpace = ( node, targetColorSpace ) => nodeObject( new ColorSpaceNode( nodeObject( node ), WORKING_COLOR_SPACE, targetColorSpace ) ); + +/** + * TSL function for converting a given color node from the given color space to the current working color space. + * + * @tsl + * @function + * @param {Node} node - Represents the node to convert. + * @param {string} sourceColorSpace - The source color space. + * @returns {ColorSpaceNode} + */ +const colorSpaceToWorking = ( node, sourceColorSpace ) => nodeObject( new ColorSpaceNode( nodeObject( node ), sourceColorSpace, WORKING_COLOR_SPACE ) ); + +/** + * TSL function for converting a given color node from one color space to another one. + * + * @tsl + * @function + * @param {Node} node - Represents the node to convert. + * @param {string} sourceColorSpace - The source color space. + * @param {string} targetColorSpace - The target color space. + * @returns {ColorSpaceNode} + */ +const convertColorSpace = ( node, sourceColorSpace, targetColorSpace ) => nodeObject( new ColorSpaceNode( nodeObject( node ), sourceColorSpace, targetColorSpace ) ); + +addMethodChaining( 'workingToColorSpace', workingToColorSpace ); +addMethodChaining( 'colorSpaceToWorking', colorSpaceToWorking ); + +// TODO: Avoid duplicated code and ues only ReferenceBaseNode or ReferenceNode + +/** + * This class is only relevant if the referenced property is array-like. + * In this case, `ReferenceElementNode` allows to refer to a specific + * element inside the data structure via an index. + * + * @augments ArrayElementNode + */ +let ReferenceElementNode$1 = class ReferenceElementNode extends ArrayElementNode { + + static get type() { + + return 'ReferenceElementNode'; + + } + + /** + * Constructs a new reference element node. + * + * @param {ReferenceBaseNode} referenceNode - The reference node. + * @param {Node} indexNode - The index node that defines the element access. + */ + constructor( referenceNode, indexNode ) { + + super( referenceNode, indexNode ); + + /** + * Similar to {@link ReferenceBaseNode#reference}, an additional + * property references to the current node. + * + * @type {?ReferenceBaseNode} + * @default null + */ + this.referenceNode = referenceNode; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isReferenceElementNode = true; + + } + + /** + * This method is overwritten since the node type is inferred from + * the uniform type of the reference node. + * + * @return {string} The node type. + */ + getNodeType() { + + return this.referenceNode.uniformType; + + } + + generate( builder ) { + + const snippet = super.generate( builder ); + const arrayType = this.referenceNode.getNodeType(); + const elementType = this.getNodeType(); + + return builder.format( snippet, arrayType, elementType ); + + } + +}; + +/** + * Base class for nodes which establishes a reference to a property of another object. + * In this way, the value of the node is automatically linked to the value of + * referenced object. Reference nodes internally represent the linked value + * as a uniform. + * + * @augments Node + */ +class ReferenceBaseNode extends Node { + + static get type() { + + return 'ReferenceBaseNode'; + + } + + /** + * Constructs a new reference base node. + * + * @param {string} property - The name of the property the node refers to. + * @param {string} uniformType - The uniform type that should be used to represent the property value. + * @param {?Object} [object=null] - The object the property belongs to. + * @param {?number} [count=null] - When the linked property is an array-like, this parameter defines its length. + */ + constructor( property, uniformType, object = null, count = null ) { + + super(); + + /** + * The name of the property the node refers to. + * + * @type {string} + */ + this.property = property; + + /** + * The uniform type that should be used to represent the property value. + * + * @type {string} + */ + this.uniformType = uniformType; + + /** + * The object the property belongs to. + * + * @type {?Object} + * @default null + */ + this.object = object; + + /** + * When the linked property is an array, this parameter defines its length. + * + * @type {?number} + * @default null + */ + this.count = count; + + /** + * The property name might have dots so nested properties can be referred. + * The hierarchy of the names is stored inside this array. + * + * @type {Array} + */ + this.properties = property.split( '.' ); + + /** + * Points to the current referred object. This property exists next to {@link ReferenceNode#object} + * since the final reference might be updated from calling code. + * + * @type {?Object} + * @default null + */ + this.reference = object; + + /** + * The uniform node that holds the value of the reference node. + * + * @type {UniformNode} + * @default null + */ + this.node = null; + + /** + * The uniform group of the internal uniform. + * + * @type {UniformGroupNode} + * @default null + */ + this.group = null; + + /** + * Overwritten since reference nodes are updated per object. + * + * @type {string} + * @default 'object' + */ + this.updateType = NodeUpdateType.OBJECT; + + } + + /** + * Sets the uniform group for this reference node. + * + * @param {UniformGroupNode} group - The uniform group to set. + * @return {ReferenceBaseNode} A reference to this node. + */ + setGroup( group ) { + + this.group = group; + + return this; + + } + + /** + * When the referred property is array-like, this method can be used + * to access elements via an index node. + * + * @param {IndexNode} indexNode - indexNode. + * @return {ReferenceElementNode} A reference to an element. + */ + element( indexNode ) { + + return nodeObject( new ReferenceElementNode$1( this, nodeObject( indexNode ) ) ); + + } + + /** + * Sets the node type which automatically defines the internal + * uniform type. + * + * @param {string} uniformType - The type to set. + */ + setNodeType( uniformType ) { + + const node = uniform( null, uniformType ).getSelf(); + + if ( this.group !== null ) { + + node.setGroup( this.group ); + + } + + this.node = node; + + } + + /** + * This method is overwritten since the node type is inferred from + * the type of the reference node. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType( builder ) { + + if ( this.node === null ) { + + this.updateReference( builder ); + this.updateValue(); + + } + + return this.node.getNodeType( builder ); + + } + + /** + * Returns the property value from the given referred object. + * + * @param {Object} [object=this.reference] - The object to retrieve the property value from. + * @return {any} The value. + */ + getValueFromReference( object = this.reference ) { + + const { properties } = this; + + let value = object[ properties[ 0 ] ]; + + for ( let i = 1; i < properties.length; i ++ ) { + + value = value[ properties[ i ] ]; + + } + + return value; + + } + + /** + * Allows to update the reference based on the given state. The state is only + * evaluated {@link ReferenceBaseNode#object} is not set. + * + * @param {(NodeFrame|NodeBuilder)} state - The current state. + * @return {Object} The updated reference. + */ + updateReference( state ) { + + this.reference = this.object !== null ? this.object : state.object; + + return this.reference; + + } + + /** + * The output of the reference node is the internal uniform node. + * + * @return {UniformNode} The output node. + */ + setup() { + + this.updateValue(); + + return this.node; + + } + + /** + * Overwritten to update the internal uniform value. + * + * @param {NodeFrame} frame - A reference to the current node frame. + */ + update( /*frame*/ ) { + + this.updateValue(); + + } + + /** + * Retrieves the value from the referred object property and uses it + * to updated the internal uniform. + */ + updateValue() { + + if ( this.node === null ) this.setNodeType( this.uniformType ); + + const value = this.getValueFromReference(); + + if ( Array.isArray( value ) ) { + + this.node.array = value; + + } else { + + this.node.value = value; + + } + + } + +} + +/** + * TSL function for creating a reference base node. + * + * @tsl + * @function + * @param {string} name - The name of the property the node refers to. + * @param {string} type - The uniform type that should be used to represent the property value. + * @param {Object} object - The object the property belongs to. + * @returns {ReferenceBaseNode} + */ +const reference$1 = ( name, type, object ) => nodeObject( new ReferenceBaseNode( name, type, object ) ); + +/** + * This node is a special type of reference node which is intended + * for linking renderer properties with node values. + * ```js + * const exposureNode = rendererReference( 'toneMappingExposure', 'float', renderer ); + * ``` + * When changing `renderer.toneMappingExposure`, the node value of `exposureNode` will + * automatically be updated. + * + * @augments ReferenceBaseNode + */ +class RendererReferenceNode extends ReferenceBaseNode { + + static get type() { + + return 'RendererReferenceNode'; + + } + + /** + * Constructs a new renderer reference node. + * + * @param {string} property - The name of the property the node refers to. + * @param {string} inputType - The uniform type that should be used to represent the property value. + * @param {?Renderer} [renderer=null] - The renderer the property belongs to. When no renderer is set, + * the node refers to the renderer of the current state. + */ + constructor( property, inputType, renderer = null ) { + + super( property, inputType, renderer ); + + /** + * The renderer the property belongs to. When no renderer is set, + * the node refers to the renderer of the current state. + * + * @type {?Renderer} + * @default null + */ + this.renderer = renderer; + + this.setGroup( renderGroup ); + + } + + /** + * Updates the reference based on the given state. The state is only evaluated + * {@link RendererReferenceNode#renderer} is not set. + * + * @param {(NodeFrame|NodeBuilder)} state - The current state. + * @return {Object} The updated reference. + */ + updateReference( state ) { + + this.reference = this.renderer !== null ? this.renderer : state.renderer; + + return this.reference; + + } + +} + +/** + * TSL function for creating a renderer reference node. + * + * @tsl + * @function + * @param {string} name - The name of the property the node refers to. + * @param {string} type - The uniform type that should be used to represent the property value. + * @param {?Renderer} [renderer=null] - The renderer the property belongs to. When no renderer is set, + * the node refers to the renderer of the current state. + * @returns {RendererReferenceNode} + */ +const rendererReference = ( name, type, renderer = null ) => nodeObject( new RendererReferenceNode( name, type, renderer ) ); + +/** + * This node represents a tone mapping operation. + * + * @augments TempNode + */ +class ToneMappingNode extends TempNode { + + static get type() { + + return 'ToneMappingNode'; + + } + + /** + * Constructs a new tone mapping node. + * + * @param {number} toneMapping - The tone mapping type. + * @param {Node} exposureNode - The tone mapping exposure. + * @param {Node} [colorNode=null] - The color node to process. + */ + constructor( toneMapping, exposureNode = toneMappingExposure, colorNode = null ) { + + super( 'vec3' ); + + /** + * The tone mapping type. + * + * @type {number} + */ + this.toneMapping = toneMapping; + + /** + * The tone mapping exposure. + * + * @type {Node} + * @default null + */ + this.exposureNode = exposureNode; + + /** + * Represents the color to process. + * + * @type {?Node} + * @default null + */ + this.colorNode = colorNode; + + } + + /** + * Overwrites the default `customCacheKey()` implementation by including the tone + * mapping type into the cache key. + * + * @return {number} The hash. + */ + customCacheKey() { + + return hash$1( this.toneMapping ); + + } + + setup( builder ) { + + const colorNode = this.colorNode || builder.context.color; + const toneMapping = this.toneMapping; + + if ( toneMapping === NoToneMapping ) return colorNode; + + let outputNode = null; + + const toneMappingFn = builder.renderer.library.getToneMappingFunction( toneMapping ); + + if ( toneMappingFn !== null ) { + + outputNode = vec4( toneMappingFn( colorNode.rgb, this.exposureNode ), colorNode.a ); + + } else { + + console.error( 'ToneMappingNode: Unsupported Tone Mapping configuration.', toneMapping ); + + outputNode = colorNode; + + } + + return outputNode; + + } + +} + +/** + * TSL function for creating a tone mapping node. + * + * @tsl + * @function + * @param {number} mapping - The tone mapping type. + * @param {Node | number} exposure - The tone mapping exposure. + * @param {Node | Color} color - The color node to process. + * @returns {ToneMappingNode} + */ +const toneMapping = ( mapping, exposure, color ) => nodeObject( new ToneMappingNode( mapping, nodeObject( exposure ), nodeObject( color ) ) ); + +/** + * TSL object that represents the global tone mapping exposure of the renderer. + * + * @tsl + * @type {RendererReferenceNode} + */ +const toneMappingExposure = /*@__PURE__*/ rendererReference( 'toneMappingExposure', 'float' ); + +addMethodChaining( 'toneMapping', ( color, mapping, exposure ) => toneMapping( mapping, exposure, color ) ); + +/** + * In earlier `three.js` versions it was only possible to define attribute data + * on geometry level. With `BufferAttributeNode`, it is also possible to do this + * on the node level. + * ```js + * const geometry = new THREE.PlaneGeometry(); + * const positionAttribute = geometry.getAttribute( 'position' ); + * + * const colors = []; + * for ( let i = 0; i < position.count; i ++ ) { + * colors.push( 1, 0, 0 ); + * } + * + * material.colorNode = bufferAttribute( new THREE.Float32BufferAttribute( colors, 3 ) ); + * ``` + * This new approach is especially interesting when geometry data are generated via + * compute shaders. The below line converts a storage buffer into an attribute node. + * ```js + * material.positionNode = positionBuffer.toAttribute(); + * ``` + * @augments InputNode + */ +class BufferAttributeNode extends InputNode { + + static get type() { + + return 'BufferAttributeNode'; + + } + + /** + * Constructs a new buffer attribute node. + * + * @param {BufferAttribute|InterleavedBuffer|TypedArray} value - The attribute data. + * @param {?string} [bufferType=null] - The buffer type (e.g. `'vec3'`). + * @param {number} [bufferStride=0] - The buffer stride. + * @param {number} [bufferOffset=0] - The buffer offset. + */ + constructor( value, bufferType = null, bufferStride = 0, bufferOffset = 0 ) { + + super( value, bufferType ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isBufferNode = true; + + /** + * The buffer type (e.g. `'vec3'`). + * + * @type {?string} + * @default null + */ + this.bufferType = bufferType; + + /** + * The buffer stride. + * + * @type {number} + * @default 0 + */ + this.bufferStride = bufferStride; + + /** + * The buffer offset. + * + * @type {number} + * @default 0 + */ + this.bufferOffset = bufferOffset; + + /** + * The usage property. Set this to `THREE.DynamicDrawUsage` via `.setUsage()`, + * if you are planning to update the attribute data per frame. + * + * @type {number} + * @default StaticDrawUsage + */ + this.usage = StaticDrawUsage; + + /** + * Whether the attribute is instanced or not. + * + * @type {boolean} + * @default false + */ + this.instanced = false; + + /** + * A reference to the buffer attribute. + * + * @type {?BufferAttribute} + * @default null + */ + this.attribute = null; + + /** + * `BufferAttributeNode` sets this property to `true` by default. + * + * @type {boolean} + * @default true + */ + this.global = true; + + if ( value && value.isBufferAttribute === true ) { + + this.attribute = value; + this.usage = value.usage; + this.instanced = value.isInstancedBufferAttribute; + + } + + } + + /** + * This method is overwritten since the attribute data might be shared + * and thus the hash should be shared as well. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The hash. + */ + getHash( builder ) { + + if ( this.bufferStride === 0 && this.bufferOffset === 0 ) { + + let bufferData = builder.globalCache.getData( this.value ); + + if ( bufferData === undefined ) { + + bufferData = { + node: this + }; + + builder.globalCache.setData( this.value, bufferData ); + + } + + return bufferData.node.uuid; + + } + + return this.uuid; + + } + + /** + * This method is overwritten since the node type is inferred from + * the buffer attribute. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType( builder ) { + + if ( this.bufferType === null ) { + + this.bufferType = builder.getTypeFromAttribute( this.attribute ); + + } + + return this.bufferType; + + } + + /** + * Depending on which value was passed to the node, `setup()` behaves + * differently. If no instance of `BufferAttribute` was passed, the method + * creates an internal attribute and configures it respectively. + * + * @param {NodeBuilder} builder - The current node builder. + */ + setup( builder ) { + + if ( this.attribute !== null ) return; + + const type = this.getNodeType( builder ); + const array = this.value; + const itemSize = builder.getTypeLength( type ); + const stride = this.bufferStride || itemSize; + const offset = this.bufferOffset; + + const buffer = array.isInterleavedBuffer === true ? array : new InterleavedBuffer( array, stride ); + const bufferAttribute = new InterleavedBufferAttribute( buffer, itemSize, offset ); + + buffer.setUsage( this.usage ); + + this.attribute = bufferAttribute; + this.attribute.isInstancedBufferAttribute = this.instanced; // @TODO: Add a possible: InstancedInterleavedBufferAttribute + + } + + /** + * Generates the code snippet of the buffer attribute node. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The generated code snippet. + */ + generate( builder ) { + + const nodeType = this.getNodeType( builder ); + + const nodeAttribute = builder.getBufferAttributeFromNode( this, nodeType ); + const propertyName = builder.getPropertyName( nodeAttribute ); + + let output = null; + + if ( builder.shaderStage === 'vertex' || builder.shaderStage === 'compute' ) { + + this.name = propertyName; + + output = propertyName; + + } else { + + const nodeVarying = varying( this ); + + output = nodeVarying.build( builder, nodeType ); + + } + + return output; + + } + + /** + * Overwrites the default implementation to return a fixed value `'bufferAttribute'`. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The input type. + */ + getInputType( /*builder*/ ) { + + return 'bufferAttribute'; + + } + + /** + * Sets the `usage` property to the given value. + * + * @param {number} value - The usage to set. + * @return {BufferAttributeNode} A reference to this node. + */ + setUsage( value ) { + + this.usage = value; + + if ( this.attribute && this.attribute.isBufferAttribute === true ) { + + this.attribute.usage = value; + + } + + return this; + + } + + /** + * Sets the `instanced` property to the given value. + * + * @param {boolean} value - The value to set. + * @return {BufferAttributeNode} A reference to this node. + */ + setInstanced( value ) { + + this.instanced = value; + + return this; + + } + +} + +/** + * TSL function for creating a buffer attribute node. + * + * @tsl + * @function + * @param {BufferAttribute|InterleavedBuffer|TypedArray} array - The attribute data. + * @param {?string} [type=null] - The buffer type (e.g. `'vec3'`). + * @param {number} [stride=0] - The buffer stride. + * @param {number} [offset=0] - The buffer offset. + * @returns {BufferAttributeNode} + */ +const bufferAttribute = ( array, type = null, stride = 0, offset = 0 ) => nodeObject( new BufferAttributeNode( array, type, stride, offset ) ); + +/** + * TSL function for creating a buffer attribute node but with dynamic draw usage. + * Use this function if attribute data are updated per frame. + * + * @tsl + * @function + * @param {BufferAttribute|InterleavedBuffer|TypedArray} array - The attribute data. + * @param {?string} [type=null] - The buffer type (e.g. `'vec3'`). + * @param {number} [stride=0] - The buffer stride. + * @param {number} [offset=0] - The buffer offset. + * @returns {BufferAttributeNode} + */ +const dynamicBufferAttribute = ( array, type = null, stride = 0, offset = 0 ) => bufferAttribute( array, type, stride, offset ).setUsage( DynamicDrawUsage ); + +/** + * TSL function for creating a buffer attribute node but with enabled instancing + * + * @tsl + * @function + * @param {BufferAttribute|InterleavedBuffer|TypedArray} array - The attribute data. + * @param {?string} [type=null] - The buffer type (e.g. `'vec3'`). + * @param {number} [stride=0] - The buffer stride. + * @param {number} [offset=0] - The buffer offset. + * @returns {BufferAttributeNode} + */ +const instancedBufferAttribute = ( array, type = null, stride = 0, offset = 0 ) => bufferAttribute( array, type, stride, offset ).setInstanced( true ); + +/** + * TSL function for creating a buffer attribute node but with dynamic draw usage and enabled instancing + * + * @tsl + * @function + * @param {BufferAttribute|InterleavedBuffer|TypedArray} array - The attribute data. + * @param {?string} [type=null] - The buffer type (e.g. `'vec3'`). + * @param {number} [stride=0] - The buffer stride. + * @param {number} [offset=0] - The buffer offset. + * @returns {BufferAttributeNode} + */ +const instancedDynamicBufferAttribute = ( array, type = null, stride = 0, offset = 0 ) => dynamicBufferAttribute( array, type, stride, offset ).setInstanced( true ); + +addMethodChaining( 'toAttribute', ( bufferNode ) => bufferAttribute( bufferNode.value ) ); + +/** + * TODO + * + * @augments Node + */ +class ComputeNode extends Node { + + static get type() { + + return 'ComputeNode'; + + } + + /** + * Constructs a new compute node. + * + * @param {Node} computeNode - TODO + * @param {number} count - TODO. + * @param {Array} [workgroupSize=[64]] - TODO. + */ + constructor( computeNode, count, workgroupSize = [ 64 ] ) { + + super( 'void' ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isComputeNode = true; + + /** + * TODO + * + * @type {Node} + */ + this.computeNode = computeNode; + + /** + * TODO + * + * @type {number} + */ + this.count = count; + + /** + * TODO + * + * @type {Array} + * @default [64] + */ + this.workgroupSize = workgroupSize; + + /** + * TODO + * + * @type {number} + */ + this.dispatchCount = 0; + + /** + * TODO + * + * @type {number} + */ + this.version = 1; + + /** + * The name or label of the uniform. + * + * @type {string} + * @default '' + */ + this.name = ''; + + /** + * The `updateBeforeType` is set to `NodeUpdateType.OBJECT` since {@link ComputeNode#updateBefore} + * is executed once per object by default. + * + * @type {string} + * @default 'object' + */ + this.updateBeforeType = NodeUpdateType.OBJECT; + + /** + * TODO + * + * @type {?Function} + */ + this.onInitFunction = null; + + this.updateDispatchCount(); + + } + + /** + * Executes the `dispose` event for this node. + */ + dispose() { + + this.dispatchEvent( { type: 'dispose' } ); + + } + + /** + * Sets the {@link ComputeNode#name} property. + * + * @param {string} name - The name of the uniform. + * @return {ComputeNode} A reference to this node. + */ + label( name ) { + + this.name = name; + + return this; + + } + + /** + * TODO + */ + updateDispatchCount() { + + const { count, workgroupSize } = this; + + let size = workgroupSize[ 0 ]; + + for ( let i = 1; i < workgroupSize.length; i ++ ) + size *= workgroupSize[ i ]; + + this.dispatchCount = Math.ceil( count / size ); + + } + + /** + * TODO + * + * @param {Function} callback - TODO. + * @return {ComputeNode} A reference to this node. + */ + onInit( callback ) { + + this.onInitFunction = callback; + + return this; + + } + + /** + * The method execute the compute for this node. + * + * @param {NodeFrame} frame - A reference to the current node frame. + */ + updateBefore( { renderer } ) { + + renderer.compute( this ); + + } + + setup( builder ) { + + const result = this.computeNode.build( builder ); + + if ( result ) { + + const properties = builder.getNodeProperties( this ); + properties.outputComputeNode = result.outputNode; + + result.outputNode = null; + + } + + return result; + + } + + generate( builder, output ) { + + const { shaderStage } = builder; + + if ( shaderStage === 'compute' ) { + + const snippet = this.computeNode.build( builder, 'void' ); + + if ( snippet !== '' ) { + + builder.addLineFlowCode( snippet, this ); + + } + + } else { + + const properties = builder.getNodeProperties( this ); + const outputComputeNode = properties.outputComputeNode; + + if ( outputComputeNode ) { + + return outputComputeNode.build( builder, output ); + + } + + } + + } + +} + +/** + * TSL function for creating a compute node. + * + * @tsl + * @function + * @param {Node} node - TODO + * @param {number} count - TODO. + * @param {Array} [workgroupSize=[64]] - TODO. + * @returns {AtomicFunctionNode} + */ +const compute = ( node, count, workgroupSize ) => nodeObject( new ComputeNode( nodeObject( node ), count, workgroupSize ) ); + +addMethodChaining( 'compute', compute ); + +/** + * This node can be used as a cache management component for another node. + * Caching is in general used by default in {@link NodeBuilder} but this node + * allows the usage of a shared parent cache during the build process. + * + * @augments Node + */ +class CacheNode extends Node { + + static get type() { + + return 'CacheNode'; + + } + + /** + * Constructs a new cache node. + * + * @param {Node} node - The node that should be cached. + * @param {boolean} [parent=true] - Whether this node refers to a shared parent cache or not. + */ + constructor( node, parent = true ) { + + super(); + + /** + * The node that should be cached. + * + * @type {Node} + */ + this.node = node; + + /** + * Whether this node refers to a shared parent cache or not. + * + * @type {boolean} + * @default true + */ + this.parent = parent; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isCacheNode = true; + + } + + getNodeType( builder ) { + + const previousCache = builder.getCache(); + const cache = builder.getCacheFromNode( this, this.parent ); + + builder.setCache( cache ); + + const nodeType = this.node.getNodeType( builder ); + + builder.setCache( previousCache ); + + return nodeType; + + } + + build( builder, ...params ) { + + const previousCache = builder.getCache(); + const cache = builder.getCacheFromNode( this, this.parent ); + + builder.setCache( cache ); + + const data = this.node.build( builder, ...params ); + + builder.setCache( previousCache ); + + return data; + + } + +} + +/** + * TSL function for creating a cache node. + * + * @tsl + * @function + * @param {Node} node - The node that should be cached. + * @param {boolean} [parent] - Whether this node refers to a shared parent cache or not. + * @returns {CacheNode} + */ +const cache = ( node, parent ) => nodeObject( new CacheNode( nodeObject( node ), parent ) ); + +addMethodChaining( 'cache', cache ); + +/** + * The class generates the code of a given node but returns another node in the output. + * This can be used to call a method or node that does not return a value, i.e. + * type `void` on an input where returning a value is required. Example: + * + * ```js + * material.colorNode = myColor.bypass( runVoidFn() ) + *``` + * + * @augments Node + */ +class BypassNode extends Node { + + static get type() { + + return 'BypassNode'; + + } + + /** + * Constructs a new bypass node. + * + * @param {Node} outputNode - The output node. + * @param {Node} callNode - The call node. + */ + constructor( outputNode, callNode ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isBypassNode = true; + + /** + * The output node. + * + * @type {Node} + */ + this.outputNode = outputNode; + + /** + * The call node. + * + * @type {Node} + */ + this.callNode = callNode; + + } + + getNodeType( builder ) { + + return this.outputNode.getNodeType( builder ); + + } + + generate( builder ) { + + const snippet = this.callNode.build( builder, 'void' ); + + if ( snippet !== '' ) { + + builder.addLineFlowCode( snippet, this ); + + } + + return this.outputNode.build( builder ); + + } + +} + +/** + * TSL function for creating a bypass node. + * + * @tsl + * @function + * @param {Node} outputNode - The output node. + * @param {Node} callNode - The call node. + * @returns {BypassNode} + */ +const bypass = /*@__PURE__*/ nodeProxy( BypassNode ).setParameterLength( 2 ); + +addMethodChaining( 'bypass', bypass ); + +/** + * This node allows to remap a node value from one range into another. E.g a value of + * `0.4` in the range `[ 0.3, 0.5 ]` should be remapped into the normalized range `[ 0, 1 ]`. + * `RemapNode` takes care of that and converts the original value of `0.4` to `0.5`. + * + * @augments Node + */ +class RemapNode extends Node { + + static get type() { + + return 'RemapNode'; + + } + + /** + * Constructs a new remap node. + * + * @param {Node} node - The node that should be remapped. + * @param {Node} inLowNode - The source or current lower bound of the range. + * @param {Node} inHighNode - The source or current upper bound of the range. + * @param {Node} [outLowNode=float(0)] - The target lower bound of the range. + * @param {Node} [outHighNode=float(1)] - The target upper bound of the range. + */ + constructor( node, inLowNode, inHighNode, outLowNode = float( 0 ), outHighNode = float( 1 ) ) { + + super(); + + /** + * The node that should be remapped. + * + * @type {Node} + */ + this.node = node; + + /** + * The source or current lower bound of the range. + * + * @type {Node} + */ + this.inLowNode = inLowNode; + + /** + * The source or current upper bound of the range. + * + * @type {Node} + */ + this.inHighNode = inHighNode; + + /** + * The target lower bound of the range. + * + * @type {Node} + * @default float(0) + */ + this.outLowNode = outLowNode; + + /** + * The target upper bound of the range. + * + * @type {Node} + * @default float(1) + */ + this.outHighNode = outHighNode; + + /** + * Whether the node value should be clamped before + * remapping it to the target range. + * + * @type {boolean} + * @default true + */ + this.doClamp = true; + + } + + setup() { + + const { node, inLowNode, inHighNode, outLowNode, outHighNode, doClamp } = this; + + let t = node.sub( inLowNode ).div( inHighNode.sub( inLowNode ) ); + + if ( doClamp === true ) t = t.clamp(); + + return t.mul( outHighNode.sub( outLowNode ) ).add( outLowNode ); + + } + +} + +/** + * TSL function for creating a remap node. + * + * @tsl + * @function + * @param {Node} node - The node that should be remapped. + * @param {Node} inLowNode - The source or current lower bound of the range. + * @param {Node} inHighNode - The source or current upper bound of the range. + * @param {?Node} [outLowNode=float(0)] - The target lower bound of the range. + * @param {?Node} [outHighNode=float(1)] - The target upper bound of the range. + * @returns {RemapNode} + */ +const remap = /*@__PURE__*/ nodeProxy( RemapNode, null, null, { doClamp: false } ).setParameterLength( 3, 5 ); + +/** + * TSL function for creating a remap node, but with enabled clamping. + * + * @tsl + * @function + * @param {Node} node - The node that should be remapped. + * @param {Node} inLowNode - The source or current lower bound of the range. + * @param {Node} inHighNode - The source or current upper bound of the range. + * @param {?Node} [outLowNode=float(0)] - The target lower bound of the range. + * @param {?Node} [outHighNode=float(1)] - The target upper bound of the range. + * @returns {RemapNode} + */ +const remapClamp = /*@__PURE__*/ nodeProxy( RemapNode ).setParameterLength( 3, 5 ); + +addMethodChaining( 'remap', remap ); +addMethodChaining( 'remapClamp', remapClamp ); + +/** + * This class can be used to implement basic expressions in shader code. + * Basic examples for that are `return`, `continue` or `discard` statements. + * + * @augments Node + */ +class ExpressionNode extends Node { + + static get type() { + + return 'ExpressionNode'; + + } + + /** + * Constructs a new expression node. + * + * @param {string} [snippet=''] - The native code snippet. + * @param {string} [nodeType='void'] - The node type. + */ + constructor( snippet = '', nodeType = 'void' ) { + + super( nodeType ); + + /** + * The native code snippet. + * + * @type {string} + * @default '' + */ + this.snippet = snippet; + + } + + generate( builder, output ) { + + const type = this.getNodeType( builder ); + const snippet = this.snippet; + + if ( type === 'void' ) { + + builder.addLineFlowCode( snippet, this ); + + } else { + + return builder.format( snippet, type, output ); + + } + + } + +} + +/** + * TSL function for creating an expression node. + * + * @tsl + * @function + * @param {string} [snippet] - The native code snippet. + * @param {?string} [nodeType='void'] - The node type. + * @returns {ExpressionNode} + */ +const expression = /*@__PURE__*/ nodeProxy( ExpressionNode ).setParameterLength( 1, 2 ); + +/** + * Represents a `discard` shader operation in TSL. + * + * @tsl + * @function + * @param {?ConditionalNode} conditional - An optional conditional node. It allows to decide whether the discard should be executed or not. + * @return {Node} The `discard` expression. + */ +const Discard = ( conditional ) => ( conditional ? select( conditional, expression( 'discard' ) ) : expression( 'discard' ) ).toStack(); + +/** + * Represents a `return` shader operation in TSL. + * + * @tsl + * @function + * @return {ExpressionNode} The `return` expression. + */ +const Return = () => expression( 'return' ).toStack(); + +addMethodChaining( 'discard', Discard ); + +/** + * Normally, tone mapping and color conversion happens automatically + * before outputting pixel too the default (screen) framebuffer. In certain + * post processing setups this happens to late because certain effects + * require e.g. sRGB input. For such scenarios, `RenderOutputNode` can be used + * to apply tone mapping and color space conversion at an arbitrary point + * in the effect chain. + * + * When applying tone mapping and color space conversion manually with this node, + * you have to set {@link PostProcessing#outputColorTransform} to `false`. + * + * ```js + * const postProcessing = new PostProcessing( renderer ); + * postProcessing.outputColorTransform = false; + * + * const scenePass = pass( scene, camera ); + * const outputPass = renderOutput( scenePass ); + * + * postProcessing.outputNode = outputPass; + * ``` + * + * @augments TempNode + */ +class RenderOutputNode extends TempNode { + + static get type() { + + return 'RenderOutputNode'; + + } + + /** + * Constructs a new render output node. + * + * @param {Node} colorNode - The color node to process. + * @param {?number} toneMapping - The tone mapping type. + * @param {?string} outputColorSpace - The output color space. + */ + constructor( colorNode, toneMapping, outputColorSpace ) { + + super( 'vec4' ); + + /** + * The color node to process. + * + * @type {Node} + */ + this.colorNode = colorNode; + + /** + * The tone mapping type. + * + * @type {?number} + */ + this.toneMapping = toneMapping; + + /** + * The output color space. + * + * @type {?string} + */ + this.outputColorSpace = outputColorSpace; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isRenderOutputNode = true; + + } + + setup( { context } ) { + + let outputNode = this.colorNode || context.color; + + // tone mapping + + const toneMapping = ( this.toneMapping !== null ? this.toneMapping : context.toneMapping ) || NoToneMapping; + const outputColorSpace = ( this.outputColorSpace !== null ? this.outputColorSpace : context.outputColorSpace ) || NoColorSpace; + + if ( toneMapping !== NoToneMapping ) { + + outputNode = outputNode.toneMapping( toneMapping ); + + } + + // working to output color space + + if ( outputColorSpace !== NoColorSpace && outputColorSpace !== ColorManagement.workingColorSpace ) { + + outputNode = outputNode.workingToColorSpace( outputColorSpace ); + + } + + return outputNode; + + } + +} + +/** + * TSL function for creating a posterize node. + * + * @tsl + * @function + * @param {Node} color - The color node to process. + * @param {?number} [toneMapping=null] - The tone mapping type. + * @param {?string} [outputColorSpace=null] - The output color space. + * @returns {RenderOutputNode} + */ +const renderOutput = ( color, toneMapping = null, outputColorSpace = null ) => nodeObject( new RenderOutputNode( nodeObject( color ), toneMapping, outputColorSpace ) ); + +addMethodChaining( 'renderOutput', renderOutput ); + +class DebugNode extends TempNode { + + static get type() { + + return 'DebugNode'; + + } + + constructor( node, callback = null ) { + + super(); + + this.node = node; + this.callback = callback; + + } + + getNodeType( builder ) { + + return this.node.getNodeType( builder ); + + } + + setup( builder ) { + + return this.node.build( builder ); + + } + + analyze( builder ) { + + return this.node.build( builder ); + + } + + generate( builder ) { + + const callback = this.callback; + const snippet = this.node.build( builder ); + + const title = '--- TSL debug - ' + builder.shaderStage + ' shader ---'; + const border = '-'.repeat( title.length ); + + let code = ''; + code += '// #' + title + '#\n'; + code += builder.flow.code.replace( /^\t/mg, '' ) + '\n'; + code += '/* ... */ ' + snippet + ' /* ... */\n'; + code += '// #' + border + '#\n'; + + if ( callback !== null ) { + + callback( builder, code ); + + } else { + + console.log( code ); + + } + + return snippet; + + } + +} + +/** + * TSL function for creating a debug node. + * + * @tsl + * @function + * @param {Node} node - The node to debug. + * @param {?Function} [callback=null] - Optional callback function to handle the debug output. + * @returns {DebugNode} + */ +const debug = ( node, callback = null ) => nodeObject( new DebugNode( nodeObject( node ), callback ) ); + +addMethodChaining( 'debug', debug ); + +// Non-PURE exports list, side-effects are required here. +// TSL Base Syntax + + +function addNodeElement( name/*, nodeElement*/ ) { + + console.warn( 'THREE.TSL: AddNodeElement has been removed in favor of tree-shaking. Trying add', name ); + +} + +/** + * Base class for representing shader attributes as nodes. + * + * @augments Node + */ +class AttributeNode extends Node { + + static get type() { + + return 'AttributeNode'; + + } + + /** + * Constructs a new attribute node. + * + * @param {string} attributeName - The name of the attribute. + * @param {?string} nodeType - The node type. + */ + constructor( attributeName, nodeType = null ) { + + super( nodeType ); + + /** + * `AttributeNode` sets this property to `true` by default. + * + * @type {boolean} + * @default true + */ + this.global = true; + + this._attributeName = attributeName; + + } + + getHash( builder ) { + + return this.getAttributeName( builder ); + + } + + getNodeType( builder ) { + + let nodeType = this.nodeType; + + if ( nodeType === null ) { + + const attributeName = this.getAttributeName( builder ); + + if ( builder.hasGeometryAttribute( attributeName ) ) { + + const attribute = builder.geometry.getAttribute( attributeName ); + + nodeType = builder.getTypeFromAttribute( attribute ); + + } else { + + nodeType = 'float'; + + } + + } + + return nodeType; + + } + + /** + * Sets the attribute name to the given value. The method can be + * overwritten in derived classes if the final name must be computed + * analytically. + * + * @param {string} attributeName - The name of the attribute. + * @return {AttributeNode} A reference to this node. + */ + setAttributeName( attributeName ) { + + this._attributeName = attributeName; + + return this; + + } + + /** + * Returns the attribute name of this node. The method can be + * overwritten in derived classes if the final name must be computed + * analytically. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The attribute name. + */ + getAttributeName( /*builder*/ ) { + + return this._attributeName; + + } + + generate( builder ) { + + const attributeName = this.getAttributeName( builder ); + const nodeType = this.getNodeType( builder ); + const geometryAttribute = builder.hasGeometryAttribute( attributeName ); + + if ( geometryAttribute === true ) { + + const attribute = builder.geometry.getAttribute( attributeName ); + const attributeType = builder.getTypeFromAttribute( attribute ); + + const nodeAttribute = builder.getAttribute( attributeName, attributeType ); + + if ( builder.shaderStage === 'vertex' ) { + + return builder.format( nodeAttribute.name, attributeType, nodeType ); + + } else { + + const nodeVarying = varying( this ); + + return nodeVarying.build( builder, nodeType ); + + } + + } else { + + console.warn( `AttributeNode: Vertex attribute "${ attributeName }" not found on geometry.` ); + + return builder.generateConst( nodeType ); + + } + + } + + serialize( data ) { + + super.serialize( data ); + + data.global = this.global; + data._attributeName = this._attributeName; + + } + + deserialize( data ) { + + super.deserialize( data ); + + this.global = data.global; + this._attributeName = data._attributeName; + + } + +} + +/** + * TSL function for creating an attribute node. + * + * @tsl + * @function + * @param {string} name - The name of the attribute. + * @param {?string} [nodeType=null] - The node type. + * @returns {AttributeNode} + */ +const attribute = ( name, nodeType = null ) => nodeObject( new AttributeNode( name, nodeType ) ); + +/** + * TSL function for creating an uv attribute node with the given index. + * + * @tsl + * @function + * @param {number} [index=0] - The uv index. + * @return {AttributeNode} The uv attribute node. + */ +const uv$1 = ( index = 0 ) => attribute( 'uv' + ( index > 0 ? index : '' ), 'vec2' ); + +/** + * A node that represents the dimensions of a texture. The texture size is + * retrieved in the shader via built-in shader functions like `textureDimensions()` + * or `textureSize()`. + * + * @augments Node + */ +class TextureSizeNode extends Node { + + static get type() { + + return 'TextureSizeNode'; + + } + + /** + * Constructs a new texture size node. + * + * @param {TextureNode} textureNode - A texture node which size should be retrieved. + * @param {?Node} [levelNode=null] - A level node which defines the requested mip. + */ + constructor( textureNode, levelNode = null ) { + + super( 'uvec2' ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isTextureSizeNode = true; + + /** + * A texture node which size should be retrieved. + * + * @type {TextureNode} + */ + this.textureNode = textureNode; + + /** + * A level node which defines the requested mip. + * + * @type {Node} + * @default null + */ + this.levelNode = levelNode; + + } + + generate( builder, output ) { + + const textureProperty = this.textureNode.build( builder, 'property' ); + const level = this.levelNode === null ? '0' : this.levelNode.build( builder, 'int' ); + + return builder.format( `${ builder.getMethod( 'textureDimensions' ) }( ${ textureProperty }, ${ level } )`, this.getNodeType( builder ), output ); + + } + +} + +/** + * TSL function for creating a texture size node. + * + * @tsl + * @function + * @param {TextureNode} textureNode - A texture node which size should be retrieved. + * @param {?Node} [levelNode=null] - A level node which defines the requested mip. + * @returns {TextureSizeNode} + */ +const textureSize = /*@__PURE__*/ nodeProxy( TextureSizeNode ).setParameterLength( 1, 2 ); + +/** + * A special type of uniform node that computes the + * maximum mipmap level for a given texture node. + * + * ```js + * const level = maxMipLevel( textureNode ); + * ``` + * + * @augments UniformNode + */ +class MaxMipLevelNode extends UniformNode { + + static get type() { + + return 'MaxMipLevelNode'; + + } + + /** + * Constructs a new max mip level node. + * + * @param {TextureNode} textureNode - The texture node to compute the max mip level for. + */ + constructor( textureNode ) { + + super( 0 ); + + /** + * The texture node to compute the max mip level for. + * + * @private + * @type {TextureNode} + */ + this._textureNode = textureNode; + + /** + * The `updateType` is set to `NodeUpdateType.FRAME` since the node updates + * the texture once per frame in its {@link MaxMipLevelNode#update} method. + * + * @type {string} + * @default 'frame' + */ + this.updateType = NodeUpdateType.FRAME; + + } + + /** + * The texture node to compute the max mip level for. + * + * @readonly + * @type {TextureNode} + */ + get textureNode() { + + return this._textureNode; + + } + + /** + * The texture. + * + * @readonly + * @type {Texture} + */ + get texture() { + + return this._textureNode.value; + + } + + update() { + + const texture = this.texture; + const images = texture.images; + const image = ( images && images.length > 0 ) ? ( ( images[ 0 ] && images[ 0 ].image ) || images[ 0 ] ) : texture.image; + + if ( image && image.width !== undefined ) { + + const { width, height } = image; + + this.value = Math.log2( Math.max( width, height ) ); + + } + + } + +} + +/** + * TSL function for creating a max mip level node. + * + * @tsl + * @function + * @param {TextureNode} textureNode - The texture node to compute the max mip level for. + * @returns {MaxMipLevelNode} + */ +const maxMipLevel = /*@__PURE__*/ nodeProxy( MaxMipLevelNode ).setParameterLength( 1 ); + +const EmptyTexture$1 = /*@__PURE__*/ new Texture(); + +/** + * This type of uniform node represents a 2D texture. + * + * @augments UniformNode + */ +class TextureNode extends UniformNode { + + static get type() { + + return 'TextureNode'; + + } + + /** + * Constructs a new texture node. + * + * @param {Texture} [value=EmptyTexture] - The texture. + * @param {?Node} [uvNode=null] - The uv node. + * @param {?Node} [levelNode=null] - The level node. + * @param {?Node} [biasNode=null] - The bias node. + */ + constructor( value = EmptyTexture$1, uvNode = null, levelNode = null, biasNode = null ) { + + super( value ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isTextureNode = true; + + /** + * Represents the texture coordinates. + * + * @type {?Node} + * @default null + */ + this.uvNode = uvNode; + + /** + * Represents the mip level that should be selected. + * + * @type {?Node} + * @default null + */ + this.levelNode = levelNode; + + /** + * Represents the bias to be applied during level-of-detail computation. + * + * @type {?Node} + * @default null + */ + this.biasNode = biasNode; + + /** + * Represents a reference value a texture sample is compared to. + * + * @type {?Node} + * @default null + */ + this.compareNode = null; + + /** + * When using texture arrays, the depth node defines the layer to select. + * + * @type {?Node} + * @default null + */ + this.depthNode = null; + + /** + * When defined, a texture is sampled using explicit gradients. + * + * @type {?Array>} + * @default null + */ + this.gradNode = null; + + /** + * Whether texture values should be sampled or fetched. + * + * @type {boolean} + * @default true + */ + this.sampler = true; + + /** + * Whether the uv transformation matrix should be + * automatically updated or not. Use `setUpdateMatrix()` + * if you want to change the value of the property. + * + * @type {boolean} + * @default false + */ + this.updateMatrix = false; + + /** + * By default the `update()` method is not executed. `setUpdateMatrix()` + * sets the value to `frame` when the uv transformation matrix should + * automatically be updated. + * + * @type {string} + * @default 'none' + */ + this.updateType = NodeUpdateType.NONE; + + /** + * The reference node. + * + * @type {?Node} + * @default null + */ + this.referenceNode = null; + + /** + * The texture value is stored in a private property. + * + * @private + * @type {Texture} + */ + this._value = value; + + /** + * The uniform node that represents the uv transformation matrix. + * + * @private + * @type {?UniformNode} + */ + this._matrixUniform = null; + + this.setUpdateMatrix( uvNode === null ); + + } + + set value( value ) { + + if ( this.referenceNode ) { + + this.referenceNode.value = value; + + } else { + + this._value = value; + + } + + } + + /** + * The texture value. + * + * @type {Texture} + */ + get value() { + + return this.referenceNode ? this.referenceNode.value : this._value; + + } + + /** + * Overwritten since the uniform hash is defined by the texture's UUID. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The uniform hash. + */ + getUniformHash( /*builder*/ ) { + + return this.value.uuid; + + } + + /** + * Overwritten since the node type is inferred from the texture type. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType( /*builder*/ ) { + + if ( this.value.isDepthTexture === true ) return 'float'; + + if ( this.value.type === UnsignedIntType ) { + + return 'uvec4'; + + } else if ( this.value.type === IntType ) { + + return 'ivec4'; + + } + + return 'vec4'; + + } + + /** + * Overwrites the default implementation to return a fixed value `'texture'`. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The input type. + */ + getInputType( /*builder*/ ) { + + return 'texture'; + + } + + /** + * Returns a default uvs based on the current texture's channel. + * + * @return {AttributeNode} The default uvs. + */ + getDefaultUV() { + + return uv$1( this.value.channel ); + + } + + /** + * Overwritten to always return the texture reference of the node. + * + * @param {any} state - This method can be invocated in different contexts so `state` can refer to any object type. + * @return {Texture} The texture reference. + */ + updateReference( /*state*/ ) { + + return this.value; + + } + + /** + * Transforms the given uv node with the texture transformation matrix. + * + * @param {Node} uvNode - The uv node to transform. + * @return {Node} The transformed uv node. + */ + getTransformedUV( uvNode ) { + + if ( this._matrixUniform === null ) this._matrixUniform = uniform( this.value.matrix ); + + return this._matrixUniform.mul( vec3( uvNode, 1 ) ).xy; + + } + + /** + * Defines whether the uv transformation matrix should automatically be updated or not. + * + * @param {boolean} value - The update toggle. + * @return {TextureNode} A reference to this node. + */ + setUpdateMatrix( value ) { + + this.updateMatrix = value; + this.updateType = value ? NodeUpdateType.OBJECT : NodeUpdateType.NONE; + + return this; + + } + + /** + * Setups the uv node. Depending on the backend as well as texture's image and type, it might be necessary + * to modify the uv node for correct sampling. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {Node} uvNode - The uv node to setup. + * @return {Node} The updated uv node. + */ + setupUV( builder, uvNode ) { + + const texture = this.value; + + if ( builder.isFlipY() && ( ( texture.image instanceof ImageBitmap && texture.flipY === true ) || texture.isRenderTargetTexture === true || texture.isFramebufferTexture === true || texture.isDepthTexture === true ) ) { + + if ( this.sampler ) { + + uvNode = uvNode.flipY(); + + } else { + + uvNode = uvNode.setY( int( textureSize( this, this.levelNode ).y ).sub( uvNode.y ).sub( 1 ) ); + + } + + } + + return uvNode; + + } + + /** + * Setups texture node by preparing the internal nodes for code generation. + * + * @param {NodeBuilder} builder - The current node builder. + */ + setup( builder ) { + + const properties = builder.getNodeProperties( this ); + properties.referenceNode = this.referenceNode; + + // + + const texture = this.value; + + if ( ! texture || texture.isTexture !== true ) { + + throw new Error( 'THREE.TSL: `texture( value )` function expects a valid instance of THREE.Texture().' ); + + } + + // + + let uvNode = this.uvNode; + + if ( ( uvNode === null || builder.context.forceUVContext === true ) && builder.context.getUV ) { + + uvNode = builder.context.getUV( this, builder ); + + } + + if ( ! uvNode ) uvNode = this.getDefaultUV(); + + if ( this.updateMatrix === true ) { + + uvNode = this.getTransformedUV( uvNode ); + + } + + uvNode = this.setupUV( builder, uvNode ); + + // + + let levelNode = this.levelNode; + + if ( levelNode === null && builder.context.getTextureLevel ) { + + levelNode = builder.context.getTextureLevel( this ); + + } + + // + + properties.uvNode = uvNode; + properties.levelNode = levelNode; + properties.biasNode = this.biasNode; + properties.compareNode = this.compareNode; + properties.gradNode = this.gradNode; + properties.depthNode = this.depthNode; + + } + + /** + * Generates the uv code snippet. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {Node} uvNode - The uv node to generate code for. + * @return {string} The generated code snippet. + */ + generateUV( builder, uvNode ) { + + return uvNode.build( builder, this.sampler === true ? 'vec2' : 'ivec2' ); + + } + + /** + * Generates the snippet for the texture sampling. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {string} textureProperty - The texture property. + * @param {string} uvSnippet - The uv snippet. + * @param {?string} levelSnippet - The level snippet. + * @param {?string} biasSnippet - The bias snippet. + * @param {?string} depthSnippet - The depth snippet. + * @param {?string} compareSnippet - The compare snippet. + * @param {?Array} gradSnippet - The grad snippet. + * @return {string} The generated code snippet. + */ + generateSnippet( builder, textureProperty, uvSnippet, levelSnippet, biasSnippet, depthSnippet, compareSnippet, gradSnippet ) { + + const texture = this.value; + + let snippet; + + if ( levelSnippet ) { + + snippet = builder.generateTextureLevel( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet ); + + } else if ( biasSnippet ) { + + snippet = builder.generateTextureBias( texture, textureProperty, uvSnippet, biasSnippet, depthSnippet ); + + } else if ( gradSnippet ) { + + snippet = builder.generateTextureGrad( texture, textureProperty, uvSnippet, gradSnippet, depthSnippet ); + + } else if ( compareSnippet ) { + + snippet = builder.generateTextureCompare( texture, textureProperty, uvSnippet, compareSnippet, depthSnippet ); + + } else if ( this.sampler === false ) { + + snippet = builder.generateTextureLoad( texture, textureProperty, uvSnippet, depthSnippet ); + + } else { + + snippet = builder.generateTexture( texture, textureProperty, uvSnippet, depthSnippet ); + + } + + return snippet; + + } + + /** + * Generates the code snippet of the texture node. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {string} output - The current output. + * @return {string} The generated code snippet. + */ + generate( builder, output ) { + + const texture = this.value; + + const properties = builder.getNodeProperties( this ); + const textureProperty = super.generate( builder, 'property' ); + + if ( /^sampler/.test( output ) ) { + + return textureProperty + '_sampler'; + + } else if ( builder.isReference( output ) ) { + + return textureProperty; + + } else { + + const nodeData = builder.getDataFromNode( this ); + + let propertyName = nodeData.propertyName; + + if ( propertyName === undefined ) { + + const { uvNode, levelNode, biasNode, compareNode, depthNode, gradNode } = properties; + + const uvSnippet = this.generateUV( builder, uvNode ); + const levelSnippet = levelNode ? levelNode.build( builder, 'float' ) : null; + const biasSnippet = biasNode ? biasNode.build( builder, 'float' ) : null; + const depthSnippet = depthNode ? depthNode.build( builder, 'int' ) : null; + const compareSnippet = compareNode ? compareNode.build( builder, 'float' ) : null; + const gradSnippet = gradNode ? [ gradNode[ 0 ].build( builder, 'vec2' ), gradNode[ 1 ].build( builder, 'vec2' ) ] : null; + + const nodeVar = builder.getVarFromNode( this ); + + propertyName = builder.getPropertyName( nodeVar ); + + const snippet = this.generateSnippet( builder, textureProperty, uvSnippet, levelSnippet, biasSnippet, depthSnippet, compareSnippet, gradSnippet ); + + builder.addLineFlowCode( `${propertyName} = ${snippet}`, this ); + + nodeData.snippet = snippet; + nodeData.propertyName = propertyName; + + } + + let snippet = propertyName; + const nodeType = this.getNodeType( builder ); + + if ( builder.needsToWorkingColorSpace( texture ) ) { + + snippet = colorSpaceToWorking( expression( snippet, nodeType ), texture.colorSpace ).setup( builder ).build( builder, nodeType ); + + } + + return builder.format( snippet, nodeType, output ); + + } + + } + + /** + * Sets the sampler value. + * + * @param {boolean} value - The sampler value to set. + * @return {TextureNode} A reference to this texture node. + */ + setSampler( value ) { + + this.sampler = value; + + return this; + + } + + /** + * Returns the sampler value. + * + * @return {boolean} The sampler value. + */ + getSampler() { + + return this.sampler; + + } + + // @TODO: Move to TSL + + /** + * @function + * @deprecated since r172. Use {@link TextureNode#sample} instead. + * + * @param {Node} uvNode - The uv node. + * @return {TextureNode} A texture node representing the texture sample. + */ + uv( uvNode ) { // @deprecated, r172 + + console.warn( 'THREE.TextureNode: .uv() has been renamed. Use .sample() instead.' ); + + return this.sample( uvNode ); + + } + + /** + * Samples the texture with the given uv node. + * + * @param {Node} uvNode - The uv node. + * @return {TextureNode} A texture node representing the texture sample. + */ + sample( uvNode ) { + + const textureNode = this.clone(); + textureNode.uvNode = nodeObject( uvNode ); + textureNode.referenceNode = this.getSelf(); + + return nodeObject( textureNode ); + + } + + /** + * Samples a blurred version of the texture by defining an internal bias. + * + * @param {Node} amountNode - How blurred the texture should be. + * @return {TextureNode} A texture node representing the texture sample. + */ + blur( amountNode ) { + + const textureNode = this.clone(); + textureNode.biasNode = nodeObject( amountNode ).mul( maxMipLevel( textureNode ) ); + textureNode.referenceNode = this.getSelf(); + + const map = textureNode.value; + + if ( textureNode.generateMipmaps === false && ( map && map.generateMipmaps === false || map.minFilter === NearestFilter || map.magFilter === NearestFilter ) ) { + + console.warn( 'THREE.TSL: texture().blur() requires mipmaps and sampling. Use .generateMipmaps=true and .minFilter/.magFilter=THREE.LinearFilter in the Texture.' ); + + textureNode.biasNode = null; + + } + + return nodeObject( textureNode ); + + } + + /** + * Samples a specific mip of the texture. + * + * @param {Node} levelNode - The mip level to sample. + * @return {TextureNode} A texture node representing the texture sample. + */ + level( levelNode ) { + + const textureNode = this.clone(); + textureNode.levelNode = nodeObject( levelNode ); + textureNode.referenceNode = this.getSelf(); + + return nodeObject( textureNode ); + + } + + /** + * Returns the texture size of the requested level. + * + * @param {Node} levelNode - The level to compute the size for. + * @return {TextureSizeNode} The texture size. + */ + size( levelNode ) { + + return textureSize( this, levelNode ); + + } + + /** + * Samples the texture with the given bias. + * + * @param {Node} biasNode - The bias node. + * @return {TextureNode} A texture node representing the texture sample. + */ + bias( biasNode ) { + + const textureNode = this.clone(); + textureNode.biasNode = nodeObject( biasNode ); + textureNode.referenceNode = this.getSelf(); + + return nodeObject( textureNode ); + + } + + /** + * Samples the texture by executing a compare operation. + * + * @param {Node} compareNode - The node that defines the compare value. + * @return {TextureNode} A texture node representing the texture sample. + */ + compare( compareNode ) { + + const textureNode = this.clone(); + textureNode.compareNode = nodeObject( compareNode ); + textureNode.referenceNode = this.getSelf(); + + return nodeObject( textureNode ); + + } + + /** + * Samples the texture using an explicit gradient. + * + * @param {Node} gradNodeX - The gradX node. + * @param {Node} gradNodeY - The gradY node. + * @return {TextureNode} A texture node representing the texture sample. + */ + grad( gradNodeX, gradNodeY ) { + + const textureNode = this.clone(); + textureNode.gradNode = [ nodeObject( gradNodeX ), nodeObject( gradNodeY ) ]; + textureNode.referenceNode = this.getSelf(); + + return nodeObject( textureNode ); + + } + + /** + * Samples the texture by defining a depth node. + * + * @param {Node} depthNode - The depth node. + * @return {TextureNode} A texture node representing the texture sample. + */ + depth( depthNode ) { + + const textureNode = this.clone(); + textureNode.depthNode = nodeObject( depthNode ); + textureNode.referenceNode = this.getSelf(); + + return nodeObject( textureNode ); + + } + + // -- + + serialize( data ) { + + super.serialize( data ); + + data.value = this.value.toJSON( data.meta ).uuid; + data.sampler = this.sampler; + data.updateMatrix = this.updateMatrix; + data.updateType = this.updateType; + + } + + deserialize( data ) { + + super.deserialize( data ); + + this.value = data.meta.textures[ data.value ]; + this.sampler = data.sampler; + this.updateMatrix = data.updateMatrix; + this.updateType = data.updateType; + + } + + /** + * The update is used to implement the update of the uv transformation matrix. + */ + update() { + + const texture = this.value; + const matrixUniform = this._matrixUniform; + + if ( matrixUniform !== null ) matrixUniform.value = texture.matrix; + + if ( texture.matrixAutoUpdate === true ) { + + texture.updateMatrix(); + + } + + } + + /** + * Clones the texture node. + * + * @return {TextureNode} The cloned texture node. + */ + clone() { + + const newNode = new this.constructor( this.value, this.uvNode, this.levelNode, this.biasNode ); + newNode.sampler = this.sampler; + newNode.depthNode = this.depthNode; + newNode.compareNode = this.compareNode; + newNode.gradNode = this.gradNode; + + return newNode; + + } + +} + +/** + * TSL function for creating a texture node. + * + * @tsl + * @function + * @param {?Texture} value - The texture. + * @param {?Node} [uvNode=null] - The uv node. + * @param {?Node} [levelNode=null] - The level node. + * @param {?Node} [biasNode=null] - The bias node. + * @returns {TextureNode} + */ +const textureBase = /*@__PURE__*/ nodeProxy( TextureNode ).setParameterLength( 1, 4 ).setName( 'texture' ); + +/** + * TSL function for creating a texture node or sample a texture node already existing. + * + * @tsl + * @function + * @param {?Texture|TextureNode} [value=EmptyTexture] - The texture. + * @param {?Node} [uvNode=null] - The uv node. + * @param {?Node} [levelNode=null] - The level node. + * @param {?Node} [biasNode=null] - The bias node. + * @returns {TextureNode} + */ +const texture = ( value = EmptyTexture$1, uvNode = null, levelNode = null, biasNode = null ) => { + + let textureNode; + + if ( value && value.isTextureNode === true ) { + + textureNode = nodeObject( value.clone() ); + textureNode.referenceNode = value.getSelf(); // Ensure the reference is set to the original node + + if ( uvNode !== null ) textureNode.uvNode = nodeObject( uvNode ); + if ( levelNode !== null ) textureNode.levelNode = nodeObject( levelNode ); + if ( biasNode !== null ) textureNode.biasNode = nodeObject( biasNode ); + + } else { + + textureNode = textureBase( value, uvNode, levelNode, biasNode ); + + } + + return textureNode; + +}; + +/** + * TSL function for creating a uniform texture node. + * + * @tsl + * @function + * @param {?Texture} value - The texture. + * @returns {TextureNode} + */ +const uniformTexture = ( value = EmptyTexture$1 ) => texture( value ); + +/** + * TSL function for creating a texture node that fetches/loads texels without interpolation. + * + * @tsl + * @function + * @param {?Texture|TextureNode} [value=EmptyTexture] - The texture. + * @param {?Node} [uvNode=null] - The uv node. + * @param {?Node} [levelNode=null] - The level node. + * @param {?Node} [biasNode=null] - The bias node. + * @returns {TextureNode} + */ +const textureLoad = ( ...params ) => texture( ...params ).setSampler( false ); + +//export const textureLevel = ( value, uv, level ) => texture( value, uv ).level( level ); + +/** + * Converts a texture or texture node to a sampler. + * + * @tsl + * @function + * @param {TextureNode|Texture} value - The texture or texture node to convert. + * @returns {Node} + */ +const sampler = ( value ) => ( value.isNode === true ? value : texture( value ) ).convert( 'sampler' ); + +/** + * Converts a texture or texture node to a sampler comparison. + * + * @tsl + * @function + * @param {TextureNode|Texture} value - The texture or texture node to convert. + * @returns {Node} + */ +const samplerComparison = ( value ) => ( value.isNode === true ? value : texture( value ) ).convert( 'samplerComparison' ); + +/** + * A special type of uniform node which represents array-like data + * as uniform buffers. The access usually happens via `element()` + * which returns an instance of {@link ArrayElementNode}. For example: + * + * ```js + * const bufferNode = buffer( array, 'mat4', count ); + * const matrixNode = bufferNode.element( index ); // access a matrix from the buffer + * ``` + * In general, it is recommended to use the more managed {@link UniformArrayNode} + * since it handles more input types and automatically cares about buffer paddings. + * + * @augments UniformNode + */ +class BufferNode extends UniformNode { + + static get type() { + + return 'BufferNode'; + + } + + /** + * Constructs a new buffer node. + * + * @param {Array} value - Array-like buffer data. + * @param {string} bufferType - The data type of the buffer. + * @param {number} [bufferCount=0] - The count of buffer elements. + */ + constructor( value, bufferType, bufferCount = 0 ) { + + super( value, bufferType ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isBufferNode = true; + + /** + * The data type of the buffer. + * + * @type {string} + */ + this.bufferType = bufferType; + + /** + * The uniform node that holds the value of the reference node. + * + * @type {number} + * @default 0 + */ + this.bufferCount = bufferCount; + + } + + /** + * The data type of the buffer elements. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The element type. + */ + getElementType( builder ) { + + return this.getNodeType( builder ); + + } + + /** + * Overwrites the default implementation to return a fixed value `'buffer'`. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The input type. + */ + getInputType( /*builder*/ ) { + + return 'buffer'; + + } + +} + +/** + * TSL function for creating a buffer node. + * + * @tsl + * @function + * @param {Array} value - Array-like buffer data. + * @param {string} type - The data type of a buffer element. + * @param {number} count - The count of buffer elements. + * @returns {BufferNode} + */ +const buffer = ( value, type, count ) => nodeObject( new BufferNode( value, type, count ) ); + +/** + * Represents the element access on uniform array nodes. + * + * @augments ArrayElementNode + */ +class UniformArrayElementNode extends ArrayElementNode { + + static get type() { + + return 'UniformArrayElementNode'; + + } + + /** + * Constructs a new buffer node. + * + * @param {UniformArrayNode} uniformArrayNode - The uniform array node to access. + * @param {IndexNode} indexNode - The index data that define the position of the accessed element in the array. + */ + constructor( uniformArrayNode, indexNode ) { + + super( uniformArrayNode, indexNode ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isArrayBufferElementNode = true; + + } + + generate( builder ) { + + const snippet = super.generate( builder ); + const type = this.getNodeType(); + const paddedType = this.node.getPaddedType(); + + return builder.format( snippet, paddedType, type ); + + } + +} + +/** + * Similar to {@link BufferNode} this module represents array-like data as + * uniform buffers. Unlike {@link BufferNode}, it can handle more common + * data types in the array (e.g `three.js` primitives) and automatically + * manage buffer padding. It should be the first choice when working with + * uniforms buffers. + * ```js + * const tintColors = uniformArray( [ + * new Color( 1, 0, 0 ), + * new Color( 0, 1, 0 ), + * new Color( 0, 0, 1 ) + * ], 'color' ); + * + * const redColor = tintColors.element( 0 ); + * + * @augments BufferNode + */ +class UniformArrayNode extends BufferNode { + + static get type() { + + return 'UniformArrayNode'; + + } + + /** + * Constructs a new uniform array node. + * + * @param {Array} value - Array holding the buffer data. + * @param {?string} [elementType=null] - The data type of a buffer element. + */ + constructor( value, elementType = null ) { + + super( null ); + + /** + * Array holding the buffer data. Unlike {@link BufferNode}, the array can + * hold number primitives as well as three.js objects like vectors, matrices + * or colors. + * + * @type {Array} + */ + this.array = value; + + /** + * The data type of an array element. + * + * @type {string} + */ + this.elementType = elementType === null ? getValueType( value[ 0 ] ) : elementType; + + /** + * The padded type. Uniform buffers must conform to a certain buffer layout + * so a separate type is computed to ensure correct buffer size. + * + * @type {string} + */ + this.paddedType = this.getPaddedType(); + + /** + * Overwritten since uniform array nodes are updated per render. + * + * @type {string} + * @default 'render' + */ + this.updateType = NodeUpdateType.RENDER; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isArrayBufferNode = true; + + } + + /** + * This method is overwritten since the node type is inferred from the + * {@link UniformArrayNode#paddedType}. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType( /*builder*/ ) { + + return this.paddedType; + + } + + /** + * The data type of the array elements. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The element type. + */ + getElementType() { + + return this.elementType; + + } + + /** + * Returns the padded type based on the element type. + * + * @return {string} The padded type. + */ + getPaddedType() { + + const elementType = this.elementType; + + let paddedType = 'vec4'; + + if ( elementType === 'mat2' ) { + + paddedType = 'mat2'; + + } else if ( /mat/.test( elementType ) === true ) { + + paddedType = 'mat4'; + + } else if ( elementType.charAt( 0 ) === 'i' ) { + + paddedType = 'ivec4'; + + } else if ( elementType.charAt( 0 ) === 'u' ) { + + paddedType = 'uvec4'; + + } + + return paddedType; + + } + + /** + * The update makes sure to correctly transfer the data from the (complex) objects + * in the array to the internal, correctly padded value buffer. + * + * @param {NodeFrame} frame - A reference to the current node frame. + */ + update( /*frame*/ ) { + + const { array, value } = this; + + const elementType = this.elementType; + + if ( elementType === 'float' || elementType === 'int' || elementType === 'uint' ) { + + for ( let i = 0; i < array.length; i ++ ) { + + const index = i * 4; + + value[ index ] = array[ i ]; + + } + + } else if ( elementType === 'color' ) { + + for ( let i = 0; i < array.length; i ++ ) { + + const index = i * 4; + const vector = array[ i ]; + + value[ index ] = vector.r; + value[ index + 1 ] = vector.g; + value[ index + 2 ] = vector.b || 0; + //value[ index + 3 ] = vector.a || 0; + + } + + } else if ( elementType === 'mat2' ) { + + for ( let i = 0; i < array.length; i ++ ) { + + const index = i * 4; + const matrix = array[ i ]; + + value[ index ] = matrix.elements[ 0 ]; + value[ index + 1 ] = matrix.elements[ 1 ]; + value[ index + 2 ] = matrix.elements[ 2 ]; + value[ index + 3 ] = matrix.elements[ 3 ]; + + } + + } else if ( elementType === 'mat3' ) { + + for ( let i = 0; i < array.length; i ++ ) { + + const index = i * 16; + const matrix = array[ i ]; + + value[ index ] = matrix.elements[ 0 ]; + value[ index + 1 ] = matrix.elements[ 1 ]; + value[ index + 2 ] = matrix.elements[ 2 ]; + + value[ index + 4 ] = matrix.elements[ 3 ]; + value[ index + 5 ] = matrix.elements[ 4 ]; + value[ index + 6 ] = matrix.elements[ 5 ]; + + value[ index + 8 ] = matrix.elements[ 6 ]; + value[ index + 9 ] = matrix.elements[ 7 ]; + value[ index + 10 ] = matrix.elements[ 8 ]; + + value[ index + 15 ] = 1; + + } + + } else if ( elementType === 'mat4' ) { + + for ( let i = 0; i < array.length; i ++ ) { + + const index = i * 16; + const matrix = array[ i ]; + + for ( let i = 0; i < matrix.elements.length; i ++ ) { + + value[ index + i ] = matrix.elements[ i ]; + + } + + } + + } else { + + for ( let i = 0; i < array.length; i ++ ) { + + const index = i * 4; + const vector = array[ i ]; + + value[ index ] = vector.x; + value[ index + 1 ] = vector.y; + value[ index + 2 ] = vector.z || 0; + value[ index + 3 ] = vector.w || 0; + + } + + } + + } + + /** + * Implement the value buffer creation based on the array data. + * + * @param {NodeBuilder} builder - A reference to the current node builder. + * @return {null} + */ + setup( builder ) { + + const length = this.array.length; + const elementType = this.elementType; + + let arrayType = Float32Array; + + const paddedType = this.paddedType; + const paddedElementLength = builder.getTypeLength( paddedType ); + + if ( elementType.charAt( 0 ) === 'i' ) arrayType = Int32Array; + if ( elementType.charAt( 0 ) === 'u' ) arrayType = Uint32Array; + + this.value = new arrayType( length * paddedElementLength ); + this.bufferCount = length; + this.bufferType = paddedType; + + return super.setup( builder ); + + } + + /** + * Overwrites the default `element()` method to provide element access + * based on {@link UniformArrayNode}. + * + * @param {IndexNode} indexNode - The index node. + * @return {UniformArrayElementNode} + */ + element( indexNode ) { + + return nodeObject( new UniformArrayElementNode( this, nodeObject( indexNode ) ) ); + + } + +} + +/** + * TSL function for creating an uniform array node. + * + * @tsl + * @function + * @param {Array} values - Array-like data. + * @param {?string} [nodeType] - The data type of the array elements. + * @returns {UniformArrayNode} + */ +const uniformArray = ( values, nodeType ) => nodeObject( new UniformArrayNode( values, nodeType ) ); + +/** + * The node allows to set values for built-in shader variables. That is + * required for features like hardware-accelerated vertex clipping. + * + * @augments Node + */ +class BuiltinNode extends Node { + + /** + * Constructs a new builtin node. + * + * @param {string} name - The name of the built-in shader variable. + */ + constructor( name ) { + + super( 'float' ); + + /** + * The name of the built-in shader variable. + * + * @type {string} + */ + this.name = name; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isBuiltinNode = true; + + } + + /** + * Generates the code snippet of the builtin node. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The generated code snippet. + */ + generate( /* builder */ ) { + + return this.name; + + } + +} + +/** + * TSL function for creating a builtin node. + * + * @tsl + * @function + * @param {string} name - The name of the built-in shader variable. + * @returns {BuiltinNode} + */ +const builtin = nodeProxy( BuiltinNode ).setParameterLength( 1 ); + +/** + * TSL object that represents the current `index` value of the camera if used ArrayCamera. + * + * @tsl + * @type {UniformNode} + */ +const cameraIndex = /*@__PURE__*/ uniform( 0, 'uint' ).label( 'u_cameraIndex' ).setGroup( sharedUniformGroup( 'cameraIndex' ) ).toVarying( 'v_cameraIndex' ); + +/** + * TSL object that represents the `near` value of the camera used for the current render. + * + * @tsl + * @type {UniformNode} + */ +const cameraNear = /*@__PURE__*/ uniform( 'float' ).label( 'cameraNear' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.near ); + +/** + * TSL object that represents the `far` value of the camera used for the current render. + * + * @tsl + * @type {UniformNode} + */ +const cameraFar = /*@__PURE__*/ uniform( 'float' ).label( 'cameraFar' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.far ); + +/** + * TSL object that represents the projection matrix of the camera used for the current render. + * + * @tsl + * @type {UniformNode} + */ +const cameraProjectionMatrix = /*@__PURE__*/ ( Fn( ( { camera } ) => { + + let cameraProjectionMatrix; + + if ( camera.isArrayCamera && camera.cameras.length > 0 ) { + + const matrices = []; + + for ( const subCamera of camera.cameras ) { + + matrices.push( subCamera.projectionMatrix ); + + } + + const cameraProjectionMatrices = uniformArray( matrices ).setGroup( renderGroup ).label( 'cameraProjectionMatrices' ); + + cameraProjectionMatrix = cameraProjectionMatrices.element( camera.isMultiViewCamera ? builtin( 'gl_ViewID_OVR' ) : cameraIndex ).toVar( 'cameraProjectionMatrix' ); + + } else { + + cameraProjectionMatrix = uniform( 'mat4' ).label( 'cameraProjectionMatrix' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.projectionMatrix ); + + } + + return cameraProjectionMatrix; + +} ).once() )(); + +/** + * TSL object that represents the inverse projection matrix of the camera used for the current render. + * + * @tsl + * @type {UniformNode} + */ +const cameraProjectionMatrixInverse = /*@__PURE__*/ ( Fn( ( { camera } ) => { + + let cameraProjectionMatrixInverse; + + if ( camera.isArrayCamera && camera.cameras.length > 0 ) { + + const matrices = []; + + for ( const subCamera of camera.cameras ) { + + matrices.push( subCamera.projectionMatrixInverse ); + + } + + const cameraProjectionMatricesInverse = uniformArray( matrices ).setGroup( renderGroup ).label( 'cameraProjectionMatricesInverse' ); + + cameraProjectionMatrixInverse = cameraProjectionMatricesInverse.element( camera.isMultiViewCamera ? builtin( 'gl_ViewID_OVR' ) : cameraIndex ).toVar( 'cameraProjectionMatrixInverse' ); + + } else { + + cameraProjectionMatrixInverse = uniform( 'mat4' ).label( 'cameraProjectionMatrixInverse' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.projectionMatrixInverse ); + + } + + return cameraProjectionMatrixInverse; + +} ).once() )(); + +/** + * TSL object that represents the view matrix of the camera used for the current render. + * + * @tsl + * @type {UniformNode} + */ +const cameraViewMatrix = /*@__PURE__*/ ( Fn( ( { camera } ) => { + + let cameraViewMatrix; + + if ( camera.isArrayCamera && camera.cameras.length > 0 ) { + + const matrices = []; + + for ( const subCamera of camera.cameras ) { + + matrices.push( subCamera.matrixWorldInverse ); + + } + + const cameraViewMatrices = uniformArray( matrices ).setGroup( renderGroup ).label( 'cameraViewMatrices' ); + + cameraViewMatrix = cameraViewMatrices.element( camera.isMultiViewCamera ? builtin( 'gl_ViewID_OVR' ) : cameraIndex ).toVar( 'cameraViewMatrix' ); + + } else { + + cameraViewMatrix = uniform( 'mat4' ).label( 'cameraViewMatrix' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.matrixWorldInverse ); + + } + + return cameraViewMatrix; + +} ).once() )(); + +/** + * TSL object that represents the world matrix of the camera used for the current render. + * + * @tsl + * @type {UniformNode} + */ +const cameraWorldMatrix = /*@__PURE__*/ uniform( 'mat4' ).label( 'cameraWorldMatrix' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.matrixWorld ); + +/** + * TSL object that represents the normal matrix of the camera used for the current render. + * + * @tsl + * @type {UniformNode} + */ +const cameraNormalMatrix = /*@__PURE__*/ uniform( 'mat3' ).label( 'cameraNormalMatrix' ).setGroup( renderGroup ).onRenderUpdate( ( { camera } ) => camera.normalMatrix ); + +/** + * TSL object that represents the position in world space of the camera used for the current render. + * + * @tsl + * @type {UniformNode} + */ +const cameraPosition = /*@__PURE__*/ uniform( new Vector3() ).label( 'cameraPosition' ).setGroup( renderGroup ).onRenderUpdate( ( { camera }, self ) => self.value.setFromMatrixPosition( camera.matrixWorld ) ); + +const _sphere = /*@__PURE__*/ new Sphere(); + +/** + * This node can be used to access transformation related metrics of 3D objects. + * Depending on the selected scope, a different metric is represented as a uniform + * in the shader. The following scopes are supported: + * + * - `POSITION`: The object's position in world space. + * - `VIEW_POSITION`: The object's position in view/camera space. + * - `DIRECTION`: The object's direction in world space. + * - `SCALE`: The object's scale in world space. + * - `WORLD_MATRIX`: The object's matrix in world space. + * + * @augments Node + */ +class Object3DNode extends Node { + + static get type() { + + return 'Object3DNode'; + + } + + /** + * Constructs a new object 3D node. + * + * @param {('position'|'viewPosition'|'direction'|'scale'|'worldMatrix')} scope - The node represents a different type of transformation depending on the scope. + * @param {?Object3D} [object3d=null] - The 3D object. + */ + constructor( scope, object3d = null ) { + + super(); + + /** + * The node reports a different type of transformation depending on the scope. + * + * @type {('position'|'viewPosition'|'direction'|'scale'|'worldMatrix')} + */ + this.scope = scope; + + /** + * The 3D object. + * + * @type {?Object3D} + * @default null + */ + this.object3d = object3d; + + /** + * Overwritten since this type of node is updated per object. + * + * @type {string} + * @default 'object' + */ + this.updateType = NodeUpdateType.OBJECT; + + /** + * Holds the value of the node as a uniform. + * + * @type {UniformNode} + */ + this.uniformNode = new UniformNode( null ); + + } + + /** + * Overwritten since the node type is inferred from the scope. + * + * @return {string} The node type. + */ + getNodeType() { + + const scope = this.scope; + + if ( scope === Object3DNode.WORLD_MATRIX ) { + + return 'mat4'; + + } else if ( scope === Object3DNode.POSITION || scope === Object3DNode.VIEW_POSITION || scope === Object3DNode.DIRECTION || scope === Object3DNode.SCALE ) { + + return 'vec3'; + + } else if ( scope === Object3DNode.RADIUS ) { + + return 'float'; + + } + + } + + /** + * Updates the uniform value depending on the scope. + * + * @param {NodeFrame} frame - The current node frame. + */ + update( frame ) { + + const object = this.object3d; + const uniformNode = this.uniformNode; + const scope = this.scope; + + if ( scope === Object3DNode.WORLD_MATRIX ) { + + uniformNode.value = object.matrixWorld; + + } else if ( scope === Object3DNode.POSITION ) { + + uniformNode.value = uniformNode.value || new Vector3(); + + uniformNode.value.setFromMatrixPosition( object.matrixWorld ); + + } else if ( scope === Object3DNode.SCALE ) { + + uniformNode.value = uniformNode.value || new Vector3(); + + uniformNode.value.setFromMatrixScale( object.matrixWorld ); + + } else if ( scope === Object3DNode.DIRECTION ) { + + uniformNode.value = uniformNode.value || new Vector3(); + + object.getWorldDirection( uniformNode.value ); + + } else if ( scope === Object3DNode.VIEW_POSITION ) { + + const camera = frame.camera; + + uniformNode.value = uniformNode.value || new Vector3(); + uniformNode.value.setFromMatrixPosition( object.matrixWorld ); + + uniformNode.value.applyMatrix4( camera.matrixWorldInverse ); + + } else if ( scope === Object3DNode.RADIUS ) { + + const geometry = frame.object.geometry; + + if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); + + _sphere.copy( geometry.boundingSphere ).applyMatrix4( object.matrixWorld ); + + uniformNode.value = _sphere.radius; + + } + + } + + /** + * Generates the code snippet of the uniform node. The node type of the uniform + * node also depends on the selected scope. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The generated code snippet. + */ + generate( builder ) { + + const scope = this.scope; + + if ( scope === Object3DNode.WORLD_MATRIX ) { + + this.uniformNode.nodeType = 'mat4'; + + } else if ( scope === Object3DNode.POSITION || scope === Object3DNode.VIEW_POSITION || scope === Object3DNode.DIRECTION || scope === Object3DNode.SCALE ) { + + this.uniformNode.nodeType = 'vec3'; + + } else if ( scope === Object3DNode.RADIUS ) { + + this.uniformNode.nodeType = 'float'; + + } + + return this.uniformNode.build( builder ); + + } + + serialize( data ) { + + super.serialize( data ); + + data.scope = this.scope; + + } + + deserialize( data ) { + + super.deserialize( data ); + + this.scope = data.scope; + + } + +} + +Object3DNode.WORLD_MATRIX = 'worldMatrix'; +Object3DNode.POSITION = 'position'; +Object3DNode.SCALE = 'scale'; +Object3DNode.VIEW_POSITION = 'viewPosition'; +Object3DNode.DIRECTION = 'direction'; +Object3DNode.RADIUS = 'radius'; + +/** + * TSL function for creating an object 3D node that represents the object's direction in world space. + * + * @tsl + * @function + * @param {?Object3D} [object3d] - The 3D object. + * @returns {Object3DNode} + */ +const objectDirection = /*@__PURE__*/ nodeProxy( Object3DNode, Object3DNode.DIRECTION ).setParameterLength( 1 ); + +/** + * TSL function for creating an object 3D node that represents the object's world matrix. + * + * @tsl + * @function + * @param {?Object3D} [object3d] - The 3D object. + * @returns {Object3DNode} + */ +const objectWorldMatrix = /*@__PURE__*/ nodeProxy( Object3DNode, Object3DNode.WORLD_MATRIX ).setParameterLength( 1 ); + +/** + * TSL function for creating an object 3D node that represents the object's position in world space. + * + * @tsl + * @function + * @param {?Object3D} [object3d] - The 3D object. + * @returns {Object3DNode} + */ +const objectPosition = /*@__PURE__*/ nodeProxy( Object3DNode, Object3DNode.POSITION ).setParameterLength( 1 ); + +/** + * TSL function for creating an object 3D node that represents the object's scale in world space. + * + * @tsl + * @function + * @param {?Object3D} [object3d] - The 3D object. + * @returns {Object3DNode} + */ +const objectScale = /*@__PURE__*/ nodeProxy( Object3DNode, Object3DNode.SCALE ).setParameterLength( 1 ); + +/** + * TSL function for creating an object 3D node that represents the object's position in view/camera space. + * + * @tsl + * @function + * @param {?Object3D} [object3d] - The 3D object. + * @returns {Object3DNode} + */ +const objectViewPosition = /*@__PURE__*/ nodeProxy( Object3DNode, Object3DNode.VIEW_POSITION ).setParameterLength( 1 ); + +/** + * TSL function for creating an object 3D node that represents the object's radius. + * + * @tsl + * @function + * @param {?Object3D} [object3d] - The 3D object. + * @returns {Object3DNode} + */ +const objectRadius = /*@__PURE__*/ nodeProxy( Object3DNode, Object3DNode.RADIUS ).setParameterLength( 1 ); + +/** + * This type of node is a specialized version of `Object3DNode` + * with larger set of model related metrics. Unlike `Object3DNode`, + * `ModelNode` extracts the reference to the 3D object from the + * current node frame state. + * + * @augments Object3DNode + */ +class ModelNode extends Object3DNode { + + static get type() { + + return 'ModelNode'; + + } + + /** + * Constructs a new object model node. + * + * @param {('position'|'viewPosition'|'direction'|'scale'|'worldMatrix')} scope - The node represents a different type of transformation depending on the scope. + */ + constructor( scope ) { + + super( scope ); + + } + + /** + * Extracts the model reference from the frame state and then + * updates the uniform value depending on the scope. + * + * @param {NodeFrame} frame - The current node frame. + */ + update( frame ) { + + this.object3d = frame.object; + + super.update( frame ); + + } + +} + +/** + * TSL object that represents the object's direction in world space. + * + * @tsl + * @type {ModelNode} + */ +const modelDirection = /*@__PURE__*/ nodeImmutable( ModelNode, ModelNode.DIRECTION ); + +/** + * TSL object that represents the object's world matrix. + * + * @tsl + * @type {ModelNode} + */ +const modelWorldMatrix = /*@__PURE__*/ nodeImmutable( ModelNode, ModelNode.WORLD_MATRIX ); + +/** + * TSL object that represents the object's position in world space. + * + * @tsl + * @type {ModelNode} + */ +const modelPosition = /*@__PURE__*/ nodeImmutable( ModelNode, ModelNode.POSITION ); + +/** + * TSL object that represents the object's scale in world space. + * + * @tsl + * @type {ModelNode} + */ +const modelScale = /*@__PURE__*/ nodeImmutable( ModelNode, ModelNode.SCALE ); + +/** + * TSL object that represents the object's position in view/camera space. + * + * @tsl + * @type {ModelNode} + */ +const modelViewPosition = /*@__PURE__*/ nodeImmutable( ModelNode, ModelNode.VIEW_POSITION ); + +/** + * TSL object that represents the object's radius. + * + * @tsl + * @type {ModelNode} + */ +const modelRadius = /*@__PURE__*/ nodeImmutable( ModelNode, ModelNode.RADIUS ); + +/** + * TSL object that represents the object's normal matrix. + * + * @tsl + * @type {UniformNode} + */ +const modelNormalMatrix = /*@__PURE__*/ uniform( new Matrix3() ).onObjectUpdate( ( { object }, self ) => self.value.getNormalMatrix( object.matrixWorld ) ); + +/** + * TSL object that represents the object's inverse world matrix. + * + * @tsl + * @type {UniformNode} + */ +const modelWorldMatrixInverse = /*@__PURE__*/ uniform( new Matrix4() ).onObjectUpdate( ( { object }, self ) => self.value.copy( object.matrixWorld ).invert() ); + +/** + * TSL object that represents the object's model view matrix. + * + * @tsl + * @type {Node} + */ +const modelViewMatrix = /*@__PURE__*/ ( Fn( ( builder ) => { + + return builder.renderer.overrideNodes.modelViewMatrix || mediumpModelViewMatrix; + +} ).once() )().toVar( 'modelViewMatrix' ); + +// GPU Precision + +/** + * TSL object that represents the object's model view in `mediump` precision. + * + * @tsl + * @type {Node} + */ +const mediumpModelViewMatrix = /*@__PURE__*/ cameraViewMatrix.mul( modelWorldMatrix ); + +// CPU Precision + +/** + * TSL object that represents the object's model view in `highp` precision + * which is achieved by computing the matrix in JS and not in the shader. + * + * @tsl + * @type {Node} + */ +const highpModelViewMatrix = /*@__PURE__*/ ( Fn( ( builder ) => { + + builder.context.isHighPrecisionModelViewMatrix = true; + + return uniform( 'mat4' ).onObjectUpdate( ( { object, camera } ) => { + + return object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld ); + + } ); + +} ).once() )().toVar( 'highpModelViewMatrix' ); + +/** + * TSL object that represents the object's model normal view in `highp` precision + * which is achieved by computing the matrix in JS and not in the shader. + * + * @tsl + * @type {Node} + */ +const highpModelNormalViewMatrix = /*@__PURE__*/ ( Fn( ( builder ) => { + + const isHighPrecisionModelViewMatrix = builder.context.isHighPrecisionModelViewMatrix; + + return uniform( 'mat3' ).onObjectUpdate( ( { object, camera } ) => { + + if ( isHighPrecisionModelViewMatrix !== true ) { + + object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld ); + + } + + return object.normalMatrix.getNormalMatrix( object.modelViewMatrix ); + + } ); + +} ).once() )().toVar( 'highpModelNormalViewMatrix' ); + +/** + * TSL object that represents the position attribute of the current rendered object. + * + * @tsl + * @type {AttributeNode} + */ +const positionGeometry = /*@__PURE__*/ attribute( 'position', 'vec3' ); + +/** + * TSL object that represents the vertex position in local space of the current rendered object. + * + * @tsl + * @type {AttributeNode} + */ +const positionLocal = /*@__PURE__*/ positionGeometry.toVarying( 'positionLocal' ); + +/** + * TSL object that represents the previous vertex position in local space of the current rendered object. + * Used in context of {@link VelocityNode} for rendering motion vectors. + * + * @tsl + * @type {AttributeNode} + */ +const positionPrevious = /*@__PURE__*/ positionGeometry.toVarying( 'positionPrevious' ); + +/** + * TSL object that represents the vertex position in world space of the current rendered object. + * + * @tsl + * @type {VaryingNode} + */ +const positionWorld = /*@__PURE__*/ ( Fn( ( builder ) => { + + return modelWorldMatrix.mul( positionLocal ).xyz.toVarying( builder.getSubBuildProperty( 'v_positionWorld' ) ); + +}, 'vec3' ).once( [ 'POSITION' ] ) )(); + +/** + * TSL object that represents the position world direction of the current rendered object. + * + * @tsl + * @type {Node} + */ +const positionWorldDirection = /*@__PURE__*/ ( Fn( () => { + + const vertexPWD = positionLocal.transformDirection( modelWorldMatrix ).toVarying( 'v_positionWorldDirection' ); + + return vertexPWD.normalize().toVar( 'positionWorldDirection' ); + +}, 'vec3' ).once( [ 'POSITION' ] ) )(); + +/** + * TSL object that represents the vertex position in view space of the current rendered object. + * + * @tsl + * @type {VaryingNode} + */ +const positionView = /*@__PURE__*/ ( Fn( ( builder ) => { + + return builder.context.setupPositionView().toVarying( 'v_positionView' ); + +}, 'vec3' ).once( [ 'POSITION' ] ) )(); + +/** + * TSL object that represents the position view direction of the current rendered object. + * + * @tsl + * @type {VaryingNode} + */ +const positionViewDirection = /*@__PURE__*/ positionView.negate().toVarying( 'v_positionViewDirection' ).normalize().toVar( 'positionViewDirection' ); + +/** + * This node can be used to evaluate whether a primitive is front or back facing. + * + * @augments Node + */ +class FrontFacingNode extends Node { + + static get type() { + + return 'FrontFacingNode'; + + } + + /** + * Constructs a new front facing node. + */ + constructor() { + + super( 'bool' ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isFrontFacingNode = true; + + } + + generate( builder ) { + + if ( builder.shaderStage !== 'fragment' ) return 'true'; + + // + + const { renderer, material } = builder; + + if ( renderer.coordinateSystem === WebGLCoordinateSystem ) { + + if ( material.side === BackSide ) { + + return 'false'; + + } + + } + + return builder.getFrontFacing(); + + } + +} + +/** + * TSL object that represents whether a primitive is front or back facing + * + * @tsl + * @type {FrontFacingNode} + */ +const frontFacing = /*@__PURE__*/ nodeImmutable( FrontFacingNode ); + +/** + * TSL object that represents the front facing status as a number instead of a bool. + * `1` means front facing, `-1` means back facing. + * + * @tsl + * @type {Node} + */ +const faceDirection = /*@__PURE__*/ float( frontFacing ).mul( 2.0 ).sub( 1.0 ); + +/** + * Converts a direction vector to a face direction vector based on the material's side. + * + * If the material is set to `BackSide`, the direction is inverted. + * If the material is set to `DoubleSide`, the direction is multiplied by `faceDirection`. + * + * @tsl + * @param {Node} direction - The direction vector to convert. + * @returns {Node} The converted direction vector. + */ +const directionToFaceDirection = /*@__PURE__*/ Fn( ( [ direction ], { material } ) => { + + const side = material.side; + + if ( side === BackSide ) { + + direction = direction.mul( -1 ); + + } else if ( side === DoubleSide ) { + + direction = direction.mul( faceDirection ); + + } + + return direction; + +} ); + +/** + * TSL object that represents the normal attribute of the current rendered object. + * + * @tsl + * @type {Node} + */ +const normalGeometry = /*@__PURE__*/ attribute( 'normal', 'vec3' ); + +/** + * TSL object that represents the vertex normal in local space of the current rendered object. + * + * @tsl + * @type {Node} + */ +const normalLocal = /*@__PURE__*/ ( Fn( ( builder ) => { + + if ( builder.geometry.hasAttribute( 'normal' ) === false ) { + + console.warn( 'THREE.TSL: Vertex attribute "normal" not found on geometry.' ); + + return vec3( 0, 1, 0 ); + + } + + return normalGeometry; + +}, 'vec3' ).once() )().toVar( 'normalLocal' ); + +/** + * TSL object that represents the flat vertex normal in view space of the current rendered object. + * + * @tsl + * @type {Node} + */ +const normalFlat = /*@__PURE__*/ positionView.dFdx().cross( positionView.dFdy() ).normalize().toVar( 'normalFlat' ); + +/** + * TSL object that represents the vertex normal in view space of the current rendered object. + * + * @tsl + * @type {Node} + */ +const normalViewGeometry = /*@__PURE__*/ ( Fn( ( builder ) => { + + let node; + + if ( builder.material.flatShading === true ) { + + node = normalFlat; + + } else { + + node = transformNormalToView( normalLocal ).toVarying( 'v_normalViewGeometry' ).normalize(); + + } + + return node; + +}, 'vec3' ).once() )().toVar( 'normalViewGeometry' ); + +/** + * TSL object that represents the vertex normal in world space of the current rendered object. + * + * @tsl + * @type {Node} + */ +const normalWorldGeometry = /*@__PURE__*/ ( Fn( ( builder ) => { + + let normal = normalViewGeometry.transformDirection( cameraViewMatrix ); + + if ( builder.material.flatShading !== true ) { + + normal = normal.toVarying( 'v_normalWorldGeometry' ); + + } + + return normal.normalize().toVar( 'normalWorldGeometry' ); + +}, 'vec3' ).once() )(); + +/** + * TSL object that represents the transformed vertex normal in view space of the current rendered object. + * + * @tsl + * @type {Node} + */ +const normalView = /*@__PURE__*/ ( Fn( ( { subBuildFn, material, context } ) => { + + let node; + + if ( subBuildFn === 'NORMAL' || subBuildFn === 'VERTEX' ) { + + node = normalViewGeometry; + + if ( material.flatShading !== true ) { + + node = directionToFaceDirection( node ); + + } + + } else { + + // Use getUV context to avoid side effects from nodes overwriting getUV in the context (e.g. EnvironmentNode) + + node = context.setupNormal().context( { getUV: null } ); + + } + + return node; + +}, 'vec3' ).once( [ 'NORMAL', 'VERTEX' ] ) )().toVar( 'normalView' ); + +/** + * TSL object that represents the transformed vertex normal in world space of the current rendered object. + * + * @tsl + * @type {Node} + */ +const normalWorld = /*@__PURE__*/ normalView.transformDirection( cameraViewMatrix ).toVar( 'normalWorld' ); + +/** + * TSL object that represents the transformed clearcoat vertex normal in view space of the current rendered object. + * + * @tsl + * @type {Node} + */ +const clearcoatNormalView = /*@__PURE__*/ ( Fn( ( { subBuildFn, context } ) => { + + let node; + + if ( subBuildFn === 'NORMAL' || subBuildFn === 'VERTEX' ) { + + node = normalView; + + } else { + + // Use getUV context to avoid side effects from nodes overwriting getUV in the context (e.g. EnvironmentNode) + + node = context.setupClearcoatNormal().context( { getUV: null } ); + + } + + return node; + +}, 'vec3' ).once( [ 'NORMAL', 'VERTEX' ] ) )().toVar( 'clearcoatNormalView' ); + +/** + * Transforms the normal with the given matrix. + * + * @tsl + * @function + * @param {Node} normal - The normal. + * @param {Node} [matrix=modelWorldMatrix] - The matrix. + * @return {Node} The transformed normal. + */ +const transformNormal = /*@__PURE__*/ Fn( ( [ normal, matrix = modelWorldMatrix ] ) => { + + const m = mat3( matrix ); + + const transformedNormal = normal.div( vec3( m[ 0 ].dot( m[ 0 ] ), m[ 1 ].dot( m[ 1 ] ), m[ 2 ].dot( m[ 2 ] ) ) ); + + return m.mul( transformedNormal ).xyz; + +} ); + +/** + * Transforms the given normal from local to view space. + * + * @tsl + * @function + * @param {Node} normal - The normal. + * @param {NodeBuilder} builder - The current node builder. + * @return {Node} The transformed normal. + */ +const transformNormalToView = /*@__PURE__*/ Fn( ( [ normal ], builder ) => { + + const modelNormalViewMatrix = builder.renderer.overrideNodes.modelNormalViewMatrix; + + if ( modelNormalViewMatrix !== null ) { + + return modelNormalViewMatrix.transformDirection( normal ); + + } + + // + + const transformedNormal = modelNormalMatrix.mul( normal ); + + return cameraViewMatrix.transformDirection( transformedNormal ); + +} ); + +// Deprecated + +/** + * TSL object that represents the transformed vertex normal in view space of the current rendered object. + * + * @tsl + * @type {Node} + * @deprecated since r178. Use `normalView` instead. + */ +const transformedNormalView = ( Fn( () => { // @deprecated, r177 + + console.warn( 'THREE.TSL: "transformedNormalView" is deprecated. Use "normalView" instead.' ); + return normalView; + +} ).once( [ 'NORMAL', 'VERTEX' ] ) )(); + +/** + * TSL object that represents the transformed vertex normal in world space of the current rendered object. + * + * @tsl + * @type {Node} + * @deprecated since r178. Use `normalWorld` instead. + */ +const transformedNormalWorld = ( Fn( () => { // @deprecated, r177 + + console.warn( 'THREE.TSL: "transformedNormalWorld" is deprecated. Use "normalWorld" instead.' ); + return normalWorld; + +} ).once( [ 'NORMAL', 'VERTEX' ] ) )(); + +/** + * TSL object that represents the transformed clearcoat vertex normal in view space of the current rendered object. + * + * @tsl + * @type {Node} + * @deprecated since r178. Use `clearcoatNormalView` instead. + */ +const transformedClearcoatNormalView = ( Fn( () => { // @deprecated, r177 + + console.warn( 'THREE.TSL: "transformedClearcoatNormalView" is deprecated. Use "clearcoatNormalView" instead.' ); + return clearcoatNormalView; + +} ).once( [ 'NORMAL', 'VERTEX' ] ) )(); + +const _e1$1 = /*@__PURE__*/ new Euler(); +const _m1$1 = /*@__PURE__*/ new Matrix4(); + +/** + * TSL object that represents the refraction ratio of the material used for rendering the current object. + * + * @tsl + * @type {UniformNode} + */ +const materialRefractionRatio = /*@__PURE__*/ uniform( 0 ).onReference( ( { material } ) => material ).onObjectUpdate( ( { material } ) => material.refractionRatio ); + +/** + * TSL object that represents the intensity of environment maps of PBR materials. + * When `material.envMap` is set, the value is `material.envMapIntensity` otherwise `scene.environmentIntensity`. + * + * @tsl + * @type {Node} + */ +const materialEnvIntensity = /*@__PURE__*/ uniform( 1 ).onReference( ( { material } ) => material ).onObjectUpdate( function ( { material, scene } ) { + + return material.envMap ? material.envMapIntensity : scene.environmentIntensity; + +} ); + +/** + * TSL object that represents the rotation of environment maps. + * When `material.envMap` is set, the value is `material.envMapRotation`. `scene.environmentRotation` controls the + * rotation of `scene.environment` instead. + * + * @tsl + * @type {Node} + */ +const materialEnvRotation = /*@__PURE__*/ uniform( new Matrix4() ).onReference( function ( frame ) { + + return frame.material; + +} ).onObjectUpdate( function ( { material, scene } ) { + + const rotation = ( scene.environment !== null && material.envMap === null ) ? scene.environmentRotation : material.envMapRotation; + + if ( rotation ) { + + _e1$1.copy( rotation ); + + _m1$1.makeRotationFromEuler( _e1$1 ); + + } else { + + _m1$1.identity(); + + } + + return _m1$1; + +} ); + +/** + * The reflect vector in view space. + * + * @tsl + * @type {Node} + */ +const reflectView = /*@__PURE__*/ positionViewDirection.negate().reflect( normalView ); + +/** + * The refract vector in view space. + * + * @tsl + * @type {Node} + */ +const refractView = /*@__PURE__*/ positionViewDirection.negate().refract( normalView, materialRefractionRatio ); + +/** + * Used for sampling cube maps when using cube reflection mapping. + * + * @tsl + * @type {Node} + */ +const reflectVector = /*@__PURE__*/ reflectView.transformDirection( cameraViewMatrix ).toVar( 'reflectVector' ); + +/** + * Used for sampling cube maps when using cube refraction mapping. + * + * @tsl + * @type {Node} + */ +const refractVector = /*@__PURE__*/ refractView.transformDirection( cameraViewMatrix ).toVar( 'reflectVector' ); + +const EmptyTexture = /*@__PURE__*/ new CubeTexture(); + +/** + * This type of uniform node represents a cube texture. + * + * @augments TextureNode + */ +class CubeTextureNode extends TextureNode { + + static get type() { + + return 'CubeTextureNode'; + + } + + /** + * Constructs a new cube texture node. + * + * @param {CubeTexture} value - The cube texture. + * @param {?Node} [uvNode=null] - The uv node. + * @param {?Node} [levelNode=null] - The level node. + * @param {?Node} [biasNode=null] - The bias node. + */ + constructor( value, uvNode = null, levelNode = null, biasNode = null ) { + + super( value, uvNode, levelNode, biasNode ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isCubeTextureNode = true; + + } + + /** + * Overwrites the default implementation to return a fixed value `'cubeTexture'`. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The input type. + */ + getInputType( /*builder*/ ) { + + return 'cubeTexture'; + + } + + /** + * Returns a default uvs based on the mapping type of the cube texture. + * + * @return {Node} The default uv attribute. + */ + getDefaultUV() { + + const texture = this.value; + + if ( texture.mapping === CubeReflectionMapping ) { + + return reflectVector; + + } else if ( texture.mapping === CubeRefractionMapping ) { + + return refractVector; + + } else { + + console.error( 'THREE.CubeTextureNode: Mapping "%s" not supported.', texture.mapping ); + + return vec3( 0, 0, 0 ); + + } + + } + + /** + * Overwritten with an empty implementation since the `updateMatrix` flag is ignored + * for cube textures. The uv transformation matrix is not applied to cube textures. + * + * @param {boolean} value - The update toggle. + */ + setUpdateMatrix( /*updateMatrix*/ ) { } // Ignore .updateMatrix for CubeTextureNode + + /** + * Setups the uv node. Depending on the backend as well as the texture type, it might be necessary + * to modify the uv node for correct sampling. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {Node} uvNode - The uv node to setup. + * @return {Node} The updated uv node. + */ + setupUV( builder, uvNode ) { + + const texture = this.value; + + if ( builder.renderer.coordinateSystem === WebGPUCoordinateSystem || ! texture.isRenderTargetTexture ) { + + uvNode = vec3( uvNode.x.negate(), uvNode.yz ); + + } + + return materialEnvRotation.mul( uvNode ); + + } + + /** + * Generates the uv code snippet. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {Node} cubeUV - The uv node to generate code for. + * @return {string} The generated code snippet. + */ + generateUV( builder, cubeUV ) { + + return cubeUV.build( builder, 'vec3' ); + + } + +} + +/** + * TSL function for creating a cube texture node. + * + * @tsl + * @function + * @param {CubeTexture} value - The cube texture. + * @param {?Node} [uvNode=null] - The uv node. + * @param {?Node} [levelNode=null] - The level node. + * @param {?Node} [biasNode=null] - The bias node. + * @returns {CubeTextureNode} + */ +const cubeTextureBase = /*@__PURE__*/ nodeProxy( CubeTextureNode ).setParameterLength( 1, 4 ).setName( 'cubeTexture' ); + +/** + * TSL function for creating a cube texture uniform node. + * + * @tsl + * @function + * @param {?CubeTexture|CubeTextureNode} [value=EmptyTexture] - The cube texture. + * @param {?Node} [uvNode=null] - The uv node. + * @param {?Node} [levelNode=null] - The level node. + * @param {?Node} [biasNode=null] - The bias node. + * @returns {CubeTextureNode} + */ +const cubeTexture = ( value = EmptyTexture, uvNode = null, levelNode = null, biasNode = null ) => { + + let textureNode; + + if ( value && value.isCubeTextureNode === true ) { + + textureNode = nodeObject( value.clone() ); + textureNode.referenceNode = value.getSelf(); // Ensure the reference is set to the original node + + if ( uvNode !== null ) textureNode.uvNode = nodeObject( uvNode ); + if ( levelNode !== null ) textureNode.levelNode = nodeObject( levelNode ); + if ( biasNode !== null ) textureNode.biasNode = nodeObject( biasNode ); + + } else { + + textureNode = cubeTextureBase( value, uvNode, levelNode, biasNode ); + + } + + return textureNode; + +}; + +/** + * TSL function for creating a uniform cube texture node. + * + * @tsl + * @function + * @param {?CubeTexture} [value=EmptyTexture] - The cube texture. + * @returns {CubeTextureNode} + */ +const uniformCubeTexture = ( value = EmptyTexture ) => cubeTextureBase( value ); + +// TODO: Avoid duplicated code and ues only ReferenceBaseNode or ReferenceNode + +/** + * This class is only relevant if the referenced property is array-like. + * In this case, `ReferenceElementNode` allows to refer to a specific + * element inside the data structure via an index. + * + * @augments ArrayElementNode + */ +class ReferenceElementNode extends ArrayElementNode { + + static get type() { + + return 'ReferenceElementNode'; + + } + + /** + * Constructs a new reference element node. + * + * @param {?ReferenceNode} referenceNode - The reference node. + * @param {Node} indexNode - The index node that defines the element access. + */ + constructor( referenceNode, indexNode ) { + + super( referenceNode, indexNode ); + + /** + * Similar to {@link ReferenceNode#reference}, an additional + * property references to the current node. + * + * @type {?ReferenceNode} + * @default null + */ + this.referenceNode = referenceNode; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isReferenceElementNode = true; + + } + + /** + * This method is overwritten since the node type is inferred from + * the uniform type of the reference node. + * + * @return {string} The node type. + */ + getNodeType() { + + return this.referenceNode.uniformType; + + } + + generate( builder ) { + + const snippet = super.generate( builder ); + const arrayType = this.referenceNode.getNodeType(); + const elementType = this.getNodeType(); + + return builder.format( snippet, arrayType, elementType ); + + } + +} + +/** + * This type of node establishes a reference to a property of another object. + * In this way, the value of the node is automatically linked to the value of + * referenced object. Reference nodes internally represent the linked value + * as a uniform. + * + * @augments Node + */ +class ReferenceNode extends Node { + + static get type() { + + return 'ReferenceNode'; + + } + + /** + * Constructs a new reference node. + * + * @param {string} property - The name of the property the node refers to. + * @param {string} uniformType - The uniform type that should be used to represent the property value. + * @param {?Object} [object=null] - The object the property belongs to. + * @param {?number} [count=null] - When the linked property is an array-like, this parameter defines its length. + */ + constructor( property, uniformType, object = null, count = null ) { + + super(); + + /** + * The name of the property the node refers to. + * + * @type {string} + */ + this.property = property; + + /** + * The uniform type that should be used to represent the property value. + * + * @type {string} + */ + this.uniformType = uniformType; + + /** + * The object the property belongs to. + * + * @type {?Object} + * @default null + */ + this.object = object; + + /** + * When the linked property is an array, this parameter defines its length. + * + * @type {?number} + * @default null + */ + this.count = count; + + /** + * The property name might have dots so nested properties can be referred. + * The hierarchy of the names is stored inside this array. + * + * @type {Array} + */ + this.properties = property.split( '.' ); + + /** + * Points to the current referred object. This property exists next to {@link ReferenceNode#object} + * since the final reference might be updated from calling code. + * + * @type {?Object} + * @default null + */ + this.reference = object; + + /** + * The uniform node that holds the value of the reference node. + * + * @type {UniformNode} + * @default null + */ + this.node = null; + + /** + * The uniform group of the internal uniform. + * + * @type {UniformGroupNode} + * @default null + */ + this.group = null; + + /** + * An optional label of the internal uniform node. + * + * @type {?string} + * @default null + */ + this.name = null; + + /** + * Overwritten since reference nodes are updated per object. + * + * @type {string} + * @default 'object' + */ + this.updateType = NodeUpdateType.OBJECT; + + } + + /** + * When the referred property is array-like, this method can be used + * to access elements via an index node. + * + * @param {IndexNode} indexNode - indexNode. + * @return {ReferenceElementNode} A reference to an element. + */ + element( indexNode ) { + + return nodeObject( new ReferenceElementNode( this, nodeObject( indexNode ) ) ); + + } + + /** + * Sets the uniform group for this reference node. + * + * @param {UniformGroupNode} group - The uniform group to set. + * @return {ReferenceNode} A reference to this node. + */ + setGroup( group ) { + + this.group = group; + + return this; + + } + + /** + * Sets the label for the internal uniform. + * + * @param {string} name - The label to set. + * @return {ReferenceNode} A reference to this node. + */ + label( name ) { + + this.name = name; + + return this; + + } + + /** + * Sets the node type which automatically defines the internal + * uniform type. + * + * @param {string} uniformType - The type to set. + */ + setNodeType( uniformType ) { + + let node = null; + + if ( this.count !== null ) { + + node = buffer( null, uniformType, this.count ); + + } else if ( Array.isArray( this.getValueFromReference() ) ) { + + node = uniformArray( null, uniformType ); + + } else if ( uniformType === 'texture' ) { + + node = texture( null ); + + } else if ( uniformType === 'cubeTexture' ) { + + node = cubeTexture( null ); + + } else { + + node = uniform( null, uniformType ); + + } + + if ( this.group !== null ) { + + node.setGroup( this.group ); + + } + + if ( this.name !== null ) node.label( this.name ); + + this.node = node.getSelf(); + + } + + /** + * This method is overwritten since the node type is inferred from + * the type of the reference node. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType( builder ) { + + if ( this.node === null ) { + + this.updateReference( builder ); + this.updateValue(); + + } + + return this.node.getNodeType( builder ); + + } + + /** + * Returns the property value from the given referred object. + * + * @param {Object} [object=this.reference] - The object to retrieve the property value from. + * @return {any} The value. + */ + getValueFromReference( object = this.reference ) { + + const { properties } = this; + + let value = object[ properties[ 0 ] ]; + + for ( let i = 1; i < properties.length; i ++ ) { + + value = value[ properties[ i ] ]; + + } + + return value; + + } + + /** + * Allows to update the reference based on the given state. The state is only + * evaluated {@link ReferenceNode#object} is not set. + * + * @param {(NodeFrame|NodeBuilder)} state - The current state. + * @return {Object} The updated reference. + */ + updateReference( state ) { + + this.reference = this.object !== null ? this.object : state.object; + + return this.reference; + + } + + /** + * The output of the reference node is the internal uniform node. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {UniformNode} The output node. + */ + setup( /* builder */ ) { + + this.updateValue(); + + return this.node; + + } + + /** + * Overwritten to update the internal uniform value. + * + * @param {NodeFrame} frame - A reference to the current node frame. + */ + update( /*frame*/ ) { + + this.updateValue(); + + } + + /** + * Retrieves the value from the referred object property and uses it + * to updated the internal uniform. + */ + updateValue() { + + if ( this.node === null ) this.setNodeType( this.uniformType ); + + const value = this.getValueFromReference(); + + if ( Array.isArray( value ) ) { + + this.node.array = value; + + } else { + + this.node.value = value; + + } + + } + +} + +/** + * TSL function for creating a reference node. + * + * @tsl + * @function + * @param {string} name - The name of the property the node refers to. + * @param {string} type - The uniform type that should be used to represent the property value. + * @param {?Object} [object] - The object the property belongs to. + * @returns {ReferenceNode} + */ +const reference = ( name, type, object ) => nodeObject( new ReferenceNode( name, type, object ) ); + +/** + * TSL function for creating a reference node. Use this function if you want need a reference + * to an array-like property that should be represented as a uniform buffer. + * + * @tsl + * @function + * @param {string} name - The name of the property the node refers to. + * @param {string} type - The uniform type that should be used to represent the property value. + * @param {number} count - The number of value inside the array-like object. + * @param {Object} object - An array-like object the property belongs to. + * @returns {ReferenceNode} + */ +const referenceBuffer = ( name, type, count, object ) => nodeObject( new ReferenceNode( name, type, object, count ) ); + +/** + * This node is a special type of reference node which is intended + * for linking material properties with node values. + * ```js + * const opacityNode = materialReference( 'opacity', 'float', material ); + * ``` + * When changing `material.opacity`, the node value of `opacityNode` will + * automatically be updated. + * + * @augments ReferenceNode + */ +class MaterialReferenceNode extends ReferenceNode { + + static get type() { + + return 'MaterialReferenceNode'; + + } + + /** + * Constructs a new material reference node. + * + * @param {string} property - The name of the property the node refers to. + * @param {string} inputType - The uniform type that should be used to represent the property value. + * @param {?Material} [material=null] - The material the property belongs to. When no material is set, + * the node refers to the material of the current rendered object. + */ + constructor( property, inputType, material = null ) { + + super( property, inputType, material ); + + /** + * The material the property belongs to. When no material is set, + * the node refers to the material of the current rendered object. + * + * @type {?Material} + * @default null + */ + this.material = material; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMaterialReferenceNode = true; + + } + + /** + * Updates the reference based on the given state. The state is only evaluated + * {@link MaterialReferenceNode#material} is not set. + * + * @param {(NodeFrame|NodeBuilder)} state - The current state. + * @return {Object} The updated reference. + */ + updateReference( state ) { + + this.reference = this.material !== null ? this.material : state.material; + + return this.reference; + + } + +} + +/** + * TSL function for creating a material reference node. + * + * @tsl + * @function + * @param {string} name - The name of the property the node refers to. + * @param {string} type - The uniform type that should be used to represent the property value. + * @param {?Material} [material=null] - The material the property belongs to. + * When no material is set, the node refers to the material of the current rendered object. + * @returns {MaterialReferenceNode} + */ +const materialReference = ( name, type, material = null ) => nodeObject( new MaterialReferenceNode( name, type, material ) ); + +// Normal Mapping Without Precomputed Tangents +// http://www.thetenthplanet.de/archives/1180 + +const uv = uv$1(); + +const q0 = positionView.dFdx(); +const q1 = positionView.dFdy(); +const st0 = uv.dFdx(); +const st1 = uv.dFdy(); + +const N = normalView; + +const q1perp = q1.cross( N ); +const q0perp = N.cross( q0 ); + +const T = q1perp.mul( st0.x ).add( q0perp.mul( st1.x ) ); +const B = q1perp.mul( st0.y ).add( q0perp.mul( st1.y ) ); + +const det = T.dot( T ).max( B.dot( B ) ); +const scale = det.equal( 0.0 ).select( 0.0, det.inverseSqrt() ); + +/** + * Tangent vector in view space, computed dynamically from geometry and UV derivatives. + * Useful for normal mapping without precomputed tangents. + * + * Reference: http://www.thetenthplanet.de/archives/1180 + * + * @tsl + * @type {Node} + */ +const tangentViewFrame = /*@__PURE__*/ T.mul( scale ).toVar( 'tangentViewFrame' ); + +/** + * Bitangent vector in view space, computed dynamically from geometry and UV derivatives. + * Complements the tangentViewFrame for constructing the tangent space basis. + * + * Reference: http://www.thetenthplanet.de/archives/1180 + * + * @tsl + * @type {Node} + */ +const bitangentViewFrame = /*@__PURE__*/ B.mul( scale ).toVar( 'bitangentViewFrame' ); + +/** + * TSL object that represents the tangent attribute of the current rendered object. + * + * @tsl + * @type {Node} + */ +const tangentGeometry = /*@__PURE__*/ Fn( ( builder ) => { + + if ( builder.geometry.hasAttribute( 'tangent' ) === false ) { + + builder.geometry.computeTangents(); + + } + + return attribute( 'tangent', 'vec4' ); + +} )(); + +/** + * TSL object that represents the vertex tangent in local space of the current rendered object. + * + * @tsl + * @type {Node} + */ +const tangentLocal = /*@__PURE__*/ tangentGeometry.xyz.toVar( 'tangentLocal' ); + +/** + * TSL object that represents the vertex tangent in view space of the current rendered object. + * + * @tsl + * @type {Node} + */ +const tangentView = /*@__PURE__*/ ( Fn( ( { subBuildFn, geometry, material } ) => { + + let node; + + if ( subBuildFn === 'VERTEX' || geometry.hasAttribute( 'tangent' ) ) { + + node = modelViewMatrix.mul( vec4( tangentLocal, 0 ) ).xyz.toVarying( 'v_tangentView' ).normalize(); + + } else { + + node = tangentViewFrame; + + } + + if ( material.flatShading !== true ) { + + node = directionToFaceDirection( node ); + + } + + return node; + +}, 'vec3' ).once( [ 'NORMAL', 'VERTEX' ] ) )().toVar( 'tangentView' ); + +/** + * TSL object that represents the vertex tangent in world space of the current rendered object. + * + * @tsl + * @type {Node} + */ +const tangentWorld = /*@__PURE__*/ tangentView.transformDirection( cameraViewMatrix ).toVarying( 'v_tangentWorld' ).normalize().toVar( 'tangentWorld' ); + +/** + * Returns the bitangent node and assigns it to a varying if the material is not flat shaded. + * + * @tsl + * @private + * @param {Node} crossNormalTangent - The cross product of the normal and tangent vectors. + * @param {string} varyingName - The name of the varying to assign the bitangent to. + * @returns {Node} The bitangent node. + */ +const getBitangent = /*@__PURE__*/ Fn( ( [ crossNormalTangent, varyingName ], { subBuildFn, material } ) => { + + let bitangent = crossNormalTangent.mul( tangentGeometry.w ).xyz; + + if ( subBuildFn === 'NORMAL' && material.flatShading !== true ) { + + bitangent = bitangent.toVarying( varyingName ); + + } + + return bitangent; + +} ).once( [ 'NORMAL' ] ); + +/** + * TSL object that represents the bitangent attribute of the current rendered object. + * + * @tsl + * @type {Node} + */ +const bitangentGeometry = /*@__PURE__*/ getBitangent( normalGeometry.cross( tangentGeometry ), 'v_bitangentGeometry' ).normalize().toVar( 'bitangentGeometry' ); + +/** + * TSL object that represents the vertex bitangent in local space of the current rendered object. + * + * @tsl + * @type {Node} + */ +const bitangentLocal = /*@__PURE__*/ getBitangent( normalLocal.cross( tangentLocal ), 'v_bitangentLocal' ).normalize().toVar( 'bitangentLocal' ); + +/** + * TSL object that represents the vertex bitangent in view space of the current rendered object. + * + * @tsl + * @type {Node} + */ +const bitangentView = /*@__PURE__*/ ( Fn( ( { subBuildFn, geometry, material } ) => { + + let node; + + if ( subBuildFn === 'VERTEX' || geometry.hasAttribute( 'tangent' ) ) { + + node = getBitangent( normalView.cross( tangentView ), 'v_bitangentView' ).normalize(); + + } else { + + node = bitangentViewFrame; + + } + + if ( material.flatShading !== true ) { + + node = directionToFaceDirection( node ); + + } + + return node; + +}, 'vec3' ).once( [ 'NORMAL', 'VERTEX' ] ) )().toVar( 'bitangentView' ); + +/** + * TSL object that represents the vertex bitangent in world space of the current rendered object. + * + * @tsl + * @type {Node} + */ +const bitangentWorld = /*@__PURE__*/ getBitangent( normalWorld.cross( tangentWorld ), 'v_bitangentWorld' ).normalize().toVar( 'bitangentWorld' ); + +/** + * TSL object that represents the TBN matrix in view space. + * + * @tsl + * @type {Node} + */ +const TBNViewMatrix = /*@__PURE__*/ mat3( tangentView, bitangentView, normalView ).toVar( 'TBNViewMatrix' ); + +/** + * TSL object that represents the parallax direction. + * + * @tsl + * @type {Node} + */ +const parallaxDirection = /*@__PURE__*/ positionViewDirection.mul( TBNViewMatrix )/*.normalize()*/; + +/** + * TSL function for computing parallax uv coordinates. + * + * @tsl + * @function + * @param {Node} uv - A uv node. + * @param {Node} scale - A scale node. + * @returns {Node} Parallax uv coordinates. + */ +const parallaxUV = ( uv, scale ) => uv.sub( parallaxDirection.mul( scale ) ); + +/** + * TSL function for computing bent normals. + * + * @tsl + * @function + * @returns {Node} Bent normals. + */ +const bentNormalView = /*@__PURE__*/ ( Fn( () => { + + // https://google.github.io/filament/Filament.md.html#lighting/imagebasedlights/anisotropy + + let bentNormal = anisotropyB.cross( positionViewDirection ); + bentNormal = bentNormal.cross( anisotropyB ).normalize(); + bentNormal = mix( bentNormal, normalView, anisotropy.mul( roughness.oneMinus() ).oneMinus().pow2().pow2() ).normalize(); + + return bentNormal; + +} ).once() )(); + +/** + * This class can be used for applying normals maps to materials. + * + * ```js + * material.normalNode = normalMap( texture( normalTex ) ); + * ``` + * + * @augments TempNode + */ +class NormalMapNode extends TempNode { + + static get type() { + + return 'NormalMapNode'; + + } + + /** + * Constructs a new normal map node. + * + * @param {Node} node - Represents the normal map data. + * @param {?Node} [scaleNode=null] - Controls the intensity of the effect. + */ + constructor( node, scaleNode = null ) { + + super( 'vec3' ); + + /** + * Represents the normal map data. + * + * @type {Node} + */ + this.node = node; + + /** + * Controls the intensity of the effect. + * + * @type {?Node} + * @default null + */ + this.scaleNode = scaleNode; + + /** + * The normal map type. + * + * @type {(TangentSpaceNormalMap|ObjectSpaceNormalMap)} + * @default TangentSpaceNormalMap + */ + this.normalMapType = TangentSpaceNormalMap; + + } + + setup( { material } ) { + + const { normalMapType, scaleNode } = this; + + let normalMap = this.node.mul( 2.0 ).sub( 1.0 ); + + if ( scaleNode !== null ) { + + let scale = scaleNode; + + if ( material.flatShading === true ) { + + scale = directionToFaceDirection( scale ); + + } + + normalMap = vec3( normalMap.xy.mul( scale ), normalMap.z ); + + } + + let output = null; + + if ( normalMapType === ObjectSpaceNormalMap ) { + + output = transformNormalToView( normalMap ); + + } else if ( normalMapType === TangentSpaceNormalMap ) { + + output = TBNViewMatrix.mul( normalMap ).normalize(); + + } else { + + console.error( `THREE.NodeMaterial: Unsupported normal map type: ${ normalMapType }` ); + + output = normalView; // Fallback to default normal view + + } + + return output; + + } + +} + +/** + * TSL function for creating a normal map node. + * + * @tsl + * @function + * @param {Node} node - Represents the normal map data. + * @param {?Node} [scaleNode=null] - Controls the intensity of the effect. + * @returns {NormalMapNode} + */ +const normalMap = /*@__PURE__*/ nodeProxy( NormalMapNode ).setParameterLength( 1, 2 ); + +// Bump Mapping Unparametrized Surfaces on the GPU by Morten S. Mikkelsen +// https://mmikk.github.io/papers3d/mm_sfgrad_bump.pdf + +const dHdxy_fwd = Fn( ( { textureNode, bumpScale } ) => { + + // It's used to preserve the same TextureNode instance + const sampleTexture = ( callback ) => textureNode.cache().context( { getUV: ( texNode ) => callback( texNode.uvNode || uv$1() ), forceUVContext: true } ); + + const Hll = float( sampleTexture( ( uvNode ) => uvNode ) ); + + return vec2( + float( sampleTexture( ( uvNode ) => uvNode.add( uvNode.dFdx() ) ) ).sub( Hll ), + float( sampleTexture( ( uvNode ) => uvNode.add( uvNode.dFdy() ) ) ).sub( Hll ) + ).mul( bumpScale ); + +} ); + +// Evaluate the derivative of the height w.r.t. screen-space using forward differencing (listing 2) + +const perturbNormalArb = Fn( ( inputs ) => { + + const { surf_pos, surf_norm, dHdxy } = inputs; + + // normalize is done to ensure that the bump map looks the same regardless of the texture's scale + const vSigmaX = surf_pos.dFdx().normalize(); + const vSigmaY = surf_pos.dFdy().normalize(); + const vN = surf_norm; // normalized + + const R1 = vSigmaY.cross( vN ); + const R2 = vN.cross( vSigmaX ); + + const fDet = vSigmaX.dot( R1 ).mul( faceDirection ); + + const vGrad = fDet.sign().mul( dHdxy.x.mul( R1 ).add( dHdxy.y.mul( R2 ) ) ); + + return fDet.abs().mul( surf_norm ).sub( vGrad ).normalize(); + +} ); + +/** + * This class can be used for applying bump maps to materials. + * + * ```js + * material.normalNode = bumpMap( texture( bumpTex ) ); + * ``` + * + * @augments TempNode + */ +class BumpMapNode extends TempNode { + + static get type() { + + return 'BumpMapNode'; + + } + + /** + * Constructs a new bump map node. + * + * @param {Node} textureNode - Represents the bump map data. + * @param {?Node} [scaleNode=null] - Controls the intensity of the bump effect. + */ + constructor( textureNode, scaleNode = null ) { + + super( 'vec3' ); + + /** + * Represents the bump map data. + * + * @type {Node} + */ + this.textureNode = textureNode; + + /** + * Controls the intensity of the bump effect. + * + * @type {?Node} + * @default null + */ + this.scaleNode = scaleNode; + + } + + setup() { + + const bumpScale = this.scaleNode !== null ? this.scaleNode : 1; + const dHdxy = dHdxy_fwd( { textureNode: this.textureNode, bumpScale } ); + + return perturbNormalArb( { + surf_pos: positionView, + surf_norm: normalView, + dHdxy + } ); + + } + +} + +/** + * TSL function for creating a bump map node. + * + * @tsl + * @function + * @param {Node} textureNode - Represents the bump map data. + * @param {?Node} [scaleNode=null] - Controls the intensity of the bump effect. + * @returns {BumpMapNode} + */ +const bumpMap = /*@__PURE__*/ nodeProxy( BumpMapNode ).setParameterLength( 1, 2 ); + +const _propertyCache = new Map(); + +/** + * This class should simplify the node access to material properties. + * It internal uses reference nodes to make sure changes to material + * properties are automatically reflected to predefined TSL objects + * like e.g. `materialColor`. + * + * @augments Node + */ +class MaterialNode extends Node { + + static get type() { + + return 'MaterialNode'; + + } + + /** + * Constructs a new material node. + * + * @param {string} scope - The scope defines what kind of material property is referred by the node. + */ + constructor( scope ) { + + super(); + + /** + * The scope defines what material property is referred by the node. + * + * @type {string} + */ + this.scope = scope; + + } + + /** + * Returns a cached reference node for the given property and type. + * + * @param {string} property - The name of the material property. + * @param {string} type - The uniform type of the property. + * @return {MaterialReferenceNode} A material reference node representing the property access. + */ + getCache( property, type ) { + + let node = _propertyCache.get( property ); + + if ( node === undefined ) { + + node = materialReference( property, type ); + + _propertyCache.set( property, node ); + + } + + return node; + + } + + /** + * Returns a float-typed material reference node for the given property name. + * + * @param {string} property - The name of the material property. + * @return {MaterialReferenceNode} A material reference node representing the property access. + */ + getFloat( property ) { + + return this.getCache( property, 'float' ); + + } + + /** + * Returns a color-typed material reference node for the given property name. + * + * @param {string} property - The name of the material property. + * @return {MaterialReferenceNode} A material reference node representing the property access. + */ + getColor( property ) { + + return this.getCache( property, 'color' ); + + } + + /** + * Returns a texture-typed material reference node for the given property name. + * + * @param {string} property - The name of the material property. + * @return {MaterialReferenceNode} A material reference node representing the property access. + */ + getTexture( property ) { + + return this.getCache( property === 'map' ? 'map' : property + 'Map', 'texture' ); + + } + + /** + * The node setup is done depending on the selected scope. Multiple material properties + * might be grouped into a single node composition if they logically belong together. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {Node} The node representing the selected scope. + */ + setup( builder ) { + + const material = builder.context.material; + const scope = this.scope; + + let node = null; + + if ( scope === MaterialNode.COLOR ) { + + const colorNode = material.color !== undefined ? this.getColor( scope ) : vec3(); + + if ( material.map && material.map.isTexture === true ) { + + node = colorNode.mul( this.getTexture( 'map' ) ); + + } else { + + node = colorNode; + + } + + } else if ( scope === MaterialNode.OPACITY ) { + + const opacityNode = this.getFloat( scope ); + + if ( material.alphaMap && material.alphaMap.isTexture === true ) { + + node = opacityNode.mul( this.getTexture( 'alpha' ) ); + + } else { + + node = opacityNode; + + } + + } else if ( scope === MaterialNode.SPECULAR_STRENGTH ) { + + if ( material.specularMap && material.specularMap.isTexture === true ) { + + node = this.getTexture( 'specular' ).r; + + } else { + + node = float( 1 ); + + } + + } else if ( scope === MaterialNode.SPECULAR_INTENSITY ) { + + const specularIntensityNode = this.getFloat( scope ); + + if ( material.specularIntensityMap && material.specularIntensityMap.isTexture === true ) { + + node = specularIntensityNode.mul( this.getTexture( scope ).a ); + + } else { + + node = specularIntensityNode; + + } + + } else if ( scope === MaterialNode.SPECULAR_COLOR ) { + + const specularColorNode = this.getColor( scope ); + + if ( material.specularColorMap && material.specularColorMap.isTexture === true ) { + + node = specularColorNode.mul( this.getTexture( scope ).rgb ); + + } else { + + node = specularColorNode; + + } + + } else if ( scope === MaterialNode.ROUGHNESS ) { // TODO: cleanup similar branches + + const roughnessNode = this.getFloat( scope ); + + if ( material.roughnessMap && material.roughnessMap.isTexture === true ) { + + node = roughnessNode.mul( this.getTexture( scope ).g ); + + } else { + + node = roughnessNode; + + } + + } else if ( scope === MaterialNode.METALNESS ) { + + const metalnessNode = this.getFloat( scope ); + + if ( material.metalnessMap && material.metalnessMap.isTexture === true ) { + + node = metalnessNode.mul( this.getTexture( scope ).b ); + + } else { + + node = metalnessNode; + + } + + } else if ( scope === MaterialNode.EMISSIVE ) { + + const emissiveIntensityNode = this.getFloat( 'emissiveIntensity' ); + const emissiveNode = this.getColor( scope ).mul( emissiveIntensityNode ); + + if ( material.emissiveMap && material.emissiveMap.isTexture === true ) { + + node = emissiveNode.mul( this.getTexture( scope ) ); + + } else { + + node = emissiveNode; + + } + + } else if ( scope === MaterialNode.NORMAL ) { + + if ( material.normalMap ) { + + node = normalMap( this.getTexture( 'normal' ), this.getCache( 'normalScale', 'vec2' ) ); + node.normalMapType = material.normalMapType; + + } else if ( material.bumpMap ) { + + node = bumpMap( this.getTexture( 'bump' ).r, this.getFloat( 'bumpScale' ) ); + + } else { + + node = normalView; + + } + + } else if ( scope === MaterialNode.CLEARCOAT ) { + + const clearcoatNode = this.getFloat( scope ); + + if ( material.clearcoatMap && material.clearcoatMap.isTexture === true ) { + + node = clearcoatNode.mul( this.getTexture( scope ).r ); + + } else { + + node = clearcoatNode; + + } + + } else if ( scope === MaterialNode.CLEARCOAT_ROUGHNESS ) { + + const clearcoatRoughnessNode = this.getFloat( scope ); + + if ( material.clearcoatRoughnessMap && material.clearcoatRoughnessMap.isTexture === true ) { + + node = clearcoatRoughnessNode.mul( this.getTexture( scope ).r ); + + } else { + + node = clearcoatRoughnessNode; + + } + + } else if ( scope === MaterialNode.CLEARCOAT_NORMAL ) { + + if ( material.clearcoatNormalMap ) { + + node = normalMap( this.getTexture( scope ), this.getCache( scope + 'Scale', 'vec2' ) ); + + } else { + + node = normalView; + + } + + } else if ( scope === MaterialNode.SHEEN ) { + + const sheenNode = this.getColor( 'sheenColor' ).mul( this.getFloat( 'sheen' ) ); // Move this mul() to CPU + + if ( material.sheenColorMap && material.sheenColorMap.isTexture === true ) { + + node = sheenNode.mul( this.getTexture( 'sheenColor' ).rgb ); + + } else { + + node = sheenNode; + + } + + } else if ( scope === MaterialNode.SHEEN_ROUGHNESS ) { + + const sheenRoughnessNode = this.getFloat( scope ); + + if ( material.sheenRoughnessMap && material.sheenRoughnessMap.isTexture === true ) { + + node = sheenRoughnessNode.mul( this.getTexture( scope ).a ); + + } else { + + node = sheenRoughnessNode; + + } + + node = node.clamp( 0.07, 1.0 ); + + } else if ( scope === MaterialNode.ANISOTROPY ) { + + if ( material.anisotropyMap && material.anisotropyMap.isTexture === true ) { + + const anisotropyPolar = this.getTexture( scope ); + const anisotropyMat = mat2( materialAnisotropyVector.x, materialAnisotropyVector.y, materialAnisotropyVector.y.negate(), materialAnisotropyVector.x ); + + node = anisotropyMat.mul( anisotropyPolar.rg.mul( 2.0 ).sub( vec2( 1.0 ) ).normalize().mul( anisotropyPolar.b ) ); + + } else { + + node = materialAnisotropyVector; + + } + + } else if ( scope === MaterialNode.IRIDESCENCE_THICKNESS ) { + + const iridescenceThicknessMaximum = reference( '1', 'float', material.iridescenceThicknessRange ); + + if ( material.iridescenceThicknessMap ) { + + const iridescenceThicknessMinimum = reference( '0', 'float', material.iridescenceThicknessRange ); + + node = iridescenceThicknessMaximum.sub( iridescenceThicknessMinimum ).mul( this.getTexture( scope ).g ).add( iridescenceThicknessMinimum ); + + } else { + + node = iridescenceThicknessMaximum; + + } + + } else if ( scope === MaterialNode.TRANSMISSION ) { + + const transmissionNode = this.getFloat( scope ); + + if ( material.transmissionMap ) { + + node = transmissionNode.mul( this.getTexture( scope ).r ); + + } else { + + node = transmissionNode; + + } + + } else if ( scope === MaterialNode.THICKNESS ) { + + const thicknessNode = this.getFloat( scope ); + + if ( material.thicknessMap ) { + + node = thicknessNode.mul( this.getTexture( scope ).g ); + + } else { + + node = thicknessNode; + + } + + } else if ( scope === MaterialNode.IOR ) { + + node = this.getFloat( scope ); + + } else if ( scope === MaterialNode.LIGHT_MAP ) { + + node = this.getTexture( scope ).rgb.mul( this.getFloat( 'lightMapIntensity' ) ); + + } else if ( scope === MaterialNode.AO ) { + + node = this.getTexture( scope ).r.sub( 1.0 ).mul( this.getFloat( 'aoMapIntensity' ) ).add( 1.0 ); + + } else if ( scope === MaterialNode.LINE_DASH_OFFSET ) { + + node = ( material.dashOffset ) ? this.getFloat( scope ) : float( 0 ); + + } else { + + const outputType = this.getNodeType( builder ); + + node = this.getCache( scope, outputType ); + + } + + return node; + + } + +} + +MaterialNode.ALPHA_TEST = 'alphaTest'; +MaterialNode.COLOR = 'color'; +MaterialNode.OPACITY = 'opacity'; +MaterialNode.SHININESS = 'shininess'; +MaterialNode.SPECULAR = 'specular'; +MaterialNode.SPECULAR_STRENGTH = 'specularStrength'; +MaterialNode.SPECULAR_INTENSITY = 'specularIntensity'; +MaterialNode.SPECULAR_COLOR = 'specularColor'; +MaterialNode.REFLECTIVITY = 'reflectivity'; +MaterialNode.ROUGHNESS = 'roughness'; +MaterialNode.METALNESS = 'metalness'; +MaterialNode.NORMAL = 'normal'; +MaterialNode.CLEARCOAT = 'clearcoat'; +MaterialNode.CLEARCOAT_ROUGHNESS = 'clearcoatRoughness'; +MaterialNode.CLEARCOAT_NORMAL = 'clearcoatNormal'; +MaterialNode.EMISSIVE = 'emissive'; +MaterialNode.ROTATION = 'rotation'; +MaterialNode.SHEEN = 'sheen'; +MaterialNode.SHEEN_ROUGHNESS = 'sheenRoughness'; +MaterialNode.ANISOTROPY = 'anisotropy'; +MaterialNode.IRIDESCENCE = 'iridescence'; +MaterialNode.IRIDESCENCE_IOR = 'iridescenceIOR'; +MaterialNode.IRIDESCENCE_THICKNESS = 'iridescenceThickness'; +MaterialNode.IOR = 'ior'; +MaterialNode.TRANSMISSION = 'transmission'; +MaterialNode.THICKNESS = 'thickness'; +MaterialNode.ATTENUATION_DISTANCE = 'attenuationDistance'; +MaterialNode.ATTENUATION_COLOR = 'attenuationColor'; +MaterialNode.LINE_SCALE = 'scale'; +MaterialNode.LINE_DASH_SIZE = 'dashSize'; +MaterialNode.LINE_GAP_SIZE = 'gapSize'; +MaterialNode.LINE_WIDTH = 'linewidth'; +MaterialNode.LINE_DASH_OFFSET = 'dashOffset'; +MaterialNode.POINT_SIZE = 'size'; +MaterialNode.DISPERSION = 'dispersion'; +MaterialNode.LIGHT_MAP = 'light'; +MaterialNode.AO = 'ao'; + +/** + * TSL object that represents alpha test of the current material. + * + * @tsl + * @type {Node} + */ +const materialAlphaTest = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.ALPHA_TEST ); + +/** + * TSL object that represents the diffuse color of the current material. + * The value is composed via `color` * `map`. + * + * @tsl + * @type {Node} + */ +const materialColor = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.COLOR ); + +/** + * TSL object that represents the shininess of the current material. + * + * @tsl + * @type {Node} + */ +const materialShininess = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.SHININESS ); + +/** + * TSL object that represents the emissive color of the current material. + * The value is composed via `emissive` * `emissiveIntensity` * `emissiveMap`. + * + * @tsl + * @type {Node} + */ +const materialEmissive = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.EMISSIVE ); + +/** + * TSL object that represents the opacity of the current material. + * The value is composed via `opacity` * `alphaMap`. + * + * @tsl + * @type {Node} + */ +const materialOpacity = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.OPACITY ); + +/** + * TSL object that represents the specular of the current material. + * + * @tsl + * @type {Node} + */ +const materialSpecular = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.SPECULAR ); + +/** + * TSL object that represents the specular intensity of the current material. + * The value is composed via `specularIntensity` * `specularMap.a`. + * + * @tsl + * @type {Node} + */ +const materialSpecularIntensity = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.SPECULAR_INTENSITY ); + +/** + * TSL object that represents the specular color of the current material. + * The value is composed via `specularColor` * `specularMap.rgb`. + * + * @tsl + * @type {Node} + */ +const materialSpecularColor = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.SPECULAR_COLOR ); + +/** + * TSL object that represents the specular strength of the current material. + * The value is composed via `specularMap.r`. + * + * @tsl + * @type {Node} + */ +const materialSpecularStrength = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.SPECULAR_STRENGTH ); + +/** + * TSL object that represents the reflectivity of the current material. + * + * @tsl + * @type {Node} + */ +const materialReflectivity = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.REFLECTIVITY ); + +/** + * TSL object that represents the roughness of the current material. + * The value is composed via `roughness` * `roughnessMap.g`. + * + * @tsl + * @type {Node} + */ +const materialRoughness = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.ROUGHNESS ); + +/** + * TSL object that represents the metalness of the current material. + * The value is composed via `metalness` * `metalnessMap.b`. + * + * @tsl + * @type {Node} + */ +const materialMetalness = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.METALNESS ); + +/** + * TSL object that represents the normal of the current material. + * The value will be either `normalMap` * `normalScale`, `bumpMap` * `bumpScale` or `normalView`. + * + * @tsl + * @type {Node} + */ +const materialNormal = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.NORMAL ); + +/** + * TSL object that represents the clearcoat of the current material. + * The value is composed via `clearcoat` * `clearcoatMap.r` + * + * @tsl + * @type {Node} + */ +const materialClearcoat = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.CLEARCOAT ); + +/** + * TSL object that represents the clearcoat roughness of the current material. + * The value is composed via `clearcoatRoughness` * `clearcoatRoughnessMap.r`. + * + * @tsl + * @type {Node} + */ +const materialClearcoatRoughness = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.CLEARCOAT_ROUGHNESS ); + +/** + * TSL object that represents the clearcoat normal of the current material. + * The value will be either `clearcoatNormalMap` or `normalView`. + * + * @tsl + * @type {Node} + */ +const materialClearcoatNormal = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.CLEARCOAT_NORMAL ); + +/** + * TSL object that represents the rotation of the current sprite material. + * + * @tsl + * @type {Node} + */ +const materialRotation = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.ROTATION ); + +/** + * TSL object that represents the sheen color of the current material. + * The value is composed via `sheen` * `sheenColor` * `sheenColorMap`. + * + * @tsl + * @type {Node} + */ +const materialSheen = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.SHEEN ); + +/** + * TSL object that represents the sheen roughness of the current material. + * The value is composed via `sheenRoughness` * `sheenRoughnessMap.a`. + * + * @tsl + * @type {Node} + */ +const materialSheenRoughness = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.SHEEN_ROUGHNESS ); + +/** + * TSL object that represents the anisotropy of the current material. + * + * @tsl + * @type {Node} + */ +const materialAnisotropy = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.ANISOTROPY ); + +/** + * TSL object that represents the iridescence of the current material. + * + * @tsl + * @type {Node} + */ +const materialIridescence = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.IRIDESCENCE ); + +/** + * TSL object that represents the iridescence IOR of the current material. + * + * @tsl + * @type {Node} + */ +const materialIridescenceIOR = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.IRIDESCENCE_IOR ); + +/** + * TSL object that represents the iridescence thickness of the current material. + * + * @tsl + * @type {Node} + */ +const materialIridescenceThickness = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.IRIDESCENCE_THICKNESS ); + +/** + * TSL object that represents the transmission of the current material. + * The value is composed via `transmission` * `transmissionMap.r`. + * + * @tsl + * @type {Node} + */ +const materialTransmission = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.TRANSMISSION ); + +/** + * TSL object that represents the thickness of the current material. + * The value is composed via `thickness` * `thicknessMap.g`. + * + * @tsl + * @type {Node} + */ +const materialThickness = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.THICKNESS ); + +/** + * TSL object that represents the IOR of the current material. + * + * @tsl + * @type {Node} + */ +const materialIOR = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.IOR ); + +/** + * TSL object that represents the attenuation distance of the current material. + * + * @tsl + * @type {Node} + */ +const materialAttenuationDistance = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.ATTENUATION_DISTANCE ); + +/** + * TSL object that represents the attenuation color of the current material. + * + * @tsl + * @type {Node} + */ +const materialAttenuationColor = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.ATTENUATION_COLOR ); + +/** + * TSL object that represents the scale of the current dashed line material. + * + * @tsl + * @type {Node} + */ +const materialLineScale = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.LINE_SCALE ); + +/** + * TSL object that represents the dash size of the current dashed line material. + * + * @tsl + * @type {Node} + */ +const materialLineDashSize = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.LINE_DASH_SIZE ); + +/** + * TSL object that represents the gap size of the current dashed line material. + * + * @tsl + * @type {Node} + */ +const materialLineGapSize = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.LINE_GAP_SIZE ); + +/** + * TSL object that represents the line width of the current line material. + * + * @tsl + * @type {Node} + */ +const materialLineWidth = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.LINE_WIDTH ); + +/** + * TSL object that represents the dash offset of the current line material. + * + * @tsl + * @type {Node} + */ +const materialLineDashOffset = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.LINE_DASH_OFFSET ); + +/** + * TSL object that represents the point size of the current points material. + * + * @tsl + * @type {Node} + */ +const materialPointSize = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.POINT_SIZE ); + +/** + * TSL object that represents the dispersion of the current material. + * + * @tsl + * @type {Node} + */ +const materialDispersion = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.DISPERSION ); + +/** + * TSL object that represents the light map of the current material. + * The value is composed via `lightMapIntensity` * `lightMap.rgb`. + * + * @tsl + * @type {Node} + */ +const materialLightMap = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.LIGHT_MAP ); + +/** + * TSL object that represents the ambient occlusion map of the current material. + * The value is composed via `aoMap.r` - 1 * `aoMapIntensity` + 1. + * + * @tsl + * @type {Node} + */ +const materialAO = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.AO ); + +/** + * TSL object that represents the anisotropy vector of the current material. + * + * @tsl + * @type {Node} + */ +const materialAnisotropyVector = /*@__PURE__*/ uniform( new Vector2() ).onReference( function ( frame ) { + + return frame.material; + +} ).onRenderUpdate( function ( { material } ) { + + this.value.set( material.anisotropy * Math.cos( material.anisotropyRotation ), material.anisotropy * Math.sin( material.anisotropyRotation ) ); + +} ); + +/** + * TSL object that represents the position in clip space after the model-view-projection transform of the current rendered object. + * + * @tsl + * @type {VaryingNode} + */ +const modelViewProjection = /*@__PURE__*/ ( Fn( ( builder ) => { + + return builder.context.setupModelViewProjection(); + +}, 'vec4' ).once() )().toVarying( 'v_modelViewProjection' ); + +/** + * This class represents shader indices of different types. The following predefined node + * objects cover frequent use cases: + * + * - `vertexIndex`: The index of a vertex within a mesh. + * - `instanceIndex`: The index of either a mesh instance or an invocation of a compute shader. + * - `drawIndex`: The index of a draw call. + * - `invocationLocalIndex`: The index of a compute invocation within the scope of a workgroup load. + * - `invocationSubgroupIndex`: The index of a compute invocation within the scope of a subgroup. + * - `subgroupIndex`: The index of the subgroup the current compute invocation belongs to. + * + * @augments Node + */ +class IndexNode extends Node { + + static get type() { + + return 'IndexNode'; + + } + + /** + * Constructs a new index node. + * + * @param {('vertex'|'instance'|'subgroup'|'invocationLocal'|'invocationSubgroup'|'draw')} scope - The scope of the index node. + */ + constructor( scope ) { + + super( 'uint' ); + + /** + * The scope of the index node. + * + * @type {string} + */ + this.scope = scope; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isIndexNode = true; + + } + + generate( builder ) { + + const nodeType = this.getNodeType( builder ); + const scope = this.scope; + + let propertyName; + + if ( scope === IndexNode.VERTEX ) { + + propertyName = builder.getVertexIndex(); + + } else if ( scope === IndexNode.INSTANCE ) { + + propertyName = builder.getInstanceIndex(); + + } else if ( scope === IndexNode.DRAW ) { + + propertyName = builder.getDrawIndex(); + + } else if ( scope === IndexNode.INVOCATION_LOCAL ) { + + propertyName = builder.getInvocationLocalIndex(); + + } else if ( scope === IndexNode.INVOCATION_SUBGROUP ) { + + propertyName = builder.getInvocationSubgroupIndex(); + + } else if ( scope === IndexNode.SUBGROUP ) { + + propertyName = builder.getSubgroupIndex(); + + } else { + + throw new Error( 'THREE.IndexNode: Unknown scope: ' + scope ); + + } + + let output; + + if ( builder.shaderStage === 'vertex' || builder.shaderStage === 'compute' ) { + + output = propertyName; + + } else { + + const nodeVarying = varying( this ); + + output = nodeVarying.build( builder, nodeType ); + + } + + return output; + + } + +} + +IndexNode.VERTEX = 'vertex'; +IndexNode.INSTANCE = 'instance'; +IndexNode.SUBGROUP = 'subgroup'; +IndexNode.INVOCATION_LOCAL = 'invocationLocal'; +IndexNode.INVOCATION_SUBGROUP = 'invocationSubgroup'; +IndexNode.DRAW = 'draw'; + +/** + * TSL object that represents the index of a vertex within a mesh. + * + * @tsl + * @type {IndexNode} + */ +const vertexIndex = /*@__PURE__*/ nodeImmutable( IndexNode, IndexNode.VERTEX ); + +/** + * TSL object that represents the index of either a mesh instance or an invocation of a compute shader. + * + * @tsl + * @type {IndexNode} + */ +const instanceIndex = /*@__PURE__*/ nodeImmutable( IndexNode, IndexNode.INSTANCE ); + +/** + * TSL object that represents the index of the subgroup the current compute invocation belongs to. + * + * @tsl + * @type {IndexNode} + */ +const subgroupIndex = /*@__PURE__*/ nodeImmutable( IndexNode, IndexNode.SUBGROUP ); + +/** + * TSL object that represents the index of a compute invocation within the scope of a subgroup. + * + * @tsl + * @type {IndexNode} + */ +const invocationSubgroupIndex = /*@__PURE__*/ nodeImmutable( IndexNode, IndexNode.INVOCATION_SUBGROUP ); + +/** + * TSL object that represents the index of a compute invocation within the scope of a workgroup load. + * + * @tsl + * @type {IndexNode} + */ +const invocationLocalIndex = /*@__PURE__*/ nodeImmutable( IndexNode, IndexNode.INVOCATION_LOCAL ); + +/** + * TSL object that represents the index of a draw call. + * + * @tsl + * @type {IndexNode} + */ +const drawIndex = /*@__PURE__*/ nodeImmutable( IndexNode, IndexNode.DRAW ); + +/** + * This node implements the vertex shader logic which is required + * when rendering 3D objects via instancing. The code makes sure + * vertex positions, normals and colors can be modified via instanced + * data. + * + * @augments Node + */ +class InstanceNode extends Node { + + static get type() { + + return 'InstanceNode'; + + } + + /** + * Constructs a new instance node. + * + * @param {number} count - The number of instances. + * @param {InstancedBufferAttribute} instanceMatrix - Instanced buffer attribute representing the instance transformations. + * @param {?InstancedBufferAttribute} instanceColor - Instanced buffer attribute representing the instance colors. + */ + constructor( count, instanceMatrix, instanceColor = null ) { + + super( 'void' ); + + /** + * The number of instances. + * + * @type {number} + */ + this.count = count; + + /** + * Instanced buffer attribute representing the transformation of instances. + * + * @type {InstancedBufferAttribute} + */ + this.instanceMatrix = instanceMatrix; + + /** + * Instanced buffer attribute representing the color of instances. + * + * @type {InstancedBufferAttribute} + */ + this.instanceColor = instanceColor; + + /** + * The node that represents the instance matrix data. + * + * @type {?Node} + */ + this.instanceMatrixNode = null; + + /** + * The node that represents the instance color data. + * + * @type {?Node} + * @default null + */ + this.instanceColorNode = null; + + /** + * The update type is set to `frame` since an update + * of instanced buffer data must be checked per frame. + * + * @type {string} + * @default 'frame' + */ + this.updateType = NodeUpdateType.FRAME; + + /** + * A reference to a buffer that is used by `instanceMatrixNode`. + * + * @type {?InstancedInterleavedBuffer} + */ + this.buffer = null; + + /** + * A reference to a buffer that is used by `instanceColorNode`. + * + * @type {?InstancedBufferAttribute} + */ + this.bufferColor = null; + + } + + /** + * Setups the internal buffers and nodes and assigns the transformed vertex data + * to predefined node variables for accumulation. That follows the same patterns + * like with morph and skinning nodes. + * + * @param {NodeBuilder} builder - The current node builder. + */ + setup( builder ) { + + const { count, instanceMatrix, instanceColor } = this; + + let { instanceMatrixNode, instanceColorNode } = this; + + if ( instanceMatrixNode === null ) { + + // Both WebGPU and WebGL backends have UBO max limited to 64kb. Matrix count number bigger than 1000 ( 16 * 4 * 1000 = 64kb ) will fallback to attribute. + + if ( count <= 1000 ) { + + instanceMatrixNode = buffer( instanceMatrix.array, 'mat4', Math.max( count, 1 ) ).element( instanceIndex ); + + } else { + + const buffer = new InstancedInterleavedBuffer( instanceMatrix.array, 16, 1 ); + + this.buffer = buffer; + + const bufferFn = instanceMatrix.usage === DynamicDrawUsage ? instancedDynamicBufferAttribute : instancedBufferAttribute; + + const instanceBuffers = [ + // F.Signature -> bufferAttribute( array, type, stride, offset ) + bufferFn( buffer, 'vec4', 16, 0 ), + bufferFn( buffer, 'vec4', 16, 4 ), + bufferFn( buffer, 'vec4', 16, 8 ), + bufferFn( buffer, 'vec4', 16, 12 ) + ]; + + instanceMatrixNode = mat4( ...instanceBuffers ); + + } + + this.instanceMatrixNode = instanceMatrixNode; + + } + + if ( instanceColor && instanceColorNode === null ) { + + const buffer = new InstancedBufferAttribute( instanceColor.array, 3 ); + + const bufferFn = instanceColor.usage === DynamicDrawUsage ? instancedDynamicBufferAttribute : instancedBufferAttribute; + + this.bufferColor = buffer; + + instanceColorNode = vec3( bufferFn( buffer, 'vec3', 3, 0 ) ); + + this.instanceColorNode = instanceColorNode; + + } + + // POSITION + + const instancePosition = instanceMatrixNode.mul( positionLocal ).xyz; + positionLocal.assign( instancePosition ); + + // NORMAL + + if ( builder.hasGeometryAttribute( 'normal' ) ) { + + const instanceNormal = transformNormal( normalLocal, instanceMatrixNode ); + + // ASSIGNS + + normalLocal.assign( instanceNormal ); + + } + + // COLOR + + if ( this.instanceColorNode !== null ) { + + varyingProperty( 'vec3', 'vInstanceColor' ).assign( this.instanceColorNode ); + + } + + } + + /** + * Checks if the internal buffers required an update. + * + * @param {NodeFrame} frame - The current node frame. + */ + update( /*frame*/ ) { + + if ( this.instanceMatrix.usage !== DynamicDrawUsage && this.buffer !== null && this.instanceMatrix.version !== this.buffer.version ) { + + this.buffer.version = this.instanceMatrix.version; + + } + + if ( this.instanceColor && this.instanceColor.usage !== DynamicDrawUsage && this.bufferColor !== null && this.instanceColor.version !== this.bufferColor.version ) { + + this.bufferColor.version = this.instanceColor.version; + + } + + } + +} + +/** + * TSL function for creating an instance node. + * + * @tsl + * @function + * @param {number} count - The number of instances. + * @param {InstancedBufferAttribute} instanceMatrix - Instanced buffer attribute representing the instance transformations. + * @param {?InstancedBufferAttribute} instanceColor - Instanced buffer attribute representing the instance colors. + * @returns {InstanceNode} + */ +const instance = /*@__PURE__*/ nodeProxy( InstanceNode ).setParameterLength( 2, 3 ); + +/** + * This is a special version of `InstanceNode` which requires the usage of {@link InstancedMesh}. + * It allows an easier setup of the instance node. + * + * @augments InstanceNode + */ +class InstancedMeshNode extends InstanceNode { + + static get type() { + + return 'InstancedMeshNode'; + + } + + /** + * Constructs a new instanced mesh node. + * + * @param {InstancedMesh} instancedMesh - The instanced mesh. + */ + constructor( instancedMesh ) { + + const { count, instanceMatrix, instanceColor } = instancedMesh; + + super( count, instanceMatrix, instanceColor ); + + /** + * A reference to the instanced mesh. + * + * @type {InstancedMesh} + */ + this.instancedMesh = instancedMesh; + + } + +} + +/** + * TSL function for creating an instanced mesh node. + * + * @tsl + * @function + * @param {InstancedMesh} instancedMesh - The instancedMesh. + * @returns {InstancedMeshNode} + */ +const instancedMesh = /*@__PURE__*/ nodeProxy( InstancedMeshNode ).setParameterLength( 1 ); + +/** + * This node implements the vertex shader logic which is required + * when rendering 3D objects via batching. `BatchNode` must be used + * with instances of {@link BatchedMesh}. + * + * @augments Node + */ +class BatchNode extends Node { + + static get type() { + + return 'BatchNode'; + + } + + /** + * Constructs a new batch node. + * + * @param {BatchedMesh} batchMesh - A reference to batched mesh. + */ + constructor( batchMesh ) { + + super( 'void' ); + + /** + * A reference to batched mesh. + * + * @type {BatchedMesh} + */ + this.batchMesh = batchMesh; + + /** + * The batching index node. + * + * @type {?IndexNode} + * @default null + */ + this.batchingIdNode = null; + + } + + /** + * Setups the internal buffers and nodes and assigns the transformed vertex data + * to predefined node variables for accumulation. That follows the same patterns + * like with morph and skinning nodes. + * + * @param {NodeBuilder} builder - The current node builder. + */ + setup( builder ) { + + if ( this.batchingIdNode === null ) { + + if ( builder.getDrawIndex() === null ) { + + this.batchingIdNode = instanceIndex; + + } else { + + this.batchingIdNode = drawIndex; + + } + + } + + const getIndirectIndex = Fn( ( [ id ] ) => { + + const size = int( textureSize( textureLoad( this.batchMesh._indirectTexture ), 0 ).x ); + const x = int( id ).mod( size ); + const y = int( id ).div( size ); + return textureLoad( this.batchMesh._indirectTexture, ivec2( x, y ) ).x; + + } ).setLayout( { + name: 'getIndirectIndex', + type: 'uint', + inputs: [ + { name: 'id', type: 'int' } + ] + } ); + + const indirectId = getIndirectIndex( int( this.batchingIdNode ) ); + + const matricesTexture = this.batchMesh._matricesTexture; + + const size = int( textureSize( textureLoad( matricesTexture ), 0 ).x ); + const j = float( indirectId ).mul( 4 ).toInt().toVar(); + + const x = j.mod( size ); + const y = j.div( size ); + const batchingMatrix = mat4( + textureLoad( matricesTexture, ivec2( x, y ) ), + textureLoad( matricesTexture, ivec2( x.add( 1 ), y ) ), + textureLoad( matricesTexture, ivec2( x.add( 2 ), y ) ), + textureLoad( matricesTexture, ivec2( x.add( 3 ), y ) ) + ); + + + const colorsTexture = this.batchMesh._colorsTexture; + + if ( colorsTexture !== null ) { + + const getBatchingColor = Fn( ( [ id ] ) => { + + const size = int( textureSize( textureLoad( colorsTexture ), 0 ).x ); + const j = id; + const x = j.mod( size ); + const y = j.div( size ); + return textureLoad( colorsTexture, ivec2( x, y ) ).rgb; + + } ).setLayout( { + name: 'getBatchingColor', + type: 'vec3', + inputs: [ + { name: 'id', type: 'int' } + ] + } ); + + const color = getBatchingColor( indirectId ); + + varyingProperty( 'vec3', 'vBatchColor' ).assign( color ); + + } + + const bm = mat3( batchingMatrix ); + + positionLocal.assign( batchingMatrix.mul( positionLocal ) ); + + const transformedNormal = normalLocal.div( vec3( bm[ 0 ].dot( bm[ 0 ] ), bm[ 1 ].dot( bm[ 1 ] ), bm[ 2 ].dot( bm[ 2 ] ) ) ); + + const batchingNormal = bm.mul( transformedNormal ).xyz; + + normalLocal.assign( batchingNormal ); + + if ( builder.hasGeometryAttribute( 'tangent' ) ) { + + tangentLocal.mulAssign( bm ); + + } + + } + +} + +/** + * TSL function for creating a batch node. + * + * @tsl + * @function + * @param {BatchedMesh} batchMesh - A reference to batched mesh. + * @returns {BatchNode} + */ +const batch = /*@__PURE__*/ nodeProxy( BatchNode ).setParameterLength( 1 ); + +/** + * This class enables element access on instances of {@link StorageBufferNode}. + * In most cases, it is indirectly used when accessing elements with the + * {@link StorageBufferNode#element} method. + * + * ```js + * const position = positionStorage.element( instanceIndex ); + * ``` + * + * @augments ArrayElementNode + */ +class StorageArrayElementNode extends ArrayElementNode { + + static get type() { + + return 'StorageArrayElementNode'; + + } + + /** + * Constructs storage buffer element node. + * + * @param {StorageBufferNode} storageBufferNode - The storage buffer node. + * @param {Node} indexNode - The index node that defines the element access. + */ + constructor( storageBufferNode, indexNode ) { + + super( storageBufferNode, indexNode ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isStorageArrayElementNode = true; + + } + + /** + * The storage buffer node. + * + * @param {Node} value + * @type {StorageBufferNode} + */ + set storageBufferNode( value ) { + + this.node = value; + + } + + get storageBufferNode() { + + return this.node; + + } + + getMemberType( builder, name ) { + + const structTypeNode = this.storageBufferNode.structTypeNode; + + if ( structTypeNode ) { + + return structTypeNode.getMemberType( builder, name ); + + } + + return 'void'; + + } + + setup( builder ) { + + if ( builder.isAvailable( 'storageBuffer' ) === false ) { + + if ( this.node.isPBO === true ) { + + builder.setupPBO( this.node ); + + } + + } + + return super.setup( builder ); + + } + + generate( builder, output ) { + + let snippet; + + const isAssignContext = builder.context.assign; + + // + + if ( builder.isAvailable( 'storageBuffer' ) === false ) { + + if ( this.node.isPBO === true && isAssignContext !== true && ( this.node.value.isInstancedBufferAttribute || builder.shaderStage !== 'compute' ) ) { + + snippet = builder.generatePBO( this ); + + } else { + + snippet = this.node.build( builder ); + + } + + } else { + + snippet = super.generate( builder ); + + } + + if ( isAssignContext !== true ) { + + const type = this.getNodeType( builder ); + + snippet = builder.format( snippet, type, output ); + + } + + return snippet; + + } + +} + +/** + * TSL function for creating a storage element node. + * + * @tsl + * @function + * @param {StorageBufferNode} storageBufferNode - The storage buffer node. + * @param {Node} indexNode - The index node that defines the element access. + * @returns {StorageArrayElementNode} + */ +const storageElement = /*@__PURE__*/ nodeProxy( StorageArrayElementNode ).setParameterLength( 2 ); + +/** + * This node is used in context of compute shaders and allows to define a + * storage buffer for data. A typical workflow is to create instances of + * this node with the convenience functions `attributeArray()` or `instancedArray()`, + * setup up a compute shader that writes into the buffers and then convert + * the storage buffers to attribute nodes for rendering. + * + * ```js + * const positionBuffer = instancedArray( particleCount, 'vec3' ); // the storage buffer node + * + * const computeInit = Fn( () => { // the compute shader + * + * const position = positionBuffer.element( instanceIndex ); + * + * // compute position data + * + * position.x = 1; + * position.y = 1; + * position.z = 1; + * + * } )().compute( particleCount ); + * + * const particleMaterial = new THREE.SpriteNodeMaterial(); + * particleMaterial.positionNode = positionBuffer.toAttribute(); + * + * renderer.computeAsync( computeInit ); + * + * ``` + * + * @augments BufferNode + */ +class StorageBufferNode extends BufferNode { + + static get type() { + + return 'StorageBufferNode'; + + } + + /** + * Constructs a new storage buffer node. + * + * @param {StorageBufferAttribute|StorageInstancedBufferAttribute|BufferAttribute} value - The buffer data. + * @param {?(string|Struct)} [bufferType=null] - The buffer type (e.g. `'vec3'`). + * @param {number} [bufferCount=0] - The buffer count. + */ + constructor( value, bufferType = null, bufferCount = 0 ) { + + let nodeType, structTypeNode = null; + + if ( bufferType && bufferType.isStruct ) { + + nodeType = 'struct'; + structTypeNode = bufferType.layout; + + if ( value.isStorageBufferAttribute || value.isStorageInstancedBufferAttribute ) { + + bufferCount = value.count; + + } + + } else if ( bufferType === null && ( value.isStorageBufferAttribute || value.isStorageInstancedBufferAttribute ) ) { + + nodeType = getTypeFromLength( value.itemSize ); + bufferCount = value.count; + + } else { + + nodeType = bufferType; + + } + + super( value, nodeType, bufferCount ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isStorageBufferNode = true; + + + /** + * The buffer struct type. + * + * @type {?StructTypeNode} + * @default null + */ + this.structTypeNode = structTypeNode; + + /** + * The access type of the texture node. + * + * @type {string} + * @default 'readWrite' + */ + this.access = NodeAccess.READ_WRITE; + + /** + * Whether the node is atomic or not. + * + * @type {boolean} + * @default false + */ + this.isAtomic = false; + + /** + * Whether the node represents a PBO or not. + * Only relevant for WebGL. + * + * @type {boolean} + * @default false + */ + this.isPBO = false; + + /** + * A reference to the internal buffer attribute node. + * + * @type {?BufferAttributeNode} + * @default null + */ + this._attribute = null; + + /** + * A reference to the internal varying node. + * + * @type {?VaryingNode} + * @default null + */ + this._varying = null; + + /** + * `StorageBufferNode` sets this property to `true` by default. + * + * @type {boolean} + * @default true + */ + this.global = true; + + if ( value.isStorageBufferAttribute !== true && value.isStorageInstancedBufferAttribute !== true ) { + + // TODO: Improve it, possibly adding a new property to the BufferAttribute to identify it as a storage buffer read-only attribute in Renderer + + if ( value.isInstancedBufferAttribute ) value.isStorageInstancedBufferAttribute = true; + else value.isStorageBufferAttribute = true; + + } + + } + + /** + * This method is overwritten since the buffer data might be shared + * and thus the hash should be shared as well. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The hash. + */ + getHash( builder ) { + + if ( this.bufferCount === 0 ) { + + let bufferData = builder.globalCache.getData( this.value ); + + if ( bufferData === undefined ) { + + bufferData = { + node: this + }; + + builder.globalCache.setData( this.value, bufferData ); + + } + + return bufferData.node.uuid; + + } + + return this.uuid; + + } + + /** + * Overwrites the default implementation to return a fixed value `'indirectStorageBuffer'` or `'storageBuffer'`. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The input type. + */ + getInputType( /*builder*/ ) { + + return this.value.isIndirectStorageBufferAttribute ? 'indirectStorageBuffer' : 'storageBuffer'; + + } + + /** + * Enables element access with the given index node. + * + * @param {IndexNode} indexNode - The index node. + * @return {StorageArrayElementNode} A node representing the element access. + */ + element( indexNode ) { + + return storageElement( this, indexNode ); + + } + + /** + * Defines whether this node is a PBO or not. Only relevant for WebGL. + * + * @param {boolean} value - The value so set. + * @return {StorageBufferNode} A reference to this node. + */ + setPBO( value ) { + + this.isPBO = value; + + return this; + + } + + /** + * Returns the `isPBO` value. + * + * @return {boolean} Whether the node represents a PBO or not. + */ + getPBO() { + + return this.isPBO; + + } + + /** + * Defines the node access. + * + * @param {string} value - The node access. + * @return {StorageBufferNode} A reference to this node. + */ + setAccess( value ) { + + this.access = value; + + return this; + + } + + /** + * Convenience method for configuring a read-only node access. + * + * @return {StorageBufferNode} A reference to this node. + */ + toReadOnly() { + + return this.setAccess( NodeAccess.READ_ONLY ); + + } + + /** + * Defines whether the node is atomic or not. + * + * @param {boolean} value - The atomic flag. + * @return {StorageBufferNode} A reference to this node. + */ + setAtomic( value ) { + + this.isAtomic = value; + + return this; + + } + + /** + * Convenience method for making this node atomic. + * + * @return {StorageBufferNode} A reference to this node. + */ + toAtomic() { + + return this.setAtomic( true ); + + } + + /** + * Returns attribute data for this storage buffer node. + * + * @return {{attribute: BufferAttributeNode, varying: VaryingNode}} The attribute data. + */ + getAttributeData() { + + if ( this._attribute === null ) { + + this._attribute = bufferAttribute( this.value ); + this._varying = varying( this._attribute ); + + } + + return { + attribute: this._attribute, + varying: this._varying + }; + + } + + /** + * This method is overwritten since the node type from the availability of storage buffers + * and the attribute data. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType( builder ) { + + if ( this.structTypeNode !== null ) { + + return this.structTypeNode.getNodeType( builder ); + + } + + if ( builder.isAvailable( 'storageBuffer' ) || builder.isAvailable( 'indirectStorageBuffer' ) ) { + + return super.getNodeType( builder ); + + } + + const { attribute } = this.getAttributeData(); + + return attribute.getNodeType( builder ); + + } + + /** + * Returns the type of a member of the struct. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {string} name - The name of the member. + * @return {string} The type of the member. + */ + getMemberType( builder, name ) { + + if ( this.structTypeNode !== null ) { + + return this.structTypeNode.getMemberType( builder, name ); + + } + + return 'void'; + + } + + /** + * Generates the code snippet of the storage buffer node. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The generated code snippet. + */ + generate( builder ) { + + if ( this.structTypeNode !== null ) this.structTypeNode.build( builder ); + + if ( builder.isAvailable( 'storageBuffer' ) || builder.isAvailable( 'indirectStorageBuffer' ) ) { + + return super.generate( builder ); + + } + + const { attribute, varying } = this.getAttributeData(); + + const output = varying.build( builder ); + + builder.registerTransform( output, attribute ); + + return output; + + } + +} + +/** + * TSL function for creating a storage buffer node. + * + * @tsl + * @function + * @param {StorageBufferAttribute|StorageInstancedBufferAttribute|BufferAttribute} value - The buffer data. + * @param {?(string|Struct)} [type=null] - The buffer type (e.g. `'vec3'`). + * @param {number} [count=0] - The buffer count. + * @returns {StorageBufferNode} + */ +const storage = ( value, type = null, count = 0 ) => nodeObject( new StorageBufferNode( value, type, count ) ); + +/** + * @tsl + * @function + * @deprecated since r171. Use `storage().setPBO( true )` instead. + * + * @param {StorageBufferAttribute|StorageInstancedBufferAttribute|BufferAttribute} value - The buffer data. + * @param {?string} type - The buffer type (e.g. `'vec3'`). + * @param {number} count - The buffer count. + * @returns {StorageBufferNode} + */ +const storageObject = ( value, type, count ) => { // @deprecated, r171 + + console.warn( 'THREE.TSL: "storageObject()" is deprecated. Use "storage().setPBO( true )" instead.' ); + + return storage( value, type, count ).setPBO( true ); + +}; + +const _frameId = new WeakMap(); + +/** + * This node implements the vertex transformation shader logic which is required + * for skinning/skeletal animation. + * + * @augments Node + */ +class SkinningNode extends Node { + + static get type() { + + return 'SkinningNode'; + + } + + /** + * Constructs a new skinning node. + * + * @param {SkinnedMesh} skinnedMesh - The skinned mesh. + */ + constructor( skinnedMesh ) { + + super( 'void' ); + + /** + * The skinned mesh. + * + * @type {SkinnedMesh} + */ + this.skinnedMesh = skinnedMesh; + + /** + * The update type overwritten since skinning nodes are updated per object. + * + * @type {string} + */ + this.updateType = NodeUpdateType.OBJECT; + + // + + /** + * The skin index attribute. + * + * @type {AttributeNode} + */ + this.skinIndexNode = attribute( 'skinIndex', 'uvec4' ); + + /** + * The skin weight attribute. + * + * @type {AttributeNode} + */ + this.skinWeightNode = attribute( 'skinWeight', 'vec4' ); + + /** + * The bind matrix node. + * + * @type {Node} + */ + this.bindMatrixNode = reference( 'bindMatrix', 'mat4' ); + + /** + * The bind matrix inverse node. + * + * @type {Node} + */ + this.bindMatrixInverseNode = reference( 'bindMatrixInverse', 'mat4' ); + + /** + * The bind matrices as a uniform buffer node. + * + * @type {Node} + */ + this.boneMatricesNode = referenceBuffer( 'skeleton.boneMatrices', 'mat4', skinnedMesh.skeleton.bones.length ); + + /** + * The current vertex position in local space. + * + * @type {Node} + */ + this.positionNode = positionLocal; + + /** + * The result of vertex position in local space. + * + * @type {Node} + */ + this.toPositionNode = positionLocal; + + /** + * The previous bind matrices as a uniform buffer node. + * Required for computing motion vectors. + * + * @type {?Node} + * @default null + */ + this.previousBoneMatricesNode = null; + + } + + /** + * Transforms the given vertex position via skinning. + * + * @param {Node} [boneMatrices=this.boneMatricesNode] - The bone matrices + * @param {Node} [position=this.positionNode] - The vertex position in local space. + * @return {Node} The transformed vertex position. + */ + getSkinnedPosition( boneMatrices = this.boneMatricesNode, position = this.positionNode ) { + + const { skinIndexNode, skinWeightNode, bindMatrixNode, bindMatrixInverseNode } = this; + + const boneMatX = boneMatrices.element( skinIndexNode.x ); + const boneMatY = boneMatrices.element( skinIndexNode.y ); + const boneMatZ = boneMatrices.element( skinIndexNode.z ); + const boneMatW = boneMatrices.element( skinIndexNode.w ); + + // POSITION + + const skinVertex = bindMatrixNode.mul( position ); + + const skinned = add( + boneMatX.mul( skinWeightNode.x ).mul( skinVertex ), + boneMatY.mul( skinWeightNode.y ).mul( skinVertex ), + boneMatZ.mul( skinWeightNode.z ).mul( skinVertex ), + boneMatW.mul( skinWeightNode.w ).mul( skinVertex ) + ); + + return bindMatrixInverseNode.mul( skinned ).xyz; + + } + + /** + * Transforms the given vertex normal via skinning. + * + * @param {Node} [boneMatrices=this.boneMatricesNode] - The bone matrices + * @param {Node} [normal=normalLocal] - The vertex normal in local space. + * @return {Node} The transformed vertex normal. + */ + getSkinnedNormal( boneMatrices = this.boneMatricesNode, normal = normalLocal ) { + + const { skinIndexNode, skinWeightNode, bindMatrixNode, bindMatrixInverseNode } = this; + + const boneMatX = boneMatrices.element( skinIndexNode.x ); + const boneMatY = boneMatrices.element( skinIndexNode.y ); + const boneMatZ = boneMatrices.element( skinIndexNode.z ); + const boneMatW = boneMatrices.element( skinIndexNode.w ); + + // NORMAL + + let skinMatrix = add( + skinWeightNode.x.mul( boneMatX ), + skinWeightNode.y.mul( boneMatY ), + skinWeightNode.z.mul( boneMatZ ), + skinWeightNode.w.mul( boneMatW ) + ); + + skinMatrix = bindMatrixInverseNode.mul( skinMatrix ).mul( bindMatrixNode ); + + return skinMatrix.transformDirection( normal ).xyz; + + } + + /** + * Computes the transformed/skinned vertex position of the previous frame. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {Node} The skinned position from the previous frame. + */ + getPreviousSkinnedPosition( builder ) { + + const skinnedMesh = builder.object; + + if ( this.previousBoneMatricesNode === null ) { + + skinnedMesh.skeleton.previousBoneMatrices = new Float32Array( skinnedMesh.skeleton.boneMatrices ); + + this.previousBoneMatricesNode = referenceBuffer( 'skeleton.previousBoneMatrices', 'mat4', skinnedMesh.skeleton.bones.length ); + + } + + return this.getSkinnedPosition( this.previousBoneMatricesNode, positionPrevious ); + + } + + /** + * Returns `true` if bone matrices from the previous frame are required. Relevant + * when computing motion vectors with {@link VelocityNode}. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {boolean} Whether bone matrices from the previous frame are required or not. + */ + needsPreviousBoneMatrices( builder ) { + + const mrt = builder.renderer.getMRT(); + + return ( mrt && mrt.has( 'velocity' ) ) || getDataFromObject( builder.object ).useVelocity === true; + + } + + /** + * Setups the skinning node by assigning the transformed vertex data to predefined node variables. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {Node} The transformed vertex position. + */ + setup( builder ) { + + if ( this.needsPreviousBoneMatrices( builder ) ) { + + positionPrevious.assign( this.getPreviousSkinnedPosition( builder ) ); + + } + + const skinPosition = this.getSkinnedPosition(); + + if ( this.toPositionNode ) this.toPositionNode.assign( skinPosition ); + + // + + if ( builder.hasGeometryAttribute( 'normal' ) ) { + + const skinNormal = this.getSkinnedNormal(); + + normalLocal.assign( skinNormal ); + + if ( builder.hasGeometryAttribute( 'tangent' ) ) { + + tangentLocal.assign( skinNormal ); + + } + + } + + return skinPosition; + + } + + /** + * Generates the code snippet of the skinning node. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {string} output - The current output. + * @return {string} The generated code snippet. + */ + generate( builder, output ) { + + if ( output !== 'void' ) { + + return super.generate( builder, output ); + + } + + } + + /** + * Updates the state of the skinned mesh by updating the skeleton once per frame. + * + * @param {NodeFrame} frame - The current node frame. + */ + update( frame ) { + + const skeleton = frame.object && frame.object.skeleton ? frame.object.skeleton : this.skinnedMesh.skeleton; + + if ( _frameId.get( skeleton ) === frame.frameId ) return; + + _frameId.set( skeleton, frame.frameId ); + + if ( this.previousBoneMatricesNode !== null ) skeleton.previousBoneMatrices.set( skeleton.boneMatrices ); + + skeleton.update(); + + } + +} + +/** + * TSL function for creating a skinning node. + * + * @tsl + * @function + * @param {SkinnedMesh} skinnedMesh - The skinned mesh. + * @returns {SkinningNode} + */ +const skinning = ( skinnedMesh ) => nodeObject( new SkinningNode( skinnedMesh ) ); + +/** + * TSL function for computing skinning. + * + * @tsl + * @function + * @param {SkinnedMesh} skinnedMesh - The skinned mesh. + * @param {Node} [toPosition=null] - The target position. + * @returns {SkinningNode} + */ +const computeSkinning = ( skinnedMesh, toPosition = null ) => { + + const node = new SkinningNode( skinnedMesh ); + node.positionNode = storage( new InstancedBufferAttribute( skinnedMesh.geometry.getAttribute( 'position' ).array, 3 ), 'vec3' ).setPBO( true ).toReadOnly().element( instanceIndex ).toVar(); + node.skinIndexNode = storage( new InstancedBufferAttribute( new Uint32Array( skinnedMesh.geometry.getAttribute( 'skinIndex' ).array ), 4 ), 'uvec4' ).setPBO( true ).toReadOnly().element( instanceIndex ).toVar(); + node.skinWeightNode = storage( new InstancedBufferAttribute( skinnedMesh.geometry.getAttribute( 'skinWeight' ).array, 4 ), 'vec4' ).setPBO( true ).toReadOnly().element( instanceIndex ).toVar(); + node.bindMatrixNode = uniform( skinnedMesh.bindMatrix, 'mat4' ); + node.bindMatrixInverseNode = uniform( skinnedMesh.bindMatrixInverse, 'mat4' ); + node.boneMatricesNode = buffer( skinnedMesh.skeleton.boneMatrices, 'mat4', skinnedMesh.skeleton.bones.length ); + node.toPositionNode = toPosition; + + return nodeObject( node ); + +}; + +/** + * This module offers a variety of ways to implement loops in TSL. In it's basic form it's: + * ```js + * Loop( count, ( { i } ) => { + * + * } ); + * ``` + * However, it is also possible to define a start and end ranges, data types and loop conditions: + * ```js + * Loop( { start: int( 0 ), end: int( 10 ), type: 'int', condition: '<' }, ( { i } ) => { + * + * } ); + *``` + * Nested loops can be defined in a compacted form: + * ```js + * Loop( 10, 5, ( { i, j } ) => { + * + * } ); + * ``` + * Loops that should run backwards can be defined like so: + * ```js + * Loop( { start: 10 }, () => {} ); + * ``` + * It is possible to execute with boolean values, similar to the `while` syntax. + * ```js + * const value = float( 0 ).toVar(); + * + * Loop( value.lessThan( 10 ), () => { + * + * value.addAssign( 1 ); + * + * } ); + * ``` + * The module also provides `Break()` and `Continue()` TSL expression for loop control. + * @augments Node + */ +class LoopNode extends Node { + + static get type() { + + return 'LoopNode'; + + } + + /** + * Constructs a new loop node. + * + * @param {Array} params - Depending on the loop type, array holds different parameterization values for the loop. + */ + constructor( params = [] ) { + + super(); + + this.params = params; + + } + + /** + * Returns a loop variable name based on an index. The pattern is + * `0` = `i`, `1`= `j`, `2`= `k` and so on. + * + * @param {number} index - The index. + * @return {string} The loop variable name. + */ + getVarName( index ) { + + return String.fromCharCode( 'i'.charCodeAt( 0 ) + index ); + + } + + /** + * Returns properties about this node. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {Object} The node properties. + */ + getProperties( builder ) { + + const properties = builder.getNodeProperties( this ); + + if ( properties.stackNode !== undefined ) return properties; + + // + + const inputs = {}; + + for ( let i = 0, l = this.params.length - 1; i < l; i ++ ) { + + const param = this.params[ i ]; + + const name = ( param.isNode !== true && param.name ) || this.getVarName( i ); + const type = ( param.isNode !== true && param.type ) || 'int'; + + inputs[ name ] = expression( name, type ); + + } + + const stack = builder.addStack(); // TODO: cache() it + + properties.returnsNode = this.params[ this.params.length - 1 ]( inputs, builder ); + properties.stackNode = stack; + + const baseParam = this.params[ 0 ]; + + if ( baseParam.isNode !== true && typeof baseParam.update === 'function' ) { + + properties.updateNode = Fn( this.params[ 0 ].update )( inputs ); + + } + + builder.removeStack(); + + return properties; + + } + + /** + * This method is overwritten since the node type is inferred based on the loop configuration. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType( builder ) { + + const { returnsNode } = this.getProperties( builder ); + + return returnsNode ? returnsNode.getNodeType( builder ) : 'void'; + + } + + setup( builder ) { + + // setup properties + + this.getProperties( builder ); + + } + + generate( builder ) { + + const properties = this.getProperties( builder ); + + const params = this.params; + const stackNode = properties.stackNode; + + for ( let i = 0, l = params.length - 1; i < l; i ++ ) { + + const param = params[ i ]; + + let isWhile = false, start = null, end = null, name = null, type = null, condition = null, update = null; + + if ( param.isNode ) { + + if ( param.getNodeType( builder ) === 'bool' ) { + + isWhile = true; + type = 'bool'; + end = param.build( builder, type ); + + } else { + + type = 'int'; + name = this.getVarName( i ); + start = '0'; + end = param.build( builder, type ); + condition = '<'; + + } + + } else { + + type = param.type || 'int'; + name = param.name || this.getVarName( i ); + start = param.start; + end = param.end; + condition = param.condition; + update = param.update; + + if ( typeof start === 'number' ) start = builder.generateConst( type, start ); + else if ( start && start.isNode ) start = start.build( builder, type ); + + if ( typeof end === 'number' ) end = builder.generateConst( type, end ); + else if ( end && end.isNode ) end = end.build( builder, type ); + + if ( start !== undefined && end === undefined ) { + + start = start + ' - 1'; + end = '0'; + condition = '>='; + + } else if ( end !== undefined && start === undefined ) { + + start = '0'; + condition = '<'; + + } + + if ( condition === undefined ) { + + if ( Number( start ) > Number( end ) ) { + + condition = '>='; + + } else { + + condition = '<'; + + } + + } + + } + + let loopSnippet; + + if ( isWhile ) { + + loopSnippet = `while ( ${ end } )`; + + } else { + + const internalParam = { start, end}; + + // + + const startSnippet = internalParam.start; + const endSnippet = internalParam.end; + + let updateSnippet; + + const deltaOperator = () => condition.includes( '<' ) ? '+=' : '-='; + + if ( update !== undefined && update !== null ) { + + switch ( typeof update ) { + + case 'function': + + const flow = builder.flowStagesNode( properties.updateNode, 'void' ); + const snippet = flow.code.replace( /\t|;/g, '' ); + + updateSnippet = snippet; + + break; + + case 'number': + + updateSnippet = name + ' ' + deltaOperator() + ' ' + builder.generateConst( type, update ); + + break; + + case 'string': + + updateSnippet = name + ' ' + update; + + break; + + default: + + if ( update.isNode ) { + + updateSnippet = name + ' ' + deltaOperator() + ' ' + update.build( builder ); + + } else { + + console.error( 'THREE.TSL: \'Loop( { update: ... } )\' is not a function, string or number.' ); + + updateSnippet = 'break /* invalid update */'; + + } + + } + + } else { + + if ( type === 'int' || type === 'uint' ) { + + update = condition.includes( '<' ) ? '++' : '--'; + + } else { + + update = deltaOperator() + ' 1.'; + + } + + updateSnippet = name + ' ' + update; + + } + + const declarationSnippet = builder.getVar( type, name ) + ' = ' + startSnippet; + const conditionalSnippet = name + ' ' + condition + ' ' + endSnippet; + + loopSnippet = `for ( ${ declarationSnippet }; ${ conditionalSnippet }; ${ updateSnippet } )`; + + } + + builder.addFlowCode( ( i === 0 ? '\n' : '' ) + builder.tab + loopSnippet + ' {\n\n' ).addFlowTab(); + + } + + const stackSnippet = stackNode.build( builder, 'void' ); + + const returnsSnippet = properties.returnsNode ? properties.returnsNode.build( builder ) : ''; + + builder.removeFlowTab().addFlowCode( '\n' + builder.tab + stackSnippet ); + + for ( let i = 0, l = this.params.length - 1; i < l; i ++ ) { + + builder.addFlowCode( ( i === 0 ? '' : builder.tab ) + '}\n\n' ).removeFlowTab(); + + } + + builder.addFlowTab(); + + return returnsSnippet; + + } + +} + +/** + * TSL function for creating a loop node. + * + * @tsl + * @function + * @param {...any} params - A list of parameters. + * @returns {LoopNode} + */ +const Loop = ( ...params ) => nodeObject( new LoopNode( nodeArray( params, 'int' ) ) ).toStack(); + +/** + * TSL function for creating a `Continue()` expression. + * + * @tsl + * @function + * @returns {ExpressionNode} + */ +const Continue = () => expression( 'continue' ).toStack(); + +/** + * TSL function for creating a `Break()` expression. + * + * @tsl + * @function + * @returns {ExpressionNode} + */ +const Break = () => expression( 'break' ).toStack(); + +const _morphTextures = /*@__PURE__*/ new WeakMap(); +const _morphVec4 = /*@__PURE__*/ new Vector4(); + +const getMorph = /*@__PURE__*/ Fn( ( { bufferMap, influence, stride, width, depth, offset } ) => { + + const texelIndex = int( vertexIndex ).mul( stride ).add( offset ); + + const y = texelIndex.div( width ); + const x = texelIndex.sub( y.mul( width ) ); + + const bufferAttrib = textureLoad( bufferMap, ivec2( x, y ) ).depth( depth ).xyz; + + return bufferAttrib.mul( influence ); + +} ); + +function getEntry( geometry ) { + + const hasMorphPosition = geometry.morphAttributes.position !== undefined; + const hasMorphNormals = geometry.morphAttributes.normal !== undefined; + const hasMorphColors = geometry.morphAttributes.color !== undefined; + + // instead of using attributes, the WebGL 2 code path encodes morph targets + // into an array of data textures. Each layer represents a single morph target. + + const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color; + const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0; + + let entry = _morphTextures.get( geometry ); + + if ( entry === undefined || entry.count !== morphTargetsCount ) { + + if ( entry !== undefined ) entry.texture.dispose(); + + const morphTargets = geometry.morphAttributes.position || []; + const morphNormals = geometry.morphAttributes.normal || []; + const morphColors = geometry.morphAttributes.color || []; + + let vertexDataCount = 0; + + if ( hasMorphPosition === true ) vertexDataCount = 1; + if ( hasMorphNormals === true ) vertexDataCount = 2; + if ( hasMorphColors === true ) vertexDataCount = 3; + + let width = geometry.attributes.position.count * vertexDataCount; + let height = 1; + + const maxTextureSize = 4096; // @TODO: Use 'capabilities.maxTextureSize' + + if ( width > maxTextureSize ) { + + height = Math.ceil( width / maxTextureSize ); + width = maxTextureSize; + + } + + const buffer = new Float32Array( width * height * 4 * morphTargetsCount ); + + const bufferTexture = new DataArrayTexture( buffer, width, height, morphTargetsCount ); + bufferTexture.type = FloatType; + bufferTexture.needsUpdate = true; + + // fill buffer + + const vertexDataStride = vertexDataCount * 4; + + for ( let i = 0; i < morphTargetsCount; i ++ ) { + + const morphTarget = morphTargets[ i ]; + const morphNormal = morphNormals[ i ]; + const morphColor = morphColors[ i ]; + + const offset = width * height * 4 * i; + + for ( let j = 0; j < morphTarget.count; j ++ ) { + + const stride = j * vertexDataStride; + + if ( hasMorphPosition === true ) { + + _morphVec4.fromBufferAttribute( morphTarget, j ); + + buffer[ offset + stride + 0 ] = _morphVec4.x; + buffer[ offset + stride + 1 ] = _morphVec4.y; + buffer[ offset + stride + 2 ] = _morphVec4.z; + buffer[ offset + stride + 3 ] = 0; + + } + + if ( hasMorphNormals === true ) { + + _morphVec4.fromBufferAttribute( morphNormal, j ); + + buffer[ offset + stride + 4 ] = _morphVec4.x; + buffer[ offset + stride + 5 ] = _morphVec4.y; + buffer[ offset + stride + 6 ] = _morphVec4.z; + buffer[ offset + stride + 7 ] = 0; + + } + + if ( hasMorphColors === true ) { + + _morphVec4.fromBufferAttribute( morphColor, j ); + + buffer[ offset + stride + 8 ] = _morphVec4.x; + buffer[ offset + stride + 9 ] = _morphVec4.y; + buffer[ offset + stride + 10 ] = _morphVec4.z; + buffer[ offset + stride + 11 ] = ( morphColor.itemSize === 4 ) ? _morphVec4.w : 1; + + } + + } + + } + + entry = { + count: morphTargetsCount, + texture: bufferTexture, + stride: vertexDataCount, + size: new Vector2( width, height ) + }; + + _morphTextures.set( geometry, entry ); + + function disposeTexture() { + + bufferTexture.dispose(); + + _morphTextures.delete( geometry ); + + geometry.removeEventListener( 'dispose', disposeTexture ); + + } + + geometry.addEventListener( 'dispose', disposeTexture ); + + } + + return entry; + +} + +/** + * This node implements the vertex transformation shader logic which is required + * for morph target animation. + * + * @augments Node + */ +class MorphNode extends Node { + + static get type() { + + return 'MorphNode'; + + } + + /** + * Constructs a new morph node. + * + * @param {Mesh} mesh - The mesh holding the morph targets. + */ + constructor( mesh ) { + + super( 'void' ); + + /** + * The mesh holding the morph targets. + * + * @type {Mesh} + */ + this.mesh = mesh; + + /** + * A uniform node which represents the morph base influence value. + * + * @type {UniformNode} + */ + this.morphBaseInfluence = uniform( 1 ); + + /** + * The update type overwritten since morph nodes are updated per object. + * + * @type {string} + */ + this.updateType = NodeUpdateType.OBJECT; + + } + + /** + * Setups the morph node by assigning the transformed vertex data to predefined node variables. + * + * @param {NodeBuilder} builder - The current node builder. + */ + setup( builder ) { + + const { geometry } = builder; + + const hasMorphPosition = geometry.morphAttributes.position !== undefined; + const hasMorphNormals = geometry.hasAttribute( 'normal' ) && geometry.morphAttributes.normal !== undefined; + + const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color; + const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0; + + // nodes + + const { texture: bufferMap, stride, size } = getEntry( geometry ); + + if ( hasMorphPosition === true ) positionLocal.mulAssign( this.morphBaseInfluence ); + if ( hasMorphNormals === true ) normalLocal.mulAssign( this.morphBaseInfluence ); + + const width = int( size.width ); + + Loop( morphTargetsCount, ( { i } ) => { + + const influence = float( 0 ).toVar(); + + if ( this.mesh.count > 1 && ( this.mesh.morphTexture !== null && this.mesh.morphTexture !== undefined ) ) { + + influence.assign( textureLoad( this.mesh.morphTexture, ivec2( int( i ).add( 1 ), int( instanceIndex ) ) ).r ); + + } else { + + influence.assign( reference( 'morphTargetInfluences', 'float' ).element( i ).toVar() ); + + } + + If( influence.notEqual( 0 ), () => { + + if ( hasMorphPosition === true ) { + + positionLocal.addAssign( getMorph( { + bufferMap, + influence, + stride, + width, + depth: i, + offset: int( 0 ) + } ) ); + + } + + if ( hasMorphNormals === true ) { + + normalLocal.addAssign( getMorph( { + bufferMap, + influence, + stride, + width, + depth: i, + offset: int( 1 ) + } ) ); + + } + + } ); + + } ); + + } + + /** + * Updates the state of the morphed mesh by updating the base influence. + * + * @param {NodeFrame} frame - The current node frame. + */ + update( /*frame*/ ) { + + const morphBaseInfluence = this.morphBaseInfluence; + + if ( this.mesh.geometry.morphTargetsRelative ) { + + morphBaseInfluence.value = 1; + + } else { + + morphBaseInfluence.value = 1 - this.mesh.morphTargetInfluences.reduce( ( a, b ) => a + b, 0 ); + + } + + } + +} + +/** + * TSL function for creating a morph node. + * + * @tsl + * @function + * @param {Mesh} mesh - The mesh holding the morph targets. + * @returns {MorphNode} + */ +const morphReference = /*@__PURE__*/ nodeProxy( MorphNode ).setParameterLength( 1 ); + +/** + * Base class for lighting nodes. + * + * @augments Node + */ +class LightingNode extends Node { + + static get type() { + + return 'LightingNode'; + + } + + /** + * Constructs a new lighting node. + */ + constructor() { + + super( 'vec3' ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLightingNode = true; + + } + +} + +/** + * A generic class that can be used by nodes which contribute + * ambient occlusion to the scene. E.g. an ambient occlusion map + * node can be used as input for this module. Used in {@link NodeMaterial}. + * + * @augments LightingNode + */ +class AONode extends LightingNode { + + static get type() { + + return 'AONode'; + + } + + /** + * Constructs a new AO node. + * + * @param {?Node} [aoNode=null] - The ambient occlusion node. + */ + constructor( aoNode = null ) { + + super(); + + /** + * The ambient occlusion node. + * + * @type {?Node} + * @default null + */ + this.aoNode = aoNode; + + } + + setup( builder ) { + + builder.context.ambientOcclusion.mulAssign( this.aoNode ); + + } + +} + +/** + * `LightingContextNode` represents an extension of the {@link ContextNode} module + * by adding lighting specific context data. It represents the runtime context of + * {@link LightsNode}. + * + * @augments ContextNode + */ +class LightingContextNode extends ContextNode { + + static get type() { + + return 'LightingContextNode'; + + } + + /** + * Constructs a new lighting context node. + * + * @param {LightsNode} lightsNode - The lights node. + * @param {?LightingModel} [lightingModel=null] - The current lighting model. + * @param {?Node} [backdropNode=null] - A backdrop node. + * @param {?Node} [backdropAlphaNode=null] - A backdrop alpha node. + */ + constructor( lightsNode, lightingModel = null, backdropNode = null, backdropAlphaNode = null ) { + + super( lightsNode ); + + /** + * The current lighting model. + * + * @type {?LightingModel} + * @default null + */ + this.lightingModel = lightingModel; + + /** + * A backdrop node. + * + * @type {?Node} + * @default null + */ + this.backdropNode = backdropNode; + + /** + * A backdrop alpha node. + * + * @type {?Node} + * @default null + */ + this.backdropAlphaNode = backdropAlphaNode; + + this._value = null; + + } + + /** + * Returns a lighting context object. + * + * @return {{ + * radiance: Node, + * irradiance: Node, + * iblIrradiance: Node, + * ambientOcclusion: Node, + * reflectedLight: {directDiffuse: Node, directSpecular: Node, indirectDiffuse: Node, indirectSpecular: Node}, + * backdrop: Node, + * backdropAlpha: Node + * }} The lighting context object. + */ + getContext() { + + const { backdropNode, backdropAlphaNode } = this; + + const directDiffuse = vec3().toVar( 'directDiffuse' ), + directSpecular = vec3().toVar( 'directSpecular' ), + indirectDiffuse = vec3().toVar( 'indirectDiffuse' ), + indirectSpecular = vec3().toVar( 'indirectSpecular' ); + + const reflectedLight = { + directDiffuse, + directSpecular, + indirectDiffuse, + indirectSpecular + }; + + const context = { + radiance: vec3().toVar( 'radiance' ), + irradiance: vec3().toVar( 'irradiance' ), + iblIrradiance: vec3().toVar( 'iblIrradiance' ), + ambientOcclusion: float( 1 ).toVar( 'ambientOcclusion' ), + reflectedLight, + backdrop: backdropNode, + backdropAlpha: backdropAlphaNode + }; + + return context; + + } + + setup( builder ) { + + this.value = this._value || ( this._value = this.getContext() ); + this.value.lightingModel = this.lightingModel || builder.context.lightingModel; + + return super.setup( builder ); + + } + +} + +const lightingContext = /*@__PURE__*/ nodeProxy( LightingContextNode ); + +/** + * A generic class that can be used by nodes which contribute + * irradiance to the scene. E.g. a light map node can be used + * as input for this module. Used in {@link NodeMaterial}. + * + * @augments LightingNode + */ +class IrradianceNode extends LightingNode { + + static get type() { + + return 'IrradianceNode'; + + } + + /** + * Constructs a new irradiance node. + * + * @param {Node} node - A node contributing irradiance. + */ + constructor( node ) { + + super(); + + /** + * A node contributing irradiance. + * + * @type {Node} + */ + this.node = node; + + } + + setup( builder ) { + + builder.context.irradiance.addAssign( this.node ); + + } + +} + +let screenSizeVec, viewportVec; + +/** + * This node provides a collection of screen related metrics. + * Depending on {@link ScreenNode#scope}, the nodes can represent + * resolution or viewport data as well as fragment or uv coordinates. + * + * @augments Node + */ +class ScreenNode extends Node { + + static get type() { + + return 'ScreenNode'; + + } + + /** + * Constructs a new screen node. + * + * @param {('coordinate'|'viewport'|'size'|'uv')} scope - The node's scope. + */ + constructor( scope ) { + + super(); + + /** + * The node represents different metric depending on which scope is selected. + * + * - `ScreenNode.COORDINATE`: Window-relative coordinates of the current fragment according to WebGPU standards. + * - `ScreenNode.VIEWPORT`: The current viewport defined as a four-dimensional vector. + * - `ScreenNode.SIZE`: The dimensions of the current bound framebuffer. + * - `ScreenNode.UV`: Normalized coordinates. + * + * @type {('coordinate'|'viewport'|'size'|'uv')} + */ + this.scope = scope; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isViewportNode = true; + + } + + /** + * This method is overwritten since the node type depends on the selected scope. + * + * @return {('vec2'|'vec4')} The node type. + */ + getNodeType() { + + if ( this.scope === ScreenNode.VIEWPORT ) return 'vec4'; + else return 'vec2'; + + } + + /** + * This method is overwritten since the node's update type depends on the selected scope. + * + * @return {NodeUpdateType} The update type. + */ + getUpdateType() { + + let updateType = NodeUpdateType.NONE; + + if ( this.scope === ScreenNode.SIZE || this.scope === ScreenNode.VIEWPORT ) { + + updateType = NodeUpdateType.RENDER; + + } + + this.updateType = updateType; + + return updateType; + + } + + /** + * `ScreenNode` implements {@link Node#update} to retrieve viewport and size information + * from the current renderer. + * + * @param {NodeFrame} frame - A reference to the current node frame. + */ + update( { renderer } ) { + + const renderTarget = renderer.getRenderTarget(); + + if ( this.scope === ScreenNode.VIEWPORT ) { + + if ( renderTarget !== null ) { + + viewportVec.copy( renderTarget.viewport ); + + } else { + + renderer.getViewport( viewportVec ); + + viewportVec.multiplyScalar( renderer.getPixelRatio() ); + + } + + } else { + + if ( renderTarget !== null ) { + + screenSizeVec.width = renderTarget.width; + screenSizeVec.height = renderTarget.height; + + } else { + + renderer.getDrawingBufferSize( screenSizeVec ); + + } + + } + + } + + setup( /*builder*/ ) { + + const scope = this.scope; + + let output = null; + + if ( scope === ScreenNode.SIZE ) { + + output = uniform( screenSizeVec || ( screenSizeVec = new Vector2() ) ); + + } else if ( scope === ScreenNode.VIEWPORT ) { + + output = uniform( viewportVec || ( viewportVec = new Vector4() ) ); + + } else { + + output = vec2( screenCoordinate.div( screenSize ) ); + + } + + return output; + + } + + generate( builder ) { + + if ( this.scope === ScreenNode.COORDINATE ) { + + let coord = builder.getFragCoord(); + + if ( builder.isFlipY() ) { + + // follow webgpu standards + + const size = builder.getNodeProperties( screenSize ).outputNode.build( builder ); + + coord = `${ builder.getType( 'vec2' ) }( ${ coord }.x, ${ size }.y - ${ coord }.y )`; + + } + + return coord; + + } + + return super.generate( builder ); + + } + +} + +ScreenNode.COORDINATE = 'coordinate'; +ScreenNode.VIEWPORT = 'viewport'; +ScreenNode.SIZE = 'size'; +ScreenNode.UV = 'uv'; + +// Screen + +/** + * TSL object that represents normalized screen coordinates, unitless in `[0, 1]`. + * + * @tsl + * @type {ScreenNode} + */ +const screenUV = /*@__PURE__*/ nodeImmutable( ScreenNode, ScreenNode.UV ); + +/** + * TSL object that represents the screen resolution in physical pixel units. + * + * @tsl + * @type {ScreenNode} + */ +const screenSize = /*@__PURE__*/ nodeImmutable( ScreenNode, ScreenNode.SIZE ); + +/** + * TSL object that represents the current `x`/`y` pixel position on the screen in physical pixel units. + * + * @tsl + * @type {ScreenNode} + */ +const screenCoordinate = /*@__PURE__*/ nodeImmutable( ScreenNode, ScreenNode.COORDINATE ); + +// Viewport + +/** + * TSL object that represents the viewport rectangle as `x`, `y`, `width` and `height` in physical pixel units. + * + * @tsl + * @type {ScreenNode} + */ +const viewport = /*@__PURE__*/ nodeImmutable( ScreenNode, ScreenNode.VIEWPORT ); + +/** + * TSL object that represents the viewport resolution in physical pixel units. + * + * @tsl + * @type {ScreenNode} + */ +const viewportSize = viewport.zw; + +/** + * TSL object that represents the current `x`/`y` pixel position on the viewport in physical pixel units. + * + * @tsl + * @type {ScreenNode} + */ +const viewportCoordinate = /*@__PURE__*/ screenCoordinate.sub( viewport.xy ); + +/** + * TSL object that represents normalized viewport coordinates, unitless in `[0, 1]`. + * + * @tsl + * @type {ScreenNode} + */ +const viewportUV = /*@__PURE__*/ viewportCoordinate.div( viewportSize ); + +// Deprecated + +/** + * @deprecated since r169. Use {@link screenSize} instead. + */ +const viewportResolution = /*@__PURE__*/ ( Fn( () => { // @deprecated, r169 + + console.warn( 'THREE.TSL: "viewportResolution" is deprecated. Use "screenSize" instead.' ); + + return screenSize; + +}, 'vec2' ).once() )(); + +const _size$4 = /*@__PURE__*/ new Vector2(); + +/** + * A special type of texture node which represents the data of the current viewport + * as a texture. The module extracts data from the current bound framebuffer with + * a copy operation so no extra render pass is required to produce the texture data + * (which is good for performance). `ViewportTextureNode` can be used as an input for a + * variety of effects like refractive or transmissive materials. + * + * @augments TextureNode + */ +class ViewportTextureNode extends TextureNode { + + static get type() { + + return 'ViewportTextureNode'; + + } + + /** + * Constructs a new viewport texture node. + * + * @param {Node} [uvNode=screenUV] - The uv node. + * @param {?Node} [levelNode=null] - The level node. + * @param {?Texture} [framebufferTexture=null] - A framebuffer texture holding the viewport data. If not provided, a framebuffer texture is created automatically. + */ + constructor( uvNode = screenUV, levelNode = null, framebufferTexture = null ) { + + if ( framebufferTexture === null ) { + + framebufferTexture = new FramebufferTexture(); + framebufferTexture.minFilter = LinearMipmapLinearFilter; + + } + + super( framebufferTexture, uvNode, levelNode ); + + /** + * Whether to generate mipmaps or not. + * + * @type {boolean} + * @default false + */ + this.generateMipmaps = false; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isOutputTextureNode = true; + + /** + * The `updateBeforeType` is set to `NodeUpdateType.FRAME` since the node renders the + * scene once per frame in its {@link ViewportTextureNode#updateBefore} method. + * + * @type {string} + * @default 'frame' + */ + this.updateBeforeType = NodeUpdateType.FRAME; + + } + + updateBefore( frame ) { + + const renderer = frame.renderer; + renderer.getDrawingBufferSize( _size$4 ); + + // + + const framebufferTexture = this.value; + + if ( framebufferTexture.image.width !== _size$4.width || framebufferTexture.image.height !== _size$4.height ) { + + framebufferTexture.image.width = _size$4.width; + framebufferTexture.image.height = _size$4.height; + framebufferTexture.needsUpdate = true; + + } + + // + + const currentGenerateMipmaps = framebufferTexture.generateMipmaps; + framebufferTexture.generateMipmaps = this.generateMipmaps; + + renderer.copyFramebufferToTexture( framebufferTexture ); + + framebufferTexture.generateMipmaps = currentGenerateMipmaps; + + } + + clone() { + + const viewportTextureNode = new this.constructor( this.uvNode, this.levelNode, this.value ); + viewportTextureNode.generateMipmaps = this.generateMipmaps; + + return viewportTextureNode; + + } + +} + +/** + * TSL function for creating a viewport texture node. + * + * @tsl + * @function + * @param {?Node} [uvNode=screenUV] - The uv node. + * @param {?Node} [levelNode=null] - The level node. + * @param {?Texture} [framebufferTexture=null] - A framebuffer texture holding the viewport data. If not provided, a framebuffer texture is created automatically. + * @returns {ViewportTextureNode} + */ +const viewportTexture = /*@__PURE__*/ nodeProxy( ViewportTextureNode ).setParameterLength( 0, 3 ); + +/** + * TSL function for creating a viewport texture node with enabled mipmap generation. + * + * @tsl + * @function + * @param {?Node} [uvNode=screenUV] - The uv node. + * @param {?Node} [levelNode=null] - The level node. + * @param {?Texture} [framebufferTexture=null] - A framebuffer texture holding the viewport data. If not provided, a framebuffer texture is created automatically. + * @returns {ViewportTextureNode} + */ +const viewportMipTexture = /*@__PURE__*/ nodeProxy( ViewportTextureNode, null, null, { generateMipmaps: true } ).setParameterLength( 0, 3 ); + +let sharedDepthbuffer = null; + +/** + * Represents the depth of the current viewport as a texture. This module + * can be used in combination with viewport texture to achieve effects + * that require depth evaluation. + * + * @augments ViewportTextureNode + */ +class ViewportDepthTextureNode extends ViewportTextureNode { + + static get type() { + + return 'ViewportDepthTextureNode'; + + } + + /** + * Constructs a new viewport depth texture node. + * + * @param {Node} [uvNode=screenUV] - The uv node. + * @param {?Node} [levelNode=null] - The level node. + */ + constructor( uvNode = screenUV, levelNode = null ) { + + if ( sharedDepthbuffer === null ) { + + sharedDepthbuffer = new DepthTexture(); + + } + + super( uvNode, levelNode, sharedDepthbuffer ); + + } + +} + +/** + * TSL function for a viewport depth texture node. + * + * @tsl + * @function + * @param {?Node} [uvNode=screenUV] - The uv node. + * @param {?Node} [levelNode=null] - The level node. + * @returns {ViewportDepthTextureNode} + */ +const viewportDepthTexture = /*@__PURE__*/ nodeProxy( ViewportDepthTextureNode ).setParameterLength( 0, 2 ); + +/** + * This node offers a collection of features in context of the depth logic in the fragment shader. + * Depending on {@link ViewportDepthNode#scope}, it can be used to define a depth value for the current + * fragment or for depth evaluation purposes. + * + * @augments Node + */ +class ViewportDepthNode extends Node { + + static get type() { + + return 'ViewportDepthNode'; + + } + + /** + * Constructs a new viewport depth node. + * + * @param {('depth'|'depthBase'|'linearDepth')} scope - The node's scope. + * @param {?Node} [valueNode=null] - The value node. + */ + constructor( scope, valueNode = null ) { + + super( 'float' ); + + /** + * The node behaves differently depending on which scope is selected. + * + * - `ViewportDepthNode.DEPTH_BASE`: Allows to define a value for the current fragment's depth. + * - `ViewportDepthNode.DEPTH`: Represents the depth value for the current fragment (`valueNode` is ignored). + * - `ViewportDepthNode.LINEAR_DEPTH`: Represents the linear (orthographic) depth value of the current fragment. + * If a `valueNode` is set, the scope can be used to convert perspective depth data to linear data. + * + * @type {('depth'|'depthBase'|'linearDepth')} + */ + this.scope = scope; + + /** + * Can be used to define a custom depth value. + * The property is ignored in the `ViewportDepthNode.DEPTH` scope. + * + * @type {?Node} + * @default null + */ + this.valueNode = valueNode; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isViewportDepthNode = true; + + } + + generate( builder ) { + + const { scope } = this; + + if ( scope === ViewportDepthNode.DEPTH_BASE ) { + + return builder.getFragDepth(); + + } + + return super.generate( builder ); + + } + + setup( { camera } ) { + + const { scope } = this; + const value = this.valueNode; + + let node = null; + + if ( scope === ViewportDepthNode.DEPTH_BASE ) { + + if ( value !== null ) { + + node = depthBase().assign( value ); + + } + + } else if ( scope === ViewportDepthNode.DEPTH ) { + + if ( camera.isPerspectiveCamera ) { + + node = viewZToPerspectiveDepth( positionView.z, cameraNear, cameraFar ); + + } else { + + node = viewZToOrthographicDepth( positionView.z, cameraNear, cameraFar ); + + } + + } else if ( scope === ViewportDepthNode.LINEAR_DEPTH ) { + + if ( value !== null ) { + + if ( camera.isPerspectiveCamera ) { + + const viewZ = perspectiveDepthToViewZ( value, cameraNear, cameraFar ); + + node = viewZToOrthographicDepth( viewZ, cameraNear, cameraFar ); + + } else { + + node = value; + + } + + } else { + + node = viewZToOrthographicDepth( positionView.z, cameraNear, cameraFar ); + + } + + } + + return node; + + } + +} + +ViewportDepthNode.DEPTH_BASE = 'depthBase'; +ViewportDepthNode.DEPTH = 'depth'; +ViewportDepthNode.LINEAR_DEPTH = 'linearDepth'; + +// NOTE: viewZ, the z-coordinate in camera space, is negative for points in front of the camera + +/** + * TSL function for converting a viewZ value to an orthographic depth value. + * + * @tsl + * @function + * @param {Node} viewZ - The viewZ node. + * @param {Node} near - The camera's near value. + * @param {Node} far - The camera's far value. + * @returns {Node} + */ +const viewZToOrthographicDepth = ( viewZ, near, far ) => viewZ.add( near ).div( near.sub( far ) ); + +/** + * TSL function for converting an orthographic depth value to a viewZ value. + * + * @tsl + * @function + * @param {Node} depth - The orthographic depth. + * @param {Node} near - The camera's near value. + * @param {Node} far - The camera's far value. + * @returns {Node} + */ +const orthographicDepthToViewZ = ( depth, near, far ) => near.sub( far ).mul( depth ).sub( near ); + +/** + * TSL function for converting a viewZ value to a perspective depth value. + * + * Note: {link https://twitter.com/gonnavis/status/1377183786949959682}. + * + * @tsl + * @function + * @param {Node} viewZ - The viewZ node. + * @param {Node} near - The camera's near value. + * @param {Node} far - The camera's far value. + * @returns {Node} + */ +const viewZToPerspectiveDepth = ( viewZ, near, far ) => near.add( viewZ ).mul( far ).div( far.sub( near ).mul( viewZ ) ); + +/** + * TSL function for converting a perspective depth value to a viewZ value. + * + * @tsl + * @function + * @param {Node} depth - The perspective depth. + * @param {Node} near - The camera's near value. + * @param {Node} far - The camera's far value. + * @returns {Node} + */ +const perspectiveDepthToViewZ = ( depth, near, far ) => near.mul( far ).div( far.sub( near ).mul( depth ).sub( far ) ); + +/** + * TSL function for converting a viewZ value to a logarithmic depth value. + * + * @tsl + * @function + * @param {Node} viewZ - The viewZ node. + * @param {Node} near - The camera's near value. + * @param {Node} far - The camera's far value. + * @returns {Node} + */ +const viewZToLogarithmicDepth = ( viewZ, near, far ) => { + + // NOTE: viewZ must be negative--see explanation at the end of this comment block. + // The final logarithmic depth formula used here is adapted from one described in an + // article by Thatcher Ulrich (see http://tulrich.com/geekstuff/log_depth_buffer.txt), + // which was an improvement upon an earlier formula one described in an + // Outerra article (https://outerra.blogspot.com/2009/08/logarithmic-z-buffer.html). + // Ulrich's formula is the following: + // z = K * log( w / cameraNear ) / log( cameraFar / cameraNear ) + // where K = 2^k - 1, and k is the number of bits in the depth buffer. + // The Outerra variant ignored the camera near plane (it assumed it was 0) and instead + // opted for a "C-constant" for resolution adjustment of objects near the camera. + // Outerra states: "Notice that the 'C' variant doesn’t use a near plane distance, it has it + // set at 0" (quote from https://outerra.blogspot.com/2012/11/maximizing-depth-buffer-range-and.html). + // Ulrich's variant has the benefit of constant relative precision over the whole near-far range. + // It was debated here whether Outerra's "C-constant" or Ulrich's "near plane" variant should + // be used, and ultimately Ulrich's "near plane" version was chosen. + // Outerra eventually made another improvement to their original "C-constant" variant, + // but it still does not incorporate the camera near plane (for this version, + // see https://outerra.blogspot.com/2013/07/logarithmic-depth-buffer-optimizations.html). + // Here we make 4 changes to Ulrich's formula: + // 1. Clamp the camera near plane so we don't divide by 0. + // 2. Use log2 instead of log to avoid an extra multiply (shaders implement log using log2). + // 3. Assume K is 1 (K = maximum value in depth buffer; see Ulrich's formula above). + // 4. To maintain consistency with the functions "viewZToOrthographicDepth" and "viewZToPerspectiveDepth", + // we modify the formula here to use 'viewZ' instead of 'w'. The other functions expect a negative viewZ, + // so we do the same here, hence the 'viewZ.negate()' call. + // For visual representation of this depth curve, see https://www.desmos.com/calculator/uyqk0vex1u + near = near.max( 1e-6 ).toVar(); + const numerator = log2( viewZ.negate().div( near ) ); + const denominator = log2( far.div( near ) ); + return numerator.div( denominator ); + +}; + +/** + * TSL function for converting a logarithmic depth value to a viewZ value. + * + * @tsl + * @function + * @param {Node} depth - The logarithmic depth. + * @param {Node} near - The camera's near value. + * @param {Node} far - The camera's far value. + * @returns {Node} + */ +const logarithmicDepthToViewZ = ( depth, near, far ) => { + + // NOTE: we add a 'negate()' call to the return value here to maintain consistency with + // the functions "orthographicDepthToViewZ" and "perspectiveDepthToViewZ" (they return + // a negative viewZ). + const exponent = depth.mul( log( far.div( near ) ) ); + return float( Math.E ).pow( exponent ).mul( near ).negate(); + +}; + +/** + * TSL function for defining a value for the current fragment's depth. + * + * @tsl + * @function + * @param {Node} value - The depth value to set. + * @returns {ViewportDepthNode} + */ +const depthBase = /*@__PURE__*/ nodeProxy( ViewportDepthNode, ViewportDepthNode.DEPTH_BASE ); + +/** + * TSL object that represents the depth value for the current fragment. + * + * @tsl + * @type {ViewportDepthNode} + */ +const depth = /*@__PURE__*/ nodeImmutable( ViewportDepthNode, ViewportDepthNode.DEPTH ); + +/** + * TSL function for converting a perspective depth value to linear depth. + * + * @tsl + * @function + * @param {?Node} [value=null] - The perspective depth. If `null` is provided, the current fragment's depth is used. + * @returns {ViewportDepthNode} + */ +const linearDepth = /*@__PURE__*/ nodeProxy( ViewportDepthNode, ViewportDepthNode.LINEAR_DEPTH ).setParameterLength( 0, 1 ); + +/** + * TSL object that represents the linear (orthographic) depth value of the current fragment + * + * @tsl + * @type {ViewportDepthNode} + */ +const viewportLinearDepth = /*@__PURE__*/ linearDepth( viewportDepthTexture() ); + +depth.assign = ( value ) => depthBase( value ); + +/** + * This node is used in {@link NodeMaterial} to setup the clipping + * which can happen hardware-accelerated (if supported) and optionally + * use alpha-to-coverage for anti-aliasing clipped edges. + * + * @augments Node + */ +class ClippingNode extends Node { + + static get type() { + + return 'ClippingNode'; + + } + + /** + * Constructs a new clipping node. + * + * @param {('default'|'hardware'|'alphaToCoverage')} [scope='default'] - The node's scope. Similar to other nodes, + * the selected scope influences the behavior of the node and what type of code is generated. + */ + constructor( scope = ClippingNode.DEFAULT ) { + + super(); + + /** + * The node's scope. Similar to other nodes, the selected scope influences + * the behavior of the node and what type of code is generated. + * + * @type {('default'|'hardware'|'alphaToCoverage')} + */ + this.scope = scope; + + } + + /** + * Setups the node depending on the selected scope. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {Node} The result node. + */ + setup( builder ) { + + super.setup( builder ); + + const clippingContext = builder.clippingContext; + const { intersectionPlanes, unionPlanes } = clippingContext; + + this.hardwareClipping = builder.material.hardwareClipping; + + if ( this.scope === ClippingNode.ALPHA_TO_COVERAGE ) { + + return this.setupAlphaToCoverage( intersectionPlanes, unionPlanes ); + + } else if ( this.scope === ClippingNode.HARDWARE ) { + + return this.setupHardwareClipping( unionPlanes, builder ); + + } else { + + return this.setupDefault( intersectionPlanes, unionPlanes ); + + } + + } + + /** + * Setups alpha to coverage. + * + * @param {Array} intersectionPlanes - The intersection planes. + * @param {Array} unionPlanes - The union planes. + * @return {Node} The result node. + */ + setupAlphaToCoverage( intersectionPlanes, unionPlanes ) { + + return Fn( () => { + + const distanceToPlane = float().toVar( 'distanceToPlane' ); + const distanceGradient = float().toVar( 'distanceToGradient' ); + + const clipOpacity = float( 1 ).toVar( 'clipOpacity' ); + + const numUnionPlanes = unionPlanes.length; + + if ( this.hardwareClipping === false && numUnionPlanes > 0 ) { + + const clippingPlanes = uniformArray( unionPlanes ); + + Loop( numUnionPlanes, ( { i } ) => { + + const plane = clippingPlanes.element( i ); + + distanceToPlane.assign( positionView.dot( plane.xyz ).negate().add( plane.w ) ); + distanceGradient.assign( distanceToPlane.fwidth().div( 2.0 ) ); + + clipOpacity.mulAssign( smoothstep( distanceGradient.negate(), distanceGradient, distanceToPlane ) ); + + } ); + + } + + const numIntersectionPlanes = intersectionPlanes.length; + + if ( numIntersectionPlanes > 0 ) { + + const clippingPlanes = uniformArray( intersectionPlanes ); + const intersectionClipOpacity = float( 1 ).toVar( 'intersectionClipOpacity' ); + + Loop( numIntersectionPlanes, ( { i } ) => { + + const plane = clippingPlanes.element( i ); + + distanceToPlane.assign( positionView.dot( plane.xyz ).negate().add( plane.w ) ); + distanceGradient.assign( distanceToPlane.fwidth().div( 2.0 ) ); + + intersectionClipOpacity.mulAssign( smoothstep( distanceGradient.negate(), distanceGradient, distanceToPlane ).oneMinus() ); + + } ); + + clipOpacity.mulAssign( intersectionClipOpacity.oneMinus() ); + + } + + diffuseColor.a.mulAssign( clipOpacity ); + + diffuseColor.a.equal( 0.0 ).discard(); + + } )(); + + } + + /** + * Setups the default clipping. + * + * @param {Array} intersectionPlanes - The intersection planes. + * @param {Array} unionPlanes - The union planes. + * @return {Node} The result node. + */ + setupDefault( intersectionPlanes, unionPlanes ) { + + return Fn( () => { + + const numUnionPlanes = unionPlanes.length; + + if ( this.hardwareClipping === false && numUnionPlanes > 0 ) { + + const clippingPlanes = uniformArray( unionPlanes ); + + Loop( numUnionPlanes, ( { i } ) => { + + const plane = clippingPlanes.element( i ); + positionView.dot( plane.xyz ).greaterThan( plane.w ).discard(); + + } ); + + } + + const numIntersectionPlanes = intersectionPlanes.length; + + if ( numIntersectionPlanes > 0 ) { + + const clippingPlanes = uniformArray( intersectionPlanes ); + const clipped = bool( true ).toVar( 'clipped' ); + + Loop( numIntersectionPlanes, ( { i } ) => { + + const plane = clippingPlanes.element( i ); + clipped.assign( positionView.dot( plane.xyz ).greaterThan( plane.w ).and( clipped ) ); + + } ); + + clipped.discard(); + + } + + } )(); + + } + + /** + * Setups hardware clipping. + * + * @param {Array} unionPlanes - The union planes. + * @param {NodeBuilder} builder - The current node builder. + * @return {Node} The result node. + */ + setupHardwareClipping( unionPlanes, builder ) { + + const numUnionPlanes = unionPlanes.length; + + builder.enableHardwareClipping( numUnionPlanes ); + + return Fn( () => { + + const clippingPlanes = uniformArray( unionPlanes ); + const hw_clip_distances = builtin( builder.getClipDistance() ); + + Loop( numUnionPlanes, ( { i } ) => { + + const plane = clippingPlanes.element( i ); + + const distance = positionView.dot( plane.xyz ).sub( plane.w ).negate(); + hw_clip_distances.element( i ).assign( distance ); + + } ); + + } )(); + + } + +} + +ClippingNode.ALPHA_TO_COVERAGE = 'alphaToCoverage'; +ClippingNode.DEFAULT = 'default'; +ClippingNode.HARDWARE = 'hardware'; + +/** + * TSL function for setting up the default clipping logic. + * + * @tsl + * @function + * @returns {ClippingNode} + */ +const clipping = () => nodeObject( new ClippingNode() ); + +/** + * TSL function for setting up alpha to coverage. + * + * @tsl + * @function + * @returns {ClippingNode} + */ +const clippingAlpha = () => nodeObject( new ClippingNode( ClippingNode.ALPHA_TO_COVERAGE ) ); + +/** + * TSL function for setting up hardware-based clipping. + * + * @tsl + * @function + * @returns {ClippingNode} + */ +const hardwareClipping = () => nodeObject( new ClippingNode( ClippingNode.HARDWARE ) ); + +// See: https://casual-effects.com/research/Wyman2017Hashed/index.html + +const ALPHA_HASH_SCALE = 0.05; // Derived from trials only, and may be changed. + +const hash2D = /*@__PURE__*/ Fn( ( [ value ] ) => { + + return fract( mul( 1.0e4, sin( mul( 17.0, value.x ).add( mul( 0.1, value.y ) ) ) ).mul( add( 0.1, abs( sin( mul( 13.0, value.y ).add( value.x ) ) ) ) ) ); + +} ); + +const hash3D = /*@__PURE__*/ Fn( ( [ value ] ) => { + + return hash2D( vec2( hash2D( value.xy ), value.z ) ); + +} ); + +const getAlphaHashThreshold = /*@__PURE__*/ Fn( ( [ position ] ) => { + + // Find the discretized derivatives of our coordinates + const maxDeriv = max$1( + length( dFdx( position.xyz ) ), + length( dFdy( position.xyz ) ) + ); + + const pixScale = float( 1 ).div( float( ALPHA_HASH_SCALE ).mul( maxDeriv ) ).toVar( 'pixScale' ); + + // Find two nearest log-discretized noise scales + const pixScales = vec2( + exp2( floor( log2( pixScale ) ) ), + exp2( ceil( log2( pixScale ) ) ) + ); + + // Compute alpha thresholds at our two noise scales + const alpha = vec2( + hash3D( floor( pixScales.x.mul( position.xyz ) ) ), + hash3D( floor( pixScales.y.mul( position.xyz ) ) ), + ); + + // Factor to interpolate lerp with + const lerpFactor = fract( log2( pixScale ) ); + + // Interpolate alpha threshold from noise at two scales + const x = add( mul( lerpFactor.oneMinus(), alpha.x ), mul( lerpFactor, alpha.y ) ); + + // Pass into CDF to compute uniformly distrib threshold + const a = min$1( lerpFactor, lerpFactor.oneMinus() ); + const cases = vec3( + x.mul( x ).div( mul( 2.0, a ).mul( sub( 1.0, a ) ) ), + x.sub( mul( 0.5, a ) ).div( sub( 1.0, a ) ), + sub( 1.0, sub( 1.0, x ).mul( sub( 1.0, x ) ).div( mul( 2.0, a ).mul( sub( 1.0, a ) ) ) ) ); + + // Find our final, uniformly distributed alpha threshold (ατ) + const threshold = x.lessThan( a.oneMinus() ).select( x.lessThan( a ).select( cases.x, cases.y ), cases.z ); + + // Avoids ατ == 0. Could also do ατ =1-ατ + return clamp( threshold, 1.0e-6, 1.0 ); + +} ).setLayout( { + name: 'getAlphaHashThreshold', + type: 'float', + inputs: [ + { name: 'position', type: 'vec3' } + ] +} ); + +/** + * An attribute node for representing vertex colors. + * + * @augments AttributeNode + */ +class VertexColorNode extends AttributeNode { + + static get type() { + + return 'VertexColorNode'; + + } + + /** + * Constructs a new vertex color node. + * + * @param {number} index - The attribute index. + */ + constructor( index ) { + + super( null, 'vec4' ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isVertexColorNode = true; + + /** + * The attribute index to enable more than one sets of vertex colors. + * + * @type {number} + * @default 0 + */ + this.index = index; + + } + + /** + * Overwrites the default implementation by honoring the attribute index. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The attribute name. + */ + getAttributeName( /*builder*/ ) { + + const index = this.index; + + return 'color' + ( index > 0 ? index : '' ); + + } + + generate( builder ) { + + const attributeName = this.getAttributeName( builder ); + const geometryAttribute = builder.hasGeometryAttribute( attributeName ); + + let result; + + if ( geometryAttribute === true ) { + + result = super.generate( builder ); + + } else { + + // Vertex color fallback should be white + result = builder.generateConst( this.nodeType, new Vector4( 1, 1, 1, 1 ) ); + + } + + return result; + + } + + serialize( data ) { + + super.serialize( data ); + + data.index = this.index; + + } + + deserialize( data ) { + + super.deserialize( data ); + + this.index = data.index; + + } + +} + +/** + * TSL function for creating a reference node. + * + * @tsl + * @function + * @param {number} [index=0] - The attribute index. + * @returns {VertexColorNode} + */ +const vertexColor = ( index = 0 ) => nodeObject( new VertexColorNode( index ) ); + +/** + * Represents a "Color Burn" blend mode. + * + * It's designed to darken the base layer's colors based on the color of the blend layer. + * It significantly increases the contrast of the base layer, making the colors more vibrant and saturated. + * The darker the color in the blend layer, the stronger the darkening and contrast effect on the base layer. + * + * @tsl + * @function + * @param {Node} base - The base color. + * @param {Node} blend - The blend color. A white (#ffffff) blend color does not alter the base color. + * @return {Node} The result. + */ +const blendBurn = /*@__PURE__*/ Fn( ( [ base, blend ] ) => { + + return min$1( 1.0, base.oneMinus().div( blend ) ).oneMinus(); + +} ).setLayout( { + name: 'blendBurn', + type: 'vec3', + inputs: [ + { name: 'base', type: 'vec3' }, + { name: 'blend', type: 'vec3' } + ] +} ); + +/** + * Represents a "Color Dodge" blend mode. + * + * It's designed to lighten the base layer's colors based on the color of the blend layer. + * It significantly increases the brightness of the base layer, making the colors lighter and more vibrant. + * The brighter the color in the blend layer, the stronger the lightening and contrast effect on the base layer. + * + * @tsl + * @function + * @param {Node} base - The base color. + * @param {Node} blend - The blend color. A black (#000000) blend color does not alter the base color. + * @return {Node} The result. + */ +const blendDodge = /*@__PURE__*/ Fn( ( [ base, blend ] ) => { + + return min$1( base.div( blend.oneMinus() ), 1.0 ); + +} ).setLayout( { + name: 'blendDodge', + type: 'vec3', + inputs: [ + { name: 'base', type: 'vec3' }, + { name: 'blend', type: 'vec3' } + ] +} ); + +/** + * Represents a "Screen" blend mode. + * + * Similar to `blendDodge()`, this mode also lightens the base layer's colors based on the color of the blend layer. + * The "Screen" blend mode is better for general brightening whereas the "Dodge" results in more subtle and nuanced + * effects. + * + * @tsl + * @function + * @param {Node} base - The base color. + * @param {Node} blend - The blend color. A black (#000000) blend color does not alter the base color. + * @return {Node} The result. + */ +const blendScreen = /*@__PURE__*/ Fn( ( [ base, blend ] ) => { + + return base.oneMinus().mul( blend.oneMinus() ).oneMinus(); + +} ).setLayout( { + name: 'blendScreen', + type: 'vec3', + inputs: [ + { name: 'base', type: 'vec3' }, + { name: 'blend', type: 'vec3' } + ] +} ); + +/** + * Represents a "Overlay" blend mode. + * + * It's designed to increase the contrast of the base layer based on the color of the blend layer. + * It amplifies the existing colors and contrast in the base layer, making lighter areas lighter and darker areas darker. + * The color of the blend layer significantly influences the resulting contrast and color shift in the base layer. + * + * @tsl + * @function + * @param {Node} base - The base color. + * @param {Node} blend - The blend color + * @return {Node} The result. + */ +const blendOverlay = /*@__PURE__*/ Fn( ( [ base, blend ] ) => { + + return mix( base.mul( 2.0 ).mul( blend ), base.oneMinus().mul( 2.0 ).mul( blend.oneMinus() ).oneMinus(), step( 0.5, base ) ); + +} ).setLayout( { + name: 'blendOverlay', + type: 'vec3', + inputs: [ + { name: 'base', type: 'vec3' }, + { name: 'blend', type: 'vec3' } + ] +} ); + +/** + * This function blends two color based on their alpha values by replicating the behavior of `THREE.NormalBlending`. + * It assumes both input colors have non-premultiplied alpha. + * + * @tsl + * @function + * @param {Node} base - The base color. + * @param {Node} blend - The blend color + * @return {Node} The result. + */ +const blendColor = /*@__PURE__*/ Fn( ( [ base, blend ] ) => { + + const outAlpha = blend.a.add( base.a.mul( blend.a.oneMinus() ) ); + + return vec4( blend.rgb.mul( blend.a ).add( base.rgb.mul( base.a ).mul( blend.a.oneMinus() ) ).div( outAlpha ), outAlpha ); + +} ).setLayout( { + name: 'blendColor', + type: 'vec4', + inputs: [ + { name: 'base', type: 'vec4' }, + { name: 'blend', type: 'vec4' } + ] +} ); + +/** + * Premultiplies the RGB channels of a color by its alpha channel. + * + * This function is useful for converting a non-premultiplied alpha color + * into a premultiplied alpha format, where the RGB values are scaled + * by the alpha value. Premultiplied alpha is often used in graphics + * rendering for certain operations, such as compositing and image processing. + * + * @tsl + * @function + * @param {Node} color - The input color with non-premultiplied alpha. + * @return {Node} The color with premultiplied alpha. + */ +const premultiplyAlpha = /*@__PURE__*/ Fn( ( [ color ] ) => { + + return vec4( color.rgb.mul( color.a ), color.a ); + +}, { color: 'vec4', return: 'vec4' } ); + +/** + * Unpremultiplies the RGB channels of a color by its alpha channel. + * + * This function is useful for converting a premultiplied alpha color + * back into a non-premultiplied alpha format, where the RGB values are + * divided by the alpha value. Unpremultiplied alpha is often used in graphics + * rendering for certain operations, such as compositing and image processing. + * + * @tsl + * @function + * @param {Node} color - The input color with premultiplied alpha. + * @return {Node} The color with non-premultiplied alpha. + */ +const unpremultiplyAlpha = /*@__PURE__*/ Fn( ( [ color ] ) => { + + If( color.a.equal( 0.0 ), () => vec4( 0.0 ) ); + + return vec4( color.rgb.div( color.a ), color.a ); + +}, { color: 'vec4', return: 'vec4' } ); + + +// Deprecated + +/** + * @tsl + * @function + * @deprecated since r171. Use {@link blendBurn} instead. + * + * @param {...any} params + * @returns {Function} + */ +const burn = ( ...params ) => { // @deprecated, r171 + + console.warn( 'THREE.TSL: "burn" has been renamed. Use "blendBurn" instead.' ); + return blendBurn( params ); + +}; + +/** + * @tsl + * @function + * @deprecated since r171. Use {@link blendDodge} instead. + * + * @param {...any} params + * @returns {Function} + */ +const dodge = ( ...params ) => { // @deprecated, r171 + + console.warn( 'THREE.TSL: "dodge" has been renamed. Use "blendDodge" instead.' ); + return blendDodge( params ); + +}; + +/** + * @tsl + * @function + * @deprecated since r171. Use {@link blendScreen} instead. + * + * @param {...any} params + * @returns {Function} + */ +const screen = ( ...params ) => { // @deprecated, r171 + + console.warn( 'THREE.TSL: "screen" has been renamed. Use "blendScreen" instead.' ); + return blendScreen( params ); + +}; + +/** + * @tsl + * @function + * @deprecated since r171. Use {@link blendOverlay} instead. + * + * @param {...any} params + * @returns {Function} + */ +const overlay = ( ...params ) => { // @deprecated, r171 + + console.warn( 'THREE.TSL: "overlay" has been renamed. Use "blendOverlay" instead.' ); + return blendOverlay( params ); + +}; + +/** + * Base class for all node materials. + * + * @augments Material + */ +class NodeMaterial extends Material { + + static get type() { + + return 'NodeMaterial'; + + } + + /** + * Represents the type of the node material. + * + * @type {string} + */ + get type() { + + return this.constructor.type; + + } + + set type( _value ) { /* */ } + + /** + * Constructs a new node material. + */ + constructor() { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isNodeMaterial = true; + + /** + * Whether this material is affected by fog or not. + * + * @type {boolean} + * @default true + */ + this.fog = true; + + /** + * Whether this material is affected by lights or not. + * + * @type {boolean} + * @default false + */ + this.lights = false; + + /** + * Whether this material uses hardware clipping or not. + * This property is managed by the engine and should not be + * modified by apps. + * + * @type {boolean} + * @default false + */ + this.hardwareClipping = false; + + /** + * Node materials which set their `lights` property to `true` + * are affected by all lights of the scene. Sometimes selective + * lighting is wanted which means only _some_ lights in the scene + * affect a material. This can be achieved by creating an instance + * of {@link LightsNode} with a list of selective + * lights and assign the node to this property. + * + * ```js + * const customLightsNode = lights( [ light1, light2 ] ); + * material.lightsNode = customLightsNode; + * ``` + * + * @type {?LightsNode} + * @default null + */ + this.lightsNode = null; + + /** + * The environment of node materials can be defined by an environment + * map assigned to the `envMap` property or by `Scene.environment` + * if the node material is a PBR material. This node property allows to overwrite + * the default behavior and define the environment with a custom node. + * + * ```js + * material.envNode = pmremTexture( renderTarget.texture ); + * ``` + * + * @type {?Node} + * @default null + */ + this.envNode = null; + + /** + * The lighting of node materials might be influenced by ambient occlusion. + * The default AO is inferred from an ambient occlusion map assigned to `aoMap` + * and the respective `aoMapIntensity`. This node property allows to overwrite + * the default and define the ambient occlusion with a custom node instead. + * + * If you don't want to overwrite the diffuse color but modify the existing + * values instead, use {@link materialAO}. + * + * @type {?Node} + * @default null + */ + this.aoNode = null; + + /** + * The diffuse color of node materials is by default inferred from the + * `color` and `map` properties. This node property allows to overwrite the default + * and define the diffuse color with a node instead. + * + * ```js + * material.colorNode = color( 0xff0000 ); // define red color + * ``` + * + * If you don't want to overwrite the diffuse color but modify the existing + * values instead, use {@link materialColor}. + * + * ```js + * material.colorNode = materialColor.mul( color( 0xff0000 ) ); // give diffuse colors a red tint + * ``` + * + * @type {?Node} + * @default null + */ + this.colorNode = null; + + /** + * The normals of node materials are by default inferred from the `normalMap`/`normalScale` + * or `bumpMap`/`bumpScale` properties. This node property allows to overwrite the default + * and define the normals with a node instead. + * + * If you don't want to overwrite the normals but modify the existing values instead, + * use {@link materialNormal}. + * + * @type {?Node} + * @default null + */ + this.normalNode = null; + + /** + * The opacity of node materials is by default inferred from the `opacity` + * and `alphaMap` properties. This node property allows to overwrite the default + * and define the opacity with a node instead. + * + * If you don't want to overwrite the normals but modify the existing + * value instead, use {@link materialOpacity}. + * + * @type {?Node} + * @default null + */ + this.opacityNode = null; + + /** + * This node can be used to implement a variety of filter-like effects. The idea is + * to store the current rendering into a texture e.g. via `viewportSharedTexture()`, use it + * to create an arbitrary effect and then assign the node composition to this property. + * Everything behind the object using this material will now be affected by a filter. + * + * ```js + * const material = new NodeMaterial() + * material.transparent = true; + * + * // everything behind the object will be monochromatic + * material.backdropNode = saturation( viewportSharedTexture().rgb, 0 ); + * ``` + * + * Backdrop computations are part of the lighting so only lit materials can use this property. + * + * @type {?Node} + * @default null + */ + this.backdropNode = null; + + /** + * This node allows to modulate the influence of `backdropNode` to the outgoing light. + * + * @type {?Node} + * @default null + */ + this.backdropAlphaNode = null; + + /** + * The alpha test of node materials is by default inferred from the `alphaTest` + * property. This node property allows to overwrite the default and define the + * alpha test with a node instead. + * + * If you don't want to overwrite the alpha test but modify the existing + * value instead, use {@link materialAlphaTest}. + * + * @type {?Node} + * @default null + */ + this.alphaTestNode = null; + + + /** + * Discards the fragment if the mask value is `false`. + * + * @type {?Node} + * @default null + */ + this.maskNode = null; + + /** + * The local vertex positions are computed based on multiple factors like the + * attribute data, morphing or skinning. This node property allows to overwrite + * the default and define local vertex positions with nodes instead. + * + * If you don't want to overwrite the vertex positions but modify the existing + * values instead, use {@link positionLocal}. + * + *```js + * material.positionNode = positionLocal.add( displace ); + * ``` + * + * @type {?Node} + * @default null + */ + this.positionNode = null; + + /** + * This node property is intended for logic which modifies geometry data once or per animation step. + * Apps usually place such logic randomly in initialization routines or in the animation loop. + * `geometryNode` is intended as a dedicated API so there is an intended spot where geometry modifications + * can be implemented. + * + * The idea is to assign a `Fn` definition that holds the geometry modification logic. A typical example + * would be a GPU based particle system that provides a node material for usage on app level. The particle + * simulation would be implemented as compute shaders and managed inside a `Fn` function. This function is + * eventually assigned to `geometryNode`. + * + * @type {?Function} + * @default null + */ + this.geometryNode = null; + + /** + * Allows to overwrite depth values in the fragment shader. + * + * @type {?Node} + * @default null + */ + this.depthNode = null; + + /** + * Allows to overwrite the position used for shadow map rendering which + * is by default {@link positionWorld}, the vertex position + * in world space. + * + * @type {?Node} + * @default null + */ + this.receivedShadowPositionNode = null; + + /** + * Allows to overwrite the geometry position used for shadow map projection which + * is by default {@link positionLocal}, the vertex position in local space. + * + * @type {?Node} + * @default null + */ + this.castShadowPositionNode = null; + + /** + * This node can be used to influence how an object using this node material + * receive shadows. + * + * ```js + * const totalShadows = float( 1 ).toVar(); + * material.receivedShadowNode = Fn( ( [ shadow ] ) => { + * totalShadows.mulAssign( shadow ); + * //return float( 1 ); // bypass received shadows + * return shadow.mix( color( 0xff0000 ), 1 ); // modify shadow color + * } ); + * + * @type {?(Function|FunctionNode)} + * @default null + */ + this.receivedShadowNode = null; + + /** + * This node can be used to influence how an object using this node material + * casts shadows. To apply a color to shadows, you can simply do: + * + * ```js + * material.castShadowNode = vec4( 1, 0, 0, 1 ); + * ``` + * + * Which can be nice to fake colored shadows of semi-transparent objects. It + * is also common to use the property with `Fn` function so checks are performed + * per fragment. + * + * ```js + * materialCustomShadow.castShadowNode = Fn( () => { + * hash( vertexIndex ).greaterThan( 0.5 ).discard(); + * return materialColor; + * } )(); + * ``` + * + * @type {?Node} + * @default null + */ + this.castShadowNode = null; + + /** + * This node can be used to define the final output of the material. + * + * TODO: Explain the differences to `fragmentNode`. + * + * @type {?Node} + * @default null + */ + this.outputNode = null; + + /** + * MRT configuration is done on renderer or pass level. This node allows to + * overwrite what values are written into MRT targets on material level. This + * can be useful for implementing selective FX features that should only affect + * specific objects. + * + * @type {?MRTNode} + * @default null + */ + this.mrtNode = null; + + /** + * This node property can be used if you need complete freedom in implementing + * the fragment shader. Assigning a node will replace the built-in material + * logic used in the fragment stage. + * + * @type {?Node} + * @default null + */ + this.fragmentNode = null; + + /** + * This node property can be used if you need complete freedom in implementing + * the vertex shader. Assigning a node will replace the built-in material logic + * used in the vertex stage. + * + * @type {?Node} + * @default null + */ + this.vertexNode = null; + + // Deprecated properties + + Object.defineProperty( this, 'shadowPositionNode', { // @deprecated, r176 + + get: () => { + + return this.receivedShadowPositionNode; + + }, + + set: ( value ) => { + + console.warn( 'THREE.NodeMaterial: ".shadowPositionNode" was renamed to ".receivedShadowPositionNode".' ); + + this.receivedShadowPositionNode = value; + + } + + } ); + + } + + /** + * Allows to define a custom cache key that influence the material key computation + * for render objects. + * + * @return {string} The custom cache key. + */ + customProgramCacheKey() { + + return this.type + getCacheKey$1( this ); + + } + + /** + * Builds this material with the given node builder. + * + * @param {NodeBuilder} builder - The current node builder. + */ + build( builder ) { + + this.setup( builder ); + + } + + /** + * Setups a node material observer with the given builder. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {NodeMaterialObserver} The node material observer. + */ + setupObserver( builder ) { + + return new NodeMaterialObserver( builder ); + + } + + /** + * Setups the vertex and fragment stage of this node material. + * + * @param {NodeBuilder} builder - The current node builder. + */ + setup( builder ) { + + builder.context.setupNormal = () => subBuild( this.setupNormal( builder ), 'NORMAL', 'vec3' ); + builder.context.setupPositionView = () => this.setupPositionView( builder ); + builder.context.setupModelViewProjection = () => this.setupModelViewProjection( builder ); + + const renderer = builder.renderer; + const renderTarget = renderer.getRenderTarget(); + + // < VERTEX STAGE > + + builder.addStack(); + + const mvp = subBuild( this.setupVertex( builder ), 'VERTEX' ); + + const vertexNode = this.vertexNode || mvp; + + builder.stack.outputNode = vertexNode; + + this.setupHardwareClipping( builder ); + + if ( this.geometryNode !== null ) { + + builder.stack.outputNode = builder.stack.outputNode.bypass( this.geometryNode ); + + } + + builder.addFlow( 'vertex', builder.removeStack() ); + + // < FRAGMENT STAGE > + + builder.addStack(); + + let resultNode; + + const clippingNode = this.setupClipping( builder ); + + if ( this.depthWrite === true || this.depthTest === true ) { + + // only write depth if depth buffer is configured + + if ( renderTarget !== null ) { + + if ( renderTarget.depthBuffer === true ) this.setupDepth( builder ); + + } else { + + if ( renderer.depth === true ) this.setupDepth( builder ); + + } + + } + + if ( this.fragmentNode === null ) { + + this.setupDiffuseColor( builder ); + this.setupVariants( builder ); + + const outgoingLightNode = this.setupLighting( builder ); + + if ( clippingNode !== null ) builder.stack.add( clippingNode ); + + // force unsigned floats - useful for RenderTargets + + const basicOutput = vec4( outgoingLightNode, diffuseColor.a ).max( 0 ); + + resultNode = this.setupOutput( builder, basicOutput ); + + // OUTPUT NODE + + output.assign( resultNode ); + + // + + const isCustomOutput = this.outputNode !== null; + + if ( isCustomOutput ) resultNode = this.outputNode; + + // MRT + + if ( renderTarget !== null ) { + + const mrt = renderer.getMRT(); + const materialMRT = this.mrtNode; + + if ( mrt !== null ) { + + if ( isCustomOutput ) output.assign( resultNode ); + + resultNode = mrt; + + if ( materialMRT !== null ) { + + resultNode = mrt.merge( materialMRT ); + + } + + } else if ( materialMRT !== null ) { + + resultNode = materialMRT; + + } + + } + + } else { + + let fragmentNode = this.fragmentNode; + + if ( fragmentNode.isOutputStructNode !== true ) { + + fragmentNode = vec4( fragmentNode ); + + } + + resultNode = this.setupOutput( builder, fragmentNode ); + + } + + builder.stack.outputNode = resultNode; + + builder.addFlow( 'fragment', builder.removeStack() ); + + // < OBSERVER > + + builder.observer = this.setupObserver( builder ); + + } + + /** + * Setups the clipping node. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {ClippingNode} The clipping node. + */ + setupClipping( builder ) { + + if ( builder.clippingContext === null ) return null; + + const { unionPlanes, intersectionPlanes } = builder.clippingContext; + + let result = null; + + if ( unionPlanes.length > 0 || intersectionPlanes.length > 0 ) { + + const samples = builder.renderer.samples; + + if ( this.alphaToCoverage && samples > 1 ) { + + // to be added to flow when the color/alpha value has been determined + result = clippingAlpha(); + + } else { + + builder.stack.add( clipping() ); + + } + + } + + return result; + + } + + /** + * Setups the hardware clipping if available on the current device. + * + * @param {NodeBuilder} builder - The current node builder. + */ + setupHardwareClipping( builder ) { + + this.hardwareClipping = false; + + if ( builder.clippingContext === null ) return; + + const candidateCount = builder.clippingContext.unionPlanes.length; + + // 8 planes supported by WebGL ANGLE_clip_cull_distance and WebGPU clip-distances + + if ( candidateCount > 0 && candidateCount <= 8 && builder.isAvailable( 'clipDistance' ) ) { + + builder.stack.add( hardwareClipping() ); + + this.hardwareClipping = true; + + } + + return; + + } + + /** + * Setups the depth of this material. + * + * @param {NodeBuilder} builder - The current node builder. + */ + setupDepth( builder ) { + + const { renderer, camera } = builder; + + // Depth + + let depthNode = this.depthNode; + + if ( depthNode === null ) { + + const mrt = renderer.getMRT(); + + if ( mrt && mrt.has( 'depth' ) ) { + + depthNode = mrt.get( 'depth' ); + + } else if ( renderer.logarithmicDepthBuffer === true ) { + + if ( camera.isPerspectiveCamera ) { + + depthNode = viewZToLogarithmicDepth( positionView.z, cameraNear, cameraFar ); + + } else { + + depthNode = viewZToOrthographicDepth( positionView.z, cameraNear, cameraFar ); + + } + + } + + } + + if ( depthNode !== null ) { + + depth.assign( depthNode ).toStack(); + + } + + } + + /** + * Setups the position node in view space. This method exists + * so derived node materials can modify the implementation e.g. sprite materials. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {Node} The position in view space. + */ + setupPositionView( /*builder*/ ) { + + return modelViewMatrix.mul( positionLocal ).xyz; + + } + + /** + * Setups the position in clip space. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {Node} The position in view space. + */ + setupModelViewProjection( /*builder*/ ) { + + return cameraProjectionMatrix.mul( positionView ); + + } + + /** + * Setups the logic for the vertex stage. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {Node} The position in clip space. + */ + setupVertex( builder ) { + + builder.addStack(); + + this.setupPosition( builder ); + + builder.context.vertex = builder.removeStack(); + + return modelViewProjection; + + } + + /** + * Setups the computation of the position in local space. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {Node} The position in local space. + */ + setupPosition( builder ) { + + const { object, geometry } = builder; + + if ( geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color ) { + + morphReference( object ).toStack(); + + } + + if ( object.isSkinnedMesh === true ) { + + skinning( object ).toStack(); + + } + + if ( this.displacementMap ) { + + const displacementMap = materialReference( 'displacementMap', 'texture' ); + const displacementScale = materialReference( 'displacementScale', 'float' ); + const displacementBias = materialReference( 'displacementBias', 'float' ); + + positionLocal.addAssign( normalLocal.normalize().mul( ( displacementMap.x.mul( displacementScale ).add( displacementBias ) ) ) ); + + } + + if ( object.isBatchedMesh ) { + + batch( object ).toStack(); + + } + + if ( ( object.isInstancedMesh && object.instanceMatrix && object.instanceMatrix.isInstancedBufferAttribute === true ) ) { + + instancedMesh( object ).toStack(); + + } + + if ( this.positionNode !== null ) { + + positionLocal.assign( subBuild( this.positionNode, 'POSITION', 'vec3' ) ); + + } + + return positionLocal; + + } + + /** + * Setups the computation of the material's diffuse color. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {BufferGeometry} geometry - The geometry. + */ + setupDiffuseColor( { object, geometry } ) { + + // MASK + + if ( this.maskNode !== null ) { + + // Discard if the mask is `false` + + bool( this.maskNode ).not().discard(); + + } + + // COLOR + + let colorNode = this.colorNode ? vec4( this.colorNode ) : materialColor; + + // VERTEX COLORS + + if ( this.vertexColors === true && geometry.hasAttribute( 'color' ) ) { + + colorNode = colorNode.mul( vertexColor() ); + + } + + // INSTANCED COLORS + + if ( object.instanceColor ) { + + const instanceColor = varyingProperty( 'vec3', 'vInstanceColor' ); + + colorNode = instanceColor.mul( colorNode ); + + } + + if ( object.isBatchedMesh && object._colorsTexture ) { + + const batchColor = varyingProperty( 'vec3', 'vBatchColor' ); + + colorNode = batchColor.mul( colorNode ); + + } + + // DIFFUSE COLOR + + diffuseColor.assign( colorNode ); + + // OPACITY + + const opacityNode = this.opacityNode ? float( this.opacityNode ) : materialOpacity; + diffuseColor.a.assign( diffuseColor.a.mul( opacityNode ) ); + + // ALPHA TEST + + let alphaTestNode = null; + + if ( this.alphaTestNode !== null || this.alphaTest > 0 ) { + + alphaTestNode = this.alphaTestNode !== null ? float( this.alphaTestNode ) : materialAlphaTest; + + diffuseColor.a.lessThanEqual( alphaTestNode ).discard(); + + } + + // ALPHA HASH + + if ( this.alphaHash === true ) { + + diffuseColor.a.lessThan( getAlphaHashThreshold( positionLocal ) ).discard(); + + } + + // OPAQUE + + const isOpaque = this.transparent === false && this.blending === NormalBlending && this.alphaToCoverage === false; + + if ( isOpaque ) { + + diffuseColor.a.assign( 1.0 ); + + } else if ( alphaTestNode === null ) { + + diffuseColor.a.lessThanEqual( 0 ).discard(); + + } + + } + + /** + * Abstract interface method that can be implemented by derived materials + * to setup material-specific node variables. + * + * @abstract + * @param {NodeBuilder} builder - The current node builder. + */ + setupVariants( /*builder*/ ) { + + // Interface function. + + } + + /** + * Setups the outgoing light node variable + * + * @return {Node} The outgoing light node. + */ + setupOutgoingLight() { + + return ( this.lights === true ) ? vec3( 0 ) : diffuseColor.rgb; + + } + + /** + * Setups the normal node from the material. + * + * @return {Node} The normal node. + */ + setupNormal() { + + return this.normalNode ? vec3( this.normalNode ) : materialNormal; + + } + + /** + * Setups the environment node from the material. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {Node} The environment node. + */ + setupEnvironment( /*builder*/ ) { + + let node = null; + + if ( this.envNode ) { + + node = this.envNode; + + } else if ( this.envMap ) { + + node = this.envMap.isCubeTexture ? materialReference( 'envMap', 'cubeTexture' ) : materialReference( 'envMap', 'texture' ); + + } + + return node; + + } + + /** + * Setups the light map node from the material. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {Node} The light map node. + */ + setupLightMap( builder ) { + + let node = null; + + if ( builder.material.lightMap ) { + + node = new IrradianceNode( materialLightMap ); + + } + + return node; + + } + + /** + * Setups the lights node based on the scene, environment and material. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {LightsNode} The lights node. + */ + setupLights( builder ) { + + const materialLightsNode = []; + + // + + const envNode = this.setupEnvironment( builder ); + + if ( envNode && envNode.isLightingNode ) { + + materialLightsNode.push( envNode ); + + } + + const lightMapNode = this.setupLightMap( builder ); + + if ( lightMapNode && lightMapNode.isLightingNode ) { + + materialLightsNode.push( lightMapNode ); + + } + + if ( this.aoNode !== null || builder.material.aoMap ) { + + const aoNode = this.aoNode !== null ? this.aoNode : materialAO; + + materialLightsNode.push( new AONode( aoNode ) ); + + } + + let lightsN = this.lightsNode || builder.lightsNode; + + if ( materialLightsNode.length > 0 ) { + + lightsN = builder.renderer.lighting.createNode( [ ...lightsN.getLights(), ...materialLightsNode ] ); + + } + + return lightsN; + + } + + /** + * This method should be implemented by most derived materials + * since it defines the material's lighting model. + * + * @abstract + * @param {NodeBuilder} builder - The current node builder. + * @return {LightingModel} The lighting model. + */ + setupLightingModel( /*builder*/ ) { + + // Interface function. + + } + + /** + * Setups the outgoing light node. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {Node} The outgoing light node. + */ + setupLighting( builder ) { + + const { material } = builder; + const { backdropNode, backdropAlphaNode, emissiveNode } = this; + + // OUTGOING LIGHT + + const lights = this.lights === true || this.lightsNode !== null; + + const lightsNode = lights ? this.setupLights( builder ) : null; + + let outgoingLightNode = this.setupOutgoingLight( builder ); + + if ( lightsNode && lightsNode.getScope().hasLights ) { + + const lightingModel = this.setupLightingModel( builder ) || null; + + outgoingLightNode = lightingContext( lightsNode, lightingModel, backdropNode, backdropAlphaNode ); + + } else if ( backdropNode !== null ) { + + outgoingLightNode = vec3( backdropAlphaNode !== null ? mix( outgoingLightNode, backdropNode, backdropAlphaNode ) : backdropNode ); + + } + + // EMISSIVE + + if ( ( emissiveNode && emissiveNode.isNode === true ) || ( material.emissive && material.emissive.isColor === true ) ) { + + emissive.assign( vec3( emissiveNode ? emissiveNode : materialEmissive ) ); + + outgoingLightNode = outgoingLightNode.add( emissive ); + + } + + return outgoingLightNode; + + } + + /** + * Setup the fog. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {Node} outputNode - The existing output node. + * @return {Node} The output node. + */ + setupFog( builder, outputNode ) { + + const fogNode = builder.fogNode; + + if ( fogNode ) { + + output.assign( outputNode ); + + outputNode = vec4( fogNode ); + + } + + return outputNode; + + } + + /** + * Setups premultiplied alpha. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {Node} outputNode - The existing output node. + * @return {Node} The output node. + */ + setupPremultipliedAlpha( builder, outputNode ) { + + return premultiplyAlpha( outputNode ); + + } + + /** + * Setups the output node. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {Node} outputNode - The existing output node. + * @return {Node} The output node. + */ + setupOutput( builder, outputNode ) { + + // FOG + + if ( this.fog === true ) { + + outputNode = this.setupFog( builder, outputNode ); + + } + + // PREMULTIPLIED ALPHA + + if ( this.premultipliedAlpha === true ) { + + outputNode = this.setupPremultipliedAlpha( builder, outputNode ); + + } + + return outputNode; + + } + + /** + * Most classic material types have a node pendant e.g. for `MeshBasicMaterial` + * there is `MeshBasicNodeMaterial`. This utility method is intended for + * defining all material properties of the classic type in the node type. + * + * @param {Material} material - The material to copy properties with their values to this node material. + */ + setDefaultValues( material ) { + + // This approach is to reuse the native refreshUniforms* + // and turn available the use of features like transmission and environment in core + + for ( const property in material ) { + + const value = material[ property ]; + + if ( this[ property ] === undefined ) { + + this[ property ] = value; + + if ( value && value.clone ) this[ property ] = value.clone(); + + } + + } + + const descriptors = Object.getOwnPropertyDescriptors( material.constructor.prototype ); + + for ( const key in descriptors ) { + + if ( Object.getOwnPropertyDescriptor( this.constructor.prototype, key ) === undefined && + descriptors[ key ].get !== undefined ) { + + Object.defineProperty( this.constructor.prototype, key, descriptors[ key ] ); + + } + + } + + } + + /** + * Serializes this material to JSON. + * + * @param {?(Object|string)} meta - The meta information for serialization. + * @return {Object} The serialized node. + */ + toJSON( meta ) { + + const isRoot = ( meta === undefined || typeof meta === 'string' ); + + if ( isRoot ) { + + meta = { + textures: {}, + images: {}, + nodes: {} + }; + + } + + const data = Material.prototype.toJSON.call( this, meta ); + const nodeChildren = getNodeChildren( this ); + + data.inputNodes = {}; + + for ( const { property, childNode } of nodeChildren ) { + + data.inputNodes[ property ] = childNode.toJSON( meta ).uuid; + + } + + // TODO: Copied from Object3D.toJSON + + function extractFromCache( cache ) { + + const values = []; + + for ( const key in cache ) { + + const data = cache[ key ]; + delete data.metadata; + values.push( data ); + + } + + return values; + + } + + if ( isRoot ) { + + const textures = extractFromCache( meta.textures ); + const images = extractFromCache( meta.images ); + const nodes = extractFromCache( meta.nodes ); + + if ( textures.length > 0 ) data.textures = textures; + if ( images.length > 0 ) data.images = images; + if ( nodes.length > 0 ) data.nodes = nodes; + + } + + return data; + + } + + /** + * Copies the properties of the given node material to this instance. + * + * @param {NodeMaterial} source - The material to copy. + * @return {NodeMaterial} A reference to this node material. + */ + copy( source ) { + + this.lightsNode = source.lightsNode; + this.envNode = source.envNode; + + this.colorNode = source.colorNode; + this.normalNode = source.normalNode; + this.opacityNode = source.opacityNode; + this.backdropNode = source.backdropNode; + this.backdropAlphaNode = source.backdropAlphaNode; + this.alphaTestNode = source.alphaTestNode; + this.maskNode = source.maskNode; + + this.positionNode = source.positionNode; + this.geometryNode = source.geometryNode; + + this.depthNode = source.depthNode; + this.receivedShadowPositionNode = source.receivedShadowPositionNode; + this.castShadowPositionNode = source.castShadowPositionNode; + this.receivedShadowNode = source.receivedShadowNode; + this.castShadowNode = source.castShadowNode; + + this.outputNode = source.outputNode; + this.mrtNode = source.mrtNode; + + this.fragmentNode = source.fragmentNode; + this.vertexNode = source.vertexNode; + + return super.copy( source ); + + } + +} + +const _defaultValues$d = /*@__PURE__*/ new LineBasicMaterial(); + +/** + * Node material version of {@link LineBasicMaterial}. + * + * @augments NodeMaterial + */ +class LineBasicNodeMaterial extends NodeMaterial { + + static get type() { + + return 'LineBasicNodeMaterial'; + + } + + /** + * Constructs a new line basic node material. + * + * @param {Object} [parameters] - The configuration parameter. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLineBasicNodeMaterial = true; + + this.setDefaultValues( _defaultValues$d ); + + this.setValues( parameters ); + + } + +} + +const _defaultValues$c = /*@__PURE__*/ new LineDashedMaterial(); + +/** + * Node material version of {@link LineDashedMaterial}. + * + * @augments NodeMaterial + */ +class LineDashedNodeMaterial extends NodeMaterial { + + static get type() { + + return 'LineDashedNodeMaterial'; + + } + + /** + * Constructs a new line dashed node material. + * + * @param {Object} [parameters] - The configuration parameter. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLineDashedNodeMaterial = true; + + this.setDefaultValues( _defaultValues$c ); + + /** + * The dash offset. + * + * @type {number} + * @default 0 + */ + this.dashOffset = 0; + + /** + * The offset of dash materials is by default inferred from the `dashOffset` + * property. This node property allows to overwrite the default + * and define the offset with a node instead. + * + * If you don't want to overwrite the offset but modify the existing + * value instead, use {@link materialLineDashOffset}. + * + * @type {?Node} + * @default null + */ + this.offsetNode = null; + + /** + * The scale of dash materials is by default inferred from the `scale` + * property. This node property allows to overwrite the default + * and define the scale with a node instead. + * + * If you don't want to overwrite the scale but modify the existing + * value instead, use {@link materialLineScale}. + * + * @type {?Node} + * @default null + */ + this.dashScaleNode = null; + + /** + * The dash size of dash materials is by default inferred from the `dashSize` + * property. This node property allows to overwrite the default + * and define the dash size with a node instead. + * + * If you don't want to overwrite the dash size but modify the existing + * value instead, use {@link materialLineDashSize}. + * + * @type {?Node} + * @default null + */ + this.dashSizeNode = null; + + /** + * The gap size of dash materials is by default inferred from the `gapSize` + * property. This node property allows to overwrite the default + * and define the gap size with a node instead. + * + * If you don't want to overwrite the gap size but modify the existing + * value instead, use {@link materialLineGapSize}. + * + * @type {?Node} + * @default null + */ + this.gapSizeNode = null; + + this.setValues( parameters ); + + } + + /** + * Setups the dash specific node variables. + * + * @param {NodeBuilder} builder - The current node builder. + */ + setupVariants( /* builder */ ) { + + const offsetNode = this.offsetNode ? float( this.offsetNode ) : materialLineDashOffset; + const dashScaleNode = this.dashScaleNode ? float( this.dashScaleNode ) : materialLineScale; + const dashSizeNode = this.dashSizeNode ? float( this.dashSizeNode ) : materialLineDashSize; + const gapSizeNode = this.gapSizeNode ? float( this.gapSizeNode ) : materialLineGapSize; + + dashSize.assign( dashSizeNode ); + gapSize.assign( gapSizeNode ); + + const vLineDistance = varying( attribute( 'lineDistance' ).mul( dashScaleNode ) ); + const vLineDistanceOffset = offsetNode ? vLineDistance.add( offsetNode ) : vLineDistance; + + vLineDistanceOffset.mod( dashSize.add( gapSize ) ).greaterThan( dashSize ).discard(); + + } + +} + +let _sharedFramebuffer = null; + +/** + * `ViewportTextureNode` creates an internal texture for each node instance. This module + * shares a texture across all instances of `ViewportSharedTextureNode`. It should + * be the first choice when using data of the default/screen framebuffer for performance reasons. + * + * @augments ViewportTextureNode + */ +class ViewportSharedTextureNode extends ViewportTextureNode { + + static get type() { + + return 'ViewportSharedTextureNode'; + + } + + /** + * Constructs a new viewport shared texture node. + * + * @param {Node} [uvNode=screenUV] - The uv node. + * @param {?Node} [levelNode=null] - The level node. + */ + constructor( uvNode = screenUV, levelNode = null ) { + + if ( _sharedFramebuffer === null ) { + + _sharedFramebuffer = new FramebufferTexture(); + + } + + super( uvNode, levelNode, _sharedFramebuffer ); + + } + + updateReference() { + + return this; + + } + +} + +/** + * TSL function for creating a shared viewport texture node. + * + * @tsl + * @function + * @param {?Node} [uvNode=screenUV] - The uv node. + * @param {?Node} [levelNode=null] - The level node. + * @returns {ViewportSharedTextureNode} + */ +const viewportSharedTexture = /*@__PURE__*/ nodeProxy( ViewportSharedTextureNode ).setParameterLength( 0, 2 ); + +const _defaultValues$b = /*@__PURE__*/ new LineDashedMaterial(); + +/** + * This node material can be used to render lines with a size larger than one + * by representing them as instanced meshes. + * + * @augments NodeMaterial + */ +class Line2NodeMaterial extends NodeMaterial { + + static get type() { + + return 'Line2NodeMaterial'; + + } + + /** + * Constructs a new node material for wide line rendering. + * + * @param {Object} [parameters={}] - The configuration parameter. + */ + constructor( parameters = {} ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLine2NodeMaterial = true; + + this.setDefaultValues( _defaultValues$b ); + + /** + * Whether vertex colors should be used or not. + * + * @type {boolean} + * @default false + */ + this.useColor = parameters.vertexColors; + + /** + * The dash offset. + * + * @type {number} + * @default 0 + */ + this.dashOffset = 0; + + /** + * The line width. + * + * @type {number} + * @default 0 + */ + this.lineWidth = 1; + + /** + * Defines the lines color. + * + * @type {?Node} + * @default null + */ + this.lineColorNode = null; + + /** + * Defines the offset. + * + * @type {?Node} + * @default null + */ + this.offsetNode = null; + + /** + * Defines the dash scale. + * + * @type {?Node} + * @default null + */ + this.dashScaleNode = null; + + /** + * Defines the dash size. + * + * @type {?Node} + * @default null + */ + this.dashSizeNode = null; + + /** + * Defines the gap size. + * + * @type {?Node} + * @default null + */ + this.gapSizeNode = null; + + /** + * Blending is set to `NoBlending` since transparency + * is not supported, yet. + * + * @type {number} + * @default 0 + */ + this.blending = NoBlending; + + this._useDash = parameters.dashed; + this._useAlphaToCoverage = true; + this._useWorldUnits = false; + + this.setValues( parameters ); + + } + + /** + * Setups the vertex and fragment stage of this node material. + * + * @param {NodeBuilder} builder - The current node builder. + */ + setup( builder ) { + + const { renderer } = builder; + + const useAlphaToCoverage = this._useAlphaToCoverage; + const useColor = this.useColor; + const useDash = this._useDash; + const useWorldUnits = this._useWorldUnits; + + const trimSegment = Fn( ( { start, end } ) => { + + const a = cameraProjectionMatrix.element( 2 ).element( 2 ); // 3nd entry in 3th column + const b = cameraProjectionMatrix.element( 3 ).element( 2 ); // 3nd entry in 4th column + const nearEstimate = b.mul( -0.5 ).div( a ); + + const alpha = nearEstimate.sub( start.z ).div( end.z.sub( start.z ) ); + + return vec4( mix( start.xyz, end.xyz, alpha ), end.w ); + + } ).setLayout( { + name: 'trimSegment', + type: 'vec4', + inputs: [ + { name: 'start', type: 'vec4' }, + { name: 'end', type: 'vec4' } + ] + } ); + + this.vertexNode = Fn( () => { + + const instanceStart = attribute( 'instanceStart' ); + const instanceEnd = attribute( 'instanceEnd' ); + + // camera space + + const start = vec4( modelViewMatrix.mul( vec4( instanceStart, 1.0 ) ) ).toVar( 'start' ); + const end = vec4( modelViewMatrix.mul( vec4( instanceEnd, 1.0 ) ) ).toVar( 'end' ); + + if ( useDash ) { + + const dashScaleNode = this.dashScaleNode ? float( this.dashScaleNode ) : materialLineScale; + const offsetNode = this.offsetNode ? float( this.offsetNode ) : materialLineDashOffset; + + const instanceDistanceStart = attribute( 'instanceDistanceStart' ); + const instanceDistanceEnd = attribute( 'instanceDistanceEnd' ); + + let lineDistance = positionGeometry.y.lessThan( 0.5 ).select( dashScaleNode.mul( instanceDistanceStart ), dashScaleNode.mul( instanceDistanceEnd ) ); + lineDistance = lineDistance.add( offsetNode ); + + varyingProperty( 'float', 'lineDistance' ).assign( lineDistance ); + + } + + if ( useWorldUnits ) { + + varyingProperty( 'vec3', 'worldStart' ).assign( start.xyz ); + varyingProperty( 'vec3', 'worldEnd' ).assign( end.xyz ); + + } + + const aspect = viewport.z.div( viewport.w ); + + // special case for perspective projection, and segments that terminate either in, or behind, the camera plane + // clearly the gpu firmware has a way of addressing this issue when projecting into ndc space + // but we need to perform ndc-space calculations in the shader, so we must address this issue directly + // perhaps there is a more elegant solution -- WestLangley + + const perspective = cameraProjectionMatrix.element( 2 ).element( 3 ).equal( -1 ); // 4th entry in the 3rd column + + If( perspective, () => { + + If( start.z.lessThan( 0.0 ).and( end.z.greaterThan( 0.0 ) ), () => { + + end.assign( trimSegment( { start: start, end: end } ) ); + + } ).ElseIf( end.z.lessThan( 0.0 ).and( start.z.greaterThanEqual( 0.0 ) ), () => { + + start.assign( trimSegment( { start: end, end: start } ) ); + + } ); + + } ); + + // clip space + const clipStart = cameraProjectionMatrix.mul( start ); + const clipEnd = cameraProjectionMatrix.mul( end ); + + // ndc space + const ndcStart = clipStart.xyz.div( clipStart.w ); + const ndcEnd = clipEnd.xyz.div( clipEnd.w ); + + // direction + const dir = ndcEnd.xy.sub( ndcStart.xy ).toVar(); + + // account for clip-space aspect ratio + dir.x.assign( dir.x.mul( aspect ) ); + dir.assign( dir.normalize() ); + + const clip = vec4().toVar(); + + if ( useWorldUnits ) { + + // get the offset direction as perpendicular to the view vector + + const worldDir = end.xyz.sub( start.xyz ).normalize(); + const tmpFwd = mix( start.xyz, end.xyz, 0.5 ).normalize(); + const worldUp = worldDir.cross( tmpFwd ).normalize(); + const worldFwd = worldDir.cross( worldUp ); + + const worldPos = varyingProperty( 'vec4', 'worldPos' ); + + worldPos.assign( positionGeometry.y.lessThan( 0.5 ).select( start, end ) ); + + // height offset + const hw = materialLineWidth.mul( 0.5 ); + worldPos.addAssign( vec4( positionGeometry.x.lessThan( 0.0 ).select( worldUp.mul( hw ), worldUp.mul( hw ).negate() ), 0 ) ); + + // don't extend the line if we're rendering dashes because we + // won't be rendering the endcaps + if ( ! useDash ) { + + // cap extension + worldPos.addAssign( vec4( positionGeometry.y.lessThan( 0.5 ).select( worldDir.mul( hw ).negate(), worldDir.mul( hw ) ), 0 ) ); + + // add width to the box + worldPos.addAssign( vec4( worldFwd.mul( hw ), 0 ) ); + + // endcaps + If( positionGeometry.y.greaterThan( 1.0 ).or( positionGeometry.y.lessThan( 0.0 ) ), () => { + + worldPos.subAssign( vec4( worldFwd.mul( 2.0 ).mul( hw ), 0 ) ); + + } ); + + } + + // project the worldpos + clip.assign( cameraProjectionMatrix.mul( worldPos ) ); + + // shift the depth of the projected points so the line + // segments overlap neatly + const clipPose = vec3().toVar(); + + clipPose.assign( positionGeometry.y.lessThan( 0.5 ).select( ndcStart, ndcEnd ) ); + clip.z.assign( clipPose.z.mul( clip.w ) ); + + } else { + + const offset = vec2( dir.y, dir.x.negate() ).toVar( 'offset' ); + + // undo aspect ratio adjustment + dir.x.assign( dir.x.div( aspect ) ); + offset.x.assign( offset.x.div( aspect ) ); + + // sign flip + offset.assign( positionGeometry.x.lessThan( 0.0 ).select( offset.negate(), offset ) ); + + // endcaps + If( positionGeometry.y.lessThan( 0.0 ), () => { + + offset.assign( offset.sub( dir ) ); + + } ).ElseIf( positionGeometry.y.greaterThan( 1.0 ), () => { + + offset.assign( offset.add( dir ) ); + + } ); + + // adjust for linewidth + offset.assign( offset.mul( materialLineWidth ) ); + + // adjust for clip-space to screen-space conversion // maybe resolution should be based on viewport ... + offset.assign( offset.div( viewport.w ) ); + + // select end + clip.assign( positionGeometry.y.lessThan( 0.5 ).select( clipStart, clipEnd ) ); + + // back to clip space + offset.assign( offset.mul( clip.w ) ); + + clip.assign( clip.add( vec4( offset, 0, 0 ) ) ); + + } + + return clip; + + } )(); + + const closestLineToLine = Fn( ( { p1, p2, p3, p4 } ) => { + + const p13 = p1.sub( p3 ); + const p43 = p4.sub( p3 ); + + const p21 = p2.sub( p1 ); + + const d1343 = p13.dot( p43 ); + const d4321 = p43.dot( p21 ); + const d1321 = p13.dot( p21 ); + const d4343 = p43.dot( p43 ); + const d2121 = p21.dot( p21 ); + + const denom = d2121.mul( d4343 ).sub( d4321.mul( d4321 ) ); + const numer = d1343.mul( d4321 ).sub( d1321.mul( d4343 ) ); + + const mua = numer.div( denom ).clamp(); + const mub = d1343.add( d4321.mul( mua ) ).div( d4343 ).clamp(); + + return vec2( mua, mub ); + + } ); + + this.colorNode = Fn( () => { + + const vUv = uv$1(); + + if ( useDash ) { + + const dashSizeNode = this.dashSizeNode ? float( this.dashSizeNode ) : materialLineDashSize; + const gapSizeNode = this.gapSizeNode ? float( this.gapSizeNode ) : materialLineGapSize; + + dashSize.assign( dashSizeNode ); + gapSize.assign( gapSizeNode ); + + const vLineDistance = varyingProperty( 'float', 'lineDistance' ); + + vUv.y.lessThan( -1 ).or( vUv.y.greaterThan( 1.0 ) ).discard(); // discard endcaps + vLineDistance.mod( dashSize.add( gapSize ) ).greaterThan( dashSize ).discard(); // todo - FIX + + } + + const alpha = float( 1 ).toVar( 'alpha' ); + + if ( useWorldUnits ) { + + const worldStart = varyingProperty( 'vec3', 'worldStart' ); + const worldEnd = varyingProperty( 'vec3', 'worldEnd' ); + + // Find the closest points on the view ray and the line segment + const rayEnd = varyingProperty( 'vec4', 'worldPos' ).xyz.normalize().mul( 1e5 ); + const lineDir = worldEnd.sub( worldStart ); + const params = closestLineToLine( { p1: worldStart, p2: worldEnd, p3: vec3( 0.0, 0.0, 0.0 ), p4: rayEnd } ); + + const p1 = worldStart.add( lineDir.mul( params.x ) ); + const p2 = rayEnd.mul( params.y ); + const delta = p1.sub( p2 ); + const len = delta.length(); + const norm = len.div( materialLineWidth ); + + if ( ! useDash ) { + + if ( useAlphaToCoverage && renderer.samples > 1 ) { + + const dnorm = norm.fwidth(); + alpha.assign( smoothstep( dnorm.negate().add( 0.5 ), dnorm.add( 0.5 ), norm ).oneMinus() ); + + } else { + + norm.greaterThan( 0.5 ).discard(); + + } + + } + + } else { + + // round endcaps + + if ( useAlphaToCoverage && renderer.samples > 1 ) { + + const a = vUv.x; + const b = vUv.y.greaterThan( 0.0 ).select( vUv.y.sub( 1.0 ), vUv.y.add( 1.0 ) ); + + const len2 = a.mul( a ).add( b.mul( b ) ); + + const dlen = float( len2.fwidth() ).toVar( 'dlen' ); + + If( vUv.y.abs().greaterThan( 1.0 ), () => { + + alpha.assign( smoothstep( dlen.oneMinus(), dlen.add( 1 ), len2 ).oneMinus() ); + + } ); + + } else { + + If( vUv.y.abs().greaterThan( 1.0 ), () => { + + const a = vUv.x; + const b = vUv.y.greaterThan( 0.0 ).select( vUv.y.sub( 1.0 ), vUv.y.add( 1.0 ) ); + const len2 = a.mul( a ).add( b.mul( b ) ); + + len2.greaterThan( 1.0 ).discard(); + + } ); + + } + + } + + let lineColorNode; + + if ( this.lineColorNode ) { + + lineColorNode = this.lineColorNode; + + } else { + + if ( useColor ) { + + const instanceColorStart = attribute( 'instanceColorStart' ); + const instanceColorEnd = attribute( 'instanceColorEnd' ); + + const instanceColor = positionGeometry.y.lessThan( 0.5 ).select( instanceColorStart, instanceColorEnd ); + + lineColorNode = instanceColor.mul( materialColor ); + + } else { + + lineColorNode = materialColor; + + } + + } + + return vec4( lineColorNode, alpha ); + + } )(); + + if ( this.transparent ) { + + const opacityNode = this.opacityNode ? float( this.opacityNode ) : materialOpacity; + + this.outputNode = vec4( this.colorNode.rgb.mul( opacityNode ).add( viewportSharedTexture().rgb.mul( opacityNode.oneMinus() ) ), this.colorNode.a ); + + } + + super.setup( builder ); + + } + + /** + * Whether the lines should sized in world units or not. + * When set to `false` the unit is pixel. + * + * @type {boolean} + * @default false + */ + get worldUnits() { + + return this._useWorldUnits; + + } + + set worldUnits( value ) { + + if ( this._useWorldUnits !== value ) { + + this._useWorldUnits = value; + this.needsUpdate = true; + + } + + } + + /** + * Whether the lines should be dashed or not. + * + * @type {boolean} + * @default false + */ + get dashed() { + + return this._useDash; + + } + + set dashed( value ) { + + if ( this._useDash !== value ) { + + this._useDash = value; + this.needsUpdate = true; + + } + + } + + /** + * Whether alpha to coverage should be used or not. + * + * @type {boolean} + * @default true + */ + get alphaToCoverage() { + + return this._useAlphaToCoverage; + + } + + set alphaToCoverage( value ) { + + if ( this._useAlphaToCoverage !== value ) { + + this._useAlphaToCoverage = value; + this.needsUpdate = true; + + } + + } + +} + +/** + * Packs a direction vector into a color value. + * + * @tsl + * @function + * @param {Node} node - The direction to pack. + * @return {Node} The color. + */ +const directionToColor = ( node ) => nodeObject( node ).mul( 0.5 ).add( 0.5 ); + +/** + * Unpacks a color value into a direction vector. + * + * @tsl + * @function + * @param {Node} node - The color to unpack. + * @return {Node} The direction. + */ +const colorToDirection = ( node ) => nodeObject( node ).mul( 2.0 ).sub( 1 ); + +const _defaultValues$a = /*@__PURE__*/ new MeshNormalMaterial(); + +/** + * Node material version of {@link MeshNormalMaterial}. + * + * @augments NodeMaterial + */ +class MeshNormalNodeMaterial extends NodeMaterial { + + static get type() { + + return 'MeshNormalNodeMaterial'; + + } + + /** + * Constructs a new mesh normal node material. + * + * @param {Object} [parameters] - The configuration parameter. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshNormalNodeMaterial = true; + + this.setDefaultValues( _defaultValues$a ); + + this.setValues( parameters ); + + } + + /** + * Overwrites the default implementation by computing the diffuse color + * based on the normal data. + */ + setupDiffuseColor() { + + const opacityNode = this.opacityNode ? float( this.opacityNode ) : materialOpacity; + + // By convention, a normal packed to RGB is in sRGB color space. Convert it to working color space. + + diffuseColor.assign( colorSpaceToWorking( vec4( directionToColor( normalView ), opacityNode ), SRGBColorSpace ) ); + + } + +} + +/** + * TSL function for creating an equirect uv node. + * + * Can be used to compute texture coordinates for projecting an + * equirectangular texture onto a mesh for using it as the scene's + * background. + * + * ```js + * scene.backgroundNode = texture( equirectTexture, equirectUV() ); + * ``` + * + * @tsl + * @function + * @param {?Node} [dirNode=positionWorldDirection] - A direction vector for sampling which is by default `positionWorldDirection`. + * @returns {Node} + */ +const equirectUV = /*@__PURE__*/ Fn( ( [ dir = positionWorldDirection ] ) => { + + const u = dir.z.atan( dir.x ).mul( 1 / ( Math.PI * 2 ) ).add( 0.5 ); + const v = dir.y.clamp( -1, 1.0 ).asin().mul( 1 / Math.PI ).add( 0.5 ); + + return vec2( u, v ); + +} ); + +// @TODO: Consider rename WebGLCubeRenderTarget to just CubeRenderTarget + +/** + * This class represents a cube render target. It is a special version + * of `WebGLCubeRenderTarget` which is compatible with `WebGPURenderer`. + * + * @augments WebGLCubeRenderTarget + */ +class CubeRenderTarget extends WebGLCubeRenderTarget { + + /** + * Constructs a new cube render target. + * + * @param {number} [size=1] - The size of the render target. + * @param {RenderTarget~Options} [options] - The configuration object. + */ + constructor( size = 1, options = {} ) { + + super( size, options ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isCubeRenderTarget = true; + + } + + /** + * Converts the given equirectangular texture to a cube map. + * + * @param {Renderer} renderer - The renderer. + * @param {Texture} texture - The equirectangular texture. + * @return {CubeRenderTarget} A reference to this cube render target. + */ + fromEquirectangularTexture( renderer, texture$1 ) { + + const currentMinFilter = texture$1.minFilter; + const currentGenerateMipmaps = texture$1.generateMipmaps; + + texture$1.generateMipmaps = true; + + this.texture.type = texture$1.type; + this.texture.colorSpace = texture$1.colorSpace; + + this.texture.generateMipmaps = texture$1.generateMipmaps; + this.texture.minFilter = texture$1.minFilter; + this.texture.magFilter = texture$1.magFilter; + + const geometry = new BoxGeometry( 5, 5, 5 ); + + const uvNode = equirectUV( positionWorldDirection ); + + const material = new NodeMaterial(); + material.colorNode = texture( texture$1, uvNode, 0 ); + material.side = BackSide; + material.blending = NoBlending; + + const mesh = new Mesh( geometry, material ); + + const scene = new Scene(); + scene.add( mesh ); + + // Avoid blurred poles + if ( texture$1.minFilter === LinearMipmapLinearFilter ) texture$1.minFilter = LinearFilter; + + const camera = new CubeCamera( 1, 10, this ); + + const currentMRT = renderer.getMRT(); + renderer.setMRT( null ); + + camera.update( renderer, scene ); + + renderer.setMRT( currentMRT ); + + texture$1.minFilter = currentMinFilter; + texture$1.currentGenerateMipmaps = currentGenerateMipmaps; + + mesh.geometry.dispose(); + mesh.material.dispose(); + + return this; + + } + +} + +const _cache$1 = new WeakMap(); + +/** + * This node can be used to automatically convert environment maps in the + * equirectangular format into the cube map format. + * + * @augments TempNode + */ +class CubeMapNode extends TempNode { + + static get type() { + + return 'CubeMapNode'; + + } + + /** + * Constructs a new cube map node. + * + * @param {Node} envNode - The node representing the environment map. + */ + constructor( envNode ) { + + super( 'vec3' ); + + /** + * The node representing the environment map. + * + * @type {Node} + */ + this.envNode = envNode; + + /** + * A reference to the internal cube texture. + * + * @private + * @type {?CubeTexture} + * @default null + */ + this._cubeTexture = null; + + /** + * A reference to the internal cube texture node. + * + * @private + * @type {CubeTextureNode} + */ + this._cubeTextureNode = cubeTexture( null ); + + const defaultTexture = new CubeTexture(); + defaultTexture.isRenderTargetTexture = true; + + /** + * A default cube texture that acts as a placeholder. + * It is used when the conversion from equirectangular to cube + * map has not finished yet for a given texture. + * + * @private + * @type {CubeTexture} + */ + this._defaultTexture = defaultTexture; + + /** + * The `updateBeforeType` is set to `NodeUpdateType.RENDER` since the node updates + * the texture once per render in its {@link CubeMapNode#updateBefore} method. + * + * @type {string} + * @default 'render' + */ + this.updateBeforeType = NodeUpdateType.RENDER; + + } + + updateBefore( frame ) { + + const { renderer, material } = frame; + + const envNode = this.envNode; + + if ( envNode.isTextureNode || envNode.isMaterialReferenceNode ) { + + const texture = ( envNode.isTextureNode ) ? envNode.value : material[ envNode.property ]; + + if ( texture && texture.isTexture ) { + + const mapping = texture.mapping; + + if ( mapping === EquirectangularReflectionMapping || mapping === EquirectangularRefractionMapping ) { + + // check for converted cubemap map + + if ( _cache$1.has( texture ) ) { + + const cubeMap = _cache$1.get( texture ); + + mapTextureMapping( cubeMap, texture.mapping ); + this._cubeTexture = cubeMap; + + } else { + + // create cube map from equirectangular map + + const image = texture.image; + + if ( isEquirectangularMapReady$1( image ) ) { + + const renderTarget = new CubeRenderTarget( image.height ); + renderTarget.fromEquirectangularTexture( renderer, texture ); + + mapTextureMapping( renderTarget.texture, texture.mapping ); + this._cubeTexture = renderTarget.texture; + + _cache$1.set( texture, renderTarget.texture ); + + texture.addEventListener( 'dispose', onTextureDispose ); + + } else { + + // default cube texture as fallback when equirectangular texture is not yet loaded + + this._cubeTexture = this._defaultTexture; + + } + + } + + // + + this._cubeTextureNode.value = this._cubeTexture; + + } else { + + // envNode already refers to a cube map + + this._cubeTextureNode = this.envNode; + + } + + } + + } + + } + + setup( builder ) { + + this.updateBefore( builder ); + + return this._cubeTextureNode; + + } + +} + +/** + * Returns true if the given equirectangular image has been fully loaded + * and is ready for further processing. + * + * @private + * @param {Image} image - The equirectangular image to check. + * @return {boolean} Whether the image is ready or not. + */ +function isEquirectangularMapReady$1( image ) { + + if ( image === null || image === undefined ) return false; + + return image.height > 0; + +} + +/** + * This function is executed when `dispose()` is called on the equirectangular + * texture. In this case, the generated cube map with its render target + * is deleted as well. + * + * @private + * @param {Object} event - The event object. + */ +function onTextureDispose( event ) { + + const texture = event.target; + + texture.removeEventListener( 'dispose', onTextureDispose ); + + const renderTarget = _cache$1.get( texture ); + + if ( renderTarget !== undefined ) { + + _cache$1.delete( texture ); + + renderTarget.dispose(); + + } + +} + +/** + * This function makes sure the generated cube map uses the correct + * texture mapping that corresponds to the equirectangular original. + * + * @private + * @param {Texture} texture - The cube texture. + * @param {number} mapping - The original texture mapping. + */ +function mapTextureMapping( texture, mapping ) { + + if ( mapping === EquirectangularReflectionMapping ) { + + texture.mapping = CubeReflectionMapping; + + } else if ( mapping === EquirectangularRefractionMapping ) { + + texture.mapping = CubeRefractionMapping; + + } + +} + +/** + * TSL function for creating a cube map node. + * + * @tsl + * @function + * @param {Node} envNode - The node representing the environment map. + * @returns {CubeMapNode} + */ +const cubeMapNode = /*@__PURE__*/ nodeProxy( CubeMapNode ).setParameterLength( 1 ); + +/** + * Represents a basic model for Image-based lighting (IBL). The environment + * is defined via environment maps in the equirectangular or cube map format. + * `BasicEnvironmentNode` is intended for non-PBR materials like {@link MeshBasicNodeMaterial} + * or {@link MeshPhongNodeMaterial}. + * + * @augments LightingNode + */ +class BasicEnvironmentNode extends LightingNode { + + static get type() { + + return 'BasicEnvironmentNode'; + + } + + /** + * Constructs a new basic environment node. + * + * @param {Node} [envNode=null] - A node representing the environment. + */ + constructor( envNode = null ) { + + super(); + + /** + * A node representing the environment. + * + * @type {Node} + * @default null + */ + this.envNode = envNode; + + } + + setup( builder ) { + + // environment property is used in the finish() method of BasicLightingModel + + builder.context.environment = cubeMapNode( this.envNode ); + + } + +} + +/** + * A specific version of {@link IrradianceNode} that is only relevant + * for {@link MeshBasicNodeMaterial}. Since the material is unlit, it + * requires a special scaling factor for the light map. + * + * @augments LightingNode + */ +class BasicLightMapNode extends LightingNode { + + static get type() { + + return 'BasicLightMapNode'; + + } + + /** + * Constructs a new basic light map node. + * + * @param {?Node} [lightMapNode=null] - The light map node. + */ + constructor( lightMapNode = null ) { + + super(); + + /** + * The light map node. + * + * @type {?Node} + */ + this.lightMapNode = lightMapNode; + + } + + setup( builder ) { + + // irradianceLightMap property is used in the indirectDiffuse() method of BasicLightingModel + + const RECIPROCAL_PI = float( 1 / Math.PI ); + + builder.context.irradianceLightMap = this.lightMapNode.mul( RECIPROCAL_PI ); + + } + +} + +/** + * Abstract class for implementing lighting models. The module defines + * multiple methods that concrete lighting models can implement. These + * methods are executed at different points during the light evaluation + * process. + */ +class LightingModel { + + /** + * This method is intended for setting up lighting model and context data + * which are later used in the evaluation process. + * + * @abstract + * @param {NodeBuilder} builder - The current node builder. + */ + start( builder ) { + + // lights ( direct ) + + builder.lightsNode.setupLights( builder, builder.lightsNode.getLightNodes( builder ) ); + + // indirect + + this.indirect( builder ); + + } + + /** + * This method is intended for executing final tasks like final updates + * to the outgoing light. + * + * @abstract + * @param {NodeBuilder} builder - The current node builder. + */ + finish( /*builder*/ ) { } + + /** + * This method is intended for implementing the direct light term and + * executed during the build process of directional, point and spot light nodes. + * + * @abstract + * @param {Object} lightData - The light data. + * @param {NodeBuilder} builder - The current node builder. + */ + direct( /*lightData, builder*/ ) { } + + /** + * This method is intended for implementing the direct light term for + * rect area light nodes. + * + * @abstract + * @param {Object} lightData - The light data. + * @param {NodeBuilder} builder - The current node builder. + */ + directRectArea( /*lightData, builder*/ ) {} + + /** + * This method is intended for implementing the indirect light term. + * + * @abstract + * @param {NodeBuilder} builder - The current node builder. + */ + indirect( /*builder*/ ) { } + + /** + * This method is intended for implementing the ambient occlusion term. + * Unlike other methods, this method must be called manually by the lighting + * model in its indirect term. + * + * @abstract + * @param {NodeBuilder} builder - The current node builder. + */ + ambientOcclusion( /*input, stack, builder*/ ) { } + +} + +/** + * Represents the lighting model for unlit materials. The only light contribution + * is baked indirect lighting modulated with ambient occlusion and the material's + * diffuse color. Environment mapping is supported. Used in {@link MeshBasicNodeMaterial}. + * + * @augments LightingModel + */ +class BasicLightingModel extends LightingModel { + + /** + * Constructs a new basic lighting model. + */ + constructor() { + + super(); + + } + + /** + * Implements the baked indirect lighting with its modulation. + * + * @param {NodeBuilder} builder - The current node builder. + */ + indirect( { context } ) { + + const ambientOcclusion = context.ambientOcclusion; + const reflectedLight = context.reflectedLight; + const irradianceLightMap = context.irradianceLightMap; + + reflectedLight.indirectDiffuse.assign( vec4( 0.0 ) ); + + // accumulation (baked indirect lighting only) + + if ( irradianceLightMap ) { + + reflectedLight.indirectDiffuse.addAssign( irradianceLightMap ); + + } else { + + reflectedLight.indirectDiffuse.addAssign( vec4( 1.0, 1.0, 1.0, 0.0 ) ); + + } + + // modulation + + reflectedLight.indirectDiffuse.mulAssign( ambientOcclusion ); + + reflectedLight.indirectDiffuse.mulAssign( diffuseColor.rgb ); + + } + + /** + * Implements the environment mapping. + * + * @param {NodeBuilder} builder - The current node builder. + */ + finish( builder ) { + + const { material, context } = builder; + + const outgoingLight = context.outgoingLight; + const envNode = builder.context.environment; + + if ( envNode ) { + + switch ( material.combine ) { + + case MultiplyOperation: + outgoingLight.rgb.assign( mix( outgoingLight.rgb, outgoingLight.rgb.mul( envNode.rgb ), materialSpecularStrength.mul( materialReflectivity ) ) ); + break; + + case MixOperation: + outgoingLight.rgb.assign( mix( outgoingLight.rgb, envNode.rgb, materialSpecularStrength.mul( materialReflectivity ) ) ); + break; + + case AddOperation: + outgoingLight.rgb.addAssign( envNode.rgb.mul( materialSpecularStrength.mul( materialReflectivity ) ) ); + break; + + default: + console.warn( 'THREE.BasicLightingModel: Unsupported .combine value:', material.combine ); + break; + + } + + } + + } + +} + +const _defaultValues$9 = /*@__PURE__*/ new MeshBasicMaterial(); + +/** + * Node material version of {@link MeshBasicMaterial}. + * + * @augments NodeMaterial + */ +class MeshBasicNodeMaterial extends NodeMaterial { + + static get type() { + + return 'MeshBasicNodeMaterial'; + + } + + /** + * Constructs a new mesh basic node material. + * + * @param {Object} [parameters] - The configuration parameter. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshBasicNodeMaterial = true; + + /** + * Although the basic material is by definition unlit, we set + * this property to `true` since we use a lighting model to compute + * the outgoing light of the fragment shader. + * + * @type {boolean} + * @default true + */ + this.lights = true; + + this.setDefaultValues( _defaultValues$9 ); + + this.setValues( parameters ); + + } + + /** + * Basic materials are not affected by normal and bump maps so we + * return by default {@link normalViewGeometry}. + * + * @return {Node} The normal node. + */ + setupNormal() { + + return directionToFaceDirection( normalViewGeometry ); // see #28839 + + } + + /** + * Overwritten since this type of material uses {@link BasicEnvironmentNode} + * to implement the default environment mapping. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {?BasicEnvironmentNode} The environment node. + */ + setupEnvironment( builder ) { + + const envNode = super.setupEnvironment( builder ); + + return envNode ? new BasicEnvironmentNode( envNode ) : null; + + } + + /** + * This method must be overwritten since light maps are evaluated + * with a special scaling factor for basic materials. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {?BasicLightMapNode} The light map node. + */ + setupLightMap( builder ) { + + let node = null; + + if ( builder.material.lightMap ) { + + node = new BasicLightMapNode( materialLightMap ); + + } + + return node; + + } + + /** + * The material overwrites this method because `lights` is set to `true` but + * we still want to return the diffuse color as the outgoing light. + * + * @return {Node} The outgoing light node. + */ + setupOutgoingLight() { + + return diffuseColor.rgb; + + } + + /** + * Setups the lighting model. + * + * @return {BasicLightingModel} The lighting model. + */ + setupLightingModel() { + + return new BasicLightingModel(); + + } + +} + +const F_Schlick = /*@__PURE__*/ Fn( ( { f0, f90, dotVH } ) => { + + // Original approximation by Christophe Schlick '94 + // float fresnel = pow( 1.0 - dotVH, 5.0 ); + + // Optimized variant (presented by Epic at SIGGRAPH '13) + // https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf + const fresnel = dotVH.mul( -5.55473 ).sub( 6.98316 ).mul( dotVH ).exp2(); + + return f0.mul( fresnel.oneMinus() ).add( f90.mul( fresnel ) ); + +} ); // validated + +const BRDF_Lambert = /*@__PURE__*/ Fn( ( inputs ) => { + + return inputs.diffuseColor.mul( 1 / Math.PI ); // punctual light + +} ); // validated + +const G_BlinnPhong_Implicit = () => float( 0.25 ); + +const D_BlinnPhong = /*@__PURE__*/ Fn( ( { dotNH } ) => { + + return shininess.mul( float( 0.5 ) ).add( 1.0 ).mul( float( 1 / Math.PI ) ).mul( dotNH.pow( shininess ) ); + +} ); + +const BRDF_BlinnPhong = /*@__PURE__*/ Fn( ( { lightDirection } ) => { + + const halfDir = lightDirection.add( positionViewDirection ).normalize(); + + const dotNH = normalView.dot( halfDir ).clamp(); + const dotVH = positionViewDirection.dot( halfDir ).clamp(); + + const F = F_Schlick( { f0: specularColor, f90: 1.0, dotVH } ); + const G = G_BlinnPhong_Implicit(); + const D = D_BlinnPhong( { dotNH } ); + + return F.mul( G ).mul( D ); + +} ); + +/** + * Represents the lighting model for a phong material. Used in {@link MeshPhongNodeMaterial}. + * + * @augments BasicLightingModel + */ +class PhongLightingModel extends BasicLightingModel { + + /** + * Constructs a new phong lighting model. + * + * @param {boolean} [specular=true] - Whether specular is supported or not. + */ + constructor( specular = true ) { + + super(); + + /** + * Whether specular is supported or not. Set this to `false` if you are + * looking for a Lambert-like material meaning a material for non-shiny + * surfaces, without specular highlights. + * + * @type {boolean} + * @default true + */ + this.specular = specular; + + } + + /** + * Implements the direct lighting. The specular portion is optional an can be controlled + * with the {@link PhongLightingModel#specular} flag. + * + * @param {Object} lightData - The light data. + */ + direct( { lightDirection, lightColor, reflectedLight } ) { + + const dotNL = normalView.dot( lightDirection ).clamp(); + const irradiance = dotNL.mul( lightColor ); + + reflectedLight.directDiffuse.addAssign( irradiance.mul( BRDF_Lambert( { diffuseColor: diffuseColor.rgb } ) ) ); + + if ( this.specular === true ) { + + reflectedLight.directSpecular.addAssign( irradiance.mul( BRDF_BlinnPhong( { lightDirection } ) ).mul( materialSpecularStrength ) ); + + } + + } + + /** + * Implements the indirect lighting. + * + * @param {NodeBuilder} builder - The current node builder. + */ + indirect( builder ) { + + const { ambientOcclusion, irradiance, reflectedLight } = builder.context; + + reflectedLight.indirectDiffuse.addAssign( irradiance.mul( BRDF_Lambert( { diffuseColor } ) ) ); + + reflectedLight.indirectDiffuse.mulAssign( ambientOcclusion ); + + } + +} + +const _defaultValues$8 = /*@__PURE__*/ new MeshLambertMaterial(); + +/** + * Node material version of {@link MeshLambertMaterial}. + * + * @augments NodeMaterial + */ +class MeshLambertNodeMaterial extends NodeMaterial { + + static get type() { + + return 'MeshLambertNodeMaterial'; + + } + + /** + * Constructs a new mesh lambert node material. + * + * @param {Object} [parameters] - The configuration parameter. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshLambertNodeMaterial = true; + + /** + * Set to `true` because lambert materials react on lights. + * + * @type {boolean} + * @default true + */ + this.lights = true; + + this.setDefaultValues( _defaultValues$8 ); + + this.setValues( parameters ); + + } + + /** + * Overwritten since this type of material uses {@link BasicEnvironmentNode} + * to implement the default environment mapping. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {?BasicEnvironmentNode} The environment node. + */ + setupEnvironment( builder ) { + + const envNode = super.setupEnvironment( builder ); + + return envNode ? new BasicEnvironmentNode( envNode ) : null; + + } + + /** + * Setups the lighting model. + * + * @return {PhongLightingModel} The lighting model. + */ + setupLightingModel( /*builder*/ ) { + + return new PhongLightingModel( false ); // ( specular ) -> force lambert + + } + +} + +const _defaultValues$7 = /*@__PURE__*/ new MeshPhongMaterial(); + +/** + * Node material version of {@link MeshPhongMaterial}. + * + * @augments NodeMaterial + */ +class MeshPhongNodeMaterial extends NodeMaterial { + + static get type() { + + return 'MeshPhongNodeMaterial'; + + } + + /** + * Constructs a new mesh lambert node material. + * + * @param {Object} [parameters] - The configuration parameter. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshPhongNodeMaterial = true; + + /** + * Set to `true` because phong materials react on lights. + * + * @type {boolean} + * @default true + */ + this.lights = true; + + /** + * The shininess of phong materials is by default inferred from the `shininess` + * property. This node property allows to overwrite the default + * and define the shininess with a node instead. + * + * If you don't want to overwrite the shininess but modify the existing + * value instead, use {@link materialShininess}. + * + * @type {?Node} + * @default null + */ + this.shininessNode = null; + + /** + * The specular color of phong materials is by default inferred from the + * `specular` property. This node property allows to overwrite the default + * and define the specular color with a node instead. + * + * If you don't want to overwrite the specular color but modify the existing + * value instead, use {@link materialSpecular}. + * + * @type {?Node} + * @default null + */ + this.specularNode = null; + + this.setDefaultValues( _defaultValues$7 ); + + this.setValues( parameters ); + + } + + /** + * Overwritten since this type of material uses {@link BasicEnvironmentNode} + * to implement the default environment mapping. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {?BasicEnvironmentNode} The environment node. + */ + setupEnvironment( builder ) { + + const envNode = super.setupEnvironment( builder ); + + return envNode ? new BasicEnvironmentNode( envNode ) : null; + + } + + /** + * Setups the lighting model. + * + * @return {PhongLightingModel} The lighting model. + */ + setupLightingModel( /*builder*/ ) { + + return new PhongLightingModel(); + + } + + /** + * Setups the phong specific node variables. + * + * @param {NodeBuilder} builder - The current node builder. + */ + setupVariants( /*builder*/ ) { + + // SHININESS + + const shininessNode = ( this.shininessNode ? float( this.shininessNode ) : materialShininess ).max( 1e-4 ); // to prevent pow( 0.0, 0.0 ) + + shininess.assign( shininessNode ); + + // SPECULAR COLOR + + const specularNode = this.specularNode || materialSpecular; + + specularColor.assign( specularNode ); + + } + + copy( source ) { + + this.shininessNode = source.shininessNode; + this.specularNode = source.specularNode; + + return super.copy( source ); + + } + +} + +const getGeometryRoughness = /*@__PURE__*/ Fn( ( builder ) => { + + if ( builder.geometry.hasAttribute( 'normal' ) === false ) { + + return float( 0 ); + + } + + const dxy = normalViewGeometry.dFdx().abs().max( normalViewGeometry.dFdy().abs() ); + const geometryRoughness = dxy.x.max( dxy.y ).max( dxy.z ); + + return geometryRoughness; + +} ); + +const getRoughness = /*@__PURE__*/ Fn( ( inputs ) => { + + const { roughness } = inputs; + + const geometryRoughness = getGeometryRoughness(); + + let roughnessFactor = roughness.max( 0.0525 ); // 0.0525 corresponds to the base mip of a 256 cubemap. + roughnessFactor = roughnessFactor.add( geometryRoughness ); + roughnessFactor = roughnessFactor.min( 1.0 ); + + return roughnessFactor; + +} ); + +// Moving Frostbite to Physically Based Rendering 3.0 - page 12, listing 2 +// https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf +const V_GGX_SmithCorrelated = /*@__PURE__*/ Fn( ( { alpha, dotNL, dotNV } ) => { + + const a2 = alpha.pow2(); + + const gv = dotNL.mul( a2.add( a2.oneMinus().mul( dotNV.pow2() ) ).sqrt() ); + const gl = dotNV.mul( a2.add( a2.oneMinus().mul( dotNL.pow2() ) ).sqrt() ); + + return div( 0.5, gv.add( gl ).max( EPSILON ) ); + +} ).setLayout( { + name: 'V_GGX_SmithCorrelated', + type: 'float', + inputs: [ + { name: 'alpha', type: 'float' }, + { name: 'dotNL', type: 'float' }, + { name: 'dotNV', type: 'float' } + ] +} ); // validated + +// https://google.github.io/filament/Filament.md.html#materialsystem/anisotropicmodel/anisotropicspecularbrdf + +const V_GGX_SmithCorrelated_Anisotropic = /*@__PURE__*/ Fn( ( { alphaT, alphaB, dotTV, dotBV, dotTL, dotBL, dotNV, dotNL } ) => { + + const gv = dotNL.mul( vec3( alphaT.mul( dotTV ), alphaB.mul( dotBV ), dotNV ).length() ); + const gl = dotNV.mul( vec3( alphaT.mul( dotTL ), alphaB.mul( dotBL ), dotNL ).length() ); + const v = div( 0.5, gv.add( gl ) ); + + return v.saturate(); + +} ).setLayout( { + name: 'V_GGX_SmithCorrelated_Anisotropic', + type: 'float', + inputs: [ + { name: 'alphaT', type: 'float', qualifier: 'in' }, + { name: 'alphaB', type: 'float', qualifier: 'in' }, + { name: 'dotTV', type: 'float', qualifier: 'in' }, + { name: 'dotBV', type: 'float', qualifier: 'in' }, + { name: 'dotTL', type: 'float', qualifier: 'in' }, + { name: 'dotBL', type: 'float', qualifier: 'in' }, + { name: 'dotNV', type: 'float', qualifier: 'in' }, + { name: 'dotNL', type: 'float', qualifier: 'in' } + ] +} ); + +// Microfacet Models for Refraction through Rough Surfaces - equation (33) +// http://graphicrants.blogspot.com/2013/08/specular-brdf-reference.html +// alpha is "roughness squared" in Disney’s reparameterization +const D_GGX = /*@__PURE__*/ Fn( ( { alpha, dotNH } ) => { + + const a2 = alpha.pow2(); + + const denom = dotNH.pow2().mul( a2.oneMinus() ).oneMinus(); // avoid alpha = 0 with dotNH = 1 + + return a2.div( denom.pow2() ).mul( 1 / Math.PI ); + +} ).setLayout( { + name: 'D_GGX', + type: 'float', + inputs: [ + { name: 'alpha', type: 'float' }, + { name: 'dotNH', type: 'float' } + ] +} ); // validated + +const RECIPROCAL_PI = /*@__PURE__*/ float( 1 / Math.PI ); + +// https://google.github.io/filament/Filament.md.html#materialsystem/anisotropicmodel/anisotropicspecularbrdf + +const D_GGX_Anisotropic = /*@__PURE__*/ Fn( ( { alphaT, alphaB, dotNH, dotTH, dotBH } ) => { + + const a2 = alphaT.mul( alphaB ); + const v = vec3( alphaB.mul( dotTH ), alphaT.mul( dotBH ), a2.mul( dotNH ) ); + const v2 = v.dot( v ); + const w2 = a2.div( v2 ); + + return RECIPROCAL_PI.mul( a2.mul( w2.pow2() ) ); + +} ).setLayout( { + name: 'D_GGX_Anisotropic', + type: 'float', + inputs: [ + { name: 'alphaT', type: 'float', qualifier: 'in' }, + { name: 'alphaB', type: 'float', qualifier: 'in' }, + { name: 'dotNH', type: 'float', qualifier: 'in' }, + { name: 'dotTH', type: 'float', qualifier: 'in' }, + { name: 'dotBH', type: 'float', qualifier: 'in' } + ] +} ); + +// GGX Distribution, Schlick Fresnel, GGX_SmithCorrelated Visibility +const BRDF_GGX = /*@__PURE__*/ Fn( ( { lightDirection, f0, f90, roughness, f, normalView: normalView$1 = normalView, USE_IRIDESCENCE, USE_ANISOTROPY } ) => { + + const alpha = roughness.pow2(); // UE4's roughness + + const halfDir = lightDirection.add( positionViewDirection ).normalize(); + + const dotNL = normalView$1.dot( lightDirection ).clamp(); + const dotNV = normalView$1.dot( positionViewDirection ).clamp(); // @ TODO: Move to core dotNV + const dotNH = normalView$1.dot( halfDir ).clamp(); + const dotVH = positionViewDirection.dot( halfDir ).clamp(); + + let F = F_Schlick( { f0, f90, dotVH } ); + let V, D; + + if ( defined( USE_IRIDESCENCE ) ) { + + F = iridescence.mix( F, f ); + + } + + if ( defined( USE_ANISOTROPY ) ) { + + const dotTL = anisotropyT.dot( lightDirection ); + const dotTV = anisotropyT.dot( positionViewDirection ); + const dotTH = anisotropyT.dot( halfDir ); + const dotBL = anisotropyB.dot( lightDirection ); + const dotBV = anisotropyB.dot( positionViewDirection ); + const dotBH = anisotropyB.dot( halfDir ); + + V = V_GGX_SmithCorrelated_Anisotropic( { alphaT, alphaB: alpha, dotTV, dotBV, dotTL, dotBL, dotNV, dotNL } ); + D = D_GGX_Anisotropic( { alphaT, alphaB: alpha, dotNH, dotTH, dotBH } ); + + } else { + + V = V_GGX_SmithCorrelated( { alpha, dotNL, dotNV } ); + D = D_GGX( { alpha, dotNH } ); + + } + + return F.mul( V ).mul( D ); + +} ); // validated + +// Analytical approximation of the DFG LUT, one half of the +// split-sum approximation used in indirect specular lighting. +// via 'environmentBRDF' from "Physically Based Shading on Mobile" +// https://www.unrealengine.com/blog/physically-based-shading-on-mobile +const DFGApprox = /*@__PURE__*/ Fn( ( { roughness, dotNV } ) => { + + const c0 = vec4( -1, -0.0275, -0.572, 0.022 ); + + const c1 = vec4( 1, 0.0425, 1.04, -0.04 ); + + const r = roughness.mul( c0 ).add( c1 ); + + const a004 = r.x.mul( r.x ).min( dotNV.mul( -9.28 ).exp2() ).mul( r.x ).add( r.y ); + + const fab = vec2( -1.04, 1.04 ).mul( a004 ).add( r.zw ); + + return fab; + +} ).setLayout( { + name: 'DFGApprox', + type: 'vec2', + inputs: [ + { name: 'roughness', type: 'float' }, + { name: 'dotNV', type: 'vec3' } + ] +} ); + +const EnvironmentBRDF = /*@__PURE__*/ Fn( ( inputs ) => { + + const { dotNV, specularColor, specularF90, roughness } = inputs; + + const fab = DFGApprox( { dotNV, roughness } ); + return specularColor.mul( fab.x ).add( specularF90.mul( fab.y ) ); + +} ); + +const Schlick_to_F0 = /*@__PURE__*/ Fn( ( { f, f90, dotVH } ) => { + + const x = dotVH.oneMinus().saturate(); + const x2 = x.mul( x ); + const x5 = x.mul( x2, x2 ).clamp( 0, .9999 ); + + return f.sub( vec3( f90 ).mul( x5 ) ).div( x5.oneMinus() ); + +} ).setLayout( { + name: 'Schlick_to_F0', + type: 'vec3', + inputs: [ + { name: 'f', type: 'vec3' }, + { name: 'f90', type: 'float' }, + { name: 'dotVH', type: 'float' } + ] +} ); + +// https://github.com/google/filament/blob/master/shaders/src/brdf.fs +const D_Charlie = /*@__PURE__*/ Fn( ( { roughness, dotNH } ) => { + + const alpha = roughness.pow2(); + + // Estevez and Kulla 2017, "Production Friendly Microfacet Sheen BRDF" + const invAlpha = float( 1.0 ).div( alpha ); + const cos2h = dotNH.pow2(); + const sin2h = cos2h.oneMinus().max( 0.0078125 ); // 2^(-14/2), so sin2h^2 > 0 in fp16 + + return float( 2.0 ).add( invAlpha ).mul( sin2h.pow( invAlpha.mul( 0.5 ) ) ).div( 2.0 * Math.PI ); + +} ).setLayout( { + name: 'D_Charlie', + type: 'float', + inputs: [ + { name: 'roughness', type: 'float' }, + { name: 'dotNH', type: 'float' } + ] +} ); + +// https://github.com/google/filament/blob/master/shaders/src/brdf.fs +const V_Neubelt = /*@__PURE__*/ Fn( ( { dotNV, dotNL } ) => { + + // Neubelt and Pettineo 2013, "Crafting a Next-gen Material Pipeline for The Order: 1886" + return float( 1.0 ).div( float( 4.0 ).mul( dotNL.add( dotNV ).sub( dotNL.mul( dotNV ) ) ) ); + +} ).setLayout( { + name: 'V_Neubelt', + type: 'float', + inputs: [ + { name: 'dotNV', type: 'float' }, + { name: 'dotNL', type: 'float' } + ] +} ); + +const BRDF_Sheen = /*@__PURE__*/ Fn( ( { lightDirection } ) => { + + const halfDir = lightDirection.add( positionViewDirection ).normalize(); + + const dotNL = normalView.dot( lightDirection ).clamp(); + const dotNV = normalView.dot( positionViewDirection ).clamp(); + const dotNH = normalView.dot( halfDir ).clamp(); + + const D = D_Charlie( { roughness: sheenRoughness, dotNH } ); + const V = V_Neubelt( { dotNV, dotNL } ); + + return sheen.mul( D ).mul( V ); + +} ); + +// Rect Area Light + +// Real-Time Polygonal-Light Shading with Linearly Transformed Cosines +// by Eric Heitz, Jonathan Dupuy, Stephen Hill and David Neubelt +// code: https://github.com/selfshadow/ltc_code/ + +const LTC_Uv = /*@__PURE__*/ Fn( ( { N, V, roughness } ) => { + + const LUT_SIZE = 64.0; + const LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE; + const LUT_BIAS = 0.5 / LUT_SIZE; + + const dotNV = N.dot( V ).saturate(); + + // texture parameterized by sqrt( GGX alpha ) and sqrt( 1 - cos( theta ) ) + const uv = vec2( roughness, dotNV.oneMinus().sqrt() ); + + uv.assign( uv.mul( LUT_SCALE ).add( LUT_BIAS ) ); + + return uv; + +} ).setLayout( { + name: 'LTC_Uv', + type: 'vec2', + inputs: [ + { name: 'N', type: 'vec3' }, + { name: 'V', type: 'vec3' }, + { name: 'roughness', type: 'float' } + ] +} ); + +const LTC_ClippedSphereFormFactor = /*@__PURE__*/ Fn( ( { f } ) => { + + // Real-Time Area Lighting: a Journey from Research to Production (p.102) + // An approximation of the form factor of a horizon-clipped rectangle. + + const l = f.length(); + + return max$1( l.mul( l ).add( f.z ).div( l.add( 1.0 ) ), 0 ); + +} ).setLayout( { + name: 'LTC_ClippedSphereFormFactor', + type: 'float', + inputs: [ + { name: 'f', type: 'vec3' } + ] +} ); + +const LTC_EdgeVectorFormFactor = /*@__PURE__*/ Fn( ( { v1, v2 } ) => { + + const x = v1.dot( v2 ); + const y = x.abs().toVar(); + + // rational polynomial approximation to theta / sin( theta ) / 2PI + const a = y.mul( 0.0145206 ).add( 0.4965155 ).mul( y ).add( 0.8543985 ).toVar(); + const b = y.add( 4.1616724 ).mul( y ).add( 3.4175940 ).toVar(); + const v = a.div( b ); + + const theta_sintheta = x.greaterThan( 0.0 ).select( v, max$1( x.mul( x ).oneMinus(), 1e-7 ).inverseSqrt().mul( 0.5 ).sub( v ) ); + + return v1.cross( v2 ).mul( theta_sintheta ); + +} ).setLayout( { + name: 'LTC_EdgeVectorFormFactor', + type: 'vec3', + inputs: [ + { name: 'v1', type: 'vec3' }, + { name: 'v2', type: 'vec3' } + ] +} ); + +const LTC_Evaluate = /*@__PURE__*/ Fn( ( { N, V, P, mInv, p0, p1, p2, p3 } ) => { + + // bail if point is on back side of plane of light + // assumes ccw winding order of light vertices + const v1 = p1.sub( p0 ).toVar(); + const v2 = p3.sub( p0 ).toVar(); + + const lightNormal = v1.cross( v2 ); + const result = vec3().toVar(); + + If( lightNormal.dot( P.sub( p0 ) ).greaterThanEqual( 0.0 ), () => { + + // construct orthonormal basis around N + const T1 = V.sub( N.mul( V.dot( N ) ) ).normalize(); + const T2 = N.cross( T1 ).negate(); // negated from paper; possibly due to a different handedness of world coordinate system + + // compute transform + const mat = mInv.mul( mat3( T1, T2, N ).transpose() ).toVar(); + + // transform rect + // & project rect onto sphere + const coords0 = mat.mul( p0.sub( P ) ).normalize().toVar(); + const coords1 = mat.mul( p1.sub( P ) ).normalize().toVar(); + const coords2 = mat.mul( p2.sub( P ) ).normalize().toVar(); + const coords3 = mat.mul( p3.sub( P ) ).normalize().toVar(); + + // calculate vector form factor + const vectorFormFactor = vec3( 0 ).toVar(); + vectorFormFactor.addAssign( LTC_EdgeVectorFormFactor( { v1: coords0, v2: coords1 } ) ); + vectorFormFactor.addAssign( LTC_EdgeVectorFormFactor( { v1: coords1, v2: coords2 } ) ); + vectorFormFactor.addAssign( LTC_EdgeVectorFormFactor( { v1: coords2, v2: coords3 } ) ); + vectorFormFactor.addAssign( LTC_EdgeVectorFormFactor( { v1: coords3, v2: coords0 } ) ); + + // adjust for horizon clipping + result.assign( vec3( LTC_ClippedSphereFormFactor( { f: vectorFormFactor } ) ) ); + + } ); + + return result; + +} ).setLayout( { + name: 'LTC_Evaluate', + type: 'vec3', + inputs: [ + { name: 'N', type: 'vec3' }, + { name: 'V', type: 'vec3' }, + { name: 'P', type: 'vec3' }, + { name: 'mInv', type: 'mat3' }, + { name: 'p0', type: 'vec3' }, + { name: 'p1', type: 'vec3' }, + { name: 'p2', type: 'vec3' }, + { name: 'p3', type: 'vec3' } + ] +} ); + +const LTC_Evaluate_Volume = /*@__PURE__*/ Fn( ( { P, p0, p1, p2, p3 } ) => { + + // bail if point is on back side of plane of light + // assumes ccw winding order of light vertices + const v1 = p1.sub( p0 ).toVar(); + const v2 = p3.sub( p0 ).toVar(); + + const lightNormal = v1.cross( v2 ); + const result = vec3().toVar(); + + If( lightNormal.dot( P.sub( p0 ) ).greaterThanEqual( 0.0 ), () => { + + // transform rect + // & project rect onto sphere + const coords0 = p0.sub( P ).normalize().toVar(); + const coords1 = p1.sub( P ).normalize().toVar(); + const coords2 = p2.sub( P ).normalize().toVar(); + const coords3 = p3.sub( P ).normalize().toVar(); + + // calculate vector form factor + const vectorFormFactor = vec3( 0 ).toVar(); + vectorFormFactor.addAssign( LTC_EdgeVectorFormFactor( { v1: coords0, v2: coords1 } ) ); + vectorFormFactor.addAssign( LTC_EdgeVectorFormFactor( { v1: coords1, v2: coords2 } ) ); + vectorFormFactor.addAssign( LTC_EdgeVectorFormFactor( { v1: coords2, v2: coords3 } ) ); + vectorFormFactor.addAssign( LTC_EdgeVectorFormFactor( { v1: coords3, v2: coords0 } ) ); + + // adjust for horizon clipping + result.assign( vec3( LTC_ClippedSphereFormFactor( { f: vectorFormFactor.abs() } ) ) ); + + } ); + + return result; + +} ).setLayout( { + name: 'LTC_Evaluate', + type: 'vec3', + inputs: [ + { name: 'P', type: 'vec3' }, + { name: 'p0', type: 'vec3' }, + { name: 'p1', type: 'vec3' }, + { name: 'p2', type: 'vec3' }, + { name: 'p3', type: 'vec3' } + ] +} ); + +// Mipped Bicubic Texture Filtering by N8 +// https://www.shadertoy.com/view/Dl2SDW + +const bC = 1.0 / 6.0; + +const w0 = ( a ) => mul( bC, mul( a, mul( a, a.negate().add( 3.0 ) ).sub( 3.0 ) ).add( 1.0 ) ); + +const w1 = ( a ) => mul( bC, mul( a, mul( a, mul( 3.0, a ).sub( 6.0 ) ) ).add( 4.0 ) ); + +const w2 = ( a ) => mul( bC, mul( a, mul( a, mul( -3, a ).add( 3.0 ) ).add( 3.0 ) ).add( 1.0 ) ); + +const w3 = ( a ) => mul( bC, pow( a, 3 ) ); + +const g0 = ( a ) => w0( a ).add( w1( a ) ); + +const g1 = ( a ) => w2( a ).add( w3( a ) ); + +// h0 and h1 are the two offset functions +const h0 = ( a ) => add( -1, w1( a ).div( w0( a ).add( w1( a ) ) ) ); + +const h1 = ( a ) => add( 1.0, w3( a ).div( w2( a ).add( w3( a ) ) ) ); + +const bicubic = ( textureNode, texelSize, lod ) => { + + const uv = textureNode.uvNode; + const uvScaled = mul( uv, texelSize.zw ).add( 0.5 ); + + const iuv = floor( uvScaled ); + const fuv = fract( uvScaled ); + + const g0x = g0( fuv.x ); + const g1x = g1( fuv.x ); + const h0x = h0( fuv.x ); + const h1x = h1( fuv.x ); + const h0y = h0( fuv.y ); + const h1y = h1( fuv.y ); + + const p0 = vec2( iuv.x.add( h0x ), iuv.y.add( h0y ) ).sub( 0.5 ).mul( texelSize.xy ); + const p1 = vec2( iuv.x.add( h1x ), iuv.y.add( h0y ) ).sub( 0.5 ).mul( texelSize.xy ); + const p2 = vec2( iuv.x.add( h0x ), iuv.y.add( h1y ) ).sub( 0.5 ).mul( texelSize.xy ); + const p3 = vec2( iuv.x.add( h1x ), iuv.y.add( h1y ) ).sub( 0.5 ).mul( texelSize.xy ); + + const a = g0( fuv.y ).mul( add( g0x.mul( textureNode.sample( p0 ).level( lod ) ), g1x.mul( textureNode.sample( p1 ).level( lod ) ) ) ); + const b = g1( fuv.y ).mul( add( g0x.mul( textureNode.sample( p2 ).level( lod ) ), g1x.mul( textureNode.sample( p3 ).level( lod ) ) ) ); + + return a.add( b ); + +}; + +/** + * Applies mipped bicubic texture filtering to the given texture node. + * + * @tsl + * @function + * @param {TextureNode} textureNode - The texture node that should be filtered. + * @param {Node} lodNode - Defines the LOD to sample from. + * @return {Node} The filtered texture sample. + */ +const textureBicubicLevel = /*@__PURE__*/ Fn( ( [ textureNode, lodNode ] ) => { + + const fLodSize = vec2( textureNode.size( int( lodNode ) ) ); + const cLodSize = vec2( textureNode.size( int( lodNode.add( 1.0 ) ) ) ); + const fLodSizeInv = div( 1.0, fLodSize ); + const cLodSizeInv = div( 1.0, cLodSize ); + const fSample = bicubic( textureNode, vec4( fLodSizeInv, fLodSize ), floor( lodNode ) ); + const cSample = bicubic( textureNode, vec4( cLodSizeInv, cLodSize ), ceil( lodNode ) ); + + return fract( lodNode ).mix( fSample, cSample ); + +} ); + +/** + * Applies mipped bicubic texture filtering to the given texture node. + * + * @tsl + * @function + * @param {TextureNode} textureNode - The texture node that should be filtered. + * @param {Node} [strength] - Defines the strength of the bicubic filtering. + * @return {Node} The filtered texture sample. + */ +const textureBicubic = /*@__PURE__*/ Fn( ( [ textureNode, strength ] ) => { + + const lod = strength.mul( maxMipLevel( textureNode ) ); + + return textureBicubicLevel( textureNode, lod ); + +} ); + +// +// Transmission +// + +const getVolumeTransmissionRay = /*@__PURE__*/ Fn( ( [ n, v, thickness, ior, modelMatrix ] ) => { + + // Direction of refracted light. + const refractionVector = vec3( refract( v.negate(), normalize( n ), div( 1.0, ior ) ) ); + + // Compute rotation-independent scaling of the model matrix. + const modelScale = vec3( + length( modelMatrix[ 0 ].xyz ), + length( modelMatrix[ 1 ].xyz ), + length( modelMatrix[ 2 ].xyz ) + ); + + // The thickness is specified in local space. + return normalize( refractionVector ).mul( thickness.mul( modelScale ) ); + +} ).setLayout( { + name: 'getVolumeTransmissionRay', + type: 'vec3', + inputs: [ + { name: 'n', type: 'vec3' }, + { name: 'v', type: 'vec3' }, + { name: 'thickness', type: 'float' }, + { name: 'ior', type: 'float' }, + { name: 'modelMatrix', type: 'mat4' } + ] +} ); + +const applyIorToRoughness = /*@__PURE__*/ Fn( ( [ roughness, ior ] ) => { + + // Scale roughness with IOR so that an IOR of 1.0 results in no microfacet refraction and + // an IOR of 1.5 results in the default amount of microfacet refraction. + return roughness.mul( clamp( ior.mul( 2.0 ).sub( 2.0 ), 0.0, 1.0 ) ); + +} ).setLayout( { + name: 'applyIorToRoughness', + type: 'float', + inputs: [ + { name: 'roughness', type: 'float' }, + { name: 'ior', type: 'float' } + ] +} ); + +const viewportBackSideTexture = /*@__PURE__*/ viewportMipTexture(); +const viewportFrontSideTexture = /*@__PURE__*/ viewportMipTexture(); + +const getTransmissionSample = /*@__PURE__*/ Fn( ( [ fragCoord, roughness, ior ], { material } ) => { + + const vTexture = material.side === BackSide ? viewportBackSideTexture : viewportFrontSideTexture; + + const transmissionSample = vTexture.sample( fragCoord ); + //const transmissionSample = viewportMipTexture( fragCoord ); + + const lod = log2( screenSize.x ).mul( applyIorToRoughness( roughness, ior ) ); + + return textureBicubicLevel( transmissionSample, lod ); + +} ); + +const volumeAttenuation = /*@__PURE__*/ Fn( ( [ transmissionDistance, attenuationColor, attenuationDistance ] ) => { + + If( attenuationDistance.notEqual( 0 ), () => { + + // Compute light attenuation using Beer's law. + const attenuationCoefficient = log( attenuationColor ).negate().div( attenuationDistance ); + const transmittance = exp( attenuationCoefficient.negate().mul( transmissionDistance ) ); + + return transmittance; + + } ); + + // Attenuation distance is +∞, i.e. the transmitted color is not attenuated at all. + return vec3( 1.0 ); + +} ).setLayout( { + name: 'volumeAttenuation', + type: 'vec3', + inputs: [ + { name: 'transmissionDistance', type: 'float' }, + { name: 'attenuationColor', type: 'vec3' }, + { name: 'attenuationDistance', type: 'float' } + ] +} ); + +const getIBLVolumeRefraction = /*@__PURE__*/ Fn( ( [ n, v, roughness, diffuseColor, specularColor, specularF90, position, modelMatrix, viewMatrix, projMatrix, ior, thickness, attenuationColor, attenuationDistance, dispersion ] ) => { + + let transmittedLight, transmittance; + + if ( dispersion ) { + + transmittedLight = vec4().toVar(); + transmittance = vec3().toVar(); + + const halfSpread = ior.sub( 1.0 ).mul( dispersion.mul( 0.025 ) ); + const iors = vec3( ior.sub( halfSpread ), ior, ior.add( halfSpread ) ); + + Loop( { start: 0, end: 3 }, ( { i } ) => { + + const ior = iors.element( i ); + + const transmissionRay = getVolumeTransmissionRay( n, v, thickness, ior, modelMatrix ); + const refractedRayExit = position.add( transmissionRay ); + + // Project refracted vector on the framebuffer, while mapping to normalized device coordinates. + const ndcPos = projMatrix.mul( viewMatrix.mul( vec4( refractedRayExit, 1.0 ) ) ); + const refractionCoords = vec2( ndcPos.xy.div( ndcPos.w ) ).toVar(); + refractionCoords.addAssign( 1.0 ); + refractionCoords.divAssign( 2.0 ); + refractionCoords.assign( vec2( refractionCoords.x, refractionCoords.y.oneMinus() ) ); // webgpu + + // Sample framebuffer to get pixel the refracted ray hits. + const transmissionSample = getTransmissionSample( refractionCoords, roughness, ior ); + + transmittedLight.element( i ).assign( transmissionSample.element( i ) ); + transmittedLight.a.addAssign( transmissionSample.a ); + + transmittance.element( i ).assign( diffuseColor.element( i ).mul( volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance ).element( i ) ) ); + + } ); + + transmittedLight.a.divAssign( 3.0 ); + + } else { + + const transmissionRay = getVolumeTransmissionRay( n, v, thickness, ior, modelMatrix ); + const refractedRayExit = position.add( transmissionRay ); + + // Project refracted vector on the framebuffer, while mapping to normalized device coordinates. + const ndcPos = projMatrix.mul( viewMatrix.mul( vec4( refractedRayExit, 1.0 ) ) ); + const refractionCoords = vec2( ndcPos.xy.div( ndcPos.w ) ).toVar(); + refractionCoords.addAssign( 1.0 ); + refractionCoords.divAssign( 2.0 ); + refractionCoords.assign( vec2( refractionCoords.x, refractionCoords.y.oneMinus() ) ); // webgpu + + // Sample framebuffer to get pixel the refracted ray hits. + transmittedLight = getTransmissionSample( refractionCoords, roughness, ior ); + transmittance = diffuseColor.mul( volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance ) ); + + } + + const attenuatedColor = transmittance.rgb.mul( transmittedLight.rgb ); + const dotNV = n.dot( v ).clamp(); + + // Get the specular component. + const F = vec3( EnvironmentBRDF( { // n, v, specularColor, specularF90, roughness + dotNV, + specularColor, + specularF90, + roughness + } ) ); + + // As less light is transmitted, the opacity should be increased. This simple approximation does a decent job + // of modulating a CSS background, and has no effect when the buffer is opaque, due to a solid object or clear color. + const transmittanceFactor = transmittance.r.add( transmittance.g, transmittance.b ).div( 3.0 ); + + return vec4( F.oneMinus().mul( attenuatedColor ), transmittedLight.a.oneMinus().mul( transmittanceFactor ).oneMinus() ); + +} ); + +// +// Iridescence +// + +// XYZ to linear-sRGB color space +const XYZ_TO_REC709 = /*@__PURE__*/ mat3( + 3.2404542, -0.969266, 0.0556434, + -1.5371385, 1.8760108, -0.2040259, + -0.4985314, 0.0415560, 1.0572252 +); + +// Assume air interface for top +// Note: We don't handle the case fresnel0 == 1 +const Fresnel0ToIor = ( fresnel0 ) => { + + const sqrtF0 = fresnel0.sqrt(); + return vec3( 1.0 ).add( sqrtF0 ).div( vec3( 1.0 ).sub( sqrtF0 ) ); + +}; + +// ior is a value between 1.0 and 3.0. 1.0 is air interface +const IorToFresnel0 = ( transmittedIor, incidentIor ) => { + + return transmittedIor.sub( incidentIor ).div( transmittedIor.add( incidentIor ) ).pow2(); + +}; + +// Fresnel equations for dielectric/dielectric interfaces. +// Ref: https://belcour.github.io/blog/research/2017/05/01/brdf-thin-film.html +// Evaluation XYZ sensitivity curves in Fourier space +const evalSensitivity = ( OPD, shift ) => { + + const phase = OPD.mul( 2.0 * Math.PI * 1.0e-9 ); + const val = vec3( 5.4856e-13, 4.4201e-13, 5.2481e-13 ); + const pos = vec3( 1.6810e+06, 1.7953e+06, 2.2084e+06 ); + const VAR = vec3( 4.3278e+09, 9.3046e+09, 6.6121e+09 ); + + const x = float( 9.7470e-14 * Math.sqrt( 2.0 * Math.PI * 4.5282e+09 ) ).mul( phase.mul( 2.2399e+06 ).add( shift.x ).cos() ).mul( phase.pow2().mul( -45282e5 ).exp() ); + + let xyz = val.mul( VAR.mul( 2.0 * Math.PI ).sqrt() ).mul( pos.mul( phase ).add( shift ).cos() ).mul( phase.pow2().negate().mul( VAR ).exp() ); + xyz = vec3( xyz.x.add( x ), xyz.y, xyz.z ).div( 1.0685e-7 ); + + const rgb = XYZ_TO_REC709.mul( xyz ); + + return rgb; + +}; + +const evalIridescence = /*@__PURE__*/ Fn( ( { outsideIOR, eta2, cosTheta1, thinFilmThickness, baseF0 } ) => { + + // Force iridescenceIOR -> outsideIOR when thinFilmThickness -> 0.0 + const iridescenceIOR = mix( outsideIOR, eta2, smoothstep( 0.0, 0.03, thinFilmThickness ) ); + // Evaluate the cosTheta on the base layer (Snell law) + const sinTheta2Sq = outsideIOR.div( iridescenceIOR ).pow2().mul( cosTheta1.pow2().oneMinus() ); + + // Handle TIR: + const cosTheta2Sq = sinTheta2Sq.oneMinus(); + + If( cosTheta2Sq.lessThan( 0 ), () => { + + return vec3( 1.0 ); + + } ); + + const cosTheta2 = cosTheta2Sq.sqrt(); + + // First interface + const R0 = IorToFresnel0( iridescenceIOR, outsideIOR ); + const R12 = F_Schlick( { f0: R0, f90: 1.0, dotVH: cosTheta1 } ); + //const R21 = R12; + const T121 = R12.oneMinus(); + const phi12 = iridescenceIOR.lessThan( outsideIOR ).select( Math.PI, 0.0 ); + const phi21 = float( Math.PI ).sub( phi12 ); + + // Second interface + const baseIOR = Fresnel0ToIor( baseF0.clamp( 0.0, 0.9999 ) ); // guard against 1.0 + const R1 = IorToFresnel0( baseIOR, iridescenceIOR.toVec3() ); + const R23 = F_Schlick( { f0: R1, f90: 1.0, dotVH: cosTheta2 } ); + const phi23 = vec3( + baseIOR.x.lessThan( iridescenceIOR ).select( Math.PI, 0.0 ), + baseIOR.y.lessThan( iridescenceIOR ).select( Math.PI, 0.0 ), + baseIOR.z.lessThan( iridescenceIOR ).select( Math.PI, 0.0 ) + ); + + // Phase shift + const OPD = iridescenceIOR.mul( thinFilmThickness, cosTheta2, 2.0 ); + const phi = vec3( phi21 ).add( phi23 ); + + // Compound terms + const R123 = R12.mul( R23 ).clamp( 1e-5, 0.9999 ); + const r123 = R123.sqrt(); + const Rs = T121.pow2().mul( R23 ).div( vec3( 1.0 ).sub( R123 ) ); + + // Reflectance term for m = 0 (DC term amplitude) + const C0 = R12.add( Rs ); + const I = C0.toVar(); + + // Reflectance term for m > 0 (pairs of diracs) + const Cm = Rs.sub( T121 ).toVar(); + + Loop( { start: 1, end: 2, condition: '<=', name: 'm' }, ( { m } ) => { + + Cm.mulAssign( r123 ); + const Sm = evalSensitivity( float( m ).mul( OPD ), float( m ).mul( phi ) ).mul( 2.0 ); + I.addAssign( Cm.mul( Sm ) ); + + } ); + + // Since out of gamut colors might be produced, negative color values are clamped to 0. + return I.max( vec3( 0.0 ) ); + +} ).setLayout( { + name: 'evalIridescence', + type: 'vec3', + inputs: [ + { name: 'outsideIOR', type: 'float' }, + { name: 'eta2', type: 'float' }, + { name: 'cosTheta1', type: 'float' }, + { name: 'thinFilmThickness', type: 'float' }, + { name: 'baseF0', type: 'vec3' } + ] +} ); + +// +// Sheen +// + +// This is a curve-fit approximation to the "Charlie sheen" BRDF integrated over the hemisphere from +// Estevez and Kulla 2017, "Production Friendly Microfacet Sheen BRDF". The analysis can be found +// in the Sheen section of https://drive.google.com/file/d/1T0D1VSyR4AllqIJTQAraEIzjlb5h4FKH/view?usp=sharing +const IBLSheenBRDF = /*@__PURE__*/ Fn( ( { normal, viewDir, roughness } ) => { + + const dotNV = normal.dot( viewDir ).saturate(); + + const r2 = roughness.pow2(); + + const a = select( + roughness.lessThan( 0.25 ), + float( -339.2 ).mul( r2 ).add( float( 161.4 ).mul( roughness ) ).sub( 25.9 ), + float( -8.48 ).mul( r2 ).add( float( 14.3 ).mul( roughness ) ).sub( 9.95 ) + ); + + const b = select( + roughness.lessThan( 0.25 ), + float( 44.0 ).mul( r2 ).sub( float( 23.7 ).mul( roughness ) ).add( 3.26 ), + float( 1.97 ).mul( r2 ).sub( float( 3.27 ).mul( roughness ) ).add( 0.72 ) + ); + + const DG = select( roughness.lessThan( 0.25 ), 0.0, float( 0.1 ).mul( roughness ).sub( 0.025 ) ).add( a.mul( dotNV ).add( b ).exp() ); + + return DG.mul( 1.0 / Math.PI ).saturate(); + +} ); + +const clearcoatF0 = vec3( 0.04 ); +const clearcoatF90 = float( 1 ); + + +/** + * Represents the lighting model for a PBR material. + * + * @augments LightingModel + */ +class PhysicalLightingModel extends LightingModel { + + /** + * Constructs a new physical lighting model. + * + * @param {boolean} [clearcoat=false] - Whether clearcoat is supported or not. + * @param {boolean} [sheen=false] - Whether sheen is supported or not. + * @param {boolean} [iridescence=false] - Whether iridescence is supported or not. + * @param {boolean} [anisotropy=false] - Whether anisotropy is supported or not. + * @param {boolean} [transmission=false] - Whether transmission is supported or not. + * @param {boolean} [dispersion=false] - Whether dispersion is supported or not. + */ + constructor( clearcoat = false, sheen = false, iridescence = false, anisotropy = false, transmission = false, dispersion = false ) { + + super(); + + /** + * Whether clearcoat is supported or not. + * + * @type {boolean} + * @default false + */ + this.clearcoat = clearcoat; + + /** + * Whether sheen is supported or not. + * + * @type {boolean} + * @default false + */ + this.sheen = sheen; + + /** + * Whether iridescence is supported or not. + * + * @type {boolean} + * @default false + */ + this.iridescence = iridescence; + + /** + * Whether anisotropy is supported or not. + * + * @type {boolean} + * @default false + */ + this.anisotropy = anisotropy; + + /** + * Whether transmission is supported or not. + * + * @type {boolean} + * @default false + */ + this.transmission = transmission; + + /** + * Whether dispersion is supported or not. + * + * @type {boolean} + * @default false + */ + this.dispersion = dispersion; + + /** + * The clear coat radiance. + * + * @type {?Node} + * @default null + */ + this.clearcoatRadiance = null; + + /** + * The clear coat specular direct. + * + * @type {?Node} + * @default null + */ + this.clearcoatSpecularDirect = null; + + /** + * The clear coat specular indirect. + * + * @type {?Node} + * @default null + */ + this.clearcoatSpecularIndirect = null; + + /** + * The sheen specular direct. + * + * @type {?Node} + * @default null + */ + this.sheenSpecularDirect = null; + + /** + * The sheen specular indirect. + * + * @type {?Node} + * @default null + */ + this.sheenSpecularIndirect = null; + + /** + * The iridescence Fresnel. + * + * @type {?Node} + * @default null + */ + this.iridescenceFresnel = null; + + /** + * The iridescence F0. + * + * @type {?Node} + * @default null + */ + this.iridescenceF0 = null; + + } + + /** + * Depending on what features are requested, the method prepares certain node variables + * which are later used for lighting computations. + * + * @param {NodeBuilder} builder - The current node builder. + */ + start( builder ) { + + if ( this.clearcoat === true ) { + + this.clearcoatRadiance = vec3().toVar( 'clearcoatRadiance' ); + this.clearcoatSpecularDirect = vec3().toVar( 'clearcoatSpecularDirect' ); + this.clearcoatSpecularIndirect = vec3().toVar( 'clearcoatSpecularIndirect' ); + + } + + if ( this.sheen === true ) { + + this.sheenSpecularDirect = vec3().toVar( 'sheenSpecularDirect' ); + this.sheenSpecularIndirect = vec3().toVar( 'sheenSpecularIndirect' ); + + } + + if ( this.iridescence === true ) { + + const dotNVi = normalView.dot( positionViewDirection ).clamp(); + + this.iridescenceFresnel = evalIridescence( { + outsideIOR: float( 1.0 ), + eta2: iridescenceIOR, + cosTheta1: dotNVi, + thinFilmThickness: iridescenceThickness, + baseF0: specularColor + } ); + + this.iridescenceF0 = Schlick_to_F0( { f: this.iridescenceFresnel, f90: 1.0, dotVH: dotNVi } ); + + } + + if ( this.transmission === true ) { + + const position = positionWorld; + const v = cameraPosition.sub( positionWorld ).normalize(); // TODO: Create Node for this, same issue in MaterialX + const n = normalWorld; + + const context = builder.context; + + context.backdrop = getIBLVolumeRefraction( + n, + v, + roughness, + diffuseColor, + specularColor, + specularF90, // specularF90 + position, // positionWorld + modelWorldMatrix, // modelMatrix + cameraViewMatrix, // viewMatrix + cameraProjectionMatrix, // projMatrix + ior, + thickness, + attenuationColor, + attenuationDistance, + this.dispersion ? dispersion : null + ); + + context.backdropAlpha = transmission; + + diffuseColor.a.mulAssign( mix( 1, context.backdrop.a, transmission ) ); + + } + + super.start( builder ); + + } + + // Fdez-Agüera's "Multiple-Scattering Microfacet Model for Real-Time Image Based Lighting" + // Approximates multi-scattering in order to preserve energy. + // http://www.jcgt.org/published/0008/01/03/ + + computeMultiscattering( singleScatter, multiScatter, specularF90 ) { + + const dotNV = normalView.dot( positionViewDirection ).clamp(); // @ TODO: Move to core dotNV + + const fab = DFGApprox( { roughness, dotNV } ); + + const Fr = this.iridescenceF0 ? iridescence.mix( specularColor, this.iridescenceF0 ) : specularColor; + + const FssEss = Fr.mul( fab.x ).add( specularF90.mul( fab.y ) ); + + const Ess = fab.x.add( fab.y ); + const Ems = Ess.oneMinus(); + + const Favg = specularColor.add( specularColor.oneMinus().mul( 0.047619 ) ); // 1/21 + const Fms = FssEss.mul( Favg ).div( Ems.mul( Favg ).oneMinus() ); + + singleScatter.addAssign( FssEss ); + multiScatter.addAssign( Fms.mul( Ems ) ); + + } + + /** + * Implements the direct light. + * + * @param {Object} lightData - The light data. + * @param {NodeBuilder} builder - The current node builder. + */ + direct( { lightDirection, lightColor, reflectedLight } ) { + + const dotNL = normalView.dot( lightDirection ).clamp(); + const irradiance = dotNL.mul( lightColor ); + + if ( this.sheen === true ) { + + this.sheenSpecularDirect.addAssign( irradiance.mul( BRDF_Sheen( { lightDirection } ) ) ); + + } + + if ( this.clearcoat === true ) { + + const dotNLcc = clearcoatNormalView.dot( lightDirection ).clamp(); + const ccIrradiance = dotNLcc.mul( lightColor ); + + this.clearcoatSpecularDirect.addAssign( ccIrradiance.mul( BRDF_GGX( { lightDirection, f0: clearcoatF0, f90: clearcoatF90, roughness: clearcoatRoughness, normalView: clearcoatNormalView } ) ) ); + + } + + reflectedLight.directDiffuse.addAssign( irradiance.mul( BRDF_Lambert( { diffuseColor: diffuseColor.rgb } ) ) ); + + reflectedLight.directSpecular.addAssign( irradiance.mul( BRDF_GGX( { lightDirection, f0: specularColor, f90: 1, roughness, iridescence: this.iridescence, f: this.iridescenceFresnel, USE_IRIDESCENCE: this.iridescence, USE_ANISOTROPY: this.anisotropy } ) ) ); + + } + + /** + * This method is intended for implementing the direct light term for + * rect area light nodes. + * + * @param {Object} input - The input data. + * @param {NodeBuilder} builder - The current node builder. + */ + directRectArea( { lightColor, lightPosition, halfWidth, halfHeight, reflectedLight, ltc_1, ltc_2 } ) { + + const p0 = lightPosition.add( halfWidth ).sub( halfHeight ); // counterclockwise; light shines in local neg z direction + const p1 = lightPosition.sub( halfWidth ).sub( halfHeight ); + const p2 = lightPosition.sub( halfWidth ).add( halfHeight ); + const p3 = lightPosition.add( halfWidth ).add( halfHeight ); + + const N = normalView; + const V = positionViewDirection; + const P = positionView.toVar(); + + const uv = LTC_Uv( { N, V, roughness } ); + + const t1 = ltc_1.sample( uv ).toVar(); + const t2 = ltc_2.sample( uv ).toVar(); + + const mInv = mat3( + vec3( t1.x, 0, t1.y ), + vec3( 0, 1, 0 ), + vec3( t1.z, 0, t1.w ) + ).toVar(); + + // LTC Fresnel Approximation by Stephen Hill + // http://blog.selfshadow.com/publications/s2016-advances/s2016_ltc_fresnel.pdf + const fresnel = specularColor.mul( t2.x ).add( specularColor.oneMinus().mul( t2.y ) ).toVar(); + + reflectedLight.directSpecular.addAssign( lightColor.mul( fresnel ).mul( LTC_Evaluate( { N, V, P, mInv, p0, p1, p2, p3 } ) ) ); + + reflectedLight.directDiffuse.addAssign( lightColor.mul( diffuseColor ).mul( LTC_Evaluate( { N, V, P, mInv: mat3( 1, 0, 0, 0, 1, 0, 0, 0, 1 ), p0, p1, p2, p3 } ) ) ); + + } + + /** + * Implements the indirect lighting. + * + * @param {NodeBuilder} builder - The current node builder. + */ + indirect( builder ) { + + this.indirectDiffuse( builder ); + this.indirectSpecular( builder ); + this.ambientOcclusion( builder ); + + } + + /** + * Implements the indirect diffuse term. + * + * @param {NodeBuilder} builder - The current node builder. + */ + indirectDiffuse( builder ) { + + const { irradiance, reflectedLight } = builder.context; + + reflectedLight.indirectDiffuse.addAssign( irradiance.mul( BRDF_Lambert( { diffuseColor } ) ) ); + + } + + /** + * Implements the indirect specular term. + * + * @param {NodeBuilder} builder - The current node builder. + */ + indirectSpecular( builder ) { + + const { radiance, iblIrradiance, reflectedLight } = builder.context; + + if ( this.sheen === true ) { + + this.sheenSpecularIndirect.addAssign( iblIrradiance.mul( + sheen, + IBLSheenBRDF( { + normal: normalView, + viewDir: positionViewDirection, + roughness: sheenRoughness + } ) + ) ); + + } + + if ( this.clearcoat === true ) { + + const dotNVcc = clearcoatNormalView.dot( positionViewDirection ).clamp(); + + const clearcoatEnv = EnvironmentBRDF( { + dotNV: dotNVcc, + specularColor: clearcoatF0, + specularF90: clearcoatF90, + roughness: clearcoatRoughness + } ); + + this.clearcoatSpecularIndirect.addAssign( this.clearcoatRadiance.mul( clearcoatEnv ) ); + + } + + // Both indirect specular and indirect diffuse light accumulate here + + const singleScattering = vec3().toVar( 'singleScattering' ); + const multiScattering = vec3().toVar( 'multiScattering' ); + const cosineWeightedIrradiance = iblIrradiance.mul( 1 / Math.PI ); + + this.computeMultiscattering( singleScattering, multiScattering, specularF90 ); + + const totalScattering = singleScattering.add( multiScattering ); + + const diffuse = diffuseColor.mul( totalScattering.r.max( totalScattering.g ).max( totalScattering.b ).oneMinus() ); + + reflectedLight.indirectSpecular.addAssign( radiance.mul( singleScattering ) ); + reflectedLight.indirectSpecular.addAssign( multiScattering.mul( cosineWeightedIrradiance ) ); + + reflectedLight.indirectDiffuse.addAssign( diffuse.mul( cosineWeightedIrradiance ) ); + + } + + /** + * Implements the ambient occlusion term. + * + * @param {NodeBuilder} builder - The current node builder. + */ + ambientOcclusion( builder ) { + + const { ambientOcclusion, reflectedLight } = builder.context; + + const dotNV = normalView.dot( positionViewDirection ).clamp(); // @ TODO: Move to core dotNV + + const aoNV = dotNV.add( ambientOcclusion ); + const aoExp = roughness.mul( -16 ).oneMinus().negate().exp2(); + + const aoNode = ambientOcclusion.sub( aoNV.pow( aoExp ).oneMinus() ).clamp(); + + if ( this.clearcoat === true ) { + + this.clearcoatSpecularIndirect.mulAssign( ambientOcclusion ); + + } + + if ( this.sheen === true ) { + + this.sheenSpecularIndirect.mulAssign( ambientOcclusion ); + + } + + reflectedLight.indirectDiffuse.mulAssign( ambientOcclusion ); + reflectedLight.indirectSpecular.mulAssign( aoNode ); + + } + + /** + * Used for final lighting accumulations depending on the requested features. + * + * @param {NodeBuilder} builder - The current node builder. + */ + finish( { context } ) { + + const { outgoingLight } = context; + + if ( this.clearcoat === true ) { + + const dotNVcc = clearcoatNormalView.dot( positionViewDirection ).clamp(); + + const Fcc = F_Schlick( { + dotVH: dotNVcc, + f0: clearcoatF0, + f90: clearcoatF90 + } ); + + const clearcoatLight = outgoingLight.mul( clearcoat.mul( Fcc ).oneMinus() ).add( this.clearcoatSpecularDirect.add( this.clearcoatSpecularIndirect ).mul( clearcoat ) ); + + outgoingLight.assign( clearcoatLight ); + + } + + if ( this.sheen === true ) { + + const sheenEnergyComp = sheen.r.max( sheen.g ).max( sheen.b ).mul( 0.157 ).oneMinus(); + const sheenLight = outgoingLight.mul( sheenEnergyComp ).add( this.sheenSpecularDirect, this.sheenSpecularIndirect ); + + outgoingLight.assign( sheenLight ); + + } + + } + +} + +// These defines must match with PMREMGenerator + +const cubeUV_r0 = /*@__PURE__*/ float( 1.0 ); +const cubeUV_m0 = /*@__PURE__*/ float( -2 ); +const cubeUV_r1 = /*@__PURE__*/ float( 0.8 ); +const cubeUV_m1 = /*@__PURE__*/ float( -1 ); +const cubeUV_r4 = /*@__PURE__*/ float( 0.4 ); +const cubeUV_m4 = /*@__PURE__*/ float( 2.0 ); +const cubeUV_r5 = /*@__PURE__*/ float( 0.305 ); +const cubeUV_m5 = /*@__PURE__*/ float( 3.0 ); +const cubeUV_r6 = /*@__PURE__*/ float( 0.21 ); +const cubeUV_m6 = /*@__PURE__*/ float( 4.0 ); + +const cubeUV_minMipLevel = /*@__PURE__*/ float( 4.0 ); +const cubeUV_minTileSize = /*@__PURE__*/ float( 16.0 ); + +// These shader functions convert between the UV coordinates of a single face of +// a cubemap, the 0-5 integer index of a cube face, and the direction vector for +// sampling a textureCube (not generally normalized ). + +const getFace = /*@__PURE__*/ Fn( ( [ direction ] ) => { + + const absDirection = vec3( abs( direction ) ).toVar(); + const face = float( -1 ).toVar(); + + If( absDirection.x.greaterThan( absDirection.z ), () => { + + If( absDirection.x.greaterThan( absDirection.y ), () => { + + face.assign( select( direction.x.greaterThan( 0.0 ), 0.0, 3.0 ) ); + + } ).Else( () => { + + face.assign( select( direction.y.greaterThan( 0.0 ), 1.0, 4.0 ) ); + + } ); + + } ).Else( () => { + + If( absDirection.z.greaterThan( absDirection.y ), () => { + + face.assign( select( direction.z.greaterThan( 0.0 ), 2.0, 5.0 ) ); + + } ).Else( () => { + + face.assign( select( direction.y.greaterThan( 0.0 ), 1.0, 4.0 ) ); + + } ); + + } ); + + return face; + +} ).setLayout( { + name: 'getFace', + type: 'float', + inputs: [ + { name: 'direction', type: 'vec3' } + ] +} ); + +// RH coordinate system; PMREM face-indexing convention +const getUV = /*@__PURE__*/ Fn( ( [ direction, face ] ) => { + + const uv = vec2().toVar(); + + If( face.equal( 0.0 ), () => { + + uv.assign( vec2( direction.z, direction.y ).div( abs( direction.x ) ) ); // pos x + + } ).ElseIf( face.equal( 1.0 ), () => { + + uv.assign( vec2( direction.x.negate(), direction.z.negate() ).div( abs( direction.y ) ) ); // pos y + + } ).ElseIf( face.equal( 2.0 ), () => { + + uv.assign( vec2( direction.x.negate(), direction.y ).div( abs( direction.z ) ) ); // pos z + + } ).ElseIf( face.equal( 3.0 ), () => { + + uv.assign( vec2( direction.z.negate(), direction.y ).div( abs( direction.x ) ) ); // neg x + + } ).ElseIf( face.equal( 4.0 ), () => { + + uv.assign( vec2( direction.x.negate(), direction.z ).div( abs( direction.y ) ) ); // neg y + + } ).Else( () => { + + uv.assign( vec2( direction.x, direction.y ).div( abs( direction.z ) ) ); // neg z + + } ); + + return mul( 0.5, uv.add( 1.0 ) ); + +} ).setLayout( { + name: 'getUV', + type: 'vec2', + inputs: [ + { name: 'direction', type: 'vec3' }, + { name: 'face', type: 'float' } + ] +} ); + +const roughnessToMip = /*@__PURE__*/ Fn( ( [ roughness ] ) => { + + const mip = float( 0.0 ).toVar(); + + If( roughness.greaterThanEqual( cubeUV_r1 ), () => { + + mip.assign( cubeUV_r0.sub( roughness ).mul( cubeUV_m1.sub( cubeUV_m0 ) ).div( cubeUV_r0.sub( cubeUV_r1 ) ).add( cubeUV_m0 ) ); + + } ).ElseIf( roughness.greaterThanEqual( cubeUV_r4 ), () => { + + mip.assign( cubeUV_r1.sub( roughness ).mul( cubeUV_m4.sub( cubeUV_m1 ) ).div( cubeUV_r1.sub( cubeUV_r4 ) ).add( cubeUV_m1 ) ); + + } ).ElseIf( roughness.greaterThanEqual( cubeUV_r5 ), () => { + + mip.assign( cubeUV_r4.sub( roughness ).mul( cubeUV_m5.sub( cubeUV_m4 ) ).div( cubeUV_r4.sub( cubeUV_r5 ) ).add( cubeUV_m4 ) ); + + } ).ElseIf( roughness.greaterThanEqual( cubeUV_r6 ), () => { + + mip.assign( cubeUV_r5.sub( roughness ).mul( cubeUV_m6.sub( cubeUV_m5 ) ).div( cubeUV_r5.sub( cubeUV_r6 ) ).add( cubeUV_m5 ) ); + + } ).Else( () => { + + mip.assign( float( -2 ).mul( log2( mul( 1.16, roughness ) ) ) ); // 1.16 = 1.79^0.25 + + } ); + + return mip; + +} ).setLayout( { + name: 'roughnessToMip', + type: 'float', + inputs: [ + { name: 'roughness', type: 'float' } + ] +} ); + +// RH coordinate system; PMREM face-indexing convention +const getDirection = /*@__PURE__*/ Fn( ( [ uv_immutable, face ] ) => { + + const uv = uv_immutable.toVar(); + uv.assign( mul( 2.0, uv ).sub( 1.0 ) ); + const direction = vec3( uv, 1.0 ).toVar(); + + If( face.equal( 0.0 ), () => { + + direction.assign( direction.zyx ); // ( 1, v, u ) pos x + + } ).ElseIf( face.equal( 1.0 ), () => { + + direction.assign( direction.xzy ); + direction.xz.mulAssign( -1 ); // ( -u, 1, -v ) pos y + + } ).ElseIf( face.equal( 2.0 ), () => { + + direction.x.mulAssign( -1 ); // ( -u, v, 1 ) pos z + + } ).ElseIf( face.equal( 3.0 ), () => { + + direction.assign( direction.zyx ); + direction.xz.mulAssign( -1 ); // ( -1, v, -u ) neg x + + } ).ElseIf( face.equal( 4.0 ), () => { + + direction.assign( direction.xzy ); + direction.xy.mulAssign( -1 ); // ( -u, -1, v ) neg y + + } ).ElseIf( face.equal( 5.0 ), () => { + + direction.z.mulAssign( -1 ); // ( u, v, -1 ) neg zS + + } ); + + return direction; + +} ).setLayout( { + name: 'getDirection', + type: 'vec3', + inputs: [ + { name: 'uv', type: 'vec2' }, + { name: 'face', type: 'float' } + ] +} ); + +// + +const textureCubeUV = /*@__PURE__*/ Fn( ( [ envMap, sampleDir_immutable, roughness_immutable, CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP ] ) => { + + const roughness = float( roughness_immutable ); + const sampleDir = vec3( sampleDir_immutable ); + + const mip = clamp( roughnessToMip( roughness ), cubeUV_m0, CUBEUV_MAX_MIP ); + const mipF = fract( mip ); + const mipInt = floor( mip ); + const color0 = vec3( bilinearCubeUV( envMap, sampleDir, mipInt, CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP ) ).toVar(); + + If( mipF.notEqual( 0.0 ), () => { + + const color1 = vec3( bilinearCubeUV( envMap, sampleDir, mipInt.add( 1.0 ), CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP ) ).toVar(); + + color0.assign( mix( color0, color1, mipF ) ); + + } ); + + return color0; + +} ); + +const bilinearCubeUV = /*@__PURE__*/ Fn( ( [ envMap, direction_immutable, mipInt_immutable, CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP ] ) => { + + const mipInt = float( mipInt_immutable ).toVar(); + const direction = vec3( direction_immutable ); + const face = float( getFace( direction ) ).toVar(); + const filterInt = float( max$1( cubeUV_minMipLevel.sub( mipInt ), 0.0 ) ).toVar(); + mipInt.assign( max$1( mipInt, cubeUV_minMipLevel ) ); + const faceSize = float( exp2( mipInt ) ).toVar(); + const uv = vec2( getUV( direction, face ).mul( faceSize.sub( 2.0 ) ).add( 1.0 ) ).toVar(); + + If( face.greaterThan( 2.0 ), () => { + + uv.y.addAssign( faceSize ); + face.subAssign( 3.0 ); + + } ); + + uv.x.addAssign( face.mul( faceSize ) ); + uv.x.addAssign( filterInt.mul( mul( 3.0, cubeUV_minTileSize ) ) ); + uv.y.addAssign( mul( 4.0, exp2( CUBEUV_MAX_MIP ).sub( faceSize ) ) ); + uv.x.mulAssign( CUBEUV_TEXEL_WIDTH ); + uv.y.mulAssign( CUBEUV_TEXEL_HEIGHT ); + + return envMap.sample( uv ).grad( vec2(), vec2() ); // disable anisotropic filtering + +} ); + +const getSample = /*@__PURE__*/ Fn( ( { envMap, mipInt, outputDirection, theta, axis, CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP } ) => { + + const cosTheta = cos( theta ); + + // Rodrigues' axis-angle rotation + const sampleDirection = outputDirection.mul( cosTheta ) + .add( axis.cross( outputDirection ).mul( sin( theta ) ) ) + .add( axis.mul( axis.dot( outputDirection ).mul( cosTheta.oneMinus() ) ) ); + + return bilinearCubeUV( envMap, sampleDirection, mipInt, CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP ); + +} ); + +const blur = /*@__PURE__*/ Fn( ( { n, latitudinal, poleAxis, outputDirection, weights, samples, dTheta, mipInt, envMap, CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP } ) => { + + const axis = vec3( select( latitudinal, poleAxis, cross( poleAxis, outputDirection ) ) ).toVar(); + + If( axis.equal( vec3( 0.0 ) ), () => { + + axis.assign( vec3( outputDirection.z, 0.0, outputDirection.x.negate() ) ); + + } ); + + axis.assign( normalize( axis ) ); + + const gl_FragColor = vec3().toVar(); + gl_FragColor.addAssign( weights.element( 0 ).mul( getSample( { theta: 0.0, axis, outputDirection, mipInt, envMap, CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP } ) ) ); + + Loop( { start: int( 1 ), end: n }, ( { i } ) => { + + If( i.greaterThanEqual( samples ), () => { + + Break(); + + } ); + + const theta = float( dTheta.mul( float( i ) ) ).toVar(); + gl_FragColor.addAssign( weights.element( i ).mul( getSample( { theta: theta.mul( -1 ), axis, outputDirection, mipInt, envMap, CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP } ) ) ); + gl_FragColor.addAssign( weights.element( i ).mul( getSample( { theta, axis, outputDirection, mipInt, envMap, CUBEUV_TEXEL_WIDTH, CUBEUV_TEXEL_HEIGHT, CUBEUV_MAX_MIP } ) ) ); + + } ); + + return vec4( gl_FragColor, 1 ); + +} ); + +const LOD_MIN = 4; + +// The standard deviations (radians) associated with the extra mips. These are +// chosen to approximate a Trowbridge-Reitz distribution function times the +// geometric shadowing function. These sigma values squared must match the +// variance #defines in cube_uv_reflection_fragment.glsl.js. +const EXTRA_LOD_SIGMA = [ 0.125, 0.215, 0.35, 0.446, 0.526, 0.582 ]; + +// The maximum length of the blur for loop. Smaller sigmas will use fewer +// samples and exit early, but not recompile the shader. +const MAX_SAMPLES = 20; + +const _flatCamera = /*@__PURE__*/ new OrthographicCamera( -1, 1, 1, -1, 0, 1 ); +const _cubeCamera = /*@__PURE__*/ new PerspectiveCamera( 90, 1 ); +const _clearColor$2 = /*@__PURE__*/ new Color(); +let _oldTarget = null; +let _oldActiveCubeFace = 0; +let _oldActiveMipmapLevel = 0; + +// Golden Ratio +const PHI = ( 1 + Math.sqrt( 5 ) ) / 2; +const INV_PHI = 1 / PHI; + +// Vertices of a dodecahedron (except the opposites, which represent the +// same axis), used as axis directions evenly spread on a sphere. +const _axisDirections = [ + /*@__PURE__*/ new Vector3( - PHI, INV_PHI, 0 ), + /*@__PURE__*/ new Vector3( PHI, INV_PHI, 0 ), + /*@__PURE__*/ new Vector3( - INV_PHI, 0, PHI ), + /*@__PURE__*/ new Vector3( INV_PHI, 0, PHI ), + /*@__PURE__*/ new Vector3( 0, PHI, - INV_PHI ), + /*@__PURE__*/ new Vector3( 0, PHI, INV_PHI ), + /*@__PURE__*/ new Vector3( -1, 1, -1 ), + /*@__PURE__*/ new Vector3( 1, 1, -1 ), + /*@__PURE__*/ new Vector3( -1, 1, 1 ), + /*@__PURE__*/ new Vector3( 1, 1, 1 ) +]; + +const _origin = /*@__PURE__*/ new Vector3(); + +// maps blur materials to their uniforms dictionary + +const _uniformsMap = new WeakMap(); + +// WebGPU Face indices +const _faceLib = [ + 3, 1, 5, + 0, 4, 2 +]; + +const _direction = /*@__PURE__*/ getDirection( uv$1(), attribute( 'faceIndex' ) ).normalize(); +const _outputDirection = /*@__PURE__*/ vec3( _direction.x, _direction.y, _direction.z ); + +/** + * This class generates a Prefiltered, Mipmapped Radiance Environment Map + * (PMREM) from a cubeMap environment texture. This allows different levels of + * blur to be quickly accessed based on material roughness. It is packed into a + * special CubeUV format that allows us to perform custom interpolation so that + * we can support nonlinear formats such as RGBE. Unlike a traditional mipmap + * chain, it only goes down to the LOD_MIN level (above), and then creates extra + * even more filtered 'mips' at the same LOD_MIN resolution, associated with + * higher roughness levels. In this way we maintain resolution to smoothly + * interpolate diffuse lighting while limiting sampling computation. + * + * Paper: Fast, Accurate Image-Based Lighting: + * {@link https://drive.google.com/file/d/15y8r_UpKlU9SvV4ILb0C3qCPecS8pvLz/view} +*/ +class PMREMGenerator { + + /** + * Constructs a new PMREM generator. + * + * @param {Renderer} renderer - The renderer. + */ + constructor( renderer ) { + + this._renderer = renderer; + this._pingPongRenderTarget = null; + + this._lodMax = 0; + this._cubeSize = 0; + this._lodPlanes = []; + this._sizeLods = []; + this._sigmas = []; + this._lodMeshes = []; + + this._blurMaterial = null; + this._cubemapMaterial = null; + this._equirectMaterial = null; + this._backgroundBox = null; + + } + + get _hasInitialized() { + + return this._renderer.hasInitialized(); + + } + + /** + * Generates a PMREM from a supplied Scene, which can be faster than using an + * image if networking bandwidth is low. Optional sigma specifies a blur radius + * in radians to be applied to the scene before PMREM generation. Optional near + * and far planes ensure the scene is rendered in its entirety. + * + * @param {Scene} scene - The scene to be captured. + * @param {number} [sigma=0] - The blur radius in radians. + * @param {number} [near=0.1] - The near plane distance. + * @param {number} [far=100] - The far plane distance. + * @param {Object} [options={}] - The configuration options. + * @param {number} [options.size=256] - The texture size of the PMREM. + * @param {Vector3} [options.renderTarget=origin] - The position of the internal cube camera that renders the scene. + * @param {?RenderTarget} [options.renderTarget=null] - The render target to use. + * @return {RenderTarget} The resulting PMREM. + * @see {@link PMREMGenerator#fromSceneAsync} + */ + fromScene( scene, sigma = 0, near = 0.1, far = 100, options = {} ) { + + const { + size = 256, + position = _origin, + renderTarget = null, + } = options; + + this._setSize( size ); + + if ( this._hasInitialized === false ) { + + console.warn( 'THREE.PMREMGenerator: .fromScene() called before the backend is initialized. Try using .fromSceneAsync() instead.' ); + + const cubeUVRenderTarget = renderTarget || this._allocateTarget(); + + options.renderTarget = cubeUVRenderTarget; + + this.fromSceneAsync( scene, sigma, near, far, options ); + + return cubeUVRenderTarget; + + } + + _oldTarget = this._renderer.getRenderTarget(); + _oldActiveCubeFace = this._renderer.getActiveCubeFace(); + _oldActiveMipmapLevel = this._renderer.getActiveMipmapLevel(); + + const cubeUVRenderTarget = renderTarget || this._allocateTarget(); + cubeUVRenderTarget.depthBuffer = true; + + this._init( cubeUVRenderTarget ); + + this._sceneToCubeUV( scene, near, far, cubeUVRenderTarget, position ); + + if ( sigma > 0 ) { + + this._blur( cubeUVRenderTarget, 0, 0, sigma ); + + } + + this._applyPMREM( cubeUVRenderTarget ); + + this._cleanup( cubeUVRenderTarget ); + + return cubeUVRenderTarget; + + } + + /** + * Generates a PMREM from a supplied Scene, which can be faster than using an + * image if networking bandwidth is low. Optional sigma specifies a blur radius + * in radians to be applied to the scene before PMREM generation. Optional near + * and far planes ensure the scene is rendered in its entirety (the cubeCamera + * is placed at the origin). + * + * @param {Scene} scene - The scene to be captured. + * @param {number} [sigma=0] - The blur radius in radians. + * @param {number} [near=0.1] - The near plane distance. + * @param {number} [far=100] - The far plane distance. + * @param {Object} [options={}] - The configuration options. + * @param {number} [options.size=256] - The texture size of the PMREM. + * @param {Vector3} [options.position=origin] - The position of the internal cube camera that renders the scene. + * @param {?RenderTarget} [options.renderTarget=null] - The render target to use. + * @return {Promise} A Promise that resolve with the PMREM when the generation has been finished. + * @see {@link PMREMGenerator#fromScene} + */ + async fromSceneAsync( scene, sigma = 0, near = 0.1, far = 100, options = {} ) { + + if ( this._hasInitialized === false ) await this._renderer.init(); + + return this.fromScene( scene, sigma, near, far, options ); + + } + + /** + * Generates a PMREM from an equirectangular texture, which can be either LDR + * or HDR. The ideal input image size is 1k (1024 x 512), + * as this matches best with the 256 x 256 cubemap output. + * + * @param {Texture} equirectangular - The equirectangular texture to be converted. + * @param {?RenderTarget} [renderTarget=null] - The render target to use. + * @return {RenderTarget} The resulting PMREM. + * @see {@link PMREMGenerator#fromEquirectangularAsync} + */ + fromEquirectangular( equirectangular, renderTarget = null ) { + + if ( this._hasInitialized === false ) { + + console.warn( 'THREE.PMREMGenerator: .fromEquirectangular() called before the backend is initialized. Try using .fromEquirectangularAsync() instead.' ); + + this._setSizeFromTexture( equirectangular ); + + const cubeUVRenderTarget = renderTarget || this._allocateTarget(); + + this.fromEquirectangularAsync( equirectangular, cubeUVRenderTarget ); + + return cubeUVRenderTarget; + + } + + return this._fromTexture( equirectangular, renderTarget ); + + } + + /** + * Generates a PMREM from an equirectangular texture, which can be either LDR + * or HDR. The ideal input image size is 1k (1024 x 512), + * as this matches best with the 256 x 256 cubemap output. + * + * @param {Texture} equirectangular - The equirectangular texture to be converted. + * @param {?RenderTarget} [renderTarget=null] - The render target to use. + * @return {Promise} The resulting PMREM. + * @see {@link PMREMGenerator#fromEquirectangular} + */ + async fromEquirectangularAsync( equirectangular, renderTarget = null ) { + + if ( this._hasInitialized === false ) await this._renderer.init(); + + return this._fromTexture( equirectangular, renderTarget ); + + } + + /** + * Generates a PMREM from an cubemap texture, which can be either LDR + * or HDR. The ideal input cube size is 256 x 256, + * as this matches best with the 256 x 256 cubemap output. + * + * @param {Texture} cubemap - The cubemap texture to be converted. + * @param {?RenderTarget} [renderTarget=null] - The render target to use. + * @return {RenderTarget} The resulting PMREM. + * @see {@link PMREMGenerator#fromCubemapAsync} + */ + fromCubemap( cubemap, renderTarget = null ) { + + if ( this._hasInitialized === false ) { + + console.warn( 'THREE.PMREMGenerator: .fromCubemap() called before the backend is initialized. Try using .fromCubemapAsync() instead.' ); + + this._setSizeFromTexture( cubemap ); + + const cubeUVRenderTarget = renderTarget || this._allocateTarget(); + + this.fromCubemapAsync( cubemap, renderTarget ); + + return cubeUVRenderTarget; + + } + + return this._fromTexture( cubemap, renderTarget ); + + } + + /** + * Generates a PMREM from an cubemap texture, which can be either LDR + * or HDR. The ideal input cube size is 256 x 256, + * with the 256 x 256 cubemap output. + * + * @param {Texture} cubemap - The cubemap texture to be converted. + * @param {?RenderTarget} [renderTarget=null] - The render target to use. + * @return {Promise} The resulting PMREM. + * @see {@link PMREMGenerator#fromCubemap} + */ + async fromCubemapAsync( cubemap, renderTarget = null ) { + + if ( this._hasInitialized === false ) await this._renderer.init(); + + return this._fromTexture( cubemap, renderTarget ); + + } + + /** + * Pre-compiles the cubemap shader. You can get faster start-up by invoking this method during + * your texture's network fetch for increased concurrency. + * + * @returns {Promise} + */ + async compileCubemapShader() { + + if ( this._cubemapMaterial === null ) { + + this._cubemapMaterial = _getCubemapMaterial(); + await this._compileMaterial( this._cubemapMaterial ); + + } + + } + + /** + * Pre-compiles the equirectangular shader. You can get faster start-up by invoking this method during + * your texture's network fetch for increased concurrency. + * + * @returns {Promise} + */ + async compileEquirectangularShader() { + + if ( this._equirectMaterial === null ) { + + this._equirectMaterial = _getEquirectMaterial(); + await this._compileMaterial( this._equirectMaterial ); + + } + + } + + /** + * Disposes of the PMREMGenerator's internal memory. Note that PMREMGenerator is a static class, + * so you should not need more than one PMREMGenerator object. If you do, calling dispose() on + * one of them will cause any others to also become unusable. + */ + dispose() { + + this._dispose(); + + if ( this._cubemapMaterial !== null ) this._cubemapMaterial.dispose(); + if ( this._equirectMaterial !== null ) this._equirectMaterial.dispose(); + if ( this._backgroundBox !== null ) { + + this._backgroundBox.geometry.dispose(); + this._backgroundBox.material.dispose(); + + } + + } + + // private interface + + _setSizeFromTexture( texture ) { + + if ( texture.mapping === CubeReflectionMapping || texture.mapping === CubeRefractionMapping ) { + + this._setSize( texture.image.length === 0 ? 16 : ( texture.image[ 0 ].width || texture.image[ 0 ].image.width ) ); + + } else { // Equirectangular + + this._setSize( texture.image.width / 4 ); + + } + + } + + _setSize( cubeSize ) { + + this._lodMax = Math.floor( Math.log2( cubeSize ) ); + this._cubeSize = Math.pow( 2, this._lodMax ); + + } + + _dispose() { + + if ( this._blurMaterial !== null ) this._blurMaterial.dispose(); + + if ( this._pingPongRenderTarget !== null ) this._pingPongRenderTarget.dispose(); + + for ( let i = 0; i < this._lodPlanes.length; i ++ ) { + + this._lodPlanes[ i ].dispose(); + + } + + } + + _cleanup( outputTarget ) { + + this._renderer.setRenderTarget( _oldTarget, _oldActiveCubeFace, _oldActiveMipmapLevel ); + outputTarget.scissorTest = false; + _setViewport( outputTarget, 0, 0, outputTarget.width, outputTarget.height ); + + } + + _fromTexture( texture, renderTarget ) { + + this._setSizeFromTexture( texture ); + + _oldTarget = this._renderer.getRenderTarget(); + _oldActiveCubeFace = this._renderer.getActiveCubeFace(); + _oldActiveMipmapLevel = this._renderer.getActiveMipmapLevel(); + + const cubeUVRenderTarget = renderTarget || this._allocateTarget(); + this._init( cubeUVRenderTarget ); + this._textureToCubeUV( texture, cubeUVRenderTarget ); + this._applyPMREM( cubeUVRenderTarget ); + this._cleanup( cubeUVRenderTarget ); + + return cubeUVRenderTarget; + + } + + _allocateTarget() { + + const width = 3 * Math.max( this._cubeSize, 16 * 7 ); + const height = 4 * this._cubeSize; + + const cubeUVRenderTarget = _createRenderTarget( width, height ); + + return cubeUVRenderTarget; + + } + + _init( renderTarget ) { + + if ( this._pingPongRenderTarget === null || this._pingPongRenderTarget.width !== renderTarget.width || this._pingPongRenderTarget.height !== renderTarget.height ) { + + if ( this._pingPongRenderTarget !== null ) { + + this._dispose(); + + } + + this._pingPongRenderTarget = _createRenderTarget( renderTarget.width, renderTarget.height ); + + const { _lodMax } = this; + ( { sizeLods: this._sizeLods, lodPlanes: this._lodPlanes, sigmas: this._sigmas, lodMeshes: this._lodMeshes } = _createPlanes( _lodMax ) ); + + this._blurMaterial = _getBlurShader( _lodMax, renderTarget.width, renderTarget.height ); + + } + + } + + async _compileMaterial( material ) { + + const tmpMesh = new Mesh( this._lodPlanes[ 0 ], material ); + await this._renderer.compile( tmpMesh, _flatCamera ); + + } + + _sceneToCubeUV( scene, near, far, cubeUVRenderTarget, position ) { + + const cubeCamera = _cubeCamera; + cubeCamera.near = near; + cubeCamera.far = far; + + // px, py, pz, nx, ny, nz + const upSign = [ 1, 1, 1, 1, -1, 1 ]; + const forwardSign = [ 1, -1, 1, -1, 1, -1 ]; + + const renderer = this._renderer; + + const originalAutoClear = renderer.autoClear; + + renderer.getClearColor( _clearColor$2 ); + + renderer.autoClear = false; + + let backgroundBox = this._backgroundBox; + + if ( backgroundBox === null ) { + + const backgroundMaterial = new MeshBasicMaterial( { + name: 'PMREM.Background', + side: BackSide, + depthWrite: false, + depthTest: false + } ); + + backgroundBox = new Mesh( new BoxGeometry(), backgroundMaterial ); + + } + + let useSolidColor = false; + const background = scene.background; + + if ( background ) { + + if ( background.isColor ) { + + backgroundBox.material.color.copy( background ); + scene.background = null; + useSolidColor = true; + + } + + } else { + + backgroundBox.material.color.copy( _clearColor$2 ); + useSolidColor = true; + + } + + renderer.setRenderTarget( cubeUVRenderTarget ); + + renderer.clear(); + + if ( useSolidColor ) { + + renderer.render( backgroundBox, cubeCamera ); + + } + + for ( let i = 0; i < 6; i ++ ) { + + const col = i % 3; + + if ( col === 0 ) { + + cubeCamera.up.set( 0, upSign[ i ], 0 ); + cubeCamera.position.set( position.x, position.y, position.z ); + cubeCamera.lookAt( position.x + forwardSign[ i ], position.y, position.z ); + + } else if ( col === 1 ) { + + cubeCamera.up.set( 0, 0, upSign[ i ] ); + cubeCamera.position.set( position.x, position.y, position.z ); + cubeCamera.lookAt( position.x, position.y + forwardSign[ i ], position.z ); + + + } else { + + cubeCamera.up.set( 0, upSign[ i ], 0 ); + cubeCamera.position.set( position.x, position.y, position.z ); + cubeCamera.lookAt( position.x, position.y, position.z + forwardSign[ i ] ); + + + } + + const size = this._cubeSize; + + _setViewport( cubeUVRenderTarget, col * size, i > 2 ? size : 0, size, size ); + + renderer.render( scene, cubeCamera ); + + } + + renderer.autoClear = originalAutoClear; + scene.background = background; + + } + + _textureToCubeUV( texture, cubeUVRenderTarget ) { + + const renderer = this._renderer; + + const isCubeTexture = ( texture.mapping === CubeReflectionMapping || texture.mapping === CubeRefractionMapping ); + + if ( isCubeTexture ) { + + if ( this._cubemapMaterial === null ) { + + this._cubemapMaterial = _getCubemapMaterial( texture ); + + } + + } else { + + if ( this._equirectMaterial === null ) { + + this._equirectMaterial = _getEquirectMaterial( texture ); + + } + + } + + const material = isCubeTexture ? this._cubemapMaterial : this._equirectMaterial; + material.fragmentNode.value = texture; + + const mesh = this._lodMeshes[ 0 ]; + mesh.material = material; + + const size = this._cubeSize; + + _setViewport( cubeUVRenderTarget, 0, 0, 3 * size, 2 * size ); + + renderer.setRenderTarget( cubeUVRenderTarget ); + renderer.render( mesh, _flatCamera ); + + } + + _applyPMREM( cubeUVRenderTarget ) { + + const renderer = this._renderer; + const autoClear = renderer.autoClear; + renderer.autoClear = false; + const n = this._lodPlanes.length; + + for ( let i = 1; i < n; i ++ ) { + + const sigma = Math.sqrt( this._sigmas[ i ] * this._sigmas[ i ] - this._sigmas[ i - 1 ] * this._sigmas[ i - 1 ] ); + + const poleAxis = _axisDirections[ ( n - i - 1 ) % _axisDirections.length ]; + + this._blur( cubeUVRenderTarget, i - 1, i, sigma, poleAxis ); + + } + + renderer.autoClear = autoClear; + + } + + /** + * This is a two-pass Gaussian blur for a cubemap. Normally this is done + * vertically and horizontally, but this breaks down on a cube. Here we apply + * the blur latitudinally (around the poles), and then longitudinally (towards + * the poles) to approximate the orthogonally-separable blur. It is least + * accurate at the poles, but still does a decent job. + * + * @private + * @param {RenderTarget} cubeUVRenderTarget - The cubemap render target. + * @param {number} lodIn - The input level-of-detail. + * @param {number} lodOut - The output level-of-detail. + * @param {number} sigma - The blur radius in radians. + * @param {Vector3} [poleAxis] - The pole axis. + */ + _blur( cubeUVRenderTarget, lodIn, lodOut, sigma, poleAxis ) { + + const pingPongRenderTarget = this._pingPongRenderTarget; + + this._halfBlur( + cubeUVRenderTarget, + pingPongRenderTarget, + lodIn, + lodOut, + sigma, + 'latitudinal', + poleAxis ); + + this._halfBlur( + pingPongRenderTarget, + cubeUVRenderTarget, + lodOut, + lodOut, + sigma, + 'longitudinal', + poleAxis ); + + } + + _halfBlur( targetIn, targetOut, lodIn, lodOut, sigmaRadians, direction, poleAxis ) { + + const renderer = this._renderer; + const blurMaterial = this._blurMaterial; + + if ( direction !== 'latitudinal' && direction !== 'longitudinal' ) { + + console.error( 'blur direction must be either latitudinal or longitudinal!' ); + + } + + // Number of standard deviations at which to cut off the discrete approximation. + const STANDARD_DEVIATIONS = 3; + + const blurMesh = this._lodMeshes[ lodOut ]; + blurMesh.material = blurMaterial; + + const blurUniforms = _uniformsMap.get( blurMaterial ); + + const pixels = this._sizeLods[ lodIn ] - 1; + const radiansPerPixel = isFinite( sigmaRadians ) ? Math.PI / ( 2 * pixels ) : 2 * Math.PI / ( 2 * MAX_SAMPLES - 1 ); + const sigmaPixels = sigmaRadians / radiansPerPixel; + const samples = isFinite( sigmaRadians ) ? 1 + Math.floor( STANDARD_DEVIATIONS * sigmaPixels ) : MAX_SAMPLES; + + if ( samples > MAX_SAMPLES ) { + + console.warn( `sigmaRadians, ${ + sigmaRadians}, is too large and will clip, as it requested ${ + samples} samples when the maximum is set to ${MAX_SAMPLES}` ); + + } + + const weights = []; + let sum = 0; + + for ( let i = 0; i < MAX_SAMPLES; ++ i ) { + + const x = i / sigmaPixels; + const weight = Math.exp( - x * x / 2 ); + weights.push( weight ); + + if ( i === 0 ) { + + sum += weight; + + } else if ( i < samples ) { + + sum += 2 * weight; + + } + + } + + for ( let i = 0; i < weights.length; i ++ ) { + + weights[ i ] = weights[ i ] / sum; + + } + + targetIn.texture.frame = ( targetIn.texture.frame || 0 ) + 1; + + blurUniforms.envMap.value = targetIn.texture; + blurUniforms.samples.value = samples; + blurUniforms.weights.array = weights; + blurUniforms.latitudinal.value = direction === 'latitudinal' ? 1 : 0; + + if ( poleAxis ) { + + blurUniforms.poleAxis.value = poleAxis; + + } + + const { _lodMax } = this; + blurUniforms.dTheta.value = radiansPerPixel; + blurUniforms.mipInt.value = _lodMax - lodIn; + + const outputSize = this._sizeLods[ lodOut ]; + const x = 3 * outputSize * ( lodOut > _lodMax - LOD_MIN ? lodOut - _lodMax + LOD_MIN : 0 ); + const y = 4 * ( this._cubeSize - outputSize ); + + _setViewport( targetOut, x, y, 3 * outputSize, 2 * outputSize ); + renderer.setRenderTarget( targetOut ); + renderer.render( blurMesh, _flatCamera ); + + } + +} + +function _createPlanes( lodMax ) { + + const lodPlanes = []; + const sizeLods = []; + const sigmas = []; + const lodMeshes = []; + + let lod = lodMax; + + const totalLods = lodMax - LOD_MIN + 1 + EXTRA_LOD_SIGMA.length; + + for ( let i = 0; i < totalLods; i ++ ) { + + const sizeLod = Math.pow( 2, lod ); + sizeLods.push( sizeLod ); + let sigma = 1.0 / sizeLod; + + if ( i > lodMax - LOD_MIN ) { + + sigma = EXTRA_LOD_SIGMA[ i - lodMax + LOD_MIN - 1 ]; + + } else if ( i === 0 ) { + + sigma = 0; + + } + + sigmas.push( sigma ); + + const texelSize = 1.0 / ( sizeLod - 2 ); + const min = - texelSize; + const max = 1 + texelSize; + const uv1 = [ min, min, max, min, max, max, min, min, max, max, min, max ]; + + const cubeFaces = 6; + const vertices = 6; + const positionSize = 3; + const uvSize = 2; + const faceIndexSize = 1; + + const position = new Float32Array( positionSize * vertices * cubeFaces ); + const uv = new Float32Array( uvSize * vertices * cubeFaces ); + const faceIndex = new Float32Array( faceIndexSize * vertices * cubeFaces ); + + for ( let face = 0; face < cubeFaces; face ++ ) { + + const x = ( face % 3 ) * 2 / 3 - 1; + const y = face > 2 ? 0 : -1; + const coordinates = [ + x, y, 0, + x + 2 / 3, y, 0, + x + 2 / 3, y + 1, 0, + x, y, 0, + x + 2 / 3, y + 1, 0, + x, y + 1, 0 + ]; + + const faceIdx = _faceLib[ face ]; + position.set( coordinates, positionSize * vertices * faceIdx ); + uv.set( uv1, uvSize * vertices * faceIdx ); + const fill = [ faceIdx, faceIdx, faceIdx, faceIdx, faceIdx, faceIdx ]; + faceIndex.set( fill, faceIndexSize * vertices * faceIdx ); + + } + + const planes = new BufferGeometry(); + planes.setAttribute( 'position', new BufferAttribute( position, positionSize ) ); + planes.setAttribute( 'uv', new BufferAttribute( uv, uvSize ) ); + planes.setAttribute( 'faceIndex', new BufferAttribute( faceIndex, faceIndexSize ) ); + lodPlanes.push( planes ); + lodMeshes.push( new Mesh( planes, null ) ); + + if ( lod > LOD_MIN ) { + + lod --; + + } + + } + + return { lodPlanes, sizeLods, sigmas, lodMeshes }; + +} + +function _createRenderTarget( width, height ) { + + const params = { + magFilter: LinearFilter, + minFilter: LinearFilter, + generateMipmaps: false, + type: HalfFloatType, + format: RGBAFormat, + colorSpace: LinearSRGBColorSpace, + //depthBuffer: false + }; + + const cubeUVRenderTarget = new RenderTarget( width, height, params ); + cubeUVRenderTarget.texture.mapping = CubeUVReflectionMapping; + cubeUVRenderTarget.texture.name = 'PMREM.cubeUv'; + cubeUVRenderTarget.texture.isPMREMTexture = true; + cubeUVRenderTarget.scissorTest = true; + return cubeUVRenderTarget; + +} + +function _setViewport( target, x, y, width, height ) { + + target.viewport.set( x, y, width, height ); + target.scissor.set( x, y, width, height ); + +} + +function _getMaterial( type ) { + + const material = new NodeMaterial(); + material.depthTest = false; + material.depthWrite = false; + material.blending = NoBlending; + material.name = `PMREM_${ type }`; + + return material; + +} + +function _getBlurShader( lodMax, width, height ) { + + const weights = uniformArray( new Array( MAX_SAMPLES ).fill( 0 ) ); + const poleAxis = uniform( new Vector3( 0, 1, 0 ) ); + const dTheta = uniform( 0 ); + const n = float( MAX_SAMPLES ); + const latitudinal = uniform( 0 ); // false, bool + const samples = uniform( 1 ); // int + const envMap = texture( null ); + const mipInt = uniform( 0 ); // int + const CUBEUV_TEXEL_WIDTH = float( 1 / width ); + const CUBEUV_TEXEL_HEIGHT = float( 1 / height ); + const CUBEUV_MAX_MIP = float( lodMax ); + + const materialUniforms = { + n, + latitudinal, + weights, + poleAxis, + outputDirection: _outputDirection, + dTheta, + samples, + envMap, + mipInt, + CUBEUV_TEXEL_WIDTH, + CUBEUV_TEXEL_HEIGHT, + CUBEUV_MAX_MIP + }; + + const material = _getMaterial( 'blur' ); + material.fragmentNode = blur( { ...materialUniforms, latitudinal: latitudinal.equal( 1 ) } ); + + _uniformsMap.set( material, materialUniforms ); + + return material; + +} + +function _getCubemapMaterial( envTexture ) { + + const material = _getMaterial( 'cubemap' ); + material.fragmentNode = cubeTexture( envTexture, _outputDirection ); + + return material; + +} + +function _getEquirectMaterial( envTexture ) { + + const material = _getMaterial( 'equirect' ); + material.fragmentNode = texture( envTexture, equirectUV( _outputDirection ), 0 ); + + return material; + +} + +const _cache = new WeakMap(); + +/** + * Generates the cubeUV size based on the given image height. + * + * @private + * @param {number} imageHeight - The image height. + * @return {{texelWidth: number,texelHeight: number, maxMip: number}} The result object. + */ +function _generateCubeUVSize( imageHeight ) { + + const maxMip = Math.log2( imageHeight ) - 2; + + const texelHeight = 1.0 / imageHeight; + + const texelWidth = 1.0 / ( 3 * Math.max( Math.pow( 2, maxMip ), 7 * 16 ) ); + + return { texelWidth, texelHeight, maxMip }; + +} + +/** + * Generates a PMREM from the given texture. + * + * @private + * @param {Texture} texture - The texture to create the PMREM for. + * @param {Renderer} renderer - The renderer. + * @param {PMREMGenerator} generator - The PMREM generator. + * @return {?Texture} The PMREM. + */ +function _getPMREMFromTexture( texture, renderer, generator ) { + + const cache = _getCache( renderer ); + + let cacheTexture = cache.get( texture ); + + const pmremVersion = cacheTexture !== undefined ? cacheTexture.pmremVersion : -1; + + if ( pmremVersion !== texture.pmremVersion ) { + + const image = texture.image; + + if ( texture.isCubeTexture ) { + + if ( isCubeMapReady( image ) ) { + + cacheTexture = generator.fromCubemap( texture, cacheTexture ); + + } else { + + return null; + + } + + + } else { + + if ( isEquirectangularMapReady( image ) ) { + + cacheTexture = generator.fromEquirectangular( texture, cacheTexture ); + + } else { + + return null; + + } + + } + + cacheTexture.pmremVersion = texture.pmremVersion; + + cache.set( texture, cacheTexture ); + + } + + return cacheTexture.texture; + +} + +/** + * Returns a cache that stores generated PMREMs for the respective textures. + * A cache must be maintained per renderer since PMREMs are render target textures + * which can't be shared across render contexts. + * + * @private + * @param {Renderer} renderer - The renderer. + * @return {WeakMap} The PMREM cache. + */ +function _getCache( renderer ) { + + let rendererCache = _cache.get( renderer ); + + if ( rendererCache === undefined ) { + + rendererCache = new WeakMap(); + _cache.set( renderer, rendererCache ); + + } + + return rendererCache; + +} + +/** + * This node represents a PMREM which is a special type of preprocessed + * environment map intended for PBR materials. + * + * ```js + * const material = new MeshStandardNodeMaterial(); + * material.envNode = pmremTexture( envMap ); + * ``` + * + * @augments TempNode + */ +class PMREMNode extends TempNode { + + static get type() { + + return 'PMREMNode'; + + } + + /** + * Constructs a new function overloading node. + * + * @param {Texture} value - The input texture. + * @param {Node} [uvNode=null] - The uv node. + * @param {Node} [levelNode=null] - The level node. + */ + constructor( value, uvNode = null, levelNode = null ) { + + super( 'vec3' ); + + /** + * Reference to the input texture. + * + * @private + * @type {Texture} + */ + this._value = value; + + /** + * Reference to the generated PMREM. + * + * @private + * @type {Texture | null} + * @default null + */ + this._pmrem = null; + + /** + * The uv node. + * + * @type {Node} + */ + this.uvNode = uvNode; + + /** + * The level node. + * + * @type {Node} + */ + this.levelNode = levelNode; + + /** + * Reference to a PMREM generator. + * + * @private + * @type {?PMREMGenerator} + * @default null + */ + this._generator = null; + + const defaultTexture = new Texture(); + defaultTexture.isRenderTargetTexture = true; + + /** + * The texture node holding the generated PMREM. + * + * @private + * @type {TextureNode} + */ + this._texture = texture( defaultTexture ); + + /** + * A uniform representing the PMREM's width. + * + * @private + * @type {UniformNode} + */ + this._width = uniform( 0 ); + + /** + * A uniform representing the PMREM's height. + * + * @private + * @type {UniformNode} + */ + this._height = uniform( 0 ); + + /** + * A uniform representing the PMREM's max Mip. + * + * @private + * @type {UniformNode} + */ + this._maxMip = uniform( 0 ); + + /** + * The `updateBeforeType` is set to `NodeUpdateType.RENDER`. + * + * @type {string} + * @default 'render' + */ + this.updateBeforeType = NodeUpdateType.RENDER; + + } + + set value( value ) { + + this._value = value; + this._pmrem = null; + + } + + /** + * The node's texture value. + * + * @type {Texture} + */ + get value() { + + return this._value; + + } + + /** + * Uses the given PMREM texture to update internal values. + * + * @param {Texture} texture - The PMREM texture. + */ + updateFromTexture( texture ) { + + const cubeUVSize = _generateCubeUVSize( texture.image.height ); + + this._texture.value = texture; + this._width.value = cubeUVSize.texelWidth; + this._height.value = cubeUVSize.texelHeight; + this._maxMip.value = cubeUVSize.maxMip; + + } + + updateBefore( frame ) { + + let pmrem = this._pmrem; + + const pmremVersion = pmrem ? pmrem.pmremVersion : -1; + const texture = this._value; + + if ( pmremVersion !== texture.pmremVersion ) { + + if ( texture.isPMREMTexture === true ) { + + pmrem = texture; + + } else { + + pmrem = _getPMREMFromTexture( texture, frame.renderer, this._generator ); + + } + + if ( pmrem !== null ) { + + this._pmrem = pmrem; + + this.updateFromTexture( pmrem ); + + } + + } + + } + + setup( builder ) { + + if ( this._generator === null ) { + + this._generator = new PMREMGenerator( builder.renderer ); + + } + + this.updateBefore( builder ); + + // + + let uvNode = this.uvNode; + + if ( uvNode === null && builder.context.getUV ) { + + uvNode = builder.context.getUV( this ); + + } + + // + + uvNode = materialEnvRotation.mul( vec3( uvNode.x, uvNode.y.negate(), uvNode.z ) ); + + // + + let levelNode = this.levelNode; + + if ( levelNode === null && builder.context.getTextureLevel ) { + + levelNode = builder.context.getTextureLevel( this ); + + } + + // + + return textureCubeUV( this._texture, uvNode, levelNode, this._width, this._height, this._maxMip ); + + } + + dispose() { + + super.dispose(); + + if ( this._generator !== null ) this._generator.dispose(); + + } + +} + +/** + * Returns `true` if the given cube map image has been fully loaded. + * + * @private + * @param {?Array<(Image|Object)>} [image] - The cube map image. + * @return {boolean} Whether the given cube map is ready or not. + */ +function isCubeMapReady( image ) { + + if ( image === null || image === undefined ) return false; + + let count = 0; + const length = 6; + + for ( let i = 0; i < length; i ++ ) { + + if ( image[ i ] !== undefined ) count ++; + + } + + return count === length; + + +} + +/** + * Returns `true` if the given equirectangular image has been fully loaded. + * + * @private + * @param {(Image|Object)} image - The equirectangular image. + * @return {boolean} Whether the given cube map is ready or not. + */ +function isEquirectangularMapReady( image ) { + + if ( image === null || image === undefined ) return false; + + return image.height > 0; + +} + +/** + * TSL function for creating a PMREM node. + * + * @tsl + * @function + * @param {Texture} value - The input texture. + * @param {?Node} [uvNode=null] - The uv node. + * @param {?Node} [levelNode=null] - The level node. + * @returns {PMREMNode} + */ +const pmremTexture = /*@__PURE__*/ nodeProxy( PMREMNode ).setParameterLength( 1, 3 ); + +const _envNodeCache = new WeakMap(); + +/** + * Represents a physical model for Image-based lighting (IBL). The environment + * is defined via environment maps in the equirectangular, cube map or cubeUV (PMREM) format. + * `EnvironmentNode` is intended for PBR materials like {@link MeshStandardNodeMaterial}. + * + * @augments LightingNode + */ +class EnvironmentNode extends LightingNode { + + static get type() { + + return 'EnvironmentNode'; + + } + + /** + * Constructs a new environment node. + * + * @param {Node} [envNode=null] - A node representing the environment. + */ + constructor( envNode = null ) { + + super(); + + /** + * A node representing the environment. + * + * @type {?Node} + * @default null + */ + this.envNode = envNode; + + } + + setup( builder ) { + + const { material } = builder; + + let envNode = this.envNode; + + if ( envNode.isTextureNode || envNode.isMaterialReferenceNode ) { + + const value = ( envNode.isTextureNode ) ? envNode.value : material[ envNode.property ]; + + let cacheEnvNode = _envNodeCache.get( value ); + + if ( cacheEnvNode === undefined ) { + + cacheEnvNode = pmremTexture( value ); + + _envNodeCache.set( value, cacheEnvNode ); + + } + + envNode = cacheEnvNode; + + } + + // + + const useAnisotropy = material.useAnisotropy === true || material.anisotropy > 0; + const radianceNormalView = useAnisotropy ? bentNormalView : normalView; + + const radiance = envNode.context( createRadianceContext( roughness, radianceNormalView ) ).mul( materialEnvIntensity ); + const irradiance = envNode.context( createIrradianceContext( normalWorld ) ).mul( Math.PI ).mul( materialEnvIntensity ); + + const isolateRadiance = cache( radiance ); + const isolateIrradiance = cache( irradiance ); + + // + + builder.context.radiance.addAssign( isolateRadiance ); + + builder.context.iblIrradiance.addAssign( isolateIrradiance ); + + // + + const clearcoatRadiance = builder.context.lightingModel.clearcoatRadiance; + + if ( clearcoatRadiance ) { + + const clearcoatRadianceContext = envNode.context( createRadianceContext( clearcoatRoughness, clearcoatNormalView ) ).mul( materialEnvIntensity ); + const isolateClearcoatRadiance = cache( clearcoatRadianceContext ); + + clearcoatRadiance.addAssign( isolateClearcoatRadiance ); + + } + + } + +} + +const createRadianceContext = ( roughnessNode, normalViewNode ) => { + + let reflectVec = null; + + return { + getUV: () => { + + if ( reflectVec === null ) { + + reflectVec = positionViewDirection.negate().reflect( normalViewNode ); + + // Mixing the reflection with the normal is more accurate and keeps rough objects from gathering light from behind their tangent plane. + reflectVec = roughnessNode.mul( roughnessNode ).mix( reflectVec, normalViewNode ).normalize(); + + reflectVec = reflectVec.transformDirection( cameraViewMatrix ); + + } + + return reflectVec; + + }, + getTextureLevel: () => { + + return roughnessNode; + + } + }; + +}; + +const createIrradianceContext = ( normalWorldNode ) => { + + return { + getUV: () => { + + return normalWorldNode; + + }, + getTextureLevel: () => { + + return float( 1.0 ); + + } + }; + +}; + +const _defaultValues$6 = /*@__PURE__*/ new MeshStandardMaterial(); + +/** + * Node material version of {@link MeshStandardMaterial}. + * + * @augments NodeMaterial + */ +class MeshStandardNodeMaterial extends NodeMaterial { + + static get type() { + + return 'MeshStandardNodeMaterial'; + + } + + /** + * Constructs a new mesh standard node material. + * + * @param {Object} [parameters] - The configuration parameter. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshStandardNodeMaterial = true; + + /** + * Set to `true` because standard materials react on lights. + * + * @type {boolean} + * @default true + */ + this.lights = true; + + /** + * The emissive color of standard materials is by default inferred from the `emissive`, + * `emissiveIntensity` and `emissiveMap` properties. This node property allows to + * overwrite the default and define the emissive color with a node instead. + * + * If you don't want to overwrite the emissive color but modify the existing + * value instead, use {@link materialEmissive}. + * + * @type {?Node} + * @default null + */ + this.emissiveNode = null; + + /** + * The metalness of standard materials is by default inferred from the `metalness`, + * and `metalnessMap` properties. This node property allows to + * overwrite the default and define the metalness with a node instead. + * + * If you don't want to overwrite the metalness but modify the existing + * value instead, use {@link materialMetalness}. + * + * @type {?Node} + * @default null + */ + this.metalnessNode = null; + + /** + * The roughness of standard materials is by default inferred from the `roughness`, + * and `roughnessMap` properties. This node property allows to + * overwrite the default and define the roughness with a node instead. + * + * If you don't want to overwrite the roughness but modify the existing + * value instead, use {@link materialRoughness}. + * + * @type {?Node} + * @default null + */ + this.roughnessNode = null; + + this.setDefaultValues( _defaultValues$6 ); + + this.setValues( parameters ); + + } + + /** + * Overwritten since this type of material uses {@link EnvironmentNode} + * to implement the PBR (PMREM based) environment mapping. Besides, the + * method honors `Scene.environment`. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {?EnvironmentNode} The environment node. + */ + setupEnvironment( builder ) { + + let envNode = super.setupEnvironment( builder ); + + if ( envNode === null && builder.environmentNode ) { + + envNode = builder.environmentNode; + + } + + return envNode ? new EnvironmentNode( envNode ) : null; + + } + + /** + * Setups the lighting model. + * + * @return {PhysicalLightingModel} The lighting model. + */ + setupLightingModel( /*builder*/ ) { + + return new PhysicalLightingModel(); + + } + + /** + * Setups the specular related node variables. + */ + setupSpecular() { + + const specularColorNode = mix( vec3( 0.04 ), diffuseColor.rgb, metalness ); + + specularColor.assign( specularColorNode ); + specularF90.assign( 1.0 ); + + } + + /** + * Setups the standard specific node variables. + * + * @param {NodeBuilder} builder - The current node builder. + */ + setupVariants() { + + // METALNESS + + const metalnessNode = this.metalnessNode ? float( this.metalnessNode ) : materialMetalness; + + metalness.assign( metalnessNode ); + + // ROUGHNESS + + let roughnessNode = this.roughnessNode ? float( this.roughnessNode ) : materialRoughness; + roughnessNode = getRoughness( { roughness: roughnessNode } ); + + roughness.assign( roughnessNode ); + + // SPECULAR COLOR + + this.setupSpecular(); + + // DIFFUSE COLOR + + diffuseColor.assign( vec4( diffuseColor.rgb.mul( metalnessNode.oneMinus() ), diffuseColor.a ) ); + + } + + copy( source ) { + + this.emissiveNode = source.emissiveNode; + + this.metalnessNode = source.metalnessNode; + this.roughnessNode = source.roughnessNode; + + return super.copy( source ); + + } + +} + +const _defaultValues$5 = /*@__PURE__*/ new MeshPhysicalMaterial(); + +/** + * Node material version of {@link MeshPhysicalMaterial}. + * + * @augments MeshStandardNodeMaterial + */ +class MeshPhysicalNodeMaterial extends MeshStandardNodeMaterial { + + static get type() { + + return 'MeshPhysicalNodeMaterial'; + + } + + /** + * Constructs a new mesh physical node material. + * + * @param {Object} [parameters] - The configuration parameter. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshPhysicalNodeMaterial = true; + + /** + * The clearcoat of physical materials is by default inferred from the `clearcoat` + * and `clearcoatMap` properties. This node property allows to overwrite the default + * and define the clearcoat with a node instead. + * + * If you don't want to overwrite the clearcoat but modify the existing + * value instead, use {@link materialClearcoat}. + * + * @type {?Node} + * @default null + */ + this.clearcoatNode = null; + + /** + * The clearcoat roughness of physical materials is by default inferred from the `clearcoatRoughness` + * and `clearcoatRoughnessMap` properties. This node property allows to overwrite the default + * and define the clearcoat roughness with a node instead. + * + * If you don't want to overwrite the clearcoat roughness but modify the existing + * value instead, use {@link materialClearcoatRoughness}. + * + * @type {?Node} + * @default null + */ + this.clearcoatRoughnessNode = null; + + /** + * The clearcoat normal of physical materials is by default inferred from the `clearcoatNormalMap` + * property. This node property allows to overwrite the default + * and define the clearcoat normal with a node instead. + * + * If you don't want to overwrite the clearcoat normal but modify the existing + * value instead, use {@link materialClearcoatNormal}. + * + * @type {?Node} + * @default null + */ + this.clearcoatNormalNode = null; + + /** + * The sheen of physical materials is by default inferred from the `sheen`, `sheenColor` + * and `sheenColorMap` properties. This node property allows to overwrite the default + * and define the sheen with a node instead. + * + * If you don't want to overwrite the sheen but modify the existing + * value instead, use {@link materialSheen}. + * + * @type {?Node} + * @default null + */ + this.sheenNode = null; + + /** + * The sheen roughness of physical materials is by default inferred from the `sheenRoughness` and + * `sheenRoughnessMap` properties. This node property allows to overwrite the default + * and define the sheen roughness with a node instead. + * + * If you don't want to overwrite the sheen roughness but modify the existing + * value instead, use {@link materialSheenRoughness}. + * + * @type {?Node} + * @default null + */ + this.sheenRoughnessNode = null; + + /** + * The iridescence of physical materials is by default inferred from the `iridescence` + * property. This node property allows to overwrite the default + * and define the iridescence with a node instead. + * + * If you don't want to overwrite the iridescence but modify the existing + * value instead, use {@link materialIridescence}. + * + * @type {?Node} + * @default null + */ + this.iridescenceNode = null; + + /** + * The iridescence IOR of physical materials is by default inferred from the `iridescenceIOR` + * property. This node property allows to overwrite the default + * and define the iridescence IOR with a node instead. + * + * If you don't want to overwrite the iridescence IOR but modify the existing + * value instead, use {@link materialIridescenceIOR}. + * + * @type {?Node} + * @default null + */ + this.iridescenceIORNode = null; + + /** + * The iridescence thickness of physical materials is by default inferred from the `iridescenceThicknessRange` + * and `iridescenceThicknessMap` properties. This node property allows to overwrite the default + * and define the iridescence thickness with a node instead. + * + * If you don't want to overwrite the iridescence thickness but modify the existing + * value instead, use {@link materialIridescenceThickness}. + * + * @type {?Node} + * @default null + */ + this.iridescenceThicknessNode = null; + + /** + * The specular intensity of physical materials is by default inferred from the `specularIntensity` + * and `specularIntensityMap` properties. This node property allows to overwrite the default + * and define the specular intensity with a node instead. + * + * If you don't want to overwrite the specular intensity but modify the existing + * value instead, use {@link materialSpecularIntensity}. + * + * @type {?Node} + * @default null + */ + this.specularIntensityNode = null; + + /** + * The specular color of physical materials is by default inferred from the `specularColor` + * and `specularColorMap` properties. This node property allows to overwrite the default + * and define the specular color with a node instead. + * + * If you don't want to overwrite the specular color but modify the existing + * value instead, use {@link materialSpecularColor}. + * + * @type {?Node} + * @default null + */ + this.specularColorNode = null; + + /** + * The ior of physical materials is by default inferred from the `ior` + * property. This node property allows to overwrite the default + * and define the ior with a node instead. + * + * If you don't want to overwrite the ior but modify the existing + * value instead, use {@link materialIOR}. + * + * @type {?Node} + * @default null + */ + this.iorNode = null; + + /** + * The transmission of physical materials is by default inferred from the `transmission` and + * `transmissionMap` properties. This node property allows to overwrite the default + * and define the transmission with a node instead. + * + * If you don't want to overwrite the transmission but modify the existing + * value instead, use {@link materialTransmission}. + * + * @type {?Node} + * @default null + */ + this.transmissionNode = null; + + /** + * The thickness of physical materials is by default inferred from the `thickness` and + * `thicknessMap` properties. This node property allows to overwrite the default + * and define the thickness with a node instead. + * + * If you don't want to overwrite the thickness but modify the existing + * value instead, use {@link materialThickness}. + * + * @type {?Node} + * @default null + */ + this.thicknessNode = null; + + /** + * The attenuation distance of physical materials is by default inferred from the + * `attenuationDistance` property. This node property allows to overwrite the default + * and define the attenuation distance with a node instead. + * + * If you don't want to overwrite the attenuation distance but modify the existing + * value instead, use {@link materialAttenuationDistance}. + * + * @type {?Node} + * @default null + */ + this.attenuationDistanceNode = null; + + /** + * The attenuation color of physical materials is by default inferred from the + * `attenuationColor` property. This node property allows to overwrite the default + * and define the attenuation color with a node instead. + * + * If you don't want to overwrite the attenuation color but modify the existing + * value instead, use {@link materialAttenuationColor}. + * + * @type {?Node} + * @default null + */ + this.attenuationColorNode = null; + + /** + * The dispersion of physical materials is by default inferred from the + * `dispersion` property. This node property allows to overwrite the default + * and define the dispersion with a node instead. + * + * If you don't want to overwrite the dispersion but modify the existing + * value instead, use {@link materialDispersion}. + * + * @type {?Node} + * @default null + */ + this.dispersionNode = null; + + /** + * The anisotropy of physical materials is by default inferred from the + * `anisotropy` property. This node property allows to overwrite the default + * and define the anisotropy with a node instead. + * + * If you don't want to overwrite the anisotropy but modify the existing + * value instead, use {@link materialAnisotropy}. + * + * @type {?Node} + * @default null + */ + this.anisotropyNode = null; + + this.setDefaultValues( _defaultValues$5 ); + + this.setValues( parameters ); + + } + + /** + * Whether the lighting model should use clearcoat or not. + * + * @type {boolean} + * @default true + */ + get useClearcoat() { + + return this.clearcoat > 0 || this.clearcoatNode !== null; + + } + + /** + * Whether the lighting model should use iridescence or not. + * + * @type {boolean} + * @default true + */ + get useIridescence() { + + return this.iridescence > 0 || this.iridescenceNode !== null; + + } + + /** + * Whether the lighting model should use sheen or not. + * + * @type {boolean} + * @default true + */ + get useSheen() { + + return this.sheen > 0 || this.sheenNode !== null; + + } + + /** + * Whether the lighting model should use anisotropy or not. + * + * @type {boolean} + * @default true + */ + get useAnisotropy() { + + return this.anisotropy > 0 || this.anisotropyNode !== null; + + } + + /** + * Whether the lighting model should use transmission or not. + * + * @type {boolean} + * @default true + */ + get useTransmission() { + + return this.transmission > 0 || this.transmissionNode !== null; + + } + + /** + * Whether the lighting model should use dispersion or not. + * + * @type {boolean} + * @default true + */ + get useDispersion() { + + return this.dispersion > 0 || this.dispersionNode !== null; + + } + + /** + * Setups the specular related node variables. + */ + setupSpecular() { + + const iorNode = this.iorNode ? float( this.iorNode ) : materialIOR; + + ior.assign( iorNode ); + specularColor.assign( mix( min$1( pow2( ior.sub( 1.0 ).div( ior.add( 1.0 ) ) ).mul( materialSpecularColor ), vec3( 1.0 ) ).mul( materialSpecularIntensity ), diffuseColor.rgb, metalness ) ); + specularF90.assign( mix( materialSpecularIntensity, 1.0, metalness ) ); + + } + + /** + * Setups the lighting model. + * + * @return {PhysicalLightingModel} The lighting model. + */ + setupLightingModel( /*builder*/ ) { + + return new PhysicalLightingModel( this.useClearcoat, this.useSheen, this.useIridescence, this.useAnisotropy, this.useTransmission, this.useDispersion ); + + } + + /** + * Setups the physical specific node variables. + * + * @param {NodeBuilder} builder - The current node builder. + */ + setupVariants( builder ) { + + super.setupVariants( builder ); + + // CLEARCOAT + + if ( this.useClearcoat ) { + + const clearcoatNode = this.clearcoatNode ? float( this.clearcoatNode ) : materialClearcoat; + const clearcoatRoughnessNode = this.clearcoatRoughnessNode ? float( this.clearcoatRoughnessNode ) : materialClearcoatRoughness; + + clearcoat.assign( clearcoatNode ); + clearcoatRoughness.assign( getRoughness( { roughness: clearcoatRoughnessNode } ) ); + + } + + // SHEEN + + if ( this.useSheen ) { + + const sheenNode = this.sheenNode ? vec3( this.sheenNode ) : materialSheen; + const sheenRoughnessNode = this.sheenRoughnessNode ? float( this.sheenRoughnessNode ) : materialSheenRoughness; + + sheen.assign( sheenNode ); + sheenRoughness.assign( sheenRoughnessNode ); + + } + + // IRIDESCENCE + + if ( this.useIridescence ) { + + const iridescenceNode = this.iridescenceNode ? float( this.iridescenceNode ) : materialIridescence; + const iridescenceIORNode = this.iridescenceIORNode ? float( this.iridescenceIORNode ) : materialIridescenceIOR; + const iridescenceThicknessNode = this.iridescenceThicknessNode ? float( this.iridescenceThicknessNode ) : materialIridescenceThickness; + + iridescence.assign( iridescenceNode ); + iridescenceIOR.assign( iridescenceIORNode ); + iridescenceThickness.assign( iridescenceThicknessNode ); + + } + + // ANISOTROPY + + if ( this.useAnisotropy ) { + + const anisotropyV = ( this.anisotropyNode ? vec2( this.anisotropyNode ) : materialAnisotropy ).toVar(); + + anisotropy.assign( anisotropyV.length() ); + + If( anisotropy.equal( 0.0 ), () => { + + anisotropyV.assign( vec2( 1.0, 0.0 ) ); + + } ).Else( () => { + + anisotropyV.divAssign( vec2( anisotropy ) ); + anisotropy.assign( anisotropy.saturate() ); + + } ); + + // Roughness along the anisotropy bitangent is the material roughness, while the tangent roughness increases with anisotropy. + alphaT.assign( anisotropy.pow2().mix( roughness.pow2(), 1.0 ) ); + + anisotropyT.assign( TBNViewMatrix[ 0 ].mul( anisotropyV.x ).add( TBNViewMatrix[ 1 ].mul( anisotropyV.y ) ) ); + anisotropyB.assign( TBNViewMatrix[ 1 ].mul( anisotropyV.x ).sub( TBNViewMatrix[ 0 ].mul( anisotropyV.y ) ) ); + + } + + // TRANSMISSION + + if ( this.useTransmission ) { + + const transmissionNode = this.transmissionNode ? float( this.transmissionNode ) : materialTransmission; + const thicknessNode = this.thicknessNode ? float( this.thicknessNode ) : materialThickness; + const attenuationDistanceNode = this.attenuationDistanceNode ? float( this.attenuationDistanceNode ) : materialAttenuationDistance; + const attenuationColorNode = this.attenuationColorNode ? vec3( this.attenuationColorNode ) : materialAttenuationColor; + + transmission.assign( transmissionNode ); + thickness.assign( thicknessNode ); + attenuationDistance.assign( attenuationDistanceNode ); + attenuationColor.assign( attenuationColorNode ); + + if ( this.useDispersion ) { + + const dispersionNode = this.dispersionNode ? float( this.dispersionNode ) : materialDispersion; + + dispersion.assign( dispersionNode ); + + } + + } + + } + + /** + * Setups the clearcoat normal node. + * + * @return {Node} The clearcoat normal. + */ + setupClearcoatNormal() { + + return this.clearcoatNormalNode ? vec3( this.clearcoatNormalNode ) : materialClearcoatNormal; + + } + + setup( builder ) { + + builder.context.setupClearcoatNormal = () => subBuild( this.setupClearcoatNormal( builder ), 'NORMAL', 'vec3' ); + + super.setup( builder ); + + } + + copy( source ) { + + this.clearcoatNode = source.clearcoatNode; + this.clearcoatRoughnessNode = source.clearcoatRoughnessNode; + this.clearcoatNormalNode = source.clearcoatNormalNode; + + this.sheenNode = source.sheenNode; + this.sheenRoughnessNode = source.sheenRoughnessNode; + + this.iridescenceNode = source.iridescenceNode; + this.iridescenceIORNode = source.iridescenceIORNode; + this.iridescenceThicknessNode = source.iridescenceThicknessNode; + + this.specularIntensityNode = source.specularIntensityNode; + this.specularColorNode = source.specularColorNode; + + this.transmissionNode = source.transmissionNode; + this.thicknessNode = source.thicknessNode; + this.attenuationDistanceNode = source.attenuationDistanceNode; + this.attenuationColorNode = source.attenuationColorNode; + this.dispersionNode = source.dispersionNode; + + this.anisotropyNode = source.anisotropyNode; + + return super.copy( source ); + + } + +} + +/** + * Represents the lighting model for {@link MeshSSSNodeMaterial}. + * + * @augments PhysicalLightingModel + */ +class SSSLightingModel extends PhysicalLightingModel { + + /** + * Constructs a new physical lighting model. + * + * @param {boolean} [clearcoat=false] - Whether clearcoat is supported or not. + * @param {boolean} [sheen=false] - Whether sheen is supported or not. + * @param {boolean} [iridescence=false] - Whether iridescence is supported or not. + * @param {boolean} [anisotropy=false] - Whether anisotropy is supported or not. + * @param {boolean} [transmission=false] - Whether transmission is supported or not. + * @param {boolean} [dispersion=false] - Whether dispersion is supported or not. + * @param {boolean} [sss=false] - Whether SSS is supported or not. + */ + constructor( clearcoat = false, sheen = false, iridescence = false, anisotropy = false, transmission = false, dispersion = false, sss = false ) { + + super( clearcoat, sheen, iridescence, anisotropy, transmission, dispersion ); + + /** + * Whether the lighting model should use SSS or not. + * + * @type {boolean} + * @default false + */ + this.useSSS = sss; + + } + + /** + * Extends the default implementation with a SSS term. + * + * Reference: [Approximating Translucency for a Fast, Cheap and Convincing Subsurface Scattering Look]{@link https://colinbarrebrisebois.com/2011/03/07/gdc-2011-approximating-translucency-for-a-fast-cheap-and-convincing-subsurface-scattering-look/} + * + * @param {Object} input - The input data. + * @param {NodeBuilder} builder - The current node builder. + */ + direct( { lightDirection, lightColor, reflectedLight }, builder ) { + + if ( this.useSSS === true ) { + + const material = builder.material; + + const { thicknessColorNode, thicknessDistortionNode, thicknessAmbientNode, thicknessAttenuationNode, thicknessPowerNode, thicknessScaleNode } = material; + + const scatteringHalf = lightDirection.add( normalView.mul( thicknessDistortionNode ) ).normalize(); + const scatteringDot = float( positionViewDirection.dot( scatteringHalf.negate() ).saturate().pow( thicknessPowerNode ).mul( thicknessScaleNode ) ); + const scatteringIllu = vec3( scatteringDot.add( thicknessAmbientNode ).mul( thicknessColorNode ) ); + + reflectedLight.directDiffuse.addAssign( scatteringIllu.mul( thicknessAttenuationNode.mul( lightColor ) ) ); + + } + + super.direct( { lightDirection, lightColor, reflectedLight }, builder ); + + } + +} + +/** + * This node material is an experimental extension of {@link MeshPhysicalNodeMaterial} + * that implements a Subsurface scattering (SSS) term. + * + * @augments MeshPhysicalNodeMaterial + */ +class MeshSSSNodeMaterial extends MeshPhysicalNodeMaterial { + + static get type() { + + return 'MeshSSSNodeMaterial'; + + } + + /** + * Constructs a new mesh SSS node material. + * + * @param {Object} [parameters] - The configuration parameter. + */ + constructor( parameters ) { + + super( parameters ); + + /** + * Represents the thickness color. + * + * @type {?Node} + * @default null + */ + this.thicknessColorNode = null; + + /** + * Represents the distortion factor. + * + * @type {?Node} + */ + this.thicknessDistortionNode = float( 0.1 ); + + /** + * Represents the thickness ambient factor. + * + * @type {?Node} + */ + this.thicknessAmbientNode = float( 0.0 ); + + /** + * Represents the thickness attenuation. + * + * @type {?Node} + */ + this.thicknessAttenuationNode = float( .1 ); + + /** + * Represents the thickness power. + * + * @type {?Node} + */ + this.thicknessPowerNode = float( 2.0 ); + + /** + * Represents the thickness scale. + * + * @type {?Node} + */ + this.thicknessScaleNode = float( 10.0 ); + + } + + /** + * Whether the lighting model should use SSS or not. + * + * @type {boolean} + * @default true + */ + get useSSS() { + + return this.thicknessColorNode !== null; + + } + + /** + * Setups the lighting model. + * + * @return {SSSLightingModel} The lighting model. + */ + setupLightingModel( /*builder*/ ) { + + return new SSSLightingModel( this.useClearcoat, this.useSheen, this.useIridescence, this.useAnisotropy, this.useTransmission, this.useDispersion, this.useSSS ); + + } + + copy( source ) { + + this.thicknessColorNode = source.thicknessColorNode; + this.thicknessDistortionNode = source.thicknessDistortionNode; + this.thicknessAmbientNode = source.thicknessAmbientNode; + this.thicknessAttenuationNode = source.thicknessAttenuationNode; + this.thicknessPowerNode = source.thicknessPowerNode; + this.thicknessScaleNode = source.thicknessScaleNode; + + return super.copy( source ); + + } + +} + +const getGradientIrradiance = /*@__PURE__*/ Fn( ( { normal, lightDirection, builder } ) => { + + // dotNL will be from -1.0 to 1.0 + const dotNL = normal.dot( lightDirection ); + const coord = vec2( dotNL.mul( 0.5 ).add( 0.5 ), 0.0 ); + + if ( builder.material.gradientMap ) { + + const gradientMap = materialReference( 'gradientMap', 'texture' ).context( { getUV: () => coord } ); + + return vec3( gradientMap.r ); + + } else { + + const fw = coord.fwidth().mul( 0.5 ); + + return mix( vec3( 0.7 ), vec3( 1.0 ), smoothstep( float( 0.7 ).sub( fw.x ), float( 0.7 ).add( fw.x ), coord.x ) ); + + } + +} ); + +/** + * Represents the lighting model for a toon material. Used in {@link MeshToonNodeMaterial}. + * + * @augments LightingModel + */ +class ToonLightingModel extends LightingModel { + + /** + * Implements the direct lighting. Instead of using a conventional smooth irradiance, the irradiance is + * reduced to a small number of discrete shades to create a comic-like, flat look. + * + * @param {Object} lightData - The light data. + * @param {NodeBuilder} builder - The current node builder. + */ + direct( { lightDirection, lightColor, reflectedLight }, builder ) { + + const irradiance = getGradientIrradiance( { normal: normalGeometry, lightDirection, builder } ).mul( lightColor ); + + reflectedLight.directDiffuse.addAssign( irradiance.mul( BRDF_Lambert( { diffuseColor: diffuseColor.rgb } ) ) ); + + } + + /** + * Implements the indirect lighting. + * + * @param {NodeBuilder} builder - The current node builder. + */ + indirect( builder ) { + + const { ambientOcclusion, irradiance, reflectedLight } = builder.context; + + reflectedLight.indirectDiffuse.addAssign( irradiance.mul( BRDF_Lambert( { diffuseColor } ) ) ); + + reflectedLight.indirectDiffuse.mulAssign( ambientOcclusion ); + + } + +} + +const _defaultValues$4 = /*@__PURE__*/ new MeshToonMaterial(); + +/** + * Node material version of {@link MeshToonMaterial}. + * + * @augments NodeMaterial + */ +class MeshToonNodeMaterial extends NodeMaterial { + + static get type() { + + return 'MeshToonNodeMaterial'; + + } + + /** + * Constructs a new mesh toon node material. + * + * @param {Object} [parameters] - The configuration parameter. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshToonNodeMaterial = true; + + /** + * Set to `true` because toon materials react on lights. + * + * @type {boolean} + * @default true + */ + this.lights = true; + + this.setDefaultValues( _defaultValues$4 ); + + this.setValues( parameters ); + + } + + /** + * Setups the lighting model. + * + * @return {ToonLightingModel} The lighting model. + */ + setupLightingModel( /*builder*/ ) { + + return new ToonLightingModel(); + + } + +} + +/** + * TSL function for creating a matcap uv node. + * + * Can be used to compute texture coordinates for projecting a + * matcap onto a mesh. Used by {@link MeshMatcapNodeMaterial}. + * + * @tsl + * @function + * @returns {Node} The matcap UV coordinates. + */ +const matcapUV = /*@__PURE__*/ Fn( () => { + + const x = vec3( positionViewDirection.z, 0, positionViewDirection.x.negate() ).normalize(); + const y = positionViewDirection.cross( x ); + + return vec2( x.dot( normalView ), y.dot( normalView ) ).mul( 0.495 ).add( 0.5 ); // 0.495 to remove artifacts caused by undersized matcap disks + +} ).once( [ 'NORMAL', 'VERTEX' ] )().toVar( 'matcapUV' ); + +const _defaultValues$3 = /*@__PURE__*/ new MeshMatcapMaterial(); + +/** + * Node material version of {@link MeshMatcapMaterial}. + * + * @augments NodeMaterial + */ +class MeshMatcapNodeMaterial extends NodeMaterial { + + static get type() { + + return 'MeshMatcapNodeMaterial'; + + } + + /** + * Constructs a new mesh normal node material. + * + * @param {Object} [parameters] - The configuration parameter. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMeshMatcapNodeMaterial = true; + + this.setDefaultValues( _defaultValues$3 ); + + this.setValues( parameters ); + + } + + /** + * Setups the matcap specific node variables. + * + * @param {NodeBuilder} builder - The current node builder. + */ + setupVariants( builder ) { + + const uv = matcapUV; + + let matcapColor; + + if ( builder.material.matcap ) { + + matcapColor = materialReference( 'matcap', 'texture' ).context( { getUV: () => uv } ); + + } else { + + matcapColor = vec3( mix( 0.2, 0.8, uv.y ) ); // default if matcap is missing + + } + + diffuseColor.rgb.mulAssign( matcapColor.rgb ); + + } + +} + +/** + * Applies a rotation to the given position node. + * + * @augments TempNode + */ +class RotateNode extends TempNode { + + static get type() { + + return 'RotateNode'; + + } + + /** + * Constructs a new rotate node. + * + * @param {Node} positionNode - The position node. + * @param {Node} rotationNode - Represents the rotation that is applied to the position node. Depending + * on whether the position data are 2D or 3D, the rotation is expressed a single float value or an Euler value. + */ + constructor( positionNode, rotationNode ) { + + super(); + + /** + * The position node. + * + * @type {Node} + */ + this.positionNode = positionNode; + + /** + * Represents the rotation that is applied to the position node. + * Depending on whether the position data are 2D or 3D, the rotation is expressed a single float value or an Euler value. + * + * @type {Node} + */ + this.rotationNode = rotationNode; + + } + + /** + * The type of the {@link RotateNode#positionNode} defines the node's type. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node's type. + */ + getNodeType( builder ) { + + return this.positionNode.getNodeType( builder ); + + } + + setup( builder ) { + + const { rotationNode, positionNode } = this; + + const nodeType = this.getNodeType( builder ); + + if ( nodeType === 'vec2' ) { + + const cosAngle = rotationNode.cos(); + const sinAngle = rotationNode.sin(); + + const rotationMatrix = mat2( + cosAngle, sinAngle, + sinAngle.negate(), cosAngle + ); + + return rotationMatrix.mul( positionNode ); + + } else { + + const rotation = rotationNode; + const rotationXMatrix = mat4( vec4( 1.0, 0.0, 0.0, 0.0 ), vec4( 0.0, cos( rotation.x ), sin( rotation.x ).negate(), 0.0 ), vec4( 0.0, sin( rotation.x ), cos( rotation.x ), 0.0 ), vec4( 0.0, 0.0, 0.0, 1.0 ) ); + const rotationYMatrix = mat4( vec4( cos( rotation.y ), 0.0, sin( rotation.y ), 0.0 ), vec4( 0.0, 1.0, 0.0, 0.0 ), vec4( sin( rotation.y ).negate(), 0.0, cos( rotation.y ), 0.0 ), vec4( 0.0, 0.0, 0.0, 1.0 ) ); + const rotationZMatrix = mat4( vec4( cos( rotation.z ), sin( rotation.z ).negate(), 0.0, 0.0 ), vec4( sin( rotation.z ), cos( rotation.z ), 0.0, 0.0 ), vec4( 0.0, 0.0, 1.0, 0.0 ), vec4( 0.0, 0.0, 0.0, 1.0 ) ); + + return rotationXMatrix.mul( rotationYMatrix ).mul( rotationZMatrix ).mul( vec4( positionNode, 1.0 ) ).xyz; + + } + + } + +} + +/** + * TSL function for creating a rotate node. + * + * @tsl + * @function + * @param {Node} positionNode - The position node. + * @param {Node} rotationNode - Represents the rotation that is applied to the position node. Depending + * on whether the position data are 2D or 3D, the rotation is expressed a single float value or an Euler value. + * @returns {RotateNode} + */ +const rotate = /*@__PURE__*/ nodeProxy( RotateNode ).setParameterLength( 2 ); + +const _defaultValues$2 = /*@__PURE__*/ new SpriteMaterial(); + +/** + * Node material version of {@link SpriteMaterial}. + * + * @augments NodeMaterial + */ +class SpriteNodeMaterial extends NodeMaterial { + + static get type() { + + return 'SpriteNodeMaterial'; + + } + + /** + * Constructs a new sprite node material. + * + * @param {Object} [parameters] - The configuration parameter. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSpriteNodeMaterial = true; + + this._useSizeAttenuation = true; + + /** + * This property makes it possible to define the position of the sprite with a + * node. That can be useful when the material is used with instanced rendering + * and node data are defined with an instanced attribute node: + * ```js + * const positionAttribute = new InstancedBufferAttribute( new Float32Array( positions ), 3 ); + * material.positionNode = instancedBufferAttribute( positionAttribute ); + * ``` + * Another possibility is to compute the instanced data with a compute shader: + * ```js + * const positionBuffer = instancedArray( particleCount, 'vec3' ); + * particleMaterial.positionNode = positionBuffer.toAttribute(); + * ``` + * + * @type {?Node} + * @default null + */ + this.positionNode = null; + + /** + * The rotation of sprite materials is by default inferred from the `rotation`, + * property. This node property allows to overwrite the default and define + * the rotation with a node instead. + * + * If you don't want to overwrite the rotation but modify the existing + * value instead, use {@link materialRotation}. + * + * @type {?Node} + * @default null + */ + this.rotationNode = null; + + /** + * This node property provides an additional way to scale sprites next to + * `Object3D.scale`. The scale transformation based in `Object3D.scale` + * is multiplied with the scale value of this node in the vertex shader. + * + * @type {?Node} + * @default null + */ + this.scaleNode = null; + + /** + * In Sprites, the transparent property is enabled by default. + * + * @type {boolean} + * @default true + */ + this.transparent = true; + + this.setDefaultValues( _defaultValues$2 ); + + this.setValues( parameters ); + + } + + /** + * Setups the position node in view space. This method implements + * the sprite specific vertex shader. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {Node} The position in view space. + */ + setupPositionView( builder ) { + + const { object, camera } = builder; + + const sizeAttenuation = this.sizeAttenuation; + + const { positionNode, rotationNode, scaleNode } = this; + + const mvPosition = modelViewMatrix.mul( vec3( positionNode || 0 ) ); + + let scale = vec2( modelWorldMatrix[ 0 ].xyz.length(), modelWorldMatrix[ 1 ].xyz.length() ); + + if ( scaleNode !== null ) { + + scale = scale.mul( vec2( scaleNode ) ); + + } + + if ( sizeAttenuation === false ) { + + if ( camera.isPerspectiveCamera ) { + + scale = scale.mul( mvPosition.z.negate() ); + + } else { + + const orthoScale = float( 2.0 ).div( cameraProjectionMatrix.element( 1 ).element( 1 ) ); + scale = scale.mul( orthoScale.mul( 2 ) ); + + } + + } + + let alignedPosition = positionGeometry.xy; + + if ( object.center && object.center.isVector2 === true ) { + + const center = reference$1( 'center', 'vec2', object ); + + alignedPosition = alignedPosition.sub( center.sub( 0.5 ) ); + + } + + alignedPosition = alignedPosition.mul( scale ); + + const rotation = float( rotationNode || materialRotation ); + + const rotatedPosition = rotate( alignedPosition, rotation ); + + return vec4( mvPosition.xy.add( rotatedPosition ), mvPosition.zw ); + + } + + copy( source ) { + + this.positionNode = source.positionNode; + this.rotationNode = source.rotationNode; + this.scaleNode = source.scaleNode; + + return super.copy( source ); + + } + + /** + * Whether to use size attenuation or not. + * + * @type {boolean} + * @default true + */ + get sizeAttenuation() { + + return this._useSizeAttenuation; + + } + + set sizeAttenuation( value ) { + + if ( this._useSizeAttenuation !== value ) { + + this._useSizeAttenuation = value; + this.needsUpdate = true; + + } + + } + +} + +const _defaultValues$1 = /*@__PURE__*/ new PointsMaterial(); + +/** + * Node material version of {@link PointsMaterial}. + * + * @augments SpriteNodeMaterial + */ +class PointsNodeMaterial extends SpriteNodeMaterial { + + static get type() { + + return 'PointsNodeMaterial'; + + } + + /** + * Constructs a new points node material. + * + * @param {Object} [parameters] - The configuration parameter. + */ + constructor( parameters ) { + + super(); + + /** + * This node property provides an additional way to set the point size. + * + * @type {?Node} + * @default null + */ + this.sizeNode = null; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isPointsNodeMaterial = true; + + this.setDefaultValues( _defaultValues$1 ); + + this.setValues( parameters ); + + } + + setupPositionView() { + + const { positionNode } = this; + + return modelViewMatrix.mul( vec3( positionNode || positionLocal ) ).xyz; + + } + + setupVertex( builder ) { + + const mvp = super.setupVertex( builder ); + + // skip further processing if the material is not a node material + + if ( builder.material.isNodeMaterial !== true ) { + + return mvp; + + } + + // ndc space + + const { rotationNode, scaleNode, sizeNode } = this; + + const alignedPosition = positionGeometry.xy.toVar(); + const aspect = viewport.z.div( viewport.w ); + + // rotation + + if ( rotationNode && rotationNode.isNode ) { + + const rotation = float( rotationNode ); + + alignedPosition.assign( rotate( alignedPosition, rotation ) ); + + } + + // point size + + let pointSize = sizeNode !== null ? vec2( sizeNode ) : materialPointSize; + + if ( this.sizeAttenuation === true ) { + + pointSize = pointSize.mul( pointSize.div( positionView.z.negate() ) ); + + } + + // scale + + if ( scaleNode && scaleNode.isNode ) { + + pointSize = pointSize.mul( vec2( scaleNode ) ); + + } + + alignedPosition.mulAssign( pointSize.mul( 2 ) ); + + alignedPosition.assign( alignedPosition.div( viewport.z ) ); + alignedPosition.y.assign( alignedPosition.y.mul( aspect ) ); + + // back to clip space + alignedPosition.assign( alignedPosition.mul( mvp.w ) ); + + //clipPos.xy += offset; + mvp.addAssign( vec4( alignedPosition, 0, 0 ) ); + + return mvp; + + } + + /** + * Whether alpha to coverage should be used or not. + * + * @type {boolean} + * @default true + */ + get alphaToCoverage() { + + return this._useAlphaToCoverage; + + } + + set alphaToCoverage( value ) { + + if ( this._useAlphaToCoverage !== value ) { + + this._useAlphaToCoverage = value; + this.needsUpdate = true; + + } + + } + +} + +/** + * Represents lighting model for a shadow material. Used in {@link ShadowNodeMaterial}. + * + * @augments LightingModel + */ +class ShadowMaskModel extends LightingModel { + + /** + * Constructs a new shadow mask model. + */ + constructor() { + + super(); + + /** + * The shadow mask node. + * + * @type {Node} + */ + this.shadowNode = float( 1 ).toVar( 'shadowMask' ); + + } + + /** + * Only used to save the shadow mask. + * + * @param {Object} input - The input data. + */ + direct( { lightNode } ) { + + if ( lightNode.shadowNode !== null ) { + + this.shadowNode.mulAssign( lightNode.shadowNode ); + + } + + } + + /** + * Uses the shadow mask to produce the final color. + * + * @param {NodeBuilder} builder - The current node builder. + */ + finish( { context } ) { + + diffuseColor.a.mulAssign( this.shadowNode.oneMinus() ); + + context.outgoingLight.rgb.assign( diffuseColor.rgb ); // TODO: Optimize LightsNode to avoid this assignment + + } + +} + +const _defaultValues = /*@__PURE__*/ new ShadowMaterial(); + +/** + * Node material version of {@link ShadowMaterial}. + * + * @augments NodeMaterial + */ +class ShadowNodeMaterial extends NodeMaterial { + + static get type() { + + return 'ShadowNodeMaterial'; + + } + + /** + * Constructs a new shadow node material. + * + * @param {Object} [parameters] - The configuration parameter. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isShadowNodeMaterial = true; + + /** + * Set to `true` because so it's possible to implement + * the shadow mask effect. + * + * @type {boolean} + * @default true + */ + this.lights = true; + + /** + * Overwritten since shadow materials are transparent + * by default. + * + * @type {boolean} + * @default true + */ + this.transparent = true; + + this.setDefaultValues( _defaultValues ); + + this.setValues( parameters ); + + } + + /** + * Setups the lighting model. + * + * @return {ShadowMaskModel} The lighting model. + */ + setupLightingModel( /*builder*/ ) { + + return new ShadowMaskModel(); + + } + +} + +const scatteringDensity = property( 'vec3' ); +const linearDepthRay = property( 'vec3' ); +const outgoingRayLight = property( 'vec3' ); + +/** + * VolumetricLightingModel class extends the LightingModel to implement volumetric lighting effects. + * This model calculates the scattering and transmittance of light through a volumetric medium. + * It dynamically adjusts the direction of the ray based on the camera and object positions. + * The model supports custom scattering and depth nodes to enhance the lighting effects. + * + * @augments LightingModel + */ +class VolumetricLightingModel extends LightingModel { + + constructor() { + + super(); + + } + + start( builder ) { + + const { material, context } = builder; + + const startPos = property( 'vec3' ); + const endPos = property( 'vec3' ); + + // This approach dynamically changes the direction of the ray, + // prioritizing the ray from the camera to the object if it is inside the mesh, and from the object to the camera if it is far away. + + If( cameraPosition.sub( positionWorld ).length().greaterThan( modelRadius.mul( 2 ) ), () => { + + startPos.assign( cameraPosition ); + endPos.assign( positionWorld ); + + } ).Else( () => { + + startPos.assign( positionWorld ); + endPos.assign( cameraPosition ); + + } ); + + // + + const viewVector = endPos.sub( startPos ); + + const steps = uniform( 'int' ).onRenderUpdate( ( { material } ) => material.steps ); + const stepSize = viewVector.length().div( steps ).toVar(); + + const rayDir = viewVector.normalize().toVar(); // TODO: toVar() should be automatic here ( in loop ) + + const distTravelled = float( 0.0 ).toVar(); + const transmittance = vec3( 1 ).toVar(); + + if ( material.offsetNode ) { + + // reduce banding + + distTravelled.addAssign( material.offsetNode.mul( stepSize ) ); + + } + + Loop( steps, () => { + + const positionRay = startPos.add( rayDir.mul( distTravelled ) ); + const positionViewRay = cameraViewMatrix.mul( vec4( positionRay, 1 ) ).xyz; + + if ( material.depthNode !== null ) { + + linearDepthRay.assign( linearDepth( viewZToPerspectiveDepth( positionViewRay.z, cameraNear, cameraFar ) ) ); + + context.sceneDepthNode = linearDepth( material.depthNode ).toVar(); + + } + + context.positionWorld = positionRay; + context.shadowPositionWorld = positionRay; + context.positionView = positionViewRay; + + scatteringDensity.assign( 0 ); + + let scatteringNode; + + if ( material.scatteringNode ) { + + scatteringNode = material.scatteringNode( { + positionRay + } ); + + } + + super.start( builder ); + + if ( scatteringNode ) { + + scatteringDensity.mulAssign( scatteringNode ); + + } + + // beer's law + + const falloff = scatteringDensity.mul( .01 ).negate().mul( stepSize ).exp(); + transmittance.mulAssign( falloff ); + + // move along the ray + + distTravelled.addAssign( stepSize ); + + } ); + + outgoingRayLight.addAssign( transmittance.saturate().oneMinus() ); + + } + + scatteringLight( lightColor, builder ) { + + const sceneDepthNode = builder.context.sceneDepthNode; + + if ( sceneDepthNode ) { + + If( sceneDepthNode.greaterThanEqual( linearDepthRay ), () => { + + scatteringDensity.addAssign( lightColor ); + + } ); + + } else { + + scatteringDensity.addAssign( lightColor ); + + } + + } + + direct( { lightNode, lightColor }, builder ) { + + // Ignore lights with infinite distance + + if ( lightNode.light.distance === undefined ) return; + + // TODO: We need a viewportOpaque*() ( output, depth ) to fit with modern rendering approaches + + const directLight = lightColor.xyz.toVar(); + directLight.mulAssign( lightNode.shadowNode ); // it no should be necessary if used in the same render pass + + this.scatteringLight( directLight, builder ); + + } + + directRectArea( { lightColor, lightPosition, halfWidth, halfHeight }, builder ) { + + const p0 = lightPosition.add( halfWidth ).sub( halfHeight ); // counterclockwise; light shines in local neg z direction + const p1 = lightPosition.sub( halfWidth ).sub( halfHeight ); + const p2 = lightPosition.sub( halfWidth ).add( halfHeight ); + const p3 = lightPosition.add( halfWidth ).add( halfHeight ); + + const P = builder.context.positionView; + + const directLight = lightColor.xyz.mul( LTC_Evaluate_Volume( { P, p0, p1, p2, p3 } ) ).pow( 1.5 ); + + this.scatteringLight( directLight, builder ); + + } + + finish( builder ) { + + builder.context.outgoingLight.assign( outgoingRayLight ); + + } + +} + +/** + * Volume node material. + * + * @augments NodeMaterial + */ +class VolumeNodeMaterial extends NodeMaterial { + + static get type() { + + return 'VolumeNodeMaterial'; + + } + + /** + * Constructs a new volume node material. + * + * @param {Object} [parameters] - The configuration parameter. + */ + constructor( parameters ) { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isVolumeNodeMaterial = true; + + /** + * Number of steps used for raymarching. + * + * @type {number} + * @default 25 + */ + this.steps = 25; + + /** + * Offsets the distance a ray has been traveled through a volume. + * Can be used to implement dithering to reduce banding. + * + * @type {Node} + * @default null + */ + this.offsetNode = null; + + /** + * Node used for scattering calculations. + * + * @type {Function|FunctionNode} + * @default null + */ + this.scatteringNode = null; + + this.lights = true; + + this.transparent = true; + this.side = BackSide; + + this.depthTest = false; + this.depthWrite = false; + + this.setValues( parameters ); + + } + + setupLightingModel() { + + return new VolumetricLightingModel(); + + } + +} + +/** + * This module manages the internal animation loop of the renderer. + * + * @private + */ +class Animation { + + /** + * Constructs a new animation loop management component. + * + * @param {Nodes} nodes - Renderer component for managing nodes related logic. + * @param {Info} info - Renderer component for managing metrics and monitoring data. + */ + constructor( nodes, info ) { + + /** + * Renderer component for managing nodes related logic. + * + * @type {Nodes} + */ + this.nodes = nodes; + + /** + * Renderer component for managing metrics and monitoring data. + * + * @type {Info} + */ + this.info = info; + + /** + * A reference to the context from `requestAnimationFrame()` can + * be called (usually `window`). + * + * @type {?(Window|XRSession)} + */ + this._context = typeof self !== 'undefined' ? self : null; + + /** + * The user-defined animation loop. + * + * @type {?Function} + * @default null + */ + this._animationLoop = null; + + /** + * The requestId which is returned from the `requestAnimationFrame()` call. + * Can be used to cancel the stop the animation loop. + * + * @type {?number} + * @default null + */ + this._requestId = null; + + } + + /** + * Starts the internal animation loop. + */ + start() { + + const update = ( time, xrFrame ) => { + + this._requestId = this._context.requestAnimationFrame( update ); + + if ( this.info.autoReset === true ) this.info.reset(); + + this.nodes.nodeFrame.update(); + + this.info.frame = this.nodes.nodeFrame.frameId; + + if ( this._animationLoop !== null ) this._animationLoop( time, xrFrame ); + + }; + + update(); + + } + + /** + * Stops the internal animation loop. + */ + stop() { + + this._context.cancelAnimationFrame( this._requestId ); + + this._requestId = null; + + } + + /** + * Returns the user-level animation loop. + * + * @return {?Function} The animation loop. + */ + getAnimationLoop() { + + return this._animationLoop; + + } + + /** + * Defines the user-level animation loop. + * + * @param {?Function} callback - The animation loop. + */ + setAnimationLoop( callback ) { + + this._animationLoop = callback; + + } + + /** + * Returns the animation context. + * + * @return {Window|XRSession} The animation context. + */ + getContext() { + + return this._context; + + } + + /** + * Defines the context in which `requestAnimationFrame()` is executed. + * + * @param {Window|XRSession} context - The context to set. + */ + setContext( context ) { + + this._context = context; + + } + + /** + * Frees all internal resources and stops the animation loop. + */ + dispose() { + + this.stop(); + + } + +} + +/** + * Data structure for the renderer. It allows defining values + * with chained, hierarchical keys. Keys are meant to be + * objects since the module internally works with Weak Maps + * for performance reasons. + * + * @private + */ +class ChainMap { + + /** + * Constructs a new Chain Map. + */ + constructor() { + + /** + * The root Weak Map. + * + * @type {WeakMap} + */ + this.weakMap = new WeakMap(); + + } + + /** + * Returns the value for the given array of keys. + * + * @param {Array} keys - List of keys. + * @return {any} The value. Returns `undefined` if no value was found. + */ + get( keys ) { + + let map = this.weakMap; + + for ( let i = 0; i < keys.length - 1; i ++ ) { + + map = map.get( keys[ i ] ); + + if ( map === undefined ) return undefined; + + } + + return map.get( keys[ keys.length - 1 ] ); + + } + + /** + * Sets the value for the given keys. + * + * @param {Array} keys - List of keys. + * @param {any} value - The value to set. + * @return {ChainMap} A reference to this Chain Map. + */ + set( keys, value ) { + + let map = this.weakMap; + + for ( let i = 0; i < keys.length - 1; i ++ ) { + + const key = keys[ i ]; + + if ( map.has( key ) === false ) map.set( key, new WeakMap() ); + + map = map.get( key ); + + } + + map.set( keys[ keys.length - 1 ], value ); + + return this; + + } + + /** + * Deletes a value for the given keys. + * + * @param {Array} keys - The keys. + * @return {boolean} Returns `true` if the value has been removed successfully and `false` if the value has not be found. + */ + delete( keys ) { + + let map = this.weakMap; + + for ( let i = 0; i < keys.length - 1; i ++ ) { + + map = map.get( keys[ i ] ); + + if ( map === undefined ) return false; + + } + + return map.delete( keys[ keys.length - 1 ] ); + + } + +} + +let _id$9 = 0; + +function getKeys( obj ) { + + const keys = Object.keys( obj ); + + let proto = Object.getPrototypeOf( obj ); + + while ( proto ) { + + const descriptors = Object.getOwnPropertyDescriptors( proto ); + + for ( const key in descriptors ) { + + if ( descriptors[ key ] !== undefined ) { + + const descriptor = descriptors[ key ]; + + if ( descriptor && typeof descriptor.get === 'function' ) { + + keys.push( key ); + + } + + } + + } + + proto = Object.getPrototypeOf( proto ); + + } + + return keys; + +} + +/** + * A render object is the renderer's representation of single entity that gets drawn + * with a draw command. There is no unique mapping of render objects to 3D objects in the + * scene since render objects also depend from the used material, the current render context + * and the current scene's lighting. + * + * In general, the basic process of the renderer is: + * + * - Analyze the 3D objects in the scene and generate render lists containing render items. + * - Process the render lists by calling one or more render commands for each render item. + * - For each render command, request a render object and perform the draw. + * + * The module provides an interface to get data required for the draw command like the actual + * draw parameters or vertex buffers. It also holds a series of caching related methods since + * creating render objects should only be done when necessary. + * + * @private + */ +class RenderObject { + + /** + * Constructs a new render object. + * + * @param {Nodes} nodes - Renderer component for managing nodes related logic. + * @param {Geometries} geometries - Renderer component for managing geometries. + * @param {Renderer} renderer - The renderer. + * @param {Object3D} object - The 3D object. + * @param {Material} material - The 3D object's material. + * @param {Scene} scene - The scene the 3D object belongs to. + * @param {Camera} camera - The camera the object should be rendered with. + * @param {LightsNode} lightsNode - The lights node. + * @param {RenderContext} renderContext - The render context. + * @param {ClippingContext} clippingContext - The clipping context. + */ + constructor( nodes, geometries, renderer, object, material, scene, camera, lightsNode, renderContext, clippingContext ) { + + this.id = _id$9 ++; + + /** + * Renderer component for managing nodes related logic. + * + * @type {Nodes} + * @private + */ + this._nodes = nodes; + + /** + * Renderer component for managing geometries. + * + * @type {Geometries} + * @private + */ + this._geometries = geometries; + + /** + * The renderer. + * + * @type {Renderer} + */ + this.renderer = renderer; + + /** + * The 3D object. + * + * @type {Object3D} + */ + this.object = object; + + /** + * The 3D object's material. + * + * @type {Material} + */ + this.material = material; + + /** + * The scene the 3D object belongs to. + * + * @type {Scene} + */ + this.scene = scene; + + /** + * The camera the 3D object should be rendered with. + * + * @type {Camera} + */ + this.camera = camera; + + /** + * The lights node. + * + * @type {LightsNode} + */ + this.lightsNode = lightsNode; + + /** + * The render context. + * + * @type {RenderContext} + */ + this.context = renderContext; + + /** + * The 3D object's geometry. + * + * @type {BufferGeometry} + */ + this.geometry = object.geometry; + + /** + * The render object's version. + * + * @type {number} + */ + this.version = material.version; + + /** + * The draw range of the geometry. + * + * @type {?Object} + * @default null + */ + this.drawRange = null; + + /** + * An array holding the buffer attributes + * of the render object. This entails attribute + * definitions on geometry and node level. + * + * @type {?Array} + * @default null + */ + this.attributes = null; + + /** + * An object holding the version of the + * attributes. The keys are the attribute names + * and the values are the attribute versions. + * + * @type {?Object} + * @default null + */ + this.attributesId = null; + + /** + * A reference to a render pipeline the render + * object is processed with. + * + * @type {RenderPipeline} + * @default null + */ + this.pipeline = null; + + /** + * Only relevant for objects using + * multiple materials. This represents a group entry + * from the respective `BufferGeometry`. + * + * @type {?{start: number, count: number}} + * @default null + */ + this.group = null; + + /** + * An array holding the vertex buffers which can + * be buffer attributes but also interleaved buffers. + * + * @type {?Array} + * @default null + */ + this.vertexBuffers = null; + + /** + * The parameters for the draw command. + * + * @type {?Object} + * @default null + */ + this.drawParams = null; + + /** + * If this render object is used inside a render bundle, + * this property points to the respective bundle group. + * + * @type {?BundleGroup} + * @default null + */ + this.bundle = null; + + /** + * The clipping context. + * + * @type {ClippingContext} + */ + this.clippingContext = clippingContext; + + /** + * The clipping context's cache key. + * + * @type {string} + */ + this.clippingContextCacheKey = clippingContext !== null ? clippingContext.cacheKey : ''; + + /** + * The initial node cache key. + * + * @type {number} + */ + this.initialNodesCacheKey = this.getDynamicCacheKey(); + + /** + * The initial cache key. + * + * @type {number} + */ + this.initialCacheKey = this.getCacheKey(); + + /** + * The node builder state. + * + * @type {?NodeBuilderState} + * @private + * @default null + */ + this._nodeBuilderState = null; + + /** + * An array of bindings. + * + * @type {?Array} + * @private + * @default null + */ + this._bindings = null; + + /** + * Reference to the node material observer. + * + * @type {?NodeMaterialObserver} + * @private + * @default null + */ + this._monitor = null; + + /** + * An event listener which is defined by `RenderObjects`. It performs + * clean up tasks when `dispose()` on this render object. + * + * @method + */ + this.onDispose = null; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isRenderObject = true; + + /** + * An event listener which is executed when `dispose()` is called on + * the material of this render object. + * + * @method + */ + this.onMaterialDispose = () => { + + this.dispose(); + + }; + + /** + * An event listener which is executed when `dispose()` is called on + * the geometry of this render object. + * + * @method + */ + this.onGeometryDispose = () => { + + // clear geometry cache attributes + + this.attributes = null; + this.attributesId = null; + + }; + + this.material.addEventListener( 'dispose', this.onMaterialDispose ); + this.geometry.addEventListener( 'dispose', this.onGeometryDispose ); + + } + + /** + * Updates the clipping context. + * + * @param {ClippingContext} context - The clipping context to set. + */ + updateClipping( context ) { + + this.clippingContext = context; + + } + + /** + * Whether the clipping requires an update or not. + * + * @type {boolean} + * @readonly + */ + get clippingNeedsUpdate() { + + if ( this.clippingContext === null || this.clippingContext.cacheKey === this.clippingContextCacheKey ) return false; + + this.clippingContextCacheKey = this.clippingContext.cacheKey; + + return true; + + } + + /** + * The number of clipping planes defined in context of hardware clipping. + * + * @type {number} + * @readonly + */ + get hardwareClippingPlanes() { + + return this.material.hardwareClipping === true ? this.clippingContext.unionClippingCount : 0; + + } + + /** + * Returns the node builder state of this render object. + * + * @return {NodeBuilderState} The node builder state. + */ + getNodeBuilderState() { + + return this._nodeBuilderState || ( this._nodeBuilderState = this._nodes.getForRender( this ) ); + + } + + /** + * Returns the node material observer of this render object. + * + * @return {NodeMaterialObserver} The node material observer. + */ + getMonitor() { + + return this._monitor || ( this._monitor = this.getNodeBuilderState().observer ); + + } + + /** + * Returns an array of bind groups of this render object. + * + * @return {Array} The bindings. + */ + getBindings() { + + return this._bindings || ( this._bindings = this.getNodeBuilderState().createBindings() ); + + } + + /** + * Returns a binding group by group name of this render object. + * + * @param {string} name - The name of the binding group. + * @return {?BindGroup} The bindings. + */ + getBindingGroup( name ) { + + for ( const bindingGroup of this.getBindings() ) { + + if ( bindingGroup.name === name ) { + + return bindingGroup; + + } + + } + + } + + /** + * Returns the index of the render object's geometry. + * + * @return {?BufferAttribute} The index. Returns `null` for non-indexed geometries. + */ + getIndex() { + + return this._geometries.getIndex( this ); + + } + + /** + * Returns the indirect buffer attribute. + * + * @return {?BufferAttribute} The indirect attribute. `null` if no indirect drawing is used. + */ + getIndirect() { + + return this._geometries.getIndirect( this ); + + } + + /** + * Returns an array that acts as a key for identifying the render object in a chain map. + * + * @return {Array} An array with object references. + */ + getChainArray() { + + return [ this.object, this.material, this.context, this.lightsNode ]; + + } + + /** + * This method is used when the geometry of a 3D object has been exchanged and the + * respective render object now requires an update. + * + * @param {BufferGeometry} geometry - The geometry to set. + */ + setGeometry( geometry ) { + + this.geometry = geometry; + this.attributes = null; + this.attributesId = null; + + } + + /** + * Returns the buffer attributes of the render object. The returned array holds + * attribute definitions on geometry and node level. + * + * @return {Array} An array with buffer attributes. + */ + getAttributes() { + + if ( this.attributes !== null ) return this.attributes; + + const nodeAttributes = this.getNodeBuilderState().nodeAttributes; + const geometry = this.geometry; + + const attributes = []; + const vertexBuffers = new Set(); + + const attributesId = {}; + + for ( const nodeAttribute of nodeAttributes ) { + + let attribute; + + if ( nodeAttribute.node && nodeAttribute.node.attribute ) { + + // node attribute + attribute = nodeAttribute.node.attribute; + + } else { + + // geometry attribute + attribute = geometry.getAttribute( nodeAttribute.name ); + + attributesId[ nodeAttribute.name ] = attribute.version; + + } + + if ( attribute === undefined ) continue; + + attributes.push( attribute ); + + const bufferAttribute = attribute.isInterleavedBufferAttribute ? attribute.data : attribute; + vertexBuffers.add( bufferAttribute ); + + } + + this.attributes = attributes; + this.attributesId = attributesId; + this.vertexBuffers = Array.from( vertexBuffers.values() ); + + return attributes; + + } + + /** + * Returns the vertex buffers of the render object. + * + * @return {Array} An array with buffer attribute or interleaved buffers. + */ + getVertexBuffers() { + + if ( this.vertexBuffers === null ) this.getAttributes(); + + return this.vertexBuffers; + + } + + /** + * Returns the draw parameters for the render object. + * + * @return {?{vertexCount: number, firstVertex: number, instanceCount: number, firstInstance: number}} The draw parameters. + */ + getDrawParameters() { + + const { object, material, geometry, group, drawRange } = this; + + const drawParams = this.drawParams || ( this.drawParams = { + vertexCount: 0, + firstVertex: 0, + instanceCount: 0, + firstInstance: 0 + } ); + + const index = this.getIndex(); + const hasIndex = ( index !== null ); + + let instanceCount = 1; + + if ( geometry.isInstancedBufferGeometry === true ) { + + instanceCount = geometry.instanceCount; + + } else if ( object.count !== undefined ) { + + instanceCount = Math.max( 0, object.count ); + + } + + if ( instanceCount === 0 ) return null; + + drawParams.instanceCount = instanceCount; + + if ( object.isBatchedMesh === true ) return drawParams; + + let rangeFactor = 1; + + if ( material.wireframe === true && ! object.isPoints && ! object.isLineSegments && ! object.isLine && ! object.isLineLoop ) { + + rangeFactor = 2; + + } + + let firstVertex = drawRange.start * rangeFactor; + let lastVertex = ( drawRange.start + drawRange.count ) * rangeFactor; + + if ( group !== null ) { + + firstVertex = Math.max( firstVertex, group.start * rangeFactor ); + lastVertex = Math.min( lastVertex, ( group.start + group.count ) * rangeFactor ); + + } + + const position = geometry.attributes.position; + let itemCount = Infinity; + + if ( hasIndex ) { + + itemCount = index.count; + + } else if ( position !== undefined && position !== null ) { + + itemCount = position.count; + + } + + firstVertex = Math.max( firstVertex, 0 ); + lastVertex = Math.min( lastVertex, itemCount ); + + const count = lastVertex - firstVertex; + + if ( count < 0 || count === Infinity ) return null; + + drawParams.vertexCount = count; + drawParams.firstVertex = firstVertex; + + return drawParams; + + } + + /** + * Returns the render object's geometry cache key. + * + * The geometry cache key is part of the material cache key. + * + * @return {string} The geometry cache key. + */ + getGeometryCacheKey() { + + const { geometry } = this; + + let cacheKey = ''; + + for ( const name of Object.keys( geometry.attributes ).sort() ) { + + const attribute = geometry.attributes[ name ]; + + cacheKey += name + ','; + + if ( attribute.data ) cacheKey += attribute.data.stride + ','; + if ( attribute.offset ) cacheKey += attribute.offset + ','; + if ( attribute.itemSize ) cacheKey += attribute.itemSize + ','; + if ( attribute.normalized ) cacheKey += 'n,'; + + } + + // structural equality isn't sufficient for morph targets since the + // data are maintained in textures. only if the targets are all equal + // the texture and thus the instance of `MorphNode` can be shared. + + for ( const name of Object.keys( geometry.morphAttributes ).sort() ) { + + const targets = geometry.morphAttributes[ name ]; + + cacheKey += 'morph-' + name + ','; + + for ( let i = 0, l = targets.length; i < l; i ++ ) { + + const attribute = targets[ i ]; + + cacheKey += attribute.id + ','; + + } + + } + + if ( geometry.index ) { + + cacheKey += 'index,'; + + } + + return cacheKey; + + } + + /** + * Returns the render object's material cache key. + * + * The material cache key is part of the render object cache key. + * + * @return {number} The material cache key. + */ + getMaterialCacheKey() { + + const { object, material } = this; + + let cacheKey = material.customProgramCacheKey(); + + for ( const property of getKeys( material ) ) { + + if ( /^(is[A-Z]|_)|^(visible|version|uuid|name|opacity|userData)$/.test( property ) ) continue; + + const value = material[ property ]; + + let valueKey; + + if ( value !== null ) { + + // some material values require a formatting + + const type = typeof value; + + if ( type === 'number' ) { + + valueKey = value !== 0 ? '1' : '0'; // Convert to on/off, important for clearcoat, transmission, etc + + } else if ( type === 'object' ) { + + valueKey = '{'; + + if ( value.isTexture ) { + + valueKey += value.mapping; + + } + + valueKey += '}'; + + } else { + + valueKey = String( value ); + + } + + } else { + + valueKey = String( value ); + + } + + cacheKey += /*property + ':' +*/ valueKey + ','; + + } + + cacheKey += this.clippingContextCacheKey + ','; + + if ( object.geometry ) { + + cacheKey += this.getGeometryCacheKey(); + + } + + if ( object.skeleton ) { + + cacheKey += object.skeleton.bones.length + ','; + + } + + if ( object.isBatchedMesh ) { + + cacheKey += object._matricesTexture.uuid + ','; + + if ( object._colorsTexture !== null ) { + + cacheKey += object._colorsTexture.uuid + ','; + + } + + } + + if ( object.count > 1 ) { + + // TODO: https://github.com/mrdoob/three.js/pull/29066#issuecomment-2269400850 + + cacheKey += object.uuid + ','; + + } + + cacheKey += object.receiveShadow + ','; + + return hashString( cacheKey ); + + } + + /** + * Whether the geometry requires an update or not. + * + * @type {boolean} + * @readonly + */ + get needsGeometryUpdate() { + + if ( this.geometry.id !== this.object.geometry.id ) return true; + + if ( this.attributes !== null ) { + + const attributesId = this.attributesId; + + for ( const name in attributesId ) { + + const attribute = this.geometry.getAttribute( name ); + + if ( attribute === undefined || attributesId[ name ] !== attribute.id ) { + + return true; + + } + + } + + } + + return false; + + } + + /** + * Whether the render object requires an update or not. + * + * Note: There are two distinct places where render objects are checked for an update. + * + * 1. In `RenderObjects.get()` which is executed when the render object is request. This + * method checks the `needsUpdate` flag and recreates the render object if necessary. + * 2. In `Renderer._renderObjectDirect()` right after getting the render object via + * `RenderObjects.get()`. The render object's NodeMaterialObserver is then used to detect + * a need for a refresh due to material, geometry or object related value changes. + * + * TODO: Investigate if it's possible to merge both steps so there is only a single place + * that performs the 'needsUpdate' check. + * + * @type {boolean} + * @readonly + */ + get needsUpdate() { + + return /*this.object.static !== true &&*/ ( this.initialNodesCacheKey !== this.getDynamicCacheKey() || this.clippingNeedsUpdate ); + + } + + /** + * Returns the dynamic cache key which represents a key that is computed per draw command. + * + * @return {number} The cache key. + */ + getDynamicCacheKey() { + + let cacheKey = 0; + + // `Nodes.getCacheKey()` returns an environment cache key which is not relevant when + // the renderer is inside a shadow pass. + + if ( this.material.isShadowPassMaterial !== true ) { + + cacheKey = this._nodes.getCacheKey( this.scene, this.lightsNode ); + + } + + if ( this.camera.isArrayCamera ) { + + cacheKey = hash$1( cacheKey, this.camera.cameras.length ); + + } + + if ( this.object.receiveShadow ) { + + cacheKey = hash$1( cacheKey, 1 ); + + } + + return cacheKey; + + } + + /** + * Returns the render object's cache key. + * + * @return {number} The cache key. + */ + getCacheKey() { + + return this.getMaterialCacheKey() + this.getDynamicCacheKey(); + + } + + /** + * Frees internal resources. + */ + dispose() { + + this.material.removeEventListener( 'dispose', this.onMaterialDispose ); + this.geometry.removeEventListener( 'dispose', this.onGeometryDispose ); + + this.onDispose(); + + } + +} + +const _chainKeys$5 = []; + +/** + * This module manages the render objects of the renderer. + * + * @private + */ +class RenderObjects { + + /** + * Constructs a new render object management component. + * + * @param {Renderer} renderer - The renderer. + * @param {Nodes} nodes - Renderer component for managing nodes related logic. + * @param {Geometries} geometries - Renderer component for managing geometries. + * @param {Pipelines} pipelines - Renderer component for managing pipelines. + * @param {Bindings} bindings - Renderer component for managing bindings. + * @param {Info} info - Renderer component for managing metrics and monitoring data. + */ + constructor( renderer, nodes, geometries, pipelines, bindings, info ) { + + /** + * The renderer. + * + * @type {Renderer} + */ + this.renderer = renderer; + + /** + * Renderer component for managing nodes related logic. + * + * @type {Nodes} + */ + this.nodes = nodes; + + /** + * Renderer component for managing geometries. + * + * @type {Geometries} + */ + this.geometries = geometries; + + /** + * Renderer component for managing pipelines. + * + * @type {Pipelines} + */ + this.pipelines = pipelines; + + /** + * Renderer component for managing bindings. + * + * @type {Bindings} + */ + this.bindings = bindings; + + /** + * Renderer component for managing metrics and monitoring data. + * + * @type {Info} + */ + this.info = info; + + /** + * A dictionary that manages render contexts in chain maps + * for each pass ID. + * + * @type {Object} + */ + this.chainMaps = {}; + + } + + /** + * Returns a render object for the given object and state data. + * + * @param {Object3D} object - The 3D object. + * @param {Material} material - The 3D object's material. + * @param {Scene} scene - The scene the 3D object belongs to. + * @param {Camera} camera - The camera the 3D object should be rendered with. + * @param {LightsNode} lightsNode - The lights node. + * @param {RenderContext} renderContext - The render context. + * @param {ClippingContext} clippingContext - The clipping context. + * @param {string} [passId] - An optional ID for identifying the pass. + * @return {RenderObject} The render object. + */ + get( object, material, scene, camera, lightsNode, renderContext, clippingContext, passId ) { + + const chainMap = this.getChainMap( passId ); + + // reuse chainArray + _chainKeys$5[ 0 ] = object; + _chainKeys$5[ 1 ] = material; + _chainKeys$5[ 2 ] = renderContext; + _chainKeys$5[ 3 ] = lightsNode; + + let renderObject = chainMap.get( _chainKeys$5 ); + + if ( renderObject === undefined ) { + + renderObject = this.createRenderObject( this.nodes, this.geometries, this.renderer, object, material, scene, camera, lightsNode, renderContext, clippingContext, passId ); + + chainMap.set( _chainKeys$5, renderObject ); + + } else { + + renderObject.updateClipping( clippingContext ); + + if ( renderObject.needsGeometryUpdate ) { + + renderObject.setGeometry( object.geometry ); + + } + + if ( renderObject.version !== material.version || renderObject.needsUpdate ) { + + if ( renderObject.initialCacheKey !== renderObject.getCacheKey() ) { + + renderObject.dispose(); + + renderObject = this.get( object, material, scene, camera, lightsNode, renderContext, clippingContext, passId ); + + } else { + + renderObject.version = material.version; + + } + + } + + } + + _chainKeys$5.length = 0; + + return renderObject; + + } + + /** + * Returns a chain map for the given pass ID. + * + * @param {string} [passId='default'] - The pass ID. + * @return {ChainMap} The chain map. + */ + getChainMap( passId = 'default' ) { + + return this.chainMaps[ passId ] || ( this.chainMaps[ passId ] = new ChainMap() ); + + } + + /** + * Frees internal resources. + */ + dispose() { + + this.chainMaps = {}; + + } + + /** + * Factory method for creating render objects with the given list of parameters. + * + * @param {Nodes} nodes - Renderer component for managing nodes related logic. + * @param {Geometries} geometries - Renderer component for managing geometries. + * @param {Renderer} renderer - The renderer. + * @param {Object3D} object - The 3D object. + * @param {Material} material - The object's material. + * @param {Scene} scene - The scene the 3D object belongs to. + * @param {Camera} camera - The camera the object should be rendered with. + * @param {LightsNode} lightsNode - The lights node. + * @param {RenderContext} renderContext - The render context. + * @param {ClippingContext} clippingContext - The clipping context. + * @param {string} [passId] - An optional ID for identifying the pass. + * @return {RenderObject} The render object. + */ + createRenderObject( nodes, geometries, renderer, object, material, scene, camera, lightsNode, renderContext, clippingContext, passId ) { + + const chainMap = this.getChainMap( passId ); + + const renderObject = new RenderObject( nodes, geometries, renderer, object, material, scene, camera, lightsNode, renderContext, clippingContext ); + + renderObject.onDispose = () => { + + this.pipelines.delete( renderObject ); + this.bindings.delete( renderObject ); + this.nodes.delete( renderObject ); + + chainMap.delete( renderObject.getChainArray() ); + + }; + + return renderObject; + + } + + +} + +/** + * Data structure for the renderer. It is intended to manage + * data of objects in dictionaries. + * + * @private + */ +class DataMap { + + /** + * Constructs a new data map. + */ + constructor() { + + /** + * `DataMap` internally uses a weak map + * to manage its data. + * + * @type {WeakMap} + */ + this.data = new WeakMap(); + + } + + /** + * Returns the dictionary for the given object. + * + * @param {Object} object - The object. + * @return {Object} The dictionary. + */ + get( object ) { + + let map = this.data.get( object ); + + if ( map === undefined ) { + + map = {}; + this.data.set( object, map ); + + } + + return map; + + } + + /** + * Deletes the dictionary for the given object. + * + * @param {Object} object - The object. + * @return {?Object} The deleted dictionary. + */ + delete( object ) { + + let map = null; + + if ( this.data.has( object ) ) { + + map = this.data.get( object ); + + this.data.delete( object ); + + } + + return map; + + } + + /** + * Returns `true` if the given object has a dictionary defined. + * + * @param {Object} object - The object to test. + * @return {boolean} Whether a dictionary is defined or not. + */ + has( object ) { + + return this.data.has( object ); + + } + + /** + * Frees internal resources. + */ + dispose() { + + this.data = new WeakMap(); + + } + +} + +const AttributeType = { + VERTEX: 1, + INDEX: 2, + STORAGE: 3, + INDIRECT: 4 +}; + +// size of a chunk in bytes (STD140 layout) + +const GPU_CHUNK_BYTES = 16; + +// @TODO: Move to src/constants.js + +const BlendColorFactor = 211; +const OneMinusBlendColorFactor = 212; + +/** + * This renderer module manages geometry attributes. + * + * @private + * @augments DataMap + */ +class Attributes extends DataMap { + + /** + * Constructs a new attribute management component. + * + * @param {Backend} backend - The renderer's backend. + */ + constructor( backend ) { + + super(); + + /** + * The renderer's backend. + * + * @type {Backend} + */ + this.backend = backend; + + } + + /** + * Deletes the data for the given attribute. + * + * @param {BufferAttribute} attribute - The attribute. + * @return {Object|null} The deleted attribute data. + */ + delete( attribute ) { + + const attributeData = super.delete( attribute ); + + if ( attributeData !== null ) { + + this.backend.destroyAttribute( attribute ); + + } + + return attributeData; + + } + + /** + * Updates the given attribute. This method creates attribute buffers + * for new attributes and updates data for existing ones. + * + * @param {BufferAttribute} attribute - The attribute to update. + * @param {number} type - The attribute type. + */ + update( attribute, type ) { + + const data = this.get( attribute ); + + if ( data.version === undefined ) { + + if ( type === AttributeType.VERTEX ) { + + this.backend.createAttribute( attribute ); + + } else if ( type === AttributeType.INDEX ) { + + this.backend.createIndexAttribute( attribute ); + + } else if ( type === AttributeType.STORAGE ) { + + this.backend.createStorageAttribute( attribute ); + + } else if ( type === AttributeType.INDIRECT ) { + + this.backend.createIndirectStorageAttribute( attribute ); + + } + + data.version = this._getBufferAttribute( attribute ).version; + + } else { + + const bufferAttribute = this._getBufferAttribute( attribute ); + + if ( data.version < bufferAttribute.version || bufferAttribute.usage === DynamicDrawUsage ) { + + this.backend.updateAttribute( attribute ); + + data.version = bufferAttribute.version; + + } + + } + + } + + /** + * Utility method for handling interleaved buffer attributes correctly. + * To process them, their `InterleavedBuffer` is returned. + * + * @param {BufferAttribute} attribute - The attribute. + * @return {BufferAttribute|InterleavedBuffer} + */ + _getBufferAttribute( attribute ) { + + if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; + + return attribute; + + } + +} + +/** + * Returns the wireframe version for the given geometry. + * + * @private + * @function + * @param {BufferGeometry} geometry - The geometry. + * @return {number} The version. + */ +function getWireframeVersion( geometry ) { + + return ( geometry.index !== null ) ? geometry.index.version : geometry.attributes.position.version; + +} + +/** + * Returns a wireframe index attribute for the given geometry. + * + * @private + * @function + * @param {BufferGeometry} geometry - The geometry. + * @return {BufferAttribute} The wireframe index attribute. + */ +function getWireframeIndex( geometry ) { + + const indices = []; + + const geometryIndex = geometry.index; + const geometryPosition = geometry.attributes.position; + + if ( geometryIndex !== null ) { + + const array = geometryIndex.array; + + for ( let i = 0, l = array.length; i < l; i += 3 ) { + + const a = array[ i + 0 ]; + const b = array[ i + 1 ]; + const c = array[ i + 2 ]; + + indices.push( a, b, b, c, c, a ); + + } + + } else { + + const array = geometryPosition.array; + + for ( let i = 0, l = ( array.length / 3 ) - 1; i < l; i += 3 ) { + + const a = i + 0; + const b = i + 1; + const c = i + 2; + + indices.push( a, b, b, c, c, a ); + + } + + } + + const attribute = new ( arrayNeedsUint32( indices ) ? Uint32BufferAttribute : Uint16BufferAttribute )( indices, 1 ); + attribute.version = getWireframeVersion( geometry ); + + return attribute; + +} + +/** + * This renderer module manages geometries. + * + * @private + * @augments DataMap + */ +class Geometries extends DataMap { + + /** + * Constructs a new geometry management component. + * + * @param {Attributes} attributes - Renderer component for managing attributes. + * @param {Info} info - Renderer component for managing metrics and monitoring data. + */ + constructor( attributes, info ) { + + super(); + + /** + * Renderer component for managing attributes. + * + * @type {Attributes} + */ + this.attributes = attributes; + + /** + * Renderer component for managing metrics and monitoring data. + * + * @type {Info} + */ + this.info = info; + + /** + * Weak Map for managing attributes for wireframe rendering. + * + * @type {WeakMap} + */ + this.wireframes = new WeakMap(); + + /** + * This Weak Map is used to make sure buffer attributes are + * updated only once per render call. + * + * @type {WeakMap} + */ + this.attributeCall = new WeakMap(); + + } + + /** + * Returns `true` if the given render object has an initialized geometry. + * + * @param {RenderObject} renderObject - The render object. + * @return {boolean} Whether if the given render object has an initialized geometry or not. + */ + has( renderObject ) { + + const geometry = renderObject.geometry; + + return super.has( geometry ) && this.get( geometry ).initialized === true; + + } + + /** + * Prepares the geometry of the given render object for rendering. + * + * @param {RenderObject} renderObject - The render object. + */ + updateForRender( renderObject ) { + + if ( this.has( renderObject ) === false ) this.initGeometry( renderObject ); + + this.updateAttributes( renderObject ); + + } + + /** + * Initializes the geometry of the given render object. + * + * @param {RenderObject} renderObject - The render object. + */ + initGeometry( renderObject ) { + + const geometry = renderObject.geometry; + const geometryData = this.get( geometry ); + + geometryData.initialized = true; + + this.info.memory.geometries ++; + + const onDispose = () => { + + this.info.memory.geometries --; + + const index = geometry.index; + const geometryAttributes = renderObject.getAttributes(); + + if ( index !== null ) { + + this.attributes.delete( index ); + + } + + for ( const geometryAttribute of geometryAttributes ) { + + this.attributes.delete( geometryAttribute ); + + } + + const wireframeAttribute = this.wireframes.get( geometry ); + + if ( wireframeAttribute !== undefined ) { + + this.attributes.delete( wireframeAttribute ); + + } + + geometry.removeEventListener( 'dispose', onDispose ); + + }; + + geometry.addEventListener( 'dispose', onDispose ); + + } + + /** + * Updates the geometry attributes of the given render object. + * + * @param {RenderObject} renderObject - The render object. + */ + updateAttributes( renderObject ) { + + // attributes + + const attributes = renderObject.getAttributes(); + + for ( const attribute of attributes ) { + + if ( attribute.isStorageBufferAttribute || attribute.isStorageInstancedBufferAttribute ) { + + this.updateAttribute( attribute, AttributeType.STORAGE ); + + } else { + + this.updateAttribute( attribute, AttributeType.VERTEX ); + + } + + } + + // indexes + + const index = this.getIndex( renderObject ); + + if ( index !== null ) { + + this.updateAttribute( index, AttributeType.INDEX ); + + } + + // indirect + + const indirect = renderObject.geometry.indirect; + + if ( indirect !== null ) { + + this.updateAttribute( indirect, AttributeType.INDIRECT ); + + } + + } + + /** + * Updates the given attribute. + * + * @param {BufferAttribute} attribute - The attribute to update. + * @param {number} type - The attribute type. + */ + updateAttribute( attribute, type ) { + + const callId = this.info.render.calls; + + if ( ! attribute.isInterleavedBufferAttribute ) { + + if ( this.attributeCall.get( attribute ) !== callId ) { + + this.attributes.update( attribute, type ); + + this.attributeCall.set( attribute, callId ); + + } + + } else { + + if ( this.attributeCall.get( attribute ) === undefined ) { + + this.attributes.update( attribute, type ); + + this.attributeCall.set( attribute, callId ); + + } else if ( this.attributeCall.get( attribute.data ) !== callId ) { + + this.attributes.update( attribute, type ); + + this.attributeCall.set( attribute.data, callId ); + + this.attributeCall.set( attribute, callId ); + + } + + } + + } + + /** + * Returns the indirect buffer attribute of the given render object. + * + * @param {RenderObject} renderObject - The render object. + * @return {?BufferAttribute} The indirect attribute. `null` if no indirect drawing is used. + */ + getIndirect( renderObject ) { + + return renderObject.geometry.indirect; + + } + + /** + * Returns the index of the given render object's geometry. This is implemented + * in a method to return a wireframe index if necessary. + * + * @param {RenderObject} renderObject - The render object. + * @return {?BufferAttribute} The index. Returns `null` for non-indexed geometries. + */ + getIndex( renderObject ) { + + const { geometry, material } = renderObject; + + let index = geometry.index; + + if ( material.wireframe === true ) { + + const wireframes = this.wireframes; + + let wireframeAttribute = wireframes.get( geometry ); + + if ( wireframeAttribute === undefined ) { + + wireframeAttribute = getWireframeIndex( geometry ); + + wireframes.set( geometry, wireframeAttribute ); + + } else if ( wireframeAttribute.version !== getWireframeVersion( geometry ) ) { + + this.attributes.delete( wireframeAttribute ); + + wireframeAttribute = getWireframeIndex( geometry ); + + wireframes.set( geometry, wireframeAttribute ); + + } + + index = wireframeAttribute; + + } + + return index; + + } + +} + +/** + * This renderer module provides a series of statistical information + * about the GPU memory and the rendering process. Useful for debugging + * and monitoring. + */ +class Info { + + /** + * Constructs a new info component. + */ + constructor() { + + /** + * Whether frame related metrics should automatically + * be resetted or not. This property should be set to `false` + * by apps which manage their own animation loop. They must + * then call `renderer.info.reset()` once per frame manually. + * + * @type {boolean} + * @default true + */ + this.autoReset = true; + + /** + * The current frame ID. This ID is managed + * by `NodeFrame`. + * + * @type {number} + * @readonly + * @default 0 + */ + this.frame = 0; + + /** + * The number of render calls since the + * app has been started. + * + * @type {number} + * @readonly + * @default 0 + */ + this.calls = 0; + + /** + * Render related metrics. + * + * @type {Object} + * @readonly + * @property {number} calls - The number of render calls since the app has been started. + * @property {number} frameCalls - The number of render calls of the current frame. + * @property {number} drawCalls - The number of draw calls of the current frame. + * @property {number} triangles - The number of rendered triangle primitives of the current frame. + * @property {number} points - The number of rendered point primitives of the current frame. + * @property {number} lines - The number of rendered line primitives of the current frame. + * @property {number} timestamp - The timestamp of the frame when using `renderer.renderAsync()`. + */ + this.render = { + calls: 0, + frameCalls: 0, + drawCalls: 0, + triangles: 0, + points: 0, + lines: 0, + timestamp: 0, + }; + + /** + * Compute related metrics. + * + * @type {Object} + * @readonly + * @property {number} calls - The number of compute calls since the app has been started. + * @property {number} frameCalls - The number of compute calls of the current frame. + * @property {number} timestamp - The timestamp of the frame when using `renderer.computeAsync()`. + */ + this.compute = { + calls: 0, + frameCalls: 0, + timestamp: 0 + }; + + /** + * Memory related metrics. + * + * @type {Object} + * @readonly + * @property {number} geometries - The number of active geometries. + * @property {number} frameCalls - The number of active textures. + */ + this.memory = { + geometries: 0, + textures: 0 + }; + + } + + /** + * This method should be executed per draw call and updates the corresponding metrics. + * + * @param {Object3D} object - The 3D object that is going to be rendered. + * @param {number} count - The vertex or index count. + * @param {number} instanceCount - The instance count. + */ + update( object, count, instanceCount ) { + + this.render.drawCalls ++; + + if ( object.isMesh || object.isSprite ) { + + this.render.triangles += instanceCount * ( count / 3 ); + + } else if ( object.isPoints ) { + + this.render.points += instanceCount * count; + + } else if ( object.isLineSegments ) { + + this.render.lines += instanceCount * ( count / 2 ); + + } else if ( object.isLine ) { + + this.render.lines += instanceCount * ( count - 1 ); + + } else { + + console.error( 'THREE.WebGPUInfo: Unknown object type.' ); + + } + + } + + /** + * Resets frame related metrics. + */ + reset() { + + this.render.drawCalls = 0; + this.render.frameCalls = 0; + this.compute.frameCalls = 0; + + this.render.triangles = 0; + this.render.points = 0; + this.render.lines = 0; + + + } + + /** + * Performs a complete reset of the object. + */ + dispose() { + + this.reset(); + + this.calls = 0; + + this.render.calls = 0; + this.compute.calls = 0; + + this.render.timestamp = 0; + this.compute.timestamp = 0; + this.memory.geometries = 0; + this.memory.textures = 0; + + } + +} + +/** + * Abstract class for representing pipelines. + * + * @private + * @abstract + */ +class Pipeline { + + /** + * Constructs a new pipeline. + * + * @param {string} cacheKey - The pipeline's cache key. + */ + constructor( cacheKey ) { + + /** + * The pipeline's cache key. + * + * @type {string} + */ + this.cacheKey = cacheKey; + + /** + * How often the pipeline is currently in use. + * + * @type {number} + * @default 0 + */ + this.usedTimes = 0; + + } + +} + +/** + * Class for representing render pipelines. + * + * @private + * @augments Pipeline + */ +class RenderPipeline extends Pipeline { + + /** + * Constructs a new render pipeline. + * + * @param {string} cacheKey - The pipeline's cache key. + * @param {ProgrammableStage} vertexProgram - The pipeline's vertex shader. + * @param {ProgrammableStage} fragmentProgram - The pipeline's fragment shader. + */ + constructor( cacheKey, vertexProgram, fragmentProgram ) { + + super( cacheKey ); + + /** + * The pipeline's vertex shader. + * + * @type {ProgrammableStage} + */ + this.vertexProgram = vertexProgram; + + /** + * The pipeline's fragment shader. + * + * @type {ProgrammableStage} + */ + this.fragmentProgram = fragmentProgram; + + } + +} + +/** + * Class for representing compute pipelines. + * + * @private + * @augments Pipeline + */ +class ComputePipeline extends Pipeline { + + /** + * Constructs a new render pipeline. + * + * @param {string} cacheKey - The pipeline's cache key. + * @param {ProgrammableStage} computeProgram - The pipeline's compute shader. + */ + constructor( cacheKey, computeProgram ) { + + super( cacheKey ); + + /** + * The pipeline's compute shader. + * + * @type {ProgrammableStage} + */ + this.computeProgram = computeProgram; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isComputePipeline = true; + + } + +} + +let _id$8 = 0; + +/** + * Class for representing programmable stages which are vertex, + * fragment or compute shaders. Unlike fixed-function states (like blending), + * they represent the programmable part of a pipeline. + * + * @private + */ +class ProgrammableStage { + + /** + * Constructs a new programmable stage. + * + * @param {string} code - The shader code. + * @param {('vertex'|'fragment'|'compute')} stage - The type of stage. + * @param {string} name - The name of the shader. + * @param {?Array} [transforms=null] - The transforms (only relevant for compute stages with WebGL 2 which uses Transform Feedback). + * @param {?Array} [attributes=null] - The attributes (only relevant for compute stages with WebGL 2 which uses Transform Feedback). + */ + constructor( code, stage, name, transforms = null, attributes = null ) { + + /** + * The id of the programmable stage. + * + * @type {number} + */ + this.id = _id$8 ++; + + /** + * The shader code. + * + * @type {string} + */ + this.code = code; + + /** + * The type of stage. + * + * @type {string} + */ + this.stage = stage; + + /** + * The name of the stage. + * This is used for debugging purposes. + * + * @type {string} + */ + this.name = name; + + /** + * The transforms (only relevant for compute stages with WebGL 2 which uses Transform Feedback). + * + * @type {?Array} + */ + this.transforms = transforms; + + /** + * The attributes (only relevant for compute stages with WebGL 2 which uses Transform Feedback). + * + * @type {?Array} + */ + this.attributes = attributes; + + /** + * How often the programmable stage is currently in use. + * + * @type {number} + * @default 0 + */ + this.usedTimes = 0; + + } + +} + +/** + * This renderer module manages the pipelines of the renderer. + * + * @private + * @augments DataMap + */ +class Pipelines extends DataMap { + + /** + * Constructs a new pipeline management component. + * + * @param {Backend} backend - The renderer's backend. + * @param {Nodes} nodes - Renderer component for managing nodes related logic. + */ + constructor( backend, nodes ) { + + super(); + + /** + * The renderer's backend. + * + * @type {Backend} + */ + this.backend = backend; + + /** + * Renderer component for managing nodes related logic. + * + * @type {Nodes} + */ + this.nodes = nodes; + + /** + * A references to the bindings management component. + * This reference will be set inside the `Bindings` + * constructor. + * + * @type {?Bindings} + * @default null + */ + this.bindings = null; + + /** + * Internal cache for maintaining pipelines. + * The key of the map is a cache key, the value the pipeline. + * + * @type {Map} + */ + this.caches = new Map(); + + /** + * This dictionary maintains for each shader stage type (vertex, + * fragment and compute) the programmable stage objects which + * represent the actual shader code. + * + * @type {Object} + */ + this.programs = { + vertex: new Map(), + fragment: new Map(), + compute: new Map() + }; + + } + + /** + * Returns a compute pipeline for the given compute node. + * + * @param {Node} computeNode - The compute node. + * @param {Array} bindings - The bindings. + * @return {ComputePipeline} The compute pipeline. + */ + getForCompute( computeNode, bindings ) { + + const { backend } = this; + + const data = this.get( computeNode ); + + if ( this._needsComputeUpdate( computeNode ) ) { + + const previousPipeline = data.pipeline; + + if ( previousPipeline ) { + + previousPipeline.usedTimes --; + previousPipeline.computeProgram.usedTimes --; + + } + + // get shader + + const nodeBuilderState = this.nodes.getForCompute( computeNode ); + + // programmable stage + + let stageCompute = this.programs.compute.get( nodeBuilderState.computeShader ); + + if ( stageCompute === undefined ) { + + if ( previousPipeline && previousPipeline.computeProgram.usedTimes === 0 ) this._releaseProgram( previousPipeline.computeProgram ); + + stageCompute = new ProgrammableStage( nodeBuilderState.computeShader, 'compute', computeNode.name, nodeBuilderState.transforms, nodeBuilderState.nodeAttributes ); + this.programs.compute.set( nodeBuilderState.computeShader, stageCompute ); + + backend.createProgram( stageCompute ); + + } + + // determine compute pipeline + + const cacheKey = this._getComputeCacheKey( computeNode, stageCompute ); + + let pipeline = this.caches.get( cacheKey ); + + if ( pipeline === undefined ) { + + if ( previousPipeline && previousPipeline.usedTimes === 0 ) this._releasePipeline( previousPipeline ); + + pipeline = this._getComputePipeline( computeNode, stageCompute, cacheKey, bindings ); + + } + + // keep track of all used times + + pipeline.usedTimes ++; + stageCompute.usedTimes ++; + + // + + data.version = computeNode.version; + data.pipeline = pipeline; + + } + + return data.pipeline; + + } + + /** + * Returns a render pipeline for the given render object. + * + * @param {RenderObject} renderObject - The render object. + * @param {?Array} [promises=null] - An array of compilation promises which is only relevant in context of `Renderer.compileAsync()`. + * @return {RenderPipeline} The render pipeline. + */ + getForRender( renderObject, promises = null ) { + + const { backend } = this; + + const data = this.get( renderObject ); + + if ( this._needsRenderUpdate( renderObject ) ) { + + const previousPipeline = data.pipeline; + + if ( previousPipeline ) { + + previousPipeline.usedTimes --; + previousPipeline.vertexProgram.usedTimes --; + previousPipeline.fragmentProgram.usedTimes --; + + } + + // get shader + + const nodeBuilderState = renderObject.getNodeBuilderState(); + + const name = renderObject.material ? renderObject.material.name : ''; + + // programmable stages + + let stageVertex = this.programs.vertex.get( nodeBuilderState.vertexShader ); + + if ( stageVertex === undefined ) { + + if ( previousPipeline && previousPipeline.vertexProgram.usedTimes === 0 ) this._releaseProgram( previousPipeline.vertexProgram ); + + stageVertex = new ProgrammableStage( nodeBuilderState.vertexShader, 'vertex', name ); + this.programs.vertex.set( nodeBuilderState.vertexShader, stageVertex ); + + backend.createProgram( stageVertex ); + + } + + let stageFragment = this.programs.fragment.get( nodeBuilderState.fragmentShader ); + + if ( stageFragment === undefined ) { + + if ( previousPipeline && previousPipeline.fragmentProgram.usedTimes === 0 ) this._releaseProgram( previousPipeline.fragmentProgram ); + + stageFragment = new ProgrammableStage( nodeBuilderState.fragmentShader, 'fragment', name ); + this.programs.fragment.set( nodeBuilderState.fragmentShader, stageFragment ); + + backend.createProgram( stageFragment ); + + } + + // determine render pipeline + + const cacheKey = this._getRenderCacheKey( renderObject, stageVertex, stageFragment ); + + let pipeline = this.caches.get( cacheKey ); + + if ( pipeline === undefined ) { + + if ( previousPipeline && previousPipeline.usedTimes === 0 ) this._releasePipeline( previousPipeline ); + + pipeline = this._getRenderPipeline( renderObject, stageVertex, stageFragment, cacheKey, promises ); + + } else { + + renderObject.pipeline = pipeline; + + } + + // keep track of all used times + + pipeline.usedTimes ++; + stageVertex.usedTimes ++; + stageFragment.usedTimes ++; + + // + + data.pipeline = pipeline; + + } + + return data.pipeline; + + } + + /** + * Deletes the pipeline for the given render object. + * + * @param {RenderObject} object - The render object. + * @return {?Object} The deleted dictionary. + */ + delete( object ) { + + const pipeline = this.get( object ).pipeline; + + if ( pipeline ) { + + // pipeline + + pipeline.usedTimes --; + + if ( pipeline.usedTimes === 0 ) this._releasePipeline( pipeline ); + + // programs + + if ( pipeline.isComputePipeline ) { + + pipeline.computeProgram.usedTimes --; + + if ( pipeline.computeProgram.usedTimes === 0 ) this._releaseProgram( pipeline.computeProgram ); + + } else { + + pipeline.fragmentProgram.usedTimes --; + pipeline.vertexProgram.usedTimes --; + + if ( pipeline.vertexProgram.usedTimes === 0 ) this._releaseProgram( pipeline.vertexProgram ); + if ( pipeline.fragmentProgram.usedTimes === 0 ) this._releaseProgram( pipeline.fragmentProgram ); + + } + + } + + return super.delete( object ); + + } + + /** + * Frees internal resources. + */ + dispose() { + + super.dispose(); + + this.caches = new Map(); + this.programs = { + vertex: new Map(), + fragment: new Map(), + compute: new Map() + }; + + } + + /** + * Updates the pipeline for the given render object. + * + * @param {RenderObject} renderObject - The render object. + */ + updateForRender( renderObject ) { + + this.getForRender( renderObject ); + + } + + /** + * Returns a compute pipeline for the given parameters. + * + * @private + * @param {Node} computeNode - The compute node. + * @param {ProgrammableStage} stageCompute - The programmable stage representing the compute shader. + * @param {string} cacheKey - The cache key. + * @param {Array} bindings - The bindings. + * @return {ComputePipeline} The compute pipeline. + */ + _getComputePipeline( computeNode, stageCompute, cacheKey, bindings ) { + + // check for existing pipeline + + cacheKey = cacheKey || this._getComputeCacheKey( computeNode, stageCompute ); + + let pipeline = this.caches.get( cacheKey ); + + if ( pipeline === undefined ) { + + pipeline = new ComputePipeline( cacheKey, stageCompute ); + + this.caches.set( cacheKey, pipeline ); + + this.backend.createComputePipeline( pipeline, bindings ); + + } + + return pipeline; + + } + + /** + * Returns a render pipeline for the given parameters. + * + * @private + * @param {RenderObject} renderObject - The render object. + * @param {ProgrammableStage} stageVertex - The programmable stage representing the vertex shader. + * @param {ProgrammableStage} stageFragment - The programmable stage representing the fragment shader. + * @param {string} cacheKey - The cache key. + * @param {?Array} promises - An array of compilation promises which is only relevant in context of `Renderer.compileAsync()`. + * @return {ComputePipeline} The compute pipeline. + */ + _getRenderPipeline( renderObject, stageVertex, stageFragment, cacheKey, promises ) { + + // check for existing pipeline + + cacheKey = cacheKey || this._getRenderCacheKey( renderObject, stageVertex, stageFragment ); + + let pipeline = this.caches.get( cacheKey ); + + if ( pipeline === undefined ) { + + pipeline = new RenderPipeline( cacheKey, stageVertex, stageFragment ); + + this.caches.set( cacheKey, pipeline ); + + renderObject.pipeline = pipeline; + + // The `promises` array is `null` by default and only set to an empty array when + // `Renderer.compileAsync()` is used. The next call actually fills the array with + // pending promises that resolve when the render pipelines are ready for rendering. + + this.backend.createRenderPipeline( renderObject, promises ); + + } + + return pipeline; + + } + + /** + * Computes a cache key representing a compute pipeline. + * + * @private + * @param {Node} computeNode - The compute node. + * @param {ProgrammableStage} stageCompute - The programmable stage representing the compute shader. + * @return {string} The cache key. + */ + _getComputeCacheKey( computeNode, stageCompute ) { + + return computeNode.id + ',' + stageCompute.id; + + } + + /** + * Computes a cache key representing a render pipeline. + * + * @private + * @param {RenderObject} renderObject - The render object. + * @param {ProgrammableStage} stageVertex - The programmable stage representing the vertex shader. + * @param {ProgrammableStage} stageFragment - The programmable stage representing the fragment shader. + * @return {string} The cache key. + */ + _getRenderCacheKey( renderObject, stageVertex, stageFragment ) { + + return stageVertex.id + ',' + stageFragment.id + ',' + this.backend.getRenderCacheKey( renderObject ); + + } + + /** + * Releases the given pipeline. + * + * @private + * @param {Pipeline} pipeline - The pipeline to release. + */ + _releasePipeline( pipeline ) { + + this.caches.delete( pipeline.cacheKey ); + + } + + /** + * Releases the shader program. + * + * @private + * @param {Object} program - The shader program to release. + */ + _releaseProgram( program ) { + + const code = program.code; + const stage = program.stage; + + this.programs[ stage ].delete( code ); + + } + + /** + * Returns `true` if the compute pipeline for the given compute node requires an update. + * + * @private + * @param {Node} computeNode - The compute node. + * @return {boolean} Whether the compute pipeline for the given compute node requires an update or not. + */ + _needsComputeUpdate( computeNode ) { + + const data = this.get( computeNode ); + + return data.pipeline === undefined || data.version !== computeNode.version; + + } + + /** + * Returns `true` if the render pipeline for the given render object requires an update. + * + * @private + * @param {RenderObject} renderObject - The render object. + * @return {boolean} Whether the render object for the given render object requires an update or not. + */ + _needsRenderUpdate( renderObject ) { + + const data = this.get( renderObject ); + + return data.pipeline === undefined || this.backend.needsRenderUpdate( renderObject ); + + } + +} + +/** + * This renderer module manages the bindings of the renderer. + * + * @private + * @augments DataMap + */ +class Bindings extends DataMap { + + /** + * Constructs a new bindings management component. + * + * @param {Backend} backend - The renderer's backend. + * @param {Nodes} nodes - Renderer component for managing nodes related logic. + * @param {Textures} textures - Renderer component for managing textures. + * @param {Attributes} attributes - Renderer component for managing attributes. + * @param {Pipelines} pipelines - Renderer component for managing pipelines. + * @param {Info} info - Renderer component for managing metrics and monitoring data. + */ + constructor( backend, nodes, textures, attributes, pipelines, info ) { + + super(); + + /** + * The renderer's backend. + * + * @type {Backend} + */ + this.backend = backend; + + /** + * Renderer component for managing textures. + * + * @type {Textures} + */ + this.textures = textures; + + /** + * Renderer component for managing pipelines. + * + * @type {Pipelines} + */ + this.pipelines = pipelines; + + /** + * Renderer component for managing attributes. + * + * @type {Attributes} + */ + this.attributes = attributes; + + /** + * Renderer component for managing nodes related logic. + * + * @type {Nodes} + */ + this.nodes = nodes; + + /** + * Renderer component for managing metrics and monitoring data. + * + * @type {Info} + */ + this.info = info; + + this.pipelines.bindings = this; // assign bindings to pipelines + + } + + /** + * Returns the bind groups for the given render object. + * + * @param {RenderObject} renderObject - The render object. + * @return {Array} The bind groups. + */ + getForRender( renderObject ) { + + const bindings = renderObject.getBindings(); + + for ( const bindGroup of bindings ) { + + const groupData = this.get( bindGroup ); + + if ( groupData.bindGroup === undefined ) { + + // each object defines an array of bindings (ubos, textures, samplers etc.) + + this._init( bindGroup ); + + this.backend.createBindings( bindGroup, bindings, 0 ); + + groupData.bindGroup = bindGroup; + + } + + } + + return bindings; + + } + + /** + * Returns the bind groups for the given compute node. + * + * @param {Node} computeNode - The compute node. + * @return {Array} The bind groups. + */ + getForCompute( computeNode ) { + + const bindings = this.nodes.getForCompute( computeNode ).bindings; + + for ( const bindGroup of bindings ) { + + const groupData = this.get( bindGroup ); + + if ( groupData.bindGroup === undefined ) { + + this._init( bindGroup ); + + this.backend.createBindings( bindGroup, bindings, 0 ); + + groupData.bindGroup = bindGroup; + + } + + } + + return bindings; + + } + + /** + * Updates the bindings for the given compute node. + * + * @param {Node} computeNode - The compute node. + */ + updateForCompute( computeNode ) { + + this._updateBindings( this.getForCompute( computeNode ) ); + + } + + /** + * Updates the bindings for the given render object. + * + * @param {RenderObject} renderObject - The render object. + */ + updateForRender( renderObject ) { + + this._updateBindings( this.getForRender( renderObject ) ); + + } + + /** + * Updates the given array of bindings. + * + * @param {Array} bindings - The bind groups. + */ + _updateBindings( bindings ) { + + for ( const bindGroup of bindings ) { + + this._update( bindGroup, bindings ); + + } + + } + + /** + * Initializes the given bind group. + * + * @param {BindGroup} bindGroup - The bind group to initialize. + */ + _init( bindGroup ) { + + for ( const binding of bindGroup.bindings ) { + + if ( binding.isSampledTexture ) { + + this.textures.updateTexture( binding.texture ); + + } else if ( binding.isStorageBuffer ) { + + const attribute = binding.attribute; + const attributeType = attribute.isIndirectStorageBufferAttribute ? AttributeType.INDIRECT : AttributeType.STORAGE; + + this.attributes.update( attribute, attributeType ); + + } + + } + + } + + /** + * Updates the given bind group. + * + * @param {BindGroup} bindGroup - The bind group to update. + * @param {Array} bindings - The bind groups. + */ + _update( bindGroup, bindings ) { + + const { backend } = this; + + let needsBindingsUpdate = false; + let cacheBindings = true; + let cacheIndex = 0; + let version = 0; + + // iterate over all bindings and check if buffer updates or a new binding group is required + + for ( const binding of bindGroup.bindings ) { + + if ( binding.isNodeUniformsGroup ) { + + const updated = this.nodes.updateGroup( binding ); + + // every uniforms group is a uniform buffer. So if no update is required, + // we move one with the next binding. Otherwise the next if block will update the group. + + if ( updated === false ) continue; + + } + + if ( binding.isStorageBuffer ) { + + const attribute = binding.attribute; + const attributeType = attribute.isIndirectStorageBufferAttribute ? AttributeType.INDIRECT : AttributeType.STORAGE; + + this.attributes.update( attribute, attributeType ); + + + } + + if ( binding.isUniformBuffer ) { + + const updated = binding.update(); + + if ( updated ) { + + backend.updateBinding( binding ); + + } + + } else if ( binding.isSampler ) { + + binding.update(); + + } else if ( binding.isSampledTexture ) { + + const texturesTextureData = this.textures.get( binding.texture ); + + if ( binding.needsBindingsUpdate( texturesTextureData.generation ) ) needsBindingsUpdate = true; + + const updated = binding.update(); + + const texture = binding.texture; + + if ( updated ) { + + this.textures.updateTexture( texture ); + + } + + const textureData = backend.get( texture ); + + if ( textureData.externalTexture !== undefined || texturesTextureData.isDefaultTexture ) { + + cacheBindings = false; + + } else { + + cacheIndex = cacheIndex * 10 + texture.id; + version += texture.version; + + } + + if ( backend.isWebGPUBackend === true && textureData.texture === undefined && textureData.externalTexture === undefined ) { + + // TODO: Remove this once we found why updated === false isn't bound to a texture in the WebGPU backend + console.error( 'Bindings._update: binding should be available:', binding, updated, texture, binding.textureNode.value, needsBindingsUpdate ); + + this.textures.updateTexture( texture ); + needsBindingsUpdate = true; + + } + + if ( texture.isStorageTexture === true ) { + + const textureData = this.get( texture ); + + if ( binding.store === true ) { + + textureData.needsMipmap = true; + + } else if ( this.textures.needsMipmaps( texture ) && textureData.needsMipmap === true ) { + + this.backend.generateMipmaps( texture ); + + textureData.needsMipmap = false; + + } + + } + + } + + } + + if ( needsBindingsUpdate === true ) { + + this.backend.updateBindings( bindGroup, bindings, cacheBindings ? cacheIndex : 0, version ); + + } + + } + +} + +/** + * Default sorting function for opaque render items. + * + * @private + * @function + * @param {Object} a - The first render item. + * @param {Object} b - The second render item. + * @return {number} A numeric value which defines the sort order. + */ +function painterSortStable( a, b ) { + + if ( a.groupOrder !== b.groupOrder ) { + + return a.groupOrder - b.groupOrder; + + } else if ( a.renderOrder !== b.renderOrder ) { + + return a.renderOrder - b.renderOrder; + + } else if ( a.z !== b.z ) { + + return a.z - b.z; + + } else { + + return a.id - b.id; + + } + +} + +/** + * Default sorting function for transparent render items. + * + * @private + * @function + * @param {Object} a - The first render item. + * @param {Object} b - The second render item. + * @return {number} A numeric value which defines the sort order. + */ +function reversePainterSortStable( a, b ) { + + if ( a.groupOrder !== b.groupOrder ) { + + return a.groupOrder - b.groupOrder; + + } else if ( a.renderOrder !== b.renderOrder ) { + + return a.renderOrder - b.renderOrder; + + } else if ( a.z !== b.z ) { + + return b.z - a.z; + + } else { + + return a.id - b.id; + + } + +} + +/** + * Returns `true` if the given transparent material requires a double pass. + * + * @private + * @function + * @param {Material} material - The transparent material. + * @return {boolean} Whether the given material requires a double pass or not. + */ +function needsDoublePass( material ) { + + const hasTransmission = material.transmission > 0 || material.transmissionNode; + + return hasTransmission && material.side === DoubleSide && material.forceSinglePass === false; + +} + +/** + * When the renderer analyzes the scene at the beginning of a render call, + * it stores 3D object for further processing in render lists. Depending on the + * properties of a 3D objects (like their transformation or material state), the + * objects are maintained in ordered lists for the actual rendering. + * + * Render lists are unique per scene and camera combination. + * + * @private + * @augments Pipeline + */ +class RenderList { + + /** + * Constructs a render list. + * + * @param {Lighting} lighting - The lighting management component. + * @param {Scene} scene - The scene. + * @param {Camera} camera - The camera the scene is rendered with. + */ + constructor( lighting, scene, camera ) { + + /** + * 3D objects are transformed into render items and stored in this array. + * + * @type {Array} + */ + this.renderItems = []; + + /** + * The current render items index. + * + * @type {number} + * @default 0 + */ + this.renderItemsIndex = 0; + + /** + * A list with opaque render items. + * + * @type {Array} + */ + this.opaque = []; + + /** + * A list with transparent render items which require + * double pass rendering (e.g. transmissive objects). + * + * @type {Array} + */ + this.transparentDoublePass = []; + + /** + * A list with transparent render items. + * + * @type {Array} + */ + this.transparent = []; + + /** + * A list with transparent render bundle data. + * + * @type {Array} + */ + this.bundles = []; + + /** + * The render list's lights node. This node is later + * relevant for the actual analytical light nodes which + * compute the scene's lighting in the shader. + * + * @type {LightsNode} + */ + this.lightsNode = lighting.getNode( scene, camera ); + + /** + * The scene's lights stored in an array. This array + * is used to setup the lights node. + * + * @type {Array} + */ + this.lightsArray = []; + + /** + * The scene. + * + * @type {Scene} + */ + this.scene = scene; + + /** + * The camera the scene is rendered with. + * + * @type {Camera} + */ + this.camera = camera; + + /** + * How many objects perform occlusion query tests. + * + * @type {number} + * @default 0 + */ + this.occlusionQueryCount = 0; + + } + + /** + * This method is called right at the beginning of a render call + * before the scene is analyzed. It prepares the internal data + * structures for the upcoming render lists generation. + * + * @return {RenderList} A reference to this render list. + */ + begin() { + + this.renderItemsIndex = 0; + + this.opaque.length = 0; + this.transparentDoublePass.length = 0; + this.transparent.length = 0; + this.bundles.length = 0; + + this.lightsArray.length = 0; + + this.occlusionQueryCount = 0; + + return this; + + } + + /** + * Returns a render item for the giving render item state. The state is defined + * by a series of object-related parameters. + * + * The method avoids object creation by holding render items and reusing them in + * subsequent render calls (just with different property values). + * + * @param {Object3D} object - The 3D object. + * @param {BufferGeometry} geometry - The 3D object's geometry. + * @param {Material} material - The 3D object's material. + * @param {number} groupOrder - The current group order. + * @param {number} z - Th 3D object's depth value (z value in clip space). + * @param {?number} group - {?Object} group - Only relevant for objects using multiple materials. This represents a group entry from the respective `BufferGeometry`. + * @param {ClippingContext} clippingContext - The current clipping context. + * @return {Object} The render item. + */ + getNextRenderItem( object, geometry, material, groupOrder, z, group, clippingContext ) { + + let renderItem = this.renderItems[ this.renderItemsIndex ]; + + if ( renderItem === undefined ) { + + renderItem = { + id: object.id, + object: object, + geometry: geometry, + material: material, + groupOrder: groupOrder, + renderOrder: object.renderOrder, + z: z, + group: group, + clippingContext: clippingContext + }; + + this.renderItems[ this.renderItemsIndex ] = renderItem; + + } else { + + renderItem.id = object.id; + renderItem.object = object; + renderItem.geometry = geometry; + renderItem.material = material; + renderItem.groupOrder = groupOrder; + renderItem.renderOrder = object.renderOrder; + renderItem.z = z; + renderItem.group = group; + renderItem.clippingContext = clippingContext; + + } + + this.renderItemsIndex ++; + + return renderItem; + + } + + /** + * Pushes the given object as a render item to the internal render lists. + * The selected lists depend on the object properties. + * + * @param {Object3D} object - The 3D object. + * @param {BufferGeometry} geometry - The 3D object's geometry. + * @param {Material} material - The 3D object's material. + * @param {number} groupOrder - The current group order. + * @param {number} z - Th 3D object's depth value (z value in clip space). + * @param {?number} group - {?Object} group - Only relevant for objects using multiple materials. This represents a group entry from the respective `BufferGeometry`. + * @param {ClippingContext} clippingContext - The current clipping context. + */ + push( object, geometry, material, groupOrder, z, group, clippingContext ) { + + const renderItem = this.getNextRenderItem( object, geometry, material, groupOrder, z, group, clippingContext ); + + if ( object.occlusionTest === true ) this.occlusionQueryCount ++; + + if ( material.transparent === true || material.transmission > 0 ) { + + if ( needsDoublePass( material ) ) this.transparentDoublePass.push( renderItem ); + + this.transparent.push( renderItem ); + + } else { + + this.opaque.push( renderItem ); + + } + + } + + /** + * Inserts the given object as a render item at the start of the internal render lists. + * The selected lists depend on the object properties. + * + * @param {Object3D} object - The 3D object. + * @param {BufferGeometry} geometry - The 3D object's geometry. + * @param {Material} material - The 3D object's material. + * @param {number} groupOrder - The current group order. + * @param {number} z - Th 3D object's depth value (z value in clip space). + * @param {?number} group - {?Object} group - Only relevant for objects using multiple materials. This represents a group entry from the respective `BufferGeometry`. + * @param {ClippingContext} clippingContext - The current clipping context. + */ + unshift( object, geometry, material, groupOrder, z, group, clippingContext ) { + + const renderItem = this.getNextRenderItem( object, geometry, material, groupOrder, z, group, clippingContext ); + + if ( material.transparent === true || material.transmission > 0 ) { + + if ( needsDoublePass( material ) ) this.transparentDoublePass.unshift( renderItem ); + + this.transparent.unshift( renderItem ); + + } else { + + this.opaque.unshift( renderItem ); + + } + + } + + /** + * Pushes render bundle group data into the render list. + * + * @param {Object} group - Bundle group data. + */ + pushBundle( group ) { + + this.bundles.push( group ); + + } + + /** + * Pushes a light into the render list. + * + * @param {Light} light - The light. + */ + pushLight( light ) { + + this.lightsArray.push( light ); + + } + + /** + * Sorts the internal render lists. + * + * @param {?function(any, any): number} customOpaqueSort - A custom sort function for opaque objects. + * @param {?function(any, any): number} customTransparentSort - A custom sort function for transparent objects. + */ + sort( customOpaqueSort, customTransparentSort ) { + + if ( this.opaque.length > 1 ) this.opaque.sort( customOpaqueSort || painterSortStable ); + if ( this.transparentDoublePass.length > 1 ) this.transparentDoublePass.sort( customTransparentSort || reversePainterSortStable ); + if ( this.transparent.length > 1 ) this.transparent.sort( customTransparentSort || reversePainterSortStable ); + + } + + /** + * This method performs finalizing tasks right after the render lists + * have been generated. + */ + finish() { + + // update lights + + this.lightsNode.setLights( this.lightsArray ); + + // Clear references from inactive renderItems in the list + + for ( let i = this.renderItemsIndex, il = this.renderItems.length; i < il; i ++ ) { + + const renderItem = this.renderItems[ i ]; + + if ( renderItem.id === null ) break; + + renderItem.id = null; + renderItem.object = null; + renderItem.geometry = null; + renderItem.material = null; + renderItem.groupOrder = null; + renderItem.renderOrder = null; + renderItem.z = null; + renderItem.group = null; + renderItem.clippingContext = null; + + } + + } + +} + +const _chainKeys$4 = []; + +/** + * This renderer module manages the render lists which are unique + * per scene and camera combination. + * + * @private + */ +class RenderLists { + + /** + * Constructs a render lists management component. + * + * @param {Lighting} lighting - The lighting management component. + */ + constructor( lighting ) { + + /** + * The lighting management component. + * + * @type {Lighting} + */ + this.lighting = lighting; + + /** + * The internal chain map which holds the render lists. + * + * @type {ChainMap} + */ + this.lists = new ChainMap(); + + } + + /** + * Returns a render list for the given scene and camera. + * + * @param {Scene} scene - The scene. + * @param {Camera} camera - The camera. + * @return {RenderList} The render list. + */ + get( scene, camera ) { + + const lists = this.lists; + + _chainKeys$4[ 0 ] = scene; + _chainKeys$4[ 1 ] = camera; + + let list = lists.get( _chainKeys$4 ); + + if ( list === undefined ) { + + list = new RenderList( this.lighting, scene, camera ); + lists.set( _chainKeys$4, list ); + + } + + _chainKeys$4.length = 0; + + return list; + + } + + /** + * Frees all internal resources. + */ + dispose() { + + this.lists = new ChainMap(); + + } + +} + +let _id$7 = 0; + +/** + * Any render or compute command is executed in a specific context that defines + * the state of the renderer and its backend. Typical examples for such context + * data are the current clear values or data from the active framebuffer. This + * module is used to represent these contexts as objects. + * + * @private + */ +class RenderContext { + + /** + * Constructs a new render context. + */ + constructor() { + + /** + * The context's ID. + * + * @type {number} + */ + this.id = _id$7 ++; + + /** + * Whether the current active framebuffer has a color attachment. + * + * @type {boolean} + * @default true + */ + this.color = true; + + /** + * Whether the color attachment should be cleared or not. + * + * @type {boolean} + * @default true + */ + this.clearColor = true; + + /** + * The clear color value. + * + * @type {Object} + * @default true + */ + this.clearColorValue = { r: 0, g: 0, b: 0, a: 1 }; + + /** + * Whether the current active framebuffer has a depth attachment. + * + * @type {boolean} + * @default true + */ + this.depth = true; + + /** + * Whether the depth attachment should be cleared or not. + * + * @type {boolean} + * @default true + */ + this.clearDepth = true; + + /** + * The clear depth value. + * + * @type {number} + * @default 1 + */ + this.clearDepthValue = 1; + + /** + * Whether the current active framebuffer has a stencil attachment. + * + * @type {boolean} + * @default false + */ + this.stencil = false; + + /** + * Whether the stencil attachment should be cleared or not. + * + * @type {boolean} + * @default true + */ + this.clearStencil = true; + + /** + * The clear stencil value. + * + * @type {number} + * @default 1 + */ + this.clearStencilValue = 1; + + /** + * By default the viewport encloses the entire framebuffer If a smaller + * viewport is manually defined, this property is to `true` by the renderer. + * + * @type {boolean} + * @default false + */ + this.viewport = false; + + /** + * The viewport value. This value is in physical pixels meaning it incorporates + * the renderer's pixel ratio. The viewport property of render targets or + * the renderer is in logical pixels. + * + * @type {Vector4} + */ + this.viewportValue = new Vector4(); + + /** + * When the scissor test is active and scissor rectangle smaller than the + * framebuffers dimensions, this property is to `true` by the renderer. + * + * @type {boolean} + * @default false + */ + this.scissor = false; + + /** + * The scissor rectangle. + * + * @type {Vector4} + */ + this.scissorValue = new Vector4(); + + /** + * The active render target. + * + * @type {?RenderTarget} + * @default null + */ + this.renderTarget = null; + + /** + * The textures of the active render target. + * `null` when no render target is set. + * + * @type {?Array} + * @default null + */ + this.textures = null; + + /** + * The depth texture of the active render target. + * `null` when no render target is set. + * + * @type {?DepthTexture} + * @default null + */ + this.depthTexture = null; + + /** + * The active cube face. + * + * @type {number} + * @default 0 + */ + this.activeCubeFace = 0; + + /** + * The active mipmap level. + * + * @type {number} + * @default 0 + */ + this.activeMipmapLevel = 0; + + /** + * The number of MSAA samples. This value is always `1` when + * MSAA isn't used. + * + * @type {number} + * @default 1 + */ + this.sampleCount = 1; + + /** + * The active render target's width in physical pixels. + * + * @type {number} + * @default 0 + */ + this.width = 0; + + /** + * The active render target's height in physical pixels. + * + * @type {number} + * @default 0 + */ + this.height = 0; + + /** + * The occlusion query count. + * + * @type {number} + * @default 0 + */ + this.occlusionQueryCount = 0; + + /** + * The current clipping context. + * + * @type {?ClippingContext} + * @default null + */ + this.clippingContext = null; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isRenderContext = true; + + } + + /** + * Returns the cache key of this render context. + * + * @return {number} The cache key. + */ + getCacheKey() { + + return getCacheKey( this ); + + } + +} + +/** + * Computes a cache key for the given render context. This key + * should identify the render target state so it is possible to + * configure the correct attachments in the respective backend. + * + * @param {RenderContext} renderContext - The render context. + * @return {number} The cache key. + */ +function getCacheKey( renderContext ) { + + const { textures, activeCubeFace } = renderContext; + + const values = [ activeCubeFace ]; + + for ( const texture of textures ) { + + values.push( texture.id ); + + } + + return hashArray( values ); + +} + +const _chainKeys$3 = []; +const _defaultScene = /*@__PURE__*/ new Scene(); +const _defaultCamera = /*@__PURE__*/ new Camera(); + +/** + * This module manages the render contexts of the renderer. + * + * @private + */ +class RenderContexts { + + /** + * Constructs a new render context management component. + */ + constructor() { + + /** + * A dictionary that manages render contexts in chain maps + * for each attachment state. + * + * @type {Object} + */ + this.chainMaps = {}; + + } + + /** + * Returns a render context for the given scene, camera and render target. + * + * @param {Scene} scene - The scene. + * @param {Camera} camera - The camera that is used to render the scene. + * @param {?RenderTarget} [renderTarget=null] - The active render target. + * @return {RenderContext} The render context. + */ + get( scene, camera, renderTarget = null ) { + + _chainKeys$3[ 0 ] = scene; + _chainKeys$3[ 1 ] = camera; + + let attachmentState; + + if ( renderTarget === null ) { + + attachmentState = 'default'; + + } else { + + const format = renderTarget.texture.format; + const count = renderTarget.textures.length; + + attachmentState = `${ count }:${ format }:${ renderTarget.samples }:${ renderTarget.depthBuffer }:${ renderTarget.stencilBuffer }`; + + } + + const chainMap = this._getChainMap( attachmentState ); + + let renderState = chainMap.get( _chainKeys$3 ); + + if ( renderState === undefined ) { + + renderState = new RenderContext(); + + chainMap.set( _chainKeys$3, renderState ); + + } + + _chainKeys$3.length = 0; + + if ( renderTarget !== null ) renderState.sampleCount = renderTarget.samples === 0 ? 1 : renderTarget.samples; + + return renderState; + + } + + /** + * Returns a render context intended for clear operations. + * + * @param {?RenderTarget} [renderTarget=null] - The active render target. + * @return {RenderContext} The render context. + */ + getForClear( renderTarget = null ) { + + return this.get( _defaultScene, _defaultCamera, renderTarget ); + + } + + /** + * Returns a chain map for the given attachment state. + * + * @private + * @param {string} attachmentState - The attachment state. + * @return {ChainMap} The chain map. + */ + _getChainMap( attachmentState ) { + + return this.chainMaps[ attachmentState ] || ( this.chainMaps[ attachmentState ] = new ChainMap() ); + + } + + /** + * Frees internal resources. + */ + dispose() { + + this.chainMaps = {}; + + } + +} + +const _size$3 = /*@__PURE__*/ new Vector3(); + +/** + * This module manages the textures of the renderer. + * + * @private + * @augments DataMap + */ +class Textures extends DataMap { + + /** + * Constructs a new texture management component. + * + * @param {Renderer} renderer - The renderer. + * @param {Backend} backend - The renderer's backend. + * @param {Info} info - Renderer component for managing metrics and monitoring data. + */ + constructor( renderer, backend, info ) { + + super(); + + /** + * The renderer. + * + * @type {Renderer} + */ + this.renderer = renderer; + + /** + * The backend. + * + * @type {Backend} + */ + this.backend = backend; + + /** + * Renderer component for managing metrics and monitoring data. + * + * @type {Info} + */ + this.info = info; + + } + + /** + * Updates the given render target. Based on the given render target configuration, + * it updates the texture states representing the attachments of the framebuffer. + * + * @param {RenderTarget} renderTarget - The render target to update. + * @param {number} [activeMipmapLevel=0] - The active mipmap level. + */ + updateRenderTarget( renderTarget, activeMipmapLevel = 0 ) { + + const renderTargetData = this.get( renderTarget ); + + const sampleCount = renderTarget.samples === 0 ? 1 : renderTarget.samples; + const depthTextureMips = renderTargetData.depthTextureMips || ( renderTargetData.depthTextureMips = {} ); + + const textures = renderTarget.textures; + + const size = this.getSize( textures[ 0 ] ); + + const mipWidth = size.width >> activeMipmapLevel; + const mipHeight = size.height >> activeMipmapLevel; + + let depthTexture = renderTarget.depthTexture || depthTextureMips[ activeMipmapLevel ]; + const useDepthTexture = renderTarget.depthBuffer === true || renderTarget.stencilBuffer === true; + + let textureNeedsUpdate = false; + + if ( depthTexture === undefined && useDepthTexture ) { + + depthTexture = new DepthTexture(); + + depthTexture.format = renderTarget.stencilBuffer ? DepthStencilFormat : DepthFormat; + depthTexture.type = renderTarget.stencilBuffer ? UnsignedInt248Type : UnsignedIntType; // FloatType + depthTexture.image.width = mipWidth; + depthTexture.image.height = mipHeight; + depthTexture.image.depth = size.depth; + depthTexture.isArrayTexture = renderTarget.multiview === true && size.depth > 1; + + depthTextureMips[ activeMipmapLevel ] = depthTexture; + + } + + if ( renderTargetData.width !== size.width || size.height !== renderTargetData.height ) { + + textureNeedsUpdate = true; + + if ( depthTexture ) { + + depthTexture.needsUpdate = true; + depthTexture.image.width = mipWidth; + depthTexture.image.height = mipHeight; + depthTexture.image.depth = depthTexture.isArrayTexture ? depthTexture.image.depth : 1; + + } + + } + + renderTargetData.width = size.width; + renderTargetData.height = size.height; + renderTargetData.textures = textures; + renderTargetData.depthTexture = depthTexture || null; + renderTargetData.depth = renderTarget.depthBuffer; + renderTargetData.stencil = renderTarget.stencilBuffer; + renderTargetData.renderTarget = renderTarget; + + if ( renderTargetData.sampleCount !== sampleCount ) { + + textureNeedsUpdate = true; + + if ( depthTexture ) { + + depthTexture.needsUpdate = true; + + } + + renderTargetData.sampleCount = sampleCount; + + } + + // + + + const options = { sampleCount }; + + // XR render targets require no texture updates + + if ( renderTarget.isXRRenderTarget !== true ) { + + for ( let i = 0; i < textures.length; i ++ ) { + + const texture = textures[ i ]; + + if ( textureNeedsUpdate ) texture.needsUpdate = true; + + this.updateTexture( texture, options ); + + } + + if ( depthTexture ) { + + this.updateTexture( depthTexture, options ); + + } + + } + + // dispose handler + + if ( renderTargetData.initialized !== true ) { + + renderTargetData.initialized = true; + + // dispose + + const onDispose = () => { + + renderTarget.removeEventListener( 'dispose', onDispose ); + + for ( let i = 0; i < textures.length; i ++ ) { + + this._destroyTexture( textures[ i ] ); + + } + + if ( depthTexture ) { + + this._destroyTexture( depthTexture ); + + } + + this.delete( renderTarget ); + + }; + + renderTarget.addEventListener( 'dispose', onDispose ); + + } + + } + + /** + * Updates the given texture. Depending on the texture state, this method + * triggers the upload of texture data to the GPU memory. If the texture data are + * not yet ready for the upload, it uses default texture data for as a placeholder. + * + * @param {Texture} texture - The texture to update. + * @param {Object} [options={}] - The options. + */ + updateTexture( texture, options = {} ) { + + const textureData = this.get( texture ); + if ( textureData.initialized === true && textureData.version === texture.version ) return; + + const isRenderTarget = texture.isRenderTargetTexture || texture.isDepthTexture || texture.isFramebufferTexture; + const backend = this.backend; + + if ( isRenderTarget && textureData.initialized === true ) { + + // it's an update + + backend.destroySampler( texture ); + backend.destroyTexture( texture ); + + } + + // + + if ( texture.isFramebufferTexture ) { + + const renderTarget = this.renderer.getRenderTarget(); + + if ( renderTarget ) { + + texture.type = renderTarget.texture.type; + + } else { + + texture.type = UnsignedByteType; + + } + + } + + // + + const { width, height, depth } = this.getSize( texture ); + + options.width = width; + options.height = height; + options.depth = depth; + options.needsMipmaps = this.needsMipmaps( texture ); + options.levels = options.needsMipmaps ? this.getMipLevels( texture, width, height ) : 1; + + // + + if ( isRenderTarget || texture.isStorageTexture === true ) { + + backend.createSampler( texture ); + backend.createTexture( texture, options ); + + textureData.generation = texture.version; + + } else { + + const needsCreate = textureData.initialized !== true; + + if ( needsCreate ) backend.createSampler( texture ); + + if ( texture.version > 0 ) { + + const image = texture.image; + + if ( image === undefined ) { + + console.warn( 'THREE.Renderer: Texture marked for update but image is undefined.' ); + + } else if ( image.complete === false ) { + + console.warn( 'THREE.Renderer: Texture marked for update but image is incomplete.' ); + + } else { + + if ( texture.images ) { + + const images = []; + + for ( const image of texture.images ) { + + images.push( image ); + + } + + options.images = images; + + } else { + + options.image = image; + + } + + if ( textureData.isDefaultTexture === undefined || textureData.isDefaultTexture === true ) { + + backend.createTexture( texture, options ); + + textureData.isDefaultTexture = false; + textureData.generation = texture.version; + + } + + if ( texture.source.dataReady === true ) backend.updateTexture( texture, options ); + + if ( options.needsMipmaps && texture.mipmaps.length === 0 ) backend.generateMipmaps( texture ); + + } + + } else { + + // async update + + backend.createDefaultTexture( texture ); + + textureData.isDefaultTexture = true; + textureData.generation = texture.version; + + } + + } + + // dispose handler + + if ( textureData.initialized !== true ) { + + textureData.initialized = true; + textureData.generation = texture.version; + + // + + this.info.memory.textures ++; + + // dispose + + const onDispose = () => { + + texture.removeEventListener( 'dispose', onDispose ); + + this._destroyTexture( texture ); + + }; + + texture.addEventListener( 'dispose', onDispose ); + + } + + // + + textureData.version = texture.version; + + } + + /** + * Computes the size of the given texture and writes the result + * into the target vector. This vector is also returned by the + * method. + * + * If no texture data are available for the compute yet, the method + * returns default size values. + * + * @param {Texture} texture - The texture to compute the size for. + * @param {Vector3} target - The target vector. + * @return {Vector3} The target vector. + */ + getSize( texture, target = _size$3 ) { + + let image = texture.images ? texture.images[ 0 ] : texture.image; + + if ( image ) { + + if ( image.image !== undefined ) image = image.image; + + target.width = image.width || 1; + target.height = image.height || 1; + target.depth = texture.isCubeTexture ? 6 : ( image.depth || 1 ); + + } else { + + target.width = target.height = target.depth = 1; + + } + + return target; + + } + + /** + * Computes the number of mipmap levels for the given texture. + * + * @param {Texture} texture - The texture. + * @param {number} width - The texture's width. + * @param {number} height - The texture's height. + * @return {number} The number of mipmap levels. + */ + getMipLevels( texture, width, height ) { + + let mipLevelCount; + + if ( texture.isCompressedTexture ) { + + if ( texture.mipmaps ) { + + mipLevelCount = texture.mipmaps.length; + + } else { + + mipLevelCount = 1; + + } + + } else { + + mipLevelCount = Math.floor( Math.log2( Math.max( width, height ) ) ) + 1; + + } + + return mipLevelCount; + + } + + /** + * Returns `true` if the given texture requires mipmaps. + * + * @param {Texture} texture - The texture. + * @return {boolean} Whether mipmaps are required or not. + */ + needsMipmaps( texture ) { + + return texture.isCompressedTexture === true || texture.generateMipmaps; + + } + + /** + * Frees internal resource when the given texture isn't + * required anymore. + * + * @param {Texture} texture - The texture to destroy. + */ + _destroyTexture( texture ) { + + if ( this.has( texture ) === true ) { + + this.backend.destroySampler( texture ); + this.backend.destroyTexture( texture ); + + this.delete( texture ); + + this.info.memory.textures --; + + } + + } + +} + +/** + * A four-component version of {@link Color} which is internally + * used by the renderer to represents clear color with alpha as + * one object. + * + * @private + * @augments Color + */ +class Color4 extends Color { + + /** + * Constructs a new four-component color. + * You can also pass a single THREE.Color, hex or + * string argument to this constructor. + * + * @param {number|string} [r=1] - The red value. + * @param {number} [g=1] - The green value. + * @param {number} [b=1] - The blue value. + * @param {number} [a=1] - The alpha value. + */ + constructor( r, g, b, a = 1 ) { + + super( r, g, b ); + + this.a = a; + + } + + /** + * Overwrites the default to honor alpha. + * You can also pass a single THREE.Color, hex or + * string argument to this method. + * + * @param {number|string|Color} r - The red value. + * @param {number} g - The green value. + * @param {number} b - The blue value. + * @param {number} [a=1] - The alpha value. + * @return {Color4} A reference to this object. + */ + set( r, g, b, a = 1 ) { + + this.a = a; + + return super.set( r, g, b ); + + } + + /** + * Overwrites the default to honor alpha. + * + * @param {Color4} color - The color to copy. + * @return {Color4} A reference to this object. + */ + copy( color ) { + + if ( color.a !== undefined ) this.a = color.a; + + return super.copy( color ); + + } + + /** + * Overwrites the default to honor alpha. + * + * @return {Color4} The cloned color. + */ + clone() { + + return new this.constructor( this.r, this.g, this.b, this.a ); + + } + +} + +/** + * Special version of {@link PropertyNode} which is used for parameters. + * + * @augments PropertyNode + */ +class ParameterNode extends PropertyNode { + + static get type() { + + return 'ParameterNode'; + + } + + /** + * Constructs a new parameter node. + * + * @param {string} nodeType - The type of the node. + * @param {?string} [name=null] - The name of the parameter in the shader. + */ + constructor( nodeType, name = null ) { + + super( nodeType, name ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isParameterNode = true; + + } + + getHash() { + + return this.uuid; + + } + + generate() { + + return this.name; + + } + +} + +/** + * TSL function for creating a parameter node. + * + * @tsl + * @function + * @param {string} type - The type of the node. + * @param {?string} name - The name of the parameter in the shader. + * @returns {ParameterNode} + */ +const parameter = ( type, name ) => nodeObject( new ParameterNode( type, name ) ); + +/** + * Stack is a helper for Nodes that need to produce stack-based code instead of continuous flow. + * They are usually needed in cases like `If`, `Else`. + * + * @augments Node + */ +class StackNode extends Node { + + static get type() { + + return 'StackNode'; + + } + + /** + * Constructs a new stack node. + * + * @param {?StackNode} [parent=null] - The parent stack node. + */ + constructor( parent = null ) { + + super(); + + /** + * List of nodes. + * + * @type {Array} + */ + this.nodes = []; + + /** + * The output node. + * + * @type {?Node} + * @default null + */ + this.outputNode = null; + + /** + * The parent stack node. + * + * @type {?StackNode} + * @default null + */ + this.parent = parent; + + /** + * The current conditional node. + * + * @private + * @type {ConditionalNode} + * @default null + */ + this._currentCond = null; + + /** + * The expression node. Only + * relevant for Switch/Case. + * + * @private + * @type {Node} + * @default null + */ + this._expressionNode = null; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isStackNode = true; + + } + + getNodeType( builder ) { + + return this.outputNode ? this.outputNode.getNodeType( builder ) : 'void'; + + } + + getMemberType( builder, name ) { + + return this.outputNode ? this.outputNode.getMemberType( builder, name ) : 'void'; + + } + + /** + * Adds a node to this stack. + * + * @param {Node} node - The node to add. + * @return {StackNode} A reference to this stack node. + */ + add( node ) { + + this.nodes.push( node ); + + return this; + + } + + /** + * Represent an `if` statement in TSL. + * + * @param {Node} boolNode - Represents the condition. + * @param {Function} method - TSL code which is executed if the condition evaluates to `true`. + * @return {StackNode} A reference to this stack node. + */ + If( boolNode, method ) { + + const methodNode = new ShaderNode( method ); + this._currentCond = select( boolNode, methodNode ); + + return this.add( this._currentCond ); + + } + + /** + * Represent an `elseif` statement in TSL. + * + * @param {Node} boolNode - Represents the condition. + * @param {Function} method - TSL code which is executed if the condition evaluates to `true`. + * @return {StackNode} A reference to this stack node. + */ + ElseIf( boolNode, method ) { + + const methodNode = new ShaderNode( method ); + const ifNode = select( boolNode, methodNode ); + + this._currentCond.elseNode = ifNode; + this._currentCond = ifNode; + + return this; + + } + + /** + * Represent an `else` statement in TSL. + * + * @param {Function} method - TSL code which is executed in the `else` case. + * @return {StackNode} A reference to this stack node. + */ + Else( method ) { + + this._currentCond.elseNode = new ShaderNode( method ); + + return this; + + } + + /** + * Represents a `switch` statement in TSL. + * + * @param {any} expression - Represents the expression. + * @param {Function} method - TSL code which is executed if the condition evaluates to `true`. + * @return {StackNode} A reference to this stack node. + */ + Switch( expression ) { + + this._expressionNode = nodeObject( expression ); + + return this; + + } + + /** + * Represents a `case` statement in TSL. The TSL version accepts an arbitrary numbers of values. + * The last parameter must be the callback method that should be executed in the `true` case. + * + * @param {...any} params - The values of the `Case()` statement as well as the callback method. + * @return {StackNode} A reference to this stack node. + */ + Case( ...params ) { + + const caseNodes = []; + + // extract case nodes from the parameter list + + if ( params.length >= 2 ) { + + for ( let i = 0; i < params.length - 1; i ++ ) { + + caseNodes.push( this._expressionNode.equal( nodeObject( params[ i ] ) ) ); + + } + + } else { + + throw new Error( 'TSL: Invalid parameter length. Case() requires at least two parameters.' ); + + } + + // extract method + + const method = params[ params.length - 1 ]; + const methodNode = new ShaderNode( method ); + + // chain multiple cases when using Case( 1, 2, 3, () => {} ) + + let caseNode = caseNodes[ 0 ]; + + for ( let i = 1; i < caseNodes.length; i ++ ) { + + caseNode = caseNode.or( caseNodes[ i ] ); + + } + + // build condition + + const condNode = select( caseNode, methodNode ); + + if ( this._currentCond === null ) { + + this._currentCond = condNode; + + return this.add( this._currentCond ); + + } else { + + this._currentCond.elseNode = condNode; + this._currentCond = condNode; + + return this; + + } + + } + + /** + * Represents the default code block of a Switch/Case statement. + * + * @param {Function} method - TSL code which is executed in the `else` case. + * @return {StackNode} A reference to this stack node. + */ + Default( method ) { + + this.Else( method ); + + return this; + + } + + build( builder, ...params ) { + + const previousBuildStack = builder.currentStack; + const previousStack = getCurrentStack(); + + setCurrentStack( this ); + + builder.currentStack = this; + + const buildStage = builder.buildStage; + + for ( const node of this.nodes ) { + + if ( buildStage === 'setup' ) { + + node.build( builder ); + + } else if ( buildStage === 'analyze' ) { + + node.build( builder, this ); + + } else if ( buildStage === 'generate' ) { + + const stages = builder.getDataFromNode( node, 'any' ).stages; + const parents = stages && stages[ builder.shaderStage ]; + + if ( node.isVarNode && parents && parents.length === 1 && parents[ 0 ] && parents[ 0 ].isStackNode ) { + + continue; // skip var nodes that are only used in .toVarying() + + } + + node.build( builder, 'void' ); + + } + + } + + const result = this.outputNode ? this.outputNode.build( builder, ...params ) : super.build( builder, ...params ); + + setCurrentStack( previousStack ); + + builder.currentStack = previousBuildStack; + + return result; + + } + +} + +/** + * TSL function for creating a stack node. + * + * @tsl + * @function + * @param {?StackNode} [parent=null] - The parent stack node. + * @returns {StackNode} + */ +const stack = /*@__PURE__*/ nodeProxy( StackNode ).setParameterLength( 0, 1 ); + +/** + * Generates a layout for struct members. + * This function takes an object representing struct members and returns an array of member layouts. + * Each member layout includes the member's name, type, and whether it is atomic. + * + * @param {Object.} members - An object where keys are member names and values are either types (as strings) or objects with type and atomic properties. + * @returns {Array.<{name: string, type: string, atomic: boolean}>} An array of member layouts. + */ +function getMembersLayout( members ) { + + return Object.entries( members ).map( ( [ name, value ] ) => { + + if ( typeof value === 'string' ) { + + return { name, type: value, atomic: false }; + + } + + return { name, type: value.type, atomic: value.atomic || false }; + + } ); + +} + +/** + * Represents a struct type node in the node-based system. + * This class is used to define and manage the layout and types of struct members. + * It extends the base Node class and provides methods to get the length of the struct, + * retrieve member types, and generate the struct type for a builder. + * + * @augments Node + */ +class StructTypeNode extends Node { + + static get type() { + + return 'StructTypeNode'; + + } + + /** + * Creates an instance of StructTypeNode. + * + * @param {Object} membersLayout - The layout of the members for the struct. + * @param {?string} [name=null] - The optional name of the struct. + */ + constructor( membersLayout, name = null ) { + + super( 'struct' ); + + /** + * The layout of the members for the struct + * + * @type {Array.<{name: string, type: string, atomic: boolean}>} + */ + this.membersLayout = getMembersLayout( membersLayout ); + + /** + * The name of the struct. + * + * @type {?string} + * @default null + */ + this.name = name; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isStructLayoutNode = true; + + } + + /** + * Returns the length of the struct. + * The length is calculated by summing the lengths of the struct's members. + * + * @returns {number} The length of the struct. + */ + getLength() { + + const GPU_CHUNK_BYTES = 8; + const BYTES_PER_ELEMENT = Float32Array.BYTES_PER_ELEMENT; + + let offset = 0; // global buffer offset in bytes + + for ( const member of this.membersLayout ) { + + const type = member.type; + + const itemSize = getMemoryLengthFromType( type ) * BYTES_PER_ELEMENT; + const boundary = getByteBoundaryFromType( type ); + + const chunkOffset = offset % GPU_CHUNK_BYTES; // offset in the current chunk + const chunkPadding = chunkOffset % boundary; // required padding to match boundary + const chunkStart = chunkOffset + chunkPadding; // start position in the current chunk for the data + + offset += chunkPadding; + + // Check for chunk overflow + if ( chunkStart !== 0 && ( GPU_CHUNK_BYTES - chunkStart ) < itemSize ) { + + // Add padding to the end of the chunk + offset += ( GPU_CHUNK_BYTES - chunkStart ); + + } + + offset += itemSize; + + } + + return ( Math.ceil( offset / GPU_CHUNK_BYTES ) * GPU_CHUNK_BYTES ) / BYTES_PER_ELEMENT; + + } + + getMemberType( builder, name ) { + + const member = this.membersLayout.find( m => m.name === name ); + + return member ? member.type : 'void'; + + } + + getNodeType( builder ) { + + const structType = builder.getStructTypeFromNode( this, this.membersLayout, this.name ); + + return structType.name; + + } + + setup( builder ) { + + builder.addInclude( this ); + + } + + generate( builder ) { + + return this.getNodeType( builder ); + + } + +} + +/** + * StructNode allows to create custom structures with multiple members. + * This can also be used to define structures in attribute and uniform data. + * + * ```js + * // Define a custom struct + * const BoundingBox = struct( { min: 'vec3', max: 'vec3' } ); + * + * // Create a new instance of the struct + * const bb = BoundingBox( vec3( 0 ), vec3( 1 ) ); // style 1 + * const bb = BoundingBox( { min: vec3( 0 ), max: vec3( 1 ) } ); // style 2 + * + * // Access the struct members + * const min = bb.get( 'min' ); + * + * // Assign a new value to a member + * min.assign( vec3() ); + * ``` + * @augments Node + */ +class StructNode extends Node { + + static get type() { + + return 'StructNode'; + + } + + constructor( structLayoutNode, values ) { + + super( 'vec3' ); + + this.structLayoutNode = structLayoutNode; + this.values = values; + + this.isStructNode = true; + + } + + getNodeType( builder ) { + + return this.structLayoutNode.getNodeType( builder ); + + } + + getMemberType( builder, name ) { + + return this.structLayoutNode.getMemberType( builder, name ); + + } + + generate( builder ) { + + const nodeVar = builder.getVarFromNode( this ); + const structType = nodeVar.type; + const propertyName = builder.getPropertyName( nodeVar ); + + builder.addLineFlowCode( `${ propertyName } = ${ builder.generateStruct( structType, this.structLayoutNode.membersLayout, this.values ) }`, this ); + + return nodeVar.name; + + } + +} + +/** + * TSL function for creating a struct node. + * + * @tsl + * @function + * @param {Object} membersLayout - The layout of the struct members. + * @param {?string} [name=null] - The name of the struct. + * @returns {Function} The struct function. + */ +const struct = ( membersLayout, name = null ) => { + + const structLayout = new StructTypeNode( membersLayout, name ); + + const struct = ( ...params ) => { + + let values = null; + + if ( params.length > 0 ) { + + if ( params[ 0 ].isNode ) { + + values = {}; + + const names = Object.keys( membersLayout ); + + for ( let i = 0; i < params.length; i ++ ) { + + values[ names[ i ] ] = params[ i ]; + + } + + } else { + + values = params[ 0 ]; + + } + + } + + return nodeObject( new StructNode( structLayout, values ) ); + + }; + + struct.layout = structLayout; + struct.isStruct = true; + + return struct; + +}; + +/** + * This node can be used to define multiple outputs in a shader programs. + * + * @augments Node + */ +class OutputStructNode extends Node { + + static get type() { + + return 'OutputStructNode'; + + } + + /** + * Constructs a new output struct node. The constructor can be invoked with an + * arbitrary number of nodes representing the members. + * + * @param {...Node} members - A parameter list of nodes. + */ + constructor( ...members ) { + + super(); + + /** + * An array of nodes which defines the output. + * + * @type {Array} + */ + this.members = members; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isOutputStructNode = true; + + } + + getNodeType( builder ) { + + const properties = builder.getNodeProperties( this ); + + if ( properties.membersLayout === undefined ) { + + const members = this.members; + const membersLayout = []; + + for ( let i = 0; i < members.length; i ++ ) { + + const name = 'm' + i; + const type = members[ i ].getNodeType( builder ); + + membersLayout.push( { name, type, index: i } ); + + } + + properties.membersLayout = membersLayout; + properties.structType = builder.getOutputStructTypeFromNode( this, properties.membersLayout ); + + } + + return properties.structType.name; + + } + + generate( builder ) { + + const propertyName = builder.getOutputStructName(); + const members = this.members; + + const structPrefix = propertyName !== '' ? propertyName + '.' : ''; + + for ( let i = 0; i < members.length; i ++ ) { + + const snippet = members[ i ].build( builder ); + + builder.addLineFlowCode( `${ structPrefix }m${ i } = ${ snippet }`, this ); + + } + + return propertyName; + + } + +} + +/** + * TSL function for creating an output struct node. + * + * @tsl + * @function + * @param {...Node} members - A parameter list of nodes. + * @returns {OutputStructNode} + */ +const outputStruct = /*@__PURE__*/ nodeProxy( OutputStructNode ); + +/** + * Returns the MRT texture index for the given name. + * + * @param {Array} textures - The textures of a MRT-configured render target. + * @param {string} name - The name of the MRT texture which index is requested. + * @return {number} The texture index. + */ +function getTextureIndex( textures, name ) { + + for ( let i = 0; i < textures.length; i ++ ) { + + if ( textures[ i ].name === name ) { + + return i; + + } + + } + + return -1; + +} + +/** + * This node can be used setup a MRT context for rendering. A typical MRT setup for + * post-processing is shown below: + * ```js + * const mrtNode = mrt( { + * output: output, + * normal: normalView + * } ) ); + * ``` + * The MRT output is defined as a dictionary. + * + * @augments OutputStructNode + */ +class MRTNode extends OutputStructNode { + + static get type() { + + return 'MRTNode'; + + } + + /** + * Constructs a new output struct node. + * + * @param {Object} outputNodes - The MRT outputs. + */ + constructor( outputNodes ) { + + super(); + + /** + * A dictionary representing the MRT outputs. The key + * is the name of the output, the value the node which produces + * the output result. + * + * @type {Object} + */ + this.outputNodes = outputNodes; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMRTNode = true; + + } + + /** + * Returns `true` if the MRT node has an output with the given name. + * + * @param {string} name - The name of the output. + * @return {NodeBuilder} Whether the MRT node has an output for the given name or not. + */ + has( name ) { + + return this.outputNodes[ name ] !== undefined; + + } + + /** + * Returns the output node for the given name. + * + * @param {string} name - The name of the output. + * @return {Node} The output node. + */ + get( name ) { + + return this.outputNodes[ name ]; + + } + + /** + * Merges the outputs of the given MRT node with the outputs of this node. + * + * @param {MRTNode} mrtNode - The MRT to merge. + * @return {MRTNode} A new MRT node with merged outputs.. + */ + merge( mrtNode ) { + + const outputs = { ...this.outputNodes, ...mrtNode.outputNodes }; + + return mrt( outputs ); + + } + + setup( builder ) { + + const outputNodes = this.outputNodes; + const mrt = builder.renderer.getRenderTarget(); + + const members = []; + + const textures = mrt.textures; + + for ( const name in outputNodes ) { + + const index = getTextureIndex( textures, name ); + + members[ index ] = vec4( outputNodes[ name ] ); + + } + + this.members = members; + + return super.setup( builder ); + + } + +} + +/** + * TSL function for creating a MRT node. + * + * @tsl + * @function + * @param {Object} outputNodes - The MRT outputs. + * @returns {MRTNode} + */ +const mrt = /*@__PURE__*/ nodeProxy( MRTNode ); + +/** + * Generates a hash value in the range `[0, 1]` from the given seed. + * + * @tsl + * @function + * @param {Node} seed - The seed. + * @return {Node} The hash value. + */ +const hash = /*@__PURE__*/ Fn( ( [ seed ] ) => { + + // Taken from https://www.shadertoy.com/view/XlGcRh, originally from pcg-random.org + + const state = seed.toUint().mul( 747796405 ).add( 2891336453 ); + const word = state.shiftRight( state.shiftRight( 28 ).add( 4 ) ).bitXor( state ).mul( 277803737 ); + const result = word.shiftRight( 22 ).bitXor( word ); + + return result.toFloat().mul( 1 / 2 ** 32 ); // Convert to range [0, 1) + +} ); + +/** + * A function that remaps the `[0,1]` interval into the `[0,1]` interval. + * The corners are mapped to `0` and the center to `1`. + * Reference: {@link https://iquilezles.org/articles/functions/}. + * + * @tsl + * @function + * @param {Node} x - The value to remap. + * @param {Node} k - Allows to control the remapping functions shape by rising the parabola to a power `k`. + * @return {Node} The remapped value. + */ +const parabola = ( x, k ) => pow( mul( 4.0, x.mul( sub( 1.0, x ) ) ), k ); + +/** + * A function that remaps the `[0,1]` interval into the `[0,1]` interval. + * Expands the sides and compresses the center, and keeps `0.5` mapped to `0.5`. + * Reference: {@link https://iquilezles.org/articles/functions/}. + * + * @tsl + * @function + * @param {Node} x - The value to remap. + * @param {Node} k - `k=1` is the identity curve,`k<1` produces the classic `gain()` shape, and `k>1` produces "s" shaped curves. + * @return {Node} The remapped value. + */ +const gain = ( x, k ) => x.lessThan( 0.5 ) ? parabola( x.mul( 2.0 ), k ).div( 2.0 ) : sub( 1.0, parabola( mul( sub( 1.0, x ), 2.0 ), k ).div( 2.0 ) ); + +/** + * A function that remaps the `[0,1]` interval into the `[0,1]` interval. + * A generalization of the `parabola()`. Keeps the corners mapped to 0 but allows the control of the shape one either side of the curve. + * Reference: {@link https://iquilezles.org/articles/functions/}. + * + * @tsl + * @function + * @param {Node} x - The value to remap. + * @param {Node} a - First control parameter. + * @param {Node} b - Second control parameter. + * @return {Node} The remapped value. + */ +const pcurve = ( x, a, b ) => pow( div( pow( x, a ), add( pow( x, a ), pow( sub( 1.0, x ), b ) ) ), 1.0 / a ); + +/** + * A phase shifted sinus curve that starts at zero and ends at zero, with bouncing behavior. + * Reference: {@link https://iquilezles.org/articles/functions/}. + * + * @tsl + * @function + * @param {Node} x - The value to compute the sin for. + * @param {Node} k - Controls the amount of bounces. + * @return {Node} The result value. + */ +const sinc = ( x, k ) => sin( PI.mul( k.mul( x ).sub( 1.0 ) ) ).div( PI.mul( k.mul( x ).sub( 1.0 ) ) ); + +// https://github.com/cabbibo/glsl-tri-noise-3d + + +const tri = /*@__PURE__*/ Fn( ( [ x ] ) => { + + return x.fract().sub( .5 ).abs(); + +} ).setLayout( { + name: 'tri', + type: 'float', + inputs: [ + { name: 'x', type: 'float' } + ] +} ); + +const tri3 = /*@__PURE__*/ Fn( ( [ p ] ) => { + + return vec3( tri( p.z.add( tri( p.y.mul( 1. ) ) ) ), tri( p.z.add( tri( p.x.mul( 1. ) ) ) ), tri( p.y.add( tri( p.x.mul( 1. ) ) ) ) ); + +} ).setLayout( { + name: 'tri3', + type: 'vec3', + inputs: [ + { name: 'p', type: 'vec3' } + ] +} ); + +/** + * Generates a noise value from the given position, speed and time parameters. + * + * @tsl + * @function + * @param {Node} position - The position. + * @param {Node} speed - The speed. + * @param {Node} time - The time. + * @return {Node} The generated noise. + */ +const triNoise3D = /*@__PURE__*/ Fn( ( [ position, speed, time ] ) => { + + const p = vec3( position ).toVar(); + const z = float( 1.4 ).toVar(); + const rz = float( 0.0 ).toVar(); + const bp = vec3( p ).toVar(); + + Loop( { start: float( 0.0 ), end: float( 3.0 ), type: 'float', condition: '<=' }, () => { + + const dg = vec3( tri3( bp.mul( 2.0 ) ) ).toVar(); + p.addAssign( dg.add( time.mul( float( 0.1 ).mul( speed ) ) ) ); + bp.mulAssign( 1.8 ); + z.mulAssign( 1.5 ); + p.mulAssign( 1.2 ); + + const t = float( tri( p.z.add( tri( p.x.add( tri( p.y ) ) ) ) ) ).toVar(); + rz.addAssign( t.div( z ) ); + bp.addAssign( 0.14 ); + + } ); + + return rz; + +} ).setLayout( { + name: 'triNoise3D', + type: 'float', + inputs: [ + { name: 'position', type: 'vec3' }, + { name: 'speed', type: 'float' }, + { name: 'time', type: 'float' } + ] +} ); + +/** + * This class allows to define multiple overloaded versions + * of the same function. Depending on the parameters of the function + * call, the node picks the best-fit overloaded version. + * + * @augments Node + */ +class FunctionOverloadingNode extends Node { + + static get type() { + + return 'FunctionOverloadingNode'; + + } + + /** + * Constructs a new function overloading node. + * + * @param {Array} functionNodes - Array of `Fn` function definitions. + * @param {...Node} parametersNodes - A list of parameter nodes. + */ + constructor( functionNodes = [], ...parametersNodes ) { + + super(); + + /** + * Array of `Fn` function definitions. + * + * @type {Array} + */ + this.functionNodes = functionNodes; + + /** + * A list of parameter nodes. + * + * @type {Array} + */ + this.parametersNodes = parametersNodes; + + /** + * The selected overloaded function call. + * + * @private + * @type {ShaderCallNodeInternal} + */ + this._candidateFnCall = null; + + /** + * This node is marked as global. + * + * @type {boolean} + * @default true + */ + this.global = true; + + } + + /** + * This method is overwritten since the node type is inferred from + * the function's return type. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType() { + + return this.functionNodes[ 0 ].shaderNode.layout.type; + + } + + setup( builder ) { + + const params = this.parametersNodes; + + let candidateFnCall = this._candidateFnCall; + + if ( candidateFnCall === null ) { + + let candidateFn = null; + let candidateScore = -1; + + for ( const functionNode of this.functionNodes ) { + + const shaderNode = functionNode.shaderNode; + const layout = shaderNode.layout; + + if ( layout === null ) { + + throw new Error( 'FunctionOverloadingNode: FunctionNode must be a layout.' ); + + } + + const inputs = layout.inputs; + + if ( params.length === inputs.length ) { + + let score = 0; + + for ( let i = 0; i < params.length; i ++ ) { + + const param = params[ i ]; + const input = inputs[ i ]; + + if ( param.getNodeType( builder ) === input.type ) { + + score ++; + + } else { + + score = 0; + + } + + } + + if ( score > candidateScore ) { + + candidateFn = functionNode; + candidateScore = score; + + } + + } + + } + + this._candidateFnCall = candidateFnCall = candidateFn( ...params ); + + } + + return candidateFnCall; + + } + +} + +const overloadingBaseFn = /*@__PURE__*/ nodeProxy( FunctionOverloadingNode ); + +/** + * TSL function for creating a function overloading node. + * + * @tsl + * @function + * @param {Array} functionNodes - Array of `Fn` function definitions. + * @returns {FunctionOverloadingNode} + */ +const overloadingFn = ( functionNodes ) => ( ...params ) => overloadingBaseFn( functionNodes, ...params ); + +/** + * Represents the elapsed time in seconds. + * + * @tsl + * @type {UniformNode} + */ +const time = /*@__PURE__*/ uniform( 0 ).setGroup( renderGroup ).onRenderUpdate( ( frame ) => frame.time ); + +/** + * Represents the delta time in seconds. + * + * @tsl + * @type {UniformNode} + */ +const deltaTime = /*@__PURE__*/ uniform( 0 ).setGroup( renderGroup ).onRenderUpdate( ( frame ) => frame.deltaTime ); + +/** + * Represents the current frame ID. + * + * @tsl + * @type {UniformNode} + */ +const frameId = /*@__PURE__*/ uniform( 0, 'uint' ).setGroup( renderGroup ).onRenderUpdate( ( frame ) => frame.frameId ); + +// Deprecated + +/** + * @tsl + * @function + * @deprecated since r170. Use {@link time} instead. + * + * @param {number} [timeScale=1] - The time scale. + * @returns {UniformNode} + */ +const timerLocal = ( timeScale = 1 ) => { // @deprecated, r170 + + console.warn( 'TSL: timerLocal() is deprecated. Use "time" instead.' ); + return time.mul( timeScale ); + +}; + +/** + * @tsl + * @function + * @deprecated since r170. Use {@link time} instead. + * + * @param {number} [timeScale=1] - The time scale. + * @returns {UniformNode} + */ +const timerGlobal = ( timeScale = 1 ) => { // @deprecated, r170 + + console.warn( 'TSL: timerGlobal() is deprecated. Use "time" instead.' ); + return time.mul( timeScale ); + +}; + +/** + * @tsl + * @function + * @deprecated since r170. Use {@link deltaTime} instead. + * + * @param {number} [timeScale=1] - The time scale. + * @returns {UniformNode} + */ +const timerDelta = ( timeScale = 1 ) => { // @deprecated, r170 + + console.warn( 'TSL: timerDelta() is deprecated. Use "deltaTime" instead.' ); + return deltaTime.mul( timeScale ); + +}; + +/** + * Generates a sine wave oscillation based on a timer. + * + * @tsl + * @function + * @param {Node} t - The timer to generate the oscillation with. + * @return {Node} The oscillation node. + */ +const oscSine = ( t = time ) => t.add( 0.75 ).mul( Math.PI * 2 ).sin().mul( 0.5 ).add( 0.5 ); + +/** + * Generates a square wave oscillation based on a timer. + * + * @tsl + * @function + * @param {Node} t - The timer to generate the oscillation with. + * @return {Node} The oscillation node. + */ +const oscSquare = ( t = time ) => t.fract().round(); + +/** + * Generates a triangle wave oscillation based on a timer. + * + * @tsl + * @function + * @param {Node} t - The timer to generate the oscillation with. + * @return {Node} The oscillation node. + */ +const oscTriangle = ( t = time ) => t.add( 0.5 ).fract().mul( 2 ).sub( 1 ).abs(); + +/** + * Generates a sawtooth wave oscillation based on a timer. + * + * @tsl + * @function + * @param {Node} t - The timer to generate the oscillation with. + * @return {Node} The oscillation node. + */ +const oscSawtooth = ( t = time ) => t.fract(); + +/** + * Rotates the given uv coordinates around a center point + * + * @tsl + * @function + * @param {Node} uv - The uv coordinates. + * @param {Node} rotation - The rotation defined in radians. + * @param {Node} center - The center of rotation + * @return {Node} The rotated uv coordinates. + */ +const rotateUV = /*@__PURE__*/ Fn( ( [ uv, rotation, center = vec2( 0.5 ) ] ) => { + + return rotate( uv.sub( center ), rotation ).add( center ); + +} ); + +/** + * Applies a spherical warping effect to the given uv coordinates. + * + * @tsl + * @function + * @param {Node} uv - The uv coordinates. + * @param {Node} strength - The strength of the effect. + * @param {Node} center - The center point + * @return {Node} The updated uv coordinates. + */ +const spherizeUV = /*@__PURE__*/ Fn( ( [ uv, strength, center = vec2( 0.5 ) ] ) => { + + const delta = uv.sub( center ); + const delta2 = delta.dot( delta ); + const delta4 = delta2.mul( delta2 ); + const deltaOffset = delta4.mul( strength ); + + return uv.add( delta.mul( deltaOffset ) ); + +} ); + +/** + * This can be used to achieve a billboarding behavior for flat meshes. That means they are + * oriented always towards the camera. + * + * ```js + * material.vertexNode = billboarding(); + * ``` + * + * @tsl + * @function + * @param {Object} config - The configuration object. + * @param {?Node} [config.position=null] - Can be used to define the vertex positions in world space. + * @param {boolean} [config.horizontal=true] - Whether to follow the camera rotation horizontally or not. + * @param {boolean} [config.vertical=false] - Whether to follow the camera rotation vertically or not. + * @return {Node} The updated vertex position in clip space. + */ +const billboarding = /*@__PURE__*/ Fn( ( { position = null, horizontal = true, vertical = false } ) => { + + let worldMatrix; + + if ( position !== null ) { + + worldMatrix = modelWorldMatrix.toVar(); + worldMatrix[ 3 ][ 0 ] = position.x; + worldMatrix[ 3 ][ 1 ] = position.y; + worldMatrix[ 3 ][ 2 ] = position.z; + + } else { + + worldMatrix = modelWorldMatrix; + + } + + const modelViewMatrix = cameraViewMatrix.mul( worldMatrix ); + + if ( defined( horizontal ) ) { + + modelViewMatrix[ 0 ][ 0 ] = modelWorldMatrix[ 0 ].length(); + modelViewMatrix[ 0 ][ 1 ] = 0; + modelViewMatrix[ 0 ][ 2 ] = 0; + + } + + if ( defined( vertical ) ) { + + modelViewMatrix[ 1 ][ 0 ] = 0; + modelViewMatrix[ 1 ][ 1 ] = modelWorldMatrix[ 1 ].length(); + modelViewMatrix[ 1 ][ 2 ] = 0; + + } + + modelViewMatrix[ 2 ][ 0 ] = 0; + modelViewMatrix[ 2 ][ 1 ] = 0; + modelViewMatrix[ 2 ][ 2 ] = 1; + + return cameraProjectionMatrix.mul( modelViewMatrix ).mul( positionLocal ); + +} ); + +/** + * A special version of a screen uv function that involves a depth comparison + * when computing the final uvs. The function mitigates visual errors when + * using viewport texture nodes for refraction purposes. Without this function + * objects in front of a refractive surface might appear on the refractive surface + * which is incorrect. + * + * @tsl + * @function + * @param {?Node} uv - Optional uv coordinates. By default `screenUV` is used. + * @return {Node} The update uv coordinates. + */ +const viewportSafeUV = /*@__PURE__*/ Fn( ( [ uv = null ] ) => { + + const depth = linearDepth(); + const depthDiff = linearDepth( viewportDepthTexture( uv ) ).sub( depth ); + const finalUV = depthDiff.lessThan( 0 ).select( screenUV, uv ); + + return finalUV; + +} ); + +/** + * Can be used to compute texture coordinates for animated sprite sheets. + * + * ```js + * const uvNode = spritesheetUV( vec2( 6, 6 ), uv(), time.mul( animationSpeed ) ); + * + * material.colorNode = texture( spriteSheet, uvNode ); + * ``` + * + * @augments Node + */ +class SpriteSheetUVNode extends Node { + + static get type() { + + return 'SpriteSheetUVNode'; + + } + + /** + * Constructs a new sprite sheet uv node. + * + * @param {Node} countNode - The node that defines the number of sprites in the x and y direction (e.g 6x6). + * @param {Node} [uvNode=uv()] - The uv node. + * @param {Node} [frameNode=float()] - The node that defines the current frame/sprite. + */ + constructor( countNode, uvNode = uv$1(), frameNode = float( 0 ) ) { + + super( 'vec2' ); + + /** + * The node that defines the number of sprites in the x and y direction (e.g 6x6). + * + * @type {Node} + */ + this.countNode = countNode; + + /** + * The uv node. + * + * @type {Node} + */ + this.uvNode = uvNode; + + /** + * The node that defines the current frame/sprite. + * + * @type {Node} + */ + this.frameNode = frameNode; + + } + + setup() { + + const { frameNode, uvNode, countNode } = this; + + const { width, height } = countNode; + + const frameNum = frameNode.mod( width.mul( height ) ).floor(); + + const column = frameNum.mod( width ); + const row = height.sub( frameNum.add( 1 ).div( width ).ceil() ); + + const scale = countNode.reciprocal(); + const uvFrameOffset = vec2( column, row ); + + return uvNode.add( uvFrameOffset ).mul( scale ); + + } + +} + +/** + * TSL function for creating a sprite sheet uv node. + * + * @tsl + * @function + * @param {Node} countNode - The node that defines the number of sprites in the x and y direction (e.g 6x6). + * @param {?Node} [uvNode=uv()] - The uv node. + * @param {?Node} [frameNode=float()] - The node that defines the current frame/sprite. + * @returns {SpriteSheetUVNode} + */ +const spritesheetUV = /*@__PURE__*/ nodeProxy( SpriteSheetUVNode ).setParameterLength( 3 ); + +/** + * TSL function for creating a triplanar textures node. + * + * Can be used for triplanar texture mapping. + * + * ```js + * material.colorNode = triplanarTexture( texture( diffuseMap ) ); + * ``` + * + * @tsl + * @function + * @param {Node} textureXNode - First texture node. + * @param {?Node} [textureYNode=null] - Second texture node. When not set, the shader will sample from `textureXNode` instead. + * @param {?Node} [textureZNode=null] - Third texture node. When not set, the shader will sample from `textureXNode` instead. + * @param {?Node} [scaleNode=float(1)] - The scale node. + * @param {?Node} [positionNode=positionLocal] - Vertex positions in local space. + * @param {?Node} [normalNode=normalLocal] - Normals in local space. + * @returns {Node} + */ +const triplanarTextures = /*@__PURE__*/ Fn( ( [ textureXNode, textureYNode = null, textureZNode = null, scaleNode = float( 1 ), positionNode = positionLocal, normalNode = normalLocal ] ) => { + + // Reference: https://github.com/keijiro/StandardTriplanar + + // Blending factor of triplanar mapping + let bf = normalNode.abs().normalize(); + bf = bf.div( bf.dot( vec3( 1.0 ) ) ); + + // Triplanar mapping + const tx = positionNode.yz.mul( scaleNode ); + const ty = positionNode.zx.mul( scaleNode ); + const tz = positionNode.xy.mul( scaleNode ); + + // Base color + const textureX = textureXNode.value; + const textureY = textureYNode !== null ? textureYNode.value : textureX; + const textureZ = textureZNode !== null ? textureZNode.value : textureX; + + const cx = texture( textureX, tx ).mul( bf.x ); + const cy = texture( textureY, ty ).mul( bf.y ); + const cz = texture( textureZ, tz ).mul( bf.z ); + + return add( cx, cy, cz ); + +} ); + +/** + * TSL function for creating a triplanar textures node. + * + * @tsl + * @function + * @param {Node} textureXNode - First texture node. + * @param {?Node} [textureYNode=null] - Second texture node. When not set, the shader will sample from `textureXNode` instead. + * @param {?Node} [textureZNode=null] - Third texture node. When not set, the shader will sample from `textureXNode` instead. + * @param {?Node} [scaleNode=float(1)] - The scale node. + * @param {?Node} [positionNode=positionLocal] - Vertex positions in local space. + * @param {?Node} [normalNode=normalLocal] - Normals in local space. + * @returns {Node} + */ +const triplanarTexture = ( ...params ) => triplanarTextures( ...params ); + +const _reflectorPlane = new Plane(); +const _normal = new Vector3(); +const _reflectorWorldPosition = new Vector3(); +const _cameraWorldPosition = new Vector3(); +const _rotationMatrix = new Matrix4(); +const _lookAtPosition = new Vector3( 0, 0, -1 ); +const clipPlane = new Vector4(); + +const _view = new Vector3(); +const _target = new Vector3(); +const _q = new Vector4(); + +const _size$2 = new Vector2(); + +const _defaultRT = new RenderTarget(); +const _defaultUV = screenUV.flipX(); + +_defaultRT.depthTexture = new DepthTexture( 1, 1 ); + +let _inReflector = false; + +/** + * This node can be used to implement mirror-like flat reflective surfaces. + * + * ```js + * const groundReflector = reflector(); + * material.colorNode = groundReflector; + * + * const plane = new Mesh( geometry, material ); + * plane.add( groundReflector.target ); + * ``` + * + * @augments TextureNode + */ +class ReflectorNode extends TextureNode { + + static get type() { + + return 'ReflectorNode'; + + } + + /** + * Constructs a new reflector node. + * + * @param {Object} [parameters={}] - An object holding configuration parameters. + * @param {Object3D} [parameters.target=new Object3D()] - The 3D object the reflector is linked to. + * @param {number} [parameters.resolution=1] - The resolution scale. + * @param {boolean} [parameters.generateMipmaps=false] - Whether mipmaps should be generated or not. + * @param {boolean} [parameters.bounces=true] - Whether reflectors can render other reflector nodes or not. + * @param {boolean} [parameters.depth=false] - Whether depth data should be generated or not. + * @param {TextureNode} [parameters.defaultTexture] - The default texture node. + * @param {ReflectorBaseNode} [parameters.reflector] - The reflector base node. + */ + constructor( parameters = {} ) { + + super( parameters.defaultTexture || _defaultRT.texture, _defaultUV ); + + /** + * A reference to the internal reflector base node which holds the actual implementation. + * + * @private + * @type {ReflectorBaseNode} + * @default ReflectorBaseNode + */ + this._reflectorBaseNode = parameters.reflector || new ReflectorBaseNode( this, parameters ); + + /** + * A reference to the internal depth node. + * + * @private + * @type {?Node} + * @default null + */ + this._depthNode = null; + + this.setUpdateMatrix( false ); + + } + + /** + * A reference to the internal reflector node. + * + * @type {ReflectorBaseNode} + */ + get reflector() { + + return this._reflectorBaseNode; + + } + + /** + * A reference to 3D object the reflector is linked to. + * + * @type {Object3D} + */ + get target() { + + return this._reflectorBaseNode.target; + + } + + /** + * Returns a node representing the mirror's depth. That can be used + * to implement more advanced reflection effects like distance attenuation. + * + * @return {Node} The depth node. + */ + getDepthNode() { + + if ( this._depthNode === null ) { + + if ( this._reflectorBaseNode.depth !== true ) { + + throw new Error( 'THREE.ReflectorNode: Depth node can only be requested when the reflector is created with { depth: true }. ' ); + + } + + this._depthNode = nodeObject( new ReflectorNode( { + defaultTexture: _defaultRT.depthTexture, + reflector: this._reflectorBaseNode + } ) ); + + } + + return this._depthNode; + + } + + setup( builder ) { + + // ignore if used in post-processing + if ( ! builder.object.isQuadMesh ) this._reflectorBaseNode.build( builder ); + + return super.setup( builder ); + + } + + clone() { + + const newNode = new this.constructor( this.reflectorNode ); + newNode.uvNode = this.uvNode; + newNode.levelNode = this.levelNode; + newNode.biasNode = this.biasNode; + newNode.sampler = this.sampler; + newNode.depthNode = this.depthNode; + newNode.compareNode = this.compareNode; + newNode.gradNode = this.gradNode; + newNode._reflectorBaseNode = this._reflectorBaseNode; + + return newNode; + + } + + /** + * Frees internal resources. Should be called when the node is no longer in use. + */ + dispose() { + + super.dispose(); + + this._reflectorBaseNode.dispose(); + + } + +} + +/** + * Holds the actual implementation of the reflector. + * + * TODO: Explain why `ReflectorBaseNode`. Originally the entire logic was implemented + * in `ReflectorNode`, see #29619. + * + * @private + * @augments Node + */ +class ReflectorBaseNode extends Node { + + static get type() { + + return 'ReflectorBaseNode'; + + } + + /** + * Constructs a new reflector base node. + * + * @param {TextureNode} textureNode - Represents the rendered reflections as a texture node. + * @param {Object} [parameters={}] - An object holding configuration parameters. + * @param {Object3D} [parameters.target=new Object3D()] - The 3D object the reflector is linked to. + * @param {number} [parameters.resolution=1] - The resolution scale. + * @param {boolean} [parameters.generateMipmaps=false] - Whether mipmaps should be generated or not. + * @param {boolean} [parameters.bounces=true] - Whether reflectors can render other reflector nodes or not. + * @param {boolean} [parameters.depth=false] - Whether depth data should be generated or not. + */ + constructor( textureNode, parameters = {} ) { + + super(); + + const { + target = new Object3D(), + resolution = 1, + generateMipmaps = false, + bounces = true, + depth = false + } = parameters; + + /** + * Represents the rendered reflections as a texture node. + * + * @type {TextureNode} + */ + this.textureNode = textureNode; + + /** + * The 3D object the reflector is linked to. + * + * @type {Object3D} + * @default {new Object3D()} + */ + this.target = target; + + /** + * The resolution scale. + * + * @type {number} + * @default {1} + */ + this.resolution = resolution; + + /** + * Whether mipmaps should be generated or not. + * + * @type {boolean} + * @default {false} + */ + this.generateMipmaps = generateMipmaps; + + /** + * Whether reflectors can render other reflector nodes or not. + * + * @type {boolean} + * @default {true} + */ + this.bounces = bounces; + + /** + * Whether depth data should be generated or not. + * + * @type {boolean} + * @default {false} + */ + this.depth = depth; + + /** + * The `updateBeforeType` is set to `NodeUpdateType.RENDER` when {@link ReflectorBaseNode#bounces} + * is `true`. Otherwise it's `NodeUpdateType.FRAME`. + * + * @type {string} + * @default 'render' + */ + this.updateBeforeType = bounces ? NodeUpdateType.RENDER : NodeUpdateType.FRAME; + + /** + * Weak map for managing virtual cameras. + * + * @type {WeakMap} + */ + this.virtualCameras = new WeakMap(); + + /** + * Weak map for managing render targets. + * + * @type {Map} + */ + this.renderTargets = new Map(); + + /** + * Force render even if reflector is facing away from camera. + * + * @type {boolean} + * @default {false} + */ + this.forceUpdate = false; + + /** + * Whether the reflector has been rendered or not. + * + * When the reflector is facing away from the camera, + * this flag is set to `false` and the texture will be empty(black). + * + * @type {boolean} + * @default {false} + */ + this.hasOutput = false; + + } + + /** + * Updates the resolution of the internal render target. + * + * @private + * @param {RenderTarget} renderTarget - The render target to resize. + * @param {Renderer} renderer - The renderer that is used to determine the new size. + */ + _updateResolution( renderTarget, renderer ) { + + const resolution = this.resolution; + + renderer.getDrawingBufferSize( _size$2 ); + + renderTarget.setSize( Math.round( _size$2.width * resolution ), Math.round( _size$2.height * resolution ) ); + + } + + setup( builder ) { + + this._updateResolution( _defaultRT, builder.renderer ); + + return super.setup( builder ); + + } + + /** + * Frees internal resources. Should be called when the node is no longer in use. + */ + dispose() { + + super.dispose(); + + for ( const renderTarget of this.renderTargets.values() ) { + + renderTarget.dispose(); + + } + + } + + /** + * Returns a virtual camera for the given camera. The virtual camera is used to + * render the scene from the reflector's view so correct reflections can be produced. + * + * @param {Camera} camera - The scene's camera. + * @return {Camera} The corresponding virtual camera. + */ + getVirtualCamera( camera ) { + + let virtualCamera = this.virtualCameras.get( camera ); + + if ( virtualCamera === undefined ) { + + virtualCamera = camera.clone(); + + this.virtualCameras.set( camera, virtualCamera ); + + } + + return virtualCamera; + + } + + /** + * Returns a render target for the given camera. The reflections are rendered + * into this render target. + * + * @param {Camera} camera - The scene's camera. + * @return {RenderTarget} The render target. + */ + getRenderTarget( camera ) { + + let renderTarget = this.renderTargets.get( camera ); + + if ( renderTarget === undefined ) { + + renderTarget = new RenderTarget( 0, 0, { type: HalfFloatType } ); + + if ( this.generateMipmaps === true ) { + + renderTarget.texture.minFilter = LinearMipMapLinearFilter; + renderTarget.texture.generateMipmaps = true; + + } + + if ( this.depth === true ) { + + renderTarget.depthTexture = new DepthTexture(); + + } + + this.renderTargets.set( camera, renderTarget ); + + } + + return renderTarget; + + } + + updateBefore( frame ) { + + if ( this.bounces === false && _inReflector ) return false; + + _inReflector = true; + + const { scene, camera, renderer, material } = frame; + const { target } = this; + + const virtualCamera = this.getVirtualCamera( camera ); + const renderTarget = this.getRenderTarget( virtualCamera ); + + renderer.getDrawingBufferSize( _size$2 ); + + this._updateResolution( renderTarget, renderer ); + + // + + _reflectorWorldPosition.setFromMatrixPosition( target.matrixWorld ); + _cameraWorldPosition.setFromMatrixPosition( camera.matrixWorld ); + + _rotationMatrix.extractRotation( target.matrixWorld ); + + _normal.set( 0, 0, 1 ); + _normal.applyMatrix4( _rotationMatrix ); + + _view.subVectors( _reflectorWorldPosition, _cameraWorldPosition ); + + // Avoid rendering when reflector is facing away unless forcing an update + const isFacingAway = _view.dot( _normal ) > 0; + + let needsClear = false; + + if ( isFacingAway === true && this.forceUpdate === false ) { + + if ( this.hasOutput === false ) { + + _inReflector = false; + + return; + + } + + needsClear = true; + + } + + _view.reflect( _normal ).negate(); + _view.add( _reflectorWorldPosition ); + + _rotationMatrix.extractRotation( camera.matrixWorld ); + + _lookAtPosition.set( 0, 0, -1 ); + _lookAtPosition.applyMatrix4( _rotationMatrix ); + _lookAtPosition.add( _cameraWorldPosition ); + + _target.subVectors( _reflectorWorldPosition, _lookAtPosition ); + _target.reflect( _normal ).negate(); + _target.add( _reflectorWorldPosition ); + + // + + virtualCamera.coordinateSystem = camera.coordinateSystem; + virtualCamera.position.copy( _view ); + virtualCamera.up.set( 0, 1, 0 ); + virtualCamera.up.applyMatrix4( _rotationMatrix ); + virtualCamera.up.reflect( _normal ); + virtualCamera.lookAt( _target ); + + virtualCamera.near = camera.near; + virtualCamera.far = camera.far; + + virtualCamera.updateMatrixWorld(); + virtualCamera.projectionMatrix.copy( camera.projectionMatrix ); + + // Now update projection matrix with new clip plane, implementing code from: http://www.terathon.com/code/oblique.html + // Paper explaining this technique: http://www.terathon.com/lengyel/Lengyel-Oblique.pdf + _reflectorPlane.setFromNormalAndCoplanarPoint( _normal, _reflectorWorldPosition ); + _reflectorPlane.applyMatrix4( virtualCamera.matrixWorldInverse ); + + clipPlane.set( _reflectorPlane.normal.x, _reflectorPlane.normal.y, _reflectorPlane.normal.z, _reflectorPlane.constant ); + + const projectionMatrix = virtualCamera.projectionMatrix; + + _q.x = ( Math.sign( clipPlane.x ) + projectionMatrix.elements[ 8 ] ) / projectionMatrix.elements[ 0 ]; + _q.y = ( Math.sign( clipPlane.y ) + projectionMatrix.elements[ 9 ] ) / projectionMatrix.elements[ 5 ]; + _q.z = -1; + _q.w = ( 1.0 + projectionMatrix.elements[ 10 ] ) / projectionMatrix.elements[ 14 ]; + + // Calculate the scaled plane vector + clipPlane.multiplyScalar( 1.0 / clipPlane.dot( _q ) ); + + const clipBias = 0; + + // Replacing the third row of the projection matrix + projectionMatrix.elements[ 2 ] = clipPlane.x; + projectionMatrix.elements[ 6 ] = clipPlane.y; + projectionMatrix.elements[ 10 ] = ( renderer.coordinateSystem === WebGPUCoordinateSystem ) ? ( clipPlane.z - clipBias ) : ( clipPlane.z + 1.0 - clipBias ); + projectionMatrix.elements[ 14 ] = clipPlane.w; + + // + + this.textureNode.value = renderTarget.texture; + + if ( this.depth === true ) { + + this.textureNode.getDepthNode().value = renderTarget.depthTexture; + + } + + material.visible = false; + + const currentRenderTarget = renderer.getRenderTarget(); + const currentMRT = renderer.getMRT(); + const currentAutoClear = renderer.autoClear; + + renderer.setMRT( null ); + renderer.setRenderTarget( renderTarget ); + renderer.autoClear = true; + + if ( needsClear ) { + + renderer.clear(); + + this.hasOutput = false; + + } else { + + renderer.render( scene, virtualCamera ); + + this.hasOutput = true; + + } + + renderer.setMRT( currentMRT ); + renderer.setRenderTarget( currentRenderTarget ); + renderer.autoClear = currentAutoClear; + + material.visible = true; + + _inReflector = false; + + this.forceUpdate = false; + + } + +} + +/** + * TSL function for creating a reflector node. + * + * @tsl + * @function + * @param {Object} [parameters={}] - An object holding configuration parameters. + * @param {Object3D} [parameters.target=new Object3D()] - The 3D object the reflector is linked to. + * @param {number} [parameters.resolution=1] - The resolution scale. + * @param {boolean} [parameters.generateMipmaps=false] - Whether mipmaps should be generated or not. + * @param {boolean} [parameters.bounces=true] - Whether reflectors can render other reflector nodes or not. + * @param {boolean} [parameters.depth=false] - Whether depth data should be generated or not. + * @param {TextureNode} [parameters.defaultTexture] - The default texture node. + * @param {ReflectorBaseNode} [parameters.reflector] - The reflector base node. + * @returns {ReflectorNode} + */ +const reflector = ( parameters ) => nodeObject( new ReflectorNode( parameters ) ); + +const _camera = /*@__PURE__*/ new OrthographicCamera( -1, 1, 1, -1, 0, 1 ); + +/** + * The purpose of this special geometry is to fill the entire viewport with a single triangle. + * + * Reference: {@link https://github.com/mrdoob/three.js/pull/21358} + * + * @private + * @augments BufferGeometry + */ +class QuadGeometry extends BufferGeometry { + + /** + * Constructs a new quad geometry. + * + * @param {boolean} [flipY=false] - Whether the uv coordinates should be flipped along the vertical axis or not. + */ + constructor( flipY = false ) { + + super(); + + const uv = flipY === false ? [ 0, -1, 0, 1, 2, 1 ] : [ 0, 2, 0, 0, 2, 0 ]; + + this.setAttribute( 'position', new Float32BufferAttribute( [ -1, 3, 0, -1, -1, 0, 3, -1, 0 ], 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uv, 2 ) ); + + } + +} + +const _geometry = /*@__PURE__*/ new QuadGeometry(); + + +/** + * This module is a helper for passes which need to render a full + * screen effect which is quite common in context of post processing. + * + * The intended usage is to reuse a single quad mesh for rendering + * subsequent passes by just reassigning the `material` reference. + * + * Note: This module can only be used with `WebGPURenderer`. + * + * @augments Mesh + */ +class QuadMesh extends Mesh { + + /** + * Constructs a new quad mesh. + * + * @param {?Material} [material=null] - The material to render the quad mesh with. + */ + constructor( material = null ) { + + super( _geometry, material ); + + /** + * The camera to render the quad mesh with. + * + * @type {OrthographicCamera} + * @readonly + */ + this.camera = _camera; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isQuadMesh = true; + + } + + /** + * Async version of `render()`. + * + * @async + * @param {Renderer} renderer - The renderer. + * @return {Promise} A Promise that resolves when the render has been finished. + */ + async renderAsync( renderer ) { + + return renderer.renderAsync( this, _camera ); + + } + + /** + * Renders the quad mesh + * + * @param {Renderer} renderer - The renderer. + */ + render( renderer ) { + + renderer.render( this, _camera ); + + } + +} + +const _size$1 = /*@__PURE__*/ new Vector2(); + +/** + * `RTTNode` takes another node and uses it with a `QuadMesh` to render into a texture (RTT). + * This module is especially relevant in context of post processing where certain nodes require + * texture input for their effects. With the helper function `convertToTexture()` which is based + * on this module, the node system can automatically ensure texture input if required. + * + * @augments TextureNode + */ +class RTTNode extends TextureNode { + + static get type() { + + return 'RTTNode'; + + } + + /** + * Constructs a new RTT node. + * + * @param {Node} node - The node to render a texture with. + * @param {?number} [width=null] - The width of the internal render target. If not width is applied, the render target is automatically resized. + * @param {?number} [height=null] - The height of the internal render target. + * @param {Object} [options={type:HalfFloatType}] - The options for the internal render target. + */ + constructor( node, width = null, height = null, options = { type: HalfFloatType } ) { + + const renderTarget = new RenderTarget( width, height, options ); + + super( renderTarget.texture, uv$1() ); + + /** + * The node to render a texture with. + * + * @type {Node} + */ + this.node = node; + + /** + * The width of the internal render target. + * If not width is applied, the render target is automatically resized. + * + * @type {?number} + * @default null + */ + this.width = width; + + /** + * The height of the internal render target. + * + * @type {?number} + * @default null + */ + this.height = height; + + /** + * The pixel ratio + * + * @type {number} + * @default 1 + */ + this.pixelRatio = 1; + + /** + * The render target + * + * @type {RenderTarget} + */ + this.renderTarget = renderTarget; + + /** + * Whether the texture requires an update or not. + * + * @type {boolean} + * @default true + */ + this.textureNeedsUpdate = true; + + /** + * Whether the texture should automatically be updated or not. + * + * @type {boolean} + * @default true + */ + this.autoUpdate = true; + + /** + * The node which is used with the quad mesh for RTT. + * + * @private + * @type {Node} + * @default null + */ + this._rttNode = null; + + /** + * The internal quad mesh for RTT. + * + * @private + * @type {QuadMesh} + */ + this._quadMesh = new QuadMesh( new NodeMaterial() ); + + /** + * The `updateBeforeType` is set to `NodeUpdateType.RENDER` since the node updates + * the texture once per render in its {@link RTTNode#updateBefore} method. + * + * @type {string} + * @default 'render' + */ + this.updateBeforeType = NodeUpdateType.RENDER; + + } + + /** + * Whether the internal render target should automatically be resized or not. + * + * @type {boolean} + * @readonly + * @default true + */ + get autoResize() { + + return this.width === null; + + } + + setup( builder ) { + + this._rttNode = this.node.context( builder.getSharedContext() ); + this._quadMesh.material.name = 'RTT'; + this._quadMesh.material.needsUpdate = true; + + return super.setup( builder ); + + } + + /** + * Sets the size of the internal render target + * + * @param {number} width - The width to set. + * @param {number} height - The width to set. + */ + setSize( width, height ) { + + this.width = width; + this.height = height; + + const effectiveWidth = width * this.pixelRatio; + const effectiveHeight = height * this.pixelRatio; + + this.renderTarget.setSize( effectiveWidth, effectiveHeight ); + + this.textureNeedsUpdate = true; + + } + + /** + * Sets the pixel ratio. This will also resize the render target. + * + * @param {number} pixelRatio - The pixel ratio to set. + */ + setPixelRatio( pixelRatio ) { + + this.pixelRatio = pixelRatio; + + this.setSize( this.width, this.height ); + + } + + updateBefore( { renderer } ) { + + if ( this.textureNeedsUpdate === false && this.autoUpdate === false ) return; + + this.textureNeedsUpdate = false; + + // + + if ( this.autoResize === true ) { + + const pixelRatio = renderer.getPixelRatio(); + const size = renderer.getSize( _size$1 ); + + const effectiveWidth = size.width * pixelRatio; + const effectiveHeight = size.height * pixelRatio; + + if ( effectiveWidth !== this.renderTarget.width || effectiveHeight !== this.renderTarget.height ) { + + this.renderTarget.setSize( effectiveWidth, effectiveHeight ); + + this.textureNeedsUpdate = true; + + } + + } + + // + + this._quadMesh.material.fragmentNode = this._rttNode; + + // + + const currentRenderTarget = renderer.getRenderTarget(); + + renderer.setRenderTarget( this.renderTarget ); + + this._quadMesh.render( renderer ); + + renderer.setRenderTarget( currentRenderTarget ); + + } + + clone() { + + const newNode = new TextureNode( this.value, this.uvNode, this.levelNode ); + newNode.sampler = this.sampler; + newNode.referenceNode = this; + + return newNode; + + } + +} + +/** + * TSL function for creating a RTT node. + * + * @tsl + * @function + * @param {Node} node - The node to render a texture with. + * @param {?number} [width=null] - The width of the internal render target. If not width is applied, the render target is automatically resized. + * @param {?number} [height=null] - The height of the internal render target. + * @param {Object} [options={type:HalfFloatType}] - The options for the internal render target. + * @returns {RTTNode} + */ +const rtt = ( node, ...params ) => nodeObject( new RTTNode( nodeObject( node ), ...params ) ); + +/** + * TSL function for converting nodes to textures nodes. + * + * @tsl + * @function + * @param {Node} node - The node to render a texture with. + * @param {?number} [width=null] - The width of the internal render target. If not width is applied, the render target is automatically resized. + * @param {?number} [height=null] - The height of the internal render target. + * @param {Object} [options={type:HalfFloatType}] - The options for the internal render target. + * @returns {RTTNode} + */ +const convertToTexture = ( node, ...params ) => { + + if ( node.isTextureNode ) return node; + if ( node.isPassNode ) return node.getTextureNode(); + + return rtt( node, ...params ); + +}; + +/** + * Computes a position in view space based on a fragment's screen position expressed as uv coordinates, the fragments + * depth value and the camera's inverse projection matrix. + * + * @tsl + * @function + * @param {Node} screenPosition - The fragment's screen position expressed as uv coordinates. + * @param {Node} depth - The fragment's depth value. + * @param {Node} projectionMatrixInverse - The camera's inverse projection matrix. + * @return {Node} The fragments position in view space. + */ +const getViewPosition = /*@__PURE__*/ Fn( ( [ screenPosition, depth, projectionMatrixInverse ], builder ) => { + + let clipSpacePosition; + + if ( builder.renderer.coordinateSystem === WebGPUCoordinateSystem ) { + + screenPosition = vec2( screenPosition.x, screenPosition.y.oneMinus() ).mul( 2.0 ).sub( 1.0 ); + clipSpacePosition = vec4( vec3( screenPosition, depth ), 1.0 ); + + } else { + + clipSpacePosition = vec4( vec3( screenPosition.x, screenPosition.y.oneMinus(), depth ).mul( 2.0 ).sub( 1.0 ), 1.0 ); + + } + + const viewSpacePosition = vec4( projectionMatrixInverse.mul( clipSpacePosition ) ); + + return viewSpacePosition.xyz.div( viewSpacePosition.w ); + +} ); + +/** + * Computes a screen position expressed as uv coordinates based on a fragment's position in view space + * and the camera's projection matrix + * + * @tsl + * @function + * @param {Node} viewPosition - The fragments position in view space. + * @param {Node} projectionMatrix - The camera's projection matrix. + * @return {Node} The fragment's screen position expressed as uv coordinates. + */ +const getScreenPosition = /*@__PURE__*/ Fn( ( [ viewPosition, projectionMatrix ] ) => { + + const sampleClipPos = projectionMatrix.mul( vec4( viewPosition, 1.0 ) ); + const sampleUv = sampleClipPos.xy.div( sampleClipPos.w ).mul( 0.5 ).add( 0.5 ).toVar(); + return vec2( sampleUv.x, sampleUv.y.oneMinus() ); + +} ); + +/** + * Computes a normal vector based on depth data. Can be used as a fallback when no normal render + * target is available or if flat surface normals are required. + * + * @tsl + * @function + * @param {Node} uv - The texture coordinate. + * @param {DepthTexture} depthTexture - The depth texture. + * @param {Node} projectionMatrixInverse - The camera's inverse projection matrix. + * @return {Node} The computed normal vector. + */ +const getNormalFromDepth = /*@__PURE__*/ Fn( ( [ uv, depthTexture, projectionMatrixInverse ] ) => { + + const size = textureSize( textureLoad( depthTexture ) ); + const p = ivec2( uv.mul( size ) ).toVar(); + + const c0 = textureLoad( depthTexture, p ).toVar(); + + const l2 = textureLoad( depthTexture, p.sub( ivec2( 2, 0 ) ) ).toVar(); + const l1 = textureLoad( depthTexture, p.sub( ivec2( 1, 0 ) ) ).toVar(); + const r1 = textureLoad( depthTexture, p.add( ivec2( 1, 0 ) ) ).toVar(); + const r2 = textureLoad( depthTexture, p.add( ivec2( 2, 0 ) ) ).toVar(); + const b2 = textureLoad( depthTexture, p.add( ivec2( 0, 2 ) ) ).toVar(); + const b1 = textureLoad( depthTexture, p.add( ivec2( 0, 1 ) ) ).toVar(); + const t1 = textureLoad( depthTexture, p.sub( ivec2( 0, 1 ) ) ).toVar(); + const t2 = textureLoad( depthTexture, p.sub( ivec2( 0, 2 ) ) ).toVar(); + + const dl = abs( sub( float( 2 ).mul( l1 ).sub( l2 ), c0 ) ).toVar(); + const dr = abs( sub( float( 2 ).mul( r1 ).sub( r2 ), c0 ) ).toVar(); + const db = abs( sub( float( 2 ).mul( b1 ).sub( b2 ), c0 ) ).toVar(); + const dt = abs( sub( float( 2 ).mul( t1 ).sub( t2 ), c0 ) ).toVar(); + + const ce = getViewPosition( uv, c0, projectionMatrixInverse ).toVar(); + + const dpdx = dl.lessThan( dr ).select( ce.sub( getViewPosition( uv.sub( vec2( float( 1 ).div( size.x ), 0 ) ), l1, projectionMatrixInverse ) ), ce.negate().add( getViewPosition( uv.add( vec2( float( 1 ).div( size.x ), 0 ) ), r1, projectionMatrixInverse ) ) ); + const dpdy = db.lessThan( dt ).select( ce.sub( getViewPosition( uv.add( vec2( 0, float( 1 ).div( size.y ) ) ), b1, projectionMatrixInverse ) ), ce.negate().add( getViewPosition( uv.sub( vec2( 0, float( 1 ).div( size.y ) ) ), t1, projectionMatrixInverse ) ) ); + + return normalize( cross( dpdx, dpdy ) ); + +} ); + +/** + * Class representing a node that samples a value using a provided callback function. + * + * @extends Node + */ +class SampleNode extends Node { + + /** + * Returns the type of the node. + * + * @type {string} + * @readonly + * @static + */ + static get type() { + + return 'SampleNode'; + + } + + /** + * Creates an instance of SampleNode. + * + * @param {Function} callback - The function to be called when sampling. Should accept a UV node and return a value. + */ + constructor( callback ) { + + super(); + + this.callback = callback; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSampleNode = true; + + } + + /** + * Sets up the node by sampling with the default UV accessor. + * + * @returns {Node} The result of the callback function when called with the UV node. + */ + setup() { + + return this.sample( uv$1() ); + + } + + /** + * Calls the callback function with the provided UV node. + * + * @param {Node} uv - The UV node or value to be passed to the callback. + * @returns {Node} The result of the callback function. + */ + sample( uv ) { + + return this.callback( uv ); + + } + +} + +/** + * Helper function to create a SampleNode wrapped as a node object. + * + * @function + * @param {Function} callback - The function to be called when sampling. Should accept a UV node and return a value. + * @returns {SampleNode} The created SampleNode instance wrapped as a node object. + */ +const sample = ( callback ) => nodeObject( new SampleNode( callback ) ); + +/** + * This special type of instanced buffer attribute is intended for compute shaders. + * In earlier three.js versions it was only possible to update attribute data + * on the CPU via JavaScript and then upload the data to the GPU. With the + * new material system and renderer it is now possible to use compute shaders + * to compute the data for an attribute more efficiently on the GPU. + * + * The idea is to create an instance of this class and provide it as an input + * to {@link StorageBufferNode}. + * + * Note: This type of buffer attribute can only be used with `WebGPURenderer`. + * + * @augments InstancedBufferAttribute + */ +class StorageInstancedBufferAttribute extends InstancedBufferAttribute { + + /** + * Constructs a new storage instanced buffer attribute. + * + * @param {number|TypedArray} count - The item count. It is also valid to pass a typed array as an argument. + * The subsequent parameters are then obsolete. + * @param {number} itemSize - The item size. + * @param {TypedArray.constructor} [typeClass=Float32Array] - A typed array constructor. + */ + constructor( count, itemSize, typeClass = Float32Array ) { + + const array = ArrayBuffer.isView( count ) ? count : new typeClass( count * itemSize ); + + super( array, itemSize ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isStorageInstancedBufferAttribute = true; + + } + +} + +/** + * This special type of buffer attribute is intended for compute shaders. + * In earlier three.js versions it was only possible to update attribute data + * on the CPU via JavaScript and then upload the data to the GPU. With the + * new material system and renderer it is now possible to use compute shaders + * to compute the data for an attribute more efficiently on the GPU. + * + * The idea is to create an instance of this class and provide it as an input + * to {@link StorageBufferNode}. + * + * Note: This type of buffer attribute can only be used with `WebGPURenderer`. + * + * @augments BufferAttribute + */ +class StorageBufferAttribute extends BufferAttribute { + + /** + * Constructs a new storage buffer attribute. + * + * @param {number|TypedArray} count - The item count. It is also valid to pass a typed array as an argument. + * The subsequent parameters are then obsolete. + * @param {number} itemSize - The item size. + * @param {TypedArray.constructor} [typeClass=Float32Array] - A typed array constructor. + */ + constructor( count, itemSize, typeClass = Float32Array ) { + + const array = ArrayBuffer.isView( count ) ? count : new typeClass( count * itemSize ); + + super( array, itemSize ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isStorageBufferAttribute = true; + + } + +} + +/** + * TSL function for creating a storage buffer node with a configured `StorageBufferAttribute`. + * + * @tsl + * @function + * @param {number|TypedArray} count - The data count. It is also valid to pass a typed array as an argument. + * @param {string|Struct} [type='float'] - The data type. + * @returns {StorageBufferNode} + */ +const attributeArray = ( count, type = 'float' ) => { + + let itemSize, typedArray; + + if ( type.isStruct === true ) { + + itemSize = type.layout.getLength(); + typedArray = getTypedArrayFromType( 'float' ); + + } else { + + itemSize = getLengthFromType( type ); + typedArray = getTypedArrayFromType( type ); + + } + + const buffer = new StorageBufferAttribute( count, itemSize, typedArray ); + const node = storage( buffer, type, count ); + + return node; + +}; + +/** + * TSL function for creating a storage buffer node with a configured `StorageInstancedBufferAttribute`. + * + * @tsl + * @function + * @param {number|TypedArray} count - The data count. It is also valid to pass a typed array as an argument. + * @param {string|Struct} [type='float'] - The data type. + * @returns {StorageBufferNode} + */ +const instancedArray = ( count, type = 'float' ) => { + + let itemSize, typedArray; + + if ( type.isStruct === true ) { + + itemSize = type.layout.getLength(); + typedArray = getTypedArrayFromType( 'float' ); + + } else { + + itemSize = getLengthFromType( type ); + typedArray = getTypedArrayFromType( type ); + + } + + const buffer = new StorageInstancedBufferAttribute( count, itemSize, typedArray ); + const node = storage( buffer, type, count ); + + return node; + +}; + +/** + * A node for representing the uv coordinates of points. + * + * Can only be used with a WebGL backend. In WebGPU, point + * primitives always have the size of one pixel and can thus + * can't be used as sprite-like objects that display textures. + * + * @augments Node + */ +class PointUVNode extends Node { + + static get type() { + + return 'PointUVNode'; + + } + + /** + * Constructs a new point uv node. + */ + constructor() { + + super( 'vec2' ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isPointUVNode = true; + + } + + generate( /*builder*/ ) { + + return 'vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y )'; + + } + +} + +/** + * TSL object that represents the uv coordinates of points. + * + * @tsl + * @type {PointUVNode} + */ +const pointUV = /*@__PURE__*/ nodeImmutable( PointUVNode ); + +const _e1 = /*@__PURE__*/ new Euler(); +const _m1 = /*@__PURE__*/ new Matrix4(); + +/** + * This module allows access to a collection of scene properties. The following predefined TSL objects + * are available for easier use: + * + * - `backgroundBlurriness`: A node that represents the scene's background blurriness. + * - `backgroundIntensity`: A node that represents the scene's background intensity. + * - `backgroundRotation`: A node that represents the scene's background rotation. + * + * @augments Node + */ +class SceneNode extends Node { + + static get type() { + + return 'SceneNode'; + + } + + /** + * Constructs a new scene node. + * + * @param {('backgroundBlurriness'|'backgroundIntensity'|'backgroundRotation')} scope - The scope defines the type of scene property that is accessed. + * @param {?Scene} [scene=null] - A reference to the scene. + */ + constructor( scope = SceneNode.BACKGROUND_BLURRINESS, scene = null ) { + + super(); + + /** + * The scope defines the type of scene property that is accessed. + * + * @type {('backgroundBlurriness'|'backgroundIntensity'|'backgroundRotation')} + */ + this.scope = scope; + + /** + * A reference to the scene that is going to be accessed. + * + * @type {?Scene} + * @default null + */ + this.scene = scene; + + } + + /** + * Depending on the scope, the method returns a different type of node that represents + * the respective scene property. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {Node} The output node. + */ + setup( builder ) { + + const scope = this.scope; + const scene = this.scene !== null ? this.scene : builder.scene; + + let output; + + if ( scope === SceneNode.BACKGROUND_BLURRINESS ) { + + output = reference( 'backgroundBlurriness', 'float', scene ); + + } else if ( scope === SceneNode.BACKGROUND_INTENSITY ) { + + output = reference( 'backgroundIntensity', 'float', scene ); + + } else if ( scope === SceneNode.BACKGROUND_ROTATION ) { + + output = uniform( 'mat4' ).label( 'backgroundRotation' ).setGroup( renderGroup ).onRenderUpdate( () => { + + const background = scene.background; + + if ( background !== null && background.isTexture && background.mapping !== UVMapping ) { + + _e1.copy( scene.backgroundRotation ); + + // accommodate left-handed frame + _e1.x *= -1; _e1.y *= -1; _e1.z *= -1; + + _m1.makeRotationFromEuler( _e1 ); + + } else { + + _m1.identity(); + + } + + return _m1; + + } ); + + } else { + + console.error( 'THREE.SceneNode: Unknown scope:', scope ); + + } + + return output; + + } + +} + +SceneNode.BACKGROUND_BLURRINESS = 'backgroundBlurriness'; +SceneNode.BACKGROUND_INTENSITY = 'backgroundIntensity'; +SceneNode.BACKGROUND_ROTATION = 'backgroundRotation'; + +/** + * TSL object that represents the scene's background blurriness. + * + * @tsl + * @type {SceneNode} + */ +const backgroundBlurriness = /*@__PURE__*/ nodeImmutable( SceneNode, SceneNode.BACKGROUND_BLURRINESS ); + +/** + * TSL object that represents the scene's background intensity. + * + * @tsl + * @type {SceneNode} + */ +const backgroundIntensity = /*@__PURE__*/ nodeImmutable( SceneNode, SceneNode.BACKGROUND_INTENSITY ); + +/** + * TSL object that represents the scene's background rotation. + * + * @tsl + * @type {SceneNode} + */ +const backgroundRotation = /*@__PURE__*/ nodeImmutable( SceneNode, SceneNode.BACKGROUND_ROTATION ); + +/** + * This special version of a texture node can be used to + * write data into a storage texture with a compute shader. + * + * ```js + * const storageTexture = new THREE.StorageTexture( width, height ); + * + * const computeTexture = Fn( ( { storageTexture } ) => { + * + * const posX = instanceIndex.mod( width ); + * const posY = instanceIndex.div( width ); + * const indexUV = uvec2( posX, posY ); + * + * // generate RGB values + * + * const r = 1; + * const g = 1; + * const b = 1; + * + * textureStore( storageTexture, indexUV, vec4( r, g, b, 1 ) ).toWriteOnly(); + * + * } ); + * + * const computeNode = computeTexture( { storageTexture } ).compute( width * height ); + * renderer.computeAsync( computeNode ); + * ``` + * + * This node can only be used with a WebGPU backend. + * + * @augments TextureNode + */ +class StorageTextureNode extends TextureNode { + + static get type() { + + return 'StorageTextureNode'; + + } + + /** + * Constructs a new storage texture node. + * + * @param {StorageTexture} value - The storage texture. + * @param {Node} uvNode - The uv node. + * @param {?Node} [storeNode=null] - The value node that should be stored in the texture. + */ + constructor( value, uvNode, storeNode = null ) { + + super( value, uvNode ); + + /** + * The value node that should be stored in the texture. + * + * @type {?Node} + * @default null + */ + this.storeNode = storeNode; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isStorageTextureNode = true; + + /** + * The access type of the texture node. + * + * @type {string} + * @default 'writeOnly' + */ + this.access = NodeAccess.WRITE_ONLY; + + } + + /** + * Overwrites the default implementation to return a fixed value `'storageTexture'`. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The input type. + */ + getInputType( /*builder*/ ) { + + return 'storageTexture'; + + } + + setup( builder ) { + + super.setup( builder ); + + const properties = builder.getNodeProperties( this ); + properties.storeNode = this.storeNode; + + return properties; + + } + + /** + * Defines the node access. + * + * @param {string} value - The node access. + * @return {StorageTextureNode} A reference to this node. + */ + setAccess( value ) { + + this.access = value; + return this; + + } + + /** + * Generates the code snippet of the storage node. If no `storeNode` + * is defined, the texture node is generated as normal texture. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {string} output - The current output. + * @return {string} The generated code snippet. + */ + generate( builder, output ) { + + let snippet; + + if ( this.storeNode !== null ) { + + snippet = this.generateStore( builder ); + + } else { + + snippet = super.generate( builder, output ); + + } + + return snippet; + + } + + /** + * Convenience method for configuring a read/write node access. + * + * @return {StorageTextureNode} A reference to this node. + */ + toReadWrite() { + + return this.setAccess( NodeAccess.READ_WRITE ); + + } + + /** + * Convenience method for configuring a read-only node access. + * + * @return {StorageTextureNode} A reference to this node. + */ + toReadOnly() { + + return this.setAccess( NodeAccess.READ_ONLY ); + + } + + /** + * Convenience method for configuring a write-only node access. + * + * @return {StorageTextureNode} A reference to this node. + */ + toWriteOnly() { + + return this.setAccess( NodeAccess.WRITE_ONLY ); + + } + + /** + * Generates the code snippet of the storage texture node. + * + * @param {NodeBuilder} builder - The current node builder. + */ + generateStore( builder ) { + + const properties = builder.getNodeProperties( this ); + + const { uvNode, storeNode, depthNode } = properties; + + const textureProperty = super.generate( builder, 'property' ); + const uvSnippet = uvNode.build( builder, 'uvec2' ); + const storeSnippet = storeNode.build( builder, 'vec4' ); + const depthSnippet = depthNode ? depthNode.build( builder, 'int' ) : null; + + const snippet = builder.generateTextureStore( builder, textureProperty, uvSnippet, depthSnippet, storeSnippet ); + + builder.addLineFlowCode( snippet, this ); + + } + + clone() { + + const newNode = super.clone(); + newNode.storeNode = this.storeNode; + return newNode; + + } + +} + +/** + * TSL function for creating a storage texture node. + * + * @tsl + * @function + * @param {StorageTexture} value - The storage texture. + * @param {?Node} uvNode - The uv node. + * @param {?Node} [storeNode=null] - The value node that should be stored in the texture. + * @returns {StorageTextureNode} + */ +const storageTexture = /*@__PURE__*/ nodeProxy( StorageTextureNode ).setParameterLength( 1, 3 ); + + +/** + * TODO: Explain difference to `storageTexture()`. + * + * @tsl + * @function + * @param {StorageTexture} value - The storage texture. + * @param {Node} uvNode - The uv node. + * @param {?Node} [storeNode=null] - The value node that should be stored in the texture. + * @returns {StorageTextureNode} + */ +const textureStore = ( value, uvNode, storeNode ) => { + + const node = storageTexture( value, uvNode, storeNode ); + + if ( storeNode !== null ) node.toStack(); + + return node; + +}; + +const normal = Fn( ( { texture, uv } ) => { + + const epsilon = 0.0001; + + const ret = vec3().toVar(); + + If( uv.x.lessThan( epsilon ), () => { + + ret.assign( vec3( 1, 0, 0 ) ); + + } ).ElseIf( uv.y.lessThan( epsilon ), () => { + + ret.assign( vec3( 0, 1, 0 ) ); + + } ).ElseIf( uv.z.lessThan( epsilon ), () => { + + ret.assign( vec3( 0, 0, 1 ) ); + + } ).ElseIf( uv.x.greaterThan( 1 - epsilon ), () => { + + ret.assign( vec3( -1, 0, 0 ) ); + + } ).ElseIf( uv.y.greaterThan( 1 - epsilon ), () => { + + ret.assign( vec3( 0, -1, 0 ) ); + + } ).ElseIf( uv.z.greaterThan( 1 - epsilon ), () => { + + ret.assign( vec3( 0, 0, -1 ) ); + + } ).Else( () => { + + const step = 0.01; + + const x = texture.sample( uv.add( vec3( - step, 0.0, 0.0 ) ) ).r.sub( texture.sample( uv.add( vec3( step, 0.0, 0.0 ) ) ).r ); + const y = texture.sample( uv.add( vec3( 0.0, - step, 0.0 ) ) ).r.sub( texture.sample( uv.add( vec3( 0.0, step, 0.0 ) ) ).r ); + const z = texture.sample( uv.add( vec3( 0.0, 0.0, - step ) ) ).r.sub( texture.sample( uv.add( vec3( 0.0, 0.0, step ) ) ).r ); + + ret.assign( vec3( x, y, z ) ); + + } ); + + return ret.normalize(); + +} ); + +/** + * This type of uniform node represents a 3D texture. + * + * @augments TextureNode + */ +class Texture3DNode extends TextureNode { + + static get type() { + + return 'Texture3DNode'; + + } + + /** + * Constructs a new 3D texture node. + * + * @param {Data3DTexture} value - The 3D texture. + * @param {?Node} [uvNode=null] - The uv node. + * @param {?Node} [levelNode=null] - The level node. + */ + constructor( value, uvNode = null, levelNode = null ) { + + super( value, uvNode, levelNode ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isTexture3DNode = true; + + } + + /** + * Overwrites the default implementation to return a fixed value `'texture3D'`. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The input type. + */ + getInputType( /*builder*/ ) { + + return 'texture3D'; + + } + + /** + * Returns a default uv node which is in context of 3D textures a three-dimensional + * uv node. + * + * @return {Node} The default uv node. + */ + getDefaultUV() { + + return vec3( 0.5, 0.5, 0.5 ); + + } + + /** + * Overwritten with an empty implementation since the `updateMatrix` flag is ignored + * for 3D textures. The uv transformation matrix is not applied to 3D textures. + * + * @param {boolean} value - The update toggle. + */ + setUpdateMatrix( /*value*/ ) { } // Ignore .updateMatrix for 3d TextureNode + + /** + * Overwrites the default implementation to return the unmodified uv node. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {Node} uvNode - The uv node to setup. + * @return {Node} The unmodified uv node. + */ + setupUV( builder, uvNode ) { + + const texture = this.value; + + if ( builder.isFlipY() && ( texture.isRenderTargetTexture === true || texture.isFramebufferTexture === true ) ) { + + if ( this.sampler ) { + + uvNode = uvNode.flipY(); + + } else { + + uvNode = uvNode.setY( int( textureSize( this, this.levelNode ).y ).sub( uvNode.y ).sub( 1 ) ); + + } + + } + + return uvNode; + + } + + /** + * Generates the uv code snippet. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {Node} uvNode - The uv node to generate code for. + * @return {string} The generated code snippet. + */ + generateUV( builder, uvNode ) { + + return uvNode.build( builder, 'vec3' ); + + } + + /** + * TODO. + * + * @param {Node} uvNode - The uv node . + * @return {Node} TODO. + */ + normal( uvNode ) { + + return normal( { texture: this, uv: uvNode } ); + + } + +} + +/** + * TSL function for creating a 3D texture node. + * + * @tsl + * @function + * @param {Data3DTexture} value - The 3D texture. + * @param {?Node} [uvNode=null] - The uv node. + * @param {?Node} [levelNode=null] - The level node. + * @returns {Texture3DNode} + */ +const texture3D = /*@__PURE__*/ nodeProxy( Texture3DNode ).setParameterLength( 1, 3 ); + +/** + * A special type of reference node that allows to link values in + * `userData` fields to node objects. + * ```js + * sprite.userData.rotation = 1; // stores individual rotation per sprite + * + * const material = new THREE.SpriteNodeMaterial(); + * material.rotationNode = userData( 'rotation', 'float' ); + * ``` + * Since `UserDataNode` is extended from {@link ReferenceNode}, the node value + * will automatically be updated when the `rotation` user data field changes. + * + * @augments ReferenceNode + */ +class UserDataNode extends ReferenceNode { + + static get type() { + + return 'UserDataNode'; + + } + + /** + * Constructs a new user data node. + * + * @param {string} property - The property name that should be referenced by the node. + * @param {string} inputType - The node data type of the reference. + * @param {?Object} [userData=null] - A reference to the `userData` object. If not provided, the `userData` property of the 3D object that uses the node material is evaluated. + */ + constructor( property, inputType, userData = null ) { + + super( property, inputType, userData ); + + /** + * A reference to the `userData` object. If not provided, the `userData` + * property of the 3D object that uses the node material is evaluated. + * + * @type {?Object} + * @default null + */ + this.userData = userData; + + } + + /** + * Overwritten to make sure {@link ReferenceNode#reference} points to the correct + * `userData` field. + * + * @param {(NodeFrame|NodeBuilder)} state - The current state to evaluate. + * @return {Object} A reference to the `userData` field. + */ + updateReference( state ) { + + this.reference = this.userData !== null ? this.userData : state.object.userData; + + return this.reference; + + } + +} + +/** + * TSL function for creating a user data node. + * + * @tsl + * @function + * @param {string} name - The property name that should be referenced by the node. + * @param {string} inputType - The node data type of the reference. + * @param {?Object} userData - A reference to the `userData` object. If not provided, the `userData` property of the 3D object that uses the node material is evaluated. + * @returns {UserDataNode} + */ +const userData = ( name, inputType, userData ) => nodeObject( new UserDataNode( name, inputType, userData ) ); + +const _objectData = new WeakMap(); + +/** + * A node for representing motion or velocity vectors. Foundation + * for advanced post processing effects like motion blur or TRAA. + * + * The node keeps track of the model, view and projection matrices + * of the previous frame and uses them to compute offsets in NDC space. + * These offsets represent the final velocity. + * + * @augments TempNode + */ +class VelocityNode extends TempNode { + + static get type() { + + return 'VelocityNode'; + + } + + /** + * Constructs a new vertex color node. + */ + constructor() { + + super( 'vec2' ); + + /** + * The current projection matrix. + * + * @type {?Matrix4} + * @default null + */ + this.projectionMatrix = null; + + /** + * Overwritten since velocity nodes are updated per object. + * + * @type {string} + * @default 'object' + */ + this.updateType = NodeUpdateType.OBJECT; + + /** + * Overwritten since velocity nodes save data after the update. + * + * @type {string} + * @default 'object' + */ + this.updateAfterType = NodeUpdateType.OBJECT; + + /** + * Uniform node representing the previous model matrix in world space. + * + * @type {UniformNode} + * @default null + */ + this.previousModelWorldMatrix = uniform( new Matrix4() ); + + /** + * Uniform node representing the previous projection matrix. + * + * @type {UniformNode} + * @default null + */ + this.previousProjectionMatrix = uniform( new Matrix4() ).setGroup( renderGroup ); + + /** + * Uniform node representing the previous view matrix. + * + * @type {UniformNode} + * @default null + */ + this.previousCameraViewMatrix = uniform( new Matrix4() ); + + } + + /** + * Sets the given projection matrix. + * + * @param {Matrix4} projectionMatrix - The projection matrix to set. + */ + setProjectionMatrix( projectionMatrix ) { + + this.projectionMatrix = projectionMatrix; + + } + + /** + * Updates velocity specific uniforms. + * + * @param {NodeFrame} frame - A reference to the current node frame. + */ + update( { frameId, camera, object } ) { + + const previousModelMatrix = getPreviousMatrix( object ); + + this.previousModelWorldMatrix.value.copy( previousModelMatrix ); + + // + + const cameraData = getData( camera ); + + if ( cameraData.frameId !== frameId ) { + + cameraData.frameId = frameId; + + if ( cameraData.previousProjectionMatrix === undefined ) { + + cameraData.previousProjectionMatrix = new Matrix4(); + cameraData.previousCameraViewMatrix = new Matrix4(); + + cameraData.currentProjectionMatrix = new Matrix4(); + cameraData.currentCameraViewMatrix = new Matrix4(); + + cameraData.previousProjectionMatrix.copy( this.projectionMatrix || camera.projectionMatrix ); + cameraData.previousCameraViewMatrix.copy( camera.matrixWorldInverse ); + + } else { + + cameraData.previousProjectionMatrix.copy( cameraData.currentProjectionMatrix ); + cameraData.previousCameraViewMatrix.copy( cameraData.currentCameraViewMatrix ); + + } + + cameraData.currentProjectionMatrix.copy( this.projectionMatrix || camera.projectionMatrix ); + cameraData.currentCameraViewMatrix.copy( camera.matrixWorldInverse ); + + this.previousProjectionMatrix.value.copy( cameraData.previousProjectionMatrix ); + this.previousCameraViewMatrix.value.copy( cameraData.previousCameraViewMatrix ); + + } + + } + + /** + * Overwritten to updated velocity specific uniforms. + * + * @param {NodeFrame} frame - A reference to the current node frame. + */ + updateAfter( { object } ) { + + getPreviousMatrix( object ).copy( object.matrixWorld ); + + } + + /** + * Implements the velocity computation based on the previous and current vertex data. + * + * @param {NodeBuilder} builder - A reference to the current node builder. + * @return {Node} The motion vector. + */ + setup( /*builder*/ ) { + + const projectionMatrix = ( this.projectionMatrix === null ) ? cameraProjectionMatrix : uniform( this.projectionMatrix ); + + const previousModelViewMatrix = this.previousCameraViewMatrix.mul( this.previousModelWorldMatrix ); + + const clipPositionCurrent = projectionMatrix.mul( modelViewMatrix ).mul( positionLocal ); + const clipPositionPrevious = this.previousProjectionMatrix.mul( previousModelViewMatrix ).mul( positionPrevious ); + + const ndcPositionCurrent = clipPositionCurrent.xy.div( clipPositionCurrent.w ); + const ndcPositionPrevious = clipPositionPrevious.xy.div( clipPositionPrevious.w ); + + const velocity = sub( ndcPositionCurrent, ndcPositionPrevious ); + + return velocity; + + } + +} + +function getData( object ) { + + let objectData = _objectData.get( object ); + + if ( objectData === undefined ) { + + objectData = {}; + _objectData.set( object, objectData ); + + } + + return objectData; + +} + +function getPreviousMatrix( object, index = 0 ) { + + const objectData = getData( object ); + + let matrix = objectData[ index ]; + + if ( matrix === undefined ) { + + objectData[ index ] = matrix = new Matrix4(); + objectData[ index ].copy( object.matrixWorld ); + + } + + return matrix; + +} + +/** + * TSL object that represents the velocity of a render pass. + * + * @tsl + * @type {VelocityNode} + */ +const velocity = /*@__PURE__*/ nodeImmutable( VelocityNode ); + +/** + * Computes a grayscale value for the given RGB color value. + * + * @tsl + * @function + * @param {Node} color - The color value to compute the grayscale for. + * @return {Node} The grayscale color. + */ +const grayscale = /*@__PURE__*/ Fn( ( [ color ] ) => { + + return luminance( color.rgb ); + +} ); + +/** + * Super-saturates or desaturates the given RGB color. + * + * @tsl + * @function + * @param {Node} color - The input color. + * @param {Node} [adjustment=1] - Specifies the amount of the conversion. A value under `1` desaturates the color, a value over `1` super-saturates it. + * @return {Node} The saturated color. + */ +const saturation = /*@__PURE__*/ Fn( ( [ color, adjustment = float( 1 ) ] ) => { + + return adjustment.mix( luminance( color.rgb ), color.rgb ); + +} ); + +/** + * Selectively enhance the intensity of less saturated RGB colors. Can result + * in a more natural and visually appealing image with enhanced color depth + * compared to {@link ColorAdjustment#saturation}. + * + * @tsl + * @function + * @param {Node} color - The input color. + * @param {Node} [adjustment=1] - Controls the intensity of the vibrance effect. + * @return {Node} The updated color. + */ +const vibrance = /*@__PURE__*/ Fn( ( [ color, adjustment = float( 1 ) ] ) => { + + const average = add( color.r, color.g, color.b ).div( 3.0 ); + + const mx = color.r.max( color.g.max( color.b ) ); + const amt = mx.sub( average ).mul( adjustment ).mul( -3 ); + + return mix( color.rgb, mx, amt ); + +} ); + +/** + * Updates the hue component of the given RGB color while preserving its luminance and saturation. + * + * @tsl + * @function + * @param {Node} color - The input color. + * @param {Node} [adjustment=1] - Defines the degree of hue rotation in radians. A positive value rotates the hue clockwise, while a negative value rotates it counterclockwise. + * @return {Node} The updated color. + */ +const hue = /*@__PURE__*/ Fn( ( [ color, adjustment = float( 1 ) ] ) => { + + const k = vec3( 0.57735, 0.57735, 0.57735 ); + + const cosAngle = adjustment.cos(); + + return vec3( color.rgb.mul( cosAngle ).add( k.cross( color.rgb ).mul( adjustment.sin() ).add( k.mul( dot( k, color.rgb ).mul( cosAngle.oneMinus() ) ) ) ) ); + +} ); + +/** + * Computes the luminance for the given RGB color value. + * + * @tsl + * @function + * @param {Node} color - The color value to compute the luminance for. + * @param {?Node} luminanceCoefficients - The luminance coefficients. By default predefined values of the current working color space are used. + * @return {Node} The luminance. + */ +const luminance = ( + color, + luminanceCoefficients = vec3( ColorManagement.getLuminanceCoefficients( new Vector3() ) ) +) => dot( color, luminanceCoefficients ); + +/** + * Color Decision List (CDL) v1.2 + * + * Compact representation of color grading information, defined by slope, offset, power, and + * saturation. The CDL should be typically be given input in a log space (such as LogC, ACEScc, + * or AgX Log), and will return output in the same space. Output may require clamping >=0. + * + * @tsl + * @function + * @param {Node} color Input (-Infinity < input < +Infinity) + * @param {Node} slope Slope (0 ≤ slope < +Infinity) + * @param {Node} offset Offset (-Infinity < offset < +Infinity; typically -1 < offset < 1) + * @param {Node} power Power (0 < power < +Infinity) + * @param {Node} saturation Saturation (0 ≤ saturation < +Infinity; typically 0 ≤ saturation < 4) + * @param {Node} luminanceCoefficients Luminance coefficients for saturation term, typically Rec. 709 + * @return {Node} Output, -Infinity < output < +Infinity + * + * References: + * - ASC CDL v1.2 + * - {@link https://blender.stackexchange.com/a/55239/43930} + * - {@link https://docs.acescentral.com/specifications/acescc/} + */ +const cdl = /*@__PURE__*/ Fn( ( [ + color, + slope = vec3( 1 ), + offset = vec3( 0 ), + power = vec3( 1 ), + saturation = float( 1 ), + // ASC CDL v1.2 explicitly requires Rec. 709 luminance coefficients. + luminanceCoefficients = vec3( ColorManagement.getLuminanceCoefficients( new Vector3(), LinearSRGBColorSpace ) ) +] ) => { + + // NOTE: The ASC CDL v1.2 defines a [0, 1] clamp on the slope+offset term, and another on the + // saturation term. Per the ACEScc specification and Filament, limits may be omitted to support + // values outside [0, 1], requiring a workaround for negative values in the power expression. + + const luma = color.rgb.dot( vec3( luminanceCoefficients ) ); + + const v = max$1( color.rgb.mul( slope ).add( offset ), 0.0 ).toVar(); + const pv = v.pow( power ).toVar(); + + If( v.r.greaterThan( 0.0 ), () => { v.r.assign( pv.r ); } ); // eslint-disable-line + If( v.g.greaterThan( 0.0 ), () => { v.g.assign( pv.g ); } ); // eslint-disable-line + If( v.b.greaterThan( 0.0 ), () => { v.b.assign( pv.b ); } ); // eslint-disable-line + + v.assign( luma.add( v.sub( luma ).mul( saturation ) ) ); + + return vec4( v.rgb, color.a ); + +} ); + +/** + * Represents a posterize effect which reduces the number of colors + * in an image, resulting in a more blocky and stylized appearance. + * + * @augments TempNode + */ +class PosterizeNode extends TempNode { + + static get type() { + + return 'PosterizeNode'; + + } + + /** + * Constructs a new posterize node. + * + * @param {Node} sourceNode - The input color. + * @param {Node} stepsNode - Controls the intensity of the posterization effect. A lower number results in a more blocky appearance. + */ + constructor( sourceNode, stepsNode ) { + + super(); + + /** + * The input color. + * + * @type {Node} + */ + this.sourceNode = sourceNode; + + /** + * Controls the intensity of the posterization effect. A lower number results in a more blocky appearance. + * + * @type {Node} + */ + this.stepsNode = stepsNode; + + } + + setup() { + + const { sourceNode, stepsNode } = this; + + return sourceNode.mul( stepsNode ).floor().div( stepsNode ); + + } + +} + +/** + * TSL function for creating a posterize node. + * + * @tsl + * @function + * @param {Node} sourceNode - The input color. + * @param {Node} stepsNode - Controls the intensity of the posterization effect. A lower number results in a more blocky appearance. + * @returns {PosterizeNode} + */ +const posterize = /*@__PURE__*/ nodeProxy( PosterizeNode ).setParameterLength( 2 ); + +const _size = /*@__PURE__*/ new Vector2(); + +/** + * Represents the texture of a pass node. + * + * @augments TextureNode + */ +class PassTextureNode extends TextureNode { + + static get type() { + + return 'PassTextureNode'; + + } + + /** + * Constructs a new pass texture node. + * + * @param {PassNode} passNode - The pass node. + * @param {Texture} texture - The output texture. + */ + constructor( passNode, texture ) { + + super( texture ); + + /** + * A reference to the pass node. + * + * @type {PassNode} + */ + this.passNode = passNode; + + this.setUpdateMatrix( false ); + + } + + setup( builder ) { + + if ( builder.object.isQuadMesh ) this.passNode.build( builder ); + + return super.setup( builder ); + + } + + clone() { + + return new this.constructor( this.passNode, this.value ); + + } + +} + +/** + * An extension of `PassTextureNode` which allows to manage more than one + * internal texture. Relevant for the `getPreviousTexture()` related API. + * + * @augments PassTextureNode + */ +class PassMultipleTextureNode extends PassTextureNode { + + static get type() { + + return 'PassMultipleTextureNode'; + + } + + /** + * Constructs a new pass texture node. + * + * @param {PassNode} passNode - The pass node. + * @param {string} textureName - The output texture name. + * @param {boolean} [previousTexture=false] - Whether previous frame data should be used or not. + */ + constructor( passNode, textureName, previousTexture = false ) { + + // null is passed to the super call since this class does not + // use an external texture for rendering pass data into. Instead + // the texture is managed by the pass node itself + + super( passNode, null ); + + /** + * The output texture name. + * + * @type {string} + */ + this.textureName = textureName; + + /** + * Whether previous frame data should be used or not. + * + * @type {boolean} + */ + this.previousTexture = previousTexture; + + } + + /** + * Updates the texture reference of this node. + */ + updateTexture() { + + this.value = this.previousTexture ? this.passNode.getPreviousTexture( this.textureName ) : this.passNode.getTexture( this.textureName ); + + } + + setup( builder ) { + + this.updateTexture(); + + return super.setup( builder ); + + } + + clone() { + + const newNode = new this.constructor( this.passNode, this.textureName, this.previousTexture ); + newNode.uvNode = this.uvNode; + newNode.levelNode = this.levelNode; + newNode.biasNode = this.biasNode; + newNode.sampler = this.sampler; + newNode.depthNode = this.depthNode; + newNode.compareNode = this.compareNode; + newNode.gradNode = this.gradNode; + + return newNode; + + } + +} + +/** + * Represents a render pass (sometimes called beauty pass) in context of post processing. + * This pass produces a render for the given scene and camera and can provide multiple outputs + * via MRT for further processing. + * + * ```js + * const postProcessing = new PostProcessing( renderer ); + * + * const scenePass = pass( scene, camera ); + * + * postProcessing.outputNode = scenePass; + * ``` + * + * @augments TempNode + */ +class PassNode extends TempNode { + + static get type() { + + return 'PassNode'; + + } + + /** + * Constructs a new pass node. + * + * @param {('color'|'depth')} scope - The scope of the pass. The scope determines whether the node outputs color or depth. + * @param {Scene} scene - A reference to the scene. + * @param {Camera} camera - A reference to the camera. + * @param {Object} options - Options for the internal render target. + */ + constructor( scope, scene, camera, options = {} ) { + + super( 'vec4' ); + + /** + * The scope of the pass. The scope determines whether the node outputs color or depth. + * + * @type {('color'|'depth')} + */ + this.scope = scope; + + /** + * A reference to the scene. + * + * @type {Scene} + */ + this.scene = scene; + + /** + * A reference to the camera. + * + * @type {Camera} + */ + this.camera = camera; + + /** + * Options for the internal render target. + * + * @type {Object} + */ + this.options = options; + + /** + * The pass's pixel ratio. Will be kept automatically kept in sync with the renderer's pixel ratio. + * + * @private + * @type {number} + * @default 1 + */ + this._pixelRatio = 1; + + /** + * The pass's pixel width. Will be kept automatically kept in sync with the renderer's width. + * @private + * @type {number} + * @default 1 + */ + this._width = 1; + + /** + * The pass's pixel height. Will be kept automatically kept in sync with the renderer's height. + * @private + * @type {number} + * @default 1 + */ + this._height = 1; + + const depthTexture = new DepthTexture(); + depthTexture.isRenderTargetTexture = true; + //depthTexture.type = FloatType; + depthTexture.name = 'depth'; + + const renderTarget = new RenderTarget( this._width * this._pixelRatio, this._height * this._pixelRatio, { type: HalfFloatType, ...options, } ); + renderTarget.texture.name = 'output'; + renderTarget.depthTexture = depthTexture; + + /** + * The pass's render target. + * + * @type {RenderTarget} + */ + this.renderTarget = renderTarget; + + /** + * A dictionary holding the internal result textures. + * + * @private + * @type {Object} + */ + this._textures = { + output: renderTarget.texture, + depth: depthTexture + }; + + /** + * A dictionary holding the internal texture nodes. + * + * @private + * @type {Object} + */ + this._textureNodes = {}; + + /** + * A dictionary holding the internal depth nodes. + * + * @private + * @type {Object} + */ + this._linearDepthNodes = {}; + + /** + * A dictionary holding the internal viewZ nodes. + * + * @private + * @type {Object} + */ + this._viewZNodes = {}; + + /** + * A dictionary holding the texture data of the previous frame. + * Used for computing velocity/motion vectors. + * + * @private + * @type {Object} + */ + this._previousTextures = {}; + + /** + * A dictionary holding the texture nodes of the previous frame. + * Used for computing velocity/motion vectors. + * + * @private + * @type {Object} + */ + this._previousTextureNodes = {}; + + /** + * The `near` property of the camera as a uniform. + * + * @private + * @type {UniformNode} + */ + this._cameraNear = uniform( 0 ); + + /** + * The `far` property of the camera as a uniform. + * + * @private + * @type {UniformNode} + */ + this._cameraFar = uniform( 0 ); + + /** + * A MRT node configuring the MRT settings. + * + * @private + * @type {?MRTNode} + * @default null + */ + this._mrt = null; + + this._layers = null; + + this._resolution = 1; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isPassNode = true; + + /** + * The `updateBeforeType` is set to `NodeUpdateType.FRAME` since the node renders the + * scene once per frame in its {@link PassNode#updateBefore} method. + * + * @type {string} + * @default 'frame' + */ + this.updateBeforeType = NodeUpdateType.FRAME; + + /** + * This flag is used for global cache. + * + * @type {boolean} + * @default true + */ + this.global = true; + + } + + /** + * Sets the resolution for the pass. + * The resolution is a factor that is multiplied with the renderer's width and height. + * + * @param {number} resolution - The resolution to set. A value of `1` means full resolution. + * @return {PassNode} A reference to this pass. + */ + setResolution( resolution ) { + + this._resolution = resolution; + + return this; + + } + + /** + * Gets the current resolution of the pass. + * + * @return {number} The current resolution. A value of `1` means full resolution. + * @default 1 + */ + getResolution() { + + return this._resolution; + + } + + setLayers( layers ) { + + this._layers = layers; + + return this; + + } + + getLayers() { + + return this._layers; + + } + + /** + * Sets the given MRT node to setup MRT for this pass. + * + * @param {MRTNode} mrt - The MRT object. + * @return {PassNode} A reference to this pass. + */ + setMRT( mrt ) { + + this._mrt = mrt; + + return this; + + } + + /** + * Returns the current MRT node. + * + * @return {MRTNode} The current MRT node. + */ + getMRT() { + + return this._mrt; + + } + + /** + * Returns the texture for the given output name. + * + * @param {string} name - The output name to get the texture for. + * @return {Texture} The texture. + */ + getTexture( name ) { + + let texture = this._textures[ name ]; + + if ( texture === undefined ) { + + const refTexture = this.renderTarget.texture; + + texture = refTexture.clone(); + texture.name = name; + + this._textures[ name ] = texture; + + this.renderTarget.textures.push( texture ); + + } + + return texture; + + } + + /** + * Returns the texture holding the data of the previous frame for the given output name. + * + * @param {string} name - The output name to get the texture for. + * @return {Texture} The texture holding the data of the previous frame. + */ + getPreviousTexture( name ) { + + let texture = this._previousTextures[ name ]; + + if ( texture === undefined ) { + + texture = this.getTexture( name ).clone(); + + this._previousTextures[ name ] = texture; + + } + + return texture; + + } + + /** + * Switches current and previous textures for the given output name. + * + * @param {string} name - The output name. + */ + toggleTexture( name ) { + + const prevTexture = this._previousTextures[ name ]; + + if ( prevTexture !== undefined ) { + + const texture = this._textures[ name ]; + + const index = this.renderTarget.textures.indexOf( texture ); + this.renderTarget.textures[ index ] = prevTexture; + + this._textures[ name ] = prevTexture; + this._previousTextures[ name ] = texture; + + this._textureNodes[ name ].updateTexture(); + this._previousTextureNodes[ name ].updateTexture(); + + } + + } + + /** + * Returns the texture node for the given output name. + * + * @param {string} [name='output'] - The output name to get the texture node for. + * @return {TextureNode} The texture node. + */ + getTextureNode( name = 'output' ) { + + let textureNode = this._textureNodes[ name ]; + + if ( textureNode === undefined ) { + + textureNode = nodeObject( new PassMultipleTextureNode( this, name ) ); + textureNode.updateTexture(); + this._textureNodes[ name ] = textureNode; + + } + + return textureNode; + + } + + /** + * Returns the previous texture node for the given output name. + * + * @param {string} [name='output'] - The output name to get the previous texture node for. + * @return {TextureNode} The previous texture node. + */ + getPreviousTextureNode( name = 'output' ) { + + let textureNode = this._previousTextureNodes[ name ]; + + if ( textureNode === undefined ) { + + if ( this._textureNodes[ name ] === undefined ) this.getTextureNode( name ); + + textureNode = nodeObject( new PassMultipleTextureNode( this, name, true ) ); + textureNode.updateTexture(); + this._previousTextureNodes[ name ] = textureNode; + + } + + return textureNode; + + } + + /** + * Returns a viewZ node of this pass. + * + * @param {string} [name='depth'] - The output name to get the viewZ node for. In most cases the default `'depth'` can be used however the parameter exists for custom depth outputs. + * @return {Node} The viewZ node. + */ + getViewZNode( name = 'depth' ) { + + let viewZNode = this._viewZNodes[ name ]; + + if ( viewZNode === undefined ) { + + const cameraNear = this._cameraNear; + const cameraFar = this._cameraFar; + + this._viewZNodes[ name ] = viewZNode = perspectiveDepthToViewZ( this.getTextureNode( name ), cameraNear, cameraFar ); + + } + + return viewZNode; + + } + + /** + * Returns a linear depth node of this pass. + * + * @param {string} [name='depth'] - The output name to get the linear depth node for. In most cases the default `'depth'` can be used however the parameter exists for custom depth outputs. + * @return {Node} The linear depth node. + */ + getLinearDepthNode( name = 'depth' ) { + + let linearDepthNode = this._linearDepthNodes[ name ]; + + if ( linearDepthNode === undefined ) { + + const cameraNear = this._cameraNear; + const cameraFar = this._cameraFar; + const viewZNode = this.getViewZNode( name ); + + // TODO: just if ( builder.camera.isPerspectiveCamera ) + + this._linearDepthNodes[ name ] = linearDepthNode = viewZToOrthographicDepth( viewZNode, cameraNear, cameraFar ); + + } + + return linearDepthNode; + + } + + setup( { renderer } ) { + + this.renderTarget.samples = this.options.samples === undefined ? renderer.samples : this.options.samples; + + this.renderTarget.texture.type = renderer.getColorBufferType(); + + return this.scope === PassNode.COLOR ? this.getTextureNode() : this.getLinearDepthNode(); + + } + + updateBefore( frame ) { + + const { renderer } = frame; + const { scene } = this; + + let camera; + let pixelRatio; + + const outputRenderTarget = renderer.getOutputRenderTarget(); + + if ( outputRenderTarget && outputRenderTarget.isXRRenderTarget === true ) { + + pixelRatio = 1; + camera = renderer.xr.getCamera(); + + renderer.xr.updateCamera( camera ); + + _size.set( outputRenderTarget.width, outputRenderTarget.height ); + + } else { + + camera = this.camera; + pixelRatio = renderer.getPixelRatio(); + + renderer.getSize( _size ); + + } + + this._pixelRatio = pixelRatio; + + this.setSize( _size.width, _size.height ); + + const currentRenderTarget = renderer.getRenderTarget(); + const currentMRT = renderer.getMRT(); + const currentMask = camera.layers.mask; + + this._cameraNear.value = camera.near; + this._cameraFar.value = camera.far; + + if ( this._layers !== null ) { + + camera.layers.mask = this._layers.mask; + + } + + for ( const name in this._previousTextures ) { + + this.toggleTexture( name ); + + } + + renderer.setRenderTarget( this.renderTarget ); + renderer.setMRT( this._mrt ); + + renderer.render( scene, camera ); + + renderer.setRenderTarget( currentRenderTarget ); + renderer.setMRT( currentMRT ); + + camera.layers.mask = currentMask; + + } + + /** + * Sets the size of the pass's render target. Honors the pixel ratio. + * + * @param {number} width - The width to set. + * @param {number} height - The height to set. + */ + setSize( width, height ) { + + this._width = width; + this._height = height; + + const effectiveWidth = this._width * this._pixelRatio * this._resolution; + const effectiveHeight = this._height * this._pixelRatio * this._resolution; + + this.renderTarget.setSize( effectiveWidth, effectiveHeight ); + + } + + /** + * Sets the pixel ratio the pass's render target and updates the size. + * + * @param {number} pixelRatio - The pixel ratio to set. + */ + setPixelRatio( pixelRatio ) { + + this._pixelRatio = pixelRatio; + + this.setSize( this._width, this._height ); + + } + + /** + * Frees internal resources. Should be called when the node is no longer in use. + */ + dispose() { + + this.renderTarget.dispose(); + + } + + +} + +/** + * @static + * @type {'color'} + * @default 'color' + */ +PassNode.COLOR = 'color'; + +/** + * @static + * @type {'depth'} + * @default 'depth' + */ +PassNode.DEPTH = 'depth'; + +/** + * TSL function for creating a pass node. + * + * @tsl + * @function + * @param {Scene} scene - A reference to the scene. + * @param {Camera} camera - A reference to the camera. + * @param {Object} options - Options for the internal render target. + * @returns {PassNode} + */ +const pass = ( scene, camera, options ) => nodeObject( new PassNode( PassNode.COLOR, scene, camera, options ) ); + +/** + * TSL function for creating a pass texture node. + * + * @tsl + * @function + * @param {PassNode} pass - The pass node. + * @param {Texture} texture - The output texture. + * @returns {PassTextureNode} + */ +const passTexture = ( pass, texture ) => nodeObject( new PassTextureNode( pass, texture ) ); + +/** + * TSL function for creating a depth pass node. + * + * @tsl + * @function + * @param {Scene} scene - A reference to the scene. + * @param {Camera} camera - A reference to the camera. + * @param {Object} options - Options for the internal render target. + * @returns {PassNode} + */ +const depthPass = ( scene, camera, options ) => nodeObject( new PassNode( PassNode.DEPTH, scene, camera, options ) ); + +/** + * Represents a render pass for producing a toon outline effect on compatible objects. + * Only 3D objects with materials of type `MeshToonMaterial` and `MeshToonNodeMaterial` + * will receive the outline. + * + * ```js + * const postProcessing = new PostProcessing( renderer ); + * + * const scenePass = toonOutlinePass( scene, camera ); + * + * postProcessing.outputNode = scenePass; + * ``` + * @augments PassNode + */ +class ToonOutlinePassNode extends PassNode { + + static get type() { + + return 'ToonOutlinePassNode'; + + } + + /** + * Constructs a new outline pass node. + * + * @param {Scene} scene - A reference to the scene. + * @param {Camera} camera - A reference to the camera. + * @param {Node} colorNode - Defines the outline's color. + * @param {Node} thicknessNode - Defines the outline's thickness. + * @param {Node} alphaNode - Defines the outline's alpha. + */ + constructor( scene, camera, colorNode, thicknessNode, alphaNode ) { + + super( PassNode.COLOR, scene, camera ); + + /** + * Defines the outline's color. + * + * @type {Node} + */ + this.colorNode = colorNode; + + /** + * Defines the outline's thickness. + * + * @type {Node} + */ + this.thicknessNode = thicknessNode; + + /** + * Defines the outline's alpha. + * + * @type {Node} + */ + this.alphaNode = alphaNode; + + /** + * An internal material cache. + * + * @private + * @type {WeakMap} + */ + this._materialCache = new WeakMap(); + + } + + updateBefore( frame ) { + + const { renderer } = frame; + + const currentRenderObjectFunction = renderer.getRenderObjectFunction(); + + renderer.setRenderObjectFunction( ( object, scene, camera, geometry, material, group, lightsNode, clippingContext ) => { + + // only render outline for supported materials + + if ( material.isMeshToonMaterial || material.isMeshToonNodeMaterial ) { + + if ( material.wireframe === false ) { + + const outlineMaterial = this._getOutlineMaterial( material ); + renderer.renderObject( object, scene, camera, geometry, outlineMaterial, group, lightsNode, clippingContext ); + + } + + } + + // default + + renderer.renderObject( object, scene, camera, geometry, material, group, lightsNode, clippingContext ); + + } ); + + super.updateBefore( frame ); + + renderer.setRenderObjectFunction( currentRenderObjectFunction ); + + } + + /** + * Creates the material used for outline rendering. + * + * @private + * @return {NodeMaterial} The outline material. + */ + _createMaterial() { + + const material = new NodeMaterial(); + material.isMeshToonOutlineMaterial = true; + material.name = 'Toon_Outline'; + material.side = BackSide; + + // vertex node + + const outlineNormal = normalLocal.negate(); + const mvp = cameraProjectionMatrix.mul( modelViewMatrix ); + + const ratio = float( 1.0 ); // TODO: support outline thickness ratio for each vertex + const pos = mvp.mul( vec4( positionLocal, 1.0 ) ); + const pos2 = mvp.mul( vec4( positionLocal.add( outlineNormal ), 1.0 ) ); + const norm = normalize( pos.sub( pos2 ) ); // NOTE: subtract pos2 from pos because BackSide objectNormal is negative + + material.vertexNode = pos.add( norm.mul( this.thicknessNode ).mul( pos.w ).mul( ratio ) ); + + // color node + + material.colorNode = vec4( this.colorNode, this.alphaNode ); + + return material; + + } + + /** + * For the given toon material, this method returns a corresponding + * outline material. + * + * @private + * @param {(MeshToonMaterial|MeshToonNodeMaterial)} originalMaterial - The toon material. + * @return {NodeMaterial} The outline material. + */ + _getOutlineMaterial( originalMaterial ) { + + let outlineMaterial = this._materialCache.get( originalMaterial ); + + if ( outlineMaterial === undefined ) { + + outlineMaterial = this._createMaterial(); + + this._materialCache.set( originalMaterial, outlineMaterial ); + + } + + return outlineMaterial; + + } + +} + +/** + * TSL function for creating a toon outline pass node. + * + * @tsl + * @function + * @param {Scene} scene - A reference to the scene. + * @param {Camera} camera - A reference to the camera. + * @param {Color} color - Defines the outline's color. + * @param {number} [thickness=0.003] - Defines the outline's thickness. + * @param {number} [alpha=1] - Defines the outline's alpha. + * @returns {ToonOutlinePassNode} + */ +const toonOutlinePass = ( scene, camera, color = new Color( 0, 0, 0 ), thickness = 0.003, alpha = 1 ) => nodeObject( new ToonOutlinePassNode( scene, camera, nodeObject( color ), nodeObject( thickness ), nodeObject( alpha ) ) ); + +/** + * Linear tone mapping, exposure only. + * + * @tsl + * @function + * @param {Node} color - The color that should be tone mapped. + * @param {Node} exposure - The exposure. + * @return {Node} The tone mapped color. + */ +const linearToneMapping = /*@__PURE__*/ Fn( ( [ color, exposure ] ) => { + + return color.mul( exposure ).clamp(); + +} ).setLayout( { + name: 'linearToneMapping', + type: 'vec3', + inputs: [ + { name: 'color', type: 'vec3' }, + { name: 'exposure', type: 'float' } + ] +} ); + +/** + * Reinhard tone mapping. + * + * Reference: {@link https://www.cs.utah.edu/docs/techreports/2002/pdf/UUCS-02-001.pdf} + * + * @tsl + * @function + * @param {Node} color - The color that should be tone mapped. + * @param {Node} exposure - The exposure. + * @return {Node} The tone mapped color. + */ +const reinhardToneMapping = /*@__PURE__*/ Fn( ( [ color, exposure ] ) => { + + color = color.mul( exposure ); + + return color.div( color.add( 1.0 ) ).clamp(); + +} ).setLayout( { + name: 'reinhardToneMapping', + type: 'vec3', + inputs: [ + { name: 'color', type: 'vec3' }, + { name: 'exposure', type: 'float' } + ] +} ); + +/** + * Cineon tone mapping. + * + * Reference: {@link http://filmicworlds.com/blog/filmic-tonemapping-operators/} + * + * @tsl + * @function + * @param {Node} color - The color that should be tone mapped. + * @param {Node} exposure - The exposure. + * @return {Node} The tone mapped color. + */ +const cineonToneMapping = /*@__PURE__*/ Fn( ( [ color, exposure ] ) => { + + // filmic operator by Jim Hejl and Richard Burgess-Dawson + color = color.mul( exposure ); + color = color.sub( 0.004 ).max( 0.0 ); + + const a = color.mul( color.mul( 6.2 ).add( 0.5 ) ); + const b = color.mul( color.mul( 6.2 ).add( 1.7 ) ).add( 0.06 ); + + return a.div( b ).pow( 2.2 ); + +} ).setLayout( { + name: 'cineonToneMapping', + type: 'vec3', + inputs: [ + { name: 'color', type: 'vec3' }, + { name: 'exposure', type: 'float' } + ] +} ); + +// source: https://github.com/selfshadow/ltc_code/blob/master/webgl/shaders/ltc/ltc_blit.fs + +const RRTAndODTFit = /*@__PURE__*/ Fn( ( [ color ] ) => { + + const a = color.mul( color.add( 0.0245786 ) ).sub( 0.000090537 ); + const b = color.mul( color.add( 0.4329510 ).mul( 0.983729 ) ).add( 0.238081 ); + + return a.div( b ); + +} ); + +/** + * ACESFilmic tone mapping. + * + * Reference: {@link https://github.com/selfshadow/ltc_code/blob/master/webgl/shaders/ltc/ltc_blit.fs} + * + * @tsl + * @function + * @param {Node} color - The color that should be tone mapped. + * @param {Node} exposure - The exposure. + * @return {Node} The tone mapped color. + */ +const acesFilmicToneMapping = /*@__PURE__*/ Fn( ( [ color, exposure ] ) => { + + // sRGB => XYZ => D65_2_D60 => AP1 => RRT_SAT + const ACESInputMat = mat3( + 0.59719, 0.35458, 0.04823, + 0.07600, 0.90834, 0.01566, + 0.02840, 0.13383, 0.83777 + ); + + // ODT_SAT => XYZ => D60_2_D65 => sRGB + const ACESOutputMat = mat3( + 1.60475, -0.53108, -0.07367, + -0.10208, 1.10813, -605e-5, + -327e-5, -0.07276, 1.07602 + ); + + color = color.mul( exposure ).div( 0.6 ); + + color = ACESInputMat.mul( color ); + + // Apply RRT and ODT + color = RRTAndODTFit( color ); + + color = ACESOutputMat.mul( color ); + + // Clamp to [0, 1] + return color.clamp(); + +} ).setLayout( { + name: 'acesFilmicToneMapping', + type: 'vec3', + inputs: [ + { name: 'color', type: 'vec3' }, + { name: 'exposure', type: 'float' } + ] +} ); + +const LINEAR_REC2020_TO_LINEAR_SRGB = /*@__PURE__*/ mat3( vec3( 1.6605, -0.1246, -0.0182 ), vec3( -0.5876, 1.1329, -0.1006 ), vec3( -0.0728, -83e-4, 1.1187 ) ); +const LINEAR_SRGB_TO_LINEAR_REC2020 = /*@__PURE__*/ mat3( vec3( 0.6274, 0.0691, 0.0164 ), vec3( 0.3293, 0.9195, 0.0880 ), vec3( 0.0433, 0.0113, 0.8956 ) ); + +const agxDefaultContrastApprox = /*@__PURE__*/ Fn( ( [ x_immutable ] ) => { + + const x = vec3( x_immutable ).toVar(); + const x2 = vec3( x.mul( x ) ).toVar(); + const x4 = vec3( x2.mul( x2 ) ).toVar(); + + return float( 15.5 ).mul( x4.mul( x2 ) ).sub( mul( 40.14, x4.mul( x ) ) ).add( mul( 31.96, x4 ).sub( mul( 6.868, x2.mul( x ) ) ).add( mul( 0.4298, x2 ).add( mul( 0.1191, x ).sub( 0.00232 ) ) ) ); + +} ); + +/** + * AgX tone mapping. + * + * @tsl + * @function + * @param {Node} color - The color that should be tone mapped. + * @param {Node} exposure - The exposure. + * @return {Node} The tone mapped color. + */ +const agxToneMapping = /*@__PURE__*/ Fn( ( [ color, exposure ] ) => { + + const colortone = vec3( color ).toVar(); + const AgXInsetMatrix = mat3( vec3( 0.856627153315983, 0.137318972929847, 0.11189821299995 ), vec3( 0.0951212405381588, 0.761241990602591, 0.0767994186031903 ), vec3( 0.0482516061458583, 0.101439036467562, 0.811302368396859 ) ); + const AgXOutsetMatrix = mat3( vec3( 1.1271005818144368, -0.1413297634984383, -0.14132976349843826 ), vec3( -0.11060664309660323, 1.157823702216272, -0.11060664309660294 ), vec3( -0.016493938717834573, -0.016493938717834257, 1.2519364065950405 ) ); + const AgxMinEv = float( -12.47393 ); + const AgxMaxEv = float( 4.026069 ); + colortone.mulAssign( exposure ); + colortone.assign( LINEAR_SRGB_TO_LINEAR_REC2020.mul( colortone ) ); + colortone.assign( AgXInsetMatrix.mul( colortone ) ); + colortone.assign( max$1( colortone, 1e-10 ) ); + colortone.assign( log2( colortone ) ); + colortone.assign( colortone.sub( AgxMinEv ).div( AgxMaxEv.sub( AgxMinEv ) ) ); + colortone.assign( clamp( colortone, 0.0, 1.0 ) ); + colortone.assign( agxDefaultContrastApprox( colortone ) ); + colortone.assign( AgXOutsetMatrix.mul( colortone ) ); + colortone.assign( pow( max$1( vec3( 0.0 ), colortone ), vec3( 2.2 ) ) ); + colortone.assign( LINEAR_REC2020_TO_LINEAR_SRGB.mul( colortone ) ); + colortone.assign( clamp( colortone, 0.0, 1.0 ) ); + + return colortone; + +} ).setLayout( { + name: 'agxToneMapping', + type: 'vec3', + inputs: [ + { name: 'color', type: 'vec3' }, + { name: 'exposure', type: 'float' } + ] +} ); + +/** + * Neutral tone mapping. + * + * Reference: {@link https://modelviewer.dev/examples/tone-mapping} + * + * @tsl + * @function + * @param {Node} color - The color that should be tone mapped. + * @param {Node} exposure - The exposure. + * @return {Node} The tone mapped color. + */ +const neutralToneMapping = /*@__PURE__*/ Fn( ( [ color, exposure ] ) => { + + const StartCompression = float( 0.8 - 0.04 ); + const Desaturation = float( 0.15 ); + + color = color.mul( exposure ); + + const x = min$1( color.r, min$1( color.g, color.b ) ); + const offset = select( x.lessThan( 0.08 ), x.sub( mul( 6.25, x.mul( x ) ) ), 0.04 ); + + color.subAssign( offset ); + + const peak = max$1( color.r, max$1( color.g, color.b ) ); + + If( peak.lessThan( StartCompression ), () => { + + return color; + + } ); + + const d = sub( 1, StartCompression ); + const newPeak = sub( 1, d.mul( d ).div( peak.add( d.sub( StartCompression ) ) ) ); + color.mulAssign( newPeak.div( peak ) ); + const g = sub( 1, div( 1, Desaturation.mul( peak.sub( newPeak ) ).add( 1 ) ) ); + + return mix( color, vec3( newPeak ), g ); + +} ).setLayout( { + name: 'neutralToneMapping', + type: 'vec3', + inputs: [ + { name: 'color', type: 'vec3' }, + { name: 'exposure', type: 'float' } + ] +} ); + +/** + * This class represents native code sections. It is the base + * class for modules like {@link FunctionNode} which allows to implement + * functions with native shader languages. + * + * @augments Node + */ +class CodeNode extends Node { + + static get type() { + + return 'CodeNode'; + + } + + /** + * Constructs a new code node. + * + * @param {string} [code=''] - The native code. + * @param {Array} [includes=[]] - An array of includes. + * @param {('js'|'wgsl'|'glsl')} [language=''] - The used language. + */ + constructor( code = '', includes = [], language = '' ) { + + super( 'code' ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isCodeNode = true; + + /** + * This flag is used for global cache. + * + * @type {boolean} + * @default true + */ + this.global = true; + + /** + * The native code. + * + * @type {string} + * @default '' + */ + this.code = code; + + /** + * An array of includes + * + * @type {Array} + * @default [] + */ + this.includes = includes; + + /** + * The used language. + * + * @type {('js'|'wgsl'|'glsl')} + * @default '' + */ + this.language = language; + + } + + /** + * Sets the includes of this code node. + * + * @param {Array} includes - The includes to set. + * @return {CodeNode} A reference to this node. + */ + setIncludes( includes ) { + + this.includes = includes; + + return this; + + } + + /** + * Returns the includes of this code node. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {Array} The includes. + */ + getIncludes( /*builder*/ ) { + + return this.includes; + + } + + generate( builder ) { + + const includes = this.getIncludes( builder ); + + for ( const include of includes ) { + + include.build( builder ); + + } + + const nodeCode = builder.getCodeFromNode( this, this.getNodeType( builder ) ); + nodeCode.code = this.code; + + return nodeCode.code; + + } + + serialize( data ) { + + super.serialize( data ); + + data.code = this.code; + data.language = this.language; + + } + + deserialize( data ) { + + super.deserialize( data ); + + this.code = data.code; + this.language = data.language; + + } + +} + +/** + * TSL function for creating a code node. + * + * @tsl + * @function + * @param {string} [code] - The native code. + * @param {?Array} [includes=[]] - An array of includes. + * @param {?('js'|'wgsl'|'glsl')} [language=''] - The used language. + * @returns {CodeNode} + */ +const code = /*@__PURE__*/ nodeProxy( CodeNode ).setParameterLength( 1, 3 ); + +/** + * TSL function for creating a JS code node. + * + * @tsl + * @function + * @param {string} src - The native code. + * @param {Array} includes - An array of includes. + * @returns {CodeNode} + */ +const js = ( src, includes ) => code( src, includes, 'js' ); + +/** + * TSL function for creating a WGSL code node. + * + * @tsl + * @function + * @param {string} src - The native code. + * @param {Array} includes - An array of includes. + * @returns {CodeNode} + */ +const wgsl = ( src, includes ) => code( src, includes, 'wgsl' ); + +/** + * TSL function for creating a GLSL code node. + * + * @tsl + * @function + * @param {string} src - The native code. + * @param {Array} includes - An array of includes. + * @returns {CodeNode} + */ +const glsl = ( src, includes ) => code( src, includes, 'glsl' ); + +/** + * This class represents a native shader function. It can be used to implement + * certain aspects of a node material with native shader code. There are two predefined + * TSL functions for easier usage. + * + * - `wgslFn`: Creates a WGSL function node. + * - `glslFn`: Creates a GLSL function node. + * + * A basic example with one include looks like so: + * + * ```js + * const desaturateWGSLFn = wgslFn( ` + * fn desaturate( color:vec3 ) -> vec3 { + * let lum = vec3( 0.299, 0.587, 0.114 ); + * return vec3( dot( lum, color ) ); + * }` + *); + * const someWGSLFn = wgslFn( ` + * fn someFn( color:vec3 ) -> vec3 { + * return desaturate( color ); + * } + * `, [ desaturateWGSLFn ] ); + * material.colorNode = someWGSLFn( { color: texture( map ) } ); + *``` + * @augments CodeNode + */ +class FunctionNode extends CodeNode { + + static get type() { + + return 'FunctionNode'; + + } + + /** + * Constructs a new function node. + * + * @param {string} [code=''] - The native code. + * @param {Array} [includes=[]] - An array of includes. + * @param {('js'|'wgsl'|'glsl')} [language=''] - The used language. + */ + constructor( code = '', includes = [], language = '' ) { + + super( code, includes, language ); + + } + + getNodeType( builder ) { + + return this.getNodeFunction( builder ).type; + + } + + /** + * Returns the inputs of this function node. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {Array} The inputs. + */ + getInputs( builder ) { + + return this.getNodeFunction( builder ).inputs; + + } + + /** + * Returns the node function for this function node. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {NodeFunction} The node function. + */ + getNodeFunction( builder ) { + + const nodeData = builder.getDataFromNode( this ); + + let nodeFunction = nodeData.nodeFunction; + + if ( nodeFunction === undefined ) { + + nodeFunction = builder.parser.parseFunction( this.code ); + + nodeData.nodeFunction = nodeFunction; + + } + + return nodeFunction; + + } + + generate( builder, output ) { + + super.generate( builder ); + + const nodeFunction = this.getNodeFunction( builder ); + + const name = nodeFunction.name; + const type = nodeFunction.type; + + const nodeCode = builder.getCodeFromNode( this, type ); + + if ( name !== '' ) { + + // use a custom property name + + nodeCode.name = name; + + } + + const propertyName = builder.getPropertyName( nodeCode ); + + const code = this.getNodeFunction( builder ).getCode( propertyName ); + + nodeCode.code = code + '\n'; + + if ( output === 'property' ) { + + return propertyName; + + } else { + + return builder.format( `${ propertyName }()`, type, output ); + + } + + } + +} + +const nativeFn = ( code, includes = [], language = '' ) => { + + for ( let i = 0; i < includes.length; i ++ ) { + + const include = includes[ i ]; + + // TSL Function: glslFn, wgslFn + + if ( typeof include === 'function' ) { + + includes[ i ] = include.functionNode; + + } + + } + + const functionNode = nodeObject( new FunctionNode( code, includes, language ) ); + + const fn = ( ...params ) => functionNode.call( ...params ); + fn.functionNode = functionNode; + + return fn; + +}; + +const glslFn = ( code, includes ) => nativeFn( code, includes, 'glsl' ); +const wgslFn = ( code, includes ) => nativeFn( code, includes, 'wgsl' ); + +/** + * `ScriptableNode` uses this class to manage script inputs and outputs. + * + * @augments Node + */ +class ScriptableValueNode extends Node { + + static get type() { + + return 'ScriptableValueNode'; + + } + + /** + * Constructs a new scriptable node. + * + * @param {any} [value=null] - The value. + */ + constructor( value = null ) { + + super(); + + /** + * A reference to the value. + * + * @private + * @default null + */ + this._value = value; + + /** + * Depending on the type of `_value`, this property might cache parsed data. + * + * @private + * @default null + */ + this._cache = null; + + /** + * If this node represents an input, this property represents the input type. + * + * @type {?string} + * @default null + */ + this.inputType = null; + + /** + * If this node represents an output, this property represents the output type. + * + * @type {?string} + * @default null + */ + this.outputType = null; + + /** + * An event dispatcher for managing events. + * + * @type {EventDispatcher} + */ + this.events = new EventDispatcher(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isScriptableValueNode = true; + + } + + /** + * Whether this node represents an output or not. + * + * @type {boolean} + * @readonly + * @default true + */ + get isScriptableOutputNode() { + + return this.outputType !== null; + + } + + set value( val ) { + + if ( this._value === val ) return; + + if ( this._cache && this.inputType === 'URL' && this.value.value instanceof ArrayBuffer ) { + + URL.revokeObjectURL( this._cache ); + + this._cache = null; + + } + + this._value = val; + + this.events.dispatchEvent( { type: 'change' } ); + + this.refresh(); + + } + + /** + * The node's value. + * + * @type {any} + */ + get value() { + + return this._value; + + } + + /** + * Dispatches the `refresh` event. + */ + refresh() { + + this.events.dispatchEvent( { type: 'refresh' } ); + + } + + /** + * The `value` property usually represents a node or even binary data in form of array buffers. + * In this case, this method tries to return the actual value behind the complex type. + * + * @return {any} The value. + */ + getValue() { + + const value = this.value; + + if ( value && this._cache === null && this.inputType === 'URL' && value.value instanceof ArrayBuffer ) { + + this._cache = URL.createObjectURL( new Blob( [ value.value ] ) ); + + } else if ( value && value.value !== null && value.value !== undefined && ( + ( ( this.inputType === 'URL' || this.inputType === 'String' ) && typeof value.value === 'string' ) || + ( this.inputType === 'Number' && typeof value.value === 'number' ) || + ( this.inputType === 'Vector2' && value.value.isVector2 ) || + ( this.inputType === 'Vector3' && value.value.isVector3 ) || + ( this.inputType === 'Vector4' && value.value.isVector4 ) || + ( this.inputType === 'Color' && value.value.isColor ) || + ( this.inputType === 'Matrix3' && value.value.isMatrix3 ) || + ( this.inputType === 'Matrix4' && value.value.isMatrix4 ) + ) ) { + + return value.value; + + } + + return this._cache || value; + + } + + /** + * Overwritten since the node type is inferred from the value. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType( builder ) { + + return this.value && this.value.isNode ? this.value.getNodeType( builder ) : 'float'; + + } + + setup() { + + return this.value && this.value.isNode ? this.value : float(); + + } + + serialize( data ) { + + super.serialize( data ); + + if ( this.value !== null ) { + + if ( this.inputType === 'ArrayBuffer' ) { + + data.value = arrayBufferToBase64( this.value ); + + } else { + + data.value = this.value ? this.value.toJSON( data.meta ).uuid : null; + + } + + } else { + + data.value = null; + + } + + data.inputType = this.inputType; + data.outputType = this.outputType; + + } + + deserialize( data ) { + + super.deserialize( data ); + + let value = null; + + if ( data.value !== null ) { + + if ( data.inputType === 'ArrayBuffer' ) { + + value = base64ToArrayBuffer( data.value ); + + } else if ( data.inputType === 'Texture' ) { + + value = data.meta.textures[ data.value ]; + + } else { + + value = data.meta.nodes[ data.value ] || null; + + } + + } + + this.value = value; + + this.inputType = data.inputType; + this.outputType = data.outputType; + + } + +} + +/** + * TSL function for creating a scriptable value node. + * + * @tsl + * @function + * @param {any} [value] - The value. + * @returns {ScriptableValueNode} + */ +const scriptableValue = /*@__PURE__*/ nodeProxy( ScriptableValueNode ).setParameterLength( 1 ); + +/** + * A Map-like data structure for managing resources of scriptable nodes. + * + * @augments Map + */ +class Resources extends Map { + + get( key, callback = null, ...params ) { + + if ( this.has( key ) ) return super.get( key ); + + if ( callback !== null ) { + + const value = callback( ...params ); + this.set( key, value ); + return value; + + } + + } + +} + +class Parameters { + + constructor( scriptableNode ) { + + this.scriptableNode = scriptableNode; + + } + + get parameters() { + + return this.scriptableNode.parameters; + + } + + get layout() { + + return this.scriptableNode.getLayout(); + + } + + getInputLayout( id ) { + + return this.scriptableNode.getInputLayout( id ); + + } + + get( name ) { + + const param = this.parameters[ name ]; + const value = param ? param.getValue() : null; + + return value; + + } + +} + +/** + * Defines the resources (e.g. namespaces) of scriptable nodes. + * + * @type {Resources} + */ +const ScriptableNodeResources = new Resources(); + +/** + * This type of node allows to implement nodes with custom scripts. The script + * section is represented as an instance of `CodeNode` written with JavaScript. + * The script itself must adhere to a specific structure. + * + * - main(): Executed once by default and every time `node.needsUpdate` is set. + * - layout: The layout object defines the script's interface (inputs and outputs). + * + * ```js + * ScriptableNodeResources.set( 'TSL', TSL ); + * + * const scriptableNode = scriptable( js( ` + * layout = { + * outputType: 'node', + * elements: [ + * { name: 'source', inputType: 'node' }, + * ] + * }; + * + * const { mul, oscSine } = TSL; + * + * function main() { + * const source = parameters.get( 'source' ) || float(); + * return mul( source, oscSine() ) ); + * } + * + * ` ) ); + * + * scriptableNode.setParameter( 'source', color( 1, 0, 0 ) ); + * + * const material = new THREE.MeshBasicNodeMaterial(); + * material.colorNode = scriptableNode; + * ``` + * + * @augments Node + */ +class ScriptableNode extends Node { + + static get type() { + + return 'ScriptableNode'; + + } + + /** + * Constructs a new scriptable node. + * + * @param {?CodeNode} [codeNode=null] - The code node. + * @param {Object} [parameters={}] - The parameters definition. + */ + constructor( codeNode = null, parameters = {} ) { + + super(); + + /** + * The code node. + * + * @type {?CodeNode} + * @default null + */ + this.codeNode = codeNode; + + /** + * The parameters definition. + * + * @type {Object} + * @default {} + */ + this.parameters = parameters; + + this._local = new Resources(); + this._output = scriptableValue( null ); + this._outputs = {}; + this._source = this.source; + this._method = null; + this._object = null; + this._value = null; + this._needsOutputUpdate = true; + + this.onRefresh = this.onRefresh.bind( this ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isScriptableNode = true; + + } + + /** + * The source code of the scriptable node. + * + * @type {string} + */ + get source() { + + return this.codeNode ? this.codeNode.code : ''; + + } + + /** + * Sets the reference of a local script variable. + * + * @param {string} name - The variable name. + * @param {Object} value - The reference to set. + * @return {Resources} The resource map + */ + setLocal( name, value ) { + + return this._local.set( name, value ); + + } + + /** + * Gets the value of a local script variable. + * + * @param {string} name - The variable name. + * @return {Object} The value. + */ + getLocal( name ) { + + return this._local.get( name ); + + } + + /** + * Event listener for the `refresh` event. + */ + onRefresh() { + + this._refresh(); + + } + + /** + * Returns an input from the layout with the given id/name. + * + * @param {string} id - The id/name of the input. + * @return {Object} The element entry. + */ + getInputLayout( id ) { + + for ( const element of this.getLayout() ) { + + if ( element.inputType && ( element.id === id || element.name === id ) ) { + + return element; + + } + + } + + } + + /** + * Returns an output from the layout with the given id/name. + * + * @param {string} id - The id/name of the output. + * @return {Object} The element entry. + */ + getOutputLayout( id ) { + + for ( const element of this.getLayout() ) { + + if ( element.outputType && ( element.id === id || element.name === id ) ) { + + return element; + + } + + } + + } + + /** + * Defines a script output for the given name and value. + * + * @param {string} name - The name of the output. + * @param {Node} value - The node value. + * @return {ScriptableNode} A reference to this node. + */ + setOutput( name, value ) { + + const outputs = this._outputs; + + if ( outputs[ name ] === undefined ) { + + outputs[ name ] = scriptableValue( value ); + + } else { + + outputs[ name ].value = value; + + } + + return this; + + } + + /** + * Returns a script output for the given name. + * + * @param {string} name - The name of the output. + * @return {ScriptableValueNode} The node value. + */ + getOutput( name ) { + + return this._outputs[ name ]; + + } + + /** + * Returns a parameter for the given name + * + * @param {string} name - The name of the parameter. + * @return {ScriptableValueNode} The node value. + */ + getParameter( name ) { + + return this.parameters[ name ]; + + } + + /** + * Sets a value for the given parameter name. + * + * @param {string} name - The parameter name. + * @param {any} value - The parameter value. + * @return {ScriptableNode} A reference to this node. + */ + setParameter( name, value ) { + + const parameters = this.parameters; + + if ( value && value.isScriptableNode ) { + + this.deleteParameter( name ); + + parameters[ name ] = value; + parameters[ name ].getDefaultOutput().events.addEventListener( 'refresh', this.onRefresh ); + + } else if ( value && value.isScriptableValueNode ) { + + this.deleteParameter( name ); + + parameters[ name ] = value; + parameters[ name ].events.addEventListener( 'refresh', this.onRefresh ); + + } else if ( parameters[ name ] === undefined ) { + + parameters[ name ] = scriptableValue( value ); + parameters[ name ].events.addEventListener( 'refresh', this.onRefresh ); + + } else { + + parameters[ name ].value = value; + + } + + return this; + + } + + /** + * Returns the value of this node which is the value of + * the default output. + * + * @return {Node} The value. + */ + getValue() { + + return this.getDefaultOutput().getValue(); + + } + + /** + * Deletes a parameter from the script. + * + * @param {string} name - The parameter to remove. + * @return {ScriptableNode} A reference to this node. + */ + deleteParameter( name ) { + + let valueNode = this.parameters[ name ]; + + if ( valueNode ) { + + if ( valueNode.isScriptableNode ) valueNode = valueNode.getDefaultOutput(); + + valueNode.events.removeEventListener( 'refresh', this.onRefresh ); + + } + + return this; + + } + + /** + * Deletes all parameters from the script. + * + * @return {ScriptableNode} A reference to this node. + */ + clearParameters() { + + for ( const name of Object.keys( this.parameters ) ) { + + this.deleteParameter( name ); + + } + + this.needsUpdate = true; + + return this; + + } + + /** + * Calls a function from the script. + * + * @param {string} name - The function name. + * @param {...any} params - A list of parameters. + * @return {any} The result of the function call. + */ + call( name, ...params ) { + + const object = this.getObject(); + const method = object[ name ]; + + if ( typeof method === 'function' ) { + + return method( ...params ); + + } + + } + + /** + * Asynchronously calls a function from the script. + * + * @param {string} name - The function name. + * @param {...any} params - A list of parameters. + * @return {Promise} The result of the function call. + */ + async callAsync( name, ...params ) { + + const object = this.getObject(); + const method = object[ name ]; + + if ( typeof method === 'function' ) { + + return method.constructor.name === 'AsyncFunction' ? await method( ...params ) : method( ...params ); + + } + + } + + /** + * Overwritten since the node types is inferred from the script's output. + * + * @param {NodeBuilder} builder - The current node builder + * @return {string} The node type. + */ + getNodeType( builder ) { + + return this.getDefaultOutputNode().getNodeType( builder ); + + } + + /** + * Refreshes the script node. + * + * @param {?string} [output=null] - An optional output. + */ + refresh( output = null ) { + + if ( output !== null ) { + + this.getOutput( output ).refresh(); + + } else { + + this._refresh(); + + } + + } + + /** + * Returns an object representation of the script. + * + * @return {Object} The result object. + */ + getObject() { + + if ( this.needsUpdate ) this.dispose(); + if ( this._object !== null ) return this._object; + + // + + const refresh = () => this.refresh(); + const setOutput = ( id, value ) => this.setOutput( id, value ); + + const parameters = new Parameters( this ); + + const THREE = ScriptableNodeResources.get( 'THREE' ); + const TSL = ScriptableNodeResources.get( 'TSL' ); + + const method = this.getMethod(); + const params = [ parameters, this._local, ScriptableNodeResources, refresh, setOutput, THREE, TSL ]; + + this._object = method( ...params ); + + const layout = this._object.layout; + + if ( layout ) { + + if ( layout.cache === false ) { + + this._local.clear(); + + } + + // default output + this._output.outputType = layout.outputType || null; + + if ( Array.isArray( layout.elements ) ) { + + for ( const element of layout.elements ) { + + const id = element.id || element.name; + + if ( element.inputType ) { + + if ( this.getParameter( id ) === undefined ) this.setParameter( id, null ); + + this.getParameter( id ).inputType = element.inputType; + + } + + if ( element.outputType ) { + + if ( this.getOutput( id ) === undefined ) this.setOutput( id, null ); + + this.getOutput( id ).outputType = element.outputType; + + } + + } + + } + + } + + return this._object; + + } + + deserialize( data ) { + + super.deserialize( data ); + + for ( const name in this.parameters ) { + + let valueNode = this.parameters[ name ]; + + if ( valueNode.isScriptableNode ) valueNode = valueNode.getDefaultOutput(); + + valueNode.events.addEventListener( 'refresh', this.onRefresh ); + + } + + } + + /** + * Returns the layout of the script. + * + * @return {Object} The script's layout. + */ + getLayout() { + + return this.getObject().layout; + + } + + /** + * Returns default node output of the script. + * + * @return {Node} The default node output. + */ + getDefaultOutputNode() { + + const output = this.getDefaultOutput().value; + + if ( output && output.isNode ) { + + return output; + + } + + return float(); + + } + + /** + * Returns default output of the script. + * + * @return {ScriptableValueNode} The default output. + */ + getDefaultOutput() { + + return this._exec()._output; + + } + + /** + * Returns a function created from the node's script. + * + * @return {Function} The function representing the node's code. + */ + getMethod() { + + if ( this.needsUpdate ) this.dispose(); + if ( this._method !== null ) return this._method; + + // + + const parametersProps = [ 'parameters', 'local', 'global', 'refresh', 'setOutput', 'THREE', 'TSL' ]; + const interfaceProps = [ 'layout', 'init', 'main', 'dispose' ]; + + const properties = interfaceProps.join( ', ' ); + const declarations = 'var ' + properties + '; var output = {};\n'; + const returns = '\nreturn { ...output, ' + properties + ' };'; + + const code = declarations + this.codeNode.code + returns; + + // + + this._method = new Function( ...parametersProps, code ); + + return this._method; + + } + + /** + * Frees all internal resources. + */ + dispose() { + + if ( this._method === null ) return; + + if ( this._object && typeof this._object.dispose === 'function' ) { + + this._object.dispose(); + + } + + this._method = null; + this._object = null; + this._source = null; + this._value = null; + this._needsOutputUpdate = true; + this._output.value = null; + this._outputs = {}; + + } + + setup() { + + return this.getDefaultOutputNode(); + + } + + getCacheKey( force ) { + + const values = [ hashString( this.source ), this.getDefaultOutputNode().getCacheKey( force ) ]; + + for ( const param in this.parameters ) { + + values.push( this.parameters[ param ].getCacheKey( force ) ); + + } + + return hashArray( values ); + + } + + set needsUpdate( value ) { + + if ( value === true ) this.dispose(); + + } + + get needsUpdate() { + + return this.source !== this._source; + + } + + /** + * Executes the `main` function of the script. + * + * @private + * @return {ScriptableNode} A reference to this node. + */ + _exec() { + + if ( this.codeNode === null ) return this; + + if ( this._needsOutputUpdate === true ) { + + this._value = this.call( 'main' ); + + this._needsOutputUpdate = false; + + } + + this._output.value = this._value; + + return this; + + } + + /** + * Executes the refresh. + * + * @private + */ + _refresh() { + + this.needsUpdate = true; + + this._exec(); + + this._output.refresh(); + + } + +} + +/** + * TSL function for creating a scriptable node. + * + * @tsl + * @function + * @param {CodeNode} [codeNode] - The code node. + * @param {?Object} [parameters={}] - The parameters definition. + * @returns {ScriptableNode} + */ +const scriptable = /*@__PURE__*/ nodeProxy( ScriptableNode ).setParameterLength( 1, 2 ); + +/** + * Returns a node that represents the `z` coordinate in view space + * for the current fragment. It's a different representation of the + * default depth value. + * + * This value can be part of a computation that defines how the fog + * density increases when moving away from the camera. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {Node} The viewZ node. + */ +function getViewZNode( builder ) { + + let viewZ; + + const getViewZ = builder.context.getViewZ; + + if ( getViewZ !== undefined ) { + + viewZ = getViewZ( this ); + + } + + return ( viewZ || positionView.z ).negate(); + +} + +/** + * Constructs a new range factor node. + * + * @tsl + * @function + * @param {Node} near - Defines the near value. + * @param {Node} far - Defines the far value. + */ +const rangeFogFactor = Fn( ( [ near, far ], builder ) => { + + const viewZ = getViewZNode( builder ); + + return smoothstep( near, far, viewZ ); + +} ); + +/** + * Represents an exponential squared fog. This type of fog gives + * a clear view near the camera and a faster than exponentially + * densening fog farther from the camera. + * + * @tsl + * @function + * @param {Node} density - Defines the fog density. + */ +const densityFogFactor = Fn( ( [ density ], builder ) => { + + const viewZ = getViewZNode( builder ); + + return density.mul( density, viewZ, viewZ ).negate().exp().oneMinus(); + +} ); + +/** + * This class can be used to configure a fog for the scene. + * Nodes of this type are assigned to `Scene.fogNode`. + * + * @tsl + * @function + * @param {Node} color - Defines the color of the fog. + * @param {Node} factor - Defines how the fog is factored in the scene. + */ +const fog = Fn( ( [ color, factor ] ) => { + + return vec4( factor.toFloat().mix( output.rgb, color.toVec3() ), output.a ); + +} ); + +// Deprecated + +/** + * @tsl + * @function + * @deprecated since r171. Use `fog( color, rangeFogFactor( near, far ) )` instead. + * + * @param {Node} color + * @param {Node} near + * @param {Node} far + * @returns {Function} + */ +function rangeFog( color, near, far ) { // @deprecated, r171 + + console.warn( 'THREE.TSL: "rangeFog( color, near, far )" is deprecated. Use "fog( color, rangeFogFactor( near, far ) )" instead.' ); + return fog( color, rangeFogFactor( near, far ) ); + +} + +/** + * @tsl + * @function + * @deprecated since r171. Use `fog( color, densityFogFactor( density ) )` instead. + * + * @param {Node} color + * @param {Node} density + * @returns {Function} + */ +function densityFog( color, density ) { // @deprecated, r171 + + console.warn( 'THREE.TSL: "densityFog( color, density )" is deprecated. Use "fog( color, densityFogFactor( density ) )" instead.' ); + return fog( color, densityFogFactor( density ) ); + +} + +let min = null; +let max = null; + +/** + * `RangeNode` generates random instanced attribute data in a defined range. + * An exemplary use case for this utility node is to generate random per-instance + * colors: + * ```js + * const material = new MeshBasicNodeMaterial(); + * material.colorNode = range( new Color( 0x000000 ), new Color( 0xFFFFFF ) ); + * const mesh = new InstancedMesh( geometry, material, count ); + * ``` + * @augments Node + */ +class RangeNode extends Node { + + static get type() { + + return 'RangeNode'; + + } + + /** + * Constructs a new range node. + * + * @param {Node} [minNode=float()] - A node defining the lower bound of the range. + * @param {Node} [maxNode=float()] - A node defining the upper bound of the range. + */ + constructor( minNode = float(), maxNode = float() ) { + + super(); + + /** + * A node defining the lower bound of the range. + * + * @type {Node} + * @default float() + */ + this.minNode = minNode; + + /** + * A node defining the upper bound of the range. + * + * @type {Node} + * @default float() + */ + this.maxNode = maxNode; + + } + + /** + * Returns the vector length which is computed based on the range definition. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {number} The vector length. + */ + getVectorLength( builder ) { + + const minLength = builder.getTypeLength( getValueType( this.minNode.value ) ); + const maxLength = builder.getTypeLength( getValueType( this.maxNode.value ) ); + + return minLength > maxLength ? minLength : maxLength; + + } + + /** + * This method is overwritten since the node type is inferred from range definition. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType( builder ) { + + return builder.object.count > 1 ? builder.getTypeFromLength( this.getVectorLength( builder ) ) : 'float'; + + } + + setup( builder ) { + + const object = builder.object; + + let output = null; + + if ( object.count > 1 ) { + + const minValue = this.minNode.value; + const maxValue = this.maxNode.value; + + const minLength = builder.getTypeLength( getValueType( minValue ) ); + const maxLength = builder.getTypeLength( getValueType( maxValue ) ); + + min = min || new Vector4(); + max = max || new Vector4(); + + min.setScalar( 0 ); + max.setScalar( 0 ); + + if ( minLength === 1 ) min.setScalar( minValue ); + else if ( minValue.isColor ) min.set( minValue.r, minValue.g, minValue.b, 1 ); + else min.set( minValue.x, minValue.y, minValue.z || 0, minValue.w || 0 ); + + if ( maxLength === 1 ) max.setScalar( maxValue ); + else if ( maxValue.isColor ) max.set( maxValue.r, maxValue.g, maxValue.b, 1 ); + else max.set( maxValue.x, maxValue.y, maxValue.z || 0, maxValue.w || 0 ); + + const stride = 4; + + const length = stride * object.count; + const array = new Float32Array( length ); + + for ( let i = 0; i < length; i ++ ) { + + const index = i % stride; + + const minElementValue = min.getComponent( index ); + const maxElementValue = max.getComponent( index ); + + array[ i ] = MathUtils.lerp( minElementValue, maxElementValue, Math.random() ); + + } + + const nodeType = this.getNodeType( builder ); + + if ( object.count <= 4096 ) { + + output = buffer( array, 'vec4', object.count ).element( instanceIndex ).convert( nodeType ); + + } else { + + // TODO: Improve anonymous buffer attribute creation removing this part + const bufferAttribute = new InstancedBufferAttribute( array, 4 ); + builder.geometry.setAttribute( '__range' + this.id, bufferAttribute ); + + output = instancedBufferAttribute( bufferAttribute ).convert( nodeType ); + + } + + } else { + + output = float( 0 ); + + } + + return output; + + } + +} + +/** + * TSL function for creating a range node. + * + * @tsl + * @function + * @param {Node} [minNode=float()] - A node defining the lower bound of the range. + * @param {Node} [maxNode=float()] - A node defining the upper bound of the range. + * @returns {RangeNode} + */ +const range = /*@__PURE__*/ nodeProxy( RangeNode ).setParameterLength( 2 ); + +/** + * `ComputeBuiltinNode` represents a compute-scope builtin value that expose information + * about the currently running dispatch and/or the device it is running on. + * + * This node can only be used with a WebGPU backend. + * + * @augments Node + */ +class ComputeBuiltinNode extends Node { + + static get type() { + + return 'ComputeBuiltinNode'; + + } + + /** + * Constructs a new compute builtin node. + * + * @param {string} builtinName - The built-in name. + * @param {string} nodeType - The node type. + */ + constructor( builtinName, nodeType ) { + + super( nodeType ); + + /** + * The built-in name. + * + * @private + * @type {string} + */ + this._builtinName = builtinName; + + } + + /** + * This method is overwritten since hash is derived from the built-in name. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The hash. + */ + getHash( builder ) { + + return this.getBuiltinName( builder ); + + } + + /** + * This method is overwritten since the node type is simply derived from `nodeType`.. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType( /*builder*/ ) { + + return this.nodeType; + + } + + /** + * Sets the builtin name. + * + * @param {string} builtinName - The built-in name. + * @return {ComputeBuiltinNode} A reference to this node. + */ + setBuiltinName( builtinName ) { + + this._builtinName = builtinName; + + return this; + + } + + /** + * Returns the builtin name. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The builtin name. + */ + getBuiltinName( /*builder*/ ) { + + return this._builtinName; + + } + + /** + * Whether the current node builder has the builtin or not. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {boolean} Whether the builder has the builtin or not. + */ + hasBuiltin( builder ) { + + return builder.hasBuiltin( this._builtinName ); + + } + + generate( builder, output ) { + + const builtinName = this.getBuiltinName( builder ); + const nodeType = this.getNodeType( builder ); + + if ( builder.shaderStage === 'compute' ) { + + return builder.format( builtinName, nodeType, output ); + + } else { + + console.warn( `ComputeBuiltinNode: Compute built-in value ${builtinName} can not be accessed in the ${builder.shaderStage} stage` ); + return builder.generateConst( nodeType ); + + } + + } + + serialize( data ) { + + super.serialize( data ); + + data.global = this.global; + data._builtinName = this._builtinName; + + } + + deserialize( data ) { + + super.deserialize( data ); + + this.global = data.global; + this._builtinName = data._builtinName; + + } + +} + +/** + * TSL function for creating a compute builtin node. + * + * @tsl + * @function + * @param {string} name - The built-in name. + * @param {string} nodeType - The node type. + * @returns {ComputeBuiltinNode} + */ +const computeBuiltin = ( name, nodeType ) => nodeObject( new ComputeBuiltinNode( name, nodeType ) ); + +/** + * Represents the number of workgroups dispatched by the compute shader. + * ```js + * // Run 512 invocations/threads with a workgroup size of 128. + * const computeFn = Fn(() => { + * + * // numWorkgroups.x = 4 + * storageBuffer.element(0).assign(numWorkgroups.x) + * + * })().compute(512, [128]); + * + * // Run 512 invocations/threads with the default workgroup size of 64. + * const computeFn = Fn(() => { + * + * // numWorkgroups.x = 8 + * storageBuffer.element(0).assign(numWorkgroups.x) + * + * })().compute(512); + * ``` + * + * @tsl + * @type {ComputeBuiltinNode} + */ +const numWorkgroups = /*@__PURE__*/ computeBuiltin( 'numWorkgroups', 'uvec3' ); + +/** + * Represents the 3-dimensional index of the workgroup the current compute invocation belongs to. + * ```js + * // Execute 12 compute threads with a workgroup size of 3. + * const computeFn = Fn( () => { + * + * If( workgroupId.x.mod( 2 ).equal( 0 ), () => { + * + * storageBuffer.element( instanceIndex ).assign( instanceIndex ); + * + * } ).Else( () => { + * + * storageBuffer.element( instanceIndex ).assign( 0 ); + * + * } ); + * + * } )().compute( 12, [ 3 ] ); + * + * // workgroupId.x = [0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3]; + * // Buffer Output = [0, 1, 2, 0, 0, 0, 6, 7, 8, 0, 0, 0]; + * ``` + * + * @tsl + * @type {ComputeBuiltinNode} + */ +const workgroupId = /*@__PURE__*/ computeBuiltin( 'workgroupId', 'uvec3' ); + +/** + * A non-linearized 3-dimensional representation of the current invocation's position within a 3D global grid. + * + * @tsl + * @type {ComputeBuiltinNode} + */ +const globalId = /*@__PURE__*/ computeBuiltin( 'globalId', 'uvec3' ); +/** + * A non-linearized 3-dimensional representation of the current invocation's position within a 3D workgroup grid. + * + * @tsl + * @type {ComputeBuiltinNode} + */ +const localId = /*@__PURE__*/ computeBuiltin( 'localId', 'uvec3' ); + +/** + * A device dependent variable that exposes the size of the current invocation's subgroup. + * + * @tsl + * @type {ComputeBuiltinNode} + */ +const subgroupSize = /*@__PURE__*/ computeBuiltin( 'subgroupSize', 'uint' ); + +/** + * Represents a GPU control barrier that synchronizes compute operations within a given scope. + * + * This node can only be used with a WebGPU backend. + * + * @augments Node + */ +class BarrierNode extends Node { + + /** + * Constructs a new barrier node. + * + * @param {string} scope - The scope defines the behavior of the node. + */ + constructor( scope ) { + + super(); + + this.scope = scope; + + } + + generate( builder ) { + + const { scope } = this; + const { renderer } = builder; + + if ( renderer.backend.isWebGLBackend === true ) { + + builder.addFlowCode( `\t// ${scope}Barrier \n` ); + + } else { + + builder.addLineFlowCode( `${scope}Barrier()`, this ); + + } + + } + +} + +/** + * TSL function for creating a barrier node. + * + * @tsl + * @function + * @param {string} scope - The scope defines the behavior of the node.. + * @returns {BarrierNode} + */ +const barrier = nodeProxy( BarrierNode ); + +/** + * TSL function for creating a workgroup barrier. All compute shader + * invocations must wait for each invocation within a workgroup to + * complete before the barrier can be surpassed. + * + * @tsl + * @function + * @returns {BarrierNode} + */ +const workgroupBarrier = () => barrier( 'workgroup' ).toStack(); + +/** + * TSL function for creating a storage barrier. All invocations must + * wait for each access to variables within the 'storage' address space + * to complete before the barrier can be passed. + * + * @tsl + * @function + * @returns {BarrierNode} + */ +const storageBarrier = () => barrier( 'storage' ).toStack(); + +/** + * TSL function for creating a texture barrier. All invocations must + * wait for each access to variables within the 'texture' address space + * to complete before the barrier can be passed. + * + * @tsl + * @function + * @returns {BarrierNode} + */ +const textureBarrier = () => barrier( 'texture' ).toStack(); + +/** + * Represents an element of a 'workgroup' scoped buffer. + * + * @augments ArrayElementNode + */ +class WorkgroupInfoElementNode extends ArrayElementNode { + + /** + * Constructs a new workgroup info element node. + * + * @param {Node} workgroupInfoNode - The workgroup info node. + * @param {Node} indexNode - The index node that defines the element access. + */ + constructor( workgroupInfoNode, indexNode ) { + + super( workgroupInfoNode, indexNode ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isWorkgroupInfoElementNode = true; + + } + + generate( builder, output ) { + + let snippet; + + const isAssignContext = builder.context.assign; + snippet = super.generate( builder ); + + if ( isAssignContext !== true ) { + + const type = this.getNodeType( builder ); + + snippet = builder.format( snippet, type, output ); + + } + + // TODO: Possibly activate clip distance index on index access rather than from clipping context + + return snippet; + + } + +} + +/** + * A node allowing the user to create a 'workgroup' scoped buffer within the + * context of a compute shader. Typically, workgroup scoped buffers are + * created to hold data that is transferred from a global storage scope into + * a local workgroup scope. For invocations within a workgroup, data + * access speeds on 'workgroup' scoped buffers can be significantly faster + * than similar access operations on globally accessible storage buffers. + * + * This node can only be used with a WebGPU backend. + * + * @augments Node + */ +class WorkgroupInfoNode extends Node { + + /** + * Constructs a new buffer scoped to type scope. + * + * @param {string} scope - TODO. + * @param {string} bufferType - The data type of a 'workgroup' scoped buffer element. + * @param {number} [bufferCount=0] - The number of elements in the buffer. + */ + constructor( scope, bufferType, bufferCount = 0 ) { + + super( bufferType ); + + /** + * The buffer type. + * + * @type {string} + */ + this.bufferType = bufferType; + + /** + * The buffer count. + * + * @type {number} + * @default 0 + */ + this.bufferCount = bufferCount; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isWorkgroupInfoNode = true; + + /** + * The data type of the array buffer. + * + * @type {string} + */ + this.elementType = bufferType; + + /** + * TODO. + * + * @type {string} + */ + this.scope = scope; + + } + + /** + * Sets the name/label of this node. + * + * @param {string} name - The name to set. + * @return {WorkgroupInfoNode} A reference to this node. + */ + label( name ) { + + this.name = name; + + return this; + + } + + /** + * Sets the scope of this node. + * + * @param {string} scope - The scope to set. + * @return {WorkgroupInfoNode} A reference to this node. + */ + setScope( scope ) { + + this.scope = scope; + + return this; + + } + + + /** + * The data type of the array buffer. + * + * @return {string} The element type. + */ + getElementType() { + + return this.elementType; + + } + + /** + * Overwrites the default implementation since the input type + * is inferred from the scope. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The input type. + */ + getInputType( /*builder*/ ) { + + return `${this.scope}Array`; + + } + + /** + * This method can be used to access elements via an index node. + * + * @param {IndexNode} indexNode - indexNode. + * @return {WorkgroupInfoElementNode} A reference to an element. + */ + element( indexNode ) { + + return nodeObject( new WorkgroupInfoElementNode( this, indexNode ) ); + + } + + generate( builder ) { + + return builder.getScopedArray( this.name || `${this.scope}Array_${this.id}`, this.scope.toLowerCase(), this.bufferType, this.bufferCount ); + + } + +} + +/** + * TSL function for creating a workgroup info node. + * Creates a new 'workgroup' scoped array buffer. + * + * @tsl + * @function + * @param {string} type - The data type of a 'workgroup' scoped buffer element. + * @param {number} [count=0] - The number of elements in the buffer. + * @returns {WorkgroupInfoNode} + */ +const workgroupArray = ( type, count ) => nodeObject( new WorkgroupInfoNode( 'Workgroup', type, count ) ); + +/** + * `AtomicFunctionNode` represents any function that can operate on atomic variable types + * within a shader. In an atomic function, any modification to an atomic variable will + * occur as an indivisible step with a defined order relative to other modifications. + * Accordingly, even if multiple atomic functions are modifying an atomic variable at once + * atomic operations will not interfere with each other. + * + * This node can only be used with a WebGPU backend. + * + * @augments Node + */ +class AtomicFunctionNode extends Node { + + static get type() { + + return 'AtomicFunctionNode'; + + } + + /** + * Constructs a new atomic function node. + * + * @param {string} method - The signature of the atomic function to construct. + * @param {Node} pointerNode - An atomic variable or element of an atomic buffer. + * @param {Node} valueNode - The value that mutates the atomic variable. + */ + constructor( method, pointerNode, valueNode ) { + + super( 'uint' ); + + /** + * The signature of the atomic function to construct. + * + * @type {string} + */ + this.method = method; + + /** + * An atomic variable or element of an atomic buffer. + * + * @type {Node} + */ + this.pointerNode = pointerNode; + + /** + * A value that modifies the atomic variable. + * + * @type {Node} + */ + this.valueNode = valueNode; + + /** + * Creates a list of the parents for this node for detecting if the node needs to return a value. + * + * @type {boolean} + * @default true + */ + this.parents = true; + + } + + /** + * Overwrites the default implementation to return the type of + * the pointer node. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The input type. + */ + getInputType( builder ) { + + return this.pointerNode.getNodeType( builder ); + + } + + /** + * Overwritten since the node type is inferred from the input type. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {string} The node type. + */ + getNodeType( builder ) { + + return this.getInputType( builder ); + + } + + generate( builder ) { + + const properties = builder.getNodeProperties( this ); + const parents = properties.parents; + + const method = this.method; + + const type = this.getNodeType( builder ); + const inputType = this.getInputType( builder ); + + const a = this.pointerNode; + const b = this.valueNode; + + const params = []; + + params.push( `&${ a.build( builder, inputType ) }` ); + + if ( b !== null ) { + + params.push( b.build( builder, inputType ) ); + + + } + + const methodSnippet = `${ builder.getMethod( method, type ) }( ${ params.join( ', ' ) } )`; + const isVoid = parents.length === 1 && parents[ 0 ].isStackNode === true; + + if ( isVoid ) { + + builder.addLineFlowCode( methodSnippet, this ); + + } else { + + if ( properties.constNode === undefined ) { + + properties.constNode = expression( methodSnippet, type ).toConst(); + + } + + return properties.constNode.build( builder ); + + } + + } + +} + +AtomicFunctionNode.ATOMIC_LOAD = 'atomicLoad'; +AtomicFunctionNode.ATOMIC_STORE = 'atomicStore'; +AtomicFunctionNode.ATOMIC_ADD = 'atomicAdd'; +AtomicFunctionNode.ATOMIC_SUB = 'atomicSub'; +AtomicFunctionNode.ATOMIC_MAX = 'atomicMax'; +AtomicFunctionNode.ATOMIC_MIN = 'atomicMin'; +AtomicFunctionNode.ATOMIC_AND = 'atomicAnd'; +AtomicFunctionNode.ATOMIC_OR = 'atomicOr'; +AtomicFunctionNode.ATOMIC_XOR = 'atomicXor'; + +/** + * TSL function for creating an atomic function node. + * + * @tsl + * @function + * @param {string} method - The signature of the atomic function to construct. + * @param {Node} pointerNode - An atomic variable or element of an atomic buffer. + * @param {Node} valueNode - The value that mutates the atomic variable. + * @returns {AtomicFunctionNode} + */ +const atomicNode = nodeProxy( AtomicFunctionNode ); + +/** + * TSL function for appending an atomic function call into the programmatic flow of a compute shader. + * + * @tsl + * @function + * @param {string} method - The signature of the atomic function to construct. + * @param {Node} pointerNode - An atomic variable or element of an atomic buffer. + * @param {Node} valueNode - The value that mutates the atomic variable. + * @returns {AtomicFunctionNode} + */ +const atomicFunc = ( method, pointerNode, valueNode ) => { + + return atomicNode( method, pointerNode, valueNode ).toStack(); + +}; + +/** + * Loads the value stored in the atomic variable. + * + * @tsl + * @function + * @param {Node} pointerNode - An atomic variable or element of an atomic buffer. + * @returns {AtomicFunctionNode} + */ +const atomicLoad = ( pointerNode ) => atomicFunc( AtomicFunctionNode.ATOMIC_LOAD, pointerNode, null ); + +/** + * Stores a value in the atomic variable. + * + * @tsl + * @function + * @param {Node} pointerNode - An atomic variable or element of an atomic buffer. + * @param {Node} valueNode - The value that mutates the atomic variable. + * @returns {AtomicFunctionNode} + */ +const atomicStore = ( pointerNode, valueNode ) => atomicFunc( AtomicFunctionNode.ATOMIC_STORE, pointerNode, valueNode ); + +/** + * Increments the value stored in the atomic variable. + * + * @tsl + * @function + * @param {Node} pointerNode - An atomic variable or element of an atomic buffer. + * @param {Node} valueNode - The value that mutates the atomic variable. + * @returns {AtomicFunctionNode} + */ +const atomicAdd = ( pointerNode, valueNode ) => atomicFunc( AtomicFunctionNode.ATOMIC_ADD, pointerNode, valueNode ); + +/** + * Decrements the value stored in the atomic variable. + * + * @tsl + * @function + * @param {Node} pointerNode - An atomic variable or element of an atomic buffer. + * @param {Node} valueNode - The value that mutates the atomic variable. + * @returns {AtomicFunctionNode} + */ +const atomicSub = ( pointerNode, valueNode ) => atomicFunc( AtomicFunctionNode.ATOMIC_SUB, pointerNode, valueNode ); + +/** + * Stores in an atomic variable the maximum between its current value and a parameter. + * + * @tsl + * @function + * @param {Node} pointerNode - An atomic variable or element of an atomic buffer. + * @param {Node} valueNode - The value that mutates the atomic variable. + * @returns {AtomicFunctionNode} + */ +const atomicMax = ( pointerNode, valueNode ) => atomicFunc( AtomicFunctionNode.ATOMIC_MAX, pointerNode, valueNode ); + +/** + * Stores in an atomic variable the minimum between its current value and a parameter. + * + * @tsl + * @function + * @param {Node} pointerNode - An atomic variable or element of an atomic buffer. + * @param {Node} valueNode - The value that mutates the atomic variable. + * @returns {AtomicFunctionNode} + */ +const atomicMin = ( pointerNode, valueNode ) => atomicFunc( AtomicFunctionNode.ATOMIC_MIN, pointerNode, valueNode ); + +/** + * Stores in an atomic variable the bitwise AND of its value with a parameter. + * + * @tsl + * @function + * @param {Node} pointerNode - An atomic variable or element of an atomic buffer. + * @param {Node} valueNode - The value that mutates the atomic variable. + * @returns {AtomicFunctionNode} + */ +const atomicAnd = ( pointerNode, valueNode ) => atomicFunc( AtomicFunctionNode.ATOMIC_AND, pointerNode, valueNode ); + +/** + * Stores in an atomic variable the bitwise OR of its value with a parameter. + * + * @tsl + * @function + * @param {Node} pointerNode - An atomic variable or element of an atomic buffer. + * @param {Node} valueNode - The value that mutates the atomic variable. + * @returns {AtomicFunctionNode} + */ +const atomicOr = ( pointerNode, valueNode ) => atomicFunc( AtomicFunctionNode.ATOMIC_OR, pointerNode, valueNode ); + +/** + * Stores in an atomic variable the bitwise XOR of its value with a parameter. + * + * @tsl + * @function + * @param {Node} pointerNode - An atomic variable or element of an atomic buffer. + * @param {Node} valueNode - The value that mutates the atomic variable. + * @returns {AtomicFunctionNode} + */ +const atomicXor = ( pointerNode, valueNode ) => atomicFunc( AtomicFunctionNode.ATOMIC_XOR, pointerNode, valueNode ); + +let uniformsLib; + +function getLightData( light ) { + + uniformsLib = uniformsLib || new WeakMap(); + + let uniforms = uniformsLib.get( light ); + + if ( uniforms === undefined ) uniformsLib.set( light, uniforms = {} ); + + return uniforms; + +} + +/** + * TSL function for getting a shadow matrix uniform node for the given light. + * + * @tsl + * @function + * @param {Light} light -The light source. + * @returns {UniformNode} The shadow matrix uniform node. + */ +function lightShadowMatrix( light ) { + + const data = getLightData( light ); + + return data.shadowMatrix || ( data.shadowMatrix = uniform( 'mat4' ).setGroup( renderGroup ).onRenderUpdate( ( frame ) => { + + if ( light.castShadow !== true || frame.renderer.shadowMap.enabled === false ) { + + light.shadow.updateMatrices( light ); + + } + + return light.shadow.matrix; + + } ) ); + +} + +/** + * TSL function for getting projected uv coordinates for the given light. + * Relevant when using maps with spot lights. + * + * @tsl + * @function + * @param {Light} light -The light source. + * @param {Node} [position=positionWorld] -The position to project. + * @returns {Node} The projected uvs. + */ +function lightProjectionUV( light, position = positionWorld ) { + + const spotLightCoord = lightShadowMatrix( light ).mul( position ); + const projectionUV = spotLightCoord.xyz.div( spotLightCoord.w ); + + return projectionUV; + +} + +/** + * TSL function for getting the position in world space for the given light. + * + * @tsl + * @function + * @param {Light} light -The light source. + * @returns {UniformNode} The light's position in world space. + */ +function lightPosition( light ) { + + const data = getLightData( light ); + + return data.position || ( data.position = uniform( new Vector3() ).setGroup( renderGroup ).onRenderUpdate( ( _, self ) => self.value.setFromMatrixPosition( light.matrixWorld ) ) ); + +} + +/** + * TSL function for getting the light target position in world space for the given light. + * + * @tsl + * @function + * @param {Light} light -The light source. + * @returns {UniformNode} The light target position in world space. + */ +function lightTargetPosition( light ) { + + const data = getLightData( light ); + + return data.targetPosition || ( data.targetPosition = uniform( new Vector3() ).setGroup( renderGroup ).onRenderUpdate( ( _, self ) => self.value.setFromMatrixPosition( light.target.matrixWorld ) ) ); + +} + +/** + * TSL function for getting the position in view space for the given light. + * + * @tsl + * @function + * @param {Light} light - The light source. + * @returns {UniformNode} The light's position in view space. + */ +function lightViewPosition( light ) { + + const data = getLightData( light ); + + return data.viewPosition || ( data.viewPosition = uniform( new Vector3() ).setGroup( renderGroup ).onRenderUpdate( ( { camera }, self ) => { + + self.value = self.value || new Vector3(); + self.value.setFromMatrixPosition( light.matrixWorld ); + + self.value.applyMatrix4( camera.matrixWorldInverse ); + + } ) ); + +} + +/** + * TSL function for getting the light target direction for the given light. + * + * @tsl + * @function + * @param {Light} light -The light source. + * @returns {Node} The light's target direction. + */ +const lightTargetDirection = ( light ) => cameraViewMatrix.transformDirection( lightPosition( light ).sub( lightTargetPosition( light ) ) ); + +const sortLights = ( lights ) => { + + return lights.sort( ( a, b ) => a.id - b.id ); + +}; + +const getLightNodeById = ( id, lightNodes ) => { + + for ( const lightNode of lightNodes ) { + + if ( lightNode.isAnalyticLightNode && lightNode.light.id === id ) { + + return lightNode; + + } + + } + + return null; + +}; + +const _lightsNodeRef = /*@__PURE__*/ new WeakMap(); +const _hashData = []; + +/** + * This node represents the scene's lighting and manages the lighting model's life cycle + * for the current build 3D object. It is responsible for computing the total outgoing + * light in a given lighting context. + * + * @augments Node + */ +class LightsNode extends Node { + + static get type() { + + return 'LightsNode'; + + } + + /** + * Constructs a new lights node. + */ + constructor() { + + super( 'vec3' ); + + /** + * A node representing the total diffuse light. + * + * @type {Node} + */ + this.totalDiffuseNode = property( 'vec3', 'totalDiffuse' ); + + /** + * A node representing the total specular light. + * + * @type {Node} + */ + this.totalSpecularNode = property( 'vec3', 'totalSpecular' ); + + /** + * A node representing the outgoing light. + * + * @type {Node} + */ + this.outgoingLightNode = property( 'vec3', 'outgoingLight' ); + + /** + * An array representing the lights in the scene. + * + * @private + * @type {Array} + */ + this._lights = []; + + /** + * For each light in the scene, this node will create a + * corresponding light node. + * + * @private + * @type {?Array} + * @default null + */ + this._lightNodes = null; + + /** + * A hash for identifying the current light nodes setup. + * + * @private + * @type {?string} + * @default null + */ + this._lightNodesHash = null; + + /** + * `LightsNode` sets this property to `true` by default. + * + * @type {boolean} + * @default true + */ + this.global = true; + + } + + /** + * Overwrites the default {@link Node#customCacheKey} implementation by including + * light data into the cache key. + * + * @return {number} The custom cache key. + */ + customCacheKey() { + + const lights = this._lights; + + for ( let i = 0; i < lights.length; i ++ ) { + + const light = lights[ i ]; + + _hashData.push( light.id ); + _hashData.push( light.castShadow ? 1 : 0 ); + + if ( light.isSpotLight === true ) { + + const hashMap = ( light.map !== null ) ? light.map.id : -1; + const hashColorNode = ( light.colorNode ) ? light.colorNode.getCacheKey() : -1; + + _hashData.push( hashMap, hashColorNode ); + + } + + } + + const cacheKey = hashArray( _hashData ); + + _hashData.length = 0; + + return cacheKey; + + } + + /** + * Computes a hash value for identifying the current light nodes setup. + * + * @param {NodeBuilder} builder - A reference to the current node builder. + * @return {string} The computed hash. + */ + getHash( builder ) { + + if ( this._lightNodesHash === null ) { + + if ( this._lightNodes === null ) this.setupLightsNode( builder ); + + const hash = []; + + for ( const lightNode of this._lightNodes ) { + + hash.push( lightNode.getSelf().getHash() ); + + } + + this._lightNodesHash = 'lights-' + hash.join( ',' ); + + } + + return this._lightNodesHash; + + } + + analyze( builder ) { + + const properties = builder.getNodeProperties( this ); + + for ( const node of properties.nodes ) { + + node.build( builder ); + + } + + properties.outputNode.build( builder ); + + } + + /** + * Creates lighting nodes for each scene light. This makes it possible to further + * process lights in the node system. + * + * @param {NodeBuilder} builder - A reference to the current node builder. + */ + setupLightsNode( builder ) { + + const lightNodes = []; + + const previousLightNodes = this._lightNodes; + + const lights = sortLights( this._lights ); + const nodeLibrary = builder.renderer.library; + + for ( const light of lights ) { + + if ( light.isNode ) { + + lightNodes.push( nodeObject( light ) ); + + } else { + + let lightNode = null; + + if ( previousLightNodes !== null ) { + + lightNode = getLightNodeById( light.id, previousLightNodes ); // reuse existing light node + + } + + if ( lightNode === null ) { + + // find the corresponding node type for a given light + + const lightNodeClass = nodeLibrary.getLightNodeClass( light.constructor ); + + if ( lightNodeClass === null ) { + + console.warn( `LightsNode.setupNodeLights: Light node not found for ${ light.constructor.name }` ); + continue; + + } + + let lightNode = null; + + if ( ! _lightsNodeRef.has( light ) ) { + + lightNode = nodeObject( new lightNodeClass( light ) ); + _lightsNodeRef.set( light, lightNode ); + + } else { + + lightNode = _lightsNodeRef.get( light ); + + } + + lightNodes.push( lightNode ); + + } + + } + + } + + this._lightNodes = lightNodes; + + } + + /** + * Sets up a direct light in the lighting model. + * + * @param {Object} builder - The builder object containing the context and stack. + * @param {Object} lightNode - The light node. + * @param {Object} lightData - The light object containing color and direction properties. + */ + setupDirectLight( builder, lightNode, lightData ) { + + const { lightingModel, reflectedLight } = builder.context; + + lightingModel.direct( { + ...lightData, + lightNode, + reflectedLight + }, builder ); + + } + + setupDirectRectAreaLight( builder, lightNode, lightData ) { + + const { lightingModel, reflectedLight } = builder.context; + + lightingModel.directRectArea( { + ...lightData, + lightNode, + reflectedLight + }, builder ); + + } + + /** + * Setups the internal lights by building all respective + * light nodes. + * + * @param {NodeBuilder} builder - A reference to the current node builder. + * @param {Array} lightNodes - An array of lighting nodes. + */ + setupLights( builder, lightNodes ) { + + for ( const lightNode of lightNodes ) { + + lightNode.build( builder ); + + } + + } + + getLightNodes( builder ) { + + if ( this._lightNodes === null ) this.setupLightsNode( builder ); + + return this._lightNodes; + + } + + /** + * The implementation makes sure that for each light in the scene + * there is a corresponding light node. By building the light nodes + * and evaluating the lighting model the outgoing light is computed. + * + * @param {NodeBuilder} builder - A reference to the current node builder. + * @return {Node} A node representing the outgoing light. + */ + setup( builder ) { + + const currentLightsNode = builder.lightsNode; + + builder.lightsNode = this; + + // + + let outgoingLightNode = this.outgoingLightNode; + + const context = builder.context; + const lightingModel = context.lightingModel; + + const properties = builder.getNodeProperties( this ); + + if ( lightingModel ) { + + const { totalDiffuseNode, totalSpecularNode } = this; + + context.outgoingLight = outgoingLightNode; + + const stack = builder.addStack(); + + // + + properties.nodes = stack.nodes; + + // + + lightingModel.start( builder ); + + // + + const { backdrop, backdropAlpha } = context; + const { directDiffuse, directSpecular, indirectDiffuse, indirectSpecular } = context.reflectedLight; + + let totalDiffuse = directDiffuse.add( indirectDiffuse ); + + if ( backdrop !== null ) { + + if ( backdropAlpha !== null ) { + + totalDiffuse = vec3( backdropAlpha.mix( totalDiffuse, backdrop ) ); + + } else { + + totalDiffuse = vec3( backdrop ); + + } + + context.material.transparent = true; + + } + + totalDiffuseNode.assign( totalDiffuse ); + totalSpecularNode.assign( directSpecular.add( indirectSpecular ) ); + + outgoingLightNode.assign( totalDiffuseNode.add( totalSpecularNode ) ); + + // + + lightingModel.finish( builder ); + + // + + outgoingLightNode = outgoingLightNode.bypass( builder.removeStack() ); + + } else { + + properties.nodes = []; + + } + + // + + builder.lightsNode = currentLightsNode; + + return outgoingLightNode; + + } + + /** + * Configures this node with an array of lights. + * + * @param {Array} lights - An array of lights. + * @return {LightsNode} A reference to this node. + */ + setLights( lights ) { + + this._lights = lights; + + this._lightNodes = null; + this._lightNodesHash = null; + + return this; + + } + + /** + * Returns an array of the scene's lights. + * + * @return {Array} The scene's lights. + */ + getLights() { + + return this._lights; + + } + + /** + * Whether the scene has lights or not. + * + * @type {boolean} + */ + get hasLights() { + + return this._lights.length > 0; + + } + +} + +/** + * TSL function for creating an instance of `LightsNode` and configuring + * it with the given array of lights. + * + * @tsl + * @function + * @param {Array} lights - An array of lights. + * @return {LightsNode} The created lights node. + */ +const lights = ( lights = [] ) => nodeObject( new LightsNode() ).setLights( lights ); + +/** + * Base class for all shadow nodes. + * + * Shadow nodes encapsulate shadow related logic and are always coupled to lighting nodes. + * Lighting nodes might share the same shadow node type or use specific ones depending on + * their requirements. + * + * @augments Node + */ +class ShadowBaseNode extends Node { + + static get type() { + + return 'ShadowBaseNode'; + + } + + /** + * Constructs a new shadow base node. + * + * @param {Light} light - The shadow casting light. + */ + constructor( light ) { + + super(); + + /** + * The shadow casting light. + * + * @type {Light} + */ + this.light = light; + + /** + * Overwritten since shadows are updated by default per render. + * + * @type {string} + * @default 'render' + */ + this.updateBeforeType = NodeUpdateType.RENDER; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isShadowBaseNode = true; + + } + + /** + * Setups the shadow position node which is by default the predefined TSL node object `shadowPositionWorld`. + * + * @param {NodeBuilder} object - A configuration object that must at least hold a material reference. + */ + setupShadowPosition( { context, material } ) { + + // Use assign inside an Fn() + + shadowPositionWorld.assign( material.receivedShadowPositionNode || context.shadowPositionWorld || positionWorld ); + + } + +} + +/** + * TSL object that represents the vertex position in world space during the shadow pass. + * + * @tsl + * @type {Node} + */ +const shadowPositionWorld = /*@__PURE__*/ property( 'vec3', 'shadowPositionWorld' ); + +/** + * Saves the state of the given renderer and stores it into the given state object. + * + * If not state object is provided, the function creates one. + * + * @function + * @param {Renderer} renderer - The renderer. + * @param {Object} [state={}] - The state. + * @return {Object} The state. + */ +function saveRendererState( renderer, state = {} ) { + + state.toneMapping = renderer.toneMapping; + state.toneMappingExposure = renderer.toneMappingExposure; + state.outputColorSpace = renderer.outputColorSpace; + state.renderTarget = renderer.getRenderTarget(); + state.activeCubeFace = renderer.getActiveCubeFace(); + state.activeMipmapLevel = renderer.getActiveMipmapLevel(); + state.renderObjectFunction = renderer.getRenderObjectFunction(); + state.pixelRatio = renderer.getPixelRatio(); + state.mrt = renderer.getMRT(); + state.clearColor = renderer.getClearColor( state.clearColor || new Color() ); + state.clearAlpha = renderer.getClearAlpha(); + state.autoClear = renderer.autoClear; + state.scissorTest = renderer.getScissorTest(); + + return state; + +} + +/** + * Saves the state of the given renderer and stores it into the given state object. + * Besides, the function also resets the state of the renderer to its default values. + * + * If not state object is provided, the function creates one. + * + * @function + * @param {Renderer} renderer - The renderer. + * @param {Object} [state={}] - The state. + * @return {Object} The state. + */ +function resetRendererState( renderer, state ) { + + state = saveRendererState( renderer, state ); + + renderer.setMRT( null ); + renderer.setRenderObjectFunction( null ); + renderer.setClearColor( 0x000000, 1 ); + renderer.autoClear = true; + + return state; + +} + +/** + * Restores the state of the given renderer from the given state object. + * + * @function + * @param {Renderer} renderer - The renderer. + * @param {Object} state - The state to restore. + */ +function restoreRendererState( renderer, state ) { + + renderer.toneMapping = state.toneMapping; + renderer.toneMappingExposure = state.toneMappingExposure; + renderer.outputColorSpace = state.outputColorSpace; + renderer.setRenderTarget( state.renderTarget, state.activeCubeFace, state.activeMipmapLevel ); + renderer.setRenderObjectFunction( state.renderObjectFunction ); + renderer.setPixelRatio( state.pixelRatio ); + renderer.setMRT( state.mrt ); + renderer.setClearColor( state.clearColor, state.clearAlpha ); + renderer.autoClear = state.autoClear; + renderer.setScissorTest( state.scissorTest ); + +} + +/** + * Saves the state of the given scene and stores it into the given state object. + * + * If not state object is provided, the function creates one. + * + * @function + * @param {Scene} scene - The scene. + * @param {Object} [state={}] - The state. + * @return {Object} The state. + */ +function saveSceneState( scene, state = {} ) { + + state.background = scene.background; + state.backgroundNode = scene.backgroundNode; + state.overrideMaterial = scene.overrideMaterial; + + return state; + +} + +/** + * Saves the state of the given scene and stores it into the given state object. + * Besides, the function also resets the state of the scene to its default values. + * + * If not state object is provided, the function creates one. + * + * @function + * @param {Scene} scene - The scene. + * @param {Object} [state={}] - The state. + * @return {Object} The state. + */ +function resetSceneState( scene, state ) { + + state = saveSceneState( scene, state ); + + scene.background = null; + scene.backgroundNode = null; + scene.overrideMaterial = null; + + return state; + +} + +/** + * Restores the state of the given scene from the given state object. + * + * @function + * @param {Scene} scene - The scene. + * @param {Object} state - The state to restore. + */ +function restoreSceneState( scene, state ) { + + scene.background = state.background; + scene.backgroundNode = state.backgroundNode; + scene.overrideMaterial = state.overrideMaterial; + +} + +/** + * Saves the state of the given renderer and scene and stores it into the given state object. + * + * If not state object is provided, the function creates one. + * + * @function + * @param {Renderer} renderer - The renderer. + * @param {Scene} scene - The scene. + * @param {Object} [state={}] - The state. + * @return {Object} The state. + */ +function saveRendererAndSceneState( renderer, scene, state = {} ) { + + state = saveRendererState( renderer, state ); + state = saveSceneState( scene, state ); + + return state; + +} + +/** + * Saves the state of the given renderer and scene and stores it into the given state object. + * Besides, the function also resets the state of the renderer and scene to its default values. + * + * If not state object is provided, the function creates one. + * + * @function + * @param {Renderer} renderer - The renderer. + * @param {Scene} scene - The scene. + * @param {Object} [state={}] - The state. + * @return {Object} The state. + */ +function resetRendererAndSceneState( renderer, scene, state ) { + + state = resetRendererState( renderer, state ); + state = resetSceneState( scene, state ); + + return state; + +} + +/** + * Restores the state of the given renderer and scene from the given state object. + * + * @function + * @param {Renderer} renderer - The renderer. + * @param {Scene} scene - The scene. + * @param {Object} state - The state to restore. + */ +function restoreRendererAndSceneState( renderer, scene, state ) { + + restoreRendererState( renderer, state ); + restoreSceneState( scene, state ); + +} + +var RendererUtils = /*#__PURE__*/Object.freeze({ + __proto__: null, + resetRendererAndSceneState: resetRendererAndSceneState, + resetRendererState: resetRendererState, + resetSceneState: resetSceneState, + restoreRendererAndSceneState: restoreRendererAndSceneState, + restoreRendererState: restoreRendererState, + restoreSceneState: restoreSceneState, + saveRendererAndSceneState: saveRendererAndSceneState, + saveRendererState: saveRendererState, + saveSceneState: saveSceneState +}); + +const shadowMaterialLib = /*@__PURE__*/ new WeakMap(); + +/** + * A shadow filtering function performing basic filtering. This is in fact an unfiltered version of the shadow map + * with a binary `[0,1]` result. + * + * @method + * @param {Object} inputs - The input parameter object. + * @param {DepthTexture} inputs.depthTexture - A reference to the shadow map's texture data. + * @param {Node} inputs.shadowCoord - The shadow coordinates. + * @return {Node} The filtering result. + */ +const BasicShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, depthLayer } ) => { + + let basic = texture( depthTexture, shadowCoord.xy ).label( 't_basic' ); + + if ( depthTexture.isArrayTexture ) { + + basic = basic.depth( depthLayer ); + + } + + return basic.compare( shadowCoord.z ); + +} ); + +/** + * A shadow filtering function performing PCF filtering. + * + * @method + * @param {Object} inputs - The input parameter object. + * @param {DepthTexture} inputs.depthTexture - A reference to the shadow map's texture data. + * @param {Node} inputs.shadowCoord - The shadow coordinates. + * @param {LightShadow} inputs.shadow - The light shadow. + * @return {Node} The filtering result. + */ +const PCFShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, depthLayer } ) => { + + const depthCompare = ( uv, compare ) => { + + let depth = texture( depthTexture, uv ); + + if ( depthTexture.isArrayTexture ) { + + depth = depth.depth( depthLayer ); + + } + + return depth.compare( compare ); + + }; + + const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); + const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup ); + + const texelSize = vec2( 1 ).div( mapSize ); + const dx0 = texelSize.x.negate().mul( radius ); + const dy0 = texelSize.y.negate().mul( radius ); + const dx1 = texelSize.x.mul( radius ); + const dy1 = texelSize.y.mul( radius ); + const dx2 = dx0.div( 2 ); + const dy2 = dy0.div( 2 ); + const dx3 = dx1.div( 2 ); + const dy3 = dy1.div( 2 ); + + return add( + depthCompare( shadowCoord.xy.add( vec2( dx0, dy0 ) ), shadowCoord.z ), + depthCompare( shadowCoord.xy.add( vec2( 0, dy0 ) ), shadowCoord.z ), + depthCompare( shadowCoord.xy.add( vec2( dx1, dy0 ) ), shadowCoord.z ), + depthCompare( shadowCoord.xy.add( vec2( dx2, dy2 ) ), shadowCoord.z ), + depthCompare( shadowCoord.xy.add( vec2( 0, dy2 ) ), shadowCoord.z ), + depthCompare( shadowCoord.xy.add( vec2( dx3, dy2 ) ), shadowCoord.z ), + depthCompare( shadowCoord.xy.add( vec2( dx0, 0 ) ), shadowCoord.z ), + depthCompare( shadowCoord.xy.add( vec2( dx2, 0 ) ), shadowCoord.z ), + depthCompare( shadowCoord.xy, shadowCoord.z ), + depthCompare( shadowCoord.xy.add( vec2( dx3, 0 ) ), shadowCoord.z ), + depthCompare( shadowCoord.xy.add( vec2( dx1, 0 ) ), shadowCoord.z ), + depthCompare( shadowCoord.xy.add( vec2( dx2, dy3 ) ), shadowCoord.z ), + depthCompare( shadowCoord.xy.add( vec2( 0, dy3 ) ), shadowCoord.z ), + depthCompare( shadowCoord.xy.add( vec2( dx3, dy3 ) ), shadowCoord.z ), + depthCompare( shadowCoord.xy.add( vec2( dx0, dy1 ) ), shadowCoord.z ), + depthCompare( shadowCoord.xy.add( vec2( 0, dy1 ) ), shadowCoord.z ), + depthCompare( shadowCoord.xy.add( vec2( dx1, dy1 ) ), shadowCoord.z ) + ).mul( 1 / 17 ); + +} ); + +/** + * A shadow filtering function performing PCF soft filtering. + * + * @method + * @param {Object} inputs - The input parameter object. + * @param {DepthTexture} inputs.depthTexture - A reference to the shadow map's texture data. + * @param {Node} inputs.shadowCoord - The shadow coordinates. + * @param {LightShadow} inputs.shadow - The light shadow. + * @return {Node} The filtering result. + */ +const PCFSoftShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, shadow, depthLayer } ) => { + + const depthCompare = ( uv, compare ) => { + + let depth = texture( depthTexture, uv ); + + if ( depthTexture.isArrayTexture ) { + + depth = depth.depth( depthLayer ); + + } + + return depth.compare( compare ); + + }; + + + const mapSize = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); + + const texelSize = vec2( 1 ).div( mapSize ); + const dx = texelSize.x; + const dy = texelSize.y; + + const uv = shadowCoord.xy; + const f = fract( uv.mul( mapSize ).add( 0.5 ) ); + uv.subAssign( f.mul( texelSize ) ); + + return add( + depthCompare( uv, shadowCoord.z ), + depthCompare( uv.add( vec2( dx, 0 ) ), shadowCoord.z ), + depthCompare( uv.add( vec2( 0, dy ) ), shadowCoord.z ), + depthCompare( uv.add( texelSize ), shadowCoord.z ), + mix( + depthCompare( uv.add( vec2( dx.negate(), 0 ) ), shadowCoord.z ), + depthCompare( uv.add( vec2( dx.mul( 2 ), 0 ) ), shadowCoord.z ), + f.x + ), + mix( + depthCompare( uv.add( vec2( dx.negate(), dy ) ), shadowCoord.z ), + depthCompare( uv.add( vec2( dx.mul( 2 ), dy ) ), shadowCoord.z ), + f.x + ), + mix( + depthCompare( uv.add( vec2( 0, dy.negate() ) ), shadowCoord.z ), + depthCompare( uv.add( vec2( 0, dy.mul( 2 ) ) ), shadowCoord.z ), + f.y + ), + mix( + depthCompare( uv.add( vec2( dx, dy.negate() ) ), shadowCoord.z ), + depthCompare( uv.add( vec2( dx, dy.mul( 2 ) ) ), shadowCoord.z ), + f.y + ), + mix( + mix( + depthCompare( uv.add( vec2( dx.negate(), dy.negate() ) ), shadowCoord.z ), + depthCompare( uv.add( vec2( dx.mul( 2 ), dy.negate() ) ), shadowCoord.z ), + f.x + ), + mix( + depthCompare( uv.add( vec2( dx.negate(), dy.mul( 2 ) ) ), shadowCoord.z ), + depthCompare( uv.add( vec2( dx.mul( 2 ), dy.mul( 2 ) ) ), shadowCoord.z ), + f.x + ), + f.y + ) + ).mul( 1 / 9 ); + +} ); + +/** + * A shadow filtering function performing VSM filtering. + * + * @method + * @param {Object} inputs - The input parameter object. + * @param {DepthTexture} inputs.depthTexture - A reference to the shadow map's texture data. + * @param {Node} inputs.shadowCoord - The shadow coordinates. + * @return {Node} The filtering result. + */ +const VSMShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, shadowCoord, depthLayer } ) => { + + const occlusion = float( 1 ).toVar(); + + let distribution = texture( depthTexture ).sample( shadowCoord.xy ); + + if ( depthTexture.isArrayTexture ) { + + distribution = distribution.depth( depthLayer ); + + } + + distribution = distribution.rg; + + const hardShadow = step( shadowCoord.z, distribution.x ); + + If( hardShadow.notEqual( float( 1.0 ) ), () => { + + const distance = shadowCoord.z.sub( distribution.x ); + const variance = max$1( 0, distribution.y.mul( distribution.y ) ); + let softnessProbability = variance.div( variance.add( distance.mul( distance ) ) ); // Chebeyshevs inequality + softnessProbability = clamp( sub( softnessProbability, 0.3 ).div( 0.95 - 0.3 ) ); + occlusion.assign( clamp( max$1( hardShadow, softnessProbability ) ) ); + + } ); + + return occlusion; + +} ); + +// + +const linearDistance = /*@__PURE__*/ Fn( ( [ position, cameraNear, cameraFar ] ) => { + + let dist = positionWorld.sub( position ).length(); + dist = dist.sub( cameraNear ).div( cameraFar.sub( cameraNear ) ); + dist = dist.saturate(); // clamp to [ 0, 1 ] + + return dist; + +} ); + +const linearShadowDistance = ( light ) => { + + const camera = light.shadow.camera; + + const nearDistance = reference( 'near', 'float', camera ).setGroup( renderGroup ); + const farDistance = reference( 'far', 'float', camera ).setGroup( renderGroup ); + + const referencePosition = objectPosition( light ); + + return linearDistance( referencePosition, nearDistance, farDistance ); + +}; + +/** + * Retrieves or creates a shadow material for the given light source. + * + * This function checks if a shadow material already exists for the provided light. + * If not, it creates a new `NodeMaterial` configured for shadow rendering and stores it + * in the `shadowMaterialLib` for future use. + * + * @param {Light} light - The light source for which the shadow material is needed. + * If the light is a point light, a depth node is calculated + * using the linear shadow distance. + * @returns {NodeMaterial} The shadow material associated with the given light. + */ +const getShadowMaterial = ( light ) => { + + let material = shadowMaterialLib.get( light ); + + if ( material === undefined ) { + + const depthNode = light.isPointLight ? linearShadowDistance( light ) : null; + + material = new NodeMaterial(); + material.colorNode = vec4( 0, 0, 0, 1 ); + material.depthNode = depthNode; + material.isShadowPassMaterial = true; // Use to avoid other overrideMaterial override material.colorNode unintentionally when using material.shadowNode + material.name = 'ShadowMaterial'; + material.fog = false; + + shadowMaterialLib.set( light, material ); + + } + + return material; + +}; + +// + +const _shadowRenderObjectLibrary = /*@__PURE__*/ new ChainMap(); +const _shadowRenderObjectKeys = []; + +/** + * Creates a function to render shadow objects in a scene. + * + * @param {Renderer} renderer - The renderer. + * @param {LightShadow} shadow - The light shadow object containing shadow properties. + * @param {number} shadowType - The type of shadow map (e.g., BasicShadowMap). + * @param {boolean} useVelocity - Whether to use velocity data for rendering. + * @return {Function} A function that renders shadow objects. + * + * The returned function has the following parameters: + * @param {Object3D} object - The 3D object to render. + * @param {Scene} scene - The scene containing the object. + * @param {Camera} _camera - The camera used for rendering. + * @param {BufferGeometry} geometry - The geometry of the object. + * @param {Material} material - The material of the object. + * @param {Group} group - The group the object belongs to. + * @param {...any} params - Additional parameters for rendering. + */ +const getShadowRenderObjectFunction = ( renderer, shadow, shadowType, useVelocity ) => { + + _shadowRenderObjectKeys[ 0 ] = renderer; + _shadowRenderObjectKeys[ 1 ] = shadow; + + let renderObjectFunction = _shadowRenderObjectLibrary.get( _shadowRenderObjectKeys ); + + if ( renderObjectFunction === undefined || ( renderObjectFunction.shadowType !== shadowType || renderObjectFunction.useVelocity !== useVelocity ) ) { + + renderObjectFunction = ( object, scene, _camera, geometry, material, group, ...params ) => { + + if ( object.castShadow === true || ( object.receiveShadow && shadowType === VSMShadowMap ) ) { + + if ( useVelocity ) { + + getDataFromObject( object ).useVelocity = true; + + } + + object.onBeforeShadow( renderer, object, _camera, shadow.camera, geometry, scene.overrideMaterial, group ); + + renderer.renderObject( object, scene, _camera, geometry, material, group, ...params ); + + object.onAfterShadow( renderer, object, _camera, shadow.camera, geometry, scene.overrideMaterial, group ); + + } + + }; + + renderObjectFunction.shadowType = shadowType; + renderObjectFunction.useVelocity = useVelocity; + + _shadowRenderObjectLibrary.set( _shadowRenderObjectKeys, renderObjectFunction ); + + } + + _shadowRenderObjectKeys[ 0 ] = null; + _shadowRenderObjectKeys[ 1 ] = null; + + return renderObjectFunction; + +}; + +/** + * Represents the shader code for the first VSM render pass. + * + * @method + * @param {Object} inputs - The input parameter object. + * @param {Node} inputs.samples - The number of samples + * @param {Node} inputs.radius - The radius. + * @param {Node} inputs.size - The size. + * @param {TextureNode} inputs.shadowPass - A reference to the render target's depth data. + * @return {Node} The VSM output. + */ +const VSMPassVertical = /*@__PURE__*/ Fn( ( { samples, radius, size, shadowPass, depthLayer } ) => { + + const mean = float( 0 ).toVar( 'meanVertical' ); + const squaredMean = float( 0 ).toVar( 'squareMeanVertical' ); + + const uvStride = samples.lessThanEqual( float( 1 ) ).select( float( 0 ), float( 2 ).div( samples.sub( 1 ) ) ); + const uvStart = samples.lessThanEqual( float( 1 ) ).select( float( 0 ), float( -1 ) ); + + Loop( { start: int( 0 ), end: int( samples ), type: 'int', condition: '<' }, ( { i } ) => { + + const uvOffset = uvStart.add( float( i ).mul( uvStride ) ); + + let depth = shadowPass.sample( add( screenCoordinate.xy, vec2( 0, uvOffset ).mul( radius ) ).div( size ) ); + + if ( shadowPass.value.isArrayTexture ) { + + depth = depth.depth( depthLayer ); + + } + + depth = depth.x; + + mean.addAssign( depth ); + squaredMean.addAssign( depth.mul( depth ) ); + + } ); + + mean.divAssign( samples ); + squaredMean.divAssign( samples ); + + const std_dev = sqrt( squaredMean.sub( mean.mul( mean ) ) ); + return vec2( mean, std_dev ); + +} ); + +/** + * Represents the shader code for the second VSM render pass. + * + * @method + * @param {Object} inputs - The input parameter object. + * @param {Node} inputs.samples - The number of samples + * @param {Node} inputs.radius - The radius. + * @param {Node} inputs.size - The size. + * @param {TextureNode} inputs.shadowPass - The result of the first VSM render pass. + * @return {Node} The VSM output. + */ +const VSMPassHorizontal = /*@__PURE__*/ Fn( ( { samples, radius, size, shadowPass, depthLayer } ) => { + + const mean = float( 0 ).toVar( 'meanHorizontal' ); + const squaredMean = float( 0 ).toVar( 'squareMeanHorizontal' ); + + const uvStride = samples.lessThanEqual( float( 1 ) ).select( float( 0 ), float( 2 ).div( samples.sub( 1 ) ) ); + const uvStart = samples.lessThanEqual( float( 1 ) ).select( float( 0 ), float( -1 ) ); + + Loop( { start: int( 0 ), end: int( samples ), type: 'int', condition: '<' }, ( { i } ) => { + + const uvOffset = uvStart.add( float( i ).mul( uvStride ) ); + + let distribution = shadowPass.sample( add( screenCoordinate.xy, vec2( uvOffset, 0 ).mul( radius ) ).div( size ) ); + + if ( shadowPass.value.isArrayTexture ) { + + distribution = distribution.depth( depthLayer ); + + } + + mean.addAssign( distribution.x ); + squaredMean.addAssign( add( distribution.y.mul( distribution.y ), distribution.x.mul( distribution.x ) ) ); + + } ); + + mean.divAssign( samples ); + squaredMean.divAssign( samples ); + + const std_dev = sqrt( squaredMean.sub( mean.mul( mean ) ) ); + return vec2( mean, std_dev ); + +} ); + +const _shadowFilterLib = [ BasicShadowFilter, PCFShadowFilter, PCFSoftShadowFilter, VSMShadowFilter ]; + +// + +let _rendererState; +const _quadMesh = /*@__PURE__*/ new QuadMesh(); + +/** + * Represents the default shadow implementation for lighting nodes. + * + * @augments ShadowBaseNode + */ +class ShadowNode extends ShadowBaseNode { + + static get type() { + + return 'ShadowNode'; + + } + + /** + * Constructs a new shadow node. + * + * @param {Light} light - The shadow casting light. + * @param {?LightShadow} [shadow=null] - An optional light shadow. + */ + constructor( light, shadow = null ) { + + super( light ); + + /** + * The light shadow which defines the properties light's + * shadow. + * + * @type {?LightShadow} + * @default null + */ + this.shadow = shadow || light.shadow; + + /** + * A reference to the shadow map which is a render target. + * + * @type {?RenderTarget} + * @default null + */ + this.shadowMap = null; + + /** + * Only relevant for VSM shadows. Render target for the + * first VSM render pass. + * + * @type {?RenderTarget} + * @default null + */ + this.vsmShadowMapVertical = null; + + /** + * Only relevant for VSM shadows. Render target for the + * second VSM render pass. + * + * @type {?RenderTarget} + * @default null + */ + this.vsmShadowMapHorizontal = null; + + /** + * Only relevant for VSM shadows. Node material which + * is used to render the first VSM pass. + * + * @type {?NodeMaterial} + * @default null + */ + this.vsmMaterialVertical = null; + + /** + * Only relevant for VSM shadows. Node material which + * is used to render the second VSM pass. + * + * @type {?NodeMaterial} + * @default null + */ + this.vsmMaterialHorizontal = null; + + /** + * A reference to the output node which defines the + * final result of this shadow node. + * + * @type {?Node} + * @private + * @default null + */ + this._node = null; + + this._cameraFrameId = new WeakMap(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isShadowNode = true; + + /** + * This index can be used when overriding setupRenderTarget with a RenderTarget Array to specify the depth layer. + * + * @type {number} + * @readonly + * @default true + */ + this.depthLayer = 0; + + } + + /** + * Setups the shadow filtering. + * + * @param {NodeBuilder} builder - A reference to the current node builder. + * @param {Object} inputs - A configuration object that defines the shadow filtering. + * @param {Function} inputs.filterFn - This function defines the filtering type of the shadow map e.g. PCF. + * @param {DepthTexture} inputs.depthTexture - A reference to the shadow map's texture data. + * @param {Node} inputs.shadowCoord - Shadow coordinates which are used to sample from the shadow map. + * @param {LightShadow} inputs.shadow - The light shadow. + * @return {Node} The result node of the shadow filtering. + */ + setupShadowFilter( builder, { filterFn, depthTexture, shadowCoord, shadow, depthLayer } ) { + + const frustumTest = shadowCoord.x.greaterThanEqual( 0 ) + .and( shadowCoord.x.lessThanEqual( 1 ) ) + .and( shadowCoord.y.greaterThanEqual( 0 ) ) + .and( shadowCoord.y.lessThanEqual( 1 ) ) + .and( shadowCoord.z.lessThanEqual( 1 ) ); + + const shadowNode = filterFn( { depthTexture, shadowCoord, shadow, depthLayer } ); + + return frustumTest.select( shadowNode, float( 1 ) ); + + } + + /** + * Setups the shadow coordinates. + * + * @param {NodeBuilder} builder - A reference to the current node builder. + * @param {Node} shadowPosition - A node representing the shadow position. + * @return {Node} The shadow coordinates. + */ + setupShadowCoord( builder, shadowPosition ) { + + const { shadow } = this; + const { renderer } = builder; + + const bias = reference( 'bias', 'float', shadow ).setGroup( renderGroup ); + + let shadowCoord = shadowPosition; + let coordZ; + + if ( shadow.camera.isOrthographicCamera || renderer.logarithmicDepthBuffer !== true ) { + + shadowCoord = shadowCoord.xyz.div( shadowCoord.w ); + + coordZ = shadowCoord.z; + + if ( renderer.coordinateSystem === WebGPUCoordinateSystem ) { + + coordZ = coordZ.mul( 2 ).sub( 1 ); // WebGPU: Conversion [ 0, 1 ] to [ - 1, 1 ] + + } + + } else { + + const w = shadowCoord.w; + shadowCoord = shadowCoord.xy.div( w ); // <-- Only divide X/Y coords since we don't need Z + + // The normally available "cameraNear" and "cameraFar" nodes cannot be used here because they do not get + // updated to use the shadow camera. So, we have to declare our own "local" ones here. + // TODO: How do we get the cameraNear/cameraFar nodes to use the shadow camera so we don't have to declare local ones here? + const cameraNearLocal = reference( 'near', 'float', shadow.camera ).setGroup( renderGroup ); + const cameraFarLocal = reference( 'far', 'float', shadow.camera ).setGroup( renderGroup ); + + coordZ = viewZToLogarithmicDepth( w.negate(), cameraNearLocal, cameraFarLocal ); + + } + + shadowCoord = vec3( + shadowCoord.x, + shadowCoord.y.oneMinus(), // follow webgpu standards + coordZ.add( bias ) + ); + + return shadowCoord; + + } + + /** + * Returns the shadow filtering function for the given shadow type. + * + * @param {number} type - The shadow type. + * @return {Function} The filtering function. + */ + getShadowFilterFn( type ) { + + return _shadowFilterLib[ type ]; + + } + + + setupRenderTarget( shadow, builder ) { + + const depthTexture = new DepthTexture( shadow.mapSize.width, shadow.mapSize.height ); + depthTexture.name = 'ShadowDepthTexture'; + depthTexture.compareFunction = LessCompare; + + const shadowMap = builder.createRenderTarget( shadow.mapSize.width, shadow.mapSize.height ); + shadowMap.texture.name = 'ShadowMap'; + shadowMap.texture.type = shadow.mapType; + shadowMap.depthTexture = depthTexture; + + return { shadowMap, depthTexture }; + + } + + /** + * Setups the shadow output node. + * + * @param {NodeBuilder} builder - A reference to the current node builder. + * @return {Node} The shadow output node. + */ + setupShadow( builder ) { + + const { renderer } = builder; + + const { light, shadow } = this; + + const shadowMapType = renderer.shadowMap.type; + + const { depthTexture, shadowMap } = this.setupRenderTarget( shadow, builder ); + + shadow.camera.updateProjectionMatrix(); + + // VSM + + if ( shadowMapType === VSMShadowMap && shadow.isPointLightShadow !== true ) { + + depthTexture.compareFunction = null; // VSM does not use textureSampleCompare()/texture2DCompare() + + if ( shadowMap.depth > 1 ) { + + if ( ! shadowMap._vsmShadowMapVertical ) { + + shadowMap._vsmShadowMapVertical = builder.createRenderTarget( shadow.mapSize.width, shadow.mapSize.height, { format: RGFormat, type: HalfFloatType, depth: shadowMap.depth, depthBuffer: false } ); + shadowMap._vsmShadowMapVertical.texture.name = 'VSMVertical'; + + } + + this.vsmShadowMapVertical = shadowMap._vsmShadowMapVertical; + + if ( ! shadowMap._vsmShadowMapHorizontal ) { + + shadowMap._vsmShadowMapHorizontal = builder.createRenderTarget( shadow.mapSize.width, shadow.mapSize.height, { format: RGFormat, type: HalfFloatType, depth: shadowMap.depth, depthBuffer: false } ); + shadowMap._vsmShadowMapHorizontal.texture.name = 'VSMHorizontal'; + + } + + this.vsmShadowMapHorizontal = shadowMap._vsmShadowMapHorizontal; + + } else { + + this.vsmShadowMapVertical = builder.createRenderTarget( shadow.mapSize.width, shadow.mapSize.height, { format: RGFormat, type: HalfFloatType, depthBuffer: false } ); + this.vsmShadowMapHorizontal = builder.createRenderTarget( shadow.mapSize.width, shadow.mapSize.height, { format: RGFormat, type: HalfFloatType, depthBuffer: false } ); + + } + + + let shadowPassVertical = texture( depthTexture ); + + if ( depthTexture.isArrayTexture ) { + + shadowPassVertical = shadowPassVertical.depth( this.depthLayer ); + + } + + let shadowPassHorizontal = texture( this.vsmShadowMapVertical.texture ); + + if ( depthTexture.isArrayTexture ) { + + shadowPassHorizontal = shadowPassHorizontal.depth( this.depthLayer ); + + } + + const samples = reference( 'blurSamples', 'float', shadow ).setGroup( renderGroup ); + const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup ); + const size = reference( 'mapSize', 'vec2', shadow ).setGroup( renderGroup ); + + let material = this.vsmMaterialVertical || ( this.vsmMaterialVertical = new NodeMaterial() ); + material.fragmentNode = VSMPassVertical( { samples, radius, size, shadowPass: shadowPassVertical, depthLayer: this.depthLayer } ).context( builder.getSharedContext() ); + material.name = 'VSMVertical'; + + material = this.vsmMaterialHorizontal || ( this.vsmMaterialHorizontal = new NodeMaterial() ); + material.fragmentNode = VSMPassHorizontal( { samples, radius, size, shadowPass: shadowPassHorizontal, depthLayer: this.depthLayer } ).context( builder.getSharedContext() ); + material.name = 'VSMHorizontal'; + + } + + // + + const shadowIntensity = reference( 'intensity', 'float', shadow ).setGroup( renderGroup ); + const normalBias = reference( 'normalBias', 'float', shadow ).setGroup( renderGroup ); + + const shadowPosition = lightShadowMatrix( light ).mul( shadowPositionWorld.add( normalWorld.mul( normalBias ) ) ); + const shadowCoord = this.setupShadowCoord( builder, shadowPosition ); + + // + + const filterFn = shadow.filterNode || this.getShadowFilterFn( renderer.shadowMap.type ) || null; + + if ( filterFn === null ) { + + throw new Error( 'THREE.WebGPURenderer: Shadow map type not supported yet.' ); + + } + + const shadowDepthTexture = ( shadowMapType === VSMShadowMap && shadow.isPointLightShadow !== true ) ? this.vsmShadowMapHorizontal.texture : depthTexture; + + const shadowNode = this.setupShadowFilter( builder, { filterFn, shadowTexture: shadowMap.texture, depthTexture: shadowDepthTexture, shadowCoord, shadow, depthLayer: this.depthLayer } ); + + let shadowColor = texture( shadowMap.texture, shadowCoord ); + + if ( depthTexture.isArrayTexture ) { + + shadowColor = shadowColor.depth( this.depthLayer ); + + } + + const shadowOutput = mix( 1, shadowNode.rgb.mix( shadowColor, 1 ), shadowIntensity.mul( shadowColor.a ) ).toVar(); + + this.shadowMap = shadowMap; + this.shadow.map = shadowMap; + + return shadowOutput; + + } + + /** + * The implementation performs the setup of the output node. An output is only + * produces if shadow mapping is globally enabled in the renderer. + * + * @param {NodeBuilder} builder - A reference to the current node builder. + * @return {ShaderCallNodeInternal} The output node. + */ + setup( builder ) { + + if ( builder.renderer.shadowMap.enabled === false ) return; + + return Fn( () => { + + let node = this._node; + + this.setupShadowPosition( builder ); + + if ( node === null ) { + + this._node = node = this.setupShadow( builder ); + + } + + if ( builder.material.shadowNode ) { // @deprecated, r171 + + console.warn( 'THREE.NodeMaterial: ".shadowNode" is deprecated. Use ".castShadowNode" instead.' ); + + } + + if ( builder.material.receivedShadowNode ) { + + node = builder.material.receivedShadowNode( node ); + + } + + return node; + + } )(); + + } + + /** + * Renders the shadow. The logic of this function could be included + * into {@link ShadowNode#updateShadow} however more specialized shadow + * nodes might require a custom shadow map rendering. By having a + * dedicated method, it's easier to overwrite the default behavior. + * + * @param {NodeFrame} frame - A reference to the current node frame. + */ + renderShadow( frame ) { + + const { shadow, shadowMap, light } = this; + const { renderer, scene } = frame; + + shadow.updateMatrices( light ); + + shadowMap.setSize( shadow.mapSize.width, shadow.mapSize.height, shadowMap.depth ); + + renderer.render( scene, shadow.camera ); + + } + + /** + * Updates the shadow. + * + * @param {NodeFrame} frame - A reference to the current node frame. + */ + updateShadow( frame ) { + + const { shadowMap, light, shadow } = this; + const { renderer, scene, camera } = frame; + + const shadowType = renderer.shadowMap.type; + + const depthVersion = shadowMap.depthTexture.version; + this._depthVersionCached = depthVersion; + + const _shadowCameraLayer = shadow.camera.layers.mask; + + if ( ( shadow.camera.layers.mask & 0xFFFFFFFE ) === 0 ) { + + shadow.camera.layers.mask = camera.layers.mask; + + } + + const currentRenderObjectFunction = renderer.getRenderObjectFunction(); + + const currentMRT = renderer.getMRT(); + const useVelocity = currentMRT ? currentMRT.has( 'velocity' ) : false; + + _rendererState = resetRendererAndSceneState( renderer, scene, _rendererState ); + + scene.overrideMaterial = getShadowMaterial( light ); + + renderer.setRenderObjectFunction( getShadowRenderObjectFunction( renderer, shadow, shadowType, useVelocity ) ); + + renderer.setClearColor( 0x000000, 0 ); + + renderer.setRenderTarget( shadowMap ); + + this.renderShadow( frame ); + + renderer.setRenderObjectFunction( currentRenderObjectFunction ); + + // vsm blur pass + + if ( shadowType === VSMShadowMap && shadow.isPointLightShadow !== true ) { + + this.vsmPass( renderer ); + + } + + shadow.camera.layers.mask = _shadowCameraLayer; + + restoreRendererAndSceneState( renderer, scene, _rendererState ); + + } + + /** + * For VSM additional render passes are required. + * + * @param {Renderer} renderer - A reference to the current renderer. + */ + vsmPass( renderer ) { + + const { shadow } = this; + + const depth = this.shadowMap.depth; + this.vsmShadowMapVertical.setSize( shadow.mapSize.width, shadow.mapSize.height, depth ); + this.vsmShadowMapHorizontal.setSize( shadow.mapSize.width, shadow.mapSize.height, depth ); + + renderer.setRenderTarget( this.vsmShadowMapVertical ); + _quadMesh.material = this.vsmMaterialVertical; + _quadMesh.render( renderer ); + + renderer.setRenderTarget( this.vsmShadowMapHorizontal ); + _quadMesh.material = this.vsmMaterialHorizontal; + _quadMesh.render( renderer ); + + } + + /** + * Frees the internal resources of this shadow node. + */ + dispose() { + + this.shadowMap.dispose(); + this.shadowMap = null; + + if ( this.vsmShadowMapVertical !== null ) { + + this.vsmShadowMapVertical.dispose(); + this.vsmShadowMapVertical = null; + + this.vsmMaterialVertical.dispose(); + this.vsmMaterialVertical = null; + + } + + if ( this.vsmShadowMapHorizontal !== null ) { + + this.vsmShadowMapHorizontal.dispose(); + this.vsmShadowMapHorizontal = null; + + this.vsmMaterialHorizontal.dispose(); + this.vsmMaterialHorizontal = null; + + } + + super.dispose(); + + } + + /** + * The implementation performs the update of the shadow map if necessary. + * + * @param {NodeFrame} frame - A reference to the current node frame. + */ + updateBefore( frame ) { + + const { shadow } = this; + + let needsUpdate = shadow.needsUpdate || shadow.autoUpdate; + + if ( needsUpdate ) { + + if ( this._cameraFrameId[ frame.camera ] === frame.frameId ) { + + needsUpdate = false; + + } + + this._cameraFrameId[ frame.camera ] = frame.frameId; + + } + + if ( needsUpdate ) { + + this.updateShadow( frame ); + + if ( this.shadowMap.depthTexture.version === this._depthVersionCached ) { + + shadow.needsUpdate = false; + + } + + } + + } + +} + +/** + * TSL function for creating an instance of `ShadowNode`. + * + * @tsl + * @function + * @param {Light} light - The shadow casting light. + * @param {?LightShadow} [shadow] - The light shadow. + * @return {ShadowNode} The created shadow node. + */ +const shadow = ( light, shadow ) => nodeObject( new ShadowNode( light, shadow ) ); + +const _clearColor$1 = /*@__PURE__*/ new Color(); + +// cubeToUV() maps a 3D direction vector suitable for cube texture mapping to a 2D +// vector suitable for 2D texture mapping. This code uses the following layout for the +// 2D texture: +// +// xzXZ +// y Y +// +// Y - Positive y direction +// y - Negative y direction +// X - Positive x direction +// x - Negative x direction +// Z - Positive z direction +// z - Negative z direction +// +// Source and test bed: +// https://gist.github.com/tschw/da10c43c467ce8afd0c4 + +const cubeToUV = /*@__PURE__*/ Fn( ( [ pos, texelSizeY ] ) => { + + const v = pos.toVar(); + + // Number of texels to avoid at the edge of each square + + const absV = abs( v ); + + // Intersect unit cube + + const scaleToCube = div( 1.0, max$1( absV.x, max$1( absV.y, absV.z ) ) ); + absV.mulAssign( scaleToCube ); + + // Apply scale to avoid seams + + // two texels less per square (one texel will do for NEAREST) + v.mulAssign( scaleToCube.mul( texelSizeY.mul( 2 ).oneMinus() ) ); + + // Unwrap + + // space: -1 ... 1 range for each square + // + // #X## dim := ( 4 , 2 ) + // # # center := ( 1 , 1 ) + + const planar = vec2( v.xy ).toVar(); + + const almostATexel = texelSizeY.mul( 1.5 ); + const almostOne = almostATexel.oneMinus(); + + If( absV.z.greaterThanEqual( almostOne ), () => { + + If( v.z.greaterThan( 0.0 ), () => { + + planar.x.assign( sub( 4.0, v.x ) ); + + } ); + + } ).ElseIf( absV.x.greaterThanEqual( almostOne ), () => { + + const signX = sign( v.x ); + planar.x.assign( v.z.mul( signX ).add( signX.mul( 2.0 ) ) ); + + } ).ElseIf( absV.y.greaterThanEqual( almostOne ), () => { + + const signY = sign( v.y ); + planar.x.assign( v.x.add( signY.mul( 2.0 ) ).add( 2.0 ) ); + planar.y.assign( v.z.mul( signY ).sub( 2.0 ) ); + + } ); + + // Transform to UV space + + // scale := 0.5 / dim + // translate := ( center + 0.5 ) / dim + return vec2( 0.125, 0.25 ).mul( planar ).add( vec2( 0.375, 0.75 ) ).flipY(); + +} ).setLayout( { + name: 'cubeToUV', + type: 'vec2', + inputs: [ + { name: 'pos', type: 'vec3' }, + { name: 'texelSizeY', type: 'float' } + ] +} ); + +const BasicPointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp, texelSize } ) => { + + return texture( depthTexture, cubeToUV( bd3D, texelSize.y ) ).compare( dp ); + +} ); + +const PointShadowFilter = /*@__PURE__*/ Fn( ( { depthTexture, bd3D, dp, texelSize, shadow } ) => { + + const radius = reference( 'radius', 'float', shadow ).setGroup( renderGroup ); + const offset = vec2( -1, 1.0 ).mul( radius ).mul( texelSize.y ); + + return texture( depthTexture, cubeToUV( bd3D.add( offset.xyy ), texelSize.y ) ).compare( dp ) + .add( texture( depthTexture, cubeToUV( bd3D.add( offset.yyy ), texelSize.y ) ).compare( dp ) ) + .add( texture( depthTexture, cubeToUV( bd3D.add( offset.xyx ), texelSize.y ) ).compare( dp ) ) + .add( texture( depthTexture, cubeToUV( bd3D.add( offset.yyx ), texelSize.y ) ).compare( dp ) ) + .add( texture( depthTexture, cubeToUV( bd3D, texelSize.y ) ).compare( dp ) ) + .add( texture( depthTexture, cubeToUV( bd3D.add( offset.xxy ), texelSize.y ) ).compare( dp ) ) + .add( texture( depthTexture, cubeToUV( bd3D.add( offset.yxy ), texelSize.y ) ).compare( dp ) ) + .add( texture( depthTexture, cubeToUV( bd3D.add( offset.xxx ), texelSize.y ) ).compare( dp ) ) + .add( texture( depthTexture, cubeToUV( bd3D.add( offset.yxx ), texelSize.y ) ).compare( dp ) ) + .mul( 1.0 / 9.0 ); + +} ); + +const pointShadowFilter = /*@__PURE__*/ Fn( ( { filterFn, depthTexture, shadowCoord, shadow } ) => { + + // for point lights, the uniform @vShadowCoord is re-purposed to hold + // the vector from the light to the world-space position of the fragment. + const lightToPosition = shadowCoord.xyz.toVar(); + const lightToPositionLength = lightToPosition.length(); + + const cameraNearLocal = uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.near ); + const cameraFarLocal = uniform( 'float' ).setGroup( renderGroup ).onRenderUpdate( () => shadow.camera.far ); + const bias = reference( 'bias', 'float', shadow ).setGroup( renderGroup ); + const mapSize = uniform( shadow.mapSize ).setGroup( renderGroup ); + + const result = float( 1.0 ).toVar(); + + If( lightToPositionLength.sub( cameraFarLocal ).lessThanEqual( 0.0 ).and( lightToPositionLength.sub( cameraNearLocal ).greaterThanEqual( 0.0 ) ), () => { + + // dp = normalized distance from light to fragment position + const dp = lightToPositionLength.sub( cameraNearLocal ).div( cameraFarLocal.sub( cameraNearLocal ) ).toVar(); // need to clamp? + dp.addAssign( bias ); + + // bd3D = base direction 3D + const bd3D = lightToPosition.normalize(); + const texelSize = vec2( 1.0 ).div( mapSize.mul( vec2( 4.0, 2.0 ) ) ); + + // percentage-closer filtering + result.assign( filterFn( { depthTexture, bd3D, dp, texelSize, shadow } ) ); + + } ); + + return result; + +} ); + +const _viewport = /*@__PURE__*/ new Vector4(); +const _viewportSize = /*@__PURE__*/ new Vector2(); +const _shadowMapSize = /*@__PURE__*/ new Vector2(); + + +/** + * Represents the shadow implementation for point light nodes. + * + * @augments ShadowNode + */ +class PointShadowNode extends ShadowNode { + + static get type() { + + return 'PointShadowNode'; + + } + + /** + * Constructs a new point shadow node. + * + * @param {PointLight} light - The shadow casting point light. + * @param {?PointLightShadow} [shadow=null] - An optional point light shadow. + */ + constructor( light, shadow = null ) { + + super( light, shadow ); + + } + + /** + * Overwrites the default implementation to return point light shadow specific + * filtering functions. + * + * @param {number} type - The shadow type. + * @return {Function} The filtering function. + */ + getShadowFilterFn( type ) { + + return type === BasicShadowMap ? BasicPointShadowFilter : PointShadowFilter; + + } + + /** + * Overwrites the default implementation so the unaltered shadow position is used. + * + * @param {NodeBuilder} builder - A reference to the current node builder. + * @param {Node} shadowPosition - A node representing the shadow position. + * @return {Node} The shadow coordinates. + */ + setupShadowCoord( builder, shadowPosition ) { + + return shadowPosition; + + } + + /** + * Overwrites the default implementation to only use point light specific + * shadow filter functions. + * + * @param {NodeBuilder} builder - A reference to the current node builder. + * @param {Object} inputs - A configuration object that defines the shadow filtering. + * @param {Function} inputs.filterFn - This function defines the filtering type of the shadow map e.g. PCF. + * @param {Texture} inputs.shadowTexture - A reference to the shadow map's texture. + * @param {DepthTexture} inputs.depthTexture - A reference to the shadow map's texture data. + * @param {Node} inputs.shadowCoord - Shadow coordinates which are used to sample from the shadow map. + * @param {LightShadow} inputs.shadow - The light shadow. + * @return {Node} The result node of the shadow filtering. + */ + setupShadowFilter( builder, { filterFn, shadowTexture, depthTexture, shadowCoord, shadow } ) { + + return pointShadowFilter( { filterFn, shadowTexture, depthTexture, shadowCoord, shadow } ); + + } + + /** + * Overwrites the default implementation with point light specific + * rendering code. + * + * @param {NodeFrame} frame - A reference to the current node frame. + */ + renderShadow( frame ) { + + const { shadow, shadowMap, light } = this; + const { renderer, scene } = frame; + + const shadowFrameExtents = shadow.getFrameExtents(); + + _shadowMapSize.copy( shadow.mapSize ); + _shadowMapSize.multiply( shadowFrameExtents ); + + shadowMap.setSize( _shadowMapSize.width, _shadowMapSize.height ); + + _viewportSize.copy( shadow.mapSize ); + + // + + const previousAutoClear = renderer.autoClear; + + const previousClearColor = renderer.getClearColor( _clearColor$1 ); + const previousClearAlpha = renderer.getClearAlpha(); + + renderer.autoClear = false; + renderer.setClearColor( shadow.clearColor, shadow.clearAlpha ); + renderer.clear(); + + const viewportCount = shadow.getViewportCount(); + + for ( let vp = 0; vp < viewportCount; vp ++ ) { + + const viewport = shadow.getViewport( vp ); + + const x = _viewportSize.x * viewport.x; + const y = _shadowMapSize.y - _viewportSize.y - ( _viewportSize.y * viewport.y ); + + _viewport.set( + x, + y, + _viewportSize.x * viewport.z, + _viewportSize.y * viewport.w + ); + + shadowMap.viewport.copy( _viewport ); + + shadow.updateMatrices( light, vp ); + + renderer.render( scene, shadow.camera ); + + } + + // + + renderer.autoClear = previousAutoClear; + renderer.setClearColor( previousClearColor, previousClearAlpha ); + + } + +} + +/** + * TSL function for creating an instance of `PointShadowNode`. + * + * @tsl + * @function + * @param {PointLight} light - The shadow casting point light. + * @param {?PointLightShadow} [shadow=null] - An optional point light shadow. + * @return {PointShadowNode} The created point shadow node. + */ +const pointShadow = ( light, shadow ) => nodeObject( new PointShadowNode( light, shadow ) ); + +/** + * Base class for analytic light nodes. + * + * @augments LightingNode + */ +class AnalyticLightNode extends LightingNode { + + static get type() { + + return 'AnalyticLightNode'; + + } + + /** + * Constructs a new analytic light node. + * + * @param {?Light} [light=null] - The light source. + */ + constructor( light = null ) { + + super(); + + /** + * The light source. + * + * @type {?Light} + * @default null + */ + this.light = light; + + /** + * The light's color value. + * + * @type {Color} + */ + this.color = new Color(); + + /** + * The light's color node. Points to `colorNode` of the light source, if set. Otherwise + * it creates a uniform node based on {@link AnalyticLightNode#color}. + * + * @type {Node} + */ + this.colorNode = ( light && light.colorNode ) || uniform( this.color ).setGroup( renderGroup ); + + /** + * This property is used to retain a reference to the original value of {@link AnalyticLightNode#colorNode}. + * The final color node is represented by a different node when using shadows. + * + * @type {?Node} + * @default null + */ + this.baseColorNode = null; + + /** + * Represents the light's shadow. + * + * @type {?ShadowNode} + * @default null + */ + this.shadowNode = null; + + /** + * Represents the light's shadow color. + * + * @type {?Node} + * @default null + */ + this.shadowColorNode = null; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isAnalyticLightNode = true; + + /** + * Overwritten since analytic light nodes are updated + * once per frame. + * + * @type {string} + * @default 'frame' + */ + this.updateType = NodeUpdateType.FRAME; + + } + + getHash() { + + return this.light.uuid; + + } + + /** + * Returns a node representing a direction vector which points from the current + * position in view space to the light's position in view space. + * + * @param {NodeBuilder} builder - The builder object used for setting up the light. + * @return {Node} The light vector node. + */ + getLightVector( builder ) { + + return lightViewPosition( this.light ).sub( builder.context.positionView || positionView ); + + } + + /** + * Sets up the direct lighting for the analytic light node. + * + * @abstract + * @param {NodeBuilder} builder - The builder object used for setting up the light. + * @return {Object|undefined} The direct light data (color and direction). + */ + setupDirect( /*builder*/ ) { } + + /** + * Sets up the direct rect area lighting for the analytic light node. + * + * @abstract + * @param {NodeBuilder} builder - The builder object used for setting up the light. + * @return {Object|undefined} The direct rect area light data. + */ + setupDirectRectArea( /*builder*/ ) { } + + /** + * Setups the shadow node for this light. The method exists so concrete light classes + * can setup different types of shadow nodes. + * + * @return {ShadowNode} The created shadow node. + */ + setupShadowNode() { + + return shadow( this.light ); + + } + + /** + * Setups the shadow for this light. This method is only executed if the light + * cast shadows and the current build object receives shadows. It incorporates + * shadows into the lighting computation. + * + * @param {NodeBuilder} builder - The current node builder. + */ + setupShadow( builder ) { + + const { renderer } = builder; + + if ( renderer.shadowMap.enabled === false ) return; + + let shadowColorNode = this.shadowColorNode; + + if ( shadowColorNode === null ) { + + const customShadowNode = this.light.shadow.shadowNode; + + let shadowNode; + + if ( customShadowNode !== undefined ) { + + shadowNode = nodeObject( customShadowNode ); + + } else { + + shadowNode = this.setupShadowNode(); + + } + + this.shadowNode = shadowNode; + + this.shadowColorNode = shadowColorNode = this.colorNode.mul( shadowNode ); + + this.baseColorNode = this.colorNode; + + } + + // + + this.colorNode = shadowColorNode; + + } + + /** + * Unlike most other nodes, lighting nodes do not return a output node in {@link Node#setup}. + * The main purpose of lighting nodes is to configure the current {@link LightingModel} and/or + * invocate the respective interface methods. + * + * @param {NodeBuilder} builder - The current node builder. + */ + setup( builder ) { + + this.colorNode = this.baseColorNode || this.colorNode; + + if ( this.light.castShadow ) { + + if ( builder.object.receiveShadow ) { + + this.setupShadow( builder ); + + } + + } else if ( this.shadowNode !== null ) { + + this.shadowNode.dispose(); + this.shadowNode = null; + this.shadowColorNode = null; + + } + + const directLightData = this.setupDirect( builder ); + const directRectAreaLightData = this.setupDirectRectArea( builder ); + + if ( directLightData ) { + + builder.lightsNode.setupDirectLight( builder, this, directLightData ); + + } + + if ( directRectAreaLightData ) { + + builder.lightsNode.setupDirectRectAreaLight( builder, this, directRectAreaLightData ); + + } + + } + + /** + * The update method is used to update light uniforms per frame. + * Potentially overwritten in concrete light nodes to update light + * specific uniforms. + * + * @param {NodeFrame} frame - A reference to the current node frame. + */ + update( /*frame*/ ) { + + const { light } = this; + + this.color.copy( light.color ).multiplyScalar( light.intensity ); + + } + +} + +/** + * Represents a `discard` shader operation in TSL. + * + * @method + * @param {Object} inputs - The input parameter object. + * @param {Node} inputs.lightDistance - The distance of the light's position to the current fragment position. + * @param {Node} inputs.cutoffDistance - The light's cutoff distance. + * @param {Node} inputs.decayExponent - The light's decay exponent. + * @return {Node} The distance falloff. + */ +const getDistanceAttenuation = /*@__PURE__*/ Fn( ( { lightDistance, cutoffDistance, decayExponent } ) => { + + // based upon Frostbite 3 Moving to Physically-based Rendering + // page 32, equation 26: E[window1] + // https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf + const distanceFalloff = lightDistance.pow( decayExponent ).max( 0.01 ).reciprocal(); + + return cutoffDistance.greaterThan( 0 ).select( + distanceFalloff.mul( lightDistance.div( cutoffDistance ).pow4().oneMinus().clamp().pow2() ), + distanceFalloff + ); + +} ); // validated + +const directPointLight = ( { color, lightVector, cutoffDistance, decayExponent } ) => { + + const lightDirection = lightVector.normalize(); + const lightDistance = lightVector.length(); + + const attenuation = getDistanceAttenuation( { + lightDistance, + cutoffDistance, + decayExponent + } ); + + const lightColor = color.mul( attenuation ); + + return { lightDirection, lightColor }; + +}; + +/** + * Module for representing point lights as nodes. + * + * @augments AnalyticLightNode + */ +class PointLightNode extends AnalyticLightNode { + + static get type() { + + return 'PointLightNode'; + + } + + /** + * Constructs a new point light node. + * + * @param {?PointLight} [light=null] - The point light source. + */ + constructor( light = null ) { + + super( light ); + + /** + * Uniform node representing the cutoff distance. + * + * @type {UniformNode} + */ + this.cutoffDistanceNode = uniform( 0 ).setGroup( renderGroup ); + + /** + * Uniform node representing the decay exponent. + * + * @type {UniformNode} + */ + this.decayExponentNode = uniform( 2 ).setGroup( renderGroup ); + + } + + /** + * Overwritten to updated point light specific uniforms. + * + * @param {NodeFrame} frame - A reference to the current node frame. + */ + update( frame ) { + + const { light } = this; + + super.update( frame ); + + this.cutoffDistanceNode.value = light.distance; + this.decayExponentNode.value = light.decay; + + } + + /** + * Overwritten to setup point light specific shadow. + * + * @return {PointShadowNode} + */ + setupShadowNode() { + + return pointShadow( this.light ); + + } + + setupDirect( builder ) { + + return directPointLight( { + color: this.colorNode, + lightVector: this.getLightVector( builder ), + cutoffDistance: this.cutoffDistanceNode, + decayExponent: this.decayExponentNode + } ); + + } + +} + +/** + * Creates a 2x2 checkerboard pattern that can be used as procedural texture data. + * + * @tsl + * @function + * @param {Node} coord - The uv coordinates. + * @return {Node} The result data. + */ +const checker = /*@__PURE__*/ Fn( ( [ coord = uv$1() ] ) => { + + const uv = coord.mul( 2.0 ); + + const cx = uv.x.floor(); + const cy = uv.y.floor(); + const result = cx.add( cy ).mod( 2.0 ); + + return result.sign(); + +} ); + +/** + * Generates a circle based on the uv coordinates. + * + * @tsl + * @function + * @param {Node} coord - The uv to generate the circle. + * @return {Node} The circle shape. + */ +const shapeCircle = Fn( ( [ coord = uv$1() ], { renderer, material } ) => { + + const len2 = lengthSq( coord.mul( 2 ).sub( 1 ) ); + + let alpha; + + if ( material.alphaToCoverage && renderer.samples > 1 ) { + + const dlen = float( len2.fwidth() ).toVar(); + + alpha = smoothstep( dlen.oneMinus(), dlen.add( 1 ), len2 ).oneMinus(); + + } else { + + alpha = select( len2.greaterThan( 1.0 ), 0, 1 ); + + } + + return alpha; + +} ); + +// Three.js Transpiler +// https://raw.githubusercontent.com/AcademySoftwareFoundation/MaterialX/main/libraries/stdlib/genglsl/lib/mx_noise.glsl + + + +const mx_select = /*@__PURE__*/ Fn( ( [ b_immutable, t_immutable, f_immutable ] ) => { + + const f = float( f_immutable ).toVar(); + const t = float( t_immutable ).toVar(); + const b = bool( b_immutable ).toVar(); + + return select( b, t, f ); + +} ).setLayout( { + name: 'mx_select', + type: 'float', + inputs: [ + { name: 'b', type: 'bool' }, + { name: 't', type: 'float' }, + { name: 'f', type: 'float' } + ] +} ); + +const mx_negate_if = /*@__PURE__*/ Fn( ( [ val_immutable, b_immutable ] ) => { + + const b = bool( b_immutable ).toVar(); + const val = float( val_immutable ).toVar(); + + return select( b, val.negate(), val ); + +} ).setLayout( { + name: 'mx_negate_if', + type: 'float', + inputs: [ + { name: 'val', type: 'float' }, + { name: 'b', type: 'bool' } + ] +} ); + +const mx_floor = /*@__PURE__*/ Fn( ( [ x_immutable ] ) => { + + const x = float( x_immutable ).toVar(); + + return int( floor( x ) ); + +} ).setLayout( { + name: 'mx_floor', + type: 'int', + inputs: [ + { name: 'x', type: 'float' } + ] +} ); + +const mx_floorfrac = /*@__PURE__*/ Fn( ( [ x_immutable, i ] ) => { + + const x = float( x_immutable ).toVar(); + i.assign( mx_floor( x ) ); + + return x.sub( float( i ) ); + +} ); + +const mx_bilerp_0 = /*@__PURE__*/ Fn( ( [ v0_immutable, v1_immutable, v2_immutable, v3_immutable, s_immutable, t_immutable ] ) => { + + const t = float( t_immutable ).toVar(); + const s = float( s_immutable ).toVar(); + const v3 = float( v3_immutable ).toVar(); + const v2 = float( v2_immutable ).toVar(); + const v1 = float( v1_immutable ).toVar(); + const v0 = float( v0_immutable ).toVar(); + const s1 = float( sub( 1.0, s ) ).toVar(); + + return sub( 1.0, t ).mul( v0.mul( s1 ).add( v1.mul( s ) ) ).add( t.mul( v2.mul( s1 ).add( v3.mul( s ) ) ) ); + +} ).setLayout( { + name: 'mx_bilerp_0', + type: 'float', + inputs: [ + { name: 'v0', type: 'float' }, + { name: 'v1', type: 'float' }, + { name: 'v2', type: 'float' }, + { name: 'v3', type: 'float' }, + { name: 's', type: 'float' }, + { name: 't', type: 'float' } + ] +} ); + +const mx_bilerp_1 = /*@__PURE__*/ Fn( ( [ v0_immutable, v1_immutable, v2_immutable, v3_immutable, s_immutable, t_immutable ] ) => { + + const t = float( t_immutable ).toVar(); + const s = float( s_immutable ).toVar(); + const v3 = vec3( v3_immutable ).toVar(); + const v2 = vec3( v2_immutable ).toVar(); + const v1 = vec3( v1_immutable ).toVar(); + const v0 = vec3( v0_immutable ).toVar(); + const s1 = float( sub( 1.0, s ) ).toVar(); + + return sub( 1.0, t ).mul( v0.mul( s1 ).add( v1.mul( s ) ) ).add( t.mul( v2.mul( s1 ).add( v3.mul( s ) ) ) ); + +} ).setLayout( { + name: 'mx_bilerp_1', + type: 'vec3', + inputs: [ + { name: 'v0', type: 'vec3' }, + { name: 'v1', type: 'vec3' }, + { name: 'v2', type: 'vec3' }, + { name: 'v3', type: 'vec3' }, + { name: 's', type: 'float' }, + { name: 't', type: 'float' } + ] +} ); + +const mx_bilerp = /*@__PURE__*/ overloadingFn( [ mx_bilerp_0, mx_bilerp_1 ] ); + +const mx_trilerp_0 = /*@__PURE__*/ Fn( ( [ v0_immutable, v1_immutable, v2_immutable, v3_immutable, v4_immutable, v5_immutable, v6_immutable, v7_immutable, s_immutable, t_immutable, r_immutable ] ) => { + + const r = float( r_immutable ).toVar(); + const t = float( t_immutable ).toVar(); + const s = float( s_immutable ).toVar(); + const v7 = float( v7_immutable ).toVar(); + const v6 = float( v6_immutable ).toVar(); + const v5 = float( v5_immutable ).toVar(); + const v4 = float( v4_immutable ).toVar(); + const v3 = float( v3_immutable ).toVar(); + const v2 = float( v2_immutable ).toVar(); + const v1 = float( v1_immutable ).toVar(); + const v0 = float( v0_immutable ).toVar(); + const s1 = float( sub( 1.0, s ) ).toVar(); + const t1 = float( sub( 1.0, t ) ).toVar(); + const r1 = float( sub( 1.0, r ) ).toVar(); + + return r1.mul( t1.mul( v0.mul( s1 ).add( v1.mul( s ) ) ).add( t.mul( v2.mul( s1 ).add( v3.mul( s ) ) ) ) ).add( r.mul( t1.mul( v4.mul( s1 ).add( v5.mul( s ) ) ).add( t.mul( v6.mul( s1 ).add( v7.mul( s ) ) ) ) ) ); + +} ).setLayout( { + name: 'mx_trilerp_0', + type: 'float', + inputs: [ + { name: 'v0', type: 'float' }, + { name: 'v1', type: 'float' }, + { name: 'v2', type: 'float' }, + { name: 'v3', type: 'float' }, + { name: 'v4', type: 'float' }, + { name: 'v5', type: 'float' }, + { name: 'v6', type: 'float' }, + { name: 'v7', type: 'float' }, + { name: 's', type: 'float' }, + { name: 't', type: 'float' }, + { name: 'r', type: 'float' } + ] +} ); + +const mx_trilerp_1 = /*@__PURE__*/ Fn( ( [ v0_immutable, v1_immutable, v2_immutable, v3_immutable, v4_immutable, v5_immutable, v6_immutable, v7_immutable, s_immutable, t_immutable, r_immutable ] ) => { + + const r = float( r_immutable ).toVar(); + const t = float( t_immutable ).toVar(); + const s = float( s_immutable ).toVar(); + const v7 = vec3( v7_immutable ).toVar(); + const v6 = vec3( v6_immutable ).toVar(); + const v5 = vec3( v5_immutable ).toVar(); + const v4 = vec3( v4_immutable ).toVar(); + const v3 = vec3( v3_immutable ).toVar(); + const v2 = vec3( v2_immutable ).toVar(); + const v1 = vec3( v1_immutable ).toVar(); + const v0 = vec3( v0_immutable ).toVar(); + const s1 = float( sub( 1.0, s ) ).toVar(); + const t1 = float( sub( 1.0, t ) ).toVar(); + const r1 = float( sub( 1.0, r ) ).toVar(); + + return r1.mul( t1.mul( v0.mul( s1 ).add( v1.mul( s ) ) ).add( t.mul( v2.mul( s1 ).add( v3.mul( s ) ) ) ) ).add( r.mul( t1.mul( v4.mul( s1 ).add( v5.mul( s ) ) ).add( t.mul( v6.mul( s1 ).add( v7.mul( s ) ) ) ) ) ); + +} ).setLayout( { + name: 'mx_trilerp_1', + type: 'vec3', + inputs: [ + { name: 'v0', type: 'vec3' }, + { name: 'v1', type: 'vec3' }, + { name: 'v2', type: 'vec3' }, + { name: 'v3', type: 'vec3' }, + { name: 'v4', type: 'vec3' }, + { name: 'v5', type: 'vec3' }, + { name: 'v6', type: 'vec3' }, + { name: 'v7', type: 'vec3' }, + { name: 's', type: 'float' }, + { name: 't', type: 'float' }, + { name: 'r', type: 'float' } + ] +} ); + +const mx_trilerp = /*@__PURE__*/ overloadingFn( [ mx_trilerp_0, mx_trilerp_1 ] ); + +const mx_gradient_float_0 = /*@__PURE__*/ Fn( ( [ hash_immutable, x_immutable, y_immutable ] ) => { + + const y = float( y_immutable ).toVar(); + const x = float( x_immutable ).toVar(); + const hash = uint( hash_immutable ).toVar(); + const h = uint( hash.bitAnd( uint( 7 ) ) ).toVar(); + const u = float( mx_select( h.lessThan( uint( 4 ) ), x, y ) ).toVar(); + const v = float( mul( 2.0, mx_select( h.lessThan( uint( 4 ) ), y, x ) ) ).toVar(); + + return mx_negate_if( u, bool( h.bitAnd( uint( 1 ) ) ) ).add( mx_negate_if( v, bool( h.bitAnd( uint( 2 ) ) ) ) ); + +} ).setLayout( { + name: 'mx_gradient_float_0', + type: 'float', + inputs: [ + { name: 'hash', type: 'uint' }, + { name: 'x', type: 'float' }, + { name: 'y', type: 'float' } + ] +} ); + +const mx_gradient_float_1 = /*@__PURE__*/ Fn( ( [ hash_immutable, x_immutable, y_immutable, z_immutable ] ) => { + + const z = float( z_immutable ).toVar(); + const y = float( y_immutable ).toVar(); + const x = float( x_immutable ).toVar(); + const hash = uint( hash_immutable ).toVar(); + const h = uint( hash.bitAnd( uint( 15 ) ) ).toVar(); + const u = float( mx_select( h.lessThan( uint( 8 ) ), x, y ) ).toVar(); + const v = float( mx_select( h.lessThan( uint( 4 ) ), y, mx_select( h.equal( uint( 12 ) ).or( h.equal( uint( 14 ) ) ), x, z ) ) ).toVar(); + + return mx_negate_if( u, bool( h.bitAnd( uint( 1 ) ) ) ).add( mx_negate_if( v, bool( h.bitAnd( uint( 2 ) ) ) ) ); + +} ).setLayout( { + name: 'mx_gradient_float_1', + type: 'float', + inputs: [ + { name: 'hash', type: 'uint' }, + { name: 'x', type: 'float' }, + { name: 'y', type: 'float' }, + { name: 'z', type: 'float' } + ] +} ); + +const mx_gradient_float = /*@__PURE__*/ overloadingFn( [ mx_gradient_float_0, mx_gradient_float_1 ] ); + +const mx_gradient_vec3_0 = /*@__PURE__*/ Fn( ( [ hash_immutable, x_immutable, y_immutable ] ) => { + + const y = float( y_immutable ).toVar(); + const x = float( x_immutable ).toVar(); + const hash = uvec3( hash_immutable ).toVar(); + + return vec3( mx_gradient_float( hash.x, x, y ), mx_gradient_float( hash.y, x, y ), mx_gradient_float( hash.z, x, y ) ); + +} ).setLayout( { + name: 'mx_gradient_vec3_0', + type: 'vec3', + inputs: [ + { name: 'hash', type: 'uvec3' }, + { name: 'x', type: 'float' }, + { name: 'y', type: 'float' } + ] +} ); + +const mx_gradient_vec3_1 = /*@__PURE__*/ Fn( ( [ hash_immutable, x_immutable, y_immutable, z_immutable ] ) => { + + const z = float( z_immutable ).toVar(); + const y = float( y_immutable ).toVar(); + const x = float( x_immutable ).toVar(); + const hash = uvec3( hash_immutable ).toVar(); + + return vec3( mx_gradient_float( hash.x, x, y, z ), mx_gradient_float( hash.y, x, y, z ), mx_gradient_float( hash.z, x, y, z ) ); + +} ).setLayout( { + name: 'mx_gradient_vec3_1', + type: 'vec3', + inputs: [ + { name: 'hash', type: 'uvec3' }, + { name: 'x', type: 'float' }, + { name: 'y', type: 'float' }, + { name: 'z', type: 'float' } + ] +} ); + +const mx_gradient_vec3 = /*@__PURE__*/ overloadingFn( [ mx_gradient_vec3_0, mx_gradient_vec3_1 ] ); + +const mx_gradient_scale2d_0 = /*@__PURE__*/ Fn( ( [ v_immutable ] ) => { + + const v = float( v_immutable ).toVar(); + + return mul( 0.6616, v ); + +} ).setLayout( { + name: 'mx_gradient_scale2d_0', + type: 'float', + inputs: [ + { name: 'v', type: 'float' } + ] +} ); + +const mx_gradient_scale3d_0 = /*@__PURE__*/ Fn( ( [ v_immutable ] ) => { + + const v = float( v_immutable ).toVar(); + + return mul( 0.9820, v ); + +} ).setLayout( { + name: 'mx_gradient_scale3d_0', + type: 'float', + inputs: [ + { name: 'v', type: 'float' } + ] +} ); + +const mx_gradient_scale2d_1 = /*@__PURE__*/ Fn( ( [ v_immutable ] ) => { + + const v = vec3( v_immutable ).toVar(); + + return mul( 0.6616, v ); + +} ).setLayout( { + name: 'mx_gradient_scale2d_1', + type: 'vec3', + inputs: [ + { name: 'v', type: 'vec3' } + ] +} ); + +const mx_gradient_scale2d = /*@__PURE__*/ overloadingFn( [ mx_gradient_scale2d_0, mx_gradient_scale2d_1 ] ); + +const mx_gradient_scale3d_1 = /*@__PURE__*/ Fn( ( [ v_immutable ] ) => { + + const v = vec3( v_immutable ).toVar(); + + return mul( 0.9820, v ); + +} ).setLayout( { + name: 'mx_gradient_scale3d_1', + type: 'vec3', + inputs: [ + { name: 'v', type: 'vec3' } + ] +} ); + +const mx_gradient_scale3d = /*@__PURE__*/ overloadingFn( [ mx_gradient_scale3d_0, mx_gradient_scale3d_1 ] ); + +const mx_rotl32 = /*@__PURE__*/ Fn( ( [ x_immutable, k_immutable ] ) => { + + const k = int( k_immutable ).toVar(); + const x = uint( x_immutable ).toVar(); + + return x.shiftLeft( k ).bitOr( x.shiftRight( int( 32 ).sub( k ) ) ); + +} ).setLayout( { + name: 'mx_rotl32', + type: 'uint', + inputs: [ + { name: 'x', type: 'uint' }, + { name: 'k', type: 'int' } + ] +} ); + +const mx_bjmix = /*@__PURE__*/ Fn( ( [ a, b, c ] ) => { + + a.subAssign( c ); + a.bitXorAssign( mx_rotl32( c, int( 4 ) ) ); + c.addAssign( b ); + b.subAssign( a ); + b.bitXorAssign( mx_rotl32( a, int( 6 ) ) ); + a.addAssign( c ); + c.subAssign( b ); + c.bitXorAssign( mx_rotl32( b, int( 8 ) ) ); + b.addAssign( a ); + a.subAssign( c ); + a.bitXorAssign( mx_rotl32( c, int( 16 ) ) ); + c.addAssign( b ); + b.subAssign( a ); + b.bitXorAssign( mx_rotl32( a, int( 19 ) ) ); + a.addAssign( c ); + c.subAssign( b ); + c.bitXorAssign( mx_rotl32( b, int( 4 ) ) ); + b.addAssign( a ); + +} ); + +const mx_bjfinal = /*@__PURE__*/ Fn( ( [ a_immutable, b_immutable, c_immutable ] ) => { + + const c = uint( c_immutable ).toVar(); + const b = uint( b_immutable ).toVar(); + const a = uint( a_immutable ).toVar(); + c.bitXorAssign( b ); + c.subAssign( mx_rotl32( b, int( 14 ) ) ); + a.bitXorAssign( c ); + a.subAssign( mx_rotl32( c, int( 11 ) ) ); + b.bitXorAssign( a ); + b.subAssign( mx_rotl32( a, int( 25 ) ) ); + c.bitXorAssign( b ); + c.subAssign( mx_rotl32( b, int( 16 ) ) ); + a.bitXorAssign( c ); + a.subAssign( mx_rotl32( c, int( 4 ) ) ); + b.bitXorAssign( a ); + b.subAssign( mx_rotl32( a, int( 14 ) ) ); + c.bitXorAssign( b ); + c.subAssign( mx_rotl32( b, int( 24 ) ) ); + + return c; + +} ).setLayout( { + name: 'mx_bjfinal', + type: 'uint', + inputs: [ + { name: 'a', type: 'uint' }, + { name: 'b', type: 'uint' }, + { name: 'c', type: 'uint' } + ] +} ); + +const mx_bits_to_01 = /*@__PURE__*/ Fn( ( [ bits_immutable ] ) => { + + const bits = uint( bits_immutable ).toVar(); + + return float( bits ).div( float( uint( int( 0xffffffff ) ) ) ); + +} ).setLayout( { + name: 'mx_bits_to_01', + type: 'float', + inputs: [ + { name: 'bits', type: 'uint' } + ] +} ); + +const mx_fade = /*@__PURE__*/ Fn( ( [ t_immutable ] ) => { + + const t = float( t_immutable ).toVar(); + + return t.mul( t ).mul( t ).mul( t.mul( t.mul( 6.0 ).sub( 15.0 ) ).add( 10.0 ) ); + +} ).setLayout( { + name: 'mx_fade', + type: 'float', + inputs: [ + { name: 't', type: 'float' } + ] +} ); + +const mx_hash_int_0 = /*@__PURE__*/ Fn( ( [ x_immutable ] ) => { + + const x = int( x_immutable ).toVar(); + const len = uint( uint( 1 ) ).toVar(); + const seed = uint( uint( int( 0xdeadbeef ) ).add( len.shiftLeft( uint( 2 ) ) ).add( uint( 13 ) ) ).toVar(); + + return mx_bjfinal( seed.add( uint( x ) ), seed, seed ); + +} ).setLayout( { + name: 'mx_hash_int_0', + type: 'uint', + inputs: [ + { name: 'x', type: 'int' } + ] +} ); + +const mx_hash_int_1 = /*@__PURE__*/ Fn( ( [ x_immutable, y_immutable ] ) => { + + const y = int( y_immutable ).toVar(); + const x = int( x_immutable ).toVar(); + const len = uint( uint( 2 ) ).toVar(); + const a = uint().toVar(), b = uint().toVar(), c = uint().toVar(); + a.assign( b.assign( c.assign( uint( int( 0xdeadbeef ) ).add( len.shiftLeft( uint( 2 ) ) ).add( uint( 13 ) ) ) ) ); + a.addAssign( uint( x ) ); + b.addAssign( uint( y ) ); + + return mx_bjfinal( a, b, c ); + +} ).setLayout( { + name: 'mx_hash_int_1', + type: 'uint', + inputs: [ + { name: 'x', type: 'int' }, + { name: 'y', type: 'int' } + ] +} ); + +const mx_hash_int_2 = /*@__PURE__*/ Fn( ( [ x_immutable, y_immutable, z_immutable ] ) => { + + const z = int( z_immutable ).toVar(); + const y = int( y_immutable ).toVar(); + const x = int( x_immutable ).toVar(); + const len = uint( uint( 3 ) ).toVar(); + const a = uint().toVar(), b = uint().toVar(), c = uint().toVar(); + a.assign( b.assign( c.assign( uint( int( 0xdeadbeef ) ).add( len.shiftLeft( uint( 2 ) ) ).add( uint( 13 ) ) ) ) ); + a.addAssign( uint( x ) ); + b.addAssign( uint( y ) ); + c.addAssign( uint( z ) ); + + return mx_bjfinal( a, b, c ); + +} ).setLayout( { + name: 'mx_hash_int_2', + type: 'uint', + inputs: [ + { name: 'x', type: 'int' }, + { name: 'y', type: 'int' }, + { name: 'z', type: 'int' } + ] +} ); + +const mx_hash_int_3 = /*@__PURE__*/ Fn( ( [ x_immutable, y_immutable, z_immutable, xx_immutable ] ) => { + + const xx = int( xx_immutable ).toVar(); + const z = int( z_immutable ).toVar(); + const y = int( y_immutable ).toVar(); + const x = int( x_immutable ).toVar(); + const len = uint( uint( 4 ) ).toVar(); + const a = uint().toVar(), b = uint().toVar(), c = uint().toVar(); + a.assign( b.assign( c.assign( uint( int( 0xdeadbeef ) ).add( len.shiftLeft( uint( 2 ) ) ).add( uint( 13 ) ) ) ) ); + a.addAssign( uint( x ) ); + b.addAssign( uint( y ) ); + c.addAssign( uint( z ) ); + mx_bjmix( a, b, c ); + a.addAssign( uint( xx ) ); + + return mx_bjfinal( a, b, c ); + +} ).setLayout( { + name: 'mx_hash_int_3', + type: 'uint', + inputs: [ + { name: 'x', type: 'int' }, + { name: 'y', type: 'int' }, + { name: 'z', type: 'int' }, + { name: 'xx', type: 'int' } + ] +} ); + +const mx_hash_int_4 = /*@__PURE__*/ Fn( ( [ x_immutable, y_immutable, z_immutable, xx_immutable, yy_immutable ] ) => { + + const yy = int( yy_immutable ).toVar(); + const xx = int( xx_immutable ).toVar(); + const z = int( z_immutable ).toVar(); + const y = int( y_immutable ).toVar(); + const x = int( x_immutable ).toVar(); + const len = uint( uint( 5 ) ).toVar(); + const a = uint().toVar(), b = uint().toVar(), c = uint().toVar(); + a.assign( b.assign( c.assign( uint( int( 0xdeadbeef ) ).add( len.shiftLeft( uint( 2 ) ) ).add( uint( 13 ) ) ) ) ); + a.addAssign( uint( x ) ); + b.addAssign( uint( y ) ); + c.addAssign( uint( z ) ); + mx_bjmix( a, b, c ); + a.addAssign( uint( xx ) ); + b.addAssign( uint( yy ) ); + + return mx_bjfinal( a, b, c ); + +} ).setLayout( { + name: 'mx_hash_int_4', + type: 'uint', + inputs: [ + { name: 'x', type: 'int' }, + { name: 'y', type: 'int' }, + { name: 'z', type: 'int' }, + { name: 'xx', type: 'int' }, + { name: 'yy', type: 'int' } + ] +} ); + +const mx_hash_int = /*@__PURE__*/ overloadingFn( [ mx_hash_int_0, mx_hash_int_1, mx_hash_int_2, mx_hash_int_3, mx_hash_int_4 ] ); + +const mx_hash_vec3_0 = /*@__PURE__*/ Fn( ( [ x_immutable, y_immutable ] ) => { + + const y = int( y_immutable ).toVar(); + const x = int( x_immutable ).toVar(); + const h = uint( mx_hash_int( x, y ) ).toVar(); + const result = uvec3().toVar(); + result.x.assign( h.bitAnd( int( 0xFF ) ) ); + result.y.assign( h.shiftRight( int( 8 ) ).bitAnd( int( 0xFF ) ) ); + result.z.assign( h.shiftRight( int( 16 ) ).bitAnd( int( 0xFF ) ) ); + + return result; + +} ).setLayout( { + name: 'mx_hash_vec3_0', + type: 'uvec3', + inputs: [ + { name: 'x', type: 'int' }, + { name: 'y', type: 'int' } + ] +} ); + +const mx_hash_vec3_1 = /*@__PURE__*/ Fn( ( [ x_immutable, y_immutable, z_immutable ] ) => { + + const z = int( z_immutable ).toVar(); + const y = int( y_immutable ).toVar(); + const x = int( x_immutable ).toVar(); + const h = uint( mx_hash_int( x, y, z ) ).toVar(); + const result = uvec3().toVar(); + result.x.assign( h.bitAnd( int( 0xFF ) ) ); + result.y.assign( h.shiftRight( int( 8 ) ).bitAnd( int( 0xFF ) ) ); + result.z.assign( h.shiftRight( int( 16 ) ).bitAnd( int( 0xFF ) ) ); + + return result; + +} ).setLayout( { + name: 'mx_hash_vec3_1', + type: 'uvec3', + inputs: [ + { name: 'x', type: 'int' }, + { name: 'y', type: 'int' }, + { name: 'z', type: 'int' } + ] +} ); + +const mx_hash_vec3 = /*@__PURE__*/ overloadingFn( [ mx_hash_vec3_0, mx_hash_vec3_1 ] ); + +const mx_perlin_noise_float_0 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => { + + const p = vec2( p_immutable ).toVar(); + const X = int().toVar(), Y = int().toVar(); + const fx = float( mx_floorfrac( p.x, X ) ).toVar(); + const fy = float( mx_floorfrac( p.y, Y ) ).toVar(); + const u = float( mx_fade( fx ) ).toVar(); + const v = float( mx_fade( fy ) ).toVar(); + const result = float( mx_bilerp( mx_gradient_float( mx_hash_int( X, Y ), fx, fy ), mx_gradient_float( mx_hash_int( X.add( int( 1 ) ), Y ), fx.sub( 1.0 ), fy ), mx_gradient_float( mx_hash_int( X, Y.add( int( 1 ) ) ), fx, fy.sub( 1.0 ) ), mx_gradient_float( mx_hash_int( X.add( int( 1 ) ), Y.add( int( 1 ) ) ), fx.sub( 1.0 ), fy.sub( 1.0 ) ), u, v ) ).toVar(); + + return mx_gradient_scale2d( result ); + +} ).setLayout( { + name: 'mx_perlin_noise_float_0', + type: 'float', + inputs: [ + { name: 'p', type: 'vec2' } + ] +} ); + +const mx_perlin_noise_float_1 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => { + + const p = vec3( p_immutable ).toVar(); + const X = int().toVar(), Y = int().toVar(), Z = int().toVar(); + const fx = float( mx_floorfrac( p.x, X ) ).toVar(); + const fy = float( mx_floorfrac( p.y, Y ) ).toVar(); + const fz = float( mx_floorfrac( p.z, Z ) ).toVar(); + const u = float( mx_fade( fx ) ).toVar(); + const v = float( mx_fade( fy ) ).toVar(); + const w = float( mx_fade( fz ) ).toVar(); + const result = float( mx_trilerp( mx_gradient_float( mx_hash_int( X, Y, Z ), fx, fy, fz ), mx_gradient_float( mx_hash_int( X.add( int( 1 ) ), Y, Z ), fx.sub( 1.0 ), fy, fz ), mx_gradient_float( mx_hash_int( X, Y.add( int( 1 ) ), Z ), fx, fy.sub( 1.0 ), fz ), mx_gradient_float( mx_hash_int( X.add( int( 1 ) ), Y.add( int( 1 ) ), Z ), fx.sub( 1.0 ), fy.sub( 1.0 ), fz ), mx_gradient_float( mx_hash_int( X, Y, Z.add( int( 1 ) ) ), fx, fy, fz.sub( 1.0 ) ), mx_gradient_float( mx_hash_int( X.add( int( 1 ) ), Y, Z.add( int( 1 ) ) ), fx.sub( 1.0 ), fy, fz.sub( 1.0 ) ), mx_gradient_float( mx_hash_int( X, Y.add( int( 1 ) ), Z.add( int( 1 ) ) ), fx, fy.sub( 1.0 ), fz.sub( 1.0 ) ), mx_gradient_float( mx_hash_int( X.add( int( 1 ) ), Y.add( int( 1 ) ), Z.add( int( 1 ) ) ), fx.sub( 1.0 ), fy.sub( 1.0 ), fz.sub( 1.0 ) ), u, v, w ) ).toVar(); + + return mx_gradient_scale3d( result ); + +} ).setLayout( { + name: 'mx_perlin_noise_float_1', + type: 'float', + inputs: [ + { name: 'p', type: 'vec3' } + ] +} ); + +const mx_perlin_noise_float = /*@__PURE__*/ overloadingFn( [ mx_perlin_noise_float_0, mx_perlin_noise_float_1 ] ); + +const mx_perlin_noise_vec3_0 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => { + + const p = vec2( p_immutable ).toVar(); + const X = int().toVar(), Y = int().toVar(); + const fx = float( mx_floorfrac( p.x, X ) ).toVar(); + const fy = float( mx_floorfrac( p.y, Y ) ).toVar(); + const u = float( mx_fade( fx ) ).toVar(); + const v = float( mx_fade( fy ) ).toVar(); + const result = vec3( mx_bilerp( mx_gradient_vec3( mx_hash_vec3( X, Y ), fx, fy ), mx_gradient_vec3( mx_hash_vec3( X.add( int( 1 ) ), Y ), fx.sub( 1.0 ), fy ), mx_gradient_vec3( mx_hash_vec3( X, Y.add( int( 1 ) ) ), fx, fy.sub( 1.0 ) ), mx_gradient_vec3( mx_hash_vec3( X.add( int( 1 ) ), Y.add( int( 1 ) ) ), fx.sub( 1.0 ), fy.sub( 1.0 ) ), u, v ) ).toVar(); + + return mx_gradient_scale2d( result ); + +} ).setLayout( { + name: 'mx_perlin_noise_vec3_0', + type: 'vec3', + inputs: [ + { name: 'p', type: 'vec2' } + ] +} ); + +const mx_perlin_noise_vec3_1 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => { + + const p = vec3( p_immutable ).toVar(); + const X = int().toVar(), Y = int().toVar(), Z = int().toVar(); + const fx = float( mx_floorfrac( p.x, X ) ).toVar(); + const fy = float( mx_floorfrac( p.y, Y ) ).toVar(); + const fz = float( mx_floorfrac( p.z, Z ) ).toVar(); + const u = float( mx_fade( fx ) ).toVar(); + const v = float( mx_fade( fy ) ).toVar(); + const w = float( mx_fade( fz ) ).toVar(); + const result = vec3( mx_trilerp( mx_gradient_vec3( mx_hash_vec3( X, Y, Z ), fx, fy, fz ), mx_gradient_vec3( mx_hash_vec3( X.add( int( 1 ) ), Y, Z ), fx.sub( 1.0 ), fy, fz ), mx_gradient_vec3( mx_hash_vec3( X, Y.add( int( 1 ) ), Z ), fx, fy.sub( 1.0 ), fz ), mx_gradient_vec3( mx_hash_vec3( X.add( int( 1 ) ), Y.add( int( 1 ) ), Z ), fx.sub( 1.0 ), fy.sub( 1.0 ), fz ), mx_gradient_vec3( mx_hash_vec3( X, Y, Z.add( int( 1 ) ) ), fx, fy, fz.sub( 1.0 ) ), mx_gradient_vec3( mx_hash_vec3( X.add( int( 1 ) ), Y, Z.add( int( 1 ) ) ), fx.sub( 1.0 ), fy, fz.sub( 1.0 ) ), mx_gradient_vec3( mx_hash_vec3( X, Y.add( int( 1 ) ), Z.add( int( 1 ) ) ), fx, fy.sub( 1.0 ), fz.sub( 1.0 ) ), mx_gradient_vec3( mx_hash_vec3( X.add( int( 1 ) ), Y.add( int( 1 ) ), Z.add( int( 1 ) ) ), fx.sub( 1.0 ), fy.sub( 1.0 ), fz.sub( 1.0 ) ), u, v, w ) ).toVar(); + + return mx_gradient_scale3d( result ); + +} ).setLayout( { + name: 'mx_perlin_noise_vec3_1', + type: 'vec3', + inputs: [ + { name: 'p', type: 'vec3' } + ] +} ); + +const mx_perlin_noise_vec3 = /*@__PURE__*/ overloadingFn( [ mx_perlin_noise_vec3_0, mx_perlin_noise_vec3_1 ] ); + +const mx_cell_noise_float_0 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => { + + const p = float( p_immutable ).toVar(); + const ix = int( mx_floor( p ) ).toVar(); + + return mx_bits_to_01( mx_hash_int( ix ) ); + +} ).setLayout( { + name: 'mx_cell_noise_float_0', + type: 'float', + inputs: [ + { name: 'p', type: 'float' } + ] +} ); + +const mx_cell_noise_float_1 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => { + + const p = vec2( p_immutable ).toVar(); + const ix = int( mx_floor( p.x ) ).toVar(); + const iy = int( mx_floor( p.y ) ).toVar(); + + return mx_bits_to_01( mx_hash_int( ix, iy ) ); + +} ).setLayout( { + name: 'mx_cell_noise_float_1', + type: 'float', + inputs: [ + { name: 'p', type: 'vec2' } + ] +} ); + +const mx_cell_noise_float_2 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => { + + const p = vec3( p_immutable ).toVar(); + const ix = int( mx_floor( p.x ) ).toVar(); + const iy = int( mx_floor( p.y ) ).toVar(); + const iz = int( mx_floor( p.z ) ).toVar(); + + return mx_bits_to_01( mx_hash_int( ix, iy, iz ) ); + +} ).setLayout( { + name: 'mx_cell_noise_float_2', + type: 'float', + inputs: [ + { name: 'p', type: 'vec3' } + ] +} ); + +const mx_cell_noise_float_3 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => { + + const p = vec4( p_immutable ).toVar(); + const ix = int( mx_floor( p.x ) ).toVar(); + const iy = int( mx_floor( p.y ) ).toVar(); + const iz = int( mx_floor( p.z ) ).toVar(); + const iw = int( mx_floor( p.w ) ).toVar(); + + return mx_bits_to_01( mx_hash_int( ix, iy, iz, iw ) ); + +} ).setLayout( { + name: 'mx_cell_noise_float_3', + type: 'float', + inputs: [ + { name: 'p', type: 'vec4' } + ] +} ); + +const mx_cell_noise_float$1 = /*@__PURE__*/ overloadingFn( [ mx_cell_noise_float_0, mx_cell_noise_float_1, mx_cell_noise_float_2, mx_cell_noise_float_3 ] ); + +const mx_cell_noise_vec3_0 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => { + + const p = float( p_immutable ).toVar(); + const ix = int( mx_floor( p ) ).toVar(); + + return vec3( mx_bits_to_01( mx_hash_int( ix, int( 0 ) ) ), mx_bits_to_01( mx_hash_int( ix, int( 1 ) ) ), mx_bits_to_01( mx_hash_int( ix, int( 2 ) ) ) ); + +} ).setLayout( { + name: 'mx_cell_noise_vec3_0', + type: 'vec3', + inputs: [ + { name: 'p', type: 'float' } + ] +} ); + +const mx_cell_noise_vec3_1 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => { + + const p = vec2( p_immutable ).toVar(); + const ix = int( mx_floor( p.x ) ).toVar(); + const iy = int( mx_floor( p.y ) ).toVar(); + + return vec3( mx_bits_to_01( mx_hash_int( ix, iy, int( 0 ) ) ), mx_bits_to_01( mx_hash_int( ix, iy, int( 1 ) ) ), mx_bits_to_01( mx_hash_int( ix, iy, int( 2 ) ) ) ); + +} ).setLayout( { + name: 'mx_cell_noise_vec3_1', + type: 'vec3', + inputs: [ + { name: 'p', type: 'vec2' } + ] +} ); + +const mx_cell_noise_vec3_2 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => { + + const p = vec3( p_immutable ).toVar(); + const ix = int( mx_floor( p.x ) ).toVar(); + const iy = int( mx_floor( p.y ) ).toVar(); + const iz = int( mx_floor( p.z ) ).toVar(); + + return vec3( mx_bits_to_01( mx_hash_int( ix, iy, iz, int( 0 ) ) ), mx_bits_to_01( mx_hash_int( ix, iy, iz, int( 1 ) ) ), mx_bits_to_01( mx_hash_int( ix, iy, iz, int( 2 ) ) ) ); + +} ).setLayout( { + name: 'mx_cell_noise_vec3_2', + type: 'vec3', + inputs: [ + { name: 'p', type: 'vec3' } + ] +} ); + +const mx_cell_noise_vec3_3 = /*@__PURE__*/ Fn( ( [ p_immutable ] ) => { + + const p = vec4( p_immutable ).toVar(); + const ix = int( mx_floor( p.x ) ).toVar(); + const iy = int( mx_floor( p.y ) ).toVar(); + const iz = int( mx_floor( p.z ) ).toVar(); + const iw = int( mx_floor( p.w ) ).toVar(); + + return vec3( mx_bits_to_01( mx_hash_int( ix, iy, iz, iw, int( 0 ) ) ), mx_bits_to_01( mx_hash_int( ix, iy, iz, iw, int( 1 ) ) ), mx_bits_to_01( mx_hash_int( ix, iy, iz, iw, int( 2 ) ) ) ); + +} ).setLayout( { + name: 'mx_cell_noise_vec3_3', + type: 'vec3', + inputs: [ + { name: 'p', type: 'vec4' } + ] +} ); + +const mx_cell_noise_vec3 = /*@__PURE__*/ overloadingFn( [ mx_cell_noise_vec3_0, mx_cell_noise_vec3_1, mx_cell_noise_vec3_2, mx_cell_noise_vec3_3 ] ); + +const mx_fractal_noise_float$1 = /*@__PURE__*/ Fn( ( [ p_immutable, octaves_immutable, lacunarity_immutable, diminish_immutable ] ) => { + + const diminish = float( diminish_immutable ).toVar(); + const lacunarity = float( lacunarity_immutable ).toVar(); + const octaves = int( octaves_immutable ).toVar(); + const p = vec3( p_immutable ).toVar(); + const result = float( 0.0 ).toVar(); + const amplitude = float( 1.0 ).toVar(); + + Loop( octaves, () => { + + result.addAssign( amplitude.mul( mx_perlin_noise_float( p ) ) ); + amplitude.mulAssign( diminish ); + p.mulAssign( lacunarity ); + + } ); + + return result; + +} ).setLayout( { + name: 'mx_fractal_noise_float', + type: 'float', + inputs: [ + { name: 'p', type: 'vec3' }, + { name: 'octaves', type: 'int' }, + { name: 'lacunarity', type: 'float' }, + { name: 'diminish', type: 'float' } + ] +} ); + +const mx_fractal_noise_vec3$1 = /*@__PURE__*/ Fn( ( [ p_immutable, octaves_immutable, lacunarity_immutable, diminish_immutable ] ) => { + + const diminish = float( diminish_immutable ).toVar(); + const lacunarity = float( lacunarity_immutable ).toVar(); + const octaves = int( octaves_immutable ).toVar(); + const p = vec3( p_immutable ).toVar(); + const result = vec3( 0.0 ).toVar(); + const amplitude = float( 1.0 ).toVar(); + + Loop( octaves, () => { + + result.addAssign( amplitude.mul( mx_perlin_noise_vec3( p ) ) ); + amplitude.mulAssign( diminish ); + p.mulAssign( lacunarity ); + + } ); + + return result; + +} ).setLayout( { + name: 'mx_fractal_noise_vec3', + type: 'vec3', + inputs: [ + { name: 'p', type: 'vec3' }, + { name: 'octaves', type: 'int' }, + { name: 'lacunarity', type: 'float' }, + { name: 'diminish', type: 'float' } + ] +} ); + +const mx_fractal_noise_vec2$1 = /*@__PURE__*/ Fn( ( [ p_immutable, octaves_immutable, lacunarity_immutable, diminish_immutable ] ) => { + + const diminish = float( diminish_immutable ).toVar(); + const lacunarity = float( lacunarity_immutable ).toVar(); + const octaves = int( octaves_immutable ).toVar(); + const p = vec3( p_immutable ).toVar(); + + return vec2( mx_fractal_noise_float$1( p, octaves, lacunarity, diminish ), mx_fractal_noise_float$1( p.add( vec3( int( 19 ), int( 193 ), int( 17 ) ) ), octaves, lacunarity, diminish ) ); + +} ).setLayout( { + name: 'mx_fractal_noise_vec2', + type: 'vec2', + inputs: [ + { name: 'p', type: 'vec3' }, + { name: 'octaves', type: 'int' }, + { name: 'lacunarity', type: 'float' }, + { name: 'diminish', type: 'float' } + ] +} ); + +const mx_fractal_noise_vec4$1 = /*@__PURE__*/ Fn( ( [ p_immutable, octaves_immutable, lacunarity_immutable, diminish_immutable ] ) => { + + const diminish = float( diminish_immutable ).toVar(); + const lacunarity = float( lacunarity_immutable ).toVar(); + const octaves = int( octaves_immutable ).toVar(); + const p = vec3( p_immutable ).toVar(); + const c = vec3( mx_fractal_noise_vec3$1( p, octaves, lacunarity, diminish ) ).toVar(); + const f = float( mx_fractal_noise_float$1( p.add( vec3( int( 19 ), int( 193 ), int( 17 ) ) ), octaves, lacunarity, diminish ) ).toVar(); + + return vec4( c, f ); + +} ).setLayout( { + name: 'mx_fractal_noise_vec4', + type: 'vec4', + inputs: [ + { name: 'p', type: 'vec3' }, + { name: 'octaves', type: 'int' }, + { name: 'lacunarity', type: 'float' }, + { name: 'diminish', type: 'float' } + ] +} ); + +const mx_worley_distance_0 = /*@__PURE__*/ Fn( ( [ p_immutable, x_immutable, y_immutable, xoff_immutable, yoff_immutable, jitter_immutable, metric_immutable ] ) => { + + const metric = int( metric_immutable ).toVar(); + const jitter = float( jitter_immutable ).toVar(); + const yoff = int( yoff_immutable ).toVar(); + const xoff = int( xoff_immutable ).toVar(); + const y = int( y_immutable ).toVar(); + const x = int( x_immutable ).toVar(); + const p = vec2( p_immutable ).toVar(); + const tmp = vec3( mx_cell_noise_vec3( vec2( x.add( xoff ), y.add( yoff ) ) ) ).toVar(); + const off = vec2( tmp.x, tmp.y ).toVar(); + off.subAssign( 0.5 ); + off.mulAssign( jitter ); + off.addAssign( 0.5 ); + const cellpos = vec2( vec2( float( x ), float( y ) ).add( off ) ).toVar(); + const diff = vec2( cellpos.sub( p ) ).toVar(); + + If( metric.equal( int( 2 ) ), () => { + + return abs( diff.x ).add( abs( diff.y ) ); + + } ); + + If( metric.equal( int( 3 ) ), () => { + + return max$1( abs( diff.x ), abs( diff.y ) ); + + } ); + + return dot( diff, diff ); + +} ).setLayout( { + name: 'mx_worley_distance_0', + type: 'float', + inputs: [ + { name: 'p', type: 'vec2' }, + { name: 'x', type: 'int' }, + { name: 'y', type: 'int' }, + { name: 'xoff', type: 'int' }, + { name: 'yoff', type: 'int' }, + { name: 'jitter', type: 'float' }, + { name: 'metric', type: 'int' } + ] +} ); + +const mx_worley_distance_1 = /*@__PURE__*/ Fn( ( [ p_immutable, x_immutable, y_immutable, z_immutable, xoff_immutable, yoff_immutable, zoff_immutable, jitter_immutable, metric_immutable ] ) => { + + const metric = int( metric_immutable ).toVar(); + const jitter = float( jitter_immutable ).toVar(); + const zoff = int( zoff_immutable ).toVar(); + const yoff = int( yoff_immutable ).toVar(); + const xoff = int( xoff_immutable ).toVar(); + const z = int( z_immutable ).toVar(); + const y = int( y_immutable ).toVar(); + const x = int( x_immutable ).toVar(); + const p = vec3( p_immutable ).toVar(); + const off = vec3( mx_cell_noise_vec3( vec3( x.add( xoff ), y.add( yoff ), z.add( zoff ) ) ) ).toVar(); + off.subAssign( 0.5 ); + off.mulAssign( jitter ); + off.addAssign( 0.5 ); + const cellpos = vec3( vec3( float( x ), float( y ), float( z ) ).add( off ) ).toVar(); + const diff = vec3( cellpos.sub( p ) ).toVar(); + + If( metric.equal( int( 2 ) ), () => { + + return abs( diff.x ).add( abs( diff.y ) ).add( abs( diff.z ) ); + + } ); + + If( metric.equal( int( 3 ) ), () => { + + return max$1( abs( diff.x ), abs( diff.y ), abs( diff.z ) ); + + } ); + + return dot( diff, diff ); + +} ).setLayout( { + name: 'mx_worley_distance_1', + type: 'float', + inputs: [ + { name: 'p', type: 'vec3' }, + { name: 'x', type: 'int' }, + { name: 'y', type: 'int' }, + { name: 'z', type: 'int' }, + { name: 'xoff', type: 'int' }, + { name: 'yoff', type: 'int' }, + { name: 'zoff', type: 'int' }, + { name: 'jitter', type: 'float' }, + { name: 'metric', type: 'int' } + ] +} ); + +const mx_worley_distance = /*@__PURE__*/ overloadingFn( [ mx_worley_distance_0, mx_worley_distance_1 ] ); + +const mx_worley_noise_float_0 = /*@__PURE__*/ Fn( ( [ p_immutable, jitter_immutable, metric_immutable ] ) => { + + const metric = int( metric_immutable ).toVar(); + const jitter = float( jitter_immutable ).toVar(); + const p = vec2( p_immutable ).toVar(); + const X = int().toVar(), Y = int().toVar(); + const localpos = vec2( mx_floorfrac( p.x, X ), mx_floorfrac( p.y, Y ) ).toVar(); + const sqdist = float( 1e6 ).toVar(); + + Loop( { start: -1, end: int( 1 ), name: 'x', condition: '<=' }, ( { x } ) => { + + Loop( { start: -1, end: int( 1 ), name: 'y', condition: '<=' }, ( { y } ) => { + + const dist = float( mx_worley_distance( localpos, x, y, X, Y, jitter, metric ) ).toVar(); + sqdist.assign( min$1( sqdist, dist ) ); + + } ); + + } ); + + If( metric.equal( int( 0 ) ), () => { + + sqdist.assign( sqrt( sqdist ) ); + + } ); + + return sqdist; + +} ).setLayout( { + name: 'mx_worley_noise_float_0', + type: 'float', + inputs: [ + { name: 'p', type: 'vec2' }, + { name: 'jitter', type: 'float' }, + { name: 'metric', type: 'int' } + ] +} ); + +const mx_worley_noise_vec2_0 = /*@__PURE__*/ Fn( ( [ p_immutable, jitter_immutable, metric_immutable ] ) => { + + const metric = int( metric_immutable ).toVar(); + const jitter = float( jitter_immutable ).toVar(); + const p = vec2( p_immutable ).toVar(); + const X = int().toVar(), Y = int().toVar(); + const localpos = vec2( mx_floorfrac( p.x, X ), mx_floorfrac( p.y, Y ) ).toVar(); + const sqdist = vec2( 1e6, 1e6 ).toVar(); + + Loop( { start: -1, end: int( 1 ), name: 'x', condition: '<=' }, ( { x } ) => { + + Loop( { start: -1, end: int( 1 ), name: 'y', condition: '<=' }, ( { y } ) => { + + const dist = float( mx_worley_distance( localpos, x, y, X, Y, jitter, metric ) ).toVar(); + + If( dist.lessThan( sqdist.x ), () => { + + sqdist.y.assign( sqdist.x ); + sqdist.x.assign( dist ); + + } ).ElseIf( dist.lessThan( sqdist.y ), () => { + + sqdist.y.assign( dist ); + + } ); + + } ); + + } ); + + If( metric.equal( int( 0 ) ), () => { + + sqdist.assign( sqrt( sqdist ) ); + + } ); + + return sqdist; + +} ).setLayout( { + name: 'mx_worley_noise_vec2_0', + type: 'vec2', + inputs: [ + { name: 'p', type: 'vec2' }, + { name: 'jitter', type: 'float' }, + { name: 'metric', type: 'int' } + ] +} ); + +const mx_worley_noise_vec3_0 = /*@__PURE__*/ Fn( ( [ p_immutable, jitter_immutable, metric_immutable ] ) => { + + const metric = int( metric_immutable ).toVar(); + const jitter = float( jitter_immutable ).toVar(); + const p = vec2( p_immutable ).toVar(); + const X = int().toVar(), Y = int().toVar(); + const localpos = vec2( mx_floorfrac( p.x, X ), mx_floorfrac( p.y, Y ) ).toVar(); + const sqdist = vec3( 1e6, 1e6, 1e6 ).toVar(); + + Loop( { start: -1, end: int( 1 ), name: 'x', condition: '<=' }, ( { x } ) => { + + Loop( { start: -1, end: int( 1 ), name: 'y', condition: '<=' }, ( { y } ) => { + + const dist = float( mx_worley_distance( localpos, x, y, X, Y, jitter, metric ) ).toVar(); + + If( dist.lessThan( sqdist.x ), () => { + + sqdist.z.assign( sqdist.y ); + sqdist.y.assign( sqdist.x ); + sqdist.x.assign( dist ); + + } ).ElseIf( dist.lessThan( sqdist.y ), () => { + + sqdist.z.assign( sqdist.y ); + sqdist.y.assign( dist ); + + } ).ElseIf( dist.lessThan( sqdist.z ), () => { + + sqdist.z.assign( dist ); + + } ); + + } ); + + } ); + + If( metric.equal( int( 0 ) ), () => { + + sqdist.assign( sqrt( sqdist ) ); + + } ); + + return sqdist; + +} ).setLayout( { + name: 'mx_worley_noise_vec3_0', + type: 'vec3', + inputs: [ + { name: 'p', type: 'vec2' }, + { name: 'jitter', type: 'float' }, + { name: 'metric', type: 'int' } + ] +} ); + +const mx_worley_noise_float_1 = /*@__PURE__*/ Fn( ( [ p_immutable, jitter_immutable, metric_immutable ] ) => { + + const metric = int( metric_immutable ).toVar(); + const jitter = float( jitter_immutable ).toVar(); + const p = vec3( p_immutable ).toVar(); + const X = int().toVar(), Y = int().toVar(), Z = int().toVar(); + const localpos = vec3( mx_floorfrac( p.x, X ), mx_floorfrac( p.y, Y ), mx_floorfrac( p.z, Z ) ).toVar(); + const sqdist = float( 1e6 ).toVar(); + + Loop( { start: -1, end: int( 1 ), name: 'x', condition: '<=' }, ( { x } ) => { + + Loop( { start: -1, end: int( 1 ), name: 'y', condition: '<=' }, ( { y } ) => { + + Loop( { start: -1, end: int( 1 ), name: 'z', condition: '<=' }, ( { z } ) => { + + const dist = float( mx_worley_distance( localpos, x, y, z, X, Y, Z, jitter, metric ) ).toVar(); + sqdist.assign( min$1( sqdist, dist ) ); + + } ); + + } ); + + } ); + + If( metric.equal( int( 0 ) ), () => { + + sqdist.assign( sqrt( sqdist ) ); + + } ); + + return sqdist; + +} ).setLayout( { + name: 'mx_worley_noise_float_1', + type: 'float', + inputs: [ + { name: 'p', type: 'vec3' }, + { name: 'jitter', type: 'float' }, + { name: 'metric', type: 'int' } + ] +} ); + +const mx_worley_noise_float$1 = /*@__PURE__*/ overloadingFn( [ mx_worley_noise_float_0, mx_worley_noise_float_1 ] ); + +const mx_worley_noise_vec2_1 = /*@__PURE__*/ Fn( ( [ p_immutable, jitter_immutable, metric_immutable ] ) => { + + const metric = int( metric_immutable ).toVar(); + const jitter = float( jitter_immutable ).toVar(); + const p = vec3( p_immutable ).toVar(); + const X = int().toVar(), Y = int().toVar(), Z = int().toVar(); + const localpos = vec3( mx_floorfrac( p.x, X ), mx_floorfrac( p.y, Y ), mx_floorfrac( p.z, Z ) ).toVar(); + const sqdist = vec2( 1e6, 1e6 ).toVar(); + + Loop( { start: -1, end: int( 1 ), name: 'x', condition: '<=' }, ( { x } ) => { + + Loop( { start: -1, end: int( 1 ), name: 'y', condition: '<=' }, ( { y } ) => { + + Loop( { start: -1, end: int( 1 ), name: 'z', condition: '<=' }, ( { z } ) => { + + const dist = float( mx_worley_distance( localpos, x, y, z, X, Y, Z, jitter, metric ) ).toVar(); + + If( dist.lessThan( sqdist.x ), () => { + + sqdist.y.assign( sqdist.x ); + sqdist.x.assign( dist ); + + } ).ElseIf( dist.lessThan( sqdist.y ), () => { + + sqdist.y.assign( dist ); + + } ); + + } ); + + } ); + + } ); + + If( metric.equal( int( 0 ) ), () => { + + sqdist.assign( sqrt( sqdist ) ); + + } ); + + return sqdist; + +} ).setLayout( { + name: 'mx_worley_noise_vec2_1', + type: 'vec2', + inputs: [ + { name: 'p', type: 'vec3' }, + { name: 'jitter', type: 'float' }, + { name: 'metric', type: 'int' } + ] +} ); + +const mx_worley_noise_vec2$1 = /*@__PURE__*/ overloadingFn( [ mx_worley_noise_vec2_0, mx_worley_noise_vec2_1 ] ); + +const mx_worley_noise_vec3_1 = /*@__PURE__*/ Fn( ( [ p_immutable, jitter_immutable, metric_immutable ] ) => { + + const metric = int( metric_immutable ).toVar(); + const jitter = float( jitter_immutable ).toVar(); + const p = vec3( p_immutable ).toVar(); + const X = int().toVar(), Y = int().toVar(), Z = int().toVar(); + const localpos = vec3( mx_floorfrac( p.x, X ), mx_floorfrac( p.y, Y ), mx_floorfrac( p.z, Z ) ).toVar(); + const sqdist = vec3( 1e6, 1e6, 1e6 ).toVar(); + + Loop( { start: -1, end: int( 1 ), name: 'x', condition: '<=' }, ( { x } ) => { + + Loop( { start: -1, end: int( 1 ), name: 'y', condition: '<=' }, ( { y } ) => { + + Loop( { start: -1, end: int( 1 ), name: 'z', condition: '<=' }, ( { z } ) => { + + const dist = float( mx_worley_distance( localpos, x, y, z, X, Y, Z, jitter, metric ) ).toVar(); + + If( dist.lessThan( sqdist.x ), () => { + + sqdist.z.assign( sqdist.y ); + sqdist.y.assign( sqdist.x ); + sqdist.x.assign( dist ); + + } ).ElseIf( dist.lessThan( sqdist.y ), () => { + + sqdist.z.assign( sqdist.y ); + sqdist.y.assign( dist ); + + } ).ElseIf( dist.lessThan( sqdist.z ), () => { + + sqdist.z.assign( dist ); + + } ); + + } ); + + } ); + + } ); + + If( metric.equal( int( 0 ) ), () => { + + sqdist.assign( sqrt( sqdist ) ); + + } ); + + return sqdist; + +} ).setLayout( { + name: 'mx_worley_noise_vec3_1', + type: 'vec3', + inputs: [ + { name: 'p', type: 'vec3' }, + { name: 'jitter', type: 'float' }, + { name: 'metric', type: 'int' } + ] +} ); + +const mx_worley_noise_vec3$1 = /*@__PURE__*/ overloadingFn( [ mx_worley_noise_vec3_0, mx_worley_noise_vec3_1 ] ); + +// Three.js Transpiler +// https://github.com/AcademySoftwareFoundation/MaterialX/blob/main/libraries/stdlib/genglsl/lib/mx_hsv.glsl + + +const mx_hsvtorgb = /*@__PURE__*/ Fn( ( [ hsv ] ) => { + + const s = hsv.y; + const v = hsv.z; + + const result = vec3().toVar(); + + If( s.lessThan( 0.0001 ), () => { + + result.assign( vec3( v, v, v ) ); + + } ).Else( () => { + + let h = hsv.x; + h = h.sub( floor( h ) ).mul( 6.0 ).toVar(); // TODO: check what .toVar() is needed in node system cache + const hi = int( trunc( h ) ); + const f = h.sub( float( hi ) ); + const p = v.mul( s.oneMinus() ); + const q = v.mul( s.mul( f ).oneMinus() ); + const t = v.mul( s.mul( f.oneMinus() ).oneMinus() ); + + If( hi.equal( int( 0 ) ), () => { + + result.assign( vec3( v, t, p ) ); + + } ).ElseIf( hi.equal( int( 1 ) ), () => { + + result.assign( vec3( q, v, p ) ); + + } ).ElseIf( hi.equal( int( 2 ) ), () => { + + result.assign( vec3( p, v, t ) ); + + } ).ElseIf( hi.equal( int( 3 ) ), () => { + + result.assign( vec3( p, q, v ) ); + + } ).ElseIf( hi.equal( int( 4 ) ), () => { + + result.assign( vec3( t, p, v ) ); + + } ).Else( () => { + + result.assign( vec3( v, p, q ) ); + + } ); + + } ); + + return result; + +} ).setLayout( { + name: 'mx_hsvtorgb', + type: 'vec3', + inputs: [ + { name: 'hsv', type: 'vec3' } + ] +} ); + +const mx_rgbtohsv = /*@__PURE__*/ Fn( ( [ c_immutable ] ) => { + + const c = vec3( c_immutable ).toVar(); + const r = float( c.x ).toVar(); + const g = float( c.y ).toVar(); + const b = float( c.z ).toVar(); + const mincomp = float( min$1( r, min$1( g, b ) ) ).toVar(); + const maxcomp = float( max$1( r, max$1( g, b ) ) ).toVar(); + const delta = float( maxcomp.sub( mincomp ) ).toVar(); + const h = float().toVar(), s = float().toVar(), v = float().toVar(); + v.assign( maxcomp ); + + If( maxcomp.greaterThan( 0.0 ), () => { + + s.assign( delta.div( maxcomp ) ); + + } ).Else( () => { + + s.assign( 0.0 ); + + } ); + + If( s.lessThanEqual( 0.0 ), () => { + + h.assign( 0.0 ); + + } ).Else( () => { + + If( r.greaterThanEqual( maxcomp ), () => { + + h.assign( g.sub( b ).div( delta ) ); + + } ).ElseIf( g.greaterThanEqual( maxcomp ), () => { + + h.assign( add( 2.0, b.sub( r ).div( delta ) ) ); + + } ).Else( () => { + + h.assign( add( 4.0, r.sub( g ).div( delta ) ) ); + + } ); + + h.mulAssign( 1.0 / 6.0 ); + + If( h.lessThan( 0.0 ), () => { + + h.addAssign( 1.0 ); + + } ); + + } ); + + return vec3( h, s, v ); + +} ).setLayout( { + name: 'mx_rgbtohsv', + type: 'vec3', + inputs: [ + { name: 'c', type: 'vec3' } + ] +} ); + +// Three.js Transpiler +// https://github.com/AcademySoftwareFoundation/MaterialX/blob/main/libraries/stdlib/genglsl/lib/mx_transform_color.glsl + + +const mx_srgb_texture_to_lin_rec709 = /*@__PURE__*/ Fn( ( [ color_immutable ] ) => { + + const color = vec3( color_immutable ).toVar(); + const isAbove = bvec3( greaterThan( color, vec3( 0.04045 ) ) ).toVar(); + const linSeg = vec3( color.div( 12.92 ) ).toVar(); + const powSeg = vec3( pow( max$1( color.add( vec3( 0.055 ) ), vec3( 0.0 ) ).div( 1.055 ), vec3( 2.4 ) ) ).toVar(); + + return mix( linSeg, powSeg, isAbove ); + +} ).setLayout( { + name: 'mx_srgb_texture_to_lin_rec709', + type: 'vec3', + inputs: [ + { name: 'color', type: 'vec3' } + ] +} ); + +const mx_aastep = ( threshold, value ) => { + + threshold = float( threshold ); + value = float( value ); + + const afwidth = vec2( value.dFdx(), value.dFdy() ).length().mul( 0.70710678118654757 ); + + return smoothstep( threshold.sub( afwidth ), threshold.add( afwidth ), value ); + +}; + +const _ramp = ( a, b, uv, p ) => mix( a, b, uv[ p ].clamp() ); +const mx_ramplr = ( valuel, valuer, texcoord = uv$1() ) => _ramp( valuel, valuer, texcoord, 'x' ); +const mx_ramptb = ( valuet, valueb, texcoord = uv$1() ) => _ramp( valuet, valueb, texcoord, 'y' ); + +const _split = ( a, b, center, uv, p ) => mix( a, b, mx_aastep( center, uv[ p ] ) ); +const mx_splitlr = ( valuel, valuer, center, texcoord = uv$1() ) => _split( valuel, valuer, center, texcoord, 'x' ); +const mx_splittb = ( valuet, valueb, center, texcoord = uv$1() ) => _split( valuet, valueb, center, texcoord, 'y' ); + +const mx_transform_uv = ( uv_scale = 1, uv_offset = 0, uv_geo = uv$1() ) => uv_geo.mul( uv_scale ).add( uv_offset ); + +const mx_safepower = ( in1, in2 = 1 ) => { + + in1 = float( in1 ); + + return in1.abs().pow( in2 ).mul( in1.sign() ); + +}; + +const mx_contrast = ( input, amount = 1, pivot = .5 ) => float( input ).sub( pivot ).mul( amount ).add( pivot ); + +const mx_noise_float = ( texcoord = uv$1(), amplitude = 1, pivot = 0 ) => mx_perlin_noise_float( texcoord.convert( 'vec2|vec3' ) ).mul( amplitude ).add( pivot ); +//export const mx_noise_vec2 = ( texcoord = uv(), amplitude = 1, pivot = 0 ) => mx_perlin_noise_vec3( texcoord.convert( 'vec2|vec3' ) ).mul( amplitude ).add( pivot ); +const mx_noise_vec3 = ( texcoord = uv$1(), amplitude = 1, pivot = 0 ) => mx_perlin_noise_vec3( texcoord.convert( 'vec2|vec3' ) ).mul( amplitude ).add( pivot ); +const mx_noise_vec4 = ( texcoord = uv$1(), amplitude = 1, pivot = 0 ) => { + + texcoord = texcoord.convert( 'vec2|vec3' ); // overloading type + + const noise_vec4 = vec4( mx_perlin_noise_vec3( texcoord ), mx_perlin_noise_float( texcoord.add( vec2( 19, 73 ) ) ) ); + + return noise_vec4.mul( amplitude ).add( pivot ); + +}; + +const mx_worley_noise_float = ( texcoord = uv$1(), jitter = 1 ) => mx_worley_noise_float$1( texcoord.convert( 'vec2|vec3' ), jitter, int( 1 ) ); +const mx_worley_noise_vec2 = ( texcoord = uv$1(), jitter = 1 ) => mx_worley_noise_vec2$1( texcoord.convert( 'vec2|vec3' ), jitter, int( 1 ) ); +const mx_worley_noise_vec3 = ( texcoord = uv$1(), jitter = 1 ) => mx_worley_noise_vec3$1( texcoord.convert( 'vec2|vec3' ), jitter, int( 1 ) ); + +const mx_cell_noise_float = ( texcoord = uv$1() ) => mx_cell_noise_float$1( texcoord.convert( 'vec2|vec3' ) ); + +const mx_fractal_noise_float = ( position = uv$1(), octaves = 3, lacunarity = 2, diminish = .5, amplitude = 1 ) => mx_fractal_noise_float$1( position, int( octaves ), lacunarity, diminish ).mul( amplitude ); +const mx_fractal_noise_vec2 = ( position = uv$1(), octaves = 3, lacunarity = 2, diminish = .5, amplitude = 1 ) => mx_fractal_noise_vec2$1( position, int( octaves ), lacunarity, diminish ).mul( amplitude ); +const mx_fractal_noise_vec3 = ( position = uv$1(), octaves = 3, lacunarity = 2, diminish = .5, amplitude = 1 ) => mx_fractal_noise_vec3$1( position, int( octaves ), lacunarity, diminish ).mul( amplitude ); +const mx_fractal_noise_vec4 = ( position = uv$1(), octaves = 3, lacunarity = 2, diminish = .5, amplitude = 1 ) => mx_fractal_noise_vec4$1( position, int( octaves ), lacunarity, diminish ).mul( amplitude ); + +/** + * This computes a parallax corrected normal which is used for box-projected cube mapping (BPCEM). + * + * Reference: {@link https://devlog-martinsh.blogspot.com/2011/09/box-projected-cube-environment-mapping.html} + * + * ```js + * const uvNode = getParallaxCorrectNormal( reflectVector, vec3( 200, 100, 100 ), vec3( 0, - 50, 0 ) ); + * material.envNode = pmremTexture( renderTarget.texture, uvNode ); + * ``` + * + * @tsl + * @function + * @param {Node} normal - The normal to correct. + * @param {Node} cubeSize - The cube size should reflect the size of the environment (BPCEM is usually applied in closed environments like rooms). + * @param {Node} cubePos - The cube position. + * @return {Node} The parallax corrected normal. + */ +const getParallaxCorrectNormal = /*@__PURE__*/ Fn( ( [ normal, cubeSize, cubePos ] ) => { + + const nDir = normalize( normal ).toVar(); + const rbmax = sub( float( 0.5 ).mul( cubeSize.sub( cubePos ) ), positionWorld ).div( nDir ).toVar(); + const rbmin = sub( float( -0.5 ).mul( cubeSize.sub( cubePos ) ), positionWorld ).div( nDir ).toVar(); + const rbminmax = vec3().toVar(); + rbminmax.x = nDir.x.greaterThan( float( 0 ) ).select( rbmax.x, rbmin.x ); + rbminmax.y = nDir.y.greaterThan( float( 0 ) ).select( rbmax.y, rbmin.y ); + rbminmax.z = nDir.z.greaterThan( float( 0 ) ).select( rbmax.z, rbmin.z ); + + const correction = min$1( rbminmax.x, rbminmax.y, rbminmax.z ).toVar(); + const boxIntersection = positionWorld.add( nDir.mul( correction ) ).toVar(); + return boxIntersection.sub( cubePos ); + +} ); + +const getShIrradianceAt = /*@__PURE__*/ Fn( ( [ normal, shCoefficients ] ) => { + + // normal is assumed to have unit length + + const x = normal.x, y = normal.y, z = normal.z; + + // band 0 + let result = shCoefficients.element( 0 ).mul( 0.886227 ); + + // band 1 + result = result.add( shCoefficients.element( 1 ).mul( 2.0 * 0.511664 ).mul( y ) ); + result = result.add( shCoefficients.element( 2 ).mul( 2.0 * 0.511664 ).mul( z ) ); + result = result.add( shCoefficients.element( 3 ).mul( 2.0 * 0.511664 ).mul( x ) ); + + // band 2 + result = result.add( shCoefficients.element( 4 ).mul( 2.0 * 0.429043 ).mul( x ).mul( y ) ); + result = result.add( shCoefficients.element( 5 ).mul( 2.0 * 0.429043 ).mul( y ).mul( z ) ); + result = result.add( shCoefficients.element( 6 ).mul( z.mul( z ).mul( 0.743125 ).sub( 0.247708 ) ) ); + result = result.add( shCoefficients.element( 7 ).mul( 2.0 * 0.429043 ).mul( x ).mul( z ) ); + result = result.add( shCoefficients.element( 8 ).mul( 0.429043 ).mul( mul( x, x ).sub( mul( y, y ) ) ) ); + + return result; + +} ); + +// constants + +var TSL = /*#__PURE__*/Object.freeze({ + __proto__: null, + BRDF_GGX: BRDF_GGX, + BRDF_Lambert: BRDF_Lambert, + BasicPointShadowFilter: BasicPointShadowFilter, + BasicShadowFilter: BasicShadowFilter, + Break: Break, + Const: Const, + Continue: Continue, + DFGApprox: DFGApprox, + D_GGX: D_GGX, + Discard: Discard, + EPSILON: EPSILON, + F_Schlick: F_Schlick, + Fn: Fn, + INFINITY: INFINITY, + If: If, + Loop: Loop, + NodeAccess: NodeAccess, + NodeShaderStage: NodeShaderStage, + NodeType: NodeType, + NodeUpdateType: NodeUpdateType, + PCFShadowFilter: PCFShadowFilter, + PCFSoftShadowFilter: PCFSoftShadowFilter, + PI: PI, + PI2: PI2, + PointShadowFilter: PointShadowFilter, + Return: Return, + Schlick_to_F0: Schlick_to_F0, + ScriptableNodeResources: ScriptableNodeResources, + ShaderNode: ShaderNode, + Stack: Stack, + Switch: Switch, + TBNViewMatrix: TBNViewMatrix, + VSMShadowFilter: VSMShadowFilter, + V_GGX_SmithCorrelated: V_GGX_SmithCorrelated, + Var: Var, + abs: abs, + acesFilmicToneMapping: acesFilmicToneMapping, + acos: acos, + add: add, + addMethodChaining: addMethodChaining, + addNodeElement: addNodeElement, + agxToneMapping: agxToneMapping, + all: all, + alphaT: alphaT, + and: and, + anisotropy: anisotropy, + anisotropyB: anisotropyB, + anisotropyT: anisotropyT, + any: any, + append: append, + array: array, + arrayBuffer: arrayBuffer, + asin: asin, + assign: assign, + atan: atan, + atan2: atan2, + atomicAdd: atomicAdd, + atomicAnd: atomicAnd, + atomicFunc: atomicFunc, + atomicLoad: atomicLoad, + atomicMax: atomicMax, + atomicMin: atomicMin, + atomicOr: atomicOr, + atomicStore: atomicStore, + atomicSub: atomicSub, + atomicXor: atomicXor, + attenuationColor: attenuationColor, + attenuationDistance: attenuationDistance, + attribute: attribute, + attributeArray: attributeArray, + backgroundBlurriness: backgroundBlurriness, + backgroundIntensity: backgroundIntensity, + backgroundRotation: backgroundRotation, + batch: batch, + bentNormalView: bentNormalView, + billboarding: billboarding, + bitAnd: bitAnd, + bitNot: bitNot, + bitOr: bitOr, + bitXor: bitXor, + bitangentGeometry: bitangentGeometry, + bitangentLocal: bitangentLocal, + bitangentView: bitangentView, + bitangentWorld: bitangentWorld, + bitcast: bitcast, + blendBurn: blendBurn, + blendColor: blendColor, + blendDodge: blendDodge, + blendOverlay: blendOverlay, + blendScreen: blendScreen, + blur: blur, + bool: bool, + buffer: buffer, + bufferAttribute: bufferAttribute, + bumpMap: bumpMap, + burn: burn, + bvec2: bvec2, + bvec3: bvec3, + bvec4: bvec4, + bypass: bypass, + cache: cache, + call: call, + cameraFar: cameraFar, + cameraIndex: cameraIndex, + cameraNear: cameraNear, + cameraNormalMatrix: cameraNormalMatrix, + cameraPosition: cameraPosition, + cameraProjectionMatrix: cameraProjectionMatrix, + cameraProjectionMatrixInverse: cameraProjectionMatrixInverse, + cameraViewMatrix: cameraViewMatrix, + cameraWorldMatrix: cameraWorldMatrix, + cbrt: cbrt, + cdl: cdl, + ceil: ceil, + checker: checker, + cineonToneMapping: cineonToneMapping, + clamp: clamp, + clearcoat: clearcoat, + clearcoatNormalView: clearcoatNormalView, + clearcoatRoughness: clearcoatRoughness, + code: code, + color: color, + colorSpaceToWorking: colorSpaceToWorking, + colorToDirection: colorToDirection, + compute: compute, + computeSkinning: computeSkinning, + context: context, + convert: convert, + convertColorSpace: convertColorSpace, + convertToTexture: convertToTexture, + cos: cos, + cross: cross, + cubeTexture: cubeTexture, + cubeTextureBase: cubeTextureBase, + cubeToUV: cubeToUV, + dFdx: dFdx, + dFdy: dFdy, + dashSize: dashSize, + debug: debug, + decrement: decrement, + decrementBefore: decrementBefore, + defaultBuildStages: defaultBuildStages, + defaultShaderStages: defaultShaderStages, + defined: defined, + degrees: degrees, + deltaTime: deltaTime, + densityFog: densityFog, + densityFogFactor: densityFogFactor, + depth: depth, + depthPass: depthPass, + difference: difference, + diffuseColor: diffuseColor, + directPointLight: directPointLight, + directionToColor: directionToColor, + directionToFaceDirection: directionToFaceDirection, + dispersion: dispersion, + distance: distance, + div: div, + dodge: dodge, + dot: dot, + drawIndex: drawIndex, + dynamicBufferAttribute: dynamicBufferAttribute, + element: element, + emissive: emissive, + equal: equal, + equals: equals, + equirectUV: equirectUV, + exp: exp, + exp2: exp2, + expression: expression, + faceDirection: faceDirection, + faceForward: faceForward, + faceforward: faceforward, + float: float, + floor: floor, + fog: fog, + fract: fract, + frameGroup: frameGroup, + frameId: frameId, + frontFacing: frontFacing, + fwidth: fwidth, + gain: gain, + gapSize: gapSize, + getConstNodeType: getConstNodeType, + getCurrentStack: getCurrentStack, + getDirection: getDirection, + getDistanceAttenuation: getDistanceAttenuation, + getGeometryRoughness: getGeometryRoughness, + getNormalFromDepth: getNormalFromDepth, + getParallaxCorrectNormal: getParallaxCorrectNormal, + getRoughness: getRoughness, + getScreenPosition: getScreenPosition, + getShIrradianceAt: getShIrradianceAt, + getShadowMaterial: getShadowMaterial, + getShadowRenderObjectFunction: getShadowRenderObjectFunction, + getTextureIndex: getTextureIndex, + getViewPosition: getViewPosition, + globalId: globalId, + glsl: glsl, + glslFn: glslFn, + grayscale: grayscale, + greaterThan: greaterThan, + greaterThanEqual: greaterThanEqual, + hash: hash, + highpModelNormalViewMatrix: highpModelNormalViewMatrix, + highpModelViewMatrix: highpModelViewMatrix, + hue: hue, + increment: increment, + incrementBefore: incrementBefore, + instance: instance, + instanceIndex: instanceIndex, + instancedArray: instancedArray, + instancedBufferAttribute: instancedBufferAttribute, + instancedDynamicBufferAttribute: instancedDynamicBufferAttribute, + instancedMesh: instancedMesh, + int: int, + inverseSqrt: inverseSqrt, + inversesqrt: inversesqrt, + invocationLocalIndex: invocationLocalIndex, + invocationSubgroupIndex: invocationSubgroupIndex, + ior: ior, + iridescence: iridescence, + iridescenceIOR: iridescenceIOR, + iridescenceThickness: iridescenceThickness, + ivec2: ivec2, + ivec3: ivec3, + ivec4: ivec4, + js: js, + label: label, + length: length, + lengthSq: lengthSq, + lessThan: lessThan, + lessThanEqual: lessThanEqual, + lightPosition: lightPosition, + lightProjectionUV: lightProjectionUV, + lightShadowMatrix: lightShadowMatrix, + lightTargetDirection: lightTargetDirection, + lightTargetPosition: lightTargetPosition, + lightViewPosition: lightViewPosition, + lightingContext: lightingContext, + lights: lights, + linearDepth: linearDepth, + linearToneMapping: linearToneMapping, + localId: localId, + log: log, + log2: log2, + logarithmicDepthToViewZ: logarithmicDepthToViewZ, + luminance: luminance, + mat2: mat2, + mat3: mat3, + mat4: mat4, + matcapUV: matcapUV, + materialAO: materialAO, + materialAlphaTest: materialAlphaTest, + materialAnisotropy: materialAnisotropy, + materialAnisotropyVector: materialAnisotropyVector, + materialAttenuationColor: materialAttenuationColor, + materialAttenuationDistance: materialAttenuationDistance, + materialClearcoat: materialClearcoat, + materialClearcoatNormal: materialClearcoatNormal, + materialClearcoatRoughness: materialClearcoatRoughness, + materialColor: materialColor, + materialDispersion: materialDispersion, + materialEmissive: materialEmissive, + materialEnvIntensity: materialEnvIntensity, + materialEnvRotation: materialEnvRotation, + materialIOR: materialIOR, + materialIridescence: materialIridescence, + materialIridescenceIOR: materialIridescenceIOR, + materialIridescenceThickness: materialIridescenceThickness, + materialLightMap: materialLightMap, + materialLineDashOffset: materialLineDashOffset, + materialLineDashSize: materialLineDashSize, + materialLineGapSize: materialLineGapSize, + materialLineScale: materialLineScale, + materialLineWidth: materialLineWidth, + materialMetalness: materialMetalness, + materialNormal: materialNormal, + materialOpacity: materialOpacity, + materialPointSize: materialPointSize, + materialReference: materialReference, + materialReflectivity: materialReflectivity, + materialRefractionRatio: materialRefractionRatio, + materialRotation: materialRotation, + materialRoughness: materialRoughness, + materialSheen: materialSheen, + materialSheenRoughness: materialSheenRoughness, + materialShininess: materialShininess, + materialSpecular: materialSpecular, + materialSpecularColor: materialSpecularColor, + materialSpecularIntensity: materialSpecularIntensity, + materialSpecularStrength: materialSpecularStrength, + materialThickness: materialThickness, + materialTransmission: materialTransmission, + max: max$1, + maxMipLevel: maxMipLevel, + mediumpModelViewMatrix: mediumpModelViewMatrix, + metalness: metalness, + min: min$1, + mix: mix, + mixElement: mixElement, + mod: mod, + modInt: modInt, + modelDirection: modelDirection, + modelNormalMatrix: modelNormalMatrix, + modelPosition: modelPosition, + modelRadius: modelRadius, + modelScale: modelScale, + modelViewMatrix: modelViewMatrix, + modelViewPosition: modelViewPosition, + modelViewProjection: modelViewProjection, + modelWorldMatrix: modelWorldMatrix, + modelWorldMatrixInverse: modelWorldMatrixInverse, + morphReference: morphReference, + mrt: mrt, + mul: mul, + mx_aastep: mx_aastep, + mx_cell_noise_float: mx_cell_noise_float, + mx_contrast: mx_contrast, + mx_fractal_noise_float: mx_fractal_noise_float, + mx_fractal_noise_vec2: mx_fractal_noise_vec2, + mx_fractal_noise_vec3: mx_fractal_noise_vec3, + mx_fractal_noise_vec4: mx_fractal_noise_vec4, + mx_hsvtorgb: mx_hsvtorgb, + mx_noise_float: mx_noise_float, + mx_noise_vec3: mx_noise_vec3, + mx_noise_vec4: mx_noise_vec4, + mx_ramplr: mx_ramplr, + mx_ramptb: mx_ramptb, + mx_rgbtohsv: mx_rgbtohsv, + mx_safepower: mx_safepower, + mx_splitlr: mx_splitlr, + mx_splittb: mx_splittb, + mx_srgb_texture_to_lin_rec709: mx_srgb_texture_to_lin_rec709, + mx_transform_uv: mx_transform_uv, + mx_worley_noise_float: mx_worley_noise_float, + mx_worley_noise_vec2: mx_worley_noise_vec2, + mx_worley_noise_vec3: mx_worley_noise_vec3, + negate: negate, + neutralToneMapping: neutralToneMapping, + nodeArray: nodeArray, + nodeImmutable: nodeImmutable, + nodeObject: nodeObject, + nodeObjects: nodeObjects, + nodeProxy: nodeProxy, + normalFlat: normalFlat, + normalGeometry: normalGeometry, + normalLocal: normalLocal, + normalMap: normalMap, + normalView: normalView, + normalViewGeometry: normalViewGeometry, + normalWorld: normalWorld, + normalWorldGeometry: normalWorldGeometry, + normalize: normalize, + not: not, + notEqual: notEqual, + numWorkgroups: numWorkgroups, + objectDirection: objectDirection, + objectGroup: objectGroup, + objectPosition: objectPosition, + objectRadius: objectRadius, + objectScale: objectScale, + objectViewPosition: objectViewPosition, + objectWorldMatrix: objectWorldMatrix, + oneMinus: oneMinus, + or: or, + orthographicDepthToViewZ: orthographicDepthToViewZ, + oscSawtooth: oscSawtooth, + oscSine: oscSine, + oscSquare: oscSquare, + oscTriangle: oscTriangle, + output: output, + outputStruct: outputStruct, + overlay: overlay, + overloadingFn: overloadingFn, + parabola: parabola, + parallaxDirection: parallaxDirection, + parallaxUV: parallaxUV, + parameter: parameter, + pass: pass, + passTexture: passTexture, + pcurve: pcurve, + perspectiveDepthToViewZ: perspectiveDepthToViewZ, + pmremTexture: pmremTexture, + pointShadow: pointShadow, + pointUV: pointUV, + pointWidth: pointWidth, + positionGeometry: positionGeometry, + positionLocal: positionLocal, + positionPrevious: positionPrevious, + positionView: positionView, + positionViewDirection: positionViewDirection, + positionWorld: positionWorld, + positionWorldDirection: positionWorldDirection, + posterize: posterize, + pow: pow, + pow2: pow2, + pow3: pow3, + pow4: pow4, + premultiplyAlpha: premultiplyAlpha, + property: property, + radians: radians, + rand: rand, + range: range, + rangeFog: rangeFog, + rangeFogFactor: rangeFogFactor, + reciprocal: reciprocal, + reference: reference, + referenceBuffer: referenceBuffer, + reflect: reflect, + reflectVector: reflectVector, + reflectView: reflectView, + reflector: reflector, + refract: refract, + refractVector: refractVector, + refractView: refractView, + reinhardToneMapping: reinhardToneMapping, + remap: remap, + remapClamp: remapClamp, + renderGroup: renderGroup, + renderOutput: renderOutput, + rendererReference: rendererReference, + rotate: rotate, + rotateUV: rotateUV, + roughness: roughness, + round: round, + rtt: rtt, + sRGBTransferEOTF: sRGBTransferEOTF, + sRGBTransferOETF: sRGBTransferOETF, + sample: sample, + sampler: sampler, + samplerComparison: samplerComparison, + saturate: saturate, + saturation: saturation, + screen: screen, + screenCoordinate: screenCoordinate, + screenSize: screenSize, + screenUV: screenUV, + scriptable: scriptable, + scriptableValue: scriptableValue, + select: select, + setCurrentStack: setCurrentStack, + shaderStages: shaderStages, + shadow: shadow, + shadowPositionWorld: shadowPositionWorld, + shapeCircle: shapeCircle, + sharedUniformGroup: sharedUniformGroup, + sheen: sheen, + sheenRoughness: sheenRoughness, + shiftLeft: shiftLeft, + shiftRight: shiftRight, + shininess: shininess, + sign: sign, + sin: sin, + sinc: sinc, + skinning: skinning, + smoothstep: smoothstep, + smoothstepElement: smoothstepElement, + specularColor: specularColor, + specularF90: specularF90, + spherizeUV: spherizeUV, + split: split, + spritesheetUV: spritesheetUV, + sqrt: sqrt, + stack: stack, + step: step, + stepElement: stepElement, + storage: storage, + storageBarrier: storageBarrier, + storageObject: storageObject, + storageTexture: storageTexture, + string: string, + struct: struct, + sub: sub, + subBuild: subBuild, + subgroupIndex: subgroupIndex, + subgroupSize: subgroupSize, + tan: tan, + tangentGeometry: tangentGeometry, + tangentLocal: tangentLocal, + tangentView: tangentView, + tangentWorld: tangentWorld, + temp: temp, + texture: texture, + texture3D: texture3D, + textureBarrier: textureBarrier, + textureBicubic: textureBicubic, + textureBicubicLevel: textureBicubicLevel, + textureCubeUV: textureCubeUV, + textureLoad: textureLoad, + textureSize: textureSize, + textureStore: textureStore, + thickness: thickness, + time: time, + timerDelta: timerDelta, + timerGlobal: timerGlobal, + timerLocal: timerLocal, + toneMapping: toneMapping, + toneMappingExposure: toneMappingExposure, + toonOutlinePass: toonOutlinePass, + transformDirection: transformDirection, + transformNormal: transformNormal, + transformNormalToView: transformNormalToView, + transformedClearcoatNormalView: transformedClearcoatNormalView, + transformedNormalView: transformedNormalView, + transformedNormalWorld: transformedNormalWorld, + transmission: transmission, + transpose: transpose, + triNoise3D: triNoise3D, + triplanarTexture: triplanarTexture, + triplanarTextures: triplanarTextures, + trunc: trunc, + uint: uint, + uniform: uniform, + uniformArray: uniformArray, + uniformCubeTexture: uniformCubeTexture, + uniformGroup: uniformGroup, + uniformTexture: uniformTexture, + unpremultiplyAlpha: unpremultiplyAlpha, + userData: userData, + uv: uv$1, + uvec2: uvec2, + uvec3: uvec3, + uvec4: uvec4, + varying: varying, + varyingProperty: varyingProperty, + vec2: vec2, + vec3: vec3, + vec4: vec4, + vectorComponents: vectorComponents, + velocity: velocity, + vertexColor: vertexColor, + vertexIndex: vertexIndex, + vertexStage: vertexStage, + vibrance: vibrance, + viewZToLogarithmicDepth: viewZToLogarithmicDepth, + viewZToOrthographicDepth: viewZToOrthographicDepth, + viewZToPerspectiveDepth: viewZToPerspectiveDepth, + viewport: viewport, + viewportCoordinate: viewportCoordinate, + viewportDepthTexture: viewportDepthTexture, + viewportLinearDepth: viewportLinearDepth, + viewportMipTexture: viewportMipTexture, + viewportResolution: viewportResolution, + viewportSafeUV: viewportSafeUV, + viewportSharedTexture: viewportSharedTexture, + viewportSize: viewportSize, + viewportTexture: viewportTexture, + viewportUV: viewportUV, + wgsl: wgsl, + wgslFn: wgslFn, + workgroupArray: workgroupArray, + workgroupBarrier: workgroupBarrier, + workgroupId: workgroupId, + workingToColorSpace: workingToColorSpace, + xor: xor +}); + +const _clearColor = /*@__PURE__*/ new Color4(); + +/** + * This renderer module manages the background. + * + * @private + * @augments DataMap + */ +class Background extends DataMap { + + /** + * Constructs a new background management component. + * + * @param {Renderer} renderer - The renderer. + * @param {Nodes} nodes - Renderer component for managing nodes related logic. + */ + constructor( renderer, nodes ) { + + super(); + + /** + * The renderer. + * + * @type {Renderer} + */ + this.renderer = renderer; + + /** + * Renderer component for managing nodes related logic. + * + * @type {Nodes} + */ + this.nodes = nodes; + + } + + /** + * Updates the background for the given scene. Depending on how `Scene.background` + * or `Scene.backgroundNode` are configured, this method might configure a simple clear + * or add a mesh to the render list for rendering the background as a textured plane + * or skybox. + * + * @param {Scene} scene - The scene. + * @param {RenderList} renderList - The current render list. + * @param {RenderContext} renderContext - The current render context. + */ + update( scene, renderList, renderContext ) { + + const renderer = this.renderer; + const background = this.nodes.getBackgroundNode( scene ) || scene.background; + + let forceClear = false; + + if ( background === null ) { + + // no background settings, use clear color configuration from the renderer + + renderer._clearColor.getRGB( _clearColor ); + _clearColor.a = renderer._clearColor.a; + + } else if ( background.isColor === true ) { + + // background is an opaque color + + background.getRGB( _clearColor ); + _clearColor.a = 1; + + forceClear = true; + + } else if ( background.isNode === true ) { + + const sceneData = this.get( scene ); + const backgroundNode = background; + + _clearColor.copy( renderer._clearColor ); + + let backgroundMesh = sceneData.backgroundMesh; + + if ( backgroundMesh === undefined ) { + + const backgroundMeshNode = context( vec4( backgroundNode ).mul( backgroundIntensity ), { + // @TODO: Add Texture2D support using node context + getUV: () => backgroundRotation.mul( normalWorldGeometry ), + getTextureLevel: () => backgroundBlurriness + } ); + + let viewProj = modelViewProjection; + viewProj = viewProj.setZ( viewProj.w ); + + const nodeMaterial = new NodeMaterial(); + nodeMaterial.name = 'Background.material'; + nodeMaterial.side = BackSide; + nodeMaterial.depthTest = false; + nodeMaterial.depthWrite = false; + nodeMaterial.allowOverride = false; + nodeMaterial.fog = false; + nodeMaterial.lights = false; + nodeMaterial.vertexNode = viewProj; + nodeMaterial.colorNode = backgroundMeshNode; + + sceneData.backgroundMeshNode = backgroundMeshNode; + sceneData.backgroundMesh = backgroundMesh = new Mesh( new SphereGeometry( 1, 32, 32 ), nodeMaterial ); + backgroundMesh.frustumCulled = false; + backgroundMesh.name = 'Background.mesh'; + + backgroundMesh.onBeforeRender = function ( renderer, scene, camera ) { + + this.matrixWorld.copyPosition( camera.matrixWorld ); + + }; + + function onBackgroundDispose() { + + background.removeEventListener( 'dispose', onBackgroundDispose ); + + backgroundMesh.material.dispose(); + backgroundMesh.geometry.dispose(); + + } + + background.addEventListener( 'dispose', onBackgroundDispose ); + + } + + const backgroundCacheKey = backgroundNode.getCacheKey(); + + if ( sceneData.backgroundCacheKey !== backgroundCacheKey ) { + + sceneData.backgroundMeshNode.node = vec4( backgroundNode ).mul( backgroundIntensity ); + sceneData.backgroundMeshNode.needsUpdate = true; + + backgroundMesh.material.needsUpdate = true; + + sceneData.backgroundCacheKey = backgroundCacheKey; + + } + + renderList.unshift( backgroundMesh, backgroundMesh.geometry, backgroundMesh.material, 0, 0, null, null ); + + } else { + + console.error( 'THREE.Renderer: Unsupported background configuration.', background ); + + } + + // + + const environmentBlendMode = renderer.xr.getEnvironmentBlendMode(); + + if ( environmentBlendMode === 'additive' ) { + + _clearColor.set( 0, 0, 0, 1 ); + + } else if ( environmentBlendMode === 'alpha-blend' ) { + + _clearColor.set( 0, 0, 0, 0 ); + + } + + // + + if ( renderer.autoClear === true || forceClear === true ) { + + const clearColorValue = renderContext.clearColorValue; + + clearColorValue.r = _clearColor.r; + clearColorValue.g = _clearColor.g; + clearColorValue.b = _clearColor.b; + clearColorValue.a = _clearColor.a; + + // premultiply alpha + + if ( renderer.backend.isWebGLBackend === true || renderer.alpha === true ) { + + clearColorValue.r *= clearColorValue.a; + clearColorValue.g *= clearColorValue.a; + clearColorValue.b *= clearColorValue.a; + + } + + // + + renderContext.depthClearValue = renderer._clearDepth; + renderContext.stencilClearValue = renderer._clearStencil; + + renderContext.clearColor = renderer.autoClearColor === true; + renderContext.clearDepth = renderer.autoClearDepth === true; + renderContext.clearStencil = renderer.autoClearStencil === true; + + } else { + + renderContext.clearColor = false; + renderContext.clearDepth = false; + renderContext.clearStencil = false; + + } + + } + +} + +let _id$6 = 0; + +/** + * A bind group represents a collection of bindings and thus a collection + * or resources. Bind groups are assigned to pipelines to provide them + * with the required resources (like uniform buffers or textures). + * + * @private + */ +class BindGroup { + + /** + * Constructs a new bind group. + * + * @param {string} name - The bind group's name. + * @param {Array} bindings - An array of bindings. + * @param {number} index - The group index. + * @param {Array} bindingsReference - An array of reference bindings. + */ + constructor( name = '', bindings = [], index = 0, bindingsReference = [] ) { + + /** + * The bind group's name. + * + * @type {string} + */ + this.name = name; + + /** + * An array of bindings. + * + * @type {Array} + */ + this.bindings = bindings; + + /** + * The group index. + * + * @type {number} + */ + this.index = index; + + /** + * An array of reference bindings. + * + * @type {Array} + */ + this.bindingsReference = bindingsReference; + + /** + * The group's ID. + * + * @type {number} + */ + this.id = _id$6 ++; + + } + +} + +/** + * This module represents the state of a node builder after it was + * used to build the nodes for a render object. The state holds the + * results of the build for further processing in the renderer. + * + * Render objects with identical cache keys share the same node builder state. + * + * @private + */ +class NodeBuilderState { + + /** + * Constructs a new node builder state. + * + * @param {string} vertexShader - The native vertex shader code. + * @param {string} fragmentShader - The native fragment shader code. + * @param {string} computeShader - The native compute shader code. + * @param {Array} nodeAttributes - An array of node attributes. + * @param {Array} bindings - An array of bind groups. + * @param {Array} updateNodes - An array of nodes that implement their `update()` method. + * @param {Array} updateBeforeNodes - An array of nodes that implement their `updateBefore()` method. + * @param {Array} updateAfterNodes - An array of nodes that implement their `updateAfter()` method. + * @param {NodeMaterialObserver} observer - A node material observer. + * @param {Array} transforms - An array with transform attribute objects. Only relevant when using compute shaders with WebGL 2. + */ + constructor( vertexShader, fragmentShader, computeShader, nodeAttributes, bindings, updateNodes, updateBeforeNodes, updateAfterNodes, observer, transforms = [] ) { + + /** + * The native vertex shader code. + * + * @type {string} + */ + this.vertexShader = vertexShader; + + /** + * The native fragment shader code. + * + * @type {string} + */ + this.fragmentShader = fragmentShader; + + /** + * The native compute shader code. + * + * @type {string} + */ + this.computeShader = computeShader; + + /** + * An array with transform attribute objects. + * Only relevant when using compute shaders with WebGL 2. + * + * @type {Array} + */ + this.transforms = transforms; + + /** + * An array of node attributes representing + * the attributes of the shaders. + * + * @type {Array} + */ + this.nodeAttributes = nodeAttributes; + + /** + * An array of bind groups representing the uniform or storage + * buffers, texture or samplers of the shader. + * + * @type {Array} + */ + this.bindings = bindings; + + /** + * An array of nodes that implement their `update()` method. + * + * @type {Array} + */ + this.updateNodes = updateNodes; + + /** + * An array of nodes that implement their `updateBefore()` method. + * + * @type {Array} + */ + this.updateBeforeNodes = updateBeforeNodes; + + /** + * An array of nodes that implement their `updateAfter()` method. + * + * @type {Array} + */ + this.updateAfterNodes = updateAfterNodes; + + /** + * A node material observer. + * + * @type {NodeMaterialObserver} + */ + this.observer = observer; + + /** + * How often this state is used by render objects. + * + * @type {number} + */ + this.usedTimes = 0; + + } + + /** + * This method is used to create a array of bind groups based + * on the existing bind groups of this state. Shared groups are + * not cloned. + * + * @return {Array} A array of bind groups. + */ + createBindings() { + + const bindings = []; + + for ( const instanceGroup of this.bindings ) { + + const shared = instanceGroup.bindings[ 0 ].groupNode.shared; // All bindings in the group must have the same groupNode. + + if ( shared !== true ) { + + const bindingsGroup = new BindGroup( instanceGroup.name, [], instanceGroup.index, instanceGroup ); + bindings.push( bindingsGroup ); + + for ( const instanceBinding of instanceGroup.bindings ) { + + bindingsGroup.bindings.push( instanceBinding.clone() ); + + } + + } else { + + bindings.push( instanceGroup ); + + } + + } + + return bindings; + + } + +} + +/** + * {@link NodeBuilder} is going to create instances of this class during the build process + * of nodes. They represent the final shader attributes that are going to be generated + * by the builder. Arrays of node attributes is maintained in {@link NodeBuilder#attributes} + * and {@link NodeBuilder#bufferAttributes} for this purpose. + */ +class NodeAttribute { + + /** + * Constructs a new node attribute. + * + * @param {string} name - The name of the attribute. + * @param {string} type - The type of the attribute. + * @param {?Node} node - An optional reference to the node. + */ + constructor( name, type, node = null ) { + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isNodeAttribute = true; + + /** + * The name of the attribute. + * + * @type {string} + */ + this.name = name; + + /** + * The type of the attribute. + * + * @type {string} + */ + this.type = type; + + /** + * An optional reference to the node. + * + * @type {?Node} + * @default null + */ + this.node = node; + + } + +} + +/** + * {@link NodeBuilder} is going to create instances of this class during the build process + * of nodes. They represent the final shader uniforms that are going to be generated + * by the builder. A dictionary of node uniforms is maintained in {@link NodeBuilder#uniforms} + * for this purpose. + */ +class NodeUniform { + + /** + * Constructs a new node uniform. + * + * @param {string} name - The name of the uniform. + * @param {string} type - The type of the uniform. + * @param {UniformNode} node - An reference to the node. + */ + constructor( name, type, node ) { + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isNodeUniform = true; + + /** + * The name of the uniform. + * + * @type {string} + */ + this.name = name; + + /** + * The type of the uniform. + * + * @type {string} + */ + this.type = type; + + /** + * An reference to the node. + * + * @type {UniformNode} + */ + this.node = node.getSelf(); + + } + + /** + * The value of the uniform node. + * + * @type {any} + */ + get value() { + + return this.node.value; + + } + + set value( val ) { + + this.node.value = val; + + } + + /** + * The id of the uniform node. + * + * @type {number} + */ + get id() { + + return this.node.id; + + } + + /** + * The uniform node's group. + * + * @type {UniformGroupNode} + */ + get groupNode() { + + return this.node.groupNode; + + } + +} + +/** + * {@link NodeBuilder} is going to create instances of this class during the build process + * of nodes. They represent the final shader variables that are going to be generated + * by the builder. A dictionary of node variables is maintained in {@link NodeBuilder#vars} for + * this purpose. + */ +class NodeVar { + + /** + * Constructs a new node variable. + * + * @param {string} name - The name of the variable. + * @param {string} type - The type of the variable. + * @param {boolean} [readOnly=false] - The read-only flag. + * @param {?number} [count=null] - The size. + */ + constructor( name, type, readOnly = false, count = null ) { + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isNodeVar = true; + + /** + * The name of the variable. + * + * @type {string} + */ + this.name = name; + + /** + * The type of the variable. + * + * @type {string} + */ + this.type = type; + + /** + * The read-only flag. + * + * @type {boolean} + */ + this.readOnly = readOnly; + + /** + * The size. + * + * @type {?number} + */ + this.count = count; + + } + +} + +/** + * {@link NodeBuilder} is going to create instances of this class during the build process + * of nodes. They represent the final shader varyings that are going to be generated + * by the builder. An array of node varyings is maintained in {@link NodeBuilder#varyings} for + * this purpose. + * + * @augments NodeVar + */ +class NodeVarying extends NodeVar { + + /** + * Constructs a new node varying. + * + * @param {string} name - The name of the varying. + * @param {string} type - The type of the varying. + * @param {?string} interpolationType - The interpolation type of the varying. + * @param {?string} interpolationSampling - The interpolation sampling type of the varying. + */ + constructor( name, type, interpolationType = null, interpolationSampling = null ) { + + super( name, type ); + + /** + * Whether this varying requires interpolation or not. This property can be used + * to check if the varying can be optimized for a variable. + * + * @type {boolean} + * @default false + */ + this.needsInterpolation = false; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isNodeVarying = true; + + /** + * The interpolation type of the varying data. + * + * @type {?string} + * @default null + */ + this.interpolationType = interpolationType; + + /** + * The interpolation sampling type of varying data. + * + * @type {?string} + * @default null + */ + this.interpolationSampling = interpolationSampling; + + } + +} + +/** + * {@link NodeBuilder} is going to create instances of this class during the build process + * of nodes. They represent user-defined, native shader code portions that are going to be + * injected by the builder. A dictionary of node codes is maintained in {@link NodeBuilder#codes} + * for this purpose. + */ +class NodeCode { + + /** + * Constructs a new code node. + * + * @param {string} name - The name of the code. + * @param {string} type - The node type. + * @param {string} [code=''] - The native shader code. + */ + constructor( name, type, code = '' ) { + + /** + * The name of the code. + * + * @type {string} + */ + this.name = name; + + /** + * The node type. + * + * @type {string} + */ + this.type = type; + + /** + * The native shader code. + * + * @type {string} + * @default '' + */ + this.code = code; + + Object.defineProperty( this, 'isNodeCode', { value: true } ); + + } + +} + +let _id$5 = 0; + +/** + * This utility class is used in {@link NodeBuilder} as an internal + * cache data structure for node data. + */ +class NodeCache { + + /** + * Constructs a new node cache. + * + * @param {?NodeCache} parent - A reference to a parent cache. + */ + constructor( parent = null ) { + + /** + * The id of the cache. + * + * @type {number} + * @readonly + */ + this.id = _id$5 ++; + + /** + * A weak map for managing node data. + * + * @type {WeakMap} + */ + this.nodesData = new WeakMap(); + + /** + * Reference to a parent node cache. + * + * @type {?NodeCache} + * @default null + */ + this.parent = parent; + + } + + /** + * Returns the data for the given node. + * + * @param {Node} node - The node. + * @return {?Object} The data for the node. + */ + getData( node ) { + + let data = this.nodesData.get( node ); + + if ( data === undefined && this.parent !== null ) { + + data = this.parent.getData( node ); + + } + + return data; + + } + + /** + * Sets the data for a given node. + * + * @param {Node} node - The node. + * @param {Object} data - The data that should be cached. + */ + setData( node, data ) { + + this.nodesData.set( node, data ); + + } + +} + +class StructType { + + constructor( name, members ) { + + this.name = name; + this.members = members; + this.output = false; + + } + +} + +/** + * Abstract base class for uniforms. + * + * @abstract + * @private + */ +class Uniform { + + /** + * Constructs a new uniform. + * + * @param {string} name - The uniform's name. + * @param {any} value - The uniform's value. + */ + constructor( name, value ) { + + /** + * The uniform's name. + * + * @type {string} + */ + this.name = name; + + /** + * The uniform's value. + * + * @type {any} + */ + this.value = value; + + /** + * Used to build the uniform buffer according to the STD140 layout. + * Derived uniforms will set this property to a data type specific + * value. + * + * @type {number} + */ + this.boundary = 0; + + /** + * The item size. Derived uniforms will set this property to a data + * type specific value. + * + * @type {number} + */ + this.itemSize = 0; + + /** + * This property is set by {@link UniformsGroup} and marks + * the start position in the uniform buffer. + * + * @type {number} + */ + this.offset = 0; + + } + + /** + * Sets the uniform's value. + * + * @param {any} value - The value to set. + */ + setValue( value ) { + + this.value = value; + + } + + /** + * Returns the uniform's value. + * + * @return {any} The value. + */ + getValue() { + + return this.value; + + } + +} + +/** + * Represents a Number uniform. + * + * @private + * @augments Uniform + */ +class NumberUniform extends Uniform { + + /** + * Constructs a new Number uniform. + * + * @param {string} name - The uniform's name. + * @param {number} value - The uniform's value. + */ + constructor( name, value = 0 ) { + + super( name, value ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isNumberUniform = true; + + this.boundary = 4; + this.itemSize = 1; + + } + +} + +/** + * Represents a Vector2 uniform. + * + * @private + * @augments Uniform + */ +class Vector2Uniform extends Uniform { + + /** + * Constructs a new Number uniform. + * + * @param {string} name - The uniform's name. + * @param {Vector2} value - The uniform's value. + */ + constructor( name, value = new Vector2() ) { + + super( name, value ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isVector2Uniform = true; + + this.boundary = 8; + this.itemSize = 2; + + } + +} + +/** + * Represents a Vector3 uniform. + * + * @private + * @augments Uniform + */ +class Vector3Uniform extends Uniform { + + /** + * Constructs a new Number uniform. + * + * @param {string} name - The uniform's name. + * @param {Vector3} value - The uniform's value. + */ + constructor( name, value = new Vector3() ) { + + super( name, value ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isVector3Uniform = true; + + this.boundary = 16; + this.itemSize = 3; + + } + +} + +/** + * Represents a Vector4 uniform. + * + * @private + * @augments Uniform + */ +class Vector4Uniform extends Uniform { + + /** + * Constructs a new Number uniform. + * + * @param {string} name - The uniform's name. + * @param {Vector4} value - The uniform's value. + */ + constructor( name, value = new Vector4() ) { + + super( name, value ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isVector4Uniform = true; + + this.boundary = 16; + this.itemSize = 4; + + } + +} + +/** + * Represents a Color uniform. + * + * @private + * @augments Uniform + */ +class ColorUniform extends Uniform { + + /** + * Constructs a new Number uniform. + * + * @param {string} name - The uniform's name. + * @param {Color} value - The uniform's value. + */ + constructor( name, value = new Color() ) { + + super( name, value ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isColorUniform = true; + + this.boundary = 16; + this.itemSize = 3; + + } + +} + +/** + * Represents a Matrix2 uniform. + * + * @private + * @augments Uniform + */ +class Matrix2Uniform extends Uniform { + + /** + * Constructs a new Number uniform. + * + * @param {string} name - The uniform's name. + * @param {Matrix2} value - The uniform's value. + */ + constructor( name, value = new Matrix2() ) { + + super( name, value ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMatrix2Uniform = true; + + this.boundary = 8; + this.itemSize = 4; + + } + +} + + +/** + * Represents a Matrix3 uniform. + * + * @private + * @augments Uniform + */ +class Matrix3Uniform extends Uniform { + + /** + * Constructs a new Number uniform. + * + * @param {string} name - The uniform's name. + * @param {Matrix3} value - The uniform's value. + */ + constructor( name, value = new Matrix3() ) { + + super( name, value ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMatrix3Uniform = true; + + this.boundary = 48; + this.itemSize = 12; + + } + +} + +/** + * Represents a Matrix4 uniform. + * + * @private + * @augments Uniform + */ +class Matrix4Uniform extends Uniform { + + /** + * Constructs a new Number uniform. + * + * @param {string} name - The uniform's name. + * @param {Matrix4} value - The uniform's value. + */ + constructor( name, value = new Matrix4() ) { + + super( name, value ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isMatrix4Uniform = true; + + this.boundary = 64; + this.itemSize = 16; + + } + +} + +/** + * A special form of Number uniform binding type. + * It's value is managed by a node object. + * + * @private + * @augments NumberUniform + */ +class NumberNodeUniform extends NumberUniform { + + /** + * Constructs a new node-based Number uniform. + * + * @param {NodeUniform} nodeUniform - The node uniform. + */ + constructor( nodeUniform ) { + + super( nodeUniform.name, nodeUniform.value ); + + /** + * The node uniform. + * + * @type {NodeUniform} + */ + this.nodeUniform = nodeUniform; + + } + + /** + * Overwritten to return the value of the node uniform. + * + * @return {number} The value. + */ + getValue() { + + return this.nodeUniform.value; + + } + + /** + * Returns the node uniform data type. + * + * @return {string} The data type. + */ + getType() { + + return this.nodeUniform.type; + + } + +} + +/** + * A special form of Vector2 uniform binding type. + * It's value is managed by a node object. + * + * @private + * @augments Vector2Uniform + */ +class Vector2NodeUniform extends Vector2Uniform { + + /** + * Constructs a new node-based Vector2 uniform. + * + * @param {NodeUniform} nodeUniform - The node uniform. + */ + constructor( nodeUniform ) { + + super( nodeUniform.name, nodeUniform.value ); + + /** + * The node uniform. + * + * @type {NodeUniform} + */ + this.nodeUniform = nodeUniform; + + } + + /** + * Overwritten to return the value of the node uniform. + * + * @return {Vector2} The value. + */ + getValue() { + + return this.nodeUniform.value; + + } + + /** + * Returns the node uniform data type. + * + * @return {string} The data type. + */ + getType() { + + return this.nodeUniform.type; + + } + +} + +/** + * A special form of Vector3 uniform binding type. + * It's value is managed by a node object. + * + * @private + * @augments Vector3Uniform + */ +class Vector3NodeUniform extends Vector3Uniform { + + /** + * Constructs a new node-based Vector3 uniform. + * + * @param {NodeUniform} nodeUniform - The node uniform. + */ + constructor( nodeUniform ) { + + super( nodeUniform.name, nodeUniform.value ); + + /** + * The node uniform. + * + * @type {NodeUniform} + */ + this.nodeUniform = nodeUniform; + + } + + /** + * Overwritten to return the value of the node uniform. + * + * @return {Vector3} The value. + */ + getValue() { + + return this.nodeUniform.value; + + } + + /** + * Returns the node uniform data type. + * + * @return {string} The data type. + */ + getType() { + + return this.nodeUniform.type; + + } + +} + +/** + * A special form of Vector4 uniform binding type. + * It's value is managed by a node object. + * + * @private + * @augments Vector4Uniform + */ +class Vector4NodeUniform extends Vector4Uniform { + + /** + * Constructs a new node-based Vector4 uniform. + * + * @param {NodeUniform} nodeUniform - The node uniform. + */ + constructor( nodeUniform ) { + + super( nodeUniform.name, nodeUniform.value ); + + /** + * The node uniform. + * + * @type {NodeUniform} + */ + this.nodeUniform = nodeUniform; + + } + + /** + * Overwritten to return the value of the node uniform. + * + * @return {Vector4} The value. + */ + getValue() { + + return this.nodeUniform.value; + + } + + /** + * Returns the node uniform data type. + * + * @return {string} The data type. + */ + getType() { + + return this.nodeUniform.type; + + } + +} + +/** + * A special form of Color uniform binding type. + * It's value is managed by a node object. + * + * @private + * @augments ColorUniform + */ +class ColorNodeUniform extends ColorUniform { + + /** + * Constructs a new node-based Color uniform. + * + * @param {NodeUniform} nodeUniform - The node uniform. + */ + constructor( nodeUniform ) { + + super( nodeUniform.name, nodeUniform.value ); + + /** + * The node uniform. + * + * @type {NodeUniform} + */ + this.nodeUniform = nodeUniform; + + } + + /** + * Overwritten to return the value of the node uniform. + * + * @return {Color} The value. + */ + getValue() { + + return this.nodeUniform.value; + + } + + /** + * Returns the node uniform data type. + * + * @return {string} The data type. + */ + getType() { + + return this.nodeUniform.type; + + } + +} + + +/** + * A special form of Matrix2 uniform binding type. + * It's value is managed by a node object. + * + * @private + * @augments Matrix2Uniform + */ +class Matrix2NodeUniform extends Matrix2Uniform { + + /** + * Constructs a new node-based Matrix2 uniform. + * + * @param {NodeUniform} nodeUniform - The node uniform. + */ + constructor( nodeUniform ) { + + super( nodeUniform.name, nodeUniform.value ); + + /** + * The node uniform. + * + * @type {NodeUniform} + */ + this.nodeUniform = nodeUniform; + + } + + /** + * Overwritten to return the value of the node uniform. + * + * @return {Matrix2} The value. + */ + getValue() { + + return this.nodeUniform.value; + + } + + /** + * Returns the node uniform data type. + * + * @return {string} The data type. + */ + getType() { + + return this.nodeUniform.type; + + } + +} + +/** + * A special form of Matrix3 uniform binding type. + * It's value is managed by a node object. + * + * @private + * @augments Matrix3Uniform + */ +class Matrix3NodeUniform extends Matrix3Uniform { + + /** + * Constructs a new node-based Matrix3 uniform. + * + * @param {NodeUniform} nodeUniform - The node uniform. + */ + constructor( nodeUniform ) { + + super( nodeUniform.name, nodeUniform.value ); + + /** + * The node uniform. + * + * @type {NodeUniform} + */ + this.nodeUniform = nodeUniform; + + } + + /** + * Overwritten to return the value of the node uniform. + * + * @return {Matrix3} The value. + */ + getValue() { + + return this.nodeUniform.value; + + } + + /** + * Returns the node uniform data type. + * + * @return {string} The data type. + */ + getType() { + + return this.nodeUniform.type; + + } + +} + +/** + * A special form of Matrix4 uniform binding type. + * It's value is managed by a node object. + * + * @private + * @augments Matrix4Uniform + */ +class Matrix4NodeUniform extends Matrix4Uniform { + + /** + * Constructs a new node-based Matrix4 uniform. + * + * @param {NodeUniform} nodeUniform - The node uniform. + */ + constructor( nodeUniform ) { + + super( nodeUniform.name, nodeUniform.value ); + + /** + * The node uniform. + * + * @type {NodeUniform} + */ + this.nodeUniform = nodeUniform; + + } + + /** + * Overwritten to return the value of the node uniform. + * + * @return {Matrix4} The value. + */ + getValue() { + + return this.nodeUniform.value; + + } + + /** + * Returns the node uniform data type. + * + * @return {string} The data type. + */ + getType() { + + return this.nodeUniform.type; + + } + +} + +const rendererCache = new WeakMap(); + +const typeFromArray = new Map( [ + [ Int8Array, 'int' ], + [ Int16Array, 'int' ], + [ Int32Array, 'int' ], + [ Uint8Array, 'uint' ], + [ Uint16Array, 'uint' ], + [ Uint32Array, 'uint' ], + [ Float32Array, 'float' ] +] ); + +const toFloat = ( value ) => { + + if ( /e/g.test( value ) ) { + + return String( value ).replace( /\+/g, '' ); + + } else { + + value = Number( value ); + + return value + ( value % 1 ? '' : '.0' ); + + } + +}; + +/** + * Base class for builders which generate a shader program based + * on a 3D object and its node material definition. + */ +class NodeBuilder { + + /** + * Constructs a new node builder. + * + * @param {Object3D} object - The 3D object. + * @param {Renderer} renderer - The current renderer. + * @param {NodeParser} parser - A reference to a node parser. + */ + constructor( object, renderer, parser ) { + + /** + * The 3D object. + * + * @type {Object3D} + */ + this.object = object; + + /** + * The material of the 3D object. + * + * @type {?Material} + */ + this.material = ( object && object.material ) || null; + + /** + * The geometry of the 3D object. + * + * @type {?BufferGeometry} + */ + this.geometry = ( object && object.geometry ) || null; + + /** + * The current renderer. + * + * @type {Renderer} + */ + this.renderer = renderer; + + /** + * A reference to a node parser. + * + * @type {NodeParser} + */ + this.parser = parser; + + /** + * The scene the 3D object belongs to. + * + * @type {?Scene} + * @default null + */ + this.scene = null; + + /** + * The camera the 3D object is rendered with. + * + * @type {?Camera} + * @default null + */ + this.camera = null; + + /** + * A list of all nodes the builder is processing + * for this 3D object. + * + * @type {Array} + */ + this.nodes = []; + + /** + * A list of all sequential nodes. + * + * @type {Array} + */ + this.sequentialNodes = []; + + /** + * A list of all nodes which {@link Node#update} method should be executed. + * + * @type {Array} + */ + this.updateNodes = []; + + /** + * A list of all nodes which {@link Node#updateBefore} method should be executed. + * + * @type {Array} + */ + this.updateBeforeNodes = []; + + /** + * A list of all nodes which {@link Node#updateAfter} method should be executed. + * + * @type {Array} + */ + this.updateAfterNodes = []; + + /** + * A dictionary that assigns each node to a unique hash. + * + * @type {Object} + */ + this.hashNodes = {}; + + /** + * A reference to a node material observer. + * + * @type {?NodeMaterialObserver} + * @default null + */ + this.observer = null; + + /** + * A reference to the current lights node. + * + * @type {?LightsNode} + * @default null + */ + this.lightsNode = null; + + /** + * A reference to the current environment node. + * + * @type {?Node} + * @default null + */ + this.environmentNode = null; + + /** + * A reference to the current fog node. + * + * @type {?FogNode} + * @default null + */ + this.fogNode = null; + + /** + * The current clipping context. + * + * @type {?ClippingContext} + */ + this.clippingContext = null; + + /** + * The generated vertex shader. + * + * @type {?string} + */ + this.vertexShader = null; + + /** + * The generated fragment shader. + * + * @type {?string} + */ + this.fragmentShader = null; + + /** + * The generated compute shader. + * + * @type {?string} + */ + this.computeShader = null; + + /** + * Nodes used in the primary flow of code generation. + * + * @type {Object>} + */ + this.flowNodes = { vertex: [], fragment: [], compute: [] }; + + /** + * Nodes code from `.flowNodes`. + * + * @type {Object} + */ + this.flowCode = { vertex: '', fragment: '', compute: '' }; + + /** + * This dictionary holds the node uniforms of the builder. + * The uniforms are maintained in an array for each shader stage. + * + * @type {Object} + */ + this.uniforms = { vertex: [], fragment: [], compute: [], index: 0 }; + + /** + * This dictionary holds the output structs of the builder. + * The structs are maintained in an array for each shader stage. + * + * @type {Object} + */ + this.structs = { vertex: [], fragment: [], compute: [], index: 0 }; + + /** + * This dictionary holds the bindings for each shader stage. + * + * @type {Object} + */ + this.bindings = { vertex: {}, fragment: {}, compute: {} }; + + /** + * This dictionary maintains the binding indices per bind group. + * + * @type {Object} + */ + this.bindingsIndexes = {}; + + /** + * Reference to the array of bind groups. + * + * @type {?Array} + */ + this.bindGroups = null; + + /** + * This array holds the node attributes of this builder + * created via {@link AttributeNode}. + * + * @type {Array} + */ + this.attributes = []; + + /** + * This array holds the node attributes of this builder + * created via {@link BufferAttributeNode}. + * + * @type {Array} + */ + this.bufferAttributes = []; + + /** + * This array holds the node varyings of this builder. + * + * @type {Array} + */ + this.varyings = []; + + /** + * This dictionary holds the (native) node codes of this builder. + * The codes are maintained in an array for each shader stage. + * + * @type {Object>} + */ + this.codes = {}; + + /** + * This dictionary holds the node variables of this builder. + * The variables are maintained in an array for each shader stage. + * This dictionary is also used to count the number of variables + * according to their type (const, vars). + * + * @type {Object|number>} + */ + this.vars = {}; + + /** + * This dictionary holds the declarations for each shader stage. + * + * @type {Object} + */ + this.declarations = {}; + + /** + * Current code flow. + * All code generated in this stack will be stored in `.flow`. + * + * @type {{code: string}} + */ + this.flow = { code: '' }; + + /** + * A chain of nodes. + * Used to check recursive calls in node-graph. + * + * @type {Array} + */ + this.chaining = []; + + /** + * The current stack. + * This reflects the current process in the code block hierarchy, + * it is useful to know if the current process is inside a conditional for example. + * + * @type {StackNode} + */ + this.stack = stack(); + + /** + * List of stack nodes. + * The current stack hierarchy is stored in an array. + * + * @type {Array} + */ + this.stacks = []; + + /** + * A tab value. Used for shader string generation. + * + * @type {string} + * @default '\t' + */ + this.tab = '\t'; + + /** + * Reference to the current function node. + * + * @type {?FunctionNode} + * @default null + */ + this.currentFunctionNode = null; + + /** + * The builder's context. + * + * @type {Object} + */ + this.context = { + material: this.material + }; + + /** + * The builder's cache. + * + * @type {NodeCache} + */ + this.cache = new NodeCache(); + + /** + * Since the {@link NodeBuilder#cache} might be temporarily + * overwritten by other caches, this member retains the reference + * to the builder's own cache. + * + * @type {NodeCache} + * @default this.cache + */ + this.globalCache = this.cache; + + this.flowsData = new WeakMap(); + + /** + * The current shader stage. + * + * @type {?('vertex'|'fragment'|'compute'|'any')} + */ + this.shaderStage = null; + + /** + * The current build stage. + * + * @type {?('setup'|'analyze'|'generate')} + */ + this.buildStage = null; + + /** + * The sub-build layers. + * + * @type {Array} + * @default [] + */ + this.subBuildLayers = []; + + /** + * The current stack of nodes. + * + * @type {?StackNode} + * @default null + */ + this.currentStack = null; + + /** + * The current sub-build TSL function(Fn). + * + * @type {?string} + * @default null + */ + this.subBuildFn = null; + + } + + /** + * Returns the bind groups of the current renderer. + * + * @return {ChainMap} The cache. + */ + getBindGroupsCache() { + + let bindGroupsCache = rendererCache.get( this.renderer ); + + if ( bindGroupsCache === undefined ) { + + bindGroupsCache = new ChainMap(); + + rendererCache.set( this.renderer, bindGroupsCache ); + + } + + return bindGroupsCache; + + } + + /** + * Factory method for creating an instance of {@link RenderTarget} with the given + * dimensions and options. + * + * @param {number} width - The width of the render target. + * @param {number} height - The height of the render target. + * @param {Object} options - The options of the render target. + * @return {RenderTarget} The render target. + */ + createRenderTarget( width, height, options ) { + + return new RenderTarget( width, height, options ); + + } + + /** + * Factory method for creating an instance of {@link CubeRenderTarget} with the given + * dimensions and options. + * + * @param {number} size - The size of the cube render target. + * @param {Object} options - The options of the cube render target. + * @return {CubeRenderTarget} The cube render target. + */ + createCubeRenderTarget( size, options ) { + + return new CubeRenderTarget( size, options ); + + } + + /** + * Whether the given node is included in the internal array of nodes or not. + * + * @param {Node} node - The node to test. + * @return {boolean} Whether the given node is included in the internal array of nodes or not. + */ + includes( node ) { + + return this.nodes.includes( node ); + + } + + /** + * Returns the output struct name which is required by + * {@link OutputStructNode}. + * + * @abstract + * @return {string} The name of the output struct. + */ + getOutputStructName() {} + + /** + * Returns a bind group for the given group name and binding. + * + * @private + * @param {string} groupName - The group name. + * @param {Array} bindings - List of bindings. + * @return {BindGroup} The bind group + */ + _getBindGroup( groupName, bindings ) { + + const bindGroupsCache = this.getBindGroupsCache(); + + // + + const bindingsArray = []; + + let sharedGroup = true; + + for ( const binding of bindings ) { + + bindingsArray.push( binding ); + + sharedGroup = sharedGroup && binding.groupNode.shared !== true; + + } + + // + + let bindGroup; + + if ( sharedGroup ) { + + bindGroup = bindGroupsCache.get( bindingsArray ); + + if ( bindGroup === undefined ) { + + bindGroup = new BindGroup( groupName, bindingsArray, this.bindingsIndexes[ groupName ].group, bindingsArray ); + + bindGroupsCache.set( bindingsArray, bindGroup ); + + } + + } else { + + bindGroup = new BindGroup( groupName, bindingsArray, this.bindingsIndexes[ groupName ].group, bindingsArray ); + + } + + return bindGroup; + + } + + /** + * Returns an array of node uniform groups for the given group name and shader stage. + * + * @param {string} groupName - The group name. + * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage. + * @return {Array} The array of node uniform groups. + */ + getBindGroupArray( groupName, shaderStage ) { + + const bindings = this.bindings[ shaderStage ]; + + let bindGroup = bindings[ groupName ]; + + if ( bindGroup === undefined ) { + + if ( this.bindingsIndexes[ groupName ] === undefined ) { + + this.bindingsIndexes[ groupName ] = { binding: 0, group: Object.keys( this.bindingsIndexes ).length }; + + } + + bindings[ groupName ] = bindGroup = []; + + } + + return bindGroup; + + } + + /** + * Returns a list bindings of all shader stages separated by groups. + * + * @return {Array} The list of bindings. + */ + getBindings() { + + let bindingsGroups = this.bindGroups; + + if ( bindingsGroups === null ) { + + const groups = {}; + const bindings = this.bindings; + + for ( const shaderStage of shaderStages ) { + + for ( const groupName in bindings[ shaderStage ] ) { + + const uniforms = bindings[ shaderStage ][ groupName ]; + + const groupUniforms = groups[ groupName ] || ( groups[ groupName ] = [] ); + groupUniforms.push( ...uniforms ); + + } + + } + + bindingsGroups = []; + + for ( const groupName in groups ) { + + const group = groups[ groupName ]; + + const bindingsGroup = this._getBindGroup( groupName, group ); + + bindingsGroups.push( bindingsGroup ); + + } + + this.bindGroups = bindingsGroups; + + } + + return bindingsGroups; + + } + + /** + * Sorts the bind groups and updates {@link NodeBuilder#bindingsIndexes}. + */ + sortBindingGroups() { + + const bindingsGroups = this.getBindings(); + + bindingsGroups.sort( ( a, b ) => ( a.bindings[ 0 ].groupNode.order - b.bindings[ 0 ].groupNode.order ) ); + + for ( let i = 0; i < bindingsGroups.length; i ++ ) { + + const bindingGroup = bindingsGroups[ i ]; + this.bindingsIndexes[ bindingGroup.name ].group = i; + + bindingGroup.index = i; + + } + + } + + /** + * The builder maintains each node in a hash-based dictionary. + * This method sets the given node (value) with the given hash (key) into this dictionary. + * + * @param {Node} node - The node to add. + * @param {number} hash - The hash of the node. + */ + setHashNode( node, hash ) { + + this.hashNodes[ hash ] = node; + + } + + /** + * Adds a node to this builder. + * + * @param {Node} node - The node to add. + */ + addNode( node ) { + + if ( this.nodes.includes( node ) === false ) { + + this.nodes.push( node ); + + this.setHashNode( node, node.getHash( this ) ); + + } + + } + + /** + * It is used to add Nodes that will be used as FRAME and RENDER events, + * and need to follow a certain sequence in the calls to work correctly. + * This function should be called after 'setup()' in the 'build()' process to ensure that the child nodes are processed first. + * + * @param {Node} node - The node to add. + */ + addSequentialNode( node ) { + + if ( this.sequentialNodes.includes( node ) === false ) { + + this.sequentialNodes.push( node ); + + } + + } + + /** + * Checks the update types of nodes + */ + buildUpdateNodes() { + + for ( const node of this.nodes ) { + + const updateType = node.getUpdateType(); + + if ( updateType !== NodeUpdateType.NONE ) { + + this.updateNodes.push( node.getSelf() ); + + } + + } + + for ( const node of this.sequentialNodes ) { + + const updateBeforeType = node.getUpdateBeforeType(); + const updateAfterType = node.getUpdateAfterType(); + + if ( updateBeforeType !== NodeUpdateType.NONE ) { + + this.updateBeforeNodes.push( node.getSelf() ); + + } + + if ( updateAfterType !== NodeUpdateType.NONE ) { + + this.updateAfterNodes.push( node.getSelf() ); + + } + + } + + } + + /** + * A reference the current node which is the + * last node in the chain of nodes. + * + * @type {Node} + */ + get currentNode() { + + return this.chaining[ this.chaining.length - 1 ]; + + } + + /** + * Whether the given texture is filtered or not. + * + * @param {Texture} texture - The texture to check. + * @return {boolean} Whether the given texture is filtered or not. + */ + isFilteredTexture( texture ) { + + return ( texture.magFilter === LinearFilter || texture.magFilter === LinearMipmapNearestFilter || texture.magFilter === NearestMipmapLinearFilter || texture.magFilter === LinearMipmapLinearFilter || + texture.minFilter === LinearFilter || texture.minFilter === LinearMipmapNearestFilter || texture.minFilter === NearestMipmapLinearFilter || texture.minFilter === LinearMipmapLinearFilter ); + + } + + /** + * Adds the given node to the internal node chain. + * This is used to check recursive calls in node-graph. + * + * @param {Node} node - The node to add. + */ + addChain( node ) { + + /* + if ( this.chaining.indexOf( node ) !== - 1 ) { + + console.warn( 'Recursive node: ', node ); + + } + */ + + this.chaining.push( node ); + + } + + /** + * Removes the given node from the internal node chain. + * + * @param {Node} node - The node to remove. + */ + removeChain( node ) { + + const lastChain = this.chaining.pop(); + + if ( lastChain !== node ) { + + throw new Error( 'NodeBuilder: Invalid node chaining!' ); + + } + + } + + /** + * Returns the native shader method name for a given generic name. E.g. + * the method name `textureDimensions` matches the WGSL name but must be + * resolved to `textureSize` in GLSL. + * + * @abstract + * @param {string} method - The method name to resolve. + * @return {string} The resolved method name. + */ + getMethod( method ) { + + return method; + + } + + /** + * Returns a node for the given hash, see {@link NodeBuilder#setHashNode}. + * + * @param {number} hash - The hash of the node. + * @return {Node} The found node. + */ + getNodeFromHash( hash ) { + + return this.hashNodes[ hash ]; + + } + + /** + * Adds the Node to a target flow so that it can generate code in the 'generate' process. + * + * @param {('vertex'|'fragment'|'compute')} shaderStage - The shader stage. + * @param {Node} node - The node to add. + * @return {Node} The node. + */ + addFlow( shaderStage, node ) { + + this.flowNodes[ shaderStage ].push( node ); + + return node; + + } + + /** + * Sets builder's context. + * + * @param {Object} context - The context to set. + */ + setContext( context ) { + + this.context = context; + + } + + /** + * Returns the builder's current context. + * + * @return {Object} The builder's current context. + */ + getContext() { + + return this.context; + + } + + /** + * Gets a context used in shader construction that can be shared across different materials. + * This is necessary since the renderer cache can reuse shaders generated in one material and use them in another. + * + * @return {Object} The builder's current context without material. + */ + getSharedContext() { + + ({ ...this.context }); + + return this.context; + + } + + /** + * Sets builder's cache. + * + * @param {NodeCache} cache - The cache to set. + */ + setCache( cache ) { + + this.cache = cache; + + } + + /** + * Returns the builder's current cache. + * + * @return {NodeCache} The builder's current cache. + */ + getCache() { + + return this.cache; + + } + + /** + * Returns a cache for the given node. + * + * @param {Node} node - The node. + * @param {boolean} [parent=true] - Whether this node refers to a shared parent cache or not. + * @return {NodeCache} The cache. + */ + getCacheFromNode( node, parent = true ) { + + const data = this.getDataFromNode( node ); + if ( data.cache === undefined ) data.cache = new NodeCache( parent ? this.getCache() : null ); + + return data.cache; + + } + + /** + * Whether the requested feature is available or not. + * + * @abstract + * @param {string} name - The requested feature. + * @return {boolean} Whether the requested feature is supported or not. + */ + isAvailable( /*name*/ ) { + + return false; + + } + + /** + * Returns the vertexIndex input variable as a native shader string. + * + * @abstract + * @return {string} The instanceIndex shader string. + */ + getVertexIndex() { + + console.warn( 'Abstract function.' ); + + } + + /** + * Returns the instanceIndex input variable as a native shader string. + * + * @abstract + * @return {string} The instanceIndex shader string. + */ + getInstanceIndex() { + + console.warn( 'Abstract function.' ); + + } + + /** + * Returns the drawIndex input variable as a native shader string. + * Only relevant for WebGL and its `WEBGL_multi_draw` extension. + * + * @abstract + * @return {?string} The drawIndex shader string. + */ + getDrawIndex() { + + console.warn( 'Abstract function.' ); + + } + + /** + * Returns the frontFacing input variable as a native shader string. + * + * @abstract + * @return {string} The frontFacing shader string. + */ + getFrontFacing() { + + console.warn( 'Abstract function.' ); + + } + + /** + * Returns the fragCoord input variable as a native shader string. + * + * @abstract + * @return {string} The fragCoord shader string. + */ + getFragCoord() { + + console.warn( 'Abstract function.' ); + + } + + /** + * Whether to flip texture data along its vertical axis or not. WebGL needs + * this method evaluate to `true`, WebGPU to `false`. + * + * @abstract + * @return {boolean} Whether to flip texture data along its vertical axis or not. + */ + isFlipY() { + + return false; + + } + + /** + * Calling this method increases the usage count for the given node by one. + * + * @param {Node} node - The node to increase the usage count for. + * @return {number} The updated usage count. + */ + increaseUsage( node ) { + + const nodeData = this.getDataFromNode( node ); + nodeData.usageCount = nodeData.usageCount === undefined ? 1 : nodeData.usageCount + 1; + + return nodeData.usageCount; + + } + + /** + * Generates a texture sample shader string for the given texture data. + * + * @abstract + * @param {Texture} texture - The texture. + * @param {string} textureProperty - The texture property name. + * @param {string} uvSnippet - Snippet defining the texture coordinates. + * @return {string} The generated shader string. + */ + generateTexture( /* texture, textureProperty, uvSnippet */ ) { + + console.warn( 'Abstract function.' ); + + } + + /** + * Generates a texture LOD shader string for the given texture data. + * + * @abstract + * @param {Texture} texture - The texture. + * @param {string} textureProperty - The texture property name. + * @param {string} uvSnippet - Snippet defining the texture coordinates. + * @param {?string} depthSnippet - Snippet defining the 0-based texture array index to sample. + * @param {string} levelSnippet - Snippet defining the mip level. + * @return {string} The generated shader string. + */ + generateTextureLod( /* texture, textureProperty, uvSnippet, depthSnippet, levelSnippet */ ) { + + console.warn( 'Abstract function.' ); + + } + + /** + * Generates the array declaration string. + * + * @param {string} type - The type. + * @param {?number} [count] - The count. + * @return {string} The generated value as a shader string. + */ + generateArrayDeclaration( type, count ) { + + return this.getType( type ) + '[ ' + count + ' ]'; + + } + + /** + * Generates the array shader string for the given type and value. + * + * @param {string} type - The type. + * @param {?number} [count] - The count. + * @param {?Array} [values=null] - The default values. + * @return {string} The generated value as a shader string. + */ + generateArray( type, count, values = null ) { + + let snippet = this.generateArrayDeclaration( type, count ) + '( '; + + for ( let i = 0; i < count; i ++ ) { + + const value = values ? values[ i ] : null; + + if ( value !== null ) { + + snippet += value.build( this, type ); + + } else { + + snippet += this.generateConst( type ); + + } + + if ( i < count - 1 ) snippet += ', '; + + } + + snippet += ' )'; + + return snippet; + + } + + /** + * Generates the struct shader string. + * + * @param {string} type - The type. + * @param {Array} [membersLayout] - The count. + * @param {?Array} [values=null] - The default values. + * @return {string} The generated value as a shader string. + */ + generateStruct( type, membersLayout, values = null ) { + + const snippets = []; + + for ( const member of membersLayout ) { + + const { name, type } = member; + + if ( values && values[ name ] && values[ name ].isNode ) { + + snippets.push( values[ name ].build( this, type ) ); + + } else { + + snippets.push( this.generateConst( type ) ); + + } + + } + + return type + '( ' + snippets.join( ', ' ) + ' )'; + + } + + + /** + * Generates the shader string for the given type and value. + * + * @param {string} type - The type. + * @param {?any} [value=null] - The value. + * @return {string} The generated value as a shader string. + */ + generateConst( type, value = null ) { + + if ( value === null ) { + + if ( type === 'float' || type === 'int' || type === 'uint' ) value = 0; + else if ( type === 'bool' ) value = false; + else if ( type === 'color' ) value = new Color(); + else if ( type === 'vec2' ) value = new Vector2(); + else if ( type === 'vec3' ) value = new Vector3(); + else if ( type === 'vec4' ) value = new Vector4(); + + } + + if ( type === 'float' ) return toFloat( value ); + if ( type === 'int' ) return `${ Math.round( value ) }`; + if ( type === 'uint' ) return value >= 0 ? `${ Math.round( value ) }u` : '0u'; + if ( type === 'bool' ) return value ? 'true' : 'false'; + if ( type === 'color' ) return `${ this.getType( 'vec3' ) }( ${ toFloat( value.r ) }, ${ toFloat( value.g ) }, ${ toFloat( value.b ) } )`; + + const typeLength = this.getTypeLength( type ); + + const componentType = this.getComponentType( type ); + + const generateConst = value => this.generateConst( componentType, value ); + + if ( typeLength === 2 ) { + + return `${ this.getType( type ) }( ${ generateConst( value.x ) }, ${ generateConst( value.y ) } )`; + + } else if ( typeLength === 3 ) { + + return `${ this.getType( type ) }( ${ generateConst( value.x ) }, ${ generateConst( value.y ) }, ${ generateConst( value.z ) } )`; + + } else if ( typeLength === 4 && type !== 'mat2' ) { + + return `${ this.getType( type ) }( ${ generateConst( value.x ) }, ${ generateConst( value.y ) }, ${ generateConst( value.z ) }, ${ generateConst( value.w ) } )`; + + } else if ( typeLength >= 4 && value && ( value.isMatrix2 || value.isMatrix3 || value.isMatrix4 ) ) { + + return `${ this.getType( type ) }( ${ value.elements.map( generateConst ).join( ', ' ) } )`; + + } else if ( typeLength > 4 ) { + + return `${ this.getType( type ) }()`; + + } + + throw new Error( `NodeBuilder: Type '${type}' not found in generate constant attempt.` ); + + } + + /** + * It might be necessary to convert certain data types to different ones + * so this method can be used to hide the conversion. + * + * @param {string} type - The type. + * @return {string} The updated type. + */ + getType( type ) { + + if ( type === 'color' ) return 'vec3'; + + return type; + + } + + /** + * Whether the given attribute name is defined in the geometry or not. + * + * @param {string} name - The attribute name. + * @return {boolean} Whether the given attribute name is defined in the geometry. + */ + hasGeometryAttribute( name ) { + + return this.geometry && this.geometry.getAttribute( name ) !== undefined; + + } + + /** + * Returns a node attribute for the given name and type. + * + * @param {string} name - The attribute's name. + * @param {string} type - The attribute's type. + * @return {NodeAttribute} The node attribute. + */ + getAttribute( name, type ) { + + const attributes = this.attributes; + + // find attribute + + for ( const attribute of attributes ) { + + if ( attribute.name === name ) { + + return attribute; + + } + + } + + // create a new if no exist + + const attribute = new NodeAttribute( name, type ); + + this.registerDeclaration( attribute ); + + attributes.push( attribute ); + + return attribute; + + } + + /** + * Returns for the given node and shader stage the property name for the shader. + * + * @param {Node} node - The node. + * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage. + * @return {string} The property name. + */ + getPropertyName( node/*, shaderStage*/ ) { + + return node.name; + + } + + /** + * Whether the given type is a vector type or not. + * + * @param {string} type - The type to check. + * @return {boolean} Whether the given type is a vector type or not. + */ + isVector( type ) { + + return /vec\d/.test( type ); + + } + + /** + * Whether the given type is a matrix type or not. + * + * @param {string} type - The type to check. + * @return {boolean} Whether the given type is a matrix type or not. + */ + isMatrix( type ) { + + return /mat\d/.test( type ); + + } + + /** + * Whether the given type is a reference type or not. + * + * @param {string} type - The type to check. + * @return {boolean} Whether the given type is a reference type or not. + */ + isReference( type ) { + + return type === 'void' || type === 'property' || type === 'sampler' || type === 'samplerComparison' || type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' || type === 'depthTexture' || type === 'texture3D'; + + } + + /** + * Checks if the given texture requires a manual conversion to the working color space. + * + * @abstract + * @param {Texture} texture - The texture to check. + * @return {boolean} Whether the given texture requires a conversion to working color space or not. + */ + needsToWorkingColorSpace( /*texture*/ ) { + + return false; + + } + + /** + * Returns the component type of a given texture. + * + * @param {Texture} texture - The texture. + * @return {string} The component type. + */ + getComponentTypeFromTexture( texture ) { + + const type = texture.type; + + if ( texture.isDataTexture ) { + + if ( type === IntType ) return 'int'; + if ( type === UnsignedIntType ) return 'uint'; + + } + + return 'float'; + + } + + /** + * Returns the element type for a given type. + * + * @param {string} type - The type. + * @return {string} The element type. + */ + getElementType( type ) { + + if ( type === 'mat2' ) return 'vec2'; + if ( type === 'mat3' ) return 'vec3'; + if ( type === 'mat4' ) return 'vec4'; + + return this.getComponentType( type ); + + } + + /** + * Returns the component type for a given type. + * + * @param {string} type - The type. + * @return {string} The component type. + */ + getComponentType( type ) { + + type = this.getVectorType( type ); + + if ( type === 'float' || type === 'bool' || type === 'int' || type === 'uint' ) return type; + + const componentType = /(b|i|u|)(vec|mat)([2-4])/.exec( type ); + + if ( componentType === null ) return null; + + if ( componentType[ 1 ] === 'b' ) return 'bool'; + if ( componentType[ 1 ] === 'i' ) return 'int'; + if ( componentType[ 1 ] === 'u' ) return 'uint'; + + return 'float'; + + } + + /** + * Returns the vector type for a given type. + * + * @param {string} type - The type. + * @return {string} The vector type. + */ + getVectorType( type ) { + + if ( type === 'color' ) return 'vec3'; + if ( type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' || type === 'texture3D' ) return 'vec4'; + + return type; + + } + + /** + * Returns the data type for the given the length and component type. + * + * @param {number} length - The length. + * @param {string} [componentType='float'] - The component type. + * @return {string} The type. + */ + getTypeFromLength( length, componentType = 'float' ) { + + if ( length === 1 ) return componentType; + + let baseType = getTypeFromLength( length ); + const prefix = componentType === 'float' ? '' : componentType[ 0 ]; + + // fix edge case for mat2x2 being same size as vec4 + if ( /mat2/.test( componentType ) === true ) { + + baseType = baseType.replace( 'vec', 'mat' ); + + } + + return prefix + baseType; + + } + + /** + * Returns the type for a given typed array. + * + * @param {TypedArray} array - The typed array. + * @return {string} The type. + */ + getTypeFromArray( array ) { + + return typeFromArray.get( array.constructor ); + + } + + /** + * Returns the type is an integer type. + * + * @param {string} type - The type. + * @return {boolean} Whether the type is an integer type or not. + */ + isInteger( type ) { + + return /int|uint|(i|u)vec/.test( type ); + + } + + /** + * Returns the type for a given buffer attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute. + * @return {string} The type. + */ + getTypeFromAttribute( attribute ) { + + let dataAttribute = attribute; + + if ( attribute.isInterleavedBufferAttribute ) dataAttribute = attribute.data; + + const array = dataAttribute.array; + const itemSize = attribute.itemSize; + const normalized = attribute.normalized; + + let arrayType; + + if ( ! ( attribute instanceof Float16BufferAttribute ) && normalized !== true ) { + + arrayType = this.getTypeFromArray( array ); + + } + + return this.getTypeFromLength( itemSize, arrayType ); + + } + + /** + * Returns the length for the given data type. + * + * @param {string} type - The data type. + * @return {number} The length. + */ + getTypeLength( type ) { + + const vecType = this.getVectorType( type ); + const vecNum = /vec([2-4])/.exec( vecType ); + + if ( vecNum !== null ) return Number( vecNum[ 1 ] ); + if ( vecType === 'float' || vecType === 'bool' || vecType === 'int' || vecType === 'uint' ) return 1; + if ( /mat2/.test( type ) === true ) return 4; + if ( /mat3/.test( type ) === true ) return 9; + if ( /mat4/.test( type ) === true ) return 16; + + return 0; + + } + + /** + * Returns the vector type for a given matrix type. + * + * @param {string} type - The matrix type. + * @return {string} The vector type. + */ + getVectorFromMatrix( type ) { + + return type.replace( 'mat', 'vec' ); + + } + + /** + * For a given type this method changes the component type to the + * given value. E.g. `vec4` should be changed to the new component type + * `uint` which results in `uvec4`. + * + * @param {string} type - The type. + * @param {string} newComponentType - The new component type. + * @return {string} The new type. + */ + changeComponentType( type, newComponentType ) { + + return this.getTypeFromLength( this.getTypeLength( type ), newComponentType ); + + } + + /** + * Returns the integer type pendant for the given type. + * + * @param {string} type - The type. + * @return {string} The integer type. + */ + getIntegerType( type ) { + + const componentType = this.getComponentType( type ); + + if ( componentType === 'int' || componentType === 'uint' ) return type; + + return this.changeComponentType( type, 'int' ); + + } + + /** + * Adds a stack node to the internal stack. + * + * @return {StackNode} The added stack node. + */ + addStack() { + + this.stack = stack( this.stack ); + + this.stacks.push( getCurrentStack() || this.stack ); + setCurrentStack( this.stack ); + + return this.stack; + + } + + /** + * Removes the last stack node from the internal stack. + * + * @return {StackNode} The removed stack node. + */ + removeStack() { + + const lastStack = this.stack; + this.stack = lastStack.parent; + + setCurrentStack( this.stacks.pop() ); + + return lastStack; + + } + + /** + * The builder maintains (cached) data for each node during the building process. This method + * can be used to get these data for a specific shader stage and cache. + * + * @param {Node} node - The node to get the data for. + * @param {('vertex'|'fragment'|'compute'|'any')} [shaderStage=this.shaderStage] - The shader stage. + * @param {?NodeCache} cache - An optional cache. + * @return {Object} The node data. + */ + getDataFromNode( node, shaderStage = this.shaderStage, cache = null ) { + + cache = cache === null ? ( node.isGlobal( this ) ? this.globalCache : this.cache ) : cache; + + let nodeData = cache.getData( node ); + + if ( nodeData === undefined ) { + + nodeData = {}; + + cache.setData( node, nodeData ); + + } + + if ( nodeData[ shaderStage ] === undefined ) nodeData[ shaderStage ] = {}; + + // + + let data = nodeData[ shaderStage ]; + + const subBuilds = nodeData.any ? nodeData.any.subBuilds : null; + const subBuild = this.getClosestSubBuild( subBuilds ); + + if ( subBuild ) { + + if ( data.subBuildsCache === undefined ) data.subBuildsCache = {}; + + data = data.subBuildsCache[ subBuild ] || ( data.subBuildsCache[ subBuild ] = {} ); + data.subBuilds = subBuilds; + + } + + return data; + + } + + /** + * Returns the properties for the given node and shader stage. + * + * @param {Node} node - The node to get the properties for. + * @param {('vertex'|'fragment'|'compute'|'any')} [shaderStage='any'] - The shader stage. + * @return {Object} The node properties. + */ + getNodeProperties( node, shaderStage = 'any' ) { + + const nodeData = this.getDataFromNode( node, shaderStage ); + + return nodeData.properties || ( nodeData.properties = { outputNode: null } ); + + } + + /** + * Returns an instance of {@link NodeAttribute} for the given buffer attribute node. + * + * @param {BufferAttributeNode} node - The buffer attribute node. + * @param {string} type - The node type. + * @return {NodeAttribute} The node attribute. + */ + getBufferAttributeFromNode( node, type ) { + + const nodeData = this.getDataFromNode( node ); + + let bufferAttribute = nodeData.bufferAttribute; + + if ( bufferAttribute === undefined ) { + + const index = this.uniforms.index ++; + + bufferAttribute = new NodeAttribute( 'nodeAttribute' + index, type, node ); + + this.bufferAttributes.push( bufferAttribute ); + + nodeData.bufferAttribute = bufferAttribute; + + } + + return bufferAttribute; + + } + + /** + * Returns an instance of {@link StructType} for the given output struct node. + * + * @param {OutputStructNode} node - The output struct node. + * @param {Array} membersLayout - The output struct types. + * @param {?string} [name=null] - The name of the struct. + * @param {('vertex'|'fragment'|'compute'|'any')} [shaderStage=this.shaderStage] - The shader stage. + * @return {StructType} The struct type attribute. + */ + getStructTypeFromNode( node, membersLayout, name = null, shaderStage = this.shaderStage ) { + + const nodeData = this.getDataFromNode( node, shaderStage, this.globalCache ); + + let structType = nodeData.structType; + + if ( structType === undefined ) { + + const index = this.structs.index ++; + + if ( name === null ) name = 'StructType' + index; + + structType = new StructType( name, membersLayout ); + + this.structs[ shaderStage ].push( structType ); + + nodeData.structType = structType; + + } + + return structType; + + } + + /** + * Returns an instance of {@link StructType} for the given output struct node. + * + * @param {OutputStructNode} node - The output struct node. + * @param {Array} membersLayout - The output struct types. + * @return {StructType} The struct type attribute. + */ + getOutputStructTypeFromNode( node, membersLayout ) { + + const structType = this.getStructTypeFromNode( node, membersLayout, 'OutputType', 'fragment' ); + structType.output = true; + + return structType; + + } + + /** + * Returns an instance of {@link NodeUniform} for the given uniform node. + * + * @param {UniformNode} node - The uniform node. + * @param {string} type - The uniform type. + * @param {('vertex'|'fragment'|'compute'|'any')} [shaderStage=this.shaderStage] - The shader stage. + * @param {?string} name - The name of the uniform. + * @return {NodeUniform} The node uniform. + */ + getUniformFromNode( node, type, shaderStage = this.shaderStage, name = null ) { + + const nodeData = this.getDataFromNode( node, shaderStage, this.globalCache ); + + let nodeUniform = nodeData.uniform; + + if ( nodeUniform === undefined ) { + + const index = this.uniforms.index ++; + + nodeUniform = new NodeUniform( name || ( 'nodeUniform' + index ), type, node ); + + this.uniforms[ shaderStage ].push( nodeUniform ); + + this.registerDeclaration( nodeUniform ); + + nodeData.uniform = nodeUniform; + + } + + return nodeUniform; + + } + + /** + * Returns the array length. + * + * @param {Node} node - The node. + * @return {?number} The array length. + */ + getArrayCount( node ) { + + let count = null; + + if ( node.isArrayNode ) count = node.count; + else if ( node.isVarNode && node.node.isArrayNode ) count = node.node.count; + + return count; + + } + + /** + * Returns an instance of {@link NodeVar} for the given variable node. + * + * @param {VarNode} node - The variable node. + * @param {?string} name - The variable's name. + * @param {string} [type=node.getNodeType( this )] - The variable's type. + * @param {('vertex'|'fragment'|'compute'|'any')} [shaderStage=this.shaderStage] - The shader stage. + * @param {boolean} [readOnly=false] - Whether the variable is read-only or not. + * + * @return {NodeVar} The node variable. + */ + getVarFromNode( node, name = null, type = node.getNodeType( this ), shaderStage = this.shaderStage, readOnly = false ) { + + const nodeData = this.getDataFromNode( node, shaderStage ); + const subBuildVariable = this.getSubBuildProperty( 'variable', nodeData.subBuilds ); + + let nodeVar = nodeData[ subBuildVariable ]; + + if ( nodeVar === undefined ) { + + const idNS = readOnly ? '_const' : '_var'; + + const vars = this.vars[ shaderStage ] || ( this.vars[ shaderStage ] = [] ); + const id = this.vars[ idNS ] || ( this.vars[ idNS ] = 0 ); + + if ( name === null ) { + + name = ( readOnly ? 'nodeConst' : 'nodeVar' ) + id; + + this.vars[ idNS ] ++; + + } + + // + + if ( subBuildVariable !== 'variable' ) { + + name = this.getSubBuildProperty( name, nodeData.subBuilds ); + + } + + // + + const count = this.getArrayCount( node ); + + nodeVar = new NodeVar( name, type, readOnly, count ); + + if ( ! readOnly ) { + + vars.push( nodeVar ); + + } + + this.registerDeclaration( nodeVar ); + + nodeData[ subBuildVariable ] = nodeVar; + + } + + return nodeVar; + + } + + /** + * Returns whether a Node or its flow is deterministic, useful for use in `const`. + * + * @param {Node} node - The varying node. + * @return {boolean} Returns true if deterministic. + */ + isDeterministic( node ) { + + if ( node.isMathNode ) { + + return this.isDeterministic( node.aNode ) && + ( node.bNode ? this.isDeterministic( node.bNode ) : true ) && + ( node.cNode ? this.isDeterministic( node.cNode ) : true ); + + } else if ( node.isOperatorNode ) { + + return this.isDeterministic( node.aNode ) && + ( node.bNode ? this.isDeterministic( node.bNode ) : true ); + + } else if ( node.isArrayNode ) { + + if ( node.values !== null ) { + + for ( const n of node.values ) { + + if ( ! this.isDeterministic( n ) ) { + + return false; + + } + + } + + } + + return true; + + } else if ( node.isConstNode ) { + + return true; + + } + + return false; + + } + + /** + * Returns an instance of {@link NodeVarying} for the given varying node. + * + * @param {(VaryingNode|PropertyNode)} node - The varying node. + * @param {?string} name - The varying's name. + * @param {string} [type=node.getNodeType( this )] - The varying's type. + * @param {?string} interpolationType - The interpolation type of the varying. + * @param {?string} interpolationSampling - The interpolation sampling type of the varying. + * @return {NodeVar} The node varying. + */ + getVaryingFromNode( node, name = null, type = node.getNodeType( this ), interpolationType = null, interpolationSampling = null ) { + + const nodeData = this.getDataFromNode( node, 'any' ); + const subBuildVarying = this.getSubBuildProperty( 'varying', nodeData.subBuilds ); + + let nodeVarying = nodeData[ subBuildVarying ]; + + if ( nodeVarying === undefined ) { + + const varyings = this.varyings; + const index = varyings.length; + + if ( name === null ) name = 'nodeVarying' + index; + + // + + if ( subBuildVarying !== 'varying' ) { + + name = this.getSubBuildProperty( name, nodeData.subBuilds ); + + } + + // + + nodeVarying = new NodeVarying( name, type, interpolationType, interpolationSampling ); + + varyings.push( nodeVarying ); + + this.registerDeclaration( nodeVarying ); + + nodeData[ subBuildVarying ] = nodeVarying; + + } + + return nodeVarying; + + } + + /** + * Registers a node declaration in the current shader stage. + * + * @param {Object} node - The node to be registered. + */ + registerDeclaration( node ) { + + const shaderStage = this.shaderStage; + const declarations = this.declarations[ shaderStage ] || ( this.declarations[ shaderStage ] = {} ); + + const property = this.getPropertyName( node ); + + let index = 1; + let name = property; + + // Automatically renames the property if the name is already in use. + + while ( declarations[ name ] !== undefined ) { + + name = property + '_' + index ++; + + } + + if ( index > 1 ) { + + node.name = name; + + console.warn( `THREE.TSL: Declaration name '${ property }' of '${ node.type }' already in use. Renamed to '${ name }'.` ); + + } + + declarations[ name ] = node; + + } + + /** + * Returns an instance of {@link NodeCode} for the given code node. + * + * @param {CodeNode} node - The code node. + * @param {string} type - The node type. + * @param {('vertex'|'fragment'|'compute'|'any')} [shaderStage=this.shaderStage] - The shader stage. + * @return {NodeCode} The node code. + */ + getCodeFromNode( node, type, shaderStage = this.shaderStage ) { + + const nodeData = this.getDataFromNode( node ); + + let nodeCode = nodeData.code; + + if ( nodeCode === undefined ) { + + const codes = this.codes[ shaderStage ] || ( this.codes[ shaderStage ] = [] ); + const index = codes.length; + + nodeCode = new NodeCode( 'nodeCode' + index, type ); + + codes.push( nodeCode ); + + nodeData.code = nodeCode; + + } + + return nodeCode; + + } + + /** + * Adds a code flow based on the code-block hierarchy. + + * This is used so that code-blocks like If,Else create their variables locally if the Node + * is only used inside one of these conditionals in the current shader stage. + * + * @param {Node} node - The node to add. + * @param {Node} nodeBlock - Node-based code-block. Usually 'ConditionalNode'. + */ + addFlowCodeHierarchy( node, nodeBlock ) { + + const { flowCodes, flowCodeBlock } = this.getDataFromNode( node ); + + let needsFlowCode = true; + let nodeBlockHierarchy = nodeBlock; + + while ( nodeBlockHierarchy ) { + + if ( flowCodeBlock.get( nodeBlockHierarchy ) === true ) { + + needsFlowCode = false; + break; + + } + + nodeBlockHierarchy = this.getDataFromNode( nodeBlockHierarchy ).parentNodeBlock; + + } + + if ( needsFlowCode ) { + + for ( const flowCode of flowCodes ) { + + this.addLineFlowCode( flowCode ); + + } + + } + + } + + /** + * Add a inline-code to the current flow code-block. + * + * @param {Node} node - The node to add. + * @param {string} code - The code to add. + * @param {Node} nodeBlock - Current ConditionalNode + */ + addLineFlowCodeBlock( node, code, nodeBlock ) { + + const nodeData = this.getDataFromNode( node ); + const flowCodes = nodeData.flowCodes || ( nodeData.flowCodes = [] ); + const codeBlock = nodeData.flowCodeBlock || ( nodeData.flowCodeBlock = new WeakMap() ); + + flowCodes.push( code ); + codeBlock.set( nodeBlock, true ); + + } + + /** + * Add a inline-code to the current flow. + * + * @param {string} code - The code to add. + * @param {?Node} [node= null] - Optional Node, can help the system understand if the Node is part of a code-block. + * @return {NodeBuilder} A reference to this node builder. + */ + addLineFlowCode( code, node = null ) { + + if ( code === '' ) return this; + + if ( node !== null && this.context.nodeBlock ) { + + this.addLineFlowCodeBlock( node, code, this.context.nodeBlock ); + + } + + code = this.tab + code; + + if ( ! /;\s*$/.test( code ) ) { + + code = code + ';\n'; + + } + + this.flow.code += code; + + return this; + + } + + /** + * Adds a code to the current code flow. + * + * @param {string} code - Shader code. + * @return {NodeBuilder} A reference to this node builder. + */ + addFlowCode( code ) { + + this.flow.code += code; + + return this; + + } + + /** + * Add tab in the code that will be generated so that other snippets respect the current tabulation. + * Typically used in codes with If,Else. + * + * @return {NodeBuilder} A reference to this node builder. + */ + addFlowTab() { + + this.tab += '\t'; + + return this; + + } + + /** + * Removes a tab. + * + * @return {NodeBuilder} A reference to this node builder. + */ + removeFlowTab() { + + this.tab = this.tab.slice( 0, -1 ); + + return this; + + } + + /** + * Gets the current flow data based on a Node. + * + * @param {Node} node - Node that the flow was started. + * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage. + * @return {Object} The flow data. + */ + getFlowData( node/*, shaderStage*/ ) { + + return this.flowsData.get( node ); + + } + + /** + * Executes the node flow based on a root node to generate the final shader code. + * + * @param {Node} node - The node to execute. + * @return {Object} The code flow. + */ + flowNode( node ) { + + const output = node.getNodeType( this ); + + const flowData = this.flowChildNode( node, output ); + + this.flowsData.set( node, flowData ); + + return flowData; + + } + + /** + * Includes a node in the current function node. + * + * @param {Node} node - The node to include. + * @returns {void} + */ + addInclude( node ) { + + if ( this.currentFunctionNode !== null ) { + + this.currentFunctionNode.includes.push( node ); + + } + + } + + /** + * Returns the native shader operator name for a given generic name. + * It is a similar type of method like {@link NodeBuilder#getMethod}. + * + * @param {ShaderNodeInternal} shaderNode - The shader node to build the function node with. + * @return {FunctionNode} The build function node. + */ + buildFunctionNode( shaderNode ) { + + const fn = new FunctionNode(); + + const previous = this.currentFunctionNode; + + this.currentFunctionNode = fn; + + fn.code = this.buildFunctionCode( shaderNode ); + + this.currentFunctionNode = previous; + + return fn; + + } + + /** + * Generates a code flow based on a TSL function: Fn(). + * + * @param {ShaderNodeInternal} shaderNode - A function code will be generated based on the input. + * @return {Object} + */ + flowShaderNode( shaderNode ) { + + const layout = shaderNode.layout; + + const inputs = { + [ Symbol.iterator ]() { + + let index = 0; + const values = Object.values( this ); + return { + next: () => ( { + value: values[ index ], + done: index ++ >= values.length + } ) + }; + + } + }; + + for ( const input of layout.inputs ) { + + inputs[ input.name ] = new ParameterNode( input.type, input.name ); + + } + + // + + shaderNode.layout = null; + + const callNode = shaderNode.call( inputs ); + const flowData = this.flowStagesNode( callNode, layout.type ); + + shaderNode.layout = layout; + + return flowData; + + } + + /** + * Runs the node flow through all the steps of creation, 'setup', 'analyze', 'generate'. + * + * @param {Node} node - The node to execute. + * @param {?string} output - Expected output type. For example 'vec3'. + * @return {Object} + */ + flowStagesNode( node, output = null ) { + + const previousFlow = this.flow; + const previousVars = this.vars; + const previousDeclarations = this.declarations; + const previousCache = this.cache; + const previousBuildStage = this.buildStage; + const previousStack = this.stack; + + const flow = { + code: '' + }; + + this.flow = flow; + this.vars = {}; + this.declarations = {}; + this.cache = new NodeCache(); + this.stack = stack(); + + for ( const buildStage of defaultBuildStages ) { + + this.setBuildStage( buildStage ); + + flow.result = node.build( this, output ); + + } + + flow.vars = this.getVars( this.shaderStage ); + + this.flow = previousFlow; + this.vars = previousVars; + this.declarations = previousDeclarations; + this.cache = previousCache; + this.stack = previousStack; + + this.setBuildStage( previousBuildStage ); + + return flow; + + } + + /** + * Returns the native shader operator name for a given generic name. + * It is a similar type of method like {@link NodeBuilder#getMethod}. + * + * @abstract + * @param {string} op - The operator name to resolve. + * @return {?string} The resolved operator name. + */ + getFunctionOperator( /* op */ ) { + + return null; + + } + + /** + * Builds the given shader node. + * + * @abstract + * @param {ShaderNodeInternal} shaderNode - The shader node. + * @return {string} The function code. + */ + buildFunctionCode( /* shaderNode */ ) { + + console.warn( 'Abstract function.' ); + + } + + /** + * Generates a code flow based on a child Node. + * + * @param {Node} node - The node to execute. + * @param {?string} output - Expected output type. For example 'vec3'. + * @return {Object} The code flow. + */ + flowChildNode( node, output = null ) { + + const previousFlow = this.flow; + + const flow = { + code: '' + }; + + this.flow = flow; + + flow.result = node.build( this, output ); + + this.flow = previousFlow; + + return flow; + + } + + /** + * Executes a flow of code in a different stage. + * + * Some nodes like `varying()` have the ability to compute code in vertex-stage and + * return the value in fragment-stage even if it is being executed in an input fragment. + * + * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage. + * @param {Node} node - The node to execute. + * @param {?string} output - Expected output type. For example 'vec3'. + * @param {?string} propertyName - The property name to assign the result. + * @return {Object|Node|null} The code flow or node.build() result. + */ + flowNodeFromShaderStage( shaderStage, node, output = null, propertyName = null ) { + + const previousTab = this.tab; + const previousCache = this.cache; + const previousShaderStage = this.shaderStage; + const previousContext = this.context; + + this.setShaderStage( shaderStage ); + + const context = { ...this.context }; + delete context.nodeBlock; + + this.cache = this.globalCache; + this.tab = '\t'; + this.context = context; + + let result = null; + + if ( this.buildStage === 'generate' ) { + + const flowData = this.flowChildNode( node, output ); + + if ( propertyName !== null ) { + + flowData.code += `${ this.tab + propertyName } = ${ flowData.result };\n`; + + } + + this.flowCode[ shaderStage ] = this.flowCode[ shaderStage ] + flowData.code; + + result = flowData; + + } else { + + result = node.build( this ); + + } + + this.setShaderStage( previousShaderStage ); + + this.cache = previousCache; + this.tab = previousTab; + this.context = previousContext; + + return result; + + } + + /** + * Returns an array holding all node attributes of this node builder. + * + * @return {Array} The node attributes of this builder. + */ + getAttributesArray() { + + return this.attributes.concat( this.bufferAttributes ); + + } + + /** + * Returns the attribute definitions as a shader string for the given shader stage. + * + * @abstract + * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage. + * @return {string} The attribute code section. + */ + getAttributes( /*shaderStage*/ ) { + + console.warn( 'Abstract function.' ); + + } + + /** + * Returns the varying definitions as a shader string for the given shader stage. + * + * @abstract + * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage. + * @return {string} The varying code section. + */ + getVaryings( /*shaderStage*/ ) { + + console.warn( 'Abstract function.' ); + + } + + /** + * Returns a single variable definition as a shader string for the given variable type and name. + * + * @param {string} type - The variable's type. + * @param {string} name - The variable's name. + * @param {?number} [count=null] - The array length. + * @return {string} The shader string. + */ + getVar( type, name, count = null ) { + + return `${ count !== null ? this.generateArrayDeclaration( type, count ) : this.getType( type ) } ${ name }`; + + } + + /** + * Returns the variable definitions as a shader string for the given shader stage. + * + * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage. + * @return {string} The variable code section. + */ + getVars( shaderStage ) { + + let snippet = ''; + + const vars = this.vars[ shaderStage ]; + + if ( vars !== undefined ) { + + for ( const variable of vars ) { + + snippet += `${ this.getVar( variable.type, variable.name ) }; `; + + } + + } + + return snippet; + + } + + /** + * Returns the uniform definitions as a shader string for the given shader stage. + * + * @abstract + * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage. + * @return {string} The uniform code section. + */ + getUniforms( /*shaderStage*/ ) { + + console.warn( 'Abstract function.' ); + + } + + /** + * Returns the native code definitions as a shader string for the given shader stage. + * + * @param {('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage. + * @return {string} The native code section. + */ + getCodes( shaderStage ) { + + const codes = this.codes[ shaderStage ]; + + let code = ''; + + if ( codes !== undefined ) { + + for ( const nodeCode of codes ) { + + code += nodeCode.code + '\n'; + + } + + } + + return code; + + } + + /** + * Returns the hash of this node builder. + * + * @return {string} The hash. + */ + getHash() { + + return this.vertexShader + this.fragmentShader + this.computeShader; + + } + + /** + * Sets the current shader stage. + * + * @param {?('vertex'|'fragment'|'compute'|'any')} shaderStage - The shader stage to set. + */ + setShaderStage( shaderStage ) { + + this.shaderStage = shaderStage; + + } + + /** + * Returns the current shader stage. + * + * @return {?('vertex'|'fragment'|'compute'|'any')} The current shader stage. + */ + getShaderStage() { + + return this.shaderStage; + + } + + /** + * Sets the current build stage. + * + * @param {?('setup'|'analyze'|'generate')} buildStage - The build stage to set. + */ + setBuildStage( buildStage ) { + + this.buildStage = buildStage; + + } + + /** + * Returns the current build stage. + * + * @return {?('setup'|'analyze'|'generate')} The current build stage. + */ + getBuildStage() { + + return this.buildStage; + + } + + /** + * Controls the code build of the shader stages. + * + * @abstract + */ + buildCode() { + + console.warn( 'Abstract function.' ); + + } + + /** + * Returns the current sub-build layer. + * + * @return {SubBuildNode} The current sub-build layers. + */ + get subBuild() { + + return this.subBuildLayers[ this.subBuildLayers.length - 1 ] || null; + + } + + /** + * Adds a sub-build layer to the node builder. + * + * @param {SubBuildNode} subBuild - The sub-build layer to add. + */ + addSubBuild( subBuild ) { + + this.subBuildLayers.push( subBuild ); + + } + + /** + * Removes the last sub-build layer from the node builder. + * + * @return {SubBuildNode} The removed sub-build layer. + */ + removeSubBuild() { + + return this.subBuildLayers.pop(); + + } + + /** + * Returns the closest sub-build layer for the given data. + * + * @param {Node|Set|Array} data - The data to get the closest sub-build layer from. + * @return {?string} The closest sub-build name or null if none found. + */ + getClosestSubBuild( data ) { + + let subBuilds; + + if ( data && data.isNode ) { + + if ( data.isShaderCallNodeInternal ) { + + subBuilds = data.shaderNode.subBuilds; + + } else if ( data.isStackNode ) { + + subBuilds = [ data.subBuild ]; + + } else { + + subBuilds = this.getDataFromNode( data, 'any' ).subBuilds; + + } + + } else if ( data instanceof Set ) { + + subBuilds = [ ...data ]; + + } else { + + subBuilds = data; + + } + + if ( ! subBuilds ) return null; + + const subBuildLayers = this.subBuildLayers; + + for ( let i = subBuilds.length - 1; i >= 0; i -- ) { + + const subBuild = subBuilds[ i ]; + + if ( subBuildLayers.includes( subBuild ) ) { + + return subBuild; + + } + + } + + return null; + + } + + + /** + * Returns the output node of a sub-build layer. + * + * @param {Node} node - The node to get the output from. + * @return {string} The output node name. + */ + getSubBuildOutput( node ) { + + return this.getSubBuildProperty( 'outputNode', node ); + + } + + /** + * Returns the sub-build property name for the given property and node. + * + * @param {string} [property=''] - The property name. + * @param {?Node} [node=null] - The node to get the sub-build from. + * @return {string} The sub-build property name. + */ + getSubBuildProperty( property = '', node = null ) { + + let subBuild; + + if ( node !== null ) { + + subBuild = this.getClosestSubBuild( node ); + + } else { + + subBuild = this.subBuildFn; + + } + + let result; + + if ( subBuild ) { + + result = property ? ( subBuild + '_' + property ) : subBuild; + + } else { + + result = property; + + } + + return result; + + } + + /** + * Central build method which controls the build for the given object. + * + * @return {NodeBuilder} A reference to this node builder. + */ + build() { + + const { object, material, renderer } = this; + + if ( material !== null ) { + + let nodeMaterial = renderer.library.fromMaterial( material ); + + if ( nodeMaterial === null ) { + + console.error( `NodeMaterial: Material "${ material.type }" is not compatible.` ); + + nodeMaterial = new NodeMaterial(); + + } + + nodeMaterial.build( this ); + + } else { + + this.addFlow( 'compute', object ); + + } + + // setup() -> stage 1: create possible new nodes and returns an output reference node + // analyze() -> stage 2: analyze nodes to possible optimization and validation + // generate() -> stage 3: generate shader + + for ( const buildStage of defaultBuildStages ) { + + this.setBuildStage( buildStage ); + + if ( this.context.vertex && this.context.vertex.isNode ) { + + this.flowNodeFromShaderStage( 'vertex', this.context.vertex ); + + } + + for ( const shaderStage of shaderStages ) { + + this.setShaderStage( shaderStage ); + + const flowNodes = this.flowNodes[ shaderStage ]; + + for ( const node of flowNodes ) { + + if ( buildStage === 'generate' ) { + + this.flowNode( node ); + + } else { + + node.build( this ); + + } + + } + + } + + } + + this.setBuildStage( null ); + this.setShaderStage( null ); + + // stage 4: build code for a specific output + + this.buildCode(); + this.buildUpdateNodes(); + + return this; + + } + + /** + * Returns a uniform representation which is later used for UBO generation and rendering. + * + * @param {NodeUniform} uniformNode - The uniform node. + * @param {string} type - The requested type. + * @return {Uniform} The uniform. + */ + getNodeUniform( uniformNode, type ) { + + if ( type === 'float' || type === 'int' || type === 'uint' ) return new NumberNodeUniform( uniformNode ); + if ( type === 'vec2' || type === 'ivec2' || type === 'uvec2' ) return new Vector2NodeUniform( uniformNode ); + if ( type === 'vec3' || type === 'ivec3' || type === 'uvec3' ) return new Vector3NodeUniform( uniformNode ); + if ( type === 'vec4' || type === 'ivec4' || type === 'uvec4' ) return new Vector4NodeUniform( uniformNode ); + if ( type === 'color' ) return new ColorNodeUniform( uniformNode ); + if ( type === 'mat2' ) return new Matrix2NodeUniform( uniformNode ); + if ( type === 'mat3' ) return new Matrix3NodeUniform( uniformNode ); + if ( type === 'mat4' ) return new Matrix4NodeUniform( uniformNode ); + + throw new Error( `Uniform "${type}" not declared.` ); + + } + + /** + * Formats the given shader snippet from a given type into another one. E.g. + * this method might be used to convert a simple float string `"1.0"` into a + * `vec3` representation: `"vec3( 1.0 )"`. + * + * @param {string} snippet - The shader snippet. + * @param {string} fromType - The source type. + * @param {string} toType - The target type. + * @return {string} The updated shader string. + */ + format( snippet, fromType, toType ) { + + fromType = this.getVectorType( fromType ); + toType = this.getVectorType( toType ); + + if ( fromType === toType || toType === null || this.isReference( toType ) ) { + + return snippet; + + } + + const fromTypeLength = this.getTypeLength( fromType ); + const toTypeLength = this.getTypeLength( toType ); + + if ( fromTypeLength === 16 && toTypeLength === 9 ) { + + return `${ this.getType( toType ) }( ${ snippet }[ 0 ].xyz, ${ snippet }[ 1 ].xyz, ${ snippet }[ 2 ].xyz )`; + + } + + if ( fromTypeLength === 9 && toTypeLength === 4 ) { + + return `${ this.getType( toType ) }( ${ snippet }[ 0 ].xy, ${ snippet }[ 1 ].xy )`; + + } + + + if ( fromTypeLength > 4 ) { // fromType is matrix-like + + // @TODO: ignore for now + + return snippet; + + } + + if ( toTypeLength > 4 || toTypeLength === 0 ) { // toType is matrix-like or unknown + + // @TODO: ignore for now + + return snippet; + + } + + if ( fromTypeLength === toTypeLength ) { + + return `${ this.getType( toType ) }( ${ snippet } )`; + + } + + if ( fromTypeLength > toTypeLength ) { + + snippet = toType === 'bool' ? `all( ${ snippet } )` : `${ snippet }.${ 'xyz'.slice( 0, toTypeLength ) }`; + + return this.format( snippet, this.getTypeFromLength( toTypeLength, this.getComponentType( fromType ) ), toType ); + + } + + if ( toTypeLength === 4 && fromTypeLength > 1 ) { // toType is vec4-like + + return `${ this.getType( toType ) }( ${ this.format( snippet, fromType, 'vec3' ) }, 1.0 )`; + + } + + if ( fromTypeLength === 2 ) { // fromType is vec2-like and toType is vec3-like + + return `${ this.getType( toType ) }( ${ this.format( snippet, fromType, 'vec2' ) }, 0.0 )`; + + } + + if ( fromTypeLength === 1 && toTypeLength > 1 && fromType !== this.getComponentType( toType ) ) { // fromType is float-like + + // convert a number value to vector type, e.g: + // vec3( 1u ) -> vec3( float( 1u ) ) + + snippet = `${ this.getType( this.getComponentType( toType ) ) }( ${ snippet } )`; + + } + + return `${ this.getType( toType ) }( ${ snippet } )`; // fromType is float-like + + } + + /** + * Returns a signature with the engine's current revision. + * + * @return {string} The signature. + */ + getSignature() { + + return `// Three.js r${ REVISION } - Node System\n`; + + } + + /** + * Prevents the node builder from being used as an iterable in TSL.Fn(), avoiding potential runtime errors. + */ + *[ Symbol.iterator ]() { } + +} + +/** + * Management class for updating nodes. The module tracks metrics like + * the elapsed time, delta time, the render and frame ID to correctly + * call the node update methods {@link Node#updateBefore}, {@link Node#update} + * and {@link Node#updateAfter} depending on the node's configuration. + */ +class NodeFrame { + + /** + * Constructs a new node fame. + */ + constructor() { + + /** + * The elapsed time in seconds. + * + * @type {number} + * @default 0 + */ + this.time = 0; + + /** + * The delta time in seconds. + * + * @type {number} + * @default 0 + */ + this.deltaTime = 0; + + /** + * The frame ID. + * + * @type {number} + * @default 0 + */ + this.frameId = 0; + + /** + * The render ID. + * + * @type {number} + * @default 0 + */ + this.renderId = 0; + + /** + * Used to control the {@link Node#update} call. + * + * @type {WeakMap} + */ + this.updateMap = new WeakMap(); + + /** + * Used to control the {@link Node#updateBefore} call. + * + * @type {WeakMap} + */ + this.updateBeforeMap = new WeakMap(); + + /** + * Used to control the {@link Node#updateAfter} call. + * + * @type {WeakMap} + */ + this.updateAfterMap = new WeakMap(); + + /** + * A reference to the current renderer. + * + * @type {?Renderer} + * @default null + */ + this.renderer = null; + + /** + * A reference to the current material. + * + * @type {?Material} + * @default null + */ + this.material = null; + + /** + * A reference to the current camera. + * + * @type {?Camera} + * @default null + */ + this.camera = null; + + /** + * A reference to the current 3D object. + * + * @type {?Object3D} + * @default null + */ + this.object = null; + + /** + * A reference to the current scene. + * + * @type {?Scene} + * @default null + */ + this.scene = null; + + } + + /** + * Returns a dictionary for a given node and update map which + * is used to correctly call node update methods per frame or render. + * + * @private + * @param {WeakMap} referenceMap - The reference weak map. + * @param {Node} nodeRef - The reference to the current node. + * @return {Object} The dictionary. + */ + _getMaps( referenceMap, nodeRef ) { + + let maps = referenceMap.get( nodeRef ); + + if ( maps === undefined ) { + + maps = { + renderMap: new WeakMap(), + frameMap: new WeakMap() + }; + + referenceMap.set( nodeRef, maps ); + + } + + return maps; + + } + + /** + * This method executes the {@link Node#updateBefore} for the given node. + * It makes sure {@link Node#updateBeforeType} is honored meaning the update + * is only executed once per frame, render or object depending on the update + * type. + * + * @param {Node} node - The node that should be updated. + */ + updateBeforeNode( node ) { + + const updateType = node.getUpdateBeforeType(); + const reference = node.updateReference( this ); + + if ( updateType === NodeUpdateType.FRAME ) { + + const { frameMap } = this._getMaps( this.updateBeforeMap, reference ); + + if ( frameMap.get( reference ) !== this.frameId ) { + + if ( node.updateBefore( this ) !== false ) { + + frameMap.set( reference, this.frameId ); + + } + + } + + } else if ( updateType === NodeUpdateType.RENDER ) { + + const { renderMap } = this._getMaps( this.updateBeforeMap, reference ); + + if ( renderMap.get( reference ) !== this.renderId ) { + + if ( node.updateBefore( this ) !== false ) { + + renderMap.set( reference, this.renderId ); + + } + + } + + } else if ( updateType === NodeUpdateType.OBJECT ) { + + node.updateBefore( this ); + + } + + } + + /** + * This method executes the {@link Node#updateAfter} for the given node. + * It makes sure {@link Node#updateAfterType} is honored meaning the update + * is only executed once per frame, render or object depending on the update + * type. + * + * @param {Node} node - The node that should be updated. + */ + updateAfterNode( node ) { + + const updateType = node.getUpdateAfterType(); + const reference = node.updateReference( this ); + + if ( updateType === NodeUpdateType.FRAME ) { + + const { frameMap } = this._getMaps( this.updateAfterMap, reference ); + + if ( frameMap.get( reference ) !== this.frameId ) { + + if ( node.updateAfter( this ) !== false ) { + + frameMap.set( reference, this.frameId ); + + } + + } + + } else if ( updateType === NodeUpdateType.RENDER ) { + + const { renderMap } = this._getMaps( this.updateAfterMap, reference ); + + if ( renderMap.get( reference ) !== this.renderId ) { + + if ( node.updateAfter( this ) !== false ) { + + renderMap.set( reference, this.renderId ); + + } + + } + + } else if ( updateType === NodeUpdateType.OBJECT ) { + + node.updateAfter( this ); + + } + + } + + /** + * This method executes the {@link Node#update} for the given node. + * It makes sure {@link Node#updateType} is honored meaning the update + * is only executed once per frame, render or object depending on the update + * type. + * + * @param {Node} node - The node that should be updated. + */ + updateNode( node ) { + + const updateType = node.getUpdateType(); + const reference = node.updateReference( this ); + + if ( updateType === NodeUpdateType.FRAME ) { + + const { frameMap } = this._getMaps( this.updateMap, reference ); + + if ( frameMap.get( reference ) !== this.frameId ) { + + if ( node.update( this ) !== false ) { + + frameMap.set( reference, this.frameId ); + + } + + } + + } else if ( updateType === NodeUpdateType.RENDER ) { + + const { renderMap } = this._getMaps( this.updateMap, reference ); + + if ( renderMap.get( reference ) !== this.renderId ) { + + if ( node.update( this ) !== false ) { + + renderMap.set( reference, this.renderId ); + + } + + } + + } else if ( updateType === NodeUpdateType.OBJECT ) { + + node.update( this ); + + } + + } + + /** + * Updates the internal state of the node frame. This method is + * called by the renderer in its internal animation loop. + */ + update() { + + this.frameId ++; + + if ( this.lastTime === undefined ) this.lastTime = performance.now(); + + this.deltaTime = ( performance.now() - this.lastTime ) / 1000; + + this.lastTime = performance.now(); + + this.time += this.deltaTime; + + } + +} + +/** + * Describes the input of a {@link NodeFunction}. + */ +class NodeFunctionInput { + + /** + * Constructs a new node function input. + * + * @param {string} type - The input type. + * @param {string} name - The input name. + * @param {?number} [count=null] - If the input is an Array, count will be the length. + * @param {('in'|'out'|'inout')} [qualifier=''] - The parameter qualifier (only relevant for GLSL). + * @param {boolean} [isConst=false] - Whether the input uses a const qualifier or not (only relevant for GLSL). + */ + constructor( type, name, count = null, qualifier = '', isConst = false ) { + + /** + * The input type. + * + * @type {string} + */ + this.type = type; + + /** + * The input name. + * + * @type {string} + */ + this.name = name; + + /** + * If the input is an Array, count will be the length. + * + * @type {?number} + * @default null + */ + this.count = count; + + /** + *The parameter qualifier (only relevant for GLSL). + * + * @type {('in'|'out'|'inout')} + * @default '' + */ + this.qualifier = qualifier; + + /** + * Whether the input uses a const qualifier or not (only relevant for GLSL). + * + * @type {boolean} + * @default false + */ + this.isConst = isConst; + + } + +} + +NodeFunctionInput.isNodeFunctionInput = true; + +/** + * Module for representing directional lights as nodes. + * + * @augments AnalyticLightNode + */ +class DirectionalLightNode extends AnalyticLightNode { + + static get type() { + + return 'DirectionalLightNode'; + + } + + /** + * Constructs a new directional light node. + * + * @param {?DirectionalLight} [light=null] - The directional light source. + */ + constructor( light = null ) { + + super( light ); + + } + + setupDirect() { + + const lightColor = this.colorNode; + const lightDirection = lightTargetDirection( this.light ); + + return { lightDirection, lightColor }; + + } + +} + +const _matrix41 = /*@__PURE__*/ new Matrix4(); +const _matrix42 = /*@__PURE__*/ new Matrix4(); + +let _ltcLib = null; + +/** + * Module for representing rect area lights as nodes. + * + * @augments AnalyticLightNode + */ +class RectAreaLightNode extends AnalyticLightNode { + + static get type() { + + return 'RectAreaLightNode'; + + } + + /** + * Constructs a new rect area light node. + * + * @param {?RectAreaLight} [light=null] - The rect area light source. + */ + constructor( light = null ) { + + super( light ); + + /** + * Uniform node representing the half height of the are light. + * + * @type {UniformNode} + */ + this.halfHeight = uniform( new Vector3() ).setGroup( renderGroup ); + + /** + * Uniform node representing the half width of the are light. + * + * @type {UniformNode} + */ + this.halfWidth = uniform( new Vector3() ).setGroup( renderGroup ); + + /** + * The `updateType` is set to `NodeUpdateType.RENDER` since the light + * relies on `viewMatrix` which might vary per render call. + * + * @type {string} + * @default 'render' + */ + this.updateType = NodeUpdateType.RENDER; + + } + + /** + * Overwritten to updated rect area light specific uniforms. + * + * @param {NodeFrame} frame - A reference to the current node frame. + */ + update( frame ) { + + super.update( frame ); + + const { light } = this; + + const viewMatrix = frame.camera.matrixWorldInverse; + + _matrix42.identity(); + _matrix41.copy( light.matrixWorld ); + _matrix41.premultiply( viewMatrix ); + _matrix42.extractRotation( _matrix41 ); + + this.halfWidth.value.set( light.width * 0.5, 0.0, 0.0 ); + this.halfHeight.value.set( 0.0, light.height * 0.5, 0.0 ); + + this.halfWidth.value.applyMatrix4( _matrix42 ); + this.halfHeight.value.applyMatrix4( _matrix42 ); + + } + + setupDirectRectArea( builder ) { + + let ltc_1, ltc_2; + + if ( builder.isAvailable( 'float32Filterable' ) ) { + + ltc_1 = texture( _ltcLib.LTC_FLOAT_1 ); + ltc_2 = texture( _ltcLib.LTC_FLOAT_2 ); + + } else { + + ltc_1 = texture( _ltcLib.LTC_HALF_1 ); + ltc_2 = texture( _ltcLib.LTC_HALF_2 ); + + } + + const { colorNode, light } = this; + + const lightPosition = lightViewPosition( light ); + + return { + lightColor: colorNode, + lightPosition, + halfWidth: this.halfWidth, + halfHeight: this.halfHeight, + ltc_1, + ltc_2 + }; + + } + + /** + * Used to configure the internal BRDF approximation texture data. + * + * @param {RectAreaLightTexturesLib} ltc - The BRDF approximation texture data. + */ + static setLTC( ltc ) { + + _ltcLib = ltc; + + } + +} + +/** + * Module for representing spot lights as nodes. + * + * @augments AnalyticLightNode + */ +class SpotLightNode extends AnalyticLightNode { + + static get type() { + + return 'SpotLightNode'; + + } + + /** + * Constructs a new spot light node. + * + * @param {?SpotLight} [light=null] - The spot light source. + */ + constructor( light = null ) { + + super( light ); + + /** + * Uniform node representing the cone cosine. + * + * @type {UniformNode} + */ + this.coneCosNode = uniform( 0 ).setGroup( renderGroup ); + + /** + * Uniform node representing the penumbra cosine. + * + * @type {UniformNode} + */ + this.penumbraCosNode = uniform( 0 ).setGroup( renderGroup ); + + /** + * Uniform node representing the cutoff distance. + * + * @type {UniformNode} + */ + this.cutoffDistanceNode = uniform( 0 ).setGroup( renderGroup ); + + /** + * Uniform node representing the decay exponent. + * + * @type {UniformNode} + */ + this.decayExponentNode = uniform( 0 ).setGroup( renderGroup ); + + /** + * Uniform node representing the light color. + * + * @type {UniformNode} + */ + this.colorNode = uniform( this.color ).setGroup( renderGroup ); + + } + + /** + * Overwritten to updated spot light specific uniforms. + * + * @param {NodeFrame} frame - A reference to the current node frame. + */ + update( frame ) { + + super.update( frame ); + + const { light } = this; + + this.coneCosNode.value = Math.cos( light.angle ); + this.penumbraCosNode.value = Math.cos( light.angle * ( 1 - light.penumbra ) ); + + this.cutoffDistanceNode.value = light.distance; + this.decayExponentNode.value = light.decay; + + } + + /** + * Computes the spot attenuation for the given angle. + * + * @param {NodeBuilder} builder - The node builder. + * @param {Node} angleCosine - The angle to compute the spot attenuation for. + * @return {Node} The spot attenuation. + */ + getSpotAttenuation( builder, angleCosine ) { + + const { coneCosNode, penumbraCosNode } = this; + + return smoothstep( coneCosNode, penumbraCosNode, angleCosine ); + + } + + getLightCoord( builder ) { + + const properties = builder.getNodeProperties( this ); + let projectionUV = properties.projectionUV; + + if ( projectionUV === undefined ) { + + projectionUV = lightProjectionUV( this.light, builder.context.positionWorld ); + + properties.projectionUV = projectionUV; + + } + + return projectionUV; + + } + + setupDirect( builder ) { + + const { colorNode, cutoffDistanceNode, decayExponentNode, light } = this; + + const lightVector = this.getLightVector( builder ); + + const lightDirection = lightVector.normalize(); + const angleCos = lightDirection.dot( lightTargetDirection( light ) ); + + const spotAttenuation = this.getSpotAttenuation( builder, angleCos ); + + const lightDistance = lightVector.length(); + + const lightAttenuation = getDistanceAttenuation( { + lightDistance, + cutoffDistance: cutoffDistanceNode, + decayExponent: decayExponentNode + } ); + + let lightColor = colorNode.mul( spotAttenuation ).mul( lightAttenuation ); + + let projected, lightCoord; + + if ( light.colorNode ) { + + lightCoord = this.getLightCoord( builder ); + projected = light.colorNode( lightCoord ); + + } else if ( light.map ) { + + lightCoord = this.getLightCoord( builder ); + projected = texture( light.map, lightCoord.xy ).onRenderUpdate( () => light.map ); + + } + + if ( projected ) { + + const inSpotLightMap = lightCoord.mul( 2. ).sub( 1. ).abs().lessThan( 1. ).all(); + + lightColor = inSpotLightMap.select( lightColor.mul( projected ), lightColor ); + + } + + return { lightColor, lightDirection }; + + } + +} + +/** + * An IES version of the default spot light node. + * + * @augments SpotLightNode + */ +class IESSpotLightNode extends SpotLightNode { + + static get type() { + + return 'IESSpotLightNode'; + + } + + /** + * Overwrites the default implementation to compute an IES conform spot attenuation. + * + * @param {NodeBuilder} builder - The node builder. + * @param {Node} angleCosine - The angle to compute the spot attenuation for. + * @return {Node} The spot attenuation. + */ + getSpotAttenuation( builder, angleCosine ) { + + const iesMap = this.light.iesMap; + + let spotAttenuation = null; + + if ( iesMap && iesMap.isTexture === true ) { + + const angle = angleCosine.acos().mul( 1.0 / Math.PI ); + + spotAttenuation = texture( iesMap, vec2( angle, 0 ), 0 ).r; + + } else { + + spotAttenuation = super.getSpotAttenuation( angleCosine ); + + } + + return spotAttenuation; + + } + +} + +const sdBox = /*@__PURE__*/ Fn( ( [ p, b ] ) => { + + const d = p.abs().sub( b ); + + return length( max$1( d, 0.0 ) ).add( min$1( max$1( d.x, d.y ), 0.0 ) ); + +} ); + +/** + * An implementation of a projector light node. + * + * @augments SpotLightNode + */ +class ProjectorLightNode extends SpotLightNode { + + static get type() { + + return 'ProjectorLightNode'; + + } + + update( frame ) { + + super.update( frame ); + + const light = this.light; + + this.penumbraCosNode.value = Math.min( Math.cos( light.angle * ( 1 - light.penumbra ) ), .99999 ); + + if ( light.aspect === null ) { + + let aspect = 1; + + if ( light.map !== null ) { + + aspect = light.map.width / light.map.height; + + } + + light.shadow.aspect = aspect; + + } else { + + light.shadow.aspect = light.aspect; + + } + + } + + /** + * Overwrites the default implementation to compute projection attenuation. + * + * @param {NodeBuilder} builder - The node builder. + * @return {Node} The spot attenuation. + */ + getSpotAttenuation( builder ) { + + const penumbraCos = this.penumbraCosNode; + const spotLightCoord = this.getLightCoord( builder ); + const coord = spotLightCoord.xyz.div( spotLightCoord.w ); + + const boxDist = sdBox( coord.xy.sub( vec2( 0.5 ) ), vec2( 0.5 ) ); + const angleFactor = div( -1, sub( 1.0, acos( penumbraCos ) ).sub( 1.0 ) ); + const attenuation = saturate( boxDist.mul( -2 ).mul( angleFactor ) ); + + return attenuation; + + } + +} + +/** + * Module for representing ambient lights as nodes. + * + * @augments AnalyticLightNode + */ +class AmbientLightNode extends AnalyticLightNode { + + static get type() { + + return 'AmbientLightNode'; + + } + + /** + * Constructs a new ambient light node. + * + * @param {?AmbientLight} [light=null] - The ambient light source. + */ + constructor( light = null ) { + + super( light ); + + } + + setup( { context } ) { + + context.irradiance.addAssign( this.colorNode ); + + } + +} + +/** + * Module for representing hemisphere lights as nodes. + * + * @augments AnalyticLightNode + */ +class HemisphereLightNode extends AnalyticLightNode { + + static get type() { + + return 'HemisphereLightNode'; + + } + + /** + * Constructs a new hemisphere light node. + * + * @param {?HemisphereLight} [light=null] - The hemisphere light source. + */ + constructor( light = null ) { + + super( light ); + + /** + * Uniform node representing the light's position. + * + * @type {UniformNode} + */ + this.lightPositionNode = lightPosition( light ); + + /** + * A node representing the light's direction. + * + * @type {Node} + */ + this.lightDirectionNode = this.lightPositionNode.normalize(); + + /** + * Uniform node representing the light's ground color. + * + * @type {UniformNode} + */ + this.groundColorNode = uniform( new Color() ).setGroup( renderGroup ); + + } + + /** + * Overwritten to updated hemisphere light specific uniforms. + * + * @param {NodeFrame} frame - A reference to the current node frame. + */ + update( frame ) { + + const { light } = this; + + super.update( frame ); + + this.lightPositionNode.object3d = light; + + this.groundColorNode.value.copy( light.groundColor ).multiplyScalar( light.intensity ); + + } + + setup( builder ) { + + const { colorNode, groundColorNode, lightDirectionNode } = this; + + const dotNL = normalWorld.dot( lightDirectionNode ); + const hemiDiffuseWeight = dotNL.mul( 0.5 ).add( 0.5 ); + + const irradiance = mix( groundColorNode, colorNode, hemiDiffuseWeight ); + + builder.context.irradiance.addAssign( irradiance ); + + } + +} + +/** + * Module for representing light probes as nodes. + * + * @augments AnalyticLightNode + */ +class LightProbeNode extends AnalyticLightNode { + + static get type() { + + return 'LightProbeNode'; + + } + + /** + * Constructs a new light probe node. + * + * @param {?LightProbe} [light=null] - The light probe. + */ + constructor( light = null ) { + + super( light ); + + const array = []; + + for ( let i = 0; i < 9; i ++ ) array.push( new Vector3() ); + + /** + * Light probe represented as a uniform of spherical harmonics. + * + * @type {UniformArrayNode} + */ + this.lightProbe = uniformArray( array ); + + } + + /** + * Overwritten to updated light probe specific uniforms. + * + * @param {NodeFrame} frame - A reference to the current node frame. + */ + update( frame ) { + + const { light } = this; + + super.update( frame ); + + // + + for ( let i = 0; i < 9; i ++ ) { + + this.lightProbe.array[ i ].copy( light.sh.coefficients[ i ] ).multiplyScalar( light.intensity ); + + } + + } + + setup( builder ) { + + const irradiance = getShIrradianceAt( normalWorld, this.lightProbe ); + + builder.context.irradiance.addAssign( irradiance ); + + } + +} + +/** + * Base class for node parsers. A derived parser must be implemented + * for each supported native shader language. + */ +class NodeParser { + + /** + * The method parses the given native code an returns a node function. + * + * @abstract + * @param {string} source - The native shader code. + * @return {NodeFunction} A node function. + */ + parseFunction( /*source*/ ) { + + console.warn( 'Abstract function.' ); + + } + +} + +/** + * Base class for node functions. A derived module must be implemented + * for each supported native shader language. Similar to other `Node*` modules, + * this class is only relevant during the building process and not used + * in user-level code. + */ +class NodeFunction { + + /** + * Constructs a new node function. + * + * @param {string} type - The node type. This type is the return type of the node function. + * @param {Array} inputs - The function's inputs. + * @param {string} [name=''] - The function's name. + * @param {string} [precision=''] - The precision qualifier. + */ + constructor( type, inputs, name = '', precision = '' ) { + + /** + * The node type. This type is the return type of the node function. + * + * @type {string} + */ + this.type = type; + + /** + * The function's inputs. + * + * @type {Array} + */ + this.inputs = inputs; + + /** + * The name of the uniform. + * + * @type {string} + * @default '' + */ + this.name = name; + + /** + * The precision qualifier. + * + * @type {string} + * @default '' + */ + this.precision = precision; + + } + + /** + * This method returns the native code of the node function. + * + * @abstract + * @param {string} name - The function's name. + * @return {string} A shader code. + */ + getCode( /*name = this.name*/ ) { + + console.warn( 'Abstract function.' ); + + } + +} + +NodeFunction.isNodeFunction = true; + +const declarationRegexp$1 = /^\s*(highp|mediump|lowp)?\s*([a-z_0-9]+)\s*([a-z_0-9]+)?\s*\(([\s\S]*?)\)/i; +const propertiesRegexp$1 = /[a-z_0-9]+/ig; + +const pragmaMain = '#pragma main'; + +const parse$1 = ( source ) => { + + source = source.trim(); + + const pragmaMainIndex = source.indexOf( pragmaMain ); + + const mainCode = pragmaMainIndex !== -1 ? source.slice( pragmaMainIndex + pragmaMain.length ) : source; + + const declaration = mainCode.match( declarationRegexp$1 ); + + if ( declaration !== null && declaration.length === 5 ) { + + // tokenizer + + const inputsCode = declaration[ 4 ]; + const propsMatches = []; + + let nameMatch = null; + + while ( ( nameMatch = propertiesRegexp$1.exec( inputsCode ) ) !== null ) { + + propsMatches.push( nameMatch ); + + } + + // parser + + const inputs = []; + + let i = 0; + + while ( i < propsMatches.length ) { + + const isConst = propsMatches[ i ][ 0 ] === 'const'; + + if ( isConst === true ) { + + i ++; + + } + + let qualifier = propsMatches[ i ][ 0 ]; + + if ( qualifier === 'in' || qualifier === 'out' || qualifier === 'inout' ) { + + i ++; + + } else { + + qualifier = ''; + + } + + const type = propsMatches[ i ++ ][ 0 ]; + + let count = Number.parseInt( propsMatches[ i ][ 0 ] ); + + if ( Number.isNaN( count ) === false ) i ++; + else count = null; + + const name = propsMatches[ i ++ ][ 0 ]; + + inputs.push( new NodeFunctionInput( type, name, count, qualifier, isConst ) ); + + } + + // + + const blockCode = mainCode.substring( declaration[ 0 ].length ); + + const name = declaration[ 3 ] !== undefined ? declaration[ 3 ] : ''; + const type = declaration[ 2 ]; + + const precision = declaration[ 1 ] !== undefined ? declaration[ 1 ] : ''; + + const headerCode = pragmaMainIndex !== -1 ? source.slice( 0, pragmaMainIndex ) : ''; + + return { + type, + inputs, + name, + precision, + inputsCode, + blockCode, + headerCode + }; + + } else { + + throw new Error( 'FunctionNode: Function is not a GLSL code.' ); + + } + +}; + +/** + * This class represents a GLSL node function. + * + * @augments NodeFunction + */ +class GLSLNodeFunction extends NodeFunction { + + /** + * Constructs a new GLSL node function. + * + * @param {string} source - The GLSL source. + */ + constructor( source ) { + + const { type, inputs, name, precision, inputsCode, blockCode, headerCode } = parse$1( source ); + + super( type, inputs, name, precision ); + + this.inputsCode = inputsCode; + this.blockCode = blockCode; + this.headerCode = headerCode; + + } + + /** + * This method returns the GLSL code of the node function. + * + * @param {string} [name=this.name] - The function's name. + * @return {string} The shader code. + */ + getCode( name = this.name ) { + + let code; + + const blockCode = this.blockCode; + + if ( blockCode !== '' ) { + + const { type, inputsCode, headerCode, precision } = this; + + let declarationCode = `${ type } ${ name } ( ${ inputsCode.trim() } )`; + + if ( precision !== '' ) { + + declarationCode = `${ precision } ${ declarationCode }`; + + } + + code = headerCode + declarationCode + blockCode; + + } else { + + // interface function + + code = ''; + + } + + return code; + + } + +} + +/** + * A GLSL node parser. + * + * @augments NodeParser + */ +class GLSLNodeParser extends NodeParser { + + /** + * The method parses the given GLSL code an returns a node function. + * + * @param {string} source - The GLSL code. + * @return {GLSLNodeFunction} A node function. + */ + parseFunction( source ) { + + return new GLSLNodeFunction( source ); + + } + +} + +const _outputNodeMap = new WeakMap(); +const _chainKeys$2 = []; +const _cacheKeyValues = []; + +/** + * This renderer module manages node-related objects and is the + * primary interface between the renderer and the node system. + * + * @private + * @augments DataMap + */ +class Nodes extends DataMap { + + /** + * Constructs a new nodes management component. + * + * @param {Renderer} renderer - The renderer. + * @param {Backend} backend - The renderer's backend. + */ + constructor( renderer, backend ) { + + super(); + + /** + * The renderer. + * + * @type {Renderer} + */ + this.renderer = renderer; + + /** + * The renderer's backend. + * + * @type {Backend} + */ + this.backend = backend; + + /** + * The node frame. + * + * @type {Renderer} + */ + this.nodeFrame = new NodeFrame(); + + /** + * A cache for managing node builder states. + * + * @type {Map} + */ + this.nodeBuilderCache = new Map(); + + /** + * A cache for managing data cache key data. + * + * @type {ChainMap} + */ + this.callHashCache = new ChainMap(); + + /** + * A cache for managing node uniforms group data. + * + * @type {ChainMap} + */ + this.groupsData = new ChainMap(); + + /** + * A cache for managing node objects of + * scene properties like fog or environments. + * + * @type {Object} + */ + this.cacheLib = {}; + + } + + /** + * Returns `true` if the given node uniforms group must be updated or not. + * + * @param {NodeUniformsGroup} nodeUniformsGroup - The node uniforms group. + * @return {boolean} Whether the node uniforms group requires an update or not. + */ + updateGroup( nodeUniformsGroup ) { + + const groupNode = nodeUniformsGroup.groupNode; + const name = groupNode.name; + + // objectGroup is always updated + + if ( name === objectGroup.name ) return true; + + // renderGroup is updated once per render/compute call + + if ( name === renderGroup.name ) { + + const uniformsGroupData = this.get( nodeUniformsGroup ); + const renderId = this.nodeFrame.renderId; + + if ( uniformsGroupData.renderId !== renderId ) { + + uniformsGroupData.renderId = renderId; + + return true; + + } + + return false; + + } + + // frameGroup is updated once per frame + + if ( name === frameGroup.name ) { + + const uniformsGroupData = this.get( nodeUniformsGroup ); + const frameId = this.nodeFrame.frameId; + + if ( uniformsGroupData.frameId !== frameId ) { + + uniformsGroupData.frameId = frameId; + + return true; + + } + + return false; + + } + + // other groups are updated just when groupNode.needsUpdate is true + + _chainKeys$2[ 0 ] = groupNode; + _chainKeys$2[ 1 ] = nodeUniformsGroup; + + let groupData = this.groupsData.get( _chainKeys$2 ); + if ( groupData === undefined ) this.groupsData.set( _chainKeys$2, groupData = {} ); + + _chainKeys$2.length = 0; + + if ( groupData.version !== groupNode.version ) { + + groupData.version = groupNode.version; + + return true; + + } + + return false; + + } + + /** + * Returns the cache key for the given render object. + * + * @param {RenderObject} renderObject - The render object. + * @return {number} The cache key. + */ + getForRenderCacheKey( renderObject ) { + + return renderObject.initialCacheKey; + + } + + /** + * Returns a node builder state for the given render object. + * + * @param {RenderObject} renderObject - The render object. + * @return {NodeBuilderState} The node builder state. + */ + getForRender( renderObject ) { + + const renderObjectData = this.get( renderObject ); + + let nodeBuilderState = renderObjectData.nodeBuilderState; + + if ( nodeBuilderState === undefined ) { + + const { nodeBuilderCache } = this; + + const cacheKey = this.getForRenderCacheKey( renderObject ); + + nodeBuilderState = nodeBuilderCache.get( cacheKey ); + + if ( nodeBuilderState === undefined ) { + + const nodeBuilder = this.backend.createNodeBuilder( renderObject.object, this.renderer ); + nodeBuilder.scene = renderObject.scene; + nodeBuilder.material = renderObject.material; + nodeBuilder.camera = renderObject.camera; + nodeBuilder.context.material = renderObject.material; + nodeBuilder.lightsNode = renderObject.lightsNode; + nodeBuilder.environmentNode = this.getEnvironmentNode( renderObject.scene ); + nodeBuilder.fogNode = this.getFogNode( renderObject.scene ); + nodeBuilder.clippingContext = renderObject.clippingContext; + if ( this.renderer.getOutputRenderTarget() ? this.renderer.getOutputRenderTarget().multiview : false ) { + + nodeBuilder.enableMultiview(); + + } + + nodeBuilder.build(); + + nodeBuilderState = this._createNodeBuilderState( nodeBuilder ); + + nodeBuilderCache.set( cacheKey, nodeBuilderState ); + + } + + nodeBuilderState.usedTimes ++; + + renderObjectData.nodeBuilderState = nodeBuilderState; + + } + + return nodeBuilderState; + + } + + /** + * Deletes the given object from the internal data map + * + * @param {any} object - The object to delete. + * @return {?Object} The deleted dictionary. + */ + delete( object ) { + + if ( object.isRenderObject ) { + + const nodeBuilderState = this.get( object ).nodeBuilderState; + nodeBuilderState.usedTimes --; + + if ( nodeBuilderState.usedTimes === 0 ) { + + this.nodeBuilderCache.delete( this.getForRenderCacheKey( object ) ); + + } + + } + + return super.delete( object ); + + } + + /** + * Returns a node builder state for the given compute node. + * + * @param {Node} computeNode - The compute node. + * @return {NodeBuilderState} The node builder state. + */ + getForCompute( computeNode ) { + + const computeData = this.get( computeNode ); + + let nodeBuilderState = computeData.nodeBuilderState; + + if ( nodeBuilderState === undefined ) { + + const nodeBuilder = this.backend.createNodeBuilder( computeNode, this.renderer ); + nodeBuilder.build(); + + nodeBuilderState = this._createNodeBuilderState( nodeBuilder ); + + computeData.nodeBuilderState = nodeBuilderState; + + } + + return nodeBuilderState; + + } + + /** + * Creates a node builder state for the given node builder. + * + * @private + * @param {NodeBuilder} nodeBuilder - The node builder. + * @return {NodeBuilderState} The node builder state. + */ + _createNodeBuilderState( nodeBuilder ) { + + return new NodeBuilderState( + nodeBuilder.vertexShader, + nodeBuilder.fragmentShader, + nodeBuilder.computeShader, + nodeBuilder.getAttributesArray(), + nodeBuilder.getBindings(), + nodeBuilder.updateNodes, + nodeBuilder.updateBeforeNodes, + nodeBuilder.updateAfterNodes, + nodeBuilder.observer, + nodeBuilder.transforms + ); + + } + + /** + * Returns an environment node for the current configured + * scene environment. + * + * @param {Scene} scene - The scene. + * @return {Node} A node representing the current scene environment. + */ + getEnvironmentNode( scene ) { + + this.updateEnvironment( scene ); + + let environmentNode = null; + + if ( scene.environmentNode && scene.environmentNode.isNode ) { + + environmentNode = scene.environmentNode; + + } else { + + const sceneData = this.get( scene ); + + if ( sceneData.environmentNode ) { + + environmentNode = sceneData.environmentNode; + + } + + } + + return environmentNode; + + } + + /** + * Returns a background node for the current configured + * scene background. + * + * @param {Scene} scene - The scene. + * @return {Node} A node representing the current scene background. + */ + getBackgroundNode( scene ) { + + this.updateBackground( scene ); + + let backgroundNode = null; + + if ( scene.backgroundNode && scene.backgroundNode.isNode ) { + + backgroundNode = scene.backgroundNode; + + } else { + + const sceneData = this.get( scene ); + + if ( sceneData.backgroundNode ) { + + backgroundNode = sceneData.backgroundNode; + + } + + } + + return backgroundNode; + + } + + /** + * Returns a fog node for the current configured scene fog. + * + * @param {Scene} scene - The scene. + * @return {Node} A node representing the current scene fog. + */ + getFogNode( scene ) { + + this.updateFog( scene ); + + return scene.fogNode || this.get( scene ).fogNode || null; + + } + + /** + * Returns a cache key for the given scene and lights node. + * This key is used by `RenderObject` as a part of the dynamic + * cache key (a key that must be checked every time the render + * objects is drawn). + * + * @param {Scene} scene - The scene. + * @param {LightsNode} lightsNode - The lights node. + * @return {number} The cache key. + */ + getCacheKey( scene, lightsNode ) { + + _chainKeys$2[ 0 ] = scene; + _chainKeys$2[ 1 ] = lightsNode; + + const callId = this.renderer.info.calls; + + const cacheKeyData = this.callHashCache.get( _chainKeys$2 ) || {}; + + if ( cacheKeyData.callId !== callId ) { + + const environmentNode = this.getEnvironmentNode( scene ); + const fogNode = this.getFogNode( scene ); + + if ( lightsNode ) _cacheKeyValues.push( lightsNode.getCacheKey( true ) ); + if ( environmentNode ) _cacheKeyValues.push( environmentNode.getCacheKey() ); + if ( fogNode ) _cacheKeyValues.push( fogNode.getCacheKey() ); + + _cacheKeyValues.push( this.renderer.getOutputRenderTarget() && this.renderer.getOutputRenderTarget().multiview ? 1 : 0 ); + _cacheKeyValues.push( this.renderer.shadowMap.enabled ? 1 : 0 ); + + cacheKeyData.callId = callId; + cacheKeyData.cacheKey = hashArray( _cacheKeyValues ); + + this.callHashCache.set( _chainKeys$2, cacheKeyData ); + + _cacheKeyValues.length = 0; + + } + + _chainKeys$2.length = 0; + + return cacheKeyData.cacheKey; + + } + + /** + * A boolean that indicates whether tone mapping should be enabled + * or not. + * + * @type {boolean} + */ + get isToneMappingState() { + + return this.renderer.getRenderTarget() ? false : true; + + } + + /** + * If a scene background is configured, this method makes sure to + * represent the background with a corresponding node-based implementation. + * + * @param {Scene} scene - The scene. + */ + updateBackground( scene ) { + + const sceneData = this.get( scene ); + const background = scene.background; + + if ( background ) { + + const forceUpdate = ( scene.backgroundBlurriness === 0 && sceneData.backgroundBlurriness > 0 ) || ( scene.backgroundBlurriness > 0 && sceneData.backgroundBlurriness === 0 ); + + if ( sceneData.background !== background || forceUpdate ) { + + const backgroundNode = this.getCacheNode( 'background', background, () => { + + if ( background.isCubeTexture === true || ( background.mapping === EquirectangularReflectionMapping || background.mapping === EquirectangularRefractionMapping || background.mapping === CubeUVReflectionMapping ) ) { + + if ( scene.backgroundBlurriness > 0 || background.mapping === CubeUVReflectionMapping ) { + + return pmremTexture( background ); + + } else { + + let envMap; + + if ( background.isCubeTexture === true ) { + + envMap = cubeTexture( background ); + + } else { + + envMap = texture( background ); + + } + + return cubeMapNode( envMap ); + + } + + } else if ( background.isTexture === true ) { + + return texture( background, screenUV.flipY() ).setUpdateMatrix( true ); + + } else if ( background.isColor !== true ) { + + console.error( 'WebGPUNodes: Unsupported background configuration.', background ); + + } + + }, forceUpdate ); + + sceneData.backgroundNode = backgroundNode; + sceneData.background = background; + sceneData.backgroundBlurriness = scene.backgroundBlurriness; + + } + + } else if ( sceneData.backgroundNode ) { + + delete sceneData.backgroundNode; + delete sceneData.background; + + } + + } + + /** + * This method is part of the caching of nodes which are used to represents the + * scene's background, fog or environment. + * + * @param {string} type - The type of object to cache. + * @param {Object} object - The object. + * @param {Function} callback - A callback that produces a node representation for the given object. + * @param {boolean} [forceUpdate=false] - Whether an update should be enforced or not. + * @return {Node} The node representation. + */ + getCacheNode( type, object, callback, forceUpdate = false ) { + + const nodeCache = this.cacheLib[ type ] || ( this.cacheLib[ type ] = new WeakMap() ); + + let node = nodeCache.get( object ); + + if ( node === undefined || forceUpdate ) { + + node = callback(); + nodeCache.set( object, node ); + + } + + return node; + + } + + /** + * If a scene fog is configured, this method makes sure to + * represent the fog with a corresponding node-based implementation. + * + * @param {Scene} scene - The scene. + */ + updateFog( scene ) { + + const sceneData = this.get( scene ); + const sceneFog = scene.fog; + + if ( sceneFog ) { + + if ( sceneData.fog !== sceneFog ) { + + const fogNode = this.getCacheNode( 'fog', sceneFog, () => { + + if ( sceneFog.isFogExp2 ) { + + const color = reference( 'color', 'color', sceneFog ).setGroup( renderGroup ); + const density = reference( 'density', 'float', sceneFog ).setGroup( renderGroup ); + + return fog( color, densityFogFactor( density ) ); + + } else if ( sceneFog.isFog ) { + + const color = reference( 'color', 'color', sceneFog ).setGroup( renderGroup ); + const near = reference( 'near', 'float', sceneFog ).setGroup( renderGroup ); + const far = reference( 'far', 'float', sceneFog ).setGroup( renderGroup ); + + return fog( color, rangeFogFactor( near, far ) ); + + } else { + + console.error( 'THREE.Renderer: Unsupported fog configuration.', sceneFog ); + + } + + } ); + + sceneData.fogNode = fogNode; + sceneData.fog = sceneFog; + + } + + } else { + + delete sceneData.fogNode; + delete sceneData.fog; + + } + + } + + /** + * If a scene environment is configured, this method makes sure to + * represent the environment with a corresponding node-based implementation. + * + * @param {Scene} scene - The scene. + */ + updateEnvironment( scene ) { + + const sceneData = this.get( scene ); + const environment = scene.environment; + + if ( environment ) { + + if ( sceneData.environment !== environment ) { + + const environmentNode = this.getCacheNode( 'environment', environment, () => { + + if ( environment.isCubeTexture === true ) { + + return cubeTexture( environment ); + + } else if ( environment.isTexture === true ) { + + return texture( environment ); + + } else { + + console.error( 'Nodes: Unsupported environment configuration.', environment ); + + } + + } ); + + sceneData.environmentNode = environmentNode; + sceneData.environment = environment; + + } + + } else if ( sceneData.environmentNode ) { + + delete sceneData.environmentNode; + delete sceneData.environment; + + } + + } + + getNodeFrame( renderer = this.renderer, scene = null, object = null, camera = null, material = null ) { + + const nodeFrame = this.nodeFrame; + nodeFrame.renderer = renderer; + nodeFrame.scene = scene; + nodeFrame.object = object; + nodeFrame.camera = camera; + nodeFrame.material = material; + + return nodeFrame; + + } + + getNodeFrameForRender( renderObject ) { + + return this.getNodeFrame( renderObject.renderer, renderObject.scene, renderObject.object, renderObject.camera, renderObject.material ); + + } + + /** + * Returns the current output cache key. + * + * @return {string} The output cache key. + */ + getOutputCacheKey() { + + const renderer = this.renderer; + + return renderer.toneMapping + ',' + renderer.currentColorSpace + ',' + renderer.xr.isPresenting; + + } + + /** + * Checks if the output configuration (tone mapping and color space) for + * the given target has changed. + * + * @param {Texture} outputTarget - The output target. + * @return {boolean} Whether the output configuration has changed or not. + */ + hasOutputChange( outputTarget ) { + + const cacheKey = _outputNodeMap.get( outputTarget ); + + return cacheKey !== this.getOutputCacheKey(); + + } + + /** + * Returns a node that represents the output configuration (tone mapping and + * color space) for the current target. + * + * @param {Texture} outputTarget - The output target. + * @return {Node} The output node. + */ + getOutputNode( outputTarget ) { + + const renderer = this.renderer; + const cacheKey = this.getOutputCacheKey(); + + const output = outputTarget.isArrayTexture ? + texture3D( outputTarget, vec3( screenUV, builtin( 'gl_ViewID_OVR' ) ) ).renderOutput( renderer.toneMapping, renderer.currentColorSpace ) : + texture( outputTarget, screenUV ).renderOutput( renderer.toneMapping, renderer.currentColorSpace ); + + _outputNodeMap.set( outputTarget, cacheKey ); + + return output; + + } + + /** + * Triggers the call of `updateBefore()` methods + * for all nodes of the given render object. + * + * @param {RenderObject} renderObject - The render object. + */ + updateBefore( renderObject ) { + + const nodeBuilder = renderObject.getNodeBuilderState(); + + for ( const node of nodeBuilder.updateBeforeNodes ) { + + // update frame state for each node + + this.getNodeFrameForRender( renderObject ).updateBeforeNode( node ); + + } + + } + + /** + * Triggers the call of `updateAfter()` methods + * for all nodes of the given render object. + * + * @param {RenderObject} renderObject - The render object. + */ + updateAfter( renderObject ) { + + const nodeBuilder = renderObject.getNodeBuilderState(); + + for ( const node of nodeBuilder.updateAfterNodes ) { + + // update frame state for each node + + this.getNodeFrameForRender( renderObject ).updateAfterNode( node ); + + } + + } + + /** + * Triggers the call of `update()` methods + * for all nodes of the given compute node. + * + * @param {Node} computeNode - The compute node. + */ + updateForCompute( computeNode ) { + + const nodeFrame = this.getNodeFrame(); + const nodeBuilder = this.getForCompute( computeNode ); + + for ( const node of nodeBuilder.updateNodes ) { + + nodeFrame.updateNode( node ); + + } + + } + + /** + * Triggers the call of `update()` methods + * for all nodes of the given compute node. + * + * @param {RenderObject} renderObject - The render object. + */ + updateForRender( renderObject ) { + + const nodeFrame = this.getNodeFrameForRender( renderObject ); + const nodeBuilder = renderObject.getNodeBuilderState(); + + for ( const node of nodeBuilder.updateNodes ) { + + nodeFrame.updateNode( node ); + + } + + } + + /** + * Returns `true` if the given render object requires a refresh. + * + * @param {RenderObject} renderObject - The render object. + * @return {boolean} Whether the given render object requires a refresh or not. + */ + needsRefresh( renderObject ) { + + const nodeFrame = this.getNodeFrameForRender( renderObject ); + const monitor = renderObject.getMonitor(); + + return monitor.needsRefresh( renderObject, nodeFrame ); + + } + + /** + * Frees the internal resources. + */ + dispose() { + + super.dispose(); + + this.nodeFrame = new NodeFrame(); + this.nodeBuilderCache = new Map(); + this.cacheLib = {}; + + } + +} + +const _plane = /*@__PURE__*/ new Plane(); + +/** + * Represents the state that is used to perform clipping via clipping planes. + * There is a default clipping context for each render context. When the + * scene holds instances of `ClippingGroup`, there will be a context for each + * group. + * + * @private + */ +class ClippingContext { + + /** + * Constructs a new clipping context. + * + * @param {?ClippingContext} [parentContext=null] - A reference to the parent clipping context. + */ + constructor( parentContext = null ) { + + /** + * The clipping context's version. + * + * @type {number} + * @readonly + */ + this.version = 0; + + /** + * Whether the intersection of the clipping planes is used to clip objects, rather than their union. + * + * @type {?boolean} + * @default null + */ + this.clipIntersection = null; + + /** + * The clipping context's cache key. + * + * @type {string} + */ + this.cacheKey = ''; + + /** + * Whether the shadow pass is active or not. + * + * @type {boolean} + * @default false + */ + this.shadowPass = false; + + /** + * The view normal matrix. + * + * @type {Matrix3} + */ + this.viewNormalMatrix = new Matrix3(); + + /** + * Internal cache for maintaining clipping contexts. + * + * @type {WeakMap} + */ + this.clippingGroupContexts = new WeakMap(); + + /** + * The intersection planes. + * + * @type {Array} + */ + this.intersectionPlanes = []; + + /** + * The intersection planes. + * + * @type {Array} + */ + this.unionPlanes = []; + + /** + * The version of the clipping context's parent context. + * + * @type {?number} + * @readonly + */ + this.parentVersion = null; + + if ( parentContext !== null ) { + + this.viewNormalMatrix = parentContext.viewNormalMatrix; + this.clippingGroupContexts = parentContext.clippingGroupContexts; + + this.shadowPass = parentContext.shadowPass; + this.viewMatrix = parentContext.viewMatrix; + + } + + } + + /** + * Projects the given source clipping planes and writes the result into the + * destination array. + * + * @param {Array} source - The source clipping planes. + * @param {Array} destination - The destination. + * @param {number} offset - The offset. + */ + projectPlanes( source, destination, offset ) { + + const l = source.length; + + for ( let i = 0; i < l; i ++ ) { + + _plane.copy( source[ i ] ).applyMatrix4( this.viewMatrix, this.viewNormalMatrix ); + + const v = destination[ offset + i ]; + const normal = _plane.normal; + + v.x = - normal.x; + v.y = - normal.y; + v.z = - normal.z; + v.w = _plane.constant; + + } + + } + + /** + * Updates the root clipping context of a scene. + * + * @param {Scene} scene - The scene. + * @param {Camera} camera - The camera that is used to render the scene. + */ + updateGlobal( scene, camera ) { + + this.shadowPass = ( scene.overrideMaterial !== null && scene.overrideMaterial.isShadowPassMaterial ); + this.viewMatrix = camera.matrixWorldInverse; + + this.viewNormalMatrix.getNormalMatrix( this.viewMatrix ); + + } + + /** + * Updates the clipping context. + * + * @param {ClippingContext} parentContext - The parent context. + * @param {ClippingGroup} clippingGroup - The clipping group this context belongs to. + */ + update( parentContext, clippingGroup ) { + + let update = false; + + if ( parentContext.version !== this.parentVersion ) { + + this.intersectionPlanes = Array.from( parentContext.intersectionPlanes ); + this.unionPlanes = Array.from( parentContext.unionPlanes ); + this.parentVersion = parentContext.version; + + } + + if ( this.clipIntersection !== clippingGroup.clipIntersection ) { + + this.clipIntersection = clippingGroup.clipIntersection; + + if ( this.clipIntersection ) { + + this.unionPlanes.length = parentContext.unionPlanes.length; + + } else { + + this.intersectionPlanes.length = parentContext.intersectionPlanes.length; + + } + + } + + const srcClippingPlanes = clippingGroup.clippingPlanes; + const l = srcClippingPlanes.length; + + let dstClippingPlanes; + let offset; + + if ( this.clipIntersection ) { + + dstClippingPlanes = this.intersectionPlanes; + offset = parentContext.intersectionPlanes.length; + + } else { + + dstClippingPlanes = this.unionPlanes; + offset = parentContext.unionPlanes.length; + + } + + if ( dstClippingPlanes.length !== offset + l ) { + + dstClippingPlanes.length = offset + l; + + for ( let i = 0; i < l; i ++ ) { + + dstClippingPlanes[ offset + i ] = new Vector4(); + + } + + update = true; + + } + + this.projectPlanes( srcClippingPlanes, dstClippingPlanes, offset ); + + if ( update ) { + + this.version ++; + this.cacheKey = `${ this.intersectionPlanes.length }:${ this.unionPlanes.length }`; + + } + + } + + /** + * Returns a clipping context for the given clipping group. + * + * @param {ClippingGroup} clippingGroup - The clipping group. + * @return {ClippingContext} The clipping context. + */ + getGroupContext( clippingGroup ) { + + if ( this.shadowPass && ! clippingGroup.clipShadows ) return this; + + let context = this.clippingGroupContexts.get( clippingGroup ); + + if ( context === undefined ) { + + context = new ClippingContext( this ); + this.clippingGroupContexts.set( clippingGroup, context ); + + } + + context.update( this, clippingGroup ); + + return context; + + } + + /** + * The count of union clipping planes. + * + * @type {number} + * @readonly + */ + get unionClippingCount() { + + return this.unionPlanes.length; + + } + +} + +/** + * This module is used to represent render bundles inside the renderer + * for further processing. + * + * @private + */ +class RenderBundle { + + /** + * Constructs a new bundle group. + * + * @param {BundleGroup} bundleGroup - The bundle group. + * @param {Camera} camera - The camera the bundle group is rendered with. + */ + constructor( bundleGroup, camera ) { + + this.bundleGroup = bundleGroup; + this.camera = camera; + + } + +} + +const _chainKeys$1 = []; + +/** + * This renderer module manages render bundles. + * + * @private + */ +class RenderBundles { + + /** + * Constructs a new render bundle management component. + */ + constructor() { + + /** + * A chain map for maintaining the render bundles. + * + * @type {ChainMap} + */ + this.bundles = new ChainMap(); + + } + + /** + * Returns a render bundle for the given bundle group and camera. + * + * @param {BundleGroup} bundleGroup - The bundle group. + * @param {Camera} camera - The camera the bundle group is rendered with. + * @return {RenderBundle} The render bundle. + */ + get( bundleGroup, camera ) { + + const bundles = this.bundles; + + _chainKeys$1[ 0 ] = bundleGroup; + _chainKeys$1[ 1 ] = camera; + + let bundle = bundles.get( _chainKeys$1 ); + + if ( bundle === undefined ) { + + bundle = new RenderBundle( bundleGroup, camera ); + bundles.set( _chainKeys$1, bundle ); + + } + + _chainKeys$1.length = 0; + + return bundle; + + } + + /** + * Frees all internal resources. + */ + dispose() { + + this.bundles = new ChainMap(); + + } + +} + +/** + * The purpose of a node library is to assign node implementations + * to existing library features. In `WebGPURenderer` lights, materials + * which are not based on `NodeMaterial` as well as tone mapping techniques + * are implemented with node-based modules. + * + * @private + */ +class NodeLibrary { + + /** + * Constructs a new node library. + */ + constructor() { + + /** + * A weak map that maps lights to light nodes. + * + * @type {WeakMap} + */ + this.lightNodes = new WeakMap(); + + /** + * A map that maps materials to node materials. + * + * @type {Map} + */ + this.materialNodes = new Map(); + + /** + * A map that maps tone mapping techniques (constants) + * to tone mapping node functions. + * + * @type {Map} + */ + this.toneMappingNodes = new Map(); + + } + + /** + * Returns a matching node material instance for the given material object. + * + * This method also assigns/copies the properties of the given material object + * to the node material. This is done to make sure the current material + * configuration carries over to the node version. + * + * @param {Material} material - A material. + * @return {NodeMaterial} The corresponding node material. + */ + fromMaterial( material ) { + + if ( material.isNodeMaterial ) return material; + + let nodeMaterial = null; + + const nodeMaterialClass = this.getMaterialNodeClass( material.type ); + + if ( nodeMaterialClass !== null ) { + + nodeMaterial = new nodeMaterialClass(); + + for ( const key in material ) { + + nodeMaterial[ key ] = material[ key ]; + + } + + } + + return nodeMaterial; + + } + + /** + * Adds a tone mapping node function for a tone mapping technique (constant). + * + * @param {Function} toneMappingNode - The tone mapping node function. + * @param {number} toneMapping - The tone mapping. + */ + addToneMapping( toneMappingNode, toneMapping ) { + + this.addType( toneMappingNode, toneMapping, this.toneMappingNodes ); + + } + + /** + * Returns a tone mapping node function for a tone mapping technique (constant). + * + * @param {number} toneMapping - The tone mapping. + * @return {?Function} The tone mapping node function. Returns `null` if no node function is found. + */ + getToneMappingFunction( toneMapping ) { + + return this.toneMappingNodes.get( toneMapping ) || null; + + } + + /** + * Returns a node material class definition for a material type. + * + * @param {string} materialType - The material type. + * @return {?NodeMaterial.constructor} The node material class definition. Returns `null` if no node material is found. + */ + getMaterialNodeClass( materialType ) { + + return this.materialNodes.get( materialType ) || null; + + } + + /** + * Adds a node material class definition for a given material type. + * + * @param {NodeMaterial.constructor} materialNodeClass - The node material class definition. + * @param {string} materialClassType - The material type. + */ + addMaterial( materialNodeClass, materialClassType ) { + + this.addType( materialNodeClass, materialClassType, this.materialNodes ); + + } + + /** + * Returns a light node class definition for a light class definition. + * + * @param {Light.constructor} light - The light class definition. + * @return {?AnalyticLightNode.constructor} The light node class definition. Returns `null` if no light node is found. + */ + getLightNodeClass( light ) { + + return this.lightNodes.get( light ) || null; + + } + + /** + * Adds a light node class definition for a given light class definition. + * + * @param {AnalyticLightNode.constructor} lightNodeClass - The light node class definition. + * @param {Light.constructor} lightClass - The light class definition. + */ + addLight( lightNodeClass, lightClass ) { + + this.addClass( lightNodeClass, lightClass, this.lightNodes ); + + } + + /** + * Adds a node class definition for the given type to the provided type library. + * + * @param {any} nodeClass - The node class definition. + * @param {number|string} type - The object type. + * @param {Map} library - The type library. + */ + addType( nodeClass, type, library ) { + + if ( library.has( type ) ) { + + console.warn( `Redefinition of node ${ type }` ); + return; + + } + + if ( typeof nodeClass !== 'function' ) throw new Error( `Node class ${ nodeClass.name } is not a class.` ); + if ( typeof type === 'function' || typeof type === 'object' ) throw new Error( `Base class ${ type } is not a class.` ); + + library.set( type, nodeClass ); + + } + + /** + * Adds a node class definition for the given class definition to the provided type library. + * + * @param {any} nodeClass - The node class definition. + * @param {any} baseClass - The class definition. + * @param {WeakMap} library - The type library. + */ + addClass( nodeClass, baseClass, library ) { + + if ( library.has( baseClass ) ) { + + console.warn( `Redefinition of node ${ baseClass.name }` ); + return; + + } + + if ( typeof nodeClass !== 'function' ) throw new Error( `Node class ${ nodeClass.name } is not a class.` ); + if ( typeof baseClass !== 'function' ) throw new Error( `Base class ${ baseClass.name } is not a class.` ); + + library.set( baseClass, nodeClass ); + + } + +} + +const _defaultLights = /*@__PURE__*/ new LightsNode(); +const _chainKeys = []; + +/** + * This renderer module manages the lights nodes which are unique + * per scene and camera combination. + * + * The lights node itself is later configured in the render list + * with the actual lights from the scene. + * + * @private + * @augments ChainMap + */ +class Lighting extends ChainMap { + + /** + * Constructs a lighting management component. + */ + constructor() { + + super(); + + } + + /** + * Creates a new lights node for the given array of lights. + * + * @param {Array} lights - The render object. + * @return {LightsNode} The lights node. + */ + createNode( lights = [] ) { + + return new LightsNode().setLights( lights ); + + } + + /** + * Returns a lights node for the given scene and camera. + * + * @param {Scene} scene - The scene. + * @param {Camera} camera - The camera. + * @return {LightsNode} The lights node. + */ + getNode( scene, camera ) { + + // ignore post-processing + + if ( scene.isQuadMesh ) return _defaultLights; + + _chainKeys[ 0 ] = scene; + _chainKeys[ 1 ] = camera; + + let node = this.get( _chainKeys ); + + if ( node === undefined ) { + + node = this.createNode(); + this.set( _chainKeys, node ); + + } + + _chainKeys.length = 0; + + return node; + + } + +} + +/** + * A special type of render target that is used when rendering + * with the WebXR Device API. + * + * @private + * @augments RenderTarget + */ +class XRRenderTarget extends RenderTarget { + + /** + * Constructs a new XR render target. + * + * @param {number} [width=1] - The width of the render target. + * @param {number} [height=1] - The height of the render target. + * @param {Object} [options={}] - The configuration options. + */ + constructor( width = 1, height = 1, options = {} ) { + + super( width, height, options ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isXRRenderTarget = true; + + /** + * Whether the attachments of the render target + * are defined by external textures. This flag is + * set to `true` when using the WebXR Layers API. + * + * @private + * @type {boolean} + * @default false + */ + this._hasExternalTextures = false; + + /** + * Whether a depth buffer should automatically be allocated + * for this XR render target or not. + * + * Allocating a depth buffer is the default behavior of XR render + * targets. However, when using the WebXR Layers API, this flag + * must be set to `false` when the `ignoreDepthValues` property of + * the projection layers evaluates to `false`. + * + * Reference: {@link https://www.w3.org/TR/webxrlayers-1/#dom-xrprojectionlayer-ignoredepthvalues}. + * + * @private + * @type {boolean} + * @default true + */ + this._autoAllocateDepthBuffer = true; + + /** + * Whether this render target is associated with a XRWebGLLayer. + * + * A XRWebGLLayer points to an opaque framebuffer. Basically, + * this means that you don't have access to its bound color, + * stencil and depth buffers. We need to handle this framebuffer + * differently since its textures are always bound. + * + * @private + * @type {boolean} + * @default false + * */ + this._isOpaqueFramebuffer = false; + + } + + copy( source ) { + + super.copy( source ); + + this._hasExternalTextures = source._hasExternalTextures; + this._autoAllocateDepthBuffer = source._autoAllocateDepthBuffer; + this._isOpaqueFramebuffer = source._isOpaqueFramebuffer; + + return this; + + } + + +} + +const _cameraLPos = /*@__PURE__*/ new Vector3(); +const _cameraRPos = /*@__PURE__*/ new Vector3(); + +/** + * The XR manager is built on top of the WebXR Device API to + * manage XR sessions with `WebGPURenderer`. + * + * XR is currently only supported with a WebGL 2 backend. + * + * @augments EventDispatcher + */ +class XRManager extends EventDispatcher { + + /** + * Constructs a new XR manager. + * + * @param {Renderer} renderer - The renderer. + * @param {boolean} [multiview=false] - Enables multiview if the device supports it. + */ + constructor( renderer, multiview = false ) { + + super(); + + /** + * This flag globally enables XR rendering. + * + * @type {boolean} + * @default false + */ + this.enabled = false; + + /** + * Whether the XR device is currently presenting or not. + * + * @type {boolean} + * @default false + * @readonly + */ + this.isPresenting = false; + + /** + * Whether the XR camera should automatically be updated or not. + * + * @type {boolean} + * @default true + */ + this.cameraAutoUpdate = true; + + /** + * The renderer. + * + * @private + * @type {Renderer} + */ + this._renderer = renderer; + + // camera + + /** + * Represents the camera for the left eye. + * + * @private + * @type {PerspectiveCamera} + */ + this._cameraL = new PerspectiveCamera(); + this._cameraL.viewport = new Vector4(); + + /** + * Represents the camera for the right eye. + * + * @private + * @type {PerspectiveCamera} + */ + this._cameraR = new PerspectiveCamera(); + this._cameraR.viewport = new Vector4(); + + /** + * A list of cameras used for rendering the XR views. + * + * @private + * @type {Array} + */ + this._cameras = [ this._cameraL, this._cameraR ]; + + /** + * The main XR camera. + * + * @private + * @type {ArrayCamera} + */ + this._cameraXR = new ArrayCamera(); + + /** + * The current near value of the XR camera. + * + * @private + * @type {?number} + * @default null + */ + this._currentDepthNear = null; + + /** + * The current far value of the XR camera. + * + * @private + * @type {?number} + * @default null + */ + this._currentDepthFar = null; + + /** + * A list of WebXR controllers requested by the application. + * + * @private + * @type {Array} + */ + this._controllers = []; + + /** + * A list of XR input source. Each input source belongs to + * an instance of WebXRController. + * + * @private + * @type {Array} + */ + this._controllerInputSources = []; + + /** + * The XR render target that represents the rendering destination + * during an active XR session. + * + * @private + * @type {?RenderTarget} + * @default null + */ + this._xrRenderTarget = null; + + /** + * An array holding all the non-projection layers + * + * @private + * @type {Array} + * @default [] + */ + this._layers = []; + + /** + * Whether the device has support for all layer types. + * + * @type {boolean} + * @default false + */ + this._supportsLayers = false; + + this._frameBufferTargets = null; + + /** + * Helper function to create native WebXR Layer. + * + * @private + * @type {Function} + */ + this._createXRLayer = createXRLayer.bind( this ); + + /** + * The current WebGL context. + * + * @private + * @type {?WebGL2RenderingContext} + * @default null + */ + this._gl = null; + + /** + * The current animation context. + * + * @private + * @type {?Window} + * @default null + */ + this._currentAnimationContext = null; + + /** + * The current animation loop. + * + * @private + * @type {?Function} + * @default null + */ + this._currentAnimationLoop = null; + + /** + * The current pixel ratio. + * + * @private + * @type {?number} + * @default null + */ + this._currentPixelRatio = null; + + /** + * The current size of the renderer's canvas + * in logical pixel unit. + * + * @private + * @type {Vector2} + */ + this._currentSize = new Vector2(); + + /** + * The default event listener for handling events inside a XR session. + * + * @private + * @type {Function} + */ + this._onSessionEvent = onSessionEvent.bind( this ); + + /** + * The event listener for handling the end of a XR session. + * + * @private + * @type {Function} + */ + this._onSessionEnd = onSessionEnd.bind( this ); + + /** + * The event listener for handling the `inputsourceschange` event. + * + * @private + * @type {Function} + */ + this._onInputSourcesChange = onInputSourcesChange.bind( this ); + + /** + * The animation loop which is used as a replacement for the default + * animation loop of the application. It is only used when a XR session + * is active. + * + * @private + * @type {Function} + */ + this._onAnimationFrame = onAnimationFrame.bind( this ); + + /** + * The current XR reference space. + * + * @private + * @type {?XRReferenceSpace} + * @default null + */ + this._referenceSpace = null; + + /** + * The current XR reference space type. + * + * @private + * @type {XRReferenceSpaceType} + * @default 'local-floor' + */ + this._referenceSpaceType = 'local-floor'; + + /** + * A custom reference space defined by the application. + * + * @private + * @type {?XRReferenceSpace} + * @default null + */ + this._customReferenceSpace = null; + + /** + * The framebuffer scale factor. + * + * @private + * @type {number} + * @default 1 + */ + this._framebufferScaleFactor = 1; + + /** + * The foveation factor. + * + * @private + * @type {number} + * @default 1 + */ + this._foveation = 1.0; + + /** + * A reference to the current XR session. + * + * @private + * @type {?XRSession} + * @default null + */ + this._session = null; + + /** + * A reference to the current XR base layer. + * + * @private + * @type {?XRWebGLLayer} + * @default null + */ + this._glBaseLayer = null; + + /** + * A reference to the current XR binding. + * + * @private + * @type {?XRWebGLBinding} + * @default null + */ + this._glBinding = null; + + /** + * A reference to the current XR projection layer. + * + * @private + * @type {?XRProjectionLayer} + * @default null + */ + this._glProjLayer = null; + + /** + * A reference to the current XR frame. + * + * @private + * @type {?XRFrame} + * @default null + */ + this._xrFrame = null; + + /** + * Whether to use the WebXR Layers API or not. + * + * @private + * @type {boolean} + * @readonly + */ + this._useLayers = ( typeof XRWebGLBinding !== 'undefined' && 'createProjectionLayer' in XRWebGLBinding.prototype ); // eslint-disable-line compat/compat + + /** + * Whether the usage of multiview has been requested by the application or not. + * + * @private + * @type {boolean} + * @default false + * @readonly + */ + this._useMultiviewIfPossible = multiview; + + /** + * Whether the usage of multiview is actually enabled. This flag only evaluates to `true` + * if multiview has been requested by the application and the `OVR_multiview2` is available. + * + * @private + * @type {boolean} + * @readonly + */ + this._useMultiview = false; + + } + + /** + * Returns an instance of `THREE.Group` that represents the transformation + * of a XR controller in target ray space. The requested controller is defined + * by the given index. + * + * @param {number} index - The index of the XR controller. + * @return {Group} A group that represents the controller's transformation. + */ + getController( index ) { + + const controller = this._getController( index ); + + return controller.getTargetRaySpace(); + + } + + /** + * Returns an instance of `THREE.Group` that represents the transformation + * of a XR controller in grip space. The requested controller is defined + * by the given index. + * + * @param {number} index - The index of the XR controller. + * @return {Group} A group that represents the controller's transformation. + */ + getControllerGrip( index ) { + + const controller = this._getController( index ); + + return controller.getGripSpace(); + + } + + /** + * Returns an instance of `THREE.Group` that represents the transformation + * of a XR controller in hand space. The requested controller is defined + * by the given index. + * + * @param {number} index - The index of the XR controller. + * @return {Group} A group that represents the controller's transformation. + */ + getHand( index ) { + + const controller = this._getController( index ); + + return controller.getHandSpace(); + + } + + /** + * Returns the foveation value. + * + * @return {number|undefined} The foveation value. Returns `undefined` if no base or projection layer is defined. + */ + getFoveation() { + + if ( this._glProjLayer === null && this._glBaseLayer === null ) { + + return undefined; + + } + + return this._foveation; + + } + + /** + * Sets the foveation value. + * + * @param {number} foveation - A number in the range `[0,1]` where `0` means no foveation (full resolution) + * and `1` means maximum foveation (the edges render at lower resolution). + */ + setFoveation( foveation ) { + + this._foveation = foveation; + + if ( this._glProjLayer !== null ) { + + this._glProjLayer.fixedFoveation = foveation; + + } + + if ( this._glBaseLayer !== null && this._glBaseLayer.fixedFoveation !== undefined ) { + + this._glBaseLayer.fixedFoveation = foveation; + + } + + } + + /** + * Returns the framebuffer scale factor. + * + * @return {number} The framebuffer scale factor. + */ + getFramebufferScaleFactor() { + + return this._framebufferScaleFactor; + + } + + /** + * Sets the framebuffer scale factor. + * + * This method can not be used during a XR session. + * + * @param {number} factor - The framebuffer scale factor. + */ + setFramebufferScaleFactor( factor ) { + + this._framebufferScaleFactor = factor; + + if ( this.isPresenting === true ) { + + console.warn( 'THREE.XRManager: Cannot change framebuffer scale while presenting.' ); + + } + + } + + /** + * Returns the reference space type. + * + * @return {XRReferenceSpaceType} The reference space type. + */ + getReferenceSpaceType() { + + return this._referenceSpaceType; + + } + + /** + * Sets the reference space type. + * + * This method can not be used during a XR session. + * + * @param {XRReferenceSpaceType} type - The reference space type. + */ + setReferenceSpaceType( type ) { + + this._referenceSpaceType = type; + + if ( this.isPresenting === true ) { + + console.warn( 'THREE.XRManager: Cannot change reference space type while presenting.' ); + + } + + } + + /** + * Returns the XR reference space. + * + * @return {XRReferenceSpace} The XR reference space. + */ + getReferenceSpace() { + + return this._customReferenceSpace || this._referenceSpace; + + } + + /** + * Sets a custom XR reference space. + * + * @param {XRReferenceSpace} space - The XR reference space. + */ + setReferenceSpace( space ) { + + this._customReferenceSpace = space; + + } + + /** + * Returns the XR camera. + * + * @return {ArrayCamera} The XR camera. + */ + getCamera() { + + return this._cameraXR; + + } + + /** + * Returns the environment blend mode from the current XR session. + * + * @return {'opaque'|'additive'|'alpha-blend'|undefined} The environment blend mode. Returns `undefined` when used outside of a XR session. + */ + getEnvironmentBlendMode() { + + if ( this._session !== null ) { + + return this._session.environmentBlendMode; + + } + + } + + /** + * Returns the current XR frame. + * + * @return {?XRFrame} The XR frame. Returns `null` when used outside a XR session. + */ + getFrame() { + + return this._xrFrame; + + } + + /** + * Returns `true` if the engine renders to a multiview target. + * + * @return {boolean} Whether the engine renders to a multiview render target or not. + */ + useMultiview() { + + return this._useMultiview; + + } + + /** + * This method can be used in XR applications to create a quadratic layer that presents a separate + * rendered scene. + * + * @param {number} width - The width of the layer plane in world units. + * @param {number} height - The height of the layer plane in world units. + * @param {Vector3} translation - The position/translation of the layer plane in world units. + * @param {Quaternion} quaternion - The orientation of the layer plane expressed as a quaternion. + * @param {number} pixelwidth - The width of the layer's render target in pixels. + * @param {number} pixelheight - The height of the layer's render target in pixels. + * @param {Function} rendercall - A callback function that renders the layer. Similar to code in + * the default animation loop, this method can be used to update/transform 3D object in the layer's scene. + * @param {Object} [attributes={}] - Allows to configure the layer's render target. + * @return {Mesh} A mesh representing the quadratic XR layer. This mesh should be added to the XR scene. + */ + createQuadLayer( width, height, translation, quaternion, pixelwidth, pixelheight, rendercall, attributes = {} ) { + + const geometry = new PlaneGeometry( width, height ); + const renderTarget = new XRRenderTarget( + pixelwidth, + pixelheight, + { + format: RGBAFormat, + type: UnsignedByteType, + depthTexture: new DepthTexture( + pixelwidth, + pixelheight, + attributes.stencil ? UnsignedInt248Type : UnsignedIntType, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + attributes.stencil ? DepthStencilFormat : DepthFormat + ), + stencilBuffer: attributes.stencil, + resolveDepthBuffer: false, + resolveStencilBuffer: false + } ); + + renderTarget._autoAllocateDepthBuffer = true; + + const material = new MeshBasicMaterial( { color: 0xffffff, side: FrontSide } ); + material.map = renderTarget.texture; + material.map.offset.y = 1; + material.map.repeat.y = -1; + const plane = new Mesh( geometry, material ); + plane.position.copy( translation ); + plane.quaternion.copy( quaternion ); + + const layer = { + type: 'quad', + width: width, + height: height, + translation: translation, + quaternion: quaternion, + pixelwidth: pixelwidth, + pixelheight: pixelheight, + plane: plane, + material: material, + rendercall: rendercall, + renderTarget: renderTarget }; + + this._layers.push( layer ); + + if ( this._session !== null ) { + + layer.plane.material = new MeshBasicMaterial( { color: 0xffffff, side: FrontSide } ); + layer.plane.material.blending = CustomBlending; + layer.plane.material.blendEquation = AddEquation; + layer.plane.material.blendSrc = ZeroFactor; + layer.plane.material.blendDst = ZeroFactor; + + layer.xrlayer = this._createXRLayer( layer ); + + const xrlayers = this._session.renderState.layers; + xrlayers.unshift( layer.xrlayer ); + this._session.updateRenderState( { layers: xrlayers } ); + + } else { + + renderTarget.isXRRenderTarget = false; + + } + + return plane; + + } + + /** + * This method can be used in XR applications to create a cylindrical layer that presents a separate + * rendered scene. + * + * @param {number} radius - The radius of the cylinder in world units. + * @param {number} centralAngle - The central angle of the cylinder in radians. + * @param {number} aspectratio - The aspect ratio. + * @param {Vector3} translation - The position/translation of the layer plane in world units. + * @param {Quaternion} quaternion - The orientation of the layer plane expressed as a quaternion. + * @param {number} pixelwidth - The width of the layer's render target in pixels. + * @param {number} pixelheight - The height of the layer's render target in pixels. + * @param {Function} rendercall - A callback function that renders the layer. Similar to code in + * the default animation loop, this method can be used to update/transform 3D object in the layer's scene. + * @param {Object} [attributes={}] - Allows to configure the layer's render target. + * @return {Mesh} A mesh representing the cylindrical XR layer. This mesh should be added to the XR scene. + */ + createCylinderLayer( radius, centralAngle, aspectratio, translation, quaternion, pixelwidth, pixelheight, rendercall, attributes = {} ) { + + const geometry = new CylinderGeometry( radius, radius, radius * centralAngle / aspectratio, 64, 64, true, Math.PI - centralAngle / 2, centralAngle ); + const renderTarget = new XRRenderTarget( + pixelwidth, + pixelheight, + { + format: RGBAFormat, + type: UnsignedByteType, + depthTexture: new DepthTexture( + pixelwidth, + pixelheight, + attributes.stencil ? UnsignedInt248Type : UnsignedIntType, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + attributes.stencil ? DepthStencilFormat : DepthFormat + ), + stencilBuffer: attributes.stencil, + resolveDepthBuffer: false, + resolveStencilBuffer: false + } ); + + renderTarget._autoAllocateDepthBuffer = true; + + const material = new MeshBasicMaterial( { color: 0xffffff, side: BackSide } ); + material.map = renderTarget.texture; + material.map.offset.y = 1; + material.map.repeat.y = -1; + const plane = new Mesh( geometry, material ); + plane.position.copy( translation ); + plane.quaternion.copy( quaternion ); + + const layer = { + type: 'cylinder', + radius: radius, + centralAngle: centralAngle, + aspectratio: aspectratio, + translation: translation, + quaternion: quaternion, + pixelwidth: pixelwidth, + pixelheight: pixelheight, + plane: plane, + material: material, + rendercall: rendercall, + renderTarget: renderTarget }; + + this._layers.push( layer ); + + if ( this._session !== null ) { + + layer.plane.material = new MeshBasicMaterial( { color: 0xffffff, side: BackSide } ); + layer.plane.material.blending = CustomBlending; + layer.plane.material.blendEquation = AddEquation; + layer.plane.material.blendSrc = ZeroFactor; + layer.plane.material.blendDst = ZeroFactor; + + layer.xrlayer = this._createXRLayer( layer ); + + const xrlayers = this._session.renderState.layers; + xrlayers.unshift( layer.xrlayer ); + this._session.updateRenderState( { layers: xrlayers } ); + + } else { + + renderTarget.isXRRenderTarget = false; + + } + + return plane; + + } + + /** + * Renders the XR layers that have been previously added to the scene. + * + * This method is usually called in your animation loop before rendering + * the actual scene via `renderer.render( scene, camera );`. + */ + renderLayers( ) { + + const translationObject = new Vector3(); + const quaternionObject = new Quaternion(); + const renderer = this._renderer; + + const wasPresenting = this.isPresenting; + const rendererOutputTarget = renderer.getOutputRenderTarget(); + const rendererFramebufferTarget = renderer._frameBufferTarget; + this.isPresenting = false; + + const rendererSize = new Vector2(); + renderer.getSize( rendererSize ); + const rendererQuad = renderer._quad; + + for ( const layer of this._layers ) { + + layer.renderTarget.isXRRenderTarget = this._session !== null; + layer.renderTarget._hasExternalTextures = layer.renderTarget.isXRRenderTarget; + + if ( layer.renderTarget.isXRRenderTarget && this._supportsLayers ) { + + layer.xrlayer.transform = new XRRigidTransform( layer.plane.getWorldPosition( translationObject ), layer.plane.getWorldQuaternion( quaternionObject ) ); + + const glSubImage = this._glBinding.getSubImage( layer.xrlayer, this._xrFrame ); + renderer.backend.setXRRenderTargetTextures( + layer.renderTarget, + glSubImage.colorTexture, + undefined ); + + renderer._setXRLayerSize( layer.renderTarget.width, layer.renderTarget.height ); + renderer.setOutputRenderTarget( layer.renderTarget ); + renderer.setRenderTarget( null ); + renderer._frameBufferTarget = null; + + this._frameBufferTargets || ( this._frameBufferTargets = new WeakMap() ); + const { frameBufferTarget, quad } = this._frameBufferTargets.get( layer.renderTarget ) || { frameBufferTarget: null, quad: null }; + if ( ! frameBufferTarget ) { + + renderer._quad = new QuadMesh( new NodeMaterial() ); + this._frameBufferTargets.set( layer.renderTarget, { frameBufferTarget: renderer._getFrameBufferTarget(), quad: renderer._quad } ); + + } else { + + renderer._frameBufferTarget = frameBufferTarget; + renderer._quad = quad; + + } + + layer.rendercall(); + + renderer._frameBufferTarget = null; + + } else { + + renderer.setRenderTarget( layer.renderTarget ); + layer.rendercall(); + + } + + } + + renderer.setRenderTarget( null ); + renderer.setOutputRenderTarget( rendererOutputTarget ); + renderer._frameBufferTarget = rendererFramebufferTarget; + renderer._setXRLayerSize( rendererSize.x, rendererSize.y ); + renderer._quad = rendererQuad; + this.isPresenting = wasPresenting; + + } + + + /** + * Returns the current XR session. + * + * @return {?XRSession} The XR session. Returns `null` when used outside a XR session. + */ + getSession() { + + return this._session; + + } + + /** + * After a XR session has been requested usually with one of the `*Button` modules, it + * is injected into the renderer with this method. This method triggers the start of + * the actual XR rendering. + * + * @async + * @param {XRSession} session - The XR session to set. + * @return {Promise} A Promise that resolves when the session has been set. + */ + async setSession( session ) { + + const renderer = this._renderer; + const backend = renderer.backend; + + this._gl = renderer.getContext(); + const gl = this._gl; + const attributes = gl.getContextAttributes(); + + this._session = session; + + if ( session !== null ) { + + if ( backend.isWebGPUBackend === true ) throw new Error( 'THREE.XRManager: XR is currently not supported with a WebGPU backend. Use WebGL by passing "{ forceWebGL: true }" to the constructor of the renderer.' ); + + session.addEventListener( 'select', this._onSessionEvent ); + session.addEventListener( 'selectstart', this._onSessionEvent ); + session.addEventListener( 'selectend', this._onSessionEvent ); + session.addEventListener( 'squeeze', this._onSessionEvent ); + session.addEventListener( 'squeezestart', this._onSessionEvent ); + session.addEventListener( 'squeezeend', this._onSessionEvent ); + session.addEventListener( 'end', this._onSessionEnd ); + session.addEventListener( 'inputsourceschange', this._onInputSourcesChange ); + + await backend.makeXRCompatible(); + + this._currentPixelRatio = renderer.getPixelRatio(); + renderer.getSize( this._currentSize ); + + this._currentAnimationContext = renderer._animation.getContext(); + this._currentAnimationLoop = renderer._animation.getAnimationLoop(); + renderer._animation.stop(); + + // + + if ( this._useLayers === true ) { + + // default path using XRWebGLBinding/XRProjectionLayer + + let depthFormat = null; + let depthType = null; + let glDepthFormat = null; + + if ( renderer.depth ) { + + glDepthFormat = renderer.stencil ? gl.DEPTH24_STENCIL8 : gl.DEPTH_COMPONENT24; + depthFormat = renderer.stencil ? DepthStencilFormat : DepthFormat; + depthType = renderer.stencil ? UnsignedInt248Type : UnsignedIntType; + + } + + const projectionlayerInit = { + colorFormat: gl.RGBA8, + depthFormat: glDepthFormat, + scaleFactor: this._framebufferScaleFactor, + clearOnAccess: false + }; + + if ( this._useMultiviewIfPossible && renderer.hasFeature( 'OVR_multiview2' ) ) { + + projectionlayerInit.textureType = 'texture-array'; + this._useMultiview = true; + + } + + const glBinding = new XRWebGLBinding( session, gl ); + const glProjLayer = glBinding.createProjectionLayer( projectionlayerInit ); + const layersArray = [ glProjLayer ]; + + this._glBinding = glBinding; + this._glProjLayer = glProjLayer; + + renderer.setPixelRatio( 1 ); + renderer._setXRLayerSize( glProjLayer.textureWidth, glProjLayer.textureHeight ); + + const depth = this._useMultiview ? 2 : 1; + const depthTexture = new DepthTexture( glProjLayer.textureWidth, glProjLayer.textureHeight, depthType, undefined, undefined, undefined, undefined, undefined, undefined, depthFormat, depth ); + + this._xrRenderTarget = new XRRenderTarget( + glProjLayer.textureWidth, + glProjLayer.textureHeight, + { + format: RGBAFormat, + type: UnsignedByteType, + colorSpace: renderer.outputColorSpace, + depthTexture: depthTexture, + stencilBuffer: renderer.stencil, + samples: attributes.antialias ? 4 : 0, + resolveDepthBuffer: ( glProjLayer.ignoreDepthValues === false ), + resolveStencilBuffer: ( glProjLayer.ignoreDepthValues === false ), + depth: this._useMultiview ? 2 : 1, + multiview: this._useMultiview + } ); + + this._xrRenderTarget._hasExternalTextures = true; + this._xrRenderTarget.depth = this._useMultiview ? 2 : 1; + + this._supportsLayers = session.enabledFeatures.includes( 'layers' ); + + this._referenceSpace = await session.requestReferenceSpace( this.getReferenceSpaceType() ); + + if ( this._supportsLayers ) { + + // switch layers to native + for ( const layer of this._layers ) { + + // change material so it "punches" out a hole to show the XR Layer. + layer.plane.material = new MeshBasicMaterial( { color: 0xffffff, side: layer.type === 'cylinder' ? BackSide : FrontSide } ); + layer.plane.material.blending = CustomBlending; + layer.plane.material.blendEquation = AddEquation; + layer.plane.material.blendSrc = ZeroFactor; + layer.plane.material.blendDst = ZeroFactor; + + layer.xrlayer = this._createXRLayer( layer ); + + layersArray.unshift( layer.xrlayer ); + + } + + } + + session.updateRenderState( { layers: layersArray } ); + + } else { + + // fallback to XRWebGLLayer + + const layerInit = { + antialias: renderer.samples > 0, + alpha: true, + depth: renderer.depth, + stencil: renderer.stencil, + framebufferScaleFactor: this.getFramebufferScaleFactor() + }; + + const glBaseLayer = new XRWebGLLayer( session, gl, layerInit ); + this._glBaseLayer = glBaseLayer; + + session.updateRenderState( { baseLayer: glBaseLayer } ); + + renderer.setPixelRatio( 1 ); + renderer._setXRLayerSize( glBaseLayer.framebufferWidth, glBaseLayer.framebufferHeight ); + + this._xrRenderTarget = new XRRenderTarget( + glBaseLayer.framebufferWidth, + glBaseLayer.framebufferHeight, + { + format: RGBAFormat, + type: UnsignedByteType, + colorSpace: renderer.outputColorSpace, + stencilBuffer: renderer.stencil, + resolveDepthBuffer: ( glBaseLayer.ignoreDepthValues === false ), + resolveStencilBuffer: ( glBaseLayer.ignoreDepthValues === false ), + } + ); + + this._xrRenderTarget._isOpaqueFramebuffer = true; + this._referenceSpace = await session.requestReferenceSpace( this.getReferenceSpaceType() ); + + } + + // + + this.setFoveation( this.getFoveation() ); + + renderer._animation.setAnimationLoop( this._onAnimationFrame ); + renderer._animation.setContext( session ); + renderer._animation.start(); + + this.isPresenting = true; + + this.dispatchEvent( { type: 'sessionstart' } ); + + } + + } + + /** + * This method is called by the renderer per frame and updates the XR camera + * and it sub cameras based on the given camera. The given camera is the "user" + * camera created on application level and used for non-XR rendering. + * + * @param {PerspectiveCamera} camera - The camera. + */ + updateCamera( camera ) { + + const session = this._session; + + if ( session === null ) return; + + const depthNear = camera.near; + const depthFar = camera.far; + + const cameraXR = this._cameraXR; + const cameraL = this._cameraL; + const cameraR = this._cameraR; + + cameraXR.near = cameraR.near = cameraL.near = depthNear; + cameraXR.far = cameraR.far = cameraL.far = depthFar; + cameraXR.isMultiViewCamera = this._useMultiview; + + if ( this._currentDepthNear !== cameraXR.near || this._currentDepthFar !== cameraXR.far ) { + + // Note that the new renderState won't apply until the next frame. See #18320 + + session.updateRenderState( { + depthNear: cameraXR.near, + depthFar: cameraXR.far + } ); + + this._currentDepthNear = cameraXR.near; + this._currentDepthFar = cameraXR.far; + + } + + cameraL.layers.mask = camera.layers.mask | 0b010; + cameraR.layers.mask = camera.layers.mask | 0b100; + cameraXR.layers.mask = cameraL.layers.mask | cameraR.layers.mask; + + const parent = camera.parent; + const cameras = cameraXR.cameras; + + updateCamera( cameraXR, parent ); + + for ( let i = 0; i < cameras.length; i ++ ) { + + updateCamera( cameras[ i ], parent ); + + } + + // update projection matrix for proper view frustum culling + + if ( cameras.length === 2 ) { + + setProjectionFromUnion( cameraXR, cameraL, cameraR ); + + } else { + + // assume single camera setup (AR) + + cameraXR.projectionMatrix.copy( cameraL.projectionMatrix ); + + } + + // update user camera and its children + + updateUserCamera( camera, cameraXR, parent ); + + + } + + /** + * Returns a WebXR controller for the given controller index. + * + * @private + * @param {number} index - The controller index. + * @return {WebXRController} The XR controller. + */ + _getController( index ) { + + let controller = this._controllers[ index ]; + + if ( controller === undefined ) { + + controller = new WebXRController(); + this._controllers[ index ] = controller; + + } + + return controller; + + } + +} + +/** + * Assumes 2 cameras that are parallel and share an X-axis, and that + * the cameras' projection and world matrices have already been set. + * And that near and far planes are identical for both cameras. + * Visualization of this technique: https://computergraphics.stackexchange.com/a/4765 + * + * @param {ArrayCamera} camera - The camera to update. + * @param {PerspectiveCamera} cameraL - The left camera. + * @param {PerspectiveCamera} cameraR - The right camera. + */ +function setProjectionFromUnion( camera, cameraL, cameraR ) { + + _cameraLPos.setFromMatrixPosition( cameraL.matrixWorld ); + _cameraRPos.setFromMatrixPosition( cameraR.matrixWorld ); + + const ipd = _cameraLPos.distanceTo( _cameraRPos ); + + const projL = cameraL.projectionMatrix.elements; + const projR = cameraR.projectionMatrix.elements; + + // VR systems will have identical far and near planes, and + // most likely identical top and bottom frustum extents. + // Use the left camera for these values. + const near = projL[ 14 ] / ( projL[ 10 ] - 1 ); + const far = projL[ 14 ] / ( projL[ 10 ] + 1 ); + const topFov = ( projL[ 9 ] + 1 ) / projL[ 5 ]; + const bottomFov = ( projL[ 9 ] - 1 ) / projL[ 5 ]; + + const leftFov = ( projL[ 8 ] - 1 ) / projL[ 0 ]; + const rightFov = ( projR[ 8 ] + 1 ) / projR[ 0 ]; + const left = near * leftFov; + const right = near * rightFov; + + // Calculate the new camera's position offset from the + // left camera. xOffset should be roughly half `ipd`. + const zOffset = ipd / ( - leftFov + rightFov ); + const xOffset = zOffset * - leftFov; + + // TODO: Better way to apply this offset? + cameraL.matrixWorld.decompose( camera.position, camera.quaternion, camera.scale ); + camera.translateX( xOffset ); + camera.translateZ( zOffset ); + camera.matrixWorld.compose( camera.position, camera.quaternion, camera.scale ); + camera.matrixWorldInverse.copy( camera.matrixWorld ).invert(); + + // Check if the projection uses an infinite far plane. + if ( projL[ 10 ] === -1 ) { + + // Use the projection matrix from the left eye. + // The camera offset is sufficient to include the view volumes + // of both eyes (assuming symmetric projections). + camera.projectionMatrix.copy( cameraL.projectionMatrix ); + camera.projectionMatrixInverse.copy( cameraL.projectionMatrixInverse ); + + } else { + + // Find the union of the frustum values of the cameras and scale + // the values so that the near plane's position does not change in world space, + // although must now be relative to the new union camera. + const near2 = near + zOffset; + const far2 = far + zOffset; + const left2 = left - xOffset; + const right2 = right + ( ipd - xOffset ); + const top2 = topFov * far / far2 * near2; + const bottom2 = bottomFov * far / far2 * near2; + + camera.projectionMatrix.makePerspective( left2, right2, top2, bottom2, near2, far2 ); + camera.projectionMatrixInverse.copy( camera.projectionMatrix ).invert(); + + } + +} + +/** + * Updates the world matrices for the given camera based on the parent 3D object. + * + * @inner + * @param {Camera} camera - The camera to update. + * @param {Object3D} parent - The parent 3D object. + */ +function updateCamera( camera, parent ) { + + if ( parent === null ) { + + camera.matrixWorld.copy( camera.matrix ); + + } else { + + camera.matrixWorld.multiplyMatrices( parent.matrixWorld, camera.matrix ); + + } + + camera.matrixWorldInverse.copy( camera.matrixWorld ).invert(); + +} + +/** + * Updates the given camera with the transformation of the XR camera and parent object. + * + * @inner + * @param {Camera} camera - The camera to update. + * @param {ArrayCamera} cameraXR - The XR camera. + * @param {Object3D} parent - The parent 3D object. + */ +function updateUserCamera( camera, cameraXR, parent ) { + + if ( parent === null ) { + + camera.matrix.copy( cameraXR.matrixWorld ); + + } else { + + camera.matrix.copy( parent.matrixWorld ); + camera.matrix.invert(); + camera.matrix.multiply( cameraXR.matrixWorld ); + + } + + camera.matrix.decompose( camera.position, camera.quaternion, camera.scale ); + camera.updateMatrixWorld( true ); + + camera.projectionMatrix.copy( cameraXR.projectionMatrix ); + camera.projectionMatrixInverse.copy( cameraXR.projectionMatrixInverse ); + + if ( camera.isPerspectiveCamera ) { + + camera.fov = RAD2DEG * 2 * Math.atan( 1 / camera.projectionMatrix.elements[ 5 ] ); + camera.zoom = 1; + + } + +} + +function onSessionEvent( event ) { + + const controllerIndex = this._controllerInputSources.indexOf( event.inputSource ); + + if ( controllerIndex === -1 ) { + + return; + + } + + const controller = this._controllers[ controllerIndex ]; + + if ( controller !== undefined ) { + + const referenceSpace = this.getReferenceSpace(); + + controller.update( event.inputSource, event.frame, referenceSpace ); + controller.dispatchEvent( { type: event.type, data: event.inputSource } ); + + } + +} + +function onSessionEnd() { + + const session = this._session; + const renderer = this._renderer; + + session.removeEventListener( 'select', this._onSessionEvent ); + session.removeEventListener( 'selectstart', this._onSessionEvent ); + session.removeEventListener( 'selectend', this._onSessionEvent ); + session.removeEventListener( 'squeeze', this._onSessionEvent ); + session.removeEventListener( 'squeezestart', this._onSessionEvent ); + session.removeEventListener( 'squeezeend', this._onSessionEvent ); + session.removeEventListener( 'end', this._onSessionEnd ); + session.removeEventListener( 'inputsourceschange', this._onInputSourcesChange ); + + for ( let i = 0; i < this._controllers.length; i ++ ) { + + const inputSource = this._controllerInputSources[ i ]; + + if ( inputSource === null ) continue; + + this._controllerInputSources[ i ] = null; + + this._controllers[ i ].disconnect( inputSource ); + + } + + this._currentDepthNear = null; + this._currentDepthFar = null; + + // restore framebuffer/rendering state + + renderer._resetXRState(); + + this._session = null; + this._xrRenderTarget = null; + + // switch layers back to emulated + if ( this._supportsLayers === true ) { + + for ( const layer of this._layers ) { + + // Recreate layer render target to reset state + layer.renderTarget = new XRRenderTarget( + layer.pixelwidth, + layer.pixelheight, + { + format: RGBAFormat, + type: UnsignedByteType, + depthTexture: new DepthTexture( + layer.pixelwidth, + layer.pixelheight, + layer.stencilBuffer ? UnsignedInt248Type : UnsignedIntType, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + layer.stencilBuffer ? DepthStencilFormat : DepthFormat + ), + stencilBuffer: layer.stencilBuffer, + resolveDepthBuffer: false, + resolveStencilBuffer: false + } ); + + layer.renderTarget.isXRRenderTarget = false; + + layer.plane.material = layer.material; + layer.material.map = layer.renderTarget.texture; + layer.material.map.offset.y = 1; + layer.material.map.repeat.y = -1; + delete layer.xrlayer; + + } + + } + + // + + this.isPresenting = false; + this._useMultiview = false; + + renderer._animation.stop(); + renderer._animation.setAnimationLoop( this._currentAnimationLoop ); + renderer._animation.setContext( this._currentAnimationContext ); + renderer._animation.start(); + + renderer.setPixelRatio( this._currentPixelRatio ); + renderer.setSize( this._currentSize.width, this._currentSize.height, false ); + + this.dispatchEvent( { type: 'sessionend' } ); + +} + +function onInputSourcesChange( event ) { + + const controllers = this._controllers; + const controllerInputSources = this._controllerInputSources; + + // Notify disconnected + + for ( let i = 0; i < event.removed.length; i ++ ) { + + const inputSource = event.removed[ i ]; + const index = controllerInputSources.indexOf( inputSource ); + + if ( index >= 0 ) { + + controllerInputSources[ index ] = null; + controllers[ index ].disconnect( inputSource ); + + } + + } + + // Notify connected + + for ( let i = 0; i < event.added.length; i ++ ) { + + const inputSource = event.added[ i ]; + + let controllerIndex = controllerInputSources.indexOf( inputSource ); + + if ( controllerIndex === -1 ) { + + // Assign input source a controller that currently has no input source + + for ( let i = 0; i < controllers.length; i ++ ) { + + if ( i >= controllerInputSources.length ) { + + controllerInputSources.push( inputSource ); + controllerIndex = i; + break; + + } else if ( controllerInputSources[ i ] === null ) { + + controllerInputSources[ i ] = inputSource; + controllerIndex = i; + break; + + } + + } + + // If all controllers do currently receive input we ignore new ones + + if ( controllerIndex === -1 ) break; + + } + + const controller = controllers[ controllerIndex ]; + + if ( controller ) { + + controller.connect( inputSource ); + + } + + } + +} + +// Creation method for native WebXR layers +function createXRLayer( layer ) { + + if ( layer.type === 'quad' ) { + + return this._glBinding.createQuadLayer( { + transform: new XRRigidTransform( layer.translation, layer.quaternion ), + width: layer.width / 2, + height: layer.height / 2, + space: this._referenceSpace, + viewPixelWidth: layer.pixelwidth, + viewPixelHeight: layer.pixelheight, + clearOnAccess: false + } ); + + } else { + + return this._glBinding.createCylinderLayer( { + transform: new XRRigidTransform( layer.translation, layer.quaternion ), + radius: layer.radius, + centralAngle: layer.centralAngle, + aspectRatio: layer.aspectRatio, + space: this._referenceSpace, + viewPixelWidth: layer.pixelwidth, + viewPixelHeight: layer.pixelheight, + clearOnAccess: false + } ); + + } + +} + +// Animation Loop + +function onAnimationFrame( time, frame ) { + + if ( frame === undefined ) return; + + const cameraXR = this._cameraXR; + const renderer = this._renderer; + const backend = renderer.backend; + + const glBaseLayer = this._glBaseLayer; + + const referenceSpace = this.getReferenceSpace(); + const pose = frame.getViewerPose( referenceSpace ); + + this._xrFrame = frame; + + if ( pose !== null ) { + + const views = pose.views; + + if ( this._glBaseLayer !== null ) { + + backend.setXRTarget( glBaseLayer.framebuffer ); + + } + + let cameraXRNeedsUpdate = false; + + // check if it's necessary to rebuild cameraXR's camera list + + if ( views.length !== cameraXR.cameras.length ) { + + cameraXR.cameras.length = 0; + cameraXRNeedsUpdate = true; + + } + + for ( let i = 0; i < views.length; i ++ ) { + + const view = views[ i ]; + + let viewport; + + if ( this._useLayers === true ) { + + const glSubImage = this._glBinding.getViewSubImage( this._glProjLayer, view ); + viewport = glSubImage.viewport; + + // For side-by-side projection, we only produce a single texture for both eyes. + if ( i === 0 ) { + + backend.setXRRenderTargetTextures( + this._xrRenderTarget, + glSubImage.colorTexture, + ( this._glProjLayer.ignoreDepthValues && ! this._useMultiview ) ? undefined : glSubImage.depthStencilTexture + ); + + } + + } else { + + viewport = glBaseLayer.getViewport( view ); + + } + + let camera = this._cameras[ i ]; + + if ( camera === undefined ) { + + camera = new PerspectiveCamera(); + camera.layers.enable( i ); + camera.viewport = new Vector4(); + this._cameras[ i ] = camera; + + } + + camera.matrix.fromArray( view.transform.matrix ); + camera.matrix.decompose( camera.position, camera.quaternion, camera.scale ); + camera.projectionMatrix.fromArray( view.projectionMatrix ); + camera.projectionMatrixInverse.copy( camera.projectionMatrix ).invert(); + camera.viewport.set( viewport.x, viewport.y, viewport.width, viewport.height ); + + if ( i === 0 ) { + + cameraXR.matrix.copy( camera.matrix ); + cameraXR.matrix.decompose( cameraXR.position, cameraXR.quaternion, cameraXR.scale ); + + } + + if ( cameraXRNeedsUpdate === true ) { + + cameraXR.cameras.push( camera ); + + } + + } + + renderer.setOutputRenderTarget( this._xrRenderTarget ); + + } + + // + + for ( let i = 0; i < this._controllers.length; i ++ ) { + + const inputSource = this._controllerInputSources[ i ]; + const controller = this._controllers[ i ]; + + if ( inputSource !== null && controller !== undefined ) { + + controller.update( inputSource, frame, referenceSpace ); + + } + + } + + if ( this._currentAnimationLoop ) this._currentAnimationLoop( time, frame ); + + if ( frame.detectedPlanes ) { + + this.dispatchEvent( { type: 'planesdetected', data: frame } ); + + } + + this._xrFrame = null; + +} + +const _scene = /*@__PURE__*/ new Scene(); +const _drawingBufferSize$1 = /*@__PURE__*/ new Vector2(); +const _screen = /*@__PURE__*/ new Vector4(); +const _frustum = /*@__PURE__*/ new Frustum(); +const _frustumArray = /*@__PURE__*/ new FrustumArray(); + +const _projScreenMatrix = /*@__PURE__*/ new Matrix4(); +const _vector4 = /*@__PURE__*/ new Vector4(); + +/** + * Base class for renderers. + */ +class Renderer { + + /** + * Renderer options. + * + * @typedef {Object} Renderer~Options + * @property {boolean} [logarithmicDepthBuffer=false] - Whether logarithmic depth buffer is enabled or not. + * @property {boolean} [alpha=true] - Whether the default framebuffer (which represents the final contents of the canvas) should be transparent or opaque. + * @property {boolean} [depth=true] - Whether the default framebuffer should have a depth buffer or not. + * @property {boolean} [stencil=false] - Whether the default framebuffer should have a stencil buffer or not. + * @property {boolean} [antialias=false] - Whether MSAA as the default anti-aliasing should be enabled or not. + * @property {number} [samples=0] - When `antialias` is `true`, `4` samples are used by default. This parameter can set to any other integer value than 0 + * to overwrite the default. + * @property {?Function} [getFallback=null] - This callback function can be used to provide a fallback backend, if the primary backend can't be targeted. + * @property {number} [colorBufferType=HalfFloatType] - Defines the type of color buffers. The default `HalfFloatType` is recommend for best + * quality. To save memory and bandwidth, `UnsignedByteType` might be used. This will reduce rendering quality though. + * @property {boolean} [multiview=false] - If set to `true`, the renderer will use multiview during WebXR rendering if supported. + */ + + /** + * Constructs a new renderer. + * + * @param {Backend} backend - The backend the renderer is targeting (e.g. WebGPU or WebGL 2). + * @param {Renderer~Options} [parameters] - The configuration parameter. + + */ + constructor( backend, parameters = {} ) { + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isRenderer = true; + + // + + const { + logarithmicDepthBuffer = false, + alpha = true, + depth = true, + stencil = false, + antialias = false, + samples = 0, + getFallback = null, + colorBufferType = HalfFloatType, + multiview = false + } = parameters; + + /** + * A reference to the canvas element the renderer is drawing to. + * This value of this property will automatically be created by + * the renderer. + * + * @type {HTMLCanvasElement|OffscreenCanvas} + */ + this.domElement = backend.getDomElement(); + + /** + * A reference to the current backend. + * + * @type {Backend} + */ + this.backend = backend; + + /** + * The number of MSAA samples. + * + * @type {number} + * @default 0 + */ + this.samples = samples || ( antialias === true ) ? 4 : 0; + + /** + * Whether the renderer should automatically clear the current rendering target + * before execute a `render()` call. The target can be the canvas (default framebuffer) + * or the current bound render target (custom framebuffer). + * + * @type {boolean} + * @default true + */ + this.autoClear = true; + + /** + * When `autoClear` is set to `true`, this property defines whether the renderer + * should clear the color buffer. + * + * @type {boolean} + * @default true + */ + this.autoClearColor = true; + + /** + * When `autoClear` is set to `true`, this property defines whether the renderer + * should clear the depth buffer. + * + * @type {boolean} + * @default true + */ + this.autoClearDepth = true; + + /** + * When `autoClear` is set to `true`, this property defines whether the renderer + * should clear the stencil buffer. + * + * @type {boolean} + * @default true + */ + this.autoClearStencil = true; + + /** + * Whether the default framebuffer should be transparent or opaque. + * + * @type {boolean} + * @default true + */ + this.alpha = alpha; + + /** + * Whether logarithmic depth buffer is enabled or not. + * + * @type {boolean} + * @default false + */ + this.logarithmicDepthBuffer = logarithmicDepthBuffer; + + /** + * Defines the output color space of the renderer. + * + * @type {string} + * @default SRGBColorSpace + */ + this.outputColorSpace = SRGBColorSpace; + + /** + * Defines the tone mapping of the renderer. + * + * @type {number} + * @default NoToneMapping + */ + this.toneMapping = NoToneMapping; + + /** + * Defines the tone mapping exposure. + * + * @type {number} + * @default 1 + */ + this.toneMappingExposure = 1.0; + + /** + * Whether the renderer should sort its render lists or not. + * + * Note: Sorting is used to attempt to properly render objects that have some degree of transparency. + * By definition, sorting objects may not work in all cases. Depending on the needs of application, + * it may be necessary to turn off sorting and use other methods to deal with transparency rendering + * e.g. manually determining each object's rendering order. + * + * @type {boolean} + * @default true + */ + this.sortObjects = true; + + /** + * Whether the default framebuffer should have a depth buffer or not. + * + * @type {boolean} + * @default true + */ + this.depth = depth; + + /** + * Whether the default framebuffer should have a stencil buffer or not. + * + * @type {boolean} + * @default false + */ + this.stencil = stencil; + + /** + * Holds a series of statistical information about the GPU memory + * and the rendering process. Useful for debugging and monitoring. + * + * @type {Info} + */ + this.info = new Info(); + + /** + * Stores override nodes for specific transformations or calculations. + * These nodes can be used to replace default behavior in the rendering pipeline. + * + * @type {Object} + * @property {?Node} modelViewMatrix - An override node for the model-view matrix. + * @property {?Node} modelNormalViewMatrix - An override node for the model normal view matrix. + */ + this.overrideNodes = { + modelViewMatrix: null, + modelNormalViewMatrix: null + }; + + /** + * The node library defines how certain library objects like materials, lights + * or tone mapping functions are mapped to node types. This is required since + * although instances of classes like `MeshBasicMaterial` or `PointLight` can + * be part of the scene graph, they are internally represented as nodes for + * further processing. + * + * @type {NodeLibrary} + */ + this.library = new NodeLibrary(); + + /** + * A map-like data structure for managing lights. + * + * @type {Lighting} + */ + this.lighting = new Lighting(); + + // internals + + /** + * This callback function can be used to provide a fallback backend, if the primary backend can't be targeted. + * + * @private + * @type {?Function} + */ + this._getFallback = getFallback; + + /** + * The renderer's pixel ratio. + * + * @private + * @type {number} + * @default 1 + */ + this._pixelRatio = 1; + + /** + * The width of the renderer's default framebuffer in logical pixel unit. + * + * @private + * @type {number} + */ + this._width = this.domElement.width; + + /** + * The height of the renderer's default framebuffer in logical pixel unit. + * + * @private + * @type {number} + */ + this._height = this.domElement.height; + + /** + * The viewport of the renderer in logical pixel unit. + * + * @private + * @type {Vector4} + */ + this._viewport = new Vector4( 0, 0, this._width, this._height ); + + /** + * The scissor rectangle of the renderer in logical pixel unit. + * + * @private + * @type {Vector4} + */ + this._scissor = new Vector4( 0, 0, this._width, this._height ); + + /** + * Whether the scissor test should be enabled or not. + * + * @private + * @type {boolean} + */ + this._scissorTest = false; + + /** + * A reference to a renderer module for managing shader attributes. + * + * @private + * @type {?Attributes} + * @default null + */ + this._attributes = null; + + /** + * A reference to a renderer module for managing geometries. + * + * @private + * @type {?Geometries} + * @default null + */ + this._geometries = null; + + /** + * A reference to a renderer module for managing node related logic. + * + * @private + * @type {?Nodes} + * @default null + */ + this._nodes = null; + + /** + * A reference to a renderer module for managing the internal animation loop. + * + * @private + * @type {?Animation} + * @default null + */ + this._animation = null; + + /** + * A reference to a renderer module for managing shader program bindings. + * + * @private + * @type {?Bindings} + * @default null + */ + this._bindings = null; + + /** + * A reference to a renderer module for managing render objects. + * + * @private + * @type {?RenderObjects} + * @default null + */ + this._objects = null; + + /** + * A reference to a renderer module for managing render and compute pipelines. + * + * @private + * @type {?Pipelines} + * @default null + */ + this._pipelines = null; + + /** + * A reference to a renderer module for managing render bundles. + * + * @private + * @type {?RenderBundles} + * @default null + */ + this._bundles = null; + + /** + * A reference to a renderer module for managing render lists. + * + * @private + * @type {?RenderLists} + * @default null + */ + this._renderLists = null; + + /** + * A reference to a renderer module for managing render contexts. + * + * @private + * @type {?RenderContexts} + * @default null + */ + this._renderContexts = null; + + /** + * A reference to a renderer module for managing textures. + * + * @private + * @type {?Textures} + * @default null + */ + this._textures = null; + + /** + * A reference to a renderer module for backgrounds. + * + * @private + * @type {?Background} + * @default null + */ + this._background = null; + + /** + * This fullscreen quad is used for internal render passes + * like the tone mapping and color space output pass. + * + * @private + * @type {QuadMesh} + */ + this._quad = new QuadMesh( new NodeMaterial() ); + this._quad.material.name = 'Renderer_output'; + + /** + * A reference to the current render context. + * + * @private + * @type {?RenderContext} + * @default null + */ + this._currentRenderContext = null; + + /** + * A custom sort function for the opaque render list. + * + * @private + * @type {?Function} + * @default null + */ + this._opaqueSort = null; + + /** + * A custom sort function for the transparent render list. + * + * @private + * @type {?Function} + * @default null + */ + this._transparentSort = null; + + /** + * The framebuffer target. + * + * @private + * @type {?RenderTarget} + * @default null + */ + this._frameBufferTarget = null; + + const alphaClear = this.alpha === true ? 0 : 1; + + /** + * The clear color value. + * + * @private + * @type {Color4} + */ + this._clearColor = new Color4( 0, 0, 0, alphaClear ); + + /** + * The clear depth value. + * + * @private + * @type {number} + * @default 1 + */ + this._clearDepth = 1; + + /** + * The clear stencil value. + * + * @private + * @type {number} + * @default 0 + */ + this._clearStencil = 0; + + /** + * The current render target. + * + * @private + * @type {?RenderTarget} + * @default null + */ + this._renderTarget = null; + + /** + * The active cube face. + * + * @private + * @type {number} + * @default 0 + */ + this._activeCubeFace = 0; + + /** + * The active mipmap level. + * + * @private + * @type {number} + * @default 0 + */ + this._activeMipmapLevel = 0; + + /** + * The current output render target. + * + * @private + * @type {?RenderTarget} + * @default null + */ + this._outputRenderTarget = null; + + /** + * The MRT setting. + * + * @private + * @type {?MRTNode} + * @default null + */ + this._mrt = null; + + /** + * This function defines how a render object is going + * to be rendered. + * + * @private + * @type {?Function} + * @default null + */ + this._renderObjectFunction = null; + + /** + * Used to keep track of the current render object function. + * + * @private + * @type {?Function} + * @default null + */ + this._currentRenderObjectFunction = null; + + /** + * Used to keep track of the current render bundle. + * + * @private + * @type {?RenderBundle} + * @default null + */ + this._currentRenderBundle = null; + + /** + * Next to `_renderObjectFunction()`, this function provides another hook + * for influencing the render process of a render object. It is meant for internal + * use and only relevant for `compileAsync()` right now. Instead of using + * the default logic of `_renderObjectDirect()` which actually draws the render object, + * a different function might be used which performs no draw but just the node + * and pipeline updates. + * + * @private + * @type {?Function} + * @default null + */ + this._handleObjectFunction = this._renderObjectDirect; + + /** + * Indicates whether the device has been lost or not. In WebGL terms, the device + * lost is considered as a context lost. When this is set to `true`, rendering + * isn't possible anymore. + * + * @private + * @type {boolean} + * @default false + */ + this._isDeviceLost = false; + + /** + * A callback function that defines what should happen when a device/context lost occurs. + * + * @type {Function} + */ + this.onDeviceLost = this._onDeviceLost; + + /** + * Defines the type of color buffers. The default `HalfFloatType` is recommend for + * best quality. To save memory and bandwidth, `UnsignedByteType` might be used. + * This will reduce rendering quality though. + * + * @private + * @type {number} + * @default HalfFloatType + */ + this._colorBufferType = colorBufferType; + + /** + * Whether the renderer has been initialized or not. + * + * @private + * @type {boolean} + * @default false + */ + this._initialized = false; + + /** + * A reference to the promise which initializes the renderer. + * + * @private + * @type {?Promise} + * @default null + */ + this._initPromise = null; + + /** + * An array of compilation promises which are used in `compileAsync()`. + * + * @private + * @type {?Array} + * @default null + */ + this._compilationPromises = null; + + /** + * Whether the renderer should render transparent render objects or not. + * + * @type {boolean} + * @default true + */ + this.transparent = true; + + /** + * Whether the renderer should render opaque render objects or not. + * + * @type {boolean} + * @default true + */ + this.opaque = true; + + /** + * Shadow map configuration + * @typedef {Object} ShadowMapConfig + * @property {boolean} enabled - Whether to globally enable shadows or not. + * @property {number} type - The shadow map type. + */ + + /** + * The renderer's shadow configuration. + * + * @type {ShadowMapConfig} + */ + this.shadowMap = { + enabled: false, + type: PCFShadowMap + }; + + /** + * XR configuration. + * @typedef {Object} XRConfig + * @property {boolean} enabled - Whether to globally enable XR or not. + */ + + /** + * The renderer's XR manager. + * + * @type {XRManager} + */ + this.xr = new XRManager( this, multiview ); + + /** + * Debug configuration. + * @typedef {Object} DebugConfig + * @property {boolean} checkShaderErrors - Whether shader errors should be checked or not. + * @property {?Function} onShaderError - A callback function that is executed when a shader error happens. Only supported with WebGL 2 right now. + * @property {Function} getShaderAsync - Allows the get the raw shader code for the given scene, camera and 3D object. + */ + + /** + * The renderer's debug configuration. + * + * @type {DebugConfig} + */ + this.debug = { + checkShaderErrors: true, + onShaderError: null, + getShaderAsync: async ( scene, camera, object ) => { + + await this.compileAsync( scene, camera ); + + const renderList = this._renderLists.get( scene, camera ); + const renderContext = this._renderContexts.get( scene, camera, this._renderTarget ); + + const material = scene.overrideMaterial || object.material; + + const renderObject = this._objects.get( object, material, scene, camera, renderList.lightsNode, renderContext, renderContext.clippingContext ); + + const { fragmentShader, vertexShader } = renderObject.getNodeBuilderState(); + + return { fragmentShader, vertexShader }; + + } + }; + + } + + /** + * Initializes the renderer so it is ready for usage. + * + * @async + * @return {Promise} A Promise that resolves when the renderer has been initialized. + */ + async init() { + + if ( this._initialized ) { + + throw new Error( 'Renderer: Backend has already been initialized.' ); + + } + + if ( this._initPromise !== null ) { + + return this._initPromise; + + } + + this._initPromise = new Promise( async ( resolve, reject ) => { + + let backend = this.backend; + + try { + + await backend.init( this ); + + } catch ( error ) { + + if ( this._getFallback !== null ) { + + // try the fallback + + try { + + this.backend = backend = this._getFallback( error ); + await backend.init( this ); + + } catch ( error ) { + + reject( error ); + return; + + } + + } else { + + reject( error ); + return; + + } + + } + + this._nodes = new Nodes( this, backend ); + this._animation = new Animation( this._nodes, this.info ); + this._attributes = new Attributes( backend ); + this._background = new Background( this, this._nodes ); + this._geometries = new Geometries( this._attributes, this.info ); + this._textures = new Textures( this, backend, this.info ); + this._pipelines = new Pipelines( backend, this._nodes ); + this._bindings = new Bindings( backend, this._nodes, this._textures, this._attributes, this._pipelines, this.info ); + this._objects = new RenderObjects( this, this._nodes, this._geometries, this._pipelines, this._bindings, this.info ); + this._renderLists = new RenderLists( this.lighting ); + this._bundles = new RenderBundles(); + this._renderContexts = new RenderContexts(); + + // + + this._animation.start(); + this._initialized = true; + + resolve( this ); + + } ); + + return this._initPromise; + + } + + /** + * The coordinate system of the renderer. The value of this property + * depends on the selected backend. Either `THREE.WebGLCoordinateSystem` or + * `THREE.WebGPUCoordinateSystem`. + * + * @readonly + * @type {number} + */ + get coordinateSystem() { + + return this.backend.coordinateSystem; + + } + + /** + * Compiles all materials in the given scene. This can be useful to avoid a + * phenomenon which is called "shader compilation stutter", which occurs when + * rendering an object with a new shader for the first time. + * + * If you want to add a 3D object to an existing scene, use the third optional + * parameter for applying the target scene. Note that the (target) scene's lighting + * and environment must be configured before calling this method. + * + * @async + * @param {Object3D} scene - The scene or 3D object to precompile. + * @param {Camera} camera - The camera that is used to render the scene. + * @param {?Scene} targetScene - If the first argument is a 3D object, this parameter must represent the scene the 3D object is going to be added. + * @return {Promise} A Promise that resolves when the compile has been finished. + */ + async compileAsync( scene, camera, targetScene = null ) { + + if ( this._isDeviceLost === true ) return; + + if ( this._initialized === false ) await this.init(); + + // preserve render tree + + const nodeFrame = this._nodes.nodeFrame; + + const previousRenderId = nodeFrame.renderId; + const previousRenderContext = this._currentRenderContext; + const previousRenderObjectFunction = this._currentRenderObjectFunction; + const previousCompilationPromises = this._compilationPromises; + + // + + const sceneRef = ( scene.isScene === true ) ? scene : _scene; + + if ( targetScene === null ) targetScene = scene; + + const renderTarget = this._renderTarget; + const renderContext = this._renderContexts.get( targetScene, camera, renderTarget ); + const activeMipmapLevel = this._activeMipmapLevel; + + const compilationPromises = []; + + this._currentRenderContext = renderContext; + this._currentRenderObjectFunction = this.renderObject; + + this._handleObjectFunction = this._createObjectPipeline; + + this._compilationPromises = compilationPromises; + + nodeFrame.renderId ++; + + // + + nodeFrame.update(); + + // + + renderContext.depth = this.depth; + renderContext.stencil = this.stencil; + + if ( ! renderContext.clippingContext ) renderContext.clippingContext = new ClippingContext(); + renderContext.clippingContext.updateGlobal( sceneRef, camera ); + + // + + sceneRef.onBeforeRender( this, scene, camera, renderTarget ); + + // + + const renderList = this._renderLists.get( scene, camera ); + renderList.begin(); + + this._projectObject( scene, camera, 0, renderList, renderContext.clippingContext ); + + // include lights from target scene + if ( targetScene !== scene ) { + + targetScene.traverseVisible( function ( object ) { + + if ( object.isLight && object.layers.test( camera.layers ) ) { + + renderList.pushLight( object ); + + } + + } ); + + } + + renderList.finish(); + + // + + if ( renderTarget !== null ) { + + this._textures.updateRenderTarget( renderTarget, activeMipmapLevel ); + + const renderTargetData = this._textures.get( renderTarget ); + + renderContext.textures = renderTargetData.textures; + renderContext.depthTexture = renderTargetData.depthTexture; + + } else { + + renderContext.textures = null; + renderContext.depthTexture = null; + + } + + // + + this._background.update( sceneRef, renderList, renderContext ); + + // process render lists + + const opaqueObjects = renderList.opaque; + const transparentObjects = renderList.transparent; + const transparentDoublePassObjects = renderList.transparentDoublePass; + const lightsNode = renderList.lightsNode; + + if ( this.opaque === true && opaqueObjects.length > 0 ) this._renderObjects( opaqueObjects, camera, sceneRef, lightsNode ); + if ( this.transparent === true && transparentObjects.length > 0 ) this._renderTransparents( transparentObjects, transparentDoublePassObjects, camera, sceneRef, lightsNode ); + + // restore render tree + + nodeFrame.renderId = previousRenderId; + + this._currentRenderContext = previousRenderContext; + this._currentRenderObjectFunction = previousRenderObjectFunction; + this._compilationPromises = previousCompilationPromises; + + this._handleObjectFunction = this._renderObjectDirect; + + // wait for all promises setup by backends awaiting compilation/linking/pipeline creation to complete + + await Promise.all( compilationPromises ); + + } + + /** + * Renders the scene in an async fashion. + * + * @async + * @param {Object3D} scene - The scene or 3D object to render. + * @param {Camera} camera - The camera. + * @return {Promise} A Promise that resolves when the render has been finished. + */ + async renderAsync( scene, camera ) { + + if ( this._initialized === false ) await this.init(); + + this._renderScene( scene, camera ); + + } + + /** + * Can be used to synchronize CPU operations with GPU tasks. So when this method is called, + * the CPU waits for the GPU to complete its operation (e.g. a compute task). + * + * @async + * @return {Promise} A Promise that resolves when synchronization has been finished. + */ + async waitForGPU() { + + await this.backend.waitForGPU(); + + } + + /** + * Enables or disables high precision for model-view and normal-view matrices. + * When enabled, will use CPU 64-bit precision for higher precision instead of GPU 32-bit for higher performance. + * + * NOTE: 64-bit precision is not compatible with `InstancedMesh` and `SkinnedMesh`. + * + * @param {boolean} value - Whether to enable or disable high precision. + * @type {boolean} + */ + set highPrecision( value ) { + + if ( value === true ) { + + this.overrideNodes.modelViewMatrix = highpModelViewMatrix; + this.overrideNodes.modelNormalViewMatrix = highpModelNormalViewMatrix; + + } else if ( this.highPrecision ) { + + this.overrideNodes.modelViewMatrix = null; + this.overrideNodes.modelNormalViewMatrix = null; + + } + + } + + /** + * Returns whether high precision is enabled or not. + * + * @return {boolean} Whether high precision is enabled or not. + * @type {boolean} + */ + get highPrecision() { + + return this.overrideNodes.modelViewMatrix === highpModelViewMatrix && this.overrideNodes.modelNormalViewMatrix === highpModelNormalViewMatrix; + + } + + /** + * Sets the given MRT configuration. + * + * @param {MRTNode} mrt - The MRT node to set. + * @return {Renderer} A reference to this renderer. + */ + setMRT( mrt ) { + + this._mrt = mrt; + + return this; + + } + + /** + * Returns the MRT configuration. + * + * @return {MRTNode} The MRT configuration. + */ + getMRT() { + + return this._mrt; + + } + + /** + * Returns the color buffer type. + * + * @return {number} The color buffer type. + */ + getColorBufferType() { + + return this._colorBufferType; + + } + + /** + * Default implementation of the device lost callback. + * + * @private + * @param {Object} info - Information about the context lost. + */ + _onDeviceLost( info ) { + + let errorMessage = `THREE.WebGPURenderer: ${info.api} Device Lost:\n\nMessage: ${info.message}`; + + if ( info.reason ) { + + errorMessage += `\nReason: ${info.reason}`; + + } + + console.error( errorMessage ); + + this._isDeviceLost = true; + + } + + /** + * Renders the given render bundle. + * + * @private + * @param {Object} bundle - Render bundle data. + * @param {Scene} sceneRef - The scene the render bundle belongs to. + * @param {LightsNode} lightsNode - The lights node. + */ + _renderBundle( bundle, sceneRef, lightsNode ) { + + const { bundleGroup, camera, renderList } = bundle; + + const renderContext = this._currentRenderContext; + + // + + const renderBundle = this._bundles.get( bundleGroup, camera ); + const renderBundleData = this.backend.get( renderBundle ); + + if ( renderBundleData.renderContexts === undefined ) renderBundleData.renderContexts = new Set(); + + // + + const needsUpdate = bundleGroup.version !== renderBundleData.version; + const renderBundleNeedsUpdate = renderBundleData.renderContexts.has( renderContext ) === false || needsUpdate; + + renderBundleData.renderContexts.add( renderContext ); + + if ( renderBundleNeedsUpdate ) { + + this.backend.beginBundle( renderContext ); + + if ( renderBundleData.renderObjects === undefined || needsUpdate ) { + + renderBundleData.renderObjects = []; + + } + + this._currentRenderBundle = renderBundle; + + const { + transparentDoublePass: transparentDoublePassObjects, + transparent: transparentObjects, + opaque: opaqueObjects + } = renderList; + + if ( this.opaque === true && opaqueObjects.length > 0 ) this._renderObjects( opaqueObjects, camera, sceneRef, lightsNode ); + if ( this.transparent === true && transparentObjects.length > 0 ) this._renderTransparents( transparentObjects, transparentDoublePassObjects, camera, sceneRef, lightsNode ); + + this._currentRenderBundle = null; + + // + + this.backend.finishBundle( renderContext, renderBundle ); + + renderBundleData.version = bundleGroup.version; + + } else { + + const { renderObjects } = renderBundleData; + + for ( let i = 0, l = renderObjects.length; i < l; i ++ ) { + + const renderObject = renderObjects[ i ]; + + if ( this._nodes.needsRefresh( renderObject ) ) { + + this._nodes.updateBefore( renderObject ); + + this._nodes.updateForRender( renderObject ); + this._bindings.updateForRender( renderObject ); + + this._nodes.updateAfter( renderObject ); + + } + + } + + } + + this.backend.addBundle( renderContext, renderBundle ); + + } + + /** + * Renders the scene or 3D object with the given camera. This method can only be called + * if the renderer has been initialized. + * + * The target of the method is the default framebuffer (meaning the canvas) + * or alternatively a render target when specified via `setRenderTarget()`. + * + * @param {Object3D} scene - The scene or 3D object to render. + * @param {Camera} camera - The camera to render the scene with. + * @return {?Promise} A Promise that resolve when the scene has been rendered. + * Only returned when the renderer has not been initialized. + */ + render( scene, camera ) { + + if ( this._initialized === false ) { + + console.warn( 'THREE.Renderer: .render() called before the backend is initialized. Try using .renderAsync() instead.' ); + + return this.renderAsync( scene, camera ); + + } + + this._renderScene( scene, camera ); + + } + + /** + * Returns an internal render target which is used when computing the output tone mapping + * and color space conversion. Unlike in `WebGLRenderer`, this is done in a separate render + * pass and not inline to achieve more correct results. + * + * @private + * @return {?RenderTarget} The render target. The method returns `null` if no output conversion should be applied. + */ + _getFrameBufferTarget() { + + const { currentToneMapping, currentColorSpace } = this; + + const useToneMapping = currentToneMapping !== NoToneMapping; + const useColorSpace = currentColorSpace !== LinearSRGBColorSpace; + + if ( useToneMapping === false && useColorSpace === false ) return null; + + const { width, height } = this.getDrawingBufferSize( _drawingBufferSize$1 ); + const { depth, stencil } = this; + + let frameBufferTarget = this._frameBufferTarget; + + if ( frameBufferTarget === null ) { + + frameBufferTarget = new RenderTarget( width, height, { + depthBuffer: depth, + stencilBuffer: stencil, + type: this._colorBufferType, + format: RGBAFormat, + colorSpace: LinearSRGBColorSpace, + generateMipmaps: false, + minFilter: LinearFilter, + magFilter: LinearFilter, + samples: this.samples + } ); + + frameBufferTarget.isPostProcessingRenderTarget = true; + + this._frameBufferTarget = frameBufferTarget; + + } + + const outputRenderTarget = this.getOutputRenderTarget(); + + frameBufferTarget.depthBuffer = depth; + frameBufferTarget.stencilBuffer = stencil; + if ( outputRenderTarget !== null ) { + + frameBufferTarget.setSize( outputRenderTarget.width, outputRenderTarget.height, outputRenderTarget.depth ); + + } else { + + frameBufferTarget.setSize( width, height, 1 ); + + } + + frameBufferTarget.viewport.copy( this._viewport ); + frameBufferTarget.scissor.copy( this._scissor ); + frameBufferTarget.viewport.multiplyScalar( this._pixelRatio ); + frameBufferTarget.scissor.multiplyScalar( this._pixelRatio ); + frameBufferTarget.scissorTest = this._scissorTest; + frameBufferTarget.multiview = outputRenderTarget !== null ? outputRenderTarget.multiview : false; + frameBufferTarget.resolveDepthBuffer = outputRenderTarget !== null ? outputRenderTarget.resolveDepthBuffer : true; + frameBufferTarget._autoAllocateDepthBuffer = outputRenderTarget !== null ? outputRenderTarget._autoAllocateDepthBuffer : false; + + return frameBufferTarget; + + } + + /** + * Renders the scene or 3D object with the given camera. + * + * @private + * @param {Object3D} scene - The scene or 3D object to render. + * @param {Camera} camera - The camera to render the scene with. + * @param {boolean} [useFrameBufferTarget=true] - Whether to use a framebuffer target or not. + * @return {RenderContext} The current render context. + */ + _renderScene( scene, camera, useFrameBufferTarget = true ) { + + if ( this._isDeviceLost === true ) return; + + const frameBufferTarget = useFrameBufferTarget ? this._getFrameBufferTarget() : null; + + // preserve render tree + + const nodeFrame = this._nodes.nodeFrame; + + const previousRenderId = nodeFrame.renderId; + const previousRenderContext = this._currentRenderContext; + const previousRenderObjectFunction = this._currentRenderObjectFunction; + + // + + const sceneRef = ( scene.isScene === true ) ? scene : _scene; + + const outputRenderTarget = this._renderTarget || this._outputRenderTarget; + + const activeCubeFace = this._activeCubeFace; + const activeMipmapLevel = this._activeMipmapLevel; + + // + + let renderTarget; + + if ( frameBufferTarget !== null ) { + + renderTarget = frameBufferTarget; + + this.setRenderTarget( renderTarget ); + + } else { + + renderTarget = outputRenderTarget; + + } + + // + + const renderContext = this._renderContexts.get( scene, camera, renderTarget ); + + this._currentRenderContext = renderContext; + this._currentRenderObjectFunction = this._renderObjectFunction || this.renderObject; + + // + + this.info.calls ++; + this.info.render.calls ++; + this.info.render.frameCalls ++; + + nodeFrame.renderId = this.info.calls; + + // + + const coordinateSystem = this.coordinateSystem; + const xr = this.xr; + + if ( camera.coordinateSystem !== coordinateSystem && xr.isPresenting === false ) { + + camera.coordinateSystem = coordinateSystem; + camera.updateProjectionMatrix(); + + if ( camera.isArrayCamera ) { + + for ( const subCamera of camera.cameras ) { + + subCamera.coordinateSystem = coordinateSystem; + subCamera.updateProjectionMatrix(); + + } + + } + + } + + // + + if ( scene.matrixWorldAutoUpdate === true ) scene.updateMatrixWorld(); + + if ( camera.parent === null && camera.matrixWorldAutoUpdate === true ) camera.updateMatrixWorld(); + + if ( xr.enabled === true && xr.isPresenting === true ) { + + if ( xr.cameraAutoUpdate === true ) xr.updateCamera( camera ); + camera = xr.getCamera(); // use XR camera for rendering + + } + + // + + let viewport = this._viewport; + let scissor = this._scissor; + let pixelRatio = this._pixelRatio; + + if ( renderTarget !== null ) { + + viewport = renderTarget.viewport; + scissor = renderTarget.scissor; + pixelRatio = 1; + + } + + this.getDrawingBufferSize( _drawingBufferSize$1 ); + + _screen.set( 0, 0, _drawingBufferSize$1.width, _drawingBufferSize$1.height ); + + const minDepth = ( viewport.minDepth === undefined ) ? 0 : viewport.minDepth; + const maxDepth = ( viewport.maxDepth === undefined ) ? 1 : viewport.maxDepth; + + renderContext.viewportValue.copy( viewport ).multiplyScalar( pixelRatio ).floor(); + renderContext.viewportValue.width >>= activeMipmapLevel; + renderContext.viewportValue.height >>= activeMipmapLevel; + renderContext.viewportValue.minDepth = minDepth; + renderContext.viewportValue.maxDepth = maxDepth; + renderContext.viewport = renderContext.viewportValue.equals( _screen ) === false; + + renderContext.scissorValue.copy( scissor ).multiplyScalar( pixelRatio ).floor(); + renderContext.scissor = this._scissorTest && renderContext.scissorValue.equals( _screen ) === false; + renderContext.scissorValue.width >>= activeMipmapLevel; + renderContext.scissorValue.height >>= activeMipmapLevel; + + if ( ! renderContext.clippingContext ) renderContext.clippingContext = new ClippingContext(); + renderContext.clippingContext.updateGlobal( sceneRef, camera ); + + // + + sceneRef.onBeforeRender( this, scene, camera, renderTarget ); + + // + + const frustum = camera.isArrayCamera ? _frustumArray : _frustum; + + if ( ! camera.isArrayCamera ) { + + _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); + frustum.setFromProjectionMatrix( _projScreenMatrix, coordinateSystem ); + + } + + const renderList = this._renderLists.get( scene, camera ); + renderList.begin(); + + this._projectObject( scene, camera, 0, renderList, renderContext.clippingContext ); + + renderList.finish(); + + if ( this.sortObjects === true ) { + + renderList.sort( this._opaqueSort, this._transparentSort ); + + } + + // + + if ( renderTarget !== null ) { + + this._textures.updateRenderTarget( renderTarget, activeMipmapLevel ); + + const renderTargetData = this._textures.get( renderTarget ); + + renderContext.textures = renderTargetData.textures; + renderContext.depthTexture = renderTargetData.depthTexture; + renderContext.width = renderTargetData.width; + renderContext.height = renderTargetData.height; + renderContext.renderTarget = renderTarget; + renderContext.depth = renderTarget.depthBuffer; + renderContext.stencil = renderTarget.stencilBuffer; + + } else { + + renderContext.textures = null; + renderContext.depthTexture = null; + renderContext.width = this.domElement.width; + renderContext.height = this.domElement.height; + renderContext.depth = this.depth; + renderContext.stencil = this.stencil; + + } + + renderContext.width >>= activeMipmapLevel; + renderContext.height >>= activeMipmapLevel; + renderContext.activeCubeFace = activeCubeFace; + renderContext.activeMipmapLevel = activeMipmapLevel; + renderContext.occlusionQueryCount = renderList.occlusionQueryCount; + + // + + this._background.update( sceneRef, renderList, renderContext ); + + // + + renderContext.camera = camera; + this.backend.beginRender( renderContext ); + + // process render lists + + const { + bundles, + lightsNode, + transparentDoublePass: transparentDoublePassObjects, + transparent: transparentObjects, + opaque: opaqueObjects + } = renderList; + + if ( bundles.length > 0 ) this._renderBundles( bundles, sceneRef, lightsNode ); + if ( this.opaque === true && opaqueObjects.length > 0 ) this._renderObjects( opaqueObjects, camera, sceneRef, lightsNode ); + if ( this.transparent === true && transparentObjects.length > 0 ) this._renderTransparents( transparentObjects, transparentDoublePassObjects, camera, sceneRef, lightsNode ); + + // finish render pass + + this.backend.finishRender( renderContext ); + + // restore render tree + + nodeFrame.renderId = previousRenderId; + + this._currentRenderContext = previousRenderContext; + this._currentRenderObjectFunction = previousRenderObjectFunction; + + // + + if ( frameBufferTarget !== null ) { + + this.setRenderTarget( outputRenderTarget, activeCubeFace, activeMipmapLevel ); + + this._renderOutput( renderTarget ); + + } + + // + + sceneRef.onAfterRender( this, scene, camera, renderTarget ); + + // + + return renderContext; + + } + + _setXRLayerSize( width, height ) { + + this._width = width; + this._height = height; + + this.setViewport( 0, 0, width, height ); + + } + + /** + * The output pass performs tone mapping and color space conversion. + * + * @private + * @param {RenderTarget} renderTarget - The current render target. + */ + _renderOutput( renderTarget ) { + + const quad = this._quad; + + if ( this._nodes.hasOutputChange( renderTarget.texture ) ) { + + quad.material.fragmentNode = this._nodes.getOutputNode( renderTarget.texture ); + quad.material.needsUpdate = true; + + } + + // a clear operation clears the intermediate renderTarget texture, but should not update the screen canvas. + + const currentAutoClear = this.autoClear; + const currentXR = this.xr.enabled; + + this.autoClear = false; + this.xr.enabled = false; + + this._renderScene( quad, quad.camera, false ); + + this.autoClear = currentAutoClear; + this.xr.enabled = currentXR; + + + } + + /** + * Returns the maximum available anisotropy for texture filtering. + * + * @return {number} The maximum available anisotropy. + */ + getMaxAnisotropy() { + + return this.backend.getMaxAnisotropy(); + + } + + /** + * Returns the active cube face. + * + * @return {number} The active cube face. + */ + getActiveCubeFace() { + + return this._activeCubeFace; + + } + + /** + * Returns the active mipmap level. + * + * @return {number} The active mipmap level. + */ + getActiveMipmapLevel() { + + return this._activeMipmapLevel; + + } + + /** + * Applications are advised to always define the animation loop + * with this method and not manually with `requestAnimationFrame()` + * for best compatibility. + * + * @async + * @param {?Function} callback - The application's animation loop. + * @return {Promise} A Promise that resolves when the set has been executed. + */ + async setAnimationLoop( callback ) { + + if ( this._initialized === false ) await this.init(); + + this._animation.setAnimationLoop( callback ); + + } + + /** + * Can be used to transfer buffer data from a storage buffer attribute + * from the GPU to the CPU in context of compute shaders. + * + * @async + * @param {StorageBufferAttribute} attribute - The storage buffer attribute. + * @return {Promise} A promise that resolves with the buffer data when the data are ready. + */ + async getArrayBufferAsync( attribute ) { + + return await this.backend.getArrayBufferAsync( attribute ); + + } + + /** + * Returns the rendering context. + * + * @return {GPUCanvasContext|WebGL2RenderingContext} The rendering context. + */ + getContext() { + + return this.backend.getContext(); + + } + + /** + * Returns the pixel ratio. + * + * @return {number} The pixel ratio. + */ + getPixelRatio() { + + return this._pixelRatio; + + } + + /** + * Returns the drawing buffer size in physical pixels. This method honors the pixel ratio. + * + * @param {Vector2} target - The method writes the result in this target object. + * @return {Vector2} The drawing buffer size. + */ + getDrawingBufferSize( target ) { + + return target.set( this._width * this._pixelRatio, this._height * this._pixelRatio ).floor(); + + } + + /** + * Returns the renderer's size in logical pixels. This method does not honor the pixel ratio. + * + * @param {Vector2} target - The method writes the result in this target object. + * @return {Vector2} The renderer's size in logical pixels. + */ + getSize( target ) { + + return target.set( this._width, this._height ); + + } + + /** + * Sets the given pixel ratio and resizes the canvas if necessary. + * + * @param {number} [value=1] - The pixel ratio. + */ + setPixelRatio( value = 1 ) { + + if ( this._pixelRatio === value ) return; + + this._pixelRatio = value; + + this.setSize( this._width, this._height, false ); + + } + + /** + * This method allows to define the drawing buffer size by specifying + * width, height and pixel ratio all at once. The size of the drawing + * buffer is computed with this formula: + * ```js + * size.x = width * pixelRatio; + * size.y = height * pixelRatio; + * ``` + * + * @param {number} width - The width in logical pixels. + * @param {number} height - The height in logical pixels. + * @param {number} pixelRatio - The pixel ratio. + */ + setDrawingBufferSize( width, height, pixelRatio ) { + + // Renderer can't be resized while presenting in XR. + if ( this.xr && this.xr.isPresenting ) return; + + this._width = width; + this._height = height; + + this._pixelRatio = pixelRatio; + + this.domElement.width = Math.floor( width * pixelRatio ); + this.domElement.height = Math.floor( height * pixelRatio ); + + this.setViewport( 0, 0, width, height ); + + if ( this._initialized ) this.backend.updateSize(); + + } + + /** + * Sets the size of the renderer. + * + * @param {number} width - The width in logical pixels. + * @param {number} height - The height in logical pixels. + * @param {boolean} [updateStyle=true] - Whether to update the `style` attribute of the canvas or not. + */ + setSize( width, height, updateStyle = true ) { + + // Renderer can't be resized while presenting in XR. + if ( this.xr && this.xr.isPresenting ) return; + + this._width = width; + this._height = height; + + this.domElement.width = Math.floor( width * this._pixelRatio ); + this.domElement.height = Math.floor( height * this._pixelRatio ); + + if ( updateStyle === true ) { + + this.domElement.style.width = width + 'px'; + this.domElement.style.height = height + 'px'; + + } + + this.setViewport( 0, 0, width, height ); + + if ( this._initialized ) this.backend.updateSize(); + + } + + /** + * Defines a manual sort function for the opaque render list. + * Pass `null` to use the default sort. + * + * @param {Function} method - The sort function. + */ + setOpaqueSort( method ) { + + this._opaqueSort = method; + + } + + /** + * Defines a manual sort function for the transparent render list. + * Pass `null` to use the default sort. + * + * @param {Function} method - The sort function. + */ + setTransparentSort( method ) { + + this._transparentSort = method; + + } + + /** + * Returns the scissor rectangle. + * + * @param {Vector4} target - The method writes the result in this target object. + * @return {Vector4} The scissor rectangle. + */ + getScissor( target ) { + + const scissor = this._scissor; + + target.x = scissor.x; + target.y = scissor.y; + target.width = scissor.width; + target.height = scissor.height; + + return target; + + } + + /** + * Defines the scissor rectangle. + * + * @param {number | Vector4} x - The horizontal coordinate for the lower left corner of the box in logical pixel unit. + * Instead of passing four arguments, the method also works with a single four-dimensional vector. + * @param {number} y - The vertical coordinate for the lower left corner of the box in logical pixel unit. + * @param {number} width - The width of the scissor box in logical pixel unit. + * @param {number} height - The height of the scissor box in logical pixel unit. + */ + setScissor( x, y, width, height ) { + + const scissor = this._scissor; + + if ( x.isVector4 ) { + + scissor.copy( x ); + + } else { + + scissor.set( x, y, width, height ); + + } + + } + + /** + * Returns the scissor test value. + * + * @return {boolean} Whether the scissor test should be enabled or not. + */ + getScissorTest() { + + return this._scissorTest; + + } + + /** + * Defines the scissor test. + * + * @param {boolean} boolean - Whether the scissor test should be enabled or not. + */ + setScissorTest( boolean ) { + + this._scissorTest = boolean; + + this.backend.setScissorTest( boolean ); + + } + + /** + * Returns the viewport definition. + * + * @param {Vector4} target - The method writes the result in this target object. + * @return {Vector4} The viewport definition. + */ + getViewport( target ) { + + return target.copy( this._viewport ); + + } + + /** + * Defines the viewport. + * + * @param {number | Vector4} x - The horizontal coordinate for the lower left corner of the viewport origin in logical pixel unit. + * @param {number} y - The vertical coordinate for the lower left corner of the viewport origin in logical pixel unit. + * @param {number} width - The width of the viewport in logical pixel unit. + * @param {number} height - The height of the viewport in logical pixel unit. + * @param {number} minDepth - The minimum depth value of the viewport. WebGPU only. + * @param {number} maxDepth - The maximum depth value of the viewport. WebGPU only. + */ + setViewport( x, y, width, height, minDepth = 0, maxDepth = 1 ) { + + const viewport = this._viewport; + + if ( x.isVector4 ) { + + viewport.copy( x ); + + } else { + + viewport.set( x, y, width, height ); + + } + + viewport.minDepth = minDepth; + viewport.maxDepth = maxDepth; + + } + + /** + * Returns the clear color. + * + * @param {Color} target - The method writes the result in this target object. + * @return {Color} The clear color. + */ + getClearColor( target ) { + + return target.copy( this._clearColor ); + + } + + /** + * Defines the clear color and optionally the clear alpha. + * + * @param {Color} color - The clear color. + * @param {number} [alpha=1] - The clear alpha. + */ + setClearColor( color, alpha = 1 ) { + + this._clearColor.set( color ); + this._clearColor.a = alpha; + + } + + /** + * Returns the clear alpha. + * + * @return {number} The clear alpha. + */ + getClearAlpha() { + + return this._clearColor.a; + + } + + /** + * Defines the clear alpha. + * + * @param {number} alpha - The clear alpha. + */ + setClearAlpha( alpha ) { + + this._clearColor.a = alpha; + + } + + /** + * Returns the clear depth. + * + * @return {number} The clear depth. + */ + getClearDepth() { + + return this._clearDepth; + + } + + /** + * Defines the clear depth. + * + * @param {number} depth - The clear depth. + */ + setClearDepth( depth ) { + + this._clearDepth = depth; + + } + + /** + * Returns the clear stencil. + * + * @return {number} The clear stencil. + */ + getClearStencil() { + + return this._clearStencil; + + } + + /** + * Defines the clear stencil. + * + * @param {number} stencil - The clear stencil. + */ + setClearStencil( stencil ) { + + this._clearStencil = stencil; + + } + + /** + * This method performs an occlusion query for the given 3D object. + * It returns `true` if the given 3D object is fully occluded by other + * 3D objects in the scene. + * + * @param {Object3D} object - The 3D object to test. + * @return {boolean} Whether the 3D object is fully occluded or not. + */ + isOccluded( object ) { + + const renderContext = this._currentRenderContext; + + return renderContext && this.backend.isOccluded( renderContext, object ); + + } + + /** + * Performs a manual clear operation. This method ignores `autoClear` properties. + * + * @param {boolean} [color=true] - Whether the color buffer should be cleared or not. + * @param {boolean} [depth=true] - Whether the depth buffer should be cleared or not. + * @param {boolean} [stencil=true] - Whether the stencil buffer should be cleared or not. + * @return {Promise} A Promise that resolves when the clear operation has been executed. + * Only returned when the renderer has not been initialized. + */ + clear( color = true, depth = true, stencil = true ) { + + if ( this._initialized === false ) { + + console.warn( 'THREE.Renderer: .clear() called before the backend is initialized. Try using .clearAsync() instead.' ); + + return this.clearAsync( color, depth, stencil ); + + } + + const renderTarget = this._renderTarget || this._getFrameBufferTarget(); + + let renderContext = null; + + if ( renderTarget !== null ) { + + this._textures.updateRenderTarget( renderTarget ); + + const renderTargetData = this._textures.get( renderTarget ); + + renderContext = this._renderContexts.getForClear( renderTarget ); + renderContext.textures = renderTargetData.textures; + renderContext.depthTexture = renderTargetData.depthTexture; + renderContext.width = renderTargetData.width; + renderContext.height = renderTargetData.height; + renderContext.renderTarget = renderTarget; + renderContext.depth = renderTarget.depthBuffer; + renderContext.stencil = renderTarget.stencilBuffer; + // #30329 + renderContext.clearColorValue = this.backend.getClearColor(); + renderContext.activeCubeFace = this.getActiveCubeFace(); + renderContext.activeMipmapLevel = this.getActiveMipmapLevel(); + + } + + this.backend.clear( color, depth, stencil, renderContext ); + + if ( renderTarget !== null && this._renderTarget === null ) { + + this._renderOutput( renderTarget ); + + } + + } + + /** + * Performs a manual clear operation of the color buffer. This method ignores `autoClear` properties. + * + * @return {Promise} A Promise that resolves when the clear operation has been executed. + * Only returned when the renderer has not been initialized. + */ + clearColor() { + + return this.clear( true, false, false ); + + } + + /** + * Performs a manual clear operation of the depth buffer. This method ignores `autoClear` properties. + * + * @return {Promise} A Promise that resolves when the clear operation has been executed. + * Only returned when the renderer has not been initialized. + */ + clearDepth() { + + return this.clear( false, true, false ); + + } + + /** + * Performs a manual clear operation of the stencil buffer. This method ignores `autoClear` properties. + * + * @return {Promise} A Promise that resolves when the clear operation has been executed. + * Only returned when the renderer has not been initialized. + */ + clearStencil() { + + return this.clear( false, false, true ); + + } + + /** + * Async version of {@link Renderer#clear}. + * + * @async + * @param {boolean} [color=true] - Whether the color buffer should be cleared or not. + * @param {boolean} [depth=true] - Whether the depth buffer should be cleared or not. + * @param {boolean} [stencil=true] - Whether the stencil buffer should be cleared or not. + * @return {Promise} A Promise that resolves when the clear operation has been executed. + */ + async clearAsync( color = true, depth = true, stencil = true ) { + + if ( this._initialized === false ) await this.init(); + + this.clear( color, depth, stencil ); + + } + + /** + * Async version of {@link Renderer#clearColor}. + * + * @async + * @return {Promise} A Promise that resolves when the clear operation has been executed. + */ + async clearColorAsync() { + + this.clearAsync( true, false, false ); + + } + + /** + * Async version of {@link Renderer#clearDepth}. + * + * @async + * @return {Promise} A Promise that resolves when the clear operation has been executed. + */ + async clearDepthAsync() { + + this.clearAsync( false, true, false ); + + } + + /** + * Async version of {@link Renderer#clearStencil}. + * + * @async + * @return {Promise} A Promise that resolves when the clear operation has been executed. + */ + async clearStencilAsync() { + + this.clearAsync( false, false, true ); + + } + + /** + * The current output tone mapping of the renderer. When a render target is set, + * the output tone mapping is always `NoToneMapping`. + * + * @type {number} + */ + get currentToneMapping() { + + return this.isOutputTarget ? this.toneMapping : NoToneMapping; + + } + + /** + * The current output color space of the renderer. When a render target is set, + * the output color space is always `LinearSRGBColorSpace`. + * + * @type {string} + */ + get currentColorSpace() { + + return this.isOutputTarget ? this.outputColorSpace : LinearSRGBColorSpace; + + } + + /** + * Returns `true` if the rendering settings are set to screen output. + * + * @returns {boolean} True if the current render target is the same of output render target or `null`, otherwise false. + */ + get isOutputTarget() { + + return this._renderTarget === this._outputRenderTarget || this._renderTarget === null; + + } + + /** + * Frees all internal resources of the renderer. Call this method if the renderer + * is no longer in use by your app. + */ + dispose() { + + this.info.dispose(); + this.backend.dispose(); + + this._animation.dispose(); + this._objects.dispose(); + this._pipelines.dispose(); + this._nodes.dispose(); + this._bindings.dispose(); + this._renderLists.dispose(); + this._renderContexts.dispose(); + this._textures.dispose(); + + if ( this._frameBufferTarget !== null ) this._frameBufferTarget.dispose(); + + Object.values( this.backend.timestampQueryPool ).forEach( queryPool => { + + if ( queryPool !== null ) queryPool.dispose(); + + } ); + + this.setRenderTarget( null ); + this.setAnimationLoop( null ); + + } + + /** + * Sets the given render target. Calling this method means the renderer does not + * target the default framebuffer (meaning the canvas) anymore but a custom framebuffer. + * Use `null` as the first argument to reset the state. + * + * @param {?RenderTarget} renderTarget - The render target to set. + * @param {number} [activeCubeFace=0] - The active cube face. + * @param {number} [activeMipmapLevel=0] - The active mipmap level. + */ + setRenderTarget( renderTarget, activeCubeFace = 0, activeMipmapLevel = 0 ) { + + this._renderTarget = renderTarget; + this._activeCubeFace = activeCubeFace; + this._activeMipmapLevel = activeMipmapLevel; + + } + + /** + * Returns the current render target. + * + * @return {?RenderTarget} The render target. Returns `null` if no render target is set. + */ + getRenderTarget() { + + return this._renderTarget; + + } + + /** + * Sets the output render target for the renderer. + * + * @param {Object} renderTarget - The render target to set as the output target. + */ + setOutputRenderTarget( renderTarget ) { + + this._outputRenderTarget = renderTarget; + + } + + /** + * Returns the current output target. + * + * @return {?RenderTarget} The current output render target. Returns `null` if no output target is set. + */ + getOutputRenderTarget() { + + return this._outputRenderTarget; + + } + + /** + * Resets the renderer to the initial state before WebXR started. + * + */ + _resetXRState() { + + this.backend.setXRTarget( null ); + this.setOutputRenderTarget( null ); + this.setRenderTarget( null ); + + this._frameBufferTarget.dispose(); + this._frameBufferTarget = null; + + } + + /** + * Callback for {@link Renderer#setRenderObjectFunction}. + * + * @callback renderObjectFunction + * @param {Object3D} object - The 3D object. + * @param {Scene} scene - The scene the 3D object belongs to. + * @param {Camera} camera - The camera the object should be rendered with. + * @param {BufferGeometry} geometry - The object's geometry. + * @param {Material} material - The object's material. + * @param {?Object} group - Only relevant for objects using multiple materials. This represents a group entry from the respective `BufferGeometry`. + * @param {LightsNode} lightsNode - The current lights node. + * @param {ClippingContext} clippingContext - The clipping context. + * @param {?string} [passId=null] - An optional ID for identifying the pass. + */ + + /** + * Sets the given render object function. Calling this method overwrites the default implementation + * which is {@link Renderer#renderObject}. Defining a custom function can be useful + * if you want to modify the way objects are rendered. For example you can define things like "every + * object that has material of a certain type should perform a pre-pass with a special overwrite material". + * The custom function must always call `renderObject()` in its implementation. + * + * Use `null` as the first argument to reset the state. + * + * @param {?renderObjectFunction} renderObjectFunction - The render object function. + */ + setRenderObjectFunction( renderObjectFunction ) { + + this._renderObjectFunction = renderObjectFunction; + + } + + /** + * Returns the current render object function. + * + * @return {?Function} The current render object function. Returns `null` if no function is set. + */ + getRenderObjectFunction() { + + return this._renderObjectFunction; + + } + + /** + * Execute a single or an array of compute nodes. This method can only be called + * if the renderer has been initialized. + * + * @param {Node|Array} computeNodes - The compute node(s). + * @return {Promise|undefined} A Promise that resolve when the compute has finished. Only returned when the renderer has not been initialized. + */ + compute( computeNodes ) { + + if ( this._isDeviceLost === true ) return; + + if ( this._initialized === false ) { + + console.warn( 'THREE.Renderer: .compute() called before the backend is initialized. Try using .computeAsync() instead.' ); + + return this.computeAsync( computeNodes ); + + } + + // + + const nodeFrame = this._nodes.nodeFrame; + + const previousRenderId = nodeFrame.renderId; + + // + + this.info.calls ++; + this.info.compute.calls ++; + this.info.compute.frameCalls ++; + + nodeFrame.renderId = this.info.calls; + + // + + const backend = this.backend; + const pipelines = this._pipelines; + const bindings = this._bindings; + const nodes = this._nodes; + + const computeList = Array.isArray( computeNodes ) ? computeNodes : [ computeNodes ]; + + if ( computeList[ 0 ] === undefined || computeList[ 0 ].isComputeNode !== true ) { + + throw new Error( 'THREE.Renderer: .compute() expects a ComputeNode.' ); + + } + + backend.beginCompute( computeNodes ); + + for ( const computeNode of computeList ) { + + // onInit + + if ( pipelines.has( computeNode ) === false ) { + + const dispose = () => { + + computeNode.removeEventListener( 'dispose', dispose ); + + pipelines.delete( computeNode ); + bindings.delete( computeNode ); + nodes.delete( computeNode ); + + }; + + computeNode.addEventListener( 'dispose', dispose ); + + // + + const onInitFn = computeNode.onInitFunction; + + if ( onInitFn !== null ) { + + onInitFn.call( computeNode, { renderer: this } ); + + } + + } + + nodes.updateForCompute( computeNode ); + bindings.updateForCompute( computeNode ); + + const computeBindings = bindings.getForCompute( computeNode ); + const computePipeline = pipelines.getForCompute( computeNode, computeBindings ); + + backend.compute( computeNodes, computeNode, computeBindings, computePipeline ); + + } + + backend.finishCompute( computeNodes ); + + // + + nodeFrame.renderId = previousRenderId; + + } + + /** + * Execute a single or an array of compute nodes. + * + * @async + * @param {Node|Array} computeNodes - The compute node(s). + * @return {Promise} A Promise that resolve when the compute has finished. + */ + async computeAsync( computeNodes ) { + + if ( this._initialized === false ) await this.init(); + + this.compute( computeNodes ); + + } + + /** + * Checks if the given feature is supported by the selected backend. + * + * @async + * @param {string} name - The feature's name. + * @return {Promise} A Promise that resolves with a bool that indicates whether the feature is supported or not. + */ + async hasFeatureAsync( name ) { + + if ( this._initialized === false ) await this.init(); + + return this.backend.hasFeature( name ); + + } + + async resolveTimestampsAsync( type = 'render' ) { + + if ( this._initialized === false ) await this.init(); + + return this.backend.resolveTimestampsAsync( type ); + + } + + /** + * Checks if the given feature is supported by the selected backend. If the + * renderer has not been initialized, this method always returns `false`. + * + * @param {string} name - The feature's name. + * @return {boolean} Whether the feature is supported or not. + */ + hasFeature( name ) { + + if ( this._initialized === false ) { + + console.warn( 'THREE.Renderer: .hasFeature() called before the backend is initialized. Try using .hasFeatureAsync() instead.' ); + + return false; + + } + + return this.backend.hasFeature( name ); + + } + + /** + * Returns `true` when the renderer has been initialized. + * + * @return {boolean} Whether the renderer has been initialized or not. + */ + hasInitialized() { + + return this._initialized; + + } + + /** + * Initializes the given textures. Useful for preloading a texture rather than waiting until first render + * (which can cause noticeable lags due to decode and GPU upload overhead). + * + * @async + * @param {Texture} texture - The texture. + * @return {Promise} A Promise that resolves when the texture has been initialized. + */ + async initTextureAsync( texture ) { + + if ( this._initialized === false ) await this.init(); + + this._textures.updateTexture( texture ); + + } + + /** + * Initializes the given texture. Useful for preloading a texture rather than waiting until first render + * (which can cause noticeable lags due to decode and GPU upload overhead). + * + * This method can only be used if the renderer has been initialized. + * + * @param {Texture} texture - The texture. + */ + initTexture( texture ) { + + if ( this._initialized === false ) { + + console.warn( 'THREE.Renderer: .initTexture() called before the backend is initialized. Try using .initTextureAsync() instead.' ); + + } + + this._textures.updateTexture( texture ); + + } + + /** + * Copies the current bound framebuffer into the given texture. + * + * @param {FramebufferTexture} framebufferTexture - The texture. + * @param {?Vector2|Vector4} [rectangle=null] - A two or four dimensional vector that defines the rectangular portion of the framebuffer that should be copied. + */ + copyFramebufferToTexture( framebufferTexture, rectangle = null ) { + + if ( rectangle !== null ) { + + if ( rectangle.isVector2 ) { + + rectangle = _vector4.set( rectangle.x, rectangle.y, framebufferTexture.image.width, framebufferTexture.image.height ).floor(); + + } else if ( rectangle.isVector4 ) { + + rectangle = _vector4.copy( rectangle ).floor(); + + } else { + + console.error( 'THREE.Renderer.copyFramebufferToTexture: Invalid rectangle.' ); + + return; + + } + + } else { + + rectangle = _vector4.set( 0, 0, framebufferTexture.image.width, framebufferTexture.image.height ); + + } + + // + + let renderContext = this._currentRenderContext; + let renderTarget; + + if ( renderContext !== null ) { + + renderTarget = renderContext.renderTarget; + + } else { + + renderTarget = this._renderTarget || this._getFrameBufferTarget(); + + if ( renderTarget !== null ) { + + this._textures.updateRenderTarget( renderTarget ); + + renderContext = this._textures.get( renderTarget ); + + } + + } + + // + + this._textures.updateTexture( framebufferTexture, { renderTarget } ); + + this.backend.copyFramebufferToTexture( framebufferTexture, renderContext, rectangle ); + + } + + /** + * Copies data of the given source texture into a destination texture. + * + * @param {Texture} srcTexture - The source texture. + * @param {Texture} dstTexture - The destination texture. + * @param {Box2|Box3} [srcRegion=null] - A bounding box which describes the source region. Can be two or three-dimensional. + * @param {Vector2|Vector3} [dstPosition=null] - A vector that represents the origin of the destination region. Can be two or three-dimensional. + * @param {number} [srcLevel=0] - The source mip level to copy from. + * @param {number} [dstLevel=0] - The destination mip level to copy to. + */ + copyTextureToTexture( srcTexture, dstTexture, srcRegion = null, dstPosition = null, srcLevel = 0, dstLevel = 0 ) { + + this._textures.updateTexture( srcTexture ); + this._textures.updateTexture( dstTexture ); + + this.backend.copyTextureToTexture( srcTexture, dstTexture, srcRegion, dstPosition, srcLevel, dstLevel ); + + } + + /** + * Reads pixel data from the given render target. + * + * @async + * @param {RenderTarget} renderTarget - The render target to read from. + * @param {number} x - The `x` coordinate of the copy region's origin. + * @param {number} y - The `y` coordinate of the copy region's origin. + * @param {number} width - The width of the copy region. + * @param {number} height - The height of the copy region. + * @param {number} [textureIndex=0] - The texture index of a MRT render target. + * @param {number} [faceIndex=0] - The active cube face index. + * @return {Promise} A Promise that resolves when the read has been finished. The resolve provides the read data as a typed array. + */ + async readRenderTargetPixelsAsync( renderTarget, x, y, width, height, textureIndex = 0, faceIndex = 0 ) { + + return this.backend.copyTextureToBuffer( renderTarget.textures[ textureIndex ], x, y, width, height, faceIndex ); + + } + + /** + * Analyzes the given 3D object's hierarchy and builds render lists from the + * processed hierarchy. + * + * @param {Object3D} object - The 3D object to process (usually a scene). + * @param {Camera} camera - The camera the object is rendered with. + * @param {number} groupOrder - The group order is derived from the `renderOrder` of groups and is used to group 3D objects within groups. + * @param {RenderList} renderList - The current render list. + * @param {ClippingContext} clippingContext - The current clipping context. + */ + _projectObject( object, camera, groupOrder, renderList, clippingContext ) { + + if ( object.visible === false ) return; + + const visible = object.layers.test( camera.layers ); + + if ( visible ) { + + if ( object.isGroup ) { + + groupOrder = object.renderOrder; + + if ( object.isClippingGroup && object.enabled ) clippingContext = clippingContext.getGroupContext( object ); + + } else if ( object.isLOD ) { + + if ( object.autoUpdate === true ) object.update( camera ); + + } else if ( object.isLight ) { + + renderList.pushLight( object ); + + } else if ( object.isSprite ) { + + const frustum = camera.isArrayCamera ? _frustumArray : _frustum; + + if ( ! object.frustumCulled || frustum.intersectsSprite( object, camera ) ) { + + if ( this.sortObjects === true ) { + + _vector4.setFromMatrixPosition( object.matrixWorld ).applyMatrix4( _projScreenMatrix ); + + } + + const { geometry, material } = object; + + if ( material.visible ) { + + renderList.push( object, geometry, material, groupOrder, _vector4.z, null, clippingContext ); + + } + + } + + } else if ( object.isLineLoop ) { + + console.error( 'THREE.Renderer: Objects of type THREE.LineLoop are not supported. Please use THREE.Line or THREE.LineSegments.' ); + + } else if ( object.isMesh || object.isLine || object.isPoints ) { + + const frustum = camera.isArrayCamera ? _frustumArray : _frustum; + + if ( ! object.frustumCulled || frustum.intersectsObject( object, camera ) ) { + + const { geometry, material } = object; + + if ( this.sortObjects === true ) { + + if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); + + _vector4 + .copy( geometry.boundingSphere.center ) + .applyMatrix4( object.matrixWorld ) + .applyMatrix4( _projScreenMatrix ); + + } + + if ( Array.isArray( material ) ) { + + const groups = geometry.groups; + + for ( let i = 0, l = groups.length; i < l; i ++ ) { + + const group = groups[ i ]; + const groupMaterial = material[ group.materialIndex ]; + + if ( groupMaterial && groupMaterial.visible ) { + + renderList.push( object, geometry, groupMaterial, groupOrder, _vector4.z, group, clippingContext ); + + } + + } + + } else if ( material.visible ) { + + renderList.push( object, geometry, material, groupOrder, _vector4.z, null, clippingContext ); + + } + + } + + } + + } + + if ( object.isBundleGroup === true && this.backend.beginBundle !== undefined ) { + + const baseRenderList = renderList; + + // replace render list + renderList = this._renderLists.get( object, camera ); + + renderList.begin(); + + baseRenderList.pushBundle( { + bundleGroup: object, + camera, + renderList, + } ); + + renderList.finish(); + + } + + const children = object.children; + + for ( let i = 0, l = children.length; i < l; i ++ ) { + + this._projectObject( children[ i ], camera, groupOrder, renderList, clippingContext ); + + } + + } + + /** + * Renders the given render bundles. + * + * @private + * @param {Array} bundles - Array with render bundle data. + * @param {Scene} sceneRef - The scene the render bundles belong to. + * @param {LightsNode} lightsNode - The current lights node. + */ + _renderBundles( bundles, sceneRef, lightsNode ) { + + for ( const bundle of bundles ) { + + this._renderBundle( bundle, sceneRef, lightsNode ); + + } + + } + + /** + * Renders the transparent objects from the given render lists. + * + * @private + * @param {Array} renderList - The transparent render list. + * @param {Array} doublePassList - The list of transparent objects which require a double pass (e.g. because of transmission). + * @param {Camera} camera - The camera the render list should be rendered with. + * @param {Scene} scene - The scene the render list belongs to. + * @param {LightsNode} lightsNode - The current lights node. + */ + _renderTransparents( renderList, doublePassList, camera, scene, lightsNode ) { + + if ( doublePassList.length > 0 ) { + + // render back side + + for ( const { material } of doublePassList ) { + + material.side = BackSide; + + } + + this._renderObjects( doublePassList, camera, scene, lightsNode, 'backSide' ); + + // render front side + + for ( const { material } of doublePassList ) { + + material.side = FrontSide; + + } + + this._renderObjects( renderList, camera, scene, lightsNode ); + + // restore + + for ( const { material } of doublePassList ) { + + material.side = DoubleSide; + + } + + } else { + + this._renderObjects( renderList, camera, scene, lightsNode ); + + } + + } + + /** + * Renders the objects from the given render list. + * + * @private + * @param {Array} renderList - The render list. + * @param {Camera} camera - The camera the render list should be rendered with. + * @param {Scene} scene - The scene the render list belongs to. + * @param {LightsNode} lightsNode - The current lights node. + * @param {?string} [passId=null] - An optional ID for identifying the pass. + */ + _renderObjects( renderList, camera, scene, lightsNode, passId = null ) { + + for ( let i = 0, il = renderList.length; i < il; i ++ ) { + + const { object, geometry, material, group, clippingContext } = renderList[ i ]; + + this._currentRenderObjectFunction( object, scene, camera, geometry, material, group, lightsNode, clippingContext, passId ); + + } + + } + + /** + * This method represents the default render object function that manages the render lifecycle + * of the object. + * + * @param {Object3D} object - The 3D object. + * @param {Scene} scene - The scene the 3D object belongs to. + * @param {Camera} camera - The camera the object should be rendered with. + * @param {BufferGeometry} geometry - The object's geometry. + * @param {Material} material - The object's material. + * @param {?Object} group - Only relevant for objects using multiple materials. This represents a group entry from the respective `BufferGeometry`. + * @param {LightsNode} lightsNode - The current lights node. + * @param {?ClippingContext} clippingContext - The clipping context. + * @param {?string} [passId=null] - An optional ID for identifying the pass. + */ + renderObject( object, scene, camera, geometry, material, group, lightsNode, clippingContext = null, passId = null ) { + + let overridePositionNode; + let overrideColorNode; + let overrideDepthNode; + + // + + object.onBeforeRender( this, scene, camera, geometry, material, group ); + + // + + if ( material.allowOverride === true && scene.overrideMaterial !== null ) { + + const overrideMaterial = scene.overrideMaterial; + + if ( material.positionNode && material.positionNode.isNode ) { + + overridePositionNode = overrideMaterial.positionNode; + overrideMaterial.positionNode = material.positionNode; + + } + + overrideMaterial.alphaTest = material.alphaTest; + overrideMaterial.alphaMap = material.alphaMap; + overrideMaterial.transparent = material.transparent || material.transmission > 0; + + if ( overrideMaterial.isShadowPassMaterial ) { + + overrideMaterial.side = material.shadowSide === null ? material.side : material.shadowSide; + + if ( material.depthNode && material.depthNode.isNode ) { + + overrideDepthNode = overrideMaterial.depthNode; + overrideMaterial.depthNode = material.depthNode; + + } + + if ( material.castShadowNode && material.castShadowNode.isNode ) { + + overrideColorNode = overrideMaterial.colorNode; + overrideMaterial.colorNode = material.castShadowNode; + + } + + if ( material.castShadowPositionNode && material.castShadowPositionNode.isNode ) { + + overridePositionNode = overrideMaterial.positionNode; + overrideMaterial.positionNode = material.castShadowPositionNode; + + } + + } + + material = overrideMaterial; + + } + + // + + if ( material.transparent === true && material.side === DoubleSide && material.forceSinglePass === false ) { + + material.side = BackSide; + this._handleObjectFunction( object, material, scene, camera, lightsNode, group, clippingContext, 'backSide' ); // create backSide pass id + + material.side = FrontSide; + this._handleObjectFunction( object, material, scene, camera, lightsNode, group, clippingContext, passId ); // use default pass id + + material.side = DoubleSide; + + } else { + + this._handleObjectFunction( object, material, scene, camera, lightsNode, group, clippingContext, passId ); + + } + + // + + if ( overridePositionNode !== undefined ) { + + scene.overrideMaterial.positionNode = overridePositionNode; + + } + + if ( overrideDepthNode !== undefined ) { + + scene.overrideMaterial.depthNode = overrideDepthNode; + + } + + if ( overrideColorNode !== undefined ) { + + scene.overrideMaterial.colorNode = overrideColorNode; + + } + + // + + object.onAfterRender( this, scene, camera, geometry, material, group ); + + } + + /** + * This method represents the default `_handleObjectFunction` implementation which creates + * a render object from the given data and performs the draw command with the selected backend. + * + * @private + * @param {Object3D} object - The 3D object. + * @param {Material} material - The object's material. + * @param {Scene} scene - The scene the 3D object belongs to. + * @param {Camera} camera - The camera the object should be rendered with. + * @param {LightsNode} lightsNode - The current lights node. + * @param {?{start: number, count: number}} group - Only relevant for objects using multiple materials. This represents a group entry from the respective `BufferGeometry`. + * @param {ClippingContext} clippingContext - The clipping context. + * @param {?string} [passId=null] - An optional ID for identifying the pass. + */ + _renderObjectDirect( object, material, scene, camera, lightsNode, group, clippingContext, passId ) { + + const renderObject = this._objects.get( object, material, scene, camera, lightsNode, this._currentRenderContext, clippingContext, passId ); + renderObject.drawRange = object.geometry.drawRange; + renderObject.group = group; + + // + + const needsRefresh = this._nodes.needsRefresh( renderObject ); + + if ( needsRefresh ) { + + this._nodes.updateBefore( renderObject ); + + this._geometries.updateForRender( renderObject ); + + this._nodes.updateForRender( renderObject ); + this._bindings.updateForRender( renderObject ); + + } + + this._pipelines.updateForRender( renderObject ); + + // + + if ( this._currentRenderBundle !== null ) { + + const renderBundleData = this.backend.get( this._currentRenderBundle ); + + renderBundleData.renderObjects.push( renderObject ); + + renderObject.bundle = this._currentRenderBundle.bundleGroup; + + } + + this.backend.draw( renderObject, this.info ); + + if ( needsRefresh ) this._nodes.updateAfter( renderObject ); + + } + + /** + * A different implementation for `_handleObjectFunction` which only makes sure the object is ready for rendering. + * Used in `compileAsync()`. + * + * @private + * @param {Object3D} object - The 3D object. + * @param {Material} material - The object's material. + * @param {Scene} scene - The scene the 3D object belongs to. + * @param {Camera} camera - The camera the object should be rendered with. + * @param {LightsNode} lightsNode - The current lights node. + * @param {?{start: number, count: number}} group - Only relevant for objects using multiple materials. This represents a group entry from the respective `BufferGeometry`. + * @param {ClippingContext} clippingContext - The clipping context. + * @param {?string} [passId=null] - An optional ID for identifying the pass. + */ + _createObjectPipeline( object, material, scene, camera, lightsNode, group, clippingContext, passId ) { + + const renderObject = this._objects.get( object, material, scene, camera, lightsNode, this._currentRenderContext, clippingContext, passId ); + renderObject.drawRange = object.geometry.drawRange; + renderObject.group = group; + + // + + this._nodes.updateBefore( renderObject ); + + this._geometries.updateForRender( renderObject ); + + this._nodes.updateForRender( renderObject ); + this._bindings.updateForRender( renderObject ); + + this._pipelines.getForRender( renderObject, this._compilationPromises ); + + this._nodes.updateAfter( renderObject ); + + } + + /** + * Alias for `compileAsync()`. + * + * @method + * @param {Object3D} scene - The scene or 3D object to precompile. + * @param {Camera} camera - The camera that is used to render the scene. + * @param {Scene} targetScene - If the first argument is a 3D object, this parameter must represent the scene the 3D object is going to be added. + * @return {function(Object3D, Camera, ?Scene): Promise|undefined} A Promise that resolves when the compile has been finished. + */ + get compile() { + + return this.compileAsync; + + } + +} + +/** + * A binding represents the connection between a resource (like a texture, sampler + * or uniform buffer) and the resource definition in a shader stage. + * + * This module is an abstract base class for all concrete bindings types. + * + * @abstract + * @private + */ +class Binding { + + /** + * Constructs a new binding. + * + * @param {string} [name=''] - The binding's name. + */ + constructor( name = '' ) { + + /** + * The binding's name. + * + * @type {string} + */ + this.name = name; + + /** + * A bitmask that defines in what shader stages the + * binding's resource is accessible. + * + * @type {number} + */ + this.visibility = 0; + + } + + /** + * Makes sure binding's resource is visible for the given shader stage. + * + * @param {number} visibility - The shader stage. + */ + setVisibility( visibility ) { + + this.visibility |= visibility; + + } + + /** + * Clones the binding. + * + * @return {Binding} The cloned binding. + */ + clone() { + + return Object.assign( new this.constructor(), this ); + + } + +} + +/** + * This function is usually called with the length in bytes of an array buffer. + * It returns an padded value which ensure chunk size alignment according to STD140 layout. + * + * @function + * @param {number} floatLength - The buffer length. + * @return {number} The padded length. + */ +function getFloatLength( floatLength ) { + + // ensure chunk size alignment (STD140 layout) + + return floatLength + ( ( GPU_CHUNK_BYTES - ( floatLength % GPU_CHUNK_BYTES ) ) % GPU_CHUNK_BYTES ); + +} + +/** + * Represents a buffer binding type. + * + * @private + * @abstract + * @augments Binding + */ +class Buffer extends Binding { + + /** + * Constructs a new buffer. + * + * @param {string} name - The buffer's name. + * @param {TypedArray} [buffer=null] - The buffer. + */ + constructor( name, buffer = null ) { + + super( name ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isBuffer = true; + + /** + * The bytes per element. + * + * @type {number} + */ + this.bytesPerElement = Float32Array.BYTES_PER_ELEMENT; + + /** + * A reference to the internal buffer. + * + * @private + * @type {TypedArray} + */ + this._buffer = buffer; + + } + + /** + * The buffer's byte length. + * + * @type {number} + * @readonly + */ + get byteLength() { + + return getFloatLength( this._buffer.byteLength ); + + } + + /** + * A reference to the internal buffer. + * + * @type {Float32Array} + * @readonly + */ + get buffer() { + + return this._buffer; + + } + + /** + * Updates the binding. + * + * @return {boolean} Whether the buffer has been updated and must be + * uploaded to the GPU. + */ + update() { + + return true; + + } + +} + +/** + * Represents a uniform buffer binding type. + * + * @private + * @augments Buffer + */ +class UniformBuffer extends Buffer { + + /** + * Constructs a new uniform buffer. + * + * @param {string} name - The buffer's name. + * @param {TypedArray} [buffer=null] - The buffer. + */ + constructor( name, buffer = null ) { + + super( name, buffer ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isUniformBuffer = true; + + } + +} + +let _id$4 = 0; + +/** + * A special form of uniform buffer binding type. + * It's buffer value is managed by a node object. + * + * @private + * @augments UniformBuffer + */ +class NodeUniformBuffer extends UniformBuffer { + + /** + * Constructs a new node-based uniform buffer. + * + * @param {BufferNode} nodeUniform - The uniform buffer node. + * @param {UniformGroupNode} groupNode - The uniform group node. + */ + constructor( nodeUniform, groupNode ) { + + super( 'UniformBuffer_' + _id$4 ++, nodeUniform ? nodeUniform.value : null ); + + /** + * The uniform buffer node. + * + * @type {BufferNode} + */ + this.nodeUniform = nodeUniform; + + /** + * The uniform group node. + * + * @type {UniformGroupNode} + */ + this.groupNode = groupNode; + + } + + /** + * The uniform buffer. + * + * @type {Float32Array} + */ + get buffer() { + + return this.nodeUniform.value; + + } + +} + +/** + * This class represents a uniform buffer binding but with + * an API that allows to maintain individual uniform objects. + * + * @private + * @augments UniformBuffer + */ +class UniformsGroup extends UniformBuffer { + + /** + * Constructs a new uniforms group. + * + * @param {string} name - The group's name. + */ + constructor( name ) { + + super( name ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isUniformsGroup = true; + + /** + * An array with the raw uniform values. + * + * @private + * @type {?Array} + * @default null + */ + this._values = null; + + /** + * An array of uniform objects. + * + * The order of uniforms in this array must match the order of uniforms in the shader. + * + * @type {Array} + */ + this.uniforms = []; + + } + + /** + * Adds a uniform to this group. + * + * @param {Uniform} uniform - The uniform to add. + * @return {UniformsGroup} A reference to this group. + */ + addUniform( uniform ) { + + this.uniforms.push( uniform ); + + return this; + + } + + /** + * Removes a uniform from this group. + * + * @param {Uniform} uniform - The uniform to remove. + * @return {UniformsGroup} A reference to this group. + */ + removeUniform( uniform ) { + + const index = this.uniforms.indexOf( uniform ); + + if ( index !== -1 ) { + + this.uniforms.splice( index, 1 ); + + } + + return this; + + } + + /** + * An array with the raw uniform values. + * + * @type {Array} + */ + get values() { + + if ( this._values === null ) { + + this._values = Array.from( this.buffer ); + + } + + return this._values; + + } + + /** + * A Float32 array buffer with the uniform values. + * + * @type {Float32Array} + */ + get buffer() { + + let buffer = this._buffer; + + if ( buffer === null ) { + + const byteLength = this.byteLength; + + buffer = new Float32Array( new ArrayBuffer( byteLength ) ); + + this._buffer = buffer; + + } + + return buffer; + + } + + /** + * The byte length of the buffer with correct buffer alignment. + * + * @type {number} + */ + get byteLength() { + + const bytesPerElement = this.bytesPerElement; + + let offset = 0; // global buffer offset in bytes + + for ( let i = 0, l = this.uniforms.length; i < l; i ++ ) { + + const uniform = this.uniforms[ i ]; + + const boundary = uniform.boundary; + const itemSize = uniform.itemSize * bytesPerElement; // size of the uniform in bytes + + const chunkOffset = offset % GPU_CHUNK_BYTES; // offset in the current chunk + const chunkPadding = chunkOffset % boundary; // required padding to match boundary + const chunkStart = chunkOffset + chunkPadding; // start position in the current chunk for the data + + offset += chunkPadding; + + // Check for chunk overflow + if ( chunkStart !== 0 && ( GPU_CHUNK_BYTES - chunkStart ) < itemSize ) { + + // Add padding to the end of the chunk + offset += ( GPU_CHUNK_BYTES - chunkStart ); + + } + + uniform.offset = offset / bytesPerElement; + + offset += itemSize; + + } + + return Math.ceil( offset / GPU_CHUNK_BYTES ) * GPU_CHUNK_BYTES; + + } + + /** + * Updates this group by updating each uniform object of + * the internal uniform list. The uniform objects check if their + * values has actually changed so this method only returns + * `true` if there is a real value change. + * + * @return {boolean} Whether the uniforms have been updated and + * must be uploaded to the GPU. + */ + update() { + + let updated = false; + + for ( const uniform of this.uniforms ) { + + if ( this.updateByType( uniform ) === true ) { + + updated = true; + + } + + } + + return updated; + + } + + /** + * Updates a given uniform by calling an update method matching + * the uniforms type. + * + * @param {Uniform} uniform - The uniform to update. + * @return {boolean} Whether the uniform has been updated or not. + */ + updateByType( uniform ) { + + if ( uniform.isNumberUniform ) return this.updateNumber( uniform ); + if ( uniform.isVector2Uniform ) return this.updateVector2( uniform ); + if ( uniform.isVector3Uniform ) return this.updateVector3( uniform ); + if ( uniform.isVector4Uniform ) return this.updateVector4( uniform ); + if ( uniform.isColorUniform ) return this.updateColor( uniform ); + if ( uniform.isMatrix3Uniform ) return this.updateMatrix3( uniform ); + if ( uniform.isMatrix4Uniform ) return this.updateMatrix4( uniform ); + + console.error( 'THREE.WebGPUUniformsGroup: Unsupported uniform type.', uniform ); + + } + + /** + * Updates a given Number uniform. + * + * @param {NumberUniform} uniform - The Number uniform. + * @return {boolean} Whether the uniform has been updated or not. + */ + updateNumber( uniform ) { + + let updated = false; + + const a = this.values; + const v = uniform.getValue(); + const offset = uniform.offset; + const type = uniform.getType(); + + if ( a[ offset ] !== v ) { + + const b = this._getBufferForType( type ); + + b[ offset ] = a[ offset ] = v; + updated = true; + + } + + return updated; + + } + + /** + * Updates a given Vector2 uniform. + * + * @param {Vector2Uniform} uniform - The Vector2 uniform. + * @return {boolean} Whether the uniform has been updated or not. + */ + updateVector2( uniform ) { + + let updated = false; + + const a = this.values; + const v = uniform.getValue(); + const offset = uniform.offset; + const type = uniform.getType(); + + if ( a[ offset + 0 ] !== v.x || a[ offset + 1 ] !== v.y ) { + + const b = this._getBufferForType( type ); + + b[ offset + 0 ] = a[ offset + 0 ] = v.x; + b[ offset + 1 ] = a[ offset + 1 ] = v.y; + + updated = true; + + } + + return updated; + + } + + /** + * Updates a given Vector3 uniform. + * + * @param {Vector3Uniform} uniform - The Vector3 uniform. + * @return {boolean} Whether the uniform has been updated or not. + */ + updateVector3( uniform ) { + + let updated = false; + + const a = this.values; + const v = uniform.getValue(); + const offset = uniform.offset; + const type = uniform.getType(); + + if ( a[ offset + 0 ] !== v.x || a[ offset + 1 ] !== v.y || a[ offset + 2 ] !== v.z ) { + + const b = this._getBufferForType( type ); + + b[ offset + 0 ] = a[ offset + 0 ] = v.x; + b[ offset + 1 ] = a[ offset + 1 ] = v.y; + b[ offset + 2 ] = a[ offset + 2 ] = v.z; + + updated = true; + + } + + return updated; + + } + + /** + * Updates a given Vector4 uniform. + * + * @param {Vector4Uniform} uniform - The Vector4 uniform. + * @return {boolean} Whether the uniform has been updated or not. + */ + updateVector4( uniform ) { + + let updated = false; + + const a = this.values; + const v = uniform.getValue(); + const offset = uniform.offset; + const type = uniform.getType(); + + if ( a[ offset + 0 ] !== v.x || a[ offset + 1 ] !== v.y || a[ offset + 2 ] !== v.z || a[ offset + 4 ] !== v.w ) { + + const b = this._getBufferForType( type ); + + b[ offset + 0 ] = a[ offset + 0 ] = v.x; + b[ offset + 1 ] = a[ offset + 1 ] = v.y; + b[ offset + 2 ] = a[ offset + 2 ] = v.z; + b[ offset + 3 ] = a[ offset + 3 ] = v.w; + + updated = true; + + } + + return updated; + + } + + /** + * Updates a given Color uniform. + * + * @param {ColorUniform} uniform - The Color uniform. + * @return {boolean} Whether the uniform has been updated or not. + */ + updateColor( uniform ) { + + let updated = false; + + const a = this.values; + const c = uniform.getValue(); + const offset = uniform.offset; + + if ( a[ offset + 0 ] !== c.r || a[ offset + 1 ] !== c.g || a[ offset + 2 ] !== c.b ) { + + const b = this.buffer; + + b[ offset + 0 ] = a[ offset + 0 ] = c.r; + b[ offset + 1 ] = a[ offset + 1 ] = c.g; + b[ offset + 2 ] = a[ offset + 2 ] = c.b; + + updated = true; + + } + + return updated; + + } + + /** + * Updates a given Matrix3 uniform. + * + * @param {Matrix3Uniform} uniform - The Matrix3 uniform. + * @return {boolean} Whether the uniform has been updated or not. + */ + updateMatrix3( uniform ) { + + let updated = false; + + const a = this.values; + const e = uniform.getValue().elements; + const offset = uniform.offset; + + if ( a[ offset + 0 ] !== e[ 0 ] || a[ offset + 1 ] !== e[ 1 ] || a[ offset + 2 ] !== e[ 2 ] || + a[ offset + 4 ] !== e[ 3 ] || a[ offset + 5 ] !== e[ 4 ] || a[ offset + 6 ] !== e[ 5 ] || + a[ offset + 8 ] !== e[ 6 ] || a[ offset + 9 ] !== e[ 7 ] || a[ offset + 10 ] !== e[ 8 ] ) { + + const b = this.buffer; + + b[ offset + 0 ] = a[ offset + 0 ] = e[ 0 ]; + b[ offset + 1 ] = a[ offset + 1 ] = e[ 1 ]; + b[ offset + 2 ] = a[ offset + 2 ] = e[ 2 ]; + b[ offset + 4 ] = a[ offset + 4 ] = e[ 3 ]; + b[ offset + 5 ] = a[ offset + 5 ] = e[ 4 ]; + b[ offset + 6 ] = a[ offset + 6 ] = e[ 5 ]; + b[ offset + 8 ] = a[ offset + 8 ] = e[ 6 ]; + b[ offset + 9 ] = a[ offset + 9 ] = e[ 7 ]; + b[ offset + 10 ] = a[ offset + 10 ] = e[ 8 ]; + + updated = true; + + } + + return updated; + + } + + /** + * Updates a given Matrix4 uniform. + * + * @param {Matrix4Uniform} uniform - The Matrix4 uniform. + * @return {boolean} Whether the uniform has been updated or not. + */ + updateMatrix4( uniform ) { + + let updated = false; + + const a = this.values; + const e = uniform.getValue().elements; + const offset = uniform.offset; + + if ( arraysEqual( a, e, offset ) === false ) { + + const b = this.buffer; + b.set( e, offset ); + setArray( a, e, offset ); + updated = true; + + } + + return updated; + + } + + /** + * Returns a typed array that matches the given data type. + * + * @param {string} type - The data type. + * @return {TypedArray} The typed array. + */ + _getBufferForType( type ) { + + if ( type === 'int' || type === 'ivec2' || type === 'ivec3' || type === 'ivec4' ) return new Int32Array( this.buffer.buffer ); + if ( type === 'uint' || type === 'uvec2' || type === 'uvec3' || type === 'uvec4' ) return new Uint32Array( this.buffer.buffer ); + return this.buffer; + + } + +} + +/** + * Sets the values of the second array to the first array. + * + * @private + * @param {TypedArray} a - The first array. + * @param {TypedArray} b - The second array. + * @param {number} offset - An index offset for the first array. + */ +function setArray( a, b, offset ) { + + for ( let i = 0, l = b.length; i < l; i ++ ) { + + a[ offset + i ] = b[ i ]; + + } + +} + +/** + * Returns `true` if the given arrays are equal. + * + * @private + * @param {TypedArray} a - The first array. + * @param {TypedArray} b - The second array. + * @param {number} offset - An index offset for the first array. + * @return {boolean} Whether the given arrays are equal or not. + */ +function arraysEqual( a, b, offset ) { + + for ( let i = 0, l = b.length; i < l; i ++ ) { + + if ( a[ offset + i ] !== b[ i ] ) return false; + + } + + return true; + +} + +let _id$3 = 0; + +/** + * A special form of uniforms group that represents + * the individual uniforms as node-based uniforms. + * + * @private + * @augments UniformsGroup + */ +class NodeUniformsGroup extends UniformsGroup { + + /** + * Constructs a new node-based uniforms group. + * + * @param {string} name - The group's name. + * @param {UniformGroupNode} groupNode - The uniform group node. + */ + constructor( name, groupNode ) { + + super( name ); + + /** + * The group's ID. + * + * @type {number} + */ + this.id = _id$3 ++; + + /** + * The uniform group node. + * + * @type {UniformGroupNode} + */ + this.groupNode = groupNode; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isNodeUniformsGroup = true; + + } + +} + +let _id$2 = 0; + +/** + * Represents a sampled texture binding type. + * + * @private + * @augments Binding + */ +class SampledTexture extends Binding { + + /** + * Constructs a new sampled texture. + * + * @param {string} name - The sampled texture's name. + * @param {?Texture} texture - The texture this binding is referring to. + */ + constructor( name, texture ) { + + super( name ); + + /** + * This identifier. + * + * @type {number} + */ + this.id = _id$2 ++; + + /** + * The texture this binding is referring to. + * + * @type {?Texture} + */ + this.texture = texture; + + /** + * The binding's version. + * + * @type {number} + */ + this.version = texture ? texture.version : 0; + + /** + * Whether the texture is a storage texture or not. + * + * @type {boolean} + * @default false + */ + this.store = false; + + /** + * The binding's generation which is an additional version + * qualifier. + * + * @type {?number} + * @default null + */ + this.generation = null; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSampledTexture = true; + + } + + /** + * Returns `true` whether this binding requires an update for the + * given generation. + * + * @param {number} generation - The generation. + * @return {boolean} Whether an update is required or not. + */ + needsBindingsUpdate( generation ) { + + const { texture } = this; + + if ( generation !== this.generation ) { + + this.generation = generation; + + return true; + + } + + return texture.isVideoTexture; + + } + + /** + * Updates the binding. + * + * @return {boolean} Whether the texture has been updated and must be + * uploaded to the GPU. + */ + update() { + + const { texture, version } = this; + + if ( version !== texture.version ) { + + this.version = texture.version; + + return true; + + } + + return false; + + } + +} + +/** + * A special form of sampled texture binding type. + * It's texture value is managed by a node object. + * + * @private + * @augments SampledTexture + */ +class NodeSampledTexture extends SampledTexture { + + /** + * Constructs a new node-based sampled texture. + * + * @param {string} name - The textures's name. + * @param {TextureNode} textureNode - The texture node. + * @param {UniformGroupNode} groupNode - The uniform group node. + * @param {?string} [access=null] - The access type. + */ + constructor( name, textureNode, groupNode, access = null ) { + + super( name, textureNode ? textureNode.value : null ); + + /** + * The texture node. + * + * @type {TextureNode} + */ + this.textureNode = textureNode; + + /** + * The uniform group node. + * + * @type {UniformGroupNode} + */ + this.groupNode = groupNode; + + /** + * The access type. + * + * @type {?string} + * @default null + */ + this.access = access; + + } + + /** + * Overwrites the default to additionally check if the node value has changed. + * + * @param {number} generation - The generation. + * @return {boolean} Whether an update is required or not. + */ + needsBindingsUpdate( generation ) { + + return this.textureNode.value !== this.texture || super.needsBindingsUpdate( generation ); + + } + + /** + * Updates the binding. + * + * @return {boolean} Whether the texture has been updated and must be + * uploaded to the GPU. + */ + update() { + + const { textureNode } = this; + + if ( this.texture !== textureNode.value ) { + + this.texture = textureNode.value; + + return true; + + } + + return super.update(); + + } + +} + +/** + * A special form of sampled cube texture binding type. + * It's texture value is managed by a node object. + * + * @private + * @augments NodeSampledTexture + */ +class NodeSampledCubeTexture extends NodeSampledTexture { + + /** + * Constructs a new node-based sampled cube texture. + * + * @param {string} name - The textures's name. + * @param {TextureNode} textureNode - The texture node. + * @param {UniformGroupNode} groupNode - The uniform group node. + * @param {?string} [access=null] - The access type. + */ + constructor( name, textureNode, groupNode, access = null ) { + + super( name, textureNode, groupNode, access ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSampledCubeTexture = true; + + } + +} + +/** + * A special form of sampled 3D texture binding type. + * It's texture value is managed by a node object. + * + * @private + * @augments NodeSampledTexture + */ +class NodeSampledTexture3D extends NodeSampledTexture { + + /** + * Constructs a new node-based sampled 3D texture. + * + * @param {string} name - The textures's name. + * @param {TextureNode} textureNode - The texture node. + * @param {UniformGroupNode} groupNode - The uniform group node. + * @param {?string} [access=null] - The access type. + */ + constructor( name, textureNode, groupNode, access = null ) { + + super( name, textureNode, groupNode, access ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSampledTexture3D = true; + + } + +} + +const glslMethods = { + textureDimensions: 'textureSize', + equals: 'equal' +}; + +const precisionLib = { + low: 'lowp', + medium: 'mediump', + high: 'highp' +}; + +const supports$1 = { + swizzleAssign: true, + storageBuffer: false +}; + +const interpolationTypeMap = { + perspective: 'smooth', + linear: 'noperspective' +}; + +const interpolationModeMap = { + 'centroid': 'centroid' +}; + +const defaultPrecisions = ` +precision highp float; +precision highp int; +precision highp sampler2D; +precision highp sampler3D; +precision highp samplerCube; +precision highp sampler2DArray; + +precision highp usampler2D; +precision highp usampler3D; +precision highp usamplerCube; +precision highp usampler2DArray; + +precision highp isampler2D; +precision highp isampler3D; +precision highp isamplerCube; +precision highp isampler2DArray; + +precision lowp sampler2DShadow; +precision lowp sampler2DArrayShadow; +precision lowp samplerCubeShadow; +`; + +/** + * A node builder targeting GLSL. + * + * This module generates GLSL shader code from node materials and also + * generates the respective bindings and vertex buffer definitions. These + * data are later used by the renderer to create render and compute pipelines + * for render objects. + * + * @augments NodeBuilder + */ +class GLSLNodeBuilder extends NodeBuilder { + + /** + * Constructs a new GLSL node builder renderer. + * + * @param {Object3D} object - The 3D object. + * @param {Renderer} renderer - The renderer. + */ + constructor( object, renderer ) { + + super( object, renderer, new GLSLNodeParser() ); + + /** + * A dictionary holds for each shader stage ('vertex', 'fragment', 'compute') + * another dictionary which manages UBOs per group ('render','frame','object'). + * + * @type {Object>} + */ + this.uniformGroups = {}; + + /** + * An array that holds objects defining the varying and attribute data in + * context of Transform Feedback. + * + * @type {Array>} + */ + this.transforms = []; + + /** + * A dictionary that holds for each shader stage a Map of used extensions. + * + * @type {Object>} + */ + this.extensions = {}; + + /** + * A dictionary that holds for each shader stage an Array of used builtins. + * + * @type {Object>} + */ + this.builtins = { vertex: [], fragment: [], compute: [] }; + + } + + /** + * Checks if the given texture requires a manual conversion to the working color space. + * + * @param {Texture} texture - The texture to check. + * @return {boolean} Whether the given texture requires a conversion to working color space or not. + */ + needsToWorkingColorSpace( texture ) { + + return texture.isVideoTexture === true && texture.colorSpace !== NoColorSpace; + + } + + /** + * Returns the native shader method name for a given generic name. + * + * @param {string} method - The method name to resolve. + * @return {string} The resolved GLSL method name. + */ + getMethod( method ) { + + return glslMethods[ method ] || method; + + } + + /** + * Returns the output struct name. Not relevant for GLSL. + * + * @return {string} + */ + getOutputStructName() { + + return ''; + + } + + /** + * Builds the given shader node. + * + * @param {ShaderNodeInternal} shaderNode - The shader node. + * @return {string} The GLSL function code. + */ + buildFunctionCode( shaderNode ) { + + const layout = shaderNode.layout; + const flowData = this.flowShaderNode( shaderNode ); + + const parameters = []; + + for ( const input of layout.inputs ) { + + parameters.push( this.getType( input.type ) + ' ' + input.name ); + + } + + // + + const code = `${ this.getType( layout.type ) } ${ layout.name }( ${ parameters.join( ', ' ) } ) { + + ${ flowData.vars } + +${ flowData.code } + return ${ flowData.result }; + +}`; + + // + + return code; + + } + + /** + * Setups the Pixel Buffer Object (PBO) for the given storage + * buffer node. + * + * @param {StorageBufferNode} storageBufferNode - The storage buffer node. + */ + setupPBO( storageBufferNode ) { + + const attribute = storageBufferNode.value; + + if ( attribute.pbo === undefined ) { + + const originalArray = attribute.array; + const numElements = attribute.count * attribute.itemSize; + + const { itemSize } = attribute; + + const isInteger = attribute.array.constructor.name.toLowerCase().includes( 'int' ); + + let format = isInteger ? RedIntegerFormat : RedFormat; + + if ( itemSize === 2 ) { + + format = isInteger ? RGIntegerFormat : RGFormat; + + } else if ( itemSize === 3 ) { + + format = isInteger ? RGBIntegerFormat : RGBFormat; + + } else if ( itemSize === 4 ) { + + format = isInteger ? RGBAIntegerFormat : RGBAFormat; + + } + + const typeMap = { + Float32Array: FloatType, + Uint8Array: UnsignedByteType, + Uint16Array: UnsignedShortType, + Uint32Array: UnsignedIntType, + Int8Array: ByteType, + Int16Array: ShortType, + Int32Array: IntType, + Uint8ClampedArray: UnsignedByteType, + }; + + const width = Math.pow( 2, Math.ceil( Math.log2( Math.sqrt( numElements / itemSize ) ) ) ); + let height = Math.ceil( ( numElements / itemSize ) / width ); + if ( width * height * itemSize < numElements ) height ++; // Ensure enough space + + const newSize = width * height * itemSize; + + const newArray = new originalArray.constructor( newSize ); + + newArray.set( originalArray, 0 ); + + attribute.array = newArray; + + const pboTexture = new DataTexture( attribute.array, width, height, format, typeMap[ attribute.array.constructor.name ] || FloatType ); + pboTexture.needsUpdate = true; + pboTexture.isPBOTexture = true; + + const pbo = new TextureNode( pboTexture, null, null ); + pbo.setPrecision( 'high' ); + + attribute.pboNode = pbo; + attribute.pbo = pbo.value; + + this.getUniformFromNode( attribute.pboNode, 'texture', this.shaderStage, this.context.label ); + + } + + } + + /** + * Returns a GLSL snippet that represents the property name of the given node. + * + * @param {Node} node - The node. + * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for. + * @return {string} The property name. + */ + getPropertyName( node, shaderStage = this.shaderStage ) { + + if ( node.isNodeUniform && node.node.isTextureNode !== true && node.node.isBufferNode !== true ) { + + return shaderStage.charAt( 0 ) + '_' + node.name; + + } + + return super.getPropertyName( node, shaderStage ); + + } + + /** + * Setups the Pixel Buffer Object (PBO) for the given storage + * buffer node. + * + * @param {StorageArrayElementNode} storageArrayElementNode - The storage array element node. + * @return {string} The property name. + */ + generatePBO( storageArrayElementNode ) { + + const { node, indexNode } = storageArrayElementNode; + const attribute = node.value; + + if ( this.renderer.backend.has( attribute ) ) { + + const attributeData = this.renderer.backend.get( attribute ); + attributeData.pbo = attribute.pbo; + + } + + const nodeUniform = this.getUniformFromNode( attribute.pboNode, 'texture', this.shaderStage, this.context.label ); + const textureName = this.getPropertyName( nodeUniform ); + + this.increaseUsage( indexNode ); // force cache generate to be used as index in x,y + const indexSnippet = indexNode.build( this, 'uint' ); + + const elementNodeData = this.getDataFromNode( storageArrayElementNode ); + + let propertyName = elementNodeData.propertyName; + + if ( propertyName === undefined ) { + + // property element + + const nodeVar = this.getVarFromNode( storageArrayElementNode ); + + propertyName = this.getPropertyName( nodeVar ); + + // property size + + const bufferNodeData = this.getDataFromNode( node ); + + let propertySizeName = bufferNodeData.propertySizeName; + + if ( propertySizeName === undefined ) { + + propertySizeName = propertyName + 'Size'; + + this.getVarFromNode( node, propertySizeName, 'uint' ); + + this.addLineFlowCode( `${ propertySizeName } = uint( textureSize( ${ textureName }, 0 ).x )`, storageArrayElementNode ); + + bufferNodeData.propertySizeName = propertySizeName; + + } + + // + + const { itemSize } = attribute; + + const channel = '.' + vectorComponents.join( '' ).slice( 0, itemSize ); + const uvSnippet = `ivec2(${indexSnippet} % ${ propertySizeName }, ${indexSnippet} / ${ propertySizeName })`; + + const snippet = this.generateTextureLoad( null, textureName, uvSnippet, null, '0' ); + + // + + + let prefix = 'vec4'; + + if ( attribute.pbo.type === UnsignedIntType ) { + + prefix = 'uvec4'; + + } else if ( attribute.pbo.type === IntType ) { + + prefix = 'ivec4'; + + } + + this.addLineFlowCode( `${ propertyName } = ${prefix}(${ snippet })${channel}`, storageArrayElementNode ); + + elementNodeData.propertyName = propertyName; + + } + + return propertyName; + + } + + /** + * Generates the GLSL snippet that reads a single texel from a texture without sampling or filtering. + * + * @param {Texture} texture - The texture. + * @param {string} textureProperty - The name of the texture uniform in the shader. + * @param {string} uvIndexSnippet - A GLSL snippet that represents texture coordinates used for sampling. + * @param {?string} depthSnippet - A GLSL snippet that represents the 0-based texture array index to sample. + * @param {string} [levelSnippet='0u'] - A GLSL snippet that represents the mip level, with level 0 containing a full size version of the texture. + * @return {string} The GLSL snippet. + */ + generateTextureLoad( texture, textureProperty, uvIndexSnippet, depthSnippet, levelSnippet = '0' ) { + + if ( depthSnippet ) { + + return `texelFetch( ${ textureProperty }, ivec3( ${ uvIndexSnippet }, ${ depthSnippet } ), ${ levelSnippet } )`; + + } else { + + return `texelFetch( ${ textureProperty }, ${ uvIndexSnippet }, ${ levelSnippet } )`; + + } + + } + + /** + * Generates the GLSL snippet for sampling/loading the given texture. + * + * @param {Texture} texture - The texture. + * @param {string} textureProperty - The name of the texture uniform in the shader. + * @param {string} uvSnippet - A GLSL snippet that represents texture coordinates used for sampling. + * @param {?string} depthSnippet - A GLSL snippet that represents the 0-based texture array index to sample. + * @return {string} The GLSL snippet. + */ + generateTexture( texture, textureProperty, uvSnippet, depthSnippet ) { + + if ( texture.isDepthTexture ) { + + if ( depthSnippet ) uvSnippet = `vec4( ${ uvSnippet }, ${ depthSnippet } )`; + + return `texture( ${ textureProperty }, ${ uvSnippet } ).x`; + + } else { + + if ( depthSnippet ) uvSnippet = `vec3( ${ uvSnippet }, ${ depthSnippet } )`; + + return `texture( ${ textureProperty }, ${ uvSnippet } )`; + + } + + } + + /** + * Generates the GLSL snippet when sampling textures with explicit mip level. + * + * @param {Texture} texture - The texture. + * @param {string} textureProperty - The name of the texture uniform in the shader. + * @param {string} uvSnippet - A GLSL snippet that represents texture coordinates used for sampling. + * @param {string} levelSnippet - A GLSL snippet that represents the mip level, with level 0 containing a full size version of the texture. + * @return {string} The GLSL snippet. + */ + generateTextureLevel( texture, textureProperty, uvSnippet, levelSnippet ) { + + return `textureLod( ${ textureProperty }, ${ uvSnippet }, ${ levelSnippet } )`; + + } + + /** + * Generates the GLSL snippet when sampling textures with a bias to the mip level. + * + * @param {Texture} texture - The texture. + * @param {string} textureProperty - The name of the texture uniform in the shader. + * @param {string} uvSnippet - A GLSL snippet that represents texture coordinates used for sampling. + * @param {string} biasSnippet - A GLSL snippet that represents the bias to apply to the mip level before sampling. + * @return {string} The GLSL snippet. + */ + generateTextureBias( texture, textureProperty, uvSnippet, biasSnippet ) { + + return `texture( ${ textureProperty }, ${ uvSnippet }, ${ biasSnippet } )`; + + } + + /** + * Generates the GLSL snippet for sampling/loading the given texture using explicit gradients. + * + * @param {Texture} texture - The texture. + * @param {string} textureProperty - The name of the texture uniform in the shader. + * @param {string} uvSnippet - A GLSL snippet that represents texture coordinates used for sampling. + * @param {Array} gradSnippet - An array holding both gradient GLSL snippets. + * @return {string} The GLSL snippet. + */ + generateTextureGrad( texture, textureProperty, uvSnippet, gradSnippet ) { + + return `textureGrad( ${ textureProperty }, ${ uvSnippet }, ${ gradSnippet[ 0 ] }, ${ gradSnippet[ 1 ] } )`; + + } + + /** + * Generates the GLSL snippet for sampling a depth texture and comparing the sampled depth values + * against a reference value. + * + * @param {Texture} texture - The texture. + * @param {string} textureProperty - The name of the texture uniform in the shader. + * @param {string} uvSnippet - A GLSL snippet that represents texture coordinates used for sampling. + * @param {string} compareSnippet - A GLSL snippet that represents the reference value. + * @param {?string} depthSnippet - A GLSL snippet that represents 0-based texture array index to sample. + * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for. + * @return {string} The GLSL snippet. + */ + generateTextureCompare( texture, textureProperty, uvSnippet, compareSnippet, depthSnippet, shaderStage = this.shaderStage ) { + + if ( shaderStage === 'fragment' ) { + + if ( depthSnippet ) { + + return `texture( ${ textureProperty }, vec4( ${ uvSnippet }, ${ depthSnippet }, ${ compareSnippet } ) )`; + + } + + return `texture( ${ textureProperty }, vec3( ${ uvSnippet }, ${ compareSnippet } ) )`; + + } else { + + console.error( `WebGPURenderer: THREE.DepthTexture.compareFunction() does not support ${ shaderStage } shader.` ); + + } + + } + + /** + * Returns the variables of the given shader stage as a GLSL string. + * + * @param {string} shaderStage - The shader stage. + * @return {string} The GLSL snippet that defines the variables. + */ + getVars( shaderStage ) { + + const snippets = []; + + const vars = this.vars[ shaderStage ]; + + if ( vars !== undefined ) { + + for ( const variable of vars ) { + + snippets.push( `${ this.getVar( variable.type, variable.name, variable.count ) };` ); + + } + + } + + return snippets.join( '\n\t' ); + + } + + /** + * Returns the uniforms of the given shader stage as a GLSL string. + * + * @param {string} shaderStage - The shader stage. + * @return {string} The GLSL snippet that defines the uniforms. + */ + getUniforms( shaderStage ) { + + const uniforms = this.uniforms[ shaderStage ]; + + const bindingSnippets = []; + const uniformGroups = {}; + + for ( const uniform of uniforms ) { + + let snippet = null; + let group = false; + + if ( uniform.type === 'texture' || uniform.type === 'texture3D' ) { + + const texture = uniform.node.value; + + let typePrefix = ''; + + if ( texture.isDataTexture === true || texture.isData3DTexture === true ) { + + if ( texture.type === UnsignedIntType ) { + + typePrefix = 'u'; + + } else if ( texture.type === IntType ) { + + typePrefix = 'i'; + + } + + } + + if ( uniform.type === 'texture3D' && texture.isArrayTexture === false ) { + + snippet = `${typePrefix}sampler3D ${ uniform.name };`; + + } else if ( texture.compareFunction ) { + + if ( texture.isArrayTexture === true ) { + + snippet = `sampler2DArrayShadow ${ uniform.name };`; + + } else { + + snippet = `sampler2DShadow ${ uniform.name };`; + + } + + } else if ( texture.isArrayTexture === true || texture.isDataArrayTexture === true || texture.isCompressedArrayTexture === true ) { + + snippet = `${typePrefix}sampler2DArray ${ uniform.name };`; + + } else { + + snippet = `${typePrefix}sampler2D ${ uniform.name };`; + + } + + } else if ( uniform.type === 'cubeTexture' ) { + + snippet = `samplerCube ${ uniform.name };`; + + } else if ( uniform.type === 'buffer' ) { + + const bufferNode = uniform.node; + const bufferType = this.getType( bufferNode.bufferType ); + const bufferCount = bufferNode.bufferCount; + + const bufferCountSnippet = bufferCount > 0 ? bufferCount : ''; + snippet = `${bufferNode.name} {\n\t${ bufferType } ${ uniform.name }[${ bufferCountSnippet }];\n};\n`; + + } else { + + const vectorType = this.getVectorType( uniform.type ); + + snippet = `${ vectorType } ${ this.getPropertyName( uniform, shaderStage ) };`; + + group = true; + + } + + const precision = uniform.node.precision; + + if ( precision !== null ) { + + snippet = precisionLib[ precision ] + ' ' + snippet; + + } + + if ( group ) { + + snippet = '\t' + snippet; + + const groupName = uniform.groupNode.name; + const groupSnippets = uniformGroups[ groupName ] || ( uniformGroups[ groupName ] = [] ); + + groupSnippets.push( snippet ); + + } else { + + snippet = 'uniform ' + snippet; + + bindingSnippets.push( snippet ); + + } + + } + + let output = ''; + + for ( const name in uniformGroups ) { + + const groupSnippets = uniformGroups[ name ]; + + output += this._getGLSLUniformStruct( shaderStage + '_' + name, groupSnippets.join( '\n' ) ) + '\n'; + + } + + output += bindingSnippets.join( '\n' ); + + return output; + + } + + /** + * Returns the type for a given buffer attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute. + * @return {string} The type. + */ + getTypeFromAttribute( attribute ) { + + let nodeType = super.getTypeFromAttribute( attribute ); + + if ( /^[iu]/.test( nodeType ) && attribute.gpuType !== IntType ) { + + let dataAttribute = attribute; + + if ( attribute.isInterleavedBufferAttribute ) dataAttribute = attribute.data; + + const array = dataAttribute.array; + + if ( ( array instanceof Uint32Array || array instanceof Int32Array ) === false ) { + + nodeType = nodeType.slice( 1 ); + + } + + } + + return nodeType; + + } + + /** + * Returns the shader attributes of the given shader stage as a GLSL string. + * + * @param {string} shaderStage - The shader stage. + * @return {string} The GLSL snippet that defines the shader attributes. + */ + getAttributes( shaderStage ) { + + let snippet = ''; + + if ( shaderStage === 'vertex' || shaderStage === 'compute' ) { + + const attributes = this.getAttributesArray(); + + let location = 0; + + for ( const attribute of attributes ) { + + snippet += `layout( location = ${ location ++ } ) in ${ attribute.type } ${ attribute.name };\n`; + + } + + } + + return snippet; + + } + + /** + * Returns the members of the given struct type node as a GLSL string. + * + * @param {StructTypeNode} struct - The struct type node. + * @return {string} The GLSL snippet that defines the struct members. + */ + getStructMembers( struct ) { + + const snippets = []; + + for ( const member of struct.members ) { + + snippets.push( `\t${ member.type } ${ member.name };` ); + + } + + return snippets.join( '\n' ); + + } + + /** + * Returns the structs of the given shader stage as a GLSL string. + * + * @param {string} shaderStage - The shader stage. + * @return {string} The GLSL snippet that defines the structs. + */ + getStructs( shaderStage ) { + + const snippets = []; + const structs = this.structs[ shaderStage ]; + + const outputSnippet = []; + + for ( const struct of structs ) { + + if ( struct.output ) { + + for ( const member of struct.members ) { + + outputSnippet.push( `layout( location = ${ member.index } ) out ${ member.type } ${ member.name };` ); + + } + + } else { + + let snippet = 'struct ' + struct.name + ' {\n'; + snippet += this.getStructMembers( struct ); + snippet += '\n};\n'; + + snippets.push( snippet ); + + } + + } + + if ( outputSnippet.length === 0 ) { + + outputSnippet.push( 'layout( location = 0 ) out vec4 fragColor;' ); + + } + + return '\n' + outputSnippet.join( '\n' ) + '\n\n' + snippets.join( '\n' ); + + } + + /** + * Returns the varyings of the given shader stage as a GLSL string. + * + * @param {string} shaderStage - The shader stage. + * @return {string} The GLSL snippet that defines the varyings. + */ + getVaryings( shaderStage ) { + + let snippet = ''; + + const varyings = this.varyings; + + if ( shaderStage === 'vertex' || shaderStage === 'compute' ) { + + for ( const varying of varyings ) { + + if ( shaderStage === 'compute' ) varying.needsInterpolation = true; + + const type = this.getType( varying.type ); + + if ( varying.needsInterpolation ) { + + if ( varying.interpolationType ) { + + const interpolationType = interpolationTypeMap[ varying.interpolationType ] || varying.interpolationType; + const sampling = interpolationModeMap[ varying.interpolationSampling ] || ''; + + snippet += `${ interpolationType } ${ sampling } out ${ type } ${ varying.name };\n`; + + } else { + + const flat = type.includes( 'int' ) || type.includes( 'uv' ) || type.includes( 'iv' ) ? 'flat ' : ''; + + snippet += `${ flat }out ${ type } ${ varying.name };\n`; + + } + + } else { + + snippet += `${type} ${varying.name};\n`; // generate variable (no varying required) + + } + + } + + } else if ( shaderStage === 'fragment' ) { + + for ( const varying of varyings ) { + + if ( varying.needsInterpolation ) { + + const type = this.getType( varying.type ); + + if ( varying.interpolationType ) { + + const interpolationType = interpolationTypeMap[ varying.interpolationType ] || varying.interpolationType; + const sampling = interpolationModeMap[ varying.interpolationSampling ] || ''; + + snippet += `${ interpolationType } ${ sampling } in ${ type } ${ varying.name };\n`; + + + } else { + + const flat = type.includes( 'int' ) || type.includes( 'uv' ) || type.includes( 'iv' ) ? 'flat ' : ''; + + snippet += `${ flat }in ${ type } ${ varying.name };\n`; + + } + + } + + } + + } + + for ( const builtin of this.builtins[ shaderStage ] ) { + + snippet += `${builtin};\n`; + + } + + return snippet; + + } + + /** + * Returns the vertex index builtin. + * + * @return {string} The vertex index. + */ + getVertexIndex() { + + return 'uint( gl_VertexID )'; + + } + + /** + * Returns the instance index builtin. + * + * @return {string} The instance index. + */ + getInstanceIndex() { + + return 'uint( gl_InstanceID )'; + + } + + /** + * Returns the invocation local index builtin. + * + * @return {string} The invocation local index. + */ + getInvocationLocalIndex() { + + const workgroupSize = this.object.workgroupSize; + + const size = workgroupSize.reduce( ( acc, curr ) => acc * curr, 1 ); + + return `uint( gl_InstanceID ) % ${size}u`; + + } + + /** + * Returns the draw index builtin. + * + * @return {?string} The drawIndex shader string. Returns `null` if `WEBGL_multi_draw` isn't supported by the device. + */ + getDrawIndex() { + + const extensions = this.renderer.backend.extensions; + + if ( extensions.has( 'WEBGL_multi_draw' ) ) { + + return 'uint( gl_DrawID )'; + + } + + return null; + + } + + /** + * Returns the front facing builtin. + * + * @return {string} The front facing builtin. + */ + getFrontFacing() { + + return 'gl_FrontFacing'; + + } + + /** + * Returns the frag coord builtin. + * + * @return {string} The frag coord builtin. + */ + getFragCoord() { + + return 'gl_FragCoord.xy'; + + } + + /** + * Returns the frag depth builtin. + * + * @return {string} The frag depth builtin. + */ + getFragDepth() { + + return 'gl_FragDepth'; + + } + + /** + * Enables the given extension. + * + * @param {string} name - The extension name. + * @param {string} behavior - The extension behavior. + * @param {string} [shaderStage=this.shaderStage] - The shader stage. + */ + enableExtension( name, behavior, shaderStage = this.shaderStage ) { + + const map = this.extensions[ shaderStage ] || ( this.extensions[ shaderStage ] = new Map() ); + + if ( map.has( name ) === false ) { + + map.set( name, { + name, + behavior + } ); + + } + + } + + /** + * Returns the enabled extensions of the given shader stage as a GLSL string. + * + * @param {string} shaderStage - The shader stage. + * @return {string} The GLSL snippet that defines the enabled extensions. + */ + getExtensions( shaderStage ) { + + const snippets = []; + + if ( shaderStage === 'vertex' ) { + + const ext = this.renderer.backend.extensions; + const isBatchedMesh = this.object.isBatchedMesh; + + if ( isBatchedMesh && ext.has( 'WEBGL_multi_draw' ) ) { + + this.enableExtension( 'GL_ANGLE_multi_draw', 'require', shaderStage ); + + } + + } + + const extensions = this.extensions[ shaderStage ]; + + if ( extensions !== undefined ) { + + for ( const { name, behavior } of extensions.values() ) { + + snippets.push( `#extension ${name} : ${behavior}` ); + + } + + } + + return snippets.join( '\n' ); + + } + + /** + * Returns the clip distances builtin. + * + * @return {string} The clip distances builtin. + */ + getClipDistance() { + + return 'gl_ClipDistance'; + + } + + /** + * Whether the requested feature is available or not. + * + * @param {string} name - The requested feature. + * @return {boolean} Whether the requested feature is supported or not. + */ + isAvailable( name ) { + + let result = supports$1[ name ]; + + if ( result === undefined ) { + + let extensionName; + + result = false; + + switch ( name ) { + + case 'float32Filterable': + extensionName = 'OES_texture_float_linear'; + break; + + case 'clipDistance': + extensionName = 'WEBGL_clip_cull_distance'; + break; + + } + + if ( extensionName !== undefined ) { + + const extensions = this.renderer.backend.extensions; + + if ( extensions.has( extensionName ) ) { + + extensions.get( extensionName ); + result = true; + + } + + } + + supports$1[ name ] = result; + + } + + return result; + + } + + /** + * Whether to flip texture data along its vertical axis or not. + * + * @return {boolean} Returns always `true` in context of GLSL. + */ + isFlipY() { + + return true; + + } + + /** + * Enables hardware clipping. + * + * @param {string} planeCount - The clipping plane count. + */ + enableHardwareClipping( planeCount ) { + + this.enableExtension( 'GL_ANGLE_clip_cull_distance', 'require' ); + + this.builtins[ 'vertex' ].push( `out float gl_ClipDistance[ ${ planeCount } ]` ); + + } + + /** + * Enables multiview. + */ + enableMultiview() { + + this.enableExtension( 'GL_OVR_multiview2', 'require', 'fragment' ); + this.enableExtension( 'GL_OVR_multiview2', 'require', 'vertex' ); + + this.builtins[ 'vertex' ].push( 'layout(num_views = 2) in' ); + + } + + /** + * Registers a transform in context of Transform Feedback. + * + * @param {string} varyingName - The varying name. + * @param {AttributeNode} attributeNode - The attribute node. + */ + registerTransform( varyingName, attributeNode ) { + + this.transforms.push( { varyingName, attributeNode } ); + + } + + /** + * Returns the transforms of the given shader stage as a GLSL string. + * + * @param {string} shaderStage - The shader stage. + * @return {string} The GLSL snippet that defines the transforms. + */ + getTransforms( /* shaderStage */ ) { + + const transforms = this.transforms; + + let snippet = ''; + + for ( let i = 0; i < transforms.length; i ++ ) { + + const transform = transforms[ i ]; + const attributeName = this.getPropertyName( transform.attributeNode ); + + if ( attributeName ) snippet += `${ transform.varyingName } = ${ attributeName };\n\t`; + + } + + return snippet; + + } + + /** + * Returns a GLSL struct based on the given name and variables. + * + * @private + * @param {string} name - The struct name. + * @param {string} vars - The struct variables. + * @return {string} The GLSL snippet representing a struct. + */ + _getGLSLUniformStruct( name, vars ) { + + return ` +layout( std140 ) uniform ${name} { +${vars} +};`; + + } + + /** + * Returns a GLSL vertex shader based on the given shader data. + * + * @private + * @param {Object} shaderData - The shader data. + * @return {string} The vertex shader. + */ + _getGLSLVertexCode( shaderData ) { + + return `#version 300 es + +${ this.getSignature() } + +// extensions +${shaderData.extensions} + +// precision +${ defaultPrecisions } + +// uniforms +${shaderData.uniforms} + +// varyings +${shaderData.varyings} + +// attributes +${shaderData.attributes} + +// codes +${shaderData.codes} + +void main() { + + // vars + ${shaderData.vars} + + // transforms + ${shaderData.transforms} + + // flow + ${shaderData.flow} + + gl_PointSize = 1.0; + +} +`; + + } + + /** + * Returns a GLSL fragment shader based on the given shader data. + * + * @private + * @param {Object} shaderData - The shader data. + * @return {string} The vertex shader. + */ + _getGLSLFragmentCode( shaderData ) { + + return `#version 300 es + +${ this.getSignature() } + +// extensions +${shaderData.extensions} + +// precision +${ defaultPrecisions } + +// uniforms +${shaderData.uniforms} + +// varyings +${shaderData.varyings} + +// codes +${shaderData.codes} + +// structs +${shaderData.structs} + +void main() { + + // vars + ${shaderData.vars} + + // flow + ${shaderData.flow} + +} +`; + + } + + /** + * Controls the code build of the shader stages. + */ + buildCode() { + + const shadersData = this.material !== null ? { fragment: {}, vertex: {} } : { compute: {} }; + + this.sortBindingGroups(); + + for ( const shaderStage in shadersData ) { + + let flow = '// code\n\n'; + flow += this.flowCode[ shaderStage ]; + + const flowNodes = this.flowNodes[ shaderStage ]; + const mainNode = flowNodes[ flowNodes.length - 1 ]; + + for ( const node of flowNodes ) { + + const flowSlotData = this.getFlowData( node/*, shaderStage*/ ); + const slotName = node.name; + + if ( slotName ) { + + if ( flow.length > 0 ) flow += '\n'; + + flow += `\t// flow -> ${ slotName }\n\t`; + + } + + flow += `${ flowSlotData.code }\n\t`; + + if ( node === mainNode && shaderStage !== 'compute' ) { + + flow += '// result\n\t'; + + if ( shaderStage === 'vertex' ) { + + flow += 'gl_Position = '; + flow += `${ flowSlotData.result };`; + + } else if ( shaderStage === 'fragment' ) { + + if ( ! node.outputNode.isOutputStructNode ) { + + flow += 'fragColor = '; + flow += `${ flowSlotData.result };`; + + } + + } + + } + + } + + const stageData = shadersData[ shaderStage ]; + + stageData.extensions = this.getExtensions( shaderStage ); + stageData.uniforms = this.getUniforms( shaderStage ); + stageData.attributes = this.getAttributes( shaderStage ); + stageData.varyings = this.getVaryings( shaderStage ); + stageData.vars = this.getVars( shaderStage ); + stageData.structs = this.getStructs( shaderStage ); + stageData.codes = this.getCodes( shaderStage ); + stageData.transforms = this.getTransforms( shaderStage ); + stageData.flow = flow; + + } + + if ( this.material !== null ) { + + this.vertexShader = this._getGLSLVertexCode( shadersData.vertex ); + this.fragmentShader = this._getGLSLFragmentCode( shadersData.fragment ); + + } else { + + this.computeShader = this._getGLSLVertexCode( shadersData.compute ); + + } + + } + + /** + * This method is one of the more important ones since it's responsible + * for generating a matching binding instance for the given uniform node. + * + * These bindings are later used in the renderer to create bind groups + * and layouts. + * + * @param {UniformNode} node - The uniform node. + * @param {string} type - The node data type. + * @param {string} shaderStage - The shader stage. + * @param {?string} [name=null] - An optional uniform name. + * @return {NodeUniform} The node uniform object. + */ + getUniformFromNode( node, type, shaderStage, name = null ) { + + const uniformNode = super.getUniformFromNode( node, type, shaderStage, name ); + const nodeData = this.getDataFromNode( node, shaderStage, this.globalCache ); + + let uniformGPU = nodeData.uniformGPU; + + if ( uniformGPU === undefined ) { + + const group = node.groupNode; + const groupName = group.name; + + const bindings = this.getBindGroupArray( groupName, shaderStage ); + + if ( type === 'texture' ) { + + uniformGPU = new NodeSampledTexture( uniformNode.name, uniformNode.node, group ); + bindings.push( uniformGPU ); + + } else if ( type === 'cubeTexture' ) { + + uniformGPU = new NodeSampledCubeTexture( uniformNode.name, uniformNode.node, group ); + bindings.push( uniformGPU ); + + } else if ( type === 'texture3D' ) { + + uniformGPU = new NodeSampledTexture3D( uniformNode.name, uniformNode.node, group ); + bindings.push( uniformGPU ); + + } else if ( type === 'buffer' ) { + + node.name = `NodeBuffer_${ node.id }`; + uniformNode.name = `buffer${ node.id }`; + + const buffer = new NodeUniformBuffer( node, group ); + buffer.name = node.name; + + bindings.push( buffer ); + + uniformGPU = buffer; + + } else { + + const uniformsStage = this.uniformGroups[ shaderStage ] || ( this.uniformGroups[ shaderStage ] = {} ); + + let uniformsGroup = uniformsStage[ groupName ]; + + if ( uniformsGroup === undefined ) { + + uniformsGroup = new NodeUniformsGroup( shaderStage + '_' + groupName, group ); + //uniformsGroup.setVisibility( gpuShaderStageLib[ shaderStage ] ); + + uniformsStage[ groupName ] = uniformsGroup; + + bindings.push( uniformsGroup ); + + } + + uniformGPU = this.getNodeUniform( uniformNode, type ); + + uniformsGroup.addUniform( uniformGPU ); + + } + + nodeData.uniformGPU = uniformGPU; + + } + + return uniformNode; + + } + +} + +let _vector2 = null; +let _color4 = null; + +/** + * Most of the rendering related logic is implemented in the + * {@link Renderer} module and related management components. + * Sometimes it is required though to execute commands which are + * specific to the current 3D backend (which is WebGPU or WebGL 2). + * This abstract base class defines an interface that encapsulates + * all backend-related logic. Derived classes for each backend must + * implement the interface. + * + * @abstract + * @private + */ +class Backend { + + /** + * Constructs a new backend. + * + * @param {Object} parameters - An object holding parameters for the backend. + */ + constructor( parameters = {} ) { + + /** + * The parameters of the backend. + * + * @type {Object} + */ + this.parameters = Object.assign( {}, parameters ); + + /** + * This weak map holds backend-specific data of objects + * like textures, attributes or render targets. + * + * @type {WeakMap} + */ + this.data = new WeakMap(); + + /** + * A reference to the renderer. + * + * @type {?Renderer} + * @default null + */ + this.renderer = null; + + /** + * A reference to the canvas element the renderer is drawing to. + * + * @type {?(HTMLCanvasElement|OffscreenCanvas)} + * @default null + */ + this.domElement = null; + + /** + * A reference to the timestamp query pool. + * + * @type {{render: ?TimestampQueryPool, compute: ?TimestampQueryPool}} + */ + this.timestampQueryPool = { + 'render': null, + 'compute': null + }; + + /** + * Whether to track timestamps with a Timestamp Query API or not. + * + * @type {boolean} + * @default false + */ + this.trackTimestamp = ( parameters.trackTimestamp === true ); + + } + + /** + * Initializes the backend so it is ready for usage. Concrete backends + * are supposed to implement their rendering context creation and related + * operations in this method. + * + * @async + * @param {Renderer} renderer - The renderer. + * @return {Promise} A Promise that resolves when the backend has been initialized. + */ + async init( renderer ) { + + this.renderer = renderer; + + } + + /** + * The coordinate system of the backend. + * + * @abstract + * @type {number} + * @readonly + */ + get coordinateSystem() {} + + // render context + + /** + * This method is executed at the beginning of a render call and + * can be used by the backend to prepare the state for upcoming + * draw calls. + * + * @abstract + * @param {RenderContext} renderContext - The render context. + */ + beginRender( /*renderContext*/ ) {} + + /** + * This method is executed at the end of a render call and + * can be used by the backend to finalize work after draw + * calls. + * + * @abstract + * @param {RenderContext} renderContext - The render context. + */ + finishRender( /*renderContext*/ ) {} + + /** + * This method is executed at the beginning of a compute call and + * can be used by the backend to prepare the state for upcoming + * compute tasks. + * + * @abstract + * @param {Node|Array} computeGroup - The compute node(s). + */ + beginCompute( /*computeGroup*/ ) {} + + /** + * This method is executed at the end of a compute call and + * can be used by the backend to finalize work after compute + * tasks. + * + * @abstract + * @param {Node|Array} computeGroup - The compute node(s). + */ + finishCompute( /*computeGroup*/ ) {} + + // render object + + /** + * Executes a draw command for the given render object. + * + * @abstract + * @param {RenderObject} renderObject - The render object to draw. + * @param {Info} info - Holds a series of statistical information about the GPU memory and the rendering process. + */ + draw( /*renderObject, info*/ ) { } + + // compute node + + /** + * Executes a compute command for the given compute node. + * + * @abstract + * @param {Node|Array} computeGroup - The group of compute nodes of a compute call. Can be a single compute node. + * @param {Node} computeNode - The compute node. + * @param {Array} bindings - The bindings. + * @param {ComputePipeline} computePipeline - The compute pipeline. + */ + compute( /*computeGroup, computeNode, computeBindings, computePipeline*/ ) { } + + // program + + /** + * Creates a shader program from the given programmable stage. + * + * @abstract + * @param {ProgrammableStage} program - The programmable stage. + */ + createProgram( /*program*/ ) { } + + /** + * Destroys the shader program of the given programmable stage. + * + * @abstract + * @param {ProgrammableStage} program - The programmable stage. + */ + destroyProgram( /*program*/ ) { } + + // bindings + + /** + * Creates bindings from the given bind group definition. + * + * @abstract + * @param {BindGroup} bindGroup - The bind group. + * @param {Array} bindings - Array of bind groups. + * @param {number} cacheIndex - The cache index. + * @param {number} version - The version. + */ + createBindings( /*bindGroup, bindings, cacheIndex, version*/ ) { } + + /** + * Updates the given bind group definition. + * + * @abstract + * @param {BindGroup} bindGroup - The bind group. + * @param {Array} bindings - Array of bind groups. + * @param {number} cacheIndex - The cache index. + * @param {number} version - The version. + */ + updateBindings( /*bindGroup, bindings, cacheIndex, version*/ ) { } + + /** + * Updates a buffer binding. + * + * @abstract + * @param {Buffer} binding - The buffer binding to update. + */ + updateBinding( /*binding*/ ) { } + + // pipeline + + /** + * Creates a render pipeline for the given render object. + * + * @abstract + * @param {RenderObject} renderObject - The render object. + * @param {Array} promises - An array of compilation promises which are used in `compileAsync()`. + */ + createRenderPipeline( /*renderObject, promises*/ ) { } + + /** + * Creates a compute pipeline for the given compute node. + * + * @abstract + * @param {ComputePipeline} computePipeline - The compute pipeline. + * @param {Array} bindings - The bindings. + */ + createComputePipeline( /*computePipeline, bindings*/ ) { } + + // cache key + + /** + * Returns `true` if the render pipeline requires an update. + * + * @abstract + * @param {RenderObject} renderObject - The render object. + * @return {boolean} Whether the render pipeline requires an update or not. + */ + needsRenderUpdate( /*renderObject*/ ) { } + + /** + * Returns a cache key that is used to identify render pipelines. + * + * @abstract + * @param {RenderObject} renderObject - The render object. + * @return {string} The cache key. + */ + getRenderCacheKey( /*renderObject*/ ) { } + + // node builder + + /** + * Returns a node builder for the given render object. + * + * @abstract + * @param {RenderObject} renderObject - The render object. + * @param {Renderer} renderer - The renderer. + * @return {NodeBuilder} The node builder. + */ + createNodeBuilder( /*renderObject, renderer*/ ) { } + + // textures + + /** + * Creates a GPU sampler for the given texture. + * + * @abstract + * @param {Texture} texture - The texture to create the sampler for. + */ + createSampler( /*texture*/ ) { } + + /** + * Destroys the GPU sampler for the given texture. + * + * @abstract + * @param {Texture} texture - The texture to destroy the sampler for. + */ + destroySampler( /*texture*/ ) {} + + /** + * Creates a default texture for the given texture that can be used + * as a placeholder until the actual texture is ready for usage. + * + * @abstract + * @param {Texture} texture - The texture to create a default texture for. + */ + createDefaultTexture( /*texture*/ ) { } + + /** + * Defines a texture on the GPU for the given texture object. + * + * @abstract + * @param {Texture} texture - The texture. + * @param {Object} [options={}] - Optional configuration parameter. + */ + createTexture( /*texture, options={}*/ ) { } + + /** + * Uploads the updated texture data to the GPU. + * + * @abstract + * @param {Texture} texture - The texture. + * @param {Object} [options={}] - Optional configuration parameter. + */ + updateTexture( /*texture, options = {}*/ ) { } + + /** + * Generates mipmaps for the given texture. + * + * @abstract + * @param {Texture} texture - The texture. + */ + generateMipmaps( /*texture*/ ) { } + + /** + * Destroys the GPU data for the given texture object. + * + * @abstract + * @param {Texture} texture - The texture. + */ + destroyTexture( /*texture*/ ) { } + + /** + * Returns texture data as a typed array. + * + * @abstract + * @async + * @param {Texture} texture - The texture to copy. + * @param {number} x - The x coordinate of the copy origin. + * @param {number} y - The y coordinate of the copy origin. + * @param {number} width - The width of the copy. + * @param {number} height - The height of the copy. + * @param {number} faceIndex - The face index. + * @return {Promise} A Promise that resolves with a typed array when the copy operation has finished. + */ + async copyTextureToBuffer( /*texture, x, y, width, height, faceIndex*/ ) {} + + /** + * Copies data of the given source texture to the given destination texture. + * + * @abstract + * @param {Texture} srcTexture - The source texture. + * @param {Texture} dstTexture - The destination texture. + * @param {?(Box3|Box2)} [srcRegion=null] - The region of the source texture to copy. + * @param {?(Vector2|Vector3)} [dstPosition=null] - The destination position of the copy. + * @param {number} [srcLevel=0] - The source mip level to copy from. + * @param {number} [dstLevel=0] - The destination mip level to copy to. + */ + copyTextureToTexture( /*srcTexture, dstTexture, srcRegion = null, dstPosition = null, srcLevel = 0, dstLevel = 0*/ ) {} + + /** + * Copies the current bound framebuffer to the given texture. + * + * @abstract + * @param {Texture} texture - The destination texture. + * @param {RenderContext} renderContext - The render context. + * @param {Vector4} rectangle - A four dimensional vector defining the origin and dimension of the copy. + */ + copyFramebufferToTexture( /*texture, renderContext, rectangle*/ ) {} + + // attributes + + /** + * Creates the GPU buffer of a shader attribute. + * + * @abstract + * @param {BufferAttribute} attribute - The buffer attribute. + */ + createAttribute( /*attribute*/ ) { } + + /** + * Creates the GPU buffer of an indexed shader attribute. + * + * @abstract + * @param {BufferAttribute} attribute - The indexed buffer attribute. + */ + createIndexAttribute( /*attribute*/ ) { } + + /** + * Creates the GPU buffer of a storage attribute. + * + * @abstract + * @param {BufferAttribute} attribute - The buffer attribute. + */ + createStorageAttribute( /*attribute*/ ) { } + + /** + * Updates the GPU buffer of a shader attribute. + * + * @abstract + * @param {BufferAttribute} attribute - The buffer attribute to update. + */ + updateAttribute( /*attribute*/ ) { } + + /** + * Destroys the GPU buffer of a shader attribute. + * + * @abstract + * @param {BufferAttribute} attribute - The buffer attribute to destroy. + */ + destroyAttribute( /*attribute*/ ) { } + + // canvas + + /** + * Returns the backend's rendering context. + * + * @abstract + * @return {Object} The rendering context. + */ + getContext() { } + + /** + * Backends can use this method if they have to run + * logic when the renderer gets resized. + * + * @abstract + */ + updateSize() { } + + /** + * Updates the viewport with the values from the given render context. + * + * @abstract + * @param {RenderContext} renderContext - The render context. + */ + updateViewport( /*renderContext*/ ) {} + + // utils + + /** + * Returns `true` if the given 3D object is fully occluded by other + * 3D objects in the scene. Backends must implement this method by using + * a Occlusion Query API. + * + * @abstract + * @param {RenderContext} renderContext - The render context. + * @param {Object3D} object - The 3D object to test. + * @return {boolean} Whether the 3D object is fully occluded or not. + */ + isOccluded( /*renderContext, object*/ ) {} + + /** + * Resolves the time stamp for the given render context and type. + * + * @async + * @abstract + * @param {string} [type='render'] - The type of the time stamp. + * @return {Promise} A Promise that resolves with the time stamp. + */ + async resolveTimestampsAsync( type = 'render' ) { + + if ( ! this.trackTimestamp ) { + + warnOnce( 'WebGPURenderer: Timestamp tracking is disabled.' ); + return; + + } + + const queryPool = this.timestampQueryPool[ type ]; + if ( ! queryPool ) { + + warnOnce( `WebGPURenderer: No timestamp query pool for type '${type}' found.` ); + return; + + } + + const duration = await queryPool.resolveQueriesAsync(); + + this.renderer.info[ type ].timestamp = duration; + + return duration; + + } + + /** + * Can be used to synchronize CPU operations with GPU tasks. So when this method is called, + * the CPU waits for the GPU to complete its operation (e.g. a compute task). + * + * @async + * @abstract + * @return {Promise} A Promise that resolves when synchronization has been finished. + */ + async waitForGPU() {} + + /** + * This method performs a readback operation by moving buffer data from + * a storage buffer attribute from the GPU to the CPU. + * + * @async + * @param {StorageBufferAttribute} attribute - The storage buffer attribute. + * @return {Promise} A promise that resolves with the buffer data when the data are ready. + */ + async getArrayBufferAsync( /* attribute */ ) {} + + /** + * Checks if the given feature is supported by the backend. + * + * @async + * @abstract + * @param {string} name - The feature's name. + * @return {Promise} A Promise that resolves with a bool that indicates whether the feature is supported or not. + */ + async hasFeatureAsync( /*name*/ ) { } + + /** + * Checks if the given feature is supported by the backend. + * + * @abstract + * @param {string} name - The feature's name. + * @return {boolean} Whether the feature is supported or not. + */ + hasFeature( /*name*/ ) {} + + /** + * Returns the maximum anisotropy texture filtering value. + * + * @abstract + * @return {number} The maximum anisotropy texture filtering value. + */ + getMaxAnisotropy() {} + + /** + * Returns the drawing buffer size. + * + * @return {Vector2} The drawing buffer size. + */ + getDrawingBufferSize() { + + _vector2 = _vector2 || new Vector2(); + + return this.renderer.getDrawingBufferSize( _vector2 ); + + } + + /** + * Defines the scissor test. + * + * @abstract + * @param {boolean} boolean - Whether the scissor test should be enabled or not. + */ + setScissorTest( /*boolean*/ ) { } + + /** + * Returns the clear color and alpha into a single + * color object. + * + * @return {Color4} The clear color. + */ + getClearColor() { + + const renderer = this.renderer; + + _color4 = _color4 || new Color4(); + + renderer.getClearColor( _color4 ); + + _color4.getRGB( _color4 ); + + return _color4; + + } + + /** + * Returns the DOM element. If no DOM element exists, the backend + * creates a new one. + * + * @return {HTMLCanvasElement} The DOM element. + */ + getDomElement() { + + let domElement = this.domElement; + + if ( domElement === null ) { + + domElement = ( this.parameters.canvas !== undefined ) ? this.parameters.canvas : createCanvasElement(); + + // OffscreenCanvas does not have setAttribute, see #22811 + if ( 'setAttribute' in domElement ) domElement.setAttribute( 'data-engine', `three.js r${REVISION} webgpu` ); + + this.domElement = domElement; + + } + + return domElement; + + } + + /** + * Sets a dictionary for the given object into the + * internal data structure. + * + * @param {Object} object - The object. + * @param {Object} value - The dictionary to set. + */ + set( object, value ) { + + this.data.set( object, value ); + + } + + /** + * Returns the dictionary for the given object. + * + * @param {Object} object - The object. + * @return {Object} The object's dictionary. + */ + get( object ) { + + let map = this.data.get( object ); + + if ( map === undefined ) { + + map = {}; + this.data.set( object, map ); + + } + + return map; + + } + + /** + * Checks if the given object has a dictionary + * with data defined. + * + * @param {Object} object - The object. + * @return {boolean} Whether a dictionary for the given object as been defined or not. + */ + has( object ) { + + return this.data.has( object ); + + } + + /** + * Deletes an object from the internal data structure. + * + * @param {Object} object - The object to delete. + */ + delete( object ) { + + this.data.delete( object ); + + } + + /** + * Frees internal resources. + * + * @abstract + */ + dispose() { } + +} + +let _id$1 = 0; + +/** + * This module is internally used in context of compute shaders. + * This type of shader is not natively supported in WebGL 2 and + * thus implemented via Transform Feedback. `DualAttributeData` + * manages the related data. + * + * @private + */ +class DualAttributeData { + + constructor( attributeData, dualBuffer ) { + + this.buffers = [ attributeData.bufferGPU, dualBuffer ]; + this.type = attributeData.type; + this.bufferType = attributeData.bufferType; + this.pbo = attributeData.pbo; + this.byteLength = attributeData.byteLength; + this.bytesPerElement = attributeData.BYTES_PER_ELEMENT; + this.version = attributeData.version; + this.isInteger = attributeData.isInteger; + this.activeBufferIndex = 0; + this.baseId = attributeData.id; + + } + + + get id() { + + return `${ this.baseId }|${ this.activeBufferIndex }`; + + } + + get bufferGPU() { + + return this.buffers[ this.activeBufferIndex ]; + + } + + get transformBuffer() { + + return this.buffers[ this.activeBufferIndex ^ 1 ]; + + } + + switchBuffers() { + + this.activeBufferIndex ^= 1; + + } + +} + +/** + * A WebGL 2 backend utility module for managing shader attributes. + * + * @private + */ +class WebGLAttributeUtils { + + /** + * Constructs a new utility object. + * + * @param {WebGLBackend} backend - The WebGL 2 backend. + */ + constructor( backend ) { + + /** + * A reference to the WebGL 2 backend. + * + * @type {WebGLBackend} + */ + this.backend = backend; + + } + + /** + * Creates the GPU buffer for the given buffer attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute. + * @param {GLenum } bufferType - A flag that indicates the buffer type and thus binding point target. + */ + createAttribute( attribute, bufferType ) { + + const backend = this.backend; + const { gl } = backend; + + const array = attribute.array; + const usage = attribute.usage || gl.STATIC_DRAW; + + const bufferAttribute = attribute.isInterleavedBufferAttribute ? attribute.data : attribute; + const bufferData = backend.get( bufferAttribute ); + + let bufferGPU = bufferData.bufferGPU; + + if ( bufferGPU === undefined ) { + + bufferGPU = this._createBuffer( gl, bufferType, array, usage ); + + bufferData.bufferGPU = bufferGPU; + bufferData.bufferType = bufferType; + bufferData.version = bufferAttribute.version; + + } + + //attribute.onUploadCallback(); + + let type; + + if ( array instanceof Float32Array ) { + + type = gl.FLOAT; + + } else if ( typeof Float16Array !== 'undefined' && array instanceof Float16Array ) { + + type = gl.HALF_FLOAT; + + } else if ( array instanceof Uint16Array ) { + + if ( attribute.isFloat16BufferAttribute ) { + + type = gl.HALF_FLOAT; + + } else { + + type = gl.UNSIGNED_SHORT; + + } + + } else if ( array instanceof Int16Array ) { + + type = gl.SHORT; + + } else if ( array instanceof Uint32Array ) { + + type = gl.UNSIGNED_INT; + + } else if ( array instanceof Int32Array ) { + + type = gl.INT; + + } else if ( array instanceof Int8Array ) { + + type = gl.BYTE; + + } else if ( array instanceof Uint8Array ) { + + type = gl.UNSIGNED_BYTE; + + } else if ( array instanceof Uint8ClampedArray ) { + + type = gl.UNSIGNED_BYTE; + + } else { + + throw new Error( 'THREE.WebGLBackend: Unsupported buffer data format: ' + array ); + + } + + let attributeData = { + bufferGPU, + bufferType, + type, + byteLength: array.byteLength, + bytesPerElement: array.BYTES_PER_ELEMENT, + version: attribute.version, + pbo: attribute.pbo, + isInteger: type === gl.INT || type === gl.UNSIGNED_INT || attribute.gpuType === IntType, + id: _id$1 ++ + }; + + if ( attribute.isStorageBufferAttribute || attribute.isStorageInstancedBufferAttribute ) { + + // create buffer for transform feedback use + const bufferGPUDual = this._createBuffer( gl, bufferType, array, usage ); + attributeData = new DualAttributeData( attributeData, bufferGPUDual ); + + } + + backend.set( attribute, attributeData ); + + } + + /** + * Updates the GPU buffer of the given buffer attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute. + */ + updateAttribute( attribute ) { + + const backend = this.backend; + const { gl } = backend; + + const array = attribute.array; + const bufferAttribute = attribute.isInterleavedBufferAttribute ? attribute.data : attribute; + const bufferData = backend.get( bufferAttribute ); + const bufferType = bufferData.bufferType; + const updateRanges = attribute.isInterleavedBufferAttribute ? attribute.data.updateRanges : attribute.updateRanges; + + gl.bindBuffer( bufferType, bufferData.bufferGPU ); + + if ( updateRanges.length === 0 ) { + + // Not using update ranges + + gl.bufferSubData( bufferType, 0, array ); + + } else { + + for ( let i = 0, l = updateRanges.length; i < l; i ++ ) { + + const range = updateRanges[ i ]; + gl.bufferSubData( bufferType, range.start * array.BYTES_PER_ELEMENT, + array, range.start, range.count ); + + } + + bufferAttribute.clearUpdateRanges(); + + } + + gl.bindBuffer( bufferType, null ); + + bufferData.version = bufferAttribute.version; + + } + + /** + * Destroys the GPU buffer of the given buffer attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute. + */ + destroyAttribute( attribute ) { + + const backend = this.backend; + const { gl } = backend; + + if ( attribute.isInterleavedBufferAttribute ) { + + backend.delete( attribute.data ); + + } + + const attributeData = backend.get( attribute ); + + gl.deleteBuffer( attributeData.bufferGPU ); + + backend.delete( attribute ); + + } + + /** + * This method performs a readback operation by moving buffer data from + * a storage buffer attribute from the GPU to the CPU. + * + * @async + * @param {StorageBufferAttribute} attribute - The storage buffer attribute. + * @return {Promise} A promise that resolves with the buffer data when the data are ready. + */ + async getArrayBufferAsync( attribute ) { + + const backend = this.backend; + const { gl } = backend; + + const bufferAttribute = attribute.isInterleavedBufferAttribute ? attribute.data : attribute; + const { bufferGPU } = backend.get( bufferAttribute ); + + const array = attribute.array; + const byteLength = array.byteLength; + + gl.bindBuffer( gl.COPY_READ_BUFFER, bufferGPU ); + + const writeBuffer = gl.createBuffer(); + + gl.bindBuffer( gl.COPY_WRITE_BUFFER, writeBuffer ); + gl.bufferData( gl.COPY_WRITE_BUFFER, byteLength, gl.STREAM_READ ); + + gl.copyBufferSubData( gl.COPY_READ_BUFFER, gl.COPY_WRITE_BUFFER, 0, 0, byteLength ); + + await backend.utils._clientWaitAsync(); + + const dstBuffer = new attribute.array.constructor( array.length ); + + // Ensure the buffer is bound before reading + gl.bindBuffer( gl.COPY_WRITE_BUFFER, writeBuffer ); + + gl.getBufferSubData( gl.COPY_WRITE_BUFFER, 0, dstBuffer ); + + gl.deleteBuffer( writeBuffer ); + + gl.bindBuffer( gl.COPY_READ_BUFFER, null ); + gl.bindBuffer( gl.COPY_WRITE_BUFFER, null ); + + return dstBuffer.buffer; + + } + + /** + * Creates a WebGL buffer with the given data. + * + * @private + * @param {WebGL2RenderingContext} gl - The rendering context. + * @param {GLenum } bufferType - A flag that indicates the buffer type and thus binding point target. + * @param {TypedArray} array - The array of the buffer attribute. + * @param {GLenum} usage - The usage. + * @return {WebGLBuffer} The WebGL buffer. + */ + _createBuffer( gl, bufferType, array, usage ) { + + const bufferGPU = gl.createBuffer(); + + gl.bindBuffer( bufferType, bufferGPU ); + gl.bufferData( bufferType, array, usage ); + gl.bindBuffer( bufferType, null ); + + return bufferGPU; + + } + +} + +let equationToGL, factorToGL; + +/** + * A WebGL 2 backend utility module for managing the WebGL state. + * + * The major goal of this module is to reduce the number of state changes + * by caching the WEbGL state with a series of variables. In this way, the + * renderer only executes state change commands when necessary which + * improves the overall performance. + * + * @private + */ +class WebGLState { + + /** + * Constructs a new utility object. + * + * @param {WebGLBackend} backend - The WebGL 2 backend. + */ + constructor( backend ) { + + /** + * A reference to the WebGL 2 backend. + * + * @type {WebGLBackend} + */ + this.backend = backend; + + /** + * A reference to the rendering context. + * + * @type {WebGL2RenderingContext} + */ + this.gl = this.backend.gl; + + // Below properties are intended to cache + // the WebGL state and are not explicitly + // documented for convenience reasons. + + this.enabled = {}; + this.currentFlipSided = null; + this.currentCullFace = null; + this.currentProgram = null; + this.currentBlendingEnabled = false; + this.currentBlending = null; + this.currentBlendSrc = null; + this.currentBlendDst = null; + this.currentBlendSrcAlpha = null; + this.currentBlendDstAlpha = null; + this.currentPremultipledAlpha = null; + this.currentPolygonOffsetFactor = null; + this.currentPolygonOffsetUnits = null; + this.currentColorMask = null; + this.currentDepthFunc = null; + this.currentDepthMask = null; + this.currentStencilFunc = null; + this.currentStencilRef = null; + this.currentStencilFuncMask = null; + this.currentStencilFail = null; + this.currentStencilZFail = null; + this.currentStencilZPass = null; + this.currentStencilMask = null; + this.currentLineWidth = null; + this.currentClippingPlanes = 0; + + this.currentVAO = null; + this.currentIndex = null; + + this.currentBoundFramebuffers = {}; + this.currentDrawbuffers = new WeakMap(); + + this.maxTextures = this.gl.getParameter( this.gl.MAX_TEXTURE_IMAGE_UNITS ); + this.currentTextureSlot = null; + this.currentBoundTextures = {}; + this.currentBoundBufferBases = {}; + + + this._init(); + + } + + /** + * Inits the state of the utility. + * + * @private + */ + _init() { + + const gl = this.gl; + + // Store only WebGL constants here. + + equationToGL = { + [ AddEquation ]: gl.FUNC_ADD, + [ SubtractEquation ]: gl.FUNC_SUBTRACT, + [ ReverseSubtractEquation ]: gl.FUNC_REVERSE_SUBTRACT + }; + + factorToGL = { + [ ZeroFactor ]: gl.ZERO, + [ OneFactor ]: gl.ONE, + [ SrcColorFactor ]: gl.SRC_COLOR, + [ SrcAlphaFactor ]: gl.SRC_ALPHA, + [ SrcAlphaSaturateFactor ]: gl.SRC_ALPHA_SATURATE, + [ DstColorFactor ]: gl.DST_COLOR, + [ DstAlphaFactor ]: gl.DST_ALPHA, + [ OneMinusSrcColorFactor ]: gl.ONE_MINUS_SRC_COLOR, + [ OneMinusSrcAlphaFactor ]: gl.ONE_MINUS_SRC_ALPHA, + [ OneMinusDstColorFactor ]: gl.ONE_MINUS_DST_COLOR, + [ OneMinusDstAlphaFactor ]: gl.ONE_MINUS_DST_ALPHA + }; + + const scissorParam = gl.getParameter( gl.SCISSOR_BOX ); + const viewportParam = gl.getParameter( gl.VIEWPORT ); + + this.currentScissor = new Vector4().fromArray( scissorParam ); + this.currentViewport = new Vector4().fromArray( viewportParam ); + + this._tempVec4 = new Vector4(); + + } + + /** + * Enables the given WebGL capability. + * + * This method caches the capability state so + * `gl.enable()` is only called when necessary. + * + * @param {GLenum} id - The capability to enable. + */ + enable( id ) { + + const { enabled } = this; + + if ( enabled[ id ] !== true ) { + + this.gl.enable( id ); + enabled[ id ] = true; + + } + + } + + /** + * Disables the given WebGL capability. + * + * This method caches the capability state so + * `gl.disable()` is only called when necessary. + * + * @param {GLenum} id - The capability to enable. + */ + disable( id ) { + + const { enabled } = this; + + if ( enabled[ id ] !== false ) { + + this.gl.disable( id ); + enabled[ id ] = false; + + } + + } + + /** + * Specifies whether polygons are front- or back-facing + * by setting the winding orientation. + * + * This method caches the state so `gl.frontFace()` is only + * called when necessary. + * + * @param {boolean} flipSided - Whether triangles flipped their sides or not. + */ + setFlipSided( flipSided ) { + + if ( this.currentFlipSided !== flipSided ) { + + const { gl } = this; + + if ( flipSided ) { + + gl.frontFace( gl.CW ); + + } else { + + gl.frontFace( gl.CCW ); + + } + + this.currentFlipSided = flipSided; + + } + + } + + /** + * Specifies whether or not front- and/or back-facing + * polygons can be culled. + * + * This method caches the state so `gl.cullFace()` is only + * called when necessary. + * + * @param {number} cullFace - Defines which polygons are candidates for culling. + */ + setCullFace( cullFace ) { + + const { gl } = this; + + if ( cullFace !== CullFaceNone ) { + + this.enable( gl.CULL_FACE ); + + if ( cullFace !== this.currentCullFace ) { + + if ( cullFace === CullFaceBack ) { + + gl.cullFace( gl.BACK ); + + } else if ( cullFace === CullFaceFront ) { + + gl.cullFace( gl.FRONT ); + + } else { + + gl.cullFace( gl.FRONT_AND_BACK ); + + } + + } + + } else { + + this.disable( gl.CULL_FACE ); + + } + + this.currentCullFace = cullFace; + + } + + /** + * Specifies the width of line primitives. + * + * This method caches the state so `gl.lineWidth()` is only + * called when necessary. + * + * @param {number} width - The line width. + */ + setLineWidth( width ) { + + const { currentLineWidth, gl } = this; + + if ( width !== currentLineWidth ) { + + gl.lineWidth( width ); + + this.currentLineWidth = width; + + } + + } + + /** + * Defines the blending. + * + * This method caches the state so `gl.blendEquation()`, `gl.blendEquationSeparate()`, + * `gl.blendFunc()` and `gl.blendFuncSeparate()` are only called when necessary. + * + * @param {number} blending - The blending type. + * @param {number} blendEquation - The blending equation. + * @param {number} blendSrc - Only relevant for custom blending. The RGB source blending factor. + * @param {number} blendDst - Only relevant for custom blending. The RGB destination blending factor. + * @param {number} blendEquationAlpha - Only relevant for custom blending. The blending equation for alpha. + * @param {number} blendSrcAlpha - Only relevant for custom blending. The alpha source blending factor. + * @param {number} blendDstAlpha - Only relevant for custom blending. The alpha destination blending factor. + * @param {boolean} premultipliedAlpha - Whether premultiplied alpha is enabled or not. + */ + setBlending( blending, blendEquation, blendSrc, blendDst, blendEquationAlpha, blendSrcAlpha, blendDstAlpha, premultipliedAlpha ) { + + const { gl } = this; + + if ( blending === NoBlending ) { + + if ( this.currentBlendingEnabled === true ) { + + this.disable( gl.BLEND ); + this.currentBlendingEnabled = false; + + } + + return; + + } + + if ( this.currentBlendingEnabled === false ) { + + this.enable( gl.BLEND ); + this.currentBlendingEnabled = true; + + } + + if ( blending !== CustomBlending ) { + + if ( blending !== this.currentBlending || premultipliedAlpha !== this.currentPremultipledAlpha ) { + + if ( this.currentBlendEquation !== AddEquation || this.currentBlendEquationAlpha !== AddEquation ) { + + gl.blendEquation( gl.FUNC_ADD ); + + this.currentBlendEquation = AddEquation; + this.currentBlendEquationAlpha = AddEquation; + + } + + if ( premultipliedAlpha ) { + + switch ( blending ) { + + case NormalBlending: + gl.blendFuncSeparate( gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA ); + break; + + case AdditiveBlending: + gl.blendFunc( gl.ONE, gl.ONE ); + break; + + case SubtractiveBlending: + gl.blendFuncSeparate( gl.ZERO, gl.ONE_MINUS_SRC_COLOR, gl.ZERO, gl.ONE ); + break; + + case MultiplyBlending: + gl.blendFuncSeparate( gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA, gl.ZERO, gl.ONE ); + break; + + default: + console.error( 'THREE.WebGLState: Invalid blending: ', blending ); + break; + + } + + } else { + + switch ( blending ) { + + case NormalBlending: + gl.blendFuncSeparate( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA ); + break; + + case AdditiveBlending: + gl.blendFuncSeparate( gl.SRC_ALPHA, gl.ONE, gl.ONE, gl.ONE ); + break; + + case SubtractiveBlending: + console.error( 'THREE.WebGLState: SubtractiveBlending requires material.premultipliedAlpha = true' ); + break; + + case MultiplyBlending: + console.error( 'THREE.WebGLState: MultiplyBlending requires material.premultipliedAlpha = true' ); + break; + + default: + console.error( 'THREE.WebGLState: Invalid blending: ', blending ); + break; + + } + + } + + this.currentBlendSrc = null; + this.currentBlendDst = null; + this.currentBlendSrcAlpha = null; + this.currentBlendDstAlpha = null; + + this.currentBlending = blending; + this.currentPremultipledAlpha = premultipliedAlpha; + + } + + return; + + } + + // custom blending + + blendEquationAlpha = blendEquationAlpha || blendEquation; + blendSrcAlpha = blendSrcAlpha || blendSrc; + blendDstAlpha = blendDstAlpha || blendDst; + + if ( blendEquation !== this.currentBlendEquation || blendEquationAlpha !== this.currentBlendEquationAlpha ) { + + gl.blendEquationSeparate( equationToGL[ blendEquation ], equationToGL[ blendEquationAlpha ] ); + + this.currentBlendEquation = blendEquation; + this.currentBlendEquationAlpha = blendEquationAlpha; + + } + + if ( blendSrc !== this.currentBlendSrc || blendDst !== this.currentBlendDst || blendSrcAlpha !== this.currentBlendSrcAlpha || blendDstAlpha !== this.currentBlendDstAlpha ) { + + gl.blendFuncSeparate( factorToGL[ blendSrc ], factorToGL[ blendDst ], factorToGL[ blendSrcAlpha ], factorToGL[ blendDstAlpha ] ); + + this.currentBlendSrc = blendSrc; + this.currentBlendDst = blendDst; + this.currentBlendSrcAlpha = blendSrcAlpha; + this.currentBlendDstAlpha = blendDstAlpha; + + } + + this.currentBlending = blending; + this.currentPremultipledAlpha = false; + + } + + /** + * Specifies whether colors can be written when rendering + * into a framebuffer or not. + * + * This method caches the state so `gl.colorMask()` is only + * called when necessary. + * + * @param {boolean} colorMask - The color mask. + */ + setColorMask( colorMask ) { + + if ( this.currentColorMask !== colorMask ) { + + this.gl.colorMask( colorMask, colorMask, colorMask, colorMask ); + this.currentColorMask = colorMask; + + } + + } + + /** + * Specifies whether the depth test is enabled or not. + * + * @param {boolean} depthTest - Whether the depth test is enabled or not. + */ + setDepthTest( depthTest ) { + + const { gl } = this; + + if ( depthTest ) { + + this.enable( gl.DEPTH_TEST ); + + } else { + + this.disable( gl.DEPTH_TEST ); + + } + + } + + /** + * Specifies whether depth values can be written when rendering + * into a framebuffer or not. + * + * This method caches the state so `gl.depthMask()` is only + * called when necessary. + * + * @param {boolean} depthMask - The depth mask. + */ + setDepthMask( depthMask ) { + + if ( this.currentDepthMask !== depthMask ) { + + this.gl.depthMask( depthMask ); + this.currentDepthMask = depthMask; + + } + + } + + /** + * Specifies the depth compare function. + * + * This method caches the state so `gl.depthFunc()` is only + * called when necessary. + * + * @param {number} depthFunc - The depth compare function. + */ + setDepthFunc( depthFunc ) { + + if ( this.currentDepthFunc !== depthFunc ) { + + const { gl } = this; + + switch ( depthFunc ) { + + case NeverDepth: + + gl.depthFunc( gl.NEVER ); + break; + + case AlwaysDepth: + + gl.depthFunc( gl.ALWAYS ); + break; + + case LessDepth: + + gl.depthFunc( gl.LESS ); + break; + + case LessEqualDepth: + + gl.depthFunc( gl.LEQUAL ); + break; + + case EqualDepth: + + gl.depthFunc( gl.EQUAL ); + break; + + case GreaterEqualDepth: + + gl.depthFunc( gl.GEQUAL ); + break; + + case GreaterDepth: + + gl.depthFunc( gl.GREATER ); + break; + + case NotEqualDepth: + + gl.depthFunc( gl.NOTEQUAL ); + break; + + default: + + gl.depthFunc( gl.LEQUAL ); + + } + + this.currentDepthFunc = depthFunc; + + } + + } + + /** + * Specifies the scissor box. + * + * @param {number} x - The x-coordinate of the lower left corner of the viewport. + * @param {number} y - The y-coordinate of the lower left corner of the viewport. + * @param {number} width - The width of the viewport. + * @param {number} height - The height of the viewport. + * + */ + scissor( x, y, width, height ) { + + const scissor = this._tempVec4.set( x, y, width, height ); + + if ( this.currentScissor.equals( scissor ) === false ) { + + const { gl } = this; + + gl.scissor( scissor.x, scissor.y, scissor.z, scissor.w ); + this.currentScissor.copy( scissor ); + + } + + } + + /** + * Specifies the viewport. + * + * @param {number} x - The x-coordinate of the lower left corner of the viewport. + * @param {number} y - The y-coordinate of the lower left corner of the viewport. + * @param {number} width - The width of the viewport. + * @param {number} height - The height of the viewport. + * + */ + viewport( x, y, width, height ) { + + const viewport = this._tempVec4.set( x, y, width, height ); + + if ( this.currentViewport.equals( viewport ) === false ) { + + const { gl } = this; + + gl.viewport( viewport.x, viewport.y, viewport.z, viewport.w ); + this.currentViewport.copy( viewport ); + + } + + } + + /** + * Defines the scissor test. + * + * @param {boolean} boolean - Whether the scissor test should be enabled or not. + */ + setScissorTest( boolean ) { + + const gl = this.gl; + + if ( boolean ) { + + gl.enable( gl.SCISSOR_TEST ); + + } else { + + gl.disable( gl.SCISSOR_TEST ); + + } + + } + + /** + * Specifies whether the stencil test is enabled or not. + * + * @param {boolean} stencilTest - Whether the stencil test is enabled or not. + */ + setStencilTest( stencilTest ) { + + const { gl } = this; + + if ( stencilTest ) { + + this.enable( gl.STENCIL_TEST ); + + } else { + + this.disable( gl.STENCIL_TEST ); + + } + + } + + /** + * Specifies whether stencil values can be written when rendering + * into a framebuffer or not. + * + * This method caches the state so `gl.stencilMask()` is only + * called when necessary. + * + * @param {boolean} stencilMask - The stencil mask. + */ + setStencilMask( stencilMask ) { + + if ( this.currentStencilMask !== stencilMask ) { + + this.gl.stencilMask( stencilMask ); + this.currentStencilMask = stencilMask; + + } + + } + + /** + * Specifies whether the stencil test functions. + * + * This method caches the state so `gl.stencilFunc()` is only + * called when necessary. + * + * @param {number} stencilFunc - The stencil compare function. + * @param {number} stencilRef - The reference value for the stencil test. + * @param {number} stencilMask - A bit-wise mask that is used to AND the reference value and the stored stencil value when the test is done. + */ + setStencilFunc( stencilFunc, stencilRef, stencilMask ) { + + if ( this.currentStencilFunc !== stencilFunc || + this.currentStencilRef !== stencilRef || + this.currentStencilFuncMask !== stencilMask ) { + + this.gl.stencilFunc( stencilFunc, stencilRef, stencilMask ); + + this.currentStencilFunc = stencilFunc; + this.currentStencilRef = stencilRef; + this.currentStencilFuncMask = stencilMask; + + } + + } + + /** + * Specifies whether the stencil test operation. + * + * This method caches the state so `gl.stencilOp()` is only + * called when necessary. + * + * @param {number} stencilFail - The function to use when the stencil test fails. + * @param {number} stencilZFail - The function to use when the stencil test passes, but the depth test fail. + * @param {number} stencilZPass - The function to use when both the stencil test and the depth test pass, + * or when the stencil test passes and there is no depth buffer or depth testing is disabled. + */ + setStencilOp( stencilFail, stencilZFail, stencilZPass ) { + + if ( this.currentStencilFail !== stencilFail || + this.currentStencilZFail !== stencilZFail || + this.currentStencilZPass !== stencilZPass ) { + + this.gl.stencilOp( stencilFail, stencilZFail, stencilZPass ); + + this.currentStencilFail = stencilFail; + this.currentStencilZFail = stencilZFail; + this.currentStencilZPass = stencilZPass; + + } + + } + + /** + * Configures the WebGL state for the given material. + * + * @param {Material} material - The material to configure the state for. + * @param {number} frontFaceCW - Whether the front faces are counter-clockwise or not. + * @param {number} hardwareClippingPlanes - The number of hardware clipping planes. + */ + setMaterial( material, frontFaceCW, hardwareClippingPlanes ) { + + const { gl } = this; + + material.side === DoubleSide + ? this.disable( gl.CULL_FACE ) + : this.enable( gl.CULL_FACE ); + + let flipSided = ( material.side === BackSide ); + if ( frontFaceCW ) flipSided = ! flipSided; + + this.setFlipSided( flipSided ); + + ( material.blending === NormalBlending && material.transparent === false ) + ? this.setBlending( NoBlending ) + : this.setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst, material.blendEquationAlpha, material.blendSrcAlpha, material.blendDstAlpha, material.premultipliedAlpha ); + + this.setDepthFunc( material.depthFunc ); + this.setDepthTest( material.depthTest ); + this.setDepthMask( material.depthWrite ); + this.setColorMask( material.colorWrite ); + + const stencilWrite = material.stencilWrite; + this.setStencilTest( stencilWrite ); + if ( stencilWrite ) { + + this.setStencilMask( material.stencilWriteMask ); + this.setStencilFunc( material.stencilFunc, material.stencilRef, material.stencilFuncMask ); + this.setStencilOp( material.stencilFail, material.stencilZFail, material.stencilZPass ); + + } + + this.setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits ); + + material.alphaToCoverage === true && this.backend.renderer.samples > 1 + ? this.enable( gl.SAMPLE_ALPHA_TO_COVERAGE ) + : this.disable( gl.SAMPLE_ALPHA_TO_COVERAGE ); + + if ( hardwareClippingPlanes > 0 ) { + + if ( this.currentClippingPlanes !== hardwareClippingPlanes ) { + + const CLIP_DISTANCE0_WEBGL = 0x3000; + + for ( let i = 0; i < 8; i ++ ) { + + if ( i < hardwareClippingPlanes ) { + + this.enable( CLIP_DISTANCE0_WEBGL + i ); + + } else { + + this.disable( CLIP_DISTANCE0_WEBGL + i ); + + } + + } + + } + + } + + } + + /** + * Specifies the polygon offset. + * + * This method caches the state so `gl.polygonOffset()` is only + * called when necessary. + * + * @param {boolean} polygonOffset - Whether polygon offset is enabled or not. + * @param {number} factor - The scale factor for the variable depth offset for each polygon. + * @param {number} units - The multiplier by which an implementation-specific value is multiplied with to create a constant depth offset. + */ + setPolygonOffset( polygonOffset, factor, units ) { + + const { gl } = this; + + if ( polygonOffset ) { + + this.enable( gl.POLYGON_OFFSET_FILL ); + + if ( this.currentPolygonOffsetFactor !== factor || this.currentPolygonOffsetUnits !== units ) { + + gl.polygonOffset( factor, units ); + + this.currentPolygonOffsetFactor = factor; + this.currentPolygonOffsetUnits = units; + + } + + } else { + + this.disable( gl.POLYGON_OFFSET_FILL ); + + } + + } + + /** + * Defines the usage of the given WebGL program. + * + * This method caches the state so `gl.useProgram()` is only + * called when necessary. + * + * @param {WebGLProgram} program - The WebGL program to use. + * @return {boolean} Whether a program change has been executed or not. + */ + useProgram( program ) { + + if ( this.currentProgram !== program ) { + + this.gl.useProgram( program ); + + this.currentProgram = program; + + return true; + + } + + return false; + + } + + /** + * Sets the vertex state by binding the given VAO and element buffer. + * + * @param {WebGLVertexArrayObject} vao - The VAO. + * @param {WebGLBuffer} indexBuffer - The index buffer. + * @return {boolean} Whether a vertex state has been changed or not. + */ + setVertexState( vao, indexBuffer = null ) { + + const gl = this.gl; + + if ( this.currentVAO !== vao || this.currentIndex !== indexBuffer ) { + + gl.bindVertexArray( vao ); + + if ( indexBuffer !== null ) { + + gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, indexBuffer ); + + } + + this.currentVAO = vao; + this.currentIndex = indexBuffer; + + return true; + + } + + return false; + + } + + /** + * Resets the vertex array state by resetting the VAO and element buffer. + */ + resetVertexState() { + + const gl = this.gl; + + gl.bindVertexArray( null ); + gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, null ); + + this.currentVAO = null; + this.currentIndex = null; + + } + + // framebuffer + + + /** + * Binds the given framebuffer. + * + * This method caches the state so `gl.bindFramebuffer()` is only + * called when necessary. + * + * @param {number} target - The binding point (target). + * @param {WebGLFramebuffer} framebuffer - The WebGL framebuffer to bind. + * @return {boolean} Whether a bind has been executed or not. + */ + bindFramebuffer( target, framebuffer ) { + + const { gl, currentBoundFramebuffers } = this; + + if ( currentBoundFramebuffers[ target ] !== framebuffer ) { + + gl.bindFramebuffer( target, framebuffer ); + + currentBoundFramebuffers[ target ] = framebuffer; + + // gl.DRAW_FRAMEBUFFER is equivalent to gl.FRAMEBUFFER + + if ( target === gl.DRAW_FRAMEBUFFER ) { + + currentBoundFramebuffers[ gl.FRAMEBUFFER ] = framebuffer; + + } + + if ( target === gl.FRAMEBUFFER ) { + + currentBoundFramebuffers[ gl.DRAW_FRAMEBUFFER ] = framebuffer; + + } + + return true; + + } + + return false; + + } + + /** + * Defines draw buffers to which fragment colors are written into. + * Configures the MRT setup of custom framebuffers. + * + * This method caches the state so `gl.drawBuffers()` is only + * called when necessary. + * + * @param {RenderContext} renderContext - The render context. + * @param {WebGLFramebuffer} framebuffer - The WebGL framebuffer. + */ + drawBuffers( renderContext, framebuffer ) { + + const { gl } = this; + + let drawBuffers = []; + + let needsUpdate = false; + + if ( renderContext.textures !== null ) { + + drawBuffers = this.currentDrawbuffers.get( framebuffer ); + + if ( drawBuffers === undefined ) { + + drawBuffers = []; + this.currentDrawbuffers.set( framebuffer, drawBuffers ); + + } + + + const textures = renderContext.textures; + + if ( drawBuffers.length !== textures.length || drawBuffers[ 0 ] !== gl.COLOR_ATTACHMENT0 ) { + + for ( let i = 0, il = textures.length; i < il; i ++ ) { + + drawBuffers[ i ] = gl.COLOR_ATTACHMENT0 + i; + + } + + drawBuffers.length = textures.length; + + needsUpdate = true; + + } + + + } else { + + if ( drawBuffers[ 0 ] !== gl.BACK ) { + + drawBuffers[ 0 ] = gl.BACK; + + needsUpdate = true; + + } + + } + + if ( needsUpdate ) { + + gl.drawBuffers( drawBuffers ); + + } + + } + + + // texture + + /** + * Makes the given texture unit active. + * + * This method caches the state so `gl.activeTexture()` is only + * called when necessary. + * + * @param {number} webglSlot - The texture unit to make active. + */ + activeTexture( webglSlot ) { + + const { gl, currentTextureSlot, maxTextures } = this; + + if ( webglSlot === undefined ) webglSlot = gl.TEXTURE0 + maxTextures - 1; + + if ( currentTextureSlot !== webglSlot ) { + + gl.activeTexture( webglSlot ); + this.currentTextureSlot = webglSlot; + + } + + } + + /** + * Binds the given WebGL texture to a target. + * + * This method caches the state so `gl.bindTexture()` is only + * called when necessary. + * + * @param {number} webglType - The binding point (target). + * @param {WebGLTexture} webglTexture - The WebGL texture to bind. + * @param {number} webglSlot - The texture. + */ + bindTexture( webglType, webglTexture, webglSlot ) { + + const { gl, currentTextureSlot, currentBoundTextures, maxTextures } = this; + + if ( webglSlot === undefined ) { + + if ( currentTextureSlot === null ) { + + webglSlot = gl.TEXTURE0 + maxTextures - 1; + + } else { + + webglSlot = currentTextureSlot; + + } + + } + + let boundTexture = currentBoundTextures[ webglSlot ]; + + if ( boundTexture === undefined ) { + + boundTexture = { type: undefined, texture: undefined }; + currentBoundTextures[ webglSlot ] = boundTexture; + + } + + if ( boundTexture.type !== webglType || boundTexture.texture !== webglTexture ) { + + if ( currentTextureSlot !== webglSlot ) { + + gl.activeTexture( webglSlot ); + this.currentTextureSlot = webglSlot; + + } + + gl.bindTexture( webglType, webglTexture ); + + boundTexture.type = webglType; + boundTexture.texture = webglTexture; + + } + + } + + /** + * Binds a given WebGL buffer to a given binding point (target) at a given index. + * + * This method caches the state so `gl.bindBufferBase()` is only + * called when necessary. + * + * @param {number} target - The target for the bind operation. + * @param {number} index - The index of the target. + * @param {WebGLBuffer} buffer - The WebGL buffer. + * @return {boolean} Whether a bind has been executed or not. + */ + bindBufferBase( target, index, buffer ) { + + const { gl } = this; + + const key = `${target}-${index}`; + + if ( this.currentBoundBufferBases[ key ] !== buffer ) { + + gl.bindBufferBase( target, index, buffer ); + this.currentBoundBufferBases[ key ] = buffer; + + return true; + + } + + return false; + + } + + + /** + * Unbinds the current bound texture. + * + * This method caches the state so `gl.bindTexture()` is only + * called when necessary. + */ + unbindTexture() { + + const { gl, currentTextureSlot, currentBoundTextures } = this; + + const boundTexture = currentBoundTextures[ currentTextureSlot ]; + + if ( boundTexture !== undefined && boundTexture.type !== undefined ) { + + gl.bindTexture( boundTexture.type, null ); + + boundTexture.type = undefined; + boundTexture.texture = undefined; + + } + + } + +} + +/** + * A WebGL 2 backend utility module with common helpers. + * + * @private + */ +class WebGLUtils { + + /** + * Constructs a new utility object. + * + * @param {WebGLBackend} backend - The WebGL 2 backend. + */ + constructor( backend ) { + + /** + * A reference to the WebGL 2 backend. + * + * @type {WebGLBackend} + */ + this.backend = backend; + + /** + * A reference to the rendering context. + * + * @type {WebGL2RenderingContext} + */ + this.gl = this.backend.gl; + + /** + * A reference to a backend module holding extension-related + * utility functions. + * + * @type {WebGLExtensions} + */ + this.extensions = backend.extensions; + + } + + /** + * Converts the given three.js constant into a WebGL constant. + * The method currently supports the conversion of texture formats + * and types. + * + * @param {number} p - The three.js constant. + * @param {string} [colorSpace=NoColorSpace] - The color space. + * @return {?number} The corresponding WebGL constant. + */ + convert( p, colorSpace = NoColorSpace ) { + + const { gl, extensions } = this; + + let extension; + + const transfer = ColorManagement.getTransfer( colorSpace ); + + if ( p === UnsignedByteType ) return gl.UNSIGNED_BYTE; + if ( p === UnsignedShort4444Type ) return gl.UNSIGNED_SHORT_4_4_4_4; + if ( p === UnsignedShort5551Type ) return gl.UNSIGNED_SHORT_5_5_5_1; + if ( p === UnsignedInt5999Type ) return gl.UNSIGNED_INT_5_9_9_9_REV; + + if ( p === ByteType ) return gl.BYTE; + if ( p === ShortType ) return gl.SHORT; + if ( p === UnsignedShortType ) return gl.UNSIGNED_SHORT; + if ( p === IntType ) return gl.INT; + if ( p === UnsignedIntType ) return gl.UNSIGNED_INT; + if ( p === FloatType ) return gl.FLOAT; + + if ( p === HalfFloatType ) { + + return gl.HALF_FLOAT; + + } + + if ( p === AlphaFormat ) return gl.ALPHA; + if ( p === RGBFormat ) return gl.RGB; + if ( p === RGBAFormat ) return gl.RGBA; + if ( p === DepthFormat ) return gl.DEPTH_COMPONENT; + if ( p === DepthStencilFormat ) return gl.DEPTH_STENCIL; + + // WebGL2 formats. + + if ( p === RedFormat ) return gl.RED; + if ( p === RedIntegerFormat ) return gl.RED_INTEGER; + if ( p === RGFormat ) return gl.RG; + if ( p === RGIntegerFormat ) return gl.RG_INTEGER; + if ( p === RGBAIntegerFormat ) return gl.RGBA_INTEGER; + + // S3TC + + if ( p === RGB_S3TC_DXT1_Format || p === RGBA_S3TC_DXT1_Format || p === RGBA_S3TC_DXT3_Format || p === RGBA_S3TC_DXT5_Format ) { + + if ( transfer === SRGBTransfer ) { + + extension = extensions.get( 'WEBGL_compressed_texture_s3tc_srgb' ); + + if ( extension !== null ) { + + if ( p === RGB_S3TC_DXT1_Format ) return extension.COMPRESSED_SRGB_S3TC_DXT1_EXT; + if ( p === RGBA_S3TC_DXT1_Format ) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT; + if ( p === RGBA_S3TC_DXT3_Format ) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT; + if ( p === RGBA_S3TC_DXT5_Format ) return extension.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT; + + } else { + + return null; + + } + + } else { + + extension = extensions.get( 'WEBGL_compressed_texture_s3tc' ); + + if ( extension !== null ) { + + if ( p === RGB_S3TC_DXT1_Format ) return extension.COMPRESSED_RGB_S3TC_DXT1_EXT; + if ( p === RGBA_S3TC_DXT1_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT1_EXT; + if ( p === RGBA_S3TC_DXT3_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT3_EXT; + if ( p === RGBA_S3TC_DXT5_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT5_EXT; + + } else { + + return null; + + } + + } + + } + + // PVRTC + + if ( p === RGB_PVRTC_4BPPV1_Format || p === RGB_PVRTC_2BPPV1_Format || p === RGBA_PVRTC_4BPPV1_Format || p === RGBA_PVRTC_2BPPV1_Format ) { + + extension = extensions.get( 'WEBGL_compressed_texture_pvrtc' ); + + if ( extension !== null ) { + + if ( p === RGB_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_4BPPV1_IMG; + if ( p === RGB_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_2BPPV1_IMG; + if ( p === RGBA_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; + if ( p === RGBA_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG; + + } else { + + return null; + + } + + } + + // ETC + + if ( p === RGB_ETC1_Format || p === RGB_ETC2_Format || p === RGBA_ETC2_EAC_Format ) { + + extension = extensions.get( 'WEBGL_compressed_texture_etc' ); + + if ( extension !== null ) { + + if ( p === RGB_ETC1_Format || p === RGB_ETC2_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ETC2 : extension.COMPRESSED_RGB8_ETC2; + if ( p === RGBA_ETC2_EAC_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ETC2_EAC : extension.COMPRESSED_RGBA8_ETC2_EAC; + + } else { + + return null; + + } + + } + + // ASTC + + if ( p === RGBA_ASTC_4x4_Format || p === RGBA_ASTC_5x4_Format || p === RGBA_ASTC_5x5_Format || + p === RGBA_ASTC_6x5_Format || p === RGBA_ASTC_6x6_Format || p === RGBA_ASTC_8x5_Format || + p === RGBA_ASTC_8x6_Format || p === RGBA_ASTC_8x8_Format || p === RGBA_ASTC_10x5_Format || + p === RGBA_ASTC_10x6_Format || p === RGBA_ASTC_10x8_Format || p === RGBA_ASTC_10x10_Format || + p === RGBA_ASTC_12x10_Format || p === RGBA_ASTC_12x12_Format ) { + + extension = extensions.get( 'WEBGL_compressed_texture_astc' ); + + if ( extension !== null ) { + + if ( p === RGBA_ASTC_4x4_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR : extension.COMPRESSED_RGBA_ASTC_4x4_KHR; + if ( p === RGBA_ASTC_5x4_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR : extension.COMPRESSED_RGBA_ASTC_5x4_KHR; + if ( p === RGBA_ASTC_5x5_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR : extension.COMPRESSED_RGBA_ASTC_5x5_KHR; + if ( p === RGBA_ASTC_6x5_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR : extension.COMPRESSED_RGBA_ASTC_6x5_KHR; + if ( p === RGBA_ASTC_6x6_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR : extension.COMPRESSED_RGBA_ASTC_6x6_KHR; + if ( p === RGBA_ASTC_8x5_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR : extension.COMPRESSED_RGBA_ASTC_8x5_KHR; + if ( p === RGBA_ASTC_8x6_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR : extension.COMPRESSED_RGBA_ASTC_8x6_KHR; + if ( p === RGBA_ASTC_8x8_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR : extension.COMPRESSED_RGBA_ASTC_8x8_KHR; + if ( p === RGBA_ASTC_10x5_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR : extension.COMPRESSED_RGBA_ASTC_10x5_KHR; + if ( p === RGBA_ASTC_10x6_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR : extension.COMPRESSED_RGBA_ASTC_10x6_KHR; + if ( p === RGBA_ASTC_10x8_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR : extension.COMPRESSED_RGBA_ASTC_10x8_KHR; + if ( p === RGBA_ASTC_10x10_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR : extension.COMPRESSED_RGBA_ASTC_10x10_KHR; + if ( p === RGBA_ASTC_12x10_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR : extension.COMPRESSED_RGBA_ASTC_12x10_KHR; + if ( p === RGBA_ASTC_12x12_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR : extension.COMPRESSED_RGBA_ASTC_12x12_KHR; + + } else { + + return null; + + } + + } + + // BPTC + + if ( p === RGBA_BPTC_Format ) { + + extension = extensions.get( 'EXT_texture_compression_bptc' ); + + if ( extension !== null ) { + + if ( p === RGBA_BPTC_Format ) return ( transfer === SRGBTransfer ) ? extension.COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT : extension.COMPRESSED_RGBA_BPTC_UNORM_EXT; + + } else { + + return null; + + } + + } + + // RGTC + + if ( p === RED_RGTC1_Format || p === SIGNED_RED_RGTC1_Format || p === RED_GREEN_RGTC2_Format || p === SIGNED_RED_GREEN_RGTC2_Format ) { + + extension = extensions.get( 'EXT_texture_compression_rgtc' ); + + if ( extension !== null ) { + + if ( p === RGBA_BPTC_Format ) return extension.COMPRESSED_RED_RGTC1_EXT; + if ( p === SIGNED_RED_RGTC1_Format ) return extension.COMPRESSED_SIGNED_RED_RGTC1_EXT; + if ( p === RED_GREEN_RGTC2_Format ) return extension.COMPRESSED_RED_GREEN_RGTC2_EXT; + if ( p === SIGNED_RED_GREEN_RGTC2_Format ) return extension.COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT; + + } else { + + return null; + + } + + } + + // + + if ( p === UnsignedInt248Type ) { + + return gl.UNSIGNED_INT_24_8; + + } + + // if "p" can't be resolved, assume the user defines a WebGL constant as a string (fallback/workaround for packed RGB formats) + + return ( gl[ p ] !== undefined ) ? gl[ p ] : null; + + } + + /** + * This method can be used to synchronize the CPU with the GPU by waiting until + * ongoing GPU commands have been completed. + * + * @private + * @return {Promise} A promise that resolves when all ongoing GPU commands have been completed. + */ + _clientWaitAsync() { + + const { gl } = this; + + const sync = gl.fenceSync( gl.SYNC_GPU_COMMANDS_COMPLETE, 0 ); + + gl.flush(); + + return new Promise( ( resolve, reject ) => { + + function test() { + + const res = gl.clientWaitSync( sync, gl.SYNC_FLUSH_COMMANDS_BIT, 0 ); + + if ( res === gl.WAIT_FAILED ) { + + gl.deleteSync( sync ); + + reject(); + return; + + } + + if ( res === gl.TIMEOUT_EXPIRED ) { + + requestAnimationFrame( test ); + return; + + } + + gl.deleteSync( sync ); + + resolve(); + + } + + test(); + + } ); + + } + +} + +let initialized = false, wrappingToGL, filterToGL, compareToGL; + +/** + * A WebGL 2 backend utility module for managing textures. + * + * @private + */ +class WebGLTextureUtils { + + /** + * Constructs a new utility object. + * + * @param {WebGLBackend} backend - The WebGL 2 backend. + */ + constructor( backend ) { + + /** + * A reference to the WebGL 2 backend. + * + * @type {WebGLBackend} + */ + this.backend = backend; + + /** + * A reference to the rendering context. + * + * @type {WebGL2RenderingContext} + */ + this.gl = backend.gl; + + /** + * A reference to a backend module holding extension-related + * utility functions. + * + * @type {WebGLExtensions} + */ + this.extensions = backend.extensions; + + /** + * A dictionary for managing default textures. The key + * is the binding point (target), the value the WEbGL texture object. + * + * @type {Object} + */ + this.defaultTextures = {}; + + if ( initialized === false ) { + + this._init(); + + initialized = true; + + } + + } + + /** + * Inits the state of the utility. + * + * @private + */ + _init() { + + const gl = this.gl; + + // Store only WebGL constants here. + + wrappingToGL = { + [ RepeatWrapping ]: gl.REPEAT, + [ ClampToEdgeWrapping ]: gl.CLAMP_TO_EDGE, + [ MirroredRepeatWrapping ]: gl.MIRRORED_REPEAT + }; + + filterToGL = { + [ NearestFilter ]: gl.NEAREST, + [ NearestMipmapNearestFilter ]: gl.NEAREST_MIPMAP_NEAREST, + [ NearestMipmapLinearFilter ]: gl.NEAREST_MIPMAP_LINEAR, + + [ LinearFilter ]: gl.LINEAR, + [ LinearMipmapNearestFilter ]: gl.LINEAR_MIPMAP_NEAREST, + [ LinearMipmapLinearFilter ]: gl.LINEAR_MIPMAP_LINEAR + }; + + compareToGL = { + [ NeverCompare ]: gl.NEVER, + [ AlwaysCompare ]: gl.ALWAYS, + [ LessCompare ]: gl.LESS, + [ LessEqualCompare ]: gl.LEQUAL, + [ EqualCompare ]: gl.EQUAL, + [ GreaterEqualCompare ]: gl.GEQUAL, + [ GreaterCompare ]: gl.GREATER, + [ NotEqualCompare ]: gl.NOTEQUAL + }; + + } + + /** + * Returns the native texture type for the given texture. + * + * @param {Texture} texture - The texture. + * @return {GLenum} The native texture type. + */ + getGLTextureType( texture ) { + + const { gl } = this; + + let glTextureType; + + if ( texture.isCubeTexture === true ) { + + glTextureType = gl.TEXTURE_CUBE_MAP; + + } else if ( texture.isArrayTexture === true || texture.isDataArrayTexture === true || texture.isCompressedArrayTexture === true ) { + + glTextureType = gl.TEXTURE_2D_ARRAY; + + } else if ( texture.isData3DTexture === true ) { // TODO: isCompressed3DTexture, wait for #26642 + + glTextureType = gl.TEXTURE_3D; + + } else { + + glTextureType = gl.TEXTURE_2D; + + + } + + return glTextureType; + + } + + /** + * Returns the native texture type for the given texture. + * + * @param {?string} internalFormatName - The internal format name. When `null`, the internal format is derived from the subsequent parameters. + * @param {GLenum} glFormat - The WebGL format. + * @param {GLenum} glType - The WebGL type. + * @param {string} colorSpace - The texture's color space. + * @param {boolean} [forceLinearTransfer=false] - Whether to force a linear transfer or not. + * @return {GLenum} The internal format. + */ + getInternalFormat( internalFormatName, glFormat, glType, colorSpace, forceLinearTransfer = false ) { + + const { gl, extensions } = this; + + if ( internalFormatName !== null ) { + + if ( gl[ internalFormatName ] !== undefined ) return gl[ internalFormatName ]; + + console.warn( 'THREE.WebGLRenderer: Attempt to use non-existing WebGL internal format \'' + internalFormatName + '\'' ); + + } + + let internalFormat = glFormat; + + if ( glFormat === gl.RED ) { + + if ( glType === gl.FLOAT ) internalFormat = gl.R32F; + if ( glType === gl.HALF_FLOAT ) internalFormat = gl.R16F; + if ( glType === gl.UNSIGNED_BYTE ) internalFormat = gl.R8; + if ( glType === gl.UNSIGNED_SHORT ) internalFormat = gl.R16; + if ( glType === gl.UNSIGNED_INT ) internalFormat = gl.R32UI; + if ( glType === gl.BYTE ) internalFormat = gl.R8I; + if ( glType === gl.SHORT ) internalFormat = gl.R16I; + if ( glType === gl.INT ) internalFormat = gl.R32I; + + } + + if ( glFormat === gl.RED_INTEGER ) { + + if ( glType === gl.UNSIGNED_BYTE ) internalFormat = gl.R8UI; + if ( glType === gl.UNSIGNED_SHORT ) internalFormat = gl.R16UI; + if ( glType === gl.UNSIGNED_INT ) internalFormat = gl.R32UI; + if ( glType === gl.BYTE ) internalFormat = gl.R8I; + if ( glType === gl.SHORT ) internalFormat = gl.R16I; + if ( glType === gl.INT ) internalFormat = gl.R32I; + + } + + if ( glFormat === gl.RG ) { + + if ( glType === gl.FLOAT ) internalFormat = gl.RG32F; + if ( glType === gl.HALF_FLOAT ) internalFormat = gl.RG16F; + if ( glType === gl.UNSIGNED_BYTE ) internalFormat = gl.RG8; + if ( glType === gl.UNSIGNED_SHORT ) internalFormat = gl.RG16; + if ( glType === gl.UNSIGNED_INT ) internalFormat = gl.RG32UI; + if ( glType === gl.BYTE ) internalFormat = gl.RG8I; + if ( glType === gl.SHORT ) internalFormat = gl.RG16I; + if ( glType === gl.INT ) internalFormat = gl.RG32I; + + } + + if ( glFormat === gl.RG_INTEGER ) { + + if ( glType === gl.UNSIGNED_BYTE ) internalFormat = gl.RG8UI; + if ( glType === gl.UNSIGNED_SHORT ) internalFormat = gl.RG16UI; + if ( glType === gl.UNSIGNED_INT ) internalFormat = gl.RG32UI; + if ( glType === gl.BYTE ) internalFormat = gl.RG8I; + if ( glType === gl.SHORT ) internalFormat = gl.RG16I; + if ( glType === gl.INT ) internalFormat = gl.RG32I; + + } + + if ( glFormat === gl.RGB ) { + + const transfer = forceLinearTransfer ? LinearTransfer : ColorManagement.getTransfer( colorSpace ); + + if ( glType === gl.FLOAT ) internalFormat = gl.RGB32F; + if ( glType === gl.HALF_FLOAT ) internalFormat = gl.RGB16F; + if ( glType === gl.UNSIGNED_BYTE ) internalFormat = gl.RGB8; + if ( glType === gl.UNSIGNED_SHORT ) internalFormat = gl.RGB16; + if ( glType === gl.UNSIGNED_INT ) internalFormat = gl.RGB32UI; + if ( glType === gl.BYTE ) internalFormat = gl.RGB8I; + if ( glType === gl.SHORT ) internalFormat = gl.RGB16I; + if ( glType === gl.INT ) internalFormat = gl.RGB32I; + if ( glType === gl.UNSIGNED_BYTE ) internalFormat = ( transfer === SRGBTransfer ) ? gl.SRGB8 : gl.RGB8; + if ( glType === gl.UNSIGNED_SHORT_5_6_5 ) internalFormat = gl.RGB565; + if ( glType === gl.UNSIGNED_SHORT_5_5_5_1 ) internalFormat = gl.RGB5_A1; + if ( glType === gl.UNSIGNED_SHORT_4_4_4_4 ) internalFormat = gl.RGB4; + if ( glType === gl.UNSIGNED_INT_5_9_9_9_REV ) internalFormat = gl.RGB9_E5; + + } + + if ( glFormat === gl.RGB_INTEGER ) { + + if ( glType === gl.UNSIGNED_BYTE ) internalFormat = gl.RGB8UI; + if ( glType === gl.UNSIGNED_SHORT ) internalFormat = gl.RGB16UI; + if ( glType === gl.UNSIGNED_INT ) internalFormat = gl.RGB32UI; + if ( glType === gl.BYTE ) internalFormat = gl.RGB8I; + if ( glType === gl.SHORT ) internalFormat = gl.RGB16I; + if ( glType === gl.INT ) internalFormat = gl.RGB32I; + + } + + if ( glFormat === gl.RGBA ) { + + const transfer = forceLinearTransfer ? LinearTransfer : ColorManagement.getTransfer( colorSpace ); + + if ( glType === gl.FLOAT ) internalFormat = gl.RGBA32F; + if ( glType === gl.HALF_FLOAT ) internalFormat = gl.RGBA16F; + if ( glType === gl.UNSIGNED_BYTE ) internalFormat = gl.RGBA8; + if ( glType === gl.UNSIGNED_SHORT ) internalFormat = gl.RGBA16; + if ( glType === gl.UNSIGNED_INT ) internalFormat = gl.RGBA32UI; + if ( glType === gl.BYTE ) internalFormat = gl.RGBA8I; + if ( glType === gl.SHORT ) internalFormat = gl.RGBA16I; + if ( glType === gl.INT ) internalFormat = gl.RGBA32I; + if ( glType === gl.UNSIGNED_BYTE ) internalFormat = ( transfer === SRGBTransfer ) ? gl.SRGB8_ALPHA8 : gl.RGBA8; + if ( glType === gl.UNSIGNED_SHORT_4_4_4_4 ) internalFormat = gl.RGBA4; + if ( glType === gl.UNSIGNED_SHORT_5_5_5_1 ) internalFormat = gl.RGB5_A1; + + } + + if ( glFormat === gl.RGBA_INTEGER ) { + + if ( glType === gl.UNSIGNED_BYTE ) internalFormat = gl.RGBA8UI; + if ( glType === gl.UNSIGNED_SHORT ) internalFormat = gl.RGBA16UI; + if ( glType === gl.UNSIGNED_INT ) internalFormat = gl.RGBA32UI; + if ( glType === gl.BYTE ) internalFormat = gl.RGBA8I; + if ( glType === gl.SHORT ) internalFormat = gl.RGBA16I; + if ( glType === gl.INT ) internalFormat = gl.RGBA32I; + + } + + if ( glFormat === gl.DEPTH_COMPONENT ) { + + if ( glType === gl.UNSIGNED_SHORT ) internalFormat = gl.DEPTH_COMPONENT16; + if ( glType === gl.UNSIGNED_INT ) internalFormat = gl.DEPTH_COMPONENT24; + if ( glType === gl.FLOAT ) internalFormat = gl.DEPTH_COMPONENT32F; + + } + + if ( glFormat === gl.DEPTH_STENCIL ) { + + if ( glType === gl.UNSIGNED_INT_24_8 ) internalFormat = gl.DEPTH24_STENCIL8; + + } + + if ( internalFormat === gl.R16F || internalFormat === gl.R32F || + internalFormat === gl.RG16F || internalFormat === gl.RG32F || + internalFormat === gl.RGBA16F || internalFormat === gl.RGBA32F ) { + + extensions.get( 'EXT_color_buffer_float' ); + + } + + return internalFormat; + + } + + /** + * Sets the texture parameters for the given texture. + * + * @param {GLenum} textureType - The texture type. + * @param {Texture} texture - The texture. + */ + setTextureParameters( textureType, texture ) { + + const { gl, extensions, backend } = this; + + const workingPrimaries = ColorManagement.getPrimaries( ColorManagement.workingColorSpace ); + const texturePrimaries = texture.colorSpace === NoColorSpace ? null : ColorManagement.getPrimaries( texture.colorSpace ); + const unpackConversion = texture.colorSpace === NoColorSpace || workingPrimaries === texturePrimaries ? gl.NONE : gl.BROWSER_DEFAULT_WEBGL; + + gl.pixelStorei( gl.UNPACK_FLIP_Y_WEBGL, texture.flipY ); + gl.pixelStorei( gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha ); + gl.pixelStorei( gl.UNPACK_ALIGNMENT, texture.unpackAlignment ); + gl.pixelStorei( gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, unpackConversion ); + + gl.texParameteri( textureType, gl.TEXTURE_WRAP_S, wrappingToGL[ texture.wrapS ] ); + gl.texParameteri( textureType, gl.TEXTURE_WRAP_T, wrappingToGL[ texture.wrapT ] ); + + if ( textureType === gl.TEXTURE_3D || textureType === gl.TEXTURE_2D_ARRAY ) { + + // WebGL 2 does not support wrapping for depth 2D array textures + if ( ! texture.isArrayTexture ) { + + gl.texParameteri( textureType, gl.TEXTURE_WRAP_R, wrappingToGL[ texture.wrapR ] ); + + } + + } + + gl.texParameteri( textureType, gl.TEXTURE_MAG_FILTER, filterToGL[ texture.magFilter ] ); + + + const hasMipmaps = texture.mipmaps !== undefined && texture.mipmaps.length > 0; + + // follow WebGPU backend mapping for texture filtering + const minFilter = texture.minFilter === LinearFilter && hasMipmaps ? LinearMipmapLinearFilter : texture.minFilter; + + gl.texParameteri( textureType, gl.TEXTURE_MIN_FILTER, filterToGL[ minFilter ] ); + + if ( texture.compareFunction ) { + + gl.texParameteri( textureType, gl.TEXTURE_COMPARE_MODE, gl.COMPARE_REF_TO_TEXTURE ); + gl.texParameteri( textureType, gl.TEXTURE_COMPARE_FUNC, compareToGL[ texture.compareFunction ] ); + + } + + if ( extensions.has( 'EXT_texture_filter_anisotropic' ) === true ) { + + if ( texture.magFilter === NearestFilter ) return; + if ( texture.minFilter !== NearestMipmapLinearFilter && texture.minFilter !== LinearMipmapLinearFilter ) return; + if ( texture.type === FloatType && extensions.has( 'OES_texture_float_linear' ) === false ) return; // verify extension for WebGL 1 and WebGL 2 + + if ( texture.anisotropy > 1 ) { + + const extension = extensions.get( 'EXT_texture_filter_anisotropic' ); + gl.texParameterf( textureType, extension.TEXTURE_MAX_ANISOTROPY_EXT, Math.min( texture.anisotropy, backend.getMaxAnisotropy() ) ); + + } + + } + + } + + /** + * Creates a default texture for the given texture that can be used + * as a placeholder until the actual texture is ready for usage. + * + * @param {Texture} texture - The texture to create a default texture for. + */ + createDefaultTexture( texture ) { + + const { gl, backend, defaultTextures } = this; + + + const glTextureType = this.getGLTextureType( texture ); + + let textureGPU = defaultTextures[ glTextureType ]; + + if ( textureGPU === undefined ) { + + textureGPU = gl.createTexture(); + + backend.state.bindTexture( glTextureType, textureGPU ); + gl.texParameteri( glTextureType, gl.TEXTURE_MIN_FILTER, gl.NEAREST ); + gl.texParameteri( glTextureType, gl.TEXTURE_MAG_FILTER, gl.NEAREST ); + + // gl.texImage2D( glTextureType, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, data ); + + defaultTextures[ glTextureType ] = textureGPU; + + } + + backend.set( texture, { + textureGPU, + glTextureType, + isDefault: true + } ); + + } + + /** + * Defines a texture on the GPU for the given texture object. + * + * @param {Texture} texture - The texture. + * @param {Object} [options={}] - Optional configuration parameter. + * @return {undefined} + */ + createTexture( texture, options ) { + + const { gl, backend } = this; + const { levels, width, height, depth } = options; + + const glFormat = backend.utils.convert( texture.format, texture.colorSpace ); + const glType = backend.utils.convert( texture.type ); + const glInternalFormat = this.getInternalFormat( texture.internalFormat, glFormat, glType, texture.colorSpace, texture.isVideoTexture ); + + const textureGPU = gl.createTexture(); + const glTextureType = this.getGLTextureType( texture ); + + backend.state.bindTexture( glTextureType, textureGPU ); + + this.setTextureParameters( glTextureType, texture ); + + if ( texture.isArrayTexture || texture.isDataArrayTexture || texture.isCompressedArrayTexture ) { + + gl.texStorage3D( gl.TEXTURE_2D_ARRAY, levels, glInternalFormat, width, height, depth ); + + } else if ( texture.isData3DTexture ) { + + gl.texStorage3D( gl.TEXTURE_3D, levels, glInternalFormat, width, height, depth ); + + } else if ( ! texture.isVideoTexture ) { + + gl.texStorage2D( glTextureType, levels, glInternalFormat, width, height ); + + } + + backend.set( texture, { + textureGPU, + glTextureType, + glFormat, + glType, + glInternalFormat + } ); + + } + + /** + * Uploads texture buffer data to the GPU memory. + * + * @param {WebGLBuffer} buffer - The buffer data. + * @param {Texture} texture - The texture, + */ + copyBufferToTexture( buffer, texture ) { + + const { gl, backend } = this; + + const { textureGPU, glTextureType, glFormat, glType } = backend.get( texture ); + + const { width, height } = texture.source.data; + + gl.bindBuffer( gl.PIXEL_UNPACK_BUFFER, buffer ); + + backend.state.bindTexture( glTextureType, textureGPU ); + + gl.pixelStorei( gl.UNPACK_FLIP_Y_WEBGL, false ); + gl.pixelStorei( gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false ); + gl.texSubImage2D( glTextureType, 0, 0, 0, width, height, glFormat, glType, 0 ); + + gl.bindBuffer( gl.PIXEL_UNPACK_BUFFER, null ); + + backend.state.unbindTexture(); + // debug + // const framebuffer = gl.createFramebuffer(); + // gl.bindFramebuffer( gl.FRAMEBUFFER, framebuffer ); + // gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, glTextureType, textureGPU, 0 ); + + // const readout = new Float32Array( width * height * 4 ); + + // const altFormat = gl.getParameter( gl.IMPLEMENTATION_COLOR_READ_FORMAT ); + // const altType = gl.getParameter( gl.IMPLEMENTATION_COLOR_READ_TYPE ); + + // gl.readPixels( 0, 0, width, height, altFormat, altType, readout ); + // gl.bindFramebuffer( gl.FRAMEBUFFER, null ); + // console.log( readout ); + + } + + /** + * Uploads the updated texture data to the GPU. + * + * @param {Texture} texture - The texture. + * @param {Object} [options={}] - Optional configuration parameter. + */ + updateTexture( texture, options ) { + + const { gl } = this; + const { width, height } = options; + const { textureGPU, glTextureType, glFormat, glType, glInternalFormat } = this.backend.get( texture ); + + if ( texture.isRenderTargetTexture || ( textureGPU === undefined /* unsupported texture format */ ) ) + return; + + this.backend.state.bindTexture( glTextureType, textureGPU ); + + this.setTextureParameters( glTextureType, texture ); + + if ( texture.isCompressedTexture ) { + + const mipmaps = texture.mipmaps; + const image = options.image; + + for ( let i = 0; i < mipmaps.length; i ++ ) { + + const mipmap = mipmaps[ i ]; + + if ( texture.isCompressedArrayTexture ) { + + + if ( texture.format !== gl.RGBA ) { + + if ( glFormat !== null ) { + + gl.compressedTexSubImage3D( gl.TEXTURE_2D_ARRAY, i, 0, 0, 0, mipmap.width, mipmap.height, image.depth, glFormat, mipmap.data ); + + } else { + + console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()' ); + + } + + } else { + + gl.texSubImage3D( gl.TEXTURE_2D_ARRAY, i, 0, 0, 0, mipmap.width, mipmap.height, image.depth, glFormat, glType, mipmap.data ); + + } + + } else { + + if ( glFormat !== null ) { + + gl.compressedTexSubImage2D( gl.TEXTURE_2D, i, 0, 0, mipmap.width, mipmap.height, glFormat, mipmap.data ); + + } else { + + console.warn( 'Unsupported compressed texture format' ); + + } + + } + + } + + + } else if ( texture.isCubeTexture ) { + + const images = options.images; + + for ( let i = 0; i < 6; i ++ ) { + + const image = getImage( images[ i ] ); + + gl.texSubImage2D( gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, 0, 0, width, height, glFormat, glType, image ); + + } + + } else if ( texture.isDataArrayTexture || texture.isArrayTexture ) { + + const image = options.image; + + gl.texSubImage3D( gl.TEXTURE_2D_ARRAY, 0, 0, 0, 0, image.width, image.height, image.depth, glFormat, glType, image.data ); + + } else if ( texture.isData3DTexture ) { + + const image = options.image; + + gl.texSubImage3D( gl.TEXTURE_3D, 0, 0, 0, 0, image.width, image.height, image.depth, glFormat, glType, image.data ); + + } else if ( texture.isVideoTexture ) { + + texture.update(); + + gl.texImage2D( glTextureType, 0, glInternalFormat, glFormat, glType, options.image ); + + + } else { + + const image = getImage( options.image ); + + gl.texSubImage2D( glTextureType, 0, 0, 0, width, height, glFormat, glType, image ); + + } + + } + + /** + * Generates mipmaps for the given texture. + * + * @param {Texture} texture - The texture. + */ + generateMipmaps( texture ) { + + const { gl, backend } = this; + const { textureGPU, glTextureType } = backend.get( texture ); + + backend.state.bindTexture( glTextureType, textureGPU ); + gl.generateMipmap( glTextureType ); + + } + + /** + * Deallocates the render buffers of the given render target. + * + * @param {RenderTarget} renderTarget - The render target. + */ + deallocateRenderBuffers( renderTarget ) { + + const { gl, backend } = this; + + // remove framebuffer reference + if ( renderTarget ) { + + const renderContextData = backend.get( renderTarget ); + + renderContextData.renderBufferStorageSetup = undefined; + + if ( renderContextData.framebuffers ) { + + for ( const cacheKey in renderContextData.framebuffers ) { + + gl.deleteFramebuffer( renderContextData.framebuffers[ cacheKey ] ); + + } + + delete renderContextData.framebuffers; + + } + + if ( renderContextData.depthRenderbuffer ) { + + gl.deleteRenderbuffer( renderContextData.depthRenderbuffer ); + delete renderContextData.depthRenderbuffer; + + } + + if ( renderContextData.stencilRenderbuffer ) { + + gl.deleteRenderbuffer( renderContextData.stencilRenderbuffer ); + delete renderContextData.stencilRenderbuffer; + + } + + if ( renderContextData.msaaFrameBuffer ) { + + gl.deleteFramebuffer( renderContextData.msaaFrameBuffer ); + delete renderContextData.msaaFrameBuffer; + + } + + if ( renderContextData.msaaRenderbuffers ) { + + for ( let i = 0; i < renderContextData.msaaRenderbuffers.length; i ++ ) { + + gl.deleteRenderbuffer( renderContextData.msaaRenderbuffers[ i ] ); + + } + + delete renderContextData.msaaRenderbuffers; + + } + + } + + } + + /** + * Destroys the GPU data for the given texture object. + * + * @param {Texture} texture - The texture. + */ + destroyTexture( texture ) { + + const { gl, backend } = this; + const { textureGPU, renderTarget } = backend.get( texture ); + + this.deallocateRenderBuffers( renderTarget ); + gl.deleteTexture( textureGPU ); + + backend.delete( texture ); + + } + + /** + * Copies data of the given source texture to the given destination texture. + * + * @param {Texture} srcTexture - The source texture. + * @param {Texture} dstTexture - The destination texture. + * @param {?(Box3|Box2)} [srcRegion=null] - The region of the source texture to copy. + * @param {?(Vector2|Vector3)} [dstPosition=null] - The destination position of the copy. + * @param {number} [srcLevel=0] - The source mip level to copy from. + * @param {number} [dstLevel=0] - The destination mip level to copy to. + */ + copyTextureToTexture( srcTexture, dstTexture, srcRegion = null, dstPosition = null, srcLevel = 0, dstLevel = 0 ) { + + const { gl, backend } = this; + const { state } = this.backend; + + const { textureGPU: dstTextureGPU, glTextureType, glType, glFormat } = backend.get( dstTexture ); + + state.bindTexture( glTextureType, dstTextureGPU ); + + // gather the necessary dimensions to copy + let width, height, depth, minX, minY, minZ; + let dstX, dstY, dstZ; + const image = srcTexture.isCompressedTexture ? srcTexture.mipmaps[ dstLevel ] : srcTexture.image; + + if ( srcRegion !== null ) { + + width = srcRegion.max.x - srcRegion.min.x; + height = srcRegion.max.y - srcRegion.min.y; + depth = srcRegion.isBox3 ? srcRegion.max.z - srcRegion.min.z : 1; + minX = srcRegion.min.x; + minY = srcRegion.min.y; + minZ = srcRegion.isBox3 ? srcRegion.min.z : 0; + + } else { + + const levelScale = Math.pow( 2, - srcLevel ); + width = Math.floor( image.width * levelScale ); + height = Math.floor( image.height * levelScale ); + + if ( srcTexture.isDataArrayTexture || srcTexture.isArrayTexture ) { + + depth = image.depth; + + } else if ( srcTexture.isData3DTexture ) { + + depth = Math.floor( image.depth * levelScale ); + + } else { + + depth = 1; + + } + + minX = 0; + minY = 0; + minZ = 0; + + } + + if ( dstPosition !== null ) { + + dstX = dstPosition.x; + dstY = dstPosition.y; + dstZ = dstPosition.z; + + } else { + + dstX = 0; + dstY = 0; + dstZ = 0; + + } + + + gl.pixelStorei( gl.UNPACK_FLIP_Y_WEBGL, dstTexture.flipY ); + gl.pixelStorei( gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, dstTexture.premultiplyAlpha ); + gl.pixelStorei( gl.UNPACK_ALIGNMENT, dstTexture.unpackAlignment ); + + // used for copying data from cpu + const currentUnpackRowLen = gl.getParameter( gl.UNPACK_ROW_LENGTH ); + const currentUnpackImageHeight = gl.getParameter( gl.UNPACK_IMAGE_HEIGHT ); + const currentUnpackSkipPixels = gl.getParameter( gl.UNPACK_SKIP_PIXELS ); + const currentUnpackSkipRows = gl.getParameter( gl.UNPACK_SKIP_ROWS ); + const currentUnpackSkipImages = gl.getParameter( gl.UNPACK_SKIP_IMAGES ); + + gl.pixelStorei( gl.UNPACK_ROW_LENGTH, image.width ); + gl.pixelStorei( gl.UNPACK_IMAGE_HEIGHT, image.height ); + gl.pixelStorei( gl.UNPACK_SKIP_PIXELS, minX ); + gl.pixelStorei( gl.UNPACK_SKIP_ROWS, minY ); + gl.pixelStorei( gl.UNPACK_SKIP_IMAGES, minZ ); + + // set up the src texture + const isDst3D = dstTexture.isDataArrayTexture || dstTexture.isData3DTexture || dstTexture.isArrayTexture; + if ( srcTexture.isRenderTargetTexture || srcTexture.isDepthTexture ) { + + const srcTextureData = backend.get( srcTexture ); + const dstTextureData = backend.get( dstTexture ); + + const srcRenderContextData = backend.get( srcTextureData.renderTarget ); + const dstRenderContextData = backend.get( dstTextureData.renderTarget ); + + const srcFramebuffer = srcRenderContextData.framebuffers[ srcTextureData.cacheKey ]; + const dstFramebuffer = dstRenderContextData.framebuffers[ dstTextureData.cacheKey ]; + + state.bindFramebuffer( gl.READ_FRAMEBUFFER, srcFramebuffer ); + state.bindFramebuffer( gl.DRAW_FRAMEBUFFER, dstFramebuffer ); + + let mask = gl.COLOR_BUFFER_BIT; + + if ( srcTexture.isDepthTexture ) mask = gl.DEPTH_BUFFER_BIT; + + gl.blitFramebuffer( minX, minY, width, height, dstX, dstY, width, height, mask, gl.NEAREST ); + + state.bindFramebuffer( gl.READ_FRAMEBUFFER, null ); + state.bindFramebuffer( gl.DRAW_FRAMEBUFFER, null ); + + } else { + + if ( isDst3D ) { + + // copy data into the 3d texture + if ( srcTexture.isDataTexture || srcTexture.isData3DTexture ) { + + gl.texSubImage3D( glTextureType, dstLevel, dstX, dstY, dstZ, width, height, depth, glFormat, glType, image.data ); + + } else if ( dstTexture.isCompressedArrayTexture ) { + + gl.compressedTexSubImage3D( glTextureType, dstLevel, dstX, dstY, dstZ, width, height, depth, glFormat, image.data ); + + } else { + + gl.texSubImage3D( glTextureType, dstLevel, dstX, dstY, dstZ, width, height, depth, glFormat, glType, image ); + + } + + } else { + + // copy data into the 2d texture + if ( srcTexture.isDataTexture ) { + + gl.texSubImage2D( glTextureType, dstLevel, dstX, dstY, width, height, glFormat, glType, image.data ); + + } else if ( srcTexture.isCompressedTexture ) { + + gl.compressedTexSubImage2D( glTextureType, dstLevel, dstX, dstY, image.width, image.height, glFormat, image.data ); + + } else { + + gl.texSubImage2D( glTextureType, dstLevel, dstX, dstY, width, height, glFormat, glType, image ); + + } + + } + + } + + // reset values + gl.pixelStorei( gl.UNPACK_ROW_LENGTH, currentUnpackRowLen ); + gl.pixelStorei( gl.UNPACK_IMAGE_HEIGHT, currentUnpackImageHeight ); + gl.pixelStorei( gl.UNPACK_SKIP_PIXELS, currentUnpackSkipPixels ); + gl.pixelStorei( gl.UNPACK_SKIP_ROWS, currentUnpackSkipRows ); + gl.pixelStorei( gl.UNPACK_SKIP_IMAGES, currentUnpackSkipImages ); + + // Generate mipmaps only when copying level 0 + if ( dstLevel === 0 && dstTexture.generateMipmaps ) { + + gl.generateMipmap( glTextureType ); + + } + + state.unbindTexture(); + + } + + + /** + * Copies the current bound framebuffer to the given texture. + * + * @param {Texture} texture - The destination texture. + * @param {RenderContext} renderContext - The render context. + * @param {Vector4} rectangle - A four dimensional vector defining the origin and dimension of the copy. + */ + copyFramebufferToTexture( texture, renderContext, rectangle ) { + + const { gl } = this; + const { state } = this.backend; + + const { textureGPU } = this.backend.get( texture ); + + const { x, y, z: width, w: height } = rectangle; + + const requireDrawFrameBuffer = texture.isDepthTexture === true || ( renderContext.renderTarget && renderContext.renderTarget.samples > 0 ); + + const srcHeight = renderContext.renderTarget ? renderContext.renderTarget.height : this.backend.getDrawingBufferSize().y; + + if ( requireDrawFrameBuffer ) { + + const partial = ( x !== 0 || y !== 0 ); + let mask; + let attachment; + + if ( texture.isDepthTexture === true ) { + + mask = gl.DEPTH_BUFFER_BIT; + attachment = gl.DEPTH_ATTACHMENT; + + if ( renderContext.stencil ) { + + mask |= gl.STENCIL_BUFFER_BIT; + + } + + } else { + + mask = gl.COLOR_BUFFER_BIT; + attachment = gl.COLOR_ATTACHMENT0; + + } + + if ( partial ) { + + const renderTargetContextData = this.backend.get( renderContext.renderTarget ); + + const fb = renderTargetContextData.framebuffers[ renderContext.getCacheKey() ]; + const msaaFrameBuffer = renderTargetContextData.msaaFrameBuffer; + + state.bindFramebuffer( gl.DRAW_FRAMEBUFFER, fb ); + state.bindFramebuffer( gl.READ_FRAMEBUFFER, msaaFrameBuffer ); + + const flippedY = srcHeight - y - height; + + gl.blitFramebuffer( x, flippedY, x + width, flippedY + height, x, flippedY, x + width, flippedY + height, mask, gl.NEAREST ); + + state.bindFramebuffer( gl.READ_FRAMEBUFFER, fb ); + + state.bindTexture( gl.TEXTURE_2D, textureGPU ); + + gl.copyTexSubImage2D( gl.TEXTURE_2D, 0, 0, 0, x, flippedY, width, height ); + + state.unbindTexture(); + + } else { + + const fb = gl.createFramebuffer(); + + state.bindFramebuffer( gl.DRAW_FRAMEBUFFER, fb ); + + gl.framebufferTexture2D( gl.DRAW_FRAMEBUFFER, attachment, gl.TEXTURE_2D, textureGPU, 0 ); + gl.blitFramebuffer( 0, 0, width, height, 0, 0, width, height, mask, gl.NEAREST ); + + gl.deleteFramebuffer( fb ); + + } + + } else { + + state.bindTexture( gl.TEXTURE_2D, textureGPU ); + gl.copyTexSubImage2D( gl.TEXTURE_2D, 0, 0, 0, x, srcHeight - height - y, width, height ); + + state.unbindTexture(); + + } + + if ( texture.generateMipmaps ) this.generateMipmaps( texture ); + + this.backend._setFramebuffer( renderContext ); + + } + + /** + * SetupS storage for internal depth/stencil buffers and bind to correct framebuffer. + * + * @param {WebGLRenderbuffer} renderbuffer - The render buffer. + * @param {RenderContext} renderContext - The render context. + * @param {number} samples - The MSAA sample count. + * @param {boolean} [useMultisampledRTT=false] - Whether to use WEBGL_multisampled_render_to_texture or not. + */ + setupRenderBufferStorage( renderbuffer, renderContext, samples, useMultisampledRTT = false ) { + + const { gl } = this; + const renderTarget = renderContext.renderTarget; + + const { depthTexture, depthBuffer, stencilBuffer, width, height } = renderTarget; + + gl.bindRenderbuffer( gl.RENDERBUFFER, renderbuffer ); + + if ( depthBuffer && ! stencilBuffer ) { + + let glInternalFormat = gl.DEPTH_COMPONENT24; + + if ( useMultisampledRTT === true ) { + + const multisampledRTTExt = this.extensions.get( 'WEBGL_multisampled_render_to_texture' ); + + multisampledRTTExt.renderbufferStorageMultisampleEXT( gl.RENDERBUFFER, renderTarget.samples, glInternalFormat, width, height ); + + } else if ( samples > 0 ) { + + if ( depthTexture && depthTexture.isDepthTexture ) { + + if ( depthTexture.type === gl.FLOAT ) { + + glInternalFormat = gl.DEPTH_COMPONENT32F; + + } + + } + + gl.renderbufferStorageMultisample( gl.RENDERBUFFER, samples, glInternalFormat, width, height ); + + } else { + + gl.renderbufferStorage( gl.RENDERBUFFER, glInternalFormat, width, height ); + + } + + gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, renderbuffer ); + + } else if ( depthBuffer && stencilBuffer ) { + + if ( samples > 0 ) { + + gl.renderbufferStorageMultisample( gl.RENDERBUFFER, samples, gl.DEPTH24_STENCIL8, width, height ); + + } else { + + gl.renderbufferStorage( gl.RENDERBUFFER, gl.DEPTH_STENCIL, width, height ); + + } + + + gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, renderbuffer ); + + } + + gl.bindRenderbuffer( gl.RENDERBUFFER, null ); + + } + + /** + * Returns texture data as a typed array. + * + * @async + * @param {Texture} texture - The texture to copy. + * @param {number} x - The x coordinate of the copy origin. + * @param {number} y - The y coordinate of the copy origin. + * @param {number} width - The width of the copy. + * @param {number} height - The height of the copy. + * @param {number} faceIndex - The face index. + * @return {Promise} A Promise that resolves with a typed array when the copy operation has finished. + */ + async copyTextureToBuffer( texture, x, y, width, height, faceIndex ) { + + const { backend, gl } = this; + + const { textureGPU, glFormat, glType } = this.backend.get( texture ); + + const fb = gl.createFramebuffer(); + + gl.bindFramebuffer( gl.READ_FRAMEBUFFER, fb ); + + const target = texture.isCubeTexture ? gl.TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex : gl.TEXTURE_2D; + + gl.framebufferTexture2D( gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, target, textureGPU, 0 ); + + const typedArrayType = this._getTypedArrayType( glType ); + const bytesPerTexel = this._getBytesPerTexel( glType, glFormat ); + + const elementCount = width * height; + const byteLength = elementCount * bytesPerTexel; + + const buffer = gl.createBuffer(); + + gl.bindBuffer( gl.PIXEL_PACK_BUFFER, buffer ); + gl.bufferData( gl.PIXEL_PACK_BUFFER, byteLength, gl.STREAM_READ ); + gl.readPixels( x, y, width, height, glFormat, glType, 0 ); + gl.bindBuffer( gl.PIXEL_PACK_BUFFER, null ); + + await backend.utils._clientWaitAsync(); + + const dstBuffer = new typedArrayType( byteLength / typedArrayType.BYTES_PER_ELEMENT ); + + gl.bindBuffer( gl.PIXEL_PACK_BUFFER, buffer ); + gl.getBufferSubData( gl.PIXEL_PACK_BUFFER, 0, dstBuffer ); + gl.bindBuffer( gl.PIXEL_PACK_BUFFER, null ); + + gl.deleteFramebuffer( fb ); + + return dstBuffer; + + } + + /** + * Returns the corresponding typed array type for the given WebGL data type. + * + * @private + * @param {GLenum} glType - The WebGL data type. + * @return {TypedArray.constructor} The typed array type. + */ + _getTypedArrayType( glType ) { + + const { gl } = this; + + if ( glType === gl.UNSIGNED_BYTE ) return Uint8Array; + + if ( glType === gl.UNSIGNED_SHORT_4_4_4_4 ) return Uint16Array; + if ( glType === gl.UNSIGNED_SHORT_5_5_5_1 ) return Uint16Array; + if ( glType === gl.UNSIGNED_SHORT_5_6_5 ) return Uint16Array; + if ( glType === gl.UNSIGNED_SHORT ) return Uint16Array; + if ( glType === gl.UNSIGNED_INT ) return Uint32Array; + + if ( glType === gl.HALF_FLOAT ) return Uint16Array; + if ( glType === gl.FLOAT ) return Float32Array; + + throw new Error( `Unsupported WebGL type: ${glType}` ); + + } + + /** + * Returns the bytes-per-texel value for the given WebGL data type and texture format. + * + * @private + * @param {GLenum} glType - The WebGL data type. + * @param {GLenum} glFormat - The WebGL texture format. + * @return {number} The bytes-per-texel. + */ + _getBytesPerTexel( glType, glFormat ) { + + const { gl } = this; + + let bytesPerComponent = 0; + + if ( glType === gl.UNSIGNED_BYTE ) bytesPerComponent = 1; + + if ( glType === gl.UNSIGNED_SHORT_4_4_4_4 || + glType === gl.UNSIGNED_SHORT_5_5_5_1 || + glType === gl.UNSIGNED_SHORT_5_6_5 || + glType === gl.UNSIGNED_SHORT || + glType === gl.HALF_FLOAT ) bytesPerComponent = 2; + + if ( glType === gl.UNSIGNED_INT || + glType === gl.FLOAT ) bytesPerComponent = 4; + + if ( glFormat === gl.RGBA ) return bytesPerComponent * 4; + if ( glFormat === gl.RGB ) return bytesPerComponent * 3; + if ( glFormat === gl.ALPHA ) return bytesPerComponent; + + } + +} + +function getImage( source ) { + + if ( source.isDataTexture ) { + + return source.image.data; + + } else if ( ( typeof HTMLImageElement !== 'undefined' && source instanceof HTMLImageElement ) || + ( typeof HTMLCanvasElement !== 'undefined' && source instanceof HTMLCanvasElement ) || + ( typeof ImageBitmap !== 'undefined' && source instanceof ImageBitmap ) || + ( typeof OffscreenCanvas !== 'undefined' && source instanceof OffscreenCanvas ) ) { + + return source; + + } + + return source.data; + +} + +/** + * A WebGL 2 backend utility module for managing extensions. + * + * @private + */ +class WebGLExtensions { + + /** + * Constructs a new utility object. + * + * @param {WebGLBackend} backend - The WebGL 2 backend. + */ + constructor( backend ) { + + /** + * A reference to the WebGL 2 backend. + * + * @type {WebGLBackend} + */ + this.backend = backend; + + /** + * A reference to the rendering context. + * + * @type {WebGL2RenderingContext} + */ + this.gl = this.backend.gl; + + /** + * A list with all the supported WebGL extensions. + * + * @type {Array} + */ + this.availableExtensions = this.gl.getSupportedExtensions(); + + /** + * A dictionary with requested WebGL extensions. + * The key is the name of the extension, the value + * the requested extension object. + * + * @type {Object} + */ + this.extensions = {}; + + } + + /** + * Returns the extension object for the given extension name. + * + * @param {string} name - The extension name. + * @return {Object} The extension object. + */ + get( name ) { + + let extension = this.extensions[ name ]; + + if ( extension === undefined ) { + + extension = this.gl.getExtension( name ); + + this.extensions[ name ] = extension; + + } + + return extension; + + } + + /** + * Returns `true` if the requested extension is available. + * + * @param {string} name - The extension name. + * @return {boolean} Whether the given extension is available or not. + */ + has( name ) { + + return this.availableExtensions.includes( name ); + + } + +} + +/** + * A WebGL 2 backend utility module for managing the device's capabilities. + * + * @private + */ +class WebGLCapabilities { + + /** + * Constructs a new utility object. + * + * @param {WebGLBackend} backend - The WebGL 2 backend. + */ + constructor( backend ) { + + /** + * A reference to the WebGL 2 backend. + * + * @type {WebGLBackend} + */ + this.backend = backend; + + /** + * This value holds the cached max anisotropy value. + * + * @type {?number} + * @default null + */ + this.maxAnisotropy = null; + + } + + /** + * Returns the maximum anisotropy texture filtering value. This value + * depends on the device and is reported by the `EXT_texture_filter_anisotropic` + * WebGL extension. + * + * @return {number} The maximum anisotropy texture filtering value. + */ + getMaxAnisotropy() { + + if ( this.maxAnisotropy !== null ) return this.maxAnisotropy; + + const gl = this.backend.gl; + const extensions = this.backend.extensions; + + if ( extensions.has( 'EXT_texture_filter_anisotropic' ) === true ) { + + const extension = extensions.get( 'EXT_texture_filter_anisotropic' ); + + this.maxAnisotropy = gl.getParameter( extension.MAX_TEXTURE_MAX_ANISOTROPY_EXT ); + + } else { + + this.maxAnisotropy = 0; + + } + + return this.maxAnisotropy; + + } + +} + +const GLFeatureName = { + + 'WEBGL_multi_draw': 'WEBGL_multi_draw', + 'WEBGL_compressed_texture_astc': 'texture-compression-astc', + 'WEBGL_compressed_texture_etc': 'texture-compression-etc2', + 'WEBGL_compressed_texture_etc1': 'texture-compression-etc1', + 'WEBGL_compressed_texture_pvrtc': 'texture-compression-pvrtc', + 'WEBKIT_WEBGL_compressed_texture_pvrtc': 'texture-compression-pvrtc', + 'WEBGL_compressed_texture_s3tc': 'texture-compression-bc', + 'EXT_texture_compression_bptc': 'texture-compression-bptc', + 'EXT_disjoint_timer_query_webgl2': 'timestamp-query', + 'OVR_multiview2': 'OVR_multiview2' + +}; + +class WebGLBufferRenderer { + + constructor( backend ) { + + this.gl = backend.gl; + this.extensions = backend.extensions; + this.info = backend.renderer.info; + this.mode = null; + this.index = 0; + this.type = null; + this.object = null; + + } + + render( start, count ) { + + const { gl, mode, object, type, info, index } = this; + + if ( index !== 0 ) { + + gl.drawElements( mode, count, type, start ); + + } else { + + gl.drawArrays( mode, start, count ); + + } + + info.update( object, count, 1 ); + + } + + renderInstances( start, count, primcount ) { + + const { gl, mode, type, index, object, info } = this; + + if ( primcount === 0 ) return; + + if ( index !== 0 ) { + + gl.drawElementsInstanced( mode, count, type, start, primcount ); + + } else { + + gl.drawArraysInstanced( mode, start, count, primcount ); + + } + + info.update( object, count, primcount ); + + } + + renderMultiDraw( starts, counts, drawCount ) { + + const { extensions, mode, object, info } = this; + + if ( drawCount === 0 ) return; + + const extension = extensions.get( 'WEBGL_multi_draw' ); + + if ( extension === null ) { + + for ( let i = 0; i < drawCount; i ++ ) { + + this.render( starts[ i ], counts[ i ] ); + + } + + } else { + + if ( this.index !== 0 ) { + + extension.multiDrawElementsWEBGL( mode, counts, 0, this.type, starts, 0, drawCount ); + + } else { + + extension.multiDrawArraysWEBGL( mode, starts, 0, counts, 0, drawCount ); + + } + + let elementCount = 0; + for ( let i = 0; i < drawCount; i ++ ) { + + elementCount += counts[ i ]; + + } + + info.update( object, elementCount, 1 ); + + } + + } + + renderMultiDrawInstances( starts, counts, drawCount, primcount ) { + + const { extensions, mode, object, info } = this; + + if ( drawCount === 0 ) return; + + const extension = extensions.get( 'WEBGL_multi_draw' ); + + if ( extension === null ) { + + for ( let i = 0; i < drawCount; i ++ ) { + + this.renderInstances( starts[ i ], counts[ i ], primcount[ i ] ); + + } + + } else { + + if ( this.index !== 0 ) { + + extension.multiDrawElementsInstancedWEBGL( mode, counts, 0, this.type, starts, 0, primcount, 0, drawCount ); + + } else { + + extension.multiDrawArraysInstancedWEBGL( mode, starts, 0, counts, 0, primcount, 0, drawCount ); + + } + + let elementCount = 0; + for ( let i = 0; i < drawCount; i ++ ) { + + elementCount += counts[ i ] * primcount[ i ]; + + } + + info.update( object, elementCount, 1 ); + + } + + } + + // + +} + +/** + * Abstract base class of a timestamp query pool. + * + * @abstract + */ +class TimestampQueryPool { + + /** + * Creates a new timestamp query pool. + * + * @param {number} [maxQueries=256] - Maximum number of queries this pool can hold. + */ + constructor( maxQueries = 256 ) { + + /** + * Whether to track timestamps or not. + * + * @type {boolean} + * @default true + */ + this.trackTimestamp = true; + + /** + * Maximum number of queries this pool can hold. + * + * @type {number} + * @default 256 + */ + this.maxQueries = maxQueries; + + /** + * How many queries allocated so far. + * + * @type {number} + * @default 0 + */ + this.currentQueryIndex = 0; + + /** + * Tracks offsets for different contexts. + * + * @type {Map} + */ + this.queryOffsets = new Map(); + + /** + * Whether the pool has been disposed or not. + * + * @type {boolean} + * @default false + */ + this.isDisposed = false; + + /** + * TODO + * + * @type {number} + * @default 0 + */ + this.lastValue = 0; + + /** + * TODO + * + * @type {boolean} + * @default false + */ + this.pendingResolve = false; + + } + + /** + * Allocate queries for a specific renderContext. + * + * @abstract + * @param {Object} renderContext - The render context to allocate queries for. + * @returns {?number} + */ + allocateQueriesForContext( /* renderContext */ ) {} + + /** + * Resolve all timestamps and return data (or process them). + * + * @abstract + * @async + * @returns {Promise|number} The resolved timestamp value. + */ + async resolveQueriesAsync() {} + + /** + * Dispose of the query pool. + * + * @abstract + */ + dispose() {} + +} + +/** + * Manages a pool of WebGL timestamp queries for performance measurement. + * Handles creation, execution, and resolution of timer queries using WebGL extensions. + * + * @augments TimestampQueryPool + */ +class WebGLTimestampQueryPool extends TimestampQueryPool { + + /** + * Creates a new WebGL timestamp query pool. + * + * @param {WebGLRenderingContext|WebGL2RenderingContext} gl - The WebGL context. + * @param {string} type - The type identifier for this query pool. + * @param {number} [maxQueries=2048] - Maximum number of queries this pool can hold. + */ + constructor( gl, type, maxQueries = 2048 ) { + + super( maxQueries ); + + this.gl = gl; + this.type = type; + + // Check for timer query extensions + this.ext = gl.getExtension( 'EXT_disjoint_timer_query_webgl2' ) || + gl.getExtension( 'EXT_disjoint_timer_query' ); + + if ( ! this.ext ) { + + console.warn( 'EXT_disjoint_timer_query not supported; timestamps will be disabled.' ); + this.trackTimestamp = false; + return; + + } + + // Create query objects + this.queries = []; + for ( let i = 0; i < this.maxQueries; i ++ ) { + + this.queries.push( gl.createQuery() ); + + } + + this.activeQuery = null; + this.queryStates = new Map(); // Track state of each query: 'inactive', 'started', 'ended' + + } + + /** + * Allocates a pair of queries for a given render context. + * + * @param {Object} renderContext - The render context to allocate queries for. + * @returns {?number} The base offset for the allocated queries, or null if allocation failed. + */ + allocateQueriesForContext( renderContext ) { + + if ( ! this.trackTimestamp ) return null; + + // Check if we have enough space for a new query pair + if ( this.currentQueryIndex + 2 > this.maxQueries ) { + + warnOnce( `WebGPUTimestampQueryPool [${ this.type }]: Maximum number of queries exceeded, when using trackTimestamp it is necessary to resolves the queries via renderer.resolveTimestampsAsync( THREE.TimestampQuery.${ this.type.toUpperCase() } ).` ); + return null; + + } + + const baseOffset = this.currentQueryIndex; + this.currentQueryIndex += 2; + + // Initialize query states + this.queryStates.set( baseOffset, 'inactive' ); + this.queryOffsets.set( renderContext.id, baseOffset ); + + return baseOffset; + + } + + /** + * Begins a timestamp query for the specified render context. + * + * @param {Object} renderContext - The render context to begin timing for. + */ + beginQuery( renderContext ) { + + if ( ! this.trackTimestamp || this.isDisposed ) { + + return; + + } + + const baseOffset = this.queryOffsets.get( renderContext.id ); + if ( baseOffset == null ) { + + return; + + } + + // Don't start a new query if there's an active one + if ( this.activeQuery !== null ) { + + return; + + } + + const query = this.queries[ baseOffset ]; + if ( ! query ) { + + return; + + } + + try { + + // Only begin if query is inactive + if ( this.queryStates.get( baseOffset ) === 'inactive' ) { + + this.gl.beginQuery( this.ext.TIME_ELAPSED_EXT, query ); + this.activeQuery = baseOffset; + this.queryStates.set( baseOffset, 'started' ); + + } + + } catch ( error ) { + + console.error( 'Error in beginQuery:', error ); + this.activeQuery = null; + this.queryStates.set( baseOffset, 'inactive' ); + + } + + } + + /** + * Ends the active timestamp query for the specified render context. + * + * @param {Object} renderContext - The render context to end timing for. + * @param {string} renderContext.id - Unique identifier for the render context. + */ + endQuery( renderContext ) { + + if ( ! this.trackTimestamp || this.isDisposed ) { + + return; + + } + + const baseOffset = this.queryOffsets.get( renderContext.id ); + if ( baseOffset == null ) { + + return; + + } + + // Only end if this is the active query + if ( this.activeQuery !== baseOffset ) { + + return; + + } + + try { + + this.gl.endQuery( this.ext.TIME_ELAPSED_EXT ); + this.queryStates.set( baseOffset, 'ended' ); + this.activeQuery = null; + + } catch ( error ) { + + console.error( 'Error in endQuery:', error ); + // Reset state on error + this.queryStates.set( baseOffset, 'inactive' ); + this.activeQuery = null; + + } + + } + + /** + * Asynchronously resolves all completed queries and returns the total duration. + * + * @async + * @returns {Promise} The total duration in milliseconds, or the last valid value if resolution fails. + */ + async resolveQueriesAsync() { + + if ( ! this.trackTimestamp || this.pendingResolve ) { + + return this.lastValue; + + } + + this.pendingResolve = true; + + try { + + // Wait for all ended queries to complete + const resolvePromises = []; + + for ( const [ baseOffset, state ] of this.queryStates ) { + + if ( state === 'ended' ) { + + const query = this.queries[ baseOffset ]; + resolvePromises.push( this.resolveQuery( query ) ); + + } + + } + + if ( resolvePromises.length === 0 ) { + + return this.lastValue; + + } + + const results = await Promise.all( resolvePromises ); + const totalDuration = results.reduce( ( acc, val ) => acc + val, 0 ); + + // Store the last valid result + this.lastValue = totalDuration; + + // Reset states + this.currentQueryIndex = 0; + this.queryOffsets.clear(); + this.queryStates.clear(); + this.activeQuery = null; + + return totalDuration; + + } catch ( error ) { + + console.error( 'Error resolving queries:', error ); + return this.lastValue; + + } finally { + + this.pendingResolve = false; + + } + + } + + /** + * Resolves a single query, checking for completion and disjoint operation. + * + * @async + * @param {WebGLQuery} query - The query object to resolve. + * @returns {Promise} The elapsed time in milliseconds. + */ + async resolveQuery( query ) { + + return new Promise( ( resolve ) => { + + if ( this.isDisposed ) { + + resolve( this.lastValue ); + return; + + } + + let timeoutId; + let isResolved = false; + + const cleanup = () => { + + if ( timeoutId ) { + + clearTimeout( timeoutId ); + timeoutId = null; + + } + + }; + + const finalizeResolution = ( value ) => { + + if ( ! isResolved ) { + + isResolved = true; + cleanup(); + resolve( value ); + + } + + }; + + const checkQuery = () => { + + if ( this.isDisposed ) { + + finalizeResolution( this.lastValue ); + return; + + } + + try { + + // Check if the GPU timer was disjoint (i.e., timing was unreliable) + const disjoint = this.gl.getParameter( this.ext.GPU_DISJOINT_EXT ); + if ( disjoint ) { + + finalizeResolution( this.lastValue ); + return; + + } + + const available = this.gl.getQueryParameter( query, this.gl.QUERY_RESULT_AVAILABLE ); + if ( ! available ) { + + timeoutId = setTimeout( checkQuery, 1 ); + return; + + } + + const elapsed = this.gl.getQueryParameter( query, this.gl.QUERY_RESULT ); + resolve( Number( elapsed ) / 1e6 ); // Convert nanoseconds to milliseconds + + } catch ( error ) { + + console.error( 'Error checking query:', error ); + resolve( this.lastValue ); + + } + + }; + + checkQuery(); + + } ); + + } + + /** + * Releases all resources held by this query pool. + * This includes deleting all query objects and clearing internal state. + */ + dispose() { + + if ( this.isDisposed ) { + + return; + + } + + this.isDisposed = true; + + if ( ! this.trackTimestamp ) return; + + for ( const query of this.queries ) { + + this.gl.deleteQuery( query ); + + } + + this.queries = []; + this.queryStates.clear(); + this.queryOffsets.clear(); + this.lastValue = 0; + this.activeQuery = null; + + } + +} + +const _drawingBufferSize = /*@__PURE__*/ new Vector2(); + +/** + * A backend implementation targeting WebGL 2. + * + * @private + * @augments Backend + */ +class WebGLBackend extends Backend { + + /** + * WebGLBackend options. + * + * @typedef {Object} WebGLBackend~Options + * @property {boolean} [logarithmicDepthBuffer=false] - Whether logarithmic depth buffer is enabled or not. + * @property {boolean} [alpha=true] - Whether the default framebuffer (which represents the final contents of the canvas) should be transparent or opaque. + * @property {boolean} [depth=true] - Whether the default framebuffer should have a depth buffer or not. + * @property {boolean} [stencil=false] - Whether the default framebuffer should have a stencil buffer or not. + * @property {boolean} [antialias=false] - Whether MSAA as the default anti-aliasing should be enabled or not. + * @property {number} [samples=0] - When `antialias` is `true`, `4` samples are used by default. Set this parameter to any other integer value than 0 to overwrite the default. + * @property {boolean} [forceWebGL=false] - If set to `true`, the renderer uses a WebGL 2 backend no matter if WebGPU is supported or not. + * @property {WebGL2RenderingContext} [context=undefined] - A WebGL 2 rendering context. + */ + + /** + * Constructs a new WebGPU backend. + * + * @param {WebGLBackend~Options} [parameters] - The configuration parameter. + */ + constructor( parameters = {} ) { + + super( parameters ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isWebGLBackend = true; + + /** + * A reference to a backend module holding shader attribute-related + * utility functions. + * + * @type {?WebGLAttributeUtils} + * @default null + */ + this.attributeUtils = null; + + /** + * A reference to a backend module holding extension-related + * utility functions. + * + * @type {?WebGLExtensions} + * @default null + */ + this.extensions = null; + + /** + * A reference to a backend module holding capability-related + * utility functions. + * + * @type {?WebGLCapabilities} + * @default null + */ + this.capabilities = null; + + /** + * A reference to a backend module holding texture-related + * utility functions. + * + * @type {?WebGLTextureUtils} + * @default null + */ + this.textureUtils = null; + + /** + * A reference to a backend module holding renderer-related + * utility functions. + * + * @type {?WebGLBufferRenderer} + * @default null + */ + this.bufferRenderer = null; + + /** + * A reference to the rendering context. + * + * @type {?WebGL2RenderingContext} + * @default null + */ + this.gl = null; + + /** + * A reference to a backend module holding state-related + * utility functions. + * + * @type {?WebGLState} + * @default null + */ + this.state = null; + + /** + * A reference to a backend module holding common + * utility functions. + * + * @type {?WebGLUtils} + * @default null + */ + this.utils = null; + + /** + * Dictionary for caching VAOs. + * + * @type {Object} + */ + this.vaoCache = {}; + + /** + * Dictionary for caching transform feedback objects. + * + * @type {Object} + */ + this.transformFeedbackCache = {}; + + /** + * Controls if `gl.RASTERIZER_DISCARD` should be enabled or not. + * Only relevant when using compute shaders. + * + * @type {boolean} + * @default false + */ + this.discard = false; + + /** + * A reference to the `EXT_disjoint_timer_query_webgl2` extension. `null` if the + * device does not support the extension. + * + * @type {?EXTDisjointTimerQueryWebGL2} + * @default null + */ + this.disjoint = null; + + /** + * A reference to the `KHR_parallel_shader_compile` extension. `null` if the + * device does not support the extension. + * + * @type {?KHRParallelShaderCompile} + * @default null + */ + this.parallel = null; + + /** + * A reference to the current render context. + * + * @private + * @type {RenderContext} + * @default null + */ + this._currentContext = null; + + /** + * A unique collection of bindings. + * + * @private + * @type {WeakSet} + */ + this._knownBindings = new WeakSet(); + + + /** + * Whether the device supports framebuffers invalidation or not. + * + * @private + * @type {boolean} + */ + this._supportsInvalidateFramebuffer = typeof navigator === 'undefined' ? false : /OculusBrowser/g.test( navigator.userAgent ); + + /** + * The target framebuffer when rendering with + * the WebXR device API. + * + * @private + * @type {WebGLFramebuffer} + * @default null + */ + this._xrFramebuffer = null; + + } + + /** + * Initializes the backend so it is ready for usage. + * + * @param {Renderer} renderer - The renderer. + */ + init( renderer ) { + + super.init( renderer ); + + // + + const parameters = this.parameters; + + const contextAttributes = { + antialias: renderer.samples > 0, + alpha: true, // always true for performance reasons + depth: renderer.depth, + stencil: renderer.stencil + }; + + const glContext = ( parameters.context !== undefined ) ? parameters.context : renderer.domElement.getContext( 'webgl2', contextAttributes ); + + function onContextLost( event ) { + + event.preventDefault(); + + const contextLossInfo = { + api: 'WebGL', + message: event.statusMessage || 'Unknown reason', + reason: null, + originalEvent: event + }; + + renderer.onDeviceLost( contextLossInfo ); + + } + + this._onContextLost = onContextLost; + + renderer.domElement.addEventListener( 'webglcontextlost', onContextLost, false ); + + this.gl = glContext; + + this.extensions = new WebGLExtensions( this ); + this.capabilities = new WebGLCapabilities( this ); + this.attributeUtils = new WebGLAttributeUtils( this ); + this.textureUtils = new WebGLTextureUtils( this ); + this.bufferRenderer = new WebGLBufferRenderer( this ); + + this.state = new WebGLState( this ); + this.utils = new WebGLUtils( this ); + + this.extensions.get( 'EXT_color_buffer_float' ); + this.extensions.get( 'WEBGL_clip_cull_distance' ); + this.extensions.get( 'OES_texture_float_linear' ); + this.extensions.get( 'EXT_color_buffer_half_float' ); + this.extensions.get( 'WEBGL_multisampled_render_to_texture' ); + this.extensions.get( 'WEBGL_render_shared_exponent' ); + this.extensions.get( 'WEBGL_multi_draw' ); + this.extensions.get( 'OVR_multiview2' ); + + this.disjoint = this.extensions.get( 'EXT_disjoint_timer_query_webgl2' ); + this.parallel = this.extensions.get( 'KHR_parallel_shader_compile' ); + + } + + /** + * The coordinate system of the backend. + * + * @type {number} + * @readonly + */ + get coordinateSystem() { + + return WebGLCoordinateSystem; + + } + + /** + * This method performs a readback operation by moving buffer data from + * a storage buffer attribute from the GPU to the CPU. + * + * @async + * @param {StorageBufferAttribute} attribute - The storage buffer attribute. + * @return {Promise} A promise that resolves with the buffer data when the data are ready. + */ + async getArrayBufferAsync( attribute ) { + + return await this.attributeUtils.getArrayBufferAsync( attribute ); + + } + + /** + * Can be used to synchronize CPU operations with GPU tasks. So when this method is called, + * the CPU waits for the GPU to complete its operation (e.g. a compute task). + * + * @async + * @return {Promise} A Promise that resolves when synchronization has been finished. + */ + async waitForGPU() { + + await this.utils._clientWaitAsync(); + + } + + /** + * Ensures the backend is XR compatible. + * + * @async + * @return {Promise} A Promise that resolve when the renderer is XR compatible. + */ + async makeXRCompatible() { + + const attributes = this.gl.getContextAttributes(); + + if ( attributes.xrCompatible !== true ) { + + await this.gl.makeXRCompatible(); + + } + + } + /** + * Sets the XR rendering destination. + * + * @param {WebGLFramebuffer} xrFramebuffer - The XR framebuffer. + */ + setXRTarget( xrFramebuffer ) { + + this._xrFramebuffer = xrFramebuffer; + + } + + /** + * Configures the given XR render target with external textures. + * + * This method is only relevant when using the WebXR Layers API. + * + * @param {XRRenderTarget} renderTarget - The XR render target. + * @param {WebGLTexture} colorTexture - A native color texture. + * @param {?WebGLTexture} [depthTexture=null] - A native depth texture. + */ + setXRRenderTargetTextures( renderTarget, colorTexture, depthTexture = null ) { + + const gl = this.gl; + + this.set( renderTarget.texture, { textureGPU: colorTexture, glInternalFormat: gl.RGBA8 } ); // see #24698 why RGBA8 and not SRGB8_ALPHA8 is used + + if ( depthTexture !== null ) { + + const glInternalFormat = renderTarget.stencilBuffer ? gl.DEPTH24_STENCIL8 : gl.DEPTH_COMPONENT24; + + this.set( renderTarget.depthTexture, { textureGPU: depthTexture, glInternalFormat: glInternalFormat } ); + + // The multisample_render_to_texture extension doesn't work properly if there + // are midframe flushes and an external depth texture. + if ( ( this.extensions.has( 'WEBGL_multisampled_render_to_texture' ) === true ) && renderTarget._autoAllocateDepthBuffer === true && renderTarget.multiview === false ) { + + console.warn( 'THREE.WebGLBackend: Render-to-texture extension was disabled because an external texture was provided' ); + + } + + renderTarget._autoAllocateDepthBuffer = false; + + } + + } + + /** + * Inits a time stamp query for the given render context. + * + * @param {RenderContext} renderContext - The render context. + */ + initTimestampQuery( renderContext ) { + + if ( ! this.disjoint || ! this.trackTimestamp ) return; + + const type = renderContext.isComputeNode ? 'compute' : 'render'; + + if ( ! this.timestampQueryPool[ type ] ) { + + // TODO: Variable maxQueries? + this.timestampQueryPool[ type ] = new WebGLTimestampQueryPool( this.gl, type, 2048 ); + + } + + const timestampQueryPool = this.timestampQueryPool[ type ]; + + const baseOffset = timestampQueryPool.allocateQueriesForContext( renderContext ); + + if ( baseOffset !== null ) { + + timestampQueryPool.beginQuery( renderContext ); + + } + + } + + // timestamp utils + + /** + * Prepares the timestamp buffer. + * + * @param {RenderContext} renderContext - The render context. + */ + prepareTimestampBuffer( renderContext ) { + + if ( ! this.disjoint || ! this.trackTimestamp ) return; + + const type = renderContext.isComputeNode ? 'compute' : 'render'; + const timestampQueryPool = this.timestampQueryPool[ type ]; + + timestampQueryPool.endQuery( renderContext ); + + } + + + /** + * Returns the backend's rendering context. + * + * @return {WebGL2RenderingContext} The rendering context. + */ + getContext() { + + return this.gl; + + } + + /** + * This method is executed at the beginning of a render call and prepares + * the WebGL state for upcoming render calls + * + * @param {RenderContext} renderContext - The render context. + */ + beginRender( renderContext ) { + + const { state } = this; + const renderContextData = this.get( renderContext ); + + // + + if ( renderContext.viewport ) { + + this.updateViewport( renderContext ); + + } else { + + const { width, height } = this.getDrawingBufferSize( _drawingBufferSize ); + state.viewport( 0, 0, width, height ); + + } + + if ( renderContext.scissor ) { + + const { x, y, width, height } = renderContext.scissorValue; + + state.scissor( x, renderContext.height - height - y, width, height ); + + } + + // + + this.initTimestampQuery( renderContext ); + + renderContextData.previousContext = this._currentContext; + this._currentContext = renderContext; + + this._setFramebuffer( renderContext ); + this.clear( renderContext.clearColor, renderContext.clearDepth, renderContext.clearStencil, renderContext, false ); + + const occlusionQueryCount = renderContext.occlusionQueryCount; + + if ( occlusionQueryCount > 0 ) { + + // Get a reference to the array of objects with queries. The renderContextData property + // can be changed by another render pass before the async reading of all previous queries complete + renderContextData.currentOcclusionQueries = renderContextData.occlusionQueries; + renderContextData.currentOcclusionQueryObjects = renderContextData.occlusionQueryObjects; + + renderContextData.lastOcclusionObject = null; + renderContextData.occlusionQueries = new Array( occlusionQueryCount ); + renderContextData.occlusionQueryObjects = new Array( occlusionQueryCount ); + renderContextData.occlusionQueryIndex = 0; + + } + + } + + /** + * This method is executed at the end of a render call and finalizes work + * after draw calls. + * + * @param {RenderContext} renderContext - The render context. + */ + finishRender( renderContext ) { + + const { gl, state } = this; + const renderContextData = this.get( renderContext ); + const previousContext = renderContextData.previousContext; + + state.resetVertexState(); + + const occlusionQueryCount = renderContext.occlusionQueryCount; + + if ( occlusionQueryCount > 0 ) { + + if ( occlusionQueryCount > renderContextData.occlusionQueryIndex ) { + + gl.endQuery( gl.ANY_SAMPLES_PASSED ); + + } + + this.resolveOccludedAsync( renderContext ); + + } + + const textures = renderContext.textures; + + if ( textures !== null ) { + + for ( let i = 0; i < textures.length; i ++ ) { + + const texture = textures[ i ]; + + if ( texture.generateMipmaps ) { + + this.generateMipmaps( texture ); + + } + + } + + } + + this._currentContext = previousContext; + const renderTarget = renderContext.renderTarget; + + if ( renderContext.textures !== null && renderTarget ) { + + const renderTargetContextData = this.get( renderTarget ); + + if ( renderTarget.samples > 0 && this._useMultisampledExtension( renderTarget ) === false ) { + + const fb = renderTargetContextData.framebuffers[ renderContext.getCacheKey() ]; + + let mask = gl.COLOR_BUFFER_BIT; + + if ( renderTarget.resolveDepthBuffer ) { + + if ( renderTarget.depthBuffer ) mask |= gl.DEPTH_BUFFER_BIT; + if ( renderTarget.stencilBuffer && renderTarget.resolveStencilBuffer ) mask |= gl.STENCIL_BUFFER_BIT; + + } + + const msaaFrameBuffer = renderTargetContextData.msaaFrameBuffer; + const msaaRenderbuffers = renderTargetContextData.msaaRenderbuffers; + + const textures = renderContext.textures; + const isMRT = textures.length > 1; + + state.bindFramebuffer( gl.READ_FRAMEBUFFER, msaaFrameBuffer ); + state.bindFramebuffer( gl.DRAW_FRAMEBUFFER, fb ); + + if ( isMRT ) { + + // blitFramebuffer() can only copy/resolve the first color attachment of a framebuffer. When using MRT, + // the engine temporarily removes all attachments and then configures each attachment for the resolve. + + for ( let i = 0; i < textures.length; i ++ ) { + + gl.framebufferRenderbuffer( gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.RENDERBUFFER, null ); + gl.framebufferTexture2D( gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.TEXTURE_2D, null, 0 ); + + } + + } + + for ( let i = 0; i < textures.length; i ++ ) { + + if ( isMRT ) { + + // configure attachment for resolve + + const { textureGPU } = this.get( textures[ i ] ); + + gl.framebufferRenderbuffer( gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, msaaRenderbuffers[ i ] ); + gl.framebufferTexture2D( gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, textureGPU, 0 ); + + } + + if ( renderContext.scissor ) { + + const { x, y, width, height } = renderContext.scissorValue; + + const viewY = renderContext.height - height - y; + + gl.blitFramebuffer( x, viewY, x + width, viewY + height, x, viewY, x + width, viewY + height, mask, gl.NEAREST ); + + } else { + + gl.blitFramebuffer( 0, 0, renderContext.width, renderContext.height, 0, 0, renderContext.width, renderContext.height, mask, gl.NEAREST ); + + } + + } + + if ( isMRT ) { + + // restore attachments + + for ( let i = 0; i < textures.length; i ++ ) { + + const { textureGPU } = this.get( textures[ i ] ); + + gl.framebufferRenderbuffer( gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.RENDERBUFFER, msaaRenderbuffers[ i ] ); + gl.framebufferTexture2D( gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.TEXTURE_2D, textureGPU, 0 ); + + } + + } + + if ( this._supportsInvalidateFramebuffer === true ) { + + gl.invalidateFramebuffer( gl.READ_FRAMEBUFFER, renderTargetContextData.invalidationArray ); + + } + + } else if ( renderTarget.resolveDepthBuffer === false && renderTargetContextData.framebuffers ) { + + const fb = renderTargetContextData.framebuffers[ renderContext.getCacheKey() ]; + state.bindFramebuffer( gl.DRAW_FRAMEBUFFER, fb ); + gl.invalidateFramebuffer( gl.DRAW_FRAMEBUFFER, renderTargetContextData.depthInvalidationArray ); + + } + + } + + if ( previousContext !== null ) { + + this._setFramebuffer( previousContext ); + + if ( previousContext.viewport ) { + + this.updateViewport( previousContext ); + + } else { + + const { width, height } = this.getDrawingBufferSize( _drawingBufferSize ); + state.viewport( 0, 0, width, height ); + + } + + } + + this.prepareTimestampBuffer( renderContext ); + + } + + /** + * This method processes the result of occlusion queries and writes it + * into render context data. + * + * @async + * @param {RenderContext} renderContext - The render context. + */ + resolveOccludedAsync( renderContext ) { + + const renderContextData = this.get( renderContext ); + + // handle occlusion query results + + const { currentOcclusionQueries, currentOcclusionQueryObjects } = renderContextData; + + if ( currentOcclusionQueries && currentOcclusionQueryObjects ) { + + const occluded = new WeakSet(); + const { gl } = this; + + renderContextData.currentOcclusionQueryObjects = null; + renderContextData.currentOcclusionQueries = null; + + const check = () => { + + let completed = 0; + + // check all queries and requeue as appropriate + for ( let i = 0; i < currentOcclusionQueries.length; i ++ ) { + + const query = currentOcclusionQueries[ i ]; + + if ( query === null ) continue; + + if ( gl.getQueryParameter( query, gl.QUERY_RESULT_AVAILABLE ) ) { + + if ( gl.getQueryParameter( query, gl.QUERY_RESULT ) === 0 ) occluded.add( currentOcclusionQueryObjects[ i ] ); + + currentOcclusionQueries[ i ] = null; + gl.deleteQuery( query ); + + completed ++; + + } + + } + + if ( completed < currentOcclusionQueries.length ) { + + requestAnimationFrame( check ); + + } else { + + renderContextData.occluded = occluded; + + } + + }; + + check(); + + } + + } + + /** + * Returns `true` if the given 3D object is fully occluded by other + * 3D objects in the scene. + * + * @param {RenderContext} renderContext - The render context. + * @param {Object3D} object - The 3D object to test. + * @return {boolean} Whether the 3D object is fully occluded or not. + */ + isOccluded( renderContext, object ) { + + const renderContextData = this.get( renderContext ); + + return renderContextData.occluded && renderContextData.occluded.has( object ); + + } + + /** + * Updates the viewport with the values from the given render context. + * + * @param {RenderContext} renderContext - The render context. + */ + updateViewport( renderContext ) { + + const { state } = this; + const { x, y, width, height } = renderContext.viewportValue; + + state.viewport( x, renderContext.height - height - y, width, height ); + + } + + /** + * Defines the scissor test. + * + * @param {boolean} boolean - Whether the scissor test should be enabled or not. + */ + setScissorTest( boolean ) { + + const state = this.state; + + state.setScissorTest( boolean ); + + } + + /** + * Returns the clear color and alpha into a single + * color object. + * + * @return {Color4} The clear color. + */ + getClearColor() { + + const clearColor = super.getClearColor(); + + // Since the canvas is always created with alpha: true, + // WebGL must always premultiply the clear color. + + clearColor.r *= clearColor.a; + clearColor.g *= clearColor.a; + clearColor.b *= clearColor.a; + + return clearColor; + + } + + /** + * Performs a clear operation. + * + * @param {boolean} color - Whether the color buffer should be cleared or not. + * @param {boolean} depth - Whether the depth buffer should be cleared or not. + * @param {boolean} stencil - Whether the stencil buffer should be cleared or not. + * @param {?Object} [descriptor=null] - The render context of the current set render target. + * @param {boolean} [setFrameBuffer=true] - TODO. + */ + clear( color, depth, stencil, descriptor = null, setFrameBuffer = true ) { + + const { gl, renderer } = this; + + if ( descriptor === null ) { + + const clearColor = this.getClearColor(); + + descriptor = { + textures: null, + clearColorValue: clearColor + }; + + } + + // + + let clear = 0; + + if ( color ) clear |= gl.COLOR_BUFFER_BIT; + if ( depth ) clear |= gl.DEPTH_BUFFER_BIT; + if ( stencil ) clear |= gl.STENCIL_BUFFER_BIT; + + if ( clear !== 0 ) { + + let clearColor; + + if ( descriptor.clearColorValue ) { + + clearColor = descriptor.clearColorValue; + + } else { + + clearColor = this.getClearColor(); + + } + + const clearDepth = renderer.getClearDepth(); + const clearStencil = renderer.getClearStencil(); + + if ( depth ) this.state.setDepthMask( true ); + + if ( descriptor.textures === null ) { + + gl.clearColor( clearColor.r, clearColor.g, clearColor.b, clearColor.a ); + gl.clear( clear ); + + } else { + + if ( setFrameBuffer ) this._setFramebuffer( descriptor ); + + if ( color ) { + + for ( let i = 0; i < descriptor.textures.length; i ++ ) { + + if ( i === 0 ) { + + gl.clearBufferfv( gl.COLOR, i, [ clearColor.r, clearColor.g, clearColor.b, clearColor.a ] ); + + } else { + + gl.clearBufferfv( gl.COLOR, i, [ 0, 0, 0, 1 ] ); + + } + + } + + } + + if ( depth && stencil ) { + + gl.clearBufferfi( gl.DEPTH_STENCIL, 0, clearDepth, clearStencil ); + + } else if ( depth ) { + + gl.clearBufferfv( gl.DEPTH, 0, [ clearDepth ] ); + + } else if ( stencil ) { + + gl.clearBufferiv( gl.STENCIL, 0, [ clearStencil ] ); + + } + + } + + } + + } + + /** + * This method is executed at the beginning of a compute call and + * prepares the state for upcoming compute tasks. + * + * @param {Node|Array} computeGroup - The compute node(s). + */ + beginCompute( computeGroup ) { + + const { state, gl } = this; + + state.bindFramebuffer( gl.FRAMEBUFFER, null ); + this.initTimestampQuery( computeGroup ); + + } + + /** + * Executes a compute command for the given compute node. + * + * @param {Node|Array} computeGroup - The group of compute nodes of a compute call. Can be a single compute node. + * @param {Node} computeNode - The compute node. + * @param {Array} bindings - The bindings. + * @param {ComputePipeline} pipeline - The compute pipeline. + */ + compute( computeGroup, computeNode, bindings, pipeline ) { + + const { state, gl } = this; + + if ( this.discard === false ) { + + // required here to handle async behaviour of render.compute() + gl.enable( gl.RASTERIZER_DISCARD ); + this.discard = true; + + } + + const { programGPU, transformBuffers, attributes } = this.get( pipeline ); + + const vaoKey = this._getVaoKey( attributes ); + + const vaoGPU = this.vaoCache[ vaoKey ]; + + if ( vaoGPU === undefined ) { + + this.vaoCache[ vaoKey ] = this._createVao( attributes ); + + } else { + + state.setVertexState( vaoGPU ); + + } + + state.useProgram( programGPU ); + + this._bindUniforms( bindings ); + + const transformFeedbackGPU = this._getTransformFeedback( transformBuffers ); + + gl.bindTransformFeedback( gl.TRANSFORM_FEEDBACK, transformFeedbackGPU ); + gl.beginTransformFeedback( gl.POINTS ); + + if ( attributes[ 0 ].isStorageInstancedBufferAttribute ) { + + gl.drawArraysInstanced( gl.POINTS, 0, 1, computeNode.count ); + + } else { + + gl.drawArrays( gl.POINTS, 0, computeNode.count ); + + } + + gl.endTransformFeedback(); + gl.bindTransformFeedback( gl.TRANSFORM_FEEDBACK, null ); + + // switch active buffers + + for ( let i = 0; i < transformBuffers.length; i ++ ) { + + const dualAttributeData = transformBuffers[ i ]; + + if ( dualAttributeData.pbo && this.has( dualAttributeData.pbo ) ) { + + this.textureUtils.copyBufferToTexture( dualAttributeData.transformBuffer, dualAttributeData.pbo ); + + } + + dualAttributeData.switchBuffers(); + + + } + + } + + /** + * This method is executed at the end of a compute call and + * finalizes work after compute tasks. + * + * @param {Node|Array} computeGroup - The compute node(s). + */ + finishCompute( computeGroup ) { + + const gl = this.gl; + + this.discard = false; + + gl.disable( gl.RASTERIZER_DISCARD ); + + this.prepareTimestampBuffer( computeGroup ); + + if ( this._currentContext ) { + + this._setFramebuffer( this._currentContext ); + + } + + } + + /** + * Internal to determine if the current render target is a render target array with depth 2D array texture. + * + * @param {RenderContext} renderContext - The render context. + * @return {boolean} Whether the render target is a render target array with depth 2D array texture. + * + * @private + */ + _isRenderCameraDepthArray( renderContext ) { + + return renderContext.depthTexture && renderContext.depthTexture.isArrayTexture && renderContext.camera.isArrayCamera; + + } + + /** + * Executes a draw command for the given render object. + * + * @param {RenderObject} renderObject - The render object to draw. + * @param {Info} info - Holds a series of statistical information about the GPU memory and the rendering process. + */ + draw( renderObject/*, info*/ ) { + + const { object, pipeline, material, context, hardwareClippingPlanes } = renderObject; + const { programGPU } = this.get( pipeline ); + + const { gl, state } = this; + + const contextData = this.get( context ); + + const drawParams = renderObject.getDrawParameters(); + + if ( drawParams === null ) return; + + // + + this._bindUniforms( renderObject.getBindings() ); + + const frontFaceCW = ( object.isMesh && object.matrixWorld.determinant() < 0 ); + + state.setMaterial( material, frontFaceCW, hardwareClippingPlanes ); + + state.useProgram( programGPU ); + + // vertex state + + const attributes = renderObject.getAttributes(); + const attributesData = this.get( attributes ); + + let vaoGPU = attributesData.vaoGPU; + + if ( vaoGPU === undefined ) { + + const vaoKey = this._getVaoKey( attributes ); + + vaoGPU = this.vaoCache[ vaoKey ]; + + if ( vaoGPU === undefined ) { + + vaoGPU = this._createVao( attributes ); + + this.vaoCache[ vaoKey ] = vaoGPU; + attributesData.vaoGPU = vaoGPU; + + } + + } + + const index = renderObject.getIndex(); + const indexGPU = ( index !== null ) ? this.get( index ).bufferGPU : null; + + state.setVertexState( vaoGPU, indexGPU ); + + // + + const lastObject = contextData.lastOcclusionObject; + + if ( lastObject !== object && lastObject !== undefined ) { + + if ( lastObject !== null && lastObject.occlusionTest === true ) { + + gl.endQuery( gl.ANY_SAMPLES_PASSED ); + + contextData.occlusionQueryIndex ++; + + } + + if ( object.occlusionTest === true ) { + + const query = gl.createQuery(); + + gl.beginQuery( gl.ANY_SAMPLES_PASSED, query ); + + contextData.occlusionQueries[ contextData.occlusionQueryIndex ] = query; + contextData.occlusionQueryObjects[ contextData.occlusionQueryIndex ] = object; + + } + + contextData.lastOcclusionObject = object; + + } + + // + const renderer = this.bufferRenderer; + + if ( object.isPoints ) renderer.mode = gl.POINTS; + else if ( object.isLineSegments ) renderer.mode = gl.LINES; + else if ( object.isLine ) renderer.mode = gl.LINE_STRIP; + else if ( object.isLineLoop ) renderer.mode = gl.LINE_LOOP; + else { + + if ( material.wireframe === true ) { + + state.setLineWidth( material.wireframeLinewidth * this.renderer.getPixelRatio() ); + renderer.mode = gl.LINES; + + } else { + + renderer.mode = gl.TRIANGLES; + + } + + } + + // + + const { vertexCount, instanceCount } = drawParams; + let { firstVertex } = drawParams; + + renderer.object = object; + + if ( index !== null ) { + + firstVertex *= index.array.BYTES_PER_ELEMENT; + + const indexData = this.get( index ); + + renderer.index = index.count; + renderer.type = indexData.type; + + } else { + + renderer.index = 0; + + } + + const draw = () => { + + if ( object.isBatchedMesh ) { + + if ( object._multiDrawInstances !== null ) { + + // @deprecated, r174 + warnOnce( 'THREE.WebGLBackend: renderMultiDrawInstances has been deprecated and will be removed in r184. Append to renderMultiDraw arguments and use indirection.' ); + renderer.renderMultiDrawInstances( object._multiDrawStarts, object._multiDrawCounts, object._multiDrawCount, object._multiDrawInstances ); + + } else if ( ! this.hasFeature( 'WEBGL_multi_draw' ) ) { + + warnOnce( 'THREE.WebGLRenderer: WEBGL_multi_draw not supported.' ); + + } else { + + renderer.renderMultiDraw( object._multiDrawStarts, object._multiDrawCounts, object._multiDrawCount ); + + } + + } else if ( instanceCount > 1 ) { + + renderer.renderInstances( firstVertex, vertexCount, instanceCount ); + + } else { + + renderer.render( firstVertex, vertexCount ); + + } + + }; + + if ( renderObject.camera.isArrayCamera === true && renderObject.camera.cameras.length > 0 && renderObject.camera.isMultiViewCamera === false ) { + + const cameraData = this.get( renderObject.camera ); + const cameras = renderObject.camera.cameras; + const cameraIndex = renderObject.getBindingGroup( 'cameraIndex' ).bindings[ 0 ]; + + if ( cameraData.indexesGPU === undefined || cameraData.indexesGPU.length !== cameras.length ) { + + const data = new Uint32Array( [ 0, 0, 0, 0 ] ); + const indexesGPU = []; + + for ( let i = 0, len = cameras.length; i < len; i ++ ) { + + const bufferGPU = gl.createBuffer(); + + data[ 0 ] = i; + + gl.bindBuffer( gl.UNIFORM_BUFFER, bufferGPU ); + gl.bufferData( gl.UNIFORM_BUFFER, data, gl.STATIC_DRAW ); + + indexesGPU.push( bufferGPU ); + + } + + cameraData.indexesGPU = indexesGPU; // TODO: Create a global library for this + + } + + const cameraIndexData = this.get( cameraIndex ); + const pixelRatio = this.renderer.getPixelRatio(); + + const renderTarget = this._currentContext.renderTarget; + const isRenderCameraDepthArray = this._isRenderCameraDepthArray( this._currentContext ); + const prevActiveCubeFace = this._currentContext.activeCubeFace; + + if ( isRenderCameraDepthArray ) { + + // Clear the depth texture + const textureData = this.get( renderTarget.depthTexture ); + + if ( textureData.clearedRenderId !== this.renderer._nodes.nodeFrame.renderId ) { + + textureData.clearedRenderId = this.renderer._nodes.nodeFrame.renderId; + + const { stencilBuffer } = renderTarget; + + for ( let i = 0, len = cameras.length; i < len; i ++ ) { + + this.renderer._activeCubeFace = i; + this._currentContext.activeCubeFace = i; + + this._setFramebuffer( this._currentContext ); + this.clear( false, true, stencilBuffer, this._currentContext, false ); + + } + + this.renderer._activeCubeFace = prevActiveCubeFace; + this._currentContext.activeCubeFace = prevActiveCubeFace; + + } + + } + + for ( let i = 0, len = cameras.length; i < len; i ++ ) { + + const subCamera = cameras[ i ]; + + if ( object.layers.test( subCamera.layers ) ) { + + if ( isRenderCameraDepthArray ) { + + // Update the active layer + this.renderer._activeCubeFace = i; + this._currentContext.activeCubeFace = i; + + this._setFramebuffer( this._currentContext ); + + } + + const vp = subCamera.viewport; + + if ( vp !== undefined ) { + + const x = vp.x * pixelRatio; + const y = vp.y * pixelRatio; + const width = vp.width * pixelRatio; + const height = vp.height * pixelRatio; + + state.viewport( + Math.floor( x ), + Math.floor( renderObject.context.height - height - y ), + Math.floor( width ), + Math.floor( height ) + ); + + } + + state.bindBufferBase( gl.UNIFORM_BUFFER, cameraIndexData.index, cameraData.indexesGPU[ i ] ); + + draw(); + + } + + this._currentContext.activeCubeFace = prevActiveCubeFace; + this.renderer._activeCubeFace = prevActiveCubeFace; + + } + + } else { + + draw(); + + } + + } + + /** + * Explain why always null is returned. + * + * @param {RenderObject} renderObject - The render object. + * @return {boolean} Whether the render pipeline requires an update or not. + */ + needsRenderUpdate( /*renderObject*/ ) { + + return false; + + } + + /** + * Explain why no cache key is computed. + * + * @param {RenderObject} renderObject - The render object. + * @return {string} The cache key. + */ + getRenderCacheKey( /*renderObject*/ ) { + + return ''; + + } + + // textures + + /** + * Creates a default texture for the given texture that can be used + * as a placeholder until the actual texture is ready for usage. + * + * @param {Texture} texture - The texture to create a default texture for. + */ + createDefaultTexture( texture ) { + + this.textureUtils.createDefaultTexture( texture ); + + } + + /** + * Defines a texture on the GPU for the given texture object. + * + * @param {Texture} texture - The texture. + * @param {Object} [options={}] - Optional configuration parameter. + */ + createTexture( texture, options ) { + + this.textureUtils.createTexture( texture, options ); + + } + + /** + * Uploads the updated texture data to the GPU. + * + * @param {Texture} texture - The texture. + * @param {Object} [options={}] - Optional configuration parameter. + */ + updateTexture( texture, options ) { + + this.textureUtils.updateTexture( texture, options ); + + } + + /** + * Generates mipmaps for the given texture. + * + * @param {Texture} texture - The texture. + */ + generateMipmaps( texture ) { + + this.textureUtils.generateMipmaps( texture ); + + } + + /** + * Destroys the GPU data for the given texture object. + * + * @param {Texture} texture - The texture. + */ + destroyTexture( texture ) { + + this.textureUtils.destroyTexture( texture ); + + } + + /** + * Returns texture data as a typed array. + * + * @async + * @param {Texture} texture - The texture to copy. + * @param {number} x - The x coordinate of the copy origin. + * @param {number} y - The y coordinate of the copy origin. + * @param {number} width - The width of the copy. + * @param {number} height - The height of the copy. + * @param {number} faceIndex - The face index. + * @return {Promise} A Promise that resolves with a typed array when the copy operation has finished. + */ + async copyTextureToBuffer( texture, x, y, width, height, faceIndex ) { + + return this.textureUtils.copyTextureToBuffer( texture, x, y, width, height, faceIndex ); + + } + + /** + * This method does nothing since WebGL 2 has no concept of samplers. + * + * @param {Texture} texture - The texture to create the sampler for. + */ + createSampler( /*texture*/ ) { + + //console.warn( 'Abstract class.' ); + + } + + /** + * This method does nothing since WebGL 2 has no concept of samplers. + * + * @param {Texture} texture - The texture to destroy the sampler for. + */ + destroySampler( /*texture*/ ) {} + + // node builder + + /** + * Returns a node builder for the given render object. + * + * @param {RenderObject} object - The render object. + * @param {Renderer} renderer - The renderer. + * @return {GLSLNodeBuilder} The node builder. + */ + createNodeBuilder( object, renderer ) { + + return new GLSLNodeBuilder( object, renderer ); + + } + + // program + + /** + * Creates a shader program from the given programmable stage. + * + * @param {ProgrammableStage} program - The programmable stage. + */ + createProgram( program ) { + + const gl = this.gl; + const { stage, code } = program; + + const shader = stage === 'fragment' ? gl.createShader( gl.FRAGMENT_SHADER ) : gl.createShader( gl.VERTEX_SHADER ); + + gl.shaderSource( shader, code ); + gl.compileShader( shader ); + + this.set( program, { + shaderGPU: shader + } ); + + } + + /** + * Destroys the shader program of the given programmable stage. + * + * @param {ProgrammableStage} program - The programmable stage. + */ + destroyProgram( program ) { + + this.delete( program ); + + } + + /** + * Creates a render pipeline for the given render object. + * + * @param {RenderObject} renderObject - The render object. + * @param {Array} promises - An array of compilation promises which are used in `compileAsync()`. + */ + createRenderPipeline( renderObject, promises ) { + + const gl = this.gl; + const pipeline = renderObject.pipeline; + + // Program + + const { fragmentProgram, vertexProgram } = pipeline; + + const programGPU = gl.createProgram(); + + const fragmentShader = this.get( fragmentProgram ).shaderGPU; + const vertexShader = this.get( vertexProgram ).shaderGPU; + + gl.attachShader( programGPU, fragmentShader ); + gl.attachShader( programGPU, vertexShader ); + gl.linkProgram( programGPU ); + + this.set( pipeline, { + programGPU, + fragmentShader, + vertexShader + } ); + + if ( promises !== null && this.parallel ) { + + const p = new Promise( ( resolve /*, reject*/ ) => { + + const parallel = this.parallel; + const checkStatus = () => { + + if ( gl.getProgramParameter( programGPU, parallel.COMPLETION_STATUS_KHR ) ) { + + this._completeCompile( renderObject, pipeline ); + resolve(); + + } else { + + requestAnimationFrame( checkStatus ); + + } + + }; + + checkStatus(); + + } ); + + promises.push( p ); + + return; + + } + + this._completeCompile( renderObject, pipeline ); + + } + + /** + * Formats the source code of error messages. + * + * @private + * @param {string} string - The code. + * @param {number} errorLine - The error line. + * @return {string} The formatted code. + */ + _handleSource( string, errorLine ) { + + const lines = string.split( '\n' ); + const lines2 = []; + + const from = Math.max( errorLine - 6, 0 ); + const to = Math.min( errorLine + 6, lines.length ); + + for ( let i = from; i < to; i ++ ) { + + const line = i + 1; + lines2.push( `${line === errorLine ? '>' : ' '} ${line}: ${lines[ i ]}` ); + + } + + return lines2.join( '\n' ); + + } + + /** + * Gets the shader compilation errors from the info log. + * + * @private + * @param {WebGL2RenderingContext} gl - The rendering context. + * @param {WebGLShader} shader - The WebGL shader object. + * @param {string} type - The shader type. + * @return {string} The shader errors. + */ + _getShaderErrors( gl, shader, type ) { + + const status = gl.getShaderParameter( shader, gl.COMPILE_STATUS ); + const errors = gl.getShaderInfoLog( shader ).trim(); + + if ( status && errors === '' ) return ''; + + const errorMatches = /ERROR: 0:(\d+)/.exec( errors ); + if ( errorMatches ) { + + const errorLine = parseInt( errorMatches[ 1 ] ); + return type.toUpperCase() + '\n\n' + errors + '\n\n' + this._handleSource( gl.getShaderSource( shader ), errorLine ); + + } else { + + return errors; + + } + + } + + /** + * Logs shader compilation errors. + * + * @private + * @param {WebGLProgram} programGPU - The WebGL program. + * @param {WebGLShader} glFragmentShader - The fragment shader as a native WebGL shader object. + * @param {WebGLShader} glVertexShader - The vertex shader as a native WebGL shader object. + */ + _logProgramError( programGPU, glFragmentShader, glVertexShader ) { + + if ( this.renderer.debug.checkShaderErrors ) { + + const gl = this.gl; + + const programLog = gl.getProgramInfoLog( programGPU ).trim(); + + if ( gl.getProgramParameter( programGPU, gl.LINK_STATUS ) === false ) { + + + if ( typeof this.renderer.debug.onShaderError === 'function' ) { + + this.renderer.debug.onShaderError( gl, programGPU, glVertexShader, glFragmentShader ); + + } else { + + // default error reporting + + const vertexErrors = this._getShaderErrors( gl, glVertexShader, 'vertex' ); + const fragmentErrors = this._getShaderErrors( gl, glFragmentShader, 'fragment' ); + + console.error( + 'THREE.WebGLProgram: Shader Error ' + gl.getError() + ' - ' + + 'VALIDATE_STATUS ' + gl.getProgramParameter( programGPU, gl.VALIDATE_STATUS ) + '\n\n' + + 'Program Info Log: ' + programLog + '\n' + + vertexErrors + '\n' + + fragmentErrors + ); + + } + + } else if ( programLog !== '' ) { + + console.warn( 'THREE.WebGLProgram: Program Info Log:', programLog ); + + } + + } + + } + + /** + * Completes the shader program setup for the given render object. + * + * @private + * @param {RenderObject} renderObject - The render object. + * @param {RenderPipeline} pipeline - The render pipeline. + */ + _completeCompile( renderObject, pipeline ) { + + const { state, gl } = this; + const pipelineData = this.get( pipeline ); + const { programGPU, fragmentShader, vertexShader } = pipelineData; + + if ( gl.getProgramParameter( programGPU, gl.LINK_STATUS ) === false ) { + + this._logProgramError( programGPU, fragmentShader, vertexShader ); + + } + + state.useProgram( programGPU ); + + // Bindings + + const bindings = renderObject.getBindings(); + + this._setupBindings( bindings, programGPU ); + + // + + this.set( pipeline, { + programGPU + } ); + + } + + /** + * Creates a compute pipeline for the given compute node. + * + * @param {ComputePipeline} computePipeline - The compute pipeline. + * @param {Array} bindings - The bindings. + */ + createComputePipeline( computePipeline, bindings ) { + + const { state, gl } = this; + + // Program + + const fragmentProgram = { + stage: 'fragment', + code: '#version 300 es\nprecision highp float;\nvoid main() {}' + }; + + this.createProgram( fragmentProgram ); + + const { computeProgram } = computePipeline; + + const programGPU = gl.createProgram(); + + const fragmentShader = this.get( fragmentProgram ).shaderGPU; + const vertexShader = this.get( computeProgram ).shaderGPU; + + const transforms = computeProgram.transforms; + + const transformVaryingNames = []; + const transformAttributeNodes = []; + + for ( let i = 0; i < transforms.length; i ++ ) { + + const transform = transforms[ i ]; + + transformVaryingNames.push( transform.varyingName ); + transformAttributeNodes.push( transform.attributeNode ); + + } + + gl.attachShader( programGPU, fragmentShader ); + gl.attachShader( programGPU, vertexShader ); + + gl.transformFeedbackVaryings( + programGPU, + transformVaryingNames, + gl.SEPARATE_ATTRIBS + ); + + gl.linkProgram( programGPU ); + + if ( gl.getProgramParameter( programGPU, gl.LINK_STATUS ) === false ) { + + this._logProgramError( programGPU, fragmentShader, vertexShader ); + + + } + + state.useProgram( programGPU ); + + // Bindings + + this._setupBindings( bindings, programGPU ); + + const attributeNodes = computeProgram.attributes; + const attributes = []; + const transformBuffers = []; + + for ( let i = 0; i < attributeNodes.length; i ++ ) { + + const attribute = attributeNodes[ i ].node.attribute; + + attributes.push( attribute ); + + if ( ! this.has( attribute ) ) this.attributeUtils.createAttribute( attribute, gl.ARRAY_BUFFER ); + + } + + for ( let i = 0; i < transformAttributeNodes.length; i ++ ) { + + const attribute = transformAttributeNodes[ i ].attribute; + + if ( ! this.has( attribute ) ) this.attributeUtils.createAttribute( attribute, gl.ARRAY_BUFFER ); + + const attributeData = this.get( attribute ); + + transformBuffers.push( attributeData ); + + } + + // + + this.set( computePipeline, { + programGPU, + transformBuffers, + attributes + } ); + + } + + /** + * Creates bindings from the given bind group definition. + * + * @param {BindGroup} bindGroup - The bind group. + * @param {Array} bindings - Array of bind groups. + * @param {number} cacheIndex - The cache index. + * @param {number} version - The version. + */ + createBindings( bindGroup, bindings /*, cacheIndex, version*/ ) { + + if ( this._knownBindings.has( bindings ) === false ) { + + this._knownBindings.add( bindings ); + + let uniformBuffers = 0; + let textures = 0; + + for ( const bindGroup of bindings ) { + + this.set( bindGroup, { + textures: textures, + uniformBuffers: uniformBuffers + } ); + + for ( const binding of bindGroup.bindings ) { + + if ( binding.isUniformBuffer ) uniformBuffers ++; + if ( binding.isSampledTexture ) textures ++; + + } + + } + + } + + this.updateBindings( bindGroup, bindings ); + + } + + /** + * Updates the given bind group definition. + * + * @param {BindGroup} bindGroup - The bind group. + * @param {Array} bindings - Array of bind groups. + * @param {number} cacheIndex - The cache index. + * @param {number} version - The version. + */ + updateBindings( bindGroup /*, bindings, cacheIndex, version*/ ) { + + const { gl } = this; + + const bindGroupData = this.get( bindGroup ); + + let i = bindGroupData.uniformBuffers; + let t = bindGroupData.textures; + + for ( const binding of bindGroup.bindings ) { + + if ( binding.isUniformsGroup || binding.isUniformBuffer ) { + + const data = binding.buffer; + const bufferGPU = gl.createBuffer(); + + gl.bindBuffer( gl.UNIFORM_BUFFER, bufferGPU ); + gl.bufferData( gl.UNIFORM_BUFFER, data, gl.DYNAMIC_DRAW ); + + this.set( binding, { + index: i ++, + bufferGPU + } ); + + } else if ( binding.isSampledTexture ) { + + const { textureGPU, glTextureType } = this.get( binding.texture ); + + this.set( binding, { + index: t ++, + textureGPU, + glTextureType + } ); + + } + + } + + } + + /** + * Updates a buffer binding. + * + * @param {Buffer} binding - The buffer binding to update. + */ + updateBinding( binding ) { + + const gl = this.gl; + + if ( binding.isUniformsGroup || binding.isUniformBuffer ) { + + const bindingData = this.get( binding ); + const bufferGPU = bindingData.bufferGPU; + const data = binding.buffer; + + gl.bindBuffer( gl.UNIFORM_BUFFER, bufferGPU ); + gl.bufferData( gl.UNIFORM_BUFFER, data, gl.DYNAMIC_DRAW ); + + } + + } + + // attributes + + /** + * Creates the GPU buffer of an indexed shader attribute. + * + * @param {BufferAttribute} attribute - The indexed buffer attribute. + */ + createIndexAttribute( attribute ) { + + const gl = this.gl; + + this.attributeUtils.createAttribute( attribute, gl.ELEMENT_ARRAY_BUFFER ); + + } + + /** + * Creates the GPU buffer of a shader attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute. + */ + createAttribute( attribute ) { + + if ( this.has( attribute ) ) return; + + const gl = this.gl; + + this.attributeUtils.createAttribute( attribute, gl.ARRAY_BUFFER ); + + } + + /** + * Creates the GPU buffer of a storage attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute. + */ + createStorageAttribute( attribute ) { + + if ( this.has( attribute ) ) return; + + const gl = this.gl; + + this.attributeUtils.createAttribute( attribute, gl.ARRAY_BUFFER ); + + } + + /** + * Updates the GPU buffer of a shader attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute to update. + */ + updateAttribute( attribute ) { + + this.attributeUtils.updateAttribute( attribute ); + + } + + /** + * Destroys the GPU buffer of a shader attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute to destroy. + */ + destroyAttribute( attribute ) { + + this.attributeUtils.destroyAttribute( attribute ); + + } + + /** + * Checks if the given feature is supported by the backend. + * + * @param {string} name - The feature's name. + * @return {boolean} Whether the feature is supported or not. + */ + hasFeature( name ) { + + const keysMatching = Object.keys( GLFeatureName ).filter( key => GLFeatureName[ key ] === name ); + + const extensions = this.extensions; + + for ( let i = 0; i < keysMatching.length; i ++ ) { + + if ( extensions.has( keysMatching[ i ] ) ) return true; + + } + + return false; + + } + + /** + * Returns the maximum anisotropy texture filtering value. + * + * @return {number} The maximum anisotropy texture filtering value. + */ + getMaxAnisotropy() { + + return this.capabilities.getMaxAnisotropy(); + + } + + /** + * Copies data of the given source texture to the given destination texture. + * + * @param {Texture} srcTexture - The source texture. + * @param {Texture} dstTexture - The destination texture. + * @param {?(Box3|Box2)} [srcRegion=null] - The region of the source texture to copy. + * @param {?(Vector2|Vector3)} [dstPosition=null] - The destination position of the copy. + * @param {number} [srcLevel=0] - The source mip level to copy from. + * @param {number} [dstLevel=0] - The destination mip level to copy to. + */ + copyTextureToTexture( srcTexture, dstTexture, srcRegion = null, dstPosition = null, srcLevel = 0, dstLevel = 0 ) { + + this.textureUtils.copyTextureToTexture( srcTexture, dstTexture, srcRegion, dstPosition, srcLevel, dstLevel ); + + } + + /** + * Copies the current bound framebuffer to the given texture. + * + * @param {Texture} texture - The destination texture. + * @param {RenderContext} renderContext - The render context. + * @param {Vector4} rectangle - A four dimensional vector defining the origin and dimension of the copy. + */ + copyFramebufferToTexture( texture, renderContext, rectangle ) { + + this.textureUtils.copyFramebufferToTexture( texture, renderContext, rectangle ); + + } + + /** + * Configures the active framebuffer from the given render context. + * + * @private + * @param {RenderContext} descriptor - The render context. + */ + _setFramebuffer( descriptor ) { + + const { gl, state } = this; + + let currentFrameBuffer = null; + + if ( descriptor.textures !== null ) { + + const renderTarget = descriptor.renderTarget; + const renderTargetContextData = this.get( renderTarget ); + const { samples, depthBuffer, stencilBuffer } = renderTarget; + + const isCube = renderTarget.isWebGLCubeRenderTarget === true; + const isRenderTarget3D = renderTarget.isRenderTarget3D === true; + const isRenderTargetArray = renderTarget.depth > 1; + const isXRRenderTarget = renderTarget.isXRRenderTarget === true; + const _hasExternalTextures = ( isXRRenderTarget === true && renderTarget._hasExternalTextures === true ); + + let msaaFb = renderTargetContextData.msaaFrameBuffer; + let depthRenderbuffer = renderTargetContextData.depthRenderbuffer; + const multisampledRTTExt = this.extensions.get( 'WEBGL_multisampled_render_to_texture' ); + const multiviewExt = this.extensions.get( 'OVR_multiview2' ); + const useMultisampledRTT = this._useMultisampledExtension( renderTarget ); + const cacheKey = getCacheKey( descriptor ); + + let fb; + + if ( isCube ) { + + renderTargetContextData.cubeFramebuffers || ( renderTargetContextData.cubeFramebuffers = {} ); + + fb = renderTargetContextData.cubeFramebuffers[ cacheKey ]; + + } else if ( isXRRenderTarget && _hasExternalTextures === false ) { + + fb = this._xrFramebuffer; + + } else { + + renderTargetContextData.framebuffers || ( renderTargetContextData.framebuffers = {} ); + + fb = renderTargetContextData.framebuffers[ cacheKey ]; + + } + + if ( fb === undefined ) { + + fb = gl.createFramebuffer(); + + state.bindFramebuffer( gl.FRAMEBUFFER, fb ); + + const textures = descriptor.textures; + const depthInvalidationArray = []; + + if ( isCube ) { + + renderTargetContextData.cubeFramebuffers[ cacheKey ] = fb; + + const { textureGPU } = this.get( textures[ 0 ] ); + + const cubeFace = this.renderer._activeCubeFace; + + gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X + cubeFace, textureGPU, 0 ); + + } else { + + renderTargetContextData.framebuffers[ cacheKey ] = fb; + + for ( let i = 0; i < textures.length; i ++ ) { + + const texture = textures[ i ]; + const textureData = this.get( texture ); + textureData.renderTarget = descriptor.renderTarget; + textureData.cacheKey = cacheKey; // required for copyTextureToTexture() + + const attachment = gl.COLOR_ATTACHMENT0 + i; + + if ( renderTarget.multiview ) { + + multiviewExt.framebufferTextureMultisampleMultiviewOVR( gl.FRAMEBUFFER, attachment, textureData.textureGPU, 0, samples, 0, 2 ); + + } else if ( isRenderTarget3D || isRenderTargetArray ) { + + const layer = this.renderer._activeCubeFace; + + gl.framebufferTextureLayer( gl.FRAMEBUFFER, attachment, textureData.textureGPU, 0, layer ); + + } else { + + if ( useMultisampledRTT ) { + + multisampledRTTExt.framebufferTexture2DMultisampleEXT( gl.FRAMEBUFFER, attachment, gl.TEXTURE_2D, textureData.textureGPU, 0, samples ); + + } else { + + gl.framebufferTexture2D( gl.FRAMEBUFFER, attachment, gl.TEXTURE_2D, textureData.textureGPU, 0 ); + + } + + } + + } + + } + + const depthStyle = stencilBuffer ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT; + + if ( renderTarget._autoAllocateDepthBuffer === true ) { + + const renderbuffer = gl.createRenderbuffer(); + this.textureUtils.setupRenderBufferStorage( renderbuffer, descriptor, 0, useMultisampledRTT ); + renderTargetContextData.xrDepthRenderbuffer = renderbuffer; + depthInvalidationArray.push( stencilBuffer ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT ); + + gl.bindRenderbuffer( gl.RENDERBUFFER, renderbuffer ); + gl.framebufferRenderbuffer( gl.FRAMEBUFFER, depthStyle, gl.RENDERBUFFER, renderbuffer ); + + + } else { + + if ( descriptor.depthTexture !== null ) { + + depthInvalidationArray.push( stencilBuffer ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT ); + + const textureData = this.get( descriptor.depthTexture ); + textureData.renderTarget = descriptor.renderTarget; + textureData.cacheKey = cacheKey; // required for copyTextureToTexture() + + if ( renderTarget.multiview ) { + + multiviewExt.framebufferTextureMultisampleMultiviewOVR( gl.FRAMEBUFFER, depthStyle, textureData.textureGPU, 0, samples, 0, 2 ); + + } else if ( _hasExternalTextures && useMultisampledRTT ) { + + multisampledRTTExt.framebufferTexture2DMultisampleEXT( gl.FRAMEBUFFER, depthStyle, gl.TEXTURE_2D, textureData.textureGPU, 0, samples ); + + } else { + + if ( descriptor.depthTexture.isArrayTexture ) { + + const layer = this.renderer._activeCubeFace; + + gl.framebufferTextureLayer( gl.FRAMEBUFFER, depthStyle, textureData.textureGPU, 0, layer ); + + } else { + + gl.framebufferTexture2D( gl.FRAMEBUFFER, depthStyle, gl.TEXTURE_2D, textureData.textureGPU, 0 ); + + } + + } + + } + + } + + renderTargetContextData.depthInvalidationArray = depthInvalidationArray; + + + } else { + + const isRenderCameraDepthArray = this._isRenderCameraDepthArray( descriptor ); + + if ( isRenderCameraDepthArray ) { + + state.bindFramebuffer( gl.FRAMEBUFFER, fb ); + + const layer = this.renderer._activeCubeFace; + + const depthData = this.get( descriptor.depthTexture ); + const depthStyle = stencilBuffer ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT; + gl.framebufferTextureLayer( + gl.FRAMEBUFFER, + depthStyle, + depthData.textureGPU, + 0, + layer + ); + + } + + // rebind external XR textures + + if ( ( isXRRenderTarget || useMultisampledRTT || renderTarget.multiview ) && ( renderTarget._isOpaqueFramebuffer !== true ) ) { + + state.bindFramebuffer( gl.FRAMEBUFFER, fb ); + + // rebind color + + const textureData = this.get( descriptor.textures[ 0 ] ); + + if ( renderTarget.multiview ) { + + multiviewExt.framebufferTextureMultisampleMultiviewOVR( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, textureData.textureGPU, 0, samples, 0, 2 ); + + } else if ( useMultisampledRTT ) { + + multisampledRTTExt.framebufferTexture2DMultisampleEXT( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, textureData.textureGPU, 0, samples ); + + } else { + + gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, textureData.textureGPU, 0 ); + + } + + // rebind depth + + const depthStyle = stencilBuffer ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT; + + if ( renderTarget._autoAllocateDepthBuffer === true ) { + + const renderbuffer = renderTargetContextData.xrDepthRenderbuffer; + gl.bindRenderbuffer( gl.RENDERBUFFER, renderbuffer ); + gl.framebufferRenderbuffer( gl.FRAMEBUFFER, depthStyle, gl.RENDERBUFFER, renderbuffer ); + + } else { + + const textureData = this.get( descriptor.depthTexture ); + + if ( renderTarget.multiview ) { + + multiviewExt.framebufferTextureMultisampleMultiviewOVR( gl.FRAMEBUFFER, depthStyle, textureData.textureGPU, 0, samples, 0, 2 ); + + } else if ( useMultisampledRTT ) { + + multisampledRTTExt.framebufferTexture2DMultisampleEXT( gl.FRAMEBUFFER, depthStyle, gl.TEXTURE_2D, textureData.textureGPU, 0, samples ); + + } else { + + gl.framebufferTexture2D( gl.FRAMEBUFFER, depthStyle, gl.TEXTURE_2D, textureData.textureGPU, 0 ); + + } + + } + + } + + } + + if ( samples > 0 && useMultisampledRTT === false && ! renderTarget.multiview ) { + + if ( msaaFb === undefined ) { + + const invalidationArray = []; + + msaaFb = gl.createFramebuffer(); + + state.bindFramebuffer( gl.FRAMEBUFFER, msaaFb ); + + const msaaRenderbuffers = []; + + const textures = descriptor.textures; + + for ( let i = 0; i < textures.length; i ++ ) { + + msaaRenderbuffers[ i ] = gl.createRenderbuffer(); + + gl.bindRenderbuffer( gl.RENDERBUFFER, msaaRenderbuffers[ i ] ); + + invalidationArray.push( gl.COLOR_ATTACHMENT0 + i ); + + const texture = descriptor.textures[ i ]; + const textureData = this.get( texture ); + + gl.renderbufferStorageMultisample( gl.RENDERBUFFER, samples, textureData.glInternalFormat, descriptor.width, descriptor.height ); + gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.RENDERBUFFER, msaaRenderbuffers[ i ] ); + + + } + + gl.bindRenderbuffer( gl.RENDERBUFFER, null ); + + renderTargetContextData.msaaFrameBuffer = msaaFb; + renderTargetContextData.msaaRenderbuffers = msaaRenderbuffers; + + if ( depthBuffer && depthRenderbuffer === undefined ) { + + depthRenderbuffer = gl.createRenderbuffer(); + this.textureUtils.setupRenderBufferStorage( depthRenderbuffer, descriptor, samples ); + + renderTargetContextData.depthRenderbuffer = depthRenderbuffer; + + const depthStyle = stencilBuffer ? gl.DEPTH_STENCIL_ATTACHMENT : gl.DEPTH_ATTACHMENT; + invalidationArray.push( depthStyle ); + + } + + renderTargetContextData.invalidationArray = invalidationArray; + + } + + currentFrameBuffer = renderTargetContextData.msaaFrameBuffer; + + } else { + + currentFrameBuffer = fb; + + } + + state.drawBuffers( descriptor, fb ); + + } + + state.bindFramebuffer( gl.FRAMEBUFFER, currentFrameBuffer ); + + } + + /** + * Computes the VAO key for the given index and attributes. + * + * @private + * @param {Array} attributes - An array of buffer attributes. + * @return {string} The VAO key. + */ + _getVaoKey( attributes ) { + + let key = ''; + + for ( let i = 0; i < attributes.length; i ++ ) { + + const attributeData = this.get( attributes[ i ] ); + + key += ':' + attributeData.id; + + } + + return key; + + } + + /** + * Creates a VAO from the index and attributes. + * + * @private + * @param {Array} attributes - An array of buffer attributes. + * @return {Object} The VAO data. + */ + _createVao( attributes ) { + + const { gl } = this; + + const vaoGPU = gl.createVertexArray(); + + gl.bindVertexArray( vaoGPU ); + + for ( let i = 0; i < attributes.length; i ++ ) { + + const attribute = attributes[ i ]; + const attributeData = this.get( attribute ); + + gl.bindBuffer( gl.ARRAY_BUFFER, attributeData.bufferGPU ); + gl.enableVertexAttribArray( i ); + + let stride, offset; + + if ( attribute.isInterleavedBufferAttribute === true ) { + + stride = attribute.data.stride * attributeData.bytesPerElement; + offset = attribute.offset * attributeData.bytesPerElement; + + } else { + + stride = 0; + offset = 0; + + } + + if ( attributeData.isInteger ) { + + gl.vertexAttribIPointer( i, attribute.itemSize, attributeData.type, stride, offset ); + + } else { + + gl.vertexAttribPointer( i, attribute.itemSize, attributeData.type, attribute.normalized, stride, offset ); + + } + + if ( attribute.isInstancedBufferAttribute && ! attribute.isInterleavedBufferAttribute ) { + + gl.vertexAttribDivisor( i, attribute.meshPerAttribute ); + + } else if ( attribute.isInterleavedBufferAttribute && attribute.data.isInstancedInterleavedBuffer ) { + + gl.vertexAttribDivisor( i, attribute.data.meshPerAttribute ); + + } + + } + + gl.bindBuffer( gl.ARRAY_BUFFER, null ); + + return vaoGPU; + + } + + /** + * Creates a transform feedback from the given transform buffers. + * + * @private + * @param {Array} transformBuffers - The transform buffers. + * @return {WebGLTransformFeedback} The transform feedback. + */ + _getTransformFeedback( transformBuffers ) { + + let key = ''; + + for ( let i = 0; i < transformBuffers.length; i ++ ) { + + key += ':' + transformBuffers[ i ].id; + + } + + let transformFeedbackGPU = this.transformFeedbackCache[ key ]; + + if ( transformFeedbackGPU !== undefined ) { + + return transformFeedbackGPU; + + } + + const { gl } = this; + + transformFeedbackGPU = gl.createTransformFeedback(); + + gl.bindTransformFeedback( gl.TRANSFORM_FEEDBACK, transformFeedbackGPU ); + + for ( let i = 0; i < transformBuffers.length; i ++ ) { + + const attributeData = transformBuffers[ i ]; + + gl.bindBufferBase( gl.TRANSFORM_FEEDBACK_BUFFER, i, attributeData.transformBuffer ); + + } + + gl.bindTransformFeedback( gl.TRANSFORM_FEEDBACK, null ); + + this.transformFeedbackCache[ key ] = transformFeedbackGPU; + + return transformFeedbackGPU; + + } + + /** + * Setups the given bindings. + * + * @private + * @param {Array} bindings - The bindings. + * @param {WebGLProgram} programGPU - The WebGL program. + */ + _setupBindings( bindings, programGPU ) { + + const gl = this.gl; + + for ( const bindGroup of bindings ) { + + for ( const binding of bindGroup.bindings ) { + + const bindingData = this.get( binding ); + const index = bindingData.index; + + if ( binding.isUniformsGroup || binding.isUniformBuffer ) { + + const location = gl.getUniformBlockIndex( programGPU, binding.name ); + gl.uniformBlockBinding( programGPU, location, index ); + + } else if ( binding.isSampledTexture ) { + + const location = gl.getUniformLocation( programGPU, binding.name ); + gl.uniform1i( location, index ); + + } + + } + + } + + } + + /** + * Binds the given uniforms. + * + * @private + * @param {Array} bindings - The bindings. + */ + _bindUniforms( bindings ) { + + const { gl, state } = this; + + for ( const bindGroup of bindings ) { + + for ( const binding of bindGroup.bindings ) { + + const bindingData = this.get( binding ); + const index = bindingData.index; + + if ( binding.isUniformsGroup || binding.isUniformBuffer ) { + + // TODO USE bindBufferRange to group multiple uniform buffers + state.bindBufferBase( gl.UNIFORM_BUFFER, index, bindingData.bufferGPU ); + + } else if ( binding.isSampledTexture ) { + + state.bindTexture( bindingData.glTextureType, bindingData.textureGPU, gl.TEXTURE0 + index ); + + } + + } + + } + + } + + /** + * Returns `true` if the `WEBGL_multisampled_render_to_texture` extension + * should be used when MSAA is enabled. + * + * @private + * @param {RenderTarget} renderTarget - The render target that should be multisampled. + * @return {boolean} Whether to use the `WEBGL_multisampled_render_to_texture` extension for MSAA or not. + */ + _useMultisampledExtension( renderTarget ) { + + if ( renderTarget.multiview === true ) { + + return true; + + } + + return renderTarget.samples > 0 && this.extensions.has( 'WEBGL_multisampled_render_to_texture' ) === true && renderTarget._autoAllocateDepthBuffer !== false; + + } + + /** + * Frees internal resources. + */ + dispose() { + + const extension = this.extensions.get( 'WEBGL_lose_context' ); + if ( extension ) extension.loseContext(); + + this.renderer.domElement.removeEventListener( 'webglcontextlost', this._onContextLost ); + + } + +} + +const GPUPrimitiveTopology = { + PointList: 'point-list', + LineList: 'line-list', + LineStrip: 'line-strip', + TriangleList: 'triangle-list', + TriangleStrip: 'triangle-strip', +}; + +const GPUCompareFunction = { + Never: 'never', + Less: 'less', + Equal: 'equal', + LessEqual: 'less-equal', + Greater: 'greater', + NotEqual: 'not-equal', + GreaterEqual: 'greater-equal', + Always: 'always' +}; + +const GPUStoreOp = { + Store: 'store'}; + +const GPULoadOp = { + Load: 'load', + Clear: 'clear' +}; + +const GPUFrontFace = { + CCW: 'ccw'}; + +const GPUCullMode = { + None: 'none', + Front: 'front', + Back: 'back' +}; + +const GPUIndexFormat = { + Uint16: 'uint16', + Uint32: 'uint32' +}; + +const GPUTextureFormat = { + + // 8-bit formats + + R8Unorm: 'r8unorm', + R8Snorm: 'r8snorm', + R8Uint: 'r8uint', + R8Sint: 'r8sint', + + // 16-bit formats + + R16Uint: 'r16uint', + R16Sint: 'r16sint', + R16Float: 'r16float', + RG8Unorm: 'rg8unorm', + RG8Snorm: 'rg8snorm', + RG8Uint: 'rg8uint', + RG8Sint: 'rg8sint', + + // 32-bit formats + + R32Uint: 'r32uint', + R32Sint: 'r32sint', + R32Float: 'r32float', + RG16Uint: 'rg16uint', + RG16Sint: 'rg16sint', + RG16Float: 'rg16float', + RGBA8Unorm: 'rgba8unorm', + RGBA8UnormSRGB: 'rgba8unorm-srgb', + RGBA8Snorm: 'rgba8snorm', + RGBA8Uint: 'rgba8uint', + RGBA8Sint: 'rgba8sint', + BGRA8Unorm: 'bgra8unorm', + BGRA8UnormSRGB: 'bgra8unorm-srgb', + // Packed 32-bit formats + RGB9E5UFloat: 'rgb9e5ufloat', + RGB10A2Unorm: 'rgb10a2unorm', + RG11B10UFloat: 'rgb10a2unorm', + + // 64-bit formats + + RG32Uint: 'rg32uint', + RG32Sint: 'rg32sint', + RG32Float: 'rg32float', + RGBA16Uint: 'rgba16uint', + RGBA16Sint: 'rgba16sint', + RGBA16Float: 'rgba16float', + + // 128-bit formats + + RGBA32Uint: 'rgba32uint', + RGBA32Sint: 'rgba32sint', + RGBA32Float: 'rgba32float', + + Depth16Unorm: 'depth16unorm', + Depth24Plus: 'depth24plus', + Depth24PlusStencil8: 'depth24plus-stencil8', + Depth32Float: 'depth32float', + + // 'depth32float-stencil8' extension + + Depth32FloatStencil8: 'depth32float-stencil8', + + // BC compressed formats usable if 'texture-compression-bc' is both + // supported by the device/user agent and enabled in requestDevice. + + BC1RGBAUnorm: 'bc1-rgba-unorm', + BC1RGBAUnormSRGB: 'bc1-rgba-unorm-srgb', + BC2RGBAUnorm: 'bc2-rgba-unorm', + BC2RGBAUnormSRGB: 'bc2-rgba-unorm-srgb', + BC3RGBAUnorm: 'bc3-rgba-unorm', + BC3RGBAUnormSRGB: 'bc3-rgba-unorm-srgb', + BC4RUnorm: 'bc4-r-unorm', + BC4RSnorm: 'bc4-r-snorm', + BC5RGUnorm: 'bc5-rg-unorm', + BC5RGSnorm: 'bc5-rg-snorm', + BC6HRGBUFloat: 'bc6h-rgb-ufloat', + BC6HRGBFloat: 'bc6h-rgb-float', + BC7RGBAUnorm: 'bc7-rgba-unorm', + BC7RGBAUnormSRGB: 'bc7-rgba-srgb', + + // ETC2 compressed formats usable if 'texture-compression-etc2' is both + // supported by the device/user agent and enabled in requestDevice. + + ETC2RGB8Unorm: 'etc2-rgb8unorm', + ETC2RGB8UnormSRGB: 'etc2-rgb8unorm-srgb', + ETC2RGB8A1Unorm: 'etc2-rgb8a1unorm', + ETC2RGB8A1UnormSRGB: 'etc2-rgb8a1unorm-srgb', + ETC2RGBA8Unorm: 'etc2-rgba8unorm', + ETC2RGBA8UnormSRGB: 'etc2-rgba8unorm-srgb', + EACR11Unorm: 'eac-r11unorm', + EACR11Snorm: 'eac-r11snorm', + EACRG11Unorm: 'eac-rg11unorm', + EACRG11Snorm: 'eac-rg11snorm', + + // ASTC compressed formats usable if 'texture-compression-astc' is both + // supported by the device/user agent and enabled in requestDevice. + + ASTC4x4Unorm: 'astc-4x4-unorm', + ASTC4x4UnormSRGB: 'astc-4x4-unorm-srgb', + ASTC5x4Unorm: 'astc-5x4-unorm', + ASTC5x4UnormSRGB: 'astc-5x4-unorm-srgb', + ASTC5x5Unorm: 'astc-5x5-unorm', + ASTC5x5UnormSRGB: 'astc-5x5-unorm-srgb', + ASTC6x5Unorm: 'astc-6x5-unorm', + ASTC6x5UnormSRGB: 'astc-6x5-unorm-srgb', + ASTC6x6Unorm: 'astc-6x6-unorm', + ASTC6x6UnormSRGB: 'astc-6x6-unorm-srgb', + ASTC8x5Unorm: 'astc-8x5-unorm', + ASTC8x5UnormSRGB: 'astc-8x5-unorm-srgb', + ASTC8x6Unorm: 'astc-8x6-unorm', + ASTC8x6UnormSRGB: 'astc-8x6-unorm-srgb', + ASTC8x8Unorm: 'astc-8x8-unorm', + ASTC8x8UnormSRGB: 'astc-8x8-unorm-srgb', + ASTC10x5Unorm: 'astc-10x5-unorm', + ASTC10x5UnormSRGB: 'astc-10x5-unorm-srgb', + ASTC10x6Unorm: 'astc-10x6-unorm', + ASTC10x6UnormSRGB: 'astc-10x6-unorm-srgb', + ASTC10x8Unorm: 'astc-10x8-unorm', + ASTC10x8UnormSRGB: 'astc-10x8-unorm-srgb', + ASTC10x10Unorm: 'astc-10x10-unorm', + ASTC10x10UnormSRGB: 'astc-10x10-unorm-srgb', + ASTC12x10Unorm: 'astc-12x10-unorm', + ASTC12x10UnormSRGB: 'astc-12x10-unorm-srgb', + ASTC12x12Unorm: 'astc-12x12-unorm', + ASTC12x12UnormSRGB: 'astc-12x12-unorm-srgb', + +}; + +const GPUAddressMode = { + ClampToEdge: 'clamp-to-edge', + Repeat: 'repeat', + MirrorRepeat: 'mirror-repeat' +}; + +const GPUFilterMode = { + Linear: 'linear', + Nearest: 'nearest' +}; + +const GPUBlendFactor = { + Zero: 'zero', + One: 'one', + Src: 'src', + OneMinusSrc: 'one-minus-src', + SrcAlpha: 'src-alpha', + OneMinusSrcAlpha: 'one-minus-src-alpha', + Dst: 'dst', + OneMinusDst: 'one-minus-dst', + DstAlpha: 'dst-alpha', + OneMinusDstAlpha: 'one-minus-dst-alpha', + SrcAlphaSaturated: 'src-alpha-saturated', + Constant: 'constant', + OneMinusConstant: 'one-minus-constant' +}; + +const GPUBlendOperation = { + Add: 'add', + Subtract: 'subtract', + ReverseSubtract: 'reverse-subtract', + Min: 'min', + Max: 'max' +}; + +const GPUColorWriteFlags = { + None: 0, + All: 0xF +}; + +const GPUStencilOperation = { + Keep: 'keep', + Zero: 'zero', + Replace: 'replace', + Invert: 'invert', + IncrementClamp: 'increment-clamp', + DecrementClamp: 'decrement-clamp', + IncrementWrap: 'increment-wrap', + DecrementWrap: 'decrement-wrap' +}; + +const GPUBufferBindingType = { + Storage: 'storage', + ReadOnlyStorage: 'read-only-storage' +}; + +const GPUStorageTextureAccess = { + WriteOnly: 'write-only', + ReadOnly: 'read-only', + ReadWrite: 'read-write', +}; + +const GPUSamplerBindingType = { + NonFiltering: 'non-filtering', + Comparison: 'comparison' +}; + +const GPUTextureSampleType = { + Float: 'float', + UnfilterableFloat: 'unfilterable-float', + Depth: 'depth', + SInt: 'sint', + UInt: 'uint' +}; + +const GPUTextureDimension = { + TwoD: '2d', + ThreeD: '3d' +}; + +const GPUTextureViewDimension = { + TwoD: '2d', + TwoDArray: '2d-array', + Cube: 'cube', + ThreeD: '3d' +}; + +const GPUTextureAspect = { + All: 'all'}; + +const GPUInputStepMode = { + Vertex: 'vertex', + Instance: 'instance' +}; + +const GPUFeatureName = { + CoreFeaturesAndLimits: 'core-features-and-limits', + DepthClipControl: 'depth-clip-control', + Depth32FloatStencil8: 'depth32float-stencil8', + TextureCompressionBC: 'texture-compression-bc', + TextureCompressionBCSliced3D: 'texture-compression-bc-sliced-3d', + TextureCompressionETC2: 'texture-compression-etc2', + TextureCompressionASTC: 'texture-compression-astc', + TextureCompressionASTCSliced3D: 'texture-compression-astc-sliced-3d', + TimestampQuery: 'timestamp-query', + IndirectFirstInstance: 'indirect-first-instance', + ShaderF16: 'shader-f16', + RG11B10UFloat: 'rg11b10ufloat-renderable', + BGRA8UNormStorage: 'bgra8unorm-storage', + Float32Filterable: 'float32-filterable', + Float32Blendable: 'float32-blendable', + ClipDistances: 'clip-distances', + DualSourceBlending: 'dual-source-blending', + Subgroups: 'subgroups', + TextureFormatsTier1: 'texture-formats-tier1', + TextureFormatsTier2: 'texture-formats-tier2' +}; + +/** + * Represents a sampler binding type. + * + * @private + * @augments Binding + */ +class Sampler extends Binding { + + /** + * Constructs a new sampler. + * + * @param {string} name - The samplers's name. + * @param {?Texture} texture - The texture this binding is referring to. + */ + constructor( name, texture ) { + + super( name ); + + /** + * The texture the sampler is referring to. + * + * @type {?Texture} + */ + this.texture = texture; + + /** + * The binding's version. + * + * @type {number} + */ + this.version = texture ? texture.version : 0; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSampler = true; + + } + +} + +/** + * A special form of sampler binding type. + * It's texture value is managed by a node object. + * + * @private + * @augments Sampler + */ +class NodeSampler extends Sampler { + + /** + * Constructs a new node-based sampler. + * + * @param {string} name - The samplers's name. + * @param {TextureNode} textureNode - The texture node. + * @param {UniformGroupNode} groupNode - The uniform group node. + */ + constructor( name, textureNode, groupNode ) { + + super( name, textureNode ? textureNode.value : null ); + + /** + * The texture node. + * + * @type {TextureNode} + */ + this.textureNode = textureNode; + + /** + * The uniform group node. + * + * @type {UniformGroupNode} + */ + this.groupNode = groupNode; + + } + + /** + * Updates the texture value of this sampler. + */ + update() { + + this.texture = this.textureNode.value; + + } + +} + +/** + * Represents a storage buffer binding type. + * + * @private + * @augments Buffer + */ +class StorageBuffer extends Buffer { + + /** + * Constructs a new uniform buffer. + * + * @param {string} name - The buffer's name. + * @param {BufferAttribute} attribute - The buffer attribute. + */ + constructor( name, attribute ) { + + super( name, attribute ? attribute.array : null ); + + /** + * This flag can be used for type testing. + * + * @type {BufferAttribute} + */ + this.attribute = attribute; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isStorageBuffer = true; + + } + +} + +let _id = 0; + +/** + * A special form of storage buffer binding type. + * It's buffer value is managed by a node object. + * + * @private + * @augments StorageBuffer + */ +class NodeStorageBuffer extends StorageBuffer { + + /** + * Constructs a new node-based storage buffer. + * + * @param {StorageBufferNode} nodeUniform - The storage buffer node. + * @param {UniformGroupNode} groupNode - The uniform group node. + */ + constructor( nodeUniform, groupNode ) { + + super( 'StorageBuffer_' + _id ++, nodeUniform ? nodeUniform.value : null ); + + /** + * The node uniform. + * + * @type {StorageBufferNode} + */ + this.nodeUniform = nodeUniform; + + /** + * The access type. + * + * @type {string} + */ + this.access = nodeUniform ? nodeUniform.access : NodeAccess.READ_WRITE; + + /** + * The uniform group node. + * + * @type {UniformGroupNode} + */ + this.groupNode = groupNode; + + } + + /** + * The storage buffer. + * + * @type {BufferAttribute} + */ + get buffer() { + + return this.nodeUniform.value; + + } + +} + +/** + * A WebGPU backend utility module used by {@link WebGPUTextureUtils}. + * + * @private + */ +class WebGPUTexturePassUtils extends DataMap { + + /** + * Constructs a new utility object. + * + * @param {GPUDevice} device - The WebGPU device. + */ + constructor( device ) { + + super(); + + /** + * The WebGPU device. + * + * @type {GPUDevice} + */ + this.device = device; + + const mipmapVertexSource = ` +struct VarysStruct { + @builtin( position ) Position: vec4, + @location( 0 ) vTex : vec2 +}; + +@vertex +fn main( @builtin( vertex_index ) vertexIndex : u32 ) -> VarysStruct { + + var Varys : VarysStruct; + + var pos = array< vec2, 4 >( + vec2( -1.0, 1.0 ), + vec2( 1.0, 1.0 ), + vec2( -1.0, -1.0 ), + vec2( 1.0, -1.0 ) + ); + + var tex = array< vec2, 4 >( + vec2( 0.0, 0.0 ), + vec2( 1.0, 0.0 ), + vec2( 0.0, 1.0 ), + vec2( 1.0, 1.0 ) + ); + + Varys.vTex = tex[ vertexIndex ]; + Varys.Position = vec4( pos[ vertexIndex ], 0.0, 1.0 ); + + return Varys; + +} +`; + + const mipmapFragmentSource = ` +@group( 0 ) @binding( 0 ) +var imgSampler : sampler; + +@group( 0 ) @binding( 1 ) +var img : texture_2d; + +@fragment +fn main( @location( 0 ) vTex : vec2 ) -> @location( 0 ) vec4 { + + return textureSample( img, imgSampler, vTex ); + +} +`; + + const flipYFragmentSource = ` +@group( 0 ) @binding( 0 ) +var imgSampler : sampler; + +@group( 0 ) @binding( 1 ) +var img : texture_2d; + +@fragment +fn main( @location( 0 ) vTex : vec2 ) -> @location( 0 ) vec4 { + + return textureSample( img, imgSampler, vec2( vTex.x, 1.0 - vTex.y ) ); + +} +`; + + /** + * The mipmap GPU sampler. + * + * @type {GPUSampler} + */ + this.mipmapSampler = device.createSampler( { minFilter: GPUFilterMode.Linear } ); + + /** + * The flipY GPU sampler. + * + * @type {GPUSampler} + */ + this.flipYSampler = device.createSampler( { minFilter: GPUFilterMode.Nearest } ); //@TODO?: Consider using textureLoad() + + /** + * A cache for GPU render pipelines used for copy/transfer passes. + * Every texture format requires a unique pipeline. + * + * @type {Object} + */ + this.transferPipelines = {}; + + /** + * A cache for GPU render pipelines used for flipY passes. + * Every texture format requires a unique pipeline. + * + * @type {Object} + */ + this.flipYPipelines = {}; + + /** + * The mipmap vertex shader module. + * + * @type {GPUShaderModule} + */ + this.mipmapVertexShaderModule = device.createShaderModule( { + label: 'mipmapVertex', + code: mipmapVertexSource + } ); + + /** + * The mipmap fragment shader module. + * + * @type {GPUShaderModule} + */ + this.mipmapFragmentShaderModule = device.createShaderModule( { + label: 'mipmapFragment', + code: mipmapFragmentSource + } ); + + /** + * The flipY fragment shader module. + * + * @type {GPUShaderModule} + */ + this.flipYFragmentShaderModule = device.createShaderModule( { + label: 'flipYFragment', + code: flipYFragmentSource + } ); + + } + + /** + * Returns a render pipeline for the internal copy render pass. The pass + * requires a unique render pipeline for each texture format. + * + * @param {string} format - The GPU texture format + * @return {GPURenderPipeline} The GPU render pipeline. + */ + getTransferPipeline( format ) { + + let pipeline = this.transferPipelines[ format ]; + + if ( pipeline === undefined ) { + + pipeline = this.device.createRenderPipeline( { + label: `mipmap-${ format }`, + vertex: { + module: this.mipmapVertexShaderModule, + entryPoint: 'main' + }, + fragment: { + module: this.mipmapFragmentShaderModule, + entryPoint: 'main', + targets: [ { format } ] + }, + primitive: { + topology: GPUPrimitiveTopology.TriangleStrip, + stripIndexFormat: GPUIndexFormat.Uint32 + }, + layout: 'auto' + } ); + + this.transferPipelines[ format ] = pipeline; + + } + + return pipeline; + + } + + /** + * Returns a render pipeline for the flipY render pass. The pass + * requires a unique render pipeline for each texture format. + * + * @param {string} format - The GPU texture format + * @return {GPURenderPipeline} The GPU render pipeline. + */ + getFlipYPipeline( format ) { + + let pipeline = this.flipYPipelines[ format ]; + + if ( pipeline === undefined ) { + + pipeline = this.device.createRenderPipeline( { + label: `flipY-${ format }`, + vertex: { + module: this.mipmapVertexShaderModule, + entryPoint: 'main' + }, + fragment: { + module: this.flipYFragmentShaderModule, + entryPoint: 'main', + targets: [ { format } ] + }, + primitive: { + topology: GPUPrimitiveTopology.TriangleStrip, + stripIndexFormat: GPUIndexFormat.Uint32 + }, + layout: 'auto' + } ); + + this.flipYPipelines[ format ] = pipeline; + + } + + return pipeline; + + } + + /** + * Flip the contents of the given GPU texture along its vertical axis. + * + * @param {GPUTexture} textureGPU - The GPU texture object. + * @param {Object} textureGPUDescriptor - The texture descriptor. + * @param {number} [baseArrayLayer=0] - The index of the first array layer accessible to the texture view. + */ + flipY( textureGPU, textureGPUDescriptor, baseArrayLayer = 0 ) { + + const format = textureGPUDescriptor.format; + const { width, height } = textureGPUDescriptor.size; + + const transferPipeline = this.getTransferPipeline( format ); + const flipYPipeline = this.getFlipYPipeline( format ); + + const tempTexture = this.device.createTexture( { + size: { width, height, depthOrArrayLayers: 1 }, + format, + usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING + } ); + + const srcView = textureGPU.createView( { + baseMipLevel: 0, + mipLevelCount: 1, + dimension: GPUTextureViewDimension.TwoD, + baseArrayLayer + } ); + + const dstView = tempTexture.createView( { + baseMipLevel: 0, + mipLevelCount: 1, + dimension: GPUTextureViewDimension.TwoD, + baseArrayLayer: 0 + } ); + + const commandEncoder = this.device.createCommandEncoder( {} ); + + const pass = ( pipeline, sourceView, destinationView ) => { + + const bindGroupLayout = pipeline.getBindGroupLayout( 0 ); // @TODO: Consider making this static. + + const bindGroup = this.device.createBindGroup( { + layout: bindGroupLayout, + entries: [ { + binding: 0, + resource: this.flipYSampler + }, { + binding: 1, + resource: sourceView + } ] + } ); + + const passEncoder = commandEncoder.beginRenderPass( { + colorAttachments: [ { + view: destinationView, + loadOp: GPULoadOp.Clear, + storeOp: GPUStoreOp.Store, + clearValue: [ 0, 0, 0, 0 ] + } ] + } ); + + passEncoder.setPipeline( pipeline ); + passEncoder.setBindGroup( 0, bindGroup ); + passEncoder.draw( 4, 1, 0, 0 ); + passEncoder.end(); + + }; + + pass( transferPipeline, srcView, dstView ); + pass( flipYPipeline, dstView, srcView ); + + this.device.queue.submit( [ commandEncoder.finish() ] ); + + tempTexture.destroy(); + + } + + /** + * Generates mipmaps for the given GPU texture. + * + * @param {GPUTexture} textureGPU - The GPU texture object. + * @param {Object} textureGPUDescriptor - The texture descriptor. + * @param {number} [baseArrayLayer=0] - The index of the first array layer accessible to the texture view. + */ + generateMipmaps( textureGPU, textureGPUDescriptor, baseArrayLayer = 0 ) { + + const textureData = this.get( textureGPU ); + + if ( textureData.useCount === undefined ) { + + textureData.useCount = 0; + textureData.layers = []; + + } + + const passes = textureData.layers[ baseArrayLayer ] || this._mipmapCreateBundles( textureGPU, textureGPUDescriptor, baseArrayLayer ); + + const commandEncoder = this.device.createCommandEncoder( {} ); + + this._mipmapRunBundles( commandEncoder, passes ); + + this.device.queue.submit( [ commandEncoder.finish() ] ); + + if ( textureData.useCount !== 0 ) textureData.layers[ baseArrayLayer ] = passes; + + textureData.useCount ++; + + } + + /** + * Since multiple copy render passes are required to generate mipmaps, the passes + * are managed as render bundles to improve performance. + * + * @param {GPUTexture} textureGPU - The GPU texture object. + * @param {Object} textureGPUDescriptor - The texture descriptor. + * @param {number} baseArrayLayer - The index of the first array layer accessible to the texture view. + * @return {Array} An array of render bundles. + */ + _mipmapCreateBundles( textureGPU, textureGPUDescriptor, baseArrayLayer ) { + + const pipeline = this.getTransferPipeline( textureGPUDescriptor.format ); + + const bindGroupLayout = pipeline.getBindGroupLayout( 0 ); // @TODO: Consider making this static. + + let srcView = textureGPU.createView( { + baseMipLevel: 0, + mipLevelCount: 1, + dimension: GPUTextureViewDimension.TwoD, + baseArrayLayer + } ); + + const passes = []; + + for ( let i = 1; i < textureGPUDescriptor.mipLevelCount; i ++ ) { + + const bindGroup = this.device.createBindGroup( { + layout: bindGroupLayout, + entries: [ { + binding: 0, + resource: this.mipmapSampler + }, { + binding: 1, + resource: srcView + } ] + } ); + + const dstView = textureGPU.createView( { + baseMipLevel: i, + mipLevelCount: 1, + dimension: GPUTextureViewDimension.TwoD, + baseArrayLayer + } ); + + const passDescriptor = { + colorAttachments: [ { + view: dstView, + loadOp: GPULoadOp.Clear, + storeOp: GPUStoreOp.Store, + clearValue: [ 0, 0, 0, 0 ] + } ] + }; + + const passEncoder = this.device.createRenderBundleEncoder( { + colorFormats: [ textureGPUDescriptor.format ] + } ); + + passEncoder.setPipeline( pipeline ); + passEncoder.setBindGroup( 0, bindGroup ); + passEncoder.draw( 4, 1, 0, 0 ); + + passes.push( { + renderBundles: [ passEncoder.finish() ], + passDescriptor + } ); + + srcView = dstView; + + } + + return passes; + + } + + /** + * Executes the render bundles. + * + * @param {GPUCommandEncoder} commandEncoder - The GPU command encoder. + * @param {Array} passes - An array of render bundles. + */ + _mipmapRunBundles( commandEncoder, passes ) { + + const levels = passes.length; + + for ( let i = 0; i < levels; i ++ ) { + + const pass = passes[ i ]; + + const passEncoder = commandEncoder.beginRenderPass( pass.passDescriptor ); + + passEncoder.executeBundles( pass.renderBundles ); + + passEncoder.end(); + + } + + } + +} + +const _compareToWebGPU = { + [ NeverCompare ]: 'never', + [ LessCompare ]: 'less', + [ EqualCompare ]: 'equal', + [ LessEqualCompare ]: 'less-equal', + [ GreaterCompare ]: 'greater', + [ GreaterEqualCompare ]: 'greater-equal', + [ AlwaysCompare ]: 'always', + [ NotEqualCompare ]: 'not-equal' +}; + +const _flipMap = [ 0, 1, 3, 2, 4, 5 ]; + +/** + * A WebGPU backend utility module for managing textures. + * + * @private + */ +class WebGPUTextureUtils { + + /** + * Constructs a new utility object. + * + * @param {WebGPUBackend} backend - The WebGPU backend. + */ + constructor( backend ) { + + /** + * A reference to the WebGPU backend. + * + * @type {WebGPUBackend} + */ + this.backend = backend; + + /** + * A reference to the pass utils. + * + * @type {?WebGPUTexturePassUtils} + * @default null + */ + this._passUtils = null; + + /** + * A dictionary for managing default textures. The key + * is the texture format, the value the texture object. + * + * @type {Object} + */ + this.defaultTexture = {}; + + /** + * A dictionary for managing default cube textures. The key + * is the texture format, the value the texture object. + * + * @type {Object} + */ + this.defaultCubeTexture = {}; + + /** + * A default video frame. + * + * @type {?VideoFrame} + * @default null + */ + this.defaultVideoFrame = null; + + /** + * Represents the color attachment of the default framebuffer. + * + * @type {?GPUTexture} + * @default null + */ + this.colorBuffer = null; + + /** + * Represents the depth attachment of the default framebuffer. + * + * @type {DepthTexture} + */ + this.depthTexture = new DepthTexture(); + this.depthTexture.name = 'depthBuffer'; + + } + + /** + * Creates a GPU sampler for the given texture. + * + * @param {Texture} texture - The texture to create the sampler for. + */ + createSampler( texture ) { + + const backend = this.backend; + const device = backend.device; + + const textureGPU = backend.get( texture ); + + const samplerDescriptorGPU = { + addressModeU: this._convertAddressMode( texture.wrapS ), + addressModeV: this._convertAddressMode( texture.wrapT ), + addressModeW: this._convertAddressMode( texture.wrapR ), + magFilter: this._convertFilterMode( texture.magFilter ), + minFilter: this._convertFilterMode( texture.minFilter ), + mipmapFilter: this._convertFilterMode( texture.minFilter ), + maxAnisotropy: 1 + }; + + // anisotropy can only be used when all filter modes are set to linear. + + if ( samplerDescriptorGPU.magFilter === GPUFilterMode.Linear && samplerDescriptorGPU.minFilter === GPUFilterMode.Linear && samplerDescriptorGPU.mipmapFilter === GPUFilterMode.Linear ) { + + samplerDescriptorGPU.maxAnisotropy = texture.anisotropy; + + } + + if ( texture.isDepthTexture && texture.compareFunction !== null ) { + + samplerDescriptorGPU.compare = _compareToWebGPU[ texture.compareFunction ]; + + } + + textureGPU.sampler = device.createSampler( samplerDescriptorGPU ); + + } + + /** + * Creates a default texture for the given texture that can be used + * as a placeholder until the actual texture is ready for usage. + * + * @param {Texture} texture - The texture to create a default texture for. + */ + createDefaultTexture( texture ) { + + let textureGPU; + + const format = getFormat( texture ); + + if ( texture.isCubeTexture ) { + + textureGPU = this._getDefaultCubeTextureGPU( format ); + + } else if ( texture.isVideoTexture ) { + + this.backend.get( texture ).externalTexture = this._getDefaultVideoFrame(); + + } else { + + textureGPU = this._getDefaultTextureGPU( format ); + + } + + this.backend.get( texture ).texture = textureGPU; + + } + + /** + * Defines a texture on the GPU for the given texture object. + * + * @param {Texture} texture - The texture. + * @param {Object} [options={}] - Optional configuration parameter. + */ + createTexture( texture, options = {} ) { + + const backend = this.backend; + const textureData = backend.get( texture ); + + if ( textureData.initialized ) { + + throw new Error( 'WebGPUTextureUtils: Texture already initialized.' ); + + } + + if ( options.needsMipmaps === undefined ) options.needsMipmaps = false; + if ( options.levels === undefined ) options.levels = 1; + if ( options.depth === undefined ) options.depth = 1; + + const { width, height, depth, levels } = options; + + if ( texture.isFramebufferTexture ) { + + if ( options.renderTarget ) { + + options.format = this.backend.utils.getCurrentColorFormat( options.renderTarget ); + + } else { + + options.format = this.backend.utils.getPreferredCanvasFormat(); + + } + + } + + const dimension = this._getDimension( texture ); + const format = texture.internalFormat || options.format || getFormat( texture, backend.device ); + + textureData.format = format; + + const { samples, primarySamples, isMSAA } = backend.utils.getTextureSampleData( texture ); + + let usage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC; + + if ( texture.isStorageTexture === true ) { + + usage |= GPUTextureUsage.STORAGE_BINDING; + + } + + if ( texture.isCompressedTexture !== true && texture.isCompressedArrayTexture !== true ) { + + usage |= GPUTextureUsage.RENDER_ATTACHMENT; + + } + + const textureDescriptorGPU = { + label: texture.name, + size: { + width: width, + height: height, + depthOrArrayLayers: depth, + }, + mipLevelCount: levels, + sampleCount: primarySamples, + dimension: dimension, + format: format, + usage: usage + }; + + // texture creation + + if ( texture.isVideoTexture ) { + + const video = texture.source.data; + const videoFrame = new VideoFrame( video ); + + textureDescriptorGPU.size.width = videoFrame.displayWidth; + textureDescriptorGPU.size.height = videoFrame.displayHeight; + + videoFrame.close(); + + textureData.externalTexture = video; + + } else { + + if ( format === undefined ) { + + console.warn( 'WebGPURenderer: Texture format not supported.' ); + + this.createDefaultTexture( texture ); + return; + + } + + if ( texture.isCubeTexture ) { + + textureDescriptorGPU.textureBindingViewDimension = GPUTextureViewDimension.Cube; + + } + + textureData.texture = backend.device.createTexture( textureDescriptorGPU ); + + } + + if ( isMSAA ) { + + const msaaTextureDescriptorGPU = Object.assign( {}, textureDescriptorGPU ); + + msaaTextureDescriptorGPU.label = msaaTextureDescriptorGPU.label + '-msaa'; + msaaTextureDescriptorGPU.sampleCount = samples; + + textureData.msaaTexture = backend.device.createTexture( msaaTextureDescriptorGPU ); + + } + + textureData.initialized = true; + + textureData.textureDescriptorGPU = textureDescriptorGPU; + + } + + /** + * Destroys the GPU data for the given texture object. + * + * @param {Texture} texture - The texture. + */ + destroyTexture( texture ) { + + const backend = this.backend; + const textureData = backend.get( texture ); + + if ( textureData.texture !== undefined ) textureData.texture.destroy(); + + if ( textureData.msaaTexture !== undefined ) textureData.msaaTexture.destroy(); + + backend.delete( texture ); + + } + + /** + * Destroys the GPU sampler for the given texture. + * + * @param {Texture} texture - The texture to destroy the sampler for. + */ + destroySampler( texture ) { + + const backend = this.backend; + const textureData = backend.get( texture ); + + delete textureData.sampler; + + } + + /** + * Generates mipmaps for the given texture. + * + * @param {Texture} texture - The texture. + */ + generateMipmaps( texture ) { + + const textureData = this.backend.get( texture ); + + if ( texture.isCubeTexture ) { + + for ( let i = 0; i < 6; i ++ ) { + + this._generateMipmaps( textureData.texture, textureData.textureDescriptorGPU, i ); + + } + + } else { + + const depth = texture.image.depth || 1; + + for ( let i = 0; i < depth; i ++ ) { + + this._generateMipmaps( textureData.texture, textureData.textureDescriptorGPU, i ); + + } + + } + + } + + /** + * Returns the color buffer representing the color + * attachment of the default framebuffer. + * + * @return {GPUTexture} The color buffer. + */ + getColorBuffer() { + + if ( this.colorBuffer ) this.colorBuffer.destroy(); + + const backend = this.backend; + const { width, height } = backend.getDrawingBufferSize(); + + this.colorBuffer = backend.device.createTexture( { + label: 'colorBuffer', + size: { + width: width, + height: height, + depthOrArrayLayers: 1 + }, + sampleCount: backend.utils.getSampleCount( backend.renderer.samples ), + format: backend.utils.getPreferredCanvasFormat(), + usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC + } ); + + return this.colorBuffer; + + } + + /** + * Returns the depth buffer representing the depth + * attachment of the default framebuffer. + * + * @param {boolean} [depth=true] - Whether depth is enabled or not. + * @param {boolean} [stencil=false] - Whether stencil is enabled or not. + * @return {GPUTexture} The depth buffer. + */ + getDepthBuffer( depth = true, stencil = false ) { + + const backend = this.backend; + const { width, height } = backend.getDrawingBufferSize(); + + const depthTexture = this.depthTexture; + const depthTextureGPU = backend.get( depthTexture ).texture; + + let format, type; + + if ( stencil ) { + + format = DepthStencilFormat; + type = UnsignedInt248Type; + + } else if ( depth ) { + + format = DepthFormat; + type = UnsignedIntType; + + } + + if ( depthTextureGPU !== undefined ) { + + if ( depthTexture.image.width === width && depthTexture.image.height === height && depthTexture.format === format && depthTexture.type === type ) { + + return depthTextureGPU; + + } + + this.destroyTexture( depthTexture ); + + } + + depthTexture.name = 'depthBuffer'; + depthTexture.format = format; + depthTexture.type = type; + depthTexture.image.width = width; + depthTexture.image.height = height; + + this.createTexture( depthTexture, { width, height } ); + + return backend.get( depthTexture ).texture; + + } + + /** + * Uploads the updated texture data to the GPU. + * + * @param {Texture} texture - The texture. + * @param {Object} [options={}] - Optional configuration parameter. + */ + updateTexture( texture, options ) { + + const textureData = this.backend.get( texture ); + + const { textureDescriptorGPU } = textureData; + + if ( texture.isRenderTargetTexture || ( textureDescriptorGPU === undefined /* unsupported texture format */ ) ) + return; + + // transfer texture data + + if ( texture.isDataTexture ) { + + this._copyBufferToTexture( options.image, textureData.texture, textureDescriptorGPU, 0, texture.flipY ); + + } else if ( texture.isArrayTexture || texture.isDataArrayTexture || texture.isData3DTexture ) { + + for ( let i = 0; i < options.image.depth; i ++ ) { + + this._copyBufferToTexture( options.image, textureData.texture, textureDescriptorGPU, i, texture.flipY, i ); + + } + + } else if ( texture.isCompressedTexture || texture.isCompressedArrayTexture ) { + + this._copyCompressedBufferToTexture( texture.mipmaps, textureData.texture, textureDescriptorGPU ); + + } else if ( texture.isCubeTexture ) { + + this._copyCubeMapToTexture( options.images, textureData.texture, textureDescriptorGPU, texture.flipY, texture.premultiplyAlpha ); + + } else if ( texture.isVideoTexture ) { + + const video = texture.source.data; + + textureData.externalTexture = video; + + } else { + + this._copyImageToTexture( options.image, textureData.texture, textureDescriptorGPU, 0, texture.flipY, texture.premultiplyAlpha ); + + } + + // + + textureData.version = texture.version; + + if ( texture.onUpdate ) texture.onUpdate( texture ); + + } + + /** + * Returns texture data as a typed array. + * + * @async + * @param {Texture} texture - The texture to copy. + * @param {number} x - The x coordinate of the copy origin. + * @param {number} y - The y coordinate of the copy origin. + * @param {number} width - The width of the copy. + * @param {number} height - The height of the copy. + * @param {number} faceIndex - The face index. + * @return {Promise} A Promise that resolves with a typed array when the copy operation has finished. + */ + async copyTextureToBuffer( texture, x, y, width, height, faceIndex ) { + + const device = this.backend.device; + + const textureData = this.backend.get( texture ); + const textureGPU = textureData.texture; + const format = textureData.textureDescriptorGPU.format; + const bytesPerTexel = this._getBytesPerTexel( format ); + + let bytesPerRow = width * bytesPerTexel; + bytesPerRow = Math.ceil( bytesPerRow / 256 ) * 256; // Align to 256 bytes + + const readBuffer = device.createBuffer( + { + size: width * height * bytesPerTexel, + usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ + } + ); + + const encoder = device.createCommandEncoder(); + + encoder.copyTextureToBuffer( + { + texture: textureGPU, + origin: { x, y, z: faceIndex }, + }, + { + buffer: readBuffer, + bytesPerRow: bytesPerRow + }, + { + width: width, + height: height + } + + ); + + const typedArrayType = this._getTypedArrayType( format ); + + device.queue.submit( [ encoder.finish() ] ); + + await readBuffer.mapAsync( GPUMapMode.READ ); + + const buffer = readBuffer.getMappedRange(); + + return new typedArrayType( buffer ); + + } + + /** + * Returns the default GPU texture for the given format. + * + * @private + * @param {string} format - The GPU format. + * @return {GPUTexture} The GPU texture. + */ + _getDefaultTextureGPU( format ) { + + let defaultTexture = this.defaultTexture[ format ]; + + if ( defaultTexture === undefined ) { + + const texture = new Texture(); + texture.minFilter = NearestFilter; + texture.magFilter = NearestFilter; + + this.createTexture( texture, { width: 1, height: 1, format } ); + + this.defaultTexture[ format ] = defaultTexture = texture; + + } + + return this.backend.get( defaultTexture ).texture; + + } + + /** + * Returns the default GPU cube texture for the given format. + * + * @private + * @param {string} format - The GPU format. + * @return {GPUTexture} The GPU texture. + */ + _getDefaultCubeTextureGPU( format ) { + + let defaultCubeTexture = this.defaultTexture[ format ]; + + if ( defaultCubeTexture === undefined ) { + + const texture = new CubeTexture(); + texture.minFilter = NearestFilter; + texture.magFilter = NearestFilter; + + this.createTexture( texture, { width: 1, height: 1, depth: 6 } ); + + this.defaultCubeTexture[ format ] = defaultCubeTexture = texture; + + } + + return this.backend.get( defaultCubeTexture ).texture; + + } + + /** + * Returns the default video frame used as default data in context of video textures. + * + * @private + * @return {VideoFrame} The video frame. + */ + _getDefaultVideoFrame() { + + let defaultVideoFrame = this.defaultVideoFrame; + + if ( defaultVideoFrame === null ) { + + const init = { + timestamp: 0, + codedWidth: 1, + codedHeight: 1, + format: 'RGBA', + }; + + this.defaultVideoFrame = defaultVideoFrame = new VideoFrame( new Uint8Array( [ 0, 0, 0, 0xff ] ), init ); + + } + + return defaultVideoFrame; + + } + + /** + * Uploads cube texture image data to the GPU memory. + * + * @private + * @param {Array} images - The cube image data. + * @param {GPUTexture} textureGPU - The GPU texture. + * @param {Object} textureDescriptorGPU - The GPU texture descriptor. + * @param {boolean} flipY - Whether to flip texture data along their vertical axis or not. + * @param {boolean} premultiplyAlpha - Whether the texture should have its RGB channels premultiplied by the alpha channel or not. + */ + _copyCubeMapToTexture( images, textureGPU, textureDescriptorGPU, flipY, premultiplyAlpha ) { + + for ( let i = 0; i < 6; i ++ ) { + + const image = images[ i ]; + + const flipIndex = flipY === true ? _flipMap[ i ] : i; + + if ( image.isDataTexture ) { + + this._copyBufferToTexture( image.image, textureGPU, textureDescriptorGPU, flipIndex, flipY ); + + } else { + + this._copyImageToTexture( image, textureGPU, textureDescriptorGPU, flipIndex, flipY, premultiplyAlpha ); + + } + + } + + } + + /** + * Uploads texture image data to the GPU memory. + * + * @private + * @param {HTMLImageElement|ImageBitmap|HTMLCanvasElement} image - The image data. + * @param {GPUTexture} textureGPU - The GPU texture. + * @param {Object} textureDescriptorGPU - The GPU texture descriptor. + * @param {number} originDepth - The origin depth. + * @param {boolean} flipY - Whether to flip texture data along their vertical axis or not. + * @param {boolean} premultiplyAlpha - Whether the texture should have its RGB channels premultiplied by the alpha channel or not. + */ + _copyImageToTexture( image, textureGPU, textureDescriptorGPU, originDepth, flipY, premultiplyAlpha ) { + + const device = this.backend.device; + + device.queue.copyExternalImageToTexture( + { + source: image, + flipY: flipY + }, { + texture: textureGPU, + mipLevel: 0, + origin: { x: 0, y: 0, z: originDepth }, + premultipliedAlpha: premultiplyAlpha + }, { + width: image.width, + height: image.height, + depthOrArrayLayers: 1 + } + ); + + } + + /** + * Returns the pass utils singleton. + * + * @private + * @return {WebGPUTexturePassUtils} The utils instance. + */ + _getPassUtils() { + + let passUtils = this._passUtils; + + if ( passUtils === null ) { + + this._passUtils = passUtils = new WebGPUTexturePassUtils( this.backend.device ); + + } + + return passUtils; + + } + + /** + * Generates mipmaps for the given GPU texture. + * + * @private + * @param {GPUTexture} textureGPU - The GPU texture object. + * @param {Object} textureDescriptorGPU - The texture descriptor. + * @param {number} [baseArrayLayer=0] - The index of the first array layer accessible to the texture view. + */ + _generateMipmaps( textureGPU, textureDescriptorGPU, baseArrayLayer = 0 ) { + + this._getPassUtils().generateMipmaps( textureGPU, textureDescriptorGPU, baseArrayLayer ); + + } + + /** + * Flip the contents of the given GPU texture along its vertical axis. + * + * @private + * @param {GPUTexture} textureGPU - The GPU texture object. + * @param {Object} textureDescriptorGPU - The texture descriptor. + * @param {number} [originDepth=0] - The origin depth. + */ + _flipY( textureGPU, textureDescriptorGPU, originDepth = 0 ) { + + this._getPassUtils().flipY( textureGPU, textureDescriptorGPU, originDepth ); + + } + + /** + * Uploads texture buffer data to the GPU memory. + * + * @private + * @param {Object} image - An object defining the image buffer data. + * @param {GPUTexture} textureGPU - The GPU texture. + * @param {Object} textureDescriptorGPU - The GPU texture descriptor. + * @param {number} originDepth - The origin depth. + * @param {boolean} flipY - Whether to flip texture data along their vertical axis or not. + * @param {number} [depth=0] - TODO. + */ + _copyBufferToTexture( image, textureGPU, textureDescriptorGPU, originDepth, flipY, depth = 0 ) { + + // @TODO: Consider to use GPUCommandEncoder.copyBufferToTexture() + // @TODO: Consider to support valid buffer layouts with other formats like RGB + + const device = this.backend.device; + + const data = image.data; + + const bytesPerTexel = this._getBytesPerTexel( textureDescriptorGPU.format ); + const bytesPerRow = image.width * bytesPerTexel; + + device.queue.writeTexture( + { + texture: textureGPU, + mipLevel: 0, + origin: { x: 0, y: 0, z: originDepth } + }, + data, + { + offset: image.width * image.height * bytesPerTexel * depth, + bytesPerRow + }, + { + width: image.width, + height: image.height, + depthOrArrayLayers: 1 + } ); + + if ( flipY === true ) { + + this._flipY( textureGPU, textureDescriptorGPU, originDepth ); + + } + + } + + /** + * Uploads compressed texture data to the GPU memory. + * + * @private + * @param {Array} mipmaps - An array with mipmap data. + * @param {GPUTexture} textureGPU - The GPU texture. + * @param {Object} textureDescriptorGPU - The GPU texture descriptor. + */ + _copyCompressedBufferToTexture( mipmaps, textureGPU, textureDescriptorGPU ) { + + // @TODO: Consider to use GPUCommandEncoder.copyBufferToTexture() + + const device = this.backend.device; + + const blockData = this._getBlockData( textureDescriptorGPU.format ); + const isArrayTexture = textureDescriptorGPU.size.depthOrArrayLayers > 1; + + for ( let i = 0; i < mipmaps.length; i ++ ) { + + const mipmap = mipmaps[ i ]; + + const width = mipmap.width; + const height = mipmap.height; + const depth = isArrayTexture ? textureDescriptorGPU.size.depthOrArrayLayers : 1; + + const bytesPerRow = Math.ceil( width / blockData.width ) * blockData.byteLength; + const bytesPerImage = bytesPerRow * Math.ceil( height / blockData.height ); + + for ( let j = 0; j < depth; j ++ ) { + + device.queue.writeTexture( + { + texture: textureGPU, + mipLevel: i, + origin: { x: 0, y: 0, z: j } + }, + mipmap.data, + { + offset: j * bytesPerImage, + bytesPerRow, + rowsPerImage: Math.ceil( height / blockData.height ) + }, + { + width: Math.ceil( width / blockData.width ) * blockData.width, + height: Math.ceil( height / blockData.height ) * blockData.height, + depthOrArrayLayers: 1 + } + ); + + } + + } + + } + + /** + * This method is only relevant for compressed texture formats. It returns a block + * data descriptor for the given GPU compressed texture format. + * + * @private + * @param {string} format - The GPU compressed texture format. + * @return {Object} The block data descriptor. + */ + _getBlockData( format ) { + + if ( format === GPUTextureFormat.BC1RGBAUnorm || format === GPUTextureFormat.BC1RGBAUnormSRGB ) return { byteLength: 8, width: 4, height: 4 }; // DXT1 + if ( format === GPUTextureFormat.BC2RGBAUnorm || format === GPUTextureFormat.BC2RGBAUnormSRGB ) return { byteLength: 16, width: 4, height: 4 }; // DXT3 + if ( format === GPUTextureFormat.BC3RGBAUnorm || format === GPUTextureFormat.BC3RGBAUnormSRGB ) return { byteLength: 16, width: 4, height: 4 }; // DXT5 + if ( format === GPUTextureFormat.BC4RUnorm || format === GPUTextureFormat.BC4RSnorm ) return { byteLength: 8, width: 4, height: 4 }; // RGTC1 + if ( format === GPUTextureFormat.BC5RGUnorm || format === GPUTextureFormat.BC5RGSnorm ) return { byteLength: 16, width: 4, height: 4 }; // RGTC2 + if ( format === GPUTextureFormat.BC6HRGBUFloat || format === GPUTextureFormat.BC6HRGBFloat ) return { byteLength: 16, width: 4, height: 4 }; // BPTC (float) + if ( format === GPUTextureFormat.BC7RGBAUnorm || format === GPUTextureFormat.BC7RGBAUnormSRGB ) return { byteLength: 16, width: 4, height: 4 }; // BPTC (unorm) + + if ( format === GPUTextureFormat.ETC2RGB8Unorm || format === GPUTextureFormat.ETC2RGB8UnormSRGB ) return { byteLength: 8, width: 4, height: 4 }; + if ( format === GPUTextureFormat.ETC2RGB8A1Unorm || format === GPUTextureFormat.ETC2RGB8A1UnormSRGB ) return { byteLength: 8, width: 4, height: 4 }; + if ( format === GPUTextureFormat.ETC2RGBA8Unorm || format === GPUTextureFormat.ETC2RGBA8UnormSRGB ) return { byteLength: 16, width: 4, height: 4 }; + if ( format === GPUTextureFormat.EACR11Unorm ) return { byteLength: 8, width: 4, height: 4 }; + if ( format === GPUTextureFormat.EACR11Snorm ) return { byteLength: 8, width: 4, height: 4 }; + if ( format === GPUTextureFormat.EACRG11Unorm ) return { byteLength: 16, width: 4, height: 4 }; + if ( format === GPUTextureFormat.EACRG11Snorm ) return { byteLength: 16, width: 4, height: 4 }; + + if ( format === GPUTextureFormat.ASTC4x4Unorm || format === GPUTextureFormat.ASTC4x4UnormSRGB ) return { byteLength: 16, width: 4, height: 4 }; + if ( format === GPUTextureFormat.ASTC5x4Unorm || format === GPUTextureFormat.ASTC5x4UnormSRGB ) return { byteLength: 16, width: 5, height: 4 }; + if ( format === GPUTextureFormat.ASTC5x5Unorm || format === GPUTextureFormat.ASTC5x5UnormSRGB ) return { byteLength: 16, width: 5, height: 5 }; + if ( format === GPUTextureFormat.ASTC6x5Unorm || format === GPUTextureFormat.ASTC6x5UnormSRGB ) return { byteLength: 16, width: 6, height: 5 }; + if ( format === GPUTextureFormat.ASTC6x6Unorm || format === GPUTextureFormat.ASTC6x6UnormSRGB ) return { byteLength: 16, width: 6, height: 6 }; + if ( format === GPUTextureFormat.ASTC8x5Unorm || format === GPUTextureFormat.ASTC8x5UnormSRGB ) return { byteLength: 16, width: 8, height: 5 }; + if ( format === GPUTextureFormat.ASTC8x6Unorm || format === GPUTextureFormat.ASTC8x6UnormSRGB ) return { byteLength: 16, width: 8, height: 6 }; + if ( format === GPUTextureFormat.ASTC8x8Unorm || format === GPUTextureFormat.ASTC8x8UnormSRGB ) return { byteLength: 16, width: 8, height: 8 }; + if ( format === GPUTextureFormat.ASTC10x5Unorm || format === GPUTextureFormat.ASTC10x5UnormSRGB ) return { byteLength: 16, width: 10, height: 5 }; + if ( format === GPUTextureFormat.ASTC10x6Unorm || format === GPUTextureFormat.ASTC10x6UnormSRGB ) return { byteLength: 16, width: 10, height: 6 }; + if ( format === GPUTextureFormat.ASTC10x8Unorm || format === GPUTextureFormat.ASTC10x8UnormSRGB ) return { byteLength: 16, width: 10, height: 8 }; + if ( format === GPUTextureFormat.ASTC10x10Unorm || format === GPUTextureFormat.ASTC10x10UnormSRGB ) return { byteLength: 16, width: 10, height: 10 }; + if ( format === GPUTextureFormat.ASTC12x10Unorm || format === GPUTextureFormat.ASTC12x10UnormSRGB ) return { byteLength: 16, width: 12, height: 10 }; + if ( format === GPUTextureFormat.ASTC12x12Unorm || format === GPUTextureFormat.ASTC12x12UnormSRGB ) return { byteLength: 16, width: 12, height: 12 }; + + } + + /** + * Converts the three.js uv wrapping constants to GPU address mode constants. + * + * @private + * @param {number} value - The three.js constant defining a uv wrapping mode. + * @return {string} The GPU address mode. + */ + _convertAddressMode( value ) { + + let addressMode = GPUAddressMode.ClampToEdge; + + if ( value === RepeatWrapping ) { + + addressMode = GPUAddressMode.Repeat; + + } else if ( value === MirroredRepeatWrapping ) { + + addressMode = GPUAddressMode.MirrorRepeat; + + } + + return addressMode; + + } + + /** + * Converts the three.js filter constants to GPU filter constants. + * + * @private + * @param {number} value - The three.js constant defining a filter mode. + * @return {string} The GPU filter mode. + */ + _convertFilterMode( value ) { + + let filterMode = GPUFilterMode.Linear; + + if ( value === NearestFilter || value === NearestMipmapNearestFilter || value === NearestMipmapLinearFilter ) { + + filterMode = GPUFilterMode.Nearest; + + } + + return filterMode; + + } + + /** + * Returns the bytes-per-texel value for the given GPU texture format. + * + * @private + * @param {string} format - The GPU texture format. + * @return {number} The bytes-per-texel. + */ + _getBytesPerTexel( format ) { + + // 8-bit formats + if ( format === GPUTextureFormat.R8Unorm || + format === GPUTextureFormat.R8Snorm || + format === GPUTextureFormat.R8Uint || + format === GPUTextureFormat.R8Sint ) return 1; + + // 16-bit formats + if ( format === GPUTextureFormat.R16Uint || + format === GPUTextureFormat.R16Sint || + format === GPUTextureFormat.R16Float || + format === GPUTextureFormat.RG8Unorm || + format === GPUTextureFormat.RG8Snorm || + format === GPUTextureFormat.RG8Uint || + format === GPUTextureFormat.RG8Sint ) return 2; + + // 32-bit formats + if ( format === GPUTextureFormat.R32Uint || + format === GPUTextureFormat.R32Sint || + format === GPUTextureFormat.R32Float || + format === GPUTextureFormat.RG16Uint || + format === GPUTextureFormat.RG16Sint || + format === GPUTextureFormat.RG16Float || + format === GPUTextureFormat.RGBA8Unorm || + format === GPUTextureFormat.RGBA8UnormSRGB || + format === GPUTextureFormat.RGBA8Snorm || + format === GPUTextureFormat.RGBA8Uint || + format === GPUTextureFormat.RGBA8Sint || + format === GPUTextureFormat.BGRA8Unorm || + format === GPUTextureFormat.BGRA8UnormSRGB || + // Packed 32-bit formats + format === GPUTextureFormat.RGB9E5UFloat || + format === GPUTextureFormat.RGB10A2Unorm || + format === GPUTextureFormat.RG11B10UFloat || + format === GPUTextureFormat.Depth32Float || + format === GPUTextureFormat.Depth24Plus || + format === GPUTextureFormat.Depth24PlusStencil8 || + format === GPUTextureFormat.Depth32FloatStencil8 ) return 4; + + // 64-bit formats + if ( format === GPUTextureFormat.RG32Uint || + format === GPUTextureFormat.RG32Sint || + format === GPUTextureFormat.RG32Float || + format === GPUTextureFormat.RGBA16Uint || + format === GPUTextureFormat.RGBA16Sint || + format === GPUTextureFormat.RGBA16Float ) return 8; + + // 128-bit formats + if ( format === GPUTextureFormat.RGBA32Uint || + format === GPUTextureFormat.RGBA32Sint || + format === GPUTextureFormat.RGBA32Float ) return 16; + + + } + + /** + * Returns the corresponding typed array type for the given GPU texture format. + * + * @private + * @param {string} format - The GPU texture format. + * @return {TypedArray.constructor} The typed array type. + */ + _getTypedArrayType( format ) { + + if ( format === GPUTextureFormat.R8Uint ) return Uint8Array; + if ( format === GPUTextureFormat.R8Sint ) return Int8Array; + if ( format === GPUTextureFormat.R8Unorm ) return Uint8Array; + if ( format === GPUTextureFormat.R8Snorm ) return Int8Array; + if ( format === GPUTextureFormat.RG8Uint ) return Uint8Array; + if ( format === GPUTextureFormat.RG8Sint ) return Int8Array; + if ( format === GPUTextureFormat.RG8Unorm ) return Uint8Array; + if ( format === GPUTextureFormat.RG8Snorm ) return Int8Array; + if ( format === GPUTextureFormat.RGBA8Uint ) return Uint8Array; + if ( format === GPUTextureFormat.RGBA8Sint ) return Int8Array; + if ( format === GPUTextureFormat.RGBA8Unorm ) return Uint8Array; + if ( format === GPUTextureFormat.RGBA8Snorm ) return Int8Array; + + + if ( format === GPUTextureFormat.R16Uint ) return Uint16Array; + if ( format === GPUTextureFormat.R16Sint ) return Int16Array; + if ( format === GPUTextureFormat.RG16Uint ) return Uint16Array; + if ( format === GPUTextureFormat.RG16Sint ) return Int16Array; + if ( format === GPUTextureFormat.RGBA16Uint ) return Uint16Array; + if ( format === GPUTextureFormat.RGBA16Sint ) return Int16Array; + if ( format === GPUTextureFormat.R16Float ) return Uint16Array; + if ( format === GPUTextureFormat.RG16Float ) return Uint16Array; + if ( format === GPUTextureFormat.RGBA16Float ) return Uint16Array; + + + if ( format === GPUTextureFormat.R32Uint ) return Uint32Array; + if ( format === GPUTextureFormat.R32Sint ) return Int32Array; + if ( format === GPUTextureFormat.R32Float ) return Float32Array; + if ( format === GPUTextureFormat.RG32Uint ) return Uint32Array; + if ( format === GPUTextureFormat.RG32Sint ) return Int32Array; + if ( format === GPUTextureFormat.RG32Float ) return Float32Array; + if ( format === GPUTextureFormat.RGBA32Uint ) return Uint32Array; + if ( format === GPUTextureFormat.RGBA32Sint ) return Int32Array; + if ( format === GPUTextureFormat.RGBA32Float ) return Float32Array; + + if ( format === GPUTextureFormat.BGRA8Unorm ) return Uint8Array; + if ( format === GPUTextureFormat.BGRA8UnormSRGB ) return Uint8Array; + if ( format === GPUTextureFormat.RGB10A2Unorm ) return Uint32Array; + if ( format === GPUTextureFormat.RGB9E5UFloat ) return Uint32Array; + if ( format === GPUTextureFormat.RG11B10UFloat ) return Uint32Array; + + if ( format === GPUTextureFormat.Depth32Float ) return Float32Array; + if ( format === GPUTextureFormat.Depth24Plus ) return Uint32Array; + if ( format === GPUTextureFormat.Depth24PlusStencil8 ) return Uint32Array; + if ( format === GPUTextureFormat.Depth32FloatStencil8 ) return Float32Array; + + } + + /** + * Returns the GPU dimensions for the given texture. + * + * @private + * @param {Texture} texture - The texture. + * @return {string} The GPU dimension. + */ + _getDimension( texture ) { + + let dimension; + + if ( texture.is3DTexture || texture.isData3DTexture ) { + + dimension = GPUTextureDimension.ThreeD; + + } else { + + dimension = GPUTextureDimension.TwoD; + + } + + return dimension; + + } + +} + +/** + * Returns the GPU format for the given texture. + * + * @param {Texture} texture - The texture. + * @param {?GPUDevice} [device=null] - The GPU device which is used for feature detection. + * It is not necessary to apply the device for most formats. + * @return {string} The GPU format. + */ +function getFormat( texture, device = null ) { + + const format = texture.format; + const type = texture.type; + const colorSpace = texture.colorSpace; + const transfer = ColorManagement.getTransfer( colorSpace ); + + let formatGPU; + + if ( texture.isCompressedTexture === true || texture.isCompressedArrayTexture === true ) { + + switch ( format ) { + + case RGBA_S3TC_DXT1_Format: + formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.BC1RGBAUnormSRGB : GPUTextureFormat.BC1RGBAUnorm; + break; + + case RGBA_S3TC_DXT3_Format: + formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.BC2RGBAUnormSRGB : GPUTextureFormat.BC2RGBAUnorm; + break; + + case RGBA_S3TC_DXT5_Format: + formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.BC3RGBAUnormSRGB : GPUTextureFormat.BC3RGBAUnorm; + break; + + case RGB_ETC2_Format: + formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ETC2RGB8UnormSRGB : GPUTextureFormat.ETC2RGB8Unorm; + break; + + case RGBA_ETC2_EAC_Format: + formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ETC2RGBA8UnormSRGB : GPUTextureFormat.ETC2RGBA8Unorm; + break; + + case RGBA_ASTC_4x4_Format: + formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC4x4UnormSRGB : GPUTextureFormat.ASTC4x4Unorm; + break; + + case RGBA_ASTC_5x4_Format: + formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC5x4UnormSRGB : GPUTextureFormat.ASTC5x4Unorm; + break; + + case RGBA_ASTC_5x5_Format: + formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC5x5UnormSRGB : GPUTextureFormat.ASTC5x5Unorm; + break; + + case RGBA_ASTC_6x5_Format: + formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC6x5UnormSRGB : GPUTextureFormat.ASTC6x5Unorm; + break; + + case RGBA_ASTC_6x6_Format: + formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC6x6UnormSRGB : GPUTextureFormat.ASTC6x6Unorm; + break; + + case RGBA_ASTC_8x5_Format: + formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC8x5UnormSRGB : GPUTextureFormat.ASTC8x5Unorm; + break; + + case RGBA_ASTC_8x6_Format: + formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC8x6UnormSRGB : GPUTextureFormat.ASTC8x6Unorm; + break; + + case RGBA_ASTC_8x8_Format: + formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC8x8UnormSRGB : GPUTextureFormat.ASTC8x8Unorm; + break; + + case RGBA_ASTC_10x5_Format: + formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC10x5UnormSRGB : GPUTextureFormat.ASTC10x5Unorm; + break; + + case RGBA_ASTC_10x6_Format: + formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC10x6UnormSRGB : GPUTextureFormat.ASTC10x6Unorm; + break; + + case RGBA_ASTC_10x8_Format: + formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC10x8UnormSRGB : GPUTextureFormat.ASTC10x8Unorm; + break; + + case RGBA_ASTC_10x10_Format: + formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC10x10UnormSRGB : GPUTextureFormat.ASTC10x10Unorm; + break; + + case RGBA_ASTC_12x10_Format: + formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC12x10UnormSRGB : GPUTextureFormat.ASTC12x10Unorm; + break; + + case RGBA_ASTC_12x12_Format: + formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.ASTC12x12UnormSRGB : GPUTextureFormat.ASTC12x12Unorm; + break; + + case RGBAFormat: + formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.RGBA8UnormSRGB : GPUTextureFormat.RGBA8Unorm; + break; + + default: + console.error( 'WebGPURenderer: Unsupported texture format.', format ); + + } + + } else { + + switch ( format ) { + + case RGBAFormat: + + switch ( type ) { + + case ByteType: + formatGPU = GPUTextureFormat.RGBA8Snorm; + break; + + case ShortType: + formatGPU = GPUTextureFormat.RGBA16Sint; + break; + + case UnsignedShortType: + formatGPU = GPUTextureFormat.RGBA16Uint; + break; + case UnsignedIntType: + formatGPU = GPUTextureFormat.RGBA32Uint; + break; + + case IntType: + formatGPU = GPUTextureFormat.RGBA32Sint; + break; + + case UnsignedByteType: + formatGPU = ( transfer === SRGBTransfer ) ? GPUTextureFormat.RGBA8UnormSRGB : GPUTextureFormat.RGBA8Unorm; + break; + + case HalfFloatType: + formatGPU = GPUTextureFormat.RGBA16Float; + break; + + case FloatType: + formatGPU = GPUTextureFormat.RGBA32Float; + break; + + default: + console.error( 'WebGPURenderer: Unsupported texture type with RGBAFormat.', type ); + + } + + break; + + case RGBFormat: + + switch ( type ) { + + case UnsignedInt5999Type: + formatGPU = GPUTextureFormat.RGB9E5UFloat; + break; + + default: + console.error( 'WebGPURenderer: Unsupported texture type with RGBFormat.', type ); + + } + + break; + + case RedFormat: + + switch ( type ) { + + case ByteType: + formatGPU = GPUTextureFormat.R8Snorm; + break; + + case ShortType: + formatGPU = GPUTextureFormat.R16Sint; + break; + + case UnsignedShortType: + formatGPU = GPUTextureFormat.R16Uint; + break; + + case UnsignedIntType: + formatGPU = GPUTextureFormat.R32Uint; + break; + + case IntType: + formatGPU = GPUTextureFormat.R32Sint; + break; + + case UnsignedByteType: + formatGPU = GPUTextureFormat.R8Unorm; + break; + + case HalfFloatType: + formatGPU = GPUTextureFormat.R16Float; + break; + + case FloatType: + formatGPU = GPUTextureFormat.R32Float; + break; + + default: + console.error( 'WebGPURenderer: Unsupported texture type with RedFormat.', type ); + + } + + break; + + case RGFormat: + + switch ( type ) { + + case ByteType: + formatGPU = GPUTextureFormat.RG8Snorm; + break; + + case ShortType: + formatGPU = GPUTextureFormat.RG16Sint; + break; + + case UnsignedShortType: + formatGPU = GPUTextureFormat.RG16Uint; + break; + + case UnsignedIntType: + formatGPU = GPUTextureFormat.RG32Uint; + break; + + case IntType: + formatGPU = GPUTextureFormat.RG32Sint; + break; + + case UnsignedByteType: + formatGPU = GPUTextureFormat.RG8Unorm; + break; + + case HalfFloatType: + formatGPU = GPUTextureFormat.RG16Float; + break; + + case FloatType: + formatGPU = GPUTextureFormat.RG32Float; + break; + + default: + console.error( 'WebGPURenderer: Unsupported texture type with RGFormat.', type ); + + } + + break; + + case DepthFormat: + + switch ( type ) { + + case UnsignedShortType: + formatGPU = GPUTextureFormat.Depth16Unorm; + break; + + case UnsignedIntType: + formatGPU = GPUTextureFormat.Depth24Plus; + break; + + case FloatType: + formatGPU = GPUTextureFormat.Depth32Float; + break; + + default: + console.error( 'WebGPURenderer: Unsupported texture type with DepthFormat.', type ); + + } + + break; + + case DepthStencilFormat: + + switch ( type ) { + + case UnsignedInt248Type: + formatGPU = GPUTextureFormat.Depth24PlusStencil8; + break; + + case FloatType: + + if ( device && device.features.has( GPUFeatureName.Depth32FloatStencil8 ) === false ) { + + console.error( 'WebGPURenderer: Depth textures with DepthStencilFormat + FloatType can only be used with the "depth32float-stencil8" GPU feature.' ); + + } + + formatGPU = GPUTextureFormat.Depth32FloatStencil8; + + break; + + default: + console.error( 'WebGPURenderer: Unsupported texture type with DepthStencilFormat.', type ); + + } + + break; + + case RedIntegerFormat: + + switch ( type ) { + + case IntType: + formatGPU = GPUTextureFormat.R32Sint; + break; + + case UnsignedIntType: + formatGPU = GPUTextureFormat.R32Uint; + break; + + default: + console.error( 'WebGPURenderer: Unsupported texture type with RedIntegerFormat.', type ); + + } + + break; + + case RGIntegerFormat: + + switch ( type ) { + + case IntType: + formatGPU = GPUTextureFormat.RG32Sint; + break; + + case UnsignedIntType: + formatGPU = GPUTextureFormat.RG32Uint; + break; + + default: + console.error( 'WebGPURenderer: Unsupported texture type with RGIntegerFormat.', type ); + + } + + break; + + case RGBAIntegerFormat: + + switch ( type ) { + + case IntType: + formatGPU = GPUTextureFormat.RGBA32Sint; + break; + + case UnsignedIntType: + formatGPU = GPUTextureFormat.RGBA32Uint; + break; + + default: + console.error( 'WebGPURenderer: Unsupported texture type with RGBAIntegerFormat.', type ); + + } + + break; + + default: + console.error( 'WebGPURenderer: Unsupported texture format.', format ); + + } + + } + + return formatGPU; + +} + +const declarationRegexp = /^[fn]*\s*([a-z_0-9]+)?\s*\(([\s\S]*?)\)\s*[\-\>]*\s*([a-z_0-9]+(?:<[\s\S]+?>)?)/i; +const propertiesRegexp = /([a-z_0-9]+)\s*:\s*([a-z_0-9]+(?:<[\s\S]+?>)?)/ig; + +const wgslTypeLib$1 = { + 'f32': 'float', + 'i32': 'int', + 'u32': 'uint', + 'bool': 'bool', + + 'vec2': 'vec2', + 'vec2': 'ivec2', + 'vec2': 'uvec2', + 'vec2': 'bvec2', + + 'vec2f': 'vec2', + 'vec2i': 'ivec2', + 'vec2u': 'uvec2', + 'vec2b': 'bvec2', + + 'vec3': 'vec3', + 'vec3': 'ivec3', + 'vec3': 'uvec3', + 'vec3': 'bvec3', + + 'vec3f': 'vec3', + 'vec3i': 'ivec3', + 'vec3u': 'uvec3', + 'vec3b': 'bvec3', + + 'vec4': 'vec4', + 'vec4': 'ivec4', + 'vec4': 'uvec4', + 'vec4': 'bvec4', + + 'vec4f': 'vec4', + 'vec4i': 'ivec4', + 'vec4u': 'uvec4', + 'vec4b': 'bvec4', + + 'mat2x2': 'mat2', + 'mat2x2f': 'mat2', + + 'mat3x3': 'mat3', + 'mat3x3f': 'mat3', + + 'mat4x4': 'mat4', + 'mat4x4f': 'mat4', + + 'sampler': 'sampler', + + 'texture_1d': 'texture', + + 'texture_2d': 'texture', + 'texture_2d_array': 'texture', + 'texture_multisampled_2d': 'cubeTexture', + + 'texture_depth_2d': 'depthTexture', + 'texture_depth_2d_array': 'depthTexture', + 'texture_depth_multisampled_2d': 'depthTexture', + 'texture_depth_cube': 'depthTexture', + 'texture_depth_cube_array': 'depthTexture', + + 'texture_3d': 'texture3D', + + 'texture_cube': 'cubeTexture', + 'texture_cube_array': 'cubeTexture', + + 'texture_storage_1d': 'storageTexture', + 'texture_storage_2d': 'storageTexture', + 'texture_storage_2d_array': 'storageTexture', + 'texture_storage_3d': 'storageTexture' + +}; + +const parse = ( source ) => { + + source = source.trim(); + + const declaration = source.match( declarationRegexp ); + + if ( declaration !== null && declaration.length === 4 ) { + + const inputsCode = declaration[ 2 ]; + const propsMatches = []; + let match = null; + + while ( ( match = propertiesRegexp.exec( inputsCode ) ) !== null ) { + + propsMatches.push( { name: match[ 1 ], type: match[ 2 ] } ); + + } + + // Process matches to correctly pair names and types + const inputs = []; + for ( let i = 0; i < propsMatches.length; i ++ ) { + + const { name, type } = propsMatches[ i ]; + + let resolvedType = type; + + if ( resolvedType.startsWith( 'ptr' ) ) { + + resolvedType = 'pointer'; + + } else { + + if ( resolvedType.startsWith( 'texture' ) ) { + + resolvedType = type.split( '<' )[ 0 ]; + + } + + resolvedType = wgslTypeLib$1[ resolvedType ]; + + } + + inputs.push( new NodeFunctionInput( resolvedType, name ) ); + + } + + const blockCode = source.substring( declaration[ 0 ].length ); + const outputType = declaration[ 3 ] || 'void'; + + const name = declaration[ 1 ] !== undefined ? declaration[ 1 ] : ''; + const type = wgslTypeLib$1[ outputType ] || outputType; + + return { + type, + inputs, + name, + inputsCode, + blockCode, + outputType + }; + + } else { + + throw new Error( 'FunctionNode: Function is not a WGSL code.' ); + + } + +}; + +/** + * This class represents a WSL node function. + * + * @augments NodeFunction + */ +class WGSLNodeFunction extends NodeFunction { + + /** + * Constructs a new WGSL node function. + * + * @param {string} source - The WGSL source. + */ + constructor( source ) { + + const { type, inputs, name, inputsCode, blockCode, outputType } = parse( source ); + + super( type, inputs, name ); + + this.inputsCode = inputsCode; + this.blockCode = blockCode; + this.outputType = outputType; + + } + + /** + * This method returns the WGSL code of the node function. + * + * @param {string} [name=this.name] - The function's name. + * @return {string} The shader code. + */ + getCode( name = this.name ) { + + const outputType = this.outputType !== 'void' ? '-> ' + this.outputType : ''; + + return `fn ${ name } ( ${ this.inputsCode.trim() } ) ${ outputType }` + this.blockCode; + + } + +} + +/** + * A WGSL node parser. + * + * @augments NodeParser + */ +class WGSLNodeParser extends NodeParser { + + /** + * The method parses the given WGSL code an returns a node function. + * + * @param {string} source - The WGSL code. + * @return {WGSLNodeFunction} A node function. + */ + parseFunction( source ) { + + return new WGSLNodeFunction( source ); + + } + +} + +// GPUShaderStage is not defined in browsers not supporting WebGPU +const GPUShaderStage = ( typeof self !== 'undefined' ) ? self.GPUShaderStage : { VERTEX: 1, FRAGMENT: 2, COMPUTE: 4 }; + +const accessNames = { + [ NodeAccess.READ_ONLY ]: 'read', + [ NodeAccess.WRITE_ONLY ]: 'write', + [ NodeAccess.READ_WRITE ]: 'read_write' +}; + +const wrapNames = { + [ RepeatWrapping ]: 'repeat', + [ ClampToEdgeWrapping ]: 'clamp', + [ MirroredRepeatWrapping ]: 'mirror' +}; + +const gpuShaderStageLib = { + 'vertex': GPUShaderStage ? GPUShaderStage.VERTEX : 1, + 'fragment': GPUShaderStage ? GPUShaderStage.FRAGMENT : 2, + 'compute': GPUShaderStage ? GPUShaderStage.COMPUTE : 4 +}; + +const supports = { + instance: true, + swizzleAssign: false, + storageBuffer: true +}; + +const wgslFnOpLib = { + '^^': 'tsl_xor' +}; + +const wgslTypeLib = { + float: 'f32', + int: 'i32', + uint: 'u32', + bool: 'bool', + color: 'vec3', + + vec2: 'vec2', + ivec2: 'vec2', + uvec2: 'vec2', + bvec2: 'vec2', + + vec3: 'vec3', + ivec3: 'vec3', + uvec3: 'vec3', + bvec3: 'vec3', + + vec4: 'vec4', + ivec4: 'vec4', + uvec4: 'vec4', + bvec4: 'vec4', + + mat2: 'mat2x2', + mat3: 'mat3x3', + mat4: 'mat4x4' +}; + +const wgslCodeCache = {}; + +const wgslPolyfill = { + tsl_xor: new CodeNode( 'fn tsl_xor( a : bool, b : bool ) -> bool { return ( a || b ) && !( a && b ); }' ), + mod_float: new CodeNode( 'fn tsl_mod_float( x : f32, y : f32 ) -> f32 { return x - y * floor( x / y ); }' ), + mod_vec2: new CodeNode( 'fn tsl_mod_vec2( x : vec2f, y : vec2f ) -> vec2f { return x - y * floor( x / y ); }' ), + mod_vec3: new CodeNode( 'fn tsl_mod_vec3( x : vec3f, y : vec3f ) -> vec3f { return x - y * floor( x / y ); }' ), + mod_vec4: new CodeNode( 'fn tsl_mod_vec4( x : vec4f, y : vec4f ) -> vec4f { return x - y * floor( x / y ); }' ), + equals_bool: new CodeNode( 'fn tsl_equals_bool( a : bool, b : bool ) -> bool { return a == b; }' ), + equals_bvec2: new CodeNode( 'fn tsl_equals_bvec2( a : vec2f, b : vec2f ) -> vec2 { return vec2( a.x == b.x, a.y == b.y ); }' ), + equals_bvec3: new CodeNode( 'fn tsl_equals_bvec3( a : vec3f, b : vec3f ) -> vec3 { return vec3( a.x == b.x, a.y == b.y, a.z == b.z ); }' ), + equals_bvec4: new CodeNode( 'fn tsl_equals_bvec4( a : vec4f, b : vec4f ) -> vec4 { return vec4( a.x == b.x, a.y == b.y, a.z == b.z, a.w == b.w ); }' ), + repeatWrapping_float: new CodeNode( 'fn tsl_repeatWrapping_float( coord: f32 ) -> f32 { return fract( coord ); }' ), + mirrorWrapping_float: new CodeNode( 'fn tsl_mirrorWrapping_float( coord: f32 ) -> f32 { let mirrored = fract( coord * 0.5 ) * 2.0; return 1.0 - abs( 1.0 - mirrored ); }' ), + clampWrapping_float: new CodeNode( 'fn tsl_clampWrapping_float( coord: f32 ) -> f32 { return clamp( coord, 0.0, 1.0 ); }' ), + biquadraticTexture: new CodeNode( /* wgsl */` +fn tsl_biquadraticTexture( map : texture_2d, coord : vec2f, iRes : vec2u, level : u32 ) -> vec4f { + + let res = vec2f( iRes ); + + let uvScaled = coord * res; + let uvWrapping = ( ( uvScaled % res ) + res ) % res; + + // https://www.shadertoy.com/view/WtyXRy + + let uv = uvWrapping - 0.5; + let iuv = floor( uv ); + let f = fract( uv ); + + let rg1 = textureLoad( map, vec2u( iuv + vec2( 0.5, 0.5 ) ) % iRes, level ); + let rg2 = textureLoad( map, vec2u( iuv + vec2( 1.5, 0.5 ) ) % iRes, level ); + let rg3 = textureLoad( map, vec2u( iuv + vec2( 0.5, 1.5 ) ) % iRes, level ); + let rg4 = textureLoad( map, vec2u( iuv + vec2( 1.5, 1.5 ) ) % iRes, level ); + + return mix( mix( rg1, rg2, f.x ), mix( rg3, rg4, f.x ), f.y ); + +} +` ) +}; + +const wgslMethods = { + dFdx: 'dpdx', + dFdy: '- dpdy', + mod_float: 'tsl_mod_float', + mod_vec2: 'tsl_mod_vec2', + mod_vec3: 'tsl_mod_vec3', + mod_vec4: 'tsl_mod_vec4', + equals_bool: 'tsl_equals_bool', + equals_bvec2: 'tsl_equals_bvec2', + equals_bvec3: 'tsl_equals_bvec3', + equals_bvec4: 'tsl_equals_bvec4', + inversesqrt: 'inverseSqrt', + bitcast: 'bitcast' +}; + +// WebGPU issue: does not support pow() with negative base on Windows + +if ( typeof navigator !== 'undefined' && /Windows/g.test( navigator.userAgent ) ) { + + wgslPolyfill.pow_float = new CodeNode( 'fn tsl_pow_float( a : f32, b : f32 ) -> f32 { return select( -pow( -a, b ), pow( a, b ), a > 0.0 ); }' ); + wgslPolyfill.pow_vec2 = new CodeNode( 'fn tsl_pow_vec2( a : vec2f, b : vec2f ) -> vec2f { return vec2f( tsl_pow_float( a.x, b.x ), tsl_pow_float( a.y, b.y ) ); }', [ wgslPolyfill.pow_float ] ); + wgslPolyfill.pow_vec3 = new CodeNode( 'fn tsl_pow_vec3( a : vec3f, b : vec3f ) -> vec3f { return vec3f( tsl_pow_float( a.x, b.x ), tsl_pow_float( a.y, b.y ), tsl_pow_float( a.z, b.z ) ); }', [ wgslPolyfill.pow_float ] ); + wgslPolyfill.pow_vec4 = new CodeNode( 'fn tsl_pow_vec4( a : vec4f, b : vec4f ) -> vec4f { return vec4f( tsl_pow_float( a.x, b.x ), tsl_pow_float( a.y, b.y ), tsl_pow_float( a.z, b.z ), tsl_pow_float( a.w, b.w ) ); }', [ wgslPolyfill.pow_float ] ); + + wgslMethods.pow_float = 'tsl_pow_float'; + wgslMethods.pow_vec2 = 'tsl_pow_vec2'; + wgslMethods.pow_vec3 = 'tsl_pow_vec3'; + wgslMethods.pow_vec4 = 'tsl_pow_vec4'; + +} + +// + +let diagnostics = ''; + +if ( ( typeof navigator !== 'undefined' && /Firefox|Deno/g.test( navigator.userAgent ) ) !== true ) { + + diagnostics += 'diagnostic( off, derivative_uniformity );\n'; + +} + +/** + * A node builder targeting WGSL. + * + * This module generates WGSL shader code from node materials and also + * generates the respective bindings and vertex buffer definitions. These + * data are later used by the renderer to create render and compute pipelines + * for render objects. + * + * @augments NodeBuilder + */ +class WGSLNodeBuilder extends NodeBuilder { + + /** + * Constructs a new WGSL node builder renderer. + * + * @param {Object3D} object - The 3D object. + * @param {Renderer} renderer - The renderer. + */ + constructor( object, renderer ) { + + super( object, renderer, new WGSLNodeParser() ); + + /** + * A dictionary that holds for each shader stage ('vertex', 'fragment', 'compute') + * another dictionary which manages UBOs per group ('render','frame','object'). + * + * @type {Object>} + */ + this.uniformGroups = {}; + + /** + * A dictionary that holds for each shader stage a Map of builtins. + * + * @type {Object>} + */ + this.builtins = {}; + + /** + * A dictionary that holds for each shader stage a Set of directives. + * + * @type {Object>} + */ + this.directives = {}; + + /** + * A map for managing scope arrays. Only relevant for when using + * {@link WorkgroupInfoNode} in context of compute shaders. + * + * @type {Map} + */ + this.scopedArrays = new Map(); + + } + + /** + * Checks if the given texture requires a manual conversion to the working color space. + * + * @param {Texture} texture - The texture to check. + * @return {boolean} Whether the given texture requires a conversion to working color space or not. + */ + needsToWorkingColorSpace( texture ) { + + return texture.isVideoTexture === true && texture.colorSpace !== NoColorSpace; + + } + + /** + * Generates the WGSL snippet for sampled textures. + * + * @private + * @param {Texture} texture - The texture. + * @param {string} textureProperty - The name of the texture uniform in the shader. + * @param {string} uvSnippet - A WGSL snippet that represents texture coordinates used for sampling. + * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample. + * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for. + * @return {string} The WGSL snippet. + */ + _generateTextureSample( texture, textureProperty, uvSnippet, depthSnippet, shaderStage = this.shaderStage ) { + + if ( shaderStage === 'fragment' ) { + + if ( depthSnippet ) { + + return `textureSample( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ depthSnippet } )`; + + } else { + + return `textureSample( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet } )`; + + } + + } else { + + return this._generateTextureSampleLevel( texture, textureProperty, uvSnippet, '0', depthSnippet ); + + } + + } + + /** + * Generates the WGSL snippet when sampling video textures. + * + * @private + * @param {string} textureProperty - The name of the video texture uniform in the shader. + * @param {string} uvSnippet - A WGSL snippet that represents texture coordinates used for sampling. + * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for. + * @return {string} The WGSL snippet. + */ + _generateVideoSample( textureProperty, uvSnippet, shaderStage = this.shaderStage ) { + + if ( shaderStage === 'fragment' ) { + + return `textureSampleBaseClampToEdge( ${ textureProperty }, ${ textureProperty }_sampler, vec2( ${ uvSnippet }.x, 1.0 - ${ uvSnippet }.y ) )`; + + } else { + + console.error( `WebGPURenderer: THREE.VideoTexture does not support ${ shaderStage } shader.` ); + + } + + } + + /** + * Generates the WGSL snippet when sampling textures with explicit mip level. + * + * @private + * @param {Texture} texture - The texture. + * @param {string} textureProperty - The name of the texture uniform in the shader. + * @param {string} uvSnippet - A WGSL snippet that represents texture coordinates used for sampling. + * @param {string} levelSnippet - A WGSL snippet that represents the mip level, with level 0 containing a full size version of the texture. + * @param {string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample. + * @return {string} The WGSL snippet. + */ + _generateTextureSampleLevel( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet ) { + + if ( this.isUnfilterable( texture ) === false ) { + + return `textureSampleLevel( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ levelSnippet } )`; + + } else if ( this.isFilteredTexture( texture ) ) { + + return this.generateFilteredTexture( texture, textureProperty, uvSnippet, levelSnippet ); + + } else { + + return this.generateTextureLod( texture, textureProperty, uvSnippet, depthSnippet, levelSnippet ); + + } + + } + + /** + * Generates a wrap function used in context of textures. + * + * @param {Texture} texture - The texture to generate the function for. + * @return {string} The name of the generated function. + */ + generateWrapFunction( texture ) { + + const functionName = `tsl_coord_${ wrapNames[ texture.wrapS ] }S_${ wrapNames[ texture.wrapT ] }_${ texture.isData3DTexture ? '3d' : '2d' }T`; + + let nodeCode = wgslCodeCache[ functionName ]; + + if ( nodeCode === undefined ) { + + const includes = []; + + // For 3D textures, use vec3f; for texture arrays, keep vec2f since array index is separate + const coordType = texture.isData3DTexture ? 'vec3f' : 'vec2f'; + let code = `fn ${ functionName }( coord : ${ coordType } ) -> ${ coordType } {\n\n\treturn ${ coordType }(\n`; + + const addWrapSnippet = ( wrap, axis ) => { + + if ( wrap === RepeatWrapping ) { + + includes.push( wgslPolyfill.repeatWrapping_float ); + + code += `\t\ttsl_repeatWrapping_float( coord.${ axis } )`; + + } else if ( wrap === ClampToEdgeWrapping ) { + + includes.push( wgslPolyfill.clampWrapping_float ); + + code += `\t\ttsl_clampWrapping_float( coord.${ axis } )`; + + } else if ( wrap === MirroredRepeatWrapping ) { + + includes.push( wgslPolyfill.mirrorWrapping_float ); + + code += `\t\ttsl_mirrorWrapping_float( coord.${ axis } )`; + + } else { + + code += `\t\tcoord.${ axis }`; + + console.warn( `WebGPURenderer: Unsupported texture wrap type "${ wrap }" for vertex shader.` ); + + } + + }; + + addWrapSnippet( texture.wrapS, 'x' ); + + code += ',\n'; + + addWrapSnippet( texture.wrapT, 'y' ); + + if ( texture.isData3DTexture ) { + + code += ',\n'; + addWrapSnippet( texture.wrapR, 'z' ); + + } + + code += '\n\t);\n\n}\n'; + + wgslCodeCache[ functionName ] = nodeCode = new CodeNode( code, includes ); + + } + + nodeCode.build( this ); + + return functionName; + + } + + /** + * Generates the array declaration string. + * + * @param {string} type - The type. + * @param {?number} [count] - The count. + * @return {string} The generated value as a shader string. + */ + generateArrayDeclaration( type, count ) { + + return `array< ${ this.getType( type ) }, ${ count } >`; + + } + + /** + * Generates a WGSL variable that holds the texture dimension of the given texture. + * It also returns information about the number of layers (elements) of an arrayed + * texture as well as the cube face count of cube textures. + * + * @param {Texture} texture - The texture to generate the function for. + * @param {string} textureProperty - The name of the video texture uniform in the shader. + * @param {string} levelSnippet - A WGSL snippet that represents the mip level, with level 0 containing a full size version of the texture. + * @return {string} The name of the dimension variable. + */ + generateTextureDimension( texture, textureProperty, levelSnippet ) { + + const textureData = this.getDataFromNode( texture, this.shaderStage, this.globalCache ); + + if ( textureData.dimensionsSnippet === undefined ) textureData.dimensionsSnippet = {}; + + let textureDimensionNode = textureData.dimensionsSnippet[ levelSnippet ]; + + if ( textureData.dimensionsSnippet[ levelSnippet ] === undefined ) { + + let textureDimensionsParams; + let dimensionType; + + const { primarySamples } = this.renderer.backend.utils.getTextureSampleData( texture ); + const isMultisampled = primarySamples > 1; + + if ( texture.isData3DTexture ) { + + dimensionType = 'vec3'; + + } else { + + // Regular 2D textures, depth textures, etc. + dimensionType = 'vec2'; + + } + + // Build parameters string based on texture type and multisampling + if ( isMultisampled || texture.isVideoTexture || texture.isStorageTexture ) { + + textureDimensionsParams = textureProperty; + + } else { + + textureDimensionsParams = `${textureProperty}${levelSnippet ? `, u32( ${ levelSnippet } )` : ''}`; + + } + + textureDimensionNode = new VarNode( new ExpressionNode( `textureDimensions( ${ textureDimensionsParams } )`, dimensionType ) ); + + textureData.dimensionsSnippet[ levelSnippet ] = textureDimensionNode; + + if ( texture.isArrayTexture || texture.isDataArrayTexture || texture.isData3DTexture ) { + + textureData.arrayLayerCount = new VarNode( + new ExpressionNode( + `textureNumLayers(${textureProperty})`, + 'u32' + ) + ); + + } + + // For cube textures, we know it's always 6 faces + if ( texture.isTextureCube ) { + + textureData.cubeFaceCount = new VarNode( + new ExpressionNode( '6u', 'u32' ) + ); + + } + + } + + return textureDimensionNode.build( this ); + + } + + /** + * Generates the WGSL snippet for a manual filtered texture. + * + * @param {Texture} texture - The texture. + * @param {string} textureProperty - The name of the texture uniform in the shader. + * @param {string} uvSnippet - A WGSL snippet that represents texture coordinates used for sampling. + * @param {string} levelSnippet - A WGSL snippet that represents the mip level, with level 0 containing a full size version of the texture. + * @return {string} The WGSL snippet. + */ + generateFilteredTexture( texture, textureProperty, uvSnippet, levelSnippet = '0u' ) { + + this._include( 'biquadraticTexture' ); + + const wrapFunction = this.generateWrapFunction( texture ); + const textureDimension = this.generateTextureDimension( texture, textureProperty, levelSnippet ); + + return `tsl_biquadraticTexture( ${ textureProperty }, ${ wrapFunction }( ${ uvSnippet } ), ${ textureDimension }, u32( ${ levelSnippet } ) )`; + + } + + /** + * Generates the WGSL snippet for a texture lookup with explicit level-of-detail. + * Since it's a lookup, no sampling or filtering is applied. + * + * @param {Texture} texture - The texture. + * @param {string} textureProperty - The name of the texture uniform in the shader. + * @param {string} uvSnippet - A WGSL snippet that represents texture coordinates used for sampling. + * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample. + * @param {string} [levelSnippet='0u'] - A WGSL snippet that represents the mip level, with level 0 containing a full size version of the texture. + * @return {string} The WGSL snippet. + */ + generateTextureLod( texture, textureProperty, uvSnippet, depthSnippet, levelSnippet = '0u' ) { + + const wrapFunction = this.generateWrapFunction( texture ); + const textureDimension = this.generateTextureDimension( texture, textureProperty, levelSnippet ); + + const vecType = texture.isData3DTexture ? 'vec3' : 'vec2'; + const coordSnippet = `${ vecType }( ${ wrapFunction }( ${ uvSnippet } ) * ${ vecType }( ${ textureDimension } ) )`; + + return this.generateTextureLoad( texture, textureProperty, coordSnippet, depthSnippet, levelSnippet ); + + } + + /** + * Generates the WGSL snippet that reads a single texel from a texture without sampling or filtering. + * + * @param {Texture} texture - The texture. + * @param {string} textureProperty - The name of the texture uniform in the shader. + * @param {string} uvIndexSnippet - A WGSL snippet that represents texture coordinates used for sampling. + * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample. + * @param {string} [levelSnippet='0u'] - A WGSL snippet that represents the mip level, with level 0 containing a full size version of the texture. + * @return {string} The WGSL snippet. + */ + generateTextureLoad( texture, textureProperty, uvIndexSnippet, depthSnippet, levelSnippet = '0u' ) { + + let snippet; + + if ( texture.isVideoTexture === true ) { + + snippet = `textureLoad( ${ textureProperty }, ${ uvIndexSnippet } )`; + + } else if ( depthSnippet ) { + + snippet = `textureLoad( ${ textureProperty }, ${ uvIndexSnippet }, ${ depthSnippet }, u32( ${ levelSnippet } ) )`; + + } else { + + snippet = `textureLoad( ${ textureProperty }, ${ uvIndexSnippet }, u32( ${ levelSnippet } ) )`; + + if ( this.renderer.backend.compatibilityMode && texture.isDepthTexture ) { + + snippet += '.x'; + + } + + } + + return snippet; + + } + + /** + * Generates the WGSL snippet that writes a single texel to a texture. + * + * @param {Texture} texture - The texture. + * @param {string} textureProperty - The name of the texture uniform in the shader. + * @param {string} uvIndexSnippet - A WGSL snippet that represents texture coordinates used for sampling. + * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample. + * @param {string} valueSnippet - A WGSL snippet that represent the new texel value. + * @return {string} The WGSL snippet. + */ + generateTextureStore( texture, textureProperty, uvIndexSnippet, depthSnippet, valueSnippet ) { + + let snippet; + + if ( depthSnippet ) { + + snippet = `textureStore( ${ textureProperty }, ${ uvIndexSnippet }, ${ depthSnippet }, ${ valueSnippet } )`; + + } else { + + snippet = `textureStore( ${ textureProperty }, ${ uvIndexSnippet }, ${ valueSnippet } )`; + + } + + return snippet; + + } + + /** + * Returns `true` if the sampled values of the given texture should be compared against a reference value. + * + * @param {Texture} texture - The texture. + * @return {boolean} Whether the sampled values of the given texture should be compared against a reference value or not. + */ + isSampleCompare( texture ) { + + return texture.isDepthTexture === true && texture.compareFunction !== null; + + } + + /** + * Returns `true` if the given texture is unfilterable. + * + * @param {Texture} texture - The texture. + * @return {boolean} Whether the given texture is unfilterable or not. + */ + isUnfilterable( texture ) { + + return this.getComponentTypeFromTexture( texture ) !== 'float' || + ( ! this.isAvailable( 'float32Filterable' ) && texture.isDataTexture === true && texture.type === FloatType ) || + ( this.isSampleCompare( texture ) === false && texture.minFilter === NearestFilter && texture.magFilter === NearestFilter ) || + this.renderer.backend.utils.getTextureSampleData( texture ).primarySamples > 1; + + } + + /** + * Generates the WGSL snippet for sampling/loading the given texture. + * + * @param {Texture} texture - The texture. + * @param {string} textureProperty - The name of the texture uniform in the shader. + * @param {string} uvSnippet - A WGSL snippet that represents texture coordinates used for sampling. + * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample. + * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for. + * @return {string} The WGSL snippet. + */ + generateTexture( texture, textureProperty, uvSnippet, depthSnippet, shaderStage = this.shaderStage ) { + + let snippet = null; + + if ( texture.isVideoTexture === true ) { + + snippet = this._generateVideoSample( textureProperty, uvSnippet, shaderStage ); + + } else if ( this.isUnfilterable( texture ) ) { + + snippet = this.generateTextureLod( texture, textureProperty, uvSnippet, depthSnippet, '0', shaderStage ); + + } else { + + snippet = this._generateTextureSample( texture, textureProperty, uvSnippet, depthSnippet, shaderStage ); + + } + + return snippet; + + } + + /** + * Generates the WGSL snippet for sampling/loading the given texture using explicit gradients. + * + * @param {Texture} texture - The texture. + * @param {string} textureProperty - The name of the texture uniform in the shader. + * @param {string} uvSnippet - A WGSL snippet that represents texture coordinates used for sampling. + * @param {Array} gradSnippet - An array holding both gradient WGSL snippets. + * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample. + * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for. + * @return {string} The WGSL snippet. + */ + generateTextureGrad( texture, textureProperty, uvSnippet, gradSnippet, depthSnippet, shaderStage = this.shaderStage ) { + + if ( shaderStage === 'fragment' ) { + + // TODO handle i32 or u32 --> uvSnippet, array_index: A, ddx, ddy + return `textureSampleGrad( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ gradSnippet[ 0 ] }, ${ gradSnippet[ 1 ] } )`; + + } else { + + console.error( `WebGPURenderer: THREE.TextureNode.gradient() does not support ${ shaderStage } shader.` ); + + } + + } + + /** + * Generates the WGSL snippet for sampling a depth texture and comparing the sampled depth values + * against a reference value. + * + * @param {Texture} texture - The texture. + * @param {string} textureProperty - The name of the texture uniform in the shader. + * @param {string} uvSnippet - A WGSL snippet that represents texture coordinates used for sampling. + * @param {string} compareSnippet - A WGSL snippet that represents the reference value. + * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample. + * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for. + * @return {string} The WGSL snippet. + */ + generateTextureCompare( texture, textureProperty, uvSnippet, compareSnippet, depthSnippet, shaderStage = this.shaderStage ) { + + if ( shaderStage === 'fragment' ) { + + if ( texture.isDepthTexture === true && texture.isArrayTexture === true ) { + + return `textureSampleCompare( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ depthSnippet }, ${ compareSnippet } )`; + + } + + return `textureSampleCompare( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ compareSnippet } )`; + + } else { + + console.error( `WebGPURenderer: THREE.DepthTexture.compareFunction() does not support ${ shaderStage } shader.` ); + + } + + } + + /** + * Generates the WGSL snippet when sampling textures with explicit mip level. + * + * @param {Texture} texture - The texture. + * @param {string} textureProperty - The name of the texture uniform in the shader. + * @param {string} uvSnippet - A WGSL snippet that represents texture coordinates used for sampling. + * @param {string} levelSnippet - A WGSL snippet that represents the mip level, with level 0 containing a full size version of the texture. + * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample. + * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for. + * @return {string} The WGSL snippet. + */ + generateTextureLevel( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet, shaderStage = this.shaderStage ) { + + let snippet = null; + + if ( texture.isVideoTexture === true ) { + + snippet = this._generateVideoSample( textureProperty, uvSnippet, shaderStage ); + + } else { + + snippet = this._generateTextureSampleLevel( texture, textureProperty, uvSnippet, levelSnippet, depthSnippet ); + + } + + return snippet; + + } + + /** + * Generates the WGSL snippet when sampling textures with a bias to the mip level. + * + * @param {Texture} texture - The texture. + * @param {string} textureProperty - The name of the texture uniform in the shader. + * @param {string} uvSnippet - A WGSL snippet that represents texture coordinates used for sampling. + * @param {string} biasSnippet - A WGSL snippet that represents the bias to apply to the mip level before sampling. + * @param {?string} depthSnippet - A WGSL snippet that represents 0-based texture array index to sample. + * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for. + * @return {string} The WGSL snippet. + */ + generateTextureBias( texture, textureProperty, uvSnippet, biasSnippet, depthSnippet, shaderStage = this.shaderStage ) { + + if ( shaderStage === 'fragment' ) { + + return `textureSampleBias( ${ textureProperty }, ${ textureProperty }_sampler, ${ uvSnippet }, ${ biasSnippet } )`; + + } else { + + console.error( `WebGPURenderer: THREE.TextureNode.biasNode does not support ${ shaderStage } shader.` ); + + } + + } + + /** + * Returns a WGSL snippet that represents the property name of the given node. + * + * @param {Node} node - The node. + * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for. + * @return {string} The property name. + */ + getPropertyName( node, shaderStage = this.shaderStage ) { + + if ( node.isNodeVarying === true && node.needsInterpolation === true ) { + + if ( shaderStage === 'vertex' ) { + + return `varyings.${ node.name }`; + + } + + } else if ( node.isNodeUniform === true ) { + + const name = node.name; + const type = node.type; + + if ( type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' || type === 'texture3D' ) { + + return name; + + } else if ( type === 'buffer' || type === 'storageBuffer' || type === 'indirectStorageBuffer' ) { + + if ( this.isCustomStruct( node ) ) { + + return name; + + } + + return name + '.value'; + + } else { + + return node.groupNode.name + '.' + name; + + } + + } + + return super.getPropertyName( node ); + + } + + /** + * Returns the output struct name. + * + * @return {string} The name of the output struct. + */ + getOutputStructName() { + + return 'output'; + + } + + /** + * Returns the native shader operator name for a given generic name. + * + * @param {string} op - The operator name to resolve. + * @return {?string} The resolved operator name. + */ + getFunctionOperator( op ) { + + const fnOp = wgslFnOpLib[ op ]; + + if ( fnOp !== undefined ) { + + this._include( fnOp ); + + return fnOp; + + } + + return null; + + } + + /** + * Returns the node access for the given node and shader stage. + * + * @param {StorageTextureNode|StorageBufferNode} node - The storage node. + * @param {string} shaderStage - The shader stage. + * @return {string} The node access. + */ + getNodeAccess( node, shaderStage ) { + + if ( shaderStage !== 'compute' ) + return NodeAccess.READ_ONLY; + + return node.access; + + } + + /** + * Returns A WGSL snippet representing the storage access. + * + * @param {StorageTextureNode|StorageBufferNode} node - The storage node. + * @param {string} shaderStage - The shader stage. + * @return {string} The WGSL snippet representing the storage access. + */ + getStorageAccess( node, shaderStage ) { + + return accessNames[ this.getNodeAccess( node, shaderStage ) ]; + + } + + /** + * This method is one of the more important ones since it's responsible + * for generating a matching binding instance for the given uniform node. + * + * These bindings are later used in the renderer to create bind groups + * and layouts. + * + * @param {UniformNode} node - The uniform node. + * @param {string} type - The node data type. + * @param {string} shaderStage - The shader stage. + * @param {?string} [name=null] - An optional uniform name. + * @return {NodeUniform} The node uniform object. + */ + getUniformFromNode( node, type, shaderStage, name = null ) { + + const uniformNode = super.getUniformFromNode( node, type, shaderStage, name ); + const nodeData = this.getDataFromNode( node, shaderStage, this.globalCache ); + + if ( nodeData.uniformGPU === undefined ) { + + let uniformGPU; + + const group = node.groupNode; + const groupName = group.name; + + const bindings = this.getBindGroupArray( groupName, shaderStage ); + + if ( type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' || type === 'texture3D' ) { + + let texture = null; + + const access = this.getNodeAccess( node, shaderStage ); + + if ( type === 'texture' || type === 'storageTexture' ) { + + texture = new NodeSampledTexture( uniformNode.name, uniformNode.node, group, access ); + + } else if ( type === 'cubeTexture' ) { + + texture = new NodeSampledCubeTexture( uniformNode.name, uniformNode.node, group, access ); + + } else if ( type === 'texture3D' ) { + + texture = new NodeSampledTexture3D( uniformNode.name, uniformNode.node, group, access ); + + } + + texture.store = node.isStorageTextureNode === true; + texture.setVisibility( gpuShaderStageLib[ shaderStage ] ); + + if ( this.isUnfilterable( node.value ) === false && texture.store === false ) { + + const sampler = new NodeSampler( `${ uniformNode.name }_sampler`, uniformNode.node, group ); + sampler.setVisibility( gpuShaderStageLib[ shaderStage ] ); + + bindings.push( sampler, texture ); + + uniformGPU = [ sampler, texture ]; + + } else { + + bindings.push( texture ); + + uniformGPU = [ texture ]; + + } + + } else if ( type === 'buffer' || type === 'storageBuffer' || type === 'indirectStorageBuffer' ) { + + const bufferClass = type === 'buffer' ? NodeUniformBuffer : NodeStorageBuffer; + + const buffer = new bufferClass( node, group ); + buffer.setVisibility( gpuShaderStageLib[ shaderStage ] ); + + bindings.push( buffer ); + + uniformGPU = buffer; + + uniformNode.name = name ? name : 'NodeBuffer_' + uniformNode.id; + + } else { + + const uniformsStage = this.uniformGroups[ shaderStage ] || ( this.uniformGroups[ shaderStage ] = {} ); + + let uniformsGroup = uniformsStage[ groupName ]; + + if ( uniformsGroup === undefined ) { + + uniformsGroup = new NodeUniformsGroup( groupName, group ); + uniformsGroup.setVisibility( gpuShaderStageLib[ shaderStage ] ); + + uniformsStage[ groupName ] = uniformsGroup; + + bindings.push( uniformsGroup ); + + } + + uniformGPU = this.getNodeUniform( uniformNode, type ); + + uniformsGroup.addUniform( uniformGPU ); + + } + + nodeData.uniformGPU = uniformGPU; + + } + + return uniformNode; + + } + + /** + * This method should be used whenever builtins are required in nodes. + * The internal builtins data structure will make sure builtins are + * defined in the WGSL source. + * + * @param {string} name - The builtin name. + * @param {string} property - The property name. + * @param {string} type - The node data type. + * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for. + * @return {string} The property name. + */ + getBuiltin( name, property, type, shaderStage = this.shaderStage ) { + + const map = this.builtins[ shaderStage ] || ( this.builtins[ shaderStage ] = new Map() ); + + if ( map.has( name ) === false ) { + + map.set( name, { + name, + property, + type + } ); + + } + + return property; + + } + + /** + * Returns `true` if the given builtin is defined in the given shader stage. + * + * @param {string} name - The builtin name. + * @param {string} [shaderStage=this.shaderStage] - The shader stage this code snippet is generated for. + * @return {boolean} Whether the given builtin is defined in the given shader stage or not. + */ + hasBuiltin( name, shaderStage = this.shaderStage ) { + + return ( this.builtins[ shaderStage ] !== undefined && this.builtins[ shaderStage ].has( name ) ); + + } + + /** + * Returns the vertex index builtin. + * + * @return {string} The vertex index. + */ + getVertexIndex() { + + if ( this.shaderStage === 'vertex' ) { + + return this.getBuiltin( 'vertex_index', 'vertexIndex', 'u32', 'attribute' ); + + } + + return 'vertexIndex'; + + } + + /** + * Builds the given shader node. + * + * @param {ShaderNodeInternal} shaderNode - The shader node. + * @return {string} The WGSL function code. + */ + buildFunctionCode( shaderNode ) { + + const layout = shaderNode.layout; + const flowData = this.flowShaderNode( shaderNode ); + + const parameters = []; + + for ( const input of layout.inputs ) { + + parameters.push( input.name + ' : ' + this.getType( input.type ) ); + + } + + // + + let code = `fn ${ layout.name }( ${ parameters.join( ', ' ) } ) -> ${ this.getType( layout.type ) } { +${ flowData.vars } +${ flowData.code } +`; + + if ( flowData.result ) { + + code += `\treturn ${ flowData.result };\n`; + + } + + code += '\n}\n'; + + // + + return code; + + } + + /** + * Returns the instance index builtin. + * + * @return {string} The instance index. + */ + getInstanceIndex() { + + if ( this.shaderStage === 'vertex' ) { + + return this.getBuiltin( 'instance_index', 'instanceIndex', 'u32', 'attribute' ); + + } + + return 'instanceIndex'; + + } + + /** + * Returns the invocation local index builtin. + * + * @return {string} The invocation local index. + */ + getInvocationLocalIndex() { + + return this.getBuiltin( 'local_invocation_index', 'invocationLocalIndex', 'u32', 'attribute' ); + + } + + /** + * Returns the subgroup size builtin. + * + * @return {string} The subgroup size. + */ + getSubgroupSize() { + + this.enableSubGroups(); + + return this.getBuiltin( 'subgroup_size', 'subgroupSize', 'u32', 'attribute' ); + + } + + /** + * Returns the invocation subgroup index builtin. + * + * @return {string} The invocation subgroup index. + */ + getInvocationSubgroupIndex() { + + this.enableSubGroups(); + + return this.getBuiltin( 'subgroup_invocation_id', 'invocationSubgroupIndex', 'u32', 'attribute' ); + + } + + /** + * Returns the subgroup index builtin. + * + * @return {string} The subgroup index. + */ + getSubgroupIndex() { + + this.enableSubGroups(); + + return this.getBuiltin( 'subgroup_id', 'subgroupIndex', 'u32', 'attribute' ); + + } + + /** + * Overwritten as a NOP since this method is intended for the WebGL 2 backend. + * + * @return {null} Null. + */ + getDrawIndex() { + + return null; + + } + + /** + * Returns the front facing builtin. + * + * @return {string} The front facing builtin. + */ + getFrontFacing() { + + return this.getBuiltin( 'front_facing', 'isFront', 'bool' ); + + } + + /** + * Returns the frag coord builtin. + * + * @return {string} The frag coord builtin. + */ + getFragCoord() { + + return this.getBuiltin( 'position', 'fragCoord', 'vec4' ) + '.xy'; + + } + + /** + * Returns the frag depth builtin. + * + * @return {string} The frag depth builtin. + */ + getFragDepth() { + + return 'output.' + this.getBuiltin( 'frag_depth', 'depth', 'f32', 'output' ); + + } + + /** + * Returns the clip distances builtin. + * + * @return {string} The clip distances builtin. + */ + getClipDistance() { + + return 'varyings.hw_clip_distances'; + + } + + /** + * Whether to flip texture data along its vertical axis or not. + * + * @return {boolean} Returns always `false` in context of WGSL. + */ + isFlipY() { + + return false; + + } + + /** + * Enables the given directive for the given shader stage. + * + * @param {string} name - The directive name. + * @param {string} [shaderStage=this.shaderStage] - The shader stage to enable the directive for. + */ + enableDirective( name, shaderStage = this.shaderStage ) { + + const stage = this.directives[ shaderStage ] || ( this.directives[ shaderStage ] = new Set() ); + stage.add( name ); + + } + + /** + * Returns the directives of the given shader stage as a WGSL string. + * + * @param {string} shaderStage - The shader stage. + * @return {string} A WGSL snippet that enables the directives of the given stage. + */ + getDirectives( shaderStage ) { + + const snippets = []; + const directives = this.directives[ shaderStage ]; + + if ( directives !== undefined ) { + + for ( const directive of directives ) { + + snippets.push( `enable ${directive};` ); + + } + + } + + return snippets.join( '\n' ); + + } + + /** + * Enables the 'subgroups' directive. + */ + enableSubGroups() { + + this.enableDirective( 'subgroups' ); + + } + + /** + * Enables the 'subgroups-f16' directive. + */ + enableSubgroupsF16() { + + this.enableDirective( 'subgroups-f16' ); + + } + + /** + * Enables the 'clip_distances' directive. + */ + enableClipDistances() { + + this.enableDirective( 'clip_distances' ); + + } + + /** + * Enables the 'f16' directive. + */ + enableShaderF16() { + + this.enableDirective( 'f16' ); + + } + + /** + * Enables the 'dual_source_blending' directive. + */ + enableDualSourceBlending() { + + this.enableDirective( 'dual_source_blending' ); + + } + + /** + * Enables hardware clipping. + * + * @param {string} planeCount - The clipping plane count. + */ + enableHardwareClipping( planeCount ) { + + this.enableClipDistances(); + this.getBuiltin( 'clip_distances', 'hw_clip_distances', `array`, 'vertex' ); + + } + + /** + * Returns the builtins of the given shader stage as a WGSL string. + * + * @param {string} shaderStage - The shader stage. + * @return {string} A WGSL snippet that represents the builtins of the given stage. + */ + getBuiltins( shaderStage ) { + + const snippets = []; + const builtins = this.builtins[ shaderStage ]; + + if ( builtins !== undefined ) { + + for ( const { name, property, type } of builtins.values() ) { + + snippets.push( `@builtin( ${name} ) ${property} : ${type}` ); + + } + + } + + return snippets.join( ',\n\t' ); + + } + + /** + * This method should be used when a new scoped buffer is used in context of + * compute shaders. It adds the array to the internal data structure which is + * later used to generate the respective WGSL. + * + * @param {string} name - The array name. + * @param {string} scope - The scope. + * @param {string} bufferType - The buffer type. + * @param {string} bufferCount - The buffer count. + * @return {string} The array name. + */ + getScopedArray( name, scope, bufferType, bufferCount ) { + + if ( this.scopedArrays.has( name ) === false ) { + + this.scopedArrays.set( name, { + name, + scope, + bufferType, + bufferCount + } ); + + } + + return name; + + } + + /** + * Returns the scoped arrays of the given shader stage as a WGSL string. + * + * @param {string} shaderStage - The shader stage. + * @return {string|undefined} The WGSL snippet that defines the scoped arrays. + * Returns `undefined` when used in the vertex or fragment stage. + */ + getScopedArrays( shaderStage ) { + + if ( shaderStage !== 'compute' ) { + + return; + + } + + const snippets = []; + + for ( const { name, scope, bufferType, bufferCount } of this.scopedArrays.values() ) { + + const type = this.getType( bufferType ); + + snippets.push( `var<${scope}> ${name}: array< ${type}, ${bufferCount} >;` ); + + } + + return snippets.join( '\n' ); + + } + + /** + * Returns the shader attributes of the given shader stage as a WGSL string. + * + * @param {string} shaderStage - The shader stage. + * @return {string} The WGSL snippet that defines the shader attributes. + */ + getAttributes( shaderStage ) { + + const snippets = []; + + if ( shaderStage === 'compute' ) { + + this.getBuiltin( 'global_invocation_id', 'globalId', 'vec3', 'attribute' ); + this.getBuiltin( 'workgroup_id', 'workgroupId', 'vec3', 'attribute' ); + this.getBuiltin( 'local_invocation_id', 'localId', 'vec3', 'attribute' ); + this.getBuiltin( 'num_workgroups', 'numWorkgroups', 'vec3', 'attribute' ); + + if ( this.renderer.hasFeature( 'subgroups' ) ) { + + this.enableDirective( 'subgroups', shaderStage ); + this.getBuiltin( 'subgroup_size', 'subgroupSize', 'u32', 'attribute' ); + + } + + } + + if ( shaderStage === 'vertex' || shaderStage === 'compute' ) { + + const builtins = this.getBuiltins( 'attribute' ); + + if ( builtins ) snippets.push( builtins ); + + const attributes = this.getAttributesArray(); + + for ( let index = 0, length = attributes.length; index < length; index ++ ) { + + const attribute = attributes[ index ]; + const name = attribute.name; + const type = this.getType( attribute.type ); + + snippets.push( `@location( ${index} ) ${ name } : ${ type }` ); + + } + + } + + return snippets.join( ',\n\t' ); + + } + + /** + * Returns the members of the given struct type node as a WGSL string. + * + * @param {StructTypeNode} struct - The struct type node. + * @return {string} The WGSL snippet that defines the struct members. + */ + getStructMembers( struct ) { + + const snippets = []; + + for ( const member of struct.members ) { + + const prefix = struct.output ? '@location( ' + member.index + ' ) ' : ''; + + let type = this.getType( member.type ); + + if ( member.atomic ) { + + type = 'atomic< ' + type + ' >'; + + } + + snippets.push( `\t${ prefix + member.name } : ${ type }` ); + + } + + if ( struct.output ) { + + snippets.push( `\t${ this.getBuiltins( 'output' ) }` ); + + } + + return snippets.join( ',\n' ); + + } + + /** + * Returns the structs of the given shader stage as a WGSL string. + * + * @param {string} shaderStage - The shader stage. + * @return {string} The WGSL snippet that defines the structs. + */ + getStructs( shaderStage ) { + + let result = ''; + + const structs = this.structs[ shaderStage ]; + + if ( structs.length > 0 ) { + + const snippets = []; + + for ( const struct of structs ) { + + let snippet = `struct ${ struct.name } {\n`; + snippet += this.getStructMembers( struct ); + snippet += '\n};'; + + snippets.push( snippet ); + + } + + result = '\n' + snippets.join( '\n\n' ) + '\n'; + + } + + return result; + + } + + /** + * Returns a WGSL string representing a variable. + * + * @param {string} type - The variable's type. + * @param {string} name - The variable's name. + * @param {?number} [count=null] - The array length. + * @return {string} The WGSL snippet that defines a variable. + */ + getVar( type, name, count = null ) { + + let snippet = `var ${ name } : `; + + if ( count !== null ) { + + snippet += this.generateArrayDeclaration( type, count ); + + } else { + + snippet += this.getType( type ); + + } + + return snippet; + + } + + /** + * Returns the variables of the given shader stage as a WGSL string. + * + * @param {string} shaderStage - The shader stage. + * @return {string} The WGSL snippet that defines the variables. + */ + getVars( shaderStage ) { + + const snippets = []; + const vars = this.vars[ shaderStage ]; + + if ( vars !== undefined ) { + + for ( const variable of vars ) { + + snippets.push( `\t${ this.getVar( variable.type, variable.name, variable.count ) };` ); + + } + + } + + return `\n${ snippets.join( '\n' ) }\n`; + + } + + /** + * Returns the varyings of the given shader stage as a WGSL string. + * + * @param {string} shaderStage - The shader stage. + * @return {string} The WGSL snippet that defines the varyings. + */ + getVaryings( shaderStage ) { + + const snippets = []; + + if ( shaderStage === 'vertex' ) { + + this.getBuiltin( 'position', 'Vertex', 'vec4', 'vertex' ); + + } + + if ( shaderStage === 'vertex' || shaderStage === 'fragment' ) { + + const varyings = this.varyings; + const vars = this.vars[ shaderStage ]; + + for ( let index = 0; index < varyings.length; index ++ ) { + + const varying = varyings[ index ]; + + if ( varying.needsInterpolation ) { + + let attributesSnippet = `@location( ${index} )`; + + if ( varying.interpolationType ) { + + const samplingSnippet = varying.interpolationSampling !== null ? `, ${ varying.interpolationSampling } )` : ' )'; + + attributesSnippet += ` @interpolate( ${ varying.interpolationType }${ samplingSnippet }`; + + // Otherwise, optimize interpolation when sensible + + } else if ( /^(int|uint|ivec|uvec)/.test( varying.type ) ) { + + attributesSnippet += ` @interpolate( ${ this.renderer.backend.compatibilityMode ? 'flat, either' : 'flat' } )`; + + } + + snippets.push( `${ attributesSnippet } ${ varying.name } : ${ this.getType( varying.type ) }` ); + + } else if ( shaderStage === 'vertex' && vars.includes( varying ) === false ) { + + vars.push( varying ); + + } + + } + + } + + const builtins = this.getBuiltins( shaderStage ); + + if ( builtins ) snippets.push( builtins ); + + const code = snippets.join( ',\n\t' ); + + return shaderStage === 'vertex' ? this._getWGSLStruct( 'VaryingsStruct', '\t' + code ) : code; + + } + + isCustomStruct( nodeUniform ) { + + const attribute = nodeUniform.value; + const bufferNode = nodeUniform.node; + + const isAttributeStructType = ( attribute.isBufferAttribute || attribute.isInstancedBufferAttribute ) && bufferNode.structTypeNode !== null; + + const isStructArray = + ( bufferNode.value && bufferNode.value.array ) && + ( typeof bufferNode.value.itemSize === 'number' && bufferNode.value.array.length > bufferNode.value.itemSize ); + + return isAttributeStructType && ! isStructArray; + + } + + /** + * Returns the uniforms of the given shader stage as a WGSL string. + * + * @param {string} shaderStage - The shader stage. + * @return {string} The WGSL snippet that defines the uniforms. + */ + getUniforms( shaderStage ) { + + const uniforms = this.uniforms[ shaderStage ]; + + const bindingSnippets = []; + const bufferSnippets = []; + const structSnippets = []; + const uniformGroups = {}; + + for ( const uniform of uniforms ) { + + const groupName = uniform.groupNode.name; + const uniformIndexes = this.bindingsIndexes[ groupName ]; + + if ( uniform.type === 'texture' || uniform.type === 'cubeTexture' || uniform.type === 'storageTexture' || uniform.type === 'texture3D' ) { + + const texture = uniform.node.value; + + if ( this.isUnfilterable( texture ) === false && uniform.node.isStorageTextureNode !== true ) { + + if ( this.isSampleCompare( texture ) ) { + + bindingSnippets.push( `@binding( ${ uniformIndexes.binding ++ } ) @group( ${ uniformIndexes.group } ) var ${ uniform.name }_sampler : sampler_comparison;` ); + + } else { + + bindingSnippets.push( `@binding( ${ uniformIndexes.binding ++ } ) @group( ${ uniformIndexes.group } ) var ${ uniform.name }_sampler : sampler;` ); + + } + + } + + let textureType; + + let multisampled = ''; + + const { primarySamples } = this.renderer.backend.utils.getTextureSampleData( texture ); + + if ( primarySamples > 1 ) { + + multisampled = '_multisampled'; + + } + + if ( texture.isCubeTexture === true ) { + + textureType = 'texture_cube'; + + } else if ( texture.isDepthTexture === true ) { + + if ( this.renderer.backend.compatibilityMode && texture.compareFunction === null ) { + + textureType = `texture${ multisampled }_2d`; + + } else { + + textureType = `texture_depth${ multisampled }_2d${ texture.isArrayTexture === true ? '_array' : '' }`; + + } + + } else if ( uniform.node.isStorageTextureNode === true ) { + + const format = getFormat( texture ); + const access = this.getStorageAccess( uniform.node, shaderStage ); + + const is3D = uniform.node.value.is3DTexture; + const isArrayTexture = uniform.node.value.isArrayTexture; + + const dimension = is3D ? '3d' : `2d${ isArrayTexture ? '_array' : '' }`; + + textureType = `texture_storage_${ dimension }<${ format }, ${ access }>`; + + } else if ( texture.isArrayTexture === true || texture.isDataArrayTexture === true || texture.isCompressedArrayTexture === true ) { + + textureType = 'texture_2d_array'; + + } else if ( texture.is3DTexture === true || texture.isData3DTexture === true ) { + + textureType = 'texture_3d'; + + } else if ( texture.isVideoTexture === true ) { + + textureType = 'texture_external'; + + } else { + + const componentPrefix = this.getComponentTypeFromTexture( texture ).charAt( 0 ); + + textureType = `texture${ multisampled }_2d<${ componentPrefix }32>`; + + } + + bindingSnippets.push( `@binding( ${ uniformIndexes.binding ++ } ) @group( ${ uniformIndexes.group } ) var ${ uniform.name } : ${ textureType };` ); + + } else if ( uniform.type === 'buffer' || uniform.type === 'storageBuffer' || uniform.type === 'indirectStorageBuffer' ) { + + const bufferNode = uniform.node; + const bufferType = this.getType( bufferNode.getNodeType( this ) ); + const bufferCount = bufferNode.bufferCount; + const bufferCountSnippet = bufferCount > 0 && uniform.type === 'buffer' ? ', ' + bufferCount : ''; + const bufferAccessMode = bufferNode.isStorageBufferNode ? `storage, ${ this.getStorageAccess( bufferNode, shaderStage ) }` : 'uniform'; + + if ( this.isCustomStruct( uniform ) ) { + + bufferSnippets.push( `@binding( ${ uniformIndexes.binding ++ } ) @group( ${ uniformIndexes.group } ) var<${ bufferAccessMode }> ${ uniform.name } : ${ bufferType };` ); + + } else { + + const bufferTypeSnippet = bufferNode.isAtomic ? `atomic<${ bufferType }>` : `${ bufferType }`; + const bufferSnippet = `\tvalue : array< ${ bufferTypeSnippet }${ bufferCountSnippet } >`; + + bufferSnippets.push( this._getWGSLStructBinding( uniform.name, bufferSnippet, bufferAccessMode, uniformIndexes.binding ++, uniformIndexes.group ) ); + + } + + } else { + + const vectorType = this.getType( this.getVectorType( uniform.type ) ); + const groupName = uniform.groupNode.name; + + const group = uniformGroups[ groupName ] || ( uniformGroups[ groupName ] = { + index: uniformIndexes.binding ++, + id: uniformIndexes.group, + snippets: [] + } ); + + group.snippets.push( `\t${ uniform.name } : ${ vectorType }` ); + + } + + } + + for ( const name in uniformGroups ) { + + const group = uniformGroups[ name ]; + + structSnippets.push( this._getWGSLStructBinding( name, group.snippets.join( ',\n' ), 'uniform', group.index, group.id ) ); + + } + + let code = bindingSnippets.join( '\n' ); + code += bufferSnippets.join( '\n' ); + code += structSnippets.join( '\n' ); + + return code; + + } + + /** + * Controls the code build of the shader stages. + */ + buildCode() { + + const shadersData = this.material !== null ? { fragment: {}, vertex: {} } : { compute: {} }; + + this.sortBindingGroups(); + + for ( const shaderStage in shadersData ) { + + this.shaderStage = shaderStage; + + const stageData = shadersData[ shaderStage ]; + stageData.uniforms = this.getUniforms( shaderStage ); + stageData.attributes = this.getAttributes( shaderStage ); + stageData.varyings = this.getVaryings( shaderStage ); + stageData.structs = this.getStructs( shaderStage ); + stageData.vars = this.getVars( shaderStage ); + stageData.codes = this.getCodes( shaderStage ); + stageData.directives = this.getDirectives( shaderStage ); + stageData.scopedArrays = this.getScopedArrays( shaderStage ); + + // + + let flow = '// code\n\n'; + flow += this.flowCode[ shaderStage ]; + + const flowNodes = this.flowNodes[ shaderStage ]; + const mainNode = flowNodes[ flowNodes.length - 1 ]; + + const outputNode = mainNode.outputNode; + const isOutputStruct = ( outputNode !== undefined && outputNode.isOutputStructNode === true ); + + for ( const node of flowNodes ) { + + const flowSlotData = this.getFlowData( node/*, shaderStage*/ ); + const slotName = node.name; + + if ( slotName ) { + + if ( flow.length > 0 ) flow += '\n'; + + flow += `\t// flow -> ${ slotName }\n`; + + } + + flow += `${ flowSlotData.code }\n\t`; + + if ( node === mainNode && shaderStage !== 'compute' ) { + + flow += '// result\n\n\t'; + + if ( shaderStage === 'vertex' ) { + + flow += `varyings.Vertex = ${ flowSlotData.result };`; + + } else if ( shaderStage === 'fragment' ) { + + if ( isOutputStruct ) { + + stageData.returnType = outputNode.getNodeType( this ); + stageData.structs += 'var output : ' + stageData.returnType + ';'; + + flow += `return ${ flowSlotData.result };`; + + } else { + + let structSnippet = '\t@location(0) color: vec4'; + + const builtins = this.getBuiltins( 'output' ); + + if ( builtins ) structSnippet += ',\n\t' + builtins; + + stageData.returnType = 'OutputStruct'; + stageData.structs += this._getWGSLStruct( 'OutputStruct', structSnippet ); + stageData.structs += '\nvar output : OutputStruct;'; + + flow += `output.color = ${ flowSlotData.result };\n\n\treturn output;`; + + } + + } + + } + + } + + stageData.flow = flow; + + } + + this.shaderStage = null; + + if ( this.material !== null ) { + + this.vertexShader = this._getWGSLVertexCode( shadersData.vertex ); + this.fragmentShader = this._getWGSLFragmentCode( shadersData.fragment ); + + } else { + + this.computeShader = this._getWGSLComputeCode( shadersData.compute, ( this.object.workgroupSize || [ 64 ] ).join( ', ' ) ); + + } + + } + + /** + * Returns the native shader method name for a given generic name. + * + * @param {string} method - The method name to resolve. + * @param {?string} [output=null] - An optional output. + * @return {string} The resolved WGSL method name. + */ + getMethod( method, output = null ) { + + let wgslMethod; + + if ( output !== null ) { + + wgslMethod = this._getWGSLMethod( method + '_' + output ); + + } + + if ( wgslMethod === undefined ) { + + wgslMethod = this._getWGSLMethod( method ); + + } + + return wgslMethod || method; + + } + + /** + * Returns the WGSL type of the given node data type. + * + * @param {string} type - The node data type. + * @return {string} The WGSL type. + */ + getType( type ) { + + return wgslTypeLib[ type ] || type; + + } + + /** + * Whether the requested feature is available or not. + * + * @param {string} name - The requested feature. + * @return {boolean} Whether the requested feature is supported or not. + */ + isAvailable( name ) { + + let result = supports[ name ]; + + if ( result === undefined ) { + + if ( name === 'float32Filterable' ) { + + result = this.renderer.hasFeature( 'float32-filterable' ); + + } else if ( name === 'clipDistance' ) { + + result = this.renderer.hasFeature( 'clip-distances' ); + + } + + supports[ name ] = result; + + } + + return result; + + } + + /** + * Returns the native shader method name for a given generic name. + * + * @private + * @param {string} method - The method name to resolve. + * @return {string} The resolved WGSL method name. + */ + _getWGSLMethod( method ) { + + if ( wgslPolyfill[ method ] !== undefined ) { + + this._include( method ); + + } + + return wgslMethods[ method ]; + + } + + /** + * Includes the given method name into the current + * function node. + * + * @private + * @param {string} name - The method name to include. + * @return {CodeNode} The respective code node. + */ + _include( name ) { + + const codeNode = wgslPolyfill[ name ]; + codeNode.build( this ); + + if ( this.currentFunctionNode !== null ) { + + this.currentFunctionNode.includes.push( codeNode ); + + } + + return codeNode; + + } + + /** + * Returns a WGSL vertex shader based on the given shader data. + * + * @private + * @param {Object} shaderData - The shader data. + * @return {string} The vertex shader. + */ + _getWGSLVertexCode( shaderData ) { + + return `${ this.getSignature() } +// directives +${shaderData.directives} + +// structs +${shaderData.structs} + +// uniforms +${shaderData.uniforms} + +// varyings +${shaderData.varyings} +var varyings : VaryingsStruct; + +// codes +${shaderData.codes} + +@vertex +fn main( ${shaderData.attributes} ) -> VaryingsStruct { + + // vars + ${shaderData.vars} + + // flow + ${shaderData.flow} + + return varyings; + +} +`; + + } + + /** + * Returns a WGSL fragment shader based on the given shader data. + * + * @private + * @param {Object} shaderData - The shader data. + * @return {string} The vertex shader. + */ + _getWGSLFragmentCode( shaderData ) { + + return `${ this.getSignature() } +// global +${ diagnostics } + +// structs +${shaderData.structs} + +// uniforms +${shaderData.uniforms} + +// codes +${shaderData.codes} + +@fragment +fn main( ${shaderData.varyings} ) -> ${shaderData.returnType} { + + // vars + ${shaderData.vars} + + // flow + ${shaderData.flow} + +} +`; + + } + + /** + * Returns a WGSL compute shader based on the given shader data. + * + * @private + * @param {Object} shaderData - The shader data. + * @param {string} workgroupSize - The workgroup size. + * @return {string} The vertex shader. + */ + _getWGSLComputeCode( shaderData, workgroupSize ) { + + return `${ this.getSignature() } +// directives +${shaderData.directives} + +// system +var instanceIndex : u32; + +// locals +${shaderData.scopedArrays} + +// structs +${shaderData.structs} + +// uniforms +${shaderData.uniforms} + +// codes +${shaderData.codes} + +@compute @workgroup_size( ${workgroupSize} ) +fn main( ${shaderData.attributes} ) { + + // system + instanceIndex = globalId.x + globalId.y * numWorkgroups.x * u32(${workgroupSize}) + globalId.z * numWorkgroups.x * numWorkgroups.y * u32(${workgroupSize}); + + // vars + ${shaderData.vars} + + // flow + ${shaderData.flow} + +} +`; + + } + + /** + * Returns a WGSL struct based on the given name and variables. + * + * @private + * @param {string} name - The struct name. + * @param {string} vars - The struct variables. + * @return {string} The WGSL snippet representing a struct. + */ + _getWGSLStruct( name, vars ) { + + return ` +struct ${name} { +${vars} +};`; + + } + + /** + * Returns a WGSL struct binding. + * + * @private + * @param {string} name - The struct name. + * @param {string} vars - The struct variables. + * @param {string} access - The access. + * @param {number} [binding=0] - The binding index. + * @param {number} [group=0] - The group index. + * @return {string} The WGSL snippet representing a struct binding. + */ + _getWGSLStructBinding( name, vars, access, binding = 0, group = 0 ) { + + const structName = name + 'Struct'; + const structSnippet = this._getWGSLStruct( structName, vars ); + + return `${structSnippet} +@binding( ${ binding } ) @group( ${ group } ) +var<${access}> ${ name } : ${ structName };`; + + } + +} + +/** + * A WebGPU backend utility module with common helpers. + * + * @private + */ +class WebGPUUtils { + + /** + * Constructs a new utility object. + * + * @param {WebGPUBackend} backend - The WebGPU backend. + */ + constructor( backend ) { + + /** + * A reference to the WebGPU backend. + * + * @type {WebGPUBackend} + */ + this.backend = backend; + + } + + /** + * Returns the depth/stencil GPU format for the given render context. + * + * @param {RenderContext} renderContext - The render context. + * @return {string} The depth/stencil GPU texture format. + */ + getCurrentDepthStencilFormat( renderContext ) { + + let format; + + if ( renderContext.depthTexture !== null ) { + + format = this.getTextureFormatGPU( renderContext.depthTexture ); + + } else if ( renderContext.depth && renderContext.stencil ) { + + format = GPUTextureFormat.Depth24PlusStencil8; + + } else if ( renderContext.depth ) { + + format = GPUTextureFormat.Depth24Plus; + + } + + return format; + + } + + /** + * Returns the GPU format for the given texture. + * + * @param {Texture} texture - The texture. + * @return {string} The GPU texture format. + */ + getTextureFormatGPU( texture ) { + + return this.backend.get( texture ).format; + + } + + /** + * Returns an object that defines the multi-sampling state of the given texture. + * + * @param {Texture} texture - The texture. + * @return {Object} The multi-sampling state. + */ + getTextureSampleData( texture ) { + + let samples; + + if ( texture.isFramebufferTexture ) { + + samples = 1; + + } else if ( texture.isDepthTexture && ! texture.renderTarget ) { + + const renderer = this.backend.renderer; + const renderTarget = renderer.getRenderTarget(); + + samples = renderTarget ? renderTarget.samples : renderer.samples; + + } else if ( texture.renderTarget ) { + + samples = texture.renderTarget.samples; + + } + + samples = samples || 1; + + const isMSAA = samples > 1 && texture.renderTarget !== null && ( texture.isDepthTexture !== true && texture.isFramebufferTexture !== true ); + const primarySamples = isMSAA ? 1 : samples; + + return { samples, primarySamples, isMSAA }; + + } + + /** + * Returns the default color attachment's GPU format of the current render context. + * + * @param {RenderContext} renderContext - The render context. + * @return {string} The GPU texture format of the default color attachment. + */ + getCurrentColorFormat( renderContext ) { + + let format; + + if ( renderContext.textures !== null ) { + + format = this.getTextureFormatGPU( renderContext.textures[ 0 ] ); + + } else { + + format = this.getPreferredCanvasFormat(); // default context format + + } + + return format; + + } + + /** + * Returns the output color space of the current render context. + * + * @param {RenderContext} renderContext - The render context. + * @return {string} The output color space. + */ + getCurrentColorSpace( renderContext ) { + + if ( renderContext.textures !== null ) { + + return renderContext.textures[ 0 ].colorSpace; + + } + + return this.backend.renderer.outputColorSpace; + + } + + /** + * Returns GPU primitive topology for the given object and material. + * + * @param {Object3D} object - The 3D object. + * @param {Material} material - The material. + * @return {string} The GPU primitive topology. + */ + getPrimitiveTopology( object, material ) { + + if ( object.isPoints ) return GPUPrimitiveTopology.PointList; + else if ( object.isLineSegments || ( object.isMesh && material.wireframe === true ) ) return GPUPrimitiveTopology.LineList; + else if ( object.isLine ) return GPUPrimitiveTopology.LineStrip; + else if ( object.isMesh ) return GPUPrimitiveTopology.TriangleList; + + } + + /** + * Returns a modified sample count from the given sample count value. + * + * That is required since WebGPU does not support arbitrary sample counts. + * + * @param {number} sampleCount - The input sample count. + * @return {number} The (potentially updated) output sample count. + */ + getSampleCount( sampleCount ) { + + let count = 1; + + if ( sampleCount > 1 ) { + + // WebGPU only supports power-of-two sample counts and 2 is not a valid value + count = Math.pow( 2, Math.floor( Math.log2( sampleCount ) ) ); + + if ( count === 2 ) { + + count = 4; + + } + + } + + return count; + + } + + /** + * Returns the sample count of the given render context. + * + * @param {RenderContext} renderContext - The render context. + * @return {number} The sample count. + */ + getSampleCountRenderContext( renderContext ) { + + if ( renderContext.textures !== null ) { + + return this.getSampleCount( renderContext.sampleCount ); + + } + + return this.getSampleCount( this.backend.renderer.samples ); + + } + + /** + * Returns the preferred canvas format. + * + * There is a separate method for this so it's possible to + * honor edge cases for specific devices. + * + * @return {string} The GPU texture format of the canvas. + */ + getPreferredCanvasFormat() { + + const outputType = this.backend.parameters.outputType; + + if ( outputType === undefined ) { + + return navigator.gpu.getPreferredCanvasFormat(); + + } else if ( outputType === UnsignedByteType ) { + + return GPUTextureFormat.BGRA8Unorm; + + } else if ( outputType === HalfFloatType ) { + + return GPUTextureFormat.RGBA16Float; + + } else { + + throw new Error( 'Unsupported outputType' ); + + } + + } + +} + +const typedArraysToVertexFormatPrefix = new Map( [ + [ Int8Array, [ 'sint8', 'snorm8' ]], + [ Uint8Array, [ 'uint8', 'unorm8' ]], + [ Int16Array, [ 'sint16', 'snorm16' ]], + [ Uint16Array, [ 'uint16', 'unorm16' ]], + [ Int32Array, [ 'sint32', 'snorm32' ]], + [ Uint32Array, [ 'uint32', 'unorm32' ]], + [ Float32Array, [ 'float32', ]], +] ); + +if ( typeof Float16Array !== 'undefined' ) { + + typedArraysToVertexFormatPrefix.set( Float16Array, [ 'float16' ] ); + +} + +const typedAttributeToVertexFormatPrefix = new Map( [ + [ Float16BufferAttribute, [ 'float16', ]], +] ); + +const typeArraysToVertexFormatPrefixForItemSize1 = new Map( [ + [ Int32Array, 'sint32' ], + [ Int16Array, 'sint32' ], // patch for INT16 + [ Uint32Array, 'uint32' ], + [ Uint16Array, 'uint32' ], // patch for UINT16 + [ Float32Array, 'float32' ] +] ); + +/** + * A WebGPU backend utility module for managing shader attributes. + * + * @private + */ +class WebGPUAttributeUtils { + + /** + * Constructs a new utility object. + * + * @param {WebGPUBackend} backend - The WebGPU backend. + */ + constructor( backend ) { + + /** + * A reference to the WebGPU backend. + * + * @type {WebGPUBackend} + */ + this.backend = backend; + + } + + /** + * Creates the GPU buffer for the given buffer attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute. + * @param {GPUBufferUsage} usage - A flag that indicates how the buffer may be used after its creation. + */ + createAttribute( attribute, usage ) { + + const bufferAttribute = this._getBufferAttribute( attribute ); + + const backend = this.backend; + const bufferData = backend.get( bufferAttribute ); + + let buffer = bufferData.buffer; + + if ( buffer === undefined ) { + + const device = backend.device; + + let array = bufferAttribute.array; + + // patch for INT16 and UINT16 + if ( attribute.normalized === false ) { + + if ( array.constructor === Int16Array || array.constructor === Int8Array ) { + + array = new Int32Array( array ); + + } else if ( array.constructor === Uint16Array || array.constructor === Uint8Array ) { + + array = new Uint32Array( array ); + + if ( usage & GPUBufferUsage.INDEX ) { + + for ( let i = 0; i < array.length; i ++ ) { + + if ( array[ i ] === 0xffff ) array[ i ] = 0xffffffff; // use correct primitive restart index + + } + + } + + } + + } + + bufferAttribute.array = array; + + if ( ( bufferAttribute.isStorageBufferAttribute || bufferAttribute.isStorageInstancedBufferAttribute ) && bufferAttribute.itemSize === 3 ) { + + array = new array.constructor( bufferAttribute.count * 4 ); + + for ( let i = 0; i < bufferAttribute.count; i ++ ) { + + array.set( bufferAttribute.array.subarray( i * 3, i * 3 + 3 ), i * 4 ); + + } + + // Update BufferAttribute + bufferAttribute.itemSize = 4; + bufferAttribute.array = array; + + bufferData._force3to4BytesAlignment = true; + + } + + // ensure 4 byte alignment + const byteLength = array.byteLength; + const size = byteLength + ( ( 4 - ( byteLength % 4 ) ) % 4 ); + + buffer = device.createBuffer( { + label: bufferAttribute.name, + size: size, + usage: usage, + mappedAtCreation: true + } ); + + new array.constructor( buffer.getMappedRange() ).set( array ); + + buffer.unmap(); + + bufferData.buffer = buffer; + + } + + } + + /** + * Updates the GPU buffer of the given buffer attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute. + */ + updateAttribute( attribute ) { + + const bufferAttribute = this._getBufferAttribute( attribute ); + + const backend = this.backend; + const device = backend.device; + + const bufferData = backend.get( bufferAttribute ); + const buffer = backend.get( bufferAttribute ).buffer; + + let array = bufferAttribute.array; + + // if storage buffer ensure 4 byte alignment + if ( bufferData._force3to4BytesAlignment === true ) { + + array = new array.constructor( bufferAttribute.count * 4 ); + + for ( let i = 0; i < bufferAttribute.count; i ++ ) { + + array.set( bufferAttribute.array.subarray( i * 3, i * 3 + 3 ), i * 4 ); + + } + + bufferAttribute.array = array; + + } + + + const isTypedArray = this._isTypedArray( array ); + const updateRanges = bufferAttribute.updateRanges; + + if ( updateRanges.length === 0 ) { + + // Not using update ranges + + device.queue.writeBuffer( + buffer, + 0, + array, + 0 + ); + + } else { + + const byteOffsetFactor = isTypedArray ? 1 : array.BYTES_PER_ELEMENT; + + for ( let i = 0, l = updateRanges.length; i < l; i ++ ) { + + const range = updateRanges[ i ]; + let dataOffset, size; + + if ( bufferData._force3to4BytesAlignment === true ) { + + const vertexStart = Math.floor( range.start / 3 ); + const vertexCount = Math.ceil( range.count / 3 ); + dataOffset = vertexStart * 4 * byteOffsetFactor; + size = vertexCount * 4 * byteOffsetFactor; + + } else { + + dataOffset = range.start * byteOffsetFactor; + size = range.count * byteOffsetFactor; + + } + + const bufferOffset = dataOffset * ( isTypedArray ? array.BYTES_PER_ELEMENT : 1 ); // bufferOffset is always in bytes + + device.queue.writeBuffer( + buffer, + bufferOffset, + array, + dataOffset, + size + ); + + } + + bufferAttribute.clearUpdateRanges(); + + } + + } + + /** + * This method creates the vertex buffer layout data which are + * require when creating a render pipeline for the given render object. + * + * @param {RenderObject} renderObject - The render object. + * @return {Array} An array holding objects which describe the vertex buffer layout. + */ + createShaderVertexBuffers( renderObject ) { + + const attributes = renderObject.getAttributes(); + const vertexBuffers = new Map(); + + for ( let slot = 0; slot < attributes.length; slot ++ ) { + + const geometryAttribute = attributes[ slot ]; + const bytesPerElement = geometryAttribute.array.BYTES_PER_ELEMENT; + const bufferAttribute = this._getBufferAttribute( geometryAttribute ); + + let vertexBufferLayout = vertexBuffers.get( bufferAttribute ); + + if ( vertexBufferLayout === undefined ) { + + let arrayStride, stepMode; + + if ( geometryAttribute.isInterleavedBufferAttribute === true ) { + + arrayStride = geometryAttribute.data.stride * bytesPerElement; + stepMode = geometryAttribute.data.isInstancedInterleavedBuffer ? GPUInputStepMode.Instance : GPUInputStepMode.Vertex; + + } else { + + arrayStride = geometryAttribute.itemSize * bytesPerElement; + stepMode = geometryAttribute.isInstancedBufferAttribute ? GPUInputStepMode.Instance : GPUInputStepMode.Vertex; + + } + + // patch for INT16 and UINT16 + if ( geometryAttribute.normalized === false && ( geometryAttribute.array.constructor === Int16Array || geometryAttribute.array.constructor === Uint16Array ) ) { + + arrayStride = 4; + + } + + vertexBufferLayout = { + arrayStride, + attributes: [], + stepMode + }; + + vertexBuffers.set( bufferAttribute, vertexBufferLayout ); + + } + + const format = this._getVertexFormat( geometryAttribute ); + const offset = ( geometryAttribute.isInterleavedBufferAttribute === true ) ? geometryAttribute.offset * bytesPerElement : 0; + + vertexBufferLayout.attributes.push( { + shaderLocation: slot, + offset, + format + } ); + + } + + return Array.from( vertexBuffers.values() ); + + } + + /** + * Destroys the GPU buffer of the given buffer attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute. + */ + destroyAttribute( attribute ) { + + const backend = this.backend; + const data = backend.get( this._getBufferAttribute( attribute ) ); + + data.buffer.destroy(); + + backend.delete( attribute ); + + } + + /** + * This method performs a readback operation by moving buffer data from + * a storage buffer attribute from the GPU to the CPU. + * + * @async + * @param {StorageBufferAttribute} attribute - The storage buffer attribute. + * @return {Promise} A promise that resolves with the buffer data when the data are ready. + */ + async getArrayBufferAsync( attribute ) { + + const backend = this.backend; + const device = backend.device; + + const data = backend.get( this._getBufferAttribute( attribute ) ); + const bufferGPU = data.buffer; + const size = bufferGPU.size; + + const readBufferGPU = device.createBuffer( { + label: `${ attribute.name }_readback`, + size, + usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ + } ); + + const cmdEncoder = device.createCommandEncoder( { + label: `readback_encoder_${ attribute.name }` + } ); + + cmdEncoder.copyBufferToBuffer( + bufferGPU, + 0, + readBufferGPU, + 0, + size + ); + + const gpuCommands = cmdEncoder.finish(); + device.queue.submit( [ gpuCommands ] ); + + await readBufferGPU.mapAsync( GPUMapMode.READ ); + + const arrayBuffer = readBufferGPU.getMappedRange(); + + const dstBuffer = new attribute.array.constructor( arrayBuffer.slice( 0 ) ); + + readBufferGPU.unmap(); + + return dstBuffer.buffer; + + } + + /** + * Returns the vertex format of the given buffer attribute. + * + * @private + * @param {BufferAttribute} geometryAttribute - The buffer attribute. + * @return {string|undefined} The vertex format (e.g. 'float32x3'). + */ + _getVertexFormat( geometryAttribute ) { + + const { itemSize, normalized } = geometryAttribute; + const ArrayType = geometryAttribute.array.constructor; + const AttributeType = geometryAttribute.constructor; + + let format; + + if ( itemSize === 1 ) { + + format = typeArraysToVertexFormatPrefixForItemSize1.get( ArrayType ); + + } else { + + const prefixOptions = typedAttributeToVertexFormatPrefix.get( AttributeType ) || typedArraysToVertexFormatPrefix.get( ArrayType ); + const prefix = prefixOptions[ normalized ? 1 : 0 ]; + + if ( prefix ) { + + const bytesPerUnit = ArrayType.BYTES_PER_ELEMENT * itemSize; + const paddedBytesPerUnit = Math.floor( ( bytesPerUnit + 3 ) / 4 ) * 4; + const paddedItemSize = paddedBytesPerUnit / ArrayType.BYTES_PER_ELEMENT; + + if ( paddedItemSize % 1 ) { + + throw new Error( 'THREE.WebGPUAttributeUtils: Bad vertex format item size.' ); + + } + + format = `${prefix}x${paddedItemSize}`; + + } + + } + + if ( ! format ) { + + console.error( 'THREE.WebGPUAttributeUtils: Vertex format not supported yet.' ); + + } + + return format; + + } + + /** + * Returns `true` if the given array is a typed array. + * + * @private + * @param {any} array - The array. + * @return {boolean} Whether the given array is a typed array or not. + */ + _isTypedArray( array ) { + + return ArrayBuffer.isView( array ) && ! ( array instanceof DataView ); + + } + + /** + * Utility method for handling interleaved buffer attributes correctly. + * To process them, their `InterleavedBuffer` is returned. + * + * @private + * @param {BufferAttribute} attribute - The attribute. + * @return {BufferAttribute|InterleavedBuffer} + */ + _getBufferAttribute( attribute ) { + + if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; + + return attribute; + + } + +} + +/** + * A WebGPU backend utility module for managing bindings. + * + * When reading the documentation it's helpful to keep in mind that + * all class definitions starting with 'GPU*' are modules from the + * WebGPU API. So for example `BindGroup` is a class from the engine + * whereas `GPUBindGroup` is a class from WebGPU. + * + * @private + */ +class WebGPUBindingUtils { + + /** + * Constructs a new utility object. + * + * @param {WebGPUBackend} backend - The WebGPU backend. + */ + constructor( backend ) { + + /** + * A reference to the WebGPU backend. + * + * @type {WebGPUBackend} + */ + this.backend = backend; + + /** + * A cache for managing bind group layouts. + * + * @type {WeakMap,GPUBindGroupLayout>} + */ + this.bindGroupLayoutCache = new WeakMap(); + + } + + /** + * Creates a GPU bind group layout for the given bind group. + * + * @param {BindGroup} bindGroup - The bind group. + * @return {GPUBindGroupLayout} The GPU bind group layout. + */ + createBindingsLayout( bindGroup ) { + + const backend = this.backend; + const device = backend.device; + + const entries = []; + + let index = 0; + + for ( const binding of bindGroup.bindings ) { + + const bindingGPU = { + binding: index ++, + visibility: binding.visibility + }; + + if ( binding.isUniformBuffer || binding.isStorageBuffer ) { + + const buffer = {}; // GPUBufferBindingLayout + + if ( binding.isStorageBuffer ) { + + if ( binding.visibility & 4 ) { + + // compute + + if ( binding.access === NodeAccess.READ_WRITE || binding.access === NodeAccess.WRITE_ONLY ) { + + buffer.type = GPUBufferBindingType.Storage; + + } else { + + buffer.type = GPUBufferBindingType.ReadOnlyStorage; + + } + + } else { + + buffer.type = GPUBufferBindingType.ReadOnlyStorage; + + } + + } + + bindingGPU.buffer = buffer; + + } else if ( binding.isSampler ) { + + const sampler = {}; // GPUSamplerBindingLayout + + if ( binding.texture.isDepthTexture ) { + + if ( binding.texture.compareFunction !== null ) { + + sampler.type = GPUSamplerBindingType.Comparison; + + } else if ( backend.compatibilityMode ) { + + sampler.type = GPUSamplerBindingType.NonFiltering; + + } + + } + + bindingGPU.sampler = sampler; + + } else if ( binding.isSampledTexture && binding.texture.isVideoTexture ) { + + bindingGPU.externalTexture = {}; // GPUExternalTextureBindingLayout + + } else if ( binding.isSampledTexture && binding.store ) { + + const storageTexture = {}; // GPUStorageTextureBindingLayout + storageTexture.format = this.backend.get( binding.texture ).texture.format; + + const access = binding.access; + + if ( access === NodeAccess.READ_WRITE ) { + + storageTexture.access = GPUStorageTextureAccess.ReadWrite; + + } else if ( access === NodeAccess.WRITE_ONLY ) { + + storageTexture.access = GPUStorageTextureAccess.WriteOnly; + + } else { + + storageTexture.access = GPUStorageTextureAccess.ReadOnly; + + } + + if ( binding.texture.isArrayTexture ) { + + storageTexture.viewDimension = GPUTextureViewDimension.TwoDArray; + + } else if ( binding.texture.is3DTexture ) { + + storageTexture.viewDimension = GPUTextureViewDimension.ThreeD; + + } + + bindingGPU.storageTexture = storageTexture; + + } else if ( binding.isSampledTexture ) { + + const texture = {}; // GPUTextureBindingLayout + + const { primarySamples } = backend.utils.getTextureSampleData( binding.texture ); + + if ( primarySamples > 1 ) { + + texture.multisampled = true; + + if ( ! binding.texture.isDepthTexture ) { + + texture.sampleType = GPUTextureSampleType.UnfilterableFloat; + + } + + } + + if ( binding.texture.isDepthTexture ) { + + if ( backend.compatibilityMode && binding.texture.compareFunction === null ) { + + texture.sampleType = GPUTextureSampleType.UnfilterableFloat; + + } else { + + texture.sampleType = GPUTextureSampleType.Depth; + + } + + } else if ( binding.texture.isDataTexture || binding.texture.isDataArrayTexture || binding.texture.isData3DTexture ) { + + const type = binding.texture.type; + + if ( type === IntType ) { + + texture.sampleType = GPUTextureSampleType.SInt; + + } else if ( type === UnsignedIntType ) { + + texture.sampleType = GPUTextureSampleType.UInt; + + } else if ( type === FloatType ) { + + if ( this.backend.hasFeature( 'float32-filterable' ) ) { + + texture.sampleType = GPUTextureSampleType.Float; + + } else { + + texture.sampleType = GPUTextureSampleType.UnfilterableFloat; + + } + + } + + } + + if ( binding.isSampledCubeTexture ) { + + texture.viewDimension = GPUTextureViewDimension.Cube; + + } else if ( binding.texture.isArrayTexture || binding.texture.isDataArrayTexture || binding.texture.isCompressedArrayTexture ) { + + texture.viewDimension = GPUTextureViewDimension.TwoDArray; + + } else if ( binding.isSampledTexture3D ) { + + texture.viewDimension = GPUTextureViewDimension.ThreeD; + + } + + bindingGPU.texture = texture; + + } else { + + console.error( `WebGPUBindingUtils: Unsupported binding "${ binding }".` ); + + } + + entries.push( bindingGPU ); + + } + + return device.createBindGroupLayout( { entries } ); + + } + + /** + * Creates bindings from the given bind group definition. + * + * @param {BindGroup} bindGroup - The bind group. + * @param {Array} bindings - Array of bind groups. + * @param {number} cacheIndex - The cache index. + * @param {number} version - The version. + */ + createBindings( bindGroup, bindings, cacheIndex, version = 0 ) { + + const { backend, bindGroupLayoutCache } = this; + const bindingsData = backend.get( bindGroup ); + + // setup (static) binding layout and (dynamic) binding group + + let bindLayoutGPU = bindGroupLayoutCache.get( bindGroup.bindingsReference ); + + if ( bindLayoutGPU === undefined ) { + + bindLayoutGPU = this.createBindingsLayout( bindGroup ); + bindGroupLayoutCache.set( bindGroup.bindingsReference, bindLayoutGPU ); + + } + + let bindGroupGPU; + + if ( cacheIndex > 0 ) { + + if ( bindingsData.groups === undefined ) { + + bindingsData.groups = []; + bindingsData.versions = []; + + } + + if ( bindingsData.versions[ cacheIndex ] === version ) { + + bindGroupGPU = bindingsData.groups[ cacheIndex ]; + + } + + } + + if ( bindGroupGPU === undefined ) { + + bindGroupGPU = this.createBindGroup( bindGroup, bindLayoutGPU ); + + if ( cacheIndex > 0 ) { + + bindingsData.groups[ cacheIndex ] = bindGroupGPU; + bindingsData.versions[ cacheIndex ] = version; + + } + + } + + bindingsData.group = bindGroupGPU; + bindingsData.layout = bindLayoutGPU; + + } + + /** + * Updates a buffer binding. + * + * @param {Buffer} binding - The buffer binding to update. + */ + updateBinding( binding ) { + + const backend = this.backend; + const device = backend.device; + + const buffer = binding.buffer; + const bufferGPU = backend.get( binding ).buffer; + + device.queue.writeBuffer( bufferGPU, 0, buffer, 0 ); + + } + + /** + * Creates a GPU bind group for the camera index. + * + * @param {Uint32Array} data - The index data. + * @param {GPUBindGroupLayout} layout - The GPU bind group layout. + * @return {GPUBindGroup} The GPU bind group. + */ + createBindGroupIndex( data, layout ) { + + const backend = this.backend; + const device = backend.device; + + const usage = GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST; + const index = data[ 0 ]; + + const buffer = device.createBuffer( { + label: 'bindingCameraIndex_' + index, + size: 16, // uint(4) * 4 + usage: usage + } ); + + device.queue.writeBuffer( buffer, 0, data, 0 ); + + const entries = [ { binding: 0, resource: { buffer } } ]; + + return device.createBindGroup( { + label: 'bindGroupCameraIndex_' + index, + layout, + entries + } ); + + } + + /** + * Creates a GPU bind group for the given bind group and GPU layout. + * + * @param {BindGroup} bindGroup - The bind group. + * @param {GPUBindGroupLayout} layoutGPU - The GPU bind group layout. + * @return {GPUBindGroup} The GPU bind group. + */ + createBindGroup( bindGroup, layoutGPU ) { + + const backend = this.backend; + const device = backend.device; + + let bindingPoint = 0; + const entriesGPU = []; + + for ( const binding of bindGroup.bindings ) { + + if ( binding.isUniformBuffer ) { + + const bindingData = backend.get( binding ); + + if ( bindingData.buffer === undefined ) { + + const byteLength = binding.byteLength; + + const usage = GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST; + + const bufferGPU = device.createBuffer( { + label: 'bindingBuffer_' + binding.name, + size: byteLength, + usage: usage + } ); + + bindingData.buffer = bufferGPU; + + } + + entriesGPU.push( { binding: bindingPoint, resource: { buffer: bindingData.buffer } } ); + + } else if ( binding.isStorageBuffer ) { + + const bindingData = backend.get( binding ); + + if ( bindingData.buffer === undefined ) { + + const attribute = binding.attribute; + //const usage = GPUBufferUsage.STORAGE | GPUBufferUsage.VERTEX | /*GPUBufferUsage.COPY_SRC |*/ GPUBufferUsage.COPY_DST; + + //backend.attributeUtils.createAttribute( attribute, usage ); // @TODO: Move it to universal renderer + + bindingData.buffer = backend.get( attribute ).buffer; + + } + + entriesGPU.push( { binding: bindingPoint, resource: { buffer: bindingData.buffer } } ); + + } else if ( binding.isSampler ) { + + const textureGPU = backend.get( binding.texture ); + + entriesGPU.push( { binding: bindingPoint, resource: textureGPU.sampler } ); + + } else if ( binding.isSampledTexture ) { + + const textureData = backend.get( binding.texture ); + + let resourceGPU; + + if ( textureData.externalTexture !== undefined ) { + + resourceGPU = device.importExternalTexture( { source: textureData.externalTexture } ); + + } else { + + const mipLevelCount = binding.store ? 1 : textureData.texture.mipLevelCount; + const propertyName = `view-${ textureData.texture.width }-${ textureData.texture.height }-${ mipLevelCount }`; + + resourceGPU = textureData[ propertyName ]; + + if ( resourceGPU === undefined ) { + + const aspectGPU = GPUTextureAspect.All; + + let dimensionViewGPU; + + if ( binding.isSampledCubeTexture ) { + + dimensionViewGPU = GPUTextureViewDimension.Cube; + + } else if ( binding.isSampledTexture3D ) { + + dimensionViewGPU = GPUTextureViewDimension.ThreeD; + + } else if ( binding.texture.isArrayTexture || binding.texture.isDataArrayTexture || binding.texture.isCompressedArrayTexture ) { + + dimensionViewGPU = GPUTextureViewDimension.TwoDArray; + + } else { + + dimensionViewGPU = GPUTextureViewDimension.TwoD; + + } + + resourceGPU = textureData[ propertyName ] = textureData.texture.createView( { aspect: aspectGPU, dimension: dimensionViewGPU, mipLevelCount } ); + + } + + } + + entriesGPU.push( { binding: bindingPoint, resource: resourceGPU } ); + + } + + bindingPoint ++; + + } + + return device.createBindGroup( { + label: 'bindGroup_' + bindGroup.name, + layout: layoutGPU, + entries: entriesGPU + } ); + + } + +} + +/** + * A WebGPU backend utility module for managing pipelines. + * + * @private + */ +class WebGPUPipelineUtils { + + /** + * Constructs a new utility object. + * + * @param {WebGPUBackend} backend - The WebGPU backend. + */ + constructor( backend ) { + + /** + * A reference to the WebGPU backend. + * + * @type {WebGPUBackend} + */ + this.backend = backend; + + /** + * A Weak Map that tracks the active pipeline for render or compute passes. + * + * @private + * @type {WeakMap<(GPURenderPassEncoder|GPUComputePassEncoder),(GPURenderPipeline|GPUComputePipeline)>} + */ + this._activePipelines = new WeakMap(); + + } + + /** + * Sets the given pipeline for the given pass. The method makes sure to only set the + * pipeline when necessary. + * + * @param {(GPURenderPassEncoder|GPUComputePassEncoder)} pass - The pass encoder. + * @param {(GPURenderPipeline|GPUComputePipeline)} pipeline - The pipeline. + */ + setPipeline( pass, pipeline ) { + + const currentPipeline = this._activePipelines.get( pass ); + + if ( currentPipeline !== pipeline ) { + + pass.setPipeline( pipeline ); + + this._activePipelines.set( pass, pipeline ); + + } + + } + + /** + * Returns the sample count derived from the given render context. + * + * @private + * @param {RenderContext} renderContext - The render context. + * @return {number} The sample count. + */ + _getSampleCount( renderContext ) { + + return this.backend.utils.getSampleCountRenderContext( renderContext ); + + } + + /** + * Creates a render pipeline for the given render object. + * + * @param {RenderObject} renderObject - The render object. + * @param {Array} promises - An array of compilation promises which are used in `compileAsync()`. + */ + createRenderPipeline( renderObject, promises ) { + + const { object, material, geometry, pipeline } = renderObject; + const { vertexProgram, fragmentProgram } = pipeline; + + const backend = this.backend; + const device = backend.device; + const utils = backend.utils; + + const pipelineData = backend.get( pipeline ); + + // bind group layouts + + const bindGroupLayouts = []; + + for ( const bindGroup of renderObject.getBindings() ) { + + const bindingsData = backend.get( bindGroup ); + + bindGroupLayouts.push( bindingsData.layout ); + + } + + // vertex buffers + + const vertexBuffers = backend.attributeUtils.createShaderVertexBuffers( renderObject ); + + // blending + + let blending; + + if ( material.blending !== NoBlending && ( material.blending !== NormalBlending || material.transparent !== false ) ) { + + blending = this._getBlending( material ); + + } + + // stencil + + let stencilFront = {}; + + if ( material.stencilWrite === true ) { + + stencilFront = { + compare: this._getStencilCompare( material ), + failOp: this._getStencilOperation( material.stencilFail ), + depthFailOp: this._getStencilOperation( material.stencilZFail ), + passOp: this._getStencilOperation( material.stencilZPass ) + }; + + } + + const colorWriteMask = this._getColorWriteMask( material ); + + const targets = []; + + if ( renderObject.context.textures !== null ) { + + const textures = renderObject.context.textures; + + for ( let i = 0; i < textures.length; i ++ ) { + + const colorFormat = utils.getTextureFormatGPU( textures[ i ] ); + + targets.push( { + format: colorFormat, + blend: blending, + writeMask: colorWriteMask + } ); + + } + + } else { + + const colorFormat = utils.getCurrentColorFormat( renderObject.context ); + + targets.push( { + format: colorFormat, + blend: blending, + writeMask: colorWriteMask + } ); + + } + + const vertexModule = backend.get( vertexProgram ).module; + const fragmentModule = backend.get( fragmentProgram ).module; + + const primitiveState = this._getPrimitiveState( object, geometry, material ); + const depthCompare = this._getDepthCompare( material ); + const depthStencilFormat = utils.getCurrentDepthStencilFormat( renderObject.context ); + + const sampleCount = this._getSampleCount( renderObject.context ); + + const pipelineDescriptor = { + label: `renderPipeline_${ material.name || material.type }_${ material.id }`, + vertex: Object.assign( {}, vertexModule, { buffers: vertexBuffers } ), + fragment: Object.assign( {}, fragmentModule, { targets } ), + primitive: primitiveState, + multisample: { + count: sampleCount, + alphaToCoverageEnabled: material.alphaToCoverage && sampleCount > 1 + }, + layout: device.createPipelineLayout( { + bindGroupLayouts + } ) + }; + + + const depthStencil = {}; + const renderDepth = renderObject.context.depth; + const renderStencil = renderObject.context.stencil; + + if ( renderDepth === true || renderStencil === true ) { + + if ( renderDepth === true ) { + + depthStencil.format = depthStencilFormat; + depthStencil.depthWriteEnabled = material.depthWrite; + depthStencil.depthCompare = depthCompare; + + } + + if ( renderStencil === true ) { + + depthStencil.stencilFront = stencilFront; + depthStencil.stencilBack = {}; // three.js does not provide an API to configure the back function (gl.stencilFuncSeparate() was never used) + depthStencil.stencilReadMask = material.stencilFuncMask; + depthStencil.stencilWriteMask = material.stencilWriteMask; + + } + + if ( material.polygonOffset === true ) { + + depthStencil.depthBias = material.polygonOffsetUnits; + depthStencil.depthBiasSlopeScale = material.polygonOffsetFactor; + depthStencil.depthBiasClamp = 0; // three.js does not provide an API to configure this value + + } + + pipelineDescriptor.depthStencil = depthStencil; + + } + + + if ( promises === null ) { + + pipelineData.pipeline = device.createRenderPipeline( pipelineDescriptor ); + + } else { + + const p = new Promise( ( resolve /*, reject*/ ) => { + + device.createRenderPipelineAsync( pipelineDescriptor ).then( pipeline => { + + pipelineData.pipeline = pipeline; + resolve(); + + } ); + + } ); + + promises.push( p ); + + } + + } + + /** + * Creates GPU render bundle encoder for the given render context. + * + * @param {RenderContext} renderContext - The render context. + * @param {?string} [label='renderBundleEncoder'] - The label. + * @return {GPURenderBundleEncoder} The GPU render bundle encoder. + */ + createBundleEncoder( renderContext, label = 'renderBundleEncoder' ) { + + const backend = this.backend; + const { utils, device } = backend; + + const depthStencilFormat = utils.getCurrentDepthStencilFormat( renderContext ); + const colorFormat = utils.getCurrentColorFormat( renderContext ); + const sampleCount = this._getSampleCount( renderContext ); + + const descriptor = { + label: label, + colorFormats: [ colorFormat ], + depthStencilFormat, + sampleCount + }; + + return device.createRenderBundleEncoder( descriptor ); + + } + + /** + * Creates a compute pipeline for the given compute node. + * + * @param {ComputePipeline} pipeline - The compute pipeline. + * @param {Array} bindings - The bindings. + */ + createComputePipeline( pipeline, bindings ) { + + const backend = this.backend; + const device = backend.device; + + const computeProgram = backend.get( pipeline.computeProgram ).module; + + const pipelineGPU = backend.get( pipeline ); + + // bind group layouts + + const bindGroupLayouts = []; + + for ( const bindingsGroup of bindings ) { + + const bindingsData = backend.get( bindingsGroup ); + + bindGroupLayouts.push( bindingsData.layout ); + + } + + pipelineGPU.pipeline = device.createComputePipeline( { + compute: computeProgram, + layout: device.createPipelineLayout( { + bindGroupLayouts + } ) + } ); + + } + + /** + * Returns the blending state as a descriptor object required + * for the pipeline creation. + * + * @private + * @param {Material} material - The material. + * @return {Object} The blending state. + */ + _getBlending( material ) { + + let color, alpha; + + const blending = material.blending; + const blendSrc = material.blendSrc; + const blendDst = material.blendDst; + const blendEquation = material.blendEquation; + + + if ( blending === CustomBlending ) { + + const blendSrcAlpha = material.blendSrcAlpha !== null ? material.blendSrcAlpha : blendSrc; + const blendDstAlpha = material.blendDstAlpha !== null ? material.blendDstAlpha : blendDst; + const blendEquationAlpha = material.blendEquationAlpha !== null ? material.blendEquationAlpha : blendEquation; + + color = { + srcFactor: this._getBlendFactor( blendSrc ), + dstFactor: this._getBlendFactor( blendDst ), + operation: this._getBlendOperation( blendEquation ) + }; + + alpha = { + srcFactor: this._getBlendFactor( blendSrcAlpha ), + dstFactor: this._getBlendFactor( blendDstAlpha ), + operation: this._getBlendOperation( blendEquationAlpha ) + }; + + } else { + + const premultipliedAlpha = material.premultipliedAlpha; + + const setBlend = ( srcRGB, dstRGB, srcAlpha, dstAlpha ) => { + + color = { + srcFactor: srcRGB, + dstFactor: dstRGB, + operation: GPUBlendOperation.Add + }; + + alpha = { + srcFactor: srcAlpha, + dstFactor: dstAlpha, + operation: GPUBlendOperation.Add + }; + + }; + + if ( premultipliedAlpha ) { + + switch ( blending ) { + + case NormalBlending: + setBlend( GPUBlendFactor.One, GPUBlendFactor.OneMinusSrcAlpha, GPUBlendFactor.One, GPUBlendFactor.OneMinusSrcAlpha ); + break; + + case AdditiveBlending: + setBlend( GPUBlendFactor.One, GPUBlendFactor.One, GPUBlendFactor.One, GPUBlendFactor.One ); + break; + + case SubtractiveBlending: + setBlend( GPUBlendFactor.Zero, GPUBlendFactor.OneMinusSrc, GPUBlendFactor.Zero, GPUBlendFactor.One ); + break; + + case MultiplyBlending: + setBlend( GPUBlendFactor.Dst, GPUBlendFactor.OneMinusSrcAlpha, GPUBlendFactor.Zero, GPUBlendFactor.One ); + break; + + } + + } else { + + switch ( blending ) { + + case NormalBlending: + setBlend( GPUBlendFactor.SrcAlpha, GPUBlendFactor.OneMinusSrcAlpha, GPUBlendFactor.One, GPUBlendFactor.OneMinusSrcAlpha ); + break; + + case AdditiveBlending: + setBlend( GPUBlendFactor.SrcAlpha, GPUBlendFactor.One, GPUBlendFactor.One, GPUBlendFactor.One ); + break; + + case SubtractiveBlending: + console.error( 'THREE.WebGPURenderer: SubtractiveBlending requires material.premultipliedAlpha = true' ); + break; + + case MultiplyBlending: + console.error( 'THREE.WebGPURenderer: MultiplyBlending requires material.premultipliedAlpha = true' ); + break; + + } + + } + + } + + if ( color !== undefined && alpha !== undefined ) { + + return { color, alpha }; + + } else { + + console.error( 'THREE.WebGPURenderer: Invalid blending: ', blending ); + + } + + } + /** + * Returns the GPU blend factor which is required for the pipeline creation. + * + * @private + * @param {number} blend - The blend factor as a three.js constant. + * @return {string} The GPU blend factor. + */ + _getBlendFactor( blend ) { + + let blendFactor; + + switch ( blend ) { + + case ZeroFactor: + blendFactor = GPUBlendFactor.Zero; + break; + + case OneFactor: + blendFactor = GPUBlendFactor.One; + break; + + case SrcColorFactor: + blendFactor = GPUBlendFactor.Src; + break; + + case OneMinusSrcColorFactor: + blendFactor = GPUBlendFactor.OneMinusSrc; + break; + + case SrcAlphaFactor: + blendFactor = GPUBlendFactor.SrcAlpha; + break; + + case OneMinusSrcAlphaFactor: + blendFactor = GPUBlendFactor.OneMinusSrcAlpha; + break; + + case DstColorFactor: + blendFactor = GPUBlendFactor.Dst; + break; + + case OneMinusDstColorFactor: + blendFactor = GPUBlendFactor.OneMinusDst; + break; + + case DstAlphaFactor: + blendFactor = GPUBlendFactor.DstAlpha; + break; + + case OneMinusDstAlphaFactor: + blendFactor = GPUBlendFactor.OneMinusDstAlpha; + break; + + case SrcAlphaSaturateFactor: + blendFactor = GPUBlendFactor.SrcAlphaSaturated; + break; + + case BlendColorFactor: + blendFactor = GPUBlendFactor.Constant; + break; + + case OneMinusBlendColorFactor: + blendFactor = GPUBlendFactor.OneMinusConstant; + break; + + default: + console.error( 'THREE.WebGPURenderer: Blend factor not supported.', blend ); + + } + + return blendFactor; + + } + + /** + * Returns the GPU stencil compare function which is required for the pipeline creation. + * + * @private + * @param {Material} material - The material. + * @return {string} The GPU stencil compare function. + */ + _getStencilCompare( material ) { + + let stencilCompare; + + const stencilFunc = material.stencilFunc; + + switch ( stencilFunc ) { + + case NeverStencilFunc: + stencilCompare = GPUCompareFunction.Never; + break; + + case AlwaysStencilFunc: + stencilCompare = GPUCompareFunction.Always; + break; + + case LessStencilFunc: + stencilCompare = GPUCompareFunction.Less; + break; + + case LessEqualStencilFunc: + stencilCompare = GPUCompareFunction.LessEqual; + break; + + case EqualStencilFunc: + stencilCompare = GPUCompareFunction.Equal; + break; + + case GreaterEqualStencilFunc: + stencilCompare = GPUCompareFunction.GreaterEqual; + break; + + case GreaterStencilFunc: + stencilCompare = GPUCompareFunction.Greater; + break; + + case NotEqualStencilFunc: + stencilCompare = GPUCompareFunction.NotEqual; + break; + + default: + console.error( 'THREE.WebGPURenderer: Invalid stencil function.', stencilFunc ); + + } + + return stencilCompare; + + } + + /** + * Returns the GPU stencil operation which is required for the pipeline creation. + * + * @private + * @param {number} op - A three.js constant defining the stencil operation. + * @return {string} The GPU stencil operation. + */ + _getStencilOperation( op ) { + + let stencilOperation; + + switch ( op ) { + + case KeepStencilOp: + stencilOperation = GPUStencilOperation.Keep; + break; + + case ZeroStencilOp: + stencilOperation = GPUStencilOperation.Zero; + break; + + case ReplaceStencilOp: + stencilOperation = GPUStencilOperation.Replace; + break; + + case InvertStencilOp: + stencilOperation = GPUStencilOperation.Invert; + break; + + case IncrementStencilOp: + stencilOperation = GPUStencilOperation.IncrementClamp; + break; + + case DecrementStencilOp: + stencilOperation = GPUStencilOperation.DecrementClamp; + break; + + case IncrementWrapStencilOp: + stencilOperation = GPUStencilOperation.IncrementWrap; + break; + + case DecrementWrapStencilOp: + stencilOperation = GPUStencilOperation.DecrementWrap; + break; + + default: + console.error( 'THREE.WebGPURenderer: Invalid stencil operation.', stencilOperation ); + + } + + return stencilOperation; + + } + + /** + * Returns the GPU blend operation which is required for the pipeline creation. + * + * @private + * @param {number} blendEquation - A three.js constant defining the blend equation. + * @return {string} The GPU blend operation. + */ + _getBlendOperation( blendEquation ) { + + let blendOperation; + + switch ( blendEquation ) { + + case AddEquation: + blendOperation = GPUBlendOperation.Add; + break; + + case SubtractEquation: + blendOperation = GPUBlendOperation.Subtract; + break; + + case ReverseSubtractEquation: + blendOperation = GPUBlendOperation.ReverseSubtract; + break; + + case MinEquation: + blendOperation = GPUBlendOperation.Min; + break; + + case MaxEquation: + blendOperation = GPUBlendOperation.Max; + break; + + default: + console.error( 'THREE.WebGPUPipelineUtils: Blend equation not supported.', blendEquation ); + + } + + return blendOperation; + + } + + /** + * Returns the primitive state as a descriptor object required + * for the pipeline creation. + * + * @private + * @param {Object3D} object - The 3D object. + * @param {BufferGeometry} geometry - The geometry. + * @param {Material} material - The material. + * @return {Object} The primitive state. + */ + _getPrimitiveState( object, geometry, material ) { + + const descriptor = {}; + const utils = this.backend.utils; + + descriptor.topology = utils.getPrimitiveTopology( object, material ); + + if ( geometry.index !== null && object.isLine === true && object.isLineSegments !== true ) { + + descriptor.stripIndexFormat = ( geometry.index.array instanceof Uint16Array ) ? GPUIndexFormat.Uint16 : GPUIndexFormat.Uint32; + + } + + switch ( material.side ) { + + case FrontSide: + descriptor.frontFace = GPUFrontFace.CCW; + descriptor.cullMode = GPUCullMode.Back; + break; + + case BackSide: + descriptor.frontFace = GPUFrontFace.CCW; + descriptor.cullMode = GPUCullMode.Front; + break; + + case DoubleSide: + descriptor.frontFace = GPUFrontFace.CCW; + descriptor.cullMode = GPUCullMode.None; + break; + + default: + console.error( 'THREE.WebGPUPipelineUtils: Unknown material.side value.', material.side ); + break; + + } + + return descriptor; + + } + + /** + * Returns the GPU color write mask which is required for the pipeline creation. + * + * @private + * @param {Material} material - The material. + * @return {string} The GPU color write mask. + */ + _getColorWriteMask( material ) { + + return ( material.colorWrite === true ) ? GPUColorWriteFlags.All : GPUColorWriteFlags.None; + + } + + /** + * Returns the GPU depth compare function which is required for the pipeline creation. + * + * @private + * @param {Material} material - The material. + * @return {string} The GPU depth compare function. + */ + _getDepthCompare( material ) { + + let depthCompare; + + if ( material.depthTest === false ) { + + depthCompare = GPUCompareFunction.Always; + + } else { + + const depthFunc = material.depthFunc; + + switch ( depthFunc ) { + + case NeverDepth: + depthCompare = GPUCompareFunction.Never; + break; + + case AlwaysDepth: + depthCompare = GPUCompareFunction.Always; + break; + + case LessDepth: + depthCompare = GPUCompareFunction.Less; + break; + + case LessEqualDepth: + depthCompare = GPUCompareFunction.LessEqual; + break; + + case EqualDepth: + depthCompare = GPUCompareFunction.Equal; + break; + + case GreaterEqualDepth: + depthCompare = GPUCompareFunction.GreaterEqual; + break; + + case GreaterDepth: + depthCompare = GPUCompareFunction.Greater; + break; + + case NotEqualDepth: + depthCompare = GPUCompareFunction.NotEqual; + break; + + default: + console.error( 'THREE.WebGPUPipelineUtils: Invalid depth function.', depthFunc ); + + } + + } + + return depthCompare; + + } + +} + +/** + * Manages a pool of WebGPU timestamp queries for performance measurement. + * Extends the base TimestampQueryPool to provide WebGPU-specific implementation. + * + * @augments TimestampQueryPool + */ +class WebGPUTimestampQueryPool extends TimestampQueryPool { + + /** + * Creates a new WebGPU timestamp query pool. + * + * @param {GPUDevice} device - The WebGPU device to create queries on. + * @param {string} type - The type identifier for this query pool. + * @param {number} [maxQueries=2048] - Maximum number of queries this pool can hold. + */ + constructor( device, type, maxQueries = 2048 ) { + + super( maxQueries ); + this.device = device; + this.type = type; + + this.querySet = this.device.createQuerySet( { + type: 'timestamp', + count: this.maxQueries, + label: `queryset_global_timestamp_${type}` + } ); + + const bufferSize = this.maxQueries * 8; + this.resolveBuffer = this.device.createBuffer( { + label: `buffer_timestamp_resolve_${type}`, + size: bufferSize, + usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC + } ); + + this.resultBuffer = this.device.createBuffer( { + label: `buffer_timestamp_result_${type}`, + size: bufferSize, + usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ + } ); + + } + + /** + * Allocates a pair of queries for a given render context. + * + * @param {Object} renderContext - The render context to allocate queries for. + * @returns {?number} The base offset for the allocated queries, or null if allocation failed. + */ + allocateQueriesForContext( renderContext ) { + + if ( ! this.trackTimestamp || this.isDisposed ) return null; + + if ( this.currentQueryIndex + 2 > this.maxQueries ) { + + warnOnce( `WebGPUTimestampQueryPool [${ this.type }]: Maximum number of queries exceeded, when using trackTimestamp it is necessary to resolves the queries via renderer.resolveTimestampsAsync( THREE.TimestampQuery.${ this.type.toUpperCase() } ).` ); + return null; + + } + + const baseOffset = this.currentQueryIndex; + this.currentQueryIndex += 2; + + this.queryOffsets.set( renderContext.id, baseOffset ); + return baseOffset; + + } + + /** + * Asynchronously resolves all pending queries and returns the total duration. + * If there's already a pending resolve operation, returns that promise instead. + * + * @async + * @returns {Promise} The total duration in milliseconds, or the last valid value if resolution fails. + */ + async resolveQueriesAsync() { + + if ( ! this.trackTimestamp || this.currentQueryIndex === 0 || this.isDisposed ) { + + return this.lastValue; + + } + + if ( this.pendingResolve ) { + + return this.pendingResolve; + + } + + this.pendingResolve = this._resolveQueries(); + + try { + + const result = await this.pendingResolve; + return result; + + } finally { + + this.pendingResolve = null; + + } + + } + + /** + * Internal method to resolve queries and calculate total duration. + * + * @async + * @private + * @returns {Promise} The total duration in milliseconds. + */ + async _resolveQueries() { + + if ( this.isDisposed ) { + + return this.lastValue; + + } + + try { + + if ( this.resultBuffer.mapState !== 'unmapped' ) { + + return this.lastValue; + + } + + const currentOffsets = new Map( this.queryOffsets ); + const queryCount = this.currentQueryIndex; + const bytesUsed = queryCount * 8; + + // Reset state before GPU work + this.currentQueryIndex = 0; + this.queryOffsets.clear(); + + const commandEncoder = this.device.createCommandEncoder(); + + commandEncoder.resolveQuerySet( + this.querySet, + 0, + queryCount, + this.resolveBuffer, + 0 + ); + + commandEncoder.copyBufferToBuffer( + this.resolveBuffer, + 0, + this.resultBuffer, + 0, + bytesUsed + ); + + const commandBuffer = commandEncoder.finish(); + this.device.queue.submit( [ commandBuffer ] ); + + if ( this.resultBuffer.mapState !== 'unmapped' ) { + + return this.lastValue; + + } + + // Create and track the mapping operation + await this.resultBuffer.mapAsync( GPUMapMode.READ, 0, bytesUsed ); + + if ( this.isDisposed ) { + + if ( this.resultBuffer.mapState === 'mapped' ) { + + this.resultBuffer.unmap(); + + } + + return this.lastValue; + + } + + const times = new BigUint64Array( this.resultBuffer.getMappedRange( 0, bytesUsed ) ); + let totalDuration = 0; + + for ( const [ , baseOffset ] of currentOffsets ) { + + const startTime = times[ baseOffset ]; + const endTime = times[ baseOffset + 1 ]; + const duration = Number( endTime - startTime ) / 1e6; + totalDuration += duration; + + } + + this.resultBuffer.unmap(); + this.lastValue = totalDuration; + + return totalDuration; + + } catch ( error ) { + + console.error( 'Error resolving queries:', error ); + if ( this.resultBuffer.mapState === 'mapped' ) { + + this.resultBuffer.unmap(); + + } + + return this.lastValue; + + } + + } + + /** + * Dispose of the query pool. + * + * @async + * @returns {Promise} A Promise that resolves when the dispose has been executed. + */ + async dispose() { + + if ( this.isDisposed ) { + + return; + + } + + this.isDisposed = true; + + // Wait for pending resolve operation + if ( this.pendingResolve ) { + + try { + + await this.pendingResolve; + + } catch ( error ) { + + console.error( 'Error waiting for pending resolve:', error ); + + } + + } + + // Ensure buffer is unmapped before destroying + if ( this.resultBuffer && this.resultBuffer.mapState === 'mapped' ) { + + try { + + this.resultBuffer.unmap(); + + } catch ( error ) { + + console.error( 'Error unmapping buffer:', error ); + + } + + } + + // Destroy resources + if ( this.querySet ) { + + this.querySet.destroy(); + this.querySet = null; + + } + + if ( this.resolveBuffer ) { + + this.resolveBuffer.destroy(); + this.resolveBuffer = null; + + } + + if ( this.resultBuffer ) { + + this.resultBuffer.destroy(); + this.resultBuffer = null; + + } + + this.queryOffsets.clear(); + this.pendingResolve = null; + + } + +} + +/*// debugger tools +import 'https://greggman.github.io/webgpu-avoid-redundant-state-setting/webgpu-check-redundant-state-setting.js'; +//*/ + + +/** + * A backend implementation targeting WebGPU. + * + * @private + * @augments Backend + */ +class WebGPUBackend extends Backend { + + /** + * WebGPUBackend options. + * + * @typedef {Object} WebGPUBackend~Options + * @property {boolean} [logarithmicDepthBuffer=false] - Whether logarithmic depth buffer is enabled or not. + * @property {boolean} [alpha=true] - Whether the default framebuffer (which represents the final contents of the canvas) should be transparent or opaque. + * @property {boolean} [compatibilityMode=false] - Whether the backend should be in compatibility mode or not. + * @property {boolean} [depth=true] - Whether the default framebuffer should have a depth buffer or not. + * @property {boolean} [stencil=false] - Whether the default framebuffer should have a stencil buffer or not. + * @property {boolean} [antialias=false] - Whether MSAA as the default anti-aliasing should be enabled or not. + * @property {number} [samples=0] - When `antialias` is `true`, `4` samples are used by default. Set this parameter to any other integer value than 0 to overwrite the default. + * @property {boolean} [forceWebGL=false] - If set to `true`, the renderer uses a WebGL 2 backend no matter if WebGPU is supported or not. + * @property {boolean} [trackTimestamp=false] - Whether to track timestamps with a Timestamp Query API or not. + * @property {string} [powerPreference=undefined] - The power preference. + * @property {Object} [requiredLimits=undefined] - Specifies the limits that are required by the device request. The request will fail if the adapter cannot provide these limits. + * @property {GPUDevice} [device=undefined] - If there is an existing GPU device on app level, it can be passed to the renderer as a parameter. + * @property {number} [outputType=undefined] - Texture type for output to canvas. By default, device's preferred format is used; other formats may incur overhead. + */ + + /** + * Constructs a new WebGPU backend. + * + * @param {WebGPUBackend~Options} [parameters] - The configuration parameter. + */ + constructor( parameters = {} ) { + + super( parameters ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isWebGPUBackend = true; + + // some parameters require default values other than "undefined" + this.parameters.alpha = ( parameters.alpha === undefined ) ? true : parameters.alpha; + this.parameters.compatibilityMode = ( parameters.compatibilityMode === undefined ) ? false : parameters.compatibilityMode; + + this.parameters.requiredLimits = ( parameters.requiredLimits === undefined ) ? {} : parameters.requiredLimits; + + /** + * Indicates whether the backend is in compatibility mode or not. + * @type {boolean} + * @default false + */ + this.compatibilityMode = this.parameters.compatibilityMode; + + /** + * A reference to the device. + * + * @type {?GPUDevice} + * @default null + */ + this.device = null; + + /** + * A reference to the context. + * + * @type {?GPUCanvasContext} + * @default null + */ + this.context = null; + + /** + * A reference to the color attachment of the default framebuffer. + * + * @type {?GPUTexture} + * @default null + */ + this.colorBuffer = null; + + /** + * A reference to the default render pass descriptor. + * + * @type {?Object} + * @default null + */ + this.defaultRenderPassdescriptor = null; + + /** + * A reference to a backend module holding common utility functions. + * + * @type {WebGPUUtils} + */ + this.utils = new WebGPUUtils( this ); + + /** + * A reference to a backend module holding shader attribute-related + * utility functions. + * + * @type {WebGPUAttributeUtils} + */ + this.attributeUtils = new WebGPUAttributeUtils( this ); + + /** + * A reference to a backend module holding shader binding-related + * utility functions. + * + * @type {WebGPUBindingUtils} + */ + this.bindingUtils = new WebGPUBindingUtils( this ); + + /** + * A reference to a backend module holding shader pipeline-related + * utility functions. + * + * @type {WebGPUPipelineUtils} + */ + this.pipelineUtils = new WebGPUPipelineUtils( this ); + + /** + * A reference to a backend module holding shader texture-related + * utility functions. + * + * @type {WebGPUTextureUtils} + */ + this.textureUtils = new WebGPUTextureUtils( this ); + + /** + * A map that manages the resolve buffers for occlusion queries. + * + * @type {Map} + */ + this.occludedResolveCache = new Map(); + + } + + /** + * Initializes the backend so it is ready for usage. + * + * @async + * @param {Renderer} renderer - The renderer. + * @return {Promise} A Promise that resolves when the backend has been initialized. + */ + async init( renderer ) { + + await super.init( renderer ); + + // + + const parameters = this.parameters; + + // create the device if it is not passed with parameters + + let device; + + if ( parameters.device === undefined ) { + + const adapterOptions = { + powerPreference: parameters.powerPreference, + featureLevel: parameters.compatibilityMode ? 'compatibility' : undefined + }; + + const adapter = ( typeof navigator !== 'undefined' ) ? await navigator.gpu.requestAdapter( adapterOptions ) : null; + + if ( adapter === null ) { + + throw new Error( 'WebGPUBackend: Unable to create WebGPU adapter.' ); + + } + + // feature support + + const features = Object.values( GPUFeatureName ); + + const supportedFeatures = []; + + for ( const name of features ) { + + if ( adapter.features.has( name ) ) { + + supportedFeatures.push( name ); + + } + + } + + const deviceDescriptor = { + requiredFeatures: supportedFeatures, + requiredLimits: parameters.requiredLimits + }; + + device = await adapter.requestDevice( deviceDescriptor ); + + } else { + + device = parameters.device; + + } + + device.lost.then( ( info ) => { + + const deviceLossInfo = { + api: 'WebGPU', + message: info.message || 'Unknown reason', + reason: info.reason || null, + originalEvent: info + }; + + renderer.onDeviceLost( deviceLossInfo ); + + } ); + + const context = ( parameters.context !== undefined ) ? parameters.context : renderer.domElement.getContext( 'webgpu' ); + + this.device = device; + this.context = context; + + const alphaMode = parameters.alpha ? 'premultiplied' : 'opaque'; + + this.trackTimestamp = this.trackTimestamp && this.hasFeature( GPUFeatureName.TimestampQuery ); + + this.context.configure( { + device: this.device, + format: this.utils.getPreferredCanvasFormat(), + usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC, + alphaMode: alphaMode + } ); + + this.updateSize(); + + } + + /** + * The coordinate system of the backend. + * + * @type {number} + * @readonly + */ + get coordinateSystem() { + + return WebGPUCoordinateSystem; + + } + + /** + * This method performs a readback operation by moving buffer data from + * a storage buffer attribute from the GPU to the CPU. + * + * @async + * @param {StorageBufferAttribute} attribute - The storage buffer attribute. + * @return {Promise} A promise that resolves with the buffer data when the data are ready. + */ + async getArrayBufferAsync( attribute ) { + + return await this.attributeUtils.getArrayBufferAsync( attribute ); + + } + + /** + * Returns the backend's rendering context. + * + * @return {GPUCanvasContext} The rendering context. + */ + getContext() { + + return this.context; + + } + + /** + * Returns the default render pass descriptor. + * + * In WebGPU, the default framebuffer must be configured + * like custom framebuffers so the backend needs a render + * pass descriptor even when rendering directly to screen. + * + * @private + * @return {Object} The render pass descriptor. + */ + _getDefaultRenderPassDescriptor() { + + let descriptor = this.defaultRenderPassdescriptor; + + if ( descriptor === null ) { + + const renderer = this.renderer; + + descriptor = { + colorAttachments: [ { + view: null + } ], + }; + + if ( this.renderer.depth === true || this.renderer.stencil === true ) { + + descriptor.depthStencilAttachment = { + view: this.textureUtils.getDepthBuffer( renderer.depth, renderer.stencil ).createView() + }; + + } + + const colorAttachment = descriptor.colorAttachments[ 0 ]; + + if ( this.renderer.samples > 0 ) { + + colorAttachment.view = this.colorBuffer.createView(); + + } else { + + colorAttachment.resolveTarget = undefined; + + } + + this.defaultRenderPassdescriptor = descriptor; + + } + + const colorAttachment = descriptor.colorAttachments[ 0 ]; + + if ( this.renderer.samples > 0 ) { + + colorAttachment.resolveTarget = this.context.getCurrentTexture().createView(); + + } else { + + colorAttachment.view = this.context.getCurrentTexture().createView(); + + } + + return descriptor; + + } + + /** + * Internal to determine if the current render target is a render target array with depth 2D array texture. + * + * @param {RenderContext} renderContext - The render context. + * @return {boolean} Whether the render target is a render target array with depth 2D array texture. + * + * @private + */ + _isRenderCameraDepthArray( renderContext ) { + + return renderContext.depthTexture && renderContext.depthTexture.image.depth > 1 && renderContext.camera.isArrayCamera; + + } + + /** + * Returns the render pass descriptor for the given render context. + * + * @private + * @param {RenderContext} renderContext - The render context. + * @param {Object} colorAttachmentsConfig - Configuration object for the color attachments. + * @return {Object} The render pass descriptor. + */ + _getRenderPassDescriptor( renderContext, colorAttachmentsConfig = {} ) { + + const renderTarget = renderContext.renderTarget; + const renderTargetData = this.get( renderTarget ); + + let descriptors = renderTargetData.descriptors; + + if ( descriptors === undefined || + renderTargetData.width !== renderTarget.width || + renderTargetData.height !== renderTarget.height || + renderTargetData.dimensions !== renderTarget.dimensions || + renderTargetData.activeMipmapLevel !== renderContext.activeMipmapLevel || + renderTargetData.activeCubeFace !== renderContext.activeCubeFace || + renderTargetData.samples !== renderTarget.samples + ) { + + descriptors = {}; + + renderTargetData.descriptors = descriptors; + + // dispose + + const onDispose = () => { + + renderTarget.removeEventListener( 'dispose', onDispose ); + this.delete( renderTarget ); + + }; + + if ( renderTarget.hasEventListener( 'dispose', onDispose ) === false ) { + + renderTarget.addEventListener( 'dispose', onDispose ); + + } + + } + + const cacheKey = renderContext.getCacheKey(); + let descriptorBase = descriptors[ cacheKey ]; + + if ( descriptorBase === undefined ) { + + const textures = renderContext.textures; + const textureViews = []; + + let sliceIndex; + + const isRenderCameraDepthArray = this._isRenderCameraDepthArray( renderContext ); + + for ( let i = 0; i < textures.length; i ++ ) { + + const textureData = this.get( textures[ i ] ); + + const viewDescriptor = { + label: `colorAttachment_${ i }`, + baseMipLevel: renderContext.activeMipmapLevel, + mipLevelCount: 1, + baseArrayLayer: renderContext.activeCubeFace, + arrayLayerCount: 1, + dimension: GPUTextureViewDimension.TwoD + }; + + if ( renderTarget.isRenderTarget3D ) { + + sliceIndex = renderContext.activeCubeFace; + + viewDescriptor.baseArrayLayer = 0; + viewDescriptor.dimension = GPUTextureViewDimension.ThreeD; + viewDescriptor.depthOrArrayLayers = textures[ i ].image.depth; + + } else if ( renderTarget.isRenderTarget && textures[ i ].image.depth > 1 ) { + + if ( isRenderCameraDepthArray === true ) { + + const cameras = renderContext.camera.cameras; + for ( let layer = 0; layer < cameras.length; layer ++ ) { + + const layerViewDescriptor = { + ...viewDescriptor, + baseArrayLayer: layer, + arrayLayerCount: 1, + dimension: GPUTextureViewDimension.TwoD + }; + const textureView = textureData.texture.createView( layerViewDescriptor ); + textureViews.push( { + view: textureView, + resolveTarget: undefined, + depthSlice: undefined + } ); + + } + + } else { + + viewDescriptor.dimension = GPUTextureViewDimension.TwoDArray; + viewDescriptor.depthOrArrayLayers = textures[ i ].image.depth; + + } + + } + + if ( isRenderCameraDepthArray !== true ) { + + const textureView = textureData.texture.createView( viewDescriptor ); + + let view, resolveTarget; + + if ( textureData.msaaTexture !== undefined ) { + + view = textureData.msaaTexture.createView(); + resolveTarget = textureView; + + } else { + + view = textureView; + resolveTarget = undefined; + + } + + textureViews.push( { + view, + resolveTarget, + depthSlice: sliceIndex + } ); + + } + + } + + descriptorBase = { textureViews }; + + if ( renderContext.depth ) { + + const depthTextureData = this.get( renderContext.depthTexture ); + const options = {}; + if ( renderContext.depthTexture.isArrayTexture ) { + + options.dimension = GPUTextureViewDimension.TwoD; + options.arrayLayerCount = 1; + options.baseArrayLayer = renderContext.activeCubeFace; + + } + + descriptorBase.depthStencilView = depthTextureData.texture.createView( options ); + + } + + descriptors[ cacheKey ] = descriptorBase; + + renderTargetData.width = renderTarget.width; + renderTargetData.height = renderTarget.height; + renderTargetData.samples = renderTarget.samples; + renderTargetData.activeMipmapLevel = renderContext.activeMipmapLevel; + renderTargetData.activeCubeFace = renderContext.activeCubeFace; + renderTargetData.dimensions = renderTarget.dimensions; + + } + + const descriptor = { + colorAttachments: [] + }; + + // Apply dynamic properties to cached views + for ( let i = 0; i < descriptorBase.textureViews.length; i ++ ) { + + const viewInfo = descriptorBase.textureViews[ i ]; + + let clearValue = { r: 0, g: 0, b: 0, a: 1 }; + if ( i === 0 && colorAttachmentsConfig.clearValue ) { + + clearValue = colorAttachmentsConfig.clearValue; + + } + + descriptor.colorAttachments.push( { + view: viewInfo.view, + depthSlice: viewInfo.depthSlice, + resolveTarget: viewInfo.resolveTarget, + loadOp: colorAttachmentsConfig.loadOp || GPULoadOp.Load, + storeOp: colorAttachmentsConfig.storeOp || GPUStoreOp.Store, + clearValue: clearValue + } ); + + } + + if ( descriptorBase.depthStencilView ) { + + descriptor.depthStencilAttachment = { + view: descriptorBase.depthStencilView + }; + + } + + return descriptor; + + } + + /** + * This method is executed at the beginning of a render call and prepares + * the WebGPU state for upcoming render calls + * + * @param {RenderContext} renderContext - The render context. + */ + beginRender( renderContext ) { + + const renderContextData = this.get( renderContext ); + + const device = this.device; + const occlusionQueryCount = renderContext.occlusionQueryCount; + + let occlusionQuerySet; + + if ( occlusionQueryCount > 0 ) { + + if ( renderContextData.currentOcclusionQuerySet ) renderContextData.currentOcclusionQuerySet.destroy(); + if ( renderContextData.currentOcclusionQueryBuffer ) renderContextData.currentOcclusionQueryBuffer.destroy(); + + // Get a reference to the array of objects with queries. The renderContextData property + // can be changed by another render pass before the buffer.mapAsyc() completes. + renderContextData.currentOcclusionQuerySet = renderContextData.occlusionQuerySet; + renderContextData.currentOcclusionQueryBuffer = renderContextData.occlusionQueryBuffer; + renderContextData.currentOcclusionQueryObjects = renderContextData.occlusionQueryObjects; + + // + + occlusionQuerySet = device.createQuerySet( { type: 'occlusion', count: occlusionQueryCount, label: `occlusionQuerySet_${ renderContext.id }` } ); + + renderContextData.occlusionQuerySet = occlusionQuerySet; + renderContextData.occlusionQueryIndex = 0; + renderContextData.occlusionQueryObjects = new Array( occlusionQueryCount ); + + renderContextData.lastOcclusionObject = null; + + } + + let descriptor; + + if ( renderContext.textures === null ) { + + descriptor = this._getDefaultRenderPassDescriptor(); + + } else { + + descriptor = this._getRenderPassDescriptor( renderContext, { loadOp: GPULoadOp.Load } ); + + } + + this.initTimestampQuery( renderContext, descriptor ); + + descriptor.occlusionQuerySet = occlusionQuerySet; + + const depthStencilAttachment = descriptor.depthStencilAttachment; + + if ( renderContext.textures !== null ) { + + const colorAttachments = descriptor.colorAttachments; + + for ( let i = 0; i < colorAttachments.length; i ++ ) { + + const colorAttachment = colorAttachments[ i ]; + + if ( renderContext.clearColor ) { + + colorAttachment.clearValue = i === 0 ? renderContext.clearColorValue : { r: 0, g: 0, b: 0, a: 1 }; + colorAttachment.loadOp = GPULoadOp.Clear; + + } else { + + colorAttachment.loadOp = GPULoadOp.Load; + + } + + colorAttachment.storeOp = GPUStoreOp.Store; + + } + + } else { + + const colorAttachment = descriptor.colorAttachments[ 0 ]; + + if ( renderContext.clearColor ) { + + colorAttachment.clearValue = renderContext.clearColorValue; + colorAttachment.loadOp = GPULoadOp.Clear; + + } else { + + colorAttachment.loadOp = GPULoadOp.Load; + + } + + colorAttachment.storeOp = GPUStoreOp.Store; + + } + + // + + if ( renderContext.depth ) { + + if ( renderContext.clearDepth ) { + + depthStencilAttachment.depthClearValue = renderContext.clearDepthValue; + depthStencilAttachment.depthLoadOp = GPULoadOp.Clear; + + } else { + + depthStencilAttachment.depthLoadOp = GPULoadOp.Load; + + } + + depthStencilAttachment.depthStoreOp = GPUStoreOp.Store; + + } + + if ( renderContext.stencil ) { + + if ( renderContext.clearStencil ) { + + depthStencilAttachment.stencilClearValue = renderContext.clearStencilValue; + depthStencilAttachment.stencilLoadOp = GPULoadOp.Clear; + + } else { + + depthStencilAttachment.stencilLoadOp = GPULoadOp.Load; + + } + + depthStencilAttachment.stencilStoreOp = GPUStoreOp.Store; + + } + + // + + const encoder = device.createCommandEncoder( { label: 'renderContext_' + renderContext.id } ); + + // shadow arrays - prepare bundle encoders for each camera in an array camera + + if ( this._isRenderCameraDepthArray( renderContext ) === true ) { + + const cameras = renderContext.camera.cameras; + + if ( ! renderContextData.layerDescriptors || renderContextData.layerDescriptors.length !== cameras.length ) { + + this._createDepthLayerDescriptors( renderContext, renderContextData, descriptor, cameras ); + + } else { + + this._updateDepthLayerDescriptors( renderContext, renderContextData, cameras ); + + } + + // Create bundle encoders for each layer + renderContextData.bundleEncoders = []; + renderContextData.bundleSets = []; + + // Create separate bundle encoders for each camera in the array + for ( let i = 0; i < cameras.length; i ++ ) { + + const bundleEncoder = this.pipelineUtils.createBundleEncoder( + renderContext, + 'renderBundleArrayCamera_' + i + ); + + // Initialize state tracking for this bundle + const bundleSets = { + attributes: {}, + bindingGroups: [], + pipeline: null, + index: null + }; + + renderContextData.bundleEncoders.push( bundleEncoder ); + renderContextData.bundleSets.push( bundleSets ); + + } + + // We'll complete the bundles in finishRender + renderContextData.currentPass = null; + + } else { + + const currentPass = encoder.beginRenderPass( descriptor ); + renderContextData.currentPass = currentPass; + + if ( renderContext.viewport ) { + + this.updateViewport( renderContext ); + + } + + if ( renderContext.scissor ) { + + const { x, y, width, height } = renderContext.scissorValue; + currentPass.setScissorRect( x, y, width, height ); + + } + + } + + // + + renderContextData.descriptor = descriptor; + renderContextData.encoder = encoder; + renderContextData.currentSets = { attributes: {}, bindingGroups: [], pipeline: null, index: null }; + renderContextData.renderBundles = []; + + } + + /** + * This method creates layer descriptors for each camera in an array camera + * to prepare for rendering to a depth array texture. + * + * @param {RenderContext} renderContext - The render context. + * @param {Object} renderContextData - The render context data. + * @param {Object} descriptor - The render pass descriptor. + * @param {ArrayCamera} cameras - The array camera. + * + * @private + */ + _createDepthLayerDescriptors( renderContext, renderContextData, descriptor, cameras ) { + + const depthStencilAttachment = descriptor.depthStencilAttachment; + renderContextData.layerDescriptors = []; + + const depthTextureData = this.get( renderContext.depthTexture ); + if ( ! depthTextureData.viewCache ) { + + depthTextureData.viewCache = []; + + } + + for ( let i = 0; i < cameras.length; i ++ ) { + + const layerDescriptor = { + ...descriptor, + colorAttachments: [ { + ...descriptor.colorAttachments[ 0 ], + view: descriptor.colorAttachments[ i ].view + } ] + }; + + if ( descriptor.depthStencilAttachment ) { + + const layerIndex = i; + + if ( ! depthTextureData.viewCache[ layerIndex ] ) { + + depthTextureData.viewCache[ layerIndex ] = depthTextureData.texture.createView( { + dimension: GPUTextureViewDimension.TwoD, + baseArrayLayer: i, + arrayLayerCount: 1 + } ); + + } + + layerDescriptor.depthStencilAttachment = { + view: depthTextureData.viewCache[ layerIndex ], + depthLoadOp: depthStencilAttachment.depthLoadOp || GPULoadOp.Clear, + depthStoreOp: depthStencilAttachment.depthStoreOp || GPUStoreOp.Store, + depthClearValue: depthStencilAttachment.depthClearValue || 1.0 + }; + + if ( renderContext.stencil ) { + + layerDescriptor.depthStencilAttachment.stencilLoadOp = depthStencilAttachment.stencilLoadOp; + layerDescriptor.depthStencilAttachment.stencilStoreOp = depthStencilAttachment.stencilStoreOp; + layerDescriptor.depthStencilAttachment.stencilClearValue = depthStencilAttachment.stencilClearValue; + + } + + } else { + + layerDescriptor.depthStencilAttachment = { ...depthStencilAttachment }; + + } + + renderContextData.layerDescriptors.push( layerDescriptor ); + + } + + } + + /** + * This method updates the layer descriptors for each camera in an array camera + * to prepare for rendering to a depth array texture. + * + * @param {RenderContext} renderContext - The render context. + * @param {Object} renderContextData - The render context data. + * @param {ArrayCamera} cameras - The array camera. + * + */ + _updateDepthLayerDescriptors( renderContext, renderContextData, cameras ) { + + for ( let i = 0; i < cameras.length; i ++ ) { + + const layerDescriptor = renderContextData.layerDescriptors[ i ]; + + if ( layerDescriptor.depthStencilAttachment ) { + + const depthAttachment = layerDescriptor.depthStencilAttachment; + + if ( renderContext.depth ) { + + if ( renderContext.clearDepth ) { + + depthAttachment.depthClearValue = renderContext.clearDepthValue; + depthAttachment.depthLoadOp = GPULoadOp.Clear; + + } else { + + depthAttachment.depthLoadOp = GPULoadOp.Load; + + } + + } + + if ( renderContext.stencil ) { + + if ( renderContext.clearStencil ) { + + depthAttachment.stencilClearValue = renderContext.clearStencilValue; + depthAttachment.stencilLoadOp = GPULoadOp.Clear; + + } else { + + depthAttachment.stencilLoadOp = GPULoadOp.Load; + + } + + } + + } + + } + + } + + /** + * This method is executed at the end of a render call and finalizes work + * after draw calls. + * + * @param {RenderContext} renderContext - The render context. + */ + finishRender( renderContext ) { + + const renderContextData = this.get( renderContext ); + const occlusionQueryCount = renderContext.occlusionQueryCount; + + if ( renderContextData.renderBundles.length > 0 ) { + + renderContextData.currentPass.executeBundles( renderContextData.renderBundles ); + + } + + if ( occlusionQueryCount > renderContextData.occlusionQueryIndex ) { + + renderContextData.currentPass.endOcclusionQuery(); + + } + + // shadow arrays - Execute bundles for each layer + + const encoder = renderContextData.encoder; + + if ( this._isRenderCameraDepthArray( renderContext ) === true ) { + + const bundles = []; + + for ( let i = 0; i < renderContextData.bundleEncoders.length; i ++ ) { + + const bundleEncoder = renderContextData.bundleEncoders[ i ]; + bundles.push( bundleEncoder.finish() ); + + } + + for ( let i = 0; i < renderContextData.layerDescriptors.length; i ++ ) { + + if ( i < bundles.length ) { + + const layerDescriptor = renderContextData.layerDescriptors[ i ]; + const renderPass = encoder.beginRenderPass( layerDescriptor ); + + if ( renderContext.viewport ) { + + const { x, y, width, height, minDepth, maxDepth } = renderContext.viewportValue; + renderPass.setViewport( x, y, width, height, minDepth, maxDepth ); + + } + + if ( renderContext.scissor ) { + + const { x, y, width, height } = renderContext.scissorValue; + renderPass.setScissorRect( x, y, width, height ); + + } + + renderPass.executeBundles( [ bundles[ i ] ] ); + + renderPass.end(); + + } + + } + + } else if ( renderContextData.currentPass ) { + + renderContextData.currentPass.end(); + + } + + if ( occlusionQueryCount > 0 ) { + + const bufferSize = occlusionQueryCount * 8; // 8 byte entries for query results + + // + + let queryResolveBuffer = this.occludedResolveCache.get( bufferSize ); + + if ( queryResolveBuffer === undefined ) { + + queryResolveBuffer = this.device.createBuffer( + { + size: bufferSize, + usage: GPUBufferUsage.QUERY_RESOLVE | GPUBufferUsage.COPY_SRC + } + ); + + this.occludedResolveCache.set( bufferSize, queryResolveBuffer ); + + } + + // + + const readBuffer = this.device.createBuffer( + { + size: bufferSize, + usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ + } + ); + + // two buffers required here - WebGPU doesn't allow usage of QUERY_RESOLVE & MAP_READ to be combined + renderContextData.encoder.resolveQuerySet( renderContextData.occlusionQuerySet, 0, occlusionQueryCount, queryResolveBuffer, 0 ); + renderContextData.encoder.copyBufferToBuffer( queryResolveBuffer, 0, readBuffer, 0, bufferSize ); + + renderContextData.occlusionQueryBuffer = readBuffer; + + // + + this.resolveOccludedAsync( renderContext ); + + } + + this.device.queue.submit( [ renderContextData.encoder.finish() ] ); + + + // + + if ( renderContext.textures !== null ) { + + const textures = renderContext.textures; + + for ( let i = 0; i < textures.length; i ++ ) { + + const texture = textures[ i ]; + + if ( texture.generateMipmaps === true ) { + + this.textureUtils.generateMipmaps( texture ); + + } + + } + + } + + } + + /** + * Returns `true` if the given 3D object is fully occluded by other + * 3D objects in the scene. + * + * @param {RenderContext} renderContext - The render context. + * @param {Object3D} object - The 3D object to test. + * @return {boolean} Whether the 3D object is fully occluded or not. + */ + isOccluded( renderContext, object ) { + + const renderContextData = this.get( renderContext ); + + return renderContextData.occluded && renderContextData.occluded.has( object ); + + } + + /** + * This method processes the result of occlusion queries and writes it + * into render context data. + * + * @async + * @param {RenderContext} renderContext - The render context. + * @return {Promise} A Promise that resolves when the occlusion query results have been processed. + */ + async resolveOccludedAsync( renderContext ) { + + const renderContextData = this.get( renderContext ); + + // handle occlusion query results + + const { currentOcclusionQueryBuffer, currentOcclusionQueryObjects } = renderContextData; + + if ( currentOcclusionQueryBuffer && currentOcclusionQueryObjects ) { + + const occluded = new WeakSet(); + + renderContextData.currentOcclusionQueryObjects = null; + renderContextData.currentOcclusionQueryBuffer = null; + + await currentOcclusionQueryBuffer.mapAsync( GPUMapMode.READ ); + + const buffer = currentOcclusionQueryBuffer.getMappedRange(); + const results = new BigUint64Array( buffer ); + + for ( let i = 0; i < currentOcclusionQueryObjects.length; i ++ ) { + + if ( results[ i ] === BigInt( 0 ) ) { + + occluded.add( currentOcclusionQueryObjects[ i ] ); + + } + + } + + currentOcclusionQueryBuffer.destroy(); + + renderContextData.occluded = occluded; + + } + + } + + /** + * Updates the viewport with the values from the given render context. + * + * @param {RenderContext} renderContext - The render context. + */ + updateViewport( renderContext ) { + + const { currentPass } = this.get( renderContext ); + const { x, y, width, height, minDepth, maxDepth } = renderContext.viewportValue; + + currentPass.setViewport( x, y, width, height, minDepth, maxDepth ); + + } + + /** + * Returns the clear color and alpha into a single + * color object. + * + * @return {Color4} The clear color. + */ + getClearColor() { + + const clearColor = super.getClearColor(); + + // only premultiply alpha when alphaMode is "premultiplied" + + if ( this.renderer.alpha === true ) { + + clearColor.r *= clearColor.a; + clearColor.g *= clearColor.a; + clearColor.b *= clearColor.a; + + } + + return clearColor; + + } + + /** + * Performs a clear operation. + * + * @param {boolean} color - Whether the color buffer should be cleared or not. + * @param {boolean} depth - Whether the depth buffer should be cleared or not. + * @param {boolean} stencil - Whether the stencil buffer should be cleared or not. + * @param {?RenderContext} [renderTargetContext=null] - The render context of the current set render target. + */ + clear( color, depth, stencil, renderTargetContext = null ) { + + const device = this.device; + const renderer = this.renderer; + + let colorAttachments = []; + let depthStencilAttachment; + let clearValue; + + let supportsDepth; + let supportsStencil; + + if ( color ) { + + const clearColor = this.getClearColor(); + clearValue = { r: clearColor.r, g: clearColor.g, b: clearColor.b, a: clearColor.a }; + + } + + if ( renderTargetContext === null ) { + + supportsDepth = renderer.depth; + supportsStencil = renderer.stencil; + + const descriptor = this._getDefaultRenderPassDescriptor(); + + if ( color ) { + + colorAttachments = descriptor.colorAttachments; + + const colorAttachment = colorAttachments[ 0 ]; + + colorAttachment.clearValue = clearValue; + colorAttachment.loadOp = GPULoadOp.Clear; + colorAttachment.storeOp = GPUStoreOp.Store; + + } + + if ( supportsDepth || supportsStencil ) { + + depthStencilAttachment = descriptor.depthStencilAttachment; + + } + + } else { + + supportsDepth = renderTargetContext.depth; + supportsStencil = renderTargetContext.stencil; + + const clearConfig = { + loadOp: color ? GPULoadOp.Clear : GPULoadOp.Load, + clearValue: color ? clearValue : undefined + }; + + if ( supportsDepth ) { + + clearConfig.depthLoadOp = depth ? GPULoadOp.Clear : GPULoadOp.Load; + clearConfig.depthClearValue = depth ? renderer.getClearDepth() : undefined; + clearConfig.depthStoreOp = GPUStoreOp.Store; + + } + + if ( supportsStencil ) { + + clearConfig.stencilLoadOp = stencil ? GPULoadOp.Clear : GPULoadOp.Load; + clearConfig.stencilClearValue = stencil ? renderer.getClearStencil() : undefined; + clearConfig.stencilStoreOp = GPUStoreOp.Store; + + } + + const descriptor = this._getRenderPassDescriptor( renderTargetContext, clearConfig ); + + colorAttachments = descriptor.colorAttachments; + depthStencilAttachment = descriptor.depthStencilAttachment; + + } + + if ( supportsDepth && depthStencilAttachment && depthStencilAttachment.depthLoadOp === undefined ) { + + if ( depth ) { + + depthStencilAttachment.depthLoadOp = GPULoadOp.Clear; + depthStencilAttachment.depthClearValue = renderer.getClearDepth(); + depthStencilAttachment.depthStoreOp = GPUStoreOp.Store; + + } else { + + depthStencilAttachment.depthLoadOp = GPULoadOp.Load; + depthStencilAttachment.depthStoreOp = GPUStoreOp.Store; + + } + + } + + // + + if ( supportsStencil && depthStencilAttachment && depthStencilAttachment.stencilLoadOp === undefined ) { + + if ( stencil ) { + + depthStencilAttachment.stencilLoadOp = GPULoadOp.Clear; + depthStencilAttachment.stencilClearValue = renderer.getClearStencil(); + depthStencilAttachment.stencilStoreOp = GPUStoreOp.Store; + + } else { + + depthStencilAttachment.stencilLoadOp = GPULoadOp.Load; + depthStencilAttachment.stencilStoreOp = GPUStoreOp.Store; + + } + + } + + // + + const encoder = device.createCommandEncoder( { label: 'clear' } ); + const currentPass = encoder.beginRenderPass( { + colorAttachments, + depthStencilAttachment + } ); + + currentPass.end(); + + device.queue.submit( [ encoder.finish() ] ); + + } + + // compute + + /** + * This method is executed at the beginning of a compute call and + * prepares the state for upcoming compute tasks. + * + * @param {Node|Array} computeGroup - The compute node(s). + */ + beginCompute( computeGroup ) { + + const groupGPU = this.get( computeGroup ); + + + const descriptor = { + label: 'computeGroup_' + computeGroup.id + }; + + this.initTimestampQuery( computeGroup, descriptor ); + + groupGPU.cmdEncoderGPU = this.device.createCommandEncoder( { label: 'computeGroup_' + computeGroup.id } ); + + groupGPU.passEncoderGPU = groupGPU.cmdEncoderGPU.beginComputePass( descriptor ); + + } + + /** + * Executes a compute command for the given compute node. + * + * @param {Node|Array} computeGroup - The group of compute nodes of a compute call. Can be a single compute node. + * @param {Node} computeNode - The compute node. + * @param {Array} bindings - The bindings. + * @param {ComputePipeline} pipeline - The compute pipeline. + */ + compute( computeGroup, computeNode, bindings, pipeline ) { + + const { passEncoderGPU } = this.get( computeGroup ); + + // pipeline + + const pipelineGPU = this.get( pipeline ).pipeline; + + this.pipelineUtils.setPipeline( passEncoderGPU, pipelineGPU ); + + // bind groups + + for ( let i = 0, l = bindings.length; i < l; i ++ ) { + + const bindGroup = bindings[ i ]; + const bindingsData = this.get( bindGroup ); + + passEncoderGPU.setBindGroup( i, bindingsData.group ); + + } + + const maxComputeWorkgroupsPerDimension = this.device.limits.maxComputeWorkgroupsPerDimension; + + const computeNodeData = this.get( computeNode ); + + if ( computeNodeData.dispatchSize === undefined ) computeNodeData.dispatchSize = { x: 0, y: 1, z: 1 }; + + const { dispatchSize } = computeNodeData; + + if ( computeNode.dispatchCount > maxComputeWorkgroupsPerDimension ) { + + dispatchSize.x = Math.min( computeNode.dispatchCount, maxComputeWorkgroupsPerDimension ); + dispatchSize.y = Math.ceil( computeNode.dispatchCount / maxComputeWorkgroupsPerDimension ); + + } else { + + dispatchSize.x = computeNode.dispatchCount; + + } + + passEncoderGPU.dispatchWorkgroups( + dispatchSize.x, + dispatchSize.y, + dispatchSize.z + ); + + } + + /** + * This method is executed at the end of a compute call and + * finalizes work after compute tasks. + * + * @param {Node|Array} computeGroup - The compute node(s). + */ + finishCompute( computeGroup ) { + + const groupData = this.get( computeGroup ); + + groupData.passEncoderGPU.end(); + + this.device.queue.submit( [ groupData.cmdEncoderGPU.finish() ] ); + + } + + /** + * Can be used to synchronize CPU operations with GPU tasks. So when this method is called, + * the CPU waits for the GPU to complete its operation (e.g. a compute task). + * + * @async + * @return {Promise} A Promise that resolves when synchronization has been finished. + */ + async waitForGPU() { + + await this.device.queue.onSubmittedWorkDone(); + + } + + // render object + + /** + * Executes a draw command for the given render object. + * + * @param {RenderObject} renderObject - The render object to draw. + * @param {Info} info - Holds a series of statistical information about the GPU memory and the rendering process. + */ + draw( renderObject, info ) { + + const { object, material, context, pipeline } = renderObject; + const bindings = renderObject.getBindings(); + const renderContextData = this.get( context ); + const pipelineGPU = this.get( pipeline ).pipeline; + + const index = renderObject.getIndex(); + const hasIndex = ( index !== null ); + + + const drawParams = renderObject.getDrawParameters(); + if ( drawParams === null ) return; + + // pipeline + + const setPipelineAndBindings = ( passEncoderGPU, currentSets ) => { + + // pipeline + this.pipelineUtils.setPipeline( passEncoderGPU, pipelineGPU ); + currentSets.pipeline = pipelineGPU; + + // bind groups + const currentBindingGroups = currentSets.bindingGroups; + for ( let i = 0, l = bindings.length; i < l; i ++ ) { + + const bindGroup = bindings[ i ]; + const bindingsData = this.get( bindGroup ); + if ( currentBindingGroups[ bindGroup.index ] !== bindGroup.id ) { + + passEncoderGPU.setBindGroup( bindGroup.index, bindingsData.group ); + currentBindingGroups[ bindGroup.index ] = bindGroup.id; + + } + + } + + // attributes + + // index + + if ( hasIndex === true ) { + + if ( currentSets.index !== index ) { + + const buffer = this.get( index ).buffer; + const indexFormat = ( index.array instanceof Uint16Array ) ? GPUIndexFormat.Uint16 : GPUIndexFormat.Uint32; + + passEncoderGPU.setIndexBuffer( buffer, indexFormat ); + + currentSets.index = index; + + } + + } + // vertex buffers + + const vertexBuffers = renderObject.getVertexBuffers(); + + for ( let i = 0, l = vertexBuffers.length; i < l; i ++ ) { + + const vertexBuffer = vertexBuffers[ i ]; + + if ( currentSets.attributes[ i ] !== vertexBuffer ) { + + const buffer = this.get( vertexBuffer ).buffer; + passEncoderGPU.setVertexBuffer( i, buffer ); + + currentSets.attributes[ i ] = vertexBuffer; + + } + + } + // stencil + + if ( context.stencil === true && material.stencilWrite === true && renderContextData.currentStencilRef !== material.stencilRef ) { + + passEncoderGPU.setStencilReference( material.stencilRef ); + renderContextData.currentStencilRef = material.stencilRef; + + } + + + }; + + // Define draw function + const draw = ( passEncoderGPU, currentSets ) => { + + setPipelineAndBindings( passEncoderGPU, currentSets ); + + if ( object.isBatchedMesh === true ) { + + const starts = object._multiDrawStarts; + const counts = object._multiDrawCounts; + const drawCount = object._multiDrawCount; + const drawInstances = object._multiDrawInstances; + + if ( drawInstances !== null ) { + + // @deprecated, r174 + warnOnce( 'THREE.WebGPUBackend: renderMultiDrawInstances has been deprecated and will be removed in r184. Append to renderMultiDraw arguments and use indirection.' ); + + } + + for ( let i = 0; i < drawCount; i ++ ) { + + const count = drawInstances ? drawInstances[ i ] : 1; + const firstInstance = count > 1 ? 0 : i; + + if ( hasIndex === true ) { + + passEncoderGPU.drawIndexed( counts[ i ], count, starts[ i ] / index.array.BYTES_PER_ELEMENT, 0, firstInstance ); + + } else { + + passEncoderGPU.draw( counts[ i ], count, starts[ i ], firstInstance ); + + } + + info.update( object, counts[ i ], count ); + + } + + } else if ( hasIndex === true ) { + + const { vertexCount: indexCount, instanceCount, firstVertex: firstIndex } = drawParams; + + const indirect = renderObject.getIndirect(); + + if ( indirect !== null ) { + + const buffer = this.get( indirect ).buffer; + + passEncoderGPU.drawIndexedIndirect( buffer, 0 ); + + } else { + + passEncoderGPU.drawIndexed( indexCount, instanceCount, firstIndex, 0, 0 ); + + } + + info.update( object, indexCount, instanceCount ); + + } else { + + const { vertexCount, instanceCount, firstVertex } = drawParams; + + const indirect = renderObject.getIndirect(); + + if ( indirect !== null ) { + + const buffer = this.get( indirect ).buffer; + + passEncoderGPU.drawIndirect( buffer, 0 ); + + } else { + + passEncoderGPU.draw( vertexCount, instanceCount, firstVertex, 0 ); + + } + + info.update( object, vertexCount, instanceCount ); + + } + + }; + + if ( renderObject.camera.isArrayCamera && renderObject.camera.cameras.length > 0 ) { + + const cameraData = this.get( renderObject.camera ); + const cameras = renderObject.camera.cameras; + const cameraIndex = renderObject.getBindingGroup( 'cameraIndex' ); + + if ( cameraData.indexesGPU === undefined || cameraData.indexesGPU.length !== cameras.length ) { + + const bindingsData = this.get( cameraIndex ); + const indexesGPU = []; + + const data = new Uint32Array( [ 0, 0, 0, 0 ] ); + + for ( let i = 0, len = cameras.length; i < len; i ++ ) { + + data[ 0 ] = i; + + const bindGroupIndex = this.bindingUtils.createBindGroupIndex( data, bindingsData.layout ); + + indexesGPU.push( bindGroupIndex ); + + } + + cameraData.indexesGPU = indexesGPU; // TODO: Create a global library for this + + } + + const pixelRatio = this.renderer.getPixelRatio(); + + for ( let i = 0, len = cameras.length; i < len; i ++ ) { + + const subCamera = cameras[ i ]; + + if ( object.layers.test( subCamera.layers ) ) { + + const vp = subCamera.viewport; + + + + let pass = renderContextData.currentPass; + let sets = renderContextData.currentSets; + if ( renderContextData.bundleEncoders ) { + + const bundleEncoder = renderContextData.bundleEncoders[ i ]; + const bundleSets = renderContextData.bundleSets[ i ]; + pass = bundleEncoder; + sets = bundleSets; + + } + + + + if ( vp ) { + + pass.setViewport( + Math.floor( vp.x * pixelRatio ), + Math.floor( vp.y * pixelRatio ), + Math.floor( vp.width * pixelRatio ), + Math.floor( vp.height * pixelRatio ), + context.viewportValue.minDepth, + context.viewportValue.maxDepth + ); + + } + + + // Set camera index binding for this layer + if ( cameraIndex && cameraData.indexesGPU ) { + + pass.setBindGroup( cameraIndex.index, cameraData.indexesGPU[ i ] ); + sets.bindingGroups[ cameraIndex.index ] = cameraIndex.id; + + } + + draw( pass, sets ); + + + } + + } + + } else { + + // Regular single camera rendering + if ( renderContextData.currentPass ) { + + // Handle occlusion queries + if ( renderContextData.occlusionQuerySet !== undefined ) { + + const lastObject = renderContextData.lastOcclusionObject; + if ( lastObject !== object ) { + + if ( lastObject !== null && lastObject.occlusionTest === true ) { + + renderContextData.currentPass.endOcclusionQuery(); + renderContextData.occlusionQueryIndex ++; + + } + + if ( object.occlusionTest === true ) { + + renderContextData.currentPass.beginOcclusionQuery( renderContextData.occlusionQueryIndex ); + renderContextData.occlusionQueryObjects[ renderContextData.occlusionQueryIndex ] = object; + + } + + renderContextData.lastOcclusionObject = object; + + } + + } + + draw( renderContextData.currentPass, renderContextData.currentSets ); + + } + + } + + } + + // cache key + + /** + * Returns `true` if the render pipeline requires an update. + * + * @param {RenderObject} renderObject - The render object. + * @return {boolean} Whether the render pipeline requires an update or not. + */ + needsRenderUpdate( renderObject ) { + + const data = this.get( renderObject ); + + const { object, material } = renderObject; + + const utils = this.utils; + + const sampleCount = utils.getSampleCountRenderContext( renderObject.context ); + const colorSpace = utils.getCurrentColorSpace( renderObject.context ); + const colorFormat = utils.getCurrentColorFormat( renderObject.context ); + const depthStencilFormat = utils.getCurrentDepthStencilFormat( renderObject.context ); + const primitiveTopology = utils.getPrimitiveTopology( object, material ); + + let needsUpdate = false; + + if ( data.material !== material || data.materialVersion !== material.version || + data.transparent !== material.transparent || data.blending !== material.blending || data.premultipliedAlpha !== material.premultipliedAlpha || + data.blendSrc !== material.blendSrc || data.blendDst !== material.blendDst || data.blendEquation !== material.blendEquation || + data.blendSrcAlpha !== material.blendSrcAlpha || data.blendDstAlpha !== material.blendDstAlpha || data.blendEquationAlpha !== material.blendEquationAlpha || + data.colorWrite !== material.colorWrite || data.depthWrite !== material.depthWrite || data.depthTest !== material.depthTest || data.depthFunc !== material.depthFunc || + data.stencilWrite !== material.stencilWrite || data.stencilFunc !== material.stencilFunc || + data.stencilFail !== material.stencilFail || data.stencilZFail !== material.stencilZFail || data.stencilZPass !== material.stencilZPass || + data.stencilFuncMask !== material.stencilFuncMask || data.stencilWriteMask !== material.stencilWriteMask || + data.side !== material.side || data.alphaToCoverage !== material.alphaToCoverage || + data.sampleCount !== sampleCount || data.colorSpace !== colorSpace || + data.colorFormat !== colorFormat || data.depthStencilFormat !== depthStencilFormat || + data.primitiveTopology !== primitiveTopology || + data.clippingContextCacheKey !== renderObject.clippingContextCacheKey + ) { + + data.material = material; data.materialVersion = material.version; + data.transparent = material.transparent; data.blending = material.blending; data.premultipliedAlpha = material.premultipliedAlpha; + data.blendSrc = material.blendSrc; data.blendDst = material.blendDst; data.blendEquation = material.blendEquation; + data.blendSrcAlpha = material.blendSrcAlpha; data.blendDstAlpha = material.blendDstAlpha; data.blendEquationAlpha = material.blendEquationAlpha; + data.colorWrite = material.colorWrite; + data.depthWrite = material.depthWrite; data.depthTest = material.depthTest; data.depthFunc = material.depthFunc; + data.stencilWrite = material.stencilWrite; data.stencilFunc = material.stencilFunc; + data.stencilFail = material.stencilFail; data.stencilZFail = material.stencilZFail; data.stencilZPass = material.stencilZPass; + data.stencilFuncMask = material.stencilFuncMask; data.stencilWriteMask = material.stencilWriteMask; + data.side = material.side; data.alphaToCoverage = material.alphaToCoverage; + data.sampleCount = sampleCount; + data.colorSpace = colorSpace; + data.colorFormat = colorFormat; + data.depthStencilFormat = depthStencilFormat; + data.primitiveTopology = primitiveTopology; + data.clippingContextCacheKey = renderObject.clippingContextCacheKey; + + needsUpdate = true; + + } + + return needsUpdate; + + } + + /** + * Returns a cache key that is used to identify render pipelines. + * + * @param {RenderObject} renderObject - The render object. + * @return {string} The cache key. + */ + getRenderCacheKey( renderObject ) { + + const { object, material } = renderObject; + + const utils = this.utils; + const renderContext = renderObject.context; + + return [ + material.transparent, material.blending, material.premultipliedAlpha, + material.blendSrc, material.blendDst, material.blendEquation, + material.blendSrcAlpha, material.blendDstAlpha, material.blendEquationAlpha, + material.colorWrite, + material.depthWrite, material.depthTest, material.depthFunc, + material.stencilWrite, material.stencilFunc, + material.stencilFail, material.stencilZFail, material.stencilZPass, + material.stencilFuncMask, material.stencilWriteMask, + material.side, + utils.getSampleCountRenderContext( renderContext ), + utils.getCurrentColorSpace( renderContext ), utils.getCurrentColorFormat( renderContext ), utils.getCurrentDepthStencilFormat( renderContext ), + utils.getPrimitiveTopology( object, material ), + renderObject.getGeometryCacheKey(), + renderObject.clippingContextCacheKey + ].join(); + + } + + // textures + + /** + * Creates a GPU sampler for the given texture. + * + * @param {Texture} texture - The texture to create the sampler for. + */ + createSampler( texture ) { + + this.textureUtils.createSampler( texture ); + + } + + /** + * Destroys the GPU sampler for the given texture. + * + * @param {Texture} texture - The texture to destroy the sampler for. + */ + destroySampler( texture ) { + + this.textureUtils.destroySampler( texture ); + + } + + /** + * Creates a default texture for the given texture that can be used + * as a placeholder until the actual texture is ready for usage. + * + * @param {Texture} texture - The texture to create a default texture for. + */ + createDefaultTexture( texture ) { + + this.textureUtils.createDefaultTexture( texture ); + + } + + /** + * Defines a texture on the GPU for the given texture object. + * + * @param {Texture} texture - The texture. + * @param {Object} [options={}] - Optional configuration parameter. + */ + createTexture( texture, options ) { + + this.textureUtils.createTexture( texture, options ); + + } + + /** + * Uploads the updated texture data to the GPU. + * + * @param {Texture} texture - The texture. + * @param {Object} [options={}] - Optional configuration parameter. + */ + updateTexture( texture, options ) { + + this.textureUtils.updateTexture( texture, options ); + + } + + /** + * Generates mipmaps for the given texture. + * + * @param {Texture} texture - The texture. + */ + generateMipmaps( texture ) { + + this.textureUtils.generateMipmaps( texture ); + + } + + /** + * Destroys the GPU data for the given texture object. + * + * @param {Texture} texture - The texture. + */ + destroyTexture( texture ) { + + this.textureUtils.destroyTexture( texture ); + + } + + /** + * Returns texture data as a typed array. + * + * @async + * @param {Texture} texture - The texture to copy. + * @param {number} x - The x coordinate of the copy origin. + * @param {number} y - The y coordinate of the copy origin. + * @param {number} width - The width of the copy. + * @param {number} height - The height of the copy. + * @param {number} faceIndex - The face index. + * @return {Promise} A Promise that resolves with a typed array when the copy operation has finished. + */ + async copyTextureToBuffer( texture, x, y, width, height, faceIndex ) { + + return this.textureUtils.copyTextureToBuffer( texture, x, y, width, height, faceIndex ); + + } + + /** + * Inits a time stamp query for the given render context. + * + * @param {RenderContext} renderContext - The render context. + * @param {Object} descriptor - The query descriptor. + */ + initTimestampQuery( renderContext, descriptor ) { + + if ( ! this.trackTimestamp ) return; + + const type = renderContext.isComputeNode ? 'compute' : 'render'; + + if ( ! this.timestampQueryPool[ type ] ) { + + // TODO: Variable maxQueries? + this.timestampQueryPool[ type ] = new WebGPUTimestampQueryPool( this.device, type, 2048 ); + + } + + const timestampQueryPool = this.timestampQueryPool[ type ]; + + const baseOffset = timestampQueryPool.allocateQueriesForContext( renderContext ); + + descriptor.timestampWrites = { + querySet: timestampQueryPool.querySet, + beginningOfPassWriteIndex: baseOffset, + endOfPassWriteIndex: baseOffset + 1, + }; + + } + + + // node builder + + /** + * Returns a node builder for the given render object. + * + * @param {RenderObject} object - The render object. + * @param {Renderer} renderer - The renderer. + * @return {WGSLNodeBuilder} The node builder. + */ + createNodeBuilder( object, renderer ) { + + return new WGSLNodeBuilder( object, renderer ); + + } + + // program + + /** + * Creates a shader program from the given programmable stage. + * + * @param {ProgrammableStage} program - The programmable stage. + */ + createProgram( program ) { + + const programGPU = this.get( program ); + + programGPU.module = { + module: this.device.createShaderModule( { code: program.code, label: program.stage + ( program.name !== '' ? `_${ program.name }` : '' ) } ), + entryPoint: 'main' + }; + + } + + /** + * Destroys the shader program of the given programmable stage. + * + * @param {ProgrammableStage} program - The programmable stage. + */ + destroyProgram( program ) { + + this.delete( program ); + + } + + // pipelines + + /** + * Creates a render pipeline for the given render object. + * + * @param {RenderObject} renderObject - The render object. + * @param {Array} promises - An array of compilation promises which are used in `compileAsync()`. + */ + createRenderPipeline( renderObject, promises ) { + + this.pipelineUtils.createRenderPipeline( renderObject, promises ); + + } + + /** + * Creates a compute pipeline for the given compute node. + * + * @param {ComputePipeline} computePipeline - The compute pipeline. + * @param {Array} bindings - The bindings. + */ + createComputePipeline( computePipeline, bindings ) { + + this.pipelineUtils.createComputePipeline( computePipeline, bindings ); + + } + + /** + * Prepares the state for encoding render bundles. + * + * @param {RenderContext} renderContext - The render context. + */ + beginBundle( renderContext ) { + + const renderContextData = this.get( renderContext ); + + renderContextData._currentPass = renderContextData.currentPass; + renderContextData._currentSets = renderContextData.currentSets; + + renderContextData.currentSets = { attributes: {}, bindingGroups: [], pipeline: null, index: null }; + renderContextData.currentPass = this.pipelineUtils.createBundleEncoder( renderContext ); + + } + + /** + * After processing render bundles this method finalizes related work. + * + * @param {RenderContext} renderContext - The render context. + * @param {RenderBundle} bundle - The render bundle. + */ + finishBundle( renderContext, bundle ) { + + const renderContextData = this.get( renderContext ); + + const bundleEncoder = renderContextData.currentPass; + const bundleGPU = bundleEncoder.finish(); + + this.get( bundle ).bundleGPU = bundleGPU; + + // restore render pass state + + renderContextData.currentSets = renderContextData._currentSets; + renderContextData.currentPass = renderContextData._currentPass; + + } + + /** + * Adds a render bundle to the render context data. + * + * @param {RenderContext} renderContext - The render context. + * @param {RenderBundle} bundle - The render bundle to add. + */ + addBundle( renderContext, bundle ) { + + const renderContextData = this.get( renderContext ); + + renderContextData.renderBundles.push( this.get( bundle ).bundleGPU ); + + } + + // bindings + + /** + * Creates bindings from the given bind group definition. + * + * @param {BindGroup} bindGroup - The bind group. + * @param {Array} bindings - Array of bind groups. + * @param {number} cacheIndex - The cache index. + * @param {number} version - The version. + */ + createBindings( bindGroup, bindings, cacheIndex, version ) { + + this.bindingUtils.createBindings( bindGroup, bindings, cacheIndex, version ); + + } + + /** + * Updates the given bind group definition. + * + * @param {BindGroup} bindGroup - The bind group. + * @param {Array} bindings - Array of bind groups. + * @param {number} cacheIndex - The cache index. + * @param {number} version - The version. + */ + updateBindings( bindGroup, bindings, cacheIndex, version ) { + + this.bindingUtils.createBindings( bindGroup, bindings, cacheIndex, version ); + + } + + /** + * Updates a buffer binding. + * + * @param {Buffer} binding - The buffer binding to update. + */ + updateBinding( binding ) { + + this.bindingUtils.updateBinding( binding ); + + } + + // attributes + + /** + * Creates the buffer of an indexed shader attribute. + * + * @param {BufferAttribute} attribute - The indexed buffer attribute. + */ + createIndexAttribute( attribute ) { + + let usage = GPUBufferUsage.INDEX | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST; + + if ( attribute.isStorageBufferAttribute || attribute.isStorageInstancedBufferAttribute ) { + + usage |= GPUBufferUsage.STORAGE; + + } + + this.attributeUtils.createAttribute( attribute, usage ); + + } + + /** + * Creates the GPU buffer of a shader attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute. + */ + createAttribute( attribute ) { + + this.attributeUtils.createAttribute( attribute, GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST ); + + } + + /** + * Creates the GPU buffer of a storage attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute. + */ + createStorageAttribute( attribute ) { + + this.attributeUtils.createAttribute( attribute, GPUBufferUsage.STORAGE | GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST ); + + } + + /** + * Creates the GPU buffer of an indirect storage attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute. + */ + createIndirectStorageAttribute( attribute ) { + + this.attributeUtils.createAttribute( attribute, GPUBufferUsage.STORAGE | GPUBufferUsage.INDIRECT | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST ); + + } + + /** + * Updates the GPU buffer of a shader attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute to update. + */ + updateAttribute( attribute ) { + + this.attributeUtils.updateAttribute( attribute ); + + } + + /** + * Destroys the GPU buffer of a shader attribute. + * + * @param {BufferAttribute} attribute - The buffer attribute to destroy. + */ + destroyAttribute( attribute ) { + + this.attributeUtils.destroyAttribute( attribute ); + + } + + // canvas + + /** + * Triggers an update of the default render pass descriptor. + */ + updateSize() { + + this.colorBuffer = this.textureUtils.getColorBuffer(); + this.defaultRenderPassdescriptor = null; + + } + + // utils public + + /** + * Returns the maximum anisotropy texture filtering value. + * + * @return {number} The maximum anisotropy texture filtering value. + */ + getMaxAnisotropy() { + + return 16; + + } + + /** + * Checks if the given feature is supported by the backend. + * + * @param {string} name - The feature's name. + * @return {boolean} Whether the feature is supported or not. + */ + hasFeature( name ) { + + return this.device.features.has( name ); + + } + + /** + * Copies data of the given source texture to the given destination texture. + * + * @param {Texture} srcTexture - The source texture. + * @param {Texture} dstTexture - The destination texture. + * @param {?(Box3|Box2)} [srcRegion=null] - The region of the source texture to copy. + * @param {?(Vector2|Vector3)} [dstPosition=null] - The destination position of the copy. + * @param {number} [srcLevel=0] - The mipmap level to copy. + * @param {number} [dstLevel=0] - The destination mip level to copy to. + */ + copyTextureToTexture( srcTexture, dstTexture, srcRegion = null, dstPosition = null, srcLevel = 0, dstLevel = 0 ) { + + let dstX = 0; + let dstY = 0; + let dstZ = 0; + + let srcX = 0; + let srcY = 0; + let srcZ = 0; + + let srcWidth = srcTexture.image.width; + let srcHeight = srcTexture.image.height; + let srcDepth = 1; + + + if ( srcRegion !== null ) { + + if ( srcRegion.isBox3 === true ) { + + srcX = srcRegion.min.x; + srcY = srcRegion.min.y; + srcZ = srcRegion.min.z; + srcWidth = srcRegion.max.x - srcRegion.min.x; + srcHeight = srcRegion.max.y - srcRegion.min.y; + srcDepth = srcRegion.max.z - srcRegion.min.z; + + } else { + + // Assume it's a Box2 + srcX = srcRegion.min.x; + srcY = srcRegion.min.y; + srcWidth = srcRegion.max.x - srcRegion.min.x; + srcHeight = srcRegion.max.y - srcRegion.min.y; + srcDepth = 1; + + } + + } + + + if ( dstPosition !== null ) { + + dstX = dstPosition.x; + dstY = dstPosition.y; + dstZ = dstPosition.z || 0; + + } + + const encoder = this.device.createCommandEncoder( { label: 'copyTextureToTexture_' + srcTexture.id + '_' + dstTexture.id } ); + + const sourceGPU = this.get( srcTexture ).texture; + const destinationGPU = this.get( dstTexture ).texture; + + encoder.copyTextureToTexture( + { + texture: sourceGPU, + mipLevel: srcLevel, + origin: { x: srcX, y: srcY, z: srcZ } + }, + { + texture: destinationGPU, + mipLevel: dstLevel, + origin: { x: dstX, y: dstY, z: dstZ } + }, + [ + srcWidth, + srcHeight, + srcDepth + ] + ); + + this.device.queue.submit( [ encoder.finish() ] ); + + if ( dstLevel === 0 && dstTexture.generateMipmaps ) { + + this.textureUtils.generateMipmaps( dstTexture ); + + } + + } + + /** + * Copies the current bound framebuffer to the given texture. + * + * @param {Texture} texture - The destination texture. + * @param {RenderContext} renderContext - The render context. + * @param {Vector4} rectangle - A four dimensional vector defining the origin and dimension of the copy. + */ + copyFramebufferToTexture( texture, renderContext, rectangle ) { + + const renderContextData = this.get( renderContext ); + + let sourceGPU = null; + + if ( renderContext.renderTarget ) { + + if ( texture.isDepthTexture ) { + + sourceGPU = this.get( renderContext.depthTexture ).texture; + + } else { + + sourceGPU = this.get( renderContext.textures[ 0 ] ).texture; + + } + + } else { + + if ( texture.isDepthTexture ) { + + sourceGPU = this.textureUtils.getDepthBuffer( renderContext.depth, renderContext.stencil ); + + } else { + + sourceGPU = this.context.getCurrentTexture(); + + } + + } + + const destinationGPU = this.get( texture ).texture; + + if ( sourceGPU.format !== destinationGPU.format ) { + + console.error( 'WebGPUBackend: copyFramebufferToTexture: Source and destination formats do not match.', sourceGPU.format, destinationGPU.format ); + + return; + + } + + let encoder; + + if ( renderContextData.currentPass ) { + + renderContextData.currentPass.end(); + + encoder = renderContextData.encoder; + + } else { + + encoder = this.device.createCommandEncoder( { label: 'copyFramebufferToTexture_' + texture.id } ); + + } + + encoder.copyTextureToTexture( + { + texture: sourceGPU, + origin: [ rectangle.x, rectangle.y, 0 ], + }, + { + texture: destinationGPU + }, + [ + rectangle.z, + rectangle.w + ] + ); + + if ( renderContextData.currentPass ) { + + const { descriptor } = renderContextData; + + for ( let i = 0; i < descriptor.colorAttachments.length; i ++ ) { + + descriptor.colorAttachments[ i ].loadOp = GPULoadOp.Load; + + } + + if ( renderContext.depth ) descriptor.depthStencilAttachment.depthLoadOp = GPULoadOp.Load; + if ( renderContext.stencil ) descriptor.depthStencilAttachment.stencilLoadOp = GPULoadOp.Load; + + renderContextData.currentPass = encoder.beginRenderPass( descriptor ); + renderContextData.currentSets = { attributes: {}, bindingGroups: [], pipeline: null, index: null }; + + if ( renderContext.viewport ) { + + this.updateViewport( renderContext ); + + } + + if ( renderContext.scissor ) { + + const { x, y, width, height } = renderContext.scissorValue; + + renderContextData.currentPass.setScissorRect( x, y, width, height ); + + } + + } else { + + this.device.queue.submit( [ encoder.finish() ] ); + + } + + if ( texture.generateMipmaps ) { + + this.textureUtils.generateMipmaps( texture ); + + } + + } + +} + +/** + * A IES version of {@link SpotLight}. Can only be used with {@link WebGPURenderer}. + * + * @augments SpotLight + */ +class IESSpotLight extends SpotLight { + + /** + * Constructs a new IES spot light. + * + * @param {(number|Color|string)} [color=0xffffff] - The light's color. + * @param {number} [intensity=1] - The light's strength/intensity measured in candela (cd). + * @param {number} [distance=0] - Maximum range of the light. `0` means no limit. + * @param {number} [angle=Math.PI/3] - Maximum angle of light dispersion from its direction whose upper bound is `Math.PI/2`. + * @param {number} [penumbra=0] - Percent of the spotlight cone that is attenuated due to penumbra. Value range is `[0,1]`. + * @param {number} [decay=2] - The amount the light dims along the distance of the light. + */ + constructor( color, intensity, distance, angle, penumbra, decay ) { + + super( color, intensity, distance, angle, penumbra, decay ); + + /** + * TODO + * + * @type {?Texture} + * @default null + */ + this.iesMap = null; + + } + + copy( source, recursive ) { + + super.copy( source, recursive ); + + this.iesMap = source.iesMap; + + return this; + + } + +} + +/** + * A projector light version of {@link SpotLight}. Can only be used with {@link WebGPURenderer}. + * + * @augments SpotLight + */ +class ProjectorLight extends SpotLight { + + /** + * Constructs a new projector light. + * + * @param {(number|Color|string)} [color=0xffffff] - The light's color. + * @param {number} [intensity=1] - The light's strength/intensity measured in candela (cd). + * @param {number} [distance=0] - Maximum range of the light. `0` means no limit. + * @param {number} [angle=Math.PI/3] - Maximum angle of light dispersion from its direction whose upper bound is `Math.PI/2`. + * @param {number} [penumbra=0] - Percent of the spotlight cone that is attenuated due to penumbra. Value range is `[0,1]`. + * @param {number} [decay=2] - The amount the light dims along the distance of the light. + */ + constructor( color, intensity, distance, angle, penumbra, decay ) { + + super( color, intensity, distance, angle, penumbra, decay ); + + /** + * Aspect ratio of the light. Set to `null` to use the texture aspect ratio. + * + * @type {number} + * @default null + */ + this.aspect = null; + + } + + copy( source, recursive ) { + + super.copy( source, recursive ); + + this.aspect = source.aspect; + + return this; + + } + +} + +/** + * This version of a node library represents a basic version + * just focusing on lights and tone mapping techniques. + * + * @private + * @augments NodeLibrary + */ +class BasicNodeLibrary extends NodeLibrary { + + /** + * Constructs a new basic node library. + */ + constructor() { + + super(); + + this.addLight( PointLightNode, PointLight ); + this.addLight( DirectionalLightNode, DirectionalLight ); + this.addLight( RectAreaLightNode, RectAreaLight ); + this.addLight( SpotLightNode, SpotLight ); + this.addLight( AmbientLightNode, AmbientLight ); + this.addLight( HemisphereLightNode, HemisphereLight ); + this.addLight( LightProbeNode, LightProbe ); + this.addLight( IESSpotLightNode, IESSpotLight ); + this.addLight( ProjectorLightNode, ProjectorLight ); + + this.addToneMapping( linearToneMapping, LinearToneMapping ); + this.addToneMapping( reinhardToneMapping, ReinhardToneMapping ); + this.addToneMapping( cineonToneMapping, CineonToneMapping ); + this.addToneMapping( acesFilmicToneMapping, ACESFilmicToneMapping ); + this.addToneMapping( agxToneMapping, AgXToneMapping ); + this.addToneMapping( neutralToneMapping, NeutralToneMapping ); + + } + +} + +/** + * This alternative version of {@link WebGPURenderer} only supports node materials. + * So classes like `MeshBasicMaterial` are not compatible. + * + * @private + * @augments Renderer + */ +class WebGPURenderer extends Renderer { + + /** + * Constructs a new WebGPU renderer. + * + * @param {WebGPURenderer~Options} [parameters] - The configuration parameter. + */ + constructor( parameters = {} ) { + + let BackendClass; + + if ( parameters.forceWebGL ) { + + BackendClass = WebGLBackend; + + } else { + + BackendClass = WebGPUBackend; + + parameters.getFallback = () => { + + console.warn( 'THREE.WebGPURenderer: WebGPU is not available, running under WebGL2 backend.' ); + + return new WebGLBackend( parameters ); + + }; + + } + + const backend = new BackendClass( parameters ); + + super( backend, parameters ); + + /** + * The generic default value is overwritten with the + * standard node library for type mapping. Material + * mapping is not supported with this version. + * + * @type {BasicNodeLibrary} + */ + this.library = new BasicNodeLibrary(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isWebGPURenderer = true; + + } + +} + +/** + * A specialized group which enables applications access to the + * Render Bundle API of WebGPU. The group with all its descendant nodes + * are considered as one render bundle and processed as such by + * the renderer. + * + * This module is only fully supported by `WebGPURenderer` with a WebGPU backend. + * With a WebGL backend, the group can technically be rendered but without + * any performance improvements. + * + * @augments Group + */ +class BundleGroup extends Group { + + /** + * Constructs a new bundle group. + */ + constructor() { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isBundleGroup = true; + + /** + * This property is only relevant for detecting types + * during serialization/deserialization. It should always + * match the class name. + * + * @type {string} + * @readonly + * @default 'BundleGroup' + */ + this.type = 'BundleGroup'; + + /** + * Whether the bundle is static or not. When set to `true`, the structure + * is assumed to be static and does not change. E.g. no new objects are + * added to the group + * + * If a change is required, an update can still be forced by setting the + * `needsUpdate` flag to `true`. + * + * @type {boolean} + * @default true + */ + this.static = true; + + /** + * The bundle group's version. + * + * @type {number} + * @readonly + * @default 0 + */ + this.version = 0; + + } + + /** + * Set this property to `true` when the bundle group has changed. + * + * @type {boolean} + * @default false + * @param {boolean} value + */ + set needsUpdate( value ) { + + if ( value === true ) this.version ++; + + } + +} + +/** + * This module is responsible to manage the post processing setups in apps. + * You usually create a single instance of this class and use it to define + * the output of your post processing effect chain. + * ```js + * const postProcessing = new PostProcessing( renderer ); + * + * const scenePass = pass( scene, camera ); + * + * postProcessing.outputNode = scenePass; + * ``` + * + * Note: This module can only be used with `WebGPURenderer`. + */ +class PostProcessing { + + /** + * Constructs a new post processing management module. + * + * @param {Renderer} renderer - A reference to the renderer. + * @param {Node} outputNode - An optional output node. + */ + constructor( renderer, outputNode = vec4( 0, 0, 1, 1 ) ) { + + /** + * A reference to the renderer. + * + * @type {Renderer} + */ + this.renderer = renderer; + + /** + * A node which defines the final output of the post + * processing. This is usually the last node in a chain + * of effect nodes. + * + * @type {Node} + */ + this.outputNode = outputNode; + + /** + * Whether the default output tone mapping and color + * space transformation should be enabled or not. + * + * It is enabled by default by it must be disabled when + * effects must be executed after tone mapping and color + * space conversion. A typical example is FXAA which + * requires sRGB input. + * + * When set to `false`, the app must control the output + * transformation with `RenderOutputNode`. + * + * ```js + * const outputPass = renderOutput( scenePass ); + * ``` + * + * @type {boolean} + */ + this.outputColorTransform = true; + + /** + * Must be set to `true` when the output node changes. + * + * @type {Node} + */ + this.needsUpdate = true; + + const material = new NodeMaterial(); + material.name = 'PostProcessing'; + + /** + * The full screen quad that is used to render + * the effects. + * + * @private + * @type {QuadMesh} + */ + this._quadMesh = new QuadMesh( material ); + + } + + /** + * When `PostProcessing` is used to apply post processing effects, + * the application must use this version of `render()` inside + * its animation loop (not the one from the renderer). + */ + render() { + + this._update(); + + const renderer = this.renderer; + + const toneMapping = renderer.toneMapping; + const outputColorSpace = renderer.outputColorSpace; + + renderer.toneMapping = NoToneMapping; + renderer.outputColorSpace = LinearSRGBColorSpace; + + // + + const currentXR = renderer.xr.enabled; + renderer.xr.enabled = false; + + this._quadMesh.render( renderer ); + + renderer.xr.enabled = currentXR; + + // + + renderer.toneMapping = toneMapping; + renderer.outputColorSpace = outputColorSpace; + + } + + /** + * Frees internal resources. + */ + dispose() { + + this._quadMesh.material.dispose(); + + } + + /** + * Updates the state of the module. + * + * @private + */ + _update() { + + if ( this.needsUpdate === true ) { + + const renderer = this.renderer; + + const toneMapping = renderer.toneMapping; + const outputColorSpace = renderer.outputColorSpace; + + this._quadMesh.material.fragmentNode = this.outputColorTransform === true ? renderOutput( this.outputNode, toneMapping, outputColorSpace ) : this.outputNode.context( { toneMapping, outputColorSpace } ); + this._quadMesh.material.needsUpdate = true; + + this.needsUpdate = false; + + } + + } + + /** + * When `PostProcessing` is used to apply post processing effects, + * the application must use this version of `renderAsync()` inside + * its animation loop (not the one from the renderer). + * + * @async + * @return {Promise} A Promise that resolves when the render has been finished. + */ + async renderAsync() { + + this._update(); + + const renderer = this.renderer; + + const toneMapping = renderer.toneMapping; + const outputColorSpace = renderer.outputColorSpace; + + renderer.toneMapping = NoToneMapping; + renderer.outputColorSpace = LinearSRGBColorSpace; + + // + + const currentXR = renderer.xr.enabled; + renderer.xr.enabled = false; + + await this._quadMesh.renderAsync( renderer ); + + renderer.xr.enabled = currentXR; + + // + + renderer.toneMapping = toneMapping; + renderer.outputColorSpace = outputColorSpace; + + } + +} + +/** + * This special type of texture is intended for compute shaders. + * It can be used to compute the data of a texture with a compute shader. + * + * Note: This type of texture can only be used with `WebGPURenderer` + * and a WebGPU backend. + * + * @augments Texture + */ +class StorageTexture extends Texture { + + /** + * Constructs a new storage texture. + * + * @param {number} [width=1] - The storage texture's width. + * @param {number} [height=1] - The storage texture's height. + */ + constructor( width = 1, height = 1 ) { + + super(); + + /** + * The image object which just represents the texture's dimension. + * + * @type {{width: number, height: number}} + */ + this.image = { width, height }; + + /** + * The default `magFilter` for storage textures is `THREE.LinearFilter`. + * + * @type {number} + */ + this.magFilter = LinearFilter; + + /** + * The default `minFilter` for storage textures is `THREE.LinearFilter`. + * + * @type {number} + */ + this.minFilter = LinearFilter; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isStorageTexture = true; + + } + +} + +/** + * This special type of buffer attribute is intended for compute shaders. + * It can be used to encode draw parameters for indirect draw calls. + * + * Note: This type of buffer attribute can only be used with `WebGPURenderer` + * and a WebGPU backend. + * + * @augments StorageBufferAttribute + */ +class IndirectStorageBufferAttribute extends StorageBufferAttribute { + + /** + * Constructs a new storage buffer attribute. + * + * @param {number|Uint32Array} count - The item count. It is also valid to pass a `Uint32Array` as an argument. + * The subsequent parameter is then obsolete. + * @param {number} itemSize - The item size. + */ + constructor( count, itemSize ) { + + super( count, itemSize, Uint32Array ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isIndirectStorageBufferAttribute = true; + + } + +} + +/** + * A loader for loading node objects in the three.js JSON Object/Scene format. + * + * @augments Loader + */ +class NodeLoader extends Loader { + + /** + * Constructs a new node loader. + * + * @param {LoadingManager} [manager] - A reference to a loading manager. + */ + constructor( manager ) { + + super( manager ); + + /** + * Represents a dictionary of textures. + * + * @type {Object} + */ + this.textures = {}; + + /** + * Represents a dictionary of node types. + * + * @type {Object} + */ + this.nodes = {}; + + } + + /** + * Loads the node definitions from the given URL. + * + * @param {string} url - The path/URL of the file to be loaded. + * @param {Function} onLoad - Will be called when load completes. + * @param {Function} onProgress - Will be called while load progresses. + * @param {Function} onError - Will be called when errors are thrown during the loading process. + */ + load( url, onLoad, onProgress, onError ) { + + const loader = new FileLoader( this.manager ); + loader.setPath( this.path ); + loader.setRequestHeader( this.requestHeader ); + loader.setWithCredentials( this.withCredentials ); + loader.load( url, ( text ) => { + + try { + + onLoad( this.parse( JSON.parse( text ) ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + this.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + /** + * Parse the node dependencies for the loaded node. + * + * @param {Array} [json] - The JSON definition + * @return {Object} A dictionary with node dependencies. + */ + parseNodes( json ) { + + const nodes = {}; + + if ( json !== undefined ) { + + for ( const nodeJSON of json ) { + + const { uuid, type } = nodeJSON; + + nodes[ uuid ] = this.createNodeFromType( type ); + nodes[ uuid ].uuid = uuid; + + } + + const meta = { nodes, textures: this.textures }; + + for ( const nodeJSON of json ) { + + nodeJSON.meta = meta; + + const node = nodes[ nodeJSON.uuid ]; + node.deserialize( nodeJSON ); + + delete nodeJSON.meta; + + } + + } + + return nodes; + + } + + /** + * Parses the node from the given JSON. + * + * @param {Object} json - The JSON definition + * @param {string} json.type - The node type. + * @param {string} json.uuid - The node UUID. + * @param {Array} [json.nodes] - The node dependencies. + * @param {Object} [json.meta] - The meta data. + * @return {Node} The parsed node. + */ + parse( json ) { + + const node = this.createNodeFromType( json.type ); + node.uuid = json.uuid; + + const nodes = this.parseNodes( json.nodes ); + const meta = { nodes, textures: this.textures }; + + json.meta = meta; + + node.deserialize( json ); + + delete json.meta; + + return node; + + } + + /** + * Defines the dictionary of textures. + * + * @param {Object} value - The texture library defines as ``. + * @return {NodeLoader} A reference to this loader. + */ + setTextures( value ) { + + this.textures = value; + return this; + + } + + /** + * Defines the dictionary of node types. + * + * @param {Object} value - The node library defined as ``. + * @return {NodeLoader} A reference to this loader. + */ + setNodes( value ) { + + this.nodes = value; + return this; + + } + + /** + * Creates a node object from the given type. + * + * @param {string} type - The node type. + * @return {Node} The created node instance. + */ + createNodeFromType( type ) { + + if ( this.nodes[ type ] === undefined ) { + + console.error( 'THREE.NodeLoader: Node type not found:', type ); + return float(); + + } + + return nodeObject( new this.nodes[ type ]() ); + + } + +} + +/** + * A special type of material loader for loading node materials. + * + * @augments MaterialLoader + */ +class NodeMaterialLoader extends MaterialLoader { + + /** + * Constructs a new node material loader. + * + * @param {LoadingManager} [manager] - A reference to a loading manager. + */ + constructor( manager ) { + + super( manager ); + + /** + * Represents a dictionary of node types. + * + * @type {Object} + */ + this.nodes = {}; + + /** + * Represents a dictionary of node material types. + * + * @type {Object} + */ + this.nodeMaterials = {}; + + } + + /** + * Parses the node material from the given JSON. + * + * @param {Object} json - The JSON definition + * @return {NodeMaterial}. The parsed material. + */ + parse( json ) { + + const material = super.parse( json ); + + const nodes = this.nodes; + const inputNodes = json.inputNodes; + + for ( const property in inputNodes ) { + + const uuid = inputNodes[ property ]; + + material[ property ] = nodes[ uuid ]; + + } + + return material; + + } + + /** + * Defines the dictionary of node types. + * + * @param {Object} value - The node library defined as ``. + * @return {NodeLoader} A reference to this loader. + */ + setNodes( value ) { + + this.nodes = value; + return this; + + } + + /** + * Defines the dictionary of node material types. + * + * @param {Object} value - The node material library defined as ``. + * @return {NodeLoader} A reference to this loader. + */ + setNodeMaterials( value ) { + + this.nodeMaterials = value; + return this; + + } + + /** + * Creates a node material from the given type. + * + * @param {string} type - The node material type. + * @return {Node} The created node material instance. + */ + createMaterialFromType( type ) { + + const materialClass = this.nodeMaterials[ type ]; + + if ( materialClass !== undefined ) { + + return new materialClass(); + + } + + return super.createMaterialFromType( type ); + + } + +} + +/** + * A special type of object loader for loading 3D objects using + * node materials. + * + * @augments ObjectLoader + */ +class NodeObjectLoader extends ObjectLoader { + + /** + * Constructs a new node object loader. + * + * @param {LoadingManager} [manager] - A reference to a loading manager. + */ + constructor( manager ) { + + super( manager ); + + /** + * Represents a dictionary of node types. + * + * @type {Object} + */ + this.nodes = {}; + + /** + * Represents a dictionary of node material types. + * + * @type {Object} + */ + this.nodeMaterials = {}; + + /** + * A reference to hold the `nodes` JSON property. + * + * @private + * @type {?Object[]} + */ + this._nodesJSON = null; + + } + + /** + * Defines the dictionary of node types. + * + * @param {Object} value - The node library defined as ``. + * @return {NodeObjectLoader} A reference to this loader. + */ + setNodes( value ) { + + this.nodes = value; + return this; + + } + + /** + * Defines the dictionary of node material types. + * + * @param {Object} value - The node material library defined as ``. + * @return {NodeObjectLoader} A reference to this loader. + */ + setNodeMaterials( value ) { + + this.nodeMaterials = value; + return this; + + } + + /** + * Parses the node objects from the given JSON. + * + * @param {Object} json - The JSON definition + * @param {Function} onLoad - The onLoad callback function. + * @return {Object3D}. The parsed 3D object. + */ + parse( json, onLoad ) { + + this._nodesJSON = json.nodes; + + const data = super.parse( json, onLoad ); + + this._nodesJSON = null; // dispose + + return data; + + } + + /** + * Parses the node objects from the given JSON and textures. + * + * @param {Object[]} json - The JSON definition + * @param {Object} textures - The texture library. + * @return {Object}. The parsed nodes. + */ + parseNodes( json, textures ) { + + if ( json !== undefined ) { + + const loader = new NodeLoader(); + loader.setNodes( this.nodes ); + loader.setTextures( textures ); + + return loader.parseNodes( json ); + + } + + return {}; + + } + + /** + * Parses the node objects from the given JSON and textures. + * + * @param {Object} json - The JSON definition + * @param {Object} textures - The texture library. + * @return {Object}. The parsed materials. + */ + parseMaterials( json, textures ) { + + const materials = {}; + + if ( json !== undefined ) { + + const nodes = this.parseNodes( this._nodesJSON, textures ); + + const loader = new NodeMaterialLoader(); + loader.setTextures( textures ); + loader.setNodes( nodes ); + loader.setNodeMaterials( this.nodeMaterials ); + + for ( let i = 0, l = json.length; i < l; i ++ ) { + + const data = json[ i ]; + + materials[ data.uuid ] = loader.parse( data ); + + } + + } + + return materials; + + } + +} + +/** + * In earlier three.js versions, clipping was defined globally + * on the renderer or on material level. This special version of + * `THREE.Group` allows to encode the clipping state into the scene + * graph. Meaning if you create an instance of this group, all + * descendant 3D objects will be affected by the respective clipping + * planes. + * + * Note: `ClippingGroup` can only be used with `WebGPURenderer`. + * + * @augments Group + */ +class ClippingGroup extends Group { + + /** + * Constructs a new clipping group. + */ + constructor() { + + super(); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isClippingGroup = true; + + /** + * An array with clipping planes. + * + * @type {Array} + */ + this.clippingPlanes = []; + + /** + * Whether clipping should be enabled or not. + * + * @type {boolean} + * @default true + */ + this.enabled = true; + + /** + * Whether the intersection of the clipping planes is used to clip objects, rather than their union. + * + * @type {boolean} + * @default false + */ + this.clipIntersection = false; + + /** + * Whether shadows should be clipped or not. + * + * @type {boolean} + * @default false + */ + this.clipShadows = false; + + } + +} + +export { ACESFilmicToneMapping, AONode, AddEquation, AddOperation, AdditiveBlending, AgXToneMapping, AlphaFormat, AlwaysCompare, AlwaysDepth, AlwaysStencilFunc, AmbientLight, AmbientLightNode, AnalyticLightNode, ArrayCamera, ArrayElementNode, ArrayNode, AssignNode, AttributeNode, BackSide, BasicEnvironmentNode, BasicShadowMap, BatchNode, BoxGeometry, BufferAttribute, BufferAttributeNode, BufferGeometry, BufferNode, BumpMapNode, BundleGroup, BypassNode, ByteType, CacheNode, Camera, CineonToneMapping, ClampToEdgeWrapping, ClippingGroup, CodeNode, Color, ColorManagement, ColorSpaceNode, ComputeNode, ConstNode, ContextNode, ConvertNode, CubeCamera, CubeReflectionMapping, CubeRefractionMapping, CubeTexture, CubeTextureNode, CubeUVReflectionMapping, CullFaceBack, CullFaceFront, CullFaceNone, CustomBlending, CylinderGeometry, DataArrayTexture, DataTexture, DebugNode, DecrementStencilOp, DecrementWrapStencilOp, DepthFormat, DepthStencilFormat, DepthTexture, DirectionalLight, DirectionalLightNode, DoubleSide, DstAlphaFactor, DstColorFactor, DynamicDrawUsage, EnvironmentNode, EqualCompare, EqualDepth, EqualStencilFunc, EquirectangularReflectionMapping, EquirectangularRefractionMapping, Euler, EventDispatcher, ExpressionNode, FileLoader, Float16BufferAttribute, Float32BufferAttribute, FloatType, FramebufferTexture, FrontFacingNode, FrontSide, Frustum, FrustumArray, FunctionCallNode, FunctionNode, FunctionOverloadingNode, GLSLNodeParser, GreaterCompare, GreaterDepth, GreaterEqualCompare, GreaterEqualDepth, GreaterEqualStencilFunc, GreaterStencilFunc, Group, HalfFloatType, HemisphereLight, HemisphereLightNode, IESSpotLight, IESSpotLightNode, IncrementStencilOp, IncrementWrapStencilOp, IndexNode, IndirectStorageBufferAttribute, InstanceNode, InstancedBufferAttribute, InstancedInterleavedBuffer, InstancedMeshNode, IntType, InterleavedBuffer, InterleavedBufferAttribute, InvertStencilOp, IrradianceNode, JoinNode, KeepStencilOp, LessCompare, LessDepth, LessEqualCompare, LessEqualDepth, LessEqualStencilFunc, LessStencilFunc, LightProbe, LightProbeNode, Lighting, LightingContextNode, LightingModel, LightingNode, LightsNode, Line2NodeMaterial, LineBasicMaterial, LineBasicNodeMaterial, LineDashedMaterial, LineDashedNodeMaterial, LinearFilter, LinearMipMapLinearFilter, LinearMipmapLinearFilter, LinearMipmapNearestFilter, LinearSRGBColorSpace, LinearToneMapping, LinearTransfer, Loader, LoopNode, MRTNode, Material, MaterialLoader, MaterialNode, MaterialReferenceNode, MathUtils, Matrix2, Matrix3, Matrix4, MaxEquation, MaxMipLevelNode, MemberNode, Mesh, MeshBasicMaterial, MeshBasicNodeMaterial, MeshLambertMaterial, MeshLambertNodeMaterial, MeshMatcapMaterial, MeshMatcapNodeMaterial, MeshNormalMaterial, MeshNormalNodeMaterial, MeshPhongMaterial, MeshPhongNodeMaterial, MeshPhysicalMaterial, MeshPhysicalNodeMaterial, MeshSSSNodeMaterial, MeshStandardMaterial, MeshStandardNodeMaterial, MeshToonMaterial, MeshToonNodeMaterial, MinEquation, MirroredRepeatWrapping, MixOperation, ModelNode, MorphNode, MultiplyBlending, MultiplyOperation, NearestFilter, NearestMipmapLinearFilter, NearestMipmapNearestFilter, NeutralToneMapping, NeverCompare, NeverDepth, NeverStencilFunc, NoBlending, NoColorSpace, NoToneMapping, Node, NodeAccess, NodeAttribute, NodeBuilder, NodeCache, NodeCode, NodeFrame, NodeFunctionInput, NodeLoader, NodeMaterial, NodeMaterialLoader, NodeMaterialObserver, NodeObjectLoader, NodeShaderStage, NodeType, NodeUniform, NodeUpdateType, NodeUtils, NodeVar, NodeVarying, NormalBlending, NormalMapNode, NotEqualCompare, NotEqualDepth, NotEqualStencilFunc, Object3D, Object3DNode, ObjectLoader, ObjectSpaceNormalMap, OneFactor, OneMinusDstAlphaFactor, OneMinusDstColorFactor, OneMinusSrcAlphaFactor, OneMinusSrcColorFactor, OrthographicCamera, OutputStructNode, PCFShadowMap, PMREMGenerator, PMREMNode, ParameterNode, PassNode, PerspectiveCamera, PhongLightingModel, PhysicalLightingModel, Plane, PlaneGeometry, PointLight, PointLightNode, PointUVNode, PointsMaterial, PointsNodeMaterial, PostProcessing, PosterizeNode, ProjectorLight, ProjectorLightNode, PropertyNode, QuadMesh, Quaternion, RED_GREEN_RGTC2_Format, RED_RGTC1_Format, REVISION, RGBAFormat, RGBAIntegerFormat, RGBA_ASTC_10x10_Format, RGBA_ASTC_10x5_Format, RGBA_ASTC_10x6_Format, RGBA_ASTC_10x8_Format, RGBA_ASTC_12x10_Format, RGBA_ASTC_12x12_Format, RGBA_ASTC_4x4_Format, RGBA_ASTC_5x4_Format, RGBA_ASTC_5x5_Format, RGBA_ASTC_6x5_Format, RGBA_ASTC_6x6_Format, RGBA_ASTC_8x5_Format, RGBA_ASTC_8x6_Format, RGBA_ASTC_8x8_Format, RGBA_BPTC_Format, RGBA_ETC2_EAC_Format, RGBA_PVRTC_2BPPV1_Format, RGBA_PVRTC_4BPPV1_Format, RGBA_S3TC_DXT1_Format, RGBA_S3TC_DXT3_Format, RGBA_S3TC_DXT5_Format, RGBFormat, RGBIntegerFormat, RGB_ETC1_Format, RGB_ETC2_Format, RGB_PVRTC_2BPPV1_Format, RGB_PVRTC_4BPPV1_Format, RGB_S3TC_DXT1_Format, RGFormat, RGIntegerFormat, RTTNode, RangeNode, RectAreaLight, RectAreaLightNode, RedFormat, RedIntegerFormat, ReferenceNode, ReflectorNode, ReinhardToneMapping, RemapNode, RenderOutputNode, RenderTarget, RendererReferenceNode, RendererUtils, RepeatWrapping, ReplaceStencilOp, ReverseSubtractEquation, RotateNode, SIGNED_RED_GREEN_RGTC2_Format, SIGNED_RED_RGTC1_Format, SRGBColorSpace, SRGBTransfer, Scene, SceneNode, ScreenNode, ScriptableNode, ScriptableValueNode, SetNode, ShadowBaseNode, ShadowMaterial, ShadowNode, ShadowNodeMaterial, ShortType, SkinningNode, Sphere, SphereGeometry, SplitNode, SpotLight, SpotLightNode, SpriteMaterial, SpriteNodeMaterial, SpriteSheetUVNode, SrcAlphaFactor, SrcAlphaSaturateFactor, SrcColorFactor, StackNode, StaticDrawUsage, StorageArrayElementNode, StorageBufferAttribute, StorageBufferNode, StorageInstancedBufferAttribute, StorageTexture, StorageTextureNode, StructNode, StructTypeNode, SubBuildNode, SubtractEquation, SubtractiveBlending, TSL, TangentSpaceNormalMap, TempNode, Texture, Texture3DNode, TextureNode, TextureSizeNode, ToneMappingNode, ToonOutlinePassNode, UVMapping, Uint16BufferAttribute, Uint32BufferAttribute, UniformArrayNode, UniformGroupNode, UniformNode, UnsignedByteType, UnsignedInt248Type, UnsignedInt5999Type, UnsignedIntType, UnsignedShort4444Type, UnsignedShort5551Type, UnsignedShortType, UserDataNode, VSMShadowMap, VarNode, VaryingNode, Vector2, Vector3, Vector4, VertexColorNode, ViewportDepthNode, ViewportDepthTextureNode, ViewportSharedTextureNode, ViewportTextureNode, VolumeNodeMaterial, WebGLCoordinateSystem, WebGLCubeRenderTarget, WebGPUCoordinateSystem, WebGPURenderer, WebXRController, ZeroFactor, ZeroStencilOp, createCanvasElement, defaultBuildStages, defaultShaderStages, shaderStages, vectorComponents }; diff --git a/build/three.webgpu.nodes.min.js b/build/three.webgpu.nodes.min.js new file mode 100644 index 00000000000000..16ebbf9b906c21 --- /dev/null +++ b/build/three.webgpu.nodes.min.js @@ -0,0 +1,6 @@ +/** + * @license + * Copyright 2010-2025 Three.js Authors + * SPDX-License-Identifier: MIT + */ +import{Color as e,Vector2 as t,Vector3 as r,Vector4 as s,Matrix2 as i,Matrix3 as n,Matrix4 as a,EventDispatcher as o,MathUtils as u,WebGLCoordinateSystem as l,WebGPUCoordinateSystem as d,ColorManagement as c,SRGBTransfer as h,NoToneMapping as p,StaticDrawUsage as g,InterleavedBuffer as m,InterleavedBufferAttribute as f,DynamicDrawUsage as y,NoColorSpace as b,Texture as x,UnsignedIntType as T,IntType as _,NearestFilter as v,Sphere as N,BackSide as S,DoubleSide as E,Euler as w,CubeTexture as A,CubeReflectionMapping as R,CubeRefractionMapping as C,TangentSpaceNormalMap as M,ObjectSpaceNormalMap as P,InstancedInterleavedBuffer as B,InstancedBufferAttribute as F,DataArrayTexture as L,FloatType as D,FramebufferTexture as I,LinearMipmapLinearFilter as V,DepthTexture as U,Material as O,NormalBlending as k,LineBasicMaterial as G,LineDashedMaterial as z,NoBlending as H,MeshNormalMaterial as $,SRGBColorSpace as W,WebGLCubeRenderTarget as j,BoxGeometry as q,Mesh as X,Scene as K,LinearFilter as Y,CubeCamera as Q,EquirectangularReflectionMapping as Z,EquirectangularRefractionMapping as J,AddOperation as ee,MixOperation as te,MultiplyOperation as re,MeshBasicMaterial as se,MeshLambertMaterial as ie,MeshPhongMaterial as ne,OrthographicCamera as ae,PerspectiveCamera as oe,RenderTarget as ue,LinearSRGBColorSpace as le,RGBAFormat as de,HalfFloatType as ce,CubeUVReflectionMapping as he,BufferGeometry as pe,BufferAttribute as ge,MeshStandardMaterial as me,MeshPhysicalMaterial as fe,MeshToonMaterial as ye,MeshMatcapMaterial as be,SpriteMaterial as xe,PointsMaterial as Te,ShadowMaterial as _e,Uint32BufferAttribute as ve,Uint16BufferAttribute as Ne,arrayNeedsUint32 as Se,Camera as Ee,DepthStencilFormat as we,DepthFormat as Ae,UnsignedInt248Type as Re,UnsignedByteType as Ce,Plane as Me,Object3D as Pe,LinearMipMapLinearFilter as Be,Float32BufferAttribute as Fe,UVMapping as Le,VSMShadowMap as De,LessCompare as Ie,RGFormat as Ve,BasicShadowMap as Ue,SphereGeometry as Oe,LinearMipmapNearestFilter as ke,NearestMipmapLinearFilter as Ge,Float16BufferAttribute as ze,REVISION as He,ArrayCamera as $e,PlaneGeometry as We,FrontSide as je,CustomBlending as qe,AddEquation as Xe,ZeroFactor as Ke,CylinderGeometry as Ye,Quaternion as Qe,WebXRController as Ze,RAD2DEG as Je,PCFShadowMap as et,FrustumArray as tt,Frustum as rt,DataTexture as st,RedIntegerFormat as it,RedFormat as nt,ShortType as at,ByteType as ot,UnsignedShortType as ut,RGIntegerFormat as lt,RGBIntegerFormat as dt,RGBFormat as ct,RGBAIntegerFormat as ht,warnOnce as pt,createCanvasElement as gt,ReverseSubtractEquation as mt,SubtractEquation as ft,OneMinusDstAlphaFactor as yt,OneMinusDstColorFactor as bt,OneMinusSrcAlphaFactor as xt,OneMinusSrcColorFactor as Tt,DstAlphaFactor as _t,DstColorFactor as vt,SrcAlphaSaturateFactor as Nt,SrcAlphaFactor as St,SrcColorFactor as Et,OneFactor as wt,CullFaceNone as At,CullFaceBack as Rt,CullFaceFront as Ct,MultiplyBlending as Mt,SubtractiveBlending as Pt,AdditiveBlending as Bt,NotEqualDepth as Ft,GreaterDepth as Lt,GreaterEqualDepth as Dt,EqualDepth as It,LessEqualDepth as Vt,LessDepth as Ut,AlwaysDepth as Ot,NeverDepth as kt,UnsignedShort4444Type as Gt,UnsignedShort5551Type as zt,UnsignedInt5999Type as Ht,AlphaFormat as $t,RGB_S3TC_DXT1_Format as Wt,RGBA_S3TC_DXT1_Format as jt,RGBA_S3TC_DXT3_Format as qt,RGBA_S3TC_DXT5_Format as Xt,RGB_PVRTC_4BPPV1_Format as Kt,RGB_PVRTC_2BPPV1_Format as Yt,RGBA_PVRTC_4BPPV1_Format as Qt,RGBA_PVRTC_2BPPV1_Format as Zt,RGB_ETC1_Format as Jt,RGB_ETC2_Format as er,RGBA_ETC2_EAC_Format as tr,RGBA_ASTC_4x4_Format as rr,RGBA_ASTC_5x4_Format as sr,RGBA_ASTC_5x5_Format as ir,RGBA_ASTC_6x5_Format as nr,RGBA_ASTC_6x6_Format as ar,RGBA_ASTC_8x5_Format as or,RGBA_ASTC_8x6_Format as ur,RGBA_ASTC_8x8_Format as lr,RGBA_ASTC_10x5_Format as dr,RGBA_ASTC_10x6_Format as cr,RGBA_ASTC_10x8_Format as hr,RGBA_ASTC_10x10_Format as pr,RGBA_ASTC_12x10_Format as gr,RGBA_ASTC_12x12_Format as mr,RGBA_BPTC_Format as fr,RED_RGTC1_Format as yr,SIGNED_RED_RGTC1_Format as br,RED_GREEN_RGTC2_Format as xr,SIGNED_RED_GREEN_RGTC2_Format as Tr,MirroredRepeatWrapping as _r,ClampToEdgeWrapping as vr,RepeatWrapping as Nr,NearestMipmapNearestFilter as Sr,NotEqualCompare as Er,GreaterCompare as wr,GreaterEqualCompare as Ar,EqualCompare as Rr,LessEqualCompare as Cr,AlwaysCompare as Mr,NeverCompare as Pr,LinearTransfer as Br,NotEqualStencilFunc as Fr,GreaterStencilFunc as Lr,GreaterEqualStencilFunc as Dr,EqualStencilFunc as Ir,LessEqualStencilFunc as Vr,LessStencilFunc as Ur,AlwaysStencilFunc as Or,NeverStencilFunc as kr,DecrementWrapStencilOp as Gr,IncrementWrapStencilOp as zr,DecrementStencilOp as Hr,IncrementStencilOp as $r,InvertStencilOp as Wr,ReplaceStencilOp as jr,ZeroStencilOp as qr,KeepStencilOp as Xr,MaxEquation as Kr,MinEquation as Yr,SpotLight as Qr,PointLight as Zr,DirectionalLight as Jr,RectAreaLight as es,AmbientLight as ts,HemisphereLight as rs,LightProbe as ss,LinearToneMapping as is,ReinhardToneMapping as ns,CineonToneMapping as as,ACESFilmicToneMapping as os,AgXToneMapping as us,NeutralToneMapping as ls,Group as ds,Loader as cs,FileLoader as hs,MaterialLoader as ps,ObjectLoader as gs}from"./three.core.min.js";export{AdditiveAnimationBlendMode,AnimationAction,AnimationClip,AnimationLoader,AnimationMixer,AnimationObjectGroup,AnimationUtils,ArcCurve,ArrowHelper,AttachedBindMode,Audio,AudioAnalyser,AudioContext,AudioListener,AudioLoader,AxesHelper,BasicDepthPacking,BatchedMesh,Bone,BooleanKeyframeTrack,Box2,Box3,Box3Helper,BoxHelper,BufferGeometryLoader,Cache,CameraHelper,CanvasTexture,CapsuleGeometry,CatmullRomCurve3,CircleGeometry,Clock,ColorKeyframeTrack,CompressedArrayTexture,CompressedCubeTexture,CompressedTexture,CompressedTextureLoader,ConeGeometry,ConstantAlphaFactor,ConstantColorFactor,Controls,CubeTextureLoader,CubicBezierCurve,CubicBezierCurve3,CubicInterpolant,CullFaceFrontBack,Curve,CurvePath,CustomToneMapping,Cylindrical,Data3DTexture,DataTextureLoader,DataUtils,DefaultLoadingManager,DetachedBindMode,DirectionalLightHelper,DiscreteInterpolant,DodecahedronGeometry,DynamicCopyUsage,DynamicReadUsage,EdgesGeometry,EllipseCurve,ExtrudeGeometry,Fog,FogExp2,GLBufferAttribute,GLSL1,GLSL3,GridHelper,HemisphereLightHelper,IcosahedronGeometry,ImageBitmapLoader,ImageLoader,ImageUtils,InstancedBufferGeometry,InstancedMesh,Int16BufferAttribute,Int32BufferAttribute,Int8BufferAttribute,Interpolant,InterpolateDiscrete,InterpolateLinear,InterpolateSmooth,InterpolationSamplingMode,InterpolationSamplingType,KeyframeTrack,LOD,LatheGeometry,Layers,Light,Line,Line3,LineCurve,LineCurve3,LineLoop,LineSegments,LinearInterpolant,LinearMipMapNearestFilter,LoaderUtils,LoadingManager,LoopOnce,LoopPingPong,LoopRepeat,MOUSE,MeshDepthMaterial,MeshDistanceMaterial,NearestMipMapLinearFilter,NearestMipMapNearestFilter,NormalAnimationBlendMode,NumberKeyframeTrack,OctahedronGeometry,OneMinusConstantAlphaFactor,OneMinusConstantColorFactor,PCFSoftShadowMap,Path,PlaneHelper,PointLightHelper,Points,PolarGridHelper,PolyhedronGeometry,PositionalAudio,PropertyBinding,PropertyMixer,QuadraticBezierCurve,QuadraticBezierCurve3,QuaternionKeyframeTrack,QuaternionLinearInterpolant,RGBADepthPacking,RGBDepthPacking,RGB_BPTC_SIGNED_Format,RGB_BPTC_UNSIGNED_Format,RGDepthPacking,RawShaderMaterial,Ray,Raycaster,RenderTarget3D,RingGeometry,ShaderMaterial,Shape,ShapeGeometry,ShapePath,ShapeUtils,Skeleton,SkeletonHelper,SkinnedMesh,Source,Spherical,SphericalHarmonics3,SplineCurve,SpotLightHelper,Sprite,StaticCopyUsage,StaticReadUsage,StereoCamera,StreamCopyUsage,StreamDrawUsage,StreamReadUsage,StringKeyframeTrack,TOUCH,TetrahedronGeometry,TextureLoader,TextureUtils,TimestampQuery,TorusGeometry,TorusKnotGeometry,Triangle,TriangleFanDrawMode,TriangleStripDrawMode,TrianglesDrawMode,TubeGeometry,Uint8BufferAttribute,Uint8ClampedBufferAttribute,Uniform,UniformsGroup,VectorKeyframeTrack,VideoFrameTexture,VideoTexture,WebGL3DRenderTarget,WebGLArrayRenderTarget,WebGLRenderTarget,WireframeGeometry,WrapAroundEnding,ZeroCurvatureEnding,ZeroSlopeEnding}from"./three.core.min.js";const ms=["alphaMap","alphaTest","anisotropy","anisotropyMap","anisotropyRotation","aoMap","aoMapIntensity","attenuationColor","attenuationDistance","bumpMap","clearcoat","clearcoatMap","clearcoatNormalMap","clearcoatNormalScale","clearcoatRoughness","color","dispersion","displacementMap","emissive","emissiveIntensity","emissiveMap","envMap","envMapIntensity","gradientMap","ior","iridescence","iridescenceIOR","iridescenceMap","iridescenceThicknessMap","lightMap","lightMapIntensity","map","matcap","metalness","metalnessMap","normalMap","normalScale","opacity","roughness","roughnessMap","sheen","sheenColor","sheenColorMap","sheenRoughnessMap","shininess","specular","specularColor","specularColorMap","specularIntensity","specularIntensityMap","specularMap","thickness","transmission","transmissionMap"];class fs{constructor(e){this.renderObjects=new WeakMap,this.hasNode=this.containsNode(e),this.hasAnimation=!0===e.object.isSkinnedMesh,this.refreshUniforms=ms,this.renderId=0}firstInitialization(e){return!1===this.renderObjects.has(e)&&(this.getRenderObjectData(e),!0)}needsVelocity(e){const t=e.getMRT();return null!==t&&t.has("velocity")}getRenderObjectData(e){let t=this.renderObjects.get(e);if(void 0===t){const{geometry:r,material:s,object:i}=e;if(t={material:this.getMaterialData(s),geometry:{id:r.id,attributes:this.getAttributesData(r.attributes),indexVersion:r.index?r.index.version:null,drawRange:{start:r.drawRange.start,count:r.drawRange.count}},worldMatrix:i.matrixWorld.clone()},i.center&&(t.center=i.center.clone()),i.morphTargetInfluences&&(t.morphTargetInfluences=i.morphTargetInfluences.slice()),null!==e.bundle&&(t.version=e.bundle.version),t.material.transmission>0){const{width:r,height:s}=e.context;t.bufferWidth=r,t.bufferHeight=s}this.renderObjects.set(e,t)}return t}getAttributesData(e){const t={};for(const r in e){const s=e[r];t[r]={version:s.version}}return t}containsNode(e){const t=e.material;for(const e in t)if(t[e]&&t[e].isNode)return!0;return null!==e.renderer.overrideNodes.modelViewMatrix||null!==e.renderer.overrideNodes.modelNormalViewMatrix}getMaterialData(e){const t={};for(const r of this.refreshUniforms){const s=e[r];null!=s&&("object"==typeof s&&void 0!==s.clone?!0===s.isTexture?t[r]={id:s.id,version:s.version}:t[r]=s.clone():t[r]=s)}return t}equals(e){const{object:t,material:r,geometry:s}=e,i=this.getRenderObjectData(e);if(!0!==i.worldMatrix.equals(t.matrixWorld))return i.worldMatrix.copy(t.matrixWorld),!1;const n=i.material;for(const e in n){const t=n[e],s=r[e];if(void 0!==t.equals){if(!1===t.equals(s))return t.copy(s),!1}else if(!0===s.isTexture){if(t.id!==s.id||t.version!==s.version)return t.id=s.id,t.version=s.version,!1}else if(t!==s)return n[e]=s,!1}if(n.transmission>0){const{width:t,height:r}=e.context;if(i.bufferWidth!==t||i.bufferHeight!==r)return i.bufferWidth=t,i.bufferHeight=r,!1}const a=i.geometry,o=s.attributes,u=a.attributes,l=Object.keys(u),d=Object.keys(o);if(a.id!==s.id)return a.id=s.id,!1;if(l.length!==d.length)return i.geometry.attributes=this.getAttributesData(o),!1;for(const e of l){const t=u[e],r=o[e];if(void 0===r)return delete u[e],!1;if(t.version!==r.version)return t.version=r.version,!1}const c=s.index,h=a.indexVersion,p=c?c.version:null;if(h!==p)return a.indexVersion=p,!1;if(a.drawRange.start!==s.drawRange.start||a.drawRange.count!==s.drawRange.count)return a.drawRange.start=s.drawRange.start,a.drawRange.count=s.drawRange.count,!1;if(i.morphTargetInfluences){let e=!1;for(let r=0;r>>16,2246822507),r^=Math.imul(s^s>>>13,3266489909),s=Math.imul(s^s>>>16,2246822507),s^=Math.imul(r^r>>>13,3266489909),4294967296*(2097151&s)+(r>>>0)}const bs=e=>ys(e),xs=e=>ys(e),Ts=(...e)=>ys(e);function _s(e,t=!1){const r=[];!0===e.isNode&&(r.push(e.id),e=e.getSelf());for(const{property:s,childNode:i}of vs(e))r.push(ys(s.slice(0,-4)),i.getCacheKey(t));return ys(r)}function*vs(e,t=!1){for(const r in e){if(!0===r.startsWith("_"))continue;const s=e[r];if(!0===Array.isArray(s))for(let e=0;ee.charCodeAt(0)).buffer}var Ds=Object.freeze({__proto__:null,arrayBufferToBase64:Fs,base64ToArrayBuffer:Ls,getByteBoundaryFromType:Cs,getCacheKey:_s,getDataFromObject:Bs,getLengthFromType:As,getMemoryLengthFromType:Rs,getNodeChildren:vs,getTypeFromLength:Es,getTypedArrayFromType:ws,getValueFromType:Ps,getValueType:Ms,hash:Ts,hashArray:xs,hashString:bs});const Is={VERTEX:"vertex",FRAGMENT:"fragment"},Vs={NONE:"none",FRAME:"frame",RENDER:"render",OBJECT:"object"},Us={BOOLEAN:"bool",INTEGER:"int",FLOAT:"float",VECTOR2:"vec2",VECTOR3:"vec3",VECTOR4:"vec4",MATRIX2:"mat2",MATRIX3:"mat3",MATRIX4:"mat4"},Os={READ_ONLY:"readOnly",WRITE_ONLY:"writeOnly",READ_WRITE:"readWrite"},ks=["fragment","vertex"],Gs=["setup","analyze","generate"],zs=[...ks,"compute"],Hs=["x","y","z","w"],$s={analyze:"setup",generate:"analyze"};let Ws=0;class js extends o{static get type(){return"Node"}constructor(e=null){super(),this.nodeType=e,this.updateType=Vs.NONE,this.updateBeforeType=Vs.NONE,this.updateAfterType=Vs.NONE,this.uuid=u.generateUUID(),this.version=0,this.global=!1,this.parents=!1,this.isNode=!0,this._cacheKey=null,this._cacheKeyVersion=0,Object.defineProperty(this,"id",{value:Ws++})}set needsUpdate(e){!0===e&&this.version++}get type(){return this.constructor.type}onUpdate(e,t){return this.updateType=t,this.update=e.bind(this.getSelf()),this}onFrameUpdate(e){return this.onUpdate(e,Vs.FRAME)}onRenderUpdate(e){return this.onUpdate(e,Vs.RENDER)}onObjectUpdate(e){return this.onUpdate(e,Vs.OBJECT)}onReference(e){return this.updateReference=e.bind(this.getSelf()),this}getSelf(){return this.self||this}updateReference(){return this}isGlobal(){return this.global}*getChildren(){for(const{childNode:e}of vs(this))yield e}dispose(){this.dispatchEvent({type:"dispose"})}traverse(e){e(this);for(const t of this.getChildren())t.traverse(e)}getCacheKey(e=!1){return!0!==(e=e||this.version!==this._cacheKeyVersion)&&null!==this._cacheKey||(this._cacheKey=Ts(_s(this,e),this.customCacheKey()),this._cacheKeyVersion=this.version),this._cacheKey}customCacheKey(){return 0}getScope(){return this}getHash(){return this.uuid}getUpdateType(){return this.updateType}getUpdateBeforeType(){return this.updateBeforeType}getUpdateAfterType(){return this.updateAfterType}getElementType(e){const t=this.getNodeType(e);return e.getElementType(t)}getMemberType(){return"void"}getNodeType(e){const t=e.getNodeProperties(this);return t.outputNode?t.outputNode.getNodeType(e):this.nodeType}getShared(e){const t=this.getHash(e);return e.getNodeFromHash(t)||this}setup(e){const t=e.getNodeProperties(this);let r=0;for(const e of this.getChildren())t["node"+r++]=e;return t.outputNode||null}analyze(e,t=null){const r=e.increaseUsage(this);if(!0===this.parents){const r=e.getDataFromNode(this,"any");r.stages=r.stages||{},r.stages[e.shaderStage]=r.stages[e.shaderStage]||[],r.stages[e.shaderStage].push(t)}if(1===r){const t=e.getNodeProperties(this);for(const r of Object.values(t))r&&!0===r.isNode&&r.build(e,this)}}generate(e,t){const{outputNode:r}=e.getNodeProperties(this);if(r&&!0===r.isNode)return r.build(e,t)}updateBefore(){console.warn("Abstract function.")}updateAfter(){console.warn("Abstract function.")}update(){console.warn("Abstract function.")}build(e,t=null){const r=this.getShared(e);if(this!==r)return r.build(e,t);const s=e.getDataFromNode(this);s.buildStages=s.buildStages||{},s.buildStages[e.buildStage]=!0;const i=$s[e.buildStage];if(i&&!0!==s.buildStages[i]){const t=e.getBuildStage();e.setBuildStage(i),this.build(e),e.setBuildStage(t)}e.addNode(this),e.addChain(this);let n=null;const a=e.getBuildStage();if("setup"===a){this.updateReference(e);const t=e.getNodeProperties(this);if(!0!==t.initialized){t.initialized=!0,t.outputNode=this.setup(e)||t.outputNode||null;for(const r of Object.values(t))if(r&&!0===r.isNode){if(!0===r.parents){const t=e.getNodeProperties(r);t.parents=t.parents||[],t.parents.push(this)}r.build(e)}}n=t.outputNode}else if("analyze"===a)this.analyze(e,t);else if("generate"===a){if(1===this.generate.length){const r=this.getNodeType(e),s=e.getDataFromNode(this);n=s.snippet,void 0===n?void 0===s.generated?(s.generated=!0,n=this.generate(e)||"",s.snippet=n):(console.warn("THREE.Node: Recursion detected.",this),n="/* Recursion detected. */"):void 0!==s.flowCodes&&void 0!==e.context.nodeBlock&&e.addFlowCodeHierarchy(this,e.context.nodeBlock),n=e.format(n,r,t)}else n=this.generate(e,t)||""}return e.removeChain(this),e.addSequentialNode(this),n}getSerializeChildren(){return vs(this)}serialize(e){const t=this.getSerializeChildren(),r={};for(const{property:s,index:i,childNode:n}of t)void 0!==i?(void 0===r[s]&&(r[s]=Number.isInteger(i)?[]:{}),r[s][i]=n.toJSON(e.meta).uuid):r[s]=n.toJSON(e.meta).uuid;Object.keys(r).length>0&&(e.inputNodes=r)}deserialize(e){if(void 0!==e.inputNodes){const t=e.meta.nodes;for(const r in e.inputNodes)if(Array.isArray(e.inputNodes[r])){const s=[];for(const i of e.inputNodes[r])s.push(t[i]);this[r]=s}else if("object"==typeof e.inputNodes[r]){const s={};for(const i in e.inputNodes[r]){const n=e.inputNodes[r][i];s[i]=t[n]}this[r]=s}else{const s=e.inputNodes[r];this[r]=t[s]}}}toJSON(e){const{uuid:t,type:r}=this,s=void 0===e||"string"==typeof e;s&&(e={textures:{},images:{},nodes:{}});let i=e.nodes[t];function n(e){const t=[];for(const r in e){const s=e[r];delete s.metadata,t.push(s)}return t}if(void 0===i&&(i={uuid:t,type:r,meta:e,metadata:{version:4.7,type:"Node",generator:"Node.toJSON"}},!0!==s&&(e.nodes[i.uuid]=i),this.serialize(i),delete i.meta),s){const t=n(e.textures),r=n(e.images),s=n(e.nodes);t.length>0&&(i.textures=t),r.length>0&&(i.images=r),s.length>0&&(i.nodes=s)}return i}}class qs extends js{static get type(){return"ArrayElementNode"}constructor(e,t){super(),this.node=e,this.indexNode=t,this.isArrayElementNode=!0}getNodeType(e){return this.node.getElementType(e)}generate(e){const t=this.indexNode.getNodeType(e);return`${this.node.build(e)}[ ${this.indexNode.build(e,!e.isVector(t)&&e.isInteger(t)?t:"uint")} ]`}}class Xs extends js{static get type(){return"ConvertNode"}constructor(e,t){super(),this.node=e,this.convertTo=t}getNodeType(e){const t=this.node.getNodeType(e);let r=null;for(const s of this.convertTo.split("|"))null!==r&&e.getTypeLength(t)!==e.getTypeLength(s)||(r=s);return r}serialize(e){super.serialize(e),e.convertTo=this.convertTo}deserialize(e){super.deserialize(e),this.convertTo=e.convertTo}generate(e,t){const r=this.node,s=this.getNodeType(e),i=r.build(e,s);return e.format(i,s,t)}}class Ks extends js{static get type(){return"TempNode"}constructor(e=null){super(e),this.isTempNode=!0}hasDependencies(e){return e.getDataFromNode(this).usageCount>1}build(e,t){if("generate"===e.getBuildStage()){const r=e.getVectorType(this.getNodeType(e,t)),s=e.getDataFromNode(this);if(void 0!==s.propertyName)return e.format(s.propertyName,r,t);if("void"!==r&&"void"!==t&&this.hasDependencies(e)){const i=super.build(e,r),n=e.getVarFromNode(this,null,r),a=e.getPropertyName(n);return e.addLineFlowCode(`${a} = ${i}`,this),s.snippet=i,s.propertyName=a,e.format(s.propertyName,r,t)}}return super.build(e,t)}}class Ys extends Ks{static get type(){return"JoinNode"}constructor(e=[],t=null){super(t),this.nodes=e}getNodeType(e){return null!==this.nodeType?e.getVectorType(this.nodeType):e.getTypeFromLength(this.nodes.reduce((t,r)=>t+e.getTypeLength(r.getNodeType(e)),0))}generate(e,t){const r=this.getNodeType(e),s=e.getTypeLength(r),i=this.nodes,n=e.getComponentType(r),a=[];let o=0;for(const t of i){if(o>=s){console.error(`THREE.TSL: Length of parameters exceeds maximum length of function '${r}()' type.`);break}let i,u=t.getNodeType(e),l=e.getTypeLength(u);o+l>s&&(console.error(`THREE.TSL: Length of '${r}()' data exceeds maximum length of output type.`),l=s-o,u=e.getTypeFromLength(l)),o+=l,i=t.build(e,u);const d=e.getComponentType(u);d!==n&&(i=e.format(i,d,n)),a.push(i)}const u=`${e.getType(r)}( ${a.join(", ")} )`;return e.format(u,r,t)}}const Qs=Hs.join("");class Zs extends js{static get type(){return"SplitNode"}constructor(e,t="x"){super(),this.node=e,this.components=t,this.isSplitNode=!0}getVectorLength(){let e=this.components.length;for(const t of this.components)e=Math.max(Hs.indexOf(t)+1,e);return e}getComponentType(e){return e.getComponentType(this.node.getNodeType(e))}getNodeType(e){return e.getTypeFromLength(this.components.length,this.getComponentType(e))}generate(e,t){const r=this.node,s=e.getTypeLength(r.getNodeType(e));let i=null;if(s>1){let n=null;this.getVectorLength()>=s&&(n=e.getTypeFromLength(this.getVectorLength(),this.getComponentType(e)));const a=r.build(e,n);i=this.components.length===s&&this.components===Qs.slice(0,this.components.length)?e.format(a,n,t):e.format(`${a}.${this.components}`,this.getNodeType(e),t)}else i=r.build(e,t);return i}serialize(e){super.serialize(e),e.components=this.components}deserialize(e){super.deserialize(e),this.components=e.components}}class Js extends Ks{static get type(){return"SetNode"}constructor(e,t,r){super(),this.sourceNode=e,this.components=t,this.targetNode=r}getNodeType(e){return this.sourceNode.getNodeType(e)}generate(e){const{sourceNode:t,components:r,targetNode:s}=this,i=this.getNodeType(e),n=e.getComponentType(s.getNodeType(e)),a=e.getTypeFromLength(r.length,n),o=s.build(e,a),u=t.build(e,i),l=e.getTypeLength(i),d=[];for(let e=0;ee.replace(/r|s/g,"x").replace(/g|t/g,"y").replace(/b|p/g,"z").replace(/a|q/g,"w"),li=e=>ui(e).split("").sort().join(""),di={setup(e,t){const r=t.shift();return e(Di(r),...t)},get(e,t,r){if("string"==typeof t&&void 0===e[t]){if(!0!==e.isStackNode&&"assign"===t)return(...e)=>(ni.assign(r,...e),r);if(ai.has(t)){const s=ai.get(t);return e.isStackNode?(...e)=>r.add(s(...e)):(...e)=>s(r,...e)}if("self"===t)return e;if(t.endsWith("Assign")&&ai.has(t.slice(0,t.length-6))){const s=ai.get(t.slice(0,t.length-6));return e.isStackNode?(...e)=>r.assign(e[0],s(...e)):(...e)=>r.assign(s(r,...e))}if(!0===/^[xyzwrgbastpq]{1,4}$/.test(t))return t=ui(t),Li(new Zs(r,t));if(!0===/^set[XYZWRGBASTPQ]{1,4}$/.test(t))return t=li(t.slice(3).toLowerCase()),r=>Li(new Js(e,t,Li(r)));if(!0===/^flip[XYZWRGBASTPQ]{1,4}$/.test(t))return t=li(t.slice(4).toLowerCase()),()=>Li(new ei(Li(e),t));if("width"===t||"height"===t||"depth"===t)return"width"===t?t="x":"height"===t?t="y":"depth"===t&&(t="z"),Li(new Zs(e,t));if(!0===/^\d+$/.test(t))return Li(new qs(r,new si(Number(t),"uint")));if(!0===/^get$/.test(t))return e=>Li(new ii(r,e))}return Reflect.get(e,t,r)},set:(e,t,r,s)=>"string"!=typeof t||void 0!==e[t]||!0!==/^[xyzwrgbastpq]{1,4}$/.test(t)&&"width"!==t&&"height"!==t&&"depth"!==t&&!0!==/^\d+$/.test(t)?Reflect.set(e,t,r,s):(s[t].assign(r),!0)},ci=new WeakMap,hi=new WeakMap,pi=function(e,t=null){for(const r in e)e[r]=Li(e[r],t);return e},gi=function(e,t=null){const r=e.length;for(let s=0;sLi(null!==s?Object.assign(e,s):e);let n,a,o,u=t;function l(t){let r;return r=u?/[a-z]/i.test(u)?u+"()":u:e.type,void 0!==a&&t.lengtho?(console.error(`THREE.TSL: "${r}" parameter length exceeds limit.`),t.slice(0,o)):t}return null===t?n=(...t)=>i(new e(...Ii(l(t)))):null!==r?(r=Li(r),n=(...s)=>i(new e(t,...Ii(l(s)),r))):n=(...r)=>i(new e(t,...Ii(l(r)))),n.setParameterLength=(...e)=>(1===e.length?a=o=e[0]:2===e.length&&([a,o]=e),n),n.setName=e=>(u=e,n),n},fi=function(e,...t){return Li(new e(...Ii(t)))};class yi extends js{constructor(e,t){super(),this.shaderNode=e,this.inputNodes=t,this.isShaderCallNodeInternal=!0}getNodeType(e){return this.shaderNode.nodeType||this.getOutputNode(e).getNodeType(e)}getMemberType(e,t){return this.getOutputNode(e).getMemberType(e,t)}call(e){const{shaderNode:t,inputNodes:r}=this,s=e.getNodeProperties(t),i=e.getClosestSubBuild(t.subBuilds)||"",n=i||"default";if(s[n])return s[n];const a=e.subBuildFn;e.subBuildFn=i;let o=null;if(t.layout){let s=hi.get(e.constructor);void 0===s&&(s=new WeakMap,hi.set(e.constructor,s));let i=s.get(t);void 0===i&&(i=Li(e.buildFunctionNode(t)),s.set(t,i)),e.addInclude(i),o=Li(i.call(r))}else{const s=t.jsFunc,i=null!==r||s.length>1?s(r||[],e):s(e);o=Li(i)}return e.subBuildFn=a,t.once&&(s[n]=o),o}setupOutput(e){return e.addStack(),e.stack.outputNode=this.call(e),e.removeStack()}getOutputNode(e){const t=e.getNodeProperties(this),r=e.getSubBuildOutput(this);return t[r]=t[r]||this.setupOutput(e),t[r].subBuild=e.getClosestSubBuild(this),t[r]}build(e,t=null){let r=null;const s=e.getBuildStage(),i=e.getNodeProperties(this),n=e.getSubBuildOutput(this),a=this.getOutputNode(e);if("setup"===s){const t=e.getSubBuildProperty("initialized",this);if(!0!==i[t]&&(i[t]=!0,i[n]=this.getOutputNode(e),i[n].build(e),this.shaderNode.subBuilds))for(const t of e.chaining){const r=e.getDataFromNode(t,"any");r.subBuilds=r.subBuilds||new Set;for(const e of this.shaderNode.subBuilds)r.subBuilds.add(e)}r=i[n]}else"analyze"===s?a.build(e,t):"generate"===s&&(r=a.build(e,t)||"");return r}}class bi extends js{constructor(e,t){super(t),this.jsFunc=e,this.layout=null,this.global=!0,this.once=!1}setLayout(e){return this.layout=e,this}call(e=null){return Di(e),Li(new yi(this,e))}setup(){return this.call()}}const xi=[!1,!0],Ti=[0,1,2,3],_i=[-1,-2],vi=[.5,1.5,1/3,1e-6,1e6,Math.PI,2*Math.PI,1/Math.PI,2/Math.PI,1/(2*Math.PI),Math.PI/2],Ni=new Map;for(const e of xi)Ni.set(e,new si(e));const Si=new Map;for(const e of Ti)Si.set(e,new si(e,"uint"));const Ei=new Map([...Si].map(e=>new si(e.value,"int")));for(const e of _i)Ei.set(e,new si(e,"int"));const wi=new Map([...Ei].map(e=>new si(e.value)));for(const e of vi)wi.set(e,new si(e));for(const e of vi)wi.set(-e,new si(-e));const Ai={bool:Ni,uint:Si,ints:Ei,float:wi},Ri=new Map([...Ni,...wi]),Ci=(e,t)=>Ri.has(e)?Ri.get(e):!0===e.isNode?e:new si(e,t),Mi=function(e,t=null){return(...r)=>{if((0===r.length||!["bool","float","int","uint"].includes(e)&&r.every(e=>"object"!=typeof e))&&(r=[Ps(e,...r)]),1===r.length&&null!==t&&t.has(r[0]))return Li(t.get(r[0]));if(1===r.length){const t=Ci(r[0],e);return t.nodeType===e?Li(t):Li(new Xs(t,e))}const s=r.map(e=>Ci(e));return Li(new Ys(s,e))}},Pi=e=>"object"==typeof e&&null!==e?e.value:e,Bi=e=>null!=e?e.nodeType||e.convertTo||("string"==typeof e?e:null):null;function Fi(e,t){return new Proxy(new bi(e,t),di)}const Li=(e,t=null)=>function(e,t=null){const r=Ms(e);if("node"===r){let t=ci.get(e);return void 0===t&&(t=new Proxy(e,di),ci.set(e,t),ci.set(t,t)),t}return null===t&&("float"===r||"boolean"===r)||r&&"shader"!==r&&"string"!==r?Li(Ci(e,t)):"shader"===r?e.isFn?e:ki(e):e}(e,t),Di=(e,t=null)=>new pi(e,t),Ii=(e,t=null)=>new gi(e,t),Vi=(...e)=>new mi(...e),Ui=(...e)=>new fi(...e);let Oi=0;const ki=(e,t=null)=>{let r=null;null!==t&&("object"==typeof t?r=t.return:("string"==typeof t?r=t:console.error("THREE.TSL: Invalid layout type."),t=null));const s=new Fi(e,r),i=(...e)=>{let t;Di(e);t=e[0]&&(e[0].isNode||Object.getPrototypeOf(e[0])!==Object.prototype)?[...e]:e[0];const i=s.call(t);return"void"===r&&i.toStack(),i};if(i.shaderNode=s,i.id=s.id,i.isFn=!0,i.getNodeType=(...e)=>s.getNodeType(...e),i.getCacheKey=(...e)=>s.getCacheKey(...e),i.setLayout=e=>(s.setLayout(e),i),i.once=(e=null)=>(s.once=!0,s.subBuilds=e,i),null!==t){if("object"!=typeof t.inputs){const e={name:"fn"+Oi++,type:r,inputs:[]};for(const r in t)"return"!==r&&e.inputs.push({name:r,type:t[r]});t=e}i.setLayout(t)}return i},Gi=e=>{ni=e},zi=()=>ni,Hi=(...e)=>ni.If(...e);function $i(e){return ni&&ni.add(e),e}oi("toStack",$i);const Wi=new Mi("color"),ji=new Mi("float",Ai.float),qi=new Mi("int",Ai.ints),Xi=new Mi("uint",Ai.uint),Ki=new Mi("bool",Ai.bool),Yi=new Mi("vec2"),Qi=new Mi("ivec2"),Zi=new Mi("uvec2"),Ji=new Mi("bvec2"),en=new Mi("vec3"),tn=new Mi("ivec3"),rn=new Mi("uvec3"),sn=new Mi("bvec3"),nn=new Mi("vec4"),an=new Mi("ivec4"),on=new Mi("uvec4"),un=new Mi("bvec4"),ln=new Mi("mat2"),dn=new Mi("mat3"),cn=new Mi("mat4");oi("toColor",Wi),oi("toFloat",ji),oi("toInt",qi),oi("toUint",Xi),oi("toBool",Ki),oi("toVec2",Yi),oi("toIVec2",Qi),oi("toUVec2",Zi),oi("toBVec2",Ji),oi("toVec3",en),oi("toIVec3",tn),oi("toUVec3",rn),oi("toBVec3",sn),oi("toVec4",nn),oi("toIVec4",an),oi("toUVec4",on),oi("toBVec4",un),oi("toMat2",ln),oi("toMat3",dn),oi("toMat4",cn);const hn=Vi(qs).setParameterLength(2),pn=(e,t)=>Li(new Xs(Li(e),t));oi("element",hn),oi("convert",pn);oi("append",e=>(console.warn("THREE.TSL: .append() has been renamed to .toStack()."),$i(e)));class gn extends js{static get type(){return"PropertyNode"}constructor(e,t=null,r=!1){super(e),this.name=t,this.varying=r,this.isPropertyNode=!0,this.global=!0}getHash(e){return this.name||super.getHash(e)}generate(e){let t;return!0===this.varying?(t=e.getVaryingFromNode(this,this.name),t.needsInterpolation=!0):t=e.getVarFromNode(this,this.name),e.getPropertyName(t)}}const mn=(e,t)=>Li(new gn(e,t)),fn=(e,t)=>Li(new gn(e,t,!0)),yn=Ui(gn,"vec4","DiffuseColor"),bn=Ui(gn,"vec3","EmissiveColor"),xn=Ui(gn,"float","Roughness"),Tn=Ui(gn,"float","Metalness"),_n=Ui(gn,"float","Clearcoat"),vn=Ui(gn,"float","ClearcoatRoughness"),Nn=Ui(gn,"vec3","Sheen"),Sn=Ui(gn,"float","SheenRoughness"),En=Ui(gn,"float","Iridescence"),wn=Ui(gn,"float","IridescenceIOR"),An=Ui(gn,"float","IridescenceThickness"),Rn=Ui(gn,"float","AlphaT"),Cn=Ui(gn,"float","Anisotropy"),Mn=Ui(gn,"vec3","AnisotropyT"),Pn=Ui(gn,"vec3","AnisotropyB"),Bn=Ui(gn,"color","SpecularColor"),Fn=Ui(gn,"float","SpecularF90"),Ln=Ui(gn,"float","Shininess"),Dn=Ui(gn,"vec4","Output"),In=Ui(gn,"float","dashSize"),Vn=Ui(gn,"float","gapSize"),Un=Ui(gn,"float","pointWidth"),On=Ui(gn,"float","IOR"),kn=Ui(gn,"float","Transmission"),Gn=Ui(gn,"float","Thickness"),zn=Ui(gn,"float","AttenuationDistance"),Hn=Ui(gn,"color","AttenuationColor"),$n=Ui(gn,"float","Dispersion");class Wn extends js{static get type(){return"UniformGroupNode"}constructor(e,t=!1,r=1){super("string"),this.name=e,this.shared=t,this.order=r,this.isUniformGroup=!0}serialize(e){super.serialize(e),e.name=this.name,e.version=this.version,e.shared=this.shared}deserialize(e){super.deserialize(e),this.name=e.name,this.version=e.version,this.shared=e.shared}}const jn=e=>new Wn(e),qn=(e,t=0)=>new Wn(e,!0,t),Xn=qn("frame"),Kn=qn("render"),Yn=jn("object");class Qn extends ti{static get type(){return"UniformNode"}constructor(e,t=null){super(e,t),this.isUniformNode=!0,this.name="",this.groupNode=Yn}label(e){return this.name=e,this}setGroup(e){return this.groupNode=e,this}getGroup(){return this.groupNode}getUniformHash(e){return this.getHash(e)}onUpdate(e,t){const r=this.getSelf();return e=e.bind(r),super.onUpdate(t=>{const s=e(t,r);void 0!==s&&(this.value=s)},t)}generate(e,t){const r=this.getNodeType(e),s=this.getUniformHash(e);let i=e.getNodeFromHash(s);void 0===i&&(e.setHashNode(this,s),i=this);const n=i.getInputType(e),a=e.getUniformFromNode(i,n,e.shaderStage,this.name||e.context.label),o=e.getPropertyName(a);return void 0!==e.context.label&&delete e.context.label,e.format(o,r,t)}}const Zn=(e,t)=>{const r=Bi(t||e),s=e&&!0===e.isNode?e.node&&e.node.value||e.value:e;return Li(new Qn(s,r))};class Jn extends Ks{static get type(){return"ArrayNode"}constructor(e,t,r=null){super(e),this.count=t,this.values=r,this.isArrayNode=!0}getNodeType(e){return null===this.nodeType&&(this.nodeType=this.values[0].getNodeType(e)),this.nodeType}getElementType(e){return this.getNodeType(e)}generate(e){const t=this.getNodeType(e);return e.generateArray(t,this.count,this.values)}}const ea=(...e)=>{let t;if(1===e.length){const r=e[0];t=new Jn(null,r.length,r)}else{const r=e[0],s=e[1];t=new Jn(r,s)}return Li(t)};oi("toArray",(e,t)=>ea(Array(t).fill(e)));class ta extends Ks{static get type(){return"AssignNode"}constructor(e,t){super(),this.targetNode=e,this.sourceNode=t,this.isAssignNode=!0}hasDependencies(){return!1}getNodeType(e,t){return"void"!==t?this.targetNode.getNodeType(e):"void"}needsSplitAssign(e){const{targetNode:t}=this;if(!1===e.isAvailable("swizzleAssign")&&t.isSplitNode&&t.components.length>1){const r=e.getTypeLength(t.node.getNodeType(e));return Hs.join("").slice(0,r)!==t.components}return!1}setup(e){const{targetNode:t,sourceNode:r}=this,s=e.getNodeProperties(this);s.sourceNode=r,s.targetNode=t.context({assign:!0})}generate(e,t){const{targetNode:r,sourceNode:s}=e.getNodeProperties(this),i=this.needsSplitAssign(e),n=r.getNodeType(e),a=r.build(e),o=s.build(e,n),u=s.getNodeType(e),l=e.getDataFromNode(this);let d;if(!0===l.initialized)"void"!==t&&(d=a);else if(i){const s=e.getVarFromNode(this,null,n),i=e.getPropertyName(s);e.addLineFlowCode(`${i} = ${o}`,this);const u=r.node,l=u.node.context({assign:!0}).build(e);for(let t=0;t{const s=r.type;let i;return i="pointer"===s?"&"+t.build(e):t.build(e,s),i};if(Array.isArray(i)){if(i.length>s.length)console.error("THREE.TSL: The number of provided parameters exceeds the expected number of inputs in 'Fn()'."),i.length=s.length;else if(i.length(t=t.length>1||t[0]&&!0===t[0].isNode?Ii(t):Di(t[0]),Li(new sa(Li(e),t)));oi("call",ia);const na={"==":"equal","!=":"notEqual","<":"lessThan",">":"greaterThan","<=":"lessThanEqual",">=":"greaterThanEqual","%":"mod"};class aa extends Ks{static get type(){return"OperatorNode"}constructor(e,t,r,...s){if(super(),s.length>0){let i=new aa(e,t,r);for(let t=0;t>"===t||"<<"===t)return e.getIntegerType(i);if("!"===t||"&&"===t||"||"===t||"^^"===t)return"bool";if("=="===t||"!="===t||"<"===t||">"===t||"<="===t||">="===t){const t=Math.max(e.getTypeLength(i),e.getTypeLength(n));return t>1?`bvec${t}`:"bool"}if(e.isMatrix(i)){if("float"===n)return i;if(e.isVector(n))return e.getVectorFromMatrix(i);if(e.isMatrix(n))return i}else if(e.isMatrix(n)){if("float"===i)return n;if(e.isVector(i))return e.getVectorFromMatrix(n)}return e.getTypeLength(n)>e.getTypeLength(i)?n:i}generate(e,t){const r=this.op,{aNode:s,bNode:i}=this,n=this.getNodeType(e);let a=null,o=null;"void"!==n?(a=s.getNodeType(e),o=i?i.getNodeType(e):null,"<"===r||">"===r||"<="===r||">="===r||"=="===r||"!="===r?e.isVector(a)?o=a:e.isVector(o)?a=o:a!==o&&(a=o="float"):">>"===r||"<<"===r?(a=n,o=e.changeComponentType(o,"uint")):"%"===r?(a=n,o=e.isInteger(a)&&e.isInteger(o)?o:a):e.isMatrix(a)?"float"===o?o="float":e.isVector(o)?o=e.getVectorFromMatrix(a):e.isMatrix(o)||(a=o=n):a=e.isMatrix(o)?"float"===a?"float":e.isVector(a)?e.getVectorFromMatrix(o):o=n:o=n):a=o=n;const u=s.build(e,a),d=i?i.build(e,o):null,c=e.getFunctionOperator(r);if("void"!==t){const s=e.renderer.coordinateSystem===l;if("=="===r||"!="===r||"<"===r||">"===r||"<="===r||">="===r)return s&&e.isVector(a)?e.format(`${this.getOperatorMethod(e,t)}( ${u}, ${d} )`,n,t):e.format(`( ${u} ${r} ${d} )`,n,t);if("%"===r)return e.isInteger(o)?e.format(`( ${u} % ${d} )`,n,t):e.format(`${this.getOperatorMethod(e,n)}( ${u}, ${d} )`,n,t);if("!"===r||"~"===r)return e.format(`(${r}${u})`,a,t);if(c)return e.format(`${c}( ${u}, ${d} )`,n,t);if(e.isMatrix(a)&&"float"===o)return e.format(`( ${d} ${r} ${u} )`,n,t);if("float"===a&&e.isMatrix(o))return e.format(`${u} ${r} ${d}`,n,t);{let i=`( ${u} ${r} ${d} )`;return!s&&"bool"===n&&e.isVector(a)&&e.isVector(o)&&(i=`all${i}`),e.format(i,n,t)}}if("void"!==a)return c?e.format(`${c}( ${u}, ${d} )`,n,t):e.isMatrix(a)&&"float"===o?e.format(`${d} ${r} ${u}`,n,t):e.format(`${u} ${r} ${d}`,n,t)}serialize(e){super.serialize(e),e.op=this.op}deserialize(e){super.deserialize(e),this.op=e.op}}const oa=Vi(aa,"+").setParameterLength(2,1/0).setName("add"),ua=Vi(aa,"-").setParameterLength(2,1/0).setName("sub"),la=Vi(aa,"*").setParameterLength(2,1/0).setName("mul"),da=Vi(aa,"/").setParameterLength(2,1/0).setName("div"),ca=Vi(aa,"%").setParameterLength(2).setName("mod"),ha=Vi(aa,"==").setParameterLength(2).setName("equal"),pa=Vi(aa,"!=").setParameterLength(2).setName("notEqual"),ga=Vi(aa,"<").setParameterLength(2).setName("lessThan"),ma=Vi(aa,">").setParameterLength(2).setName("greaterThan"),fa=Vi(aa,"<=").setParameterLength(2).setName("lessThanEqual"),ya=Vi(aa,">=").setParameterLength(2).setName("greaterThanEqual"),ba=Vi(aa,"&&").setParameterLength(2,1/0).setName("and"),xa=Vi(aa,"||").setParameterLength(2,1/0).setName("or"),Ta=Vi(aa,"!").setParameterLength(1).setName("not"),_a=Vi(aa,"^^").setParameterLength(2).setName("xor"),va=Vi(aa,"&").setParameterLength(2).setName("bitAnd"),Na=Vi(aa,"~").setParameterLength(2).setName("bitNot"),Sa=Vi(aa,"|").setParameterLength(2).setName("bitOr"),Ea=Vi(aa,"^").setParameterLength(2).setName("bitXor"),wa=Vi(aa,"<<").setParameterLength(2).setName("shiftLeft"),Aa=Vi(aa,">>").setParameterLength(2).setName("shiftRight"),Ra=ki(([e])=>(e.addAssign(1),e)),Ca=ki(([e])=>(e.subAssign(1),e)),Ma=ki(([e])=>{const t=qi(e).toConst();return e.addAssign(1),t}),Pa=ki(([e])=>{const t=qi(e).toConst();return e.subAssign(1),t});oi("add",oa),oi("sub",ua),oi("mul",la),oi("div",da),oi("mod",ca),oi("equal",ha),oi("notEqual",pa),oi("lessThan",ga),oi("greaterThan",ma),oi("lessThanEqual",fa),oi("greaterThanEqual",ya),oi("and",ba),oi("or",xa),oi("not",Ta),oi("xor",_a),oi("bitAnd",va),oi("bitNot",Na),oi("bitOr",Sa),oi("bitXor",Ea),oi("shiftLeft",wa),oi("shiftRight",Aa),oi("incrementBefore",Ra),oi("decrementBefore",Ca),oi("increment",Ma),oi("decrement",Pa);const Ba=(e,t)=>(console.warn('THREE.TSL: "modInt()" is deprecated. Use "mod( int( ... ) )" instead.'),ca(qi(e),qi(t)));oi("modInt",Ba);class Fa extends Ks{static get type(){return"MathNode"}constructor(e,t,r=null,s=null){if(super(),(e===Fa.MAX||e===Fa.MIN)&&arguments.length>3){let i=new Fa(e,t,r);for(let t=2;tn&&i>a?t:n>a?r:a>i?s:t}getNodeType(e){const t=this.method;return t===Fa.LENGTH||t===Fa.DISTANCE||t===Fa.DOT?"float":t===Fa.CROSS?"vec3":t===Fa.ALL||t===Fa.ANY?"bool":t===Fa.EQUALS?e.changeComponentType(this.aNode.getNodeType(e),"bool"):this.getInputType(e)}setup(e){const{aNode:t,bNode:r,method:s}=this;let i=null;if(s===Fa.ONE_MINUS)i=ua(1,t);else if(s===Fa.RECIPROCAL)i=da(1,t);else if(s===Fa.DIFFERENCE)i=io(ua(t,r));else if(s===Fa.TRANSFORM_DIRECTION){let s=t,n=r;e.isMatrix(s.getNodeType(e))?n=nn(en(n),0):s=nn(en(s),0);const a=la(s,n).xyz;i=Ya(a)}return null!==i?i:super.setup(e)}generate(e,t){if(e.getNodeProperties(this).outputNode)return super.generate(e,t);let r=this.method;const s=this.getNodeType(e),i=this.getInputType(e),n=this.aNode,a=this.bNode,o=this.cNode,u=e.renderer.coordinateSystem;if(r===Fa.NEGATE)return e.format("( - "+n.build(e,i)+" )",s,t);{const c=[];return r===Fa.CROSS?c.push(n.build(e,s),a.build(e,s)):u===l&&r===Fa.STEP?c.push(n.build(e,1===e.getTypeLength(n.getNodeType(e))?"float":i),a.build(e,i)):u!==l||r!==Fa.MIN&&r!==Fa.MAX?r===Fa.REFRACT?c.push(n.build(e,i),a.build(e,i),o.build(e,"float")):r===Fa.MIX?c.push(n.build(e,i),a.build(e,i),o.build(e,1===e.getTypeLength(o.getNodeType(e))?"float":i)):(u===d&&r===Fa.ATAN&&null!==a&&(r="atan2"),"fragment"===e.shaderStage||r!==Fa.DFDX&&r!==Fa.DFDY||(console.warn(`THREE.TSL: '${r}' is not supported in the ${e.shaderStage} stage.`),r="/*"+r+"*/"),c.push(n.build(e,i)),null!==a&&c.push(a.build(e,i)),null!==o&&c.push(o.build(e,i))):c.push(n.build(e,i),a.build(e,1===e.getTypeLength(a.getNodeType(e))?"float":i)),e.format(`${e.getMethod(r,s)}( ${c.join(", ")} )`,s,t)}}serialize(e){super.serialize(e),e.method=this.method}deserialize(e){super.deserialize(e),this.method=e.method}}Fa.ALL="all",Fa.ANY="any",Fa.RADIANS="radians",Fa.DEGREES="degrees",Fa.EXP="exp",Fa.EXP2="exp2",Fa.LOG="log",Fa.LOG2="log2",Fa.SQRT="sqrt",Fa.INVERSE_SQRT="inversesqrt",Fa.FLOOR="floor",Fa.CEIL="ceil",Fa.NORMALIZE="normalize",Fa.FRACT="fract",Fa.SIN="sin",Fa.COS="cos",Fa.TAN="tan",Fa.ASIN="asin",Fa.ACOS="acos",Fa.ATAN="atan",Fa.ABS="abs",Fa.SIGN="sign",Fa.LENGTH="length",Fa.NEGATE="negate",Fa.ONE_MINUS="oneMinus",Fa.DFDX="dFdx",Fa.DFDY="dFdy",Fa.ROUND="round",Fa.RECIPROCAL="reciprocal",Fa.TRUNC="trunc",Fa.FWIDTH="fwidth",Fa.TRANSPOSE="transpose",Fa.BITCAST="bitcast",Fa.EQUALS="equals",Fa.MIN="min",Fa.MAX="max",Fa.STEP="step",Fa.REFLECT="reflect",Fa.DISTANCE="distance",Fa.DIFFERENCE="difference",Fa.DOT="dot",Fa.CROSS="cross",Fa.POW="pow",Fa.TRANSFORM_DIRECTION="transformDirection",Fa.MIX="mix",Fa.CLAMP="clamp",Fa.REFRACT="refract",Fa.SMOOTHSTEP="smoothstep",Fa.FACEFORWARD="faceforward";const La=ji(1e-6),Da=ji(1e6),Ia=ji(Math.PI),Va=ji(2*Math.PI),Ua=Vi(Fa,Fa.ALL).setParameterLength(1),Oa=Vi(Fa,Fa.ANY).setParameterLength(1),ka=Vi(Fa,Fa.RADIANS).setParameterLength(1),Ga=Vi(Fa,Fa.DEGREES).setParameterLength(1),za=Vi(Fa,Fa.EXP).setParameterLength(1),Ha=Vi(Fa,Fa.EXP2).setParameterLength(1),$a=Vi(Fa,Fa.LOG).setParameterLength(1),Wa=Vi(Fa,Fa.LOG2).setParameterLength(1),ja=Vi(Fa,Fa.SQRT).setParameterLength(1),qa=Vi(Fa,Fa.INVERSE_SQRT).setParameterLength(1),Xa=Vi(Fa,Fa.FLOOR).setParameterLength(1),Ka=Vi(Fa,Fa.CEIL).setParameterLength(1),Ya=Vi(Fa,Fa.NORMALIZE).setParameterLength(1),Qa=Vi(Fa,Fa.FRACT).setParameterLength(1),Za=Vi(Fa,Fa.SIN).setParameterLength(1),Ja=Vi(Fa,Fa.COS).setParameterLength(1),eo=Vi(Fa,Fa.TAN).setParameterLength(1),to=Vi(Fa,Fa.ASIN).setParameterLength(1),ro=Vi(Fa,Fa.ACOS).setParameterLength(1),so=Vi(Fa,Fa.ATAN).setParameterLength(1,2),io=Vi(Fa,Fa.ABS).setParameterLength(1),no=Vi(Fa,Fa.SIGN).setParameterLength(1),ao=Vi(Fa,Fa.LENGTH).setParameterLength(1),oo=Vi(Fa,Fa.NEGATE).setParameterLength(1),uo=Vi(Fa,Fa.ONE_MINUS).setParameterLength(1),lo=Vi(Fa,Fa.DFDX).setParameterLength(1),co=Vi(Fa,Fa.DFDY).setParameterLength(1),ho=Vi(Fa,Fa.ROUND).setParameterLength(1),po=Vi(Fa,Fa.RECIPROCAL).setParameterLength(1),go=Vi(Fa,Fa.TRUNC).setParameterLength(1),mo=Vi(Fa,Fa.FWIDTH).setParameterLength(1),fo=Vi(Fa,Fa.TRANSPOSE).setParameterLength(1),yo=Vi(Fa,Fa.BITCAST).setParameterLength(2),bo=(e,t)=>(console.warn('THREE.TSL: "equals" is deprecated. Use "equal" inside a vector instead, like: "bvec*( equal( ... ) )"'),ha(e,t)),xo=Vi(Fa,Fa.MIN).setParameterLength(2,1/0),To=Vi(Fa,Fa.MAX).setParameterLength(2,1/0),_o=Vi(Fa,Fa.STEP).setParameterLength(2),vo=Vi(Fa,Fa.REFLECT).setParameterLength(2),No=Vi(Fa,Fa.DISTANCE).setParameterLength(2),So=Vi(Fa,Fa.DIFFERENCE).setParameterLength(2),Eo=Vi(Fa,Fa.DOT).setParameterLength(2),wo=Vi(Fa,Fa.CROSS).setParameterLength(2),Ao=Vi(Fa,Fa.POW).setParameterLength(2),Ro=Vi(Fa,Fa.POW,2).setParameterLength(1),Co=Vi(Fa,Fa.POW,3).setParameterLength(1),Mo=Vi(Fa,Fa.POW,4).setParameterLength(1),Po=Vi(Fa,Fa.TRANSFORM_DIRECTION).setParameterLength(2),Bo=e=>la(no(e),Ao(io(e),1/3)),Fo=e=>Eo(e,e),Lo=Vi(Fa,Fa.MIX).setParameterLength(3),Do=(e,t=0,r=1)=>Li(new Fa(Fa.CLAMP,Li(e),Li(t),Li(r))),Io=e=>Do(e),Vo=Vi(Fa,Fa.REFRACT).setParameterLength(3),Uo=Vi(Fa,Fa.SMOOTHSTEP).setParameterLength(3),Oo=Vi(Fa,Fa.FACEFORWARD).setParameterLength(3),ko=ki(([e])=>{const t=Eo(e.xy,Yi(12.9898,78.233)),r=ca(t,Ia);return Qa(Za(r).mul(43758.5453))}),Go=(e,t,r)=>Lo(t,r,e),zo=(e,t,r)=>Uo(t,r,e),Ho=(e,t)=>_o(t,e),$o=(e,t)=>(console.warn('THREE.TSL: "atan2" is overloaded. Use "atan" instead.'),so(e,t)),Wo=Oo,jo=qa;oi("all",Ua),oi("any",Oa),oi("equals",bo),oi("radians",ka),oi("degrees",Ga),oi("exp",za),oi("exp2",Ha),oi("log",$a),oi("log2",Wa),oi("sqrt",ja),oi("inverseSqrt",qa),oi("floor",Xa),oi("ceil",Ka),oi("normalize",Ya),oi("fract",Qa),oi("sin",Za),oi("cos",Ja),oi("tan",eo),oi("asin",to),oi("acos",ro),oi("atan",so),oi("abs",io),oi("sign",no),oi("length",ao),oi("lengthSq",Fo),oi("negate",oo),oi("oneMinus",uo),oi("dFdx",lo),oi("dFdy",co),oi("round",ho),oi("reciprocal",po),oi("trunc",go),oi("fwidth",mo),oi("atan2",$o),oi("min",xo),oi("max",To),oi("step",Ho),oi("reflect",vo),oi("distance",No),oi("dot",Eo),oi("cross",wo),oi("pow",Ao),oi("pow2",Ro),oi("pow3",Co),oi("pow4",Mo),oi("transformDirection",Po),oi("mix",Go),oi("clamp",Do),oi("refract",Vo),oi("smoothstep",zo),oi("faceForward",Oo),oi("difference",So),oi("saturate",Io),oi("cbrt",Bo),oi("transpose",fo),oi("rand",ko);class qo extends js{static get type(){return"ConditionalNode"}constructor(e,t,r=null){super(),this.condNode=e,this.ifNode=t,this.elseNode=r}getNodeType(e){const{ifNode:t,elseNode:r}=e.getNodeProperties(this);if(void 0===t)return this.setup(e),this.getNodeType(e);const s=t.getNodeType(e);if(null!==r){const t=r.getNodeType(e);if(e.getTypeLength(t)>e.getTypeLength(s))return t}return s}setup(e){const t=this.condNode.cache(),r=this.ifNode.cache(),s=this.elseNode?this.elseNode.cache():null,i=e.context.nodeBlock;e.getDataFromNode(r).parentNodeBlock=i,null!==s&&(e.getDataFromNode(s).parentNodeBlock=i);const n=e.getNodeProperties(this);n.condNode=t,n.ifNode=r.context({nodeBlock:r}),n.elseNode=s?s.context({nodeBlock:s}):null}generate(e,t){const r=this.getNodeType(e),s=e.getDataFromNode(this);if(void 0!==s.nodeProperty)return s.nodeProperty;const{condNode:i,ifNode:n,elseNode:a}=e.getNodeProperties(this),o=e.currentFunctionNode,u="void"!==t,l=u?mn(r).build(e):"";s.nodeProperty=l;const d=i.build(e,"bool");e.addFlowCode(`\n${e.tab}if ( ${d} ) {\n\n`).addFlowTab();let c=n.build(e,r);if(c&&(u?c=l+" = "+c+";":(c="return "+c+";",null===o&&(console.warn("THREE.TSL: Return statement used in an inline 'Fn()'. Define a layout struct to allow return values."),c="// "+c))),e.removeFlowTab().addFlowCode(e.tab+"\t"+c+"\n\n"+e.tab+"}"),null!==a){e.addFlowCode(" else {\n\n").addFlowTab();let t=a.build(e,r);t&&(u?t=l+" = "+t+";":(t="return "+t+";",null===o&&(console.warn("THREE.TSL: Return statement used in an inline 'Fn()'. Define a layout struct to allow return values."),t="// "+t))),e.removeFlowTab().addFlowCode(e.tab+"\t"+t+"\n\n"+e.tab+"}\n\n")}else e.addFlowCode("\n\n");return e.format(l,r,t)}}const Xo=Vi(qo).setParameterLength(2,3);oi("select",Xo);class Ko extends js{static get type(){return"ContextNode"}constructor(e,t={}){super(),this.isContextNode=!0,this.node=e,this.value=t}getScope(){return this.node.getScope()}getNodeType(e){return this.node.getNodeType(e)}analyze(e){const t=e.getContext();e.setContext({...e.context,...this.value}),this.node.build(e),e.setContext(t)}setup(e){const t=e.getContext();e.setContext({...e.context,...this.value}),this.node.build(e),e.setContext(t)}generate(e,t){const r=e.getContext();e.setContext({...e.context,...this.value});const s=this.node.build(e,t);return e.setContext(r),s}}const Yo=Vi(Ko).setParameterLength(1,2),Qo=(e,t)=>Yo(e,{label:t});oi("context",Yo),oi("label",Qo);class Zo extends js{static get type(){return"VarNode"}constructor(e,t=null,r=!1){super(),this.node=e,this.name=t,this.global=!0,this.isVarNode=!0,this.readOnly=r,this.parents=!0}getMemberType(e,t){return this.node.getMemberType(e,t)}getElementType(e){return this.node.getElementType(e)}getNodeType(e){return this.node.getNodeType(e)}generate(e){const{node:t,name:r,readOnly:s}=this,{renderer:i}=e,n=!0===i.backend.isWebGPUBackend;let a=!1,o=!1;s&&(a=e.isDeterministic(t),o=n?s:a);const u=e.getVectorType(this.getNodeType(e)),l=t.build(e,u),d=e.getVarFromNode(this,r,u,void 0,o),c=e.getPropertyName(d);let h=c;if(o)if(n)h=a?`const ${c}`:`let ${c}`;else{const r=e.getArrayCount(t);h=`const ${e.getVar(d.type,c,r)}`}return e.addLineFlowCode(`${h} = ${l}`,this),c}}const Jo=Vi(Zo),eu=(e,t=null)=>Jo(e,t).toStack(),tu=(e,t=null)=>Jo(e,t,!0).toStack();oi("toVar",eu),oi("toConst",tu);const ru=e=>(console.warn('TSL: "temp( node )" is deprecated. Use "Var( node )" or "node.toVar()" instead.'),Jo(e));oi("temp",ru);class su extends js{static get type(){return"SubBuild"}constructor(e,t,r=null){super(r),this.node=e,this.name=t,this.isSubBuildNode=!0}getNodeType(e){if(null!==this.nodeType)return this.nodeType;e.addSubBuild(this.name);const t=this.node.getNodeType(e);return e.removeSubBuild(),t}build(e,...t){e.addSubBuild(this.name);const r=this.node.build(e,...t);return e.removeSubBuild(),r}}const iu=(e,t,r=null)=>Li(new su(Li(e),t,r));class nu extends js{static get type(){return"VaryingNode"}constructor(e,t=null){super(),this.node=e,this.name=t,this.isVaryingNode=!0,this.interpolationType=null,this.interpolationSampling=null,this.global=!0}setInterpolation(e,t=null){return this.interpolationType=e,this.interpolationSampling=t,this}getHash(e){return this.name||super.getHash(e)}getNodeType(e){return this.node.getNodeType(e)}setupVarying(e){const t=e.getNodeProperties(this);let r=t.varying;if(void 0===r){const s=this.name,i=this.getNodeType(e),n=this.interpolationType,a=this.interpolationSampling;t.varying=r=e.getVaryingFromNode(this,s,i,n,a),t.node=iu(this.node,"VERTEX")}return r.needsInterpolation||(r.needsInterpolation="fragment"===e.shaderStage),r}setup(e){this.setupVarying(e),e.flowNodeFromShaderStage(Is.VERTEX,this.node)}analyze(e){this.setupVarying(e),e.flowNodeFromShaderStage(Is.VERTEX,this.node)}generate(e){const t=e.getSubBuildProperty("property",e.currentStack),r=e.getNodeProperties(this),s=this.setupVarying(e);if(void 0===r[t]){const i=this.getNodeType(e),n=e.getPropertyName(s,Is.VERTEX);e.flowNodeFromShaderStage(Is.VERTEX,r.node,i,n),r[t]=n}return e.getPropertyName(s)}}const au=Vi(nu).setParameterLength(1,2),ou=e=>au(e);oi("toVarying",au),oi("toVertexStage",ou),oi("varying",(...e)=>(console.warn("THREE.TSL: .varying() has been renamed to .toVarying()."),au(...e))),oi("vertexStage",(...e)=>(console.warn("THREE.TSL: .vertexStage() has been renamed to .toVertexStage()."),au(...e)));const uu=ki(([e])=>{const t=e.mul(.9478672986).add(.0521327014).pow(2.4),r=e.mul(.0773993808),s=e.lessThanEqual(.04045);return Lo(t,r,s)}).setLayout({name:"sRGBTransferEOTF",type:"vec3",inputs:[{name:"color",type:"vec3"}]}),lu=ki(([e])=>{const t=e.pow(.41666).mul(1.055).sub(.055),r=e.mul(12.92),s=e.lessThanEqual(.0031308);return Lo(t,r,s)}).setLayout({name:"sRGBTransferOETF",type:"vec3",inputs:[{name:"color",type:"vec3"}]}),du="WorkingColorSpace";class cu extends Ks{static get type(){return"ColorSpaceNode"}constructor(e,t,r){super("vec4"),this.colorNode=e,this.source=t,this.target=r}resolveColorSpace(e,t){return t===du?c.workingColorSpace:"OutputColorSpace"===t?e.context.outputColorSpace||e.renderer.outputColorSpace:t}setup(e){const{colorNode:t}=this,r=this.resolveColorSpace(e,this.source),s=this.resolveColorSpace(e,this.target);let i=t;return!1!==c.enabled&&r!==s&&r&&s?(c.getTransfer(r)===h&&(i=nn(uu(i.rgb),i.a)),c.getPrimaries(r)!==c.getPrimaries(s)&&(i=nn(dn(c._getMatrix(new n,r,s)).mul(i.rgb),i.a)),c.getTransfer(s)===h&&(i=nn(lu(i.rgb),i.a)),i):i}}const hu=(e,t)=>Li(new cu(Li(e),du,t)),pu=(e,t)=>Li(new cu(Li(e),t,du));oi("workingToColorSpace",hu),oi("colorSpaceToWorking",pu);let gu=class extends qs{static get type(){return"ReferenceElementNode"}constructor(e,t){super(e,t),this.referenceNode=e,this.isReferenceElementNode=!0}getNodeType(){return this.referenceNode.uniformType}generate(e){const t=super.generate(e),r=this.referenceNode.getNodeType(),s=this.getNodeType();return e.format(t,r,s)}};class mu extends js{static get type(){return"ReferenceBaseNode"}constructor(e,t,r=null,s=null){super(),this.property=e,this.uniformType=t,this.object=r,this.count=s,this.properties=e.split("."),this.reference=r,this.node=null,this.group=null,this.updateType=Vs.OBJECT}setGroup(e){return this.group=e,this}element(e){return Li(new gu(this,Li(e)))}setNodeType(e){const t=Zn(null,e).getSelf();null!==this.group&&t.setGroup(this.group),this.node=t}getNodeType(e){return null===this.node&&(this.updateReference(e),this.updateValue()),this.node.getNodeType(e)}getValueFromReference(e=this.reference){const{properties:t}=this;let r=e[t[0]];for(let e=1;eLi(new fu(e,t,r));class bu extends Ks{static get type(){return"ToneMappingNode"}constructor(e,t=Tu,r=null){super("vec3"),this.toneMapping=e,this.exposureNode=t,this.colorNode=r}customCacheKey(){return Ts(this.toneMapping)}setup(e){const t=this.colorNode||e.context.color,r=this.toneMapping;if(r===p)return t;let s=null;const i=e.renderer.library.getToneMappingFunction(r);return null!==i?s=nn(i(t.rgb,this.exposureNode),t.a):(console.error("ToneMappingNode: Unsupported Tone Mapping configuration.",r),s=t),s}}const xu=(e,t,r)=>Li(new bu(e,Li(t),Li(r))),Tu=yu("toneMappingExposure","float");oi("toneMapping",(e,t,r)=>xu(t,r,e));class _u extends ti{static get type(){return"BufferAttributeNode"}constructor(e,t=null,r=0,s=0){super(e,t),this.isBufferNode=!0,this.bufferType=t,this.bufferStride=r,this.bufferOffset=s,this.usage=g,this.instanced=!1,this.attribute=null,this.global=!0,e&&!0===e.isBufferAttribute&&(this.attribute=e,this.usage=e.usage,this.instanced=e.isInstancedBufferAttribute)}getHash(e){if(0===this.bufferStride&&0===this.bufferOffset){let t=e.globalCache.getData(this.value);return void 0===t&&(t={node:this},e.globalCache.setData(this.value,t)),t.node.uuid}return this.uuid}getNodeType(e){return null===this.bufferType&&(this.bufferType=e.getTypeFromAttribute(this.attribute)),this.bufferType}setup(e){if(null!==this.attribute)return;const t=this.getNodeType(e),r=this.value,s=e.getTypeLength(t),i=this.bufferStride||s,n=this.bufferOffset,a=!0===r.isInterleavedBuffer?r:new m(r,i),o=new f(a,s,n);a.setUsage(this.usage),this.attribute=o,this.attribute.isInstancedBufferAttribute=this.instanced}generate(e){const t=this.getNodeType(e),r=e.getBufferAttributeFromNode(this,t),s=e.getPropertyName(r);let i=null;if("vertex"===e.shaderStage||"compute"===e.shaderStage)this.name=s,i=s;else{i=au(this).build(e,t)}return i}getInputType(){return"bufferAttribute"}setUsage(e){return this.usage=e,this.attribute&&!0===this.attribute.isBufferAttribute&&(this.attribute.usage=e),this}setInstanced(e){return this.instanced=e,this}}const vu=(e,t=null,r=0,s=0)=>Li(new _u(e,t,r,s)),Nu=(e,t=null,r=0,s=0)=>vu(e,t,r,s).setUsage(y),Su=(e,t=null,r=0,s=0)=>vu(e,t,r,s).setInstanced(!0),Eu=(e,t=null,r=0,s=0)=>Nu(e,t,r,s).setInstanced(!0);oi("toAttribute",e=>vu(e.value));class wu extends js{static get type(){return"ComputeNode"}constructor(e,t,r=[64]){super("void"),this.isComputeNode=!0,this.computeNode=e,this.count=t,this.workgroupSize=r,this.dispatchCount=0,this.version=1,this.name="",this.updateBeforeType=Vs.OBJECT,this.onInitFunction=null,this.updateDispatchCount()}dispose(){this.dispatchEvent({type:"dispose"})}label(e){return this.name=e,this}updateDispatchCount(){const{count:e,workgroupSize:t}=this;let r=t[0];for(let e=1;eLi(new wu(Li(e),t,r));oi("compute",Au);class Ru extends js{static get type(){return"CacheNode"}constructor(e,t=!0){super(),this.node=e,this.parent=t,this.isCacheNode=!0}getNodeType(e){const t=e.getCache(),r=e.getCacheFromNode(this,this.parent);e.setCache(r);const s=this.node.getNodeType(e);return e.setCache(t),s}build(e,...t){const r=e.getCache(),s=e.getCacheFromNode(this,this.parent);e.setCache(s);const i=this.node.build(e,...t);return e.setCache(r),i}}const Cu=(e,t)=>Li(new Ru(Li(e),t));oi("cache",Cu);class Mu extends js{static get type(){return"BypassNode"}constructor(e,t){super(),this.isBypassNode=!0,this.outputNode=e,this.callNode=t}getNodeType(e){return this.outputNode.getNodeType(e)}generate(e){const t=this.callNode.build(e,"void");return""!==t&&e.addLineFlowCode(t,this),this.outputNode.build(e)}}const Pu=Vi(Mu).setParameterLength(2);oi("bypass",Pu);class Bu extends js{static get type(){return"RemapNode"}constructor(e,t,r,s=ji(0),i=ji(1)){super(),this.node=e,this.inLowNode=t,this.inHighNode=r,this.outLowNode=s,this.outHighNode=i,this.doClamp=!0}setup(){const{node:e,inLowNode:t,inHighNode:r,outLowNode:s,outHighNode:i,doClamp:n}=this;let a=e.sub(t).div(r.sub(t));return!0===n&&(a=a.clamp()),a.mul(i.sub(s)).add(s)}}const Fu=Vi(Bu,null,null,{doClamp:!1}).setParameterLength(3,5),Lu=Vi(Bu).setParameterLength(3,5);oi("remap",Fu),oi("remapClamp",Lu);class Du extends js{static get type(){return"ExpressionNode"}constructor(e="",t="void"){super(t),this.snippet=e}generate(e,t){const r=this.getNodeType(e),s=this.snippet;if("void"!==r)return e.format(s,r,t);e.addLineFlowCode(s,this)}}const Iu=Vi(Du).setParameterLength(1,2),Vu=e=>(e?Xo(e,Iu("discard")):Iu("discard")).toStack();oi("discard",Vu);class Uu extends Ks{static get type(){return"RenderOutputNode"}constructor(e,t,r){super("vec4"),this.colorNode=e,this.toneMapping=t,this.outputColorSpace=r,this.isRenderOutputNode=!0}setup({context:e}){let t=this.colorNode||e.color;const r=(null!==this.toneMapping?this.toneMapping:e.toneMapping)||p,s=(null!==this.outputColorSpace?this.outputColorSpace:e.outputColorSpace)||b;return r!==p&&(t=t.toneMapping(r)),s!==b&&s!==c.workingColorSpace&&(t=t.workingToColorSpace(s)),t}}const Ou=(e,t=null,r=null)=>Li(new Uu(Li(e),t,r));oi("renderOutput",Ou);class ku extends Ks{static get type(){return"DebugNode"}constructor(e,t=null){super(),this.node=e,this.callback=t}getNodeType(e){return this.node.getNodeType(e)}setup(e){return this.node.build(e)}analyze(e){return this.node.build(e)}generate(e){const t=this.callback,r=this.node.build(e),s="--- TSL debug - "+e.shaderStage+" shader ---",i="-".repeat(s.length);let n="";return n+="// #"+s+"#\n",n+=e.flow.code.replace(/^\t/gm,"")+"\n",n+="/* ... */ "+r+" /* ... */\n",n+="// #"+i+"#\n",null!==t?t(e,n):console.log(n),r}}const Gu=(e,t=null)=>Li(new ku(Li(e),t));oi("debug",Gu);class zu extends js{static get type(){return"AttributeNode"}constructor(e,t=null){super(t),this.global=!0,this._attributeName=e}getHash(e){return this.getAttributeName(e)}getNodeType(e){let t=this.nodeType;if(null===t){const r=this.getAttributeName(e);if(e.hasGeometryAttribute(r)){const s=e.geometry.getAttribute(r);t=e.getTypeFromAttribute(s)}else t="float"}return t}setAttributeName(e){return this._attributeName=e,this}getAttributeName(){return this._attributeName}generate(e){const t=this.getAttributeName(e),r=this.getNodeType(e);if(!0===e.hasGeometryAttribute(t)){const s=e.geometry.getAttribute(t),i=e.getTypeFromAttribute(s),n=e.getAttribute(t,i);if("vertex"===e.shaderStage)return e.format(n.name,i,r);return au(this).build(e,r)}return console.warn(`AttributeNode: Vertex attribute "${t}" not found on geometry.`),e.generateConst(r)}serialize(e){super.serialize(e),e.global=this.global,e._attributeName=this._attributeName}deserialize(e){super.deserialize(e),this.global=e.global,this._attributeName=e._attributeName}}const Hu=(e,t=null)=>Li(new zu(e,t)),$u=(e=0)=>Hu("uv"+(e>0?e:""),"vec2");class Wu extends js{static get type(){return"TextureSizeNode"}constructor(e,t=null){super("uvec2"),this.isTextureSizeNode=!0,this.textureNode=e,this.levelNode=t}generate(e,t){const r=this.textureNode.build(e,"property"),s=null===this.levelNode?"0":this.levelNode.build(e,"int");return e.format(`${e.getMethod("textureDimensions")}( ${r}, ${s} )`,this.getNodeType(e),t)}}const ju=Vi(Wu).setParameterLength(1,2);class qu extends Qn{static get type(){return"MaxMipLevelNode"}constructor(e){super(0),this._textureNode=e,this.updateType=Vs.FRAME}get textureNode(){return this._textureNode}get texture(){return this._textureNode.value}update(){const e=this.texture,t=e.images,r=t&&t.length>0?t[0]&&t[0].image||t[0]:e.image;if(r&&void 0!==r.width){const{width:e,height:t}=r;this.value=Math.log2(Math.max(e,t))}}}const Xu=Vi(qu).setParameterLength(1),Ku=new x;class Yu extends Qn{static get type(){return"TextureNode"}constructor(e=Ku,t=null,r=null,s=null){super(e),this.isTextureNode=!0,this.uvNode=t,this.levelNode=r,this.biasNode=s,this.compareNode=null,this.depthNode=null,this.gradNode=null,this.sampler=!0,this.updateMatrix=!1,this.updateType=Vs.NONE,this.referenceNode=null,this._value=e,this._matrixUniform=null,this.setUpdateMatrix(null===t)}set value(e){this.referenceNode?this.referenceNode.value=e:this._value=e}get value(){return this.referenceNode?this.referenceNode.value:this._value}getUniformHash(){return this.value.uuid}getNodeType(){return!0===this.value.isDepthTexture?"float":this.value.type===T?"uvec4":this.value.type===_?"ivec4":"vec4"}getInputType(){return"texture"}getDefaultUV(){return $u(this.value.channel)}updateReference(){return this.value}getTransformedUV(e){return null===this._matrixUniform&&(this._matrixUniform=Zn(this.value.matrix)),this._matrixUniform.mul(en(e,1)).xy}setUpdateMatrix(e){return this.updateMatrix=e,this.updateType=e?Vs.OBJECT:Vs.NONE,this}setupUV(e,t){const r=this.value;return e.isFlipY()&&(r.image instanceof ImageBitmap&&!0===r.flipY||!0===r.isRenderTargetTexture||!0===r.isFramebufferTexture||!0===r.isDepthTexture)&&(t=this.sampler?t.flipY():t.setY(qi(ju(this,this.levelNode).y).sub(t.y).sub(1))),t}setup(e){const t=e.getNodeProperties(this);t.referenceNode=this.referenceNode;const r=this.value;if(!r||!0!==r.isTexture)throw new Error("THREE.TSL: `texture( value )` function expects a valid instance of THREE.Texture().");let s=this.uvNode;null!==s&&!0!==e.context.forceUVContext||!e.context.getUV||(s=e.context.getUV(this,e)),s||(s=this.getDefaultUV()),!0===this.updateMatrix&&(s=this.getTransformedUV(s)),s=this.setupUV(e,s);let i=this.levelNode;null===i&&e.context.getTextureLevel&&(i=e.context.getTextureLevel(this)),t.uvNode=s,t.levelNode=i,t.biasNode=this.biasNode,t.compareNode=this.compareNode,t.gradNode=this.gradNode,t.depthNode=this.depthNode}generateUV(e,t){return t.build(e,!0===this.sampler?"vec2":"ivec2")}generateSnippet(e,t,r,s,i,n,a,o){const u=this.value;let l;return l=s?e.generateTextureLevel(u,t,r,s,n):i?e.generateTextureBias(u,t,r,i,n):o?e.generateTextureGrad(u,t,r,o,n):a?e.generateTextureCompare(u,t,r,a,n):!1===this.sampler?e.generateTextureLoad(u,t,r,n):e.generateTexture(u,t,r,n),l}generate(e,t){const r=this.value,s=e.getNodeProperties(this),i=super.generate(e,"property");if(/^sampler/.test(t))return i+"_sampler";if(e.isReference(t))return i;{const n=e.getDataFromNode(this);let a=n.propertyName;if(void 0===a){const{uvNode:t,levelNode:r,biasNode:o,compareNode:u,depthNode:l,gradNode:d}=s,c=this.generateUV(e,t),h=r?r.build(e,"float"):null,p=o?o.build(e,"float"):null,g=l?l.build(e,"int"):null,m=u?u.build(e,"float"):null,f=d?[d[0].build(e,"vec2"),d[1].build(e,"vec2")]:null,y=e.getVarFromNode(this);a=e.getPropertyName(y);const b=this.generateSnippet(e,i,c,h,p,g,m,f);e.addLineFlowCode(`${a} = ${b}`,this),n.snippet=b,n.propertyName=a}let o=a;const u=this.getNodeType(e);return e.needsToWorkingColorSpace(r)&&(o=pu(Iu(o,u),r.colorSpace).setup(e).build(e,u)),e.format(o,u,t)}}setSampler(e){return this.sampler=e,this}getSampler(){return this.sampler}uv(e){return console.warn("THREE.TextureNode: .uv() has been renamed. Use .sample() instead."),this.sample(e)}sample(e){const t=this.clone();return t.uvNode=Li(e),t.referenceNode=this.getSelf(),Li(t)}blur(e){const t=this.clone();t.biasNode=Li(e).mul(Xu(t)),t.referenceNode=this.getSelf();const r=t.value;return!1===t.generateMipmaps&&(r&&!1===r.generateMipmaps||r.minFilter===v||r.magFilter===v)&&(console.warn("THREE.TSL: texture().blur() requires mipmaps and sampling. Use .generateMipmaps=true and .minFilter/.magFilter=THREE.LinearFilter in the Texture."),t.biasNode=null),Li(t)}level(e){const t=this.clone();return t.levelNode=Li(e),t.referenceNode=this.getSelf(),Li(t)}size(e){return ju(this,e)}bias(e){const t=this.clone();return t.biasNode=Li(e),t.referenceNode=this.getSelf(),Li(t)}compare(e){const t=this.clone();return t.compareNode=Li(e),t.referenceNode=this.getSelf(),Li(t)}grad(e,t){const r=this.clone();return r.gradNode=[Li(e),Li(t)],r.referenceNode=this.getSelf(),Li(r)}depth(e){const t=this.clone();return t.depthNode=Li(e),t.referenceNode=this.getSelf(),Li(t)}serialize(e){super.serialize(e),e.value=this.value.toJSON(e.meta).uuid,e.sampler=this.sampler,e.updateMatrix=this.updateMatrix,e.updateType=this.updateType}deserialize(e){super.deserialize(e),this.value=e.meta.textures[e.value],this.sampler=e.sampler,this.updateMatrix=e.updateMatrix,this.updateType=e.updateType}update(){const e=this.value,t=this._matrixUniform;null!==t&&(t.value=e.matrix),!0===e.matrixAutoUpdate&&e.updateMatrix()}clone(){const e=new this.constructor(this.value,this.uvNode,this.levelNode,this.biasNode);return e.sampler=this.sampler,e.depthNode=this.depthNode,e.compareNode=this.compareNode,e.gradNode=this.gradNode,e}}const Qu=Vi(Yu).setParameterLength(1,4).setName("texture"),Zu=(e=Ku,t=null,r=null,s=null)=>{let i;return e&&!0===e.isTextureNode?(i=Li(e.clone()),i.referenceNode=e.getSelf(),null!==t&&(i.uvNode=Li(t)),null!==r&&(i.levelNode=Li(r)),null!==s&&(i.biasNode=Li(s))):i=Qu(e,t,r,s),i},Ju=(...e)=>Zu(...e).setSampler(!1);class el extends Qn{static get type(){return"BufferNode"}constructor(e,t,r=0){super(e,t),this.isBufferNode=!0,this.bufferType=t,this.bufferCount=r}getElementType(e){return this.getNodeType(e)}getInputType(){return"buffer"}}const tl=(e,t,r)=>Li(new el(e,t,r));class rl extends qs{static get type(){return"UniformArrayElementNode"}constructor(e,t){super(e,t),this.isArrayBufferElementNode=!0}generate(e){const t=super.generate(e),r=this.getNodeType(),s=this.node.getPaddedType();return e.format(t,s,r)}}class sl extends el{static get type(){return"UniformArrayNode"}constructor(e,t=null){super(null),this.array=e,this.elementType=null===t?Ms(e[0]):t,this.paddedType=this.getPaddedType(),this.updateType=Vs.RENDER,this.isArrayBufferNode=!0}getNodeType(){return this.paddedType}getElementType(){return this.elementType}getPaddedType(){const e=this.elementType;let t="vec4";return"mat2"===e?t="mat2":!0===/mat/.test(e)?t="mat4":"i"===e.charAt(0)?t="ivec4":"u"===e.charAt(0)&&(t="uvec4"),t}update(){const{array:e,value:t}=this,r=this.elementType;if("float"===r||"int"===r||"uint"===r)for(let r=0;rLi(new sl(e,t));const nl=Vi(class extends js{constructor(e){super("float"),this.name=e,this.isBuiltinNode=!0}generate(){return this.name}}).setParameterLength(1),al=Zn(0,"uint").label("u_cameraIndex").setGroup(qn("cameraIndex")).toVarying("v_cameraIndex"),ol=Zn("float").label("cameraNear").setGroup(Kn).onRenderUpdate(({camera:e})=>e.near),ul=Zn("float").label("cameraFar").setGroup(Kn).onRenderUpdate(({camera:e})=>e.far),ll=ki(({camera:e})=>{let t;if(e.isArrayCamera&&e.cameras.length>0){const r=[];for(const t of e.cameras)r.push(t.projectionMatrix);t=il(r).setGroup(Kn).label("cameraProjectionMatrices").element(e.isMultiViewCamera?nl("gl_ViewID_OVR"):al).toVar("cameraProjectionMatrix")}else t=Zn("mat4").label("cameraProjectionMatrix").setGroup(Kn).onRenderUpdate(({camera:e})=>e.projectionMatrix);return t}).once()(),dl=ki(({camera:e})=>{let t;if(e.isArrayCamera&&e.cameras.length>0){const r=[];for(const t of e.cameras)r.push(t.projectionMatrixInverse);t=il(r).setGroup(Kn).label("cameraProjectionMatricesInverse").element(e.isMultiViewCamera?nl("gl_ViewID_OVR"):al).toVar("cameraProjectionMatrixInverse")}else t=Zn("mat4").label("cameraProjectionMatrixInverse").setGroup(Kn).onRenderUpdate(({camera:e})=>e.projectionMatrixInverse);return t}).once()(),cl=ki(({camera:e})=>{let t;if(e.isArrayCamera&&e.cameras.length>0){const r=[];for(const t of e.cameras)r.push(t.matrixWorldInverse);t=il(r).setGroup(Kn).label("cameraViewMatrices").element(e.isMultiViewCamera?nl("gl_ViewID_OVR"):al).toVar("cameraViewMatrix")}else t=Zn("mat4").label("cameraViewMatrix").setGroup(Kn).onRenderUpdate(({camera:e})=>e.matrixWorldInverse);return t}).once()(),hl=Zn("mat4").label("cameraWorldMatrix").setGroup(Kn).onRenderUpdate(({camera:e})=>e.matrixWorld),pl=Zn("mat3").label("cameraNormalMatrix").setGroup(Kn).onRenderUpdate(({camera:e})=>e.normalMatrix),gl=Zn(new r).label("cameraPosition").setGroup(Kn).onRenderUpdate(({camera:e},t)=>t.value.setFromMatrixPosition(e.matrixWorld)),ml=new N;class fl extends js{static get type(){return"Object3DNode"}constructor(e,t=null){super(),this.scope=e,this.object3d=t,this.updateType=Vs.OBJECT,this.uniformNode=new Qn(null)}getNodeType(){const e=this.scope;return e===fl.WORLD_MATRIX?"mat4":e===fl.POSITION||e===fl.VIEW_POSITION||e===fl.DIRECTION||e===fl.SCALE?"vec3":e===fl.RADIUS?"float":void 0}update(e){const t=this.object3d,s=this.uniformNode,i=this.scope;if(i===fl.WORLD_MATRIX)s.value=t.matrixWorld;else if(i===fl.POSITION)s.value=s.value||new r,s.value.setFromMatrixPosition(t.matrixWorld);else if(i===fl.SCALE)s.value=s.value||new r,s.value.setFromMatrixScale(t.matrixWorld);else if(i===fl.DIRECTION)s.value=s.value||new r,t.getWorldDirection(s.value);else if(i===fl.VIEW_POSITION){const i=e.camera;s.value=s.value||new r,s.value.setFromMatrixPosition(t.matrixWorld),s.value.applyMatrix4(i.matrixWorldInverse)}else if(i===fl.RADIUS){const r=e.object.geometry;null===r.boundingSphere&&r.computeBoundingSphere(),ml.copy(r.boundingSphere).applyMatrix4(t.matrixWorld),s.value=ml.radius}}generate(e){const t=this.scope;return t===fl.WORLD_MATRIX?this.uniformNode.nodeType="mat4":t===fl.POSITION||t===fl.VIEW_POSITION||t===fl.DIRECTION||t===fl.SCALE?this.uniformNode.nodeType="vec3":t===fl.RADIUS&&(this.uniformNode.nodeType="float"),this.uniformNode.build(e)}serialize(e){super.serialize(e),e.scope=this.scope}deserialize(e){super.deserialize(e),this.scope=e.scope}}fl.WORLD_MATRIX="worldMatrix",fl.POSITION="position",fl.SCALE="scale",fl.VIEW_POSITION="viewPosition",fl.DIRECTION="direction",fl.RADIUS="radius";const yl=Vi(fl,fl.DIRECTION).setParameterLength(1),bl=Vi(fl,fl.WORLD_MATRIX).setParameterLength(1),xl=Vi(fl,fl.POSITION).setParameterLength(1),Tl=Vi(fl,fl.SCALE).setParameterLength(1),_l=Vi(fl,fl.VIEW_POSITION).setParameterLength(1),vl=Vi(fl,fl.RADIUS).setParameterLength(1);class Nl extends fl{static get type(){return"ModelNode"}constructor(e){super(e)}update(e){this.object3d=e.object,super.update(e)}}const Sl=Ui(Nl,Nl.DIRECTION),El=Ui(Nl,Nl.WORLD_MATRIX),wl=Ui(Nl,Nl.POSITION),Al=Ui(Nl,Nl.SCALE),Rl=Ui(Nl,Nl.VIEW_POSITION),Cl=Ui(Nl,Nl.RADIUS),Ml=Zn(new n).onObjectUpdate(({object:e},t)=>t.value.getNormalMatrix(e.matrixWorld)),Pl=Zn(new a).onObjectUpdate(({object:e},t)=>t.value.copy(e.matrixWorld).invert()),Bl=ki(e=>e.renderer.overrideNodes.modelViewMatrix||Fl).once()().toVar("modelViewMatrix"),Fl=cl.mul(El),Ll=ki(e=>(e.context.isHighPrecisionModelViewMatrix=!0,Zn("mat4").onObjectUpdate(({object:e,camera:t})=>e.modelViewMatrix.multiplyMatrices(t.matrixWorldInverse,e.matrixWorld)))).once()().toVar("highpModelViewMatrix"),Dl=ki(e=>{const t=e.context.isHighPrecisionModelViewMatrix;return Zn("mat3").onObjectUpdate(({object:e,camera:r})=>(!0!==t&&e.modelViewMatrix.multiplyMatrices(r.matrixWorldInverse,e.matrixWorld),e.normalMatrix.getNormalMatrix(e.modelViewMatrix)))}).once()().toVar("highpModelNormalViewMatrix"),Il=Hu("position","vec3"),Vl=Il.toVarying("positionLocal"),Ul=Il.toVarying("positionPrevious"),Ol=ki(e=>El.mul(Vl).xyz.toVarying(e.getSubBuildProperty("v_positionWorld")),"vec3").once(["POSITION"])(),kl=ki(()=>Vl.transformDirection(El).toVarying("v_positionWorldDirection").normalize().toVar("positionWorldDirection"),"vec3").once(["POSITION"])(),Gl=ki(e=>e.context.setupPositionView().toVarying("v_positionView"),"vec3").once(["POSITION"])(),zl=Gl.negate().toVarying("v_positionViewDirection").normalize().toVar("positionViewDirection");class Hl extends js{static get type(){return"FrontFacingNode"}constructor(){super("bool"),this.isFrontFacingNode=!0}generate(e){if("fragment"!==e.shaderStage)return"true";const{renderer:t,material:r}=e;return t.coordinateSystem===l&&r.side===S?"false":e.getFrontFacing()}}const $l=Ui(Hl),Wl=ji($l).mul(2).sub(1),jl=ki(([e],{material:t})=>{const r=t.side;return r===S?e=e.mul(-1):r===E&&(e=e.mul(Wl)),e}),ql=Hu("normal","vec3"),Xl=ki(e=>!1===e.geometry.hasAttribute("normal")?(console.warn('THREE.TSL: Vertex attribute "normal" not found on geometry.'),en(0,1,0)):ql,"vec3").once()().toVar("normalLocal"),Kl=Gl.dFdx().cross(Gl.dFdy()).normalize().toVar("normalFlat"),Yl=ki(e=>{let t;return t=!0===e.material.flatShading?Kl:rd(Xl).toVarying("v_normalViewGeometry").normalize(),t},"vec3").once()().toVar("normalViewGeometry"),Ql=ki(e=>{let t=Yl.transformDirection(cl);return!0!==e.material.flatShading&&(t=t.toVarying("v_normalWorldGeometry")),t.normalize().toVar("normalWorldGeometry")},"vec3").once()(),Zl=ki(({subBuildFn:e,material:t,context:r})=>{let s;return"NORMAL"===e||"VERTEX"===e?(s=Yl,!0!==t.flatShading&&(s=jl(s))):s=r.setupNormal().context({getUV:null}),s},"vec3").once(["NORMAL","VERTEX"])().toVar("normalView"),Jl=Zl.transformDirection(cl).toVar("normalWorld"),ed=ki(({subBuildFn:e,context:t})=>{let r;return r="NORMAL"===e||"VERTEX"===e?Zl:t.setupClearcoatNormal().context({getUV:null}),r},"vec3").once(["NORMAL","VERTEX"])().toVar("clearcoatNormalView"),td=ki(([e,t=El])=>{const r=dn(t),s=e.div(en(r[0].dot(r[0]),r[1].dot(r[1]),r[2].dot(r[2])));return r.mul(s).xyz}),rd=ki(([e],t)=>{const r=t.renderer.overrideNodes.modelNormalViewMatrix;if(null!==r)return r.transformDirection(e);const s=Ml.mul(e);return cl.transformDirection(s)}),sd=ki(()=>(console.warn('THREE.TSL: "transformedNormalView" is deprecated. Use "normalView" instead.'),Zl)).once(["NORMAL","VERTEX"])(),id=ki(()=>(console.warn('THREE.TSL: "transformedNormalWorld" is deprecated. Use "normalWorld" instead.'),Jl)).once(["NORMAL","VERTEX"])(),nd=ki(()=>(console.warn('THREE.TSL: "transformedClearcoatNormalView" is deprecated. Use "clearcoatNormalView" instead.'),ed)).once(["NORMAL","VERTEX"])(),ad=new w,od=new a,ud=Zn(0).onReference(({material:e})=>e).onObjectUpdate(({material:e})=>e.refractionRatio),ld=Zn(1).onReference(({material:e})=>e).onObjectUpdate(function({material:e,scene:t}){return e.envMap?e.envMapIntensity:t.environmentIntensity}),dd=Zn(new a).onReference(function(e){return e.material}).onObjectUpdate(function({material:e,scene:t}){const r=null!==t.environment&&null===e.envMap?t.environmentRotation:e.envMapRotation;return r?(ad.copy(r),od.makeRotationFromEuler(ad)):od.identity(),od}),cd=zl.negate().reflect(Zl),hd=zl.negate().refract(Zl,ud),pd=cd.transformDirection(cl).toVar("reflectVector"),gd=hd.transformDirection(cl).toVar("reflectVector"),md=new A;class fd extends Yu{static get type(){return"CubeTextureNode"}constructor(e,t=null,r=null,s=null){super(e,t,r,s),this.isCubeTextureNode=!0}getInputType(){return"cubeTexture"}getDefaultUV(){const e=this.value;return e.mapping===R?pd:e.mapping===C?gd:(console.error('THREE.CubeTextureNode: Mapping "%s" not supported.',e.mapping),en(0,0,0))}setUpdateMatrix(){}setupUV(e,t){const r=this.value;return e.renderer.coordinateSystem!==d&&r.isRenderTargetTexture||(t=en(t.x.negate(),t.yz)),dd.mul(t)}generateUV(e,t){return t.build(e,"vec3")}}const yd=Vi(fd).setParameterLength(1,4).setName("cubeTexture"),bd=(e=md,t=null,r=null,s=null)=>{let i;return e&&!0===e.isCubeTextureNode?(i=Li(e.clone()),i.referenceNode=e.getSelf(),null!==t&&(i.uvNode=Li(t)),null!==r&&(i.levelNode=Li(r)),null!==s&&(i.biasNode=Li(s))):i=yd(e,t,r,s),i};class xd extends qs{static get type(){return"ReferenceElementNode"}constructor(e,t){super(e,t),this.referenceNode=e,this.isReferenceElementNode=!0}getNodeType(){return this.referenceNode.uniformType}generate(e){const t=super.generate(e),r=this.referenceNode.getNodeType(),s=this.getNodeType();return e.format(t,r,s)}}class Td extends js{static get type(){return"ReferenceNode"}constructor(e,t,r=null,s=null){super(),this.property=e,this.uniformType=t,this.object=r,this.count=s,this.properties=e.split("."),this.reference=r,this.node=null,this.group=null,this.name=null,this.updateType=Vs.OBJECT}element(e){return Li(new xd(this,Li(e)))}setGroup(e){return this.group=e,this}label(e){return this.name=e,this}setNodeType(e){let t=null;t=null!==this.count?tl(null,e,this.count):Array.isArray(this.getValueFromReference())?il(null,e):"texture"===e?Zu(null):"cubeTexture"===e?bd(null):Zn(null,e),null!==this.group&&t.setGroup(this.group),null!==this.name&&t.label(this.name),this.node=t.getSelf()}getNodeType(e){return null===this.node&&(this.updateReference(e),this.updateValue()),this.node.getNodeType(e)}getValueFromReference(e=this.reference){const{properties:t}=this;let r=e[t[0]];for(let e=1;eLi(new Td(e,t,r)),vd=(e,t,r,s)=>Li(new Td(e,t,s,r));class Nd extends Td{static get type(){return"MaterialReferenceNode"}constructor(e,t,r=null){super(e,t,r),this.material=r,this.isMaterialReferenceNode=!0}updateReference(e){return this.reference=null!==this.material?this.material:e.material,this.reference}}const Sd=(e,t,r=null)=>Li(new Nd(e,t,r)),Ed=$u(),wd=Gl.dFdx(),Ad=Gl.dFdy(),Rd=Ed.dFdx(),Cd=Ed.dFdy(),Md=Zl,Pd=Ad.cross(Md),Bd=Md.cross(wd),Fd=Pd.mul(Rd.x).add(Bd.mul(Cd.x)),Ld=Pd.mul(Rd.y).add(Bd.mul(Cd.y)),Dd=Fd.dot(Fd).max(Ld.dot(Ld)),Id=Dd.equal(0).select(0,Dd.inverseSqrt()),Vd=Fd.mul(Id).toVar("tangentViewFrame"),Ud=Ld.mul(Id).toVar("bitangentViewFrame"),Od=ki(e=>(!1===e.geometry.hasAttribute("tangent")&&e.geometry.computeTangents(),Hu("tangent","vec4")))(),kd=Od.xyz.toVar("tangentLocal"),Gd=ki(({subBuildFn:e,geometry:t,material:r})=>{let s;return s="VERTEX"===e||t.hasAttribute("tangent")?Bl.mul(nn(kd,0)).xyz.toVarying("v_tangentView").normalize():Vd,!0!==r.flatShading&&(s=jl(s)),s},"vec3").once(["NORMAL","VERTEX"])().toVar("tangentView"),zd=Gd.transformDirection(cl).toVarying("v_tangentWorld").normalize().toVar("tangentWorld"),Hd=ki(([e,t],{subBuildFn:r,material:s})=>{let i=e.mul(Od.w).xyz;return"NORMAL"===r&&!0!==s.flatShading&&(i=i.toVarying(t)),i}).once(["NORMAL"]),$d=Hd(ql.cross(Od),"v_bitangentGeometry").normalize().toVar("bitangentGeometry"),Wd=Hd(Xl.cross(kd),"v_bitangentLocal").normalize().toVar("bitangentLocal"),jd=ki(({subBuildFn:e,geometry:t,material:r})=>{let s;return s="VERTEX"===e||t.hasAttribute("tangent")?Hd(Zl.cross(Gd),"v_bitangentView").normalize():Ud,!0!==r.flatShading&&(s=jl(s)),s},"vec3").once(["NORMAL","VERTEX"])().toVar("bitangentView"),qd=Hd(Jl.cross(zd),"v_bitangentWorld").normalize().toVar("bitangentWorld"),Xd=dn(Gd,jd,Zl).toVar("TBNViewMatrix"),Kd=zl.mul(Xd),Yd=ki(()=>{let e=Pn.cross(zl);return e=e.cross(Pn).normalize(),e=Lo(e,Zl,Cn.mul(xn.oneMinus()).oneMinus().pow2().pow2()).normalize(),e}).once()();class Qd extends Ks{static get type(){return"NormalMapNode"}constructor(e,t=null){super("vec3"),this.node=e,this.scaleNode=t,this.normalMapType=M}setup({material:e}){const{normalMapType:t,scaleNode:r}=this;let s=this.node.mul(2).sub(1);if(null!==r){let t=r;!0===e.flatShading&&(t=jl(t)),s=en(s.xy.mul(t),s.z)}let i=null;return t===P?i=rd(s):t===M?i=Xd.mul(s).normalize():(console.error(`THREE.NodeMaterial: Unsupported normal map type: ${t}`),i=Zl),i}}const Zd=Vi(Qd).setParameterLength(1,2),Jd=ki(({textureNode:e,bumpScale:t})=>{const r=t=>e.cache().context({getUV:e=>t(e.uvNode||$u()),forceUVContext:!0}),s=ji(r(e=>e));return Yi(ji(r(e=>e.add(e.dFdx()))).sub(s),ji(r(e=>e.add(e.dFdy()))).sub(s)).mul(t)}),ec=ki(e=>{const{surf_pos:t,surf_norm:r,dHdxy:s}=e,i=t.dFdx().normalize(),n=r,a=t.dFdy().normalize().cross(n),o=n.cross(i),u=i.dot(a).mul(Wl),l=u.sign().mul(s.x.mul(a).add(s.y.mul(o)));return u.abs().mul(r).sub(l).normalize()});class tc extends Ks{static get type(){return"BumpMapNode"}constructor(e,t=null){super("vec3"),this.textureNode=e,this.scaleNode=t}setup(){const e=null!==this.scaleNode?this.scaleNode:1,t=Jd({textureNode:this.textureNode,bumpScale:e});return ec({surf_pos:Gl,surf_norm:Zl,dHdxy:t})}}const rc=Vi(tc).setParameterLength(1,2),sc=new Map;class ic extends js{static get type(){return"MaterialNode"}constructor(e){super(),this.scope=e}getCache(e,t){let r=sc.get(e);return void 0===r&&(r=Sd(e,t),sc.set(e,r)),r}getFloat(e){return this.getCache(e,"float")}getColor(e){return this.getCache(e,"color")}getTexture(e){return this.getCache("map"===e?"map":e+"Map","texture")}setup(e){const t=e.context.material,r=this.scope;let s=null;if(r===ic.COLOR){const e=void 0!==t.color?this.getColor(r):en();s=t.map&&!0===t.map.isTexture?e.mul(this.getTexture("map")):e}else if(r===ic.OPACITY){const e=this.getFloat(r);s=t.alphaMap&&!0===t.alphaMap.isTexture?e.mul(this.getTexture("alpha")):e}else if(r===ic.SPECULAR_STRENGTH)s=t.specularMap&&!0===t.specularMap.isTexture?this.getTexture("specular").r:ji(1);else if(r===ic.SPECULAR_INTENSITY){const e=this.getFloat(r);s=t.specularIntensityMap&&!0===t.specularIntensityMap.isTexture?e.mul(this.getTexture(r).a):e}else if(r===ic.SPECULAR_COLOR){const e=this.getColor(r);s=t.specularColorMap&&!0===t.specularColorMap.isTexture?e.mul(this.getTexture(r).rgb):e}else if(r===ic.ROUGHNESS){const e=this.getFloat(r);s=t.roughnessMap&&!0===t.roughnessMap.isTexture?e.mul(this.getTexture(r).g):e}else if(r===ic.METALNESS){const e=this.getFloat(r);s=t.metalnessMap&&!0===t.metalnessMap.isTexture?e.mul(this.getTexture(r).b):e}else if(r===ic.EMISSIVE){const e=this.getFloat("emissiveIntensity"),i=this.getColor(r).mul(e);s=t.emissiveMap&&!0===t.emissiveMap.isTexture?i.mul(this.getTexture(r)):i}else if(r===ic.NORMAL)t.normalMap?(s=Zd(this.getTexture("normal"),this.getCache("normalScale","vec2")),s.normalMapType=t.normalMapType):s=t.bumpMap?rc(this.getTexture("bump").r,this.getFloat("bumpScale")):Zl;else if(r===ic.CLEARCOAT){const e=this.getFloat(r);s=t.clearcoatMap&&!0===t.clearcoatMap.isTexture?e.mul(this.getTexture(r).r):e}else if(r===ic.CLEARCOAT_ROUGHNESS){const e=this.getFloat(r);s=t.clearcoatRoughnessMap&&!0===t.clearcoatRoughnessMap.isTexture?e.mul(this.getTexture(r).r):e}else if(r===ic.CLEARCOAT_NORMAL)s=t.clearcoatNormalMap?Zd(this.getTexture(r),this.getCache(r+"Scale","vec2")):Zl;else if(r===ic.SHEEN){const e=this.getColor("sheenColor").mul(this.getFloat("sheen"));s=t.sheenColorMap&&!0===t.sheenColorMap.isTexture?e.mul(this.getTexture("sheenColor").rgb):e}else if(r===ic.SHEEN_ROUGHNESS){const e=this.getFloat(r);s=t.sheenRoughnessMap&&!0===t.sheenRoughnessMap.isTexture?e.mul(this.getTexture(r).a):e,s=s.clamp(.07,1)}else if(r===ic.ANISOTROPY)if(t.anisotropyMap&&!0===t.anisotropyMap.isTexture){const e=this.getTexture(r);s=ln(zc.x,zc.y,zc.y.negate(),zc.x).mul(e.rg.mul(2).sub(Yi(1)).normalize().mul(e.b))}else s=zc;else if(r===ic.IRIDESCENCE_THICKNESS){const e=_d("1","float",t.iridescenceThicknessRange);if(t.iridescenceThicknessMap){const i=_d("0","float",t.iridescenceThicknessRange);s=e.sub(i).mul(this.getTexture(r).g).add(i)}else s=e}else if(r===ic.TRANSMISSION){const e=this.getFloat(r);s=t.transmissionMap?e.mul(this.getTexture(r).r):e}else if(r===ic.THICKNESS){const e=this.getFloat(r);s=t.thicknessMap?e.mul(this.getTexture(r).g):e}else if(r===ic.IOR)s=this.getFloat(r);else if(r===ic.LIGHT_MAP)s=this.getTexture(r).rgb.mul(this.getFloat("lightMapIntensity"));else if(r===ic.AO)s=this.getTexture(r).r.sub(1).mul(this.getFloat("aoMapIntensity")).add(1);else if(r===ic.LINE_DASH_OFFSET)s=t.dashOffset?this.getFloat(r):ji(0);else{const t=this.getNodeType(e);s=this.getCache(r,t)}return s}}ic.ALPHA_TEST="alphaTest",ic.COLOR="color",ic.OPACITY="opacity",ic.SHININESS="shininess",ic.SPECULAR="specular",ic.SPECULAR_STRENGTH="specularStrength",ic.SPECULAR_INTENSITY="specularIntensity",ic.SPECULAR_COLOR="specularColor",ic.REFLECTIVITY="reflectivity",ic.ROUGHNESS="roughness",ic.METALNESS="metalness",ic.NORMAL="normal",ic.CLEARCOAT="clearcoat",ic.CLEARCOAT_ROUGHNESS="clearcoatRoughness",ic.CLEARCOAT_NORMAL="clearcoatNormal",ic.EMISSIVE="emissive",ic.ROTATION="rotation",ic.SHEEN="sheen",ic.SHEEN_ROUGHNESS="sheenRoughness",ic.ANISOTROPY="anisotropy",ic.IRIDESCENCE="iridescence",ic.IRIDESCENCE_IOR="iridescenceIOR",ic.IRIDESCENCE_THICKNESS="iridescenceThickness",ic.IOR="ior",ic.TRANSMISSION="transmission",ic.THICKNESS="thickness",ic.ATTENUATION_DISTANCE="attenuationDistance",ic.ATTENUATION_COLOR="attenuationColor",ic.LINE_SCALE="scale",ic.LINE_DASH_SIZE="dashSize",ic.LINE_GAP_SIZE="gapSize",ic.LINE_WIDTH="linewidth",ic.LINE_DASH_OFFSET="dashOffset",ic.POINT_SIZE="size",ic.DISPERSION="dispersion",ic.LIGHT_MAP="light",ic.AO="ao";const nc=Ui(ic,ic.ALPHA_TEST),ac=Ui(ic,ic.COLOR),oc=Ui(ic,ic.SHININESS),uc=Ui(ic,ic.EMISSIVE),lc=Ui(ic,ic.OPACITY),dc=Ui(ic,ic.SPECULAR),cc=Ui(ic,ic.SPECULAR_INTENSITY),hc=Ui(ic,ic.SPECULAR_COLOR),pc=Ui(ic,ic.SPECULAR_STRENGTH),gc=Ui(ic,ic.REFLECTIVITY),mc=Ui(ic,ic.ROUGHNESS),fc=Ui(ic,ic.METALNESS),yc=Ui(ic,ic.NORMAL),bc=Ui(ic,ic.CLEARCOAT),xc=Ui(ic,ic.CLEARCOAT_ROUGHNESS),Tc=Ui(ic,ic.CLEARCOAT_NORMAL),_c=Ui(ic,ic.ROTATION),vc=Ui(ic,ic.SHEEN),Nc=Ui(ic,ic.SHEEN_ROUGHNESS),Sc=Ui(ic,ic.ANISOTROPY),Ec=Ui(ic,ic.IRIDESCENCE),wc=Ui(ic,ic.IRIDESCENCE_IOR),Ac=Ui(ic,ic.IRIDESCENCE_THICKNESS),Rc=Ui(ic,ic.TRANSMISSION),Cc=Ui(ic,ic.THICKNESS),Mc=Ui(ic,ic.IOR),Pc=Ui(ic,ic.ATTENUATION_DISTANCE),Bc=Ui(ic,ic.ATTENUATION_COLOR),Fc=Ui(ic,ic.LINE_SCALE),Lc=Ui(ic,ic.LINE_DASH_SIZE),Dc=Ui(ic,ic.LINE_GAP_SIZE),Ic=Ui(ic,ic.LINE_WIDTH),Vc=Ui(ic,ic.LINE_DASH_OFFSET),Uc=Ui(ic,ic.POINT_SIZE),Oc=Ui(ic,ic.DISPERSION),kc=Ui(ic,ic.LIGHT_MAP),Gc=Ui(ic,ic.AO),zc=Zn(new t).onReference(function(e){return e.material}).onRenderUpdate(function({material:e}){this.value.set(e.anisotropy*Math.cos(e.anisotropyRotation),e.anisotropy*Math.sin(e.anisotropyRotation))}),Hc=ki(e=>e.context.setupModelViewProjection(),"vec4").once()().toVarying("v_modelViewProjection");class $c extends js{static get type(){return"IndexNode"}constructor(e){super("uint"),this.scope=e,this.isIndexNode=!0}generate(e){const t=this.getNodeType(e),r=this.scope;let s,i;if(r===$c.VERTEX)s=e.getVertexIndex();else if(r===$c.INSTANCE)s=e.getInstanceIndex();else if(r===$c.DRAW)s=e.getDrawIndex();else if(r===$c.INVOCATION_LOCAL)s=e.getInvocationLocalIndex();else if(r===$c.INVOCATION_SUBGROUP)s=e.getInvocationSubgroupIndex();else{if(r!==$c.SUBGROUP)throw new Error("THREE.IndexNode: Unknown scope: "+r);s=e.getSubgroupIndex()}if("vertex"===e.shaderStage||"compute"===e.shaderStage)i=s;else{i=au(this).build(e,t)}return i}}$c.VERTEX="vertex",$c.INSTANCE="instance",$c.SUBGROUP="subgroup",$c.INVOCATION_LOCAL="invocationLocal",$c.INVOCATION_SUBGROUP="invocationSubgroup",$c.DRAW="draw";const Wc=Ui($c,$c.VERTEX),jc=Ui($c,$c.INSTANCE),qc=Ui($c,$c.SUBGROUP),Xc=Ui($c,$c.INVOCATION_SUBGROUP),Kc=Ui($c,$c.INVOCATION_LOCAL),Yc=Ui($c,$c.DRAW);class Qc extends js{static get type(){return"InstanceNode"}constructor(e,t,r=null){super("void"),this.count=e,this.instanceMatrix=t,this.instanceColor=r,this.instanceMatrixNode=null,this.instanceColorNode=null,this.updateType=Vs.FRAME,this.buffer=null,this.bufferColor=null}setup(e){const{count:t,instanceMatrix:r,instanceColor:s}=this;let{instanceMatrixNode:i,instanceColorNode:n}=this;if(null===i){if(t<=1e3)i=tl(r.array,"mat4",Math.max(t,1)).element(jc);else{const e=new B(r.array,16,1);this.buffer=e;const t=r.usage===y?Eu:Su,s=[t(e,"vec4",16,0),t(e,"vec4",16,4),t(e,"vec4",16,8),t(e,"vec4",16,12)];i=cn(...s)}this.instanceMatrixNode=i}if(s&&null===n){const e=new F(s.array,3),t=s.usage===y?Eu:Su;this.bufferColor=e,n=en(t(e,"vec3",3,0)),this.instanceColorNode=n}const a=i.mul(Vl).xyz;if(Vl.assign(a),e.hasGeometryAttribute("normal")){const e=td(Xl,i);Xl.assign(e)}null!==this.instanceColorNode&&fn("vec3","vInstanceColor").assign(this.instanceColorNode)}update(){this.instanceMatrix.usage!==y&&null!==this.buffer&&this.instanceMatrix.version!==this.buffer.version&&(this.buffer.version=this.instanceMatrix.version),this.instanceColor&&this.instanceColor.usage!==y&&null!==this.bufferColor&&this.instanceColor.version!==this.bufferColor.version&&(this.bufferColor.version=this.instanceColor.version)}}const Zc=Vi(Qc).setParameterLength(2,3);class Jc extends Qc{static get type(){return"InstancedMeshNode"}constructor(e){const{count:t,instanceMatrix:r,instanceColor:s}=e;super(t,r,s),this.instancedMesh=e}}const eh=Vi(Jc).setParameterLength(1);class th extends js{static get type(){return"BatchNode"}constructor(e){super("void"),this.batchMesh=e,this.batchingIdNode=null}setup(e){null===this.batchingIdNode&&(null===e.getDrawIndex()?this.batchingIdNode=jc:this.batchingIdNode=Yc);const t=ki(([e])=>{const t=qi(ju(Ju(this.batchMesh._indirectTexture),0).x),r=qi(e).mod(t),s=qi(e).div(t);return Ju(this.batchMesh._indirectTexture,Qi(r,s)).x}).setLayout({name:"getIndirectIndex",type:"uint",inputs:[{name:"id",type:"int"}]}),r=t(qi(this.batchingIdNode)),s=this.batchMesh._matricesTexture,i=qi(ju(Ju(s),0).x),n=ji(r).mul(4).toInt().toVar(),a=n.mod(i),o=n.div(i),u=cn(Ju(s,Qi(a,o)),Ju(s,Qi(a.add(1),o)),Ju(s,Qi(a.add(2),o)),Ju(s,Qi(a.add(3),o))),l=this.batchMesh._colorsTexture;if(null!==l){const e=ki(([e])=>{const t=qi(ju(Ju(l),0).x),r=e,s=r.mod(t),i=r.div(t);return Ju(l,Qi(s,i)).rgb}).setLayout({name:"getBatchingColor",type:"vec3",inputs:[{name:"id",type:"int"}]}),t=e(r);fn("vec3","vBatchColor").assign(t)}const d=dn(u);Vl.assign(u.mul(Vl));const c=Xl.div(en(d[0].dot(d[0]),d[1].dot(d[1]),d[2].dot(d[2]))),h=d.mul(c).xyz;Xl.assign(h),e.hasGeometryAttribute("tangent")&&kd.mulAssign(d)}}const rh=Vi(th).setParameterLength(1);class sh extends qs{static get type(){return"StorageArrayElementNode"}constructor(e,t){super(e,t),this.isStorageArrayElementNode=!0}set storageBufferNode(e){this.node=e}get storageBufferNode(){return this.node}getMemberType(e,t){const r=this.storageBufferNode.structTypeNode;return r?r.getMemberType(e,t):"void"}setup(e){return!1===e.isAvailable("storageBuffer")&&!0===this.node.isPBO&&e.setupPBO(this.node),super.setup(e)}generate(e,t){let r;const s=e.context.assign;if(r=!1===e.isAvailable("storageBuffer")?!0!==this.node.isPBO||!0===s||!this.node.value.isInstancedBufferAttribute&&"compute"===e.shaderStage?this.node.build(e):e.generatePBO(this):super.generate(e),!0!==s){const s=this.getNodeType(e);r=e.format(r,s,t)}return r}}const ih=Vi(sh).setParameterLength(2);class nh extends el{static get type(){return"StorageBufferNode"}constructor(e,t=null,r=0){let s,i=null;t&&t.isStruct?(s="struct",i=t.layout,(e.isStorageBufferAttribute||e.isStorageInstancedBufferAttribute)&&(r=e.count)):null===t&&(e.isStorageBufferAttribute||e.isStorageInstancedBufferAttribute)?(s=Es(e.itemSize),r=e.count):s=t,super(e,s,r),this.isStorageBufferNode=!0,this.structTypeNode=i,this.access=Os.READ_WRITE,this.isAtomic=!1,this.isPBO=!1,this._attribute=null,this._varying=null,this.global=!0,!0!==e.isStorageBufferAttribute&&!0!==e.isStorageInstancedBufferAttribute&&(e.isInstancedBufferAttribute?e.isStorageInstancedBufferAttribute=!0:e.isStorageBufferAttribute=!0)}getHash(e){if(0===this.bufferCount){let t=e.globalCache.getData(this.value);return void 0===t&&(t={node:this},e.globalCache.setData(this.value,t)),t.node.uuid}return this.uuid}getInputType(){return this.value.isIndirectStorageBufferAttribute?"indirectStorageBuffer":"storageBuffer"}element(e){return ih(this,e)}setPBO(e){return this.isPBO=e,this}getPBO(){return this.isPBO}setAccess(e){return this.access=e,this}toReadOnly(){return this.setAccess(Os.READ_ONLY)}setAtomic(e){return this.isAtomic=e,this}toAtomic(){return this.setAtomic(!0)}getAttributeData(){return null===this._attribute&&(this._attribute=vu(this.value),this._varying=au(this._attribute)),{attribute:this._attribute,varying:this._varying}}getNodeType(e){if(null!==this.structTypeNode)return this.structTypeNode.getNodeType(e);if(e.isAvailable("storageBuffer")||e.isAvailable("indirectStorageBuffer"))return super.getNodeType(e);const{attribute:t}=this.getAttributeData();return t.getNodeType(e)}getMemberType(e,t){return null!==this.structTypeNode?this.structTypeNode.getMemberType(e,t):"void"}generate(e){if(null!==this.structTypeNode&&this.structTypeNode.build(e),e.isAvailable("storageBuffer")||e.isAvailable("indirectStorageBuffer"))return super.generate(e);const{attribute:t,varying:r}=this.getAttributeData(),s=r.build(e);return e.registerTransform(s,t),s}}const ah=(e,t=null,r=0)=>Li(new nh(e,t,r)),oh=new WeakMap;class uh extends js{static get type(){return"SkinningNode"}constructor(e){super("void"),this.skinnedMesh=e,this.updateType=Vs.OBJECT,this.skinIndexNode=Hu("skinIndex","uvec4"),this.skinWeightNode=Hu("skinWeight","vec4"),this.bindMatrixNode=_d("bindMatrix","mat4"),this.bindMatrixInverseNode=_d("bindMatrixInverse","mat4"),this.boneMatricesNode=vd("skeleton.boneMatrices","mat4",e.skeleton.bones.length),this.positionNode=Vl,this.toPositionNode=Vl,this.previousBoneMatricesNode=null}getSkinnedPosition(e=this.boneMatricesNode,t=this.positionNode){const{skinIndexNode:r,skinWeightNode:s,bindMatrixNode:i,bindMatrixInverseNode:n}=this,a=e.element(r.x),o=e.element(r.y),u=e.element(r.z),l=e.element(r.w),d=i.mul(t),c=oa(a.mul(s.x).mul(d),o.mul(s.y).mul(d),u.mul(s.z).mul(d),l.mul(s.w).mul(d));return n.mul(c).xyz}getSkinnedNormal(e=this.boneMatricesNode,t=Xl){const{skinIndexNode:r,skinWeightNode:s,bindMatrixNode:i,bindMatrixInverseNode:n}=this,a=e.element(r.x),o=e.element(r.y),u=e.element(r.z),l=e.element(r.w);let d=oa(s.x.mul(a),s.y.mul(o),s.z.mul(u),s.w.mul(l));return d=n.mul(d).mul(i),d.transformDirection(t).xyz}getPreviousSkinnedPosition(e){const t=e.object;return null===this.previousBoneMatricesNode&&(t.skeleton.previousBoneMatrices=new Float32Array(t.skeleton.boneMatrices),this.previousBoneMatricesNode=vd("skeleton.previousBoneMatrices","mat4",t.skeleton.bones.length)),this.getSkinnedPosition(this.previousBoneMatricesNode,Ul)}needsPreviousBoneMatrices(e){const t=e.renderer.getMRT();return t&&t.has("velocity")||!0===Bs(e.object).useVelocity}setup(e){this.needsPreviousBoneMatrices(e)&&Ul.assign(this.getPreviousSkinnedPosition(e));const t=this.getSkinnedPosition();if(this.toPositionNode&&this.toPositionNode.assign(t),e.hasGeometryAttribute("normal")){const t=this.getSkinnedNormal();Xl.assign(t),e.hasGeometryAttribute("tangent")&&kd.assign(t)}return t}generate(e,t){if("void"!==t)return super.generate(e,t)}update(e){const t=e.object&&e.object.skeleton?e.object.skeleton:this.skinnedMesh.skeleton;oh.get(t)!==e.frameId&&(oh.set(t,e.frameId),null!==this.previousBoneMatricesNode&&t.previousBoneMatrices.set(t.boneMatrices),t.update())}}const lh=e=>Li(new uh(e));class dh extends js{static get type(){return"LoopNode"}constructor(e=[]){super(),this.params=e}getVarName(e){return String.fromCharCode("i".charCodeAt(0)+e)}getProperties(e){const t=e.getNodeProperties(this);if(void 0!==t.stackNode)return t;const r={};for(let e=0,t=this.params.length-1;eNumber(u)?">=":"<")),a)n=`while ( ${u} )`;else{const r={start:o,end:u},s=r.start,i=r.end;let a;const p=()=>c.includes("<")?"+=":"-=";if(null!=h)switch(typeof h){case"function":a=e.flowStagesNode(t.updateNode,"void").code.replace(/\t|;/g,"");break;case"number":a=l+" "+p()+" "+e.generateConst(d,h);break;case"string":a=l+" "+h;break;default:h.isNode?a=l+" "+p()+" "+h.build(e):(console.error("THREE.TSL: 'Loop( { update: ... } )' is not a function, string or number."),a="break /* invalid update */")}else h="int"===d||"uint"===d?c.includes("<")?"++":"--":p()+" 1.",a=l+" "+h;n=`for ( ${e.getVar(d,l)+" = "+s}; ${l+" "+c+" "+i}; ${a} )`}e.addFlowCode((0===s?"\n":"")+e.tab+n+" {\n\n").addFlowTab()}const i=s.build(e,"void"),n=t.returnsNode?t.returnsNode.build(e):"";e.removeFlowTab().addFlowCode("\n"+e.tab+i);for(let t=0,r=this.params.length-1;tLi(new dh(Ii(e,"int"))).toStack(),hh=()=>Iu("break").toStack(),ph=new WeakMap,gh=new s,mh=ki(({bufferMap:e,influence:t,stride:r,width:s,depth:i,offset:n})=>{const a=qi(Wc).mul(r).add(n),o=a.div(s),u=a.sub(o.mul(s));return Ju(e,Qi(u,o)).depth(i).xyz.mul(t)});class fh extends js{static get type(){return"MorphNode"}constructor(e){super("void"),this.mesh=e,this.morphBaseInfluence=Zn(1),this.updateType=Vs.OBJECT}setup(e){const{geometry:r}=e,s=void 0!==r.morphAttributes.position,i=r.hasAttribute("normal")&&void 0!==r.morphAttributes.normal,n=r.morphAttributes.position||r.morphAttributes.normal||r.morphAttributes.color,a=void 0!==n?n.length:0,{texture:o,stride:u,size:l}=function(e){const r=void 0!==e.morphAttributes.position,s=void 0!==e.morphAttributes.normal,i=void 0!==e.morphAttributes.color,n=e.morphAttributes.position||e.morphAttributes.normal||e.morphAttributes.color,a=void 0!==n?n.length:0;let o=ph.get(e);if(void 0===o||o.count!==a){void 0!==o&&o.texture.dispose();const u=e.morphAttributes.position||[],l=e.morphAttributes.normal||[],d=e.morphAttributes.color||[];let c=0;!0===r&&(c=1),!0===s&&(c=2),!0===i&&(c=3);let h=e.attributes.position.count*c,p=1;const g=4096;h>g&&(p=Math.ceil(h/g),h=g);const m=new Float32Array(h*p*4*a),f=new L(m,h,p,a);f.type=D,f.needsUpdate=!0;const y=4*c;for(let x=0;x{const t=ji(0).toVar();this.mesh.count>1&&null!==this.mesh.morphTexture&&void 0!==this.mesh.morphTexture?t.assign(Ju(this.mesh.morphTexture,Qi(qi(e).add(1),qi(jc))).r):t.assign(_d("morphTargetInfluences","float").element(e).toVar()),Hi(t.notEqual(0),()=>{!0===s&&Vl.addAssign(mh({bufferMap:o,influence:t,stride:u,width:d,depth:e,offset:qi(0)})),!0===i&&Xl.addAssign(mh({bufferMap:o,influence:t,stride:u,width:d,depth:e,offset:qi(1)}))})})}update(){const e=this.morphBaseInfluence;this.mesh.geometry.morphTargetsRelative?e.value=1:e.value=1-this.mesh.morphTargetInfluences.reduce((e,t)=>e+t,0)}}const yh=Vi(fh).setParameterLength(1);class bh extends js{static get type(){return"LightingNode"}constructor(){super("vec3"),this.isLightingNode=!0}}class xh extends bh{static get type(){return"AONode"}constructor(e=null){super(),this.aoNode=e}setup(e){e.context.ambientOcclusion.mulAssign(this.aoNode)}}class Th extends Ko{static get type(){return"LightingContextNode"}constructor(e,t=null,r=null,s=null){super(e),this.lightingModel=t,this.backdropNode=r,this.backdropAlphaNode=s,this._value=null}getContext(){const{backdropNode:e,backdropAlphaNode:t}=this,r={directDiffuse:en().toVar("directDiffuse"),directSpecular:en().toVar("directSpecular"),indirectDiffuse:en().toVar("indirectDiffuse"),indirectSpecular:en().toVar("indirectSpecular")};return{radiance:en().toVar("radiance"),irradiance:en().toVar("irradiance"),iblIrradiance:en().toVar("iblIrradiance"),ambientOcclusion:ji(1).toVar("ambientOcclusion"),reflectedLight:r,backdrop:e,backdropAlpha:t}}setup(e){return this.value=this._value||(this._value=this.getContext()),this.value.lightingModel=this.lightingModel||e.context.lightingModel,super.setup(e)}}const _h=Vi(Th);class vh extends bh{static get type(){return"IrradianceNode"}constructor(e){super(),this.node=e}setup(e){e.context.irradiance.addAssign(this.node)}}let Nh,Sh;class Eh extends js{static get type(){return"ScreenNode"}constructor(e){super(),this.scope=e,this.isViewportNode=!0}getNodeType(){return this.scope===Eh.VIEWPORT?"vec4":"vec2"}getUpdateType(){let e=Vs.NONE;return this.scope!==Eh.SIZE&&this.scope!==Eh.VIEWPORT||(e=Vs.RENDER),this.updateType=e,e}update({renderer:e}){const t=e.getRenderTarget();this.scope===Eh.VIEWPORT?null!==t?Sh.copy(t.viewport):(e.getViewport(Sh),Sh.multiplyScalar(e.getPixelRatio())):null!==t?(Nh.width=t.width,Nh.height=t.height):e.getDrawingBufferSize(Nh)}setup(){const e=this.scope;let r=null;return r=e===Eh.SIZE?Zn(Nh||(Nh=new t)):e===Eh.VIEWPORT?Zn(Sh||(Sh=new s)):Yi(Rh.div(Ah)),r}generate(e){if(this.scope===Eh.COORDINATE){let t=e.getFragCoord();if(e.isFlipY()){const r=e.getNodeProperties(Ah).outputNode.build(e);t=`${e.getType("vec2")}( ${t}.x, ${r}.y - ${t}.y )`}return t}return super.generate(e)}}Eh.COORDINATE="coordinate",Eh.VIEWPORT="viewport",Eh.SIZE="size",Eh.UV="uv";const wh=Ui(Eh,Eh.UV),Ah=Ui(Eh,Eh.SIZE),Rh=Ui(Eh,Eh.COORDINATE),Ch=Ui(Eh,Eh.VIEWPORT),Mh=Ch.zw,Ph=Rh.sub(Ch.xy),Bh=Ph.div(Mh),Fh=ki(()=>(console.warn('THREE.TSL: "viewportResolution" is deprecated. Use "screenSize" instead.'),Ah),"vec2").once()(),Lh=new t;class Dh extends Yu{static get type(){return"ViewportTextureNode"}constructor(e=wh,t=null,r=null){null===r&&((r=new I).minFilter=V),super(r,e,t),this.generateMipmaps=!1,this.isOutputTextureNode=!0,this.updateBeforeType=Vs.FRAME}updateBefore(e){const t=e.renderer;t.getDrawingBufferSize(Lh);const r=this.value;r.image.width===Lh.width&&r.image.height===Lh.height||(r.image.width=Lh.width,r.image.height=Lh.height,r.needsUpdate=!0);const s=r.generateMipmaps;r.generateMipmaps=this.generateMipmaps,t.copyFramebufferToTexture(r),r.generateMipmaps=s}clone(){const e=new this.constructor(this.uvNode,this.levelNode,this.value);return e.generateMipmaps=this.generateMipmaps,e}}const Ih=Vi(Dh).setParameterLength(0,3),Vh=Vi(Dh,null,null,{generateMipmaps:!0}).setParameterLength(0,3);let Uh=null;class Oh extends Dh{static get type(){return"ViewportDepthTextureNode"}constructor(e=wh,t=null){null===Uh&&(Uh=new U),super(e,t,Uh)}}const kh=Vi(Oh).setParameterLength(0,2);class Gh extends js{static get type(){return"ViewportDepthNode"}constructor(e,t=null){super("float"),this.scope=e,this.valueNode=t,this.isViewportDepthNode=!0}generate(e){const{scope:t}=this;return t===Gh.DEPTH_BASE?e.getFragDepth():super.generate(e)}setup({camera:e}){const{scope:t}=this,r=this.valueNode;let s=null;if(t===Gh.DEPTH_BASE)null!==r&&(s=jh().assign(r));else if(t===Gh.DEPTH)s=e.isPerspectiveCamera?Hh(Gl.z,ol,ul):zh(Gl.z,ol,ul);else if(t===Gh.LINEAR_DEPTH)if(null!==r)if(e.isPerspectiveCamera){const e=$h(r,ol,ul);s=zh(e,ol,ul)}else s=r;else s=zh(Gl.z,ol,ul);return s}}Gh.DEPTH_BASE="depthBase",Gh.DEPTH="depth",Gh.LINEAR_DEPTH="linearDepth";const zh=(e,t,r)=>e.add(t).div(t.sub(r)),Hh=(e,t,r)=>t.add(e).mul(r).div(r.sub(t).mul(e)),$h=(e,t,r)=>t.mul(r).div(r.sub(t).mul(e).sub(r)),Wh=(e,t,r)=>{t=t.max(1e-6).toVar();const s=Wa(e.negate().div(t)),i=Wa(r.div(t));return s.div(i)},jh=Vi(Gh,Gh.DEPTH_BASE),qh=Ui(Gh,Gh.DEPTH),Xh=Vi(Gh,Gh.LINEAR_DEPTH).setParameterLength(0,1),Kh=Xh(kh());qh.assign=e=>jh(e);class Yh extends js{static get type(){return"ClippingNode"}constructor(e=Yh.DEFAULT){super(),this.scope=e}setup(e){super.setup(e);const t=e.clippingContext,{intersectionPlanes:r,unionPlanes:s}=t;return this.hardwareClipping=e.material.hardwareClipping,this.scope===Yh.ALPHA_TO_COVERAGE?this.setupAlphaToCoverage(r,s):this.scope===Yh.HARDWARE?this.setupHardwareClipping(s,e):this.setupDefault(r,s)}setupAlphaToCoverage(e,t){return ki(()=>{const r=ji().toVar("distanceToPlane"),s=ji().toVar("distanceToGradient"),i=ji(1).toVar("clipOpacity"),n=t.length;if(!1===this.hardwareClipping&&n>0){const e=il(t);ch(n,({i:t})=>{const n=e.element(t);r.assign(Gl.dot(n.xyz).negate().add(n.w)),s.assign(r.fwidth().div(2)),i.mulAssign(Uo(s.negate(),s,r))})}const a=e.length;if(a>0){const t=il(e),n=ji(1).toVar("intersectionClipOpacity");ch(a,({i:e})=>{const i=t.element(e);r.assign(Gl.dot(i.xyz).negate().add(i.w)),s.assign(r.fwidth().div(2)),n.mulAssign(Uo(s.negate(),s,r).oneMinus())}),i.mulAssign(n.oneMinus())}yn.a.mulAssign(i),yn.a.equal(0).discard()})()}setupDefault(e,t){return ki(()=>{const r=t.length;if(!1===this.hardwareClipping&&r>0){const e=il(t);ch(r,({i:t})=>{const r=e.element(t);Gl.dot(r.xyz).greaterThan(r.w).discard()})}const s=e.length;if(s>0){const t=il(e),r=Ki(!0).toVar("clipped");ch(s,({i:e})=>{const s=t.element(e);r.assign(Gl.dot(s.xyz).greaterThan(s.w).and(r))}),r.discard()}})()}setupHardwareClipping(e,t){const r=e.length;return t.enableHardwareClipping(r),ki(()=>{const s=il(e),i=nl(t.getClipDistance());ch(r,({i:e})=>{const t=s.element(e),r=Gl.dot(t.xyz).sub(t.w).negate();i.element(e).assign(r)})})()}}Yh.ALPHA_TO_COVERAGE="alphaToCoverage",Yh.DEFAULT="default",Yh.HARDWARE="hardware";const Qh=ki(([e])=>Qa(la(1e4,Za(la(17,e.x).add(la(.1,e.y)))).mul(oa(.1,io(Za(la(13,e.y).add(e.x))))))),Zh=ki(([e])=>Qh(Yi(Qh(e.xy),e.z))),Jh=ki(([e])=>{const t=To(ao(lo(e.xyz)),ao(co(e.xyz))),r=ji(1).div(ji(.05).mul(t)).toVar("pixScale"),s=Yi(Ha(Xa(Wa(r))),Ha(Ka(Wa(r)))),i=Yi(Zh(Xa(s.x.mul(e.xyz))),Zh(Xa(s.y.mul(e.xyz)))),n=Qa(Wa(r)),a=oa(la(n.oneMinus(),i.x),la(n,i.y)),o=xo(n,n.oneMinus()),u=en(a.mul(a).div(la(2,o).mul(ua(1,o))),a.sub(la(.5,o)).div(ua(1,o)),ua(1,ua(1,a).mul(ua(1,a)).div(la(2,o).mul(ua(1,o))))),l=a.lessThan(o.oneMinus()).select(a.lessThan(o).select(u.x,u.y),u.z);return Do(l,1e-6,1)}).setLayout({name:"getAlphaHashThreshold",type:"float",inputs:[{name:"position",type:"vec3"}]});class ep extends zu{static get type(){return"VertexColorNode"}constructor(e){super(null,"vec4"),this.isVertexColorNode=!0,this.index=e}getAttributeName(){const e=this.index;return"color"+(e>0?e:"")}generate(e){const t=this.getAttributeName(e);let r;return r=!0===e.hasGeometryAttribute(t)?super.generate(e):e.generateConst(this.nodeType,new s(1,1,1,1)),r}serialize(e){super.serialize(e),e.index=this.index}deserialize(e){super.deserialize(e),this.index=e.index}}const tp=(e=0)=>Li(new ep(e)),rp=ki(([e,t])=>xo(1,e.oneMinus().div(t)).oneMinus()).setLayout({name:"blendBurn",type:"vec3",inputs:[{name:"base",type:"vec3"},{name:"blend",type:"vec3"}]}),sp=ki(([e,t])=>xo(e.div(t.oneMinus()),1)).setLayout({name:"blendDodge",type:"vec3",inputs:[{name:"base",type:"vec3"},{name:"blend",type:"vec3"}]}),ip=ki(([e,t])=>e.oneMinus().mul(t.oneMinus()).oneMinus()).setLayout({name:"blendScreen",type:"vec3",inputs:[{name:"base",type:"vec3"},{name:"blend",type:"vec3"}]}),np=ki(([e,t])=>Lo(e.mul(2).mul(t),e.oneMinus().mul(2).mul(t.oneMinus()).oneMinus(),_o(.5,e))).setLayout({name:"blendOverlay",type:"vec3",inputs:[{name:"base",type:"vec3"},{name:"blend",type:"vec3"}]}),ap=ki(([e,t])=>{const r=t.a.add(e.a.mul(t.a.oneMinus()));return nn(t.rgb.mul(t.a).add(e.rgb.mul(e.a).mul(t.a.oneMinus())).div(r),r)}).setLayout({name:"blendColor",type:"vec4",inputs:[{name:"base",type:"vec4"},{name:"blend",type:"vec4"}]}),op=ki(([e])=>nn(e.rgb.mul(e.a),e.a),{color:"vec4",return:"vec4"}),up=ki(([e])=>(Hi(e.a.equal(0),()=>nn(0)),nn(e.rgb.div(e.a),e.a)),{color:"vec4",return:"vec4"});class lp extends O{static get type(){return"NodeMaterial"}get type(){return this.constructor.type}set type(e){}constructor(){super(),this.isNodeMaterial=!0,this.fog=!0,this.lights=!1,this.hardwareClipping=!1,this.lightsNode=null,this.envNode=null,this.aoNode=null,this.colorNode=null,this.normalNode=null,this.opacityNode=null,this.backdropNode=null,this.backdropAlphaNode=null,this.alphaTestNode=null,this.maskNode=null,this.positionNode=null,this.geometryNode=null,this.depthNode=null,this.receivedShadowPositionNode=null,this.castShadowPositionNode=null,this.receivedShadowNode=null,this.castShadowNode=null,this.outputNode=null,this.mrtNode=null,this.fragmentNode=null,this.vertexNode=null,Object.defineProperty(this,"shadowPositionNode",{get:()=>this.receivedShadowPositionNode,set:e=>{console.warn('THREE.NodeMaterial: ".shadowPositionNode" was renamed to ".receivedShadowPositionNode".'),this.receivedShadowPositionNode=e}})}customProgramCacheKey(){return this.type+_s(this)}build(e){this.setup(e)}setupObserver(e){return new fs(e)}setup(e){e.context.setupNormal=()=>iu(this.setupNormal(e),"NORMAL","vec3"),e.context.setupPositionView=()=>this.setupPositionView(e),e.context.setupModelViewProjection=()=>this.setupModelViewProjection(e);const t=e.renderer,r=t.getRenderTarget();e.addStack();const s=iu(this.setupVertex(e),"VERTEX"),i=this.vertexNode||s;let n;e.stack.outputNode=i,this.setupHardwareClipping(e),null!==this.geometryNode&&(e.stack.outputNode=e.stack.outputNode.bypass(this.geometryNode)),e.addFlow("vertex",e.removeStack()),e.addStack();const a=this.setupClipping(e);if(!0!==this.depthWrite&&!0!==this.depthTest||(null!==r?!0===r.depthBuffer&&this.setupDepth(e):!0===t.depth&&this.setupDepth(e)),null===this.fragmentNode){this.setupDiffuseColor(e),this.setupVariants(e);const s=this.setupLighting(e);null!==a&&e.stack.add(a);const i=nn(s,yn.a).max(0);n=this.setupOutput(e,i),Dn.assign(n);const o=null!==this.outputNode;if(o&&(n=this.outputNode),null!==r){const e=t.getMRT(),r=this.mrtNode;null!==e?(o&&Dn.assign(n),n=e,null!==r&&(n=e.merge(r))):null!==r&&(n=r)}}else{let t=this.fragmentNode;!0!==t.isOutputStructNode&&(t=nn(t)),n=this.setupOutput(e,t)}e.stack.outputNode=n,e.addFlow("fragment",e.removeStack()),e.observer=this.setupObserver(e)}setupClipping(e){if(null===e.clippingContext)return null;const{unionPlanes:t,intersectionPlanes:r}=e.clippingContext;let s=null;if(t.length>0||r.length>0){const t=e.renderer.samples;this.alphaToCoverage&&t>1?s=Li(new Yh(Yh.ALPHA_TO_COVERAGE)):e.stack.add(Li(new Yh))}return s}setupHardwareClipping(e){if(this.hardwareClipping=!1,null===e.clippingContext)return;const t=e.clippingContext.unionPlanes.length;t>0&&t<=8&&e.isAvailable("clipDistance")&&(e.stack.add(Li(new Yh(Yh.HARDWARE))),this.hardwareClipping=!0)}setupDepth(e){const{renderer:t,camera:r}=e;let s=this.depthNode;if(null===s){const e=t.getMRT();e&&e.has("depth")?s=e.get("depth"):!0===t.logarithmicDepthBuffer&&(s=r.isPerspectiveCamera?Wh(Gl.z,ol,ul):zh(Gl.z,ol,ul))}null!==s&&qh.assign(s).toStack()}setupPositionView(){return Bl.mul(Vl).xyz}setupModelViewProjection(){return ll.mul(Gl)}setupVertex(e){return e.addStack(),this.setupPosition(e),e.context.vertex=e.removeStack(),Hc}setupPosition(e){const{object:t,geometry:r}=e;if((r.morphAttributes.position||r.morphAttributes.normal||r.morphAttributes.color)&&yh(t).toStack(),!0===t.isSkinnedMesh&&lh(t).toStack(),this.displacementMap){const e=Sd("displacementMap","texture"),t=Sd("displacementScale","float"),r=Sd("displacementBias","float");Vl.addAssign(Xl.normalize().mul(e.x.mul(t).add(r)))}return t.isBatchedMesh&&rh(t).toStack(),t.isInstancedMesh&&t.instanceMatrix&&!0===t.instanceMatrix.isInstancedBufferAttribute&&eh(t).toStack(),null!==this.positionNode&&Vl.assign(iu(this.positionNode,"POSITION","vec3")),Vl}setupDiffuseColor({object:e,geometry:t}){null!==this.maskNode&&Ki(this.maskNode).not().discard();let r=this.colorNode?nn(this.colorNode):ac;if(!0===this.vertexColors&&t.hasAttribute("color")&&(r=r.mul(tp())),e.instanceColor){r=fn("vec3","vInstanceColor").mul(r)}if(e.isBatchedMesh&&e._colorsTexture){r=fn("vec3","vBatchColor").mul(r)}yn.assign(r);const s=this.opacityNode?ji(this.opacityNode):lc;yn.a.assign(yn.a.mul(s));let i=null;(null!==this.alphaTestNode||this.alphaTest>0)&&(i=null!==this.alphaTestNode?ji(this.alphaTestNode):nc,yn.a.lessThanEqual(i).discard()),!0===this.alphaHash&&yn.a.lessThan(Jh(Vl)).discard();!1===this.transparent&&this.blending===k&&!1===this.alphaToCoverage?yn.a.assign(1):null===i&&yn.a.lessThanEqual(0).discard()}setupVariants(){}setupOutgoingLight(){return!0===this.lights?en(0):yn.rgb}setupNormal(){return this.normalNode?en(this.normalNode):yc}setupEnvironment(){let e=null;return this.envNode?e=this.envNode:this.envMap&&(e=this.envMap.isCubeTexture?Sd("envMap","cubeTexture"):Sd("envMap","texture")),e}setupLightMap(e){let t=null;return e.material.lightMap&&(t=new vh(kc)),t}setupLights(e){const t=[],r=this.setupEnvironment(e);r&&r.isLightingNode&&t.push(r);const s=this.setupLightMap(e);if(s&&s.isLightingNode&&t.push(s),null!==this.aoNode||e.material.aoMap){const e=null!==this.aoNode?this.aoNode:Gc;t.push(new xh(e))}let i=this.lightsNode||e.lightsNode;return t.length>0&&(i=e.renderer.lighting.createNode([...i.getLights(),...t])),i}setupLightingModel(){}setupLighting(e){const{material:t}=e,{backdropNode:r,backdropAlphaNode:s,emissiveNode:i}=this,n=!0===this.lights||null!==this.lightsNode?this.setupLights(e):null;let a=this.setupOutgoingLight(e);if(n&&n.getScope().hasLights){const t=this.setupLightingModel(e)||null;a=_h(n,t,r,s)}else null!==r&&(a=en(null!==s?Lo(a,r,s):r));return(i&&!0===i.isNode||t.emissive&&!0===t.emissive.isColor)&&(bn.assign(en(i||uc)),a=a.add(bn)),a}setupFog(e,t){const r=e.fogNode;return r&&(Dn.assign(t),t=nn(r)),t}setupPremultipliedAlpha(e,t){return op(t)}setupOutput(e,t){return!0===this.fog&&(t=this.setupFog(e,t)),!0===this.premultipliedAlpha&&(t=this.setupPremultipliedAlpha(e,t)),t}setDefaultValues(e){for(const t in e){const r=e[t];void 0===this[t]&&(this[t]=r,r&&r.clone&&(this[t]=r.clone()))}const t=Object.getOwnPropertyDescriptors(e.constructor.prototype);for(const e in t)void 0===Object.getOwnPropertyDescriptor(this.constructor.prototype,e)&&void 0!==t[e].get&&Object.defineProperty(this.constructor.prototype,e,t[e])}toJSON(e){const t=void 0===e||"string"==typeof e;t&&(e={textures:{},images:{},nodes:{}});const r=O.prototype.toJSON.call(this,e),s=vs(this);r.inputNodes={};for(const{property:t,childNode:i}of s)r.inputNodes[t]=i.toJSON(e).uuid;function i(e){const t=[];for(const r in e){const s=e[r];delete s.metadata,t.push(s)}return t}if(t){const t=i(e.textures),s=i(e.images),n=i(e.nodes);t.length>0&&(r.textures=t),s.length>0&&(r.images=s),n.length>0&&(r.nodes=n)}return r}copy(e){return this.lightsNode=e.lightsNode,this.envNode=e.envNode,this.colorNode=e.colorNode,this.normalNode=e.normalNode,this.opacityNode=e.opacityNode,this.backdropNode=e.backdropNode,this.backdropAlphaNode=e.backdropAlphaNode,this.alphaTestNode=e.alphaTestNode,this.maskNode=e.maskNode,this.positionNode=e.positionNode,this.geometryNode=e.geometryNode,this.depthNode=e.depthNode,this.receivedShadowPositionNode=e.receivedShadowPositionNode,this.castShadowPositionNode=e.castShadowPositionNode,this.receivedShadowNode=e.receivedShadowNode,this.castShadowNode=e.castShadowNode,this.outputNode=e.outputNode,this.mrtNode=e.mrtNode,this.fragmentNode=e.fragmentNode,this.vertexNode=e.vertexNode,super.copy(e)}}const dp=new G;class cp extends lp{static get type(){return"LineBasicNodeMaterial"}constructor(e){super(),this.isLineBasicNodeMaterial=!0,this.setDefaultValues(dp),this.setValues(e)}}const hp=new z;class pp extends lp{static get type(){return"LineDashedNodeMaterial"}constructor(e){super(),this.isLineDashedNodeMaterial=!0,this.setDefaultValues(hp),this.dashOffset=0,this.offsetNode=null,this.dashScaleNode=null,this.dashSizeNode=null,this.gapSizeNode=null,this.setValues(e)}setupVariants(){const e=this.offsetNode?ji(this.offsetNode):Vc,t=this.dashScaleNode?ji(this.dashScaleNode):Fc,r=this.dashSizeNode?ji(this.dashSizeNode):Lc,s=this.gapSizeNode?ji(this.gapSizeNode):Dc;In.assign(r),Vn.assign(s);const i=au(Hu("lineDistance").mul(t));(e?i.add(e):i).mod(In.add(Vn)).greaterThan(In).discard()}}let gp=null;class mp extends Dh{static get type(){return"ViewportSharedTextureNode"}constructor(e=wh,t=null){null===gp&&(gp=new I),super(e,t,gp)}updateReference(){return this}}const fp=Vi(mp).setParameterLength(0,2),yp=new z;class bp extends lp{static get type(){return"Line2NodeMaterial"}constructor(e={}){super(),this.isLine2NodeMaterial=!0,this.setDefaultValues(yp),this.useColor=e.vertexColors,this.dashOffset=0,this.lineWidth=1,this.lineColorNode=null,this.offsetNode=null,this.dashScaleNode=null,this.dashSizeNode=null,this.gapSizeNode=null,this.blending=H,this._useDash=e.dashed,this._useAlphaToCoverage=!0,this._useWorldUnits=!1,this.setValues(e)}setup(e){const{renderer:t}=e,r=this._useAlphaToCoverage,s=this.useColor,i=this._useDash,n=this._useWorldUnits,a=ki(({start:e,end:t})=>{const r=ll.element(2).element(2),s=ll.element(3).element(2).mul(-.5).div(r).sub(e.z).div(t.z.sub(e.z));return nn(Lo(e.xyz,t.xyz,s),t.w)}).setLayout({name:"trimSegment",type:"vec4",inputs:[{name:"start",type:"vec4"},{name:"end",type:"vec4"}]});this.vertexNode=ki(()=>{const e=Hu("instanceStart"),t=Hu("instanceEnd"),r=nn(Bl.mul(nn(e,1))).toVar("start"),s=nn(Bl.mul(nn(t,1))).toVar("end");if(i){const e=this.dashScaleNode?ji(this.dashScaleNode):Fc,t=this.offsetNode?ji(this.offsetNode):Vc,r=Hu("instanceDistanceStart"),s=Hu("instanceDistanceEnd");let i=Il.y.lessThan(.5).select(e.mul(r),e.mul(s));i=i.add(t),fn("float","lineDistance").assign(i)}n&&(fn("vec3","worldStart").assign(r.xyz),fn("vec3","worldEnd").assign(s.xyz));const o=Ch.z.div(Ch.w),u=ll.element(2).element(3).equal(-1);Hi(u,()=>{Hi(r.z.lessThan(0).and(s.z.greaterThan(0)),()=>{s.assign(a({start:r,end:s}))}).ElseIf(s.z.lessThan(0).and(r.z.greaterThanEqual(0)),()=>{r.assign(a({start:s,end:r}))})});const l=ll.mul(r),d=ll.mul(s),c=l.xyz.div(l.w),h=d.xyz.div(d.w),p=h.xy.sub(c.xy).toVar();p.x.assign(p.x.mul(o)),p.assign(p.normalize());const g=nn().toVar();if(n){const e=s.xyz.sub(r.xyz).normalize(),t=Lo(r.xyz,s.xyz,.5).normalize(),n=e.cross(t).normalize(),a=e.cross(n),o=fn("vec4","worldPos");o.assign(Il.y.lessThan(.5).select(r,s));const u=Ic.mul(.5);o.addAssign(nn(Il.x.lessThan(0).select(n.mul(u),n.mul(u).negate()),0)),i||(o.addAssign(nn(Il.y.lessThan(.5).select(e.mul(u).negate(),e.mul(u)),0)),o.addAssign(nn(a.mul(u),0)),Hi(Il.y.greaterThan(1).or(Il.y.lessThan(0)),()=>{o.subAssign(nn(a.mul(2).mul(u),0))})),g.assign(ll.mul(o));const l=en().toVar();l.assign(Il.y.lessThan(.5).select(c,h)),g.z.assign(l.z.mul(g.w))}else{const e=Yi(p.y,p.x.negate()).toVar("offset");p.x.assign(p.x.div(o)),e.x.assign(e.x.div(o)),e.assign(Il.x.lessThan(0).select(e.negate(),e)),Hi(Il.y.lessThan(0),()=>{e.assign(e.sub(p))}).ElseIf(Il.y.greaterThan(1),()=>{e.assign(e.add(p))}),e.assign(e.mul(Ic)),e.assign(e.div(Ch.w)),g.assign(Il.y.lessThan(.5).select(l,d)),e.assign(e.mul(g.w)),g.assign(g.add(nn(e,0,0)))}return g})();const o=ki(({p1:e,p2:t,p3:r,p4:s})=>{const i=e.sub(r),n=s.sub(r),a=t.sub(e),o=i.dot(n),u=n.dot(a),l=i.dot(a),d=n.dot(n),c=a.dot(a).mul(d).sub(u.mul(u)),h=o.mul(u).sub(l.mul(d)).div(c).clamp(),p=o.add(u.mul(h)).div(d).clamp();return Yi(h,p)});if(this.colorNode=ki(()=>{const e=$u();if(i){const t=this.dashSizeNode?ji(this.dashSizeNode):Lc,r=this.gapSizeNode?ji(this.gapSizeNode):Dc;In.assign(t),Vn.assign(r);const s=fn("float","lineDistance");e.y.lessThan(-1).or(e.y.greaterThan(1)).discard(),s.mod(In.add(Vn)).greaterThan(In).discard()}const a=ji(1).toVar("alpha");if(n){const e=fn("vec3","worldStart"),s=fn("vec3","worldEnd"),n=fn("vec4","worldPos").xyz.normalize().mul(1e5),u=s.sub(e),l=o({p1:e,p2:s,p3:en(0,0,0),p4:n}),d=e.add(u.mul(l.x)),c=n.mul(l.y),h=d.sub(c).length().div(Ic);if(!i)if(r&&t.samples>1){const e=h.fwidth();a.assign(Uo(e.negate().add(.5),e.add(.5),h).oneMinus())}else h.greaterThan(.5).discard()}else if(r&&t.samples>1){const t=e.x,r=e.y.greaterThan(0).select(e.y.sub(1),e.y.add(1)),s=t.mul(t).add(r.mul(r)),i=ji(s.fwidth()).toVar("dlen");Hi(e.y.abs().greaterThan(1),()=>{a.assign(Uo(i.oneMinus(),i.add(1),s).oneMinus())})}else Hi(e.y.abs().greaterThan(1),()=>{const t=e.x,r=e.y.greaterThan(0).select(e.y.sub(1),e.y.add(1));t.mul(t).add(r.mul(r)).greaterThan(1).discard()});let u;if(this.lineColorNode)u=this.lineColorNode;else if(s){const e=Hu("instanceColorStart"),t=Hu("instanceColorEnd");u=Il.y.lessThan(.5).select(e,t).mul(ac)}else u=ac;return nn(u,a)})(),this.transparent){const e=this.opacityNode?ji(this.opacityNode):lc;this.outputNode=nn(this.colorNode.rgb.mul(e).add(fp().rgb.mul(e.oneMinus())),this.colorNode.a)}super.setup(e)}get worldUnits(){return this._useWorldUnits}set worldUnits(e){this._useWorldUnits!==e&&(this._useWorldUnits=e,this.needsUpdate=!0)}get dashed(){return this._useDash}set dashed(e){this._useDash!==e&&(this._useDash=e,this.needsUpdate=!0)}get alphaToCoverage(){return this._useAlphaToCoverage}set alphaToCoverage(e){this._useAlphaToCoverage!==e&&(this._useAlphaToCoverage=e,this.needsUpdate=!0)}}const xp=e=>Li(e).mul(.5).add(.5),Tp=new $;class _p extends lp{static get type(){return"MeshNormalNodeMaterial"}constructor(e){super(),this.isMeshNormalNodeMaterial=!0,this.setDefaultValues(Tp),this.setValues(e)}setupDiffuseColor(){const e=this.opacityNode?ji(this.opacityNode):lc;yn.assign(pu(nn(xp(Zl),e),W))}}const vp=ki(([e=kl])=>{const t=e.z.atan(e.x).mul(1/(2*Math.PI)).add(.5),r=e.y.clamp(-1,1).asin().mul(1/Math.PI).add(.5);return Yi(t,r)});class Np extends j{constructor(e=1,t={}){super(e,t),this.isCubeRenderTarget=!0}fromEquirectangularTexture(e,t){const r=t.minFilter,s=t.generateMipmaps;t.generateMipmaps=!0,this.texture.type=t.type,this.texture.colorSpace=t.colorSpace,this.texture.generateMipmaps=t.generateMipmaps,this.texture.minFilter=t.minFilter,this.texture.magFilter=t.magFilter;const i=new q(5,5,5),n=vp(kl),a=new lp;a.colorNode=Zu(t,n,0),a.side=S,a.blending=H;const o=new X(i,a),u=new K;u.add(o),t.minFilter===V&&(t.minFilter=Y);const l=new Q(1,10,this),d=e.getMRT();return e.setMRT(null),l.update(e,u),e.setMRT(d),t.minFilter=r,t.currentGenerateMipmaps=s,o.geometry.dispose(),o.material.dispose(),this}}const Sp=new WeakMap;class Ep extends Ks{static get type(){return"CubeMapNode"}constructor(e){super("vec3"),this.envNode=e,this._cubeTexture=null,this._cubeTextureNode=bd(null);const t=new A;t.isRenderTargetTexture=!0,this._defaultTexture=t,this.updateBeforeType=Vs.RENDER}updateBefore(e){const{renderer:t,material:r}=e,s=this.envNode;if(s.isTextureNode||s.isMaterialReferenceNode){const e=s.isTextureNode?s.value:r[s.property];if(e&&e.isTexture){const r=e.mapping;if(r===Z||r===J){if(Sp.has(e)){const t=Sp.get(e);Ap(t,e.mapping),this._cubeTexture=t}else{const r=e.image;if(function(e){return null!=e&&e.height>0}(r)){const s=new Np(r.height);s.fromEquirectangularTexture(t,e),Ap(s.texture,e.mapping),this._cubeTexture=s.texture,Sp.set(e,s.texture),e.addEventListener("dispose",wp)}else this._cubeTexture=this._defaultTexture}this._cubeTextureNode.value=this._cubeTexture}else this._cubeTextureNode=this.envNode}}}setup(e){return this.updateBefore(e),this._cubeTextureNode}}function wp(e){const t=e.target;t.removeEventListener("dispose",wp);const r=Sp.get(t);void 0!==r&&(Sp.delete(t),r.dispose())}function Ap(e,t){t===Z?e.mapping=R:t===J&&(e.mapping=C)}const Rp=Vi(Ep).setParameterLength(1);class Cp extends bh{static get type(){return"BasicEnvironmentNode"}constructor(e=null){super(),this.envNode=e}setup(e){e.context.environment=Rp(this.envNode)}}class Mp extends bh{static get type(){return"BasicLightMapNode"}constructor(e=null){super(),this.lightMapNode=e}setup(e){const t=ji(1/Math.PI);e.context.irradianceLightMap=this.lightMapNode.mul(t)}}class Pp{start(e){e.lightsNode.setupLights(e,e.lightsNode.getLightNodes(e)),this.indirect(e)}finish(){}direct(){}directRectArea(){}indirect(){}ambientOcclusion(){}}class Bp extends Pp{constructor(){super()}indirect({context:e}){const t=e.ambientOcclusion,r=e.reflectedLight,s=e.irradianceLightMap;r.indirectDiffuse.assign(nn(0)),s?r.indirectDiffuse.addAssign(s):r.indirectDiffuse.addAssign(nn(1,1,1,0)),r.indirectDiffuse.mulAssign(t),r.indirectDiffuse.mulAssign(yn.rgb)}finish(e){const{material:t,context:r}=e,s=r.outgoingLight,i=e.context.environment;if(i)switch(t.combine){case re:s.rgb.assign(Lo(s.rgb,s.rgb.mul(i.rgb),pc.mul(gc)));break;case te:s.rgb.assign(Lo(s.rgb,i.rgb,pc.mul(gc)));break;case ee:s.rgb.addAssign(i.rgb.mul(pc.mul(gc)));break;default:console.warn("THREE.BasicLightingModel: Unsupported .combine value:",t.combine)}}}const Fp=new se;class Lp extends lp{static get type(){return"MeshBasicNodeMaterial"}constructor(e){super(),this.isMeshBasicNodeMaterial=!0,this.lights=!0,this.setDefaultValues(Fp),this.setValues(e)}setupNormal(){return jl(Yl)}setupEnvironment(e){const t=super.setupEnvironment(e);return t?new Cp(t):null}setupLightMap(e){let t=null;return e.material.lightMap&&(t=new Mp(kc)),t}setupOutgoingLight(){return yn.rgb}setupLightingModel(){return new Bp}}const Dp=ki(({f0:e,f90:t,dotVH:r})=>{const s=r.mul(-5.55473).sub(6.98316).mul(r).exp2();return e.mul(s.oneMinus()).add(t.mul(s))}),Ip=ki(e=>e.diffuseColor.mul(1/Math.PI)),Vp=ki(({dotNH:e})=>Ln.mul(ji(.5)).add(1).mul(ji(1/Math.PI)).mul(e.pow(Ln))),Up=ki(({lightDirection:e})=>{const t=e.add(zl).normalize(),r=Zl.dot(t).clamp(),s=zl.dot(t).clamp(),i=Dp({f0:Bn,f90:1,dotVH:s}),n=ji(.25),a=Vp({dotNH:r});return i.mul(n).mul(a)});class Op extends Bp{constructor(e=!0){super(),this.specular=e}direct({lightDirection:e,lightColor:t,reflectedLight:r}){const s=Zl.dot(e).clamp().mul(t);r.directDiffuse.addAssign(s.mul(Ip({diffuseColor:yn.rgb}))),!0===this.specular&&r.directSpecular.addAssign(s.mul(Up({lightDirection:e})).mul(pc))}indirect(e){const{ambientOcclusion:t,irradiance:r,reflectedLight:s}=e.context;s.indirectDiffuse.addAssign(r.mul(Ip({diffuseColor:yn}))),s.indirectDiffuse.mulAssign(t)}}const kp=new ie;class Gp extends lp{static get type(){return"MeshLambertNodeMaterial"}constructor(e){super(),this.isMeshLambertNodeMaterial=!0,this.lights=!0,this.setDefaultValues(kp),this.setValues(e)}setupEnvironment(e){const t=super.setupEnvironment(e);return t?new Cp(t):null}setupLightingModel(){return new Op(!1)}}const zp=new ne;class Hp extends lp{static get type(){return"MeshPhongNodeMaterial"}constructor(e){super(),this.isMeshPhongNodeMaterial=!0,this.lights=!0,this.shininessNode=null,this.specularNode=null,this.setDefaultValues(zp),this.setValues(e)}setupEnvironment(e){const t=super.setupEnvironment(e);return t?new Cp(t):null}setupLightingModel(){return new Op}setupVariants(){const e=(this.shininessNode?ji(this.shininessNode):oc).max(1e-4);Ln.assign(e);const t=this.specularNode||dc;Bn.assign(t)}copy(e){return this.shininessNode=e.shininessNode,this.specularNode=e.specularNode,super.copy(e)}}const $p=ki(e=>{if(!1===e.geometry.hasAttribute("normal"))return ji(0);const t=Yl.dFdx().abs().max(Yl.dFdy().abs());return t.x.max(t.y).max(t.z)}),Wp=ki(e=>{const{roughness:t}=e,r=$p();let s=t.max(.0525);return s=s.add(r),s=s.min(1),s}),jp=ki(({alpha:e,dotNL:t,dotNV:r})=>{const s=e.pow2(),i=t.mul(s.add(s.oneMinus().mul(r.pow2())).sqrt()),n=r.mul(s.add(s.oneMinus().mul(t.pow2())).sqrt());return da(.5,i.add(n).max(La))}).setLayout({name:"V_GGX_SmithCorrelated",type:"float",inputs:[{name:"alpha",type:"float"},{name:"dotNL",type:"float"},{name:"dotNV",type:"float"}]}),qp=ki(({alphaT:e,alphaB:t,dotTV:r,dotBV:s,dotTL:i,dotBL:n,dotNV:a,dotNL:o})=>{const u=o.mul(en(e.mul(r),t.mul(s),a).length()),l=a.mul(en(e.mul(i),t.mul(n),o).length());return da(.5,u.add(l)).saturate()}).setLayout({name:"V_GGX_SmithCorrelated_Anisotropic",type:"float",inputs:[{name:"alphaT",type:"float",qualifier:"in"},{name:"alphaB",type:"float",qualifier:"in"},{name:"dotTV",type:"float",qualifier:"in"},{name:"dotBV",type:"float",qualifier:"in"},{name:"dotTL",type:"float",qualifier:"in"},{name:"dotBL",type:"float",qualifier:"in"},{name:"dotNV",type:"float",qualifier:"in"},{name:"dotNL",type:"float",qualifier:"in"}]}),Xp=ki(({alpha:e,dotNH:t})=>{const r=e.pow2(),s=t.pow2().mul(r.oneMinus()).oneMinus();return r.div(s.pow2()).mul(1/Math.PI)}).setLayout({name:"D_GGX",type:"float",inputs:[{name:"alpha",type:"float"},{name:"dotNH",type:"float"}]}),Kp=ji(1/Math.PI),Yp=ki(({alphaT:e,alphaB:t,dotNH:r,dotTH:s,dotBH:i})=>{const n=e.mul(t),a=en(t.mul(s),e.mul(i),n.mul(r)),o=a.dot(a),u=n.div(o);return Kp.mul(n.mul(u.pow2()))}).setLayout({name:"D_GGX_Anisotropic",type:"float",inputs:[{name:"alphaT",type:"float",qualifier:"in"},{name:"alphaB",type:"float",qualifier:"in"},{name:"dotNH",type:"float",qualifier:"in"},{name:"dotTH",type:"float",qualifier:"in"},{name:"dotBH",type:"float",qualifier:"in"}]}),Qp=ki(({lightDirection:e,f0:t,f90:r,roughness:s,f:i,normalView:n=Zl,USE_IRIDESCENCE:a,USE_ANISOTROPY:o})=>{const u=s.pow2(),l=e.add(zl).normalize(),d=n.dot(e).clamp(),c=n.dot(zl).clamp(),h=n.dot(l).clamp(),p=zl.dot(l).clamp();let g,m,f=Dp({f0:t,f90:r,dotVH:p});if(Pi(a)&&(f=En.mix(f,i)),Pi(o)){const t=Mn.dot(e),r=Mn.dot(zl),s=Mn.dot(l),i=Pn.dot(e),n=Pn.dot(zl),a=Pn.dot(l);g=qp({alphaT:Rn,alphaB:u,dotTV:r,dotBV:n,dotTL:t,dotBL:i,dotNV:c,dotNL:d}),m=Yp({alphaT:Rn,alphaB:u,dotNH:h,dotTH:s,dotBH:a})}else g=jp({alpha:u,dotNL:d,dotNV:c}),m=Xp({alpha:u,dotNH:h});return f.mul(g).mul(m)}),Zp=ki(({roughness:e,dotNV:t})=>{const r=nn(-1,-.0275,-.572,.022),s=nn(1,.0425,1.04,-.04),i=e.mul(r).add(s),n=i.x.mul(i.x).min(t.mul(-9.28).exp2()).mul(i.x).add(i.y);return Yi(-1.04,1.04).mul(n).add(i.zw)}).setLayout({name:"DFGApprox",type:"vec2",inputs:[{name:"roughness",type:"float"},{name:"dotNV",type:"vec3"}]}),Jp=ki(e=>{const{dotNV:t,specularColor:r,specularF90:s,roughness:i}=e,n=Zp({dotNV:t,roughness:i});return r.mul(n.x).add(s.mul(n.y))}),eg=ki(({f:e,f90:t,dotVH:r})=>{const s=r.oneMinus().saturate(),i=s.mul(s),n=s.mul(i,i).clamp(0,.9999);return e.sub(en(t).mul(n)).div(n.oneMinus())}).setLayout({name:"Schlick_to_F0",type:"vec3",inputs:[{name:"f",type:"vec3"},{name:"f90",type:"float"},{name:"dotVH",type:"float"}]}),tg=ki(({roughness:e,dotNH:t})=>{const r=e.pow2(),s=ji(1).div(r),i=t.pow2().oneMinus().max(.0078125);return ji(2).add(s).mul(i.pow(s.mul(.5))).div(2*Math.PI)}).setLayout({name:"D_Charlie",type:"float",inputs:[{name:"roughness",type:"float"},{name:"dotNH",type:"float"}]}),rg=ki(({dotNV:e,dotNL:t})=>ji(1).div(ji(4).mul(t.add(e).sub(t.mul(e))))).setLayout({name:"V_Neubelt",type:"float",inputs:[{name:"dotNV",type:"float"},{name:"dotNL",type:"float"}]}),sg=ki(({lightDirection:e})=>{const t=e.add(zl).normalize(),r=Zl.dot(e).clamp(),s=Zl.dot(zl).clamp(),i=Zl.dot(t).clamp(),n=tg({roughness:Sn,dotNH:i}),a=rg({dotNV:s,dotNL:r});return Nn.mul(n).mul(a)}),ig=ki(({N:e,V:t,roughness:r})=>{const s=e.dot(t).saturate(),i=Yi(r,s.oneMinus().sqrt());return i.assign(i.mul(.984375).add(.0078125)),i}).setLayout({name:"LTC_Uv",type:"vec2",inputs:[{name:"N",type:"vec3"},{name:"V",type:"vec3"},{name:"roughness",type:"float"}]}),ng=ki(({f:e})=>{const t=e.length();return To(t.mul(t).add(e.z).div(t.add(1)),0)}).setLayout({name:"LTC_ClippedSphereFormFactor",type:"float",inputs:[{name:"f",type:"vec3"}]}),ag=ki(({v1:e,v2:t})=>{const r=e.dot(t),s=r.abs().toVar(),i=s.mul(.0145206).add(.4965155).mul(s).add(.8543985).toVar(),n=s.add(4.1616724).mul(s).add(3.417594).toVar(),a=i.div(n),o=r.greaterThan(0).select(a,To(r.mul(r).oneMinus(),1e-7).inverseSqrt().mul(.5).sub(a));return e.cross(t).mul(o)}).setLayout({name:"LTC_EdgeVectorFormFactor",type:"vec3",inputs:[{name:"v1",type:"vec3"},{name:"v2",type:"vec3"}]}),og=ki(({N:e,V:t,P:r,mInv:s,p0:i,p1:n,p2:a,p3:o})=>{const u=n.sub(i).toVar(),l=o.sub(i).toVar(),d=u.cross(l),c=en().toVar();return Hi(d.dot(r.sub(i)).greaterThanEqual(0),()=>{const u=t.sub(e.mul(t.dot(e))).normalize(),l=e.cross(u).negate(),d=s.mul(dn(u,l,e).transpose()).toVar(),h=d.mul(i.sub(r)).normalize().toVar(),p=d.mul(n.sub(r)).normalize().toVar(),g=d.mul(a.sub(r)).normalize().toVar(),m=d.mul(o.sub(r)).normalize().toVar(),f=en(0).toVar();f.addAssign(ag({v1:h,v2:p})),f.addAssign(ag({v1:p,v2:g})),f.addAssign(ag({v1:g,v2:m})),f.addAssign(ag({v1:m,v2:h})),c.assign(en(ng({f:f})))}),c}).setLayout({name:"LTC_Evaluate",type:"vec3",inputs:[{name:"N",type:"vec3"},{name:"V",type:"vec3"},{name:"P",type:"vec3"},{name:"mInv",type:"mat3"},{name:"p0",type:"vec3"},{name:"p1",type:"vec3"},{name:"p2",type:"vec3"},{name:"p3",type:"vec3"}]}),ug=ki(({P:e,p0:t,p1:r,p2:s,p3:i})=>{const n=r.sub(t).toVar(),a=i.sub(t).toVar(),o=n.cross(a),u=en().toVar();return Hi(o.dot(e.sub(t)).greaterThanEqual(0),()=>{const n=t.sub(e).normalize().toVar(),a=r.sub(e).normalize().toVar(),o=s.sub(e).normalize().toVar(),l=i.sub(e).normalize().toVar(),d=en(0).toVar();d.addAssign(ag({v1:n,v2:a})),d.addAssign(ag({v1:a,v2:o})),d.addAssign(ag({v1:o,v2:l})),d.addAssign(ag({v1:l,v2:n})),u.assign(en(ng({f:d.abs()})))}),u}).setLayout({name:"LTC_Evaluate",type:"vec3",inputs:[{name:"P",type:"vec3"},{name:"p0",type:"vec3"},{name:"p1",type:"vec3"},{name:"p2",type:"vec3"},{name:"p3",type:"vec3"}]}),lg=1/6,dg=e=>la(lg,la(e,la(e,e.negate().add(3)).sub(3)).add(1)),cg=e=>la(lg,la(e,la(e,la(3,e).sub(6))).add(4)),hg=e=>la(lg,la(e,la(e,la(-3,e).add(3)).add(3)).add(1)),pg=e=>la(lg,Ao(e,3)),gg=e=>dg(e).add(cg(e)),mg=e=>hg(e).add(pg(e)),fg=e=>oa(-1,cg(e).div(dg(e).add(cg(e)))),yg=e=>oa(1,pg(e).div(hg(e).add(pg(e)))),bg=(e,t,r)=>{const s=e.uvNode,i=la(s,t.zw).add(.5),n=Xa(i),a=Qa(i),o=gg(a.x),u=mg(a.x),l=fg(a.x),d=yg(a.x),c=fg(a.y),h=yg(a.y),p=Yi(n.x.add(l),n.y.add(c)).sub(.5).mul(t.xy),g=Yi(n.x.add(d),n.y.add(c)).sub(.5).mul(t.xy),m=Yi(n.x.add(l),n.y.add(h)).sub(.5).mul(t.xy),f=Yi(n.x.add(d),n.y.add(h)).sub(.5).mul(t.xy),y=gg(a.y).mul(oa(o.mul(e.sample(p).level(r)),u.mul(e.sample(g).level(r)))),b=mg(a.y).mul(oa(o.mul(e.sample(m).level(r)),u.mul(e.sample(f).level(r))));return y.add(b)},xg=ki(([e,t])=>{const r=Yi(e.size(qi(t))),s=Yi(e.size(qi(t.add(1)))),i=da(1,r),n=da(1,s),a=bg(e,nn(i,r),Xa(t)),o=bg(e,nn(n,s),Ka(t));return Qa(t).mix(a,o)}),Tg=ki(([e,t])=>{const r=t.mul(Xu(e));return xg(e,r)}),_g=ki(([e,t,r,s,i])=>{const n=en(Vo(t.negate(),Ya(e),da(1,s))),a=en(ao(i[0].xyz),ao(i[1].xyz),ao(i[2].xyz));return Ya(n).mul(r.mul(a))}).setLayout({name:"getVolumeTransmissionRay",type:"vec3",inputs:[{name:"n",type:"vec3"},{name:"v",type:"vec3"},{name:"thickness",type:"float"},{name:"ior",type:"float"},{name:"modelMatrix",type:"mat4"}]}),vg=ki(([e,t])=>e.mul(Do(t.mul(2).sub(2),0,1))).setLayout({name:"applyIorToRoughness",type:"float",inputs:[{name:"roughness",type:"float"},{name:"ior",type:"float"}]}),Ng=Vh(),Sg=Vh(),Eg=ki(([e,t,r],{material:s})=>{const i=(s.side===S?Ng:Sg).sample(e),n=Wa(Ah.x).mul(vg(t,r));return xg(i,n)}),wg=ki(([e,t,r])=>(Hi(r.notEqual(0),()=>{const s=$a(t).negate().div(r);return za(s.negate().mul(e))}),en(1))).setLayout({name:"volumeAttenuation",type:"vec3",inputs:[{name:"transmissionDistance",type:"float"},{name:"attenuationColor",type:"vec3"},{name:"attenuationDistance",type:"float"}]}),Ag=ki(([e,t,r,s,i,n,a,o,u,l,d,c,h,p,g])=>{let m,f;if(g){m=nn().toVar(),f=en().toVar();const i=d.sub(1).mul(g.mul(.025)),n=en(d.sub(i),d,d.add(i));ch({start:0,end:3},({i:i})=>{const d=n.element(i),g=_g(e,t,c,d,o),y=a.add(g),b=l.mul(u.mul(nn(y,1))),x=Yi(b.xy.div(b.w)).toVar();x.addAssign(1),x.divAssign(2),x.assign(Yi(x.x,x.y.oneMinus()));const T=Eg(x,r,d);m.element(i).assign(T.element(i)),m.a.addAssign(T.a),f.element(i).assign(s.element(i).mul(wg(ao(g),h,p).element(i)))}),m.a.divAssign(3)}else{const i=_g(e,t,c,d,o),n=a.add(i),g=l.mul(u.mul(nn(n,1))),y=Yi(g.xy.div(g.w)).toVar();y.addAssign(1),y.divAssign(2),y.assign(Yi(y.x,y.y.oneMinus())),m=Eg(y,r,d),f=s.mul(wg(ao(i),h,p))}const y=f.rgb.mul(m.rgb),b=e.dot(t).clamp(),x=en(Jp({dotNV:b,specularColor:i,specularF90:n,roughness:r})),T=f.r.add(f.g,f.b).div(3);return nn(x.oneMinus().mul(y),m.a.oneMinus().mul(T).oneMinus())}),Rg=dn(3.2404542,-.969266,.0556434,-1.5371385,1.8760108,-.2040259,-.4985314,.041556,1.0572252),Cg=(e,t)=>e.sub(t).div(e.add(t)).pow2(),Mg=ki(({outsideIOR:e,eta2:t,cosTheta1:r,thinFilmThickness:s,baseF0:i})=>{const n=Lo(e,t,Uo(0,.03,s)),a=e.div(n).pow2().mul(r.pow2().oneMinus()).oneMinus();Hi(a.lessThan(0),()=>en(1));const o=a.sqrt(),u=Cg(n,e),l=Dp({f0:u,f90:1,dotVH:r}),d=l.oneMinus(),c=n.lessThan(e).select(Math.PI,0),h=ji(Math.PI).sub(c),p=(e=>{const t=e.sqrt();return en(1).add(t).div(en(1).sub(t))})(i.clamp(0,.9999)),g=Cg(p,n.toVec3()),m=Dp({f0:g,f90:1,dotVH:o}),f=en(p.x.lessThan(n).select(Math.PI,0),p.y.lessThan(n).select(Math.PI,0),p.z.lessThan(n).select(Math.PI,0)),y=n.mul(s,o,2),b=en(h).add(f),x=l.mul(m).clamp(1e-5,.9999),T=x.sqrt(),_=d.pow2().mul(m).div(en(1).sub(x)),v=l.add(_).toVar(),N=_.sub(d).toVar();return ch({start:1,end:2,condition:"<=",name:"m"},({m:e})=>{N.mulAssign(T);const t=((e,t)=>{const r=e.mul(2*Math.PI*1e-9),s=en(54856e-17,44201e-17,52481e-17),i=en(1681e3,1795300,2208400),n=en(43278e5,93046e5,66121e5),a=ji(9747e-17*Math.sqrt(2*Math.PI*45282e5)).mul(r.mul(2239900).add(t.x).cos()).mul(r.pow2().mul(-45282e5).exp());let o=s.mul(n.mul(2*Math.PI).sqrt()).mul(i.mul(r).add(t).cos()).mul(r.pow2().negate().mul(n).exp());return o=en(o.x.add(a),o.y,o.z).div(1.0685e-7),Rg.mul(o)})(ji(e).mul(y),ji(e).mul(b)).mul(2);v.addAssign(N.mul(t))}),v.max(en(0))}).setLayout({name:"evalIridescence",type:"vec3",inputs:[{name:"outsideIOR",type:"float"},{name:"eta2",type:"float"},{name:"cosTheta1",type:"float"},{name:"thinFilmThickness",type:"float"},{name:"baseF0",type:"vec3"}]}),Pg=ki(({normal:e,viewDir:t,roughness:r})=>{const s=e.dot(t).saturate(),i=r.pow2(),n=Xo(r.lessThan(.25),ji(-339.2).mul(i).add(ji(161.4).mul(r)).sub(25.9),ji(-8.48).mul(i).add(ji(14.3).mul(r)).sub(9.95)),a=Xo(r.lessThan(.25),ji(44).mul(i).sub(ji(23.7).mul(r)).add(3.26),ji(1.97).mul(i).sub(ji(3.27).mul(r)).add(.72));return Xo(r.lessThan(.25),0,ji(.1).mul(r).sub(.025)).add(n.mul(s).add(a).exp()).mul(1/Math.PI).saturate()}),Bg=en(.04),Fg=ji(1);class Lg extends Pp{constructor(e=!1,t=!1,r=!1,s=!1,i=!1,n=!1){super(),this.clearcoat=e,this.sheen=t,this.iridescence=r,this.anisotropy=s,this.transmission=i,this.dispersion=n,this.clearcoatRadiance=null,this.clearcoatSpecularDirect=null,this.clearcoatSpecularIndirect=null,this.sheenSpecularDirect=null,this.sheenSpecularIndirect=null,this.iridescenceFresnel=null,this.iridescenceF0=null}start(e){if(!0===this.clearcoat&&(this.clearcoatRadiance=en().toVar("clearcoatRadiance"),this.clearcoatSpecularDirect=en().toVar("clearcoatSpecularDirect"),this.clearcoatSpecularIndirect=en().toVar("clearcoatSpecularIndirect")),!0===this.sheen&&(this.sheenSpecularDirect=en().toVar("sheenSpecularDirect"),this.sheenSpecularIndirect=en().toVar("sheenSpecularIndirect")),!0===this.iridescence){const e=Zl.dot(zl).clamp();this.iridescenceFresnel=Mg({outsideIOR:ji(1),eta2:wn,cosTheta1:e,thinFilmThickness:An,baseF0:Bn}),this.iridescenceF0=eg({f:this.iridescenceFresnel,f90:1,dotVH:e})}if(!0===this.transmission){const t=Ol,r=gl.sub(Ol).normalize(),s=Jl,i=e.context;i.backdrop=Ag(s,r,xn,yn,Bn,Fn,t,El,cl,ll,On,Gn,Hn,zn,this.dispersion?$n:null),i.backdropAlpha=kn,yn.a.mulAssign(Lo(1,i.backdrop.a,kn))}super.start(e)}computeMultiscattering(e,t,r){const s=Zl.dot(zl).clamp(),i=Zp({roughness:xn,dotNV:s}),n=(this.iridescenceF0?En.mix(Bn,this.iridescenceF0):Bn).mul(i.x).add(r.mul(i.y)),a=i.x.add(i.y).oneMinus(),o=Bn.add(Bn.oneMinus().mul(.047619)),u=n.mul(o).div(a.mul(o).oneMinus());e.addAssign(n),t.addAssign(u.mul(a))}direct({lightDirection:e,lightColor:t,reflectedLight:r}){const s=Zl.dot(e).clamp().mul(t);if(!0===this.sheen&&this.sheenSpecularDirect.addAssign(s.mul(sg({lightDirection:e}))),!0===this.clearcoat){const r=ed.dot(e).clamp().mul(t);this.clearcoatSpecularDirect.addAssign(r.mul(Qp({lightDirection:e,f0:Bg,f90:Fg,roughness:vn,normalView:ed})))}r.directDiffuse.addAssign(s.mul(Ip({diffuseColor:yn.rgb}))),r.directSpecular.addAssign(s.mul(Qp({lightDirection:e,f0:Bn,f90:1,roughness:xn,iridescence:this.iridescence,f:this.iridescenceFresnel,USE_IRIDESCENCE:this.iridescence,USE_ANISOTROPY:this.anisotropy})))}directRectArea({lightColor:e,lightPosition:t,halfWidth:r,halfHeight:s,reflectedLight:i,ltc_1:n,ltc_2:a}){const o=t.add(r).sub(s),u=t.sub(r).sub(s),l=t.sub(r).add(s),d=t.add(r).add(s),c=Zl,h=zl,p=Gl.toVar(),g=ig({N:c,V:h,roughness:xn}),m=n.sample(g).toVar(),f=a.sample(g).toVar(),y=dn(en(m.x,0,m.y),en(0,1,0),en(m.z,0,m.w)).toVar(),b=Bn.mul(f.x).add(Bn.oneMinus().mul(f.y)).toVar();i.directSpecular.addAssign(e.mul(b).mul(og({N:c,V:h,P:p,mInv:y,p0:o,p1:u,p2:l,p3:d}))),i.directDiffuse.addAssign(e.mul(yn).mul(og({N:c,V:h,P:p,mInv:dn(1,0,0,0,1,0,0,0,1),p0:o,p1:u,p2:l,p3:d})))}indirect(e){this.indirectDiffuse(e),this.indirectSpecular(e),this.ambientOcclusion(e)}indirectDiffuse(e){const{irradiance:t,reflectedLight:r}=e.context;r.indirectDiffuse.addAssign(t.mul(Ip({diffuseColor:yn})))}indirectSpecular(e){const{radiance:t,iblIrradiance:r,reflectedLight:s}=e.context;if(!0===this.sheen&&this.sheenSpecularIndirect.addAssign(r.mul(Nn,Pg({normal:Zl,viewDir:zl,roughness:Sn}))),!0===this.clearcoat){const e=ed.dot(zl).clamp(),t=Jp({dotNV:e,specularColor:Bg,specularF90:Fg,roughness:vn});this.clearcoatSpecularIndirect.addAssign(this.clearcoatRadiance.mul(t))}const i=en().toVar("singleScattering"),n=en().toVar("multiScattering"),a=r.mul(1/Math.PI);this.computeMultiscattering(i,n,Fn);const o=i.add(n),u=yn.mul(o.r.max(o.g).max(o.b).oneMinus());s.indirectSpecular.addAssign(t.mul(i)),s.indirectSpecular.addAssign(n.mul(a)),s.indirectDiffuse.addAssign(u.mul(a))}ambientOcclusion(e){const{ambientOcclusion:t,reflectedLight:r}=e.context,s=Zl.dot(zl).clamp().add(t),i=xn.mul(-16).oneMinus().negate().exp2(),n=t.sub(s.pow(i).oneMinus()).clamp();!0===this.clearcoat&&this.clearcoatSpecularIndirect.mulAssign(t),!0===this.sheen&&this.sheenSpecularIndirect.mulAssign(t),r.indirectDiffuse.mulAssign(t),r.indirectSpecular.mulAssign(n)}finish({context:e}){const{outgoingLight:t}=e;if(!0===this.clearcoat){const e=ed.dot(zl).clamp(),r=Dp({dotVH:e,f0:Bg,f90:Fg}),s=t.mul(_n.mul(r).oneMinus()).add(this.clearcoatSpecularDirect.add(this.clearcoatSpecularIndirect).mul(_n));t.assign(s)}if(!0===this.sheen){const e=Nn.r.max(Nn.g).max(Nn.b).mul(.157).oneMinus(),r=t.mul(e).add(this.sheenSpecularDirect,this.sheenSpecularIndirect);t.assign(r)}}}const Dg=ji(1),Ig=ji(-2),Vg=ji(.8),Ug=ji(-1),Og=ji(.4),kg=ji(2),Gg=ji(.305),zg=ji(3),Hg=ji(.21),$g=ji(4),Wg=ji(4),jg=ji(16),qg=ki(([e])=>{const t=en(io(e)).toVar(),r=ji(-1).toVar();return Hi(t.x.greaterThan(t.z),()=>{Hi(t.x.greaterThan(t.y),()=>{r.assign(Xo(e.x.greaterThan(0),0,3))}).Else(()=>{r.assign(Xo(e.y.greaterThan(0),1,4))})}).Else(()=>{Hi(t.z.greaterThan(t.y),()=>{r.assign(Xo(e.z.greaterThan(0),2,5))}).Else(()=>{r.assign(Xo(e.y.greaterThan(0),1,4))})}),r}).setLayout({name:"getFace",type:"float",inputs:[{name:"direction",type:"vec3"}]}),Xg=ki(([e,t])=>{const r=Yi().toVar();return Hi(t.equal(0),()=>{r.assign(Yi(e.z,e.y).div(io(e.x)))}).ElseIf(t.equal(1),()=>{r.assign(Yi(e.x.negate(),e.z.negate()).div(io(e.y)))}).ElseIf(t.equal(2),()=>{r.assign(Yi(e.x.negate(),e.y).div(io(e.z)))}).ElseIf(t.equal(3),()=>{r.assign(Yi(e.z.negate(),e.y).div(io(e.x)))}).ElseIf(t.equal(4),()=>{r.assign(Yi(e.x.negate(),e.z).div(io(e.y)))}).Else(()=>{r.assign(Yi(e.x,e.y).div(io(e.z)))}),la(.5,r.add(1))}).setLayout({name:"getUV",type:"vec2",inputs:[{name:"direction",type:"vec3"},{name:"face",type:"float"}]}),Kg=ki(([e])=>{const t=ji(0).toVar();return Hi(e.greaterThanEqual(Vg),()=>{t.assign(Dg.sub(e).mul(Ug.sub(Ig)).div(Dg.sub(Vg)).add(Ig))}).ElseIf(e.greaterThanEqual(Og),()=>{t.assign(Vg.sub(e).mul(kg.sub(Ug)).div(Vg.sub(Og)).add(Ug))}).ElseIf(e.greaterThanEqual(Gg),()=>{t.assign(Og.sub(e).mul(zg.sub(kg)).div(Og.sub(Gg)).add(kg))}).ElseIf(e.greaterThanEqual(Hg),()=>{t.assign(Gg.sub(e).mul($g.sub(zg)).div(Gg.sub(Hg)).add(zg))}).Else(()=>{t.assign(ji(-2).mul(Wa(la(1.16,e))))}),t}).setLayout({name:"roughnessToMip",type:"float",inputs:[{name:"roughness",type:"float"}]}),Yg=ki(([e,t])=>{const r=e.toVar();r.assign(la(2,r).sub(1));const s=en(r,1).toVar();return Hi(t.equal(0),()=>{s.assign(s.zyx)}).ElseIf(t.equal(1),()=>{s.assign(s.xzy),s.xz.mulAssign(-1)}).ElseIf(t.equal(2),()=>{s.x.mulAssign(-1)}).ElseIf(t.equal(3),()=>{s.assign(s.zyx),s.xz.mulAssign(-1)}).ElseIf(t.equal(4),()=>{s.assign(s.xzy),s.xy.mulAssign(-1)}).ElseIf(t.equal(5),()=>{s.z.mulAssign(-1)}),s}).setLayout({name:"getDirection",type:"vec3",inputs:[{name:"uv",type:"vec2"},{name:"face",type:"float"}]}),Qg=ki(([e,t,r,s,i,n])=>{const a=ji(r),o=en(t),u=Do(Kg(a),Ig,n),l=Qa(u),d=Xa(u),c=en(Zg(e,o,d,s,i,n)).toVar();return Hi(l.notEqual(0),()=>{const t=en(Zg(e,o,d.add(1),s,i,n)).toVar();c.assign(Lo(c,t,l))}),c}),Zg=ki(([e,t,r,s,i,n])=>{const a=ji(r).toVar(),o=en(t),u=ji(qg(o)).toVar(),l=ji(To(Wg.sub(a),0)).toVar();a.assign(To(a,Wg));const d=ji(Ha(a)).toVar(),c=Yi(Xg(o,u).mul(d.sub(2)).add(1)).toVar();return Hi(u.greaterThan(2),()=>{c.y.addAssign(d),u.subAssign(3)}),c.x.addAssign(u.mul(d)),c.x.addAssign(l.mul(la(3,jg))),c.y.addAssign(la(4,Ha(n).sub(d))),c.x.mulAssign(s),c.y.mulAssign(i),e.sample(c).grad(Yi(),Yi())}),Jg=ki(({envMap:e,mipInt:t,outputDirection:r,theta:s,axis:i,CUBEUV_TEXEL_WIDTH:n,CUBEUV_TEXEL_HEIGHT:a,CUBEUV_MAX_MIP:o})=>{const u=Ja(s),l=r.mul(u).add(i.cross(r).mul(Za(s))).add(i.mul(i.dot(r).mul(u.oneMinus())));return Zg(e,l,t,n,a,o)}),em=ki(({n:e,latitudinal:t,poleAxis:r,outputDirection:s,weights:i,samples:n,dTheta:a,mipInt:o,envMap:u,CUBEUV_TEXEL_WIDTH:l,CUBEUV_TEXEL_HEIGHT:d,CUBEUV_MAX_MIP:c})=>{const h=en(Xo(t,r,wo(r,s))).toVar();Hi(h.equal(en(0)),()=>{h.assign(en(s.z,0,s.x.negate()))}),h.assign(Ya(h));const p=en().toVar();return p.addAssign(i.element(0).mul(Jg({theta:0,axis:h,outputDirection:s,mipInt:o,envMap:u,CUBEUV_TEXEL_WIDTH:l,CUBEUV_TEXEL_HEIGHT:d,CUBEUV_MAX_MIP:c}))),ch({start:qi(1),end:e},({i:e})=>{Hi(e.greaterThanEqual(n),()=>{hh()});const t=ji(a.mul(ji(e))).toVar();p.addAssign(i.element(e).mul(Jg({theta:t.mul(-1),axis:h,outputDirection:s,mipInt:o,envMap:u,CUBEUV_TEXEL_WIDTH:l,CUBEUV_TEXEL_HEIGHT:d,CUBEUV_MAX_MIP:c}))),p.addAssign(i.element(e).mul(Jg({theta:t,axis:h,outputDirection:s,mipInt:o,envMap:u,CUBEUV_TEXEL_WIDTH:l,CUBEUV_TEXEL_HEIGHT:d,CUBEUV_MAX_MIP:c})))}),nn(p,1)}),tm=[.125,.215,.35,.446,.526,.582],rm=20,sm=new ae(-1,1,1,-1,0,1),im=new oe(90,1),nm=new e;let am=null,om=0,um=0;const lm=(1+Math.sqrt(5))/2,dm=1/lm,cm=[new r(-lm,dm,0),new r(lm,dm,0),new r(-dm,0,lm),new r(dm,0,lm),new r(0,lm,-dm),new r(0,lm,dm),new r(-1,1,-1),new r(1,1,-1),new r(-1,1,1),new r(1,1,1)],hm=new r,pm=new WeakMap,gm=[3,1,5,0,4,2],mm=Yg($u(),Hu("faceIndex")).normalize(),fm=en(mm.x,mm.y,mm.z);class ym{constructor(e){this._renderer=e,this._pingPongRenderTarget=null,this._lodMax=0,this._cubeSize=0,this._lodPlanes=[],this._sizeLods=[],this._sigmas=[],this._lodMeshes=[],this._blurMaterial=null,this._cubemapMaterial=null,this._equirectMaterial=null,this._backgroundBox=null}get _hasInitialized(){return this._renderer.hasInitialized()}fromScene(e,t=0,r=.1,s=100,i={}){const{size:n=256,position:a=hm,renderTarget:o=null}=i;if(this._setSize(n),!1===this._hasInitialized){console.warn("THREE.PMREMGenerator: .fromScene() called before the backend is initialized. Try using .fromSceneAsync() instead.");const n=o||this._allocateTarget();return i.renderTarget=n,this.fromSceneAsync(e,t,r,s,i),n}am=this._renderer.getRenderTarget(),om=this._renderer.getActiveCubeFace(),um=this._renderer.getActiveMipmapLevel();const u=o||this._allocateTarget();return u.depthBuffer=!0,this._init(u),this._sceneToCubeUV(e,r,s,u,a),t>0&&this._blur(u,0,0,t),this._applyPMREM(u),this._cleanup(u),u}async fromSceneAsync(e,t=0,r=.1,s=100,i={}){return!1===this._hasInitialized&&await this._renderer.init(),this.fromScene(e,t,r,s,i)}fromEquirectangular(e,t=null){if(!1===this._hasInitialized){console.warn("THREE.PMREMGenerator: .fromEquirectangular() called before the backend is initialized. Try using .fromEquirectangularAsync() instead."),this._setSizeFromTexture(e);const r=t||this._allocateTarget();return this.fromEquirectangularAsync(e,r),r}return this._fromTexture(e,t)}async fromEquirectangularAsync(e,t=null){return!1===this._hasInitialized&&await this._renderer.init(),this._fromTexture(e,t)}fromCubemap(e,t=null){if(!1===this._hasInitialized){console.warn("THREE.PMREMGenerator: .fromCubemap() called before the backend is initialized. Try using .fromCubemapAsync() instead."),this._setSizeFromTexture(e);const r=t||this._allocateTarget();return this.fromCubemapAsync(e,t),r}return this._fromTexture(e,t)}async fromCubemapAsync(e,t=null){return!1===this._hasInitialized&&await this._renderer.init(),this._fromTexture(e,t)}async compileCubemapShader(){null===this._cubemapMaterial&&(this._cubemapMaterial=_m(),await this._compileMaterial(this._cubemapMaterial))}async compileEquirectangularShader(){null===this._equirectMaterial&&(this._equirectMaterial=vm(),await this._compileMaterial(this._equirectMaterial))}dispose(){this._dispose(),null!==this._cubemapMaterial&&this._cubemapMaterial.dispose(),null!==this._equirectMaterial&&this._equirectMaterial.dispose(),null!==this._backgroundBox&&(this._backgroundBox.geometry.dispose(),this._backgroundBox.material.dispose())}_setSizeFromTexture(e){e.mapping===R||e.mapping===C?this._setSize(0===e.image.length?16:e.image[0].width||e.image[0].image.width):this._setSize(e.image.width/4)}_setSize(e){this._lodMax=Math.floor(Math.log2(e)),this._cubeSize=Math.pow(2,this._lodMax)}_dispose(){null!==this._blurMaterial&&this._blurMaterial.dispose(),null!==this._pingPongRenderTarget&&this._pingPongRenderTarget.dispose();for(let e=0;ee-4?u=tm[o-e+4-1]:0===o&&(u=0),s.push(u);const l=1/(a-2),d=-l,c=1+l,h=[d,d,c,d,c,c,d,d,c,c,d,c],p=6,g=6,m=3,f=2,y=1,b=new Float32Array(m*g*p),x=new Float32Array(f*g*p),T=new Float32Array(y*g*p);for(let e=0;e2?0:-1,s=[t,r,0,t+2/3,r,0,t+2/3,r+1,0,t,r,0,t+2/3,r+1,0,t,r+1,0],i=gm[e];b.set(s,m*g*i),x.set(h,f*g*i);const n=[i,i,i,i,i,i];T.set(n,y*g*i)}const _=new pe;_.setAttribute("position",new ge(b,m)),_.setAttribute("uv",new ge(x,f)),_.setAttribute("faceIndex",new ge(T,y)),t.push(_),i.push(new X(_,null)),n>4&&n--}return{lodPlanes:t,sizeLods:r,sigmas:s,lodMeshes:i}}(t)),this._blurMaterial=function(e,t,s){const i=il(new Array(rm).fill(0)),n=Zn(new r(0,1,0)),a=Zn(0),o=ji(rm),u=Zn(0),l=Zn(1),d=Zu(null),c=Zn(0),h=ji(1/t),p=ji(1/s),g=ji(e),m={n:o,latitudinal:u,weights:i,poleAxis:n,outputDirection:fm,dTheta:a,samples:l,envMap:d,mipInt:c,CUBEUV_TEXEL_WIDTH:h,CUBEUV_TEXEL_HEIGHT:p,CUBEUV_MAX_MIP:g},f=Tm("blur");return f.fragmentNode=em({...m,latitudinal:u.equal(1)}),pm.set(f,m),f}(t,e.width,e.height)}}async _compileMaterial(e){const t=new X(this._lodPlanes[0],e);await this._renderer.compile(t,sm)}_sceneToCubeUV(e,t,r,s,i){const n=im;n.near=t,n.far=r;const a=[1,1,1,1,-1,1],o=[1,-1,1,-1,1,-1],u=this._renderer,l=u.autoClear;u.getClearColor(nm),u.autoClear=!1;let d=this._backgroundBox;if(null===d){const e=new se({name:"PMREM.Background",side:S,depthWrite:!1,depthTest:!1});d=new X(new q,e)}let c=!1;const h=e.background;h?h.isColor&&(d.material.color.copy(h),e.background=null,c=!0):(d.material.color.copy(nm),c=!0),u.setRenderTarget(s),u.clear(),c&&u.render(d,n);for(let t=0;t<6;t++){const r=t%3;0===r?(n.up.set(0,a[t],0),n.position.set(i.x,i.y,i.z),n.lookAt(i.x+o[t],i.y,i.z)):1===r?(n.up.set(0,0,a[t]),n.position.set(i.x,i.y,i.z),n.lookAt(i.x,i.y+o[t],i.z)):(n.up.set(0,a[t],0),n.position.set(i.x,i.y,i.z),n.lookAt(i.x,i.y,i.z+o[t]));const l=this._cubeSize;xm(s,r*l,t>2?l:0,l,l),u.render(e,n)}u.autoClear=l,e.background=h}_textureToCubeUV(e,t){const r=this._renderer,s=e.mapping===R||e.mapping===C;s?null===this._cubemapMaterial&&(this._cubemapMaterial=_m(e)):null===this._equirectMaterial&&(this._equirectMaterial=vm(e));const i=s?this._cubemapMaterial:this._equirectMaterial;i.fragmentNode.value=e;const n=this._lodMeshes[0];n.material=i;const a=this._cubeSize;xm(t,0,0,3*a,2*a),r.setRenderTarget(t),r.render(n,sm)}_applyPMREM(e){const t=this._renderer,r=t.autoClear;t.autoClear=!1;const s=this._lodPlanes.length;for(let t=1;trm&&console.warn(`sigmaRadians, ${i}, is too large and will clip, as it requested ${g} samples when the maximum is set to 20`);const m=[];let f=0;for(let e=0;ey-4?s-y+4:0),4*(this._cubeSize-b),3*b,2*b),o.setRenderTarget(t),o.render(l,sm)}}function bm(e,t){const r=new ue(e,t,{magFilter:Y,minFilter:Y,generateMipmaps:!1,type:ce,format:de,colorSpace:le});return r.texture.mapping=he,r.texture.name="PMREM.cubeUv",r.texture.isPMREMTexture=!0,r.scissorTest=!0,r}function xm(e,t,r,s,i){e.viewport.set(t,r,s,i),e.scissor.set(t,r,s,i)}function Tm(e){const t=new lp;return t.depthTest=!1,t.depthWrite=!1,t.blending=H,t.name=`PMREM_${e}`,t}function _m(e){const t=Tm("cubemap");return t.fragmentNode=bd(e,fm),t}function vm(e){const t=Tm("equirect");return t.fragmentNode=Zu(e,vp(fm),0),t}const Nm=new WeakMap;function Sm(e,t,r){const s=function(e){let t=Nm.get(e);void 0===t&&(t=new WeakMap,Nm.set(e,t));return t}(t);let i=s.get(e);if((void 0!==i?i.pmremVersion:-1)!==e.pmremVersion){const t=e.image;if(e.isCubeTexture){if(!function(e){if(null==e)return!1;let t=0;const r=6;for(let s=0;s0}(t))return null;i=r.fromEquirectangular(e,i)}i.pmremVersion=e.pmremVersion,s.set(e,i)}return i.texture}class Em extends Ks{static get type(){return"PMREMNode"}constructor(e,t=null,r=null){super("vec3"),this._value=e,this._pmrem=null,this.uvNode=t,this.levelNode=r,this._generator=null;const s=new x;s.isRenderTargetTexture=!0,this._texture=Zu(s),this._width=Zn(0),this._height=Zn(0),this._maxMip=Zn(0),this.updateBeforeType=Vs.RENDER}set value(e){this._value=e,this._pmrem=null}get value(){return this._value}updateFromTexture(e){const t=function(e){const t=Math.log2(e)-2,r=1/e;return{texelWidth:1/(3*Math.max(Math.pow(2,t),112)),texelHeight:r,maxMip:t}}(e.image.height);this._texture.value=e,this._width.value=t.texelWidth,this._height.value=t.texelHeight,this._maxMip.value=t.maxMip}updateBefore(e){let t=this._pmrem;const r=t?t.pmremVersion:-1,s=this._value;r!==s.pmremVersion&&(t=!0===s.isPMREMTexture?s:Sm(s,e.renderer,this._generator),null!==t&&(this._pmrem=t,this.updateFromTexture(t)))}setup(e){null===this._generator&&(this._generator=new ym(e.renderer)),this.updateBefore(e);let t=this.uvNode;null===t&&e.context.getUV&&(t=e.context.getUV(this)),t=dd.mul(en(t.x,t.y.negate(),t.z));let r=this.levelNode;return null===r&&e.context.getTextureLevel&&(r=e.context.getTextureLevel(this)),Qg(this._texture,t,r,this._width,this._height,this._maxMip)}dispose(){super.dispose(),null!==this._generator&&this._generator.dispose()}}const wm=Vi(Em).setParameterLength(1,3),Am=new WeakMap;class Rm extends bh{static get type(){return"EnvironmentNode"}constructor(e=null){super(),this.envNode=e}setup(e){const{material:t}=e;let r=this.envNode;if(r.isTextureNode||r.isMaterialReferenceNode){const e=r.isTextureNode?r.value:t[r.property];let s=Am.get(e);void 0===s&&(s=wm(e),Am.set(e,s)),r=s}const s=!0===t.useAnisotropy||t.anisotropy>0?Yd:Zl,i=r.context(Cm(xn,s)).mul(ld),n=r.context(Mm(Jl)).mul(Math.PI).mul(ld),a=Cu(i),o=Cu(n);e.context.radiance.addAssign(a),e.context.iblIrradiance.addAssign(o);const u=e.context.lightingModel.clearcoatRadiance;if(u){const e=r.context(Cm(vn,ed)).mul(ld),t=Cu(e);u.addAssign(t)}}}const Cm=(e,t)=>{let r=null;return{getUV:()=>(null===r&&(r=zl.negate().reflect(t),r=e.mul(e).mix(r,t).normalize(),r=r.transformDirection(cl)),r),getTextureLevel:()=>e}},Mm=e=>({getUV:()=>e,getTextureLevel:()=>ji(1)}),Pm=new me;class Bm extends lp{static get type(){return"MeshStandardNodeMaterial"}constructor(e){super(),this.isMeshStandardNodeMaterial=!0,this.lights=!0,this.emissiveNode=null,this.metalnessNode=null,this.roughnessNode=null,this.setDefaultValues(Pm),this.setValues(e)}setupEnvironment(e){let t=super.setupEnvironment(e);return null===t&&e.environmentNode&&(t=e.environmentNode),t?new Rm(t):null}setupLightingModel(){return new Lg}setupSpecular(){const e=Lo(en(.04),yn.rgb,Tn);Bn.assign(e),Fn.assign(1)}setupVariants(){const e=this.metalnessNode?ji(this.metalnessNode):fc;Tn.assign(e);let t=this.roughnessNode?ji(this.roughnessNode):mc;t=Wp({roughness:t}),xn.assign(t),this.setupSpecular(),yn.assign(nn(yn.rgb.mul(e.oneMinus()),yn.a))}copy(e){return this.emissiveNode=e.emissiveNode,this.metalnessNode=e.metalnessNode,this.roughnessNode=e.roughnessNode,super.copy(e)}}const Fm=new fe;class Lm extends Bm{static get type(){return"MeshPhysicalNodeMaterial"}constructor(e){super(),this.isMeshPhysicalNodeMaterial=!0,this.clearcoatNode=null,this.clearcoatRoughnessNode=null,this.clearcoatNormalNode=null,this.sheenNode=null,this.sheenRoughnessNode=null,this.iridescenceNode=null,this.iridescenceIORNode=null,this.iridescenceThicknessNode=null,this.specularIntensityNode=null,this.specularColorNode=null,this.iorNode=null,this.transmissionNode=null,this.thicknessNode=null,this.attenuationDistanceNode=null,this.attenuationColorNode=null,this.dispersionNode=null,this.anisotropyNode=null,this.setDefaultValues(Fm),this.setValues(e)}get useClearcoat(){return this.clearcoat>0||null!==this.clearcoatNode}get useIridescence(){return this.iridescence>0||null!==this.iridescenceNode}get useSheen(){return this.sheen>0||null!==this.sheenNode}get useAnisotropy(){return this.anisotropy>0||null!==this.anisotropyNode}get useTransmission(){return this.transmission>0||null!==this.transmissionNode}get useDispersion(){return this.dispersion>0||null!==this.dispersionNode}setupSpecular(){const e=this.iorNode?ji(this.iorNode):Mc;On.assign(e),Bn.assign(Lo(xo(Ro(On.sub(1).div(On.add(1))).mul(hc),en(1)).mul(cc),yn.rgb,Tn)),Fn.assign(Lo(cc,1,Tn))}setupLightingModel(){return new Lg(this.useClearcoat,this.useSheen,this.useIridescence,this.useAnisotropy,this.useTransmission,this.useDispersion)}setupVariants(e){if(super.setupVariants(e),this.useClearcoat){const e=this.clearcoatNode?ji(this.clearcoatNode):bc,t=this.clearcoatRoughnessNode?ji(this.clearcoatRoughnessNode):xc;_n.assign(e),vn.assign(Wp({roughness:t}))}if(this.useSheen){const e=this.sheenNode?en(this.sheenNode):vc,t=this.sheenRoughnessNode?ji(this.sheenRoughnessNode):Nc;Nn.assign(e),Sn.assign(t)}if(this.useIridescence){const e=this.iridescenceNode?ji(this.iridescenceNode):Ec,t=this.iridescenceIORNode?ji(this.iridescenceIORNode):wc,r=this.iridescenceThicknessNode?ji(this.iridescenceThicknessNode):Ac;En.assign(e),wn.assign(t),An.assign(r)}if(this.useAnisotropy){const e=(this.anisotropyNode?Yi(this.anisotropyNode):Sc).toVar();Cn.assign(e.length()),Hi(Cn.equal(0),()=>{e.assign(Yi(1,0))}).Else(()=>{e.divAssign(Yi(Cn)),Cn.assign(Cn.saturate())}),Rn.assign(Cn.pow2().mix(xn.pow2(),1)),Mn.assign(Xd[0].mul(e.x).add(Xd[1].mul(e.y))),Pn.assign(Xd[1].mul(e.x).sub(Xd[0].mul(e.y)))}if(this.useTransmission){const e=this.transmissionNode?ji(this.transmissionNode):Rc,t=this.thicknessNode?ji(this.thicknessNode):Cc,r=this.attenuationDistanceNode?ji(this.attenuationDistanceNode):Pc,s=this.attenuationColorNode?en(this.attenuationColorNode):Bc;if(kn.assign(e),Gn.assign(t),zn.assign(r),Hn.assign(s),this.useDispersion){const e=this.dispersionNode?ji(this.dispersionNode):Oc;$n.assign(e)}}}setupClearcoatNormal(){return this.clearcoatNormalNode?en(this.clearcoatNormalNode):Tc}setup(e){e.context.setupClearcoatNormal=()=>iu(this.setupClearcoatNormal(e),"NORMAL","vec3"),super.setup(e)}copy(e){return this.clearcoatNode=e.clearcoatNode,this.clearcoatRoughnessNode=e.clearcoatRoughnessNode,this.clearcoatNormalNode=e.clearcoatNormalNode,this.sheenNode=e.sheenNode,this.sheenRoughnessNode=e.sheenRoughnessNode,this.iridescenceNode=e.iridescenceNode,this.iridescenceIORNode=e.iridescenceIORNode,this.iridescenceThicknessNode=e.iridescenceThicknessNode,this.specularIntensityNode=e.specularIntensityNode,this.specularColorNode=e.specularColorNode,this.transmissionNode=e.transmissionNode,this.thicknessNode=e.thicknessNode,this.attenuationDistanceNode=e.attenuationDistanceNode,this.attenuationColorNode=e.attenuationColorNode,this.dispersionNode=e.dispersionNode,this.anisotropyNode=e.anisotropyNode,super.copy(e)}}class Dm extends Lg{constructor(e=!1,t=!1,r=!1,s=!1,i=!1,n=!1,a=!1){super(e,t,r,s,i,n),this.useSSS=a}direct({lightDirection:e,lightColor:t,reflectedLight:r},s){if(!0===this.useSSS){const i=s.material,{thicknessColorNode:n,thicknessDistortionNode:a,thicknessAmbientNode:o,thicknessAttenuationNode:u,thicknessPowerNode:l,thicknessScaleNode:d}=i,c=e.add(Zl.mul(a)).normalize(),h=ji(zl.dot(c.negate()).saturate().pow(l).mul(d)),p=en(h.add(o).mul(n));r.directDiffuse.addAssign(p.mul(u.mul(t)))}super.direct({lightDirection:e,lightColor:t,reflectedLight:r},s)}}class Im extends Lm{static get type(){return"MeshSSSNodeMaterial"}constructor(e){super(e),this.thicknessColorNode=null,this.thicknessDistortionNode=ji(.1),this.thicknessAmbientNode=ji(0),this.thicknessAttenuationNode=ji(.1),this.thicknessPowerNode=ji(2),this.thicknessScaleNode=ji(10)}get useSSS(){return null!==this.thicknessColorNode}setupLightingModel(){return new Dm(this.useClearcoat,this.useSheen,this.useIridescence,this.useAnisotropy,this.useTransmission,this.useDispersion,this.useSSS)}copy(e){return this.thicknessColorNode=e.thicknessColorNode,this.thicknessDistortionNode=e.thicknessDistortionNode,this.thicknessAmbientNode=e.thicknessAmbientNode,this.thicknessAttenuationNode=e.thicknessAttenuationNode,this.thicknessPowerNode=e.thicknessPowerNode,this.thicknessScaleNode=e.thicknessScaleNode,super.copy(e)}}const Vm=ki(({normal:e,lightDirection:t,builder:r})=>{const s=e.dot(t),i=Yi(s.mul(.5).add(.5),0);if(r.material.gradientMap){const e=Sd("gradientMap","texture").context({getUV:()=>i});return en(e.r)}{const e=i.fwidth().mul(.5);return Lo(en(.7),en(1),Uo(ji(.7).sub(e.x),ji(.7).add(e.x),i.x))}});class Um extends Pp{direct({lightDirection:e,lightColor:t,reflectedLight:r},s){const i=Vm({normal:ql,lightDirection:e,builder:s}).mul(t);r.directDiffuse.addAssign(i.mul(Ip({diffuseColor:yn.rgb})))}indirect(e){const{ambientOcclusion:t,irradiance:r,reflectedLight:s}=e.context;s.indirectDiffuse.addAssign(r.mul(Ip({diffuseColor:yn}))),s.indirectDiffuse.mulAssign(t)}}const Om=new ye;class km extends lp{static get type(){return"MeshToonNodeMaterial"}constructor(e){super(),this.isMeshToonNodeMaterial=!0,this.lights=!0,this.setDefaultValues(Om),this.setValues(e)}setupLightingModel(){return new Um}}const Gm=ki(()=>{const e=en(zl.z,0,zl.x.negate()).normalize(),t=zl.cross(e);return Yi(e.dot(Zl),t.dot(Zl)).mul(.495).add(.5)}).once(["NORMAL","VERTEX"])().toVar("matcapUV"),zm=new be;class Hm extends lp{static get type(){return"MeshMatcapNodeMaterial"}constructor(e){super(),this.isMeshMatcapNodeMaterial=!0,this.setDefaultValues(zm),this.setValues(e)}setupVariants(e){const t=Gm;let r;r=e.material.matcap?Sd("matcap","texture").context({getUV:()=>t}):en(Lo(.2,.8,t.y)),yn.rgb.mulAssign(r.rgb)}}class $m extends Ks{static get type(){return"RotateNode"}constructor(e,t){super(),this.positionNode=e,this.rotationNode=t}getNodeType(e){return this.positionNode.getNodeType(e)}setup(e){const{rotationNode:t,positionNode:r}=this;if("vec2"===this.getNodeType(e)){const e=t.cos(),s=t.sin();return ln(e,s,s.negate(),e).mul(r)}{const e=t,s=cn(nn(1,0,0,0),nn(0,Ja(e.x),Za(e.x).negate(),0),nn(0,Za(e.x),Ja(e.x),0),nn(0,0,0,1)),i=cn(nn(Ja(e.y),0,Za(e.y),0),nn(0,1,0,0),nn(Za(e.y).negate(),0,Ja(e.y),0),nn(0,0,0,1)),n=cn(nn(Ja(e.z),Za(e.z).negate(),0,0),nn(Za(e.z),Ja(e.z),0,0),nn(0,0,1,0),nn(0,0,0,1));return s.mul(i).mul(n).mul(nn(r,1)).xyz}}}const Wm=Vi($m).setParameterLength(2),jm=new xe;class qm extends lp{static get type(){return"SpriteNodeMaterial"}constructor(e){super(),this.isSpriteNodeMaterial=!0,this._useSizeAttenuation=!0,this.positionNode=null,this.rotationNode=null,this.scaleNode=null,this.transparent=!0,this.setDefaultValues(jm),this.setValues(e)}setupPositionView(e){const{object:t,camera:r}=e,s=this.sizeAttenuation,{positionNode:i,rotationNode:n,scaleNode:a}=this,o=Bl.mul(en(i||0));let u=Yi(El[0].xyz.length(),El[1].xyz.length());if(null!==a&&(u=u.mul(Yi(a))),!1===s)if(r.isPerspectiveCamera)u=u.mul(o.z.negate());else{const e=ji(2).div(ll.element(1).element(1));u=u.mul(e.mul(2))}let l=Il.xy;if(t.center&&!0===t.center.isVector2){const e=((e,t,r)=>Li(new mu(e,t,r)))("center","vec2",t);l=l.sub(e.sub(.5))}l=l.mul(u);const d=ji(n||_c),c=Wm(l,d);return nn(o.xy.add(c),o.zw)}copy(e){return this.positionNode=e.positionNode,this.rotationNode=e.rotationNode,this.scaleNode=e.scaleNode,super.copy(e)}get sizeAttenuation(){return this._useSizeAttenuation}set sizeAttenuation(e){this._useSizeAttenuation!==e&&(this._useSizeAttenuation=e,this.needsUpdate=!0)}}const Xm=new Te;class Km extends qm{static get type(){return"PointsNodeMaterial"}constructor(e){super(),this.sizeNode=null,this.isPointsNodeMaterial=!0,this.setDefaultValues(Xm),this.setValues(e)}setupPositionView(){const{positionNode:e}=this;return Bl.mul(en(e||Vl)).xyz}setupVertex(e){const t=super.setupVertex(e);if(!0!==e.material.isNodeMaterial)return t;const{rotationNode:r,scaleNode:s,sizeNode:i}=this,n=Il.xy.toVar(),a=Ch.z.div(Ch.w);if(r&&r.isNode){const e=ji(r);n.assign(Wm(n,e))}let o=null!==i?Yi(i):Uc;return!0===this.sizeAttenuation&&(o=o.mul(o.div(Gl.z.negate()))),s&&s.isNode&&(o=o.mul(Yi(s))),n.mulAssign(o.mul(2)),n.assign(n.div(Ch.z)),n.y.assign(n.y.mul(a)),n.assign(n.mul(t.w)),t.addAssign(nn(n,0,0)),t}get alphaToCoverage(){return this._useAlphaToCoverage}set alphaToCoverage(e){this._useAlphaToCoverage!==e&&(this._useAlphaToCoverage=e,this.needsUpdate=!0)}}class Ym extends Pp{constructor(){super(),this.shadowNode=ji(1).toVar("shadowMask")}direct({lightNode:e}){null!==e.shadowNode&&this.shadowNode.mulAssign(e.shadowNode)}finish({context:e}){yn.a.mulAssign(this.shadowNode.oneMinus()),e.outgoingLight.rgb.assign(yn.rgb)}}const Qm=new _e;class Zm extends lp{static get type(){return"ShadowNodeMaterial"}constructor(e){super(),this.isShadowNodeMaterial=!0,this.lights=!0,this.transparent=!0,this.setDefaultValues(Qm),this.setValues(e)}setupLightingModel(){return new Ym}}const Jm=mn("vec3"),ef=mn("vec3"),tf=mn("vec3");class rf extends Pp{constructor(){super()}start(e){const{material:t,context:r}=e,s=mn("vec3"),i=mn("vec3");Hi(gl.sub(Ol).length().greaterThan(Cl.mul(2)),()=>{s.assign(gl),i.assign(Ol)}).Else(()=>{s.assign(Ol),i.assign(gl)});const n=i.sub(s),a=Zn("int").onRenderUpdate(({material:e})=>e.steps),o=n.length().div(a).toVar(),u=n.normalize().toVar(),l=ji(0).toVar(),d=en(1).toVar();t.offsetNode&&l.addAssign(t.offsetNode.mul(o)),ch(a,()=>{const i=s.add(u.mul(l)),n=cl.mul(nn(i,1)).xyz;let a;null!==t.depthNode&&(ef.assign(Xh(Hh(n.z,ol,ul))),r.sceneDepthNode=Xh(t.depthNode).toVar()),r.positionWorld=i,r.shadowPositionWorld=i,r.positionView=n,Jm.assign(0),t.scatteringNode&&(a=t.scatteringNode({positionRay:i})),super.start(e),a&&Jm.mulAssign(a);const c=Jm.mul(.01).negate().mul(o).exp();d.mulAssign(c),l.addAssign(o)}),tf.addAssign(d.saturate().oneMinus())}scatteringLight(e,t){const r=t.context.sceneDepthNode;r?Hi(r.greaterThanEqual(ef),()=>{Jm.addAssign(e)}):Jm.addAssign(e)}direct({lightNode:e,lightColor:t},r){if(void 0===e.light.distance)return;const s=t.xyz.toVar();s.mulAssign(e.shadowNode),this.scatteringLight(s,r)}directRectArea({lightColor:e,lightPosition:t,halfWidth:r,halfHeight:s},i){const n=t.add(r).sub(s),a=t.sub(r).sub(s),o=t.sub(r).add(s),u=t.add(r).add(s),l=i.context.positionView,d=e.xyz.mul(ug({P:l,p0:n,p1:a,p2:o,p3:u})).pow(1.5);this.scatteringLight(d,i)}finish(e){e.context.outgoingLight.assign(tf)}}class sf extends lp{static get type(){return"VolumeNodeMaterial"}constructor(e){super(),this.isVolumeNodeMaterial=!0,this.steps=25,this.offsetNode=null,this.scatteringNode=null,this.lights=!0,this.transparent=!0,this.side=S,this.depthTest=!1,this.depthWrite=!1,this.setValues(e)}setupLightingModel(){return new rf}}class nf{constructor(e,t){this.nodes=e,this.info=t,this._context="undefined"!=typeof self?self:null,this._animationLoop=null,this._requestId=null}start(){const e=(t,r)=>{this._requestId=this._context.requestAnimationFrame(e),!0===this.info.autoReset&&this.info.reset(),this.nodes.nodeFrame.update(),this.info.frame=this.nodes.nodeFrame.frameId,null!==this._animationLoop&&this._animationLoop(t,r)};e()}stop(){this._context.cancelAnimationFrame(this._requestId),this._requestId=null}getAnimationLoop(){return this._animationLoop}setAnimationLoop(e){this._animationLoop=e}getContext(){return this._context}setContext(e){this._context=e}dispose(){this.stop()}}class af{constructor(){this.weakMap=new WeakMap}get(e){let t=this.weakMap;for(let r=0;r{this.dispose()},this.onGeometryDispose=()=>{this.attributes=null,this.attributesId=null},this.material.addEventListener("dispose",this.onMaterialDispose),this.geometry.addEventListener("dispose",this.onGeometryDispose)}updateClipping(e){this.clippingContext=e}get clippingNeedsUpdate(){return null!==this.clippingContext&&this.clippingContext.cacheKey!==this.clippingContextCacheKey&&(this.clippingContextCacheKey=this.clippingContext.cacheKey,!0)}get hardwareClippingPlanes(){return!0===this.material.hardwareClipping?this.clippingContext.unionClippingCount:0}getNodeBuilderState(){return this._nodeBuilderState||(this._nodeBuilderState=this._nodes.getForRender(this))}getMonitor(){return this._monitor||(this._monitor=this.getNodeBuilderState().observer)}getBindings(){return this._bindings||(this._bindings=this.getNodeBuilderState().createBindings())}getBindingGroup(e){for(const t of this.getBindings())if(t.name===e)return t}getIndex(){return this._geometries.getIndex(this)}getIndirect(){return this._geometries.getIndirect(this)}getChainArray(){return[this.object,this.material,this.context,this.lightsNode]}setGeometry(e){this.geometry=e,this.attributes=null,this.attributesId=null}getAttributes(){if(null!==this.attributes)return this.attributes;const e=this.getNodeBuilderState().nodeAttributes,t=this.geometry,r=[],s=new Set,i={};for(const n of e){let e;if(n.node&&n.node.attribute?e=n.node.attribute:(e=t.getAttribute(n.name),i[n.name]=e.version),void 0===e)continue;r.push(e);const a=e.isInterleavedBufferAttribute?e.data:e;s.add(a)}return this.attributes=r,this.attributesId=i,this.vertexBuffers=Array.from(s.values()),r}getVertexBuffers(){return null===this.vertexBuffers&&this.getAttributes(),this.vertexBuffers}getDrawParameters(){const{object:e,material:t,geometry:r,group:s,drawRange:i}=this,n=this.drawParams||(this.drawParams={vertexCount:0,firstVertex:0,instanceCount:0,firstInstance:0}),a=this.getIndex(),o=null!==a;let u=1;if(!0===r.isInstancedBufferGeometry?u=r.instanceCount:void 0!==e.count&&(u=Math.max(0,e.count)),0===u)return null;if(n.instanceCount=u,!0===e.isBatchedMesh)return n;let l=1;!0!==t.wireframe||e.isPoints||e.isLineSegments||e.isLine||e.isLineLoop||(l=2);let d=i.start*l,c=(i.start+i.count)*l;null!==s&&(d=Math.max(d,s.start*l),c=Math.min(c,(s.start+s.count)*l));const h=r.attributes.position;let p=1/0;o?p=a.count:null!=h&&(p=h.count),d=Math.max(d,0),c=Math.min(c,p);const g=c-d;return g<0||g===1/0?null:(n.vertexCount=g,n.firstVertex=d,n)}getGeometryCacheKey(){const{geometry:e}=this;let t="";for(const r of Object.keys(e.attributes).sort()){const s=e.attributes[r];t+=r+",",s.data&&(t+=s.data.stride+","),s.offset&&(t+=s.offset+","),s.itemSize&&(t+=s.itemSize+","),s.normalized&&(t+="n,")}for(const r of Object.keys(e.morphAttributes).sort()){const s=e.morphAttributes[r];t+="morph-"+r+",";for(let e=0,r=s.length;e1&&(r+=e.uuid+","),r+=e.receiveShadow+",",bs(r)}get needsGeometryUpdate(){if(this.geometry.id!==this.object.geometry.id)return!0;if(null!==this.attributes){const e=this.attributesId;for(const t in e){const r=this.geometry.getAttribute(t);if(void 0===r||e[t]!==r.id)return!0}}return!1}get needsUpdate(){return this.initialNodesCacheKey!==this.getDynamicCacheKey()||this.clippingNeedsUpdate}getDynamicCacheKey(){let e=0;return!0!==this.material.isShadowPassMaterial&&(e=this._nodes.getCacheKey(this.scene,this.lightsNode)),this.camera.isArrayCamera&&(e=Ts(e,this.camera.cameras.length)),this.object.receiveShadow&&(e=Ts(e,1)),e}getCacheKey(){return this.getMaterialCacheKey()+this.getDynamicCacheKey()}dispose(){this.material.removeEventListener("dispose",this.onMaterialDispose),this.geometry.removeEventListener("dispose",this.onGeometryDispose),this.onDispose()}}const lf=[];class df{constructor(e,t,r,s,i,n){this.renderer=e,this.nodes=t,this.geometries=r,this.pipelines=s,this.bindings=i,this.info=n,this.chainMaps={}}get(e,t,r,s,i,n,a,o){const u=this.getChainMap(o);lf[0]=e,lf[1]=t,lf[2]=n,lf[3]=i;let l=u.get(lf);return void 0===l?(l=this.createRenderObject(this.nodes,this.geometries,this.renderer,e,t,r,s,i,n,a,o),u.set(lf,l)):(l.updateClipping(a),l.needsGeometryUpdate&&l.setGeometry(e.geometry),(l.version!==t.version||l.needsUpdate)&&(l.initialCacheKey!==l.getCacheKey()?(l.dispose(),l=this.get(e,t,r,s,i,n,a,o)):l.version=t.version)),lf.length=0,l}getChainMap(e="default"){return this.chainMaps[e]||(this.chainMaps[e]=new af)}dispose(){this.chainMaps={}}createRenderObject(e,t,r,s,i,n,a,o,u,l,d){const c=this.getChainMap(d),h=new uf(e,t,r,s,i,n,a,o,u,l);return h.onDispose=()=>{this.pipelines.delete(h),this.bindings.delete(h),this.nodes.delete(h),c.delete(h.getChainArray())},h}}class cf{constructor(){this.data=new WeakMap}get(e){let t=this.data.get(e);return void 0===t&&(t={},this.data.set(e,t)),t}delete(e){let t=null;return this.data.has(e)&&(t=this.data.get(e),this.data.delete(e)),t}has(e){return this.data.has(e)}dispose(){this.data=new WeakMap}}const hf=1,pf=2,gf=3,mf=4,ff=16;class yf extends cf{constructor(e){super(),this.backend=e}delete(e){const t=super.delete(e);return null!==t&&this.backend.destroyAttribute(e),t}update(e,t){const r=this.get(e);if(void 0===r.version)t===hf?this.backend.createAttribute(e):t===pf?this.backend.createIndexAttribute(e):t===gf?this.backend.createStorageAttribute(e):t===mf&&this.backend.createIndirectStorageAttribute(e),r.version=this._getBufferAttribute(e).version;else{const t=this._getBufferAttribute(e);(r.version{this.info.memory.geometries--;const s=t.index,i=e.getAttributes();null!==s&&this.attributes.delete(s);for(const e of i)this.attributes.delete(e);const n=this.wireframes.get(t);void 0!==n&&this.attributes.delete(n),t.removeEventListener("dispose",r)};t.addEventListener("dispose",r)}updateAttributes(e){const t=e.getAttributes();for(const e of t)e.isStorageBufferAttribute||e.isStorageInstancedBufferAttribute?this.updateAttribute(e,gf):this.updateAttribute(e,hf);const r=this.getIndex(e);null!==r&&this.updateAttribute(r,pf);const s=e.geometry.indirect;null!==s&&this.updateAttribute(s,mf)}updateAttribute(e,t){const r=this.info.render.calls;e.isInterleavedBufferAttribute?void 0===this.attributeCall.get(e)?(this.attributes.update(e,t),this.attributeCall.set(e,r)):this.attributeCall.get(e.data)!==r&&(this.attributes.update(e,t),this.attributeCall.set(e.data,r),this.attributeCall.set(e,r)):this.attributeCall.get(e)!==r&&(this.attributes.update(e,t),this.attributeCall.set(e,r))}getIndirect(e){return e.geometry.indirect}getIndex(e){const{geometry:t,material:r}=e;let s=t.index;if(!0===r.wireframe){const e=this.wireframes;let r=e.get(t);void 0===r?(r=xf(t),e.set(t,r)):r.version!==bf(t)&&(this.attributes.delete(r),r=xf(t),e.set(t,r)),s=r}return s}}class _f{constructor(){this.autoReset=!0,this.frame=0,this.calls=0,this.render={calls:0,frameCalls:0,drawCalls:0,triangles:0,points:0,lines:0,timestamp:0},this.compute={calls:0,frameCalls:0,timestamp:0},this.memory={geometries:0,textures:0}}update(e,t,r){this.render.drawCalls++,e.isMesh||e.isSprite?this.render.triangles+=r*(t/3):e.isPoints?this.render.points+=r*t:e.isLineSegments?this.render.lines+=r*(t/2):e.isLine?this.render.lines+=r*(t-1):console.error("THREE.WebGPUInfo: Unknown object type.")}reset(){this.render.drawCalls=0,this.render.frameCalls=0,this.compute.frameCalls=0,this.render.triangles=0,this.render.points=0,this.render.lines=0}dispose(){this.reset(),this.calls=0,this.render.calls=0,this.compute.calls=0,this.render.timestamp=0,this.compute.timestamp=0,this.memory.geometries=0,this.memory.textures=0}}class vf{constructor(e){this.cacheKey=e,this.usedTimes=0}}class Nf extends vf{constructor(e,t,r){super(e),this.vertexProgram=t,this.fragmentProgram=r}}class Sf extends vf{constructor(e,t){super(e),this.computeProgram=t,this.isComputePipeline=!0}}let Ef=0;class wf{constructor(e,t,r,s=null,i=null){this.id=Ef++,this.code=e,this.stage=t,this.name=r,this.transforms=s,this.attributes=i,this.usedTimes=0}}class Af extends cf{constructor(e,t){super(),this.backend=e,this.nodes=t,this.bindings=null,this.caches=new Map,this.programs={vertex:new Map,fragment:new Map,compute:new Map}}getForCompute(e,t){const{backend:r}=this,s=this.get(e);if(this._needsComputeUpdate(e)){const i=s.pipeline;i&&(i.usedTimes--,i.computeProgram.usedTimes--);const n=this.nodes.getForCompute(e);let a=this.programs.compute.get(n.computeShader);void 0===a&&(i&&0===i.computeProgram.usedTimes&&this._releaseProgram(i.computeProgram),a=new wf(n.computeShader,"compute",e.name,n.transforms,n.nodeAttributes),this.programs.compute.set(n.computeShader,a),r.createProgram(a));const o=this._getComputeCacheKey(e,a);let u=this.caches.get(o);void 0===u&&(i&&0===i.usedTimes&&this._releasePipeline(i),u=this._getComputePipeline(e,a,o,t)),u.usedTimes++,a.usedTimes++,s.version=e.version,s.pipeline=u}return s.pipeline}getForRender(e,t=null){const{backend:r}=this,s=this.get(e);if(this._needsRenderUpdate(e)){const i=s.pipeline;i&&(i.usedTimes--,i.vertexProgram.usedTimes--,i.fragmentProgram.usedTimes--);const n=e.getNodeBuilderState(),a=e.material?e.material.name:"";let o=this.programs.vertex.get(n.vertexShader);void 0===o&&(i&&0===i.vertexProgram.usedTimes&&this._releaseProgram(i.vertexProgram),o=new wf(n.vertexShader,"vertex",a),this.programs.vertex.set(n.vertexShader,o),r.createProgram(o));let u=this.programs.fragment.get(n.fragmentShader);void 0===u&&(i&&0===i.fragmentProgram.usedTimes&&this._releaseProgram(i.fragmentProgram),u=new wf(n.fragmentShader,"fragment",a),this.programs.fragment.set(n.fragmentShader,u),r.createProgram(u));const l=this._getRenderCacheKey(e,o,u);let d=this.caches.get(l);void 0===d?(i&&0===i.usedTimes&&this._releasePipeline(i),d=this._getRenderPipeline(e,o,u,l,t)):e.pipeline=d,d.usedTimes++,o.usedTimes++,u.usedTimes++,s.pipeline=d}return s.pipeline}delete(e){const t=this.get(e).pipeline;return t&&(t.usedTimes--,0===t.usedTimes&&this._releasePipeline(t),t.isComputePipeline?(t.computeProgram.usedTimes--,0===t.computeProgram.usedTimes&&this._releaseProgram(t.computeProgram)):(t.fragmentProgram.usedTimes--,t.vertexProgram.usedTimes--,0===t.vertexProgram.usedTimes&&this._releaseProgram(t.vertexProgram),0===t.fragmentProgram.usedTimes&&this._releaseProgram(t.fragmentProgram))),super.delete(e)}dispose(){super.dispose(),this.caches=new Map,this.programs={vertex:new Map,fragment:new Map,compute:new Map}}updateForRender(e){this.getForRender(e)}_getComputePipeline(e,t,r,s){r=r||this._getComputeCacheKey(e,t);let i=this.caches.get(r);return void 0===i&&(i=new Sf(r,t),this.caches.set(r,i),this.backend.createComputePipeline(i,s)),i}_getRenderPipeline(e,t,r,s,i){s=s||this._getRenderCacheKey(e,t,r);let n=this.caches.get(s);return void 0===n&&(n=new Nf(s,t,r),this.caches.set(s,n),e.pipeline=n,this.backend.createRenderPipeline(e,i)),n}_getComputeCacheKey(e,t){return e.id+","+t.id}_getRenderCacheKey(e,t,r){return t.id+","+r.id+","+this.backend.getRenderCacheKey(e)}_releasePipeline(e){this.caches.delete(e.cacheKey)}_releaseProgram(e){const t=e.code,r=e.stage;this.programs[r].delete(t)}_needsComputeUpdate(e){const t=this.get(e);return void 0===t.pipeline||t.version!==e.version}_needsRenderUpdate(e){return void 0===this.get(e).pipeline||this.backend.needsRenderUpdate(e)}}class Rf extends cf{constructor(e,t,r,s,i,n){super(),this.backend=e,this.textures=r,this.pipelines=i,this.attributes=s,this.nodes=t,this.info=n,this.pipelines.bindings=this}getForRender(e){const t=e.getBindings();for(const e of t){const r=this.get(e);void 0===r.bindGroup&&(this._init(e),this.backend.createBindings(e,t,0),r.bindGroup=e)}return t}getForCompute(e){const t=this.nodes.getForCompute(e).bindings;for(const e of t){const r=this.get(e);void 0===r.bindGroup&&(this._init(e),this.backend.createBindings(e,t,0),r.bindGroup=e)}return t}updateForCompute(e){this._updateBindings(this.getForCompute(e))}updateForRender(e){this._updateBindings(this.getForRender(e))}_updateBindings(e){for(const t of e)this._update(t,e)}_init(e){for(const t of e.bindings)if(t.isSampledTexture)this.textures.updateTexture(t.texture);else if(t.isStorageBuffer){const e=t.attribute,r=e.isIndirectStorageBufferAttribute?mf:gf;this.attributes.update(e,r)}}_update(e,t){const{backend:r}=this;let s=!1,i=!0,n=0,a=0;for(const t of e.bindings){if(t.isNodeUniformsGroup){if(!1===this.nodes.updateGroup(t))continue}if(t.isStorageBuffer){const e=t.attribute,r=e.isIndirectStorageBufferAttribute?mf:gf;this.attributes.update(e,r)}if(t.isUniformBuffer){t.update()&&r.updateBinding(t)}else if(t.isSampler)t.update();else if(t.isSampledTexture){const e=this.textures.get(t.texture);t.needsBindingsUpdate(e.generation)&&(s=!0);const o=t.update(),u=t.texture;o&&this.textures.updateTexture(u);const l=r.get(u);if(void 0!==l.externalTexture||e.isDefaultTexture?i=!1:(n=10*n+u.id,a+=u.version),!0===r.isWebGPUBackend&&void 0===l.texture&&void 0===l.externalTexture&&(console.error("Bindings._update: binding should be available:",t,o,u,t.textureNode.value,s),this.textures.updateTexture(u),s=!0),!0===u.isStorageTexture){const e=this.get(u);!0===t.store?e.needsMipmap=!0:this.textures.needsMipmaps(u)&&!0===e.needsMipmap&&(this.backend.generateMipmaps(u),e.needsMipmap=!1)}}}!0===s&&this.backend.updateBindings(e,t,i?n:0,a)}}function Cf(e,t){return e.groupOrder!==t.groupOrder?e.groupOrder-t.groupOrder:e.renderOrder!==t.renderOrder?e.renderOrder-t.renderOrder:e.z!==t.z?e.z-t.z:e.id-t.id}function Mf(e,t){return e.groupOrder!==t.groupOrder?e.groupOrder-t.groupOrder:e.renderOrder!==t.renderOrder?e.renderOrder-t.renderOrder:e.z!==t.z?t.z-e.z:e.id-t.id}function Pf(e){return(e.transmission>0||e.transmissionNode)&&e.side===E&&!1===e.forceSinglePass}class Bf{constructor(e,t,r){this.renderItems=[],this.renderItemsIndex=0,this.opaque=[],this.transparentDoublePass=[],this.transparent=[],this.bundles=[],this.lightsNode=e.getNode(t,r),this.lightsArray=[],this.scene=t,this.camera=r,this.occlusionQueryCount=0}begin(){return this.renderItemsIndex=0,this.opaque.length=0,this.transparentDoublePass.length=0,this.transparent.length=0,this.bundles.length=0,this.lightsArray.length=0,this.occlusionQueryCount=0,this}getNextRenderItem(e,t,r,s,i,n,a){let o=this.renderItems[this.renderItemsIndex];return void 0===o?(o={id:e.id,object:e,geometry:t,material:r,groupOrder:s,renderOrder:e.renderOrder,z:i,group:n,clippingContext:a},this.renderItems[this.renderItemsIndex]=o):(o.id=e.id,o.object=e,o.geometry=t,o.material=r,o.groupOrder=s,o.renderOrder=e.renderOrder,o.z=i,o.group=n,o.clippingContext=a),this.renderItemsIndex++,o}push(e,t,r,s,i,n,a){const o=this.getNextRenderItem(e,t,r,s,i,n,a);!0===e.occlusionTest&&this.occlusionQueryCount++,!0===r.transparent||r.transmission>0?(Pf(r)&&this.transparentDoublePass.push(o),this.transparent.push(o)):this.opaque.push(o)}unshift(e,t,r,s,i,n,a){const o=this.getNextRenderItem(e,t,r,s,i,n,a);!0===r.transparent||r.transmission>0?(Pf(r)&&this.transparentDoublePass.unshift(o),this.transparent.unshift(o)):this.opaque.unshift(o)}pushBundle(e){this.bundles.push(e)}pushLight(e){this.lightsArray.push(e)}sort(e,t){this.opaque.length>1&&this.opaque.sort(e||Cf),this.transparentDoublePass.length>1&&this.transparentDoublePass.sort(t||Mf),this.transparent.length>1&&this.transparent.sort(t||Mf)}finish(){this.lightsNode.setLights(this.lightsArray);for(let e=this.renderItemsIndex,t=this.renderItems.length;e>t,u=a.height>>t;let l=e.depthTexture||i[t];const d=!0===e.depthBuffer||!0===e.stencilBuffer;let c=!1;void 0===l&&d&&(l=new U,l.format=e.stencilBuffer?we:Ae,l.type=e.stencilBuffer?Re:T,l.image.width=o,l.image.height=u,l.image.depth=a.depth,l.isArrayTexture=!0===e.multiview&&a.depth>1,i[t]=l),r.width===a.width&&a.height===r.height||(c=!0,l&&(l.needsUpdate=!0,l.image.width=o,l.image.height=u,l.image.depth=l.isArrayTexture?l.image.depth:1)),r.width=a.width,r.height=a.height,r.textures=n,r.depthTexture=l||null,r.depth=e.depthBuffer,r.stencil=e.stencilBuffer,r.renderTarget=e,r.sampleCount!==s&&(c=!0,l&&(l.needsUpdate=!0),r.sampleCount=s);const h={sampleCount:s};if(!0!==e.isXRRenderTarget){for(let e=0;e{e.removeEventListener("dispose",t);for(let e=0;e0){const s=e.image;if(void 0===s)console.warn("THREE.Renderer: Texture marked for update but image is undefined.");else if(!1===s.complete)console.warn("THREE.Renderer: Texture marked for update but image is incomplete.");else{if(e.images){const r=[];for(const t of e.images)r.push(t);t.images=r}else t.image=s;void 0!==r.isDefaultTexture&&!0!==r.isDefaultTexture||(i.createTexture(e,t),r.isDefaultTexture=!1,r.generation=e.version),!0===e.source.dataReady&&i.updateTexture(e,t),t.needsMipmaps&&0===e.mipmaps.length&&i.generateMipmaps(e)}}else i.createDefaultTexture(e),r.isDefaultTexture=!0,r.generation=e.version}if(!0!==r.initialized){r.initialized=!0,r.generation=e.version,this.info.memory.textures++;const t=()=>{e.removeEventListener("dispose",t),this._destroyTexture(e)};e.addEventListener("dispose",t)}r.version=e.version}getSize(e,t=zf){let r=e.images?e.images[0]:e.image;return r?(void 0!==r.image&&(r=r.image),t.width=r.width||1,t.height=r.height||1,t.depth=e.isCubeTexture?6:r.depth||1):t.width=t.height=t.depth=1,t}getMipLevels(e,t,r){let s;return s=e.isCompressedTexture?e.mipmaps?e.mipmaps.length:1:Math.floor(Math.log2(Math.max(t,r)))+1,s}needsMipmaps(e){return!0===e.isCompressedTexture||e.generateMipmaps}_destroyTexture(e){!0===this.has(e)&&(this.backend.destroySampler(e),this.backend.destroyTexture(e),this.delete(e),this.info.memory.textures--)}}class $f extends e{constructor(e,t,r,s=1){super(e,t,r),this.a=s}set(e,t,r,s=1){return this.a=s,super.set(e,t,r)}copy(e){return void 0!==e.a&&(this.a=e.a),super.copy(e)}clone(){return new this.constructor(this.r,this.g,this.b,this.a)}}class Wf extends gn{static get type(){return"ParameterNode"}constructor(e,t=null){super(e,t),this.isParameterNode=!0}getHash(){return this.uuid}generate(){return this.name}}class jf extends js{static get type(){return"StackNode"}constructor(e=null){super(),this.nodes=[],this.outputNode=null,this.parent=e,this._currentCond=null,this._expressionNode=null,this.isStackNode=!0}getNodeType(e){return this.outputNode?this.outputNode.getNodeType(e):"void"}getMemberType(e,t){return this.outputNode?this.outputNode.getMemberType(e,t):"void"}add(e){return this.nodes.push(e),this}If(e,t){const r=new Fi(t);return this._currentCond=Xo(e,r),this.add(this._currentCond)}ElseIf(e,t){const r=new Fi(t),s=Xo(e,r);return this._currentCond.elseNode=s,this._currentCond=s,this}Else(e){return this._currentCond.elseNode=new Fi(e),this}Switch(e){return this._expressionNode=Li(e),this}Case(...e){const t=[];if(!(e.length>=2))throw new Error("TSL: Invalid parameter length. Case() requires at least two parameters.");for(let r=0;r"string"==typeof t?{name:e,type:t,atomic:!1}:{name:e,type:t.type,atomic:t.atomic||!1})),this.name=t,this.isStructLayoutNode=!0}getLength(){const e=Float32Array.BYTES_PER_ELEMENT;let t=0;for(const r of this.membersLayout){const s=r.type,i=Rs(s)*e,n=t%8,a=n%Cs(s),o=n+a;t+=a,0!==o&&8-oe.name===t);return r?r.type:"void"}getNodeType(e){return e.getStructTypeFromNode(this,this.membersLayout,this.name).name}setup(e){e.addInclude(this)}generate(e){return this.getNodeType(e)}}class Kf extends js{static get type(){return"StructNode"}constructor(e,t){super("vec3"),this.structLayoutNode=e,this.values=t,this.isStructNode=!0}getNodeType(e){return this.structLayoutNode.getNodeType(e)}getMemberType(e,t){return this.structLayoutNode.getMemberType(e,t)}generate(e){const t=e.getVarFromNode(this),r=t.type,s=e.getPropertyName(t);return e.addLineFlowCode(`${s} = ${e.generateStruct(r,this.structLayoutNode.membersLayout,this.values)}`,this),t.name}}class Yf extends js{static get type(){return"OutputStructNode"}constructor(...e){super(),this.members=e,this.isOutputStructNode=!0}getNodeType(e){const t=e.getNodeProperties(this);if(void 0===t.membersLayout){const r=this.members,s=[];for(let t=0;t{const t=e.toUint().mul(747796405).add(2891336453),r=t.shiftRight(t.shiftRight(28).add(4)).bitXor(t).mul(277803737);return r.shiftRight(22).bitXor(r).toFloat().mul(1/2**32)}),ry=(e,t)=>Ao(la(4,e.mul(ua(1,e))),t),sy=ki(([e])=>e.fract().sub(.5).abs()).setLayout({name:"tri",type:"float",inputs:[{name:"x",type:"float"}]}),iy=ki(([e])=>en(sy(e.z.add(sy(e.y.mul(1)))),sy(e.z.add(sy(e.x.mul(1)))),sy(e.y.add(sy(e.x.mul(1)))))).setLayout({name:"tri3",type:"vec3",inputs:[{name:"p",type:"vec3"}]}),ny=ki(([e,t,r])=>{const s=en(e).toVar(),i=ji(1.4).toVar(),n=ji(0).toVar(),a=en(s).toVar();return ch({start:ji(0),end:ji(3),type:"float",condition:"<="},()=>{const e=en(iy(a.mul(2))).toVar();s.addAssign(e.add(r.mul(ji(.1).mul(t)))),a.mulAssign(1.8),i.mulAssign(1.5),s.mulAssign(1.2);const o=ji(sy(s.z.add(sy(s.x.add(sy(s.y)))))).toVar();n.addAssign(o.div(i)),a.addAssign(.14)}),n}).setLayout({name:"triNoise3D",type:"float",inputs:[{name:"position",type:"vec3"},{name:"speed",type:"float"},{name:"time",type:"float"}]});class ay extends js{static get type(){return"FunctionOverloadingNode"}constructor(e=[],...t){super(),this.functionNodes=e,this.parametersNodes=t,this._candidateFnCall=null,this.global=!0}getNodeType(){return this.functionNodes[0].shaderNode.layout.type}setup(e){const t=this.parametersNodes;let r=this._candidateFnCall;if(null===r){let s=null,i=-1;for(const r of this.functionNodes){const n=r.shaderNode.layout;if(null===n)throw new Error("FunctionOverloadingNode: FunctionNode must be a layout.");const a=n.inputs;if(t.length===a.length){let n=0;for(let r=0;ri&&(s=r,i=n)}}this._candidateFnCall=r=s(...t)}return r}}const oy=Vi(ay),uy=e=>(...t)=>oy(e,...t),ly=Zn(0).setGroup(Kn).onRenderUpdate(e=>e.time),dy=Zn(0).setGroup(Kn).onRenderUpdate(e=>e.deltaTime),cy=Zn(0,"uint").setGroup(Kn).onRenderUpdate(e=>e.frameId),hy=ki(([e,t,r=Yi(.5)])=>Wm(e.sub(r),t).add(r)),py=ki(([e,t,r=Yi(.5)])=>{const s=e.sub(r),i=s.dot(s),n=i.mul(i).mul(t);return e.add(s.mul(n))}),gy=ki(({position:e=null,horizontal:t=!0,vertical:r=!1})=>{let s;null!==e?(s=El.toVar(),s[3][0]=e.x,s[3][1]=e.y,s[3][2]=e.z):s=El;const i=cl.mul(s);return Pi(t)&&(i[0][0]=El[0].length(),i[0][1]=0,i[0][2]=0),Pi(r)&&(i[1][0]=0,i[1][1]=El[1].length(),i[1][2]=0),i[2][0]=0,i[2][1]=0,i[2][2]=1,ll.mul(i).mul(Vl)}),my=ki(([e=null])=>{const t=Xh();return Xh(kh(e)).sub(t).lessThan(0).select(wh,e)});class fy extends js{static get type(){return"SpriteSheetUVNode"}constructor(e,t=$u(),r=ji(0)){super("vec2"),this.countNode=e,this.uvNode=t,this.frameNode=r}setup(){const{frameNode:e,uvNode:t,countNode:r}=this,{width:s,height:i}=r,n=e.mod(s.mul(i)).floor(),a=n.mod(s),o=i.sub(n.add(1).div(s).ceil()),u=r.reciprocal(),l=Yi(a,o);return t.add(l).mul(u)}}const yy=Vi(fy).setParameterLength(3),by=ki(([e,t=null,r=null,s=ji(1),i=Vl,n=Xl])=>{let a=n.abs().normalize();a=a.div(a.dot(en(1)));const o=i.yz.mul(s),u=i.zx.mul(s),l=i.xy.mul(s),d=e.value,c=null!==t?t.value:d,h=null!==r?r.value:d,p=Zu(d,o).mul(a.x),g=Zu(c,u).mul(a.y),m=Zu(h,l).mul(a.z);return oa(p,g,m)}),xy=new Me,Ty=new r,_y=new r,vy=new r,Ny=new a,Sy=new r(0,0,-1),Ey=new s,wy=new r,Ay=new r,Ry=new s,Cy=new t,My=new ue,Py=wh.flipX();My.depthTexture=new U(1,1);let By=!1;class Fy extends Yu{static get type(){return"ReflectorNode"}constructor(e={}){super(e.defaultTexture||My.texture,Py),this._reflectorBaseNode=e.reflector||new Ly(this,e),this._depthNode=null,this.setUpdateMatrix(!1)}get reflector(){return this._reflectorBaseNode}get target(){return this._reflectorBaseNode.target}getDepthNode(){if(null===this._depthNode){if(!0!==this._reflectorBaseNode.depth)throw new Error("THREE.ReflectorNode: Depth node can only be requested when the reflector is created with { depth: true }. ");this._depthNode=Li(new Fy({defaultTexture:My.depthTexture,reflector:this._reflectorBaseNode}))}return this._depthNode}setup(e){return e.object.isQuadMesh||this._reflectorBaseNode.build(e),super.setup(e)}clone(){const e=new this.constructor(this.reflectorNode);return e.uvNode=this.uvNode,e.levelNode=this.levelNode,e.biasNode=this.biasNode,e.sampler=this.sampler,e.depthNode=this.depthNode,e.compareNode=this.compareNode,e.gradNode=this.gradNode,e._reflectorBaseNode=this._reflectorBaseNode,e}dispose(){super.dispose(),this._reflectorBaseNode.dispose()}}class Ly extends js{static get type(){return"ReflectorBaseNode"}constructor(e,t={}){super();const{target:r=new Pe,resolution:s=1,generateMipmaps:i=!1,bounces:n=!0,depth:a=!1}=t;this.textureNode=e,this.target=r,this.resolution=s,this.generateMipmaps=i,this.bounces=n,this.depth=a,this.updateBeforeType=n?Vs.RENDER:Vs.FRAME,this.virtualCameras=new WeakMap,this.renderTargets=new Map,this.forceUpdate=!1,this.hasOutput=!1}_updateResolution(e,t){const r=this.resolution;t.getDrawingBufferSize(Cy),e.setSize(Math.round(Cy.width*r),Math.round(Cy.height*r))}setup(e){return this._updateResolution(My,e.renderer),super.setup(e)}dispose(){super.dispose();for(const e of this.renderTargets.values())e.dispose()}getVirtualCamera(e){let t=this.virtualCameras.get(e);return void 0===t&&(t=e.clone(),this.virtualCameras.set(e,t)),t}getRenderTarget(e){let t=this.renderTargets.get(e);return void 0===t&&(t=new ue(0,0,{type:ce}),!0===this.generateMipmaps&&(t.texture.minFilter=Be,t.texture.generateMipmaps=!0),!0===this.depth&&(t.depthTexture=new U),this.renderTargets.set(e,t)),t}updateBefore(e){if(!1===this.bounces&&By)return!1;By=!0;const{scene:t,camera:r,renderer:s,material:i}=e,{target:n}=this,a=this.getVirtualCamera(r),o=this.getRenderTarget(a);s.getDrawingBufferSize(Cy),this._updateResolution(o,s),_y.setFromMatrixPosition(n.matrixWorld),vy.setFromMatrixPosition(r.matrixWorld),Ny.extractRotation(n.matrixWorld),Ty.set(0,0,1),Ty.applyMatrix4(Ny),wy.subVectors(_y,vy);let u=!1;if(!0===wy.dot(Ty)>0&&!1===this.forceUpdate){if(!1===this.hasOutput)return void(By=!1);u=!0}wy.reflect(Ty).negate(),wy.add(_y),Ny.extractRotation(r.matrixWorld),Sy.set(0,0,-1),Sy.applyMatrix4(Ny),Sy.add(vy),Ay.subVectors(_y,Sy),Ay.reflect(Ty).negate(),Ay.add(_y),a.coordinateSystem=r.coordinateSystem,a.position.copy(wy),a.up.set(0,1,0),a.up.applyMatrix4(Ny),a.up.reflect(Ty),a.lookAt(Ay),a.near=r.near,a.far=r.far,a.updateMatrixWorld(),a.projectionMatrix.copy(r.projectionMatrix),xy.setFromNormalAndCoplanarPoint(Ty,_y),xy.applyMatrix4(a.matrixWorldInverse),Ey.set(xy.normal.x,xy.normal.y,xy.normal.z,xy.constant);const l=a.projectionMatrix;Ry.x=(Math.sign(Ey.x)+l.elements[8])/l.elements[0],Ry.y=(Math.sign(Ey.y)+l.elements[9])/l.elements[5],Ry.z=-1,Ry.w=(1+l.elements[10])/l.elements[14],Ey.multiplyScalar(1/Ey.dot(Ry));l.elements[2]=Ey.x,l.elements[6]=Ey.y,l.elements[10]=s.coordinateSystem===d?Ey.z-0:Ey.z+1-0,l.elements[14]=Ey.w,this.textureNode.value=o.texture,!0===this.depth&&(this.textureNode.getDepthNode().value=o.depthTexture),i.visible=!1;const c=s.getRenderTarget(),h=s.getMRT(),p=s.autoClear;s.setMRT(null),s.setRenderTarget(o),s.autoClear=!0,u?(s.clear(),this.hasOutput=!1):(s.render(t,a),this.hasOutput=!0),s.setMRT(h),s.setRenderTarget(c),s.autoClear=p,i.visible=!0,By=!1,this.forceUpdate=!1}}const Dy=new ae(-1,1,1,-1,0,1);class Iy extends pe{constructor(e=!1){super();const t=!1===e?[0,-1,0,1,2,1]:[0,2,0,0,2,0];this.setAttribute("position",new Fe([-1,3,0,-1,-1,0,3,-1,0],3)),this.setAttribute("uv",new Fe(t,2))}}const Vy=new Iy;class Uy extends X{constructor(e=null){super(Vy,e),this.camera=Dy,this.isQuadMesh=!0}async renderAsync(e){return e.renderAsync(this,Dy)}render(e){e.render(this,Dy)}}const Oy=new t;class ky extends Yu{static get type(){return"RTTNode"}constructor(e,t=null,r=null,s={type:ce}){const i=new ue(t,r,s);super(i.texture,$u()),this.node=e,this.width=t,this.height=r,this.pixelRatio=1,this.renderTarget=i,this.textureNeedsUpdate=!0,this.autoUpdate=!0,this._rttNode=null,this._quadMesh=new Uy(new lp),this.updateBeforeType=Vs.RENDER}get autoResize(){return null===this.width}setup(e){return this._rttNode=this.node.context(e.getSharedContext()),this._quadMesh.material.name="RTT",this._quadMesh.material.needsUpdate=!0,super.setup(e)}setSize(e,t){this.width=e,this.height=t;const r=e*this.pixelRatio,s=t*this.pixelRatio;this.renderTarget.setSize(r,s),this.textureNeedsUpdate=!0}setPixelRatio(e){this.pixelRatio=e,this.setSize(this.width,this.height)}updateBefore({renderer:e}){if(!1===this.textureNeedsUpdate&&!1===this.autoUpdate)return;if(this.textureNeedsUpdate=!1,!0===this.autoResize){const t=e.getPixelRatio(),r=e.getSize(Oy),s=r.width*t,i=r.height*t;s===this.renderTarget.width&&i===this.renderTarget.height||(this.renderTarget.setSize(s,i),this.textureNeedsUpdate=!0)}this._quadMesh.material.fragmentNode=this._rttNode;const t=e.getRenderTarget();e.setRenderTarget(this.renderTarget),this._quadMesh.render(e),e.setRenderTarget(t)}clone(){const e=new Yu(this.value,this.uvNode,this.levelNode);return e.sampler=this.sampler,e.referenceNode=this,e}}const Gy=(e,...t)=>Li(new ky(Li(e),...t)),zy=ki(([e,t,r],s)=>{let i;s.renderer.coordinateSystem===d?(e=Yi(e.x,e.y.oneMinus()).mul(2).sub(1),i=nn(en(e,t),1)):i=nn(en(e.x,e.y.oneMinus(),t).mul(2).sub(1),1);const n=nn(r.mul(i));return n.xyz.div(n.w)}),Hy=ki(([e,t])=>{const r=t.mul(nn(e,1)),s=r.xy.div(r.w).mul(.5).add(.5).toVar();return Yi(s.x,s.y.oneMinus())}),$y=ki(([e,t,r])=>{const s=ju(Ju(t)),i=Qi(e.mul(s)).toVar(),n=Ju(t,i).toVar(),a=Ju(t,i.sub(Qi(2,0))).toVar(),o=Ju(t,i.sub(Qi(1,0))).toVar(),u=Ju(t,i.add(Qi(1,0))).toVar(),l=Ju(t,i.add(Qi(2,0))).toVar(),d=Ju(t,i.add(Qi(0,2))).toVar(),c=Ju(t,i.add(Qi(0,1))).toVar(),h=Ju(t,i.sub(Qi(0,1))).toVar(),p=Ju(t,i.sub(Qi(0,2))).toVar(),g=io(ua(ji(2).mul(o).sub(a),n)).toVar(),m=io(ua(ji(2).mul(u).sub(l),n)).toVar(),f=io(ua(ji(2).mul(c).sub(d),n)).toVar(),y=io(ua(ji(2).mul(h).sub(p),n)).toVar(),b=zy(e,n,r).toVar(),x=g.lessThan(m).select(b.sub(zy(e.sub(Yi(ji(1).div(s.x),0)),o,r)),b.negate().add(zy(e.add(Yi(ji(1).div(s.x),0)),u,r))),T=f.lessThan(y).select(b.sub(zy(e.add(Yi(0,ji(1).div(s.y))),c,r)),b.negate().add(zy(e.sub(Yi(0,ji(1).div(s.y))),h,r)));return Ya(wo(x,T))});class Wy extends js{static get type(){return"SampleNode"}constructor(e){super(),this.callback=e,this.isSampleNode=!0}setup(){return this.sample($u())}sample(e){return this.callback(e)}}class jy extends F{constructor(e,t,r=Float32Array){super(ArrayBuffer.isView(e)?e:new r(e*t),t),this.isStorageInstancedBufferAttribute=!0}}class qy extends ge{constructor(e,t,r=Float32Array){super(ArrayBuffer.isView(e)?e:new r(e*t),t),this.isStorageBufferAttribute=!0}}class Xy extends js{static get type(){return"PointUVNode"}constructor(){super("vec2"),this.isPointUVNode=!0}generate(){return"vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y )"}}const Ky=Ui(Xy),Yy=new w,Qy=new a;class Zy extends js{static get type(){return"SceneNode"}constructor(e=Zy.BACKGROUND_BLURRINESS,t=null){super(),this.scope=e,this.scene=t}setup(e){const t=this.scope,r=null!==this.scene?this.scene:e.scene;let s;return t===Zy.BACKGROUND_BLURRINESS?s=_d("backgroundBlurriness","float",r):t===Zy.BACKGROUND_INTENSITY?s=_d("backgroundIntensity","float",r):t===Zy.BACKGROUND_ROTATION?s=Zn("mat4").label("backgroundRotation").setGroup(Kn).onRenderUpdate(()=>{const e=r.background;return null!==e&&e.isTexture&&e.mapping!==Le?(Yy.copy(r.backgroundRotation),Yy.x*=-1,Yy.y*=-1,Yy.z*=-1,Qy.makeRotationFromEuler(Yy)):Qy.identity(),Qy}):console.error("THREE.SceneNode: Unknown scope:",t),s}}Zy.BACKGROUND_BLURRINESS="backgroundBlurriness",Zy.BACKGROUND_INTENSITY="backgroundIntensity",Zy.BACKGROUND_ROTATION="backgroundRotation";const Jy=Ui(Zy,Zy.BACKGROUND_BLURRINESS),eb=Ui(Zy,Zy.BACKGROUND_INTENSITY),tb=Ui(Zy,Zy.BACKGROUND_ROTATION);class rb extends Yu{static get type(){return"StorageTextureNode"}constructor(e,t,r=null){super(e,t),this.storeNode=r,this.isStorageTextureNode=!0,this.access=Os.WRITE_ONLY}getInputType(){return"storageTexture"}setup(e){super.setup(e);const t=e.getNodeProperties(this);return t.storeNode=this.storeNode,t}setAccess(e){return this.access=e,this}generate(e,t){let r;return r=null!==this.storeNode?this.generateStore(e):super.generate(e,t),r}toReadWrite(){return this.setAccess(Os.READ_WRITE)}toReadOnly(){return this.setAccess(Os.READ_ONLY)}toWriteOnly(){return this.setAccess(Os.WRITE_ONLY)}generateStore(e){const t=e.getNodeProperties(this),{uvNode:r,storeNode:s,depthNode:i}=t,n=super.generate(e,"property"),a=r.build(e,"uvec2"),o=s.build(e,"vec4"),u=i?i.build(e,"int"):null,l=e.generateTextureStore(e,n,a,u,o);e.addLineFlowCode(l,this)}clone(){const e=super.clone();return e.storeNode=this.storeNode,e}}const sb=Vi(rb).setParameterLength(1,3),ib=ki(({texture:e,uv:t})=>{const r=1e-4,s=en().toVar();return Hi(t.x.lessThan(r),()=>{s.assign(en(1,0,0))}).ElseIf(t.y.lessThan(r),()=>{s.assign(en(0,1,0))}).ElseIf(t.z.lessThan(r),()=>{s.assign(en(0,0,1))}).ElseIf(t.x.greaterThan(.9999),()=>{s.assign(en(-1,0,0))}).ElseIf(t.y.greaterThan(.9999),()=>{s.assign(en(0,-1,0))}).ElseIf(t.z.greaterThan(.9999),()=>{s.assign(en(0,0,-1))}).Else(()=>{const r=.01,i=e.sample(t.add(en(-.01,0,0))).r.sub(e.sample(t.add(en(r,0,0))).r),n=e.sample(t.add(en(0,-.01,0))).r.sub(e.sample(t.add(en(0,r,0))).r),a=e.sample(t.add(en(0,0,-.01))).r.sub(e.sample(t.add(en(0,0,r))).r);s.assign(en(i,n,a))}),s.normalize()});class nb extends Yu{static get type(){return"Texture3DNode"}constructor(e,t=null,r=null){super(e,t,r),this.isTexture3DNode=!0}getInputType(){return"texture3D"}getDefaultUV(){return en(.5,.5,.5)}setUpdateMatrix(){}setupUV(e,t){const r=this.value;return!e.isFlipY()||!0!==r.isRenderTargetTexture&&!0!==r.isFramebufferTexture||(t=this.sampler?t.flipY():t.setY(qi(ju(this,this.levelNode).y).sub(t.y).sub(1))),t}generateUV(e,t){return t.build(e,"vec3")}normal(e){return ib({texture:this,uv:e})}}const ab=Vi(nb).setParameterLength(1,3);class ob extends Td{static get type(){return"UserDataNode"}constructor(e,t,r=null){super(e,t,r),this.userData=r}updateReference(e){return this.reference=null!==this.userData?this.userData:e.object.userData,this.reference}}const ub=new WeakMap;class lb extends Ks{static get type(){return"VelocityNode"}constructor(){super("vec2"),this.projectionMatrix=null,this.updateType=Vs.OBJECT,this.updateAfterType=Vs.OBJECT,this.previousModelWorldMatrix=Zn(new a),this.previousProjectionMatrix=Zn(new a).setGroup(Kn),this.previousCameraViewMatrix=Zn(new a)}setProjectionMatrix(e){this.projectionMatrix=e}update({frameId:e,camera:t,object:r}){const s=cb(r);this.previousModelWorldMatrix.value.copy(s);const i=db(t);i.frameId!==e&&(i.frameId=e,void 0===i.previousProjectionMatrix?(i.previousProjectionMatrix=new a,i.previousCameraViewMatrix=new a,i.currentProjectionMatrix=new a,i.currentCameraViewMatrix=new a,i.previousProjectionMatrix.copy(this.projectionMatrix||t.projectionMatrix),i.previousCameraViewMatrix.copy(t.matrixWorldInverse)):(i.previousProjectionMatrix.copy(i.currentProjectionMatrix),i.previousCameraViewMatrix.copy(i.currentCameraViewMatrix)),i.currentProjectionMatrix.copy(this.projectionMatrix||t.projectionMatrix),i.currentCameraViewMatrix.copy(t.matrixWorldInverse),this.previousProjectionMatrix.value.copy(i.previousProjectionMatrix),this.previousCameraViewMatrix.value.copy(i.previousCameraViewMatrix))}updateAfter({object:e}){cb(e).copy(e.matrixWorld)}setup(){const e=null===this.projectionMatrix?ll:Zn(this.projectionMatrix),t=this.previousCameraViewMatrix.mul(this.previousModelWorldMatrix),r=e.mul(Bl).mul(Vl),s=this.previousProjectionMatrix.mul(t).mul(Ul),i=r.xy.div(r.w),n=s.xy.div(s.w);return ua(i,n)}}function db(e){let t=ub.get(e);return void 0===t&&(t={},ub.set(e,t)),t}function cb(e,t=0){const r=db(e);let s=r[t];return void 0===s&&(r[t]=s=new a,r[t].copy(e.matrixWorld)),s}const hb=Ui(lb),pb=ki(([e])=>yb(e.rgb)),gb=ki(([e,t=ji(1)])=>t.mix(yb(e.rgb),e.rgb)),mb=ki(([e,t=ji(1)])=>{const r=oa(e.r,e.g,e.b).div(3),s=e.r.max(e.g.max(e.b)),i=s.sub(r).mul(t).mul(-3);return Lo(e.rgb,s,i)}),fb=ki(([e,t=ji(1)])=>{const r=en(.57735,.57735,.57735),s=t.cos();return en(e.rgb.mul(s).add(r.cross(e.rgb).mul(t.sin()).add(r.mul(Eo(r,e.rgb).mul(s.oneMinus())))))}),yb=(e,t=en(c.getLuminanceCoefficients(new r)))=>Eo(e,t),bb=ki(([e,t=en(1),s=en(0),i=en(1),n=ji(1),a=en(c.getLuminanceCoefficients(new r,le))])=>{const o=e.rgb.dot(en(a)),u=To(e.rgb.mul(t).add(s),0).toVar(),l=u.pow(i).toVar();return Hi(u.r.greaterThan(0),()=>{u.r.assign(l.r)}),Hi(u.g.greaterThan(0),()=>{u.g.assign(l.g)}),Hi(u.b.greaterThan(0),()=>{u.b.assign(l.b)}),u.assign(o.add(u.sub(o).mul(n))),nn(u.rgb,e.a)});class xb extends Ks{static get type(){return"PosterizeNode"}constructor(e,t){super(),this.sourceNode=e,this.stepsNode=t}setup(){const{sourceNode:e,stepsNode:t}=this;return e.mul(t).floor().div(t)}}const Tb=Vi(xb).setParameterLength(2),_b=new t;class vb extends Yu{static get type(){return"PassTextureNode"}constructor(e,t){super(t),this.passNode=e,this.setUpdateMatrix(!1)}setup(e){return e.object.isQuadMesh&&this.passNode.build(e),super.setup(e)}clone(){return new this.constructor(this.passNode,this.value)}}class Nb extends vb{static get type(){return"PassMultipleTextureNode"}constructor(e,t,r=!1){super(e,null),this.textureName=t,this.previousTexture=r}updateTexture(){this.value=this.previousTexture?this.passNode.getPreviousTexture(this.textureName):this.passNode.getTexture(this.textureName)}setup(e){return this.updateTexture(),super.setup(e)}clone(){const e=new this.constructor(this.passNode,this.textureName,this.previousTexture);return e.uvNode=this.uvNode,e.levelNode=this.levelNode,e.biasNode=this.biasNode,e.sampler=this.sampler,e.depthNode=this.depthNode,e.compareNode=this.compareNode,e.gradNode=this.gradNode,e}}class Sb extends Ks{static get type(){return"PassNode"}constructor(e,t,r,s={}){super("vec4"),this.scope=e,this.scene=t,this.camera=r,this.options=s,this._pixelRatio=1,this._width=1,this._height=1;const i=new U;i.isRenderTargetTexture=!0,i.name="depth";const n=new ue(this._width*this._pixelRatio,this._height*this._pixelRatio,{type:ce,...s});n.texture.name="output",n.depthTexture=i,this.renderTarget=n,this._textures={output:n.texture,depth:i},this._textureNodes={},this._linearDepthNodes={},this._viewZNodes={},this._previousTextures={},this._previousTextureNodes={},this._cameraNear=Zn(0),this._cameraFar=Zn(0),this._mrt=null,this._layers=null,this._resolution=1,this.isPassNode=!0,this.updateBeforeType=Vs.FRAME,this.global=!0}setResolution(e){return this._resolution=e,this}getResolution(){return this._resolution}setLayers(e){return this._layers=e,this}getLayers(){return this._layers}setMRT(e){return this._mrt=e,this}getMRT(){return this._mrt}getTexture(e){let t=this._textures[e];if(void 0===t){t=this.renderTarget.texture.clone(),t.name=e,this._textures[e]=t,this.renderTarget.textures.push(t)}return t}getPreviousTexture(e){let t=this._previousTextures[e];return void 0===t&&(t=this.getTexture(e).clone(),this._previousTextures[e]=t),t}toggleTexture(e){const t=this._previousTextures[e];if(void 0!==t){const r=this._textures[e],s=this.renderTarget.textures.indexOf(r);this.renderTarget.textures[s]=t,this._textures[e]=t,this._previousTextures[e]=r,this._textureNodes[e].updateTexture(),this._previousTextureNodes[e].updateTexture()}}getTextureNode(e="output"){let t=this._textureNodes[e];return void 0===t&&(t=Li(new Nb(this,e)),t.updateTexture(),this._textureNodes[e]=t),t}getPreviousTextureNode(e="output"){let t=this._previousTextureNodes[e];return void 0===t&&(void 0===this._textureNodes[e]&&this.getTextureNode(e),t=Li(new Nb(this,e,!0)),t.updateTexture(),this._previousTextureNodes[e]=t),t}getViewZNode(e="depth"){let t=this._viewZNodes[e];if(void 0===t){const r=this._cameraNear,s=this._cameraFar;this._viewZNodes[e]=t=$h(this.getTextureNode(e),r,s)}return t}getLinearDepthNode(e="depth"){let t=this._linearDepthNodes[e];if(void 0===t){const r=this._cameraNear,s=this._cameraFar,i=this.getViewZNode(e);this._linearDepthNodes[e]=t=zh(i,r,s)}return t}setup({renderer:e}){return this.renderTarget.samples=void 0===this.options.samples?e.samples:this.options.samples,this.renderTarget.texture.type=e.getColorBufferType(),this.scope===Sb.COLOR?this.getTextureNode():this.getLinearDepthNode()}updateBefore(e){const{renderer:t}=e,{scene:r}=this;let s,i;const n=t.getOutputRenderTarget();n&&!0===n.isXRRenderTarget?(i=1,s=t.xr.getCamera(),t.xr.updateCamera(s),_b.set(n.width,n.height)):(s=this.camera,i=t.getPixelRatio(),t.getSize(_b)),this._pixelRatio=i,this.setSize(_b.width,_b.height);const a=t.getRenderTarget(),o=t.getMRT(),u=s.layers.mask;this._cameraNear.value=s.near,this._cameraFar.value=s.far,null!==this._layers&&(s.layers.mask=this._layers.mask);for(const e in this._previousTextures)this.toggleTexture(e);t.setRenderTarget(this.renderTarget),t.setMRT(this._mrt),t.render(r,s),t.setRenderTarget(a),t.setMRT(o),s.layers.mask=u}setSize(e,t){this._width=e,this._height=t;const r=this._width*this._pixelRatio*this._resolution,s=this._height*this._pixelRatio*this._resolution;this.renderTarget.setSize(r,s)}setPixelRatio(e){this._pixelRatio=e,this.setSize(this._width,this._height)}dispose(){this.renderTarget.dispose()}}Sb.COLOR="color",Sb.DEPTH="depth";class Eb extends Sb{static get type(){return"ToonOutlinePassNode"}constructor(e,t,r,s,i){super(Sb.COLOR,e,t),this.colorNode=r,this.thicknessNode=s,this.alphaNode=i,this._materialCache=new WeakMap}updateBefore(e){const{renderer:t}=e,r=t.getRenderObjectFunction();t.setRenderObjectFunction((e,r,s,i,n,a,o,u)=>{if((n.isMeshToonMaterial||n.isMeshToonNodeMaterial)&&!1===n.wireframe){const l=this._getOutlineMaterial(n);t.renderObject(e,r,s,i,l,a,o,u)}t.renderObject(e,r,s,i,n,a,o,u)}),super.updateBefore(e),t.setRenderObjectFunction(r)}_createMaterial(){const e=new lp;e.isMeshToonOutlineMaterial=!0,e.name="Toon_Outline",e.side=S;const t=Xl.negate(),r=ll.mul(Bl),s=ji(1),i=r.mul(nn(Vl,1)),n=r.mul(nn(Vl.add(t),1)),a=Ya(i.sub(n));return e.vertexNode=i.add(a.mul(this.thicknessNode).mul(i.w).mul(s)),e.colorNode=nn(this.colorNode,this.alphaNode),e}_getOutlineMaterial(e){let t=this._materialCache.get(e);return void 0===t&&(t=this._createMaterial(),this._materialCache.set(e,t)),t}}const wb=ki(([e,t])=>e.mul(t).clamp()).setLayout({name:"linearToneMapping",type:"vec3",inputs:[{name:"color",type:"vec3"},{name:"exposure",type:"float"}]}),Ab=ki(([e,t])=>(e=e.mul(t)).div(e.add(1)).clamp()).setLayout({name:"reinhardToneMapping",type:"vec3",inputs:[{name:"color",type:"vec3"},{name:"exposure",type:"float"}]}),Rb=ki(([e,t])=>{const r=(e=(e=e.mul(t)).sub(.004).max(0)).mul(e.mul(6.2).add(.5)),s=e.mul(e.mul(6.2).add(1.7)).add(.06);return r.div(s).pow(2.2)}).setLayout({name:"cineonToneMapping",type:"vec3",inputs:[{name:"color",type:"vec3"},{name:"exposure",type:"float"}]}),Cb=ki(([e])=>{const t=e.mul(e.add(.0245786)).sub(90537e-9),r=e.mul(e.add(.432951).mul(.983729)).add(.238081);return t.div(r)}),Mb=ki(([e,t])=>{const r=dn(.59719,.35458,.04823,.076,.90834,.01566,.0284,.13383,.83777),s=dn(1.60475,-.53108,-.07367,-.10208,1.10813,-.00605,-.00327,-.07276,1.07602);return e=e.mul(t).div(.6),e=r.mul(e),e=Cb(e),(e=s.mul(e)).clamp()}).setLayout({name:"acesFilmicToneMapping",type:"vec3",inputs:[{name:"color",type:"vec3"},{name:"exposure",type:"float"}]}),Pb=dn(en(1.6605,-.1246,-.0182),en(-.5876,1.1329,-.1006),en(-.0728,-.0083,1.1187)),Bb=dn(en(.6274,.0691,.0164),en(.3293,.9195,.088),en(.0433,.0113,.8956)),Fb=ki(([e])=>{const t=en(e).toVar(),r=en(t.mul(t)).toVar(),s=en(r.mul(r)).toVar();return ji(15.5).mul(s.mul(r)).sub(la(40.14,s.mul(t))).add(la(31.96,s).sub(la(6.868,r.mul(t))).add(la(.4298,r).add(la(.1191,t).sub(.00232))))}),Lb=ki(([e,t])=>{const r=en(e).toVar(),s=dn(en(.856627153315983,.137318972929847,.11189821299995),en(.0951212405381588,.761241990602591,.0767994186031903),en(.0482516061458583,.101439036467562,.811302368396859)),i=dn(en(1.1271005818144368,-.1413297634984383,-.14132976349843826),en(-.11060664309660323,1.157823702216272,-.11060664309660294),en(-.016493938717834573,-.016493938717834257,1.2519364065950405)),n=ji(-12.47393),a=ji(4.026069);return r.mulAssign(t),r.assign(Bb.mul(r)),r.assign(s.mul(r)),r.assign(To(r,1e-10)),r.assign(Wa(r)),r.assign(r.sub(n).div(a.sub(n))),r.assign(Do(r,0,1)),r.assign(Fb(r)),r.assign(i.mul(r)),r.assign(Ao(To(en(0),r),en(2.2))),r.assign(Pb.mul(r)),r.assign(Do(r,0,1)),r}).setLayout({name:"agxToneMapping",type:"vec3",inputs:[{name:"color",type:"vec3"},{name:"exposure",type:"float"}]}),Db=ki(([e,t])=>{const r=ji(.76),s=ji(.15);e=e.mul(t);const i=xo(e.r,xo(e.g,e.b)),n=Xo(i.lessThan(.08),i.sub(la(6.25,i.mul(i))),.04);e.subAssign(n);const a=To(e.r,To(e.g,e.b));Hi(a.lessThan(r),()=>e);const o=ua(1,r),u=ua(1,o.mul(o).div(a.add(o.sub(r))));e.mulAssign(u.div(a));const l=ua(1,da(1,s.mul(a.sub(u)).add(1)));return Lo(e,en(u),l)}).setLayout({name:"neutralToneMapping",type:"vec3",inputs:[{name:"color",type:"vec3"},{name:"exposure",type:"float"}]});class Ib extends js{static get type(){return"CodeNode"}constructor(e="",t=[],r=""){super("code"),this.isCodeNode=!0,this.global=!0,this.code=e,this.includes=t,this.language=r}setIncludes(e){return this.includes=e,this}getIncludes(){return this.includes}generate(e){const t=this.getIncludes(e);for(const r of t)r.build(e);const r=e.getCodeFromNode(this,this.getNodeType(e));return r.code=this.code,r.code}serialize(e){super.serialize(e),e.code=this.code,e.language=this.language}deserialize(e){super.deserialize(e),this.code=e.code,this.language=e.language}}const Vb=Vi(Ib).setParameterLength(1,3);class Ub extends Ib{static get type(){return"FunctionNode"}constructor(e="",t=[],r=""){super(e,t,r)}getNodeType(e){return this.getNodeFunction(e).type}getInputs(e){return this.getNodeFunction(e).inputs}getNodeFunction(e){const t=e.getDataFromNode(this);let r=t.nodeFunction;return void 0===r&&(r=e.parser.parseFunction(this.code),t.nodeFunction=r),r}generate(e,t){super.generate(e);const r=this.getNodeFunction(e),s=r.name,i=r.type,n=e.getCodeFromNode(this,i);""!==s&&(n.name=s);const a=e.getPropertyName(n),o=this.getNodeFunction(e).getCode(a);return n.code=o+"\n","property"===t?a:e.format(`${a}()`,i,t)}}const Ob=(e,t=[],r="")=>{for(let e=0;es.call(...e);return i.functionNode=s,i};class kb extends js{static get type(){return"ScriptableValueNode"}constructor(e=null){super(),this._value=e,this._cache=null,this.inputType=null,this.outputType=null,this.events=new o,this.isScriptableValueNode=!0}get isScriptableOutputNode(){return null!==this.outputType}set value(e){this._value!==e&&(this._cache&&"URL"===this.inputType&&this.value.value instanceof ArrayBuffer&&(URL.revokeObjectURL(this._cache),this._cache=null),this._value=e,this.events.dispatchEvent({type:"change"}),this.refresh())}get value(){return this._value}refresh(){this.events.dispatchEvent({type:"refresh"})}getValue(){const e=this.value;if(e&&null===this._cache&&"URL"===this.inputType&&e.value instanceof ArrayBuffer)this._cache=URL.createObjectURL(new Blob([e.value]));else if(e&&null!==e.value&&void 0!==e.value&&(("URL"===this.inputType||"String"===this.inputType)&&"string"==typeof e.value||"Number"===this.inputType&&"number"==typeof e.value||"Vector2"===this.inputType&&e.value.isVector2||"Vector3"===this.inputType&&e.value.isVector3||"Vector4"===this.inputType&&e.value.isVector4||"Color"===this.inputType&&e.value.isColor||"Matrix3"===this.inputType&&e.value.isMatrix3||"Matrix4"===this.inputType&&e.value.isMatrix4))return e.value;return this._cache||e}getNodeType(e){return this.value&&this.value.isNode?this.value.getNodeType(e):"float"}setup(){return this.value&&this.value.isNode?this.value:ji()}serialize(e){super.serialize(e),null!==this.value?"ArrayBuffer"===this.inputType?e.value=Fs(this.value):e.value=this.value?this.value.toJSON(e.meta).uuid:null:e.value=null,e.inputType=this.inputType,e.outputType=this.outputType}deserialize(e){super.deserialize(e);let t=null;null!==e.value&&(t="ArrayBuffer"===e.inputType?Ls(e.value):"Texture"===e.inputType?e.meta.textures[e.value]:e.meta.nodes[e.value]||null),this.value=t,this.inputType=e.inputType,this.outputType=e.outputType}}const Gb=Vi(kb).setParameterLength(1);class zb extends Map{get(e,t=null,...r){if(this.has(e))return super.get(e);if(null!==t){const s=t(...r);return this.set(e,s),s}}}class Hb{constructor(e){this.scriptableNode=e}get parameters(){return this.scriptableNode.parameters}get layout(){return this.scriptableNode.getLayout()}getInputLayout(e){return this.scriptableNode.getInputLayout(e)}get(e){const t=this.parameters[e];return t?t.getValue():null}}const $b=new zb;class Wb extends js{static get type(){return"ScriptableNode"}constructor(e=null,t={}){super(),this.codeNode=e,this.parameters=t,this._local=new zb,this._output=Gb(null),this._outputs={},this._source=this.source,this._method=null,this._object=null,this._value=null,this._needsOutputUpdate=!0,this.onRefresh=this.onRefresh.bind(this),this.isScriptableNode=!0}get source(){return this.codeNode?this.codeNode.code:""}setLocal(e,t){return this._local.set(e,t)}getLocal(e){return this._local.get(e)}onRefresh(){this._refresh()}getInputLayout(e){for(const t of this.getLayout())if(t.inputType&&(t.id===e||t.name===e))return t}getOutputLayout(e){for(const t of this.getLayout())if(t.outputType&&(t.id===e||t.name===e))return t}setOutput(e,t){const r=this._outputs;return void 0===r[e]?r[e]=Gb(t):r[e].value=t,this}getOutput(e){return this._outputs[e]}getParameter(e){return this.parameters[e]}setParameter(e,t){const r=this.parameters;return t&&t.isScriptableNode?(this.deleteParameter(e),r[e]=t,r[e].getDefaultOutput().events.addEventListener("refresh",this.onRefresh)):t&&t.isScriptableValueNode?(this.deleteParameter(e),r[e]=t,r[e].events.addEventListener("refresh",this.onRefresh)):void 0===r[e]?(r[e]=Gb(t),r[e].events.addEventListener("refresh",this.onRefresh)):r[e].value=t,this}getValue(){return this.getDefaultOutput().getValue()}deleteParameter(e){let t=this.parameters[e];return t&&(t.isScriptableNode&&(t=t.getDefaultOutput()),t.events.removeEventListener("refresh",this.onRefresh)),this}clearParameters(){for(const e of Object.keys(this.parameters))this.deleteParameter(e);return this.needsUpdate=!0,this}call(e,...t){const r=this.getObject()[e];if("function"==typeof r)return r(...t)}async callAsync(e,...t){const r=this.getObject()[e];if("function"==typeof r)return"AsyncFunction"===r.constructor.name?await r(...t):r(...t)}getNodeType(e){return this.getDefaultOutputNode().getNodeType(e)}refresh(e=null){null!==e?this.getOutput(e).refresh():this._refresh()}getObject(){if(this.needsUpdate&&this.dispose(),null!==this._object)return this._object;const e=new Hb(this),t=$b.get("THREE"),r=$b.get("TSL"),s=this.getMethod(),i=[e,this._local,$b,()=>this.refresh(),(e,t)=>this.setOutput(e,t),t,r];this._object=s(...i);const n=this._object.layout;if(n&&(!1===n.cache&&this._local.clear(),this._output.outputType=n.outputType||null,Array.isArray(n.elements)))for(const e of n.elements){const t=e.id||e.name;e.inputType&&(void 0===this.getParameter(t)&&this.setParameter(t,null),this.getParameter(t).inputType=e.inputType),e.outputType&&(void 0===this.getOutput(t)&&this.setOutput(t,null),this.getOutput(t).outputType=e.outputType)}return this._object}deserialize(e){super.deserialize(e);for(const e in this.parameters){let t=this.parameters[e];t.isScriptableNode&&(t=t.getDefaultOutput()),t.events.addEventListener("refresh",this.onRefresh)}}getLayout(){return this.getObject().layout}getDefaultOutputNode(){const e=this.getDefaultOutput().value;return e&&e.isNode?e:ji()}getDefaultOutput(){return this._exec()._output}getMethod(){if(this.needsUpdate&&this.dispose(),null!==this._method)return this._method;const e=["layout","init","main","dispose"].join(", "),t="\nreturn { ...output, "+e+" };",r="var "+e+"; var output = {};\n"+this.codeNode.code+t;return this._method=new Function(...["parameters","local","global","refresh","setOutput","THREE","TSL"],r),this._method}dispose(){null!==this._method&&(this._object&&"function"==typeof this._object.dispose&&this._object.dispose(),this._method=null,this._object=null,this._source=null,this._value=null,this._needsOutputUpdate=!0,this._output.value=null,this._outputs={})}setup(){return this.getDefaultOutputNode()}getCacheKey(e){const t=[bs(this.source),this.getDefaultOutputNode().getCacheKey(e)];for(const r in this.parameters)t.push(this.parameters[r].getCacheKey(e));return xs(t)}set needsUpdate(e){!0===e&&this.dispose()}get needsUpdate(){return this.source!==this._source}_exec(){return null===this.codeNode||(!0===this._needsOutputUpdate&&(this._value=this.call("main"),this._needsOutputUpdate=!1),this._output.value=this._value),this}_refresh(){this.needsUpdate=!0,this._exec(),this._output.refresh()}}const jb=Vi(Wb).setParameterLength(1,2);function qb(e){let t;const r=e.context.getViewZ;return void 0!==r&&(t=r(this)),(t||Gl.z).negate()}const Xb=ki(([e,t],r)=>{const s=qb(r);return Uo(e,t,s)}),Kb=ki(([e],t)=>{const r=qb(t);return e.mul(e,r,r).negate().exp().oneMinus()}),Yb=ki(([e,t])=>nn(t.toFloat().mix(Dn.rgb,e.toVec3()),Dn.a));let Qb=null,Zb=null;class Jb extends js{static get type(){return"RangeNode"}constructor(e=ji(),t=ji()){super(),this.minNode=e,this.maxNode=t}getVectorLength(e){const t=e.getTypeLength(Ms(this.minNode.value)),r=e.getTypeLength(Ms(this.maxNode.value));return t>r?t:r}getNodeType(e){return e.object.count>1?e.getTypeFromLength(this.getVectorLength(e)):"float"}setup(e){const t=e.object;let r=null;if(t.count>1){const i=this.minNode.value,n=this.maxNode.value,a=e.getTypeLength(Ms(i)),o=e.getTypeLength(Ms(n));Qb=Qb||new s,Zb=Zb||new s,Qb.setScalar(0),Zb.setScalar(0),1===a?Qb.setScalar(i):i.isColor?Qb.set(i.r,i.g,i.b,1):Qb.set(i.x,i.y,i.z||0,i.w||0),1===o?Zb.setScalar(n):n.isColor?Zb.set(n.r,n.g,n.b,1):Zb.set(n.x,n.y,n.z||0,n.w||0);const l=4,d=l*t.count,c=new Float32Array(d);for(let e=0;eLi(new tx(e,t)),sx=rx("numWorkgroups","uvec3"),ix=rx("workgroupId","uvec3"),nx=rx("globalId","uvec3"),ax=rx("localId","uvec3"),ox=rx("subgroupSize","uint");const ux=Vi(class extends js{constructor(e){super(),this.scope=e}generate(e){const{scope:t}=this,{renderer:r}=e;!0===r.backend.isWebGLBackend?e.addFlowCode(`\t// ${t}Barrier \n`):e.addLineFlowCode(`${t}Barrier()`,this)}});class lx extends qs{constructor(e,t){super(e,t),this.isWorkgroupInfoElementNode=!0}generate(e,t){let r;const s=e.context.assign;if(r=super.generate(e),!0!==s){const s=this.getNodeType(e);r=e.format(r,s,t)}return r}}class dx extends js{constructor(e,t,r=0){super(t),this.bufferType=t,this.bufferCount=r,this.isWorkgroupInfoNode=!0,this.elementType=t,this.scope=e}label(e){return this.name=e,this}setScope(e){return this.scope=e,this}getElementType(){return this.elementType}getInputType(){return`${this.scope}Array`}element(e){return Li(new lx(this,e))}generate(e){return e.getScopedArray(this.name||`${this.scope}Array_${this.id}`,this.scope.toLowerCase(),this.bufferType,this.bufferCount)}}class cx extends js{static get type(){return"AtomicFunctionNode"}constructor(e,t,r){super("uint"),this.method=e,this.pointerNode=t,this.valueNode=r,this.parents=!0}getInputType(e){return this.pointerNode.getNodeType(e)}getNodeType(e){return this.getInputType(e)}generate(e){const t=e.getNodeProperties(this),r=t.parents,s=this.method,i=this.getNodeType(e),n=this.getInputType(e),a=this.pointerNode,o=this.valueNode,u=[];u.push(`&${a.build(e,n)}`),null!==o&&u.push(o.build(e,n));const l=`${e.getMethod(s,i)}( ${u.join(", ")} )`;if(!(1===r.length&&!0===r[0].isStackNode))return void 0===t.constNode&&(t.constNode=Iu(l,i).toConst()),t.constNode.build(e);e.addLineFlowCode(l,this)}}cx.ATOMIC_LOAD="atomicLoad",cx.ATOMIC_STORE="atomicStore",cx.ATOMIC_ADD="atomicAdd",cx.ATOMIC_SUB="atomicSub",cx.ATOMIC_MAX="atomicMax",cx.ATOMIC_MIN="atomicMin",cx.ATOMIC_AND="atomicAnd",cx.ATOMIC_OR="atomicOr",cx.ATOMIC_XOR="atomicXor";const hx=Vi(cx),px=(e,t,r)=>hx(e,t,r).toStack();let gx;function mx(e){gx=gx||new WeakMap;let t=gx.get(e);return void 0===t&&gx.set(e,t={}),t}function fx(e){const t=mx(e);return t.shadowMatrix||(t.shadowMatrix=Zn("mat4").setGroup(Kn).onRenderUpdate(t=>(!0===e.castShadow&&!1!==t.renderer.shadowMap.enabled||e.shadow.updateMatrices(e),e.shadow.matrix)))}function yx(e,t=Ol){const r=fx(e).mul(t);return r.xyz.div(r.w)}function bx(e){const t=mx(e);return t.position||(t.position=Zn(new r).setGroup(Kn).onRenderUpdate((t,r)=>r.value.setFromMatrixPosition(e.matrixWorld)))}function xx(e){const t=mx(e);return t.targetPosition||(t.targetPosition=Zn(new r).setGroup(Kn).onRenderUpdate((t,r)=>r.value.setFromMatrixPosition(e.target.matrixWorld)))}function Tx(e){const t=mx(e);return t.viewPosition||(t.viewPosition=Zn(new r).setGroup(Kn).onRenderUpdate(({camera:t},s)=>{s.value=s.value||new r,s.value.setFromMatrixPosition(e.matrixWorld),s.value.applyMatrix4(t.matrixWorldInverse)}))}const _x=e=>cl.transformDirection(bx(e).sub(xx(e))),vx=(e,t)=>{for(const r of t)if(r.isAnalyticLightNode&&r.light.id===e)return r;return null},Nx=new WeakMap,Sx=[];class Ex extends js{static get type(){return"LightsNode"}constructor(){super("vec3"),this.totalDiffuseNode=mn("vec3","totalDiffuse"),this.totalSpecularNode=mn("vec3","totalSpecular"),this.outgoingLightNode=mn("vec3","outgoingLight"),this._lights=[],this._lightNodes=null,this._lightNodesHash=null,this.global=!0}customCacheKey(){const e=this._lights;for(let t=0;te.sort((e,t)=>e.id-t.id))(this._lights),i=e.renderer.library;for(const e of s)if(e.isNode)t.push(Li(e));else{let s=null;if(null!==r&&(s=vx(e.id,r)),null===s){const r=i.getLightNodeClass(e.constructor);if(null===r){console.warn(`LightsNode.setupNodeLights: Light node not found for ${e.constructor.name}`);continue}let s=null;Nx.has(e)?s=Nx.get(e):(s=Li(new r(e)),Nx.set(e,s)),t.push(s)}}this._lightNodes=t}setupDirectLight(e,t,r){const{lightingModel:s,reflectedLight:i}=e.context;s.direct({...r,lightNode:t,reflectedLight:i},e)}setupDirectRectAreaLight(e,t,r){const{lightingModel:s,reflectedLight:i}=e.context;s.directRectArea({...r,lightNode:t,reflectedLight:i},e)}setupLights(e,t){for(const r of t)r.build(e)}getLightNodes(e){return null===this._lightNodes&&this.setupLightsNode(e),this._lightNodes}setup(e){const t=e.lightsNode;e.lightsNode=this;let r=this.outgoingLightNode;const s=e.context,i=s.lightingModel,n=e.getNodeProperties(this);if(i){const{totalDiffuseNode:t,totalSpecularNode:a}=this;s.outgoingLight=r;const o=e.addStack();n.nodes=o.nodes,i.start(e);const{backdrop:u,backdropAlpha:l}=s,{directDiffuse:d,directSpecular:c,indirectDiffuse:h,indirectSpecular:p}=s.reflectedLight;let g=d.add(h);null!==u&&(g=en(null!==l?l.mix(g,u):u),s.material.transparent=!0),t.assign(g),a.assign(c.add(p)),r.assign(t.add(a)),i.finish(e),r=r.bypass(e.removeStack())}else n.nodes=[];return e.lightsNode=t,r}setLights(e){return this._lights=e,this._lightNodes=null,this._lightNodesHash=null,this}getLights(){return this._lights}get hasLights(){return this._lights.length>0}}class wx extends js{static get type(){return"ShadowBaseNode"}constructor(e){super(),this.light=e,this.updateBeforeType=Vs.RENDER,this.isShadowBaseNode=!0}setupShadowPosition({context:e,material:t}){Ax.assign(t.receivedShadowPositionNode||e.shadowPositionWorld||Ol)}}const Ax=mn("vec3","shadowPositionWorld");function Rx(t,r={}){return r.toneMapping=t.toneMapping,r.toneMappingExposure=t.toneMappingExposure,r.outputColorSpace=t.outputColorSpace,r.renderTarget=t.getRenderTarget(),r.activeCubeFace=t.getActiveCubeFace(),r.activeMipmapLevel=t.getActiveMipmapLevel(),r.renderObjectFunction=t.getRenderObjectFunction(),r.pixelRatio=t.getPixelRatio(),r.mrt=t.getMRT(),r.clearColor=t.getClearColor(r.clearColor||new e),r.clearAlpha=t.getClearAlpha(),r.autoClear=t.autoClear,r.scissorTest=t.getScissorTest(),r}function Cx(e,t){return t=Rx(e,t),e.setMRT(null),e.setRenderObjectFunction(null),e.setClearColor(0,1),e.autoClear=!0,t}function Mx(e,t){e.toneMapping=t.toneMapping,e.toneMappingExposure=t.toneMappingExposure,e.outputColorSpace=t.outputColorSpace,e.setRenderTarget(t.renderTarget,t.activeCubeFace,t.activeMipmapLevel),e.setRenderObjectFunction(t.renderObjectFunction),e.setPixelRatio(t.pixelRatio),e.setMRT(t.mrt),e.setClearColor(t.clearColor,t.clearAlpha),e.autoClear=t.autoClear,e.setScissorTest(t.scissorTest)}function Px(e,t={}){return t.background=e.background,t.backgroundNode=e.backgroundNode,t.overrideMaterial=e.overrideMaterial,t}function Bx(e,t){return t=Px(e,t),e.background=null,e.backgroundNode=null,e.overrideMaterial=null,t}function Fx(e,t){e.background=t.background,e.backgroundNode=t.backgroundNode,e.overrideMaterial=t.overrideMaterial}function Lx(e,t,r){return r=Bx(t,r=Cx(e,r))}function Dx(e,t,r){Mx(e,r),Fx(t,r)}var Ix=Object.freeze({__proto__:null,resetRendererAndSceneState:Lx,resetRendererState:Cx,resetSceneState:Bx,restoreRendererAndSceneState:Dx,restoreRendererState:Mx,restoreSceneState:Fx,saveRendererAndSceneState:function(e,t,r={}){return r=Px(t,r=Rx(e,r))},saveRendererState:Rx,saveSceneState:Px});const Vx=new WeakMap,Ux=ki(({depthTexture:e,shadowCoord:t,depthLayer:r})=>{let s=Zu(e,t.xy).label("t_basic");return e.isArrayTexture&&(s=s.depth(r)),s.compare(t.z)}),Ox=ki(({depthTexture:e,shadowCoord:t,shadow:r,depthLayer:s})=>{const i=(t,r)=>{let i=Zu(e,t);return e.isArrayTexture&&(i=i.depth(s)),i.compare(r)},n=_d("mapSize","vec2",r).setGroup(Kn),a=_d("radius","float",r).setGroup(Kn),o=Yi(1).div(n),u=o.x.negate().mul(a),l=o.y.negate().mul(a),d=o.x.mul(a),c=o.y.mul(a),h=u.div(2),p=l.div(2),g=d.div(2),m=c.div(2);return oa(i(t.xy.add(Yi(u,l)),t.z),i(t.xy.add(Yi(0,l)),t.z),i(t.xy.add(Yi(d,l)),t.z),i(t.xy.add(Yi(h,p)),t.z),i(t.xy.add(Yi(0,p)),t.z),i(t.xy.add(Yi(g,p)),t.z),i(t.xy.add(Yi(u,0)),t.z),i(t.xy.add(Yi(h,0)),t.z),i(t.xy,t.z),i(t.xy.add(Yi(g,0)),t.z),i(t.xy.add(Yi(d,0)),t.z),i(t.xy.add(Yi(h,m)),t.z),i(t.xy.add(Yi(0,m)),t.z),i(t.xy.add(Yi(g,m)),t.z),i(t.xy.add(Yi(u,c)),t.z),i(t.xy.add(Yi(0,c)),t.z),i(t.xy.add(Yi(d,c)),t.z)).mul(1/17)}),kx=ki(({depthTexture:e,shadowCoord:t,shadow:r,depthLayer:s})=>{const i=(t,r)=>{let i=Zu(e,t);return e.isArrayTexture&&(i=i.depth(s)),i.compare(r)},n=_d("mapSize","vec2",r).setGroup(Kn),a=Yi(1).div(n),o=a.x,u=a.y,l=t.xy,d=Qa(l.mul(n).add(.5));return l.subAssign(d.mul(a)),oa(i(l,t.z),i(l.add(Yi(o,0)),t.z),i(l.add(Yi(0,u)),t.z),i(l.add(a),t.z),Lo(i(l.add(Yi(o.negate(),0)),t.z),i(l.add(Yi(o.mul(2),0)),t.z),d.x),Lo(i(l.add(Yi(o.negate(),u)),t.z),i(l.add(Yi(o.mul(2),u)),t.z),d.x),Lo(i(l.add(Yi(0,u.negate())),t.z),i(l.add(Yi(0,u.mul(2))),t.z),d.y),Lo(i(l.add(Yi(o,u.negate())),t.z),i(l.add(Yi(o,u.mul(2))),t.z),d.y),Lo(Lo(i(l.add(Yi(o.negate(),u.negate())),t.z),i(l.add(Yi(o.mul(2),u.negate())),t.z),d.x),Lo(i(l.add(Yi(o.negate(),u.mul(2))),t.z),i(l.add(Yi(o.mul(2),u.mul(2))),t.z),d.x),d.y)).mul(1/9)}),Gx=ki(({depthTexture:e,shadowCoord:t,depthLayer:r})=>{const s=ji(1).toVar();let i=Zu(e).sample(t.xy);e.isArrayTexture&&(i=i.depth(r)),i=i.rg;const n=_o(t.z,i.x);return Hi(n.notEqual(ji(1)),()=>{const e=t.z.sub(i.x),r=To(0,i.y.mul(i.y));let a=r.div(r.add(e.mul(e)));a=Do(ua(a,.3).div(.95-.3)),s.assign(Do(To(n,a)))}),s}),zx=ki(([e,t,r])=>{let s=Ol.sub(e).length();return s=s.sub(t).div(r.sub(t)),s=s.saturate(),s}),Hx=e=>{let t=Vx.get(e);if(void 0===t){const r=e.isPointLight?(e=>{const t=e.shadow.camera,r=_d("near","float",t).setGroup(Kn),s=_d("far","float",t).setGroup(Kn),i=xl(e);return zx(i,r,s)})(e):null;t=new lp,t.colorNode=nn(0,0,0,1),t.depthNode=r,t.isShadowPassMaterial=!0,t.name="ShadowMaterial",t.fog=!1,Vx.set(e,t)}return t},$x=new af,Wx=[],jx=(e,t,r,s)=>{Wx[0]=e,Wx[1]=t;let i=$x.get(Wx);return void 0!==i&&i.shadowType===r&&i.useVelocity===s||(i=(i,n,a,o,u,l,...d)=>{(!0===i.castShadow||i.receiveShadow&&r===De)&&(s&&(Bs(i).useVelocity=!0),i.onBeforeShadow(e,i,a,t.camera,o,n.overrideMaterial,l),e.renderObject(i,n,a,o,u,l,...d),i.onAfterShadow(e,i,a,t.camera,o,n.overrideMaterial,l))},i.shadowType=r,i.useVelocity=s,$x.set(Wx,i)),Wx[0]=null,Wx[1]=null,i},qx=ki(({samples:e,radius:t,size:r,shadowPass:s,depthLayer:i})=>{const n=ji(0).toVar("meanVertical"),a=ji(0).toVar("squareMeanVertical"),o=e.lessThanEqual(ji(1)).select(ji(0),ji(2).div(e.sub(1))),u=e.lessThanEqual(ji(1)).select(ji(0),ji(-1));ch({start:qi(0),end:qi(e),type:"int",condition:"<"},({i:e})=>{const l=u.add(ji(e).mul(o));let d=s.sample(oa(Rh.xy,Yi(0,l).mul(t)).div(r));s.value.isArrayTexture&&(d=d.depth(i)),d=d.x,n.addAssign(d),a.addAssign(d.mul(d))}),n.divAssign(e),a.divAssign(e);const l=ja(a.sub(n.mul(n)));return Yi(n,l)}),Xx=ki(({samples:e,radius:t,size:r,shadowPass:s,depthLayer:i})=>{const n=ji(0).toVar("meanHorizontal"),a=ji(0).toVar("squareMeanHorizontal"),o=e.lessThanEqual(ji(1)).select(ji(0),ji(2).div(e.sub(1))),u=e.lessThanEqual(ji(1)).select(ji(0),ji(-1));ch({start:qi(0),end:qi(e),type:"int",condition:"<"},({i:e})=>{const l=u.add(ji(e).mul(o));let d=s.sample(oa(Rh.xy,Yi(l,0).mul(t)).div(r));s.value.isArrayTexture&&(d=d.depth(i)),n.addAssign(d.x),a.addAssign(oa(d.y.mul(d.y),d.x.mul(d.x)))}),n.divAssign(e),a.divAssign(e);const l=ja(a.sub(n.mul(n)));return Yi(n,l)}),Kx=[Ux,Ox,kx,Gx];let Yx;const Qx=new Uy;class Zx extends wx{static get type(){return"ShadowNode"}constructor(e,t=null){super(e),this.shadow=t||e.shadow,this.shadowMap=null,this.vsmShadowMapVertical=null,this.vsmShadowMapHorizontal=null,this.vsmMaterialVertical=null,this.vsmMaterialHorizontal=null,this._node=null,this._cameraFrameId=new WeakMap,this.isShadowNode=!0,this.depthLayer=0}setupShadowFilter(e,{filterFn:t,depthTexture:r,shadowCoord:s,shadow:i,depthLayer:n}){const a=s.x.greaterThanEqual(0).and(s.x.lessThanEqual(1)).and(s.y.greaterThanEqual(0)).and(s.y.lessThanEqual(1)).and(s.z.lessThanEqual(1)),o=t({depthTexture:r,shadowCoord:s,shadow:i,depthLayer:n});return a.select(o,ji(1))}setupShadowCoord(e,t){const{shadow:r}=this,{renderer:s}=e,i=_d("bias","float",r).setGroup(Kn);let n,a=t;if(r.camera.isOrthographicCamera||!0!==s.logarithmicDepthBuffer)a=a.xyz.div(a.w),n=a.z,s.coordinateSystem===d&&(n=n.mul(2).sub(1));else{const e=a.w;a=a.xy.div(e);const t=_d("near","float",r.camera).setGroup(Kn),s=_d("far","float",r.camera).setGroup(Kn);n=Wh(e.negate(),t,s)}return a=en(a.x,a.y.oneMinus(),n.add(i)),a}getShadowFilterFn(e){return Kx[e]}setupRenderTarget(e,t){const r=new U(e.mapSize.width,e.mapSize.height);r.name="ShadowDepthTexture",r.compareFunction=Ie;const s=t.createRenderTarget(e.mapSize.width,e.mapSize.height);return s.texture.name="ShadowMap",s.texture.type=e.mapType,s.depthTexture=r,{shadowMap:s,depthTexture:r}}setupShadow(e){const{renderer:t}=e,{light:r,shadow:s}=this,i=t.shadowMap.type,{depthTexture:n,shadowMap:a}=this.setupRenderTarget(s,e);if(s.camera.updateProjectionMatrix(),i===De&&!0!==s.isPointLightShadow){n.compareFunction=null,a.depth>1?(a._vsmShadowMapVertical||(a._vsmShadowMapVertical=e.createRenderTarget(s.mapSize.width,s.mapSize.height,{format:Ve,type:ce,depth:a.depth,depthBuffer:!1}),a._vsmShadowMapVertical.texture.name="VSMVertical"),this.vsmShadowMapVertical=a._vsmShadowMapVertical,a._vsmShadowMapHorizontal||(a._vsmShadowMapHorizontal=e.createRenderTarget(s.mapSize.width,s.mapSize.height,{format:Ve,type:ce,depth:a.depth,depthBuffer:!1}),a._vsmShadowMapHorizontal.texture.name="VSMHorizontal"),this.vsmShadowMapHorizontal=a._vsmShadowMapHorizontal):(this.vsmShadowMapVertical=e.createRenderTarget(s.mapSize.width,s.mapSize.height,{format:Ve,type:ce,depthBuffer:!1}),this.vsmShadowMapHorizontal=e.createRenderTarget(s.mapSize.width,s.mapSize.height,{format:Ve,type:ce,depthBuffer:!1}));let t=Zu(n);n.isArrayTexture&&(t=t.depth(this.depthLayer));let r=Zu(this.vsmShadowMapVertical.texture);n.isArrayTexture&&(r=r.depth(this.depthLayer));const i=_d("blurSamples","float",s).setGroup(Kn),o=_d("radius","float",s).setGroup(Kn),u=_d("mapSize","vec2",s).setGroup(Kn);let l=this.vsmMaterialVertical||(this.vsmMaterialVertical=new lp);l.fragmentNode=qx({samples:i,radius:o,size:u,shadowPass:t,depthLayer:this.depthLayer}).context(e.getSharedContext()),l.name="VSMVertical",l=this.vsmMaterialHorizontal||(this.vsmMaterialHorizontal=new lp),l.fragmentNode=Xx({samples:i,radius:o,size:u,shadowPass:r,depthLayer:this.depthLayer}).context(e.getSharedContext()),l.name="VSMHorizontal"}const o=_d("intensity","float",s).setGroup(Kn),u=_d("normalBias","float",s).setGroup(Kn),l=fx(r).mul(Ax.add(Jl.mul(u))),d=this.setupShadowCoord(e,l),c=s.filterNode||this.getShadowFilterFn(t.shadowMap.type)||null;if(null===c)throw new Error("THREE.WebGPURenderer: Shadow map type not supported yet.");const h=i===De&&!0!==s.isPointLightShadow?this.vsmShadowMapHorizontal.texture:n,p=this.setupShadowFilter(e,{filterFn:c,shadowTexture:a.texture,depthTexture:h,shadowCoord:d,shadow:s,depthLayer:this.depthLayer});let g=Zu(a.texture,d);n.isArrayTexture&&(g=g.depth(this.depthLayer));const m=Lo(1,p.rgb.mix(g,1),o.mul(g.a)).toVar();return this.shadowMap=a,this.shadow.map=a,m}setup(e){if(!1!==e.renderer.shadowMap.enabled)return ki(()=>{let t=this._node;return this.setupShadowPosition(e),null===t&&(this._node=t=this.setupShadow(e)),e.material.shadowNode&&console.warn('THREE.NodeMaterial: ".shadowNode" is deprecated. Use ".castShadowNode" instead.'),e.material.receivedShadowNode&&(t=e.material.receivedShadowNode(t)),t})()}renderShadow(e){const{shadow:t,shadowMap:r,light:s}=this,{renderer:i,scene:n}=e;t.updateMatrices(s),r.setSize(t.mapSize.width,t.mapSize.height,r.depth),i.render(n,t.camera)}updateShadow(e){const{shadowMap:t,light:r,shadow:s}=this,{renderer:i,scene:n,camera:a}=e,o=i.shadowMap.type,u=t.depthTexture.version;this._depthVersionCached=u;const l=s.camera.layers.mask;4294967294&s.camera.layers.mask||(s.camera.layers.mask=a.layers.mask);const d=i.getRenderObjectFunction(),c=i.getMRT(),h=!!c&&c.has("velocity");Yx=Lx(i,n,Yx),n.overrideMaterial=Hx(r),i.setRenderObjectFunction(jx(i,s,o,h)),i.setClearColor(0,0),i.setRenderTarget(t),this.renderShadow(e),i.setRenderObjectFunction(d),o===De&&!0!==s.isPointLightShadow&&this.vsmPass(i),s.camera.layers.mask=l,Dx(i,n,Yx)}vsmPass(e){const{shadow:t}=this,r=this.shadowMap.depth;this.vsmShadowMapVertical.setSize(t.mapSize.width,t.mapSize.height,r),this.vsmShadowMapHorizontal.setSize(t.mapSize.width,t.mapSize.height,r),e.setRenderTarget(this.vsmShadowMapVertical),Qx.material=this.vsmMaterialVertical,Qx.render(e),e.setRenderTarget(this.vsmShadowMapHorizontal),Qx.material=this.vsmMaterialHorizontal,Qx.render(e)}dispose(){this.shadowMap.dispose(),this.shadowMap=null,null!==this.vsmShadowMapVertical&&(this.vsmShadowMapVertical.dispose(),this.vsmShadowMapVertical=null,this.vsmMaterialVertical.dispose(),this.vsmMaterialVertical=null),null!==this.vsmShadowMapHorizontal&&(this.vsmShadowMapHorizontal.dispose(),this.vsmShadowMapHorizontal=null,this.vsmMaterialHorizontal.dispose(),this.vsmMaterialHorizontal=null),super.dispose()}updateBefore(e){const{shadow:t}=this;let r=t.needsUpdate||t.autoUpdate;r&&(this._cameraFrameId[e.camera]===e.frameId&&(r=!1),this._cameraFrameId[e.camera]=e.frameId),r&&(this.updateShadow(e),this.shadowMap.depthTexture.version===this._depthVersionCached&&(t.needsUpdate=!1))}}const Jx=(e,t)=>Li(new Zx(e,t)),eT=new e,tT=ki(([e,t])=>{const r=e.toVar(),s=io(r),i=da(1,To(s.x,To(s.y,s.z)));s.mulAssign(i),r.mulAssign(i.mul(t.mul(2).oneMinus()));const n=Yi(r.xy).toVar(),a=t.mul(1.5).oneMinus();return Hi(s.z.greaterThanEqual(a),()=>{Hi(r.z.greaterThan(0),()=>{n.x.assign(ua(4,r.x))})}).ElseIf(s.x.greaterThanEqual(a),()=>{const e=no(r.x);n.x.assign(r.z.mul(e).add(e.mul(2)))}).ElseIf(s.y.greaterThanEqual(a),()=>{const e=no(r.y);n.x.assign(r.x.add(e.mul(2)).add(2)),n.y.assign(r.z.mul(e).sub(2))}),Yi(.125,.25).mul(n).add(Yi(.375,.75)).flipY()}).setLayout({name:"cubeToUV",type:"vec2",inputs:[{name:"pos",type:"vec3"},{name:"texelSizeY",type:"float"}]}),rT=ki(({depthTexture:e,bd3D:t,dp:r,texelSize:s})=>Zu(e,tT(t,s.y)).compare(r)),sT=ki(({depthTexture:e,bd3D:t,dp:r,texelSize:s,shadow:i})=>{const n=_d("radius","float",i).setGroup(Kn),a=Yi(-1,1).mul(n).mul(s.y);return Zu(e,tT(t.add(a.xyy),s.y)).compare(r).add(Zu(e,tT(t.add(a.yyy),s.y)).compare(r)).add(Zu(e,tT(t.add(a.xyx),s.y)).compare(r)).add(Zu(e,tT(t.add(a.yyx),s.y)).compare(r)).add(Zu(e,tT(t,s.y)).compare(r)).add(Zu(e,tT(t.add(a.xxy),s.y)).compare(r)).add(Zu(e,tT(t.add(a.yxy),s.y)).compare(r)).add(Zu(e,tT(t.add(a.xxx),s.y)).compare(r)).add(Zu(e,tT(t.add(a.yxx),s.y)).compare(r)).mul(1/9)}),iT=ki(({filterFn:e,depthTexture:t,shadowCoord:r,shadow:s})=>{const i=r.xyz.toVar(),n=i.length(),a=Zn("float").setGroup(Kn).onRenderUpdate(()=>s.camera.near),o=Zn("float").setGroup(Kn).onRenderUpdate(()=>s.camera.far),u=_d("bias","float",s).setGroup(Kn),l=Zn(s.mapSize).setGroup(Kn),d=ji(1).toVar();return Hi(n.sub(o).lessThanEqual(0).and(n.sub(a).greaterThanEqual(0)),()=>{const r=n.sub(a).div(o.sub(a)).toVar();r.addAssign(u);const c=i.normalize(),h=Yi(1).div(l.mul(Yi(4,2)));d.assign(e({depthTexture:t,bd3D:c,dp:r,texelSize:h,shadow:s}))}),d}),nT=new s,aT=new t,oT=new t;class uT extends Zx{static get type(){return"PointShadowNode"}constructor(e,t=null){super(e,t)}getShadowFilterFn(e){return e===Ue?rT:sT}setupShadowCoord(e,t){return t}setupShadowFilter(e,{filterFn:t,shadowTexture:r,depthTexture:s,shadowCoord:i,shadow:n}){return iT({filterFn:t,shadowTexture:r,depthTexture:s,shadowCoord:i,shadow:n})}renderShadow(e){const{shadow:t,shadowMap:r,light:s}=this,{renderer:i,scene:n}=e,a=t.getFrameExtents();oT.copy(t.mapSize),oT.multiply(a),r.setSize(oT.width,oT.height),aT.copy(t.mapSize);const o=i.autoClear,u=i.getClearColor(eT),l=i.getClearAlpha();i.autoClear=!1,i.setClearColor(t.clearColor,t.clearAlpha),i.clear();const d=t.getViewportCount();for(let e=0;eLi(new uT(e,t));class dT extends bh{static get type(){return"AnalyticLightNode"}constructor(t=null){super(),this.light=t,this.color=new e,this.colorNode=t&&t.colorNode||Zn(this.color).setGroup(Kn),this.baseColorNode=null,this.shadowNode=null,this.shadowColorNode=null,this.isAnalyticLightNode=!0,this.updateType=Vs.FRAME}getHash(){return this.light.uuid}getLightVector(e){return Tx(this.light).sub(e.context.positionView||Gl)}setupDirect(){}setupDirectRectArea(){}setupShadowNode(){return Jx(this.light)}setupShadow(e){const{renderer:t}=e;if(!1===t.shadowMap.enabled)return;let r=this.shadowColorNode;if(null===r){const e=this.light.shadow.shadowNode;let t;t=void 0!==e?Li(e):this.setupShadowNode(),this.shadowNode=t,this.shadowColorNode=r=this.colorNode.mul(t),this.baseColorNode=this.colorNode}this.colorNode=r}setup(e){this.colorNode=this.baseColorNode||this.colorNode,this.light.castShadow?e.object.receiveShadow&&this.setupShadow(e):null!==this.shadowNode&&(this.shadowNode.dispose(),this.shadowNode=null,this.shadowColorNode=null);const t=this.setupDirect(e),r=this.setupDirectRectArea(e);t&&e.lightsNode.setupDirectLight(e,this,t),r&&e.lightsNode.setupDirectRectAreaLight(e,this,r)}update(){const{light:e}=this;this.color.copy(e.color).multiplyScalar(e.intensity)}}const cT=ki(({lightDistance:e,cutoffDistance:t,decayExponent:r})=>{const s=e.pow(r).max(.01).reciprocal();return t.greaterThan(0).select(s.mul(e.div(t).pow4().oneMinus().clamp().pow2()),s)}),hT=({color:e,lightVector:t,cutoffDistance:r,decayExponent:s})=>{const i=t.normalize(),n=t.length(),a=cT({lightDistance:n,cutoffDistance:r,decayExponent:s});return{lightDirection:i,lightColor:e.mul(a)}};class pT extends dT{static get type(){return"PointLightNode"}constructor(e=null){super(e),this.cutoffDistanceNode=Zn(0).setGroup(Kn),this.decayExponentNode=Zn(2).setGroup(Kn)}update(e){const{light:t}=this;super.update(e),this.cutoffDistanceNode.value=t.distance,this.decayExponentNode.value=t.decay}setupShadowNode(){return lT(this.light)}setupDirect(e){return hT({color:this.colorNode,lightVector:this.getLightVector(e),cutoffDistance:this.cutoffDistanceNode,decayExponent:this.decayExponentNode})}}const gT=ki(([e=$u()])=>{const t=e.mul(2),r=t.x.floor(),s=t.y.floor();return r.add(s).mod(2).sign()}),mT=ki(([e=$u()],{renderer:t,material:r})=>{const s=Fo(e.mul(2).sub(1));let i;if(r.alphaToCoverage&&t.samples>1){const e=ji(s.fwidth()).toVar();i=Uo(e.oneMinus(),e.add(1),s).oneMinus()}else i=Xo(s.greaterThan(1),0,1);return i}),fT=ki(([e,t,r])=>{const s=ji(r).toVar(),i=ji(t).toVar(),n=Ki(e).toVar();return Xo(n,i,s)}).setLayout({name:"mx_select",type:"float",inputs:[{name:"b",type:"bool"},{name:"t",type:"float"},{name:"f",type:"float"}]}),yT=ki(([e,t])=>{const r=Ki(t).toVar(),s=ji(e).toVar();return Xo(r,s.negate(),s)}).setLayout({name:"mx_negate_if",type:"float",inputs:[{name:"val",type:"float"},{name:"b",type:"bool"}]}),bT=ki(([e])=>{const t=ji(e).toVar();return qi(Xa(t))}).setLayout({name:"mx_floor",type:"int",inputs:[{name:"x",type:"float"}]}),xT=ki(([e,t])=>{const r=ji(e).toVar();return t.assign(bT(r)),r.sub(ji(t))}),TT=uy([ki(([e,t,r,s,i,n])=>{const a=ji(n).toVar(),o=ji(i).toVar(),u=ji(s).toVar(),l=ji(r).toVar(),d=ji(t).toVar(),c=ji(e).toVar(),h=ji(ua(1,o)).toVar();return ua(1,a).mul(c.mul(h).add(d.mul(o))).add(a.mul(l.mul(h).add(u.mul(o))))}).setLayout({name:"mx_bilerp_0",type:"float",inputs:[{name:"v0",type:"float"},{name:"v1",type:"float"},{name:"v2",type:"float"},{name:"v3",type:"float"},{name:"s",type:"float"},{name:"t",type:"float"}]}),ki(([e,t,r,s,i,n])=>{const a=ji(n).toVar(),o=ji(i).toVar(),u=en(s).toVar(),l=en(r).toVar(),d=en(t).toVar(),c=en(e).toVar(),h=ji(ua(1,o)).toVar();return ua(1,a).mul(c.mul(h).add(d.mul(o))).add(a.mul(l.mul(h).add(u.mul(o))))}).setLayout({name:"mx_bilerp_1",type:"vec3",inputs:[{name:"v0",type:"vec3"},{name:"v1",type:"vec3"},{name:"v2",type:"vec3"},{name:"v3",type:"vec3"},{name:"s",type:"float"},{name:"t",type:"float"}]})]),_T=uy([ki(([e,t,r,s,i,n,a,o,u,l,d])=>{const c=ji(d).toVar(),h=ji(l).toVar(),p=ji(u).toVar(),g=ji(o).toVar(),m=ji(a).toVar(),f=ji(n).toVar(),y=ji(i).toVar(),b=ji(s).toVar(),x=ji(r).toVar(),T=ji(t).toVar(),_=ji(e).toVar(),v=ji(ua(1,p)).toVar(),N=ji(ua(1,h)).toVar();return ji(ua(1,c)).toVar().mul(N.mul(_.mul(v).add(T.mul(p))).add(h.mul(x.mul(v).add(b.mul(p))))).add(c.mul(N.mul(y.mul(v).add(f.mul(p))).add(h.mul(m.mul(v).add(g.mul(p))))))}).setLayout({name:"mx_trilerp_0",type:"float",inputs:[{name:"v0",type:"float"},{name:"v1",type:"float"},{name:"v2",type:"float"},{name:"v3",type:"float"},{name:"v4",type:"float"},{name:"v5",type:"float"},{name:"v6",type:"float"},{name:"v7",type:"float"},{name:"s",type:"float"},{name:"t",type:"float"},{name:"r",type:"float"}]}),ki(([e,t,r,s,i,n,a,o,u,l,d])=>{const c=ji(d).toVar(),h=ji(l).toVar(),p=ji(u).toVar(),g=en(o).toVar(),m=en(a).toVar(),f=en(n).toVar(),y=en(i).toVar(),b=en(s).toVar(),x=en(r).toVar(),T=en(t).toVar(),_=en(e).toVar(),v=ji(ua(1,p)).toVar(),N=ji(ua(1,h)).toVar();return ji(ua(1,c)).toVar().mul(N.mul(_.mul(v).add(T.mul(p))).add(h.mul(x.mul(v).add(b.mul(p))))).add(c.mul(N.mul(y.mul(v).add(f.mul(p))).add(h.mul(m.mul(v).add(g.mul(p))))))}).setLayout({name:"mx_trilerp_1",type:"vec3",inputs:[{name:"v0",type:"vec3"},{name:"v1",type:"vec3"},{name:"v2",type:"vec3"},{name:"v3",type:"vec3"},{name:"v4",type:"vec3"},{name:"v5",type:"vec3"},{name:"v6",type:"vec3"},{name:"v7",type:"vec3"},{name:"s",type:"float"},{name:"t",type:"float"},{name:"r",type:"float"}]})]),vT=ki(([e,t,r])=>{const s=ji(r).toVar(),i=ji(t).toVar(),n=Xi(e).toVar(),a=Xi(n.bitAnd(Xi(7))).toVar(),o=ji(fT(a.lessThan(Xi(4)),i,s)).toVar(),u=ji(la(2,fT(a.lessThan(Xi(4)),s,i))).toVar();return yT(o,Ki(a.bitAnd(Xi(1)))).add(yT(u,Ki(a.bitAnd(Xi(2)))))}).setLayout({name:"mx_gradient_float_0",type:"float",inputs:[{name:"hash",type:"uint"},{name:"x",type:"float"},{name:"y",type:"float"}]}),NT=ki(([e,t,r,s])=>{const i=ji(s).toVar(),n=ji(r).toVar(),a=ji(t).toVar(),o=Xi(e).toVar(),u=Xi(o.bitAnd(Xi(15))).toVar(),l=ji(fT(u.lessThan(Xi(8)),a,n)).toVar(),d=ji(fT(u.lessThan(Xi(4)),n,fT(u.equal(Xi(12)).or(u.equal(Xi(14))),a,i))).toVar();return yT(l,Ki(u.bitAnd(Xi(1)))).add(yT(d,Ki(u.bitAnd(Xi(2)))))}).setLayout({name:"mx_gradient_float_1",type:"float",inputs:[{name:"hash",type:"uint"},{name:"x",type:"float"},{name:"y",type:"float"},{name:"z",type:"float"}]}),ST=uy([vT,NT]),ET=ki(([e,t,r])=>{const s=ji(r).toVar(),i=ji(t).toVar(),n=rn(e).toVar();return en(ST(n.x,i,s),ST(n.y,i,s),ST(n.z,i,s))}).setLayout({name:"mx_gradient_vec3_0",type:"vec3",inputs:[{name:"hash",type:"uvec3"},{name:"x",type:"float"},{name:"y",type:"float"}]}),wT=ki(([e,t,r,s])=>{const i=ji(s).toVar(),n=ji(r).toVar(),a=ji(t).toVar(),o=rn(e).toVar();return en(ST(o.x,a,n,i),ST(o.y,a,n,i),ST(o.z,a,n,i))}).setLayout({name:"mx_gradient_vec3_1",type:"vec3",inputs:[{name:"hash",type:"uvec3"},{name:"x",type:"float"},{name:"y",type:"float"},{name:"z",type:"float"}]}),AT=uy([ET,wT]),RT=ki(([e])=>{const t=ji(e).toVar();return la(.6616,t)}).setLayout({name:"mx_gradient_scale2d_0",type:"float",inputs:[{name:"v",type:"float"}]}),CT=ki(([e])=>{const t=ji(e).toVar();return la(.982,t)}).setLayout({name:"mx_gradient_scale3d_0",type:"float",inputs:[{name:"v",type:"float"}]}),MT=uy([RT,ki(([e])=>{const t=en(e).toVar();return la(.6616,t)}).setLayout({name:"mx_gradient_scale2d_1",type:"vec3",inputs:[{name:"v",type:"vec3"}]})]),PT=uy([CT,ki(([e])=>{const t=en(e).toVar();return la(.982,t)}).setLayout({name:"mx_gradient_scale3d_1",type:"vec3",inputs:[{name:"v",type:"vec3"}]})]),BT=ki(([e,t])=>{const r=qi(t).toVar(),s=Xi(e).toVar();return s.shiftLeft(r).bitOr(s.shiftRight(qi(32).sub(r)))}).setLayout({name:"mx_rotl32",type:"uint",inputs:[{name:"x",type:"uint"},{name:"k",type:"int"}]}),FT=ki(([e,t,r])=>{e.subAssign(r),e.bitXorAssign(BT(r,qi(4))),r.addAssign(t),t.subAssign(e),t.bitXorAssign(BT(e,qi(6))),e.addAssign(r),r.subAssign(t),r.bitXorAssign(BT(t,qi(8))),t.addAssign(e),e.subAssign(r),e.bitXorAssign(BT(r,qi(16))),r.addAssign(t),t.subAssign(e),t.bitXorAssign(BT(e,qi(19))),e.addAssign(r),r.subAssign(t),r.bitXorAssign(BT(t,qi(4))),t.addAssign(e)}),LT=ki(([e,t,r])=>{const s=Xi(r).toVar(),i=Xi(t).toVar(),n=Xi(e).toVar();return s.bitXorAssign(i),s.subAssign(BT(i,qi(14))),n.bitXorAssign(s),n.subAssign(BT(s,qi(11))),i.bitXorAssign(n),i.subAssign(BT(n,qi(25))),s.bitXorAssign(i),s.subAssign(BT(i,qi(16))),n.bitXorAssign(s),n.subAssign(BT(s,qi(4))),i.bitXorAssign(n),i.subAssign(BT(n,qi(14))),s.bitXorAssign(i),s.subAssign(BT(i,qi(24))),s}).setLayout({name:"mx_bjfinal",type:"uint",inputs:[{name:"a",type:"uint"},{name:"b",type:"uint"},{name:"c",type:"uint"}]}),DT=ki(([e])=>{const t=Xi(e).toVar();return ji(t).div(ji(Xi(qi(4294967295))))}).setLayout({name:"mx_bits_to_01",type:"float",inputs:[{name:"bits",type:"uint"}]}),IT=ki(([e])=>{const t=ji(e).toVar();return t.mul(t).mul(t).mul(t.mul(t.mul(6).sub(15)).add(10))}).setLayout({name:"mx_fade",type:"float",inputs:[{name:"t",type:"float"}]}),VT=uy([ki(([e])=>{const t=qi(e).toVar(),r=Xi(Xi(1)).toVar(),s=Xi(Xi(qi(3735928559)).add(r.shiftLeft(Xi(2))).add(Xi(13))).toVar();return LT(s.add(Xi(t)),s,s)}).setLayout({name:"mx_hash_int_0",type:"uint",inputs:[{name:"x",type:"int"}]}),ki(([e,t])=>{const r=qi(t).toVar(),s=qi(e).toVar(),i=Xi(Xi(2)).toVar(),n=Xi().toVar(),a=Xi().toVar(),o=Xi().toVar();return n.assign(a.assign(o.assign(Xi(qi(3735928559)).add(i.shiftLeft(Xi(2))).add(Xi(13))))),n.addAssign(Xi(s)),a.addAssign(Xi(r)),LT(n,a,o)}).setLayout({name:"mx_hash_int_1",type:"uint",inputs:[{name:"x",type:"int"},{name:"y",type:"int"}]}),ki(([e,t,r])=>{const s=qi(r).toVar(),i=qi(t).toVar(),n=qi(e).toVar(),a=Xi(Xi(3)).toVar(),o=Xi().toVar(),u=Xi().toVar(),l=Xi().toVar();return o.assign(u.assign(l.assign(Xi(qi(3735928559)).add(a.shiftLeft(Xi(2))).add(Xi(13))))),o.addAssign(Xi(n)),u.addAssign(Xi(i)),l.addAssign(Xi(s)),LT(o,u,l)}).setLayout({name:"mx_hash_int_2",type:"uint",inputs:[{name:"x",type:"int"},{name:"y",type:"int"},{name:"z",type:"int"}]}),ki(([e,t,r,s])=>{const i=qi(s).toVar(),n=qi(r).toVar(),a=qi(t).toVar(),o=qi(e).toVar(),u=Xi(Xi(4)).toVar(),l=Xi().toVar(),d=Xi().toVar(),c=Xi().toVar();return l.assign(d.assign(c.assign(Xi(qi(3735928559)).add(u.shiftLeft(Xi(2))).add(Xi(13))))),l.addAssign(Xi(o)),d.addAssign(Xi(a)),c.addAssign(Xi(n)),FT(l,d,c),l.addAssign(Xi(i)),LT(l,d,c)}).setLayout({name:"mx_hash_int_3",type:"uint",inputs:[{name:"x",type:"int"},{name:"y",type:"int"},{name:"z",type:"int"},{name:"xx",type:"int"}]}),ki(([e,t,r,s,i])=>{const n=qi(i).toVar(),a=qi(s).toVar(),o=qi(r).toVar(),u=qi(t).toVar(),l=qi(e).toVar(),d=Xi(Xi(5)).toVar(),c=Xi().toVar(),h=Xi().toVar(),p=Xi().toVar();return c.assign(h.assign(p.assign(Xi(qi(3735928559)).add(d.shiftLeft(Xi(2))).add(Xi(13))))),c.addAssign(Xi(l)),h.addAssign(Xi(u)),p.addAssign(Xi(o)),FT(c,h,p),c.addAssign(Xi(a)),h.addAssign(Xi(n)),LT(c,h,p)}).setLayout({name:"mx_hash_int_4",type:"uint",inputs:[{name:"x",type:"int"},{name:"y",type:"int"},{name:"z",type:"int"},{name:"xx",type:"int"},{name:"yy",type:"int"}]})]),UT=uy([ki(([e,t])=>{const r=qi(t).toVar(),s=qi(e).toVar(),i=Xi(VT(s,r)).toVar(),n=rn().toVar();return n.x.assign(i.bitAnd(qi(255))),n.y.assign(i.shiftRight(qi(8)).bitAnd(qi(255))),n.z.assign(i.shiftRight(qi(16)).bitAnd(qi(255))),n}).setLayout({name:"mx_hash_vec3_0",type:"uvec3",inputs:[{name:"x",type:"int"},{name:"y",type:"int"}]}),ki(([e,t,r])=>{const s=qi(r).toVar(),i=qi(t).toVar(),n=qi(e).toVar(),a=Xi(VT(n,i,s)).toVar(),o=rn().toVar();return o.x.assign(a.bitAnd(qi(255))),o.y.assign(a.shiftRight(qi(8)).bitAnd(qi(255))),o.z.assign(a.shiftRight(qi(16)).bitAnd(qi(255))),o}).setLayout({name:"mx_hash_vec3_1",type:"uvec3",inputs:[{name:"x",type:"int"},{name:"y",type:"int"},{name:"z",type:"int"}]})]),OT=uy([ki(([e])=>{const t=Yi(e).toVar(),r=qi().toVar(),s=qi().toVar(),i=ji(xT(t.x,r)).toVar(),n=ji(xT(t.y,s)).toVar(),a=ji(IT(i)).toVar(),o=ji(IT(n)).toVar(),u=ji(TT(ST(VT(r,s),i,n),ST(VT(r.add(qi(1)),s),i.sub(1),n),ST(VT(r,s.add(qi(1))),i,n.sub(1)),ST(VT(r.add(qi(1)),s.add(qi(1))),i.sub(1),n.sub(1)),a,o)).toVar();return MT(u)}).setLayout({name:"mx_perlin_noise_float_0",type:"float",inputs:[{name:"p",type:"vec2"}]}),ki(([e])=>{const t=en(e).toVar(),r=qi().toVar(),s=qi().toVar(),i=qi().toVar(),n=ji(xT(t.x,r)).toVar(),a=ji(xT(t.y,s)).toVar(),o=ji(xT(t.z,i)).toVar(),u=ji(IT(n)).toVar(),l=ji(IT(a)).toVar(),d=ji(IT(o)).toVar(),c=ji(_T(ST(VT(r,s,i),n,a,o),ST(VT(r.add(qi(1)),s,i),n.sub(1),a,o),ST(VT(r,s.add(qi(1)),i),n,a.sub(1),o),ST(VT(r.add(qi(1)),s.add(qi(1)),i),n.sub(1),a.sub(1),o),ST(VT(r,s,i.add(qi(1))),n,a,o.sub(1)),ST(VT(r.add(qi(1)),s,i.add(qi(1))),n.sub(1),a,o.sub(1)),ST(VT(r,s.add(qi(1)),i.add(qi(1))),n,a.sub(1),o.sub(1)),ST(VT(r.add(qi(1)),s.add(qi(1)),i.add(qi(1))),n.sub(1),a.sub(1),o.sub(1)),u,l,d)).toVar();return PT(c)}).setLayout({name:"mx_perlin_noise_float_1",type:"float",inputs:[{name:"p",type:"vec3"}]})]),kT=uy([ki(([e])=>{const t=Yi(e).toVar(),r=qi().toVar(),s=qi().toVar(),i=ji(xT(t.x,r)).toVar(),n=ji(xT(t.y,s)).toVar(),a=ji(IT(i)).toVar(),o=ji(IT(n)).toVar(),u=en(TT(AT(UT(r,s),i,n),AT(UT(r.add(qi(1)),s),i.sub(1),n),AT(UT(r,s.add(qi(1))),i,n.sub(1)),AT(UT(r.add(qi(1)),s.add(qi(1))),i.sub(1),n.sub(1)),a,o)).toVar();return MT(u)}).setLayout({name:"mx_perlin_noise_vec3_0",type:"vec3",inputs:[{name:"p",type:"vec2"}]}),ki(([e])=>{const t=en(e).toVar(),r=qi().toVar(),s=qi().toVar(),i=qi().toVar(),n=ji(xT(t.x,r)).toVar(),a=ji(xT(t.y,s)).toVar(),o=ji(xT(t.z,i)).toVar(),u=ji(IT(n)).toVar(),l=ji(IT(a)).toVar(),d=ji(IT(o)).toVar(),c=en(_T(AT(UT(r,s,i),n,a,o),AT(UT(r.add(qi(1)),s,i),n.sub(1),a,o),AT(UT(r,s.add(qi(1)),i),n,a.sub(1),o),AT(UT(r.add(qi(1)),s.add(qi(1)),i),n.sub(1),a.sub(1),o),AT(UT(r,s,i.add(qi(1))),n,a,o.sub(1)),AT(UT(r.add(qi(1)),s,i.add(qi(1))),n.sub(1),a,o.sub(1)),AT(UT(r,s.add(qi(1)),i.add(qi(1))),n,a.sub(1),o.sub(1)),AT(UT(r.add(qi(1)),s.add(qi(1)),i.add(qi(1))),n.sub(1),a.sub(1),o.sub(1)),u,l,d)).toVar();return PT(c)}).setLayout({name:"mx_perlin_noise_vec3_1",type:"vec3",inputs:[{name:"p",type:"vec3"}]})]),GT=uy([ki(([e])=>{const t=ji(e).toVar(),r=qi(bT(t)).toVar();return DT(VT(r))}).setLayout({name:"mx_cell_noise_float_0",type:"float",inputs:[{name:"p",type:"float"}]}),ki(([e])=>{const t=Yi(e).toVar(),r=qi(bT(t.x)).toVar(),s=qi(bT(t.y)).toVar();return DT(VT(r,s))}).setLayout({name:"mx_cell_noise_float_1",type:"float",inputs:[{name:"p",type:"vec2"}]}),ki(([e])=>{const t=en(e).toVar(),r=qi(bT(t.x)).toVar(),s=qi(bT(t.y)).toVar(),i=qi(bT(t.z)).toVar();return DT(VT(r,s,i))}).setLayout({name:"mx_cell_noise_float_2",type:"float",inputs:[{name:"p",type:"vec3"}]}),ki(([e])=>{const t=nn(e).toVar(),r=qi(bT(t.x)).toVar(),s=qi(bT(t.y)).toVar(),i=qi(bT(t.z)).toVar(),n=qi(bT(t.w)).toVar();return DT(VT(r,s,i,n))}).setLayout({name:"mx_cell_noise_float_3",type:"float",inputs:[{name:"p",type:"vec4"}]})]),zT=uy([ki(([e])=>{const t=ji(e).toVar(),r=qi(bT(t)).toVar();return en(DT(VT(r,qi(0))),DT(VT(r,qi(1))),DT(VT(r,qi(2))))}).setLayout({name:"mx_cell_noise_vec3_0",type:"vec3",inputs:[{name:"p",type:"float"}]}),ki(([e])=>{const t=Yi(e).toVar(),r=qi(bT(t.x)).toVar(),s=qi(bT(t.y)).toVar();return en(DT(VT(r,s,qi(0))),DT(VT(r,s,qi(1))),DT(VT(r,s,qi(2))))}).setLayout({name:"mx_cell_noise_vec3_1",type:"vec3",inputs:[{name:"p",type:"vec2"}]}),ki(([e])=>{const t=en(e).toVar(),r=qi(bT(t.x)).toVar(),s=qi(bT(t.y)).toVar(),i=qi(bT(t.z)).toVar();return en(DT(VT(r,s,i,qi(0))),DT(VT(r,s,i,qi(1))),DT(VT(r,s,i,qi(2))))}).setLayout({name:"mx_cell_noise_vec3_2",type:"vec3",inputs:[{name:"p",type:"vec3"}]}),ki(([e])=>{const t=nn(e).toVar(),r=qi(bT(t.x)).toVar(),s=qi(bT(t.y)).toVar(),i=qi(bT(t.z)).toVar(),n=qi(bT(t.w)).toVar();return en(DT(VT(r,s,i,n,qi(0))),DT(VT(r,s,i,n,qi(1))),DT(VT(r,s,i,n,qi(2))))}).setLayout({name:"mx_cell_noise_vec3_3",type:"vec3",inputs:[{name:"p",type:"vec4"}]})]),HT=ki(([e,t,r,s])=>{const i=ji(s).toVar(),n=ji(r).toVar(),a=qi(t).toVar(),o=en(e).toVar(),u=ji(0).toVar(),l=ji(1).toVar();return ch(a,()=>{u.addAssign(l.mul(OT(o))),l.mulAssign(i),o.mulAssign(n)}),u}).setLayout({name:"mx_fractal_noise_float",type:"float",inputs:[{name:"p",type:"vec3"},{name:"octaves",type:"int"},{name:"lacunarity",type:"float"},{name:"diminish",type:"float"}]}),$T=ki(([e,t,r,s])=>{const i=ji(s).toVar(),n=ji(r).toVar(),a=qi(t).toVar(),o=en(e).toVar(),u=en(0).toVar(),l=ji(1).toVar();return ch(a,()=>{u.addAssign(l.mul(kT(o))),l.mulAssign(i),o.mulAssign(n)}),u}).setLayout({name:"mx_fractal_noise_vec3",type:"vec3",inputs:[{name:"p",type:"vec3"},{name:"octaves",type:"int"},{name:"lacunarity",type:"float"},{name:"diminish",type:"float"}]}),WT=ki(([e,t,r,s])=>{const i=ji(s).toVar(),n=ji(r).toVar(),a=qi(t).toVar(),o=en(e).toVar();return Yi(HT(o,a,n,i),HT(o.add(en(qi(19),qi(193),qi(17))),a,n,i))}).setLayout({name:"mx_fractal_noise_vec2",type:"vec2",inputs:[{name:"p",type:"vec3"},{name:"octaves",type:"int"},{name:"lacunarity",type:"float"},{name:"diminish",type:"float"}]}),jT=ki(([e,t,r,s])=>{const i=ji(s).toVar(),n=ji(r).toVar(),a=qi(t).toVar(),o=en(e).toVar(),u=en($T(o,a,n,i)).toVar(),l=ji(HT(o.add(en(qi(19),qi(193),qi(17))),a,n,i)).toVar();return nn(u,l)}).setLayout({name:"mx_fractal_noise_vec4",type:"vec4",inputs:[{name:"p",type:"vec3"},{name:"octaves",type:"int"},{name:"lacunarity",type:"float"},{name:"diminish",type:"float"}]}),qT=uy([ki(([e,t,r,s,i,n,a])=>{const o=qi(a).toVar(),u=ji(n).toVar(),l=qi(i).toVar(),d=qi(s).toVar(),c=qi(r).toVar(),h=qi(t).toVar(),p=Yi(e).toVar(),g=en(zT(Yi(h.add(d),c.add(l)))).toVar(),m=Yi(g.x,g.y).toVar();m.subAssign(.5),m.mulAssign(u),m.addAssign(.5);const f=Yi(Yi(ji(h),ji(c)).add(m)).toVar(),y=Yi(f.sub(p)).toVar();return Hi(o.equal(qi(2)),()=>io(y.x).add(io(y.y))),Hi(o.equal(qi(3)),()=>To(io(y.x),io(y.y))),Eo(y,y)}).setLayout({name:"mx_worley_distance_0",type:"float",inputs:[{name:"p",type:"vec2"},{name:"x",type:"int"},{name:"y",type:"int"},{name:"xoff",type:"int"},{name:"yoff",type:"int"},{name:"jitter",type:"float"},{name:"metric",type:"int"}]}),ki(([e,t,r,s,i,n,a,o,u])=>{const l=qi(u).toVar(),d=ji(o).toVar(),c=qi(a).toVar(),h=qi(n).toVar(),p=qi(i).toVar(),g=qi(s).toVar(),m=qi(r).toVar(),f=qi(t).toVar(),y=en(e).toVar(),b=en(zT(en(f.add(p),m.add(h),g.add(c)))).toVar();b.subAssign(.5),b.mulAssign(d),b.addAssign(.5);const x=en(en(ji(f),ji(m),ji(g)).add(b)).toVar(),T=en(x.sub(y)).toVar();return Hi(l.equal(qi(2)),()=>io(T.x).add(io(T.y)).add(io(T.z))),Hi(l.equal(qi(3)),()=>To(io(T.x),io(T.y),io(T.z))),Eo(T,T)}).setLayout({name:"mx_worley_distance_1",type:"float",inputs:[{name:"p",type:"vec3"},{name:"x",type:"int"},{name:"y",type:"int"},{name:"z",type:"int"},{name:"xoff",type:"int"},{name:"yoff",type:"int"},{name:"zoff",type:"int"},{name:"jitter",type:"float"},{name:"metric",type:"int"}]})]),XT=ki(([e,t,r])=>{const s=qi(r).toVar(),i=ji(t).toVar(),n=Yi(e).toVar(),a=qi().toVar(),o=qi().toVar(),u=Yi(xT(n.x,a),xT(n.y,o)).toVar(),l=ji(1e6).toVar();return ch({start:-1,end:qi(1),name:"x",condition:"<="},({x:e})=>{ch({start:-1,end:qi(1),name:"y",condition:"<="},({y:t})=>{const r=ji(qT(u,e,t,a,o,i,s)).toVar();l.assign(xo(l,r))})}),Hi(s.equal(qi(0)),()=>{l.assign(ja(l))}),l}).setLayout({name:"mx_worley_noise_float_0",type:"float",inputs:[{name:"p",type:"vec2"},{name:"jitter",type:"float"},{name:"metric",type:"int"}]}),KT=ki(([e,t,r])=>{const s=qi(r).toVar(),i=ji(t).toVar(),n=Yi(e).toVar(),a=qi().toVar(),o=qi().toVar(),u=Yi(xT(n.x,a),xT(n.y,o)).toVar(),l=Yi(1e6,1e6).toVar();return ch({start:-1,end:qi(1),name:"x",condition:"<="},({x:e})=>{ch({start:-1,end:qi(1),name:"y",condition:"<="},({y:t})=>{const r=ji(qT(u,e,t,a,o,i,s)).toVar();Hi(r.lessThan(l.x),()=>{l.y.assign(l.x),l.x.assign(r)}).ElseIf(r.lessThan(l.y),()=>{l.y.assign(r)})})}),Hi(s.equal(qi(0)),()=>{l.assign(ja(l))}),l}).setLayout({name:"mx_worley_noise_vec2_0",type:"vec2",inputs:[{name:"p",type:"vec2"},{name:"jitter",type:"float"},{name:"metric",type:"int"}]}),YT=ki(([e,t,r])=>{const s=qi(r).toVar(),i=ji(t).toVar(),n=Yi(e).toVar(),a=qi().toVar(),o=qi().toVar(),u=Yi(xT(n.x,a),xT(n.y,o)).toVar(),l=en(1e6,1e6,1e6).toVar();return ch({start:-1,end:qi(1),name:"x",condition:"<="},({x:e})=>{ch({start:-1,end:qi(1),name:"y",condition:"<="},({y:t})=>{const r=ji(qT(u,e,t,a,o,i,s)).toVar();Hi(r.lessThan(l.x),()=>{l.z.assign(l.y),l.y.assign(l.x),l.x.assign(r)}).ElseIf(r.lessThan(l.y),()=>{l.z.assign(l.y),l.y.assign(r)}).ElseIf(r.lessThan(l.z),()=>{l.z.assign(r)})})}),Hi(s.equal(qi(0)),()=>{l.assign(ja(l))}),l}).setLayout({name:"mx_worley_noise_vec3_0",type:"vec3",inputs:[{name:"p",type:"vec2"},{name:"jitter",type:"float"},{name:"metric",type:"int"}]}),QT=uy([XT,ki(([e,t,r])=>{const s=qi(r).toVar(),i=ji(t).toVar(),n=en(e).toVar(),a=qi().toVar(),o=qi().toVar(),u=qi().toVar(),l=en(xT(n.x,a),xT(n.y,o),xT(n.z,u)).toVar(),d=ji(1e6).toVar();return ch({start:-1,end:qi(1),name:"x",condition:"<="},({x:e})=>{ch({start:-1,end:qi(1),name:"y",condition:"<="},({y:t})=>{ch({start:-1,end:qi(1),name:"z",condition:"<="},({z:r})=>{const n=ji(qT(l,e,t,r,a,o,u,i,s)).toVar();d.assign(xo(d,n))})})}),Hi(s.equal(qi(0)),()=>{d.assign(ja(d))}),d}).setLayout({name:"mx_worley_noise_float_1",type:"float",inputs:[{name:"p",type:"vec3"},{name:"jitter",type:"float"},{name:"metric",type:"int"}]})]),ZT=uy([KT,ki(([e,t,r])=>{const s=qi(r).toVar(),i=ji(t).toVar(),n=en(e).toVar(),a=qi().toVar(),o=qi().toVar(),u=qi().toVar(),l=en(xT(n.x,a),xT(n.y,o),xT(n.z,u)).toVar(),d=Yi(1e6,1e6).toVar();return ch({start:-1,end:qi(1),name:"x",condition:"<="},({x:e})=>{ch({start:-1,end:qi(1),name:"y",condition:"<="},({y:t})=>{ch({start:-1,end:qi(1),name:"z",condition:"<="},({z:r})=>{const n=ji(qT(l,e,t,r,a,o,u,i,s)).toVar();Hi(n.lessThan(d.x),()=>{d.y.assign(d.x),d.x.assign(n)}).ElseIf(n.lessThan(d.y),()=>{d.y.assign(n)})})})}),Hi(s.equal(qi(0)),()=>{d.assign(ja(d))}),d}).setLayout({name:"mx_worley_noise_vec2_1",type:"vec2",inputs:[{name:"p",type:"vec3"},{name:"jitter",type:"float"},{name:"metric",type:"int"}]})]),JT=uy([YT,ki(([e,t,r])=>{const s=qi(r).toVar(),i=ji(t).toVar(),n=en(e).toVar(),a=qi().toVar(),o=qi().toVar(),u=qi().toVar(),l=en(xT(n.x,a),xT(n.y,o),xT(n.z,u)).toVar(),d=en(1e6,1e6,1e6).toVar();return ch({start:-1,end:qi(1),name:"x",condition:"<="},({x:e})=>{ch({start:-1,end:qi(1),name:"y",condition:"<="},({y:t})=>{ch({start:-1,end:qi(1),name:"z",condition:"<="},({z:r})=>{const n=ji(qT(l,e,t,r,a,o,u,i,s)).toVar();Hi(n.lessThan(d.x),()=>{d.z.assign(d.y),d.y.assign(d.x),d.x.assign(n)}).ElseIf(n.lessThan(d.y),()=>{d.z.assign(d.y),d.y.assign(n)}).ElseIf(n.lessThan(d.z),()=>{d.z.assign(n)})})})}),Hi(s.equal(qi(0)),()=>{d.assign(ja(d))}),d}).setLayout({name:"mx_worley_noise_vec3_1",type:"vec3",inputs:[{name:"p",type:"vec3"},{name:"jitter",type:"float"},{name:"metric",type:"int"}]})]),e_=ki(([e])=>{const t=e.y,r=e.z,s=en().toVar();return Hi(t.lessThan(1e-4),()=>{s.assign(en(r,r,r))}).Else(()=>{let i=e.x;i=i.sub(Xa(i)).mul(6).toVar();const n=qi(go(i)),a=i.sub(ji(n)),o=r.mul(t.oneMinus()),u=r.mul(t.mul(a).oneMinus()),l=r.mul(t.mul(a.oneMinus()).oneMinus());Hi(n.equal(qi(0)),()=>{s.assign(en(r,l,o))}).ElseIf(n.equal(qi(1)),()=>{s.assign(en(u,r,o))}).ElseIf(n.equal(qi(2)),()=>{s.assign(en(o,r,l))}).ElseIf(n.equal(qi(3)),()=>{s.assign(en(o,u,r))}).ElseIf(n.equal(qi(4)),()=>{s.assign(en(l,o,r))}).Else(()=>{s.assign(en(r,o,u))})}),s}).setLayout({name:"mx_hsvtorgb",type:"vec3",inputs:[{name:"hsv",type:"vec3"}]}),t_=ki(([e])=>{const t=en(e).toVar(),r=ji(t.x).toVar(),s=ji(t.y).toVar(),i=ji(t.z).toVar(),n=ji(xo(r,xo(s,i))).toVar(),a=ji(To(r,To(s,i))).toVar(),o=ji(a.sub(n)).toVar(),u=ji().toVar(),l=ji().toVar(),d=ji().toVar();return d.assign(a),Hi(a.greaterThan(0),()=>{l.assign(o.div(a))}).Else(()=>{l.assign(0)}),Hi(l.lessThanEqual(0),()=>{u.assign(0)}).Else(()=>{Hi(r.greaterThanEqual(a),()=>{u.assign(s.sub(i).div(o))}).ElseIf(s.greaterThanEqual(a),()=>{u.assign(oa(2,i.sub(r).div(o)))}).Else(()=>{u.assign(oa(4,r.sub(s).div(o)))}),u.mulAssign(1/6),Hi(u.lessThan(0),()=>{u.addAssign(1)})}),en(u,l,d)}).setLayout({name:"mx_rgbtohsv",type:"vec3",inputs:[{name:"c",type:"vec3"}]}),r_=ki(([e])=>{const t=en(e).toVar(),r=sn(ma(t,en(.04045))).toVar(),s=en(t.div(12.92)).toVar(),i=en(Ao(To(t.add(en(.055)),en(0)).div(1.055),en(2.4))).toVar();return Lo(s,i,r)}).setLayout({name:"mx_srgb_texture_to_lin_rec709",type:"vec3",inputs:[{name:"color",type:"vec3"}]}),s_=(e,t)=>{e=ji(e),t=ji(t);const r=Yi(t.dFdx(),t.dFdy()).length().mul(.7071067811865476);return Uo(e.sub(r),e.add(r),t)},i_=(e,t,r,s)=>Lo(e,t,r[s].clamp()),n_=(e,t,r,s,i)=>Lo(e,t,s_(r,s[i])),a_=ki(([e,t,r])=>{const s=Ya(e).toVar(),i=ua(ji(.5).mul(t.sub(r)),Ol).div(s).toVar(),n=ua(ji(-.5).mul(t.sub(r)),Ol).div(s).toVar(),a=en().toVar();a.x=s.x.greaterThan(ji(0)).select(i.x,n.x),a.y=s.y.greaterThan(ji(0)).select(i.y,n.y),a.z=s.z.greaterThan(ji(0)).select(i.z,n.z);const o=xo(a.x,a.y,a.z).toVar();return Ol.add(s.mul(o)).toVar().sub(r)}),o_=ki(([e,t])=>{const r=e.x,s=e.y,i=e.z;let n=t.element(0).mul(.886227);return n=n.add(t.element(1).mul(1.023328).mul(s)),n=n.add(t.element(2).mul(1.023328).mul(i)),n=n.add(t.element(3).mul(1.023328).mul(r)),n=n.add(t.element(4).mul(.858086).mul(r).mul(s)),n=n.add(t.element(5).mul(.858086).mul(s).mul(i)),n=n.add(t.element(6).mul(i.mul(i).mul(.743125).sub(.247708))),n=n.add(t.element(7).mul(.858086).mul(r).mul(i)),n=n.add(t.element(8).mul(.429043).mul(la(r,r).sub(la(s,s)))),n});var u_=Object.freeze({__proto__:null,BRDF_GGX:Qp,BRDF_Lambert:Ip,BasicPointShadowFilter:rT,BasicShadowFilter:Ux,Break:hh,Const:tu,Continue:()=>Iu("continue").toStack(),DFGApprox:Zp,D_GGX:Xp,Discard:Vu,EPSILON:La,F_Schlick:Dp,Fn:ki,INFINITY:Da,If:Hi,Loop:ch,NodeAccess:Os,NodeShaderStage:Is,NodeType:Us,NodeUpdateType:Vs,PCFShadowFilter:Ox,PCFSoftShadowFilter:kx,PI:Ia,PI2:Va,PointShadowFilter:sT,Return:()=>Iu("return").toStack(),Schlick_to_F0:eg,ScriptableNodeResources:$b,ShaderNode:Fi,Stack:$i,Switch:(...e)=>ni.Switch(...e),TBNViewMatrix:Xd,VSMShadowFilter:Gx,V_GGX_SmithCorrelated:jp,Var:eu,abs:io,acesFilmicToneMapping:Mb,acos:ro,add:oa,addMethodChaining:oi,addNodeElement:function(e){console.warn("THREE.TSL: AddNodeElement has been removed in favor of tree-shaking. Trying add",e)},agxToneMapping:Lb,all:Ua,alphaT:Rn,and:ba,anisotropy:Cn,anisotropyB:Pn,anisotropyT:Mn,any:Oa,append:e=>(console.warn("THREE.TSL: append() has been renamed to Stack()."),$i(e)),array:ea,arrayBuffer:e=>Li(new si(e,"ArrayBuffer")),asin:to,assign:ra,atan:so,atan2:$o,atomicAdd:(e,t)=>px(cx.ATOMIC_ADD,e,t),atomicAnd:(e,t)=>px(cx.ATOMIC_AND,e,t),atomicFunc:px,atomicLoad:e=>px(cx.ATOMIC_LOAD,e,null),atomicMax:(e,t)=>px(cx.ATOMIC_MAX,e,t),atomicMin:(e,t)=>px(cx.ATOMIC_MIN,e,t),atomicOr:(e,t)=>px(cx.ATOMIC_OR,e,t),atomicStore:(e,t)=>px(cx.ATOMIC_STORE,e,t),atomicSub:(e,t)=>px(cx.ATOMIC_SUB,e,t),atomicXor:(e,t)=>px(cx.ATOMIC_XOR,e,t),attenuationColor:Hn,attenuationDistance:zn,attribute:Hu,attributeArray:(e,t="float")=>{let r,s;!0===t.isStruct?(r=t.layout.getLength(),s=ws("float")):(r=As(t),s=ws(t));const i=new qy(e,r,s);return ah(i,t,e)},backgroundBlurriness:Jy,backgroundIntensity:eb,backgroundRotation:tb,batch:rh,bentNormalView:Yd,billboarding:gy,bitAnd:va,bitNot:Na,bitOr:Sa,bitXor:Ea,bitangentGeometry:$d,bitangentLocal:Wd,bitangentView:jd,bitangentWorld:qd,bitcast:yo,blendBurn:rp,blendColor:ap,blendDodge:sp,blendOverlay:np,blendScreen:ip,blur:em,bool:Ki,buffer:tl,bufferAttribute:vu,bumpMap:rc,burn:(...e)=>(console.warn('THREE.TSL: "burn" has been renamed. Use "blendBurn" instead.'),rp(e)),bvec2:Ji,bvec3:sn,bvec4:un,bypass:Pu,cache:Cu,call:ia,cameraFar:ul,cameraIndex:al,cameraNear:ol,cameraNormalMatrix:pl,cameraPosition:gl,cameraProjectionMatrix:ll,cameraProjectionMatrixInverse:dl,cameraViewMatrix:cl,cameraWorldMatrix:hl,cbrt:Bo,cdl:bb,ceil:Ka,checker:gT,cineonToneMapping:Rb,clamp:Do,clearcoat:_n,clearcoatNormalView:ed,clearcoatRoughness:vn,code:Vb,color:Wi,colorSpaceToWorking:pu,colorToDirection:e=>Li(e).mul(2).sub(1),compute:Au,computeSkinning:(e,t=null)=>{const r=new uh(e);return r.positionNode=ah(new F(e.geometry.getAttribute("position").array,3),"vec3").setPBO(!0).toReadOnly().element(jc).toVar(),r.skinIndexNode=ah(new F(new Uint32Array(e.geometry.getAttribute("skinIndex").array),4),"uvec4").setPBO(!0).toReadOnly().element(jc).toVar(),r.skinWeightNode=ah(new F(e.geometry.getAttribute("skinWeight").array,4),"vec4").setPBO(!0).toReadOnly().element(jc).toVar(),r.bindMatrixNode=Zn(e.bindMatrix,"mat4"),r.bindMatrixInverseNode=Zn(e.bindMatrixInverse,"mat4"),r.boneMatricesNode=tl(e.skeleton.boneMatrices,"mat4",e.skeleton.bones.length),r.toPositionNode=t,Li(r)},context:Yo,convert:pn,convertColorSpace:(e,t,r)=>Li(new cu(Li(e),t,r)),convertToTexture:(e,...t)=>e.isTextureNode?e:e.isPassNode?e.getTextureNode():Gy(e,...t),cos:Ja,cross:wo,cubeTexture:bd,cubeTextureBase:yd,cubeToUV:tT,dFdx:lo,dFdy:co,dashSize:In,debug:Gu,decrement:Pa,decrementBefore:Ca,defaultBuildStages:Gs,defaultShaderStages:ks,defined:Pi,degrees:Ga,deltaTime:dy,densityFog:function(e,t){return console.warn('THREE.TSL: "densityFog( color, density )" is deprecated. Use "fog( color, densityFogFactor( density ) )" instead.'),Yb(e,Kb(t))},densityFogFactor:Kb,depth:qh,depthPass:(e,t,r)=>Li(new Sb(Sb.DEPTH,e,t,r)),difference:So,diffuseColor:yn,directPointLight:hT,directionToColor:xp,directionToFaceDirection:jl,dispersion:$n,distance:No,div:da,dodge:(...e)=>(console.warn('THREE.TSL: "dodge" has been renamed. Use "blendDodge" instead.'),sp(e)),dot:Eo,drawIndex:Yc,dynamicBufferAttribute:Nu,element:hn,emissive:bn,equal:ha,equals:bo,equirectUV:vp,exp:za,exp2:Ha,expression:Iu,faceDirection:Wl,faceForward:Oo,faceforward:Wo,float:ji,floor:Xa,fog:Yb,fract:Qa,frameGroup:Xn,frameId:cy,frontFacing:$l,fwidth:mo,gain:(e,t)=>e.lessThan(.5)?ry(e.mul(2),t).div(2):ua(1,ry(la(ua(1,e),2),t).div(2)),gapSize:Vn,getConstNodeType:Bi,getCurrentStack:zi,getDirection:Yg,getDistanceAttenuation:cT,getGeometryRoughness:$p,getNormalFromDepth:$y,getParallaxCorrectNormal:a_,getRoughness:Wp,getScreenPosition:Hy,getShIrradianceAt:o_,getShadowMaterial:Hx,getShadowRenderObjectFunction:jx,getTextureIndex:Zf,getViewPosition:zy,globalId:nx,glsl:(e,t)=>Vb(e,t,"glsl"),glslFn:(e,t)=>Ob(e,t,"glsl"),grayscale:pb,greaterThan:ma,greaterThanEqual:ya,hash:ty,highpModelNormalViewMatrix:Dl,highpModelViewMatrix:Ll,hue:fb,increment:Ma,incrementBefore:Ra,instance:Zc,instanceIndex:jc,instancedArray:(e,t="float")=>{let r,s;!0===t.isStruct?(r=t.layout.getLength(),s=ws("float")):(r=As(t),s=ws(t));const i=new jy(e,r,s);return ah(i,t,e)},instancedBufferAttribute:Su,instancedDynamicBufferAttribute:Eu,instancedMesh:eh,int:qi,inverseSqrt:qa,inversesqrt:jo,invocationLocalIndex:Kc,invocationSubgroupIndex:Xc,ior:On,iridescence:En,iridescenceIOR:wn,iridescenceThickness:An,ivec2:Qi,ivec3:tn,ivec4:an,js:(e,t)=>Vb(e,t,"js"),label:Qo,length:ao,lengthSq:Fo,lessThan:ga,lessThanEqual:fa,lightPosition:bx,lightProjectionUV:yx,lightShadowMatrix:fx,lightTargetDirection:_x,lightTargetPosition:xx,lightViewPosition:Tx,lightingContext:_h,lights:(e=[])=>Li(new Ex).setLights(e),linearDepth:Xh,linearToneMapping:wb,localId:ax,log:$a,log2:Wa,logarithmicDepthToViewZ:(e,t,r)=>{const s=e.mul($a(r.div(t)));return ji(Math.E).pow(s).mul(t).negate()},luminance:yb,mat2:ln,mat3:dn,mat4:cn,matcapUV:Gm,materialAO:Gc,materialAlphaTest:nc,materialAnisotropy:Sc,materialAnisotropyVector:zc,materialAttenuationColor:Bc,materialAttenuationDistance:Pc,materialClearcoat:bc,materialClearcoatNormal:Tc,materialClearcoatRoughness:xc,materialColor:ac,materialDispersion:Oc,materialEmissive:uc,materialEnvIntensity:ld,materialEnvRotation:dd,materialIOR:Mc,materialIridescence:Ec,materialIridescenceIOR:wc,materialIridescenceThickness:Ac,materialLightMap:kc,materialLineDashOffset:Vc,materialLineDashSize:Lc,materialLineGapSize:Dc,materialLineScale:Fc,materialLineWidth:Ic,materialMetalness:fc,materialNormal:yc,materialOpacity:lc,materialPointSize:Uc,materialReference:Sd,materialReflectivity:gc,materialRefractionRatio:ud,materialRotation:_c,materialRoughness:mc,materialSheen:vc,materialSheenRoughness:Nc,materialShininess:oc,materialSpecular:dc,materialSpecularColor:hc,materialSpecularIntensity:cc,materialSpecularStrength:pc,materialThickness:Cc,materialTransmission:Rc,max:To,maxMipLevel:Xu,mediumpModelViewMatrix:Fl,metalness:Tn,min:xo,mix:Lo,mixElement:Go,mod:ca,modInt:Ba,modelDirection:Sl,modelNormalMatrix:Ml,modelPosition:wl,modelRadius:Cl,modelScale:Al,modelViewMatrix:Bl,modelViewPosition:Rl,modelViewProjection:Hc,modelWorldMatrix:El,modelWorldMatrixInverse:Pl,morphReference:yh,mrt:ey,mul:la,mx_aastep:s_,mx_cell_noise_float:(e=$u())=>GT(e.convert("vec2|vec3")),mx_contrast:(e,t=1,r=.5)=>ji(e).sub(r).mul(t).add(r),mx_fractal_noise_float:(e=$u(),t=3,r=2,s=.5,i=1)=>HT(e,qi(t),r,s).mul(i),mx_fractal_noise_vec2:(e=$u(),t=3,r=2,s=.5,i=1)=>WT(e,qi(t),r,s).mul(i),mx_fractal_noise_vec3:(e=$u(),t=3,r=2,s=.5,i=1)=>$T(e,qi(t),r,s).mul(i),mx_fractal_noise_vec4:(e=$u(),t=3,r=2,s=.5,i=1)=>jT(e,qi(t),r,s).mul(i),mx_hsvtorgb:e_,mx_noise_float:(e=$u(),t=1,r=0)=>OT(e.convert("vec2|vec3")).mul(t).add(r),mx_noise_vec3:(e=$u(),t=1,r=0)=>kT(e.convert("vec2|vec3")).mul(t).add(r),mx_noise_vec4:(e=$u(),t=1,r=0)=>{e=e.convert("vec2|vec3");return nn(kT(e),OT(e.add(Yi(19,73)))).mul(t).add(r)},mx_ramplr:(e,t,r=$u())=>i_(e,t,r,"x"),mx_ramptb:(e,t,r=$u())=>i_(e,t,r,"y"),mx_rgbtohsv:t_,mx_safepower:(e,t=1)=>(e=ji(e)).abs().pow(t).mul(e.sign()),mx_splitlr:(e,t,r,s=$u())=>n_(e,t,r,s,"x"),mx_splittb:(e,t,r,s=$u())=>n_(e,t,r,s,"y"),mx_srgb_texture_to_lin_rec709:r_,mx_transform_uv:(e=1,t=0,r=$u())=>r.mul(e).add(t),mx_worley_noise_float:(e=$u(),t=1)=>QT(e.convert("vec2|vec3"),t,qi(1)),mx_worley_noise_vec2:(e=$u(),t=1)=>ZT(e.convert("vec2|vec3"),t,qi(1)),mx_worley_noise_vec3:(e=$u(),t=1)=>JT(e.convert("vec2|vec3"),t,qi(1)),negate:oo,neutralToneMapping:Db,nodeArray:Ii,nodeImmutable:Ui,nodeObject:Li,nodeObjects:Di,nodeProxy:Vi,normalFlat:Kl,normalGeometry:ql,normalLocal:Xl,normalMap:Zd,normalView:Zl,normalViewGeometry:Yl,normalWorld:Jl,normalWorldGeometry:Ql,normalize:Ya,not:Ta,notEqual:pa,numWorkgroups:sx,objectDirection:yl,objectGroup:Yn,objectPosition:xl,objectRadius:vl,objectScale:Tl,objectViewPosition:_l,objectWorldMatrix:bl,oneMinus:uo,or:xa,orthographicDepthToViewZ:(e,t,r)=>t.sub(r).mul(e).sub(t),oscSawtooth:(e=ly)=>e.fract(),oscSine:(e=ly)=>e.add(.75).mul(2*Math.PI).sin().mul(.5).add(.5),oscSquare:(e=ly)=>e.fract().round(),oscTriangle:(e=ly)=>e.add(.5).fract().mul(2).sub(1).abs(),output:Dn,outputStruct:Qf,overlay:(...e)=>(console.warn('THREE.TSL: "overlay" has been renamed. Use "blendOverlay" instead.'),np(e)),overloadingFn:uy,parabola:ry,parallaxDirection:Kd,parallaxUV:(e,t)=>e.sub(Kd.mul(t)),parameter:(e,t)=>Li(new Wf(e,t)),pass:(e,t,r)=>Li(new Sb(Sb.COLOR,e,t,r)),passTexture:(e,t)=>Li(new vb(e,t)),pcurve:(e,t,r)=>Ao(da(Ao(e,t),oa(Ao(e,t),Ao(ua(1,e),r))),1/t),perspectiveDepthToViewZ:$h,pmremTexture:wm,pointShadow:lT,pointUV:Ky,pointWidth:Un,positionGeometry:Il,positionLocal:Vl,positionPrevious:Ul,positionView:Gl,positionViewDirection:zl,positionWorld:Ol,positionWorldDirection:kl,posterize:Tb,pow:Ao,pow2:Ro,pow3:Co,pow4:Mo,premultiplyAlpha:op,property:mn,radians:ka,rand:ko,range:ex,rangeFog:function(e,t,r){return console.warn('THREE.TSL: "rangeFog( color, near, far )" is deprecated. Use "fog( color, rangeFogFactor( near, far ) )" instead.'),Yb(e,Xb(t,r))},rangeFogFactor:Xb,reciprocal:po,reference:_d,referenceBuffer:vd,reflect:vo,reflectVector:pd,reflectView:cd,reflector:e=>Li(new Fy(e)),refract:Vo,refractVector:gd,refractView:hd,reinhardToneMapping:Ab,remap:Fu,remapClamp:Lu,renderGroup:Kn,renderOutput:Ou,rendererReference:yu,rotate:Wm,rotateUV:hy,roughness:xn,round:ho,rtt:Gy,sRGBTransferEOTF:uu,sRGBTransferOETF:lu,sample:e=>Li(new Wy(e)),sampler:e=>(!0===e.isNode?e:Zu(e)).convert("sampler"),samplerComparison:e=>(!0===e.isNode?e:Zu(e)).convert("samplerComparison"),saturate:Io,saturation:gb,screen:(...e)=>(console.warn('THREE.TSL: "screen" has been renamed. Use "blendScreen" instead.'),ip(e)),screenCoordinate:Rh,screenSize:Ah,screenUV:wh,scriptable:jb,scriptableValue:Gb,select:Xo,setCurrentStack:Gi,shaderStages:zs,shadow:Jx,shadowPositionWorld:Ax,shapeCircle:mT,sharedUniformGroup:qn,sheen:Nn,sheenRoughness:Sn,shiftLeft:wa,shiftRight:Aa,shininess:Ln,sign:no,sin:Za,sinc:(e,t)=>Za(Ia.mul(t.mul(e).sub(1))).div(Ia.mul(t.mul(e).sub(1))),skinning:lh,smoothstep:Uo,smoothstepElement:zo,specularColor:Bn,specularF90:Fn,spherizeUV:py,split:(e,t)=>Li(new Zs(Li(e),t)),spritesheetUV:yy,sqrt:ja,stack:qf,step:_o,stepElement:Ho,storage:ah,storageBarrier:()=>ux("storage").toStack(),storageObject:(e,t,r)=>(console.warn('THREE.TSL: "storageObject()" is deprecated. Use "storage().setPBO( true )" instead.'),ah(e,t,r).setPBO(!0)),storageTexture:sb,string:(e="")=>Li(new si(e,"string")),struct:(e,t=null)=>{const r=new Xf(e,t),s=(...t)=>{let s=null;if(t.length>0)if(t[0].isNode){s={};const r=Object.keys(e);for(let e=0;eux("texture").toStack(),textureBicubic:Tg,textureBicubicLevel:xg,textureCubeUV:Qg,textureLoad:Ju,textureSize:ju,textureStore:(e,t,r)=>{const s=sb(e,t,r);return null!==r&&s.toStack(),s},thickness:Gn,time:ly,timerDelta:(e=1)=>(console.warn('TSL: timerDelta() is deprecated. Use "deltaTime" instead.'),dy.mul(e)),timerGlobal:(e=1)=>(console.warn('TSL: timerGlobal() is deprecated. Use "time" instead.'),ly.mul(e)),timerLocal:(e=1)=>(console.warn('TSL: timerLocal() is deprecated. Use "time" instead.'),ly.mul(e)),toneMapping:xu,toneMappingExposure:Tu,toonOutlinePass:(t,r,s=new e(0,0,0),i=.003,n=1)=>Li(new Eb(t,r,Li(s),Li(i),Li(n))),transformDirection:Po,transformNormal:td,transformNormalToView:rd,transformedClearcoatNormalView:nd,transformedNormalView:sd,transformedNormalWorld:id,transmission:kn,transpose:fo,triNoise3D:ny,triplanarTexture:(...e)=>by(...e),triplanarTextures:by,trunc:go,uint:Xi,uniform:Zn,uniformArray:il,uniformCubeTexture:(e=md)=>yd(e),uniformGroup:jn,uniformTexture:(e=Ku)=>Zu(e),unpremultiplyAlpha:up,userData:(e,t,r)=>Li(new ob(e,t,r)),uv:$u,uvec2:Zi,uvec3:rn,uvec4:on,varying:au,varyingProperty:fn,vec2:Yi,vec3:en,vec4:nn,vectorComponents:Hs,velocity:hb,vertexColor:tp,vertexIndex:Wc,vertexStage:ou,vibrance:mb,viewZToLogarithmicDepth:Wh,viewZToOrthographicDepth:zh,viewZToPerspectiveDepth:Hh,viewport:Ch,viewportCoordinate:Ph,viewportDepthTexture:kh,viewportLinearDepth:Kh,viewportMipTexture:Vh,viewportResolution:Fh,viewportSafeUV:my,viewportSharedTexture:fp,viewportSize:Mh,viewportTexture:Ih,viewportUV:Bh,wgsl:(e,t)=>Vb(e,t,"wgsl"),wgslFn:(e,t)=>Ob(e,t,"wgsl"),workgroupArray:(e,t)=>Li(new dx("Workgroup",e,t)),workgroupBarrier:()=>ux("workgroup").toStack(),workgroupId:ix,workingToColorSpace:hu,xor:_a});const l_=new $f;class d_ extends cf{constructor(e,t){super(),this.renderer=e,this.nodes=t}update(e,t,r){const s=this.renderer,i=this.nodes.getBackgroundNode(e)||e.background;let n=!1;if(null===i)s._clearColor.getRGB(l_),l_.a=s._clearColor.a;else if(!0===i.isColor)i.getRGB(l_),l_.a=1,n=!0;else if(!0===i.isNode){const o=this.get(e),u=i;l_.copy(s._clearColor);let l=o.backgroundMesh;if(void 0===l){const c=Yo(nn(u).mul(eb),{getUV:()=>tb.mul(Ql),getTextureLevel:()=>Jy});let h=Hc;h=h.setZ(h.w);const p=new lp;function g(){i.removeEventListener("dispose",g),l.material.dispose(),l.geometry.dispose()}p.name="Background.material",p.side=S,p.depthTest=!1,p.depthWrite=!1,p.allowOverride=!1,p.fog=!1,p.lights=!1,p.vertexNode=h,p.colorNode=c,o.backgroundMeshNode=c,o.backgroundMesh=l=new X(new Oe(1,32,32),p),l.frustumCulled=!1,l.name="Background.mesh",l.onBeforeRender=function(e,t,r){this.matrixWorld.copyPosition(r.matrixWorld)},i.addEventListener("dispose",g)}const d=u.getCacheKey();o.backgroundCacheKey!==d&&(o.backgroundMeshNode.node=nn(u).mul(eb),o.backgroundMeshNode.needsUpdate=!0,l.material.needsUpdate=!0,o.backgroundCacheKey=d),t.unshift(l,l.geometry,l.material,0,0,null,null)}else console.error("THREE.Renderer: Unsupported background configuration.",i);const a=s.xr.getEnvironmentBlendMode();if("additive"===a?l_.set(0,0,0,1):"alpha-blend"===a&&l_.set(0,0,0,0),!0===s.autoClear||!0===n){const m=r.clearColorValue;m.r=l_.r,m.g=l_.g,m.b=l_.b,m.a=l_.a,!0!==s.backend.isWebGLBackend&&!0!==s.alpha||(m.r*=m.a,m.g*=m.a,m.b*=m.a),r.depthClearValue=s._clearDepth,r.stencilClearValue=s._clearStencil,r.clearColor=!0===s.autoClearColor,r.clearDepth=!0===s.autoClearDepth,r.clearStencil=!0===s.autoClearStencil}else r.clearColor=!1,r.clearDepth=!1,r.clearStencil=!1}}let c_=0;class h_{constructor(e="",t=[],r=0,s=[]){this.name=e,this.bindings=t,this.index=r,this.bindingsReference=s,this.id=c_++}}class p_{constructor(e,t,r,s,i,n,a,o,u,l=[]){this.vertexShader=e,this.fragmentShader=t,this.computeShader=r,this.transforms=l,this.nodeAttributes=s,this.bindings=i,this.updateNodes=n,this.updateBeforeNodes=a,this.updateAfterNodes=o,this.observer=u,this.usedTimes=0}createBindings(){const e=[];for(const t of this.bindings){if(!0!==t.bindings[0].groupNode.shared){const r=new h_(t.name,[],t.index,t);e.push(r);for(const e of t.bindings)r.bindings.push(e.clone())}else e.push(t)}return e}}class g_{constructor(e,t,r=null){this.isNodeAttribute=!0,this.name=e,this.type=t,this.node=r}}class m_{constructor(e,t,r){this.isNodeUniform=!0,this.name=e,this.type=t,this.node=r.getSelf()}get value(){return this.node.value}set value(e){this.node.value=e}get id(){return this.node.id}get groupNode(){return this.node.groupNode}}class f_{constructor(e,t,r=!1,s=null){this.isNodeVar=!0,this.name=e,this.type=t,this.readOnly=r,this.count=s}}class y_ extends f_{constructor(e,t,r=null,s=null){super(e,t),this.needsInterpolation=!1,this.isNodeVarying=!0,this.interpolationType=r,this.interpolationSampling=s}}class b_{constructor(e,t,r=""){this.name=e,this.type=t,this.code=r,Object.defineProperty(this,"isNodeCode",{value:!0})}}let x_=0;class T_{constructor(e=null){this.id=x_++,this.nodesData=new WeakMap,this.parent=e}getData(e){let t=this.nodesData.get(e);return void 0===t&&null!==this.parent&&(t=this.parent.getData(e)),t}setData(e,t){this.nodesData.set(e,t)}}class __{constructor(e,t){this.name=e,this.members=t,this.output=!1}}class v_{constructor(e,t){this.name=e,this.value=t,this.boundary=0,this.itemSize=0,this.offset=0}setValue(e){this.value=e}getValue(){return this.value}}class N_ extends v_{constructor(e,t=0){super(e,t),this.isNumberUniform=!0,this.boundary=4,this.itemSize=1}}class S_ extends v_{constructor(e,r=new t){super(e,r),this.isVector2Uniform=!0,this.boundary=8,this.itemSize=2}}class E_ extends v_{constructor(e,t=new r){super(e,t),this.isVector3Uniform=!0,this.boundary=16,this.itemSize=3}}class w_ extends v_{constructor(e,t=new s){super(e,t),this.isVector4Uniform=!0,this.boundary=16,this.itemSize=4}}class A_ extends v_{constructor(t,r=new e){super(t,r),this.isColorUniform=!0,this.boundary=16,this.itemSize=3}}class R_ extends v_{constructor(e,t=new i){super(e,t),this.isMatrix2Uniform=!0,this.boundary=8,this.itemSize=4}}class C_ extends v_{constructor(e,t=new n){super(e,t),this.isMatrix3Uniform=!0,this.boundary=48,this.itemSize=12}}class M_ extends v_{constructor(e,t=new a){super(e,t),this.isMatrix4Uniform=!0,this.boundary=64,this.itemSize=16}}class P_ extends N_{constructor(e){super(e.name,e.value),this.nodeUniform=e}getValue(){return this.nodeUniform.value}getType(){return this.nodeUniform.type}}class B_ extends S_{constructor(e){super(e.name,e.value),this.nodeUniform=e}getValue(){return this.nodeUniform.value}getType(){return this.nodeUniform.type}}class F_ extends E_{constructor(e){super(e.name,e.value),this.nodeUniform=e}getValue(){return this.nodeUniform.value}getType(){return this.nodeUniform.type}}class L_ extends w_{constructor(e){super(e.name,e.value),this.nodeUniform=e}getValue(){return this.nodeUniform.value}getType(){return this.nodeUniform.type}}class D_ extends A_{constructor(e){super(e.name,e.value),this.nodeUniform=e}getValue(){return this.nodeUniform.value}getType(){return this.nodeUniform.type}}class I_ extends R_{constructor(e){super(e.name,e.value),this.nodeUniform=e}getValue(){return this.nodeUniform.value}getType(){return this.nodeUniform.type}}class V_ extends C_{constructor(e){super(e.name,e.value),this.nodeUniform=e}getValue(){return this.nodeUniform.value}getType(){return this.nodeUniform.type}}class U_ extends M_{constructor(e){super(e.name,e.value),this.nodeUniform=e}getValue(){return this.nodeUniform.value}getType(){return this.nodeUniform.type}}const O_=new WeakMap,k_=new Map([[Int8Array,"int"],[Int16Array,"int"],[Int32Array,"int"],[Uint8Array,"uint"],[Uint16Array,"uint"],[Uint32Array,"uint"],[Float32Array,"float"]]),G_=e=>/e/g.test(e)?String(e).replace(/\+/g,""):(e=Number(e))+(e%1?"":".0");class z_{constructor(e,t,r){this.object=e,this.material=e&&e.material||null,this.geometry=e&&e.geometry||null,this.renderer=t,this.parser=r,this.scene=null,this.camera=null,this.nodes=[],this.sequentialNodes=[],this.updateNodes=[],this.updateBeforeNodes=[],this.updateAfterNodes=[],this.hashNodes={},this.observer=null,this.lightsNode=null,this.environmentNode=null,this.fogNode=null,this.clippingContext=null,this.vertexShader=null,this.fragmentShader=null,this.computeShader=null,this.flowNodes={vertex:[],fragment:[],compute:[]},this.flowCode={vertex:"",fragment:"",compute:""},this.uniforms={vertex:[],fragment:[],compute:[],index:0},this.structs={vertex:[],fragment:[],compute:[],index:0},this.bindings={vertex:{},fragment:{},compute:{}},this.bindingsIndexes={},this.bindGroups=null,this.attributes=[],this.bufferAttributes=[],this.varyings=[],this.codes={},this.vars={},this.declarations={},this.flow={code:""},this.chaining=[],this.stack=qf(),this.stacks=[],this.tab="\t",this.currentFunctionNode=null,this.context={material:this.material},this.cache=new T_,this.globalCache=this.cache,this.flowsData=new WeakMap,this.shaderStage=null,this.buildStage=null,this.subBuildLayers=[],this.currentStack=null,this.subBuildFn=null}getBindGroupsCache(){let e=O_.get(this.renderer);return void 0===e&&(e=new af,O_.set(this.renderer,e)),e}createRenderTarget(e,t,r){return new ue(e,t,r)}createCubeRenderTarget(e,t){return new Np(e,t)}includes(e){return this.nodes.includes(e)}getOutputStructName(){}_getBindGroup(e,t){const r=this.getBindGroupsCache(),s=[];let i,n=!0;for(const e of t)s.push(e),n=n&&!0!==e.groupNode.shared;return n?(i=r.get(s),void 0===i&&(i=new h_(e,s,this.bindingsIndexes[e].group,s),r.set(s,i))):i=new h_(e,s,this.bindingsIndexes[e].group,s),i}getBindGroupArray(e,t){const r=this.bindings[t];let s=r[e];return void 0===s&&(void 0===this.bindingsIndexes[e]&&(this.bindingsIndexes[e]={binding:0,group:Object.keys(this.bindingsIndexes).length}),r[e]=s=[]),s}getBindings(){let e=this.bindGroups;if(null===e){const t={},r=this.bindings;for(const e of zs)for(const s in r[e]){const i=r[e][s];(t[s]||(t[s]=[])).push(...i)}e=[];for(const r in t){const s=t[r],i=this._getBindGroup(r,s);e.push(i)}this.bindGroups=e}return e}sortBindingGroups(){const e=this.getBindings();e.sort((e,t)=>e.bindings[0].groupNode.order-t.bindings[0].groupNode.order);for(let t=0;t=0?`${Math.round(n)}u`:"0u";if("bool"===i)return n?"true":"false";if("color"===i)return`${this.getType("vec3")}( ${G_(n.r)}, ${G_(n.g)}, ${G_(n.b)} )`;const a=this.getTypeLength(i),o=this.getComponentType(i),u=e=>this.generateConst(o,e);if(2===a)return`${this.getType(i)}( ${u(n.x)}, ${u(n.y)} )`;if(3===a)return`${this.getType(i)}( ${u(n.x)}, ${u(n.y)}, ${u(n.z)} )`;if(4===a&&"mat2"!==i)return`${this.getType(i)}( ${u(n.x)}, ${u(n.y)}, ${u(n.z)}, ${u(n.w)} )`;if(a>=4&&n&&(n.isMatrix2||n.isMatrix3||n.isMatrix4))return`${this.getType(i)}( ${n.elements.map(u).join(", ")} )`;if(a>4)return`${this.getType(i)}()`;throw new Error(`NodeBuilder: Type '${i}' not found in generate constant attempt.`)}getType(e){return"color"===e?"vec3":e}hasGeometryAttribute(e){return this.geometry&&void 0!==this.geometry.getAttribute(e)}getAttribute(e,t){const r=this.attributes;for(const t of r)if(t.name===e)return t;const s=new g_(e,t);return this.registerDeclaration(s),r.push(s),s}getPropertyName(e){return e.name}isVector(e){return/vec\d/.test(e)}isMatrix(e){return/mat\d/.test(e)}isReference(e){return"void"===e||"property"===e||"sampler"===e||"samplerComparison"===e||"texture"===e||"cubeTexture"===e||"storageTexture"===e||"depthTexture"===e||"texture3D"===e}needsToWorkingColorSpace(){return!1}getComponentTypeFromTexture(e){const t=e.type;if(e.isDataTexture){if(t===_)return"int";if(t===T)return"uint"}return"float"}getElementType(e){return"mat2"===e?"vec2":"mat3"===e?"vec3":"mat4"===e?"vec4":this.getComponentType(e)}getComponentType(e){if("float"===(e=this.getVectorType(e))||"bool"===e||"int"===e||"uint"===e)return e;const t=/(b|i|u|)(vec|mat)([2-4])/.exec(e);return null===t?null:"b"===t[1]?"bool":"i"===t[1]?"int":"u"===t[1]?"uint":"float"}getVectorType(e){return"color"===e?"vec3":"texture"===e||"cubeTexture"===e||"storageTexture"===e||"texture3D"===e?"vec4":e}getTypeFromLength(e,t="float"){if(1===e)return t;let r=Es(e);const s="float"===t?"":t[0];return!0===/mat2/.test(t)&&(r=r.replace("vec","mat")),s+r}getTypeFromArray(e){return k_.get(e.constructor)}isInteger(e){return/int|uint|(i|u)vec/.test(e)}getTypeFromAttribute(e){let t=e;e.isInterleavedBufferAttribute&&(t=e.data);const r=t.array,s=e.itemSize,i=e.normalized;let n;return e instanceof ze||!0===i||(n=this.getTypeFromArray(r)),this.getTypeFromLength(s,n)}getTypeLength(e){const t=this.getVectorType(e),r=/vec([2-4])/.exec(t);return null!==r?Number(r[1]):"float"===t||"bool"===t||"int"===t||"uint"===t?1:!0===/mat2/.test(e)?4:!0===/mat3/.test(e)?9:!0===/mat4/.test(e)?16:0}getVectorFromMatrix(e){return e.replace("mat","vec")}changeComponentType(e,t){return this.getTypeFromLength(this.getTypeLength(e),t)}getIntegerType(e){const t=this.getComponentType(e);return"int"===t||"uint"===t?e:this.changeComponentType(e,"int")}addStack(){return this.stack=qf(this.stack),this.stacks.push(zi()||this.stack),Gi(this.stack),this.stack}removeStack(){const e=this.stack;return this.stack=e.parent,Gi(this.stacks.pop()),e}getDataFromNode(e,t=this.shaderStage,r=null){let s=(r=null===r?e.isGlobal(this)?this.globalCache:this.cache:r).getData(e);void 0===s&&(s={},r.setData(e,s)),void 0===s[t]&&(s[t]={});let i=s[t];const n=s.any?s.any.subBuilds:null,a=this.getClosestSubBuild(n);return a&&(void 0===i.subBuildsCache&&(i.subBuildsCache={}),i=i.subBuildsCache[a]||(i.subBuildsCache[a]={}),i.subBuilds=n),i}getNodeProperties(e,t="any"){const r=this.getDataFromNode(e,t);return r.properties||(r.properties={outputNode:null})}getBufferAttributeFromNode(e,t){const r=this.getDataFromNode(e);let s=r.bufferAttribute;if(void 0===s){const i=this.uniforms.index++;s=new g_("nodeAttribute"+i,t,e),this.bufferAttributes.push(s),r.bufferAttribute=s}return s}getStructTypeFromNode(e,t,r=null,s=this.shaderStage){const i=this.getDataFromNode(e,s,this.globalCache);let n=i.structType;if(void 0===n){const e=this.structs.index++;null===r&&(r="StructType"+e),n=new __(r,t),this.structs[s].push(n),i.structType=n}return n}getOutputStructTypeFromNode(e,t){const r=this.getStructTypeFromNode(e,t,"OutputType","fragment");return r.output=!0,r}getUniformFromNode(e,t,r=this.shaderStage,s=null){const i=this.getDataFromNode(e,r,this.globalCache);let n=i.uniform;if(void 0===n){const a=this.uniforms.index++;n=new m_(s||"nodeUniform"+a,t,e),this.uniforms[r].push(n),this.registerDeclaration(n),i.uniform=n}return n}getArrayCount(e){let t=null;return e.isArrayNode?t=e.count:e.isVarNode&&e.node.isArrayNode&&(t=e.node.count),t}getVarFromNode(e,t=null,r=e.getNodeType(this),s=this.shaderStage,i=!1){const n=this.getDataFromNode(e,s),a=this.getSubBuildProperty("variable",n.subBuilds);let o=n[a];if(void 0===o){const u=i?"_const":"_var",l=this.vars[s]||(this.vars[s]=[]),d=this.vars[u]||(this.vars[u]=0);null===t&&(t=(i?"nodeConst":"nodeVar")+d,this.vars[u]++),"variable"!==a&&(t=this.getSubBuildProperty(t,n.subBuilds));const c=this.getArrayCount(e);o=new f_(t,r,i,c),i||l.push(o),this.registerDeclaration(o),n[a]=o}return o}isDeterministic(e){if(e.isMathNode)return this.isDeterministic(e.aNode)&&(!e.bNode||this.isDeterministic(e.bNode))&&(!e.cNode||this.isDeterministic(e.cNode));if(e.isOperatorNode)return this.isDeterministic(e.aNode)&&(!e.bNode||this.isDeterministic(e.bNode));if(e.isArrayNode){if(null!==e.values)for(const t of e.values)if(!this.isDeterministic(t))return!1;return!0}return!!e.isConstNode}getVaryingFromNode(e,t=null,r=e.getNodeType(this),s=null,i=null){const n=this.getDataFromNode(e,"any"),a=this.getSubBuildProperty("varying",n.subBuilds);let o=n[a];if(void 0===o){const e=this.varyings,u=e.length;null===t&&(t="nodeVarying"+u),"varying"!==a&&(t=this.getSubBuildProperty(t,n.subBuilds)),o=new y_(t,r,s,i),e.push(o),this.registerDeclaration(o),n[a]=o}return o}registerDeclaration(e){const t=this.shaderStage,r=this.declarations[t]||(this.declarations[t]={}),s=this.getPropertyName(e);let i=1,n=s;for(;void 0!==r[n];)n=s+"_"+i++;i>1&&(e.name=n,console.warn(`THREE.TSL: Declaration name '${s}' of '${e.type}' already in use. Renamed to '${n}'.`)),r[n]=e}getCodeFromNode(e,t,r=this.shaderStage){const s=this.getDataFromNode(e);let i=s.code;if(void 0===i){const e=this.codes[r]||(this.codes[r]=[]),n=e.length;i=new b_("nodeCode"+n,t),e.push(i),s.code=i}return i}addFlowCodeHierarchy(e,t){const{flowCodes:r,flowCodeBlock:s}=this.getDataFromNode(e);let i=!0,n=t;for(;n;){if(!0===s.get(n)){i=!1;break}n=this.getDataFromNode(n).parentNodeBlock}if(i)for(const e of r)this.addLineFlowCode(e)}addLineFlowCodeBlock(e,t,r){const s=this.getDataFromNode(e),i=s.flowCodes||(s.flowCodes=[]),n=s.flowCodeBlock||(s.flowCodeBlock=new WeakMap);i.push(t),n.set(r,!0)}addLineFlowCode(e,t=null){return""===e||(null!==t&&this.context.nodeBlock&&this.addLineFlowCodeBlock(t,e,this.context.nodeBlock),e=this.tab+e,/;\s*$/.test(e)||(e+=";\n"),this.flow.code+=e),this}addFlowCode(e){return this.flow.code+=e,this}addFlowTab(){return this.tab+="\t",this}removeFlowTab(){return this.tab=this.tab.slice(0,-1),this}getFlowData(e){return this.flowsData.get(e)}flowNode(e){const t=e.getNodeType(this),r=this.flowChildNode(e,t);return this.flowsData.set(e,r),r}addInclude(e){null!==this.currentFunctionNode&&this.currentFunctionNode.includes.push(e)}buildFunctionNode(e){const t=new Ub,r=this.currentFunctionNode;return this.currentFunctionNode=t,t.code=this.buildFunctionCode(e),this.currentFunctionNode=r,t}flowShaderNode(e){const t=e.layout,r={[Symbol.iterator](){let e=0;const t=Object.values(this);return{next:()=>({value:t[e],done:e++>=t.length})}}};for(const e of t.inputs)r[e.name]=new Wf(e.type,e.name);e.layout=null;const s=e.call(r),i=this.flowStagesNode(s,t.type);return e.layout=t,i}flowStagesNode(e,t=null){const r=this.flow,s=this.vars,i=this.declarations,n=this.cache,a=this.buildStage,o=this.stack,u={code:""};this.flow=u,this.vars={},this.declarations={},this.cache=new T_,this.stack=qf();for(const r of Gs)this.setBuildStage(r),u.result=e.build(this,t);return u.vars=this.getVars(this.shaderStage),this.flow=r,this.vars=s,this.declarations=i,this.cache=n,this.stack=o,this.setBuildStage(a),u}getFunctionOperator(){return null}buildFunctionCode(){console.warn("Abstract function.")}flowChildNode(e,t=null){const r=this.flow,s={code:""};return this.flow=s,s.result=e.build(this,t),this.flow=r,s}flowNodeFromShaderStage(e,t,r=null,s=null){const i=this.tab,n=this.cache,a=this.shaderStage,o=this.context;this.setShaderStage(e);const u={...this.context};delete u.nodeBlock,this.cache=this.globalCache,this.tab="\t",this.context=u;let l=null;if("generate"===this.buildStage){const i=this.flowChildNode(t,r);null!==s&&(i.code+=`${this.tab+s} = ${i.result};\n`),this.flowCode[e]=this.flowCode[e]+i.code,l=i}else l=t.build(this);return this.setShaderStage(a),this.cache=n,this.tab=i,this.context=o,l}getAttributesArray(){return this.attributes.concat(this.bufferAttributes)}getAttributes(){console.warn("Abstract function.")}getVaryings(){console.warn("Abstract function.")}getVar(e,t,r=null){return`${null!==r?this.generateArrayDeclaration(e,r):this.getType(e)} ${t}`}getVars(e){let t="";const r=this.vars[e];if(void 0!==r)for(const e of r)t+=`${this.getVar(e.type,e.name)}; `;return t}getUniforms(){console.warn("Abstract function.")}getCodes(e){const t=this.codes[e];let r="";if(void 0!==t)for(const e of t)r+=e.code+"\n";return r}getHash(){return this.vertexShader+this.fragmentShader+this.computeShader}setShaderStage(e){this.shaderStage=e}getShaderStage(){return this.shaderStage}setBuildStage(e){this.buildStage=e}getBuildStage(){return this.buildStage}buildCode(){console.warn("Abstract function.")}get subBuild(){return this.subBuildLayers[this.subBuildLayers.length-1]||null}addSubBuild(e){this.subBuildLayers.push(e)}removeSubBuild(){return this.subBuildLayers.pop()}getClosestSubBuild(e){let t;if(t=e&&e.isNode?e.isShaderCallNodeInternal?e.shaderNode.subBuilds:e.isStackNode?[e.subBuild]:this.getDataFromNode(e,"any").subBuilds:e instanceof Set?[...e]:e,!t)return null;const r=this.subBuildLayers;for(let e=t.length-1;e>=0;e--){const s=t[e];if(r.includes(s))return s}return null}getSubBuildOutput(e){return this.getSubBuildProperty("outputNode",e)}getSubBuildProperty(e="",t=null){let r,s;return r=null!==t?this.getClosestSubBuild(t):this.subBuildFn,s=r?e?r+"_"+e:r:e,s}build(){const{object:e,material:t,renderer:r}=this;if(null!==t){let e=r.library.fromMaterial(t);null===e&&(console.error(`NodeMaterial: Material "${t.type}" is not compatible.`),e=new lp),e.build(this)}else this.addFlow("compute",e);for(const e of Gs){this.setBuildStage(e),this.context.vertex&&this.context.vertex.isNode&&this.flowNodeFromShaderStage("vertex",this.context.vertex);for(const t of zs){this.setShaderStage(t);const r=this.flowNodes[t];for(const t of r)"generate"===e?this.flowNode(t):t.build(this)}}return this.setBuildStage(null),this.setShaderStage(null),this.buildCode(),this.buildUpdateNodes(),this}getNodeUniform(e,t){if("float"===t||"int"===t||"uint"===t)return new P_(e);if("vec2"===t||"ivec2"===t||"uvec2"===t)return new B_(e);if("vec3"===t||"ivec3"===t||"uvec3"===t)return new F_(e);if("vec4"===t||"ivec4"===t||"uvec4"===t)return new L_(e);if("color"===t)return new D_(e);if("mat2"===t)return new I_(e);if("mat3"===t)return new V_(e);if("mat4"===t)return new U_(e);throw new Error(`Uniform "${t}" not declared.`)}format(e,t,r){if((t=this.getVectorType(t))===(r=this.getVectorType(r))||null===r||this.isReference(r))return e;const s=this.getTypeLength(t),i=this.getTypeLength(r);return 16===s&&9===i?`${this.getType(r)}( ${e}[ 0 ].xyz, ${e}[ 1 ].xyz, ${e}[ 2 ].xyz )`:9===s&&4===i?`${this.getType(r)}( ${e}[ 0 ].xy, ${e}[ 1 ].xy )`:s>4||i>4||0===i?e:s===i?`${this.getType(r)}( ${e} )`:s>i?(e="bool"===r?`all( ${e} )`:`${e}.${"xyz".slice(0,i)}`,this.format(e,this.getTypeFromLength(i,this.getComponentType(t)),r)):4===i&&s>1?`${this.getType(r)}( ${this.format(e,t,"vec3")}, 1.0 )`:2===s?`${this.getType(r)}( ${this.format(e,t,"vec2")}, 0.0 )`:(1===s&&i>1&&t!==this.getComponentType(r)&&(e=`${this.getType(this.getComponentType(r))}( ${e} )`),`${this.getType(r)}( ${e} )`)}getSignature(){return`// Three.js r${He} - Node System\n`}*[Symbol.iterator](){}}class H_{constructor(){this.time=0,this.deltaTime=0,this.frameId=0,this.renderId=0,this.updateMap=new WeakMap,this.updateBeforeMap=new WeakMap,this.updateAfterMap=new WeakMap,this.renderer=null,this.material=null,this.camera=null,this.object=null,this.scene=null}_getMaps(e,t){let r=e.get(t);return void 0===r&&(r={renderMap:new WeakMap,frameMap:new WeakMap},e.set(t,r)),r}updateBeforeNode(e){const t=e.getUpdateBeforeType(),r=e.updateReference(this);if(t===Vs.FRAME){const{frameMap:t}=this._getMaps(this.updateBeforeMap,r);t.get(r)!==this.frameId&&!1!==e.updateBefore(this)&&t.set(r,this.frameId)}else if(t===Vs.RENDER){const{renderMap:t}=this._getMaps(this.updateBeforeMap,r);t.get(r)!==this.renderId&&!1!==e.updateBefore(this)&&t.set(r,this.renderId)}else t===Vs.OBJECT&&e.updateBefore(this)}updateAfterNode(e){const t=e.getUpdateAfterType(),r=e.updateReference(this);if(t===Vs.FRAME){const{frameMap:t}=this._getMaps(this.updateAfterMap,r);t.get(r)!==this.frameId&&!1!==e.updateAfter(this)&&t.set(r,this.frameId)}else if(t===Vs.RENDER){const{renderMap:t}=this._getMaps(this.updateAfterMap,r);t.get(r)!==this.renderId&&!1!==e.updateAfter(this)&&t.set(r,this.renderId)}else t===Vs.OBJECT&&e.updateAfter(this)}updateNode(e){const t=e.getUpdateType(),r=e.updateReference(this);if(t===Vs.FRAME){const{frameMap:t}=this._getMaps(this.updateMap,r);t.get(r)!==this.frameId&&!1!==e.update(this)&&t.set(r,this.frameId)}else if(t===Vs.RENDER){const{renderMap:t}=this._getMaps(this.updateMap,r);t.get(r)!==this.renderId&&!1!==e.update(this)&&t.set(r,this.renderId)}else t===Vs.OBJECT&&e.update(this)}update(){this.frameId++,void 0===this.lastTime&&(this.lastTime=performance.now()),this.deltaTime=(performance.now()-this.lastTime)/1e3,this.lastTime=performance.now(),this.time+=this.deltaTime}}class $_{constructor(e,t,r=null,s="",i=!1){this.type=e,this.name=t,this.count=r,this.qualifier=s,this.isConst=i}}$_.isNodeFunctionInput=!0;class W_ extends dT{static get type(){return"DirectionalLightNode"}constructor(e=null){super(e)}setupDirect(){const e=this.colorNode;return{lightDirection:_x(this.light),lightColor:e}}}const j_=new a,q_=new a;let X_=null;class K_ extends dT{static get type(){return"RectAreaLightNode"}constructor(e=null){super(e),this.halfHeight=Zn(new r).setGroup(Kn),this.halfWidth=Zn(new r).setGroup(Kn),this.updateType=Vs.RENDER}update(e){super.update(e);const{light:t}=this,r=e.camera.matrixWorldInverse;q_.identity(),j_.copy(t.matrixWorld),j_.premultiply(r),q_.extractRotation(j_),this.halfWidth.value.set(.5*t.width,0,0),this.halfHeight.value.set(0,.5*t.height,0),this.halfWidth.value.applyMatrix4(q_),this.halfHeight.value.applyMatrix4(q_)}setupDirectRectArea(e){let t,r;e.isAvailable("float32Filterable")?(t=Zu(X_.LTC_FLOAT_1),r=Zu(X_.LTC_FLOAT_2)):(t=Zu(X_.LTC_HALF_1),r=Zu(X_.LTC_HALF_2));const{colorNode:s,light:i}=this;return{lightColor:s,lightPosition:Tx(i),halfWidth:this.halfWidth,halfHeight:this.halfHeight,ltc_1:t,ltc_2:r}}static setLTC(e){X_=e}}class Y_ extends dT{static get type(){return"SpotLightNode"}constructor(e=null){super(e),this.coneCosNode=Zn(0).setGroup(Kn),this.penumbraCosNode=Zn(0).setGroup(Kn),this.cutoffDistanceNode=Zn(0).setGroup(Kn),this.decayExponentNode=Zn(0).setGroup(Kn),this.colorNode=Zn(this.color).setGroup(Kn)}update(e){super.update(e);const{light:t}=this;this.coneCosNode.value=Math.cos(t.angle),this.penumbraCosNode.value=Math.cos(t.angle*(1-t.penumbra)),this.cutoffDistanceNode.value=t.distance,this.decayExponentNode.value=t.decay}getSpotAttenuation(e,t){const{coneCosNode:r,penumbraCosNode:s}=this;return Uo(r,s,t)}getLightCoord(e){const t=e.getNodeProperties(this);let r=t.projectionUV;return void 0===r&&(r=yx(this.light,e.context.positionWorld),t.projectionUV=r),r}setupDirect(e){const{colorNode:t,cutoffDistanceNode:r,decayExponentNode:s,light:i}=this,n=this.getLightVector(e),a=n.normalize(),o=a.dot(_x(i)),u=this.getSpotAttenuation(e,o),l=n.length(),d=cT({lightDistance:l,cutoffDistance:r,decayExponent:s});let c,h,p=t.mul(u).mul(d);if(i.colorNode?(h=this.getLightCoord(e),c=i.colorNode(h)):i.map&&(h=this.getLightCoord(e),c=Zu(i.map,h.xy).onRenderUpdate(()=>i.map)),c){p=h.mul(2).sub(1).abs().lessThan(1).all().select(p.mul(c),p)}return{lightColor:p,lightDirection:a}}}class Q_ extends Y_{static get type(){return"IESSpotLightNode"}getSpotAttenuation(e,t){const r=this.light.iesMap;let s=null;if(r&&!0===r.isTexture){const e=t.acos().mul(1/Math.PI);s=Zu(r,Yi(e,0),0).r}else s=super.getSpotAttenuation(t);return s}}const Z_=ki(([e,t])=>{const r=e.abs().sub(t);return ao(To(r,0)).add(xo(To(r.x,r.y),0))});class J_ extends Y_{static get type(){return"ProjectorLightNode"}update(e){super.update(e);const t=this.light;if(this.penumbraCosNode.value=Math.min(Math.cos(t.angle*(1-t.penumbra)),.99999),null===t.aspect){let e=1;null!==t.map&&(e=t.map.width/t.map.height),t.shadow.aspect=e}else t.shadow.aspect=t.aspect}getSpotAttenuation(e){const t=this.penumbraCosNode,r=this.getLightCoord(e),s=r.xyz.div(r.w),i=Z_(s.xy.sub(Yi(.5)),Yi(.5)),n=da(-1,ua(1,ro(t)).sub(1));return Io(i.mul(-2).mul(n))}}class ev extends dT{static get type(){return"AmbientLightNode"}constructor(e=null){super(e)}setup({context:e}){e.irradiance.addAssign(this.colorNode)}}class tv extends dT{static get type(){return"HemisphereLightNode"}constructor(t=null){super(t),this.lightPositionNode=bx(t),this.lightDirectionNode=this.lightPositionNode.normalize(),this.groundColorNode=Zn(new e).setGroup(Kn)}update(e){const{light:t}=this;super.update(e),this.lightPositionNode.object3d=t,this.groundColorNode.value.copy(t.groundColor).multiplyScalar(t.intensity)}setup(e){const{colorNode:t,groundColorNode:r,lightDirectionNode:s}=this,i=Jl.dot(s).mul(.5).add(.5),n=Lo(r,t,i);e.context.irradiance.addAssign(n)}}class rv extends dT{static get type(){return"LightProbeNode"}constructor(e=null){super(e);const t=[];for(let e=0;e<9;e++)t.push(new r);this.lightProbe=il(t)}update(e){const{light:t}=this;super.update(e);for(let e=0;e<9;e++)this.lightProbe.array[e].copy(t.sh.coefficients[e]).multiplyScalar(t.intensity)}setup(e){const t=o_(Jl,this.lightProbe);e.context.irradiance.addAssign(t)}}class sv{parseFunction(){console.warn("Abstract function.")}}class iv{constructor(e,t,r="",s=""){this.type=e,this.inputs=t,this.name=r,this.precision=s}getCode(){console.warn("Abstract function.")}}iv.isNodeFunction=!0;const nv=/^\s*(highp|mediump|lowp)?\s*([a-z_0-9]+)\s*([a-z_0-9]+)?\s*\(([\s\S]*?)\)/i,av=/[a-z_0-9]+/gi,ov="#pragma main";class uv extends iv{constructor(e){const{type:t,inputs:r,name:s,precision:i,inputsCode:n,blockCode:a,headerCode:o}=(e=>{const t=(e=e.trim()).indexOf(ov),r=-1!==t?e.slice(t+12):e,s=r.match(nv);if(null!==s&&5===s.length){const i=s[4],n=[];let a=null;for(;null!==(a=av.exec(i));)n.push(a);const o=[];let u=0;for(;u0||e.backgroundBlurriness>0&&0===t.backgroundBlurriness;if(t.background!==r||s){const i=this.getCacheNode("background",r,()=>{if(!0===r.isCubeTexture||r.mapping===Z||r.mapping===J||r.mapping===he){if(e.backgroundBlurriness>0||r.mapping===he)return wm(r);{let e;return e=!0===r.isCubeTexture?bd(r):Zu(r),Rp(e)}}if(!0===r.isTexture)return Zu(r,wh.flipY()).setUpdateMatrix(!0);!0!==r.isColor&&console.error("WebGPUNodes: Unsupported background configuration.",r)},s);t.backgroundNode=i,t.background=r,t.backgroundBlurriness=e.backgroundBlurriness}}else t.backgroundNode&&(delete t.backgroundNode,delete t.background)}getCacheNode(e,t,r,s=!1){const i=this.cacheLib[e]||(this.cacheLib[e]=new WeakMap);let n=i.get(t);return(void 0===n||s)&&(n=r(),i.set(t,n)),n}updateFog(e){const t=this.get(e),r=e.fog;if(r){if(t.fog!==r){const e=this.getCacheNode("fog",r,()=>{if(r.isFogExp2){const e=_d("color","color",r).setGroup(Kn),t=_d("density","float",r).setGroup(Kn);return Yb(e,Kb(t))}if(r.isFog){const e=_d("color","color",r).setGroup(Kn),t=_d("near","float",r).setGroup(Kn),s=_d("far","float",r).setGroup(Kn);return Yb(e,Xb(t,s))}console.error("THREE.Renderer: Unsupported fog configuration.",r)});t.fogNode=e,t.fog=r}}else delete t.fogNode,delete t.fog}updateEnvironment(e){const t=this.get(e),r=e.environment;if(r){if(t.environment!==r){const e=this.getCacheNode("environment",r,()=>!0===r.isCubeTexture?bd(r):!0===r.isTexture?Zu(r):void console.error("Nodes: Unsupported environment configuration.",r));t.environmentNode=e,t.environment=r}}else t.environmentNode&&(delete t.environmentNode,delete t.environment)}getNodeFrame(e=this.renderer,t=null,r=null,s=null,i=null){const n=this.nodeFrame;return n.renderer=e,n.scene=t,n.object=r,n.camera=s,n.material=i,n}getNodeFrameForRender(e){return this.getNodeFrame(e.renderer,e.scene,e.object,e.camera,e.material)}getOutputCacheKey(){const e=this.renderer;return e.toneMapping+","+e.currentColorSpace+","+e.xr.isPresenting}hasOutputChange(e){return dv.get(e)!==this.getOutputCacheKey()}getOutputNode(e){const t=this.renderer,r=this.getOutputCacheKey(),s=e.isArrayTexture?ab(e,en(wh,nl("gl_ViewID_OVR"))).renderOutput(t.toneMapping,t.currentColorSpace):Zu(e,wh).renderOutput(t.toneMapping,t.currentColorSpace);return dv.set(e,r),s}updateBefore(e){const t=e.getNodeBuilderState();for(const r of t.updateBeforeNodes)this.getNodeFrameForRender(e).updateBeforeNode(r)}updateAfter(e){const t=e.getNodeBuilderState();for(const r of t.updateAfterNodes)this.getNodeFrameForRender(e).updateAfterNode(r)}updateForCompute(e){const t=this.getNodeFrame(),r=this.getForCompute(e);for(const e of r.updateNodes)t.updateNode(e)}updateForRender(e){const t=this.getNodeFrameForRender(e),r=e.getNodeBuilderState();for(const e of r.updateNodes)t.updateNode(e)}needsRefresh(e){const t=this.getNodeFrameForRender(e);return e.getMonitor().needsRefresh(e,t)}dispose(){super.dispose(),this.nodeFrame=new H_,this.nodeBuilderCache=new Map,this.cacheLib={}}}const gv=new Me;class mv{constructor(e=null){this.version=0,this.clipIntersection=null,this.cacheKey="",this.shadowPass=!1,this.viewNormalMatrix=new n,this.clippingGroupContexts=new WeakMap,this.intersectionPlanes=[],this.unionPlanes=[],this.parentVersion=null,null!==e&&(this.viewNormalMatrix=e.viewNormalMatrix,this.clippingGroupContexts=e.clippingGroupContexts,this.shadowPass=e.shadowPass,this.viewMatrix=e.viewMatrix)}projectPlanes(e,t,r){const s=e.length;for(let i=0;i0,alpha:!0,depth:t.depth,stencil:t.stencil,framebufferScaleFactor:this.getFramebufferScaleFactor()},i=new XRWebGLLayer(e,s,r);this._glBaseLayer=i,e.updateRenderState({baseLayer:i}),t.setPixelRatio(1),t._setXRLayerSize(i.framebufferWidth,i.framebufferHeight),this._xrRenderTarget=new Nv(i.framebufferWidth,i.framebufferHeight,{format:de,type:Ce,colorSpace:t.outputColorSpace,stencilBuffer:t.stencil,resolveDepthBuffer:!1===i.ignoreDepthValues,resolveStencilBuffer:!1===i.ignoreDepthValues}),this._xrRenderTarget._isOpaqueFramebuffer=!0,this._referenceSpace=await e.requestReferenceSpace(this.getReferenceSpaceType())}this.setFoveation(this.getFoveation()),t._animation.setAnimationLoop(this._onAnimationFrame),t._animation.setContext(e),t._animation.start(),this.isPresenting=!0,this.dispatchEvent({type:"sessionstart"})}}updateCamera(e){const t=this._session;if(null===t)return;const r=e.near,s=e.far,i=this._cameraXR,n=this._cameraL,a=this._cameraR;i.near=a.near=n.near=r,i.far=a.far=n.far=s,i.isMultiViewCamera=this._useMultiview,this._currentDepthNear===i.near&&this._currentDepthFar===i.far||(t.updateRenderState({depthNear:i.near,depthFar:i.far}),this._currentDepthNear=i.near,this._currentDepthFar=i.far),n.layers.mask=2|e.layers.mask,a.layers.mask=4|e.layers.mask,i.layers.mask=n.layers.mask|a.layers.mask;const o=e.parent,u=i.cameras;Av(i,o);for(let e=0;e=0&&(r[n]=null,t[n].disconnect(i))}for(let s=0;s=r.length){r.push(i),n=e;break}if(null===r[e]){r[e]=i,n=e;break}}if(-1===n)break}const a=t[n];a&&a.connect(i)}}function Pv(e){return"quad"===e.type?this._glBinding.createQuadLayer({transform:new XRRigidTransform(e.translation,e.quaternion),width:e.width/2,height:e.height/2,space:this._referenceSpace,viewPixelWidth:e.pixelwidth,viewPixelHeight:e.pixelheight,clearOnAccess:!1}):this._glBinding.createCylinderLayer({transform:new XRRigidTransform(e.translation,e.quaternion),radius:e.radius,centralAngle:e.centralAngle,aspectRatio:e.aspectRatio,space:this._referenceSpace,viewPixelWidth:e.pixelwidth,viewPixelHeight:e.pixelheight,clearOnAccess:!1})}function Bv(e,t){if(void 0===t)return;const r=this._cameraXR,i=this._renderer,n=i.backend,a=this._glBaseLayer,o=this.getReferenceSpace(),u=t.getViewerPose(o);if(this._xrFrame=t,null!==u){const e=u.views;null!==this._glBaseLayer&&n.setXRTarget(a.framebuffer);let t=!1;e.length!==r.cameras.length&&(r.cameras.length=0,t=!0);for(let i=0;i{await this.compileAsync(e,t);const s=this._renderLists.get(e,t),i=this._renderContexts.get(e,t,this._renderTarget),n=e.overrideMaterial||r.material,a=this._objects.get(r,n,e,t,s.lightsNode,i,i.clippingContext),{fragmentShader:o,vertexShader:u}=a.getNodeBuilderState();return{fragmentShader:o,vertexShader:u}}}}async init(){if(this._initialized)throw new Error("Renderer: Backend has already been initialized.");return null!==this._initPromise||(this._initPromise=new Promise(async(e,t)=>{let r=this.backend;try{await r.init(this)}catch(e){if(null===this._getFallback)return void t(e);try{this.backend=r=this._getFallback(e),await r.init(this)}catch(e){return void t(e)}}this._nodes=new pv(this,r),this._animation=new nf(this._nodes,this.info),this._attributes=new yf(r),this._background=new d_(this,this._nodes),this._geometries=new Tf(this._attributes,this.info),this._textures=new Hf(this,r,this.info),this._pipelines=new Af(r,this._nodes),this._bindings=new Rf(r,this._nodes,this._textures,this._attributes,this._pipelines,this.info),this._objects=new df(this,this._nodes,this._geometries,this._pipelines,this._bindings,this.info),this._renderLists=new Lf(this.lighting),this._bundles=new bv,this._renderContexts=new Gf,this._animation.start(),this._initialized=!0,e(this)})),this._initPromise}get coordinateSystem(){return this.backend.coordinateSystem}async compileAsync(e,t,r=null){if(!0===this._isDeviceLost)return;!1===this._initialized&&await this.init();const s=this._nodes.nodeFrame,i=s.renderId,n=this._currentRenderContext,a=this._currentRenderObjectFunction,o=this._compilationPromises,u=!0===e.isScene?e:Fv;null===r&&(r=e);const l=this._renderTarget,d=this._renderContexts.get(r,t,l),c=this._activeMipmapLevel,h=[];this._currentRenderContext=d,this._currentRenderObjectFunction=this.renderObject,this._handleObjectFunction=this._createObjectPipeline,this._compilationPromises=h,s.renderId++,s.update(),d.depth=this.depth,d.stencil=this.stencil,d.clippingContext||(d.clippingContext=new mv),d.clippingContext.updateGlobal(u,t),u.onBeforeRender(this,e,t,l);const p=this._renderLists.get(e,t);if(p.begin(),this._projectObject(e,t,0,p,d.clippingContext),r!==e&&r.traverseVisible(function(e){e.isLight&&e.layers.test(t.layers)&&p.pushLight(e)}),p.finish(),null!==l){this._textures.updateRenderTarget(l,c);const e=this._textures.get(l);d.textures=e.textures,d.depthTexture=e.depthTexture}else d.textures=null,d.depthTexture=null;this._background.update(u,p,d);const g=p.opaque,m=p.transparent,f=p.transparentDoublePass,y=p.lightsNode;!0===this.opaque&&g.length>0&&this._renderObjects(g,t,u,y),!0===this.transparent&&m.length>0&&this._renderTransparents(m,f,t,u,y),s.renderId=i,this._currentRenderContext=n,this._currentRenderObjectFunction=a,this._compilationPromises=o,this._handleObjectFunction=this._renderObjectDirect,await Promise.all(h)}async renderAsync(e,t){!1===this._initialized&&await this.init(),this._renderScene(e,t)}async waitForGPU(){await this.backend.waitForGPU()}set highPrecision(e){!0===e?(this.overrideNodes.modelViewMatrix=Ll,this.overrideNodes.modelNormalViewMatrix=Dl):this.highPrecision&&(this.overrideNodes.modelViewMatrix=null,this.overrideNodes.modelNormalViewMatrix=null)}get highPrecision(){return this.overrideNodes.modelViewMatrix===Ll&&this.overrideNodes.modelNormalViewMatrix===Dl}setMRT(e){return this._mrt=e,this}getMRT(){return this._mrt}getColorBufferType(){return this._colorBufferType}_onDeviceLost(e){let t=`THREE.WebGPURenderer: ${e.api} Device Lost:\n\nMessage: ${e.message}`;e.reason&&(t+=`\nReason: ${e.reason}`),console.error(t),this._isDeviceLost=!0}_renderBundle(e,t,r){const{bundleGroup:s,camera:i,renderList:n}=e,a=this._currentRenderContext,o=this._bundles.get(s,i),u=this.backend.get(o);void 0===u.renderContexts&&(u.renderContexts=new Set);const l=s.version!==u.version,d=!1===u.renderContexts.has(a)||l;if(u.renderContexts.add(a),d){this.backend.beginBundle(a),(void 0===u.renderObjects||l)&&(u.renderObjects=[]),this._currentRenderBundle=o;const{transparentDoublePass:e,transparent:d,opaque:c}=n;!0===this.opaque&&c.length>0&&this._renderObjects(c,i,t,r),!0===this.transparent&&d.length>0&&this._renderTransparents(d,e,i,t,r),this._currentRenderBundle=null,this.backend.finishBundle(a,o),u.version=s.version}else{const{renderObjects:e}=u;for(let t=0,r=e.length;t>=c,p.viewportValue.height>>=c,p.viewportValue.minDepth=x,p.viewportValue.maxDepth=T,p.viewport=!1===p.viewportValue.equals(Dv),p.scissorValue.copy(y).multiplyScalar(b).floor(),p.scissor=this._scissorTest&&!1===p.scissorValue.equals(Dv),p.scissorValue.width>>=c,p.scissorValue.height>>=c,p.clippingContext||(p.clippingContext=new mv),p.clippingContext.updateGlobal(u,t),u.onBeforeRender(this,e,t,h);const _=t.isArrayCamera?Vv:Iv;t.isArrayCamera||(Uv.multiplyMatrices(t.projectionMatrix,t.matrixWorldInverse),_.setFromProjectionMatrix(Uv,g));const v=this._renderLists.get(e,t);if(v.begin(),this._projectObject(e,t,0,v,p.clippingContext),v.finish(),!0===this.sortObjects&&v.sort(this._opaqueSort,this._transparentSort),null!==h){this._textures.updateRenderTarget(h,c);const e=this._textures.get(h);p.textures=e.textures,p.depthTexture=e.depthTexture,p.width=e.width,p.height=e.height,p.renderTarget=h,p.depth=h.depthBuffer,p.stencil=h.stencilBuffer}else p.textures=null,p.depthTexture=null,p.width=this.domElement.width,p.height=this.domElement.height,p.depth=this.depth,p.stencil=this.stencil;p.width>>=c,p.height>>=c,p.activeCubeFace=d,p.activeMipmapLevel=c,p.occlusionQueryCount=v.occlusionQueryCount,this._background.update(u,v,p),p.camera=t,this.backend.beginRender(p);const{bundles:N,lightsNode:S,transparentDoublePass:E,transparent:w,opaque:A}=v;return N.length>0&&this._renderBundles(N,u,S),!0===this.opaque&&A.length>0&&this._renderObjects(A,t,u,S),!0===this.transparent&&w.length>0&&this._renderTransparents(w,E,t,u,S),this.backend.finishRender(p),i.renderId=n,this._currentRenderContext=a,this._currentRenderObjectFunction=o,null!==s&&(this.setRenderTarget(l,d,c),this._renderOutput(h)),u.onAfterRender(this,e,t,h),p}_setXRLayerSize(e,t){this._width=e,this._height=t,this.setViewport(0,0,e,t)}_renderOutput(e){const t=this._quad;this._nodes.hasOutputChange(e.texture)&&(t.material.fragmentNode=this._nodes.getOutputNode(e.texture),t.material.needsUpdate=!0);const r=this.autoClear,s=this.xr.enabled;this.autoClear=!1,this.xr.enabled=!1,this._renderScene(t,t.camera,!1),this.autoClear=r,this.xr.enabled=s}getMaxAnisotropy(){return this.backend.getMaxAnisotropy()}getActiveCubeFace(){return this._activeCubeFace}getActiveMipmapLevel(){return this._activeMipmapLevel}async setAnimationLoop(e){!1===this._initialized&&await this.init(),this._animation.setAnimationLoop(e)}async getArrayBufferAsync(e){return await this.backend.getArrayBufferAsync(e)}getContext(){return this.backend.getContext()}getPixelRatio(){return this._pixelRatio}getDrawingBufferSize(e){return e.set(this._width*this._pixelRatio,this._height*this._pixelRatio).floor()}getSize(e){return e.set(this._width,this._height)}setPixelRatio(e=1){this._pixelRatio!==e&&(this._pixelRatio=e,this.setSize(this._width,this._height,!1))}setDrawingBufferSize(e,t,r){this.xr&&this.xr.isPresenting||(this._width=e,this._height=t,this._pixelRatio=r,this.domElement.width=Math.floor(e*r),this.domElement.height=Math.floor(t*r),this.setViewport(0,0,e,t),this._initialized&&this.backend.updateSize())}setSize(e,t,r=!0){this.xr&&this.xr.isPresenting||(this._width=e,this._height=t,this.domElement.width=Math.floor(e*this._pixelRatio),this.domElement.height=Math.floor(t*this._pixelRatio),!0===r&&(this.domElement.style.width=e+"px",this.domElement.style.height=t+"px"),this.setViewport(0,0,e,t),this._initialized&&this.backend.updateSize())}setOpaqueSort(e){this._opaqueSort=e}setTransparentSort(e){this._transparentSort=e}getScissor(e){const t=this._scissor;return e.x=t.x,e.y=t.y,e.width=t.width,e.height=t.height,e}setScissor(e,t,r,s){const i=this._scissor;e.isVector4?i.copy(e):i.set(e,t,r,s)}getScissorTest(){return this._scissorTest}setScissorTest(e){this._scissorTest=e,this.backend.setScissorTest(e)}getViewport(e){return e.copy(this._viewport)}setViewport(e,t,r,s,i=0,n=1){const a=this._viewport;e.isVector4?a.copy(e):a.set(e,t,r,s),a.minDepth=i,a.maxDepth=n}getClearColor(e){return e.copy(this._clearColor)}setClearColor(e,t=1){this._clearColor.set(e),this._clearColor.a=t}getClearAlpha(){return this._clearColor.a}setClearAlpha(e){this._clearColor.a=e}getClearDepth(){return this._clearDepth}setClearDepth(e){this._clearDepth=e}getClearStencil(){return this._clearStencil}setClearStencil(e){this._clearStencil=e}isOccluded(e){const t=this._currentRenderContext;return t&&this.backend.isOccluded(t,e)}clear(e=!0,t=!0,r=!0){if(!1===this._initialized)return console.warn("THREE.Renderer: .clear() called before the backend is initialized. Try using .clearAsync() instead."),this.clearAsync(e,t,r);const s=this._renderTarget||this._getFrameBufferTarget();let i=null;if(null!==s){this._textures.updateRenderTarget(s);const e=this._textures.get(s);i=this._renderContexts.getForClear(s),i.textures=e.textures,i.depthTexture=e.depthTexture,i.width=e.width,i.height=e.height,i.renderTarget=s,i.depth=s.depthBuffer,i.stencil=s.stencilBuffer,i.clearColorValue=this.backend.getClearColor(),i.activeCubeFace=this.getActiveCubeFace(),i.activeMipmapLevel=this.getActiveMipmapLevel()}this.backend.clear(e,t,r,i),null!==s&&null===this._renderTarget&&this._renderOutput(s)}clearColor(){return this.clear(!0,!1,!1)}clearDepth(){return this.clear(!1,!0,!1)}clearStencil(){return this.clear(!1,!1,!0)}async clearAsync(e=!0,t=!0,r=!0){!1===this._initialized&&await this.init(),this.clear(e,t,r)}async clearColorAsync(){this.clearAsync(!0,!1,!1)}async clearDepthAsync(){this.clearAsync(!1,!0,!1)}async clearStencilAsync(){this.clearAsync(!1,!1,!0)}get currentToneMapping(){return this.isOutputTarget?this.toneMapping:p}get currentColorSpace(){return this.isOutputTarget?this.outputColorSpace:le}get isOutputTarget(){return this._renderTarget===this._outputRenderTarget||null===this._renderTarget}dispose(){this.info.dispose(),this.backend.dispose(),this._animation.dispose(),this._objects.dispose(),this._pipelines.dispose(),this._nodes.dispose(),this._bindings.dispose(),this._renderLists.dispose(),this._renderContexts.dispose(),this._textures.dispose(),null!==this._frameBufferTarget&&this._frameBufferTarget.dispose(),Object.values(this.backend.timestampQueryPool).forEach(e=>{null!==e&&e.dispose()}),this.setRenderTarget(null),this.setAnimationLoop(null)}setRenderTarget(e,t=0,r=0){this._renderTarget=e,this._activeCubeFace=t,this._activeMipmapLevel=r}getRenderTarget(){return this._renderTarget}setOutputRenderTarget(e){this._outputRenderTarget=e}getOutputRenderTarget(){return this._outputRenderTarget}_resetXRState(){this.backend.setXRTarget(null),this.setOutputRenderTarget(null),this.setRenderTarget(null),this._frameBufferTarget.dispose(),this._frameBufferTarget=null}setRenderObjectFunction(e){this._renderObjectFunction=e}getRenderObjectFunction(){return this._renderObjectFunction}compute(e){if(!0===this._isDeviceLost)return;if(!1===this._initialized)return console.warn("THREE.Renderer: .compute() called before the backend is initialized. Try using .computeAsync() instead."),this.computeAsync(e);const t=this._nodes.nodeFrame,r=t.renderId;this.info.calls++,this.info.compute.calls++,this.info.compute.frameCalls++,t.renderId=this.info.calls;const s=this.backend,i=this._pipelines,n=this._bindings,a=this._nodes,o=Array.isArray(e)?e:[e];if(void 0===o[0]||!0!==o[0].isComputeNode)throw new Error("THREE.Renderer: .compute() expects a ComputeNode.");s.beginCompute(e);for(const t of o){if(!1===i.has(t)){const e=()=>{t.removeEventListener("dispose",e),i.delete(t),n.delete(t),a.delete(t)};t.addEventListener("dispose",e);const r=t.onInitFunction;null!==r&&r.call(t,{renderer:this})}a.updateForCompute(t),n.updateForCompute(t);const r=n.getForCompute(t),o=i.getForCompute(t,r);s.compute(e,t,r,o)}s.finishCompute(e),t.renderId=r}async computeAsync(e){!1===this._initialized&&await this.init(),this.compute(e)}async hasFeatureAsync(e){return!1===this._initialized&&await this.init(),this.backend.hasFeature(e)}async resolveTimestampsAsync(e="render"){return!1===this._initialized&&await this.init(),this.backend.resolveTimestampsAsync(e)}hasFeature(e){return!1===this._initialized?(console.warn("THREE.Renderer: .hasFeature() called before the backend is initialized. Try using .hasFeatureAsync() instead."),!1):this.backend.hasFeature(e)}hasInitialized(){return this._initialized}async initTextureAsync(e){!1===this._initialized&&await this.init(),this._textures.updateTexture(e)}initTexture(e){!1===this._initialized&&console.warn("THREE.Renderer: .initTexture() called before the backend is initialized. Try using .initTextureAsync() instead."),this._textures.updateTexture(e)}copyFramebufferToTexture(e,t=null){if(null!==t)if(t.isVector2)t=Ov.set(t.x,t.y,e.image.width,e.image.height).floor();else{if(!t.isVector4)return void console.error("THREE.Renderer.copyFramebufferToTexture: Invalid rectangle.");t=Ov.copy(t).floor()}else t=Ov.set(0,0,e.image.width,e.image.height);let r,s=this._currentRenderContext;null!==s?r=s.renderTarget:(r=this._renderTarget||this._getFrameBufferTarget(),null!==r&&(this._textures.updateRenderTarget(r),s=this._textures.get(r))),this._textures.updateTexture(e,{renderTarget:r}),this.backend.copyFramebufferToTexture(e,s,t)}copyTextureToTexture(e,t,r=null,s=null,i=0,n=0){this._textures.updateTexture(e),this._textures.updateTexture(t),this.backend.copyTextureToTexture(e,t,r,s,i,n)}async readRenderTargetPixelsAsync(e,t,r,s,i,n=0,a=0){return this.backend.copyTextureToBuffer(e.textures[n],t,r,s,i,a)}_projectObject(e,t,r,s,i){if(!1===e.visible)return;if(e.layers.test(t.layers))if(e.isGroup)r=e.renderOrder,e.isClippingGroup&&e.enabled&&(i=i.getGroupContext(e));else if(e.isLOD)!0===e.autoUpdate&&e.update(t);else if(e.isLight)s.pushLight(e);else if(e.isSprite){const n=t.isArrayCamera?Vv:Iv;if(!e.frustumCulled||n.intersectsSprite(e,t)){!0===this.sortObjects&&Ov.setFromMatrixPosition(e.matrixWorld).applyMatrix4(Uv);const{geometry:t,material:n}=e;n.visible&&s.push(e,t,n,r,Ov.z,null,i)}}else if(e.isLineLoop)console.error("THREE.Renderer: Objects of type THREE.LineLoop are not supported. Please use THREE.Line or THREE.LineSegments.");else if(e.isMesh||e.isLine||e.isPoints){const n=t.isArrayCamera?Vv:Iv;if(!e.frustumCulled||n.intersectsObject(e,t)){const{geometry:t,material:n}=e;if(!0===this.sortObjects&&(null===t.boundingSphere&&t.computeBoundingSphere(),Ov.copy(t.boundingSphere.center).applyMatrix4(e.matrixWorld).applyMatrix4(Uv)),Array.isArray(n)){const a=t.groups;for(let o=0,u=a.length;o0){for(const{material:e}of t)e.side=S;this._renderObjects(t,r,s,i,"backSide");for(const{material:e}of t)e.side=je;this._renderObjects(e,r,s,i);for(const{material:e}of t)e.side=E}else this._renderObjects(e,r,s,i)}_renderObjects(e,t,r,s,i=null){for(let n=0,a=e.length;n0,e.isShadowPassMaterial&&(e.side=null===i.shadowSide?i.side:i.shadowSide,i.depthNode&&i.depthNode.isNode&&(c=e.depthNode,e.depthNode=i.depthNode),i.castShadowNode&&i.castShadowNode.isNode&&(d=e.colorNode,e.colorNode=i.castShadowNode),i.castShadowPositionNode&&i.castShadowPositionNode.isNode&&(l=e.positionNode,e.positionNode=i.castShadowPositionNode)),i=e}!0===i.transparent&&i.side===E&&!1===i.forceSinglePass?(i.side=S,this._handleObjectFunction(e,i,t,r,a,n,o,"backSide"),i.side=je,this._handleObjectFunction(e,i,t,r,a,n,o,u),i.side=E):this._handleObjectFunction(e,i,t,r,a,n,o,u),void 0!==l&&(t.overrideMaterial.positionNode=l),void 0!==c&&(t.overrideMaterial.depthNode=c),void 0!==d&&(t.overrideMaterial.colorNode=d),e.onAfterRender(this,t,r,s,i,n)}_renderObjectDirect(e,t,r,s,i,n,a,o){const u=this._objects.get(e,t,r,s,i,this._currentRenderContext,a,o);u.drawRange=e.geometry.drawRange,u.group=n;const l=this._nodes.needsRefresh(u);if(l&&(this._nodes.updateBefore(u),this._geometries.updateForRender(u),this._nodes.updateForRender(u),this._bindings.updateForRender(u)),this._pipelines.updateForRender(u),null!==this._currentRenderBundle){this.backend.get(this._currentRenderBundle).renderObjects.push(u),u.bundle=this._currentRenderBundle.bundleGroup}this.backend.draw(u,this.info),l&&this._nodes.updateAfter(u)}_createObjectPipeline(e,t,r,s,i,n,a,o){const u=this._objects.get(e,t,r,s,i,this._currentRenderContext,a,o);u.drawRange=e.geometry.drawRange,u.group=n,this._nodes.updateBefore(u),this._geometries.updateForRender(u),this._nodes.updateForRender(u),this._bindings.updateForRender(u),this._pipelines.getForRender(u,this._compilationPromises),this._nodes.updateAfter(u)}get compile(){return this.compileAsync}}class Gv{constructor(e=""){this.name=e,this.visibility=0}setVisibility(e){this.visibility|=e}clone(){return Object.assign(new this.constructor,this)}}class zv extends Gv{constructor(e,t=null){super(e),this.isBuffer=!0,this.bytesPerElement=Float32Array.BYTES_PER_ELEMENT,this._buffer=t}get byteLength(){return(e=this._buffer.byteLength)+(ff-e%ff)%ff;var e}get buffer(){return this._buffer}update(){return!0}}class Hv extends zv{constructor(e,t=null){super(e,t),this.isUniformBuffer=!0}}let $v=0;class Wv extends Hv{constructor(e,t){super("UniformBuffer_"+$v++,e?e.value:null),this.nodeUniform=e,this.groupNode=t}get buffer(){return this.nodeUniform.value}}class jv extends Hv{constructor(e){super(e),this.isUniformsGroup=!0,this._values=null,this.uniforms=[]}addUniform(e){return this.uniforms.push(e),this}removeUniform(e){const t=this.uniforms.indexOf(e);return-1!==t&&this.uniforms.splice(t,1),this}get values(){return null===this._values&&(this._values=Array.from(this.buffer)),this._values}get buffer(){let e=this._buffer;if(null===e){const t=this.byteLength;e=new Float32Array(new ArrayBuffer(t)),this._buffer=e}return e}get byteLength(){const e=this.bytesPerElement;let t=0;for(let r=0,s=this.uniforms.length;r0?s:"";t=`${e.name} {\n\t${r} ${i.name}[${n}];\n};\n`}else{t=`${this.getVectorType(i.type)} ${this.getPropertyName(i,e)};`,n=!0}const a=i.node.precision;if(null!==a&&(t=tN[a]+" "+t),n){t="\t"+t;const e=i.groupNode.name;(s[e]||(s[e]=[])).push(t)}else t="uniform "+t,r.push(t)}let i="";for(const t in s){const r=s[t];i+=this._getGLSLUniformStruct(e+"_"+t,r.join("\n"))+"\n"}return i+=r.join("\n"),i}getTypeFromAttribute(e){let t=super.getTypeFromAttribute(e);if(/^[iu]/.test(t)&&e.gpuType!==_){let r=e;e.isInterleavedBufferAttribute&&(r=e.data);const s=r.array;!1==(s instanceof Uint32Array||s instanceof Int32Array)&&(t=t.slice(1))}return t}getAttributes(e){let t="";if("vertex"===e||"compute"===e){const e=this.getAttributesArray();let r=0;for(const s of e)t+=`layout( location = ${r++} ) in ${s.type} ${s.name};\n`}return t}getStructMembers(e){const t=[];for(const r of e.members)t.push(`\t${r.type} ${r.name};`);return t.join("\n")}getStructs(e){const t=[],r=this.structs[e],s=[];for(const e of r)if(e.output)for(const t of e.members)s.push(`layout( location = ${t.index} ) out ${t.type} ${t.name};`);else{let r="struct "+e.name+" {\n";r+=this.getStructMembers(e),r+="\n};\n",t.push(r)}return 0===s.length&&s.push("layout( location = 0 ) out vec4 fragColor;"),"\n"+s.join("\n")+"\n\n"+t.join("\n")}getVaryings(e){let t="";const r=this.varyings;if("vertex"===e||"compute"===e)for(const s of r){"compute"===e&&(s.needsInterpolation=!0);const r=this.getType(s.type);if(s.needsInterpolation)if(s.interpolationType){t+=`${sN[s.interpolationType]||s.interpolationType} ${iN[s.interpolationSampling]||""} out ${r} ${s.name};\n`}else{t+=`${r.includes("int")||r.includes("uv")||r.includes("iv")?"flat ":""}out ${r} ${s.name};\n`}else t+=`${r} ${s.name};\n`}else if("fragment"===e)for(const e of r)if(e.needsInterpolation){const r=this.getType(e.type);if(e.interpolationType){t+=`${sN[e.interpolationType]||e.interpolationType} ${iN[e.interpolationSampling]||""} in ${r} ${e.name};\n`}else{t+=`${r.includes("int")||r.includes("uv")||r.includes("iv")?"flat ":""}in ${r} ${e.name};\n`}}for(const r of this.builtins[e])t+=`${r};\n`;return t}getVertexIndex(){return"uint( gl_VertexID )"}getInstanceIndex(){return"uint( gl_InstanceID )"}getInvocationLocalIndex(){return`uint( gl_InstanceID ) % ${this.object.workgroupSize.reduce((e,t)=>e*t,1)}u`}getDrawIndex(){return this.renderer.backend.extensions.has("WEBGL_multi_draw")?"uint( gl_DrawID )":null}getFrontFacing(){return"gl_FrontFacing"}getFragCoord(){return"gl_FragCoord.xy"}getFragDepth(){return"gl_FragDepth"}enableExtension(e,t,r=this.shaderStage){const s=this.extensions[r]||(this.extensions[r]=new Map);!1===s.has(e)&&s.set(e,{name:e,behavior:t})}getExtensions(e){const t=[];if("vertex"===e){const t=this.renderer.backend.extensions;this.object.isBatchedMesh&&t.has("WEBGL_multi_draw")&&this.enableExtension("GL_ANGLE_multi_draw","require",e)}const r=this.extensions[e];if(void 0!==r)for(const{name:e,behavior:s}of r.values())t.push(`#extension ${e} : ${s}`);return t.join("\n")}getClipDistance(){return"gl_ClipDistance"}isAvailable(e){let t=rN[e];if(void 0===t){let r;switch(t=!1,e){case"float32Filterable":r="OES_texture_float_linear";break;case"clipDistance":r="WEBGL_clip_cull_distance"}if(void 0!==r){const e=this.renderer.backend.extensions;e.has(r)&&(e.get(r),t=!0)}rN[e]=t}return t}isFlipY(){return!0}enableHardwareClipping(e){this.enableExtension("GL_ANGLE_clip_cull_distance","require"),this.builtins.vertex.push(`out float gl_ClipDistance[ ${e} ]`)}enableMultiview(){this.enableExtension("GL_OVR_multiview2","require","fragment"),this.enableExtension("GL_OVR_multiview2","require","vertex"),this.builtins.vertex.push("layout(num_views = 2) in")}registerTransform(e,t){this.transforms.push({varyingName:e,attributeNode:t})}getTransforms(){const e=this.transforms;let t="";for(let r=0;r0&&(r+="\n"),r+=`\t// flow -> ${n}\n\t`),r+=`${s.code}\n\t`,e===i&&"compute"!==t&&(r+="// result\n\t","vertex"===t?(r+="gl_Position = ",r+=`${s.result};`):"fragment"===t&&(e.outputNode.isOutputStructNode||(r+="fragColor = ",r+=`${s.result};`)))}const n=e[t];n.extensions=this.getExtensions(t),n.uniforms=this.getUniforms(t),n.attributes=this.getAttributes(t),n.varyings=this.getVaryings(t),n.vars=this.getVars(t),n.structs=this.getStructs(t),n.codes=this.getCodes(t),n.transforms=this.getTransforms(t),n.flow=r}null!==this.material?(this.vertexShader=this._getGLSLVertexCode(e.vertex),this.fragmentShader=this._getGLSLFragmentCode(e.fragment)):this.computeShader=this._getGLSLVertexCode(e.compute)}getUniformFromNode(e,t,r,s=null){const i=super.getUniformFromNode(e,t,r,s),n=this.getDataFromNode(e,r,this.globalCache);let a=n.uniformGPU;if(void 0===a){const s=e.groupNode,o=s.name,u=this.getBindGroupArray(o,r);if("texture"===t)a=new Qv(i.name,i.node,s),u.push(a);else if("cubeTexture"===t)a=new Zv(i.name,i.node,s),u.push(a);else if("texture3D"===t)a=new Jv(i.name,i.node,s),u.push(a);else if("buffer"===t){e.name=`NodeBuffer_${e.id}`,i.name=`buffer${e.id}`;const t=new Wv(e,s);t.name=e.name,u.push(t),a=t}else{const e=this.uniformGroups[r]||(this.uniformGroups[r]={});let n=e[o];void 0===n&&(n=new Xv(r+"_"+o,s),e[o]=n,u.push(n)),a=this.getNodeUniform(i,t),n.addUniform(a)}n.uniformGPU=a}return i}}let oN=null,uN=null;class lN{constructor(e={}){this.parameters=Object.assign({},e),this.data=new WeakMap,this.renderer=null,this.domElement=null,this.timestampQueryPool={render:null,compute:null},this.trackTimestamp=!0===e.trackTimestamp}async init(e){this.renderer=e}get coordinateSystem(){}beginRender(){}finishRender(){}beginCompute(){}finishCompute(){}draw(){}compute(){}createProgram(){}destroyProgram(){}createBindings(){}updateBindings(){}updateBinding(){}createRenderPipeline(){}createComputePipeline(){}needsRenderUpdate(){}getRenderCacheKey(){}createNodeBuilder(){}createSampler(){}destroySampler(){}createDefaultTexture(){}createTexture(){}updateTexture(){}generateMipmaps(){}destroyTexture(){}async copyTextureToBuffer(){}copyTextureToTexture(){}copyFramebufferToTexture(){}createAttribute(){}createIndexAttribute(){}createStorageAttribute(){}updateAttribute(){}destroyAttribute(){}getContext(){}updateSize(){}updateViewport(){}isOccluded(){}async resolveTimestampsAsync(e="render"){if(!this.trackTimestamp)return void pt("WebGPURenderer: Timestamp tracking is disabled.");const t=this.timestampQueryPool[e];if(!t)return void pt(`WebGPURenderer: No timestamp query pool for type '${e}' found.`);const r=await t.resolveQueriesAsync();return this.renderer.info[e].timestamp=r,r}async waitForGPU(){}async getArrayBufferAsync(){}async hasFeatureAsync(){}hasFeature(){}getMaxAnisotropy(){}getDrawingBufferSize(){return oN=oN||new t,this.renderer.getDrawingBufferSize(oN)}setScissorTest(){}getClearColor(){const e=this.renderer;return uN=uN||new $f,e.getClearColor(uN),uN.getRGB(uN),uN}getDomElement(){let e=this.domElement;return null===e&&(e=void 0!==this.parameters.canvas?this.parameters.canvas:gt(),"setAttribute"in e&&e.setAttribute("data-engine",`three.js r${He} webgpu`),this.domElement=e),e}set(e,t){this.data.set(e,t)}get(e){let t=this.data.get(e);return void 0===t&&(t={},this.data.set(e,t)),t}has(e){return this.data.has(e)}delete(e){this.data.delete(e)}dispose(){}}let dN,cN,hN=0;class pN{constructor(e,t){this.buffers=[e.bufferGPU,t],this.type=e.type,this.bufferType=e.bufferType,this.pbo=e.pbo,this.byteLength=e.byteLength,this.bytesPerElement=e.BYTES_PER_ELEMENT,this.version=e.version,this.isInteger=e.isInteger,this.activeBufferIndex=0,this.baseId=e.id}get id(){return`${this.baseId}|${this.activeBufferIndex}`}get bufferGPU(){return this.buffers[this.activeBufferIndex]}get transformBuffer(){return this.buffers[1^this.activeBufferIndex]}switchBuffers(){this.activeBufferIndex^=1}}class gN{constructor(e){this.backend=e}createAttribute(e,t){const r=this.backend,{gl:s}=r,i=e.array,n=e.usage||s.STATIC_DRAW,a=e.isInterleavedBufferAttribute?e.data:e,o=r.get(a);let u,l=o.bufferGPU;if(void 0===l&&(l=this._createBuffer(s,t,i,n),o.bufferGPU=l,o.bufferType=t,o.version=a.version),i instanceof Float32Array)u=s.FLOAT;else if("undefined"!=typeof Float16Array&&i instanceof Float16Array)u=s.HALF_FLOAT;else if(i instanceof Uint16Array)u=e.isFloat16BufferAttribute?s.HALF_FLOAT:s.UNSIGNED_SHORT;else if(i instanceof Int16Array)u=s.SHORT;else if(i instanceof Uint32Array)u=s.UNSIGNED_INT;else if(i instanceof Int32Array)u=s.INT;else if(i instanceof Int8Array)u=s.BYTE;else if(i instanceof Uint8Array)u=s.UNSIGNED_BYTE;else{if(!(i instanceof Uint8ClampedArray))throw new Error("THREE.WebGLBackend: Unsupported buffer data format: "+i);u=s.UNSIGNED_BYTE}let d={bufferGPU:l,bufferType:t,type:u,byteLength:i.byteLength,bytesPerElement:i.BYTES_PER_ELEMENT,version:e.version,pbo:e.pbo,isInteger:u===s.INT||u===s.UNSIGNED_INT||e.gpuType===_,id:hN++};if(e.isStorageBufferAttribute||e.isStorageInstancedBufferAttribute){const e=this._createBuffer(s,t,i,n);d=new pN(d,e)}r.set(e,d)}updateAttribute(e){const t=this.backend,{gl:r}=t,s=e.array,i=e.isInterleavedBufferAttribute?e.data:e,n=t.get(i),a=n.bufferType,o=e.isInterleavedBufferAttribute?e.data.updateRanges:e.updateRanges;if(r.bindBuffer(a,n.bufferGPU),0===o.length)r.bufferSubData(a,0,s);else{for(let e=0,t=o.length;e1?this.enable(s.SAMPLE_ALPHA_TO_COVERAGE):this.disable(s.SAMPLE_ALPHA_TO_COVERAGE),r>0&&this.currentClippingPlanes!==r){const e=12288;for(let t=0;t<8;t++)t{!function i(){const n=e.clientWaitSync(t,e.SYNC_FLUSH_COMMANDS_BIT,0);if(n===e.WAIT_FAILED)return e.deleteSync(t),void s();n!==e.TIMEOUT_EXPIRED?(e.deleteSync(t),r()):requestAnimationFrame(i)}()})}}let yN,bN,xN,TN=!1;class _N{constructor(e){this.backend=e,this.gl=e.gl,this.extensions=e.extensions,this.defaultTextures={},!1===TN&&(this._init(),TN=!0)}_init(){const e=this.gl;yN={[Nr]:e.REPEAT,[vr]:e.CLAMP_TO_EDGE,[_r]:e.MIRRORED_REPEAT},bN={[v]:e.NEAREST,[Sr]:e.NEAREST_MIPMAP_NEAREST,[Ge]:e.NEAREST_MIPMAP_LINEAR,[Y]:e.LINEAR,[ke]:e.LINEAR_MIPMAP_NEAREST,[V]:e.LINEAR_MIPMAP_LINEAR},xN={[Pr]:e.NEVER,[Mr]:e.ALWAYS,[Ie]:e.LESS,[Cr]:e.LEQUAL,[Rr]:e.EQUAL,[Ar]:e.GEQUAL,[wr]:e.GREATER,[Er]:e.NOTEQUAL}}getGLTextureType(e){const{gl:t}=this;let r;return r=!0===e.isCubeTexture?t.TEXTURE_CUBE_MAP:!0===e.isArrayTexture||!0===e.isDataArrayTexture||!0===e.isCompressedArrayTexture?t.TEXTURE_2D_ARRAY:!0===e.isData3DTexture?t.TEXTURE_3D:t.TEXTURE_2D,r}getInternalFormat(e,t,r,s,i=!1){const{gl:n,extensions:a}=this;if(null!==e){if(void 0!==n[e])return n[e];console.warn("THREE.WebGLRenderer: Attempt to use non-existing WebGL internal format '"+e+"'")}let o=t;if(t===n.RED&&(r===n.FLOAT&&(o=n.R32F),r===n.HALF_FLOAT&&(o=n.R16F),r===n.UNSIGNED_BYTE&&(o=n.R8),r===n.UNSIGNED_SHORT&&(o=n.R16),r===n.UNSIGNED_INT&&(o=n.R32UI),r===n.BYTE&&(o=n.R8I),r===n.SHORT&&(o=n.R16I),r===n.INT&&(o=n.R32I)),t===n.RED_INTEGER&&(r===n.UNSIGNED_BYTE&&(o=n.R8UI),r===n.UNSIGNED_SHORT&&(o=n.R16UI),r===n.UNSIGNED_INT&&(o=n.R32UI),r===n.BYTE&&(o=n.R8I),r===n.SHORT&&(o=n.R16I),r===n.INT&&(o=n.R32I)),t===n.RG&&(r===n.FLOAT&&(o=n.RG32F),r===n.HALF_FLOAT&&(o=n.RG16F),r===n.UNSIGNED_BYTE&&(o=n.RG8),r===n.UNSIGNED_SHORT&&(o=n.RG16),r===n.UNSIGNED_INT&&(o=n.RG32UI),r===n.BYTE&&(o=n.RG8I),r===n.SHORT&&(o=n.RG16I),r===n.INT&&(o=n.RG32I)),t===n.RG_INTEGER&&(r===n.UNSIGNED_BYTE&&(o=n.RG8UI),r===n.UNSIGNED_SHORT&&(o=n.RG16UI),r===n.UNSIGNED_INT&&(o=n.RG32UI),r===n.BYTE&&(o=n.RG8I),r===n.SHORT&&(o=n.RG16I),r===n.INT&&(o=n.RG32I)),t===n.RGB){const e=i?Br:c.getTransfer(s);r===n.FLOAT&&(o=n.RGB32F),r===n.HALF_FLOAT&&(o=n.RGB16F),r===n.UNSIGNED_BYTE&&(o=n.RGB8),r===n.UNSIGNED_SHORT&&(o=n.RGB16),r===n.UNSIGNED_INT&&(o=n.RGB32UI),r===n.BYTE&&(o=n.RGB8I),r===n.SHORT&&(o=n.RGB16I),r===n.INT&&(o=n.RGB32I),r===n.UNSIGNED_BYTE&&(o=e===h?n.SRGB8:n.RGB8),r===n.UNSIGNED_SHORT_5_6_5&&(o=n.RGB565),r===n.UNSIGNED_SHORT_5_5_5_1&&(o=n.RGB5_A1),r===n.UNSIGNED_SHORT_4_4_4_4&&(o=n.RGB4),r===n.UNSIGNED_INT_5_9_9_9_REV&&(o=n.RGB9_E5)}if(t===n.RGB_INTEGER&&(r===n.UNSIGNED_BYTE&&(o=n.RGB8UI),r===n.UNSIGNED_SHORT&&(o=n.RGB16UI),r===n.UNSIGNED_INT&&(o=n.RGB32UI),r===n.BYTE&&(o=n.RGB8I),r===n.SHORT&&(o=n.RGB16I),r===n.INT&&(o=n.RGB32I)),t===n.RGBA){const e=i?Br:c.getTransfer(s);r===n.FLOAT&&(o=n.RGBA32F),r===n.HALF_FLOAT&&(o=n.RGBA16F),r===n.UNSIGNED_BYTE&&(o=n.RGBA8),r===n.UNSIGNED_SHORT&&(o=n.RGBA16),r===n.UNSIGNED_INT&&(o=n.RGBA32UI),r===n.BYTE&&(o=n.RGBA8I),r===n.SHORT&&(o=n.RGBA16I),r===n.INT&&(o=n.RGBA32I),r===n.UNSIGNED_BYTE&&(o=e===h?n.SRGB8_ALPHA8:n.RGBA8),r===n.UNSIGNED_SHORT_4_4_4_4&&(o=n.RGBA4),r===n.UNSIGNED_SHORT_5_5_5_1&&(o=n.RGB5_A1)}return t===n.RGBA_INTEGER&&(r===n.UNSIGNED_BYTE&&(o=n.RGBA8UI),r===n.UNSIGNED_SHORT&&(o=n.RGBA16UI),r===n.UNSIGNED_INT&&(o=n.RGBA32UI),r===n.BYTE&&(o=n.RGBA8I),r===n.SHORT&&(o=n.RGBA16I),r===n.INT&&(o=n.RGBA32I)),t===n.DEPTH_COMPONENT&&(r===n.UNSIGNED_SHORT&&(o=n.DEPTH_COMPONENT16),r===n.UNSIGNED_INT&&(o=n.DEPTH_COMPONENT24),r===n.FLOAT&&(o=n.DEPTH_COMPONENT32F)),t===n.DEPTH_STENCIL&&r===n.UNSIGNED_INT_24_8&&(o=n.DEPTH24_STENCIL8),o!==n.R16F&&o!==n.R32F&&o!==n.RG16F&&o!==n.RG32F&&o!==n.RGBA16F&&o!==n.RGBA32F||a.get("EXT_color_buffer_float"),o}setTextureParameters(e,t){const{gl:r,extensions:s,backend:i}=this,n=c.getPrimaries(c.workingColorSpace),a=t.colorSpace===b?null:c.getPrimaries(t.colorSpace),o=t.colorSpace===b||n===a?r.NONE:r.BROWSER_DEFAULT_WEBGL;r.pixelStorei(r.UNPACK_FLIP_Y_WEBGL,t.flipY),r.pixelStorei(r.UNPACK_PREMULTIPLY_ALPHA_WEBGL,t.premultiplyAlpha),r.pixelStorei(r.UNPACK_ALIGNMENT,t.unpackAlignment),r.pixelStorei(r.UNPACK_COLORSPACE_CONVERSION_WEBGL,o),r.texParameteri(e,r.TEXTURE_WRAP_S,yN[t.wrapS]),r.texParameteri(e,r.TEXTURE_WRAP_T,yN[t.wrapT]),e!==r.TEXTURE_3D&&e!==r.TEXTURE_2D_ARRAY||t.isArrayTexture||r.texParameteri(e,r.TEXTURE_WRAP_R,yN[t.wrapR]),r.texParameteri(e,r.TEXTURE_MAG_FILTER,bN[t.magFilter]);const u=void 0!==t.mipmaps&&t.mipmaps.length>0,l=t.minFilter===Y&&u?V:t.minFilter;if(r.texParameteri(e,r.TEXTURE_MIN_FILTER,bN[l]),t.compareFunction&&(r.texParameteri(e,r.TEXTURE_COMPARE_MODE,r.COMPARE_REF_TO_TEXTURE),r.texParameteri(e,r.TEXTURE_COMPARE_FUNC,xN[t.compareFunction])),!0===s.has("EXT_texture_filter_anisotropic")){if(t.magFilter===v)return;if(t.minFilter!==Ge&&t.minFilter!==V)return;if(t.type===D&&!1===s.has("OES_texture_float_linear"))return;if(t.anisotropy>1){const n=s.get("EXT_texture_filter_anisotropic");r.texParameterf(e,n.TEXTURE_MAX_ANISOTROPY_EXT,Math.min(t.anisotropy,i.getMaxAnisotropy()))}}}createDefaultTexture(e){const{gl:t,backend:r,defaultTextures:s}=this,i=this.getGLTextureType(e);let n=s[i];void 0===n&&(n=t.createTexture(),r.state.bindTexture(i,n),t.texParameteri(i,t.TEXTURE_MIN_FILTER,t.NEAREST),t.texParameteri(i,t.TEXTURE_MAG_FILTER,t.NEAREST),s[i]=n),r.set(e,{textureGPU:n,glTextureType:i,isDefault:!0})}createTexture(e,t){const{gl:r,backend:s}=this,{levels:i,width:n,height:a,depth:o}=t,u=s.utils.convert(e.format,e.colorSpace),l=s.utils.convert(e.type),d=this.getInternalFormat(e.internalFormat,u,l,e.colorSpace,e.isVideoTexture),c=r.createTexture(),h=this.getGLTextureType(e);s.state.bindTexture(h,c),this.setTextureParameters(h,e),e.isArrayTexture||e.isDataArrayTexture||e.isCompressedArrayTexture?r.texStorage3D(r.TEXTURE_2D_ARRAY,i,d,n,a,o):e.isData3DTexture?r.texStorage3D(r.TEXTURE_3D,i,d,n,a,o):e.isVideoTexture||r.texStorage2D(h,i,d,n,a),s.set(e,{textureGPU:c,glTextureType:h,glFormat:u,glType:l,glInternalFormat:d})}copyBufferToTexture(e,t){const{gl:r,backend:s}=this,{textureGPU:i,glTextureType:n,glFormat:a,glType:o}=s.get(t),{width:u,height:l}=t.source.data;r.bindBuffer(r.PIXEL_UNPACK_BUFFER,e),s.state.bindTexture(n,i),r.pixelStorei(r.UNPACK_FLIP_Y_WEBGL,!1),r.pixelStorei(r.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!1),r.texSubImage2D(n,0,0,0,u,l,a,o,0),r.bindBuffer(r.PIXEL_UNPACK_BUFFER,null),s.state.unbindTexture()}updateTexture(e,t){const{gl:r}=this,{width:s,height:i}=t,{textureGPU:n,glTextureType:a,glFormat:o,glType:u,glInternalFormat:l}=this.backend.get(e);if(!e.isRenderTargetTexture&&void 0!==n)if(this.backend.state.bindTexture(a,n),this.setTextureParameters(a,e),e.isCompressedTexture){const s=e.mipmaps,i=t.image;for(let t=0;t0,c=t.renderTarget?t.renderTarget.height:this.backend.getDrawingBufferSize().y;if(d){const r=0!==a||0!==o;let d,h;if(!0===e.isDepthTexture?(d=s.DEPTH_BUFFER_BIT,h=s.DEPTH_ATTACHMENT,t.stencil&&(d|=s.STENCIL_BUFFER_BIT)):(d=s.COLOR_BUFFER_BIT,h=s.COLOR_ATTACHMENT0),r){const e=this.backend.get(t.renderTarget),r=e.framebuffers[t.getCacheKey()],h=e.msaaFrameBuffer;i.bindFramebuffer(s.DRAW_FRAMEBUFFER,r),i.bindFramebuffer(s.READ_FRAMEBUFFER,h);const p=c-o-l;s.blitFramebuffer(a,p,a+u,p+l,a,p,a+u,p+l,d,s.NEAREST),i.bindFramebuffer(s.READ_FRAMEBUFFER,r),i.bindTexture(s.TEXTURE_2D,n),s.copyTexSubImage2D(s.TEXTURE_2D,0,0,0,a,p,u,l),i.unbindTexture()}else{const e=s.createFramebuffer();i.bindFramebuffer(s.DRAW_FRAMEBUFFER,e),s.framebufferTexture2D(s.DRAW_FRAMEBUFFER,h,s.TEXTURE_2D,n,0),s.blitFramebuffer(0,0,u,l,0,0,u,l,d,s.NEAREST),s.deleteFramebuffer(e)}}else i.bindTexture(s.TEXTURE_2D,n),s.copyTexSubImage2D(s.TEXTURE_2D,0,0,0,a,c-l-o,u,l),i.unbindTexture();e.generateMipmaps&&this.generateMipmaps(e),this.backend._setFramebuffer(t)}setupRenderBufferStorage(e,t,r,s=!1){const{gl:i}=this,n=t.renderTarget,{depthTexture:a,depthBuffer:o,stencilBuffer:u,width:l,height:d}=n;if(i.bindRenderbuffer(i.RENDERBUFFER,e),o&&!u){let t=i.DEPTH_COMPONENT24;if(!0===s){this.extensions.get("WEBGL_multisampled_render_to_texture").renderbufferStorageMultisampleEXT(i.RENDERBUFFER,n.samples,t,l,d)}else r>0?(a&&a.isDepthTexture&&a.type===i.FLOAT&&(t=i.DEPTH_COMPONENT32F),i.renderbufferStorageMultisample(i.RENDERBUFFER,r,t,l,d)):i.renderbufferStorage(i.RENDERBUFFER,t,l,d);i.framebufferRenderbuffer(i.FRAMEBUFFER,i.DEPTH_ATTACHMENT,i.RENDERBUFFER,e)}else o&&u&&(r>0?i.renderbufferStorageMultisample(i.RENDERBUFFER,r,i.DEPTH24_STENCIL8,l,d):i.renderbufferStorage(i.RENDERBUFFER,i.DEPTH_STENCIL,l,d),i.framebufferRenderbuffer(i.FRAMEBUFFER,i.DEPTH_STENCIL_ATTACHMENT,i.RENDERBUFFER,e));i.bindRenderbuffer(i.RENDERBUFFER,null)}async copyTextureToBuffer(e,t,r,s,i,n){const{backend:a,gl:o}=this,{textureGPU:u,glFormat:l,glType:d}=this.backend.get(e),c=o.createFramebuffer();o.bindFramebuffer(o.READ_FRAMEBUFFER,c);const h=e.isCubeTexture?o.TEXTURE_CUBE_MAP_POSITIVE_X+n:o.TEXTURE_2D;o.framebufferTexture2D(o.READ_FRAMEBUFFER,o.COLOR_ATTACHMENT0,h,u,0);const p=this._getTypedArrayType(d),g=s*i*this._getBytesPerTexel(d,l),m=o.createBuffer();o.bindBuffer(o.PIXEL_PACK_BUFFER,m),o.bufferData(o.PIXEL_PACK_BUFFER,g,o.STREAM_READ),o.readPixels(t,r,s,i,l,d,0),o.bindBuffer(o.PIXEL_PACK_BUFFER,null),await a.utils._clientWaitAsync();const f=new p(g/p.BYTES_PER_ELEMENT);return o.bindBuffer(o.PIXEL_PACK_BUFFER,m),o.getBufferSubData(o.PIXEL_PACK_BUFFER,0,f),o.bindBuffer(o.PIXEL_PACK_BUFFER,null),o.deleteFramebuffer(c),f}_getTypedArrayType(e){const{gl:t}=this;if(e===t.UNSIGNED_BYTE)return Uint8Array;if(e===t.UNSIGNED_SHORT_4_4_4_4)return Uint16Array;if(e===t.UNSIGNED_SHORT_5_5_5_1)return Uint16Array;if(e===t.UNSIGNED_SHORT_5_6_5)return Uint16Array;if(e===t.UNSIGNED_SHORT)return Uint16Array;if(e===t.UNSIGNED_INT)return Uint32Array;if(e===t.HALF_FLOAT)return Uint16Array;if(e===t.FLOAT)return Float32Array;throw new Error(`Unsupported WebGL type: ${e}`)}_getBytesPerTexel(e,t){const{gl:r}=this;let s=0;return e===r.UNSIGNED_BYTE&&(s=1),e!==r.UNSIGNED_SHORT_4_4_4_4&&e!==r.UNSIGNED_SHORT_5_5_5_1&&e!==r.UNSIGNED_SHORT_5_6_5&&e!==r.UNSIGNED_SHORT&&e!==r.HALF_FLOAT||(s=2),e!==r.UNSIGNED_INT&&e!==r.FLOAT||(s=4),t===r.RGBA?4*s:t===r.RGB?3*s:t===r.ALPHA?s:void 0}}function vN(e){return e.isDataTexture?e.image.data:"undefined"!=typeof HTMLImageElement&&e instanceof HTMLImageElement||"undefined"!=typeof HTMLCanvasElement&&e instanceof HTMLCanvasElement||"undefined"!=typeof ImageBitmap&&e instanceof ImageBitmap||"undefined"!=typeof OffscreenCanvas&&e instanceof OffscreenCanvas?e:e.data}class NN{constructor(e){this.backend=e,this.gl=this.backend.gl,this.availableExtensions=this.gl.getSupportedExtensions(),this.extensions={}}get(e){let t=this.extensions[e];return void 0===t&&(t=this.gl.getExtension(e),this.extensions[e]=t),t}has(e){return this.availableExtensions.includes(e)}}class SN{constructor(e){this.backend=e,this.maxAnisotropy=null}getMaxAnisotropy(){if(null!==this.maxAnisotropy)return this.maxAnisotropy;const e=this.backend.gl,t=this.backend.extensions;if(!0===t.has("EXT_texture_filter_anisotropic")){const r=t.get("EXT_texture_filter_anisotropic");this.maxAnisotropy=e.getParameter(r.MAX_TEXTURE_MAX_ANISOTROPY_EXT)}else this.maxAnisotropy=0;return this.maxAnisotropy}}const EN={WEBGL_multi_draw:"WEBGL_multi_draw",WEBGL_compressed_texture_astc:"texture-compression-astc",WEBGL_compressed_texture_etc:"texture-compression-etc2",WEBGL_compressed_texture_etc1:"texture-compression-etc1",WEBGL_compressed_texture_pvrtc:"texture-compression-pvrtc",WEBKIT_WEBGL_compressed_texture_pvrtc:"texture-compression-pvrtc",WEBGL_compressed_texture_s3tc:"texture-compression-bc",EXT_texture_compression_bptc:"texture-compression-bptc",EXT_disjoint_timer_query_webgl2:"timestamp-query",OVR_multiview2:"OVR_multiview2"};class wN{constructor(e){this.gl=e.gl,this.extensions=e.extensions,this.info=e.renderer.info,this.mode=null,this.index=0,this.type=null,this.object=null}render(e,t){const{gl:r,mode:s,object:i,type:n,info:a,index:o}=this;0!==o?r.drawElements(s,t,n,e):r.drawArrays(s,e,t),a.update(i,t,1)}renderInstances(e,t,r){const{gl:s,mode:i,type:n,index:a,object:o,info:u}=this;0!==r&&(0!==a?s.drawElementsInstanced(i,t,n,e,r):s.drawArraysInstanced(i,e,t,r),u.update(o,t,r))}renderMultiDraw(e,t,r){const{extensions:s,mode:i,object:n,info:a}=this;if(0===r)return;const o=s.get("WEBGL_multi_draw");if(null===o)for(let s=0;sthis.maxQueries)return pt(`WebGPUTimestampQueryPool [${this.type}]: Maximum number of queries exceeded, when using trackTimestamp it is necessary to resolves the queries via renderer.resolveTimestampsAsync( THREE.TimestampQuery.${this.type.toUpperCase()} ).`),null;const t=this.currentQueryIndex;return this.currentQueryIndex+=2,this.queryStates.set(t,"inactive"),this.queryOffsets.set(e.id,t),t}beginQuery(e){if(!this.trackTimestamp||this.isDisposed)return;const t=this.queryOffsets.get(e.id);if(null==t)return;if(null!==this.activeQuery)return;const r=this.queries[t];if(r)try{"inactive"===this.queryStates.get(t)&&(this.gl.beginQuery(this.ext.TIME_ELAPSED_EXT,r),this.activeQuery=t,this.queryStates.set(t,"started"))}catch(e){console.error("Error in beginQuery:",e),this.activeQuery=null,this.queryStates.set(t,"inactive")}}endQuery(e){if(!this.trackTimestamp||this.isDisposed)return;const t=this.queryOffsets.get(e.id);if(null!=t&&this.activeQuery===t)try{this.gl.endQuery(this.ext.TIME_ELAPSED_EXT),this.queryStates.set(t,"ended"),this.activeQuery=null}catch(e){console.error("Error in endQuery:",e),this.queryStates.set(t,"inactive"),this.activeQuery=null}}async resolveQueriesAsync(){if(!this.trackTimestamp||this.pendingResolve)return this.lastValue;this.pendingResolve=!0;try{const e=[];for(const[t,r]of this.queryStates)if("ended"===r){const r=this.queries[t];e.push(this.resolveQuery(r))}if(0===e.length)return this.lastValue;const t=(await Promise.all(e)).reduce((e,t)=>e+t,0);return this.lastValue=t,this.currentQueryIndex=0,this.queryOffsets.clear(),this.queryStates.clear(),this.activeQuery=null,t}catch(e){return console.error("Error resolving queries:",e),this.lastValue}finally{this.pendingResolve=!1}}async resolveQuery(e){return new Promise(t=>{if(this.isDisposed)return void t(this.lastValue);let r,s=!1;const i=e=>{s||(s=!0,r&&(clearTimeout(r),r=null),t(e))},n=()=>{if(this.isDisposed)i(this.lastValue);else try{if(this.gl.getParameter(this.ext.GPU_DISJOINT_EXT))return void i(this.lastValue);if(!this.gl.getQueryParameter(e,this.gl.QUERY_RESULT_AVAILABLE))return void(r=setTimeout(n,1));const s=this.gl.getQueryParameter(e,this.gl.QUERY_RESULT);t(Number(s)/1e6)}catch(e){console.error("Error checking query:",e),t(this.lastValue)}};n()})}dispose(){if(!this.isDisposed&&(this.isDisposed=!0,this.trackTimestamp)){for(const e of this.queries)this.gl.deleteQuery(e);this.queries=[],this.queryStates.clear(),this.queryOffsets.clear(),this.lastValue=0,this.activeQuery=null}}}const CN=new t;class MN extends lN{constructor(e={}){super(e),this.isWebGLBackend=!0,this.attributeUtils=null,this.extensions=null,this.capabilities=null,this.textureUtils=null,this.bufferRenderer=null,this.gl=null,this.state=null,this.utils=null,this.vaoCache={},this.transformFeedbackCache={},this.discard=!1,this.disjoint=null,this.parallel=null,this._currentContext=null,this._knownBindings=new WeakSet,this._supportsInvalidateFramebuffer="undefined"!=typeof navigator&&/OculusBrowser/g.test(navigator.userAgent),this._xrFramebuffer=null}init(e){super.init(e);const t=this.parameters,r={antialias:e.samples>0,alpha:!0,depth:e.depth,stencil:e.stencil},s=void 0!==t.context?t.context:e.domElement.getContext("webgl2",r);function i(t){t.preventDefault();const r={api:"WebGL",message:t.statusMessage||"Unknown reason",reason:null,originalEvent:t};e.onDeviceLost(r)}this._onContextLost=i,e.domElement.addEventListener("webglcontextlost",i,!1),this.gl=s,this.extensions=new NN(this),this.capabilities=new SN(this),this.attributeUtils=new gN(this),this.textureUtils=new _N(this),this.bufferRenderer=new wN(this),this.state=new mN(this),this.utils=new fN(this),this.extensions.get("EXT_color_buffer_float"),this.extensions.get("WEBGL_clip_cull_distance"),this.extensions.get("OES_texture_float_linear"),this.extensions.get("EXT_color_buffer_half_float"),this.extensions.get("WEBGL_multisampled_render_to_texture"),this.extensions.get("WEBGL_render_shared_exponent"),this.extensions.get("WEBGL_multi_draw"),this.extensions.get("OVR_multiview2"),this.disjoint=this.extensions.get("EXT_disjoint_timer_query_webgl2"),this.parallel=this.extensions.get("KHR_parallel_shader_compile")}get coordinateSystem(){return l}async getArrayBufferAsync(e){return await this.attributeUtils.getArrayBufferAsync(e)}async waitForGPU(){await this.utils._clientWaitAsync()}async makeXRCompatible(){!0!==this.gl.getContextAttributes().xrCompatible&&await this.gl.makeXRCompatible()}setXRTarget(e){this._xrFramebuffer=e}setXRRenderTargetTextures(e,t,r=null){const s=this.gl;if(this.set(e.texture,{textureGPU:t,glInternalFormat:s.RGBA8}),null!==r){const t=e.stencilBuffer?s.DEPTH24_STENCIL8:s.DEPTH_COMPONENT24;this.set(e.depthTexture,{textureGPU:r,glInternalFormat:t}),!0===this.extensions.has("WEBGL_multisampled_render_to_texture")&&!0===e._autoAllocateDepthBuffer&&!1===e.multiview&&console.warn("THREE.WebGLBackend: Render-to-texture extension was disabled because an external texture was provided"),e._autoAllocateDepthBuffer=!1}}initTimestampQuery(e){if(!this.disjoint||!this.trackTimestamp)return;const t=e.isComputeNode?"compute":"render";this.timestampQueryPool[t]||(this.timestampQueryPool[t]=new RN(this.gl,t,2048));const r=this.timestampQueryPool[t];null!==r.allocateQueriesForContext(e)&&r.beginQuery(e)}prepareTimestampBuffer(e){if(!this.disjoint||!this.trackTimestamp)return;const t=e.isComputeNode?"compute":"render";this.timestampQueryPool[t].endQuery(e)}getContext(){return this.gl}beginRender(e){const{state:t}=this,r=this.get(e);if(e.viewport)this.updateViewport(e);else{const{width:e,height:r}=this.getDrawingBufferSize(CN);t.viewport(0,0,e,r)}if(e.scissor){const{x:r,y:s,width:i,height:n}=e.scissorValue;t.scissor(r,e.height-n-s,i,n)}this.initTimestampQuery(e),r.previousContext=this._currentContext,this._currentContext=e,this._setFramebuffer(e),this.clear(e.clearColor,e.clearDepth,e.clearStencil,e,!1);const s=e.occlusionQueryCount;s>0&&(r.currentOcclusionQueries=r.occlusionQueries,r.currentOcclusionQueryObjects=r.occlusionQueryObjects,r.lastOcclusionObject=null,r.occlusionQueries=new Array(s),r.occlusionQueryObjects=new Array(s),r.occlusionQueryIndex=0)}finishRender(e){const{gl:t,state:r}=this,s=this.get(e),i=s.previousContext;r.resetVertexState();const n=e.occlusionQueryCount;n>0&&(n>s.occlusionQueryIndex&&t.endQuery(t.ANY_SAMPLES_PASSED),this.resolveOccludedAsync(e));const a=e.textures;if(null!==a)for(let e=0;e0&&!1===this._useMultisampledExtension(o)){const i=s.framebuffers[e.getCacheKey()];let n=t.COLOR_BUFFER_BIT;o.resolveDepthBuffer&&(o.depthBuffer&&(n|=t.DEPTH_BUFFER_BIT),o.stencilBuffer&&o.resolveStencilBuffer&&(n|=t.STENCIL_BUFFER_BIT));const a=s.msaaFrameBuffer,u=s.msaaRenderbuffers,l=e.textures,d=l.length>1;if(r.bindFramebuffer(t.READ_FRAMEBUFFER,a),r.bindFramebuffer(t.DRAW_FRAMEBUFFER,i),d)for(let e=0;e{let a=0;for(let t=0;t{t.isBatchedMesh?null!==t._multiDrawInstances?(pt("THREE.WebGLBackend: renderMultiDrawInstances has been deprecated and will be removed in r184. Append to renderMultiDraw arguments and use indirection."),b.renderMultiDrawInstances(t._multiDrawStarts,t._multiDrawCounts,t._multiDrawCount,t._multiDrawInstances)):this.hasFeature("WEBGL_multi_draw")?b.renderMultiDraw(t._multiDrawStarts,t._multiDrawCounts,t._multiDrawCount):pt("THREE.WebGLRenderer: WEBGL_multi_draw not supported."):T>1?b.renderInstances(_,x,T):b.render(_,x)};if(!0===e.camera.isArrayCamera&&e.camera.cameras.length>0&&!1===e.camera.isMultiViewCamera){const r=this.get(e.camera),s=e.camera.cameras,i=e.getBindingGroup("cameraIndex").bindings[0];if(void 0===r.indexesGPU||r.indexesGPU.length!==s.length){const e=new Uint32Array([0,0,0,0]),t=[];for(let r=0,i=s.length;r{const i=this.parallel,n=()=>{r.getProgramParameter(a,i.COMPLETION_STATUS_KHR)?(this._completeCompile(e,s),t()):requestAnimationFrame(n)};n()});return void t.push(i)}this._completeCompile(e,s)}_handleSource(e,t){const r=e.split("\n"),s=[],i=Math.max(t-6,0),n=Math.min(t+6,r.length);for(let e=i;e":" "} ${i}: ${r[e]}`)}return s.join("\n")}_getShaderErrors(e,t,r){const s=e.getShaderParameter(t,e.COMPILE_STATUS),i=e.getShaderInfoLog(t).trim();if(s&&""===i)return"";const n=/ERROR: 0:(\d+)/.exec(i);if(n){const s=parseInt(n[1]);return r.toUpperCase()+"\n\n"+i+"\n\n"+this._handleSource(e.getShaderSource(t),s)}return i}_logProgramError(e,t,r){if(this.renderer.debug.checkShaderErrors){const s=this.gl,i=s.getProgramInfoLog(e).trim();if(!1===s.getProgramParameter(e,s.LINK_STATUS))if("function"==typeof this.renderer.debug.onShaderError)this.renderer.debug.onShaderError(s,e,r,t);else{const n=this._getShaderErrors(s,r,"vertex"),a=this._getShaderErrors(s,t,"fragment");console.error("THREE.WebGLProgram: Shader Error "+s.getError()+" - VALIDATE_STATUS "+s.getProgramParameter(e,s.VALIDATE_STATUS)+"\n\nProgram Info Log: "+i+"\n"+n+"\n"+a)}else""!==i&&console.warn("THREE.WebGLProgram: Program Info Log:",i)}}_completeCompile(e,t){const{state:r,gl:s}=this,i=this.get(t),{programGPU:n,fragmentShader:a,vertexShader:o}=i;!1===s.getProgramParameter(n,s.LINK_STATUS)&&this._logProgramError(n,a,o),r.useProgram(n);const u=e.getBindings();this._setupBindings(u,n),this.set(t,{programGPU:n})}createComputePipeline(e,t){const{state:r,gl:s}=this,i={stage:"fragment",code:"#version 300 es\nprecision highp float;\nvoid main() {}"};this.createProgram(i);const{computeProgram:n}=e,a=s.createProgram(),o=this.get(i).shaderGPU,u=this.get(n).shaderGPU,l=n.transforms,d=[],c=[];for(let e=0;eEN[t]===e),r=this.extensions;for(let e=0;e1,h=!0===i.isXRRenderTarget,p=!0===h&&!0===i._hasExternalTextures;let g=n.msaaFrameBuffer,m=n.depthRenderbuffer;const f=this.extensions.get("WEBGL_multisampled_render_to_texture"),y=this.extensions.get("OVR_multiview2"),b=this._useMultisampledExtension(i),x=Vf(e);let T;if(l?(n.cubeFramebuffers||(n.cubeFramebuffers={}),T=n.cubeFramebuffers[x]):h&&!1===p?T=this._xrFramebuffer:(n.framebuffers||(n.framebuffers={}),T=n.framebuffers[x]),void 0===T){T=t.createFramebuffer(),r.bindFramebuffer(t.FRAMEBUFFER,T);const s=e.textures,o=[];if(l){n.cubeFramebuffers[x]=T;const{textureGPU:e}=this.get(s[0]),r=this.renderer._activeCubeFace;t.framebufferTexture2D(t.FRAMEBUFFER,t.COLOR_ATTACHMENT0,t.TEXTURE_CUBE_MAP_POSITIVE_X+r,e,0)}else{n.framebuffers[x]=T;for(let r=0;r0&&!1===b&&!i.multiview){if(void 0===g){const s=[];g=t.createFramebuffer(),r.bindFramebuffer(t.FRAMEBUFFER,g);const i=[],l=e.textures;for(let r=0;r0&&!0===this.extensions.has("WEBGL_multisampled_render_to_texture")&&!1!==e._autoAllocateDepthBuffer}dispose(){const e=this.extensions.get("WEBGL_lose_context");e&&e.loseContext(),this.renderer.domElement.removeEventListener("webglcontextlost",this._onContextLost)}}const PN="point-list",BN="line-list",FN="line-strip",LN="triangle-list",DN="triangle-strip",IN="never",VN="less",UN="equal",ON="less-equal",kN="greater",GN="not-equal",zN="greater-equal",HN="always",$N="store",WN="load",jN="clear",qN="ccw",XN="none",KN="front",YN="back",QN="uint16",ZN="uint32",JN="r8unorm",eS="r8snorm",tS="r8uint",rS="r8sint",sS="r16uint",iS="r16sint",nS="r16float",aS="rg8unorm",oS="rg8snorm",uS="rg8uint",lS="rg8sint",dS="r32uint",cS="r32sint",hS="r32float",pS="rg16uint",gS="rg16sint",mS="rg16float",fS="rgba8unorm",yS="rgba8unorm-srgb",bS="rgba8snorm",xS="rgba8uint",TS="rgba8sint",_S="bgra8unorm",vS="bgra8unorm-srgb",NS="rgb9e5ufloat",SS="rgb10a2unorm",ES="rgb10a2unorm",wS="rg32uint",AS="rg32sint",RS="rg32float",CS="rgba16uint",MS="rgba16sint",PS="rgba16float",BS="rgba32uint",FS="rgba32sint",LS="rgba32float",DS="depth16unorm",IS="depth24plus",VS="depth24plus-stencil8",US="depth32float",OS="depth32float-stencil8",kS="bc1-rgba-unorm",GS="bc1-rgba-unorm-srgb",zS="bc2-rgba-unorm",HS="bc2-rgba-unorm-srgb",$S="bc3-rgba-unorm",WS="bc3-rgba-unorm-srgb",jS="bc4-r-unorm",qS="bc4-r-snorm",XS="bc5-rg-unorm",KS="bc5-rg-snorm",YS="bc6h-rgb-ufloat",QS="bc6h-rgb-float",ZS="bc7-rgba-unorm",JS="bc7-rgba-srgb",eE="etc2-rgb8unorm",tE="etc2-rgb8unorm-srgb",rE="etc2-rgb8a1unorm",sE="etc2-rgb8a1unorm-srgb",iE="etc2-rgba8unorm",nE="etc2-rgba8unorm-srgb",aE="eac-r11unorm",oE="eac-r11snorm",uE="eac-rg11unorm",lE="eac-rg11snorm",dE="astc-4x4-unorm",cE="astc-4x4-unorm-srgb",hE="astc-5x4-unorm",pE="astc-5x4-unorm-srgb",gE="astc-5x5-unorm",mE="astc-5x5-unorm-srgb",fE="astc-6x5-unorm",yE="astc-6x5-unorm-srgb",bE="astc-6x6-unorm",xE="astc-6x6-unorm-srgb",TE="astc-8x5-unorm",_E="astc-8x5-unorm-srgb",vE="astc-8x6-unorm",NE="astc-8x6-unorm-srgb",SE="astc-8x8-unorm",EE="astc-8x8-unorm-srgb",wE="astc-10x5-unorm",AE="astc-10x5-unorm-srgb",RE="astc-10x6-unorm",CE="astc-10x6-unorm-srgb",ME="astc-10x8-unorm",PE="astc-10x8-unorm-srgb",BE="astc-10x10-unorm",FE="astc-10x10-unorm-srgb",LE="astc-12x10-unorm",DE="astc-12x10-unorm-srgb",IE="astc-12x12-unorm",VE="astc-12x12-unorm-srgb",UE="clamp-to-edge",OE="repeat",kE="mirror-repeat",GE="linear",zE="nearest",HE="zero",$E="one",WE="src",jE="one-minus-src",qE="src-alpha",XE="one-minus-src-alpha",KE="dst",YE="one-minus-dst",QE="dst-alpha",ZE="one-minus-dst-alpha",JE="src-alpha-saturated",ew="constant",tw="one-minus-constant",rw="add",sw="subtract",iw="reverse-subtract",nw="min",aw="max",ow=0,uw=15,lw="keep",dw="zero",cw="replace",hw="invert",pw="increment-clamp",gw="decrement-clamp",mw="increment-wrap",fw="decrement-wrap",yw="storage",bw="read-only-storage",xw="write-only",Tw="read-only",_w="read-write",vw="non-filtering",Nw="comparison",Sw="float",Ew="unfilterable-float",ww="depth",Aw="sint",Rw="uint",Cw="2d",Mw="3d",Pw="2d",Bw="2d-array",Fw="cube",Lw="3d",Dw="all",Iw="vertex",Vw="instance",Uw={CoreFeaturesAndLimits:"core-features-and-limits",DepthClipControl:"depth-clip-control",Depth32FloatStencil8:"depth32float-stencil8",TextureCompressionBC:"texture-compression-bc",TextureCompressionBCSliced3D:"texture-compression-bc-sliced-3d",TextureCompressionETC2:"texture-compression-etc2",TextureCompressionASTC:"texture-compression-astc",TextureCompressionASTCSliced3D:"texture-compression-astc-sliced-3d",TimestampQuery:"timestamp-query",IndirectFirstInstance:"indirect-first-instance",ShaderF16:"shader-f16",RG11B10UFloat:"rg11b10ufloat-renderable",BGRA8UNormStorage:"bgra8unorm-storage",Float32Filterable:"float32-filterable",Float32Blendable:"float32-blendable",ClipDistances:"clip-distances",DualSourceBlending:"dual-source-blending",Subgroups:"subgroups",TextureFormatsTier1:"texture-formats-tier1",TextureFormatsTier2:"texture-formats-tier2"};class Ow extends Gv{constructor(e,t){super(e),this.texture=t,this.version=t?t.version:0,this.isSampler=!0}}class kw extends Ow{constructor(e,t,r){super(e,t?t.value:null),this.textureNode=t,this.groupNode=r}update(){this.texture=this.textureNode.value}}class Gw extends zv{constructor(e,t){super(e,t?t.array:null),this.attribute=t,this.isStorageBuffer=!0}}let zw=0;class Hw extends Gw{constructor(e,t){super("StorageBuffer_"+zw++,e?e.value:null),this.nodeUniform=e,this.access=e?e.access:Os.READ_WRITE,this.groupNode=t}get buffer(){return this.nodeUniform.value}}class $w extends cf{constructor(e){super(),this.device=e;this.mipmapSampler=e.createSampler({minFilter:GE}),this.flipYSampler=e.createSampler({minFilter:zE}),this.transferPipelines={},this.flipYPipelines={},this.mipmapVertexShaderModule=e.createShaderModule({label:"mipmapVertex",code:"\nstruct VarysStruct {\n\t@builtin( position ) Position: vec4,\n\t@location( 0 ) vTex : vec2\n};\n\n@vertex\nfn main( @builtin( vertex_index ) vertexIndex : u32 ) -> VarysStruct {\n\n\tvar Varys : VarysStruct;\n\n\tvar pos = array< vec2, 4 >(\n\t\tvec2( -1.0, 1.0 ),\n\t\tvec2( 1.0, 1.0 ),\n\t\tvec2( -1.0, -1.0 ),\n\t\tvec2( 1.0, -1.0 )\n\t);\n\n\tvar tex = array< vec2, 4 >(\n\t\tvec2( 0.0, 0.0 ),\n\t\tvec2( 1.0, 0.0 ),\n\t\tvec2( 0.0, 1.0 ),\n\t\tvec2( 1.0, 1.0 )\n\t);\n\n\tVarys.vTex = tex[ vertexIndex ];\n\tVarys.Position = vec4( pos[ vertexIndex ], 0.0, 1.0 );\n\n\treturn Varys;\n\n}\n"}),this.mipmapFragmentShaderModule=e.createShaderModule({label:"mipmapFragment",code:"\n@group( 0 ) @binding( 0 )\nvar imgSampler : sampler;\n\n@group( 0 ) @binding( 1 )\nvar img : texture_2d;\n\n@fragment\nfn main( @location( 0 ) vTex : vec2 ) -> @location( 0 ) vec4 {\n\n\treturn textureSample( img, imgSampler, vTex );\n\n}\n"}),this.flipYFragmentShaderModule=e.createShaderModule({label:"flipYFragment",code:"\n@group( 0 ) @binding( 0 )\nvar imgSampler : sampler;\n\n@group( 0 ) @binding( 1 )\nvar img : texture_2d;\n\n@fragment\nfn main( @location( 0 ) vTex : vec2 ) -> @location( 0 ) vec4 {\n\n\treturn textureSample( img, imgSampler, vec2( vTex.x, 1.0 - vTex.y ) );\n\n}\n"})}getTransferPipeline(e){let t=this.transferPipelines[e];return void 0===t&&(t=this.device.createRenderPipeline({label:`mipmap-${e}`,vertex:{module:this.mipmapVertexShaderModule,entryPoint:"main"},fragment:{module:this.mipmapFragmentShaderModule,entryPoint:"main",targets:[{format:e}]},primitive:{topology:DN,stripIndexFormat:ZN},layout:"auto"}),this.transferPipelines[e]=t),t}getFlipYPipeline(e){let t=this.flipYPipelines[e];return void 0===t&&(t=this.device.createRenderPipeline({label:`flipY-${e}`,vertex:{module:this.mipmapVertexShaderModule,entryPoint:"main"},fragment:{module:this.flipYFragmentShaderModule,entryPoint:"main",targets:[{format:e}]},primitive:{topology:DN,stripIndexFormat:ZN},layout:"auto"}),this.flipYPipelines[e]=t),t}flipY(e,t,r=0){const s=t.format,{width:i,height:n}=t.size,a=this.getTransferPipeline(s),o=this.getFlipYPipeline(s),u=this.device.createTexture({size:{width:i,height:n,depthOrArrayLayers:1},format:s,usage:GPUTextureUsage.RENDER_ATTACHMENT|GPUTextureUsage.TEXTURE_BINDING}),l=e.createView({baseMipLevel:0,mipLevelCount:1,dimension:Pw,baseArrayLayer:r}),d=u.createView({baseMipLevel:0,mipLevelCount:1,dimension:Pw,baseArrayLayer:0}),c=this.device.createCommandEncoder({}),h=(e,t,r)=>{const s=e.getBindGroupLayout(0),i=this.device.createBindGroup({layout:s,entries:[{binding:0,resource:this.flipYSampler},{binding:1,resource:t}]}),n=c.beginRenderPass({colorAttachments:[{view:r,loadOp:jN,storeOp:$N,clearValue:[0,0,0,0]}]});n.setPipeline(e),n.setBindGroup(0,i),n.draw(4,1,0,0),n.end()};h(a,l,d),h(o,d,l),this.device.queue.submit([c.finish()]),u.destroy()}generateMipmaps(e,t,r=0){const s=this.get(e);void 0===s.useCount&&(s.useCount=0,s.layers=[]);const i=s.layers[r]||this._mipmapCreateBundles(e,t,r),n=this.device.createCommandEncoder({});this._mipmapRunBundles(n,i),this.device.queue.submit([n.finish()]),0!==s.useCount&&(s.layers[r]=i),s.useCount++}_mipmapCreateBundles(e,t,r){const s=this.getTransferPipeline(t.format),i=s.getBindGroupLayout(0);let n=e.createView({baseMipLevel:0,mipLevelCount:1,dimension:Pw,baseArrayLayer:r});const a=[];for(let o=1;o1;for(let a=0;a]*\s*([a-z_0-9]+(?:<[\s\S]+?>)?)/i,Yw=/([a-z_0-9]+)\s*:\s*([a-z_0-9]+(?:<[\s\S]+?>)?)/gi,Qw={f32:"float",i32:"int",u32:"uint",bool:"bool","vec2":"vec2","vec2":"ivec2","vec2":"uvec2","vec2":"bvec2",vec2f:"vec2",vec2i:"ivec2",vec2u:"uvec2",vec2b:"bvec2","vec3":"vec3","vec3":"ivec3","vec3":"uvec3","vec3":"bvec3",vec3f:"vec3",vec3i:"ivec3",vec3u:"uvec3",vec3b:"bvec3","vec4":"vec4","vec4":"ivec4","vec4":"uvec4","vec4":"bvec4",vec4f:"vec4",vec4i:"ivec4",vec4u:"uvec4",vec4b:"bvec4","mat2x2":"mat2",mat2x2f:"mat2","mat3x3":"mat3",mat3x3f:"mat3","mat4x4":"mat4",mat4x4f:"mat4",sampler:"sampler",texture_1d:"texture",texture_2d:"texture",texture_2d_array:"texture",texture_multisampled_2d:"cubeTexture",texture_depth_2d:"depthTexture",texture_depth_2d_array:"depthTexture",texture_depth_multisampled_2d:"depthTexture",texture_depth_cube:"depthTexture",texture_depth_cube_array:"depthTexture",texture_3d:"texture3D",texture_cube:"cubeTexture",texture_cube_array:"cubeTexture",texture_storage_1d:"storageTexture",texture_storage_2d:"storageTexture",texture_storage_2d_array:"storageTexture",texture_storage_3d:"storageTexture"};class Zw extends iv{constructor(e){const{type:t,inputs:r,name:s,inputsCode:i,blockCode:n,outputType:a}=(e=>{const t=(e=e.trim()).match(Kw);if(null!==t&&4===t.length){const r=t[2],s=[];let i=null;for(;null!==(i=Yw.exec(r));)s.push({name:i[1],type:i[2]});const n=[];for(let e=0;e "+this.outputType:"";return`fn ${e} ( ${this.inputsCode.trim()} ) ${t}`+this.blockCode}}class Jw extends sv{parseFunction(e){return new Zw(e)}}const eA="undefined"!=typeof self?self.GPUShaderStage:{VERTEX:1,FRAGMENT:2,COMPUTE:4},tA={[Os.READ_ONLY]:"read",[Os.WRITE_ONLY]:"write",[Os.READ_WRITE]:"read_write"},rA={[Nr]:"repeat",[vr]:"clamp",[_r]:"mirror"},sA={vertex:eA?eA.VERTEX:1,fragment:eA?eA.FRAGMENT:2,compute:eA?eA.COMPUTE:4},iA={instance:!0,swizzleAssign:!1,storageBuffer:!0},nA={"^^":"tsl_xor"},aA={float:"f32",int:"i32",uint:"u32",bool:"bool",color:"vec3",vec2:"vec2",ivec2:"vec2",uvec2:"vec2",bvec2:"vec2",vec3:"vec3",ivec3:"vec3",uvec3:"vec3",bvec3:"vec3",vec4:"vec4",ivec4:"vec4",uvec4:"vec4",bvec4:"vec4",mat2:"mat2x2",mat3:"mat3x3",mat4:"mat4x4"},oA={},uA={tsl_xor:new Ib("fn tsl_xor( a : bool, b : bool ) -> bool { return ( a || b ) && !( a && b ); }"),mod_float:new Ib("fn tsl_mod_float( x : f32, y : f32 ) -> f32 { return x - y * floor( x / y ); }"),mod_vec2:new Ib("fn tsl_mod_vec2( x : vec2f, y : vec2f ) -> vec2f { return x - y * floor( x / y ); }"),mod_vec3:new Ib("fn tsl_mod_vec3( x : vec3f, y : vec3f ) -> vec3f { return x - y * floor( x / y ); }"),mod_vec4:new Ib("fn tsl_mod_vec4( x : vec4f, y : vec4f ) -> vec4f { return x - y * floor( x / y ); }"),equals_bool:new Ib("fn tsl_equals_bool( a : bool, b : bool ) -> bool { return a == b; }"),equals_bvec2:new Ib("fn tsl_equals_bvec2( a : vec2f, b : vec2f ) -> vec2 { return vec2( a.x == b.x, a.y == b.y ); }"),equals_bvec3:new Ib("fn tsl_equals_bvec3( a : vec3f, b : vec3f ) -> vec3 { return vec3( a.x == b.x, a.y == b.y, a.z == b.z ); }"),equals_bvec4:new Ib("fn tsl_equals_bvec4( a : vec4f, b : vec4f ) -> vec4 { return vec4( a.x == b.x, a.y == b.y, a.z == b.z, a.w == b.w ); }"),repeatWrapping_float:new Ib("fn tsl_repeatWrapping_float( coord: f32 ) -> f32 { return fract( coord ); }"),mirrorWrapping_float:new Ib("fn tsl_mirrorWrapping_float( coord: f32 ) -> f32 { let mirrored = fract( coord * 0.5 ) * 2.0; return 1.0 - abs( 1.0 - mirrored ); }"),clampWrapping_float:new Ib("fn tsl_clampWrapping_float( coord: f32 ) -> f32 { return clamp( coord, 0.0, 1.0 ); }"),biquadraticTexture:new Ib("\nfn tsl_biquadraticTexture( map : texture_2d, coord : vec2f, iRes : vec2u, level : u32 ) -> vec4f {\n\n\tlet res = vec2f( iRes );\n\n\tlet uvScaled = coord * res;\n\tlet uvWrapping = ( ( uvScaled % res ) + res ) % res;\n\n\t// https://www.shadertoy.com/view/WtyXRy\n\n\tlet uv = uvWrapping - 0.5;\n\tlet iuv = floor( uv );\n\tlet f = fract( uv );\n\n\tlet rg1 = textureLoad( map, vec2u( iuv + vec2( 0.5, 0.5 ) ) % iRes, level );\n\tlet rg2 = textureLoad( map, vec2u( iuv + vec2( 1.5, 0.5 ) ) % iRes, level );\n\tlet rg3 = textureLoad( map, vec2u( iuv + vec2( 0.5, 1.5 ) ) % iRes, level );\n\tlet rg4 = textureLoad( map, vec2u( iuv + vec2( 1.5, 1.5 ) ) % iRes, level );\n\n\treturn mix( mix( rg1, rg2, f.x ), mix( rg3, rg4, f.x ), f.y );\n\n}\n")},lA={dFdx:"dpdx",dFdy:"- dpdy",mod_float:"tsl_mod_float",mod_vec2:"tsl_mod_vec2",mod_vec3:"tsl_mod_vec3",mod_vec4:"tsl_mod_vec4",equals_bool:"tsl_equals_bool",equals_bvec2:"tsl_equals_bvec2",equals_bvec3:"tsl_equals_bvec3",equals_bvec4:"tsl_equals_bvec4",inversesqrt:"inverseSqrt",bitcast:"bitcast"};"undefined"!=typeof navigator&&/Windows/g.test(navigator.userAgent)&&(uA.pow_float=new Ib("fn tsl_pow_float( a : f32, b : f32 ) -> f32 { return select( -pow( -a, b ), pow( a, b ), a > 0.0 ); }"),uA.pow_vec2=new Ib("fn tsl_pow_vec2( a : vec2f, b : vec2f ) -> vec2f { return vec2f( tsl_pow_float( a.x, b.x ), tsl_pow_float( a.y, b.y ) ); }",[uA.pow_float]),uA.pow_vec3=new Ib("fn tsl_pow_vec3( a : vec3f, b : vec3f ) -> vec3f { return vec3f( tsl_pow_float( a.x, b.x ), tsl_pow_float( a.y, b.y ), tsl_pow_float( a.z, b.z ) ); }",[uA.pow_float]),uA.pow_vec4=new Ib("fn tsl_pow_vec4( a : vec4f, b : vec4f ) -> vec4f { return vec4f( tsl_pow_float( a.x, b.x ), tsl_pow_float( a.y, b.y ), tsl_pow_float( a.z, b.z ), tsl_pow_float( a.w, b.w ) ); }",[uA.pow_float]),lA.pow_float="tsl_pow_float",lA.pow_vec2="tsl_pow_vec2",lA.pow_vec3="tsl_pow_vec3",lA.pow_vec4="tsl_pow_vec4");let dA="";!0!==("undefined"!=typeof navigator&&/Firefox|Deno/g.test(navigator.userAgent))&&(dA+="diagnostic( off, derivative_uniformity );\n");class cA extends z_{constructor(e,t){super(e,t,new Jw),this.uniformGroups={},this.builtins={},this.directives={},this.scopedArrays=new Map}needsToWorkingColorSpace(e){return!0===e.isVideoTexture&&e.colorSpace!==b}_generateTextureSample(e,t,r,s,i=this.shaderStage){return"fragment"===i?s?`textureSample( ${t}, ${t}_sampler, ${r}, ${s} )`:`textureSample( ${t}, ${t}_sampler, ${r} )`:this._generateTextureSampleLevel(e,t,r,"0",s)}_generateVideoSample(e,t,r=this.shaderStage){if("fragment"===r)return`textureSampleBaseClampToEdge( ${e}, ${e}_sampler, vec2( ${t}.x, 1.0 - ${t}.y ) )`;console.error(`WebGPURenderer: THREE.VideoTexture does not support ${r} shader.`)}_generateTextureSampleLevel(e,t,r,s,i){return!1===this.isUnfilterable(e)?`textureSampleLevel( ${t}, ${t}_sampler, ${r}, ${s} )`:this.isFilteredTexture(e)?this.generateFilteredTexture(e,t,r,s):this.generateTextureLod(e,t,r,i,s)}generateWrapFunction(e){const t=`tsl_coord_${rA[e.wrapS]}S_${rA[e.wrapT]}_${e.isData3DTexture?"3d":"2d"}T`;let r=oA[t];if(void 0===r){const s=[],i=e.isData3DTexture?"vec3f":"vec2f";let n=`fn ${t}( coord : ${i} ) -> ${i} {\n\n\treturn ${i}(\n`;const a=(e,t)=>{e===Nr?(s.push(uA.repeatWrapping_float),n+=`\t\ttsl_repeatWrapping_float( coord.${t} )`):e===vr?(s.push(uA.clampWrapping_float),n+=`\t\ttsl_clampWrapping_float( coord.${t} )`):e===_r?(s.push(uA.mirrorWrapping_float),n+=`\t\ttsl_mirrorWrapping_float( coord.${t} )`):(n+=`\t\tcoord.${t}`,console.warn(`WebGPURenderer: Unsupported texture wrap type "${e}" for vertex shader.`))};a(e.wrapS,"x"),n+=",\n",a(e.wrapT,"y"),e.isData3DTexture&&(n+=",\n",a(e.wrapR,"z")),n+="\n\t);\n\n}\n",oA[t]=r=new Ib(n,s)}return r.build(this),t}generateArrayDeclaration(e,t){return`array< ${this.getType(e)}, ${t} >`}generateTextureDimension(e,t,r){const s=this.getDataFromNode(e,this.shaderStage,this.globalCache);void 0===s.dimensionsSnippet&&(s.dimensionsSnippet={});let i=s.dimensionsSnippet[r];if(void 0===s.dimensionsSnippet[r]){let n,a;const{primarySamples:o}=this.renderer.backend.utils.getTextureSampleData(e),u=o>1;a=e.isData3DTexture?"vec3":"vec2",n=u||e.isVideoTexture||e.isStorageTexture?t:`${t}${r?`, u32( ${r} )`:""}`,i=new Zo(new Du(`textureDimensions( ${n} )`,a)),s.dimensionsSnippet[r]=i,(e.isArrayTexture||e.isDataArrayTexture||e.isData3DTexture)&&(s.arrayLayerCount=new Zo(new Du(`textureNumLayers(${t})`,"u32"))),e.isTextureCube&&(s.cubeFaceCount=new Zo(new Du("6u","u32")))}return i.build(this)}generateFilteredTexture(e,t,r,s="0u"){this._include("biquadraticTexture");return`tsl_biquadraticTexture( ${t}, ${this.generateWrapFunction(e)}( ${r} ), ${this.generateTextureDimension(e,t,s)}, u32( ${s} ) )`}generateTextureLod(e,t,r,s,i="0u"){const n=this.generateWrapFunction(e),a=this.generateTextureDimension(e,t,i),o=e.isData3DTexture?"vec3":"vec2",u=`${o}( ${n}( ${r} ) * ${o}( ${a} ) )`;return this.generateTextureLoad(e,t,u,s,i)}generateTextureLoad(e,t,r,s,i="0u"){let n;return!0===e.isVideoTexture?n=`textureLoad( ${t}, ${r} )`:s?n=`textureLoad( ${t}, ${r}, ${s}, u32( ${i} ) )`:(n=`textureLoad( ${t}, ${r}, u32( ${i} ) )`,this.renderer.backend.compatibilityMode&&e.isDepthTexture&&(n+=".x")),n}generateTextureStore(e,t,r,s,i){let n;return n=s?`textureStore( ${t}, ${r}, ${s}, ${i} )`:`textureStore( ${t}, ${r}, ${i} )`,n}isSampleCompare(e){return!0===e.isDepthTexture&&null!==e.compareFunction}isUnfilterable(e){return"float"!==this.getComponentTypeFromTexture(e)||!this.isAvailable("float32Filterable")&&!0===e.isDataTexture&&e.type===D||!1===this.isSampleCompare(e)&&e.minFilter===v&&e.magFilter===v||this.renderer.backend.utils.getTextureSampleData(e).primarySamples>1}generateTexture(e,t,r,s,i=this.shaderStage){let n=null;return n=!0===e.isVideoTexture?this._generateVideoSample(t,r,i):this.isUnfilterable(e)?this.generateTextureLod(e,t,r,s,"0",i):this._generateTextureSample(e,t,r,s,i),n}generateTextureGrad(e,t,r,s,i,n=this.shaderStage){if("fragment"===n)return`textureSampleGrad( ${t}, ${t}_sampler, ${r}, ${s[0]}, ${s[1]} )`;console.error(`WebGPURenderer: THREE.TextureNode.gradient() does not support ${n} shader.`)}generateTextureCompare(e,t,r,s,i,n=this.shaderStage){if("fragment"===n)return!0===e.isDepthTexture&&!0===e.isArrayTexture?`textureSampleCompare( ${t}, ${t}_sampler, ${r}, ${i}, ${s} )`:`textureSampleCompare( ${t}, ${t}_sampler, ${r}, ${s} )`;console.error(`WebGPURenderer: THREE.DepthTexture.compareFunction() does not support ${n} shader.`)}generateTextureLevel(e,t,r,s,i,n=this.shaderStage){let a=null;return a=!0===e.isVideoTexture?this._generateVideoSample(t,r,n):this._generateTextureSampleLevel(e,t,r,s,i),a}generateTextureBias(e,t,r,s,i,n=this.shaderStage){if("fragment"===n)return`textureSampleBias( ${t}, ${t}_sampler, ${r}, ${s} )`;console.error(`WebGPURenderer: THREE.TextureNode.biasNode does not support ${n} shader.`)}getPropertyName(e,t=this.shaderStage){if(!0===e.isNodeVarying&&!0===e.needsInterpolation){if("vertex"===t)return`varyings.${e.name}`}else if(!0===e.isNodeUniform){const t=e.name,r=e.type;return"texture"===r||"cubeTexture"===r||"storageTexture"===r||"texture3D"===r?t:"buffer"===r||"storageBuffer"===r||"indirectStorageBuffer"===r?this.isCustomStruct(e)?t:t+".value":e.groupNode.name+"."+t}return super.getPropertyName(e)}getOutputStructName(){return"output"}getFunctionOperator(e){const t=nA[e];return void 0!==t?(this._include(t),t):null}getNodeAccess(e,t){return"compute"!==t?Os.READ_ONLY:e.access}getStorageAccess(e,t){return tA[this.getNodeAccess(e,t)]}getUniformFromNode(e,t,r,s=null){const i=super.getUniformFromNode(e,t,r,s),n=this.getDataFromNode(e,r,this.globalCache);if(void 0===n.uniformGPU){let a;const o=e.groupNode,u=o.name,l=this.getBindGroupArray(u,r);if("texture"===t||"cubeTexture"===t||"storageTexture"===t||"texture3D"===t){let s=null;const n=this.getNodeAccess(e,r);if("texture"===t||"storageTexture"===t?s=new Qv(i.name,i.node,o,n):"cubeTexture"===t?s=new Zv(i.name,i.node,o,n):"texture3D"===t&&(s=new Jv(i.name,i.node,o,n)),s.store=!0===e.isStorageTextureNode,s.setVisibility(sA[r]),!1===this.isUnfilterable(e.value)&&!1===s.store){const e=new kw(`${i.name}_sampler`,i.node,o);e.setVisibility(sA[r]),l.push(e,s),a=[e,s]}else l.push(s),a=[s]}else if("buffer"===t||"storageBuffer"===t||"indirectStorageBuffer"===t){const n=new("buffer"===t?Wv:Hw)(e,o);n.setVisibility(sA[r]),l.push(n),a=n,i.name=s||"NodeBuffer_"+i.id}else{const e=this.uniformGroups[r]||(this.uniformGroups[r]={});let s=e[u];void 0===s&&(s=new Xv(u,o),s.setVisibility(sA[r]),e[u]=s,l.push(s)),a=this.getNodeUniform(i,t),s.addUniform(a)}n.uniformGPU=a}return i}getBuiltin(e,t,r,s=this.shaderStage){const i=this.builtins[s]||(this.builtins[s]=new Map);return!1===i.has(e)&&i.set(e,{name:e,property:t,type:r}),t}hasBuiltin(e,t=this.shaderStage){return void 0!==this.builtins[t]&&this.builtins[t].has(e)}getVertexIndex(){return"vertex"===this.shaderStage?this.getBuiltin("vertex_index","vertexIndex","u32","attribute"):"vertexIndex"}buildFunctionCode(e){const t=e.layout,r=this.flowShaderNode(e),s=[];for(const e of t.inputs)s.push(e.name+" : "+this.getType(e.type));let i=`fn ${t.name}( ${s.join(", ")} ) -> ${this.getType(t.type)} {\n${r.vars}\n${r.code}\n`;return r.result&&(i+=`\treturn ${r.result};\n`),i+="\n}\n",i}getInstanceIndex(){return"vertex"===this.shaderStage?this.getBuiltin("instance_index","instanceIndex","u32","attribute"):"instanceIndex"}getInvocationLocalIndex(){return this.getBuiltin("local_invocation_index","invocationLocalIndex","u32","attribute")}getSubgroupSize(){return this.enableSubGroups(),this.getBuiltin("subgroup_size","subgroupSize","u32","attribute")}getInvocationSubgroupIndex(){return this.enableSubGroups(),this.getBuiltin("subgroup_invocation_id","invocationSubgroupIndex","u32","attribute")}getSubgroupIndex(){return this.enableSubGroups(),this.getBuiltin("subgroup_id","subgroupIndex","u32","attribute")}getDrawIndex(){return null}getFrontFacing(){return this.getBuiltin("front_facing","isFront","bool")}getFragCoord(){return this.getBuiltin("position","fragCoord","vec4")+".xy"}getFragDepth(){return"output."+this.getBuiltin("frag_depth","depth","f32","output")}getClipDistance(){return"varyings.hw_clip_distances"}isFlipY(){return!1}enableDirective(e,t=this.shaderStage){(this.directives[t]||(this.directives[t]=new Set)).add(e)}getDirectives(e){const t=[],r=this.directives[e];if(void 0!==r)for(const e of r)t.push(`enable ${e};`);return t.join("\n")}enableSubGroups(){this.enableDirective("subgroups")}enableSubgroupsF16(){this.enableDirective("subgroups-f16")}enableClipDistances(){this.enableDirective("clip_distances")}enableShaderF16(){this.enableDirective("f16")}enableDualSourceBlending(){this.enableDirective("dual_source_blending")}enableHardwareClipping(e){this.enableClipDistances(),this.getBuiltin("clip_distances","hw_clip_distances",`array`,"vertex")}getBuiltins(e){const t=[],r=this.builtins[e];if(void 0!==r)for(const{name:e,property:s,type:i}of r.values())t.push(`@builtin( ${e} ) ${s} : ${i}`);return t.join(",\n\t")}getScopedArray(e,t,r,s){return!1===this.scopedArrays.has(e)&&this.scopedArrays.set(e,{name:e,scope:t,bufferType:r,bufferCount:s}),e}getScopedArrays(e){if("compute"!==e)return;const t=[];for(const{name:e,scope:r,bufferType:s,bufferCount:i}of this.scopedArrays.values()){const n=this.getType(s);t.push(`var<${r}> ${e}: array< ${n}, ${i} >;`)}return t.join("\n")}getAttributes(e){const t=[];if("compute"===e&&(this.getBuiltin("global_invocation_id","globalId","vec3","attribute"),this.getBuiltin("workgroup_id","workgroupId","vec3","attribute"),this.getBuiltin("local_invocation_id","localId","vec3","attribute"),this.getBuiltin("num_workgroups","numWorkgroups","vec3","attribute"),this.renderer.hasFeature("subgroups")&&(this.enableDirective("subgroups",e),this.getBuiltin("subgroup_size","subgroupSize","u32","attribute"))),"vertex"===e||"compute"===e){const e=this.getBuiltins("attribute");e&&t.push(e);const r=this.getAttributesArray();for(let e=0,s=r.length;e"),t.push(`\t${s+r.name} : ${i}`)}return e.output&&t.push(`\t${this.getBuiltins("output")}`),t.join(",\n")}getStructs(e){let t="";const r=this.structs[e];if(r.length>0){const e=[];for(const t of r){let r=`struct ${t.name} {\n`;r+=this.getStructMembers(t),r+="\n};",e.push(r)}t="\n"+e.join("\n\n")+"\n"}return t}getVar(e,t,r=null){let s=`var ${t} : `;return s+=null!==r?this.generateArrayDeclaration(e,r):this.getType(e),s}getVars(e){const t=[],r=this.vars[e];if(void 0!==r)for(const e of r)t.push(`\t${this.getVar(e.type,e.name,e.count)};`);return`\n${t.join("\n")}\n`}getVaryings(e){const t=[];if("vertex"===e&&this.getBuiltin("position","Vertex","vec4","vertex"),"vertex"===e||"fragment"===e){const r=this.varyings,s=this.vars[e];for(let i=0;ir.value.itemSize;return s&&!i}getUniforms(e){const t=this.uniforms[e],r=[],s=[],i=[],n={};for(const i of t){const t=i.groupNode.name,a=this.bindingsIndexes[t];if("texture"===i.type||"cubeTexture"===i.type||"storageTexture"===i.type||"texture3D"===i.type){const t=i.node.value;let s;!1===this.isUnfilterable(t)&&!0!==i.node.isStorageTextureNode&&(this.isSampleCompare(t)?r.push(`@binding( ${a.binding++} ) @group( ${a.group} ) var ${i.name}_sampler : sampler_comparison;`):r.push(`@binding( ${a.binding++} ) @group( ${a.group} ) var ${i.name}_sampler : sampler;`));let n="";const{primarySamples:o}=this.renderer.backend.utils.getTextureSampleData(t);if(o>1&&(n="_multisampled"),!0===t.isCubeTexture)s="texture_cube";else if(!0===t.isDepthTexture)s=this.renderer.backend.compatibilityMode&&null===t.compareFunction?`texture${n}_2d`:`texture_depth${n}_2d${!0===t.isArrayTexture?"_array":""}`;else if(!0===i.node.isStorageTextureNode){const r=Xw(t),n=this.getStorageAccess(i.node,e),a=i.node.value.is3DTexture,o=i.node.value.isArrayTexture;s=`texture_storage_${a?"3d":"2d"+(o?"_array":"")}<${r}, ${n}>`}else if(!0===t.isArrayTexture||!0===t.isDataArrayTexture||!0===t.isCompressedArrayTexture)s="texture_2d_array";else if(!0===t.is3DTexture||!0===t.isData3DTexture)s="texture_3d";else if(!0===t.isVideoTexture)s="texture_external";else{s=`texture${n}_2d<${this.getComponentTypeFromTexture(t).charAt(0)}32>`}r.push(`@binding( ${a.binding++} ) @group( ${a.group} ) var ${i.name} : ${s};`)}else if("buffer"===i.type||"storageBuffer"===i.type||"indirectStorageBuffer"===i.type){const t=i.node,r=this.getType(t.getNodeType(this)),n=t.bufferCount,o=n>0&&"buffer"===i.type?", "+n:"",u=t.isStorageBufferNode?`storage, ${this.getStorageAccess(t,e)}`:"uniform";if(this.isCustomStruct(i))s.push(`@binding( ${a.binding++} ) @group( ${a.group} ) var<${u}> ${i.name} : ${r};`);else{const e=`\tvalue : array< ${t.isAtomic?`atomic<${r}>`:`${r}`}${o} >`;s.push(this._getWGSLStructBinding(i.name,e,u,a.binding++,a.group))}}else{const e=this.getType(this.getVectorType(i.type)),t=i.groupNode.name;(n[t]||(n[t]={index:a.binding++,id:a.group,snippets:[]})).snippets.push(`\t${i.name} : ${e}`)}}for(const e in n){const t=n[e];i.push(this._getWGSLStructBinding(e,t.snippets.join(",\n"),"uniform",t.index,t.id))}let a=r.join("\n");return a+=s.join("\n"),a+=i.join("\n"),a}buildCode(){const e=null!==this.material?{fragment:{},vertex:{}}:{compute:{}};this.sortBindingGroups();for(const t in e){this.shaderStage=t;const r=e[t];r.uniforms=this.getUniforms(t),r.attributes=this.getAttributes(t),r.varyings=this.getVaryings(t),r.structs=this.getStructs(t),r.vars=this.getVars(t),r.codes=this.getCodes(t),r.directives=this.getDirectives(t),r.scopedArrays=this.getScopedArrays(t);let s="// code\n\n";s+=this.flowCode[t];const i=this.flowNodes[t],n=i[i.length-1],a=n.outputNode,o=void 0!==a&&!0===a.isOutputStructNode;for(const e of i){const i=this.getFlowData(e),u=e.name;if(u&&(s.length>0&&(s+="\n"),s+=`\t// flow -> ${u}\n`),s+=`${i.code}\n\t`,e===n&&"compute"!==t)if(s+="// result\n\n\t","vertex"===t)s+=`varyings.Vertex = ${i.result};`;else if("fragment"===t)if(o)r.returnType=a.getNodeType(this),r.structs+="var output : "+r.returnType+";",s+=`return ${i.result};`;else{let e="\t@location(0) color: vec4";const t=this.getBuiltins("output");t&&(e+=",\n\t"+t),r.returnType="OutputStruct",r.structs+=this._getWGSLStruct("OutputStruct",e),r.structs+="\nvar output : OutputStruct;",s+=`output.color = ${i.result};\n\n\treturn output;`}}r.flow=s}this.shaderStage=null,null!==this.material?(this.vertexShader=this._getWGSLVertexCode(e.vertex),this.fragmentShader=this._getWGSLFragmentCode(e.fragment)):this.computeShader=this._getWGSLComputeCode(e.compute,(this.object.workgroupSize||[64]).join(", "))}getMethod(e,t=null){let r;return null!==t&&(r=this._getWGSLMethod(e+"_"+t)),void 0===r&&(r=this._getWGSLMethod(e)),r||e}getType(e){return aA[e]||e}isAvailable(e){let t=iA[e];return void 0===t&&("float32Filterable"===e?t=this.renderer.hasFeature("float32-filterable"):"clipDistance"===e&&(t=this.renderer.hasFeature("clip-distances")),iA[e]=t),t}_getWGSLMethod(e){return void 0!==uA[e]&&this._include(e),lA[e]}_include(e){const t=uA[e];return t.build(this),null!==this.currentFunctionNode&&this.currentFunctionNode.includes.push(t),t}_getWGSLVertexCode(e){return`${this.getSignature()}\n// directives\n${e.directives}\n\n// structs\n${e.structs}\n\n// uniforms\n${e.uniforms}\n\n// varyings\n${e.varyings}\nvar varyings : VaryingsStruct;\n\n// codes\n${e.codes}\n\n@vertex\nfn main( ${e.attributes} ) -> VaryingsStruct {\n\n\t// vars\n\t${e.vars}\n\n\t// flow\n\t${e.flow}\n\n\treturn varyings;\n\n}\n`}_getWGSLFragmentCode(e){return`${this.getSignature()}\n// global\n${dA}\n\n// structs\n${e.structs}\n\n// uniforms\n${e.uniforms}\n\n// codes\n${e.codes}\n\n@fragment\nfn main( ${e.varyings} ) -> ${e.returnType} {\n\n\t// vars\n\t${e.vars}\n\n\t// flow\n\t${e.flow}\n\n}\n`}_getWGSLComputeCode(e,t){return`${this.getSignature()}\n// directives\n${e.directives}\n\n// system\nvar instanceIndex : u32;\n\n// locals\n${e.scopedArrays}\n\n// structs\n${e.structs}\n\n// uniforms\n${e.uniforms}\n\n// codes\n${e.codes}\n\n@compute @workgroup_size( ${t} )\nfn main( ${e.attributes} ) {\n\n\t// system\n\tinstanceIndex = globalId.x + globalId.y * numWorkgroups.x * u32(${t}) + globalId.z * numWorkgroups.x * numWorkgroups.y * u32(${t});\n\n\t// vars\n\t${e.vars}\n\n\t// flow\n\t${e.flow}\n\n}\n`}_getWGSLStruct(e,t){return`\nstruct ${e} {\n${t}\n};`}_getWGSLStructBinding(e,t,r,s=0,i=0){const n=e+"Struct";return`${this._getWGSLStruct(n,t)}\n@binding( ${s} ) @group( ${i} )\nvar<${r}> ${e} : ${n};`}}class hA{constructor(e){this.backend=e}getCurrentDepthStencilFormat(e){let t;return null!==e.depthTexture?t=this.getTextureFormatGPU(e.depthTexture):e.depth&&e.stencil?t=VS:e.depth&&(t=IS),t}getTextureFormatGPU(e){return this.backend.get(e).format}getTextureSampleData(e){let t;if(e.isFramebufferTexture)t=1;else if(e.isDepthTexture&&!e.renderTarget){const e=this.backend.renderer,r=e.getRenderTarget();t=r?r.samples:e.samples}else e.renderTarget&&(t=e.renderTarget.samples);t=t||1;const r=t>1&&null!==e.renderTarget&&!0!==e.isDepthTexture&&!0!==e.isFramebufferTexture;return{samples:t,primarySamples:r?1:t,isMSAA:r}}getCurrentColorFormat(e){let t;return t=null!==e.textures?this.getTextureFormatGPU(e.textures[0]):this.getPreferredCanvasFormat(),t}getCurrentColorSpace(e){return null!==e.textures?e.textures[0].colorSpace:this.backend.renderer.outputColorSpace}getPrimitiveTopology(e,t){return e.isPoints?PN:e.isLineSegments||e.isMesh&&!0===t.wireframe?BN:e.isLine?FN:e.isMesh?LN:void 0}getSampleCount(e){let t=1;return e>1&&(t=Math.pow(2,Math.floor(Math.log2(e))),2===t&&(t=4)),t}getSampleCountRenderContext(e){return null!==e.textures?this.getSampleCount(e.sampleCount):this.getSampleCount(this.backend.renderer.samples)}getPreferredCanvasFormat(){const e=this.backend.parameters.outputType;if(void 0===e)return navigator.gpu.getPreferredCanvasFormat();if(e===Ce)return _S;if(e===ce)return PS;throw new Error("Unsupported outputType")}}const pA=new Map([[Int8Array,["sint8","snorm8"]],[Uint8Array,["uint8","unorm8"]],[Int16Array,["sint16","snorm16"]],[Uint16Array,["uint16","unorm16"]],[Int32Array,["sint32","snorm32"]],[Uint32Array,["uint32","unorm32"]],[Float32Array,["float32"]]]);"undefined"!=typeof Float16Array&&pA.set(Float16Array,["float16"]);const gA=new Map([[ze,["float16"]]]),mA=new Map([[Int32Array,"sint32"],[Int16Array,"sint32"],[Uint32Array,"uint32"],[Uint16Array,"uint32"],[Float32Array,"float32"]]);class fA{constructor(e){this.backend=e}createAttribute(e,t){const r=this._getBufferAttribute(e),s=this.backend,i=s.get(r);let n=i.buffer;if(void 0===n){const a=s.device;let o=r.array;if(!1===e.normalized)if(o.constructor===Int16Array||o.constructor===Int8Array)o=new Int32Array(o);else if((o.constructor===Uint16Array||o.constructor===Uint8Array)&&(o=new Uint32Array(o),t&GPUBufferUsage.INDEX))for(let e=0;e1&&(s.multisampled=!0,r.texture.isDepthTexture||(s.sampleType=Ew)),r.texture.isDepthTexture)t.compatibilityMode&&null===r.texture.compareFunction?s.sampleType=Ew:s.sampleType=ww;else if(r.texture.isDataTexture||r.texture.isDataArrayTexture||r.texture.isData3DTexture){const e=r.texture.type;e===_?s.sampleType=Aw:e===T?s.sampleType=Rw:e===D&&(this.backend.hasFeature("float32-filterable")?s.sampleType=Sw:s.sampleType=Ew)}r.isSampledCubeTexture?s.viewDimension=Fw:r.texture.isArrayTexture||r.texture.isDataArrayTexture||r.texture.isCompressedArrayTexture?s.viewDimension=Bw:r.isSampledTexture3D&&(s.viewDimension=Lw),e.texture=s}else console.error(`WebGPUBindingUtils: Unsupported binding "${r}".`);s.push(e)}return r.createBindGroupLayout({entries:s})}createBindings(e,t,r,s=0){const{backend:i,bindGroupLayoutCache:n}=this,a=i.get(e);let o,u=n.get(e.bindingsReference);void 0===u&&(u=this.createBindingsLayout(e),n.set(e.bindingsReference,u)),r>0&&(void 0===a.groups&&(a.groups=[],a.versions=[]),a.versions[r]===s&&(o=a.groups[r])),void 0===o&&(o=this.createBindGroup(e,u),r>0&&(a.groups[r]=o,a.versions[r]=s)),a.group=o,a.layout=u}updateBinding(e){const t=this.backend,r=t.device,s=e.buffer,i=t.get(e).buffer;r.queue.writeBuffer(i,0,s,0)}createBindGroupIndex(e,t){const r=this.backend.device,s=GPUBufferUsage.UNIFORM|GPUBufferUsage.COPY_DST,i=e[0],n=r.createBuffer({label:"bindingCameraIndex_"+i,size:16,usage:s});r.queue.writeBuffer(n,0,e,0);const a=[{binding:0,resource:{buffer:n}}];return r.createBindGroup({label:"bindGroupCameraIndex_"+i,layout:t,entries:a})}createBindGroup(e,t){const r=this.backend,s=r.device;let i=0;const n=[];for(const t of e.bindings){if(t.isUniformBuffer){const e=r.get(t);if(void 0===e.buffer){const r=t.byteLength,i=GPUBufferUsage.UNIFORM|GPUBufferUsage.COPY_DST,n=s.createBuffer({label:"bindingBuffer_"+t.name,size:r,usage:i});e.buffer=n}n.push({binding:i,resource:{buffer:e.buffer}})}else if(t.isStorageBuffer){const e=r.get(t);if(void 0===e.buffer){const s=t.attribute;e.buffer=r.get(s).buffer}n.push({binding:i,resource:{buffer:e.buffer}})}else if(t.isSampler){const e=r.get(t.texture);n.push({binding:i,resource:e.sampler})}else if(t.isSampledTexture){const e=r.get(t.texture);let a;if(void 0!==e.externalTexture)a=s.importExternalTexture({source:e.externalTexture});else{const r=t.store?1:e.texture.mipLevelCount,s=`view-${e.texture.width}-${e.texture.height}-${r}`;if(a=e[s],void 0===a){const i=Dw;let n;n=t.isSampledCubeTexture?Fw:t.isSampledTexture3D?Lw:t.texture.isArrayTexture||t.texture.isDataArrayTexture||t.texture.isCompressedArrayTexture?Bw:Pw,a=e[s]=e.texture.createView({aspect:i,dimension:n,mipLevelCount:r})}}n.push({binding:i,resource:a})}i++}return s.createBindGroup({label:"bindGroup_"+e.name,layout:t,entries:n})}}class bA{constructor(e){this.backend=e,this._activePipelines=new WeakMap}setPipeline(e,t){this._activePipelines.get(e)!==t&&(e.setPipeline(t),this._activePipelines.set(e,t))}_getSampleCount(e){return this.backend.utils.getSampleCountRenderContext(e)}createRenderPipeline(e,t){const{object:r,material:s,geometry:i,pipeline:n}=e,{vertexProgram:a,fragmentProgram:o}=n,u=this.backend,l=u.device,d=u.utils,c=u.get(n),h=[];for(const t of e.getBindings()){const e=u.get(t);h.push(e.layout)}const p=u.attributeUtils.createShaderVertexBuffers(e);let g;s.blending===H||s.blending===k&&!1===s.transparent||(g=this._getBlending(s));let m={};!0===s.stencilWrite&&(m={compare:this._getStencilCompare(s),failOp:this._getStencilOperation(s.stencilFail),depthFailOp:this._getStencilOperation(s.stencilZFail),passOp:this._getStencilOperation(s.stencilZPass)});const f=this._getColorWriteMask(s),y=[];if(null!==e.context.textures){const t=e.context.textures;for(let e=0;e1},layout:l.createPipelineLayout({bindGroupLayouts:h})},E={},w=e.context.depth,A=e.context.stencil;if(!0!==w&&!0!==A||(!0===w&&(E.format=v,E.depthWriteEnabled=s.depthWrite,E.depthCompare=_),!0===A&&(E.stencilFront=m,E.stencilBack={},E.stencilReadMask=s.stencilFuncMask,E.stencilWriteMask=s.stencilWriteMask),!0===s.polygonOffset&&(E.depthBias=s.polygonOffsetUnits,E.depthBiasSlopeScale=s.polygonOffsetFactor,E.depthBiasClamp=0),S.depthStencil=E),null===t)c.pipeline=l.createRenderPipeline(S);else{const e=new Promise(e=>{l.createRenderPipelineAsync(S).then(t=>{c.pipeline=t,e()})});t.push(e)}}createBundleEncoder(e,t="renderBundleEncoder"){const r=this.backend,{utils:s,device:i}=r,n=s.getCurrentDepthStencilFormat(e),a={label:t,colorFormats:[s.getCurrentColorFormat(e)],depthStencilFormat:n,sampleCount:this._getSampleCount(e)};return i.createRenderBundleEncoder(a)}createComputePipeline(e,t){const r=this.backend,s=r.device,i=r.get(e.computeProgram).module,n=r.get(e),a=[];for(const e of t){const t=r.get(e);a.push(t.layout)}n.pipeline=s.createComputePipeline({compute:i,layout:s.createPipelineLayout({bindGroupLayouts:a})})}_getBlending(e){let t,r;const s=e.blending,i=e.blendSrc,n=e.blendDst,a=e.blendEquation;if(s===qe){const s=null!==e.blendSrcAlpha?e.blendSrcAlpha:i,o=null!==e.blendDstAlpha?e.blendDstAlpha:n,u=null!==e.blendEquationAlpha?e.blendEquationAlpha:a;t={srcFactor:this._getBlendFactor(i),dstFactor:this._getBlendFactor(n),operation:this._getBlendOperation(a)},r={srcFactor:this._getBlendFactor(s),dstFactor:this._getBlendFactor(o),operation:this._getBlendOperation(u)}}else{const i=(e,s,i,n)=>{t={srcFactor:e,dstFactor:s,operation:rw},r={srcFactor:i,dstFactor:n,operation:rw}};if(e.premultipliedAlpha)switch(s){case k:i($E,XE,$E,XE);break;case Bt:i($E,$E,$E,$E);break;case Pt:i(HE,jE,HE,$E);break;case Mt:i(KE,XE,HE,$E)}else switch(s){case k:i(qE,XE,$E,XE);break;case Bt:i(qE,$E,$E,$E);break;case Pt:console.error("THREE.WebGPURenderer: SubtractiveBlending requires material.premultipliedAlpha = true");break;case Mt:console.error("THREE.WebGPURenderer: MultiplyBlending requires material.premultipliedAlpha = true")}}if(void 0!==t&&void 0!==r)return{color:t,alpha:r};console.error("THREE.WebGPURenderer: Invalid blending: ",s)}_getBlendFactor(e){let t;switch(e){case Ke:t=HE;break;case wt:t=$E;break;case Et:t=WE;break;case Tt:t=jE;break;case St:t=qE;break;case xt:t=XE;break;case vt:t=KE;break;case bt:t=YE;break;case _t:t=QE;break;case yt:t=ZE;break;case Nt:t=JE;break;case 211:t=ew;break;case 212:t=tw;break;default:console.error("THREE.WebGPURenderer: Blend factor not supported.",e)}return t}_getStencilCompare(e){let t;const r=e.stencilFunc;switch(r){case kr:t=IN;break;case Or:t=HN;break;case Ur:t=VN;break;case Vr:t=ON;break;case Ir:t=UN;break;case Dr:t=zN;break;case Lr:t=kN;break;case Fr:t=GN;break;default:console.error("THREE.WebGPURenderer: Invalid stencil function.",r)}return t}_getStencilOperation(e){let t;switch(e){case Xr:t=lw;break;case qr:t=dw;break;case jr:t=cw;break;case Wr:t=hw;break;case $r:t=pw;break;case Hr:t=gw;break;case zr:t=mw;break;case Gr:t=fw;break;default:console.error("THREE.WebGPURenderer: Invalid stencil operation.",t)}return t}_getBlendOperation(e){let t;switch(e){case Xe:t=rw;break;case ft:t=sw;break;case mt:t=iw;break;case Yr:t=nw;break;case Kr:t=aw;break;default:console.error("THREE.WebGPUPipelineUtils: Blend equation not supported.",e)}return t}_getPrimitiveState(e,t,r){const s={},i=this.backend.utils;switch(s.topology=i.getPrimitiveTopology(e,r),null!==t.index&&!0===e.isLine&&!0!==e.isLineSegments&&(s.stripIndexFormat=t.index.array instanceof Uint16Array?QN:ZN),r.side){case je:s.frontFace=qN,s.cullMode=YN;break;case S:s.frontFace=qN,s.cullMode=KN;break;case E:s.frontFace=qN,s.cullMode=XN;break;default:console.error("THREE.WebGPUPipelineUtils: Unknown material.side value.",r.side)}return s}_getColorWriteMask(e){return!0===e.colorWrite?uw:ow}_getDepthCompare(e){let t;if(!1===e.depthTest)t=HN;else{const r=e.depthFunc;switch(r){case kt:t=IN;break;case Ot:t=HN;break;case Ut:t=VN;break;case Vt:t=ON;break;case It:t=UN;break;case Dt:t=zN;break;case Lt:t=kN;break;case Ft:t=GN;break;default:console.error("THREE.WebGPUPipelineUtils: Invalid depth function.",r)}}return t}}class xA extends AN{constructor(e,t,r=2048){super(r),this.device=e,this.type=t,this.querySet=this.device.createQuerySet({type:"timestamp",count:this.maxQueries,label:`queryset_global_timestamp_${t}`});const s=8*this.maxQueries;this.resolveBuffer=this.device.createBuffer({label:`buffer_timestamp_resolve_${t}`,size:s,usage:GPUBufferUsage.QUERY_RESOLVE|GPUBufferUsage.COPY_SRC}),this.resultBuffer=this.device.createBuffer({label:`buffer_timestamp_result_${t}`,size:s,usage:GPUBufferUsage.COPY_DST|GPUBufferUsage.MAP_READ})}allocateQueriesForContext(e){if(!this.trackTimestamp||this.isDisposed)return null;if(this.currentQueryIndex+2>this.maxQueries)return pt(`WebGPUTimestampQueryPool [${this.type}]: Maximum number of queries exceeded, when using trackTimestamp it is necessary to resolves the queries via renderer.resolveTimestampsAsync( THREE.TimestampQuery.${this.type.toUpperCase()} ).`),null;const t=this.currentQueryIndex;return this.currentQueryIndex+=2,this.queryOffsets.set(e.id,t),t}async resolveQueriesAsync(){if(!this.trackTimestamp||0===this.currentQueryIndex||this.isDisposed)return this.lastValue;if(this.pendingResolve)return this.pendingResolve;this.pendingResolve=this._resolveQueries();try{return await this.pendingResolve}finally{this.pendingResolve=null}}async _resolveQueries(){if(this.isDisposed)return this.lastValue;try{if("unmapped"!==this.resultBuffer.mapState)return this.lastValue;const e=new Map(this.queryOffsets),t=this.currentQueryIndex,r=8*t;this.currentQueryIndex=0,this.queryOffsets.clear();const s=this.device.createCommandEncoder();s.resolveQuerySet(this.querySet,0,t,this.resolveBuffer,0),s.copyBufferToBuffer(this.resolveBuffer,0,this.resultBuffer,0,r);const i=s.finish();if(this.device.queue.submit([i]),"unmapped"!==this.resultBuffer.mapState)return this.lastValue;if(await this.resultBuffer.mapAsync(GPUMapMode.READ,0,r),this.isDisposed)return"mapped"===this.resultBuffer.mapState&&this.resultBuffer.unmap(),this.lastValue;const n=new BigUint64Array(this.resultBuffer.getMappedRange(0,r));let a=0;for(const[,t]of e){const e=n[t],r=n[t+1];a+=Number(r-e)/1e6}return this.resultBuffer.unmap(),this.lastValue=a,a}catch(e){return console.error("Error resolving queries:",e),"mapped"===this.resultBuffer.mapState&&this.resultBuffer.unmap(),this.lastValue}}async dispose(){if(!this.isDisposed){if(this.isDisposed=!0,this.pendingResolve)try{await this.pendingResolve}catch(e){console.error("Error waiting for pending resolve:",e)}if(this.resultBuffer&&"mapped"===this.resultBuffer.mapState)try{this.resultBuffer.unmap()}catch(e){console.error("Error unmapping buffer:",e)}this.querySet&&(this.querySet.destroy(),this.querySet=null),this.resolveBuffer&&(this.resolveBuffer.destroy(),this.resolveBuffer=null),this.resultBuffer&&(this.resultBuffer.destroy(),this.resultBuffer=null),this.queryOffsets.clear(),this.pendingResolve=null}}}class TA extends lN{constructor(e={}){super(e),this.isWebGPUBackend=!0,this.parameters.alpha=void 0===e.alpha||e.alpha,this.parameters.compatibilityMode=void 0!==e.compatibilityMode&&e.compatibilityMode,this.parameters.requiredLimits=void 0===e.requiredLimits?{}:e.requiredLimits,this.compatibilityMode=this.parameters.compatibilityMode,this.device=null,this.context=null,this.colorBuffer=null,this.defaultRenderPassdescriptor=null,this.utils=new hA(this),this.attributeUtils=new fA(this),this.bindingUtils=new yA(this),this.pipelineUtils=new bA(this),this.textureUtils=new qw(this),this.occludedResolveCache=new Map}async init(e){await super.init(e);const t=this.parameters;let r;if(void 0===t.device){const e={powerPreference:t.powerPreference,featureLevel:t.compatibilityMode?"compatibility":void 0},s="undefined"!=typeof navigator?await navigator.gpu.requestAdapter(e):null;if(null===s)throw new Error("WebGPUBackend: Unable to create WebGPU adapter.");const i=Object.values(Uw),n=[];for(const e of i)s.features.has(e)&&n.push(e);const a={requiredFeatures:n,requiredLimits:t.requiredLimits};r=await s.requestDevice(a)}else r=t.device;r.lost.then(t=>{const r={api:"WebGPU",message:t.message||"Unknown reason",reason:t.reason||null,originalEvent:t};e.onDeviceLost(r)});const s=void 0!==t.context?t.context:e.domElement.getContext("webgpu");this.device=r,this.context=s;const i=t.alpha?"premultiplied":"opaque";this.trackTimestamp=this.trackTimestamp&&this.hasFeature(Uw.TimestampQuery),this.context.configure({device:this.device,format:this.utils.getPreferredCanvasFormat(),usage:GPUTextureUsage.RENDER_ATTACHMENT|GPUTextureUsage.COPY_SRC,alphaMode:i}),this.updateSize()}get coordinateSystem(){return d}async getArrayBufferAsync(e){return await this.attributeUtils.getArrayBufferAsync(e)}getContext(){return this.context}_getDefaultRenderPassDescriptor(){let e=this.defaultRenderPassdescriptor;if(null===e){const t=this.renderer;e={colorAttachments:[{view:null}]},!0!==this.renderer.depth&&!0!==this.renderer.stencil||(e.depthStencilAttachment={view:this.textureUtils.getDepthBuffer(t.depth,t.stencil).createView()});const r=e.colorAttachments[0];this.renderer.samples>0?r.view=this.colorBuffer.createView():r.resolveTarget=void 0,this.defaultRenderPassdescriptor=e}const t=e.colorAttachments[0];return this.renderer.samples>0?t.resolveTarget=this.context.getCurrentTexture().createView():t.view=this.context.getCurrentTexture().createView(),e}_isRenderCameraDepthArray(e){return e.depthTexture&&e.depthTexture.image.depth>1&&e.camera.isArrayCamera}_getRenderPassDescriptor(e,t={}){const r=e.renderTarget,s=this.get(r);let i=s.descriptors;if(void 0===i||s.width!==r.width||s.height!==r.height||s.dimensions!==r.dimensions||s.activeMipmapLevel!==e.activeMipmapLevel||s.activeCubeFace!==e.activeCubeFace||s.samples!==r.samples){i={},s.descriptors=i;const e=()=>{r.removeEventListener("dispose",e),this.delete(r)};!1===r.hasEventListener("dispose",e)&&r.addEventListener("dispose",e)}const n=e.getCacheKey();let a=i[n];if(void 0===a){const t=e.textures,o=[];let u;const l=this._isRenderCameraDepthArray(e);for(let s=0;s1)if(!0===l){const t=e.camera.cameras;for(let e=0;e0&&(t.currentOcclusionQuerySet&&t.currentOcclusionQuerySet.destroy(),t.currentOcclusionQueryBuffer&&t.currentOcclusionQueryBuffer.destroy(),t.currentOcclusionQuerySet=t.occlusionQuerySet,t.currentOcclusionQueryBuffer=t.occlusionQueryBuffer,t.currentOcclusionQueryObjects=t.occlusionQueryObjects,i=r.createQuerySet({type:"occlusion",count:s,label:`occlusionQuerySet_${e.id}`}),t.occlusionQuerySet=i,t.occlusionQueryIndex=0,t.occlusionQueryObjects=new Array(s),t.lastOcclusionObject=null),n=null===e.textures?this._getDefaultRenderPassDescriptor():this._getRenderPassDescriptor(e,{loadOp:WN}),this.initTimestampQuery(e,n),n.occlusionQuerySet=i;const a=n.depthStencilAttachment;if(null!==e.textures){const t=n.colorAttachments;for(let r=0;r0&&t.currentPass.executeBundles(t.renderBundles),r>t.occlusionQueryIndex&&t.currentPass.endOcclusionQuery();const s=t.encoder;if(!0===this._isRenderCameraDepthArray(e)){const r=[];for(let e=0;e0){const s=8*r;let i=this.occludedResolveCache.get(s);void 0===i&&(i=this.device.createBuffer({size:s,usage:GPUBufferUsage.QUERY_RESOLVE|GPUBufferUsage.COPY_SRC}),this.occludedResolveCache.set(s,i));const n=this.device.createBuffer({size:s,usage:GPUBufferUsage.COPY_DST|GPUBufferUsage.MAP_READ});t.encoder.resolveQuerySet(t.occlusionQuerySet,0,r,i,0),t.encoder.copyBufferToBuffer(i,0,n,0,s),t.occlusionQueryBuffer=n,this.resolveOccludedAsync(e)}if(this.device.queue.submit([t.encoder.finish()]),null!==e.textures){const t=e.textures;for(let e=0;ea?(u.x=Math.min(t.dispatchCount,a),u.y=Math.ceil(t.dispatchCount/a)):u.x=t.dispatchCount,i.dispatchWorkgroups(u.x,u.y,u.z)}finishCompute(e){const t=this.get(e);t.passEncoderGPU.end(),this.device.queue.submit([t.cmdEncoderGPU.finish()])}async waitForGPU(){await this.device.queue.onSubmittedWorkDone()}draw(e,t){const{object:r,material:s,context:i,pipeline:n}=e,a=e.getBindings(),o=this.get(i),u=this.get(n).pipeline,l=e.getIndex(),d=null!==l,c=e.getDrawParameters();if(null===c)return;const h=(t,r)=>{this.pipelineUtils.setPipeline(t,u),r.pipeline=u;const n=r.bindingGroups;for(let e=0,r=a.length;e{if(h(s,i),!0===r.isBatchedMesh){const e=r._multiDrawStarts,i=r._multiDrawCounts,n=r._multiDrawCount,a=r._multiDrawInstances;null!==a&&pt("THREE.WebGPUBackend: renderMultiDrawInstances has been deprecated and will be removed in r184. Append to renderMultiDraw arguments and use indirection.");for(let o=0;o1?0:o;!0===d?s.drawIndexed(i[o],n,e[o]/l.array.BYTES_PER_ELEMENT,0,u):s.draw(i[o],n,e[o],u),t.update(r,i[o],n)}}else if(!0===d){const{vertexCount:i,instanceCount:n,firstVertex:a}=c,o=e.getIndirect();if(null!==o){const e=this.get(o).buffer;s.drawIndexedIndirect(e,0)}else s.drawIndexed(i,n,a,0,0);t.update(r,i,n)}else{const{vertexCount:i,instanceCount:n,firstVertex:a}=c,o=e.getIndirect();if(null!==o){const e=this.get(o).buffer;s.drawIndirect(e,0)}else s.draw(i,n,a,0);t.update(r,i,n)}};if(e.camera.isArrayCamera&&e.camera.cameras.length>0){const t=this.get(e.camera),s=e.camera.cameras,n=e.getBindingGroup("cameraIndex");if(void 0===t.indexesGPU||t.indexesGPU.length!==s.length){const e=this.get(n),r=[],i=new Uint32Array([0,0,0,0]);for(let t=0,n=s.length;t(console.warn("THREE.WebGPURenderer: WebGPU is not available, running under WebGL2 backend."),new MN(e)));super(new t(e),e),this.library=new NA,this.isWebGPURenderer=!0}}class EA extends ds{constructor(){super(),this.isBundleGroup=!0,this.type="BundleGroup",this.static=!0,this.version=0}set needsUpdate(e){!0===e&&this.version++}}class wA{constructor(e,t=nn(0,0,1,1)){this.renderer=e,this.outputNode=t,this.outputColorTransform=!0,this.needsUpdate=!0;const r=new lp;r.name="PostProcessing",this._quadMesh=new Uy(r)}render(){this._update();const e=this.renderer,t=e.toneMapping,r=e.outputColorSpace;e.toneMapping=p,e.outputColorSpace=le;const s=e.xr.enabled;e.xr.enabled=!1,this._quadMesh.render(e),e.xr.enabled=s,e.toneMapping=t,e.outputColorSpace=r}dispose(){this._quadMesh.material.dispose()}_update(){if(!0===this.needsUpdate){const e=this.renderer,t=e.toneMapping,r=e.outputColorSpace;this._quadMesh.material.fragmentNode=!0===this.outputColorTransform?Ou(this.outputNode,t,r):this.outputNode.context({toneMapping:t,outputColorSpace:r}),this._quadMesh.material.needsUpdate=!0,this.needsUpdate=!1}}async renderAsync(){this._update();const e=this.renderer,t=e.toneMapping,r=e.outputColorSpace;e.toneMapping=p,e.outputColorSpace=le;const s=e.xr.enabled;e.xr.enabled=!1,await this._quadMesh.renderAsync(e),e.xr.enabled=s,e.toneMapping=t,e.outputColorSpace=r}}class AA extends x{constructor(e=1,t=1){super(),this.image={width:e,height:t},this.magFilter=Y,this.minFilter=Y,this.isStorageTexture=!0}}class RA extends qy{constructor(e,t){super(e,t,Uint32Array),this.isIndirectStorageBufferAttribute=!0}}class CA extends cs{constructor(e){super(e),this.textures={},this.nodes={}}load(e,t,r,s){const i=new hs(this.manager);i.setPath(this.path),i.setRequestHeader(this.requestHeader),i.setWithCredentials(this.withCredentials),i.load(e,r=>{try{t(this.parse(JSON.parse(r)))}catch(t){s?s(t):console.error(t),this.manager.itemError(e)}},r,s)}parseNodes(e){const t={};if(void 0!==e){for(const r of e){const{uuid:e,type:s}=r;t[e]=this.createNodeFromType(s),t[e].uuid=e}const r={nodes:t,textures:this.textures};for(const s of e){s.meta=r;t[s.uuid].deserialize(s),delete s.meta}}return t}parse(e){const t=this.createNodeFromType(e.type);t.uuid=e.uuid;const r={nodes:this.parseNodes(e.nodes),textures:this.textures};return e.meta=r,t.deserialize(e),delete e.meta,t}setTextures(e){return this.textures=e,this}setNodes(e){return this.nodes=e,this}createNodeFromType(e){return void 0===this.nodes[e]?(console.error("THREE.NodeLoader: Node type not found:",e),ji()):Li(new this.nodes[e])}}class MA extends ps{constructor(e){super(e),this.nodes={},this.nodeMaterials={}}parse(e){const t=super.parse(e),r=this.nodes,s=e.inputNodes;for(const e in s){const i=s[e];t[e]=r[i]}return t}setNodes(e){return this.nodes=e,this}setNodeMaterials(e){return this.nodeMaterials=e,this}createMaterialFromType(e){const t=this.nodeMaterials[e];return void 0!==t?new t:super.createMaterialFromType(e)}}class PA extends gs{constructor(e){super(e),this.nodes={},this.nodeMaterials={},this._nodesJSON=null}setNodes(e){return this.nodes=e,this}setNodeMaterials(e){return this.nodeMaterials=e,this}parse(e,t){this._nodesJSON=e.nodes;const r=super.parse(e,t);return this._nodesJSON=null,r}parseNodes(e,t){if(void 0!==e){const r=new CA;return r.setNodes(this.nodes),r.setTextures(t),r.parseNodes(e)}return{}}parseMaterials(e,t){const r={};if(void 0!==e){const s=this.parseNodes(this._nodesJSON,t),i=new MA;i.setTextures(t),i.setNodes(s),i.setNodeMaterials(this.nodeMaterials);for(let t=0,s=e.length;t[property:Boolean enabled]

يؤدي تعيين *enabled* إلى *false* إلى تعطيل هذا الإجراء ، بحيث لا يكون له أي تأثير. الافتراضي هو *true*.

- عند إعادة تمكين الإجراء ، يستمر الرسم المتحرك من وقته الحالي [page:.time time] (الإعداد *enabled* إلى *false* لا يعيد تعيين الإجراء).

+ عند إعادة تمكين الإجراء ، يستمر الرسم المتحرك من وقته الحالي [page:.time time] (العدد *enabled* إلى *false* لا يعيد تعيين الإجراء).

ملاحظة: لا يؤدي إعداد *enabled* إلى *true* إلى إعادة تشغيل الرسوم المتحركة تلقائيًا. سيؤدي ضبط *enabled* إلى *true* إلى إعادة تشغيل الرسوم المتحركة فورًا فقط إذا تم استيفاء الشرط التالي: [page:.paused paused] تحتوي قيمة *false* ، لم يتم إلغاء تنشيط هذا الإجراء في هذه الأثناء (بتنفيذ أمر [page:.stop stop] أو [page:.reset reset]) ، وليس [page:.weight weight] أو [page:.timeScale timeScale] يساوي 0. diff --git a/docs/api/ar/animation/AnimationUtils.html b/docs/api/ar/animation/AnimationUtils.html index 3e445b030fc437..6e3f53a7326640 100644 --- a/docs/api/ar/animation/AnimationUtils.html +++ b/docs/api/ar/animation/AnimationUtils.html @@ -17,11 +17,6 @@

[name]

الوظائف (Methods)

-

[method:Array arraySlice]( array, from, to )

-

- نفسه عمل Array.prototype.slice ، ولكنه يعمل أيضًا على المصفوفات المكتوبة. -

-

[method:Array convertArray]( array, type, forceClone )

يحول مصفوفة إلى نوع معين. diff --git a/docs/api/ar/audio/Audio.html b/docs/api/ar/audio/Audio.html index bac0abded5ec3c..57c002fca93f41 100644 --- a/docs/api/ar/audio/Audio.html +++ b/docs/api/ar/audio/Audio.html @@ -136,7 +136,7 @@

[method:Float getPlaybackRate]()

ترجع القيمة الخاصة بـ[page:Audio.playbackRate playbackRate].

-

[method:Float getVolume]( value )

+

[method:Float getVolume]()

إعادة الحجم الحالي.

diff --git a/docs/api/ar/cameras/OrthographicCamera.html b/docs/api/ar/cameras/OrthographicCamera.html new file mode 100644 index 00000000000000..790ec7818ba2b7 --- /dev/null +++ b/docs/api/ar/cameras/OrthographicCamera.html @@ -0,0 +1,141 @@ + + + + + + + + + + [page:Object3D] → [page:Camera] → + +

[name]

+

+ كاميرا تستخدم + [link:https://en.wikipedia.org/wiki/Orthographic_projection الإسقاط المجسمي].

+ + في هذا النمط من الإسقاط، يبقى حجم الجسم + ثابتاً في الصورة المُرسَمَة بغض النظر عن مسافته من الكاميرا.

+ + يمكن أن يكون هذا مفيداً لتصوير المشاهد ثنائية الأبعاد وعناصر واجهة المستخدم، وغيرها من + الأشياء. +

+ +

مثال للكود

+ + const camera = new THREE.OrthographicCamera( width / - 2, width / 2, height / 2, height / - 2, 1, 1000 ); + scene.add( camera ); + + +

أمثلة (Examples)

+

+ [example:webgl_camera camera ]
+ [example:webgl_interactive_cubes_ortho interactive / cubes / ortho ]
+ [example:webgl_materials_cubemap_dynamic materials / cubemap / dynamic]
+ [example:webgl_postprocessing_advanced postprocessing / advanced ]
+ [example:webgl_postprocessing_dof2 postprocessing / dof2 ]
+ [example:webgl_postprocessing_godrays postprocessing / godrays ]
+ [example:webgl_rtt rtt ]
+ [example:webgl_shadowmap shadowmap ] +

+ +

المنشئ (Constructor)

+

[name]( [param:Number left], [param:Number right], [param:Number top], [param:Number bottom], [param:Number near], [param:Number far] )

+

+ left — سطح الهرم الأيسر.
+ right — سطح الهرم الأيمن.
+ top — سطح الهرم العلوي.
+ bottom — سطح الهرم السفلي.
+ near — سطح الهرم الأمامي.
+ far — سطح الهرم الخلفي.

+ + معًا، تحدد هذه العناصر هرم العرض للكاميرا. + [link:https://en.wikipedia.org/wiki/Viewing_frustum هرم الرؤية]. +

+ +

الخصائص (Properties)

+

+ انظر إلى صفحة [page:Camera] الأساسية للحصول على الخصائص الشائعة.
+ يرجى ملاحظة أنه بعد إجراء تغييرات على معظم هذه الخصائص، يجب عليك استدعاء + [page:OrthographicCamera.updateProjectionMatrix .updateProjectionMatrix] + لجعل التغييرات سارية المفعول. +

+ +

[property:Float bottom]

+

سطح الهرم السفلي للكاميرا.

+ +

[property:Float far]

+

+ سطح الهرم الخلفي للكاميرا. العدد الافتراضي هو `2000`.

+يجب أن يكون أكبر من القيمة الحالية لسطح الهرم الأمامي [page:.near near]. +

+ +

[property:Boolean isOrthographicCamera]

+

علامة للقراءة فقط للتحقق مما إذا كان الكائن المعطى من النوع [name].

+ +

[property:Float left]

+

سطح الهرم الأيسر للكاميرا.

+ +

[property:Float near]

+

+ سطح الهرم الأمامي للكاميرا. العدد الافتراضي هو `0.1`.

+ النطاق الصحيح هو بين `0` وقيمة الهرم الخلفي [page:.far far]. يرجى ملاحظة أنه، على عكس الـ [page:PerspectiveCamera]، + يمثل الصفر قيمة صالحة لسطح الهرم الأمامي في OrthographicCamera. +

+ +

[property:Float right]

+

سطح الهرم الأيمن للكاميرا.

+ +

[property:Float top]

+

سطح الهرم العلوي للكاميرا.

+ +

[property:Object view]

+

+ `يتم تعيينها بواسطة [page:OrthographicCamera.setViewOffset setViewOffset]. العدد الافتراضي هو + `null`. +

+ +

[property:number zoom]

+

تُستخدم للحصول على قيمة عامل التكبير أو تعيينها للكاميرا. العدد الافتراضي هو `1`

+ +

الوظائف (Methods)

+

راجع صفحة [page:Camera] الأساسية للحصول على الأساليب الشائعة.

+ +

[method:undefined setViewOffset]( [param:Float fullWidth], [param:Float fullHeight], [param:Float x], [param:Float y], [param:Float width], [param:Float height] )

+

+ fullWidth — العرض الكامل لإعداد العرض المتعدد.
+ fullHeight — الارتفاع الكامل لإعداد العرض المتعدد.
+ x — الإزاحة الأفقية للكاميرا الفرعية.
+ y — الإزاحة العمودية للكاميرا الفرعية.
+ width — عرض الكاميرا الفرعية.
+ height — ارتفاع الكاميرا الفرعية.

+ + يُعيّن إزاحة في + [link:https://en.wikipedia.org/wiki/Viewing_frustum هرم الرؤية] + أكبر. هذا مفيد لإعدادات النوافذ المتعددة أو إعدادات الشاشات / الأجهزة المتعددة. + لمثال حول كيفية استخدامها، انظر [page:PerspectiveCamera.setViewOffset PerspectiveCamera]. +

+ +

[method:undefined clearViewOffset]()

+

تزيل أي إزاحة تم تعيينها بواسطة طريقة .setViewOffset.

+ +

[method:undefined updateProjectionMatrix]()

+

+ تحديث مصفوفة إسقاط الكاميرا. يجب استدعاؤها بعد أي تغيير في + المعلمات. +

+ +

[method:Object toJSON]([param:Object meta])

+

+ meta -- كائن يحتوي على بيانات وصفية مثل الخامات أو الصور في الأجزاء + الفرعية للكائنات.
+ تحويل الكاميرا إلى تنسيق three.js + [link:https://github.com/mrdoob/three.js/wiki/JSON-Object-Scene-format-4 JSON Object/Scene] +

+ +

المصدر (Source)

+

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/cameras/PerspectiveCamera.html b/docs/api/ar/cameras/PerspectiveCamera.html new file mode 100644 index 00000000000000..a200a311f7a17f --- /dev/null +++ b/docs/api/ar/cameras/PerspectiveCamera.html @@ -0,0 +1,219 @@ +!DOCTYPE html> + + + + + + + + + [page:Object3D] → [page:Camera] → + +

[name]

+

+ كاميرا تستخدم تصوير منظوري + [link:https://en.wikipedia.org/wiki/Perspective_(graphical) Perspective Projection]. + تم تصميم هذا الوضع لمحاكاة الطريقة التي يرى بها العين البشرية. + وهو أكثر أنماط التصوير استخدامًا لإظهار مشهد ثلاثي الأبعاد. +

+ +

مثال للكود

+ + + const camera = new THREE.PerspectiveCamera( 45, width / height, 1, 1000 ); + scene.add( camera ); + + +

أمثلة (Examples)

+

+ [example:webgl_animation_skinning_blending animation / skinning / blending]
+ [example:webgl_animation_skinning_morph animation / skinning / morph ]
+ [example:webgl_effects_stereo effects / stereo ]
+ [example:webgl_interactive_cubes interactive / cubes ]
+ [example:webgl_loader_collada_skinning loader / collada / skinning ] +

+ +

المنشئ (Constructor)

+ +

[name]( [param:Number fov], [param:Number aspect], [param:Number near], [param:Number far] )

+

+ fov — زاوية مجال الرؤية الرأسية للكاميرا.
+ aspect — نسبة العرض إلى الارتفاع للكاميرا.
+ near — سطح الهرم الأمامي للكاميرا.
+ far — سطح الهرم الخلفي للكاميرا.

+ معًا، يحدد هذه العناصر هرم الرؤية للكاميرا. + [link:https://en.wikipedia.org/wiki/Viewing_frustum هرم الرؤية] +

+ +

الخصائص (Properties)

+

+ يرجى الرجوع إلى صفحة [page:Camera] الأساسية للحصول على الخصائص الشائعة.
+ يرجى ملاحظة أنه بعد إجراء تغييرات على معظم هذه الخصائص، ستحتاج + إلى استدعاء [page:PerspectiveCamera.updateProjectionMatrix .updateProjectionMatrix] لجعل التغييرات سارية المفعول. +

+ +

[property:Float aspect]

+

+ نسبة العرض إلى الارتفاع لسطح الهرم الرؤية (frustum) للكاميرا، وعادةً ما يكون العرض الخاص باللوحة السينمائية / الشاشة مقسومًا على ارتفاعها. + العدد الافتراضي هو `1` (لوحة سينمائية مربعة). +

+ +

[property:Float far]

+

+ سطح الهرم الخلفي لسطح الرؤية (frustum) للكاميرا. العدد الافتراضي هو `2000`.

+ يجب أن يكون أكبر من القيمة الحالية لسطح الهرم الأمامي [page:.near near]. +

+ +

[property:Float filmGauge]

+

+ حجم الفيلم المستخدم للمحور الأكبر. العدد الافتراضي هو 35 (ملم). + لا يؤثر هذا المعلم على مصفوفة الإسقاط إلا إذا تم تعيين + .filmOffset إلى قيمة غير صفر. +

+ +

[property:Float filmOffset]

+

+ الإزاحة الأفقية غير المركزة في نفس وحدة `.filmGauge`. العدد الافتراضي هو + `0`. +

+ +

[property:Float focus]

+

+ مسافة الكائن المستخدمة لتحقيق تأثيرات الإسقاط المتزامن والعمق الحقيقي. + هذا المعلم لا يؤثر على مصفوفة الإسقاط إلا إذا تم استخدام [page:StereoCamera]. + العدد الافتراضي هو `10`. +

+ +

[property:Float fov]

+

+ زاوية مجال الرؤية الرأسية لسطح الهرم الرؤية (frustum) للكاميرا، من أسفل الرؤية إلى أعلى الرؤية، بالدرجات. + العدد الافتراضي هو `50`. +

+ +

[property:Boolean isPerspectiveCamera]

+

علامة تحقق للتحقق مما إذا كان الكائن المعطى من نوع [name]. هذه العلامة لا يمكن تعديلها.

+ +

[property:Float near]

+

+ سطح الهرم الأمامي لسطح الرؤية (frustum) للكاميرا. + العدد الافتراضي هو `0.1`.

+ النطاق الصحيح هو أكبر من `0` وأقل من القيمة الحالية لسطح الهرم الخلفي [page:.far far]. + يرجى ملاحظة أنه، على عكس الكاميرا المسطحة + [page:OrthographicCamera]، القيمة `0` ليست قيمة صالحة لسطح الهرم الأمامي لكاميرا من نوع PerspectiveCamera. +

+ +

[property:Object view]

+

+ مواصفات نافذة هرم الرؤية (frustum) أو `null`. + يتم تعيين هذا باستخدام طريقة [page:PerspectiveCamera.setViewOffset .setViewOffset] + ويتم مسحها باستخدام [page:PerspectiveCamera.clearViewOffset .clearViewOffset]. +

+ +

[property:number zoom]

+

يتم الحصول على قيمة عامل التكبير أو تعيينها للكاميرا. العدد الافتراضي هو `1`.

+ +

الوظائف (Methods)

+

يرجى الرجوع إلى صفحة [page:Camera] الأساسية للحصول على الأساليب الشائعة.

+ +

[method:undefined clearViewOffset]()

+

تزيل أي إزاحة تم تعيينها باستخدام طريقة [page:PerspectiveCamera.setViewOffset .setViewOffset].

+ +

[method:Float getEffectiveFOV]()

+

تُرجع زاوية مجال الرؤية الرأسية الحالية بالدرجات باعتبار .zoom.

+ +

[method:Float getFilmHeight]()

+

+ تُرجع ارتفاع الصورة على الفيلم. إذا كان + .aspect أقل من الواحد (تنسيق صورة عمودي)، فإن النتيجة تساوي .filmGauge. +

+ +

[method:Float getFilmWidth]()

+

+ تُرجع عرض الصورة على الفيلم. إذا كان + .aspect أكبر من أو يساوي الواحد (تنسيق صورة أفقي)، فإن النتيجة تساوي .filmGauge. +

+ +

[method:Float getFocalLength]()

+

+ تُرجع البعد البؤري للـ .fov الحالي بالنسبة لـ .filmGauge. +

+ +

[method:undefined setFocalLength]( [param:Float focalLength] )

+

+ يتم تعيين الـ FOV بواسطة البعد البؤري بالنسبة لـ + .filmGauge الحالي لـ[page:PerspectiveCamera].

+ بشكل افتراضي، يتم تحديد البعد البؤري للكاميرا بحجم 35 مم (إطار كامل). +

+ +

[method:undefined setViewOffset]( [param:Float fullWidth], [param:Float fullHeight], [param:Float x], [param:Float y], [param:Float width], [param:Float height] )

+

+ fullWidth — العرض الكامل لإعداد العرض المتعدد
+ fullHeight — الارتفاع الكامل لإعداد العرض المتعدد
+ x — الإزاحة الأفقية للكاميرا الفرعية
+ y — الإزاحة الرأسية للكاميرا الفرعية
+ width — عرض الكاميرا الفرعية
+ height — ارتفاع الكاميرا الفرعية +

+ +

+ يضبط الإزاحة في هرم الرؤية الأكبر. + هذا مفيد لإعدادات النوافذ المتعددة أو إعدادات متعددة الشاشات/الأجهزة. +

+ +

+ على سبيل المثال، إذا كان لديك 3x2 شاشة + وكانت كل شاشة 1920x1080 وكانت الشاشات في شبكة مثل هذه:
+

+ +
++---+---+---+
+| A | B | C |
++---+---+---+
+| D | E | F |
++---+---+---+
+		
+ + فمن أجل كل شاشة، ستقوم بالاستدعاء بهذا الشكل:
+ + const w = 1920; +const h = 1080; +const fullWidth = w * 3; +const fullHeight = h * 2; + +// A +camera.setViewOffset( fullWidth, fullHeight, w * 0, h * 0, w, h ); +// B +camera.setViewOffset( fullWidth, fullHeight, w * 1, h * 0, w, h ); +// C +camera.setViewOffset( fullWidth, fullHeight, w * 2, h * 0, w, h ); +// D +camera.setViewOffset( fullWidth, fullHeight, w * 0, h * 1, w, h ); +// E +camera.setViewOffset( fullWidth, fullHeight, w * 1, h * 1, w, h ); +// F +camera.setViewOffset( fullWidth, fullHeight, w * 2, h * 1, w, h ); + + +

يرجى ملاحظة أنه لا يوجد سبب لأن تكون الشاشات بحجم متساوٍ أو متوضعة على شكل شبكة. يمكن توزيعها بأي شكل يناسب الاحتياجات المحددة.

+ +

[method:undefined updateProjectionMatrix]()

+

+ يقوم بتحديث مصفوفة الإسقاط الخاصة بالكاميرا، ويجب استدعاءها بعد أي تغيير في + المعاملات. +

+ +

[method:Object toJSON]([param:Object meta])

+

+ meta -- كائن يحتوي على بيانات وصفية مثل القوام أو الصور في الكائنات + الفرعية.
+ يمكن تحويل الكاميرا إلى تنسيق three.js + [link:https://github.com/mrdoob/three.js/wiki/JSON-Object-Scene-format-4 JSON Object/Scene format]. +

+ +

المصدر (Source)

+

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + + diff --git a/docs/api/ar/cameras/StereoCamera.html b/docs/api/ar/cameras/StereoCamera.html new file mode 100644 index 00000000000000..a735dc28778ffa --- /dev/null +++ b/docs/api/ar/cameras/StereoCamera.html @@ -0,0 +1,59 @@ + + + + + + + + + +

[name]

+ +

+ كاميرات بتأثير مزدوج [page:PerspectiveCamera] تستخدم لتحقيق تأثيرات مثل + [link:https://en.wikipedia.org/wiki/Anaglyph_3D 3D Anaglyph] أو + [link:https://en.wikipedia.org/wiki/parallax_barrier Parallax Barrier]. +

+ +

أمثلة (Examples)

+

+ [example:webgl_effects_anaglyph effects / anaglyph ]
+ [example:webgl_effects_parallaxbarrier effects / parallaxbarrier ]
+ [example:webgl_effects_stereo effects / stereo ] +

+ +

المنشئ (Constructor)

+

[name]( )

+ +

الخصائص (Properties)

+ +

[property:Float aspect]

+

العدد الافتراضي هو `1`.

+ +

[property:Float eyeSep]

+

العدد الافتراضي هو `0.064`.

+ +

[property:PerspectiveCamera cameraL]

+

+ الكاميرا اليسرى. يتم إضافتها إلى [page:Layers layer 1] - يجب أن يتم إضافة الكائنات التي يتم عرضها + بالكاميرا اليسرى إلى هذه الطبقة أيضًا. +

+ +

[property:PerspectiveCamera cameraR]

+

+ الكاميرا اليمنى. يتم إضافتها إلى [page:Layers layer 2] - يجب أن يتم إضافة الكائنات التي يتم عرضها + بالكاميرا اليمنى إلى هذه الطبقة أيضًا. +

+ +

الوظائف (Methods)

+ +

[method:undefined update]( [param:PerspectiveCamera camera] )

+

يقوم بتحديث الكاميرات الاستريو بناءً على الكاميرا الممررة كمعامل.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/constants/Animation.html b/docs/api/ar/constants/Animation.html new file mode 100644 index 00000000000000..565bedb6848b64 --- /dev/null +++ b/docs/api/ar/constants/Animation.html @@ -0,0 +1,46 @@ + + + + + + + + + +

Animation Constants

+ +

وضعيات التكرار (Loop Modes)

+ + +THREE.LoopOnce +THREE.LoopRepeat +THREE.LoopPingPong + + +

وضعيات التداخل (Interpolation Modes)

+ +THREE.InterpolateDiscrete +THREE.InterpolateLinear +THREE.InterpolateSmooth + + +

وضعيات النهاية (Ending Modes)

+ +THREE.ZeroCurvatureEnding +THREE.ZeroSlopeEnding +THREE.WrapAroundEnding + + +

وضعيات الدمج للرسوم المتحركة (Animation Blend Modes)

+ +THREE.NormalAnimationBlendMode +THREE.AdditiveAnimationBlendMode + + +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/constants.js src/constants.js] +

+ + diff --git a/docs/api/ar/constants/BufferAttributeUsage.html b/docs/api/ar/constants/BufferAttributeUsage.html new file mode 100644 index 00000000000000..e3579832efb0e5 --- /dev/null +++ b/docs/api/ar/constants/BufferAttributeUsage.html @@ -0,0 +1,52 @@ + + + + + + + + + +

ثوابت استخدام بيانات الذاكرة المؤقتة (Buffer Attribute Usage Constants)

+ +

+ يمكن استخدام الثوابت التي تحدد طريقة استخدام بيانات الذاكرة المؤقتة للهيكل الهندسي لتعطي تلميحاً لواجهة برمجة التطبيقات API بشأن كيفية استخدام بيانات الذاكرة المؤقتة لتحسين الأداء. +

+ +

مثال للكود

+ + + const geometry = new THREE.BufferGeometry(); + const positionAttribute = new THREE.BufferAttribute( array, 3 , false ); + positionAttribute.setUsage( THREE.DynamicDrawUsage ); + geometry.setAttribute( 'position', positionAttribute ); + + +

أمثلة (Examples)

+

[example:webgl_buffergeometry_drawrange materials / buffergeometry / drawrange ]

+ +

استخدام الهيكل الهندسي

+ + THREE.StaticDrawUsage + THREE.DynamicDrawUsage + THREE.StreamDrawUsage + + THREE.StaticReadUsage + THREE.DynamicReadUsage + THREE.StreamReadUsage + + THREE.StaticCopyUsage + THREE.DynamicCopyUsage + THREE.StreamCopyUsage + + + للحصول على معلومات أكثر تفصيلاً حول كل هذه الثوابت، يرجى الرجوع إلى + [link:https://www.khronos.org/opengl/wiki/Buffer_Object#Buffer_Object_Usage هذا الدليل الخاص بـ OpenGL]. + +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/constants.js src/constants.js] +

+ + diff --git a/docs/api/ar/constants/Core.html b/docs/api/ar/constants/Core.html new file mode 100644 index 00000000000000..c9c16ad69ba2ac --- /dev/null +++ b/docs/api/ar/constants/Core.html @@ -0,0 +1,65 @@ + + + + + + + + + +

الثوابت الأساسية (Core Constants)

+ +

رقم النسخة (Revision Number)

+ + +THREE.REVISION + + +
+ رقم النسخة الحالي لـ three.js هو [link:https://github.com/mrdoob/three.js/releases رقم الإصدار]. +
+ +

مساحات الألوان (Color Spaces)

+ +THREE.NoColorSpace = "" +THREE.SRGBColorSpace = "srgb" +THREE.LinearSRGBColorSpace = "srgb-linear" + +

+ [page:NoColorSpace]ﻻ يحدد مساحة الألوان بشكل عام. ويستخدم عادة للقواميس الخاصة بالألوان المتوسطة، مثل الخرائط العادية، وخرائط التصلب، وخرائط اللمعان، وخرائط الظل، والبيانات غير اللونية الأخرى. +

+

+ [page:SRGBColorSpace] ("srgb") يشير إلى مساحة الألوان التي تم تعريفها بواسطة المعايير الرئيسية Rec. 709، ونقطة الأبيض D65، والوظائف اللاخطية لنقل sRGB. وهو المساحة الافتراضية للألوان في CSS، ويتم العثور عليها في العديد من الباليتات اللونية وأدوات اختيار الألوان. وعادةً ما تكون الألوان المعبر عنها بالتعبير الست عشري أو CSS في مساحة الألوان sRGB. +

+ +

+ [page:LinearSRGBColorSpace] ("srgb-linear") يشير إلى مساحة الألوان sRGB (أعلاه) مع وظائف نقل النقل الخطي. وهي المساحة الافتراضية للألوان في three.js، وتستخدم طوال معظم عملية العرض. وتكون مكونات RGB التي توجد في مواد three.js والشواهد (shaders) في مساحة الألوان Linear-sRGB. + +

للمزيد من المعلومات حول الخلفية والاستخدام، يرجى الرجوع إلى "إدارة الألوان".

+ +

أزرار الماوس (Mouse Buttons)

+ +THREE.MOUSE.LEFT +THREE.MOUSE.MIDDLE +THREE.MOUSE.RIGHT +THREE.MOUSE.ROTATE +THREE.MOUSE.DOLLY +THREE.MOUSE.PAN + +

+ تحمل الثوابت LEFT و ROTATE نفس القيمة الأساسية. كذلك الثوابت MIDDLE و DOLLY تحملان نفس القيمة الأساسية. وتحمل الثوابت RIGHT و PAN نفس القيمة الأساسية. +

+ +

إجراءات اللمس (Touch Actions)

+ +THREE.TOUCH.ROTATE THREE.TOUCH.PAN THREE.TOUCH.DOLLY_PAN +THREE.TOUCH.DOLLY_ROTATE + + +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/constants.js src/constants.js] +

+ + diff --git a/docs/api/ar/constants/CustomBlendingEquations.html b/docs/api/ar/constants/CustomBlendingEquations.html new file mode 100644 index 00000000000000..8b1ee8591cb63c --- /dev/null +++ b/docs/api/ar/constants/CustomBlendingEquations.html @@ -0,0 +1,68 @@ + + + + + + + + + +

ثوابت معادلات الدمج المخصصة (Custom Blending Equation Constants)

+ +

+ تعمل هذه الثوابت مع جميع أنواع المواد. يتم تعيين وضع الدمج للمادة أولاً إلى THREE.CustomBlending، ثم تعيين معادلة الدمج المطلوبة وعامل المصدر وعامل الوجهة. +

+ +

مثال للكود

+ + + const material = new THREE.MeshBasicMaterial( {color: 0x00ff00} ); + material.blending = THREE.CustomBlending; + material.blendEquation = THREE.AddEquation; //default + material.blendSrc = THREE.SrcAlphaFactor; //default + material.blendDst = THREE.OneMinusSrcAlphaFactor; //default + + +

أمثلة (Examples)

+

+ [example:webgl_materials_blending_custom materials / blending / custom ] + +

+ +

معادلات الدمج (Blending Equations)

+ + THREE.AddEquation + THREE.SubtractEquation + THREE.ReverseSubtractEquation + THREE.MinEquation + THREE.MaxEquation + + +

عوامل المصدر (Source Factors)

+ + THREE.ZeroFactor + THREE.OneFactor + THREE.SrcColorFactor + THREE.OneMinusSrcColorFactor + THREE.SrcAlphaFactor + THREE.OneMinusSrcAlphaFactor + THREE.DstAlphaFactor + THREE.OneMinusDstAlphaFactor + THREE.DstColorFactor + THREE.OneMinusDstColorFactor + THREE.SrcAlphaSaturateFactor + + +

عوامل الوجهة (Destination Factors)

+

+ جميع عوامل المصدر صالحة كعوامل وجهة، باستثناء + THREE.SrcAlphaSaturateFactor +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/constants.js src/constants.js] +

+ + diff --git a/docs/api/ar/constants/Materials.html b/docs/api/ar/constants/Materials.html new file mode 100644 index 00000000000000..c030eb0c4c9982 --- /dev/null +++ b/docs/api/ar/constants/Materials.html @@ -0,0 +1,153 @@ + + + + + + + + + +

ثوابت المواد الخام (Material Constants)

+ +

+ تحدد هذه الثوابت الخصائص المشتركة بين جميع أنواع المواد الخام، باستثناء عمليات تجميع القوام الخاصة التي تنطبق فقط على + [page:MeshBasicMaterial.combine MeshBasicMaterial]، + [page:MeshLambertMaterial.combine MeshLambertMaterial] و + [page:MeshPhongMaterial.combine MeshPhongMaterial].
+

+ +

الجانب (Side)

+ + THREE.FrontSide + THREE.BackSide + THREE.DoubleSide + + +

+ تحدد الجانب الذي سيتم عرضه للوجوه، سواءً الوجه الأمامي، الوجه الخلفي، أو كلاهما. الإعداد الافتراضي هو الوجه الأمامي. +

+ +

وضعية الدمج (Blending Mode)

+ + THREE.NoBlending + THREE.NormalBlending + THREE.AdditiveBlending + THREE.SubtractiveBlending + THREE.MultiplyBlending + THREE.CustomBlending + + +

+ تحكم هذه الثوابت في معادلات الدمج بين المصدر والوجهة للألوان والألفا التي يتم إرسالها من المواد إلى WebGLRenderer للاستخدام بواسطة WebGL. + [page:Constant NormalBlending] هي الإعداد الافتراضي. ويجب تعيين [page:Constant CustomBlending] لاستخدام [page:CustomBlendingEquation Custom Blending Equations]. + راجع مثال [example:webgl_materials_blending materials / blending]. +

+ +

وضعية العمق (Depth Mode)

+ + THREE.NeverDepth + THREE.AlwaysDepth + THREE.EqualDepth + THREE.LessDepth + THREE.LessEqualDepth + THREE.GreaterEqualDepth + THREE.GreaterDepth + THREE.NotEqualDepth + +

+ تحدد وظيفة العمق التي تستخدمها المواد لمقارنة عمق بيكسلات الإدخال مع قيمة العمق الحالية في الذاكرة المؤقتة للعمق. إذا كانت نتيجة المقارنة صحيحة، فسيتم رسم البكسل.
+ [page:Materials NeverDepth] لن يتم رسم أي بكسل.
+ [page:Materials AlwaysDepth] سيتم رسم كل البكسلات.
+ [page:Materials EqualDepth] سيتم رسم البكسل إذا كان عمق البكسل المدخل مساوٍ لعمق الذاكرة المؤقتة الحالي.
+ [page:Materials LessDepth] سيتم رسم البكسل إذا كان عمق البكسل المدخل أقل من عمق الذاكرة المؤقتة الحالي.
+ [page:Materials LessEqualDepth] هي الإعداد الافتراضي وسيتم رسم البكسل إذا كان عمق البكسل المدخل أقل من أو يساوي عمق الذاكرة المؤقتة الحالي.
+ [page:Materials GreaterEqualDepth] سيتم رسم البكسل إذا كان عمق البكسل المدخل أكبر من أو يساوي عمق الذاكرة المؤقتة الحالي.
+ [page:Materials GreaterDepth] سيتم رسم البكسل إذا كان عمق البكسل المدخل أكبر من عمق الذاكرة المؤقتة الحالي.
+ [page:Materials NotEqualDepth] سيتم رسم البكسل إذا كان عمقالبكسل المدخل لا يساوي عمق الذاكرة المؤقتة الحالي.
+

+ +

عمليات تجميع القوام الخاصة بالقوام (Texture Combine Operations)

+ + THREE.MultiplyOperation + THREE.MixOperation + THREE.AddOperation + + +

+ تحدد هذه الثوابت كيفية دمج لون سطح الكائن مع خريطة البيئة (إذا كانت متاحة) في [page:MeshBasicMaterial.combine MeshBasicMaterial]، + [page:MeshLambertMaterial.combine MeshLambertMaterial] + و [page:MeshPhongMaterial.combine MeshPhongMaterial].
+ [page:Constant MultiplyOperation] هي الإعداد الافتراضي وتقوم بضرب لون خريطة البيئة بلون سطح الكائن.
+ [page:Constant MixOperation] تستخدم معامل الانعكاسية لدمج اللونين.
+ [page:Constant AddOperation] تقوم بجمع اللونين. +

+ +

دوال القالب (Stencil Functions)

+ + THREE.NeverStencilFunc + THREE.LessStencilFunc + THREE.EqualStencilFunc + THREE.LessEqualStencilFunc + THREE.GreaterStencilFunc + THREE.NotEqualStencilFunc + THREE.GreaterEqualStencilFunc + THREE.AlwaysStencilFunc + +

+ تحدد الدالة التي يستخدمها المادة لتحديد ما إذا كان يجب أن تنفذ عملية القالب أم لا.
+ [page:Materials NeverStencilFunc] لن يعود بقيمة صحيحة أبدًا.
+ [page:Materials LessStencilFunc] سيعود بقيمة صحيحة إذا كانت قيمة المرجع الختمية أقل من القيمة الحالية للختم.
+ [page:Materials EqualStencilFunc] سيعود بقيمة صحيحة إذا كانت قيمة المرجع الختمية تساوي القيمة الحالية للختم.
+ [page:Materials LessEqualStencilFunc] سيعود بقيمة صحيحة إذا كانت قيمة المرجع الختمية أقل من أو تساوي القيمة الحالية للختم.
+ [page:Materials GreaterStencilFunc] سيعود بقيمة صحيحة إذا كانت قيمة المرجع الختمية أكبر من القيمة الحالية للختم.
+ [page:Materials NotEqualStencilFunc] سيعود بقيمة صحيحة إذا كانت قيمة المرجع الختمية لا تساوي القيمة الحالية للختم.
+ [page:Materials GreaterEqualStencilFunc] سيعود بقيمة صحيحة إذا كانت قيمة المرجع الختمية أكبر من أو تساوي القيمة الحالية للختم.
+ [page:Materials AlwaysStencilFunc] سيعود بقيمة صحيحة دائمًا.
+

+ +

عمليات القالب (Stencil Operations)

+ + THREE.ZeroStencilOp + THREE.KeepStencilOp + THREE.ReplaceStencilOp + THREE.IncrementStencilOp + THREE.DecrementStencilOp + THREE.IncrementWrapStencilOp + THREE.DecrementWrapStencilOp + THREE.InvertStencilOp + +

+ تحدد العملية التي ستقوم بها المادة على ختم البيكسل في الختم إذا مرت الدالة الختمية المقدمة.
+ [page:Materials ZeroStencilOp] سيضبط قيمة الختم على الصفر.
+ [page:Materials KeepStencilOp] لن يقوم بتغيير قيمة الختم الحالية.
+ [page:Materials ReplaceStencilOp] سيقوم بتبديل قيمة الختم بقيمة المرجع الختمية المحددة.
+ [page:Materials IncrementStencilOp] سيزيد قيمة الختم الحالية بمقدار `1`.
+ [page:Materials DecrementStencilOp] سينقص قيمة الختم الحالية بمقدار `1`.
+ [page:Materials IncrementWrapStencilOp] سيزيد قيمة الختم الحالية بمقدار `1`. إذا زادت القيمة بعد ذلك عن `255`، فستضبط على `0`.
+ [page:Materials DecrementWrapStencilOp] سينقص قيمة الختم الحالية بمقدار `1`. إذا انخفضت القيمة بعد ذلك أقل من `0`، فستضبط على `255`.
+ [page:Materials InvertStencilOp] سيقوم بتنفيذ عملية انعكاس بتشكيلة الختم الحالية.
+

+ +

نوع خريطة العرض الطبيعي (Normal map type)

+ + THREE.TangentSpaceNormalMap + THREE.ObjectSpaceNormalMap + + +

+ تحدد نوع خريطة العرض الطبيعي. لـTangentSpaceNormalMap، المعلومات ذات الصلة بالسطح الأساسي. أما بالنسبة لـ ObjectSpaceNormalMap، المعلومات ذات الصلة باتجاه الكائن. الإعداد الافتراضي هو [page:Constant TangentSpaceNormalMap]. +

+ +

إصدار GLSL

+ + THREE.GLSL1 + THREE.GLSL3 + + +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/constants.js src/constants.js] +

+ + diff --git a/docs/api/ar/constants/Renderer.html b/docs/api/ar/constants/Renderer.html new file mode 100644 index 00000000000000..9b777bf95b9209 --- /dev/null +++ b/docs/api/ar/constants/Renderer.html @@ -0,0 +1,72 @@ + + + + + + + + + +

ثوابت WebGLRenderer

+ +

أنماط قطع الوجه (Cull Face Modes)

+ + THREE.CullFaceNone + THREE.CullFaceBack + THREE.CullFaceFront + THREE.CullFaceFrontBack + +

+ [page:constant CullFaceNone] تعطيل قطع الوجه.
+ [page:constant CullFaceBack] قطع الوجوه الخلفية (الافتراضي).
+ [page:constant CullFaceFront] قطع الوجوه الأمامية.
+ [page:constant CullFaceFrontBack] قطع كلا الوجوه الأمامية والخلفية. +

+ +

أنواع الظلال (Shadow Types)

+ + THREE.BasicShadowMap + THREE.PCFShadowMap + THREE.PCFSoftShadowMap + THREE.VSMShadowMap + +

+ هذه الخيارات تحدد خاصية [page:WebGLRenderer.shadowMap.type shadowMap.type] في WebGLRenderer.

+ [page:constant BasicShadowMap] يعطي خرائط ظل غير مصفاة - الأسرع ، ولكن الأقل جودة.
+ [page:constant PCFShadowMap] يصفي خرائط الظل باستخدام خوارزمية + Percentage-Closer Filtering (PCF) (افتراضي).
+ [page:constant PCFSoftShadowMap] يصفي خرائط الظل باستخدام خوارزمية + Percentage-Closer Filtering (PCF) مع أفضل الظلال الناعمة + خاصةً عند استخدام خرائط ظل بدقة منخفضة.
+ [page:constant VSMShadowMap] يصفي خرائط الظل باستخدام خوارزمية Variance Shadow + Map (VSM). عند استخدام VSMShadowMap ، سيقوم جميع مستقبلات الظل بإلقاء الظلال أيضًا. +

+ + +

Tone Mapping

+ + THREE.NoToneMapping + THREE.LinearToneMapping + THREE.ReinhardToneMapping + THREE.CineonToneMapping + THREE.ACESFilmicToneMapping + THREE.AgXToneMapping + THREE.NeutralToneMapping + THREE.CustomToneMapping + +

+ هذه الخيارات تحدد خاصية [page:WebGLRenderer.toneMapping toneMapping] في WebGLRenderer. يتم استخدام هذا لتقريب مظهر نطاق الإضاءة العالي (HDR) على الوسط الذي يحتوي على نطاق إضاءة منخفض على شاشة الكمبيوتر القياسية أو شاشة الجوال. +

+

+ THREE.LinearToneMapping، THREE.ReinhardToneMapping، THREE.CineonToneMapping، THREE.ACESFilmicToneMapping، THREE.AgXToneMapping و THREE.NeutralToneMapping هي تنفيذات مدمجة لتقريب مظهر نطاق الإضاءة العالي (HDR). يتوقع THREE.CustomToneMapping تنفيذًا مخصصًا عن طريق تعديل شفرة GLSL لبرنامج تظليل مقطع المواد. راجع [example:webgl_tonemapping WebGL / tonemapping] مثالًا. +

+

+ THREE.NeutralToneMapping is an implementation based on the Khronos 3D Commerce Group standard tone mapping. +

+ +

المصدر (Source)

+

+ [link:https://github.com/mrdoob/three.js/blob/master/src/constants.js src/constants.js] +

+ + diff --git a/docs/api/ar/constants/Textures.html b/docs/api/ar/constants/Textures.html new file mode 100644 index 00000000000000..198e161e3572b1 --- /dev/null +++ b/docs/api/ar/constants/Textures.html @@ -0,0 +1,601 @@ + + + + + + + + + +

ثوابت القوام (Texture Constants)

+ +

أوضاع التعيين (Mapping Modes)

+ + THREE.UVMapping + THREE.CubeReflectionMapping + THREE.CubeRefractionMapping + THREE.EquirectangularReflectionMapping + THREE.EquirectangularRefractionMapping + THREE.CubeUVReflectionMapping + + +

+ هذه تحدد وضع تعيين القوام.
+ [page:Constant UVMapping] هو الافتراضي ، ويقوم بتعيين القوام باستخدام إحداثيات UV للشبكة.

+ الباقي يحدد أنواع تعيين البيئة.

+ [page:Constant CubeReflectionMapping] و [page:Constant CubeRefractionMapping] للاستخدام مع [page:CubeTexture CubeTexture] ، + والتي تتكون من ستة قوام ، واحد لكل وجه من النرد. + [page:Constant CubeReflectionMapping] هو الافتراضي لـ + [page:CubeTexture CubeTexture].

+ [page:Constant EquirectangularReflectionMapping] و [page:Constant EquirectangularRefractionMapping] للاستخدام مع خريطة بيئة مستطيلة + . يسمى أيضًا خريطة lat-long ، قوام مستطيل + يمثل عرض 360 درجة على طول خط المركز الأفقي ، وعرض 180 درجة على طول المحور الرأسي ، مع حواف الأعلى والأسفل من + الصورة المقابلة للقطبين الشمال والجنوب من مجال معين + كروية.

+ راجع المثال [example:webgl_materials_envmaps materials / envmaps]. +

+ +

أوضاع التغليف (Wrapping Modes)

+ + THREE.RepeatWrapping + THREE.ClampToEdgeWrapping + THREE.MirroredRepeatWrapping + +

+ هذه تحدد خصائص [page:Texture.wrapS wrapS] و [page:Texture.wrapT wrapT] للقوام ، التي تحدد التغليف الأفقي والرأسي للقوام.

+ مع [page:constant RepeatWrapping] ستتكرر القوام ببساطة إلى ما لانهاية.

+ [page:constant ClampToEdgeWrapping] هو الافتراضي. يمتد بكسل آخر من القوام إلى حافة الشبكة.

+ مع [page:constant MirroredRepeatWrapping] ستتكرر القوام إلى ما لانهاية ، مع عكس كل تكرار. +

+ +

مرشحات التكبير (Magnification Filters)

+ + THREE.NearestFilter + THREE.LinearFilter + + +

+ للاستخدام مع خاصية [page:Texture.magFilter magFilter] للقوام ، + هذه تحدد وظيفة تكبير القوام المستخدمة عندما يتم تعيين البكسل المتعين + إلى منطقة أقل من أو تساوي عنصر قوام واحد (texel).

+ + [page:constant NearestFilter] يعيد قيمة عنصر القوام + الأقرب (في مسافة مانهاتن) إلى الإحداثيات المحددة للقوام. +

+ + [page:constant LinearFilter] هو الافتراضي ويعيد المتوسط ​​المرجح + للعناصر الأربعة من القوام التي هي الأقرب إلى الإحداثيات المحددة + للقوام ، ويمكن أن يشمل عناصر ملفوفة أو مكررة من أجزاء أخرى + من قوام ، اعتمادًا على قيم [page:Texture.wrapS wrapS] + و [page:Texture.wrapT wrapT] ، وعلى التعيين الدقيق. +

+ +

مرشحات التصغير (Minification Filters)

+ + THREE.NearestFilter + THREE.NearestMipmapNearestFilter + THREE.NearestMipmapLinearFilter + THREE.LinearFilter + THREE.LinearMipmapNearestFilter + THREE.LinearMipmapLinearFilter + + +

+ للاستخدام مع خاصية [page:Texture.minFilter minFilter] للقوام ، + هذه تحدد وظيفة تصغير القوام المستخدمة كلما كانت البكسل المتعين + يتطابق مع منطقة أكبر من عنصر قوام واحد (texel).

+ + بالإضافة إلى [page:constant NearestFilter] و [page:constant LinearFilter] ، يمكن استخدام الأربع وظائف التالية لـ + التصغير:

+ + [page:constant NearestMipmapNearestFilter] يختار mipmap الذي + يطابق حجم البكسل المتعين بشكل أكثر تطابقًا ويستخدم + معيار [page:constant NearestFilter] (ال texel الأقرب إلى المركز + من البكسل) لإنتاج قيمة قوام.

+ + [page:constant NearestMipmapLinearFilter] يختار mipmaps اثنان + يطابق حجم البكسل المتعين بشكل أكثر تطابقًا ويستخدم + معيار [page:constant NearestFilter] لإنتاج قيمة قوام من + كل mipmap. قيمة القوام النهائية هى متوسط ​​مرجح من هذه القيمتين.

+ + [page:constant LinearMipmapNearestFilter] يختار mipmap الذي + يطابق حجم البكسل المتعين بشكل أكثر تطابقًا ويستخدم + معيار [page:constant LinearFilter] (المتوسط ​​المرجح لأربعة `texels`) + التي هي الأقرب إلى مركز البكسل) لإنتاج قيمة قوام.

+ + [page:constant LinearMipmapLinearFilter] هو الافتراضي ويلتقط mipmaps اثنان + يطابق حجم البكسل المتعين بشكل أكثر تطابقًا ويلتقط [page:constant LinearFilter] + معاير لإنتاج قيمة قوام من كل mipmap. قيمة القوام النهائية هى متوسط ​​مرجح من + هذه القيمتين. + + انظر إلى المثال [example:webgl_materials_texture_filters materials / texture / filters]. +

+ +

الأنواع (Types)

+ + THREE.UnsignedByteType + THREE.ByteType + THREE.ShortType + THREE.UnsignedShortType + THREE.IntType + THREE.UnsignedIntType + THREE.FloatType + THREE.HalfFloatType + THREE.UnsignedShort4444Type + THREE.UnsignedShort5551Type + THREE.UnsignedInt248Type + THREE.UnsignedInt5999Type + +

+ للاستخدام مع خاصية [page:Texture.type type] للقوام ، التي يجب + أن تتوافق مع التنسيق الصحيح. انظر أدناه للحصول على التفاصيل.

+ + [page:constant UnsignedByteType] هو الافتراضي. +

+ +

التنسيقات (Formats)

+ + THREE.AlphaFormat + THREE.RedFormat + THREE.RedIntegerFormat + THREE.RGFormat + THREE.RGIntegerFormat + THREE.RGBFormat + THREE.RGBAFormat + THREE.RGBAIntegerFormat + THREE.DepthFormat + THREE.DepthStencilFormat + +

+ للاستخدام مع خاصية [page:Texture.format format] للقوام ، هذه + تحدد كيفية قراءة عناصر قوام ثنائي الأبعاد ، أو `texels` ، بواسطة المظللات.

+ + [page:constant AlphaFormat] يتجاهل المكونات الحمراء والخضراء والزرقاء + ويقرأ فقط المكون الألفا.

+ + [page:constant RedFormat] يتجاهل المكونات الخضراء والزرقاء ويقرأ + فقط المكون الأحمر. +

+ + [page:constant RedIntegerFormat] يتجاهل المكونات الخضراء والزرقاء + ويقرأ فقط المكون الأحمر. يتم قراءة `texels` كأعداد صحيحة بدلاً من + نقطة عائمة. +

+ + [page:constant RGFormat] يتجاهل المكونات الألفا والزرقاء ويقرأ + المكونات الحمراء والخضراء. +

+ + [page:constant RGIntegerFormat] يتجاهل المكونات الألفا والزرقاء + ويقرأ المكونات الحمراء والخضراء. يتم قراءة `texels` كأعداد صحيحة بدلاً من + نقطة عائمة. +

+ + [page:constant RGBAFormat] هو الافتراضي ويلتقط المكونات الحمراء والخضراء والزرقاء + والألفا.

+ + [page:constant RGBAIntegerFormat] هو الافتراضي ويلتقط المكونات الحمراء والخضراء ، + الزرقاء والألفا. يتم قراءة `texels` كأعداد صحيحة بدلاً من + نقطة عائمة. +

+ + [page:constant DepthFormat] يقرأ كل عنصر كقيمة عمق واحدة ، + يتحول إلى نقطة عائمة ، ويتم تثبيته في النطاق [0،1]. هذا هو + الافتراضي لـ [page:DepthTexture DepthTexture].

+ + [page:constant DepthStencilFormat] يقرأ كل عنصر هو زوج من العمق + وقيم المسح. يتم تفسير مكون العمق من الزوج كما هو مذكور في + [page:constant DepthFormat]. يتم تفسير مكون المسح بناءً على + التنسيق الداخلي للعمق + المسح. +

+ + لاحظ أنه يجب أن يكون للقوام نوع [page:Texture.type type] صحيحًا + كما هو مذكور أعلاه. انظر + [link:https://developer.mozilla.org/en/docs/Web/API/WebGLRenderingContext/texImage2D WebGLRenderingContext.texImage2D] للحصول على التفاصيل. +

+ +

تنسيقات القوام المضغوطة DDS / ST3C

+ + THREE.RGB_S3TC_DXT1_Format + THREE.RGBA_S3TC_DXT1_Format + THREE.RGBA_S3TC_DXT3_Format + THREE.RGBA_S3TC_DXT5_Format + +

+ للاستخدام مع خاصية [page:Texture.format format] لـ [page:CompressedTexture CompressedTexture] ، + هذه تتطلب دعمًا للتمديد + [link:https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/ WEBGL_compressed_texture_s3tc].

+ + هناك أربعة [link:https://en.wikipedia.org/wiki/S3_Texture_Compression تنسيقات S3TC] + متاحة عبر هذا التمديد. هذه هي:
+ [page:constant RGB_S3TC_DXT1_Format]: صورة مضغوطة بتنسيق DXT1 في تنسيق صورة RGB. +
+ [page:constant RGBA_S3TC_DXT1_Format]: صورة مضغوطة بتنسيق DXT1 في تنسيق صورة RGB + مع قيمة ألفا بسيطة تشغيل / إيقاف.
+ [page:constant RGBA_S3TC_DXT3_Format]: صورة مضغوطة بتنسيق DXT3 في تنسيق صورة RGBA. + مقارنة بقوام RGBA 32 بت ، يوفر ضغطًا 4: 1.
+ [page:constant RGBA_S3TC_DXT5_Format]: صورة مضغوطة بتنسيق DXT5 في تنسيق صورة RGBA. + كما يوفر ضغطًا 4: 1 ، ولكن يختلف عن ضغط DXT3 في كيفية ضغط الألفا.
+

+ +

تنسيقات القوام المضغوط PVRTC

+ + THREE.RGB_PVRTC_4BPPV1_Format + THREE.RGB_PVRTC_2BPPV1_Format + THREE.RGBA_PVRTC_4BPPV1_Format + THREE.RGBA_PVRTC_2BPPV1_Format + +

+ للاستخدام مع خاصية [page:Texture.format format] لـ [page:CompressedTexture CompressedTexture] ، + هذه تتطلب دعمًا للتمديد + [link:https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_pvrtc/ WEBGL_compressed_texture_pvrtc].
+ عادةً ما يكون PVRTC متاحًا فقط على الأجهزة المحمولة التي تحتوي على شرائح PowerVR ، + والتي هي في الغالب أجهزة Apple.

+ + هناك أربعة [link:https://en.wikipedia.org/wiki/PVRTC تنسيقات PVRTC] + متاحة عبر هذا التمديد. هذه هي:
+ [page:constant RGB_PVRTC_4BPPV1_Format]: ضغط RGB في وضع 4 بت. + كتلة واحدة لكل 4×4 بكسل.
+ [page:constant RGB_PVRTC_2BPPV1_Format]: ضغط RGB في وضع 2 بت. + كتلة واحدة لكل 8×4 بكسل.
+ [page:constant RGBA_PVRTC_4BPPV1_Format]: ضغط RGBA في وضع 4 بت. + كتلة واحدة لكل 4×4 بكسل.
+ [page:constant RGBA_PVRTC_2BPPV1_Format]: ضغط RGBA في وضع 2 بت. + كتلة واحدة لكل 8×4 بكسل.
+

+ + +

تنسيق القوام المضغوط ETC

+ + THREE.RGB_ETC1_Format + THREE.RGB_ETC2_Format + THREE.RGBA_ETC2_EAC_Format + +

+ للاستخدام مع خاصية [page:Texture.format format] لـ [page:CompressedTexture CompressedTexture] ، + هذه تتطلب دعمًا للتمديد + [link:https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_etc1/ WEBGL_compressed_texture_etc1] (ETC1) أو + [link:https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_etc/ WEBGL_compressed_texture_etc] (ETC2).

+

+ +

تنسيق القوام المضغوط ASTC

+ + THREE.RGBA_ASTC_4x4_Format + THREE.RGBA_ASTC_5x4_Format + THREE.RGBA_ASTC_5x5_Format + THREE.RGBA_ASTC_6x5_Format + THREE.RGBA_ASTC_6x6_Format + THREE.RGBA_ASTC_8x5_Format + THREE.RGBA_ASTC_8x6_Format + THREE.RGBA_ASTC_8x8_Format + THREE.RGBA_ASTC_10x5_Format + THREE.RGBA_ASTC_10x6_Format + THREE.RGBA_ASTC_10x8_Format + THREE.RGBA_ASTC_10x10_Format + THREE.RGBA_ASTC_12x10_Format + THREE.RGBA_ASTC_12x12_Format + +

+ للاستخدام مع خاصية [page:Texture.format format] لـ [page:CompressedTexture CompressedTexture] ، + هذه تتطلب دعمًا للتمديد + [link:https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_astc/ WEBGL_compressed_texture_astc].

+

+ +

تنسيق القوام المضغوط BPTC

+ + THREE.RGBA_BPTC_Format + +

+ للاستخدام مع خاصية [page:Texture.format format] لـ [page:CompressedTexture CompressedTexture] ، + هذه تتطلب دعمًا للتمديد + [link:https://www.khronos.org/registry/webgl/extensions/EXT_texture_compression_bptc/ EXT_texture_compression_bptc].

+

+ +

وظائف مقارنة القوام (Texture Comparison functions)

+ + THREE.NeverCompare + THREE.LessCompare + THREE.EqualCompare + THREE.LessEqualCompare + THREE.GreaterCompare + THREE.NotEqualCompare + THREE.GreaterEqualCompare + THREE.AlwaysCompare + + +

التنسيقات الداخلية (Internal Formats)

+ + 'ALPHA' + 'RGB' + 'RGBA' + 'LUMINANCE' + 'LUMINANCE_ALPHA' + 'RED_INTEGER' + 'R8' + 'R8_SNORM' + 'R8I' + 'R8UI' + 'R16I' + 'R16UI' + 'R16F' + 'R32I' + 'R32UI' + 'R32F' + 'RG8' + 'RG8_SNORM' + 'RG8I' + 'RG8UI' + 'RG16I' + 'RG16UI' + 'RG16F' + 'RG32I' + 'RG32UI' + 'RG32F' + 'RGB565' + 'RGB8' + 'RGB8_SNORM' + 'RGB8I' + 'RGB8UI' + 'RGB16I' + 'RGB16UI' + 'RGB16F' + 'RGB32I' + 'RGB32UI' + 'RGB32F' + 'RGB9_E5' + 'SRGB8' + 'R11F_G11F_B10F' + 'RGBA4' + 'RGBA8' + 'RGBA8_SNORM' + 'RGBA8I' + 'RGBA8UI' + 'RGBA16I' + 'RGBA16UI' + 'RGBA16F' + 'RGBA32I' + 'RGBA32UI' + 'RGBA32F' + 'RGB5_A1' + 'RGB10_A2' + 'RGB10_A2UI' + 'SRGB8_ALPHA8' + 'DEPTH_COMPONENT16' + 'DEPTH_COMPONENT24' + 'DEPTH_COMPONENT32F' + 'DEPTH24_STENCIL8' + 'DEPTH32F_STENCIL8' + + +

+ للاستخدام مع خاصية [page:Texture.internalFormat internalFormat] للقوام ، + هذه تحدد كيفية تخزين عناصر قوام ، أو `texels` ، + على وحدة معالجة الرسومات.

+ + [page:constant R8] يخزن المكون الأحمر على 8 بت.

+ + [page:constant R8_SNORM] يخزن المكون الأحمر على 8 بت. المكون + يتم تخزينه كمعيار.

+ + [page:constant R8I] يخزن المكون الأحمر على 8 بت. المكون هو + يتم تخزينه كعدد صحيح.

+ + [page:constant R8UI] يخزن المكون الأحمر على 8 بت. المكون هو + يتم تخزينه كعدد صحيح غير موقع.

+ + [page:constant R16I] يخزن المكون الأحمر على 16 بت. المكون هو + يتم تخزينه كعدد صحيح.

+ + [page:constant R16UI] يخزن المكون الأحمر على 16 بت. المكون + يتم تخزينه كعدد صحيح غير موقع.

+ + [page:constant R16F] يخزن المكون الأحمر على 16 بت. المكون هو + يتم تخزينه كعائمة نقطية.

+ + [page:constant R32I] يخزن المكون الأحمر على 32 بت. المكون هو + يتم تخزينه كعدد صحيح.

+ + [page:constant R32UI] يخزن المكون الأحمر على 32 بت. المكون + يتم تخزينه كعدد صحيح غير موقع.

+ + [page:constant R32F] يخزن المكون الأحمر على 32 بت. المكون هو + يتم تخزينه كعائمة نقطية.

+ + [page:constant RG8] يخزن المكونات الحمراء والخضراء على 8 بت لكل منهما.

+ + [page:constant RG8_SNORM] يخزن المكونات الحمراء والخضراء على 8 بت + لكل منهما. يتم تخزين كل مكون كمعيار. +

+ + [page:constant RG8I] يخزن المكونات الحمراء والخضراء على 8 بت لكل منهما. + يتم تخزين كل مكون كعدد صحيح. +

+ + [page:constant RG8UI] يخزن المكونات الحمراء والخضراء على 8 بت لكل منهما. + يتم تخزين كل مكون كعدد صحيح غير موقع. +

+ + [page:constant RG16I] يخزن المكونات الحمراء والخضراء على 16 بت لكل منهما. + يتم تخزين كل مكون كعدد صحيح. +

+ + [page:constant RG16UI] يخزن المكونات الحمراء والخضراء على 16 بت + لكل منهما. يتم تخزين كل مكون كعدد صحيح غير موقع. +

+ + [page:constant RG16F] يخزن المكونات الحمراء والخضراء على 16 بت لكل منهما. + يتم تخزين كل مكون كعائمة نقطية. +

+ + [page:constant RG32I] يخزن المكونات الحمراء والخضراء على 32 بت لكل منهما. + يتم تخزين كل مكون كعدد صحيح. +

+ + [page:constant RG32UI] يخزن المكونات الحمراء والخضراء على 32 بت. + يتم تخزين كل مكون كعدد صحيح غير موقع. +

+ + [page:constant RG32F] يخزن المكونات الحمراء والخضراء على 32 بت. + يتم تخزين كل مكون كعدد عائم. +

+ + [page:constant RGB8] يخزن المكونات الحمراء والخضراء والزرقاء على 8 بت + لكل منهما. [page:constant RGB8_SNORM] يخزن المكونات الحمراء والخضراء والزرقاء + على 8 بت لكل منهما. يتم تخزين كل مكون كمعيار. +

+ + [page:constant RGB8I] يخزن المكونات الحمراء والخضراء والزرقاء على 8 بت + لكل منهما. يتم تخزين كل مكون كعدد صحيح. +

+ + [page:constant RGB8UI] يخزن المكونات الحمراء والخضراء والزرقاء على 8 + بت لكل منهما. يتم تخزين كل مكون كعدد صحيح غير موقع. +

+ + [page:constant RGB16I] يخزن المكونات الحمراء والخضراء والزرقاء على 16 + بت لكل منهما. يتم تخزين كل مكون كعدد صحيح. +

+ + [page:constant RGB16UI] يخزن المكونات الحمراء والخضراء والزرقاء على 16 + بت لكل منهما. يتم تخزيน كل مكون كعدد صحيح غير موقع. +

+ + [page:constant RGB16F] يخزن المكونات الحمراء والخضراء والزرقاء على 16 + بت لكل منهما. يتم تخزين كل مكون كعدد عائم +

+ + [page:constant RGB32I] يخزن المكونات الحمراء والخضراء والزرقاء على 32 + بت لكل منهما. يتم تخزين كل مكون كعدد صحيح. +

+ + [page:constant RGB32UI] يخزن المكونات الحمراء والخضراء والزرقاء على 32 + بت لكل منهما. يتم تخزين كل مكون كعدد صحيح غير موقع. +

+ + [page:constant RGB32F] يخزن المكونات الحمراء والخضراء والزرقاء على 32 + بت لكل منهما. يتم تخزين كل مكون كعدد عائم. +

+ + [page:constant R11F_G11F_B10F] يخزن المكونات الحمراء والخضراء والزرقاء + على التوالي على 11 بت ، 11 بت ، و 10 بت. يتم تخزين كل مكون كعدد عائم. +

+ + [page:constant RGB565] يخزن المكونات الحمراء والخضراء والزرقاء + على التوالي على 5 بت ، 6 بت ، و 5 بت.

+ + [page:constant RGB9_E5] يخزن المكونات الحمراء والخضراء والزرقاء على 9 + بت لكل منهما.

+ + [page:constant RGBA8] يخزن المكونات الحمراء والخضراء والزرقاء وألفا على + 8 بت لكل منهما.

+ + [page:constant RGBA8_SNORM] يخزن المكونات الحمراء والخضراء والزرقاء وألفا + على 8 بت. يتم تخزين كل مكون كمعيار. +

+ + [page:constant RGBA8I] يخزن المكونات الحمراء والخضراء والزرقاء وألفا + على 8 بت لكل منهما. يتم تخزين كل مكون كعدد صحيح. +

+ + [page:constant RGBA8UI] يخزن المكونات الحمراء والخضراء والزرقاء وألفا + على 8 بت. يتم تخزين كل مكون كعدد صحيح غير موقع. +

+ + [page:constant RGBA16I] يخزن المكونات الحمراء والخضراء والزرقاء وألفا + على 16 بت. يتم تخزين كل مكون كعدد صحيح. +

+ + [page:constant RGBA16UI] يخزن المكونات الحمراء والخضراء والزرقاء وألفا + على 16 بت. يتم تخزين كل مكون كعدد صحيح غير موقع. +

+ + [page:constant RGBA16F] يخزن المكونات الحمراء والخضراء والزرقاء وألفا + على 16 بت. يتم تخزين كل مكون كعدد عائم. +

+ + [page:constant RGBA32I] يخزن المكونات الحمراء والخضراء والزرقاء وألفا + على 32 بت. يتم تخزين كل مكون كعدد صحيح. +

+ + [page:constant RGBA32UI] يخزن المكونات الحمراء والخضراء والزرقاء وألفا + على 32 بت. يتم تخزين كل مكون كعدد صحيح غير موقع. +

+ + [page:constant RGBA32F] يخزن المكونات الحمراء والخضراء والزرقاء وألفا + على 32 بت. يتم تخزين كل مكون كعدد عائم. +

+ + [page:constant RGB5_A1] يخزن المكونات الحمراء والخضراء والزرقاء وألفا + على التوالي على 5 بت ، 5 بت ، 5 بت ، و 1 بت.

+ + [page:constant RGB10_A2] يخزن المكونات الحمراء والخضراء والزرقاء وألفا + على التوالي على 10 بت ، 10 بت ، 10 بت و 2 بت.

+ + [page:constant RGB10_A2UI] يخزن المكونات الحمراء والخضراء والزرقاء وألفا + على التوالي على 10 بت ، 10 بت ، 10 بت و 2 بت. يتم تخزين كل + مكون كعدد صحيح غير موقع. +

+ + [page:constant SRGB8] يخزن المكونات الحمراء والخضراء والزرقاء على 8 بت + لكل منهما.

+ + [page:constant SRGB8_ALPHA8] يخزن المكونات الحمراء والخضراء والزرقاء وألفا + على 8 بت لكل منهما.

+ + [page:constant DEPTH_COMPONENT16] يخزن مكون العمق على 16 بت.

+ + [page:constant DEPTH_COMPONENT24] يخزن مكون العمق على 24 بت.

+ + [page:constant DEPTH_COMPONENT32F] يخزن مكون العمق على 32 بت. + يتم تخزين المكون كعدد عائم.

+ + [page:constant DEPTH24_STENCIL8] يخزن مكوان العمق ، ومكوان الإستانسيل + على التوالي على 24 بت و 8 بت. يتم تخزين مكوان الإستانسيل كعدد صحيح غير موقع. +

+ + [page:constant DEPTH32F_STENCIL8] يخزن مكوان العمق ، ومكوان الإستانسيل + على التوالي على 32 بت و 8 بت. يتم تخزين مكوان العمق كعدد عائم ، ومكوان الإستانسيل كعدد صحيح غير موقع. +

+ + لاحظ أنه يجب أن تحظى المادة المستهلكة للصورة (texture) بـ [page:Texture.type type] + صحيحة، فضلاً عن [page:Texture.format format]. اطَّلِعْ عَلى + [link:https://developer.mozilla.org/en/docs/Web/API/WebGLRenderingContext/texImage2D WebGLRenderingContext.texImage2D], and + [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext/texImage3D WebGL2RenderingContext.texImage3D], لِلْحُصُولِ عَلىْ تَفَاصِيلَ أَكْثَرَ حَولَ + إِجْهِادِ [page:Texture.format format], + [page:Texture.internalFormat internalFormat], and [page:Texture.type type].

+ + لِلْحُصُولِ عَلىْ مَعْلُوْمَاتَ أَشْمَلَ حَولَ التَّشْكِيلَاتِ الدَّاخِلِيَّةِ، يُمْكِنُ أَيْضا أَ نْ تُشِيرُ إِلَى + [link:https://www.khronos.org/registry/webgl/specs/latest/2.0/ WebGL2 Specification] and to the + [link:https://www.khronos.org/registry/OpenGL/specs/es/3.0/es_spec_3.0.pdf OpenGL ES 3.0 Specification]. +

+ +

تعبئة العمق (Depth Packing)

+ + THREE.BasicDepthPacking + THREE.RGBADepthPacking + +

+ للاستخدام مع خاصية [page:MeshDepthMaterial.depthPacking depthPacking] + لـ `MeshDepthMaterial`. +

+ +

مساحة اللون (Color Space)

+ + THREE.NoColorSpace = "" + THREE.SRGBColorSpace = "srgb" + THREE.LinearSRGBColorSpace = "srgb-linear" + +

+ يستخدم لتحديد مساحة اللون للصور (ومساحة اللون الناتجة من + المُصَوِّر).

+ + إذا تم تغيير نوع مساحة اللون بعد استخدام الصورة بالفعل + بواسطة مادة ، ستحتاج إلى تعيين [page:Material.needsUpdate Material.needsUpdate] إلى `true` لإعادة تجميع المادة.

+

+ +

المصدر (Source)

+

+ [link:https://github.com/mrdoob/three.js/blob/master/src/constants.js src/constants.js] +

+ + diff --git a/docs/api/ar/core/BufferAttribute.html b/docs/api/ar/core/BufferAttribute.html new file mode 100644 index 00000000000000..6be95c087f8ae2 --- /dev/null +++ b/docs/api/ar/core/BufferAttribute.html @@ -0,0 +1,241 @@ + + + + + + + + + +

[name]

+ +

+ تخزن هذه الفئة بيانات لسمة (مثل مواضع الرأس ، ومؤشرات الوجه ، والمعايير ، والألوان ، والأشعة فوق البنفسجية ، وأي سمات مخصصة) المرتبطة + مع [page:BufferGeometry] ، مما يسمح بتمرير البيانات بشكل أكثر كفاءة + إلى وحدة معالجة الرسومات. انظر تلك الصفحة للحصول على التفاصيل ومثال على الاستخدام. عند العمل + مع بيانات مثل المتجهات ، قد تكون طرق المساعد .fromBufferAttribute(attribute, index) + على [page:Vector2.fromBufferAttribute Vector2]، + [page:Vector3.fromBufferAttribute Vector3]، + [page:Vector4.fromBufferAttribute Vector4]، و + [page:Color.fromBufferAttribute Color] قد تكون مفيدة. +

+ +

المنشئ (Constructor)

+

[name]([param:TypedArray array]، [param:Integer itemSize]، [param:Boolean normalized])

+

+ [page:TypedArray array] - يجب أن يكون + [link:https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/TypedArray TypedArray]. يستخدم لإنشاء المخزن المؤقت.
+ يجب أن يحتوي هذا المصفوفة على + + itemSize * numVertices + + عناصر ، حيث numVertices هو عدد الرؤوس في المرتبطة + [page:BufferGemetry BufferGeometry].

+ + [page:Integer itemSize] - عدد قيم المصفوفة التي يجب + ترتبط برأس معين. على سبيل المثال ، إذا كانت هذه السمة هي + تخزين متجه من 3 مكونات (مثل الموضع أو الطبيعي أو اللون) ، فإن + itemSize يجب أن يكون 3. +

+ + [page:Boolean normalized] - (اختياري) ينطبق على البيانات الصحيحة فقط. + يشير إلى كيفية تعيين البيانات الأساسية في المخزن المؤقت إلى القيم في + كود GLSL. على سبيل المثال ، إذا كان [page:TypedArray array] هو نسخة من + UInt16Array ، و [page:Boolean normalized] صحيحًا ، فإن القиم `0 - + +65535` في بيانات المصفوفة ستُخرج إلى 0.0f - +1.0f في GLSL + attribute. ستُخرج Int16Array (signed) من -32768 - +32767 إلى -1.0f + - +1.0f. إذا كان [page:Boolean normalized] خطأً ، فستكون القيم + تحول إلى floats دون تعديل ، أي 32767 يصبح 32767.0f. +

+ +

الخصائص (Properties)

+ +

[property:TypedArray array]

+

ال[page:TypedArray array] التي تحمل البيانات المخزنة في المخزن المؤقت.

+ +

[property:Integer count]

+

+ يخزن طول [page:BufferAttribute.array array] مقسومًا على + [page:BufferAttribute.itemSize itemSize]. Read-only property.

+ + إذا كان المخزن يخزن متجهًا من 3 مكونات (مثل الموضع أو الطبيعي أو اللون) ، فسيحسب عدد هذه المتجهات المخزنة. +

+ +

[property:Boolean isBufferAttribute]

+

علامة للقراءة فقط للتحقق مما إذا كان الكائن المعطى من نوع [name].

+ +

[property:Integer itemSize]

+

+ طول المتجهات التي يتم تخزينها في + [page:BufferAttribute.array array]. +

+ +

[property:String name]

+

اسم اختياري لهذا الحدث من السمة. الافتراضي هو سلسلة فارغة.

+ +

[property:Boolean needsUpdate]

+

+ علامة للإشارة إلى أن هذه السمة قد تغيرت ويجب إعادة إرسالها إلى + وحدة معالجة الرسومات. قم بتعيين هذا على true عند تعديل قيمة المصفوفة.

+ + تعيين هذا على true يزيد أيضًا من [page:BufferAttribute.version version]. +

+ +

[property:Boolean normalized]

+

+ يشير إلى كيفية تعيين البيانات الأساسية في المخزن المؤقت إلى القيم في + كود GLSL. راجع المُنشئ أعلاه للحصول على التفاصيل. +

+ +

[property:Function onUploadCallback]

+

+ دالة رد اتصال يتم تنفيذها بعد أن قام Renderer بنقل + بيانات مصفوفة السمة إلى وحدة معالجة الرسومات. +

+ +

[property:Object updateRange]

+

+ كائن يحتوي على:
+ [page:Integer offset]: الافتراضية هي `0`. الموضع الذي يجب أن يبدأ فيه + التحديث.
+ [page:Integer count]: الافتراضية هي `-1` ، وهذا يعني عدم استخدام التحديث + المدى.

+ + يمكن استخدام هذا لتحديث بعض مكونات المتجهات المخزنة فقط (على سبيل المثال ، فقط المكوِّن المرتبط باللون). +

+ +

[property:Usage usage]

+

+ يحدد نمط الاستخدام المقصود لمخزن البيانات لأغراض التحسين + . يتوافق مع معلمة `usage` من + [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/bufferData WebGLRenderingContext.bufferData](). الافتراضي هو [page:BufferAttributeUsage StaticDrawUsage]. راجع الاستخدام [page:BufferAttributeUsage constants] لجميع + القيم الممكنة.

+ + ملاحظة: بعد الاستخدام الأولي للمخزن المؤقت ، لا يمكن تغيير استخدامه. + بدلاً من ذلك ، قم بإنشاء نسخة جديدة وتعيين الاستخدام المطلوب قبل التالي + جعل. +

+ +

[property:Integer version]

+

+ رقم إصدار ، يزداد كل مرة + [page:BufferAttribute.needsUpdate needsUpdate] يتم تعيين خاصية على true. +

+ +

الوظائف (Methods)

+ +

[method:this applyMatrix3]([param:Matrix3 m])

+

+ تطبق المصفوفة [page:Matrix3 m] على كل عنصر Vector3 من هذا + BufferAttribute. +

+ +

[method:this applyMatrix4]([param:Matrix4 m])

+

+ تطبق المصفوفة [page:Matrix4 m] على كل عنصر Vector3 من هذا + BufferAttribute. +

+ +

[method:this applyNormalMatrix]([param:Matrix3 m])

+

+ تطبق المصفوفة الطبيعية [page:Matrix3 m] على كل عنصر Vector3 من هذا + BufferAttribute. +

+ +

[method:this transformDirection]([param:Matrix4 m])

+

+ تطبق المصفوفة [page:Matrix4 m] على كل عنصر Vector3 من هذا + BufferAttribute ، مع تفسير العناصر كمتجهات اتجاه. +

+ +

[method:BufferAttribute clone]()

+

إرجاع نسخة من هذه bufferAttribute.

+ +

[method:this copy]([param:BufferAttribute bufferAttribute])

+

ينسخ BufferAttribute آخر إلى هذه BufferAttribute.

+ +

[method:this copyArray](array)

+

+ انسخ المصفوفة المعطاة هنا (والتي يمكن أن تكون مصفوفة عادية أو TypedArray) إلى + [page:BufferAttribute.array array].

+ + انظر + [link:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/set TypedArray.set] للحصول على ملاحظات حول المتطلبات إذا كان يتم نسخ TypedArray. +

+ +

[method:this copyAt]([param:Integer index1]، [param:BufferAttribute bufferAttribute]، [param:Integer index2])

+

انسخ متجهًا من bufferAttribute[index2] إلى [page:BufferAttribute.array array][index1].

+ +

[method:Number getComponent]( [param:Integer index], [param:Integer component] )

+

Returns the given component of the vector at the given index.

+ +

[method:Number getX]([param:Integer index])

+

يعيد مكون x من المتجه في المؤشر المحدد.

+ +

[method:Number getY]([param:Integer index])

+

يعيد مكون y من المتجه في المؤشر المحدد.

+ +

[method:Number getZ]([param:Integer index])

+

يعيد مكون z من المتجه في المؤشر المحدد.

+ +

[method:Number getW]([param:Integer index])

+

يعيد مكون w من المتجه في المؤشر المحدد.

+ +

[method:this onUpload]([param:Function callback])

+

+ يضع قيمة خاصية onUploadCallback.

+ + في [example:webgl_buffergeometry WebGL / Buffergeometry] يتم استخدام هذا + لتحرير الذاكرة بعد نقل المخزن إلى وحدة معالجة الرسومات. +

+ +

[method:this set]([param:Array value]، [param:Integer offset])

+

+ value - [page:Array] أو [page:TypedArray] لنسخ القيم منه. +
+ offset - (اختياري) فهرس [page:BufferAttribute.array array] في + الذي يجب البدء في النسخ.

+ + المكالمات + [link:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/set TypedArray.set]([page:Array value]، [page:Integer offset]) على + [page:BufferAttribute.array array].

+ + على وجه الخصوص ، انظر تلك الصفحة للحصول على متطلبات [page:Array value] كونها + [page:TypedArray]. +

+ +

[method:this setUsage]([param:Usage value])

+

+ قم بتعيين [page:BufferAttribute.usage usage] إلى value. راجع الاستخدام + [page:BufferAttributeUsage constants] لجميع قيم الإدخال الممكنة. +

+ + ملاحظة: بعد الاستخدام الأولي للمخزن المؤقت ، لا يمكن تغيير استخدامه. + بدلاً من ذلك ، قم بإنشاء نسخة جديدة وتعيين الاستخدام المطلوب قبل التالي + جعل. +

+ +

[method:Number setComponent]( [param:Integer index], [param:Integer component], [param:Float value] )

+

Sets the given component of the vector at the given index.

+ +

[method:this setX]([param:Integer index], [param:Float x])

+

تضبط مكون x من المتجه في المؤشر المحدد.

+ +

[method:this setY]([param:Integer index], [param:Float y])

+

تضبط مكون y من المتجه في المؤشر المحدد.

+ +

[method:this setZ]([param:Integer index], [param:Float z])

+

تضبط مكون z من المتجه في المؤشر المحدد.

+ +

[method:this setW]([param:Integer index], [param:Float w])

+

تضبط مكون w من المتجه في المؤشر المحдد.

+ +

[method:this setXY]([param:Integer index], [param:Float x], [param:Float y])

+

تضبط مكونات x و y من المتجه في + +

المصدر (Source)

+

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/core/BufferGeometry.html b/docs/api/ar/core/BufferGeometry.html new file mode 100644 index 00000000000000..28c9b868acd1d2 --- /dev/null +++ b/docs/api/ar/core/BufferGeometry.html @@ -0,0 +1,366 @@ + + + + + + + + + +

[name]

+ +

+ تمثيل شبكة ، خط ، أو هندسة نقطية. يتضمن مواقع الرأس ، ومؤشرات الوجه ، والمعايير ، والألوان ، والأشعة فوق البنفسجية ، والسمات المخصصة + داخل المخازن المؤقتة ، مما يقلل من تكلفة تمرير كل هذه البيانات إلى وحدة معالجة الرسومات. +

+

+ لقراءة وتحرير البيانات في سمات BufferGeometry ، راجع + [page:BufferAttribute] التوثيق. +

+ +

مثال للكود

+ + + const geometry = new THREE.BufferGeometry(); + + // إنشاء شكل مربع بسيط. نحن نكرر الأعلى الأيسر والأسفل الأيمن + // الرؤوس لأن كل رأس يحتاج إلى الظهور مرة واحدة لكل مثلث. + const vertices = new Float32Array( [ + -1.0, -1.0, 1.0, // v0 + 1.0, -1.0, 1.0, // v1 + 1.0, 1.0, 1.0, // v2 + + 1.0, 1.0, 1.0, // v3 + -1.0, 1.0, 1.0, // v4 + -1.0, -1.0, 1.0 // v5 + ] ); + + // itemSize = 3 لأنه يوجد 3 قيم (مكونات) لكل رأس + geometry.setAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) ); + const material = new THREE.MeshBasicMaterial( { color: 0xff0000 } ); + const mesh = new THREE.Mesh( geometry, material ); + + +

مثال للكود (Index)

+ + + const geometry = new THREE.BufferGeometry(); + + const vertices = new Float32Array( [ + -1.0, -1.0, 1.0, // v0 + 1.0, -1.0, 1.0, // v1 + 1.0, 1.0, 1.0, // v2 + -1.0, 1.0, 1.0, // v3 + ] ); + + const indices = [ + 0, 1, 2, + 2, 3, 0, + ]; + + geometry.setIndex( indices ); + geometry.setAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) ); + + const material = new THREE.MeshBasicMaterial( { color: 0xff0000 } ); + const mesh = new THREE.Mesh( geometry, material ); + + +

أمثلة (Examples)

+

+ [example:webgl_buffergeometry شبكة بوجوه غير مفهرسة]
+ [example:webgl_buffergeometry_indexed شبكة بوجوه مفهرسة]
+ [example:webgl_buffergeometry_lines خطوط]
+ [example:webgl_buffergeometry_lines_indexed خطوط مفهرسة]
+ [example:webgl_buffergeometry_custom_attributes_particles جزيئات]
+ [example:webgl_buffergeometry_rawshader محركات الظل الخام] +

+ +

المنشئ (Constructor)

+ +

[name]()

+
+ ينشئ هذا [name] جديدًا. كما يضبط العديد من الخصائص على قيمة افتراضية + قيمة. +
+ +

الخصائص (Properties)

+ +

[property:Object attributes]

+

+ يحتوي هذا الخريطة القائمة على الهوية على اسم السمة المراد تعيينها وكقيمة + [page:BufferAttribute buffer] لتعيينه. بدلاً من الوصول إلى هذا + الملكية مباشرة ، استخدم [page:.setAttribute] و [page:.getAttribute] ل + الوصول إلى سمات هذه الهندسة. +

+ +

[property:Box3 boundingBox]

+

+ مربع التحديد لـ bufferGeometry ، والذي يمكن حسابه بـ + [page:.computeBoundingBox](). الافتراضي هو `null`. +

+ +

[property:Sphere boundingSphere]

+

+ كرة التحديد لـ bufferGeometry ، والتي يمكن حسابها بـ + [page:.computeBoundingSphere](). الافتراضي هو `null`. +

+ +

[property:Object drawRange]

+

+ يحدد جزء الهندسة المراد عرضه. يجب عدم تعيين هذا + مباشرة ، بدلاً من ذلك استخدم [page:.setDrawRange]. الافتراضي هو + + { start: 0, count: Infinity } + + بالنسبة لـ BufferGeometry غير المفهرس ، count هو عدد القمم التي يتم عرضها. + بالنسبة لـ BufferGeometry المفهرس ، count هو عدد المؤشرات التي يتم عرضها. +

+ +

[property:Array groups]

+ +

+ قسم الهندسة إلى مجموعات ، كل منها سيتم عرضه في + مكالمة WebGL رسم منفصلة. هذا يسمح باستخدام مجموعة من المواد + مع الهندسة.

+ + كل مجموعة هي كائن من الشكل: + + { start: Integer, count: Integer, materialIndex: Integer } + + حيث يحدد start العنصر الأول في هذه المكالمة - الأول + رأس للهندسة غير المفهرسة ، وإلا فإن المثلث الأول فهرس. عد + يحدد كم عدد القمم (أو المؤشرات) المضمنة ، و materialIndex + يحدد فهرس مجموعة المواد للاستخدام.

+ + استخدم [page:.addGroup] لإضافة مجموعات ، بدلاً من تعديل هذا المصفوفة + مباشرة.

+ + يجب أن ينتمي كل رأس وفهرس إلى مجموعة واحدة فقط - يجب ألا تشترك المجموعات + قمم أو مؤشرات ، ويجب ألا تترك القمم أو المؤشرات غير المستخدمة. +

+ + + +

[property:Integer id]

+

رقم فريد لهذا الحال bufferGeometry.

+ +

[property:BufferAttribute index]

+

+ يتيح إعادة استخدام القمم عبر مثلثات متعددة ؛ هذا هو + يسمى باستخدام "المثلثات المفهرسة". يرتبط كل مثلث بـ + مؤشرات ثلاث قمم. لذلك تخزن هذه السمة فهرس + كل رأس لكل وجه ثلاثي الأضلاع. إذا لم يتم تعيين هذه السمة ، فإن + [page:WebGLRenderer renderer] يفترض أن كل ثلاثة مواقع متجاورة + تمثل مثلثًا واحدًا. الافتراضي هو `null`. +

+ +

[property:Boolean isBufferGeometry]

+

علامة للقراءة فقط للتحقق مما إذا كان الكائن المحدد من نوع [name].

+ +

[property:Object morphAttributes]

+

+ خريطة قائمة على [page:BufferAttribute]s التي تحمل تفاصيل هندسة المورف + الأهداف.
+ ملاحظة: بمجرد عرض الهندسة ، لا يمكن تغيير بيانات سمة المورف + يتغير. سيتعين عليك الاتصال [page:.dispose]() ، وإنشاء جديد + نسخة من [name]. +

+ +

[property:Boolean morphTargetsRelative]

+

+ يستخدم للتحكم في سلوك هدف المورف ؛ عند تعيينه على true ، المورف + يتم التعامل مع بيانات الهدف كإزاحات نسبية ، بدلاً من كـ + المواقع / المعايير المطلقة. الافتراضي هو `false`. +

+ +

[property:String name]

+

+ اسم اختياري لهذه الحالة bufferGeometry. الافتراضي هو فارغ + سلسلة. +

+ +

[property:Object userData]

+

+ كائن يمكن استخدامه لتخزين بيانات مخصصة حول BufferGeometry. + يجب ألا يحتوي على مراجع للوظائف حيث لن يتم استنساخها. +

+ +

[property:String uuid]

+

+ [link:http://en.wikipedia.org/wiki/Universally_unique_identifier UUID] من + هذه الحالة كائن. يتم تعيين هذا تلقائيًا ولا يجب أن يكون + تحرير. +

+ +

الوظائف (Methods)

+ +

+ [page:EventDispatcher EventDispatcher] الطرق متوفرة على هذا + صف. +

+ +

[method:undefined addGroup]([param:Integer start]، [param:Integer count]، [param:Integer materialIndex])

+

+ يضيف مجموعة إلى هذه الهندسة ؛ انظر [page:BufferGeometry.groups groups] + خاصية للحصول على التفاصيل. +

+ +

[method:this applyMatrix4]([param:Matrix4 matrix])

+

تطبق تحويل المصفوفة على الهندسة.

+ +

[method:this applyQuaternion]([param:Quaternion quaternion])

+

يطبق التدوير الممثل بالرباعي على الهندسة.

+ +

[method:this center] ()

+

مركز الهندسة بناءً على مربع التحديد.

+ +

[method:undefined clearGroups]( )

+

يمسح جميع المجموعات.

+ +

[method:BufferGeometry clone]()

+

ينشئ نسخة من هذا BufferGeometry.

+ +

[method:undefined computeBoundingBox]()

+

+ يحسب مربع التحديد للهندسة ، ويحدث [page:.boundingBox] + سمة.
+ لا يتم حساب مربعات التحديد افتراضيًا. يجب حسابها بشكل صريح + حساب ، وإلا كانت `null`. +

+ +

[method:undefined computeBoundingSphere]()

+

+ يحسب كرة التحديد للهندسة ، ويحدث [page:.boundingSphere] + سمة.
+ لا تتم حساب كرات التحديد افتراضيًا. يجب حسابها بشكل صريح + حساب ، وإلا كانت `null`. +

+ +

[method:undefined computeTangents]()

+

+ يحسب ويضيف سمة مماسية لهذه الهندسة.
+ الحساب مدعوم فقط للهندسات المفهرسة وإذا تم تعريف الوضعية ، + الطبيعية ، والأشعة فوق البنفسجية. عند استخدام خريطة طبيعية للفضاء المماس + خريطة ، تفضل خوارزمية MikkTSpace المقدمة من قبل + [page:BufferGeometryUtils.computeMikkTSpaceTangents] بدلاً من ذلك. +

+ +

[method:undefined computeVertexNormals]()

+

تحسب قمم الأوجه للبيانات القمية المعطاة. بالنسبة للهندسات المفهرسة ، يضبط الأسلوب كل قمة عادية لتكون متوسط ​​أوجه الوجه التي تشارك هذه القمة. + بالنسبة للهندسات غير المفهرسة ، لا تتشارك القمم ، ويضبط الأسلوب كل قمة عادية لتكون نفس وجه الوجه.

+ +

[method:this copy]( [param:BufferGeometry bufferGeometry] )

+

ينسخ BufferGeometry آخر إلى هذا BufferGeometry.

+ +

[method:BufferAttribute deleteAttribute]( [param:String name] )

+

تحذف [page:BufferAttribute attribute] بالاسم المحدد.

+ +

[method:undefined dispose]()

+

+ يطلق الموارد المتعلقة بوحدة معالجة الرسومات التي تم تخصيصها من قبل هذا المثيل. اتصل بهذا + الأسلوب كلما لم يعد هذا المثيل مستخدمًا في تطبيقك. +

+ +

[method:BufferAttribute getAttribute]( [param:String name] )

+

يرجع [page:BufferAttribute attribute] بالاسم المحدد.

+ +

[method:BufferAttribute getIndex] ()

+

يرجع [page:.index] buffer.

+ +

[method:Boolean hasAttribute]( [param:String name] )

+

يعود `true` إذا كانت السمة بالاسم المحدد موجودة.

+ +

[method:this lookAt] ( [param:Vector3 vector] )

+

+ vector - نقطة عالمية للنظر إليها.

+ + يدور الهندسة لمواجهة نقطة في الفضاء. يتم هذا عادةً كـ + عملية واحدة ، وليس أثناء حلقة. استخدم [page:Object3D.lookAt] لـ + الاستخدام النموذجي للشبكة في الوقت الفعلي. +

+ +

[method:undefined normalizeNormals]()

+

+ سيكون كل متجه طبيعي في هندسة بقوة 1. هذا سوف + تصحح الإضاءة على سطوح الهندسة. +

+ +

[method:this rotateX] ( [param:Float radians] )

+

+ قم بتدوير الهندسة حول المحور X. يتم هذا عادةً كعملية واحدة + عملية ، وليس أثناء حلقة. استخدم [page:Object3D.rotation] لـ + تدوير شبكة نموذجية في الوقت الفعلي. +

+ +

[method:this rotateY] ( [param:Float radians] )

+

+ قم بتدوير الهندسة حول المحور Y. يتم هذا عادةً كعملية واحدة + عملية ، وليس أثناء حلقة. استخدم [page:Object3D.rotation] لـ + تدوير شبكة نموذجية في الوقت الفعلي. +

+ +

[method:this rotateZ] ( [param:Float radians] )

+

+ قم بتدوير الهندسة حول المحور Z. يتم هذا عادةً كعملية واحدة + عملية ، وليس أثناء حلقة. استخدم [page:Object3D.rotation] لـ + تدوير شبكة نموذجية في الوقت الفعلي. +

+ +

[method:this scale] ( [param:Float x], [param:Float y], [param:Float z] )

+

+ قم بتغيير حجم بيانات الهندسة. يتم هذا عادةً كعملية واحدة ، + وليس أثناء حلقة. استخدام [page:Object3D.scale] لـ + تغيير حجم شبكات نموذجية في الوقت الفعلي. +

+ +

[method:this setAttribute]( [param:String name], [param:BufferAttribute attribute] )

+

+ يضع سمة لهذه الهندسة. استخدام هذا بدلاً من خصائص + الملكية ، لأن خريطة قائمة داخلية من [page:.attributes] + يتم صيانته لتسريع التكرار على السمات. +

+ +

[method:undefined setDrawRange] ( [param:Integer start], [param:Integer count] )

+

+ قم بتعيين خاصية [page:.drawRange]. بالنسبة لـ BufferGeometry غير المفهرس ، count + هو عدد القمم التي يتم عرضها. بالنسبة لـ BufferGeometry المفهرس ، count هو + عدد المؤشرات التي يتم عرضها. +

+ +

[method:this setFromPoints] ( [param:Array points] )

+

تضبط سمات هذا BufferGeometry من مصفوفة من النقاط.

+ +

[method:this setIndex] ( [param:BufferAttribute index] )

+

ضبط buffer [page:.index].

+ +

[method:Object toJSON]()

+

+ قُم بتحويل هندسة buffer إلى three.js + [link:https://github.com/mrdoob/three.js/wiki/JSON-Object-Scene-format-4 JSON Object/Scene format]. +

+ +

[method:BufferGeometry toNonIndexed]()

+

إرجاع إصدار غير مفهرس من BufferGeometry المفهرس.

+ +

[method:this translate] ( [param:Float x], [param:Float y], [param:Float z] )

+

+ قُم بترجمة الهندسة. يتم هذا عادةً كعملية واحدة ، + وليس أثناء حلقة. استخدم [page:Object3D.position] لـ + ترجمة شبكات نموذجية في الوقت الفعلي. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/core/Clock.html b/docs/api/ar/core/Clock.html new file mode 100644 index 00000000000000..68a13032db5f71 --- /dev/null +++ b/docs/api/ar/core/Clock.html @@ -0,0 +1,91 @@ + + + + + + + + + +

[name]

+ +

+ كائن لتتبع الوقت. يستخدم هذا + [link:https://developer.mozilla.org/en-US/docs/Web/API/Performance/now performance.now]. +

+ +

المنشئ (Constructor)

+ +

[name]( [param:Boolean autoStart] )

+

+ autoStart — (اختياري) ما إذا كان يجب تشغيل الساعة تلقائيًا عندما + [page:.getDelta]() يتم استدعاؤه لأول مرة. الافتراضي هو `true`. +

+ +

الخصائص (Properties)

+ +

[property:Boolean autoStart]

+

+ إذا تم تعيينه، يبدأ الساعة تلقائيًا عندما يتم استدعاء [page:.getDelta]() + لأول مرة. الافتراضي هو `true`. +

+ +

[property:Float startTime]

+

+ يحمل الوقت الذي تم فيه استدعاء طريقة [page:Clock.start start] للساعة + آخر مرة. الافتراضي هو `0`. +

+ +

[property:Float oldTime]

+

+ يحمل الوقت الذي تم فيه استدعاء طرق [page:Clock.start start], + [page:.getElapsedTime]() أو [page:.getDelta]() للساعة آخر مرة. + الافتراضي هو `0`. +

+ +

[property:Float elapsedTime]

+

+ يتبع المجموع الكلي للوقت الذي كانت تعمل فيه الساعة. الافتراضي هو + `0`. +

+ +

[property:Boolean running]

+

ما إذا كانت الساعة تعمل أم لا. الافتراضي هو `false`.

+ +

الوظائف (Methods)

+ +

[method:undefined start]()

+

+ يبدأ الساعة. كما يضبط [page:.startTime] و [page:.oldTime] على + الوقت الحالي، ويضبط [page:.elapsedTime] على `0` و [page:.running] على + `true`. +

+ +

[method:undefined stop]()

+

+ يوقف الساعة ويضبط [page:Clock.oldTime oldTime] على الوقت الحالي. +

+ +

[method:Float getElapsedTime]()

+

+ احصل على المجموع الكلي للثواني التي مرت منذ بدء تشغيل الساعة وضبط [page:.oldTime] على + الوقت الحالي.
+ إذا كان [page:.autoStart] هو `true` والساعة لا تعمل، فإنه يبدأ أيضًا + ساعة. +

+ +

[method:Float getDelta]()

+

+ احصل على المجموع الكلي للثواني التي مرت منذ ضبط [page:.oldTime] وضبط + [page:.oldTime] على الوقت الحالي.
+ إذا كان [page:.autoStart] هو `true` والساعة لا تعمل، فإنه يبدأ أيضًа + ساعة. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/core/EventDispatcher.html b/docs/api/ar/core/EventDispatcher.html new file mode 100644 index 00000000000000..200ed6c377d7bd --- /dev/null +++ b/docs/api/ar/core/EventDispatcher.html @@ -0,0 +1,74 @@ + + + + + + + + + +

[name]

+ +

+ أحداث JavaScript للكائنات المخصصة.
+ [link:https://github.com/mrdoob/eventdispatcher.js EventDispatcher على GitHub] +

+ +

مثال الكود

+ + + // إضافة الأحداث إلى كائن مخصص + class Car extends EventDispatcher { + start() { + this.dispatchEvent( { type: 'start', message: 'vroom vroom!' } ); + } + }; + + // استخدام الأحداث مع الكائن المخصص + const car = new Car(); + car.addEventListener( 'start', function ( event ) { + alert( event.message ); + } ); + + car.start(); + + +

المنشئ (Constructor)

+ +

[name]()

+

ينشئ كائن EventDispatcher.

+ +

الوظائف (Methods)

+ +

[method:undefined addEventListener]( [param:String type], [param:Function listener] )

+

+ type - نوع الحدث الذي يتم الاستماع إليه.
+ listener - الدالة التي يتم استدعاؤها عند إطلاق الحدث. +

+

يضيف مستمعًا إلى نوع حدث.

+ +

[method:Boolean hasEventListener]( [param:String type], [param:Function listener] )

+

+ type - نوع الحدث الذي يتم الاستماع إليه.
+ listener - الدالة التي يتم استدعاؤها عند إطلاق الحدث. +

+

يتحقق مما إذا كان المستمع قد تمت إضافته إلى نوع حدث.

+ +

[method:undefined removeEventListener]( [param:String type], [param:Function listener] )

+

+ type - نوع المستمع الذي يتم إزالته.
+ listener - دالة المستمع التي يتم إزالتها. +

+

يزيل مستمعًا من نوع حدث.

+ +

[method:undefined dispatchEvent]( [param:Object event] )

+

event - الحدث الذي يتم إطلاقه.

+

إطلاق نوع حدث.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/core/GLBufferAttribute.html b/docs/api/ar/core/GLBufferAttribute.html new file mode 100644 index 00000000000000..80be253ff9b8d9 --- /dev/null +++ b/docs/api/ar/core/GLBufferAttribute.html @@ -0,0 +1,139 @@ + + + + + + + + + +

[name]

+ +

+ لا يقوم هذا الفئة السمة المخزنة بإنشاء VBO. بدلاً من ذلك، يستخدم + أي VBO يتم تمريره في المُنشئ ويمكن تغييره لاحقًا عبر خاصية + `buffer`.

+ من الضروري تمرير معلمات إضافية جنبًا إلى جنب مع VBO. هذه هي: GL + context، نوع البيانات GL، عدد المكونات لكل رأس، عدد البايتات لكل + مكون، وعدد الرؤوس.

+ أكثر حالات الاستخدام شيوعًا لهذه الفئة هي عندما يتداخل نوع من + حسابات GPGPU أو حتى ينتج VBOs المعنية. +

+ +

Examples

+

+ [example:webgl_buffergeometry_glbufferattribute Points with custom buffers]
+

+ +

المنشئ (Constructor)

+

[name]( [param:WebGLBuffer buffer], [param:GLenum type], [param:Integer itemSize], [param:Integer elementSize], [param:Integer count], [param:Boolean normalized] )

+

+ `buffer` — يجب أن يكون + [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLBuffer WebGLBuffer]. +
+ `type` — واحد من + [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Constants#Data_types أنواع بيانات WebGL]. +
+ `itemSize` — عدد قيم المصفوفة التي يجب ربطها برأس معين. على سبيل + المثال، إذا كانت هذه السمة تخزن متجهًا ثلاثي الأبعاد (مثل الموضع، + الطبيعي، أو اللون)، فيجب أن يكون itemSize هو 3. +
+ `elementSize` — 1، 2 أو 4. الحجم المقابل (بالبايت) للمعلمة "type" + المعطاة. +

+ +
    +
  • gl.FLOAT: 4
  • +
  • gl.UNSIGNED_SHORT: 2
  • +
  • gl.SHORT: 2
  • +
  • gl.UNSIGNED_INT: 4
  • +
  • gl.INT: 4
  • +
  • gl.BYTE: 1
  • +
  • gl.UNSIGNED_BYTE: 1
  • +
+

`count` — عدد الرؤوس المتوقع في VBO.

+

+ `normalized` — (optional) Applies to integer data only. + Indicates how the underlying data in the buffer maps to the values in the + GLSL code. For instance, if [page:WebGLBuffer buffer] contains data of + `gl.UNSIGNED_SHORT`, and [page:Boolean normalized] is true, the values `0 - + +65535` in the buffer data will be mapped to 0.0f - +1.0f in the GLSL + attribute. A `gl.SHORT` (signed) would map from -32768 - +32767 to -1.0f + - +1.0f. If [page:Boolean normalized] is false, the values will be + converted to floats unmodified, i.e. 32767 becomes 32767.0f. +

+ +

الخصائص (Properties)

+ +

[property:WebGLBuffer buffer]

+

+ نسخة + [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLBuffer WebGLBuffer] الحالية. +

+ +

[property:Integer count]

+

عدد الرؤوس المتوقع في VBO.

+ +

[property:Integer elementSize]

+

+ يخزن الحجم المقابل بالبايت لقيمة خاصية `type` الحالية. +

+

انظر أعلاه (المُنشئ) لقائمة بأحجام الأنواع المعروفة.

+ +

[property:Boolean isGLBufferAttribute]

+

للقراءة فقط. دائمًا `true`.

+ +

[property:Integer itemSize]

+

كم عدد القيم التي تشكل كل عنصر (رأس).

+ +

[property:String name]

+

+ اسم اختياري لهذه الحالة من السمة. الافتراضي هو سلسلة فارغة. +

+ +

[property:Boolean needsUpdate]

+

+ الافتراضي هو `false`. تعيين هذا إلى true يزاد + [page:GLBufferAttribute.version version]. +

+ +

[property:Boolean normalized]

+

+ Indicates how the underlying data in the buffer maps to the values in the + GLSL shader code. See the constructor above for details. +

+ +

[property:GLenum type]

+

+ [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Constants#Data_types نوع بيانات WebGL] يصف محتوى VBO الأساسي. +

+

+ قم بتعيين هذه الخاصية معًا مع `elementSize`. الطريقة المستحسنة هي + باستخدام طريقة `setType`. +

+ +

[property:Integer version]

+

+ رقم إصدار، يزاد كل مرة يتم فيها تعيين خاصية needsUpdate على true. +

+ +

الوظائف (Methods)

+ +

[method:this setBuffer]( buffer )

+

تضبط خاصية `buffer`.

+ +

[method:this setType]( type, elementSize )

+

تضبط كلاً من خصائص `type` و `elementSize`.

+ +

[method:this setItemSize]( itemSize )

+

تضبط خاصية `itemSize`.

+ +

[method:this setCount]( count )

+

تضبط خاصية `count`.

+ +

المصدر (Source)

+

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/core/InstancedBufferAttribute.html b/docs/api/ar/core/InstancedBufferAttribute.html new file mode 100644 index 00000000000000..d5b36337370852 --- /dev/null +++ b/docs/api/ar/core/InstancedBufferAttribute.html @@ -0,0 +1,37 @@ + + + + + + + + + + [page:BufferAttribute] → + +

[name]

+

نسخة مثيلة من [page:BufferAttribute].

+ +

المنشئ (Constructor)

+

[name]( [param:TypedArray array], [param:Integer itemSize], [param:Boolean normalized], [param:Number meshPerAttribute] )

+ +

الخصائص (Properties)

+

انظر [page:BufferAttribute] للخصائص الموروثة.

+ +

[property:Number meshPerAttribute]

+

+ يحدد مدى تكرار قيمة هذه السمة المخزنة. قيمة واحدة تعني أن كل قيمة من + السمة المثيلة يتم استخدامها لمثيل واحد. قيمتان تعني أن كل قيمة يتم + استخدامها لمثيلين متتاليين (وهكذا). الافتراضي هو `1`. +

+ +

الوظائف (Methods)

+

انظر [page:BufferAttribute] للطُرق الموروثة.

+ +

المصدر (Source)

+

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + + diff --git a/docs/api/ar/core/InstancedBufferGeometry.html b/docs/api/ar/core/InstancedBufferGeometry.html new file mode 100644 index 00000000000000..8049a661a3381e --- /dev/null +++ b/docs/api/ar/core/InstancedBufferGeometry.html @@ -0,0 +1,39 @@ + + + + + + + + + + [page:BufferGeometry] → + +

[name]

+

نسخة مثيلة من [page:BufferGeometry].

+ +

المنشئ (Constructor)

+

[name]( )

+ +

الخصائص (Properties)

+

انظر [page:BufferGeometry] للخصائص الموروثة.

+ +

[property:Number instanceCount]

+

الافتراضي هو `Infinity`.

+ +

[property:Boolean isInstancedBufferGeometry]

+

علامة للقراءة فقط للتحقق مما إذا كان الكائن المعطى هو من نوع [name].

+ +

الوظائف (Methods)

+

انظر [page:BufferGeometry] للطُرق الموروثة.

+ +

[method:this copy]( [param:InstancedBufferGeometry source] )

+

ينسخ [name] المعطى إلى هذه الحالة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/core/InstancedInterleavedBuffer.html b/docs/api/ar/core/InstancedInterleavedBuffer.html new file mode 100644 index 00000000000000..5571c6313cef91 --- /dev/null +++ b/docs/api/ar/core/InstancedInterleavedBuffer.html @@ -0,0 +1,33 @@ + + + + + + + + + + [page:InterleavedBuffer] → + +

[name]

+

نسخة مثيلة من [page:InterleavedBuffer].

+ +

المنشئ (Constructor)

+

[name]( [param:TypedArray array], [param:Integer itemSize], [param:Number meshPerAttribute] )

+ +

الخصائص (Properties)

+

انظر [page:InterleavedBuffer] للخصائص الموروثة.

+ +

[property:Number meshPerAttribute]

+

الافتراضي هو `1`.

+ +

الوظائف (Methods)

+

انظر [page:InterleavedBuffer] للطُرق الموروثة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/core/InterleavedBuffer.html b/docs/api/ar/core/InterleavedBuffer.html new file mode 100644 index 00000000000000..46c830d018c46a --- /dev/null +++ b/docs/api/ar/core/InterleavedBuffer.html @@ -0,0 +1,119 @@ + + + + + + + + + +

[name]

+

+ "متداخل" يعني أن العديد من السمات، ربما من أنواع مختلفة، + (على سبيل المثال، الموضع، الطبيعي، uv، اللون) معبأة في مصفوفة واحدة. +

+ يمكن العثور على مقدمة في المصفوفات المتداخلة هنا: + [link:https://blog.tojicode.com/2011/05/interleaved-array-basics.html أساسيات المصفوفات المتداخلة] +

+ +

أمثلة (Examples)

+

+ [example:webgl_buffergeometry_points_interleaved webgl / buffergeometry / points / interleaved] +

+ +

المنشئ (Constructor)

+

[name]( [param:TypedArray array], [param:Integer stride] )

+

+ [page:TypedArray array] -- مصفوفة مكتوبة بذاكرة مشتركة. يخزن بيانات + الهندسة.
+ [page:Integer stride] -- عدد عناصر المصفوفة المكتوبة لكل رأس. +

+ +

الخصائص (Properties)

+ +

[property:Array array]

+

مصفوفة مكتوبة بذاكرة مشتركة. يخزن بيانات الهندسة.

+ +

[property:Integer stride]

+

عدد عناصر المصفوفة المكتوبة لكل رأس.

+ +

[property:Integer count]

+

يعطي إجمالي عدد العناصر في المصفوفة.

+ +

[property:Object updateRange]

+

+ كائن يحتوي على إزاحة وعدد.
+ - [page:Number offset]: الافتراضي هو `0`.
+ - [page:Number count]: الافتراضي هو `-1`.
+

+ +

[property:String uuid]

+

+ [link:http://en.wikipedia.org/wiki/Universally_unique_identifier UUID] من + هذه الحالة. يتم تعيين هذا تلقائيًا، لذلك لا يجب تحريره. +

+ +

[property:Integer version]

+

+ رقم إصدار، يزاد كل مرة يتم فيها تعيين خاصية needsUpdate على true. +

+ +

[property:Boolean needsUpdate]

+

+ الافتراضي هو `false`. تعيين هذا إلى true يزاد + [page:InterleavedBuffer.version version]. +

+ +

[property:Usage usage]

+

+ يحدد نمط الاستخدام المقصود لمخزن البيانات لأغراض التحسين. يتوافق مع + المعلمة `usage` من + [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/bufferData WebGLRenderingContext.bufferData](). +

+ +

الوظائف (Methods)

+ +

[method:this copy]( [param:InterleavedBuffer source] )

+

ينسخ [name] آخر إلى هذه [name].

+ +

[method:this copyAt]( [param:Integer index1], [param:InterleavedBuffer attribute], [param:Integer index2] )

+

+ ينسخ البيانات من `attribute[index2]` إلى [page:InterleavedBuffer.array array][index1]. +

+ +

[method:this set]( [param:TypedArray value], [param:Integer offset] )

+

+ value - المصفوفة (المكتوبة) المصدر.
+ offset - الإزاحة في المصفوفة الهدف التي يجب البدء فيها بكتابة القيم + من المصفوفة المصدر. الافتراضي هو `0`.

+ + يخزن قيمًا متعددة في المخزن، قراءة قيم الإدخال من + مصفوفة محددة. +

+ +

[method:InterleavedBuffer clone]( [param:Object data] )

+

+ data - يحمل هذا الكائن مصفوفات ذاكرة مشتركة مطلوبة لنسخ هندسات + بسمات متداخلة بشكل صحيح.

+ + ينشئ نسخة من هذه [name]. +

+ +

[method:this setUsage] ( [param:Usage value] )

+

ضبط [page:InterleavedBuffer.usage usage] على value.

+ +

[method:Object toJSON]( [param:Object data] )

+

+ data - يحمل هذا الكائن مصفوفات ذاكرة مشتركة مطلوبة لتسلسل هندسات + بسمات متداخلة بشكل صحيح.

+ + يسلسل هذه [name]. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/core/InterleavedBufferAttribute.html b/docs/api/ar/core/InterleavedBufferAttribute.html new file mode 100644 index 00000000000000..d976cb28deef21 --- /dev/null +++ b/docs/api/ar/core/InterleavedBufferAttribute.html @@ -0,0 +1,117 @@ + + + + + + + + + +

[name]

+ +

+ +

المنشئ (Constructor)

+

[name]( [param:InterleavedBuffer interleavedBuffer], [param:Integer itemSize], [param:Integer offset], [param:Boolean normalized] )

+ +

الخصائص (Properties)

+ +

[property:InterleavedBuffer data]

+

+ نسخة [page:InterleavedBuffer InterleavedBuffer] الممررة في + المُنشئ. +

+ +

[property:TypedArray array]

+

قيمة [page:InterleavedBufferAttribute.data data].array.

+ +

[property:Integer count]

+

+ قيمة [page:InterleavedBufferAttribute.data data].count. إذا كان + المخزن يخزن عنصرًا ثلاثي الأبعاد (مثل الموضع، الطبيعي، أو + اللون)، فسيحسب عدد هذه العناصر المخزنة. +

+ +

[property:Boolean isInterleavedBufferAttribute]

+

علامة للقراءة فقط للتحقق مما إذا كان الكائن المعطى هو من نوع [name].

+ +

[property:Integer itemSize]

+

كم عدد القيم التي تشكل كل عنصر.

+ +

[property:String name]

+

+ اسم اختياري لهذه الحالة من السمة. الافتراضي هو سلسلة فارغة. +

+ +

[property:Boolean needsUpdate]

+

+ الافتراضي هو `false`. تعيين هذا إلى `true` سيرسل المخزن المتداخل + بأكمله (وليس فقط بيانات السمة المحددة) إلى GPU مرة أخرى. +

+ +

[property:Boolean normalized]

+

الافتراضي هو `false`.

+ +

[property:Integer offset]

+

الإزاحة في المصفوفة الأساسية حيث يبدأ عنصر.

+ +

الوظائف (Methods)

+ +

[method:this applyMatrix4]( [param:Matrix4 m] )

+

+ يطبق مصفوفة [page:Matrix4 m] على كل عنصر Vector3 من هذه + InterleavedBufferAttribute. +

+ +

[method:this applyNormalMatrix]( [param:Matrix3 m] )

+

+ يطبق مصفوفة الطبيعي [page:Matrix3 m] على كل عنصر Vector3 من هذه + InterleavedBufferAttribute. +

+ +

[method:this transformDirection]( [param:Matrix4 m] )

+

+ يطبق مصفوفة [page:Matrix4 m] على كل عنصر Vector3 من هذه + InterleavedBufferAttribute، تفسير العناصر كمتجهات اتجاه. +

+ +

[method:Number getX]( [param:Integer index] )

+

يعيد مكون x للعنصر في المؤشر المعطى.

+ +

[method:Number getY]( [param:Integer index] )

+

يعيد مكون y للعنصر في المؤشر المعطى.

+ +

[method:Number getZ]( [param:Integer index] )

+

يعيد مكون z للعنصر في المؤشر المعطى.

+ +

[method:Number getW]( [param:Integer index] )

+

يعيد مكون w للعنصر في المؤشر المعطى.

+ +

[method:this setX]( [param:Integer index], [param:Float x] )

+

يضبط مكون x للعنصر في المؤشر المعطى.

+ +

[method:this setY]( [param:Integer index], [param:Float y] )

+

يضبط مكون y للعنصر في المؤشر المعطى.

+ +

[method:this setZ]( [param:Integer index], [param:Float z] )

+

يضبط مكون z للعنصر في المؤشر المعطى.

+ +

[method:this setW]( [param:Integer index], [param:Float w] )

+

يضبط مكون w للعنصر في المؤشر المعطى.

+ +

[method:this setXY]( [param:Integer index], [param:Float x], [param:Float y] )

+

يضبط مكونات x و y للعنصر في المؤشر المعطى.

+ +

[method:this setXYZ]( [param:Integer index], [param:Float x], [param:Float y], [param:Float z] )

+

يضبط مكونات x و y و z للعنصر في المؤشر المعطى.

+ +

[method:this setXYZW]( [param:Integer index], [param:Float x], [param:Float y], [param:Float z], [param:Float w] )

+

يضبط مكونات x و y و z و w للعنصر في المؤشر المعطى.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/core/Layers.html b/docs/api/ar/core/Layers.html new file mode 100644 index 00000000000000..5d6df8144acb8d --- /dev/null +++ b/docs/api/ar/core/Layers.html @@ -0,0 +1,100 @@ + + + + + + + + + +

[name]

+ +

+ كائن [page:Layers] يعين [page:Object3D] إلى 1 أو أكثر من 32 + طبقة مرقمة من `0` إلى `31` - يتم تخزين الطبقات داخليًا كـ + [link:https://en.wikipedia.org/wiki/Mask_(computing) قناع بت]، وافتراضيًا + كل Object3Ds عضو في الطبقة 0.

+ + يمكن استخدام هذا للتحكم في الرؤية - يجب أن يشترك كائن في طبقة مع + [page:Camera camera] ليكون مرئيًا عندما يتم عرض هذه الكاميرا + المعروضة.

+ + جميع الفئات التي ترث من [page:Object3D] لديها + [page:Object3D.layers] خاصية وهي نسخة من هذه الفئة. +

+ +

أمثلة (Examples)

+ +

[example:webgpu_layers WebGPU / layers]

+ +

المنشئ (Constructor)

+ +

[name]()

+

إنشاء كائن طبقات جديد، مع تعيين العضوية في البداية إلى الطبقة 0.

+ +

الخصائص (Properties)

+ +

[property:Integer mask]

+

+ قناع بت يخزن أي من الطبقات الـ 32 التي يكون هذا الكائن طبقات حاليًا + عضوًا فيه. +

+ +

الوظائف (Methods)

+ +

[method:undefined disable]( [param:Integer layer] )

+

+ layer - عدد صحيح من 0 إلى 31.

+ + إزالة عضوية هذه `layer`. +

+ +

[method:undefined enable]( [param:Integer layer] )

+

+ layer - عدد صحيح من 0 إلى 31.

+ + إضافة عضوية هذه `layer`. +

+ +

[method:undefined set]( [param:Integer layer] )

+

+ layer - عدد صحيح من 0 إلى 31.

+ + تعيين العضوية إلى `layer`، وإزالة العضوية من جميع الطبقات الأخرى. +

+ +

[method:Boolean test]( [param:Layers layers] )

+

+ layers - كائن طبقات

+ + يعود بـ true إذا كان هذا وكائن `layers` الممرر لديهما على الأقل واحدة + طبقة مشتركة. +

+ +

[method:Boolean isEnabled]( [param:Integer layer] )

+

+ layer - عدد صحيح من 0 إلى 31.

+ + يعود بـ true إذا تم تمكين الطبقة المعطاة. +

+ +

[method:undefined toggle]( [param:Integer layer] )

+

+ layer - عدد صحيح من 0 إلى 31.

+ + تبديل عضوية `layer`. +

+ +

[method:undefined enableAll]()

+

إضافة عضوية لجميع الطبقات.

+ +

[method:undefined disableAll]()

+

إزالة العضوية من جميع الطبقات.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/core/Object3D.html b/docs/api/ar/core/Object3D.html new file mode 100644 index 00000000000000..4e489330bcc40f --- /dev/null +++ b/docs/api/ar/core/Object3D.html @@ -0,0 +1,603 @@ + + + + + + + + + +

[name]

+ +

+ هذه هي الفئة الأساسية لمعظم الكائنات في three.js وتوفر مجموعة من + الخصائص والطُرق للتلاعب بالكائنات في المساحة ثلاثية الأبعاد.

+ + لاحظ أن هذا يمكن استخدامه لتجميع الكائنات عبر طريقة [page:.add]( object ) التي تضيف الكائن كطفل، ومع ذلك فمن الأفضل + استخدام [page:Group] لهذا. +

+ +

المنشئ (Constructor)

+ +

[name]()

+

المُنشئ لا يأخذ أي وسائط.

+ +

الخصائص (Properties)

+ +

[property:AnimationClip animations]

+

مصفوفة بمقاطع الرسوم المتحركة للكائن.

+ +

[property:Boolean castShadow]

+

ما إذا كان يتم عرض الكائن في خريطة الظل. الافتراضي هو `false`.

+ +

[property:Array children]

+

+ مصفوفة بأطفال الكائن. انظر [page:Group] للحصول على معلومات حول + تجميع الكائنات يدويًا. +

+ +

[property:Material customDepthMaterial]

+

+ مادة عمق مخصصة للاستخدام عند التقديم إلى خريطة العمق. يمكن استخدامه فقط + في سياق المشابك. عند رمي الظل مع + [page:DirectionalLight] أو [page:SpotLight]، إذا كنت تعدل + مواضع رأس في شادر رأس يجب تحديد customDepthMaterial مخصص + لظلال صحيحة. الافتراضي هو `undefined`. +

+ +

[property:Material customDistanceMaterial]

+

+ نفس [page:.customDepthMaterial customDepthMaterial]، ولكن يستخدم مع + [page:PointLight]. الافتراضي هو `undefined`. +

+ +

[property:Boolean frustumCulled]

+

+ عند تعيين هذا، يتحقق في كل إطار ما إذا كان الكائن في مخروط + الكاميرا قبل تقديم الكائن. إذا تم تعيينه على `false` يتم تقديم الكائن + في كل إطار حتى لو لم يكن في مخروط الكاميرا. + الافتراضي هو `true`. +

+ +

[property:Integer id]

+

readonly – رقم فريد لهذه الحالة من الكائن.

+ +

[property:Boolean isObject3D]

+

علامة للقراءة فقط للتحقق مما إذا كان الكائن المعطى هو من نوع [name].

+ +

[property:Layers layers]

+

+ عضوية طبقة الكائن. الكائن مرئي فقط إذا كان لديه + على الأقل طبقة واحدة مشتركة مع [page:Camera] قيد الاستخدام. يمكن استخدام هذه الخاصية أيضًا لتصفية + كائنات غير مرغوب فيها في اختبارات التقاطع بالأشعة + عند استخدام [page:Raycaster]. +

+ +

[property:Matrix4 matrix]

+

مصفوفة التحول المحلية.

+ +

[property:Boolean matrixAutoUpdate]

+

+ عند تعيين هذا، يحسب مصفوفة الموضع، (الدوران أو + quaternion) والحجم في كل إطار وأيضًا يعيد حساب خصائص matrixWorld + . الافتراضي هو [page:Object3D.DEFAULT_MATRIX_AUTO_UPDATE] (true). +

+ +

[property:Matrix4 matrixWorld]

+

+ التحول العالمي للكائن. إذا لم يكن لدى Object3D أي والد، فإنه + مطابق للتحول المحلي [page:.matrix]. +

+ +

[property:Boolean matrixWorldAutoUpdate]

+

+ إذا تم تعيينه، فإن المقدم يتحقق في كل إطار ما إذا كان الكائن و + أطفاله يحتاجون إلى تحديثات المصفوفة. عندما لا يكون كذلك، فعليك الحفاظ على جميع + المصفوفات في الكائن وأطفاله بنفسك. الافتراضي هو + [page:Object3D.DEFAULT_MATRIX_WORLD_AUTO_UPDATE] (true). +

+ +

[property:Boolean matrixWorldNeedsUpdate]

+

+ عند تعيين هذا، يحسب matrixWorld في ذلك الإطار ويعيد + هذه الخاصية إلى false. الافتراضي هو `false`. +

+ +

[property:Matrix4 modelViewMatrix]

+

+ يتم تمرير هذا إلى الشادر ويستخدم لحساب موضع + الكائن. +

+ +

[property:String name]

+

+ اسم اختياري للكائن (لا يحتاج إلى أن يكون فريدًا). الافتراضي هو + سلسلة فارغة. +

+ +

[property:Matrix3 normalMatrix]

+

+ يتم تمرير هذا إلى الشادر ويستخدم لحساب الإضاءة لـ + الكائن. هو نقل معكوس من المصفوفة 3x3 العلوية اليسرى + من مصفوفة modelViewMatrix لهذا الكائن.

+ + سبب هذه المصفوفة الخاصة هو أن استخدام ببساطة + modelViewMatrix قد يؤدي إلى طول غير وحدة من normals (على التحجيم) + أو في اتجاه غير عمودي (على التحجيم غير الموحد).

+ + من ناحية أخرى، جزء الترجمة من modelViewMatrix غير + ذات صلة بحساب normals. وبالتالي فإن Matrix3 كافية. +

+ +

[property:Function onAfterRender]

+

+ رد اتصال اختياري يتم تنفيذه مباشرة بعد تقديم كائن ثلاثي الأبعاد + . يتم استدعاء هذه الوظيفة بالمعلمات التالية: renderer, + scene, camera, geometry, material, group. +

+

+ يرجى ملاحظة أن هذا الرد الاتصال يتم تنفيذه فقط لـ `renderable` 3D + كائنات. معنى كائنات ثلاثية الأبعاد التي تحدد مظهرها المرئي مع + الهندسة والمواد مثل نسخ [page:Mesh]، [page:Line]، + [page:Points] أو [page:Sprite]. نسخ من [page:Object3D]، [page:Group] + أو [page:Bone] ليست قابلة للتقديم وبالتالي لا يتم تنفيذ هذا الرد الاتصال + لمثل هذه الكائنات. +

+ +

[property:Function onAfterShadow]

+

+ An optional callback that is executed immediately after a 3D object is + rendered to a shadow map. This function is called with the following parameters: renderer, + scene, camera, shadowCamera, geometry, depthMaterial, group. +

+

+ يرجى ملاحظة أن هذا الرد الاتصال يتم تنفيذه فقط لـ `renderable` 3D + كائنات. معنى كائنات ثلاثية الأبعاد التي تحدد مظهرها المرئي مع + الهندسة والمواد مثل نسخ [page:Mesh]، [page:Line]، + [page:Points] أو [page:Sprite]. نسخ من [page:Object3D]، [page:Group] + أو [page:Bone] ليست قابلة للتقديم وبالتالي لا يتم تنفيذ هذا الرد الاتصال + لمثل هذه الكائنات. +

+ +

[property:Function onBeforeRender]

+

+ رد اتصال اختياري يتم تنفيذه مباشرة قبل تقديم كائن ثلاثي الأبعاد + . يتم استدعاء هذه الوظيفة بالمعلمات التالية: renderer, + scene, camera, geometry, material, group. +

+

+ يرجى ملاحظة أن هذا الرد الاتصال يتم تنفيذه فقط لـ `renderable` 3D + كائنات. معنى كائنات ثلاثية الأبعاد التي تحدد مظهرها المرئي مع + الهندسة والمواد مثل نسخ [page:Mesh]، [page:Line]، + [page:Points] أو [page:Sprite]. نسخ من [page:Object3D]، [page:Group] + أو [page:Bone] ليست قابلة للتقديم وبالتالي لا يتم تنفيذ هذا الرد الاتصال + لمثل هذه الكائنات. +

+ +

[property:Function onBeforeShadow]

+

+ An optional callback that is executed immediately before a 3D object is + rendered to a shadow map. This function is called with the following parameters: renderer, + scene, camera, shadowCamera, geometry, depthMaterial, group. +

+

+ يرجى ملاحظة أن هذا الرد الاتصال يتم تنفيذه فقط لـ `renderable` 3D + كائنات. معنى كائنات ثلاثية الأبعاد التي تحدد مظهرها المرئي مع + الهندسة والمواد مثل نسخ [page:Mesh]، [page:Line]، + [page:Points] أو [page:Sprite]. نسخ من [page:Object3D]، [page:Group] + أو [page:Bone] ليست قابلة للتقديم وبالتالي لا يتم تنفيذ هذا الرد الاتصال + لمثل هذه الكائنات. +

+ +

[property:Object3D parent]

+

+ والد كائن في [link:https://en.wikipedia.org/wiki/Scene_graph scene graph]. يمكن أن يكون لكائن واحد على الأكثر واحد. +

+ +

[property:Vector3 position]

+

+ A [page:Vector3] يمثل الموضع المحلي للكائن. الافتراضي هو `(0, + 0, 0)`. +

+ +

[property:Quaternion quaternion]

+

دوران المحلية للكائن كـ [page:Quaternion Quaternion].

+ +

[property:Boolean receiveShadow]

+

ما إذا كان المادة تتلقى الظلال. الافتراضي هو `false`.

+ +

[property:Number renderOrder]

+

+ يسمح هذا القيمة بتجاوز ترتيب التصيير الافتراضي لـ + [link:https://en.wikipedia.org/wiki/Scene_graph scene graph] objects على الرغم من أن الأشياء المعتمة والشفافة لا تزال مرتبة + بشكل مستقل. عند تعيين هذه الخاصية لمثيل [page:Group Group]، سيتم فرز جميع الكائنات النسل وتقديمها معًا. + الترتيب من أدنى إلى أعلى renderOrder. القيمة الافتراضية هي `0`. +

+ +

[property:Euler rotation]

+

+ دوران المحلي للكائن (انظر + [link:https://en.wikipedia.org/wiki/Euler_angles Euler angles])، في + شعاع. +

+ +

[property:Vector3 scale]

+

مقياس المحلي للكائن. الافتراضي هو [page:Vector3]( 1, 1, 1 ).

+ +

[property:Vector3 up]

+

+ يستخدم هذا بواسطة [page:.lookAt lookAt] method، على سبيل المثال، إلى + تحديد توجه النتيجة.
+ الافتراضي هو [page:Object3D.DEFAULT_UP] - أي `( 0, 1, 0 )`. +

+ +

[property:Object userData]

+

+ كائن يمكن استخدامه لتخزين بيانات مخصصة حول Object3D. يجب ألا يحتوي على مراجع إلى وظائف كونها لن تكون مستنسخة. +

+ +

[property:String uuid]

+

+ [link:http://en.wikipedia.org/wiki/Universally_unique_identifier UUID] من + هذه المثيلة كائن. يتم تعيين هذا تلقائيًا، لذلك يجب عدم تحريره. +

+ +

[property:Boolean visible]

+

الكائن يحصل على التصيير إذا كان `true`. الافتراضي هو `true`.

+ +

الخصائص الثابتة (Static Properties)

+

+ يتم تعريف الخصائص والطرق الثابتة لكل فئة بدلاً من كل + مثيل لتلك الفئة. هذا يعني أن تغيير + [page:Object3D.DEFAULT_UP] أو [page:Object3D.DEFAULT_MATRIX_AUTO_UPDATE] + سيغير قيم [page:.up up] و [page:.matrixAutoUpdate matrixAutoUpdate] لـ `كل` مثيل من Object3D (أو الفئات المشتقة) + تم إنشاؤه بعد إجراء التغيير (لن يتأثر Object3Ds المنشأ بالفعل). +

+ +

[property:Vector3 DEFAULT_UP]

+

+ اتجاه [page:.up up] الافتراضي للكائنات، يستخدم أيضًا كموقع افتراضي + لـ [page:DirectionalLight]، [page:HemisphereLight] و + [page:Spotlight] (الذي يخلق أضواء تشرق من الأعلى إلى الأسفل).
+ مضبوط على ( 0, 1, 0 ) بشكل افتراضي. +

+ +

[property:Boolean DEFAULT_MATRIX_AUTO_UPDATE]

+

+ العدد الافتراضي لـ [page:.matrixAutoUpdate matrixAutoUpdate] لـ + Object3Ds المنشأة حديثًا.
+

+ +

[property:Boolean DEFAULT_MATRIX_WORLD_AUTO_UPDATE]

+

+ العدد الافتراضي لـ [page:.matrixWorldAutoUpdate + matrixWorldAutoUpdate] لـ Object3Ds المنشأة حديثًا.
+

+ +

الوظائف (Methods)

+ +

+ طرق [page:EventDispatcher EventDispatcher] متاحة على هذه + فئة. +

+ +

[method:this add]( [param:Object3D object], ... )

+

+ يضيف `object` كطفل لهذا الكائن. يمكن إضافة عدد غير محدود من الكائنات. سيتم إزالة أي والد حالي على كائن ممرر هنا، نظرًا لأنه يمكن أن يكون للكائن والد واحد على الأكثر.

+ + راجع [page:Group] للحصول على معلومات حول تجميع الكائنات يدويًا. +

+ +

[method:undefined applyMatrix4]( [param:Matrix4 matrix] )

+

+ يطبق تحويل المصفوفة على الكائن ويحدث موقع الكائن ودورانه ومقياسه. +

+ +

[method:this applyQuaternion]( [param:Quaternion quaternion] )

+

يطبق الدوران الممثل بالرباعي على الكائن.

+ +

[method:this attach]( [param:Object3D object] )

+

+ يضيف `object` كطفل لهذا، مع الحفاظ على تحويل العالم للكائن. +

+ ملاحظة: هذه الطريقة لا تدعم رسومات المشهد التي تحتوي على + نقاط غير متساوية في الحجم (s). +

+ +

[method:Object3D clone]( [param:Boolean recursive] )

+

+ recursive -- إذا كان صحيحًا، فإن نسل الكائن يتم استنساخه أيضًا. الافتراضي + صحيح.

+ + يعود بنسخة من هذا الكائن واختياريًا جميع النسل. +

+ +

[method:this copy]( [param:Object3D object], [param:Boolean recursive] )

+

+ recursive -- إذا كان صحيحًا، فإن نسل الكائن يتم نسخه أيضًا. الافتراضي + صحيح.

+ + نسخ الكائن المعطى في هذا الكائن. ملاحظة: لا يتم نسخ مستمعو الأحداث و + التعليمات البرمجية المعرفة من قبل المستخدم ([page:.onAfterRender] و [page:.onBeforeRender]). +

+ +

[method:Object3D getObjectById]( [param:Integer id] )

+

+ id -- رقم فريد لمثيل الكائن

+ + يبحث في كائن وأطفاله، بدءًا من الكائن + نفسه، ويرجع أول مطابقة مع id.
+ لاحظ أنه يتم تعيين المعرفات بالترتيب التاريخي: 1، 2، 3، ...، + زيادة بواحد لكل كائن جديد. +

+ +

[method:Object3D getObjectByName]( [param:String name] )

+

+ name -- سلسلة للمطابقة مع خصائص Object3D.name للأطفال. +

+ + يبحث في كائن وأطفاله، بدءًا من الكائن + نفسه، ويرجع أول مطابقة مع اسم.
+ لاحظ أنه بالنسبة لمعظم الكائنات، يكون الاسم سلسلة فارغة بشكل افتراضي. سوف + ستضطر إلى تعيينه يدويًا للاستفادة من هذه الطريقة. +

+ +

[method:Object3D getObjectByProperty]( [param:String name], [param:Any value] )

+

+ name -- اسم الخاصية التي يتم البحث عنها.
+ value -- قيمة الخاصية المعطاة.

+ + يبحث في كائن وأطفاله، بدءًا من الكائن + نفسه، ويرجع أول مع خاصية تطابق القيمة + معطى. +

+ +

[method:Object3D getObjectsByProperty]( [param:String name], [param:Any value], [param:Array optionalTarget] )

+

+ name -- اسم الخاصية التي يتم البحث عنها.
+ value -- قيمة الخاصية المعطاة.
+ optionalTarget -- (optional) target to set the result. + Otherwise a new Array is instantiated. If set, you must clear this + array prior to each call (i.e., array.length = 0;).

+ + يبحث في كائن وأطفاله، بدءًا من الكائن + نفسه، ويرجع جميع الكائنات مع خاصية تطابق القيمة + معطى. +

+ +

[method:Vector3 getWorldPosition]( [param:Vector3 target] )

+

+ [page:Vector3 target] — سيتم نسخ النتيجة في هذا Vector3. +

+ + يعود بمتجه يمثل موقع الكائن في الفضاء العالمي. +

+ +

[method:Quaternion getWorldQuaternion]( [param:Quaternion target] )

+

+ [page:Quaternion target] — سيتم نسخ النتيجة في هذا Quaternion. +

+ + يعود برباعي يمثل دوران الكائن في الفضاء العالمي. +

+ +

[method:Vector3 getWorldScale]( [param:Vector3 target] )

+

+ [page:Vector3 target] — سيتم نسخ النتيجة في هذا Vector3. +

+ + يعود بمتجه من عوامل التحجيم المطبقة على الكائن لكل + محور في الفضاء العالمي. +

+ +

[method:Vector3 getWorldDirection]( [param:Vector3 target] )

+

+ [page:Vector3 target] — سيتم نسخ النتيجة في هذا Vector3. +

+ + يعود بمتجه يمثل اتجاه محور z الموجب للكائن في + الفضاء العالمي. +

+ +

[method:Vector3 localToWorld]( [param:Vector3 vector] )

+

+ vector - متجه يمثل موقعًا في هذا المساحة المحلية للكائن.

+ + يحول المتجه من مساحة هذا الكائن المحلية إلى الفضاء العالمي. +

+ +

[method:undefined lookAt]( [param:Vector3 vector] )
+ [method:undefined lookAt]( [param:Float x], [param:Float y], [param:Float z] ) +

+

+ vector - متجه يمثل موقعًا في الفضاء العالمي.

+ اختياريًا، [page:.x x]، [page:.y y] و [page:.z z] مكونات من + موقع الفضاء العالمي.

+ + يدور الكائن لمواجهة نقطة في الفضاء العالمي.

+ + هذه الطريقة لا تدعم كائنات تحتوي على + والد (والدين) غير متساوي (ين) في الحجم. +

+ +

[method:undefined raycast]( [param:Raycaster raycaster], [param:Array intersects] )

+

+ طريقة فارغة (فارغة) للحصول على تقاطعات بين شعاع صب وهذا + كائن. تطبق فئات فرعية مثل [page:Mesh]، [page:Line]، و [page:Points] + هذه الطريقة من أجل استخدام التصوير بالأشعة. +

+ +

[method:this remove]( [param:Object3D object], ... )

+

+ يزيل `object` كطفل لهذا الكائن. يمكن إزالة عدد غير محدود من الكائنات + قد تم إزالته. +

+ +

[method:this removeFromParent]()

+

يزيل هذا الكائن من والده الحالي.

+ +

[method:this clear]()

+

يزيل جميع كائنات الأطفال.

+ +

[method:this rotateOnAxis]( [param:Vector3 axis], [param:Float angle] )

+

+ axis -- متجه معتدل في المساحة المحلية.
+ angle -- الزاوية بالراديان.

+ + قم بتدوير كائن على طول محور في المساحة المحلية. يفترض أن يكون المحور + معتدل. +

+ +

[method:this rotateOnWorldAxis]( [param:Vector3 axis], [param:Float angle])

+

+ axis -- متجه معتدل في المساحة العالمية.
+ angle -- الزاوية بالراديان.

+ + قم بتدوير كائن على طول محور في المساحة العالمية. يفترض أن يكون المحور + معتدل. تفترض طريقة عدم وجود والد دوران. +

+ +

[method:this rotateX]( [param:Float rad] )

+

+ rad - الزاوية للتدوير بالراديان.

+ + يدور الكائن حول محور x في المساحة المحلية. +

+ +

[method:this rotateY]( [param:Float rad] )

+

+ rad - الزاوية للتدوير بالراديان.

+ + يدور الكائن حول محور y في المساحة المحلية. +

+ +

[method:this rotateZ]( [param:Float rad] )

+

+ rad - الزاوية للتدوير بالراديان.

+ + يدور الكائن حول محور z في المساحة المحلية. +

+ +

[method:undefined setRotationFromAxisAngle]( [param:Vector3 axis], [param:Float angle] )

+

+ axis -- متجه معتدل في المساحة المحلية.
+ angle -- زاوية بالراديان

+ + يستدعي [page:Quaternion.setFromAxisAngle setFromAxisAngle]( [page:Float axis], [page:Float angle] ) على [page:.quaternion]. +

+ +

[method:undefined setRotationFromEuler]( [param:Euler euler] )

+

+ euler -- زاوية أويلر تحدد كمية الدوران.
+ + يستدعي [page:Quaternion.setRotationFromEuler setRotationFromEuler]( + [page:Euler euler]) على [page:.quaternion]. +

+ +

[method:undefined setRotationFromMatrix]( [param:Matrix4 m] )

+

+ m -- قم بتدوير الرباعي بمكون الدوران من المصفوفة.
+ + يستدعي [page:Quaternion.setFromRotationMatrix setFromRotationMatrix]( + [page:Matrix4 m]) على [page:.quaternion].

+ + لاحظ أن هذا يفترض أن الـ 3x3 العلوي من m هو مصفوفة دوران نقية + (أي غير مقاس). +

+ +

[method:undefined setRotationFromQuaternion]( [param:Quaternion q] )

+

+ q -- رباعي معتدل.

+ + نسخ الرباعي المعطى في [page:.quaternion]. +

+ +

[method:Object toJSON]( [param:Object meta] )

+

+ meta -- كائن يحتوي على بيانات تعريفية مثل المواد أو القوام أو الصور + للكائن.
+ قم بتحويل الكائن إلى three.js + [link:https://github.com/mrdoob/three.js/wiki/JSON-Object-Scene-format-4 JSON Object/Scene format]. +

+ +

[method:this translateOnAxis]( [param:Vector3 axis], [param:Float distance] )

+

+ axis -- متجه معتدل في المساحة المحلية.
+ distance -- المسافة التي يتم ترجمتها.

+ + قم بترجمة كائن بمسافة على طول محور في المساحة المحلية. يفترض أن يكون المحور + معتدل. +

+ +

[method:this translateX]( [param:Float distance] )

+

يترجم الكائن على طول محور x في المساحة المحلية بوحدات `distance`.

+ +

[method:this translateY]( [param:Float distance] )

+

يترجم الكائن على طول محور y في المساحة المحلية بوحدات `distance`.

+ +

[method:this translateZ]( [param:Float distance] )

+

يترجم الكائن على طول محور z في المساحة المحلية بوحدات `distance`.

+ +

[method:undefined traverse]( [param:Function callback] )

+

+ callback - وظيفة مع كأول وسيطة كائن object3D.

+ + ينفذ رد الاتصال على هذا الكائن وجميع النسل.
+ ملاحظة: يُفضل عدم تعديل رسم المشهد داخل رد الاتصال. +

+ +

[method:undefined traverseVisible]( [param:Function callback] )

+

+ callback - وظيفة مع كأول وسيطة كائن object3D.

+ + مثل traverse، ولكن سيتم تنفيذ رد الاتصال فقط للكائنات المرئية. + لا يتم اجتياز نسل الكائنات غير المرئية.
+ ملاحظة: يُفضل عدم تعديل رسم المشهد داخل رد الاتصال. +

+ +

[method:undefined traverseAncestors]( [param:Function callback] )

+

+ callback - وظيفة مع كأول وسيطة كائن object3D.

+ + ينفذ رد الاتصال على جميع الأجداد.
+ ملاحظة: يُفضل عدم تعديل رسم المشهد داخل رد الاتصال. +

+ +

[method:undefined updateMatrix]()

+

يحدث التحويل المحلي.

+ +

[method:undefined updateMatrixWorld]( [param:Boolean force] )

+

+ force - منطقي يمكن استخدامه لتجاوز + [page:.matrixWorldAutoUpdate]، لإعادة حساب مصفوفة العالم لـ + الكائن والنسل في الإطار الحالي. مفيد إذا لم تتمكن من الانتظار + المصور لتحديثه في الإطار التالي (باستخدام + [page:.matrixWorldAutoUpdate] مضبوط على `true`).

+ + يحدث التحويل العالمي للكائن ونسله إذا كان + تحتاج مصفوفة العالم إلى تحديث ([page:.matrixWorldNeedsUpdate] مضبوط على true) أو + إذا تم تعيين معلمة `force` على `true`. +

+ +

[method:undefined updateWorldMatrix]( [param:Boolean updateParents], [param:Boolean updateChildren] )

+

+ updateParents - يحدث تحويل العالم للأجداد بشكل متكرر.
+ updateChildren - يحدث تحويل العالم للنسل بشكل متكرر.

+ + يحدث التحويل العالمي للكائن. +

+ +

[method:Vector3 worldToLocal]( [param:Vector3 vector] )

+

+ vector - متجه يمثل موقعًا في الفضاء العالمي.

+ + يحول المتجه من المساحة العالمية إلى المساحة المحلية لهذا الكائن. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/core/Raycaster.html b/docs/api/ar/core/Raycaster.html new file mode 100644 index 00000000000000..1e9d9e7721ab2a --- /dev/null +++ b/docs/api/ar/core/Raycaster.html @@ -0,0 +1,236 @@ + + + + + + + + + +

[name]

+ +

+ تم تصميم هذه الفئة للمساعدة في + [link:https://en.wikipedia.org/wiki/Ray_casting raycasting]. يتم استخدام Raycasting + لاختيار الماوس (العمل على معرفة الكائنات في المساحة ثلاثية الأبعاد التي يكون عليها الماوس + فوق) من بين أشياء أخرى. +

+ +

مثال الكود

+ + const raycaster = new THREE.Raycaster(); + const pointer = new THREE.Vector2(); + + function onPointerMove( event ) { + + // حساب موضع المؤشر في إحداثيات الجهاز المعتدلة + // (-1 إلى +1) لكلا المكونين + + pointer.x = ( event.clientX / window.innerWidth ) * 2 - 1; + pointer.y = - ( event.clientY / window.innerHeight ) * 2 + 1; + + } + + function render() { + + // تحديث شعاع التقاط مع الكاميرا وموضع المؤشر + raycaster.setFromCamera( pointer, camera ); + + // حساب الكائنات المتقاطعة مع شعاع التقاط + const intersects = raycaster.intersectObjects( scene.children ); + + for ( let i = 0; i < intersects.length; i ++ ) { + + intersects[ i ].object.material.color.set( 0xff0000 ); + + } + + renderer.render( scene, camera ); + + } + + window.addEventListener( 'pointermove', onPointerMove ); + + window.requestAnimationFrame(render); + + + +

أمثلة (Examples)

+ +

+ [example:webgl_interactive_cubes Raycasting to a Mesh]
+ [example:webgl_interactive_cubes_ortho Raycasting to a Mesh in using an OrthographicCamera]
+ [example:webgl_interactive_buffergeometry Raycasting to a Mesh with BufferGeometry]
+ [example:webgl_instancing_raycast Raycasting to a InstancedMesh]
+ [example:webgl_interactive_lines Raycasting to a Line]
+ [example:webgl_interactive_raycasting_points Raycasting to Points]
+ [example:webgl_geometry_terrain_raycast Terrain raycasting]
+ [example:webgl_interactive_voxelpainter Raycasting to paint voxels]
+ [example:webgl_raycaster_texture Raycast to a Texture] +

+ +

المنشئ (Constructor)

+ +

[name]( [param:Vector3 origin], [param:Vector3 direction], [param:Float near], [param:Float far] )

+

+ [page:Vector3 origin] — متجه المنشأ الذي يلقي منه الشعاع.
+ [page:Vector3 direction] — متجه الاتجاه الذي يعطي اتجاهًا + للشعاع. يجب تعديله.
+ [page:Float near] — جميع النتائج المرتجعة أبعد من قريب. قريب + لا يمكن أن يكون سلبيًا. القيمة الافتراضية هي 0.
+ [page:Float far] — جميع النتائج المرتجعة أقرب من بعيد. بعيد لا يمكن + أقل من قريب. القيمة الافتراضية هي Infinity. +

+

هذا يخلق كائن raycaster جديد.

+ +

الخصائص (Properties)

+ +

[property:Float far]

+

+ عامل بعيد للraycaster. هذه القيمة تشير إلى الكائنات التي يمكن + تجاهلها بناءً على المسافة. هذه القيمة لا يجب أن تكون سلبية و + يجب أن تكون أكبر من خاصية قريب. +

+ +

[property:Float near]

+

+ عامل قريب للraycaster. هذه القيمة تشير إلى الكائنات التي يمكن + تجاهلها بناءً على المسافة. هذه القيمة لا يجب أن تكون سلبية و + يجب أن تكون أصغر من خاصية بعيد. +

+ +

[property:Camera camera]

+

+ الكاميرا المستخدمة عند التصوير بالأشعة ضد كائنات تعتمد على المشهد مثل + كائنات billboarded مثل [page:Sprites]. يمكن تعيين هذا الحقل يدويًا أو + يتم تعيينه عند استدعاء "setFromCamera". افتراضات إلى null. +

+ +

[property:Layers layers]

+

+ يستخدم من قبل [name] لتجاهل كائنات 3D بشكل اختياري عند إجراء + اختبارات التقاطع. يضمن المثال التالي للكود أن كائنات 3D فقط على طبقة `1` ستحظى باحترام من قِبَل المثيل من [name]. + + + raycaster.layers.set( 1 ); + object.layers.enable( 1 ); + +

+ +

[property:Object params]

+

+ كائن به الخصائص التالية: + + + { + Mesh: {}, + Line: { threshold: 1 }, + LOD: {}, + Points: { threshold: 1 }, + Sprite: {} + } + + + حيث threshold هو دقة raycaster عند التقاط + كائنات، في وحدات العالم. +

+ +

[property:Ray ray]

+

ال[Page:Ray] المستخدم للتصوير بالأشعة.

+ +

الوظائف (Methods)

+ +

[method:undefined set]( [param:Vector3 origin], [param:Vector3 direction])

+

+ [page:Vector3 origin] — متجه المنشأ الذي يلقي منه الشعاع.
+ [page:Vector3 direction] — متجه الاتجاه المعتدل الذي يعطي + اتجاه للشعاع. +

+

+ يحدث الشعاع بمنشأ واتجاه جديد. يرجى ملاحظة أن هذا + الطريقة تنسخ فقط القيم من الوسائط. +

+ +

[method:undefined setFromCamera]( [param:Vector2 coords], [param:Camera camera] )

+

+ [page:Vector2 coords] — إحداثيات 2D للماوس، في تعديل جهاز + إحداثيات (NDC) --- يجب أن تكون مكونات X و Y بين -1 و 1.
+ [page:Camera camera] — الكاميرا التي يجب أن ينبثق منها الشعاع +

+

يحدث الشعاع بمنشأ واتجاه جديد.

+ +

[method:this setFromXRController]( [param:WebXRController controller] )

+

+ [page:WebXRController controller] — The controller to copy the position and direction from. +

+

Updates the ray with a new origin and direction.

+ +

[method:Array intersectObject]( [param:Object3D object], [param:Boolean recursive], [param:Array optionalTarget] )

+

+ [page:Object3D object] — الكائن للتحقق من التقاطع مع + شعاع.
+ [page:Boolean recursive] — إذا كان صحيحًا، فإنه يتحقق أيضًا من جميع الأجداد. + وإلا فإنه يتحقق فقط من التقاطع مع الكائن. الافتراضي صحيح.
+ [page:Array optionalTarget] — (اختياري) هدف لتعيين النتيجة. + وإلا تم تجسيد جديد لـ [page:Array]. إذا تم تعيينه، يجب عليك مسح هذا + array قبل كل استدعاء (أي ، array.length = 0;). +

+

+ يتحقق من جميع التقاطع بين الشعاع والكائن مع أو بدون + نسل. يتم إرجاع التقاطعات مرتبة حسب المسافة، أولًا أولًا. + يتم إرجاع مصفوفة من التقاطعات ... +

+ [ { distance, point, face, faceIndex, object }, ... ] +

+ [page:Float distance] – المسافة بين مصدر الشعاع و + التقاطع
+ [page:Vector3 point] – نقطة التقاطع، في إحداثيات العالم
+ [page:Object face] – وجه التقاطع
+ [page:Integer faceIndex] – فهرس الوجه المتقاطع
+ [page:Object3D object] – الكائن المتقاطع
+ [page:Vector2 uv] - إحداثيات U,V في نقطة التقاطع
+ [page:Vector2 uv1] - مجموعة ثانية من إحداثيات U,V في نقطة + التقاطع
+ [page:Vector3 normal] - متجه عادي متداخل في نقطة + التقاطع
+ [page:Integer instanceId] – رقم فهرس الحالة حيث يتقاطع الشعاع + يتقاطع InstancedMesh +

+

+ `Raycaster` يفوض إلى [page:Object3D.raycast raycast] طريقة + كائن مار، عند تقييم ما إذا كان الشعاع يتقاطع مع الكائن أم لا + ليس. هذا يسمح لـ [page:Mesh meshes] بالرد بشكل مختلف على تصوير الأشعة + من [page:Line lines] و [page:Points pointclouds]. +

+

+ *ملاحظة* أنه بالنسبة للشبكات، يجب أن تكون الوجوه موجهة نحو مصدر + [page:.ray ray] من أجل اكتشافها؛ تقاطعات الشعاع المارة + من خلال الجزء الخلفي من وجه لن يتم اكتشافه. للتصوير بالأشعة ضد كلا + وجوه كائن، سترغب في تعيين خصائص [page:Mesh.material material] + [page:Material.side side] إلى `THREE.DoubleSide`. +

+ +

[method:Array intersectObjects]( [param:Array objects], [param:Boolean recursive], [param:Array optionalTarget] )

+

+ [page:Array objects] — الكائنات للتحقق من التقاطع مع + شعاع.
+ [page:Boolean recursive] — إذا كان صحيحًا، فإنه يتحقق أيضًا من جميع نسل + كائنات. وإلا فإنه يتحقق فقط من التقاطع مع الكائنات. افتراضي + صحيح.
+ [page:Array optionalTarget] — (اختياري) هدف لتعيين النتيجة. + وإلا تم تجسيد جديد لـ [page:Array]. إذا تم تعيينه، يجب عليك مسح هذا + array قبل كل استدعاء (أي ، array.length = 0;). +

+

+ يتحقق من جميع التقاطع بين الشعاع والكائنات مع أو بدون + نسل. يتم إرجاع التقاطعات مرتبة حسب المسافة، أولًا أولًا. التقاطات هي من نفس الشكل كما تلك المرتجعة بواسطة + [page:.intersectObject]. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/core/Uniform.html b/docs/api/ar/core/Uniform.html new file mode 100644 index 00000000000000..9b37fea1f10f65 --- /dev/null +++ b/docs/api/ar/core/Uniform.html @@ -0,0 +1,274 @@ + + + + + + + + + +

[name]

+ +

+ الموحدات هي متغيرات GLSL عالمية. يتم تمريرها إلى برامج الشادر. +

+ +

مثال الكود

+

+ عند إعلان موحد من [page:ShaderMaterial]، يتم إعلانه بواسطة + القيمة أو بالكائن. +

+ + uniforms: { + time: { value: 1.0 }, + resolution: new Uniform( new Vector2() ) + }; + + +

أنواع الموحدات (Uniform types)

+

+ يجب أن يكون لكل موحد خاصية `value`. يجب أن يتوافق نوع القيمة + مع نوع المتغير الموحد في كود GLSL كما + محدد لأنواع GLSL البدائية في الجدول أدناه. موحد + الهياكل والمصفوفات مدعومة أيضًا. يجب تحديد مصفوفات GLSL من نوع بدائي + إما كمصفوفة من كائنات THREE المقابلة أو + كمصفوفة مسطحة تحتوي على بيانات جميع الكائنات. بعبارة أخرى؛ + يجب عدم تمثيل المتغيرات البدائية في GLSL في المصفوفات بواسطة المصفوفات. هذه القاعدة + لا تطبق ترانزيتيفًا. يجب أن تكون مصفوفة من مصفوفات `vec2`، كل منها بطول + خمسة متجهات، يجب أن تكون مصفوفة من المصفوفات، من خمسة [page:Vector2] + كائنات أو عشرة `number`s. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Uniform types +
GLSL typeJavaScript type
int[page:Number]
uint[page:Number]
float[page:Number]
bool[page:Boolean]
bool[page:Number]
vec2[page:Vector2 THREE.Vector2]
vec2[page:Float32Array Float32Array] (*)
vec2[page:Array Array] (*)
vec3[page:Vector3 THREE.Vector3]
vec3[page:Color THREE.Color]
vec3[page:Float32Array Float32Array] (*)
vec3[page:Array Array] (*)
vec4[page:Vector4 THREE.Vector4]
vec4[page:Quaternion THREE.Quaternion]
vec4[page:Float32Array Float32Array] (*)
vec4[page:Array Array] (*)
mat2[page:Float32Array Float32Array] (*)
mat2[page:Array Array] (*)
mat3[page:Matrix3 THREE.Matrix3]
mat3[page:Float32Array Float32Array] (*)
mat3[page:Array Array] (*)
mat4[page:Matrix4 THREE.Matrix4]
mat4[page:Float32Array Float32Array] (*)
mat4[page:Array Array] (*)
ivec2, bvec2[page:Float32Array Float32Array] (*)
ivec2, bvec2[page:Array Array] (*)
ivec3, bvec3[page:Int32Array Int32Array] (*)
ivec3, bvec3[page:Array Array] (*)
ivec4, bvec4[page:Int32Array Int32Array] (*)
ivec4, bvec4[page:Array Array] (*)
sampler2D[page:Texture THREE.Texture]
samplerCube[page:CubeTexture THREE.CubeTexture]
+ +

+ (*) نفس الشيء بالنسبة لمصفوفة (الأبعاد الداخلية) من نفس نوع GLSL، + تحتوي على مكونات جميع المتجهات أو المصفوفات في المصفوفة. +

+ +

الزي المنظم (Structured Uniforms)

+ +

+ في بعض الأحيان ترغب في تنظيم الزي كـ `structs` في كود الشيدر الخاص بك. + يجب استخدام النمط التالي حتى يتمكن `three.js` من معالجة + بيانات الزي المنظمة. +

+ + uniforms = { + data: { + value: { + position: new Vector3(), + direction: new Vector3( 0, 0, 1 ) + } + } + }; + + يمكن تعيين هذا التعريف على كود GLSL التالي: + + struct Data { + vec3 position; + vec3 direction; + }; + uniform Data data; + + +

الزي المنظم مع المصفوفات (Structured Uniforms with Arrays)

+ +

+ من الممكن أيضًا إدارة `structs` في المصفوفات. تبدو صياغة هذه الحالة + الاستخدام كما يلي: +

+ + const entry1 = { + position: new Vector3(), + direction: new Vector3( 0, 0, 1 ) + }; + const entry2 = { + position: new Vector3( 1, 1, 1 ), + direction: new Vector3( 0, 1, 0 ) + }; + + uniforms = { + data: { + value: [ entry1, entry2 ] + } + }; + + يمكن تعيين هذا التعريف على كود GLSL التالي: + + struct Data { + vec3 position; + vec3 direction; + }; + uniform Data data[ 2 ]; + + +

المنشئ (Constructor)

+ +

[name]( [param:Object value] )

+

+ value -- كائن يحتوي على القيمة لإعداد الزى. يجب أن يكون نوعه + واحدًا من أنواع الزى المذكورة أعلاه. +

+ +

الخصائص (Properties)

+ +

[property:Object value]

+

القيمة الحالية للزى.

+ +

الوظائف (Methods)

+ +

[method:Uniform clone]()

+

+ يعود بنسخة من هذا الزى.
+ إذا كانت خاصية قيمة الزى هى [page:Object] مع طريقة clone()، + يتم استخدام هذا، وإلا يتم نسخ القيمة عن طريق التعيين. تتشارك قيم المصفوفات بين [page:Uniform]s المنسوخة. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/core/bufferAttributeTypes/BufferAttributeTypes.html b/docs/api/ar/core/bufferAttributeTypes/BufferAttributeTypes.html new file mode 100644 index 00000000000000..d5c0bc39b61c1a --- /dev/null +++ b/docs/api/ar/core/bufferAttributeTypes/BufferAttributeTypes.html @@ -0,0 +1,60 @@ + + + + + + + + + + [page:BufferAttribute] → + +

BufferAttribute Types

+ +

+ تتوفر في three.js تسعة أنواع من [page:BufferAttribute]. + تتوافق هذه الموجودات مع [link:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray#Syntax Typed Arrays] في JavaScript. +

+ + + THREE.Float32BufferAttribute + THREE.Float16BufferAttribute + THREE.Uint32BufferAttribute + THREE.Int32BufferAttribute + THREE.Uint16BufferAttribute + THREE.Int16BufferAttribute + THREE.Uint8ClampedBufferAttribute + THREE.Uint8BufferAttribute + THREE.Int8BufferAttribute + + +

المنشئ (Constructor)

+ +

يتم استدعاء كل من الأعلاف بنفس الطريقة.

+ +

+ TypedBufferAttribute( [param:Array_or_Integer array], [param:Integer itemSize], [param:Boolean normalized] ) +

+

+ array -- يمكن أن يكون هذا مصفوفة مكتوبة أو غير مكتوبة، أو طول الصحيح. سيتم تحويل قيمة المصفوفة إلى النوع المحدد. إذا تم توفير طول، فسيتم إنشاء TypedArray جديد، والذي سيتم تهيئته بجميع العناصر بقيم صفر.

+ + itemSize -- عدد القيم التي يجب أن ترتبط بنقطة فريدة.

+ + normalized -- (اختياري) يشير إلى كيفية تطابق البيانات الأساسية في الذاكرة المؤقتة مع القيم الموجودة في رمز GLSL. +

+ +

الخصائص (Properties)

+ +

انظر صفحة [page:BufferAttribute] للخصائص الموروثة.

+ +

الوظائف (Methods)

+ +

انظر صفحة [page:BufferAttribute] للأساليب الموروثة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/core/BufferAttribute.js src/core/BufferAttribute.js] +

+ + diff --git a/docs/api/ar/extras/DataUtils.html b/docs/api/ar/extras/DataUtils.html new file mode 100644 index 00000000000000..94f5b4c19b81b8 --- /dev/null +++ b/docs/api/ar/extras/DataUtils.html @@ -0,0 +1,36 @@ + + + + + + + + + +

[name]

+ +

فئة تحتوي على دوال مساعدة للبيانات.

+ +

الوظائف (Methods)

+ +

[method:Number toHalfFloat]( [param:Number val] )

+

+ val -- قيمة عائمة بدقة مفردة.

+ + يعود بقيمة عائمة نصف الدقة من القيمة العائمة بدقة مفردة المعطاة. +

+ +

[method:Number fromHalfFloat]( [param:Number val] )

+

+ val -- قيمة عائمة نصف الدقة.

+ + يعود بقيمة عائمة بدقة مفردة من القيمة العائمة نصف الدقة المعطاة. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/extras/Earcut.html b/docs/api/ar/extras/Earcut.html new file mode 100644 index 00000000000000..b95ec1b4bf2c32 --- /dev/null +++ b/docs/api/ar/extras/Earcut.html @@ -0,0 +1,33 @@ + + + + + + + + + +

[name]

+ +

+ تنفيذ لخوارزمية تثليث المضلع earcut. الكود هو نقل من [link:https://github.com/mapbox/earcut mapbox/earcut]. +

+ +

الوظائف (Methods)

+ +

[method:Array triangulate]( data, holeIndices, dim )

+

+ data -- مصفوفة مسطحة من إحداثيات الرأس.
+ holeIndices -- مصفوفة من فهارس الثقوب إن وجدت.
+ dim -- عدد الإحداثيات لكل رأس في المصفوفة الإدخال.

+ + يثلث التعريف الشكل المعطى عن طريق إرجاع مصفوفة من المثلثات. يتم تعريف المثلث بثلاثة أعداد صحيحة متتالية تمثل فهارس الرأس. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/extras/ImageUtils.html b/docs/api/ar/extras/ImageUtils.html new file mode 100644 index 00000000000000..3c11c74627bcb3 --- /dev/null +++ b/docs/api/ar/extras/ImageUtils.html @@ -0,0 +1,42 @@ + + + + + + + + + +

[name]

+ +

فئة تحتوي على دوال مساعدة للصور.

+ +

الوظائف (Methods)

+ +

+ [method:String getDataURL]( [param:HTMLCanvasElement image] | + [param:HTMLImageElement image] | [param:ImageBitmap image] ) +

+

+ image -- كائن الصورة.

+ + يعود بـ URI بيانات يحتوي على تمثيل للصورة المعطاة. +

+ +

+ [method:Object sRGBToLinear]( [param:HTMLCanvasElement image] | + [param:HTMLImageElement image] | [param:ImageBitmap image] ) +

+

+ image -- كائن الصورة.

+ + يحول بيانات الصورة sRGB المعطاة إلى مساحة اللون الخطية. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/extras/PMREMGenerator.html b/docs/api/ar/extras/PMREMGenerator.html new file mode 100644 index 00000000000000..1be5d31fd919cb --- /dev/null +++ b/docs/api/ar/extras/PMREMGenerator.html @@ -0,0 +1,111 @@ + + + + + + + + + +

[name]

+ +

+ تولد هذه الفئة خريطة بيئة إشعاعية محددة مسبقًا ومتدرجة (PMREM) من نسيج بيئة cubeMap. يسمح هذا بالوصول السريع إلى مستويات مختلفة من التشويش بناءً على خشونة المادة. على عكس سلسلة mipmap التقليدية ، فإنها تنخفض فقط إلى مستوى LOD_MIN (أعلاه) ، ثم تنشئ "mips" إضافية مُصفاة حتى أكثر في نفس دقة LOD_MIN ، المرتبطة بمستويات خشونة أعلى. بهذه الطريقة نحافظ على الدقة لتداخل الإضاءة المنتشرة بسلاسة مع تحديد حسابات العينات.

+ + ملاحظة: يعتمد خشونة [page:MeshStandardMaterial] الأدنى على حجم النسيج المقدم. إذا كانت عملية التصيير لديك أبعادًا صغيرة أو كانت الأجزاء اللامعة لديها الكثير من التقوس ، فقد لا تزال قادرًا على التخلص من حجم نسيج أصغر. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
حجم النسيج (texture size)الخشونة الدنيا (minimum roughness)
160.21
320.15
640.11
1280.076
2560.054
5120.038
10240.027
+ +

المنشئ (Constructor)

+ +

[name]( [param:WebGLRenderer renderer] )

+

ينشئ هذا المنشئ [name] جديدًا.

+ +

الوظائف (Methods)

+ +

+ [method:WebGLRenderTarget fromScene]( [param:Scene scene], [param:Number sigma], [param:Number near], [param:Number far] ) +

+

+ [page:Scene scene] - المشهد المعطى.
+ [page:Number sigma] - (اختياري) يحدد نصف قطر التشويش بالراديان ليتم تطبيقه على المشهد قبل إنشاء PMREM. الافتراضي هو `0`.
+ [page:Number near] - (اختياري) قيمة السطح القريب. الافتراضي هو `0.1`.
+ [page:Number far] - (اختياري) قيمة السطح البعيد. الافتراضي هو `100`.

+ + يولد PMREM من مشهد معطى ، والذي يمكن أن يكون أسرع من استخدام صورة إذا كانت عرض النطاق الترددي للشبكة منخفضة. تضمن الأسطح القريبة والبعيدة الاختيارية عرض المشهد بأكمله (يتم وضع cubeCamera في المنشأ). +

+ +

+ [method:WebGLRenderTarget fromEquirectangular]( [param:Texture equirectangular] ) +

+

+ [page:Texture equirectangular] - نسيج equirectangular.

+ + يولد PMREM من نسيج equirectangular. +

+ +

+ [method:WebGLRenderTarget fromCubemap]( [param:CubeTexture cubemap] ) +

+

+ [page:CubeTexture cubemap] - نسيج cubemap.

+ + يولد PMREM من نسيج cubemap. +

+ +

[method:undefined compileCubemapShader]()

+

+ يجمع مسبقًا شادر cubemap. يمكنك الحصول على بدء تشغيل أسرع عن طريق استدعاء هذه الطريقة أثناء جلب شبكة نسيجك لزيادة التزامن. +

+ +

[method:undefined compileEquirectangularShader]()

+

+ يجمع مسبقًا شادر equirectangular. يمكنك الحصول على بدء تشغيل أسرع عن طريق استدعاء هذه الطريقة أثناء جلب شبكة نسيجك لزيادة التزامن. +

+ +

[method:undefined dispose]()

+

+ يحرر الموارد المتعلقة بوحدة معالجة الرسومات التي تم تخصيصها من قبل هذه الحالة. استدعِ هذه الطريقة كلما لم يعُد هذا المثيل مستخدمًا في تطبيقك. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/extras/ShapeUtils.html b/docs/api/ar/extras/ShapeUtils.html new file mode 100644 index 00000000000000..f6b63425ee775b --- /dev/null +++ b/docs/api/ar/extras/ShapeUtils.html @@ -0,0 +1,52 @@ + + + + + + + + + +

[name]

+ +

+ فئة تحتوي على دوال مساعدة للأشكال.

+ + لاحظ أن هذه كلها دوال خطية لذلك يجب حسابها بشكل منفصل لمكونات x و y (و z و w إذا كانت موجودة) من متجه. +

+ +

الوظائف (Methods)

+ +

[method:Number area]( contour )

+

+ contour -- مضلع 2D. مصفوفة من THREE.Vector2()

+ + حساب مساحة مضلع (2D) المحيط. +

+ +

[method:Boolean isClockWise]( pts )

+

+ pts -- نقاط تحدد مضلع 2D

+ + لاحظ أن هذه هي دالة خطية لذلك يجب حسابها بشكل منفصل لمكونات x و y من المضلع.

+ + يستخدم داخليًا بواسطة [page:Path Path] و [page:ExtrudeGeometry ExtrudeGeometry] و [page:ShapeGeometry ShapeGeometry]. +

+ +

[method:Array triangulateShape]( contour, holes )

+

+ contour -- مضلع 2D. مصفوفة من [page:Vector2].
+ holes -- مصفوفة تحتوي على مصفوفات من [page:Vector2]s. كل مصفوفة + تمثل تعريف ثقب واحد.

+ + يستخدم داخليًا بواسطة [page:ExtrudeGeometry ExtrudeGeometry] و + [page:ShapeGeometry ShapeGeometry] لحساب الوجوه في الأشكال التي بها ثقوب. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/extras/core/Curve.html b/docs/api/ar/extras/core/Curve.html new file mode 100644 index 00000000000000..b000e7ef505276 --- /dev/null +++ b/docs/api/ar/extras/core/Curve.html @@ -0,0 +1,131 @@ + + + + + + + + + +

[name]

+ +

+ فئة أساسية مجردة لإنشاء كائن [name] يحتوي على طرق للإدراج. لمجموعة من [name]s انظر [page:CurvePath]. +

+ +

المنشئ (Constructor)

+ +

[name]()

+ +

ينشئ هذا المُنشئ [name] جديدًا.

+ +

الخصائص (Properties)

+ +

[property:Integer arcLengthDivisions]

+

+ يحدد هذا القيمة عدد الشعبات عند حساب طول الشطر التراكمي للمنحنى عبر [page:.getLengths]. لضمان + الدقة عند استخدام طرق مثل [page:.getSpacedPoints]، يوصى بزيادة [page:.arcLengthDivisions] إذا كان المنحنى كبيرًا جدًا. الافتراضي هو 200. +

+ +

الوظائف (Methods)

+ +

[method:Vector getPoint]( [param:Float t], [param:Vector optionalTarget] )

+

+ [page:Float t] - موقع على المنحنى. يجب أن يكون في النطاق [0، 1]. +
+ [page:Vector optionalTarget] - (اختياري) إذا تم تحديده، سيتم نسخ النتيجة في هذا الفيكتور، وإلا سيتم إنشاء فيكتور جديد. +

+ + يعود بفيكتور لموقع معين على المنحنى. +

+ +

[method:Vector getPointAt]( [param:Float u], [param:Vector optionalTarget] )

+

+ [page:Float u] - موقع على المنحنى وفقًا لطول القوس. يجب أن يكون في النطاق [0، 1].
+ [page:Vector optionalTarget] - (اختياري) إذا تم تحديده، سيتم نسخ النتيجة في هذا الفيكتور، وإلا سيتم إنشاء فيكتور جديد. +

+ + يعود بفيكتور لموقع معين على المنحنى وفقًا لطول القوس. +

+ +

[method:Array getPoints]( [param:Integer divisions] )

+

+ divisions - عدد الأجزاء التي يتم تقسيم المنحنى إليها. الافتراضي هو `5`.

+ + يعود بمجموعة من divisions + 1 نقطة باستخدام getPoint( t ). +

+ +

[method:Array getSpacedPoints]( [param:Integer divisions] )

+

+ divisions - عدد الأجزاء التي يتم تقسيم المнحنى إليها. الافتراضي هو `5`.

+ + يعود بمجموعة من divisions + 1 نقطة متساوية المسافات باستخدام getPointAt( u ). +

+ +

[method:Float getLength]()

+

احصل على إجمالي طول قوس المنحنى.

+ +

[method:Array getLengths]( [param:Integer divisions] )

+

احصل على قائمة بطول الشرائح التراكمية.

+ +

[method:undefined updateArcLengths]()

+

+ تحديث ذاكرة التخزين المؤقت للمسافة الشريطية التراكمية. يجب استدعاء الطريقة + في كل مرة يتم فيها تغيير معلمات المنحنى. إذا كان المنحنى المحدث جزءًا من + منحنى مكون مثل [page:CurvePath]، يجب + استدعاء [page:.updateArcLengths]() على المنحنى المكوَّن أيضًا. +

+ +

[method:Float getUtoTmapping]( [param:Float u], [param:Float distance] )

+

+ بالنظر إلى u في النطاق (0 .. 1) ، يعود [page:Float t] أيضًا في النطاق + (0 .. 1). يمكن استخدام u و t لإعطائك نقاط هي + متساوية البعد من نهايات المنحنى ، باستخدام [page:.getPoint]. +

+ +

[method:Vector getTangent]( [param:Float t], [param:Vector optionalTarget] )

+

+ [page:Float t] - موضع على المنحنى. يجب أن يكون في النطاق [0، 1]. +
+ [page:Vector optionalTarget] - (اختياري) إذا تم تحديده ، سيتم + نسخ النتيجة في هذا Vector ، وإلا سيتم إنشاء Vector جديد. +

+ + يعود بمتجه وحدة مماس على t. إذا لم يكن المنحنى المشتق + تطبيق تفريعه المماس ، سيتم استخدام نقطتين صغيرتين delta بعيدًا للعثور على التدرج الذي يبدو أنه يعطي تقريبًا معقولًا. +

+ +

[method:Vector getTangentAt]( [param:Float u], [param:Vector optionalTarget] )

+

+ [page:Float u] - موقع على المنحنى وفقًا لطول القوس. يجب أن يكون في النطاق [0، 1]. + + [page:Vector optionalTarget] - (اختياري) إذا تم تحديده، سيتم نسخ النتيجة في هذا الفيكتور، وإلا سيتم إنشاء فيكتور جديد. + + يعود الميل في نقطة تبعد مسافة متساوية عن طرفي المنحنى من النقطة المعطاة في [page:.getTangent]. +

+ +

[method:Object computeFrenetFrames]( [param:Integer segments], [param:Boolean closed] )

+

+ إنشاء Frenet Frames. يتطلب تعريف منحى في فضاء 3D. مستخدم + في هَیْأَات مثل [page:TubeGeometry] أو [page:ExtrudeGeometry]. +

+ +

[method:Curve clone]()

+

إنشاء نسخة من هذه الحالة.

+ +

[method:this copy]( [param:Curve source] )

+

يلصق كائن [name] آخر إلى هذه الحالة.

+ +

[method:Object toJSON]()

+

يرجع كائن JSON لتمثيل هذه الحالة.

+ +

[method:this fromJSON]( [param:Object json] )

+

يلصق البيانات من كائن JSON المعطى إلى هذه الحالة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/extras/core/CurvePath.html b/docs/api/ar/extras/core/CurvePath.html new file mode 100644 index 00000000000000..75b94419c4dbb8 --- /dev/null +++ b/docs/api/ar/extras/core/CurvePath.html @@ -0,0 +1,72 @@ + + + + + + + + + + [page:Curve] → + +

[name]

+ +

+ فئة أساسية مجردة تمتد [page:Curve]. مسار المنحنى هو مجرد + مصفوفة من المنحنيات المتصلة، ولكنها تحتفظ بواجهة برمجة التطبيقات الخاصة بالمنحنى. +

+ +

المنشئ (Constructor)

+ +

[name]()

+

لا يأخذ المُنشئ أي معلمات.

+ +

الخصائص (Properties)

+

انظر فئة [page:Curve] الأساسية للخصائص المشتركة.

+ +

[property:Array curves]

+

مصفوفة من [page:Curve Curves].

+ +

[property:Boolean autoClose]

+

ما إذا كان يجب إغلاق المسار تلقائيًا أم لا.

+ +

الوظائف (Methods)

+

انظر فئة [page:Curve] الأساسية للأساليب المشتركة.

+ +

[method:undefined add]( [param:Curve curve] )

+

إضافة منحى إلى مصفوفة [page:.curves].

+ +

[method:this closePath]()

+

يضيف [page:LineCurve lineCurve] لإغلاق المسار.

+ +

[method:Array getCurveLengths]()

+

+ الحصول على قائمة بطول القطع التراكمية للمنحنيات في مصفوفة [page:.curves]. +

+ +

[method:Array getPoints]( [param:Integer divisions] )

+

+ divisions -- عدد قطع التقسيم إلى المنحنى. الافتراضي هو + `12`.

+ + يعود بمصفوفة من النقاط التي تمثل تسلسل منحنيات. يُعرِّف معلم `division` + عدد القطع التي يتم تقسيم كل منحى إليها. ومع ذلك، لأغراض التحسين والجودة، + يعتمد دقة العينات الفعلية لكل منحى على نوعه. على سبيل المثال، بالنسبة + لـ[page:LineCurve]، يكون عدد النقاط المُستَرَجَعَ دائمًا 2 فقط. +

+ +

[method:Array getSpacedPoints]( [param:Integer divisions] )

+

+ divisions -- عدد قطع التقسيم إلى المنحنى. الافتراضي هو + `40`.

+ + يعود بمجموعة من divisions + 1 نقط متساوية المسافات باستخدام getPointAt( u ). +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/extras/core/Interpolations.html b/docs/api/ar/extras/core/Interpolations.html new file mode 100644 index 00000000000000..72dd219da6eb38 --- /dev/null +++ b/docs/api/ar/extras/core/Interpolations.html @@ -0,0 +1,52 @@ + + + + + + + + + +

[name]

+ +

+ يحتوي [name] على دوال الشريط وبيزيه المستخدمة داخليًا من قبل + فئات المنحنى الخرسانية. +

+ +

الوظائف (Methods)

+ +

[method:Float CatmullRom]( [param:Float t], [param:Float p0], [param:Float p1], [param:Float p2], [param:Float p3] )

+

+ t -- وزن التداخل.
+ p0، p1، p2، p3 -- النقاط التي تُعرِّف منحى الشريط.

+ + يُستخدم داخليًا بواسطة [page:SplineCurve SplineCurve]. +

+ +

[method:Float QuadraticBezier]( [param:Float t], [param:Float p0], [param:Float p1], [param:Float p2] )

+

+ t -- وزن التداخل.
+ p0، p1، p2 -- نقاط البدء والتحكم والنهاية التي تُعرِّف المنحى.

+ + يُستخدم داخليًا بواسطة [page:QuadraticBezierCurve3 QuadraticBezierCurve3] و + [page:QuadraticBezierCurve QuadraticBezierCurve]. +

+ +

[method:Float CubicBezier]( [param:Float t], [param:Float p0], [param:Float p1], [param:Float p2], [param:Float p3] )

+

+ t -- وزن التداخل.
+ p0، p1، p2، p3 -- نقاط البدء والتحكم (مرتين) والنهاية التي تُعرِّف + المنحى.

+ + يُستخدم داخليًا بواسطة [page:CubicBezierCurve3 CubicBezierCurve3] و + [page:CubicBezierCurve CubicBezierCurve]. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/extras/core/Path.html b/docs/api/ar/extras/core/Path.html new file mode 100644 index 00000000000000..ec199c6530c17f --- /dev/null +++ b/docs/api/ar/extras/core/Path.html @@ -0,0 +1,160 @@ + + + + + + + + + + [page:Curve] → [page:CurvePath] → + +

[name]

+ +

+ تمثيل مسار ثنائي الأبعاد. توفر الفئة طرقًا لإنشاء مسارات + ومحيطات الأشكال ثنائية الأبعاد المشابهة لواجهة برمجة التطبيقات Canvas 2D. +

+ +

مثال الكود

+ + + const path = new THREE.Path(); + + path.lineTo( 0, 0.8 ); + path.quadraticCurveTo( 0, 1, 0.2, 1 ); + path.lineTo( 1, 1 ); + + const points = path.getPoints(); + + const geometry = new THREE.BufferGeometry().setFromPoints( points ); + const material = new THREE.LineBasicMaterial( { color: 0xffffff } ); + + const line = new THREE.Line( geometry, material ); + scene.add( line ); + + +

المنشئ (Constructor)

+ +

[name]( [param:Array points] )

+

+ points -- (اختياري) مصفوفة من [page:Vector2 Vector2s].

+ + يُنشئ مسارًا من النقاط. يُعرِّف النقطة الأولى الإزاحة، ثُم + يتم إضافة النقاط المتتالية إلى مصفوفة [page:CurvePath.curves curves] كـ + [page:LineCurve LineCurves].

+ + إذا لم يتم تحديد أي نقاط، يتم إنشاء مسار فارغ ويتم تعيين + [page:.currentPoint] على المنشأ. +

+ +

الخصائص (Properties)

+

انظر فئة [page:CurvePath] الأساسية للخصائص المشتركة.

+ +

[property:Vector2 currentPoint]

+

+ الإزاحة الحالية للمسار. سيبدأ أي [page:Curve] جديد يتم إضافته + هنا. +

+ +

الوظائف (Methods)

+

انظر فئة [page:CurvePath] الأساسية للأساليب المشتركة.

+ +

[method:this absarc]( [param:Float x], [param:Float y], [param:Float radius], [param:Float startAngle], [param:Float endAngle], [param:Boolean clockwise] )

+

+ x، y -- مركز القوس المطلق.
+ radius -- نصف قطر القوس.
+ startAngle -- زاوية البدء بالراديان.
+ endAngle -- زاوية النهاية بالراديان.
+ clockwise -- تدوير القوس في اتجاه عقارب الساعة. يعتمد على `false`.

+ + يضيف [page:EllipseCurve EllipseCurve] مُوضَعًا مطلقًا إلى + المسار. +

+ +

[method:this absellipse]( [param:Float x], [param:Float y], [param:Float xRadius], [param:Float yRadius], [param:Float startAngle], [param:Float endAngle], [param:Boolean clockwise], [param:Float rotation] )

+

+ x، y -- مركز القطع الناقص المطلق.
+ xRadius -- نصف قطر القطع الناقص في المحور x.
+ yRadius -- نصف قطر القطع الناقص في المحور y.
+ startAngle -- زاوية البدء بالراديان.
+ endAngle -- زاوية النهاية بالراديان.
+ clockwise -- تدوير القطع الناقص في اتجاه عقارب الساعة. يعتمد على false.
+ rotation -- زاوية دوران القطع الناقص بالراديان، عكس اتجاه عقارب + من المحور X الموجب. اختياري، يعتمد على 0.

+ + يضيف [page:EllipseCurve EllipseCurve] مُوضَعًا مطلقًا إلى + المسار. +

+ +

[method:this arc]( [param:Float x], [param:Float y], [param:Float radius], [param:Float startAngle], [param:Float endAngle], [param:Boolean clockwise] )

+

+ x، y -- مركز القوس المُزَوَّد من آخر استدعاء.
+ radius -- نصف قطر القوس.
+ startAngle -- زاوية البدء بالراديان.
+ endAngle -- زاوية النهاية بالراديان.
+ clockwise -- تدوير القوس في اتجاه عقارب الساعة. يعتمد على `false`.

+ + يضيف [page:EllipseCurve EllipseCurve] إلى المسار، مُوضَعًا نسبيًا + إلى [page:.currentPoint]. +

+ +

[method:this bezierCurveTo]( [param:Float cp1X], [param:Float cp1Y], [param:Float cp2X], [param:Float cp2Y], [param:Float x], [param:Float y] )

+

+ يُنشئ منحى بيزيه من [page:.currentPoint] مع (cp1X، cp1Y) + و(cp2X، cp2Y) كنقاط تحكم ويُحدِّث [page:.currentPoint] إلى x + و y. +

+ +

[method:this ellipse]( [param:Float x], [param:Float y], [param:Float xRadius], [param:Float yRadius], [param:Float startAngle], [param:Float endAngle], [param:Boolean clockwise], [param:Float rotation] )

+

+ x، y -- مركز القطع الناقص المُزَوَّد من آخر استدعاء.
+ xRadius -- نصف قطر القطع الناقص في المحور x.
+ yRadius -- نصف قطر القطع الناقص في المحور y.
+ startAngle -- زاوية البدء بالراديان.
+ endAngle -- زاوية النهاية بالراديان.
+ clockwise -- تدوير القطع الناقص في اتجاه عقارب الساعة. يعتمد على `false`.
+ rotation -- زاوية دوران القطع الناقص بالراديان، عكس اتجاه عقارب + من المحور X الموجب. اختياري، يعتمد على `0`.

+ + يضيف [page:EllipseCurve EllipseCurve] إلى المسار، مُوضَعًا نسبيًا + إلى [page:.currentPoint]. +

+ +

[method:this lineTo]( [param:Float x], [param:Float y] )

+

+ يُربِط [page:LineCurve] من [page:.currentPoint] إلى x، y على + المسار. +

+ +

[method:this moveTo]( [param:Float x], [param:Float y] )

+

تحريك [page:.currentPoint] إلى x، y.

+ +

[method:this quadraticCurveTo]( [param:Float cpX], [param:Float cpY], [param:Float x], [param:Float y] )

+

+ يُنشئ منحى رباعي من [page:.currentPoint] مع cpX و cpY كـ + نقطة تحكم ويُحدِّث [page:.currentPoint] إلى x و y. +

+ +

[method:this setFromPoints]( [param:Array vector2s] )

+

+ points -- مصفوفة من [page:Vector2 Vector2s].

+ + يتم إضافة النقاط إلى مصفوفة [page:CurvePath.curves curves] كـ + [page:LineCurve LineCurves]. +

+ +

[method:this splineThru] ( [param:Array points] )

+

+ points - مصفوفة من [page:Vector2 Vector2s]

+ + يُربِط منحى جديدًا من نوع SplineCurve على المسار. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/extras/core/Shape.html b/docs/api/ar/extras/core/Shape.html new file mode 100644 index 00000000000000..00da599de05a1c --- /dev/null +++ b/docs/api/ar/extras/core/Shape.html @@ -0,0 +1,104 @@ + + + + + + + + + + [page:Curve] → [page:CurvePath] → [page:Path] → + +

[name]

+ +

+ يعرف شكل طائرة 2d تعسفي باستخدام مسارات مع ثقوبات اختيارية. يمكن استخدامه مع [page:ExtrudeGeometry]، [page:ShapeGeometry]، للحصول على نقاط، أو للحصول على وجوه مثلثة. +

+ +

مثال الكود

+ + + const heartShape = new THREE.Shape(); + + heartShape.moveTo( 25, 25 ); + heartShape.bezierCurveTo( 25, 25, 20, 0, 0, 0 ); + heartShape.bezierCurveTo( - 30, 0, - 30, 35, - 30, 35 ); + heartShape.bezierCurveTo( - 30, 55, - 10, 77, 25, 95 ); + heartShape.bezierCurveTo( 60, 77, 80, 55, 80, 35 ); + heartShape.bezierCurveTo( 80, 35, 80, 0, 50, 0 ); + heartShape.bezierCurveTo( 35, 0, 25, 25, 25, 25 ); + + const extrudeSettings = { + depth: 8, + bevelEnabled: true, + bevelSegments: 2, + steps: 2, + bevelSize: 1, + bevelThickness: 1 + }; + + const geometry = new THREE.ExtrudeGeometry( heartShape, extrudeSettings ); + + const mesh = new THREE.Mesh( geometry, new THREE.MeshPhongMaterial() ); + + +

أمثلة (Examples)

+ +

+ [example:webgl_geometry_shapes geometry / shapes ]
+ [example:webgl_geometry_extrude_shapes geometry / extrude / shapes ] +

+ +

المنشئ (Constructor)

+ +

[name]( [param:Array points] )

+

+ points -- (اختياري) مصفوفة من [page:Vector2 Vector2s].

+ + ينشئ شكل من النقاط. يحدد النقطة الأولى الإزاحة، ثم يتم إضافة النقاط المتعاقبة إلى مصفوفة [page:CurvePath.curves curves] كـ + [page:LineCurve LineCurves].

+ + إذا لم يتم تحديد أي نقاط، يتم إنشاء شكل فارغ ويتم تعيين + [page:.currentPoint] على المنشأ. +

+ +

الخصائص (Properties)

+

انظر فئة [page:Path] الأساسية للخصائص المشتركة.

+ +

[property:String uuid]

+

+ [link:http://en.wikipedia.org/wiki/Universally_unique_identifier UUID] من + هذه الحالة. يتم تعيين هذا تلقائيًا، لذلك لا يجب تحريره. +

+ +

[property:Array holes]

+

مصفوفة من [page:Path paths] التي تحدد الثقوب في الشكل.

+ +

الطرق (Methods)

+

انظر فئة [page:Path] الأساسية للطرق المشتركة.

+ +

[method:Array extractPoints]( [param:Integer divisions] )

+

+ divisions -- دقة النتيجة.

+ + استدعاء [page:Curve.getPoints getPoints] على الشكل ومصفوفة [page:.holes] + ، وإرجاع كائن من الشكل: + { shape holes } + حيث shape و holes هما مصفوفات من [page:Vector2 Vector2s]. +

+ +

[method:Array getPointsHoles]( [param:Integer divisions] )

+

+ divisions -- دقة النتيجة.

+ + احصل على مصفوفة من [page:Vector2 Vector2s] التي تمثل الثقوب في + الشكل. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/extras/core/ShapePath.html b/docs/api/ar/extras/core/ShapePath.html new file mode 100644 index 00000000000000..7972a713a2be01 --- /dev/null +++ b/docs/api/ar/extras/core/ShapePath.html @@ -0,0 +1,93 @@ + + + + + + + + + +

[name]

+ +

+ يتم استخدام هذه الفئة لتحويل سلسلة من الأشكال إلى مصفوفة من + [page:Path]s، على سبيل المثال شكل SVG إلى مسار. +

+ +

المنشئ (Constructor)

+ +

[name]( )

+

+ ينشئ ShapePath جديد. على عكس [page:Path]، لا تتم مرور أي نقاط كما + يتم تصميم ShapePath ليتم إنشاؤه بعد الإنشاء. +

+ +

الخصائص (Properties)

+ +

[property:Array subPaths]

+

مصفوفة من [page:Path]s.

+ +

[property:Array currentPath]

+

[page:Path] الحالي الذي يتم إنشاؤه.

+ +

[property:Color color]

+

[page:Color] من الشكل، بشكل افتراضي مضبوط على الأبيض (0xffffff).

+ +

الطرق (Methods)

+ +

[method:this moveTo]( [param:Float x], [param:Float y] )

+

+ يبدأ [page:Path] جديد ويستدعي [page:Path.moveTo]( x, y ) على ذلك + [page:Path]. كما يشير [page:ShapePath.currentPath currentPath] إلى ذلك + [page:Path]. +

+ +

[method:this lineTo]( [param:Float x], [param:Float y] )

+

+ هذا ينشئ خطًا من إزاحة [page:ShapePath.currentPath currentPath] + إلى X و Y ويحدث الإزاحة إلى X و Y. +

+ +

+ [method:this quadraticCurveTo]( [param:Float cpX], [param:Float cpY], [param:Float x], [param:Float y] ) +

+

+ هذا ينشئ منحنى ثنائي الحدود من إزاحة [page:ShapePath.currentPath currentPath] + إلى x و y مع cpX و cpY كنقطة تحكم و + يحدث إزاحة [page:ShapePath.currentPath currentPath] + إلى x و y. +

+ + +

+ [method:this bezierCurveTo]( [param:Float cp1X], [param:Float cp1Y], [param:Float cp2X], [param:Float cp2Y], [param:Float x], [param:Float y] ) +

+

+ هذا ينشئ منحنى بيزيه من إزاحة [page:ShapePath.currentPath currentPath] + إلى x و y مع cp1X و cp1Y و cp2X و cp2Y كنقاط تحكم + ويحدث إزاحة [page:ShapePath.currentPath currentPath] + إلى x و y. +

+ +

[method:this splineThru] ( [param:Array points] )

+

points - مصفوفة من [page:Vector2]s

+

+ يربط [page:SplineCurve] جديد على [page:ShapePath.currentPath currentPath]. +

+ +

[method:Array toShapes]( [param:Boolean isCCW] )

+

isCCW -- يغير كيفية إنشاء الأشكال الصلبة والثقوب

+

+ يحول مصفوفة [page:ShapePath.subPaths subPaths] إلى مصفوفة من + الأشكال. بشكل افتراضي، يتم تعريف الأشكال الصلبة باتجاه عقارب الساعة (CW) ويتم تعريف الثقوب بعكس اتجاه عقارب الساعة (CCW). إذا تم تعيين isCCW على true، فإن هذه + مقلوب. +
+

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/extras/core/ShapePath.js src/extras/core/ShapePath.js] +

+ + diff --git a/docs/api/ar/extras/curves/ArcCurve.html b/docs/api/ar/extras/curves/ArcCurve.html new file mode 100644 index 00000000000000..9ee7d0ab7704a7 --- /dev/null +++ b/docs/api/ar/extras/curves/ArcCurve.html @@ -0,0 +1,24 @@ + + + + + + + + + + [page:Curve] → [page:EllipseCurve] → + +

[name]

+ +

اسم مستعار لـ [page:EllipseCurve].

+ +

الخصائص (Properties)

+

راجع فئة [page:EllipseCurve] للحصول على الخصائص المشتركة.

+ +

المصدر (Source)

+

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/extras/curves/CatmullRomCurve3.html b/docs/api/ar/extras/curves/CatmullRomCurve3.html new file mode 100644 index 00000000000000..acb5a3bf1a9de6 --- /dev/null +++ b/docs/api/ar/extras/curves/CatmullRomCurve3.html @@ -0,0 +1,84 @@ + + + + + + + + + + [page:Curve] → + +

[name]

+ +

+ قم بإنشاء منحنى شريط 3d ناعم من سلسلة من النقاط باستخدام + [link:https://en.wikipedia.org/wiki/Centripetal_Catmull-Rom_spline Catmull-Rom] خوارزمية. +

+ +

مثال الكود

+ + + // إنشاء حلقة مغلقة متموجة + const curve = new THREE.CatmullRomCurve3( [ + new THREE.Vector3( -10, 0, 10 ), + new THREE.Vector3( -5, 5, 5 ), + new THREE.Vector3( 0, 0, 0 ), + new THREE.Vector3( 5, -5, 5 ), + new THREE.Vector3( 10, 0, 10 ) + ] ); + + const points = curve.getPoints( 50 ); + const geometry = new THREE.BufferGeometry().setFromPoints( points ); + + const material = new THREE.LineBasicMaterial( { color: 0xff0000 } ); + + // إنشاء الكائن النهائي لإضافته إلى المشهد + const curveObject = new THREE.Line( geometry, material ); + + +

أمثلة (Examples)

+ +

+ [example:webgl_geometry_extrude_splines WebGL / geometry / extrude / splines] +

+ +

المنشئ (Constructor)

+ +

+ [name]( [param:Array points], [param:Boolean closed], [param:String curveType], [param:Float tension] ) +

+

+ points – مصفوفة من نقاط [page:Vector3]
+ closed – ما إذا كان المنحنى مغلقًا. الافتراضي هو `false`.
+ curveType – نوع المنحنى. الافتراضي هو `centripetal`.
+ tension – توتر المنحنى. الافتراضي هو `0.5`. +

+ +

الخصائص (Properties)

+

راجع فئة [page:Curve] الأساسية للحصول على الخصائص المشتركة.

+ +

[property:Array points]

+

+ مصفوفة من نقاط [page:Vector3] التي تحدد المنحنى. يحتاج على الأقل إلى دخولين. +

+ +

[property:Boolean closed]

+

سيعود المنحنى إلى نفسه عندما يكون هذا صحيحًا.

+ +

[property:String curveType]

+

القيم الممكنة هي `centripetal` و `chordal` و `catmullrom`.

+ +

[property:Float tension]

+

عندما يكون [page:.curveType] هو `catmullrom` ، يعرف توتر catmullrom.

+ +

الوظائف (Methods)

+

راجع فئة [page:Curve] الأساسية للحصول على طرق مشتركة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/extras/curves/CubicBezierCurve.html b/docs/api/ar/extras/curves/CubicBezierCurve.html new file mode 100644 index 00000000000000..8f1f089cf9eca5 --- /dev/null +++ b/docs/api/ar/extras/curves/CubicBezierCurve.html @@ -0,0 +1,79 @@ + + + + + + + + + + [page:Curve] → + +

[name]

+ +

+ قم بإنشاء منحنى بيزيه مكعب 2d ناعم + cubic bezier curve ، محدد بنقطة البداية ونقطة النهاية ونقطتي تحكم. +

+ +

مثال الكود

+ + + const curve = new THREE.CubicBezierCurve( + new THREE.Vector2( -10, 0 ), + new THREE.Vector2( -5, 15 ), + new THREE.Vector2( 20, 15 ), + new THREE.Vector2( 10, 0 ) + ); + + const points = curve.getPoints( 50 ); + const geometry = new THREE.BufferGeometry().setFromPoints( points ); + + const material = new THREE.LineBasicMaterial( { color: 0xff0000 } ); + + // إنشاء الكائن النهائي لإضافته إلى المشهد + const curveObject = new THREE.Line( geometry, material ); + + +

المنشئ (Constructor)

+ +

+ [name] ( [param:Vector2 v0], [param:Vector2 v1], [param:Vector2 v2], + [param:Vector2 v3] ) +

+

+ [page:Vector2 v0] – نقطة البداية.
+ [page:Vector2 v1] – نقطة التحكم الأولى.
+ [page:Vector2 v2] – نقطة التحكم الثانية.
+ [page:Vector2 v3] – نقطة النهاية. +

+ +

الخصائص (Properties)

+

راجع فئة [page:Curve] الأساسية للحصول على الخصائص المشتركة.

+ +

[property:Vector2 v0]

+

نقطة البداية.

+ +

[property:Vector2 v1]

+

نقطة التحكم الأولى.

+ +

[property:Vector2 v2]

+

نقطة التحكم الثانية.

+ +

[property:Vector2 v3]

+

نقطة النهاية.

+ +

الوظائف (Methods)

+

راجع فئة [page:Curve] الأساسية للحصول على طرق مشتركة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/extras/curves/CubicBezierCurve3.html b/docs/api/ar/extras/curves/CubicBezierCurve3.html new file mode 100644 index 00000000000000..a0c3e5d53ab48c --- /dev/null +++ b/docs/api/ar/extras/curves/CubicBezierCurve3.html @@ -0,0 +1,80 @@ + + + + + + + + + + [page:Curve] → + +

[name]

+ +

+ قم بإنشاء منحنى بيزيه مكعب 3d ناعم + cubic bezier curve ، محدد بنقطة البداية ونقطة النهاية ونقطتي تحكم. +

+ +

مثال الكود

+ + + const curve = new THREE.CubicBezierCurve3( + new THREE.Vector3( -10, 0, 0 ), + new THREE.Vector3( -5, 15, 0 ), + new THREE.Vector3( 20, 15, 0 ), + new THREE.Vector3( 10, 0, 0 ) + ); + + const points = curve.getPoints( 50 ); + const geometry = new THREE.BufferGeometry().setFromPoints( points ); + + const material = new THREE.LineBasicMaterial( { color: 0xff0000 } ); + + // إنشاء الكائن النهائي لإضافته إلى المشهد + const curveObject = new THREE.Line( geometry, material ); + + + +

المنشئ (Constructor)

+ +

+ [name]( [param:Vector3 v0], [param:Vector3 v1], [param:Vector3 v2], + [param:Vector3 v3] ) +

+

+ [page:Vector3 v0] – نقطة البداية.
+ [page:Vector3 v1] – نقطة التحكم الأولى.
+ [page:Vector3 v2] – نقطة التحكم الثانية.
+ [page:Vector3 v3] – نقطة النهاية. +

+ +

الخصائص (Properties)

+

راجع فئة [page:Curve] الأساسية للحصول على الخصائص المشتركة.

+ +

[property:Vector3 v0]

+

نقطة البداية.

+ +

[property:Vector3 v1]

+

نقطة التحكم الأولى.

+ +

[property:Vector3 v2]

+

نقطة التحكم الثانية.

+ +

[property:Vector3 v3]

+

نقطة النهاية.

+ +

الوظائف (Methods)

+

راجع فئة [page:Curve] الأساسية للحصول على طرق مشتركة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/extras/curves/EllipseCurve.html b/docs/api/ar/extras/curves/EllipseCurve.html new file mode 100644 index 00000000000000..c89f158dce1ab1 --- /dev/null +++ b/docs/api/ar/extras/curves/EllipseCurve.html @@ -0,0 +1,104 @@ + + + + + + + + + + [page:Curve] → + +

[name]

+ +

+ ينشئ منحنى 2d على شكل بيضاوي. يؤدي تعيين [page:Number xRadius] مساويًا لـ [page:Number yRadius] إلى الحصول على دائرة. +

+ +

مثال الكود

+ + + const curve = new THREE.EllipseCurve( + 0, 0, // ax, aY + 10, 10, // xRadius, yRadius + 0, 2 * Math.PI, // aStartAngle, aEndAngle + false, // aClockwise + 0 // aRotation + ); + + const points = curve.getPoints( 50 ); + const geometry = new THREE.BufferGeometry().setFromPoints( points ); + + const material = new THREE.LineBasicMaterial( { color: 0xff0000 } ); + + // إنشاء الكائن النهائي لإضافته إلى المشهد + const ellipse = new THREE.Line( geometry, material ); + + +

المنشئ (Constructor)

+ +

+ [name]( [param:Float aX], [param:Float aY], [param:Float xRadius], + [param:Float yRadius], [param:Radians aStartAngle], [param:Radians aEndAngle], [param:Boolean aClockwise], [param:Radians aRotation] ) +

+

+ [page:Float aX] – مركز X للبيضاوي. الافتراضي هو `0`.
+ [page:Float aY] – مركز Y للبيضاوي. الافتراضي هو `0`.
+ [page:Float xRadius] – نصف قطر البيضاوي في اتجاه x. + الافتراضي هو `1`.
+ [page:Float yRadius] – نصف قطر البيضاوي في اتجاه y. + الافتراضي هو `1`.
+ [page:Radians aStartAngle] – زاوية بدء المنحنى بالراديان + بدءًا من المحور X الموجب. الافتراضي هو `0`.
+ [page:Radians aEndAngle] – زاوية نهاية المنحنى بالراديان بدءًا + من المحور X الموجب. الافتراضي هو `2 x Math.PI`.
+ [page:Boolean aClockwise] – ما إذا كان يتم رسم البيضاوي في اتجاه عقارب الساعة. + الافتراضي هو `false`.
+ [page:Radians aRotation] – زاوية دوران البيضاوي بالراديان ، + عكس اتجاه عقارب الساعة من المحور X الموجب (اختياري). الافتراضي هو `0`.

+

+ +

الخصائص (Properties)

+

راجع فئة [page:Curve] الأساسية للحصول على الخصائص المشتركة.

+ +

[property:Float aX]

+

مركز X للبيضاوي.

+ +

[property:Float aY]

+

مركز Y للبيضاوي.

+ +

[property:Radians xRadius]

+

نصف قطر البيضاوي في اتجاه x.

+ +

[property:Radians yRadius]

+

نصف قطر البيضاوي في اتجاه y.

+ +

[property:Float aStartAngle]

+

+ زاوية بدء المنحنى بالراديان بدءًا من المنتصف الأمام. +

+ +

[property:Float aEndAngle]

+

+ زاوية نهاية المنحنى بالراديان بدءًا من المنتصف الأمام. +

+ +

[property:Boolean aClockwise]

+

ما إذا كان يتم رسم البيضاوي في اتجاه عقارب الساعة.

+ +

[property:Float aRotation]

+

+ زاوية دوران البيضاوي بالراديان ، عكس اتجاه عقارب الساعة من + المحور X الموجب (aRotation). الافتراضية هى `0`. +

+ +

الوظائف (Methods)

+

راجع فئة [page:Curve] الأساسية للحصول على طرق مشتركة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/extras/curves/LineCurve.html b/docs/api/ar/extras/curves/LineCurve.html new file mode 100644 index 00000000000000..f6f4beeb37d904 --- /dev/null +++ b/docs/api/ar/extras/curves/LineCurve.html @@ -0,0 +1,42 @@ + + + + + + + + + + [page:Curve] → + +

[name]

+ +

منحنى يمثل قطعة خطية 2d.

+ +

المنشئ (Constructor)

+ +

[name]( [param:Vector2 v1], [param:Vector2 v2] )

+

+ [page:Vector2 v1] – نقطة البداية.
+ [page:Vector2 v2] - نقطة النهاية. +

+ +

الخصائص (Properties)

+

راجع فئة [page:Curve] الأساسية للحصول على الخصائص المشتركة.

+ +

[property:Vector2 v1]

+

نقطة البداية.

+ +

[property:Vector2 v2]

+

نقطة النهاية

+ +

الوظائف (Methods)

+

راجع فئة [page:Curve] الأساسية للحصول على طرق مشتركة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/extras/curves/LineCurve3.html b/docs/api/ar/extras/curves/LineCurve3.html new file mode 100644 index 00000000000000..fe2a14f34b7769 --- /dev/null +++ b/docs/api/ar/extras/curves/LineCurve3.html @@ -0,0 +1,42 @@ + + + + + + + + + + [page:Curve] → + +

[name]

+ +

منحنى يمثل قطعة خطية 3d.

+ +

المنشئ (Constructor)

+ +

[name]( [param:Vector3 v1], [param:Vector3 v2] )

+

+ [page:Vector3 v1] – نقطة البداية.
+ [page:Vector3 v2] - نقطة النهاية. +

+ +

الخصائص (Properties)

+

راجع فئة [page:Curve] الأساسية للحصول على الخصائص المشتركة.

+ +

[property:Vector3 v1]

+

نقطة البداية.

+ +

[property:Vector3 v2]

+

نقطة النهاية.

+ +

الوظائف (Methods)

+

راجع فئة [page:Curve] الأساسية للحصول على طرق مشتركة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/extras/curves/QuadraticBezierCurve.html b/docs/api/ar/extras/curves/QuadraticBezierCurve.html new file mode 100644 index 00000000000000..b5bf0bee30b107 --- /dev/null +++ b/docs/api/ar/extras/curves/QuadraticBezierCurve.html @@ -0,0 +1,73 @@ + + + + + + + + + + [page:Curve] → + +

[name]

+ +

+ قم بإنشاء منحنى بيزيه ثنائي الأبعاد ناعم + رباعي، محدد بنقطة البداية ونقطة النهاية ونقطة تحكم واحدة. +

+ +

مثال الكود

+ + + const curve = new THREE.QuadraticBezierCurve( + new THREE.Vector2( -10, 0 ), + new THREE.Vector2( 20, 15 ), + new THREE.Vector2( 10, 0 ) + ); + + const points = curve.getPoints( 50 ); + const geometry = new THREE.BufferGeometry().setFromPoints( points ); + + const material = new THREE.LineBasicMaterial( { color: 0xff0000 } ); + + // إنشاء الكائن النهائي لإضافته إلى المشهد + const curveObject = new THREE.Line( geometry, material ); + + +

المنشئ (Constructor)

+ +

+ [name]( [param:Vector2 v0], [param:Vector2 v1], [param:Vector2 v2] ) +

+

+ [page:Vector2 v0] – نقطة البداية.
+ [page:Vector2 v1] – نقطة التحكم.
+ [page:Vector2 v2] – نقطة النهاية. +

+ +

الخصائص (Properties)

+

انظر إلى قاعدة [page:Curve] للخصائص المشتركة.

+ +

[property:Vector2 v0]

+

نقطة البداية.

+ +

[property:Vector2 v1]

+

نقطة التحكم.

+ +

[property:Vector2 v2]

+

نقطة النهاية.

+ +

الخصائص (Properties)

+

انظر إلى الفئة الأساسية [page:Curve] للحصول على الطرق المشتركة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/extras/curves/QuadraticBezierCurve3.html b/docs/api/ar/extras/curves/QuadraticBezierCurve3.html new file mode 100644 index 00000000000000..2c0b282109b376 --- /dev/null +++ b/docs/api/ar/extras/curves/QuadraticBezierCurve3.html @@ -0,0 +1,73 @@ + + + + + + + + + + [page:Curve] → + +

[name]

+ +

+ قم بإنشاء منحنى بيزيه ثلاثي الأبعاد ناعم + رباعي، محدد بنقطة البداية ونقطة النهاية ونقطة تحكم واحدة. +

+ +

مثال الكود

+ + + const curve = new THREE.QuadraticBezierCurve3( + new THREE.Vector3( -10, 0, 0 ), + new THREE.Vector3( 20, 15, 0 ), + new THREE.Vector3( 10, 0, 0 ) + ); + + const points = curve.getPoints( 50 ); + const geometry = new THREE.BufferGeometry().setFromPoints( points ); + + const material = new THREE.LineBasicMaterial( { color: 0xff0000 } ); + + // إنشاء الكائن النهائي لإضافته إلى المشهد + const curveObject = new THREE.Line( geometry, material ); + + +

المنشئ (Constructor)

+ +

+ [name]( [param:Vector3 v0], [param:Vector3 v1], [param:Vector3 v2] ) +

+

+ [page:Vector3 v0] – نقطة البداية
+ [page:Vector3 v1] – نقطة التحكم الوسطى
+ [page:Vector3 v2] – نقطة النهاية
+

+ +

الخصائص (Properties)

+

انظر إلى قاعدة [page:Curve] للخصائص المشتركة.

+ +

[property:Vector3 v0]

+

نقطة البداية.

+ +

[property:Vector3 v1]

+

نقطة التحكم.

+ +

[property:Vector3 v2]

+

نقطة النهاية.

+ +

الوظائف (Methods)

+

انظر إلى قاعدة [page:Curve] للأساليب المشتركة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/extras/curves/SplineCurve.html b/docs/api/ar/extras/curves/SplineCurve.html new file mode 100644 index 00000000000000..dfbc0b35ddfb89 --- /dev/null +++ b/docs/api/ar/extras/curves/SplineCurve.html @@ -0,0 +1,60 @@ + + + + + + + + + + [page:Curve] → + +

[name]

+ +

+ قم بإنشاء منحنى سبلاين ثنائي الأبعاد ناعم من سلسلة من النقاط. يستخدم هذا داخليًا + [page:Interpolations.CatmullRom] لإنشاء المنحنى. +

+ +

مثال الكود

+ + + // إنشاء موجة شبيهة بالجيب + const curve = new THREE.SplineCurve( [ + new THREE.Vector2( -10, 0 ), + new THREE.Vector2( -5, 5 ), + new THREE.Vector2( 0, 0 ), + new THREE.Vector2( 5, -5 ), + new THREE.Vector2( 10, 0 ) + ] ); + + const points = curve.getPoints( 50 ); + const geometry = new THREE.BufferGeometry().setFromPoints( points ); + + const material = new THREE.LineBasicMaterial( { color: 0xff0000 } ); + + // إنشاء الكائن النهائي لإضافته إلى المشهد + const splineObject = new THREE.Line( geometry, material ); + + +

المنشئ (Constructor)

+ +

[name]( [param:Array points] )

+

points – مصفوفة من نقاط [page:Vector2] التي تحدد المنحنى.

+ +

الخصائص (Properties)

+

انظر إلى قاعدة [page:Curve] للخصائص المشتركة.

+ +

[property:Array points]

+

مصفوفة من نقاط [page:Vector2] التي تحدد المنحنى.

+ +

الوظائف (Methods)

+

انظر إلى قاعدة [page:Curve] للأساليب المشتركة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/geometries/BoxGeometry.html b/docs/api/ar/geometries/BoxGeometry.html new file mode 100644 index 00000000000000..484592bde9dbef --- /dev/null +++ b/docs/api/ar/geometries/BoxGeometry.html @@ -0,0 +1,99 @@ + + + + + + + + + + [page:BufferGeometry] → + +

[name]

+ +

+ [name] هي فئة هندسية لمكعب مستطيل مع 'العرض' المحدد ، + 'الارتفاع' و 'العمق'. عند الإنشاء ، يتم توسيط المكعب على الأصل ، + مع كل حافة موازية لأحد المحاور. +

+ + + + + +

مثال الكود

+ + +const geometry = new THREE.BoxGeometry( 1, 1, 1 ); +const material = new THREE.MeshBasicMaterial( {color: 0x00ff00} ); +const cube = new THREE.Mesh( geometry, material ); +scene.add( cube ); + + +

المنشئ (Constructor)

+ +

+ [name]([param:Float width], [param:Float height], [param:Float depth], + [param:Integer widthSegments], [param:Integer heightSegments], + [param:Integer depthSegments]) +

+

+ width — العرض؛ أي طول الحواف الموازية لمحور X. + اختياري؛ الافتراضي هو 1. +
+ height — الارتفاع؛ أي طول الحواف الموازية لمحور Y. + اختياري؛ الافتراضي هو 1. +
+ depth — العمق؛ أي طول الحواف الموازية لمحور Z. + اختياري؛ الافتراضي هو 1. +
+ widthSegments — عدد الوجوه المستطيلة المقسمة على طول عرض + الجانب. اختياري؛ الافتراضي هو 1. +
+ heightSegments — عدد الوجوه المستطيلة المقسمة على طول ارتفاع + الجانب. اختياري؛ الافتراضي هو 1. +
+ depthSegments — عدد الوجوه المستطيلة المقسمة على طول عمق + الجانب. اختياري؛ الافتراضي هو 1. +
+

+ +

الخصائص (Properties)

+

انظر فئة [page:BufferGeometry] الأساسية للخصائص المشتركة.

+ +

[property:Object parameters]

+

+ كائن يحتوي على خاصية لكل من معلمات المنشئ. أي تعديل بعد التجسيد لا يغير + الهندسة. +

+ +

الطرق (Methods)

+

انظر فئة [page:BufferGeometry] الأساسية للطرق المشتركة.

+ + +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/geometries/CapsuleGeometry.html b/docs/api/ar/geometries/CapsuleGeometry.html new file mode 100644 index 00000000000000..487c0d62672813 --- /dev/null +++ b/docs/api/ar/geometries/CapsuleGeometry.html @@ -0,0 +1,81 @@ + + + + + + + + + + [page:BufferGeometry] → [page:LatheGeometry] → + +

[name]

+ +

+ [name] هي فئة هندسة لكبسولة بأشعة وارتفاع محدد. يتم بناؤها باستخدام مخرطة. +

+ + + + + +

مثال الكود

+ + + const geometry = new THREE.CapsuleGeometry( 1, 1, 4, 8 ); + const material = new THREE.MeshBasicMaterial( {color: 0x00ff00} ); + const capsule = new THREE.Mesh( geometry, material ); scene.add( capsule ); + + +

المنشئ (Constructor)

+ +

+ [name]([param:Float radius], [param:Float height], [param:Integer capSegments], [param:Integer radialSegments], [param:Integer heightSegments]) +

+

+ radius — نصف قطر الكبسولة. اختياري؛ الافتراضي هو 1.
+ height — ارتفاع القسم الأوسط. اختياري؛ الافتراضي هو 1.
+ capSegments — عدد قطاعات المنحنى المستخدمة لبناء الأغطية. اختياري؛ + الافتراضي هو 4.
+ radialSegments — عدد الوجوه المقسمة حول محيط + الكبسولة. اختياري؛ الافتراضي هو 8.
+ heightSegments — عدد الوجوه المقسمة على الارتفاع + الكبسولة. اختياري؛ الافتراضي هو 1.
+

+ +

الخصائص (Properties)

+

انظر فئة [page:BufferGeometry] الأساسية للخصائص المشتركة.

+ +

[property:Object parameters]

+

+ كائن يحتوي على خاصية لكل من معلمات المنشئ. أي تعديل بعد التجسيد لا يغير + الهندسة. +

+ +

الطرق (Methods)

+

انظر فئة [page:BufferGeometry] الأساسية للطرق المشتركة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/geometries/CircleGeometry.html b/docs/api/ar/geometries/CircleGeometry.html new file mode 100644 index 00000000000000..3b8e2c48b489e5 --- /dev/null +++ b/docs/api/ar/geometries/CircleGeometry.html @@ -0,0 +1,78 @@ + + + + + + + + + + [page:BufferGeometry] → + +

[name]

+ +

+ [name] هو شكل بسيط من الهندسة الإقليدية. يتم بناؤه من عدد من الشرائح المثلثية التي تتجه حول نقطة مركزية وتمتد إلى أبعد ما يمكن من نصف قطر معين. يتم بناؤه عكس اتجاه عقارب الساعة من زاوية بدء وزاوية مركزية معينة. يمكن أيضًا استخدامه لإنشاء مضلعات منتظمة، حيث يحدد عدد الشرائح عدد الجوانب. +

+ + + + + +

مثال الكود

+ + +const geometry = new THREE.CircleGeometry( 5, 32 ); +const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); +const circle = new THREE.Mesh( geometry, material ); scene.add( circle ); + + +

المنشئ (Constructor)

+ +

+ [name]([param:Float radius], [param:Integer segments], [param:Float thetaStart], [param:Float thetaLength]) +

+

+ radius — نصف قطر الدائرة، الافتراضي = 1.
+ segments — عدد الشرائح (المثلثات)، الحد الأدنى = 3، الافتراضي = 32.
+ thetaStart — زاوية البدء للشريحة الأولى، الافتراضي = 0 (موقع الساعة + الثالثة).
+ thetaLength — الزاوية المركزية، وغالبًا ما تسمى ثيتا، للقطاع الدائري. الافتراضي هو 2 * Pi، مما يجعله دائرة كاملة. +

+ +

الخصائص (Properties)

+

انظر فئة [page:BufferGeometry] الأساسية للخصائص المشتركة.

+ +

[property:Object parameters]

+

+ كائن يحتوي على خاصية لكل من معلمات المنشئ. أي تعديل بعد التجسيد لا يغير + الهندسة. +

+ +

الطرق (Methods)

+

انظر فئة [page:BufferGeometry] الأساسية للطرق المشتركة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/geometries/ConeGeometry.html b/docs/api/ar/geometries/ConeGeometry.html new file mode 100644 index 00000000000000..e9ec6c422f0e8b --- /dev/null +++ b/docs/api/ar/geometries/ConeGeometry.html @@ -0,0 +1,79 @@ + + + + + + + + + + [page:BufferGeometry] → [page:CylinderGeometry] → + +

[name]

+ +

فئة لإنشاء هندسة المخروط.

+ + + + + +

مثال الكود + + + const geometry = new THREE.ConeGeometry( 5, 20, 32 ); + const material = new THREE.MeshBasicMaterial( {color: 0xffff00} ); + const cone = new THREE.Mesh(geometry, material ); scene.add( cone ); + + +

المنشئ (Constructor)

+ +

+ [name]([param:Float radius], [param:Float height], [param:Integer radialSegments], [param:Integer heightSegments], [param:Boolean openEnded], [param:Float thetaStart], [param:Float thetaLength]) +

+

+ radius — نصف قطر قاعدة المخروط. الافتراضي هو 1.
+ height — ارتفاع المخروط. الافتراضي هو 1.
+ radialSegments — عدد الوجوه المقسمة حول محيط + المخروط. الافتراضي هو 32
+ heightSegments — عدد صفوف الوجوه على طول ارتفاع المخروط. + الافتراضي هو 1.
+ openEnded — مؤشر منطقي يشير إلى ما إذا كانت قاعدة المخروط مفتوحة أو + مغلقة. الافتراضي هو false، أي مغلق.
+ thetaStart — زاوية البدء للشريحة الأولى، الافتراضي = 0 (موقع الساعة + الثالثة).
+ thetaLength — الزاوية المركزية، وغالبًا ما تسمى ثيتا، للقطاع الدائري. الافتراضي هو 2 * Pi، مما يجعله مخروطًا كاملاً. +

+ +

الخصائص (Properties)

+

انظر فئة [page:CylinderGeometry] الأساسية للخصائص المشتركة.

+ +

[property:Object parameters]

+

+ كائن يحتوي على خاصية لكل من معلمات المنشئ. أي تعديل بعد التجسيد لا يغير + الهندسة. +

+ +

الطرق (Methods)

+

انظر فئة [page:CylinderGeometry] الأساسية للطرق المشتركة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/geometries/CylinderGeometry.html b/docs/api/ar/geometries/CylinderGeometry.html new file mode 100644 index 00000000000000..ecf1cc8098dc0d --- /dev/null +++ b/docs/api/ar/geometries/CylinderGeometry.html @@ -0,0 +1,84 @@ + + + + + + + + + + [page:BufferGeometry] → + +

[name]

+ +

فئة لإنشاء هندسة الأسطوانة.

+ + + + + +

مثال الكود

+ + + const geometry = new THREE.CylinderGeometry( 5, 5, 20, 32 ); + const material = new THREE.MeshBasicMaterial( {color: 0xffff00} ); + const cylinder = new THREE.Mesh( geometry, material ); scene.add( cylinder ); + + +

المنشئ (Constructor)

+ +

+ [name]([param:Float radiusTop], [param:Float radiusBottom], [param:Float height], [param:Integer radialSegments], [param:Integer heightSegments], + [param:Boolean openEnded], [param:Float thetaStart], [param:Float thetaLength]) +

+

+ radiusTop — نصف قطر الأسطوانة في الأعلى. الافتراضي هو 1.
+ radiusBottom — نصف قطر الأسطوانة في الأسفل. الافتراضي هو 1.
+ height — ارتفاع الأسطوانة. الافتراضي هو 1.
+ radialSegments — عدد الوجوه المقسمة حول محيط + الأسطوانة. الافتراضي هو 32
+ heightSegments — عدد صفوف الوجوه على طول ارتفاع الأسطوانة. + الافتراضي هو 1.
+ openEnded — مؤشر منطقي يشير إلى ما إذا كانت طرفي الأسطوانة مفتوحة + أو مغلقة. الافتراضي هو false، أي مغلق.
+ thetaStart — زاوية البدء للشريحة الأولى، الافتراضي = 0 (موقع الساعة + الثالثة).
+ thetaLength — الزاوية المركزية، وغالبًا ما تسمى ثيتا، للقطاع الدائري. الافتراضي هو 2 * Pi، مما يجعله أسطوانة كاملة. +

+ +

الخصائص (Properties)

+

انظر فئة [page:BufferGeometry] الأساسية للخصائص المشتركة.

+ +

[property:Object parameters]

+

+ كائن يحتوي على خاصية لكل من معلمات المنشئ. أي تعديل بعد التجسيد لا يغير + الهندسة. +

+ +

الطرق (Methods)

+

انظر فئة [page:BufferGeometry] الأساسية للطرق المشتركة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/geometries/DodecahedronGeometry.html b/docs/api/ar/geometries/DodecahedronGeometry.html new file mode 100644 index 00000000000000..8919492245861f --- /dev/null +++ b/docs/api/ar/geometries/DodecahedronGeometry.html @@ -0,0 +1,64 @@ + + + + + + + + + + [page:BufferGeometry] → [page:PolyhedronGeometry] + +

[name]

+ +

فئة لإنشاء هندسات الدوديكاهيدرون.

+ + + + + +

المنشئ (Constructor)

+ +

[name]([param:Float radius], [param:Integer detail])

+

+ radius — نصف قطر الدوديكاهيدرون. الافتراضي هو 1.
+ detail — الافتراضي هو 0. تعيين هذه القيمة إلى قيمة أكبر من 0 يضيف + الرؤوس مما يجعله لم يعد دوديكاهيدرون. +

+ +

الخصائص (Properties)

+

انظر فئة [page:PolyhedronGeometry] الأساسية للخصائص المشتركة.

+ +

[property:Object parameters]

+

+ كائن يحتوي على خاصية لكل من معلمات المنشئ. أي تعديل بعد التجسيد لا يغير + الهندسة. +

+ +

الطرق (Methods)

+

انظر فئة [page:PolyhedronGeometry] الأساسية للطرق المشتركة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/geometries/EdgesGeometry.html b/docs/api/ar/geometries/EdgesGeometry.html new file mode 100644 index 00000000000000..9e138ec93ebe36 --- /dev/null +++ b/docs/api/ar/geometries/EdgesGeometry.html @@ -0,0 +1,61 @@ + + + + + + + + + + [page:BufferGeometry] → + +

[name]

+ +

+ يمكن استخدام هذا ككائن مساعد لعرض حواف + [page:BufferGeometry geometry]. +

+ +

مثال الكود

+ + +const geometry = new THREE.BoxGeometry( 100, 100, 100 ); +const edges = new THREE.EdgesGeometry( geometry ); +const line = new THREE.LineSegments(edges, new THREE.LineBasicMaterial( { color: 0xffffff } ) ); +scene.add( line ); + + +

أمثلة (Examples)

+

[example:webgl_helpers helpers]

+ +

المنشئ (Constructor)

+ +

+ [name]( [param:BufferGeometry geometry], [param:Integer thresholdAngle] ) +

+

+ geometry — أي كائن هندسة.
+ thresholdAngle — يتم عرض الحافة فقط إذا كانت الزاوية (بالدرجات) + بين معايير الوجه للوجوه الملحقة تتجاوز هذه القيمة. + الافتراضي = 1 درجة. +

+ +

الخصائص (Properties)

+

انظر فئة [page:BufferGeometry] الأساسية للخصائص المشتركة.

+ +

[property:Object parameters]

+

+ كائن يحتوي على خاصية لكل من معلمات المنشئ. أي تعديل بعد التجسيد لا يغير + الهندسة. +

+ +

الطرق (Methods)

+

انظر فئة [page:BufferGeometry] الأساسية للطرق المشتركة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/geometries/ExtrudeGeometry.html b/docs/api/ar/geometries/ExtrudeGeometry.html new file mode 100644 index 00000000000000..f2220bae0aa2eb --- /dev/null +++ b/docs/api/ar/geometries/ExtrudeGeometry.html @@ -0,0 +1,131 @@ + + + + + + + + + + [page:BufferGeometry] → + +

[name]

+ +

ينشئ هندسة مُبثوقة من شكل المسار.

+ + + + + +

مثال الكود

+ + + const length = 12, width = 8; + + const shape = new THREE.Shape(); + shape.moveTo( 0,0 ); + shape.lineTo( 0, width ); + shape.lineTo( length, width ); + shape.lineTo( length, 0 ); + shape.lineTo( 0, 0 ); + + const extrudeSettings = { + steps: 2, + depth: 16, + bevelEnabled: true, + bevelThickness: 1, + bevelSize: 1, + bevelOffset: 0, + bevelSegments: 1 + }; + + const geometry = new THREE.ExtrudeGeometry( shape, extrudeSettings ); + const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); + const mesh = new THREE.Mesh( geometry, material ) ; + scene.add( mesh ); + + +

المنشئ (Constructor)

+ +

[name]([param:Array shapes], [param:Object options])

+

+ shapes — شكل أو مصفوفة من الأشكال.
+ options — كائن يمكن أن يحتوي على المعلمات التالية. +

+ +
    +
  • + curveSegments — int. عدد النقاط على المنحنيات. الافتراضي هو 12. +
  • +
  • + steps — int. عدد النقاط المستخدمة لتقسيم الشرائح على طول + عمق المنحنى المبثوق. الافتراضي هو 1. +
  • +
  • depth — float. عمق لإبعاد الشكل. الافتراضي هو 1.
  • +
  • + bevelEnabled — bool. تطبيق التجعيد على الشكل. الافتراضي هو true. +
  • +
  • + bevelThickness — float. مدى اختراق التجعيد في الشكل الأصلي. + الافتراضي هو 0.2. +
  • +
  • + bevelSize — float. المسافة من مخطط الشكل التي يمتد فيها التجعيد. + الافتراضي هو bevelThickness - 0.1. +
  • +
  • + bevelOffset — float. المسافة من مخطط الشكل التي يبدأ فيها التجعيد. + الافتراضي هو 0. +
  • +
  • bevelSegments — int. عدد طبقات التجعيد. الافتراضي هو 3.
  • +
  • + extrudePath — THREE.Curve. مسار منحنى ثلاثي الأبعاد يجب أن يتم إبعاد + الشكل على طوله. لا يتم دعم التجعيد لإبعاد المسار. +
  • +
  • UVGenerator — Object. كائن يوفر وظائف مولد UV
  • +
+ +

هذا الكائن يبعد شكل ثنائي الأبعاد إلى هندسة ثلاثية الأبعاد.

+ +

+ عند إنشاء شبكة بهذه الهندسة، إذا كنت ترغب في استخدام مادة منفصلة لوجهها + وجوانبها المبثوقة، يمكنك استخدام مصفوفة من المواد. سيتم تطبيق المادة + الأولى على الوجه؛ سيتم تطبيق المادة الثانية على الجانب. +

+ +

الخصائص (Properties)

+

انظر فئة [page:BufferGeometry] الأساسية للخصائص المشتركة.

+ +

[property:Object parameters]

+

+ كائن يحتوي على خاصية لكل من معلمات المنشئ. أي تعديل بعد التجسيد لا يغير + الهندسة. +

+ +

الطرق (Methods)

+

انظر فئة [page:BufferGeometry] الأساسية للطرق المشتركة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/geometries/IcosahedronGeometry.html b/docs/api/ar/geometries/IcosahedronGeometry.html new file mode 100644 index 00000000000000..b5f614de126983 --- /dev/null +++ b/docs/api/ar/geometries/IcosahedronGeometry.html @@ -0,0 +1,64 @@ + + + + + + + + + + [page:BufferGeometry] → [page:PolyhedronGeometry] → +

[name]

+ +

فئة لإنشاء هندسة الإيكوساهيدرون.

+ + + + + +

المنشئ (Constructor)

+ +

[name]([param:Float radius], [param:Integer detail])

+

+ radius — الافتراضي هو 1.
+ detail — الافتراضي هو 0. تعيين هذه القيمة إلى قيمة أكبر من 0 يضيف المزيد + من الرؤوس مما يجعله لم يعد إيكوساهيدرون. عندما يكون التفصيل أكبر من + 1، فهو عبارة عن كرة بشكل فعال. +

+ +

الخصائص (Properties)

+

انظر فئة [page:PolyhedronGeometry] الأساسية للخصائص المشتركة.

+ +

[property:Object parameters]

+

+ كائن يحتوي على خاصية لكل من معلمات المنشئ. أي تعديل بعد التجسيد لا يغير + الهندسة. +

+ +

الطرق (Methods)

+

انظر فئة [page:PolyhedronGeometry] الأساسية للطرق المشتركة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/geometries/LatheGeometry.html b/docs/api/ar/geometries/LatheGeometry.html new file mode 100644 index 00000000000000..277a66b345e05c --- /dev/null +++ b/docs/api/ar/geometries/LatheGeometry.html @@ -0,0 +1,83 @@ + + + + + + + + + + [page:BufferGeometry] → + +

[name]

+ +

+ ينشئ شبكات مع تماثل محوري مثل الفازات. يدور الخراطة حول المحور Y. +

+ + + + +

مثال الكود

+ + + const points = []; + for ( let i = 0; i < 10; i ++ ) { + points.push( new THREE.Vector2( Math.sin( i * 0.2 ) * 10 + 5, ( i - 5 ) * 2 ) ); + } + const geometry = new THREE.LatheGeometry( points ); + const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + const lathe = new THREE.Mesh( geometry, material ); + scene.add( lathe ); + + +

المنشئ (Constructor)

+ +

+ [name]([param:Array points], [param:Integer segments], [param:Float + phiStart], [param:Float phiLength]) +

+

+ points — مصفوفة من Vector2s. يجب أن يكون الإحداثي x لكل نقطة أكبر من الصفر. الافتراضي هو مصفوفة مع (0، -0.5) ، (0.5، 0) و (0، 0.5) التي تنشئ شكل الماس البسيط.
+ segments — عدد قطاعات المحيط التي يتم إنشاؤها. الافتراضي هو + 12.
+ phiStart — الزاوية الابتدائية بالراديان. الافتراضي هو 0.
+ phiLength — المدى الرادياني (0 إلى 2PI) للقسم الملتوي 2PI هو مخرطة مغلقة ، أقل من 2PI هو جزء. الافتراضي هو 2PI. +

+

هذا ينشئ [name] بناءً على المعلمات.

+ +

الخصائص (Properties)

+

انظر فئة [page:BufferGeometry] الأساسية للخصائص المشتركة.

+ +

[property:Object parameters]

+

+ كائن به خاصية لكل من معلمات المُنشئ. أي تعديل بعد التجسيد لا يغير الهندسة. +

+ +

الطرق (Methods)

+

انظر فئة [page:BufferGeometry] الأساسية للطرق المشتركة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/geometries/OctahedronGeometry.html b/docs/api/ar/geometries/OctahedronGeometry.html new file mode 100644 index 00000000000000..ba51aa9dde7caf --- /dev/null +++ b/docs/api/ar/geometries/OctahedronGeometry.html @@ -0,0 +1,61 @@ + + + + + + + + + + [page:BufferGeometry] → [page:PolyhedronGeometry] → +

[name]

+ +

فئة لإنشاء هندسة الأوكتاهيدرون.

+ + + + +

المنشئ (Constructor)

+ +

[name]([param:Float radius], [param:Integer detail])

+

+ radius — نصف قطر الثماني. الافتراضي هو 1.
+ detail — الافتراضي هو 0. تعيين هذه القيمة إلى قيمة أكبر من الصفر يضيف + الرؤوس مما يجعله لم يعد ثمانيًا. +

+ +

الخصائص (Properties)

+

انظر فئة [page:PolyhedronGeometry] الأساسية للخصائص المشتركة.

+ +

[property:Object parameters]

+

+ كائن به خاصية لكل من معلمات المُنشئ. أي تعديل بعد التجسيد لا يغير الهندسة. +

+ +

الطرق (Methods)

+

انظر فئة [page:PolyhedronGeometry] الأساسية للطرق المشتركة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/geometries/PlaneGeometry.html b/docs/api/ar/geometries/PlaneGeometry.html new file mode 100644 index 00000000000000..19d94dda3802b4 --- /dev/null +++ b/docs/api/ar/geometries/PlaneGeometry.html @@ -0,0 +1,75 @@ + + + + + + + + + + [page:BufferGeometry] → + +

[name]

+ +

فئة لإنشاء هندسات الطائرة.

+ + + + +

مثال الكود

+ + +const geometry = new THREE.PlaneGeometry( 1, 1 ); +const material = new THREE.MeshBasicMaterial( {color: 0xffff00, side: THREE.DoubleSide} ); +const plane = new THREE.Mesh( geometry, material ); +scene.add( plane ); + + +

المنشئ (Constructor)

+ +

+ [name]([param:Float width], [param:Float height], [param:Integer + widthSegments], [param:Integer heightSegments]) +

+

+ width — العرض على طول المحور X. الافتراضي هو 1.
+ height — الارتفاع على طول المحور Y. الافتراضي هو 1.
+ widthSegments — اختياري. الافتراضي هو 1.
+ heightSegments — اختياري. الافتراضي هو 1. +

+ +

الخصائص (Properties)

+

انظر فئة [page:BufferGeometry] الأساسية للخصائص المشتركة.

+ +

[property:Object parameters]

+

+ كائن به خاصية لكل من معلمات المُنشئ. أي تعديل بعد التجسيد لا يغير الهندسة. +

+ +

الطرق (Methods)

+

انظر فئة [page:BufferGeometry] الأساسية للطرق المشتركة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/geometries/PolyhedronGeometry.html b/docs/api/ar/geometries/PolyhedronGeometry.html new file mode 100644 index 00000000000000..ebb06670182f0f --- /dev/null +++ b/docs/api/ar/geometries/PolyhedronGeometry.html @@ -0,0 +1,68 @@ + + + + + + + + + + [page:BufferGeometry] → + +

[name]

+ +

+ متعدد الأوجه هو صلب في ثلاثة أبعاد مع وجوه مسطحة. ستأخذ هذه الفئة مصفوفة من الرؤوس ، وتعرضها على كرة ، ثم تقسمها إلى المستوى المطلوب من التفاصيل. يتم استخدام هذه الفئة بواسطة [page:DodecahedronGeometry] ، [page:IcosahedronGeometry] ، [page:OctahedronGeometry] ، و [page:TetrahedronGeometry] لإنشاء هندساتهم المعنية. +

+ +

Code Example

+ +const verticesOfCube = [ + -1,-1,-1, 1,-1,-1, 1, 1,-1, -1, 1,-1, + -1,-1, 1, 1,-1, 1, 1, 1, 1, -1, 1, 1, +]; + +const indicesOfFaces = [ + 2,1,0, 0,3,2, + 0,4,7, 7,3,0, + 0,1,5, 5,4,0, + 1,2,6, 6,5,1, + 2,3,7, 7,6,2, + 4,5,6, 6,7,4 +]; + +const geometry = new THREE.PolyhedronGeometry( verticesOfCube, indicesOfFaces, 6, 2 ); + + +

المنشئ (Constructor)

+ +

+ [name]([param:Array vertices], [param:Array indices], [param:Float radius], [param:Integer detail]) +

+

+ vertices — [page:Array] من النقاط على شكل [1،1،1، -1، -1، -1، ...] +
+ indices — [page:Array] من الفهارس التي تشكل وجوه الشكل + [0،1،2، 2،3،0، ...]
+ radius — [page:Float] - نصف قطر الشكل النهائي
+ detail — [page:Integer] - عدد المستويات لتقسيم الهندسة. كلما كان التفصيل أكثر ، كان الشكل أكثر نعومة. +

+ +

الخصائص (Properties)

+

انظر فئة [page:BufferGeometry] الأساسية للخصائص المشتركة.

+ +

[property:Object parameters]

+

+ كائن به خاصية لكل من معلمات المُنشئ. أي تعديل بعد التجسيد لا يغير الهندسة. +

+ +

الطرق (Methods)

+

انظر فئة [page:BufferGeometry] الأساسية للطرق المشتركة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/geometries/RingGeometry.html b/docs/api/ar/geometries/RingGeometry.html new file mode 100644 index 00000000000000..315c34bea2187d --- /dev/null +++ b/docs/api/ar/geometries/RingGeometry.html @@ -0,0 +1,74 @@ + + + + + + + + + + [page:BufferGeometry] → + +

[name]

+ +

فئة لإنشاء هندسة حلقة ثنائية الأبعاد.

+ + + + +

Code Example

+ + + const geometry = new THREE.RingGeometry( 1, 5, 32 ); + const material = new THREE.MeshBasicMaterial( { color: 0xffff00, side: THREE.DoubleSide } ); + const mesh = new THREE.Mesh( geometry, material ); scene.add( mesh ); + + +

المنشئ (Constructor)

+ +

+ [name]([param:Float innerRadius], [param:Float outerRadius], + [param:Integer thetaSegments], [param:Integer phiSegments], [param:Float thetaStart], [param:Float thetaLength]) +

+

+ innerRadius — الافتراضي هو 0.5.
+ outerRadius — الافتراضي هو 1.
+ thetaSegments — عدد القطاعات. يعني الرقم الأعلى أن الحلقة ستكون + أكثر دائرية. الحد الأدنى هو 3. الافتراضي هو 32.
+ phiSegments — الحد الأدنى هو 1. الافتراضي هو 1.
+ thetaStart — زاوية البدء. الافتراضي هو 0.
+ thetaLength — زاوية مركزية. الافتراضي هو Math.PI * 2. +

+ +

الخصائص (Properties)

+

انظر فئة [page:BufferGeometry] الأساسية للخصائص المشتركة.

+ +

[property:Object parameters]

+

+ كائن به خاصية لكل من معلمات المُنشئ. أي تعديل بعد التجسيد لا يغير الهندسة. +

+ +

الطرق (Methods)

+

انظر فئة [page:BufferGeometry] الأساسية للطرق المشتركة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/geometries/ShapeGeometry.html b/docs/api/ar/geometries/ShapeGeometry.html new file mode 100644 index 00000000000000..a3f1238151b3a4 --- /dev/null +++ b/docs/api/ar/geometries/ShapeGeometry.html @@ -0,0 +1,87 @@ + + + + + + + + + + [page:BufferGeometry] → + +

[name]

+ +

+ ينشئ هندسة متعددة الأضلاع من جانب واحد من شكل واحد أو أكثر من المسارات. +

+ + + + + +

Code Example

+ + + const x = 0, y = 0; + + const heartShape = new THREE.Shape(); + + heartShape.moveTo( x + 5, y + 5 ); + heartShape.bezierCurveTo( x + 5, y + 5, x + 4, y, x, y ); + heartShape.bezierCurveTo( x - 6, y, x - 6, y + 7,x - 6, y + 7 ); + heartShape.bezierCurveTo( x - 6, y + 11, x - 3, y + 15.4, x + 5, y + 19 ); + heartShape.bezierCurveTo( x + 12, y + 15.4, x + 16, y + 11, x + 16, y + 7 ); + heartShape.bezierCurveTo( x + 16, y + 7, x + 16, y, x + 10, y ); + heartShape.bezierCurveTo( x + 7, y, x + 5, y + 5, x + 5, y + 5 ); + + const geometry = new THREE.ShapeGeometry( heartShape ); + const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); + const mesh = new THREE.Mesh( geometry, material ) ; + scene.add( mesh ); + + +

المنشئ (Constructor)

+ +

[name]([param:Array shapes], [param:Integer curveSegments])

+

+ shapes — [page:Array] من الأشكال أو [page:Shape shape] واحد. الافتراضي هو + شكل مثلث واحد.
+ curveSegments - [page:Integer] - عدد القطاعات لكل شكل. الافتراضي هو + 12. +

+ +

الخصائص (Properties)

+

انظر فئة [page:BufferGeometry] الأساسية للخصائص المشتركة.

+ +

[property:Object parameters]

+

+ كائن به خاصية لكل من معلمات المُنشئ. أي تعديل بعد التجسيد لا يغير الهندسة. +

+ +

الطرق (Methods)

+

انظر فئة [page:BufferGeometry] الأساسية للطرق المشتركة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/geometries/SphereGeometry.html b/docs/api/ar/geometries/SphereGeometry.html new file mode 100644 index 00000000000000..edaeccdcdc21c6 --- /dev/null +++ b/docs/api/ar/geometries/SphereGeometry.html @@ -0,0 +1,87 @@ + + + + + + + + + + [page:BufferGeometry] → + +

[name]

+ +

فئة لإنشاء هندسات الكرة.

+ + + + +

مثال الكود

+ + +const geometry = new THREE.SphereGeometry( 15, 32, 16 ); +const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); +const sphere = new THREE.Mesh( geometry, material ); scene.add( sphere ); + + +

المنشئ (Constructor)

+ +

+ [name]([param:Float radius], [param:Integer widthSegments], [param:Integer heightSegments], [param:Float phiStart], [param:Float phiLength], + [param:Float thetaStart], [param:Float thetaLength]) +

+ +

+ radius — نصف قطر الكرة. الافتراضي هو 1.
+ widthSegments — عدد القطاعات الأفقية. القيمة الدنيا هي 3 ، والافتراضي هو + 32.
+ heightSegments — عدد القطاعات العمودية. القيمة الدنيا هي 2 ، والافتراضي هو + 16.
+ phiStart — حدد زاوية البدء الأفقية. الافتراضي هو 0.
+ phiLength — حدد حجم زاوية المسح الأفقية. الافتراضي هو Math.PI * + 2.
+ thetaStart — حدد زاوية البدء العمودية. الافتراضي هو 0.
+ thetaLength — حدد حجم زاوية المسح العمودية. الافتراضي هو Math.PI.
+

+ +

+ يتم إنشاء الهندسة عن طريق التمرير وحساب قمم حول المحور Y + (المسح الأفقي) والمحور Z (المسح العمودي). وبالتالي ، يمكن إنشاء كرات ناقصة (شبيهة بـ `'شرائح كروية'`) من خلال استخدام قيم مختلفة من phiStart و phiLength و thetaStart و thetaLength ، لتحديد النقاط التي نبدأ فيها (أو ننهي) حساب تلك + قمم. +

+ +

الخصائص (Properties)

+

انظر فئة [page:BufferGeometry] الأساسية للخصائص المشتركة.

+ +

[property:Object parameters]

+

+ كائن به خاصية لكل من معلمات المُنشئ. أي تعديل بعد التجسيد لا يغير الهندسة. +

+ +

الطرق (Methods)

+

انظر فئة [page:BufferGeometry] الأساسية للطرق المشتركة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/geometries/TetrahedronGeometry.html b/docs/api/ar/geometries/TetrahedronGeometry.html new file mode 100644 index 00000000000000..7b67c06b69eb21 --- /dev/null +++ b/docs/api/ar/geometries/TetrahedronGeometry.html @@ -0,0 +1,62 @@ + + + + + + + + + + [page:BufferGeometry] → [page:PolyhedronGeometry] → + +

[name]

+ +

فئة لإنشاء هندسات التيتراهيدرون.

+ + + + +

المنشئ (Constructor)

+ +

[name]([param:Float radius], [param:Integer detail])

+

+ radius — نصف قطر المنشور الرباعي. الافتراضي هو 1.
+ detail — الافتراضي هو 0. تعيين هذه القيمة إلى قيمة أكبر من 0 يضيف + الرؤوس مما يجعله لم يعد منشورًا رباعيًا. +

+ +

الخصائص (Properties)

+

انظر فئة [page:PolyhedronGeometry] الأساسية للخصائص المشتركة.

+ +

[property:Object parameters]

+

+ كائن به خاصية لكل من معلمات المُنشئ. أي تعديل بعد التجسيد لا يغير الهندسة. +

+ +

الطرق (Methods)

+

انظر فئة [page:PolyhedronGeometry] الأساسية للطرق المشتركة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/geometries/TorusGeometry.html b/docs/api/ar/geometries/TorusGeometry.html new file mode 100644 index 00000000000000..20159e4594959b --- /dev/null +++ b/docs/api/ar/geometries/TorusGeometry.html @@ -0,0 +1,75 @@ + + + + + + + + + + [page:BufferGeometry] → + +

[name]

+ +

فئة لإنشاء هندسات التوروس.

+ + + + +

مثال الكود

+ + +const geometry = new THREE.TorusGeometry( 10, 3, 16, 100 ); +const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); +const torus = new THREE.Mesh( geometry, material ); scene.add( torus ); + + +

المنشئ (Constructor)

+ +

+ [name]([param:Float radius], [param:Float tube], [param:Integer radialSegments], [param:Integer tubularSegments], [param:Float arc]) +

+

+ radius - نصف قطر الدائرة ، من مركز الدائرة إلى مركز + الأنبوب. الافتراضي هو 1.
+ tube — نصف قطر الأنبوب. الافتراضي هو 0.4.
+ radialSegments — الافتراضي هو 12
+ tubularSegments — الافتراضي هو 48.
+ arc — زاوية مركزية. الافتراضي هو Math.PI * 2. +

+ +

الخصائص (Properties)

+

انظر فئة [page:BufferGeometry] الأساسية للخصائص المشتركة.

+ +

[property:Object parameters]

+

+ كائن به خاصية لكل من معلمات المُنشئ. أي تعديل بعد التجسيد لا يغير الهندسة. +

+ +

الطرق (Methods)

+

انظر فئة [page:BufferGeometry] الأساسية للطرق المشتركة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/geometries/TorusKnotGeometry.html b/docs/api/ar/geometries/TorusKnotGeometry.html new file mode 100644 index 00000000000000..94c9ad3e343dc5 --- /dev/null +++ b/docs/api/ar/geometries/TorusKnotGeometry.html @@ -0,0 +1,84 @@ + + + + + + + + + + [page:BufferGeometry] → + +

[name]

+ +

+ ينشئ عقدة دائرية ، والتي يتم تحديد شكلها الخاص من خلال زوج من الأعداد الصحيحة المتباينة ، p و q. إذا لم يكن p و q متباينين ، فسيكون النتيجة رابط دائري. +

+ + + + +

مثال الكود

+ + +const geometry = new THREE.TorusKnotGeometry( 10, 3, 100, 16 ); +const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); +const torusKnot = new THREE.Mesh( geometry, material ); scene.add( torusKnot ); + + +

المنشئ (Constructor)

+ +

+ [name]([param:Float radius], [param:Float tube], [param:Integer tubularSegments], [param:Integer radialSegments], [param:Integer p], + [param:Integer q]) +

+
    +
  • radius - نصف قطر الدائرة. الافتراضي هو 1.
  • +
  • tube — نصف قطر الأنبوب. الافتراضي هو 0.4.
  • +
  • tubularSegments — الافتراضي هو 64.
  • +
  • radialSegments — الافتراضي هو 8.
  • +
  • + p — هذه القيمة تحدد ، عدد مرات التفاف الهندسة حول محورها + التناظر الدوار. الافتراضي هو 2. +
  • +
  • + q — هذه القيمة تحدد ، عدد مرات التفاف الهندسة حول دائرة في داخل + الدائرة. الافتراضي هو 3. +
  • +
+ +

الخصائص (Properties)

+

انظر فئة [page:BufferGeometry] الأساسية للخصائص المشتركة.

+ +

[property:Object parameters]

+

+ كائن به خاصية لكل من معلمات المُنشئ. أي تعديل بعد التجسيد لا يغير الهندسة. +

+ +

الطرق (Methods)

+

انظر فئة [page:BufferGeometry] الأساسية للطرق المشتركة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/geometries/TubeGeometry.html b/docs/api/ar/geometries/TubeGeometry.html new file mode 100644 index 00000000000000..4edc3b10bbe797 --- /dev/null +++ b/docs/api/ar/geometries/TubeGeometry.html @@ -0,0 +1,103 @@ + + + + + + + + + + [page:BufferGeometry] → + +

[name]

+ +

ينشئ أنبوبًا يُبثق على طول منحنى ثلاثي الأبعاد.

+ + + + + +

مثال الكود

+ + + class CustomSinCurve extends THREE.Curve { + + constructor( scale = 1 ) { + super(); + this.scale = scale; + } + + getPoint( t, optionalTarget = new THREE.Vector3() ) { + + const tx = t * 3 - 1.5; + const ty = Math.sin( 2 * Math.PI * t ); + const tz = 0; + + return optionalTarget.set( tx, ty, tz ).multiplyScalar( this.scale ); + } + } + + const path = new CustomSinCurve( 10 ); + const geometry = new THREE.TubeGeometry( path, 20, 2, 8, false ); + const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); + const mesh = new THREE.Mesh( geometry, material ); + scene.add( mesh ); + + +

المنشئ (Constructor)

+ +

+ [name]([param:Curve path], [param:Integer tubularSegments], [param:Float radius], [param:Integer radialSegments], [param:Boolean closed]) +

+

+ path — [page:Curve] - مسار ثلاثي الأبعاد يرث من فئة [page:Curve] الأساسية + . الافتراضي هو منحنى بيزير رباعي.
+ tubularSegments — [page:Integer] - عدد القطاعات التي تشكل + الأنبوب. الافتراضي هو `64`.
+ radius — [page:Float] - نصف قطر الأنبوب. الافتراضي هو `1`.
+ radialSegments — [page:Integer] - عدد القطاعات التي تشكل + المقطع العرضي. الافتراضي هو `8`.
+ closed — [page:Boolean] هل الأنبوب مفتوح أم مغلق. الافتراضي هو `false`.
+

+ +

الخصائص (Properties)

+

انظر فئة [page:BufferGeometry] الأساسية للخصائص المشتركة.

+ +

[property:Object parameters]

+

+ كائن به خاصية لكل من معلمات المُنشئ. أي تعديل بعد التجسيد لا يغير الهندسة. +

+ +

[property:Array tangents]

+

مصفوفة من المماسات [page:Vector3]

+ +

[property:Array normals]

+

مصفوفة من المعايير [page:Vector3]

+ +

[property:Array binormals]

+

مصفوفة من المعايير المزدوجة [page:Vector3]

+ +

الطرق (Methods)

+

انظر فئة [page:BufferGeometry] الأساسية للطرق المشتركة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/geometries/WireframeGeometry.html b/docs/api/ar/geometries/WireframeGeometry.html new file mode 100644 index 00000000000000..52425aaa8c3a9c --- /dev/null +++ b/docs/api/ar/geometries/WireframeGeometry.html @@ -0,0 +1,54 @@ + + + + + + + + + + [page:BufferGeometry] → + +

[name]

+ +

+ يمكن استخدام هذا ككائن مساعد لعرض [page:BufferGeometry geometry] كإطار سلكي. +

+ +

مثال الكود

+ + + const geometry = new THREE.SphereGeometry( 100, 100, 100 ); + + const wireframe = new THREE.WireframeGeometry( geometry ); + + const line = new THREE.LineSegments( wireframe ); + line.material.depthTest = false; + line.material.opacity = 0.25; + line.material.transparent = true; + + scene.add( line ); + + +

أمثلة (Examples)

+ +

[example:webgl_helpers helpers]

+ +

المنشئ (Constructor)

+ +

[name]( [param:BufferGeometry geometry] )

+

geometry — أي كائن هندسي.

+ +

الخصائص (Properties)

+

انظر فئة [page:BufferGeometry] الأساسية للخصائص المشتركة.

+ +

الطرق (Methods)

+

انظر فئة [page:BufferGeometry] الأساسية للطرق المشتركة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/helpers/ArrowHelper.html b/docs/api/ar/helpers/ArrowHelper.html new file mode 100644 index 00000000000000..a81460d2a738ef --- /dev/null +++ b/docs/api/ar/helpers/ArrowHelper.html @@ -0,0 +1,103 @@ + + + + + + + + + + [page:Object3D] → + +

[name]

+ +

كائن سهم ثلاثي الأبعاد لتصور الاتجاهات.

+ +

مثال الكود

+ + + const dir = new THREE.Vector3( 1, 2, 0 ); + + //normalize the direction vector (convert to vector of length 1) + dir.normalize(); + + const origin = new THREE.Vector3( 0, 0, 0 ); + const length = 1; + const hex = 0xffff00; + + const arrowHelper = new THREE.ArrowHelper( dir, origin, length, hex ); + scene.add( arrowHelper ); + + +

أمثلة (Examples)

+ +

[example:webgl_shadowmesh WebGL / shadowmesh]

+ +

المنشئ (Constructor)

+ +

+ [name]([param:Vector3 dir], [param:Vector3 origin], [param:Number length], + [param:Number hex], [param:Number headLength], [param:Number headWidth]) +

+

+ [page:Vector3 dir] - الاتجاه من المنشأ. يجب أن يكون متجه وحدة.
+ [page:Vector3 origin] - النقطة التي يبدأ فيها السهم.
+ [page:Number length] - طول السهم. الافتراضي هو `1`.
+ [page:Number hex] - قيمة ست عشرية لتحديد اللون. الافتراضي هو + 0xffff00.
+ [page:Number headLength] - طول رأس السهم. الافتراضي + هو 0.2 * الطول.
+ [page:Number headWidth] - عرض رأس السهم. الافتراضي هو + 0.2 * headLength.
+

+ +

الخصائص (Properties)

+

انظر إلى قاعدة [page:Object3D] class للخصائص المشتركة.

+ +

[property:Line line]

+

يحتوي على جزء الخط من المساعد السهم.

+ +

[property:Mesh cone]

+

يحتوي على جزء المخروط من المساعد السهم.

+ +

الطرق (Methods)

+

انظر إلى قاعدة [page:Object3D] class للطرق المشتركة.

+ +

[method:undefined setColor]([param:Color color])

+

+ اللون - اللون المطلوب.

+ + يضبط لون المساعد السهم. +

+ +

+ [method:undefined setLength]([param:Number length], [param:Number headLength], [param:Number headWidth]) +

+

+ الطول - الطول المطلوب.
+ headLength - طول رأس السهم.
+ headWidth - عرض رأس السهم.

+ + يضبط طول المساعد السهم. +

+ +

[method:undefined setDirection]([param:Vector3 dir])

+

+ dir - الاتجاه المطلوب. يجب أن يكون متجه وحدة.

+ + يضبط اتجاه المساعد السهم. +

+ +

[method:undefined dispose]()

+

+ يحرر الموارد المتعلقة بالـ GPU التي تم تخصيصها من قبل هذا المثيل. اتصل بهذه + الطريقة كلما لم يعد هذا المثيل مستخدمًا في تطبيقك. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/helpers/AxesHelper.html b/docs/api/ar/helpers/AxesHelper.html new file mode 100644 index 00000000000000..d143a55873d0c6 --- /dev/null +++ b/docs/api/ar/helpers/AxesHelper.html @@ -0,0 +1,64 @@ + + + + + + + + + + [page:Object3D] → [page:Line] → [page:LineSegments] → + +

[name]

+

+ كائن المحور لتصور المحاور الثلاثة بطريقة بسيطة.
+ المحور X هو اللون الأحمر. المحور Y هو اللون الأخضر. المحور Z هو اللون الأزرق. +

+ +

مثال الكود

+ + +const axesHelper = new THREE.AxesHelper( 5 ); +scene.add( axesHelper ); + + +

أمثلة (Examples)

+

+ [example:webgl_buffergeometry_compression WebGL / buffergeometry / compression]
+ [example:webgl_geometry_convex WebGL / geometry / convex]
+ [example:webgl_loader_nrrd WebGL / loader / nrrd] +

+ +

المنشئ (Constructor)

+

[name]( [param:Number size] )

+

+ [page:Number size] - (اختياري) حجم الخطوط التي تمثل المحاور. + الافتراضي هو `1`. +

+ +

الخصائص (Properties)

+

انظر إلى قاعدة [page:LineSegments] class للخصائص المشتركة.

+ +

الطرق (Methods)

+

انظر إلى قاعدة [page:LineSegments] class للطرق المشتركة.

+ +

+ [method:this setColors]( [param:Color xAxisColor], [param:Color yAxisColor], [param:Color zAxisColor] ) +

+

+ يضبط ألوان المحاور إلى [page:Color xAxisColor]، [page:Color yAxisColor]، + [page:Color zAxisColor]. +

+ +

[method:undefined dispose]()

+

+ يحرر الموارد المتعلقة بالـ GPU التي تم تخصيصها من قبل هذا المثيل. اتصل بهذه + الطريقة كلما لم يعد هذا المثيل مستخدمًا في تطبيقك. +

+ +

المصدر (Source)

+

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/helpers/Box3Helper.html b/docs/api/ar/helpers/Box3Helper.html new file mode 100644 index 00000000000000..114d5d0498765e --- /dev/null +++ b/docs/api/ar/helpers/Box3Helper.html @@ -0,0 +1,62 @@ + + + + + + + + + + [page:Object3D] → [page:Line] → [page:LineSegments] → + +

[name]

+ +

كائن مساعد لتصور [page:Box3].

+ +

مثال الكود

+ + + const box = new THREE.Box3(); + box.setFromCenterAndSize( new THREE.Vector3( 1, 1, 1 ), new THREE.Vector3( 2, 1, 3 ) ); + + const helper = new THREE.Box3Helper( box, 0xffff00 ); + scene.add( helper ); + + +

المنشئ (Constructor)

+ +

[name]( [param:Box3 box], [param:Color color] )

+

+ [page:Box3 box] -- الـ Box3 المراد عرضه.
+ [page:Color color] -- (اختياري) لون الصندوق. الافتراضي هو 0xffff00.

+ + ينشئ صندوقًا جديدًا بإطار سلكي يمثل Box3 المُمرَّر. +

+ +

الخصائص (Properties)

+

انظر إلى الفئة الأساسية [page:LineSegments] للحصول على الخصائص المشتركة.

+ +

[property:Box3 box]

+

الـ Box3 المُعروض.

+ +

الطرق (Methods)

+

انظر إلى الفئة الأساسية [page:LineSegments] للحصول على الطرق المشتركة.

+ +

[method:undefined updateMatrixWorld]( [param:Boolean force] )

+

+ هذا يتجاوز الطريقة في الفئة الأساسية [page:Object3D] بحيث يُحدِّث أيضًا + الإطار السلكي للصندوق إلى مدى خاصية [page:Box3Helper.box .box]. +

+ +

[method:undefined dispose]()

+

+ يُطلِق الموارد المتعلقة بوحدة معالجة الرسومات التي تم تخصيصها من قبل هذه العيّنة. اتصل بهذه + الطريقة كلما لم يعد هذه العيّنة مستخدمة في تطبيقك. +

+ +

المصدر (Source)

+

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/helpers/BoxHelper.html b/docs/api/ar/helpers/BoxHelper.html new file mode 100644 index 00000000000000..fe36d5d57b55d5 --- /dev/null +++ b/docs/api/ar/helpers/BoxHelper.html @@ -0,0 +1,77 @@ + + + + + + + + + + [page:Object3D] → [page:Line] → [page:LineSegments] → + +

[name]

+ +

+ كائن مساعد لإظهار الحدود المحيطة المتوازية مع محور العالم بشكل بصري + حول كائن. يتم التعامل مع الحدود المحيطة الفعلية باستخدام [page:Box3]، + هذا مجرد مساعد بصري للتصحيح. يمكن تغيير حجمه تلقائيًا باستخدام طريقة [page:BoxHelper.update] + عندما يتم تحويل الكائن الذي تم إنشاؤه منه. لاحظ أن يجب أن يكون للكائن + [page:BufferGeometry] لكي يعمل هذا، لذلك لن يعمل مع [page:Sprite Sprites]. +

+ +

مثال الكود

+ + + const sphere = new THREE.SphereGeometry(); + const object = new THREE.Mesh( sphere, new THREE.MeshBasicMaterial( 0xff0000 ) ); + const box = new THREE.BoxHelper( object, 0xffff00 ); + scene.add( box ); + + +

أمثلة (Examples)

+

+ [example:webgl_helpers WebGL / helpers]
+ [example:webgl_loader_nrrd WebGL / loader / nrrd]
+ [example:webgl_buffergeometry_drawrange WebGL / buffergeometry / drawrange] +

+ +

المنشئ (Constructor)

+

[name]( [param:Object3D object], [param:Color color] )

+

+ [page:Object3D object] -- (اختياري) الـ object3D المراد عرض الحدود المحيطة المتوازية مع محور العالم.
+ [page:Color color] -- (اختياري) قيمة ست عشرية تحدد لون الصندوق. الافتراضي هو 0xffff00.

+ + ينشئ صندوقًا جديدًا بإطار سلكي يحد الكائن المُمرَّر. يستخدم هذا داخليًا + [page:Box3.setFromObject] لحساب الأبعاد. لاحظ أن هذا يشمل أي أطفال. +

+ +

الخصائص (Properties)

+

انظر إلى الفئة الأساسية [page:LineSegments] للحصول على الخصائص المشتركة.

+ +

الطرق (Methods)

+

انظر إلى الفئة الأساسية [page:LineSegments] للحصول على الطرق المشتركة.

+ +

[method:undefined update]()

+

+ يُحدِّث هندسة المساعد لتطابق أبعاد الكائن، بما في ذلك أي أطفال. انظر [page:Box3.setFromObject]. +

+ +

[method:this setFromObject]( [param:Object3D object] )

+

+ [page:Object3D object] - [page:Object3D] لإنشاء المساعد منه.

+ + يُحدِّث صندوق الإطار السلكي للكائن المُمرَّر. +

+ +

[method:undefined dispose]()

+

+ يُطلِق الموارد المتعلقة بوحدة معالجة الرسومات التي تم تخصيصها من قبل هذه العيّنة. اتصل بهذه + الطريقة كلما لم يعد هذه العيّنة مستخدمة في تطبيقك. +

+ +

المصدر (Source)

+

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/helpers/CameraHelper.html b/docs/api/ar/helpers/CameraHelper.html new file mode 100644 index 00000000000000..bc06940db58a58 --- /dev/null +++ b/docs/api/ar/helpers/CameraHelper.html @@ -0,0 +1,82 @@ + + + + + + + + + + [page:Object3D] → [page:Line] → [page:LineSegments] → + +

[name]

+ +

+ هذا يساعد في تصور ما يحتويه الكاميرا في مخروط الرؤية الخاص بها. يُصوِّر + مخروط الرؤية للكاميرا باستخدام [page:LineSegments].

+ يجب أن يكون [name] طفلًا للمشهد. +

+ +

مثال الكود

+ +const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); +const helper = new THREE.CameraHelper( camera ); +scene.add( helper ); + + +

أمثلة (Examples)

+

+ [example:webgl_camera WebGL / camera]
+ [example:webgl_geometry_extrude_splines WebGL / extrude / splines] +

+ +

المنشئ (Constructor)

+

[name]( [param:Camera camera] )

+

+ [page:Camera camera] -- الكاميرا المراد تصويرها.

+ + ينشئ [Name] جديدًا للكاميرا المحددة. +

+ +

الخصائص (Properties)

+

انظر إلى الفئة الأساسية [page:LineSegments] للحصول على الخصائص المشتركة.

+ +

[property:Camera camera]

+

الكاميرا المُعروضة.

+ +

[property:Object pointMap]

+

يحتوي هذا على النقاط المستخدمة لتصوير الكاميرا.

+ +

[property:Object matrix]

+

إشارة إلى [page:Object3D.matrixWorld camera.matrixWorld].

+ +

[property:Object matrixAutoUpdate]

+

+ انظر [page:Object3D.matrixAutoUpdate]. تعيين إلى `false` هنا كما يستخدم المساعد + [page:Object3D.matrixWorld matrixWorld] الخاص بالكاميرا. +

+ +

الطرق (Methods)

+

انظر إلى الفئة الأساسية [page:LineSegments] للحصول على الطرق المشتركة.

+ +

[method:undefined dispose]()

+

+ يُطلِق الموارد المتعلقة بوحدة معالجة الرسومات التي تم تخصيصها من قبل هذه العيّنة. اتصل بهذه + الطريقة كلما لم يعد هذه العيّنة مستخدمة في تطبيقك. +

+ +

+ [method:this setColors]( [param:Color frustum], [param:Color cone], [param:Color up], [param:Color target], [param:Color cross] ) +

+

يحدد ألوان المساعد.

+ +

[method:undefined update]()

+

يُحدِّث المساعد بناءً على projectionMatrix للكاميرا.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/helpers/DirectionalLightHelper.html b/docs/api/ar/helpers/DirectionalLightHelper.html new file mode 100644 index 00000000000000..4533b7053de714 --- /dev/null +++ b/docs/api/ar/helpers/DirectionalLightHelper.html @@ -0,0 +1,90 @@ + + + + + + + + + + [page:Object3D] → + +

[name]

+ +

+ كائن مساعد للمساعدة في تصور تأثير [page:DirectionalLight] على المشهد. + يتكون هذا من مستوى وخط يمثلان موقع الضوء واتجاهه. +

+ +

مثال الكود

+ + + const light = new THREE.DirectionalLight( 0xFFFFFF ); + scene.add( light ); + + const helper = new THREE.DirectionalLightHelper( light, 5 ); + scene.add( helper ); + + +

المنشئ (Constructor)

+ +

+ [name]( [param:DirectionalLight light], [param:Number size], [param:Hex color] ) +

+

+ [page:DirectionalLight light]-- الضوء المراد تصويره.

+ + [page:Number size] -- (اختياري) أبعاد المستوى. الافتراضي هو + `1`.

+ + [page:Hex color] -- (اختياري) إذا لم يتم تعيين هذا، فسيأخذ المساعد + لون الضوء. +

+ +

الخصائص (Properties)

+

انظر إلى الفئة الأساسية [page:Object3D] للحصول على الخصائص المشتركة.

+ +

[property:Line lightPlane]

+

يحتوي على شبكة الخط التي تظهر موقع الضوء الاتجاهي.

+ +

[property:DirectionalLight light]

+

+ إشارة إلى [page:DirectionalLight directionalLight] المُعروض. +

+ +

[property:Object matrix]

+

إشارة إلى [page:Object3D.matrixWorld matrixWorld] الخاص بالضوء.

+ +

[property:Object matrixAutoUpdate]

+

+ انظر [page:Object3D.matrixAutoUpdate]. تعيين إلى `false` هنا كما يستخدم المساعد + [page:Object3D.matrixWorld matrixWorld] الخاص بالضوء. +

+ +

[property:hex color]

+

+ معلمة اللون التي تم تمريرها في المُنشئ. الافتراضي هو `undefined`. إذا تغير هذا، + سيُحدِّث لون المساعد في المرة التالية التي يتم فيها استدعاء [page:.update update]. +

+ +

الطرق (Methods)

+

انظر إلى الفئة الأساسية [page:Object3D] للحصول على الخصائص المشتركة.

+ +

[method:undefined dispose]()

+

+ يُطلِق الموارد المتعلقة بوحدة معالجة الرسومات التي تم تخصيصها من قبل هذه العيّنة. اتصل بهذه + الطريقة كلما لم يعد هذه العيّنة مستخدمة في تطبيقك. +

+ +

[method:undefined update]()

+

+ يُحدِّث المساعد ليطابق موقع واتجاه [page:.light directionalLight] المُعروض. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/helpers/GridHelper.html b/docs/api/ar/helpers/GridHelper.html new file mode 100644 index 00000000000000..778d59726b3df7 --- /dev/null +++ b/docs/api/ar/helpers/GridHelper.html @@ -0,0 +1,69 @@ + + + + + + + + + + [page:Object3D] → [page:Line] → [page:LineSegments] → + +

[name]

+ +

+ GridHelper هو كائن لتحديد الشبكات. الشبكات هي مصفوفات ثنائية الأبعاد + من الخطوط. +

+ +

مثال الكود

+ + + const size = 10; + const divisions = 10; + + const gridHelper = new THREE.GridHelper( size, divisions ); + scene.add( gridHelper ); + + +

أمثلة (Examples)

+ +

[example:webgl_helpers WebGL / helpers]

+ +

المنشئ (Constructor)

+ +

+ [name]( [param:number size], [param:Number divisions], [param:Color colorCenterLine], [param:Color colorGrid] ) +

+

+ size -- حجم الشبكة. الافتراضي هو 10.
+ divisions -- عدد التقسيمات عبر الشبكة. الافتراضي هو 10. +
+ colorCenterLine -- لون الخط الأوسط. يمكن أن يكون هذا + [page:Color]، قيمة ست عشرية واسم لون CSS. الافتراضي هو + 0x444444
+ colorGrid -- لون خطوط الشبكة. يمكن أن يكون هذا + [page:Color]، قيمة ست عشرية واسم لون CSS. الافتراضي هو + 0x888888 +

+

+ ينشئ [name] جديدًا بحجم 'size' ومقسَّمًا إلى 'divisions' شرائح + لكل جانب. الألوان اختيارية. +

+ +

الطرق (Methods)

+

انظر إلى الفئة الأساسية [page:LineSegments] للحصول على الطرق المشتركة.

+ +

[method:undefined dispose]()

+

+ يُطلِق الموارد المتعلقة بوحدة معالجة الرسومات التي تم تخصيصها من قبل هذه العيّنة. اتصل بهذه + الطريقة كلما لم يعد هذه العيّنة مستخدمة في تطبيقك. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/helpers/HemisphereLightHelper.html b/docs/api/ar/helpers/HemisphereLightHelper.html new file mode 100644 index 00000000000000..2648f61524f41c --- /dev/null +++ b/docs/api/ar/helpers/HemisphereLightHelper.html @@ -0,0 +1,85 @@ + + + + + + + + + + [page:Object3D] → + +

[name]

+ +

+ ينشئ مساعدًا بصريًا يتكون من [page:Mesh] كروي لـ + [page:HemisphereLight HemisphereLight]. +

+ +

مثال الكود

+ + + const light = new THREE.HemisphereLight( 0xffffbb, 0x080820, 1 ); + const helper = new THREE.HemisphereLightHelper( light, 5 ); + scene.add( helper ); + + +

المنشئ (Constructor)

+ +

+ [name]( [param:HemisphereLight light], [param:Number sphereSize], + [param:Hex color] ) +

+

+ [page:HemisphereLight light] -- الضوء المراد تصويره.

+ + [page:Number size] -- حجم الشبكة المستخدمة لتصوير الضوء.

+ + [page:Hex color] -- (اختياري) إذا لم يتم تعيين هذا، فسيأخذ المساعد + لون الضوء. +

+ +

الخصائص (Properties)

+

انظر إلى الفئة الأساسية [page:Object3D] للحصول على الخصائص المشتركة.

+ +

[property:HemisphereLight light]

+

إشارة إلى HemisphereLight المُعروض.

+ +

[property:Object matrix]

+

+ إشارة إلى [page:Object3D.matrixWorld matrixWorld] الخاص بـ hemisphereLight. +

+ +

[property:Object matrixAutoUpdate]

+

+ انظر [page:Object3D.matrixAutoUpdate]. تعيين إلى `false` هنا كما يستخدم المساعد + [page:Object3D.matrixWorld matrixWorld] الخاص بـ hemisphereLight. +

+ +

[property:hex color]

+

+ معلمة اللون التي تم تمريرها في المُنشئ. الافتراضي هو `undefined`. إذا تغير هذا، + سيُحدِّث لون المساعد في المرة التالية التي يتم فيها استدعاء [page:.update update]. +

+ +

الطرق (Methods)

+

انظر إلى الفئة الأساسية [page:Object3D] للحصول على الطرق المشتركة.

+ +

[method:undefined dispose]()

+

+ يُطلِق الموارد المتعلقة بوحدة معالجة الرسومات التي تم تخصيصها من قبل هذه العيّنة. اتصل بهذه + الطريقة كلما لم يعد هذه العيّنة مستخدمة في تطبيقك. +

+ +

[method:undefined update]()

+

+ يُحدِّث المساعد ليطابق موقع واتجاه [page:.light]. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/helpers/PlaneHelper.html b/docs/api/ar/helpers/PlaneHelper.html new file mode 100644 index 00000000000000..0313a882a0a89c --- /dev/null +++ b/docs/api/ar/helpers/PlaneHelper.html @@ -0,0 +1,69 @@ + + + + + + + + + + [page:Object3D] → [page:Line] → [page:LineSegments] → + +

[name]

+ +

كائن مساعد لتصور [page:Plane].

+ +

مثال الكود

+ + + const plane = new THREE.Plane( new THREE.Vector3( 1, 1, 0.2 ), 3 ); + const helper = new THREE.PlaneHelper( plane, 1, 0xffff00 ); + scene.add( helper ); + + +

المنشئ (Constructor)

+ +

+ [name]( [param:Plane plane], [param:Float size], [param:Color hex] ) +

+

+ [page:Plane plane] -- الطائرة المراد تصويرها.
+ [page:Float size] -- (اختياري) طول جانب مساعد الطائرة. الافتراضي هو + 1.
+ [page:Color color] -- (اختياري) لون المساعد. الافتراضي هو + 0xffff00.

+ + ينشئ تمثيلًا جديدًا بإطار سلكي للطائرة المُمرَّرة. +

+ +

الخصائص (Properties)

+

انظر إلى الفئة الأساسية [page:Line] للحصول على الخصائص المشتركة.

+ +

[property:Plane plane]

+

[page:Plane plane] المُعروض.

+ +

[property:Float size]

+

أطوال جانب مساعد الطائرة.

+ +

الطرق (Methods)

+

انظر إلى الفئة الأساسية [page:LineSegments] للحصول على الطرق المشتركة.

+ +

[method:undefined updateMatrixWorld]( [param:Boolean force] )

+

+ هذا يتجاوز الطريقة في الفئة الأساسية [page:Object3D] بحيث يُحدِّث أيضًا + كائن المساعد وفقًا لخصائص [page:PlaneHelper.plane .plane] و [page:PlaneHelper.size .size]. +

+ +

[method:undefined dispose]()

+

+ يُطلِق الموارد المتعلقة بوحدة معالجة الرسومات التي تم تخصيصها من قبل هذه العيّنة. اتصل بهذه + الطريقة كلما لم يعد هذه العيّنة مستخدمة في تطبيقك. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/helpers/PointLightHelper.html b/docs/api/ar/helpers/PointLightHelper.html new file mode 100644 index 00000000000000..ed11fb3a0461ab --- /dev/null +++ b/docs/api/ar/helpers/PointLightHelper.html @@ -0,0 +1,91 @@ + + + + + + + + + + [page:Object3D] → [page:Mesh] → + +

[name]

+ +

+ يعرض كائن مساعد يتكون من [page:Mesh] كروي لتصوير + [page:PointLight]. +

+ +

مثال الكود

+ + + const pointLight = new THREE.PointLight( 0xff0000, 1, 100 ); + pointLight.position.set( 10, 10, 10 ); + scene.add( pointLight ); + + const sphereSize = 1; + const pointLightHelper = new THREE.PointLightHelper( pointLight, sphereSize ); + scene.add( pointLightHelper ); + + +

أمثلة (Examples)

+ +

[example:webgl_helpers WebGL / helpers]

+ +

المنشئ (Constructor)

+ +

+ [name]( [param:PointLight light], [param:Float sphereSize], [param:Hex color] ) +

+

+ [page:PointLight light] -- الضوء المراد تصويره.

+ + [page:Float sphereSize] -- (اختياري) حجم مساعد الكرة. + الافتراضي هو `1`.

+ + [page:Hex color] -- (اختياري) إذا لم يتم تعيين هذا، فسيأخذ المساعد + لون الضوء. +

+ +

الخصائص (Properties)

+

انظر إلى الفئة الأساسية [page:Mesh] للحصول على الخصائص المشتركة.

+ +

[property:PointLight light]

+

[page:PointLight] المُعروض.

+ +

[property:Object matrix]

+

+ إشارة إلى [page:Object3D.matrixWorld matrixWorld] الخاص بـ pointLight. +

+ +

[property:Object matrixAutoUpdate]

+

+ انظر [page:Object3D.matrixAutoUpdate]. تعيين إلى `false` هنا كما يستخدم المساعد + [page:Object3D.matrixWorld matrixWorld] الخاص بـ pointLight. +

+ +

[property:hex color]

+

+ معلمة اللون التي تم تمريرها في المُنشئ. الافتراضي هو `undefined`. إذا تغير هذا، + سيُحدِّث لون المساعد في المرة التالية التي يتم فيها استدعاء [page:.update update]. +

+ +

الطرق (Methods)

+

انظر إلى الفئة الأساسية [page:Mesh] للحصول على الطرق المشتركة.

+ +

[method:undefined dispose]()

+

+ يُطلِق الموارد المتعلقة بوحدة معالجة الرسومات التي تم تخصيصها من قبل هذه العيّنة. اتصل بهذه + الطريقة كلما لم يعد هذه العيّنة مستخدمة في تطبيقك. +

+ +

[method:undefined update]()

+

يُحدِّث المساعد ليطابق موقع [page:.light].

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/helpers/PolarGridHelper.html b/docs/api/ar/helpers/PolarGridHelper.html new file mode 100644 index 00000000000000..9b4cf23c457d00 --- /dev/null +++ b/docs/api/ar/helpers/PolarGridHelper.html @@ -0,0 +1,77 @@ + + + + + + + + + + [page:Object3D] → [page:Line] → [page:LineSegments] → + +

[name]

+ +

+ PolarGridHelper هو كائن لتحديد الشبكات القطبية. الشبكات هي مصفوفات ثنائية الأبعاد + من الخطوط. +

+ +

مثال الكود

+ + + const radius = 10; + const sectors = 16; + const rings = 8; + const divisions = 64; + + const helper = new THREE.PolarGridHelper( radius, sectors, rings, divisions ); + scene.add( helper ); + + +

أمثلة (Examples)

+ +

[example:webgl_helpers WebGL / helpers]

+ +

المنشئ (Constructor)

+ +

+ [name]( [param:Number radius], [param:Number sectors], [param:Number rings], [param:Number divisions], [param:Color color1], [param:Color color2] ) +

+

+ radius -- نصف قطر الشبكة القطبية. يمكن أن يكون هذا أي رقم موجب. + الافتراضي هو 10.
+ sectors -- عدد القطاعات التي ستُقسَّم إليها الشبكة. يمكن أن يكون هذا + أي عدد صحيح موجب. الافتراضي هو 16.
+ rings -- عدد الحلقات. يمكن أن يكون هذا أي عدد صحيح موجب. الافتراضي هو + 8.
+ divisions -- عدد شرائح الخط المستخدمة لكل دائرة. يمكن أن يكون هذا + أي عدد صحيح موجب لا يقل عن 3. الافتراضي هو 64.
+ color1 -- اللون الأول المستخدم لعناصر الشبكة. يمكن أن يكون هذا + [page:Color]، قيمة ست عشرية واسم لون CSS. الافتراضي هو + 0x444444
+ color2 -- اللون الثاني المستخدم لعناصر الشبكة. يمكن أن يكون هذا + [page:Color]، قيمة ست عشرية واسم لون CSS. الافتراضي هو + 0x888888 +

+

+ ينشئ [name] جديدًا بقطر 'radius' مع 'sectors' عدد من القطاعات + و 'rings' عدد من الحلقات، حيث تُسَهَّل كل دائرة إلى + 'divisions' عدد من شرائح الخط. الألوان اختيارية. +

+ +

الطرق (Methods)

+

انظر إلى الفئة الأساسية [page:LineSegments] للحصول على الطرق المشتركة.

+ +

[method:undefined dispose]()

+

+ يُطلِق الموارد المتعلقة بوحدة معالجة الرسومات التي تم تخصيصها من قبل هذه العيّنة. اتصل بهذه + الطريقة كلما لم يعد هذه العيّنة مستخدمة في تطبيقك. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/helpers/SkeletonHelper.html b/docs/api/ar/helpers/SkeletonHelper.html new file mode 100644 index 00000000000000..162d873881890a --- /dev/null +++ b/docs/api/ar/helpers/SkeletonHelper.html @@ -0,0 +1,68 @@ + + + + + + + + + + [page:Object3D] → [page:Line] → [page:LineSegments] → + +

[name]

+ +

+ كائن مساعد للمساعدة في تصور [page:Skeleton Skeleton]. يتم + تقديم المساعد باستخدام [page:LineBasicMaterial LineBasicMaterial]. +

+ +

مثال الكود

+ + + const helper = new THREE.SkeletonHelper( skinnedMesh ); + scene.add( helper ); + + +

أمثلة (Examples)

+ +

+ [example:webgl_animation_skinning_blending WebGL / animation / skinning / blending]
+ [example:webgl_animation_skinning_morph WebGL / animation / skinning / morph]
+ [example:webgl_loader_bvh WebGL / loader / bvh ] +

+ +

المنشئ (Constructor)

+ +

[name]( [param:Object3D object] )

+

+ object -- عادةً مثيل من [page:SkinnedMesh]. ومع ذلك ، يمكن استخدام أي مثيل + من [page:Object3D] إذا كان يمثل تسلسل هرمي من [page:Bone Bone]s (عبر [page:Object3D.children]). +

+ +

الخصائص (Properties)

+ +

[property:Array bones]

+

قائمة العظام التي يعرضها المساعد كـ [page:Line Lines].

+ +

[property:Boolean isSkeletonHelper]

+

علامة للقراءة فقط للتحقق مما إذا كان الكائن المعطى هو من نوع [name].

+ +

[property:Object3D root]

+

الكائن الذي تم تمريره في المنشئ.

+ +

الطرق (Methods)

+

انظر الفئة الأساسية [page:LineSegments] للطرق المشتركة.

+ +

[method:undefined dispose]()

+

+ تحرير الموارد ذات الصلة بوحدة معالجة الرسومات التي تم تخصيصها من قبل هذه الحالة. استدعاء هذه + الطريقة كلما لم يعد يستخدم هذا الحال في تطبيقك. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/helpers/SpotLightHelper.html b/docs/api/ar/helpers/SpotLightHelper.html new file mode 100644 index 00000000000000..12111e2484230d --- /dev/null +++ b/docs/api/ar/helpers/SpotLightHelper.html @@ -0,0 +1,86 @@ + + + + + + + + + + [page:Object3D] → + +

[name]

+ +

+ يعرض هذا مساعد على شكل مخروط لـ [page:SpotLight]. +

+ +

مثال الكود

+ + + const spotLight = new THREE.SpotLight( 0xffffff ); + spotLight.position.set( 10, 10, 10 ); + scene.add( spotLight ); + + const spotLightHelper = new THREE.SpotLightHelper( spotLight ); + scene.add( spotLightHelper ); + + +

أمثلة (Examples)

+

[example:webgl_lights_spotlights WebGL/ lights / spotlights ]

+ +

المنشئ (Constructor)

+ +

[name]( [param:SpotLight light], [param:Hex color] )

+

+ [page:SpotLight light] -- [page:SpotLight] المراد تصوره. +

+ + [page:Hex color] -- (اختياري) إذا لم يتم تعيين هذا ، فسيأخذ المساعد + لون الضوء. +

+ +

الخصائص (Properties)

+

انظر الفئة الأساسية [page:Object3D] للخصائص المشتركة.

+ +

[property:LineSegments cone]

+

[page:LineSegments] المستخدمة لتصور الضوء.

+ +

[property:SpotLight light]

+

إشارة إلى [page:SpotLight] المرئي.

+ +

[property:Object matrix]

+

إشارة إلى [page:Object3D.matrixWorld matrixWorld] الخاص بـ spotLight.

+ +

[property:Object matrixAutoUpdate]

+

+ انظر [page:Object3D.matrixAutoUpdate]. تعيين إلى `false` هنا كما يستخدم المساعد + [page:Object3D.matrixWorld matrixWorld] الخاص بـ spotLight. +

+ +

[property:hex color]

+

+ معلمة اللون الممررة في المنشئ. الافتراضي هو `undefined`. إذا + تغير هذا ، سيتحدث لون المساعد في المرة التالية + [page:.update update] يتم استدعاؤه. +

+ +

الطرق (Methods)

+

انظر الفئة الأساسية [page:Object3D] للطرق المشتركة.

+ +

[method:undefined dispose]()

+

+ تحرير الموارد ذات الصلة بوحدة معالجة الرسومات التي تم تخصيصها من قبل هذه الحالة. استدعاء هذه + الطريقة كلما لم يعد يستخدم هذا الحال في تطبيقك. +

+ +

[method:undefined update]()

+

تحديث مساعد الضوء.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + \ No newline at end of file diff --git a/docs/api/ar/lights/AmbientLight.html b/docs/api/ar/lights/AmbientLight.html new file mode 100644 index 00000000000000..9a257904840d92 --- /dev/null +++ b/docs/api/ar/lights/AmbientLight.html @@ -0,0 +1,53 @@ + + + + + + + + + + [page:Object3D] → [page:Light] → + +

[name]

+ +

+ يضيء هذا الضوء جميع الأشياء في المشهد بشكل متساوٍ على مستوى العالم.

+ + لا يمكن استخدام هذا الضوء لرمي الظلال لأنه لا يحتوي على اتجاه. +

+ +

مثال الكود

+ + + const light = new THREE.AmbientLight( 0x404040 ); // soft white light + scene.add( light ); + + +

المنشئ (Constructor)

+ +

[name]( [param:Integer color], [param:Float intensity] )

+

+ [page:Integer color] - (اختياري) قيمة عددية لمكون RGB من + اللون. الافتراضي هو 0xffffff.
+ [page:Float intensity] - (اختياري) قيمة عددية لـ + قوة / شدة الضوء. الافتراضي هو 1.

+ + ينشئ [name] جديدًا. +

+ +

الخصائص (Properties)

+

انظر الفئة الأساسية [page:Light Light] للخصائص المشتركة.

+ +

[property:Boolean isAmbientLight]

+

علامة للقراءة فقط للتحقق مما إذا كان الكائن المعطى هو من نوع [name].

+ +

الطرق (Methods)

+

انظر الفئة الأساسية [page:Light Light] للطرق المشتركة.

+

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/lights/DirectionalLight.html b/docs/api/ar/lights/DirectionalLight.html new file mode 100644 index 00000000000000..355dc7924558cd --- /dev/null +++ b/docs/api/ar/lights/DirectionalLight.html @@ -0,0 +1,146 @@ + + + + + + + + + + [page:Object3D] → [page:Light] → + +

[name]

+ +

+ ضوء ينبعث في اتجاه محدد. سيتصرف هذا الضوء كما لو كان بعيدًا لانهائيًا وأن الأشعة المنبعثة منه كلها + متوازية. الاستخدام الشائع لهذا هو محاكاة ضوء النهار ؛ الشمس + بعيد بما يكفي بحيث يمكن اعتبار موقعه لانهائيًا ، و + جميع أشعة الضوء القادمة منه متوازية.

+ + يمكن لهذا الضوء إلقاء الظلال - انظر صفحة [page:DirectionalLightShadow] + للتفاصيل. +

+ +

ملاحظة حول الموقف والهدف والدوران

+

+ نقطة شائعة من الالتباس بالنسبة للأضواء الاتجاهية هي أن تحديد + لا يؤثر التدوير. هذا لأن DirectionalLight في three.js هو + ما يعادل ما يسمى بـ "Target Direct Light" في غيره + التطبيقات.

+ + هذا يعني أن اتجاهه يتم حسابه كمؤشر من ضوء + [page:Object3D.position position] إلى [page:.target target] الموقف + (على عكس "Free Direct Light" التي لديها فقط دوران + مكون).

+ + السبب في ذلك هو السماح للضوء بإلقاء الظلال - ال + [page:.shadow shadow] تحتاج الكاميرا إلى موقف لحساب الظلال + من.

+ + انظر خاصية [page:.target target] أدناه للحصول على تفاصيل حول تحديث + الهدف. +

+ +

مثال للكود

+ + + // ضوء اتجاهي أبيض بشدة نصفية يشرق من الأعلى. + const directionalLight = new THREE.DirectionalLight( 0xffffff, 0.5 ); + scene.add( directionalLight ); + + +

أمثلة (Examples)

+

+ [example:misc_controls_fly controls / fly ]
+ [example:webgl_effects_parallaxbarrier effects / parallaxbarrier ]
+ [example:webgl_effects_stereo effects / stereo ]
+ [example:webgl_geometry_extrude_splines geometry / extrude / splines ]
+ [example:webgl_materials_bumpmap materials / bumpmap ] +

+ +

المنشئ (Constructor)

+ +

[name]( [param:Color color], [param:Float intensity] )

+

+ [page:Color color] - (اختياري) مثيل من Color ، سلسلة تمثل + لون أو رقم يمثل لونًا.
+ [page:Float intensity] - (اختياري) قيمة عددية لـ + شدة مسبار الضوء. الافتراضي هو 1.

+ + ينشئ [name] جديدًا. +

+ +

الخصائص (Properties)

+ +

انظر قائمة [page:Light Light] الأساسية للخصائص المشتركة.

+ +

[property:Boolean castShadow]

+

+ إذا تم تعيينه على `true` فإن الضوء سيلقي ظلالًا ديناميكية. تحذير: هذا هو + باهظ الثمن ويتطلب التلاعب لجعل الظلال تبدو صحيحة. انظر + [page:DirectionalLightShadow] للتفاصيل. الافتراضي هو `false`. +

+ +

[property:Boolean isDirectionalLight]

+

علامة للقراءة فقط للتحقق مما إذا كان الكائن المعطى هو من نوع [name].

+ +

[property:Vector3 position]

+

+ يتم تعيين هذا يساوي [page:Object3D.DEFAULT_UP] (0، 1، 0) ، بحيث + الضوء يشرق من الأعلى إلى الأسفل. +

+ +

[property:DirectionalLightShadow shadow]

+

+ [page:DirectionalLightShadow] يستخدم لحساب الظلال لهذا الضوء. +

+ +

[property:Object3D target]

+

+ يشير DirectionalLight من [page:.position position] إلى + target.position. الموضع الافتراضي للهدف هو `(0، 0، 0)`.
+ + ملاحظة: لتغيير موضع الهدف إلى أي شيء آخر غير + الافتراضي ، يجب إضافته إلى [page:Scene scene] باستخدام +

+ + scene.add( light.target ); + +

+ هذا حتى يتم تحديث [page:Object3D.matrixWorld matrixWorld] الخاص بالهدف + تلقائيًا كل إطار.

+ + من الممكن أيضًا تعيين الهدف ليكون كائنًا آخر في المشهد + (أي شيء يحتوي على خاصية [page:Object3D.position position]) ، مثل: +

+ + const targetObject = new THREE.Object3D(); + scene.add(targetObject); + + light.target = targetObject; + +

سيتبع directionalLight الكائن الهدف الآن.

+ +

الطرق (Methods)

+ +

انظر الفئة الأساسية [page:Light Light] للطرق المشتركة.

+ +

[method:undefined dispose]()

+

+ تحرير الموارد ذات الصلة بوحدة معالجة الرسومات التى تم تخصيصها من قبل هذه الحالة. استدعاء هذه + الطریقة كلما لم يعد يستخدم هذه الحال فى تطبیقك. +

+ +

[method:this copy]( [param:DirectionalLight source] )

+

+ ينسخ قيمة جمیع خصائص من [page:DirectionalLight source] + إلى هذا DirectionalLight. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/lights/HemisphereLight.html b/docs/api/ar/lights/HemisphereLight.html new file mode 100644 index 00000000000000..cfac6af9969049 --- /dev/null +++ b/docs/api/ar/lights/HemisphereLight.html @@ -0,0 +1,93 @@ + + + + + + + + + + [page:Object3D] → [page:Light] → + +

[name]

+ +

+ مصدر ضوء موضوع مباشرة فوق المشهد ، مع تلاشي اللون من + لون السماء إلى لون الأرض.

+ لا يمكن استخدام هذا الضوء لإلقاء الظلال. +

+ +

مثال للكود

+ + const light = new THREE.HemisphereLight( 0xffffbb, 0x080820, 1 ); + scene.add( light ); + + +

أمثلة (Examples)

+ +

+ [example:webgl_animation_skinning_blending animation / skinning / blending ]
+ [example:webgl_lights_hemisphere lights / hemisphere ]
+ [example:misc_controls_pointerlock controls / pointerlock ]
+ [example:webgl_loader_collada_kinematics loader / collada / kinematics ]
+ [example:webgl_loader_stl loader / stl ] +

+ +

المنشئ (Constructor)

+

+ [name]( [param:Integer skyColor], [param:Integer groundColor], + [param:Float intensity] ) +

+

+ [page:Integer skyColor] - (optional) لون سداسي عشري للسماء. الافتراضي + هو 0xffffff.
+ [page:Integer groundColor] - (optional) لون سداسي عشري للأرض. + الافتراضي هو 0xffffff.
+ [page:Float intensity] - (optional) قيمة رقمية لـ + قوة / شدة الضوء. الافتراضي هو 1.

+ + يخلق جديد [name]. +

+ +

الخصائص (Properties)

+

انظر قائمة [page:Light Light] الأساسية للخصائص المشتركة.

+ +

[property:Float color]

+

+ لون سماء الضوء ، كما تم تمريره في المُنشئ. الافتراضي هو جديد + [page:Color] محدد على الأبيض (0xffffff). +

+ +

[property:Float groundColor]

+

+ لون أرضية الضوء ، كما تم تمريره في المُنشئ. الافتراضي هو جديد + [page:Color] محدد على الأبيض (0xffffff). +

+ +

[property:Boolean isHemisphereLight]

+

علامة للقراءة فقط للتحقق مما إذا كان كائنًا معينًا من نوع [name].

+ +

[property:Vector3 position]

+

+ يتم تعيين هذا يساوي [page:Object3D.DEFAULT_UP] (0، 1، 0) ، بحيث + يشرق الضوء من أعلى إلى أسفل. +

+ +

الطرق (Methods)

+ +

انظر قائمة [page:Light Light] الأساسية للطرق المشتركة.

+ +

[method:this copy]( [param:HemisphereLight source] )

+

+ ينسخ قيمة [page:.color color] و[page:.intensity intensity] و + [page:.groundColor groundColor] من ضوء المصدر [page:Light source] إلى + هذا واحد. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/lights/Light.html b/docs/api/ar/lights/Light.html new file mode 100644 index 00000000000000..19b862c464b51b --- /dev/null +++ b/docs/api/ar/lights/Light.html @@ -0,0 +1,79 @@ + + + + + + + + + + [page:Object3D] → + +

[name]

+ +

+ الفئة الأساسية المجردة للأضواء - ترث جميع أنواع الأضواء الأخرى + الخصائص والطرق الموصوفة هنا. +

+ +

المنشئ (Constructor)

+ +

[name]( [param:Integer color], [param:Float intensity] )

+

+ [page:Integer color] - (اختياري) لون سداسي عشري للضوء. الافتراضي + هو 0xffffff (أبيض).
+ [page:Float intensity] - (اختياري) قيمة رقمية لـ + قوة / شدة الضوء. الافتراضي هو 1.

+ يخلق جديد [name]. يرجى ملاحظة أنه لا يُقصد منه أن يتم استدعاؤه مباشرةً + (استخدم واحدًا من الفئات المشتقة بدلاً من ذلك). +

+ +

الخصائص (Properties)

+

انظر قائمة [page:Object3D Object3D] الأساسية للخصائص المشتركة.

+ +

[property:Color color]

+

+ لون الضوء. يعود إلى جديد [page:Color] محدد على الأبيض ، إذا لم + تمرير في المُنشئ.
+

+ +

[property:Float intensity]

+

+ شدة الضوء ، أو قوته.
+ وحدات الشدة تعتمد على نوع الضوء.
+ الافتراضي - `1.0`. +

+ +

[property:Boolean isLight]

+

علامة للقراءة فقط للتحقق مما إذا كان كائنًا معينًا من نوع [name].

+ +

الطرق (Methods)

+

انظر قائمة [page:Object3D Object3D] الأساسية للطرق المشتركة.

+ +

[method:undefined dispose]()

+

+ طريقة التخلص المجردة للفئات التي تمتد هذه الفئة ؛ تنفذه + الفئات الفرعية التي لديها موارد قابلة للتصرف متعلقة بالجهاز. +

+ +

[method:this copy]( [param:Light source] )

+

+ ينسخ قيمة [page:.color color] و[page:.intensity intensity] + من ضوء المصدر [page:Light source] إلى + هذا واحد. +

+ +

[method:Object toJSON]( [param:Object meta] )

+

+ meta - كائن يحتوي على بيانات تعريفية مثل المواد والقوام لـ + objects.
+ حول الضوء إلى three.js + [link:https://github.com/mrdoob/three.js/wiki/JSON-Object-Scene-format-4 JSON Object/Scene format]. +

+

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/lights/LightProbe.html b/docs/api/ar/lights/LightProbe.html new file mode 100644 index 00000000000000..8fbecfda89b000 --- /dev/null +++ b/docs/api/ar/lights/LightProbe.html @@ -0,0 +1,78 @@ + + + + + + + + + + [page:Object3D] → [page:Light] → + +

[name]

+ +

+ مسابير الضوء هي طريقة بديلة لإضافة الضوء إلى مشهد ثلاثي الأبعاد. على عكس + مصادر الضوء الكلاسيكية (على سبيل المثال الاتجاهية ، النقطية أو الأضواء الموجهة) ، الضوء + لا تنبعث المسابير. بدلاً من ذلك ، يخزنون معلومات حول الضوء + يمر عبر المساحة ثلاثية الأبعاد. أثناء التقديم ، يتم تقريب الضوء الذي يضرب 3D + الكائن باستخدام بيانات من مسبار الضوء. +

+ +

+ عادةً ما يتم إنشاء مسابير الضوء من خرائط البيئة (الإشعاع). جناح + [page:LightProbeGenerator] يمكن استخدامه لإنشاء مسابير ضوء من + مثيلات [page:CubeTexture] أو [page:WebGLCubeRenderTarget]. ومع ذلك، + يمكن توفير بيانات تقدير الضوء أيضًا في أشكال أخرى على سبيل المثال من قبل WebXR. + هذا يتيح تقديم محتوى الواقع المعزز الذي يتفاعل مع + إضاءة العالم الحقيقي. +

+ +

+ تدعم تطبيق المسبار الحالي في three.js ما يسمى بـ + مسابير ضوء ناعمة. هذا النوع من مسبار الضوء هو وظيفيًا مكافئ لـ + خريطة بيئة إشعاعية. +

+ +

أمثلة (Examples)

+

+ [example:webgl_lightprobe WebGL / light probe ]
+ [example:webgl_lightprobe_cubecamera WebGL / light probe / cube camera ] +

+ +

المنشئ (Constructor)

+ +

[name]( [param:SphericalHarmonics3 sh], [param:Float intensity] )

+

+ [page:SphericalHarmonics3 sh] - (اختياري) مثيل من + [page:SphericalHarmonics3].
+ [page:Float intensity] - (اختياري) قيمة رقمية لـ + شدة مسبار الضوء. الافتراضي هو 1.

+ + يخلق جديد [name]. +

+ +

الخصائص (Properties)

+

+ انظر قائمة [page:Light Light] الأساسية للخصائص المشتركة. جناح + خاصية [page:Light.color color] لا يتم تقديرها حاليًا وبالتالي لديها + لا تأثير. +

+ +

[property:Boolean isLightProbe]

+

علامة للقراءة فقط للتحقق مما إذا كان كائنًا معينًا من نوع [name].

+ +

[property:SphericalHarmonics3 sh]

+

+ يستخدم مسبار الضوء التجانسات المجالية لتشفير معلومات التحديد. +

+ +

الطرق (Methods)

+

انظر قائمة [page:Light Light] الأساسية للطرق المشتركة.

+

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/lights/PointLight.html b/docs/api/ar/lights/PointLight.html new file mode 100644 index 00000000000000..6cd522a39f09f0 --- /dev/null +++ b/docs/api/ar/lights/PointLight.html @@ -0,0 +1,131 @@ + + + + + + + + + + [page:Object3D] → [page:Light] → + +

[name]

+ +

+ ضوء ينبعث من نقطة واحدة في جميع الاتجاهات. حالة استخدام شائعة لهذا هي تكرار الضوء الذي ينبعث من مصباح عاري + .

+ + يمكن لهذا الضوء إلقاء الظلال - انظر صفحة [page:PointLightShadow] للحصول على + تفاصيل. +

+ +

مثال للكود

+ + + const light = new THREE.PointLight( 0xff0000, 1, 100 ); + light.position.set( 50, 50, 50 ); + scene.add( light ); + + +

أمثلة (Examples)

+ +

+ [example:webgpu_lights_pointlights lights / pointlights ]
+ [example:webgl_effects_anaglyph effects / anaglyph ]
+ [example:webgl_geometry_text geometry / text ]
+ [example:webgl_lensflares lensflares ] +

+ +

المنشئ (Constructor)

+ +

+ [name]( [param:Integer color], [param:Float intensity], [param:Number distance], [param:Float decay] ) +

+

+ [page:Integer color] - (اختياري) لون سداسي عشري للضوء. الافتراضي + هو 0xffffff (أبيض).
+ [page:Float intensity] - (اختياري) قيمة رقمية لقوة / شدة الضوء. + الافتراضي هو 1.
+ [page:Number distance] - المدى الأقصى للضوء. الافتراضي هو 0 (لا + حدود).
+ [page:Float decay] - مقدار تضاؤل الضوء على طول مسافة الضوء. + الافتراضي هو 2.

+ + إنشاء جديد [name]. +

+ +

الخصائص (Properties)

+

انظر إلى قائمة [page:Light Light] للخصائص المشتركة.

+ +

[property:Boolean castShadow]

+

+ إذا تم تعيينه إلى `true`، ستلقي الضوء ظلالًا ديناميكية. تحذير: هذا مكلف ويتطلب التعديل للحصول على الظلال المناسبة. راجع [page:PointLightShadow] للحصول على التفاصيل. الافتراضي هو `false`. +

+ +

[property:Float decay]

+

+ مقدار تضاؤل الضوء على طول مسافة الضوء. القيمة الافتراضية هي + `2`.
+ في سياق التصيير الفعلي الصحيح ، يجب عدم تغيير القيمة الافتراضية. +

+ +

[property:Float distance]

+

+ عندما تكون المسافة صفرًا ، سيتلاشى الضوء وفقًا لقانون المربع المعكوس + إلى مسافة لانهائية. عندما تكون المسافة غير صفرية ، سيتلاشى الضوء + وفقًا لقانون المربع المعكوس حتى قرب نقطة قطع المسافة ، حيث + سيتلاشى بسرعة وبسلاسة إلى 0. بطبعه، ليست نقطة القطع فيزيائية صحيحة. +

+

القيمة الافتراضية هي `0.0`.

+ +

[property:Float intensity]

+

+ شدة الضوء. القيمة الافتراضية هي `1`.
+ الشدة هي شدة إضاءة الضوء المقاسة بالشمعات + (cd).

+ + تغيير الشدة سيغير أيضًا قوة الضوء. +

+ +

[property:Float power]

+

+ قوة الضوء.
+ الطاقة هي قوة إضاءة الضوء المقاسة باللومن (lm). +

+ + تغيير الطاقة سيغير أيضًا شدة الضوء. +

+ +

[property:PointLightShadow shadow]

+

+ [page:PointLightShadow] يستخدم لحساب ظلال هذا الضوء.

+ + [page:LightShadow.camera كاميرا] lightShadow مُعَدَّلَهُ إلى + [page:PerspectiveCamera] مع [page:PerspectiveCamera.fov fov] من 90، + [page:PerspectiveCamera.aspect aspect] من 1، [page:PerspectiveCamera.near near] + clipping plane at 0.5 and [page:PerspectiveCamera.far far] clipping + plane at 500. +

+ +

الطرق (Methods)

+

انظر إلى قائمة [page:Light Light] للطرق المشتركة.

+ +

[method:undefined dispose]()

+

+ تحرير المصادر المتعلقة بالجهاز GPU التي تم تخصيصها من قبل هذه المثيل. اتصل بهذه + الطريقة كلما لم يُستخدَم هذه المثيل في تطبيقك. +

+ +

[method:this copy]( [param:PointLight source] )

+

+ نسخ قِيَم جميع خصائص [page:PointLight source] إلى + هذه PointLight. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/lights/RectAreaLight.html b/docs/api/ar/lights/RectAreaLight.html new file mode 100644 index 00000000000000..3d25e2e7f16775 --- /dev/null +++ b/docs/api/ar/lights/RectAreaLight.html @@ -0,0 +1,109 @@ + + + + + + + + + + [page:Object3D] → [page:Light] → + +

[name]

+ +

+ ينبعث RectAreaLight بالضوء بشكل موحد عبر وجه سطح مستطيل. يمكن استخدام هذا النوع من الضوء لمحاكاة مصادر الضوء مثل النوافذ المشرقة أو الإضاءة المخططة.

+ + ملاحظات هامة: +

+
    +
  • لا يوجد دعم للظلال.
  • +
  • + يتم دعم [page:MeshStandardMaterial MeshStandardMaterial] و + [page:MeshPhysicalMaterial MeshPhysicalMaterial] فقط. +
  • +
  • + يجب عليك تضمين + [link:https://threejs.org/examples/jsm/lights/RectAreaLightUniformsLib.js RectAreaLightUniformsLib] في مشهدك واستدعاء `init()`. +
  • +
+ +

مثال للكود

+ + + const width = 10; + const height = 10; + const intensity = 1; + const rectLight = new THREE.RectAreaLight( 0xffffff, intensity, width, height ); + rectLight.position.set( 5, 5, 0 ); + rectLight.lookAt( 0, 0, 0 ); + scene.add( rectLight ) + + const rectLightHelper = new RectAreaLightHelper( rectLight ); + rectLight.add( rectLightHelper ); + + +

أمثلة (Examples)

+ +

[example:webgl_lights_rectarealight WebGL / rectarealight ]

+ +

المنشئ (Constructor)

+ +

+ [name]( [param:Integer color], [param:Float intensity], [param:Float width], [param:Float height] ) +

+

+ [page:Integer color] - (اختياري) لون سداسي عشري للضوء. الافتراضي + هو 0xffffff (أبيض).
+ [page:Float intensity] - (اختياري) شدة الضوء ، أو سطوعه. + الافتراضي هو 1.
+ [page:Float width] - (اختياري) عرض الضوء. الافتراضي هو 10.
+ [page:Float height] - (اختياري) ارتفاع الضوء. الافتراضي هو 10.

+ + إنشاء جديد [name]. +

+ +

الخصائص (Properties)

+

انظر إلى قائمة [page:Light Light] للخصائص المشتركة.

+ +

[property:Float height]

+

ارتفاع الضوء.

+ +

[property:Float intensity]

+

+ شدة الضوء. القيمة الافتراضية هي `1`.
+ الشدة هي الإضاءة (السطوع) للضوء المقاسة بالنيتات + (cd/m^2).

+ + تغيير الشدة سيغير أيضًا قوة الضوء. +

+ +

[property:Boolean isRectAreaLight]

+

علامة للقراءة فقط للتحقق مما إذا كان كائنًا معينًا من نوع [name].

+ +

[property:Float power]

+

+ قوة الضوء.
+ الطاقة هي قوة إضاءة الضوء المقاسة باللومن (lm). +

+ + تغيير الطاقة سيغير أيضًا شدة الضوء. +

+ +

[property:Float width]

+

عرض الضوء.

+ +

الطرق (Methods)

+

انظر إلى قائمة [page:Light Light] للطرق المشتركة.

+ +

[method:this copy]( [param:RectAreaLight source] )

+

+ نسخ قِيَم جميع خصائص [page:RectAreaLight source] إلى + هذه RectAreaLight. +

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/lights/SpotLight.html b/docs/api/ar/lights/SpotLight.html new file mode 100644 index 00000000000000..bef43422d032cf --- /dev/null +++ b/docs/api/ar/lights/SpotLight.html @@ -0,0 +1,180 @@ + + + + + + + + + + [page:Object3D] → [page:Light] → + +

[name]

+ +

+ يتم إصدار هذا الضوء من نقطة واحدة في اتجاه واحد ، على طول مخروط + يزداد حجمه كلما ابتعد عن الضوء.

+ يمكن لهذا الضوء إلقاء الظلال - انظر صفحة [page:SpotLightShadow] للحصول على + تفاصيل. +

+ +

مثال للكود

+ + // مصباح كاشف أبيض يشرق من الجانب ، يتم تعديله بواسطة ملمس ، يلقي ظلاً + + const spotLight = new THREE.SpotLight( 0xffffff ); + spotLight.position.set( 100, 1000, 100 ); + spotLight.map = new THREE.TextureLoader().load( url ); + + spotLight.castShadow = true; + + spotLight.shadow.mapSize.width = 1024; + spotLight.shadow.mapSize.height = 1024; + + spotLight.shadow.camera.near = 500; + spotLight.shadow.camera.far = 4000; + spotLight.shadow.camera.fov = 30; + + scene.add( spotLight ); + + +

أمثلة (Examples)

+

+ [example:webgl_lights_spotlight lights / spotlight ]
+ [example:webgl_lights_spotlights lights / spotlights ] +

+ +

المنشئ (Constructor)

+

+ [name]( [param:Integer color], [param:Float intensity], [param:Float distance], [param:Radians angle], [param:Float penumbra], [param:Float decay] ) +

+

+ [page:Integer color] - (اختياري) لون سداسي عشري للضوء. الافتراضي + هو 0xffffff (أبيض).
+ [page:Float intensity] - (اختياري) قيمة رقمية لقوة / شدة الضوء. + الافتراضي هو 1.
+ [page:Float distance] - المدى الأقصى للضوء. الافتراضي هو 0 (لا + حدود).
+ [page:Radians angle] - أقصى زاوية لانتشار الضوء من + اتجاهه الذي يعتبر حدًا أعلى Math.PI/2.
+ [page:Float penumbra] - نسبة مخروط الضوء الذي يتلاشى + بسبب الظلال الجانبية. يأخذ قيم بين الصفر و 1. الافتراضي هو صفر.
+ [page:Float decay] - مقدار تضاؤل الضوء على طول مسافة ال + ضوء.

+ إنشاء جديد [name]. +

+ +

الخصائص (Properties)

+

انظر إلى قائمة [page:Light Light] للخصائص المشتركة.

+ +

[property:Float angle]

+

+ أقصى مدى للضوء الكاشف ، بالراديان ، من اتجاهه. يجب أن يكون + لا يزيد عن `Math.PI/2`. القيمة الافتراضية هي `Math.PI/3`. +

+ +

[property:Boolean castShadow]

+

+ إذا تم تعيينه على `true` ، فسيقوم الضوء بإلقاء ظلال ديناميكية. تحذير: هذا + مكلف ويتطلب التعديل لجعل الظلال تبدو صحيحة. انظر + [page:SpotLightShadow] للحصول على التفاصيل. الافتراضي هو `false`. +

+ +

[property:Float decay]

+

+ مقدار تضاؤل الضوء على طول مسافة الضوء. القيمة الافتراضية هي + `2`.
+ في سياق التصيير الفعلي الصحيح ، يجب عدم تغيير القيمة الافتراضية. +

+ +

[property:Float distance]

+

+ عندما تكون المسافة صفرًا ، سيتلاشى الضوء وفقًا لقانون المربع المعكوس + إلى مسافة لانهائية. عندما تكون المسافة غير صفرية ، سيتلاشى الضوء + وفقًا لقانون المربع المعكوس حتى قرب نقطة قطع المسافة ، حيث + سيتلاشى بسرعة وبسلاسة إلى `0`. بطبعه، ليست نقطة القطع فيزيائية صحيحة. +

+

القيمة الافتراضية هى `0.0`.

+ +

[property:Float intensity]

+

+ شدة الضوء. القيمة الافتراضية هي `1`.
+ الشدة هى شدة إضاءة الضوء المقاسة بالشمعات(cd).

+ تغيير الشدة سيغير أيضًا قوة الضوء. +

+ +

[property:Boolean isSpotLight]

+

علامة للقراءة فقط للتحقق مما إذا كان كائنًا معينًا من نوع [name].

+ +

[property:Float penumbra]

+

+ نسبة مخروط الضوء الذي يتلاشى بسبب الظلال الجانبية. يأخذ + قيم بين الصفر و 1. القيمة الافتراضية هي `0.0`. +

+ +

[property:Vector3 position]

+

+ يتم تعيين هذا مساويًا لـ [page:Object3D.DEFAULT_UP] (0، 1، 0) ، بحيث + الضوء يشرق من الأعلى إلى الأسفل. +

+ +

[property:Float power]

+

+ قوة الضوء.
+ الطاقة هي قوة إضاءة الضوء المقاسة باللومن (lm).

+ تغيير الطاقة سيغير أيضًا شدة الضوء. +

+ +

[property:SpotLightShadow shadow]

+

[page:SpotLightShadow] يستخدم لحساب ظلال هذا الضوء.

+ +

[property:Object3D target]

+

+ يشير Spotlight من [page:.position position] إلى + target.position. موقع الهدف الافتراضي هو `(0، 0، 0)`.
+ ملاحظة: لتغيير موقع الهدف إلى أي شيء آخر غير + الافتراضي ، يجب إضافته إلى [page:Scene scene] باستخدام + scene.add( light.target ); + هذا حتى يتم تحديث [page:Object3D.matrixWorld matrixWorld] للهدف + تلقائيًا في كل إطار.

+ من الممكن أيضًا تعيين الهدف لكونه كائنًا آخر في المشهد + (أي شيء لديه خاصية [page:Object3D.position position]) ، مثل: + + const targetObject = new THREE.Object3D(); + scene.add(targetObject); + + light.target = targetObject; + + سوف يتبع المصباح الكاشف الآن كائن الهدف. +

+ +

[property:Texture map]

+

+ [page:Texture] يستخدم لتعديل لون الضوء. يتم خلط لون المصباح + مع قيمة RGB لهذه الملمس ، بنسبة + تتوافق مع قيمة ألفا. يتم تكرار التأثير المشابه للكعكة باستخدام قِيَم بكسل (0، 0، 0، 1-قِيَم_الكعكة). تحذير: + [page:.map] مُعَطَّلَ إذا كان [page:.castShadow] + خطأ. +

+ +

الطرق (Methods)

+ +

انظر إلى قائمة [page:Light Light] للطرق المشتركة.

+ +

[method:undefined dispose]()

+

+ تحرير المصادر المتعلقة بالجهاز GPU التي تم تخصيصها من قبل هذه المثيل. اتصل بهذه + الطريقة كلما لم يُستخدَم هذه المثيل في تطبيقك. +

+ +

[method:this copy]( [param:SpotLight source] )

+

+ نسخ قِيَم جميع خصائص [page:SpotLight source] إلى + هذه SpotLight. +

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/lights/shadows/DirectionalLightShadow.html b/docs/api/ar/lights/shadows/DirectionalLightShadow.html new file mode 100644 index 00000000000000..4df6f3a12b0745 --- /dev/null +++ b/docs/api/ar/lights/shadows/DirectionalLightShadow.html @@ -0,0 +1,101 @@ + + + + + + + + + + [page:LightShadow] → + +

[name]

+ +

+ يتم استخدام هذا داخليًا من قبل [page:DirectionalLight DirectionalLights] لـ + حساب الظلال.

+ + على عكس فئات الظلال الأخرى ، يستخدم هذا [page:OrthographicCamera] لـ + حساب الظلال ، بدلاً من [page:PerspectiveCamera]. هذا هو + لأن أشعة الضوء من [page:DirectionalLight] متوازية. +

+ +

مثال الكود

+ + + //Create a WebGLRenderer and turn on shadows in the renderer + const renderer = new THREE.WebGLRenderer(); + renderer.shadowMap.enabled = true; + renderer.shadowMap.type = THREE.PCFSoftShadowMap; // default THREE.PCFShadowMap + + //Create a DirectionalLight and turn on shadows for the light + const light = new THREE.DirectionalLight( 0xffffff, 1 ); + light.position.set( 0, 1, 0 ); //default; light shining from top + light.castShadow = true; // default false + scene.add( light ); + + //Set up shadow properties for the light + light.shadow.mapSize.width = 512; // default + light.shadow.mapSize.height = 512; // default + light.shadow.camera.near = 0.5; // default + light.shadow.camera.far = 500; // default + + //Create a sphere that cast shadows (but does not receive them) + const sphereGeometry = new THREE.SphereGeometry( 5, 32, 32 ); + const sphereMaterial = new THREE.MeshStandardMaterial( { color: 0xff0000 } ); + const sphere = new THREE.Mesh( sphereGeometry, sphereMaterial ); + sphere.castShadow = true; //default is false + sphere.receiveShadow = false; //default + scene.add( sphere ); + + //Create a plane that receives shadows (but does not cast them) + const planeGeometry = new THREE.PlaneGeometry( 20, 20, 32, 32 ); + const planeMaterial = new THREE.MeshStandardMaterial( { color: 0x00ff00 } ) + const plane = new THREE.Mesh( planeGeometry, planeMaterial ); + plane.receiveShadow = true; + scene.add( plane ); + + //Create a helper for the shadow camera (optional) + const helper = new THREE.CameraHelper( light.shadow.camera ); + scene.add( helper ); + + +

المنشئ (Constructor)

+

[name]( )

+

+ ينشئ [name] جديدًا. لا يُقصد من هذا الاتصال مباشرة - هو + يتم استدعاؤه داخليًا من قبل [page:DirectionalLight]. +

+ +

الخصائص (Properties)

+

+ انظر الفئة الأساسية [page:LightShadow LightShadow] للخصائص المشتركة. +

+ +

[property:Camera camera]

+

+ رؤية الضوء للعالم. يتم استخدام هذا لإنشاء خريطة عمق لـ + المشهد ؛ الأشياء خلف الأشياء الأخرى من منظور الضوء ستكون + في الظل.

+ + الافتراضي هو [page:OrthographicCamera] مع + [page:OrthographicCamera.left left] و [page:OrthographicCamera.bottom bottom] + مضبوط على -5 ، [page:OrthographicCamera.right right] و + [page:OrthographicCamera.top top] مضبوط على 5 ، + [page:OrthographicCamera.near near] كليبينغ بلان عند 0.5 و + [page:OrthographicCamera.far far] كليبينغ بلان عند 500. +

+ +

[property:Boolean isDirectionalLightShadow]

+

علامة للقراءة فقط للتحقق مما إذا كان الكائن المعطى هو من نوع [name].

+ +

الطرق (Methods)

+

انظر الفئة الأساسية [page:LightShadow LightShadow] للطرق المشتركة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/lights/[name].js src/lights/[name].js] +

+ + diff --git a/docs/api/ar/lights/shadows/LightShadow.html b/docs/api/ar/lights/shadows/LightShadow.html new file mode 100644 index 00000000000000..2d0eca0e02d43d --- /dev/null +++ b/docs/api/ar/lights/shadows/LightShadow.html @@ -0,0 +1,170 @@ + + + + + + + + + +

[name]

+ +

يعمل كفئة أساسية لفئات الظلال الأخرى.

+ +

المنشئ (Constructor)

+ +

[name]( [param:Camera camera] )

+

+ [page:Camera camera] - رؤية الضوء للعالم.

+ + إنشاء [name] جديد. لا يُقصد من هذا الاتصال مباشرة - هو + يستخدم كفئة أساسية من قبل ظلال الضوء الأخرى. +

+ +

الخصائص (Properties)

+ +

[property:Boolean autoUpdate]

+

+ يتيح التحديثات التلقائية لظل الضوء. الافتراضي هو `true`. إذا كنت + لا تتطلب إضاءة / ظلال ديناميكية ، يمكنك تعيين هذا على `false`. +

+ +

[property:Camera camera]

+

+ رؤية الضوء للعالم. يتم استخدام هذا لإنشاء خريطة عمق لـ + المشهد ؛ الأشياء خلف الأشياء الأخرى من منظور الضوء ستكون + في الظل. +

+ +

[property:Float bias]

+

+ انحياز خريطة الظل ، كم يجب إضافة أو طرح من العمق المعاد تدويره + عند تحديد ما إذا كانت سطحًا في الظل.
+ الافتراضي هو 0. قد تساعد التعديلات الصغيرة جدًا هنا (بترتيب 0.0001) + تقليل التحف في الظلال +

+ +

[property:Integer blurSamples]

+

عدد العينات المستخدمة عند طمس خريطة ظل VSM.

+ +

[property:Float intensity]

+

+ The intensity of the shadow. The default is `1`. Valid values are in the range `[0, 1]`. +

+ +

[property:WebGLRenderTarget map]

+

+ خريطة العمق التي تم إنشاؤها باستخدام الكاميرا الداخلية ؛ موقع خارج + عمق بكسل في الظل. يتم حسابه داخليًا أثناء التقديم. +

+ +

[property:WebGLRenderTarget mapPass]

+

+ خريطة التوزيع التي تم إنشاؤها باستخدام الكاميرا الداخلية ؛ يتم حساب احتجاب + بناءً على توزيع الأعماق. يتم حسابه داخليًا أثناء + التقديم. +

+ +

[property:Vector2 mapSize]

+

+ [Page:Vector2] يحدد عرض وارتفاع خريطة الظل.

+ + تعطي قيم أعلى جودة أفضل للظلال بتكلفة وقت الحساب. + يجب أن تكون قيم قوى 2 ، حتى + [page:WebGLRenderer.capabilities].maxTextureSize لجهاز معين, + على الرغم من أنه لا يجب أن يكون نفسه (ولذلك ، على سبيل المثال ، + (512، 1024) صالح). الافتراضي هو * (512، 512) *. +

+ +

[property:Matrix4 matrix]

+

+ نموذج لمساحة الكاميرا الظلية ، لحساب الموقع والعمق في خريطة الظل. + تخزين في [page:Matrix4 Matrix4]. يتم حساب هذا داخليًا أثناء + التقديم. +

+ +

[property:Boolean needsUpdate]

+

+ عند تعيينه على `true` ، سيتم تحديث خرائط الظل في الاتصال التالي `render`. + الافتراضي هو `false`. إذا قمت بتعيين [page:.autoUpdate] على `false` ، فأنت + سوف تحتاج إلى تعيين هذه الخاصية على `true` ثم إجراء مكالمة تقديم لـ + تحديث ظل الضوء. +

+ +

[property:Float normalBias]

+

+ يحدد كمية الموضع المستخدم للاستعلام عن خريطة الظل معوضًا عن + الكائن الطبيعي. الافتراضي هو 0. يمكن استخدام زيادة هذه القيمة لـ + تقليل حب الشباب في الظلال خاصة في المشاهد الكبيرة التي يضيء فيها الضوء + الهندسة بزاوية ضحلة. التكلفة هي أن الظلال قد تبدو + مشوه. +

+ +

[property:Float radius]

+

+ إعداد هذه القيم إلى قيم أكبر من 1 ستطمس حواف + الظل.
+ + ستسبب قيم عالية تأثيرات تجزئة غير مرغوب فيها في الظلال - أكبر + [page:.mapSize mapSize] ستسمح بقيمة أعلى للاستخدام هنا + قبل أن تصبح هذه التأثيرات مرئية.
+ إذا تم تعيين [page:WebGLRenderer.shadowMap.type] على [page:Renderer PCFSoftShadowMap]، + لا يوجد لـ radius أثر و يُفضل زيادة + النعومة عن طريق تقليل [page:.mapSize mapSize] بدلاً من ذلك.

+ + لاحظ أن هذا لا يؤثر إذا كان [page:WebGLRenderer.shadowMap.type] هو + تعيين إلى [page:Renderer BasicShadowMap]. +

+ +

الطرق (Methods)

+ +

[method:Vector2 getFrameExtents]()

+

+ تستخدم داخليًا من قبل المصور لتوسيع خريطة الظل لتحتوى على جميع + viewports +

+ +

[method:undefined updateMatrices]( [param:Light light] )

+

+ تحديث المصفوفات للكاميرا والظل ، يستخدم داخليًا من قبل المصور.

+ + light -- الضوء الذي يتم تقديم الظل له. +

+ +

[method:Frustum getFrustum]()

+

+ تحصل على فروستوم كاميرات الظل. يستخدم داخليًا من قبل المصور لإزالة + الأشياء. +

+ +

[method:number getViewportCount]()

+

+ تستخدم داخليًا من قبل المصور للحصول على عدد viewports التى تحتاج + إلى التقديم لهذا الظل. +

+ +

[method:undefined dispose]()

+

+ تحرير الموارد ذات الصلة بوحدة معالجة الرسومات التى تم تخصيصها من قبل هذه الحالة. استدعاء هذه + الطریقة كلما لم يعد يستخدم هذه الحال فى تطبیقك. +

+ +

[method:this copy]( [param:LightShadow source] )

+

+ ينسخ قيمة جميع الخصائص من [page:LightShadow source] إلى + هذا الضوء. +

+ +

[method:LightShadow clone]()

+

ينشئ LightShadow جديد بنفس خصائص هذا.

+ +

[method:Object toJSON]()

+

سيريالايز هذا LightShadow.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/lights/[name].js src/lights/[name].js] +

+ + diff --git a/docs/api/ar/lights/shadows/PointLightShadow.html b/docs/api/ar/lights/shadows/PointLightShadow.html new file mode 100644 index 00000000000000..aadee02bebcf8f --- /dev/null +++ b/docs/api/ar/lights/shadows/PointLightShadow.html @@ -0,0 +1,94 @@ + + + + + + + + + + [page:LightShadow] → + +

[name]

+ +

+ يتم استخدام هذا داخليًا من قبل [page:PointLight PointLights] لحساب + الظلال. +

+ +

مثال الكود

+ + + //Create a WebGLRenderer and turn on shadows in the renderer + const renderer = new THREE.WebGLRenderer(); + renderer.shadowMap.enabled = true; + renderer.shadowMap.type = THREE.PCFSoftShadowMap; // default THREE.PCFShadowMap + + //Create a PointLight and turn on shadows for the light + const light = new THREE.PointLight( 0xffffff, 1, 100 ); + light.position.set( 0, 10, 4 ); + light.castShadow = true; // default false + scene.add( light ); + + //Set up shadow properties for the light + light.shadow.mapSize.width = 512; // default + light.shadow.mapSize.height = 512; // default + light.shadow.camera.near = 0.5; // default + light.shadow.camera.far = 500; // default + + //Create a sphere that cast shadows (but does not receive them) + const sphereGeometry = new THREE.SphereGeometry( 5, 32, 32 ); + const sphereMaterial = new THREE.MeshStandardMaterial( { color: 0xff0000 } ); + const sphere = new THREE.Mesh( sphereGeometry, sphereMaterial ); + sphere.castShadow = true; //default is false + sphere.receiveShadow = false; //default + scene.add( sphere ); + + //Create a plane that receives shadows (but does not cast them) + const planeGeometry = new THREE.PlaneGeometry( 20, 20, 32, 32 ); + const planeMaterial = new THREE.MeshStandardMaterial( { color: 0x00ff00 } ) + const plane = new THREE.Mesh( planeGeometry, planeMaterial ); + plane.receiveShadow = true; + scene.add( plane ); + + //Create a helper for the shadow camera (optional) + const helper = new THREE.CameraHelper( light.shadow.camera ); + scene.add( helper ); + + +

المنشئ (Constructor)

+

[name]( )

+

+ ينشئ [name] جديدًا. لا يُقصد من هذا الاتصال مباشرة - هو + يتم استدعاؤه داخليًا من قبل [page:PointLight]. +

+ +

الخصائص (Properties)

+

+ انظر الفئة الأساسية [page:LightShadow LightShadow] للخصائص المشتركة. +

+ +

[property:Boolean isPointLightShadow]

+

علامة للقراءة فقط للتحقق مما إذا كان الكائن المعطى هو من نوع [name].

+ +

الطرق (Methods)

+ +

انظر الفئة الأساسية [page:LightShadow LightShadow] للطرق المشتركة.

+ +

+ [method:undefined updateMatrices]( [param:Light light], [param:number viewportIndex]) +

+

+ تحديث المصفوفات للكاميرا والظل ، يستخدم داخليًا من قبل المصور.

+ + light -- الضوء الذي يتم تقديم الظل له.
+ viewportIndex -- يحسب المصفوفة لهذا viewport +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/lights/[name].js src/lights/[name].js] +

+ + diff --git a/docs/api/ar/lights/shadows/SpotLightShadow.html b/docs/api/ar/lights/shadows/SpotLightShadow.html new file mode 100644 index 00000000000000..8c41bf052079b0 --- /dev/null +++ b/docs/api/ar/lights/shadows/SpotLightShadow.html @@ -0,0 +1,106 @@ + + + + + + + + + + [page:LightShadow] → + +

[name]

+ +

+ يتم استخدام هذا داخليًا من قبل [page:SpotLight SpotLights] لحساب + الظلال. +

+ +

مثال الكود

+ + //Create a WebGLRenderer and turn on shadows in the renderer + const renderer = new THREE.WebGLRenderer(); + renderer.shadowMap.enabled = true; + renderer.shadowMap.type = THREE.PCFSoftShadowMap; // default THREE.PCFShadowMap + + //Create a SpotLight and turn on shadows for the light + const light = new THREE.SpotLight( 0xffffff ); + light.castShadow = true; // default false + scene.add( light ); + + //Set up shadow properties for the light + light.shadow.mapSize.width = 512; // default + light.shadow.mapSize.height = 512; // default + light.shadow.camera.near = 0.5; // default + light.shadow.camera.far = 500; // default + light.shadow.focus = 1; // default + + //Create a sphere that cast shadows (but does not receive them) + const sphereGeometry = new THREE.SphereGeometry( 5, 32, 32 ); + const sphereMaterial = new THREE.MeshStandardMaterial( { color: 0xff0000 } ); + const sphere = new THREE.Mesh( sphereGeometry, sphereMaterial ); + sphere.castShadow = true; //default is false + sphere.receiveShadow = false; //default + scene.add( sphere ); + + //Create a plane that receives shadows (but does not cast them) + const planeGeometry = new THREE.PlaneGeometry( 20, 20, 32, 32 ); + const planeMaterial = new THREE.MeshStandardMaterial( { color: 0x00ff00 } ) + const plane = new THREE.Mesh( planeGeometry, planeMaterial ); + plane.receiveShadow = true; + scene.add( plane ); + + //Create a helper for the shadow camera (optional) + const helper = new THREE.CameraHelper( light.shadow.camera ); + scene.add( helper ); + + +

المنشئ (Constructor)

+ +

+ ينشئ المنشئ [param:PerspectiveCamera PerspectiveCamera] لـ + إدارة رؤية الظل للعالم. +

+ +

الخصائص (Properties)

+

+ انظر الفئة الأساسية [page:LightShadow LightShadow] للخصائص المشتركة. +

+ +

[property:Camera camera]

+

+ رؤية الضوء للعالم. يتم استخدام هذا لإنشاء خريطة عمق لـ + المشهد ؛ الأشياء خلف الأشياء الأخرى من منظور الضوء ستكون + في الظل.

+ + الافتراضي هو [page:PerspectiveCamera] مع + [page:PerspectiveCamera.near near] كليبينغ بلان عند `0.5`. ال + [page:PerspectiveCamera.fov fov] ستتبع [page:SpotLight.angle angle] + خاصية [page:SpotLight SpotLight] المملوكة عبر + [page:SpotLightShadow.update update] طريقة. بالمثل ، + [page:PerspectiveCamera.aspect aspect] خاصية ستتبع نسبة + [page:LightShadow.mapSize mapSize]. إذا تم تعيين [page:SpotLight.distance distance] + خاصية الضوء ، فستتبع [page:PerspectiveCamera.far far] + كليبينغ بلان ذلك ، وإلا فإنه يفترض `500`. +

+ +

[property:Number focus]

+

+ تستخدم لتركيز كاميرا الظل. يتم تعيين حقل رؤية الكاميرا كـ + نسبة مئوية من حقل رؤية المصباح. المدى هو `[0، 1]`. الافتراضي هو + `1.0`.
+

+ +

[property:Boolean isSpotLightShadow]

+

علامة للقراءة فقط للتحقق مما إذا كان الكائن المعطى هو من نوع [name].

+ +

الطرق (Methods)

+

انظر الفئة الأساسية [page:LightShadow LightShadow] للطرق المشتركة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/lights/[name].js src/lights/[name].js] +

+ + diff --git a/docs/api/ar/loaders/AnimationLoader.html b/docs/api/ar/loaders/AnimationLoader.html new file mode 100644 index 00000000000000..dc20e0b3acb67c --- /dev/null +++ b/docs/api/ar/loaders/AnimationLoader.html @@ -0,0 +1,91 @@ + + + + + + + + + + [page:Loader] → + +

[name]

+ +

+ فئة لتحميل [page:AnimationClip AnimationClips] بتنسيق JSON. هذا + يستخدم [page:FileLoader] داخليًا لتحميل الملفات. +

+ +

مثال للكود

+ + + // instantiate a loader + const loader = new THREE.AnimationLoader(); + + // load a resource + loader.load( + // resource URL + 'animations/animation.js', + + // onLoad callback + function ( animations ) { + // animations is an array of AnimationClips + }, + + // onProgress callback + function ( xhr ) { + console.log( (xhr.loaded / xhr.total * 100) + '% loaded' ); + }, + + // onError callback + function ( err ) { + console.log( 'An error happened' ); + } + ); + + +

المنشئ (Constructor)

+ +

[name]( [param:LoadingManager manager] )

+

+ [page:LoadingManager manager] — [page:LoadingManager loadingManager] + للمحمل الذي سيتم استخدامه. الافتراضي هو [page:LoadingManager THREE.DefaultLoadingManager].

+ ينشئ جديدًا [name]. +

+ +

الخصائص (Properties)

+

انظر الفئة الأساسية [page:Loader] للخصائص المشتركة.

+ +

الطرق (Methods)

+

انظر الفئة الأساسية [page:Loader] للطرق المشتركة.

+ +

+ [method:undefined load]( [param:String url], [param:Function onLoad], [param:Function onProgress], [param:Function onError] ) +

+

+ [page:String url] — المسار أو عنوان URL للملف. يمكن أن يكون هذا أيضًا + [link:https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs Data URI].
+ [page:Function onLoad] — سيتم استدعاؤه عند اكتمال التحميل. الحجة + ستكون المقاطع المحملة [page:AnimationClip animation].
+ [page:Function onProgress] (اختياري) — سيتم استدعاؤه أثناء التقدم في التحميل + يتقدم. الحجة ستكون نسخة ProgressEvent، والتي + يحتوي على .[page:Boolean lengthComputable]، .[page:Integer total] و + .[page:Integer loaded]. إذا لم يضبط الخادم رأس Content-Length + .[page:Integer total] سيكون 0.
+ [page:Function onError] (اختياري) — سيتم استدعاؤه إذا حدث خطأ في التحميل.

+ ابدأ التحميل من url وأرسل المقطع المحمل إلى onLoad. +

+ +

[method:Array parse]( [param:JSON json] )

+

+ [page:JSON json] — مطلوب

+ قم بتحليل كائن JSON وإرجاع مصفوفة من مقاطع الرسوم المتحركة. فردي + سيتم تحليل المقاطع في الكائن باستخدام [page:AnimationClip.parse]. +

+ +

المصدر (Source)

+

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/loaders/AudioLoader.html b/docs/api/ar/loaders/AudioLoader.html new file mode 100644 index 00000000000000..6841925bb09cd2 --- /dev/null +++ b/docs/api/ar/loaders/AudioLoader.html @@ -0,0 +1,107 @@ + + + + + + + + + + [page:Loader] → + +

[name]

+ +

+ فئة لتحميل + [link:https://developer.mozilla.org/en-US/docs/Web/API/AudioBuffer AudioBuffer]. + يستخدم هذا [page:FileLoader] داخليًا لتحميل + الملفات. +

+ +

مثال للكود

+ + + // instantiate a listener + const audioListener = new THREE.AudioListener(); + + // add the listener to the camera + camera.add( audioListener ); + + // instantiate audio object + const oceanAmbientSound = new THREE.Audio( audioListener ); + + // add the audio object to the scene + scene.add( oceanAmbientSound ); + + // instantiate a loader + const loader = new THREE.AudioLoader(); + + // load a resource + loader.load( + // resource URL + 'audio/ambient_ocean.ogg', + + // onLoad callback + function ( audioBuffer ) { + // set the audio object buffer to the loaded object + oceanAmbientSound.setBuffer( audioBuffer ); + + // play the audio + oceanAmbientSound.play(); + }, + + // onProgress callback + function ( xhr ) { + console.log( (xhr.loaded / xhr.total * 100) + '% loaded' ); + }, + + // onError callback + function ( err ) { + console.log( 'An error happened' ); + } + ); + + +

المنشئ (Constructor)

+ +

[name]( [param:LoadingManager manager] )

+

+ [page:LoadingManager manager] — [page:LoadingManager loadingManager] + للمحمل الذي سيتم استخدامه. الافتراضي هو [page:LoadingManager THREE.DefaultLoadingManager].

+ + ينشئ جديدًا [name]. +

+ +

الخصائص (Properties)

+

انظر الفئة الأساسية [page:Loader] للخصائص المشتركة.

+ +

الطرق (Methods)

+

انظر الفئة الأساسية [page:Loader] للطرق المشتركة.

+ +

+ [method:undefined load]( [param:String url], [param:Function onLoad], [param:Function onProgress], [param:Function onError] ) +

+

+ [page:String url] — المسار أو عنوان URL للملف. يمكن أن يكون هذا أيضًا + [link:https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs Data URI].
+ [page:Function onLoad] — سيتم استدعاؤه عند اكتمال التحميل. الحجة + ستكون الاستجابة المحملة للنص.
+ [page:Function onProgress] (اختياري) — سيتم استدعاؤه أثناء التقدم في التحميل + يتقدم. الحجة ستكون نسخة ProgressEvent، والتي + يحتوي على .[page:Boolean lengthComputable]، .[page:Integer total] و + .[page:Integer loaded]. إذا لم يضبط الخادم رأس Content-Length + .[page:Integer total] سيكون 0.
+ [page:Function onError] (اختياري) — سيتم استدعاؤه إذا حدث خطأ في التحميل.
+

+

+ ابدأ التحميل من url وأرسل المحمَّل [page:String AudioBuffer] إلى + onLoad. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/loaders/BufferGeometryLoader.html b/docs/api/ar/loaders/BufferGeometryLoader.html new file mode 100644 index 00000000000000..7f517b2a6f5422 --- /dev/null +++ b/docs/api/ar/loaders/BufferGeometryLoader.html @@ -0,0 +1,94 @@ + + + + + + + + + + [page:Loader] → + +

[name]

+ +

+ محمل لتحميل [page:BufferGeometry]. يستخدم هذا + [page:FileLoader] داخليًا لتحميل الملفات. +

+ +

مثال للكود

+ + + // instantiate a loader + const loader = new THREE.BufferGeometryLoader(); + + // load a resource + loader.load( + // resource URL + 'models/json/pressure.json', + + // onLoad callback + function ( geometry ) { + const material = new THREE.MeshLambertMaterial( { color: 0xF5F5F5 } ); + const object = new THREE.Mesh( geometry, material ); + scene.add( object ); + }, + + // onProgress callback + function ( xhr ) { + console.log( (xhr.loaded / xhr.total * 100) + '% loaded' ); + }, + + // onError callback + function ( err ) { + console.log( 'An error happened' ); + } + ); + + +

المنشئ (Constructor)

+ +

[name]( [param:LoadingManager manager] )

+

+ [page:LoadingManager manager] — [page:LoadingManager loadingManager] + للمحمل الذي سيتم استخدامه. الافتراضي هو [page:LoadingManager THREE.DefaultLoadingManager]. +

+

ينشئ جديدًا [name].

+ +

الخصائص (Properties)

+

انظر الفئة الأساسية [page:Loader] للخصائص المشتركة.

+ +

الطرق (Methods)

+

انظر الفئة الأساسية [page:Loader] للطرق المشتركة.

+ +

+ [method:undefined load]( [param:String url], [param:Function onLoad], [param:Function onProgress], [param:Function onError] ) +

+

+ [page:String url] — المسار أو عنوان URL للملف. يمكن أن يكون هذا أيضًا + [link:https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs Data URI].d
+ [page:Function onLoad] — سيتم استدعاؤه عند اكتمال التحميل. الحجة + ستكون المحمَّلة [page:BufferGeometry].
+ [page:Function onProgress] (اختياري) — سيتم استدعاؤه أثناء التقدم في التحميل + يتقدم. الحجة ستكون نسخة ProgressEvent، والتي + يحتوي على .[page:Boolean lengthComputable]، .[page:Integer total] و + .[page:Integer loaded]. إذا لم يضبط الخادم رأس Content-Length + .[page:Integer total] سيكون 0.
+ [page:Function onError] (اختياري) — سيتم استدعاؤه إذا حدث خطأ في التحميل.
+

+

+ ابدأ التحميل من url واتصل بـ onLoad مع محتوى الاستجابة المحلَّل. +

+ +

[method:BufferGeometry parse]( [param:Object json] )

+

+ [page:Object json] — هيكل `JSON` للتحليل.

+ قم بتحليل هيكل `JSON` وإرجاع [page:BufferGeometry]. +

+ +

المصدر (Source)

+

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/loaders/Cache.html b/docs/api/ar/loaders/Cache.html new file mode 100644 index 00000000000000..13a5fab7ab874d --- /dev/null +++ b/docs/api/ar/loaders/Cache.html @@ -0,0 +1,74 @@ + + + + + + + + + +

[name]

+ +

+ نظام تخزين مؤقت بسيط، يستخدم داخليًا من قبل [page:FileLoader]. +

+ +

مثال للكود

+ +

لتمكين التخزين المؤقت عبر جميع المحملات التي تستخدم [page:FileLoader]، قم بتعيين

+ THREE.Cache.enabled = true. + +

أمثلة (Examples)

+ +

+ [example:webgl_geometry_text WebGL / geometry / text ]
+ [example:webgl_interactive_instances_gpu WebGL / interactive / instances / gpu]
+ [example:webgl_loader_ttf WebGL / loader / ttf] +

+ +

الخصائص (Properties)

+ +

[property:Boolean enabled]

+

هل التخزين المؤقت ممكَّن. الافتراضي هو `false`.

+ +

[property:Object files]

+

[page:Object object] يحتوي على الملفات المخزنة مؤقتًا.

+ +

الطرق (Methods)

+ +

[method:undefined add]( [param:String key], [param:Object file] )

+

+ [page:String key] — [page:String key] للإشارة إلى الملف المخزن مؤقتًا + بواسطته.
+ [page:Object file] — الملف الذي سيتم تخزينه مؤقتًا.

+ + يضيف إدخال تخزين مؤقت بمفتاح للإشارة إلى الملف. إذا كان هذا المفتاح بالفعل + يحمل ملفًا، يتم استبداله. +

+ +

[method:Any get]( [param:String key] )

+

+ [page:String key] — مفتاح نصي

+ + احصل على قيمة [page:String key]. إذا لم يكن المفتاح موجودًا `undefined` + يتم إرجاعه. +

+ +

[method:undefined remove]( [param:String key] )

+

+ [page:String key] — مفتاح نصي يشير إلى ملف مخزن مؤقتًا.

+ + قم بإزالة الملف المخزن مؤقتًا المرتبط بالمفتاح. +

+ +

[method:undefined clear]()

+

إزالة جميع القيم من التخزين المؤقت.

+ + +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/loaders/CompressedTextureLoader.html b/docs/api/ar/loaders/CompressedTextureLoader.html new file mode 100644 index 00000000000000..298e33b84d4bba --- /dev/null +++ b/docs/api/ar/loaders/CompressedTextureLoader.html @@ -0,0 +1,71 @@ + + + + + + + + + + [page:Loader] → + +

[name]

+ +

+ الفئة الأساسية المجردة لمحمل القوام المستندة إلى الكتل (dds، pvr، ...). هذا + يستخدم [page:FileLoader] داخليًا لتحميل الملفات. +

+ +

أمثلة (Examples)

+ +

+ انظر + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/loaders/DDSLoader.js DDSLoader] + و [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/loaders/PVRLoader.js PVRLoader] + لأمثلة على الفئات المشتقة. +

+ +

المنشئ (Constructor)

+ +

[name]( [param:LoadingManager manager] )

+

+ [page:LoadingManager manager] — [page:LoadingManager loadingManager] + للمحمل الذي سيتم استخدامه. الافتراضي هو [page:LoadingManager THREE.DefaultLoadingManager].

+ + ينشئ جديدًا [name]. +

+ +

الخصائص (Properties)

+

انظر الفئة الأساسية [page:Loader] للخصائص المشتركة.

+ +

الطرق (Methods)

+

انظر الفئة الأساسية [page:Loader] للطرق المشتركة.

+ +

+ [method:CompressedTexture load]( [param:String url], [param:Function onLoad], [param:Function onProgress], [param:Function onError] ) +

+

+ [page:String url] — المسار أو عنوان URL للملف. يمكن أن يكون هذا أيضًا + [link:https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs Data URI].
+ [page:Function onLoad] (اختياري) — سيتم استدعاؤه عند اكتمال التحميل. + الحجة ستكون القوام المحمَّل.
+ [page:Function onProgress] (اختياري) — سيتم استدعاؤه أثناء التقدم في التحميل + يتقدم. الحجة ستكون نسخة ProgressEvent، والتي + يحتوي على .[page:Boolean lengthComputable]، .[page:Integer total] و + .[page:Integer loaded]. إذا لم يضبط الخادم رأس Content-Length + .[page:Integer total] سيكون 0.
+ [page:Function onError] (اختياري) — سيتم استدعاؤه إذا حدث خطأ في التحميل.
+

+

+ ابدأ التحميل من url وأرسل القوام المحمَّل إلى onLoad. تعود الطريقة + أيضًا كائن قوام جديد يمكن استخدامه مباشرةً لإنشاء مادة + . +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/loaders/CubeTextureLoader.html b/docs/api/ar/loaders/CubeTextureLoader.html new file mode 100644 index 00000000000000..29d1b0e5db54fc --- /dev/null +++ b/docs/api/ar/loaders/CubeTextureLoader.html @@ -0,0 +1,93 @@ + + + + + + + + + + [page:Loader] → + +

[name]

+ +

+ يمكن استخدام [name] لتحميل خرائط المكعب. يُرجع محمل نسخة من [page:CubeTexture] ويتوقع تعريف خريطة المكعب + على أنها ستة صور منفصلة تمثل جوانب المكعب. لا يتم دعم تعاريف خريطة المكعب الأخرى مثل الصليب العمودي والأفقي والتصميم بالأعمدة والصفوف. +

+

+ [page:CubeTexture] المحملة موجودة في مساحة الألوان sRGB. يعني ذلك أن [page:Texture.colorSpace colorSpace] + تم تعيين الخاصية إلى `THREE.SRGBColorSpace` افتراضيًا. +

+ +

مثال للكود

+ + +const scene = new THREE.Scene(); +scene.background = new THREE.CubeTextureLoader() + .setPath( 'textures/cubeMaps/' ) + .load( [ + 'px.png', + 'nx.png', + 'py.png', + 'ny.png', + 'pz.png', + 'nz.png' + ] ); + + +

أمثلة (Examples)

+ +

+ [example:webgl_materials_cubemap materials / cubemap]
+ [example:webgl_materials_cubemap_dynamic materials / cubemap / dynamic]
+ [example:webgl_materials_cubemap_refraction materials / cubemap / refraction] +

+ +

المنشئ (Constructor)

+ +

[name]( [param:LoadingManager manager] )

+

+ [page:LoadingManager manager] — [page:LoadingManager loadingManager] + للمحمل الذي سيتم استخدامه. الافتراضي هو [page:LoadingManager THREE.DefaultLoadingManager].

+ + ينشئ جديدًا [name]. +

+ +

الخصائص (Properties)

+

انظر الفئة الأساسية [page:Loader] للخصائص المشتركة.

+ +

الطرق (Methods)

+

انظر الفئة الأساسية [page:Loader] للطرق المشتركة.

+ +

+ [method:CubeTexture load]( [param:String urls], [param:Function onLoad], [param:Function onProgress], [param:Function onError] ) +

+

+ [page:String urls] — مصفوفة من 6 عناوين URL للصور، واحدة لكل جانب من + CubeTexture. يجب تحديد عناوين URL بالترتيب التالي: pos-x، + neg-x، pos-y، neg-y، pos-z، neg-z. يمكن أن يكونوا أيضًا + [link:https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs Data URIs].
+ يجب ملاحظة أنه بالاتفاقية، تتم تحديد خرائط المكعب في نظام إحداثيات + فيه x الموجب إلى اليمين عند النظر إلى محور z الموجب - بعبارة أخرى، باستخدام نظام إحداثيات ذو يد يسرى. نظرًا لأن three.js يستخدم + نظام إحداثيات ذو يد يمنى، ستكون خرائط البيئة المستخدمة في three.js + pos-x و neg-x مبادلتان.
+ [page:Function onLoad] (اختياري) — سيتم استدعاؤه عند اكتمال التحميل. + الحجة ستكون القوام المحمَّلة [page:CubeTexture texture].
+ [page:Function onProgress] (اختياري) — هذه الدالة التابعة للرد + غير معتمد حاليًا.
+ [page:Function onError] (اختياري) — سيتم استدعاؤه إذا حدث خطأ في التحميل.
+

+

+ ابدأ التحميل من url وأرسل القوام المحمَّلة [page:CubeTexture texture] إلى + onLoad. تعود الطريقة أيضًا كائن قوام جديد يمكن استخدامه مباشرةً لإنشاء مادة + . +

+ + +

المصدر (Source)

+

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/loaders/DataTextureLoader.html b/docs/api/ar/loaders/DataTextureLoader.html new file mode 100644 index 00000000000000..7cb90c3afda150 --- /dev/null +++ b/docs/api/ar/loaders/DataTextureLoader.html @@ -0,0 +1,68 @@ + + + + + + + + + + [page:Loader] → + +

[name]

+ +

+ الفئة الأساسية المجردة لتحميل تنسيقات القوام الثنائية العامة (rgbe، hdr، + ...). يستخدم هذا [page:FileLoader] داخليًا لتحميل الملفات، و + ينشئ جديدًا [page:DataTexture]. +

+ +

أمثلة (Examples)

+

+ انظر + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/loaders/RGBELoader.js RGBELoader] + لمثال على فئة مشتقة. +

+ +

المنشئ (Constructor)

+

[name]( [param:LoadingManager manager] )

+

+ [page:LoadingManager manager] — [page:LoadingManager loadingManager] + للمحمل الذي سيتم استخدامه. الافتراضي هو [page:LoadingManager THREE.DefaultLoadingManager].

+ + ينشئ جديدًا [name]. +

+ +

الخصائص (Properties)

+

انظر الفئة الأساسية [page:Loader] للخصائص المشتركة.

+ +

الطرق (Methods)

+

انظر الفئة الأساسية [page:Loader] للطرق المشتركة.

+ +

+ [method:DataTexture load]( [param:String url], [param:Function onLoad], [param:Function onProgress], [param:Function onError] ) +

+

+ [page:String url] — المسار أو عنوان URL للملف. يمكن أن يكون هذا أيضًا + [link:https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs Data URI].
+ [page:Function onLoad] (اختياري) — سيتم استدعاؤه عند اكتمال التحميل. + الحجة ستكون القوام المحمَّلة.
+ [page:Function onProgress] (اختياري) — سيتم استدعاؤه أثناء التقدم في التحميل + يتقدم. الحجة ستكون نسخة ProgressEvent، والتي تحتوي على + .[page:Boolean lengthComputable]، .[page:Integer total] و .[page:Integer loaded]. + إذا لم يضبط الخادم رأس Content-Length؛ + .[page:Integer total] سيكون 0.
+ [page:Function onError] (اختياري) — سيتم استدعاؤه إذا حدث خطأ في التحميل.
+

+

+ ابدأ التحميل من url وأرسل القوام المحمَّلة إلى onLoad. تعود الطريقة + أيضًا كائن قوام جديد يمكن استخدامه مباشرةً لإنشاء مادة + . +

+ +

المصدر (Source)

+

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/loaders/FileLoader.html b/docs/api/ar/loaders/FileLoader.html new file mode 100644 index 00000000000000..c8561d333fafd7 --- /dev/null +++ b/docs/api/ar/loaders/FileLoader.html @@ -0,0 +1,131 @@ + + + + + + + + + + [page:Loader] → + +

[name]

+ +

+ فئة منخفضة المستوى لتحميل الموارد باستخدام Fetch ، يتم استخدامها داخليًا من قبل + معظم المحملات. يمكن أيضًا استخدامها مباشرة لتحميل أي نوع ملف لا يحتوي على + محمل. +

+ +

مثال الكود

+ + const loader = new THREE.FileLoader(); + + //load a text file and output the result to the console + loader.load( + // resource URL + 'example.txt', + + // onLoad callback + function ( data ) { + // output the text to the console + console.log( data ) + }, + + // onProgress callback + function ( xhr ) { + console.log( (xhr.loaded / xhr.total * 100) + '% loaded' ); + }, + + // onError callback + function ( err ) { + console.error( 'An error happened' ); + } + ); + + +

+ * ملاحظة: * يجب تمكين ذاكرة التخزين المؤقت باستخدام + THREE.Cache.enabled = true; + هذه خاصية عالمية ولا يحتاج إلى تعيينها مرة واحدة فقط لتستخدمها جميع + المحملات التي تستخدم FileLoader داخليًا. [page:Cache Cache] هو وحدة ذاكرة التخزين المؤقت + التي تحتوي على الاستجابة من كل طلب يتم من خلال هذا المحمل ، + لذلك يتم طلب كل ملف مرة واحدة. +

+ +

المنشئ (Constructor)

+ +

[name] ( [param:LoadingManager manager] )

+

+ [page:LoadingManager manager] - [page:LoadingManager loadingManager] + للمحمل الذي سيستخدمه. الافتراضي هو [page:DefaultLoadingManager]. +

+ +

الخصائص (Properties)

+

انظر إلى الصفحة الأساسية [page:Loader] للخصائص المشتركة.

+ +

[property:String mimeType]

+

+ المتوقع + [link:https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types mimeType]. + انظر [page:.setMimeType]. الافتراضي هو `undefined`. +

+ +

[property:String responseType]

+

+ نوع الاستجابة المتوقع. انظر [page:.setResponseType]. الافتراضي هو + `undefined`. +

+ +

الطرق (Methods)

+

انظر إلى الصفحة الأساسية [page:Loader] للطرق المشتركة.

+ +

+ [method:undefined load]( [param:String url], [param:Function onLoad], [param:Function onProgress], [param:Function onError] ) +

+

+ [page:String url] - المسار أو عنوان URL للملف. يمكن أن يكون هذا أيضًا + [link:https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs Data URI].
+ [page:Function onLoad] (اختياري) - سيتم استدعاؤه عند اكتمال التحميل. + الوسيطة ستكون الاستجابة المحملة.
+ [page:Function onProgress] (اختياري) - سيتم استدعاؤه أثناء تقدم التحميل. + الوسيطة ستكون مثيل ProgressEvent ، والذي + يحتوي على .[page:Boolean lengthComputable]، .[page:Integer total] و + .[page:Integer loaded]. إذا لم يضع الخادم رأس Content-Length + ؛ .[page:Integer total] ستكون 0.
+ [page:Function onError] (اختياري) - سيتم استدعاؤه في حالة حدوث خطأ.

+ + قم بتحميل عنوان URL وإرسال الاستجابة إلى وظيفة onLoad. +

+ +

[method:this setMimeType]( [param:String mimeType] )

+

+ قم بتعيين + [link:https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types mimeType] + المتوقع للملف الذي يتم تحميله. لاحظ أنه في كثير من الحالات سيتم + تحديده تلقائيًا ، لذلك بشكل افتراضي هو `undefined`. +

+ +

[method:this setResponseType]( [param:String responseType] )

+

+ قم بتغيير نوع الاستجابة. القيم المقبولة هي:
+ [page:String text] أو سلسلة فارغة (افتراضية) - تعود بالبيانات كـ + [page:String String].
+ [page:String arraybuffer] - يحمل البيانات في + [link:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer ArrayBuffer] + ويرجع ذلك.
+ [page:String blob] - يعود بالبيانات كـ + [link:https://developer.mozilla.org/en/docs/Web/API/Blob Blob].
+ [page:String document] - يحلل الملف باستخدام + [link:https://developer.mozilla.org/en-US/docs/Web/API/DOMParser DOMParser].
+ [page:String json] - يحلل الملف باستخدام + [link:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse JSON.parse].
+

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/loaders/ImageBitmapLoader.html b/docs/api/ar/loaders/ImageBitmapLoader.html new file mode 100644 index 00000000000000..e6d2b50658ee14 --- /dev/null +++ b/docs/api/ar/loaders/ImageBitmapLoader.html @@ -0,0 +1,117 @@ + + + + + + + + + + [page:Loader] → + +

[name]

+ +

+ محمل لتحميل صورة [page:Image] كـ + [link:https://developer.mozilla.org/en-US/docs/Web/API/ImageBitmap ImageBitmap]. يوفر ImageBitmap مسارًا غير متزامن وفعال من حيث الموارد + لإعداد القوام للتقديم في WebGL.
+ على عكس [page:FileLoader]، [name] لا يتجنب الطلبات المتعددة المتزامنة + إلى نفس عنوان URL. +

+ +

+ لاحظ أن [page:Texture.flipY] و [page:Texture.premultiplyAlpha] مع + [link:https://developer.mozilla.org/en-US/docs/Web/API/ImageBitmap ImageBitmap] يتم تجاهلهم. + [link:https://developer.mozilla.org/en-US/docs/Web/API/ImageBitmap ImageBitmap] + يحتاج هذه التكوينات على إنشاء الخريطة البيانية على عكس الصور العادية التي تحتاجها عند تحميلها على وحدة معالجة الرسومات. تحتاج إلى تعيين الخيارات المكافئة عبر [page:ImageBitmapLoader.setOptions] بدلاً من ذلك. راجع + [link:https://www.khronos.org/registry/webgl/specs/latest/1.0/#6.10 WebGL specification] للحصول على التفاصيل. +

+ +

مثال الكود

+ + + const loader = new THREE.FileLoader(); + + //load a text file and output the result to the console + loader.load( + // resource URL + 'example.txt', + + // onLoad callback + function ( data ) { + // output the text to the console + console.log( data ) + }, + + // onProgress callback + function ( xhr ) { + console.log( (xhr.loaded / xhr.total * 100) + '% loaded' ); + }, + + // onError callback + function ( err ) { + console.error( 'An error happened' ); + } + ); + + +

أمثلة (Examples)

+ +

[example:webgl_loader_imagebitmap WebGL / loader / ImageBitmap]

+ +

المنشئ (Constructor)

+ +

[name]( [param:LoadingManager manager] )

+

+ [page:LoadingManager manager] - [page:LoadingManager loadingManager] + للمحمل الذي سيستخدمه. الافتراضي هو [page:LoadingManager THREE.DefaultLoadingManager].

+ + ينشئ جديد [name]. +

+ +

الخصائص (Properties)

+

انظر إلى الصفحة الأساسية [page:Loader] للخصائص المشتركة.

+ +

[property:Boolean isImageBitmapLoader]

+

علامة للقراءة فقط للتحقق مما إذا كان الكائن المعطى هو من نوع [name].

+ +

[property:String options]

+

+ كائن اختياري يضع خيارات لـ + [link:https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/createImageBitmap createImageBitmap] + طريقة المصنع المستخدمة داخليًا. الافتراضي هو `undefined`. +

+ +

الطرق (Methods)

+

انظر إلى الصفحة الأساسية [page:Loader] للطرق المشتركة.

+ +

+ [method:undefined load]( [param:String url], [param:Function onLoad], [param:Function onProgress], [param:Function onError] ) +

+

+ [page:String url] - المسار أو عنوان URL للملف. يمكن أن يكون هذا أيضًا + [link:https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs Data URI].
+ [page:Function onLoad] - سيتم استدعاؤه عند اكتمال التحميل. الوسيطة + ستكون الصورة المحملة [page:Image image].
+ [page:Function onProgress] (اختياري) - هذه وظيفة رد الاتصال + غير مدعوم حاليًا.
+ [page:Function onError] (اختياري) - سيتم استدعاؤه عند حدوث خطأ في التحميل.
+

+

+ قم ببدء التحميل من عنوان URL وإرجاع كائن الصورة [page:ImageBitmap image] + الذي سيحتوي على البيانات. +

+ +

[method:this setOptions]( [param:Object options] )

+

+ يضع كائن الخيارات لـ + [link:https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/createImageBitmap createImageBitmap]. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/loaders/ImageLoader.html b/docs/api/ar/loaders/ImageLoader.html new file mode 100644 index 00000000000000..2595889249ba88 --- /dev/null +++ b/docs/api/ar/loaders/ImageLoader.html @@ -0,0 +1,100 @@ + + + + + + + + + + [page:Loader] → + +

[name]

+ +

+ محمل لتحميل صورة [page:Image]. يتم استخدام هذا داخليًا من قبل + [page:CubeTextureLoader]، [page:ObjectLoader] و [page:TextureLoader]. +

+ +

مثال الكود

+ + + // instantiate a loader + const loader = new THREE.ImageLoader(); + + // load a image resource + loader.load( + // resource URL + 'image.png', + + // onLoad callback + function ( image ) { + // use the image, e.g. draw part of it on a canvas + const canvas = document.createElement( 'canvas' ); + const context = canvas.getContext( '2d' ); + context.drawImage( image, 100, 100 ); + }, + + // onProgress callback currently not supported + undefined, + + // onError callback + function () { + console.error( 'An error happened.' ); + } + ); + + +

+ يرجى ملاحظة أن three.js r84 قام بإسقاط دعم أحداث التقدم لـ ImageLoader. + لـ ImageLoader يدعم أحداث التقدم ، انظر + [link:https://github.com/mrdoob/three.js/issues/10439#issuecomment-275785639 هذا الموضوع]. +

+ +

أمثلة (Examples)

+ +

+ [example:webgl_loader_obj WebGL / loader / obj]
+ [example:webgl_shaders_ocean WebGL / shaders / ocean] +

+ +

المنشئ (Constructor)

+ +

[name]( [param:LoadingManager manager] )

+

+ [page:LoadingManager manager] - [page:LoadingManager loadingManager] + للمحمل الذي سيستخدمه. الافتراضي هو [page:LoadingManager THREE.DefaultLoadingManager].

+ + ينشئ جديد [name]. +

+ +

الخصائص (Properties)

+

انظر إلى الصفحة الأساسية [page:Loader] للخصائص المشتركة.

+ +

الطرق (Methods)

+

انظر إلى الصفحة الأساسية [page:Loader] للطرق المشتركة.

+ +

+ [method:HTMLImageElement load]( [param:String url], [param:Function onLoad], [param:Function onProgress], [param:Function onError] ) +

+

+ [page:String url] - المسار أو عنوان URL للملف. يمكن أن يكون هذا أيضًا + [link:https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs Data URI].
+ [page:Function onLoad] - سيتم استدعاؤه عند اكتمال التحميل. الوسيطة + ستكون الصورة المحملة [page:Image image].
+ [page:Function onProgress] (اختياري) - هذه وظيفة رد الاتصال + غير مدعوم حاليًا.
+ [page:Function onError] (اختياري) - سيتم استدعاؤه عند حدوث خطأ في التحميل.
+

+

+ قم ببدء التحميل من عنوان URL وإرجاع كائن الصورة [page:Image image] + الذي سيحتوي على البيانات. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/loaders/Loader.html b/docs/api/ar/loaders/Loader.html new file mode 100644 index 00000000000000..43a0eeb7d92110 --- /dev/null +++ b/docs/api/ar/loaders/Loader.html @@ -0,0 +1,138 @@ + + + + + + + + + +

[name]

+ +

الفئة الأساسية لتنفيذ المحملات.

+ +

المنشئ (Constructor)

+ +

[name]( [param:LoadingManager manager] )

+

+ [page:LoadingManager manager] — [page:LoadingManager loadingManager] + للمحمل الذي سيتم استخدامه. الافتراضي هو [page:LoadingManager THREE.DefaultLoadingManager]. +

+

ينشئ [name] جديد.

+ +

الخصائص (Properties)

+ +

[property:String crossOrigin]

+

+ السلسلة crossOrigin لتنفيذ CORS لتحميل عنوان url من + نطاق مختلف يسمح بـ CORS. الافتراضي هو `anonymous`. +

+ +

[property:Boolean withCredentials]

+

+ ما إذا كان XMLHttpRequest يستخدم بيانات الاعتماد. انظر + [page:.setWithCredentials]. الافتراضي هو `false`. +

+ +

[property:LoadingManager manager]

+

+ [page:LoadingManager loadingManager] الذي يستخدمه المحمل. الافتراضي هو + [page:DefaultLoadingManager]. +

+ +

[property:String path]

+

+ المسار الأساسي الذي سيتم منه تحميل الأصل. الافتراضي هو + سلسلة فارغة. +

+ +

[property:String resourcePath]

+

+ المسار الأساسي الذي سيتم منه تحميل الموارد الإضافية مثل الملمسات. + الافتراضي هو سلسلة فارغة. +

+ +

[property:Object requestHeader]

+

+ [link:https://developer.mozilla.org/en-US/docs/Glossary/Request_header رأس الطلب] + المستخدم في طلب HTTP. انظر [page:.setRequestHeader]. + الافتراضي هو كائن فارغ. +

+ +

الطرق (Methods)

+ +

[method:undefined load]()

+

+ يجب تنفيذ هذه الطريقة من قبل جميع المحملات الخرسانية. يحتوي على + المنطق لتحميل الأصل من الخلفية. +

+ +

+ [method:Promise loadAsync]( [param:String url], [param:Function onProgress] ) +

+

+ [page:String url] — سلسلة تحتوي على مسار / عنوان URL للملف المراد + تحميله.
+ [page:Function onProgress] (اختياري) — وظيفة يتم استدعاؤها أثناء + التحميل قيد التقدم. ستكون الوسيطة هي مثيل ProgressEvent ، + الذي يحتوي على .[page:Boolean lengthComputable] ، .[page:Integer total] و + .[page:Integer loaded]. إذا لم يقم الخادم بتعيين رأس Content-Length ؛ + .[page:Integer total] ستكون 0.
+

+

+ هذه الطريقة مكافئة لـ [page:.load] ، ولكنها تعود بـ + [link:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise Promise]. +

+

+ يتم التعامل مع [page:Function onLoad] من قبل + [link:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve Promise.resolve] + ويتم التعامل مع [page:Function onError] من قبل + [link:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/reject Promise.reject]. +

+ +

[method:undefined parse]()

+

+ يجب تنفيذ هذه الطريقة من قبل جميع المحملات الخرسانية. يحتوي على + المنطق لتحليل الأصول إلى كائنات three.js. +

+ +

[method:this setCrossOrigin]( [param:String crossOrigin] )

+

+ [page:String crossOrigin] — سلسلة crossOrigin لتطبيق CORS لـ + تحميل عنوان url من نطاق مختلف يسمح بـ CORS. +

+ +

[method:this setWithCredentials]( [param:Boolean value] )

+

+ ما إذا كان XMLHttpRequest يستخدم بيانات اعتماد مثل ملفات تعريف الارتباط ، ورؤوس التفويض + أو شهادات عميل TLS. انظر + [link:https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials XMLHttpRequest.withCredentials].
+ لاحظ أن هذا لا يؤثر إذا كنت تقوم بتحميل الملفات محليًا أو من + نفس المجال. +

+ +

[method:this setPath]( [param:String path] )

+

[page:String path] — حدد المسار الأساسي للأصول.

+ +

[method:this setResourcePath]( [param:String resourcePath] )

+

+ [page:String resourcePath] — حدد المسار الأساسي للموارد التابعة + مثل الملمس. +

+ +

[method:this setRequestHeader]( [param:Object requestHeader] )

+

+ [page:Object requestHeader] - key: اسم الرأس الذي ستُضبط قيمته. + value: القيمة التى ستضبط كجسم للرأس.

+ + حدد + [link:https://developer.mozilla.org/en-US/docs/Glossary/Request_header request header] المستخدم في طلب HTTP. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/loaders/LoaderUtils.html b/docs/api/ar/loaders/LoaderUtils.html new file mode 100644 index 00000000000000..2344e86bccd310 --- /dev/null +++ b/docs/api/ar/loaders/LoaderUtils.html @@ -0,0 +1,37 @@ + + + + + + + + + +

[name]

+ +

كائن يحتوي على العديد من وظائف المحمل المساعدة.

+ +

الطرق (Methods)

+ +

[method:String extractUrlBase]( [param:String url] )

+

[page:String url] — عنوان url الذي سيتم استخراج العنوان الأساسي منه.

+

استخراج الأساس من عنوان URL.

+ +

+ [method:String resolveURL]( [param:String url], [param:String path] ) +

+

+ [page:String url] — عنوان url المطلق أو النسبي للحل. [page:String path] — المسار الأساسي لحل عناوين url النسبية ضده. +

+

+ يحل عناوين url النسبية ضد المسار المعطى. ستعاد المسارات المطلقة وعناوين url للبيانات + وعناوين url للكتل كما هي. ستعود عناوين url غير صالحة + سلسلة فارغة. +

+ +

المصدر (Source)

+

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/loaders/MaterialLoader.html b/docs/api/ar/loaders/MaterialLoader.html new file mode 100644 index 00000000000000..8c2b8a8ab355e0 --- /dev/null +++ b/docs/api/ar/loaders/MaterialLoader.html @@ -0,0 +1,106 @@ + + + + + + + + + + [page:Loader] → + +

[name]

+ +

+ محمل لتحميل [page:Material] بتنسيق JSON. يستخدم هذا + [page:FileLoader] داخليًا لتحميل الملفات. +

+ +

مثال الكود

+ + + // instantiate a loader + const loader = new THREE.MaterialLoader(); + + // load a resource + loader.load( + // resource URL + 'path/to/material.json', + + // onLoad callback + function ( material ) { + object.material = material; + }, + + // onProgress callback + function ( xhr ) { + console.log( (xhr.loaded / xhr.total * 100) + '% loaded' ); + }, + + // onError callback + function ( err ) { + console.log( 'An error happened' ); + } + ); + + +

المنشئ (Constructor)

+ +

[name]( [param:LoadingManager manager] )

+

+ [page:LoadingManager manager] — [page:LoadingManager loadingManager] + للمحمل الذي سيتم استخدامه. الافتراضي هو [page:LoadingManager THREE.DefaultLoadingManager].

+ ينشئ [name] جديد. +

+ + +

الخصائص (Properties)

+

انظر الفئة الأساسية [page:Loader] للخصائص المشتركة.

+ +

[property:Object textures]

+

+ كائن يحمل أي نسيج يستخدمه المواد. انظر [page:.setTextures]. +

+ +

الطرق (Methods)

+

انظر الفئة الأساسية [page:Loader] للطرق المشتركة.

+ +

+ [method:undefined load]( [param:String url], [param:Function onLoad], [param:Function onProgress], [param:Function onError] ) +

+

+ [page:String url] — المسار أو عنوان URL للملف. يمكن أن يكون هذا أيضًا + [link:https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs Data URI].
+ [page:Function onLoad] — سيتم استدعاؤه عند اكتمال التحميل. الحجة + سيكون ال [page:Material] المحمّل.
+ [page:Function onProgress] (اختياري) — سيتم استدعاؤه أثناء تقدم التحميل. الحجة ستكون مثيل ProgressEvent، والذي + يحتوي على .[page:Boolean lengthComputable]، .[page:Integer total] و + .[page:Integer loaded]. إذا لم يضبط الخادم رأس Content-Length + ؛ سيكون .[page:Integer total] 0.
+ [page:Function onError] (اختياري) — سيتم استدعاؤه عند حدوث خطأ في التحميل.

+ + ابدأ التحميل من url. +

+ +

[method:Material parse]( [param:Object json] )

+

+ [page:Object json] — كائن json الذي يحتوي على معلمات + Material.

+ + قم بتحليل هيكل `JSON` وإنشاء جديد [page:Material] من نوع + [page:String json.type] مع المعلمات المحددة في كائن json. +

+ +

[method:this setTextures]( [param:Object textures] )

+

+ [page:Object textures] — كائن يحتوي على أي نسيج يستخدمه + المادة. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/loaders/ObjectLoader.html b/docs/api/ar/loaders/ObjectLoader.html new file mode 100644 index 00000000000000..b9e053236772aa --- /dev/null +++ b/docs/api/ar/loaders/ObjectLoader.html @@ -0,0 +1,161 @@ + + + + + + + + + + [page:Loader] → + +

[name]

+ +

+ محمل لتحميل مورد JSON في + [link:https://github.com/mrdoob/three.js/wiki/JSON-Object-Scene-format-4 JSON Object/Scene format].

+ يستخدم هذا [page:FileLoader] داخليًا لتحميل الملفات. +

+ +

مثال الكود

+ + + const loader = new THREE.ObjectLoader(); + + loader.load( + // resource URL + "models/json/example.json", + + // onLoad callback + // Here the loaded data is assumed to be an object + function ( obj ) { + // Add the loaded object to the scene + scene.add( obj ); + }, + + // onProgress callback + function ( xhr ) { + console.log( (xhr.loaded / xhr.total * 100) + '% loaded' ); + }, + + // onError callback + function ( err ) { + console.error( 'An error happened' ); + } + ); + + + // Alternatively, to parse a previously loaded JSON structure + const object = loader.parse( a_json_object ); + + scene.add( object ); + + +

أمثلة (Examples)

+ +

[example:webgpu_materials_lightmap WebGL / materials / lightmap]

+ +

المنشئ (Constructor)

+ +

[name]( [param:LoadingManager manager] )

+

+ [page:LoadingManager manager] — [page:LoadingManager loadingManager] + للمحمل الذي سيتم استخدامه. الافتراضي هو [page:LoadingManager THREE.DefaultLoadingManager].

+ ينشئ [name] جديد. +

+ +

الخصائص (Properties)

+

انظر الفئة الأساسية [page:Loader] للخصائص المشتركة.

+ +

الطرق (Methods)

+

انظر الفئة الأساسية [page:Loader] للطرق المشتركة.

+ +

+ [method:undefined load]( [param:String url], [param:Function onLoad], [param:Function onProgress], [param:Function onError] ) +

+

+ [page:String url] — المسار أو عنوان URL للملف. يمكن أن يكون هذا أيضًا + [link:https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs Data URI].
+ [page:Function onLoad] — سيتم استدعاؤه عند اكتمال التحميل. الحجة + سيكون ال [page:Object3D object] المحمّل.
+ [page:Function onProgress] (اختياري) — سيتم استدعاؤه أثناء تقدم التحميل. الحجة ستكون مثيل ProgressEvent، والذي + يحتوي على .[page:Boolean lengthComputable]، .[page:Integer total] و + .[page:Integer loaded]. إذا لم يضبط الخادم رأس Content-Length + ؛ سيكون .[page:Integer total] 0.
+ [page:Function onError] (اختياري) — سيتم استدعاؤه عند حدوث خطأ في التحميل.
+

+

+ ابدأ التحميل من url واستدعِ onLoad مع محتوى الاستجابة المحلل. +

+ +

+ [method:Object3D parse]( [param:Object json], [param:Function onLoad] ) +

+

+ [page:Object json] — مطلوب. مصدر JSON للتحليل.

+ [page:Function onLoad] — سيتم استدعاؤه عند اكتمال التحليل. ال + الحجة ستكون ال [page:Object3D object] المحلل.

+ قم بتحليل هيكل `JSON` وإرجاع كائن three.js. يستخدم هذا + داخليًا بواسطة [page:.load]() ولكن يمكن أيضًا استخدامه مباشرة لتحليل + هيكل JSON محمّل مسبقًا. +

+ +

[method:Object parseGeometries]( [param:Object json] )

+

+ [page:Object json] — مطلوب. مصدر JSON للتحليل.

+ + يستخدم هذا بواسطة [page:.parse]() لتحليل أي [page:BufferGeometry geometries] في هيكل JSON. +

+ +

[method:Object parseMaterials]( [param:Object json] )

+

+ [page:Object json] — مطلوب. مصدر JSON للتحليل.

+ + يستخدم هذا بواسطة [page:.parse]() لتحليل أي مواد في ال JSON + هيكل باستخدام [page:MaterialLoader]. +

+ +

[method:Object parseAnimations]( [param:Object json] )

+

+ [page:Object json] — مطلوب. مصدر JSON للتحليل.

+ + يستخدم هذا بواسطة [page:.parse]() لتحليل أي رسوم متحركة في ال JSON + هيكل، باستخدام [page:AnimationClip.parse](). +

+ +

[method:Object parseImages]( [param:Object json] )

+

+ [page:Object json] — مطلوب. مصدر JSON للتحليل.

+ + يستخدم هذا بواسطة [page:.parse]() لتحليل أي صور في هيكل JSON، + باستخدام [page:ImageLoader]. +

+ +

[method:Object parseTextures]( [param:Object json] )

+

+ [page:Object json] — مطلوب. مصدر JSON للتحليل.

+ + يستخدم هذا بواسطة [page:.parse]() لتحليل أي نسيج في ال JSON + هيكل. +

+ +

+ [method:Object3D parseObject]( [param:Object json], [param:BufferGeometry geometries], [param:Material materials], [param:AnimationClip animations] ) +

+

+ [page:Object json] — مطلوب. مصدر JSON للتحليل.
+ [page:BufferGeometry geometries] — مطلوب. الهندسات الخاصة بـ + JSON.
+ [page:Material materials] — مطلوب. المواد الخاصة بـ JSON.
+ [page:AnimationClip animations] — مطلوب. الرسوم المتحركة الخاصة بـ JSON.

+ + يستخدم هذا بواسطة [page:.parse]() لتحليل أي كائنات ثُنائية الأبعاد في ال JSON + هيكل. +

+ +

المصدر (Source)

+

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/loaders/TextureLoader.html b/docs/api/ar/loaders/TextureLoader.html new file mode 100644 index 00000000000000..a727f10fa2289a --- /dev/null +++ b/docs/api/ar/loaders/TextureLoader.html @@ -0,0 +1,104 @@ + + + + + + + + + + [page:Loader] → + +

[name]

+ +

+ فئة لتحميل [page:Texture texture]. يستخدم هذا + [page:ImageLoader] داخليًا لتحميل الملفات. +

+ +

مثال الكود

+ + + const texture = new THREE.TextureLoader().load('textures/land_ocean_ice_cloud_2048.jpg' ); + // immediately use the texture for material creation + + const material = new THREE.MeshBasicMaterial( { map:texture } ); + + +

مثال الكود مع الردود (Code Example with Callbacks)

+ + + // instantiate a loader + const loader = new THREE.TextureLoader(); + + // load a resource + loader.load( + // resource URL + 'textures/land_ocean_ice_cloud_2048.jpg', + + // onLoad callback + function ( texture ) { + // in this example we create the material when the texture is loaded + const material = new THREE.MeshBasicMaterial( { + map: texture + } ); + }, + + // onProgress callback currently not supported + undefined, + + // onError callback + function ( err ) { + console.error( 'An error happened.' ); + } + ); + + +

+ يرجى ملاحظة أن three.js r84 قام بإسقاط دعم حدث التقدم لـ TextureLoader + . لـ TextureLoader يدعم أحداث التقدم، انظر + [link:https://github.com/mrdoob/three.js/issues/10439#issuecomment-293260145 هذا الموضوع]. +

+ +

أمثلة (Examples)

+

[example:webgl_geometry_cube geometry / cube]

+ +

المنشئ (Constructor)

+

[name]( [param:LoadingManager manager] )

+

+ [page:LoadingManager manager] — [page:LoadingManager loadingManager] + للمحمل الذي سيتم استخدامه. الافتراضي هو [page:LoadingManager THREE.DefaultLoadingManager].

+ + ينشئ [name] جديد. +

+ +

الخصائص (Properties)

+

انظر الفئة الأساسية [page:Loader] للخصائص المشتركة.

+ +

الطرق (Methods)

+

انظر الفئة الأساسية [page:Loader] للطرق المشتركة.

+ +

+ [method:Texture load]( [param:String url], [param:Function onLoad], [param:Function onProgress], [param:Function onError] ) +

+

+ [page:String url] — المسار أو عنوان URL للملف. يمكن أن يكون هذا أيضًا + [link:https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs Data URI].
+ [page:Function onLoad] (اختياري) — سيتم استدعاؤه عند اكتمال التحميل. + الحجة ستكون ال [page:Texture texture] المحمّل.
+ [page:Function onProgress] (اختياري) — هذه الدالة الرجعية + غير مدعومة حاليًا.
+ [page:Function onError] (اختياري) — سيتم استدعاؤه عند حدوث خطأ في التحميل.

+ + ابدأ التحميل من عنوان URL المعطى وأرسل الـ[page:Texture texture] المحمّل بالكامل + إلى onLoad. تعيد الطريقة أيضًا كائن نسيج جديد يمكن + استخدامه مباشرة لإنشاء المادة. إذا قمت بذلك بهذه الطريقة، فقد يظهر النسيج + في مشهدك بمجرد انتهاء عملية التحميل المعنية. +

+ +

المصدر (Source)

+

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/loaders/managers/DefaultLoadingManager.html b/docs/api/ar/loaders/managers/DefaultLoadingManager.html new file mode 100644 index 00000000000000..7fa15480bc662f --- /dev/null +++ b/docs/api/ar/loaders/managers/DefaultLoadingManager.html @@ -0,0 +1,68 @@ + + + + + + + + + +

[name]

+ +

+ نسخة عالمية من [page:LoadingManager LoadingManager]، يستخدمها + معظم المحملات عندما لم يتم تحديد مدير مخصص.

+ + هذا سيكون كافيًا لمعظم الأغراض، ولكن قد يكون هناك أوقات عندما + ترغب في مديري تحميل منفصلين للقول، القوام والنماذج. +

+ +

مثال للكود

+ +

+ يمكنك تعيين [page:LoadingManager.onStart onStart]، + [page:LoadingManager.onLoad onLoad]، [page:LoadingManager.onProgress onProgress]، + [page:LoadingManager.onStart onError] وظائف لل + مدير. ستطبق هذه على أي محملات تستخدم + DefaultLoadingManager.

+ + يجب عدم الخلط بين هذه الوظائف المسماة بشكل مشابه + من المحملات الفردية، لأنها مخصصة لعرض المعلومات + حول الحالة العامة للتحميل، بدلاً من التعامل مع البيانات + التي تم تحميلها. +

+ +THREE.DefaultLoadingManager.onStart = function ( url, itemsLoaded, itemsTotal ) { + console.log( 'Started loading file: ' + url + '.\nLoaded ' + itemsLoaded + ' of ' + itemsTotal + ' files.' ); +}; + +THREE.DefaultLoadingManager.onLoad = function ( ) { + console.log( 'Loading Complete!'); +}; + +THREE.DefaultLoadingManager.onProgress = function ( url, itemsLoaded, itemsTotal ) { + console.log( 'Loading file: ' + url + '.\nLoaded ' + itemsLoaded + ' of ' + itemsTotal + ' files.' ); +}; + +THREE.DefaultLoadingManager.onError = function ( url ) { + console.log( 'There was an error loading ' + url ); +}; + + +

الخصائص (Properties)

+

+ انظر صفحة [page:LoadingManager LoadingManager] لتفاصيل + الخصائص. +

+ +

الطرق (Methods)

+

+ انظر صفحة [page:LoadingManager LoadingManager] لتفاصيل الطرق. +

+ +

المصدر (Source)

+

+ [link:https://github.com/mrdoob/three.js/blob/master/src/loaders/LoadingManager.js src/loaders/LoadingManager.js] +

+ + diff --git a/docs/api/ar/loaders/managers/LoadingManager.html b/docs/api/ar/loaders/managers/LoadingManager.html new file mode 100644 index 00000000000000..88d0f2657f3829 --- /dev/null +++ b/docs/api/ar/loaders/managers/LoadingManager.html @@ -0,0 +1,236 @@ + + + + + + + + + +

[name]

+ +

+ يتعامل ويتتبع البيانات المحملة والمعلقة. يتم إنشاء نسخة عالمية افتراضية + من هذه الفئة واستخدامها من قبل المحملات إذا لم يتم توفيرها + يدويًا - انظر [page:DefaultLoadingManager].

+ + بشكل عام يجب أن يكون ذلك كافيًا، ولكن هناك أوقات يمكن أن تكون فيها + مفيدًا لديك محملات منفصلة - على سبيل المثال إذا كنت ترغب في عرض + شرائط تحميل منفصلة للأشياء والقوام. +

+ +

مثال للكود

+ +

+ يوضح هذا المثال كيفية استخدام LoadingManager لتتبع التقدم + [page:OBJLoader]. +

+ + + const manager = new THREE.LoadingManager(); + manager.onStart = function ( url, itemsLoaded, itemsTotal ) { + console.log( 'Started loading file: ' + url + '.\nLoaded ' + itemsLoaded + ' of ' + itemsTotal + ' files.' ); + }; + + manager.onLoad = function ( ) { + console.log( 'Loading complete!'); + }; + + manager.onProgress = function ( url, itemsLoaded, itemsTotal ) { + console.log( 'Loading file: ' + url + '.\nLoaded ' + itemsLoaded + ' of ' + itemsTotal + ' files.' ); + }; + + manager.onError = function ( url ) { + console.log( 'There was an error loading ' + url ); + }; + + const loader = new OBJLoader( manager ); + loader.load( 'file.obj', function ( object ) { + // + } ); + + +

+ بالإضافة إلى مراقبة التقدم، يمكن استخدام LoadingManager ل + تجاوز عناوين URL للموارد أثناء التحميل. قد يكون ذلك مفيدًا للأصول + القادمة من أحداث السحب والإفلات، WebSockets، WebRTC، أو غيرها من واجهات برمجة التطبيقات. A + مثال يظهر كيفية تحميل نموذج في الذاكرة باستخدام عناوين URL لـ Blob أدناه. +

+ + + // Blob or File objects created when dragging files into the webpage. + const blobs = {'fish.gltf': blob1, 'diffuse.png': blob2, 'normal.png': blob3}; + + const manager = new THREE.LoadingManager(); + + // Initialize loading manager with URL callback. + const objectURLs = []; + manager.setURLModifier( ( url ) => { + + url = URL.createObjectURL( blobs[ url ] ); + objectURLs.push( url ); + return url; + + } ); + + // Load as usual, then revoke the blob URLs. + const loader = new GLTFLoader( manager ); + loader.load( 'fish.gltf', (gltf) => { + + scene.add( gltf.scene ); + objectURLs.forEach( ( url ) => URL.revokeObjectURL( url ) ); + + }); + + +

أمثلة (Examples)

+ +

+ [example:webgl_loader_obj WebGL / loader / obj]
+ [example:webgl_postprocessing_outline WebGL / postprocesing / outline] +

+ +

المنشئ (Constructor)

+

+ [name]( [param:Function onLoad], [param:Function onProgress], + [param:Function onError] ) +

+

+ [page:Function onLoad] — (اختياري) سيتم استدعاء هذه الدالة عندما يتم + جميع المحملات.
+ [page:Function onProgress] — (اختياري) سيتم استدعاء هذه الدالة عند + اكتمال عنصر.
+ [page:Function onError] — (اختياري) سيتم استدعاء هذه الدالة عندما يواجه المحمل + أخطاء.
+ + ينشئ جديدًا [name]. +

+ +

الخصائص (Properties)

+ +

[property:Function onStart]

+

+ سيتم استدعاء هذه الدالة عند بدء التحميل. المعاملات هي:
+ [page:String url] — عنوان url للعنصر المحمل للتو.
+ [page:Integer itemsLoaded] — عدد العناصر المحملة حتى الآن.
+ [page:Integer itemsTotal] — إجمالي عدد العناصر التي يجب تحميلها.

+ + بشكل افتراضي هذا غير محدد. +

+ +

[property:Function onLoad]

+

+ سيتم استدعاء هذه الدالة عند اكتمال جميع التحميلات. بشكل افتراضي + هذا غير محدد، ما لم يتم تمريره في المنشئ. +

+ +

[property:Function onProgress]

+

+ سيتم استدعاء هذه الدالة عند اكتمال عنصر. المعاملات + هي:
+ [page:String url] — عنوان url للعنصر المحمل للتو.
+ [page:Integer itemsLoaded] — عدد العناصر المحملة حتى الآن.
+ [page:Integer itemsTotal] — إجمالي عدد العناصر التي يجب تحميلها.

+ + بشكل افتراضي هذا غير محدد، ما لم يتم تمريره في المنشئ. +

+ +

[property:Function onError]

+

+ سيتم استدعاء هذه الدالة عند حدوث خطأ في أي عنصر، مع الحجة:
+ [page:String url] — عنوان url للعنصر الذي حدث فيه خطأ.

+ + بشكل افتراضي هذا غير محدد، ما لم يتم تمريره في المنشئ. +

+ +

الطرق (Methods)

+ +

+ [method:this addHandler]( [param:Object regex], [param:Loader loader] ) +

+

+ [page:Object regex] — تعبير منتظم.
+ [page:Loader loader] — المحمل. +

+ +

+ يسجل محملًا مع التعبير المنتظم المعطى. يمكن استخدامه ل + تحديد أي محمل يجب استخدامه لتحميل ملفات معينة. A + حالة استخدام نموذجية هي الكتابة فوق المحمل الافتراضي للقوام. +

+ +// add handler for TGA textures +manager.addHandler( /\.tga$/i, new TGALoader() ); + + +

[method:Loader getHandler]( [param:String file] )

+

[page:String file] — مسار الملف.

+ +

+ يمكن استخدامه لاسترجاع المحمل المسجل لمسار الملف المعطى. +

+ +

[method:this removeHandler]( [param:Object regex] )

+

[page:Object regex] — تعبير منتظم.

+ +

يزيل المحمل للتعبير المنتظم المعطى.

+ +

[method:String resolveURL]( [param:String url] )

+

+ [page:String url] — عنوان url للتحميل

+ + بالنظر إلى عنوان URL، يستخدم رد الاتصال بتعديل URL (إن وجد) ويعيد + عنوان URL المحلول. إذا لم يتم تعيين تعديل URL، فسيتم إرجاع العنوان الأصلي. +

+ +

[method:this setURLModifier]( [param:Function callback] )

+

+ [page:Function callback] — رد اتصال تعديل URL. يتم استدعاؤه مع [page:String url] الحجة، + ويجب أن يعود [page:String resolvedURL].

+ + إذا تم توفيره، سيتم تمرير رد الاتصال لكل عنوان URL للمورد قبل + يتم إرسال طلب. قد يعود الرد الاتصال بالعنوان الأصلي، أو عنوان URL جديد ل + تجاوز سلوك التحميل. يمكن استخدام هذا السلوك لتحميل الأصول من + ملفات .ZIP، واجهات برمجة التطبيقات السحب والإفلات، وعناوين URI البيانات. +

+ +
+ +

+ + ملاحظة: تم تصميم الطرق التالية ليتم استدعاؤها داخليًا من قبل + المحملات. يجب ألا تستدعيها مباشرة. + +

+ +

[method:undefined itemStart]( [param:String url] )

+

+ [page:String url] — عنوان url للتحميل

+ + يجب استدعاء هذا من قبل أي محمل يستخدم المدير عندما يبدأ المحمل + تحميل عنوان url. +

+ +

[method:undefined itemEnd]( [param:String url] )

+

+ [page:String url] — عنوان url المحمَّل

+ + يجب استدعاء هذا من قبل أي محمل يستخدم المدير عند انتهاء المحمل + تحميل عنوان url. +

+ +

[method:undefined itemError]( [param:String url] )

+

+ [page:String url] — عنوان url المحمَّل

+ + يجب استدعاء هذا من قبل أي محمل يستخدم المدير عند حدوث خطأ في المحمل + تحميل عنوان url. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/loaders/LoadingManager.js src/loaders/LoadingManager.js] +

+ + diff --git a/docs/api/ar/materials/LineBasicMaterial.html b/docs/api/ar/materials/LineBasicMaterial.html new file mode 100644 index 00000000000000..a02727e47d3ae8 --- /dev/null +++ b/docs/api/ar/materials/LineBasicMaterial.html @@ -0,0 +1,104 @@ + + + + + + + + + + [page:Material] → + +

[name]

+ +

مادة لرسم الهندسة على طريقة الإطار السلكي.

+ +

مثال الكود

+ + const material = new THREE.LineBasicMaterial( { + color: 0xffffff, + linewidth: 1, + linecap: 'round', //ignored by WebGLRenderer + linejoin: 'round' //ignored by WebGLRenderer + } ); + + +

أمثلة (Examples)

+

+ [example:webgl_buffergeometry_drawrange WebGL / buffergeometry / drawrange]
+ [example:webgl_buffergeometry_lines WebGL / buffergeometry / lines]
+ [example:webgl_buffergeometry_lines_indexed WebGL / buffergeometry / lines / indexed]
+ [example:webgl_decals WebGL / decals]
+ [example:webgl_geometry_nurbs WebGL / geometry / nurbs]
+ [example:webgl_geometry_shapes WebGL / geometry / shapes]
+ [example:webgl_geometry_spline_editor WebGL / geometry / spline / editor]
+ [example:webgl_interactive_buffergeometry WebGL / interactive / buffergeometry]
+ [example:webgl_interactive_voxelpainter WebGL / interactive / voxelpainter]
+ [example:webgl_lines_colors WebGL / lines / colors]
+ [example:webgl_lines_dashed WebGL / lines / dashed]
+ [example:physics_ammo_rope physics / ammo / rope] +

+ +

المنشئ (Constructor)

+

[name]( [param:Object parameters] )

+ +

+ [page:Object parameters] - (اختياري) كائن به خاصية واحدة أو أكثر + تحدد مظهر المادة. يمكن تمرير أي خاصية من المادة (بما في ذلك أي خاصية موروثة من [page:Material]) هنا.

+ + الاستثناء هو الخاصية [page:Hexadecimal color]، التي يمكن تمريرها كسلسلة ست عشرية وهي `0xffffff` (أبيض) بشكل افتراضي. + يتم استدعاء [page:Color.set]( color ) داخليًا. +

+ +

الخصائص (Properties)

+

انظر إلى الفئة الأساسية [page:Material] للحصول على خصائص شائعة.

+ +

[property:Color color]

+

[page:Color] للمادة ، الافتراضي هو الأبيض (0xffffff).

+ +

[property:Boolean fog]

+

ما إذا كانت المادة متأثرة بالضباب. الافتراضي هو `true`.

+ +

[property:Float linewidth]

+

+ يتحكم في سمك الخط. الافتراضي هو `1`.

+ + بسبب قيود + [link:https://www.khronos.org/registry/OpenGL/specs/gl/glspec46.core.pdf OpenGL Core Profile] + مع [page:WebGLRenderer WebGL] renderer على معظم + المنصات سيكون linewidth دائمًا 1 بغض النظر عن القيمة المحددة. +

+ +

[property:String linecap]

+

+ تحديد مظهر نهايات الخط. القيم الممكنة هي 'butt' و 'round' و + 'square'. الافتراضي هو 'round'.

+ + هذا يتوافق مع + [link:https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D/lineCap 2D Canvas lineCap] + خاصية ويتم تجاهلها من قبل [page:WebGLRenderer WebGL] renderer. +

+ +

[property:String linejoin]

+

+ تحديد مظهر مفاصل الخط. القيم الممكنة هي 'round' و 'bevel' و + 'miter'. الافتراضي هو 'round'.

+ + هذا يتوافق مع + [link:https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D/lineJoin 2D Canvas lineJoin] + خاصية ويتم تجاهلها من قبل [page:WebGLRenderer WebGL] renderer. +

+ +

[property:Texture map]

+

يحدد لون الخطوط باستخدام بيانات من [page:Texture].

+ +

الطرق (Methods)

+

انظر إلى قاعدة [page:Material] class للطرق المشتركة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/materials/LineDashedMaterial.html b/docs/api/ar/materials/LineDashedMaterial.html new file mode 100644 index 00000000000000..ccbeeb7a0ba101 --- /dev/null +++ b/docs/api/ar/materials/LineDashedMaterial.html @@ -0,0 +1,70 @@ + + + + + + + + + + [page:Material] → [page:LineBasicMaterial] → + +

[name]

+ +

+ مادة لرسم الهندسة على طريقة الإطار السلكي بخطوط متقطعة.
+ Note: You must call [page:Line.computeLineDistances]() when using [name]. +

+ +

مثال الكود

+ + + const material = new THREE.LineDashedMaterial( { + color: 0xffffff, + linewidth: 1, + scale: 1, + dashSize: 3, + gapSize: 1, + } ); + + +

أمثلة (Examples)

+ +

[example:webgl_lines_dashed WebGL / lines / dashed]

+ +

المنشئ (Constructor)

+ +

[name]( [param:Object parameters] )

+

+ [page:Object parameters] - (اختياري) كائن به خاصية واحدة أو أكثر + تحدد مظهر المادة. يمكن تمرير أي خاصية من المادة (بما في ذلك أي خاصية موروثة من [page:LineBasicMaterial]) + هنا. +

+ +

الخصائص (Properties)

+

انظر إلى الفئة الأساسية [page:LineBasicMaterial] للحصول على خصائص شائعة.

+ +

[property:number dashSize]

+

+ حجم الشَرطة. هذا هو كل من الفجوة مع الضربة. الافتراضي هو + `3`. +

+ +

[property:number gapSize]

+

حجم الفجوة. الافتراضي هو `1`.

+ +

[property:Boolean isLineDashedMaterial]

+

علامة قراءة فقط للتحقق مما إذا كان كائنًا معينًا من نوع [name].

+ +

[property:number scale]

+

مقياس الجزء المتقطع من الخط. الافتراضي هو `1`.

+ +

الطرق (Methods)

+

انظر إلى الفئة الأساسية [page:LineBasicMaterial] للحصول على طرق شائعة.

+ +

المصدر (Source)

+

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/materials/Material.html b/docs/api/ar/materials/Material.html new file mode 100644 index 00000000000000..3ea47bdf5db8c6 --- /dev/null +++ b/docs/api/ar/materials/Material.html @@ -0,0 +1,477 @@ + + + + + + + + + +

[name]

+ +

+ الفئة الأساسية المجردة للمواد.

+ + تصف المواد مظهر [page:Object objects]. هم + محددة بطريقة (غالبًا) مستقلة عن المصور ، لذلك لا تضطر إلى + إعادة كتابة المواد إذا قررت استخدام مصور مختلف.

+ + تمتلك الخصائص والطرق التالية من جميع أنواع المواد الأخرى + (على الرغم من أنها قد تكون لها افتراضات مختلفة). +

+ +

المنشئ (Constructor)

+ +

[name]()

+

هذا ينشئ مادة عامة.

+ +

الخصائص (Properties)

+ +

[property:Boolean alphaHash]

+

+ Enables alpha hashed transparency, an alternative to [page:.transparent] or [page:.alphaTest]. + The material will not be rendered if opacity is lower than a random threshold. + Randomization introduces some grain or noise, but approximates alpha blending without + the associated problems of sorting. Using TAARenderPass can reduce the resulting noise. +

+ +

[property:Float alphaTest]

+

+ يحدد قيمة ألفا التي يجب استخدامها عند تشغيل اختبار ألفا. المادة + لن يتم عرضه إذا كانت الشفافية أقل من هذه القيمة. الافتراضي هو + `0`. +

+ +

[property:Boolean alphaToCoverage]

+

+ يمكّن ألفا للتغطية. يمكن استخدامه فقط مع سياقات MSAA-enabled + (وهذا يعني عند إنشاء المصور بمعامل `antialias` مضبوطًا على + `true`). الافتراضي هو `false`. +

+ +

[property:Integer blendDst]

+

+ وجهة المزج. الافتراضي هو [page:CustomBlendingEquation OneMinusSrcAlphaFactor]. + انظر عوامل الوجهة [page:CustomBlendingEquation constants] لجميع القِيَم المحتملة.
+ يجب تعيين [page:Constant blending] للمادة على [page:Materials CustomBlending] + لهذا لديه أي تأثير. +

+ +

[property:Integer blendDstAlpha]

+

+ شفافية [page:.blendDst]. يستخدم قيمة [page:.blendDst] إذا + null. الافتراضي هو `null`. +

+ +

[property:Integer blendEquation]

+

+ معادلة المزج للاستخدام عند تطبيق المزج. الافتراضي هو + [page:CustomBlendingEquation AddEquation]. انظر معادلة المزج + [page:CustomBlendingEquation constants] لجميع القيم الممكنة.
+ يجب تعيين [page:Constant blending] للمادة على [page:Materials CustomBlending] + لهذا لديه أي تأثير. +

+ +

[property:Integer blendEquationAlpha]

+

+ شفافية [page:.blendEquation]. يستخدم قيمة [page:.blendEquation] + إذا كان null. الافتراضي هو `null`. +

+ +

[property:Blending blending]

+

+ أي مزج للاستخدام عند عرض الكائنات بهذه المادة.
+ يجب تعيين هذا على [page:Materials CustomBlending] لاستخدام مخصص + [page:Constant blendSrc]، [page:Constant blendDst] أو [page:Constant blendEquation].
+ انظر وضع المزج [page:Materials constants] لجميع القيم الممكنة. + الافتراضي هو [page:Materials NormalBlending]. +

+ +

[property:Integer blendSrc]

+

+ مصدر المزج. الافتراضي هو [page:CustomBlendingEquation SrcAlphaFactor]. + انظر عوامل المصدر [page:CustomBlendingEquation constants] للكل + القِيَم المحتملة.
+ يجب تعيين [page:Constant blending] للمادة على [page:Materials CustomBlending] + لهذا لديه أي تأثير. +

+ +

[property:Integer blendSrcAlpha]

+

+ شفافية [page:.blendSrc]. يستخدم قيمة [page:.blendSrc] إذا + null. الافتراضي هو `null`. +

+ +

[property:Boolean clipIntersection]

+

+ تغير سلوك مستويات القطع بحيث يتم قطع تقاطعها فقط + ، بدلاً من اتحادهم. الافتراضي هو `false`. +

+ +

[property:Array clippingPlanes]

+

+ مستويات القطع المحددة من قبل المستخدم محددة كـ THREE.Plane objects في العالم + الفضاء. تنطبق هذه المستويات على الكائنات التي يتم إرفاق هذه المادة بها. + يتم قطع النقاط في الفضاء التي يكون مسافتها الموقعة إلى المستوى سلبية + (غير معروض). هذا يتطلب [page:WebGLRenderer.localClippingEnabled] إلى + كن `true`. انظر [example:webgl_clipping_intersection WebGL / clipping /intersection] مثال. الافتراضي هو `null`. +

+ +

[property:Boolean clipShadows]

+

+ يحدد ما إذا كان يجب قطع الظلال وفقًا للمستويات القطع المحددة + على هذه المادة. الافتراضي هو `false`. +

+ +

[property:Boolean colorWrite]

+

+ سواء كان يجب عرض لون المادة. يمكن استخدام هذا بالتزامن + مع خاصية [page:Integer renderOrder] لشبكة لإنشاء غير مرئية + الأشياء التي تحجب الأشياء الأخرى. الافتراضي هو `true`. +

+ +

[property:Object defines]

+

+ تعريفات مخصصة لإدخالها في المظلل. يتم تمرير هذه في شكل + محدد كائن حرفي ، بأزواج من المفتاح / القيمة. `{ MY_CUSTOM_DEFINE: '' , PI2: + Math.PI * 2 }`. يتم تعريف الأزواج في كل من قمة وشظية المظللات. + الافتراضي هو `undefined`. +

+ +

[property:Integer depthFunc]

+

+ أي دالة عمق للاستخدام. الافتراضي هو [page:Materials LessEqualDepth]. + انظر وضع العمق [page:Materials constants] لجميع القيم الممكنة. +

+ +

[property:Boolean depthTest]

+

+ سواء كان يجب تمكين اختبار العمق عند عرض هذه المادة. افتراضي + هو `true`. +

+ +

[property:Boolean depthWrite]

+

+ سواء كان عرض هذه المادة له أي تأثير على مخزن العمق. + الافتراضي هو `true`.

+ + عند رسم تراكبات 2D ، يمكن أن يكون من المفيد تعطيل كتابة العمق في + لطبقة عدة أشياء معًا دون إنشاء فنون فهرس z. +

+ +

[property:Boolean forceSinglePass]

+

+ سواء كان يجب عرض الأشياء الشفافة ذات الجانبين بمرور واحد + أم لا. الافتراضي هو `false`.

+ + يعرض المحرك الأشياء الشفافة ذات الجانبين بمكالمتين رسم + (الوجوه الخلفية أولاً ، ثم الوجوه الأمامية) للتخفيف من التحولات الشفافة. + هناك سيناريوهات ومع ذلك حيث لا ينتج هذا النهج عن مكاسب في الجودة + ولكن لا يزال يضاعف المكالمات الرسمية على سبيل المثال عند عرض نباتات مسطحة مثل + رشات العشب. في هذه الحالات ، قم بتعيين علامة `forceSinglePass` على `true` إلى + تعطيل التقديم المزدوج لتجنب مشاكل الأداء. +

+ +

[property:Boolean isMaterial]

+

علامة للقراءة فقط للتحقق مما إذا كان كائنًا معينًا من نوع [name].

+ +

[property:Boolean stencilWrite]

+

+ سواء كانت تتم عمليات الشبكة ضد مخزن الشبكة. في + لإجراء كتابات أو مقارنات ضد مخزن الشبكة هذه + القيمة يجب أن تكون `true`. الافتراضي هو `false`. +

+ +

[property:Integer stencilWriteMask]

+

+ قناع البت المستخدم عند الكتابة إلى مخزن الشبكة. الافتراضي هو `0xFF`. +

+ +

[property:Integer stencilFunc]

+

+ دالة المقارنة بالشبكة التي يجب استخدامها. الافتراضي هو [page:Materials AlwaysStencilFunc]. + انظر دالة شبكية [page:Materials constants] لـ + جميع القيم الممكنة. +

+ +

[property:Integer stencilRef]

+

+ القيمة المستخدمة عند إجراء مقارنات شبكية أو شبكية + عمليات. الافتراضي هو `0`. +

+ +

[property:Integer stencilFuncMask]

+

+ قناع البت المستخدم عند المقارنة مع مخزن الشبكة. الافتراضي هو + `0xFF`. +

+ +

[property:Integer stencilFail]

+

+ العملية الشبكية التي يجب تنفيذها عندما تعود دالة المقارنة + خطأ. الافتراضي هو [page:Materials KeepStencilOp]. انظر شبكية + عمليات [page:Materials constants] لجميع القيم الممكنة. +

+ +

[property:Integer stencilZFail]

+

+ العملية الشبكية التي يجب تنفيذها عندما تعود دالة المقارنة + صحيح ولكن اختبار العمق يفشل. الافتراضي هو [page:Materials KeepStencilOp]. + انظر عمليات شبكية [page:Materials constants] لجميع المحتمل + القِيَم. +

+ +

[property:Integer stencilZPass]

+

+ العملية الشبكية التي يجب تنفيذها عندما تعود دالة المقارنة + صحيح واختبار العمق يمر. الافتراضي هو [page:Materials KeepStencilOp]. + انظر عمليات الشبكة [page:Materials constants] لجميع القيم الممكنة +

+ +

[property:Integer id]

+

رقم فريد لهذه المادة.

+ +

[property:String name]

+

+ اسم اختياري للكائن (لا يحتاج إلى أن يكون فريدًا). الافتراضي هو + سلسلة فارغة. +

+ +

[property:Boolean needsUpdate]

+

يحدد أن المادة تحتاج إلى إعادة التجميع.

+ +

[property:Float opacity]

+

+ عائم في نطاق `0.0` - `1.0` يشير إلى مدى شفافية + المادة. قيمة `0.0` تشير إلى الشفافية الكاملة ، `1.0` هو كامل + غير شفاف.
+ إذا لم يتم تعيين خاصية [page:Boolean transparent] للمادة على + `true`، ستظل المادة غير شفافة تمامًا وسيؤثر هذا القيمة فقط + تأثير لونه.
+ الافتراضي هو `1.0`. +

+ +

[property:Boolean polygonOffset]

+

+ سواء كان يجب استخدام إزاحة المضلع. الافتراضي هو `false`. هذا يتوافق مع + `GL_POLYGON_OFFSET_FILL` ميزة WebGL. +

+ +

[property:Integer polygonOffsetFactor]

+

تعيين عامل إزاحة المضلع. الافتراضي هو `0`.

+ +

[property:Integer polygonOffsetUnits]

+

تعيين وحدات إزاحة المضلع. الافتراضي هو `0`.

+ +

[property:String precision]

+

+ تجاوز دقة المصور الافتراضية لهذه المادة. يمكن أن يكون + `"highp"`، `"mediump"` أو `"lowp"`. الافتراضي هو `null`. +

+ +

[property:Boolean premultipliedAlpha]

+

+ سواء كان يجب ضرب قيمة ألفا (الشفافية). انظر + [Example:webgl_materials_physical_transmission WebGL / Materials / Physical / Transmission] + لمثال على الفرق. الافتراضي هو `false`. +

+ +

[property:Boolean dithering]

+

+ سواء كان يجب تطبيق التدرج على اللون لإزالة مظهر + الفرقة. الافتراضي هو `false`. +

+ +

[property:Integer shadowSide]

+

+ يحدد أي جانب من الوجوه يلقي الظلال. عند التعيين، يمكن أن يكون [page:Materials THREE.FrontSide]، + [page:Materials THREE.BackSide]، أو [page:Materials THREE.DoubleSide]. + الافتراضي هو `null`.
+ إذا كان `null`، يتم تحديد الجانب الذي يلقي الظلال على النحو التالي:
+

+ + + + + + + + + + + + + + + + + + + + + + +
[page:Material.side]Side casting shadows
THREE.FrontSideback side
THREE.BackSidefront side
THREE.DoubleSideboth sides
+ +

[property:Integer side]

+

+ يحدد أي جانب من الوجوه سيتم عرضه - الأمامية، الخلفية أو كلاهما. + الافتراضي هو [page:Materials THREE.FrontSide]. خيارات أخرى هي + [page:Materials THREE.BackSide] أو [page:Materials THREE.DoubleSide]. +

+ +

[property:Boolean toneMapped]

+

+ يحدد ما إذا كانت هذه المادة معدلة نغمة وفقًا لإعدادات [page:WebGLRenderer.toneMapping toneMapping] للمُصير. + الافتراضي هو `true`. +

+ +

[property:Boolean transparent]

+

+ يحدد ما إذا كانت هذه المادة شفافة. له تأثير على + التصيير كما تحتاج الأشياء الشفافة إلى معاملة خاصة وتُصاغ + بعد الأشياء غير الشفافة.
+ عند تعيينه على true، يتم التحكم في مدى شفافية المادة + عن طريق تعيين خاصية [page:Float opacity] الخاصة به.
+ الافتراضي هو `false`. +

+ +

[property:String type]

+

+ القيمة هي السلسلة 'Material'. يجب عدم تغيير هذا، ويمكن استخدامه + للعثور على جميع الكائنات من هذا النوع في المشهد. +

+ +

[property:String uuid]

+

+ [link:http://en.wikipedia.org/wiki/Universally_unique_identifier UUID] من + هذه الحالة المادية. يتم تعيين هذا تلقائيًا، لذلك يجب عدم تحريره. +

+ +

[property:Integer version]

+

+ يبدأ هذا في `0` ويحسب كم مرة يتم تعيين [page:Material.needsUpdate .needsUpdate] إلى `true`. +

+ +

[property:Boolean vertexColors]

+

+ يحدد ما إذا كان يتم استخدام تلوين الرأس. الافتراضي هو `false`. يدعم المحرك + ألوان RGB و RGBA للرأس اعتمادًا على ما إذا كان يتم استخدام سمة مخزنة لونية + ذات ثلاثة (RGB) أو أربعة (RGBA) مكونات. +

+ +

[property:Boolean visible]

+

يحدد ما إذا كانت هذه المادة مرئية. الافتراضي هو `true`.

+ +

[property:Object userData]

+

+ كائن يمكن استخدامه لتخزين بيانات مخصصة حول المادة. يجب ألا يحتوي على + مراجع إلى الوظائف لأنها لن تتم نسخها. +

+ +

الطرق (Methods)

+

+ طرق [page:EventDispatcher EventDispatcher] متاحة على هذه + الفئة. +

+ +

[method:Material clone]( )

+

يرجع مادة جديدة بنفس المعلمات كهذه المادة.

+ +

[method:this copy]( [param:material material] )

+

انسخ المعلمات من المادة المارة إلى هذه المادة.

+ +

[method:undefined dispose]()

+

+ يحرر الموارد المتعلقة بوحدة معالجة الرسومات التي تم تخصيصها بواسطة هذه الحالة. اتصل بهذه + الطريقة كلما لم يعد هذه الحالة مستخدمة في التطبيق الخاص بك. +

+

+ يجب التخلص من نسج المادة من خلال طريقة dispose() من + [page:Texture Texture]. +

+ +

+ [method:undefined onBeforeCompile]( [param:Shader shader], [param:WebGLRenderer renderer] ) +

+

+ رد الاتصال الاختياري الذي يتم تنفيذه مباشرة قبل تجميع برنامج الشادر. يتم استدعاء هذه الدالة مع شفرة مصدر الشادر كمعلمة. مفيد لتعديل المواد المدمجة. +

+

+ على عكس الخصائص، لا يتم دعم رد الاتصال بواسطة [page:Material.clone .clone]()، + [page:Material.copy .copy]() و [page:Material.toJSON .toJSON](). +

+

+ This callback is only supported in `WebGLRenderer` (not `WebGPURenderer`). +

+

+ [example:webgl_materials_modified WebGL / materials / modified]
+ [example:webgl_shadow_contact WebGL / shadow / contact] +

+ +

+ [method:undefined onBeforeRender]( [param:WebGLRenderer renderer], [param:Scene scene], [param:Camera camera], [param:BufferGeometry geometry], [param:Object3D object], [param:Group group] ) +

+

+ An optional callback that is executed immediately before the material is used to + render a 3D object. +

+

+ Unlike properties, the callback is not supported by [page:Material.clone .clone](), + [page:Material.copy .copy]() and [page:Material.toJSON .toJSON](). +

+

+ This callback is only supported in `WebGLRenderer` (not `WebGPURenderer`). +

+ +

[method:String customProgramCacheKey]()

+

+ في حالة استخدام onBeforeCompile، يمكن استخدام هذا الرد الاتصال لتحديد + قيم إعدادات المستخدمة في onBeforeCompile، بحيث يمكن لـ three.js إعادة استخدام + شادر مخزن في ذاكرة التخزين المؤقت أو إعادة تجميع شادر لهذه المادة حسب الحاجة. +

+ +

+ على سبيل المثال، إذا كان onBeforeCompile يحتوي على جملة شرطية مثل:
+ + + if ( black ) { + shader.fragmentShader = shader.fragmentShader.replace('gl_FragColor = vec4(1)', + 'gl_FragColor = vec4(0)') + } + + + فيجب تعيين customProgramCacheKey مثل هذا:
+ + + material.customProgramCacheKey = function() { + return black ? '1' : '0'; + } + +

+ +

+ على عكس الخصائص، لا يتم دعم رد الاتصال بواسطة [page:Material.clone .clone]()، + [page:Material.copy .copy]() و [page:Material.toJSON .toJSON](). +

+ +

[method:undefined setValues]( [param:Object values] )

+

+ values - حاوية بالمعلمات.
+ يضبط الخصائص بناءً على `values`. +

+ +

[method:Object toJSON]( [param:Object meta] )

+

+ meta - كائن يحتوي على بيانات تعريفية مثل الملمس أو الصور للمادة.
+ تحويل المادة إلى three.js + [link:https://github.com/mrdoob/three.js/wiki/JSON-Object-Scene-format-4 JSON Object/Scene format]. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/materials/MeshBasicMaterial.html b/docs/api/ar/materials/MeshBasicMaterial.html new file mode 100644 index 00000000000000..5c7c30542793ed --- /dev/null +++ b/docs/api/ar/materials/MeshBasicMaterial.html @@ -0,0 +1,178 @@ + + + + + + + + + + [page:Material] → + +

[name]

+ +

+ مادة لرسم الهندسة بطريقة مظللة بسيطة (مسطحة أو إطار سلكي) + طريقة.

+ + هذه المادة لا تتأثر بالأضواء. +

+ + + + + +

المنشئ (Constructor)

+ +

[name]( [param:Object parameters] )

+

+ [page:Object parameters] - (اختياري) كائن يحتوي على واحد أو أكثر + خصائص تحدد مظهر المادة. يمكن تمرير أي خاصية من + المادة (بما في ذلك أي خاصية موروثة من [page:Material]) يمكن + تمريرها هنا.

+ + الاستثناء هو خاصية [page:Hexadecimal color] ، والتي يمكن + تمريرها كسلسلة ست عشرية وهي `0xffffff` (أبيض) بشكل افتراضي. + يتم استدعاء [page:Color.set]( color ) داخليًا. +

+ +

الخصائص (Properties)

+

انظر فئة [page:Material] الأساسية للخصائص المشتركة.

+ +

[property:Texture alphaMap]

+

+ خريطة الألفا هي نسيج رمادي يتحكم في التعتيم عبر + السطح (أسود: شفاف تمامًا ؛ أبيض: غير شفاف تمامًا). الافتراضي هو + null.

+ + يتم استخدام لون النسيج فقط ، مع تجاهل قناة الألفا إذا كانت واحدة + موجود. بالنسبة للقوام RGB و RGBA ، سيستخدم [page:WebGLRenderer WebGL] renderer + قناة اللون الأخضر عند أخذ عينات من هذا النسيج بسبب البت الإضافي + من الدقة المقدمة للأخضر في تنسيقات DXT المضغوطة و RGB 565 غير المضغوطة + التنسيقات. ستعمل القوام المضاء فقط والقوام المضاء / ألفا أيضًا كما هو متوقع. +

+ +

[property:Texture aoMap]

+

+ يتم استخدام قناة اللون الأحمر من هذه القوام كخريطة إضاءة محيطية. + الافتراضي هو null. يتطلب aoMap مجموعة ثانية من UVs. +

+ +

[property:Float aoMapIntensity]

+

+ شدة تأثير الإضاءة المحيطة. الافتراضي هو 1. صفر لا + تأثير انعكاس. +

+ +

[property:Color color]

+

[page:Color] المادة ، بشكل افتراضي محدد كأبيض (0xffffff).

+ +

[property:Integer combine]

+

+ كيفية دمج نتيجة لون السطح مع خريطة البيئة ، إن وجدت.

+ + الخيارات هي [page:Materials THREE.MultiplyOperation] (الافتراضية)، + [page:Materials THREE.MixOperation]، [page:Materials THREE.AddOperation]. + إذا تم اختيار المزج ، يتم استخدام [page:.reflectivity] للمزج بين + اللونان. +

+ +

[property:Texture envMap]

+

خريطة البيئة. الافتراضي هو null.

+ +

[property:Boolean fog]

+

هل يتأثر المادة بالضباب. الافتراضي هو `true`.

+ +

[property:Texture lightMap]

+

+ خريطة الإضاءة. الافتراضي هو null. يتطلب lightMap مجموعة ثانية من UVs. +

+ +

[property:Float lightMapIntensity]

+

شدة الإضاءة المخبوزة. الافتراضي هو 1.

+ +

[property:Texture map]

+

+ خريطة اللون. قد تتضمن قناة ألفا اختياريًا ، عادةً ما يتم دمجها + مع [page:Material.transparent .transparent] أو [page:Material.alphaTest .alphaTest]. الافتراضي هو null. +

+ +

[property:Float reflectivity]

+

+ مدى تأثير خريطة البيئة على السطح ؛ انظر أيضًا + [page:.combine]. القيمة الافتراضية هي 1 والنطاق الصحيح هو بين 0 + (لا انعكاسات) و 1 (انعكاسات كاملة). +

+ +

[property:Float refractionRatio]

+

+ مؤشر انكسار (IOR) الهواء (حوالي 1) مقسومًا على + مؤشر انكسار المادة. يستخدم مع وسائط تعيين البيئة + [page:Textures THREE.CubeRefractionMapping] و [page:Textures THREE.EquirectangularRefractionMapping]. + نسبة الانكسار لا يجب أن تتجاوز 1. الافتراضي هو `0.98`. +

+ +

[property:Texture specularMap]

+

خريطة التألق المستخدمة من قبل المادة. الافتراضي هو null.

+ +

[property:Boolean wireframe]

+

+ عرض الهندسة كإطار سلكي. الافتراضي هو `false` (أي عرض كـ + مضلعات مسطحة). +

+ +

[property:String wireframeLinecap]

+

+ تحديد مظهر نهاية الخط. القيم الممكنة هي "butt" و "round" و + "square". الافتراضي هو 'round'.

+ + يتوافق هذا مع + [link:https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D/lineCap 2D Canvas lineCap] + خاصية وهو يتجاهل من قبل [page:WebGLRenderer WebGL] renderer. +

+ +

[property:String wireframeLinejoin]

+

+ تحديد مظهر مفصلات الخط. القيم الممكنة هي "round" و "bevel" و + "miter". الافتراضي هو 'round'.

+ + يتوافق هذا مع + [link:https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D/lineJoin 2D Canvas lineJoin] + خاصية وهو يتجاهل من قبل [page:WebGLRenderer WebGL] renderer. +

+ +

[property:Float wireframeLinewidth]

+

+ التحكم في سمك الإطار السلكي. الافتراضي هو 1.

+ + بسبب قصور + [link:https://www.khronos.org/registry/OpenGL/specs/gl/glspec46.core.pdf OpenGL Core Profile] + مع [page:WebGLRenderer WebGL] renderer على معظم المنصات ستظل linewidth دائمًا 1 بغض النظر عن القيمة المحددة. +

+ +

الطرق (Methods)

+

انظر فئة [page:Material] الأساسية للطرق المشتركة.

+ +

المصدر (Source)

+

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/materials/MeshDepthMaterial.html b/docs/api/ar/materials/MeshDepthMaterial.html new file mode 100644 index 00000000000000..c43c2516447d98 --- /dev/null +++ b/docs/api/ar/materials/MeshDepthMaterial.html @@ -0,0 +1,122 @@ + + + + + + + + + + [page:Material] → + +

[name]

+ +

+ مادة لرسم الهندسة بالعمق. يعتمد العمق على الكاميرا + قريب وبعيد. الأبيض هو الأقرب ، الأسود هو الأبعد. +

+ + + + + +

المنشئ (Constructor)

+ +

[name]( [param:Object parameters] )

+

+ [page:Object parameters] - (اختياري) كائن به واحد أو أكثر + خصائص تحدد مظهر المادة. يمكن تمرير أي خاصية من + المادة (بما في ذلك أي خاصية موروثة من [page:Material]) يمكن + تمريرها هنا. +

+ +

الخصائص (Properties)

+

انظر فئة [page:Material] الأساسية للخصائص المشتركة.

+ +

[property:Texture alphaMap]

+

+ خريطة الألفا هي نسيج رمادي يتحكم في التعتيم عبر + السطح (أسود: شفاف تمامًا ؛ أبيض: غير شفاف تمامًا). الافتراضية هى + null.

+ + يتم استخدام لون النسيج فقط ، مع تجاهل قناة الألفا إذا كانت واحدة + موجود. بالنسبة للقوام RGB و RGBA ، سيستخدم [page:WebGLRenderer WebGL] renderer + قناة اللون الأخضر عند عيّنة هذا القوام بسبب البت الإضافي + من الدقة المقدمة للأخضر في DXT-compressed وغير المضغوط RGB 565 + التنسيقات. ستعمل قوام الإضاءة فقط وقوام الإضاءة / الألفا أيضًا كما هو متوقع. +

+ +

[property:Constant depthPacking]

+

نوع التعبئة العميقة. الافتراضية هى [page:Textures BasicDepthPacking].

+ +

[property:Texture displacementMap]

+

+ يؤثر خريطة التشويه على موضع رؤوس الشبكة. على عكس + الخرائط الأخرى التي تؤثر فقط على الضوء والظل من المادة + يمكن للرؤوس المشوهة أن تلقي ظلالًا ، وتحجب الأشياء الأخرى ، وغيرها + يعمل كهندسة حقيقية. نسيج التشويه هو صورة حيث القيمة + من كل بكسل (الأبيض هو الأعلى) يتم تعيينها ضد ، و + إعادة تحديد مواقع ، رؤوس الشبكة. +

+ +

[property:Float displacementScale]

+

+ مدى تأثير خريطة التشويه على الشبكة (حيث الأسود هو لا + التشويه ، والأبيض هو التشويه الأقصى). بدون تشويه + تم تعيين خريطة ، لا يتم تطبيق هذه القيمة. الافتراضية هي 1. +

+ +

[property:Float displacementBias]

+

+ إزاحة قيم خريطة التشويه على رؤوس شبكة المصفوفات. + بدون تعيين خريطة التشويه ، لا يتم تطبيق هذه القيمة. الافتراضية هى 0. +

+ +

[property:Texture map]

+

+ خريطة اللون. قد يتضمن اختيارًا قناة ألفا ، عادةً مجتمعة + مع [page:Material.transparent .transparent] أو [page:Material.alphaTest .alphaTest]. + الافتراضية هى null. +

+ +

[property:Boolean wireframe]

+

+ عرض الهندسة كإطار سلكي. الافتراضية هى false (أى + عرض كظل ملساء). +

+ +

[property:Float wireframeLinewidth]

+

+ يتحكم في سُمك الإطار السلكى. الافتراضية هى 1.

+ + نظرًا للقصور في + [link:https://www.khronos.org/registry/OpenGL/specs/gl/glspec46.core.pdf OpenGL Core Profile] + مع [page:WebGLRenderer WebGL] renderer على معظم المنصات ستكون linewidth دائمًا 1 بغض النظر عن القيمة المحددة. +

+ +

الطرق (Methods)

+

انظر فئة [page:Material] الأساسية للطُرُق المشتركة.

+ +

المصدر (Source)

+

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/materials/MeshDistanceMaterial.html b/docs/api/ar/materials/MeshDistanceMaterial.html new file mode 100644 index 00000000000000..c2f20b8d668981 --- /dev/null +++ b/docs/api/ar/materials/MeshDistanceMaterial.html @@ -0,0 +1,108 @@ + + + + + + + + + + [page:Material] → + +

[name]

+ +

+ يتم استخدام [name] داخليًا لتنفيذ تعيين الظلال مع + [page:PointLight]s.

+ + يمكن أيضًا استخدامه لتخصيص رمي الظل لكائن عن طريق تعيين + مثيل من [name] إلى [page:Object3D.customDistanceMaterial]. ال + يوضح الأمثلة التالية هذا النهج لضمان + أجزاء شفافة من الأشياء لا تلقي ظلالًا. +

+ +

أمثلة (Examples)

+ +

[example:webgl_shadowmap_pointlight WebGL / shadowmap / pointlight]

+ + + +

المنشئ (Constructor)

+ +

[name]( [param:Object parameters] )

+

+ [page:Object parameters] - (اختياري) كائن به واحد أو أكثر + خصائص تحدد مظهر المادة. يمكن تمرير أي خاصية من + المادة (بما في ذلك أي خاصية موروثة من [page:Material]) يمكن + تمريرها هنا. +

+ +

الخصائص (Properties)

+

انظر فئة [page:Material] الأساسية للخصائص المشتركة.

+ +

[property:Texture alphaMap]

+

+ خريطة الألفا هي نسيج رمادي يتحكم في التعتيم عبر + السطح (أسود: شفاف تمامًا ؛ أبيض: غير شفاف تمامًا). الافتراضية هى + null.

+ + يتم استخدام لون النسيج فقط ، مع تجاهل قناة الألفا إذا كانت واحدة + موجود. بالنسبة للقوام RGB و RGBA ، سيستخدم [page:WebGLRenderer WebGL] renderer + قناة اللون الأخضر عند عيّنة هذا القوام بسبب البت الإضافي + من الدقة المقدمة للأخضر في DXT-compressed وغير المضغوط RGB 565 + التنسيقات. ستعمل قوام الإضاءة فقط وقوام الإضاءة / الألفا أيضًا كما هو متوقع. +

+ +

[property:Texture displacementMap]

+

+ يؤثر خريطة التشوه على موضع رؤوس الشبكة. على عكس + الخرائط الأخرى التي تؤثر فقط على الضوء والظل من المادة + يمكن للرؤوس المشوهة أن تلقي ظلالًا ، وتحجب الأشياء الأخرى ، وغيرها + يعمل كهندسة حقيقية. نسيج التشوه هو صورة حيث القيمة + من كل بكسل (الأبيض هو الأعلى) يتم تعيينها ضد ، و + إعادة تحديد مواقع ، رؤوس الشبكة. +

+ +

[property:Float displacementScale]

+

+ مدى تأثير خريطة التشوه على الشبكة (حيث الأسود هو لا + التشوه ، والأبيض هو التشوه الأقصى). بدون تشوه + تم تعيين خريطة ، لا يتم تطبيق هذه القيمة. الافتراضية هى 1. +

+ +

[property:Float displacementBias]

+

+ إزاحة قيم خريطة التشوه على رؤوس شبكة المصفوفات. + بدون تعيين خريطة التشوه ، لا يتم تطبيق هذه القيمة. الافتراضية هى 0. +

+ +

[property:Texture map]

+

+ خريطة اللون. قد يتضمن اختيارًا قناة ألفا ، عادةً مجتمعة + مع [page:Material.transparent .transparent] أو [page:Material.alphaTest .alphaTest]. + الافتراضية هى null. +

+ +

الطرق (Methods)

+

انظر فئة [page:Material] الأساسية للطُرُق المشتركة.

+ +

المصدر (Source)

+

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/materials/MeshLambertMaterial.html b/docs/api/ar/materials/MeshLambertMaterial.html new file mode 100644 index 00000000000000..7d2d4894fd1d22 --- /dev/null +++ b/docs/api/ar/materials/MeshLambertMaterial.html @@ -0,0 +1,278 @@ + + + + + + + + + + [page:Material] → + +

[name]

+ +

+ مادة للأسطح غير اللامعة ، بدون تسليط الضوء اللامع.

+ + تستخدم المادة نموذجًا غير قائم على الفيزياء + [link:https://en.wikipedia.org/wiki/Lambertian_reflectance Lambertian] + لحساب الانعكاس. يمكن أن يحاكي هذا بعض الأسطح (مثل + الخشب غير المعالج أو الحجر) بشكل جيد ، ولكن لا يمكن محاكاة الأسطح اللامعة مع + تسليط الضوء اللامع (مثل الخشب المصقول). [name] يستخدم لكل قطعة + التظليل.

+ + نظرًا لبساطة نماذج الانعكاس والإضاءة ، + ستكون الأداء أكبر عند استخدام هذه المادة على + [page:MeshPhongMaterial] ، [page:MeshStandardMaterial] أو + [page:MeshPhysicalMaterial] ، على حساب بعض الدقة الرسومية. +

+ + + + + +

المنشئ (Constructor)

+ +

[name]( [param:Object parameters] )

+

+ [page:Object parameters] - (اختياري) كائن به واحد أو أكثر + خصائص تحدد مظهر المادة. يمكن تمرير أي خاصية من + المادة (بما في ذلك أي خاصية موروثة من [page:Material]) يمكن + تمريرها هنا.

+ + الاستثناء هو خاصية [page:Hexadecimal color] ، التي يمكن + تمريرها كسلسلة ست عشرية وهي `0xffffff` (أبيض) افتراضيًا. + يتم استدعاء [page:Color.set]( color ) داخليًا. +

+ +

الخصائص (Properties)

+

انظر فئة [page:Material] الأساسية للخصائص المشتركة.

+ +

[property:Texture alphaMap]

+

+ خريطة الألفا هي نسيج رمادي يتحكم في التعتيم عبر + السطح (أسود: شفاف تمامًا ؛ أبيض: غير شفاف تمامًا). الافتراضية هى + null.

+ + يتم استخدام لون النسيج فقط ، مع تجاهل قناة الألفا إذا كانت واحدة + موجود. بالنسبة للقوام RGB و RGBA ، سيستخدم [page:WebGLRenderer WebGL] renderer + قناة اللون الأخضر عند عيّنة هذا القوام بسبب البت الإضافي + من الدقة المقدمة للأخضر في DXT-compressed وغير المضغوط RGB 565 + التنسيقات. ستعمل قوام الإضاءة فقط وقوام الإضاءة / الألفا أيضًا كما هو متوقع. +

+ +

[property:Texture aoMap]

+

+ يتم استخدام قناة اللون الأحمر من هذه القوام كخريطة التظليل المحيط. + الافتراضية هى null. يتطلب aoMap مجموعة ثانية من UVs. +

+ +

[property:Float aoMapIntensity]

+

+ شدة تأثير التظليل المحيط. الافتراضية هى 1. صفر هو لا + تأثير التظليل. +

+ +

[property:Texture bumpMap]

+

+ القوام لإنشاء خريطة بروز. تُعيَّن قِيَمُ الأبْيَضِ والأسْوَدِ إلى + عُمْقٍ مُدْرَكٍ بالنسبة للأضواء. لا يؤثر Bump في الواقع + هندسة الكائن ، فقط الإضاءة. إذا تم تعريف خريطة طبيعية + سيتم تجاهل هذا. +

+ +

[property:Float bumpScale]

+

+ مدى تأثير خريطة البروز على المادة. المدى النموذجي هو 0-1. + الافتراضية هى 1. +

+ +

[property:Color color]

+

[page:Color] المادة ، افتراضيًا مضبوط على أبيض (0xffffff).

+ +

[property:Integer combine]

+

+ كيفية دمج نتيجة لون السطح مع خريطة البيئة ، + إن وجد.

+ + الخيارات هي [page:Materials THREE.MultiplyOperation] (الافتراضية) ، + [page:Materials THREE.MixOperation] ، [page:Materials THREE.AddOperation]. + إذا تم اختيار المزج ، يتم استخدام [page:.reflectivity] للمزج بين + اللونين. +

+ +

[property:Texture displacementMap]

+

+ يؤثر خريطة التشوه على موضع رؤوس الشبكة. على عكس + الخرائط الأخرى التي تؤثر فقط على الضوء والظل من المادة + يمكن للرؤوس المشوهة أن تلقي ظلالًا ، وتحجب الأشياء الأخرى ، وغيرها + يعمل كهندسة حقيقية. نسيج التشوه هو صورة حيث القيمة + من كل بكسل (الأبيض هو الأعلى) يتم تعيينها ضد ، و + إعادة تحديد مواقع ، رؤوس الشبكة. +

+ +

[property:Float displacementScale]

+

+ مدى تأثير خريطة التشوه على الشبكة (حيث الأسود هو لا + التشوه ، والأبيض هو التشوه الأقصى). بدون تشوه + تم تعيين خريطة ، لا يتم تطبيق هذه القيمة. الافتراضية هى 1. +

+ +

[property:Float displacementBias]

+

+ إزاحة قيم خريطة التشوه على رؤوس شبكة المصفوفات. + بدون تعيين خريطة التشوه ، لا يتم تطبیق هذه القیمۀ. الافتراضیۀ هى 0. +

+ +

[property:Color emissive]

+

+ لون المادة المُضِئ (الضوء) ، في الأساس لون صلب + لا يتأثر بإضاءة أخرى. الافتراضية هى black. +

+ +

[property:Texture emissiveMap]

+

+ قم بتعیین خریطۀ emissive (glow). الافتراضیۀ هى null. لون خریطۀ emissive + یتم تعدیلہ بلون emissive و شدۀ emissive. إذا كان لدیك + خریطۀ emissive، تأكد من تعیین لون emissive على شیء غیر + black. +

+ +

[property:Float emissiveIntensity]

+

+ شدۀ الضوء المُضِئ. یعدل لون emissive. الافتراضیۀ هى + 1. +

+ +

[property:Texture envMap]

+

خریطۀ البیئۀ. الافتراضیۀ هى null.

+ +

[property:Boolean flatShading]

+

+ تحديد ما إذا كان يتم عرض المادة بتظليل مسطح. الافتراضية هى + false. +

+ +

[property:Boolean fog]

+

ما إذا كانت المادة متأثرة بالضباب. الافتراضية هى `true`.

+ +

[property:Texture lightMap]

+

+ خريطة الضوء. الافتراضية هى null. يتطلب lightMap مجموعة ثانية من UVs. +

+ +

[property:Float lightMapIntensity]

+

شدة الضوء المخبوز. الافتراضية هى 1.

+ +

[property:Texture map]

+

+ خريطة اللون. قد يتضمن اختيارًا قناة ألفا ، عادةً مجتمعة + مع [page:Material.transparent .transparent] أو [page:Material.alphaTest .alphaTest]. + الافتراضية هى null. +

+ +

[property:Texture normalMap]

+

+ القوام لإنشاء خريطة طبيعية. تؤثر قيم RGB على سطح + الطبيعي لكل قطعة بكسل وتغير طريقة إضاءة اللون. الخرائط الطبيعية + لا تغير شكل السطح الفعلي ، فقط الإضاءة. في + في حال كانت المادة تحتوي على خريطة طبيعية باستخدام التقليد المستخدم باليد اليسرى + يجب إنكار مكون y من normalScale للتعويض + للاختلاف في التوجه. +

+ +

[property:Integer normalMapType]

+

+ نوع خريطة الطبيعية.

+ + الخيارات هي [page:constant THREE.TangentSpaceNormalMap] (الافتراضية) ، و + [page:constant THREE.ObjectSpaceNormalMap]. +

+ +

[property:Vector2 normalScale]

+

+ مدى تأثير خريطة الطبيعية على المادة. المدى النموذجي هو 0-1. + الافتراضية هى [page:Vector2] مضبوط على (1،1). +

+ +

[property:Float reflectivity]

+

+ مدى تأثير خريطة البيئة على السطح ؛ انظر أيضًا + [page:.combine]. +

+ +

[property:Float refractionRatio]

+

+ مؤشر الانكسار (IOR) للهواء (حوالي 1) مقسومًا على + مؤشر الانكسار للمادة. يتم استخدامه مع وضعيات تعيين البيئة + [page:Textures THREE.CubeRefractionMapping] و [page:Textures THREE.EquirectangularRefractionMapping]. + نسبة الانكسار لا يجب أن + تتجاوز 1. الافتراضية هى `0.98`. +

+ +

[property:Texture specularMap]

+

خريطة التسليط المستخدمة من قبل المادة. الافتراضية هى null.

+ +

[property:Boolean wireframe]

+

+ عرض الهندسة كإطار سلكي. الافتراضية هى `false` (أى عرض كمضلعات + مسطحة). +

+ +

[property:String wireframeLinecap]

+

+ تحديد مظهر نهايات الخطوط. القيم الممكنة هي "butt" و "round" و + "square". الافتراضية هى 'round'.

+ + يتوافق هذا مع + [link:https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D/lineCap 2D Canvas lineCap] + خاصية ويتم تجاهلها من قبل [page:WebGLRenderer WebGL] renderer. +

+ +

[property:String wireframeLinejoin]

+

+ تحديد مظهر مفاصل الخطوط. القيم الممكنة هي "round" و "bevel" و + "miter". الافتراضية هى 'round'.

+ + يتوافق هذا مع + [link:https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D/lineJoin 2D Canvas lineJoin] + خاصية ويتم تجاهلها من قبل [page:WebGLRenderer WebGL] renderer. +

+ +

[property:Float wireframeLinewidth]

+

+ يتحكم في سُمك الإطار السلكى. الافتراضية هى 1.

+ + نظرًا للقصور في + [link:https://www.khronos.org/registry/OpenGL/specs/gl/glspec46.core.pdf OpenGL Core Profile] + مع [page:WebGLRenderer WebGL] renderer على معظم + المنصات ستكون linewidth دائمًا 1 بغض النظر عن القيمة المحددة. +

+ +

الطرق (Methods)

+

انظر فئة [page:Material] الأساسية للطُرُق المشتركة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/materials/MeshMatcapMaterial.html b/docs/api/ar/materials/MeshMatcapMaterial.html new file mode 100644 index 00000000000000..99d8bfdaf5eb27 --- /dev/null +++ b/docs/api/ar/materials/MeshMatcapMaterial.html @@ -0,0 +1,160 @@ + + + + + + + + + + [page:Material] → + +

[name]

+ +

+ يتم تحديد [name] بواسطة نسيج MatCap (أو Lit Sphere) ، الذي يشفر لون المادة والظلال.

+ لا يستجيب [name] للأضواء لأن ملف صورة matcap يشفر الإضاءة المخبوزة. سيطرح ظلًا على كائن يتلقى الظلال (ويعمل قص الظلال) ، ولكنه لن يظلل نفسه أو يتلقى الظلال. +

+ + + + + +

المنشئ (Constructor)

+ +

[name]( [param:Object parameters] )

+

+ [page:Object parameters] - (اختياري) كائن يحتوي على خاصية واحدة أو أكثر تحدد مظهر المادة. يمكن تمرير أي خاصية من المادة (بما في ذلك أي خاصية موروثة من [page:Material]) هنا.

+ + الاستثناء هو الخاصية [page:Hexadecimal color] ، التي يمكن تمريرها كسلسلة ست عشرية وهي `0xffffff` (أبيض) بشكل افتراضي. يتم استدعاء [page:Color.set]( color ) داخليًا. +

+ +

الخصائص (Properties)

+

انظر إلى الفئة الأساسية [page:Material] للحصول على الخصائص المشتركة.

+ +

[property:Texture alphaMap]

+

+ خريطة الألفا هي نسيج رمادي يتحكم في التعتيم عبر + + +

[property:Texture alphaMap]

+

+ خريطة الألفا هي نسيج رمادي يتحكم في التعتيم عبر + السطح (أسود: شفاف تمامًا ؛ أبيض: غير شفاف تمامًا). الافتراضية هى + null.

+ + يتم استخدام لون النسيج فقط ، مع تجاهل قناة الألفا إذا كانت واحدة + موجود. بالنسبة للقوام RGB و RGBA ، سيستخدم [page:WebGLRenderer WebGL] renderer + قناة اللون الأخضر عند عيّنة هذا القوام بسبب البت الإضافي + من الدقة المقدمة للأخضر في DXT-compressed وغير المضغوط RGB 565 + التنسيقات. ستعمل قوام الإضاءة فقط وقوام الإضاءة / الألفا أيضًا كما هو متوقع. +

+ +

[property:Texture bumpMap]

+

+ القوام لإنشاء خريطة بروز. تُعيَّن قِيَمُ الأبْيَضِ والأسْوَدِ إلى + عُمْقٍ مُدْرَكٍ بالنسبة للأضواء. لا يؤثر Bump في الواقع + هندسة الكائن ، فقط الإضاءة. إذا تم تعريف خريطة طبيعية + سيتم تجاهل هذا. +

+ +

[property:Float bumpScale]

+

+ مدى تأثير خريطة البروز على المادة. المدى النموذجي هو 0-1. + الافتراضية هى 1. +

+ +

[property:Color color]

+

[page:Color] المادة ، افتراضيًا مضبوط على أبيض (0xffffff).

+ +

[property:Texture displacementMap]

+

+ يؤثر خريطة التشوه على موضع رؤوس الشبكة. على عكس + الخرائط الأخرى التي تؤثر فقط على الضوء والظل من المادة + يمكن للرؤوس المشوهة أن تلقي ظلالًا ، وتحجب الأشياء الأخرى ، وغيرها + يعمل كهندسة حقيقية. نسيج التشوه هو صورة حيث القيمة + من كل بكسل (الأبيض هو الأعلى) يتم تعيينها ضد ، و + إعادة تحديد مواقع ، رؤوس الشبكة. +

+ +

[خاصية:Float displacementScale]

+

+ كم يؤثر خريطة التشويه على الشبكة (حيث اللون الأسود لا يوجد تشويه ، واللون الأبيض هو التشويه الأقصى). بدون تعيين خريطة تشويه ، لا يتم تطبيق هذه القيمة. الافتراضي هو 1. +

+ +

[property:Float displacementBias]

+

+ إزاحة قيم خريطة التشوه على رؤوس شبكة المصفوفات. + بدون تعيين خريطة التشوه ، لا يتم تطبيق هذه القيمة. الافتراضية هى 0. +

+ +

[property:Boolean flatShading]

+

+ تحديد ما إذا كان يتم عرض المادة بتظليل مسطح. الافتراضية هى + false. +

+ +

[property:Boolean fog]

+

ما إذا كانت المادة متأثرة بالضباب. الافتراضية هى `true`.

+ +

[property:Texture map]

+

+ خريطة اللون. قد يتضمن اختيارًا قناة ألفا ، عادةً مجتمعة + مع [page:Material.transparent .transparent] أو [page:Material.alphaTest .alphaTest]. + الافتراضية هى null. يتم تعديل لون خريطة القوام بواسطة + diffuse [page:.color]. +

+ +

[property:Texture matcap]

+

خريطة matcap. الافتراضية هى null.

+ +

[property:Texture normalMap]

+

+ القوام لإنشاء خريطة طبيعية. تؤثر قيم RGB على سطح + الطبيعي لكل قطعة بكسل وتغير طريقة إضاءة اللون. الخرائط الطبيعية + لا تغير شكل السطح الفعلي ، فقط الإضاءة. في + في حال كانت المادة تحتوي على خريطة طبيعية باستخدام التقليد المستخدم باليد اليسرى + يجب إنكار مكون y من normalScale للتعويض + للاختلاف في التوجه. +

+ +

[property:Integer normalMapType]

+

+ نوع خريطة الطبيعية.

+ + الخيارات هي [page:constant THREE.TangentSpaceNormalMap] (الافتراضية) ، و + [page:constant THREE.ObjectSpaceNormalMap]. +

+ +

[property:Vector2 normalScale]

+

+ مدى تأثير خريطة الطبيعية على المادة. المدى النموذجي هو 0-1. + الافتراضية هى [page:Vector2] مضبوط على (1،1). +

+ +

الطرق (Methods)

+

انظر فئة [page:Material] الأساسية للطُرُق المشتركة.

+ +

المصدر (Source)

+

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/materials/MeshNormalMaterial.html b/docs/api/ar/materials/MeshNormalMaterial.html new file mode 100644 index 00000000000000..85a04d9281c8a5 --- /dev/null +++ b/docs/api/ar/materials/MeshNormalMaterial.html @@ -0,0 +1,118 @@ + + + + + + + + + + [page:Material] → + +

[name]

+ +

مادة تقوم بتعيين المتجهات الطبيعية إلى ألوان RGB.

+ + + + + +

المنشئ (Constructor)

+ +

[name]( [param:Object parameters] )

+

+ [page:Object parameters] - (اختياري) كائن يحتوي على خاصية واحدة أو أكثر تحدد مظهر المادة. يمكن تمرير أي خاصية من المادة (بما في ذلك أي خاصية موروثة من [page:Material]) هنا. +

+ +

الخصائص (Properties)

+

انظر إلى الفئة الأساسية [page:Material] للحصول على الخصائص المشتركة.

+ +

[property:Texture bumpMap]

+

+ النسيج لإنشاء خريطة بامب. تقوم القيم السوداء والبيضاء بتعيين العمق المتصور فيما يتعلق بالأضواء. لا يؤثر البامب فعليًا على هندسة الكائن ، فقط على الإضاءة. إذا تم تعريف خريطة طبيعية ، سيتم تجاهل هذا. +

+ +

[property:Float bumpScale]

+

+ كم يؤثر خريطة البامب على المادة. المدى النموذجي هو 0-1. الافتراضي هو 1. +

+ +

[property:Texture displacementMap]

+

+ تؤثر خريطة التشويه على موضع رؤوس الشبكة. على عكس الخرائط الأخرى التي تؤثر فقط على ضوء وظل المادة ، يمكن للرؤوس المزحزحة أن تلقي ظلالًا وتحجب كائنات أخرى وتتصرف بطرق أخرى كهندسة حقيقية. نسيج التشويه هو صورة حيث يتم تعيين قيمة كل بكسل (الأبيض هو الأعلى) ضد وإعادة تحديد موضع رؤوس الشبكة. +

+ +

[property:Float displacementScale]

+

+ كم يؤثر خريطة التشوه على الشبكة (حيث اللون الأسود لا يوجد تشويه ، واللون الأبيض هو التشويه الأقصى). بدون تعیین خریطۀ تشویه، لا یتم تطبیق هذه القیمۀ. الافتراضی هو 1. +

+ +

[property:Float displacementBias]

+

+ إزاحة قيم خريطة التشويه على رؤوس الشبكة. بدون تعيين خريطة تشويه ، لا يتم تطبيق هذه القيمة. الافتراضي هو 0. +

+ +

[property:Boolean flatShading]

+

+ تحديد ما إذا كان يتم عرض المادة بظلال مسطحة. الافتراضي هو خطأ. +

+ +

[property:Texture normalMap]

+

+ النسيج لإنشاء خريطة طبيعية. تؤثر قيم RGB على السطح الطبيعي لكل قطعة بكسل وتغير طريقة إضاءة اللون. لا تغير خرائط العادية شكل السطح الفعلي ، فقط الإضاءة. في حال كانت المادة تحتوي على خريطة طبيعية مؤلفة باستخدام اتفاقية اليد اليسرى ، يجب إنكار مكون y من normalScale للتعويض عن اختلاف التوجه. +

+ +

[property:Integer normalMapType]

+

+ نوع خريطة العادية.

+ + الخيارات هي [page:constant THREE.TangentSpaceNormalMap] (افتراضي) ، و [page:constant THREE.ObjectSpaceNormalMap]. +

+ +

[property:Vector2 normalScale]

+

+ كم يؤثر خريطة العادية على المادة. المدى النموذجي هو 0-1. الافتراضي هو [page:Vector2] مضبوط على (1،1). +

+ +

[property:Boolean wireframe]

+

+ عرض الهندسة كإطار سلكي. الافتراضي هو false (أي عرض كظلال ناعمة). +

+ +

[property:Float wireframeLinewidth]

+

+ التحكم في سمك الإطار السلكى. الافتراضى هو 1.

+ + بسبب قصور + [link:https://www.khronos.org/registry/OpenGL/specs/gl/glspec46.core.pdf OpenGL Core Profile] + مع [page:WebGLRenderer WebGL] renderer على معظم + المنصات ستكون linewidth دائمًا 1 بغض النظر عن القيمة المحددة. +

+ +

الطرق (Methods)

+

انظر إلى فئة [page:Material] الأساسية للحصول على طرق شائعة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/materials/MeshPhongMaterial.html b/docs/api/ar/materials/MeshPhongMaterial.html new file mode 100644 index 00000000000000..ccf3d8cd3b0486 --- /dev/null +++ b/docs/api/ar/materials/MeshPhongMaterial.html @@ -0,0 +1,284 @@ + + + + + + + + + + [page:Material] → + +

[name]

+ +

+ مادة للأسطح اللامعة مع تسليط الضوء على التجمعات.

+ + تستخدم المادة نموذجًا غير مستندًا إلى الفيزياء + [link:https://en.wikipedia.org/wiki/Blinn-Phong_shading_model Blinn-Phong] + لحساب الانعكاس. على عكس نموذج Lambertian المستخدم في + [page:MeshLambertMaterial] يمكن لهذا محاكاة الأسطح اللامعة مع تسليط الضوء على التجمعات (مثل الخشب المصقول). يستخدم [name] تظليل لكل قطعة.

+ + سيكون الأداء عمومًا أكبر عند استخدام هذه المادة على + [page:MeshStandardMaterial] أو [page:MeshPhysicalMaterial] ، بتكلفة بعض الدقة الرسومية. +

+ + + + + + +

المنشئ (Constructor)

+ +

[name]( [param:Object parameters] )

+

+ [page:Object parameters] - (اختياري) كائن بواحد أو أكثر + خصائص تحدد مظهر المادة. يمكن تمرير أي خاصية من + المادة (بما في ذلك أي خاصية موروثة من [page:Material]) هنا.

+ + الاستثناء هو الخاصية [page:Hexadecimal color]، التي يمكن + تمريرها كسلسلة ست عشرية وهي `0xffffff` (أبيض) بشكل افتراضي. + يتم استدعاء [page:Color.set]( color ) داخليًا. +

+ +

الخصائص (Properties)

+

انظر إلى فئة [page:Material] الأساسية للخصائص المشتركة.

+ +

[property:Texture alphaMap]

+

+ خريطة الألفا هي قوام رمادي يتحكم في التعتيم عبر + السطح (أسود: شفاف تمامًا؛ أبيض: غير شفاف تمامًا). الافتراضي هو + null.

+ + يتم استخدام لون القوام فقط، مع تجاهل قناة الألفا إذا كانت موجودة + . بالنسبة لقوام RGB و RGBA، سيستخدم [page:WebGLRenderer WebGL] renderer + قناة اللون الأخضر عند أخذ عينات من هذا القوام بسبب البت الإضافي + من الدقة المقدمة للأخضر في تنسيقات DXT المضغوطة و RGB 565 غير المضغوطة + . ستعمل قوام الإضاءة فقط وقوام الإضاءة / الألفا أيضًا كما هو متوقع. +

+ +

[property:Texture aoMap]

+

+ يتم استخدام قناة اللون الأحمر من هذه القوام كخريطة التظليل المحيط. + الافتراضي هو null. يتطلب aoMap مجموعة ثانية من UVs. +

+ +

[property:Float aoMapIntensity]

+

+ شدة تأثير التظليل المحيط. الافتراضي هو 1. صفر هو لا + تأثير التظليل. +

+ +

[property:Texture bumpMap]

+

+ القوام لإنشاء خريطة بروز. تُعيَّن قِيَمُ الأبْيَضِ والأسْودِ إلى + العُمْقِ المُدْرَكِ في علاقة بالأضْواءِ. لا يؤثر التَّبَّرُ فِعْلِيًّا على + هندسة الكائن، فقط على الإضاءة. إذا تم تعريف خريطة عادية + سيتم تجاهل هذا. +

+ +

[property:Float bumpScale]

+

+ مدى تأثير خريطة التببر على المادة. المدى النموذجى هو 0-1. + الافتراضى هو 1. +

+ +

[property:Color color]

+

[page:Color] المادة، بشكل افتراضى مضبوط على أبيض (0xffffff).

+ +

[property:Integer combine]

+

+ كيفية دمج نتائج لون السطح مع خرائط البيئة، إذا كانت موجودة.

+ + الخيارات هى [page:Materials THREE.MultiplyOperation] (الافتراضى)، + [page:Materials THREE.MixOperation]، [page:Materials THREE.AddOperation]. + إذا تم اختيار mix، يُستخدَم [page:.reflectivity] للدمج بين + اللونَيْنِ. +

+ +

[property:Texture displacementMap]

+

+ تؤثر خريطة التشويه على موضع رؤوس الشبكة. على عكس + الخرائط الأخرى التي تؤثر فقط على الضوء والظل للمادة + يمكن للرؤوس المشوهة أن تلقي ظلالًا، وتحجب كائنات أخرى، وغير ذلك + تعمل كهندسة حقيقية. خريطة التشويه هي صورة حيث قيمة + كل بكسل (الأبيض هو الأعلى) يتم تعيينها ضد، و + إعادة تحديد مواقع، رؤوس الشبكة. +

+ +

[property:Float displacementScale]

+

+ مدى تأثير خريطة التشويه على الشبكة (حيث الأسود هو لا + تشويه، والأبيض هو التشويه الأقصى). بدون تعيين خريطة تشويه، + لا يتم تطبيق هذه القيمة. الافتراضي هو 1. +

+ +

[property:Float displacementBias]

+

+ إزاحة قيم خريطة التشويه على رؤوس شبكة المادة. + بدون تعيين خريطة تشويه، لا يتم تطبيق هذه القيمة. الافتراضي هو 0. +

+ +

[property:Color emissive]

+

+ لون المادة المُضاء (الضوء)، في الأساس لون صلب + غير متأثر بالإضاءة الأخرى. الافتراضي هو أسود. +

+ +

[property:Texture emissiveMap]

+

+ تعيين خريطة المُضاء (التوهج). الافتراضي هو null. يتم تعديل لون خريطة المُضاء بواسطة + اللون المُضاء وشدة المُضاء. إذا كان لديك خريطة مُضاء، تأكد من تعديل + اللون المُضاء إلى شئ غير أسود. +

+ +

[property:Float emissiveIntensity]

+

+ شدة الضوء المُضاء. يعدل اللون المُضاء. الافتراضي هو + 1. +

+ +

[property:Texture envMap]

+

خريطة البيئة. الافتراضية هى null.

+ +

[property:Boolean flatShading]

+

+ تحديد ما إذا كان يتم تصدير المادة بظلال مسطحة. الافتراضى هو + false. +

+ +

[property:Boolean fog]

+

ما إذا كانت المادة متأثرة بالضباب. الافتراضى هو `true`.

+ +

[property:Texture lightMap]

+

+ خرائط الإضاءة. الافتراضى هى null. يتطلب lightMap مجموعة ثانية من UVs. +

+ +

[property:Float lightMapIntensity]

+

شدة الضوء المخبوز. الافتراضي هو 1.

+ +

[property:Texture map]

+

+ خريطة اللون. قد تشمل اختياريًا قناة ألفا، عادةً ما تكون مجتمعة + مع [page:Material.transparent .transparent] أو [page:Material.alphaTest .alphaTest]. + الافتراضي هو null. يتم تعديل لون خريطة القوام بواسطة + اللون المنتشر [page:.color]. +

+ +

[property:Texture normalMap]

+

+ القوام لإنشاء خريطة عادية. تؤثر قيم RGB على السطح + العادي لكل جزء بكسل وتغير طريقة إضاءة اللون. لا تغير خرائط العادية + شكل السطح الفعلي، فقط الإضاءة. في حال كانت المادة تحتوي على خريطة عادية مصممة باستخدام المعايير + اليد اليسرى، يجب إنكار مكون y من normalScale للتعويض + عن اختلاف المعايير. +

+ +

[property:Integer normalMapType]

+

+ نوع خريطة العادية.

+ + الخيارات هي [page:constant THREE.TangentSpaceNormalMap] (الافتراضي)، و + [page:constant THREE.ObjectSpaceNormalMap]. +

+ +

[property:Vector2 normalScale]

+

+ مدى تأثير خريطة العادية على المادة. المدى النموذجي هو 0-1. + الافتراضي هو [page:Vector2] مضبوط على (1,1). +

+ +

[property:Float reflectivity]

+

+ مدى تأثير خريطة البيئة على السطح؛ انظر أيضًا + [page:.combine]. قيمة الافتراضية هي 1 والمدى الصحيح هو بين 0 + (لا انعكاسات) و 1 (انعكاسات كاملة). +

+ +

[property:Float refractionRatio]

+

+ مؤشر انكسار (IOR) الهواء (حوالى 1) مقسومًا على + مؤشر انكسار المادة. يستخدم مع وضع خرائط البيئة [page:Textures THREE.CubeRefractionMapping] و [page:Textures THREE.EquirectangularRefractionMapping]. + لا يجب أن يتجاوز نسبة الانكسار 1. الافتراضي هو `0.98`. +

+ +

[property:Float shininess]

+

+ مدى لمعان [page:.specular] highlight؛ قيمة أعلى تعطي + highlight أكثر حدة. الافتراضي هو `30`. +

+ +

[property:Color specular]

+

+ لون المواد العاكس. الافتراضي هو [page:Color] مضبوط على + `0x111111` (رمادي داكن جدًا).

+ + هذا يحدد مدى لمعان المادة ولون لمعانها. +

+ +

[property:Texture specularMap]

+

+ قيمة خريطة العاكس تؤثر على كل من مدى مساهمة تسليط الضوء على السطح العاكس ومدى تأثير خريطة البيئة على السطح. الافتراضي هو null. +

+ +

[property:Boolean wireframe]

+

+ تصيير الهندسة كإطار سلكي. الافتراضي هو `false` (أي تصيير كمضلعات مسطحة). +

+ +

[property:String wireframeLinecap]

+

+ تحديد مظهر نهايات الخطوط. القيم الممكنة هي "butt" و "round" و "square". الافتراضي هو 'round'.

+ + هذا يتوافق مع + [link:https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D/lineCap خاصية lineCap لـ 2D Canvas] + ويتم تجاهله من قبل [page:WebGLRenderer WebGL] renderer. +

+ +

[property:String wireframeLinejoin]

+

+ تحديد مظهر مفاصل الخطوط. القيم الممكنة هي "round" و "bevel" و "miter". الافتراضي هو 'round'.

+ + هذا يتوافق مع + [link:https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D/lineJoin خاصية lineJoin لـ 2D Canvas] + ويتم تجاهله من قبل [page:WebGLRenderer WebGL] renderer. +

+ +

[property:Float wireframeLinewidth]

+

+ التحكم في سُمك الإطار السلكي. الافتراضي هو 1.

+ + بسبب قيود + [link:https://www.khronos.org/registry/OpenGL/specs/gl/glspec46.core.pdf OpenGL Core Profile] + مع [page:WebGLRenderer WebGL] renderer على معظم + المنصات ستكون linewidth دائمًا 1 بغض النظر عن القيمة المحددة. +

+ +

الطرق (Methods)

+

انظر إلى فئة [page:Material] الأساسية للطرق المشتركة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/materials/MeshPhysicalMaterial.html b/docs/api/ar/materials/MeshPhysicalMaterial.html new file mode 100644 index 00000000000000..06889665cbfb1b --- /dev/null +++ b/docs/api/ar/materials/MeshPhysicalMaterial.html @@ -0,0 +1,273 @@ + + + + + + + + + + [page:Material] → [page:MeshStandardMaterial] → + +

[name]

+ +

+ تمديد لـ [page:MeshStandardMaterial]، يوفر خصائص تقديم + أكثر تقدمًا على أساس الفيزياء: +

+ +
    +
  • + طلاء شفاف: بعض المواد - مثل طلاءات السيارات والألياف الكربونية و + الأسطح الرطبة - تتطلب طبقة شفافة عاكسة فوق طبقة أخرى + قد تكون غير منتظمة أو خشنة. يقرب الطلاء الشفاف هذا التأثير، + دون الحاجة إلى سطح شفاف منفصل. +
  • +
  • + الشفافية المستندة إلى الفيزياء: إحدى قيود + [page:Material.opacity .opacity] هو أن المواد شديدة الشفافية + أقل انعكاسية. يوفر [page:.transmission] المستند إلى الفيزياء + خيارًا أكثر واقعية للأسطح الشفافة الرقيقة مثل الزجاج. +
  • +
  • + انعكاسية متقدمة: انعكاسية أكثر مرونة للمواد غير المعدنية. +
  • +
  • + اللمعان: يمكن استخدامه لتمثيل مواد القماش والأقمشة. +
  • +
+ +

+ نتيجة لهذه الميزات المعقدة للظلال، يحتوي MeshPhysicalMaterial على + تكلفة أداء أعلى، لكل بكسل، من مواد three.js الأخرى. معظم + التأثيرات معطلة افتراضيًا، وتضيف التكلفة كما يتم تمكينها. للحصول على أفضل النتائج، حدد دائمًا [page:.envMap خريطة بيئة] عند استخدام + هذه المادة. +

+ + + + + +

أمثلة (Examples)

+

+ [example:webgl_materials_physical_clearcoat materials / physical / clearcoat]
+ [example:webgl_loader_gltf_sheen loader / gltf / sheen]
+ [example:webgl_materials_physical_transmission materials / physical / transmission] +

+ +

المنشئ (Constructor)

+ +

[name]( [param:Object parameters] )

+

+ [page:Object parameters] - (اختياري) كائن بواحد أو أكثر + خصائص تحدد مظهر المادة. يمكن تمرير أي خاصية من + المادة (بما في ذلك أي خاصية موروثة من [page:Material] و + [page:MeshStandardMaterial]) هنا.

+ + الاستثناء هو الخاصية [page:Hexadecimal color]، التي يمكن + تمريرها كسلسلة ست عشرية وهي `0xffffff` (أبيض) بشكل افتراضي. + يتم استدعاء [page:Color.set]( color ) داخليًا. +

+ +

الخصائص (Properties)

+

+ انظر إلى فئات [page:Material] و [page:MeshStandardMaterial] الأساسية للخصائص المشتركة. +

+ +

[property:Color attenuationColor]

+

+ اللون الذي يتحول إليه الضوء الأبيض بسبب الامتصاص عند الوصول إلى + مسافة التخفيف. الافتراضى هو `white` (0xffffff). +

+ +

[property:Float attenuationDistance]

+

+ كثافة الوسط المعطاة كمتوسط ​​المسافة التي يسافرها الضوء في + الوسط قبل التفاعل مع جزيئة. يتم إعطاء القيمة في وحدات مساحة العالم + ، ويجب أن تكون أكبر من الصفر. الافتراضي هو `Infinity`. +

+ +

[property:Float clearcoat]

+

+ يمثل شدة طبقة الطلاء الشفاف، من `0.0` إلى `1.0`. استخدم + خصائص طلاء شفاف ذات الصلة لتمكين المواد متعددة الطبقات التي لديها + طبقة رقيقة شفافة فوق الطبقة الأساسية. الافتراضى هو `0.0`. +

+ +

[property:Texture clearcoatMap]

+

+ تتم ضرب قناة اللون الأحمر من هذه القوام ضد [page:.clearcoat]، + للتحكم في شدة طلاء لكل بكسل. الافتراضى هى `null`. +

+ +

[property:Texture clearcoatNormalMap]

+

+ يمكن استخدامه لتمكين المعايير المستقلة لطبقة الطلاء الشفاف. + الافتراضى هى `null`. +

+ +

[property:Vector2 clearcoatNormalScale]

+

+ مدى تأثير [page:.clearcoatNormalMap] على طبقة الطلاء الشفاف، من + `(0,0)` إلى `(1,1)`. الافتراضى هو `(1,1)`. +

+ +

[property:Float clearcoatRoughness]

+

+ خشونة طبقة الطلاء الشفاف، من `0.0` إلى `1.0`. الافتراضى هو `0.0`. +

+ +

[property:Texture clearcoatRoughnessMap]

+

+ تتم ضرب قناة اللون الأخضر من هذه القوام ضد + [page:.clearcoatRoughness]، للتحكم في خشونة طلاء لكل بكسل. + الافتراضى هى `null`. +

+ +

[property:Object defines]

+

+ كائن من نوع: + + { + 'STANDARD': '', + 'PHYSICAL': '', + }; + + + يستخدم هذا بواسطة [page:WebGLRenderer] لتحديد المُظَهِّرات. +

+ +

[property:Float dispersion]

+

+ Defines the strength of the angular separation of colors (chromatic aberration) transmitting through a relatively clear volume. + Any value zero or larger is valid, the typical range of realistic values is `[0, 1]`. + Default is `0` (no dispersion). + This property can be only be used with transmissive objects, see [page:.transmission]. +

+ +

[property:Float ior]

+

+ Index-of-refraction for non-metallic materials, from `1.0` to `2.333`. + Default is `1.5`.
+

+ +

[property:Float reflectivity]

+

+ درجة الانعكاسية، من `0.0` إلى `1.0`. الافتراضي هو `0.5`، الذي + يتوافق مع مؤشر انكسار 1.5.
+ + هذا ينمذج انعكاسية المواد غير المعدنية. ليس له تأثير + عندما يكون [page:MeshStandardMaterial.metalness metalness] هو `1.0` +

+ +

[property:Float sheen]

+

+ شدة طبقة اللمعان، من `0.0` إلى `1.0`. الافتراضي هو `0.0`. +

+ +

[property:Float sheenRoughness]

+

خشونة طبقة اللمعان، من `0.0` إلى `1.0`. الافتراضي هو `1.0`.

+ +

[property:Texture sheenRoughnessMap]

+

+ تتم ضرب قناة الألفا من هذه القوام ضد + [page:.sheenRoughness]، للتحكم في خشونة اللمعان لكل بكسل. + الافتراضى هى `null`. +

+ +

[property:Color sheenColor]

+

لون اللمعان. الافتراضي هو `0x000000`، أسود.

+ +

[property:Texture sheenColorMap]

+

+ تتم ضرب قنوات RGB من هذه القوام ضد + [page:.sheenColor]، للتحكم في لون اللمعان لكل بكسل. الافتراضي + هو `null`. +

+ +

[property:Float specularIntensity]

+

+ عدد عائم يُقيِّس كمية الانعكاس العاكس للأشياء غير المعدنية فقط. + عند تعيينه على صفر، يصبح النموذج فعالًا كـ Lambertian. من `0.0` إلى + `1.0`. الافتراضى هو `1.0`. +

+ +

[property:Texture specularIntensityMap]

+

+ تتم ضرب قناة الألفا من هذه القوام ضد + [page:.specularIntensity]، للتحكم في شدة الانعكاس العاكس لكل بكسل. + الافتراضى هى `null`. +

+ +

[property:Color specularColor]

+

+ [page:Color] يلون الانعكاس العاكس عند التعرض الطبيعي للأشياء غير المعدنية فقط. الافتراضي هو `0xffffff`، أبيض. +

+ +

[property:Texture specularColorMap]

+

+ تتم ضرب قنوات RGB من هذه القوام ضد + [page:.specularColor]، للتحكم في لون العاكس لكل بكسل. الافتراضي + هو `null`. +

+ +

[property:Float thickness]

+

+ سُمْكُ الحجمِ تحتَ السطحِ. يُعطى القيمةُ في + مساحة التنسيق للشبكة. إذا كانت القيمة 0 فإن المادة + رقيقة الجدران. وإلا فإن المادة هي حدود حجم. الافتراضي هو `0`. +

+ +

[property:Texture thicknessMap]

+

+ قوام يحدد السُمْكَ، مخزن في قناة G. سيتم ضرب هذا بـ + [page:.thickness]. الافتراضي هو `null`. +

+ +

[property:Float transmission]

+

+ درجة الإرسال (أو الشفافية البصرية)، من `0.0` إلى `1.0`. + الافتراضي هو `0.0`.
+ + المواد البلاستيكية أو الزجاجية رقيقة أو شفافة أو نصف شفافة تظل + عاكسة إلى حد كبير حتى لو كانت مُرسِلَة بالكامل. يمكن استخدام خاصية الإرسال لنمذجة هذه المواد.
+ + عندما يكون الإرسال غير صفر، يجب تعيين [page:Material.opacity opacity] `إلى `1. +

+ +

[property:Texture transmissionMap]

+

+ تتم ضرب قناة اللون الأحمر من هذه القوام ضد + [page:.transmission]، للتحكم في الشفافية البصرية لكل بكسل. + الافتراضى هى `null`. +

+ +

الطرق (Methods)

+

+ انظر إلى فئات [page:Material] و [page:MeshStandardMaterial] الأساسية للطرق المشتركة. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/materials/MeshStandardMaterial.html b/docs/api/ar/materials/MeshStandardMaterial.html new file mode 100644 index 00000000000000..1613fcbb3a6cbc --- /dev/null +++ b/docs/api/ar/materials/MeshStandardMaterial.html @@ -0,0 +1,312 @@ + + + + + + + + + + [page:Material] → + +

[name]

+ +

+ مادة قائمة على الفيزياء القياسية، باستخدام سير عمل Metallic-Roughness.

+ + أصبح التقديم القائم على الفيزياء (PBR) مؤخرًا المعيار في العديد من + تطبيقات 3D، مثل + [link:https://blogs.unity3d.com/2014/10/29/physically-based-shading-in-unity-5-a-primer/ Unity]، + [link:https://docs.unrealengine.com/latest/INT/Engine/Rendering/Materials/PhysicallyBased/ Unreal] و + [link:http://area.autodesk.com/blogs/the-3ds-max-blog/what039s-new-for-rendering-in-3ds-max-2017 3D Studio Max].

+ + هذا النهج يختلف عن النهج الأقدم في أنه بدلاً من استخدام + تقريبات لطريقة تفاعل الضوء مع سطح، يتم استخدام نموذج صحيح فيزيائيًا. الفكرة هي أنه، بدلاً من تعديل + المواد لتبدو جيدة تحت إضاءة محددة، يمكن إنشاء مادة + سوف تتفاعل 'بشكل صحيح' تحت جميع سيناريوهات الإضاءة.

+ + عمليًا يعطي هذا نتيجة أكثر دقة وواقعية من + [page:MeshLambertMaterial] أو [page:MeshPhongMaterial]، بتكلفة + أن يكون أكثر تكلفة حسابية قليلًا. يستخدم [name] التظليل لكل جزء.

+ + لاحظ أنه للحصول على أفضل النتائج يجب دائمًا تحديد [page:.envMap خريطة بيئة] + عند استخدام هذه المادة.

+ + لمقدمة غير فنية لمفهوم PBR وكيفية إعداد مادة PBR، تحقق من هذه المقالات من قِبَل الأشخاص في + [link:https://www.marmoset.co marmoset]: +

+
    +
  • + [link:https://www.marmoset.co/posts/basic-theory-of-physically-based-rendering/ Basic Theory of Physically Based Rendering] +
  • +
  • + [link:https://www.marmoset.co/posts/physically-based-rendering-and-you-can-too/ Physically Based Rendering and You Can Too] +
  • +
+

+ التفاصيل الفنية للنهج المستخدم في three.js (ومعظم نظم PBR الأخرى) + يمكن العثور على هذا + [link:https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf ورقة من Disney] + (pdf)، بواسطة Brent Burley. +

+ + + + + +

المنشئ (Constructor)

+ +

[name]( [param:Object parameters] )

+

+ [page:Object parameters] - (اختياري) كائن بواحد أو أكثر + خصائص تحدد مظهر المادة. يمكن تمرير أي خاصية من + المادة (بما في ذلك أي خاصية موروثة من [page:Material]) هنا.

+ + الاستثناء هو الخاصية [page:Hexadecimal color]، التي يمكن + تمريرها كسلسلة ست عشرية وهي `0xffffff` (أبيض) بشكل افتراضي. + يتم استدعاء [page:Color.set]( color ) داخليًا. +

+ +

الخصائص (Properties)

+

انظر إلى فئة [page:Material] الأساسية للخصائص المشتركة.

+ +

[property:Texture alphaMap]

+

+ خريطة الألفا هي قوام رمادي يتحكم في الشفافية عبر + السطح (أسود: شفاف تمامًا؛ أبيض: غير شفاف تمامًا). الافتراضى هى null.

+ + يُستخدَم فقط لون القوام، متجاهلاً قناة الألفا إذا كانت واحدة + موجود. بالنسبة لقوام RGB و RGBA، سوف يستخدم [page:WebGLRenderer WebGL] renderer + قناة اللون الأخضر عند أخذ عينات من هذه القوام بسبب البت الإضافي + الدقة المقدمة للأخضر في تنسيقات DXT المضغوطة و RGB 565 غير المضغوطة. + ستعمل قوام الإضاءة فقط وقوام الإضاءة / الألفا أيضًا كما هو متوقع. +

+ +

[property:Texture aoMap]

+

+ يُستخدَم قناة اللون الأحمر من هذه القوام كخريطة احتجاب محيط. + الافتراضى هى null. يتطلب aoMap مجموعة ثانية من UVs. +

+ +

[property:Float aoMapIntensity]

+

+ شدة تأثير الاحتجاب المحيط. الافتراضى هو 1. الصفر هو لا + تأثير احتجاب. +

+ +

[property:Texture bumpMap]

+

+ القوام لإنشاء خريطة بروز. تُعين قِيَمُ الأبيض والأسود إلى + العمق المدرك فيما يتعلق بالأضواء. لا يؤثر البروز فعليًا + على هندسة الكائن، فقط الإضاءة. إذا تم تعريف خريطة عادية + سيتم تجاهل هذا. +

+ +

[property:Float bumpScale]

+

+ مدى تأثير خريطة البروز على المادة. المدى النموذجى هو 0-1. + الافتراضى هو 1. +

+ +

[property:Color color]

+

[page:Color] المادة، بشكل افتراضى مضبوط على أبيض (0xffffff).

+ +

[property:Object defines]

+

+ كائن من نوع: + { 'STANDARD': '' }; + + يستخدم هذا بواسطة [page:WebGLRenderer] لتحديد المُظَهِّرات. +

+ +

[property:Texture displacementMap]

+

+ تؤثر خريطة التشويه على موضع رؤوس شبكة المادة. على عكس + خرائط أخرى التي تؤثر فقط على الضوء والظل للمادة + الرؤوس المشوهة يمكن أن تلقي ظلالًا، وتحجب كائنات أخرى، وغير ذلك + العمل كهندسة حقيقية. خريطة التشويه هي صورة حيث قِيَمَةُ كل بكسل (الأبيض هو الأعلى) مُعَيَّنَةٌ ضد، و + إعادة تحديد مواقع، رؤوس الشبكة. +

+ +

[property:Float displacementScale]

+

+ مدى تأثير خريطة التشويه على الشبكة (حيث اللون الأسود هو لا + التشويه، والأبيض هو التشويه الأقصى). بدون تعيين خريطة تشويه، + لا يتم تطبيق هذه القيمة. الافتراضى هو 1. +

+ +

[property:Float displacementBias]

+

+ إزاحة قِيَمِ خريطة التشويه على رؤوس شبكة المادة. + بدون تعيين خريطة تشويه، لا يتم تطبيق هذه القيمة. الافتراضى هو 0. +

+ +

[property:Color emissive]

+

+ لون المادة المُضِيءِ (الضوء)، عبارة عن لون صلب + غير متأثر بإضاءة أخرى. الافتراضى هو أسود. +

+ +

[property:Texture emissiveMap]

+

+ تعيين خريطة مُضِيئَة (مُضِيئَة). الافتراضى هى null. يتم تعديل لون خريطة المُضِيئَة + بواسطة اللون المُضِيئَ وشدة المُضِيئَ. إذا كان لديك + خريطة مُضِيئَ، تأكد من تعيين اللون المُضِيئَ إلى شيء آخر + غير أسود. +

+ +

[property:Float emissiveIntensity]

+

+ شدة الضوء المُضِيئَ. يعدل اللون المُضِيئَ. الافتراضى هو + 1. +

+ +

[property:Texture envMap]

+

+ خريطة البيئة. لضمان التقديم الصحيح فيزيائًا، يجب عليك + فقط إضافة خرائط بيئية تم معالجتها مسبقًا بـ + [page:PMREMGenerator]. الافتراضى هى null. +

+ +

[property:Float envMapIntensity]

+

تقوم بتحجيم تأثير خريطة البيئة عن طريق ضرب لونها.

+ +

[property:Boolean flatShading]

+

+ تحديد ما إذا كان يتم تصدير المادة بظلال مسطحة. الافتراضى هو + false. +

+ +

[property:Boolean fog]

+

ما إذا كانت المادة متأثرة بالضباب. الافتراضى هو `true`.

+ +

[property:Boolean isMeshStandardMaterial]

+

علامة للقراءة فقط للتحقق مما إذا كان كائن معين من نوع [name].

+ +

[property:Texture lightMap]

+

+ خريطة الضوء. الافتراضى هى null. يتطلب lightMap مجموعة ثانية من UVs. +

+ +

[property:Float lightMapIntensity]

+

شدة الضوء المخبوز. الافتراضى هو 1.

+ +

[property:Texture map]

+

+ خريطة اللون. قد تشمل اختياريًا قناة ألفا، عادةً مجتمعة + مع [page:Material.transparent .transparent] أو [page:Material.alphaTest .alphaTest]. + الافتراضى هى null. يتم تعديل لون خريطة القوام بواسطة اللون المُشْتَتِّ [page:.color]. +

+ +

[property:Float metalness]

+

+ مدى شبه المادة بالمعدن. المواد غير المعدنية مثل الخشب + أو الحجر استخدم 0.0، المعدن استخدم 1.0، مع عدم وجود شيء (عادة) في الوسط. + الافتراضى هو 0.0. يمكن استخدام قيمة بين 0.0 و 1.0 للحصول على مظهر معدن صدئ. + إذا تم توفير metalnessMap أيضًا، تتم ضرب كلا القيمتين. +

+ +

[property:Texture metalnessMap]

+

+ يُستخدَم قناة اللون الأزرق من هذه القوام لتغيير شبه المادة + بالمعدن. +

+ +

[property:Texture normalMap]

+

+ القوام لإنشاء خريطة عادية. تؤثر قِيَمُ RGB على سطح + الطبيعية لكل جزء بكسل وتغير طريقة إضاءة اللون. خرائط عادية + لا تغير شكل السطح الفعلي، فقط الإضاءة. في حال كانت المادة لديها خريطة عادية باستخدام التقليد + الذي يستخدم يدًا يسرى، يجب إنكار مكوِّن y من normalScale للتعويض + على التفاوت في التسلسل. +

+ +

[property:Integer normalMapType]

+

+ نوع خريطة عادية.

+ + الخيارات هى [page:constant THREE.TangentSpaceNormalMap] (الافتراضى)، و + [page:constant THREE.ObjectSpaceNormalMap]. +

+ +

[property:Vector2 normalScale]

+

+ مدى تأثير خريطة عادية على المادة. المدى النموذجى هو 0-1. + الافتراضى هو [page:Vector2] مضبوط على (1,1). +

+ +

[property:Float roughness]

+

+ مدى خشونة المادة. 0.0 يعني انعكاس مرآة ناعم، 1.0 + يعني تشتت كامل. الافتراضي هو 1.0. إذا تم توفير roughnessMap أيضًا، + تتم ضرب كلا القيمتين. +

+ +

[property:Texture roughnessMap]

+

+ يُستخدَم قناة اللون الأخضر من هذه القوام لتغيير خشونة + المادة. +

+ +

[property:Boolean wireframe]

+

+ تصدير الهندسة كإطار سلكي. الافتراضى هو `false` (أي تصدير كأشكال مسطحة). +

+ +

[property:String wireframeLinecap]

+

+ تحديد مظهر نهايات الخطوط. القيم الممكنة هى "butt"، "round" و + "square". الافتراضى هو 'round'.

+ + هذا يتوافق مع + [link:https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D/lineCap 2D Canvas lineCap] + خاصية ويتم تجاهله بواسطة [page:WebGLRenderer WebGL] renderer. +

+ +

[property:String wireframeLinejoin]

+

+ تحديد مظهر مفاصل الخطوط. القيم الممكنة هى "round"، "bevel" و + "miter". الافتراضى هو 'round'.

+ + هذا يتوافق مع + [link:https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D/lineJoin 2D Canvas lineJoin] + خاصية ويتم تجاهله بواسطة [page:WebGLRenderer WebGL] renderer. +

+ +

[property:Float wireframeLinewidth]

+

+ يتحكم في سُمْكِ الإطارِ السلكِيِّ. الافتراضى هو 1.

+ + بسبب قيود + [link:https://www.khronos.org/registry/OpenGL/specs/gl/glspec46.core.pdf OpenGL Core Profile] + مع [page:WebGLRenderer WebGL] renderer على معظم + المنصات سيظل linewidth دائمًا 1 بغض النظر عن القيمة المحددة. +

+ +

الطرق (Methods)

+

انظر إلى فئة [page:Material] الأساسية للطرق المشتركة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/materials/MeshToonMaterial.html b/docs/api/ar/materials/MeshToonMaterial.html new file mode 100644 index 00000000000000..ce6af157167871 --- /dev/null +++ b/docs/api/ar/materials/MeshToonMaterial.html @@ -0,0 +1,235 @@ + + + + + + + + + + [page:Material] → + +

[name]

+ +
مادة تنفذ التظليل الكرتوني.
+ + + + + +

أمثلة (Examples)

+

+ [example:webgl_materials_toon materials / toon] +

+ +

المنشئ (Constructor)

+ +

[name]( [param:Object parameters] )

+

+ [page:Object parameters] - (اختياري) كائن يحتوي على واحد أو أكثر + خصائص تحدد مظهر المادة. يمكن تمرير أي خاصية من + المادة (بما في ذلك أي خاصية موروثة من [page:Material]) يمكن + تمريرها هنا.

+ + الاستثناء هو خاصية [page:Hexadecimal color] ، التي يمكن + تمريرها كسلسلة ست عشرية وهي `0xffffff` (الأبيض) بشكل افتراضي. + يتم استدعاء [page:Color.set]( color ) داخليًا. +

+ +

الخصائص (Properties)

+

انظر فئة [page:Material] الأساسية للخصائص المشتركة.

+ +

[property:Texture alphaMap]

+

+ خريطة ألفا هي ملمس رمادي يتحكم في التعتيم عبر + السطح (الأسود: شفافية كاملة ؛ الأبيض: غير شفاف تمامًا). الافتراضي هو + null.

+ + يتم استخدام لون الملمس فقط ، مع تجاهل قناة الألفا إذا كانت واحدة + موجود. بالنسبة لقوام RGB و RGBA ، سوف [page:WebGLRenderer WebGL] renderer + استخدام القناة الخضراء عند أخذ عينات من هذه الملمس بسبب البت الإضافي + من الدقة المقدمة للأخضر في DXT-compressed وغير المضغوط RGB 565 + التنسيقات. ستعمل الملمسات التي تحتوي على إضاءة فقط وإضاءة / ألفا أيضًا + كما هو متوقع. +

+ +

[property:Texture aoMap]

+

+ يستخدم القناة الحمراء من هذه الملمس كخريطة احتجاب الجو. + الافتراضي هو null. يتطلب aoMap مجموعة ثانية من UVs. +

+ +

[property:Float aoMapIntensity]

+

+ شدة تأثير احتجاب الجو. الافتراضي هو 1. صفر لا + تأثير احتجاب. +

+ +

[property:Texture bumpMap]

+

+ الملمس لإنشاء خريطة البثرة. تتوافق القيم السوداء والبيضاء مع + العمق المتصور فيما يتعلق بالأضواء. لا يؤثر البثرة فعليًا + هندسة الكائن ، فقط الإضاءة. إذا تم تحديد خريطة طبيعية + سيتم تجاهل هذا. +

+ +

[property:Float bumpScale]

+

+ مدى تأثير خريطة البثرة على المادة. المدى النموذجي هو 0-1. + الافتراضي هو 1. +

+ +

[property:Color color]

+

[page:Color] المادة ، بشكل افتراضي مضبوط على الأبيض (0xffffff).

+ +

[property:Texture displacementMap]

+

+ تؤثر خريطة التشوه على موضع رؤوس شبكة الشبكة. على عكس + خرائط أخرى التي تؤثر فقط على الضوء والظل للمادة + يمكن للرؤوس المشتتة أن تلقي ظلالًا ، وتحجب كائنات أخرى ، وغيرها + يعمل كهندسة حقيقية. خريطة التشوه هي صورة حيث قيمة + كل بكسل (الأبيض هو الأعلى) يتم تعيينه ضد ، و + إعادة تحديد مواقع رؤوس الشبكة. +

+ +

[property:Float displacementScale]

+

+ مدى تأثير خريطة التشوه على الشبكة (حيث يكون الأسود لا + التشوه ، والأبيض هو التشوه الأقصى). بدون تشوه + تعيين خريطة ، لا يتم تطبيق هذه القيمة. الافتراضي هو 1. +

+ +

[property:Float displacementBias]

+

+ إزاحة قيم خريطة التشوه على رؤوس شبكة الشبكات. + بدون تعيين خريطة التشوه ، لا يتم تطبيق هذه القيمة. الافتراضية هى 0. +

+ +

[property:Color emissive]

+

+ لون المادة المنبعث (الضوء) ، في جوهره لون صلب + غير متأثر بإضاءة أخرى. الافتراضية هى سوداء. +

+ +

[property:Texture emissiveMap]

+

+ تعيين الخريطة الانبعاثية (التوهج). الافتراضي هو null. يتم تعديل لون الخريطة الانبعاثية + بواسطة اللون الانبعاثي وشدة الانبعاث. إذا كان لديك خريطة انبعاثية ، تأكد من تعيين اللون الانبعاثي على شيء آخر + من الأسود. +

+ +

[property:Float emissiveIntensity]

+

+ شدة الضوء المنبعث. يعدل اللون المنبعث. الافتراضي هو + 1. +

+ +

[property:Boolean fog]

+

ما إذا كانت المادة متأثرة بالضباب. الافتراضي هو `true`.

+ +

[property:Texture gradientMap]

+

+ خريطة التدرج للتظليل الكرتوني. يجب تعيين + [page:Texture.minFilter] و [page:Texture.magFilter] إلى [page:Textures THREE.NearestFilter] + عند استخدام هذا النوع من الملمس. الافتراضي هو `null`. +

+ +

[property:Texture lightMap]

+

+ خريطة الضوء. الافتراضي هو null. يتطلب lightMap مجموعة ثانية من UVs. +

+ +

[property:Float lightMapIntensity]

+

شدة الضوء المخبوز. الافتراضي هو 1.

+ +

[property:Texture map]

+

+ خريطة اللون. قد تشمل اختياريًا قناة ألفا ، عادةً مجتمعة + مع [page:Material.transparent .transparent] أو [page:Material.alphaTest .alphaTest]. + الافتراضي هو null. يتم تعديل لون خريطة الملمس باللون المنتشر [page:.color]. +

+ +

[property:Texture normalMap]

+

+ الملمس لإنشاء خريطة طبيعية. تؤثر قيم RGB على سطح + normal لكل جزء بكسل وتغير طريقة إضاءة اللون. لا تغير خرائط normal شكل السطح فعلًا ، فقط الإضاءة. في + في حال كانت المادة تحتوي على خريطة طبيعية باستخدام التقاليد التي يستخدمها يدًا يسارىً + يجب إنكار مكوِّن y من normalScale للتعويض عن اختلاف التقاليد. +

+ +

[property:Integer normalMapType]

+

+ نوع الخريطة الطبيعية.

+ + الخيارات هي [page:constant THREE.TangentSpaceNormalMap] (الافتراضي) ، و + [page:constant THREE.ObjectSpaceNormalMap]. +

+ +

[property:Vector2 normalScale]

+

+ مدى تأثير الخريطة الطبيعية على المادة. المدى النموذجي هو 0-1. + الافتراضي هو [page:Vector2] مضبوط على (1،1). +

+ +

[property:Boolean wireframe]

+

+ تقديم الهندسة كإطار سلكي. الافتراضي هو `false` (أي تقديم كمضلعات مسطحة). +

+ +

[property:String wireframeLinecap]

+

+ تحديد مظهر نهايات الخط. القيم الممكنة هي "butt" و "round" و + "square". الافتراضي هو 'round'.

+ + يتوافق هذا مع + [link:https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D/lineCap 2D Canvas lineCap] + خاصية و يتم تجاهلها من قبل [page:WebGLRenderer WebGL] renderer. +

+ +

[property:String wireframeLinejoin]

+

+ تحديد مظهر مفاصل الخط. القيم الممكنة هي "round" و "bevel" و + "miter". الافتراضي هو 'round'.

+ + يتوافق هذا مع + [link:https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D/lineJoin 2D Canvas lineJoin] + خاصية و يتم تجاهلها من قبل [page:WebGLRenderer WebGL] renderer. +

+ +

[property:Float wireframeLinewidth]

+

+ التحكم في سمك الإطار السلكي. الافتراضي هو 1.

+ + بسبب قيود + [link:https://www.khronos.org/registry/OpenGL/specs/gl/glspec46.core.pdf OpenGL Core Profile] + مع [page:WebGLRenderer WebGL] renderer على معظم + المنصات ستكون linewidth دائمًا 1 بغض النظر عن القيمة المحددة. +

+ +

الطرق (Methods)

+

انظر فئة [page:Material] الأساسية للطرق المشتركة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/materials/PointsMaterial.html b/docs/api/ar/materials/PointsMaterial.html new file mode 100644 index 00000000000000..858ffe56619453 --- /dev/null +++ b/docs/api/ar/materials/PointsMaterial.html @@ -0,0 +1,116 @@ + + + + + + + + + + [page:Material] → + +

[name]

+ +

المادة الافتراضية المستخدمة بواسطة [page:Points].

+ +

مثال الكود

+ + const vertices = []; + + for ( let i = 0; i < 10000; i ++ ) { + const x = THREE.MathUtils.randFloatSpread( 2000 ); + const y = THREE.MathUtils.randFloatSpread( 2000 ); + const z = THREE.MathUtils.randFloatSpread( 2000 ); + + vertices.push( x, y, z ); + } + + const geometry = new THREE.BufferGeometry(); + geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) ); + const material = new THREE.PointsMaterial( { color: 0x888888 } ); + const points = new THREE.Points( geometry, material ); + scene.add( points ); + + +

أمثلة (Examples)

+

+ [example:misc_controls_fly misc / controls / fly]
+ [example:webgl_buffergeometry_drawrange WebGL / BufferGeometry / drawrange]
+ [example:webgl_buffergeometry_points WebGL / BufferGeometry / points]
+ [example:webgl_buffergeometry_points_interleaved WebGL / BufferGeometry / points / interleaved]
+ [example:webgl_camera WebGL / camera ]
+ [example:webgl_geometry_convex WebGL / geometry / convex]
+ [example:webgl_geometry_shapes WebGL / geometry / shapes]
+ [example:webgl_interactive_raycasting_points WebGL / interactive / raycasting / points]
+ [example:webgl_multiple_elements_text WebGL / multiple / elements / text]
+ [example:webgl_points_billboards WebGL / points / billboards]
+ [example:webgl_points_dynamic WebGL / points / dynamic]
+ [example:webgl_points_sprites WebGL / points / sprites] +

+ +

المنشئ (Constructor)

+

[name]( [param:Object parameters] )

+

+ [page:Object parameters] - (اختياري) كائن يحتوي على واحد أو أكثر + خصائص تحدد مظهر المادة. يمكن تمرير أي خاصية من + المادة (بما في ذلك أي خاصية موروثة من [page:Material]) يمكن + تمريرها هنا.

+ + الاستثناء هو خاصية [page:Hexadecimal color] ، التي يمكن + تمريرها كسلسلة ست عشرية وهي `0xffffff` (الأبيض) بشكل افتراضي. + يتم استدعاء [page:Color.set]( color ) داخليًا. +

+ +

الخصائص (Properties)

+

انظر فئة [page:Material] الأساسية للخصائص المشتركة.

+ +

[property:Texture alphaMap]

+

+ خريطة ألفا هي ملمس رمادي يتحكم في التعتيم عبر + السطح (الأسود: شفافية كاملة ؛ الأبيض: غير شفاف تمامًا). الافتراضي هو + null.

+ + يتم استخدام لون الملمس فقط ، مع تجاهل قناة الألفا إذا كانت واحدة + موجود. بالنسبة لقوام RGB و RGBA ، سوف [page:WebGLRenderer WebGL] renderer + استخدام القناة الخضراء عند أخذ عينات من هذه الملمس بسبب البت الإضافي + من الدقة المقدمة للأخضر في DXT-compressed وغير المضغوط RGB 565 + التنسيقات. ستعمل الملمسات التي تحتوي على إضاءة فقط وإضاءة / ألفا أيضًا + كما هو متوقع. +

+ +

[property:Color color]

+

[page:Color] المادة ، بشكل افتراضي مضبوط على الأبيض (0xffffff).

+ +

[property:Boolean fog]

+

ما إذا كانت المادة متأثرة بالضباب. الافتراضي هو `true`.

+ +

[property:Texture map]

+

+ يحدد لون النقاط باستخدام بيانات من [page:Texture]. قد + اختیاریًا تشمل قناة ألفا ، عادةً مجتمعة مع + [page:Material.transparent .transparent] أو [page:Material.alphaTest .alphaTest]. +

+ +

[property:Number size]

+

+ يحدد حجم النقط بالبكسل. الافتراضي هو 1.0.
+ سيتم قطعه إذا تجاوز المعامل المستقل عن الأجهزة + [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/getParameter gl.ALIASED_POINT_SIZE_RANGE]. +

+ +

[property:Boolean sizeAttenuation]

+

+ حدد ما إذا كان حجم النقط يُخفَّف بعمق الكاميرا. + (كاميرات التصوير فقط.) الافتراضي هو true. +

+ +

الطرق (Methods)

+

انظر فئة [page:Material] الأساسية للطرق المشتركة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/materials/RawShaderMaterial.html b/docs/api/ar/materials/RawShaderMaterial.html new file mode 100644 index 00000000000000..a6973f4baf668c --- /dev/null +++ b/docs/api/ar/materials/RawShaderMaterial.html @@ -0,0 +1,69 @@ + + + + + + + + + + [page:Material] → [page:ShaderMaterial] → + +

[name]

+ +

+ هذه الفئة تعمل تمامًا مثل [page:ShaderMaterial]، باستثناء أن التعريفات + من الموحدات والسمات المدمجة لا يتم إلحاقها تلقائيًا بـ + كود GLSL shader. +

+ +

مثال الكود

+ + const material = new THREE.RawShaderMaterial( { + + uniforms: { + time: { value: 1.0 } + }, + vertexShader: document.getElementById( 'vertexShader' ).textContent, + fragmentShader: document.getElementById( 'fragmentShader' ).textContent, + + } ); + + +

أمثلة (Examples)

+

+ [example:webgl_buffergeometry_rawshader WebGL / buffergeometry / rawshader]
+ [example:webgl_buffergeometry_instancing_billboards WebGL / buffergeometry / instancing / billboards]
+ [example:webgl_buffergeometry_instancing WebGL / buffergeometry / instancing]
+ [example:webgl_volume_cloud WebGL / volume / cloud]
+ [example:webgl_volume_instancing WebGL / volume / instancing]
+ [example:webgl_volume_perlin WebGL / volume / perlin] +

+ +

المنشئ (Constructor)

+ +

[name]( [param:Object parameters] )

+

+ [page:Object parameters] - (اختياري) كائن يحتوي على واحد أو أكثر + خصائص تحدد مظهر المادة. يمكن تمرير أي خاصية من + المادة (بما في ذلك أي خاصية موروثة من [page:Material] و + [page:ShaderMaterial]) هنا.

+

+ +

الخصائص (Properties)

+

+ انظر إلى الفئات الأساسية [page:Material] و [page:ShaderMaterial] للخصائص المشتركة. +

+ +

الطرق (Methods)

+

+ انظر إلى الفئات الأساسية [page:Material] و [page:ShaderMaterial] للطرق المشتركة. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/materials/ShaderMaterial.html b/docs/api/ar/materials/ShaderMaterial.html new file mode 100644 index 00000000000000..ab7d59322bbdea --- /dev/null +++ b/docs/api/ar/materials/ShaderMaterial.html @@ -0,0 +1,459 @@ + + + + + + + + + + [page:Material] → + +

[name]

+ +

+ مادة يتم عرضها بشيدرات مخصصة. الشيدر هو برنامج صغير + مكتوب في + [link:https://www.khronos.org/files/opengles_shading_language.pdf GLSL] + يعمل على GPU. قد ترغب في استخدام شيدر مخصص إذا كنت بحاجة إلى: +

+ +
    +
  • + تنفيذ تأثير غير مضمن في أي من المواد المدمجة [page:Material materials] +
  • +
  • + دمج العديد من الكائنات في [page:BufferGeometry] واحد لكي + تحسين الأداء +
  • +
+ هناك الملاحظات التالية التي يجب مراعاتها عند استخدام `ShaderMaterial`: +
    +
  • + سيتم عرض `ShaderMaterial` بشكل صحيح فقط من قِبَل + [page:WebGLRenderer]، نظرًا لأن كود GLSL في + [link:https://en.wikipedia.org/wiki/Shader#Vertex_shaders vertexShader] + و [link:https://en.wikipedia.org/wiki/Shader#Pixel_shaders fragmentShader] + يجب تجميعه وتشغيله على GPU باستخدام WebGL. +
  • +
  • + اعتبارًا من THREE r72، لم يعد يتم دعم تعيين السمات مباشرة في ShaderMaterial. يجب استخدام نسخة [page:BufferGeometry] بدلاً من ذلك، + باستخدام [page:BufferAttribute] instances لتعريف السمات المخصصة. +
  • +
  • + اعتبارًا من THREE r77، لم يعد من المفترض استخدام نسخ [page:WebGLRenderTarget] أو + [page:WebGLCubeRenderTarget] كموحدات. يجب استخدام خاصية [page:Texture texture] بدلاً من ذلك. +
  • +
  • + يتم تمرير السمات والموحدات المضمنة إلى الشيدرات جنبًا إلى جنب + مع كودك. إذا كنت لا ترغب في أن يضيف [page:WebGLProgram] أي شيء إلى + كود شيدرك، يمكنك استخدام [page:RawShaderMaterial] بدلاً من هذه + الفئة. +
  • +
  • + يمكنك استخدام التوجيه #pragma unroll_loop_start و #pragma + unroll_loop_end لفك حلقة `for` في GLSL بواسطة معالج شيدر + قبل التشغيل. يجب وضع التوجيه فوق الحلقة مباشرة. يجب أن يتطابق تنسيق الحلقة مع معيار محدد. +
      +
    • + يجب أن تكون الحلقة + [link:https://en.wikipedia.org/wiki/Normalized_loop normalized]. +
    • +
    • يجب أن يكون متغير الحلقة *i*.
    • +
    • + ستستبدل قِبَال `UNROLLED_LOOP_INDEX` بالقِبَال المُعَبرَة لـ *i* للتكرار المُعَبرَ ويمكن استخدامها في بيانات قِبَال التشغيل. +
    • +
    + + #pragma unroll_loop_start + for ( int i = 0; i < 10; i ++ ) { + // ... + } + #pragma unroll_loop_end + +
  • +
+ +

مثال الكود

+ + + const material = new THREE.ShaderMaterial( { + + uniforms: { + time: { value: 1.0 }, + resolution: { value: new THREE.Vector2() } + }, + + vertexShader: document.getElementById( 'vertexShader' ).textContent, + fragmentShader: document.getElementById( 'fragmentShader' ).textContent + + } ); + + +

أمثلة (Examples)

+ +

+ [example:webgl_buffergeometry_custom_attributes_particles webgl / buffergeometry / custom / attributes / particles]
+ [example:webgl_buffergeometry_selective_draw webgl / buffergeometry / selective / draw]
+ [example:webgl_custom_attributes webgl / custom / attributes]
+ [example:webgl_custom_attributes_lines webgl / custom / attributes / lines]
+ [example:webgl_custom_attributes_points webgl / custom / attributes / points]
+ [example:webgl_custom_attributes_points2 webgl / custom / attributes / points2]
+ [example:webgl_custom_attributes_points3 webgl / custom / attributes / points3]
+ [example:webgl_depth_texture webgl / depth / texture]
+ [example:webgl_gpgpu_birds webgl / gpgpu / birds]
+ [example:webgl_gpgpu_protoplanet webgl / gpgpu / protoplanet]
+ [example:webgl_gpgpu_water webgl / gpgpu / water]
+ [example:webgl_interactive_points webgl / interactive / points]
+ [example:webgl_video_kinect webgl / video / kinect]
+ [example:webgl_lights_hemisphere webgl / lights / hemisphere]
+ [example:webgl_marchingcubes webgl / marchingcubes]
+ [example:webgl_materials_envmaps webgl / materials / envmaps]
+ [example:webgl_materials_wireframe webgl / materials / wireframe]
+ [example:webgl_modifier_tessellation webgl / modifier / tessellation]
+ [example:webgl_postprocessing_dof2 webgl / postprocessing / dof2]
+ [example:webgl_postprocessing_godrays webgl / postprocessing / + godrays] +

+ +

شيدرات الرأس وشيدرات الجزء (Vertex shaders and fragment shaders)

+ +
+

يمكنك تحديد نوعين مختلفين من الشيدرات لكل مادة:

+
    +
  • + يعمل شيدر الرأس أولاً؛ يتلقى `attributes`، يحسب / + يعدل موضع كل رأس فردي، ويمرر + بيانات إضافية (`varying`s) إلى شيدر الجزء. +
  • +
  • + يعمل شيدر الجزء (أو البكسل) ثانيًا؛ يضبط لون + كل "جزء" فردي (بكسل) يتم عرضه على الشاشة. +
  • +
+

+ هناك ثلاثة أنواع من المتغيرات في الشيدرات: الموحدات، والسمات، و + varyings: +

+
    +
  • + `Uniforms` هي متغيرات لها نفس القيمة لجميع الرؤوس - + الإضاءة، والضباب، وخرائط الظل هي أمثلة على البيانات التي ستكون + مخزنة في الموحدات. يمكن الوصول إلى الموحدات من قبل كل من شيدر الرأس + وشيدر الجزء. +
  • +
  • + `Attributes` هي متغيرات مرتبطة بكل رأس - على سبيل المثال، + موضع الرأس، والوجه الطبيعي، ولون الرأس هي جميعها أمثلة على + البيانات التي ستكون مخزنة في السمات. يمكن `فقط` الوصول إلى السمات + داخل شيدر الرأس. +
  • +
  • + `Varyings` هي متغيرات تُمَرَّر من شيدر الرأس إلى + شيدر الجزء. لكل جزء، ستتم تقديم قيمة كل varying بشكل سلس من قِبَال رؤوس مجاورة. +
  • +
+

+ يجب ملاحظة أن `within` الشيدر نفسه، تعمل الموحدات والسمات مثل + المستمرات؛ يمكنك فقط تعديل قِبَالها بتمرير قِبَال مختلفة + إلى المخازن من كود JavaScript الخاص بك. +

+
+ +

السمات والموحدات المدمجة (Built-in attributes and uniforms)

+ +
+

+ يوفر [page:WebGLRenderer] العديد من السمات والموحدات إلى + الشيدرات افتراضيًا؛ يتم إلحاق تعريفات هذه المتغيرات بكود + `fragmentShader` و `vertexShader` الخاص بك من قبل [page:WebGLProgram] عندما + يتم تجميع الشيدر؛ ليس عليك إعلانها بنفسك. انظر + [page:WebGLProgram] للحصول على تفاصيل هذه المتغيرات. +

+

+ بعض هذه الموحدات أو السمات (على سبيل المثال تلك المتعلقة بالإضاءة، + الضباب، إلخ) تتطلب تعيين خصائص على المادة لكي + [page:WebGLRenderer] لنسخ القيم المناسبة إلى GPU - تأكد من تعيين هذه العلامات إذا كنت ترغب في استخدام هذه الميزات في شيدرك الخاص. +

+

+ إذا كنت لا ترغب في أن يضيف [page:WebGLProgram] أي شيء إلى كود شيدرك + ، يمكنك استخدام [page:RawShaderMaterial] بدلاً من هذه الفئة. +

+
+ +

السمات والموحدات المخصصة (Custom attributes and uniforms)

+ +
+

+ يجب إعلان كل من السمات والموحدات المخصصة في كود GLSL shader + (داخل `vertexShader` و / أو `fragmentShader`). يجب تعريف الموحدات المخصصة + في `both` خاصية `uniforms` لـ + `ShaderMaterial`، في حين يجب تعريف أي سمات مخصصة عبر + [page:BufferAttribute] instances. يجب ملاحظة أن `varying`s يجب فقط أن + يتم إعلانها داخل كود الشيدر (وليس داخل المادة). +

+

+ لإعلان سمة مخصصة، يرجى الإشارة إلى + [page:BufferGeometry] للحصول على نظرة عامة، و + [page:BufferAttribute] للحصول على نظرة مفصلة على API `BufferAttribute`. +

+

+ عند إنشاء سماتك، يجب أن يكون كل مجموعة مُطَبَّقَة التي تقوم بإنشائها لحفظ بيانات سمتك مضروبًا في حجم نوع بياناتك. على سبيل المثال، إذا كانت سمتك من نوع [page:Vector3 THREE.Vector3] ، وكان لديك 3000 رأس في [page:BufferGeometry] ، فيجب إنشاء قيمة مجموعتك المُطَبَّقَة بطول 3000 * 3 ، أو 9000 (قيمة واحدة + لكل مُكَوِّن). يظهر جدول حجم كل نوع من أنواع البيانات أدناه للإشارة: +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Attribute sizes +
GLSL typeJavaScript typeSize
float[page:Number]1
vec2[page:Vector2 THREE.Vector2]2
vec3[page:Vector3 THREE.Vector3]3
vec3[page:Color THREE.Color]3
vec4[page:Vector4 THREE.Vector4]4
+ +

+ يجب ملاحظة أن مخازن السمات `ليست` تتحدث تلقائيًا عندما تتغير قيمها. لتحديث السمات المخصصة، قم بتعيين علامة `needsUpdate` + إلى true على [page:BufferAttribute] للهندسة (انظر + [page:BufferGeometry] لمزيد من التفاصيل). +

+ +

+ لإعلان [page:Uniform] مخصص، استخدم خاصية `uniforms`: + +uniforms: { + time: { value: 1.0 }, + resolution: { value: new THREE.Vector2() } +} + +

+ +

+ يوصى بتحديث قيم [page:Uniform] المخصصة اعتمادًا على + [page:Object3D object] و [page:Camera camera] في + [page:Object3D.onBeforeRender] لأن [page:Material] يمكن أن يتم مشاركته + بين [page:Mesh meshes]، يتم تحديث [page:Matrix4 matrixWorld] لـ [page:Scene] و + [page:Camera] في [page:WebGLRenderer.render]، وبعض + المؤثرات تعرض [page:Scene scene] باستخدام خاصة بهم [page:Camera cameras]. +

+
+ +

المنشئ (Constructor)

+ +

[name]( [param:Object parameters] )

+

+ [page:Object parameters] - (اختياري) كائن يحتوي على واحد أو أكثر + خصائص تحدد مظهر المادة. يمكن تمرير أي خاصية من + المادة (بما في ذلك أي خاصية موروثة من [page:Material]) هنا. +

+ +

الخصائص (Properties)

+

انظر إلى الفئة الأساسية [page:Material] للخصائص المشتركة.

+ +

[property:Boolean clipping]

+

+ يحدد ما إذا كانت هذه المادة تدعم القطع؛ صحيح للسماح للعارض + بتمرير الزي الرسمي clippingPlanes. الافتراضي هو false. +

+ +

[property:Object defaultAttributeValues]

+

+ عندما لا تتضمن الهندسة المعروضة هذه السمات ولكن المادة تفعل ذلك، سيتم تمرير هذه القيم الافتراضية إلى الشيدرات. هذا يتجنب الأخطاء عندما تكون بيانات المخزن المؤقت مفقودة. + + +this.defaultAttributeValues = { + 'color': [ 1, 1, 1 ], + 'uv': [ 0, 0 ], + 'uv1': [ 0, 0 ] +}; + +

+ +

[property:Object defines]

+

+ يحدد ثوابت مخصصة باستخدام توجيهات `#define` داخل كود GLSL + لكل من شيدر الرأس وشيدر الجزء؛ كل زوج من المفتاح / القيمة + يولد توجيهًا آخر: + + defines: { + FOO: 15, + BAR: true + } + + يولد الأسطر + + #define FOO 15 + #define BAR true + + في كود GLSL. +

+ +

[property:Object extensions]

+

+ كائن يحتوي على الخصائص التالية: + +this.extensions = { + clipCullDistance: false, // set to use vertex shader clipping + multiDraw: false // set to use vertex shader multi_draw / enable gl_DrawID +}; + +

+ +

[property:Boolean fog]

+

+ تحدد ما إذا كان يتم التأثير على لون المادة بإعدادات الضباب العالمية؛ صحيح + لتمرير موحدات الضباب إلى الشيدر. الافتراضي هو false. +

+ +

[property:String fragmentShader]

+

+ كود GLSL لشيدر الجزء. هذا هو الكود الفعلي للشيدر. في + المثال أعلاه، يتم استخراج كود `vertexShader` و `fragmentShader` من + DOM؛ يمكن تمريره كسلسلة مباشرة أو تحميله عبر AJAX + بدلاً من ذلك. +

+ +

[property:String glslVersion]

+

+ يحدد إصدار GLSL لكود الشيدر المخصص. ذو صلة فقط لـ WebGL 2 + لتحديد ما إذا كان يجب تحديد GLSL 3.0 أم لا. القيم المتاحة هي + `THREE.GLSL1` أو `THREE.GLSL3`. الافتراضي هو `null`. +

+ +

[property:String index0AttributeName]

+

+ إذا تم تعيينه، يستدعي هذا + [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/bindAttribLocation gl.bindAttribLocation] + لربط فهرس رأس عام بمتغير سمة. الافتراضي هو غير معرف. +

+ +

[property:Boolean isShaderMaterial]

+

علامة قراءة فقط للتحقق مما إذا كان كائنًا معينًا من نوع [name].

+ +

[property:Boolean lights]

+

+ يحدد ما إذا كانت هذه المادة تستخدم الإضاءة؛ صحيح لتمرير بيانات الموحدات + المتعلقة بالإضاءة إلى هذا الشيدر. الافتراضي هو false. +

+ +

[property:Float linewidth]

+

+ يتحكم في سُمك الإطار السلكي. الافتراضي هو 1.

+ + نظرًا للقيود + [link:https://www.khronos.org/registry/OpenGL/specs/gl/glspec46.core.pdf OpenGL Core Profile] + مع [page:WebGLRenderer WebGL] renderer على معظم + المنصات ستكون linewidth دائمًا 1 بغض النظر عن القيمة المُعَدَّة. +

+ +

[property:Boolean flatShading]

+

+ حدد ما إذا كان يتم عرض المادة بظلال مسطحة. + الافتراضي هو false. +

+ +

[property:Object uniforms]

+

+ كائن من الشكل: + + { + "uniform1": { value: 1.0 }, + "uniform2": { value: 2 } + } + + يحدد الموحدات التي يتم تمريرها إلى كود الشيدر؛ المفاتيح هي أسماء الموحدات + ، والقيم هي تعريفات من الشكل + + { + value: 1.0 + } + + حيث `value` هو قيمة الموحد. يجب أن تتطابق الأسماء مع اسم + الموحد، كما هو محدد في كود GLSL. يجب ملاحظة أن الموحدات يتم تحديثها + في كل إطار، لذلك تحديث قيمة الموحد سيؤدي فورًا إلى + تحديث القيمة المتاحة لكود GLSL. +

+ +

[property:Boolean uniformsNeedUpdate]

+

+ يمكن استخدامه لإجبار تحديث الموحدات أثناء تغيير الموحدات في + [page:Object3D.onBeforeRender](). الافتراضي هو `false`. +

+ +

[property:Boolean vertexColors]

+

يعرف ما إذا كان يتم استخدام تلوين الرأس. الافتراضي هو `false`.

+ +

[property:String vertexShader]

+

+ كود GLSL لشيدر الرأس. هذا هو الكود الفعلي للشيدر. في + المثال أعلاه، يتم استخراج كود `vertexShader` و `fragmentShader` من + DOM؛ يمكن تمريره كسلسلة مباشرة أو تحميله عبر AJAX + بدلاً من ذلك. +

+ +

[property:Boolean wireframe]

+

+ عرض الهندسة كإطار سلكي (باستخدام GL_LINES بدلاً من GL_TRIANGLES). + الافتراضي هو false (أي عرض كأشكال مسطحة). +

+ +

[property:Float wireframeLinewidth]

+

+ يتحكم في سُمك الإطار السلكي. الافتراضي هو 1.

+ + نظرًا للقيود + [link:https://www.khronos.org/registry/OpenGL/specs/gl/glspec46.core.pdf OpenGL Core Profile] + مع [page:WebGLRenderer WebGL] renderer على معظم + المنصات ستكون linewidth دائمًا 1 بغض النظر عن القيمة المُعَدَّة. +

+ +

الطرق (Methods)

+

انظر إلى الفئة الأساسية [page:Material] للطرق المشتركة.

+ +

[method:ShaderMaterial clone]()

+

+ يولِّد نسخة طبقية من هذه المادة. يجب ملاحظة أن vertexShader و + fragmentShader يتم نسخهم `by reference`، كذلك تعاريف + `attributes`؛ هذا يعني أن نسخ المادة ستشارك نفس + [page:WebGLProgram] المُجَمَّع. ومع ذلك، فإن `uniforms` يتم نسخها `by + value`، مما يتيح لك امتلاك مجموعات مختلفة من الموحدات لنسخ مختلفة من + المادة. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/materials/ShadowMaterial.html b/docs/api/ar/materials/ShadowMaterial.html new file mode 100644 index 00000000000000..6d759c2a8fb1c7 --- /dev/null +++ b/docs/api/ar/materials/ShadowMaterial.html @@ -0,0 +1,67 @@ + + + + + + + + + + [page:Material] → + +

[name]

+ +

+ يمكن لهذه المادة تلقي الظلال، ولكنها في الوقت نفسه شفافة تمامًا. +

+ +

مثال الكود

+ + + const geometry = new THREE.PlaneGeometry( 2000, 2000 ); + geometry.rotateX( - Math.PI / 2 ); + + const material = new THREE.ShadowMaterial(); + material.opacity = 0.2; + + const plane = new THREE.Mesh( geometry, material ); + plane.position.y = -200; + plane.receiveShadow = true; + scene.add( plane ); + + +

أمثلة (Examples)

+ +

[example:webgl_geometry_spline_editor geometry / spline / editor]

+ +

المنشئ (Constructor)

+ +

[name]( [param:Object parameters] )

+

+ [page:Object parameters] - (اختياري) كائن يحتوي على واحد أو أكثر + خصائص تحدد مظهر المادة. يمكن تمرير أي خاصية من + المادة (بما في ذلك أي خاصية موروثة من [page:Material]) هنا.

+

+ +

الخصائص (Properties)

+

انظر إلى الفئات الأساسية [page:Material] للخصائص المشتركة.

+ +

[property:Color color]

+

[page:Color] المادة، بشكل افتراضي مضبوط على الأسود (0x000000).

+ +

[property:Boolean fog]

+

ما إذا كانت المادة متأثرة بالضباب. الافتراضي هو `true`.

+ +

[property:Boolean transparent]

+

يحدد ما إذا كانت هذه المادة شفافة. الافتراضي هو `true`.

+ +

الطرق (Methods)

+

انظر إلى الفئات الأساسية [page:Material] للطرق المشتركة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/materials/SpriteMaterial.html b/docs/api/ar/materials/SpriteMaterial.html new file mode 100644 index 00000000000000..c86ce8f9c72563 --- /dev/null +++ b/docs/api/ar/materials/SpriteMaterial.html @@ -0,0 +1,97 @@ + + + + + + + + + + [page:Material] → + +

[name]

+ +

A material for a use with a [page:Sprite].

+ +

مثال للكود

+ + + const map = new THREE.TextureLoader().load( 'textures/sprite.png' ); + const material = new THREE.SpriteMaterial( { map: map, color: 0xffffff } ); + + const sprite = new THREE.Sprite( material ); + sprite.scale.set(200, 200, 1) + scene.add( sprite ); + + +

Examples

+

+ [example:webgl_raycaster_sprite WebGL / raycast / sprite]
+ [example:webgl_sprites WebGL / sprites]
+ [example:svg_sandbox SVG / sandbox] +

+ +

المنشئ (Constructor)

+ +

[name]( [param:Object parameters] )

+

+ [page:Object parameters] - (اختياري) كائن يحتوي على واحد أو أكثر + خصائص تحدد مظهر المادة. يمكن تمرير أي خاصية من + المادة (بما في ذلك أي خاصية موروثة من [page:Material]) هنا.

+ + الاستثناء هو خاصية [page:Hexadecimal color]، التي يمكن + تمريرها كسلسلة ست عشرية وهي `0xffffff` (أبيض) افتراضيًا. + يتم استدعاء [page:Color.set]( color ) داخليًا. لا يتم قطع SpriteMaterials باستخدام [page:Material.clippingPlanes]. +

+ +

الخصائص (Properties)

+

انظر إلى الفئة الأساسية [page:Material] للخصائص المشتركة.

+ +

[property:Texture alphaMap]

+

+ الخريطة الألفية هي نسيج رمادي يتحكم في التعتيم عبر السطح (أسود: شفاف تمامًا؛ أبيض: غير شفاف تمامًا). الافتراضي هو + null.

+ + يتم استخدام لون النسيج فقط، متجاهلاً قناة الألفا إذا كانت موجودة. بالنسبة للقوام RGB و RGBA، سيستخدم [page:WebGLRenderer WebGL] renderer + قناة الأخضر عند عينة هذا النسيج بسبب البت الإضافي من الدقة المقدمة للأخضر في تنسيقات DXT المضغوطة و RGB 565 غير المضغوطة. ستعمل القوام المُضِئَة فقط والقوام المُضِئَة / الألفية أيضًا كما هو متوقع. +

+ +

[property:Color color]

+

+ [page:Color] المادة، بشكل افتراضي مضبوط على الأبيض (0xffffff). The + [page:.map] مضروب باللون. +

+ +

[property:Boolean fog]

+

ما إذا كانت المادة متأثرة بالضباب. الافتراضي هو `true`.

+ +

[property:Boolean isSpriteMaterial]

+

علامة للقراءة فقط للتحقق مما إذا كان كائنًا معطىً من نوع [name].

+ +

[property:Texture map]

+

+ خريطة اللون. قد تشمل اختيارًا قناة ألفا، عادةً مُجَمَّعَةً مع [page:Material.transparent .transparent] أو [page:Material.alphaTest .alphaTest]. + الافتراضي هو null. +

+ +

[property:Radians rotation]

+

دوران الرذاذ بالراديان. الافتراضي هو 0.

+ +

[property:Boolean sizeAttenuation]

+

+ ما إذا كان حجم الرذاذ يُخَفَّف بعمق الكاميرات. + (كاميرات المنظور فقط.) الافتراضي هو `true`. +

+ +

[property:Boolean transparent]

+

يحدد ما إذا كانت هذه المادة شفافة. الافتراضي هو `true`.

+ +

الطرق (Methods)

+

انظر إلى الفئة الأساسية [page:Material] للطرق المشتركة.

+ +

المصدر (Source)

+

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/math/Box2.html b/docs/api/ar/math/Box2.html new file mode 100644 index 00000000000000..e44280e0d36bb6 --- /dev/null +++ b/docs/api/ar/math/Box2.html @@ -0,0 +1,203 @@ + + + + + + + + + +

[name]

+ +

+ يمثل مربعًا محدودًا بمحاور (AABB) في الفضاء ثنائي الأبعاد. +

+ +

المنشئ (Constructor)

+ +

[name]( [param:Vector2 min], [param:Vector2 max] )

+

+ [page:Vector2 min] - (اختياري) [page:Vector2] يمثل الحد السفلي (x، y) للمربع. الافتراضي هو (+ Infinity، + Infinity).
+ + [page:Vector2 max] - (اختياري) [page:Vector2] يمثل الحد العلوي (x، y) للمربع. الافتراضي هو (- Infinity، - Infinity).

+ + ينشئ [name] محدودًا بواسطة min و max. +

+ +

الخصائص (Properties)

+ +

[property:Vector2 min]

+

+ [page:Vector2] يمثل الحد السفلي (x، y) للمربع.
+ الافتراضي هو (+ Infinity، + Infinity). +

+ +

[property:Vector2 max]

+

+ [page:Vector2] يمثل الحد العلوي (x، y) للمربع.
+ الافتراضي هو (- Infinity، - Infinity). +

+ +

الطرق (Methods)

+ +

+ [method:Vector2 clampPoint]( [param:Vector2 point], [param:Vector2 target] ) +

+

+ [page:Vector2 point] - [page:Vector2] للتثبيت.
+ [page:Vector2 target] — سيتم نسخ النتيجة في هذا Vector2.

+ + [link:https://en.wikipedia.org/wiki/Clamping_(graphics) يثبت] [page:Vector2 point] داخل حدود هذا المربع.
+

+ +

[method:Box2 clone]()

+

+ يعود بـ [page:Box2] جديد مع نفس [page:.min min] و [page:.max max] كهذا المربع. +

+ +

[method:Boolean containsBox]( [param:Box2 box] )

+

+ [page:Box2 box] - [page:Box2 Box2] للاختبار للتضمين.

+ + يعود بـ true إذا كان هذا المربع يشمل كامل [page:Box2 box]. إذا كان هذا و [page:Box2 box] متطابقان،
+ فإن هذه الوظيفة تعود أيضًا بـ true. +

+ +

[method:Boolean containsPoint]( [param:Vector2 point] )

+

+ [page:Vector2 point] - [page:Vector2] للاختبار للتضمين.

+ + يعود بـ true إذا كانت [page:Vector2 point] المحددة تقع داخل أو على حدود هذا المربع. +

+ +

[method:this copy]( [param:Box2 box] )

+

+ ينسخ [page:.min min] و [page:.max max] من [page:Box2 box] إلى هذا المربع. +

+ +

[method:Float distanceToPoint]( [param:Vector2 point] )

+

+ [page:Vector2 point] - [page:Vector2] لقياس المسافة إليها.

+ + يعود بالمسافة من أي حافة لهذا المربع إلى النقطة المحددة. إذا كانت [page:Vector2 point] داخل هذا المربع، فستكون المسافة 0. +

+ +

[method:Boolean equals]( [param:Box2 box] )

+

+ [page:Box2 box] - مربع للمقارنة مع هذا المربع.

+ + يعود بـ true إذا كان هذا المربع و [page:Box2 box] يشتركان في نفس الحدود السفلى والعليا. +

+ +

[method:this expandByPoint]( [param:Vector2 point] )

+

+ [page:Vector2 point] - [page:Vector2] التي يجب تضمينها في المربع.

+ + يوسع حدود هذا المربع لتضمين [page:Vector2 point]. +

+ +

[method:this expandByScalar]( [param:Float scalar] )

+

+ [page:Float scalar] - المسافة التي يتم توسيع المربع بها.

+ + يوسع كل بُعد من أبعاد المربع بواسطة [page:Float scalar]. إذا كانت سالبة، فستتقلص أبعاد المربع. +

+ +

[method:this expandByVector]( [param:Vector2 vector] )

+

+ [page:Vector2 vector] - [page:Vector2] لتوسيع المربع بها.

+ + يوسع هذا المربع بشكل متساوٍ بواسطة [page:Vector2 vector]. سيتم توسيع عرض هذا المربع بمكون x من [page:Vector2 vector] في كلا الاتجاهين. ستتم توسيع ارتفاع هذا المربع بمكون y من [page:Vector2 vector] في كلا الاتجاهين. +

+ +

[method:Vector2 getCenter] ([param:Vector2 target])

+

+ [page:Vector2 target] — سيتم نسخ النتيجة في هذا Vector2.

+ + يعود نقطة المركز للصندوق كـ [page:Vector2]. +

+ +

[method:Vector2 getParameter] ([param:Vector2 point], [param:Vector2 target])

+

+ [page:Vector2 point] - [page:Vector2].
+ [page:Vector2 target] — سيتم نسخ النتيجة في هذا Vector2.

+ + يعود نقطة كنسبة من عرض وارتفاع هذا الصندوق. +

+ +

[method:Vector2 getSize] ([param:Vector2 target])

+

+ [page:Vector2 target] — سيتم نسخ النتيجة في هذا Vector2.

+ + يعود عرض وارتفاع هذا الصندوق. +

+ +

[method:this intersect] ([param:Box2 box])

+

+ [page:Box2 box] - مربع للتقاطع معه.

+ + يعود تقاطع هذا و [page:Box2 box]، محددًا الحد الأعلى لهذا المربع إلى أقل من حدود المربعين الأعلى والحد الأدنى لهذا المربع إلى أكبر من حدود المربعين الأدنى. +

+ +

[method:Boolean intersectsBox] ([param:Box2 box])

+

+ [page:Box2 box] - مربع للتحقق من التقاطع ضده.

+ + يحدد ما إذا كان هذا المربع يتقاطع مع [page:Box2 box]. +

+ +

[method:Boolean isEmpty]()

+

+ يُرجَع صوابًا إذا كان هذا المربع يشمل صفر نقط داخل حدوده.
+ لاحظ أن المربع الذي يحتوي على حدود سفلى وعلوية متساوية لا يزال يشمل نقطة واحدة، وهي التي تشترك فيها كلا الحدود. +

+ +

[method:this makeEmpty]()

+

يجعل هذا المربع فارغًا.

+ + +

[method:this set]([param:Vector2 min], [param:Vector2 max])

+

+ [page:Vector2 min] - (مطلوب) [page:Vector2] يمثل الحد الأدنى (x، y) للمربع.
+ [page:Vector2 max] - (مطلوب) [page:Vector2] يمثل الحد الأعلى (x، y) للمربع.

+ + يضع الحدود العليا والسفلى (x، y) لهذا المربع.
+ يرجى ملاحظة أن هذه الطريقة تنسخ فقط القيم من الكائنات المعطاة. +

+ +

[method:this setFromCenterAndSize]([param:Vector2 center], [param:Vector2 size])

+

+ [page:Vector2 center] - الموضع المركزي المطلوب للمربع ([page:Vector2]).
+ [page:Vector2 size] - أبعاد x و y المطلوبة للمربع ([page:Vector2]).

+ + يضع هذا المربع في مركز [page:Vector2 center] ويضع عرض وارتفاع هذا المربع إلى القيم المحددة في [page:Vector2 size]. +

+ +

[method:this setFromPoints]([param:Array points])

+

+ [page:Array points] - مصفوفة من [page:Vector2 Vector2s] التي ستحتوي عليها المربعات الناتجة.

+ + يضع الحدود العليا والسفلى لهذا المربع لتشمل جميع النقاط في [page:Array points]. +

+ +

[method:this translate]([param:Vector2 offset])

+

+ [page:Vector2 offset] - اتجاه ومسافة التحويل.

+ + يضيف [page:Vector2 offset] إلى كلاً من الحدود العليا والسفلى لهذا المربع، مما يؤدي بشكل فعال إلى نقل هذا المربع [page:Vector2 offset] وحدات في مسافة 2D. +

+ +

[method:this union]([param:Box2 box])

+

+ [page:Box2 box] - مربع سيتم دمجه مع هذا المربع.

+ + يجمع هذا المربع مع [page:Box2 box]، حيث يضع الحد الأقصى لهذا المربع على أكبر حدود علوية للمربعين والحد الأدنى لهذا المربع على أقل حدود سفلى للمربعين. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/math/Box3.html b/docs/api/ar/math/Box3.html new file mode 100644 index 00000000000000..ba2368d3298a55 --- /dev/null +++ b/docs/api/ar/math/Box3.html @@ -0,0 +1,303 @@ + + + + + + + + + +

[name]

+ +

+ يمثل مربعًا محدودًا بمحاور (AABB) في الفضاء ثلاثي الأبعاد. +

+ +

مثال الكود

+ + + const box = new THREE.Box3(); + + const mesh = new THREE.Mesh( + new THREE.SphereGeometry(), + new THREE.MeshBasicMaterial() + ); + + // تأكد من حساب مربع التحديد لهندسته + // يجب القيام بذلك مرة واحدة فقط (باستخدام هندسات ثابتة) + mesh.geometry.computeBoundingBox(); + + // ... + + // في حلقة الرسوم المتحركة، احسب المربع الحالي مع المصفوفة العالمية + box.copy( mesh.geometry.boundingBox ).applyMatrix4( mesh.matrixWorld ); + + +

المنشئ (Constructor)

+ +

[name]( [param:Vector3 min], [param:Vector3 max] )

+

+ [page:Vector3 min] - (اختياري) [page:Vector3] يمثل الحد السفلي (x، y، z) للمربع. الافتراضي هو (+ Infinity، + Infinity، + Infinity).
+ + [page:Vector3 max] - (اختياري) [page:Vector3] يمثل الحد العلوي (x، y، z) للمربع. الافتراضي هو (- Infinity، - Infinity، - Infinity).

+ + ينشئ [name] محدودًا بواسطة min و max. +

+ +

الخصائص (Properties)

+ +

[property:Boolean isBox3]

+

علامة للقراءة فقط للتحقق مما إذا كان كائنًا معينًا من نوع [name].

+ +

[property:Vector3 min]

+

+ [page:Vector3] يمثل الحد السفلي (x، y، z) للمربع.
+ الافتراضي هو (+ Infinity، + Infinity، + Infinity). +

+ +

[property:Vector3 max]

+

+ [page:Vector3] يمثل الحد العلوي (x، y، z) للمربع.
+ الافتراضي هو (- Infinity، - Infinity، - Infinity). +

+ +

الطرق (Methods)

+ +

[method:this applyMatrix4]( [param:Matrix4 matrix] )

+

+ [page:Matrix4 matrix] - [page:Matrix4] للتطبيق

+ + يحول هذا Box3 مع المصفوفة الموردة. +

+ +

+ [method:Vector3 clampPoint]( [param:Vector3 point], [param:Vector3 target] ) +

+

+ [page:Vector3 point] - [page:Vector3] للتثبيت.
+ [page:Vector3 target] — سيتم نسخ النتيجة في هذا Vector3.

+ + [link:https://en.wikipedia.org/wiki/Clamping_(graphics) يثبت] [page:Vector3 point] داخل حدود هذا المربع.
+

+ +

[method:Box3 clone]()

+

+ يعود بـ [page:Box3] جديد مع نفس [page:.min min] و [page:.max max] كهذا المربع. +

+ +

[method:Boolean containsBox]( [param:Box3 box] )

+

+ [page:Box3 box] - [page:Box3 Box3] للاختبار للتضمين.

+ + يعود بـ true إذا كان هذا المربع يشمل كامل [page:Box3 box]. إذا كان هذا و [page:Box3 box] متطابقان،
+ فإن هذه الوظيفة تعود أيضًا بـ true. +

+ +

[method:Boolean containsPoint]( [param:Vector3 point] )

+

+ [page:Vector3 point] - [page:Vector3] للاختبار للتضمين.

+ + يعود بـ true إذا كانت [page:Vector3 point] المحددة تقع داخل أو على حدود هذا المربع. +

+ +

[method:this copy]( [param:Box3 box] )

+

+ [page:Box3 box] - [page:Box3] للنسخ.

+ + ينسخ [page:.min min] و [page:.max max] من [page:Box3 box] إلى هذا المربع. +

+ +

[method:Float distanceToPoint]( [param:Vector3 point] )

+

+ [page:Vector3 point] - [page:Vector3] لقياس المسافة إليها.

+ + يعود بالمسافة من أي حافة لهذا المربع إلى النقطة المحددة. إذا كانت [page:Vector3 point] داخل هذا المربع، فستكون المسافة 0. +

+ +

[method:Boolean equals]( [param:Box3 box] )

+

+ [page:Box3 box] - مربع للمقارنة مع هذا المربع.

+ + يعود بـ true إذا كان هذا المربع و [page:Box3 box] يشتركان في نفس الحدود السفلى والعليا. +

+ +

+ [method:this expandByObject]( [param:Object3D object], [param:Boolean precise] ) +

+

+ [page:Object3D object] - [page:Object3D] لتوسيع المربع به.
+ precise - (اختياري) توسيع مربع التحديد بأقل ما يمكن على حساب المزيد من الحسابات. الافتراضي هو false.

+ + يوسع حدود هذا المربع لتضمين [page:Object3D object] وأطفاله، مع مراعاة تحولات الكائن والأطفال العالمية. قد يؤدي التابع إلى مربع أكبر من اللازم بشكل صارم (ما لم يتم تحديد معلمة precise على true). +

+ +

[method:this expandByPoint]( [param:Vector3 point] )

+

+ [page:Vector3 point] - [page:Vector3] التي يجب تضمينها في المربع.

+ + يوسع حدود هذا المربع لتضمين [page:Vector3 point]. +

+ +

[method:this expandByScalar]( [param:Float scalar] )

+

+ [page:Float scalar] - المسافة التي يتم توسيع المربع بها.

+ + يوسع كل بُعد من أبعاد المربع بواسطة [page:Float scalar]. إذا كانت سالبة، فستتقلص أبعاد المربع. +

+ +

[method:this expandByVector]( [param:Vector3 vector] )

+

+ [page:Vector3 vector] - [page:Vector3] لتوسيع المربع به.

+ + يوسع هذا المربع بشكل متساوٍ بواسطة [page:Vector3 vector]. سيتم توسيع عرض هذا المربع بمكون x من [page:Vector3 vector] في كلا الاتجاهين. ستتم توسيع ارتفاع هذا المربع بمكون y من [page:Vector3 vector] في كلا الاتجاهين. ستتم توسيع عمق هذا المربع بمكون z من `vector` في كلا الاتجاهين. +

+ +

[method:Sphere getBoundingSphere]( [param:Sphere target] )

+

+ [page:Sphere target] — سيتم نسخ النتيجة في هذا الكرة.

+ + يحصل على [page:Sphere] يحد المربع. +

+ +

[method:Vector3 getCenter]( [param:Vector3 target] )

+

+ [page:Vector3 target] — سيتم نسخ النتيجة في هذا Vector3.

+ + يعود بنقطة المركز للمربع كـ [page:Vector3]. +

+ +

+ [method:Vector3 getParameter]( [param:Vector3 point], [param:Vector3 target] ) +

+

+ [page:Vector3 point] - [page:Vector3].
+ [page:Vector3 target] — سيتم نسخ النتيجة في هذا Vector3.

+ + يعود بنقطة كنسبة من عرض وارتفاع وعمق هذا المربع. +

+ +

[method:Vector3 getSize]( [param:Vector3 target] )

+

+ [page:Vector3 target] — سيتم نسخ النتيجة في هذا Vector3.

+ + يعود بعرض وارتفاع وعمق هذا المربع. +

+ +

[method:this intersect]( [param:Box3 box] )

+

+ [page:Box3 box] - مربع للتقاطع معه.

+ + يحسب تقاطع هذا و [page:Box3 box]، وضع الحد الأقصى لهذا المربع على أقل حدود علوية للمربعين والحد الأدنى لهذا المربع على أكبر حدود سفلى للمربعين. إذا لم يكن هناك تداخل، يجعل هذا المربع فارغًا. +

+ +

[method:Boolean intersectsBox]( [param:Box3 box] )

+

+ [page:Box3 box] - مربع للتحقق من التقاطع ضده.

+ + يحدد ما إذا كان هذا المربع يتقاطع مع [page:Box3 box] أم لا. +

+ +

[method:Boolean intersectsPlane]( [param:Plane plane] )

+

+ [page:Plane plane] - [page:Plane] للتحقق من التقاطع ضده.

+ + يحدد ما إذا كان هذا المربع يتقاطع مع [page:Plane plane] أم لا. +

+ +

[method:Boolean intersectsSphere]( [param:Sphere sphere] )

+

+ [page:Sphere sphere] - [page:Sphere] للتحقق من التقاطع ضده.

+ + يحدد ما إذا كان هذا المربع يتقاطع مع [page:Sphere sphere] أم لا. +

+ +

[method:Boolean intersectsTriangle]( [param:Triangle triangle] )

+

+ [page:Triangle triangle] - [page:Triangle] للتحقق من التقاطع ضده.

+ + يحدد ما إذا كان هذا المربع يتقاطع مع [page:Triangle triangle] أم لا. +

+ +

[method:Boolean isEmpty]()

+

+ يعود بـ true إذا كان هذا المربع يشمل صفر نقطة داخل حدوده.
+ يرجى ملاحظة أن المربع الذي يحتوي على حدود سفلى وعلوية متساوية لا يزال يشمل نقطة واحدة، وهي النقطة التي تشترك فيها كلا الحدود. +

+ +

[method:this makeEmpty]()

+

يجعل هذا المربع فارغًا.

+ +

[method:this set]( [param:Vector3 min], [param:Vector3 max] )

+

+ [page:Vector3 min] - [page:Vector3] يمثل الحد السفلي (x، y، z) للمربع.
+ [page:Vector3 max] - [page:Vector3] يمثل الحد العلوي (x، y، z) للمربع.

+ + يضع الحدود السفلى والعلوية (x، y، z) لهذا المربع.
+ يرجى ملاحظة أن هذه الطريقة تنسخ فقط القيم من الكائنات المحددة. +

+ +

[method:this setFromArray]( [param:Array array] )

+

+ array -- مجموعة من بيانات الموضع التي سيغلفها المربع الناتج.

+ + يضع الحدود العلوية والسفلى لهذا المربع لتشمل كل البيانات في `array`. +

+ +

+ [method:this setFromBufferAttribute]( [param:BufferAttribute attribute] ) +

+

+ [page:BufferAttribute attribute] - سمة مخزنة لبيانات الموضع التي سيغلفها المربع الناتج.

+ + يضع الحدود العلوية والسفلى لهذا المربع لتشمل كل البيانات في [page:BufferAttribute attribute]. +

+ +

+ [method:this setFromCenterAndSize]( [param:Vector3 center], [param:Vector3 size] ) +

+

+ [page:Vector3 center], - الموضع المركزي المطلوب للمربع.
+ [page:Vector3 size] - الأبعاد المطلوبة x و y و z للمربع.

+ + يضع هذا المربع في مركز [page:Vector3 center] ويضع عرض وارتفاع وعمق هذا المربع على القيم المحددة
+ في [page:Vector3 size] +

+ +

+ [method:this setFromObject]( [param:Object3D object], [param:Boolean precise] ) +

+

+ [page:Object3D object] - [page:Object3D] لحساب مربع التحديد الخاص به.
+ precise - (اختياري) حساب أصغر مربع تحديد محدود بمحاور العالم على حساب المزيد من الحسابات. الافتراضي هو false.

+ + يحسب مربع التحديد المحدود بمحاور العالم لـ [page:Object3D] (بما في ذلك أطفاله)، مع مراعاة تحولات الكائن والأطفال العالمية. قد يؤدي التابع إلى مربع أكبر من اللازم بشكل صارم. +

+ +

[method:this setFromPoints]( [param:Array points] )

+

+ [page:Array points] - مجموعة من [page:Vector3 Vector3s] التي سيحتوي عليها المربع الناتج.

+ + يضع الحدود العلوية والسفلى لهذا المربع لتشمل كل النقاط في [page:Array points]. +

+ +

[method:this translate]( [param:Vector3 offset] )

+

+ [page:Vector3 offset] - اتجاه ومسافة التحويل.

+ + يضيف [page:Vector3 offset] إلى كلا من الحدود العلوية والسفلى لهذا المربع، بشكل فعال ينقل هذا المربع [page:Vector3 offset] وحدات في الفضاء ثلاثي الأبعاد. +

+ +

[method:this union]( [param:Box3 box] )

+

+ [page:Box3 box] - مربع سيتم دمجه مع هذا المربع.

+ + يحسب اتحاد هذا المربع و [page:Box3 box]، وضع الحد الأقصى لهذا المربع على أكبر حدود علوية للمربعين والحد الأدنى لهذا المربع على أقل حدود سفلى للمربعين. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/math/Color.html b/docs/api/ar/math/Color.html new file mode 100644 index 00000000000000..42a3c89fa17429 --- /dev/null +++ b/docs/api/ar/math/Color.html @@ -0,0 +1,364 @@ + + + + + + + + + +

[name]

+ +

فئة تمثل لونًا.

+ +

+ التكرار عبر مثيل [name] سيعود بمكوناته (r، g، b) بالترتيب المقابل. +

+ +

أمثلة الكود

+ +

يمكن تهيئة Color بأي من الطرق التالية:

+ + // المُنشئ الفارغ - سيكون الافتراضي أبيض + const color1 = new THREE.Color(); + + // لون سداسي عشري (مستحسن) + const color2 = new THREE.Color( 0xff0000 ); + + // سلسلة RGB + const color3 = new THREE.Color("rgb(255، 0، 0)"); + const color4 = new THREE.Color("rgb(100٪، 0٪، 0٪)"); + + // اسم لون X11 - يتم دعم جميع أسماء الألوان الـ 140. + // يرجى ملاحظة عدم وجود CamelCase في الاسم + const color5 = new THREE.Color( 'skyblue' ); + + // سلسلة HSL + const color6 = new THREE.Color("hsl(0، 100٪، 50٪)"); + + // قيم RGB منفصلة بين 0 و 1 + const color7 = new THREE.Color( 1، 0، 0 ); + + +

المنشئ (Constructor)

+ +

+ [name]( [param:Color_Hex_or_String r]، [param:Float g]، [param:Float b] ) +

+

+ [page:Color_Hex_or_String r] - (اختياري) إذا تم تعريف الوسيطتين [page:Float g] و [page:Float b]، فهو مكون أحمر للون. إذا لم يتم تعريفهما، فيمكن أن يكون [link:https://en.wikipedia.org/wiki/Web_colors#Hex_triplet ثلاثيًا سداسيًا عشريًا] (مستحسن)، أو سلسلة على طراز CSS، أو مثيل `Color` آخر.
+ [page:Float g] - (اختياري) إذا تم تعريفه، فهو مكون أخضر للون.
+ [page:Float b] - (اختياري) إذا تم تعريفه، فهو مكون أزرق للون.

+ + يرجى ملاحظة أن الطريقة القياسية لتحديد اللون في three.js هي باستخدام [link:https://en.wikipedia.org/wiki/Web_colors#Hex_triplet ثلاثية سداسية عشرية]، وتستخدم هذه الطريقة في جميع أنحاء المستندات المتبقية.

+ + عند تعريف جميع المعاملات ثم [page:Color_Hex_or_String r] هو المكوِّن الأحمر و [page:Float g] هو المكوِّن الأخضر و [page:Float b] هو المكوِّن الأزرق للون.
+ عند تعريف [page:Color_Hex_or_String r] فقط:
+

+ +
    +
  • + يمكن أن يكون [link:https://en.wikipedia.org/wiki/Web_colors#Hex_triplet ثلاثيًا سداسيًا عشريًا] يمثل اللون (مستحسن). +
  • +
  • يمكن أن يكون مثيل Color آخر.
  • +
  • + يمكن أن يكون سلسلة على طراز CSS. على سبيل المثال: +
      +
    • 'rgb(250، 0،0)'
    • +
    • 'rgb(100٪،0٪،0٪)'
    • +
    • 'hsl(0، 100٪، 50٪)'
    • +
    • '#ff0000'
    • +
    • '#f00'
    • +
    • 'red'
    • +
    +
  • +
+ +

الخصائص (Properties)

+ +

[property:Boolean isColor]

+

علامة للقراءة فقط للتحقق مما إذا كان كائنًا معينًا من نوع [name].

+ +

[property:Float r]

+

قيمة قناة حمراء بين 0 و 1. الافتراضي هو 1.

+ +

[property:Float g]

+

قيمة قناة خضراء بين 0 و 1. الافتراضي هو 1.

+ +

[property:Float b]

+

قيمة قناة زرقاء بين 0 و 1. الافتراضي هو 1.

+ +

الطرق (Methods)

+ +

[method:this add]( [param:Color color] )

+

+ يضيف قيم RGB لـ [page:Color color] إلى قيم RGB لهذا اللون. +

+ +

+ [method:this addColors]( [param:Color color1], [param:Color color2] ) +

+

+ يضع قيم RGB لهذا اللون على مجموع قيم RGB لـ [page:Color color1] و [page:Color color2]. +

+ +

[method:this addScalar]( [param:Number s] )

+

يضيف [page:Number s] إلى قيم RGB لهذا اللون.

+ +

[method:this applyMatrix3]( [param:Matrix3 m] )

+

+ يطبق التحول [page:Matrix3 m] على مكونات RGB لهذا اللون. +

+ +

[method:Color clone]()

+

+ يعود بـ Color جديد مع نفس قيم [page:.r r] و [page:.g g] و [page:.b b] كهذا المربع. +

+ +

[method:this copy]( [param:Color color] )

+

+ ينسخ معاملات [page:.r r] و [page:.g g] و [page:.b b] من [page:Color color] في هذا اللون. +

+ +

[method:this convertLinearToSRGB]()

+

يحول هذا اللون من مساحة خطية إلى مساحة sRGB.

+ +

[method:this convertSRGBToLinear]()

+

يحول هذا اللون من مساحة sRGB إلى مساحة خطية.

+ +

[method:this copyLinearToSRGB]( [param:Color color] )

+

+ [page:Color color] — لون للنسخ.
+ + ينسخ اللون المعطى في هذا اللون، ثم يحول هذا اللون من مساحة خطية إلى مساحة sRGB. +

+ + +

[method:this copySRGBToLinear]( [param:Color color] )

+

+ [page:Color color] — لون للنسخ.
+ + ينسخ اللون المعطى في هذا اللون، ثم يحول هذا اللون من مساحة sRGB إلى مساحة خطية. +

+ +

[method:Boolean equals]( [param:Color color] )

+

+ يقارن قيم RGB لـ [page:Color color] مع تلك الموجودة في هذا الكائن. يعود بـ true إذا كانت متطابقة، و false في حالة عدم التطابق. +

+ +

+ [method:this fromArray]( [param:Array array], [param:Integer offset] ) +

+

+ [page:Array array] - [page:Array] من الأرقام العشرية على شكل [[page:Float r]، [page:Float g]، [page:Float b]].
+ [page:Integer offset] - إزاحة اختيارية في المجموعة.

+ + يضع مكونات هذا اللون بناءً على مجموعة مهيأة مثل [[page:Float r]، [page:Float g]، [page:Float b]]. +

+ +

+ [method:this fromBufferAttribute]( [param:BufferAttribute attribute], [param:Integer index] ) +

+

+ [page:BufferAttribute attribute] - السمة المصدر.
+ [page:Integer index] - فهرس في السمة.

+ + يضع مكونات هذا اللون من [page:BufferAttribute attribute]. +

+ +

+ [method:Integer getHex]( [param:string colorSpace] = SRGBColorSpace ) +

+

يعود بالقيمة السداسية عشرية لهذا اللون.

+ +

+ [method:String getHexString]( [param:string colorSpace] = SRGBColorSpace ) +

+

+ يعود بالقيمة السداسية عشرية لهذا اللون كسلسلة (على سبيل المثال، 'FFFFFF'). +

+ +

+ [method:Object getHSL]( [param:Object target], [param:string colorSpace] = LinearSRGBColorSpace ) +

+

+ [page:Object target] — سيتم نسخ النتيجة في هذا الكائن. يضيف مفاتيح h و s و l إلى الكائن (إذا لم يكن موجودًا بالفعل).

+ + يحول قيم [page:.r r] و [page:.g g] و [page:.b b] لهذا اللون إلى تنسيق [link:https://en.wikipedia.org/wiki/HSL_and_HSV HSL] ويعود بكائن على الشكل: + + + { + h: 0, + s: 0, + l: 0 + } + +

+ +

+ [method:Color getRGB]( [param:Color target], [param:string colorSpace] = LinearSRGBColorSpace ) +

+

+ [page:Color target] — سيتم نسخ النتيجة في هذا الكائن.

+ + يعود بقيم RGB لهذا اللون كمثيل من [page:Color]. +

+ +

+ [method:String getStyle]( [param:string colorSpace] = SRGBColorSpace ) +

+

+ يعود بقيمة هذا اللون كسلسلة على طراز CSS. مثال: `rgb(255،0،0)`. +

+ +

[method:this lerp]( [param:Color color], [param:Float alpha] )

+

+ [page:Color color] - لون للاقتراب منه.
+ [page:Float alpha] - عامل التداخل في الفاصل المغلق `[0، 1]`.

+ + يتداخل بشكل خطي قيم RGB لهذا اللون نحو قيم RGB للحجة الممررة. يمكن التفكير في وسيطة alpha كالنسبة بين اللونين، حيث `0.0` هذا اللون و `1.0` هو الحجة الأولى. +

+ + +

+ [method:this lerpColors]( [param:Color color1], [param:Color color2], [param:Float alpha] ) +

+

+ [page:Color color1] - اللون الابتدائي [page:Color].
+ [page:Color color2] - [page:Color] للتداخل نحوه.
+ [page:Float alpha] - عامل التداخل، عادة في الفاصل المغلق `[0، 1]`.

+ + يضع هذا اللون ليكون اللون المتداخل بشكل خطي بين [page:Color color1] و [page:Color color2] حيث يكون alpha هو نسبة المسافة على الخط الذي يربط بين اللونين - سيكون alpha = 0 [page:Color color1]، وسيكون alpha = 1 [page:Color color2]. +

+ +

[method:this lerpHSL]( [param:Color color], [param:Float alpha] )

+

+ [page:Color color] - لون للاقتراب منه.
+ [page:Float alpha] - عامل التداخل في الفاصل المغلق `[0، 1]`.

+ + يتداخل بشكل خطي قيم HSL لهذا اللون نحو قيم HSL للحجة الممررة. يختلف عن [page:.lerp] الكلاسيكية بعدم التداخل مباشرة من لون إلى آخر، ولكن بدلاً من ذلك يمر عبر جميع الألوان الموجودة بين تلك الألوان. يمكن التفكير في وسيطة alpha كالنسبة بين اللونين، حيث 0.0 هذا اللون و 1.0 هو الحجة الأولى. +

+ +

[method:this multiply]( [param:Color color] )

+

+ يضرب قيم RGB لهذا اللون بقيم RGB لـ [page:Color color] المعطى. +

+ +

[method:this multiplyScalar]( [param:Number s] )

+

يضرب قيم RGB لهذا اللون بـ [page:Number s].

+ +

+ [method:this offsetHSL]( [param:Float h], [param:Float s], [param:Float l] ) +

+

+ يضيف [page:Float h] و [page:Float s] و [page:Float l] المعطى إلى قيم هذا اللون. داخليًا، يحول هذا اللون قيم [page:.r r] و [page:.g g] و [page:.b b] إلى HSL، يضيف [page:Float h] و [page:Float s] و [page:Float l]، ثم يحول اللون مرة أخرى إلى RGB. +

+ +

[method:this set]( [param:Color_Hex_or_String r], [param:Float g], [param:Float b] )

+

+ [page:Color_Hex_or_String r] - (اختياري) إذا تم تعريف الوسيطتين [page:Float g] و [page:Float b]، فهو مكون أحمر للون. إذا لم يتم تعريفهما، فيمكن أن يكون [link:https://en.wikipedia.org/wiki/Web_colors#Hex_triplet ثلاثيًا سداسيًا عشريًا] (مستحسن)، أو سلسلة على طراز CSS، أو مثيل `Color` آخر.
+ [page:Float g] - (اختياري) إذا تم تعريفه، فهو مكون أخضر للون.
+ [page:Float b] - (اختياري) إذا تم تعريفه، فهو مكون أزرق للون.

+ + انظر المُنشئ أعلاه للحصول على التفاصيل الكاملة حول الوسائط الممكنة. يفوض إلى [page:.copy]، + [page:.setStyle]، [page:.setRGB] أو [page:.setHex] اعتمادًا على نوع الإدخال. +

+ +

[method:this setFromVector3]( [param:Vector3 vector] )

+

+ يضع مكونات [page:.r r] و [page:.g g] و [page:.b b] لهذا اللون من مكونات x و y و z لـ [page:Vector3 vector] المحدد. +

+ +

+ [method:this setHex]( [param:Integer hex], [param:string colorSpace] = SRGBColorSpace ) +

+

+ [page:Integer hex] — + [link:https://en.wikipedia.org/wiki/Web_colors#Hex_triplet شكل ثلاثي سداسي عشر].

+ + يضع هذا اللون من قيمة سداسية عشرية. +

+ +

+ [method:this setHSL]( [param:Float h], [param:Float s], [param:Float l], [param:string colorSpace] = LinearSRGBColorSpace ) +

+

+ [page:Float h] — قيمة اللون بين 0.0 و 1.0
+ [page:Float s] — قيمة التشبع بين 0.0 و 1.0
+ [page:Float l] — قيمة الإضاءة بين 0.0 و 1.0

+ + يضع اللون من قيم HSL. +

+ +

+ [method:this setRGB]( [param:Float r], [param:Float g], [param:Float b], [param:string colorSpace] = LinearSRGBColorSpace ) +

+

+ [page:Float r] — قيمة قناة حمراء بين 0.0 و 1.0.
+ [page:Float g] — قيمة قناة خضراء بين 0.0 و 1.0.
+ [page:Float b] — قيمة قناة زرقاء بين 0.0 و 1.0.

+ + يضع هذا اللون من قيم RGB. +

+ +

[method:this setScalar]( [param:Float scalar] )

+

+ [page:Float scalar] — قيمة بين 0.0 و 1.0.

+ + تضع جميع مكونات اللون الثلاثة على قيمة [page:Float scalar]. +

+ +

+ [method:this setStyle]( [param:String style], [param:string colorSpace] = SRGBColorSpace ) +

+

+ [page:String style] — اللون كسلسلة على طراز CSS.

+ + يضع هذا اللون من سلسلة على طراز CSS. على سبيل المثال، "rgb(250، 0،0)"، "rgb(100٪، 0٪، 0٪)"، "hsl(0، 100٪، 50٪)"، "#ff0000"، "#f00" أو "red" (أو أي [link:https://en.wikipedia.org/wiki/X11_color_names#Color_name_chart اسم لون X11] - يتم دعم جميع أسماء الألوان الـ 140).
+ + يتم قبول الألوان شفافة مثل "rgba(255، 0، 0، 0.5)" و "hsla(0، 100٪، 50٪، 0.5)" أيضًا، ولكن سيتم تجاهل إحداثية قناة ألفا.

+ + يرجى ملاحظة أنه بالنسبة لأسماء الألوان X11، تصبح الكلمات المتعددة مثل Dark Orange السلسلة 'darkorange'. +

+ +

+ [method:this setColorName]( [param:String style], [param:string colorSpace] = SRGBColorSpace ) +

+

+ [page:String style] — اسم اللون (من [link:https://en.wikipedia.org/wiki/X11_color_names#Color_name_chart أسماء الألوان X11]).

+ + يضع هذا اللون من اسم لون. أسرع من طريقة [page:.setStyle] إذا كنت لا تحتاج إلى التنسيقات الأخرى على طراز CSS.

+ + للراحة، يتم عرض قائمة الأسماء في Color.NAMES كتجزئة: + + Color.NAMES.aliceblue // returns 0xF0F8FF + +

+ +

[method:this sub]( [param:Color color] )

+

+ يطرح مكونات RGB للون المحدد من مكونات RGB لهذا اللون. إذا نتج عن ذلك مكون سالب، يتم تعيين هذا المكون إلى صفر. +

+ +

+ [method:Array toArray]( [param:Array array], [param:Integer offset] ) +

+

+ [page:Array array] - مجموعة اختيارية لتخزين اللون فيها.
+ [page:Integer offset] - إزاحة اختيارية في المجموعة.

+ + يعود بمجموعة على شكل [r, g, b]. +

+ +

[method:Number toJSON]()

+

+ هذه الطرق تحدد نتيجة التسلسل لـ [name]. يعود باللون كقيمة سداسية عشرية. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/math/Cylindrical.html b/docs/api/ar/math/Cylindrical.html new file mode 100644 index 00000000000000..d7a017d87b0b03 --- /dev/null +++ b/docs/api/ar/math/Cylindrical.html @@ -0,0 +1,77 @@ + + + + + + + + + +

[name]

+ +

+ نقطة + [link:https://en.wikipedia.org/wiki/Cylindrical_coordinate_system الإحداثيات الاسطوانية]. +

+ +

المنشئ (Constructor)

+ +

+ [name]( [param:Float radius], [param:Float theta], [param:Float y] ) +

+

+ [page:Float radius] - المسافة من المبدأ إلى نقطة في المستوى x-z. الافتراضي هو `1.0`.
+ [page:Float theta] - الزاوية عكس عقارب الساعة في المستوى x-z مقاسة بالراديان من المحور z الموجب. الافتراضي هو `0`.
+ [page:Float y] - الارتفاع فوق المستوى x-z. الافتراضي هو `0`. +

+ +

الخصائص (Properties)

+ +

[property:Float radius]

+ +

[property:Float theta]

+ +

[property:Float y]

+ +

الطرق (Methods)

+ +

[method:Cylindrical clone]()

+

+ يعيد اسطواني جديد بنفس خصائص [page:.radius radius], + [page:.theta theta] و [page:.y y] كهذا. +

+ +

[method:this copy]( [param:Cylindrical other] )

+

+ ينسخ قيم خصائص [page:.radius radius], + [page:.theta theta] و [page:.y y] للإسطواني الممرر إلى هذا الإسطواني. +

+ +

+ [method:this set]( [param:Float radius], [param:Float theta], [param:Float y] ) +

+

+ يضبط قيم خصائص هذا الإسطواني [page:.radius radius], [page:.theta theta] و [page:.y y]. +

+ +

[method:this setFromVector3]( [param:Vector3 vec3] )

+

+ يضبط قيم خصائص هذا الإسطواني [page:.radius radius], [page:.theta theta] + و [page:.y y] من الـ[page:Vector3 Vector3]. +

+ +

+ [method:this setFromCartesianCoords]( [param:Float x], [param:Float y], [param:Float z] ) +

+

+ يضبط قيم خصائص هذا الإسطواني [page:.radius radius], [page:.theta theta] و + [page:.y y] من الإحداثيات الديكارتية. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/math/Euler.html b/docs/api/ar/math/Euler.html new file mode 100644 index 00000000000000..f1078b82115356 --- /dev/null +++ b/docs/api/ar/math/Euler.html @@ -0,0 +1,175 @@ + + + + + + + + + +

[name]

+ +

+ فئة تمثل [link:http://en.wikipedia.org/wiki/Euler_angles زوايا أويلر].

+ + تصف زوايا أويلر التحول الدوار بتدوير كائن على + محاوره المختلفة بمقادير محددة لكل محور، وترتيب محور محدد. +

+ +

+ التكرار عبر نسخة [name] سيعود بمكوناته (x، y، z، + order) في الترتيب المقابل. +

+ +

مثال الكود

+ + + const a = new THREE.Euler( 0, 1, 1.57, 'XYZ' ); + const b = new THREE.Vector3( 1, 0, 1 ); + b.applyEuler(a); + + +

المنشئ (Constructor)

+ +

+ [name]( [param:Float x], [param:Float y], [param:Float z], [param:String order] ) +

+

+ [page:Float x] - (اختياري) زاوية المحور x بالراديان. الافتراضي هو + `0`.
+ [page:Float y] - (اختياري) زاوية المحور y بالراديان. الافتراضي هو + `0`.
+ [page:Float z] - (اختياري) زاوية المحور z بالراديان. الافتراضي هو + `0`.
+ [page:String order] - (اختياري) سلسلة تمثل الترتيب الذى يتم فيه تطبیق + التدویرات، الافتراضی هو 'XYZ' (یجب أن تكون بحروف كبیرة).

+

+ +

الخصائص (Properties)

+ +

[property:Boolean isEuler]

+

علامة للقراءة فقط للتحقق مما إذا كان كائنًا معطىً من نوع [name].

+ +

[property:String order]

+

+ الترتيب الذي يتم فيه تطبيق التدويرات. الافتراضي هو 'XYZ'، مما يعني أن + الكائن سيتم تدويره أولاً حول محور X الخاص به، ثم محور Y الخاص به وأخيرًا + محور Z الخاص به. الإمكانيات الأخرى هي: 'YZX'، 'ZXY'، 'XZY'، 'YXZ' + و'ZYX'. يجب أن تكون هذه بحروف كبيرة.

+ + يستخدم Three.js زوايا Tait-Bryan `intrinsic`. هذا يعني أن التدويرات + يتم تنفيذها بالنسبة إلى نظام الإحداثيات `local`. أي للترتيب 'XYZ'، يتم + التدوير أولاً حول محور local-X (وهو نفسه محور world-X)، ثم حول local-Y + (الذي قد يكون الآن مختلفًا عن محور world Y)، ثم local-Z (الذي قد يكون + مختلفًا عن محور world Z).

+

+ +

[property:Float x]

+

القيمة الحالية للمكون x.

+ +

[property:Float y]

+

القيمة الحالية للمكون y.

+ +

[property:Float z]

+

القيمة الحالية للمكون z.

+ +

الطرق (Methods)

+ +

[method:this copy]( [param:Euler euler] )

+

ينسخ قيمة [page:Euler euler] إلى هذه الزاوية الأويلر.

+ +

[method:Euler clone]()

+

يعود إلى أويلر جديد بنفس المعلمات كهذا.

+ +

[method:Boolean equals]( [param:Euler euler] )

+

يتحقق من المساواة الصارمة لهذه الزاوية الأويلر و[page:Euler euler].

+ +

[method:this fromArray]( [param:Array array] )

+

+ [page:Array array] من طول 3 أو 4. يتوافق السجل 4 اختياريًا + إلى [page:.order order].

+ + يعيّن زاوية [page:.x x] لهذه الزاوية الأويلر إلى `array[0]`.
+ يعيّن زاوية [page:.y y] لهذه الزاوية الأويلر إلى `array[1]`.
+ يعيّن زاوية [page:.z z] لهذه الزاوية الأويلر إلى `array[2]`.
+ اختیاریًا یعین ترتیب هذه الزاویة الأویلر [page:.order order] إلى `array[3]`. +

+ +

[method:this reorder]( [param:String newOrder] )

+

+ يعيد تعيين زاوية الأويلر بترتيب جديد عن طريق إنشاء رباعية من هذه + زاوية الأويلر ثم تعيين هذه الزاوية الأويلر مع الرباعية والترتيب + الجديد.

+ + *تحذير*: هذا يتجاهل معلومات الثورة. +

+ +

+ [method:this set]( [param:Float x], [param:Float y], [param:Float z], [param:String order] ) +

+

+ [page:.x x] - زاوية المحور x بالراديان.
+ [page:.y y] - زاوية المحور y بالراديان.
+ [page:.z z] - زاوية المحور z بالراديان.
+ [page:.order order] - (اختياري) سلسلة تمثل الترتيب الذي يتم فيه تطبيق + التدويرات.

+ + يضبط زوايا هذا التحول الأويلر واختياريًا الترتيب [page:.order order]. +

+ +

+ [method:this setFromRotationMatrix]( [param:Matrix4 m], [param:String order] ) +

+

+ [page:Matrix4 m] - a [page:Matrix4] منها 3x3 العلوية من المصفوفة هي + مصفوفة دوران نقية + (أي غير مقاسة).
+ [page:.order order] - (اختياري) سلسلة تمثل الترتيب الذي يتم فيه تطبيق + التدويرات.
+ + يضبط زوايا هذا التحول الأويلر من مصفوفة دوران نقية بناءً على + التوجه المحدد بالترتيب. +

+ +

+ [method:this setFromQuaternion]( [param:Quaternion q], [param:String order] ) +

+

+ [page:Quaternion q] - رباعية معدلة.
+ [page:.order order] - (اختياري) سلسلة تمثل الترتيب الذى يتم فيه تطبیق + التدويرات.
+ + يضبط زوايا هذا التحول الأويلر من رباعية معدلة بناءً على + التوجه المحدد بـ[page:.order order]. +

+ +

+ [method:this setFromVector3]( [param:Vector3 vector], [param:String order] ) +

+

+ [page:Vector3 vector] - [page:Vector3].
+ [page:.order order] - (اختیاری) سلسلة تمثل الترتیب الذى يتم فيه تطبیق + التدویرات.

+ + قم بضبط [page:.x x], [page:.y y] و[page:.z z]، وقم اختیاریًا بتحديث + الترتیب[page:.order order]. +

+ +

+ [method:Array toArray]( [param:Array array], [param:Integer offset] ) +

+

+ [page:Array array] - (optional) array to store the euler in.
+ [page:Integer offset] (optional) offset in the array.
+ + يعود إلى مصفوفة من شكل [[page:.x x], [page:.y y], [page:.z z], + [page:.order order ]]. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/math/Frustum.html b/docs/api/ar/math/Frustum.html new file mode 100644 index 00000000000000..be360ad35f27c1 --- /dev/null +++ b/docs/api/ar/math/Frustum.html @@ -0,0 +1,111 @@ + + + + + + + + + +

[name]

+ +

+ يتم استخدام [link:http://en.wikipedia.org/wiki/Frustum الهرم] لتحديد + ما هو داخل مجال رؤية الكاميرا. يساعدون في تسريع عملية التصيير - يمكن استبعاد + الكائنات التي تقع خارج هرم الكاميرا بأمان من التصيير.

+ + هذه الفئة مخصصة بشكل رئيسي للاستخدام داخليًا من قبل محرك التصيير لحساب + هرم [page:Camera camera] أو [page:LightShadow.camera shadowCamera]. +

+ +

المنشئ (Constructor)

+ +

+ [name]([param:Plane p0], [param:Plane p1], [param:Plane p2], [param:Plane p3], [param:Plane p4], [param:Plane p5]) +

+

+ [page:Plane p0] - (اختياري) الافتراضي هو [page:Plane] جديد.
+ [page:Plane p1] - (اختياري) الافتراضي هو [page:Plane] جديد.
+ [page:Plane p2] - (اختياري) الافتراضي هو [page:Plane] جديد.
+ [page:Plane p3] - (اختياري) الافتراضي هو [page:Plane] جديد.
+ [page:Plane p4] - (اختياري) الافتراضي هو [page:Plane] جديد.
+ [page:Plane p5] - (اختياري) الافتراضي هو [page:Plane] جديد.

+ + ينشئ [name] جديد. +

+ +

الخصائص (Properties)

+ +

[property:Array planes]

+

مصفوفة من 6 مستويات [page:Plane].

+ +

الطرق (Methods)

+ +

[method:Frustum clone]()

+

أعد هرمًا جديدًا بنفس المعلمات كهذا.

+ +

[method:Boolean containsPoint]( [param:Vector3 point] )

+

+ [page:Vector3 point] - [page:Vector3] للاختبار.

+ + يتحقق مما إذا كان الهرم يحتوي على نقطة[page:Vector3]. +

+ +

[method:this copy]( [param:Frustum frustum] )

+

+ [page:Frustum frustum] - الهرم الذي يتم نسخه

+ + ينسخ خصائص [page:Frustum frustum] الممررة إلى هذا. +

+ +

[method:Boolean intersectsBox]( [param:Box3 box] )

+

+ [page:Box3 box] - [page:Box3] للتحقق من التقاطع.

+ + يعود بـtrue إذا كان [page:Box3 box] يتقاطع مع هذا الهرم. +

+ +

[method:Boolean intersectsObject]( [param:Object3D object] )

+

+ يتحقق مما إذا كانت [page:Object3D object] + [page:BufferGeometry.boundingSphere bounding sphere] تتقاطع مع + الهرم.

+ + لاحظ أن الكائن يجب أن يحتوي على [page:BufferGeometry geometry] حتى + يمكن حساب الكرة المحيطة. +

+ +

[method:Boolean intersectsSphere]( [param:Sphere sphere] )

+

+ [page:Sphere sphere] - [page:Sphere] للتحقق من التقاطع.

+ + يعود بـtrue إذا كان [page:Sphere sphere] يتقاطع مع هذا الهرم. +

+ +

[method:Boolean intersectsSprite]( [param:Sprite sprite] )

+

+ يتحقق مما إذا كان [page:Sprite sprite] يتقاطع مع الهرم.

+

+ +

+ [method:this set]( [param:Plane p0], [param:Plane p1], [param:Plane p2], [param:Plane p3], [param:Plane p4], [param:Plane p5] ) +

+

+ يضبط الهرم من المستويات الممررة. لا يوجد ترتيب مستوى ضمني.
+ لاحظ أن هذه الطريقة تنسخ فقط القيم من الكائنات المعطاة. +

+ +

[method:this setFromProjectionMatrix]( [param:Matrix4 matrix] )

+

+ [page:Matrix4 matrix] - Projection [page:Matrix4] used to set the + [page:.planes planes]

+ يضبط مستويات الهرم من مصفوفة الإسقاط. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/math/Interpolant.html b/docs/api/ar/math/Interpolant.html new file mode 100644 index 00000000000000..6219b78af3154d --- /dev/null +++ b/docs/api/ar/math/Interpolant.html @@ -0,0 +1,68 @@ + + + + + + + + + +

[name]

+ +

+ الفئة الأساسية المجردة للمتداخلات على العينات المعلمية.

+ + مجال المعلمة هو أحادي البعد، عادةً الوقت أو مسار على طول منحنى + محدد بالبيانات.

+ + يمكن أن تكون قيم العينات ذات أبعاد مختلفة وقد تطبق الفئات المشتقة + تفسيرات خاصة للبيانات.

+ + توفر هذه الفئة البحث عن الفاصل في طريقة قالب، مؤجلًا التداخل الفعلي + إلى الفئات المشتقة.

+ + تعقيد الوقت هو `O(1)` للوصول الخطي المتقاطع في نقطتين على الأكثر + و`O(log N)` للوصول العشوائي، حيث *N* هو عدد المواضع.

+ + المراجع: [link:http://www.oodesign.com/template-method-pattern.html http://www.oodesign.com/template-method-pattern.html] +

+ +

المنشئ (Constructor)

+ +

+ [name]( parameterPositions, sampleValues, sampleSize, resultBuffer ) +

+

+ parameterPositions -- مصفوفة من المواضع
+ sampleValues -- مصفوفة من العينات
+ sampleSize -- عدد العينات
+ resultBuffer -- مخزن لتخزين نتائج التداخل.

+ + ملاحظة: هذا غير مصمم ليتم استدعاؤه مباشرة. +

+ +

الخصائص (Properties)

+ +

[property:null parameterPositions]

+ +

[property:null resultBuffer]

+ +

[property:null sampleValues]

+ +

[property:Object settings]

+

هيكل إعدادات اختياري خاص بالفئة الفرعية.

+ +

[property:null valueSize]

+ +

الطرق (Methods)

+ +

[method:Array evaluate]( [param:Number t] )

+

تقيم المتداخل في الموضع *t*.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/math/Line3.html b/docs/api/ar/math/Line3.html new file mode 100644 index 00000000000000..528efecb3dc253 --- /dev/null +++ b/docs/api/ar/math/Line3.html @@ -0,0 +1,142 @@ + + + + + + + + + +

[name]

+ +

+ قطعة خطية هندسية ممثلة بنقطة البداية والنهاية. +

+ +

المنشئ (Constructor)

+ +

[name]( [param:Vector3 start], [param:Vector3 end] )

+

+ [page:Vector3 start] - بداية القطعة الخطية. الافتراضي هو `(0, 0, + 0)`.
+ [page:Vector3 end] - نهاية القطعة الخطية. الافتراضي هو `(0, 0, 0)`.

+ + ينشئ [name] جديد. +

+ +

الخصائص (Properties)

+ +

[property:Vector3 start]

+

[page:Vector3] يمثل نقطة بداية الخط.

+ +

[property:Vector3 end]

+

[page:Vector3] يمثل نقطة نهاية الخط.

+ +

الطرق (Methods)

+ +

[method:this applyMatrix4]( [param:Matrix4 matrix] )

+

يطبق تحويل المصفوفة على القطعة الخطية.

+ +

[method:Vector3 at]( [param:Float t], [param:Vector3 target] )

+

+ [page:Float t] - استخدم قيم 0-1 لإرجاع موضع على طول القطعة + الخطية.
+ [page:Vector3 target] — سيتم نسخ النتيجة في هذا Vector3.

+ + يعود بمتجه في موضع معين على طول الخط. عندما يكون [page:Float t] + = 0، يعود بالمتجه البدائي، وعندما يكون [page:Float t] = 1 يعود + بالمتجه النهائي.
+

+ +

[method:Line3 clone]()

+

+ يعود بـ[page:Line3] جديد مع نفس المتجهات [page:.start start] و + [page:.end end] كهذا. +

+ +

+ [method:Vector3 closestPointToPoint]( [param:Vector3 point], [param:Boolean clampToLine], [param:Vector3 target] ) +

+

+ [page:Vector3 point] - أعد أقرب نقطة على الخط إلى هذه + نقطة.
+ [page:Boolean clampToLine] - ما إذا كان يجب تثبيت القيمة المرجعة على + قطعة خطية.
+ [page:Vector3 target] — سيتم نسخ النتيجة في هذا Vector3.

+ + يعود بأقرب نقطة على الخط. إذا كان [page:Boolean clampToLine] + صحيحًا، فإن القيمة المرجعة ستكون مثبتة على قطعة خطية. +

+ +

+ [method:Float closestPointToPointParameter]( [param:Vector3 point], [param:Boolean clampToLine] ) +

+

+ [page:Vector3 point] - نقطة لإرجاع معلم نقطة له. +
+ [page:Boolean clampToLine] - ما إذا كان يجب تثبيت النتيجة في المدى `[0, + 1]`.

+ + يعود بمعلم نقطة بناءً على أقرب نقطة كما تم توصيلها على + قطعة خطية. إذا كان [page:Boolean clampToLine] صحيحًا، فإن القيمة المرجعة + ستكون بين 0 و 1. +

+ +

[method:this copy]( [param:Line3 line] )

+

+ ينسخ متجهات [page:.start start] و [page:.end end] للخط الممرر + إلى هذا الخط. +

+ +

[method:Vector3 delta]( [param:Vector3 target] )

+

+ [page:Vector3 target] — سيتم نسخ النتيجة في هذا Vector3.

+ + يعود بمتجه دلتا القطعة الخطية (متجه [page:.end end] + ناقص متجه [page:.start start]). +

+ +

[method:Float distance]()

+

+ يعود بـ[link:https://en.wikipedia.org/wiki/Euclidean_distance المسافة الإقليدية] + (المسافة الخطية) بين نقاط [page:.start start] و [page:.end end] للخط. +

+ +

[method:Float distanceSq]()

+

+ يعود بمربع + [link:https://en.wikipedia.org/wiki/Euclidean_distance المسافة الإقليدية] + (المسافة الخطية) بين متجهات [page:.start start] و + [page:.end end] للخط. +

+ +

[method:Boolean equals]( [param:Line3 line] )

+

+ [page:Line3 line] - [page:Line3] للمقارنة مع هذا.

+ + يعود بـtrue إذا كانت نقاط [page:.start start] و [page:.end end] لكلا الخطين + متساوية. +

+ +

[method:Vector3 getCenter]( [param:Vector3 target] )

+

+ [page:Vector3 target] — سيتم نسخ النتيجة في هذا Vector3.

+ + يعود بمركز القطعة الخطية. +

+ +

[method:this set]( [param:Vector3 start], [param:Vector3 end] )

+

+ [page:Vector3 start] - قم بضبط نقطة البدء[page:.start ] للخط.
+ [page:Vector3 end] - قم بضبط نقطة النهاية[page:.end ] للخط.

+ + يضبط قيم البدء والنهاية عن طريق نسخ المتجهات الموفرة. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/math/MathUtils.html b/docs/api/ar/math/MathUtils.html new file mode 100644 index 00000000000000..595ef12d15f05c --- /dev/null +++ b/docs/api/ar/math/MathUtils.html @@ -0,0 +1,208 @@ + + + + + + + + + +

[name]

+ +

كائن مع عدة وظائف مساعدة للرياضيات.

+ +

الطرق (Methods)

+ +

+ [method:Float clamp]( [param:Float value], [param:Float min], [param:Float max] ) +

+

+ [page:Float value] — القيمة التي يتم تثبيتها.
+ [page:Float min] — الحد الأدنى.
+ [page:Float max] — الحد الأقصى.

+ + يثبت[page:Float value ] ليكون بين[page:Float min ] و + [page:Float max ]. +

+ +

[method:Float degToRad]( [param:Float degrees] )

+

تحويل الدرجات إلى راديان.

+ +

+ [method:Integer euclideanModulo]( [param:Integer n], [param:Integer m] ) +

+

+ [page:Integer n]،[page:Integer m ] - أعداد صحيحة

+ + يحسب نمط إقليدي لـ[page:Integer m ] % [page:Integer n ]، هذا + هو: + ( ( n % m ) + m ) % m +

+ +

[method:UUID generateUUID]( )

+

+ توليد + [link:https://en.wikipedia.org/wiki/Universally_unique_identifier UUID] + (معرف فريد عالميًا). +

+ +

[method:Boolean isPowerOfTwo]( [param:Number n] )

+

يعود بـ`true` إذا كان [page:Number n] قوة لـ2.

+ +

+ [method:Float inverseLerp]( [param:Float x], [param:Float y], [param:Float value] ) +

+

+ [page:Float x] - نقطة البداية.
+ [page:Float y] - نقطة النهاية.
+ [page:Float value] - قيمة بين البداية والنهاية.

+ + يعود بالنسبة المئوية في الفترة المغلقة `[0، 1]` للقيمة المعطاة + بين نقطة البداية والنهاية. +

+ +

+ [method:Float lerp]( [param:Float x], [param:Float y], [param:Float t] ) +

+

+ [page:Float x] - نقطة البداية.
+ [page:Float y] - نقطة النهاية.
+ [page:Float t] - عامل التداخل في الفترة المغلقة `[0، 1]`.

+ + يعود بقيمة[link:https://en.wikipedia.org/wiki/Linear_interpolation متداخلة خطيًا ] + من نقطتين معروفتين بناءً على الفترة المعطاة - + [page:Float t ] = 0 سيعود بـ[page:Float x ] و[page:Float t ] = 1 سوف + يعود بـ[page:Float y ]. +

+ +

+ [method:Float damp]( [param:Float x], [param:Float y], [param:Float lambda], [param:Float dt] ) +

+

+ [page:Float x] - نقطة حالية.
+ [page:Float y] - نقطة الهدف.
+ [page:Float lambda] - قيمة lambda أعلى ستجعل الحركة أكثر + فجأة، وقيمة أقل ستجعل الحركة أكثر تدرجًا.
+ [page:Float dt] - وقت دلتا بالثواني.

+ + تداخل عدد من[page:Float x ] نحو[page:Float y ] بطريقة + ربيعية باستخدام[page:Float dt ] للحفاظ على حركة مستقلة عن معدل + الإطارات. للحصول على التفاصيل، راجع + [link:http://www.rorydriscoll.com/2016/03/07/frame-rate-independent-damping-using-lerp/ تخميد مستقل عن معدل الإطارات باستخدام lerp]. +

+ + +

+ [method:Float mapLinear]( [param:Float x], [param:Float a1], [param:Float a2], [param:Float b1], [param:Float b2] ) +

+

+ [page:Float x] — القيمة التي يتم تعيينها.
+ [page:Float a1] — الحد الأدنى للنطاق A.
+ [page:Float a2] — الحد الأقصى للنطاق A.
+ [page:Float b1] — الحد الأدنى للنطاق B.
+ [page:Float b2] — الحد الأقصى للنطاق B.

+ + تعيين خطي لـ[page:Float x ] من النطاق [[page:Float a1 ], [page:Float a2 ]] إلى النطاق [[page:Float b1 ], [page:Float b2 ]]. +

+ +

[method:Float pingpong]( [param:Float x], [param:Float length] )

+

+ [page:Float x] — القيمة التي يتم تعيينها.
+ [page:Float length] — القيمة الموجبة التي ستتعامل معها الوظيفة. + الافتراضي هو 1.

+ + يعود بقيمة تتبادل بين 0 و[param:Float length ]. +

+ +

[method:Integer ceilPowerOfTwo]( [param:Number n] )

+

+ يعود بأصغر قوة من 2 أكبر من أو يساوي + [page:Number n ]. +

+ +

[method:Integer floorPowerOfTwo]( [param:Number n] )

+

+ يعود بأكبر قوة من 2 أقل من أو يساوي[page:Number n ]. +

+ +

[method:Float radToDeg]( [param:Float radians] )

+

تحويل الراديان إلى درجات.

+ +

[method:Float randFloat]( [param:Float low], [param:Float high] )

+

عشوائية عائمة في الفترة [[page:Float low ], [page:Float high ]].

+ +

[method:Float randFloatSpread]( [param:Float range] )

+

+ عشوائية عائمة في الفترة [-[page:Float range ] / 2،[page:Float range ] + / 2]. +

+ +

+ [method:Integer randInt]( [param:Integer low], [param:Integer high] ) +

+

عشوائية صحيحة في الفترة [[page:Float low ],[page:Float high ]].

+ +

[method:Float seededRandom]( [param:Integer seed] )

+

+ عشوائية زائفة محددة عائمة في الفترة `[0، 1]`. هو اختیاری + [page:Integer seed ]. +

+ +

+ [method:Float smoothstep]( [param:Float x], [param:Float min], [param:Float max] ) +

+

+ [page:Float x] - القيمة التي يتم تقييمها بناءً على موقعها بين الحد + الأدنى والأقصى.
+ [page:Float min] - أي قيمة x أدنى من الحد الأدنى ستكون 0.
+ [page:Float max] - أي قيمة x أعلى من الحد الأقصى ستكون 1.

+ + يعود بقيمة بين 0-1 تمثل النسبة المئوية التي انتقلت فيها x بين الحد + الأدنى والأقصى، ولكن تم تجانسها أو تبطئها كلما اقترب X من + الحد الأدنى والأقصى.

+ + راجع [link:http://en.wikipedia.org/wiki/Smoothstep Smoothstep] للحصول على التفاصيل. +

+ +

+ [method:Float smootherstep]( [param:Float x], [param:Float min], [param:Float max] ) +

+

+ [page:Float x] - القيمة التي يتم تقييمها بناءً على موقعها بين الحد + الأدنى والأقصى.
+ [page:Float min] - أي قيمة x أدنى من الحد الأدنى ستكون 0.
+ [page:Float max] - أي قيمة x أعلى من الحد الأقصى ستكون 1.

+ + يعود بقيمة بين 0-1. تغير على smoothstep + يحتوي على مشتقات من الطلب 1st و 2nd صفر في x=0 و x=1. +

+ +

+ [method:undefined setQuaternionFromProperEuler]( [param:Quaternion q], [param:Float a], [param:Float b], [param:Float c], [param:String order] ) +

+

+ [page:Quaternion q] - رباعية لضبطها
+ [page:Float a] - التدوير المطبق على المحور الأول، بالراديان
+ [page:Float b] - التدوير المطبق على المحور الثاني، بالراديان +
+ [page:Float c] - التدوير المطبق على المحور الثالث، بالراديان
+ [page:String order] - سلسلة تحدد ترتيب المحاور: 'XYX'، 'XZX'، + 'YXY'، 'YZY'، 'ZXZ'، أو 'ZYZ'

+ + يضبط رباعية[page:Quaternion q ] من + [link:http://en.wikipedia.org/wiki/Euler_angles زوايا أويلر Proper intrinsic ] + المعرفة بزوايا[page:Float a ]،[page:Float b ]، و[page:Float c ]، + والترتیب[page:String order].
+ + يتم تطبیق التدویرات على المحاور بالترتیب المحدد بـ[page:String order]: + يتم تطبیق التدویر بزاویة[page:Float a ] أولًا، ثم بزاویة + [page:Float b ]، ثم بزاویة[page:Float c ]. زوایا هی فی رادیان. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/math/Matrix3.html b/docs/api/ar/math/Matrix3.html new file mode 100644 index 00000000000000..d8a136cdbf6f43 --- /dev/null +++ b/docs/api/ar/math/Matrix3.html @@ -0,0 +1,452 @@ + + + + + + + + + +

[name]

+ +

+ فئة تمثل 3x3 + [link:https://en.wikipedia.org/wiki/Matrix_(mathematics) مصفوفة]. +

+ +

مثال الكود

+ +const m = new Matrix3(); + + +

ملاحظة حول الترتيب الرئيسي للصف والعمود الرئيسي

+

+ يأخذ المنشئ وطريقة [page:set]() الوسائط في + [link:https://en.wikipedia.org/wiki/Row-_and_column-major_order#Column-major_order ترتيب الصف الرئيسي] + ، في حين أنها مخزنة داخليًا في مصفوفة [page:.elements elements] + بترتيب العمود الرئيسي.

+ + هذا يعني أن استدعاء + +m.set( 11, 12, 13, + 21, 22, 23, + 31, 32, 33 ); + + ستؤدي إلى مصفوفة [page:.elements elements] التي تحتوي على: + +m.elements = [ 11, 21, 31, + 12, 22, 32, + 13, 23, 33 ]; + + وداخليًا يتم تنفيذ جميع الحسابات باستخدام ترتيب العمود الرئيسي. + ومع ذلك ، نظرًا لأن الترتيب الفعلي لا يحدث فرقًا رياضيًا و + معظم الناس معتادون على التفكير في المصفوفات بترتيب الصف الرئيسي ، فإن + وثائق three.js تظهر المصفوفات بترتيب الصف الرئيسي. فقط تحمل في + اذهانك أنه إذا كنت تقرأ التعليمات البرمجية المصدرية ، فستضطر إلى أخذ + [link:https://en.wikipedia.org/wiki/Transpose عكس] لأية مصفوفات + الموضحة هنا لجعل المحاسبات منطقية. +

+ +

المنشئ (Constructor)

+ +

[name]( [param:Number n11], [param:Number n12], [param:Number n13], + [param:Number n21], [param:Number n22], [param:Number n23], + [param:Number n31], [param:Number n32], [param:Number n33] )

+

+ ينشئ مصفوفة 3x3 بالوسائط المعطاة بترتيب الصف الرئيسي. إذا لم يتم توفير أية وسائط ، يقوم المنشئ بتهيئة + [name] إلى مصفوفة هوية 3x3 [link:https://en.wikipedia.org/wiki/Identity_matrix]. +

+ +

الخصائص (Properties)

+ +

[property:Array elements]

+

+ قائمة [link:https://en.wikipedia.org/wiki/Row-_and_column-major_order بالعمود الرئيسي] من قيم المصفوفة. +

+ +

الطرق (Methods)

+ +

[method:Matrix3 clone]()

+

ينشئ Matrix3 جديدًا وبعناصر متطابقة مع هذا.

+ +

[method:this copy]( [param:Matrix3 m] )

+

ينسخ عناصر المصفوفة [page:Matrix3 m] في هذه المصفوفة.

+ +

[method:Float determinant]()

+

+ يحسب ويعيد [link:https://en.wikipedia.org/wiki/Determinant المحدد] لهذه المصفوفة. +

+ +

[method:Boolean equals]( [param:Matrix3 m] )

+

يرجع صحيحًا إذا كانت هذه المصفوفة و [page:Matrix3 m] متساويتين.

+ +

+ [method:this extractBasis]( [param:Vector3 xAxis], [param:Vector3 yAxis], [param:Vector3 zAxis] ) +

+

+ يستخرج [link:https://en.wikipedia.org/wiki/Basis_(linear_algebra) الأساس] + لهذه المصفوفة في ثلاثة متجهات محورية مقدمة. إذا كانت هذه المصفوفة + هي: +

+ + + + [ + + + a + b + c + + + d + e + f + + + g + h + i + + + ] + + + +

+ ثم سيتم تعيين [page:Vector3 xAxis] ، [page:Vector3 yAxis] ، [page:Vector3 zAxis] + إلى: +

+ +
+ + + xAxis + = + [ + + a + d + g + + ] + + , + + + + yAxis + = + [ + + b + e + h + + ] + + , and + + + + zAxis + = + [ + + c + f + i + + ] + + +
+ +

+ [method:this fromArray]( [param:Array array], [param:Integer offset] ) +

+

+ [page:Array array] - المصفوفة التي يتم قراءة العناصر منها.
+ [page:Integer offset] - (اختياري) فهرس العنصر الأول في المصفوفة. + الافتراضي هو 0.

+ + يضع عناصر هذه المصفوفة بناءً على مصفوفة في + [link:https://en.wikipedia.org/wiki/Row-_and_column-major_order#Column-major_order تنسيق العمود الرئيسي]. +

+ +

[method:this invert]()

+

+ يعكس هذه المصفوفة ، باستخدام + [link:https://en.wikipedia.org/wiki/Invertible_matrix#Analytic_solution الطريقة التحليلية]. + لا يمكنك العكس مع محدد صفر. إذا قمت بمحاولة هذا ، فإن الطريقة تنتج مصفوفة صفرية بدلاً من ذلك. +

+ +

[method:this getNormalMatrix]( [param:Matrix4 m] )

+

+ [page:Matrix4 m] - [page:Matrix4]

+ + يضع هذه المصفوفة كـ 3x3 العلوي الأيسر من + [link:https://en.wikipedia.org/wiki/Normal_matrix المصفوفة الطبيعية] لل + مرور [page:Matrix4 matrix4]. + المصفوفة الطبيعية هي + [link:https://en.wikipedia.org/wiki/Invertible_matrix العكس] + [link:https://en.wikipedia.org/wiki/Transpose عكس] للمصفوفة + [page:Matrix4 m]. +

+ +

[method:this identity]()

+

+ يعيد هذه المصفوفة إلى مصفوفة الهوية 3x3: +

+ + + + [ + + + 1 + 0 + 0 + + + 0 + 1 + 0 + + + 0 + 0 + 1 + + + ] + + + +

[method:this makeRotation]( [param:Float theta] )

+

+ [page:Float theta] - زاوية الدوران بالراديان. تدور القيم الموجبة + عكس عقارب الساعة.

+ + يضع هذه المصفوفة كتحول دوران ثنائي الأبعاد بـ [page:Float theta] + راديان. المصفوفة الناتجة ستكون: +

+ + + + [ + + + + cos + θ + + + -sin + θ + + + 0 + + + + + sin + θ + + + cos + θ + + + 0 + + + + 0 + 0 + 1 + + + ] + + + +

[method:this makeScale]( [param:Float x], [param:Float y] )

+

+ [page:Float x] - المبلغ الذي يتم قياسه في المحور X.
+ [page:Float y] - المبلغ الذي يتم قياسه في المحور Y.
+ + يضع هذه المصفوفة كتحول قياس ثنائي الأبعاد: +

+ + + + [ + + + x + 0 + 0 + + + 0 + y + 0 + + + 0 + 0 + 1 + + + ] + + + +

[method:this makeTranslation]( [param:Vector2 v] )

+

[method:this makeTranslation]( [param:Float x], [param:Float y] )

+

+ [page:Vector2 v] تحويل الترجمة من المتجه.
+ أو
+ [page:Float x] - المبلغ الذي يتم ترجمته في المحور X.
+ [page:Float y] - المبلغ الذي يتم ترجمته في المحور Y.
+ + يضع هذه المصفوفة كتحويل ترجمة ثنائي الأبعاد: +

+ + + + [ + + + 1 + 0 + x + + + 0 + 1 + y + + + 0 + 0 + 1 + + + ] + + + +

[method:this multiply]( [param:Matrix3 m] )

+

يضرب هذه المصفوفة بعد [page:Matrix3 m].

+ +

+ [method:this multiplyMatrices]( [param:Matrix3 a], [param:Matrix3 b] ) +

+

يضع هذه المصفوفة على [page:Matrix3 a] x [page:Matrix3 b].

+ +

[method:this multiplyScalar]( [param:Float s] )

+

يضرب كل مكون من مكونات المصفوفة بالقيمة العددية *s*.

+ +

[method:this rotate]( [param:Float theta] )

+

يدور هذه المصفوفة بالزاوية المعطاة (بالراديان).

+ +

[method:this scale]( [param:Float sx], [param:Float sy] )

+

يقيس هذه المصفوفة بالقيم العددية المعطاة.

+ +

+ [method:this set]( [param:Float n11], [param:Float n12], [param:Float n13], [param:Float n21], [param:Float n22], [param:Float n23], [param:Float n31], [param:Float n32], [param:Float n33] ) +

+

+ يضع قيم المصفوفة 3x3 على + [link:https://en.wikipedia.org/wiki/Row-_and_column-major_order تسلسل قيم رئيسية للصف]: +

+ + + + [ + + + n11 + n12 + n13 + + + n21 + n22 + n23 + + + n31 + n32 + n33 + + + ] + + + +

[method:this premultiply]( [param:Matrix3 m] )

+

Pre-multiplies this matrix by [page:Matrix3 m].

+ +

[method:this setFromMatrix4]( [param:Matrix4 m] )

+

+ قم بتعيين هذه المصفوفة إلى مصفوفة 3x3 العلوية من Matrix4 + [page:Matrix4 m]. +

+ +

+ [method:this setUvTransform]( [param:Float tx], [param:Float ty], [param:Float sx], [param:Float sy], [param:Float rotation], [param:Float cx], [param:Float cy] ) +

+

+ [page:Float tx] - الإزاحة x
+ [page:Float ty] - الإزاحة y
+ [page:Float sx] - تكرار x
+ [page:Float sy] - تكرار y
+ [page:Float rotation] - الدوران ، بالراديان. تدور القيم الموجبة + عكس عقارب الساعة
+ [page:Float cx] - مركز x للدوران
+ [page:Float cy] - مركز y للدوران

+ + يضع مصفوفة التحويل UV من الإزاحة والتكرار والدوران والمركز. +

+ +

+ [method:Array toArray]( [param:Array array], [param:Integer offset] ) +

+

+ [page:Array array] - (اختياري) مصفوفة لتخزين المتجه الناتج فيها. إذا + لم يتم إعطاء مصفوفة جديدة سيتم إنشاؤها.
+ [page:Integer offset] - (اختياري) إزاحة في المصفوفة التي يجب وضعها فيها + النتيجة.

+ + يكتب عناصر هذه المصفوفة في مصفوفة في + [link:https://en.wikipedia.org/wiki/Row-_and_column-major_order#Column-major_order تنسيق العمود الرئيسي]. +

+ +

[method:this translate]( [param:Float tx], [param:Float ty] )

+

يترجم هذه المصفوفة بالقيم العددية المعطاة.

+ +

[method:this transpose]()

+

+ [link:https://en.wikipedia.org/wiki/Transpose يعكس] هذه المصفوفة في + مكان. +

+ +

[method:this transposeIntoArray]( [param:Array array] )

+

+ [page:Array array] - مصفوفة لتخزين المتجه الناتج فيها.

+ + [link:https://en.wikipedia.org/wiki/Transpose يعكس] هذه المصفوفة في + المصفوفة الموردة ، ويرجع نفسه دون تغيير. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/math/Matrix4.html b/docs/api/ar/math/Matrix4.html new file mode 100644 index 00000000000000..5cbe12a981f0c8 --- /dev/null +++ b/docs/api/ar/math/Matrix4.html @@ -0,0 +1,998 @@ + + + + + + + + + +

[name]

+ +

+ فئة تمثل 4x4 + [link:https://en.wikipedia.org/wiki/Matrix_(mathematics) matrix].

+ + أكثر استخدامات مصفوفة 4x4 شيوعًا في الرسومات الحاسوبية ثلاثية الأبعاد هي كـ + [link:https://en.wikipedia.org/wiki/Transformation_matrix Transformation Matrix]. + لمقدمة عن مصفوفات التحول كما هو مستخدم في WebGL ، + تحقق من + [link:http://www.opengl-tutorial.org/beginners-tutorials/tutorial-3-matrices هذا البرنامج التعليمي].

+ + يسمح هذا لـ[page:Vector3] يمثل نقطة في الفضاء ثلاثي الأبعاد بالخضوع + التحولات مثل الترجمة والدوران والقص والتحجيم والانعكاس و + الإسقاط المتعامد أو المنظور وهكذا ، عن طريق ضربه بـ + المصفوفة. يُعرف هذا باسم `تطبيق` المصفوفة على المتجه.

+ + كل [page:Object3D] لديه ثلاث Matrix4s مرتبطة: +

+
    +
  • + [page:Object3D.matrix]: يخزن هذا التحول المحلي للكائن. + هذا هو تحول الكائن نسبةً إلى والده. +
  • +
  • + [page:Object3D.matrixWorld]: التحول العالمي أو العالمي لـ + الكائن. إذا لم يكن للكائن والد ، فإن هذا مطابق للتحول المحلي + المخزن في [page:Object3D.matrix matrix]. +
  • +
  • + [page:Object3D.modelViewMatrix]: يمثل هذا تحول الكائن + نسبةً إلى نظام إحداثيات الكاميرا. مصفوفة modelViewMatrix للكائن هي + matrixWorld للكائن مضروبًا بـ + matrixWorldInverse للكاميرا. +
  • +
+ + [page:Camera Cameras] لديها ثلاث Matrix4s إضافية: +
    +
  • + [page:Camera.matrixWorldInverse]: مصفوفة العرض - عكس + Camera's [page:Object3D.matrixWorld matrixWorld]. +
  • +
  • + [page:Camera.projectionMatrix]: يمثل المعلومات حول كيفية + إسقاط المشهد على مسافة قص. +
  • +
  • + [page:Camera.projectionMatrixInverse]: عكس projectionMatrix. +
  • +
+ + ملاحظة: [page:Object3D.normalMatrix] ليست Matrix4 ، ولكنها [page:Matrix3]. + +

ملاحظة حول ترتيب الصف الرئيسي والعمود الرئيسي

+

+ يأخذ الباني وطريقة [page:.set set]() المعاملات في + [link:https://en.wikipedia.org/wiki/Row-_and_column-major_order#Column-major_order row-major] + ترتيب ، في حين يتم تخزينها داخليًا في مصفوفة [page:.elements elements] بترتيب العمود الرئيسي.

+ + هذا يعني أن الاتصال + + const m = new THREE.Matrix4(); + m.set( 11، 12، 13، 14، + 21، 22، 23، 24, + 31، 32، 33، 34، + 41، 42، 43، 44 ); + + ستؤدي إلى مصفوفة [page:.elements elements] التي تحتوي على: + + m.elements = [ 11, 21, 31, 41, + 12, 22, 32, 42, + 13, 23, 33, 43, + 14, 24, 34, 44 ]; + + وداخليًا يتم إجراء جميع الحسابات باستخدام ترتيب العمود الرئيسي. + ومع ذلك ، نظرًا لأن الترتيب الفعلي لا يحدث فرقًا رياضيًا و + معظم الناس معتادون على التفكير في المصفوفات بترتيب الصف الرئيسي ، + توثق three.js المصفوفات بترتيب الصف الرئيسي. فقط تذكر أنه إذا كنت + قراءة شفرة المصدر ، ستضطر إلى أخذ + [link:https://en.wikipedia.org/wiki/Transpose transpose] من أية مصفوفات + الموضح هنا لجعل المحاسبات منطقية. +

+ + +

استخراج الموضع والدوران والمقياس

+

+ هناك العديد من الخيارات المتاحة لاستخراج الموضع والدوران و + المقياس من Matrix4. +

+
    +
  • + [page:Vector3.setFromMatrixPosition]: يمكن استخدامه لاستخراج + مكون الترجمة. +
  • +
  • + [page:Vector3.setFromMatrixScale]: يمكن استخدامه لاستخراج المقياس + مكون. +
  • +
  • + [page:Quaternion.setFromRotationMatrix] ، + [page:Euler.setFromRotationMatrix] أو [page:.extractRotation extractRotation] + يمكن استخدامه لاستخراج مكون الدوران من مصفوفة نقية (غير متساوية القياس). +
  • +
  • + [page:.decompose decompose] يمكن استخدامه لاستخراج الموضع والدوران + والمقياس كلهم في آن واحد. +
  • +
+ +

المنشئ (Constructor)

+ +

[name]( [param:Number n11], [param:Number n12], [param:Number n13], [param:Number n14], + [param:Number n21], [param:Number n22], [param:Number n23], [param:Number n24], + [param:Number n31], [param:Number n32], [param:Number n33], [param:Number n34], + [param:Number n41], [param:Number n42], [param:Number n43], [param:Number n44] )

+ +

+ ينشئ مصفوفة 4x4 بالمعاملات المعطاة بترتيب الصف. إذا لم يتم توفير أي معاملات ، فإن الباني يقوم بتهيئة + الـ[name] إلى مصفوفة 4x4[link:https://en.wikipedia.org/wiki/Identity_matrix identity matrix]. +

+ +

الخصائص (Properties)

+ +

[property:Array elements]

+

+ قائمة + [link:https://en.wikipedia.org/wiki/Row-_and_column-major_order#Column-major_order column-major] من قيم المصفوفة. +

+ +

الطرق (Methods)

+ +

[method:Matrix4 clone]()

+

+ ينشئ Matrix4 جديدًا بـ[page:.elements elements] متطابقة لهذه + واحد. +

+ +

+ [method:this compose]( [param:Vector3 position], [param:Quaternion quaternion], [param:Vector3 scale] ) +

+

+ يضع هذه المصفوفة على التحول المكون من[page:Vector3 position] ، + [page:Quaternion quaternion] و[page:Vector3 scale]. +

+ + +

[method:this copy]( [param:Matrix4 m] )

+

+ ينسخ [page:.elements elements] من المصفوفة [page:Matrix4 m] في هذه + المصفوفة. +

+ +

[method:this copyPosition]( [param:Matrix4 m] )

+

+ ينسخ مكون الترجمة من المصفوفة المعطاة [page:Matrix4 m] + في مكون الترجمة لهذه المصفوفة. +

+ +

+ [method:this decompose]( [param:Vector3 position], [param:Quaternion quaternion], [param:Vector3 scale] ) +

+

+ يقوم بتحليل هذه المصفوفة إلى مكوناتها[page:Vector3 position] ،[page:Quaternion quaternion] + و[page:Vector3 scale].

+ ملاحظة: ليست جميع المصفوفات قابلة للتحليل بهذه الطريقة. على سبيل المثال ، إذا كان + كائن لديه والد غير متساوي القياس ، فقد لا تكون مصفوفة العالم الخاصة بالكائن + قابلة للتحليل ، وقد لا تكون هذه الطريقة مناسبة. +

+ +

[method:Float determinant]()

+

+ يحسب ويعيد[link:https://en.wikipedia.org/wiki/Determinant determinant] لهذه المصفوفة.

+ + بناءً على الطريقة المبينة + [link:http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.html هنا]. +

+ +

[method:Boolean equals]( [param:Matrix4 m] )

+

يرجع صحيحًا إذا كانت هذه المصفوفة و[page:Matrix4 m] متساويتين.

+ +

+ [method:this extractBasis]( [param:Vector3 xAxis], [param:Vector3 yAxis], [param:Vector3 zAxis] ) +

+

+ يستخرج[link:https://en.wikipedia.org/wiki/Basis_(linear_algebra) basis] + من هذه المصفوفة في المتجهات الثلاثة المحورية المقدمة. إذا كانت هذه المصفوفة + : +

+ + + + [ + + + a + b + c + d + + + e + f + g + h + + + i + j + k + l + + + m + n + o + p + + + ] + + + +

+ ثم سيتم تعيين[page:Vector3 xAxis] ،[page:Vector3 yAxis] ،[page:Vector3 zAxis] + إلى: +

+ +
+ + + xAxis + = + [ + + a + e + i + + ] + + , + + + + yAxis + = + [ + + b + f + j + + ] + + , and + + + + zAxis + = + [ + + c + g + k + + ] + + +
+ +

[method:this extractRotation]( [param:Matrix4 m] )

+

+ يستخرج مكون الدوران من المصفوفة المعطاة [page:Matrix4 m] + في مكون الدوران لهذه المصفوفة. +

+ +

+ [method:this fromArray]( [param:Array array], [param:Integer offset] ) +

+

+ [page:Array array] - المصفوفة التي يتم قراءة العناصر منها.
+ [page:Integer offset] - (اختياري) إزاحة في المصفوفة. الافتراضي هو + 0.

+ + يضع عناصر هذه المصفوفة بناءً على [page:Array array] في + [link:https://en.wikipedia.org/wiki/Row-_and_column-major_order#Column-major_order column-major] تنسيق. +

+ +

[method:this invert]()

+

+ يعكس هذه المصفوفة ، باستخدام + [link:https://en.wikipedia.org/wiki/Invertible_matrix#Analytic_solution الطريقة التحليلية]. + لا يمكنك عكس مع محدد صفر. إذا + حاولت ذلك ، فإن الطريقة تنتج مصفوفة صفرية بدلاً من ذلك. +

+ +

[method:Float getMaxScaleOnAxis]()

+

يحصل على أقصى قيمة مقياس للمحاور الثلاثة.

+ +

[method:this identity]()

+

+ يعيد تعيين هذه المصفوفة إلى + [link:https://en.wikipedia.org/wiki/Identity_matrix مصفوفة الهوية]. +

+ +

+ [method:this lookAt]( [param:Vector3 eye], [param:Vector3 target], [param:Vector3 up] ) +

+

+ يبني مصفوفة دوران ، تبحث من[page:Vector3 eye] نحو + [page:Vector3 target] متجهًا بالمتجه[page:Vector3 up]. +

+ +

+ [method:this makeRotationAxis]( [param:Vector3 axis], [param:Float theta] ) +

+

+ [page:Vector3 axis] - محور الدوران ، يجب تطبيعه.
+ [page:Float theta] - زاوية الدوران بالراديان.

+ + يضع هذه المصفوفة كتحويل دوران حول[page:Vector3 axis] بـ + [page:Float theta] راديان.
+ + هذا بديل مثير للجدل نوعًا ما ولكنه صحيح رياضيًا للدوران عبر[page:Quaternion Quaternions]. انظر المناقشة + [link:https://www.gamedev.net/articles/programming/math-and-physics/do-we-really-need-quaternions-r1199 هنا]. +

+ +

+ [method:this makeBasis]( [param:Vector3 xAxis], [param:Vector3 yAxis], [param:Vector3 zAxis] ) +

+

+ قم بتعيين هذا إلى [link:https://en.wikipedia.org/wiki/Basis_(linear_algebra) basis] + مصفوفة تتكون من المتجهات الأساسية الثلاثة المقدمة: +

+ + + + [ + + + xAxis.x + yAxis.x + zAxis.x + 0 + + + xAxis.y + yAxis.y + zAxis.y + 0 + + + xAxis.z + yAxis.z + zAxis.z + 0 + + + 0 + 0 + 0 + 1 + + + ] + + + +

+ [method:this makePerspective]( [param:Float left], [param:Float right], [param:Float top], [param:Float bottom], [param:Float near], [param:Float far] ) +

+

+ ينشئ + [link:https://en.wikipedia.org/wiki/3D_projection#Perspective_projection perspective projection] + مصفوفة. يتم استخدام هذا داخليًا بواسطة + [page:PerspectiveCamera.updateProjectionMatrix]() +

+ +

+ [method:this makeOrthographic]( [param:Float left], [param:Float right], [param:Float top], [param:Float bottom], [param:Float near], [param:Float far] ) +

+

+ ينشئ مصفوفة اسقاط متعامدة[link:https://en.wikipedia.org/wiki/Orthographic_projection orthographic projection]. يتم استخدام هذا داخليًا بواسطة + [page:OrthographicCamera.updateProjectionMatrix](). +

+ +

[method:this makeRotationFromEuler]( [param:Euler euler] )

+

+ يضع مكون الدوران (المصفوفة العلوية اليسرى 3x3) من هذه المصفوفة على + الدوران المحدد بالزاوية المعطاة[page:Euler Euler Angle]. باقي + المصفوفة يتم تعيينها على المعرف. اعتمادًا على[page:Euler.order order] + من[page:Euler euler] ، هناك ست نتائج محتملة. راجع + [link:https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix هذه الصفحة] للحصول على قائمة كاملة. +

+ +

[method:this makeRotationFromQuaternion]( [param:Quaternion q] )

+

+ يضع مكون الدوران من هذه المصفوفة على الدوران المحدد بـ + [page:Quaternion q] ، كما هو مبين + [link:https://en.wikipedia.org/wiki/Rotation_matrix#Quaternion هنا]. ال + باقي من المصفوفة يتم تعيينه إلى المعرف. لذلك ، بالنظر إلى[page:Quaternion q] = + w + xi + yj + zk ، فإن المصفوفة الناتجة ستكون: +

+ + + + [ + + + + 1 + - + 2 + + y + 2 + + - + 2 + + z + 2 + + + + 2 + x + y + - + 2 + z + w + + + 2 + x + z + + + 2 + y + w + + + 0 + + + + + 2 + x + y + + + 2 + z + w + + + 1 + - + 2 + + x + 2 + + - + 2 + + z + 2 + + + + 2 + y + z + - + 2 + x + w + + + 0 + + + + + 2 + x + z + - + 2 + y + w + + + 2 + y + z + + + 2 + x + w + + + 1 + - + 2 + + x + 2 + + - + 2 + + y + 2 + + + + 0 + + + + 0 + 0 + 0 + 1 + + + ] + + + +

[method:this makeRotationX]( [param:Float theta] )

+

+ [page:Float theta] - زاوية الدوران بالراديان.

+ + يضع هذه المصفوفة كتحويل دوران حول محور X بواسطة + [page:Float theta] (θ) راديان. المصفوفة الناتجة ستكون: +

+ + + + [ + + + 1 + 0 + 0 + 0 + + + + 0 + + + cos + θ + + + - + sin + θ + + + 0 + + + + + 0 + + + sin + θ + + + cos + θ + + + 0 + + + + 0 + 0 + 0 + 1 + + + ] + + + +

[method:this makeRotationY]( [param:Float theta] )

+

+ [page:Float theta] - زاوية الدوران بالراديان.

+ + يضع هذه المصفوفة كتحويل دوران حول محور Y بواسطة + [page:Float theta] (θ) راديان. المصفوفة الناتجة ستكون: +

+ + + + [ + + + + cos + θ + + + 0 + + + sin + θ + + + 0 + + + + 0 + 1 + 0 + 0 + + + + - + sin + θ + + + 0 + + + cos + θ + + + 0 + + + + 0 + 0 + 0 + 1 + + + ] + + + +

[method:this makeRotationZ]( [param:Float theta] )

+

+ [page:Float theta] - زاوية الدوران بالراديان.

+ + يضع هذه المصفوفة كتحويل دوران حول محور Z بواسطة + [page:Float theta] (θ) راديان. المصفوفة الناتجة ستكون: +

+ + + + [ + + + + cos + θ + + + - + sin + θ + + + 0 + + + 0 + + + + + sin + θ + + + cos + θ + + + 0 + + + 0 + + + + 0 + 0 + 1 + 0 + + + 0 + 0 + 0 + 1 + + + ] + + + +

+ [method:this makeScale]( [param:Float x], [param:Float y], [param:Float z] ) +

+

+ [page:Float x] - المقدار الذي يجب تغييره في محور X.
+ [page:Float y] - المقدار الذي يجب تغييره في محور Y.
+ [page:Float z] - المقدار الذي يجب تغييره في محور Z.

+ + يضع هذه المصفوفة كتحويل قياس: +

+ + + + [ + + + x + 0 + 0 + 0 + + + 0 + y + 0 + 0 + + + 0 + 0 + z + 0 + + + 0 + 0 + 0 + 1 + + + ] + + + +

+ [method:this makeShear]( [param:Float xy], [param:Float xz], [param:Float yx], + [param:Float yz], [param:Float zx], [param:Float zy] ) +

+

+ [page:Float xy] - المقدار الذي يجب قصه X بواسطة Y.
+ [page:Float xz] - المقدار الذي يجب قصه X بواسطة Z.
+ [page:Float yx] - المقدار الذي يجب قصه Y بواسطة X.
+ [page:Float yz] - المقدار الذي يجب قصه Y بواسطة Z.
+ [page:Float zx] - المقدار الذي يجب قصه Z بواسطة X.
+ [page:Float zy] - المقدار الذي يجب قصه Z بواسطة Y.

+ + يضع هذه المصفوفة كتحويل قص: +

+ + + + [ + + + 1 + yx + zx + 0 + + + xy + 1 + zy + 0 + + + xz + yz + 1 + 0 + + + 0 + 0 + 0 + 1 + + + ] + + + +

[method:this makeTranslation]( [param:Vector3 v] )

+

+ [method:this makeTranslation]( [param:Float x], [param:Float y], [param:Float z] ) // واجهة برمجة التطبيقات الاختيارية +

+

+ يضع هذه المصفوفة كتحويل ترجمة من متجه [page:Vector3 v] ، أو أرقام [page:Float x] ، [page:Float y] و [page:Float z]: +

+ + + + [ + + + 1 + 0 + 0 + x + + + 0 + 1 + 0 + y + + + 0 + 0 + 1 + z + + + 0 + 0 + 0 + 1 + + + ] + + + +

[method:this multiply]( [param:Matrix4 m] )

+

تعدل هذه المصفوفة بعد ضربها بـ[page:Matrix4 m].

+ +

+ [method:this multiplyMatrices]( [param:Matrix4 a], [param:Matrix4 b] ) +

+

يضع هذه المصفوفة على[page:Matrix4 a] x[page:Matrix4 b].

+ +

[method:this multiplyScalar]( [param:Float s] )

+

+ يضرب كل مكون من مكونات المصفوفة بقيمة مقياسية[page:Float s]. +

+ +

[method:this premultiply]( [param:Matrix4 m] )

+

تعدل هذه المصفوفة قبل ضربها بـ[page:Matrix4 m].

+ +

[method:this scale]( [param:Vector3 v] )

+

يضرب أعمدة هذه المصفوفة بالمتجه[page:Vector3 v].

+ +

+ [method:this set]( [param:Float n11], [param:Float n12], [param:Float n13], [param:Float n14], [param:Float n21], [param:Float n22], [param:Float n23], [param:Float n24], [param:Float n31], [param:Float n32], [param:Float n33], [param:Float n34], [param:Float n41], [param:Float n42], [param:Float n43], [param:Float n44] ) +

+

+ قم بتعيين عناصر المصفوفة الخاصة بك إلى القيم الموردة بترتيب الصف الرئيسي + قيم [page:Float n11] ، [page:Float n12] ، ... [page:Float n44]. +

+ +

[method:this setFromMatrix3]( [param:Matrix3 m] )

+

+ قم بتعيين عناصر 3x3 العلوية لهذه المصفوفة إلى قيم Matrix3 + [page:Matrix3 m]. +

+ +

[method:this setPosition]( [param:Vector3 v] )

+

+ [method:this setPosition]( [param:Float x], [param:Float y], [param:Float z] ) // واجهة برمجة تطبيقات اختيارية +

+

+ يضع مكون الموضع لهذه المصفوفة من المتجه [page:Vector3 v] ، + دون التأثير على بقية المصفوفة - أي إذا كانت المصفوفة هي + حاليا: +

+ + + + [ + + + a + b + c + d + + + e + f + g + h + + + i + j + k + l + + + m + n + o + p + + + ] + + + +

هذا يصبح:

+ + + + [ + + + a + b + c + v.x + + + e + f + g + v.y + + + i + j + k + v.z + + + m + n + o + p + + + ] + + + +

+ [method:Array toArray]( [param:Array array], [param:Integer offset] ) +

+

+ [page:Array array] - (اختياري) مصفوفة لتخزين المتجه الناتج فيها.
+ [page:Integer offset] - (اختياري) إزاحة في المصفوفة التي يجب وضعها فيها + النتيجة.

+ + يكتب عناصر هذه المصفوفة في مصفوفة في + [link:https://en.wikipedia.org/wiki/Row-_and_column-major_order#Column-major_order تنسيق العمود الرئيسي]. +

+ +

[method:this transpose]()

+

+ [link:https://en.wikipedia.org/wiki/Transpose يعكس] هذه المصفوفة. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/math/Plane.html b/docs/api/ar/math/Plane.html new file mode 100644 index 00000000000000..831e73edada0e6 --- /dev/null +++ b/docs/api/ar/math/Plane.html @@ -0,0 +1,207 @@ + + + + + + + + + +

[name]

+ +

+ سطح ثنائي الأبعاد يمتد إلى ما لا نهاية في الفضاء ثلاثي الأبعاد ، ممثلًا + في [link:http://mathworld.wolfram.com/HessianNormalForm.html Hessian normal form] + بواسطة متجه طبيعي ذو طول وحدة وثابت. +

+ +

المنشئ (Constructor)

+ +

[name]( [param:Vector3 normal], [param:Float constant] )

+

+ [page:Vector3 normal] - (اختياري) وحدة طول[page:Vector3] تحدد + الطبيعي للطائرة. الافتراضي هو `(1، 0، 0)`.
+ [page:Float constant] - (اختياري) المسافة الموقعة من المنشأ إلى + الطائرة. الافتراضي هو `0`. +

+ +

الخصائص (Properties)

+ +

[property:Boolean isPlane]

+

علامة للقراءة فقط للتحقق مما إذا كان كائنًا معينًا من نوع [name].

+ +

[property:Vector3 normal]

+ +

[property:Float constant]

+ +

الطرق (Methods)

+ +

+ [method:this applyMatrix4]( [param:Matrix4 matrix], [param:Matrix3 optionalNormalMatrix] ) +

+

+ [page:Matrix4 matrix] - الـ[Page:Matrix4] للتطبيق.
+ [page:Matrix3 optionalNormalMatrix] - (اختياري) محسوب مسبقًا عادي + [Page:Matrix3] من Matrix4 قيد التطبيق.

+ + تطبيق Matrix4 على الطائرة. يجب أن تكون المصفوفة تحولًا متجانسًا ومتجانسًا. +
+ إذا كنت تزود بـ[page:Matrix3 optionalNormalMatrix] ، فيمكن إنشاؤه + هكذا: + + const optionalNormalMatrix = new THREE.Matrix3().getNormalMatrix( matrix ); + +

+ +

[method:Plane clone]()

+

+ يعيد طائرة جديدة بنفس [page:.normal normal] و + [page:.constant constant] كهذا. +

+ +

[method:Vector3 coplanarPoint]( [param:Vector3 target] )

+

+ [page:Vector3 target] - سيتم نسخ النتيجة في هذا Vector3.

+ + يعيد[page:Vector3] متعامدًا على الطائرة ، عن طريق حساب + إسقاط المتجه الطبيعي في المنشأ على الطائرة. +

+ +

[method:this copy]( [param:Plane plane] )

+

+ ينسخ قيم خصائص [page:.normal normal] و[page:.constant constant] + للطائرة الممر إلى هذه الطائرة. +

+ +

[method:Float distanceToPoint]( [param:Vector3 point] )

+

+ يعود بالمسافة الموقعة من[page:Vector3 point] إلى الطائرة. +

+ +

[method:Float distanceToSphere]( [param:Sphere sphere] )

+

+ يعود بالمسافة الموقعة من[page:Sphere sphere] إلى الطائرة. +

+ +

[method:Boolean equals]( [param:Plane plane] )

+

+ يتحقق لمعرفة ما إذا كانت طائرتان متساويتين (خصائصهما [page:.normal normal] و + [page:.constant constant] تتطابق). +

+ +

+ [method:Vector3 intersectLine]( [param:Line3 line], [param:Vector3 target] ) +

+

+ [page:Line3 line] - الـ[page:Line3] للتحقق من التقاطع.
+ [page:Vector3 target] - سيتم نسخ النتيجة في هذا Vector3.

+ + يعيد نقطة التقاطع للخط الممر والطائرة. يعود + فارغ إذا لم يتقاطع الخط. يعود نقطة البداية للخط إذا كان + الخط متعامد مع الطائرة. +

+ +

[method:Boolean intersectsBox]( [param:Box3 box] )

+

+ [page:Box3 box] - الـ[page:Box3] للتحقق من التقاطع.

+ + يحدد ما إذا كانت هذه الطائرة تتقاطع مع[page:Box3 box]. +

+ +

[method:Boolean intersectsLine]( [param:Line3 line] )

+

+ [page:Line3 line] - الـ[page:Line3] للتحقق من التقاطع.

+ + يختبر ما إذا كان قطع الخط يتقاطع مع (يمر عبر) الطائرة. +

+ +

[method:Boolean intersectsSphere]( [param:Sphere sphere] )

+

+ [page:Sphere sphere] - الـ[page:Sphere] للتحقق من التقاطع.

+ + يحدد ما إذا كانت هذه الطائرة تتقاطع مع[page:Sphere sphere]. +

+ +

[method:this negate]()

+

ينفي كلاً من المتجه الطبيعي والثابت.

+ +

[method:this normalize]()

+

+ يسوي المتجه[page:.normal normal] ، ويضبط + قيمة[page:.constant constant] بشكل ملائم. +

+ +

+ [method:Vector3 projectPoint]( [param:Vector3 point], [param:Vector3 target] ) +

+

+ [page:Vector3 point] - الـ[page:Vector3] للإسقاط على الطائرة.
+ [page:Vector3 target] - سيتم نسخ النتيجة في هذا Vector3.

+ + يسقط نقطة[page:Vector3 point] على الطائرة. +

+ +

[method:this set]( [param:Vector3 normal], [param:Float constant] )

+

+ [page:Vector3 normal] - وحدة طول [page:Vector3] تحدد الطبيعي + من الطائرة.
+ [page:Float constant] - المسافة الموقعة من المنشأ إلى الطائرة.

+ + يضع خصائص [page:.normal normal] و[page:.constant constant] + لهذه الطائرة عن طريق نسخ القيم من الطبيعي المعطى. +

+ +

+ [method:this setComponents]( [param:Float x], [param:Float y], [param:Float z], [param:Float w] ) +

+

+ [page:Float x] - قيمة x للمتجه الطبيعي ذو الطول الوحدة.
+ [page:Float y] - قيمة y للمتجه الطبيعي ذو الطول الوحدة.
+ [page:Float z] - قيمة z للمتجه الطبيعي ذو الطول الوحدة.
+ [page:Float w] - قيمة خاصية [page:.constant constant] + للطائرة.

+ + قم بتعيين المكونات الفردية التي تحدد الطائرة. +

+ +

+ [method:this setFromCoplanarPoints]( [param:Vector3 a], [param:Vector3 b], [param:Vector3 c] ) +

+

+ [page:Vector3 a] - أول نقطة على الطائرة.
+ [page:Vector3 b] - نقطة ثانية على الطائرة.
+ [page:Vector3 c] - نقطة ثالثة على الطائرة.

+ + يحدد الطائرة بناءً على 3 نقاط مقدمة. يفترض أن تكون ترتيب التغليف + عكس عقارب الساعة ، وتحدد اتجاه + [page:.normal normal]. +

+ +

+ [method:this setFromNormalAndCoplanarPoint]( [param:Vector3 normal], [param:Vector3 point] ) +

+

+ [page:Vector3 normal] - وحدة طول[page:Vector3] تحدد الطبيعي + من الطائرة.
+ [page:Vector3 point] -[page:Vector3]

+ + يضع خصائص الطائرة كما هو محدد بـ[page:Vector3 normal] و + نقطة متعامدية عشوائية[page:Vector3 point]. +

+ +

[method:this translate]( [param:Vector3 offset] )

+

+ [page:Vector3 offset] - المسافة التي يجب تحريك الطائرة بها.

+ + يترجم الطائرة بالمسافة التي يحددها متجه[page:Vector3 offset] + . لاحظ أن هذا يؤثر فقط على ثابت الطائرة ولن يؤثر + المتجه الطبيعي. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/math/Quaternion.html b/docs/api/ar/math/Quaternion.html new file mode 100644 index 00000000000000..7950959a54e856 --- /dev/null +++ b/docs/api/ar/math/Quaternion.html @@ -0,0 +1,326 @@ + + + + + + + + + +

[name]

+ +

+ تنفيذ [link:http://en.wikipedia.org/wiki/Quaternion كواترنيون].
+ يتم استخدام الكواترنيونات في three.js لتمثيل + [link:https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation الدوران]. +

+ +

+ التكرار عبر مثيل [name] سيعطي مكوناته (x، y، z، w) + بالترتيب المقابل. +

+ +

+ لاحظ أن three.js تتوقع أن يتم تسوية Quaternions. +

+ +

مثال الكود

+ + + const quaternion = new THREE.Quaternion(); + quaternion.setFromAxisAngle( new THREE.Vector3( 0, 1, 0 ), Math.PI / 2 ); + + const vector = new THREE.Vector3( 1, 0, 0 ); + vector.applyQuaternion( quaternion ); + + +

المنشئ (Constructor)

+ +

+ [name]( [param:Float x], [param:Float y], [param:Float z], [param:Float w] ) +

+

+ [page:Float x] - إحداثية x
+ [page:Float y] - إحداثية y
+ [page:Float z] - إحداثية z
+ [page:Float w] - إحداثية w +

+ +

الخصائص (Properties)

+ +

[property:Boolean isQuaternion]

+

علامة للقراءة فقط للتحقق مما إذا كان الكائن المعطى هو من نوع [name].

+ +

[property:Float x]

+ +

[property:Float y]

+ +

[property:Float z]

+ +

[property:Float w]

+ +

الطرق (Methods)

+ +

[method:Float angleTo]( [param:Quaternion q] )

+

+ يعيد الزاوية بين هذا الكواترنيون وكواترنيون [page:Quaternion q] بالراديان. +

+ +

[method:Quaternion clone]()

+

+ ينشئ كواترنيون جديدًا بخصائص [page:.x x] و [page:.y y] و [page:.z z] + و [page:.w w] متطابقة مع هذا. +

+ +

[method:this conjugate]()

+

+ يعيد التجانس الدوراني لهذا الكواترنيون. التجانس لـ + كواترنيون يمثل نفس الدوران في الاتجاه المعاكس حول + محور الدوران. +

+ +

[method:this copy]( [param:Quaternion q] )

+

+ ينسخ خصائص [page:.x x] و [page:.y y] و [page:.z z] و [page:.w w] + من [page:Quaternion q] إلى هذا الكواترنيون. +

+ +

[method:Boolean equals]( [param:Quaternion v] )

+

+ [page:Quaternion v] - كواترنيون سيتم مقارنة هذا الكواترنيون + إليه.

+ + يقارن خصائص [page:.x x] و [page:.y y] و [page:.z z] و [page:.w w] + من [page:Quaternion v] إلى الخصائص المكافئة لهذا + كواترنيون لتحديد ما إذا كانت تمثل نفس الدوران. +

+ +

[method:Float dot]( [param:Quaternion v] )

+

+ يحسب [link:https://en.wikipedia.org/wiki/Dot_product dot product] + من كواترنيونات [page:Quaternion v] وهذا. +

+ +

+ [method:this fromArray]( [param:Array array], [param:Integer offset] ) +

+

+ [page:Array array] - مصفوفة بتنسيق (x، y، z، w) تستخدم لبناء + الكواترنيون.
+ [page:Integer offset] - (اختياري) إزاحة في المصفوفة.

+ + يضع خصائص [page:.x x] و [page:.y y] و [page:.z z] و [page:.w w] + لهذا الكواترنيون من مصفوفة. +

+ +

[method:this identity]()

+

+ يضع هذا الكواترنيون على الكواترنيون المتطابق ؛ أي إلى + الكواترنيون الذي يمثل "لا دوران". +

+ +

[method:this invert]()

+

+ يعكس هذا الكواترنيون - يحسب [page:.conjugate conjugate]. The + يفترض أن الكواترنيون لديه طول وحدة. +

+ +

[method:Float length]()

+

+ يحسب [link:https://en.wikipedia.org/wiki/Euclidean_distance الطول الإقليدي] + (طول خط مستقيم) لهذا الكواترنيون ، كما هو معتبر + متجه 4 أبعاد. +

+ +

[method:Float lengthSq]()

+

+ يحسب المربع + [link:https://en.wikipedia.org/wiki/Euclidean_distance الطول الإقليدي] + (طول خط مستقيم) لهذا الكواترنيون ، كما هو معتبر متجه 4 أبعاد + . هذا يمكن أن يكون مفيدًا إذا كنت تقارن طول اثنين + كواترنيون ، حيث أن هذا حساب أكثر كفاءة قليلاً من + [page:.length length](). +

+ +

[method:this normalize]()

+

+ [link:https://en.wikipedia.org/wiki/Normalized_vector يعدل] هذا + كواترنيون - أي حساب الكواترنيون الذي يؤدي نفس + دوران كهذا ، ولكن لديه [page:.length length] يساوي `1`. +

+ + +

[method:this multiply]( [param:Quaternion q] )

+

يضرب هذا الكواترنيون بـ[page:Quaternion q].

+ +

+ [method:this multiplyQuaternions]( [param:Quaternion a], [param:Quaternion b] ) +

+

+ يضع هذا الكواترنيون على[page:Quaternion a] x[page:Quaternion b].
+ تم تكييفه من الطريقة الموضحة + [link:http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/code/index.html هنا]. +

+ +

[method:this premultiply]( [param:Quaternion q] )

+

يضرب مسبقًا هذا الكواترنيون بـ[page:Quaternion q].

+ +

[method:this random]()

+

يضع هذا الكواترنيون على كواترنيون عشوائي معتدل ومعتدل.

+ +

+ [method:this rotateTowards]( [param:Quaternion q], [param:Float step] ) +

+

+ [page:Quaternion q] - كواترنيون الهدف.
+ [page:Float step] - الخطوة الزاوية بالراديان.

+ + يدور هذا الكواترنيون بخطوة زاوية معطاة إلى كواترنيون محدد + *q*. تضمن الطريقة ألا يتجاوز الكواترنيون النهائي *q*. +

+ +

[method:this slerp]( [param:Quaternion qb], [param:Float t] )

+

+ [page:Quaternion qb] - التدوير الكواترنيون الآخر
+ [page:Float t] - عامل التداخل في الفترة المغلقة `[0، 1]`.

+ + يتعامل مع التداخل الخطي الكروي بين كواترنيون. + [page:Float t] يمثل مقدار التدوير بين هذا الكواترنيون + (حيث[page:Float t] هو 0) و[page:Quaternion qb] (حيث[page:Float t] + هو 1). يتم تعيين هذا الكواترنيون على النتيجة. اطلع أيضًا على الإصدار الثابت + من `slerp` أدناه. + + + // تدور شبكة نحو كواترنيون هدف + mesh.quaternion.slerp( endQuaternion، 0.01 ); + +

+ +

+ [method:this slerpQuaternions]( [param:Quaternion qa], [param:Quaternion qb], [param:Float t] ) +

+

+ يؤدي التداخل الخطي الكروي بين كواترنيون المعطى + وتخزين النتائج في هذا الكواترنيون. +

+ +

+ [method:this set]( [param:Float x], [param:Float y], [param:Float z], [param:Float w] ) +

+

+ يضع خصائص [page:.x x] ،[page:.y y] ،[page:.z z] ،[page:.w w] من هذا + كواترنيون. +

+ +

+ [method:this setFromAxisAngle]( [param:Vector3 axis], [param:Float angle] ) +

+

+ يضع هذا الكواترنيون من التدوير المحدد بـ[page:Vector3 axis] و + [page:Float angle].
+ تم تكييفه من الطريقة + [link:http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.html هنا].
+ يفترض أن يكون `Axis` معتدلًا ، `angle` بالراديان. +

+ +

[method:this setFromEuler]( [param:Euler euler] )

+

+ يضع هذا الكواترنيون من التدوير المحدد بزاوية[page:Euler]. +

+ +

[method:this setFromRotationMatrix]( [param:Matrix4 m] )

+

+ [page:Matrix4 m] - a[page:Matrix4] من الذي العلوي 3x3 من المصفوفة هو + مصفوفة دوران نقية + (أي غير متساوية القياس).
+ يضع هذا الكواترنيون من مكون التدوير لـ[page:Matrix4 m].
+ تم تكييفه من الطريقة + [link:http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.html هنا]. +

+ +

+ [method:this setFromUnitVectors]( [param:Vector3 vFrom], [param:Vector3 vTo] ) +

+

+ يضع هذا الكواترنيون على التدوير المطلوب لتدوير متجه الاتجاه + [page:Vector3 vFrom] إلى متجه الاتجاه[page:Vector3 vTo].
+ تم تكييفه من الطريقة + [link:http://lolengine.net/blog/2013/09/18/beautiful-maths-quaternion-from-vectors هنا].
+ يفترض أن يكون[page:Vector3 vFrom] و[page:Vector3 vTo] معتدلًا. +

+ +

+ [method:Array toArray]( [param:Array array], [param:Integer offset] ) +

+

+ [page:Array array] - مصفوفة اختيارية لتخزين الكواترنيون. إذا لم يتم + محدد ، سيتم إنشاء مصفوفة جديدة.
+ [page:Integer offset] - (اختياري) إذا تم تحديده ، فسيتم نسخ + في هذه[page:Array].

+ + يعود بالعناصر العددية لهذا الكواترنيون في مصفوفة بتنسيق + [x، y، z، w]. +

+ +

[method:Array toJSON]()

+

+ هذه الطرق تحدد نتيجة التسلسل لـ[name]. يعود + العناصر العددية لهذا الكواترنيون في مصفوفة بتنسيق [x، y، z، w]. +

+ +

+ [method:this fromBufferAttribute]( [param:BufferAttribute attribute], [param:Integer index] ) +

+

+ [page:BufferAttribute attribute] - السمة المصدر.
+ [page:Integer index] - فهرس في السمة.

+ + يضع خصائص [page:.x x] ،[page:.y y] ،[page:.z z] ،[page:.w w] من هذا + كواترنيون من[page:BufferAttribute attribute]. +

+ +

طرق ثابتة (Static Methods)

+ +

+ [method:undefined slerpFlat]( [param:Array dst], [param:Integer dstOffset], + [param:Array src0], [param:Integer srcOffset0], [param:Array src1], + [param:Integer srcOffset1], [param:Float t] ) +

+

+ [page:Array dst] - مصفوفة الإخراج.
+ [page:Integer dstOffset] - إزاحة في مصفوفة الإخراج.
+ [page:Array src0] - مصفوفة المصدر للكواترنيون البدائي.
+ [page:Integer srcOffset0] - إزاحة في مصفوفة `src0`.
+ [page:Array src1] - مصفوفة المصدر للكواترنيون المستهدف.
+ [page:Integer srcOffset1] - إزاحة في مصفوفة `src1`.
+ [page:Float t] - عامل التداخل المعتدل (بين 0 و 1).

+ + تفترض هذه التطبيقات SLERP أن بيانات الكواترنيون يتم إدارتها في + مصفوفات مسطحة. +

+ +

+ [method:Array multiplyQuaternionsFlat]( [param:Array dst], [param:Integer dstOffset], + [param:Array src0], [param:Integer srcOffset0], [param:Array src1], [param:Integer srcOffset1] ) +

+

+ [page:Array dst] - مصفوفة الإخراج.
+ [page:Integer dstOffset] - إزاحة في مصفوفة الإخراج.
+ [page:Array src0] - مصفوفة المصدر للكواترنيون البدائي.
+ [page:Integer srcOffset0] - إزاحة في مصفوفة `src0`.
+ [page:Array src1] - مصفوفة المصدر للكواترنيون المستهدف.
+ [page:Integer srcOffset1] - إزاحة في مصفوفة `src1`.

+ + تفترض هذه التطبيقات التعددية أن بيانات الكواترنيون يتم إدارتها + في مصفوفات مسطحة. +

+ + + +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/math/Ray.html b/docs/api/ar/math/Ray.html new file mode 100644 index 00000000000000..c4ad4476a8146b --- /dev/null +++ b/docs/api/ar/math/Ray.html @@ -0,0 +1,237 @@ + + + + + + + + + +

[name]

+ +

+ شعاع ينبعث من أصل في اتجاه معين. يتم استخدام هذا بواسطة + [page:Raycaster] للمساعدة في + [link:https://en.wikipedia.org/wiki/Ray_casting الإشعاع]. يتم استخدام الإشعاع + لاختيار الماوس (معرفة الكائنات في المسافة 3D التي يكون الماوس + فوقها) من بين أشياء أخرى. +

+ +

المنشئ (Constructor)

+ +

[name]( [param:Vector3 origin], [param:Vector3 direction] )

+

+ [page:Vector3 origin] - (اختياري) أصل الـ[page:Ray]. الافتراضي + هو [page:Vector3] عند (0، 0، 0).
+ [page:Vector3 direction] - [page:Vector3] اتجاه الـ[page:Ray]. + يجب تسوية هذا (بـ[page:Vector3.normalize]) لتشغيل الطرق بشكل صحيح. الافتراضي هو [page:Vector3] عند (0، 0، -1).

+ + ينشئ [name] جديدًا. +

+ +

الخصائص (Properties)

+ +

[property:Vector3 origin]

+

+ أصل الـ[page:Ray]. الافتراضي هو [page:Vector3] عند `(0، 0، 0)`. +

+ +

[property:Vector3 direction]

+

+ اتجاه الـ[page:Ray]. يجب تسوية هذا (بـ + [page:Vector3.normalize]) لتشغيل الطرق بشكل صحيح. الافتراضي هو + [page:Vector3] عند (0، 0، -1). +

+ +

الطرق (Methods)

+ +

[method:this applyMatrix4]( [param:Matrix4 matrix4] )

+

+ [page:Matrix4 matrix4] - الـ[page:Matrix4] لتطبيقه على هذا + [page:Ray].

+ + تحويل هذا الـ[page:Ray] بواسطة [page:Matrix4]. +

+ +

[method:Vector3 at]( [param:Float t], [param:Vector3 target] )

+

+ [page:Float t] - المسافة على طول الـ[page:Ray] لاسترداد موضع + ل.
+ [page:Vector3 target] — سيتم نسخ النتيجة في هذا Vector3.

+ + احصل على [page:Vector3] هو مسافة معينة على طول هذا الـ[page:Ray]. +

+ +

[method:Ray clone]()

+

+ ينشئ Ray جديدًا بخصائص [page:.origin origin] و + [page:.direction direction] متطابقة مع هذا. +

+ +

+ [method:Vector3 closestPointToPoint]( [param:Vector3 point], [param:Vector3 target] ) +

+

+ [page:Vector3 point] - النقطة التي سيتم الحصول على أقرب نهج إليها.
+ [page:Vector3 target] — سيتم نسخ النتيجة في هذا Vector3.

+ + احصل على النقطة على طول هذا الـ[page:Ray] التي تكون أقرب إلى المزود + [page:Vector3]. +

+ +

[method:this copy]( [param:Ray ray] )

+

+ ينسخ خصائص [page:.origin origin] و [page:.direction direction] + من [page:Ray ray] إلى هذا الشعاع. +

+ +

[method:Float distanceSqToPoint]( [param:Vector3 point] )

+

+ [page:Vector3 point] - الـ[page:Vector3] لحساب المسافة إليه.

+ + احصل على المسافة المربعة لأقرب نهج بين [page:Ray] + و [page:Vector3]. +

+ +

+ [method:Float distanceSqToSegment]( [param:Vector3 v0], [param:Vector3 v1], + [param:Vector3 optionalPointOnRay], [param:Vector3 optionalPointOnSegment] ) +

+

+ [page:Vector3 v0] - بداية الخط المستقيم.
+ [page:Vector3 v1] - نهاية الخط المستقيم.
+ optionalPointOnRay - (اختياري) إذا تم توفير هذا ، فإنه يتلقى النقطة + على هذا الـ[page:Ray] الأقرب إلى الشريحة.
+ optionalPointOnSegment - (اختياري) إذا تم توفير هذا ، فإنه يتلقى + نقطة على شريحة الخط الأقرب إلى هذا الـ[page:Ray].

+ + احصل على المسافة المربعة بين هذا الـ[page:Ray] وشريحة خط. +

+ +

[method:Float distanceToPlane]( [param:Plane plane] )

+

+ [page:Plane plane] - الـ[page:Plane] للحصول على المسافة إليه.

+ + احصل على المسافة من [page:.origin origin] إلى [page:Plane] ، أو `null` + إذا لم يتقاطع الـ[page:Ray] مع [page:Plane]. +

+ +

[method:Float distanceToPoint]( [param:Vector3 point] )

+

+ [page:Vector3 point] - [page:Vector3] The [page:Vector3] لحساب + مسافة إليه.

+ + احصل على مسافة أقرب نهج بين الـ[page:Ray] و + [page:Vector3 point]. +

+ +

[method:Boolean equals]( [param:Ray ray] )

+

+ [page:Ray ray] - الـ[page:Ray] للمقارنة معه.

+ + يعود بالقيمة true إذا كان هذا والآخر [page:Ray ray] لديهما [page:.origin origin] + و [page:.direction direction] متساويان. +

+ +

+ [method:Vector3 intersectBox]( [param:Box3 box], [param:Vector3 target] ) +

+

+ [page:Box3 box] - الـ[page:Box3] للتقاطع معه.
+ [page:Vector3 target] — سيتم نسخ النتيجة في هذا Vector3.

+ + تقاطع هذا الـ[page:Ray] مع [page:Box3] ، وإرجاع نقطة التقاطع + أو `null` إذا لم يكن هناك تقاطع. +

+ +

+ [method:Vector3 intersectPlane]( [param:Plane plane], [param:Vector3 target] ) +

+

+ [page:Plane plane] - الـ[page:Plane] للتقاطع معه.
+ [page:Vector3 target] — سيتم نسخ النتيجة في هذا Vector3.

+ + تقاطع هذا الـ[page:Ray] مع [page:Plane] ، وإرجاع نقطة التقاطع + أو `null` إذا لم يكن هناك تقاطع. +

+ +

+ [method:Vector3 intersectSphere]( [param:Sphere sphere], [param:Vector3 target] ) +

+

+ [page:Sphere sphere] - الـ[page:Sphere] للتقاطع معه.
+ [page:Vector3 target] — سيتم نسخ النتيجة في هذا Vector3.

+ + تقاطع هذا الـ[page:Ray] مع [page:Sphere] ، وإرجاع نقطة التقاطع + أو `null` إذا لم يكن هناك تقاطع. +

+ +

+ [method:Vector3 intersectTriangle]( [param:Vector3 a], [param:Vector3 b], [param:Vector3 c], [param:Boolean backfaceCulling], [param:Vector3 target] ) +

+

+ [page:Vector3 a], [page:Vector3 b], [page:Vector3 c] - نقاط الـ[page:Vector3] + التي تشكل المثلث.
+ [page:Boolean backfaceCulling] - ما إذا كان سيتم استخدام قص الوجه الخلفي.
+ [page:Vector3 target] — سيتم نسخ النتيجة في هذا Vector3.

+ + تقاطع هذه الـ[page:Ray] مع مثلث ، وإرجاع نقطة التقاطع + أو `null` إذا لم يكن هناك تقاطع. +

+ +

[method:Boolean intersectsBox]( [param:Box3 box] )

+

+ [page:Box3 box] - [page:Box3] للتقاطع معه.

+ + يعود بالقيمة true إذا كان هذا [page:Ray] يتقاطع مع [page:Box3]. +

+ +

[method:Boolean intersectsPlane]( [param:Plane plane] )

+

+ [page:Plane plane] - [page:Plane] للتقاطع معه.

+ + يعود بالقيمة true إذا كان هذا [page:Ray] يتقاطع مع [page:Plane]. +

+ +

[method:Boolean intersectsSphere]( [param:Sphere sphere] )

+

+ [page:Sphere sphere] - [page:Sphere] للتقاطع معه.

+ + يعود بالقيمة true إذا كان هذا [page:Ray] يتقاطع مع [page:Sphere]. +

+ +

[method:this lookAt]( [param:Vector3 v] )

+

+ [page:Vector3 v] - الـ[page:Vector3] الذي سينظر إليه.

+ + يضبط اتجاه الشعاع للإشارة إلى المتجه في الإحداثيات العالمية. +

+ +

[method:this recast]( [param:Float t] )

+

+ [page:Float t] - المسافة على طول الـ[page:Ray] للإدراج.

+ + تحريك أصل هذا الـ[page:Ray] على طول اتجاهه بالمسافة + المحددة. +

+ +

+ [method:this set]( [param:Vector3 origin], [param:Vector3 direction] ) +

+

+ [page:Vector3 origin] - أصل الـ[page:.origin origin] لـ[page:Ray].
+ [page:Vector3 direction] - اتجاه الـ[page:.direction direction] + لـ[page:Ray]. يجب تسوية هذا (بـ[page:Vector3.normalize]) لـ + تشغيل الطرق بشكل صحيح.

+ + يضبط خصائص أصل واتجاه هذا الشعاع + من خلال نسخ القيم من الكائنات المحددة. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/math/Sphere.html b/docs/api/ar/math/Sphere.html new file mode 100644 index 00000000000000..8de44b98fbe407 --- /dev/null +++ b/docs/api/ar/math/Sphere.html @@ -0,0 +1,180 @@ + + + + + + + + + +

[name]

+ +

كرة محددة بمركز ونصف قطر.

+ +

المنشئ (Constructor)

+

[name]( [param:Vector3 center], [param:Float radius] )

+

+ [page:Vector3 center] - مركز الكرة. الافتراضي هو [page:Vector3] + عند `(0، 0، 0)`.
+ [page:Float radius] - نصف قطر الكرة. الافتراضي هو -1.

+ + ينشئ [name] جديدًا. +

+ +

الخصائص (Properties)

+ +

[property:Vector3 center]

+

+ [page:Vector3] يحدد مركز الكرة. الافتراضي هو `(0، 0، + 0)`. +

+ +

[property:Boolean isSphere]

+

علامة للقراءة فقط للتحقق مما إذا كان كائنًا معينًا من نوع [name].

+ +

[property:Float radius]

+

نصف قطر الكرة. الافتراضي هو -1.

+ +

الطرق (Methods)

+ +

[method:this applyMatrix4]( [param:Matrix4 matrix] )

+

+ [page:Matrix4 matrix] - [Page:Matrix4] للتطبيق

+ + يحول هذه الكرة بواسطة [page:Matrix4] المحدد. +

+ +

+ [method:Vector3 clampPoint]( [param:Vector3 point], [param:Vector3 target] ) +

+

+ [page:Vector3 point] - [page:Vector3] النقطة التي سيتم تثبيتها.
+ [page:Vector3 target] — سيتم نسخ النتيجة في هذا Vector3.

+ + يثبت نقطة داخل الكرة. إذا كانت النقطة خارج الكرة ، فسوف + يثبتها على أقرب نقطة على حافة الكرة. لن تتأثر النقاط + بالفعل داخل الكرة. +

+ +

[method:Sphere clone]()

+

+ يعود بكرة جديدة بنفس [page:.center center] و [page:.radius radius] كهذه. +

+ +

[method:Boolean containsPoint]( [param:Vector3 point] )

+

+ [page:Vector3 point] - الـ[page:Vector3] المطلوب التحقق منه

+ + يتحقق مما إذا كانت الكرة تحتوي على [page:Vector3 point] المحدد + شاملاً سطح الكرة. +

+ +

[method:this copy]( [param:Sphere sphere] )

+

+ ينسخ قيم خصائص [page:.center center] و [page:.radius radius] للكرة المعطاة إلى هذه الكرة. +

+ +

[method:Float distanceToPoint]( [param:Vector3 point] )

+

+ يعيد أقرب مسافة من حدود الكرة إلى + [page:Vector3 point]. إذا كانت الكرة تحتوي على النقطة ، فستكون المسافة + سلبية. +

+ +

[method:this expandByPoint]( [param:Vector3 point] )

+

+ [page:Vector3 point] - [page:Vector3] التي يجب تضمينها في + الكرة.

+ + يوسع حدود هذه الكرة لتشمل [page:Vector3 point]. +

+ +

[method:Boolean isEmpty]()

+

+ يتحقق مما إذا كانت الكرة فارغة (تم تعيين نصف القطر إلى رقم + سلبي).
+ الكرات التي يبلغ نصف قطرها 0 تحتوي فقط على نقطة مركزها ولا + يعتبر فارغًا. +

+ +

[method:this makeEmpty]()

+

+ يجعل الكرة فارغة عن طريق تعيين [page:.center center] إلى (0، 0، 0) و + [page:.radius radius] إلى -1. +

+ +

[method:Boolean equals]( [param:Sphere sphere] )

+

يتحقق مما إذا كانت مراكز وأشعة الكرتين متساوية.

+ +

[method:Box3 getBoundingBox]( [param:Box3 target] )

+

+ [page:Box3 target] — سيتم نسخ النتيجة في هذا Box3.

+ + يعود بـ[link:https://en.wikipedia.org/wiki/Minimum_bounding_box Minimum Bounding Box] + للكرة. +

+ +

[method:Boolean intersectsBox]( [param:Box3 box] )

+

+ [page:Box3 box] - [page:Box3] للتحقق من التقاطع ضدها.

+ + يحدد ما إذا كانت هذه الكرة تتقاطع مع [page:Box3 box] المحددة أم لا. +

+ +

[method:Boolean intersectsPlane]( [param:Plane plane] )

+

+ [page:Plane plane] - الطائرة للتحقق من التقاطع ضدها.

+ + يحدد ما إذا كانت هذه الكرة تتقاطع مع [page:Plane plane] المحددة أم لا. +

+ +

[method:Boolean intersectsSphere]( [param:Sphere sphere] )

+

+ [page:Sphere sphere] - الكرة للتحقق من التقاطع ضدها.

+ + يتحقق مما إذا كانت كرتين تتقاطعان. +

+ +

[method:this set]( [param:Vector3 center], [param:Float radius] )

+

+ [page:Vector3 center] - مركز الكرة.
+ [page:Float radius] - نصف قطر الكرة.

+ + يضبط خصائص [page:.center center] و [page:.radius radius] + لهذه الكرة.
+ يرجى ملاحظة أن هذه الطريقة تنسخ فقط القيم من المركز المحدد. +

+ +

+ [method:this setFromPoints]( [param:Array points], [param:Vector3 optionalCenter] ) +

+

+ [page:Array points] - مصفوفة من مواضع [page:Vector3].
+ [page:Vector3 optionalCenter] - موضع اختياري لـ[page:Vector3] + مركز الكرة.

+ + يحسب الكرة المحيطة الدنيا لمصفوفة من نقاط [page:Array]. + إذا تم إعطاء [page:Vector3 optionalCenter] ، يتم استخدامه كمركز للكرة + . وإلا ، يتم حساب مركز المستطيل المحيط المستوي + تغليف نقاط [page:Array]. +

+ +

[method:this translate]( [param:Vector3 offset] )

+

ترجمة مركز الكرة بالإزاحة المحددة [page:Vector3].

+ +

[method:this union]( [param:Sphere sphere] )

+

+ [page:Sphere sphere] - كرة المحيط التي ستتحد مع هذه + كرة.

+ + يوسع هذه الكرة لإغلاق كل من الكرة الأصلية والمعطى + كرة. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/math/Spherical.html b/docs/api/ar/math/Spherical.html new file mode 100644 index 00000000000000..cabd9cdec9731a --- /dev/null +++ b/docs/api/ar/math/Spherical.html @@ -0,0 +1,90 @@ + + + + + + + + + +

[name]

+ +

+ إحداثيات [link:https://en.wikipedia.org/wiki/Spherical_coordinate_system الكروية] للنقطة. +

+ +

المنشئ (Constructor)

+ +

+ [name]( [param:Float radius], [param:Float phi], [param:Float theta] ) +

+

+ [page:Float radius] - نصف القطر ، أو + [link:https://en.wikipedia.org/wiki/Euclidean_distance المسافة الإقليدية] + (المسافة الخطية) من النقطة إلى الأصل. الافتراضي هو + `1.0`.
+ [page:Float phi] - الزاوية القطبية بالراديان من المحور y (لأعلى). الافتراضي هو + `0`.
+ [page:Float theta] - زاوية خط الاستواء بالراديان حول المحور y (لأعلى). + الافتراضي هو `0`.

+ + الأقطاب (phi) عند المحور y الموجب والسالب. يبدأ خط الاستواء + (theta) في z الموجب. +

+ +

Properties

+ +

[property:Float radius]

+ +

[property:Float phi]

+ +

[property:Float theta]

+ +

الطرق (Methods)

+ +

[method:Spherical clone]()

+

+ يعود بكرة جديدة بنفس خصائص [page:.radius radius] و [page:.phi phi] + و [page:.theta theta] كهذه. +

+ +

[method:this copy]( [param:Spherical s] )

+

+ ينسخ قيم خصائص [page:.radius radius] و [page:.phi phi] و [page:.theta theta] للكرة المعطاة إلى هذه الكرة. +

+ +

[method:this makeSafe]()

+

+ يقيد الزاوية القطبية [page:.phi phi] لتكون بين 0.000001 و pi - + 0.000001. +

+ +

+ [method:this set]( [param:Float radius], [param:Float phi], [param:Float theta] ) +

+

+ يضبط قيم خصائص [page:.radius radius] و [page:.phi phi] و + [page:.theta theta] لهذه الكرة. +

+ +

[method:this setFromVector3]( [param:Vector3 vec3] )

+

+ يضبط قيم خصائص [page:.radius radius] و [page:.phi phi] و + [page:.theta theta] لهذه الكرة من الـ[page:Vector3 Vector3]. +

+ +

+ [method:this setFromCartesianCoords]( [param:Float x], [param:Float y], [param:Float z] ) +

+

+ يضبط قيم خصائص [page:.radius radius] و [page:.phi phi] و + [page:.theta theta] لهذه الكرة من الإحداثيات الديكارتية. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/math/SphericalHarmonics3.html b/docs/api/ar/math/SphericalHarmonics3.html new file mode 100644 index 00000000000000..e62f45bc711e80 --- /dev/null +++ b/docs/api/ar/math/SphericalHarmonics3.html @@ -0,0 +1,155 @@ + + + + + + + + + +

[name]

+ +

+ يمثل تناغم كروي من الدرجة الثالثة (SH). تستخدم مسابر الضوء هذه الفئة لتشفير معلومات الإضاءة. +

+ +

المنشئ (Constructor)

+

[name]()

+

ينشئ نسخة جديدة من [name].

+ +

الخصائص (Properties)

+ +

[property:Array coefficients]

+

+ مصفوفة تحتوي على (9) معاملات SH. يتم تمثيل معامل واحد كنسخة من [page:Vector3]. +

+ +

[property:Boolean isSphericalHarmonics3]

+

علامة للقراءة فقط للتحقق مما إذا كان الكائن المعطى من نوع [name].

+ +

الطرق (Methods)

+ +

[method:this add]( [param:SphericalHarmonics3 sh] )

+

+ [page:SphericalHarmonics3 sh] - SH المراد إضافته.

+ + يضيف SH المعطى إلى هذه النسخة. +

+ +

+ [method:this addScaledSH]( [param:SphericalHarmonics3 sh], [param:Number scale] ) +

+

+ [page:SphericalHarmonics3 sh] - SH المراد إضافته.
+ [page:Number scale] - عامل التحجيم.

+ + طريقة سهلة لأداء [page:.add]() و [page:.scale]() في آن واحد. +

+ +

[method:SphericalHarmonics3 clone]()

+

يعود بحالة جديدة من [name] بمعاملات متساوية.

+ +

[method:this copy]( [param:SphericalHarmonics3 sh] )

+

+ [page:SphericalHarmonics3 sh] - الـSH للنسخ.

+ + ينسخ الـSH المعطى إلى هذه الحالة. +

+ +

[method:Boolean equals]( [param:SphericalHarmonics3 sh] )

+

+ [page:SphericalHarmonics3 sh] - الـSH للمقارنة معه.

+ + يعود بـtrue إذا كان الـSH المعطى وهذه الحالة لديهما معاملات متساوية. +

+ +

+ [method:this fromArray]( [param:Array array], [param:Number offset] ) +

+

+ [page:Array array] - المصفوفة التي تحمل أرقام معاملات SH + .
+ [page:Number offset] - (اختياري) إزاحة المصفوفة.

+ + يضبط معاملات هذه الحالة من المصفوفة المعطاة. +

+ +

+ [method:Vector3 getAt]( [param:Vector3 normal], [param:Vector3 target] ) +

+

+ [page:Vector3 normal] - متجه الطبيعي (يفترض أن يكون طول الوحدة).
+ [page:Vector3 target] - متجه النتيجة.

+ + يعود بالإشعاع في اتجاه الطبيعي المحدد. +

+ +

+ [method:Vector3 getIrradianceAt]( [param:Vector3 normal], [param:Vector3 target] ) +

+

+ [page:Vector3 normal] - متجه الطبيعي (يفترض أن يكون طول الوحدة).
+ [page:Vector3 target] - متجه النتيجة.

+ + يعود بالإشعاع (الإشعاع الملتف مع فص الجيب) في + اتجاه الطبيعي المحدد. +

+ +

+ [method:this lerp]( [param:SphericalHarmonics3 sh], [param:Number alpha] ) +

+

+ [page:SphericalHarmonics3 sh] - الـSH للتداخل معه.
+ [page:Number alpha] - عامل الألفا.

+ + يتداخل بين الـSH المعطى وهذه الحالة بواسطة + عامل الألفا المعطى. +

+ +

[method:this scale]( [param:Number scale] )

+

+ [page:Number scale] - عامل المقياس.

+ + يقيس هذا الـSH بواسطة عامل المقياس المعطى. +

+ +

[method:this set]( [param:Array coefficients] )

+

+ [page:Array coefficients] - مصفوفة من معاملات SH.

+ + يضبط معاملات SH المعطاة لهذه الحالة. +

+ +

+ [method:Array toArray]( [param:Array array], [param:Number offset] ) +

+

+ [page:Array array] - (اختياري) المصفوفة الهدف.
+ [page:Number offset] - (اختياري) إزاحة المصفوفة.

+ + يعود بمصفوفة بالمعاملات ، أو ينسخهم في المصفوفة المحددة + صفيف. يتم تمثيل المعاملات كأرقام. +

+ +

[method:this zero]()

+

يضبط جميع معاملات SH على 0.

+ +

طرق ثابتة (Static Methods)

+ +

+ [method:undefined getBasisAt]( [param:Vector3 normal], [param:Array shBasis] ) +

+

+ [page:Vector3 normal] - متجه الطبيعي (يفترض أن يكون طول الوحدة).
+ [page:Array shBasis] - الأساس SH الناتج.

+ + يحسب أساس SH لمتجه الطبيعي المحدد. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/math/Triangle.html b/docs/api/ar/math/Triangle.html new file mode 100644 index 00000000000000..faf321b6209ccd --- /dev/null +++ b/docs/api/ar/math/Triangle.html @@ -0,0 +1,184 @@ + + + + + + + + + +

[name]

+ +

+ مثلث هندسي كما هو محدد بثلاثة [page:Vector3 Vector3s] تمثل زواياه الثلاثة. +

+ +

المنشئ (Constructor)

+ +

[name]( [param:Vector3 a], [param:Vector3 b], [param:Vector3 c] )

+

+ [page:Vector3 a] - الزاوية الأولى للمثلث. الافتراضي هو [page:Vector3] في `(0، 0، 0)`.
+ [page:Vector3 b] - الزاوية الثانية للمثلث. الافتراضي هو [page:Vector3] في `(0، 0، 0)`.
+ [page:Vector3 c] - الزاوية النهائية للمثلث. الافتراضي هو [page:Vector3] في `(0، 0، 0)`.

+ + ينشئ نسخة جديدة من [name]. +

+ +

الخصائص (Properties)

+ +

[property:Vector3 a]

+

+ الزاوية الأولى للمثلث. الافتراضي هو [page:Vector3] في `(0، 0، 0)`. +

+ +

[property:Vector3 b]

+

+ الزاوية الثانية للمثلث. الافتراضي هو [page:Vector3] في `(0، 0، 0)`. +

+ +

[property:Vector3 c]

+

+ الزاوية النهائية للمثلث. الافتراضي هو [page:Vector3] في `(0، 0، 0)`. +

+ +

الطرق (Methods)

+ +

[method:Triangle clone]()

+

+ يعود بمثلث جديد بنفس خصائص [page:.a a]، [page:.b b] و [page:.c c] كهذا. +

+ +

+ [method:Vector3 closestPointToPoint]( [param:Vector3 point], [param:Vector3 target] ) +

+

+ [page:Vector3 point] - [page:Vector3]
+ [page:Vector3 target] - سيتم نسخ النتيجة في هذا Vector3.

+ + يعود بأقرب نقطة على المثلث إلى [page:Vector3 point]. +

+ +

[method:Boolean containsPoint]( [param:Vector3 point] )

+

+ [page:Vector3 point] - [page:Vector3] للتحقق.

+ + يعود بـ true إذا كانت النقطة الممررة، عندما يتم إسقاطها على مستوى المثلث، تقع داخل المثلث. +

+ +

[method:this copy]( [param:Triangle triangle] )

+

+ ينسخ قيم خصائص [page:.a a]، [page:.b b] و [page:.c c] للمثلث الممرر إلى هذا المثلث. +

+ +

[method:Boolean equals]( [param:Triangle triangle] )

+

+ يعود بـ true إذا كانت المثلثين لديهما خصائص متطابقة من [page:.a a]، [page:.b b] + و [page:.c c]. +

+ +

[method:Float getArea]()

+

يرجع مساحة المثلث.

+ +

+ [method:Vector3 getBarycoord]( [param:Vector3 point], [param:Vector3 target] ) +

+

+ [page:Vector3 point] - [page:Vector3]
+ [page:Vector3 target] - سيتم نسخ النتيجة في هذا Vector3.

+ + يرجع إحداثية ثلاثية من المتجه المعطى.

+ + [link:http://commons.wikimedia.org/wiki/File:Barycentric_coordinates_1.png صورة لإحداثيات ثلاثية] +

+ +

[method:Vector3 getMidpoint]( [param:Vector3 target] )

+

+ [page:Vector3 target] - سيتم نسخ النتيجة في هذا Vector3.

+ + حساب نقطة منتصف المثلث. +

+ +

[method:Vector3 getNormal]( [param:Vector3 target] )

+

+ [page:Vector3 target] - سيتم نسخ النتيجة في هذا Vector3.

+ + حساب [link:https://en.wikipedia.org/wiki/Normal_(geometry) متجه العادي] للمثلث. +

+ +

[method:Plane getPlane]( [param:Plane target] )

+

+ [page:Plane target] - سيتم نسخ النتيجة في هذا Plane.

+ + حساب [page:Plane plane] بناءً على المثلث. . +

+ +

+ [method:Vector getInterpolation]( [param:Vector3 point], [param:Vector3 p1], [param:Vector3 p2], [param:Vector3 p3], [param:Vector v1], [param:Vector v2], [param:Vector v3], [param:Vector target] ) +

+

+ [page:Vector3 point] - موقع النقطة المُركبة.
+ [page:Vector3 p1] - موقع الرأس الأول.
+ [page:Vector3 p2] - موقع الرأس الثاني.
+ [page:Vector3 p3] - موقع الرأس الثالث.
+ [page:Vector v1] - قيمة الرأس الأولى.
+ [page:Vector v2] - قيمة الرأس الثانية.
+ [page:Vector v3] - قيمة الرأس الثالثة.
+ [page:Vector target] — سيتم نسخ النتيجة في هذا Vector.

+ + تعود بالقيمة المُركبة بشكل ثلاثي للنقطة المعطاة على المثلث. +

+ +

[method:Boolean intersectsBox]( [param:Box3 box] )

+

+ [page:Box3 box] - مربع للتحقق من التقاطع ضده.

+ + يحدد ما إذا كان هذا المثلث يتقاطع مع [page:Box3 box] أم لا. +

+ +

[method:Boolean isFrontFacing]( [param:Vector3 direction] )

+

+ [page:Vector3 direction] - الاتجاه الذي يتم اختباره.

+ + ما إذا كان المثلث موجهًا نحو الاتجاه المعطى أم لا. +

+ +

+ [method:this set]( [param:Vector3 a], [param:Vector3 b], [param:Vector3 c] ) +

+

+ يضبط خصائص المثلث [page:.a a]، [page:.b b] و [page:.c c] على [page:Vector3 vector3s] الممررة.
+ يرجى ملاحظة أن هذه الطريقة تنسخ فقط القيم من الكائنات المعطاة. +

+ +

+ [method:this setFromAttributeAndIndices]( [param:BufferAttribute attribute], + [param:Integer i0], [param:Integer i1], [param:Integer i2] ) +

+

+ attribute - [page:BufferAttribute] من بيانات الرأس
+ i0 - فهرس [page:Integer]
+ i1 - فهرس [page:Integer]
+ i2 - فهرس [page:Integer]

+ + يضبط رؤوس المثلث من بيانات رأس سمة الحافظة. +

+ +

+ [method:this setFromPointsAndIndices]( [param:Array points], [param:Integer i0], [param:Integer i1], [param:Integer i2] ) +

+

+ points - مصفوفة من [page:Vector3]s
+ i0 - فهرس [page:Integer]
+ i1 - فهرس [page:Integer]
+ i2 - فهرس [page:Integer]

+ + يضبط متجهات المثلث على المتجهات في المصفوفة. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/math/Vector2.html b/docs/api/ar/math/Vector2.html new file mode 100644 index 00000000000000..9893bb26c5c66b --- /dev/null +++ b/docs/api/ar/math/Vector2.html @@ -0,0 +1,353 @@ + + + + + + + + + +

[name]

+ +

+ فئة تمثل متجه 2D [link:https://en.wikipedia.org/wiki/Vector_space]. متجه 2D هو زوج مرتب من الأرقام (مسمى x و y)، والذي يمكن استخدامه لتمثيل عدد من الأشياء، مثل: +

+ +
    +
  • نقطة في الفضاء 2D (أي موضع على مستوى).
  • +
  • + اتجاه وطول عبر مستوى. في three.js سيكون الطول دائمًا [link:https://en.wikipedia.org/wiki/Euclidean_distance الطول الإقليدي] (طول الخط المستقيم) من `(0، 0)` إلى `(x، y)` ويتم قياس الاتجاه أيضًا من `(0، 0)` نحو `(x، y)`. +
  • +
  • أي زوج مرتب عشوائي من الأرقام.
  • +
+ +

+ هناك أشياء أخرى يمكن استخدام متجه 2D لتمثيلها، مثل المتجهات الزخمية، والأعداد المركبة وما إلى ذلك، ومع ذلك فإن هذه هي الاستخدامات الأكثر شيوعًا في three.js. +

+ +

+ التكرار عبر نسخة [name] سيعود بمكوناته `(x، y)` بالترتيب المقابل. +

+ +

مثال للكود

+ + + const a = new THREE.Vector2( 0, 1 ); + + //no arguments; will be initialised to (0, 0) + const b = new THREE.Vector2( ); + + const d = a.distanceTo( b ); + + +

المنشئ (Constructor)

+ +

[name]( [param:Float x], [param:Float y] )

+

+ [page:Float x] - قيمة x لهذا المتجه. الافتراضي هو `0`.
+ [page:Float y] - قيمة y لهذا المتجه. الافتراضي هو `0`.

+ + ينشئ نسخة جديدة من [name]. +

+ +

الخصائص (Properties)

+ +

[property:Float height]

+

اسم آخر لـ [page:.y y].

+ +

[property:Boolean isVector2]

+

علامة للقراءة فقط للتحقق مما إذا كان الكائن المعطى من نوع [name].

+ +

[property:Float width]

+

اسم آخر لـ [page:.x x].

+ +

[property:Float x]

+ +

[property:Float y]

+ +

الطرق (Methods)

+ +

[method:this add]( [param:Vector2 v] )

+

يضيف [page:Vector2 v] إلى هذا المتجه.

+ +

[method:this addScalar]( [param:Float s] )

+

+ يضيف القيمة القياسية [page:Float s] إلى قيم [page:.x x] و [page:.y y] لهذا المتجه. +

+ +

[method:this addScaledVector]( [param:Vector2 v], [param:Float s] )

+

+ يضيف الضعف من [page:Vector2 v] و [page:Float s] إلى هذا المتجه. +

+ +

[method:this addVectors]( [param:Vector2 a], [param:Vector2 b] )

+

يضبط هذا المتجه على [page:Vector2 a] + [page:Vector2 b].

+ +

[method:Float angle]()

+

+ يحسب الزاوية بالراديان لهذا المتجه بالنسبة للمحور x الموجب. +

+ +

[method:Float angleTo]( [param:Vector2 v] )

+

+ يعود بالزاوية بين هذا المتجه والمتجه [page:Vector2 v] بالراديان. +

+ +

[method:this applyMatrix3]( [param:Matrix3 m] )

+

+ يضرب هذا المتجه (مع 1 ضمني كمكون الثالث) في m. +

+ +

[method:this ceil]()

+

+ تتم جولة مكونات [page:.x x] و [page:.y y] لهذا المتجه إلى أعلى إلى أقرب قيمة صحيحة. +

+ +

[method:this clamp]( [param:Vector2 min], [param:Vector2 max] )

+

+ [page:Vector2 min] - الحد الأدنى لقيم x و y.
+ [page:Vector2 max] - الحد الأقصى لقيم x و y في النطاق المطلوب

+ + إذا كانت قيمة x أو y لهذا المتجه أكبر من قيمة x أو y للمتجه max، يتم استبدالها بالقيمة المقابلة.

+ إذا كانت قيمة x أو y لهذا المتجه أقل من قيمة x أو y للمتجه min، يتم استبدالها بالقيمة المقابلة. +

+ +

[method:this clampLength]( [param:Float min], [param:Float max] )

+

+ [page:Float min] - الحد الأدنى الذي سيتم تثبيت الطول عليه
+ [page:Float max] - الحد الأقصى الذي سيتم تثبيت الطول عليه

+ + إذا كان طول هذا المتجه أكبر من قيمة max، يتم استبداله بالقيمة max.

+ إذا كان طول هذا المتجه أقل من قيمة min، يتم استبداله بالقيمة min. +

+ +

[method:this clampScalar]( [param:Float min], [param:Float max] )

+

+ [page:Float min] - الحد الأدنى الذي ستُثبَّت عليه العناصر +
+ [page:Float max] - الحد الأقصى الذي سُتثبَّت عليه العناصر

+ + إذا كانت قيم x أو y لهذا المتجه أكبر من قيمة max، يتم استبدالها بالقيمة max.

+ إذا كانت قيم x أو y لهذا المتجه أقل من قيمة min، يتم استبدالها بالقيمة min. +

+ +

[method:Vector2 clone]()

+

+ يعود بـ Vector2 جديد بنفس قيم [page:.x x] و [page:.y y] كهذا. +

+ +

[method:this copy]( [param:Vector2 v] )

+

+ ينسخ قيم خصائص [page:.x x] و [page:.y y] لـ Vector2 الممرر إلى هذا Vector2. +

+ +

[method:Float distanceTo]( [param:Vector2 v] )

+

يحسب المسافة من هذا المتجه إلى [page:Vector2 v].

+ +

[method:Float manhattanDistanceTo]( [param:Vector2 v] )

+

+ يحسب [link:https://en.wikipedia.org/wiki/Taxicab_geometry مسافة مانهاتن] من هذا المتجه إلى [page:Vector2 v]. +

+ +

[method:Float distanceToSquared]( [param:Vector2 v] )

+

+ يحسب المسافة المربعة من هذا المتجه إلى [page:Vector2 v]. إذا كنت تقارن فقط المسافة مع مسافة أخرى، يجب عليك مقارنة المسافة المربعة بدلاً من ذلك لأنه أكثر كفاءة قليلاً في الحساب. +

+ +

[method:this divide]( [param:Vector2 v] )

+

يقسم هذا المتجه على [page:Vector2 v].

+ +

[method:this divideScalar]( [param:Float s] )

+

يقسم هذا المتجه على العدد القياسي [page:Float s].

+ +

[method:Float dot]( [param:Vector2 v] )

+

+ يحسب [link:https://en.wikipedia.org/wiki/Dot_product حاصل الضرب النقطي] لهذا المتجه و [page:Vector2 v]. +

+ +

[method:Float cross]( [param:Vector2 v] )

+

+ يحسب [link:https://en.wikipedia.org/wiki/Cross_product حاصل الضرب المتقاطع] لهذا المتجه و [page:Vector2 v]. يرجى ملاحظة أن "حاصل الضرب المتقاطع" في 2D غير محدد بشكل جيد. تحسب هذه الوظيفة حاصل ضرب متقاطع هندسي يستخدم في الغالب في رسومات 2D +

+ +

[method:Boolean equals]( [param:Vector2 v] )

+

+ يعود بـ `true` إذا كانت مكونات هذا المتجه و [page:Vector2 v] متطابقة بدقة؛ `false` في حالة عدم ذلك. +

+ +

[method:this floor]()

+

+ تتم جولة مكونات هذا المتجه إلى أسفل إلى أقرب قيمة صحيحة. +

+ +

+ [method:this fromArray]( [param:Array array], [param:Integer offset] ) +

+

+ [page:Array array] - المصفوفة المصدر.
+ [page:Integer offset] - (اختياري) إزاحة في المصفوفة. الافتراضي هو 0.

+ + يضبط قيمة [page:.x x] لهذا المتجه على `array[ offset ]` وقيمة [page:.y y] على `array[ offset + 1 ]`. +

+ +

+ [method:this fromBufferAttribute]( [param:BufferAttribute attribute], [param:Integer index] ) +

+

+ [page:BufferAttribute attribute] - السمة المصدر.
+ [page:Integer index] - فهرس في السمة.

+ + يضبط قيم [page:.x x] و [page:.y y] لهذا المتجه من [page:BufferAttribute attribute]. +

+ +

[method:Float getComponent]( [param:Integer index] )

+

+ [page:Integer index] - 0 أو 1.

+ + إذا كان index يساوي 0 يعود بقيمة [page:.x x].
+ إذا كان index يساوي 1 يعود بقيمة [page:.y y]. +

+ +

[method:Float length]()

+

+ يحسب [link:https://en.wikipedia.org/wiki/Euclidean_distance الطول الإقليدي] (طول الخط المستقيم) من (0، 0) إلى (x، y). +

+ +

[method:Float manhattanLength]()

+

+ يحسب [link:http://en.wikipedia.org/wiki/Taxicab_geometry طول مانهاتن] لهذا المتجه. +

+ +

[method:Float lengthSq]()

+

+ يحسب مربع [link:https://en.wikipedia.org/wiki/Euclidean_distance الطول الإقليدي] (طول الخط المستقيم) من (0، 0) إلى (x، y). إذا كنت تقارن أطوال المتجهات، يجب عليك مقارنة الطول المربع بدلاً من ذلك لأنه أكثر كفاءة قليلاً في الحساب. +

+ +

[method:this lerp]( [param:Vector2 v], [param:Float alpha] )

+

+ [page:Vector2 v] - [page:Vector2] للتداخل نحوه.
+ [page:Float alpha] - عامل التداخل، عادة في الفترة المغلقة `[0، 1]`.

+ + يتداخل بشكل خطي بين هذا المتجه و [page:Vector2 v]، حيث alpha هو نسبة المسافة على طول الخط - سيكون alpha = 0 هذا المتجه، وسيكون alpha = 1 [page:Vector2 v]. +

+ +

+ [method:this lerpVectors]( [param:Vector2 v1], [param:Vector2 v2], [param:Float alpha] ) +

+

+ [page:Vector2 v1] - [page:Vector2] البداية.
+ [page:Vector2 v2] - [page:Vector2] للتداخل نحوه.
+ [page:Float alpha] - عامل التداخل، عادة في الفترة المغلقة `[0، 1]`.

+ + يضبط هذا المتجه ليكون المتجه المُركب بشكل خطي بين [page:Vector2 v1] و [page:Vector2 v2] حيث alpha هو نسبة المسافة على طول الخط الذي يربط بين المتجهين - سيكون alpha = 0 [page:Vector2 v1]، وسيكون alpha = 1 [page:Vector2 v2]. +

+ +

[method:this negate]()

+

يعكس هذا المتجه - أي يضع x = -x و y = -y.

+ +

[method:this normalize]()

+

+ يحول هذا المتجه إلى [link:https://en.wikipedia.org/wiki/Unit_vector متجه وحدة] - + أي يضعه مساوٍ لمتجه بنفس الاتجاه + كهذا، ولكن [page:.length length] 1. +

+ +

[method:this max]( [param:Vector2 v] )

+

+ إذا كانت قيمة x أو y لهذا المتجه أقل من قيمة x أو y لـ [page:Vector2 v]، استبدل تلك القيمة بالقيمة العظمى المقابلة. +

+ +

[method:this min]( [param:Vector2 v] )

+

+ إذا كانت قيمة x أو y لهذا المتجه أكبر من قيمة x أو y لـ [page:Vector2 v]، استبدل تلك القيمة بالقيمة الدنيا المقابلة. +

+ +

[method:this multiply]( [param:Vector2 v] )

+

يضرب هذا المتجه في [page:Vector2 v].

+ +

[method:this multiplyScalar]( [param:Float s] )

+

يضرب هذا المتجه في العدد القياسي [page:Float s].

+ +

+ [method:this rotateAround]( [param:Vector2 center], [param:Float angle] ) +

+

+ [page:Vector2 center] - النقطة التي يتم التدوير حولها.
+ [page:Float angle] - الزاوية التي يتم التدوير بها، بالراديان.

+ + يدور هذا المتجه حول [page:Vector2 center] بـ [page:Float angle] راديان. +

+ +

[method:this round]()

+

+ تتم جولة مكونات هذا المتجه إلى أقرب قيمة صحيحة. +

+ +

[method:this roundToZero]()

+

+ تتم جولة مكونات هذا المتجه نحو الصفر (لأعلى إذا كان سالبًا، لأسفل إذا كان موجبًا) إلى قيمة صحيحة. +

+ +

[method:this set]( [param:Float x], [param:Float y] )

+

يضبط مكونات [page:.x x] و [page:.y y] لهذا المتجه.

+ +

+ [method:this setComponent]( [param:Integer index], [param:Float value] ) +

+

+ [page:Integer index] - 0 أو 1.
+ [page:Float value] - [page:Float]

+ + إذا كان index يساوي 0 ضع [page:.x x] على [page:Float value].
+ إذا كان index يساوي 1 ضع [page:.y y] على [page:Float value] +

+ +

[method:this setLength]( [param:Float l] )

+

+ يضبط هذا المتجه على متجه بنفس الاتجاه كهذا، ولكن [page:.length length] [page:Float l]. +

+ +

[method:this setScalar]( [param:Float scalar] )

+

+ يضبط قيم [page:.x x] و [page:.y y] لهذا المتجه على حد سواء مساوية لـ [page:Float scalar]. +

+ +

[method:this setX]( [param:Float x] )

+

يستبدل قيمة [page:.x x] لهذا المتجه بـ [page:Float x].

+ +

[method:this setY]( [param:Float y] )

+

يستبدل قيمة [page:.y y] لهذا المتجه بـ [page:Float y].

+ +

[method:this sub]( [param:Vector2 v] )

+

يطرح [page:Vector2 v] من هذا المتجه.

+ +

[method:this subScalar]( [param:Float s] )

+

+ يطرح [page:Float s] من مكونات [page:.x x] و [page:.y y] لهذا المتجه. +

+ +

[method:this subVectors]( [param:Vector2 a], [param:Vector2 b] )

+

يضبط هذا المتجه على [page:Vector2 a] - [page:Vector2 b].

+ +

+ [method:Array toArray]( [param:Array array], [param:Integer offset] ) +

+

+ [page:Array array] - (اختياري) مصفوفة لتخزين هذا المتجه فيها. إذا لم يتم توفير هذا، سيتم إنشاء مصفوفة جديدة.
+ [page:Integer offset] - (اختياري) إزاحة اختيارية في المصفوفة.

+ + يرجع مصفوفة [x، y]، أو ينسخ x و y في المصفوفة المعطاة من نوع [page:Array array]. +

+ +

[method:this random]()

+

+ يضبط كل مكون من مكونات هذا المتجه على قيمة شبه عشوائية بين 0 و 1، باستثناء 1. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/math/Vector3.html b/docs/api/ar/math/Vector3.html new file mode 100644 index 00000000000000..486c1fa3fd8287 --- /dev/null +++ b/docs/api/ar/math/Vector3.html @@ -0,0 +1,523 @@ + + + + + + + + + +

[name]

+ +

+ فئة تمثل [link:https://en.wikipedia.org/wiki/Vector_space متجه] ثلاثي الأبعاد. + متجه ثلاثي الأبعاد هو ثلاثي مرتب من الأرقام (مسمى x و y و z) ، والذي يمكن استخدامه لتمثيل عدد من الأشياء ، مثل: +

+ +
    +
  • نقطة في الفضاء ثلاثي الأبعاد.
  • +
  • + اتجاه وطول في الفضاء ثلاثي الأبعاد. في three.js سيكون الطول دائمًا + [link:https://en.wikipedia.org/wiki/Euclidean_distance المسافة الإقليدية] + (المسافة المستقيمة) من `(0, 0, 0)` إلى `(x, y, z)` و + يتم قياس الاتجاه أيضًا من `(0, 0, 0)` نحو `(x, y, z)`. +
  • +
  • أي ثلاثي مرتب عشوائي من الأرقام.
  • +
+ +

+ هناك أشياء أخرى يمكن استخدام متجه ثلاثي الأبعاد لتمثيلها ، مثل + متجهات الزخم وما إلى ذلك ، ومع ذلك فإن هذه هي الاستخدامات الأكثر شيوعًا في + three.js. +

+ +

+ التكرار عبر مثيل [name] سوف ينتج عنه مكوناته `(x, y, z)` + بالترتيب المقابل. +

+ +

مثال للكود

+ + const a = new THREE.Vector3( 0, 1, 0 ); + + //no arguments; will be initialised to (0, 0, 0) + const b = new THREE.Vector3( ); + + const d = a.distanceTo( b ); + + +

المنشئ (Constructor)

+ +

[name]( [param:Float x], [param:Float y], [param:Float z] )

+

+ [page:Float x] - قيمة x لهذا الفيكتور. القيمة الافتراضية هي `0`.
+ [page:Float y] - قيمة y لهذا الفيكتور. القيمة الافتراضية هي `0`.
+ [page:Float z] - قيمة z لهذا الفيكتور. القيمة الافتراضية هي `0`.

+ + ينشئ [name] جديد. +

+ +

الخصائص (Properties)

+ +

[property:Boolean isVector3]

+

علامة للتحقق مما إذا كان الكائن المعطى من نوع [name] أم لا.

+ +

[property:Float x]

+ +

[property:Float y]

+ +

[property:Float z]

+ +

الوظائف (Methods)

+ +

[method:this add]( [param:Vector3 v] )

+

يضيف [page:Vector3 v] إلى هذا الفيكتور.

+ +

[method:this addScalar]( [param:Float s] )

+

+ يضيف قيمة العدد s إلى قيم [page:.x x]، [page:.y y] و [page:.z z] لهذا الفيكتور. +

+ +

[method:this addScaledVector]( [param:Vector3 v], [param:Float s] )

+

+ يضيف الضرب المتعدد لـ [page:Vector3 v] و [page:Float s] إلى هذا الفيكتور. +

+ +

[method:this addVectors]( [param:Vector3 a], [param:Vector3 b] )

+

يعيد هذا الفيكتور إلى [page:Vector3 a] + [page:Vector3 b].

+ +

+ [method:this applyAxisAngle]( [param:Vector3 axis], [param:Float angle] ) +

+

+ [page:Vector3 axis] - محور [page:Vector3] موحّد.
+ [page:Float angle] - زاوية بالراديان.

+ + يطبق تحويلًا يُحدد بواسطة محور وزاوية على هذا الفيكتور. +

+ +

[method:this applyEuler]( [param:Euler euler] )

+

+ يطبق تحويل أويلر على هذا الفيكتور من خلال تحويل الكائن [page:Euler] + إلى [page:Quaternion] وتطبيقه. +

+ +

[method:this applyMatrix3]( [param:Matrix3 m] )

+

يضرب هذا الفيكتور بواسطة [page:Matrix3 m].

+ +

[method:this applyMatrix4]( [param:Matrix4 m] )

+

+ يضرب هذا الفيكتور (مع وجود 1 ضمن الأبعاد الرابعة) بـ m، ويقسمه على + التوازن. +

+ +

[method:this applyNormalMatrix]( [param:Matrix3 m] )

+

+ يضرب هذا الفيكتور بواسطة مصفوفة القوامة العادية [page:Matrix3 m] ويقوم + بتطبيع النتيجة. +

+ +

[method:this applyQuaternion]( [param:Quaternion quaternion] )

+

يطبق تحويل [page:Quaternion] على هذا الفيكتور.

+ +

[method:Float angleTo]( [param:Vector3 v] )

+

+ يعيد الزاوية بين هذا الفيكتور وفيكتور [page:Vector3 v] بالراديان. +

+ +

[method:this ceil]()

+

+ يقوم بتقريب مكونات [page:.x x]، [page:.y y] و [page:.z z] لهذا الفيكتور إلى + أقرب قيمة صحيحة. +

+ +

[method:this clamp]( [param:Vector3 min], [param:Vector3 max] )

+

+ [page:Vector3 min] - القيم [page:.x x]، [page:.y y] و [page:.z z] الدنيا.
+ [page:Vector3 max] - القيم [page:.x x]، [page:.y y] و [page:.z z] العليا في النطاق المطلوب.

+ + إذا كانت قيم x أو y أو z لهذا الفيكتور أكبر من قيم x أو y أو z العليا للفيكتور، يتم استبدالها بالقيم المقابلة.

+ إذا كانت قيم x أو y أو z لهذا الفيكتور أقل من قيم x أو y أو z الدنيا للفيكتور، يتم استبدالها بالقيم المقابلة. +

+ + +

[method:this clampLength]( [param:Float min], [param:Float max] )

+

+ [page:Float min] - القيمة الدنيا التي ستتم تقييدها للطول
+ [page:Float max] - القيمة العليا التي ستتم تقييدها للطول

+ + إذا كان طول هذا الفيكتور أكبر من القيمة العليا، سيتم تصغير الفيكتور بحيث يصبح طوله هو القيمة العليا.

+ إذا كان طول هذا الفيكتور أقل من القيمة الدنيا، سيتم تكبير الفيكتور بحيث يصبح طوله هو القيمة الدنيا. +

+ +

[method:this clampScalar]( [param:Float min], [param:Float max] )

+

+ [page:Float min] - القيمة الدنيا التي ستتم تقييدها للمكونات +
+ [page:Float max] - القيمة العليا التي ستتم تقييدها للمكونات

+ + إذا كانت قيم x أو y أو z لهذا الفيكتور أكبر من القيمة العليا، سيتم استبدالها بالقيمة العليا.

+ إذا كانت قيم x أو y أو z لهذا الفيكتور أقل من القيمة الدنيا، سيتم استبدالها بالقيمة الدنيا. +

+ +

[method:Vector3 clone]()

+

+ يعيد فيكتور3 جديدًا بنفس القيم x، y، و z كهذا الفيكتور. +

+ +

[method:this copy]( [param:Vector3 v] )

+

+ ينسخ قيم خصائص x، y، و z لفيكتور3 الممرر إلى هذا الفيكتور. +

+ +

[method:this cross]( [param:Vector3 v] )

+

+ يضبط هذا الفيكتور إلى ناتج الضرب النقطي بينه وبين Vector3 v. +

+ +

[method:this crossVectors]( [param:Vector3 a], [param:Vector3 b] )

+

+ يضبط هذا الفيكتور إلى ناتج الضرب النقطي بين Vector3 a و Vector3 b. +

+ +

[method:Float distanceTo]( [param:Vector3 v] )

+

يحسب المسافة من هذا الفيكتور إلى Vector3 v.

+ +

[method:Float manhattanDistanceTo]( [param:Vector3 v] )

+

+ يحسب [link:https://en.wikipedia.org/wiki/Taxicab_geometry Manhattan distance] + من هذا المتجه إلى [page:Vector3 v]. +

+ +

[method:Float distanceToSquared]( [param:Vector3 v] )

+

+ يحسب المسافة المربعة من هذا المتجه إلى [page:Vector3 v]. إذا كنت + تقارن المسافة مع مسافة أخرى، يجب عليك مقارنة + المسافة المربعة بدلاً من ذلك كما هو أكثر كفاءة قليلاً في + الحساب. +

+ +

[method:this divide]( [param:Vector3 v] )

+

يقسم هذا المتجه بواسطة [page:Vector3 v].

+ +

[method:this divideScalar]( [param:Float s] )

+

يقسم هذا المتجه بواسطة العدد الفردي [page:Float s].

+ +

[method:Float dot]( [param:Vector3 v] )

+

+ حساب [link:https://en.wikipedia.org/wiki/Dot_product dot product] + لهذا المتجه و[page:Vector3 v]. +

+ +

[method:Boolean equals]( [param:Vector3 v] )

+

+ يعود `true` إذا كانت مكونات هذا المتجه و[page:Vector3 v] هي + صارمة متساوية؛ `false` في حالات أخرى. +

+ +

[method:this floor]()

+

+ يتم تقريب مكونات هذا المتجه إلى أسفل إلى أقرب قيمة صحيحة + قيمة. +

+ +

+ [method:this fromArray]( [param:Array array], [param:Integer offset] ) +

+

+ [page:Array array] - المصفوفة المصدر.
+ [page:Integer offset] - ( اختياري) الإزاحة في المصفوفة. الافتراضي هو + 0.

+ + يضع قيمة هذا المتجه [page:.x x] لتكون `array[ offset + 0 ]`، قيمة [page:.y y] + لتكون `array[ offset + 1 ]` وقيمة [page:.z z] لتكون `array[ offset + 2 ]`. +

+ +

+ [method:this fromBufferAttribute]( [param:BufferAttribute attribute], [param:Integer index] ) +

+

+ [page:BufferAttribute attribute] - السمة المصدر.
+ [page:Integer index] - الفهرس في السمة.

+ + يضع قيم هذا المتجه [page:.x x]، [page:.y y] و [page:.z z] من + ال[page:BufferAttribute attribute]. +

+ +

[method:Float getComponent]( [param:Integer index] )

+

+ [page:Integer index] - 0، 1 أو 2.

+ + إذا كان الفهرس يساوي 0 يعيد قيمة [page:.x x].
+ إذا كان الفهرس يساوي 1 يعيد قيمة [page:.y y].
+ إذا كان الفهرس يساوي 2 يعيد قيمة [page:.z z]. +

+ +

[method:Float length]()

+

+ يحسب [link:https://en.wikipedia.org/wiki/Euclidean_distance الطول الإقليدي] + (طول الخط المستقيم) من (0، 0، 0) إلى (x، y، z). +

+ +

[method:Float manhattanLength]()

+

+ يحسب طول مانهاتن + لهذا المتجه. +

+ +

[method:Float lengthSq]()

+

+ يحسب مربع + [link:https://en.wikipedia.org/wiki/Euclidean_distance الطول الإقليدي] + (طول الخط المستقيم) من (0، 0، 0) إلى (x، y، z). إذا كنت تقارن + أطوال المتجهات، يجب عليك مقارنة طول المربع بدلاً من ذلك كما + أنه أكثر كفاءة قليلاً في الحساب. +

+ +

[method:this lerp]( [param:Vector3 v], [param:Float alpha] )

+

+ [page:Vector3 v] - [page:Vector3] للتداخل نحوه.
+ [page:Float alpha] - عامل التداخل، عادة في المغلق + الفاصل `[0, 1]`.

+ + تداخل خطي بين هذا المتجه و[page:Vector3 v]، حيث alpha + هو نسبة المسافة على طول الخط - alpha = 0 سيكون هذا المتجه، + وalpha = 1 ستكون [page:Vector3 v]. +

+ +

+ [method:this lerpVectors]( [param:Vector3 v1], [param:Vector3 v2], [param:Float alpha] ) +

+

+ [page:Vector3 v1] - البداية [page:Vector3].
+ [page:Vector3 v2] - [page:Vector3] للتداخل نحوه.
+ [page:Float alpha] - عامل التداخل، عادة في الفاصل المغلق + `[0، 1]`.

+ + يضع هذا المتجه ليكون المتجه المتداخل خطيًا بين + [page:Vector3 v1] و [page:Vector3 v2] حيث يكون ألفا هو النسبة + المئوية للمسافة على طول الخط الذي يربط المتجهين - سيكون ألفا = 0 + [page:Vector3 v1]، وسيكون ألفا = 1 [page:Vector3 v2]. +

+ +

[method:this max]( [param:Vector3 v] )

+

+ إذا كانت قيمة x أو y أو z لهذا المتجه أقل من قيمة x أو y أو z + لـ [page:Vector3 v]، استبدل تلك القيمة بالقيمة القصوى المقابلة. +

+ +

[method:this min]( [param:Vector3 v] )

+

+ إذا كانت قيمة x أو y أو z لهذا المتجه أكبر من قيمة x أو y + أو z لـ [page:Vector3 v]، استبدل تلك القيمة بالقيمة الدنيا المقابلة. +

+ +

[method:this multiply]( [param:Vector3 v] )

+

يضرب هذا المتجه بـ [page:Vector3 v].

+ +

[method:this multiplyScalar]( [param:Float s] )

+

يضرب هذا المتجه بالعدد القائم [page:Float s].

+ +

+ [method:this multiplyVectors]( [param:Vector3 a], [param:Vector3 b] ) +

+

+ يضع هذا المتجه يساوي [page:Vector3 a] * [page:Vector3 b]، + مكونًا حكيمًا. +

+ +

[method:this negate]()

+

يعكس هذا المتجه - أي يضع x = -x، y = -y و z = -z.

+ +

[method:this normalize]()

+

+ تحويل هذا المتجه إلى متجه وحدة + - أي يضعه يساوي متجه بنفس الاتجاه + كهذا واحد، ولكن طول الصفحة 1. +

+ +

[method:this project]( [param:Camera camera] )

+

+ [page:Camera camera] — الكاميرا المستخدمة في الإسقاط.

+ + يسقط هذا المتجه من مساحة العالم إلى مساحة جهاز الكاميرا المعيارية + (NDC). +

+ +

[method:this projectOnPlane]( [param:Vector3 planeNormal] )

+

+ [page:Vector3 planeNormal] - متجه يمثل عادي الطائرة.

+ + [link:https://en.wikipedia.org/wiki/Vector_projection يسقط] هذا + المتجه على طائرة عن طريق طرح هذا المتجه المسقط على عادي الطائرة من هذا + المتجه. +

+ +

[method:this projectOnVector]( [param:Vector3 v] )

+

+ [link:https://en.wikipedia.org/wiki/Vector_projection يسقط] هذا + المتجه على [page:Vector3 v]. +

+ +

[method:this reflect]( [param:Vector3 normal] )

+

+ [page:Vector3 normal] - العادي إلى طائرة الانعكاس

+ + يعكس هذا المتجه قبالة طائرة متعامدة على [page:Vector3 normal]. + يفترض أن يكون العادي لديه طول وحدة. +

+ +

[method:this round]()

+

+ تتم جولة مكونات هذا المتجه إلى أقرب قيمة صحيحة. +

+ +

[method:this roundToZero]()

+

+ تتم جولة مكونات هذا المتجه نحو الصفر (لأعلى إذا كان سالبًا، + لأسفل إذا كان إيجابيًا) إلى قيمة صحيحة. +

+ +

+ [method:this set]( [param:Float x], [param:Float y], [param:Float z] ) +

+

+ يضبط مكونات [page:.x x]، [page:.y y] و [page:.z z] من هذا + المتجه. +

+ +

+ [method:this setComponent]( [param:Integer index], [param:Float value] ) +

+

+ [page:Integer index] - 0، 1 أو 2.
+ [page:Float value] - [page:Float]

+ + إذا كان index يساوي 0 يضبط [page:.x x] على [page:Float value].
+ إذا كان index يساوي 1 يضبط [page:.y y] على [page:Float value].
+ إذا كان index يساوي 2 يضبط [page:.z z] على [page:Float value] +

+ +

[method:this setFromColor]( [param:Color color] )

+

+ يضبط مكونات [page:.x x] و [page:.y y] و [page:.z z] لهذا المتجه من مكونات r و g و b لـ[page:Color color] المحدد. +

+ +

[method:this setFromCylindrical]( [param:Cylindrical c] )

+

+ يضبط هذا المتجه من الإحداثيات الأسطوانية [page:Cylindrical c]. +

+ +

+ [method:this setFromCylindricalCoords]( [param:Float radius], [param:Float theta], [param:Float y] ) +

+

+ يضبط هذا المتجه من الإحداثيات الأسطوانية [page:Cylindrical radius]، و[page:Cylindrical theta] و[page:Cylindrical y]. +

+ +

[method:this setFromEuler]( [param:Euler euler] )

+

+ يضبط مكونات [page:.x x] و[page:.y y] و[page:.z z] لهذا المتجه من مكونات x و y و z لـ[page:Euler Euler Angle] المحدد. +

+ +

+ [method:this setFromMatrixColumn]( [param:Matrix4 matrix], [param:Integer index] ) +

+

+ يضبط مكونات [page:.x x] و[page:.y y] و[page:.z z] لهذا المتجه من العمود [page:Integer index] من [page:Matrix4 matrix]. +

+ +

+ [method:this setFromMatrix3Column]( [param:Matrix3 matrix], [param:Integer index] ) +

+

+ يضبط مكونات [page:.x x] و [page:.y y] و [page:.z z] لهذا المتجه من العمود [page:Integer index] من [page:Matrix3 matrix]. +

+ +

[method:this setFromMatrixPosition]( [param:Matrix4 m] )

+

+ يضبط هذا المتجه على عناصر الموضع لـ [link:https://en.wikipedia.org/wiki/Transformation_matrix مصفوفة التحويل] [page:Matrix4 m]. +

+ +

[method:this setFromMatrixScale]( [param:Matrix4 m] )

+

+ يضبط هذا المتجه على عناصر الحجم لـ [link:https://en.wikipedia.org/wiki/Transformation_matrix مصفوفة التحويل] [page:Matrix4 m]. +

+ +

[method:this setFromSpherical]( [param:Spherical s] )

+

يضبط هذا المتجه من الإحداثيات الكروية [page:Spherical s].

+ +

+ [method:this setFromSphericalCoords]( [param:Float radius], [param:Float phi], [param:Float theta] ) +

+

+ يضبط هذا المتجه من الإحداثيات الكروية [page:Spherical radius]، و[page:Spherical phi] و[page:Spherical theta]. +

+ +

[method:this setLength]( [param:Float l] )

+

+ يضبط هذا المتجه على متجه بنفس اتجاه هذا، ولكن بـ [page:.length length] يساوي [page:Float l]. +

+ +

[method:this setScalar]( [param:Float scalar] )

+

+ يضبط قيمة كل من [page:.x x] و[page:.y y] و[page:.z z] لهذا المتجه على قيمة مساوية لـ[page:Float scalar]. +

+ +

[method:this setX]( [param:Float x] )

+

يستبدل قيمة [page:.x x] لهذا المتجه بـ[page:Float x].

+ +

[method:this setY]( [param:Float y] )

+

يستبدل قيمة [page:.y y] لهذا المتجه بـ[page:Float y].

+ +

[method:this setZ]( [param:Float z] )

+

يستبدل قيمة [page:.z z] لهذا المتجه بـ[page:Float z].

+ +

[method:this sub]( [param:Vector3 v] )

+

يطرح [page:Vector3 v] من هذا المتجه.

+ +

[method:this subScalar]( [param:Float s] )

+

+ يطرح [page:Float s] من مكونات [page:.x x] و [page:.y y] و [page:.z z] لهذا المتجه. +

+ +

[method:this subVectors]( [param:Vector3 a], [param:Vector3 b] )

+

يضبط هذا المتجه على [page:Vector3 a] - [page:Vector3 b].

+ +

+ [method:Array toArray]( [param:Array array], [param:Integer offset] ) +

+

+ [page:Array array] - (اختياري) مصفوفة لتخزين هذا المتجه فيها. إذا لم يتم توفير هذا، سيتم إنشاء مصفوفة جديدة.
+ [page:Integer offset] - (اختياري) إزاحة اختيارية في المصفوفة.

+ + يرجع مصفوفة [x، y، z]، أو ينسخ x و y و z في المصفوفة المعطاة من نوع [page:Array array]. +

+ +

[method:this transformDirection]( [param:Matrix4 m] )

+

+ يحول اتجاه هذا المتجه بمصفوفة (المجموعة العلوية اليسرى 3 × 3 من [page:Matrix4 m]) ثم يقوم بـ [page:.normalize تطبيع] النتيجة. +

+ +

[method:this unproject]( [param:Camera camera] )

+

+ [page:Camera camera] — الكاميرا المستخدمة في الإسقاط.

+ + يُسقِط هذا المتجه من مساحة الجهاز المعيارية للكاميرا (NDC) إلى مساحة العالم. +

+ +

[method:this random]()

+

+ يضبط كل مكون من مكونات هذا المتجه على قيمة شبه عشوائية بين 0 و 1، باستثناء 1. +

+ +

[method:this randomDirection]()

+

يضبط هذا المتجه على نقطة عشوائية موحدة على كرة وحدة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/math/Vector4.html b/docs/api/ar/math/Vector4.html new file mode 100644 index 00000000000000..1b78501a55a197 --- /dev/null +++ b/docs/api/ar/math/Vector4.html @@ -0,0 +1,392 @@ + + + + + + + + + +

[name]

+ +

+ تمثل الفئة متجه 4D [link:https://en.wikipedia.org/wiki/Vector_space vector]. + متجه 4D هو رباعي من الأرقام المرتبة (مسمى x و y و z و w) ، والذي يمكن استخدامه لتمثيل عدد من الأشياء ، مثل: +

+ +
    +
  • نقطة في الفضاء 4D.
  • +
  • + اتجاه وطول في الفضاء 4D. في three.js ، سيكون الطول دائمًا + [link:https://en.wikipedia.org/wiki/Euclidean_distance المسافة الإقليدية] + (المسافة المستقيمة) من `(0، 0، 0، 0)` إلى `(x، y، z، w)` + ويتم قياس الاتجاه أيضًا من `(0، 0، 0، 0)` نحو `(x، y، z، w)`. +
  • +
  • أي رباعي مرتب عشوائي من الأرقام.
  • +
+ +

+ هناك أشياء أخرى يمكن استخدام متجه 4D لتمثيلها ، ولكن هذه هي + الاستخدامات الأكثر شيوعًا في *three.js*. +

+ +

+ التكرار عبر مثيل [name] سيعطي مكوناته `(x، y، z، w)` بالترتيب المقابل. +

+ +

مثال للكود

+ + + const a = new THREE.Vector4( 0, 1, 0, 0 ); + + //لا معاملات؛ سيتم تهيئته إلى (0 ، 0 ، 0 ، 1) + const b = new THREE.Vector4( ); + + const d = a.dot( b ); + + +

المنشئ (Constructor)

+ +

+ [name]( [param:Float x], [param:Float y], [param:Float z], [param:Float w] ) +

+

+ [page:Float x] - قيمة x لهذا المتجه. الافتراضية هي `0`.
+ [page:Float y] - قيمة y لهذا المتجه. الافتراضية هي `0`.
+ [page:Float z] - قيمة z لهذا المتجه. الافتراضية هي `0`.
+ [page:Float w] - قيمة w لهذا المتجه. الافتراضية هي `1`.

+ + ينشئ جديدًا [name]. +

+ +

الخصائص (Properties)

+ +

[property:Boolean isVector4]

+

علامة للقراءة فقط للتحقق مما إذا كان كائنًا معطىً من نوع [name].

+ +

[property:Float x]

+ +

[property:Float y]

+ +

[property:Float z]

+ +

[property:Float w]

+ +

[property:Float width]

+

الاسم المستعار لـ [page:.z z].

+ +

[property:Float height]

+

الاسم المستعار لـ [page:.w w].

+ +

الطرق (Methods)

+ +

[method:this add]( [param:Vector4 v] )

+

يضيف [page:Vector4 v] إلى هذا المتجه.

+ +

[method:this addScalar]( [param:Float s] )

+

+ يضيف القيمة القياسية s إلى قيم [page:.x x] و [page:.y y] و + [page:.z z] و [page:.w w] لهذا المتجه. +

+ +

[method:this addScaledVector]( [param:Vector4 v], [param:Float s] )

+

+ يضيف مضاعفة [page:Vector4 v] و [page:Float s] إلى هذا المتجه. +

+ +

[method:this addVectors]( [param:Vector4 a], [param:Vector4 b] )

+

يضبط هذا المتجه على [page:Vector4 a] + [page:Vector4 b].

+ +

[method:this applyMatrix4]( [param:Matrix4 m] )

+

يضرب هذا المتجه في 4 × 4 [page:Matrix4 m].

+ +

[method:this ceil]()

+

+ يتم تقريب مكونات [page:.x x] و [page:.y y] و [page:.z z] و + [page:.w w] من هذا المتجه إلى أعلى إلى أقرب قيمة صحيحة. +

+ +

[method:this clamp]( [param:Vector4 min], [param:Vector4 max] )

+

+ [page:Vector4 min]- الحد الأدنى للقيمة [page:.x x] و [page:.y y] و + [page:.z z] و[page:.w w].
+ [page:Vector4 max]- الحد الأقصى للقيمة [page:.x x] و [page:.y y] و + [page:.z z] و[page:.w w] في النطاق المطلوب

+ + إذا كانت قيم x أو y أو z أو w لهذا المتجه أكبر من قيمة x أو y أو z أو w للمتجه الأقصى ، فإنها + تستبدل بالقيمة المقابلة.

+ إذا كانت قيم x أو y أو z أو w لهذا المتجه أقل من قيمة x أو y أو z + أو w للمتجه الأدنى ، يتم استبداله بالقيمة المقابلة. +

+ +

[method:this clampLength]( [param:Float min], [param:Float max] )

+

+ [page:Float min]- الحد الأدنى للقيمة التي سيتم تثبيتها على الطول
+ [page:Float max]- الحد الأقصى للقيمة التي سيتم تثبيتها على الطول

+ + إذا كان طول هذا المتجه أكبر من القيمة القصوى ، يتم استبداله بـ + القيمة القصوى.

+ إذا كان طول هذا المتجه أقل من الحد الأدنى للقيمة ، يتم استبداله بـ + الحد الأدنى للقيمة. +

+ +

[method:this clampScalar]( [param:Float min], [param:Float max] )

+

+ [page:Float min]- الحد الأدنى للقيمة التي سيتم تثبيت المكونات عليها +
+ [page:Float max]- الحد الأقصى للقيمة التي سيتم تثبيت المكونات عليها

+ + إذا كانت قيم x أو y أو z أو w لهذا المتجه أكبر من الحد الأقصى للقيمة ، فإنها + تستبدل بالحد الأقصى للقيمة.

+ إذا كانت قيم x أو y أو z أو w لهذا المتجه أقل من الحد الأدنى للقيمة ، فإنها + تستبدل بالحد الأدنى للقيمة. +

+ +

[method:Vector4 clone]()

+

+ يرجع Vector4 جديدًا بنفس قيم [page:.x x] و [page:.y y] و [page:.z z] + و [page:.w w] كهذا. +

+ +

[method:this copy]( [param:Vector4 v] )

+

+ ينسخ قيم خصائص [page:.x x] و [page:.y y] و [page:.z z] و [page:.w w] من Vector4 المار إلى هذا Vector4. +

+ +

[method:this divideScalar]( [param:Float s] )

+

يقسم هذا المتجه على العدد القياسي [page:Float s].

+ +

[method:Float dot]( [param:Vector4 v] )

+

+ يحسب [link:https://en.wikipedia.org/wiki/Dot_product حاصل ضرب نقطة] + هذا المتجه و [page:Vector4 v]. +

+ +

[method:Boolean equals]( [param:Vector4 v] )

+

+ يرجع `true` إذا كانت مكونات هذا المتجه و [page:Vector4 v] + صارمًا مساوية ؛ `false` في حالات أخرى. +

+ +

[method:this floor]()

+

+ يتم تقريب مكونات هذا المتجه إلى أسفل إلى أقرب قيمة صحيحة + قيمة. +

+ +

+ [method:this fromArray]( [param:Array array], [param:Integer offset]) +

+

+ [page:Array array]- المصفوفة المصدر.
+ [page:Integer offset]- (اختياري) إزاحة في المصفوفة. الافتراضي هو 0.

+ + يضبط قيمة [page:.x x] لهذا المتجه لتكون `array [offset + 0]` ، قيمة [page:.y y] + لتكون `array [offset + 1]` قيمة [page:.z z] لتكون `array [offset + 2]` + وقيمة [page:.w w] لتكون `array [offset + 3]`. +

+ +

+ [method:this fromBufferAttribute]( [param:BufferAttribute attribute], [param:Integer index]) +

+

+ [page:BufferAttribute attribute]- السمة المصدر.
+ [page:Integer index]- فهرس في السمة.

+ + يضبط قيم [page:.x x] و [page:.y y] و [page:.z z] و [page:.w w] + لهذا المتجه من [page:BufferAttribute attribute]. +

+ +

[method:Float getComponent]( [param:Integer index] )

+

+ [page:Integer index]- 0، 1، 2 أو 3.

+ + إذا كان index يساوي 0 يعود بقيمة [page:.x x].
+ إذا كان index يساوي 1 يعود بقيمة [page:.y y].
+ إذا كان index يساوي 2 يعود بقيمة [page:.z z].
+ إذا كان index يساوي 3 يعود بقيمة [page:.w w]. +

+ +

[method:Float length]()

+

+ يحسب [link:https://en.wikipedia.org/wiki/Euclidean_distance الطول الإقليدي] + (طول المستقيم) من `(0، 0، 0، 0)` إلى `(x، y، z، w)`. +

+ +

[method:Float manhattanLength]()

+

+ يحسب طول مانهاتن لهذا المتجه. +

+ +

[method:Float lengthSq]()

+

+ يحسب مربع + [link:https://en.wikipedia.org/wiki/Euclidean_distance الطول الإقليدي] + (طول المستقيم) من `(0، 0، 0، 0)` إلى `(x، y، z، w)`. إذا كنت + تقارن أطوال المتجهات ، فيجب عليك مقارنة الطول المربع + بدلاً من ذلك لأنه أكثر كفاءة قليلاً في الحساب. +

+ +

[method:this lerp]( [param:Vector4 v], [param:Float alpha] )

+

+ [page:Vector4 v]- [page:Vector4] للتداخل نحوه.
+ [page:Float alpha]- عامل التداخل ، عادةً في الفاصل المغلق + الفاصل "[0، 1]".

+ + يتداخل بشكل خطي بين هذا المتجه و [page:Vector4 v] ، حيث + alpha هو نسبة المسافة على طول الخط - سيكون `alpha = 0` هذا + المتجه ، و `alpha = 1` سيكون [page:Vector4 v]. +

+ +

+ [method:this lerpVectors]( [param:Vector4 v1], [param:Vector4 v2], [param:Float alpha]) +

+

+ [page:Vector4 v1]- [page:Vector4] الابتدائية.
+ [page:Vector4 v2]- [page:Vector4] للتداخل نحوه.
+ [page:Float alpha]- عامل التداخل ، عادةً في الفاصل المغلق + الفاصل `[0، 1]`.

+ + يضبط هذا المتجه ليكون المتجه المتداخل خطيًا بين + [page:Vector4 v1] و [page:Vector4 v2] حيث alpha هو نسبة + المسافة على طول الخط الذي يربط بين المتجهين - سيكون alpha = 0 + [page:Vector4 v1] ، وسيكون alpha = 1 [page:Vector4 v2]. +

+ +

[method:this negate]()

+

يعكس هذا المتجه - أي يضبط x = -x و y = -y و z = -z و w = -w.

+ +

[method:this normalize]()

+

+ يحول هذا المتجه إلى [link:https://en.wikipedia.org/wiki/Unit_vector متجه وحدة] + - أي يضعه يساوي متجهًا بنفس الاتجاه + كهذا ، لكن [page:.length الطول] 1. +

+ +

[method:this max]( [param:Vector4 v] )

+

+ إذا كانت قيمة x أو y أو z أو w لهذا المتجه أقل من قيمة x أو y أو z أو w لـ [page:Vector4 v] + ، استبدل تلك القيمة بالقيمة القصوى المقابلة. +

+ +

[method:this min]( [param:Vector4 v] )

+

+ إذا كانت قيمة x أو y أو z أو w لهذا المتجه أكبر من قيمة x أو y أو z أو w لـ [page:Vector4 v] + ، استبدل تلك القيمة بالقيمة الدنيا المقابلة. +

+ +

[method:this multiply]( [param:Vector4 v] )

+

يضرب هذا المتجه في [page:Vector4 v].

+ +

[method:this multiplyScalar]( [param:Float s] )

+

يضرب هذا المتجه بالعدد القياسي [page:Float s].

+ +

[method:this round]()

+

+ يتم تقريب مكونات هذا المتجه إلى أقرب قيمة صحيحة. +

+ +

[method:this roundToZero]()

+

+ يتم تقريب مكونات هذا المتجه نحو الصفر (لأعلى إذا كانت سالبة ، + لأسفل إذا كانت موجبة) إلى قيمة صحيحة. +

+ +

+ [method:this set]( [param:Float x], [param:Float y], [param:Float z], [param:Float w] ) +

+

+ يضبط مكونات [page:.x x] و [page:.y y] و [page:.z z] و [page:.w w] + من هذا المتجه. +

+ +

[method:this setAxisAngleFromQuaternion]( [param:Quaternion q] )

+

+ [page:Quaternion q] - a normalized [page:Quaternion]

+ + يضبط مكونات [page:.x x] و [page:.y y] و [page:.z z] من هذا + المتجه على محور الرباعية و[page:.w w] على الزاوية. +

+ +

[method:this setAxisAngleFromRotationMatrix]( [param:Matrix4 m] )

+

+ [page:Matrix4 m] - a [page:Matrix4] of which the upper left 3x3 matrix is + a pure rotation matrix.

+ + يضبط [page:.x x] و [page:.y y] و [page:.z z] على محور الدوران + و[page:.w w] على الزاوية. +

+ +

+ [method:this setComponent]( [param:Integer index], [param:Float value]) +

+

+ [page:Integer index]- 0، 1، 2 أو 3.

+ [page:Float value]- [page:Float]

+ + إذا كان index يساوي 0 ، فقم بتعيين [page:.x x] إلى [page:Float value].
+ إذا كان index يساوي 1 ، فقم بتعيين [page:.y y] إلى [page:Float value].
+ إذا كان index يساوي 2 ، فقم بتعيين [page:.z z] إلى [page:Float value].
+ إذا كان index يساوي 3 ، فقم بتعيين [page:.w w] إلى [page:Float value]. +

+ +

[method:this setLength]( [param:Float l] )

+

+ يضبط هذا المتجه على متجه بنفس الاتجاه كهذا ، لكن + [page:.length الطول] [page:Float l]. +

+ +

[method:this setScalar]( [param:Float scalar] )

+

+ يضبط قيم [page:.x x] و [page:.y y] و [page:.z z] و [page:.w w] من + هذا المتجه متساوية مع [page:Float scalar]. +

+ +

[method:this setX]( [param:Float x] )

+

يستبدل قيمة [page:.x x] لهذا المتجه بـ [page:Float x].

+ +

[method:this setY]( [param:Float y] )

+

يستبدل قيمة [page:.y y] لهذا المتجه بـ [page:Float y].

+ +

[method:this setZ]( [param:Float z] )

+

يستبدل قيمة [page:.z z] لهذا المتجه بـ [page:Float z].

+ +

[method:this setW]( [param:Float w] )

+

يستبدل قيمة [page:.w w] لهذا المتجه بـ [page:Float w].

+ +

[method:this sub]( [param:Vector4 v] )

+

يطرح [page:Vector4 v] من هذا المتجه.

+ +

[method:this subScalar]( [param:Float s] )

+

+ يطرح [page:Float s] من مكونات هذا المتجه [page:.x x] و [page:.y y] + و[page:.z z] و[page:.w w]. +

+ +

[method:this subVectors]( [param:Vector4 a], [param:Vector4 b] )

+

يضبط هذا المتجه على [page:Vector4 a] - [page:Vector4 b].

+ +

+ [method:Array toArray]( [param:Array array], [param:Integer offset]) +

+

+ [page:Array array]- (اختياري) مصفوفة لتخزين هذا المتجه فيها. إذا لم يتم توفير هذا ، سيتم إنشاء مصفوفة جديدة.
+ [page:Integer offset]- (اختياري) إزاحة اختيارية في المصفوفة.

+ + يرجع مصفوفة [x، y، z، w] ، أو ينسخ x و y و z و w في المصفوفة المقدمة + [page:Array array]. +

+ +

[method:this random]()

+

+ يضبط كل مكون من مكونات هذا المتجه على قيمة شبه عشوائية بين 0 و + 1 ، باستثناء 1. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/math/interpolants/CubicInterpolant.html b/docs/api/ar/math/interpolants/CubicInterpolant.html new file mode 100644 index 00000000000000..1743df5a972ab8 --- /dev/null +++ b/docs/api/ar/math/interpolants/CubicInterpolant.html @@ -0,0 +1,62 @@ + + + + + + + + + + [page:Interpolant] → + +

[name]

+ +

+ +

مثال الكود

+ + const interpolant = new THREE.[name]( + new Float32Array(2), + new Float32Array(2), + 1, + new Float32Array(1) + ); + + interpolant.evaluate(0.5); + + +

المنشئ (Constructor)

+

+ [name](parameterPositions, sampleValues, sampleSize, resultBuffer) +

+

+ parameterPositions - مصفوفة المواضع
+ sampleValues - مصفوفة العينات
+ sampleSize - عدد العينات
+ resultBuffer - مخزن لتخزين نتائج التداخل.

+

+ +

الخصائص (Properties)

+ +

[property:null parameterPositions]

+ +

[property:null resultBuffer]

+ +

[property:null sampleValues]

+ +

[property:Object settings]

+ +

[property:null valueSize]

+ +

الطرق (Methods)

+ +

[method:Array evaluate]([param:Number t])

+

تقييم المُداخِل في الموضع *t*.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/math/interpolants/DiscreteInterpolant.html b/docs/api/ar/math/interpolants/DiscreteInterpolant.html new file mode 100644 index 00000000000000..1743df5a972ab8 --- /dev/null +++ b/docs/api/ar/math/interpolants/DiscreteInterpolant.html @@ -0,0 +1,62 @@ + + + + + + + + + + [page:Interpolant] → + +

[name]

+ +

+ +

مثال الكود

+ + const interpolant = new THREE.[name]( + new Float32Array(2), + new Float32Array(2), + 1, + new Float32Array(1) + ); + + interpolant.evaluate(0.5); + + +

المنشئ (Constructor)

+

+ [name](parameterPositions, sampleValues, sampleSize, resultBuffer) +

+

+ parameterPositions - مصفوفة المواضع
+ sampleValues - مصفوفة العينات
+ sampleSize - عدد العينات
+ resultBuffer - مخزن لتخزين نتائج التداخل.

+

+ +

الخصائص (Properties)

+ +

[property:null parameterPositions]

+ +

[property:null resultBuffer]

+ +

[property:null sampleValues]

+ +

[property:Object settings]

+ +

[property:null valueSize]

+ +

الطرق (Methods)

+ +

[method:Array evaluate]([param:Number t])

+

تقييم المُداخِل في الموضع *t*.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/math/interpolants/LinearInterpolant.html b/docs/api/ar/math/interpolants/LinearInterpolant.html new file mode 100644 index 00000000000000..45b2dcb164e7f9 --- /dev/null +++ b/docs/api/ar/math/interpolants/LinearInterpolant.html @@ -0,0 +1,62 @@ + + + + + + + + + + [page:Interpolant] → + +

[name]

+ +

+ +

مثال الكود

+ + const interpolant = new THREE.[name]( + new Float32Array(2), + new Float32Array(2), + 1, + new Float32Array(1) + ); + + interpolant.evaluate(0.5); + + +

المنشئ (Constructor)

+

+ [name](parameterPositions, sampleValues, sampleSize, resultBuffer) +

+

+ parameterPositions - مصفوفة المواضع
+ sampleValues - مصفوفة العينات
+ sampleSize - عدد العينات
+ resultBuffer - مخزن لتخزين نتائج التداخل.

+

+ +

الخصائص (Properties)

+ +

[property:null parameterPositions]

+ +

[property:null resultBuffer]

+ +

[property:null sampleValues]

+ +

[property:Object settings]

+ +

[property:null valueSize]

+ +

الطرق (Methods)

+ +

[method:Array evaluate]([param:Number t])

+

تقييم المُداخِل في الموضع *t*.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/math/interpolants/QuaternionLinearInterpolant.html b/docs/api/ar/math/interpolants/QuaternionLinearInterpolant.html new file mode 100644 index 00000000000000..45b2dcb164e7f9 --- /dev/null +++ b/docs/api/ar/math/interpolants/QuaternionLinearInterpolant.html @@ -0,0 +1,62 @@ + + + + + + + + + + [page:Interpolant] → + +

[name]

+ +

+ +

مثال الكود

+ + const interpolant = new THREE.[name]( + new Float32Array(2), + new Float32Array(2), + 1, + new Float32Array(1) + ); + + interpolant.evaluate(0.5); + + +

المنشئ (Constructor)

+

+ [name](parameterPositions, sampleValues, sampleSize, resultBuffer) +

+

+ parameterPositions - مصفوفة المواضع
+ sampleValues - مصفوفة العينات
+ sampleSize - عدد العينات
+ resultBuffer - مخزن لتخزين نتائج التداخل.

+

+ +

الخصائص (Properties)

+ +

[property:null parameterPositions]

+ +

[property:null resultBuffer]

+ +

[property:null sampleValues]

+ +

[property:Object settings]

+ +

[property:null valueSize]

+ +

الطرق (Methods)

+ +

[method:Array evaluate]([param:Number t])

+

تقييم المُداخِل في الموضع *t*.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/objects/Bone.html b/docs/api/ar/objects/Bone.html new file mode 100644 index 00000000000000..5b13b7f168db7e --- /dev/null +++ b/docs/api/ar/objects/Bone.html @@ -0,0 +1,53 @@ + + + + + + + + + + [page:Object3D] → + +

[name]

+ +

+ عظمة هي جزء من [page:Skeleton]. يتم استخدام الهيكل العظمي بدوره من قبل + [page:SkinnedMesh]. العظام تكاد تكون مطابقة لـ + [page:Object3D] فارغ. +

+ +

مثال للكود

+ + + const root = new THREE.Bone(); + const child = new THREE.Bone(); + + root.add( child ); + child.position.y = 5; + + +

المنشئ (Constructor)

+ +

[name]( )

+

ينشئ جديدًا [name].

+ +

الخصائص (Properties)

+

راجع الفئة الأساسية [page:Object3D] للحصول على الخصائص المشتركة.

+ +

[property:Boolean isBone]

+

علامة للقراءة فقط للتحقق مما إذا كان كائنًا معطىً من نوع [name].

+ +

[property:String type]

+

تعيين إلى 'Bone' ، يمكن استخدام هذا للعثور على جميع العظام في المشهد.

+ +

الطرق (Methods)

+

راجع الفئة الأساسية [page:Object3D] للحصول على الطرق المشتركة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/objects/Group.html b/docs/api/ar/objects/Group.html new file mode 100644 index 00000000000000..3a07251d7a34d3 --- /dev/null +++ b/docs/api/ar/objects/Group.html @@ -0,0 +1,61 @@ + + + + + + + + + + [page:Object3D] → + +

[name]

+ +

+ هذا مطابق تقريبًا لـ [page:Object3D Object3D]. الغرض منه هو + جعل العمل مع مجموعات من الكائنات أكثر وضوحًا نحويًا. +

+ +

مثال للكود

+ + + const geometry = new THREE.BoxGeometry( 1, 1, 1 ); + const material = new THREE.MeshBasicMaterial( {color: 0x00ff00} ); + + const cubeA = new THREE.Mesh( geometry, material ); + cubeA.position.set( 100, 100, 0 ); + + const cubeB = new THREE.Mesh( geometry, material ); + cubeB.position.set( -100, -100, 0 ); + + //إنشاء مجموعة وإضافة المكعبين + //يمكن الآن تدوير / تحجيم هذه المكعبات كمجموعة + const group = new THREE.Group(); + group.add( cubeA ); + group.add( cubeB ); + + scene.add( group ); + + +

المنشئ (Constructor)

+ +

[name]( )

+ +

الخصائص (Properties)

+

راجع الفئة الأساسية [page:Object3D] للحصول على الخصائص المشتركة.

+ +

[property:Boolean isGroup]

+

علامة للقراءة فقط للتحقق مما إذا كان كائنًا معطىً من نوع [name].

+ +

[property:String type]

+

سلسلة 'Group'. يجب عدم تغيير هذا.

+ +

الطرق (Methods)

+

راجع الفئة الأساسية [page:Object3D] للحصول على الطرق المشتركة.

+ +

المصدر (Source)

+

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/objects/InstancedMesh.html b/docs/api/ar/objects/InstancedMesh.html new file mode 100644 index 00000000000000..8413a73e50c680 --- /dev/null +++ b/docs/api/ar/objects/InstancedMesh.html @@ -0,0 +1,172 @@ + + + + + + + + + + [page:Mesh] → + +

[name]

+ +

+ نسخة خاصة من [page:Mesh] مع دعم التصيير المثيل. استخدم + [name] إذا كان عليك تقديم عدد كبير من الكائنات بنفس + الهندسة والمواد ولكن مع تحولات العالم المختلفة. الاستخدام + من [name] سيساعدك على تقليل عدد مكالمات الرسم وبالتالي + تحسين أداء التصيير العام في تطبيقك. +

+ +

أمثلة (Examples)

+

+ [example:webgl_instancing_dynamic WebGL / instancing / dynamic]
+ [example:webgl_instancing_performance WebGL / instancing / performance]
+ [example:webgl_instancing_scatter WebGL / instancing / scatter]
+ [example:webgl_instancing_raycast WebGL / instancing / raycast] +

+ +

المنشئ (Constructor)

+

+ [name]( [param:BufferGeometry geometry], [param:Material material], + [param:Integer count] ) +

+

+ [page:BufferGeometry geometry] - عينة من [page:BufferGeometry].
+ [page:Material material] - عينة من [page:Material]. الافتراضية هي + جديد [page:MeshBasicMaterial].
+ [page:Integer count] - عدد العينات.
+

+ +

الخصائص (Properties)

+

راجع الفئة الأساسية [page:Mesh] للحصول على الخصائص المشتركة.

+ +

[property:Box3 boundingBox]

+

+ يحيط هذا المربع التجزئة بجميع عينات [name]. يمكن حسابه + مع [page:.computeBoundingBox](). الافتراضية هي `null`. +

+ +

[property:Sphere boundingSphere]

+

+ يحيط هذا المجال المحدد بجميع عينات [name]. يمكن + حسابه مع [page:.computeBoundingSphere](). الافتراضية هي `null`. +

+ +

[property:Integer count]

+

+ عدد العينات. قيمة `count` الممررة إلى الباني + تمثل الحد الأقصى لعدد العينات من هذه المشبك. يمكنك تغيير + عدد العينات في وقت التشغيل إلى قيمة صحيحة في النطاق [0، count]. +

+

+ إذا كنت بحاجة إلى عدد أكبر من العينات من قيمة العدد الأصلية ، فلديك + إنشاء جديد [name]. +

+ +

[property:InstancedBufferAttribute instanceColor]

+

+ يمثل ألوان جميع العينات. `null` بشكل افتراضي. يجب عليك ضبط + علامة [page:BufferAttribute.needsUpdate needsUpdate] الخاصة بها على صحيح إذا كان لديك + تعديل بيانات مثيل عبر [page:.setColorAt](). +

+ +

[property:InstancedBufferAttribute instanceMatrix]

+

+ يمثل التحول المحلي لجميع العينات. يجب عليك ضبطه + [page:BufferAttribute.needsUpdate needsUpdate] علامة صحيحة إذا قمت بتعديل + بيانات مثيل عبر [page:.setMatrixAt](). +

+ +

[property:Boolean isInstancedMesh]

+

علامة للقراءة فقط للتحقق مما إذا كان كائنًا معطىً من نوع [name].

+ +

الطرق (Methods)

+

راجع الفئة الأساسية [page:Mesh] للحصول على الطرق المشتركة.

+ +

[method:undefined computeBoundingBox]()

+

+ يحسب مربع التجزئة ، وتحديث [page:.boundingBox] سمة.
+ لا يتم حساب مربعات التجزئة افتراضيًا. يجب حسابها بشكل صريح + حسابها ، وإلا كانت `null`. +

+ +

[method:undefined computeBoundingSphere]()

+

+ يحسب المجال المحدد ، وتحديث [page:.boundingSphere] + سمة.
+ لا يتم حساب المجالات المحددة افتراضيًا. يجب حسابها بشكل صريح + حسابها ، وإلا كانت `null`. +

+ +

[method:undefined dispose]()

+

+ يطلق الموارد المتعلقة بوحدة معالجة الرسومات التي تم تخصيصها من قبل هذه العينة. استدعاء هذه + الطريقة كلما لم تستخدم هذه العينة في تطبيقك. +

+ +

+ [method:undefined getColorAt]( [param:Integer index], [param:Color color] ) +

+

+ [page:Integer index]: فهرس العينة. يجب أن تكون القيم في + النطاق [0، count]. +

+

+ [page:Color color]: سيتم تعيين كائن اللون هذا إلى لون + العينة المحددة. +

+

احصل على لون العينة المحددة.

+ +

+ [method:undefined getMatrixAt]( [param:Integer index], [param:Matrix4 matrix] ) +

+

+ [page:Integer index]: فهرس العينة. يجب أن تكون القيم في + النطاق [0، count]. +

+

+ [page:Matrix4 matrix]: ستتم تعيين مصفوفة 4x4 هذه إلى المحلية + مصفوفة التحول للعينة المحددة. +

+

احصل على مصفوفة التحول المحلية للعينة المحددة.

+ +

+ [method:undefined setColorAt]( [param:Integer index], [param:Color color] ) +

+

+ [page:Integer index]: فهرس العينة. يجب أن تكون القيم في + النطاق [0، count]. +

+

[page:Color color]: لون عينة واحدة.

+

+ يضع اللون المحدد على العينة المحددة. تأكد من ضبط + [page:.instanceColor][page:BufferAttribute.needsUpdate .needsUpdate] إلى + صحيح بعد تحديث جميع الألوان. +

+ +

+ [method:undefined setMatrixAt]( [param:Integer index], [param:Matrix4 matrix] ) +

+

+ [page:Integer index]: فهرس العينة. يجب أن تكون القيم في + النطاق [0، count]. +

+

+ [page:Matrix4 matrix]: مصفوفة 4x4 تمثل التحول المحلي + لعينة واحدة. +

+

+ يضع مصفوفة التحول المحلية المحددة على العينة المحددة. جعل + تأكد من ضبط [page:.instanceMatrix][page:BufferAttribute.needsUpdate .needsUpdate] + صحيح بعد تحديث جميع المصفوفات. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/objects/LOD.html b/docs/api/ar/objects/LOD.html new file mode 100644 index 00000000000000..09a3300730157c --- /dev/null +++ b/docs/api/ar/objects/LOD.html @@ -0,0 +1,115 @@ + + + + + + + + + + [page:Object3D] → + +

[name]

+

+ مستوى التفاصيل - عرض الشبكات بمزيد أو أقل من الهندسة بناءً على المسافة + من الكاميرا.

+ + كل مستوى مرتبط بكائن، ويمكن تبديل التصيير + بينهم على المسافات المحددة. عادةً ما تقوم بإنشاء، قل، + ثلاث شبكات، واحدة للبعيد (تفاصيل منخفضة)، واحدة للمدى المتوسط ​​(تفاصيل متوسطة) + وواحد للقريب (تفاصيل عالية). +

+ +

مثال للكود

+ + + const lod = new THREE.LOD(); + + // إنشاء كرات مع 3 مستويات من التفاصيل وإنشاء مستويات LOD جديدة لهم + for( let i = 0; i < 3; i++ ) { + const geometry = new THREE.IcosahedronGeometry( 10, 3 - i ); + const mesh = new THREE.Mesh( geometry, material ); + lod.addLevel( mesh, i * 75 ); + } + + scene.add( lod ); + + +

أمثلة (Examples)

+ +

[example:webgl_lod webgl / lod ]

+ +

المنشئ (Constructor)

+

[name]( )

+

إنشاء [name] جديد.

+ +

الخصائص (Properties)

+

انظر إلى الفئة الأساسية [page:Object3D] للحصول على الخصائص المشتركة.

+ +

[property:Boolean autoUpdate]

+

+ ما إذا كان يتم تحديث كائن LOD تلقائيًا من قبل المُصور في كل إطار + أم لا. إذا تم تعيينه إلى false، يجب عليك استدعاء [page:LOD.update]() في + حلقة التصيير بنفسك. الافتراضي هو true. +

+ +

[property:Boolean isLOD]

+

علامة للقراءة فقط للتحقق مما إذا كان كائن معين هو من نوع [name].

+ +

[property:Array levels]

+

+ مصفوفة من كائنات [page:Object level]

+ + كل مستوى هو كائن يحتوي على الخصائص التالية:
+ [page:Object3D object] - الـ [page:Object3D] المراد عرضه في هذا المستوى.
+ [page:Float distance] - المسافة التي يتم عرض هذا المستوى من التفاصيل فيها.
+ [page:Float hysteresis] - الحد الذي يستخدم لتجنب الوميض عند حدود LOD، كجزء من المسافة. +

+ +

الطرق (Methods)

+

انظر إلى الفئة الأساسية [page:Object3D] للحصول على الطرق المشتركة.

+ +

+ [method:this addLevel]( [param:Object3D object], [param:Float distance], [param:Float hysteresis] ) +

+

+ [page:Object3D object] - الـ [page:Object3D] المراد عرضه في هذا المستوى.
+ [page:Float distance] - المسافة التي يتم عرض هذا المستوى من التفاصيل فيها. الافتراضي 0.0.
+ [page:Float hysteresis] - الحد الذي يستخدم لتجنب الوميض عند حدود LOD، كجزء من المسافة. الافتراضي 0.0.

+ + إضافة شبكة ستعرض عند مسافة معينة وأكبر. عادةً ما تكون المسافة أبعد، كلما كان التفصيل أقل في الشبكة. +

+ +

[method:Integer getCurrentLevel]()

+

الحصول على مستوى LOD النشط حاليًا. كفهرس لمصفوفة المستويات.

+ +

[method:Object3D getObjectForDistance]( [param:Float distance] )

+

+ الحصول على مرجع إلى أول [page:Object3D] (شبكة) أكبر من + [page:Float distance]. +

+ +

+ [method:undefined raycast]( [param:Raycaster raycaster], [param:Array intersects] ) +

+

+ الحصول على تقاطعات بين [page:Ray] ملقى وهذا LOD. + سيتم استدعاء هذه الطريقة بواسطة [page:Raycaster.intersectObject]. +

+ +

[method:Object toJSON]( meta )

+

إنشاء بنية JSON بتفاصيل هذا كائن LOD.

+ +

[method:undefined update]( [param:Camera camera] )

+

+ تعيين رؤية كل كائن [page:Object3D object] في [page:levels level] + بناءً على المسافة من [page:Camera camera]. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/objects/Line.html b/docs/api/ar/objects/Line.html new file mode 100644 index 00000000000000..9714eaed29d61d --- /dev/null +++ b/docs/api/ar/objects/Line.html @@ -0,0 +1,111 @@ + + + + + + + + + + [page:Object3D] → + +

[name]

+ +

+ خط مستمر.

+ + هذا مطابق تقريبًا لـ [page:LineSegments] ؛ الفرق الوحيد هو + أنه يتم تقديمه باستخدام + [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/drawElements gl.LINE_STRIP] بدلاً من + [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/drawElements gl.LINES] +

+ +

مثال للكود

+ + + const material = new THREE.LineBasicMaterial({ + color: 0x0000ff + }); + + const points = []; + points.push( new THREE.Vector3( - 10, 0, 0 ) ); + points.push( new THREE.Vector3( 0, 10, 0 ) ); + points.push( new THREE.Vector3( 10, 0, 0 ) ); + + const geometry = new THREE.BufferGeometry().setFromPoints( points ); + + const line = new THREE.Line( geometry, material ); + scene.add( line ); + + +

المنشئ (Constructor)

+ +

+ [name]( [param:BufferGeometry geometry], [param:Material material] ) +

+ +

+ [page:BufferGeometry geometry] - الرؤوس التي تمثل الخط + القطعة (القطع). الافتراضية هي جديدة [page:BufferGeometry].
+ [page:Material material] - مادة للخط. الافتراضية هي جديدة + [page:LineBasicMaterial].
+

+ +

الخصائص (Properties)

+

راجع الفئة الأساسية [page:Object3D] للحصول على الخصائص المشتركة.

+ +

[property:BufferGeometry geometry]

+

الرؤوس التي تمثل الخط القطعة (القطع).

+ +

[property:Boolean isLine]

+

علامة للقراءة فقط للتحقق مما إذا كان كائنًا معطىً من نوع [name].

+ +

[property:Material material]

+

مادة للخط.

+ +

[property:Array morphTargetInfluences]

+

+ مجموعة من الأوزان عادةً من 0-1 التي تحدد مدى تطبيق التغير + يتم تطبيقه. غير محدد بشكل افتراضي ، ولكن يتم إعادة تعيينه إلى مجموعة فارغة بواسطة + [page:.updateMorphTargets](). +

+ +

[property:Object morphTargetDictionary]

+

+ قاموس من morphTargets بناءً على خاصية morphTarget.name. + غير محدد بشكل افتراضي ، ولكن يتم إعادة بنائه [page:.updateMorphTargets](). +

+ +

الطرق (Methods)

+

راجع الفئة الأساسية [page:Object3D] للحصول على الطرق المشتركة.

+ +

[method:this computeLineDistances]()

+

+ يحسب مجموعة من قيم المسافات التي هي ضرورية لـ + [page:LineDashedMaterial]. بالنسبة لكل رأس في الهندسة ، يحسب الأسلوب + الطول التراكمي من النقطة الحالية إلى + بداية جدا من الخط. +

+ +

+ [method:undefined raycast]( [param:Raycaster raycaster], [param:Array intersects] ) +

+

+ احصل على تقاطعات بين [page:Ray] المصبوب وهذا الخط. + ستستدعي [page:Raycaster.intersectObject] هذه الطريقة. +

+ +

[method:undefined updateMorphTargets]()

+

+ يحدث morphTargets ليكون لها تأثير على الكائن. يعيد تعيين + [page:.morphTargetInfluences] و [page:.morphTargetDictionary] + خصائص. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/objects/LineLoop.html b/docs/api/ar/objects/LineLoop.html new file mode 100644 index 00000000000000..1f91059aa07bb3 --- /dev/null +++ b/docs/api/ar/objects/LineLoop.html @@ -0,0 +1,53 @@ + + + + + + + + + + [page:Object3D] → [page:Line] → + +

[name]

+ +

+ خط مستمر يتصل بالعودة إلى البداية.

+ + هذا مطابق تقريبًا لـ [page:Line] ؛ الفرق الوحيد هو أنه + يتم تقديمه باستخدام + [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/drawElements gl.LINE_LOOP] بدلاً من + [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/drawElements gl.LINE_STRIP]، + الذي يرسم خطًا مستقيمًا إلى الرأس التالي ، و + يربط الرأس الأخير مرة أخرى إلى الأول. +

+ +

المنشئ (Constructor)

+ +

+ [name]( [param:BufferGeometry geometry], [param:Material material] ) +

+ +

+ [page:BufferGeometry geometry] - قائمة من الرؤوس التي تمثل نقاط على + حلقة الخط.
+ [page:Material material] - مادة للخط. الافتراضية هي + [page:LineBasicMaterial LineBasicMaterial]. +

+ +

الخصائص (Properties)

+

راجع الفئة الأساسية [page:Line] للحصول على الخصائص المشتركة.

+ +

[property:Boolean isLineLoop]

+

علامة للقراءة فقط للتحقق مما إذا كان كائنًا معطىً من نوع [name].

+ +

الطرق (Methods)

+

راجع الفئة الأساسية [page:Line] للحصول على الطرق المشتركة.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/objects/LineSegments.html b/docs/api/ar/objects/LineSegments.html new file mode 100644 index 00000000000000..c97b74e2900ecf --- /dev/null +++ b/docs/api/ar/objects/LineSegments.html @@ -0,0 +1,49 @@ + + + + + + + + + + [page:Object3D] → [page:Line] → + +

[name]

+ +

+ سلسلة من الخطوط المرسومة بين أزواج الرؤوس.

+ + هذا مشابه تقريبًا لـ [page:Line]؛ الفرق الوحيد هو أنه يتم عرضه باستخدام + [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/drawElements gl.LINES] بدلاً من + [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/drawElements gl.LINE_STRIP]. +

+ +

المنشئ (Constructor)

+ +

+ [name]( [param:BufferGeometry geometry], [param:Material material] ) +

+ +

+ [page:BufferGeometry geometry] — زوج (أزواج) من الرؤوس التي تمثل كل + خط (خطوط) القطع.
+ [page:Material material] — المادة المستخدمة للخط. الافتراضي هو + [page:LineBasicMaterial LineBasicMaterial]. +

+ +

الخصائص (Properties)

+

انظر إلى الفئة الأساسية [page:Line] للحصول على الخصائص المشتركة.

+ +

[property:Boolean isLineSegments]

+

علامة للقراءة فقط للتحقق مما إذا كان كائن معين هو من نوع [name].

+ +

الطرق (Methods)

+

انظر إلى الفئة الأساسية [page:Line] للحصول على الطرق المشتركة.

+ +

المصدر (Source)

+

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/objects/Mesh.html b/docs/api/ar/objects/Mesh.html new file mode 100644 index 00000000000000..e308fd82bb4ed0 --- /dev/null +++ b/docs/api/ar/objects/Mesh.html @@ -0,0 +1,105 @@ + + + + + + + + + + [page:Object3D] → + +

[name]

+ +

+ فئة تمثل كائنات مبنية على شبكات مضلعة ثلاثية الأضلاع + [link:https://en.wikipedia.org/wiki/Polygon_mesh]. كما تعمل كقاعدة لفئات أخرى مثل + [page:SkinnedMesh]. +

+ +

مثال للكود

+ + + const geometry = new THREE.BoxGeometry( 1, 1, 1 ); + const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); + const mesh = new THREE.Mesh( geometry, material ); + scene.add( mesh ); + + +

المنشئ (Constructor)

+ +

+ [name]( [param:BufferGeometry geometry], [param:Material material] ) +

+

+ [page:BufferGeometry geometry] — (اختياري) نسخة من + [page:BufferGeometry]. الافتراضي هو [page:BufferGeometry] جديد.
+ [page:Material material] — (اختياري) نسخة واحدة أو مصفوفة من + [page:Material]. الافتراضي هو [page:MeshBasicMaterial] جديد +

+ +

الخصائص (Properties)

+

انظر إلى الفئة الأساسية [page:Object3D] للحصول على الخصائص المشتركة.

+ +

[property:BufferGeometry geometry]

+

+ نسخة من [page:BufferGeometry] (أو الفئات المشتقة منها)، تحدد بنية + الكائن. +

+ +

[property:Boolean isMesh]

+

علامة للقراءة فقط للتحقق مما إذا كان كائن معين هو من نوع [name].

+ +

[property:Material material]

+

+ نسخة من المادة المشتقة من فئة [page:Material] الأساسية أو مصفوفة من المواد، تحدد مظهر الكائن. الافتراضي هو + [page:MeshBasicMaterial]. +

+ +

[property:Array morphTargetInfluences]

+

+ مصفوفة من الأوزان عادةً من 0-1 تحدد مقدار التحول + الذي يتم تطبيقه. غير معرف بشكل افتراضي، ولكن يتم إعادة تعيينه إلى مصفوفة فارغة بواسطة + [page:Mesh.updateMorphTargets updateMorphTargets]. +

+ +

[property:Object morphTargetDictionary]

+

+ قاموس من morphTargets بناءً على خاصية morphTarget.name. + غير معرف بشكل افتراضي، ولكن يتم إعادة بنائه [page:Mesh.updateMorphTargets updateMorphTargets]. +

+ +

الطرق (Methods)

+

انظر إلى الفئة الأساسية [page:Object3D] للحصول على الطرق المشتركة.

+ +

+ [method:Vector3 getVertexPosition]( [param:Integer index], [param:Vector3 target] ) +

+

+ الحصول على موضع الرأس المحلي في الفهرس المحدد، مع مراعاة + حالة الرسوم المتحركة الحالية لكل من التحولات والتشكيل. +

+ +

+ [method:undefined raycast]( [param:Raycaster raycaster], [param:Array intersects] ) +

+

+ الحصول على تقاطعات بين شعاع ملقى وهذه الشبكة. + سيتم استدعاء هذه الطريقة بواسطة [page:Raycaster.intersectObject]، ولكن النتائج + غير مرتبة. +

+ +

[method:undefined updateMorphTargets]()

+

+ تحديث التحولات لعدم التأثير على الكائن. يعيد تعيين خصائص + [page:Mesh.morphTargetInfluences morphTargetInfluences] و + [page:Mesh.morphTargetDictionary morphTargetDictionary]. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/objects/Points.html b/docs/api/ar/objects/Points.html new file mode 100644 index 00000000000000..c20f6771a0644f --- /dev/null +++ b/docs/api/ar/objects/Points.html @@ -0,0 +1,87 @@ + + + + + + + + + + [page:Object3D] → + +

[name]

+ +

+ فئة لعرض النقاط. يتم عرض النقاط بواسطة + [page:WebGLRenderer] باستخدام + [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/drawElements gl.POINTS]. +

+ +

المنشئ (Constructor)

+ +

+ [name]( [param:BufferGeometry geometry], [param:Material material] ) +

+

+ [page:BufferGeometry geometry] — (اختياري) نسخة من + [page:BufferGeometry]. الافتراضي هو [page:BufferGeometry] جديد.
+ [page:Material material] — (اختياري) نسخة من [page:Material]. الافتراضي هو + [page:PointsMaterial] جديد. +

+ +

الخصائص (Properties)

+

انظر إلى الفئة الأساسية [page:Object3D] للحصول على الخصائص المشتركة.

+ +

[property:BufferGeometry geometry]

+

+ نسخة من [page:BufferGeometry] (أو الفئات المشتقة منها)، تحدد بنية + الكائن. +

+ +

[property:Boolean isPoints]

+

علامة للقراءة فقط للتحقق مما إذا كان كائن معين هو من نوع [name].

+ +

[property:Material material]

+

+ نسخة من [page:Material]، تحدد مظهر الكائن. الافتراضي + هو [page:PointsMaterial]. +

+ +

[property:Array morphTargetInfluences]

+

+ مصفوفة من الأوزان عادةً من 0-1 تحدد مقدار التحول + الذي يتم تطبيقه. غير معرف بشكل افتراضي، ولكن يتم إعادة تعيينه إلى مصفوفة فارغة بواسطة + [page:Points.updateMorphTargets updateMorphTargets]. +

+ +

[property:Object morphTargetDictionary]

+

+ قاموس من morphTargets بناءً على خاصية morphTarget.name. + غير معرف بشكل افتراضي، ولكن يتم إعادة بنائه [page:Points.updateMorphTargets updateMorphTargets]. +

+ +

الطرق (Methods)

+

انظر إلى الفئة الأساسية [page:Object3D] للحصول على الطرق المشتركة.

+ +

+ [method:undefined raycast]( [param:Raycaster raycaster], [param:Array intersects] ) +

+

+ الحصول على تقاطعات بين شعاع ملقى وهذه النقاط. + سيتم استدعاء هذه الطريقة بواسطة [page:Raycaster.intersectObject]. +

+ +

[method:undefined updateMorphTargets]()

+

+ تحديث التحولات لعدم التأثير على الكائن. يعيد تعيين خصائص + [page:Points.morphTargetInfluences morphTargetInfluences] و + [page:Points.morphTargetDictionary morphTargetDictionary]. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/objects/Skeleton.html b/docs/api/ar/objects/Skeleton.html new file mode 100644 index 00000000000000..8c38b6c63d3c14 --- /dev/null +++ b/docs/api/ar/objects/Skeleton.html @@ -0,0 +1,128 @@ + + + + + + + + + +

[name]

+ +

+ استخدم مصفوفة من [page:Bone bones] لإنشاء هيكل عظمي يمكن استخدامه بواسطة + [page:SkinnedMesh]. +

+ +

مثال للكود

+ + // إنشاء "ذراع" بسيط + + const bones = []; + + const shoulder = new THREE.Bone(); + const elbow = new THREE.Bone(); + const hand = new THREE.Bone(); + + shoulder.add( elbow ); + elbow.add( hand ); + + bones.push( shoulder ); + bones.push( elbow ); + bones.push( hand ); + + shoulder.position.y = -5; + elbow.position.y = 0; + hand.position.y = 5; + + const armSkeleton = new THREE.Skeleton( bones ); + + +

+ انظر إلى صفحة [page:SkinnedMesh] للحصول على مثال على الاستخدام مع + [page:BufferGeometry] القياسية. +

+ +

المنشئ (Constructor)

+ +

[name]( [param:Array bones], [param:Array boneInverses] )

+

+ [page:Array bones] - مصفوفة من [page:Bone bones]. الافتراضي هو مصفوفة فارغة + .
+ [page:Array boneInverses] - (اختياري) مصفوفة من [page:Matrix4 Matrix4s].

+ + إنشاء [name] جديد. +

+ +

الخصائص (Properties)

+ +

[property:Array bones]

+

+ مصفوفة من [page:bone bones]. لاحظ أن هذه نسخة من المصفوفة الأصلية، + وليس مرجعًا، لذلك يمكنك تعديل المصفوفة الأصلية دون التأثير + هذه واحدة. +

+ +

[property:Array boneInverses]

+

+ مصفوفة من [page:Matrix4 Matrix4s] تمثل معكوس + [page:Matrix4 matrixWorld] للعظام الفردية. +

+ +

[property:Float32Array boneMatrices]

+

مصفوفة البيانات التي تحمل بيانات العظام عند استخدام نسيج الرأس.

+ +

[property:DataTexture boneTexture]

+

+ [page:DataTexture] التي تحمل بيانات العظام عند استخدام نسيج الرأس. +

+ +

الطرق (Methods)

+ +

[method:Skeleton clone]()

+

إرجاع نسخة من هذا كائن Skeleton.

+ +

[method:undefined calculateInverses]()

+

+ توليد مصفوفة [page:.boneInverses boneInverses] إذا لم يتم توفيرها في + المُنشئ. +

+ +

[method:this computeBoneTexture]()

+

+ حساب نسخة من [page:DataTexture] لتمرير بيانات العظام + بشكل أكثر كفاءة إلى المُظلل. يتم تعيين النسيج إلى + [page:.boneTexture boneTexture]. +

+ +

[method:undefined pose]()

+

إعادة الهيكل العظمي إلى وضعية الأساس.

+ +

[method:undefined update]()

+

+ تحديث [page:Float32Array boneMatrices] و [page:DataTexture boneTexture] + بعد تغيير العظام. يتم استدعاء هذا تلقائيًا بواسطة + [page:WebGLRenderer] إذا تم استخدام الهيكل العظمي مع [page:SkinnedMesh]. +

+ +

[method:Bone getBoneByName]( [param:String name] )

+

+ name -- سلسلة لتطابق خاصية .name لـ Bone.

+ + البحث في مصفوفة عظام الهيكل العظمي وإرجاع الأولى التي تطابق + الاسم.
+

+ +

[method:undefined dispose]()

+

+ تحرير الموارد المتعلقة بوحدة معالجة الرسومات المخصصة من قبل هذه النسخة. استدعِ هذه + الطريقة كلما لم يتم استخدام هذه النسخة في تطبيقك. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/objects/SkinnedMesh.html b/docs/api/ar/objects/SkinnedMesh.html new file mode 100644 index 00000000000000..4869dc07a4e509 --- /dev/null +++ b/docs/api/ar/objects/SkinnedMesh.html @@ -0,0 +1,186 @@ + + + + + + + + + + [page:Object3D] → [page:Mesh] → + +

[name]

+ +

+ شبكة لديها [page:Skeleton] مع [page:Bone bones] يمكن استخدامها بعد ذلك + لتحريك رؤوس الهندسة. +

+ + + + + +

مثال للكود

+ + + const geometry = new THREE.CylinderGeometry( 5, 5, 5, 5, 15, 5, 30 ); + + // create the skin indices and skin weights manually + // (typically a loader would read this data from a 3D model for you) + + const position = geometry.attributes.position; + + const vertex = new THREE.Vector3(); + + const skinIndices = []; + const skinWeights = []; + + for ( let i = 0; i < position.count; i ++ ) { + + vertex.fromBufferAttribute( position, i ); + + // compute skinIndex and skinWeight based on some configuration data + const y = ( vertex.y + sizing.halfHeight ); + const skinIndex = Math.floor( y / sizing.segmentHeight ); + const skinWeight = ( y % sizing.segmentHeight ) / sizing.segmentHeight; + skinIndices.push( skinIndex, skinIndex + 1, 0, 0 ); + skinWeights.push( 1 - skinWeight, skinWeight, 0, 0 ); + } + + geometry.setAttribute( 'skinIndex', new THREE.Uint16BufferAttribute( skinIndices, 4 ) ); + geometry.setAttribute( 'skinWeight', new THREE.Float32BufferAttribute( skinWeights, 4 ) ); + + // create skinned mesh and skeleton + + const mesh = new THREE.SkinnedMesh( geometry, material ); + const skeleton = new THREE.Skeleton( bones ); + + // see example from THREE.Skeleton + const rootBone = skeleton.bones[ 0 ]; + mesh.add( rootBone ); + + // bind the skeleton to the mesh + mesh.bind( skeleton ); + + // move the bones and manipulate the model + skeleton.bones[ 0 ].rotation.x = -0.1; + skeleton.bones[ 1 ].rotation.x = 0.2; + + +

المنشئ (Constructor)

+

+ [name]( [param:BufferGeometry geometry], [param:Material material] ) +

+

+ [page:BufferGeometry geometry] - نسخة من [page:BufferGeometry].
+ [page:Material material] - (اختياري) نسخة من [page:Material]. + الافتراضي هو [page:MeshBasicMaterial] جديد. +

+ +

الخصائص (Properties)

+

انظر إلى الفئة الأساسية [page:Mesh] للحصول على الخصائص المشتركة.

+ +

[property:String bindMode]

+

+ Either `AttachedBindMode` or `DetachedBindMode`. `AttachedBindMode` means the skinned mesh + shares the same world space as the skeleton. This is not true when using `DetachedBindMode` + which is useful when sharing a skeleton across multiple skinned meshes. + Default is `AttachedBindMode`. +

+ +

[property:Matrix4 bindMatrix]

+

المصفوفة الأساسية المستخدمة لتحويلات العظام المربوطة.

+ +

[property:Matrix4 bindMatrixInverse]

+

المصفوفة الأساسية المستخدمة لإعادة تعيين تحويلات العظام المربوطة.

+ +

[property:Box3 boundingBox]

+

+ مربع التحديد لـ [name]. يمكن حسابه بـ + [page:.computeBoundingBox](). الافتراضي هو `null`. +

+ +

[property:Sphere boundingSphere]

+

+ الكرة المحيطة لـ [name]. يمكن حسابها بـ + [page:.computeBoundingSphere](). الافتراضي هو `null`. +

+ +

[property:Boolean isSkinnedMesh]

+

علامة للقراءة فقط للتحقق مما إذا كان كائن معين هو من نوع [name].

+ +

[property:Skeleton skeleton]

+

[page:Skeleton] يمثل هيكل العظام للشبكة المشدودة.

+ +

الطرق (Methods)

+

انظر إلى الفئة الأساسية [page:Mesh] للحصول على الطرق المشتركة.

+ +

+ [method:undefined bind]( [param:Skeleton skeleton], [param:Matrix4 bindMatrix] ) +

+

+ [page:Skeleton skeleton] - [page:Skeleton] تم إنشاؤه من شجرة [page:Bone Bones].
+ [page:Matrix4 bindMatrix] - [page:Matrix4] يمثل التحويل الأساسي + للهيكل العظمي.

+ + ربط هيكل عظمي بالشبكة المشدودة. يتم حفظ bindMatrix في + خاصية .bindMatrix ويتم حساب .bindMatrixInverse. +

+ +

[method:SkinnedMesh clone]()

+

+ لا تقوم هذه الطريقة حاليًا بنسخ نسخة من [name] بشكل صحيح. + يرجى استخدام [page:SkeletonUtils.clone]() في هذه الأثناء. +

+ +

[method:undefined computeBoundingBox]()

+

+ حساب مربع التحديد، وتحديث خاصية [page:.boundingBox].
+ لا يتم حساب مربعات التحديد افتراضيًا. يجب حسابها بشكل صريح + ، وإلا كانت `null`. إذا كانت نسخة من [name] متحركة، + يجب استدعاء هذه الطريقة في كل إطار لحساب مربع تحديد صحيح. +

+ +

[method:undefined computeBoundingSphere]()

+

+ حساب الكرة المحيطة، وتحديث خاصية [page:.boundingSphere] + .
+ لا يتم حساب الكرات المحيطة افتراضيًا. يجب حسابها بشكل صريح + ، وإلا كانت `null`. إذا كانت نسخة من [name] متحركة، + يجب استدعاء هذه الطريقة في كل إطار لحساب كرة محيطة صحيحة. +

+ +

[method:undefined normalizeSkinWeights]()

+

تطبيع أوزان الجلد.

+ +

[method:undefined pose]()

+

هذه الطريقة تضع الشبكة المشدودة في وضعية الأساس (إعادة تعيين الوضعية).

+ +

+ [method:Vector3 applyBoneTransform]( [param:Integer index], [param:Vector3 vector] ) +

+

+ تطبق تحويل العظام المرتبط بالفهرس المعطى على موضع المتجه المعطى. يُعاد المتجه المُحدَّث. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/objects/Sprite.html b/docs/api/ar/objects/Sprite.html new file mode 100644 index 00000000000000..844e71ea783e0a --- /dev/null +++ b/docs/api/ar/objects/Sprite.html @@ -0,0 +1,84 @@ + + + + + + + + + + [page:Object3D] → + +

[name]

+ +

+ الرذاذ هو طائرة تواجه دائمًا نحو الكاميرا، عمومًا مع + تطبيق نسيج شفاف جزئيًا.

+ + الرذاذ لا يلقي ظلالًا، وضبط castShadow = true سوف + لا يكون له أي تأثير. +

+ +

مثال للكود

+ + + const map = new THREE.TextureLoader().load( 'sprite.png' ); + const material = new THREE.SpriteMaterial( { map: map } ); + + const sprite = new THREE.Sprite( material ); + scene.add( sprite ); + + +

المنشئ (Constructor)

+ +

[name]( [param:Material material] )

+

+ [page:Material material] - (اختياري) نسخة من + [page:SpriteMaterial]. الافتراضي هو [page:SpriteMaterial] أبيض.

+ + إنشاء [name] جديد. +

+ +

الخصائص (Properties)

+

انظر إلى الفئة الأساسية [page:Object3D] للحصول على الخصائص المشتركة.

+ +

[property:Boolean isSprite]

+

علامة للقراءة فقط للتحقق مما إذا كان كائن معين هو من نوع [name].

+ +

[property:SpriteMaterial material]

+

+ نسخة من [page:SpriteMaterial]، تحدد مظهر الكائن. + الافتراضي هو [page:SpriteMaterial] أبيض. +

+ +

[property:Vector2 center]

+

+ نقطة مرساة الرذاذ، والنقطة التي يدور حولها الرذاذ. + قيمة (0.5، 0.5) تتوافق مع منتصف الرذاذ. قيمة + من (0، 0) يتوافق مع الزاوية السفلى اليسرى من الرذاذ. الافتراضي + هو (0.5، 0.5). +

+ +

الطرق (Methods)

+

انظر إلى الفئة الأساسية [page:Object3D] للحصول على الطرق المشتركة.

+ +

[method:this copy]( [param:Sprite sprite] )

+

نسخ خصائص رذاذ المارة إلى هذه واحدة.

+ +

+ [method:undefined raycast]( [param:Raycaster raycaster], [param:Array intersects] ) +

+

+ الحصول على تقاطعات بين شعاع ملقى وهذه الرذاذ. + سيتم استدعاء هذه الطريقة بواسطة [page:Raycaster.intersectObject](). يجب تهيئة raycaster + بواسطة استدعاء [page:Raycaster.setFromCamera]() قبل + raycasting ضد sprites. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/renderers/WebGL3DRenderTarget.html b/docs/api/ar/renderers/WebGL3DRenderTarget.html new file mode 100644 index 00000000000000..a121aefc02a27e --- /dev/null +++ b/docs/api/ar/renderers/WebGL3DRenderTarget.html @@ -0,0 +1,51 @@ + + + + + + + + + + [page:WebGLRenderTarget] → + +

[name]

+ +

يمثل هدف عرض ثلاثي الأبعاد.

+ +

المنشئ (Constructor)

+ +

+ [name]( [param:Number width], [param:Number height], [param:Number depth], [param:Object options] ) +

+

+ [page:Number width] - عرض هدف العرض ، بالبكسل. الافتراضي + هو `1`.
+ [page:Number height] - ارتفاع هدف العرض ، بالبكسل. الافتراضي + هو `1`.
+ [page:Number depth] - عمق هدف العرض. الافتراضي هو `1`.

+ + ينشئ [name] جديدًا. +

+ +

الخصائص (Properties)

+

انظر [page:WebGLRenderTarget] للخصائص الموروثة.

+ +

[property:number depth]

+

عمق هدف العرض.

+ +

[property:Data3DTexture texture]

+

+ يتم استبدال خاصية القوام بمثيل من + [page:Data3DTexture]. +

+ +

الطرق (Methods)

+

انظر [page:WebGLRenderTarget] للطرق الموروثة.

+ +

المصدر (Source)

+

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/renderers/WebGLArrayRenderTarget.html b/docs/api/ar/renderers/WebGLArrayRenderTarget.html new file mode 100644 index 00000000000000..68971a07bc599e --- /dev/null +++ b/docs/api/ar/renderers/WebGLArrayRenderTarget.html @@ -0,0 +1,61 @@ + + + + + + + + + + [page:WebGLRenderTarget] → + +

[name]

+ +

+ هذا النوع من أهداف العرض يمثل مجموعة من القوام. +

+ +

أمثلة (Examples)

+ +

+ [example:webgl_rendertarget_texture2darray WebGL / render target / array]
+

+ +

المنشئ (Constructor)

+ +

+ [name]( [param:Number width], [param:Number height], [param:Number depth], [param:Object options] ) +

+

+ [page:Number width] - عرض هدف العرض ، بالبكسل. الافتراضي + هو `1`.
+ [page:Number height] - ارتفاع هدف العرض ، بالبكسل. الافتراضي + هو `1`.
+ [page:Number depth] - عمق/عدد الطبقات لهدف العرض. الافتراضي + هو `1`.

+ + ينشئ [name] جديدًا. +

+ +

الخصائص (Properties)

+ +

انظر [page:WebGLRenderTarget] للخصائص الموروثة.

+ +

[property:number depth]

+

عمق هدف العرض.

+ +

[property:DataArrayTexture texture]

+

+ يتم استبدال خاصية القوام بمثيل من + [page:DataArrayTexture]. +

+ +

الطرق (Methods)

+

انظر [page:WebGLRenderTarget] للطرق الموروثة.

+ +

المصدر (Source)

+

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/renderers/WebGLCubeRenderTarget.html b/docs/api/ar/renderers/WebGLCubeRenderTarget.html new file mode 100644 index 00000000000000..da9f3ec33c85f3 --- /dev/null +++ b/docs/api/ar/renderers/WebGLCubeRenderTarget.html @@ -0,0 +1,90 @@ + + + + + + + + + + [page:WebGLRenderTarget] → + +

[name]

+ +

+ يستخدمه [page:CubeCamera] كـ [page:WebGLRenderTarget]. +

+ +

أمثلة (Examples)

+ +

انظر [page:CubeCamera] للحصول على أمثلة.

+ +

المنشئ (Constructor)

+ +

[name]([param:Number size], [param:Object options])

+

+ [page:Float size] - الحجم ، بالبكسل. الافتراضي هو `1`.
+ options - (اختياري) كائن يحمل معلمات القوام لـ + قوام الهدف المُنشأ تلقائيًا وعمق المخزن/مخططات المخزن booleans. لـ + شرح معلمات القوام انظر [page:Texture Texture]. التالي هي خيارات صالحة:

+ + [page:Constant wrapS] - الافتراضي هو [page:Textures ClampToEdgeWrapping]. +
+ [page:Constant wrapT] - الافتراضي هو [page:Textures ClampToEdgeWrapping]. +
+ [page:Constant magFilter] - الافتراضي هو [page:Textures .LinearFilter]. +
+ [page:Constant minFilter] - الافتراضي هو [page:Textures LinearFilter]. +
+ [page:Boolean generateMipmaps] - الافتراضي هو `false`.
+ [page:Constant format] - الافتراضي هو [page:Textures RGBAFormat].
+ [page:Constant type] - الافتراضي هو [page:Textures UnsignedByteType].
+ [page:Number anisotropy] - الافتراضي هو `1`. انظر + [page:Texture.anisotropy]
+ [page:Constant colorSpace] - الافتراضي هو [page:Textures NoColorSpace]. +
+ [page:Boolean depthBuffer] - الافتراضي هو `true`.
+ [page:Boolean stencilBuffer] - الافتراضي هو `false`.

+ + ينشئ [name] جديدًا +

+ + +

الخصائص (Properties)

+ +

انظر [page:WebGLRenderTarget] للخصائص الموروثة.

+ +

الطرق (Methods)

+ +

انظر [page:WebGLRenderTarget] للطرق الموروثة.

+ +

+ [method:this fromEquirectangularTexture]( [param:WebGLRenderer renderer], [param:Texture texture] ) +

+

+ [page:WebGLRenderer renderer] — المُعالج.
+ [page:Texture texture] — القوام الإستوائي المستطيل. +

+

+ استخدم هذه الطريقة إذا كنت ترغب في تحويل بانوراما إستوائية مستطيلة إلى + تنسيق cubemap. +

+ +

+ [method:undefined clear]( [param:WebGLRenderer renderer], [param:Boolean color], + [param:Boolean depth], [param:Boolean stencil] ) +

+

+ اتصل بهذا لمسح لون هدف العرض وعمقه و/أو مخططات + المخزنات. يتم تعيين مخزن اللون على لون المسح الحالي للمُعالج. + المعاملات الافتراضية هي `true`. +

+ + +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/renderers/WebGLRenderTarget.html b/docs/api/ar/renderers/WebGLRenderTarget.html new file mode 100644 index 00000000000000..23f057b604887f --- /dev/null +++ b/docs/api/ar/renderers/WebGLRenderTarget.html @@ -0,0 +1,147 @@ + + + + + + + + + +

[name]

+ +

+ A + [link:https://webglfundamentals.org/webgl/lessons/webgl-render-to-texture.html render target] + هو مخزن حيث ترسم بطاقة الفيديو بكسل لمشهد + يتم تجسيده في الخلفية. يستخدم في تأثيرات مختلفة ، + مثل تطبيق المعالجة اللاحقة على صورة معروضة قبل عرضها + على الشاشة. +

+ +

المنشئ (Constructor)

+ +

+ [name]([param:Number width], [param:Number height], [param:Object options]) +

+ +

+ [page:Float width] - عرض renderTarget. الافتراضي هو `1`.
+ [page:Float height] - ارتفاع renderTarget. الافتراضي هو `1`.
+ الخيارات - كائن اختياري يحمل معلمات القوام لـ + قوام الهدف التلقائي المتولد وعمق المخزن / مخطط قالب booleans. لـ + شرح معلمات القوام انظر [page:Texture Texture]. التالية هي خيارات صالحة:

+ + [page:Constant wrapS] - الافتراضي هو [page:Textures ClampToEdgeWrapping]. +
+ [page:Constant wrapT] - الافتراضي هو [page:Textures ClampToEdgeWrapping]. +
+ [page:Constant magFilter] - الافتراضي هو [page:Textures LinearFilter]. +
+ [page:Constant minFilter] - الافتراضي هو [page:Textures LinearFilter]. +
+ [page:Boolean generateMipmaps] - الافتراضي هو `false`.
+ [page:Constant format] - الافتراضي هو [page:Textures RGBAFormat].
+ [page:Constant type] - الافتراضي هو [page:Textures UnsignedByteType].
+ [page:Number anisotropy] - الافتراضي هو `1`. انظر + [page:Texture.anisotropy]
+ [page:Constant colorSpace] - الافتراضي هو [page:Textures NoColorSpace]. +
+ [page:String internalFormat] - الافتراضي هو `null`.
+ [page:Boolean depthBuffer] - الافتراضي هو `true`.
+ [page:Boolean stencilBuffer] - الافتراضي هو `false`.
+ [page:Boolean resolveDepthBuffer] - الافتراضي هو `true`.
+ [page:Boolean resolveStencilBuffer] - الافتراضي هو `true`.
+ [page:Number samples] - الافتراضي هو 0.

+ + ينشئ جديدًا [name] +

+ +

الخصائص (Properties)

+ +

[property:Boolean isWebGLRenderTarget]

+

علامة للقراءة فقط للتحقق مما إذا كان كائنًا معطىً من نوع [name].

+ +

[property:number width]

+

عرض هدف التجسيم.

+ +

[property:number height]

+

ارتفاع هدف التجسيم.

+ +

[property:Vector4 scissor]

+

+ منطقة مستطيلة داخل منفذ عرض render target. سيتم تجاهل الشظايا التي + خارج المنطقة. +

+ +

[property:Boolean scissorTest]

+ +

[property:Vector4 viewport]

+

منفذ العرض الخاص بهذا الهدف التجسيم.

+ +

[property:Texture texture]

+

+ تحتوي هذه النسخة من القوام على البكسلات المعروضة. استخدمه كإدخال لـ + معالجة إضافية. +

+ +

[property:Boolean depthBuffer]

+

يعرض على مخزن العمق. الافتراضي هو true.

+ +

[property:Boolean stencilBuffer]

+

يعرض على مخزن القالب. الافتراضي هو false.

+ +

[property:Boolean resolveDepthBuffer]

+

+ Defines whether the depth buffer should be resolved when rendering into a multisampled render target. + Default is `true`. +

+ +

[property:Boolean resolveStencilBuffer]

+

+ Defines whether the stencil buffer should be resolved when rendering into a multisampled render target. + This property has no effect when [page:.resolveDepthBuffer] is set to `false`. + Default is `true`. +

+ +

[property:DepthTexture depthTexture]

+

+ إذا تم تعيينه ، سيتم عرض عمق المشهد على هذه القوام. الافتراضي هو null. +

+ +

[property:Number samples]

+

+ يحدد عدد عينات MSAA. يمكن استخدامه فقط مع WebGL 2. الافتراضي + هو `0`. +

+ +

الطرق (Methods)

+ +

+ [method:undefined setSize]( [param:Number width], [param:Number height] ) +

+

يحدد حجم هدف التجسيم.

+ +

[method:WebGLRenderTarget clone]()

+

ينشئ نسخة من هذا الهدف التجسيم.

+ +

[method:this copy]( [param:WebGLRenderTarget source] )

+

يتبنى إعدادات الهدف التجسيم المعطى.

+ +

[method:undefined dispose]()

+

+ يحرر الموارد المتعلقة بوحدة معالجة الرسومات المخصصة من قبل هذه الحالة. اتصل بهذا + طريقة كلما لم يعد يستخدم هذا المثيل في تطبيقك. +

+ +

+ [page:EventDispatcher EventDispatcher] الطرق متاحة على هذا + فئة. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/renderers/WebGLRenderer.html b/docs/api/ar/renderers/WebGLRenderer.html new file mode 100644 index 00000000000000..12891aefa83361 --- /dev/null +++ b/docs/api/ar/renderers/WebGLRenderer.html @@ -0,0 +1,648 @@ + + + + + + + + + +

[name]

+ +

+ يعرض مقدم WebGL مشاهدك المصنوعة بشكل جميل باستخدام + [link:https://en.wikipedia.org/wiki/WebGL WebGL]. +

+ +

المنشئ (Constructor)

+ +

[name]( [param:Object parameters] )

+

+ [page:Object parameters] - (اختياري) كائن بخصائص تحدد سلوك المقدم. + يقبل المنشئ أيضًا عدم وجود معلمات على الإطلاق. + في جميع الحالات ، سيفترض الافتراضات السليمة عندما تكون المعلمات مفقودة. + التالية هي المعلمات الصالحة:

+ + [page:DOMElement canvas] - A + [link:https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas canvas] + حيث يرسم المقدم ناتجه. يتوافق هذا مع + [page:WebGLRenderer.domElement domElement] property below. إذا لم يتم تمريره + هنا ، سيتم إنشاء عنصر قماش جديد.
+ + [page:WebGLRenderingContext context] - يمكن استخدام هذا لإرفاق + المقدم بـ + [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext RenderingContext] موجود. + الافتراضي هو null.
+ + [page:String precision] - دقة الشادر. يمكن أن يكون `"highp"` ، `"mediump"` + أو `"lowp"`. الافتراضي هو `"highp"` إذا كان مدعومًا من قبل الجهاز.
+ + [page:Boolean alpha] - يتحكم في قيمة alpha الافتراضية الواضحة. عند تعيينه على + `true` ، تكون القيمة `0`. وإلا فهو `1`. الافتراضي هو `false`.
+ + [page:Boolean premultipliedAlpha] - ما إذا كان المقدم سيفترض أن + الألوان لديها + [link:https://en.wikipedia.org/wiki/Glossary_of_computer_graphics#Premultiplied_alpha premultiplied alpha]. + الافتراضي هو `true`.
+ + [page:Boolean antialias] - ما إذا كان سيتم إجراء التنعيم. الافتراضي هو + `false`.
+ + [page:Boolean stencil] - ما إذا كانت ذاكرة التخزين المؤقت للرسم لديها + [link:https://en.wikipedia.org/wiki/Stencil_buffer stencil buffer] من على + الأقل 8 بت. الافتراضي هو `false`.
+ + [page:Boolean preserveDrawingBuffer] - ما إذا كان سيتم الحفاظ على المخابئ + حتى يتم مسحها يدويًا أو استبدالها. الافتراضي هو `false`.
+ + [page:String powerPreference] - يوفر تلميحًا لوكيل المستخدم + تشير إلى ما هو التكوين من GPU مناسب لهذه سياق WebGL. + يمكن أن يكون `"high-performance"` ، `"low-power"` أو `"default"`. الافتراضي هو + `"default"`. انظر + [link:https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.2 WebGL spec] للحصول على التفاصيل.
+ + [page:Boolean failIfMajorPerformanceCaveat] - ما إذا كان إنشاء المقدم + سيفشل عند اكتشاف أداء منخفض. الافتراضي هو `false`. + انظر [link:https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.2 WebGL spec] للحصول على التفاصيل.
+ + [page:Boolean depth] - ما إذا كانت ذاكرة التخزين المؤقت للرسم لديها + [link:https://en.wikipedia.org/wiki/Z-buffering مخزن عمق] من على الأقل + 16 بت. الافتراضي هو `true`.
+ + [page:Boolean logarithmicDepthBuffer] - ما إذا كان سيتم استخدام مخزن عمق لوغاريتمي + قد يكون من الضروري استخدام هذا إذا كان يتعامل مع اختلافات ضخمة + في المقياس في مشهد واحد. لاحظ أن هذا الإعداد يستخدم gl_FragDepth إذا + متوفرة والتي تعطل + [link:https://www.khronos.org/opengl/wiki/Early_Fragment_Test Early Fragment Test] + التحسين ويمكن أن يسبب انخفاضًا في الأداء. + الافتراضي هو `false`. انظر [example:webgl_camera_logarithmicdepthbuffer camera / logarithmicdepthbuffer] المثال. +

+ +

الخصائص (Properties)

+ +

[property:Boolean autoClear]

+

+ يحدد ما إذا كان المُعالج يجب أن يمسح تلقائيًا ناتجه قبل + عرض الإطار. +

+ +

[property:Boolean autoClearColor]

+

+ إذا كان [page:.autoClear autoClear] صحيحًا ، يحدد ما إذا كان المُعالج + يجب مسح مخزن اللون. الافتراضي هو `true`. +

+ +

[property:Boolean autoClearDepth]

+

+ إذا كان [page:.autoClear autoClear] صحيحًا ، يحدد ما إذا كان المُعالج + يجب مسح مخزن العمق. الافتراضي هو `true`. +

+ +

[property:Boolean autoClearStencil]

+

+ إذا كان [page:.autoClear autoClear] صحيحًا ، يحدد ما إذا كان المُعالج + يجب مسح مخزن القالب. الافتراضي هو `true`. +

+ +

[property:Object debug]

+

+ - [page:Boolean checkShaderErrors]: إذا كان صحيحًا ، يحدد ما إذا كان + يتم التحقق من برامج الشادر المادية للأخطاء أثناء التجميع و + عملية الربط. قد يكون من المفيد تعطيل هذا الفحص في الإنتاج لـ + الحصول على مكاسب في الأداء. يوصى بشدة بالاحتفاظ بهذه الفحوصات ممكّنة + أثناء التطوير. إذا لم يتم تجميع وربط الشادر - فلن يعمل + والمادة المرتبطة لن تعرض. الافتراضي هو `true`.
+ - [page:Function onShaderError]( gl, program, glVertexShader, + glFragmentShader ): وظيفة رد اتصال يمكن استخدامها للإبلاغ عن الأخطاء المخصصة + التقارير. يتلقى الرد الاتصال سياق WebGL ، ومثيل من + WebGLProgram كذلك اثنين من مثيلات WebGLShader تمثل قمة + وشادر الجزء. تعيين وظيفة مخصصة يعطل الافتراضي + التقارير عن الأخطاء. الافتراضي هو `null`. +

+ +

[property:Object capabilities]

+

+ كائن يحتوي على تفاصيل حول قدرات الحالية + [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext RenderingContext].
+ + - [page:Boolean floatFragmentTextures]: ما إذا كان السياق يدعم + [link:https://developer.mozilla.org/en-US/docs/Web/API/OES_texture_float OES_texture_float] extension.
+ - [page:Boolean floatVertexTextures]: `true` إذا كان [page:Boolean floatFragmentTextures] + و [page:Boolean vertexTextures] صحيحان على حد سواء.
+ - [page:Method getMaxAnisotropy](): يعود بأقصى قدر متاح + التشوه.
+ - [page:Method getMaxPrecision](): يعود بأقصى دقة متاحة + لشادرات القمة والجزء.
+ - [page:Boolean isWebGL2]: `true` إذا كان السياق في استخدام هو a + WebGL2RenderingContext object.
+ - [page:Boolean logarithmicDepthBuffer]: `true` إذا كان [page:parameter logarithmicDepthBuffer] + تم تعيينه على true في المُنشئ والسياق + يدعم + [link:https://developer.mozilla.org/en-US/docs/Web/API/EXT_frag_depth EXT_frag_depth] extension.
+ - [page:Integer maxAttributes]: قيمة `gl.MAX_VERTEX_ATTRIBS`.
+ - [page:Integer maxCubemapSize]: قيمة + `gl.MAX_CUBE_MAP_TEXTURE_SIZE`. أقصى ارتفاع * عرض لخرائط المكعبات + الملمسات التي يمكن استخدامها من قبل شادر.
+ - [page:Integer maxFragmentUniforms]: قيمة + `gl.MAX_FRAGMENT_UNIFORM_VECTORS`. عدد الموحدات التي يمكن استخدامها + من قبل شادر جزء.
+ - [page:Integer maxSamples]: قيمة `gl.MAX_SAMPLES`. أقصى عدد + من العينات في سياق مكافحة التعرج المتعدد (MSAA).
+ - [page:Integer maxTextureSize]: قيمة `gl.MAX_TEXTURE_SIZE`. + أقصى ارتفاع * عرض للقوام التي يستخدمها شادر.
+ - [page:Integer maxTextures]: قيمة `gl.MAX_TEXTURE_IMAGE_UNITS`. + أقصى عدد من الملمسات التي يمكن استخدامها من قبل شادر.
+ - [page:Integer maxVaryings]: قيمة `gl.MAX_VARYING_VECTORS`. The + عدد المتغيرات المتغيرة التي يمكن استخدامها من قبل shaders.
+ - [page:Integer maxVertexTextures]: قيمة + `gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS`. عدد الملمسات الت + يمكن استخدامها في شادر القمة.
+ - [page:Integer maxVertexUniforms]: قيمة + `gl.MAX_VERTEX_UNIFORM_VECTORS`. أقصى عدد من الموحدات التي يمكن + يتم استخدامها في شادر القمة.
+ - [page:String precision]: دقة الشادر التي يتم استخدامها حاليًا من قبل + المُعالج.
+ - [page:Boolean vertexTextures]: `true` إذا كان [property:Integer maxVertexTextures] + أكبر من 0 (أي يمكن استخدام ملمسات القمة).
+

+ +

[property:Array clippingPlanes]

+

+ خطوط المقص المحددة من قبل المستخدم محددة كـ THREE.Plane objects في العالم + الفضاء. تطبق هذه الطائرات عالميًا. النقاط في الفضاء التي نقطة المنتج مع + الطائرة هي سلبية يتم قطعها. الافتراضي هو []. +

+ +

[property:DOMElement domElement]

+

+ [link:https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas canvas] + حيث يرسم المُعالج ناتجه.
+ يتم إنشاء هذا تلقائيًا من قبل المُعالج في المُنشئ (إذا لم يكن + متوفر بالفعل) ؛ تحتاج فقط إلى إضافته إلى صفحتك مثل هذا:
+ + document.body.appendChild( renderer.domElement ); + +

+ +

[property:Object extensions]

+

+ - [page:Object get]( [param:String extensionName] ): يستخدم للتحقق مما إذا كان + مختلف الامتدادات مدعومة ويعود كائنًا بتفاصيل + التمديد إذا كان متوفرًا. يمكن لهذه الطريقة التحقق من التالية + extensions:
+

+ +
    +
  • `WEBGL_depth_texture`
  • +
  • `EXT_texture_filter_anisotropic`
  • +
  • `WEBGL_compressed_texture_s3tc`
  • +
  • `WEBGL_compressed_texture_pvrtc`
  • +
  • `WEBGL_compressed_texture_etc1`
  • +
+ +

[property:string outputColorSpace]

+

+ يحدد مساحة اللون الناتجة للمُعالج. الافتراضي هو [page:Textures THREE.SRGBColorSpace]. +

+

+ إذا تم تعيين هدف عرض باستخدام [page:WebGLRenderer.setRenderTarget .setRenderTarget] + ثم سيتم استخدام renderTarget.texture.colorSpace بدلاً من ذلك. +

+

+ انظر صفحة [page:Textures texture constants] للحصول على تفاصيل أخرى + صيغ. +

+ + +

[property:Object info]

+

+ كائن مع سلسلة من المعلومات الإحصائية حول ذاكرة لوحة الرسومات + عملية العرض. مفيد للتصحيح أو فقط لأجل + الفضول. يحتوي الكائن على الحقول التالية: +

+
    +
  • + ذاكرة: +
      +
    • الهندسة
    • +
    • القوام
    • +
    +
  • +
  • + عرض: +
      +
    • المكالمات
    • +
    • المثلثات
    • +
    • النقاط
    • +
    • خطوط
    • +
    • إطار
    • +
    +
  • +
  • برامج
  • +
+

+ بشكل افتراضي ، يتم إعادة تعيين هذه البيانات في كل مكالمة عرض ولكن عندما + مرورات عرض متعددة لكل إطار (على سبيل المثال عند استخدام معالجة ما بعد) يمكن + يفضل إعادة التعيين بنمط مخصص. أولاً ، قم بتعيين `autoReset` إلى + `false`. + renderer.info.autoReset = false; + استدعاء `reset()` كلما انتهيت من عرض إطار واحد. + renderer.info.reset(); +

+ +

[property:Boolean localClippingEnabled]

+

+ يحدد ما إذا كان المُعالج يحترم خطوط المقص على مستوى الكائن. + الافتراضي هو `false`. +

+ +

[property:Object properties]

+

+ يستخدم داخليًا من قبل المُعالج لتتبع خصائص الكائنات الفرعية المختلفة + خصائص. +

+ +

[property:WebGLRenderLists renderLists]

+

يستخدم داخليًا للتعامل مع ترتيب عرض كائنات المشهد.

+ +

[property:WebGLShadowMap shadowMap]

+

+ يحتوي هذا على المرجع إلى خريطة الظل ، إذا تم استخدامها.
+ - [page:Boolean enabled]: إذا تم تعيينه ، استخدم خرائط الظل في المشهد. الافتراضي هو + `false`.
+ - [page:Boolean autoUpdate]: يتيح التحديثات التلقائية للظلال في + المشهد. الافتراضي هو `true`.
+ إذا لم تكن بحاجة إلى إضاءة / ظلال ديناميكية ، فيمكنك تعيين هذا على + `false` عندما يتم تجزئة المُعالج.
+ - [page:Boolean needsUpdate]: عند تعيينه على `true` ، ستتم تحديث خرائط الظل في المشهد + سيتم التحديث في الاتصال `render` التالي. الافتراضي هو `false`.
+ إذا قمت بتعطيل التحديثات التلقائية لخرائط الظل + (`shadowMap.autoUpdate = false`) ، ستحتاج إلى تعيين هذا على `true` + وبعد ذلك قم بإجراء مكالمة عرض لتحديث الظلال في مشهدك.
+ - [page:Integer type]: يحدد نوع خريطة الظل (غير مفلترة ، نسبة + تصفية قريبة ، نسبة تصفية قريبة مع التصفية الثنائية في + شادر). الخيارات هي: +

+
    +
  • THREE.BasicShadowMap
  • +
  • THREE.PCFShadowMap (افتراضي)
  • +
  • THREE.PCFSoftShadowMap
  • +
  • THREE.VSMShadowMap
  • +
+

انظر [page:Renderer Renderer constants] للحصول على التفاصيل.

+ +

[property:Boolean sortObjects]

+

+ يحدد ما إذا كان المُعالج يجب أن يقوم بفرز الكائنات. الافتراضي هو `true`.

+ + ملاحظة: يتم استخدام الترتيب لمحاولة عرض الكائنات بشكل صحيح التي لديها بعض + درجة من الشفافية. بالتعريف ، قد لا يعمل فرز الكائنات في جميع + حالات. اعتمادًا على احتياجات التطبيق ، قد يكون من الضروري إغلاق + الترتيب واستخدام طرق أخرى للتعامل مع عرض الشفافية مثل + تحديد ترتيب عرض كل كائن يدويًا. +

+ +

[property:Object state]

+

+ يحتوي على وظائف لضبط خصائص مختلفة من + [page:WebGLRenderer.context] حالة. +

+ +

[property:Constant toneMapping]

+

+ الافتراضي هو [page:Renderer NoToneMapping]. انظر [page:Renderer Renderer constants] للاختيارات الأخرى. +

+ +

[property:Number toneMappingExposure]

+

مستوى التعرض للإطارات. الافتراضي هو `1`.

+ +

[property:WebXRManager xr]

+

+ يوفر وصولًا إلى واجهة [page:WebXRManager] المتعلقة بـ WebXR من + المُعالج. +

+ +

الطُرق(Methods)

+ +

+ [method:undefined clear]( [param:Boolean color], [param:Boolean depth], [param:Boolean stencil] ) +

+

+ يخبر المُعالج بمسح مخزن الرسم الخاص به للون أو العمق أو المخطط. + هذه الطريقة تقوم بتهيئة مخزن اللون إلى قيمة المسح الحالية + قيمة.
+ الحجج الافتراضية هي `true`. +

+ +

[method:undefined clearColor]( )

+

+ مسح مخزن اللون. يعادل استدعاء [page:WebGLRenderer.clear .clear]( true, false, false ). +

+ +

[method:undefined clearDepth]( )

+

+ مسح مخزن العمق. يعادل استدعاء [page:WebGLRenderer.clear .clear]( false, true, false ). +

+ +

[method:undefined clearStencil]( )

+

+ مسح مخزنات المخططات. يعادل استدعاء [page:WebGLRenderer.clear .clear]( false, false, true ). +

+ +

+ [method:Set compile]( [param:Object3D scene], [param:Camera camera], [param:Scene targetScene] ) +

+

+ يجمع جميع المواد الموجودة في المشهد بالكاميرا. يعد هذا مفيدًا لتجميع التظليل مسبقًا قبل العرض الأول. + إذا كنت تريد إضافة كائن ثلاثي الأبعاد إلى مشهد موجود، فاستخدم المعلمة الاختيارية الثالثة لتطبيق المشهد المستهدف.
+ لاحظ أنه يجب تكوين إضاءة المشهد قبل استدعاء هذه الطريقة. +

+ +

+ [method:Promise compileAsync]( [param:Object3D scene], [param:Camera camera], [param:Scene targetScene] ) +

+

+ إصدار غير متزامن من [page:WebGLRenderer.compile .compile](). تقوم الطريقة بإرجاع Promise + الذي يتم حله عندما يمكن عرض الكائن أو المشهد ثلاثي الأبعاد المحدد دون توقف غير ضروري بسبب تجميع التظليل.

+ + تستخدم هذه الطريقة *KHR_parallel_shader_compile*. +

+ +

+ [method:undefined copyFramebufferToTexture]( [param:FramebufferTexture texture], [param:Vector2 position], [param:Number level] ) +

+

+ ينسخ بكسلات من WebGLFramebuffer الحالي إلى قوام ثنائي الأبعاد. يتيح + الوصول إلى + [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/copyTexImage2D WebGLRenderingContext.copyTexImage2D]. +

+ +

[method:undefined copyTextureToTexture]( [param:Texture srcTexture], [param:Texture dstTexture], [param:Box2 srcRegion], [param:Vector2 dstPosition], [param:Number level] )

+

+ Copies the pixels of a texture in the bounds '[page:Box2 srcRegion]' in the destination texture starting from the given position. + Enables access to [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/texSubImage2D WebGLRenderingContext.texSubImage2D]. +

+ +

[method:undefined copyTextureToTexture3D]( [param:Texture srcTexture], [param:Texture dstTexture], [param:Box3 srcRegion], [param:Vector3 dstPosition], [param:Number level] )

+

+ Copies the pixels of a texture in the bounds '[page:Box3 srcRegion]' in the destination texture starting from the given position. + Enables access to [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext/texSubImage3D WebGL2RenderingContext.texSubImage3D]. +

+ +

[method:undefined dispose]( )

+

+ يحرر الموارد المتعلقة بـ GPU التي تم تخصيصها من قبل هذا المثيل. استدعاء هذا + الطريقة كلما لم يعد هذا المثيل مستخدمًا في تطبيقك. +

+ +

[method:undefined forceContextLoss]()

+

+ محاكاة فقدان سياق WebGL. هذا يتطلب دعمًا لـ + [link:https://developer.mozilla.org/en-US/docs/Web/API/WEBGL_lose_context WEBGL_lose_context] extensions. +

+ +

[method:undefined forceContextRestore]( )

+

+ محاكاة استعادة سياق WebGL. هذا يتطلب دعمًا لـ + [link:https://developer.mozilla.org/en-US/docs/Web/API/WEBGL_lose_context WEBGL_lose_context] extensions. +

+ +

[method:Float getClearAlpha]()

+

+ يعود [page:Float float] مع الألفا الواضح الحالي. يتراوح من 0 + إلى 1. +

+ +

[method:Color getClearColor]( [param:Color target] )

+

+ يعود مثيل [page:Color THREE.Color] مع لون المسح الحالي. +

+ +

[method:WebGL2RenderingContext getContext]()

+

يرجع سياق WebGL الحالي.

+ +

[method:WebGLContextAttributes getContextAttributes]()

+

+ يعود كائنًا يصف السمات المضبوطة على سياق WebGL + عند إنشائه. +

+ +

[method:Integer getActiveCubeFace]()

+

يعيد الوجه المكعب النشط الحالي.

+ +

[method:Integer getActiveMipmapLevel]()

+

يعيد مستوى mipmap النشط الحالي.

+ +

[method:RenderTarget getRenderTarget]()

+

+ يعيد [page:RenderTarget RenderTarget] الحالي إذا كان هناك ؛ يعود + `null` خلاف ذلك. +

+ +

[method:Vector4 getCurrentViewport]( [param:Vector4 target] )

+

+ [page:Vector4 target] — سيتم نسخ النتيجة في هذا Vector4.

+ + يعود نافذة العرض الحالية. +

+ +

[method:Vector2 getDrawingBufferSize]( [param:Vector2 target] )

+

+ [page:Vector2 target] — سيتم نسخ النتيجة في هذا Vector2.

+ + يعود عرض وارتفاع مخزن الرسم للمُعالج ، بالبكسل. +

+ +

[method:number getPixelRatio]()

+

يعود نسبة بكسل الجهاز الحالية المستخدمة.

+ +

[method:Vector4 getScissor]( [param:Vector4 target] )

+

+ [page:Vector4 target] — سيتم نسخ النتيجة في هذا Vector4.

+ + يعود منطقة المقص. +

+ +

[method:Boolean getScissorTest]()

+

يعود `true` إذا تم تمكين اختبار المقص ؛ يعود `false` خلاف ذلك.

+ +

[method:Vector2 getSize]( [param:Vector2 target] )

+

+ [page:Vector2 target] — سيتم نسخ النتيجة في هذا Vector2.

+ + يعود عرض وارتفاع قماش إخراج المُعالج ، بالبكسل. +

+ +

[method:Vector4 getViewport]( [param:Vector4 target] )

+

+ [page:Vector4 target] — سيتم نسخ النتيجة في هذا Vector4.

+ + يعود نافذة العرض. +

+ +

[method:undefined initTexture]( [param:Texture texture] )

+

+ يقوم بتهيئة القوام المعطى. مفيد لتحميل القوام مسبقًا بدلاً من + الانتظار حتى العرض الأول (الذي يمكن أن يسبب تأخيرات ملحوظة بسبب فك تشفير + وتحميل GPU). +

+ +

[method:undefined resetGLState]( )

+

+ إعادة تعيين حالة GL إلى الافتراضي. يتم استدعاؤها داخليًا إذا كان سياق WebGL + ضائع. +

+ +

+ [method:undefined readRenderTargetPixels]( [param:WebGLRenderTarget renderTarget], [param:Float x], [param:Float y], [param:Float width], [param:Float height], [param:TypedArray buffer], [param:Integer activeCubeFaceIndex] ) +

+

+ buffer - Uint8Array هو النوع الوحيد المدعوم في جميع الحالات ، + أنواع أخرى هي renderTarget ومنصة مستقلة. انظر + [link:https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.12 WebGL spec] للحصول على التفاصيل. +

+

+ يقرأ بيانات البكسل من renderTarget إلى المخزن الذي تمرره. + هذا هو حول + [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/readPixels WebGLRenderingContext.readPixels](). +

+

+ انظر [example:webgl_interactive_cubes_gpu interactive / cubes / gpu] + مثال. +

+

+ لقراءة [page:WebGLCubeRenderTarget WebGLCubeRenderTarget] استخدم + المعلمة الاختيارية activeCubeFaceIndex لتحديد أي وجه يجب + قراءة. +

+ +

+ [method:undefined render]( [param:Object3D scene], [param:Camera camera] ) +

+

+ عرض [page:Scene scene] أو نوع آخر من [page:Object3D object] + باستخدام كاميرا [page:Camera].
+ + يتم العرض إلى [page:WebGLRenderTarget renderTarget] محدد مسبقًا + مجموعة من خلال استدعاء [page:WebGLRenderer.setRenderTarget .setRenderTarget] أو إلى قماش كالمعتاد.
+ + بشكل افتراضي ، يتم مسح مخزنات العرض قبل العرض ولكن يمكنك منع + هذا عن طريق تعيين خاصية [page:WebGLRenderer.autoClear autoClear] إلى + false. إذا كنت ترغب في منع مسح مخزنات معينة فقط ، فيمكنك + تعيين خصائص [page:WebGLRenderer.autoClearColor autoClearColor], + [page:WebGLRenderer.autoClearStencil autoClearStencil] أو + [page:WebGLRenderer.autoClearDepth autoClearDepth] إلى false. لإجبار مسح واحد أو أكثر من المخزنات اتصل بـ [page:WebGLRenderer.clear .clear]. +

+ +

[method:undefined resetState]()

+

+ يمكن استخدامها لإعادة تعيين حالة WebGL الداخلية. هذه الطريقة هي في الغالب + ذات صلة بالتطبيقات التي تشارك سياق WebGL واحد عبر + مكتبات WebGL متعددة. +

+ +

[method:undefined setAnimationLoop]( [param:Function callback] )

+

+ [page:Function callback] — سيتم استدعاء الوظيفة في كل إطار متاح + الإطار. إذا تم تمرير `null` ، فسيتوقف أي رسم متحرك قائم بالفعل. +

+

+ وظيفة مدمجة يمكن استخدامها بدلاً من + [link:https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame requestAnimationFrame]. + بالنسبة لمشاريع WebXR ، يجب استخدام هذه الوظيفة. +

+ +

[method:undefined setClearAlpha]( [param:Float alpha] )

+

يضبط الألفا الواضح. المدخلات الصالحة هي عدد عشري بين `0.0` و `1.0`.

+ +

+ [method:undefined setClearColor]( [param:Color color], [param:Float alpha] ) +

+

يضبط لون المسح والشفافية.

+ +

[method:undefined setPixelRatio]( [param:number value] )

+

+ يضبط نسبة بكسل الجهاز. يتم استخدام هذا عادةً لجهاز HiDPI لمنع + قماش الإخراج المشوش. +

+ +

+ [method:undefined setRenderTarget]( [param:WebGLRenderTarget renderTarget], + [param:Integer activeCubeFace], [param:Integer activeMipmapLevel] ) +

+

+ renderTarget -- [page:WebGLRenderTarget renderTarget] التي تحتاج إلى + تفعيله. عند إعطاء `null` ، يتم تعيين قماش كـ هدف عرض نشط + بدلاً من ذلك.
+ activeCubeFace -- يحدد جانب المكعب النشط (PX 0 ، NX 1 ، PY 2 ، NY 3 ، + PZ 4 ، NZ 5) من [page:WebGLCubeRenderTarget]. عند تمرير + [page:WebGLArrayRenderTarget] أو [page:WebGL3DRenderTarget] هذا يشير + إلى طبقة z للعرض فيه (اختياري).
+ activeMipmapLevel -- يحدد مستوى mipmap النشط (اختياري).

+ هذه الطريقة تضبط rendertarget النشط. +

+ +

+ [method:undefined setScissor]( [param:Integer x], [param:Integer y], [param:Integer width], [param:Integer height] )
+ [method:undefined setScissor]( [param:Vector4 vector] ) +

+ +

+ معلمات x و y والعرض والارتفاع لمنطقة المقص.
+ اختياريًا ، متجه مكون من 4 مكونات يحدد معلمات + المنطقة.

+ + يضبط منطقة المقص من (x ، y) إلى (x + width ، y + height).
+ (x ، y) هو الزاوية السفلى اليسرى لمنطقة المقص. +

+ +

[method:undefined setScissorTest]( [param:Boolean boolean] )

+

+ تمكين أو تعطيل اختبار المقص. عند تمكين هذا ، فقط البكسلات + داخل منطقة المقص المحددة ستتأثر بإجراءات المُعالج + التالية. +

+ +

[method:undefined setOpaqueSort]( [param:Function method] )

+

+ يضبط وظيفة الترتيب الشفاف المخصصة لـ WebGLRenderLists. امرر null + لاستخدام وظيفة painterSortStable الافتراضية. +

+ +

[method:undefined setTransparentSort]( [param:Function method] )

+

+ يضبط وظيفة الترتيب الشفاف المخصصة لـ WebGLRenderLists. امرر + null لاستخدام وظيفة reversePainterSortStable الافتراضية. +

+ +

+ [method:undefined setSize]( [param:Integer width], [param:Integer height], [param:Boolean updateStyle] ) +

+

+ يغير حجم قماش الإخراج إلى (العرض ، الارتفاع) مع نسبة بكسل الجهاز التي تؤخذ في + بعين الاعتبار ، ويضبط أيضًا نافذة العرض لتناسب هذا الحجم ، بدءًا من (0 ، + 0). يؤدي تعيين [page:Boolean updateStyle] إلى false إلى منع أي تغييرات في الأسلوب + إلى قماش الإخراج. +

+ +

+ [method:undefined setViewport]( [param:Integer x], [param:Integer y], [param:Integer width], [param:Integer height] )
+ [method:undefined setViewport]( [param:Vector4 vector] ) +

+ +

+ معلمات x و y والعرض والارتفاع لنافذة العرض.
+ اختياريًا ، متجه مكون من 4 مكونات يحدد معلمات + نافذة عرض.

+ + يضبط نافذة العرض للعرض من (x ، y) إلى (x + width ، y + height).
+ (x ، y) هو الزاوية السفلى اليسرى للمنطقة. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/renderers/shaders/ShaderChunk.html b/docs/api/ar/renderers/shaders/ShaderChunk.html new file mode 100644 index 00000000000000..1d97db14ad5132 --- /dev/null +++ b/docs/api/ar/renderers/shaders/ShaderChunk.html @@ -0,0 +1,24 @@ + + + + + + + + + +

[name]

+ +

قطع الشادر لمكتبة شادر WebGL

+ +

الخصائص (Properties)

+ +

الطرق (Methods)

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/renderers/shaders/ShaderLib.html b/docs/api/ar/renderers/shaders/ShaderLib.html new file mode 100644 index 00000000000000..0bac76675304e0 --- /dev/null +++ b/docs/api/ar/renderers/shaders/ShaderLib.html @@ -0,0 +1,24 @@ + + + + + + + + + +

[name]

+ +

مكتبة شادر WebGL لـ three.js

+ +

الخصائص (Properties)

+ +

الطرق (Methods)

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/renderers/shaders/UniformsLib.html b/docs/api/ar/renderers/shaders/UniformsLib.html new file mode 100644 index 00000000000000..8bc070c0d67fb7 --- /dev/null +++ b/docs/api/ar/renderers/shaders/UniformsLib.html @@ -0,0 +1,24 @@ + + + + + + + + + +

[name]

+ +

مكتبة الزي الموحد لشادرات WebGL المشتركة

+ +

الخصائص (Properties)

+ +

الطرق (Methods)

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/renderers/shaders/UniformsUtils.html b/docs/api/ar/renderers/shaders/UniformsUtils.html new file mode 100644 index 00000000000000..3d69614f637266 --- /dev/null +++ b/docs/api/ar/renderers/shaders/UniformsUtils.html @@ -0,0 +1,41 @@ + + + + + + + + + +

[name]

+ +

يوفر دوال المساعدة لإدارة الزي الموحد.

+ +

الطرق (Methods)

+ +

[method:Object clone]( [param:Object src] )

+

+ src -- كائن يمثل تعريفات الزي الموحد.

+ + يستنسخ تعريفات الزي الموحد المعطاة عن طريق إجراء نسخة عميقة. هذا يعني + إذا كان [page:Uniform.value value] من الزي الموحد يشير إلى كائن مثل + [page:Vector3] أو [page:Texture]، فإن الزي الموحد المستنسخ سيشير إلى مرجع + كائن جديد. +

+ +

[method:Object merge]( [param:Array uniforms] )

+

+ uniforms -- مصفوفة من الكائنات التي تحتوي على تعريفات الزي الموحد.

+ + تدمج تعريفات الزي الموحد المعطاة في كائن واحد. نظرًا لأن + الطريقة تستخدم داخليًا [page:.clone](), فهو يؤدي نسخًا عميقًا عند + إنتاج تعريفات الزي الموحد المدمجة. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/renderers/webxr/WebXRManager.html b/docs/api/ar/renderers/webxr/WebXRManager.html new file mode 100644 index 00000000000000..65207d444c2838 --- /dev/null +++ b/docs/api/ar/renderers/webxr/WebXRManager.html @@ -0,0 +1,165 @@ + + + + + + + + + +

[name]

+ +

+ تمثل هذه الفئة تجريدًا لواجهة برمجة تطبيقات WebXR Device وتستخدم + داخليًا بواسطة [page:WebGLRenderer]. [name] يوفر أيضًا واجهة عامة + الذي يسمح للمستخدمين بتمكين / تعطيل XR وتنفيذ مهام ذات صلة بـ XR + مثل استرداد التحكُّمات على سبيل المثال. +

+ +

الخصائص (Properties)

+ +

[property:Boolean cameraAutoUpdate]

+

+ ما إذا كان يجب تحديث كاميرا XR الخاصة بالمدير تلقائيًا أم لا. + الافتراضي هو `true`. +

+ +

[property:Boolean enabled]

+

+ هذا العلم يخطر المقدم بأن يكون جاهزًا للتصوير XR. الافتراضي هو + `false`. قم بتعيينه على `true` إذا كنت ستستخدم XR في تطبيقك. +

+ +

[property:Boolean isPresenting]

+

+ ما إذا كان التقديم XR نشطًا أم لا. الافتراضي هو `false`. هذا العلم هو + للقراءة فقط وتعيينه تلقائيًا بواسطة [name]. +

+ +

الطرق (Methods)

+ +

[method:ArrayCamera getCamera]()

+

+ يرجع نسخة من [page:ArrayCamera] التي تمثل كاميرا XR + لجلسة XR النشطة. لكل عرض يحتفظ بكائن كاميرا منفصل + في خصائص [page:ArrayCamera.cameras cameras]. +

+

+ حاليًا لا يتم استخدام `fov` الكاميرا ولا يعكس fov من + كاميرا XR. إذا كنت بحاجة إلى fov على مستوى التطبيق ، فعليك حسابه + بشكل يدوي من مصفوفات الإسقاط الخاصة بكاميرا XR. +

+ +

[method:Group getController]( [param:Integer index] )

+

+ [page:Integer index] — فهرس المتحكم.

+ + يعود بـ [page:Group] يمثل ما يسمى بمساحة *الشعاع المستهدف* من + المتحكم XR. استخدم هذه المساحة لتصور الكائنات ثلاثية الأبعاد التي تدعم + المستخدم في مهام الإشارة مثل التفاعل مع واجهة المستخدم. +

+ +

[method:Group getControllerGrip]( [param:Integer index] )

+

+ [page:Integer index] — فهرس المتحكم.

+ + يعود بـ [page:Group] يمثل ما يسمى بمساحة `القبضة` من المتحكم XR + . استخدم هذه المساحة إذا كان المستخدم سيحمل كائنات ثلاثية الأبعاد أخرى + مثل سيف الضوء. +

+ +

+ ملاحظة: إذا كنت ترغب في إظهار شيء في يد المستخدم وتقديم + شعاع الإشارة في نفس الوقت ، فسترغب في إرفاق الكائن المحمول باليد + إلى المجموعة التي يتم إرجاعها بواسطة [page:.getControllerGrip]() والشعاع إلى + المجموعة التي يتم إرجاعها بواسطة [page:.getController](). الفكرة هي أن يكون لديك اثنان + مجموعات مختلفة في مسافتين إحداثيتين مختلفتين لنفس WebXR + التحكم. +

+ +

[method:Float getFoveation]()

+

+ يعود بكمية الفوفيشن المستخدمة من قبل ملحق XR لـ + طبقة الإسقاط. +

+ +

[method:Group getHand]( [param:Integer index] )

+

+ [page:Integer index] — فهرس المتحكم.

+ + يعود بـ [page:Group] يمثل ما يسمى بـ `hand` أو `joint` space + التحكم XR. استخدام هذه المساحة لتصور أيدي المستخدم عند عدم استخدام + التحكُّمات الفعلية. +

+ +

[method:String getReferenceSpace]()

+

يرجع المسافة المرجعية.

+ +

[method:XRSession getSession]()

+

+ يرجع كائن `XRSession` الذي يسمح بإدارة أكثر دقة + لجلسات WebXR النشطة على مستوى التطبيق. +

+ +

[method:undefined setFoveation]( [param:Float foveation] )

+

+ [page:Float foveation] — الفوفيشن المراد تعيينه.

+ + يحدد مقدار الفوفيشن المستخدم من قبل ملحق XR للطبقة. + يجب أن يكون قيمة بين `0` و `1`. +

+ +

[method:undefined setFramebufferScaleFactor]( [param:Float framebufferScaleFactor] )

+

+ [page:Float framebufferScaleFactor] — عامل مقياس الإطار المراد تعيينه.

+ + يحدد عامل التحجيم المستخدم عند تحديد حجم الإطار عند التصوير إلى جهاز XR. القيمة نسبية إلى + دقة عرض جهاز XR الافتراضية. الافتراضي هو `1`. قيمة `0.5` + تحدد إطارًا بنسبة 50٪ من دقة العرض الأصلية. +

+ +

+ ملاحظة: لا يمكن تغيير عامل مقياس الإطار أثناء + تقديم محتوى XR. +

+ +

[method:undefined setReferenceSpace]( [param:XRReferenceSpace referenceSpace] )

+

+ [page:XRReferenceSpace referenceSpace] — مساحة مرجعية مخصصة.

+ + يمكن استخدامه لتكوين مساحة مرجعية مخصصة تستبدل + المساحة المرجعية الافتراضية. +

+ +

[method:undefined setReferenceSpaceType]( [param:String referenceSpaceType] )

+

+ [page:String referenceSpaceType] — نوع المساحة المرجعية المراد تعيينه.

+ + يمكن استخدامه لتكوين علاقة فضائية مع + بيئة المستخدم الفعلية. اعتمادًا على كيفية حركة المستخدم في الفضاء ثلاثي الأبعاد ، يمكن أن يؤدي تعيين + مساحة مرجعية مناسبة إلى تحسين التتبع. الافتراضي هو + `local-floor`. يرجى التحقق من + [link:https://developer.mozilla.org/en-US/docs/Web/API/XRReferenceSpaceType MDN] + للحصول على قائمة بالقيم الممكنة وحالات استخدامها. +

+ +

[method:undefined updateCamera]( [param:PerspectiveCamera camera] )

+

+ تحديث حالة كاميرا XR. استخدم هذه الطريقة على مستوى التطبيق إذا + ضبط [page:.cameraAutoUpdate] إلى `false`. تتطلب الطريقة كاميرا غير XR + المشهد كمعلم. يتم ضبط تحولات الكاميرا التي تم تمريرها تلقائيًا إلى موضع كاميرا XR عند استدعاء + هذه الطريقة. +

+ +

+ ملاحظة: لا يمكن تغيير نوع المساحة المرجعية أثناء + تقديم محتوى XR. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/scenes/Fog.html b/docs/api/ar/scenes/Fog.html new file mode 100644 index 00000000000000..a993eed9733ce9 --- /dev/null +++ b/docs/api/ar/scenes/Fog.html @@ -0,0 +1,80 @@ + + + + + + + + + +

[name]

+ +

+ تحتوي هذه الفئة على المعلمات التي تحدد الضباب الخطي ، أي + ينمو بشكل خطي أكثر كثافة مع المسافة. +

+ +

مثال للكود

+ + + const scene = new THREE.Scene(); + scene.fog = new THREE.Fog( 0xcccccc, 10, 15 ); + + +

المنشئ (Constructor)

+ +

+ [name]( [param:Integer color], [param:Float near], [param:Float far] ) +

+

+ يتم تمرير معلمة اللون إلى [page:Color] المنشئ لتعيين + خاصية اللون. يمكن أن يكون اللون عددًا صحيحًا سداسي عشريًا أو سلسلة على طراز CSS. +

+ +

الخصائص (Properties)

+ +

[property:Boolean isFog]

+

علامة للقراءة فقط للتحقق مما إذا كان كائنًا معطىً من نوع [name].

+ +

[property:String name]

+

+ اسم اختياري للكائن (لا يحتاج إلى أن يكون فريدًا). الافتراضي هو + سلسلة فارغة. +

+ +

[property:Color color]

+

+ لون الضباب. مثال: إذا تم تعيينه على الأسود ، سيتم عرض الأشياء البعيدة + أسود. +

+ +

[property:Float near]

+

+ المسافة الدنيا لبدء تطبيق الضباب. الأشياء التي هي أقل من + 'near' units from the active camera won't be affected by fog. +

+

الافتراضي هو `1`.

+ +

[property:Float far]

+

+ المسافة القصوى التي يتوقف فيها حساب وتطبيق الضباب. + الأشياء التي تبعد أكثر من 'far' units away from the active camera won't + يتأثر بالضباب. +

+

الافتراضي هو `1000`.

+ +

الطرق (Methods)

+ +

[method:Fog clone]()

+

يرجع مثيل ضباب جديد بنفس المعلمات كهذا.

+ +

[method:Object toJSON]()

+

إرجاع بيانات الضباب في تنسيق JSON.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/scenes/FogExp2.html b/docs/api/ar/scenes/FogExp2.html new file mode 100644 index 00000000000000..ef57969c58342a --- /dev/null +++ b/docs/api/ar/scenes/FogExp2.html @@ -0,0 +1,68 @@ + + + + + + + + + +

[name]

+ +

+ تحتوي هذه الفئة على المعلمات التي تحدد الضباب المربع الأسي ، + الذي يعطي رؤية واضحة بالقرب من الكاميرا وأسرع من الضباب المتزايد بشكل أسي + بعيدًا عن الكاميرا. +

+ +

مثال للكود

+ + + const scene = new THREE.Scene(); + scene.fog = new THREE.FogExp2( 0xcccccc, 0.002 ); + + +

المنشئ (Constructor)

+ +

[name]( [param:Integer color], [param:Float density] )

+ +

+ يتم تمرير معلمة اللون إلى [page:Color] المنشئ لتعيين + خاصية اللون. يمكن أن يكون اللون عددًا صحيحًا سداسي عشريًا أو سلسلة على طراز CSS. +

+

الخصائص (Properties)

+ +

[property:Boolean isFogExp2]

+

علامة للقراءة فقط للتحقق مما إذا كان كائنًا معطىً من نوع [name].

+ +

[property:String name]

+

+ اسم اختياري للكائن (لا يحتاج إلى أن يكون فريدًا). الافتراضي هو + سلسلة فارغة. +

+ +

[property:Color color]

+

+ لون الضباب. مثال: إذا تم تعيينه على الأسود ، سيتم عرض الأشياء البعيدة + أسود. +

+ +

[property:Float density]

+

يحدد مدى سرعة نمو الضباب.

+

الافتراضي هو `0.00025`.

+ +

الطرق (Methods)

+ +

[method:FogExp2 clone]()

+

يرجع مثيل FogExp2 جديد بنفس المعلمات كهذا.

+ +

[method:Object toJSON]()

+

إرجاع بيانات FogExp2 في تنسيق JSON.

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/ar/scenes/Scene.html b/docs/api/ar/scenes/Scene.html new file mode 100644 index 00000000000000..221ef88106bc43 --- /dev/null +++ b/docs/api/ar/scenes/Scene.html @@ -0,0 +1,92 @@ + + + + + + + + + + [page:Object3D] → +

[name]

+ +

+ تسمح لك المشاهد بتحديد ما وأين يتم تجسيده بواسطة three.js. + هذا هو المكان الذي تضع فيه الأشياء والأضواء والكاميرات. +

+ +

المنشئ (Constructor)

+ +

[name]()

+

إنشاء كائن مشهد جديد.

+ +

الخصائص (Properties)

+ +

[property:Object background]

+

+ يحدد خلفية المشهد. الافتراضي هو `null`. المدخلات الصالحة هي: +

+
    +
  • [page:Color] لتحديد خلفية ملونة موحدة.
  • +
  • [page:Texture] لتحديد خلفية ملمس (مسطح).
  • +
  • + مكعبات القوام ([page:CubeTexture]) أو القوام المستطيلة لـ + تحديد صندوق سماء. +
  • +
+ ملاحظة: يتم تجاهل أي تكوينات ذات صلة بالكاميرا مثل `zoom` أو `view`. + +

[property:Float backgroundBlurriness]

+

+ يحدد ضبابية الخلفية. يؤثر فقط على خرائط البيئة + المعينة إلى [page:Scene.background]. الإدخال الصالح هو عدد عشري بين `0` + و `1`. الافتراضي هو `0`. +

+ +

[property:Float backgroundIntensity]

+

+ يضعف لون الخلفية. ينطبق فقط على خلفية + الملمس. الافتراضي هو `1`. +

+ +

[property:Texture environment]

+

+ يحدد خريطة البيئة لجميع المواد الفعلية في المشهد. ومع ذلك ، + ليس من الممكن الكتابة فوق نسيج موجود معين إلى + [page:MeshStandardMaterial.envMap]. الافتراضي هو `null`. +

+ +

[property:Fog fog]

+ +

+ مثيل [page:Fog fog] يحدد نوع الضباب الذي يؤثر على كل شيء + تم تجسيده في المشهد. الافتراضي هو `null`. +

+ +

[property:Boolean isScene]

+

علامة للقراءة فقط للتحقق مما إذا كان كائنًا معطىً من نوع [name].

+ +

[property:Material overrideMaterial]

+ +

+ يجبر كل شيء في المشهد على التجسيم باستخدام المادة المحددة. + الافتراضي هو `null`. +

+ +

الطرق (Methods)

+ +

[method:Object toJSON]( [param:Object meta] )

+

+ meta - كائن يحتوي على بيانات تعريفية مثل الملمس أو الصور لـ + scene.
+ قم بتحويل المشهد إلى three.js + [link:https://github.com/mrdoob/three.js/wiki/JSON-Object-Scene-format-4 JSON Object/Scene format]. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

+ + diff --git a/docs/api/en/animation/AnimationAction.html b/docs/api/en/animation/AnimationAction.html index a2ca9e65896672..000f3d6c359992 100644 --- a/docs/api/en/animation/AnimationAction.html +++ b/docs/api/en/animation/AnimationAction.html @@ -59,7 +59,7 @@

[property:Boolean clampWhenFinished]

automatically be switched to false when the last loop of the action has finished, so that this action has no further impact.

- Default is false.

+ Default is `false`.

Note: `clampWhenFinished` has no impact if the action is interrupted (it has only an effect if its last loop has really finished). @@ -140,7 +140,7 @@

[property:Number timeScale]

[property:Number weight]

The degree of influence of this action (in the interval `[0, 1]`). Values - between `0` (no impact) and 1 (full impact) can be used to blend between + between `0` (no impact) and `1` (full impact) can be used to blend between several actions. Default is `1`.

Properties/methods concerning `weight` are: [page:.crossFadeFrom crossFadeFrom], @@ -179,7 +179,7 @@

of the time scales) will be applied.

Note: Like with `fadeIn`/`fadeOut`, the fading starts/ends with a weight - of 1. + of `1`.

@@ -193,7 +193,7 @@

of the time scales) will be applied.

Note: Like with `fadeIn`/`fadeOut`, the fading starts/ends with a weight - of 1. + of `1`.

[method:this fadeIn]( [param:Number durationInSeconds] )

diff --git a/docs/api/en/animation/AnimationMixer.html b/docs/api/en/animation/AnimationMixer.html index b9b6d9b0be3a03..f34b10ade54d75 100644 --- a/docs/api/en/animation/AnimationMixer.html +++ b/docs/api/en/animation/AnimationMixer.html @@ -102,7 +102,8 @@

[method:undefined uncacheRoot]([param:Object3D root])

Deallocates all memory resources for a root object. Before using this method make sure to call [page:AnimationAction.stop]() for all related - actions. + actions or alternatively [page:.stopAllAction]() when the mixer operates + on a single root.

[method:undefined uncacheAction]([param:AnimationClip clip], [param:Object3D optionalRoot]) diff --git a/docs/api/en/animation/AnimationObjectGroup.html b/docs/api/en/animation/AnimationObjectGroup.html index 4699fe4cecd7b7..6486e4490e6e22 100644 --- a/docs/api/en/animation/AnimationObjectGroup.html +++ b/docs/api/en/animation/AnimationObjectGroup.html @@ -53,7 +53,7 @@

[property:Boolean isAnimationObjectGroup]

[property:Object stats]

- An object that contains some informations of this `AnimationObjectGroup` + An object that contains information of this `AnimationObjectGroup` (total number, number in use, number of bindings per object)

diff --git a/docs/api/en/animation/AnimationUtils.html b/docs/api/en/animation/AnimationUtils.html index f29efdb7f6434e..7de9a9e8c7a80b 100644 --- a/docs/api/en/animation/AnimationUtils.html +++ b/docs/api/en/animation/AnimationUtils.html @@ -16,11 +16,6 @@

[name]

Methods

-

[method:Array arraySlice]( array, from, to )

-

- This is the same as Array.prototype.slice, but also works on typed arrays. -

-

[method:Array convertArray]( array, type, forceClone )

Converts an array to a specific type.

diff --git a/docs/api/en/animation/tracks/BooleanKeyframeTrack.html b/docs/api/en/animation/tracks/BooleanKeyframeTrack.html index af3d582f2bdc66..aee2b5704c9fb6 100644 --- a/docs/api/en/animation/tracks/BooleanKeyframeTrack.html +++ b/docs/api/en/animation/tracks/BooleanKeyframeTrack.html @@ -23,6 +23,10 @@

[page:Array times] - (required) array of keyframe times.
[page:Array values] - values for the keyframes at the times specified.

+

+ This keyframe track type has no interpolation parameter because the + interpolation is always [page:Animation InterpolateDiscrete]. +

Properties

@@ -30,7 +34,7 @@

Properties

[property:Constant DefaultInterpolation]

- The default interpolation type to use, [page:Animation InterpolateDiscrete]. + The default interpolation type to use. Only [page:Animation InterpolateDiscrete] is valid for this track type.

[property:Array ValueBufferType]

diff --git a/docs/api/en/animation/tracks/ColorKeyframeTrack.html b/docs/api/en/animation/tracks/ColorKeyframeTrack.html index 04b321eae0acb2..fcaf4da8614ab7 100644 --- a/docs/api/en/animation/tracks/ColorKeyframeTrack.html +++ b/docs/api/en/animation/tracks/ColorKeyframeTrack.html @@ -20,13 +20,13 @@

[name]

Constructor

- [name]( [param:String name], [param:Array times], [param:Array values] ) + [name]( [param:String name], [param:Array times], [param:Array values], [param:Constant interpolation] )

[page:String name] - (required) identifier for the KeyframeTrack.
[page:Array times] - (required) array of keyframe times.
[page:Array values] - values for the keyframes at the times specified, a - flat array of color components between 0 and 1.
+ flat array of color components between `0` and `1`.
[page:Constant interpolation] - the type of interpolation to use. See [page:Animation Animation Constants] for possible values. Default is [page:Animation InterpolateLinear]. diff --git a/docs/api/en/animation/tracks/NumberKeyframeTrack.html b/docs/api/en/animation/tracks/NumberKeyframeTrack.html index 94b3fef4cf2001..c505f9b8b8994b 100644 --- a/docs/api/en/animation/tracks/NumberKeyframeTrack.html +++ b/docs/api/en/animation/tracks/NumberKeyframeTrack.html @@ -16,7 +16,7 @@

[name]

Constructor

- [name]( [param:String name], [param:Array times], [param:Array values] ) + [name]( [param:String name], [param:Array times], [param:Array values], [param:Constant interpolation] )

[page:String name] - (required) identifier for the KeyframeTrack.
diff --git a/docs/api/en/animation/tracks/QuaternionKeyframeTrack.html b/docs/api/en/animation/tracks/QuaternionKeyframeTrack.html index 7f1b7a05512d81..da3a545a0d808b 100644 --- a/docs/api/en/animation/tracks/QuaternionKeyframeTrack.html +++ b/docs/api/en/animation/tracks/QuaternionKeyframeTrack.html @@ -16,14 +16,14 @@

[name]

Constructor

- [name]( [param:String name], [param:Array times], [param:Array values] ) + [name]( [param:String name], [param:Array times], [param:Array values], [param:Constant interpolation] )

- [page:String name] (required) identifier for the KeyframeTrack.
- [page:Array times] (required) array of keyframe times.
- [page:Array values] values for the keyframes at the times specified, a + [page:String name] - (required) identifier for the KeyframeTrack.
+ [page:Array times] - (required) array of keyframe times.
+ [page:Array values] - values for the keyframes at the times specified, a flat array of quaternion components.
- [page:Constant interpolation] the type of interpolation to use. See + [page:Constant interpolation] - the type of interpolation to use. See [page:Animation Animation Constants] for possible values. Default is [page:Animation InterpolateLinear].

diff --git a/docs/api/en/animation/tracks/StringKeyframeTrack.html b/docs/api/en/animation/tracks/StringKeyframeTrack.html index 8464687c2e6acd..a6c6a7a73f8624 100644 --- a/docs/api/en/animation/tracks/StringKeyframeTrack.html +++ b/docs/api/en/animation/tracks/StringKeyframeTrack.html @@ -24,9 +24,10 @@

[page:String name] - (required) identifier for the KeyframeTrack.
[page:Array times] - (required) array of keyframe times.
[page:Array values] - values for the keyframes at the times specified.
- [page:Constant interpolation] - the type of interpolation to use. See - [page:Animation Animation Constants] for possible values. Default is - [page:Animation InterpolateDiscrete]. +

+

+ This keyframe track type has no interpolation parameter because the + interpolation is always [page:Animation InterpolateDiscrete].

Properties

@@ -35,7 +36,7 @@

Properties

[property:Constant DefaultInterpolation]

- The default interpolation type to use, [page:Animation InterpolateDiscrete]. + The default interpolation type to use. Only [page:Animation InterpolateDiscrete] is valid for this track type.

[property:Array ValueBufferType]

@@ -70,4 +71,4 @@

Source

- \ No newline at end of file + diff --git a/docs/api/en/animation/tracks/VectorKeyframeTrack.html b/docs/api/en/animation/tracks/VectorKeyframeTrack.html index 7d88ab2b4f77cc..81839922ef8b5f 100644 --- a/docs/api/en/animation/tracks/VectorKeyframeTrack.html +++ b/docs/api/en/animation/tracks/VectorKeyframeTrack.html @@ -17,7 +17,7 @@

[name]

Constructor

- [name]( [param:String name], [param:Array times], [param:Array values] ) + [name]( [param:String name], [param:Array times], [param:Array values], [param:Constant interpolation] )

[page:String name] - (required) identifier for the KeyframeTrack.
diff --git a/docs/api/en/audio/Audio.html b/docs/api/en/audio/Audio.html index 07946bfd0527d1..cad296b6cafdf7 100644 --- a/docs/api/en/audio/Audio.html +++ b/docs/api/en/audio/Audio.html @@ -159,11 +159,12 @@

[method:GainNode getOutput]()

[method:Float getPlaybackRate]()

Return the value of [page:Audio.playbackRate playbackRate].

-

[method:Float getVolume]( value )

+

[method:Float getVolume]()

Return the current volume.

[method:this play]( delay )

+ delay (optional) - The delay, in seconds, at which the audio should start playing.
If [page:Audio.hasPlaybackControl hasPlaybackControl] is true, starts playback.

@@ -244,8 +245,9 @@

[method:this setPlaybackRate]( [param:Float value] )

[method:this setVolume]( [param:Float value] )

Set the volume.

-

[method:this stop]()

+

[method:this stop]( delay )

+ delay (optional) - The delay, in seconds, at which the audio should stop playing.
If [page:Audio.hasPlaybackControl hasPlaybackControl] is enabled, stops playback.

diff --git a/docs/api/en/audio/AudioAnalyser.html b/docs/api/en/audio/AudioAnalyser.html index 99a418ccc26bf7..ac2aac07303b3b 100644 --- a/docs/api/en/audio/AudioAnalyser.html +++ b/docs/api/en/audio/AudioAnalyser.html @@ -10,7 +10,7 @@

[name]

- Create a AudioAnalyser object, which uses an + Create an AudioAnalyser object, which uses an [link:https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode AnalyserNode] to analyse audio data.

This uses the diff --git a/docs/api/en/cameras/OrthographicCamera.html b/docs/api/en/cameras/OrthographicCamera.html index db9ac58e001bfd..d85488a7e8395b 100644 --- a/docs/api/en/cameras/OrthographicCamera.html +++ b/docs/api/en/cameras/OrthographicCamera.html @@ -36,7 +36,6 @@

Examples

[example:webgl_postprocessing_dof2 postprocessing / dof2 ]
[example:webgl_postprocessing_godrays postprocessing / godrays ]
[example:webgl_rtt rtt ]
- [example:webgl_shaders_tonemapping shaders / tonemapping ]
[example:webgl_shadowmap shadowmap ]

diff --git a/docs/api/en/cameras/PerspectiveCamera.html b/docs/api/en/cameras/PerspectiveCamera.html index 6f0c5f749d92b2..1acaabec020b7b 100644 --- a/docs/api/en/cameras/PerspectiveCamera.html +++ b/docs/api/en/cameras/PerspectiveCamera.html @@ -66,7 +66,7 @@

[property:Float far]

[property:Float filmGauge]

- Film size used for the larger axis. Default is 35 (millimeters). This + Film size used for the larger axis. Default is `35` (millimeters). This parameter does not influence the projection matrix unless .filmOffset is set to a nonzero value.

@@ -96,7 +96,7 @@

[property:Boolean isPerspectiveCamera]

[property:Float near]

Camera frustum near plane. Default is `0.1`.

- The valid range is greater than 0 and less than the current value of the + The valid range is greater than `0` and less than the current value of the [page:.far far] plane. Note that, unlike for the [page:OrthographicCamera], `0` is not a valid value for a PerspectiveCamera's near plane. @@ -145,6 +145,19 @@

[method:undefined setFocalLength]( [param:Float focalLength] )

By default, the focal length is specified for a 35mm (full frame) camera.

+

[method:undefined getViewBounds]( [param:Float distance], [param:Vector2 minTarget], [param:Vector2 maxTarget] ) +

+

+ Computes the 2D bounds of the camera's viewable rectangle at a given distance along the viewing direction. + Sets minTarget and maxTarget to the coordinates of the lower-left and upper-right corners of the view rectangle. +

+ +

[method:Vector2 getViewSize]( [param:Float distance], [param:Vector2 target] )

+

+ Computes the width and height of the camera's viewable rectangle at a given distance along the viewing direction. + Copies the result into the target Vector2, where x is width and y is height. +

+

[method:undefined setViewOffset]( [param:Float fullWidth], [param:Float fullHeight], [param:Float x], [param:Float y], [param:Float width], [param:Float height] )

fullWidth — full width of multiview setup
@@ -214,6 +227,5 @@

Source

[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]

- diff --git a/docs/api/en/constants/CustomBlendingEquations.html b/docs/api/en/constants/CustomBlendingEquations.html index f6e9d78d14f4c3..8f99f73ebc910c 100644 --- a/docs/api/en/constants/CustomBlendingEquations.html +++ b/docs/api/en/constants/CustomBlendingEquations.html @@ -52,6 +52,10 @@

Source Factors

THREE.DstColorFactor THREE.OneMinusDstColorFactor THREE.SrcAlphaSaturateFactor + THREE.ConstantColorFactor + THREE.OneMinusConstantColorFactor + THREE.ConstantAlphaFactor + THREE.OneMinusConstantAlphaFactor

Destination Factors

diff --git a/docs/api/en/constants/Materials.html b/docs/api/en/constants/Materials.html index 7ffee38f8d0624..f793d91a143043 100644 --- a/docs/api/en/constants/Materials.html +++ b/docs/api/en/constants/Materials.html @@ -144,7 +144,7 @@

Stencil Operations

Which stencil operation the material will perform on the stencil buffer pixel if the provided stencil function passes.
- [page:Materials ZeroStencilOp] will set the stencil value to 0.
+ [page:Materials ZeroStencilOp] will set the stencil value to `0`.
[page:Materials KeepStencilOp] will not change the current stencil value.
[page:Materials ReplaceStencilOp] will replace the stencil value with the @@ -155,7 +155,7 @@

Stencil Operations

value by `1`.
[page:Materials IncrementWrapStencilOp] will increment the current stencil value by `1`. If the value increments past `255` it will be set to `0`.
- [page:Materials DecrementWrapStencilOp] will increment the current stencil + [page:Materials DecrementWrapStencilOp] will decrement the current stencil value by `1`. If the value decrements below `0` it will be set to `255`.
[page:Materials InvertStencilOp] will perform a bitwise inversion of the diff --git a/docs/api/en/constants/Renderer.html b/docs/api/en/constants/Renderer.html index eb68b61af5e8e4..55b4b2f2cae1e0 100644 --- a/docs/api/en/constants/Renderer.html +++ b/docs/api/en/constants/Renderer.html @@ -11,8 +11,8 @@

WebGLRenderer Constants

Cull Face Modes

- THREE.CullFaceNone - THREE.CullFaceBack + THREE.CullFaceNone + THREE.CullFaceBack THREE.CullFaceFront THREE.CullFaceFrontBack @@ -25,14 +25,14 @@

Cull Face Modes

Shadow Types

- THREE.BasicShadowMap - THREE.PCFShadowMap + THREE.BasicShadowMap + THREE.PCFShadowMap THREE.PCFSoftShadowMap THREE.VSMShadowMap

These define the WebGLRenderer's [page:WebGLRenderer.shadowMap.type shadowMap.type] property.

- [page:constant BasicShadowMap] gives unfiltered shadow maps - fastest, but + [page:constant BasicShadowMap] gives unfiltered shadow maps - fastest, but lowest quality.
[page:constant PCFShadowMap] filters shadow maps using the Percentage-Closer Filtering (PCF) algorithm (default).
@@ -44,14 +44,16 @@

Shadow Types

also cast shadows.

- +

Tone Mapping

- THREE.NoToneMapping - THREE.LinearToneMapping + THREE.NoToneMapping + THREE.LinearToneMapping THREE.ReinhardToneMapping - THREE.CineonToneMapping + THREE.CineonToneMapping THREE.ACESFilmicToneMapping + THREE.AgXToneMapping + THREE.NeutralToneMapping THREE.CustomToneMapping

@@ -60,12 +62,14 @@

Tone Mapping

monitor or mobile device's screen.

- THREE.LinearToneMapping, THREE.ReinhardToneMapping, - THREE.CineonToneMapping and THREE.ACESFilmicToneMapping are built-in - implementations of tone mapping. THREE.CustomToneMapping expects a custom - implementation by modyfing GLSL code of the material's fragment shader. + THREE.LinearToneMapping, THREE.ReinhardToneMapping, THREE.CineonToneMapping, THREE.ACESFilmicToneMapping, + THREE.AgXToneMapping and THREE.NeutralToneMapping are built-in implementations of tone mapping. + THREE.CustomToneMapping expects a custom implementation by modifying GLSL code of the material's fragment shader. See the [example:webgl_tonemapping WebGL / tonemapping] example.

+

+ THREE.NeutralToneMapping is an implementation based on the Khronos 3D Commerce Group standard tone mapping. +

Source

diff --git a/docs/api/en/constants/Textures.html b/docs/api/en/constants/Textures.html index 09a055425ca0a6..254f604ac94c4c 100644 --- a/docs/api/en/constants/Textures.html +++ b/docs/api/en/constants/Textures.html @@ -136,6 +136,7 @@

Types

THREE.UnsignedShort4444Type THREE.UnsignedShort5551Type THREE.UnsignedInt248Type + THREE.UnsignedInt5999Type

For use with a texture's [page:Texture.type type] property, which must @@ -151,10 +152,9 @@

Formats

THREE.RedIntegerFormat THREE.RGFormat THREE.RGIntegerFormat + THREE.RGBFormat THREE.RGBAFormat THREE.RGBAIntegerFormat - THREE.LuminanceFormat - THREE.LuminanceAlphaFormat THREE.DepthFormat THREE.DepthStencilFormat @@ -166,24 +166,21 @@

Formats

and reads just the alpha component.

[page:constant RedFormat] discards the green and blue components and reads - just the red component. (can only be used with a WebGL 2 rendering - context). + just the red component.

[page:constant RedIntegerFormat] discards the green and blue components and reads just the red component. The texels are read as integers instead - of floating point. (can only be used with a WebGL 2 rendering context). + of floating point.

[page:constant RGFormat] discards the alpha, and blue components and reads - the red, and green components. (can only be used with a WebGL 2 rendering - context). + the red, and green components.

[page:constant RGIntegerFormat] discards the alpha, and blue components and reads the red, and green components. The texels are read as integers - instead of floating point. (can only be used with a WebGL 2 rendering - context). + instead of floating point.

[page:constant RGBAFormat] is the default and reads the red, green, blue @@ -191,19 +188,9 @@

Formats

[page:constant RGBAIntegerFormat] is the default and reads the red, green, blue and alpha components. The texels are read as integers instead of - floating point. (can only be used with a WebGL 2 rendering context). + floating point.

- [page:constant LuminanceFormat] reads each element as a single luminance - component. This is then converted to a floating point, clamped to the - range [0,1], and then assembled into an RGBA element by placing the - luminance value in the red, green and blue channels, and attaching 1.0 to - the alpha channel.

- - [page:constant LuminanceAlphaFormat] reads each element as a - luminance/alpha double. The same process occurs as for the [page:constant LuminanceFormat], except that the alpha channel may have values other than - `1.0`.

- [page:constant DepthFormat] reads each element as a single depth value, converts it to floating point, and clamps to the range [0,1]. This is the default for [page:DepthTexture DepthTexture].

@@ -393,9 +380,6 @@

Internal Formats

- Heads up: changing the internal format of a texture will only affect the - texture when using a WebGL 2 rendering context.

- For use with a texture's [page:Texture.internalFormat internalFormat] property, these define how elements of a texture, or `texels`, are stored on the GPU.

diff --git a/docs/api/en/core/BufferAttribute.html b/docs/api/en/core/BufferAttribute.html index 209b930c526429..c3cef46dd6e61e 100644 --- a/docs/api/en/core/BufferAttribute.html +++ b/docs/api/en/core/BufferAttribute.html @@ -31,7 +31,7 @@

[name]( [param:TypedArray array], [param:Integer itemSize], [param:Boolean n itemSize * numVertices elements, where numVertices is the number of vertices in the associated - [page:BufferGemetry BufferGeometry].

+ [page:BufferGeometry BufferGeometry].

[page:Integer itemSize] -- the number of values of the array that should be associated with a particular vertex. For instance, if this attribute is @@ -56,16 +56,23 @@

[property:TypedArray array]

[property:Integer count]

- Stores the [page:BufferAttribute.array array]'s length divided by the - [page:BufferAttribute.itemSize itemSize].

+ Represents the number of items this buffer attribute stores. It is internally computed by dividing the [page:BufferAttribute.array array]'s length by the + [page:BufferAttribute.itemSize itemSize]. Read-only property. +

+ +

[property:Number gpuType]

+

+ Configures the bound GPU type for use in shaders. Either [page:BufferAttribute THREE.FloatType] or [page:BufferAttribute THREE.IntType], default is [page:BufferAttribute THREE.FloatType]. - If the buffer is storing a 3-component vector (such as a position, normal, - or color), then this will count the number of such vectors stored. + Note: this only has an effect for integer arrays and is not configurable for float arrays. For lower precision float types, see [page:BufferAttributeTypes THREE.Float16BufferAttribute].

[property:Boolean isBufferAttribute]

Read-only flag to check if a given object is of type [name].

+

[property:Integer id]

+

Unique number for this attribute instance.

+

[property:Integer itemSize]

The length of vectors that are being stored in the @@ -95,16 +102,16 @@

[property:Function onUploadCallback]

the attribute array data to the GPU.

-

[property:Object updateRange]

+

[property:Object updateRanges]

- Object containing:
- [page:Integer offset]: Default is `0`. Position at which to start + Array of objects containing:
+ [page:Integer start]: Position at which to start update.
- [page:Integer count]: Default is `-1`, which means don't use update - ranges.

+ [page:Integer count]: The number of components to update.

This can be used to only update some components of stored vectors (for - example, just the component related to color). + example, just the component related to color). Use the [page:BufferAttribute.addUpdateRange addUpdateRange] + function to add ranges to this array.

[property:Usage usage]

@@ -151,6 +158,18 @@

[method:this transformDirection]( [param:Matrix4 m] )

BufferAttribute, interpreting the elements as a direction vectors.

+

[method:this addUpdateRange]( [param:Integer start], [param:Integer count] )

+

+ Adds a range of data in the data array to be updated on the GPU. Adds an + object describing the range to the [page:BufferAttribute.updateRanges updateRanges] + array. +

+ +

[method:this clearUpdateRanges]()

+

+ Clears the [page:BufferAttribute.updateRanges updateRanges] array. +

+

[method:BufferAttribute clone]()

Return a copy of this bufferAttribute.

@@ -169,6 +188,9 @@

[method:this copyArray]( array )

[method:this copyAt] ( [param:Integer index1], [param:BufferAttribute bufferAttribute], [param:Integer index2] )

Copy a vector from bufferAttribute[index2] to [page:BufferAttribute.array array][index1].

+

[method:Number getComponent]( [param:Integer index], [param:Integer component] )

+

Returns the given component of the vector at the given index.

+

[method:Number getX]( [param:Integer index] )

Returns the x component of the vector at the given index.

@@ -215,6 +237,9 @@

[method:this setUsage] ( [param:Usage value] )

render.

+

[method:Number setComponent]( [param:Integer index], [param:Integer component], [param:Float value] )

+

Sets the given component of the vector at the given index.

+

[method:this setX]( [param:Integer index], [param:Float x] )

Sets the x component of the vector at the given index.

diff --git a/docs/api/en/core/BufferGeometry.html b/docs/api/en/core/BufferGeometry.html index 2469686bcc404a..9c8dc6a7c3fbe3 100644 --- a/docs/api/en/core/BufferGeometry.html +++ b/docs/api/en/core/BufferGeometry.html @@ -191,6 +191,7 @@

[property:Object userData]

An object that can be used to store custom data about the BufferGeometry. It should not hold references to functions as these will not be cloned. + Default is an empty object `{}`.

[property:String uuid]

@@ -230,18 +231,16 @@

[method:BufferGeometry clone]()

[method:undefined computeBoundingBox]()

- Computes bounding box of the geometry, updating [page:.boundingBox] - attribute.
- Bounding boxes aren't computed by default. They need to be explicitly - computed, otherwise they are `null`. + Computes the bounding box of the geometry, and updates the [page:.boundingBox] attribute. + The bounding box is not computed by the engine; it must be computed by your app. + You may need to recompute the bounding box if the geometry vertices are modified.

[method:undefined computeBoundingSphere]()

- Computes bounding sphere of the geometry, updating [page:.boundingSphere] - attribute.
- Bounding spheres aren't computed by default. They need to be explicitly - computed, otherwise they are `null`. + Computes the bounding sphere of the geometry, and updates the [page:.boundingSphere] attribute. + The engine automatically computes the bounding sphere when it is needed, e.g., for ray casting or view frustum culling. + You may need to recompute the bounding sphere if the geometry vertices are modified.

[method:undefined computeTangents]()

@@ -289,7 +288,7 @@

[method:this lookAt] ( [param:Vector3 vector] )

[method:undefined normalizeNormals]()

- Every normal vector in a geometry will have a magnitude of 1. This will + Every normal vector in a geometry will have a magnitude of `1`. This will correct lighting on the geometry surfaces.

@@ -336,7 +335,12 @@

[method:undefined setDrawRange] ( [param:Integer start], [param:Integer coun

[method:this setFromPoints] ( [param:Array points] )

-

Sets the attributes for this BufferGeometry from an array of points.

+

+ Defines a geometry by creating a `position` attribute based on the given array of points. The array can hold + instances of [page:Vector2] or [page:Vector3]. When using two-dimensional data, the `z` coordinate for all vertices is set to `0`.
+ If the method is used with an existing `position` attribute, the vertex data are overwritten with the data from the array. The length of the + array must match the vertex count. +

[method:this setIndex] ( [param:BufferAttribute index] )

Set the [page:.index] buffer.

diff --git a/docs/api/en/core/Clock.html b/docs/api/en/core/Clock.html index 16f606b60d4553..9f3c70ca9b86e5 100644 --- a/docs/api/en/core/Clock.html +++ b/docs/api/en/core/Clock.html @@ -11,9 +11,7 @@

[name]

Object for keeping track of time. This uses - [link:https://developer.mozilla.org/en-US/docs/Web/API/Performance/now performance.now] if it is available, otherwise it reverts to the less - accurate - [link:https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Date/now Date.now]. + [link:https://developer.mozilla.org/en-US/docs/Web/API/Performance/now performance.now].

Constructor

diff --git a/docs/api/en/core/GLBufferAttribute.html b/docs/api/en/core/GLBufferAttribute.html index 66e2ddad30b21d..179db8a99f1714 100644 --- a/docs/api/en/core/GLBufferAttribute.html +++ b/docs/api/en/core/GLBufferAttribute.html @@ -20,8 +20,13 @@

[name]

calculation interferes or even produces the VBOs in question.

+

Examples

+

+ [example:webgl_buffergeometry_glbufferattribute Points with custom buffers]
+

+

Constructor

-

[name]( [param:WebGLBuffer buffer], [param:GLenum type], [param:Integer itemSize], [param:Integer elementSize], [param:Integer count] )

+

[name]( [param:WebGLBuffer buffer], [param:GLenum type], [param:Integer itemSize], [param:Integer elementSize], [param:Integer count], [param:Boolean normalized] )

`buffer` — Must be a [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLBuffer WebGLBuffer]. @@ -48,6 +53,15 @@

[name]( [param:WebGLBuffer buffer], [param:GLenum type], [param:Integer item
  • gl.UNSIGNED_BYTE: 1
  • `count` — The expected number of vertices in VBO. +
    + `normalized` — (optional) Applies to integer data only. + Indicates how the underlying data in the buffer maps to the values in the + GLSL code. For instance, if [page:WebGLBuffer buffer] contains data of + `gl.UNSIGNED_SHORT`, and [page:Boolean normalized] is true, the values `0 - + +65535` in the buffer data will be mapped to 0.0f - +1.0f in the GLSL + attribute. A `gl.SHORT` (signed) would map from -32768 - +32767 to -1.0f + - +1.0f. If [page:Boolean normalized] is false, the values will be + converted to floats unmodified, i.e. 32767 becomes 32767.0f.

    Properties

    @@ -60,12 +74,6 @@

    [property:WebGLBuffer buffer]

    [property:Integer count]

    The expected number of vertices in VBO.

    -

    [property:Boolean isGLBufferAttribute]

    -

    Read-only. Always `true`.

    - -

    [property:Integer itemSize]

    -

    How many values make up each item (vertex).

    -

    [property:Integer elementSize]

    Stores the corresponding size in bytes for the current `type` property @@ -73,11 +81,29 @@

    [property:Integer elementSize]

    See above (constructor) for a list of known type sizes.

    +

    [property:Boolean isGLBufferAttribute]

    +

    Read-only. Always `true`.

    + +

    [property:Integer itemSize]

    +

    How many values make up each item (vertex).

    +

    [property:String name]

    Optional name for this attribute instance. Default is an empty string.

    +

    [property:Boolean needsUpdate]

    +

    + Default is `false`. Setting this to true increments + [page:GLBufferAttribute.version version]. +

    + +

    [property:Boolean normalized]

    +

    + Indicates how the underlying data in the buffer maps to the values in the + GLSL shader code. See the constructor above for details. +

    +

    [property:GLenum type]

    A @@ -88,6 +114,12 @@

    [property:GLenum type]

    using the `setType` method.

    +

    [property:Integer version]

    +

    + A version number, incremented every time the needsUpdate property is set + to true. +

    +

    Methods

    [method:this setBuffer]( buffer )

    @@ -102,18 +134,6 @@

    [method:this setItemSize]( itemSize )

    [method:this setCount]( count )

    Sets the `count` property.

    -

    [property:Integer version]

    -

    - A version number, incremented every time the needsUpdate property is set - to true. -

    - -

    [property:Boolean needsUpdate]

    -

    - Default is `false`. Setting this to true increments - [page:GLBufferAttribute.version version]. -

    -

    Source

    [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] diff --git a/docs/api/en/core/InterleavedBuffer.html b/docs/api/en/core/InterleavedBuffer.html index a1e1d16b96a2e5..06de44a27da0f3 100644 --- a/docs/api/en/core/InterleavedBuffer.html +++ b/docs/api/en/core/InterleavedBuffer.html @@ -40,11 +40,16 @@

    [property:Integer stride]

    [property:Integer count]

    Gives the total number of elements in the array.

    -

    [property:Object updateRange]

    +

    [property:Object updateRanges]

    - Object containing offset and count.
    - - [page:Number offset]: Default is `0`.
    - - [page:Number count]: Default is `-1`.
    + Array of objects containing:
    + [page:Integer start]: Position at which to start + update.
    + [page:Integer count]: The number of components to update.

    + + This can be used to only update some components of stored data. Use + the [page:InterleavedBuffer.addUpdateRange addUpdateRange] function + to add ranges to this array.

    [property:String uuid]

    @@ -75,6 +80,18 @@

    [property:Usage usage]

    Methods

    +

    [method:this addUpdateRange]( [param:Integer start], [param:Integer count] )

    +

    + Adds a range of data in the data array to be updated on the GPU. Adds an + object describing the range to the [page:InterleavedBuffer.updateRanges updateRanges] + array. +

    + +

    [method:this clearUpdateRanges]()

    +

    + Clears the [page:InterleavedBuffer.updateRanges updateRanges] array. +

    +

    [method:this copy]( [param:InterleavedBuffer source] )

    Copies another [name] to this [name].

    diff --git a/docs/api/en/core/InterleavedBufferAttribute.html b/docs/api/en/core/InterleavedBufferAttribute.html index c213727bb12fcc..0a09367a8c9808 100644 --- a/docs/api/en/core/InterleavedBufferAttribute.html +++ b/docs/api/en/core/InterleavedBufferAttribute.html @@ -77,6 +77,9 @@

    [method:this transformDirection]( [param:Matrix4 m] )

    vectors.

    +

    [method:Number getComponent]( [param:Integer index], [param:Integer component] )

    +

    Returns the given component of the vector at the given index.

    +

    [method:Number getX]( [param:Integer index] )

    Returns the x component of the item at the given index.

    @@ -89,6 +92,9 @@

    [method:Number getZ]( [param:Integer index] )

    [method:Number getW]( [param:Integer index] )

    Returns the w component of the item at the given index.

    +

    [method:Number setComponent]( [param:Integer index], [param:Integer component], [param:Float value] )

    +

    Sets the given component of the vector at the given index.

    +

    [method:this setX]( [param:Integer index], [param:Float x] )

    Sets the x component of the item at the given index.

    diff --git a/docs/api/en/core/Layers.html b/docs/api/en/core/Layers.html index f5f07725c8999d..c9eb86917aa26b 100644 --- a/docs/api/en/core/Layers.html +++ b/docs/api/en/core/Layers.html @@ -25,7 +25,7 @@

    [name]

    Examples

    -

    [example:webgl_layers WebGL / layers]

    +

    [example:webgpu_layers WebGPU / layers]

    Constructor

    diff --git a/docs/api/en/core/Object3D.html b/docs/api/en/core/Object3D.html index 08601c7e1acf06..d05eaab762a5c6 100644 --- a/docs/api/en/core/Object3D.html +++ b/docs/api/en/core/Object3D.html @@ -144,6 +144,21 @@

    [property:Function onAfterRender]

    for such objects.

    +

    [property:Function onAfterShadow]

    +

    + An optional callback that is executed immediately after a 3D object is + rendered to a shadow map. This function is called with the following parameters: renderer, + scene, camera, shadowCamera, geometry, depthMaterial, group. +

    +

    + Please notice that this callback is only executed for `renderable` 3D + objects. Meaning 3D objects which define their visual appearance with + geometries and materials like instances of [page:Mesh], [page:Line], + [page:Points] or [page:Sprite]. Instances of [page:Object3D], [page:Group] + or [page:Bone] are not renderable and thus this callback is not executed + for such objects. +

    +

    [property:Function onBeforeRender]

    An optional callback that is executed immediately before a 3D object is @@ -159,6 +174,21 @@

    [property:Function onBeforeRender]

    for such objects.

    +

    [property:Function onBeforeShadow]

    +

    + An optional callback that is executed immediately before a 3D object is + rendered to a shadow map. This function is called with the following parameters: renderer, + scene, camera, shadowCamera, geometry, depthMaterial, group. +

    +

    + Please notice that this callback is only executed for `renderable` 3D + objects. Meaning 3D objects which define their visual appearance with + geometries and materials like instances of [page:Mesh], [page:Line], + [page:Points] or [page:Sprite]. Instances of [page:Object3D], [page:Group] + or [page:Bone] are not renderable and thus this callback is not executed + for such objects. +

    +

    [property:Object3D parent]

    Object's parent in the [link:https://en.wikipedia.org/wiki/Scene_graph scene graph]. An object can have at most one parent. @@ -206,6 +236,7 @@

    [property:Object userData]

    An object that can be used to store custom data about the Object3D. It should not hold references to functions as these will not be cloned. + Default is an empty object `{}`.

    [property:String uuid]

    @@ -244,8 +275,8 @@

    [property:Boolean DEFAULT_MATRIX_AUTO_UPDATE]

    [property:Boolean DEFAULT_MATRIX_WORLD_AUTO_UPDATE]

    - The default setting for [page:.matrixWorldAutoUpdate - matrixWorldAutoUpdate] for newly created Object3Ds.
    + The default setting for [page:.matrixWorldAutoUpdate matrixWorldAutoUpdate] + for newly created Object3Ds.

    Methods

    @@ -281,6 +312,9 @@

    [method:this attach]( [param:Object3D object] )

    non-uniformly-scaled nodes(s).

    +

    [method:this clear]()

    +

    Removes all child objects.

    +

    [method:Object3D clone]( [param:Boolean recursive] )

    recursive -- if true, descendants of the object are also cloned. Default @@ -291,10 +325,10 @@

    [method:Object3D clone]( [param:Boolean recursive] )

    [method:this copy]( [param:Object3D object], [param:Boolean recursive] )

    - recursive -- if true, descendants of the object are also copied. Default - is true.

    + recursive -- If set to `true`, descendants of the object are copied next to the existing ones. + If set to `false`, descendants are left unchanged. Default is `true`.

    - Copy the given object into this object. Note: event listeners and + Copies the given object into this object. Note: Event listeners and user-defined callbacks ([page:.onAfterRender] and [page:.onBeforeRender]) are not copied.

    @@ -330,10 +364,13 @@

    [method:Object3D getObjectByProperty]( [param:String name], [param:Any value given.

    -

    [method:Object3D getObjectsByProperty]( [param:String name], [param:Any value] )

    +

    [method:Object3D getObjectsByProperty]( [param:String name], [param:Any value], [param:Array optionalTarget] )

    name -- the property name to search for.
    - value -- value of the given property.

    + value -- value of the given property.
    + optionalTarget -- (optional) target to set the result. + Otherwise a new Array is instantiated. If set, you must clear this + array prior to each call (i.e., array.length = 0;).

    Searches through an object and its children, starting with the object itself, and returns all the objects with a property that matches the value @@ -412,9 +449,6 @@

    [method:this remove]( [param:Object3D object], ... )

    [method:this removeFromParent]()

    Removes this object from its current parent.

    -

    [method:this clear]()

    -

    Removes all child objects.

    -

    [method:this rotateOnAxis]( [param:Vector3 axis], [param:Float angle] )

    axis -- A normalized vector in object space.
    @@ -570,6 +604,28 @@

    [method:Vector3 worldToLocal]( [param:Vector3 vector] )

    Converts the vector from world space to this object's local space.

    +

    Events

    + +

    added

    +

    + Fires when the object has been added to its parent object. +

    + +

    removed

    +

    + Fires when the object has been removed from its parent object. +

    + +

    childadded

    +

    + Fires when a new child object has been added. +

    + +

    childremoved

    +

    + Fires when a new child object has been removed. +

    +

    Source

    diff --git a/docs/api/en/core/Raycaster.html b/docs/api/en/core/Raycaster.html index 91e42a29e0c808..a18e4c97c2b976 100644 --- a/docs/api/en/core/Raycaster.html +++ b/docs/api/en/core/Raycaster.html @@ -77,7 +77,7 @@

    [name]( [param:Vector3 origin], [param:Vector3 direction], [param:Float near [page:Vector3 direction] — The direction vector that gives direction to the ray. Should be normalized.
    [page:Float near] — All results returned are further away than near. Near - can't be negative. Default value is 0.
    + can't be negative. Default value is `0`.
    [page:Float far] — All results returned are closer than far. Far can't be lower than near. Default value is Infinity.

    @@ -113,8 +113,8 @@

    [property:Layers layers]

    objects on layer `1` will be honored by the instance of [name]. -raycaster.layers.set( 1 ); -object.layers.enable( 1 ); +raycaster.layers.set( 1 ); +object.layers.enable( 1 );

    @@ -123,12 +123,12 @@

    [property:Object params]

    An object with the following properties: -{ +{ Mesh: {}, Line: { threshold: 1 }, LOD: {}, Points: { threshold: 1 }, - Sprite: {} + Sprite: {} } @@ -155,11 +155,17 @@

    [method:undefined set]( [param:Vector3 origin], [param:Vector3 direction])[method:undefined setFromCamera]( [param:Vector2 coords], [param:Camera camera] )

    [page:Vector2 coords] — 2D coordinates of the mouse, in normalized device - coordinates (NDC)---X and Y components should be between -1 and 1.
    + coordinates (NDC)---X and Y components should be between `-1` and `1`.
    [page:Camera camera] — camera from which the ray should originate

    Updates the ray with a new origin and direction.

    +

    [method:this setFromXRController]( [param:WebXRController controller] )

    +

    + [page:WebXRController controller] — The controller to copy the position and direction from. +

    +

    Updates the ray with a new origin and direction.

    +

    [method:Array intersectObject]( [param:Object3D object], [param:Boolean recursive], [param:Array optionalTarget] )

    [page:Object3D object] — The object to check for intersection with the @@ -179,6 +185,9 @@

    [method:Array intersectObject]( [param:Object3D object], [param:Boolean recu

    [page:Float distance] – distance between the origin of the ray and the intersection
    + [page:Float distanceToRay] – Some objects (f.e. [page:Points Points]) provide + the distance of the intersection to the nearest point on the ray. For other + objects it will be `undefined`
    [page:Vector3 point] – point of intersection, in world coordinates
    [page:Object face] – intersected face
    [page:Integer faceIndex] – index of the intersected face
    diff --git a/docs/api/en/core/Uniform.html b/docs/api/en/core/Uniform.html index fe572c866fbb7b..4a86e79f1c0033 100644 --- a/docs/api/en/core/Uniform.html +++ b/docs/api/en/core/Uniform.html @@ -55,7 +55,7 @@

    Uniform types

    [page:Number] - uint (WebGL 2) + uint [page:Number] diff --git a/docs/api/en/core/bufferAttributeTypes/BufferAttributeTypes.html b/docs/api/en/core/bufferAttributeTypes/BufferAttributeTypes.html index 9564956f85beef..48f22c27f0c688 100644 --- a/docs/api/en/core/bufferAttributeTypes/BufferAttributeTypes.html +++ b/docs/api/en/core/bufferAttributeTypes/BufferAttributeTypes.html @@ -18,7 +18,6 @@

    BufferAttribute Types

    - THREE.Float64BufferAttribute THREE.Float32BufferAttribute THREE.Float16BufferAttribute THREE.Uint32BufferAttribute diff --git a/docs/api/en/extras/Controls.html b/docs/api/en/extras/Controls.html new file mode 100644 index 00000000000000..71618143122473 --- /dev/null +++ b/docs/api/en/extras/Controls.html @@ -0,0 +1,116 @@ + + + + + + + + + + [page:EventDispatcher] → + +

    [name]

    + +

    + Abstract base class for controls. +

    + +

    Constructor

    + +

    [name]( [param:Object3D object], [param:HTMLDOMElement domElement] )

    + +

    + [page:Object3D object] - The object the controls should manage (usually the camera). +

    +

    + [page:HTMLDOMElement domElement]: The HTML element used for event listeners. (optional) +

    +

    + Creates a new instance of [name]. +

    + +

    Properties

    + +

    [property:HTMLDOMElement domElement]

    +

    + The HTML element used for event listeners. If not provided via the constructor, [page:.connect]() must be called after `domElement` has been set. +

    + +

    [property:Boolean enabled]

    +

    + When set to `false`, the controls will not respond to user input. Default is `true`. +

    + +

    [property:Object keys]

    +

    + This object defines the keyboard input of the controls. + Default is `{}`. +

    + +

    [property:Object mouseButtons]

    +

    + This object defines what type of actions are assigned to the available mouse buttons. + It depends on the control implementation what kind of mouse buttons and actions are supported. + Default is `{ LEFT: null, MIDDLE: null, RIGHT: null }`. +

    +

    + Possible buttons are: `LEFT`, `MIDDLE`, `RIGHT`. +

    +

    + Possible actions are defined in the [page:Core Constants] page. +

    + +

    [property:Object3D object]

    +

    + The 3D object that is managed by the controls. +

    + +

    [property:Integer state]

    +

    + The internal state of the controls. Default is `-1` (`NONE`). +

    + +

    [property:Object touches]

    +

    + This object defines what type of actions are assigned to what kind of touch interaction. + It depends on the control implementation what kind of touch interaction and actions are supported. + Default is `{ ONE: null, TWO: null }`. +

    +

    + Possible buttons are: `ONE`, `TWO`. +

    +

    + Possible actions are defined in the [page:Core Constants] page. +

    + +

    Methods

    + +

    See the base [page:EventDispatcher] class for common methods.

    + +

    [method:undefined connect] ()

    +

    + Connects the controls to the DOM. This method has so called "side effects" since it adds the module's event listeners to the DOM. +

    + +

    [method:undefined disconnect] ()

    +

    + Disconnects the controls from the DOM. +

    + +

    [method:undefined dispose] ()

    +

    + Call this method if you no longer want use to the controls. It frees all internal resources and removes all event listeners. +

    + +

    [method:undefined update] ( [param:Number delta] )

    +

    + Controls should implement this method if they have to update their internal state per simulation step. +

    + +

    Source

    + +

    + [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

    + + diff --git a/docs/api/en/extras/PMREMGenerator.html b/docs/api/en/extras/PMREMGenerator.html index f1ac5dfb54ae1e..20ad13916acf06 100644 --- a/docs/api/en/extras/PMREMGenerator.html +++ b/docs/api/en/extras/PMREMGenerator.html @@ -68,19 +68,21 @@

    [name]( [param:WebGLRenderer renderer] )

    Methods

    - [method:WebGLRenderTarget fromScene]( [param:Scene scene], [param:Number sigma], [param:Number near], [param:Number far] ) + [method:WebGLRenderTarget fromScene]( [param:Scene scene], [param:Number sigma], [param:Number near], [param:Number far], [param:Object options] )

    [page:Scene scene] - The given scene.
    [page:Number sigma] - (optional) Specifies a blur radius in radians to be applied to the scene before PMREM generation. Default is `0`.
    [page:Number near] - (optional) The near plane value. Default is `0.1`.
    - [page:Number far] - (optional) The far plane value. Default is `100`.

    + [page:Number far] - (optional) The far plane value. Default is `100`.
    + [page:Object options] - (optional) The configuration options. It allows to define + the `size` of the PMREM as a number well as the `position` of the internal cube camera as + an instance of `Vector3`.

    Generates a PMREM from a supplied Scene, which can be faster than using an image if networking bandwidth is low. Optional near and far planes ensure - the scene is rendered in its entirety (the cubeCamera is placed at the - origin). + the scene is rendered in its entirety.

    diff --git a/docs/api/en/extras/TextureUtils.html b/docs/api/en/extras/TextureUtils.html new file mode 100644 index 00000000000000..d655c6e5ab1412 --- /dev/null +++ b/docs/api/en/extras/TextureUtils.html @@ -0,0 +1,43 @@ + + + + + + + + + +

    [name]

    + +

    A class containing utility functions for textures.

    + +

    Methods

    + +

    [method:Texture contain]( [param:Texture texture], [param:Number aspect] )

    +

    + Scales the texture as large as possible within its surface without cropping or stretching the texture. The method preserves the original aspect ratio of the texture. Akin to CSS `object-fit: contain`. +

    + +

    [method:Texture cover]( [param:Texture texture], [param:Number aspect] )

    +

    + Scales the texture to the smallest possible size to fill the surface, leaving no empty space. The method preserves the original aspect ratio of the texture. Akin to CSS `object-fit: cover`. +

    + +

    [method:Texture fill]( [param:Texture texture] )

    +

    + Configures the texture to the default transformation. Akin to CSS `object-fit: fill`. +

    + +

    [method:Number getByteLength]( [param:Number width], [param:Number height], [param:Number format], [param:Number type] )

    +

    + Given the width, height, format, and type of a texture. Determines how + many bytes must be used to represent the texture. +

    + +

    Source

    + +

    + [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

    + + diff --git a/docs/api/en/extras/core/Curve.html b/docs/api/en/extras/core/Curve.html index 6bde52b833e2f5..b1a83989468b9a 100644 --- a/docs/api/en/extras/core/Curve.html +++ b/docs/api/en/extras/core/Curve.html @@ -27,7 +27,7 @@

    [property:Integer arcLengthDivisions]

    cumulative segment lengths of a curve via [page:.getLengths]. To ensure precision when using methods like [page:.getSpacedPoints], it is recommended to increase [page:.arcLengthDivisions] if the curve is very - large. Default is 200. + large. Default is `200`.

    Methods

    @@ -77,7 +77,7 @@

    [method:Array getLengths]( [param:Integer divisions] )

    [method:undefined updateArcLengths]()

    - Update the cumlative segment distance cache. The method must be called + Update the cumulative segment distance cache. The method must be called every time curve parameters are changed. If an updated curve is part of a composed curve like [page:CurvePath], [page:.updateArcLengths]() must be called on the composed curve, too. diff --git a/docs/api/en/extras/core/CurvePath.html b/docs/api/en/extras/core/CurvePath.html index 7adae8c28bc7a4..506bdc1454531b 100644 --- a/docs/api/en/extras/core/CurvePath.html +++ b/docs/api/en/extras/core/CurvePath.html @@ -36,7 +36,7 @@

    Methods

    [method:undefined add]( [param:Curve curve] )

    Add a curve to the [page:.curves] array.

    -

    [method:undefined closePath]()

    +

    [method:this closePath]()

    Adds a [page:LineCurve lineCurve] to close the path.

    [method:Array getCurveLengths]()

    diff --git a/docs/api/en/extras/core/Path.html b/docs/api/en/extras/core/Path.html index ae0c5733b58adf..c487b210ea40ca 100644 --- a/docs/api/en/extras/core/Path.html +++ b/docs/api/en/extras/core/Path.html @@ -81,7 +81,7 @@

    [method:this absellipse]( [param:Float x], [param:Float y], [param:Float xRa endAngle -- The end angle in radians.
    clockwise -- Sweep the ellipse clockwise. Defaults to false.
    rotation -- The rotation angle of the ellipse in radians, counterclockwise - from the positive X axis. Optional, defaults to 0.

    + from the positive X axis. Optional, defaults to `0`.

    Adds an absolutely positioned [page:EllipseCurve EllipseCurve] to the path. diff --git a/docs/api/en/extras/core/Shape.html b/docs/api/en/extras/core/Shape.html index 2230ecabef3f5f..dc388547125265 100644 --- a/docs/api/en/extras/core/Shape.html +++ b/docs/api/en/extras/core/Shape.html @@ -48,8 +48,7 @@

    Examples

    [example:webgl_geometry_shapes geometry / shapes ]
    - [example:webgl_geometry_extrude_shapes geometry / extrude / shapes ]
    - [example:webgl_geometry_extrude_shapes2 geometry / extrude / shapes2 ]
    + [example:webgl_geometry_extrude_shapes geometry / extrude / shapes ]

    Constructor

    diff --git a/docs/api/en/extras/core/ShapePath.html b/docs/api/en/extras/core/ShapePath.html index 42ea73713d65ae..6bba72a4cd96ac 100644 --- a/docs/api/en/extras/core/ShapePath.html +++ b/docs/api/en/extras/core/ShapePath.html @@ -11,12 +11,9 @@

    [name]

    This class is used to convert a series of shapes to an array of - [page:Path]s, for example an SVG shape to a path (see the example below). + [page:Path]s, for example an SVG shape to a path.

    -

    Examples

    -

    [example:webgl_geometry_extrude_shapes2 geometry / extrude / shapes2]

    -

    Constructor

    [name]( )

    diff --git a/docs/api/en/geometries/BoxGeometry.html b/docs/api/en/geometries/BoxGeometry.html index 8e4da1fdec7527..3672a8658491ad 100644 --- a/docs/api/en/geometries/BoxGeometry.html +++ b/docs/api/en/geometries/BoxGeometry.html @@ -43,10 +43,10 @@

    [name]

    Code Example

    - const geometry = new THREE.BoxGeometry( 1, 1, 1 ); - const material = new THREE.MeshBasicMaterial( {color: 0x00ff00} ); - const cube = new THREE.Mesh( geometry, material ); - scene.add( cube ); +const geometry = new THREE.BoxGeometry( 1, 1, 1 ); +const material = new THREE.MeshBasicMaterial( {color: 0x00ff00} ); +const cube = new THREE.Mesh( geometry, material ); +scene.add( cube );

    Constructor

    @@ -58,22 +58,22 @@

    width — Width; that is, the length of the edges parallel to the X axis. - Optional; defaults to 1. + Optional; defaults to `1`.
    height — Height; that is, the length of the edges parallel to the Y axis. - Optional; defaults to 1. + Optional; defaults to `1`.
    depth — Depth; that is, the length of the edges parallel to the Z axis. - Optional; defaults to 1. + Optional; defaults to `1`.
    widthSegments — Number of segmented rectangular faces along the width of - the sides. Optional; defaults to 1. + the sides. Optional; defaults to `1`.
    heightSegments — Number of segmented rectangular faces along the height of - the sides. Optional; defaults to 1. + the sides. Optional; defaults to `1`.
    depthSegments — Number of segmented rectangular faces along the depth of - the sides. Optional; defaults to 1. + the sides. Optional; defaults to `1`.

    diff --git a/docs/api/en/geometries/CapsuleGeometry.html b/docs/api/en/geometries/CapsuleGeometry.html index 8615d9921995da..b9a13983d630c3 100644 --- a/docs/api/en/geometries/CapsuleGeometry.html +++ b/docs/api/en/geometries/CapsuleGeometry.html @@ -48,15 +48,17 @@

    Code Example

    Constructor

    - [name]([param:Float radius], [param:Float length], [param:Integer capSubdivisions], [param:Integer radialSegments]) + [name]([param:Float radius], [param:Float height], [param:Integer capSegments], [param:Integer radialSegments], [param:Integer heightSegments])

    - radius — Radius of the capsule. Optional; defaults to 1.
    - length — Length of the middle section. Optional; defaults to 1.
    + radius — Radius of the capsule. Optional; defaults to `1`.
    + height — Height of the middle section. Optional; defaults to `1`.
    capSegments — Number of curve segments used to build the caps. Optional; - defaults to 4.
    + defaults to `4`.
    radialSegments — Number of segmented faces around the circumference of the - capsule. Optional; defaults to 8.
    + capsule. Optional; defaults to `8`.
    + heightSegments — Number of rows of faces along the height of the capsule. Optional; + defaults to `1`.

    Properties

    diff --git a/docs/api/en/geometries/CircleGeometry.html b/docs/api/en/geometries/CircleGeometry.html index c615ef262924f3..57aa380de1a493 100644 --- a/docs/api/en/geometries/CircleGeometry.html +++ b/docs/api/en/geometries/CircleGeometry.html @@ -56,11 +56,11 @@

    radius — Radius of the circle, default = 1.
    - segments — Number of segments (triangles), minimum = 3, default = 32.
    - thetaStart — Start angle for first segment, default = 0 (three o'clock + segments — Number of segments (triangles), minimum = `3`, default = `32`.
    + thetaStart — Start angle for first segment, default = `0` (three o'clock position).
    thetaLength — The central angle, often called theta, of the circular - sector. The default is 2*Pi, which makes for a complete circle. + sector. The default is `2`*Pi, which makes for a complete circle.

    Properties

    diff --git a/docs/api/en/geometries/ConeGeometry.html b/docs/api/en/geometries/ConeGeometry.html index c238c2e44d221c..ecff667f4ce0ca 100644 --- a/docs/api/en/geometries/ConeGeometry.html +++ b/docs/api/en/geometries/ConeGeometry.html @@ -45,18 +45,18 @@

    [name]([param:Float radius], [param:Float height], [param:Integer radialSegments], [param:Integer heightSegments], [param:Boolean openEnded], [param:Float thetaStart], [param:Float thetaLength])

    - radius — Radius of the cone base. Default is 1.
    - height — Height of the cone. Default is 1.
    + radius — Radius of the cone base. Default is `1`.
    + height — Height of the cone. Default is `1`.
    radialSegments — Number of segmented faces around the circumference of the - cone. Default is 32
    + cone. Default is `32`
    heightSegments — Number of rows of faces along the height of the cone. - Default is 1.
    + Default is `1`.
    openEnded — A Boolean indicating whether the base of the cone is open or capped. Default is false, meaning capped.
    thetaStart — Start angle for first segment, default = 0 (three o'clock position).
    thetaLength — The central angle, often called theta, of the circular - sector. The default is 2*Pi, which makes for a complete cone. + sector. The default is `2`*Pi, which makes for a complete cone.

    Properties

    diff --git a/docs/api/en/geometries/CylinderGeometry.html b/docs/api/en/geometries/CylinderGeometry.html index c53b2bb2dff3ff..3b9a596b8e8b35 100644 --- a/docs/api/en/geometries/CylinderGeometry.html +++ b/docs/api/en/geometries/CylinderGeometry.html @@ -49,19 +49,19 @@

    [param:Boolean openEnded], [param:Float thetaStart], [param:Float thetaLength])

    - radiusTop — Radius of the cylinder at the top. Default is 1.
    - radiusBottom — Radius of the cylinder at the bottom. Default is 1.
    - height — Height of the cylinder. Default is 1.
    + radiusTop — Radius of the cylinder at the top. Default is `1`.
    + radiusBottom — Radius of the cylinder at the bottom. Default is `1`.
    + height — Height of the cylinder. Default is `1`.
    radialSegments — Number of segmented faces around the circumference of the - cylinder. Default is 32
    + cylinder. Default is `32`
    heightSegments — Number of rows of faces along the height of the cylinder. - Default is 1.
    + Default is `1`.
    openEnded — A Boolean indicating whether the ends of the cylinder are open or capped. Default is false, meaning capped.
    thetaStart — Start angle for first segment, default = 0 (three o'clock position).
    thetaLength — The central angle, often called theta, of the circular - sector. The default is 2*Pi, which makes for a complete cylinder. + sector. The default is `2`*Pi, which makes for a complete cylinder.

    Properties

    diff --git a/docs/api/en/geometries/DodecahedronGeometry.html b/docs/api/en/geometries/DodecahedronGeometry.html index 58caebf9d07342..28f2b687cd19d5 100644 --- a/docs/api/en/geometries/DodecahedronGeometry.html +++ b/docs/api/en/geometries/DodecahedronGeometry.html @@ -38,8 +38,8 @@

    Constructor

    [name]([param:Float radius], [param:Integer detail])

    - radius — Radius of the dodecahedron. Default is 1.
    - detail — Default is 0. Setting this to a value greater than 0 adds + radius — Radius of the dodecahedron. Default is `1`.
    + detail — Default is `0`. Setting this to a value greater than `0` adds vertices making it no longer a dodecahedron.

    diff --git a/docs/api/en/geometries/ExtrudeGeometry.html b/docs/api/en/geometries/ExtrudeGeometry.html index ec228f0149e79e..867e3018d4b196 100644 --- a/docs/api/en/geometries/ExtrudeGeometry.html +++ b/docs/api/en/geometries/ExtrudeGeometry.html @@ -72,19 +72,19 @@

    [name]([param:Array shapes], [param:Object options])

    • - curveSegments — int. Number of points on the curves. Default is 12. + curveSegments — int. Number of points on the curves. Default is `12`.
    • steps — int. Number of points used for subdividing segments along the - depth of the extruded spline. Default is 1. + depth of the extruded spline. Default is `1`.
    • -
    • depth — float. Depth to extrude the shape. Default is 1.
    • +
    • depth — float. Depth to extrude the shape. Default is `1`.
    • bevelEnabled — bool. Apply beveling to the shape. Default is true.
    • bevelThickness — float. How deep into the original shape the bevel goes. - Default is 0.2. + Default is `0.2`.
    • bevelSize — float. Distance from the shape outline that the bevel @@ -92,9 +92,9 @@

      [name]([param:Array shapes], [param:Object options])

    • bevelOffset — float. Distance from the shape outline that the bevel - starts. Default is 0. + starts. Default is `0`.
    • -
    • bevelSegments — int. Number of bevel layers. Default is 3.
    • +
    • bevelSegments — int. Number of bevel layers. Default is `3`.
    • extrudePath — THREE.Curve. A 3D spline path along which the shape should be extruded. Bevels not supported for path extrusion. diff --git a/docs/api/en/geometries/IcosahedronGeometry.html b/docs/api/en/geometries/IcosahedronGeometry.html index d5be5440e54103..fe5ade45fa264a 100644 --- a/docs/api/en/geometries/IcosahedronGeometry.html +++ b/docs/api/en/geometries/IcosahedronGeometry.html @@ -37,8 +37,8 @@

      Constructor

      [name]([param:Float radius], [param:Integer detail])

      - radius — Default is 1.
      - detail — Default is 0. Setting this to a value greater than 0 adds more + radius — Default is `1`.
      + detail — Default is `0`. Setting this to a value greater than `0` adds more vertices making it no longer an icosahedron. When detail is greater than 1, it's effectively a sphere.

      diff --git a/docs/api/en/geometries/LatheGeometry.html b/docs/api/en/geometries/LatheGeometry.html index 088c0f16b3ab82..e177e55d615b79 100644 --- a/docs/api/en/geometries/LatheGeometry.html +++ b/docs/api/en/geometries/LatheGeometry.html @@ -61,7 +61,7 @@

      creates a simple diamond shape.
      segments — the number of circumference segments to generate. Default is 12.
      - phiStart — the starting angle in radians. Default is 0.
      + phiStart — the starting angle in radians. Default is `0`.
      phiLength — the radian (0 to 2PI) range of the lathed section 2PI is a closed lathe, less than 2PI is a portion. Default is 2PI.

      diff --git a/docs/api/en/geometries/OctahedronGeometry.html b/docs/api/en/geometries/OctahedronGeometry.html index 4db513f8abc642..b72649a29ce8b4 100644 --- a/docs/api/en/geometries/OctahedronGeometry.html +++ b/docs/api/en/geometries/OctahedronGeometry.html @@ -36,8 +36,8 @@

      Constructor

      [name]([param:Float radius], [param:Integer detail])

      - radius — Radius of the octahedron. Default is 1.
      - detail — Default is 0. Setting this to a value greater than zero add + radius — Radius of the octahedron. Default is `1`.
      + detail — Default is `0`. Setting this to a value greater than zero add vertices making it no longer an octahedron.

      diff --git a/docs/api/en/geometries/PlaneGeometry.html b/docs/api/en/geometries/PlaneGeometry.html index acb046c6f7e1ae..b68be0f3bc62ee 100644 --- a/docs/api/en/geometries/PlaneGeometry.html +++ b/docs/api/en/geometries/PlaneGeometry.html @@ -45,14 +45,13 @@

      Code Example

      Constructor

      - [name]([param:Float width], [param:Float height], [param:Integer - widthSegments], [param:Integer heightSegments]) + [name]([param:Float width], [param:Float height], [param:Integer widthSegments], [param:Integer heightSegments])

      - width — Width along the X axis. Default is 1.
      - height — Height along the Y axis. Default is 1.
      - widthSegments — Optional. Default is 1.
      - heightSegments — Optional. Default is 1. + width — Width along the X axis. Default is `1`.
      + height — Height along the Y axis. Default is `1`.
      + widthSegments — Optional. Default is `1`.
      + heightSegments — Optional. Default is `1`.

      Properties

      diff --git a/docs/api/en/geometries/RingGeometry.html b/docs/api/en/geometries/RingGeometry.html index e3bd23a1f49e98..ea9324d3ce2f5a 100644 --- a/docs/api/en/geometries/RingGeometry.html +++ b/docs/api/en/geometries/RingGeometry.html @@ -45,12 +45,12 @@

      [param:Integer thetaSegments], [param:Integer phiSegments], [param:Float thetaStart], [param:Float thetaLength])

      - innerRadius — Default is 0.5.
      - outerRadius — Default is 1.
      + innerRadius — Default is `0.5`.
      + outerRadius — Default is `1`.
      thetaSegments — Number of segments. A higher number means the ring will be - more round. Minimum is 3. Default is 32.
      - phiSegments — Minimum is 1. Default is 1.
      - thetaStart — Starting angle. Default is 0.
      + more round. Minimum is `3`. Default is `32`.
      + phiSegments — Number of segments per ring segment. Minimum is `1`. Default is `1`.
      + thetaStart — Starting angle. Default is `0`.
      thetaLength — Central angle. Default is Math.PI * 2.

      diff --git a/docs/api/en/geometries/SphereGeometry.html b/docs/api/en/geometries/SphereGeometry.html index 91b904d89d7b87..f5ad3a18e22471 100644 --- a/docs/api/en/geometries/SphereGeometry.html +++ b/docs/api/en/geometries/SphereGeometry.html @@ -49,15 +49,15 @@

      - radius — sphere radius. Default is 1.
      - widthSegments — number of horizontal segments. Minimum value is 3, and the - default is 32.
      - heightSegments — number of vertical segments. Minimum value is 2, and the - default is 16.
      - phiStart — specify horizontal starting angle. Default is 0.
      + radius — sphere radius. Default is `1`.
      + widthSegments — number of horizontal segments. Minimum value is `3`, and the + default is `32`.
      + heightSegments — number of vertical segments. Minimum value is `2`, and the + default is `16`.
      + phiStart — specify horizontal starting angle. Default is `0`.
      phiLength — specify horizontal sweep angle size. Default is Math.PI * 2.
      - thetaStart — specify vertical starting angle. Default is 0.
      + thetaStart — specify vertical starting angle. Default is `0`.
      thetaLength — specify vertical sweep angle size. Default is Math.PI.

      diff --git a/docs/api/en/geometries/TetrahedronGeometry.html b/docs/api/en/geometries/TetrahedronGeometry.html index ccbd9f09ebc5d2..b3384b81f3de8f 100644 --- a/docs/api/en/geometries/TetrahedronGeometry.html +++ b/docs/api/en/geometries/TetrahedronGeometry.html @@ -37,8 +37,8 @@

      Constructor

      [name]([param:Float radius], [param:Integer detail])

      - radius — Radius of the tetrahedron. Default is 1.
      - detail — Default is 0. Setting this to a value greater than 0 adds + radius — Radius of the tetrahedron. Default is `1`.
      + detail — Default is `0`. Setting this to a value greater than `0` adds vertices making it no longer a tetrahedron.

      diff --git a/docs/api/en/geometries/TorusGeometry.html b/docs/api/en/geometries/TorusGeometry.html index 709f39520bded9..a50785febb38e7 100644 --- a/docs/api/en/geometries/TorusGeometry.html +++ b/docs/api/en/geometries/TorusGeometry.html @@ -48,10 +48,10 @@

      radius - Radius of the torus, from the center of the torus to the center - of the tube. Default is 1.
      - tube — Radius of the tube. Default is 0.4.
      - radialSegments — Default is 12
      - tubularSegments — Default is 48.
      + of the tube. Default is `1`.
      + tube — Radius of the tube. Must be smaller than `radius`. Default is `0.4`.
      + radialSegments — Default is `12`
      + tubularSegments — Default is `48`.
      arc — Central angle. Default is Math.PI * 2.

      diff --git a/docs/api/en/geometries/TorusKnotGeometry.html b/docs/api/en/geometries/TorusKnotGeometry.html index 48f342ce407d68..a4bee441e62e18 100644 --- a/docs/api/en/geometries/TorusKnotGeometry.html +++ b/docs/api/en/geometries/TorusKnotGeometry.html @@ -52,17 +52,17 @@

      [param:Integer q])

        -
      • radius - Radius of the torus. Default is 1.
      • -
      • tube — Radius of the tube. Default is 0.4.
      • -
      • tubularSegments — Default is 64.
      • -
      • radialSegments — Default is 8.
      • +
      • radius - Radius of the torus. Default is `1`.
      • +
      • tube — Radius of the tube. Default is `0.4`.
      • +
      • tubularSegments — Default is `64`.
      • +
      • radialSegments — Default is `8`.
      • p — This value determines, how many times the geometry winds around its - axis of rotational symmetry. Default is 2. + axis of rotational symmetry. Default is `2`.
      • q — This value determines, how many times the geometry winds around a - circle in the interior of the torus. Default is 3. + circle in the interior of the torus. Default is `3`.
      diff --git a/docs/api/en/helpers/ArrowHelper.html b/docs/api/en/helpers/ArrowHelper.html index da0baf740e4d3a..42d3acb1b21a06 100644 --- a/docs/api/en/helpers/ArrowHelper.html +++ b/docs/api/en/helpers/ArrowHelper.html @@ -46,9 +46,9 @@

      [page:Number hex] -- hexadecimal value to define color. Default is 0xffff00.
      [page:Number headLength] -- The length of the head of the arrow. Default - is 0.2 * length.
      + is `0.2` * length.
      [page:Number headWidth] -- The width of the head of the arrow. Default is - 0.2 * headLength.
      + `0.2` * headLength.

      Properties

      diff --git a/docs/api/en/helpers/DirectionalLightHelper.html b/docs/api/en/helpers/DirectionalLightHelper.html index 29d79f95f19314..1be40a24fc89bc 100644 --- a/docs/api/en/helpers/DirectionalLightHelper.html +++ b/docs/api/en/helpers/DirectionalLightHelper.html @@ -21,6 +21,8 @@

      Code Example

      const light = new THREE.DirectionalLight( 0xFFFFFF ); + scene.add( light ); + const helper = new THREE.DirectionalLightHelper( light, 5 ); scene.add( helper ); diff --git a/docs/api/en/helpers/GridHelper.html b/docs/api/en/helpers/GridHelper.html index 956101ebb1beeb..4441ab7ea535d6 100644 --- a/docs/api/en/helpers/GridHelper.html +++ b/docs/api/en/helpers/GridHelper.html @@ -7,7 +7,7 @@ - [page:Object3D] → [page:Line] → + [page:Object3D] → [page:Line] → [page:LineSegments] →

      [name]

      @@ -36,8 +36,8 @@

      [name]( [param:number size], [param:Number divisions], [param:Color colorCenterLine], [param:Color colorGrid] )

      - size -- The size of the grid. Default is 10.
      - divisions -- The number of divisions across the grid. Default is 10. + size -- The size of the grid. Default is `10`.
      + divisions -- The number of divisions across the grid. Default is `10`.
      colorCenterLine -- The color of the centerline. This can be a [page:Color], a hexadecimal value and an CSS-Color name. Default is diff --git a/docs/api/en/helpers/PolarGridHelper.html b/docs/api/en/helpers/PolarGridHelper.html index e365305bd03167..ad5a8af6399d35 100644 --- a/docs/api/en/helpers/PolarGridHelper.html +++ b/docs/api/en/helpers/PolarGridHelper.html @@ -7,7 +7,7 @@ - [page:Object3D] → [page:Line] → + [page:Object3D] → [page:Line] → [page:LineSegments] →

      [name]

      @@ -39,13 +39,13 @@

      radius -- The radius of the polar grid. This can be any positive number. - Default is 10.
      + Default is `10`.
      sectors -- The number of sectors the grid will be divided into. This can - be any positive integer. Default is 16.
      + be any positive integer. Default is `16`.
      rings -- The number of rings. This can be any positive integer. Default is 8.
      divisions -- The number of line segments used for each circle. This can be - any positive integer that is 3 or greater. Default is 64.
      + any positive integer that is 3 or greater. Default is `64`.
      color1 -- The first color used for grid elements. This can be a [page:Color], a hexadecimal value and an CSS-Color name. Default is 0x444444
      diff --git a/docs/api/en/lights/AmbientLight.html b/docs/api/en/lights/AmbientLight.html index 70f903368ea92c..2b91e253cbb976 100644 --- a/docs/api/en/lights/AmbientLight.html +++ b/docs/api/en/lights/AmbientLight.html @@ -31,7 +31,7 @@

      [name]( [param:Integer color], [param:Float intensity] )

      [page:Integer color] - (optional) Numeric value of the RGB component of the color. Default is 0xffffff.
      [page:Float intensity] - (optional) Numeric value of the light's - strength/intensity. Default is 1.

      + strength/intensity. Default is `1`.

      Creates a new [name].

      diff --git a/docs/api/en/lights/AmbientLightProbe.html b/docs/api/en/lights/AmbientLightProbe.html deleted file mode 100644 index a36432eb4b787c..00000000000000 --- a/docs/api/en/lights/AmbientLightProbe.html +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - [page:Object3D] → [page:Light] → [page:LightProbe] - -

      [name]

      - -

      - Light probes are an alternative way of adding light to a 3D scene. - AmbientLightProbe is the light estimation data of a single ambient light - in the scene. For more information about light probes, go to - [page:LightProbe]. -

      - -

      Constructor

      - -

      [name]( [param:Color color], [param:Float intensity] )

      -

      - [page:Color color] - (optional) An instance of Color, string representing - a color or a number representing a color.
      - [page:Float intensity] - (optional) Numeric value of the light probe's - intensity. Default is 1.

      - - Creates a new [name]. -

      - -

      Properties

      -

      - See the base [page:LightProbe LightProbe] class for common properties. -

      - -

      [property:Boolean isAmbientLightProbe]

      -

      Read-only flag to check if a given object is of type [name].

      - -

      Methods

      -

      See the base [page:LightProbe LightProbe] class for common methods.

      -

      Source

      - -

      - [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] -

      - - diff --git a/docs/api/en/lights/DirectionalLight.html b/docs/api/en/lights/DirectionalLight.html index 707db4a3dab060..421e44a84459b7 100644 --- a/docs/api/en/lights/DirectionalLight.html +++ b/docs/api/en/lights/DirectionalLight.html @@ -66,7 +66,7 @@

      [name]( [param:Integer color], [param:Float intensity] )

      [page:Integer color] - (optional) hexadecimal color of the light. Default is 0xffffff (white).
      [page:Float intensity] - (optional) numeric value of the light's - strength/intensity. Default is 1.

      + strength/intensity. Default is `1`.

      Creates a new [name].

      diff --git a/docs/api/en/lights/HemisphereLight.html b/docs/api/en/lights/HemisphereLight.html index 4f1aa4102a4855..f25215a38f68fa 100644 --- a/docs/api/en/lights/HemisphereLight.html +++ b/docs/api/en/lights/HemisphereLight.html @@ -44,7 +44,7 @@

      [page:Integer groundColor] - (optional) hexadecimal color of the ground. Default is 0xffffff.
      [page:Float intensity] - (optional) numeric value of the light's - strength/intensity. Default is 1.

      + strength/intensity. Default is `1`.

      Creates a new [name].

      diff --git a/docs/api/en/lights/HemisphereLightProbe.html b/docs/api/en/lights/HemisphereLightProbe.html deleted file mode 100644 index 50eb258fd4bdf7..00000000000000 --- a/docs/api/en/lights/HemisphereLightProbe.html +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - [page:Object3D] → [page:Light] → [page:LightProbe] - -

      [name]

      - -

      - Light probes are an alternative way of adding light to a 3D scene. - HemisphereLightProbe is the light estimation data of a single hemisphere - light in the scene. For more information about light probes, go to - [page:LightProbe]. -

      - -

      Constructor

      - -

      - [name]( [param:Color skyColor], [param:Color groundColor], [param:Float intensity] ) -

      -

      - [page:Color skyColor] - (optional) An instance of Color, string - representing a color or a number representing a color.
      - [page:Color groundColor] - (optional) An instance of Color, string - representing a color or a number representing a color.
      - [page:Float intensity] - (optional) Numeric value of the light probe's - intensity. Default is 1.

      - - Creates a new [name]. -

      - -

      Properties

      -

      - See the base [page:LightProbe LightProbe] class for common properties. -

      - -

      [property:Boolean isHemisphereLightProbe]

      -

      Read-only flag to check if a given object is of type [name].

      - -

      Methods

      -

      See the base [page:LightProbe LightProbe] class for common methods.

      -

      Source

      - -

      - [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] -

      - - diff --git a/docs/api/en/lights/Light.html b/docs/api/en/lights/Light.html index dec5eca152768e..934bdb55eae2bc 100644 --- a/docs/api/en/lights/Light.html +++ b/docs/api/en/lights/Light.html @@ -23,7 +23,7 @@

      [name]( [param:Integer color], [param:Float intensity] )

      [page:Integer color] - (optional) hexadecimal color of the light. Default is 0xffffff (white).
      [page:Float intensity] - (optional) numeric value of the light's - strength/intensity. Default is 1.

      + strength/intensity. Default is `1`.

      Creates a new [name]. Note that this is not intended to be called directly (use one of derived classes instead).

      @@ -40,8 +40,7 @@

      [property:Color color]

      [property:Float intensity]

      The light's intensity, or strength.
      - When [page:WebGLRenderer.useLegacyLights legacy lighting mode] is disabled, - the units of intensity depend on the type of light.
      + The units of intensity depend on the type of light.
      Default - `1.0`.

      diff --git a/docs/api/en/lights/LightProbe.html b/docs/api/en/lights/LightProbe.html index e04787a4d8980f..35ae41af0bb4cf 100644 --- a/docs/api/en/lights/LightProbe.html +++ b/docs/api/en/lights/LightProbe.html @@ -47,7 +47,7 @@

      [name]( [param:SphericalHarmonics3 sh], [param:Float intensity] )

      [page:SphericalHarmonics3 sh] - (optional) An instance of [page:SphericalHarmonics3].
      [page:Float intensity] - (optional) Numeric value of the light probe's - intensity. Default is 1.

      + intensity. Default is `1`.

      Creates a new [name].

      diff --git a/docs/api/en/lights/PointLight.html b/docs/api/en/lights/PointLight.html index ba4665362708b1..ba855516bc32ec 100644 --- a/docs/api/en/lights/PointLight.html +++ b/docs/api/en/lights/PointLight.html @@ -31,7 +31,7 @@

      Code Example

      Examples

      - [example:webgl_lights_pointlights lights / pointlights ]
      + [example:webgpu_lights_pointlights lights / pointlights ]
      [example:webgl_effects_anaglyph effects / anaglyph ]
      [example:webgl_geometry_text geometry / text ]
      [example:webgl_lensflares lensflares ] @@ -46,11 +46,11 @@

      [page:Integer color] - (optional) hexadecimal color of the light. Default is 0xffffff (white).
      [page:Float intensity] - (optional) numeric value of the light's - strength/intensity. Default is 1.
      - [page:Number distance] - Maximum range of the light. Default is 0 (no + strength/intensity. Default is `1`.
      + [page:Number distance] - Maximum range of the light. Default is `0` (no limit).
      [page:Float decay] - The amount the light dims along the distance of the - light. Default is 2.

      + light. Default is `2`.

      Creates a new [name].

      @@ -75,12 +75,6 @@

      [property:Float decay]

      [property:Float distance]

      - `Default mode` — When distance is zero, light does not attenuate. When - distance is non-zero, light will attenuate linearly from maximum intensity - at the light's position down to zero at this distance from the light. -

      -

      - When [page:WebGLRenderer.useLegacyLights legacy lighting mode] is disabled — When distance is zero, light will attenuate according to inverse-square law to infinite distance. When distance is non-zero, light will attenuate according to inverse-square law until near the distance cutoff, where it @@ -91,19 +85,15 @@

      [property:Float distance]

      [property:Float intensity]

      - The light's intensity. Default is `1`.
      - When [page:WebGLRenderer.useLegacyLights legacy lighting mode] is disabled, - intensity is the luminous intensity of the light measured in candela - (cd).

      - + The light's luminous intensity measured in candela (cd). Default is `1`. +

      Changing the intensity will also change the light's power.

      [property:Float power]

      The light's power.
      - When [page:WebGLRenderer.useLegacyLights legacy lighting mode] is disabled, - power is the luminous power of the light measured in lumens (lm). + Power is the luminous power of the light measured in lumens (lm).

      Changing the power will also change the light's intensity. @@ -114,10 +104,10 @@

      [property:PointLightShadow shadow]

      A [page:PointLightShadow] used to calculate shadows for this light.

      The lightShadow's [page:LightShadow.camera camera] is set to a - [page:PerspectiveCamera] with [page:PerspectiveCamera.fov fov] of 90, - [page:PerspectiveCamera.aspect aspect] of 1, [page:PerspectiveCamera.near near] - clipping plane at 0.5 and [page:PerspectiveCamera.far far] clipping - plane at 500. + [page:PerspectiveCamera] with [page:PerspectiveCamera.fov fov] of `90`, + [page:PerspectiveCamera.aspect aspect] of `1`, [page:PerspectiveCamera.near near] + clipping plane at `0.5` and [page:PerspectiveCamera.far far] clipping + plane at `500`.

      Methods

      diff --git a/docs/api/en/lights/RectAreaLight.html b/docs/api/en/lights/RectAreaLight.html index 5c0c0e3b698007..f8d621eeb0709f 100644 --- a/docs/api/en/lights/RectAreaLight.html +++ b/docs/api/en/lights/RectAreaLight.html @@ -58,9 +58,9 @@

      [page:Integer color] - (optional) hexadecimal color of the light. Default is 0xffffff (white).
      [page:Float intensity] - (optional) the light's intensity, or brightness. - Default is 1.
      - [page:Float width] - (optional) width of the light. Default is 10.
      - [page:Float height] - (optional) height of the light. Default is 10.

      + Default is `1`.
      + [page:Float width] - (optional) width of the light. Default is `10`.
      + [page:Float height] - (optional) height of the light. Default is `10`.

      Creates a new [name].

      @@ -73,10 +73,8 @@

      [property:Float height]

      [property:Float intensity]

      - The light's intensity. Default is `1`.
      - When [page:WebGLRenderer.useLegacyLights legacy lighting mode] is disabled, - intensity is the luminance (brightness) of the light measured in nits - (cd/m^2).

      + The light's intensity. It is the luminance (brightness) of the light measured in nits (cd/m^2). + Default is `1`.

      Changing the intensity will also change the light's power.

      @@ -87,8 +85,7 @@

      [property:Boolean isRectAreaLight]

      [property:Float power]

      The light's power.
      - When [page:WebGLRenderer.useLegacyLights legacy lighting mode] is disabled, - power is the luminous power of the light measured in lumens (lm). + Power is the luminous power of the light measured in lumens (lm).

      Changing the power will also change the light's intensity. diff --git a/docs/api/en/lights/SpotLight.html b/docs/api/en/lights/SpotLight.html index 5cfc28e18b3b34..776deedc5263d4 100644 --- a/docs/api/en/lights/SpotLight.html +++ b/docs/api/en/lights/SpotLight.html @@ -52,13 +52,13 @@

      [page:Integer color] - (optional) hexadecimal color of the light. Default is 0xffffff (white).
      [page:Float intensity] - (optional) numeric value of the light's - strength/intensity. Default is 1.
      - [page:Float distance] - Maximum range of the light. Default is 0 (no + strength/intensity. Default is `1`.
      + [page:Float distance] - Maximum range of the light. Default is `0` (no limit).
      [page:Radians angle] - Maximum angle of light dispersion from its direction whose upper bound is Math.PI/2.
      [page:Float penumbra] - Percent of the spotlight cone that is attenuated - due to penumbra. Takes values between zero and 1. Default is zero.
      + due to penumbra. Takes values between zero and `1`. Default is zero.
      [page:Float decay] - The amount the light dims along the distance of the light.

      Creates a new [name]. @@ -90,12 +90,6 @@

      [property:Float decay]

      [property:Float distance]

      - `Default mode` — When distance is zero, light does not attenuate. When - distance is non-zero, light will attenuate linearly from maximum intensity - at the light's position down to zero at this distance from the light. -

      -

      - When [page:WebGLRenderer.useLegacyLights legacy lighting mode] is disabled — When distance is zero, light will attenuate according to inverse-square law to infinite distance. When distance is non-zero, light will attenuate according to inverse-square law until near the distance cutoff, where it @@ -106,10 +100,8 @@

      [property:Float distance]

      [property:Float intensity]

      - The light's intensity. Default is `1`.
      - When [page:WebGLRenderer.useLegacyLights legacy lighting mode] is disabled, - intensity is the luminous intensity of the light measured in candela - (cd).

      + The light's luminous intensity measured in candela (cd). Default is `1`. +

      Changing the intensity will also change the light's power.

      @@ -119,7 +111,7 @@

      [property:Boolean isSpotLight]

      [property:Float penumbra]

      Percent of the spotlight cone that is attenuated due to penumbra. Takes - values between zero and 1. The default is `0.0`. + values between zero and `1`. The default is `0.0`.

      [property:Vector3 position]

      @@ -131,8 +123,7 @@

      [property:Vector3 position]

      [property:Float power]

      The light's power.
      - When [page:WebGLRenderer.useLegacyLights legacy lighting mode] is disabled, - power is the luminous power of the light measured in lumens (lm). + Power is the luminous power of the light measured in lumens (lm).

      Changing the power will also change the light's intensity.

      @@ -143,7 +134,7 @@

      [property:SpotLightShadow shadow]

      [property:Object3D target]

      The Spotlight points from its [page:.position position] to - target.position. The default position of the target is *(0, 0, 0)*.
      + target.position. The default position of the target is `(0, 0, 0)`.
      *Note*: For the target's position to be changed to anything other than the default, it must be added to the [page:Scene scene] using scene.add( light.target ); @@ -166,8 +157,7 @@

      [property:Texture map]

      color is mixed with the RGB value of this texture, with a ratio corresponding to its alpha value. The cookie-like masking effect is reproduced using pixel values (0, 0, 0, 1-cookie_value). *Warning*: - [param:SpotLight map] is disabled if [param:SpotLight castShadow] is - *false*. + [page:.map] is disabled if [page:.castShadow] is *false*.

      Methods

      diff --git a/docs/api/en/lights/shadows/DirectionalLightShadow.html b/docs/api/en/lights/shadows/DirectionalLightShadow.html index 5201c48061a7c8..cb28f9c72c2ca1 100644 --- a/docs/api/en/lights/shadows/DirectionalLightShadow.html +++ b/docs/api/en/lights/shadows/DirectionalLightShadow.html @@ -80,10 +80,10 @@

      [property:Camera camera]

      The default is an [page:OrthographicCamera] with [page:OrthographicCamera.left left] and [page:OrthographicCamera.bottom bottom] - set to -5, [page:OrthographicCamera.right right] and - [page:OrthographicCamera.top top] set to 5, the - [page:OrthographicCamera.near near] clipping plane at 0.5 and the - [page:OrthographicCamera.far far] clipping plane at 500. + set to `-5`, [page:OrthographicCamera.right right] and + [page:OrthographicCamera.top top] set to `5`, the + [page:OrthographicCamera.near near] clipping plane at `0.5` and the + [page:OrthographicCamera.far far] clipping plane at `500`.

      [property:Boolean isDirectionalLightShadow]

      diff --git a/docs/api/en/lights/shadows/LightShadow.html b/docs/api/en/lights/shadows/LightShadow.html index e4576a6d66403c..aded7048876643 100644 --- a/docs/api/en/lights/shadows/LightShadow.html +++ b/docs/api/en/lights/shadows/LightShadow.html @@ -40,13 +40,18 @@

      [property:Float bias]

      Shadow map bias, how much to add or subtract from the normalized depth when deciding whether a surface is in shadow.
      - The default is 0. Very tiny adjustments here (in the order of 0.0001) may + The default is `0`. Very tiny adjustments here (in the order of `0.0001`) may help reduce artifacts in shadows

      [property:Integer blurSamples]

      The amount of samples to use when blurring a VSM shadow map.

      +

      [property:Float intensity]

      +

      + The intensity of the shadow. The default is `1`. Valid values are in the range `[0, 1]`. +

      +

      [property:WebGLRenderTarget map]

      The depth map generated using the internal camera; a location beyond a @@ -68,7 +73,7 @@

      [property:Vector2 mapSize]

      Values must be powers of 2, up to the [page:WebGLRenderer.capabilities].maxTextureSize for a given device, although the width and height don't have to be the same (so, for example, - (512, 1024) is valid). The default is *( 512, 512 )*. + (512, 1024) is valid). The default is `( 512, 512 )`.

      [property:Matrix4 matrix]

      @@ -89,7 +94,7 @@

      [property:Boolean needsUpdate]

      [property:Float normalBias]

      Defines how much the position used to query the shadow map is offset along - the object normal. The default is 0. Increasing this value can be used to + the object normal. The default is `0`. Increasing this value can be used to reduce shadow acne especially in large scenes where light shines onto geometry at a shallow angle. The cost is that shadows may appear distorted. diff --git a/docs/api/en/loaders/BufferGeometryLoader.html b/docs/api/en/loaders/BufferGeometryLoader.html index 302197cd542fdf..50a22c2c65b9e4 100644 --- a/docs/api/en/loaders/BufferGeometryLoader.html +++ b/docs/api/en/loaders/BufferGeometryLoader.html @@ -46,10 +46,6 @@

      Code Example

      );
      -

      Examples

      - -

      [example:webgl_performance WebGL / performance]

      -

      Constructor

      [name]( [param:LoadingManager manager] )

      diff --git a/docs/api/en/loaders/CubeTextureLoader.html b/docs/api/en/loaders/CubeTextureLoader.html index 5656f0505897ba..e769033a0876f5 100644 --- a/docs/api/en/loaders/CubeTextureLoader.html +++ b/docs/api/en/loaders/CubeTextureLoader.html @@ -12,8 +12,13 @@

      [name]

      - Class for loading a [page:CubeTexture CubeTexture]. This uses the - [page:ImageLoader] internally for loading files. + [name] can be used to load cube maps. The loader returns an instance of [page:CubeTexture] and expects the cube map to + be defined as six separate images representing the sides of a cube. Other cube map definitions like vertical and horizontal cross, + column and row layouts are not supported. +

      +

      + The loaded [page:CubeTexture] is in sRGB color space. Meaning the [page:Texture.colorSpace colorSpace] + property is set to `THREE.SRGBColorSpace` by default.

      Code Example

      diff --git a/docs/api/en/loaders/ImageBitmapLoader.html b/docs/api/en/loaders/ImageBitmapLoader.html index bef3870017380a..3b287438fb09c1 100644 --- a/docs/api/en/loaders/ImageBitmapLoader.html +++ b/docs/api/en/loaders/ImageBitmapLoader.html @@ -41,7 +41,7 @@

      Code Example

      // load a image resource loader.load( // resource URL - 'textures/skyboxsun25degtest.png', + 'image.png', // onLoad callback function ( imageBitmap ) { diff --git a/docs/api/en/loaders/ImageLoader.html b/docs/api/en/loaders/ImageLoader.html index 99ee62c78e9b3b..b65d632062fbb4 100644 --- a/docs/api/en/loaders/ImageLoader.html +++ b/docs/api/en/loaders/ImageLoader.html @@ -25,7 +25,7 @@

      Code Example

      // load a image resource loader.load( // resource URL - 'textures/skyboxsun25degtest.png', + 'image.png', // onLoad callback function ( image ) { diff --git a/docs/api/en/loaders/Loader.html b/docs/api/en/loaders/Loader.html index 3df6c1db7b3277..e74537b7d7cdbe 100644 --- a/docs/api/en/loaders/Loader.html +++ b/docs/api/en/loaders/Loader.html @@ -63,7 +63,7 @@

      Methods

      [method:undefined load]()

      - This method needs to be implement by all concrete loaders. It holds the + This method needs to be implemented by all concrete loaders. It holds the logic for loading the asset from the backend.

      @@ -92,7 +92,7 @@

      [method:undefined parse]()

      - This method needs to be implement by all concrete loaders. It holds the + This method needs to be implemented by all concrete loaders. It holds the logic for parsing the asset into three.js entities.

      diff --git a/docs/api/en/loaders/LoaderUtils.html b/docs/api/en/loaders/LoaderUtils.html index 1d23f4921a447c..508b638e090c1e 100644 --- a/docs/api/en/loaders/LoaderUtils.html +++ b/docs/api/en/loaders/LoaderUtils.html @@ -13,13 +13,6 @@

      [name]

      Functions

      -

      [method:String decodeText]( [param:TypedArray array] )

      -

      [page:TypedArray array] — A stream of bytes as a typed array.

      -

      - The function takes a stream of bytes as input and returns a string - representation. -

      -

      [method:String extractUrlBase]( [param:String url] )

      [page:String url] — The url to extract the base url from.

      Extract the base from the URL.

      diff --git a/docs/api/en/loaders/ObjectLoader.html b/docs/api/en/loaders/ObjectLoader.html index 85276489035e2c..c1c491ebbba4e9 100644 --- a/docs/api/en/loaders/ObjectLoader.html +++ b/docs/api/en/loaders/ObjectLoader.html @@ -53,7 +53,7 @@

      Code Example

      Examples

      -

      [example:webgl_materials_lightmap WebGL / materials / lightmap]

      +

      [example:webgpu_materials_lightmap WebGL / materials / lightmap]

      Constructor

      diff --git a/docs/api/en/loaders/managers/LoadingManager.html b/docs/api/en/loaders/managers/LoadingManager.html index c93af80d0d7834..ccfce45920b647 100644 --- a/docs/api/en/loaders/managers/LoadingManager.html +++ b/docs/api/en/loaders/managers/LoadingManager.html @@ -44,7 +44,7 @@

      Code Example

      console.log( 'There was an error loading ' + url ); }; - const loader = new THREE.OBJLoader( manager ); + const loader = new OBJLoader( manager ); loader.load( 'file.obj', function ( object ) { // } ); @@ -74,7 +74,7 @@

      Code Example

      } ); // Load as usual, then revoke the blob URLs. - const loader = new THREE.GLTFLoader( manager ); + const loader = new GLTFLoader( manager ); loader.load( 'fish.gltf', (gltf) => { scene.add( gltf.scene ); @@ -87,8 +87,7 @@

      Examples

      [example:webgl_loader_obj WebGL / loader / obj]
      - [example:webgl_materials_physical_reflectivity WebGL / materials / physical / reflectivity]
      - [example:webgl_postprocessing_outline WebGL / postprocesing / outline] + [example:webgl_postprocessing_outline WebGL / postprocessing / outline]

      Constructor

      diff --git a/docs/api/en/materials/LineBasicMaterial.html b/docs/api/en/materials/LineBasicMaterial.html index fcb4b5389597fe..3f24c3b601a688 100644 --- a/docs/api/en/materials/LineBasicMaterial.html +++ b/docs/api/en/materials/LineBasicMaterial.html @@ -36,8 +36,6 @@

      Examples

      [example:webgl_interactive_voxelpainter WebGL / interactive / voxelpainter]
      [example:webgl_lines_colors WebGL / lines / colors]
      [example:webgl_lines_dashed WebGL / lines / dashed]
      - [example:webgl_lines_sphere WebGL / lines / sphere]
      - [example:webgl_materials WebGL / materials]
      [example:physics_ammo_rope physics / ammo / rope]

      @@ -71,7 +69,9 @@

      [property:Float linewidth]

      Due to limitations of the [link:https://www.khronos.org/registry/OpenGL/specs/gl/glspec46.core.pdf OpenGL Core Profile] with the [page:WebGLRenderer WebGL] renderer on most - platforms linewidth will always be 1 regardless of the set value. + platforms linewidth will always be `1` regardless of the set value.

      + + If you need wider lines, consider using [page:Line2] or [page:LineSegments2] with [page:LineMaterial].

      [property:String linecap]

      diff --git a/docs/api/en/materials/LineDashedMaterial.html b/docs/api/en/materials/LineDashedMaterial.html index 3a0741fde92e0b..bcbf034567f67a 100644 --- a/docs/api/en/materials/LineDashedMaterial.html +++ b/docs/api/en/materials/LineDashedMaterial.html @@ -12,7 +12,8 @@

      [name]

      - A material for drawing wireframe-style geometries with dashed lines. + A material for drawing wireframe-style geometries with dashed lines.
      + Note: You must call [page:Line.computeLineDistances]() when using [name].

      Code Example

      diff --git a/docs/api/en/materials/Material.html b/docs/api/en/materials/Material.html index 5b43f8d5102a5c..d84a2556af27a1 100644 --- a/docs/api/en/materials/Material.html +++ b/docs/api/en/materials/Material.html @@ -27,6 +27,14 @@

      [name]()

      Properties

      +

      [property:Boolean alphaHash]

      +

      + Enables alpha hashed transparency, an alternative to [page:.transparent] or [page:.alphaTest]. + The material will not be rendered if opacity is lower than a random threshold. + Randomization introduces some grain or noise, but approximates alpha blending without + the associated problems of sorting. Using TAARenderPass can reduce the resulting noise. +

      +

      [property:Float alphaTest]

      Sets the alpha value to be used when running an alpha test. The material @@ -37,8 +45,23 @@

      [property:Float alphaTest]

      [property:Boolean alphaToCoverage]

      Enables alpha to coverage. Can only be used with MSAA-enabled contexts - (meaning when the renderer was created with `antialias` parameter set to - `true`). Default is `false`. + (meaning when the renderer was created with *antialias* parameter set to + `true`). Enabling this will smooth aliasing on clip plane edges and alphaTest-clipped edges. + Default is `false`. +

      + +

      [property:Float blendAlpha]

      +

      + Represents the alpha value of the constant blend color. Default is `0`. + + This property has only an effect when using custom blending with [page:CustomBlendingEquation ConstantAlpha] or [page:CustomBlendingEquation OneMinusConstantAlpha]. +

      + +

      [property:Color blendColor]

      +

      + Represent the RGB values of the constant blend color. Default is `0x000000`.
      + + This property has only an effect when using custom blending with [page:CustomBlendingEquation ConstantColor] or [page:CustomBlendingEquation OneMinusConstantColor].

      [property:Integer blendDst]

      @@ -139,7 +162,8 @@

      [property:Integer depthFunc]

      [property:Boolean depthTest]

      Whether to have depth test enabled when rendering this material. Default - is `true`. + is `true`. When the depth test is disabled, the depth write will also be + implicitly disabled.

      [property:Boolean depthWrite]

      @@ -315,8 +339,9 @@

      [property:Integer side]

      [property:Boolean toneMapped]

      - Defines whether this material is tone mapped according to the renderer's - [page:WebGLRenderer.toneMapping toneMapping] setting. Default is `true`. + Defines whether this material is tone mapped according to the renderer's + [page:WebGLRenderer.toneMapping toneMapping] setting. It is ignored when rendering to a render target or using post processing. + Default is `true`.

      [property:Boolean transparent]

      @@ -325,7 +350,7 @@

      [property:Boolean transparent]

      rendering as transparent objects need special treatment and are rendered after non-transparent objects.
      When set to true, the extent to which the material is transparent is - controlled by setting its [page:Float opacity] property.
      + controlled by setting its [page:Float opacity] property. Default is `false`.

      @@ -361,6 +386,7 @@

      [property:Object userData]

      An object that can be used to store custom data about the Material. It should not hold references to functions as these will not be cloned. + Default is an empty object `{}`.

      Methods

      @@ -381,7 +407,7 @@

      [method:undefined dispose]()

      method whenever this instance is no longer used in your app.

      - Material textures must be be disposed of by the dispose() method of + Material textures must be disposed of by the dispose() method of [page:Texture Texture].

      @@ -391,12 +417,36 @@

      An optional callback that is executed immediately before the shader program is compiled. This function is called with the shader source code - as a parameter. Useful for the modification of built-in materials. + as a parameter. Useful for the modification of built-in materials, but the + recommended approach moving forward is to use `WebGPURenderer` with the new + Node Material system and [link:https://github.com/mrdoob/three.js/wiki/Three.js-Shading-Language TSL].

      Unlike properties, the callback is not supported by [page:Material.clone .clone](), [page:Material.copy .copy]() and [page:Material.toJSON .toJSON]().

      +

      + This callback is only supported in `WebGLRenderer` (not `WebGPURenderer`). +

      +

      + [example:webgl_materials_modified WebGL / materials / modified]
      + [example:webgl_shadow_contact WebGL / shadow / contact] +

      + +

      + [method:undefined onBeforeRender]( [param:WebGLRenderer renderer], [param:Scene scene], [param:Camera camera], [param:BufferGeometry geometry], [param:Object3D object], [param:Group group] ) +

      +

      + An optional callback that is executed immediately before the material is used to + render a 3D object. +

      +

      + Unlike properties, the callback is not supported by [page:Material.clone .clone](), + [page:Material.copy .copy]() and [page:Material.toJSON .toJSON](). +

      +

      + This callback is only supported in `WebGLRenderer` (not `WebGPURenderer`). +

      [method:String customProgramCacheKey]()

      diff --git a/docs/api/en/materials/MeshBasicMaterial.html b/docs/api/en/materials/MeshBasicMaterial.html index 998c4dddccee62..3cac37f9c274a1 100644 --- a/docs/api/en/materials/MeshBasicMaterial.html +++ b/docs/api/en/materials/MeshBasicMaterial.html @@ -78,8 +78,10 @@

      [property:Texture aoMap]

      [property:Float aoMapIntensity]

      - Intensity of the ambient occlusion effect. Default is 1. Zero is no - occlusion effect. + Intensity of the ambient occlusion effect. Range is 0-1, where `0` + disables ambient occlusion. Where intensity is `1` and the [page:.aoMap] + red channel is also `1`, ambient light is fully occluded on a surface. + Default is `1`.

      [property:Color color]

      @@ -99,6 +101,11 @@

      [property:Integer combine]

      [property:Texture envMap]

      The environment map. Default is null.

      +

      [property:Euler envMapRotation]

      +

      + The rotation of the environment map in radians. Default is `(0,0,0)`. +

      +

      [property:Boolean fog]

      Whether the material is affected by fog. Default is `true`.

      @@ -108,7 +115,7 @@

      [property:Texture lightMap]

      [property:Float lightMapIntensity]

      -

      Intensity of the baked light. Default is 1.

      +

      Intensity of the baked light. Default is `1`.

      [property:Texture map]

      @@ -119,8 +126,8 @@

      [property:Texture map]

      [property:Float reflectivity]

      How much the environment map affects the surface; also see - [page:.combine]. The default value is 1 and the valid range is between 0 - (no reflections) and 1 (full reflections). + [page:.combine]. The default value is `1` and the valid range is between `0` + (no reflections) and `1` (full reflections).

      [property:Float refractionRatio]

      @@ -128,7 +135,7 @@

      [property:Float refractionRatio]

      The index of refraction (IOR) of air (approximately 1) divided by the index of refraction of the material. It is used with environment mapping modes [page:Textures THREE.CubeRefractionMapping] and [page:Textures THREE.EquirectangularRefractionMapping]. - The refraction ratio should not exceed 1. Default is `0.98`. + The refraction ratio should not exceed `1`. Default is `0.98`.

      [property:Texture specularMap]

      @@ -162,11 +169,11 @@

      [property:String wireframeLinejoin]

      [property:Float wireframeLinewidth]

      - Controls wireframe thickness. Default is 1.

      + Controls wireframe thickness. Default is `1`.

      Due to limitations of the [link:https://www.khronos.org/registry/OpenGL/specs/gl/glspec46.core.pdf OpenGL Core Profile] - with the [page:WebGLRenderer WebGL] renderer on most platforms linewidth will always be 1 regardless of the set value. + with the [page:WebGLRenderer WebGL] renderer on most platforms linewidth will always be `1` regardless of the set value.

      Methods

      diff --git a/docs/api/en/materials/MeshDepthMaterial.html b/docs/api/en/materials/MeshDepthMaterial.html index ee14331785741a..01a0728ca18551 100644 --- a/docs/api/en/materials/MeshDepthMaterial.html +++ b/docs/api/en/materials/MeshDepthMaterial.html @@ -81,22 +81,20 @@

      [property:Float displacementScale]

      How much the displacement map affects the mesh (where black is no displacement, and white is maximum displacement). Without a displacement - map set, this value is not applied. Default is 1. + map set, this value is not applied. Default is `1`.

      [property:Float displacementBias]

      The offset of the displacement map's values on the mesh's vertices. - Without a displacement map set, this value is not applied. Default is 0. + The bias is added to the scaled sample of the displacement map. + Without a displacement map set, this value is not applied. Default is `0`.

      -

      [property:Boolean fog]

      -

      Whether the material is affected by fog. Default is `false`.

      -

      [property:Texture map]

      The color map. May optionally include an alpha channel, typically combined - with [page:Material.transparent .transparent] or [page:Material.alphaTest .alphaTest]. + with [page:Material.transparent .transparent] or [page:Material.alphaTest .alphaTest]. Default is null.

      @@ -108,11 +106,11 @@

      [property:Boolean wireframe]

      [property:Float wireframeLinewidth]

      - Controls wireframe thickness. Default is 1.

      + Controls wireframe thickness. Default is `1`.

      Due to limitations of the - [link:https://www.khronos.org/registry/OpenGL/specs/gl/glspec46.core.pdf OpenGL Core Profile] - with the [page:WebGLRenderer WebGL] renderer on most platforms linewidth will always be 1 regardless of the set value. + [link:https://www.khronos.org/registry/OpenGL/specs/gl/glspec46.core.pdf OpenGL Core Profile] + with the [page:WebGLRenderer WebGL] renderer on most platforms linewidth will always be `1` regardless of the set value.

      Methods

      diff --git a/docs/api/en/materials/MeshDistanceMaterial.html b/docs/api/en/materials/MeshDistanceMaterial.html index e87333306c500c..d2e639dc4ac897 100644 --- a/docs/api/en/materials/MeshDistanceMaterial.html +++ b/docs/api/en/materials/MeshDistanceMaterial.html @@ -82,22 +82,20 @@

      [property:Float displacementScale]

      How much the displacement map affects the mesh (where black is no displacement, and white is maximum displacement). Without a displacement - map set, this value is not applied. Default is 1. + map set, this value is not applied. Default is `1`.

      [property:Float displacementBias]

      The offset of the displacement map's values on the mesh's vertices. - Without a displacement map set, this value is not applied. Default is 0. + The bias is added to the scaled sample of the displacement map. + Without a displacement map set, this value is not applied. Default is `0`.

      -

      [property:Boolean fog]

      -

      Whether the material is affected by fog. Default is `false`.

      -

      [property:Texture map]

      The color map. May optionally include an alpha channel, typically combined - with [page:Material.transparent .transparent] or [page:Material.alphaTest .alphaTest]. + with [page:Material.transparent .transparent] or [page:Material.alphaTest .alphaTest]. Default is null.

      diff --git a/docs/api/en/materials/MeshLambertMaterial.html b/docs/api/en/materials/MeshLambertMaterial.html index d26f53a3db61d7..46b74fbacdb322 100644 --- a/docs/api/en/materials/MeshLambertMaterial.html +++ b/docs/api/en/materials/MeshLambertMaterial.html @@ -87,8 +87,10 @@

      [property:Texture aoMap]

      [property:Float aoMapIntensity]

      - Intensity of the ambient occlusion effect. Default is 1. Zero is no - occlusion effect. + Intensity of the ambient occlusion effect. Range is 0-1, where `0` + disables ambient occlusion. Where intensity is `1` and the [page:.aoMap] + red channel is also `1`, ambient light is fully occluded on a surface. + Default is `1`.

      [property:Texture bumpMap]

      @@ -133,13 +135,14 @@

      [property:Float displacementScale]

      How much the displacement map affects the mesh (where black is no displacement, and white is maximum displacement). Without a displacement - map set, this value is not applied. Default is 1. + map set, this value is not applied. Default is `1`.

      [property:Float displacementBias]

      The offset of the displacement map's values on the mesh's vertices. - Without a displacement map set, this value is not applied. Default is 0. + The bias is added to the scaled sample of the displacement map. + Without a displacement map set, this value is not applied. Default is `0`.

      [property:Color emissive]

      @@ -165,6 +168,11 @@

      [property:Float emissiveIntensity]

      [property:Texture envMap]

      The environment map. Default is null.

      +

      [property:Euler envMapRotation]

      +

      + The rotation of the environment map in radians. Default is `(0,0,0)`. +

      +

      [property:Boolean flatShading]

      Define whether the material is rendered with flat shading. Default is @@ -180,12 +188,12 @@

      [property:Texture lightMap]

      [property:Float lightMapIntensity]

      -

      Intensity of the baked light. Default is 1.

      +

      Intensity of the baked light. Default is `1`.

      [property:Texture map]

      The color map. May optionally include an alpha channel, typically combined - with [page:Material.transparent .transparent] or [page:Material.alphaTest .alphaTest]. + with [page:Material.transparent .transparent] or [page:Material.alphaTest .alphaTest]. Default is null.

      @@ -223,9 +231,9 @@

      [property:Float refractionRatio]

      The index of refraction (IOR) of air (approximately 1) divided by the index of refraction of the material. It is used with environment mapping - modes [page:Textures THREE.CubeRefractionMapping] and [page:Textures THREE.EquirectangularRefractionMapping]. + modes [page:Textures THREE.CubeRefractionMapping] and [page:Textures THREE.EquirectangularRefractionMapping]. The refraction ratio should not - exceed 1. Default is `0.98`. + exceed `1`. Default is `0.98`.

      [property:Texture specularMap]

      @@ -243,7 +251,7 @@

      [property:String wireframeLinecap]

      "square". Default is 'round'.

      This corresponds to the - [link:https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D/lineCap 2D Canvas lineCap] + [link:https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D/lineCap 2D Canvas lineCap] property and it is ignored by the [page:WebGLRenderer WebGL] renderer.

      @@ -253,18 +261,18 @@

      [property:String wireframeLinejoin]

      "miter". Default is 'round'.

      This corresponds to the - [link:https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D/lineJoin 2D Canvas lineJoin] + [link:https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D/lineJoin 2D Canvas lineJoin] property and it is ignored by the [page:WebGLRenderer WebGL] renderer.

      [property:Float wireframeLinewidth]

      - Controls wireframe thickness. Default is 1.

      + Controls wireframe thickness. Default is `1`.

      Due to limitations of the - [link:https://www.khronos.org/registry/OpenGL/specs/gl/glspec46.core.pdf OpenGL Core Profile] + [link:https://www.khronos.org/registry/OpenGL/specs/gl/glspec46.core.pdf OpenGL Core Profile] with the [page:WebGLRenderer WebGL] renderer on most - platforms linewidth will always be 1 regardless of the set value. + platforms linewidth will always be `1` regardless of the set value.

      Methods

      diff --git a/docs/api/en/materials/MeshMatcapMaterial.html b/docs/api/en/materials/MeshMatcapMaterial.html index d0b0e64e4cd4d2..6e161b3fe41868 100644 --- a/docs/api/en/materials/MeshMatcapMaterial.html +++ b/docs/api/en/materials/MeshMatcapMaterial.html @@ -83,7 +83,7 @@

      [property:Texture bumpMap]

      [property:Float bumpScale]

      How much the bump map affects the material. Typical ranges are 0-1. - Default is 1. + Default is `1`.

      [property:Color color]

      @@ -103,13 +103,14 @@

      [property:Float displacementScale]

      How much the displacement map affects the mesh (where black is no displacement, and white is maximum displacement). Without a displacement - map set, this value is not applied. Default is 1. + map set, this value is not applied. Default is `1`.

      [property:Float displacementBias]

      The offset of the displacement map's values on the mesh's vertices. - Without a displacement map set, this value is not applied. Default is 0. + The bias is added to the scaled sample of the displacement map. + Without a displacement map set, this value is not applied. Default is `0`.

      [property:Boolean flatShading]

      @@ -124,7 +125,7 @@

      [property:Boolean fog]

      [property:Texture map]

      The color map. May optionally include an alpha channel, typically combined - with [page:Material.transparent .transparent] or [page:Material.alphaTest .alphaTest]. + with [page:Material.transparent .transparent] or [page:Material.alphaTest .alphaTest]. Default is null. The texture map color is modulated by the diffuse [page:.color].

      diff --git a/docs/api/en/materials/MeshNormalMaterial.html b/docs/api/en/materials/MeshNormalMaterial.html index 71120a6ba7c333..2cf7657ed4bb1e 100644 --- a/docs/api/en/materials/MeshNormalMaterial.html +++ b/docs/api/en/materials/MeshNormalMaterial.html @@ -58,7 +58,7 @@

      [property:Texture bumpMap]

      [property:Float bumpScale]

      How much the bump map affects the material. Typical ranges are 0-1. - Default is 1. + Default is `1`.

      [property:Texture displacementMap]

      @@ -75,13 +75,14 @@

      [property:Float displacementScale]

      How much the displacement map affects the mesh (where black is no displacement, and white is maximum displacement). Without a displacement - map set, this value is not applied. Default is 1. + map set, this value is not applied. Default is `1`.

      [property:Float displacementBias]

      The offset of the displacement map's values on the mesh's vertices. - Without a displacement map set, this value is not applied. Default is 0. + The bias is added to the scaled sample of the displacement map. + Without a displacement map set, this value is not applied. Default is `0`.

      [property:Boolean flatShading]

      @@ -90,9 +91,6 @@

      [property:Boolean flatShading]

      false.

      -

      [property:Boolean fog]

      -

      Whether the material is affected by fog. Default is `false`.

      -

      [property:Texture normalMap]

      The texture to create a normal map. The RGB values affect the surface @@ -125,12 +123,12 @@

      [property:Boolean wireframe]

      [property:Float wireframeLinewidth]

      - Controls wireframe thickness. Default is 1.

      + Controls wireframe thickness. Default is `1`.

      Due to limitations of the - [link:https://www.khronos.org/registry/OpenGL/specs/gl/glspec46.core.pdf OpenGL Core Profile] + [link:https://www.khronos.org/registry/OpenGL/specs/gl/glspec46.core.pdf OpenGL Core Profile] with the [page:WebGLRenderer WebGL] renderer on most - platforms linewidth will always be 1 regardless of the set value. + platforms linewidth will always be `1` regardless of the set value.

      Methods

      diff --git a/docs/api/en/materials/MeshPhongMaterial.html b/docs/api/en/materials/MeshPhongMaterial.html index bf280617ababb1..e8f21e10c869d0 100644 --- a/docs/api/en/materials/MeshPhongMaterial.html +++ b/docs/api/en/materials/MeshPhongMaterial.html @@ -85,8 +85,10 @@

      [property:Texture aoMap]

      [property:Float aoMapIntensity]

      - Intensity of the ambient occlusion effect. Default is 1. Zero is no - occlusion effect. + Intensity of the ambient occlusion effect. Range is 0-1, where `0` + disables ambient occlusion. Where intensity is `1` and the [page:.aoMap] + red channel is also `1`, ambient light is fully occluded on a surface. + Default is `1`.

      [property:Texture bumpMap]

      @@ -100,7 +102,7 @@

      [property:Texture bumpMap]

      [property:Float bumpScale]

      How much the bump map affects the material. Typical ranges are 0-1. - Default is 1. + Default is `1`.

      [property:Color color]

      @@ -131,13 +133,14 @@

      [property:Float displacementScale]

      How much the displacement map affects the mesh (where black is no displacement, and white is maximum displacement). Without a displacement - map set, this value is not applied. Default is 1. + map set, this value is not applied. Default is `1`.

      [property:Float displacementBias]

      The offset of the displacement map's values on the mesh's vertices. - Without a displacement map set, this value is not applied. Default is 0. + The bias is added to the scaled sample of the displacement map. + Without a displacement map set, this value is not applied. Default is `0`.

      [property:Color emissive]

      @@ -163,6 +166,11 @@

      [property:Float emissiveIntensity]

      [property:Texture envMap]

      The environment map. Default is null.

      +

      [property:Euler envMapRotation]

      +

      + The rotation of the environment map in radians. Default is `(0,0,0)`. +

      +

      [property:Boolean flatShading]

      Define whether the material is rendered with flat shading. Default is @@ -178,12 +186,12 @@

      [property:Texture lightMap]

      [property:Float lightMapIntensity]

      -

      Intensity of the baked light. Default is 1.

      +

      Intensity of the baked light. Default is `1`.

      [property:Texture map]

      The color map. May optionally include an alpha channel, typically combined - with [page:Material.transparent .transparent] or [page:Material.alphaTest .alphaTest]. + with [page:Material.transparent .transparent] or [page:Material.alphaTest .alphaTest]. Default is null. The texture map color is modulated by the diffuse [page:.color].

      @@ -215,16 +223,16 @@

      [property:Vector2 normalScale]

      [property:Float reflectivity]

      How much the environment map affects the surface; also see - [page:.combine]. The default value is 1 and the valid range is between 0 - (no reflections) and 1 (full reflections). + [page:.combine]. The default value is `1` and the valid range is between `0` + (no reflections) and `1` (full reflections).

      [property:Float refractionRatio]

      The index of refraction (IOR) of air (approximately 1) divided by the index of refraction of the material. It is used with environment mapping - modes [page:Textures THREE.CubeRefractionMapping] and [page:Textures THREE.EquirectangularRefractionMapping]. - The refraction ratio should not exceed 1. Default is `0.98`. + modes [page:Textures THREE.CubeRefractionMapping] and [page:Textures THREE.EquirectangularRefractionMapping]. + The refraction ratio should not exceed `1`. Default is `0.98`.

      [property:Float shininess]

      @@ -260,7 +268,7 @@

      [property:String wireframeLinecap]

      "square". Default is 'round'.

      This corresponds to the - [link:https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D/lineCap 2D Canvas lineCap] + [link:https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D/lineCap 2D Canvas lineCap] property and it is ignored by the [page:WebGLRenderer WebGL] renderer.

      @@ -270,18 +278,18 @@

      [property:String wireframeLinejoin]

      "miter". Default is 'round'.

      This corresponds to the - [link:https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D/lineJoin 2D Canvas lineJoin] + [link:https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D/lineJoin 2D Canvas lineJoin] property and it is ignored by the [page:WebGLRenderer WebGL] renderer.

      [property:Float wireframeLinewidth]

      - Controls wireframe thickness. Default is 1.

      + Controls wireframe thickness. Default is `1`.

      Due to limitations of the - [link:https://www.khronos.org/registry/OpenGL/specs/gl/glspec46.core.pdf OpenGL Core Profile] + [link:https://www.khronos.org/registry/OpenGL/specs/gl/glspec46.core.pdf OpenGL Core Profile] with the [page:WebGLRenderer WebGL] renderer on most - platforms linewidth will always be 1 regardless of the set value. + platforms linewidth will always be `1` regardless of the set value.

      Methods

      diff --git a/docs/api/en/materials/MeshPhysicalMaterial.html b/docs/api/en/materials/MeshPhysicalMaterial.html index 24b95998051632..be49bdf48a102e 100644 --- a/docs/api/en/materials/MeshPhysicalMaterial.html +++ b/docs/api/en/materials/MeshPhysicalMaterial.html @@ -17,12 +17,21 @@

      [name]

        +
      • + Anisotropy: Ability to represent the anisotropic property of materials + as observable with brushed metals. +
      • Clearcoat: Some materials — like car paints, carbon fiber, and wet surfaces — require a clear, reflective layer on top of another layer that may be irregular or rough. Clearcoat approximates this effect, without the need for a separate transparent surface.
      • +
      • + Iridescence: Allows to render the effect where hue varies + depending on the viewing angle and illumination angle. This can be seen on + soap bubbles, oil films, or on the wings of many insects. +
      • Physically-based transparency: One limitation of [page:Material.opacity .opacity] is that highly transparent materials @@ -69,9 +78,9 @@

        [name]

        Examples

        - [example:webgl_materials_variations_physical materials / variations / physical]
        + [example:webgl_loader_gltf_anisotropy loader / gltf / anisotropy]
        [example:webgl_materials_physical_clearcoat materials / physical / clearcoat]
        - [example:webgl_materials_physical_reflectivity materials / physical / reflectivity]
        + [example:webgl_loader_gltf_iridescence loader / gltf / iridescence]
        [example:webgl_loader_gltf_sheen loader / gltf / sheen]
        [example:webgl_materials_physical_transmission materials / physical / transmission]

        @@ -96,6 +105,25 @@

        Properties

        common properties.

        +

        [property:Float anisotropy]

        +

        + The anisotropy strength. Default is `0.0`. +

        + +

        [property:Texture anisotropyMap]

        +

        + Red and green channels represent the anisotropy direction in `[-1, 1]` tangent, + bitangent space, to be rotated by [page:.anisotropyRotation]. The blue channel + contains strength as `[0, 1]` to be multiplied by [page:.anisotropy]. Default is `null`. +

        + +

        [property:Float anisotropyRotation]

        +

        + The rotation of the anisotropy in tangent, bitangent space, measured in radians + counter-clockwise from the tangent. When [page:.anisotropyMap] is present, this + property provides additional rotation to the vectors in the texture. Default is `0.0`. +

        +

        [property:Color attenuationColor]

        The color that white light turns into due to absorption when reaching the @@ -159,10 +187,18 @@

        [property:Object defines]

        This is used by the [page:WebGLRenderer] for selecting shaders.

        +

        [property:Float dispersion]

        +

        + Defines the strength of the angular separation of colors (chromatic aberration) transmitting through a relatively clear volume. + Any value zero or larger is valid, the typical range of realistic values is `[0, 1]`. + Default is `0` (no dispersion). + This property can be only be used with transmissive objects, see [page:.transmission]. +

        +

        [property:Float ior]

        Index-of-refraction for non-metallic materials, from `1.0` to `2.333`. - Default is `1.5`.
        + Default is `1.5`.

        [property:Float reflectivity]

        @@ -174,6 +210,45 @@

        [property:Float reflectivity]

        when [page:MeshStandardMaterial.metalness metalness] is `1.0`

        +

        [property:Float iridescence]

        +

        + The intensity of the [link:https://en.wikipedia.org/wiki/Iridescence iridescence] layer, simulating RGB color shift based on the angle between the surface and the viewer, from `0.0` to `1.0`. Default is `0.0`. +

        + +

        [property:Texture iridescenceMap]

        +

        + The red channel of this texture is multiplied against + [page:.iridescence], for per-pixel control over iridescence. + Default is `null`. +

        + +

        [property:Float iridescenceIOR]

        +

        + Strength of the iridescence RGB color shift effect, represented by an index-of-refraction. Between `1.0` to `2.333`. + Default is `1.3`. +

        + +

        [property:Array iridescenceThicknessRange]

        +

        + Array of exactly 2 elements, specifying minimum and maximum thickness of the iridescence layer. + Thickness of iridescence layer has an equivalent effect of the one [page:.thickness] has on [page:.ior]. + Default is `[100, 400]`.
        + + If [page:.iridescenceThicknessMap] is not defined, iridescence thickness will use only the second element of the given array. +

        + +

        [property:Texture iridescenceThicknessMap]

        +

        + A texture that defines the thickness of the iridescence layer, stored in the green channel. + Minimum and maximum values of thickness are defined by [page:.iridescenceThicknessRange] array:
        +

          +
        • `0.0` in the green channel will result in thickness equal to first element of the array.
        • +
        • `1.0` in the green channel will result in thickness equal to second element of the array.
        • +
        • Values in-between will linearly interpolate between the elements of the array.
        • +
        + Default is `null`. +

        +

        [property:Float sheen]

        The intensity of the sheen layer, from `0.0` to `1.0`. Default is `0.0`. @@ -190,7 +265,7 @@

        [property:Texture sheenRoughnessMap]

        [property:Color sheenColor]

        -

        The sheen tint. Default is `0xffffff`, white.

        +

        The sheen tint. Default is `0x000000`, black.

        [property:Texture sheenColorMap]

        @@ -203,7 +278,7 @@

        [property:Float specularIntensity]

        A float that scales the amount of specular reflection for non-metals only. When set to zero, the model is effectively Lambertian. From `0.0` to - `1.0`. Default is `0.0`. + `1.0`. Default is `1.0`.

        [property:Texture specularIntensityMap]

        @@ -229,13 +304,13 @@

        [property:Texture specularColorMap]

        [property:Float thickness]

        The thickness of the volume beneath the surface. The value is given in the - coordinate space of the mesh. If the value is 0 the material is + coordinate space of the mesh. If the value is `0` the material is thin-walled. Otherwise the material is a volume boundary. Default is `0`.

        [property:Texture thicknessMap]

        - A texture that defines the thickness, stored in the G channel. This will + A texture that defines the thickness, stored in the green channel. This will be multiplied by [page:.thickness]. Default is `null`.

        @@ -249,7 +324,7 @@

        [property:Float transmission]

        property can be used to model these materials.
        When transmission is non-zero, [page:Material.opacity opacity] should be - set to `0`. + set to `1`.

        [property:Texture transmissionMap]

        diff --git a/docs/api/en/materials/MeshStandardMaterial.html b/docs/api/en/materials/MeshStandardMaterial.html index 5e854c19d95565..d4bb65380ddaea 100644 --- a/docs/api/en/materials/MeshStandardMaterial.html +++ b/docs/api/en/materials/MeshStandardMaterial.html @@ -32,7 +32,7 @@

        [name]

        being somewhat more computationally expensive. [name] uses per-fragment shading.

        - Note that for best results you should always specify an [page:.envMap environment map] + Note that for best results you should always specify an [page:.envMap environment map] when using this material.

        For a non-technical introduction to the concept of PBR and how to set up a @@ -50,7 +50,7 @@

        [name]

        Technical details of the approach used in three.js (and most other PBR systems) can be found is this - [link:https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf paper from Disney] + [link:https://media.disneyanimation.com/uploads/production/publication_asset/48/asset/s2012_pbs_disney_brdf_notes_v3.pdf paper from Disney] (pdf), by Brent Burley.

        @@ -114,8 +114,10 @@

        [property:Texture aoMap]

        [property:Float aoMapIntensity]

        - Intensity of the ambient occlusion effect. Default is 1. Zero is no - occlusion effect. + Intensity of the ambient occlusion effect. Range is 0-1, where `0` + disables ambient occlusion. Where intensity is `1` and the [page:.aoMap] + red channel is also `1`, ambient light is fully occluded on a surface. + Default is `1`.

        [property:Texture bumpMap]

        @@ -129,7 +131,7 @@

        [property:Texture bumpMap]

        [property:Float bumpScale]

        How much the bump map affects the material. Typical ranges are 0-1. - Default is 1. + Default is `1`.

        [property:Color color]

        @@ -157,15 +159,15 @@

        [property:Float displacementScale]

        How much the displacement map affects the mesh (where black is no displacement, and white is maximum displacement). Without a displacement - map set, this value is not applied. Default is 1. + map set, this value is not applied. Default is `1`.

        [property:Float displacementBias]

        The offset of the displacement map's values on the mesh's vertices. - Without a displacement map set, this value is not applied. Default is 0. + The bias is added to the scaled sample of the displacement map. + Without a displacement map set, this value is not applied. Default is `0`.

        -

        [property:Color emissive]

        Emissive (light) color of the material, essentially a solid color @@ -193,6 +195,11 @@

        [property:Texture envMap]

        [page:PMREMGenerator]. Default is null.

        +

        [property:Euler envMapRotation]

        +

        + The rotation of the environment map in radians. Default is `(0,0,0)`. +

        +

        [property:Float envMapIntensity]

        Scales the effect of the environment map by multiplying its color.

        @@ -214,20 +221,20 @@

        [property:Texture lightMap]

        [property:Float lightMapIntensity]

        -

        Intensity of the baked light. Default is 1.

        +

        Intensity of the baked light. Default is `1`.

        [property:Texture map]

        The color map. May optionally include an alpha channel, typically combined - with [page:Material.transparent .transparent] or [page:Material.alphaTest .alphaTest]. + with [page:Material.transparent .transparent] or [page:Material.alphaTest .alphaTest]. Default is null. The texture map color is modulated by the diffuse [page:.color].

        [property:Float metalness]

        How much the material is like a metal. Non-metallic materials such as wood - or stone use 0.0, metallic use 1.0, with nothing (usually) in between. - Default is 0.0. A value between 0.0 and 1.0 could be used for a rusty + or stone use `0.0`, metallic use `1.0`, with nothing (usually) in between. + Default is `0.0`. A value between `0.0` and `1.0` could be used for a rusty metal look. If metalnessMap is also provided, both values are multiplied.

        @@ -263,8 +270,8 @@

        [property:Vector2 normalScale]

        [property:Float roughness]

        - How rough the material appears. 0.0 means a smooth mirror reflection, 1.0 - means fully diffuse. Default is 1.0. If roughnessMap is also provided, + How rough the material appears. `0.0` means a smooth mirror reflection, `1.0` + means fully diffuse. Default is `1.0`. If roughnessMap is also provided, both values are multiplied.

        @@ -286,7 +293,7 @@

        [property:String wireframeLinecap]

        "square". Default is 'round'.

        This corresponds to the - [link:https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D/lineCap 2D Canvas lineCap] + [link:https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D/lineCap 2D Canvas lineCap] property and it is ignored by the [page:WebGLRenderer WebGL] renderer.

        @@ -296,18 +303,18 @@

        [property:String wireframeLinejoin]

        "miter". Default is 'round'.

        This corresponds to the - [link:https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D/lineJoin 2D Canvas lineJoin] + [link:https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D/lineJoin 2D Canvas lineJoin] property and it is ignored by the [page:WebGLRenderer WebGL] renderer.

        [property:Float wireframeLinewidth]

        - Controls wireframe thickness. Default is 1.

        + Controls wireframe thickness. Default is `1`.

        Due to limitations of the - [link:https://www.khronos.org/registry/OpenGL/specs/gl/glspec46.core.pdf OpenGL Core Profile] + [link:https://www.khronos.org/registry/OpenGL/specs/gl/glspec46.core.pdf OpenGL Core Profile] with the [page:WebGLRenderer WebGL] renderer on most - platforms linewidth will always be 1 regardless of the set value. + platforms linewidth will always be `1` regardless of the set value.

        Methods

        diff --git a/docs/api/en/materials/MeshToonMaterial.html b/docs/api/en/materials/MeshToonMaterial.html index ca24ed43951139..06440973f59f99 100644 --- a/docs/api/en/materials/MeshToonMaterial.html +++ b/docs/api/en/materials/MeshToonMaterial.html @@ -36,7 +36,7 @@

        [name]

        Examples

        - [example:webgl_materials_variations_toon materials / variations / toon] + [example:webgl_materials_toon materials / toon]

        Constructor

        @@ -78,8 +78,10 @@

        [property:Texture aoMap]

        [property:Float aoMapIntensity]

        - Intensity of the ambient occlusion effect. Default is 1. Zero is no - occlusion effect. + Intensity of the ambient occlusion effect. Range is 0-1, where `0` + disables ambient occlusion. Where intensity is `1` and the [page:.aoMap] + red channel is also `1`, ambient light is fully occluded on a surface. + Default is `1`.

        [property:Texture bumpMap]

        @@ -93,7 +95,7 @@

        [property:Texture bumpMap]

        [property:Float bumpScale]

        How much the bump map affects the material. Typical ranges are 0-1. - Default is 1. + Default is `1`.

        [property:Color color]

        @@ -113,13 +115,14 @@

        [property:Float displacementScale]

        How much the displacement map affects the mesh (where black is no displacement, and white is maximum displacement). Without a displacement - map set, this value is not applied. Default is 1. + map set, this value is not applied. Default is `1`.

        [property:Float displacementBias]

        The offset of the displacement map's values on the mesh's vertices. - Without a displacement map set, this value is not applied. Default is 0. + The bias is added to the scaled sample of the displacement map. + Without a displacement map set, this value is not applied. Default is `0`.

        [property:Color emissive]

        @@ -148,7 +151,7 @@

        [property:Boolean fog]

        [property:Texture gradientMap]

        Gradient map for toon shading. It's required to set - [page:Texture.minFilter] and [page:Texture.magFilter] to [page:Textures THREE.NearestFilter] + [page:Texture.minFilter] and [page:Texture.magFilter] to [page:Textures THREE.NearestFilter] when using this type of texture. Default is `null`.

        @@ -158,12 +161,12 @@

        [property:Texture lightMap]

        [property:Float lightMapIntensity]

        -

        Intensity of the baked light. Default is 1.

        +

        Intensity of the baked light. Default is `1`.

        [property:Texture map]

        The color map. May optionally include an alpha channel, typically combined - with [page:Material.transparent .transparent] or [page:Material.alphaTest .alphaTest]. + with [page:Material.transparent .transparent] or [page:Material.alphaTest .alphaTest]. Default is null. The texture map color is modulated by the diffuse [page:.color].

        @@ -203,7 +206,7 @@

        [property:String wireframeLinecap]

        "square". Default is 'round'.

        This corresponds to the - [link:https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D/lineCap 2D Canvas lineCap] + [link:https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D/lineCap 2D Canvas lineCap] property and it is ignored by the [page:WebGLRenderer WebGL] renderer.

        @@ -213,18 +216,18 @@

        [property:String wireframeLinejoin]

        "miter". Default is 'round'.

        This corresponds to the - [link:https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D/lineJoin 2D Canvas lineJoin] + [link:https://developer.mozilla.org/en/docs/Web/API/CanvasRenderingContext2D/lineJoin 2D Canvas lineJoin] property and it is ignored by the [page:WebGLRenderer WebGL] renderer.

        [property:Float wireframeLinewidth]

        - Controls wireframe thickness. Default is 1.

        + Controls wireframe thickness. Default is `1`.

        Due to limitations of the - [link:https://www.khronos.org/registry/OpenGL/specs/gl/glspec46.core.pdf OpenGL Core Profile] + [link:https://www.khronos.org/registry/OpenGL/specs/gl/glspec46.core.pdf OpenGL Core Profile] with the [page:WebGLRenderer WebGL] renderer on most - platforms linewidth will always be 1 regardless of the set value. + platforms linewidth will always be `1` regardless of the set value.

        Methods

        diff --git a/docs/api/en/materials/PointsMaterial.html b/docs/api/en/materials/PointsMaterial.html index 0157d396a2f110..1be3a4728da3a0 100644 --- a/docs/api/en/materials/PointsMaterial.html +++ b/docs/api/en/materials/PointsMaterial.html @@ -45,8 +45,7 @@

        Examples

        [example:webgl_multiple_elements_text WebGL / multiple / elements / text]
        [example:webgl_points_billboards WebGL / points / billboards]
        [example:webgl_points_dynamic WebGL / points / dynamic]
        - [example:webgl_points_sprites WebGL / points / sprites]
        - [example:webgl_trails WebGL / trails] + [example:webgl_points_sprites WebGL / points / sprites]

        Constructor

        @@ -94,7 +93,7 @@

        [property:Texture map]

        [property:Number size]

        - Defines the size of the points in pixels. Default is 1.0.
        + Defines the size of the points in pixels. Default is `1.0`.
        Will be capped if it exceeds the hardware dependent parameter [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/getParameter gl.ALIASED_POINT_SIZE_RANGE].

        diff --git a/docs/api/en/materials/RawShaderMaterial.html b/docs/api/en/materials/RawShaderMaterial.html index dc69521419f955..788e5e5fc785de 100644 --- a/docs/api/en/materials/RawShaderMaterial.html +++ b/docs/api/en/materials/RawShaderMaterial.html @@ -35,10 +35,9 @@

        Examples

        [example:webgl_buffergeometry_rawshader WebGL / buffergeometry / rawshader]
        [example:webgl_buffergeometry_instancing_billboards WebGL / buffergeometry / instancing / billboards]
        [example:webgl_buffergeometry_instancing WebGL / buffergeometry / instancing]
        - [example:webgl_raymarching_reflect WebGL / raymarching / reflect]
        - [example:webgl2_volume_cloud WebGL 2 / volume / cloud]
        - [example:webgl2_volume_instancing WebGL 2 / volume / instancing]
        - [example:webgl2_volume_perlin WebGL 2 / volume / perlin] + [example:webgl_volume_cloud WebGL / volume / cloud]
        + [example:webgl_volume_instancing WebGL / volume / instancing]
        + [example:webgl_volume_perlin WebGL / volume / perlin]

        Constructor

        diff --git a/docs/api/en/materials/ShaderMaterial.html b/docs/api/en/materials/ShaderMaterial.html index f0eb88cad13cbf..9b49d7974a7692 100644 --- a/docs/api/en/materials/ShaderMaterial.html +++ b/docs/api/en/materials/ShaderMaterial.html @@ -115,7 +115,6 @@

        Examples

        [example:webgl_lights_hemisphere webgl / lights / hemisphere]
        [example:webgl_marchingcubes webgl / marchingcubes]
        [example:webgl_materials_envmaps webgl / materials / envmaps]
        - [example:webgl_materials_lightmap webgl / materials / lightmap]
        [example:webgl_materials_wireframe webgl / materials / wireframe]
        [example:webgl_modifier_tessellation webgl / modifier / tessellation]
        [example:webgl_postprocessing_dof2 webgl / postprocessing / dof2]
        @@ -342,11 +341,8 @@

        [property:Object extensions]

        An object with the following properties: this.extensions = { - derivatives: false, // set to use derivatives - fragDepth: false, // set to use fragment depth values - drawBuffers: false, // set to use draw buffers - shaderTextureLOD: false // set to use - shader texture LOD + clipCullDistance: false, // set to use vertex shader clipping + multiDraw: false // set to use vertex shader multi_draw / enable gl_DrawID };

        @@ -367,8 +363,7 @@

        [property:String fragmentShader]

        [property:String glslVersion]

        - Defines the GLSL version of custom shader code. Only relevant for WebGL 2 - in order to define whether to specify GLSL 3.0 or not. Valid values are + Defines the GLSL version of custom shader code. Valid values are `THREE.GLSL1` or `THREE.GLSL3`. Default is `null`.

        @@ -390,12 +385,12 @@

        [property:Boolean lights]

        [property:Float linewidth]

        - Controls wireframe thickness. Default is 1.

        + Controls wireframe thickness. Default is `1`.

        Due to limitations of the [link:https://www.khronos.org/registry/OpenGL/specs/gl/glspec46.core.pdf OpenGL Core Profile] with the [page:WebGLRenderer WebGL] renderer on most - platforms linewidth will always be 1 regardless of the set value. + platforms linewidth will always be `1` regardless of the set value.

        [property:Boolean flatShading]

        @@ -451,18 +446,18 @@

        [property:Boolean wireframe]

        [property:Float wireframeLinewidth]

        - Controls wireframe thickness. Default is 1.

        + Controls wireframe thickness. Default is `1`.

        Due to limitations of the [link:https://www.khronos.org/registry/OpenGL/specs/gl/glspec46.core.pdf OpenGL Core Profile] with the [page:WebGLRenderer WebGL] renderer on most - platforms linewidth will always be 1 regardless of the set value. + platforms linewidth will always be `1` regardless of the set value.

        Methods

        See the base [page:Material] class for common methods.

        -

        [method:ShaderMaterial clone]() [param:ShaderMaterial this]

        +

        [method:ShaderMaterial clone]()

        Generates a shallow copy of this material. Note that the vertexShader and fragmentShader are copied `by reference`, as are the definitions of the diff --git a/docs/api/en/materials/SpriteMaterial.html b/docs/api/en/materials/SpriteMaterial.html index 7edbc0cc6eea3c..2313aff9e9424f 100644 --- a/docs/api/en/materials/SpriteMaterial.html +++ b/docs/api/en/materials/SpriteMaterial.html @@ -83,7 +83,7 @@

        [property:Texture map]

        [property:Radians rotation]

        -

        The rotation of the sprite in radians. Default is 0.

        +

        The rotation of the sprite in radians. Default is `0`.

        [property:Boolean sizeAttenuation]

        diff --git a/docs/api/en/math/Box2.html b/docs/api/en/math/Box2.html index 0250e3207b3f35..656e79b74446ac 100644 --- a/docs/api/en/math/Box2.html +++ b/docs/api/en/math/Box2.html @@ -86,7 +86,7 @@

        [method:Float distanceToPoint]( [param:Vector2 point] )

        [page:Vector2 point] - [page:Vector2] to measure distance to.

        Returns the distance from any edge of this box to the specified point. If - the [page:Vector2 point] lies inside of this box, the distance will be 0. + the [page:Vector2 point] lies inside of this box, the distance will be `0`.

        [method:Boolean equals]( [param:Box2 box] )

        diff --git a/docs/api/en/math/Box3.html b/docs/api/en/math/Box3.html index 573384e9900d5b..6be37e5bb64918 100644 --- a/docs/api/en/math/Box3.html +++ b/docs/api/en/math/Box3.html @@ -120,7 +120,7 @@

        [method:Float distanceToPoint]( [param:Vector3 point] )

        [page:Vector3 point] - [page:Vector3] to measure distance to.

        Returns the distance from any edge of this box to the specified point. If - the [page:Vector3 point] lies inside of this box, the distance will be 0. + the [page:Vector3 point] lies inside of this box, the distance will be `0`.

        [method:Boolean equals]( [param:Box3 box] )

        diff --git a/docs/api/en/math/Color.html b/docs/api/en/math/Color.html index ab0da689a1ff90..b5b2d4d46e1efa 100644 --- a/docs/api/en/math/Color.html +++ b/docs/api/en/math/Color.html @@ -12,7 +12,41 @@

        [name]

        Class representing a color.

        - Iterating through a [name] instance will yield its components (r, g, b) in + A Color instance is represented by RGB components in the linear working + color space, which defaults to `LinearSRGBColorSpace`. Inputs + conventionally using `SRGBColorSpace` (such as hexadecimals and CSS + strings) are converted to the working color space automatically. +

        + +

        + +// converted automatically from SRGBColorSpace to LinearSRGBColorSpace +const color = new THREE.Color().setHex( 0x112233 ); + +

        + +

        + Source color spaces may be specified explicitly, to ensure correct + conversions. +

        + +

        + +// assumed already LinearSRGBColorSpace; no conversion +const color = new THREE.Color().setRGB( 0.5, 0.5, 0.5 ); + +// converted explicitly from SRGBColorSpace to LinearSRGBColorSpace +const color = new THREE.Color().setRGB( 0.5, 0.5, 0.5, SRGBColorSpace ); + +

        + +

        + If THREE.ColorManagement is disabled, no conversions occur. For details, + see Color management. +

        + +

        + Iterating through a Color instance will yield its components (r, g, b) in the corresponding order.

        @@ -50,7 +84,7 @@

        [page:Color_Hex_or_String r] - (optional) If arguments [page:Float g] and [page:Float b] are defined, the red component of the color. If they are not defined, it can be a - [link:https://en.wikipedia.org/wiki/Web_colors#Hex_triplet hexadecimal triplet] (recommended), + [link:https://en.wikipedia.org/wiki/Web_colors#Hex_triplet hexadecimal triplet] (recommended), a CSS-style string, or another `Color` instance.
        [page:Float g] - (optional) If it is defined, the green component of the color.
        @@ -58,7 +92,7 @@

        color.

        Note that standard method of specifying color in three.js is with a - [link:https://en.wikipedia.org/wiki/Web_colors#Hex_triplet hexadecimal triplet], + [link:https://en.wikipedia.org/wiki/Web_colors#Hex_triplet hexadecimal triplet], and that method is used throughout the rest of the documentation.

        @@ -91,13 +125,13 @@

        [property:Boolean isColor]

        Read-only flag to check if a given object is of type [name].

        [property:Float r]

        -

        Red channel value between 0 and 1. Default is 1.

        +

        Red channel value between `0.0` and `1.0`. Default is `1`.

        [property:Float g]

        -

        Green channel value between 0 and 1. Default is 1.

        +

        Green channel value between `0.0` and `1.0`. Default is `1`.

        [property:Float b]

        -

        Blue channel value between 0 and 1. Default is 1.

        +

        Blue channel value between `0.0` and `1.0`. Default is `1`.

        Methods

        @@ -134,17 +168,17 @@

        [method:this copy]( [param:Color color] )

        [method:this convertLinearToSRGB]()

        -

        Converts this color from linear space to sRGB space.

        +

        Converts this color from `LinearSRGBColorSpace` to `SRGBColorSpace`.

        [method:this convertSRGBToLinear]()

        -

        Converts this color from sRGB space to linear space.

        +

        Converts this color from `SRGBColorSpace` to `LinearSRGBColorSpace`.

        [method:this copyLinearToSRGB]( [param:Color color] )

        [page:Color color] — Color to copy.
        Copies the given color into this color, and then converts this color from - linear space to sRGB space. + `LinearSRGBColorSpace` to `SRGBColorSpace`.

        [method:this copySRGBToLinear]( [param:Color color] )

        @@ -152,7 +186,7 @@

        [method:this copySRGBToLinear]( [param:Color color] )

        [page:Color color] — Color to copy.
        Copies the given color into this color, and then converts this color from - sRGB space to linear space. + `SRGBColorSpace` to `LinearSRGBColorSpace`.

        [method:Boolean equals]( [param:Color color] )

        @@ -208,16 +242,16 @@

        object of the form: - { - h: 0, - s: 0, - l: 0 - } + { + h: 0, + s: 0, + l: 0 + }

        - [method:Color getRGB]( [param:Color target], [param:string colorSpace] = SRGBColorSpace ) + [method:Color getRGB]( [param:Color target], [param:string colorSpace] = LinearSRGBColorSpace )

        [page:Color target] — the result will be copied into this object.

        @@ -253,9 +287,9 @@

        [page:Float alpha] - interpolation factor, typically in the closed interval `[0, 1]`.

        - Sets this color to be the color linearly interpolated between [page:Color color1] + Sets this color to be the color linearly interpolated between [page:Color color1] and [page:Color color2] where alpha is the percent distance along - the line connecting the two colors - alpha = 0 will be [page:Color color1], + the line connecting the two colors - alpha = 0 will be [page:Color color1], and alpha = 1 will be [page:Color color2].

        @@ -324,9 +358,9 @@

        [method:this setHSL]( [param:Float h], [param:Float s], [param:Float l], [param:string colorSpace] = LinearSRGBColorSpace )

        - [page:Float h] — hue value between 0.0 and 1.0
        - [page:Float s] — saturation value between 0.0 and 1.0
        - [page:Float l] — lightness value between 0.0 and 1.0

        + [page:Float h] — hue value between `0.0` and `1.0`
        + [page:Float s] — saturation value between `0.0` and `1.0`
        + [page:Float l] — lightness value between `0.0` and `1.0`

        Sets color from HSL values.

        @@ -335,16 +369,16 @@

        [method:this setRGB]( [param:Float r], [param:Float g], [param:Float b], [param:string colorSpace] = LinearSRGBColorSpace )

        - [page:Float r] — Red channel value between 0.0 and 1.0.
        - [page:Float g] — Green channel value between 0.0 and 1.0.
        - [page:Float b] — Blue channel value between 0.0 and 1.0.

        + [page:Float r] — Red channel value between `0.0` and `1.0`.
        + [page:Float g] — Green channel value between `0.0` and `1.0`.
        + [page:Float b] — Blue channel value between `0.0` and `1.0`.

        Sets this color from RGB values.

        [method:this setScalar]( [param:Float scalar] )

        - [page:Float scalar] — a value between 0.0 and 1.0.

        + [page:Float scalar] — a value between `0.0` and `1.0`.

        Sets all three color components to the value [page:Float scalar].

        diff --git a/docs/api/en/math/Line3.html b/docs/api/en/math/Line3.html index 6b759c14afb287..525ccf3fcfd2ed 100644 --- a/docs/api/en/math/Line3.html +++ b/docs/api/en/math/Line3.html @@ -79,7 +79,7 @@

        Returns a point parameter based on the closest point as projected on the line segment. If [page:Boolean clampToLine] is true, then the returned - value will be between 0 and 1. + value will be between `0` and `1`.

        [method:this copy]( [param:Line3 line] )

        diff --git a/docs/api/en/math/MathUtils.html b/docs/api/en/math/MathUtils.html index b2da9de8d122c8..82969838826fea 100644 --- a/docs/api/en/math/MathUtils.html +++ b/docs/api/en/math/MathUtils.html @@ -108,9 +108,9 @@

        [method:Float pingpong]( [param:Float x], [param:Float length] )

        [page:Float x] — The value to pingpong.
        [page:Float length] — The positive value the function will pingpong to. - Default is 1.

        + Default is `1`.

        - Returns a value that alternates between 0 and [param:Float length]. + Returns a value that alternates between `0` and [param:Float length].

        [method:Integer ceilPowerOfTwo]( [param:Number n] )

        @@ -121,7 +121,7 @@

        [method:Integer ceilPowerOfTwo]( [param:Number n] )

        [method:Integer floorPowerOfTwo]( [param:Number n] )

        - Returns the largest power of 2 that is less than or equal to [page:Number n]. + Returns the largest power of `2` that is less than or equal to [page:Number n].

        [method:Float radToDeg]( [param:Float radians] )

        @@ -153,8 +153,8 @@

        [page:Float x] - The value to evaluate based on its position between min and max.
        - [page:Float min] - Any x value below min will be 0.
        - [page:Float max] - Any x value above max will be 1.

        + [page:Float min] - Any x value below min will be `0`.
        + [page:Float max] - Any x value above max will be `1`.

        Returns a value between 0-1 that represents the percentage that x has moved between min and max, but smoothed or slowed down the closer X is to @@ -169,8 +169,8 @@

        [page:Float x] - The value to evaluate based on its position between min and max.
        - [page:Float min] - Any x value below min will be 0.
        - [page:Float max] - Any x value above max will be 1.

        + [page:Float min] - Any x value below min will be `0`.
        + [page:Float max] - Any x value above max will be `1`.

        Returns a value between 0-1. A [link:https://en.wikipedia.org/wiki/Smoothstep#Variations variation on smoothstep] diff --git a/docs/api/en/math/Matrix2.html b/docs/api/en/math/Matrix2.html new file mode 100644 index 00000000000000..2bcc55aa946a43 --- /dev/null +++ b/docs/api/en/math/Matrix2.html @@ -0,0 +1,132 @@ + + + + + + + + + +

        [name]

        + +

        + A class representing a 2x2 + [link:https://en.wikipedia.org/wiki/Matrix_(mathematics) matrix]. +

        + +

        Code Example

        + +const m = new Matrix2(); + + +

        A Note on Row-Major and Column-Major Ordering

        +

        + The constructor and [page:set]() method take arguments in + [link:https://en.wikipedia.org/wiki/Row-_and_column-major_order#Column-major_order row-major] + order, while internally they are stored in the [page:.elements elements] + array in column-major order.

        + + This means that calling + +m.set( 11, 12, + 21, 22 ); + + will result in the [page:.elements elements] array containing: + +m.elements = [ 11, 21, + 12, 22 ]; + + and internally all calculations are performed using column-major ordering. + However, as the actual ordering makes no difference mathematically and + most people are used to thinking about matrices in row-major order, the + three.js documentation shows matrices in row-major order. Just bear in + mind that if you are reading the source code, you'll have to take the + [link:https://en.wikipedia.org/wiki/Transpose transpose] of any matrices + outlined here to make sense of the calculations. +

        + +

        Constructor

        + +

        [name]( [param:Number n11], [param:Number n12], + [param:Number n21], [param:Number n22] )

        +

        + Creates a 2x2 matrix with the given arguments in row-major order. If no arguments are provided, the constructor initializes + the [name] to the 3x3 [link:https://en.wikipedia.org/wiki/Identity_matrix identity matrix]. +

        + +

        Properties

        + +

        [property:Array elements]

        +

        + A [link:https://en.wikipedia.org/wiki/Row-_and_column-major_order column-major] list of matrix values. +

        + +

        Methods

        + +

        + [method:this fromArray]( [param:Array array], [param:Integer offset] ) +

        +

        + [page:Array array] - the array to read the elements from.
        + [page:Integer offset] - (optional) index of first element in the array. + Default is `0`.

        + + Sets the elements of this matrix based on an array in + [link:https://en.wikipedia.org/wiki/Row-_and_column-major_order#Column-major_order column-major] format. +

        + +

        [method:this identity]()

        +

        + Resets this matrix to the 2x2 identity matrix: +

        + + + + [ + + + 1 + 0 + + + 0 + 1 + + + ] + + + +

        + [method:this set]( [param:Float n11], [param:Float n12], [param:Float n21], [param:Float n22] ) +

        +

        + Sets the 2x2 matrix values to the given + [link:https://en.wikipedia.org/wiki/Row-_and_column-major_order row-major] + sequence of values: +

        + + + + [ + + + n11 + n12 + + + n21 + n22 + + + ] + + + +

        Source

        + +

        + [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

        + + diff --git a/docs/api/en/math/Matrix3.html b/docs/api/en/math/Matrix3.html index b8a66df2bd1be4..db6855bc30cf09 100644 --- a/docs/api/en/math/Matrix3.html +++ b/docs/api/en/math/Matrix3.html @@ -50,7 +50,7 @@

        A Note on Row-Major and Column-Major Ordering

        Constructor

        [name]( [param:Number n11], [param:Number n12], [param:Number n13], - [param:Number n21], [param:Number n22], [param:Number n22], + [param:Number n21], [param:Number n22], [param:Number n23], [param:Number n31], [param:Number n32], [param:Number n33] )

        Creates a 3x3 matrix with the given arguments in row-major order. If no arguments are provided, the constructor initializes @@ -87,18 +87,79 @@

        Extracts the [link:https://en.wikipedia.org/wiki/Basis_(linear_algebra) basis] of this matrix into the three axis vectors provided. If this matrix is: - -a, b, c, -d, e, f, -g, h, i - +

        + + + + [ + + + a + b + c + + + d + e + f + + + g + h + i + + + ] + + + +

        then the [page:Vector3 xAxis], [page:Vector3 yAxis], [page:Vector3 zAxis] will be set to: - -xAxis = (a, d, g) -yAxis = (b, e, h) -zAxis = (c, f, i) - +

        + +

        + + + xAxis + = + [ + + a + d + g + + ] + + , + + + + yAxis + = + [ + + b + e + h + + ] + + , and + + + + zAxis + = + [ + + c + f + i + + ] + +

        @@ -107,7 +168,7 @@

        [page:Array array] - the array to read the elements from.
        [page:Integer offset] - (optional) index of first element in the array. - Default is 0.

        + Default is `0`.

        Sets the elements of this matrix based on an array in [link:https://en.wikipedia.org/wiki/Row-_and_column-major_order#Column-major_order column-major] format. @@ -137,12 +198,32 @@

        [method:this getNormalMatrix]( [param:Matrix4 m] )

        [method:this identity]()

        Resets this matrix to the 3x3 identity matrix: - -1, 0, 0 -0, 1, 0 -0, 0, 1

        + + + [ + + + 1 + 0 + 0 + + + 0 + 1 + 0 + + + 0 + 0 + 1 + + + ] + + +

        [method:this makeRotation]( [param:Float theta] )

        [page:Float theta] — Rotation angle in radians. Positive values rotate @@ -150,25 +231,80 @@

        [method:this makeRotation]( [param:Float theta] )

        Sets this matrix as a 2D rotational transformation by [page:Float theta] radians. The resulting matrix will be: - -cos(θ) -sin(θ) 0 -sin(θ) cos(θ) 0 -0 0 1 -

        + + + [ + + + + cos + θ + + + -sin + θ + + + 0 + + + + + sin + θ + + + cos + θ + + + 0 + + + + 0 + 0 + 1 + + + ] + + +

        [method:this makeScale]( [param:Float x], [param:Float y] )

        [page:Float x] - the amount to scale in the X axis.
        [page:Float y] - the amount to scale in the Y axis.
        Sets this matrix as a 2D scale transform: - -x, 0, 0, -0, y, 0, -0, 0, 1

        + + + [ + + + x + 0 + 0 + + + 0 + y + 0 + + + 0 + 0 + 1 + + + ] + + +

        [method:this makeTranslation]( [param:Vector2 v] )

        [method:this makeTranslation]( [param:Float x], [param:Float y] )

        @@ -178,12 +314,32 @@

        [method:this makeTranslation]( [param:Float x], [param:Float y] )

        [page:Float y] - the amount to translate in the Y axis.
        Sets this matrix as a 2D translation transform: - -1, 0, x, -0, 1, y, -0, 0, 1

        + + + [ + + + 1 + 0 + x + + + 0 + 1 + y + + + 0 + 0 + 1 + + + ] + + +

        [method:this multiply]( [param:Matrix3 m] )

        Post-multiplies this matrix by [page:Matrix3 m].

        @@ -205,18 +361,35 @@

        [method:this set]( [param:Float n11], [param:Float n12], [param:Float n13], [param:Float n21], [param:Float n22], [param:Float n23], [param:Float n31], [param:Float n32], [param:Float n33] )

        - [page:Float n11] - value to put in row 1, col 1.
        - [page:Float n12] - value to put in row 1, col 2.
        - ...
        - ...
        - [page:Float n32] - value to put in row 3, col 2.
        - [page:Float n33] - value to put in row 3, col 3.

        - Sets the 3x3 matrix values to the given [link:https://en.wikipedia.org/wiki/Row-_and_column-major_order row-major] - sequence of values. + sequence of values:

        + + + [ + + + n11 + n12 + n13 + + + n21 + n22 + n23 + + + n31 + n32 + n33 + + + ] + + +

        [method:this premultiply]( [param:Matrix3 m] )

        Pre-multiplies this matrix by [page:Matrix3 m].

        diff --git a/docs/api/en/math/Matrix4.html b/docs/api/en/math/Matrix4.html index 6516f522300649..5138ebd2f2e0ae 100644 --- a/docs/api/en/math/Matrix4.html +++ b/docs/api/en/math/Matrix4.html @@ -62,7 +62,7 @@

        [name]

        A Note on Row-Major and Column-Major Ordering

        - The constructor and [page:set]() method take arguments in + The constructor and [page:.set set]() method take arguments in [link:https://en.wikipedia.org/wiki/Row-_and_column-major_order#Column-major_order row-major] order, while internally they are stored in the [page:.elements elements] array in column-major order.

        @@ -192,19 +192,90 @@

        Extracts the [link:https://en.wikipedia.org/wiki/Basis_(linear_algebra) basis] of this matrix into the three axis vectors provided. If this matrix is: - -a, b, c, d, -e, f, g, h, -i, j, k, l, -m, n, o, p +

        + + + + [ + + + a + b + c + d + + + e + f + g + h + + + i + j + k + l + + + m + n + o + p + + + ] + + + +

        then the [page:Vector3 xAxis], [page:Vector3 yAxis], [page:Vector3 zAxis] will be set to: - -xAxis = (a, e, i) -yAxis = (b, f, j) -zAxis = (c, g, k)

        +
        + + + xAxis + = + [ + + a + e + i + + ] + + , + + + + yAxis + = + [ + + b + f + j + + ] + + , and + + + + zAxis + = + [ + + c + g + k + + ] + + +
        +

        [method:this extractRotation]( [param:Matrix4 m] )

        Extracts the rotation component of the supplied matrix [page:Matrix4 m] @@ -244,8 +315,8 @@

        [method:this lookAt]( [param:Vector3 eye], [param:Vector3 target], [param:Vector3 up] )

        - Constructs a rotation matrix, looking from [page:Vector3 eye] towards - [page:Vector3 target] oriented by the [page:Vector3 up] vector. + Sets the rotation component of the transformation matrix, looking from [page:Vector3 eye] towards + [page:Vector3 target], and oriented by the up-direction [page:Vector3 up].

        @@ -269,14 +340,41 @@

        Set this to the [link:https://en.wikipedia.org/wiki/Basis_(linear_algebra) basis] matrix consisting of the three provided basis vectors: - -xAxis.x, yAxis.x, zAxis.x, 0, -xAxis.y, yAxis.y, zAxis.y, 0, -xAxis.z, yAxis.z, zAxis.z, 0, - 0, 0, 0, 1 -

        + + + [ + + + xAxis.x + yAxis.x + zAxis.x + 0 + + + xAxis.y + yAxis.y + zAxis.y + 0 + + + xAxis.z + yAxis.z + zAxis.z + 0 + + + 0 + 0 + 0 + 1 + + + ] + + +

        [method:this makePerspective]( [param:Float left], [param:Float right], [param:Float top], [param:Float bottom], [param:Float near], [param:Float far] )

        @@ -312,53 +410,322 @@

        [method:this makeRotationFromQuaternion]( [param:Quaternion q] )

        [link:https://en.wikipedia.org/wiki/Rotation_matrix#Quaternion here]. The rest of the matrix is set to the identity. So, given [page:Quaternion q] = w + xi + yj + zk, the resulting matrix will be: - -1-2y²-2z² 2xy-2zw 2xz+2yw 0 -2xy+2zw 1-2x²-2z² 2yz-2xw 0 -2xz-2yw 2yz+2xw 1-2x²-2y² 0 - 0 0 0 1 -

        + + + [ + + + + 1 + - + 2 + + y + 2 + + - + 2 + + z + 2 + + + + 2 + x + y + - + 2 + z + w + + + 2 + x + z + + + 2 + y + w + + + 0 + + + + + 2 + x + y + + + 2 + z + w + + + 1 + - + 2 + + x + 2 + + - + 2 + + z + 2 + + + + 2 + y + z + - + 2 + x + w + + + 0 + + + + + 2 + x + z + - + 2 + y + w + + + 2 + y + z + + + 2 + x + w + + + 1 + - + 2 + + x + 2 + + - + 2 + + y + 2 + + + + 0 + + + + 0 + 0 + 0 + 1 + + + ] + + +

        [method:this makeRotationX]( [param:Float theta] )

        [page:Float theta] — Rotation angle in radians.

        Sets this matrix as a rotational transformation around the X axis by [page:Float theta] (θ) radians. The resulting matrix will be: - - 1 0 0 0 - 0 cos(θ) -sin(θ) 0 - 0 sin(θ) cos(θ) 0 - 0 0 0 1 -

        + + + [ + + + 1 + 0 + 0 + 0 + + + + 0 + + + cos + θ + + + - + sin + θ + + + 0 + + + + + 0 + + + sin + θ + + + cos + θ + + + 0 + + + + 0 + 0 + 0 + 1 + + + ] + + +

        [method:this makeRotationY]( [param:Float theta] )

        [page:Float theta] — Rotation angle in radians.

        Sets this matrix as a rotational transformation around the Y axis by [page:Float theta] (θ) radians. The resulting matrix will be: - - cos(θ) 0 sin(θ) 0 0 1 0 0 -sin(θ) 0 cos(θ) 0 0 0 - 0 1 -

        + + + [ + + + + cos + θ + + + 0 + + + sin + θ + + + 0 + + + + 0 + 1 + 0 + 0 + + + + - + sin + θ + + + 0 + + + cos + θ + + + 0 + + + + 0 + 0 + 0 + 1 + + + ] + + +

        [method:this makeRotationZ]( [param:Float theta] )

        [page:Float theta] — Rotation angle in radians.

        Sets this matrix as a rotational transformation around the Z axis by [page:Float theta] (θ) radians. The resulting matrix will be: - -cos(θ) -sin(θ) 0 0 -sin(θ) cos(θ) 0 0 - 0 0 1 0 - 0 0 0 1 -

        + + + + [ + + + + cos + θ + + + - + sin + θ + + + 0 + + + 0 + + + + + sin + θ + + + cos + θ + + + 0 + + + 0 + + + + 0 + 0 + 1 + 0 + + + 0 + 0 + 0 + 1 + + + ] + +

        [method:this makeScale]( [param:Float x], [param:Float y], [param:Float z] ) @@ -369,13 +736,41 @@

        [page:Float z] - the amount to scale in the Z axis.

        Sets this matrix as scale transform: - -x, 0, 0, 0, -0, y, 0, 0, -0, 0, z, 0, -0, 0, 0, 1

        + + + [ + + + x + 0 + 0 + 0 + + + 0 + y + 0 + 0 + + + 0 + 0 + z + 0 + + + 0 + 0 + 0 + 1 + + + ] + + +

        [method:this makeShear]( [param:Float xy], [param:Float xz], [param:Float yx], [param:Float yz], [param:Float zx], [param:Float zy] ) @@ -389,26 +784,82 @@

        [page:Float zy] - the amount to shear Z by Y.

        Sets this matrix as a shear transform: - -1, yx, zx, 0, -xy, 1, zy, 0, -xz, yz, 1, 0, -0, 0, 0, 1

        + + + [ + + + 1 + yx + zx + 0 + + + xy + 1 + zy + 0 + + + xz + yz + 1 + 0 + + + 0 + 0 + 0 + 1 + + + ] + + +

        [method:this makeTranslation]( [param:Vector3 v] )

        [method:this makeTranslation]( [param:Float x], [param:Float y], [param:Float z] ) // optional API

        Sets this matrix as a translation transform from vector [page:Vector3 v], or numbers [page:Float x], [page:Float y] and [page:Float z]: - -1, 0, 0, x, -0, 1, 0, y, -0, 0, 1, z, -0, 0, 0, 1

        + + + [ + + + 1 + 0 + 0 + x + + + 0 + 1 + 0 + y + + + 0 + 0 + 1 + z + + + 0 + 0 + 0 + 1 + + + ] + + +

        [method:this multiply]( [param:Matrix4 m] )

        Post-multiplies this matrix by [page:Matrix4 m].

        @@ -450,19 +901,76 @@

        Sets the position component for this matrix from vector [page:Vector3 v], without affecting the rest of the matrix - i.e. if the matrix is currently: - -a, b, c, d, -e, f, g, h, -i, j, k, l, -m, n, o, p - This becomes: - -a, b, c, v.x, -e, f, g, v.y, -i, j, k, v.z, -m, n, o, p

        + + + [ + + + a + b + c + d + + + e + f + g + h + + + i + j + k + l + + + m + n + o + p + + + ] + + + +

        This becomes:

        + + + + [ + + + a + b + c + v.x + + + e + f + g + v.y + + + i + j + k + v.z + + + m + n + o + p + + + ] + + +

        [method:Array toArray]( [param:Array array], [param:Integer offset] )

        diff --git a/docs/api/en/math/Plane.html b/docs/api/en/math/Plane.html index 80a097eb4c5e9b..00793873ec24ce 100644 --- a/docs/api/en/math/Plane.html +++ b/docs/api/en/math/Plane.html @@ -20,7 +20,7 @@

        Constructor

        [name]( [param:Vector3 normal], [param:Float constant] )

        [page:Vector3 normal] - (optional) a unit length [page:Vector3] defining - the normal of the plane. Default is *(1, 0, 0)*.
        + the normal of the plane. Default is `(1, 0, 0)`.
        [page:Float constant] - (optional) the signed distance from the origin to the plane. Default is `0`.

        @@ -146,7 +146,7 @@

        [method:this set]( [param:Vector3 normal], [param:Float constant] )

        [page:Vector3 normal] - a unit length [page:Vector3] defining the normal of the plane.
        [page:Float constant] - the signed distance from the origin to the plane. - Default is `0`.

        +

        Sets this plane's [page:.normal normal] and [page:.constant constant] properties by copying the values from the given normal. diff --git a/docs/api/en/math/Quaternion.html b/docs/api/en/math/Quaternion.html index 615f3dcaf88b27..b47f97e7149dd5 100644 --- a/docs/api/en/math/Quaternion.html +++ b/docs/api/en/math/Quaternion.html @@ -20,6 +20,10 @@

        [name]

        in the corresponding order.

        +

        + Note that three.js expects Quaternions to be normalized. +

        +

        Code Example

        @@ -288,7 +292,7 @@

        [page:Integer srcOffset0] - An offset into the array `src0`.
        [page:Array src1] - The source array of the target quaternion.
        [page:Integer srcOffset1] - An offset into the array `src1`.
        - [page:Float t] - Normalized interpolation factor (between 0 and 1).

        + [page:Float t] - Normalized interpolation factor (between `0` and `1`).

        This SLERP implementation assumes the quaternion data are managed in flat arrays. diff --git a/docs/api/en/math/Sphere.html b/docs/api/en/math/Sphere.html index 05cdc77d203655..11867bef220a86 100644 --- a/docs/api/en/math/Sphere.html +++ b/docs/api/en/math/Sphere.html @@ -16,7 +16,7 @@

        [name]( [param:Vector3 center], [param:Float radius] )

        [page:Vector3 center] - center of the sphere. Default is a [page:Vector3] at `(0, 0, 0)`.
        - [page:Float radius] - radius of the sphere. Default is -1.

        + [page:Float radius] - radius of the sphere. Default is `-1`.

        Creates a new [name].

        @@ -29,6 +29,9 @@

        [property:Vector3 center]

        0)`.

        +

        [property:Boolean isSphere]

        +

        Read-only flag to check if a given object is of type [name].

        +

        [property:Float radius]

        The radius of the sphere. Default is -1.

        @@ -91,7 +94,7 @@

        [method:Boolean isEmpty]()

        Checks to see if the sphere is empty (the radius set to a negative number).
        - Spheres with a radius of 0 contain only their center point and are not + Spheres with a radius of `0` contain only their center point and are not considered to be empty.

        diff --git a/docs/api/en/math/SphericalHarmonics3.html b/docs/api/en/math/SphericalHarmonics3.html index ac74838bbe2ae3..cb6de7c39ec7f6 100644 --- a/docs/api/en/math/SphericalHarmonics3.html +++ b/docs/api/en/math/SphericalHarmonics3.html @@ -135,7 +135,7 @@

        [method:this zero]()

        -

        Sets all SH coefficients to 0.

        +

        Sets all SH coefficients to `0`.

        Static Methods

        diff --git a/docs/api/en/math/Triangle.html b/docs/api/en/math/Triangle.html index 862ae98528f35f..4ddadd8bfb026f 100644 --- a/docs/api/en/math/Triangle.html +++ b/docs/api/en/math/Triangle.html @@ -97,7 +97,7 @@

        [page:Vector3 target] — the result will be copied into this Vector3.

        Return a [link:https://en.wikipedia.org/wiki/Barycentric_coordinate_system barycentric coordinate] - from the given vector.

        + from the given vector. Returns `null` if the triangle is degenerate.

        [link:http://commons.wikimedia.org/wiki/File:Barycentric_coordinates_1.png Picture of barycentric coordinates]

        @@ -137,7 +137,21 @@

        [page:Vector target] — Result will be copied into this Vector.

        Returns the value barycentrically interpolated for the given point on the - triangle. + triangle. Returns `null` if the triangle is degenerate. +

        + +

        + [method:Vector getInterpolatedAttribute]( [param:BufferAttribute attribute], [param:Number i1], [param:Vector3 i2], [param:Number i3], [param:Vector3 barycoord], [param:Vector3 target] ) +

        +

        + [page:BufferAttribute attribute] - The attribute to interpolate.
        + p1 - Index of first vertex.
        + p2 - Index of second vertex.
        + p3 - Index of third vertex.
        + barycoord - The barycoordinate value to use to interpolate.
        + [page:Vector target] — Result will be copied into this Vector.

        + + Returns the value barycentrically interpolated for the given attribute and indices.

        [method:Boolean intersectsBox]( [param:Box3 box] )

        diff --git a/docs/api/en/math/Vector2.html b/docs/api/en/math/Vector2.html index 3450db8ced24ea..c5b37f313e1c4a 100644 --- a/docs/api/en/math/Vector2.html +++ b/docs/api/en/math/Vector2.html @@ -215,7 +215,7 @@

        [page:Array array] - the source array.
        - [page:Integer offset] - (optional) offset into the array. Default is 0.

        + [page:Integer offset] - (optional) offset into the array. Default is `0`.

        Sets this vector's [page:.x x] value to be `array[ offset ]` and [page:.y y] value to be `array[ offset + 1 ]`. @@ -234,10 +234,10 @@

        [method:Float getComponent]( [param:Integer index] )

        - [page:Integer index] - 0 or 1.

        + [page:Integer index] - `0` or `1`.

        - If index equals 0 returns the [page:.x x] value.
        - If index equals 1 returns the [page:.y y] value. + If index equals `0` returns the [page:.x x] value.
        + If index equals `1` returns the [page:.y y] value.

        [method:Float length]()

        @@ -343,11 +343,11 @@

        [method:this setComponent]( [param:Integer index], [param:Float value] )

        - [page:Integer index] - 0 or 1.
        + [page:Integer index] - `0` or `1`.
        [page:Float value] - [page:Float]

        - If index equals 0 set [page:.x x] to [page:Float value].
        - If index equals 1 set [page:.y y] to [page:Float value] + If index equals `0` set [page:.x x] to [page:Float value].
        + If index equals `1` set [page:.y y] to [page:Float value]

        [method:this setLength]( [param:Float l] )

        @@ -393,8 +393,8 @@

        [method:this random]()

        - Sets each component of this vector to a pseudo-random value between 0 and - 1, excluding 1. + Sets each component of this vector to a pseudo-random value between `0` and + `1`, excluding `1`.

        Source

        diff --git a/docs/api/en/math/Vector3.html b/docs/api/en/math/Vector3.html index 9126f02d58dc2d..6de99fd136878a 100644 --- a/docs/api/en/math/Vector3.html +++ b/docs/api/en/math/Vector3.html @@ -260,11 +260,11 @@

        [method:Float getComponent]( [param:Integer index] )

        - [page:Integer index] - 0, 1 or 2.

        + [page:Integer index] - `0`, `1` or `2`.

        - If index equals 0 returns the [page:.x x] value.
        - If index equals 1 returns the [page:.y y] value.
        - If index equals 2 returns the [page:.z z] value. + If index equals `0` returns the [page:.x x] value.
        + If index equals `1` returns the [page:.y y] value.
        + If index equals `2` returns the [page:.z z] value.

        [method:Float length]()

        @@ -404,12 +404,12 @@

        [method:this setComponent]( [param:Integer index], [param:Float value] )

        - [page:Integer index] - 0, 1 or 2.
        + [page:Integer index] - `0`, `1` or `2`.
        [page:Float value] - [page:Float]

        - If index equals 0 set [page:.x x] to [page:Float value].
        - If index equals 1 set [page:.y y] to [page:Float value].
        - If index equals 2 set [page:.z z] to [page:Float value] + If index equals `0` set [page:.x x] to [page:Float value].
        + If index equals `1` set [page:.y y] to [page:Float value].
        + If index equals `2` set [page:.z z] to [page:Float value]

        [method:this setFromColor]( [param:Color color] )

        @@ -485,7 +485,7 @@

        [method:this setLength]( [param:Float l] )

        [method:this setScalar]( [param:Float scalar] )

        Set the [page:.x x], [page:.y y] and [page:.z z] values of this vector - both equal to [page:Float scalar]. + all equal to [page:Float scalar].

        [method:this setX]( [param:Float x] )

        @@ -538,8 +538,8 @@

        [method:this unproject]( [param:Camera camera] )

        [method:this random]()

        - Sets each component of this vector to a pseudo-random value between 0 and - 1, excluding 1. + Sets each component of this vector to a pseudo-random value between `0` and + `1`, excluding `1`.

        [method:this randomDirection]()

        diff --git a/docs/api/en/math/Vector4.html b/docs/api/en/math/Vector4.html index 5dd2c9a0cf6a7e..8e8cee31d0b1b8 100644 --- a/docs/api/en/math/Vector4.html +++ b/docs/api/en/math/Vector4.html @@ -157,6 +157,9 @@

        [method:this copy]( [param:Vector4 v] )

        [page:.z z] and [page:.w w] properties to this Vector4.

        +

        [method:this divide]( [param:Vector4 v] )

        +

        Divides this vector by [page:Vector4 v].

        +

        [method:this divideScalar]( [param:Float s] )

        Divides this vector by scalar [page:Float s].

        @@ -183,7 +186,7 @@

        [page:Array array] - the source array.
        - [page:Integer offset] - (optional) offset into the array. Default is 0.

        + [page:Integer offset] - (optional) offset into the array. Default is `0`.

        Sets this vector's [page:.x x] value to be `array[ offset + 0 ]`, [page:.y y] value to be `array[ offset + 1 ]` [page:.z z] value to be `array[ offset + 2 ]` @@ -203,12 +206,12 @@

        [method:Float getComponent]( [param:Integer index] )

        - [page:Integer index] - 0, 1, 2 or 3.

        + [page:Integer index] - `0`, `1`, `2` or `3`.

        - If index equals 0 returns the [page:.x x] value.
        - If index equals 1 returns the [page:.y y] value.
        - If index equals 2 returns the [page:.z z] value.
        - If index equals 3 returns the [page:.w w] value. + If index equals `0` returns the [page:.x x] value.
        + If index equals `1` returns the [page:.y y] value.
        + If index equals `2` returns the [page:.z z] value.
        + If index equals `3` returns the [page:.w w] value.

        [method:Float length]()

        @@ -321,17 +324,23 @@

        [method:this setAxisAngleFromRotationMatrix]( [param:Matrix4 m] )

        and [page:.w w] to the angle.

        +

        [method:this setFromMatrixPosition]( [param:Matrix4 m] )

        +

        + Sets this vector to the position elements of the + [link:https://en.wikipedia.org/wiki/Transformation_matrix transformation matrix] [page:Matrix4 m]. +

        +

        [method:this setComponent]( [param:Integer index], [param:Float value] )

        - [page:Integer index] - 0, 1 or 2.
        + [page:Integer index] - `0`, `1`, `2` or `3`.
        [page:Float value] - [page:Float]

        - If index equals 0 set [page:.x x] to [page:Float value].
        - If index equals 1 set [page:.y y] to [page:Float value].
        - If index equals 2 set [page:.z z] to [page:Float value].
        - If index equals 3 set [page:.w w] to [page:Float value]. + If index equals `0` set [page:.x x] to [page:Float value].
        + If index equals `1` set [page:.y y] to [page:Float value].
        + If index equals `2` set [page:.z z] to [page:Float value].
        + If index equals `3` set [page:.w w] to [page:Float value].

        [method:this setLength]( [param:Float l] )

        @@ -384,8 +393,8 @@

        [method:this random]()

        - Sets each component of this vector to a pseudo-random value between 0 and - 1, excluding 1. + Sets each component of this vector to a pseudo-random value between `0` and + `1`, excluding `1`.

        Source

        diff --git a/docs/api/en/objects/BatchedMesh.html b/docs/api/en/objects/BatchedMesh.html new file mode 100644 index 00000000000000..723950615a83c2 --- /dev/null +++ b/docs/api/en/objects/BatchedMesh.html @@ -0,0 +1,369 @@ + + + + + + + + + + [page:Mesh] → + +

        [name]

        + +

        + A special version of [page:Mesh] with multi draw batch rendering support. Use + [name] if you have to render a large number of objects with the same + material but with different geometries or world transformations. The usage of + [name] will help you to reduce the number of draw calls and thus improve the overall + rendering performance in your application. + +
        +
        + + If the [link:https://developer.mozilla.org/en-US/docs/Web/API/WEBGL_multi_draw WEBGL_multi_draw extension] is + not supported then a less performant fallback is used. +

        + +

        Code Example

        + + + const box = new THREE.BoxGeometry( 1, 1, 1 ); + const sphere = new THREE.SphereGeometry( 1, 12, 12 ); + const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); + + // initialize and add geometries into the batched mesh + const batchedMesh = new BatchedMesh( 10, 5000, 10000, material ); + const boxGeometryId = batchedMesh.addGeometry( box ); + const sphereGeometryId = batchedMesh.addGeometry( sphere ); + + // create instances of those geometries + const boxInstancedId1 = batchedMesh.addInstance( boxGeometryId ); + const boxInstancedId2 = batchedMesh.addInstance( boxGeometryId ); + + const sphereInstancedId1 = batchedMesh.addInstance( sphereGeometryId ); + const sphereInstancedId2 = batchedMesh.addInstance( sphereGeometryId ); + + // position the geometries + batchedMesh.setMatrixAt( boxInstancedId1, boxMatrix1 ); + batchedMesh.setMatrixAt( boxInstancedId2, boxMatrix2 ); + + batchedMesh.setMatrixAt( sphereInstancedId1, sphereMatrix1 ); + batchedMesh.setMatrixAt( sphereInstancedId2, sphereMatrix2 ); + + scene.add( batchedMesh ); + + +

        Examples

        +

        + [example:webgl_mesh_batch WebGL / mesh / batch]
        +

        + +

        Constructor

        +

        + [name]( + [param:Integer maxInstanceCount], [param:Integer maxVertexCount], + [param:Integer maxIndexCount], [param:Material material], + ) +

        +

        + [page:Integer maxInstanceCount] - the max number of individual instances planned to be added and rendered.
        + [page:Integer maxVertexCount] - the max number of vertices to be used by all unique geometries.
        + [page:Integer maxIndexCount] - the max number of indices to be used by all unique geometries.
        + [page:Material material] - an instance of [page:Material]. Default is a new [page:MeshBasicMaterial].
        +

        + +

        Properties

        +

        See the base [page:Mesh] class for common properties.

        + +

        [property:Box3 boundingBox]

        +

        + This bounding box encloses all instances of the [name]. Can be calculated + with [page:.computeBoundingBox](). Default is `null`. +

        + +

        [property:Sphere boundingSphere]

        +

        + This bounding sphere encloses all instances of the [name]. Can be + calculated with [page:.computeBoundingSphere](). Default is `null`. +

        + +

        [property:Boolean perObjectFrustumCulled]

        +

        + If true then the individual objects within the [name] are frustum culled. Default is `true`. +

        + +

        [property:Boolean sortObjects]

        +

        + If true then the individual objects within the [name] are sorted to improve overdraw-related artifacts. + If the material is marked as "transparent" objects are rendered back to front and if not then they are + rendered front to back. Default is `true`. +

        + +

        [property:Integer maxInstanceCount]

        +

        + The maximum number of individual instances that can be stored in the [name]. Read only. +

        + +

        [property:Boolean isBatchedMesh]

        +

        Read-only flag to check if a given object is of type [name].

        + +

        Methods

        +

        See the base [page:Mesh] class for common methods.

        + +

        [method:undefined computeBoundingBox]()

        +

        + Computes the bounding box, updating [page:.boundingBox] attribute.
        + Bounding boxes aren't computed by default. They need to be explicitly + computed, otherwise they are `null`. +

        + +

        [method:undefined computeBoundingSphere]()

        +

        + Computes the bounding sphere, updating [page:.boundingSphere] + attribute.
        + Bounding spheres aren't computed by default. They need to be explicitly + computed, otherwise they are `null`. +

        + +

        [method:undefined dispose]()

        +

        + Frees the GPU-related resources allocated by this instance. Call this + method whenever this instance is no longer used in your app. +

        + +

        [method:this setCustomSort]( [param:Function sortFunction] )

        +

        + Takes a sort a function that is run before render. The function takes a list of instances to sort and a camera. The objects + in the list include a "z" field to perform a depth-ordered sort with. +

        + +

        + [method:undefined getColorAt]( [param:Integer instanceId], [param:Color target] ) +

        +

        + [page:Integer instanceId]: The id of an instance to get the color of. +

        +

        + [page:Color target]: The target object to copy the color in to. +

        +

        Get the color of the defined geometry.

        + +

        + [method:Matrix4 getMatrixAt]( [param:Integer instanceId], [param:Matrix4 target] ) +

        +

        + [page:Integer instanceId]: The id of an instance to get the matrix of. +

        +

        + [page:Matrix4 target]: This 4x4 matrix will be set to the local + transformation matrix of the defined instance. +

        +

        Get the local transformation matrix of the defined instance.

        + +

        + [method:Boolean getVisibleAt]( [param:Integer instanceId] ) +

        +

        + [page:Integer instanceId]: The id of an instance to get the visibility state of. +

        +

        Get whether the given instance is marked as "visible" or not.

        + +

        + [method:Object getGeometryRangeAt]( [param:Integer geometryId], [param:Object target] ) +

        +

        + [page:Integer geometryId]: The id of the geometry to get the range of. +

        +

        + [page:Object target]: Optional target object to copy the range in to. +

        +

        Get the range representing the subset of triangles related to the attached geometry, indicating the starting offset and count, or `null` if invalid.

        +

        Return an object of the form:

        + { start: Integer, count: Integer } + +

        + [method:Integer getGeometryIdAt]( [param:Integer instanceId] ) +

        +

        + [page:Integer instanceId]: The id of an instance to get the geometryIndex of. +

        +

        Get the geometryIndex of the defined instance.

        + +

        + [method:undefined setColorAt]( [param:Integer instanceId], [param:Color color] ) +

        +

        + [page:Integer instanceId]: The id of the instance to set the color of. +

        +

        [page:Color color]: The color to set the instance to.

        +

        + Sets the given color to the defined geometry instance. +

        + +

        + [method:this setMatrixAt]( [param:Integer instanceId], [param:Matrix4 matrix] ) +

        +

        + [page:Integer instanceId]: The id of an instance to set the matrix of. +

        +

        + [page:Matrix4 matrix]: A 4x4 matrix representing the local transformation + of a single instance. +

        +

        + Sets the given local transformation matrix to the defined instance. +

        +

        + Negatively scaled matrices are not supported. +

        + +

        + [method:this setVisibleAt]( [param:Integer instanceId], [param:Boolean visible] ) +

        +

        + [page:Integer instanceId]: The id of the instance to set the visibility of. +

        +

        + [page:Boolean visible]: A boolean value indicating the visibility state. +

        +

        + Sets the visibility of the instance at the given index. +

        + +

        + [method:this setGeometryIdAt]( [param:Integer instanceId], [param:Integer geometryId] ) +

        +

        + [page:Integer instanceId]: The id of the instance to set the geometryIndex of. +

        +

        + [page:Integer geometryId]: The geometryIndex to be use by the instance. +

        +

        + Sets the geometryIndex of the instance at the given index. +

        + +

        + [method:Integer addGeometry]( [param:BufferGeometry geometry], [param:Integer reservedVertexRange], [param:Integer reservedIndexRange] ) +

        +

        + [page:BufferGeometry geometry]: The geometry to add into the [name]. +

        +

        + [page:Integer reservedVertexRange]: Optional parameter specifying the amount of vertex buffer space to reserve for the added geometry. This + is necessary if it is planned to set a new geometry at this index at a later time that is larger than the original geometry. Defaults to + the length of the given geometry vertex buffer. +

        +

        + [page:Integer reservedIndexRange]: Optional parameter specifying the amount of index buffer space to reserve for the added geometry. This + is necessary if it is planned to set a new geometry at this index at a later time that is larger than the original geometry. Defaults to + the length of the given geometry index buffer. +

        +

        + Adds the given geometry to the [name] and returns the associated geometry id referring to it to be used in other functions. +

        +

        + [method:Integer deleteGeometry]( [param:Integer geometryId] ) +

        +

        + [page:Integer geometryId]: The id of a geometry to remove from the [name] that was previously added via "addGeometry". Any instances referencing + this geometry will also be removed as a side effect. +

        +

        + [method:Integer addInstance]( [param:Integer geometryId] ) +

        +

        + [page:Integer geometryId]: The id of a previously added geometry via "addGeometry" to add into the [name] to render. +

        +

        + Adds a new instance to the [name] using the geometry of the given geometryId and returns a new id referring to the new instance to be used + by other functions. +

        +

        + [method:Integer deleteInstance]( [param:Integer instanceId] ) +

        +

        + [page:Integer instanceId]: The id of an instance to remove from the [name] that was previously added via "addInstance". +

        +

        + Removes an existing instance from the [name] using the given instanceId. +

        + +

        + [method:Integer setGeometryAt]( [param:Integer geometryId], [param:BufferGeometry geometry] ) +

        +

        + [page:Integer geometryId]: Which geometry id to replace with this geometry. +

        +

        + [page:BufferGeometry geometry]: The geometry to substitute at the given geometry id. +

        +

        + Replaces the geometry at `geometryId` with the provided geometry. Throws an error if there is not enough space reserved for geometry. + Calling this will change all instances that are rendering that geometry. +

        + +

        + [method:this optimize]() +

        +

        + Repacks the sub geometries in [name] to remove any unused space remaining from previously deleted geometry, freeing up space to add new geometry. +

        + +

        + [method:this setGeometrySize]( maxVertexCount, maxIndexCount ) +

        +

        + Resizes the available space in [name]'s vertex and index buffer attributes to the provided sizes. If the provided arguments shrink the geometry buffers + but there is not enough unused space at the end of the geometry attributes then an error is thrown. +

        +

        + [page:Integer maxVertexCount] - the max number of vertices to be used by all unique geometries to resize to.
        + [page:Integer maxIndexCount] - the max number of indices to be used by all unique geometries to resize to.
        +

        + +

        + [method:this setInstanceCount]( maxInstanceCount ) +

        +

        + Resizes the necessary buffers to support the provided number of instances. If the provided arguments shrink the number of instances but there are not enough + unused ids at the end of the list then an error is thrown. +

        +

        + [page:Integer maxInstanceCount] - the max number of individual instances that can be added and rendered by the [name].
        +

        + + + +

        Source

        + +

        + [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

        + + diff --git a/docs/api/en/objects/ClippingGroup.html b/docs/api/en/objects/ClippingGroup.html new file mode 100644 index 00000000000000..657f715a369ca4 --- /dev/null +++ b/docs/api/en/objects/ClippingGroup.html @@ -0,0 +1,62 @@ + + + + + + + + + + [page:Group] → + +

        [name]

        + +

        + A special version of the Group object that defines clipping planes for descendant objects. + ClippingGroups can be nested, with clipping planes accumulating by type: intersection or union. +

        + +

        Note: ClippingGroup is only supported with WebGPURenderer.

        + +

        Constructor

        + +

        [name]( )

        + +

        Properties

        +

        See the base [page:Group] class for common properties.

        + +

        [property:Boolean isClippingGroup]

        +

        Read-only flag to check if a given object is of type [name].

        + +

        [property:Array clippingPlanes]

        +

        + User-defined clipping planes specified as THREE.Plane objects in world + space. These planes apply to the objects that are children of this ClippingGroup. + Points in space whose signed distance to the plane is negative are clipped + (not rendered). See the [example:webgpu_clipping webgpu / clipping] example. Default is `[]`. +

        + +

        [property:Boolean enabled]

        +

        Determines if the clipping planes defined by this object are applied. Default is `true`.

        + +

        [property:Boolean clipIntersection]

        +

        + Changes the behavior of clipping planes so that only their intersection is + clipped, rather than their union. Default is `false`. +

        + +

        [property:Boolean clipShadows]

        +

        + Defines whether to clip shadows according to the clipping planes specified + by this ClippingGroup. Default is `false`. +

        + +

        Methods

        +

        See the base [page:Object3D] class for common methods.

        + +

        Source

        +

        + [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

        + + diff --git a/docs/api/en/objects/InstancedMesh.html b/docs/api/en/objects/InstancedMesh.html index 7fa7b1e32a0e11..2de15fdbfe18ef 100644 --- a/docs/api/en/objects/InstancedMesh.html +++ b/docs/api/en/objects/InstancedMesh.html @@ -14,7 +14,7 @@

        [name]

        A special version of [page:Mesh] with instanced rendering support. Use [name] if you have to render a large number of objects with the same - geometry and material but with different world transformations. The usage + geometry and material(s) but with different world transformations. The usage of [name] will help you to reduce the number of draw calls and thus improve the overall rendering performance in your application.

        @@ -34,8 +34,8 @@

        [page:BufferGeometry geometry] - an instance of [page:BufferGeometry].
        - [page:Material material] - an instance of [page:Material]. Default is a - new [page:MeshBasicMaterial].
        + [page:Material material] — a single or an array of + [page:Material]. Default is a new [page:MeshBasicMaterial].
        [page:Integer count] - the number of instances.

        @@ -79,6 +79,13 @@

        [property:InstancedBufferAttribute instanceMatrix]

        instanced data via [page:.setMatrixAt]().

        +

        [property:DataTexture morphTexture]

        +

        + Represents the morph target weights of all instances. You have to set its + [page:Texture.needsUpdate needsUpdate] flag to true if you modify + instanced data via [page:.setMorphAt](). +

        +

        [property:Boolean isInstancedMesh]

        Read-only flag to check if a given object is of type [name].

        @@ -87,17 +94,16 @@

        Methods

        [method:undefined computeBoundingBox]()

        - Computes the bounding box, updating [page:.boundingBox] attribute.
        - Bounding boxes aren't computed by default. They need to be explicitly - computed, otherwise they are `null`. + Computes the bounding box of the instanced mesh, and updates the [page:.boundingBox] attribute. + The bounding box is not computed by the engine; it must be computed by your app. + You may need to recompute the bounding box if an instance is transformed via [page:.setMatrixAt]().

        [method:undefined computeBoundingSphere]()

        - Computes the bounding sphere, updating [page:.boundingSphere] - attribute.
        - Bounding spheres aren't computed by default. They need to be explicitly - computed, otherwise they are `null`. + Computes the bounding sphere of the instanced mesh, and updates the [page:.boundingSphere] attribute. + The engine automatically computes the bounding sphere when it is needed, e.g., for ray casting or view frustum culling. + You may need to recompute the bounding sphere if an instance is transformed via [page:.setMatrixAt]().

        [method:undefined dispose]()

        @@ -132,6 +138,18 @@

        Get the local transformation matrix of the defined instance.

        +

        + [method:undefined getMorphAt]( [param:Integer index], [param:Mesh mesh] ) +

        +

        + [page:Integer index]: The index of an instance. Values have to be in the + range [0, count]. +

        +

        + [page:Mesh mesh]: The [page:Mesh.morphTargetInfluences .morphTargetInfluences] property of this mesh will be filled with the morph target weights of the defined instance. +

        +

        Get the morph target weights of the defined instance.

        +

        [method:undefined setColorAt]( [param:Integer index], [param:Color color] )

        @@ -162,6 +180,26 @@

        sure you set [page:.instanceMatrix][page:BufferAttribute.needsUpdate .needsUpdate] to true after updating all the matrices.

        +

        + Negatively scaled matrices are not supported. +

        + +

        + [method:undefined setMorphAt]( [param:Integer index], [param:Mesh mesh] ) +

        +

        + [page:Integer index]: The index of an instance. Values have to be in the + range [0, count]. +

        +

        + [page:Mesh mesh]: A mesh with [page:Mesh.morphTargetInfluences .morphTargetInfluences] property containing the morph target weights + of a single instance. +

        +

        + Sets the morph target weights to the defined instance. Make + sure you set [page:.morphTexture][page:Texture.needsUpdate .needsUpdate] + to true after updating all the influences. +

        Source

        diff --git a/docs/api/en/objects/LOD.html b/docs/api/en/objects/LOD.html index 6f20b73dc3bc64..ad084b064568d0 100644 --- a/docs/api/en/objects/LOD.html +++ b/docs/api/en/objects/LOD.html @@ -25,10 +25,11 @@

        Code Example

        const lod = new THREE.LOD(); + const material = new THREE.MeshBasicMaterial( { color: 0xffff00 } ); //Create spheres with 3 levels of detail and create new LOD levels for them for( let i = 0; i < 3; i++ ) { - const geometry = new THREE.IcosahedronGeometry( 10, 3 - i ) + const geometry = new THREE.IcosahedronGeometry( 10, 3 - i ); const mesh = new THREE.Mesh( geometry, material ); lod.addLevel( mesh, i * 75 ); } @@ -78,16 +79,22 @@

        [page:Object3D object] - The [page:Object3D] to display at this level.
        [page:Float distance] - The distance at which to display this level of - detail. Default 0.0.
        + detail. Default `0.0`.
        [page:Float hysteresis] - Threshold used to avoid flickering at LOD - boundaries, as a fraction of distance. Default 0.0.

        + boundaries, as a fraction of distance. Default `0.0`.

        Adds a mesh that will display at a certain distance and greater. Typically the further away the distance, the lower the detail on the mesh.

        +

        + [method:Boolean removeLevel]( [param:Float distance]) +

        +

        + distance - Distance of the level to delete.

        -

        [method:LOD clone]()

        -

        Returns a clone of this LOD object with its associated levels.

        + Removes an existing level, based on the distance from the camera. + Returns `true` when the level has been removed. Otherwise `false`. +

        [method:Integer getCurrentLevel]()

        Get the currently active LOD level. As index of the levels array.

        diff --git a/docs/api/en/objects/Line.html b/docs/api/en/objects/Line.html index 78a2880b2bbe9d..210f9a0abcf0d2 100644 --- a/docs/api/en/objects/Line.html +++ b/docs/api/en/objects/Line.html @@ -95,9 +95,6 @@

        [page:Raycaster.intersectObject] will call this method.

        -

        [method:Line clone]()

        -

        Returns a clone of this Line object and its descendants.

        -

        [method:undefined updateMorphTargets]()

        Updates the morphTargets to have no influence on the object. Resets the diff --git a/docs/api/en/objects/Mesh.html b/docs/api/en/objects/Mesh.html index 953abbe799c389..11a66790f365c7 100644 --- a/docs/api/en/objects/Mesh.html +++ b/docs/api/en/objects/Mesh.html @@ -74,9 +74,6 @@

        [property:Object morphTargetDictionary]

        Methods

        See the base [page:Object3D] class for common methods.

        -

        [method:Mesh clone]()

        -

        Returns a clone of this [name] object and its descendants.

        -

        [method:Vector3 getVertexPosition]( [param:Integer index], [param:Vector3 target] )

        diff --git a/docs/api/en/objects/Points.html b/docs/api/en/objects/Points.html index cb75ec18bcee6c..9d0cc2187b92b8 100644 --- a/docs/api/en/objects/Points.html +++ b/docs/api/en/objects/Points.html @@ -71,9 +71,6 @@

        [page:Raycaster.intersectObject] will call this method.

        -

        [method:Points clone]()

        -

        Returns a clone of this Points object and its descendants.

        -

        [method:undefined updateMorphTargets]()

        Updates the morphTargets to have no influence on the object. Resets the diff --git a/docs/api/en/objects/Skeleton.html b/docs/api/en/objects/Skeleton.html index 369a9b58d98482..11b10d36af7c53 100644 --- a/docs/api/en/objects/Skeleton.html +++ b/docs/api/en/objects/Skeleton.html @@ -77,9 +77,6 @@

        [property:DataTexture boneTexture]

        The [page:DataTexture] holding the bone data when using a vertex texture.

        -

        [property:Integer boneTextureSize]

        -

        The size of the [page:.boneTexture].

        -

        Methods

        [method:Skeleton clone]()

        diff --git a/docs/api/en/objects/SkinnedMesh.html b/docs/api/en/objects/SkinnedMesh.html index bb7dbaf3acc9a7..aaf27d36b2347d 100644 --- a/docs/api/en/objects/SkinnedMesh.html +++ b/docs/api/en/objects/SkinnedMesh.html @@ -13,10 +13,7 @@

        [name]

        A mesh that has a [page:Skeleton] with [page:Bone bones] that can then be - used to animate the vertices of the geometry.

        - - [name] can only be used with WebGL 2. With WebGL 1 `OES_texture_float` and - vertex textures support is required. + used to animate the vertices of the geometry.

        @@ -99,10 +96,10 @@

        Properties

        [property:String bindMode]

        - Either "attached" or "detached". "attached" uses the - [page:SkinnedMesh.matrixWorld] property for the base transform matrix of - the bones. "detached" uses the [page:SkinnedMesh.bindMatrix]. Default is - "attached". + Either `AttachedBindMode` or `DetachedBindMode`. `AttachedBindMode` means the skinned mesh + shares the same world space as the skeleton. This is not true when using `DetachedBindMode` + which is useful when sharing a skeleton across multiple skinned meshes. + Default is `AttachedBindMode`.

        [property:Matrix4 bindMatrix]

        @@ -152,20 +149,16 @@

        [method:SkinnedMesh clone]()

        [method:undefined computeBoundingBox]()

        - Computes the bounding box, updating [page:.boundingBox] attribute.
        - Bounding boxes aren't computed by default. They need to be explicitly - computed, otherwise they are `null`. If an instance of [name] is animated, - this method should be called per frame to compute a correct bounding box. + Computes the bounding box of the skinned mesh, and updates the [page:.boundingBox] attribute. + The bounding box is not computed by the engine; it must be computed by your app. + If the skinned mesh is animated, the bounding box should be recomputed per frame.

        [method:undefined computeBoundingSphere]()

        - Computes the bounding sphere, updating [page:.boundingSphere] - attribute.
        - Bounding spheres aren't computed by default. They need to be explicitly - computed, otherwise they are `null`. If an instance of [name] is animated, - this method should be called per frame to compute a correct bounding - sphere. + Computes the bounding sphere of the skinned mesh, and updates the [page:.boundingSphere] attribute. + The bounding sphere is automatically computed by the engine when it is needed, e.g., for ray casting and view frustum culling. + If the skinned mesh is animated, the bounding sphere should be recomputed per frame.

        [method:undefined normalizeSkinWeights]()

        diff --git a/docs/api/en/objects/Sprite.html b/docs/api/en/objects/Sprite.html index 875ba6d2efc317..0ec09a30eed30f 100644 --- a/docs/api/en/objects/Sprite.html +++ b/docs/api/en/objects/Sprite.html @@ -62,9 +62,6 @@

        [property:Vector2 center]

        Methods

        See the base [page:Object3D] class for common methods.

        -

        [method:Sprite clone]()

        -

        Returns a clone of this Sprite object and any descendants.

        -

        [method:this copy]( [param:Sprite sprite] )

        Copies the properties of the passed sprite to this one.

        diff --git a/docs/api/en/renderers/WebGL1Renderer.html b/docs/api/en/renderers/WebGL1Renderer.html deleted file mode 100644 index 6a0bd163ff7674..00000000000000 --- a/docs/api/en/renderers/WebGL1Renderer.html +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - - [page:WebGLRenderer] → - -

        [name]

        - -

        - Since r118 [page:WebGLRenderer] automatically uses a WebGL 2 rendering - context. When upgrading an existing project to => r118, applications might - break because of two reasons: -

        -
          -
        • Custom shader code needs to be GLSL 3.0 conform.
        • -
        • WebGL 1 extension checks have to be changed.
        • -
        -

        - If you can't afford the time to upgrade your code but still want to use - the latest version, you can use [name]. This version of the renderer will - enforce a WebGL 1 rendering context. -

        - -

        Constructor

        - -

        [name]( [param:Object parameters] )

        -

        Creates a new [name].

        - -

        Properties

        -

        See the base [page:WebGLRenderer] class for common properties.

        - -

        [property:Boolean isWebGL1Renderer]

        -

        Read-only flag to check if a given object is of type [name].

        - -

        Methods

        -

        See the base [page:WebGLRenderer] class for common methods.

        - -

        Source

        -

        - [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] -

        - - diff --git a/docs/api/en/renderers/WebGL3DRenderTarget.html b/docs/api/en/renderers/WebGL3DRenderTarget.html index c7dc06a3f54507..607ce364a8855f 100644 --- a/docs/api/en/renderers/WebGL3DRenderTarget.html +++ b/docs/api/en/renderers/WebGL3DRenderTarget.html @@ -16,20 +16,20 @@

        [name]

        Constructor

        - [name]( [param:Number width], [param:Number height], [param:Number depth] ) + [name]( [param:Number width], [param:Number height], [param:Number depth], [param:Object options] )

        - [page:Number width] - the width of the render target, in pixels. Default - is `1`.
        - [page:Number height] - the height of the render target, in pixels. Default - is `1`.
        - [page:Number depth] - the depth of the render target. Default is `1`.

        + [page:Number width] - the width of the render target, in pixels. Default is `1`.
        + [page:Number height] - the height of the render target, in pixels. Default is `1`.
        + [page:Number depth] - the depth of the render target. Default is `1`.
        + [page:Object options] - optional object that holds texture parameters for an + auto-generated target texture and depthBuffer/stencilBuffer booleans. See [page:WebGLRenderTarget] for details.

        Creates a new [name].

        Properties

        -

        See [page:WebGLRenderTarget] for inherited properties

        +

        See [page:WebGLRenderTarget] for inherited properties.

        [property:number depth]

        The depth of the render target.

        @@ -41,7 +41,7 @@

        [property:Data3DTexture texture]

        Methods

        -

        See [page:WebGLRenderTarget] for inherited methods

        +

        See [page:WebGLRenderTarget] for inherited methods.

        Source

        diff --git a/docs/api/en/renderers/WebGLArrayRenderTarget.html b/docs/api/en/renderers/WebGLArrayRenderTarget.html index 633b5e09b33491..60f76b624bb2e1 100644 --- a/docs/api/en/renderers/WebGLArrayRenderTarget.html +++ b/docs/api/en/renderers/WebGLArrayRenderTarget.html @@ -18,28 +18,27 @@

        [name]

        Examples

        - [example:webgl2_rendertarget_texture2darray WebGL 2 / render target / array]
        + [example:webgl_rendertarget_texture2darray WebGL / render target / array]

        Constructor

        - [name]( [param:Number width], [param:Number height], [param:Number depth] ) + [name]( [param:Number width], [param:Number height], [param:Number depth], [param:Object options] )

        - [page:Number width] - the width of the render target, in pixels. Default - is `1`.
        - [page:Number height] - the height of the render target, in pixels. Default - is `1`.
        - [page:Number depth] - the depth/layer count of the render target. Default - is `1`.

        + [page:Number width] - the width of the render target, in pixels. Default is `1`.
        + [page:Number height] - the height of the render target, in pixels. Default is `1`.
        + [page:Number depth] - the depth/layer count of the render target. Default is `1`.
        + [page:Object options] - optional object that holds texture parameters for an + auto-generated target texture and depthBuffer/stencilBuffer booleans. See [page:WebGLRenderTarget] for details.

        Creates a new [name].

        Properties

        -

        See [page:WebGLRenderTarget] for inherited properties

        +

        See [page:WebGLRenderTarget] for inherited properties.

        [property:number depth]

        The depth of the render target.

        @@ -51,7 +50,7 @@

        [property:DataArrayTexture texture]

        Methods

        -

        See [page:WebGLRenderTarget] for inherited methods

        +

        See [page:WebGLRenderTarget] for inherited methods.

        Source

        diff --git a/docs/api/en/renderers/WebGLCubeRenderTarget.html b/docs/api/en/renderers/WebGLCubeRenderTarget.html index 95c0521683cc1c..8e4e6f08034de4 100644 --- a/docs/api/en/renderers/WebGLCubeRenderTarget.html +++ b/docs/api/en/renderers/WebGLCubeRenderTarget.html @@ -52,11 +52,11 @@

        [name]([param:Number size], [param:Object options])

        Properties

        -

        See [page:WebGLRenderTarget] for inherited properties

        +

        See [page:WebGLRenderTarget] for inherited properties.

        Methods

        -

        See [page:WebGLRenderTarget] for inherited methods

        +

        See [page:WebGLRenderTarget] for inherited methods.

        [method:this fromEquirectangularTexture]( [param:WebGLRenderer renderer], [param:Texture texture] ) diff --git a/docs/api/en/renderers/WebGLMultipleRenderTargets.html b/docs/api/en/renderers/WebGLMultipleRenderTargets.html deleted file mode 100644 index d024a0b3d85cdf..00000000000000 --- a/docs/api/en/renderers/WebGLMultipleRenderTargets.html +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - - - - [page:WebGLRenderTarget] → - -

        [name]

        - -

        - A special render target that enables a fragment shader to write to several - textures. This approach is useful for advanced rendering techniques like - post-processing or deferred rendering. Heads up: [name] can only be used - with a WebGL 2 rendering context. -

        - -

        Examples

        - -

        - [example:webgl2_multiple_rendertargets webgl2 / multiple / rendertargets ] -

        - -

        Constructor

        - -

        - [name]([param:Number width], [param:Number height], [param:Number count], - [param:Object options]) -

        - -

        - [page:Number width] - The width of the render target. Default is `1`.
        - [page:Number height] - The height of the render target. Default is `1`.
        - [page:Number count] - The number of render targets. Default is `1`.
        - - options - (optional object that holds texture parameters for an - auto-generated target texture and depthBuffer/stencilBuffer booleans. For - an explanation of the texture parameters see [page:Texture Texture]. For a - list of valid options, see [page:WebGLRenderTarget WebGLRenderTarget]).

        -

        - -

        Properties

        - -

        [property:Boolean isWebGLMultipleRenderTargets]

        -

        Read-only flag to check if a given object is of type [name].

        - -

        [property:Array texture]

        -

        - The texture property is overwritten in [name] and replaced with an array. - This array holds the [page:WebGLRenderTarget.texture texture] references - of the respective render targets. -

        - -

        - [page:WebGLRenderTarget WebGLRenderTarget] properties are available on - this class. -

        - -

        Methods

        - -

        - [page:WebGLRenderTarget WebGLRenderTarget] methods are available on this - class. -

        - -

        Source

        - -

        - [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] -

        - - diff --git a/docs/api/en/renderers/WebGLRenderTarget.html b/docs/api/en/renderers/WebGLRenderTarget.html index 80f9df342c9db6..407f4ccaa7c944 100644 --- a/docs/api/en/renderers/WebGLRenderTarget.html +++ b/docs/api/en/renderers/WebGLRenderTarget.html @@ -47,9 +47,13 @@

        [page:Texture.anisotropy]
        [page:Constant colorSpace] - default is [page:Textures NoColorSpace].
        + [page:String internalFormat] - default is `null`.
        [page:Boolean depthBuffer] - default is `true`.
        [page:Boolean stencilBuffer] - default is `false`.
        - [page:Number samples] - default is 0.

        + [page:Boolean resolveDepthBuffer] - default is `true`.
        + [page:Boolean resolveStencilBuffer] - default is `true`.
        + [page:Number samples] - default is `0`.
        + [page:Number count] - default is `1`.

        Creates a new [name]

        @@ -83,12 +87,31 @@

        [property:Texture texture]

        further processing.

        +

        [property:Texture textures]

        +

        + An array holding the [page:WebGLRenderTarget.texture texture] references + of multiple render targets configured with the [page:Number count] option. +

        +

        [property:Boolean depthBuffer]

        Renders to the depth buffer. Default is true.

        [property:Boolean stencilBuffer]

        Renders to the stencil buffer. Default is false.

        +

        [property:Boolean resolveDepthBuffer]

        +

        + Defines whether the depth buffer should be resolved when rendering into a multisampled render target. + Default is `true`. +

        + +

        [property:Boolean resolveStencilBuffer]

        +

        + Defines whether the stencil buffer should be resolved when rendering into a multisampled render target. + This property has no effect when [page:.resolveDepthBuffer] is set to `false`. + Default is `true`. +

        +

        [property:DepthTexture depthTexture]

        If set, the scene depth will be rendered to this texture. Default is null. @@ -96,8 +119,7 @@

        [property:DepthTexture depthTexture]

        [property:Number samples]

        - Defines the count of MSAA samples. Can only be used with WebGL 2. Default - is `0`. + Defines the count of MSAA samples. Default is `0`.

        Methods

        @@ -111,7 +133,11 @@

        [method:WebGLRenderTarget clone]()

        Creates a copy of this render target.

        [method:this copy]( [param:WebGLRenderTarget source] )

        -

        Adopts the settings of the given render target.

        +

        + Adopts the settings of the given render target. This is a structural copy so + no resources are shared between render targets after the copy. That includes + all MRT textures and the depth texture. +

        [method:undefined dispose]()

        diff --git a/docs/api/en/renderers/WebGLRenderer.html b/docs/api/en/renderers/WebGLRenderer.html index 60140a3254661c..77c4892fe3cd1e 100644 --- a/docs/api/en/renderers/WebGLRenderer.html +++ b/docs/api/en/renderers/WebGLRenderer.html @@ -19,7 +19,7 @@

        Constructor

        [name]( [param:Object parameters] )

        [page:Object parameters] - (optional) object with properties defining the - renderer's behaviour. The constructor also accepts no parameters at all. + renderer's behavior. The constructor also accepts no parameters at all. In all cases, it will assume sane defaults when parameters are missing. The following are valid parameters:

        @@ -49,7 +49,7 @@

        [name]( [param:Object parameters] )

        [page:Boolean stencil] - whether the drawing buffer has a [link:https://en.wikipedia.org/wiki/Stencil_buffer stencil buffer] of at - least 8 bits. Default is `true`.
        + least 8 bits. Default is `false`.
        [page:Boolean preserveDrawingBuffer] - whether to preserve the buffers until manually cleared or overwritten. Default is `false`.
        @@ -75,6 +75,9 @@

        [name]( [param:Object parameters] )

        [link:https://www.khronos.org/opengl/wiki/Early_Fragment_Test Early Fragment Test] optimization and can cause a decrease in performance. Default is `false`. See the [example:webgl_camera_logarithmicdepthbuffer camera / logarithmicdepthbuffer] example. + + [page:Boolean reverseDepthBuffer] - whether to use a reverse depth buffer. Requires the `EXT_clip_control` extension. + This is a more faster and accurate version than logarithmic depth buffer. Default is `false`.

        Properties

        @@ -82,7 +85,7 @@

        Properties

        [property:Boolean autoClear]

        Defines whether the renderer should automatically clear its output before - rendering a frame. + rendering a frame. Default is `true`.

        [property:Boolean autoClearColor]

        @@ -103,22 +106,6 @@

        [property:Boolean autoClearStencil]

        should clear the stencil buffer. Default is `true`.

        -

        [property:Object debug]

        -

        - - [page:Boolean checkShaderErrors]: If it is true, defines whether - material shader programs are checked for errors during compilation and - linkage process. It may be useful to disable this check in production for - performance gain. It is strongly recommended to keep these checks enabled - during development. If the shader does not compile and link - it will not - work and associated material will not render. Default is `true`.
        - - [page:Function onShaderError]( gl, program, glVertexShader, - glFragmentShader ): A callback function that can be used for custom error - reporting. The callback receives the WebGL context, an instance of - WebGLProgram as well two instances of WebGLShader representing the vertex - and fragment shader. Assigning a custom function disables the default - error reporting. Default is `null`. -

        -

        [property:Object capabilities]

        An object containing details about the capabilities of the current @@ -135,9 +122,7 @@

        [property:Object capabilities]

        - [page:Boolean isWebGL2]: `true` if the context in use is a WebGL2RenderingContext object.
        - [page:Boolean logarithmicDepthBuffer]: `true` if the [page:parameter logarithmicDepthBuffer] - was set to true in the constructor and the context - supports the - [link:https://developer.mozilla.org/en-US/docs/Web/API/EXT_frag_depth EXT_frag_depth] extension.
        + was set to true in the constructor.
        - [page:Integer maxAttributes]: The value of `gl.MAX_VERTEX_ATTRIBS`.
        - [page:Integer maxCubemapSize]: The value of `gl.MAX_CUBE_MAP_TEXTURE_SIZE`. Maximum height * width of cube map @@ -161,6 +146,9 @@

        [property:Object capabilities]

        be used in a vertex shader.
        - [page:String precision]: The shader precision currently being used by the renderer.
        + - [page:Boolean reverseDepthBuffer]: `true` if the [page:parameter reverseDepthBuffer] + was set to `true` in the constructor and the context + supports the [link:https://registry.khronos.org/webgl/extensions/EXT_clip_control/ EXT_clip_control] extension.
        - [page:Boolean vertexTextures]: `true` if [property:Integer maxVertexTextures] is greater than 0 (i.e. vertex textures can be used).

        @@ -172,6 +160,22 @@

        [property:Array clippingPlanes]

        the plane is negative are cut away. Default is [].

        +

        [property:Object debug]

        +

        + - [page:Boolean checkShaderErrors]: If it is true, defines whether + material shader programs are checked for errors during compilation and + linkage process. It may be useful to disable this check in production for + performance gain. It is strongly recommended to keep these checks enabled + during development. If the shader does not compile and link - it will not + work and associated material will not render. Default is `true`.
        + - [page:Function onShaderError]( gl, program, glVertexShader, + glFragmentShader ): A callback function that can be used for custom error + reporting. The callback receives the WebGL context, an instance of + WebGLProgram as well two instances of WebGLShader representing the vertex + and fragment shader. Assigning a custom function disables the default + error reporting. Default is `null`. +

        +

        [property:DOMElement domElement]

        A [link:https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas canvas] @@ -197,8 +201,13 @@

        [property:Object extensions]

      • `WEBGL_compressed_texture_s3tc`
      • `WEBGL_compressed_texture_pvrtc`
      • `WEBGL_compressed_texture_etc1`
      • +
      • See more: Extension list
      +

      + - [page:Boolean has]( [param:String extensionName] ): `true` if the extension is supported. +

      +

      [property:string outputColorSpace]

      Defines the output color space of the renderer. Default is [page:Textures THREE.SRGBColorSpace]. @@ -254,9 +263,6 @@

      [property:Boolean localClippingEnabled]

      Default is `false`.

      -

      [property:Boolean useLegacyLights]

      -

      Whether to use the legacy lighting mode or not. Default is `true`.

      -

      [property:Object properties]

      Used internally by the renderer to keep track of various sub object @@ -317,6 +323,9 @@

      [property:Constant toneMapping]

      [property:Number toneMappingExposure]

      Exposure level of tone mapping. Default is `1`.

      +

      [property:Number transmissionResolutionScale]

      +

      The normalized resolution scale for the transmission render target, measured in percentage of viewport dimensions. Lowering this value can result in significant improvements to [page:MeshPhysicalMaterial MeshPhysicalMaterial] transmission performance. Default is `1`.

      +

      [property:WebXRManager xr]

      Provides access to the WebXR related [page:WebXRManager interface] of the @@ -351,39 +360,41 @@

      [method:undefined clearStencil]( )

      - [method:undefined compile]( [param:Object3D scene], [param:Camera camera] ) + [method:Set compile]( [param:Object3D scene], [param:Camera camera], [param:Scene targetScene] )

      - Compiles all materials in the scene with the camera. This is useful to - precompile shaders before the first rendering. -

      + Compiles all materials in the scene with the camera. This is useful to precompile shaders before the first rendering. + If you want to add a 3D object to an existing scene, use the third optional parameter for applying the target scene.
      + Note that the (target) scene's lighting and environment should be configured before calling this method. +

      - [method:undefined copyFramebufferToTexture]( [param:Vector2 position], [param:FramebufferTexture texture], [param:Number level] ) + [method:Promise compileAsync]( [param:Object3D scene], [param:Camera camera], [param:Scene targetScene] )

      - Copies pixels from the current WebGLFramebuffer into a 2D texture. Enables - access to - [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/copyTexImage2D WebGLRenderingContext.copyTexImage2D]. + Asynchronous version of [page:WebGLRenderer.compile .compile](). The method returns a Promise that resolves when the + given scene can be rendered without unnecessary stalling due to shader compilation.

      + + This method makes use of the *KHR_parallel_shader_compile* WebGL extension.

      - [method:undefined copyTextureToTexture]( [param:Vector2 position], [param:Texture srcTexture], [param:Texture dstTexture], [param:Number level] ) + [method:undefined copyFramebufferToTexture]( [param:FramebufferTexture texture], [param:Vector2 position], [param:Number level] )

      - Copies all pixels of a texture to an existing texture starting from the - given position. Enables access to - [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/texSubImage2D WebGLRenderingContext.texSubImage2D]. + Copies pixels from the current WebGLFramebuffer into a 2D texture. Enables + access to + [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/copyTexImage2D WebGLRenderingContext.copyTexImage2D].

      - [method:undefined copyTextureToTexture3D]( [param:Box3 sourceBox], [param:Vector3 position], [param:Texture srcTexture], [param:Texture dstTexture], [param:Number level] ) + [method:undefined copyTextureToTexture]( [param:Texture srcTexture], [param:Texture dstTexture], [param:Box2 srcRegion] | [param:Box3 srcRegion], [param:Vector2 dstPosition] | [param:Vector3 dstPosition], [param:Number srcLevel], [param:Number dstLevel] )

      - Copies the pixels of a texture in the bounds '[page:Box3 sourceBox]' in - the destination texture starting from the given position. Enables access - to - [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext/texSubImage3D WebGL2RenderingContext.texSubImage3D]. + Copies the pixels of a texture in the bounds '[page:Box3 srcRegion]' in the destination texture starting from the given position. + 2D Texture, 3D Textures, or a mix of the two can be used as source and destination texture arguments for copying between layers of 3d textures.
      + The `depthTexture` and `texture` property of render targets are supported as well.
      + When using render target textures as `srcTexture` and `dstTexture`, you must make sure both render targets are initialized e.g. via [page:.initRenderTarget]().

      [method:undefined dispose]( )

      @@ -406,8 +417,8 @@

      [method:undefined forceContextRestore]( )

      [method:Float getClearAlpha]()

      - Returns a [page:Float float] with the current clear alpha. Ranges from 0 - to 1. + Returns a [page:Float float] with the current clear alpha. Ranges from `0` + to `1`.

      [method:Color getClearColor]( [param:Color target] )

      @@ -484,6 +495,13 @@

      [method:undefined initTexture]( [param:Texture texture] )

      and GPU upload overhead).

      +

      [method:undefined initRenderTarget]( [param:WebGLRenderTarget target] )

      +

      + Initializes the given WebGLRenderTarget memory. Useful for initializing a render + target so data can be copied into it using [page:WebGLRenderer.copyTextureToTexture .copyTextureToTexture] + before it has been rendered to. +

      +

      [method:undefined resetGLState]( )

      Reset the GL state to default. Called internally if the WebGL context is @@ -503,16 +521,23 @@

      This is a wrapper around [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/readPixels WebGLRenderingContext.readPixels]().

      -

      - See the [example:webgl_interactive_cubes_gpu interactive / cubes / gpu] - example. -

      For reading out a [page:WebGLCubeRenderTarget WebGLCubeRenderTarget] use the optional parameter activeCubeFaceIndex to determine which face should be read.

      +

      + [method:Promise readRenderTargetPixelsAsync]( [param:WebGLRenderTarget renderTarget], [param:Float x], [param:Float y], [param:Float width], [param:Float height], [param:TypedArray buffer], [param:Integer activeCubeFaceIndex] ) +

      +

      + Asynchronous, non-blocking version of [page:WebGLRenderer.readRenderTargetPixels .readRenderTargetPixels]. The + returned promise resolves once the buffer data is ready to be used. +

      +

      + See the [example:webgl_interactive_cubes_gpu interactive / cubes / gpu] example. +

      +

      [method:undefined render]( [param:Object3D scene], [param:Camera camera] )

      diff --git a/docs/api/en/renderers/webxr/WebXRManager.html b/docs/api/en/renderers/webxr/WebXRManager.html index ede011d1ceffb6..fe96195f828e92 100644 --- a/docs/api/en/renderers/webxr/WebXRManager.html +++ b/docs/api/en/renderers/webxr/WebXRManager.html @@ -92,9 +92,6 @@

      [method:Group getHand]( [param:Integer index] )

      no physical controllers are used.

      -

      [method:Set getPlanes]()

      -

      Returns the set of planes detected by WebXR's plane detection API.

      -

      [method:String getReferenceSpace]()

      Returns the reference space.

      diff --git a/docs/api/en/scenes/Fog.html b/docs/api/en/scenes/Fog.html index 140dac91884963..fa46c2a490859e 100644 --- a/docs/api/en/scenes/Fog.html +++ b/docs/api/en/scenes/Fog.html @@ -16,8 +16,9 @@

      [name]

      Code Example

      - const scene = new THREE.Scene(); - scene.fog = new THREE.Fog( 0xcccccc, 10, 15 ); + + const scene = new THREE.Scene(); + scene.fog = new THREE.Fog( 0xcccccc, 10, 15 );

      Constructor

      @@ -52,7 +53,7 @@

      [property:Float near]

      The minimum distance to start applying fog. Objects that are less than 'near' units from the active camera won't be affected by fog.

      -

      Default is 1.

      +

      Default is `1`.

      [property:Float far]

      @@ -60,7 +61,7 @@

      [property:Float far]

      Objects that are more than 'far' units away from the active camera won't be affected by fog.

      -

      Default is 1000.

      +

      Default is `1000`.

      Methods

      diff --git a/docs/api/en/scenes/FogExp2.html b/docs/api/en/scenes/FogExp2.html index 200adfcb885202..6269634ca261bd 100644 --- a/docs/api/en/scenes/FogExp2.html +++ b/docs/api/en/scenes/FogExp2.html @@ -18,8 +18,8 @@

      [name]

      Code Example

      -const scene = new THREE.Scene(); -scene.fog = new THREE.FogExp2( 0xcccccc, 0.002 ); + const scene = new THREE.Scene(); + scene.fog = new THREE.FogExp2( 0xcccccc, 0.002 );

      Constructor

      @@ -49,7 +49,7 @@

      [property:Color color]

      [property:Float density]

      Defines how fast the fog will grow dense.

      -

      Default is 0.00025.

      +

      Default is `0.00025`.

      Methods

      diff --git a/docs/api/en/scenes/Scene.html b/docs/api/en/scenes/Scene.html index 81821b91ed3473..d6f865c6a70ab2 100644 --- a/docs/api/en/scenes/Scene.html +++ b/docs/api/en/scenes/Scene.html @@ -11,7 +11,7 @@

      [name]

      - Scenes allow you to set up what and where is to be rendered by three.js. + Scenes allow you to set up what is to be rendered and where by three.js. This is where you place objects, lights and cameras.

      @@ -39,14 +39,20 @@

      [property:Object background]

      [property:Float backgroundBlurriness]

      Sets the blurriness of the background. Only influences environment maps - assigned to [page:Scene.background]. Valid input is a float between *0* - and *1*. Default is *0*. + assigned to [page:Scene.background]. Valid input is a float between `0` + and `1`. Default is `0`.

      [property:Float backgroundIntensity]

      Attenuates the color of the background. Only applies to background - textures. Default is *1*. + textures. Default is `1`. +

      + +

      [property:Euler backgroundRotation]

      +

      + The rotation of the background in radians. Only influences environment maps + assigned to [page:Scene.background]. Default is `(0,0,0)`.

      [property:Texture environment]

      @@ -56,6 +62,18 @@

      [property:Texture environment]

      [page:MeshStandardMaterial.envMap]. Default is `null`.

      +

      [property:Float environmentIntensity]

      +

      + Attenuates the color of the environment. Only influences environment maps + assigned to [page:Scene.environment]. Default is `1`. +

      + +

      [property:Euler environmentRotation]

      +

      + The rotation of the environment map in radians. Only influences physical materials + in the scene when [page:.environment] is used. Default is `(0,0,0)`. +

      +

      [property:Fog fog]

      diff --git a/docs/api/en/textures/CanvasTexture.html b/docs/api/en/textures/CanvasTexture.html index 9971fbc89cfab5..9c5828d5edf975 100644 --- a/docs/api/en/textures/CanvasTexture.html +++ b/docs/api/en/textures/CanvasTexture.html @@ -57,7 +57,7 @@

      [page:Number anisotropy] -- The number of samples taken along the axis through the pixel that has the highest density of texels. By default, this - value is 1. A higher value gives a less blurry result than a basic mipmap, + value is `1`. A higher value gives a less blurry result than a basic mipmap, at the cost of more texture samples being used. Use [page:WebGLrenderer.getMaxAnisotropy renderer.getMaxAnisotropy]() to find the maximum valid anisotropy value for the GPU; this value is usually a diff --git a/docs/api/en/textures/CompressedArrayTexture.html b/docs/api/en/textures/CompressedArrayTexture.html index 7c2d2ba2a25c0d..0b1cfcd5ee6f22 100644 --- a/docs/api/en/textures/CompressedArrayTexture.html +++ b/docs/api/en/textures/CompressedArrayTexture.html @@ -61,11 +61,32 @@

      [property:number wrapR]

      [property:Object image]

      Overridden with a object containing width, height, and depth.

      +

      [property:Set layerUpdates]

      +

      + A set of all layers which need to be updated in the texture. See + [Page:CompressedTextureArray.addLayerUpdate addLayerUpdate]. +

      +

      [property:Boolean isCompressedArrayTexture]

      Read-only flag to check if a given object is of type [name].

      Methods

      +

      [method:addLayerUpdate addLayerUpdate]( layerIndex )

      +

      + Describes that a specific layer of the texture needs to be updated. + Normally when [page:Texture.needsUpdate needsUpdate] is set to true, the + entire compressed texture array is sent to the GPU. Marking specific + layers will only transmit subsets of all mipmaps associated with a + specific depth in the array which is often much more performant. +

      + +

      [method:clearLayerUpdates clearLayerUpdates]()

      +

      + Resets the layer updates registry. See + [Page:CompressedTextureArray.addLayerUpdate addLayerUpdate]. +

      +

      See the base [page:CompressedTexture CompressedTexture] class for common methods. diff --git a/docs/api/en/textures/CompressedTexture.html b/docs/api/en/textures/CompressedTexture.html index f343b3df2c6005..c0f11bae493466 100644 --- a/docs/api/en/textures/CompressedTexture.html +++ b/docs/api/en/textures/CompressedTexture.html @@ -64,7 +64,7 @@

      [page:Number anisotropy] -- The number of samples taken along the axis through the pixel that has the highest density of texels. By default, this - value is 1. A higher value gives a less blurry result than a basic mipmap, + value is `1`. A higher value gives a less blurry result than a basic mipmap, at the cost of more texture samples being used. Use renderer.getMaxAnisotropy() to find the maximum valid anisotropy value for the GPU; this value is usually a power of 2.
      diff --git a/docs/api/en/textures/Data3DTexture.html b/docs/api/en/textures/Data3DTexture.html index 782c55664f2cf5..2c619c6ec58dcf 100644 --- a/docs/api/en/textures/Data3DTexture.html +++ b/docs/api/en/textures/Data3DTexture.html @@ -13,8 +13,7 @@

      [name]

      Creates a three-dimensional texture from raw data, with parameters to - divide it into width, height, and depth. This type of texture can only be - used with a WebGL 2 rendering context. + divide it into width, height, and depth.

      Constructor

      @@ -63,10 +62,10 @@

      Code Example

      Examples

      - [example:webgl2_materials_texture3d WebGL2 / materials / texture3d]
      - [example:webgl2_materials_texture3d_partialupdate WebGL2 / materials / texture3d / partialupdate]
      - [example:webgl2_volume_cloud WebGL2 / volume / cloud]
      - [example:webgl2_volume_perlin WebGL2 / volume / perlin] + [example:webgl_texture3d WebGL / texture3d]
      + [example:webgl_texture3d_partialupdate WebGL / texture3d / partialupdate]
      + [example:webgl_volume_cloud WebGL / volume / cloud]
      + [example:webgl_volume_perlin WebGL / volume / perlin]

      Properties

      @@ -113,7 +112,7 @@

      [property:number minFilter]

      [property:number unpackAlignment]

      - 1 by default. Specifies the alignment requirements for the start of each + `1` by default. Specifies the alignment requirements for the start of each pixel row in memory. The allowable values are 1 (byte-alignment), 2 (rows aligned to even-numbered bytes), 4 (word-alignment), and 8 (rows start on double-word boundaries). See diff --git a/docs/api/en/textures/DataArrayTexture.html b/docs/api/en/textures/DataArrayTexture.html index c7b3c66861ae2d..7d078816310c23 100644 --- a/docs/api/en/textures/DataArrayTexture.html +++ b/docs/api/en/textures/DataArrayTexture.html @@ -13,8 +13,7 @@

      [name]

      Creates an array of textures directly from raw data, width and height and - depth. This type of texture can only be used with a WebGL 2 rendering - context. + depth.

      Constructor

      @@ -81,8 +80,8 @@

      Code Example

      Examples

      - [example:webgl2_materials_texture2darray WebGL2 / materials / texture2darray] - [example:webgl2_rendertarget_texture2darray WebGL2 / rendertarget / texture2darray] + [example:webgl_texture2darray WebGL / texture2darray]
      + [example:webgl_rendertarget_texture2darray WebGL / rendertarget / texture2darray]

      Properties

      @@ -127,7 +126,7 @@

      [property:number minFilter]

      [property:number unpackAlignment]

      - 1 by default. Specifies the alignment requirements for the start of each + `1` by default. Specifies the alignment requirements for the start of each pixel row in memory. The allowable values are 1 (byte-alignment), 2 (rows aligned to even-numbered bytes), 4 (word-alignment), and 8 (rows start on double-word boundaries). See @@ -144,8 +143,30 @@

      [property:number wrapR]

      page for details.

      +

      [property:Set layerUpdates]

      +

      + A set of all layers which need to be updated in the texture. See + [Page:DataArrayTexture.addLayerUpdate addLayerUpdate]. +

      +

      Methods

      +

      [method:addLayerUpdate addLayerUpdate]( layerIndex )

      +

      + Describes that a specific layer of the texture needs to be updated. + Normally when [page:Texture.needsUpdate needsUpdate] is set to true, the + entire compressed texture array is sent to the GPU. Marking specific + layers will only transmit subsets of all mipmaps associated with a + specific depth in the array which is often much more performant. +

      + +

      [method:clearLayerUpdates clearLayerUpdates]()

      +

      + Resets the layer updates registry. See + [Page:DataArrayTexture.addLayerUpdate addLayerUpdate]. +

      + +

      See the base [page:Texture Texture] class for common methods.

      Source

      diff --git a/docs/api/en/textures/DataTexture.html b/docs/api/en/textures/DataTexture.html index 44eaff9d8b21cf..201f849da97d91 100644 --- a/docs/api/en/textures/DataTexture.html +++ b/docs/api/en/textures/DataTexture.html @@ -98,7 +98,7 @@

      [property:Boolean isDataTexture]

      [property:number unpackAlignment]

      - 1 by default. Specifies the alignment requirements for the start of each + `1` by default. Specifies the alignment requirements for the start of each pixel row in memory. The allowable values are 1 (byte-alignment), 2 (rows aligned to even-numbered bytes), 4 (word-alignment), and 8 (rows start on double-word boundaries). See diff --git a/docs/api/en/textures/DepthTexture.html b/docs/api/en/textures/DepthTexture.html index 24f84ea0746f05..75e045769509c4 100644 --- a/docs/api/en/textures/DepthTexture.html +++ b/docs/api/en/textures/DepthTexture.html @@ -13,9 +13,7 @@

      [name]

      This class can be used to automatically save the depth information of a - rendering into a texture. When using a WebGL 1 rendering context, [name] - requires support for the - [link:https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/ WEBGL_depth_texture] extension. + rendering into a texture.

      Examples

      @@ -35,10 +33,7 @@

      [page:Number height] -- height of the texture.
      - [page:Constant type] -- Default is [page:Textures THREE.UnsignedIntType] - when using [page:Textures DepthFormat] and [page:Textures THREE.UnsignedInt248Type] - when using [page:Textures DepthStencilFormat]. - See [page:Textures type constants] for other choices.
      + [page:Constant type] -- Default is [page:Textures THREE.UnsignedIntType]. See [page:DepthTexture DepthTexture.type] for other choices.
      [page:Constant mapping] -- See [page:Textures mapping mode constants] for details.
      @@ -63,7 +58,7 @@

      [page:Number anisotropy] -- The number of samples taken along the axis through the pixel that has the highest density of texels. By default, this - value is 1. A higher value gives a less blurry result than a basic mipmap, + value is `1`. A higher value gives a less blurry result than a basic mipmap, at the cost of more texture samples being used. Use [page:WebGLrenderer.getMaxAnisotropy renderer.getMaxAnisotropy]() to find the maximum valid anisotropy value for the GPU; this value is usually a @@ -89,10 +84,14 @@

      [page:Texture.format format]

      [page:Texture.type type]

      - Default is [page:Textures THREE.UnsignedIntType] when using [page:Textures DepthFormat] - and [page:Textures THREE.UnsignedInt248Type] when using - [page:Textures DepthStencilFormat]. See [page:Textures format constants] - for details.
      + Default is [page:Textures THREE.UnsignedIntType]. The following are options and how they map to internal + gl depth format types depending on the stencil format, as well: + + [page:Textures THREE.UnsignedIntType] -- Uses DEPTH_COMPONENT24 or DEPTH24_STENCIL8 internally.
      + + [page:Textures THREE.FloatType] -- Uses DEPTH_COMPONENT32F or DEPTH32F_STENCIL8 internally.
      + + [page:Textures THREE.UnsignedShortType] -- Uses DEPTH_COMPONENT16 internally. Stencil buffer is unsupported when using this type.

      [page:Texture.magFilter magFilter]

      @@ -122,7 +121,7 @@

      [property:Boolean isDepthTexture]

      [property:number compareFunction]

      This is used to define the comparison function used when comparing texels in the depth texture to the value in the depth buffer. - Default is *null* which means comparison is disabled.

      + Default is `null` which means comparison is disabled.

      See the [page:Textures texture constants] page for details of other functions.

      diff --git a/docs/api/en/textures/FramebufferTexture.html b/docs/api/en/textures/FramebufferTexture.html index 3ab666b592a997..7864770d756241 100644 --- a/docs/api/en/textures/FramebufferTexture.html +++ b/docs/api/en/textures/FramebufferTexture.html @@ -33,7 +33,7 @@

      [name]

      renderer.render( scene, camera ); // copy part of the rendered frame into the framebuffer texture -renderer.copyFramebufferToTexture( vector, frameTexture ); +renderer.copyFramebufferToTexture( frameTexture, vector );

      Examples

      diff --git a/docs/api/en/textures/Source.html b/docs/api/en/textures/Source.html index 6f29f5fbc13bfc..d9902091b6a885 100644 --- a/docs/api/en/textures/Source.html +++ b/docs/api/en/textures/Source.html @@ -31,8 +31,13 @@

      [property:Boolean isSource]

      [property:Boolean needsUpdate]

      - Set this to `true` to trigger a data upload to the GPU next time the - source is used. + When the property is set to `true`, the engine allocates the memory for the texture (if necessary) and triggers the actual texture upload to the GPU next time the source is used. +

      + +

      [property:Boolean dataReady]

      +

      + This property is only relevant when [page:.needUpdate] is set to `true` and provides more control on how texture data should be processed. + When `dataReady` is set to `false`, the engine performs the memory allocation (if necessary) but does not transfer the data into the GPU memory. Default is `true`.

      [property:String uuid]

      diff --git a/docs/api/en/textures/Texture.html b/docs/api/en/textures/Texture.html index 748c7bb8d26303..db7e0d180f3539 100644 --- a/docs/api/en/textures/Texture.html +++ b/docs/api/en/textures/Texture.html @@ -133,7 +133,7 @@

      [property:number minFilter]

      [property:number anisotropy]

      The number of samples taken along the axis through the pixel that has the - highest density of texels. By default, this value is 1. A higher value + highest density of texels. By default, this value is `1`. A higher value gives a less blurry result than a basic mipmap, at the cost of more texture samples being used. Use [page:WebGLRenderer.capabilities renderer.capabilities.getMaxAnisotropy]() to find the maximum valid anisotropy value for the GPU; this value is usually a power of 2. @@ -281,6 +281,7 @@

      [property:Object userData]

      An object that can be used to store custom data about the texture. It should not hold references to functions as these will not be cloned. + Default is an empty object `{}`.

      [property:Source source]

      @@ -308,10 +309,7 @@

      [method:undefined updateMatrix]()

      [method:Texture clone]()

      Make copy of the texture. Note this is not a "deep copy", the image is - shared. Besides, cloning a texture does not automatically mark it for a - texture upload. You have to set [page:Texture.needsUpdate .needsUpdate] to - true as soon as its image property (the data source) is fully loaded or - ready. + shared. Cloning the texture automatically marks it for texture upload.

      [method:Object toJSON]( [param:Object meta] )

      diff --git a/docs/api/en/textures/VideoFrameTexture.html b/docs/api/en/textures/VideoFrameTexture.html new file mode 100644 index 00000000000000..c97a34c45da5a9 --- /dev/null +++ b/docs/api/en/textures/VideoFrameTexture.html @@ -0,0 +1,101 @@ + + + + + + + + + + [page:VideoTexture] → + +

      [name]

      + +

      + This class can be used as an alternative way to define video data. Instead of using + an instance of `HTMLVideoElement` like with `VideoTexture`, [name] expects each frame is + defined manually via [page:.setFrame setFrame](). A typical use case for this module is when + video frames are decoded with the WebCodecs API. +

      + +

      Code Example

      + + + const texture = new THREE.VideoFrameTexture(); + texture.setFrame( frame ); + + +

      Examples

      + +

      + [example:webgpu_video_frame video / frame] +

      + +

      Constructor

      +

      + [name]( [param:Constant mapping], [param:Constant wrapS], + [param:Constant wrapT], [param:Constant magFilter], [param:Constant minFilter], + [param:Constant format], [param:Constant type], [param:Number anisotropy] ) +

      +

      + [page:Constant mapping] -- How the image is applied to the object. An + object type of [page:Textures THREE.UVMapping]. + See [page:Textures mapping constants] for other choices.
      + + [page:Constant wrapS] -- The default is [page:Textures THREE.ClampToEdgeWrapping]. + See [page:Textures wrap mode constants] for + other choices.
      + + [page:Constant wrapT] -- The default is [page:Textures THREE.ClampToEdgeWrapping]. + See [page:Textures wrap mode constants] for + other choices.
      + + [page:Constant magFilter] -- How the texture is sampled when a texel + covers more than one pixel. The default is [page:Textures THREE.LinearFilter]. + See [page:Textures magnification filter constants] + for other choices.
      + + [page:Constant minFilter] -- How the texture is sampled when a texel + covers less than one pixel. The default is [page:Textures THREE.LinearFilter]. + See [page:Textures minification filter constants] for + other choices.
      + + [page:Constant format] -- The default is [page:Textures THREE.RGBAFormat]. + See [page:Textures format constants] for other choices.
      + + [page:Constant type] -- Default is [page:Textures THREE.UnsignedByteType]. + See [page:Textures type constants] for other choices.
      + + [page:Number anisotropy] -- The number of samples taken along the axis + through the pixel that has the highest density of texels. By default, this + value is `1`. A higher value gives a less blurry result than a basic mipmap, + at the cost of more texture samples being used. Use + [page:WebGLrenderer.getMaxAnisotropy renderer.getMaxAnisotropy]() to find + the maximum valid anisotropy value for the GPU; this value is usually a + power of 2.

      +

      + +

      Properties

      + +

      See the base [page:VideoTexture VideoTexture] class for common properties.

      + +

      [property:Boolean isVideoFrameTexture]

      +

      Read-only flag to check if a given object is of type [name].

      + +

      Methods

      + +

      See the base [page:VideoTexture VideoTexture] class for common methods.

      + +

      [method:undefined setFrame]( [param:VideoFrame frame] )

      +

      + Sets the current frame of the video. This will automatically update the texture + so the data can be used for rendering. +

      + +

      Source

      + +

      + [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

      + + diff --git a/docs/api/en/textures/VideoTexture.html b/docs/api/en/textures/VideoTexture.html index 2518e894da1a6b..8655646c6cf53b 100644 --- a/docs/api/en/textures/VideoTexture.html +++ b/docs/api/en/textures/VideoTexture.html @@ -75,7 +75,7 @@

      [page:Number anisotropy] -- The number of samples taken along the axis through the pixel that has the highest density of texels. By default, this - value is 1. A higher value gives a less blurry result than a basic mipmap, + value is `1`. A higher value gives a less blurry result than a basic mipmap, at the cost of more texture samples being used. Use [page:WebGLrenderer.getMaxAnisotropy renderer.getMaxAnisotropy]() to find the maximum valid anisotropy value for the GPU; this value is usually a diff --git a/docs/api/fr/animation/AnimationAction.html b/docs/api/fr/animation/AnimationAction.html index 83b50a1966ac0e..79248075205b2f 100644 --- a/docs/api/fr/animation/AnimationAction.html +++ b/docs/api/fr/animation/AnimationAction.html @@ -13,7 +13,7 @@

      [name]

      Les AnimationActions programment la performance des animations qui sont stockées dans [page:AnimationClip AnimationClips].

      - Note: La plupart des méthodes d'AnimationAction peuvent être chainées.

      + Note: La plupart des méthodes d'AnimationAction peuvent être chaînées.

      Pour avoir un aperçu des différents éléments du système d'animation de three.js, consultez l'article "Système d'Animation" dans la section "Étapes Suivantes" du manuel. @@ -29,7 +29,7 @@

      [name]( [param:AnimationMixer mixer], [param:AnimationClip clip], [param:Obj [page:AnimationClip clip] - l`AnimationClip` qui contient les données d'animation pour cette action.
      [page:Object3D localRoot] - l'objet racine sur lequel est appliqué l'action.

      - Note: Au lieu d'appeler ce constructeur directement, vous devriez instantier un AnimationAction avec + Note: Au lieu d'appeler ce constructeur directement, vous devriez instancier un AnimationAction avec [page:AnimationMixer.clipAction] étant donné que cette méthode applique une mise en cache pour obtenir de meilleures performances.

      @@ -42,7 +42,7 @@

      [property:Boolean clampWhenFinished]

      Si `clampWhenFinished` est mis à true l'animation sera automatiquement [page:.paused mise en pause] à son dernier frame.

      - Si `clampWhenFinished` est mis à false, [page:.enabled enabled] sera automatiquement reglé sur + Si `clampWhenFinished` est mis à false, [page:.enabled enabled] sera automatiquement réglé sur false quand la dernière boucle de l'action sera terminée, afin que cette action n'ai pas plus d'impact.

      @@ -68,14 +68,14 @@

      [property:Boolean enabled]

      [property:Number loop]

      - Le mode répeter (peut-être changé avec [page:.setLoop setLoop]). Sa valeur par défaut est + Le mode répéter (peut-être changé avec [page:.setLoop setLoop]). Sa valeur par défaut est [page:Animation THREE.LoopRepeat] (avec un nombre infini de répétitions [page:.repetitions repetitions])

      Doit être une de ces constantes:

      [page:Animation THREE.LoopOnce] - joue le clip une fois,
      - [page:Animation THREE.LoopRepeat] - joue le clip le nombre choisi de `répetitions`, + [page:Animation THREE.LoopRepeat] - joue le clip le nombre choisi de `répétitions`, en sautant à chaque fois de la fin du clip à son début,
      - [page:Animation THREE.LoopPingPong] - joue le clip le nombre choisi de `répetitions`, + [page:Animation THREE.LoopPingPong] - joue le clip le nombre choisi de `répétitions`, alternant entre lecture du début vers la fin et lecture de la fin vers le début.

      @@ -223,13 +223,13 @@

      [method:Boolean isRunning]()

      ([page:.startAt startAt]).

      Note: le fait que `isRunning` soit à true n'indique pas nécessairement que l'action est visible. - C'est seulement le cas si le [page:.weight weight] est reglé sur une valeur non-nulle. + C'est seulement le cas si le [page:.weight weight] est réglé sur une valeur non-nulle.

      [method:Boolean isScheduled]()

      Renvoie true, si cette action est activée dans le mixer.

      - Note: Cela n'indique pas nécessairement que l'action est en cours d'éxécution (voir les conditions additionnelles + Note: Cela n'indique pas nécessairement que l'action est en cours d'execution (voir les conditions additionnelles pour [page:.isRunning isRunning]).

      @@ -240,7 +240,7 @@

      [method:this play]()

      Note: Activer cette action ne signifie pas nécessairement que l'animation démarrera directement: Si l'action s'était déjà terminée avant (en atteignant la fin de sa dernière boucle), ou si une durée pour un départ différé a été renseignée (via [page:.startAt startAt]), un [page:.reset reset] doit être - éxécuté avant. Quelques autres paramètres ([page:.paused paused]=true, [page:.enabled enabled]=false, + exécuté avant. Quelques autres paramètres ([page:.paused paused]=true, [page:.enabled enabled]=false, [page:.weight weight]=0, [page:.timeScale timeScale]=0) peuvent empêcher cette animation d'être jouée, également.

      @@ -254,7 +254,7 @@

      [method:this reset]()

      de boucles interne et les départs différés programmés.

      Note: .`reset` est toujours appelé par [page:.stop stop], mais .`reset` n'appelle pas .`stop` de lui-même. - Cela signifie que: Si vous voulez à la fois, réinitialiser et stopper, n'appellez pas .`reset`; mais .`stop`. + Cela signifie que: Si vous voulez à la fois, réinitialiser et stopper, n’appelez pas .`reset`; mais .`stop`.

      [method:this setDuration]( [param:Number durationInSeconds] )

      @@ -267,7 +267,7 @@

      [method:this setEffectiveTimeScale]( [param:Number timeScale] )

      Renseigne le [page:.timeScale timeScale] et stoppe tous les warpings programmés. Cette méthode peut être chaînée.

      - Si [page:.paused paused] est à false, l'échelle temporelle effective (propriété interne) sera également + Si [page:.paused paused] est à false, l'échelle temporelle effective (propriété interne) sera également mise à cette valeur; par ailleurs l'échelle temporelle effective (affectant directement l'animation à cet instant) sera mis à `0`.

      @@ -332,7 +332,7 @@

      [method:this syncWith]( [param:AnimationAction otherAction] )

      La synchronisation est faite en dupliquant les valeurs du [page:.time time] et du [page:.timeScale timeScale] de l'autre action (stoppant tous les warpings programmés).

      - Note: Les changements futurs du `time` et du `timeScale` de l'autre action ne seront pas détéctés. + Note: Les changements futurs du `time` et du `timeScale` de l'autre action ne seront pas détectés.

      [method:this warp]( [param:Number startTimeScale], [param:Number endTimeScale], [param:Number durationInSeconds] )

      diff --git a/docs/api/fr/animation/AnimationClip.html b/docs/api/fr/animation/AnimationClip.html index 5adf329c74dad2..90d00c270a21f9 100644 --- a/docs/api/fr/animation/AnimationClip.html +++ b/docs/api/fr/animation/AnimationClip.html @@ -27,7 +27,7 @@

      [name]( [param:String name], [param:Number duration], [param:Array tracks] ) la durée sera calculée depuis le tableau `tracks` passé en paramètres.
      [page:Array tracks] - un tableau de [page:KeyframeTrack KeyframeTracks].

      - Note: Au lieu d'instantier un AnimationClip directement avec le constructeur, vous pouvez utiliser une de + Note: Au lieu d'instancier un AnimationClip directement avec le constructeur, vous pouvez utiliser une de ses méthodes statiques pour créer des AnimationClips: depuis un JSON ([page:.parse parse]), depuis une séquence de morph target ([page:.CreateFromMorphTargetSequence CreateFromMorphTargetSequence], [page:.CreateClipsFromMorphTargetSequences CreateClipsFromMorphTargetSequences]) ou depuis @@ -84,7 +84,7 @@

      [method:this resetDuration]()

      [method:Object toJSON]()

      - Renvoie un objet JSON représentant le clip d'animation serialisé. + Renvoie un objet JSON représentant le clip d'animation sérialisé.

      [method:this trim]()

      diff --git a/docs/api/fr/animation/AnimationMixer.html b/docs/api/fr/animation/AnimationMixer.html index badb2513e7b9a5..bc20323c6995e8 100644 --- a/docs/api/fr/animation/AnimationMixer.html +++ b/docs/api/fr/animation/AnimationMixer.html @@ -74,7 +74,7 @@

      [method:Object3D getRoot]()

      [method:this stopAllAction]()

      - Désactive toutes les actions précedemment programmées pour ce mixer. + Désactive toutes les actions précédemment programmées pour ce mixer.

      [method:this update]([param:Number deltaTimeInSeconds])

      diff --git a/docs/api/fr/animation/AnimationObjectGroup.html b/docs/api/fr/animation/AnimationObjectGroup.html index dcbe0f7c569324..4c8fa5ff2ed4bd 100644 --- a/docs/api/fr/animation/AnimationObjectGroup.html +++ b/docs/api/fr/animation/AnimationObjectGroup.html @@ -30,7 +30,7 @@

      Limitations

      Les propriétés animées doivent être compatibles avec tous les objets du groupe.

      - Une propriété peut être controlée soit directement, soit à travers un groupe cible, mais pas les deux. + Une propriété peut être contrôlée soit directement, soit à travers un groupe cible, mais pas les deux.

      diff --git a/docs/api/fr/animation/AnimationUtils.html b/docs/api/fr/animation/AnimationUtils.html index 562e08b04fa4b4..108174f6e5b1da 100644 --- a/docs/api/fr/animation/AnimationUtils.html +++ b/docs/api/fr/animation/AnimationUtils.html @@ -17,11 +17,6 @@

      [name]

      Méthodes

      -

      [method:Array arraySlice]( array, from, to )

      -

      - Cette méthode est la même que Array.prototype.slice, mais fonctionne également sur les tableaux typés. -

      -

      [method:Array convertArray]( array, type, forceClone )

      Convertis un tableau en un type spécifique. @@ -49,7 +44,7 @@

      [method:AnimationClip makeClipAdditive]( [param:AnimationClip targetClip], [

      [method:Array sortedArray]( values, stride, order )

      - Trie le tableau précedemment renvoyé par [page:AnimationUtils.getKeyframeOrder getKeyframeOrder]. + Trie le tableau précédemment renvoyé par [page:AnimationUtils.getKeyframeOrder getKeyframeOrder].

      [method:AnimationClip subclip]( [param:AnimationClip clip], [param:String name], [param:Number startFrame], [param:Number endFrame], [param:Number fps] )

      diff --git a/docs/api/fr/animation/KeyframeTrack.html b/docs/api/fr/animation/KeyframeTrack.html index 6226ef14192d6d..36d612459bb973 100644 --- a/docs/api/fr/animation/KeyframeTrack.html +++ b/docs/api/fr/animation/KeyframeTrack.html @@ -29,7 +29,7 @@

      [name]

      - A la place, il y a toujours deux tableaux dans un `KeyframeTrack`: le tableau [page:.times times] + A la place, il y a toujours deux tableaux dans un `KeyframeTrack`: le tableau [page:.times times] stocke les durées pour chaque keyframes de ce track dans un ordre séquentiel, et le tableau [page:.values values] array contient les valeurs modifiées correspondantes de la propriété animée.

      @@ -96,7 +96,7 @@

      [property:String name]

      - Le nom peut spécifier le noeud en utilisant son nom ou son uuid (bien qu'il doive être dans le + Le nom peut spécifier le noeud en utilisant son nom ou son uuid (bien qu'il doive être dans le sous-arbre du noeud du graphe de scène passé dans le mixer). Ou, si le nom du track commence par un point, le track s'applique au noeud racine qui a été passé en paramètre du mixer.

      @@ -119,7 +119,7 @@

      [property:String name]

      Note: Le nom de track ne doit pas nécessairement être unique. Plusieurs tracks peuvent gérer la même - propriété. Le résultat doit être basé sur un mélange pondéré entre les mutiples tracks selon + propriété. Le résultat doit être basé sur un mélange pondéré entre les multiples tracks selon le poids de leurs actions respectives.

      @@ -205,7 +205,7 @@

      [method:CubicInterpolant InterpolantFactoryMethodSmooth]( [link:https://deve

      [method:this optimize]()

      - Retire les clés séquentielles équivalentse, qui sont communes dans les séquences de morph target. + Retire les clés séquentielles équivalentes, qui sont communes dans les séquences de morph target.

      [method:this scale]()

      @@ -240,8 +240,8 @@

      [method:Boolean validate]()

      - Cette méthode envoie des erreurs à la console, si un track est vide, si la [page:.valueSize value taille] n'est pas valide, si un élémént - dans le tableau [page:.times times] ou [page:.values values] n'est pas un nombre valide ou si les éléménts du tableau `times` sont désorganisés. + Cette méthode envoie des erreurs à la console, si un track est vide, si la [page:.valueSize value taille] n'est pas valide, si un élément + dans le tableau [page:.times times] ou [page:.values values] n'est pas un nombre valide ou si les éléments du tableau `times` sont désorganisés.

      Méthodes Statiques

      diff --git a/docs/api/fr/animation/tracks/BooleanKeyframeTrack.html b/docs/api/fr/animation/tracks/BooleanKeyframeTrack.html index cd6d6c8ff10589..346a67c0b4e65b 100644 --- a/docs/api/fr/animation/tracks/BooleanKeyframeTrack.html +++ b/docs/api/fr/animation/tracks/BooleanKeyframeTrack.html @@ -22,7 +22,7 @@

      Constructeur

      [name]( [param:String name], [param:Array times], [param:Array values] )

      - [page:String name] - (requis) indentifiant du KeyframeTrack.
      + [page:String name] - (requis) identifiant du KeyframeTrack.
      [page:Array times] - (requis) tableau de durée des keyframes.
      [page:Array values] - valeurs des keyframes aux temps spécifiés.

      diff --git a/docs/api/fr/animation/tracks/ColorKeyframeTrack.html b/docs/api/fr/animation/tracks/ColorKeyframeTrack.html index 7e4312100f9b44..1dd5ed94400f5f 100644 --- a/docs/api/fr/animation/tracks/ColorKeyframeTrack.html +++ b/docs/api/fr/animation/tracks/ColorKeyframeTrack.html @@ -24,7 +24,7 @@

      Constructeur

      [name]( [param:String name], [param:Array times], [param:Array values] )

      - [page:String name] - (requis) indentifiant du KeyframeTrack.
      + [page:String name] - (requis) identifiant du KeyframeTrack.
      [page:Array times] - (requis) tableau de durée des keyframes.
      [page:Array values] - valeurs des keyframes aux temps spécifiés, un tableau à une dimension de composants de couleur entre 0 et 1.
      [page:Constant interpolation] - le type d'interpolation à utiliser. Voir diff --git a/docs/api/fr/animation/tracks/NumberKeyframeTrack.html b/docs/api/fr/animation/tracks/NumberKeyframeTrack.html index 6f34e54f3319dd..719030b747bfc0 100644 --- a/docs/api/fr/animation/tracks/NumberKeyframeTrack.html +++ b/docs/api/fr/animation/tracks/NumberKeyframeTrack.html @@ -22,7 +22,7 @@

      Constructeur

      [name]( [param:String name], [param:Array times], [param:Array values] )

      - [page:String name] - (requis) indentifiant du KeyframeTrack.
      + [page:String name] - (requis) identifiant du KeyframeTrack.
      [page:Array times] - (requis) tableau de durée des keyframes.
      [page:Array values] - valeurs des keyframes aux temps spécifiés.
      [page:Constant interpolation] - le type d'interpolation à utiliser. Voir diff --git a/docs/api/fr/animation/tracks/QuaternionKeyframeTrack.html b/docs/api/fr/animation/tracks/QuaternionKeyframeTrack.html index 4af13884b08de5..ab166cbbd7fd50 100644 --- a/docs/api/fr/animation/tracks/QuaternionKeyframeTrack.html +++ b/docs/api/fr/animation/tracks/QuaternionKeyframeTrack.html @@ -22,7 +22,7 @@

      Constructeur

      [name]( [param:String name], [param:Array times], [param:Array values] )

      - [page:String name] - (requis) indentifiant du KeyframeTrack.
      + [page:String name] - (requis) identifiant du KeyframeTrack.
      [page:Array times] - (requis) tableau de durée des keyframes.
      [page:Array values] - valeurs des keyframes aux temps spécifiés, un tableau à une dimension de composants quaternaires.
      [page:Constant interpolation] - le type d'interpolation à utiliser. Voir diff --git a/docs/api/fr/animation/tracks/StringKeyframeTrack.html b/docs/api/fr/animation/tracks/StringKeyframeTrack.html index 210f28dd4183f3..55d92405d8b177 100644 --- a/docs/api/fr/animation/tracks/StringKeyframeTrack.html +++ b/docs/api/fr/animation/tracks/StringKeyframeTrack.html @@ -22,7 +22,7 @@

      Constructeur

      [name]( [param:String name], [param:Array times], [param:Array values] )

      - [page:String name] - (requis) indentifiant du KeyframeTrack.
      + [page:String name] - (requis) identifiant du KeyframeTrack.
      [page:Array times] - (requis) tableau de durée de keyframes.
      [page:Array values] - valeurs des keyframes aux temps spécifiés.
      [page:Constant interpolation] - le type d'interpolation à utiliser. Voir diff --git a/docs/api/fr/animation/tracks/VectorKeyframeTrack.html b/docs/api/fr/animation/tracks/VectorKeyframeTrack.html index 64dd6ac16ec11b..933b56b9f4c055 100644 --- a/docs/api/fr/animation/tracks/VectorKeyframeTrack.html +++ b/docs/api/fr/animation/tracks/VectorKeyframeTrack.html @@ -22,7 +22,7 @@

      Constructeur

      [name]( [param:String name], [param:Array times], [param:Array values] )

      - [page:String name] - (requis) indentifiant du KeyframeTrack.
      + [page:String name] - (requis) identifiant du KeyframeTrack.
      [page:Array times] - (requis) tableau de durée des keyframes.
      [page:Array values] - valeurs des keyframes aux temps spécifiés, un tableau à une dimension de composants de vecteurs.
      [page:Constant interpolation] - le type d'interpolation à utiliser. Voir diff --git a/docs/api/fr/audio/Audio.html b/docs/api/fr/audio/Audio.html index 46121542b9034b..b87f1d42a732dc 100644 --- a/docs/api/fr/audio/Audio.html +++ b/docs/api/fr/audio/Audio.html @@ -147,7 +147,7 @@

      [method:Float getPlaybackRate]()

      Renvoie la valeur de [page:Audio.playbackRate playbackRate].

      -

      [method:Float getVolume]( value )

      +

      [method:Float getVolume]()

      Renvoie le volume actuel.

      diff --git a/docs/api/fr/audio/AudioAnalyser.html b/docs/api/fr/audio/AudioAnalyser.html index 1ff06e15e62238..f5407e982a11b8 100644 --- a/docs/api/fr/audio/AudioAnalyser.html +++ b/docs/api/fr/audio/AudioAnalyser.html @@ -72,7 +72,7 @@

      [property:Integer fftSize]

      [property:Uint8Array data]

      - Un Uint8Array avec une taille determinée par [link:https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode/frequencyBinCount analyser.frequencyBinCount] + Un Uint8Array avec une taille déterminée par [link:https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode/frequencyBinCount analyser.frequencyBinCount] utilisé pour stocker les données d'analyse.

      @@ -88,7 +88,7 @@

      [method:Uint8Array getFrequencyData]()

      [method:Number getAverageFrequency]()

      - Recupère la moyenne des fréquences retournées par la méthode [page:AudioAnalyser.getFrequencyData getFrequencyData]. + Récupère la moyenne des fréquences retournées par la méthode [page:AudioAnalyser.getFrequencyData getFrequencyData].

      Source

      diff --git a/docs/api/fr/cameras/ArrayCamera.html b/docs/api/fr/cameras/ArrayCamera.html index d4076b0af243fe..a51e0ac32d4726 100644 --- a/docs/api/fr/cameras/ArrayCamera.html +++ b/docs/api/fr/cameras/ArrayCamera.html @@ -38,7 +38,7 @@

      [property:Array cameras]

      [property:Boolean isArrayCamera]

      - Flag en lecture seul qui pemet de vérifier si un objet donné est de type [name]. + Flag en lecture seul qui permet de vérifier si un objet donné est de type [name].

      Méthodes

      diff --git a/docs/api/fr/cameras/Camera.html b/docs/api/fr/cameras/Camera.html index ccec2a0c45184a..a83965648c0202 100644 --- a/docs/api/fr/cameras/Camera.html +++ b/docs/api/fr/cameras/Camera.html @@ -21,7 +21,7 @@

      Constructeur

      [name]()

      - Crée une nouvelle [name]. Notez que cette classe n'est pas censée être appellée directement; + Crée une nouvelle [name]. Notez que cette classe n'est pas censée être appelée directement; vous aurez sans doute besoin d'une [page:PerspectiveCamera] ou d'une [page:OrthographicCamera] à la place.

      @@ -31,7 +31,7 @@

      Propriétés

      [property:Boolean isCamera]

      - Flag en lecture seul qui pemet de vérifier si un objet donné est de type [name]. + Flag en lecture seul qui permet de vérifier si un objet donné est de type [name].

      [property:Layers layers]

      diff --git a/docs/api/fr/cameras/CubeCamera.html b/docs/api/fr/cameras/CubeCamera.html index 08df9e3f049b86..c1409810b88008 100644 --- a/docs/api/fr/cameras/CubeCamera.html +++ b/docs/api/fr/cameras/CubeCamera.html @@ -76,7 +76,7 @@

      [method:undefined update]( [param:WebGLRenderer renderer], [param:Scene scen scene -- La scène actuelle

      - Appellez cette méthode pour mettre à jour le [page:CubeCamera.renderTarget renderTarget]. + Appelez cette méthode pour mettre à jour le [page:CubeCamera.renderTarget renderTarget].

      Source

      diff --git a/docs/api/fr/cameras/OrthographicCamera.html b/docs/api/fr/cameras/OrthographicCamera.html index 70391321f2ce55..851f4d5826f427 100644 --- a/docs/api/fr/cameras/OrthographicCamera.html +++ b/docs/api/fr/cameras/OrthographicCamera.html @@ -37,7 +37,6 @@

      Exemples

      [example:webgl_postprocessing_dof2 postprocessing / dof2 ]
      [example:webgl_postprocessing_godrays postprocessing / godrays ]
      [example:webgl_rtt rtt ]
      - [example:webgl_shaders_tonemapping shaders / tonemapping ]
      [example:webgl_shadowmap shadowmap ]

      @@ -76,7 +75,7 @@

      [property:Float far]

      [property:Boolean isOrthographicCamera]

      - Flag en lecture seul qui pemet de vérifier si un objet donné est de type [name]. + Flag en lecture seul qui permet de vérifier si un objet donné est de type [name].

      [property:Float left]

      @@ -132,7 +131,7 @@

      [method:undefined updateProjectionMatrix]()

      [method:Object toJSON]([param:Object meta])

      - meta -- objet contenant des metadatas comme des objets ou des textures dans des descendants des objets.
      + meta -- objet contenant des métadonnées comme des objets ou des textures dans des descendants des objets.
      Convertis la caméra en [link:https://github.com/mrdoob/three.js/wiki/JSON-Object-Scene-format-4 JSON Object/Scene format] three.js.

      diff --git a/docs/api/fr/cameras/PerspectiveCamera.html b/docs/api/fr/cameras/PerspectiveCamera.html index 2d4d749a2f85be..2c08531580c108 100644 --- a/docs/api/fr/cameras/PerspectiveCamera.html +++ b/docs/api/fr/cameras/PerspectiveCamera.html @@ -51,7 +51,7 @@

      [name]( [param:Number fov], [param:Number aspect], [param:Number near], [par

      Propriétés

      Voir la classe [page:Camera] pour connaître les propriétés communes.
      - Notez qu'après avoir changé la grande majorité de ces propriétés vous devrez appeller + Notez qu'après avoir changé la grande majorité de ces propriétés vous devrez appeler [page:PerspectiveCamera.updateProjectionMatrix .updateProjectionMatrix] afin que les changements prennent effet.

      @@ -67,7 +67,7 @@

      [property:Float far]

      [property:Float filmGauge]

      Taille de la pellicule utilisée pour l'axe le plus grand. La valeur par défaut est 35 (millimètres). Ce paramètre n'influe pas sur la matrice de projection sauf si .filmOffset est à une valeur non-nulle

      - +

      [property:Float filmOffset]

      Décalage horizontal dans la même unité que `.filmGauge`. La valeur par défaut est `0`.

      @@ -82,7 +82,7 @@

      [property:Float fov]

      [property:Boolean isPerspectiveCamera]

      - Flag en lecture seul qui pemet de vérifier si un objet donné est de type [name]. + Flag en lecture seul qui permet de vérifier si un objet donné est de type [name].

      @@ -90,7 +90,7 @@

      [property:Float near]

      Plan near du frustum de la caméra. La valeur par défaut est `0.1`.

      - L'intervalle valide est supérieure à 0 et inférieure à la valeur actuelle du plan [page:.far far]. + L'intervalle valide est supérieure à `0` et inférieure à la valeur actuelle du plan [page:.far far]. Notez que, à l'inverse de l'[page:OrthographicCamera], `0` n'est pas une valeur valide pour le plan near d'une PerspectiveCamera.

      @@ -98,7 +98,7 @@

      [property:Float near]

      [property:Object view]

      Spécification du frustum ou nul. - La valeur est fixée par la méthode [page:PerspectiveCamera.setViewOffset .setViewOffset] + La valeur est fixée par la méthode [page:PerspectiveCamera.setViewOffset .setViewOffset] et supprimée par la méthode [page:PerspectiveCamera.clearViewOffset .clearViewOffset].

      @@ -113,7 +113,7 @@

      [method:undefined clearViewOffset]()

      Retire tout décalage mis en place par la méthode [page:PerspectiveCamera.setViewOffset .setViewOffset].

      [method:Float getEffectiveFOV]()

      -

      Retourne l'angle du champ de vision verticalen degrés en prenant en compte le .zoom.

      +

      Retourne l'angle du champ de vision vertical en degrés en prenant en compte le .zoom.

      [method:Float getFilmHeight]()

      @@ -193,7 +193,7 @@

      [method:undefined updateProjectionMatrix]()

      [method:Object toJSON]([param:Object meta])

      - meta -- objet contenant des metadatas comme des objets ou des textures dans des descendants des objets.
      + meta -- objet contenant des métadonnées comme des objets ou des textures dans des descendants des objets.
      Convertis la caméra en [link:https://github.com/mrdoob/three.js/wiki/JSON-Object-Scene-format-4 JSON Object/Scene format] three.js.

      diff --git a/docs/api/fr/constants/CustomBlendingEquations.html b/docs/api/fr/constants/CustomBlendingEquations.html index d3267a0c24b516..2b5c1ee6be075b 100644 --- a/docs/api/fr/constants/CustomBlendingEquations.html +++ b/docs/api/fr/constants/CustomBlendingEquations.html @@ -50,7 +50,7 @@

      Facteurs sources

      THREE.SrcAlphaSaturateFactor
      -

      Facteur de déstination

      +

      Facteur de destination

      Tous les facteurs source sont valides comme facteurs de destination, à l'exception de THREE.SrcAlphaSaturateFactor

      diff --git a/docs/api/fr/constants/Materials.html b/docs/api/fr/constants/Materials.html index bff834df896bea..6502794bba0c34 100644 --- a/docs/api/fr/constants/Materials.html +++ b/docs/api/fr/constants/Materials.html @@ -10,7 +10,7 @@

      Constantes de matériau

      - Ces constantes définissent des propriétés communes à tous les métériaux, + Ces constantes définissent des propriétés communes à tous les matériaux, à l'exception de Texture Combine Operations qui s'applique uniquement à [page:MeshBasicMaterial.combine MeshBasicMaterial], [page:MeshLambertMaterial.combine MeshLambertMaterial] et [page:MeshPhongMaterial.combine MeshPhongMaterial].

      @@ -132,8 +132,8 @@

      Type de carte normale (normal map)

      THREE.ObjectSpaceNormalMap

      - Ces constantes définissent les types de carte normale. - Pour TangentSpaceNormalMap, l'information est relative à la surface sous-jacente. + Ces constantes définissent les types de carte normale. + Pour TangentSpaceNormalMap, l'information est relative à la surface sous-jacente. For ObjectSpaceNormalMap, l'information est relative à la rotation de l'objet. La valeur par défaut est [page:Constant TangentSpaceNormalMap].

      diff --git a/docs/api/fr/constants/Renderer.html b/docs/api/fr/constants/Renderer.html index 99e06448d9d0d1..809b3fa76d21ee 100644 --- a/docs/api/fr/constants/Renderer.html +++ b/docs/api/fr/constants/Renderer.html @@ -46,6 +46,8 @@

      Cartographie des tons

      THREE.ReinhardToneMapping THREE.CineonToneMapping THREE.ACESFilmicToneMapping + THREE.AgXToneMapping + THREE.NeutralToneMapping THREE.CustomToneMapping

      @@ -54,11 +56,13 @@

      Cartographie des tons

      milieu de plage dynamique faible d'un écran d'ordinateur standard ou d'un écran d'appareil mobile.

      - THREE.LinearToneMapping, THREE.ReinhardToneMapping, THREE.CineonToneMapping et THREE.ACESFilmicToneMapping sont des implémentations intégrées à la cartographie des tons. + THREE.LinearToneMapping, THREE.ReinhardToneMapping, THREE.CineonToneMapping, THREE.ACESFilmicToneMapping, THREE.AgXToneMapping et THREE.NeutralToneMapping sont des implémentations intégrées à la cartographie des tons. THREE.CustomToneMapping attend une implémentation personnalisée en modifiant le code GLSL du fragment shader du matériau. Voir l'exemple [example:webgl_tonemapping WebGL / tonemapping].

      - +

      + THREE.NeutralToneMapping is an implementation based on the Khronos 3D Commerce Group standard tone mapping. +

      Source

      diff --git a/docs/api/fr/constants/Textures.html b/docs/api/fr/constants/Textures.html index f42d8ec9eb119a..f44c13465115f9 100644 --- a/docs/api/fr/constants/Textures.html +++ b/docs/api/fr/constants/Textures.html @@ -48,12 +48,12 @@

      Modes d'emballage

      Ces constantes définissent les propriétés des textures [page:Texture.wrapS wrapS] et [page:Texture.wrapT wrapT], qui définissent l'emballage de texture horizontal et vertical.

      - Avec [page:constant RepeatWrapping] la texure se répetera simplement à l'infini.

      + Avec [page:constant RepeatWrapping] la texture se répétera simplement à l'infini.

      [page:constant ClampToEdgeWrapping] est la valeur par défaut. Le dernier pixel de la texture s'étend jusqu'au bord du maillage.

      - Avec [page:constant MirroredRepeatWrapping] la texure se répetera à l'infini avec un effet mirroir à chaque répétition. + Avec [page:constant MirroredRepeatWrapping] la texture se répétera à l'infini avec un effet miroir à chaque répétition.

      Filtres de grossissement

      @@ -129,6 +129,7 @@

      Types

      THREE.UnsignedShort4444Type THREE.UnsignedShort5551Type THREE.UnsignedInt248Type + THREE.UnsignedInt5999Type

      À utiliser avec la propriété [page:Texture.type type] d'une texture, qui doit correspondre au format correct. Voir ci-dessous pour plus de détails.

      @@ -143,10 +144,9 @@

      Formats

      THREE.RedIntegerFormat THREE.RGFormat THREE.RGIntegerFormat + THREE.RGBFormat THREE.RGBAFormat THREE.RGBAIntegerFormat - THREE.LuminanceFormat - THREE.LuminanceAlphaFormat THREE.DepthFormat THREE.DepthStencilFormat @@ -160,33 +160,21 @@

      Formats

      [page:constant RedIntegerFormat] supprime les composants vert et bleu et lit uniquement le composant rouge. Les texels sont lus comme des entiers au lieu de points flottants. - (ne peut être utilisé qu'avec un contexte de rendu WebGL 2).

      [page:constant RGFormat] supprime les composants alpha et bleu et lit les composants rouge et vert. - (ne peut être utilisé qu'avec un contexte de rendu WebGL 2).

      [page:constant RGIntegerFormat] supprime les composants alpha et bleu et lit les composants rouge et vert. Les texels sont lus comme des entiers au lieu de points flottants. - (ne peut être utilisé qu'avec un contexte de rendu WebGL 2).

      [page:constant RGBAFormat] est la valeur par défaut et lit les composants rouge, vert, bleu et alpha.

      [page:constant RGBAIntegerFormat] est la valeur par défaut et lit les composants rouge, vert, bleu et alpha. Les texels sont lus comme des entiers au lieu de points flottants. - (ne peut être utilisé qu'avec un contexte de rendu WebGL 2).

      - [page:constant LuminanceFormat] lit chaque élément comme une seule composante de luminance. - Celui-ci est ensuite converti en point flottant, fixé dans l'intervalle [0,1], puis assemblé - dans un élément RGBA en plaçant la valeur de luminance dans les canaux rouge, vert et bleu, - et en assignant 1.0 au canal alpha.

      - - [page:constant LuminanceAlphaFormat] lit chaque élément comme une composante de luminance/double alpha. - Il s'agit du même procédé que pour [page:constant LuminanceFormat], sauf que le canal alpha peut avoir une autre valeur que `1.0`.

      - [page:constant DepthFormat] lit chaque élément comme une seule valeur de profondeur, les converti en point flottant, fixé dans l'intervalle [0,1]. C'est la valeur par défaut pour [page:DepthTexture DepthTexture].

      @@ -208,7 +196,7 @@

      Formats de Textures Compressées DDS et ST3C

      À utiliser avec la propriété [page:Texture.format format] d'une [page:CompressedTexture CompressedTexture], - ceux-ci doivent supporter l'extension + ceux-ci doivent supporter l'extension [link:https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/ WEBGL_compressed_texture_s3tc]

      Il existe 4 formats [link:https://en.wikipedia.org/wiki/S3_Texture_Compression S3TC] disponibles avec cette extension. Ce sont :
      @@ -346,33 +334,29 @@

      Formats Internes

      - - Attention : changer le format interne d'une texture n'affectera que le - texture lors de l'utilisation d'un contexte de rendu WebGL 2.

      - À utiliser avec la propriété de texture [page:Texture.internalFormat internalFormat], ceux-ci définissent comment les éléments d'une texture, ou "texels", sont stockés sur le GPU.

      [page:constante R8] stocke la composante rouge sur 8 bits.

      [page:constant R8_SNORM] stocke la composante rouge sur 8 bits. Le composant est stocké comme normalisé.

      - + [page:constant R8I] stocke la composante rouge sur 8 bits. Le composant est stocké sous la forme d'un entier.

      - + [page:constant R8UI] stocke la composante rouge sur 8 bits. Le composant est stocké sous la forme d'un entier non signé.

      - + [page:constant R16I] stocke la composante rouge sur 16 bits. Le composant est stocké sous la forme d'un entier.

      - + [page:constant R16UI] stocke la composante rouge sur 16 bits. Le composant est stocké sous la forme d'un entier non signé.

      - + [page:constant R16F] stocke la composante rouge sur 16 bits. Le composant est stocké en point flottant.

      - + [page:constant R32I] stocke la composante rouge sur 32 bits. Le composant est stocké sous la forme d'un entier.

      - + [page:constant R32UI] stocke la composante rouge sur 32 bits. Le composant est stocké sous la forme d'un entier non signé.

      - + [page:constant R32F] stocke la composante rouge sur 32 bits. Le composant est stocké en point flottant.

      - + [page:constante RG8] stocke les composantes rouge et verte sur 8 bits chacune.

      [page:constant RG8_SNORM] stocke les composantes rouge et verte sur 8 bits chacune. diff --git a/docs/api/fr/core/BufferAttribute.html b/docs/api/fr/core/BufferAttribute.html index c2edb4ea915bb3..3a4fefc3964363 100644 --- a/docs/api/fr/core/BufferAttribute.html +++ b/docs/api/fr/core/BufferAttribute.html @@ -49,12 +49,19 @@

      [property:TypedArray array]

      [property:Integer count]

      - Stocke la longueur du tableau [page:BufferAttribute.array array] divisée par [page:BufferAttribute.itemSize itemSize].

      + Stocke la longueur du tableau [page:BufferAttribute.array array] divisée par [page:BufferAttribute.itemSize itemSize]. Read-only property.

      Si le tampon stocke un vecteur à 3 composants (tel qu'une position, une normale ou une couleur), cela comptera alors le nombre de ces vecteurs stockés.

      +

      [property:Number gpuType]

      +

      + Configures the bound GPU type for use in shaders. Either [page:BufferAttribute THREE.FloatType] or [page:BufferAttribute THREE.IntType], default is [page:BufferAttribute THREE.FloatType]. + + Note: this only has an effect for integer arrays and is not configurable for float arrays. For lower precision float types, see [page:BufferAttributeTypes THREE.Float16BufferAttribute]. +

      +

      [property:Boolean isBufferAttribute]

      Booléen en lecture seule pour vérifier si un objet donné est de type[name]. @@ -139,6 +146,9 @@

      [method:this copyArray]( array )

      [method:this copyAt] ( [param:Integer index1], [param:BufferAttribute bufferAttribute], [param:Integer index2] )

      Copie un vecteur de bufferAttribute[index2] à [page:BufferAttribute.array array][index1].

      +

      [method:Number getComponent]( [param:Integer index], [param:Integer component] )

      +

      Returns the given component of the vector at the given index.

      +

      [method:Number getX]( [param:Integer index] )

      Renvoie le composant x d'un vecteur à l'index donné.

      @@ -178,6 +188,9 @@

      [method:this setUsage] ( [param:Usage value] )

      Remarque : Après la première utilisation d'un tampon, son utilisation ne peut pas être modifiée. Au lieu de cela, instanciez-en un nouveau et définissez l'utilisation souhaitée avant le prochain rendu.

      +

      [method:Number setComponent]( [param:Integer index], [param:Integer component], [param:Float value] )

      +

      Sets the given component of the vector at the given index.

      +

      [method:this setX]( [param:Integer index], [param:Float x] )

      Définit la composante x du vecteur à l'indice donné.

      diff --git a/docs/api/fr/core/BufferGeometry.html b/docs/api/fr/core/BufferGeometry.html index a4d014b0ca2f1f..017cb9b8eaead9 100644 --- a/docs/api/fr/core/BufferGeometry.html +++ b/docs/api/fr/core/BufferGeometry.html @@ -33,7 +33,7 @@

      Exemple de code :

      -1.0, -1.0, 1.0 ] ); - // itemSize = 3 parce qu'il y a 3 valeurs (comoosants) par sommet + // itemSize = 3 parce qu'il y a 3 valeurs (composants) par sommet geometry.setAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) ); const material = new THREE.MeshBasicMaterial( { color: 0xff0000 } ); const mesh = new THREE.Mesh( geometry, material ); @@ -267,7 +267,7 @@

      [method:this rotateZ] ( [param:Float radians] )

      [method:this scale] ( [param:Float x], [param:Float y], [param:Float z] )

      - Mettre à l'échelle les données géométriques. Cela se fait généralement en une seule opération et non pendant une boucle. + Mettre à l'échelle les données géométriques. Cela se fait généralement en une seule opération et non pendant une boucle. Utilisez [page:Object3D.scale] pour une utilisation typique du maillage en temps réel.

      diff --git a/docs/api/fr/geometries/BoxGeometry.html b/docs/api/fr/geometries/BoxGeometry.html index 27719eff69964a..81ac33da7dd1b8 100644 --- a/docs/api/fr/geometries/BoxGeometry.html +++ b/docs/api/fr/geometries/BoxGeometry.html @@ -13,7 +13,7 @@

      [name]

      [name] est une classe de géométrie pour un cuboïde rectangulaire avec une largeur 'width', une hauteur 'height', et une profondeur 'depth' données. - À la création, le cuboïde est centré sur l'origine, chaque arrête est parallèle à l'un des axes. + À la création, le cuboïde est centré sur l'origine, chaque arrête est parallèle à l'un des axes.

      @@ -42,7 +42,7 @@

      Exemple de code :

      scene.add( cube ); -

      Contructeur

      +

      Constructeur

      [name]([param:Float width], [param:Float height], [param:Float depth], [param:Integer widthSegments], [param:Integer heightSegments], [param:Integer depthSegments])

      diff --git a/docs/api/fr/geometries/CapsuleGeometry.html b/docs/api/fr/geometries/CapsuleGeometry.html index da9d9c38b37725..4bf3dca14b2604 100644 --- a/docs/api/fr/geometries/CapsuleGeometry.html +++ b/docs/api/fr/geometries/CapsuleGeometry.html @@ -13,7 +13,7 @@

      [name]

      [name] est une classe de géométrie pour une capsule avec des rayons et une hauteur donnés. - Elle est construite à l'aide de demi-shpères. + Elle est construite à l'aide de demi-sphères.

      @@ -44,13 +44,14 @@

      Exemple de code :

      Constructeur

      -

      [name]([param:Float radius], [param:Float length], [param:Integer capSubdivisions], [param:Integer radialSegments])

      +

      [name]([param:Float radius], [param:Float height], [param:Integer capSegments], [param:Integer radialSegments], [param:Integer heightSegments])

      radius — Rayon de la capsule. Optionnel; par défaut à 1.
      - length — Longueur de la section médiane. Optionnel; par défaut à 1.
      + height — Hauteur de la section médiane. Optionnel; par défaut à 1.
      capSegments — Nombre de segments de courbe utilisés pour construire les demi-sphères. Optionnel; par défaut à 4.
      radialSegments — Nombre de faces segmentées autour de la circonférence de la capsule. Optionnel; par défaut à 8.
      + heightSegments — Nombre de faces rectangulaires segmentées sur la hauteur de la capsule. Optionnel; par défaut à 1.

      Propriétés

      diff --git a/docs/api/fr/materials/LineBasicMaterial.html b/docs/api/fr/materials/LineBasicMaterial.html index 2e1048cd1510b4..5cc93269ac7dce 100644 --- a/docs/api/fr/materials/LineBasicMaterial.html +++ b/docs/api/fr/materials/LineBasicMaterial.html @@ -38,8 +38,6 @@

      Exemples

      [example:webgl_interactive_voxelpainter WebGL / interactive / voxelpainter]
      [example:webgl_lines_colors WebGL / lines / colors]
      [example:webgl_lines_dashed WebGL / lines / dashed]
      - [example:webgl_lines_sphere WebGL / lines / sphere]
      - [example:webgl_materials WebGL / materials]
      [example:physics_ammo_rope physics / ammo / rope]

      @@ -49,14 +47,14 @@

      [name]( [param:Object parameters] )

      [page:Object parameters] - (optionnel) un objet avec une ou plusieurs propriétés définissant l'apparence du matériau. - Toute propriété du matériau (y compris toute proprioété héritée de [page:Material]) peut être passée dans l'objet.

      + Toute propriété du matériau (y compris toute propriété héritée de [page:Material]) peut être passée dans l'objet.

      - L'exception est la propriété [page:Hexadecimal color], qui peut être passée comme une chaine de caractères hexadécimale, + L'exception est la propriété [page:Hexadecimal color], qui peut être passée comme une chaîne de caractères hexadécimale, ayant la valeur `0xffffff` (blanc) par défaut. [page:Color.set]( color ) est appelée en interne.

      Propriétés

      -

      Voir la classe [page:Material] pour les prppriétés communes.

      +

      Voir la classe [page:Material] pour les propriétés communes.

      [property:Color color]

      [page:Color], couleur du matériau, par défaut en blanc (0xffffff).

      @@ -69,7 +67,7 @@

      [property:Float linewidth]

      Contrôle l'épaisseur des lignes, la valeur par défaut est `1`.

      A cause des limitations de [link:https://www.khronos.org/registry/OpenGL/specs/gl/glspec46.core.pdf OpenGL Core Profile] - avec le moteur de rendu [page:WebGLRenderer WebGL] sur la plupârt des plateformes, l'épaisseur de ligne (linewidth) sera toujours + avec le moteur de rendu [page:WebGLRenderer WebGL] sur la plupart des plateformes, l'épaisseur de ligne (linewidth) sera toujours à 1, indépendamment de la valeur définie.

      diff --git a/docs/api/fr/materials/LineDashedMaterial.html b/docs/api/fr/materials/LineDashedMaterial.html index 54c52ae9a7508e..82c64b0c652c40 100644 --- a/docs/api/fr/materials/LineDashedMaterial.html +++ b/docs/api/fr/materials/LineDashedMaterial.html @@ -11,7 +11,10 @@

      [name]

      -

      Un matériau pour dessiner des géométries de style filaire, avec des pointillés.

      +

      + Un matériau pour dessiner des géométries de style filaire, avec des pointillés.
      + Note: You must call [page:Line.computeLineDistances]() when using [name]. +

      Exemple de Code

      @@ -37,7 +40,7 @@

      Constructeur

      [name]( [param:Object parameters] )

      [page:Object parameters] - (optionnel) un objet avec une ou plusieurs propriétés définissant l'apparence du matériau. - Toute propriété du matériau (y compris toute proprioété héritée de [page:LineBasicMaterial]) peut être passée dans l'objet. + Toute propriété du matériau (y compris toute propriété héritée de [page:LineBasicMaterial]) peut être passée dans l'objet.

      diff --git a/docs/api/fr/materials/Material.html b/docs/api/fr/materials/Material.html index 19456dba557017..a1ca23496e8de3 100644 --- a/docs/api/fr/materials/Material.html +++ b/docs/api/fr/materials/Material.html @@ -29,6 +29,14 @@

      [name]()

      Propriétés

      +

      [property:Boolean alphaHash]

      +

      + Enables alpha hashed transparency, an alternative to [page:.transparent] or [page:.alphaTest]. + The material will not be rendered if opacity is lower than a random threshold. + Randomization introduces some grain or noise, but approximates alpha blending without + the associated problems of sorting. Using TAARenderPass can reduce the resulting noise. +

      +

      [property:Float alphaTest]

      Définit la valeur alpha à utiliser lors de l'exécution d'un test alpha. @@ -186,11 +194,11 @@

      [property:Integer id]

      Nombre unique pour cette instance de matériau.

      [property:String name]

      -

      Nom optionnel de l'object (n'a pas besoin d'être unique). La valeur par défaut est une chaine de caractères vide.

      +

      Nom optionnel de l'object (n'a pas besoin d'être unique). La valeur par défaut est une chaîne de caractères vide.

      [property:Boolean needsUpdate]

      - Scécifie que le matériau doit être recompilé. + Spécifie que le matériau doit être recompilé.

      [property:Float opacity]

      @@ -342,11 +350,35 @@

      [method:undefined dispose]()

      [method:undefined onBeforeCompile]( [param:Shader shader], [param:WebGLRenderer renderer] )

      Un callback facultatif qui est exécuté immédiatement avant la compilation du programme shader. - Cette fonction est appelée avec le code source du shader comme paramètre. Utile pour la modification des matériaux intégrés. + Cette fonction est appelée avec le code source du shader comme paramètre. Utile pour la modification des matériaux intégrés, + bien que la nouvelle méthode recommandée soit d'utiliser `WebGPURenderer` avec le nouveau système de Node Material et + [link:https://github.com/mrdoob/three.js/wiki/Three.js-Shading-Language TSL].

      Contrairement aux propriétés, le callback n'est pas pris en charge par [page:Material.clone .clone](), [page:Material.copy .copy]() et [page:Material.toJSON .toJSON]().

      +

      + This callback is only supported in `WebGLRenderer` (not `WebGPURenderer`). +

      +

      + [example:webgl_materials_modified WebGL / materials / modified]
      + [example:webgl_shadow_contact WebGL / shadow / contact] +

      + +

      + [method:undefined onBeforeRender]( [param:WebGLRenderer renderer], [param:Scene scene], [param:Camera camera], [param:BufferGeometry geometry], [param:Object3D object], [param:Group group] ) +

      +

      + An optional callback that is executed immediately before the material is used to + render a 3D object. +

      +

      + Unlike properties, the callback is not supported by [page:Material.clone .clone](), + [page:Material.copy .copy]() and [page:Material.toJSON .toJSON](). +

      +

      + This callback is only supported in `WebGLRenderer` (not `WebGPURenderer`). +

      [method:String customProgramCacheKey]()

      diff --git a/docs/api/fr/materials/MeshBasicMaterial.html b/docs/api/fr/materials/MeshBasicMaterial.html index 2e4f9ed8529748..6388e7aba70f88 100644 --- a/docs/api/fr/materials/MeshBasicMaterial.html +++ b/docs/api/fr/materials/MeshBasicMaterial.html @@ -41,9 +41,9 @@

      Constructeur

      [name]( [param:Object parameters] )

      [page:Object parameters] - (optionnel) un objet avec une ou plusieurs propriétés définissant l'apparence du matériau. - Toute propriété du matériau (y compris toute proprioété héritée de [page:Material]) peut être passée dans l'objet.

      + Toute propriété du matériau (y compris toute propriété héritée de [page:Material]) peut être passée dans l'objet.

      - L'exception est la propriété [page:Hexadecimal color], qui peut être passée comme une chaine de caractères hexadécimale, + L'exception est la propriété [page:Hexadecimal color], qui peut être passée comme une chaîne de caractères hexadécimale, ayant la valeur `0xffffff` (blanc) par défaut. [page:Color.set]( color ) est appelée en interne.

      @@ -53,11 +53,11 @@

      Propriétés

      [property:Texture alphaMap]

      La carte alpha est une texture en niveaux de gris qui contrôle l'opacité sur la surface (noir : entièrement transparent ; blanc : entièrement opaque). La valeur par défaut est nulle.

      - + Seule la couleur de la texture est utilisée, en ignorant le canal alpha s'il en existe un. Pour les textures RGB et RGBA, le moteur de rendu [page:WebGLRenderer WebGL] utilisera le canal vert lors de l'échantillonnage de cette texture en raison du peu de précision supplémentaire fourni - pour le vert dans les formats RVB 565 compressés DXT et non compressés. + pour le vert dans les formats RVB 565 compressés DXT et non compressés. Les textures avec uniquement de la luminance ou les textures luminance/alpha fonctionneront également comme prévu.

      @@ -138,7 +138,7 @@

      [property:Float wireframeLinewidth]

      Contrôle l'épaisseur du filaire. La valeur par défaut est 1.

      A cause des limitations de [link:https://www.khronos.org/registry/OpenGL/specs/gl/glspec46.core.pdf OpenGL Core Profile] - avec le moteur de rendu [page:WebGLRenderer WebGL] sur la plupârt des plateformes, l'épaisseur de ligne (linewidth) sera toujours + avec le moteur de rendu [page:WebGLRenderer WebGL] sur la plupart des plateformes, l'épaisseur de ligne (linewidth) sera toujours à 1, indépendamment de la valeur définie.

      diff --git a/docs/api/fr/materials/MeshDepthMaterial.html b/docs/api/fr/materials/MeshDepthMaterial.html index 58ecfe6f36999f..4b8f8db5c91aa2 100644 --- a/docs/api/fr/materials/MeshDepthMaterial.html +++ b/docs/api/fr/materials/MeshDepthMaterial.html @@ -36,7 +36,7 @@

      Constructeur

      [name]( [param:Object parameters] )

      [page:Object parameters] - (optionnel) un objet avec une ou plusieurs propriétés définissant l'apparence du matériau. - Toute propriété du matériau (y compris toute proprioété héritée de [page:Material]) peut être passée dans l'objet. + Toute propriété du matériau (y compris toute propriété héritée de [page:Material]) peut être passée dans l'objet.

      Propriétés

      @@ -45,11 +45,11 @@

      Propriétés

      [property:Texture alphaMap]

      La carte alpha est une texture en niveaux de gris qui contrôle l'opacité sur la surface (noir : entièrement transparent ; blanc : entièrement opaque). La valeur par défaut est nulle.

      - + Seule la couleur de la texture est utilisée, en ignorant le canal alpha s'il en existe un. Pour les textures RGB et RGBA, le moteur de rendu [page:WebGLRenderer WebGL] utilisera le canal vert lors de l'échantillonnage de cette texture en raison du peu de précision supplémentaire fourni - pour le vert dans les formats RVB 565 compressés DXT et non compressés. + pour le vert dans les formats RVB 565 compressés DXT et non compressés. Les textures avec uniquement de la luminance ou les textures luminance/alpha fonctionneront également comme prévu.

      @@ -68,19 +68,18 @@

      [property:Texture displacementMap]

      [property:Float displacementScale]

      Dans quelle mesure la carte de déplacement affecte le maillage (où le noir n'est pas un déplacement, - et le blanc est le déplacement maximal). Sans ensemble de cartes de déplacement, cette valeur n'est pas appliquée. - La valeur par défaut est 1. + et le blanc est le déplacement maximal). Sans carte de déplacement, cette valeur n'est pas appliquée. + La valeur par défaut est `1`.

      [property:Float displacementBias]

      Le décalage des valeurs de la carte de déplacement sur les sommets du maillage. - Sans ensemble de cartes de déplacement, cette valeur n'est pas appliquée. La valeur par défaut est 0. + Le décalage est ajouté après la mise à l'échelle de la carte de déplacement. + Sans carte de déplacement, cette valeur n'est pas appliquée. La valeur par défaut + est `0`.

      -

      [property:Boolean fog]

      -

      Si le matériau est affecté par le brouillard (fog) La valeur par défaut est `false`.

      -

      [property:Texture map]

      La carte des couleurs. Peut éventuellement inclure un canal alpha, généralement combiné avec @@ -94,7 +93,7 @@

      [property:Float wireframeLinewidth]

      Contrôle l'épaisseur du filaire. La valeur par défaut est 1.

      A cause des limitations de [link:https://www.khronos.org/registry/OpenGL/specs/gl/glspec46.core.pdf OpenGL Core Profile] - avec le moteur de rendu [page:WebGLRenderer WebGL] sur la plupârt des plateformes, l'épaisseur de ligne (linewidth) sera toujours + avec le moteur de rendu [page:WebGLRenderer WebGL] sur la plupart des plateformes, l'épaisseur de ligne (linewidth) sera toujours à 1, indépendamment de la valeur définie.

      diff --git a/docs/api/fr/materials/MeshDistanceMaterial.html b/docs/api/fr/materials/MeshDistanceMaterial.html index db4a186c3b6e1a..780474fee0aa8f 100644 --- a/docs/api/fr/materials/MeshDistanceMaterial.html +++ b/docs/api/fr/materials/MeshDistanceMaterial.html @@ -15,8 +15,7 @@

      [name]

      [name] est utilisé en interne pour implémenter le shadow mapping avec [page:PointLight]s.

      Peut également être utilisé pour personnaliser la projection d'ombre d'un objet en attribuant une instance de [name] à [page:Object3D.customDistanceMaterial]. - Les exemples suivants illustrent cette approche afin de s'assurer que les parties transparentes des objets ne projettent pas d'ombres.ce of [name] to [page:Object3D.customDistanceMaterial]. - The following examples demonstrates this approach in order to ensure transparent parts of objects do no cast shadows. + Les exemples suivants illustrent cette approche afin de s'assurer que les parties transparentes des objets ne projettent pas d'ombres.

      Exemples

      @@ -46,7 +45,7 @@

      Constructeur

      [name]( [param:Object parameters] )

      [page:Object parameters] - (optionnel) un objet avec une ou plusieurs propriétés définissant l'apparence du matériau. - Toute propriété du matériau (y compris toute proprioété héritée de [page:Material]) peut être passée dans l'objet. + Toute propriété du matériau (y compris toute propriété héritée de [page:Material]) peut être passée dans l'objet.

      Propriétés

      @@ -55,11 +54,11 @@

      Propriétés

      [property:Texture alphaMap]

      La carte alpha est une texture en niveaux de gris qui contrôle l'opacité sur la surface (noir : entièrement transparent ; blanc : entièrement opaque). La valeur par défaut est nulle.

      - + Seule la couleur de la texture est utilisée, en ignorant le canal alpha s'il en existe un. Pour les textures RGB et RGBA, le moteur de rendu [page:WebGLRenderer WebGL] utilisera le canal vert lors de l'échantillonnage de cette texture en raison du peu de précision supplémentaire fourni - pour le vert dans les formats RVB 565 compressés DXT et non compressés. + pour le vert dans les formats RVB 565 compressés DXT et non compressés. Les textures avec uniquement de la luminance ou les textures luminance/alpha fonctionneront également comme prévu.

      @@ -75,19 +74,18 @@

      [property:Texture displacementMap]

      [property:Float displacementScale]

      Dans quelle mesure la carte de déplacement affecte le maillage (où le noir n'est pas un déplacement, - et le blanc est le déplacement maximal). Sans ensemble de cartes de déplacement, cette valeur n'est pas appliquée. - La valeur par défaut est 1. + et le blanc est le déplacement maximal). Sans carte de déplacement, cette valeur n'est pas appliquée. + La valeur par défaut est `1`.

      [property:Float displacementBias]

      Le décalage des valeurs de la carte de déplacement sur les sommets du maillage. - Sans ensemble de cartes de déplacement, cette valeur n'est pas appliquée. La valeur par défaut est 0. + Le décalage est ajouté après la mise à l'échelle de la carte de déplacement. + Sans carte de déplacement, cette valeur n'est pas appliquée. La valeur par défaut + est `0`.

      -

      [property:Boolean fog]

      -

      Si le matériau est affecté par le brouillard (fog) La valeur par défaut est `false`.

      -

      [property:Texture map]

      La carte des couleurs. Peut éventuellement inclure un canal alpha, généralement combiné avec diff --git a/docs/api/fr/materials/MeshLambertMaterial.html b/docs/api/fr/materials/MeshLambertMaterial.html index bfbbc41cf660b6..75d096fefe5e48 100644 --- a/docs/api/fr/materials/MeshLambertMaterial.html +++ b/docs/api/fr/materials/MeshLambertMaterial.html @@ -46,9 +46,9 @@

      Constructeur

      [name]( [param:Object parameters] )

      [page:Object parameters] - (optionnel) un objet avec une ou plusieurs propriétés définissant l'apparence du matériau. - Toute propriété du matériau (y compris toute proprioété héritée de [page:Material]) peut être passée dans l'objet.

      + Toute propriété du matériau (y compris toute propriété héritée de [page:Material]) peut être passée dans l'objet.

      - L'exception est la propriété [page:Hexadecimal color], qui peut être passée comme une chaine de caractères hexadécimale, + L'exception est la propriété [page:Hexadecimal color], qui peut être passée comme une chaîne de caractères hexadécimale, ayant la valeur `0xffffff` (blanc) par défaut. [page:Color.set]( color ) est appelée en interne.

      @@ -58,11 +58,11 @@

      Propriétés

      [property:Texture alphaMap]

      La carte alpha est une texture en niveaux de gris qui contrôle l'opacité sur la surface (noir : entièrement transparent ; blanc : entièrement opaque). La valeur par défaut est nulle.

      - + Seule la couleur de la texture est utilisée, en ignorant le canal alpha s'il en existe un. Pour les textures RGB et RGBA, le moteur de rendu [page:WebGLRenderer WebGL] utilisera le canal vert lors de l'échantillonnage de cette texture en raison du peu de précision supplémentaire fourni - pour le vert dans les formats RVB 565 compressés DXT et non compressés. + pour le vert dans les formats RVB 565 compressés DXT et non compressés. Les textures avec uniquement de la luminance ou les textures luminance/alpha fonctionneront également comme prévu.

      @@ -106,14 +106,16 @@

      [property:Texture displacementMap]

      [property:Float displacementScale]

      Dans quelle mesure la carte de déplacement affecte le maillage (où le noir n'est pas un déplacement, - et le blanc est le déplacement maximal). Sans ensemble de cartes de déplacement, cette valeur n'est pas appliquée. - La valeur par défaut est 1. + et le blanc est le déplacement maximal). Sans carte de déplacement, cette valeur n'est pas appliquée. + La valeur par défaut est `1`.

      [property:Float displacementBias]

      Le décalage des valeurs de la carte de déplacement sur les sommets du maillage. - Sans ensemble de cartes de déplacement, cette valeur n'est pas appliquée. La valeur par défaut est 0. + Le décalage est ajouté après la mise à l'échelle de la carte de déplacement. + Sans carte de déplacement, cette valeur n'est pas appliquée. La valeur par défaut + est `0`.

      [property:Color emissive]

      @@ -213,7 +215,7 @@

      [property:Float wireframeLinewidth]

      Contrôle l'épaisseur du filaire. La valeur par défaut est 1.

      A cause des limitations de [link:https://www.khronos.org/registry/OpenGL/specs/gl/glspec46.core.pdf OpenGL Core Profile] - avec le moteur de rendu [page:WebGLRenderer WebGL] sur la plupârt des plateformes, l'épaisseur de ligne (linewidth) sera toujours + avec le moteur de rendu [page:WebGLRenderer WebGL] sur la plupart des plateformes, l'épaisseur de ligne (linewidth) sera toujours à 1, indépendamment de la valeur définie.

      diff --git a/docs/api/fr/materials/MeshMatcapMaterial.html b/docs/api/fr/materials/MeshMatcapMaterial.html index 4e87c44b5c6ccb..8f2eda681d2ba9 100644 --- a/docs/api/fr/materials/MeshMatcapMaterial.html +++ b/docs/api/fr/materials/MeshMatcapMaterial.html @@ -40,9 +40,9 @@

      Constructeur

      [name]( [param:Object parameters] )

      [page:Object parameters] - (optionnel) un objet avec une ou plusieurs propriétés définissant l'apparence du matériau. - Toute propriété du matériau (y compris toute proprioété héritée de [page:Material]) peut être passée dans l'objet.

      + Toute propriété du matériau (y compris toute propriété héritée de [page:Material]) peut être passée dans l'objet.

      - L'exception est la propriété [page:Hexadecimal color], qui peut être passée comme une chaine de caractères hexadécimale, + L'exception est la propriété [page:Hexadecimal color], qui peut être passée comme une chaîne de caractères hexadécimale, ayant la valeur `0xffffff` (blanc) par défaut. [page:Color.set]( color ) est appelée en interne.

      @@ -52,11 +52,11 @@

      Propriétés

      [property:Texture alphaMap]

      La carte alpha est une texture en niveaux de gris qui contrôle l'opacité sur la surface (noir : entièrement transparent ; blanc : entièrement opaque). La valeur par défaut est nulle.

      - + Seule la couleur de la texture est utilisée, en ignorant le canal alpha s'il en existe un. Pour les textures RGB et RGBA, le moteur de rendu [page:WebGLRenderer WebGL] utilisera le canal vert lors de l'échantillonnage de cette texture en raison du peu de précision supplémentaire fourni - pour le vert dans les formats RVB 565 compressés DXT et non compressés. + pour le vert dans les formats RVB 565 compressés DXT et non compressés. Les textures avec uniquement de la luminance ou les textures luminance/alpha fonctionneront également comme prévu.

      @@ -84,14 +84,16 @@

      [property:Texture displacementMap]

      [property:Float displacementScale]

      Dans quelle mesure la carte de déplacement affecte le maillage (où le noir n'est pas un déplacement, - et le blanc est le déplacement maximal). Sans ensemble de cartes de déplacement, cette valeur n'est pas appliquée. - La valeur par défaut est 1. + et le blanc est le déplacement maximal). Sans carte de déplacement, cette valeur n'est pas appliquée. + La valeur par défaut est `1`.

      [property:Float displacementBias]

      Le décalage des valeurs de la carte de déplacement sur les sommets du maillage. - Sans ensemble de cartes de déplacement, cette valeur n'est pas appliquée. La valeur par défaut est 0. + Le décalage est ajouté après la mise à l'échelle de la carte de déplacement. + Sans carte de déplacement, cette valeur n'est pas appliquée. La valeur par défaut + est `0`.

      [property:Boolean flatShading]

      diff --git a/docs/api/fr/materials/MeshNormalMaterial.html b/docs/api/fr/materials/MeshNormalMaterial.html index 98dd07141a94d5..e5ebad1cad52ed 100644 --- a/docs/api/fr/materials/MeshNormalMaterial.html +++ b/docs/api/fr/materials/MeshNormalMaterial.html @@ -36,7 +36,7 @@

      Constructeur

      [name]( [param:Object parameters] )

      [page:Object parameters] - (optionnel) un objet avec une ou plusieurs propriétés définissant l'apparence du matériau. - Toute propriété du matériau (y compris toute proprioété héritée de [page:Material]) peut être passée dans l'objet. + Toute propriété du matériau (y compris toute propriété héritée de [page:Material]) peut être passée dans l'objet.

      @@ -64,14 +64,16 @@

      [property:Texture displacementMap]

      [property:Float displacementScale]

      Dans quelle mesure la carte de déplacement affecte le maillage (où le noir n'est pas un déplacement, - et le blanc est le déplacement maximal). Sans ensemble de cartes de déplacement, cette valeur n'est pas appliquée. - La valeur par défaut est 1. + et le blanc est le déplacement maximal). Sans carte de déplacement, cette valeur n'est pas appliquée. + La valeur par défaut est `1`.

      [property:Float displacementBias]

      Le décalage des valeurs de la carte de déplacement sur les sommets du maillage. - Sans ensemble de cartes de déplacement, cette valeur n'est pas appliquée. La valeur par défaut est 0. + Le décalage est ajouté après la mise à l'échelle de la carte de déplacement. + Sans carte de déplacement, cette valeur n'est pas appliquée. La valeur par défaut + est `0`.

      [property:Boolean flatShading]

      @@ -79,9 +81,6 @@

      [property:Boolean flatShading]

      Définit si le matériau est rendu avec un ombrage plat. La valeur par défaut est false.

      -

      [property:Boolean fog]

      -

      Si le matériau est affecté par le brouillard (fog) La valeur par défaut est `false`.

      -

      [property:Texture normalMap]

      La texture pour créer une carte normale. Les valeurs RVB affectent la surface normale pour chaque fragment de pixel et changent @@ -112,7 +111,7 @@

      [property:Float wireframeLinewidth]

      Contrôle l'épaisseur du filaire. La valeur par défaut est 1.

      A cause des limitations de [link:https://www.khronos.org/registry/OpenGL/specs/gl/glspec46.core.pdf OpenGL Core Profile] - avec le moteur de rendu [page:WebGLRenderer WebGL] sur la plupârt des plateformes, l'épaisseur de ligne (linewidth) sera toujours + avec le moteur de rendu [page:WebGLRenderer WebGL] sur la plupart des plateformes, l'épaisseur de ligne (linewidth) sera toujours à 1, indépendamment de la valeur définie.

      diff --git a/docs/api/fr/materials/MeshPhongMaterial.html b/docs/api/fr/materials/MeshPhongMaterial.html index 35a0e62aa466c3..17bd513f6b1e8f 100644 --- a/docs/api/fr/materials/MeshPhongMaterial.html +++ b/docs/api/fr/materials/MeshPhongMaterial.html @@ -42,12 +42,12 @@

      [name]

      Constructeur

      -

      [name]( [param:Object parameters] )

      +

      [name]( [param:Object parameters] )

      [page:Object parameters] - (optionnel) un objet avec une ou plusieurs propriétés définissant l'apparence du matériau. - Toute propriété du matériau (y compris toute proprioété héritée de [page:Material]) peut être passée dans l'objet.

      + Toute propriété du matériau (y compris toute propriété héritée de [page:Material]) peut être passée dans l'objet.

      - L'exception est la propriété [page:Hexadecimal color], qui peut être passée comme une chaine de caractères hexadécimale, + L'exception est la propriété [page:Hexadecimal color], qui peut être passée comme une chaîne de caractères hexadécimale, ayant la valeur `0xffffff` (blanc) par défaut. [page:Color.set]( color ) est appelée en interne.

      @@ -57,11 +57,11 @@

      Propriétés

      [property:Texture alphaMap]

      La carte alpha est une texture en niveaux de gris qui contrôle l'opacité sur la surface (noir : entièrement transparent ; blanc : entièrement opaque). La valeur par défaut est nulle.

      - + Seule la couleur de la texture est utilisée, en ignorant le canal alpha s'il en existe un. Pour les textures RGB et RGBA, le moteur de rendu [page:WebGLRenderer WebGL] utilisera le canal vert lors de l'échantillonnage de cette texture en raison du peu de précision supplémentaire fourni - pour le vert dans les formats RVB 565 compressés DXT et non compressés. + pour le vert dans les formats RVB 565 compressés DXT et non compressés. Les textures avec uniquement de la luminance ou les textures luminance/alpha fonctionneront également comme prévu.

      @@ -106,14 +106,16 @@

      [property:Texture displacementMap]

      [property:Float displacementScale]

      Dans quelle mesure la carte de déplacement affecte le maillage (où le noir n'est pas un déplacement, - et le blanc est le déplacement maximal). Sans ensemble de cartes de déplacement, cette valeur n'est pas appliquée. - La valeur par défaut est 1. + et le blanc est le déplacement maximal). Sans carte de déplacement, cette valeur n'est pas appliquée. + La valeur par défaut est `1`.

      [property:Float displacementBias]

      Le décalage des valeurs de la carte de déplacement sur les sommets du maillage. - Sans ensemble de cartes de déplacement, cette valeur n'est pas appliquée. La valeur par défaut est 0. + Le décalage est ajouté après la mise à l'échelle de la carte de déplacement. + Sans carte de déplacement, cette valeur n'est pas appliquée. La valeur par défaut + est `0`.

      [property:Color emissive]

      @@ -231,7 +233,7 @@

      [property:Float wireframeLinewidth]

      Contrôle l'épaisseur du filaire. La valeur par défaut est 1.

      A cause des limitations de [link:https://www.khronos.org/registry/OpenGL/specs/gl/glspec46.core.pdf OpenGL Core Profile] - avec le moteur de rendu [page:WebGLRenderer WebGL] sur la plupârt des plateformes, l'épaisseur de ligne (linewidth) sera toujours + avec le moteur de rendu [page:WebGLRenderer WebGL] sur la plupart des plateformes, l'épaisseur de ligne (linewidth) sera toujours à 1, indépendamment de la valeur définie.

      diff --git a/docs/api/fr/materials/MeshPhysicalMaterial.html b/docs/api/fr/materials/MeshPhysicalMaterial.html index 34115aa4680e77..85a718fde78c55 100644 --- a/docs/api/fr/materials/MeshPhysicalMaterial.html +++ b/docs/api/fr/materials/MeshPhysicalMaterial.html @@ -61,9 +61,7 @@

      [name]

      Exemples

      - [example:webgl_materials_variations_physical materials / variations / physical]
      [example:webgl_materials_physical_clearcoat materials / physical / clearcoat]
      - [example:webgl_materials_physical_reflectivity materials / physical / reflectivity]
      [example:webgl_loader_gltf_sheen loader / gltf / sheen]
      [example:webgl_materials_physical_transmission materials / physical / transmission]

      @@ -133,6 +131,14 @@

      [property:Object defines]

      Est utilisé par [page:WebGLRenderer] pour sélectionner les shaders.

      +

      [property:Float dispersion]

      +

      + Defines the strength of the angular separation of colors (chromatic aberration) transmitting through a relatively clear volume. + Any value zero or larger is valid, the typical range of realistic values is `[0, 1]`. + Default is `0` (no dispersion). + This property can be only be used with transmissive objects, see [page:.transmission]. +

      +

      [property:Float ior]

      Indice de réfraction pour les matériaux non métalliques, de "1,0" à "2,333". La valeur par défaut est `1.5`.
      @@ -163,7 +169,7 @@

      [property:Texture sheenRoughnessMap]

      [property:Color sheenColor]

      - La teinte brillante. La valeur par défaut est `0xffffff`, white. + La teinte brillante. La valeur par défaut est `0x000000`, noire.

      [property:Texture sheenColorMap]

      @@ -174,7 +180,7 @@

      [property:Texture sheenColorMap]

      [property:Float specularIntensity]

      - Un float qui met à l'échelle la quantité de réflexion spéculaire pour les non-métaux uniquement. Lorsqu'il est défini sur zéro, le modèle est effectivement lambertien. De `0.0` à `1.0`. La valeur par défaut est `0.0`. + Un float qui met à l'échelle la quantité de réflexion spéculaire pour les non-métaux uniquement. Lorsqu'il est défini sur zéro, le modèle est effectivement lambertien. De `0.0` à `1.0`. La valeur par défaut est `1.0`.

      [property:Texture specularIntensityMap]

      @@ -196,7 +202,7 @@

      [property:Texture specularColorMap]

      [property:Float thickness]

      - L'épaisseur du volume sous la surface. La valeur est donnée dans l'espace de coordonnées du maillage. + L'épaisseur du volume sous la surface. La valeur est donnée dans l'espace de coordonnées du maillage. Si la valeur est 0, le matériau est à paroi mince. Sinon, le matériau est une limite de volume. La valeur par défaut est `0`.

      @@ -214,7 +220,7 @@

      [property:Float transmission]

      La propriété de transmission peut être utilisée pour modéliser ces matériaux.
      - Lorsque la transmission est différente de zéro, [page:Material.opacity opacity] doit être défini sur "0". + Lorsque la transmission est différente de zéro, [page:Material.opacity opacity] doit être défini sur `1`.

      [property:Texture transmissionMap]

      diff --git a/docs/api/fr/materials/MeshStandardMaterial.html b/docs/api/fr/materials/MeshStandardMaterial.html index ec6de1d75929c3..2b9be48c545a74 100644 --- a/docs/api/fr/materials/MeshStandardMaterial.html +++ b/docs/api/fr/materials/MeshStandardMaterial.html @@ -70,9 +70,9 @@

      Constructeur

      [name]( [param:Object parameters] )

      [page:Object parameters] - (optionnel) un objet avec une ou plusieurs propriétés définissant l'apparence du matériau. - Toute propriété du matériau (y compris toute proprioété héritée de [page:Material]) peut être passée dans l'objet.

      + Toute propriété du matériau (y compris toute propriété héritée de [page:Material]) peut être passée dans l'objet.

      - L'exception est la propriété [page:Hexadecimal color], qui peut être passée comme une chaine de caractères hexadécimale, + L'exception est la propriété [page:Hexadecimal color], qui peut être passée comme une chaîne de caractères hexadécimale, ayant la valeur `0xffffff` (blanc) par défaut. [page:Color.set]( color ) est appelée en interne.

      @@ -82,11 +82,11 @@

      Propriétés

      [property:Texture alphaMap]

      La carte alpha est une texture en niveaux de gris qui contrôle l'opacité sur la surface (noir : entièrement transparent ; blanc : entièrement opaque). La valeur par défaut est nulle.

      - + Seule la couleur de la texture est utilisée, en ignorant le canal alpha s'il en existe un. Pour les textures RGB et RGBA, le moteur de rendu [page:WebGLRenderer WebGL] utilisera le canal vert lors de l'échantillonnage de cette texture en raison du peu de précision supplémentaire fourni - pour le vert dans les formats RVB 565 compressés DXT et non compressés. + pour le vert dans les formats RVB 565 compressés DXT et non compressés. Les textures avec uniquement de la luminance ou les textures luminance/alpha fonctionneront également comme prévu.

      @@ -131,14 +131,16 @@

      [property:Texture displacementMap]

      [property:Float displacementScale]

      Dans quelle mesure la carte de déplacement affecte le maillage (où le noir n'est pas un déplacement, - et le blanc est le déplacement maximal). Sans ensemble de cartes de déplacement, cette valeur n'est pas appliquée. - La valeur par défaut est 1. + et le blanc est le déplacement maximal). Sans carte de déplacement, cette valeur n'est pas appliquée. + La valeur par défaut est `1`.

      [property:Float displacementBias]

      Le décalage des valeurs de la carte de déplacement sur les sommets du maillage. - Sans ensemble de cartes de déplacement, cette valeur n'est pas appliquée. La valeur par défaut est 0. + Le décalage est ajouté après la mise à l'échelle de la carte de déplacement. + Sans carte de déplacement, cette valeur n'est pas appliquée. La valeur par défaut + est `0`.

      [property:Color emissive]

      @@ -255,7 +257,7 @@

      [property:Float wireframeLinewidth]

      Contrôle l'épaisseur du filaire. La valeur par défaut est 1.

      A cause des limitations de [link:https://www.khronos.org/registry/OpenGL/specs/gl/glspec46.core.pdf OpenGL Core Profile] - avec le moteur de rendu [page:WebGLRenderer WebGL] sur la plupârt des plateformes, l'épaisseur de ligne (linewidth) sera toujours + avec le moteur de rendu [page:WebGLRenderer WebGL] sur la plupart des plateformes, l'épaisseur de ligne (linewidth) sera toujours à 1, indépendamment de la valeur définie.

      diff --git a/docs/api/fr/materials/MeshToonMaterial.html b/docs/api/fr/materials/MeshToonMaterial.html index 894321425dbdf0..97809b61682feb 100644 --- a/docs/api/fr/materials/MeshToonMaterial.html +++ b/docs/api/fr/materials/MeshToonMaterial.html @@ -33,7 +33,7 @@

      [name]

      Exemples

      - [example:webgl_materials_variations_toon materials / variations / toon] + [example:webgl_materials_toon materials / toon]

      Constructeur

      @@ -41,9 +41,9 @@

      Constructeur

      [name]( [param:Object parameters] )

      [page:Object parameters] - (optionnel) un objet avec une ou plusieurs propriétés définissant l'apparence du matériau. - Toute propriété du matériau (y compris toute proprioété héritée de [page:Material]) peut être passée dans l'objet.

      + Toute propriété du matériau (y compris toute propriété héritée de [page:Material]) peut être passée dans l'objet.

      - L'exception est la propriété [page:Hexadecimal color], qui peut être passée comme une chaine de caractères hexadécimale, + L'exception est la propriété [page:Hexadecimal color], qui peut être passée comme une chaîne de caractères hexadécimale, ayant la valeur `0xffffff` (blanc) par défaut. [page:Color.set]( color ) est appelée en interne.

      @@ -53,11 +53,11 @@

      Propriétés

      [property:Texture alphaMap]

      La carte alpha est une texture en niveaux de gris qui contrôle l'opacité sur la surface (noir : entièrement transparent ; blanc : entièrement opaque). La valeur par défaut est nulle.

      - + Seule la couleur de la texture est utilisée, en ignorant le canal alpha s'il en existe un. Pour les textures RGB et RGBA, le moteur de rendu [page:WebGLRenderer WebGL] utilisera le canal vert lors de l'échantillonnage de cette texture en raison du peu de précision supplémentaire fourni - pour le vert dans les formats RVB 565 compressés DXT et non compressés. + pour le vert dans les formats RVB 565 compressés DXT et non compressés. Les textures avec uniquement de la luminance ou les textures luminance/alpha fonctionneront également comme prévu.

      @@ -184,7 +184,7 @@

      [property:Float wireframeLinewidth]

      Contrôle l'épaisseur du filaire. La valeur par défaut est 1.

      A cause des limitations de [link:https://www.khronos.org/registry/OpenGL/specs/gl/glspec46.core.pdf OpenGL Core Profile] - avec le moteur de rendu [page:WebGLRenderer WebGL] sur la plupârt des plateformes, l'épaisseur de ligne (linewidth) sera toujours + avec le moteur de rendu [page:WebGLRenderer WebGL] sur la plupart des plateformes, l'épaisseur de ligne (linewidth) sera toujours à 1, indépendamment de la valeur définie.

      diff --git a/docs/api/fr/materials/PointsMaterial.html b/docs/api/fr/materials/PointsMaterial.html index 0595a8e99f7f8b..53bc47dba0d856 100644 --- a/docs/api/fr/materials/PointsMaterial.html +++ b/docs/api/fr/materials/PointsMaterial.html @@ -50,17 +50,16 @@

      Exemples

      [example:webgl_multiple_elements_text WebGL / multiple / elements / text]
      [example:webgl_points_billboards WebGL / points / billboards]
      [example:webgl_points_dynamic WebGL / points / dynamic]
      - [example:webgl_points_sprites WebGL / points / sprites]
      - [example:webgl_trails WebGL / trails] + [example:webgl_points_sprites WebGL / points / sprites]

      Constructeur

      [name]( [param:Object parameters] )

      [page:Object parameters] - (optionnel) un objet avec une ou plusieurs propriétés définissant l'apparence du matériau. - Toute propriété du matériau (y compris toute proprioété héritée de [page:Material]) peut être passée dans l'objet.

      + Toute propriété du matériau (y compris toute propriété héritée de [page:Material]) peut être passée dans l'objet.

      - L'exception est la propriété [page:Hexadecimal color], qui peut être passée comme une chaine de caractères hexadécimale, + L'exception est la propriété [page:Hexadecimal color], qui peut être passée comme une chaîne de caractères hexadécimale, ayant la valeur `0xffffff` (blanc) par défaut. [page:Color.set]( color ) est appelée en interne.

      @@ -70,11 +69,11 @@

      Propriétés

      [property:Texture alphaMap]

      La carte alpha est une texture en niveaux de gris qui contrôle l'opacité sur la surface (noir : entièrement transparent ; blanc : entièrement opaque). La valeur par défaut est nulle.

      - + Seule la couleur de la texture est utilisée, en ignorant le canal alpha s'il en existe un. Pour les textures RGB et RGBA, le moteur de rendu [page:WebGLRenderer WebGL] utilisera le canal vert lors de l'échantillonnage de cette texture en raison du peu de précision supplémentaire fourni - pour le vert dans les formats RVB 565 compressés DXT et non compressés. + pour le vert dans les formats RVB 565 compressés DXT et non compressés. Les textures avec uniquement de la luminance ou les textures luminance/alpha fonctionneront également comme prévu.

      diff --git a/docs/api/fr/materials/RawShaderMaterial.html b/docs/api/fr/materials/RawShaderMaterial.html index 81a6759c100af1..08575e9054e7df 100644 --- a/docs/api/fr/materials/RawShaderMaterial.html +++ b/docs/api/fr/materials/RawShaderMaterial.html @@ -34,10 +34,9 @@

      Exemples

      [example:webgl_buffergeometry_rawshader WebGL / buffergeometry / rawshader]
      [example:webgl_buffergeometry_instancing_billboards WebGL / buffergeometry / instancing / billboards]
      [example:webgl_buffergeometry_instancing WebGL / buffergeometry / instancing]
      - [example:webgl_raymarching_reflect WebGL / raymarching / reflect]
      - [example:webgl2_volume_cloud WebGL 2 / volume / cloud]
      - [example:webgl2_volume_instancing WebGL 2 / volume / instancing]
      - [example:webgl2_volume_perlin WebGL 2 / volume / perlin] + [example:webgl_volume_cloud WebGL / volume / cloud]
      + [example:webgl_volume_instancing WebGL / volume / instancing]
      + [example:webgl_volume_perlin WebGL / volume / perlin]

      Constructeur

      @@ -45,7 +44,7 @@

      Constructeur

      [name]( [param:Object parameters] )

      [page:Object parameters] - (optionnel) un objet avec une ou plusieurs propriétés définissant l'apparence du matériau. - Toute propriété du matériau (y compris toute proprioété héritée de [page:Material] and [page:ShaderMaterial]) peut être passée dans l'objet.

      + Toute propriété du matériau (y compris toute propriété héritée de [page:Material] and [page:ShaderMaterial]) peut être passée dans l'objet.

      diff --git a/docs/api/fr/materials/ShaderMaterial.html b/docs/api/fr/materials/ShaderMaterial.html index 82b115e1bf9edb..c32e111997aa0b 100644 --- a/docs/api/fr/materials/ShaderMaterial.html +++ b/docs/api/fr/materials/ShaderMaterial.html @@ -107,7 +107,6 @@

      Exemples

      [example:webgl_lights_hemisphere webgl / lights / hemisphere]
      [example:webgl_marchingcubes webgl / marchingcubes]
      [example:webgl_materials_envmaps webgl / materials / envmaps]
      - [example:webgl_materials_lightmap webgl / materials / lightmap]
      [example:webgl_materials_wireframe webgl / materials / wireframe]
      [example:webgl_modifier_tessellation webgl / modifier / tessellation]
      [example:webgl_postprocessing_dof2 webgl / postprocessing / dof2]
      @@ -263,7 +262,7 @@

      Constructeur

      [name]( [param:Object parameters] )

      [page:Object parameters] - (optionnel) un objet avec une ou plusieurs propriétés définissant l'apparence du matériau. - Toute propriété du matériau (y compris toute proprioété héritée de [page:Material]) peut être passée dans l'objet. + Toute propriété du matériau (y compris toute propriété héritée de [page:Material]) peut être passée dans l'objet.

      Propriétés

      @@ -313,10 +312,8 @@

      [property:Object extensions]

      Un objet avec les propriétés suivantes : this.extensions = { - derivatives: false, // set to use derivatives - fragDepth: false, // set to use fragment depth values - drawBuffers: false, // set to use draw buffers - shaderTextureLOD: false // set to use shader texture LOD + clipCullDistance: false, // set to use vertex shader clipping + multiDraw: false // set to use vertex shader multi_draw / enable gl_DrawID };

      @@ -338,8 +335,8 @@

      [property:String fragmentShader]

      [property:String glslVersion]

      - Définit la version GLSL du code de shader personnalisé. Pertinent uniquement pour WebGL 2 afin de définir s'il faut spécifier - GLSL 3.0 ou pas. Les valeurs valides sont `THREE.GLSL1` ou `THREE.GLSL3`. La valeur par défaut est `null`. + Définit la version GLSL du code de shader personnalisé. Les valeurs valides sont `THREE.GLSL1` ou `THREE.GLSL3`. + La valeur par défaut est `null`.

      [property:String index0AttributeName]

      @@ -364,7 +361,7 @@

      [property:Float linewidth]

      Contrôle l'épaisseur du filaire. La valeur par défaut est 1.

      A cause des limitations de [link:https://www.khronos.org/registry/OpenGL/specs/gl/glspec46.core.pdf OpenGL Core Profile] - avec le moteur de rendu [page:WebGLRenderer WebGL] sur la plupârt des plateformes, l'épaisseur de ligne (linewidth) sera toujours + avec le moteur de rendu [page:WebGLRenderer WebGL] sur la plupart des plateformes, l'épaisseur de ligne (linewidth) sera toujours à 1, indépendamment de la valeur définie.

      @@ -384,7 +381,7 @@

      [property:Object uniforms]

      { value: 1.0 } Quand `value` est la valeur de l'uniforme. Les noms doivent correspondre au nom de l'uniforme, - tel que défini dans le code GLSL. A noter que les uniformes sont rafraichis à chaque frame, + tel que défini dans le code GLSL. A noter que les uniformes sont rafraîchis à chaque frame, donc la mise à jour de la valeur de l'uniforme mettra immédiatement à jour la valeur disponible pour le code GLSL.

      @@ -414,7 +411,7 @@

      [property:Float wireframeLinewidth]

      Contrôle l'épaisseur du filaire. La valeur par défaut est 1.

      A cause des limitations de [link:https://www.khronos.org/registry/OpenGL/specs/gl/glspec46.core.pdf OpenGL Core Profile] - avec le moteur de rendu [page:WebGLRenderer WebGL] sur la plupârt des plateformes, l'épaisseur de ligne (linewidth) sera toujours + avec le moteur de rendu [page:WebGLRenderer WebGL] sur la plupart des plateformes, l'épaisseur de ligne (linewidth) sera toujours à 1, indépendamment de la valeur définie.

      @@ -423,7 +420,7 @@

      [property:Float wireframeLinewidth]

      Méthodes

      Voir la classe [page:Material] pour les méthodes communes.

      -

      [method:ShaderMaterial clone]() [param:ShaderMaterial this]

      +

      [method:ShaderMaterial clone]()

      Génère une copie superficielle de ce matériau. Notez que le vertexShader et le fragmentShader sont copiés "par référence", de même que les définitions des "attributs" ; cela signifie diff --git a/docs/api/fr/materials/ShadowMaterial.html b/docs/api/fr/materials/ShadowMaterial.html index 9fe8b8cc410b70..c739dae11d851d 100644 --- a/docs/api/fr/materials/ShadowMaterial.html +++ b/docs/api/fr/materials/ShadowMaterial.html @@ -41,7 +41,7 @@

      Constructeur

      [name]( [param:Object parameters] )

      [page:Object parameters] - (optionnel) un objet avec une ou plusieurs propriétés définissant l'apparence du matériau. - Toute propriété du matériau (y compris toute proprioété héritée de [page:Material]) peut être passée dans l'objet.

      + Toute propriété du matériau (y compris toute propriété héritée de [page:Material]) peut être passée dans l'objet.

      diff --git a/docs/api/fr/materials/SpriteMaterial.html b/docs/api/fr/materials/SpriteMaterial.html index ded73c3f54ed18..747af8ecb75f58 100644 --- a/docs/api/fr/materials/SpriteMaterial.html +++ b/docs/api/fr/materials/SpriteMaterial.html @@ -36,9 +36,9 @@

      Constructeur

      [name]( [param:Object parameters] )

      [page:Object parameters] - (optionnel) un objet avec une ou plusieurs propriétés définissant l'apparence du matériau. - Toute propriété du matériau (y compris toute proprioété héritée de [page:Material]) peut être passée dans l'objet.

      + Toute propriété du matériau (y compris toute propriété héritée de [page:Material]) peut être passée dans l'objet.

      - L'exception est la propriété [page:Hexadecimal color], qui peut être passée comme une chaine de caractères hexadécimale, + L'exception est la propriété [page:Hexadecimal color], qui peut être passée comme une chaîne de caractères hexadécimale, ayant la valeur `0xffffff` (blanc) par défaut. [page:Color.set]( color ) est appelée en interne. Les SpriteMaterials ne sont pas coupés en utilisant [page:Material.clippingPlanes]. @@ -51,11 +51,11 @@

      Propriétés

      [property:Texture alphaMap]

      La carte alpha est une texture en niveaux de gris qui contrôle l'opacité sur la surface (noir : entièrement transparent ; blanc : entièrement opaque). La valeur par défaut est nulle.

      - + Seule la couleur de la texture est utilisée, en ignorant le canal alpha s'il en existe un. Pour les textures RGB et RGBA, le moteur de rendu [page:WebGLRenderer WebGL] utilisera le canal vert lors de l'échantillonnage de cette texture en raison du peu de précision supplémentaire fourni - pour le vert dans les formats RVB 565 compressés DXT et non compressés. + pour le vert dans les formats RVB 565 compressés DXT et non compressés. Les textures avec uniquement de la luminance ou les textures luminance/alpha fonctionneront également comme prévu.

      diff --git a/docs/api/it/animation/AnimationUtils.html b/docs/api/it/animation/AnimationUtils.html index 69afca9f9e87f7..47b394c0d482c3 100644 --- a/docs/api/it/animation/AnimationUtils.html +++ b/docs/api/it/animation/AnimationUtils.html @@ -17,11 +17,6 @@

      [name]

      Metodi

      -

      [method:Array arraySlice]( array, from, to )

      -

      - È lo stesso di Array.prototype.slice, ma funziona anche su array tipizzati. -

      -

      [method:Array convertArray]( array, type, forceClone )

      Converte un array in un tipo specifico. diff --git a/docs/api/it/audio/Audio.html b/docs/api/it/audio/Audio.html index 34fdec70561131..ee5be465c74253 100644 --- a/docs/api/it/audio/Audio.html +++ b/docs/api/it/audio/Audio.html @@ -165,7 +165,7 @@

      [method:Float getPlaybackRate]()

      Restituisce il valore di [page:Audio.playbackRate playbackRate].

      -

      [method:Float getVolume]( value )

      +

      [method:Float getVolume]()

      Restituisce il volume corrente.

      diff --git a/docs/api/it/cameras/OrthographicCamera.html b/docs/api/it/cameras/OrthographicCamera.html index c57542a7b7e104..b754eb10cac4a7 100644 --- a/docs/api/it/cameras/OrthographicCamera.html +++ b/docs/api/it/cameras/OrthographicCamera.html @@ -37,7 +37,6 @@

      Esempi

      [example:webgl_postprocessing_dof2 postprocessing / dof2 ]
      [example:webgl_postprocessing_godrays postprocessing / godrays ]
      [example:webgl_rtt rtt ]
      - [example:webgl_shaders_tonemapping shaders / tonemapping ]
      [example:webgl_shadowmap shadowmap ]

      diff --git a/docs/api/it/constants/Renderer.html b/docs/api/it/constants/Renderer.html index 4a10d4391cad85..98302c549443ac 100644 --- a/docs/api/it/constants/Renderer.html +++ b/docs/api/it/constants/Renderer.html @@ -48,6 +48,8 @@

      Mappatura dei Toni

      THREE.ReinhardToneMapping THREE.CineonToneMapping THREE.ACESFilmicToneMapping + THREE.AgXToneMapping + THREE.NeutralToneMapping THREE.CustomToneMapping

      @@ -56,11 +58,14 @@

      Mappatura dei Toni

      gamma dinamica bassa del monitor di un computer o dello schermo di un dispositivo mobile.

      - THREE.LinearToneMapping, THREE.ReinhardToneMapping, THREE.CineonToneMapping e THREE.ACESFilmicToneMapping sono implementazioni + THREE.LinearToneMapping, THREE.ReinhardToneMapping, THREE.CineonToneMapping, THREE.ACESFilmicToneMapping THREE.AgXToneMapping e THREE.NeutralToneMapping sono implementazioni integrate della mappatura dei toni. THREE.CustomToneMapping prevede un'implementazione personalizzata modificando il codice GLSL dello shader di frammenti del materiale. Vedi l'esempio [example:webgl_tonemapping WebGL / tonemapping].

      +

      + THREE.NeutralToneMapping is an implementation based on the Khronos 3D Commerce Group standard tone mapping. +

      Source

      diff --git a/docs/api/it/constants/Textures.html b/docs/api/it/constants/Textures.html index 7f9977c546ecdf..0f812c4a52e3ea 100644 --- a/docs/api/it/constants/Textures.html +++ b/docs/api/it/constants/Textures.html @@ -125,6 +125,7 @@

      Tipi

      THREE.UnsignedShort4444Type THREE.UnsignedShort5551Type THREE.UnsignedInt248Type + THREE.UnsignedInt5999Type

      Da usare con la proprietà [page:Texture.type type] della texture, la quale deve corrispondere al formato corretto. Vedi sotto per i dettagli.

      @@ -139,10 +140,9 @@

      Formati

      THREE.RedIntegerFormat THREE.RGFormat THREE.RGIntegerFormat + THREE.RGBFormat THREE.RGBAFormat THREE.RGBAIntegerFormat - THREE.LuminanceFormat - THREE.LuminanceAlphaFormat THREE.DepthFormat THREE.DepthStencilFormat @@ -155,29 +155,17 @@

      Formati

      [page:constant RedFormat] elimina i componenti verde e blu e legge solo il componente rosso.

      [page:constant RedIntegerFormat] elimina i componenti verde e blu e legge solo il componente rosso. - I texel sono letti come interi invece che come floating point. - (può essere utilizzato solo in un contesto di rendering WebGL 2).

      + I texel sono letti come interi invece che come floating point.

      - [page:constant RGFormat] elimina i componenti alfa e blu e legge i componenti rosso e verde. - (può essere utilizzato solo in un contesto di rendering WebGL 2).

      + [page:constant RGFormat] elimina i componenti alfa e blu e legge i componenti rosso e verde.

      [page:constant RGIntegerFormat] elimina i componenti alfa e blu e legge i componenti rosso e verde. - I texel sono letti come numeri interi invece che come floating point. - (può essere utilizzato solo in un contesto di rendering WebGL 2).

      + I texel sono letti come numeri interi invece che come floating point.

      [page:constant RGBAFormat] è l'impostazione predefinita e legge i componenti rosso, verde, blu e alfa.

      [page:constant RGBAIntegerFormat] è l'impostazione di default e legge i componenti rosso, verde, blu e alfa. - I texel sono letti come numeri interi invece che come floating point. - (può essere utilizzato solo in un contesto di rendering WebGL 2).

      - - [page:constant LuminanceFormat] legge ogni elemento come un singolo componente di luminanza. - Questo viene quindi convertito in floating point, fissato all'intervallo [0,1], e quindi assemblato - in un elemento RGBA posizionando il valore di luminanza nei canali rosso, verde e blu, e allegando - 1.0 al canale alfa.

      - - [page:constant LuminanceAlphaFormat] legge ogni elemento come un doppio luminanza/alfa. Lo stesso processo si verifica - come per [page:constant LuminanceFormat], tranne per il fatto che il canale alfa può avere valori diversi da `1.0`.

      + I texel sono letti come numeri interi invece che come floating point.

      [page:constant DepthFormat] legge ogni elemento come un singolo valore depth, lo converte in floating point e si blocca nell'intervallo [0,1]. Questa è l'impostazione predefinita per [page:DepthTexture DepthTexture].

      @@ -346,9 +334,6 @@

      Formati Interni

      - Attenzione: la modifica di un formato interno di una texture avrà effetto solo - quando si utilizza un contesto di rendering WebGL 2.

      - Da usare con la proprietà [page:Texture.internalFormat internalFormat] della texture, definiscono come gli elementi della texture, o `toxel`, sono memorizzati nella GPU.

      diff --git a/docs/api/it/core/BufferAttribute.html b/docs/api/it/core/BufferAttribute.html index c8d97887dc11ed..8adec0c37e4cfa 100644 --- a/docs/api/it/core/BufferAttribute.html +++ b/docs/api/it/core/BufferAttribute.html @@ -28,7 +28,7 @@

      [name]( [param:TypedArray array], [param:Integer itemSize], [param:Boolean n dove numVertices è il numero di vertici della [page:BufferGeometry BufferGeometry] associata.

      [page:Integer itemSize] -- il numero di valori dell'array che deve essere associato ad un particolare vertice. - Per esempio, se questo atttibuto memorizza un vettore a 3 componenti (come posizione, normale o colore), + Per esempio, se questo attributo memorizza un vettore a 3 componenti (come posizione, normale o colore), itemSize dovrebbe essere 3.

      [page:Boolean normalized] -- (opzionale) Si applica solo ai dati interi. Indica il modo in cui i dati sottostanti @@ -47,12 +47,19 @@

      [property:TypedArray array]

      [property:Integer count]

      - Memorizza la lunghezza dell'[page:BufferAttribute.array array] divisa per [page:BufferAttribute.itemSize itemSize].

      + Memorizza la lunghezza dell'[page:BufferAttribute.array array] divisa per [page:BufferAttribute.itemSize itemSize]. Read-only property.

      Se il buffer memorizza un vettore a 3 componenti (come una posizione, una normale o un colore), questo conterà il numero dei vettori memorizzati.

      +

      [property:Number gpuType]

      +

      + Configures the bound GPU type for use in shaders. Either [page:BufferAttribute THREE.FloatType] or [page:BufferAttribute THREE.IntType], default is [page:BufferAttribute THREE.FloatType]. + + Note: this only has an effect for integer arrays and is not configurable for float arrays. For lower precision float types, see [page:BufferAttributeTypes THREE.Float16BufferAttribute]. +

      +

      [property:Boolean isBufferAttribute]

      Flag di sola lettura per verificare se un dato oggetto è di tipo [name]. @@ -153,6 +160,9 @@

      [method:this copyArray]( array )

      [method:this copyAt] ( [param:Integer index1], [param:BufferAttribute bufferAttribute], [param:Integer index2] )

      Copia un vettore da bufferAttribute[index2] a [page:BufferAttribute.array array][index1].

      +

      [method:Number getComponent]( [param:Integer index], [param:Integer component] )

      +

      Returns the given component of the vector at the given index.

      +

      [method:Number getX]( [param:Integer index] )

      Restituisce il componente x del vettore in corrispondenza dell'indice specificato.

      @@ -191,6 +201,9 @@

      [method:this setUsage] ( [param:Usage value] )

      Nota: Dopo l'utilizzo iniziale di un buffer, il suo utilizzo non può cambiare. Invece, istanziane uno nuovo e imposta l'utilizzo desiderato prima del rendering successivo.

      + +

      [method:Number setComponent]( [param:Integer index], [param:Integer component], [param:Float value] )

      +

      Sets the given component of the vector at the given index.

      [method:this setX]( [param:Integer index], [param:Float x] )

      Imposta il componente x del vettore in corrispondenza dell'indice specificato.

      diff --git a/docs/api/it/core/Clock.html b/docs/api/it/core/Clock.html index 3d3aec55a15501..02a9e953aea441 100644 --- a/docs/api/it/core/Clock.html +++ b/docs/api/it/core/Clock.html @@ -10,8 +10,7 @@

      [name]

      - Oggetto per tenere traccia del tempo. Questa classe utilizza [link:https://developer.mozilla.org/en-US/docs/Web/API/Performance/now performance.now] - se disponibile, altrimenti utilizza il meno accurato [link:https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Date/now Date.now]. + Oggetto per tenere traccia del tempo. Questa classe utilizza [link:https://developer.mozilla.org/en-US/docs/Web/API/Performance/now performance.now].

      Costruttore

      diff --git a/docs/api/it/core/GLBufferAttribute.html b/docs/api/it/core/GLBufferAttribute.html index 799cfd40f81ceb..d9f3469c31f177 100644 --- a/docs/api/it/core/GLBufferAttribute.html +++ b/docs/api/it/core/GLBufferAttribute.html @@ -20,8 +20,13 @@

      [name]

      calcolo GPGPU interferisce o addirittura produce i VBO in questione.

      +

      Examples

      +

      + [example:webgl_buffergeometry_glbufferattribute Points with custom buffers]
      +

      +

      Costruttore

      -

      [name]( [param:WebGLBuffer buffer], [param:GLenum type], [param:Integer itemSize], [param:Integer elementSize], [param:Integer count] )

      +

      [name]( [param:WebGLBuffer buffer], [param:GLenum type], [param:Integer itemSize], [param:Integer elementSize], [param:Integer count], [param:Boolean normalized] )

      `buffer` — Deve essere un [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLBuffer WebGLBuffer].
      @@ -41,6 +46,15 @@

      [name]( [param:WebGLBuffer buffer], [param:GLenum type], [param:Integer item
    • gl.UNSIGNED_BYTE: 1
    `count` — Il numero previsto di vertici in VBO. +
    + `normalized` — (optional) Applies to integer data only. + Indicates how the underlying data in the buffer maps to the values in the + GLSL code. For instance, if [page:WebGLBuffer buffer] contains data of + `gl.UNSIGNED_SHORT`, and [page:Boolean normalized] is true, the values `0 - + +65535` in the buffer data will be mapped to 0.0f - +1.0f in the GLSL + attribute. A `gl.SHORT` (signed) would map from -32768 - +32767 to -1.0f + - +1.0f. If [page:Boolean normalized] is false, the values will be + converted to floats unmodified, i.e. 32767 becomes 32767.0f.

    Proprietà

    @@ -55,6 +69,14 @@

    [property:Integer count]

    Il numero previsto di vertici in VBO.

    +

    [property:Integer elementSize]

    +

    + Memorizza la dimensione corrispondente in byte per il valore della proprietà del `type` corrente. +

    +

    + Vedi sopra (costruttore) per un elenco di dimensioni di type conosciute. +

    +

    [property:Boolean isGLBufferAttribute]

    Solo lettura. Sempre `true`. @@ -65,17 +87,20 @@

    [property:Integer itemSize]

    Quanti valori compongono ogni elemento (vertice).

    -

    [property:Integer elementSize]

    +

    [property:String name]

    - Memorizza la dimensione corrispondente in byte per il valore della proprietà del `type` corrente. + Un nome opzionale per questa istanza dell'attributo. Il valore predefinito è una stringa vuota.

    + +

    [property:Boolean needsUpdate]

    - Vedi sopra (costruttore) per un elenco di dimensioni di type conosciute. + Il valore predefinito è `false`. Impostando questo metodo a true incrementa la [page:GLBufferAttribute.version versione].

    -

    [property:String name]

    +

    [property:Boolean normalized]

    - Un nome opzionale per questa istanza dell'attributo. Il valore predefinito è una stringa vuota. + Indicates how the underlying data in the buffer maps to the values in the + GLSL shader code. See the constructor above for details.

    [property:GLenum type]

    @@ -88,6 +113,11 @@

    [property:GLenum type]

    di usare il metodo `setType`.

    +

    [property:Integer version]

    +

    + Un numero di versione, incrementato ogni volta che la proprietà needsUpdate è impostata a true. +

    +

    Metodi

    [method:this setBuffer]( buffer )

    @@ -102,16 +132,6 @@

    [method:this setItemSize]( itemSize )

    [method:this setCount]( count )

    Imposta la proprietà `count`.

    -

    [property:Integer version]

    -

    - Un numero di versione, incrementato ogni volta che la proprietà needsUpdate è impostata a true. -

    - -

    [property:Boolean needsUpdate]

    -

    - Il valore predefinito è `false`. Impostando questo metodo a true incrementa la [page:GLBufferAttribute.version versione]. -

    -

    Source

    [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] diff --git a/docs/api/it/core/InterleavedBuffer.html b/docs/api/it/core/InterleavedBuffer.html index 0985f458868b16..3ba2c611c5e774 100644 --- a/docs/api/it/core/InterleavedBuffer.html +++ b/docs/api/it/core/InterleavedBuffer.html @@ -85,7 +85,7 @@

    [method:this copyAt]( [param:Integer index1], [param:InterleavedBuffer attri

    [method:this set]( [param:TypedArray value], [param:Integer offset] )

    value - L'array (tipizzato) di origine.
    - offset - L'offset nell'array di destinazione in corrrispondenza del quale iniziare a scrivere i valori dall'array di origine. L'impostazione predefinita è `0`.

    + offset - L'offset nell'array di destinazione in corrispondenza del quale iniziare a scrivere i valori dall'array di origine. L'impostazione predefinita è `0`.

    Memorizza più valori nel buffer, leggendo valori di input dall'array specificato.

    diff --git a/docs/api/it/core/Layers.html b/docs/api/it/core/Layers.html index bcb501e6238515..1f7a48aa04fe05 100644 --- a/docs/api/it/core/Layers.html +++ b/docs/api/it/core/Layers.html @@ -22,9 +22,7 @@

    [name]

    Esempi

    -

    - [example:webgl_layers WebGL / layers] -

    +

    [example:webgpu_layers WebGPU / layers]

    Costruttore

    diff --git a/docs/api/it/core/Object3D.html b/docs/api/it/core/Object3D.html index d79e8063ccc6c6..de49108ddc2ea4 100644 --- a/docs/api/it/core/Object3D.html +++ b/docs/api/it/core/Object3D.html @@ -128,6 +128,17 @@

    [property:Function onAfterRender]

    Invece, le istanze di [page:Object3D], [page:Group] o [page:Bone] non sono renderizzabili e quindi questa callback non viene eseguita su questi oggetti.

    +

    [property:Function onAfterShadow]

    +

    + An optional callback that is executed immediately after a 3D object is rendered to a shadow map. + Questa funzione è chiamata con i seguenti parametri: renderer, scene, camera, shadowCamera, geometry, depthMaterial, group. +

    +

    + Si noti che questa funzione di callback viene eseguita per oggi 3D `renderizzabili` (renderable). Ovvero oggetti 3D che definiscono + il loro aspetto visivo con geometrie e materiali come istanze di [page:Mesh], [page:Line], [page:Points] o [page:Sprite]. + Invece, le istanze di [page:Object3D], [page:Group] o [page:Bone] non sono renderizzabili e quindi questa callback non viene eseguita su questi oggetti. +

    +

    [property:Function onBeforeRender]

    Una callback opzionale che viene eseguita immediatamente prima che un oggetto 3D è stato renderizzato. @@ -139,6 +150,17 @@

    [property:Function onBeforeRender]

    Invece, le istanze di [page:Object3D], [page:Group] o [page:Bone] non sono renderizzabili e quindi questa callback non viene eseguita su questi oggetti.

    +

    [property:Function onBeforeShadow]

    +

    + An optional callback that is executed immediately before a 3D object is rendered to a shadow map. + Questa funzione è chiamata con i seguenti parametri: renderer, scene, camera, shadowCamera, geometry, depthMaterial, group. +

    +

    + Si noti che questa funzione di callback viene eseguita per oggi 3D `renderizzabili` (renderable). Ovvero oggetti 3D che definiscono + il loro aspetto visivo con geometrie e materiali come istanze di [page:Mesh], [page:Line], [page:Points] o [page:Sprite]. + Invece, le istanze di [page:Object3D], [page:Group] o [page:Bone] non sono renderizzabili e quindi questa callback non viene eseguita su questi oggetti. +

    +

    [property:Object3D parent]

    Genitore dell'oggetto nel [link:https://en.wikipedia.org/wiki/Scene_graph grafo della scena]. Un oggetto può avere più di un genitore.

    @@ -285,10 +307,13 @@

    [method:Object3D getObjectByProperty]( [param:String name], [param:Any value Cerca in un oggetto e nei suoi figli, partendo dall'oggetto stesso, e restituisce il primo con la proprietà che corrisponde al valore passato.

    -

    [method:Object3D getObjectsByProperty]( [param:String name], [param:Any value] )

    +

    [method:Object3D getObjectsByProperty]( [param:String name], [param:Any value], [param:Array optionalTarget] )

    name -- il nome della proprietà da cercare.
    - value -- il valore della proprietà data.

    + value -- il valore della proprietà data.
    + optionalTarget -- (opzionale) array dove impostare il risultato. + Altrimenti viene istanziato un nuovo Array. + Se impostato, è necessario cancellare questo array prima di ogni chiamata (ad esempio, array.length = 0;).

    Cerca in un oggetto e nei suoi figli, partendo dall'oggetto stesso, e restituisce tutti gli oggetti con la proprietà che corrisponde al valore passato.

    diff --git a/docs/api/it/core/Raycaster.html b/docs/api/it/core/Raycaster.html index 691b1862fde324..b6fdedda603d09 100644 --- a/docs/api/it/core/Raycaster.html +++ b/docs/api/it/core/Raycaster.html @@ -97,7 +97,7 @@

    [property:Float near]

    [property:Camera camera]

    - La telecamera da utilizzare durante il raycast contro oggetti dipendenti dalla vista, come oggetti su cartelloni pubblicitari + La telecamera da utilizzare durante il raycast contro oggetti dipendenti dalla vista, come oggetti su cartelloni pubblicitari come [page:Sprites]. Questo campo può essere settato manualmente o viene impostato quando si chiama il metodo "setFromCamera". L'impostazione predefinita è null. @@ -156,12 +156,18 @@

    [method:undefined setFromCamera]( [param:Vector2 coords], [param:Camera came Aggiorna il raggio con una nuova origine e direzione.

    +

    [method:this setFromXRController]( [param:WebXRController controller] )

    +

    + [page:WebXRController controller] — The controller to copy the position and direction from. +

    +

    Updates the ray with a new origin and direction.

    +

    [method:Array intersectObject]( [param:Object3D object], [param:Boolean recursive], [param:Array optionalTarget] )

    [page:Object3D object] — L'oggetto da verificare per l'intersezione con il raggio.
    - [page:Boolean recursive] — Se true, controlla anche tutti i discendenti. Altrimenti controlla soltanto + [page:Boolean recursive] — Se true, controlla anche tutti i discendenti. Altrimenti controlla soltanto l'intersezione con l'oggetto. Il valore predefinito è true.
    - [page:Array optionalTarget] — (opzionale) obiettivo per impostare il risultato. + [page:Array optionalTarget] — (opzionale) obiettivo per impostare il risultato. Altrimenti viene istanziato un nuovo [page:Array]. Se impostato, è necessario cancellare questo array prima di ogni chiamata (ad esempio, array.length = 0;).

    @@ -183,8 +189,8 @@

    [method:Array intersectObject]( [param:Object3D object], [param:Boolean recu [page:Integer instanceId] – Il numero di indice dell'istanza in cui il raggio interseca la InstancedMesh.

    - `Raycaster` delega al metodo [page:Object3D.raycast raycast] dell'oggetto passato, - quando valuta se il raggio interseca l'oggetto o no. Ciò permette alle mesh di rispondere + `Raycaster` delega al metodo [page:Object3D.raycast raycast] dell'oggetto passato, + quando valuta se il raggio interseca l'oggetto o no. Ciò permette alle mesh di rispondere in modo diverso al raycasting rispetto alle linee e alle nuvole di punti.

    @@ -197,14 +203,14 @@

    [method:Array intersectObject]( [param:Object3D object], [param:Boolean recu

    [method:Array intersectObjects]( [param:Array objects], [param:Boolean recursive], [param:Array optionalTarget] )

    [page:Array objects] — Gli oggetti da controllare per l'intersezione con il raggio.
    - [page:Boolean recursive] — Se true, controlla anche i discendenti degli oggetti. Altrimenti controlla soltanto + [page:Boolean recursive] — Se true, controlla anche i discendenti degli oggetti. Altrimenti controlla soltanto l'intersezione con gli oggetti. Il valore predefinito è true.
    [page:Array optionalTarget] — (opzionale) obiettivo per impostare il risultato. Altrimenti viene istanziato un nuovo [page:Array]. Se impostato, è necessario cancellare questo array prima di ogni chiamata (ad esempio, array.length = 0;).

    Controlla tutte le intersezioni tra il raggio e gli oggetti con o senza i discendenti. - Le intersezioni sono restituite ordinate per distanza, prima le più vicine. Le intersezioni + Le intersezioni sono restituite ordinate per distanza, prima le più vicine. Le intersezioni hanno la stessa forma di quelle restituite da [page:.intersectObject].

    diff --git a/docs/api/it/core/Uniform.html b/docs/api/it/core/Uniform.html index 997e0fd3f15412..60011c873f59d5 100644 --- a/docs/api/it/core/Uniform.html +++ b/docs/api/it/core/Uniform.html @@ -50,7 +50,7 @@

    Tipi Uniform

    [page:Number] - uint (WebGL 2) + uint [page:Number] diff --git a/docs/api/it/core/bufferAttributeTypes/BufferAttributeTypes.html b/docs/api/it/core/bufferAttributeTypes/BufferAttributeTypes.html index 21be170adc4d61..9c44f6e18005e7 100644 --- a/docs/api/it/core/bufferAttributeTypes/BufferAttributeTypes.html +++ b/docs/api/it/core/bufferAttributeTypes/BufferAttributeTypes.html @@ -17,7 +17,6 @@

    Tipi di BufferAttribute

    - THREE.Float64BufferAttribute THREE.Float32BufferAttribute THREE.Float16BufferAttribute THREE.Uint32BufferAttribute diff --git a/docs/api/it/extras/core/CurvePath.html b/docs/api/it/extras/core/CurvePath.html index 0a8a011d3bc885..76654091c1fbfa 100644 --- a/docs/api/it/extras/core/CurvePath.html +++ b/docs/api/it/extras/core/CurvePath.html @@ -38,7 +38,7 @@

    Metodi

    [method:undefined add]( [param:Curve curve] )

    Aggiunge una curva all'array [page:.curves].

    -

    [method:undefined closePath]()

    +

    [method:this closePath]()

    Aggiunge una [page:LineCurve lineCurve] per chiudere il percorso.

    [method:Array getCurveLengths]()

    diff --git a/docs/api/it/extras/core/Shape.html b/docs/api/it/extras/core/Shape.html index 09390a5d5f9d33..09ac0b0b1aa9ea 100644 --- a/docs/api/it/extras/core/Shape.html +++ b/docs/api/it/extras/core/Shape.html @@ -40,8 +40,7 @@

    Esempi

    [example:webgl_geometry_shapes geometry / shapes ]
    - [example:webgl_geometry_extrude_shapes geometry / extrude / shapes ]
    - [example:webgl_geometry_extrude_shapes2 geometry / extrude / shapes2 ]
    + [example:webgl_geometry_extrude_shapes geometry / extrude / shapes ]

    diff --git a/docs/api/it/extras/core/ShapePath.html b/docs/api/it/extras/core/ShapePath.html index 541f766361a61a..fa7185d27a207b 100644 --- a/docs/api/it/extras/core/ShapePath.html +++ b/docs/api/it/extras/core/ShapePath.html @@ -11,12 +11,7 @@

    [name]

    Questa classe viene utilizzata per convertire una serie di forme in un array di [page:Path], ad esempio una - forma SVG in un path (vedere l'esempio seguente). -

    - -

    Esempi

    -

    - [example:webgl_geometry_extrude_shapes2 geometry / extrude / shapes2] + forma SVG in un path.

    Costruttore

    diff --git a/docs/api/it/geometries/CapsuleGeometry.html b/docs/api/it/geometries/CapsuleGeometry.html index 944b9008df5f0b..de13e2ed47b476 100644 --- a/docs/api/it/geometries/CapsuleGeometry.html +++ b/docs/api/it/geometries/CapsuleGeometry.html @@ -44,13 +44,14 @@

    Codice di Esempio

    Costruttore

    -

    [name]([param:Float radius], [param:Float length], [param:Integer capSubdivisions], [param:Integer radialSegments])

    +

    [name]([param:Float radius], [param:Float height], [param:Integer capSegments], [param:Integer radialSegments], [param:Integer heightSegments])

    radius — Raggio della capsula. Opzionale; il valore predefinito è 1.
    - length — Lunghezza della sezione centrale. Opzionale; il valore predefinito è 1.
    + height — Altezza della sezione centrale. Opzionale; il valore predefinito è 1.
    capSegments — Numero di segmenti curvi utilizzato per costruire i tappi della capsula. Opzionale; il valore predefinito è 4.
    radialSegments — Numero di facce segmentate attorno alla circonferenza della capsula. Opzionale; il valore predefinito è 8.
    + heightSegments — Numero di facce rettangolari segmentate sull'altezza della capsula. Opzionale; il valore predefinito è 1.

    Proprietà

    diff --git a/docs/api/it/helpers/DirectionalLightHelper.html b/docs/api/it/helpers/DirectionalLightHelper.html index 328ecac81845b4..9db24732916f87 100644 --- a/docs/api/it/helpers/DirectionalLightHelper.html +++ b/docs/api/it/helpers/DirectionalLightHelper.html @@ -20,6 +20,8 @@

    Codice di Esempio

    const light = new THREE.DirectionalLight( 0xFFFFFF ); + scene.add( light ); + const helper = new THREE.DirectionalLightHelper( light, 5 ); scene.add( helper ); diff --git a/docs/api/it/helpers/GridHelper.html b/docs/api/it/helpers/GridHelper.html index 8cbff06639f347..9ad8a50b0e9a17 100644 --- a/docs/api/it/helpers/GridHelper.html +++ b/docs/api/it/helpers/GridHelper.html @@ -7,7 +7,7 @@ - [page:Object3D] → [page:Line] → + [page:Object3D] → [page:Line] → [page:LineSegments] →

    [name]

    diff --git a/docs/api/it/helpers/PolarGridHelper.html b/docs/api/it/helpers/PolarGridHelper.html index 9ad435bdc0b329..3768d5cb4999c5 100644 --- a/docs/api/it/helpers/PolarGridHelper.html +++ b/docs/api/it/helpers/PolarGridHelper.html @@ -7,7 +7,7 @@ - [page:Object3D] → [page:Line] → + [page:Object3D] → [page:Line] → [page:LineSegments] →

    [name]

    diff --git a/docs/api/it/lights/AmbientLightProbe.html b/docs/api/it/lights/AmbientLightProbe.html deleted file mode 100644 index dfd9de3fc13c3e..00000000000000 --- a/docs/api/it/lights/AmbientLightProbe.html +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - [page:Object3D] → [page:Light] → [page:LightProbe] - -

    [name]

    - -

    - Le sonde luminose sono un modo alternativo per aggiungere luce a una scena 3D. AmbientLightProbe è il dato - di stima della luce di una singola luce ambientale nella scena. Per ulteriori informazioni sulle sonde luminose, - consultare [page:LightProbe]. -

    - -

    Costruttore

    - -

    [name]( [param:Color color], [param:Float intensity] )

    -

    - [page:Color color] - (opzionale) Un'istanza di Color, una stringa che rappresenta un colore o un numero che rappresenta un colore.
    - [page:Float intensity] - (opzionale) Valore numerico dell'intensità della sonda luminosa. Il valore predefinito è 1.

    - - Crea una nuova [name]. -

    - -

    Proprietà

    -

    - Vedi la classe base [page:LightProbe LightProbe] per le proprietà comuni. -

    - -

    [property:Boolean isAmbientLightProbe]

    -

    - Flag di sola lettura per verificare se l'oggetto dato è del tipo [name]. -

    - -

    Metodi

    -

    - Vedi la classe base [page:LightProbe LightProbe] per i metodi comuni. -

    - -

    Source

    - -

    - [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] -

    - - diff --git a/docs/api/it/lights/HemisphereLightProbe.html b/docs/api/it/lights/HemisphereLightProbe.html deleted file mode 100644 index 934a95e2daca4b..00000000000000 --- a/docs/api/it/lights/HemisphereLightProbe.html +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - [page:Object3D] → [page:Light] → [page:LightProbe] - -

    [name]

    - -

    - Le sonde luminose sono un modo alternativo di aggiungere luce alla scena 3D. HemisphereLightProbe è i dati di stima della - luce di un singolo emisfero luminoso nella scena. Per ulteriori informazioni sulle sonde di luce, consultare [page:LightProbe]. -

    - -

    Costruttore

    - -

    [name]( [param:Color skyColor], [param:Color groundColor], [param:Float intensity] )

    -

    - [page:Color skyColor] - (opzionale) Un'istanza di Color, stringa che rappresenta un colore o un numero che rappresenta un colore.
    - [page:Color groundColor] - (opzionale) Un'istanza di Color, stringa che rappresenta un colore o un numero che rappresenta un colore.
    - [page:Float intensity] - (opzionale) Valore numerico dell'intesità della sonda di luce. Il valore predefinito è 1.

    - - Crea una nuova [name]. -

    - -

    Proprietà

    -

    - Vedi la classe base [page:LightProbe LightProbe] per le proprietà comuni. -

    - - -

    [property:Boolean isHemisphereLightProbe]

    -

    - Flag di sola lettura per verificare se l'oggetto dato è del tipo [name]. -

    - -

    Metodi

    -

    - Vedi la classe base [page:LightProbe LightProbe] per i metodi comuni. -

    - -

    Source

    - -

    - [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] -

    - - diff --git a/docs/api/it/lights/Light.html b/docs/api/it/lights/Light.html index 1885ffa6d88bb6..35c6ca47ff978c 100644 --- a/docs/api/it/lights/Light.html +++ b/docs/api/it/lights/Light.html @@ -40,7 +40,7 @@

    [property:Color color]

    [property:Float intensity]

    L'intensità o la forza della luce.
    - Quando [page:WebGLRenderer.useLegacyLights legacy lighting mode] è disabilitata, le unità di intensità dipendono dal tipo di luce.
    + Le unità di intensità dipendono dal tipo di luce.
    Il valore predefinito è `1.0`.

    diff --git a/docs/api/it/lights/PointLight.html b/docs/api/it/lights/PointLight.html index e80e14be8b7ee7..0d4dc40c46e712 100644 --- a/docs/api/it/lights/PointLight.html +++ b/docs/api/it/lights/PointLight.html @@ -29,7 +29,7 @@

    Codice di Esempio

    Esempi

    - [example:webgl_lights_pointlights lights / pointlights ]
    + [example:webgpu_lights_pointlights lights / pointlights ]
    [example:webgl_effects_anaglyph effects / anaglyph ]
    [example:webgl_geometry_text geometry / text ]
    [example:webgl_lensflares lensflares ] @@ -68,16 +68,10 @@

    [property:Float decay]

    [property:Float distance]

    - `Modalità predefinita` — Quando la distanza è zero, la luce non si attenua. Quando la distanza è diversa da zero, - la luce si attenuerà linearmente dalla massima intensità nella posizione della luce fino a zero a questa distanza - dalla luce. -

    -

    - Quando [page:WebGLRenderer.useLegacyLights legacy lighting mode] è disabilitata — Quando la distanza è - zero, la luce si attenuerà secondo la legge dell'inverso del quadrato alla distanza infinita. - Quando la distanza è diversa da zero, la luce si attenuerà secondo la legge dell'inverso del quadrato - fino in prossimità del limite di distanza, dove si attenuerà quindi rapidamente e senza intoppi fino a 0. - Intrinsecamente, i limiti non sono fisicamente corretti. + Quando la distanza è zero, la luce si attenuerà secondo la legge dell'inverso del quadrato alla distanza infinita. + Quando la distanza è diversa da zero, la luce si attenuerà secondo la legge dell'inverso del quadrato + fino in prossimità del limite di distanza, dove si attenuerà quindi rapidamente e senza intoppi fino a 0. + Intrinsecamente, i limiti non sono fisicamente corretti.

    Il valore predefinito è `0.0`. @@ -86,8 +80,7 @@

    [property:Float distance]

    [property:Float intensity]

    L'intensità della luce. Il valore predefinito è `1`.
    - Quando [page:WebGLRenderer.useLegacyLights legacy lighting mode] è disabilitata, l'intensità - è l'intensità luminosa della luce misurata in candela (cd).

    + L'intensità è l'intensità luminosa della luce misurata in candela (cd).

    Modificando l'intensità si modificherà anche la potenza della luce.

    @@ -95,8 +88,7 @@

    [property:Float intensity]

    [property:Float power]

    La potenza della luce.
    - Quando [page:WebGLRenderer.useLegacyLights legacy lighting mode] è disabilitata, la potenza è la potenza - della luminosità della luce misurata in lumen (lm).

    + La potenza è la potenza della luminosità della luce misurata in lumen (lm).

    Modificando la potenza si modificherà anche l'intensità della luce.

    diff --git a/docs/api/it/lights/RectAreaLight.html b/docs/api/it/lights/RectAreaLight.html index bdbcfcf5232f3f..0f3385a3e06487 100644 --- a/docs/api/it/lights/RectAreaLight.html +++ b/docs/api/it/lights/RectAreaLight.html @@ -70,8 +70,7 @@

    [property:Float height]

    [property:Float intensity]

    L'intensità della luce. Il valore predefinito è `1`.
    - Quando [page:WebGLRenderer.useLegacyLights legacy lighting mode] è disabilitata, l'intesità è la luminanza - (luminosità) della luce misurata in nits (cd/m^2).

    + L'intesità è la luminanza (luminosità) della luce misurata in nits (cd/m^2).

    Modificando l'intensità si modificherà anche la potenza della luce.

    @@ -84,8 +83,7 @@

    [property:Boolean isRectAreaLight]

    [property:Float power]

    La potenza della luce.
    - Quando [page:WebGLRenderer.useLegacyLights legacy lighting mode] è disabilitata, la potenza è la potenza - della luminosità della luce misurata in lumen (lm).

    + La potenza è la potenza della luminosità della luce misurata in lumen (lm).

    Modificando la potenza si modificherà anche l'intensità della luce.

    diff --git a/docs/api/it/lights/SpotLight.html b/docs/api/it/lights/SpotLight.html index 43a7d4b15583e6..9b220c6b8f7ab1 100644 --- a/docs/api/it/lights/SpotLight.html +++ b/docs/api/it/lights/SpotLight.html @@ -88,13 +88,7 @@

    [property:Float decay]

    [property:Float distance]

    - `Modalità predefinita` — Quando la distanza è zero, la luce non si attenua. Quando la distanza è diversa da zero, - la luce si attenuerà linearmente dalla massima intensità nella posizione della luce fino a zero a questa distanza - dalla luce. -

    -

    - Quando [page:WebGLRenderer.useLegacyLights legacy lighting mode] è disabilitata — Quando la distanza è - zero, la luce si attenuerà secondo la legge dell'inverso del quadrato alla distanza infinita. + Quando la distanza è zero, la luce si attenuerà secondo la legge dell'inverso del quadrato alla distanza infinita. Quando la distanza è diversa da zero, la luce si attenuerà secondo la legge dell'inverso del quadrato fino in prossimità del limite di distanza, dove si attenuerà quindi rapidamente e uniformemente fino a `0`. Intrinsecamente, i limiti non sono fisicamente corretti. @@ -106,8 +100,7 @@

    [property:Float distance]

    [property:Float intensity]

    L'intensità della luce. Il valore predefinito è `1`.
    - Quando [page:WebGLRenderer.useLegacyLights legacy lighting mode] è disabilitata, l'intensità - è l'intensità luminosa della luce misurata in candela (cd).

    + L'intensità è l'intensità luminosa della luce misurata in candela (cd).

    Modificando l'intensità si modificherà anche la potenza della luce.

    @@ -132,8 +125,7 @@

    [property:Float power]

    La potenza della luce.
    - Quando [page:WebGLRenderer.useLegacyLights legacy lighting mode] è disabilitata, la potenza è la potenza - della luminosità della luce misurata in lumen (lm).

    + La potenza è la potenza della luminosità della luce misurata in lumen (lm).

    Modificando la potenza si modificherà anche l'intensità della luce.

    @@ -174,7 +166,7 @@

    [property:Texture map]

    Una [page:Texture] utilizzata per modulare il colore della luce. Il colore della luce spot viene mescolato con il valore RGB di questa texture, con un rapporto corrispondente al suo valore alfa. L'effetto di mascheramento simile a quello dei cookie viene riprodotto utilizzando i valori (0, 0, 0, 1-cookie_value). - *Attenzione*: [param:SpotLight map] è disabilitata se [param:SpotLight castShadow] è *false*. + *Attenzione*: [page:.map] è disabilitata se [page:.castShadow] è *false*.

    Metodi

    diff --git a/docs/api/it/lights/shadows/LightShadow.html b/docs/api/it/lights/shadows/LightShadow.html index 0f8111c47b28a4..be095d88f1d1ae 100644 --- a/docs/api/it/lights/shadows/LightShadow.html +++ b/docs/api/it/lights/shadows/LightShadow.html @@ -50,6 +50,11 @@

    [property:Integer blurSamples]

    La quantità di campioni da utilizzare durante la sfocatura di una mappa ombra VSM.

    +

    [property:Float intensity]

    +

    + The intensity of the shadow. The default is `1`. Valid values are in the range `[0, 1]`. +

    +

    [property:WebGLRenderTarget map]

    La mappa di profondità generata usando la telecamera interna; una posizione oltre la profondità di un pixel è in ombra. diff --git a/docs/api/it/loaders/BufferGeometryLoader.html b/docs/api/it/loaders/BufferGeometryLoader.html index dcaff410ab02f3..d9cd60f2883802 100644 --- a/docs/api/it/loaders/BufferGeometryLoader.html +++ b/docs/api/it/loaders/BufferGeometryLoader.html @@ -46,12 +46,6 @@

    Codice di Esempio

    );
    -

    Esempi

    - -

    - [example:webgl_performance WebGL / performance] -

    -

    Costruttore

    [name]( [param:LoadingManager manager] )

    diff --git a/docs/api/it/loaders/CubeTextureLoader.html b/docs/api/it/loaders/CubeTextureLoader.html index 21cacfd5b94f63..fa269891be6c87 100644 --- a/docs/api/it/loaders/CubeTextureLoader.html +++ b/docs/api/it/loaders/CubeTextureLoader.html @@ -16,6 +16,11 @@

    [name]

    Utilizza internamente l'[page:ImageLoader] per caricare i file.

    +

    + The loaded [page:CubeTexture] is in sRGB color space. Meaning the [page:Texture.colorSpace colorSpace] + property is set to `THREE.SRGBColorSpace` by default. +

    +

    Codice di Esempio

    diff --git a/docs/api/it/loaders/ImageBitmapLoader.html b/docs/api/it/loaders/ImageBitmapLoader.html index 1e0af5c28b7ace..b66f9ef5079728 100644 --- a/docs/api/it/loaders/ImageBitmapLoader.html +++ b/docs/api/it/loaders/ImageBitmapLoader.html @@ -38,7 +38,7 @@

    Codice di Esempio

    // carica un'immagine loader.load( // URL della risorsa - 'textures/skyboxsun25degtest.png', + 'image.png', // onLoad callback function ( imageBitmap ) { diff --git a/docs/api/it/loaders/ImageLoader.html b/docs/api/it/loaders/ImageLoader.html index e944c4f6d3fb96..83d7be039910ca 100644 --- a/docs/api/it/loaders/ImageLoader.html +++ b/docs/api/it/loaders/ImageLoader.html @@ -26,7 +26,7 @@

    Codice di Esempio

    // carica un'immagine loader.load( // URL della risorsa - 'textures/skyboxsun25degtest.png', + 'image.png', // onLoad callback function ( image ) { diff --git a/docs/api/it/loaders/LoaderUtils.html b/docs/api/it/loaders/LoaderUtils.html index 9280d9071e8b0e..e9c6fa3fd39c71 100644 --- a/docs/api/it/loaders/LoaderUtils.html +++ b/docs/api/it/loaders/LoaderUtils.html @@ -13,14 +13,6 @@

    [name]

    Funzioni

    -

    [method:String decodeText]( [param:TypedArray array] )

    -

    - [page:TypedArray array] — Uno stream di byte come array tipizzato. -

    -

    - La funzione prende uno stream di byte in input e restituisce una rappresentazione di stringa. -

    -

    [method:String extractUrlBase]( [param:String url] )

    [page:String url] — La url da cui estrarre la url di base. diff --git a/docs/api/it/loaders/ObjectLoader.html b/docs/api/it/loaders/ObjectLoader.html index 57ceb13f45337b..eefe5e2d20af8b 100644 --- a/docs/api/it/loaders/ObjectLoader.html +++ b/docs/api/it/loaders/ObjectLoader.html @@ -56,7 +56,7 @@

    Codice di Esempio

    Esempi

    - [example:webgl_materials_lightmap WebGL / materials / lightmap] + [example:webgpu_materials_lightmap WebGL / materials / lightmap]

    Costruttore

    diff --git a/docs/api/it/loaders/managers/LoadingManager.html b/docs/api/it/loaders/managers/LoadingManager.html index 0f750dd83382d3..dac8c972cd905f 100644 --- a/docs/api/it/loaders/managers/LoadingManager.html +++ b/docs/api/it/loaders/managers/LoadingManager.html @@ -51,7 +51,7 @@

    Codice di Esempio

    }; - const loader = new THREE.OBJLoader( manager ); + const loader = new OBJLoader( manager ); loader.load( 'file.obj', function ( object ) { // @@ -86,7 +86,7 @@

    Codice di Esempio

    } ); // Carica come di solito, quindi revoca gli URL dei Blob - const loader = new THREE.GLTFLoader( manager ); + const loader = new GLTFLoader( manager ); loader.load( 'fish.gltf', (gltf) => { scene.add( gltf.scene ); @@ -100,7 +100,6 @@

    Esempi

    [example:webgl_loader_obj WebGL / loader / obj]
    - [example:webgl_materials_physical_reflectivity WebGL / materials / physical / reflectivity]
    [example:webgl_postprocessing_outline WebGL / postprocesing / outline]

    diff --git a/docs/api/it/materials/LineBasicMaterial.html b/docs/api/it/materials/LineBasicMaterial.html index 7fc7892cacafa0..4ad5cb54d8a6d3 100644 --- a/docs/api/it/materials/LineBasicMaterial.html +++ b/docs/api/it/materials/LineBasicMaterial.html @@ -38,8 +38,6 @@

    Esempi

    [example:webgl_interactive_voxelpainter WebGL / interactive / voxelpainter]
    [example:webgl_lines_colors WebGL / lines / colors]
    [example:webgl_lines_dashed WebGL / lines / dashed]
    - [example:webgl_lines_sphere WebGL / lines / sphere]
    - [example:webgl_materials WebGL / materials]
    [example:physics_ammo_rope physics / ammo / rope]

    diff --git a/docs/api/it/materials/LineDashedMaterial.html b/docs/api/it/materials/LineDashedMaterial.html index 5cf26ee88bac76..462977168c3d58 100644 --- a/docs/api/it/materials/LineDashedMaterial.html +++ b/docs/api/it/materials/LineDashedMaterial.html @@ -11,7 +11,10 @@

    [name]

    -

    Un materiale per disegnare geometrie in stile wireframe con linee tratteggiate.

    +

    + Un materiale per disegnare geometrie in stile wireframe con linee tratteggiate.
    + Note: You must call [page:Line.computeLineDistances]() when using [name]. +

    Codice di Esempio

    diff --git a/docs/api/it/materials/Material.html b/docs/api/it/materials/Material.html index 00c139e6d7aa73..77ff678b5a0030 100644 --- a/docs/api/it/materials/Material.html +++ b/docs/api/it/materials/Material.html @@ -29,6 +29,14 @@

    [name]()

    Proprietà

    +

    [property:Boolean alphaHash]

    +

    + Enables alpha hashed transparency, an alternative to [page:.transparent] or [page:.alphaTest]. + The material will not be rendered if opacity is lower than a random threshold. + Randomization introduces some grain or noise, but approximates alpha blending without + the associated problems of sorting. Using TAARenderPass can reduce the resulting noise. +

    +

    [property:Float alphaTest]

    Imposta il valore alfa per essere usato quando vengono eseguiti i test alfa. @@ -365,6 +373,28 @@

    [method:undefined onBeforeCompile]( [param:Shader shader], [param:WebGLRende

    A differenza delle proprietà, la callback non è supportata da [page:Material.clone .clone](), [page:Material.copy .copy]() e [page:Material.toJSON .toJSON]().

    +

    + This callback is only supported in `WebGLRenderer` (not `WebGPURenderer`). +

    +

    + [example:webgl_materials_modified WebGL / materials / modified]
    + [example:webgl_shadow_contact WebGL / shadow / contact] +

    + +

    + [method:undefined onBeforeRender]( [param:WebGLRenderer renderer], [param:Scene scene], [param:Camera camera], [param:BufferGeometry geometry], [param:Object3D object], [param:Group group] ) +

    +

    + An optional callback that is executed immediately before the material is used to + render a 3D object. +

    +

    + Unlike properties, the callback is not supported by [page:Material.clone .clone](), + [page:Material.copy .copy]() and [page:Material.toJSON .toJSON](). +

    +

    + This callback is only supported in `WebGLRenderer` (not `WebGPURenderer`). +

    [method:String customProgramCacheKey]()

    diff --git a/docs/api/it/materials/MeshDepthMaterial.html b/docs/api/it/materials/MeshDepthMaterial.html index 6007e4a419e242..609bc75ae4ea17 100644 --- a/docs/api/it/materials/MeshDepthMaterial.html +++ b/docs/api/it/materials/MeshDepthMaterial.html @@ -84,9 +84,6 @@

    [property:Float displacementBias]

    non viene applicato. Il valore predefinito è 0.

    -

    [property:Boolean fog]

    -

    Indica se il materiale è influenzato dalla nebbia. Il valore predefinito è `false`.

    -

    [property:Texture map]

    La mappa colore. Può includere facoltativamente un canale alfa, tipicamente combinato con diff --git a/docs/api/it/materials/MeshDistanceMaterial.html b/docs/api/it/materials/MeshDistanceMaterial.html index 12cd31b1fa6617..5ee5ff935637f6 100644 --- a/docs/api/it/materials/MeshDistanceMaterial.html +++ b/docs/api/it/materials/MeshDistanceMaterial.html @@ -87,9 +87,6 @@

    [property:Float displacementBias]

    non viene applicato. Il valore predefinito è 0.

    -

    [property:Boolean fog]

    -

    Indica se il materiale è influenzato dalla nebbia. Il valore predefinito è `false`.

    -

    [property:Texture map]

    La mappa colore. Può includere facoltativamente un canale alfa, tipicamente combinato con diff --git a/docs/api/it/materials/MeshNormalMaterial.html b/docs/api/it/materials/MeshNormalMaterial.html index abcecf3bcb3673..2d8e83736db452 100644 --- a/docs/api/it/materials/MeshNormalMaterial.html +++ b/docs/api/it/materials/MeshNormalMaterial.html @@ -82,9 +82,6 @@

    [property:Boolean flatShading]

    Definisce se il materiale viene renderizzato con un'ombreggiatura piatta. Il valore predefinito è `false`.

    -

    [property:Boolean fog]

    -

    Indica se il materiale è influenzato dalla nebbia. Il valore predefinito è `false`.

    -

    [property:Texture normalMap]

    La texture per creare una mappa normale. I valori RGB influenzano la normale della superficie per ogni frammento di pixel diff --git a/docs/api/it/materials/MeshPhysicalMaterial.html b/docs/api/it/materials/MeshPhysicalMaterial.html index e38648f3d1542e..2a5d842570ddf9 100644 --- a/docs/api/it/materials/MeshPhysicalMaterial.html +++ b/docs/api/it/materials/MeshPhysicalMaterial.html @@ -63,9 +63,7 @@

    [name]

    Esempi

    - [example:webgl_materials_variations_physical materials / variations / physical]
    [example:webgl_materials_physical_clearcoat materials / physical / clearcoat]
    - [example:webgl_materials_physical_reflectivity materials / physical / reflectivity]
    [example:webgl_loader_gltf_sheen loader / gltf / sheen]
    [example:webgl_materials_physical_transmission materials / physical / transmission]

    @@ -138,6 +136,14 @@

    [property:Object defines]

    Questo viene utilizzato dal [page:WebGLRenderer] per selezionare gli shader.

    +

    [property:Float dispersion]

    +

    + Defines the strength of the angular separation of colors (chromatic aberration) transmitting through a relatively clear volume. + Any value zero or larger is valid, the typical range of realistic values is `[0, 1]`. + Default is `0` (no dispersion). + This property can be only be used with transmissive objects, see [page:.transmission]. +

    +

    [property:Float ior]

    Indice di rifrazione per materiali non metallici, da `1.0` a `2.333`. Il valore predefinito è `1.5`.
    @@ -168,7 +174,7 @@

    [property:Texture sheenRoughnessMap]

    [property:Color sheenColor]

    - La tinta brillante. Il valore predefinito è `0xffffff`, bianco. + La tinta brillante. Il valore predefinito è `0x000000`, nera.

    [property:Texture sheenColorMap]

    @@ -180,7 +186,7 @@

    [property:Texture sheenColorMap]

    [property:Float specularIntensity]

    Un float che ridimensiona la quantità di riflessione speculare solo per i non metalli. Quando è impostato su zero, il modello - è effettivamente Lambertiano. Da `0.0` a `1.0`. Il valore predefinito è `0.0`. + è effettivamente Lambertiano. Da `0.0` a `1.0`. Il valore predefinito è `1.0`.

    [property:Texture specularIntensityMap]

    @@ -221,7 +227,7 @@

    [property:Float transmission]

    riflettenti anche se sono completamente trasmissivi. La proprietà di trasmissione può essere utilizzata per modellare questi materiali.
    - Quando la trasmissione è diversa da zero, l'[page:Material.opacity] deve essere impostata a `0`. + Quando la trasmissione è diversa da zero, l'[page:Material.opacity] deve essere impostata a `1`.

    [property:Texture transmissionMap]

    diff --git a/docs/api/it/materials/MeshToonMaterial.html b/docs/api/it/materials/MeshToonMaterial.html index 7c398a58411976..607f2ed557d0f9 100644 --- a/docs/api/it/materials/MeshToonMaterial.html +++ b/docs/api/it/materials/MeshToonMaterial.html @@ -33,7 +33,7 @@

    [name]

    Esempi

    - [example:webgl_materials_variations_toon materials / variations / toon] + [example:webgl_materials_toon materials / toon]

    Costruttore

    diff --git a/docs/api/it/materials/PointsMaterial.html b/docs/api/it/materials/PointsMaterial.html index 3abe4d6d07e492..a692ecd1acf115 100644 --- a/docs/api/it/materials/PointsMaterial.html +++ b/docs/api/it/materials/PointsMaterial.html @@ -50,8 +50,7 @@

    Esempi

    [example:webgl_multiple_elements_text WebGL / multiple / elements / text]
    [example:webgl_points_billboards WebGL / points / billboards]
    [example:webgl_points_dynamic WebGL / points / dynamic]
    - [example:webgl_points_sprites WebGL / points / sprites]
    - [example:webgl_trails WebGL / trails] + [example:webgl_points_sprites WebGL / points / sprites]

    Costruttore

    diff --git a/docs/api/it/materials/RawShaderMaterial.html b/docs/api/it/materials/RawShaderMaterial.html index 32f3aa69f20613..f6eb750fdb747a 100644 --- a/docs/api/it/materials/RawShaderMaterial.html +++ b/docs/api/it/materials/RawShaderMaterial.html @@ -35,10 +35,9 @@

    Esempi

    [example:webgl_buffergeometry_rawshader WebGL / buffergeometry / rawshader]
    [example:webgl_buffergeometry_instancing_billboards WebGL / buffergeometry / instancing / billboards]
    [example:webgl_buffergeometry_instancing WebGL / buffergeometry / instancing]
    - [example:webgl_raymarching_reflect WebGL / raymarching / reflect]
    - [example:webgl2_volume_cloud WebGL 2 / volume / cloud]
    - [example:webgl2_volume_instancing WebGL 2 / volume / instancing]
    - [example:webgl2_volume_perlin WebGL 2 / volume / perlin] + [example:webgl_volume_cloud WebGL / volume / cloud]
    + [example:webgl_volume_instancing WebGL / volume / instancing]
    + [example:webgl_volume_perlin WebGL / volume / perlin]

    Costruttore

    diff --git a/docs/api/it/materials/ShaderMaterial.html b/docs/api/it/materials/ShaderMaterial.html index b752637ff77696..7e73ba700145ab 100644 --- a/docs/api/it/materials/ShaderMaterial.html +++ b/docs/api/it/materials/ShaderMaterial.html @@ -109,7 +109,6 @@

    Esempi

    [example:webgl_lights_hemisphere webgl / lights / hemisphere]
    [example:webgl_marchingcubes webgl / marchingcubes]
    [example:webgl_materials_envmaps webgl / materials / envmaps]
    - [example:webgl_materials_lightmap webgl / materials / lightmap]
    [example:webgl_materials_wireframe webgl / materials / wireframe]
    [example:webgl_modifier_tessellation webgl / modifier / tessellation]
    [example:webgl_postprocessing_dof2 webgl / postprocessing / dof2]
    @@ -318,10 +317,8 @@

    [property:Object extensions]

    Un oggetto con le seguenti proprietà: this.extensions = { - derivatives: false, // impostato per utilizzare le direttive - fragDepth: false, // impostato per utilizzare i valori di profondità del frammento - drawBuffers: false, // impostato per utilizzare i buffer di disegno - shaderTextureLOD: false // impostato per utilizzare la texture dello shader LOD + clipCullDistance: false, // set to use vertex shader clipping + multiDraw: false // set to use vertex shader multi_draw / enable gl_DrawID };

    @@ -343,8 +340,8 @@

    [property:String fragmentShader]

    [property:String glslVersion]

    - Definisce la versione GLSL del codice dello shader personalizzato. Rilevante solo per WebGL 2 per definire se - specificare o meno GLSL 3.0. I valori validi sono `THREE.GLSL1` o `THREE.GLSL3`. Il valore predefinito è `null`. + Definisce la versione GLSL del codice dello shader personalizzato. I valori validi sono `THREE.GLSL1` o `THREE.GLSL3`. + Il valore predefinito è `null`.

    [property:String index0AttributeName]

    @@ -428,7 +425,7 @@

    [property:Float wireframeLinewidth]

    Metodi

    Vedi la classe base [page:Material] per i metodi comuni.

    -

    [method:ShaderMaterial clone]() [param:ShaderMaterial this]

    +

    [method:ShaderMaterial clone]()

    Genera una copia superficiale di questo materiale. Si noti che il vertexShader e il fragmentShader sono copiati `per riferimento`, così come le definizioni degli `attributi`; questo significa diff --git a/docs/api/it/math/Color.html b/docs/api/it/math/Color.html index 8460c5d8679e61..c4879fb67ead94 100644 --- a/docs/api/it/math/Color.html +++ b/docs/api/it/math/Color.html @@ -181,7 +181,7 @@

    [method:Object getHSL]( [param:Object target], [param:string colorSpace] = L

    -

    [method:Color getRGB]( [param:Color target], [param:string colorSpace] = SRGBColorSpace )

    +

    [method:Color getRGB]( [param:Color target], [param:string colorSpace] = LinearSRGBColorSpace )

    [page:Color target] - questo risultato sarà copiato in questo oggetto.

    diff --git a/docs/api/it/math/Matrix3.html b/docs/api/it/math/Matrix3.html index 1733b712971e65..27321782bbc685 100644 --- a/docs/api/it/math/Matrix3.html +++ b/docs/api/it/math/Matrix3.html @@ -47,7 +47,7 @@

    Costruttore

    [name]( [param:Number n11], [param:Number n12], [param:Number n13], - [param:Number n21], [param:Number n22], [param:Number n22], + [param:Number n21], [param:Number n22], [param:Number n23], [param:Number n31], [param:Number n32], [param:Number n33] )

    Creates a 3x3 matrix with the given arguments in row-major. If no arguments are provided, the constructor initializes @@ -82,19 +82,80 @@

    [method:this extractBasis]( [param:Vector3 xAxis], [param:Vector3 yAxis], [p

    Estrae la [link:https://en.wikipedia.org/wiki/Basis_(linear_algebra) base] di questa matrice nei tre vettori asse forniti. Se questa matrice è: - -a, b, c, -d, e, f, -g, h, i - - allora [page:Vector3 xAxis], [page:Vector3 yAxis], [page:Vector3 zAxis] saranno impostate a: - -xAxis = (a, d, g) -yAxis = (b, e, h) -zAxis = (c, f, i) -

    + + + [ + + + a + b + c + + + d + e + f + + + g + h + i + + + ] + + + +

    + allora [page:Vector3 xAxis], [page:Vector3 yAxis], [page:Vector3 zAxis] saranno impostate a: +

    + +
    + + + xAxis + = + [ + + a + d + g + + ] + + , + + + + yAxis + = + [ + + b + e + h + + ] + + , and + + + + zAxis + = + [ + + c + f + i + + ] + + +
    +

    [method:this fromArray]( [param:Array array], [param:Integer offset] )

    [page:Array array] - l'array da cui leggere gli elementi.
    @@ -123,40 +184,112 @@

    [method:this getNormalMatrix]( [param:Matrix4 m] )

    [method:this identity]()

    Reimposta questa matrice alla matrice identità 3x3: - -1, 0, 0 -0, 1, 0 -0, 0, 1 - -

    + + + [ + + + 1 + 0 + 0 + + + 0 + 1 + 0 + + + 0 + 0 + 1 + + + ] + + +

    [method:this makeRotation]( [param:Float theta] )

    [page:Float theta] — Angolo di rotazione in radianti. I valori positivi ruotano in senso antiorario.

    Imposta questa matrice come una trasformazione rotazionale 2D di [page:Float teta] radianti. La matrice risultante sarà: - -cos(θ) -sin(θ) 0 -sin(θ) cos(θ) 0 -0 0 1 -

    + + + [ + + + + cos + θ + + + -sin + θ + + + 0 + + + + + sin + θ + + + cos + θ + + + 0 + + + + 0 + 0 + 1 + + + ] + + +

    [method:this makeScale]( [param:Float x], [param:Float y] )

    [page:Float x] - la quantità da scalare sull'asse X.
    [page:Float y] - la quantità da scalare sull'asse Y.
    Imposta questa matrice come una trasformazione di scala 2D: - -x, 0, 0, -0, y, 0, -0, 0, 1 -

    + + + [ + + + x + 0 + 0 + + + 0 + y + 0 + + + 0 + 0 + 1 + + + ] + + +

    [method:this makeTranslation]( [param:Vector2 v] )

    [method:this makeTranslation]( [param:Float x], [param:Float y] )

    @@ -166,13 +299,32 @@

    [method:this makeTranslation]( [param:Float x], [param:Float y] )

    [page:Float y] - la quantità da translare sull'asse Y.
    Imposta questa matrice come una trasformazione di traslazione 2D: - -1, 0, x, -0, 1, y, -0, 0, 1 -

    + + + [ + + + 1 + 0 + x + + + 0 + 1 + y + + + 0 + 0 + 1 + + + ] + + +

    [method:this multiply]( [param:Matrix3 m] )

    Post-moltiplica questa matrice per [page:Matrix3 m].

    @@ -190,17 +342,34 @@

    [method:this scale]( [param:Float sx], [param:Float sy] )

    [method:this set]( [param:Float n11], [param:Float n12], [param:Float n13], [param:Float n21], [param:Float n22], [param:Float n23], [param:Float n31], [param:Float n32], [param:Float n33] )

    - [page:Float n11] - valore da inserire nella riga 1, colonna 1.
    - [page:Float n12] - valore da inserire nella riga 1, colonna 2.
    - ...
    - ...
    - [page:Float n32] - valore da inserire nella riga 3, colonna 2.
    - [page:Float n33] - valore da inserire nella riga 3, colonna 3.

    - Imposta i valori della matrice 3x3 sulla sequenza di valori della [link:https://en.wikipedia.org/wiki/Row-_and_column-major_order row-major] specificata.

    + + + [ + + + n11 + n12 + n13 + + + n21 + n22 + n23 + + + n31 + n32 + n33 + + + ] + + +

    [method:this premultiply]( [param:Matrix3 m] )

    Pre-moltiplica questa matrice per [page:Matrix3 m].

    diff --git a/docs/api/it/math/Matrix4.html b/docs/api/it/math/Matrix4.html index adb460937354cb..6abf45d48d6bd3 100644 --- a/docs/api/it/math/Matrix4.html +++ b/docs/api/it/math/Matrix4.html @@ -54,7 +54,7 @@

    [name]

    Una nota sull'ordine delle Row-Major (righe principali) e delle Column-Major (colonne principali)

    - Il metodo [page:set]() accetta gli argomenti in ordine + Il metodo [page:.set set]() accetta gli argomenti in ordine [link:https://en.wikipedia.org/wiki/Row-_and_column-major_order#Column-major_order row-major], mentre internamente vengono memorizzati nell'array [page:.elements elements] nell'ordine column-major.

    @@ -165,20 +165,90 @@

    [method:this extractBasis]( [param:Vector3 xAxis], [param:Vector3 yAxis], [p

    Estrae la [link:https://en.wikipedia.org/wiki/Basis_(linear_algebra) base] di questa matrice nei tre vettori asse forniti. Se questa matrice è: - -a, b, c, d, -e, f, g, h, -i, j, k, l, -m, n, o, p - - allora [page:Vector3 xAxis], [page:Vector3 yAxis], [page:Vector3 zAxis] saranno impostate a: - -xAxis = (a, e, i) -yAxis = (b, f, j) -zAxis = (c, g, k) -

    + + + [ + + + a + b + c + d + + + e + f + g + h + + + i + j + k + l + + + m + n + o + p + + + ] + + + +

    + then the [page:Vector3 xAxis], [page:Vector3 yAxis], [page:Vector3 zAxis] + will be set to: +

    + +
    + + + xAxis + = + [ + + a + e + i + + ] + + , + + + + yAxis + = + [ + + b + f + j + + ] + + , and + + + + zAxis + = + [ + + c + g + k + + ] + + +
    +

    [method:this extractRotation]( [param:Matrix4 m] )

    Estrae il componente rotazione della matrice [page:Matrix4 m] fornita nel componente rotazione di questa matrice. @@ -227,14 +297,41 @@

    [method:this makeBasis]( [param:Vector3 xAxis], [param:Vector3 yAxis], [para

    Imposta questo sulla matrice di [link:https://en.wikipedia.org/wiki/Basis_(linear_algebra) base] composta dai tre vettori di base forniti: - -xAxis.x, yAxis.x, zAxis.x, 0, -xAxis.y, yAxis.y, zAxis.y, 0, -xAxis.z, yAxis.z, zAxis.z, 0, -0, 0, 0, 1 -

    + + + [ + + + xAxis.x + yAxis.x + zAxis.x + 0 + + + xAxis.y + yAxis.y + zAxis.y + 0 + + + xAxis.z + yAxis.z + zAxis.z + 0 + + + 0 + 0 + 0 + 1 + + + ] + + +

    [method:this makePerspective]( [param:Float left], [param:Float right], [param:Float top], [param:Float bottom], [param:Float near], [param:Float far] )

    Crea una matrice di [link:https://en.wikipedia.org/wiki/3D_projection#Perspective_projection proiezione prospettica]. @@ -259,55 +356,322 @@

    [method:this makeRotationFromQuaternion]( [param:Quaternion q] )

    Imposta il componente rotazinoe di questa matrice alla rotazione specificata da [page:Quaternion q], come descritto [link:https://en.wikipedia.org/wiki/Rotation_matrix#Quaternion qui]. Il resto della matrice è impostato all'identità. Quindi, dato [page:Quaternion q] = w + xi + yj + zk, la matrice risultante sarà: - -1-2y²-2z² 2xy-2zw 2xz+2yw 0 -2xy+2zw 1-2x²-2z² 2yz-2xw 0 -2xz-2yw 2yz+2xw 1-2x²-2y² 0 -0 0 0 1 -

    + + + [ + + + + 1 + - + 2 + + y + 2 + + - + 2 + + z + 2 + + + + 2 + x + y + - + 2 + z + w + + + 2 + x + z + + + 2 + y + w + + + 0 + + + + + 2 + x + y + + + 2 + z + w + + + 1 + - + 2 + + x + 2 + + - + 2 + + z + 2 + + + + 2 + y + z + - + 2 + x + w + + + 0 + + + + + 2 + x + z + - + 2 + y + w + + + 2 + y + z + + + 2 + x + w + + + 1 + - + 2 + + x + 2 + + - + 2 + + y + 2 + + + + 0 + + + + 0 + 0 + 0 + 1 + + + ] + + +

    [method:this makeRotationX]( [param:Float theta] )

    [page:Float theta] — Angolo rotazione in radianti.

    Imposta questa matrice come una trasformazione rotazionale attorno all'asse X in radianti theta [page:Float theta] (θ). La matrice risultante sarà: - -1 0 0 0 -0 cos(θ) -sin(θ) 0 -0 sin(θ) cos(θ) 0 -0 0 0 1 -

    + + + [ + + + 1 + 0 + 0 + 0 + + + + 0 + + + cos + θ + + + - + sin + θ + + + 0 + + + + + 0 + + + sin + θ + + + cos + θ + + + 0 + + + + 0 + 0 + 0 + 1 + + + ] + + +

    [method:this makeRotationY]( [param:Float theta] )

    [page:Float theta] — Angolo rotazione in radianti.

    Imposta questa matrice come una trasformazione rotazionale attorno all'asse Y in radianti theta [page:Float theta] (θ). La matrice risultante sarà: - -cos(θ) 0 sin(θ) 0 -0 1 0 0 --sin(θ) 0 cos(θ) 0 -0 0 0 1 -

    + + + [ + + + + cos + θ + + + 0 + + + sin + θ + + + 0 + + + + 0 + 1 + 0 + 0 + + + + - + sin + θ + + + 0 + + + cos + θ + + + 0 + + + + 0 + 0 + 0 + 1 + + + ] + + +

    [method:this makeRotationZ]( [param:Float theta] )

    [page:Float theta] — Angolo rotazione in radianti.

    Imposta questa matrice come una trasformazione rotazionale attorno all'asse Z in radianti theta [page:Float theta] (θ). La matrice risultante sarà: - -cos(θ) -sin(θ) 0 0 -sin(θ) cos(θ) 0 0 -0 0 1 0 -0 0 0 1 -

    + + + + [ + + + + cos + θ + + + - + sin + θ + + + 0 + + + 0 + + + + + sin + θ + + + cos + θ + + + 0 + + + 0 + + + + 0 + 0 + 1 + 0 + + + 0 + 0 + 0 + 1 + + + ] + +

    [method:this makeScale]( [param:Float x], [param:Float y], [param:Float z] )

    @@ -316,14 +680,41 @@

    [method:this makeScale]( [param:Float x], [param:Float y], [param:Float z] ) [page:Float z] - la quantità da scalare sull'asse Z.

    Imposta questa matrice come trasformazione di scala: - -x, 0, 0, 0, -0, y, 0, 0, -0, 0, z, 0, -0, 0, 0, 1 -

    + + + [ + + + x + 0 + 0 + 0 + + + 0 + y + 0 + 0 + + + 0 + 0 + z + 0 + + + 0 + 0 + 0 + 1 + + + ] + + +

    [method:this makeShear]( [param:Float xy], [param:Float xz], [param:Float yx], [param:Float yz], [param:Float zx], [param:Float zy] )

    [page:Float xy] - la quantità di taglio di X per Y.
    @@ -334,28 +725,82 @@

    [method:this makeShear]( [param:Float xy], [param:Float xz], [param:Float yx [page:Float zy] - la quantità di taglio di Z per Y.

    Imposta questa matrice come trasformata di taglio: - -1, yx, zx, 0, -xy, 1, zy, 0, -xz, yz, 1, 0, -0, 0, 0, 1 -

    + + + [ + + + 1 + yx + zx + 0 + + + xy + 1 + zy + 0 + + + xz + yz + 1 + 0 + + + 0 + 0 + 0 + 1 + + + ] + + +

    [method:this makeTranslation]( [param:Vector3 v] )

    [method:this makeTranslation]( [param:Float x], [param:Float y], [param:Float z] ) // optional API

    Imposta questa matrice come una trasformata di traslazione dal vettore [page:Vector3 v]: - -1, 0, 0, x, -0, 1, 0, y, -0, 0, 1, z, -0, 0, 0, 1 -

    + + + [ + + + 1 + 0 + 0 + x + + + 0 + 1 + 0 + y + + + 0 + 0 + 1 + z + + + 0 + 0 + 0 + 1 + + + ] + + +

    [method:this multiply]( [param:Matrix4 m] )

    Post-moltiplica questa matrice per [page:Matrix4 m].

    @@ -385,21 +830,76 @@

    [method:this setPosition]( [param:Float x], [param:Float y], [param:Float z]

    Imposta la componente posizione per questa matrice dal vettore [page:Vector3 v], senza influenzare il resto della matrice - ovvero se la matrice è attulmente: - -a, b, c, d, -e, f, g, h, -i, j, k, l, -m, n, o, p - -Questa diventa: - -a, b, c, v.x, -e, f, g, v.y, -i, j, k, v.z, -m, n, o, p -

    + + + [ + + + a + b + c + d + + + e + f + g + h + + + i + j + k + l + + + m + n + o + p + + + ] + + + +

    Questa diventa:

    + + + + [ + + + a + b + c + v.x + + + e + f + g + v.y + + + i + j + k + v.z + + + m + n + o + p + + + ] + + +

    [method:Array toArray]( [param:Array array], [param:Integer offset] )

    [page:Array array] - (opzionale) array per memorizzare il vettore risultante.
    diff --git a/docs/api/it/math/Plane.html b/docs/api/it/math/Plane.html index b2a405140ea098..172133d625f61b 100644 --- a/docs/api/it/math/Plane.html +++ b/docs/api/it/math/Plane.html @@ -131,7 +131,7 @@

    [method:Vector3 projectPoint]( [param:Vector3 point], [param:Vector3 target]

    [method:this set]( [param:Vector3 normal], [param:Float constant] )

    [page:Vector3 normal] - un [page:Vector3] di lunghezza unitaria che definisce la normale del piano.
    - [page:Float constant] - la distanza con segno dall'origine al piano. Il valore predefinito è `0`.

    + [page:Float constant] - la distanza con segno dall'origine al piano.

    Imposta le proprietà [page:.normal normal] e [page:.constant constant] del piano copiando i valori dalla normale data.

    diff --git a/docs/api/it/math/Quaternion.html b/docs/api/it/math/Quaternion.html index fe993d46e06aa0..e2b0bd6497ac32 100644 --- a/docs/api/it/math/Quaternion.html +++ b/docs/api/it/math/Quaternion.html @@ -18,6 +18,10 @@

    [name]

    L'iterazione di un'istanza di [name] produrrà le sue componenti (x, y, z, w) nell'ordine corrispondente.

    +

    + Tieni presente che three.js prevede che i quaternioni siano normalizzati. +

    +

    Codice di Esempio

    diff --git a/docs/api/it/math/Sphere.html b/docs/api/it/math/Sphere.html index a1e4be8375bb98..14903a611eb1ad 100644 --- a/docs/api/it/math/Sphere.html +++ b/docs/api/it/math/Sphere.html @@ -28,6 +28,11 @@

    Proprietà

    [property:Vector3 center]

    Un [page:Vector3] che definisce il centro della sfera. Il valore predefinito è `(0, 0, 0)`.

    +

    [property:Boolean isSphere]

    +

    + Flag di sola lettura per verificare se l'oggetto dato è di tipo [name]. +

    +

    [property:Float radius]

    Il raggio della sfera. Il valore predefinito è -1.

    diff --git a/docs/api/it/math/Vector4.html b/docs/api/it/math/Vector4.html index 16542c439c0a39..e850bf5c0124b0 100644 --- a/docs/api/it/math/Vector4.html +++ b/docs/api/it/math/Vector4.html @@ -284,7 +284,7 @@

    [method:this setAxisAngleFromRotationMatrix]( [param:Matrix4 m] )

    [method:this setComponent]( [param:Integer index], [param:Float value] )

    - [page:Integer index] - 0, 1 o 2.
    + [page:Integer index] - 0, 1, 2 o 3.
    [page:Float value] - [page:Float]

    Se l'indice è uguale a 0 imposta [page:.x x] a [page:Float value].
    diff --git a/docs/api/it/objects/LOD.html b/docs/api/it/objects/LOD.html index e79c38b86a0a89..3933833c654c34 100644 --- a/docs/api/it/objects/LOD.html +++ b/docs/api/it/objects/LOD.html @@ -27,7 +27,7 @@

    Codice di Esempio

    // Crea sfere con 3 livelli di dettaglio e crea nuovi livelli LOD per loro for( let i = 0; i < 3; i++ ) { - const geometry = new THREE.IcosahedronGeometry( 10, 3 - i ) + const geometry = new THREE.IcosahedronGeometry( 10, 3 - i ); const mesh = new THREE.Mesh( geometry, material ); @@ -88,12 +88,6 @@

    [method:this addLevel]( [param:Object3D object], [param:Float distance], [pa la distanza, minore è il dettaglio sulla mesh.

    -

    [method:LOD clone]()

    -

    - Restituisce un clone di questo oggetto LOD e degli oggetti specifici della distanza ad esso associati. -

    - -

    [method:Integer getCurrentLevel]()

    Ottiene il livello LOD attivo attualmente. Come indice dell'array dei livelli. diff --git a/docs/api/it/objects/Line.html b/docs/api/it/objects/Line.html index 173ad66f833b33..66ead5278b7111 100644 --- a/docs/api/it/objects/Line.html +++ b/docs/api/it/objects/Line.html @@ -90,11 +90,6 @@

    [method:undefined raycast]( [param:Raycaster raycaster], [param:Array inters [page:Raycaster.intersectObject] chiamerà questo metodo.

    -

    [method:Line clone]()

    -

    - Restituisce un clone di questo oggetto Line e i suoi discendenti. -

    -

    [method:undefined updateMorphTargets]()

    Aggiorna i morphTargets in modo che non abbiano alcuna influenza sull'oggetto. diff --git a/docs/api/it/objects/Mesh.html b/docs/api/it/objects/Mesh.html index 180eeda9bedde8..814a641ca59614 100644 --- a/docs/api/it/objects/Mesh.html +++ b/docs/api/it/objects/Mesh.html @@ -67,9 +67,6 @@

    [property:Object morphTargetDictionary]

    Metodi

    Vedi la classe base [page:Object3D] per i metodi comuni.

    -

    [method:Mesh clone]()

    -

    Restituisce un clone di questo oggetto [name] e i suoi discendenti.

    -

    [method:undefined raycast]( [param:Raycaster raycaster], [param:Array intersects] )

    Ottiene le intersezioni tra un raggio lanciato e questa mesh. diff --git a/docs/api/it/objects/Points.html b/docs/api/it/objects/Points.html index b0667a2f6955b8..feec987a73db2a 100644 --- a/docs/api/it/objects/Points.html +++ b/docs/api/it/objects/Points.html @@ -66,11 +66,6 @@

    [method:undefined raycast]( [param:Raycaster raycaster], [param:Array inters [page:Raycaster.intersectObject] chiamerà questo metodo, ma i risultati non saranno ordinati.

    -

    [method:Points clone]()

    -

    - Restituisce un clone di questo oggetto [name] e i suoi discendenti.

    -

    -

    [method:undefined updateMorphTargets]()

    Aggiorna i morphTargets in modo che non abbiano influenza sull'oggetto. Reimposta le proprietà diff --git a/docs/api/it/objects/Skeleton.html b/docs/api/it/objects/Skeleton.html index 4d3c46f531e2f8..c09e38276b6f83 100644 --- a/docs/api/it/objects/Skeleton.html +++ b/docs/api/it/objects/Skeleton.html @@ -79,11 +79,6 @@

    [property:DataTexture boneTexture]

    Il [page:DataTexture] che contiene i dati dell'osso quando si utilizza una texture di vertice.

    -

    [property:Integer boneTextureSize]

    -

    - La dimensione del [page:.boneTexture]. -

    -

    Metodi

    [method:Skeleton clone]()

    diff --git a/docs/api/it/objects/SkinnedMesh.html b/docs/api/it/objects/SkinnedMesh.html index 564b6600e3a3e8..43a8eceb126779 100644 --- a/docs/api/it/objects/SkinnedMesh.html +++ b/docs/api/it/objects/SkinnedMesh.html @@ -13,10 +13,7 @@

    [name]

    Una mesh che ha uno [page:Skeleton scheletro] con [page:Bone ossa] che può essere - utilizzata per animare i vertici della geometria.

    - - [name] può essere utilizzata solo con WebGL 2. Con WebGL 1 è necessario il supporto delle texture del vertice - e `OES_texture_float`. + utilizzata per animare i vertici della geometria.

    @@ -101,10 +98,11 @@

    [name]( [param:BufferGeometry geometry], [param:Material material] )

    Proprietà

    Vedi la classe base [page:Mesh] per le proprietà comuni.

    -

    [property:String bindMode]

    - O "attached" o "detached". "attached" utilizza la proprietà [page:SkinnedMesh.matrixWorld] - per la matrice di trasformazione delle ossa. "detached" utilizza [page:SkinnedMesh.bindMatrix]. Il valore predefinito è "attached". + Either `AttachedBindMode` or `DetachedBindMode`. `AttachedBindMode` means the skinned mesh + shares the same world space as the skeleton. This is not true when using `DetachedBindMode` + which is useful when sharing a skeleton across multiple skinned meshes. + Default is `AttachedBindMode`.

    [property:Matrix4 bindMatrix]

    diff --git a/docs/api/it/objects/Sprite.html b/docs/api/it/objects/Sprite.html index 6270e5ce00f4d5..95b950cb897d3c 100644 --- a/docs/api/it/objects/Sprite.html +++ b/docs/api/it/objects/Sprite.html @@ -63,11 +63,6 @@

    [property:Vector2 center]

    Metodi

    Vedi la classe base [page:Object3D] per i metodi comuni.

    -

    [method:Sprite clone]()

    -

    - Restituisce un clone di questo oggetto Sprite e i suoi discendenti. -

    -

    [method:this copy]( [param:Sprite sprite] )

    Copia le proprietà della sprite passata in questa. diff --git a/docs/api/it/renderers/WebGL1Renderer.html b/docs/api/it/renderers/WebGL1Renderer.html deleted file mode 100644 index 91f5069b8b70a9..00000000000000 --- a/docs/api/it/renderers/WebGL1Renderer.html +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - [page:WebGLRenderer] → - -

    [name]

    - -

    - Poiché la versione r118 [page:WebGLRenderer] utilizza automaticamente un contesto di rendering WebGL 2, quando viene aggiornato un progetto estistente a - => r118, l'applicazione potrebbe rompersi per due ragioni: - -

      -
    • Il codice dello shader personalizzato deve essere conforme a GLSL 3.0.
    • -
    • I controlli dell'estensione WebGL 1 devono essere cambiati.
    • -
    - - Se non hai tempo di cambiare il tuo codice ma vuoi ancora utilizzare l'ultima versione, puoi usare [name]. - Questa versione di renderer forzerà un contesto di rendering WebGL 1. -

    - -

    Costruttore

    - -

    [name]( [param:Object parameters] )

    -

    - Crea un nuovo [name]. -

    - -

    Proprietà

    -

    Vedi la classe base [page:WebGLRenderer] per le proprietà comuni.

    - -

    [property:Boolean isWebGL1Renderer]

    -

    - Flag di sola lettura per verificare se l'oggetto dato è di tipo [name]. -

    - - -

    Metodi

    -

    Vedi la classe base [page:WebGLRenderer] per i metodi comuni.

    - -

    Source

    - -

    - [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] -

    - - diff --git a/docs/api/it/renderers/WebGL3DRenderTarget.html b/docs/api/it/renderers/WebGL3DRenderTarget.html index 079b9e87e56af4..da3a2ea7612327 100644 --- a/docs/api/it/renderers/WebGL3DRenderTarget.html +++ b/docs/api/it/renderers/WebGL3DRenderTarget.html @@ -17,18 +17,20 @@

    [name]

    Costruttore

    -

    [name]( [param:Number width], [param:Number height], [param:Number depth] )

    +

    [name]( [param:Number width], [param:Number height], [param:Number depth], [param:Object options] )

    [page:Number width] - la lunghezza del render target, in pixel. Il valore predefinito è `1`.
    [page:Number height] - l'altezza del render target, in pixel. Il valore predefinito è `1`.
    - [page:Number depth] - la profondità del render target. Il valore predefinito è `1`.

    + [page:Number depth] - la profondità del render target. Il valore predefinito è `1`.
    + [page:Object options] - (opzionale) oggetto che contiene i parametri della texture per un target texture auto generato + e i booleani depthBuffer/stencilBuffer. See [page:WebGLRenderTarget] for details.

    Crea un nuovo [name].

    Proprietà

    -

    Vedi [page:WebGLRenderTarget] per le proprietà ereditate

    +

    Vedi [page:WebGLRenderTarget] per le proprietà ereditate.

    [property:number depth]

    @@ -42,7 +44,7 @@

    [property:Data3DTexture texture]

    Metodi

    -

    Vedi [page:WebGLRenderTarget] per i metodi ereditati

    +

    Vedi [page:WebGLRenderTarget] per i metodi ereditati.

    Source

    diff --git a/docs/api/it/renderers/WebGLArrayRenderTarget.html b/docs/api/it/renderers/WebGLArrayRenderTarget.html index 64f450b2a98de1..a6a85eb4fd5bcd 100644 --- a/docs/api/it/renderers/WebGLArrayRenderTarget.html +++ b/docs/api/it/renderers/WebGLArrayRenderTarget.html @@ -18,23 +18,25 @@

    [name]

    Esempi

    - [example:webgl2_rendertarget_texture2darray WebGL 2 / render target / array]
    + [example:webgl_rendertarget_texture2darray WebGL / render target / array]

    Costruttore

    -

    [name]( [param:Number width], [param:Number height], [param:Number depth] )

    +

    [name]( [param:Number width], [param:Number height], [param:Number depth], [param:Object options] )

    [page:Number width] - la lunghezza del render target, in pixel. Il valore predefinito è `1`.
    [page:Number height] - l'altezza del render target, in pixel. Il valore predefinito è `1`.
    - [page:Number depth] - la profondità del render target. Il valore predefinito è `1`.

    + [page:Number depth] - la profondità del render target. Il valore predefinito è `1`.
    + [page:Object options] - (opzionale) oggetto che contiene i parametri della texture per un target texture auto generato + e i booleani depthBuffer/stencilBuffer. See [page:WebGLRenderTarget] for details.

    Crea un nuovo [name].

    Proprietà

    -

    Vedi [page:WebGLRenderTarget] per le proprietà ereditate

    +

    Vedi [page:WebGLRenderTarget] per le proprietà ereditate.

    [property:number depth]

    @@ -48,7 +50,7 @@

    [property:DataArrayTexture texture]

    Metodi

    -

    Vedi [page:WebGLRenderTarget] per i metodi ereditati

    +

    Vedi [page:WebGLRenderTarget] per i metodi ereditati.

    Source

    diff --git a/docs/api/it/renderers/WebGLCubeRenderTarget.html b/docs/api/it/renderers/WebGLCubeRenderTarget.html index 5dd5358781ac01..1fc04755b70292 100644 --- a/docs/api/it/renderers/WebGLCubeRenderTarget.html +++ b/docs/api/it/renderers/WebGLCubeRenderTarget.html @@ -49,11 +49,11 @@

    [name]([param:Number size], [param:Object options])

    Proprietà

    -

    Vedi [page:WebGLRenderTarget] per le proprietà ereditate

    +

    Vedi [page:WebGLRenderTarget] per le proprietà ereditate.

    Metodi

    -

    Vedi [page:WebGLRenderTarget] per i metodi ereditati

    +

    Vedi [page:WebGLRenderTarget] per i metodi ereditati.

    [method:this fromEquirectangularTexture]( [param:WebGLRenderer renderer], [param:Texture texture] )

    diff --git a/docs/api/it/renderers/WebGLMultipleRenderTargets.html b/docs/api/it/renderers/WebGLMultipleRenderTargets.html deleted file mode 100644 index 2815d4ef168e15..00000000000000 --- a/docs/api/it/renderers/WebGLMultipleRenderTargets.html +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - [page:WebGLRenderTarget] → - -

    [name]

    - -

    - Un target di rendering speciale che consente a un fragment shader di scrivere su più texture. - Questo approccio è utile per le tecniche di rendering avanzate come la post-eleborazione o il rendering differito. - - Attenzione: [name] può solo essere utilizzato in un contesto di rendering WebGL 2. -

    - -

    Esempi

    - -

    - [example:webgl2_multiple_rendertargets webgl2 / multiple / rendertargets ] -

    - -

    Costruttore

    - - -

    [name]([param:Number width], [param:Number height], [param:Number count], [param:Object options])

    - -

    - [page:Number width] - La larghezza del target di rendering. Il valore predefinito è `1`.
    - [page:Number height] - L'altezza del target di rendering. Il valore predefinito è `1`.
    - [page:Number count] - Il numero di target di rendering. Il valore predefinito è `1`.
    - - options - (oggetto opzionale che contiene i parametri della texture per una texture target auto generata - e i booleani depthBuffer/stencilBuffer. Per una spiegazione dei parametri della texture consultare [page:Texture Texture]. - Per un elenco di opzioni valide, consultare [page:WebGLRenderTarget WebGLRenderTarget].)

    -

    - -

    Proprietà

    - -

    [property:Boolean isWebGLMultipleRenderTargets]

    -

    - Flag di sola lettura per verificare se l'oggetto dato è di tipo [name]. -

    - -

    [property:Array texture]

    -

    - La proprietà texture viene sovrascritta in [name] e sostituita con un array. Questo array contiene i - riferimenti alla [page:WebGLRenderTarget.texture texture] dei rispettivi target di rendering. -

    - -

    Le proprietà [page:WebGLRenderTarget WebGLRenderTarget] sono disponibili su questa classe.

    - -

    Metodi

    - -

    I metodi [page:WebGLRenderTarget WebGLRenderTarget] sono disponibili su questa classe.

    - -

    Source

    - -

    - [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] -

    - - diff --git a/docs/api/it/renderers/WebGLRenderTarget.html b/docs/api/it/renderers/WebGLRenderTarget.html index 8c52cb134d8166..3485fce7c3ba32 100644 --- a/docs/api/it/renderers/WebGLRenderTarget.html +++ b/docs/api/it/renderers/WebGLRenderTarget.html @@ -40,9 +40,13 @@

    [name]([param:Number width], [param:Number height], [param:Object options])< [page:Constant type] - il valore predefinito è [page:Textures UnsignedByteType].
    [page:Number anisotropy] - il valore predefinito è `1`. Vedi [page:Texture.anisotropy]
    [page:Constant colorSpace] - il valore predefinito è [page:Textures NoColorSpace].
    + [page:String internalFormat] - il valore predefinito è `null`.
    [page:Boolean depthBuffer] - il valore predefinito è `true`.
    [page:Boolean stencilBuffer] - il valore predefinito è `false`.
    - [page:Number samples] - il valore predefinito è 0.

    + [page:Boolean resolveDepthBuffer] - il valore predefinito è `true`.
    + [page:Boolean resolveStencilBuffer] - il valore predefinito è `true`.
    + [page:Number samples] - il valore predefinito è 0.
    + [page:Number count] - default is `1`.

    Crea un nuovo [name]

    @@ -84,6 +88,12 @@

    [property:Texture texture]

    Questa istanza della texture contiene i pixel renderizzati. Utilizzalo come input per ulteriori informazioni.

    +

    [property:Texture textures]

    +

    + An array holding the [page:WebGLRenderTarget.texture texture] references + of multiple render targets configured with the [page:Number count] option. +

    +

    [property:Boolean depthBuffer]

    Effettua il rendering al buffer di profondità. L'impostazione predefinita è `true`. @@ -94,6 +104,19 @@

    [property:Boolean stencilBuffer]

    Effettua il rendering al buffer stencil. Il valore predefinito è `false`.

    +

    [property:Boolean resolveDepthBuffer]

    +

    + Defines whether the depth buffer should be resolved when rendering into a multisampled render target. + Il valore predefinito è `true`. +

    + +

    [property:Boolean resolveStencilBuffer]

    +

    + Defines whether the stencil buffer should be resolved when rendering into a multisampled render target. + This property has no effect when [page:.resolveDepthBuffer] is set to `false`. + Il valore predefinito è `true`. +

    +

    [property:DepthTexture depthTexture]

    Se impostato, la profondità della scena verrà renderizzata su questa texture. Il valore predefinito è `null`. diff --git a/docs/api/it/renderers/WebGLRenderer.html b/docs/api/it/renderers/WebGLRenderer.html index ccf8d7c1f1101a..c6b84a5e86e369 100644 --- a/docs/api/it/renderers/WebGLRenderer.html +++ b/docs/api/it/renderers/WebGLRenderer.html @@ -45,7 +45,7 @@

    [name]( [param:Object parameters] )

    [page:Boolean stencil] - Indica se il buffer di disegno ha un [link:https://en.wikipedia.org/wiki/Stencil_buffer buffer stencil] di almeno 8 bit. - Il valore predefinito è `true`.
    + Il valore predefinito è `false`.
    [page:Boolean preserveDrawingBuffer] - Indica se conservare i buffer finché non verranno cancellati o sovrascritti manualmente. Il valore predefinito è `false`.
    @@ -72,7 +72,7 @@

    [name]( [param:Object parameters] )

    Proprietà

    [property:Boolean autoClear]

    -

    Definisce se il renderer deve cancellare automaticamente il suo output prima di eseguire il rendering di un frame.

    +

    Definisce se il renderer deve cancellare automaticamente il suo output prima di eseguire il rendering di un frame. Il valore predefinito è `true`.

    [property:Boolean autoClearColor]

    @@ -223,11 +223,6 @@

    [property:Object info]

    [property:Boolean localClippingEnabled]

    Definisce se il render rispetta i piani di taglio a livello di oggetto. Il valore predefinito è `false`.

    -

    [property:Boolean useLegacyLights]

    -

    - Whether to use the legacy lighting mode or not. Il valore predefinito è `true`. -

    -

    [property:Object properties]

    Utilizzato internamente dal renderer per mantenere traccia delle proprietà dei vari oggetti secondari. @@ -312,25 +307,36 @@

    [method:undefined clearDepth]( )

    [method:undefined clearStencil]( )

    Cancella il buffer di stencil. Equivalente a chiamare [page:WebGLRenderer.clear .clear]( false, false, true ).

    -

    [method:undefined compile]( [param:Object3D scene], [param:Camera camera] )

    -

    Compila tutti i materiali nella scena con la telecamera. Questo è utile per precompilare le ombre prima del primo rendering.

    +

    [method:Set compile]( [param:Object3D scene], [param:Camera camera], [param:Scene targetScene] )

    +

    + Compila tutti i materiali nella scena con la telecamera. Questo è utile per precompilare le ombre prima del primo rendering. + Se desideri aggiungere un oggetto 3D a una scena esistente, utilizza il terzo parametro opzionale per applicare la scena di destinazione.
    + Tieni presente che l'illuminazione della scena deve essere configurata prima di chiamare questo metodo. +

    + +

    [method:Promise compileAsync]( [param:Object3D scene], [param:Camera camera], [param:Scene targetScene] )

    +

    + Versione asincrona di [page:WebGLRenderer.compile .compile](). Il metodo restituisce una Promise che si risolve quando è possibile eseguire + il rendering di un determinato oggetto 3D o scena senza inutili stalli dovuti alla compilazione dello shader.

    + Questo metodo utilizza *KHR_parallel_shader_compile*. +

    -

    [method:undefined copyFramebufferToTexture]( [param:Vector2 position], [param:FramebufferTexture texture], [param:Number level] )

    +

    [method:undefined copyFramebufferToTexture]( [param:FramebufferTexture texture], [param:Vector2 position], [param:Number level] )

    Copia i pixel dal WebGLFramebuffer corrente in una texture 2D. Abilita l'accesso a [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/copyTexImage2D WebGLRenderingContext.copyTexImage2D].

    -

    [method:undefined copyTextureToTexture]( [param:Vector2 position], [param:Texture srcTexture], [param:Texture dstTexture], [param:Number level] )

    +

    [method:undefined copyTextureToTexture]( [param:Texture srcTexture], [param:Texture dstTexture], [param:Box2 srcRegion], [param:Vector2 dstPosition], [param:Number level] )

    - Copia tutti i pixel della texture in una texture esistente partendo dalla posizione data. Abilita l'accesso a - [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/texSubImage2D WebGLRenderingContext.texSubImage2D]. + Copies the pixels of a texture in the bounds '[page:Box2 srcRegion]' in the destination texture starting from the given position. + Enables access to [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/texSubImage2D WebGLRenderingContext.texSubImage2D].

    -

    [method:undefined copyTextureToTexture3D]( [param:Box3 sourceBox], [param:Vector3 position], [param:Texture srcTexture], [param:Texture dstTexture], [param:Number level] )

    +

    [method:undefined copyTextureToTexture3D]( [param:Texture srcTexture], [param:Texture dstTexture], [param:Box3 srcRegion], [param:Vector3 dstPosition], [param:Number level] )

    - Copia i pixel della texture nei limiti '[page:Box3 sourceBox]' nella texture di destinazione partendo dalla posizione data. Abilita l'accesso a - [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext/texSubImage3D WebGL2RenderingContext.texSubImage3D]. + Copies the pixels of a texture in the bounds '[page:Box3 srcRegion]' in the destination texture starting from the given position. + Enables access to [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext/texSubImage3D WebGL2RenderingContext.texSubImage3D].

    [method:undefined dispose]( )

    diff --git a/docs/api/it/scenes/Fog.html b/docs/api/it/scenes/Fog.html index 522a3e9449da68..25049c000dcfa1 100644 --- a/docs/api/it/scenes/Fog.html +++ b/docs/api/it/scenes/Fog.html @@ -13,8 +13,9 @@

    [name]

    Codice di Esempio

    - const scene = new THREE.Scene(); - scene.fog = new THREE.Fog( 0xcccccc, 10, 15 ); + + const scene = new THREE.Scene(); + scene.fog = new THREE.Fog( 0xcccccc, 10, 15 );

    Costruttore

    diff --git a/docs/api/it/scenes/FogExp2.html b/docs/api/it/scenes/FogExp2.html index 61515b33a4d39c..eb4c1bf3f843ec 100644 --- a/docs/api/it/scenes/FogExp2.html +++ b/docs/api/it/scenes/FogExp2.html @@ -17,8 +17,9 @@

    [name]

    Codice di Esempio

    - const scene = new THREE.Scene(); - scene.fog = new THREE.FogExp2( 0xcccccc, 0.002 ); + + const scene = new THREE.Scene(); + scene.fog = new THREE.FogExp2( 0xcccccc, 0.002 );

    Costruttore

    diff --git a/docs/api/it/textures/Data3DTexture.html b/docs/api/it/textures/Data3DTexture.html index 67b3ba015cbefb..7211f383b028d3 100644 --- a/docs/api/it/textures/Data3DTexture.html +++ b/docs/api/it/textures/Data3DTexture.html @@ -11,7 +11,7 @@

    [name]

    -

    Crea una texture tridimensionale. Questo tipo di texture può solo essere utilizzata in un contesto di rendering WebGL 2.

    +

    Crea una texture tridimensionale.

    Costruttore

    @@ -29,7 +29,7 @@

    [name]( [param:TypedArray data], [param:Number width], [param:Number height]

    Esempi

    - [example:webgl2_materials_texture3d WebGL2 / materials / texture3d] + [example:webgl_texture3d WebGL / materials / texture3d]

    Proprietà

    diff --git a/docs/api/it/textures/DataArrayTexture.html b/docs/api/it/textures/DataArrayTexture.html index 075df901760139..169dfb0367449b 100644 --- a/docs/api/it/textures/DataArrayTexture.html +++ b/docs/api/it/textures/DataArrayTexture.html @@ -13,7 +13,6 @@

    [name]

    Crea un array di texture direttamente da dati grezzi, dalla larghezza, dall'altezza e dalla profondità. - Questo tipo di texture può solo essere utilizzata in un contesto di rendering WebGL 2.

    Costruttore

    @@ -80,7 +79,7 @@

    Codice di Esempio

    Esempi

    - [example:webgl2_materials_texture2darray WebGL2 / materials / texture2darray] + [example:webgl_texture2darray WebGL / texture2darray]

    Proprietà

    diff --git a/docs/api/it/textures/DepthTexture.html b/docs/api/it/textures/DepthTexture.html index 7e463baaeb8127..0decd4225c351c 100644 --- a/docs/api/it/textures/DepthTexture.html +++ b/docs/api/it/textures/DepthTexture.html @@ -13,8 +13,6 @@

    [name]

    Questa classe può essere utilizzata per salvare automaticamente le informazioni di profondità di un rendering in una texture. - Quando viene utilizzato un contesto di rendering WebGL 1, [name] richiede supporto per l'estensione - [link:https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/ WEBGL_depth_texture].

    Esempi

    diff --git a/docs/api/it/textures/Source.html b/docs/api/it/textures/Source.html index 8a4e0eb1531788..4a1e1ad48e1c21 100644 --- a/docs/api/it/textures/Source.html +++ b/docs/api/it/textures/Source.html @@ -27,9 +27,15 @@

    [property:Any data]

    I dati effettivi di una texture. Il tipo di questa proprietà dipende dalla texture che utilizza questa istanza.

    -

    [property:Boolean isSource]

    +

    [property:Boolean needsUpdate]

    +

    + When the property is set to `true`, the engine allocates the memory for the texture (if necessary) and triggers the actual texture upload to the GPU next time the source is used. +

    + +

    [property:Boolean dataReady]

    - Flag di sola lettura per verificare se l'oggetto dato è di tipo [name]. + This property is only relevant when [page:.needUpdate] is set to `true` and provides more control on how texture data should be processed. + When `dataReady` is set to `false`, the engine performs the memory allocation (if necessary) but does not transfer the data into the GPU memory. Default is `true`.

    [property:Boolean needsUpdate]

    diff --git a/docs/api/it/textures/Texture.html b/docs/api/it/textures/Texture.html index f59aace39ff34d..8473bc5a3b7b15 100644 --- a/docs/api/it/textures/Texture.html +++ b/docs/api/it/textures/Texture.html @@ -14,7 +14,7 @@

    [name]

    - Nota: Dopo l'utilizzo iniziale di una texture, le sue dimensioni, formato, e il tipo non possono essere cambiati. + Nota: Dopo l'utilizzo iniziale di una texture, le sue dimensioni, formato, e il tipo non possono essere cambiati. Invece, chiama [page:.dispose]() sulla texture e creane una nuova.

    @@ -61,8 +61,8 @@

    [property:Image image]

    Questo può essere qualsiasi tipo di immagine (e.g., PNG, JPG, GIF, DDS) o video (e.g., MP4, OGG/OGV) supportato da three.js.

    Per utilizzare il video come texture è necessario disporre di un elemento video HTML5 - in riproduzione come sorgente per l'immagine della texture e aggiornare - continuamente questa texture finchè il video è in riproduzione - + in riproduzione come sorgente per l'immagine della texture e aggiornare + continuamente questa texture finchè il video è in riproduzione - la classe [page:VideoTexture VideoTexture] gestisce questa operazione automaticamente.

    @@ -88,7 +88,7 @@

    [property:number wrapS]

    Questo definisce come la texture è wrappata orizzontalmente e corrisponde a *U* nel mapping UV.
    L'impostazione predefinita è [page:Textures THREE.ClampToEdgeWrapping], dove il bordo è fissato ai texel del bordo esterno. - Le altre due scelte sono [page:Textures THREE.RepeatWrapping] e [page:Textures THREE.MirroredRepeatWrapping]. + Le altre due scelte sono [page:Textures THREE.RepeatWrapping] e [page:Textures THREE.MirroredRepeatWrapping]. Vedi la pagina [page:Textures texture constants] per i dettagli.

    @@ -122,7 +122,7 @@

    [property:number minFilter]

    [property:number anisotropy]

    Il numero di campioni prelevati lungo l'asse attravero il pixel che ha la densità di texel più alta. - Per impostazione predefinita, questo valore è 1. Un valore più alto fornisce un risultato meno sfuocato rispetto ad una mipmap di base, + Per impostazione predefinita, questo valore è 1. Un valore più alto fornisce un risultato meno sfuocato rispetto ad una mipmap di base, a costo di utilizzare più campioni di texture. Usa [page:WebGLRenderer.capabilities renderer.capabilities.getMaxAnisotropy]() per trovare il valore di anisotropia massimo valido per la GPU; questo valore è solitamente una potenza di 2.

    @@ -160,7 +160,7 @@

    [property:Vector2 offset]

    [property:Vector2 repeat]

    - Quante volte la texture è ripetuta sulla superficie, in ogni direzione U e V. Se la proprietà ripeat è + Quante volte la texture è ripetuta sulla superficie, in ogni direzione U e V. Se la proprietà ripeat è impostata su un valore maggiore di 1 in entrambe le direzioni, anche il parametro Wrap corrispondente deve essere impostato su [page:Textures THREE.RepeatWrapping] o [page:Textures THREE.MirroredRepeatWrapping] per ottenere l'effetto di piastrellatura desiderato. @@ -174,14 +174,14 @@

    [property:number rotation]

    [property:Vector2 center]

    - Il punto attorno al quale avviene la rotazione. Un valore (0.5, 0.5) che corrisponde al centro della texture. Il + Il punto attorno al quale avviene la rotazione. Un valore (0.5, 0.5) che corrisponde al centro della texture. Il valore predefinito è (0, 0), in basso a sinistra.

    [property:Boolean matrixAutoUpdate]

    Indica se aggiornare la [page:Texture.matrix .matrix] uv-transform della texture dalle proprietà della texture - [page:Texture.offset .offset], [page:Texture.repeat .repeat], [page:Texture.rotation .rotation], e [page:Texture.center .center]. + [page:Texture.offset .offset], [page:Texture.repeat .repeat], [page:Texture.rotation .rotation], e [page:Texture.center .center]. Il valore predefinito è `true`. Impostalo a `false` se stai specificando la matrice uv-transform direttamente.

    @@ -196,13 +196,13 @@

    [property:Matrix3 matrix]

    [property:Boolean generateMipmaps]

    - Indica se generare mipmap (se possibile) per una texure. Il valore predefinito è `true`. + Indica se generare mipmap (se possibile) per una texture. Il valore predefinito è `true`. Impostalo a false se stai creando il mipmap manualmente.

    [property:Boolean premultiplyAlpha]

    - Se impostato a `true`, il canale alfa, se presente, viene moltiplicato nei canali del colore + Se impostato a `true`, il canale alfa, se presente, viene moltiplicato nei canali del colore quando la texture viene caricata sulla GPU. Il valore predefinito è `false`.

    Si noti che questa proprietà non ha effetto per [link:https://developer.mozilla.org/en-US/docs/Web/API/ImageBitmap ImageBitmap]. @@ -220,7 +220,7 @@

    [property:Boolean flipY]

    [property:number unpackAlignment]

    Il valore predefinito è 4. Specifica i requisiti di allineamento per l'inizio di ogni riga di pixel in memoria. - I valori consentiti sono 1 (allineamento di byte), 2 (righe allineate a byte pari), 4 (allineamento di parole) + I valori consentiti sono 1 (allineamento di byte), 2 (righe allineate a byte pari), 4 (allineamento di parole) e 8 (le righe iniziano su limiti di doppia parola). Vedi [link:http://www.khronos.org/opengles/sdk/docs/man/xhtml/glPixelStorei.xml glPixelStorei] per maggiori informazioni.

    @@ -250,8 +250,8 @@

    [property:Boolean needsUpdate]

    [property:Object userData]

    - Un oggetto che può essere utilizzato per memorizzare dati personalizzati della texture. - Non deve contenere riferimenti a funzioni poiché queste non verranno clonate. + Un oggetto che può essere utilizzato per memorizzare dati personalizzati della texture. + Non deve contenere riferimenti a funzioni poiché queste non verranno clonate.

    [property:Source source]

    @@ -286,7 +286,7 @@

    [method:Object toJSON]( [param:Object meta] )

    [method:undefined dispose]()

    - Libera le risorse relative alla GPU allocate da questa istanza. + Libera le risorse relative alla GPU allocate da questa istanza. Chiama questo metodo ogni volta che questa istanza non viene più utilizzata dall'applicazione.

    diff --git a/docs/api/ko/animation/AnimationObjectGroup.html b/docs/api/ko/animation/AnimationObjectGroup.html index 00a761a4ab9972..debffd6fdd7d2d 100644 --- a/docs/api/ko/animation/AnimationObjectGroup.html +++ b/docs/api/ko/animation/AnimationObjectGroup.html @@ -46,7 +46,7 @@

    프로퍼티

    [property:Boolean isAnimationObjectGroup]

    - Read-only flag to check if a given object is of type [name]. + 주어진 객체가 [name] 타입인지 확인하기 위한 읽기 전용 플래그입니다.

    diff --git a/docs/api/ko/animation/AnimationUtils.html b/docs/api/ko/animation/AnimationUtils.html index f318fa9a679fe7..b094642ae917b6 100644 --- a/docs/api/ko/animation/AnimationUtils.html +++ b/docs/api/ko/animation/AnimationUtils.html @@ -17,11 +17,6 @@

    [name]

    메서드

    -

    [method:Array arraySlice]( array, from, to )

    -

    - Array.prototype.slice와 동일하지만, 타입 배열에서도 작동합니다. -

    -

    [method:Array convertArray]( array, type, forceClone )

    배열을 특정 타입으로 변환합니다. @@ -44,7 +39,7 @@

    [method:Boolean isTypedArray]( object )

    [method:AnimationClip makeClipAdditive]( [param:AnimationClip targetClip], [param:Number referenceFrame], [param:AnimationClip referenceClip], [param:Number fps] )

    - Converts the keyframes of the given animation clip to an additive format. + 주어진 애니메이션 클립의 keyframe들을 additive 포맷으로 변경해줍니다.

    [method:Array sortedArray]( values, stride, order )

    diff --git a/docs/api/ko/audio/Audio.html b/docs/api/ko/audio/Audio.html index e47e9a7364772d..3a5266fa71b47d 100644 --- a/docs/api/ko/audio/Audio.html +++ b/docs/api/ko/audio/Audio.html @@ -139,7 +139,7 @@

    [method:Float getPlaybackRate]()

    [page:Audio.playbackRate playbackRate]의 값을 리턴합니다.

    -

    [method:Float getVolume]( value )

    +

    [method:Float getVolume]()

    현재 볼륨을 리턴합니다.

    diff --git a/docs/api/ko/cameras/OrthographicCamera.html b/docs/api/ko/cameras/OrthographicCamera.html index 98cf3b50a9b6f0..37816e4bd23e74 100644 --- a/docs/api/ko/cameras/OrthographicCamera.html +++ b/docs/api/ko/cameras/OrthographicCamera.html @@ -36,7 +36,6 @@

    예제

    [example:webgl_postprocessing_dof2 postprocessing / dof2 ]
    [example:webgl_postprocessing_godrays postprocessing / godrays ]
    [example:webgl_rtt rtt ]
    - [example:webgl_shaders_tonemapping shaders / tonemapping ]
    [example:webgl_shadowmap shadowmap ]

    diff --git a/docs/api/ko/constants/Renderer.html b/docs/api/ko/constants/Renderer.html index 185b6eacb56c39..21c483fc72c99b 100644 --- a/docs/api/ko/constants/Renderer.html +++ b/docs/api/ko/constants/Renderer.html @@ -46,6 +46,9 @@

    톤 맵핑

    THREE.ReinhardToneMapping THREE.CineonToneMapping THREE.ACESFilmicToneMapping + THREE.AgXToneMapping + THREE.NeutralToneMapping + THREE.CustomToneMapping

    WebGLRenderer의 [page:WebGLRenderer.toneMapping toneMapping] 프로퍼티를 정의합니다. @@ -54,7 +57,9 @@

    톤 맵핑

    [example:webgl_tonemapping WebGL / tonemapping] 예제를 참고하세요.

    - +

    + THREE.NeutralToneMapping is an implementation based on the Khronos 3D Commerce Group standard tone mapping. +

    소스 코드

    diff --git a/docs/api/ko/constants/Textures.html b/docs/api/ko/constants/Textures.html index 3e4e62177f844d..33d558ce11ef93 100644 --- a/docs/api/ko/constants/Textures.html +++ b/docs/api/ko/constants/Textures.html @@ -126,6 +126,7 @@

    타입

    THREE.UnsignedShort4444Type THREE.UnsignedShort5551Type THREE.UnsignedInt248Type + THREE.UnsignedInt5999Type

    텍스쳐의 [page:Texture.type type] 프로퍼티와 함께 사용되며, 정확한 포맷이어야 합니다. 아래 세부 사항을 확인하세요.

    @@ -140,10 +141,9 @@

    포맷

    THREE.RedIntegerFormat THREE.RGFormat THREE.RGIntegerFormat + THREE.RGBFormat THREE.RGBAFormat THREE.RGBAIntegerFormat - THREE.LuminanceFormat - THREE.LuminanceAlphaFormat THREE.DepthFormat THREE.DepthStencilFormat
    @@ -157,31 +157,19 @@

    포맷

    [page:constant RedIntegerFormat] green 및 blue 요소를 버리고 red 요소만 읽어들입니다. texel은 부동 소수점 대신 정수로 읽어들입니다. - (WebGL 2 렌더링 시에만 사용 가능).

    [page:constant RGFormat] alpha, blue 요소를 버리고 red, green 요소만 읽어들입니다. - (WebGL 2 렌더링 시에만 사용 가능).

    [page:constant RGIntegerFormat] alpha, blue 요소를 버리고 red, green 요소만 읽어들입니다. texel은 부동 소수점 대신 정수로 읽어들입니다. - (WebGL 2 렌더링 시에만 사용 가능).

    [page:constant RGBAFormat]는 기본값이며 red, green, blue 및 alpha 요소를 읽어들입니다.

    [page:constant RGBAIntegerFormat]는 기본값이며 red, green, blue 및 alpha 요소를 읽어들입니다. texel은 부동 소수점 대신 정수로 읽어들입니다. - (WebGL 2 렌더링 시에만 사용 가능). -

    - - [page:constant LuminanceFormat]은 각 요소(element)를 단일 휘도 요소(component)로 읽어들입니다. - 그 다음에 부동 소수점으로 변환되어 [0,1] 범위에 고정한 다음 - 휘도 값을 적색, 녹색 및 청색 채널에 배치하고 1.0을 알파 채널에 할당하여 RGBA 요소로 조립합니다.

    - - [page:constant LuminanceAlphaFormat]은 각 요소(element)를 휘도/알파 소수로 읽어들입니다. - The same process occurs as for the [page:constant LuminanceFormat]와 같은 절차가 이루어지며, 알파 채널에 *1.0* 이외의 값이 들어갈 수 있다는 점만 다릅니다.

    [page:constant DepthFormat]은 각 요소를 단일 깊이 값으로 일거들이며 부동 소수점으로 변환하고, [0,1]범위에 고정합니다. @@ -335,9 +323,6 @@

    내부 포맷

    - - 주의: WebGL 2 렌더링의 경우에만 텍스쳐 내부 포맷 변경이 텍스쳐에 영향을 줄 것입니다.

    - 텍스쳐의 [page:Texture.internalFormat internalFormat] 프로퍼티와 함께 사용되며, 텍스쳐 혹은 *texels*의 요소들이 GPU에 어떻게 저장될지 정의합니다.

    diff --git a/docs/api/ko/core/BufferAttribute.html b/docs/api/ko/core/BufferAttribute.html index 060a119ad1f678..5847f4a57f6ad0 100644 --- a/docs/api/ko/core/BufferAttribute.html +++ b/docs/api/ko/core/BufferAttribute.html @@ -53,12 +53,19 @@

    [property:TypedArray array]

    [property:Integer count]

    - [page:BufferAttribute.itemSize itemSize]로 나눈 [page:BufferAttribute.array array]의 길이를 저장.

    + [page:BufferAttribute.itemSize itemSize]로 나눈 [page:BufferAttribute.array array]의 길이를 저장. Read-only property.

    버퍼가 3개의 컴포넌트를 저장한 벡터(위치, 법선, 색 등) 저장된 벡터들의 수를 계산합니다.

    +

    [property:Number gpuType]

    +

    + Configures the bound GPU type for use in shaders. Either [page:BufferAttribute THREE.FloatType] or [page:BufferAttribute THREE.IntType], default is [page:BufferAttribute THREE.FloatType]. + + Note: this only has an effect for integer arrays and is not configurable for float arrays. For lower precision float types, see [page:BufferAttributeTypes THREE.Float16BufferAttribute]. +

    +

    [property:Boolean isBufferAttribute]

    Read-only flag to check if a given object is of type [name]. @@ -139,6 +146,9 @@

    [method:this copyArray]( array )

    [method:this copyAt] ( [param:Integer index1], [param:BufferAttribute bufferAttribute], [param:Integer index2] )

    bufferAttribute[index2]의 벡터를 [page:BufferAttribute.array array][index1]에 복사합니다.

    +

    [method:Number getComponent]( [param:Integer index], [param:Integer component] )

    +

    Returns the given component of the vector at the given index.

    +

    [method:Number getX]( [param:Integer index] )

    해당 index의 벡터의 x 컴포넌트 값을 리턴합니다.

    @@ -172,6 +182,9 @@

    [method:this set] ( [param:Array value], [param:Integer offset] )

    [method:this setUsage] ( [param:Usage value] )

    [page:BufferAttribute.usage usage]를 value로 설정합니다.

    +

    [method:Number setComponent]( [param:Integer index], [param:Integer component], [param:Float value] )

    +

    Sets the given component of the vector at the given index.

    +

    [method:this setX]( [param:Integer index], [param:Float x] )

    x 컴포넌트 값을 설정합니다.

    diff --git a/docs/api/ko/core/Clock.html b/docs/api/ko/core/Clock.html index 0e49bc0be5efff..898b0ed81f7148 100644 --- a/docs/api/ko/core/Clock.html +++ b/docs/api/ko/core/Clock.html @@ -10,7 +10,7 @@

    [name]

    - 시간을 파악하는 객체입니다. [link:https://developer.mozilla.org/en-US/docs/Web/API/Performance/now performance.now]를 우선적으로 사용하며, 사용이 불가능할 때는 덜 정확한 [link:https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Date/now Date.now]를 사용합니다. + 시간을 파악하는 객체입니다. [link:https://developer.mozilla.org/en-US/docs/Web/API/Performance/now performance.now]

    diff --git a/docs/api/ko/core/GLBufferAttribute.html b/docs/api/ko/core/GLBufferAttribute.html index 753eb339d3e205..b5830ea32c59ab 100644 --- a/docs/api/ko/core/GLBufferAttribute.html +++ b/docs/api/ko/core/GLBufferAttribute.html @@ -17,8 +17,13 @@

    [name]

    이 클래스의 가장 일반적인 사용 사례는 어떤 종류의 GPGPU 계산이 해당 VBO를 방해하거나 심지어 생성하는 경우입니다.

    +

    Examples

    +

    + [example:webgl_buffergeometry_glbufferattribute Points with custom buffers]
    +

    +

    생성자

    -

    [name]( [param:WebGLBuffer buffer], [param:GLenum type], [param:Integer itemSize], [param:Integer elementSize], [param:Integer count] )

    +

    [name]( [param:WebGLBuffer buffer], [param:GLenum type], [param:Integer itemSize], [param:Integer elementSize], [param:Integer count], [param:Boolean normalized] )

    *buffer* — 반드시 [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLBuffer WebGLBuffer]여야 합니다.
    @@ -38,6 +43,15 @@

    [name]( [param:WebGLBuffer buffer], [param:GLenum type], [param:Integer item
  • gl.UNSIGNED_BYTE: 1
  • *count* — 예상되는 VBO의 꼭짓점 수. +
    + *normalized* — (optional) Applies to integer data only. + Indicates how the underlying data in the buffer maps to the values in the + GLSL code. For instance, if [page:WebGLBuffer buffer] contains data of + `gl.UNSIGNED_SHORT`, and [page:Boolean normalized] is true, the values `0 - + +65535` in the buffer data will be mapped to 0.0f - +1.0f in the GLSL + attribute. A `gl.SHORT` (signed) would map from -32768 - +32767 to -1.0f + - +1.0f. If [page:Boolean normalized] is false, the values will be + converted to floats unmodified, i.e. 32767 becomes 32767.0f.

    프로퍼티

    @@ -52,6 +66,14 @@

    [property:Integer count]

    VBO의 꼭짓점 수.

    +

    [property:Integer elementSize]

    +

    + 현재의 *type* 속성 값에 맞는 바이트 사이즈를 저장. +

    +

    + 알려진 타입 크기 리스트는 위의 (생성자)를 참고. +

    +

    [property:Boolean isGLBufferAttribute]

    읽기 전용. 언제나 *true*입니다. @@ -62,17 +84,20 @@

    [property:Integer itemSize]

    각 항목을 구성하는 값의 크기 (꼭짓점).

    -

    [property:Integer elementSize]

    +

    [property:String name]

    - 현재의 *type* 속성 값에 맞는 바이트 사이즈를 저장. + 이 속성 인스턴스의 임시 이름. 기본값은 빈 문자열입니다.

    + +

    [property:Boolean needsUpdate]

    - 알려진 타입 크기 리스트는 위의 (생성자)를 참고. + 기본값은 *false* 입니다. true로 설정하면 [page:GLBufferAttribute.version version]을 증가시킵니다.

    -

    [property:String name]

    +

    [property:Boolean normalized]

    - 이 속성 인스턴스의 임시 이름. 기본값은 빈 문자열입니다. + Indicates how the underlying data in the buffer maps to the values in the + GLSL shader code. See the constructor above for details.

    [property:GLenum type]

    @@ -84,6 +109,11 @@

    [property:GLenum type]

    *elementSize*와 함께 이 속성을 설정합니다. 추천하는 방법은 *setType* 메서드를 사용하는 것입니다.

    +

    [property:Integer version]

    +

    + 버전 넘버이며 needsUpdate 속성이 true가 될 때마다 증가합니다. +

    +

    메서드

    [method:this setBuffer]( buffer )

    @@ -98,16 +128,6 @@

    [method:this setItemSize]( itemSize )

    [method:this setCount]( count )

    *count* 속성을 설정합니다.

    -

    [property:Integer version]

    -

    - 버전 넘버이며 needsUpdate 속성이 true가 될 때마다 증가합니다. -

    - -

    [property:Boolean needsUpdate]

    -

    - 기본값은 *false* 입니다. true로 설정하면 [page:GLBufferAttribute.version version]을 증가시킵니다. -

    -

    소스코드

    [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] diff --git a/docs/api/ko/core/Layers.html b/docs/api/ko/core/Layers.html index d2531e35d4d929..65b20df5b2f691 100644 --- a/docs/api/ko/core/Layers.html +++ b/docs/api/ko/core/Layers.html @@ -21,9 +21,7 @@

    [name]

    예제

    -

    - [example:webgl_layers WebGL / layers] -

    +

    [example:webgpu_layers WebGPU / layers]

    생성자

    diff --git a/docs/api/ko/core/Object3D.html b/docs/api/ko/core/Object3D.html index 99c9331cdaf861..3e6ab8155430bd 100644 --- a/docs/api/ko/core/Object3D.html +++ b/docs/api/ko/core/Object3D.html @@ -122,6 +122,17 @@

    [property:Function onAfterRender]

    [page:Object3D], [page:Group] 혹은 [page:Bone] 인스턴스는 렌더링할 수 없으므로 이러한 객체에 대해서는 콜백이 실행되지 않습니다.

    +

    [property:Function onAfterShadow]

    +

    + An optional callback that is executed immediately after a 3D object is + rendered to a shadow map. This function is called with the following parameters: renderer, + scene, camera, shadowCamera, geometry, depthMaterial, group. +

    +

    + 이 콜백은 *렌더링 가능한* 3D 객체에만 실행됩니다. [page:Mesh], [page:Line], [page:Points] 혹은 [page:Sprite]같은 기하학 및 재질로 시각적 표현을 정의하는 3D 객체들에 한정됩니다. + [page:Object3D], [page:Group] 혹은 [page:Bone] 인스턴스는 렌더링할 수 없으므로 이러한 객체에 대해서는 콜백이 실행되지 않습니다. +

    +

    [property:Function onBeforeRender]

    3D 객체가 렌더링되기 바로 전에 실행되는 선택적 콜백입니다. @@ -132,6 +143,17 @@

    [property:Function onBeforeRender]

    [page:Object3D], [page:Group] 혹은 [page:Bone] 인스턴스는 렌더링할 수 없으므로 이러한 객체에 대해서는 콜백이 실행되지 않습니다.

    +

    [property:Function onBeforeShadow]

    +

    + An optional callback that is executed immediately before a 3D object is + rendered to a shadow map. This function is called with the following parameters: renderer, + scene, camera, shadowCamera, geometry, depthMaterial, group. +

    +

    + 이 콜백은 *렌더링 가능한* 3D 객체에만 실행됩니다. [page:Mesh], [page:Line], [page:Points] 혹은 [page:Sprite]같은 기하학 및 재질로 시각적 표현을 정의하는 3D 객체들에 한정됩니다. + [page:Object3D], [page:Group] 혹은 [page:Bone] 인스턴스는 렌더링할 수 없으므로 이러한 객체에 대해서는 콜백이 실행되지 않습니다. +

    +

    [property:Object3D parent]

    [link:https://en.wikipedia.org/wiki/Scene_graph scene graph]에 있는 객체의 부모입니다. 객체는 최대 한 개의 부모만 가질 수 있습니다.

    @@ -175,7 +197,7 @@

    [property:Object userData]

    [property:String uuid]

    이 객체 인스턴스의 [link:http://en.wikipedia.org/wiki/Universally_unique_identifier UUID]입니다. - 자동으로 할당되며, 수정할 수 없습니다. + 자동으로 할당되니 수정하지 마십시오.

    [property:Boolean visible]

    @@ -271,10 +293,13 @@

    [method:Object3D getObjectByProperty]( [param:String name], [param:Any value 객체 자신부터 시작하여 객체와 객체 자식 항목을 검색하고 일치하는 값의 첫 번째 항목을 리턴합니다.

    -

    [method:Object3D getObjectsByProperty]( [param:String name], [param:Any value] )

    +

    [method:Object3D getObjectsByProperty]( [param:String name], [param:Any value], [param:Array optionalTarget] )

    name -- 검색하고자하는 이름 프로퍼티.
    - value -- 프로퍼티의 값.

    + value -- 프로퍼티의 값.
    + optionalTarget -- (optional) target to set the result. + Otherwise a new Array is instantiated. If set, you must clear this + array prior to each call (i.e., array.length = 0;).

    개체 자체부터 시작하여 개체와 해당 자식을 검색하고 일치하는 값의 모든 개체를 반환합니다.

    diff --git a/docs/api/ko/core/Raycaster.html b/docs/api/ko/core/Raycaster.html index 640c5da46e4486..6cbe53c9dccd52 100644 --- a/docs/api/ko/core/Raycaster.html +++ b/docs/api/ko/core/Raycaster.html @@ -155,6 +155,12 @@

    [method:undefined setFromCamera]( [param:Vector2 coords], [param:Camera came 레이의 새 시점과 방향을 업데이트합니다.

    +

    [method:this setFromXRController]( [param:WebXRController controller] )

    +

    + [page:WebXRController controller] — The controller to copy the position and direction from. +

    +

    Updates the ray with a new origin and direction.

    +

    [method:Array intersectObject]( [param:Object3D object], [param:Boolean recursive], [param:Array optionalTarget] )

    [page:Object3D object] — 레이와의 교차 체크를 하는 객체입니다.
    diff --git a/docs/api/ko/core/Uniform.html b/docs/api/ko/core/Uniform.html index 691472c7f42799..90384bca23c9cd 100644 --- a/docs/api/ko/core/Uniform.html +++ b/docs/api/ko/core/Uniform.html @@ -46,7 +46,7 @@

    유니폼 타입

    [page:Number] - uint (WebGL 2) + uint [page:Number] diff --git a/docs/api/ko/core/bufferAttributeTypes/BufferAttributeTypes.html b/docs/api/ko/core/bufferAttributeTypes/BufferAttributeTypes.html index 6f1a2192b4f898..d5b48043f9c729 100644 --- a/docs/api/ko/core/bufferAttributeTypes/BufferAttributeTypes.html +++ b/docs/api/ko/core/bufferAttributeTypes/BufferAttributeTypes.html @@ -17,7 +17,6 @@

    BufferAttribute Types

    - THREE.Float64BufferAttribute THREE.Float32BufferAttribute THREE.Float16BufferAttribute THREE.Uint32BufferAttribute diff --git a/docs/api/ko/extras/core/CurvePath.html b/docs/api/ko/extras/core/CurvePath.html index 46df703a7917ab..62b2603d5ae0d0 100644 --- a/docs/api/ko/extras/core/CurvePath.html +++ b/docs/api/ko/extras/core/CurvePath.html @@ -37,7 +37,7 @@

    메서드

    [method:undefined add]( [param:Curve curve] )

    [page:.curves] 배열에 곡선을 추가합니다.

    -

    [method:undefined closePath]()

    +

    [method:this closePath]()

    path를 닫기위해 [page:LineCurve lineCurve]를 추가합니다.

    [method:Array getCurveLengths]()

    diff --git a/docs/api/ko/extras/core/Shape.html b/docs/api/ko/extras/core/Shape.html index 85a3a6423777cc..2fb85213fce666 100644 --- a/docs/api/ko/extras/core/Shape.html +++ b/docs/api/ko/extras/core/Shape.html @@ -39,8 +39,7 @@

    예제

    [example:webgl_geometry_shapes geometry / shapes ]
    - [example:webgl_geometry_extrude_shapes geometry / extrude / shapes ]
    - [example:webgl_geometry_extrude_shapes2 geometry / extrude / shapes2 ]
    + [example:webgl_geometry_extrude_shapes geometry / extrude / shapes ]

    diff --git a/docs/api/ko/extras/core/ShapePath.html b/docs/api/ko/extras/core/ShapePath.html index 51c5ac9a8e6638..e3ec60caa2e181 100644 --- a/docs/api/ko/extras/core/ShapePath.html +++ b/docs/api/ko/extras/core/ShapePath.html @@ -13,11 +13,6 @@

    [name]

    This class is used to convert 이 클래스는 연속된 shape들을 [page:Path] 배열로 바꾸는데에 사용되며, 일례로 SVG shape를 path로 바꾸는 것이 있습니다.(아래 예제를 확인하세요).

    -

    예제

    -

    - [example:webgl_geometry_extrude_shapes2 geometry / extrude / shapes2] -

    -

    생성자

    diff --git a/docs/api/ko/geometries/CapsuleGeometry.html b/docs/api/ko/geometries/CapsuleGeometry.html index a6dab61f3d5751..c198269b7c53fd 100644 --- a/docs/api/ko/geometries/CapsuleGeometry.html +++ b/docs/api/ko/geometries/CapsuleGeometry.html @@ -45,13 +45,14 @@

    코드 예제

    생성자

    -

    [name]([param:Float radius], [param:Float length], [param:Integer capSubdivisions], [param:Integer radialSegments])

    +

    [name]([param:Float radius], [param:Float height], [param:Integer capSegments], [param:Integer radialSegments], [param:Integer heightSegments])

    radius — 캡슐의 반경입니다. Optional; 기본값은 1 입니다.
    - length — 중간 구역의 길이입니다. Optional; 기본값은 1 입니다.
    + height — 중간 구역의 높이입니다. Optional; 기본값은 1 입니다.
    capSegments — 캡을 구성하는 데 사용된 곡선 면의 수입니다. Optional; 기본값은 4 입니다.
    radialSegments — 캡슐 둘레 주변의 분할된 면의 수입니다. Optional; 기본값은 8 입니다.
    + heightSegments — 캡슐의 높이에 따라 분할된 면의 수입니다. Optional; 기본값은 1 입니다.

    프로퍼티

    diff --git a/docs/api/pt-br/animation/AnimationUtils.html b/docs/api/pt-br/animation/AnimationUtils.html index 6ffc76fcb1339b..04002261794238 100644 --- a/docs/api/pt-br/animation/AnimationUtils.html +++ b/docs/api/pt-br/animation/AnimationUtils.html @@ -17,11 +17,6 @@

    [name]

    Métodos

    -

    [method:Array arraySlice]( array, from, to )

    -

    - É o mesmo que Array.prototype.slice, mas também funciona em arrays tipados. -

    -

    [method:Array convertArray]( array, type, forceClone )

    Converte um array em um tipo específico. diff --git a/docs/api/pt-br/audio/Audio.html b/docs/api/pt-br/audio/Audio.html index 12d054bccc45b2..7caa6cb4bacd39 100644 --- a/docs/api/pt-br/audio/Audio.html +++ b/docs/api/pt-br/audio/Audio.html @@ -147,7 +147,7 @@

    [method:Float getPlaybackRate]()

    Retorna o valor do [page:Audio.playbackRate playbackRate].

    -

    [method:Float getVolume]( value )

    +

    [method:Float getVolume]()

    Retorna o volume atual.

    diff --git a/docs/api/pt-br/cameras/OrthographicCamera.html b/docs/api/pt-br/cameras/OrthographicCamera.html index c4636a06bb53a1..007bf1702213fe 100644 --- a/docs/api/pt-br/cameras/OrthographicCamera.html +++ b/docs/api/pt-br/cameras/OrthographicCamera.html @@ -37,7 +37,6 @@

    Exemplos

    [example:webgl_postprocessing_dof2 postprocessing / dof2 ]
    [example:webgl_postprocessing_godrays postprocessing / godrays ]
    [example:webgl_rtt rtt ]
    - [example:webgl_shaders_tonemapping shaders / tonemapping ]
    [example:webgl_shadowmap shadowmap ]

    diff --git a/docs/api/pt-br/cameras/PerspectiveCamera.html b/docs/api/pt-br/cameras/PerspectiveCamera.html index a0329c2b364695..2f854629292cfb 100644 --- a/docs/api/pt-br/cameras/PerspectiveCamera.html +++ b/docs/api/pt-br/cameras/PerspectiveCamera.html @@ -90,7 +90,7 @@

    [property:Float near]

    Plano próximo do tronco da câmera. O padrão é `0.1`.

    - O intervalo válido é maior que 0 e menor que o valor atual do plano [page:.far far] (distante). + O intervalo válido é maior que `0` e menor que o valor atual do plano [page:.far far] (distante). Observe que, diferentemente da [page:OrthographicCamera], `0` não é um valor válido para um plano próximo da PerspectiveCamera.

    diff --git a/docs/api/pt-br/constants/Renderer.html b/docs/api/pt-br/constants/Renderer.html index ce399497219b44..a8a8f48fb478ea 100644 --- a/docs/api/pt-br/constants/Renderer.html +++ b/docs/api/pt-br/constants/Renderer.html @@ -46,6 +46,8 @@

    Mapeamento de Tom (Tone Mapping)

    THREE.ReinhardToneMapping THREE.CineonToneMapping THREE.ACESFilmicToneMapping + THREE.AgXToneMapping + THREE.NeutralToneMapping THREE.CustomToneMapping

    @@ -54,10 +56,13 @@

    Mapeamento de Tom (Tone Mapping)

    médio da faixa dinâmica baixa de um monitor de computador padrão ou tela de dispositivo móvel.

    - THREE.LinearToneMapping, THREE.ReinhardToneMapping, THREE.CineonToneMapping e THREE.ACESFilmicToneMapping são implementações internas de mapeamento de tom. + THREE.LinearToneMapping, THREE.ReinhardToneMapping, THREE.CineonToneMapping, THREE.ACESFilmicToneMapping, THREE.AgXToneMapping e THREE.NeutralToneMapping são implementações internas de mapeamento de tom. THREE.CustomToneMapping espera uma implementação personalizada modificando o código GLSL do sombreador de fragmento do material. Vejo o exemplo [example:webgl_tonemapping WebGL / tonemapping].

    +

    + THREE.NeutralToneMapping is an implementation based on the Khronos 3D Commerce Group standard tone mapping. +

    Source

    diff --git a/docs/api/pt-br/constants/Textures.html b/docs/api/pt-br/constants/Textures.html index 43b5f6c28f539e..572119e41f9189 100644 --- a/docs/api/pt-br/constants/Textures.html +++ b/docs/api/pt-br/constants/Textures.html @@ -129,6 +129,7 @@

    Tipos

    THREE.UnsignedShort4444Type THREE.UnsignedShort5551Type THREE.UnsignedInt248Type + THREE.UnsignedInt5999Type

    Para uso com a propriedade [page:Texture.type type] de uma textura, que deve corresponder ao formato correto. Veja abaixo para detalhes.

    @@ -143,10 +144,9 @@

    Formatos

    THREE.RedIntegerFormat THREE.RGFormat THREE.RGIntegerFormat + THREE.RGBFormat THREE.RGBAFormat THREE.RGBAIntegerFormat - THREE.LuminanceFormat - THREE.LuminanceAlphaFormat THREE.DepthFormat THREE.DepthStencilFormat @@ -160,34 +160,21 @@

    Formatos

    [page:constant RedIntegerFormat] descarta os componentes verde e azul e lê apenas o componente vermelho. Os texels são lidos como inteiros em vez de ponto flutuante. - (só pode ser usado com um contexto de renderização WebGL 2).

    [page:constant RGFormat] descarta os componentes alfa e azul e lê os componentes vermelho e verde. - (só pode ser usado com um contexto de renderização WebGL 2).

    [page:constant RGIntegerFormat] descarta os componentes alfa e azul e lê os componentes vermelho e verde. Os texels são lidos como inteiros em vez de ponto flutuante. - (só pode ser usado com um contexto de renderização WebGL 2).

    [page:constant RGBAFormat] é o padrão e lê os componentes vermelho, verde, azul e alfa.

    [page:constant RGBAIntegerFormat] é o padrão e lê os componentes vermelho, verde, azul e alfa. Os texels são lidos como inteiros em vez de ponto flutuante. - (só pode ser usado com um contexto de renderização WebGL 2).

    - [page:constant LuminanceFormat] lê cada elemento como um único componente de luminância. - Este é então convertido em um ponto flutuante, fixado no intervalo [0,1], e então montado - em um elemento RGBA, colocando o valor de luminância nos canais vermelho, verde e azul, - e anexando 1.0 ao canal alfa.

    - - [page:constant LuminanceAlphaFormat] lê cada elemento como um duplo de luminância/alfa. - O mesmo processo ocorre para o [page:constant LuminanceFormat], exceto que o - o canal alfa pode ter valores diferentes de `1.0`.

    - [page:constant DepthFormat] lê cada elemento como um único valor de profundidade, converte-o em ponto flutuante e fixa no intervalo [0,1]. Este é o padrão para [page:DepthTexture DepthTexture].

    @@ -346,10 +333,6 @@

    Formatos Internos

    - - Atenção: alterar o formato interno de uma textura afetará a - textura apenas quando for usado um contexto de renderização WebGL 2.

    - Para uso com a propriedade [page:Texture.internalFormat internalFormat] de uma textura, definem como os elementos de uma textura, ou `texels`, são armazenados na GPU.

    diff --git a/docs/api/zh/animation/AnimationAction.html b/docs/api/zh/animation/AnimationAction.html index d1fe7dc2f1462c..be1ad88ecad4b5 100644 --- a/docs/api/zh/animation/AnimationAction.html +++ b/docs/api/zh/animation/AnimationAction.html @@ -228,7 +228,7 @@

    [method:this reset]()

    [method:this setDuration]( [param:Number durationInSeconds] )

    - 设置单此循环的持续时间(通过调整时间比例([page:.timeScale timeScale])以及停用所有的变形)。此方法可以链式调用。 + 设置单次循环的持续时间(通过调整时间比例([page:.timeScale timeScale])以及停用所有的变形)。此方法可以链式调用。

    [method:this setEffectiveTimeScale]( [param:Number timeScale] )

    diff --git a/docs/api/zh/animation/AnimationUtils.html b/docs/api/zh/animation/AnimationUtils.html index dd68dc6ce9910b..75966937466c14 100644 --- a/docs/api/zh/animation/AnimationUtils.html +++ b/docs/api/zh/animation/AnimationUtils.html @@ -17,11 +17,6 @@

    [name]

    方法

    -

    [method:Array arraySlice]( array, from, to )

    -

    - 和Array.prototype.slice作用一样, 但也适用于类型化数组. -

    -

    [method:Array convertArray]( array, type, forceClone )

    将数组转换为某种特定类型。 diff --git a/docs/api/zh/audio/Audio.html b/docs/api/zh/audio/Audio.html index a126708032a184..86c3265ea52cff 100644 --- a/docs/api/zh/audio/Audio.html +++ b/docs/api/zh/audio/Audio.html @@ -137,7 +137,7 @@

    [method:Float getPlaybackRate]()

    返回[page:Audio.playbackRate playbackRate]的值.

    -

    [method:Float getVolume]( value )

    +

    [method:Float getVolume]()

    返回音量.

    diff --git a/docs/api/zh/cameras/ArrayCamera.html b/docs/api/zh/cameras/ArrayCamera.html index 40127bf1e85aa3..ff3917413b2970 100644 --- a/docs/api/zh/cameras/ArrayCamera.html +++ b/docs/api/zh/cameras/ArrayCamera.html @@ -39,7 +39,7 @@

    [property:Array cameras]

    [property:Boolean isArrayCamera]

    - Read-only flag to check if a given object is of type [name]. + 只读属性,用于检查给定对象是否为[name]类型。

    方法

    diff --git a/docs/api/zh/cameras/Camera.html b/docs/api/zh/cameras/Camera.html index fe6c78cc17b4ae..e10ab92e5e8cee 100644 --- a/docs/api/zh/cameras/Camera.html +++ b/docs/api/zh/cameras/Camera.html @@ -34,7 +34,7 @@

    属性

    [property:Boolean isCamera]

    - Read-only flag to check if a given object is of type [name]. + 只读属性,用于检查给定对象是否为[name]类型。

    [property:Layers layers]

    diff --git a/docs/api/zh/cameras/OrthographicCamera.html b/docs/api/zh/cameras/OrthographicCamera.html index b07bd82e261d54..33dfd69d2e47e7 100644 --- a/docs/api/zh/cameras/OrthographicCamera.html +++ b/docs/api/zh/cameras/OrthographicCamera.html @@ -37,7 +37,6 @@

    例子

    [example:webgl_postprocessing_dof2 postprocessing / dof2 ]
    [example:webgl_postprocessing_godrays postprocessing / godrays ]
    [example:webgl_rtt rtt ]
    - [example:webgl_shaders_tonemapping shaders / tonemapping ]
    [example:webgl_shadowmap shadowmap ]

    @@ -79,7 +78,7 @@

    [property:Float far]

    [property:Boolean isOrthographicCamera]

    - Read-only flag to check if a given object is of type [name]. + 只读属性,用于检查给定对象是否为[name]类型。

    [property:Float left]

    diff --git a/docs/api/zh/cameras/PerspectiveCamera.html b/docs/api/zh/cameras/PerspectiveCamera.html index c47dc92b4f7427..5cf98a330ec594 100644 --- a/docs/api/zh/cameras/PerspectiveCamera.html +++ b/docs/api/zh/cameras/PerspectiveCamera.html @@ -82,7 +82,7 @@

    [property:Float fov]

    [property:Boolean isPerspectiveCamera]

    - Read-only flag to check if a given object is of type [name]. + 只读属性,用于检查给定的对象是否为 [name]。

    diff --git a/docs/api/zh/constants/BufferAttributeUsage.html b/docs/api/zh/constants/BufferAttributeUsage.html index 0cd24a6e34cec9..50dc6a0da54c9c 100644 --- a/docs/api/zh/constants/BufferAttributeUsage.html +++ b/docs/api/zh/constants/BufferAttributeUsage.html @@ -1,32 +1,34 @@ - - - - - - - -

    Buffer Attribute Usage Constants

    - -

    - The usage constants can be used to provide a hint to the API regarding how the geometry buffer attribute will be used in order to optimize performance. -

    - -

    Code Example

    - - + + + + + + + + + +

    缓冲区属性使用常量([name])

    + +

    + 使用常量可用于向 API 提供有关如何使用几何缓冲区属性以优化性能的提示。 +

    + +

    代码示例

    + + const geometry = new THREE.BufferGeometry(); const positionAttribute = new THREE.BufferAttribute( array, 3 , false ); positionAttribute.setUsage( THREE.DynamicDrawUsage ); geometry.setAttribute( 'position', positionAttribute ); -

    Examples

    -

    [example:webgl_buffergeometry_drawrange materials / buffergeometry / drawrange ]

    +

    例子

    +

    [example:webgl_buffergeometry_drawrange materials / buffergeometry / drawrange ]

    -

    Geometry Usage

    - +

    几何体相关(Geometry Usage)

    + THREE.StaticDrawUsage THREE.DynamicDrawUsage THREE.StreamDrawUsage @@ -40,12 +42,14 @@

    Geometry Usage

    THREE.StreamCopyUsage
    - For more detailed information on each of these constants see [link:https://www.khronos.org/opengl/wiki/Buffer_Object#Buffer_Object_Usage this OpenGL documentation]. + 有关每个常量的更多详细信息,请参阅 + [link:https://www.khronos.org/opengl/wiki/Buffer_Object#Buffer_Object_Usage OpenGL 文档]。 + +

    源代码

    -

    Source

    +

    + [link:https://github.com/mrdoob/three.js/blob/master/src/constants.js src/constants.js] +

    + -

    - [link:https://github.com/mrdoob/three.js/blob/master/src/constants.js src/constants.js] -

    - - + \ No newline at end of file diff --git a/docs/api/zh/constants/CustomBlendingEquations.html b/docs/api/zh/constants/CustomBlendingEquations.html index 8451dbb90f3b2d..c836ed855435d8 100644 --- a/docs/api/zh/constants/CustomBlendingEquations.html +++ b/docs/api/zh/constants/CustomBlendingEquations.html @@ -48,6 +48,10 @@

    源因子

    THREE.DstColorFactor THREE.OneMinusDstColorFactor THREE.SrcAlphaSaturateFactor + THREE.ConstantColorFactor + THREE.OneMinusConstantColorFactor + THREE.ConstantAlphaFactor + THREE.OneMinusConstantAlphaFactor

    目标因子

    diff --git a/docs/api/zh/constants/Renderer.html b/docs/api/zh/constants/Renderer.html index 0d9555501c5c05..6bf048f4fb3865 100644 --- a/docs/api/zh/constants/Renderer.html +++ b/docs/api/zh/constants/Renderer.html @@ -46,6 +46,9 @@

    色调映射

    THREE.ReinhardToneMapping THREE.CineonToneMapping THREE.ACESFilmicToneMapping + THREE.AgXToneMapping + THREE.NeutralToneMapping + THREE.CustomToneMapping

    这些常量定义了WebGLRenderer中[page:WebGLRenderer.toneMapping toneMapping]的属性。 @@ -55,7 +58,9 @@

    色调映射

    请查看示例:[example:webgl_tonemapping WebGL / tonemapping]。

    - +

    + THREE.NeutralToneMapping is an implementation based on the Khronos 3D Commerce Group standard tone mapping. +

    源代码

    diff --git a/docs/api/zh/constants/Textures.html b/docs/api/zh/constants/Textures.html index 893a4f5d06873a..7694b295f22498 100644 --- a/docs/api/zh/constants/Textures.html +++ b/docs/api/zh/constants/Textures.html @@ -119,6 +119,7 @@

    类型

    THREE.UnsignedShort4444Type THREE.UnsignedShort5551Type THREE.UnsignedInt248Type + THREE.UnsignedInt5999Type

    这些常量用于纹理的[page:Texture.type type]属性,这些属性必须与正确的格式相对应。详情请查看下方。

    @@ -133,10 +134,9 @@

    格式

    THREE.RedIntegerFormat THREE.RGFormat THREE.RGIntegerFormat + THREE.RGBFormat THREE.RGBAFormat THREE.RGBAIntegerFormat - THREE.LuminanceFormat - THREE.LuminanceAlphaFormat THREE.DepthFormat THREE.DepthStencilFormat @@ -149,31 +149,21 @@

    格式

    [page:constant RedIntegerFormat] discards the green and blue components and reads just the red component. The texels are read as integers instead of floating point. - (can only be used with a WebGL 2 rendering context).

    [page:constant RGFormat] discards the alpha, and blue components and reads the red, and green components. - (can only be used with a WebGL 2 rendering context).

    [page:constant RGIntegerFormat] discards the alpha, and blue components and reads the red, and green components. The texels are read as integers instead of floating point. - (can only be used with a WebGL 2 rendering context).

    [page:constant RGBAFormat] 是默认值,它将读取红、绿、蓝和Alpha分量。

    [page:constant RGBAIntegerFormat] is the default and reads the red, green, blue and alpha components. The texels are read as integers instead of floating point. - (can only be used with a WebGL 2 rendering context).

    - [page:constant LuminanceFormat] 将每个元素作为单独的亮度分量来读取。 - 将其转换为范围限制在[0,1]区间的浮点数,然后通过将亮度值放入红、绿、蓝通道,并将1.0赋给Alpha通道,来组装成一个RGBA元素。

    - - [page:constant LuminanceAlphaFormat] 将每个元素同时作为亮度分量和Alpha分量来读取。 - 和上面[page:constant LuminanceFormat]的处理过程是一致的,除了Alpha分量具有除了*1.0*以外的值。

    - [page:constant DepthFormat]将每个元素作为单独的深度值来读取,将其转换为范围限制在[0,1]区间的浮点数。 它是[page:DepthTexture DepthTexture]的默认值。

    @@ -329,10 +319,6 @@

    Internal Formats

    - - Heads up: changing the internal format of a texture will only affect the - texture when using a WebGL 2 rendering context.

    - For use with a texture's [page:Texture.internalFormat internalFormat] property, these define how elements of a texture, or *texels*, are stored on the GPU.

    diff --git a/docs/api/zh/core/BufferAttribute.html b/docs/api/zh/core/BufferAttribute.html index 79631d9aa50394..940681ab7c1f46 100644 --- a/docs/api/zh/core/BufferAttribute.html +++ b/docs/api/zh/core/BufferAttribute.html @@ -51,11 +51,18 @@

    [property:TypedArray array]

    [property:Integer count]

    - 保存 [page:BufferAttribute.array array] 除以 [page:BufferAttribute.itemSize itemSize] 之后的大小。

    + 保存 [page:BufferAttribute.array array] 除以 [page:BufferAttribute.itemSize itemSize] 之后的大小。Read-only property.

    若缓存存储三元组(例如顶点位置、法向量、颜色值),则该值应等于队列中三元组的个数。

    +

    [property:Number gpuType]

    +

    + 配置着色器中使用的绑定 GPU 类型。[page:BufferAttribute THREE.FloatType] 或 [page:BufferAttribute THREE.IntType],默认为 [page:BufferAttribute THREE.FloatType]。 + + 注意:这仅对整数数组有效,对于浮点数组不可配置。对于精度较低的浮点类型,请参阅 [page:BufferAttributeTypes THREE.Float16BufferAttribute]。 +

    +

    [property:Boolean isBufferAttribute]

    用于判断对象是否为[name]类型的只读标记. @@ -116,6 +123,16 @@

    [method:this applyNormalMatrix]( [param:Matrix3 m] )

    [method:this transformDirection]( [param:Matrix4 m] )

    将矩阵[page:Matrix4 m]应用到此BufferAttribute的每一个Vector3元素中,并将所有元素解释为方向向量。

    + +

    [method:this addUpdateRange]( [param:Integer start], [param:Integer count] )

    +

    + 在数据数组中添加要在 GPU 上更新的数据范围。将描述范围的对象添加到 [page:BufferAttribute.updateRanges updateRanges] 数组。 +

    + +

    [method:this clearUpdateRanges]()

    +

    + 清除 [page:BufferAttribute.updateRanges updateRanges] 数组。 +

    [method:BufferAttribute clone]()

    返回该 BufferAttribute 的拷贝。

    @@ -128,6 +145,9 @@

    [method:this copyArray]( array )

    [method:this copyAt] ( [param:Integer index1], [param:BufferAttribute bufferAttribute], [param:Integer index2] )

    将一个矢量从 bufferAttribute[index2] 拷贝到 [page:BufferAttribute.array array][index1] 中。

    +

    [method:Number getComponent]( [param:Integer index], [param:Integer component] )

    +

    返回给定索引处的向量的给定分量。

    +

    [method:Number getX]( [param:Integer index] )

    获取给定索引的矢量的第一维元素 (即 X 值)。

    @@ -157,7 +177,10 @@

    [method:this set] ( [param:Array value], [param:Integer offset] )

    [method:this setUsage] ( [param:Usage value] )

    -

    Set [page:BufferAttribute.usage usage] to value. See usage [page:BufferAttributeUsage constants] for all possible input values.

    +

    设置 [page:BufferAttribute.usage usage] 值。查看所有可能的输入值的 usage [page:BufferAttributeUsage constants]。

    + +

    [method:Number setComponent]( [param:Integer index], [param:Integer component], [param:Float value] )

    +

    在给定索引处设置向量的给定分量。

    [method:this setX]( [param:Integer index], [param:Float x] )

    设置给定索引的矢量的第一维数据(设置 X 值)。

    diff --git a/docs/api/zh/core/BufferGeometry.html b/docs/api/zh/core/BufferGeometry.html index a6142f561ccc53..dd33f59c74f34b 100644 --- a/docs/api/zh/core/BufferGeometry.html +++ b/docs/api/zh/core/BufferGeometry.html @@ -37,6 +37,30 @@

    代码示例

    const material = new THREE.MeshBasicMaterial( { color: 0xff0000 } ); const mesh = new THREE.Mesh( geometry, material ); + +

    代码示例 (索引Index)

    + + + const geometry = new THREE.BufferGeometry(); + + const vertices = new Float32Array( [ + -1.0, -1.0, 1.0, // v0 + 1.0, -1.0, 1.0, // v1 + 1.0, 1.0, 1.0, // v2 + -1.0, 1.0, 1.0, // v3 + ] ); + + const indices = [ + 0, 1, 2, + 2, 3, 0, + ]; + + geometry.setIndex( indices ); + geometry.setAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) ); + + const material = new THREE.MeshBasicMaterial( { color: 0xff0000 } ); + const mesh = new THREE.Mesh( geometry, material ); +

    例子

    @@ -82,6 +106,8 @@

    [property:Object drawRange]

    { start: 0, count: Infinity } + 对于非索引 BufferGeometry,count 是要渲染的顶点数。 + 对于索引 BufferGeometry,count 是要渲染的索引数。

    [property:Array groups]

    @@ -94,8 +120,8 @@

    [property:Array groups]

    start 表明当前 draw call 中的没有索引的几何体的几何体的第一个顶点;或者第一个三角面片的索引。 count 指明当前分割包含多少顶点(或 indices)。 materialIndex 指出当前用到的材质队列的索引。

    - - 通过 [page:.addGroup] 来增加组,而不是直接更改当前队列。 + 通过 [page:.addGroup] 来增加组,而不是直接更改当前队列。

    + 每个顶点和索引必须恰好属于一个组,不同组之间不得共享顶点或索引,并且不得留下未使用的顶点或索引。

    @@ -159,54 +185,52 @@

    方法

    [page:EventDispatcher EventDispatcher] 在该类上可用的所有方法。

    -

    [method:this setAttribute]( [param:String name], [param:BufferAttribute attribute] )

    -

    - 为当前几何体设置一个 attribute 属性。在类的内部,有一个存储 [page:.attributes] 的 hashmap, - 通过该 hashmap,遍历 attributes 的速度会更快。而使用该方法,可以向 hashmap 内部增加 attribute。 - 所以,你需要使用该方法来添加 attributes。 -

    -

    [method:undefined addGroup]( [param:Integer start], [param:Integer count], [param:Integer materialIndex] )

    为当前几何体增加一个 group,详见 [page:BufferGeometry.groups groups] 属性。

    -

    [method:this applyMatrix4]( [param:Matrix4 matrix] )

    用给定矩阵转换几何体的顶点坐标。

    +

    [method:this applyQuaternion]( [param:Quaternion quaternion] )

    +

    用给四元数表示的旋转应用于几何体的顶点坐标。

    +

    [method:this center] ()

    根据边界矩形将几何体居中。

    + +

    [method:undefined clearGroups]( )

    +

    清空所有的 groups。

    [method:BufferGeometry clone]()

    克隆当前的 BufferGeometry。

    -

    [method:this copy]( [param:BufferGeometry bufferGeometry] )

    -

    将参数指定的 BufferGeometry 的值拷贝到当前 BufferGeometry 中。

    - -

    [method:undefined clearGroups]( )

    -

    清空所有的 groups。

    -

    [method:undefined computeBoundingBox]()

    - 计算当前几何体的的边界矩形,该操作会更新已有 [param:.boundingBox]。
    + 计算当前几何体的的边界矩形,该操作会更新已有 [page:.boundingBox]。
    边界矩形不会默认计算,需要调用该接口指定计算边界矩形,否则保持默认值 *null*。

    [method:undefined computeBoundingSphere]()

    - 计算当前几何体的的边界球形,该操作会更新已有 [param:.boundingSphere]。
    + 计算当前几何体的的边界球形,该操作会更新已有 [page:.boundingSphere]。
    边界球形不会默认计算,需要调用该接口指定计算边界球形,否则保持默认值 *null*。

    [method:undefined computeTangents]()

    计算并向此geometry中添加tangent attribute。
    - 只支持索引化的几何体对象,并且必须拥有position(位置),normal(法向)和 uv attributes。如果使用了切线空间法向贴图,最好使用[page:BufferGeometryUtils.computeMikkTSpaceTangents]中的MikkTSpace算法。 + 只支持索引化的几何体对象,并且必须拥有position(位置),normal(法向)和 uv attributes。如果使用了切线空间法向贴图,最好使用 [page:BufferGeometryUtils.computeMikkTSpaceTangents] 中的MikkTSpace算法。

    [method:undefined computeVertexNormals]()

    -

    通过面片法向量的平均值计算每个顶点的法向量。

    +

    通过面片法向量的平均值计算每个顶点的法向量。对于索引几何体,该方法将每个顶点法线设置为共享该顶点的面的面法线的平均值。对于非索引几何体,顶点不共享,该方法将每个顶点法线设置为与面法线相同。

    + +

    [method:this copy]( [param:BufferGeometry bufferGeometry] )

    +

    将参数指定的 BufferGeometry 的值拷贝到当前 BufferGeometry 中。

    + +

    [method:BufferAttribute deleteAttribute]( [param:String name] )

    +

    删除具有指定名称的 [page:BufferAttribute attribute]。

    [method:undefined dispose]()

    @@ -235,9 +259,6 @@

    [method:undefined normalizeNormals]()

    几何体中的每个法向量长度将会为 1。这样操作会更正光线在表面的效果。

    -

    [method:BufferAttribute deleteAttribute]( [param:String name] )

    -

    删除具有指定名称的 [page:BufferAttribute attribute]。

    -

    [method:this rotateX] ( [param:Float radians] )

    在 X 轴上旋转几何体。该操作一般在一次处理中完成,不会循环处理。典型的用法是通过调用 [page:Object3D.rotation] 实时旋转几何体。 @@ -258,24 +279,30 @@

    [method:this scale] ( [param:Float x], [param:Float y], [param:Float z] ) -

    [method:this setIndex] ( [param:BufferAttribute index] )

    -

    设置缓存的 [page:.index]。

    +

    [method:this setAttribute]( [param:String name], [param:BufferAttribute attribute] )

    +

    + 为当前几何体设置一个 attribute 属性。在类的内部,有一个存储 [page:.attributes] 的 hashmap, + 通过该 hashmap,遍历 attributes 的速度会更快。 +

    [method:undefined setDrawRange] ( [param:Integer start], [param:Integer count] )

    -

    设置缓存的 [page:.drawRange]。详见相关属性说明。

    +

    设置 [page:.drawRange] 属性。对于非索引 BufferGeometry,count 是要渲染的顶点数。对于索引 BufferGeometry,count 是要渲染的索引数。

    [method:this setFromPoints] ( [param:Array points] )

    -

    通过点队列设置该 BufferGeometry 的 attribute。

    +

    通过基于给定的 points 设置几何图形的位置属性。该数组可以保存 Vector2 或 Vector3 的实例。使用二维数据时,所有顶点的 z 坐标均设置为 0。如果该方法与现有位置属性一起使用,则顶点数据将被数组中的数据覆盖。数组的长度必须与顶点数匹配。

    + +

    [method:this setIndex] ( [param:BufferAttribute index] )

    +

    设置缓存的 [page:.index]。

    [method:Object toJSON]()

    -

    返回代表该 BufferGeometry 的 JSON 对象。

    +

    返回代表该 BufferGeometry 符合 [link:https://github.com/mrdoob/three.js/wiki/JSON-Object-Scene-format-4 Object/Scene 规范] 的 JSON 对象。

    [method:BufferGeometry toNonIndexed]()

    返回已索引的 BufferGeometry 的非索引版本。

    [method:this translate] ( [param:Float x], [param:Float y], [param:Float z] )

    - 移动几何体。该操作一般在一次处理中完成,不会循环处理。典型的用法是通过调用 [page:Object3D.rotation] 实时旋转几何体。 + 移动几何体。该操作一般在一次处理中完成,不会循环处理。典型的用法是通过调用 [page:Object3D.position] 实时移动几何体。

    Source

    diff --git a/docs/api/zh/core/Clock.html b/docs/api/zh/core/Clock.html index 424a78893be7bf..bf290dac5c9c6e 100644 --- a/docs/api/zh/core/Clock.html +++ b/docs/api/zh/core/Clock.html @@ -9,8 +9,7 @@

    [name]

    - 该对象用于跟踪时间。如果[link:https://developer.mozilla.org/en-US/docs/Web/API/Performance/now performance.now]可用,则 - Clock 对象通过该方法实现,否则回落到使用略欠精准的[link:https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Date/now Date.now]来实现。 + 该对象用于跟踪时间。如果[link:https://developer.mozilla.org/en-US/docs/Web/API/Performance/now performance.now]。

    diff --git a/docs/api/zh/core/GLBufferAttribute.html b/docs/api/zh/core/GLBufferAttribute.html index d013c8ea4cce5e..531adce7099753 100644 --- a/docs/api/zh/core/GLBufferAttribute.html +++ b/docs/api/zh/core/GLBufferAttribute.html @@ -16,7 +16,7 @@

    [name]

    构造方法(Constructor)

    -

    [name]( [param:WebGLBuffer buffer], [param:GLenum type], [param:Integer itemSize], [param:Integer elementSize], [param:Integer count] )

    +

    [name]( [param:WebGLBuffer buffer], [param:GLenum type], [param:Integer itemSize], [param:Integer elementSize], [param:Integer count], [param:Boolean normalized] )

    *buffer* — 必须是 [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLBuffer WebGLBuffer].
    @@ -35,6 +35,20 @@

    [name]( [param:WebGLBuffer buffer], [param:GLenum type], [param:Integer item
  • gl.UNSIGNED_BYTE: 1
  • *count* — VBO 中预期的顶点数。 +
    + *normalized* — (optional) Applies to integer data only. + Indicates how the underlying data in the buffer maps to the values in the + GLSL code. For instance, if [page:WebGLBuffer buffer] contains data of + `gl.UNSIGNED_SHORT`, and [page:Boolean normalized] is true, the values `0 - + +65535` in the buffer data will be mapped to 0.0f - +1.0f in the GLSL + attribute. A `gl.SHORT` (signed) would map from -32768 - +32767 to -1.0f + - +1.0f. If [page:Boolean normalized] is false, the values will be + converted to floats unmodified, i.e. 32767 becomes 32767.0f. +

    + +

    Examples

    +

    + [example:webgl_buffergeometry_glbufferattribute Points with custom buffers]

    特性(Properties)

    @@ -49,6 +63,14 @@

    [property:Integer count]

    VBO 中的预期顶点数。

    +

    [property:Integer elementSize]

    +

    + 存储当前类型属性值的相应大小(以字节为单位)。 +

    +

    + 有关已知类型大小的列表,请参见上面的(构造函数)。 +

    +

    [property:Boolean isGLBufferAttribute]

    只读。值永远为"true"。 @@ -59,17 +81,20 @@

    [property:Integer itemSize]

    每个项目(顶点)组成多少个值。

    -

    [property:Integer elementSize]

    +

    [property:String name]

    - 存储当前类型属性值的相应大小(以字节为单位)。 + 该attribute实例的别名,默认值为空字符串。

    + +

    [property:Boolean needsUpdate]

    - 有关已知类型大小的列表,请参见上面的(构造函数)。 + 默认为假。将此设置为 true 增量[page:GLBufferAttribute.version 版本]

    -

    [property:String name]

    +

    [property:Boolean normalized]

    - 该attribute实例的别名,默认值为空字符串。 + Indicates how the underlying data in the buffer maps to the values in the + GLSL shader code. See the constructor above for details.

    [property:GLenum type]

    @@ -80,6 +105,11 @@

    [property:GLenum type]

    将此属性与elementSize一起设置。推荐的方法是使用setType方法。

    +

    [property:Integer version]

    +

    + 版本号,每次将needsUpdate属性设置为true时递增。 +

    +

    方法(Methods)

    [method:this setBuffer]( buffer )

    @@ -94,16 +124,6 @@

    [method:this setItemSize]( itemSize )

    [method:this setCount]( count )

    设置计数属性。

    -

    [property:Integer version]

    -

    - 版本号,每次将needsUpdate属性设置为true时递增。 -

    - -

    [property:Boolean needsUpdate]

    -

    - 默认为假。将此设置为 true 增量[page:GLBufferAttribute.version 版本] -

    -

    源代码(Source)

    [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] diff --git a/docs/api/zh/core/InstancedBufferGeometry.html b/docs/api/zh/core/InstancedBufferGeometry.html index 219ca0d8ea4df6..4665c46e34ae1c 100644 --- a/docs/api/zh/core/InstancedBufferGeometry.html +++ b/docs/api/zh/core/InstancedBufferGeometry.html @@ -30,7 +30,7 @@

    [property:Number instanceCount]

    [property:Boolean isInstancedBufferGeometry]

    - Read-only flag to check if a given object is of type [name]. + 只读属性,用于检查给定对象是否为[name]类型。

    方法

    diff --git a/docs/api/zh/core/InterleavedBufferAttribute.html b/docs/api/zh/core/InterleavedBufferAttribute.html index 2e345765fde56d..3822d14ba23482 100644 --- a/docs/api/zh/core/InterleavedBufferAttribute.html +++ b/docs/api/zh/core/InterleavedBufferAttribute.html @@ -40,7 +40,7 @@

    [property:Integer count]

    [property:Boolean isInterleavedBufferAttribute]

    - Read-only flag to check if a given object is of type [name]. + 只读属性,用于检查给定对象是否为[name]类型。

    [property:Integer itemSize]

    diff --git a/docs/api/zh/core/Layers.html b/docs/api/zh/core/Layers.html index df08d172de6868..07876ff71a703d 100644 --- a/docs/api/zh/core/Layers.html +++ b/docs/api/zh/core/Layers.html @@ -10,9 +10,9 @@

    [name]

    - [page:Layers] 对象为 [page:Object3D] 分配 1个到 32 个图层。32个图层从 0 到 31 编号标记。 - 在内部实现上,每个图层对象被存储为一个 [link:https://en.wikipedia.org/wiki/Mask_(computing) bit mask], - 默认的,所有 [page:Object3D] 对象都存储在第 0 个图层上。

    + [page:Layers] 对象将 [page:Object3D] 分配给编号为0到31的32个层中的1个或多个, + 这些层在内部存储为 [link:https://en.wikipedia.org/wiki/Mask_(computing) 位掩码] , + 默认情况下,所有 [page:Object3D] 都是层0的成员。

    图层对象可以用于控制对象的显示。当 [page:Camera camera] 的内容被渲染时与其共享图层相同的物体会被显示。每个对象都需要与一个 [page:Camera camera] 共享图层。

    diff --git a/docs/api/zh/core/Object3D.html b/docs/api/zh/core/Object3D.html index 23aa484440b449..38baf88b5aa50d 100644 --- a/docs/api/zh/core/Object3D.html +++ b/docs/api/zh/core/Object3D.html @@ -116,6 +116,17 @@

    [property:Function onAfterRender]

    [page:Object3D]、 [page:Group] 或者 [page:Bone] 这些是不可渲染的物体,因此此回调函数不会在这样的物体上执行。

    +

    [property:Function onAfterShadow]

    +

    + An optional callback that is executed immediately after a 3D object is + rendered to a shadow map. This function is called with the following parameters: renderer, + scene, camera, shadowCamera, geometry, depthMaterial, group. +

    +

    + 注意此回调函数只会在*可渲染*的3D物体上执行。可渲染的3D物体指的是那种拥有视觉表现的、定义了几何体与材质的物体,例如像是[page:Mesh]、[page:Line]、[page:Points] 或者[page:Sprite]。 + [page:Object3D]、 [page:Group] 或者 [page:Bone] 这些是不可渲染的物体,因此此回调函数不会在这样的物体上执行。 +

    +

    [property:Function onBeforeRender]

    一个可选的回调函数,在Object3D渲染之前直接执行。 @@ -126,6 +137,17 @@

    [property:Function onBeforeRender]

    [page:Object3D]、 [page:Group] 或者 [page:Bone] 这些是不可渲染的物体,因此此回调函数不会在这样的物体上执行。

    +

    [property:Function onBeforeShadow]

    +

    + An optional callback that is executed immediately before a 3D object is + rendered to a shadow map. This function is called with the following parameters: renderer, + scene, camera, shadowCamera, geometry, depthMaterial, group. +

    +

    + 注意此回调函数只会在*可渲染*的3D物体上执行。可渲染的3D物体指的是那种拥有视觉表现的、定义了几何体与材质的物体,例如像是[page:Mesh]、[page:Line]、[page:Points] 或者[page:Sprite]。 + [page:Object3D]、 [page:Group] 或者 [page:Bone] 这些是不可渲染的物体,因此此回调函数不会在这样的物体上执行。 +

    +

    [property:Object3D parent]

    在[link:https://en.wikipedia.org/wiki/Scene_graph scene graph](场景图)中,一个对象的父级对象。 一个对象最多仅能有一个父级对象。

    @@ -264,10 +286,13 @@

    [method:Object3D getObjectByProperty]( [param:String name], [param:Any value 从该对象开始,搜索一个对象及其子级,返回第一个给定的属性中包含有匹配的值的子对象。

    -

    [method:Object3D getObjectsByProperty]( [param:String name], [param:Any value] )

    +

    [method:Object3D getObjectsByProperty]( [param:String name], [param:Any value], [param:Array optionalTarget] )

    name —— 将要用于查找的属性的名称。
    - value —— 给定的属性的值。

    + value —— 给定的属性的值。
    + optionalTarget -- (optional) target to set the result. + Otherwise a new Array is instantiated. If set, you must clear this + array prior to each call (i.e., array.length = 0;).

    从此对象开始,搜索一个对象及其子对象,返回包含给定属性的匹配值的所有子对象。

    diff --git a/docs/api/zh/core/Raycaster.html b/docs/api/zh/core/Raycaster.html index 69b88138a9158e..d139c77b9c3375 100644 --- a/docs/api/zh/core/Raycaster.html +++ b/docs/api/zh/core/Raycaster.html @@ -159,6 +159,12 @@

    [method:undefined setFromCamera]( [param:Vector2 coords], [param:Camera came 使用一个新的原点和方向来更新射线。

    +

    [method:this setFromXRController]( [param:WebXRController controller] )

    +

    + [page:WebXRController controller] — The controller to copy the position and direction from. +

    +

    Updates the ray with a new origin and direction.

    +

    [method:Array intersectObject]( [param:Object3D object], [param:Boolean recursive], [param:Array optionalTarget] )

    [page:Object3D object] —— 检查与射线相交的物体。
    @@ -179,9 +185,9 @@

    [method:Array intersectObject]( [param:Object3D object], [param:Boolean recu [page:Integer faceIndex] —— 相交的面的索引
    [page:Object3D object] —— 相交的物体
    [page:Vector2 uv] —— 相交部分的点的UV坐标。
    - [page:Vector2 uv1] —— Second set of U,V coordinates at point of intersection
    + [page:Vector2 uv1] —— 相交部分的点的第二组UV坐标
    [page:Vector3 normal] - 交点处的内插法向量
    - [page:Integer instanceId] – The index number of the instance where the ray intersects the InstancedMesh + [page:Integer instanceId] – 与InstancedMesh物体相交时的instance索引

    当计算这条射线是否和物体相交的时候,*Raycaster*将传入的对象委托给[page:Object3D.raycast raycast]方法。 diff --git a/docs/api/zh/core/Uniform.html b/docs/api/zh/core/Uniform.html index aac957e75bb94c..54fa7d3795eee2 100644 --- a/docs/api/zh/core/Uniform.html +++ b/docs/api/zh/core/Uniform.html @@ -47,7 +47,7 @@

    Uniform 种类

    [page:Number] - uint (WebGL 2) + uint [page:Number] diff --git a/docs/api/zh/core/bufferAttributeTypes/BufferAttributeTypes.html b/docs/api/zh/core/bufferAttributeTypes/BufferAttributeTypes.html index 4cd44bd945fce9..0bdb9ba187e64c 100644 --- a/docs/api/zh/core/bufferAttributeTypes/BufferAttributeTypes.html +++ b/docs/api/zh/core/bufferAttributeTypes/BufferAttributeTypes.html @@ -17,7 +17,6 @@

    BufferAttribute Types

    - THREE.Float64BufferAttribute THREE.Float32BufferAttribute THREE.Float16BufferAttribute THREE.Uint32BufferAttribute diff --git a/docs/api/zh/extras/Controls.html b/docs/api/zh/extras/Controls.html new file mode 100644 index 00000000000000..3c917ec638e495 --- /dev/null +++ b/docs/api/zh/extras/Controls.html @@ -0,0 +1,116 @@ + + + + + + + + + + [page:EventDispatcher] → + +

    控制器([name])

    + +

    + 控制器的抽象基类。 +

    + +

    构造函数

    + +

    [name]( [param:Object3D object], [param:HTMLDOMElement domElement] )

    + +

    + [page:Object3D object] - 控件应该管理的对象(通常是相机)。 +

    +

    + [page:HTMLDOMElement domElement]: 用于添加事件侦听器的 HTML 元素。(可选) +

    +

    + 创建一个 [name] 实例。 +

    + +

    属性

    + +

    [property:HTMLDOMElement domElement]

    +

    + 用于添加事件侦听器的 HTML 元素。 如果没有在构造函数中提供,[page:.connect]() 必须在 `domElement` 设置后才能调用。 +

    + +

    [property:Boolean enabled]

    +

    + 如果设置为 `false`,控制器将不再响应用户设备输入。 默认值为 `true`。 +

    + +

    [property:Object keys]

    +

    + 该对象用于定义控制器的键盘输入。 + 默认值为 `{}`。 +

    + +

    [property:Object mouseButtons]

    +

    + 此对象定义分配给可用鼠标按键的操作类型。 + 支持哪些鼠标按键和操作取决于控制器的具体实现。 + 默认值为 `{ LEFT: null, MIDDLE: null, RIGHT: null }`。 +

    +

    + 按键可能为: `LEFT`, `MIDDLE`, `RIGHT`。 +

    +

    + 可能的操作是定义在 [page:Core Constants] 中。 +

    + +

    [property:Object3D object]

    +

    + 由控制器管理的 Object3D 对象。 +

    + +

    [property:Integer state]

    +

    + 控制器的内部状态。默认值为 `-1` (`NONE`)。 +

    + +

    [property:Object touches]

    +

    + 此对象定义将哪种类型的操作分配给哪种触摸交互。 + 支持哪种触摸交互和操作取决于控制器的具体实现。 + 默认值为 `{ ONE: null, TWO: null }`。 +

    +

    + 可能触摸点操作有: `ONE`, `TWO`. +

    +

    + 可能的操作是定义在 [page:Core Constants] 中。 +

    + +

    方法

    + +

    共有方法请参见其基类[page:EventDispatcher]。

    + +

    [method:undefined connect] ()

    +

    + 将控制器连接到 DOM。此方法具有所谓的“副作用”,因为它将模块的事件侦听器添加到 DOM。 +

    + +

    [method:undefined disconnect] ()

    +

    + 断开控制器与 DOM 的连接。 +

    + +

    [method:undefined dispose] ()

    +

    + 如果您不再需要使用这些控制器,请调用此方法。它将释放所有内部资源并删除所有事件侦听器。 +

    + +

    [method:undefined update] ( [param:Number delta] )

    +

    + 如果控制器必须在每个模拟步骤中更新其内部状态,则应实现此方法。 +

    + +

    源代码

    + +

    + [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

    + + diff --git a/docs/api/zh/extras/DataUtils.html b/docs/api/zh/extras/DataUtils.html new file mode 100644 index 00000000000000..5692f69a09dfc3 --- /dev/null +++ b/docs/api/zh/extras/DataUtils.html @@ -0,0 +1,36 @@ + + + + + + + + + +

    [name]

    + +

    包含数据功能函数的类

    + +

    方法

    + +

    [method:Number toHalfFloat]( [param:Number val] )

    +

    + val -- 单精度浮点值。

    + + 根据给定的单精度浮点值返回半精度浮点值。 +

    + +

    [method:Number fromHalfFloat]( [param:Number val] )

    +

    + val -- 半精度浮点值。

    + + 根据给定的半精度浮点值返回单精度浮点值。 +

    + +

    源码

    + +

    + [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

    + + diff --git a/docs/api/zh/extras/ImageUtils.html b/docs/api/zh/extras/ImageUtils.html index 522a115c58d1cd..06dcc2b46368d3 100644 --- a/docs/api/zh/extras/ImageUtils.html +++ b/docs/api/zh/extras/ImageUtils.html @@ -10,16 +10,15 @@

    [name]

    - A class containing utility functions for images. + 包含Image功能函数的工具类

    -

    Methods

    +

    方法(Methods)

    [method:String getDataURL]( [param:HTMLCanvasElement image] | [param:HTMLImageElement image] | [param:ImageBitmap image] )

    - image -- The image object.

    - - Returns a data URI containing a representation of the given image. + image -- Image对象

    + 返回Image对象的DataURL

    Source

    diff --git a/docs/api/zh/extras/TextureUtils.html b/docs/api/zh/extras/TextureUtils.html new file mode 100644 index 00000000000000..1aec64d138bc02 --- /dev/null +++ b/docs/api/zh/extras/TextureUtils.html @@ -0,0 +1,42 @@ + + + + + + + + + +

    [name]

    + +

    包含纹理实用函数的类。

    + +

    方法

    + +

    [method:Texture contain]( [param:Texture texture], [param:Number aspect] )

    +

    + 在不裁剪或拉伸纹理的情况下,将纹理在其表面内缩放到尽可能大。该方法保留了纹理的原始纵横比。类似于 CSS 中的 `object-fit: contain`。 +

    + +

    [method:Texture cover]( [param:Texture texture], [param:Number aspect] )

    +

    + 将纹理缩放到尽可能小的尺寸以填充表面,不留空白。该方法保留了纹理的原始纵横比。类似于 CSS 中的 `object-fit: cover`。 +

    + +

    [method:Texture fill]( [param:Texture texture] )

    +

    + 将纹理配置为默认转换。类似于 CSS 中的 `object-fit: fill`。 +

    + +

    [method:Number getByteLength]( [param:Number width], [param:Number height], [param:Number format], [param:Number type] )

    +

    + 给定纹理的宽度、高度、格式和类型。确定必须使用多少个字节来表示纹理。 +

    + +

    源代码

    + +

    + [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

    + + diff --git a/docs/api/zh/extras/core/CurvePath.html b/docs/api/zh/extras/core/CurvePath.html index 09086b994535f1..74481cd7d90868 100644 --- a/docs/api/zh/extras/core/CurvePath.html +++ b/docs/api/zh/extras/core/CurvePath.html @@ -45,7 +45,7 @@

    方法

    [method:undefined add]( [param:Curve curve] )

    添加一条曲线到[page:.curves]数组中。

    -

    [method:undefined closePath]()

    +

    [method:this closePath]()

    添加一条[page:LineCurve lineCurve]用于闭合路径。

    [method:Array getCurveLengths]()

    diff --git a/docs/api/zh/extras/core/Shape.html b/docs/api/zh/extras/core/Shape.html index 65d226006e63b6..e8f8bdce8454a8 100644 --- a/docs/api/zh/extras/core/Shape.html +++ b/docs/api/zh/extras/core/Shape.html @@ -40,8 +40,7 @@

    例子

    [example:webgl_geometry_shapes geometry / shapes ]
    - [example:webgl_geometry_extrude_shapes geometry / extrude / shapes ]
    - [example:webgl_geometry_extrude_shapes2 geometry / extrude / shapes2 ] + [example:webgl_geometry_extrude_shapes geometry / extrude / shapes ]

    构造函数

    diff --git a/docs/api/zh/extras/core/ShapePath.html b/docs/api/zh/extras/core/ShapePath.html index 11247d3a073128..dcf66189edbd5b 100644 --- a/docs/api/zh/extras/core/ShapePath.html +++ b/docs/api/zh/extras/core/ShapePath.html @@ -10,12 +10,7 @@

    形状路径([name])

    - 该类用于转换一系列的形状为一个[page:Path]数组,例如转换SVG中的Path为three.js中的Path(请参阅下方的example)。 -

    - -

    例子

    -

    - [example:webgl_geometry_extrude_shapes2 geometry / extrude / shapes2] + 该类用于转换一系列的形状为一个[page:Path]数组,例如转换SVG中的Path为three.js中的Path。

    构造函数

    diff --git a/docs/api/zh/geometries/BoxGeometry.html b/docs/api/zh/geometries/BoxGeometry.html index 53fea8cdcba7e7..1de21d47733af5 100644 --- a/docs/api/zh/geometries/BoxGeometry.html +++ b/docs/api/zh/geometries/BoxGeometry.html @@ -11,7 +11,9 @@

    立方缓冲几何体([name])

    -

    [name]是四边形的原始几何类,它通常使用构造函数所提供的“width”、“height”、“depth”参数来创建立方体或者不规则四边形。

    +

    + [name] 是四边形的原始几何类,它通常使用构造函数所提供的 “width”、“height”、“depth” 参数来创建立方体或者不规则四边形。 +

    @@ -33,36 +35,37 @@

    立方缓冲几何体([name])

    代码示例

    - const geometry = new THREE.BoxGeometry( 1, 1, 1 ); - const material = new THREE.MeshBasicMaterial( {color: 0x00ff00} ); - const cube = new THREE.Mesh( geometry, material ); + + const geometry = new THREE.BoxGeometry( 1, 1, 1 ); + const material = new THREE.MeshBasicMaterial( {color: 0x00ff00} ); + const cube = new THREE.Mesh( geometry, material ); scene.add( cube ); -

    构造器

    +

    构造函数(Constructor)

    [name]([param:Float width], [param:Float height], [param:Float depth], [param:Integer widthSegments], [param:Integer heightSegments], [param:Integer depthSegments])

    - width — X轴上面的宽度,默认值为1。
    - height — Y轴上面的高度,默认值为1。
    - depth — Z轴上面的深度,默认值为1。
    - widthSegments — (可选)宽度的分段数,默认值是1。
    - heightSegments — (可选)高度的分段数,默认值是1。
    - depthSegments — (可选)深度的分段数,默认值是1。 + width — X 轴上面的宽度,默认值为 `1`。
    + height — Y 轴上面的高度,默认值为 `1`。
    + depth — Z 轴上面的深度,默认值为 `1`。
    + widthSegments — (可选)宽度的分段数,默认值是 `1`。
    + heightSegments — (可选)高度的分段数,默认值是 `1`。
    + depthSegments — (可选)深度的分段数,默认值是 `1`。

    -

    属性

    -

    共有属性请参见其基类[page:BufferGeometry]。

    +

    属性(Properties)

    +

    共有属性请参见其基类 [page:BufferGeometry]。

    [property:Object parameters]

    一个包含着构造函数中每个参数的对象。在对象实例化之后,对该属性的任何修改都不会改变这个几何体。

    -

    方法(Methods)

    -

    共有方法请参见其基类[page:BufferGeometry]。

    +

    方法(Methods)

    +

    共有方法请参见其基类 [page:BufferGeometry]。

    -

    源代码

    +

    源码(Source)

    [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] diff --git a/docs/api/zh/geometries/CapsuleGeometry.html b/docs/api/zh/geometries/CapsuleGeometry.html new file mode 100644 index 00000000000000..c4fb19a50c4682 --- /dev/null +++ b/docs/api/zh/geometries/CapsuleGeometry.html @@ -0,0 +1,77 @@ + + + + + + + + + + [page:BufferGeometry] → [page:LatheGeometry] → + +

    [name]

    + +

    + [name]是一个胶囊图形类,通过半径和高度来进行构造。使用lathe来进行构造。 +

    + + + + + +

    代码示例

    + + +const geometry = new THREE.CapsuleGeometry( 1, 1, 4, 8 ); +const material = new THREE.MeshBasicMaterial( {color: 0x00ff00} ); +const capsule = new THREE.Mesh( geometry, material ); scene.add( capsule ); + + +

    构造函数

    + +

    + [name]([param:Float radius], [param:Float height], [param:Integer capSegments], [param:Integer radialSegments], [param:Integer heightSegments]) +

    +

    + radius — 胶囊半径。可选的; 默认值为1。
    + height — 中间区域的高度。可选的; 默认值为1。
    + capSegments — 构造盖子的曲线部分的个数。可选的; 默认值为4。
    + radialSegments — 覆盖胶囊圆周的分离的面的个数。可选的; 默认值为8。
    + heightSegments — 胶囊侧面沿其高度的段数,默认值为 1。
    +

    + +

    属性

    +

    查看公共属性请参考基类[page:BufferGeometry]。

    + +

    [property:Object parameters]

    +

    + 有属性的构造函数参数对象。任何实例化之后的修改都不会改变图形。 +

    + +

    方法

    +

    查看公共属性请参考基类[page:BufferGeometry]。

    + +

    源码

    + +

    + [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

    + + diff --git a/docs/api/zh/geometries/PlaneGeometry.html b/docs/api/zh/geometries/PlaneGeometry.html index 1156a3f3ca57f3..cf89e6651c9c33 100644 --- a/docs/api/zh/geometries/PlaneGeometry.html +++ b/docs/api/zh/geometries/PlaneGeometry.html @@ -13,7 +13,10 @@

    平面缓冲几何体([name])

    一个用于生成平面几何体的类。

    - + -

    代码示例

    - const geometry = new THREE.PlaneGeometry( 1, 1 ); - const material = new THREE.MeshBasicMaterial( {color: 0xffff00, side: THREE.DoubleSide} ); - const plane = new THREE.Mesh( geometry, material ); - scene.add( plane ); + +const geometry = new THREE.PlaneGeometry( 1, 1 ); +const material = new THREE.MeshBasicMaterial( {color: 0xffff00, side: THREE.DoubleSide} ); +const plane = new THREE.Mesh( geometry, material ); +scene.add( plane ); -

    构造器

    +

    构造函数(Constructor)

    -

    [name]([param:Float width], [param:Float height], [param:Integer widthSegments], [param:Integer heightSegments])

    +

    + [name]([param:Float width], [param:Float height], [param:Integer widthSegments], [param:Integer heightSegments]) +

    - width — 平面沿着X轴的宽度。默认值是1。
    - height — 平面沿着Y轴的高度。默认值是1。
    - widthSegments — (可选)平面的宽度分段数,默认值是1。
    - heightSegments — (可选)平面的高度分段数,默认值是1。 + width — 平面沿着 X 轴的宽度。默认值是 `1`。
    + height — 平面沿着 Y 轴的高度。默认值是 `1`。
    + widthSegments — (可选)平面的宽度分段数,默认值是 `1`。
    + heightSegments — (可选)平面的高度分段数,默认值是 `1`。

    -

    属性

    -

    共有属性请参见其基类[page:BufferGeometry]。

    +

    属性(Properties)

    +

    共有属性请参见其基类 [page:BufferGeometry]。

    -

    .parameters

    +

    [property:Object parameters]

    一个包含着构造函数中每个参数的对象。在对象实例化之后,对该属性的任何修改都不会改变这个几何体。

    -

    方法(Methods)

    +

    方法(Methods)

    共有方法请参见其基类[page:BufferGeometry]。

    -

    源代码

    +

    源码(Source)

    [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] diff --git a/docs/api/zh/geometries/RingGeometry.html b/docs/api/zh/geometries/RingGeometry.html index 0c6c8d2a1e1593..5e0f5f712b0c3b 100644 --- a/docs/api/zh/geometries/RingGeometry.html +++ b/docs/api/zh/geometries/RingGeometry.html @@ -46,7 +46,7 @@

    [name]([param:Float innerRadius], [param:Float outerRadius], [param:Integer innerRadius — 内部半径,默认值为0.5。
    outerRadius — 外部半径,默认值为1。
    thetaSegments — 圆环的分段数。这个值越大,圆环就越圆。最小值为3,默认值为32。
    - phiSegments — 最小值为1,默认值为8。
    + phiSegments — 圆环半径的分段数字。最小值为1,默认值为1。
    thetaStart — 起始角度,默认值为0。
    thetaLength — 圆心角,默认值为Math.PI * 2。

    diff --git a/docs/api/zh/helpers/AxesHelper.html b/docs/api/zh/helpers/AxesHelper.html index 9b7b40750367a8..ac788485e2eddc 100644 --- a/docs/api/zh/helpers/AxesHelper.html +++ b/docs/api/zh/helpers/AxesHelper.html @@ -29,22 +29,31 @@

    例子

    [example:webgl_loader_nrrd WebGL / loader / nrrd]

    -

    构造函数

    - - +

    构造函数(Constructor)

    [name]( [param:Number size] )

    - [page:Number size] -- (可选的) 表示代表轴的线段长度. 默认为 *1*. + [page:Number size] -- (可选的) 表示代表轴的线段长度. 默认为 `1`.

    -

    属性

    +

    属性(Properties)

    请到基类 [page:LineSegments] 页面查看公共属性.

    -

    方法

    +

    方法(Methods)

    请到基类 [page:LineSegments] 页面查看公共方法.

    -

    源码

    +

    + [method:this setColors]( [param:Color xAxisColor], [param:Color yAxisColor], [param:Color zAxisColor] ) +

    +

    + 将轴颜色设置为 [page:Color xAxisColor], [page:Color yAxisColor],[page:Color zAxisColor]。 +

    + +

    [method:undefined dispose]()

    +

    + 释放此实例分配的GPU相关资源。每当应用程序中不再使用此实例时,请调用此方法。 +

    +

    源码(Source)

    [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]

    diff --git a/docs/api/zh/helpers/DirectionalLightHelper.html b/docs/api/zh/helpers/DirectionalLightHelper.html index 5b559d31e54262..0dd8d0634c7e60 100644 --- a/docs/api/zh/helpers/DirectionalLightHelper.html +++ b/docs/api/zh/helpers/DirectionalLightHelper.html @@ -21,6 +21,8 @@

    代码示例

    const light = new THREE.DirectionalLight( 0xFFFFFF ); + scene.add( light ); + const helper = new THREE.DirectionalLightHelper( light, 5 ); scene.add( helper ); diff --git a/docs/api/zh/helpers/GridHelper.html b/docs/api/zh/helpers/GridHelper.html index 2600bdfe23a384..2f9a5a478561c0 100644 --- a/docs/api/zh/helpers/GridHelper.html +++ b/docs/api/zh/helpers/GridHelper.html @@ -7,7 +7,7 @@ - [page:Object3D] → [page:Line] → + [page:Object3D] → [page:Line] → [page:LineSegments] →

    [name]

    diff --git a/docs/api/zh/helpers/PolarGridHelper.html b/docs/api/zh/helpers/PolarGridHelper.html index 2e36fce25ba65d..1232ab5d690b79 100644 --- a/docs/api/zh/helpers/PolarGridHelper.html +++ b/docs/api/zh/helpers/PolarGridHelper.html @@ -7,7 +7,7 @@ - [page:Object3D] → [page:Line] → + [page:Object3D] → [page:Line] → [page:LineSegments] →

    [name]

    diff --git a/docs/api/zh/helpers/SkeletonHelper.html b/docs/api/zh/helpers/SkeletonHelper.html index 8664a45438300f..8db47ef262f70a 100644 --- a/docs/api/zh/helpers/SkeletonHelper.html +++ b/docs/api/zh/helpers/SkeletonHelper.html @@ -36,8 +36,7 @@

    构造函数

    [name]( [param:Object3D object] )

    - object -- Usually an instance of [page:SkinnedMesh]. However, any instance of [page:Object3D] can be used if it represents - a hierarchy of [page:Bone Bone]s (via [page:Object3D.children]). + object -- 通常是 [page:SkinnedMesh]的实例. 实际上, 只要子级存在[page:Bone Bone]s的任何 [page:Object3D] 的实例都可以,(参考 [page:Object3D.children]).

    @@ -50,7 +49,7 @@

    [property:Array bones]

    [property:Boolean isSkeletonHelper]

    - Read-only flag to check if a given object is of type [name]. + 只读属性,用以检查该给定对象是否是 [name].

    [property:Object3D root]

    diff --git a/docs/api/zh/lights/AmbientLight.html b/docs/api/zh/lights/AmbientLight.html index 8f954543fdc8b5..dcdf3a019a9be0 100644 --- a/docs/api/zh/lights/AmbientLight.html +++ b/docs/api/zh/lights/AmbientLight.html @@ -20,18 +20,18 @@

    [name]

    代码示例

    - const light = new THREE.AmbientLight( 0x404040 ); // soft white light + const light = new THREE.AmbientLight( 0x404040 ); // 柔和的白光 scene.add( light );

    构造函数

    -

    [name]( [param:Integer color], [param:Float intensity] )

    +

    [name]( [param:Color color], [param:Float intensity] )

    - [page:Integer color] - (参数可选)颜色的rgb数值。缺省值为 0xffffff。
    - [page:Float intensity] - (参数可选)光照的强度。缺省值为 1。

    + [page:Color color] -(可选)一个表示颜色的 Color 的实例、字符串或数字,默认为一个白色(0xffffff)的 [page:Color Color] 对象。
    + [page:Float intensity] -(可选)光照的强度。默认值为 1。

    - 创建一个环境光对象。 + 创建一个环境光对象。

    属性

    @@ -39,14 +39,9 @@

    属性

    公共属性请查看基类 [page:Light Light]。

    -

    [property:Boolean castShadow]

    -

    - 这个参数在对象构造的时候就被设置成了 *undefined* 。因为环境光不能投射阴影。 -

    -

    [property:Boolean isAmbientLight]

    - Read-only flag to check if a given object is of type [name]. + 只读,用于检查对象的类型是否为 [name]。

    diff --git a/docs/api/zh/lights/AmbientLightProbe.html b/docs/api/zh/lights/AmbientLightProbe.html deleted file mode 100644 index c19c0e6db99546..00000000000000 --- a/docs/api/zh/lights/AmbientLightProbe.html +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - [page:Object3D] → [page:Light] → [page:LightProbe] - -

    环境光探针([name])

    - -

    - 光照探针是一种在3D场景中添加光源的另一种方法。 AmbientLightProbe 是场景中单个环境光的光照估算数据。 - 有关光照探针的更多信息,请转到 [page:LightProbe] 。 -

    - -

    构造函数

    - -

    [name]( [param:Color color], [param:Float intensity] )

    -

    - [page:Color color] - (可选)一个表示颜色的 Color 的实例、字符串或数字。
    - [page:Float intensity] - (可选)光照探针强度的数值。默认值为1。

    - - 创建一个新的[name]。 -

    - -

    属性

    -

    - See the base [page:LightProbe LightProbe] class for common properties. -

    - -

    [property:Boolean isAmbientLightProbe]

    -

    - Read-only flag to check if a given object is of type [name]. -

    - -

    方法

    -

    - See the base [page:LightProbe LightProbe] class for common methods. -

    -

    源码

    - -

    - [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] -

    - - diff --git a/docs/api/zh/lights/DirectionalLight.html b/docs/api/zh/lights/DirectionalLight.html index 8ab0787b88a0bd..68ccb278fd5a29 100644 --- a/docs/api/zh/lights/DirectionalLight.html +++ b/docs/api/zh/lights/DirectionalLight.html @@ -12,21 +12,21 @@

    平行光([name])

    - 平行光是沿着特定方向发射的光。这种光的表现像是无限远,从它发出的光线都是平行的。常常用平行光来模拟太阳光 - 的效果; 太阳足够远,因此我们可以认为太阳的位置是无限远,所以我们认为从太阳发出的光线也都是平行的。

    + 平行光是沿着特定方向发射的光。这种光的表现像是无限远,从它发出的光线都是平行的。常常用平行光来模拟太阳光的效果。 + 太阳足够远,因此我们可以认为太阳的位置是无限远,所以我们认为从太阳发出的光线也都是平行的。

    平行光可以投射阴影 - 跳转至 [page:DirectionalLightShadow] 查看更多细节。

    关于位置、目标和旋转说明

    - Three.js 的平行光常见的困惑是设置旋转没有效果。这是因为 three.js 的平行光类似与其他引擎的"目标平行光"。 + Three.js 的平行光常见的困惑是设置旋转没有效果。这是因为 three.js 的平行光类似与其他引擎的“目标平行光”。

    - 这意味着它的方向是从一个平行光的位置 [page:Object3D.position position] 到 [page:.target target]的位置。 - (而不是一个只有旋转分量的'自由平行光')。

    + 这意味着它的方向是从一个平行光的位置 [page:Object3D.position position] 到 [page:.target target] 的位置。 + (而不是一个只有旋转分量的“自由平行光”)。

    - 这样做的原因是为了让光线投射阴影。[page:.shadow shadow]摄像机需要一个位置来计算阴影。

    + 这样做是为了让光线投射阴影。[page:.shadow shadow] 摄像机需要一个位置来计算阴影。

    有关更新目标的详细信息,请参阅 [page:.target target] 下面的目标属性。

    @@ -34,7 +34,7 @@

    关于位置、目标和旋转说明

    代码示例

    - // White directional light at half intensity shining from the top. + // 从上方照射的白色平行光,强度为 0.5。 const directionalLight = new THREE.DirectionalLight( 0xffffff, 0.5 ); scene.add( directionalLight ); @@ -50,10 +50,10 @@

    例子

    构造器

    -

    [name]( [param:Integer color], [param:Float intensity] )

    +

    [name]( [param:Color color], [param:Float intensity] )

    - [page:Integer color] - (可选参数) 16进制表示光的颜色。 缺省值为 0xffffff (白色)。
    - [page:Float intensity] - (可选参数) 光照的强度。缺省值为1。

    + [page:Color color] -(可选)一个表示颜色的 Color 的实例、字符串或数字,默认为一个白色(0xffffff)的 [page:Color Color] 对象。
    + [page:Float intensity] -(可选)光照的强度。默认值为 1。

    创建一个新的 [name]。

    @@ -64,63 +64,60 @@

    属性

    [property:Boolean castShadow]

    - 如果设置为 *true* 该平行光会产生动态阴影。 警告: 这样做的代价比较高而且需要一直调整到阴影看起来正确. - 查看 [page:DirectionalLightShadow] 了解详细信息。该属性默认为 *false*。 + 此属性设置为 `true` 灯光将投射阴影。*注意*:这样做的代价比较高,需要通过调整让阴影看起来正确。 + 查看 [page:DirectionalLightShadow] 了解详细信息。 + 默认值为 `false`。

    [property:Boolean isDirectionalLight]

    - Read-only flag to check if a given object is of type [name]. + 只读,用于检查对象的类型是否为 [name]。

    [property:Vector3 position]

    - 假如这个值设置等于 [page:Object3D.DEFAULT_UP] (0, 1, 0),那么光线将会从上往下照射。 + 假如这个值设置为 [page:Object3D.DEFAULT_UP] (0, 1, 0),光线将会从上往下照射。

    [property:DirectionalLightShadow shadow]

    - 这个 [page:DirectionalLightShadow] 对象用来计算该平行光产生的阴影。 + [page:DirectionalLightShadow] 对象,用于计算该平行光产生的阴影。

    [property:Object3D target]

    - 平行光的方向是从它的位置到目标位置。默认的目标位置为原点 *(0,0,0)*。
    - - 注意: 对于目标的位置,要将其更改为除缺省值之外的任何位置,它必须被添加到 [page:Scene scene] - 场景中去。 -

    - - scene.add( light.target ); - -

    - 这使得属性target中的 [page:Object3D.matrixWorld matrixWorld] 会每帧自动更新。 -

    - 它也可以设置target为场景中的其他对象(任意拥有 [page:Object3D.position position] 属性的对象), 示例如下: -

    - - const targetObject = new THREE.Object3D(); - scene.add(targetObject); - - light.target = targetObject; - -

    - 完成上述操作后,平行光现在就可以追踪到目标对像了。 + 灯光从它的位置([page:.position position])指向目标位置。默认的目标位置为`(0, 0, 0)`。
    + *注意*:对于目标的位置,如果要改为除默认值之外的其他位置,该位置必须被添加到场景([page:Scene scene])中去。 + + scene.add( light.target ); + + 这是为了让目标的 [page:Object3D.matrixWorld matrixWorld] 在每一帧自动更新。

    + 也可以将目标设置为场景中的其他对象(任意拥有 [page:Object3D.position position] 属性的对象),如: + +const targetObject = new THREE.Object3D(); +scene.add(targetObject); + +light.target = targetObject; + + 通过上述操作,光源就可以追踪目标对象了。

    方法

    -

    公共方法请查看基类 [page:Light Light]。

    +

    [method:undefined dispose]()

    +

    + 释放由该实例分配的 GPU 相关资源。 当这个实例不再在你的应用中使用时,调用这个方法。 +

    +

    [method:this copy]( [param:DirectionalLight source] )

    复制 source 的值到这个平行光源对象。

    源码

    -

    [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]

    diff --git a/docs/api/zh/lights/HemisphereLight.html b/docs/api/zh/lights/HemisphereLight.html index 725daee2d8b6b1..b334d981847460 100644 --- a/docs/api/zh/lights/HemisphereLight.html +++ b/docs/api/zh/lights/HemisphereLight.html @@ -37,45 +37,39 @@

    例子

    构造器(Constructor)

    [name]( [param:Integer skyColor], [param:Integer groundColor], [param:Float intensity] )

    -

    - [page:Integer skyColor] - (可选参数) 天空中发出光线的颜色。 缺省值 0xffffff。
    - [page:Integer groundColor] - (可选参数) 地面发出光线的颜色。 缺省值 0xffffff。
    - [page:Float intensity] - (可选参数) 光照强度。 缺省值 1。

    +

    + [page:Color skyColor] -(可选)一个表示颜色的 Color 的实例、字符串或数字,默认为一个白色(0xffffff)的 [page:Color Color] 对象。
    + [page:Color groundColor] -(可选)一个表示颜色的 Color 的实例、字符串或数字,默认为一个白色(0xffffff)的 [page:Color Color] 对象。
    + [page:Float intensity] -(可选)光照强度。默认值为 1。

    - 创建一个半球光。 + 创建一个半球光。

    属性(Properties)

    - 公共属性请查看基类[page:Light Light]。 + 公共属性请查看基类 [page:Light Light]。

    -

    [property:Boolean castShadow]

    -

    - 该参数在构造时被设置为 *undefined* 因为半球光不能投射阴影。 -

    - - -

    [property:Float color]

    +

    [property:Color color]

    在构造时传递的天空发出光线的颜色。 - 默认会创建 [page:Color] 并设置为白色(0xffffff)。 + 默认值为白色(0xffffff)的 [page:Color] 对象。

    -

    [property:Float groundColor]

    +

    [property:Color groundColor]

    在构造时传递的地面发出光线的颜色。 - 默认会创建 [page:Color] 并设置为白色(0xffffff)。 + 默认值为白色(0xffffff)的 [page:Color] 对象。

    [property:Boolean isHemisphereLight]

    - Read-only flag to check if a given object is of type [name]. + 只读,用于检查对象的类型是否为 [name]。

    [property:Vector3 position]

    - 假如这个值设置等于 [page:Object3D.DEFAULT_UP] (0, 1, 0),那么光线将会从上往下照射。 + 假如这个值设置为 [page:Object3D.DEFAULT_UP] (0, 1, 0),光线将会从上往下照射。

    @@ -86,7 +80,7 @@

    方法(Methods)

    [method:this copy]( [param:HemisphereLight source] )

    - 从[page:Light source]复制 [page:.color color], [page:.intensity intensity] 和 + 从 [page:Light source] 复制 [page:.color color]、[page:.intensity intensity] 和 [page:.groundColor groundColor] 的值到当前半球光对象中。

    diff --git a/docs/api/zh/lights/HemisphereLightProbe.html b/docs/api/zh/lights/HemisphereLightProbe.html deleted file mode 100644 index baca4a5eecaaf2..00000000000000 --- a/docs/api/zh/lights/HemisphereLightProbe.html +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - [page:Object3D] → [page:Light] → [page:LightProbe] - -

    半球光探针[name]

    - -

    - 光照探针是一种在3D场景中添加光源的另一种方法。 HemisphereLightProbe 是场景中单个半球光的光照估算数据。 - 有关光照探针的更多信息,请转到 [page:LightProbe] 。 -

    - -

    构造函数

    - -

    [name]( [param:Color skyColor], [param:Color groundColor], [param:Float intensity] )

    -

    - [page:Color skyColor] - (可选)一个表示颜色的 Color 的实例、字符串或数字。
    - [page:Color groundColor] - (可选)一个表示颜色的 Color 的实例、字符串或数字。
    - [page:Float intensity] - (可选)光照探针强度的数值。默认值为1。

    - - 创建一个新的 [name]。 -

    - -

    属性

    -

    - See the base [page:LightProbe LightProbe] class for common properties. -

    - -

    [property:Boolean isHemisphereLightProbe]

    -

    - Read-only flag to check if a given object is of type [name]. -

    - -

    方法

    -

    - See the base [page:LightProbe LightProbe] class for common methods. -

    -

    源码

    - -

    - [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] -

    - - diff --git a/docs/api/zh/lights/Light.html b/docs/api/zh/lights/Light.html index 28aeaa24a9b5b2..46c900c6d2cf81 100644 --- a/docs/api/zh/lights/Light.html +++ b/docs/api/zh/lights/Light.html @@ -19,36 +19,34 @@

    [name]

    构造器(Constructor)

    -

    [name]( [param:Integer color], [param:Float intensity] )

    +

    [name]( [param:Color color], [param:Float intensity] )

    - [page:Integer color] - (可选参数) 16进制表示光的颜色。 缺省值 0xffffff (白色)。
    - [page:Float intensity] - (可选参数) 光照强度。 缺省值 1。

    + [page:Color color] -(可选)一个表示颜色的 Color 的实例、字符串或数字,默认为一个白色(0xffffff)的 [page:Color Color] 对象。
    + [page:Float intensity] -(可选)光照强度。默认值为 1。

    - 创造一个新的光源。注意,这并不是直接调用的(而是使用派生类之一)。 + 创造一个新的光源。注意,这并不是直接调用的(而是使用它的派生类)。

    属性(Properties)

    - 公共属性请查看基类[page:Object3D Object3D]。 + 公共属性请查看基类 [page:Object3D Object3D]。

    [property:Color color]

    - 光源的颜色。如果构造的时候没有传递,默认会创建一个新的 [page:Color] 并设置为白色。 + 光源的颜色。如果构造的时候没有传递,默认会创建一个新的 [page:Color] 对象并设置为白色。

    [property:Float intensity]

    - 光照的强度,或者说能量。 - When [page:WebGLRenderer.useLegacyLights legacy lighting mode] is disabled, [page:.color color] 和强度 - 的乘积被解析为以坎德拉(candela)为单位的发光强度。 - 默认值 - *1.0* -
    + 光照的强度,或者说能量。
    + 强度的单位取决于光的类型。
    + 默认值为 `1.0`。

    [property:Boolean isLight]

    - Read-only flag to check if a given object is of type [name]. + 只读,用于检查对象的类型是否为 [name]。

    @@ -57,11 +55,14 @@

    Methods

    公共方法请查看基类 [page:Object3D Object3D]。

    +

    [method:undefined dispose]()

    +

    + 释放由该实例分配的 GPU 相关资源。 当这个实例不再在你的应用中使用时,调用这个方法。 +

    [method:this copy]( [param:Light source] )

    - 从[page:Light source]复制 [page:.color color], [page:.intensity intensity] - 的值到当前光源对象中。 + 从 [page:Light source] 复制 [page:.color color]、[page:.intensity intensity] 的值到当前光源对象中。

    [method:Object toJSON]( [param:Object meta] )

    diff --git a/docs/api/zh/lights/LightProbe.html b/docs/api/zh/lights/LightProbe.html index c8f2866244598e..4f3908da728017 100644 --- a/docs/api/zh/lights/LightProbe.html +++ b/docs/api/zh/lights/LightProbe.html @@ -18,15 +18,15 @@

    光照探针[name]

    - 光照探针通常从(辐射)环境贴图中创建。 [page:LightProbeGenerator] 类可以根据 [page:CubeTexture] 或 + 光照探针通常从(辐射)环境贴图中创建。[page:LightProbeGenerator] 类可以用于从 [page:CubeTexture] 或 [page:WebGLCubeRenderTarget] 的实例来创建光照探针。 但是,光照估算数据同样可以以其他形式提供,例如,通过WebXR。 - 这将能够渲染可对真实世界的光照做出反应的增强现实内容。 + 这使得增强现实内容的渲染能够对现实世界的照明做出反应。

    - three.js中,当前的探针实现支持所谓的漫反射光照探针。 - 这种类型的光照探针功能上等效于辐照环境贴图。 + 目前在 Three.js 中的探测实现支持所谓的漫射光探测。 + 这种类型的光照探针在功能上等效于辐照环境贴图。

    例子

    @@ -39,21 +39,21 @@

    构造函数

    [name]( [param:SphericalHarmonics3 sh], [param:Float intensity] )

    - [page:SphericalHarmonics3 sh] - (可选)一个[page:SphericalHarmonics3]的实例。
    - [page:Float intensity] - (可选)光照探针强度的数值。默认值为1。

    + [page:SphericalHarmonics3 sh] -(可选)一个 [page:SphericalHarmonics3] 的实例。
    + [page:Float intensity] -(可选)光照探针强度的数值。默认值为 1。

    - 创建一个新的 [name] 。 + 创建一个新的 [name] 。

    属性

    - See the base [page:Light Light] class for common properties. - [page:Light.color color] 属性当前未做评估,因此不生效。 + 公共属性请查看基类 [page:Light Light]。 + [page:Light.color color] 属性当前未做评估,因此不生效。

    [property:Boolean isLightProbe]

    - Read-only flag to check if a given object is of type [name]. + 只读,用于检查对象的类型是否为 [name]。

    [property:SphericalHarmonics3 sh]

    @@ -63,10 +63,10 @@

    [property:SphericalHarmonics3 sh]

    方法

    - See the base [page:Light Light] class for common methods. + 公共方法请查看基类 [page:Light Light]。

    -

    源码

    +

    源码

    [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]

    diff --git a/docs/api/zh/lights/PointLight.html b/docs/api/zh/lights/PointLight.html index 155efe98ae98ba..ad7f0e609de826 100644 --- a/docs/api/zh/lights/PointLight.html +++ b/docs/api/zh/lights/PointLight.html @@ -28,7 +28,7 @@

    代码示例

    例子

    - [example:webgl_lights_pointlights lights / pointlights ]
    + [example:webgpu_lights_pointlights lights / pointlights ]
    [example:webgl_effects_anaglyph effects / anaglyph ]
    [example:webgl_geometry_text geometry / text ]
    [example:webgl_lensflares lensflares ] @@ -36,77 +36,62 @@

    例子

    构造器(Constructor)

    -

    [name]( [param:Integer color], [param:Float intensity], [param:Number distance], [param:Float decay] )

    +

    [name]( [param:Color color], [param:Float intensity], [param:Number distance], [param:Float decay] )

    - [page:Integer color] - (可选参数)) 十六进制光照颜色。 缺省值 0xffffff (白色)。
    - [page:Float intensity] - (可选参数) 光照强度。 缺省值 1。
    - [page:Number distance] - 这个距离表示从光源到光照强度为0的位置。 - 当设置为0时,光永远不会消失(距离无穷大)。缺省值 0.
    - [page:Float decay] - 沿着光照距离的衰退量。缺省值 2。

    + [page:Color color] -(可选)一个表示颜色的 Color 的实例、字符串或数字,默认为一个白色(0xffffff)的 [page:Color Color] 对象。
    + [page:Float intensity] -(可选)光照强度。默认值为 1。
    + [page:Number distance] - 光源照射的最大距离。默认值为 0(无限远)。
    + [page:Float decay] - 沿着光照距离的衰退量。默认值为 2。

    创建一个新的点光源(PointLight)。

    属性(Properties)

    - 公共属性请查看基类[page:Light Light]。 + 公共属性请查看基类 [page:Light Light]。

    [property:Boolean castShadow]

    - If set to `true` light will cast dynamic shadows. *Warning*: This is expensive and - requires tweaking to get shadows looking right. See the [page:PointLightShadow] for details. - The default is `false`. + 此属性设置为 `true` 灯光将投射阴影。*注意*:这样做的代价比较高,需要通过调整让阴影看起来正确。 + 查看 [page:PointLightShadow] 了解详细信息。 + 默认值为 `false`。

    [property:Float decay]

    - The amount the light dims along the distance of the light. Default is `2`.
    - In context of physically-correct rendering the default value should not be changed. + 光线随着距离增加变暗的衰减量。默认值为 `2`。
    + 在物理正确渲染的上下文中,不应更改默认值。

    [property:Float distance]

    - `Default mode` — When distance is zero, light does not attenuate. When - distance is non-zero, light will attenuate linearly from maximum intensity - at the light's position down to zero at this distance from the light. + 当值为零时,光线将根据平方反比定律衰减到无限远。 + 当值不为零时,光线会先按照平方反比定律衰减,直到距离截止点附近,然后线性衰减到 0。

    -

    - When [page:WebGLRenderer.useLegacyLights legacy lighting mode] is disabled — - When distance is zero, light will attenuate according to inverse-square - law to infinite distance. When distance is non-zero, light will attenuate - according to inverse-square law until near the distance cutoff, where it - will then attenuate quickly and smoothly to 0. Inherently, cutoffs are not - physically correct. -

    -

    Default is `0.0`.

    +

    默认值为 `0.0`。

    +

    [property:Float intensity]

    - The light's intensity. Default is `1`.
    - When [page:WebGLRenderer.useLegacyLights legacy lighting mode] is disabled, - intensity is the luminous intensity of the light measured in candela - (cd).

    - - Changing the intensity will also change the light's power. + 光源的强度。默认值为 `1`。
    + 单位是坎德拉(cd)。

    + 改变该值会影响到 `power` 的值。

    [property:Float power]

    - The light's power.
    - When [page:WebGLRenderer.useLegacyLights legacy lighting mode] is disabled, - power is the luminous power of the light measured in lumens (lm). -

    - - Changing the power will also change the light's intensity. + 光源的功率。
    + 单位为流明(lm)。

    + 改变该值会影响到 `intensity` 的值。

    [property:PointLightShadow shadow]

    - [page:PointLightShadow]用与计算此光照的阴影。

    + [page:PointLightShadow] 对象,用与计算此光照的阴影。

    - 此对象的摄像机被设置为 [page:PerspectiveCamera.fov fov] 为90度,[page:PerspectiveCamera.aspect aspect]为1, - 近裁剪面 [page:PerspectiveCamera.near near] 为0,远裁剪面[page:PerspectiveCamera.far far] - 为500的透视摄像机 [page:PerspectiveCamera]。 + 此对象的摄像机为:一个 [page:PerspectiveCamera.fov fov] 值为90度、[page:PerspectiveCamera.aspect aspect] 值为 1、 + [page:PerspectiveCamera.near near] 值为 0、[page:PerspectiveCamera.far far] + 值为 500 的透视摄像机([page:PerspectiveCamera])。

    方法(Methods)

    @@ -114,6 +99,11 @@

    方法(Methods)

    公共方法请查看基类 [page:Light Light]。

    +

    [method:undefined dispose]()

    +

    + 释放由该实例分配的 GPU 相关资源。 当这个实例不再在你的应用中使用时,调用这个方法。 +

    +

    [method:this copy]( [param:PointLight source] )

    将所有属性的值从源 [page:PointLight source] 复制到此点光源对象。 diff --git a/docs/api/zh/lights/RectAreaLight.html b/docs/api/zh/lights/RectAreaLight.html index 3e9a8fdf1b2e05..59f27bcb43b4aa 100644 --- a/docs/api/zh/lights/RectAreaLight.html +++ b/docs/api/zh/lights/RectAreaLight.html @@ -18,7 +18,7 @@

    平面光光源([name])

    • 不支持阴影。
    • 只支持 [page:MeshStandardMaterial MeshStandardMaterial] 和 [page:MeshPhysicalMaterial MeshPhysicalMaterial] 两种材质。
    • -
    • 你必须在你的场景中加入 [link:https://threejs.org/examples/jsm/lights/RectAreaLightUniformsLib.js RectAreaLightUniformsLib] ,并调用*init()*。
    • +
    • 你必须在你的场景中加入 [link:https://threejs.org/examples/jsm/lights/RectAreaLightUniformsLib.js RectAreaLightUniformsLib],并调用 `init()`。

    @@ -33,7 +33,7 @@

    代码示例

    rectLight.lookAt( 0, 0, 0 ); scene.add( rectLight ) - rectLightHelper = new RectAreaLightHelper( rectLight ); + const rectLightHelper = new RectAreaLightHelper( rectLight ); scene.add( rectLightHelper );
    @@ -46,12 +46,12 @@

    例子

    构造器(Constructor)

    -

    [name]( [param:Integer color], [param:Float intensity], [param:Float width], [param:Float height] )

    +

    [name]( [param:Color color], [param:Float intensity], [param:Float width], [param:Float height] )

    - [page:Integer color] - (可选参数) 十六进制数字表示的光照颜色。缺省值为 0xffffff (白色)
    - [page:Float intensity] - (可选参数) 光源强度/亮度 。缺省值为 1。
    - [page:Float width] - (可选参数) 光源宽度。缺省值为 10。
    - [page:Float height] - (可选参数) 光源高度。缺省值为 10。

    + [page:Color color] -(可选)一个表示颜色的 Color 的实例、字符串或数字,默认为一个白色(0xffffff)的 [page:Color Color] 对象。
    + [page:Float intensity] -(可选)光源强度/亮度 。默认值为 1。
    + [page:Float width] -(可选)光源宽度。默认值为 10。
    + [page:Float height] -(可选)光源高度。默认值为 10。

    创建一个新的平面光。

    @@ -62,35 +62,31 @@

    属性(Properties)

    [property:Float height]

    -

    The height of the light.

    +

    光源高度。

    [property:Float intensity]

    - The light's intensity. Default is `1`.
    - When [page:WebGLRenderer.useLegacyLights legacy lighting mode] is disabled, - intensity is the luminance (brightness) of the light measured in nits - (cd/m^2).

    + 光源的强度。默认值为 `1`。
    + 单位是尼特(cd/m^2)。

    - Changing the intensity will also change the light's power. + 改变该值会影响到 `power` 的值。

    [property:Boolean isRectAreaLight]

    - Read-only flag to check if a given object is of type [name]. + 只读,用于检查对象的类型是否为 [name]。

    [property:Float power]

    - The light's power.
    - When [page:WebGLRenderer.useLegacyLights legacy lighting mode] is disabled, - power is the luminous power of the light measured in lumens (lm). -

    + 光源的功率。
    + 单位为流明(lm)。

    - Changing the power will also change the light's intensity. + 改变该值会影响到 `intensity` 的值。

    [property:Float width]

    -

    The width of the light.

    +

    光源宽度。

    方法(Methods)

    diff --git a/docs/api/zh/lights/SpotLight.html b/docs/api/zh/lights/SpotLight.html index 61210f38096c73..5ae4857e2b4027 100644 --- a/docs/api/zh/lights/SpotLight.html +++ b/docs/api/zh/lights/SpotLight.html @@ -19,7 +19,7 @@

    聚光灯([name])

    代码示例

    - // white spotlight shining from the side, modulated by a texture, casting a shadow + // 白色聚光灯从侧面照射,经过纹理调节,形成阴影 const spotLight = new THREE.SpotLight( 0xffffff ); spotLight.position.set( 100, 1000, 100 ); @@ -47,124 +47,106 @@

    例子

    构造器(Constructor)

    -

    [name]( [param:Integer color], [param:Float intensity], [param:Float distance], [param:Radians angle], [param:Float penumbra], [param:Float decay] )

    +

    [name]( [param:Color color], [param:Float intensity], [param:Float distance], [param:Radians angle], [param:Float penumbra], [param:Float decay] )

    - [page:Integer color] - (可选参数) 十六进制光照颜色。 缺省值 0xffffff (白色)。
    - [page:Float intensity] - (可选参数) 光照强度。 缺省值 1。
    - [page:Float distance] - 从光源发出光的最大距离,其强度根据光源的距离线性衰减。
    - [page:Radians angle] - 光线散射角度,最大为Math.PI/2。
    - [page:Float penumbra] - 聚光锥的半影衰减百分比。在0和1之间的值。默认为0。
    - [page:Float decay] - 沿着光照距离的衰减量。

    + [page:Color color] -(可选)一个表示颜色的 Color 的实例、字符串或数字,默认为一个白色(0xffffff)的 [page:Color Color] 对象。
    + [page:Float intensity] -(可选)光照强度。默认值为 1。
    + [page:Float distance] - 光源照射的最大距离。默认值为 0(无限远)。
    + [page:Radians angle] - 光线照射范围的角度。默认值为 Math.PI/3。
    + [page:Float penumbra] - 聚光锥的半影衰减百分比。默认值为 0。
    + [page:Float decay] - 沿着光照距离的衰减量。默认值为 2。

    创建一个新的聚光灯。

    属性(Properties)

    -

    公共属性请查看基类[page:Light Light]。

    +

    公共属性请查看基类 [page:Light Light]。

    [property:Float angle]

    - 从聚光灯的位置以弧度表示聚光灯的最大范围。应该不超过 *Math.PI/2*。默认值为 *Math.PI/3*。 + 光线照射范围的角度,用弧度表示。不应超过 `Math.PI/2`。默认值为 `Math.PI/3`。

    [property:Boolean castShadow]

    - 此属性设置为 *true* 聚光灯将投射阴影。警告: 这样做的代价比较高而且需要一直调整到阴影看起来正确。 + 此属性设置为 `true` 灯光将投射阴影。*注意*:这样做的代价比较高,需要通过调整让阴影看起来正确。 查看 [page:SpotLightShadow] 了解详细信息。 - 默认值为 *false* + 默认值为 `false`。

    [property:Float decay]

    - The amount the light dims along the distance of the light. Default is `2`.
    - In context of physically-correct rendering the default value should not be changed. + 光线随着距离增加变暗的衰减量。默认值为 `2`。
    + 在物理正确渲染的上下文中,不应更改默认值。

    [property:Float distance]

    - `Default mode` — When distance is zero, light does not attenuate. When - distance is non-zero, light will attenuate linearly from maximum intensity - at the light's position down to zero at this distance from the light. + 当值为零时,光线将根据平方反比定律衰减到无限远。 + 当值不为零时,光线会先按照平方反比定律衰减,直到距离截止点附近,然后线性衰减到 0。

    -

    - When [page:WebGLRenderer.useLegacyLights legacy lighting mode] is disabled — - When distance is zero, light will attenuate according to inverse-square - law to infinite distance. When distance is non-zero, light will attenuate - according to inverse-square law until near the distance cutoff, where it - will then attenuate quickly and smoothly to `0`. Inherently, cutoffs are - not physically correct. -

    -

    Default is `0.0`.

    +

    默认值为 `0.0`。

    [property:Float intensity]

    - The light's intensity. Default is `1`.
    - When [page:WebGLRenderer.useLegacyLights legacy lighting mode] is disabled, - intensity is the luminous intensity of the light measured in candela - (cd).

    - Changing the intensity will also change the light's power. + 光源的强度。默认值为 `1`。
    + 单位是坎德拉(cd)。

    + 改变该值会影响到 `power` 的值。

    [property:Boolean isSpotLight]

    - Read-only flag to check if a given object is of type [name]. + 只读,用于检查对象的类型是否为 [name]。

    [property:Float penumbra]

    - 聚光锥的半影衰减百分比。在0和1之间的值。 - 默认值 — 0.0。 + 该属性设置照明区域在边缘附近的平滑衰减速度,取值范围在 0 到 1 之间。默认值为 `0.0`。

    [property:Vector3 position]

    - 假如这个值设置等于 [page:Object3D.DEFAULT_UP] (0, 1, 0),那么光线将会从上往下照射。 + 假如这个值设置为 [page:Object3D.DEFAULT_UP] (0, 1, 0),光线将会从上往下照射。

    [property:Float power]

    - The light's power.
    - When [page:WebGLRenderer.useLegacyLights legacy lighting mode] is disabled, - power is the luminous power of the light measured in lumens (lm). -

    - Changing the power will also change the light's intensity. + 光源的功率。
    + 单位为流明(lm)。

    + 改变该值会影响到 `intensity` 的值。

    [property:SpotLightShadow shadow]

    - [page:SpotLightShadow]用与计算此光照的阴影。 + [page:SpotLightShadow] 对象,用与计算此光照的阴影。

    [property:Object3D target]

    - 聚光灯的方向是从它的位置到目标位置.默认的目标位置为原点 *(0,0,0)*。
    - 注意: 对于目标的位置,要将其更改为除缺省值之外的任何位置,它必须被添加到 [page:Scene scene] - 场景中去。 + 灯光从它的位置([page:.position position])指向目标位置。默认的目标位置为`(0, 0, 0)`。
    + *注意*:对于目标的位置,如果要改为除默认值之外的其他位置,该位置必须被添加到场景([page:Scene scene])中去。 scene.add( light.target ); - - 这使得属性target中的 [page:Object3D.matrixWorld matrixWorld] 会每帧自动更新。

    - - 它也可以设置target为场景中的其他对象(任意拥有 [page:Object3D.position position] 属性的对象), 示例如下: + 这是为了让目标的 [page:Object3D.matrixWorld matrixWorld] 在每一帧自动更新。

    + 也可以将目标设置为场景中的其他对象(任意拥有 [page:Object3D.position position] 属性的对象),如: const targetObject = new THREE.Object3D(); scene.add(targetObject); light.target = targetObject; - 完成上述操作后,聚光灯现在就可以追踪到目标对像了。 + 通过上述操作,光源就可以追踪目标对象了。

    [property:Texture map]

    - A [page:Texture] used to modulate the color of the light. The spot light color is mixed - with the RGB value of this texture, with a ratio corresponding to its - alpha value. The cookie-like masking effect is reproduced using pixel values (0, 0, 0, 1-cookie_value). - *Warning*: [param:SpotLight map] is disabled if [param:SpotLight castShadow] is *false*. + 用于调节光线颜色的纹理([page:Texture]),聚光灯颜色会与该纹理的RGB值混合,其比例与其alpha值相对应。
    + The cookie-like masking effect is reproduced using pixel values (0, 0, 0, 1-cookie_value).
    + *注意*: 如果 [page:.castShadow] 值为 `false` 时,[page:.map] 不可用。

    @@ -172,6 +154,11 @@

    方法(Methods)

    公共方法请查看基类 [page:Light Light]。

    +

    [method:undefined dispose]()

    +

    + 释放由该实例分配的 GPU 相关资源。 当这个实例不再在你的应用中使用时,调用这个方法。 +

    +

    [method:this copy]( [param:SpotLight source] )

    将所有属性的值从源 [page:SpotLight source] 复制到此聚光灯光源对象。 @@ -182,3 +169,4 @@

    [method:this copy]( [param:SpotLight source] )

    + diff --git a/docs/api/zh/lights/shadows/DirectionalLightShadow.html b/docs/api/zh/lights/shadows/DirectionalLightShadow.html index 35fd5ad61a24b0..f451479b360759 100644 --- a/docs/api/zh/lights/shadows/DirectionalLightShadow.html +++ b/docs/api/zh/lights/shadows/DirectionalLightShadow.html @@ -75,7 +75,7 @@

    [property:Camera camera]

    [property:Boolean isDirectionalLightShadow]

    - Read-only flag to check if a given object is of type [name]. + 只读属性,用于检查给定对象是否为[name]类型。

    方法

    diff --git a/docs/api/zh/lights/shadows/LightShadow.html b/docs/api/zh/lights/shadows/LightShadow.html index 9490943615e9a4..994e2c8cbb90e4 100644 --- a/docs/api/zh/lights/shadows/LightShadow.html +++ b/docs/api/zh/lights/shadows/LightShadow.html @@ -48,6 +48,11 @@

    [property:Integer blurSamples]

    The amount of samples to use when blurring a VSM shadow map.

    +

    [property:Float intensity]

    +

    + The intensity of the shadow. The default is `1`. Valid values are in the range `[0, 1]`. +

    +

    [property:WebGLRenderTarget map]

    使用内置摄像头生成的深度图;超出像素深度的位置在阴影中。在渲染期间内部计算。 diff --git a/docs/api/zh/lights/shadows/PointLightShadow.html b/docs/api/zh/lights/shadows/PointLightShadow.html index c5783e350b90e2..aa16f734c646af 100644 --- a/docs/api/zh/lights/shadows/PointLightShadow.html +++ b/docs/api/zh/lights/shadows/PointLightShadow.html @@ -69,7 +69,7 @@

    属性

    [property:Boolean isPointLightShadow]

    - Read-only flag to check if a given object is of type [name]. + 只读属性,用于检查给定对象是否为[name]类型。

    方法

    diff --git a/docs/api/zh/lights/shadows/SpotLightShadow.html b/docs/api/zh/lights/shadows/SpotLightShadow.html index 40f7f49e9b99fd..53e4cfdef0a7f9 100644 --- a/docs/api/zh/lights/shadows/SpotLightShadow.html +++ b/docs/api/zh/lights/shadows/SpotLightShadow.html @@ -75,7 +75,7 @@

    [property:Number focus]

    [property:Boolean isSpotLightShadow]

    - Read-only flag to check if a given object is of type [name]. + 只读属性,用于检查给定对象是否为[name]类型。

    方法

    diff --git a/docs/api/zh/loaders/BufferGeometryLoader.html b/docs/api/zh/loaders/BufferGeometryLoader.html index 455f6c77f8ec28..5b95184938458c 100644 --- a/docs/api/zh/loaders/BufferGeometryLoader.html +++ b/docs/api/zh/loaders/BufferGeometryLoader.html @@ -46,12 +46,6 @@

    代码示例

    );
    -

    例子

    - -

    - [example:webgl_performance WebGL / performance] -

    -

    构造函数

    [name]( [param:LoadingManager manager] )

    diff --git a/docs/api/zh/loaders/CubeTextureLoader.html b/docs/api/zh/loaders/CubeTextureLoader.html index 5e216ecc06919b..9320e631942b7b 100644 --- a/docs/api/zh/loaders/CubeTextureLoader.html +++ b/docs/api/zh/loaders/CubeTextureLoader.html @@ -16,6 +16,11 @@

    [name]

    内部使用[page:ImageLoader]来加载文件。

    +

    + The loaded [page:CubeTexture] is in sRGB color space. Meaning the [page:Texture.colorSpace colorSpace] + property is set to `THREE.SRGBColorSpace` by default. +

    +

    代码示例

    diff --git a/docs/api/zh/loaders/ImageBitmapLoader.html b/docs/api/zh/loaders/ImageBitmapLoader.html index 2d253d8b9e2e43..45bfc291292145 100644 --- a/docs/api/zh/loaders/ImageBitmapLoader.html +++ b/docs/api/zh/loaders/ImageBitmapLoader.html @@ -33,7 +33,7 @@

    代码示例

    // 加载一个图片资源 loader.load( // 资源的URL - 'textures/skyboxsun25degtest.png', + 'image.png', // onLoad回调 function ( imageBitmap ) { @@ -71,7 +71,7 @@

    属性

    [property:Boolean isImageBitmapLoader]

    - Read-only flag to check if a given object is of type [name]. + 只读属性,用于检查给定对象是否为[name]类型。

    diff --git a/docs/api/zh/loaders/ImageLoader.html b/docs/api/zh/loaders/ImageLoader.html index d8605c19f219e8..cf3973f1f88f41 100644 --- a/docs/api/zh/loaders/ImageLoader.html +++ b/docs/api/zh/loaders/ImageLoader.html @@ -26,7 +26,7 @@

    代码示例

    // 加载一个图片资源 loader.load( // 资源URL - 'textures/skyboxsun25degtest.png', + 'image.png', // onLoad回调 function ( image ) { diff --git a/docs/api/zh/loaders/LoaderUtils.html b/docs/api/zh/loaders/LoaderUtils.html index 3bde24f4792c38..00d0f416622b38 100644 --- a/docs/api/zh/loaders/LoaderUtils.html +++ b/docs/api/zh/loaders/LoaderUtils.html @@ -13,14 +13,6 @@

    [name]

    函数

    -

    [method:String decodeText]( [param:TypedArray array] )

    -

    - [page:TypedArray array] — 作为类型化数组的字节流 -

    -

    - 该函数将字节流作为输入并返回字符串作为表示。 -

    -

    [method:String extractUrlBase]( [param:String url] )

    [page:String url] — 从基本URL中,进行提取的URL。 diff --git a/docs/api/zh/loaders/ObjectLoader.html b/docs/api/zh/loaders/ObjectLoader.html index 0ab05b05cf062d..fa0ed7d8851042 100644 --- a/docs/api/zh/loaders/ObjectLoader.html +++ b/docs/api/zh/loaders/ObjectLoader.html @@ -54,7 +54,7 @@

    代码示例

    例子

    - [example:webgl_materials_lightmap WebGL / materials / lightmap] + [example:webgpu_materials_lightmap WebGL / materials / lightmap]

    构造函数

    diff --git a/docs/api/zh/loaders/managers/LoadingManager.html b/docs/api/zh/loaders/managers/LoadingManager.html index b112a21a21d96b..e7c1edc800d732 100644 --- a/docs/api/zh/loaders/managers/LoadingManager.html +++ b/docs/api/zh/loaders/managers/LoadingManager.html @@ -50,7 +50,7 @@

    代码示例

    }; - const loader = new THREE.OBJLoader( manager ); + const loader = new OBJLoader( manager ); loader.load( 'file.obj', function ( object ) { // @@ -83,7 +83,7 @@

    代码示例

    } ); // 像通常一样加载,然后撤消blob URL - const loader = new THREE.GLTFLoader( manager ); + const loader = new GLTFLoader( manager ); loader.load( 'fish.gltf', (gltf) => { scene.add( gltf.scene ); @@ -98,7 +98,6 @@

    例子

    [example:webgl_loader_fbx WebGL / loader / fbx]
    [example:webgl_loader_obj WebGL / loader / obj]
    - [example:webgl_materials_physical_reflectivity WebGL / materials / physical / reflectivity]
    [example:webgl_postprocessing_outline WebGL / postprocesing / outline]

    diff --git a/docs/api/zh/materials/LineBasicMaterial.html b/docs/api/zh/materials/LineBasicMaterial.html index 64ca0ae18ee7dd..c051799845fc8b 100644 --- a/docs/api/zh/materials/LineBasicMaterial.html +++ b/docs/api/zh/materials/LineBasicMaterial.html @@ -38,8 +38,6 @@

    例子

    [example:webgl_interactive_voxelpainter WebGL / interactive / voxelpainter]
    [example:webgl_lines_colors WebGL / lines / colors]
    [example:webgl_lines_dashed WebGL / lines / dashed]
    - [example:webgl_lines_sphere WebGL / lines / sphere]
    - [example:webgl_materials WebGL / materials]
    [example:physics_ammo_rope physics / ammo / rope]

    @@ -81,7 +79,7 @@

    [property:String linejoin]

    [property:Texture map]

    - Sets the color of the lines using data from a [page:Texture]. + 将[page:Texture]的颜色映射到线条上。

    方法(Methods)

    diff --git a/docs/api/zh/materials/LineDashedMaterial.html b/docs/api/zh/materials/LineDashedMaterial.html index c53c07a88583fb..ff27ad91b9a1f9 100644 --- a/docs/api/zh/materials/LineDashedMaterial.html +++ b/docs/api/zh/materials/LineDashedMaterial.html @@ -11,7 +11,10 @@

    虚线材质([name])

    -

    一种用于绘制虚线样式几何体的材质。

    +

    + 一种用于绘制虚线样式几何体的材质。
    + Note: You must call [page:Line.computeLineDistances]() when using [name]. +

    代码示例

    @@ -50,7 +53,7 @@

    [property:number gapSize]

    [property:Boolean isLineDashedMaterial]

    - Read-only flag to check if a given object is of type [name]. + 只读标志,用于检查给定对象是否属于 [name] 类型。

    [property:number scale]

    diff --git a/docs/api/zh/materials/Material.html b/docs/api/zh/materials/Material.html index 8bcbeefe5a7efd..3dbe724183a731 100644 --- a/docs/api/zh/materials/Material.html +++ b/docs/api/zh/materials/Material.html @@ -24,14 +24,28 @@

    [name]()

    属性(Properties)

    +

    [property:Boolean alphaHash]

    +

    +启用alphaHash透明度,这是[page:.transparent]或[page:.alphaTest]的替代方案。如果不透明度低于随机阈值,则不会渲染材质。随机化会引入一些颗粒或噪点,但相较于传统的Alpha blend方式,避免了透明度引起的深度排序问题。使用TAARenderPass可以有效减少噪点。 +

    +

    [property:Float alphaTest]

    设置运行alphaTest时要使用的alpha值。如果不透明度低于此值,则不会渲染材质。默认值为*0*。

    [property:Boolean alphaToCoverage]

    -

    -启用alpha to coverage. 只能在开启了MSAA的渲染环境中使用 (当渲染器创建的时候*antialias* 属性要*true*才能使用). -默认为 *false*. +

    启用 alpha 覆盖。 只能与启用 MSAA 的上下文一起使用(意味着在创建渲染器时将抗锯齿参数 *antialias* 设置为 `true`)。 + 启用此选项将平滑剪裁平面边缘和 alphaTest 剪辑边缘上的锯齿。 默认值为 `false`。 +

    + +

    [property:Float blendAlpha]

    +

    表示恒定混合颜色的 alpha 值。 默认值为 `0`。
    + 此属性仅在使用 [page:CustomBlendingEquation ConstantAlpha] 或 [page:CustomBlendingEquation OneMinusConstantAlpha] 自定义混合时有效。 +

    + +

    [property:Color blendColor]

    +

    表示恒定混合颜色的 RGB 值。 默认值为 `0x000000`。
    + 此属性仅在使用 [page:CustomBlendingEquation ConstantColor] 或 [page:CustomBlendingEquation OneMinusConstantColor] 自定义混合时有效。

    [property:Integer blendDst]

    @@ -298,6 +312,26 @@

    [method:undefined onBeforeCompile]( [param:Shader shader], [param:WebGLRende

    和其他属性不一样的是,这个回调在[page:Material.clone .clone](),[page:Material.copy .copy]() 和 [page:Material.toJSON .toJSON]() 中不支持。

    +

    + 这个回调只支持在 `WebGLRenderer` (不支持 `WebGPURenderer`)。 +

    +

    + [example:webgl_materials_modified WebGL / materials / modified]
    + [example:webgl_shadow_contact WebGL / shadow / contact] +

    + +

    + [method:undefined onBeforeRender]( [param:WebGLRenderer renderer], [param:Scene scene], [param:Camera camera], [param:BufferGeometry geometry], [param:Object3D object], [param:Group group] ) +

    +

    + 在使用材质渲染 3D 对象之前立即执行的可选回调。 +

    +

    + 与属性不同,回调不支持 [page:Material.clone .clone]()、[page:Material.copy .copy]() 和 [page:Material.toJSON .toJSON]()。 +

    +

    + 此回调仅支持 `WebGLRenderer` (不支持 `WebGPURenderer`)。 +

    [method:String customProgramCacheKey]()

    diff --git a/docs/api/zh/materials/MeshBasicMaterial.html b/docs/api/zh/materials/MeshBasicMaterial.html index 5ae2247bb0a9fa..c94748bd313ed6 100644 --- a/docs/api/zh/materials/MeshBasicMaterial.html +++ b/docs/api/zh/materials/MeshBasicMaterial.html @@ -73,6 +73,11 @@

    [property:Integer combine]

    [property:Texture envMap]

    环境贴图。默认值为null。

    +

    [property:Euler envMapRotation]

    +

    + 环境贴图的旋转(以弧度为单位)。默认值为 `(0,0,0)`。 +

    +

    [property:Boolean fog]

    材质是否受雾影响。默认为*true*。

    @@ -95,8 +100,6 @@

    [property:Float reflectivity]

    [property:Float refractionRatio]

    空气的折射率(IOR)(约为1)除以材质的折射率。它与环境映射模式[page:Textures THREE.CubeRefractionMapping] 和[page:Textures THREE.EquirectangularRefractionMapping]一起使用。 - The index of refraction (IOR) of air (approximately 1) divided by the index of refraction of the material. - It is used with environment mapping mode [page:Textures THREE.CubeRefractionMapping]. 折射率不应超过1。默认值为*0.98*。

    diff --git a/docs/api/zh/materials/MeshDistanceMaterial.html b/docs/api/zh/materials/MeshDistanceMaterial.html index ba6adeac12395a..b16bcbfd4ade23 100644 --- a/docs/api/zh/materials/MeshDistanceMaterial.html +++ b/docs/api/zh/materials/MeshDistanceMaterial.html @@ -9,7 +9,7 @@ [page:Material] → -

    [name]

    +

    距离网格材质([name])

    [name] 在内部用于使用[page:PointLight]来实现阴影映射。 diff --git a/docs/api/zh/materials/MeshLambertMaterial.html b/docs/api/zh/materials/MeshLambertMaterial.html index 5fa9c7d3a4f9ea..becedd86650b9a 100644 --- a/docs/api/zh/materials/MeshLambertMaterial.html +++ b/docs/api/zh/materials/MeshLambertMaterial.html @@ -62,7 +62,8 @@

    [property:Texture aoMap]

    该纹理的红色通道用作环境遮挡贴图。默认值为null。aoMap需要第二组UV。

    [property:Float aoMapIntensity]

    -

    环境遮挡效果的强度。默认值为1。零是不遮挡效果。

    +

    环境光遮蔽效果的强度。范围为 0-1,其中 `0` 表示禁用环境光遮蔽。当强度为 `1` 且 [page:.aoMap] 红色通道也为 `1`时,环境光在表面上完全被遮蔽。默认值为`1`。 +

    [property:Texture bumpMap]

    用于创建凹凸贴图的纹理。黑色和白色值映射到与光照相关的感知深度。凹凸实际上不会影响对象的几何形状,只影响光照。如果定义了法线贴图,则将忽略该贴图。 @@ -110,6 +111,11 @@

    [property:Float emissiveIntensity]

    [property:Texture envMap]

    环境贴图。默认值为null。

    +

    [property:Euler envMapRotation]

    +

    + 环境贴图的旋转(以弧度为单位)。默认值为 `(0,0,0)`。 +

    +

    [property:Boolean flatShading]

    定义材质是否使用平面着色进行渲染。默认值为false。

    @@ -130,9 +136,9 @@

    [property:Texture map]

    [property:Texture normalMap]

    -

    用于创建法线贴图的纹理。RGB值会影响每个像素片段的曲面法线,并更改颜色照亮的方式。法线贴图不会改变曲面的实际形状,只会改变光照。 - In case the material has a normal map authored using the left handed convention, the y component of normalScale - should be negated to compensate for the different handedness. +

    + 用于创建法线贴图的纹理。RGB 值会影响每个像素片段的曲面法线,并更改颜色照亮的方式。法线贴图不会改变曲面的实际形状,只会改变光照。 + 如果材质的法线贴图使用左手惯例编写,则应将 normalScale 的 y 分量取反以补偿不同的手性。

    [property:Integer normalMapType]

    @@ -150,9 +156,7 @@

    [property:Float reflectivity]

    [property:Float refractionRatio]

    空气的折射率(IOR)(约为1)除以材质的折射率。它与环境映射模式[page:Textures THREE.CubeRefractionMapping] 和[page:Textures THREE.EquirectangularRefractionMapping]一起使用。 - The index of refraction (IOR) of air (approximately 1) divided by the index of refraction of the material. - It is used with environment mapping mode [page:Textures THREE.CubeRefractionMapping]. - 折射率不应超过1。默认值为*0.98*。 + 折射率不应超过 1。默认值为 *0.98*。

    [property:Texture specularMap]

    diff --git a/docs/api/zh/materials/MeshMatcapMaterial.html b/docs/api/zh/materials/MeshMatcapMaterial.html index 503bd22d5fb613..1ab4601c922d70 100644 --- a/docs/api/zh/materials/MeshMatcapMaterial.html +++ b/docs/api/zh/materials/MeshMatcapMaterial.html @@ -9,7 +9,7 @@ [page:Material] → -

    [name]

    +

    材质捕捉网格材质([name])

    [name] 由一个材质捕捉(MatCap,或光照球(Lit Sphere))纹理所定义,其编码了材质的颜色与明暗。

    diff --git a/docs/api/zh/materials/MeshNormalMaterial.html b/docs/api/zh/materials/MeshNormalMaterial.html index 90d39ad01dc465..dc771defd7f61c 100644 --- a/docs/api/zh/materials/MeshNormalMaterial.html +++ b/docs/api/zh/materials/MeshNormalMaterial.html @@ -94,8 +94,6 @@

    [property:Float wireframeLinewidth]

    方法(Methods)

    共有方法请参见其基类[page:Material]。

    - -

    源码(Source)

    diff --git a/docs/api/zh/materials/MeshPhongMaterial.html b/docs/api/zh/materials/MeshPhongMaterial.html index 17d0fccd9596ca..21ea67b0287f71 100644 --- a/docs/api/zh/materials/MeshPhongMaterial.html +++ b/docs/api/zh/materials/MeshPhongMaterial.html @@ -110,6 +110,11 @@

    [property:Float emissiveIntensity]

    [property:Texture envMap]

    环境贴图。默认值为null。

    +

    [property:Euler envMapRotation]

    +

    + 环境贴图的旋转(以弧度为单位)。默认值为 `(0,0,0)`。 +

    +

    [property:Boolean flatShading]

    定义材质是否使用平面着色进行渲染。默认值为false。

    @@ -131,9 +136,9 @@

    [property:Texture map]

    [property:Texture normalMap]

    -

    用于创建法线贴图的纹理。RGB值会影响每个像素片段的曲面法线,并更改颜色照亮的方式。法线贴图不会改变曲面的实际形状,只会改变光照。 - In case the material has a normal map authored using the left handed convention, the y component of normalScale - should be negated to compensate for the different handedness. +

    + 用于创建法线贴图的纹理。RGB 值会影响每个像素片段的曲面法线,并更改颜色照亮的方式。法线贴图不会改变曲面的实际形状,只会改变光照。 + 如果材质的法线贴图使用左手惯例编写,则应将 normalScale 的 y 分量取反以补偿不同的手性。

    [property:Integer normalMapType]

    diff --git a/docs/api/zh/materials/MeshPhysicalMaterial.html b/docs/api/zh/materials/MeshPhysicalMaterial.html index fed77cfa5a8daf..6da5bc4ad5faf6 100644 --- a/docs/api/zh/materials/MeshPhysicalMaterial.html +++ b/docs/api/zh/materials/MeshPhysicalMaterial.html @@ -17,7 +17,13 @@

    物理网格材质([name])

    • - Clearcoat: 有些类似于车漆,碳纤,被水打湿的表面的材质需要在面上再增加一个透明的,具有一定反光特性的面。而且这个面说不定有一定的起伏与粗糙度。Clearcoat可以在不需要重新创建一个透明的面的情况下做到类似的效果。 + 各向异性(Anisotropy): 能够表现出拉丝金属所观察到的材料的各向异性特性。 +
    • +
    • + 清漆(Clearcoat): 有些类似于车漆,碳纤,被水打湿的表面的材质需要在面上再增加一个透明的,具有一定反光特性的面。而且这个面说不定有一定的起伏与粗糙度。Clearcoat 可以在不需要重新创建一个透明的面的情况下做到类似的效果。 +
    • +
    • + 虹彩(Iridescence): 允许渲染色调根据视角和照明角度而变化的效果。这可以在肥皂泡、油膜或许多昆虫的翅膀上看到。
    • 基于物理的透明度:[page:Material.opacity .opacity]属性有一些限制:在透明度比较高的时候,反射也随之减少。使用基于物理的透光性[page:.transmission]属性可以让一些很薄的透明表面,例如玻璃,变得更真实一些。 @@ -26,7 +32,7 @@

      物理网格材质([name])

      高级光线反射: 为非金属材质提供了更多更灵活的光线反射。
    • - Sheen: Can be used for representing cloth and fabric materials. + 光泽(Sheen): 可用于表示布料和织物材料。
    @@ -55,9 +61,9 @@

    物理网格材质([name])

    例子

    - [example:webgl_materials_variations_physical materials / variations / physical]
    + [example:webgl_loader_gltf_anisotropy loader / gltf / anisotropy]
    [example:webgl_materials_physical_clearcoat materials / physical / clearcoat]
    - [example:webgl_materials_physical_reflectivity materials / physical / reflectivity]
    + [example:webgl_loader_gltf_iridescence loader / gltf / iridescence]
    [example:webgl_loader_gltf_sheen loader / gltf / sheen]
    [example:webgl_materials_physical_transmission materials / physical / transmission]

    @@ -74,14 +80,29 @@

    [name]( [param:Object parameters] )

    属性(Properties)

    共有属性请参见其基类[page:Material]和[page:MeshStandardMaterial]。

    +

    [property:Float anisotropy]

    +

    + 各向异性强度。默认值为 `0.0`。 +

    + +

    [property:Texture anisotropyMap]

    +

    + 红色和绿色通道表示 `[-1, 1]` 切线、双切线空间中的各向异性方向,将通过 [page:.anisotropyRotation] 进行旋转。蓝色通道包含 `[0, 1]` 的强度,将与 [page:.anisotropy]相乘。默认值为 `null`。 +

    + +

    [property:Float anisotropyRotation]

    +

    + 切线、双切线空间中各向异性的旋转,以弧度为单位从切线逆时针测量。当存在 [page:.anisotropyMap] 时,此属性为纹理中的矢量提供额外的旋转。默认值为 `0.0`。 +

    +

    [property:Color attenuationColor]

    - The color that white light turns into due to absorption when reaching the attenuation distance. Default is *white* (0xffffff). + 白光到达衰减距离后因吸收而变成的颜色。默认值为 *white* (0xffffff)。

    [property:Float attenuationDistance]

    - Density of the medium given as the average distance that light travels in the medium before interacting with a particle. The value is given in world space units, and must be greater than zero. Default is `Infinity`. + 介质的密度,表示光在与粒子相互作用之前在介质中传播的平均距离。该值以世界空间单位给出,并且必须大于零。默认值为 `Infinity`。

    [property:Float clearcoat]

    @@ -121,6 +142,13 @@

    [property:Object defines]

    [page:WebGLRenderer]使用它来选择shaders。

    +

    [property:Float dispersion]

    +

    + 定义穿过相对清晰体积的颜色角分离(色差)的强度。任何零或更大的值都是有效的,实际值的典型范围是 `[0, 1]`。 + 默认值为 `0` (无色散)。 + 此属性只能用于透射对象,请参见 [page:.transmission]。 +

    +

    [property:Float ior]

    为非金属材质所设置的折射率,范围由*1.0*到*2.333*。默认为*1.5*。 @@ -133,6 +161,41 @@

    [property:Float reflectivity]


    这模拟了非金属材质的反射率。当[page:MeshStandardMaterial metalness]为*1.0*时,此属性无效。

    + +

    [property:Float iridescence]

    +

    + 虹彩([link:https://en.wikipedia.org/wiki/Iridescence iridescence])层的强度,根据表面和观察者之间的角度模拟RGB颜色偏移,从 `0.0` 到 `1.0`。默认值为 `0.0`。 +

    + +

    [property:Texture iridescenceMap]

    +

    + 此纹理的红色通道与 [page:.iridescence] 相乘,以实现对虹彩的每像素控制。默认值为 `null`。 +

    + +

    [property:Float iridescenceIOR]

    +

    + 虹彩 RGB 色移效果的强度,由折射率表示。在 `1.0` 到 `2.333` 之间。 + 默认值为 `1.3`。 +

    + +

    [property:Array iridescenceThicknessRange]

    +

    + 由 2 个元素组成的数组,指定虹彩层的最小和最大厚度。 + 虹彩层的厚度与 [page:.thickness] 对 [page:.ior] 的影响相同。默认值为 `[100, 400]`。
    + + 如果未定义 [page:.iridescenceThicknessMap],则虹彩厚度将仅使用给定数组的第二个元素。 +

    + +

    [property:Texture iridescenceThicknessMap]

    +

    + 一种定义虹彩层厚度的纹理,存储在绿色通道中。厚度的最小值和最大值由 [page:.iridescenceThicknessRange] 数组定义:
    +

      +
    • 绿色通道中的 `0.0` 将导致厚度等于数组的第一个元素。
    • +
    • 绿色通道中的 `1.0` 将导致厚度等于数组的第二个元素。
    • +
    • 中间的值将在数组元素之间线性插入。
    • +
    + 默认值为 `null`。 +

    [property:Float sheen]

    @@ -151,7 +214,7 @@

    [property:Texture sheenRoughnessMap]

    [property:Color sheenColor]

    - 光泽颜色,默认为*0xffffff*白色。 + 光泽颜色,默认为*0x000000*黑色。

    [property:Texture sheenColorMap]

    @@ -161,7 +224,7 @@

    [property:Texture sheenColorMap]

    [property:Float specularIntensity]

    - 用于控制非金属材质高光反射强度的浮点值。漫反射材质对应的值为0。范围从*0.0*到*1.0*。 默认值为*0.0*。 + 用于控制非金属材质高光反射强度的浮点值。漫反射材质对应的值为0。范围从*0.0*到*1.0*。 默认值为*1.0*。

    [property:Texture specularIntensityMap]

    @@ -181,15 +244,14 @@

    [property:Texture specularColorMap]

    [property:Float thickness]

    - The thickness of the volume beneath the surface. The value is given in the coordinate space of the mesh. If the value is 0 the material is thin-walled. Otherwise the material is a volume boundary. Default is *0*. + 表面下体积的厚度。该值在网格的坐标空间中给出。如果该值为 *0*,则材料为薄壁。否则,材质将成为体积边界。默认值为 *0*。

    [property:Texture thicknessMap]

    - A texture that defines the thickness, stored in the G channel. This will be multiplied by [page:.thickness]. Default is *null*. + 定义厚度的纹理,存储在 G 通道中。这将与 [page:.thickness] 相乘。默认值为 *null*。

    -

    [property:Float transmission]

    透光率(或者说透光性),范围从*0.0*到*1.0*。默认值是*0.0*。
    diff --git a/docs/api/zh/materials/MeshStandardMaterial.html b/docs/api/zh/materials/MeshStandardMaterial.html index cd8ce1614d04a3..dad4217736202e 100644 --- a/docs/api/zh/materials/MeshStandardMaterial.html +++ b/docs/api/zh/materials/MeshStandardMaterial.html @@ -132,6 +132,11 @@

    [property:Texture envMap]

    环境贴图,为了能够保证物理渲染准确,您应该添加由[page:PMREMGenerator]预处理过的环境贴图,默认为null。

    +

    [property:Euler envMapRotation]

    +

    + 环境贴图的旋转(以弧度为单位)。默认值为 `(0,0,0)`。 +

    +

    [property:Float envMapIntensity]

    通过乘以环境贴图的颜色来缩放环境贴图的效果。

    @@ -169,9 +174,9 @@

    [property:Texture metalnessMap]

    该纹理的蓝色通道用于改变材质的金属度。

    [property:Texture normalMap]

    -

    用于创建法线贴图的纹理。RGB值会影响每个像素片段的曲面法线,并更改颜色照亮的方式。法线贴图不会改变曲面的实际形状,只会改变光照。 - In case the material has a normal map authored using the left handed convention, the y component of normalScale - should be negated to compensate for the different handedness. +

    + 用于创建法线贴图的纹理。RGB 值会影响每个像素片段的曲面法线,并更改颜色照亮的方式。法线贴图不会改变曲面的实际形状,只会改变光照。 + 如果材质的法线贴图使用左手惯例编写,则应将 normalScale 的 y 分量取反以补偿不同的手性。

    [property:Integer normalMapType]

    @@ -183,14 +188,6 @@

    [property:Vector2 normalScale]

    法线贴图对材质的影响程度。典型范围是0-1。默认值是[page:Vector2]设置为(1,1)。

    -

    [property:Float refractionRatio]

    -

    空气的折射率(IOR)(约为1)除以材质的折射率。它与环境映射模式[page:Textures THREE.CubeRefractionMapping] - 和[page:Textures THREE.EquirectangularRefractionMapping]一起使用。 - The index of refraction (IOR) of air (approximately 1) divided by the index of refraction of the material. - It is used with environment mapping mode [page:Textures THREE.CubeRefractionMapping]. - 折射率不应超过1。默认值为*0.98*。 -

    -

    [property:Float roughness]

    材质的粗糙程度。0.0表示平滑的镜面反射,1.0表示完全漫反射。默认值为1.0。如果还提供roughnessMap,则两个值相乘。

    diff --git a/docs/api/zh/materials/MeshToonMaterial.html b/docs/api/zh/materials/MeshToonMaterial.html index b03133e3f8dc6d..7da89a20662e72 100644 --- a/docs/api/zh/materials/MeshToonMaterial.html +++ b/docs/api/zh/materials/MeshToonMaterial.html @@ -9,7 +9,7 @@ [page:Material] → -

    [name]

    +

    卡通网格材质([name])

    一种实现卡通着色的材质。
    @@ -33,7 +33,7 @@

    [name]

    例子

    - [example:webgl_materials_variations_toon materials / variations / toon] + [example:webgl_materials_toon materials / toon]

    构造函数(Constructor)

    diff --git a/docs/api/zh/materials/PointsMaterial.html b/docs/api/zh/materials/PointsMaterial.html index 8693b118da28f1..62ed75df0ba3f1 100644 --- a/docs/api/zh/materials/PointsMaterial.html +++ b/docs/api/zh/materials/PointsMaterial.html @@ -51,11 +51,12 @@

    例子

    [example:webgl_multiple_elements_text WebGL / multiple / elements / text]
    [example:webgl_points_billboards WebGL / points / billboards]
    [example:webgl_points_dynamic WebGL / points / dynamic]
    - [example:webgl_points_sprites WebGL / points / sprites]
    - [example:webgl_trails WebGL / trails] + [example:webgl_points_sprites WebGL / points / sprites]

    -

    [name]( [param:Object parameters] )

    +

    构造函数(Constructor)

    + +

    [name]( [param:Object parameters] )

    [page:Object parameters] - (可选)用于定义材质外观的对象,具有一个或多个属性。 材质的任何属性都可以从此处传入(包括从[page:Material]继承的任何属性)。

    diff --git a/docs/api/zh/materials/RawShaderMaterial.html b/docs/api/zh/materials/RawShaderMaterial.html index 3cb69d8bb6bdcc..70ccf66abfe9ad 100644 --- a/docs/api/zh/materials/RawShaderMaterial.html +++ b/docs/api/zh/materials/RawShaderMaterial.html @@ -33,10 +33,9 @@

    例子

    [example:webgl_buffergeometry_rawshader WebGL / buffergeometry / rawshader]
    [example:webgl_buffergeometry_instancing_billboards WebGL / buffergeometry / instancing / billboards]
    [example:webgl_buffergeometry_instancing WebGL / buffergeometry / instancing]
    - [example:webgl_raymarching_reflect WebGL / raymarching / reflect]
    - [example:webgl2_volume_cloud WebGL 2 / volume / cloud]
    - [example:webgl2_volume_instancing WebGL 2 / volume / instancing]
    - [example:webgl2_volume_perlin WebGL 2 / volume / perlin] + [example:webgl_volume_cloud WebGL / volume / cloud]
    + [example:webgl_volume_instancing WebGL / volume / instancing]
    + [example:webgl_volume_perlin WebGL / volume / perlin]

    构造函数(Constructor)

    diff --git a/docs/api/zh/materials/ShaderMaterial.html b/docs/api/zh/materials/ShaderMaterial.html index 9aada1c0d6ffde..5e4099a39924fc 100644 --- a/docs/api/zh/materials/ShaderMaterial.html +++ b/docs/api/zh/materials/ShaderMaterial.html @@ -97,7 +97,6 @@

    例子

    [example:webgl_lights_hemisphere webgl / lights / hemisphere]
    [example:webgl_marchingcubes webgl / marchingcubes]
    [example:webgl_materials_envmaps webgl / materials / envmaps]
    - [example:webgl_materials_lightmap webgl / materials / lightmap]
    [example:webgl_materials_wireframe webgl / materials / wireframe]
    [example:webgl_modifier_tessellation webgl / modifier / tessellation]
    [example:webgl_postprocessing_dof2 webgl / postprocessing / dof2]
    @@ -280,10 +279,8 @@

    [property:Object extensions]

    一个有如下属性的对象: this.extensions = { - derivatives: false, // set to use derivatives - fragDepth: false, // set to use fragment depth values - drawBuffers: false, // set to use draw buffers - shaderTextureLOD: false // set to use shader texture LOD + clipCullDistance: false, // set to use vertex shader clipping + multiDraw: false // set to use vertex shader multi_draw / enable gl_DrawID };

    @@ -303,8 +300,7 @@

    [property:String fragmentShader]

    [property:String glslVersion]

    - Defines the GLSL version of custom shader code. Only relevant for WebGL 2 in order to define whether to specify - GLSL 3.0 or not. Valid values are *THREE.GLSL1* or *THREE.GLSL3*. Default is *null*. + 定义自定义着色器代码的 GLSL 版本。有效值为 *THREE.GLSL1* 或 *THREE.GLSL3*。默认值为 *null*。

    [property:String index0AttributeName]

    @@ -315,7 +311,7 @@

    [property:String index0AttributeName]

    [property:Boolean isShaderMaterial]

    - Read-only flag to check if a given object is of type [name]. + 只读标志,用于检查给定对象是否为 [name]。

    [property:Boolean lights]

    @@ -346,12 +342,12 @@

    [property:Object uniforms]

    [property:Boolean uniformsNeedUpdate]

    - Can be used to force a uniform update while changing uniforms in [page:Object3D.onBeforeRender](). Default is *false*. + 可用于在 [page:Object3D.onBeforeRender]()中更改 uniforms 时强制进行 uniforms 更新。默认值为 *false*。

    [property:Boolean vertexColors]

    - Defines whether vertex coloring is used. Default is *false*. + 定义是否使用顶点着色。默认为 *false*。

    [property:String vertexShader]

    @@ -374,7 +370,7 @@

    [property:Float wireframeLinewidth]

    方法(Methods)

    共有方法请参见其基类[page:Material]。

    -

    [method:ShaderMaterial clone]() [param:ShaderMaterial this]

    +

    [method:ShaderMaterial clone]()

    创建该材质的一个浅拷贝。需要注意的是,vertexShader和fragmentShader使用引用拷贝; *attributes*的定义也是如此; 这意味着,克隆的材质将共享相同的编译[page:WebGLProgram]; diff --git a/docs/api/zh/materials/ShadowMaterial.html b/docs/api/zh/materials/ShadowMaterial.html index bd056155e23261..1f9c76f82f4f91 100644 --- a/docs/api/zh/materials/ShadowMaterial.html +++ b/docs/api/zh/materials/ShadowMaterial.html @@ -48,7 +48,7 @@

    属性(Properties)

    共有属性请参见其基类[page:Material]。

    [property:Color color]

    -

    [page:Color] of the material, by default set to black (0x000000).

    +

    材质的颜色 [page:Color] ,默认设置为黑色 (0x000000)。

    [property:Boolean fog]

    材质是否受雾影响。默认为*true*。

    diff --git a/docs/api/zh/materials/SpriteMaterial.html b/docs/api/zh/materials/SpriteMaterial.html index cbde13526ec74b..04d36cb770e117 100644 --- a/docs/api/zh/materials/SpriteMaterial.html +++ b/docs/api/zh/materials/SpriteMaterial.html @@ -36,7 +36,7 @@

    构造函数(Constructor)

    [name]( [param:Object parameters] )

    [page:Object parameters] - (可选)用于定义材质外观的对象,具有一个或多个属性。 - 材质的任何属性都可以从此处传入(包括从[page:Material] 和 [page:ShaderMaterial]继承的任何属性)。

    + 材质的任何属性都可以从此处传入(包括从[page:Material]继承的任何属性)。

    属性[page:Hexadecimal color]例外,其可以作为十六进制字符串传递,默认情况下为 *0xffffff*(白色), 内部调用[page:Color.set](color)。 @@ -61,11 +61,11 @@

    [property:Color color]

    材质的颜色([page:Color]),默认值为白色 (0xffffff)。 [page:.map]会和 color 相乘。

    [property:Boolean fog]

    -

    材质是否受雾影响。默认为*true*。

    +

    材质是否受雾影响。默认为 *true*。

    [property:Boolean isSpriteMaterial]

    - Read-only flag to check if a given object is of type [name]. + 只读标志,用于检查给定对象是否属于 [name]。

    [property:Texture map]

    diff --git a/docs/api/zh/math/Box3.html b/docs/api/zh/math/Box3.html index 8ee5638bd8df36..3c0bcccaed3756 100644 --- a/docs/api/zh/math/Box3.html +++ b/docs/api/zh/math/Box3.html @@ -51,7 +51,7 @@

    属性(Properties)

    [property:Boolean isBox3]

    - Read-only flag to check if a given object is of type [name]. + 只读属性,用于检查给定对象是否为[name]类型。

    [property:Vector3 min]

    diff --git a/docs/api/zh/math/Color.html b/docs/api/zh/math/Color.html index 2e77617fb21007..764ef2a470ffa5 100644 --- a/docs/api/zh/math/Color.html +++ b/docs/api/zh/math/Color.html @@ -79,7 +79,7 @@

    属性(Properties)

    [property:Boolean isColor]

    - Read-only flag to check if a given object is of type [name]. + 只读属性,用于检查给定对象是否为[name]类型。

    [property:Float r]

    @@ -182,7 +182,7 @@

    [method:Object getHSL]( [param:Object target], [param:string colorSpace] = L

    -

    [method:Color getRGB]( [param:Color target], [param:string colorSpace] = SRGBColorSpace )

    +

    [method:Color getRGB]( [param:Color target], [param:string colorSpace] = LinearSRGBColorSpace )

    [page:Color target] - 结果将复制到这个对象中.

    diff --git a/docs/api/zh/math/Euler.html b/docs/api/zh/math/Euler.html index 0a11718514c925..747bd4da23a2a3 100644 --- a/docs/api/zh/math/Euler.html +++ b/docs/api/zh/math/Euler.html @@ -44,7 +44,7 @@

    属性(Properties)

    [property:Boolean isEuler]

    - Read-only flag to check if a given object is of type [name]. + 只读属性,用于检查给定对象是否为[name]类型。

    [property:String order]

    diff --git a/docs/api/zh/math/MathUtils.html b/docs/api/zh/math/MathUtils.html index c16840b19b3f40..6a7e6a9fa9631b 100644 --- a/docs/api/zh/math/MathUtils.html +++ b/docs/api/zh/math/MathUtils.html @@ -123,7 +123,7 @@

    [method:Float smootherstep]( [param:Float x], [param:Float min], [param:Floa

    [page:Float x] - 根据其在最小值和最大值之间的位置来计算的值。
    [page:Float min] - 任何x比最小值还小会返回0.
    - [page:Float max] - 任何x比最大值还大会返回0.

    + [page:Float max] - 任何x比最大值还大会返回1.

    返回一个0-1之间的值。它和smoothstep相同,但变动更平缓。[link:https://en.wikipedia.org/wiki/Smoothstep#Variations variation on smoothstep] 在x=0和x=1处有0阶和二阶导数。

    diff --git a/docs/api/zh/math/Matrix3.html b/docs/api/zh/math/Matrix3.html index 356cdaa77c2d89..08a587b362684b 100644 --- a/docs/api/zh/math/Matrix3.html +++ b/docs/api/zh/math/Matrix3.html @@ -44,7 +44,7 @@

    Constructor

    [name]( [param:Number n11], [param:Number n12], [param:Number n13], - [param:Number n21], [param:Number n22], [param:Number n22], + [param:Number n21], [param:Number n22], [param:Number n23], [param:Number n31], [param:Number n32], [param:Number n33] )

    Creates a 3x3 matrix with the given arguments in row-major order. If no arguments are provided, the constructor initializes @@ -80,19 +80,80 @@

    [method:Boolean equals]( [param:Matrix3 m] )

    [method:this extractBasis]( [param:Vector3 xAxis], [param:Vector3 yAxis], [param:Vector3 zAxis] )

    将该矩阵的基向量 [link:https://en.wikipedia.org/wiki/Basis_(linear_algebra) basis] 提取到提供的三个轴向中。如果该矩阵如下: - -a, b, c, -d, e, f, -g, h, i - +

    + + + + [ + + + a + b + c + + + d + e + f + + + g + h + i + + + ] + + + +

    那么 [page:Vector3 xAxis], [page:Vector3 yAxis], [page:Vector3 zAxis] 将会被设置为: - -xAxis = (a, d, g) -yAxis = (b, e, h) -zAxis = (c, f, i) -

    +
    + + + xAxis + = + [ + + a + d + g + + ] + + , + + + + yAxis + = + [ + + b + e + h + + ] + + , and + + + + zAxis + = + [ + + c + f + i + + ] + + +
    +

    [method:this fromArray]( [param:Array array], [param:Integer offset] )

    [page:Array array] - 用来存储设置元素数据的数组
    @@ -117,38 +178,110 @@

    [method:this getNormalMatrix]( [param:Matrix4 m] )

    [method:this identity]()

    将此矩阵重置为3x3单位矩阵: - -1, 0, 0 -0, 1, 0 -0, 0, 1 - -

    + + + [ + + + 1 + 0 + 0 + + + 0 + 1 + 0 + + + 0 + 0 + 1 + + + ] + + +

    [method:this makeRotation]( [param:Float theta] )

    [page:Float theta] — Rotation angle in radians. Positive values rotate counterclockwise.

    Sets this matrix as a 2D rotational transformation by [page:Float theta] radians. The resulting matrix will be: - -cos(θ) -sin(θ) 0 -sin(θ) cos(θ) 0 -0 0 1 -

    + + + [ + + + + cos + θ + + + -sin + θ + + + 0 + + + + + sin + θ + + + cos + θ + + + 0 + + + + 0 + 0 + 1 + + + ] + + +

    [method:this makeScale]( [param:Float x], [param:Float y] )

    [page:Float x] - the amount to scale in the X axis.
    [page:Float y] - the amount to scale in the Y axis.
    - Sets this matrix as a 2D scale transform: - -x, 0, 0, -0, y, 0, -0, 0, 1 - + Sets this matrix as a 2D scale transform:

    + + + + [ + + + x + 0 + 0 + + + 0 + y + 0 + + + 0 + 0 + 1 + + + ] + +

    [method:this makeTranslation]( [param:Vector2 v] )

    @@ -160,13 +293,32 @@

    [method:this makeTranslation]( [param:Float x], [param:Float y] )

    [page:Float y] - the amount to translate in the Y axis.
    Sets this matrix as a 2D translation transform: - -1, 0, x, -0, 1, y, -0, 0, 1 -

    + + + [ + + + 1 + 0 + x + + + 0 + 1 + y + + + 0 + 0 + 1 + + + ] + + +

    [method:this multiply]( [param:Matrix3 m] )

    将当前矩阵乘以矩阵[page:Matrix3 m]。

    @@ -178,16 +330,33 @@

    [method:this multiplyScalar]( [param:Float s] )

    [method:this set]( [param:Float n11], [param:Float n12], [param:Float n13], [param:Float n21], [param:Float n22], [param:Float n23], [param:Float n31], [param:Float n32], [param:Float n33] )

    - [page:Float n11] - 设置第一行第一列的值。
    - [page:Float n12] - 设置第一行第二列的值。
    - ...
    - ...
    - [page:Float n32] - 设置第三行第二列的值。
    - [page:Float n33] - 设置第三行第三列的值。

    - - 使用行优先 [link:https://en.wikipedia.org/wiki/Row-_and_column-major_order row-major] 的格式来设置该矩阵。 + 使用行优先 [link:https://en.wikipedia.org/wiki/Row-_and_column-major_order row-major] 的格式来设置该矩阵:

    + + + [ + + + n11 + n12 + n13 + + + n21 + n22 + n23 + + + n31 + n32 + n33 + + + ] + + +

    [method:this premultiply]( [param:Matrix3 m] )

    将矩阵[page:Matrix3 m]乘以当前矩阵。

    diff --git a/docs/api/zh/math/Matrix4.html b/docs/api/zh/math/Matrix4.html index 2a2251026d4c27..b8a5102ed67531 100644 --- a/docs/api/zh/math/Matrix4.html +++ b/docs/api/zh/math/Matrix4.html @@ -48,7 +48,7 @@

    四维矩阵([name])

    注意行优先列优先的顺序。

    - 设置[page:set]()方法参数采用行优先[link:https://en.wikipedia.org/wiki/Row-_and_column-major_order#Column-major_order row-major], + 设置[page:.set set]()方法参数采用行优先[link:https://en.wikipedia.org/wiki/Row-_and_column-major_order#Column-major_order row-major], 而它们在内部是用列优先[link:https://en.wikipedia.org/wiki/Row-_and_column-major_order column-major]顺序存储在数组当中。

    这意味着 @@ -153,20 +153,87 @@

    [method:this extractBasis]( [param:Vector3 xAxis], [param:Vector3 yAxis], [p

    将矩阵的基向量[link:https://en.wikipedia.org/wiki/Basis_(linear_algebra) basis]提取到指定的3个轴向量中。 如果矩阵如下: - -a, b, c, d, -e, f, g, h, -i, j, k, l, -m, n, o, p - - 然后x轴y轴z轴被设为: - -xAxis = (a, e, i) -yAxis = (b, f, j) -zAxis = (c, g, k) -

    + + + [ + + + a + b + c + d + + + e + f + g + h + + + i + j + k + l + + + m + n + o + p + + + ] + + + +

    然后x轴y轴z轴被设为:

    + +
    + + + xAxis + = + [ + + a + e + i + + ] + + , + + + + yAxis + = + [ + + b + f + j + + ] + + , and + + + + zAxis + = + [ + + c + g + k + + ] + + +
    +

    [method:this extractRotation]( [param:Matrix4 m] )

    将给定矩阵[page:Matrix4 m]的旋转分量提取到该矩阵的旋转分量中。 @@ -209,14 +276,41 @@

    [method:this makeRotationAxis]( [param:Vector3 axis], [param:Float theta] )<

    [method:this makeBasis]( [param:Vector3 xAxis], [param:Vector3 yAxis], [param:Vector3 zAxis] )

    通过给定的三个向量设置该矩阵为基矩阵[link:https://en.wikipedia.org/wiki/Basis_(linear_algebra) basis]: - -xAxis.x, yAxis.x, zAxis.x, 0, -xAxis.y, yAxis.y, zAxis.y, 0, -xAxis.z, yAxis.z, zAxis.z, 0, -0, 0, 0, 1 -

    + + + [ + + + xAxis.x + yAxis.x + zAxis.x + 0 + + + xAxis.y + yAxis.y + zAxis.y + 0 + + + xAxis.z + yAxis.z + zAxis.z + 0 + + + 0 + 0 + 0 + 1 + + + ] + + +

    [method:this makePerspective]( [param:Float left], [param:Float right], [param:Float top], [param:Float bottom], [param:Float near], [param:Float far] )

    创建一个透视投影矩阵[link:https://en.wikipedia.org/wiki/3D_projection#Perspective_projection perspective projection]。 @@ -240,56 +334,323 @@

    [method:this makeRotationFromQuaternion]( [param:Quaternion q] )

    将这个矩阵的旋转分量设置为四元数[page:Quaternion q]指定的旋转,如下链接所诉[link:https://en.wikipedia.org/wiki/Rotation_matrix#Quaternion here]。 矩阵的其余部分被设为单位矩阵。因此,给定四元数[page:Quaternion q] = w + xi + yj + zk,得到的矩阵为: - -1-2y²-2z² 2xy-2zw 2xz+2yw 0 -2xy+2zw 1-2x²-2z² 2yz-2xw 0 -2xz-2yw 2yz+2xw 1-2x²-2y² 0 -0 0 0 1 -

    + + + [ + + + + 1 + - + 2 + + y + 2 + + - + 2 + + z + 2 + + + + 2 + x + y + - + 2 + z + w + + + 2 + x + z + + + 2 + y + w + + + 0 + + + + + 2 + x + y + + + 2 + z + w + + + 1 + - + 2 + + x + 2 + + - + 2 + + z + 2 + + + + 2 + y + z + - + 2 + x + w + + + 0 + + + + + 2 + x + z + - + 2 + y + w + + + 2 + y + z + + + 2 + x + w + + + 1 + - + 2 + + x + 2 + + - + 2 + + y + 2 + + + + 0 + + + + 0 + 0 + 0 + 1 + + + ] + + +

    [method:this makeRotationX]( [param:Float theta] )

    [page:Float theta] — Rotation angle in radians.

    把该矩阵设置为绕x轴旋转弧度[page:Float theta] (θ)大小的矩阵。 结果如下: - -1 0 0 0 -0 cos(θ) -sin(θ) 0 -0 sin(θ) cos(θ) 0 -0 0 0 1 -

    + + + [ + + + 1 + 0 + 0 + 0 + + + + 0 + + + cos + θ + + + - + sin + θ + + + 0 + + + + + 0 + + + sin + θ + + + cos + θ + + + 0 + + + + 0 + 0 + 0 + 1 + + + ] + + +

    [method:this makeRotationY]( [param:Float theta] )

    [page:Float theta] — Rotation angle in radians.

    把该矩阵设置为绕Y轴旋转弧度[page:Float theta] (θ)大小的矩阵。 结果如下: - -cos(θ) 0 sin(θ) 0 -0 1 0 0 --sin(θ) 0 cos(θ) 0 -0 0 0 1 -

    + + + [ + + + + cos + θ + + + 0 + + + sin + θ + + + 0 + + + + 0 + 1 + 0 + 0 + + + + - + sin + θ + + + 0 + + + cos + θ + + + 0 + + + + 0 + 0 + 0 + 1 + + + ] + + +

    [method:this makeRotationZ]( [param:Float theta] )

    [page:Float theta] — Rotation angle in radians.

    把该矩阵设置为绕z轴旋转弧度[page:Float theta] (θ)大小的矩阵。 结果如下: - -cos(θ) -sin(θ) 0 0 -sin(θ) cos(θ) 0 0 -0 0 1 0 -0 0 0 1 -

    + + + [ + + + + cos + θ + + + - + sin + θ + + + 0 + + + 0 + + + + + sin + θ + + + cos + θ + + + 0 + + + 0 + + + + 0 + 0 + 1 + 0 + + + 0 + 0 + 0 + 1 + + + ] + + +

    [method:this makeScale]( [param:Float x], [param:Float y], [param:Float z] )

    [page:Float x] - 在X轴方向的缩放比。
    @@ -297,14 +658,41 @@

    [method:this makeScale]( [param:Float x], [param:Float y], [param:Float z] ) [page:Float z] - 在Z轴方向的缩放比。

    将这个矩阵设置为缩放变换: - -x, 0, 0, 0, -0, y, 0, 0, -0, 0, z, 0, -0, 0, 0, 1 -

    + + + [ + + + x + 0 + 0 + 0 + + + 0 + y + 0 + 0 + + + 0 + 0 + z + 0 + + + 0 + 0 + 0 + 1 + + + ] + + +

    [method:this makeShear]( [param:Float x], [param:Float y], [param:Float z] )

    [page:Float x] - 在X轴上剪切的量。
    @@ -312,28 +700,82 @@

    [method:this makeShear]( [param:Float x], [param:Float y], [param:Float z] ) [page:Float z] - 在Z轴上剪切的量。

    将此矩阵设置为剪切变换: - -1, y, z, 0, -x, 1, z, 0, -x, y, 1, 0, -0, 0, 0, 1 -

    + + + [ + + + 1 + yx + zx + 0 + + + xy + 1 + zy + 0 + + + xz + yz + 1 + 0 + + + 0 + 0 + 0 + 1 + + + ] + + +

    [method:this makeTranslation]( [param:Vector3 v] )

    [method:this makeTranslation]( [param:Float x], [param:Float y], [param:Float z] ) // optional API

    取传入参数[param:Vector3 v]中值设设置该矩阵为平移变换: - -1, 0, 0, x, -0, 1, 0, y, -0, 0, 1, z, -0, 0, 0, 1 -

    + + + [ + + + 1 + 0 + 0 + x + + + 0 + 1 + 0 + y + + + 0 + 0 + 1 + z + + + 0 + 0 + 0 + 1 + + + ] + + +

    [method:this multiply]( [param:Matrix4 m] )

    将当前矩阵乘以矩阵[page:Matrix4 m]。

    @@ -361,21 +803,76 @@

    [method:this setPosition]( [param:Vector3 v] )

    [method:this setPosition]( [param:Float x], [param:Float y], [param:Float z] ) // optional API

    取传入参数[param:Vector3 v]中值设置该矩阵的位置分量,不影响该矩阵的其余部分——即,如果该矩阵当前为: - -a, b, c, d, -e, f, g, h, -i, j, k, l, -m, n, o, p - -变成: - -a, b, c, v.x, -e, f, g, v.y, -i, j, k, v.z, -m, n, o, p -

    + + + [ + + + a + b + c + d + + + e + f + g + h + + + i + j + k + l + + + m + n + o + p + + + ] + + + +

    变成:

    + + + + [ + + + a + b + c + v.x + + + e + f + g + v.y + + + i + j + k + v.z + + + m + n + o + p + + + ] + + +

    [method:Array toArray]( [param:Array array], [param:Integer offset] )

    [page:Array array] - (可选参数) 存储矩阵元素的数组,如果未指定会创建一个新的数组。
    diff --git a/docs/api/zh/math/Plane.html b/docs/api/zh/math/Plane.html index 6c6b0f25feacc3..92e7ea701c1f81 100644 --- a/docs/api/zh/math/Plane.html +++ b/docs/api/zh/math/Plane.html @@ -28,7 +28,7 @@

    属性(Properties)

    [property:Boolean isPlane]

    - Read-only flag to check if a given object is of type [name]. + 只读属性,用于检查给定对象是否为[name]类型。

    [property:Vector3 normal]

    @@ -125,7 +125,7 @@

    [method:Vector3 projectPoint]( [param:Vector3 point], [param:Vector3 target]

    [method:this set]( [param:Vector3 normal], [param:Float constant] )

    [page:Vector3 normal] - 单位长度的向量表示平面的法向量。
    - [page:Float constant] - 原点到平面有符号距离。默认值为 *0*。

    + [page:Float constant] - 原点到平面有符号距离。

    设置平面 [page:.normal normal] 的法线和常量 [page:.constant constant] 属性值。

    diff --git a/docs/api/zh/math/Quaternion.html b/docs/api/zh/math/Quaternion.html index 26077d92c18f3a..ec1519f5f3d210 100644 --- a/docs/api/zh/math/Quaternion.html +++ b/docs/api/zh/math/Quaternion.html @@ -18,6 +18,10 @@

    四元数([name])

    对 [name] 实例进行遍历将按相应的顺序生成它的分量 (x, y, z, w)。

    +

    + 请注意,three.js 期望四元数被归一化。 +

    +

    代码示例

    @@ -45,7 +49,7 @@

    属性

    [property:Boolean isQuaternion]

    - Read-only flag to check if a given object is of type [name]. + 只读属性,用于检查给定对象是否为[name]类型。

    [property:Float x]

    diff --git a/docs/api/zh/math/Sphere.html b/docs/api/zh/math/Sphere.html index 36934895b0af49..692e586df5a58f 100644 --- a/docs/api/zh/math/Sphere.html +++ b/docs/api/zh/math/Sphere.html @@ -28,6 +28,11 @@

    属性

    [property:Vector3 center]

    A [page:Vector3]定义了球心的位置,默认值位于(0, 0, 0)。

    +

    [property:Boolean isSphere]

    +

    + 只读属性,用于检查给定对象是否为[name]类型。 +

    +

    [property:Float radius]

    球的半径,默认值为-1。

    diff --git a/docs/api/zh/math/Vector2.html b/docs/api/zh/math/Vector2.html index 9423dfc0937d76..bf44aed86d718e 100644 --- a/docs/api/zh/math/Vector2.html +++ b/docs/api/zh/math/Vector2.html @@ -67,7 +67,7 @@

    [property:Float height]

    [property:Boolean isVector2]

    - Read-only flag to check if a given object is of type [name]. + 只读属性,用于检查给定对象是否为[name]类型。

    [property:Float width]

    diff --git a/docs/api/zh/math/Vector3.html b/docs/api/zh/math/Vector3.html index 7ed6f8c781c7d9..d5819464244ac0 100644 --- a/docs/api/zh/math/Vector3.html +++ b/docs/api/zh/math/Vector3.html @@ -66,7 +66,7 @@

    属性

    [property:Boolean isVector3]

    - Read-only flag to check if a given object is of type [name]. + 只读属性,用于检查给定对象是否为[name]类型。

    [property:Float x]

    diff --git a/docs/api/zh/math/Vector4.html b/docs/api/zh/math/Vector4.html index 84bcf7975ac45f..e1317e6dd3cd12 100644 --- a/docs/api/zh/math/Vector4.html +++ b/docs/api/zh/math/Vector4.html @@ -66,7 +66,7 @@

    属性

    [property:Boolean isVector4]

    - Read-only flag to check if a given object is of type [name]. + 只读属性,用于检查给定对象是否为[name]类型。

    [property:Float x]

    diff --git a/docs/api/zh/objects/BatchedMesh.html b/docs/api/zh/objects/BatchedMesh.html new file mode 100644 index 00000000000000..78484205997187 --- /dev/null +++ b/docs/api/zh/objects/BatchedMesh.html @@ -0,0 +1,225 @@ + + + + + + + + + + + + [page:Mesh] → + +

    批处理网格([name])

    + +

    + [page:Mesh] 的特殊版本,支持多绘制批量渲染。如果您必须渲染大量具有相同材质但具有不同世界变换和几何形状的对象,请使用 [name]。使用 [name] 将帮助您减少绘制调用的数量,从而提高应用程序的整体渲染性能。 +
    +
    + + 如果不支持 [link:https://developer.mozilla.org/en-US/docs/Web/API/WEBGL_multi_draw WEBGL_multi_draw extension] + ,则使用性能较低的回调。 +

    + +

    代码示例

    + + + const box = new THREE.BoxGeometry( 1, 1, 1 ); + const sphere = new THREE.BoxGeometry( 1, 1, 1 ); + const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); + + // initialize and add geometries into the batched mesh + const batchedMesh = new BatchedMesh( 10, 5000, 10000, material ); + const boxId = batchedMesh.addGeometry( box ); + const sphereId = batchedMesh.addGeometry( sphere ); + + // position the geometries + batchedMesh.setMatrixAt( boxId, boxMatrix ); + batchedMesh.setMatrixAt( sphereId, sphereMatrix ); + + scene.add( batchedMesh ); + + +

    例子

    +

    + [example:webgl_mesh_batch WebGL / mesh / batch]
    +

    + +

    构造函数

    +

    + [name]( + [param:Integer maxInstanceCount], [param:Integer maxVertexCount], + [param:Integer maxIndexCount], [param:Material material], + ) +

    +

    + [page:Integer maxInstanceCount] - 计划添加的单个几何体的最大数量。
    + [page:Integer maxVertexCount] - 所有几何体使用的最大顶点数。
    + [page:Integer maxIndexCount] - 所有几何图形使用的最大索引数。
    + [page:Material material] - [page:Material] 的一个实例。默认是新的 [page:MeshBasicMaterial]。
    +

    + +

    属性

    +

    有关常见属性,请参阅 [page:Mesh] 基类

    + +

    [property:Box3 boundingBox]

    +

    + 该边界框包围了 [name] 的所有实例。可以用 [page:.computeBoundingBox]() 进行计算。默认为 `null`。 +

    + +

    [property:Sphere boundingSphere]

    +

    + 该边界球包围了 [name] 的所有实例。可以用 [page:.computeBoundingSphere]() 进行计算。默认为 `null`。 +

    + +

    [property:Boolean perObjectFrustumCulled]

    +

    + 如果为 true,则 [name] 内的各个对象将被视锥体剔除。默认为 `true`。 +

    + +

    [property:Boolean sortObjects]

    +

    + 如果为 true,则对 [name] 中的各个对象进行排序以改善与过度绘制相关的工件。如果材质被标记为“透明”,则对象将从后到前渲染,如果没有,则它们从前到后渲染。默认为 `true`。 +

    + +

    [property:Integer maxInstanceCount]

    +

    + 只读,[name] 中可以存储的单个几何体的最大数量。 +

    + +

    [property:Boolean isBatchedMesh]

    +

    用于检查给定对象是否属于 [name] 类型的只读标志。

    + +

    Methods

    +

    有关常用方法,请参阅 [page:Mesh] 基类。

    + +

    [method:undefined computeBoundingBox]()

    +

    + 计算边界框,更新 [page:.boundingBox] 属性。
    + 默认情况下不计算边界框。它们需要显式计算,否则就是 `null`。 +

    + +

    [method:undefined computeBoundingSphere]()

    +

    + 计算边界球,更新 [page:.boundingSphere] 属性。
    + 默认情况下不计算边界球。它们需要显式计算,否则就是 `null`。 +

    + +

    [method:undefined dispose]()

    +

    + 释放该实例分配的GPU相关资源。每当您的应用程序中不再使用此实例时,请调用此方法。 +

    + +

    [method:this setCustomSort]( [param:Function sortFunction] )

    +

    + 对渲染之前运行的函数进行排序。该函数需要一个要排序的项目列表和一个相机。列表中的对象包含一个“z”字段,用于执行深度排序。 +

    + +

    + [method:Matrix4 getMatrixAt]( [param:Integer index], [param:Matrix4 matrix] ) +

    +

    + [page:Integer index]: 实例的索引。值必须在 [0, count] 范围内。 +

    +

    + [page:Matrix4 matrix]: 这个 4x4 矩阵将被设置为定义实例的局部变换矩阵。 +

    +

    获取定义实例的局部变换矩阵。

    + +

    + [method:Boolean getVisibleAt]( [param:Integer index] ) +

    +

    + [page:Integer index]: 实例的索引。值必须在 [0, count] 范围内。 +

    +

    获取给定实例是否标记为“可见”。

    + +

    + [method:this setMatrixAt]( [param:Integer index], [param:Matrix4 matrix] ) +

    +

    + [page:Integer index]: 实例的索引。值必须在 [0, count] 范围内。 +

    +

    + [page:Matrix4 matrix]: 表示单个实例的局部变换的 4x4 矩阵。 +

    +

    + 将给定的局部变换矩阵设置为定义的实例。 +

    + +

    + [method:this setVisibleAt]( [param:Integer index], [param:Boolean visible] ) +

    +

    + [page:Integer index]: 实例的索引。值必须在 [0, count] 范围内。 +

    +

    + [page:Boolean visible]: 指示可见性状态的布尔值。 +

    +

    + 设置给定索引处对象的可见性。 +

    + +

    + [method:Integer addGeometry]( [param:BufferGeometry geometry], [param:Integer reservedVertexRange], [param:Integer + reservedIndexRange] ) +

    +

    + [page:BufferGeometry geometry]: 要添加到 [name] 中的几何体。 +

    +

    + [page:Integer reservedVertexRange]: 可选参数,指定为添加的几何体保留的顶点缓冲区空间量。如果计划稍后在此索引处设置大于原始几何图形的新几何图形,则这是必要的。默认为给定几何顶点缓冲区的长度。 +

    +

    + [page:Integer reservedIndexRange]: 可选参数,指定为添加的几何体保留的索引缓冲区空间量。如果计划稍后在此索引处设置大于原始几何图形的新几何图形,则这是必要的。默认为给定几何索引缓冲区的长度。 +

    +

    + 将给定几何体添加到 [name] 并返回引用它的关联索引。 +

    + +

    + [method:Integer setGeometryAt]( [param:Integer index], [param:BufferGeometry geometry] ) +

    +

    + [page:Integer index]: 用该几何图形替换哪个几何图形索引。 +

    +

    + [page:BufferGeometry geometry]: 在给定几何索引处替换的几何。 +

    +

    + 用提供的几何图形替换 `index` 的几何图形。如果索引处没有为几何体保留足够的空间,则会引发错误。 +

    + +

    + [method:Integer getInstanceCountAt]( [param:Integer index] ) +

    +

    + [page:Integer index]: The index of an instance. Values have to be in the + range [0, count]. +

    +

    + Gets the instance count of the geometry at `index`. Returns `null` if instance counts are not configured. +

    + +

    + [method:Integer setInstanceCountAt]( [param:Integer index], [param:Integer instanceCount ] ) +

    +

    + [page:Integer index]: Which geometry index to configure an instance count for. +

    +

    + [page:Integer instanceCount]: The number of instances to render of the given geometry index. +

    +

    + Sets an instance count of the geometry at `index`. +

    + +

    源代码

    + +

    + [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

    + + + diff --git a/docs/api/zh/objects/Bone.html b/docs/api/zh/objects/Bone.html index 9a1ce1d2f1c99f..863bc223e4b8e1 100644 --- a/docs/api/zh/objects/Bone.html +++ b/docs/api/zh/objects/Bone.html @@ -38,7 +38,7 @@

    属性

    [property:Boolean isBone]

    - Read-only flag to check if a given object is of type [name]. + 只读属性,用于检查给定对象是否为[name]类型。

    diff --git a/docs/api/zh/objects/Group.html b/docs/api/zh/objects/Group.html index bbaee720960527..2a35f311e2c37a 100644 --- a/docs/api/zh/objects/Group.html +++ b/docs/api/zh/objects/Group.html @@ -46,7 +46,7 @@

    属性

    [property:Boolean isGroup]

    - Read-only flag to check if a given object is of type [name]. + 只读属性,用于检查给定对象是否为[name]类型。

    [property:String type]

    diff --git a/docs/api/zh/objects/InstancedMesh.html b/docs/api/zh/objects/InstancedMesh.html index 83ea9ce4fae6aa..46d1fd31700c7d 100644 --- a/docs/api/zh/objects/InstancedMesh.html +++ b/docs/api/zh/objects/InstancedMesh.html @@ -34,7 +34,7 @@

    [name]( [param:BufferGeometry geometry], [param:Material material], [param:I

    属性

    -

    See the base [page:Mesh] class for common properties.

    +

    公共属性请查看基类 [page:Mesh]。

    [property:Box3 boundingBox]

    @@ -67,14 +67,19 @@

    [property:InstancedBufferAttribute instanceMatrix]

    如果你要通过 [page:.setMatrixAt]() 来修改实例数据,你必须将它的 [page:BufferAttribute.needsUpdate needsUpdate] 标识为 true 。

    +

    [property:DataTexture morphTexture]

    +

    + 用于表示所有实例的变形权重。如果你通过 [page:.setMorphAt]() 修改了实例数据,你必须将 [page:Texture.needsUpdate needsUpdate] 标识设置为 true。 +

    +

    [property:Boolean isInstancedMesh]

    - Read-only flag to check if a given object is of type [name]. + 用来检查对象是否属于 [name] 类型的只读标识。

    方法

    -

    See the base [page:Mesh] class for common methods.

    +

    公共方法请查看基类 [page:Mesh]。

    [method:undefined computeBoundingBox]()

    @@ -90,18 +95,18 @@

    [method:undefined computeBoundingSphere]()

    [method:undefined dispose]()

    - Frees the internal resources of this instance. + 释放实例的内部资源。

    [method:undefined getColorAt]( [param:Integer index], [param:Color color] )

    - [page:Integer index]: The index of an instance. Values have to be in the range [0, count]. + [page:Integer index]: 实例的索引。 值必须在 [0, count] 区间。

    - [page:Color color]: This color object will be set to the color of the defined instance. + [page:Color color]: 传入的颜色对象将会被设置为指定的实例的颜色。

    - Get the color of the defined instance. + 获取已定义实例的颜色。

    [method:undefined getMatrixAt]( [param:Integer index], [param:Matrix4 matrix] )

    @@ -115,16 +120,26 @@

    [method:undefined getMatrixAt]( [param:Integer index], [param:Matrix4 matrix 获得已定义实例的本地变换矩阵。

    +

    + [method:undefined getMorphAt]( [param:Integer index], [param:Mesh mesh] ) +

    +

    + [page:Integer index]: 实例的索引。值必须在 [0, count] 区间。 +

    +

    + [page:Mesh mesh]: 网格属性 [page:Mesh.morphTargetInfluences .morphTargetInfluences] 将会被填充为已定义实例的变形权重。 +

    +

    获取已定义实例的变形权重

    +

    [method:undefined setColorAt]( [param:Integer index], [param:Color color] )

    - [page:Integer index]: The index of an instance. Values have to be in the range [0, count]. + [page:Integer index]: 实例的索引。值必须在 [0, count] 区间。

    - [page:Color color]: The color of a single instance. + [page:Color color]: 单个实例的颜色。

    - Sets the given color to the defined instance. - Make sure you set [page:.instanceColor][page:BufferAttribute.needsUpdate .needsUpdate] to true after updating all the colors. + 设置已定义实例的颜色。请确保在更新颜色后将 [page:.instanceColor][page:BufferAttribute.needsUpdate .needsUpdate] 标识设置为 true。

    [method:undefined setMatrixAt]( [param:Integer index], [param:Matrix4 matrix] )

    @@ -139,6 +154,19 @@

    [method:undefined setMatrixAt]( [param:Integer index], [param:Matrix4 matrix 请确保在更新所有矩阵后将 [page:.instanceMatrix][page:BufferAttribute.needsUpdate .needsUpdate] 设置为true。

    +

    + [method:undefined setMorphAt]( [param:Integer index], [param:Mesh mesh] ) +

    +

    + [page:Integer index]: 实例的索引。值必须在 [0, count] 区间。 +

    +

    + [page:Mesh mesh]: 网格属性 [page:Mesh.morphTargetInfluences .morphTargetInfluences] 包含了单个实例的变形权重。 +

    +

    + 设置已定义实例的变形权重。请确保在更新所有变形数据后将 [page:.morphTexture][page:Texture.needsUpdate .needsUpdate] 设置为 true。 +

    +

    源代码

    diff --git a/docs/api/zh/objects/LOD.html b/docs/api/zh/objects/LOD.html index ade60bffbf559b..c97be3e4902d5b 100644 --- a/docs/api/zh/objects/LOD.html +++ b/docs/api/zh/objects/LOD.html @@ -25,7 +25,7 @@

    代码示例

    //Create spheres with 3 levels of detail and create new LOD levels for them for( let i = 0; i < 3; i++ ) { - const geometry = new THREE.IcosahedronGeometry( 10, 3 - i ) + const geometry = new THREE.IcosahedronGeometry( 10, 3 - i ); const mesh = new THREE.Mesh( geometry, material ); @@ -61,7 +61,7 @@

    [property:Boolean autoUpdate]

    [property:Boolean isLOD]

    - Read-only flag to check if a given object is of type [name]. + 只读属性,用于检查给定对象是否为[name]类型。

    [property:Array levels]

    @@ -86,11 +86,6 @@

    [method:this addLevel]( [param:Object3D object], [param:Float distance], [pa 添加在一定距离和更大范围内显示的网格。通常来说,距离越远,网格中的细节就越少。

    -

    [method:LOD clone]()

    -

    - 返回一个LOD对象及其所关联的在特定距离中的物体。 -

    -

    [method:Integer getCurrentLevel]()

    Get the currently active LOD level. As index of the levels array. diff --git a/docs/api/zh/objects/Line.html b/docs/api/zh/objects/Line.html index a8825a21a9ef02..e18e674a610abf 100644 --- a/docs/api/zh/objects/Line.html +++ b/docs/api/zh/objects/Line.html @@ -55,7 +55,7 @@

    [property:BufferGeometry geometry]

    [property:Boolean isLine]

    - Read-only flag to check if a given object is of type [name]. + 只读属性,用于检查给定对象是否为[name]类型。

    [property:Material material]

    @@ -88,11 +88,6 @@

    [method:undefined raycast]( [param:Raycaster raycaster], [param:Array inters [page:Raycaster.intersectObject]将会调用这个方法。

    -

    [method:Line clone]()

    -

    - 返回这条线及其子集的一个克隆对象。 -

    -

    [method:undefined updateMorphTargets]()

    Updates the morphTargets to have no influence on the object. Resets the diff --git a/docs/api/zh/objects/LineLoop.html b/docs/api/zh/objects/LineLoop.html index f1eb0310fa4026..405e4d7cc15954 100644 --- a/docs/api/zh/objects/LineLoop.html +++ b/docs/api/zh/objects/LineLoop.html @@ -33,7 +33,7 @@

    属性

    [property:Boolean isLineLoop]

    - Read-only flag to check if a given object is of type [name]. + 只读属性,用于检查给定对象是否为[name]类型。

    diff --git a/docs/api/zh/objects/LineSegments.html b/docs/api/zh/objects/LineSegments.html index 6bda6965f22808..5096a31395a01b 100644 --- a/docs/api/zh/objects/LineSegments.html +++ b/docs/api/zh/objects/LineSegments.html @@ -32,7 +32,7 @@

    属性

    [property:Boolean isLineSegments]

    - Read-only flag to check if a given object is of type [name]. + 只读属性,用于检查给定对象是否为[name]类型。

    diff --git a/docs/api/zh/objects/Mesh.html b/docs/api/zh/objects/Mesh.html index ad7ff5942545df..89faa8d2b86600 100644 --- a/docs/api/zh/objects/Mesh.html +++ b/docs/api/zh/objects/Mesh.html @@ -46,7 +46,7 @@

    [property:BufferGeometry geometry]

    [property:Boolean isMesh]

    - Read-only flag to check if a given object is of type [name]. + 只读属性,用于检查给定对象是否为[name]类型。

    [property:Material material]

    @@ -70,8 +70,12 @@

    [property:Object morphTargetDictionary]

    方法

    共有方法请参见其基类[page:Object3D]。

    -

    [method:Mesh clone]()

    -

    返回这个[name]对象及其子级的克隆。

    +

    + [method:Vector3 getVertexPosition]( [param:Integer index], [param:Vector3 target] ) +

    +

    + 获取给定索引处顶点的局部空间位置,同时考虑变形目标和蒙皮的当前动画状态。 +

    [method:undefined raycast]( [param:Raycaster raycaster], [param:Array intersects] )

    diff --git a/docs/api/zh/objects/Points.html b/docs/api/zh/objects/Points.html index 83c38b05804cca..bd10c290227399 100644 --- a/docs/api/zh/objects/Points.html +++ b/docs/api/zh/objects/Points.html @@ -40,7 +40,7 @@

    [property:BufferGeometry geometry]

    [property:Boolean isPoints]

    - Read-only flag to check if a given object is of type [name]. + 只读属性,用于检查给定对象是否为[name]类型。

    [property:Material material]

    @@ -69,11 +69,6 @@

    [method:undefined raycast]( [param:Raycaster raycaster], [param:Array inters [page:Raycaster.intersectObject]将会调用这个方法。

    -

    [method:Points clone]()

    -

    - 返回这个点及其子集的一个克隆对象。 -

    -

    [method:undefined updateMorphTargets]()

    更新morphTargets,使其不对对象产生影响,重置[page:Points.morphTargetInfluences morphTargetInfluences] 和 diff --git a/docs/api/zh/objects/Skeleton.html b/docs/api/zh/objects/Skeleton.html index 06b0f8b21038a9..f4c37d3a4008f3 100644 --- a/docs/api/zh/objects/Skeleton.html +++ b/docs/api/zh/objects/Skeleton.html @@ -76,11 +76,6 @@

    [property:DataTexture boneTexture]

    当使用顶点纹理时,[page:DataTexture]保存着骨骼数据。

    -

    [property:Integer boneTextureSize]

    -

    - The size of the [page:.boneTexture]. -

    -

    方法

    [method:Skeleton clone]()

    diff --git a/docs/api/zh/objects/SkinnedMesh.html b/docs/api/zh/objects/SkinnedMesh.html index f5e0f71937f4f7..e140c900d8c269 100644 --- a/docs/api/zh/objects/SkinnedMesh.html +++ b/docs/api/zh/objects/SkinnedMesh.html @@ -12,9 +12,7 @@

    蒙皮网格([name])

    - 具有[page:Skeleton](骨架)和[page:Bone bones](骨骼)的网格,可用于给几何体上的顶点添加动画。

    - - [name] can only be used with WebGL 2. With WebGL 1 *OES_texture_float* and vertex textures support is required. + 具有[page:Skeleton](骨架)和[page:Bone bones](骨骼)的网格,可用于给几何体上的顶点添加动画。

    @@ -100,9 +98,10 @@

    属性

    [property:String bindMode]

    - “attached”(附加)或者“detached”(分离)。“attached”使用[page:SkinnedMesh.matrixWorld] - 属性作为对骨骼的基本变换矩阵,“detached”则使用[page:SkinnedMesh.bindMatrix]。 - 默认值是“attached”。 + Either `AttachedBindMode` or `DetachedBindMode`. `AttachedBindMode` means the skinned mesh + shares the same world space as the skeleton. This is not true when using `DetachedBindMode` + which is useful when sharing a skeleton across multiple skinned meshes. + Default is `AttachedBindMode`.

    [property:Matrix4 bindMatrix]

    @@ -127,7 +126,7 @@

    [property:Sphere boundingSphere]

    [property:Boolean isSkinnedMesh]

    - Read-only flag to check if a given object is of type [name]. + 只读属性,用于检查给定对象是否为[name]类型。

    [property:Skeleton skeleton]

    diff --git a/docs/api/zh/objects/Sprite.html b/docs/api/zh/objects/Sprite.html index e311ae673cb619..3a2ca6ec81ac20 100644 --- a/docs/api/zh/objects/Sprite.html +++ b/docs/api/zh/objects/Sprite.html @@ -42,7 +42,7 @@

    属性

    [property:Boolean isSprite]

    - Read-only flag to check if a given object is of type [name]. + 只读属性,用于检查给定对象是否为[name]类型。

    @@ -61,11 +61,6 @@

    [property:Vector2 center]

    方法

    共有方法请参见其基类[page:Object3D]。

    -

    [method:Sprite clone]()

    -

    - 返回当前Sprite对象的一个克隆及其任何后代。 -

    -

    [method:this copy]( [param:Sprite sprite] )

    将前一个Sprite对象的属性复制给当前的这个对象。 diff --git a/docs/api/zh/renderers/WebGL1Renderer.html b/docs/api/zh/renderers/WebGL1Renderer.html deleted file mode 100644 index 205808bbceca0a..00000000000000 --- a/docs/api/zh/renderers/WebGL1Renderer.html +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - [page:WebGLRenderer] → - -

    [name]

    - -

    - 自r118起,[page:WebGLRenderer]会自动使用 WebGL 2 渲染上下文。当升级一个已存在的项目到 => r118 , - 应用程序可能会因为下列两种原因而损坏: - -

      -
    • 自定义着色器代码必须符合GLSL 3.0。
    • -
    • WebGL 1扩展检查到被更改
    • -
    - - 如果你不能够花时间来升级你的代码,但仍然想要使用最新版本,那你就可以使用[name]。 - 这一版本的渲染器会强制使用 WebGL 1 渲染上下文。 -

    - -

    构造函数

    - -

    [name]( [param:Object parameters] )

    -

    - Creates a new [name]. -

    - -

    属性

    -

    See the base [page:WebGLRenderer] class for common properties.

    - -

    [property:Boolean isWebGL1Renderer]

    -

    - Read-only flag to check if a given object is of type [name]. -

    - - -

    方法

    -

    See the base [page:WebGLRenderer] class for common methods.

    - -

    源码

    - -

    - [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] -

    - - diff --git a/docs/api/zh/renderers/WebGL3DRenderTarget.html b/docs/api/zh/renderers/WebGL3DRenderTarget.html new file mode 100644 index 00000000000000..643a4d6ebfa0c2 --- /dev/null +++ b/docs/api/zh/renderers/WebGL3DRenderTarget.html @@ -0,0 +1,49 @@ + + + + + + + + + + [page:WebGLRenderTarget] → + +

    [name]

    + +

    表示三维的渲染目标。

    + +

    构造函数

    + +

    + [name]( [param:Number width], [param:Number height], [param:Number depth], [param:Object options] ) +

    +

    + [page:Number width] - 渲染目标的宽度,单位为像素。默认值为`1`。
    + [page:Number height] - 渲染目标的高度,单位为像素。默认值为`1`。
    + [page:Number depth] - 渲染目标的深度。 默认值为`1`。
    + [page:Object options] - (可选)一个保存着自动生成的目标纹理的纹理参数以及表示是否使用深度缓存/模板缓存的布尔值的对象。 + 有关纹理参数的说明,请参阅[page:Texture Texture]. See [page:WebGLRenderTarget] for details.

    + 创建新的[name]. +

    + +

    属性

    +

    继承属性请参考[page:WebGLRenderTarget]。

    + +

    [property:number depth]

    +

    渲染目标的深度。

    + +

    [property:Data3DTexture texture]

    +

    + [page:Data3DTexture]实例覆盖纹理属性。 +

    + +

    方法

    +

    继承方法请参考[page:WebGLRenderTarget]。

    + +

    源码

    +

    + [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

    + + diff --git a/docs/api/zh/renderers/WebGLArrayRenderTarget.html b/docs/api/zh/renderers/WebGLArrayRenderTarget.html new file mode 100644 index 00000000000000..c918fde6aece4f --- /dev/null +++ b/docs/api/zh/renderers/WebGLArrayRenderTarget.html @@ -0,0 +1,59 @@ + + + + + + + + + + [page:WebGLRenderTarget] → + +

    [name]

    + +

    + 该类型的渲染目标表示一组纹理。 +

    + +

    示例

    + +

    + [example:webgl_rendertarget_texture2darray WebGL / render target / array]
    +

    + +

    构造函数

    + +

    + [name]( [param:Number width], [param:Number height], [param:Number depth], [param:Object options] ) +

    +

    + [page:Number width] - 渲染目标的宽度,单位为像素。默认值为`1`。
    + [page:Number height] - 渲染目标的高度,单位为像素。默认值为`1`。
    + [page:Number depth] - 渲染目标的深度或图层数。 默认值为`1`。
    + [page:Object options] - (可选)一个保存着自动生成的目标纹理的纹理参数以及表示是否使用深度缓存/模板缓存的布尔值的对象。 + 有关纹理参数的说明,请参阅[page:Texture Texture]. See [page:WebGLRenderTarget] for details.

    + + 创建新的[name]. +

    + +

    属性

    + +

    继承属性请参考[page:WebGLRenderTarget]

    + +

    [property:number depth]

    +

    渲染目标的深度。

    + +

    [property:DataArrayTexture texture]

    +

    + [page:DataArrayTexture]实例覆盖纹理属性。 +

    + +

    方法

    +

    继承方法请参考[page:WebGLRenderTarget]

    + +

    源码

    +

    + [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] +

    + + diff --git a/docs/api/zh/renderers/WebGLCubeRenderTarget.html b/docs/api/zh/renderers/WebGLCubeRenderTarget.html index a61a840bb8d09c..ac935928f1f440 100644 --- a/docs/api/zh/renderers/WebGLCubeRenderTarget.html +++ b/docs/api/zh/renderers/WebGLCubeRenderTarget.html @@ -46,12 +46,12 @@

    [name]([param:Number size], [param:Object options])

    属性

    -

    继承属性,请参阅[page:WebGLRenderTarget]

    +

    继承属性,请参阅[page:WebGLRenderTarget]。

    方法

    -

    继承方法,请参阅[page:WebGLRenderTarget]

    +

    继承方法,请参阅[page:WebGLRenderTarget]。

    [method:this fromEquirectangularTexture]( [param:WebGLRenderer renderer], [param:Texture texture] )

    diff --git a/docs/api/zh/renderers/WebGLMultipleRenderTargets.html b/docs/api/zh/renderers/WebGLMultipleRenderTargets.html deleted file mode 100644 index a266b8bba9fcf5..00000000000000 --- a/docs/api/zh/renderers/WebGLMultipleRenderTargets.html +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - - - - [page:WebGLRenderTarget] → - -

    [name]

    - -

    - 一个特殊的渲染目标,使片段着色器能够写入多个纹理。这种方法对于高级渲染技术很有用,例如后处理或延迟渲染。 - - 注意:[name]只能与 WebGL 2 渲染上下文一起使用。 -

    - -

    例子(Examples)

    - -

    - [example:webgl2_multiple_rendertargets webgl2 / multiple / rendertargets ] -

    - -

    构造器(Constructor)

    - - -

    [name]([param:Number width], [param:Number height], [param:Number count])

    - -

    - [page:Number width] - 渲染目标的宽度。默认为`1`。
    - [page:Number height] - 渲染目标的高度。默认为`1`。
    - [page:Number count] - 渲染目标的数量。默认为`1`。 -

    - -

    特性(Properties)

    - -

    [property:Boolean isWebGLMultipleRenderTargets]

    -

    - 只读标志,用于检查给定对象是否属于[name]类型。 -

    - -

    [property:Array texture]

    -

    - 纹理属性在[name]中被覆盖并替换为数组。该数组包含各个渲染目标的[page:WebGLRenderTarget.texture 纹理]引用。 -

    - -

    [page:WebGLRenderTarget WebGLRenderTarget]属性在此类上可用。

    - -

    方法(Methods)

    - -

    [page:WebGLRenderTarget WebGLRenderTarget]方法在此类上可用。

    - -

    源代码(Source)

    - -

    - [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] -

    - - diff --git a/docs/api/zh/renderers/WebGLRenderTarget.html b/docs/api/zh/renderers/WebGLRenderTarget.html index 212aa3fb73b021..229e796922ed25 100644 --- a/docs/api/zh/renderers/WebGLRenderTarget.html +++ b/docs/api/zh/renderers/WebGLRenderTarget.html @@ -36,9 +36,13 @@

    [name]([param:Number width], [param:Number height], [param:Object options])< [page:Constant type] - 默认是[page:Textures UnsignedByteType].
    [page:Number anisotropy] - 默认是`1`. 参见[page:Texture.anisotropy]
    [page:Constant colorSpace] - 默认是[page:Textures NoColorSpace].
    + [page:String internalFormat] - 默认是 `null`.
    [page:Boolean depthBuffer] - 默认是`true`.
    [page:Boolean stencilBuffer] - 默认是`false`.
    - [page:Number samples] - 默认是`0`.

    + [page:Boolean resolveDepthBuffer] - 默认是`true`.
    + [page:Boolean resolveStencilBuffer] - 默认是`true`.
    + [page:Number samples] - 默认是`0`.
    + [page:Number count] - default is `1`.

    创建一个新[name]

    @@ -47,7 +51,7 @@

    属性

    [property:Boolean isWebGLRenderTarget]

    - Read-only flag to check if a given object is of type [name]. + 只读属性,用于检查给定对象是否为[name]类型。

    [property:number width]

    @@ -80,6 +84,12 @@

    [property:Texture texture]

    纹理实例保存这渲染的像素,用作进一步处理的输入值

    +

    [property:Texture textures]

    +

    + 使用数组来存放[page:WebGLRenderTarget.texture texture] 的引用 + 来配置的多个渲染目标 +

    +

    [property:Boolean depthBuffer]

    渲染到深度缓冲区。默认true. @@ -87,7 +97,20 @@

    [property:Boolean depthBuffer]

    [property:Boolean stencilBuffer]

    - 渲染到模板缓冲区。默认为false + 渲染到模板缓冲区。默认为false. +

    + +

    [property:Boolean resolveDepthBuffer]

    +

    + 设置在渲染到多采样渲染目标时是否解析深度缓冲区 + 默认为`true`. +

    + +

    [property:Boolean resolveStencilBuffer]

    +

    + 设置在渲染到多采样渲染目标时是否解析模板缓冲区 + 这个属性在[page:.resolveDepthBuffer]设置为`false`时无效 + 默认为`true`.

    [property:DepthTexture depthTexture]

    @@ -97,7 +120,7 @@

    [property:DepthTexture depthTexture]

    [property:Number samples]

    - Defines the count of MSAA samples. Can only be used with WebGL 2. Default is *0*. + 设置渲染目标的MSAA采样值。默认是0.

    方法

    diff --git a/docs/api/zh/renderers/WebGLRenderer.html b/docs/api/zh/renderers/WebGLRenderer.html index 263e861f436fea..99a08f61458918 100644 --- a/docs/api/zh/renderers/WebGLRenderer.html +++ b/docs/api/zh/renderers/WebGLRenderer.html @@ -38,7 +38,7 @@

    [name]( [param:Object parameters] )

    [page:Boolean antialias] - 是否执行抗锯齿。默认为*false*.
    - [page:Boolean stencil] - 绘图缓存是否有一个至少8位的模板缓存([link:https://en.wikipedia.org/wiki/Stencil_buffer stencil buffer])。默认为*true*
    + [page:Boolean stencil] - 绘图缓存是否有一个至少8位的模板缓存([link:https://en.wikipedia.org/wiki/Stencil_buffer stencil buffer])。默认为*false*
    [page:Boolean preserveDrawingBuffer] -是否保留缓直到手动清除或被覆盖。 默认*false*.
    @@ -141,6 +141,7 @@

    [property:Object extensions]

    - [page:Object get]( [param:String extensionName] ): 用于检查是否支持各种扩展,并返回一个对象,其中包含扩展的详细信息。 该方法检查以下扩展:
    +

    • *WEBGL_depth_texture*
    • @@ -148,7 +149,11 @@

      [property:Object extensions]

    • *WEBGL_compressed_texture_s3tc*
    • *WEBGL_compressed_texture_pvrtc*
    • *WEBGL_compressed_texture_etc1*
    • +
    • 查看更多:Extension list
    + +

    + - [page:Boolean has]( [param:String extensionName] ): 如果支持该扩展则返回 `true`。

    [property:string outputColorSpace]

    @@ -192,11 +197,6 @@

    [property:Object info]

    [property:Boolean localClippingEnabled]

    定义渲染器是否考虑对象级剪切平面。 默认为*false*.

    -

    [property:Boolean useLegacyLights]

    -

    - Whether to use the legacy lighting mode or not. 默认是*true*。 -

    -

    [property:Object properties]

    渲染器内部使用,以跟踪各种子对象属性。 @@ -273,14 +273,33 @@

    [method:undefined clearDepth]( )

    [method:undefined clearStencil]( )

    清除模板缓存。相当于调用[page:WebGLRenderer.clear .clear]( false, false, true )

    -

    [method:undefined compile]( [param:Object3D scene], [param:Camera camera] )

    -

    使用相机编译场景中的所有材质。这对于在首次渲染之前预编译着色器很有用。

    +

    [method:Set compile]( [param:Object3D scene], [param:Camera camera], [param:Scene targetScene] )

    +

    + 使用相机编译场景中的所有材质。这对于在首次渲染之前预编译着色器很有用。 + 如果要将 3D 对象添加到现有场景,请使用第三个可选参数来应用目标场景。
    + 请注意,在调用此方法之前应配置场景的照明。 +

    -

    [method:undefined copyFramebufferToTexture]( [param:Vector2 position], [param:FramebufferTexture texture], [param:Number level] )

    +

    [method:Promise compileAsync]( [param:Object3D scene], [param:Camera camera], [param:Scene targetScene] )

    +

    + [page:WebGLRenderer.compile .compile]() 的异步版本。该方法返回一个 Promise。它解决了何时可以渲染给定的 3D 对象或场景,而不会因着色器编译而出现不必要的停顿。

    + 此方法利用 *KHR_parallel_shader_compile*。 +

    + +

    [method:undefined copyFramebufferToTexture]( [param:FramebufferTexture texture], [param:Vector2 position], [param:Number level] )

    将当前WebGLFramebuffer中的像素复制到2D纹理中。可访问[link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/copyTexImage2D WebGLRenderingContext.copyTexImage2D].

    -

    [method:undefined copyTextureToTexture]( [param:Vector2 position], [param:Texture srcTexture], [param:Texture dstTexture], [param:Number level] )

    -

    将纹理的所有像素复制到一个已有的从给定位置开始的纹理中。可访问[link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/texSubImage2D WebGLRenderingContext.texSubImage2D].

    +

    [method:undefined copyTextureToTexture]( [param:Texture srcTexture], [param:Texture dstTexture], [param:Box2 srcRegion], [param:Vector2 dstPosition], [param:Number level] )

    +

    + Copies the pixels of a texture in the bounds '[page:Box2 srcRegion]' in the destination texture starting from the given position. + Enables access to [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/texSubImage2D WebGLRenderingContext.texSubImage2D]. +

    + +

    [method:undefined copyTextureToTexture3D]( [param:Texture srcTexture], [param:Texture dstTexture], [param:Box3 srcRegion], [param:Vector3 dstPosition], [param:Number level] )

    +

    + Copies the pixels of a texture in the bounds '[page:Box3 srcRegion]' in the destination texture starting from the given position. + Enables access to [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext/texSubImage3D WebGL2RenderingContext.texSubImage3D]. +

    [method:undefined dispose]( )

    处理当前的渲染环境

    @@ -346,11 +365,11 @@

    [method:undefined render]( [param:Object3D scene], [param:Camera camera] ) 用相机([page:Camera camera])渲染一个场景([page:Scene scene])或是其它类型的[page:Object3D object]。
    - 渲染一般是在canvas上完成的,或者是[page:WebGLRenderTarget renderTarget](如果有指定)
    - - 如果[page:Boolean forceClear]值是*true*,那么颜色、深度及模板缓存将会在渲染之前清除,即使渲染器的[page:WebGLRenderer.autoClear autoClear]属性值是false
    + 渲染一般是在canvas上完成的,或者是[page:WebGLRenderTarget renderTarget](通过[page:WebGLRenderer.setRenderTarget .setRenderTarget]指定)。
    - 即便forceClear设为true, 也可以通过将[page:WebGLRenderer.autoClearColor autoClearColor]、[page:WebGLRenderer.autoClearStencil autoClearStencil]或[page:WebGLRenderer.autoClearDepth autoClearDepth]属性的值设为false来阻止对应缓存被清除。 + 默认情况下渲染缓存是会被清除的,但是你可以通过设置[page:WebGLRenderer.autoClear autoClear] 属性的值为false来阻止渲染缓存被清除。 + 如果你想阻止某个指定的缓存被清空,可以设置[page:WebGLRenderer.autoClearColor autoClearColor]、[page:WebGLRenderer.autoClearStencil autoClearStencil]或[page:WebGLRenderer.autoClearDepth autoClearDepth]属性的值为false来阻止其被清除。 + 如果想要强制清除一个或多个缓存,可以调用[page:WebGLRenderer.clear .clear]。

    [method:undefined resetState]()

    diff --git a/docs/api/zh/renderers/webxr/WebXRManager.html b/docs/api/zh/renderers/webxr/WebXRManager.html index 36dd497401217d..6c13bf7a9e502d 100644 --- a/docs/api/zh/renderers/webxr/WebXRManager.html +++ b/docs/api/zh/renderers/webxr/WebXRManager.html @@ -74,11 +74,6 @@

    [method:Group getHand]( [param:Integer index] )

    返回代表XR控制器所谓的*手或关节*空间的[page:Group 组]。在不使用物理控制器时,使用此空间可视化用户的手。

    -

    [method:Set getPlanes]()

    -

    - 返回由 WebXR 的平面检测 API 检测到的平面集。 -

    -

    [method:String getReferenceSpace]()

    返回参考空间。 diff --git a/docs/api/zh/scenes/Fog.html b/docs/api/zh/scenes/Fog.html index 17a096ad8b9085..977d63387b6ed8 100644 --- a/docs/api/zh/scenes/Fog.html +++ b/docs/api/zh/scenes/Fog.html @@ -13,8 +13,9 @@

    雾([name])

    代码示例

    - const scene = new THREE.Scene(); - scene.fog = new THREE.Fog( 0xcccccc, 10, 15 ); + + const scene = new THREE.Scene(); + scene.fog = new THREE.Fog( 0xcccccc, 10, 15 );

    构造器

    @@ -28,7 +29,7 @@

    属性

    [property:Boolean isFog]

    - Read-only flag to check if a given object is of type [name]. + 只读属性,用于检查给定对象是否为[name]类型。

    [property:String name]

    diff --git a/docs/api/zh/scenes/FogExp2.html b/docs/api/zh/scenes/FogExp2.html index 63353f14083b61..895f5a5d9f432e 100644 --- a/docs/api/zh/scenes/FogExp2.html +++ b/docs/api/zh/scenes/FogExp2.html @@ -13,8 +13,9 @@

    雾-指数([name])

    代码示例

    - const scene = new THREE.Scene(); - scene.fog = new THREE.FogExp2( 0xcccccc, 0.002 ); + + const scene = new THREE.Scene(); + scene.fog = new THREE.FogExp2( 0xcccccc, 0.002 );

    构造器

    @@ -27,7 +28,7 @@

    属性

    [property:Boolean isFogExp2]

    - Read-only flag to check if a given object is of type [name]. + 只读属性,用于检查给定对象是否为[name]类型。

    [property:String name]

    diff --git a/docs/api/zh/scenes/Scene.html b/docs/api/zh/scenes/Scene.html index b98c97efa96d49..7b3b8aa54a7219 100644 --- a/docs/api/zh/scenes/Scene.html +++ b/docs/api/zh/scenes/Scene.html @@ -34,7 +34,17 @@

    [property:Object background]

    [property:Float backgroundBlurriness]

    - Sets the blurriness of the background. Only influences environment maps assigned to [page:Scene.background]. Valid input is a float between *0* and *1*. Default is *0*. + 设置背景的模糊度。仅影响分配给 [page:Scene.background] 的环境贴图。有效输入是介于 *0* 到 *1*。默认值为 *0*。 +

    + +

    [property:Float backgroundIntensity]

    +

    + 减弱背景色。仅适用于背景纹理。默认值为 `1`。 +

    + +

    [property:Euler backgroundRotation]

    +

    + 背景的旋转(以弧度为单位)。仅影响分配给 [page:Scene.background]。默认值为 `(0,0,0)`。

    [property:Texture environment]

    @@ -43,6 +53,16 @@

    [property:Texture environment]

    然而,该属性不能够覆盖已存在的、已分配给 [page:MeshStandardMaterial.envMap] 的贴图。默认为null。

    +

    [property:Float environmentIntensity]

    +

    + 减弱环境色。仅影响指定给 [page:Scene.environment]。默认值为 `1`。 +

    + +

    [property:Euler environmentRotation]

    +

    + 环境贴图的旋转(以弧度为单位)。仅在使用 [page:.environment] 时影响场景中的物理材质。默认值为 `(0,0,0)`。 +

    +

    [property:Fog fog]

    一个[page:Fog fog]实例定义了影响场景中的每个物体的雾的类型。默认值为null。 @@ -50,7 +70,7 @@

    [property:Fog fog]

    [property:Boolean isScene]

    - Read-only flag to check if a given object is of type [name]. + 只读标志用于检查给定的对象是否属于 [name] 类型。

    [property:Material overrideMaterial]

    diff --git a/docs/api/zh/textures/CanvasTexture.html b/docs/api/zh/textures/CanvasTexture.html index aa7fa7c44625d0..fa07f2112eb696 100644 --- a/docs/api/zh/textures/CanvasTexture.html +++ b/docs/api/zh/textures/CanvasTexture.html @@ -58,7 +58,7 @@

    属性

    [property:Boolean isCanvasTexture]

    - Read-only flag to check if a given object is of type [name]. + 只读属性,用于检查给定对象是否为[name]类型。

    [property:Boolean needsUpdate]

    diff --git a/docs/api/zh/textures/CompressedArrayTexture.html b/docs/api/zh/textures/CompressedArrayTexture.html index 8383f04f18c6e8..66a7a152084b21 100644 --- a/docs/api/zh/textures/CompressedArrayTexture.html +++ b/docs/api/zh/textures/CompressedArrayTexture.html @@ -1,5 +1,5 @@ - + @@ -12,61 +12,58 @@

    [name]

    - Creates an texture 2D array based on data in compressed form, for example from a [link:https://en.wikipedia.org/wiki/DirectDraw_Surface DDS] file.

    + 基于压缩形式的数据(例如从[link:https://en.wikipedia.org/wiki/DirectDraw_Surface DDS]文件)创建纹理2D数组

    - - For use with the [page:CompressedTextureLoader CompressedTextureLoader]. + 用于 [page:CompressedTextureLoader CompressedTextureLoader].

    -

    Constructor

    +

    构造函数

    [name]( [param:Array mipmaps], [param:Number width], [param:Number height], [param:Constant format], [param:Constant type] )

    - [page:Array mipmaps] -- The mipmaps array should contain objects with data, width and height. The mipmaps should be of the correct format and type.
    + [page:Array mipmaps] -- mipmaps数组应包含具有数据、宽度和高度的对象。mipmap 应具有正确的格式和类型。
    - [page:Number width] -- The width of the biggest mipmap.
    + [page:Number width] -- 最大 mipmap 的宽度
    - [page:Number height] -- The height of the biggest mipmap.
    + [page:Number height] -- 最大 mipmap 的高度
    - [page:Number depth] -- The number of layers of the 2D array texture.
    + [page:Number depth] -- 二维阵列纹理的层数。
    - [page:Constant format] -- The format used in the mipmaps. - See [page:Textures ST3C Compressed Texture Formats], - [page:Textures PVRTC Compressed Texture Formats] and - [page:Textures ETC Compressed Texture Format] for other choices.
    + [page:Constant format] -- mipmap 中使用的格式。 + 有关其他选择,请参阅[page:Textures ST3C Compressed Texture Formats] 、 + [page:Textures PVRTC Compressed Texture Formats]和[page:Textures ETC Compressed Texture Format]。 +
    - [page:Constant type] -- Default is [page:Textures THREE.UnsignedByteType]. - See [page:Textures type constants] for other choices.
    + [page:Constant type] -- 默认值为[page:Textures THREE.UnsignedByteType]。 有关其他选择,请参阅[page:Textures type constants]。

    -

    Properties

    - - See the base [page:CompressedTexture CompressedTexture] class for common properties. +

    属性

    + 有关常见属性,请参阅基本[page:CompressedTexture CompressedTexture]类。

    [property:number wrapR]

    - This defines how the texture is wrapped in the depth direction.
    - The default is [page:Textures THREE.ClampToEdgeWrapping], where the edge is clamped to the outer edge texels. - The other two choices are [page:Textures THREE.RepeatWrapping] and [page:Textures THREE.MirroredRepeatWrapping]. - See the [page:Textures texture constants] page for details. + 这定义了纹理在深度方向上的包裹方式。
    + 默认值为[page:Textures THREE.ClampToEdgeWrapping],其中边缘被紧贴到外边缘纹素上。 + 另外两个选择是[page:Textures THREE.RepeatWrapping]和[page:Textures THREE.MirroredRepeatWrapping]。 + 有关详细信息,请参阅[page:Textures texture constants]页面。

    [property:Boolean isCompressedArrayTexture]

    - Read-only flag to check if a given object is of type [name]. + 只读标志,用于检查给定对象是否为[name]类型。

    -

    Methods

    +

    函数

    - See the base [page:CompressedTexture CompressedTexture] class for common methods. + 有关常用方法,请参阅基本[page:CompressedTexture CompressedTexture]类。

    -

    Source

    +

    源代码

    [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] diff --git a/docs/api/zh/textures/CompressedTexture.html b/docs/api/zh/textures/CompressedTexture.html index 62efc4c1d9135b..0df94bf316e262 100644 --- a/docs/api/zh/textures/CompressedTexture.html +++ b/docs/api/zh/textures/CompressedTexture.html @@ -80,7 +80,7 @@

    [property:Boolean generateMipmaps]

    [property:Boolean isCompressedTexture]

    - Read-only flag to check if a given object is of type [name]. + 只读属性,用于检查给定对象是否为[name]类型。

    diff --git a/docs/api/zh/textures/CubeTexture.html b/docs/api/zh/textures/CubeTexture.html index c8017fce2d0a33..b1f813cd85c662 100644 --- a/docs/api/zh/textures/CubeTexture.html +++ b/docs/api/zh/textures/CubeTexture.html @@ -42,23 +42,23 @@

    [name]( images, mapping, wrapS, wrapT, magFilter, minFilter, format, type, a

    属性

    - See the base [page:Texture Texture] class for common properties. + 公共属性请参考[page:Texture Texture]类。

    [property:Boolean flipY]

    - If set to *true*, the texture is flipped along the vertical axis when uploaded to the GPU. Default is *false*. + 如果设置为 *true*,纹理在上传到GPU时会被垂直翻转。默认值为 *false*。

    [property:Boolean isCubeTexture]

    - Read-only flag to check if a given object is of type [name]. + 只读属性,用于检查给定对象是否为[name]类型。

    方法

    - See the base [page:Texture Texture] class for common methods. + 公共方法请参考[page:Texture Texture]类。

    源代码

    diff --git a/docs/api/zh/textures/Data3DTexture.html b/docs/api/zh/textures/Data3DTexture.html index 759488c485ebe9..2f5644e574cd33 100644 --- a/docs/api/zh/textures/Data3DTexture.html +++ b/docs/api/zh/textures/Data3DTexture.html @@ -11,7 +11,7 @@

    [name]

    -

    创建一个三维的纹理贴图。这种纹理贴图只能被用于WebGL 2渲染环境中。

    +

    创建一个三维的纹理贴图。

    构造函数

    @@ -29,7 +29,7 @@

    [name]( [param:TypedArray data], [param:Number width], [param:Number height]

    例子

    - [example:webgl2_materials_texture3d WebGL2 / materials / texture3d] + [example:webgl_texture3d WebGL / materials / texture3d]

    属性

    diff --git a/docs/api/zh/textures/DataArrayTexture.html b/docs/api/zh/textures/DataArrayTexture.html index 46829d2a3b3104..3a265b10014ac7 100644 --- a/docs/api/zh/textures/DataArrayTexture.html +++ b/docs/api/zh/textures/DataArrayTexture.html @@ -12,7 +12,7 @@

    数据数组纹理([name])

    - 直接从原始数据、宽度、高度和深度创建纹理数组。这种类型的纹理只能与 WebGL 2 渲染上下文一起使用。 + 直接从原始数据、宽度、高度和深度创建纹理数组。

    @@ -74,8 +74,8 @@

    代码示例(Code Example)

    示例(Examples)

    - [example:webgl2_materials_texture2darray WebGL2 / materials / texture2darray] - [example:webgl2_rendertarget_texture2darray WebGL2 / rendertarget / texture2darray] + [example:webgl_texture2darray WebGL / texture2darray]
    + [example:webgl_rendertarget_texture2darray WebGL / rendertarget / texture2darray]

    特性(Properties)

    diff --git a/docs/api/zh/textures/DataTexture.html b/docs/api/zh/textures/DataTexture.html index 33ab84cdf6b1d1..63f75391139bed 100644 --- a/docs/api/zh/textures/DataTexture.html +++ b/docs/api/zh/textures/DataTexture.html @@ -87,7 +87,7 @@

    [property:Object image]

    [property:Boolean isDataTexture]

    - Read-only flag to check if a given object is of type [name]. + 只读属性,用于检查给定对象是否为[name]类型。

    [property:number unpackAlignment]

    diff --git a/docs/api/zh/textures/DepthTexture.html b/docs/api/zh/textures/DepthTexture.html index c6024eb6dc043e..4914087f9048fe 100644 --- a/docs/api/zh/textures/DepthTexture.html +++ b/docs/api/zh/textures/DepthTexture.html @@ -9,11 +9,10 @@ [page:Texture] → -

    深度纹理([name])

    +

    深度纹理([name])

    This class can be used to automatically save the depth information of a rendering into a texture. - When using a WebGL 1 rendering context, [name] requires support for the [link:https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/ WEBGL_depth_texture] extension.

    例子

    @@ -101,7 +100,7 @@

    [page:Texture.generateMipmaps generateMipmaps]

    [property:Boolean isDepthTexture]

    - Read-only flag to check if a given object is of type [name]. + 只读属性,用于检查给定对象是否为[name]类型。

    方法

    diff --git a/docs/api/zh/textures/FramebufferTexture.html b/docs/api/zh/textures/FramebufferTexture.html index 2057a4b8ebbf0d..af0ed51068b867 100644 --- a/docs/api/zh/textures/FramebufferTexture.html +++ b/docs/api/zh/textures/FramebufferTexture.html @@ -9,38 +9,82 @@ [page:Texture] → -

    [name]

    +

    帧缓冲纹理([name])

    - This class can only be used in combination with [page:WebGLRenderer.copyFramebufferToTexture](). + 这个类只能与 [page:WebGLRenderer.copyFramebufferToTexture]() 结合使用。

    -

    Constructor

    + + const pixelRatio = window.devicePixelRatio; + const textureSize = 128 * pixelRatio; + + // instantiate a framebuffer texture + const frameTexture = new FramebufferTexture( textureSize, textureSize ); + + // calculate start position for copying part of the frame data + const vector = new Vector2(); + vector.x = ( window.innerWidth * pixelRatio / 2 ) - ( textureSize / 2 ); + vector.y = ( window.innerHeight * pixelRatio / 2 ) - ( textureSize / 2 ); + + // render the scene + renderer.clear(); + renderer.render( scene, camera ); + + // copy part of the rendered frame into the framebuffer texture + renderer.copyFramebufferToTexture( frameTexture, vector ); + + +

    例子

    + +

    [example:webgl_framebuffer_texture]

    + +

    构造函数

    [name]( [param:Number width], [param:Number height] )

    - [page:Number width] -- The width of the texture.
    + [page:Number width] -- 纹理的宽度
    + + [page:Number height] -- 纹理的高度

    - [page:Number height] -- The height of the texture.

    -

    Properties

    +

    属性

    -

    See the base [page:Texture Texture] class for common properties.

    +

    共有属性请参见其基类 [page:Texture Texture]

    + +

    [property:Boolean generateMipmaps]

    +

    是否为 [name] 生成 mipmaps ,默认为`false`

    [property:Boolean isFramebufferTexture]

    +

    只读,检查给定对象是否为 [name] 类型

    + +

    [property:number magFilter]

    +

    + 纹理元素覆盖多个像素时如何对纹理进行采样。默认值为 [page:Textures THREE.NearestFilter] ,它使用最接近的纹理元素。 +

    + + 更多细节详见 [page:Textures texture constants] +

    + +

    [property:number minFilter]

    - Read-only flag to check if a given object is of type [name]. + 纹理元素覆盖少于一个像素时如何对纹理进行采样。默认值为 [page:Textures THREE.NearestFilter] ,它使用最近的纹理元素。 +

    + + 更多细节详见 [page:Textures texture constants]

    [property:Boolean needsUpdate]

    -

    Methods

    +

    默认为 `true` ,这是加载 canvas 数据所必需的

    + +

    方法

    -

    See the base [page:Texture Texture] class for common methods.

    +

    共有方法请参见其基类 [page:Texture Texture]

    -

    Source

    +

    源代码

    [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js] diff --git a/docs/api/zh/textures/Source.html b/docs/api/zh/textures/Source.html index b35f409bd9bf10..190b4d546ca116 100644 --- a/docs/api/zh/textures/Source.html +++ b/docs/api/zh/textures/Source.html @@ -29,12 +29,18 @@

    [property:Any data]

    [property:Boolean isCubeTexture]

    - Read-only flag to check if a given object is of type [name]. + 只读属性,用于检查给定对象是否为[name]类型。

    [property:Boolean needsUpdate]

    - Set this to *true* to trigger a data upload to the GPU next time the source is used. + When the property is set to `true`, the engine allocates the memory for the texture (if necessary) and triggers the actual texture upload to the GPU next time the source is used. +

    + +

    [property:Boolean dataReady]

    +

    + This property is only relevant when [page:.needUpdate] is set to `true` and provides more control on how texture data should be processed. + When `dataReady` is set to `false`, the engine performs the memory allocation (if necessary) but does not transfer the data into the GPU memory. Default is `true`.

    [property:String uuid]

    diff --git a/docs/api/zh/textures/Texture.html b/docs/api/zh/textures/Texture.html index 364308063252bc..3d8e0c566f5674 100644 --- a/docs/api/zh/textures/Texture.html +++ b/docs/api/zh/textures/Texture.html @@ -35,7 +35,7 @@

    [property:Integer id]

    [property:Boolean isTexture]

    - Read-only flag to check if a given object is of type [name]. + 只读属性,用于检查给定对象是否为[name]类型。

    [property:String uuid]

    @@ -73,7 +73,8 @@

    [property:number mapping]

    [property:Integer channel]

    - Lets you select the uv attribute to map the texture to. `0` for `uv`, `1` for `uv1`, `2` for `uv2` and `3` for `uv3`. + 选择uv名来映射纹理。默认值为`0`。 + `0` 即为 `uv`, `1` 即为 `uv1`, `2` 即为 `uv2` and `3` 即为 `uv3`.

    [property:number wrapS]

    diff --git a/docs/api/zh/textures/VideoTexture.html b/docs/api/zh/textures/VideoTexture.html index 5a6b41683fcd67..76f5018ad8c3a7 100644 --- a/docs/api/zh/textures/VideoTexture.html +++ b/docs/api/zh/textures/VideoTexture.html @@ -82,7 +82,7 @@

    [property:Boolean generateMipmaps]

    [property:Boolean isVideoTexture]

    - Read-only flag to check if a given object is of type [name]. + 只读属性,用于检查给定对象是否为[name]类型。

    [property:Boolean needsUpdate]

    diff --git a/docs/examples/en/animations/CCDIKSolver.html b/docs/examples/en/animations/CCDIKSolver.html index bb1cc7e0af7905..533510d8808615 100644 --- a/docs/examples/en/animations/CCDIKSolver.html +++ b/docs/examples/en/animations/CCDIKSolver.html @@ -9,9 +9,9 @@

    [name]

    -

    A solver for IK with `CCD Algorithm`.

    +

    A solver for IK with `CCD Algorithm`.

    [name] solves Inverse Kinematics Problem with CCD Algorithm. - [name] is designed to work with [page:SkinnedMesh] but also can be used with [page:MMDLoader] or [page:GLTFLoader] skeleton. + [name] is designed to work with [page:SkinnedMesh] but also can be used with [page:GLTFLoader] skeleton.

    @@ -116,10 +116,7 @@

    Code Example

    Examples

    - [example:webgl_animation_skinning_ik]
    - [example:webgl_loader_mmd]
    - [example:webgl_loader_mmd_pose]
    - [example:webgl_loader_mmd_audio] + [example:webgl_animation_skinning_ik]

    Constructor

    diff --git a/docs/examples/en/animations/MMDAnimationHelper.html b/docs/examples/en/animations/MMDAnimationHelper.html deleted file mode 100644 index 29ab1627380265..00000000000000 --- a/docs/examples/en/animations/MMDAnimationHelper.html +++ /dev/null @@ -1,183 +0,0 @@ - - - - - - - - - -

    [name]

    - -

    A animation helper for `MMD` resources.

    - [name] handles animation of MMD assets loaded by [page:MMDLoader] with MMD special features as IK, Grant, and Physics. - It uses [page:CCDIKSolver] and [page:MMDPhysics] inside. -

    - -

    Import

    - -

    - [name] is an add-on, and must be imported explicitly. - See [link:#manual/introduction/Installation Installation / Addons]. -

    - - - import { MMDAnimationHelper } from 'three/addons/animation/MMDAnimationHelper.js'; - - -

    Code Example

    - - - // Instantiate a helper - const helper = new MMDAnimationHelper(); - - // Load MMD resources and add to helper - new MMDLoader().loadWithAnimation( - 'models/mmd/miku.pmd', - 'models/mmd/dance.vmd', - function ( mmd ) { - - helper.add( mmd.mesh, { - animation: mmd.animation, - physics: true - } ); - - scene.add( mmd.mesh ); - - new THREE.AudioLoader().load( - 'audios/mmd/song.mp3', - function ( buffer ) { - - const listener = new THREE.AudioListener(); - const audio = new THREE.Audio( listener ).setBuffer( buffer ); - - listener.position.z = 1; - - scene.add( audio ); - scene.add( listener ); - - } - - ); - - } - ); - - function render() { - - helper.update( clock.getDelta() ); - renderer.render( scene, camera ); - - } - - -

    Examples

    - -

    - [example:webgl_loader_mmd]
    - [example:webgl_loader_mmd_pose]
    - [example:webgl_loader_mmd_audio] -

    - -

    Constructor

    - -

    [name]( [param:Object params] )

    -

    - [page:Object params] — (optional)
    -

    -
      -
    • [page:Boolean sync] - Whether animation durations of added objects are synched. Default is true.
    • -
    • [page:Number afterglow] - Default is 0.0.
    • -
    • [page:Boolean resetPhysicsOnLoop] - Default is true.
    • -
    • [page:Boolean pmxAnimation] - If it is set to true, the helper follows the complex and costly PMX animation system. - Try this option only if your PMX model animation doesn't work well. Default is false.
    • -
    -

    - Creates a new [name]. -

    - -

    Properties

    - -

    [property:Audio audio]

    -

    An [page:Audio] added to helper.

    - -

    [property:Camera camera]

    -

    An [page:Camera] added to helper.

    - -

    [property:Array meshes]

    -

    An array of [page:SkinnedMesh] added to helper.

    - -

    [property:WeakMap objects]

    -

    A [page:WeakMap] which holds animation stuffs used in helper for objects added to helper. For example, you can access [page:AnimationMixer] for an added [page:SkinnedMesh] with "helper.objects.get( mesh ).mixer"

    - -

    [property:Function onBeforePhysics]

    -

    An optional callback that is executed immediately before the physicis calculation for an [page:SkinnedMesh]. This function is called with the [page:SkinnedMesh].

    - -

    Methods

    - -

    [method:MMDAnimationHelper add]( [param:Object3D object], [param:Object params] )

    -

    - [page:Object3D object] — [page:SkinnedMesh], [page:Camera], or [page:Audio]
    - [page:Object params] — (optional)
    -

    -
      -
    • [page:AnimationClip animation] - an [page:AnimationClip] or an array of [page:AnimationClip] set to object. Only for [page:SkinnedMesh] and [page:Camera]. Default is undefined.
    • -
    • [page:Boolean physics] - Only for [page:SkinnedMesh]. A flag whether turn on physics. Default is true.
    • -
    • [page:Integer warmup] - Only for [page:SkinnedMesh] and physics is true. Physics parameter. Default is 60.
    • -
    • [page:Number unitStep] - Only for [page:SkinnedMesh] and physics is true. Physics parameter. Default is 1 / 65.
    • -
    • [page:Integer maxStepNum] - Only for [page:SkinnedMesh] and physics is true. Physics parameter. Default is 3.
    • -
    • [page:Vector3 gravity] - Only for [page:SkinnedMesh] and physics is true. Physics parameter. Default is ( 0, - 9.8 * 10, 0 ).
    • -
    • [page:Number delayTime] - Only for [page:Audio]. Default is 0.0.
    • -
    -

    - Add an [page:SkinnedMesh], [page:Camera], or [page:Audio] to helper and setup animation. The anmation durations of added objects are synched. - If camera/audio has already been added, it'll be replaced with a new one. -

    - -

    [method:MMDAnimationHelper enable]( [param:String key], [param:Boolean enabled] )

    -

    - [page:String key] — Allowed strings are 'animation', 'ik', 'grant', 'physics', and 'cameraAnimation'.
    - [page:Boolean enabled] — true is enable, false is disable
    -

    -

    - Enable/Disable an animation feature -

    - -

    [method:MMDAnimationHelper pose]( [param:SkinnedMesh mesh], [param:Object vpd], [param:Object params] )

    -

    - [page:SkinnedMesh mesh] — [page:SkinnedMesh] which changes the posing. It doesn't need to be added to helper.
    - [page:Object vpd] — VPD content obtained by [page:MMDLoader].loadVPD
    - [page:Object params] — (optional)
    -

    -
      -
    • [page:Boolean resetPose] - Default is true.
    • -
    • [page:Boolean ik] - Default is true.
    • -
    • [page:Boolean grant] - Default is true.
    • -
    -

    - Changes the posing of [page:SkinnedMesh] as VPD content specifies. -

    - -

    [method:MMDAnimationHelper remove]( [param:Object3D object] )

    -

    - [page:Object3D object] — [page:SkinnedMesh], [page:Camera], or [page:Audio]
    -

    -

    - Remove an [page:SkinnedMesh], [page:Camera], or [page:Audio] from helper. -

    - -

    [method:MMDAnimationHelper update]( [param:Number delta] )

    -

    - [page:Number delta] — number in second
    -

    -

    - Advance mixer time and update the animations of objects added to helper -

    - -

    Source

    - -

    - [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/animation/MMDAnimationHelper.js examples/jsm/animation/MMDAnimationHelper.js] -

    - - diff --git a/docs/examples/en/animations/MMDPhysics.html b/docs/examples/en/animations/MMDPhysics.html deleted file mode 100644 index ffb78be3173175..00000000000000 --- a/docs/examples/en/animations/MMDPhysics.html +++ /dev/null @@ -1,125 +0,0 @@ - - - - - - - - - -

    [name]

    - -

    A Physics handler for `MMD` resources.

    - [name] calculates Physics for model loaded by [page:MMDLoader] with [link:https://github.com/kripken/ammo.js/ ammo.js] (Bullet-based JavaScript Physics engine). -

    - -

    Import

    - -

    - [name] is an add-on, and must be imported explicitly. - See [link:#manual/introduction/Installation Installation / Addons]. -

    - - - import { MMDPhysics } from 'three/addons/animation/MMDPhysics.js'; - - -

    Code Example

    - - - let physics; - - // Load MMD resources and instantiate MMDPhysics - new MMDLoader().load( - 'models/mmd/miku.pmd', - function ( mesh ) { - - physics = new MMDPhysics( mesh ) - scene.add( mesh ); - - } - ); - - function render() { - - const delta = clock.getDelta(); - animate( delta ); // update bones - if ( physics !== undefined ) physics.update( delta ); - renderer.render( scene, camera ); - - } - - -

    Examples

    - -

    - [example:webgl_loader_mmd]
    - [example:webgl_loader_mmd_audio] -

    - -

    Constructor

    - -

    [name]( [param:SkinnedMesh mesh], [param:Array rigidBodyParams], [param:Array constraintParams], [param:Object params] )

    -

    - [page:SkinnedMesh mesh] — [page:SkinnedMesh] for which [name] calculates Physics.
    - [page:Array rigidBodyParams] — An array of [page:Object] specifying Rigid Body parameters.
    - [page:Array constraintParams] — (optional) An array of [page:Object] specifying Constraint parameters.
    - [page:Object params] — (optional)
    -

    -
      -
    • [page:Number unitStep] - Default is 1 / 65.
    • -
    • [page:Integer maxStepNum] - Default is 3.
    • -
    • [page:Vector3 gravity] - Default is ( 0, - 9.8 * 10, 0 )
    • -
    -

    - Creates a new [name]. -

    - -

    Properties

    - -

    [property:Array mesh]

    -

    [page:SkinnedMesh] passed to the constructor.

    - -

    Methods

    - -

    [method:MMDPhysicsHelper createHelper]()

    -

    - Return [page:MMDPhysicsHelper]. You can visualize Rigid bodies by adding the helper to scene. -

    - -

    [method:this reset]()

    -

    - Resets Rigid bodies transform to current bone's. -

    - -

    [method:this setGravity]( [param:Vector3 gravity] )

    -

    - [page:Vector3 gravity] — Direction and volume of gravity. -

    -

    - Set gravity. -

    - -

    [method:this update]( [param:Number delta] )

    -

    - [page:Number delta] — Time in second. -

    -

    - Advance Physics calculation and updates bones. -

    - -

    [method:this warmup]( [param:Integer cycles] )

    -

    - [page:Number delta] — Time in second. -

    -

    - Warm up Rigid bodies. Calculates cycles steps. -

    - -

    Source

    - -

    - [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/animation/MMDPhysics.js examples/jsm/animation/MMDPhysics.js] -

    - - diff --git a/docs/examples/en/controls/ArcballControls.html b/docs/examples/en/controls/ArcballControls.html index 9565896556b0d7..44c325a5a51d44 100644 --- a/docs/examples/en/controls/ArcballControls.html +++ b/docs/examples/en/controls/ArcballControls.html @@ -7,7 +7,7 @@ - [page:EventDispatcher] → + [page:Controls] →

    [name]

    @@ -16,7 +16,7 @@

    [name]

    Cursor/finger positions and movements are mapped over a virtual trackball surface represented by a gizmo and mapped in intuitive and consistent camera movements. Dragging cursor/fingers will cause camera to orbit around the center of the trackball in a conservative way (returning to the starting point - will make the camera to return to its starting orientation).

    + will make the camera return to its starting orientation).

    In addition to supporting pan, zoom and pinch gestures, Arcball controls provide focus functionality with a double click/tap for intuitively moving the object's point of interest in the center of the virtual trackball. @@ -78,9 +78,9 @@

    [name]( [param:Camera camera], [param:HTMLDOMElement domElement], [param:Sce

    [page:Camera camera]: (required) The camera to be controlled. The camera must not be a child of another object, unless that object is the scene itself.

    - [page:HTMLDOMElement domElement]: The HTML element used for event listeners.

    + [page:HTMLDOMElement domElement]: The HTML element used for event listeners. (optional)

    - [page:Scene scene]: The scene rendered by the camera. If not given, gizmos cannot be shown. + [page:Scene scene]: The scene rendered by the camera. If not given, gizmos cannot be shown. (optional)

    Events

    @@ -102,9 +102,11 @@

    end

    Properties

    +

    See the base [page:Controls] class for common properties.

    +

    [property:Boolean adjustNearFar]

    - If true, camera's near and far values will be adjusted every time zoom is performed trying to mantain the same visible portion + If true, camera's near and far values will be adjusted every time zoom is performed trying to maintain the same visible portion given by initial near and far values ( [page:PerspectiveCamera] only ). Default is false.

    @@ -125,20 +127,14 @@

    The damping inertia used if [page:.enableAnimations] is set to true.

    -

    [property:HTMLDOMElement domElement]

    -

    - The HTMLDOMElement used to listen for mouse / touch events. This must be passed in the constructor; changing it here will - not set up new event listeners. -

    - -

    [property:Boolean enabled]

    +

    [property:Boolean enableAnimations]

    - When set to `false`, the controls will not respond to user input. Default is `true`. + Set to true to enable animations for rotation (damping) and focus operation. Default is true.

    -

    [property:Boolean enableAnimations]

    +

    [property:Boolean enableFocus]

    - Set to true to enable animations for rotation (damping) and focus operation. Default is true. + Enable or disable camera focusing on double-tap (or click) operations. Default is true.

    [property:Boolean enableGrid]

    @@ -186,6 +182,16 @@

    [property:Float minZoom]

    How far you can zoom in ( [page:OrthographicCamera] only ). Default is 0.

    +

    [property:Float radiusFactor]

    +

    + The size of the gizmo relative to the screen width and height. Default is 0.67. +

    + +

    [property:Float rotateSpeed]

    +

    + Speed of rotation. Default is 1. +

    +

    [property:Float scaleFactor]

    The scaling factor used when performing zoom operation. @@ -201,14 +207,10 @@

    [property:Float wMax]

    Maximum angular velocity allowed on rotation animation start.

    -

    [property:Float radiusFactor]

    -

    - The size of the gizmo relative to the screen width and height. Default is 0.67. -

    - -

    Methods

    +

    See the base [page:Controls] class for common methods.

    +

    [method:undefined activateGizmos] ( [param:Boolean isActive] )

    Make gizmos more or less visible. @@ -219,11 +221,6 @@

    [method:undefined copyState] ()

    Copy the current state to clipboard (as a readable JSON text).

    -

    [method:undefined dispose] ()

    -

    - Remove all the event listeners, cancel any pending animation and clean the scene from gizmos and grid. -

    -

    [method:undefined pasteState] ()

    Set the controls state from the clipboard, assumes that the clipboard stores a JSON text as saved from [page:.copyState]. @@ -251,7 +248,7 @@

    [method:undefined setGizmosVisible] ( [param:Boolean value] )

    [method:undefined setTbRadius] ( [param:Float value] )

    - Update the `radiusFactor` value, redraw the gizmo and send a `changeEvent` to visualise the changes. + Update the `radiusFactor` value, redraw the gizmo and send a `changeEvent` to visualize the changes.

    [method:Boolean setMouseAction] ( [param:String operation], mouse, key )

    @@ -269,11 +266,6 @@

    [method:Boolean unsetMouseAction] ( mouse, key )

    Keyboard modifiers can be specified as 'CTRL', 'SHIFT' or null if not needed.

    -

    [method:undefined update] ()

    -

    - Update the controls. Must be called after any manual changes to the camera's transform. -

    -

    [method:Raycaster getRaycaster] ()

    Returns the [page:Raycaster] object that is used for user interaction. This object is shared between all instances of diff --git a/docs/examples/en/controls/DragControls.html b/docs/examples/en/controls/DragControls.html index 71a1ce7cce2e3c..cb6ddd78119173 100644 --- a/docs/examples/en/controls/DragControls.html +++ b/docs/examples/en/controls/DragControls.html @@ -7,7 +7,7 @@ - [page:EventDispatcher] → + [page:Controls] →

    [name]

    @@ -60,7 +60,7 @@

    [name]( [param:Array objects], [param:Camera camera], [param:HTMLDOMElement [page:Camera camera]: The camera of the rendered scene.

    - [page:HTMLDOMElement domElement]: The HTML element used for event listeners. + [page:HTMLDOMElement domElement]: The HTML element used for event listeners. (optional)

    Creates a new instance of [name]. @@ -95,45 +95,37 @@

    hoveroff

    Properties

    -

    [property:Boolean enabled]

    -

    - Whether or not the controls are enabled. -

    +

    See the base [page:Controls] class for common properties.

    -

    [property:Boolean transformGroup]

    +

    [property:Array objects]

    - This option only works if the [page:DragControls.objects] array contains a single draggable group object. - If set to `true`, [name] does not transform individual objects but the entire group. Default is `false`. + An array of draggable 3D objects.

    -

    Methods

    - -

    See the base [page:EventDispatcher] class for common methods.

    - -

    [method:undefined activate] ()

    +

    [property:Raycaster raycaster]

    - Adds the event listeners of the controls. + The internal raycaster used for detecting 3D objects.

    -

    [method:undefined deactivate] ()

    +

    [property:Boolean recursive]

    - Removes the event listeners of the controls. + Whether children of draggable objects can be dragged independently from their parent. Default is `true`.

    -

    [method:undefined dispose] ()

    +

    [property:Float rotateSpeed]

    - Should be called if the controls is no longer required. + The speed at which the object will rotate when dragged in `rotate` mode. The higher the number the faster the rotation. Default is `1`.

    -

    [method:Array getObjects] ()

    +

    [property:Boolean transformGroup]

    - Returns the array of draggable objects. + This option only works if the [page:DragControls.objects] array contains a single draggable group object. + If set to `true`, [name] does not transform individual objects but the entire group. Default is `false`.

    -

    [method:Raycaster getRaycaster] ()

    -

    - Returns the internal [page:Raycaster] instance that is used for intersection tests. -

    +

    Methods

    + +

    See the base [page:Controls] class for common methods.

    Source

    diff --git a/docs/examples/en/controls/FirstPersonControls.html b/docs/examples/en/controls/FirstPersonControls.html index 580cb786eecf9b..937fb3c64eb34d 100644 --- a/docs/examples/en/controls/FirstPersonControls.html +++ b/docs/examples/en/controls/FirstPersonControls.html @@ -7,6 +7,7 @@ + [page:Controls] →

    [name]

    @@ -37,7 +38,7 @@

    [name]( [param:Camera object], [param:HTMLDOMElement domElement] )

    [page:Camera object]: The camera to be controlled.

    - [page:HTMLDOMElement domElement]: The HTML element used for event listeners. + [page:HTMLDOMElement domElement]: The HTML element used for event listeners. (optional)

    Creates a new instance of [name]. @@ -61,17 +62,6 @@

    [property:Boolean constrainVertical]

    Whether or not looking around is vertically constrained by [[page:.verticalMin], [page:.verticalMax]]. Default is `false`.

    -

    [property:HTMLDOMElement domElement]

    -

    - The HTMLDOMElement used to listen for mouse / touch events. This must be passed in the constructor; changing it here will - not set up new event listeners. -

    - -

    [property:Boolean enabled]

    -

    - Whether or not the controls are enabled. Default is `true`. -

    -

    [property:Number heightCoef]

    Determines how much faster the camera moves when it's y-component is near [page:.heightMax]. Default is *1*. @@ -113,11 +103,6 @@

    [property:Number movementSpeed]

    The movement speed. Default is *1*.

    -

    [property:Camera object]

    -

    - The camera to be controlled. -

    -

    [property:Number verticalMax]

    How far you can vertically look around, upper limit. Range is 0 to Math.PI radians. Default is `Math.PI`. @@ -130,11 +115,6 @@

    [property:Number verticalMin]

    Methods

    -

    [method:undefined dispose] ()

    -

    - Should be called if the controls is no longer required. -

    -

    [method:undefined handleResize] ()

    Should be called if the application window is resized. @@ -156,16 +136,6 @@

    [method:FirstPersonControls lookAt]( [param:Vector3 vector] )

    -

    [method:undefined update] ( [param:Number delta] )

    -

    -

    - [page:Number delta]: Time delta value. -

    -

    - Updates the controls. Usually called in the animation loop. -

    -

    -

    Source

    diff --git a/docs/examples/en/controls/FlyControls.html b/docs/examples/en/controls/FlyControls.html index 952b6b84618f60..1f22b9525fe77b 100644 --- a/docs/examples/en/controls/FlyControls.html +++ b/docs/examples/en/controls/FlyControls.html @@ -7,6 +7,7 @@ + [page:Controls] →

    [name]

    @@ -38,7 +39,7 @@

    [name]( [param:Camera object], [param:HTMLDOMElement domElement] )

    [page:Camera object]: The camera to be controlled.

    - [page:HTMLDOMElement domElement]: The HTML element used for event listeners. + [page:HTMLDOMElement domElement]: The HTML element used for event listeners. (optional)

    Creates a new instance of [name]. @@ -54,17 +55,13 @@

    change

    Properties

    +

    See the base [page:Controls] class for common properties.

    +

    [property:Boolean autoForward]

    If set to `true`, the camera automatically moves forward (and does not stop) when initially translated. Default is `false`.

    -

    [property:HTMLDOMElement domElement]

    -

    - The HTMLDOMElement used to listen for mouse / touch events. This must be passed in the constructor; changing it here will - not set up new event listeners. -

    -

    [property:Boolean dragToLook]

    If set to `true`, you can only look around by performing a drag interaction. Default is `false`. @@ -72,12 +69,7 @@

    [property:Boolean dragToLook]

    [property:Number movementSpeed]

    - The movement speed. Default is *1*. -

    - -

    [property:Camera object]

    -

    - The camera to be controlled. + The movement speed. Default is `1`.

    [property:Number rollSpeed]

    @@ -87,20 +79,7 @@

    [property:Number rollSpeed]

    Methods

    -

    [method:undefined dispose] ()

    -

    - Should be called if the controls is no longer required. -

    - -

    [method:undefined update] ( [param:Number delta] )

    -

    -

    - [page:Number delta]: Time delta value. -

    -

    - Updates the controls. Usually called in the animation loop. -

    -

    +

    See the base [page:Controls] class for common methods.

    Source

    diff --git a/docs/examples/en/controls/OrbitControls.html b/docs/examples/en/controls/OrbitControls.html index b8f38209c9be5e..78fb9d661d771a 100644 --- a/docs/examples/en/controls/OrbitControls.html +++ b/docs/examples/en/controls/OrbitControls.html @@ -7,6 +7,8 @@ + [page:Controls] → +

    [name]

    @@ -66,7 +68,7 @@

    [name]( [param:Camera object], [param:HTMLDOMElement domElement] )

    [page:Camera object]: (required) The camera to be controlled. The camera must not be a child of another object, unless that object is the scene itself.

    - [page:HTMLDOMElement domElement]: The HTML element used for event listeners. + [page:HTMLDOMElement domElement]: The HTML element used for event listeners. (optional)

    Events

    @@ -88,10 +90,12 @@

    end

    Properties

    +

    See the base [page:Controls] class for common properties.

    +

    [property:Boolean autoRotate]

    Set to true to automatically rotate around the target.
    Note that if this is enabled, you must call [page:.update] - () in your animation loop. + () in your animation loop. If you want the auto-rotate speed to be independent of the frame rate (the refresh rate of the display), you must pass the time `deltaTime`, in seconds, to [page:.update]().

    [property:Float autoRotateSpeed]

    @@ -108,17 +112,6 @@

    call [page:.update] () in your animation loop.

    -

    [property:HTMLDOMElement domElement]

    -

    - The HTMLDOMElement used to listen for mouse / touch events. This must be passed in the constructor; changing it here will - not set up new event listeners. -

    - -

    [property:Boolean enabled]

    -

    - When set to `false`, the controls will not respond to user input. Default is `true`. -

    -

    [property:Boolean enableDamping]

    Set to true to enable damping (inertia), which can be used to give a sense of weight to the controls. Default is false.
    @@ -148,6 +141,11 @@

    [property:Float keyPanSpeed]

    How fast to pan the camera when the keyboard is used. Default is 7.0 pixels per keypress.

    +

    [property:Float keyRotateSpeed]

    +

    + How fast to rotate the camera when the keyboard is used. Default is 1. +

    +

    [property:Object keys]

    This object contains references to the keycodes for controlling camera panning. Default is the 4 arrow keys. @@ -181,6 +179,16 @@

    [property:Float maxZoom]

    How far you can zoom out ( [page:OrthographicCamera] only ). Default is Infinity.

    +

    [property:Float minTargetRadius]

    +

    + How close you can get the target to the 3D [page:.cursor]. Default is 0. +

    + +

    [property:Float maxTargetRadius]

    +

    + How far you can move the target from the 3D [page:.cursor]. Default is Infinity. +

    +

    [property:Float minAzimuthAngle]

    How far you can orbit horizontally, lower limit. If set, the interval [ min, max ] must be a sub-interval of [ - 2 PI, 2 PI ], with ( max - min < 2 PI ). Default is Infinity. @@ -214,11 +222,6 @@

    -

    [property:Camera object]

    -

    - The camera being controlled. -

    -

    [property:Float panSpeed]

    Speed of panning. Default is 1. @@ -252,6 +255,11 @@

    [property:Vector3 target]

    the focus of the controls.

    +

    [property:Vector3 cursor]

    +

    + The focus point of the [page:.minTargetRadius] and [page:.maxTargetRadius] limits. It can be updated manually at any point to change the center of interest for the [page:.target]. +

    +

    [property:Object touches]

    This object contains references to the touch actions used by the controls. @@ -273,14 +281,14 @@

    [property:Float zoomSpeed]

    Speed of zooming / dollying. Default is 1.

    - +

    [property:Boolean zoomToCursor]

    +

    + Setting this property to `true` allows to zoom to the cursor's position. Default is `false`. +

    Methods

    -

    [method:undefined dispose] ()

    -

    - Remove all the event listeners. -

    +

    See the base [page:Controls] class for common methods.

    [method:radians getAzimuthalAngle] ()

    @@ -317,10 +325,11 @@

    [method:undefined stopListenToKeyEvents] ()

    Removes the key event listener previously defined with [page:.listenToKeyEvents]().

    -

    [method:Boolean update] ()

    +

    [method:Boolean update] ( [param:Number deltaTime] )

    Update the controls. Must be called after any manual changes to the camera's transform, - or in the update loop if [page:.autoRotate] or [page:.enableDamping] are set. + or in the update loop if [page:.autoRotate] or [page:.enableDamping] are set. `deltaTime`, in seconds, is optional, + and is only required if you want the auto-rotate speed to be independent of the frame rate (the refresh rate of the display).

    Source

    diff --git a/docs/examples/en/controls/PointerLockControls.html b/docs/examples/en/controls/PointerLockControls.html index 29265de39a5a4d..a43026d1129fca 100644 --- a/docs/examples/en/controls/PointerLockControls.html +++ b/docs/examples/en/controls/PointerLockControls.html @@ -7,7 +7,7 @@ - [page:EventDispatcher] → + [page:Controls] →

    [name]

    @@ -85,12 +85,7 @@

    unlock

    Properties

    -

    [property:HTMLDOMElement domElement]

    -

    - The HTMLDOMElement used to listen for mouse / touch events. This must be passed in the constructor; changing it here will - not set up new event listeners. -

    - +

    See the base [page:Controls] class for common properties.

    [property:Boolean isLocked]

    @@ -114,17 +109,7 @@

    [property:Float pointerSpeed]

    Methods

    -

    See the base [page:EventDispatcher] class for common methods.

    - -

    [method:undefined connect] ()

    -

    - Adds the event listeners of the controls. -

    - -

    [method:undefined disconnect] ()

    -

    - Removes the event listeners of the controls. -

    +

    See the base [page:Controls] class for common methods.

    [method:Vector3 getDirection] ( [param:Vector3 target] )

    diff --git a/docs/examples/en/controls/TrackballControls.html b/docs/examples/en/controls/TrackballControls.html index ed2fd22311620f..40c4a1104c5f50 100644 --- a/docs/examples/en/controls/TrackballControls.html +++ b/docs/examples/en/controls/TrackballControls.html @@ -7,7 +7,7 @@ - [page:EventDispatcher] → + [page:Controls] →

    [name]

    @@ -41,7 +41,7 @@

    [name]( [param:Camera camera], [param:HTMLDOMElement domElement] )

    [page:Camera camera]: The camera of the rendered scene.

    - [page:HTMLDOMElement domElement]: The HTML element used for event listeners. + [page:HTMLDOMElement domElement]: The HTML element used for event listeners. (optional)

    Creates a new instance of [name]. @@ -67,22 +67,13 @@

    end

    Properties

    -

    [property:HTMLDOMElement domElement]

    -

    - The HTMLDOMElement used to listen for mouse / touch events. This must be passed in the constructor; changing it here will - not set up new event listeners. -

    +

    See the base [page:Controls] class for common properties.

    [property:Number dynamicDampingFactor]

    Defines the intensity of damping. Only considered if [page:.staticMoving staticMoving] is set to `false`. Default is `0.2`.

    -

    [property:Boolean enabled]

    -

    - Whether or not the controls are enabled. -

    -

    [property:Array keys]

    This array holds keycodes for controlling interactions. @@ -140,11 +131,6 @@

    [property:Boolean noZoom]

    Whether or not zooming is disabled. Default is `false`.

    -

    [property:Camera object]

    -

    - The camera being controlled. -

    -

    [property:Number panSpeed]

    The pan speed. Default is `0.3`. @@ -171,6 +157,11 @@

    [property:Boolean staticMoving]

    Whether or not damping is disabled. Default is `false`.

    +

    [property:Vector3 target]

    +

    + The focus point of the controls. +

    +

    [property:Number zoomSpeed]

    The zoom speed. Default is `1.2`. @@ -178,46 +169,18 @@

    [property:Number zoomSpeed]

    Methods

    -

    [method:undefined checkDistances] ()

    -

    - Ensures the controls stay in the range [minDistance, maxDistance]. Called by [page:.update update](). -

    - -

    [method:undefined dispose] ()

    -

    - Should be called if the controls is no longer required. -

    +

    See the base [page:Controls] class for common methods.

    [method:undefined handleResize] ()

    Should be called if the application window is resized.

    -

    [method:undefined panCamera] ()

    -

    - Performs panning if necessary. Called by [page:.update update](). -

    -

    [method:undefined reset] ()

    Resets the controls to its initial state.

    -

    [method:undefined rotateCamera] ()

    -

    - Rotates the camera if necessary. Called by [page:.update update](). -

    - -

    [method:undefined update] ()

    -

    - Updates the controls. Usually called in the animation loop. -

    - -

    [method:undefined zoomCamera] ()

    -

    - Performs zooming if necessary. Called by [page:.update update](). -

    -

    Source

    diff --git a/docs/examples/en/controls/TransformControls.html b/docs/examples/en/controls/TransformControls.html index 334ca36614f04c..f9a154a44c419a 100644 --- a/docs/examples/en/controls/TransformControls.html +++ b/docs/examples/en/controls/TransformControls.html @@ -7,7 +7,7 @@ - [page:Object3D] → + [page:Controls] →

    [name]

    @@ -41,7 +41,7 @@

    [name]( [param:Camera camera], [param:HTMLDOMElement domElement] )

    [page:Camera camera]: The camera of the rendered scene.

    - [page:HTMLDOMElement domElement]: The HTML element used for event listeners. + [page:HTMLDOMElement domElement]: The HTML element used for event listeners. (optional)

    Creates a new instance of [name]. @@ -73,7 +73,7 @@

    objectChange

    Properties

    -

    See the base [page:Object3D] class for common properties.

    +

    See the base [page:Controls] class for common properties.

    [property:String axis]

    @@ -85,32 +85,16 @@

    [property:Camera camera]

    The camera of the rendered scene.

    -

    [property:HTMLDOMElement domElement]

    -

    - The HTMLDOMElement used to listen for mouse / touch events. This must be passed in the constructor; changing it here will - not set up new event listeners. -

    -

    [property:Boolean dragging]

    Whether or not dragging is currently performed. Read-only property.

    -

    [property:Boolean enabled]

    -

    - Whether or not the controls are enabled. -

    -

    [property:String mode]

    The current transformation mode. Possible values are "translate", "rotate" and "scale". Default is `translate`.

    -

    [property:Object3D object]

    -

    - The 3D object being controlled. -

    -

    [property:Number rotationSnap]

    By default, 3D objects are continuously rotated. If you set this property to a numeric value (radians), you can define in which @@ -148,9 +132,39 @@

    [property:Number translationSnap]

    steps the 3D object should be translated. Default is `null`.

    +

    [property:Number minX]

    +

    + The minimum allowed X position during translation. Default is `-Infinity`. +

    + +

    [property:Number maxX]

    +

    + The maximum allowed X position during translation. Default is `Infinity`. +

    + +

    [property:Number minY]

    +

    + The minimum allowed Y position during translation. Default is `-Infinity`. +

    + +

    [property:Number maxY]

    +

    + The maximum allowed Y position during translation. Default is `Infinity`. +

    + +

    [property:Number minZ]

    +

    + The minimum allowed Z position during translation. Default is `-Infinity`. +

    + +

    [property:Number maxZ]

    +

    + The maximum allowed Z position during translation. Default is `Infinity`. +

    +

    Methods

    -

    See the base [page:Object3D] class for common methods.

    +

    See the base [page:Controls] class for common methods.

    [method:TransformControls attach] ( [param:Object3D object] )

    @@ -167,9 +181,10 @@

    [method:TransformControls detach] ()

    Removes the current 3D object from the controls and makes the helper UI invisible.

    -

    [method:undefined dispose] ()

    +

    [method:Object3D getHelper] ()

    - Should be called if the controls is no longer required. + Returns the visual representation of the controls. Add the helper to your scene to visually transform the attached + 3D object.

    [method:Raycaster getRaycaster] ()

    diff --git a/docs/examples/en/exporters/ColladaExporter.html b/docs/examples/en/exporters/ColladaExporter.html deleted file mode 100644 index e64279dfd388fa..00000000000000 --- a/docs/examples/en/exporters/ColladaExporter.html +++ /dev/null @@ -1,96 +0,0 @@ - - - - - - - - - -

    [name]

    - -

    - An exporter for `Collada`. -

    - [link:https://www.khronos.org/collada/ Collada] is a - file format for robust representation of scenes, materials, animations, and other 3D content in an xml format. - This exporter only supports exporting geometry, materials, textures, and scene hierarchy. -

    - -

    Import

    - -

    - [name] is an add-on, and must be imported explicitly. - See [link:#manual/introduction/Installation Installation / Addons]. -

    - - - import { ColladaExporter } from 'three/addons/exporters/ColladaExporter.js'; - - -

    Code Example

    - - - // Instantiate an exporter - const exporter = new ColladaExporter(); - - // Parse the input and generate the collada ( .dae ) output - const data = exporter.parse( scene, null, options ); - downloadFile( data ); - - -

    Constructor

    - -

    [name]()

    -

    -

    -

    - Creates a new [name]. -

    - -

    Methods

    - -

    [method:Object parse]( [param:Object3D input], [param:Function onCompleted], [param:Object options] )

    -

    - [page:Object input] — Object3D to be exported
    - [page:Function onCompleted] — Will be called when the export completes. Optional. The same data is immediately returned from the function.
    - [page:Options options] — Export options
    -

    -
      -
    • version - string. Which version of Collada to export. The options are "1.4.1" or "1.5.0". Defaults to "1.4.1".
    • -
    • author - string. The name to include in the author field. Author field is excluded by default.
    • -
    • textureDirectory - string. The directory relative to the Collada file to save the textures to.
    • -
    • upAxis - string. Either Y_UP (default), Z_UP or X_UP.
    • -
    • unitName - string. Name of the unit. Can be any string, but could be for example "meter", "inch", or "parsec".
    • -
    • unitMeter - number. Length of the unit in meters.
    • -
    -

    - Generates an object with Collada file and texture data. This object is returned from the function and passed into the "onCompleted" callback. - - { - // Collada file content - data: "", - - // List of referenced textures - textures: [{ - - // File directory, name, and extension of the texture data - directory: "", - name: "", - ext: "", - - // The texture data and original texture object - data: [], - original: <THREE.Texture> - }, ...] - } - -

    - -

    Source

    - -

    - [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/exporters/ColladaExporter.js examples/jsm/exporters/ColladaExporter.js] -

    - - diff --git a/docs/examples/en/exporters/DRACOExporter.html b/docs/examples/en/exporters/DRACOExporter.html index 39b8545d97b910..73f09418c45d3c 100644 --- a/docs/examples/en/exporters/DRACOExporter.html +++ b/docs/examples/en/exporters/DRACOExporter.html @@ -69,7 +69,7 @@

    [method:Int8Array parse]( [param:Mesh object] | [param:Points object], [para
  • decodeSpeed - int. Indicates how to tune the encoder regarding decode speed (0 gives better speed but worst quality). Default is 5
  • encodeSpeed - int. Indicates how to tune the encoder parameters (0 gives better speed but worst quality). Default is 5.
  • encoderMethod - int. Either sequential (very little compression) or Edgebreaker. Edgebreaker traverses the triangles of the mesh in a deterministic, spiral-like way which provides most of the benefits of this data format. Default is DRACOExporter.MESH_EDGEBREAKER_ENCODING.
  • -
  • quantization - Array of int. Indicates the presision of each type of data stored in the draco file in the order (POSITION, NORMAL, COLOR, TEX_COORD, GENERIC). Default is [ 16, 8, 8, 8, 8 ]
  • +
  • quantization - Array of int. Indicates the precision of each type of data stored in the draco file in the order (POSITION, NORMAL, COLOR, TEX_COORD, GENERIC). Default is [ 16, 8, 8, 8, 8 ]
  • exportUvs - bool. Default is true.
  • exportNormals - bool. Default is true.
  • exportColor - bool. Default is false.
  • diff --git a/docs/examples/en/exporters/EXRExporter.html b/docs/examples/en/exporters/EXRExporter.html index fba1be4c981a52..21439b0b185d99 100644 --- a/docs/examples/en/exporters/EXRExporter.html +++ b/docs/examples/en/exporters/EXRExporter.html @@ -78,6 +78,15 @@

    [method:null parse]( [param:WebGLRenderer renderer], [param:WebGLRenderTarge Generates a .exr output from the input render target.

    +

    [method:null parse]( [param:DataTexture dataTexture], [param:Object options] )

    +

    + [page:Function dataTexture] — DataTexture containing data used for exporting EXR image.
    + [page:Options options] — Export options (details above).
    +

    +

    + Generates a .exr output from the input data texture. +

    +

    Source

    diff --git a/docs/examples/en/exporters/GLTFExporter.html b/docs/examples/en/exporters/GLTFExporter.html index a4fd41627a3e13..48d849eb132ed4 100644 --- a/docs/examples/en/exporters/GLTFExporter.html +++ b/docs/examples/en/exporters/GLTFExporter.html @@ -41,6 +41,7 @@

    Extensions

    • KHR_lights_punctual
    • KHR_materials_clearcoat
    • +
    • KHR_materials_dispersion
    • KHR_materials_emissive_strength
    • KHR_materials_ior
    • KHR_materials_iridescence
    • @@ -51,6 +52,8 @@

      Extensions

    • KHR_materials_volume
    • KHR_mesh_quantization
    • KHR_texture_transform
    • +
    • EXT_materials_bump
    • +
    • EXT_mesh_gpu_instancing

    @@ -135,18 +138,29 @@

    [method:undefined parse]( [param:Object3D input], [param:Function onComplete [page:Function onError] — Will be called if there are any errors during the gltf generation.
    [page:Options options] — Export options
      -
    • trs - bool. Export position, rotation and scale instead of matrix per node. Default is false
    • -
    • onlyVisible - bool. Export only visible objects. Default is true.
    • -
    • binary - bool. Export in binary (.glb) format, returning an ArrayBuffer. Default is false.
    • -
    • maxTextureSize - int. Restricts the image maximum size (both width and height) to the given value. Default is Infinity.
    • -
    • animations - Array<[page:AnimationClip AnimationClip]>. List of animations to be included in the export.
    • -
    • includeCustomExtensions - bool. Export custom glTF extensions defined on an object's `userData.gltfExtensions` property. Default is false.
    • +
    • `trs` - bool. Export position, rotation and scale instead of matrix per node. Default is false
    • +
    • `onlyVisible` - bool. Export only visible objects. Default is true.
    • +
    • `binary` - bool. Export in binary (.glb) format, returning an ArrayBuffer. Default is false.
    • +
    • `maxTextureSize` - int. Restricts the image maximum size (both width and height) to the given value. Default is Infinity.
    • +
    • `animations` - Array<[page:AnimationClip AnimationClip]>. List of animations to be included in the export.
    • +
    • `includeCustomExtensions` - bool. Export custom glTF extensions defined on an object's `userData.gltfExtensions` property. Default is false.

    Generates a .gltf (JSON) or .glb (binary) output from the input (Scenes or Objects)

    +

    [method:Promise parseAsync]( [param:Object3D input], [param:Object options] )

    + +

    + Generates a .gltf (JSON) or .glb (binary) output from the input (Scenes or Objects). +

    +

    + This is just like the [page:.parse]() method, but instead of + accepting callbacks it returns a promise that resolves with the + result, and otherwise accepts the same options. +

    +

    Source

    diff --git a/docs/examples/en/geometries/ConvexGeometry.html b/docs/examples/en/geometries/ConvexGeometry.html index ddbbd0d087fa71..f767abde8d7d5f 100644 --- a/docs/examples/en/geometries/ConvexGeometry.html +++ b/docs/examples/en/geometries/ConvexGeometry.html @@ -29,10 +29,11 @@

    Import

    Code Example

    - const geometry = new ConvexGeometry( points ); - const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); - const mesh = new THREE.Mesh( geometry, material ); - scene.add( mesh ); + +const geometry = new ConvexGeometry( points ); +const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); +const mesh = new THREE.Mesh( geometry, material ); +scene.add( mesh );

    Examples

    diff --git a/docs/examples/en/geometries/ParametricGeometry.html b/docs/examples/en/geometries/ParametricGeometry.html index e4b5f56fe1e72d..a11d39ba661e4e 100644 --- a/docs/examples/en/geometries/ParametricGeometry.html +++ b/docs/examples/en/geometries/ParametricGeometry.html @@ -29,7 +29,7 @@

    Import

    Code Example

    - const geometry = new THREE.ParametricGeometry( THREE.ParametricGeometries.klein, 25, 25 ); + const geometry = new THREE.ParametricGeometry( klein, 25, 25 ); const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); const klein = new THREE.Mesh( geometry, material ); scene.add( klein ); diff --git a/docs/examples/en/geometries/TeapotGeometry.html b/docs/examples/en/geometries/TeapotGeometry.html new file mode 100644 index 00000000000000..98529d228381ce --- /dev/null +++ b/docs/examples/en/geometries/TeapotGeometry.html @@ -0,0 +1,66 @@ + + + + + + + + + + [page:BufferGeometry] → + +

    [name]

    + +

    + [name] tessellates the famous Utah teapot database by Martin Newell. +

    + +

    Import

    + +

    + [name] is an add-on, and must be imported explicitly. + See [link:#manual/introduction/Installation Installation / Addons]. +

    + + + import { TeapotGeometry } from 'three/addons/geometries/TeapotGeometry.js'; + + +

    Code Example

    + + + const geometry = new TeapotGeometry( 50, 18 ); + const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); + const teapot = new THREE.Mesh( geometry, material ); + scene.add( teapot ); + + +

    Constructor

    + +

    + [name]([param:Integer size], [param:Integer segments], [param:Boolean bottom], [param:Boolean lid], [param:Boolean body], + [param:Boolean fitLid], [param:Boolean blinn]) +

    +

    + size — Relative scale of the teapot. Optional; Defaults to `50`.
    + segments — Number of line segments to subdivide each patch edge. Optional; Defaults to `10`.
    + bottom — Whether the bottom of the teapot is generated or not. Optional; Defaults to `true`.
    + lid — Whether the lid is generated or not. Optional; Defaults to `true`.
    + body — Whether the body is generated or not. Optional; Defaults to `true`.
    + fitLid — Whether the lid is slightly stretched to prevent gaps between the body and lid or not. Optional; Defaults to `true`.
    + blinn — Whether the teapot is scaled vertically for better aesthetics or not. Optional; Defaults to `true`. +

    + +

    Properties

    +

    See the base [page:BufferGeometry] class for common properties.

    + +

    Methods

    +

    See the base [page:BufferGeometry] class for common methods.

    + +

    Source

    + +

    + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/geometries/TeapotGeometry.js examples/jsm/geometries/TeapotGeometry.js] +

    + + diff --git a/docs/examples/en/geometries/TextGeometry.html b/docs/examples/en/geometries/TextGeometry.html index c4eb6c29b6ba8b..76f096b9c8d519 100644 --- a/docs/examples/en/geometries/TextGeometry.html +++ b/docs/examples/en/geometries/TextGeometry.html @@ -38,7 +38,7 @@

    Code Example

    const geometry = new TextGeometry( 'Hello three.js!', { font: font, size: 80, - height: 5, + depth: 5, curveSegments: 12, bevelEnabled: true, bevelThickness: 10, @@ -64,7 +64,7 @@

    [name]([param:String text], [param:Object parameters])

    • font — an instance of THREE.Font.
    • size — Float. Size of the text. Default is 100.
    • -
    • height — Float. Thickness to extrude text. Default is 50.
    • +
    • depth — Float. Thickness to extrude text. Default is 50.
    • curveSegments — Integer. Number of points on the curves. Default is 12.
    • bevelEnabled — Boolean. Turn on bevel. Default is False.
    • bevelThickness — Float. How deep into text bevel goes. Default is 10.
    • diff --git a/docs/examples/en/lines/Line2.html b/docs/examples/en/lines/Line2.html new file mode 100644 index 00000000000000..41c484d2a7690a --- /dev/null +++ b/docs/examples/en/lines/Line2.html @@ -0,0 +1,65 @@ + + + + + + + + + + [page:Object3D] → [page:Mesh] → [page:LineSegments2] → + +

      [name]

      + +

      + A polyline drawn between vertices. +

      + +

      + This adds functionality beyond [page:Line], like arbitrary line width and changing width to be in world units. + It extends [page:LineSegments2], simplifying constructing segments from a chain of points. +

      + +

      Import

      + +

      + [name] is an add-on, and therefore must be imported explicitly. + See [link:#manual/introduction/Installation Installation / Addons]. +

      + + + import { Line2 } from 'three/addons/lines/Line2.js'; + + +

      Examples

      + +

      + [example:webgl_lines_fat WebGL / lines / fat ]
      + [example:webgl_lines_fat_raycasting WebGL / lines / fat / raycasting ]
      + [example:webgpu_lines_fat WebGPU / lines / fat / raycasting ] +

      + +

      Constructor

      + +

      [name]( [param:LineGeometry geometry], [param:LineMaterial material] )

      +

      + [page:LineGeometry geometry] — (optional) Pair(s) of vertices representing each line segment.
      + [page:Material material] — (optional) Material for the line. Default is a [page:LineMaterial] with random color. +

      + +

      Properties

      +

      See the base [page:LineSegments2] class for common properties.

      + +

      [property:Boolean isLine2]

      +

      Read-only flag to check if a given object is of type [name].

      + +

      Methods

      +

      See the base [page:LineSegments2] class for common methods.

      + +

      Source

      + +

      + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/lines/Line2.js examples/jsm/lines/Line2.js] +

      + + diff --git a/docs/examples/en/lines/LineGeometry.html b/docs/examples/en/lines/LineGeometry.html new file mode 100644 index 00000000000000..127f9da1ae4d4a --- /dev/null +++ b/docs/examples/en/lines/LineGeometry.html @@ -0,0 +1,90 @@ + + + + + + + + + + [page:BufferGeometry] → [page:InstancedBufferGeometry] → [page:LineSegmentsGeometry] → + +

      [name]

      + +

      + A chain of vertices, forming a polyline. +

      + +

      + This is used in [page:Line2] to describe the shape. +

      + +

      Import

      + +

      + [name] is an add-on, and therefore must be imported explicitly. + See [link:#manual/introduction/Installation Installation / Addons]. +

      + + + import { LineGeometry } from 'three/addons/lines/LineGeometry.js'; + + +

      Examples

      + +

      + [example:webgl_lines_fat WebGL / lines / fat ]
      + [example:webgl_lines_fat_raycasting WebGL / lines / fat / raycasting ]
      + [example:webgpu_lines_fat WebGPU / lines / fat / raycasting ] +

      + +

      Constructor

      + +

      [name]()

      +

      + Creates a new geometry. + Call [page:LineGeometry.setPositions setPositions] to add segments. +

      + +

      Properties

      +

      See the base [page:LineSegmentsGeometry] class for common properties.

      + +

      [property:Boolean isLineGeometry]

      +

      Read-only flag to check if a given object is of type [name].

      + +

      Methods

      +

      See the base [page:LineSegmentsGeometry] class for common methods.

      + +

      [method:this fromLine]( [param:Line line] )

      +

      + Copy the vertex positions of a [page:Line] object into this geometry. + Assumes the source geometry is not using indices. +

      + +

      [method:this setColors]( [param:Array array] )

      +

      + Replace the per-vertex colors. + Every triple describes a line vertex: `[r1, g1, b1]`. + The array can be an `Array` or `Float32Array`. +

      + +

      [method:this setPositions]( [param:Array array] )

      +

      + Replace the vertex positions with a new set. + The array can be an `Array` or `Float32Array`. + The length must be a multiple of three. +

      + +

      [method:this setFromPoints]( [param:Array points] )

      +

      + Replace the vertex positions with an array of points. + Can be either a `Vector3` or `Vector2` array. +

      + +

      Source

      + +

      + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/lines/LineGeometry.js examples/jsm/lines/LineGeometry.js] +

      + + diff --git a/docs/examples/en/lines/LineMaterial.html b/docs/examples/en/lines/LineMaterial.html new file mode 100644 index 00000000000000..fee2a764a28a6e --- /dev/null +++ b/docs/examples/en/lines/LineMaterial.html @@ -0,0 +1,92 @@ + + + + + + + + + + [page:Material] → [page:ShaderMaterial] → + +

      [name]

      + +

      + A material for drawing wireframe-style geometries. + Unlike [page:LineBasicMaterial], it supports arbitrary line widths and allows using world units instead of screen space units. + This material is used with [page:LineSegments2] and [page:Line2]. +

      + +

      + Lines are always rendered with round caps and round joints. +

      + +

      Examples

      +

      + [example:webgl_lines_fat WebGL / lines / fat ]
      + [example:webgl_lines_fat_raycasting WebGL / lines / fat / raycasting ]
      + [example:webgl_lines_fat_wireframe WebGL / lines / fat / wireframe ]
      + [example:webgpu_lines_fat WebGPU / lines / fat / raycasting ] +

      + +

      Constructor

      +

      [name]( [param:Object parameters] )

      + +

      + [page:Object parameters] - (optional) an object with one or more properties defining the material's appearance. + Any property of the material (including any property inherited from [page:ShaderMaterial]) can be passed in here. +

      + +

      + The exception is the property [page:Hexadecimal color], which can be passed in as a number or hexadecimal string and is `0xffffff` (white) by default. + [page:Color.set]( color ) is called internally. +

      + +

      Properties

      +

      See the base [page:ShaderMaterial] class for common properties.

      + +

      [property:Color color]

      +

      [page:Color] of the material, by default set to white (0xffffff).

      + +

      [property:Boolean dashed]

      +

      Whether the line is dashed, or solid. Default is `false`.

      + +

      [property:number dashOffset]

      +

      Where in the dash cycle the dash starts. Default is `0`.

      + +

      [property:number dashScale]

      +

      The scale of the dashes and gaps. Default is `1`.

      + +

      [property:number dashSize]

      +

      The size of the dash. Default is `1`.

      + +

      [property:number gapSize]

      +

      The size of the gap. Default is `1`.

      + +

      [property:Float linewidth]

      +

      Controls line thickness in CSS pixel units when [page:worldUnits] is `false` (default), or in world units when [page:worldUnits] is `true`. Default is `1`.

      + +

      [property:Vector2 resolution]

      +

      + The size of the viewport, in screen pixels. + This must be kept updated to make screen-space rendering accurate. + The [page:LineSegments2.onBeforeRender] callback performs the update for visible objects. + Default is `[1, 1]`. +

      + +

      [property:Boolean worldUnits]

      +

      + Whether the material's sizes (width, dash gaps) are in world units. + Default is `false` (screen space units.) +

      + +

      Methods

      +

      See the base [page:ShaderMaterial] class for common methods.

      + +

      Source

      + +

      + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/lines/LineMaterial.js examples/jsm/lines/LineMaterial.js] +

      + + diff --git a/docs/examples/en/lines/LineSegments2.html b/docs/examples/en/lines/LineSegments2.html new file mode 100644 index 00000000000000..ae32f9d8d2f17c --- /dev/null +++ b/docs/examples/en/lines/LineSegments2.html @@ -0,0 +1,69 @@ + + + + + + + + + + [page:Object3D] → [page:Mesh] → + +

      [name]

      + +

      + A series of lines drawn between pairs of vertices. +

      + +

      + This adds functionality beyond [page:LineSegments], like arbitrary line width and changing width to be in world units. + The [page:Line2] extends this object, forming a polyline instead of individual segments. +

      + +

      Import

      + +

      + [name] is an add-on, and therefore must be imported explicitly. + See [link:#manual/introduction/Installation Installation / Addons]. +

      + + + import { LineSegments2 } from 'three/addons/lines/LineSegments2.js'; + + +

      Example

      + +

      [example:webgl_lines_fat_raycasting WebGL / lines / fat / raycasting ]

      + +

      Constructor

      + +

      [name]( [param:LineSegmentsGeometry geometry], [param:LineMaterial material] )

      +

      + [page:LineSegmentsGeometry geometry] — (optional) Pair(s) of vertices representing each line segment.
      + [page:Material material] — (optional) Material for the line. Default is a [page:LineMaterial] with random color. +

      + +

      Properties

      +

      See the base [page:Mesh] class for common properties.

      + +

      [property:Boolean isLineSegments2]

      +

      Read-only flag to check if a given object is of type [name].

      + +

      Methods

      +

      See the base [page:Mesh] class for common methods.

      + +

      [method:undefined onBeforeRender]( [param:WebGLRenderer renderer] )

      +

      + Called by the framework to update the material's resolution property, needed for screen-scaled widths. +

      +

      + If your object is not visible to a camera (e.g. by [page:Object3D.layers layers] or [page:Object3D.visible visible],) you must call this manually whenever the viewport changes. +

      + +

      Source

      + +

      + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/lines/LineSegments2.js examples/jsm/lines/LineSegments2.js] +

      + + diff --git a/docs/examples/en/lines/LineSegmentsGeometry.html b/docs/examples/en/lines/LineSegmentsGeometry.html new file mode 100644 index 00000000000000..56e81af1859ec7 --- /dev/null +++ b/docs/examples/en/lines/LineSegmentsGeometry.html @@ -0,0 +1,103 @@ + + + + + + + + + + [page:BufferGeometry] → [page:InstancedBufferGeometry] → + +

      [name]

      + +

      + A series of vertex pairs, forming line segments. +

      + +

      + This is used in [page:LineSegments2] to describe the shape. +

      + +

      Import

      + +

      + [name] is an add-on, and therefore must be imported explicitly. + See [link:#manual/introduction/Installation Installation / Addons]. +

      + + + import { LineSegmentsGeometry } from 'three/addons/lines/LineSegmentsGeometry.js'; + + +

      Example

      + +

      [example:webgl_lines_fat_raycasting WebGL / lines / fat / raycasting ]

      + +

      Constructor

      + +

      [name]()

      +

      + Creates a new geometry. + Call [page:LineSegmentsGeometry.setPositions setPositions] to add segments. +

      + +

      Properties

      +

      See the base [page:InstancedBufferGeometry] class for common properties.

      + +

      [property:Boolean isLineSegmentsGeometry]

      +

      Read-only flag to check if a given object is of type [name].

      + +

      Methods

      +

      See the base [page:Mesh] class for common methods.

      + +

      [method:this fromEdgesGeometry]( [param:EdgesGeometry geometry] )

      +

      + Copy the vertex positions of an edge geometry into this geometry. +

      + +

      [method:this fromLineSegments]( [param:LineSegments lineSegments] )

      +

      + Copy the vertex positions of a [page:LineSegments] object into this geometry. + Assumes the source geometry is not using indices. +

      + +

      [method:this fromMesh]( [param:Mesh mesh] )

      +

      + Copy the vertex positions of a mesh object into this geometry. +

      + +

      [method:this fromWireframeGeometry]( [param:WireframeGeometry geometry] )

      +

      + Copy the vertex positions of a wireframe geometry into this geometry. +

      + +

      [method:this setColors]( [param:Array array] )

      +

      + Replace the per-vertex colors. + Every sixtuple describes a segment: `[r1, g1, b1, r2, g2, b2]`. + The array can be an `Array` or `Float32Array`. +

      + +

      [method:this setPositions]( [param:Array array] )

      +

      + Replace the vertex positions with a new set. + The array can be an `Array` or `Float32Array`. + The length must be a multiple of six. +

      +

      + See also [page:LineSegmentsGeometry.positions positions]. +

      + +

      [method:undefined toJSON]()

      +

      + Unimplemented. +

      + +

      Source

      + +

      + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/lines/LineSegmentsGeometry.js examples/jsm/lines/LineSegmentsGeometry.js] +

      + + diff --git a/docs/examples/en/loaders/3DMLoader.html b/docs/examples/en/loaders/3DMLoader.html index 5661da3e5725f0..21eeb5834aef13 100644 --- a/docs/examples/en/loaders/3DMLoader.html +++ b/docs/examples/en/loaders/3DMLoader.html @@ -14,7 +14,7 @@

      [name]

      A loader for Rhinoceros 3d files and objects.

      Rhinoceros is a 3D modeler used to create, edit, analyze, document, render, animate, and translate NURBS curves, surfaces, breps, extrusions, point clouds, as well as polygon meshes and SubD objects. [link:https://github.com/mcneel/rhino3dm rhino3dm.js] is compiled to WebAssembly from the open source geometry library [link:https://github.com/mcneel/opennurbs openNURBS]. - The loader currently uses [link:https://www.npmjs.com/package/rhino3dm/v/0.15.0-beta rhino3dm.js 0.15.0-beta.] + The loader currently uses [link:https://www.npmjs.com/package/rhino3dm/v/8.4.0 rhino3dm.js 8.4.0.]

      Import

      @@ -44,7 +44,7 @@

      Supported Conversions

      [page:Points Points] - PointSet + PointSet / PointCloud [page:Points Points] @@ -95,58 +95,34 @@

      Supported Conversions

      File3dm [page:Object3D Object3D] 4 + + Material / Physically Based Material + [page:MeshPhysicalMaterial MeshPhysicalMaterial] + +

      Notes:

      +

      1 NURBS curves are discretized to a hardcoded resolution.

      - 2 Types which are based on BREPs and NURBS surfaces are represented with their "Render Mesh". Render meshes might not be associated with these objects if they have not been displayed in an appropriate display mode in Rhino (i.e. "Shaded", "Rendered", etc), or are created programmatically, for example, via Grasshopper or directly with the rhino3dm library. + 2 Types which are based on BREPs and NURBS surfaces are represented with their "Render Mesh". Render meshes might not be associated with these objects if they have not been displayed in an appropriate display mode in Rhino (i.e. "Shaded", "Rendered", etc), or are created programmatically, for example, via Grasshopper or directly with the rhino3dm library. As of rhino3dm.js@8.0.0-beta2, BrepFace and Extrusions can be assigned a mesh representation, but these must be generated by the user.

      3 SubD objects are represented by subdividing their control net.

      - 4 Whether a Rhino Document (File3dm) is loaded or parsed, the returned object is an [page:Object3D Object3D] with all Rhino objects (File3dmObject) as children. + 4 Whether a Rhino Document (File3dm) is loaded or parsed, the returned object is an [page:Object3D Object3D] with all Rhino objects (File3dmObject) as children. File3dm layers and other file level properties are added to the resulting object's userData.

      5 All resulting three.js objects have useful properties from the Rhino object (i.e. layer index, name, etc.) populated in their userData object.

      - -

      Code Example

      - - - // Instantiate a loader - const loader = new Rhino3dmLoader(); - - // Specify path to a folder containing WASM/JS libraries or a CDN. - //loader.setLibraryPath( '/path_to_library/rhino3dm/' ); - loader.setLibraryPath( 'https://cdn.jsdelivr.net/npm/rhino3dm@7.15.0/' ); - - // Load a 3DM file - loader.load( - // resource URL - 'model.3dm', - // called when the resource is loaded - function ( object ) { - - scene.add( object ); - - }, - // called as loading progresses - function ( xhr ) { - - console.log( ( xhr.loaded / xhr.total * 100 ) + '% loaded' ); - - }, - // called when loading has errors - function ( error ) { - - console.log( 'An error happened' ); - - } - ); - +

      + 6 Rhino and Three.js have a different coordinate system. Upon import, you should rotate the resulting [page:Object3D Object3D] by -90º in x or set the THREE.Object3D.DEFAULT_UP at the beginning of your application: + THREE.Object3D.DEFAULT_UP.set( 0, 0, 1 ); + Keep in mind that this will affect the orientation of all of the Object3Ds in your application. +

      Examples

      @@ -172,7 +148,7 @@

      Properties

      Methods

      See the base [page:Loader] class for common methods.

      -

      [method:undefined load]( [param:String url], [param:Function onLoad], [param:Function onProgress], [param:Function onError] )

      +

      [method:Object3D load]( [param:String url], [param:Function onLoad], [param:Function onProgress], [param:Function onError] )

      [page:String url] — A string containing the path/URL of the `.3dm` file.
      [page:Function onLoad] — A function to be called after the loading is successfully completed.
      @@ -183,7 +159,41 @@

      [method:undefined load]( [param:String url], [param:Function onLoad], [param Begin loading from url and call the `onLoad` function with the resulting Object3d.

      -

      [method:undefined parse]( [param:ArrayBuffer buffer], [param:Function onLoad], [param:Function onProgress], [param:Function onError] )

      + + // Instantiate a loader + const loader = new Rhino3dmLoader(); + + // Specify path to a folder containing WASM/JS libraries or a CDN. + // For example, /jsm/libs/rhino3dm/ is the location of the library inside the three.js repository + // loader.setLibraryPath( '/path_to_library/rhino3dm/' ); + loader.setLibraryPath( 'https://cdn.jsdelivr.net/npm/rhino3dm@8.4.0/' ); + + // Load a 3DM file + loader.load( + // resource URL + 'model.3dm', + // called when the resource is loaded + function ( object ) { + + scene.add( object ); + + }, + // called as loading progresses + function ( xhr ) { + + console.log( ( xhr.loaded / xhr.total * 100 ) + '% loaded' ); + + }, + // called when loading has errors + function ( error ) { + + console.log( 'An error happened' ); + + } + ); + + +

      [method:Object3D parse]( [param:ArrayBuffer buffer], [param:Function onLoad], [param:Function onProgress], [param:Function onError] )

      [page:ArrayBuffer buffer] — An ArrayBuffer representing the Rhino `File3dm` document.
      [page:Function onLoad] — A function to be called after the loading is successfully completed.
      @@ -191,39 +201,35 @@

      [method:undefined parse]( [param:ArrayBuffer buffer], [param:Function onLoad

      Parse a File3dm ArrayBuffer and call the `onLoad` function with the resulting Object3d. - See [link:https://github.com/mcneel/rhino-developer-samples/tree/7/rhino3dm/js/SampleParse3dmObjects this example] for further reference. + See [link:https://github.com/mcneel/rhino-developer-samples/tree/8/rhino3dm/js/SampleParse3dmObjects this example] for further reference.

      - import rhino3dm from 'https://cdn.jsdelivr.net/npm/rhino3dm@7.15.0/rhino3dm.module.js' + import rhino3dm from 'https://cdn.jsdelivr.net/npm/rhino3dm@8.4.0' // Instantiate a loader const loader = new Rhino3dmLoader(); // Specify path to a folder containing WASM/JS libraries or a CDN. - loader.setLibraryPath( 'https://cdn.jsdelivr.net/npm/rhino3dm@7.15.0/' ); - - rhino3dm().then(async m => { - - console.log('Loaded rhino3dm.'); - const rhino = m; // global + loader.setLibraryPath( 'https://cdn.jsdelivr.net/npm/rhino3dm@8.4.0' ); - // create Rhino Document and add a point to it - const doc = new rhino.File3dm(); - const ptA = [0, 0, 0]; - const point = new rhino.Point( ptA ); - doc.objects().add( point, null ); + const rhino = await rhino3dm(); + console.log('Loaded rhino3dm.'); - // create a copy of the doc.toByteArray data to get an ArrayBuffer - const buffer = new Uint8Array( doc.toByteArray() ).buffer; + // create Rhino Document and add a point to it + const doc = new rhino.File3dm(); + const ptA = [0, 0, 0]; + const point = new rhino.Point( ptA ); + doc.objects().add( point, null ); - loader.parse( buffer, function ( object ) { + // create a copy of the doc.toByteArray data to get an ArrayBuffer + const buffer = new Uint8Array( doc.toByteArray() ).buffer; - scene.add( object ); + loader.parse( buffer, function ( object ) { - } ); + scene.add( object ); - }) + } ); @@ -238,7 +244,7 @@

      [method:this setLibraryPath]( [param:String value] )

      // Specify path to a folder containing the WASM/JS library: loader.setLibraryPath( '/path_to_library/rhino3dm/' ); // or from a CDN: - loader.setLibraryPath( 'https://cdn.jsdelivr.net/npm/rhino3dm@7.15.0/' ); + loader.setLibraryPath( 'https://cdn.jsdelivr.net/npm/rhino3dm@8.4.0' );

      [method:this setWorkerLimit]( [param:Number workerLimit] )

      diff --git a/docs/examples/en/loaders/GLTFLoader.html b/docs/examples/en/loaders/GLTFLoader.html index 5e276132e78aed..619ed3294d2a38 100644 --- a/docs/examples/en/loaders/GLTFLoader.html +++ b/docs/examples/en/loaders/GLTFLoader.html @@ -46,6 +46,7 @@

      Extensions

      • KHR_draco_mesh_compression
      • KHR_materials_clearcoat
      • +
      • KHR_materials_dispersion
      • KHR_materials_ior
      • KHR_materials_specular
      • KHR_materials_transmission
      • @@ -53,7 +54,7 @@

        Extensions

      • KHR_materials_unlit
      • KHR_materials_volume
      • KHR_mesh_quantization
      • -
      • KHR_lights_punctual1
      • +
      • KHR_lights_punctual
      • KHR_texture_basisu
      • KHR_texture_transform
      • EXT_texture_webp
      • @@ -66,16 +67,12 @@

        Extensions

          -
        • [link:https://github.com/takahirox/three-gltf-extensions KHR_materials_variants]2
        • +
        • [link:https://github.com/takahirox/three-gltf-extensions KHR_materials_variants]1
        • [link:https://github.com/takahirox/three-gltf-extensions MSFT_texture_dds]

        - 1Requires [link:https://threejs.org/docs/#api/en/renderers/WebGLRenderer.useLegacyLights useLegacyLights] to be disabled. -

        - -

        - 2You can also manually process the extension after loading in your application. See [link:https://threejs.org/examples/#webgl_loader_gltf_variants Three.js glTF materials variants example]. + 1You can also manually process the extension after loading in your application. See [link:https://threejs.org/examples/#webgl_loader_gltf_variants Three.js glTF materials variants example].

        Code Example

        @@ -128,22 +125,12 @@

        Examples

        Textures

        -

        Textures containing color information (.map, .emissiveMap, and .specularMap) always use sRGB colorspace in - glTF, while vertex colors and material properties (.color, .emissive, .specular) use linear colorspace. In a - typical rendering workflow, textures are converted to linear colorspace by the renderer, lighting calculations - are made, then final output is converted back to sRGB and displayed on screen. Unless you need post-processing - in linear colorspace, always configure [page:WebGLRenderer] as follows when using glTF:

        - - - renderer.outputColorSpace = THREE.SRGBColorSpace; - - -

        GLTFLoader will automatically configure textures referenced from a .gltf or .glb file correctly, with the - assumption that the renderer is set up as shown above. When loading textures externally (e.g., using - [page:TextureLoader]) and applying them to a glTF model, colorspace and orientation must be given:

        +

        When loading textures externally (e.g., using [page:TextureLoader]) and applying them to a glTF model, + textures must be configured. Textures referenced from the glTF model are configured automatically by + GLTFLoader.

        - // If texture is used for color information, set colorspace. + // If texture is used for color information (.map, .emissiveMap, .specularMap, ...), set color space texture.colorSpace = THREE.SRGBColorSpace; // UVs use the convention that (0, 0) corresponds to the upper left corner of a texture. @@ -204,7 +191,7 @@

        [method:undefined load]( [param:String url], [param:Function onLoad], [param

        [method:this setDRACOLoader]( [param:DRACOLoader dracoLoader] )

        - [page:DRACOLoader dracoLoader] — Instance of THREE.DRACOLoader, to be used for decoding assets compressed with the KHR_draco_mesh_compression extension. + [page:DRACOLoader dracoLoader] — Instance of DRACOLoader, to be used for decoding assets compressed with the KHR_draco_mesh_compression extension.

        Refer to this [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm/libs/draco#readme readme] for the details of Draco and its decoder. @@ -212,7 +199,7 @@

        [method:this setDRACOLoader]( [param:DRACOLoader dracoLoader] )

        [method:this setKTX2Loader]( [param:KTX2Loader ktx2Loader] )

        - [page:KTX2Loader ktx2Loader] — Instance of THREE.KTX2Loader, to be used for loading KTX2 compressed textures. + [page:KTX2Loader ktx2Loader] — Instance of KTX2Loader, to be used for loading KTX2 compressed textures.

        [method:undefined parse]( [param:ArrayBuffer data], [param:String path], [param:Function onLoad], [param:Function onError] )

        diff --git a/docs/examples/en/loaders/KTX2Loader.html b/docs/examples/en/loaders/KTX2Loader.html index f15530cc58cf02..0eaae0b86d9e61 100644 --- a/docs/examples/en/loaders/KTX2Loader.html +++ b/docs/examples/en/loaders/KTX2Loader.html @@ -41,7 +41,7 @@

        Import

        Code Example

        - var ktx2Loader = new THREE.KTX2Loader(); + var ktx2Loader = new KTX2Loader(); ktx2Loader.setTranscoderPath( 'examples/jsm/libs/basis/' ); ktx2Loader.detectSupport( renderer ); ktx2Loader.load( 'diffuse.ktx2', function ( texture ) { diff --git a/docs/examples/en/loaders/LUT3dlLoader.html b/docs/examples/en/loaders/LUT3dlLoader.html new file mode 100644 index 00000000000000..47ad9813588bcd --- /dev/null +++ b/docs/examples/en/loaders/LUT3dlLoader.html @@ -0,0 +1,84 @@ + + + + + + + + + + [page:Loader] → + +

        [name]

        + +

        + A 3D LUT loader that supports the .3dl file format.
        + Based on the following references: +

        + +
          +
        • [link:http://download.autodesk.com/us/systemdocs/help/2011/lustre/index.html?url=./files/WSc4e151a45a3b785a24c3d9a411df9298473-7ffd.htm,topicNumber=d0e9492]
        • +
        • [link:https://community.foundry.com/discuss/topic/103636/format-spec-for-3dl?mode=Post&postID=895258]
        • +
        + +

        Import

        + +

        + [name] is an add-on, and must be imported explicitly. + See [link:#manual/introduction/Installation Installation / Addons]. +

        + + + import { LUT3dlLoader } from 'three/addons/loaders/LUT3dlLoader.js'; + + +

        Constructor

        + +

        [name]( [param:LoadingManager manager] )

        +

        + [page:LoadingManager manager] — The LoadingManager to use. Defaults to [page:DefaultLoadingManager DefaultLoadingManager]
        +

        +

        + Creates a new [name]. +

        + +

        Properties

        +

        See the base [page:Loader] class for common properties.

        + +

        Methods

        +

        See the base [page:Loader] class for common methods.

        + +

        [method:undefined load]( [param:String url], [param:Function onLoad], [param:Function onProgress], [param:Function onError] )

        +

        + [page:String url] — A string containing the path/URL of the `.3dl` file.
        + [page:Function onLoad] — (optional) A function to be called after the loading is successfully completed. The function receives the result of the [page:Function parse] method.
        + [page:Function onProgress] — (optional) A function to be called while the loading is in progress. The argument will be the XMLHttpRequest instance, which contains [page:Integer total] and [page:Integer loaded] bytes. If the server does not set the Content-Length header; .[page:Integer total] will be 0.
        + [page:Function onError] — (optional) A function to be called if an error occurs during loading. The function receives the error as an argument.
        +

        +

        + Begin loading from url and return the loaded LUT. +

        + +

        [method:Object parse]( [param:String input] )

        +

        + [page:String input] — The 3dl data string.
        +

        +

        + Parse a 3dl data string and fire [page:Function onLoad] callback when complete. The argument to [page:Function onLoad] will be an [page:Object object] containing the following LUT data: [page:Number .size], [page:DataTexture .texture] and [page:Data3DTexture .texture3D]. +

        + +

        [method:this setType]( [param:Number type] )

        +

        + [page:Number type] - The texture type. See the [page:Textures texture constants] page for details.
        +

        +

        + Sets the desired texture type. Only [page:Textures THREE.UnsignedByteType] and [page:Textures THREE.FloatType] are supported. The default is [page:Textures THREE.UnsignedByteType]. +

        + +

        Source

        + +

        + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/loaders/[name].js examples/jsm/loaders/[name].js] +

        + + diff --git a/docs/examples/en/loaders/LUTCubeLoader.html b/docs/examples/en/loaders/LUTCubeLoader.html new file mode 100644 index 00000000000000..f7d459a6ee54ff --- /dev/null +++ b/docs/examples/en/loaders/LUTCubeLoader.html @@ -0,0 +1,83 @@ + + + + + + + + + + [page:Loader] → + +

        [name]

        + +

        + A 3D LUT loader that supports the .cube file format.
        + Based on the following reference: +

        + +
          +
        • [link:https://wwwimages2.adobe.com/content/dam/acom/en/products/speedgrade/cc/pdfs/cube-lut-specification-1.0.pdf]
        • +
        + +

        Import

        + +

        + [name] is an add-on, and must be imported explicitly. + See [link:#manual/introduction/Installation Installation / Addons]. +

        + + + import { LUTCubeLoader } from 'three/addons/loaders/LUTCubeLoader.js'; + + +

        Constructor

        + +

        [name]( [param:LoadingManager manager] )

        +

        + [page:LoadingManager manager] — The LoadingManager to use. Defaults to [page:DefaultLoadingManager DefaultLoadingManager]
        +

        +

        + Creates a new [name]. +

        + +

        Properties

        +

        See the base [page:Loader] class for common properties.

        + +

        Methods

        +

        See the base [page:Loader] class for common methods.

        + +

        [method:undefined load]( [param:String url], [param:Function onLoad], [param:Function onProgress], [param:Function onError] )

        +

        + [page:String url] — A string containing the path/URL of the `.cube` file.
        + [page:Function onLoad] — (optional) A function to be called after the loading is successfully completed. The function receives the result of the [page:Function parse] method.
        + [page:Function onProgress] — (optional) A function to be called while the loading is in progress. The argument will be the XMLHttpRequest instance, which contains [page:Integer total] and [page:Integer loaded] bytes. If the server does not set the Content-Length header; .[page:Integer total] will be 0.
        + [page:Function onError] — (optional) A function to be called if an error occurs during loading. The function receives the error as an argument.
        +

        +

        + Begin loading from url and return the loaded LUT. +

        + +

        [method:Object parse]( [param:String input] )

        +

        + [page:String input] — The cube data string.
        +

        +

        + Parse a cube data string and fire [page:Function onLoad] callback when complete. The argument to [page:Function onLoad] will be an [page:Object object] containing the following LUT data: [page:String .title], [page:Number .size], [page:Vector3 .domainMin], [page:Vector3 .domainMax], [page:DataTexture .texture] and [page:Data3DTexture .texture3D]. +

        + +

        [method:this setType]( [param:Number type] )

        +

        + [page:Number type] - The texture type. See the [page:Textures texture constants] page for details.
        +

        +

        + Sets the desired texture type. Only [page:Textures THREE.UnsignedByteType] and [page:Textures THREE.FloatType] are supported. The default is [page:Textures THREE.UnsignedByteType]. +

        + +

        Source

        + +

        + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/loaders/[name].js examples/jsm/loaders/[name].js] +

        + + diff --git a/docs/examples/en/loaders/MMDLoader.html b/docs/examples/en/loaders/MMDLoader.html deleted file mode 100644 index 89b5c416a7ad10..00000000000000 --- a/docs/examples/en/loaders/MMDLoader.html +++ /dev/null @@ -1,134 +0,0 @@ - - - - - - - - - - [page:Loader] → - -

        [name]

        - -

        A loader for `MMD` resources.

        - [name] creates Three.js Objects from MMD resources as PMD, PMX, VMD, and VPD files. - See [page:MMDAnimationHelper] for MMD animation handling as IK, Grant, and Physics.

        - - If you want raw content of MMD resources, use .loadPMD/PMX/VMD/VPD methods.

        - -

        Import

        - -

        - [name] is an add-on, and must be imported explicitly. - See [link:#manual/introduction/Installation Installation / Addons]. -

        - - - import { MMDLoader } from 'three/addons/loaders/MMDLoader.js'; - - -

        Code Example

        - - - // Instantiate a loader - const loader = new MMDLoader(); - - // Load a MMD model - loader.load( - // path to PMD/PMX file - 'models/mmd/miku.pmd', - // called when the resource is loaded - function ( mesh ) { - - scene.add( mesh ); - - }, - // called when loading is in progresses - function ( xhr ) { - - console.log( ( xhr.loaded / xhr.total * 100 ) + '% loaded' ); - - }, - // called when loading has errors - function ( error ) { - - console.log( 'An error happened' ); - - } - ); - - -

        Examples

        -

        - [example:webgl_loader_mmd]
        - [example:webgl_loader_mmd_pose]
        - [example:webgl_loader_mmd_audio] -

        - -

        Constructor

        - -

        [name]( [param:LoadingManager manager] )

        -

        - [page:LoadingManager manager] — The [page:LoadingManager loadingManager] for the loader to use. Default is [page:LoadingManager THREE.DefaultLoadingManager]. -

        -

        - Creates a new [name]. -

        - -

        Properties

        -

        See the base [page:Loader] class for common properties.

        - -

        Methods

        -

        See the base [page:Loader] class for common methods.

        - -

        [method:undefined load]( [param:String url], [param:Function onLoad], [param:Function onProgress], [param:Function onError] )

        -

        - [page:String url] — A string containing the path/URL of the `.pmd` or `.pmx` file.
        - [page:Function onLoad] — A function to be called after the loading is successfully completed.
        - [page:Function onProgress] — (optional) A function to be called while the loading is in progress. The argument will be the XMLHttpRequest instance, that contains .[page:Integer total] and .[page:Integer loaded] bytes. If the server does not set the Content-Length header; .[page:Integer total] will be 0.
        - [page:Function onError] — (optional) A function to be called if an error occurs during loading. The function receives error as an argument.
        -

        -

        - Begin loading PMD/PMX model file from url and fire the callback function with the parsed [page:SkinnedMesh] containing [page:BufferGeometry] and an array of [page:MeshToonMaterial]. -

        - -

        [method:undefined loadAnimation]( [param:String url], [param:Object3D object], [param:Function onLoad], [param:Function onProgress], [param:Function onError] )

        -

        - [page:String url] — A string or an array of string containing the path/URL of the `.vmd` file(s).If two or more files are specified, they'll be merged.
        - [page:Object3D object] — [page:SkinnedMesh] or [page:Camera]. Clip and its tracks will be fitting to this object.
        - [page:Function onLoad] — A function to be called after the loading is successfully completed.
        - [page:Function onProgress] — (optional) A function to be called while the loading is in progress. The argument will be the XMLHttpRequest instance, that contains .[page:Integer total] and .[page:Integer loaded] bytes.
        - [page:Function onError] — (optional) A function to be called if an error occurs during loading. The function receives error as an argument.
        -

        -

        - Begin loading VMD motion file(s) from url(s) and fire the callback function with the parsed [page:AnimationClip]. -

        - -

        [method:undefined loadWithAnimation]( [param:String modelUrl], [param:String vmdUrl], [param:Function onLoad], [param:Function onProgress], [param:Function onError] )

        -

        - [page:String modelUrl] — A string containing the path/URL of the `.pmd` or `.pmx` file.
        - [page:String vmdUrl] — A string or an array of string containing the path/URL of the `.vmd` file(s).
        - [page:Function onLoad] — A function to be called after the loading is successfully completed.
        - [page:Function onProgress] — (optional) A function to be called while the loading is in progress. The argument will be the XMLHttpRequest instance, that contains .[page:Integer total] and .[page:Integer loaded] bytes.
        - [page:Function onError] — (optional) A function to be called if an error occurs during loading. The function receives error as an argument.
        -

        -

        - Begin loading PMD/PMX model file and VMD motion file(s) from urls and fire the callback function with an [page:Object] containing parsed [page:SkinnedMesh] and [page:AnimationClip] fitting to the [page:SkinnedMesh]. -

        - -

        [method:this setAnimationPath]( [param:String animationPath] )

        -

        - [page:String animationPath] — Base path for loading animation data (VMD/VPD files). -

        -

        - Set the base path for additional resources like textures. -

        - -

        Source

        - -

        - [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/loaders/MMDLoader.js examples/jsm/loaders/MMDLoader.js] -

        - - diff --git a/docs/examples/en/loaders/OBJLoader.html b/docs/examples/en/loaders/OBJLoader.html index f17952c4a50812..3c9034bf4b8ee0 100644 --- a/docs/examples/en/loaders/OBJLoader.html +++ b/docs/examples/en/loaders/OBJLoader.html @@ -45,7 +45,7 @@

        Code Example

        scene.add( object ); }, - // called when loading is in progresses + // called when loading is in progress function ( xhr ) { console.log( ( xhr.loaded / xhr.total * 100 ) + '% loaded' ); diff --git a/docs/examples/en/loaders/PCDLoader.html b/docs/examples/en/loaders/PCDLoader.html index 2161564bef8ccd..a7d6e8e9c861e7 100644 --- a/docs/examples/en/loaders/PCDLoader.html +++ b/docs/examples/en/loaders/PCDLoader.html @@ -50,7 +50,7 @@

        Code Example

        scene.add( points ); }, - // called when loading is in progresses + // called when loading is in progress function ( xhr ) { console.log( ( xhr.loaded / xhr.total * 100 ) + '% loaded' ); diff --git a/docs/examples/en/loaders/PDBLoader.html b/docs/examples/en/loaders/PDBLoader.html index 906fdc1fcc2625..0bf3f273f1aa41 100644 --- a/docs/examples/en/loaders/PDBLoader.html +++ b/docs/examples/en/loaders/PDBLoader.html @@ -46,7 +46,7 @@

        Code Example

        console.log( 'This molecule has ' + json.atoms.length + ' atoms' ); }, - // called when loading is in progresses + // called when loading is in progress function ( xhr ) { console.log( ( xhr.loaded / xhr.total * 100 ) + '% loaded' ); diff --git a/docs/examples/en/loaders/PRWMLoader.html b/docs/examples/en/loaders/PRWMLoader.html deleted file mode 100644 index 2177b838edea7c..00000000000000 --- a/docs/examples/en/loaders/PRWMLoader.html +++ /dev/null @@ -1,118 +0,0 @@ - - - - - - - - - - [page:Loader] → - -

        [name]

        - -

        A loader for loading a `.prwm` resource.
        - Packed Raw WebGL Model is an open-source binary file format for nD geometries specifically designed for - JavaScript and WebGL with a strong focus on fast parsing (from 1ms to 0.1ms in Chrome 59 - on a MBP Late 2013). The parsing of PRWM file is especially fast when the endianness of the file is - the same as the endianness of the client platform. More information - on this [link:https://github.com/kchapelier/PRWM here]. -

        - -

        Import

        - -

        - [name] is an add-on, and must be imported explicitly. - See [link:#manual/introduction/Installation Installation / Addons]. -

        - - - import { PRWMLoader } from 'three/addons/loaders/PRWMLoader.js'; - - -

        Code Example

        - - - // instantiate a loader - const loader = new PRWMLoader(); - - // load a resource - loader.load( - // resource URL - 'models/nefertiti.le.prwm', - // called when resource is loaded - function ( bufferGeometry ) { - - const object = new THREE.Mesh( bufferGeometry, new THREE.MeshNormalMaterial() ); - scene.add( object ); - - }, - // called when loading is in progresses - function ( xhr ) { - - console.log( ( xhr.loaded / xhr.total * 100 ) + '% loaded' ); - - }, - // called when loading has errors - function ( error ) { - - console.log( 'An error happened' ); - - } - ); - - -

        Examples

        -

        - [example:webgl_loader_prwm] -

        - -

        Constructor

        - -

        [name]( [param:LoadingManager manager] )

        -

        - [page:LoadingManager manager] — The [page:LoadingManager loadingManager] for the loader to use. Default is [page:LoadingManager THREE.DefaultLoadingManager]. -

        -

        - Creates a new [name]. -

        - -

        Properties

        -

        See the base [page:Loader] class for common properties.

        - -

        Methods

        -

        See the base [page:Loader] class for common methods.

        - -

        [method:undefined load]( [param:String url], [param:Function onLoad], [param:Function onProgress], [param:Function onError] )

        -

        - [page:String url] — A string containing the path/URL of the `.prwm` file. Any `*` character in the URL will be automatically replaced by `le` or `be` depending on the platform endianness.
        - [page:Function onLoad] — (optional) A function to be called after the loading is successfully completed. The function receives the loaded [page:BufferGeometry] as an argument.
        - [page:Function onProgress] — (optional) A function to be called while the loading is in progress. The function receives a XMLHttpRequest instance, which contains [page:Integer total] and [page:Integer loaded] bytes. If the server does not set the Content-Length header; .[page:Integer total] will be 0.
        - [page:Function onError] — (optional) A function to be called if an error occurs during loading. The function receives error as an argument.
        -

        -

        - Begin loading from url and call onLoad with the parsed response content. -

        - -

        [method:BufferGeometry parse]( [param:ArrayBuffer arrayBuffer] )

        -

        - [page:ArrayBuffer arrayBuffer] — ArrayBuffer containing the `prwm` data. -

        -

        - Parse a `prwm` file passed as an ArrayBuffer and directly return an instance of [page:BufferGeometry]. -

        - -

        PRWMLoader.isBigEndianPlatform( )

        - -

        - Return true if the endianness of the platform is Big Endian, false otherwise. -

        - -

        Source

        - -

        - [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/loaders/PRWMLoader.js examples/jsm/loaders/PRWMLoader.js] -

        - - - diff --git a/docs/examples/en/loaders/SVGLoader.html b/docs/examples/en/loaders/SVGLoader.html index c99f1c8a1dbf0d..63d94d47f3bace 100644 --- a/docs/examples/en/loaders/SVGLoader.html +++ b/docs/examples/en/loaders/SVGLoader.html @@ -68,7 +68,7 @@

        Code Example

        scene.add( group ); }, - // called when loading is in progresses + // called when loading is in progress function ( xhr ) { console.log( ( xhr.loaded / xhr.total * 100 ) + '% loaded' ); diff --git a/docs/examples/en/loaders/TGALoader.html b/docs/examples/en/loaders/TGALoader.html index 391b931e28db9d..bd53edd826839b 100644 --- a/docs/examples/en/loaders/TGALoader.html +++ b/docs/examples/en/loaders/TGALoader.html @@ -42,7 +42,7 @@

        Code Example

        console.log( 'Texture is loaded' ); }, - // called when the loading is in progresses + // called when the loading is in progress function ( xhr ) { console.log( ( xhr.loaded / xhr.total * 100 ) + '% loaded' ); diff --git a/docs/examples/en/math/MeshSurfaceSampler.html b/docs/examples/en/math/MeshSurfaceSampler.html index ceb92ecd2d0141..6f0bfbc8336516 100644 --- a/docs/examples/en/math/MeshSurfaceSampler.html +++ b/docs/examples/en/math/MeshSurfaceSampler.html @@ -80,9 +80,9 @@

        [method:this build]()

        Processes the input geometry and prepares to return samples. Any configuration of the geometry or sampler must occur before this method is called. Time complexity is O(n) for a surface with n faces.

        -

        [method:this sample]( [param:Vector3 targetPosition], [param:Vector3 targetNormal], [param:Color targetColor] )

        +

        [method:this sample]( [param:Vector3 targetPosition], [param:Vector3 targetNormal], [param:Color targetColor], [param:Vector2 targetUV] )

        - Selects a random point on the surface of the input geometry, returning the position and optionally the normal vector and color at that point. Time complexity is O(log n) for a surface with n faces.

        + Selects a random point on the surface of the input geometry, returning the position and optionally the normal vector, color and UV Coordinate at that point. Time complexity is O(log n) for a surface with n faces.

        Source

        diff --git a/docs/examples/en/math/OBB.html b/docs/examples/en/math/OBB.html index 0ffd648f29a1c2..cd0149d1c7681b 100644 --- a/docs/examples/en/math/OBB.html +++ b/docs/examples/en/math/OBB.html @@ -148,6 +148,14 @@

        [method:Boolean intersectsOBB]( [param:OBB obb], [param:Number epsilon] ) +

        [method:Boolean intersectsPlane]( [param:Plane plane] )

        +

        + [page:Plane plane] — The plane to test. +

        +

        + Whether the given plane intersects this [name] or not. +

        +

        [method:Boolean intersectsRay]( [param:Ray ray] )

        [page:Ray ray] — The ray to test. diff --git a/docs/examples/en/math/convexhull/ConvexHull.html b/docs/examples/en/math/convexhull/ConvexHull.html index 81c74ab0fab518..2cbacc97a25f7e 100644 --- a/docs/examples/en/math/convexhull/ConvexHull.html +++ b/docs/examples/en/math/convexhull/ConvexHull.html @@ -86,7 +86,7 @@

        [method:this addNewFaces]( [param:VertexNode eyeVertex], [param:HalfEdge hor

        [method:this addVertexToFace]( [param:VertexNode vertex], [param:Face face] )

        - [page:VertexNodeNode vertex] - The vertex to add.
        + [page:VertexNode vertex] - The vertex to add.
        [page:Face face] - The target face.

        Adds a vertex to the 'assigned' list of vertices and assigns it to the given face. @@ -156,7 +156,7 @@

        [method:Vector3 intersectRay]( [param:Ray ray], [param:Vector3 target] )

        [page:Vector3 target] - The target vector representing the intersection point.

        - Performs a ray intersection test with this convext hull. If no intersection is found, `null` is returned. + Performs a ray intersection test with this convex hull. If no intersection is found, `null` is returned.

        [method:Boolean intersectsRay]( [param:Ray ray] )

        @@ -210,7 +210,7 @@

        [method:this setFromObject]( [param:Object3D object] )

        [page:Object3D object] - [page:Object3D] to compute the convex hull of.

        - Computes the convex hull of an [page:Object3D] (including its children),accounting for the world transforms of both the object and its childrens. + Computes the convex hull of an [page:Object3D] (including its children),accounting for the world transforms of both the object and its children.

        [method:this setFromPoints]( [param:Array points] )

        diff --git a/docs/examples/en/misc/Timer.html b/docs/examples/en/misc/Timer.html new file mode 100644 index 00000000000000..71788b612808c4 --- /dev/null +++ b/docs/examples/en/misc/Timer.html @@ -0,0 +1,120 @@ + + + + + + + + + + +

        [name]

        + +

        + This class is an alternative to [page:Clock] with a different API design and behavior. + The goal is to avoid the conceptual flaws that became apparent in [page:Clock] over time. + +

          +
        • [name] has an [page:.update]() method that updates its internal state. That makes it possible to call [page:.getDelta]() and [page:.getElapsed]() multiple times per simulation step without getting different values.
        • +
        • The class can make use of the Page Visibility API to avoid large time delta values when the app is inactive (e.g. tab switched or browser hidden).
        • +
        +

        + +

        Import

        + +

        + [name] is an add-on, and must be imported explicitly. + See [link:#manual/introduction/Installation Installation / Addons]. +

        + + + import { Timer } from 'three/addons/misc/Timer.js'; + + +

        Code Example

        + + + const timer = new Timer(); + + function animate( timestamp ) { + + requestAnimationFrame( animate ); + + // timestamp is optional + timer.update( timestamp ); + + const delta = timer.getDelta(); + + // do something with delta + + renderer.render( scene, camera ); + + } + + +

        Examples

        + +

        + [example:webgl_morphtargets_sphere WebGL / morphtargets / sphere] +

        + +

        Constructor

        + +

        Timer()

        + +

        Methods

        + +

        [method:this connect]( [param:Document document] )

        +

        + Connects the timer to the given document. Calling this method is not mandatory to use the timer + but enables the usage of the Page Visibility API to avoid large time delta values. +

        + +

        [method:this disconnect]()

        +

        + Disconnects the timer from the DOM and also disables the usage of the Page Visibility API. +

        + +

        [method:Number getDelta]()

        +

        + Returns the time delta in seconds. +

        + +

        [method:Number getElapsed]()

        +

        + Returns the elapsed time in seconds. +

        + +

        [method:this setTimescale]( [param:Number timescale] )

        +

        + Sets a time scale that scales the time delta in [page:.update](). +

        + +

        [method:this reset]()

        +

        + Resets the time computation for the current simulation step. +

        + +

        [method:this dispose]()

        +

        + Can be used to free all internal resources. Usually called when the timer instance isn't required anymore. +

        + +

        [method:this update]( [param:Number timestamp] )

        +

        + timestamp -- (optional) The current time in milliseconds. Can be obtained from the + [link:https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame requestAnimationFrame] + callback argument. If not provided, the current time will be determined with + [link:https://developer.mozilla.org/en-US/docs/Web/API/Performance/now performance.now].

        + + Updates the internal state of the timer. This method should be called once per simulation step + and before you perform queries against the timer (e.g. via [page:.getDelta]()). +

        + +

        Source

        + +

        + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/misc/Timer.js examples/jsm/misc/Timer.js] +

        + + diff --git a/docs/examples/en/modifiers/EdgeSplitModifier.html b/docs/examples/en/modifiers/EdgeSplitModifier.html new file mode 100644 index 00000000000000..9584f2e5716e6e --- /dev/null +++ b/docs/examples/en/modifiers/EdgeSplitModifier.html @@ -0,0 +1,67 @@ + + + + + + + + + + +

        [name]

        + +

        + [name] is intended to modify the geometry "dissolving" the edges to give a smoother look. +

        + +

        Import

        + +

        + [name] is an add-on, and therefore must be imported explicitly. + See [link:#manual/introduction/Installation Installation / Addons]. +

        + + + import { EdgeSplitModifier } from 'three/addons/modifiers/EdgeSplitModifier.js'; + + +

        Code Example

        + + + const geometry = new THREE.IcosahedronGeometry( 10, 3 );
        + const modifier = new EdgeSplitModifier();
        + const cutOffAngle = 0.5;
        + const tryKeepNormals = false;
        +
        + modifier.modify( geometry, cutOffAngle, tryKeepNormals ); +
        + +

        Examples

        + +

        [example:webgl_modifier_edgesplit misc / modifiers / EdgeSplit ]

        + +

        Constructor

        + +

        [name]()

        +

        + Create a new [name] object. +

        + +

        Methods

        + +

        [method:undefined modify]( [param:geometry], [param:cutOffAngle], [param:tryKeepNormals] )

        +

        + Using interpolated vertex normals, the mesh faces will blur at the edges and appear smooth.
        + + You can control the smoothness by setting the `cutOffAngle`.
        + + To try to keep the original normals, set `tryKeepNormals` to `true`. +

        + +

        Source

        + +

        + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/modifiers/EdgeSplitModifier.js examples/jsm/modifiers/EdgeSplitModifier.js] +

        + + diff --git a/docs/examples/en/objects/Lensflare.html b/docs/examples/en/objects/Lensflare.html index c0ccfbbdb50caf..f95c2e0f7c9449 100644 --- a/docs/examples/en/objects/Lensflare.html +++ b/docs/examples/en/objects/Lensflare.html @@ -23,7 +23,7 @@

        Import

        - import { Lensflare } from 'three/addons/objects/Lensflare.js'; + import { Lensflare, LensflareElement } from 'three/addons/objects/Lensflare.js';

        Code Example

        diff --git a/docs/examples/en/objects/Sky.html b/docs/examples/en/objects/Sky.html new file mode 100644 index 00000000000000..2908da7606d83a --- /dev/null +++ b/docs/examples/en/objects/Sky.html @@ -0,0 +1,91 @@ + + + + + + + + + + [page:Mesh] → + +

        [name]

        + +

        + [name] creates a ready to go sky environment for your scenes. +

        + +

        Import

        + +

        + [name] is an add-on, and therefore must be imported explicitly. + See [link:#manual/introduction/Installation Installation / Addons]. +

        + + + import { Sky } from 'three/addons/objects/Sky.js'; + + +

        Code Example

        + + + const sky = new Sky();
        + sky.scale.setScalar( 450000 );
        + + const phi = MathUtils.degToRad( 90 );
        + const theta = MathUtils.degToRad( 180 );
        + const sunPosition = new Vector3().setFromSphericalCoords( 1, phi, theta );
        + + sky.material.uniforms.sunPosition.value = sunPosition;
        + + scene.add( sky ); +
        + +

        Examples

        + +

        [example:webgl_shaders_sky misc / objects / Sky ]

        + +

        Constructor

        + +

        [name]()

        +

        + Create a new [name] instance. +

        + +

        Properties

        +

        + [name] instance is a [page:Mesh] with a pre-defined [page:ShaderMaterial], so every property described here should be set using [page:Uniform]s. +

        + +

        [property:Number turbidity]

        +

        + Haziness of the [name]. +

        +

        [property:Number rayleigh]

        +

        + For a more detailed explanation see: [link:https://en.wikipedia.org/wiki/Rayleigh_scattering Rayleigh scattering] . +

        +

        [property:Number mieCoefficient]

        +

        + [link:https://en.wikipedia.org/wiki/Mie_scattering Mie scattering] amount. +

        +

        [property:Number mieDirectionalG]

        +

        + [link:https://en.wikipedia.org/wiki/Mie_scattering Mie scattering] direction. +

        +

        [property:Vector3 sunPosition]

        +

        + The position of the sun. +

        +

        [property:Vector3 up]

        +

        + The sun's elevation from the horizon, in degrees. +

        + +

        Source

        + +

        + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/objects/Sky.js examples/jsm/objects/Sky.js] +

        + + diff --git a/docs/examples/en/postprocessing/EffectComposer.html b/docs/examples/en/postprocessing/EffectComposer.html index 609eaff26debc5..8ee7cee676e4c0 100644 --- a/docs/examples/en/postprocessing/EffectComposer.html +++ b/docs/examples/en/postprocessing/EffectComposer.html @@ -33,13 +33,15 @@

        Examples

        [example:webgl_postprocessing postprocessing]
        [example:webgl_postprocessing_advanced postprocessing advanced]
        [example:webgl_postprocessing_backgrounds postprocessing backgrounds]
        - [example:webgl_postprocessing_crossfade postprocessing crossfade]
        + [example:webgl_postprocessing_transition postprocessing transition]
        [example:webgl_postprocessing_dof postprocessing depth-of-field]
        [example:webgl_postprocessing_dof2 postprocessing depth-of-field 2]
        [example:webgl_postprocessing_fxaa postprocessing fxaa]
        [example:webgl_postprocessing_glitch postprocessing glitch]
        [example:webgl_postprocessing_godrays postprocessing godrays]
        + [example:webgl_postprocessing_gtao postprocessing gtao]
        [example:webgl_postprocessing_masking postprocessing masking]
        + [example:webgl_postprocessing_material_ao postprocessing material ao]
        [example:webgl_postprocessing_outline postprocessing outline]
        [example:webgl_postprocessing_pixel postprocessing pixelate]
        [example:webgl_postprocessing_procedural postprocessing procedural]
        diff --git a/docs/examples/en/utils/BufferGeometryUtils.html b/docs/examples/en/utils/BufferGeometryUtils.html index 1b4fe1f4f83fa5..ff052ba838f367 100644 --- a/docs/examples/en/utils/BufferGeometryUtils.html +++ b/docs/examples/en/utils/BufferGeometryUtils.html @@ -122,11 +122,19 @@

        [method:BufferGeometry mergeVertices]( [param:BufferGeometry geometry], [par

        [method:BufferGeometry toCreasedNormals]( [param:BufferGeometry geometry], [param:Number creaseAngle] )

        +
          +
        • geometry -- The input geometry.
        • +
        • creaseAngle -- The crease angle in radians.
        • +
        +

        - geometry -- The input geometry.
        - creaseAngle -- The crease angle.

        + Modifies the supplied geometry if it is non-indexed, otherwise creates a new, + non-indexed geometry. +

        - Creates a new, non-indexed geometry with smooth normals everywhere except faces that meet at an angle greater than the crease angle. +

        + Returns the geometry with smooth normals everywhere except faces + that meet at an angle greater than the crease angle.

        [method:BufferGeometry toTrianglesDrawMode]( [param:BufferGeometry geometry], [param:TrianglesDrawMode drawMode] )

        diff --git a/docs/examples/en/utils/CameraUtils.html b/docs/examples/en/utils/CameraUtils.html index 5d2876df7c98e4..772ddd2b98de59 100644 --- a/docs/examples/en/utils/CameraUtils.html +++ b/docs/examples/en/utils/CameraUtils.html @@ -21,7 +21,7 @@

        Import

        - import { CameraUtils } from 'three/addons/utils/CameraUtils.js'; + import * as CameraUtils from 'three/addons/utils/CameraUtils.js';

        Methods

        diff --git a/docs/examples/en/utils/SceneUtils.html b/docs/examples/en/utils/SceneUtils.html index 3ad2e2174a29c4..f28ca30f58086c 100644 --- a/docs/examples/en/utils/SceneUtils.html +++ b/docs/examples/en/utils/SceneUtils.html @@ -21,7 +21,7 @@

        Import

        - import { SceneUtils } from 'three/addons/utils/SceneUtils.js'; + import * as SceneUtils from 'three/addons/utils/SceneUtils.js';

        Methods

        @@ -85,6 +85,30 @@

        [method:undefined sortInstancedMesh]( [param:InstancedMesh mesh], [param:Fun and to reduce overdraw in opaque materials (front to back).

        +

        [method:Generator traverseGenerator]( [param:Object3D object] )

        +

        + object -- The 3D object to traverse. +

        +

        + A generator based version of [page:Object3D.traverse](). +

        + +

        [method:Generator traverseVisibleGenerator]( [param:Object3D object] )

        +

        + object -- The 3D object to traverse. +

        +

        + A generator based version of [page:Object3D.traverseVisible](). +

        + +

        [method:Generator traverseAncestorsGenerator]( [param:Object3D object] )

        +

        + object -- The 3D object to traverse. +

        +

        + A generator based version of [page:Object3D.traverseAncestors](). +

        +

        Source

        diff --git a/docs/examples/en/webxr/XREstimatedLight.html b/docs/examples/en/webxr/XREstimatedLight.html new file mode 100644 index 00000000000000..61e7480141854b --- /dev/null +++ b/docs/examples/en/webxr/XREstimatedLight.html @@ -0,0 +1,122 @@ + + + + + + + + + + [page:Group] → + +

        [name]

        + +

        + XREstimatedLight uses WebXR's light estimation to create + a light probe, a directional light, and (optionally) an environment map + that model the user's real-world environment and lighting.
        + As WebXR updates the light and environment estimation, XREstimatedLight + automatically updates the light probe, directional light, and environment map.

        + + It's important to specify `light-estimation` as an optional or required + feature when creating the WebXR session, otherwise the light estimation + can't work.

        + + See here + for browser compatibility information, as this is still an experimental feature in WebXR.

        + + + To use this, as with all files in the /examples directory, you will have to + include the file separately in your HTML. +

        + +

        Import

        + +

        + [name] is an add-on, and must be imported explicitly. + See [link:#manual/introduction/Installation Installation / Addons]. +

        + + + import { XREstimatedLight } from 'three/addons/webxr/XREstimatedLight.js'; + + +

        Code Example

        + + + renderer.xr.enabled = true; + + // Don't add the XREstimatedLight to the scene initially. + // It doesn't have any estimated lighting values until an AR session starts. + const xrLight = new XREstimatedLight( renderer ); + + xrLight.addEventListener( 'estimationstart' , () => { + + scene.add( xrLight ); + + if ( xrLight.environment ) { + + scene.environment = xrLight.environment; + + } + + } ); + + xrLight.addEventListener( 'estimationend', () => { + + scene.remove( xrLight ); + + scene.environment = null; + + } ); + + // In order for lighting estimation to work, 'light-estimation' must be included as either + // an optional or required feature. + document.body.appendChild( XRButton.createButton( renderer, { + optionalFeatures: [ 'light-estimation' ] + } ) ); + + +

        Examples

        + +

        [example:webxr_ar_lighting webxr / light estimation]

        + +

        Constructor

        + +

        [name]( [param:WebGLRenderer renderer], [param:Boolean environmentEstimation] )

        +

        + [page:WebGLRenderer renderer]: (required) The renderer used to render the Scene. Mainly used to interact with WebXRManager.

        + + environmentEstimation: If `true`, use WebXR to estimate an environment map. +

        + +

        Events

        + +

        estimationstart

        +

        + Fires when the estimated lighting values start being updated. +

        + +

        estimationend

        +

        + Fires when the estimated lighting values stop being updated. +

        + +

        Properties

        + +

        [property:Texture environment]

        +

        + The environment map estimated by WebXR. This is only available if environmentEstimation is `true`.

        + + It can be used as the [page:Scene.environment], for + [page:MeshStandardMaterial.envMap], or + as the [page:Scene.background]. +

        + +

        Source

        + +

        + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/webxr/XREstimatedLight.js examples/jsm/webxr/XREstimatedLight.js] +

        + + diff --git a/docs/examples/ko/controls/ArcballControls.html b/docs/examples/ko/controls/ArcballControls.html new file mode 100644 index 00000000000000..bca11893a12080 --- /dev/null +++ b/docs/examples/ko/controls/ArcballControls.html @@ -0,0 +1,290 @@ + + + + + + + + + + + + [page:EventDispatcher] → + +

        슬라이드 볼 컨트롤러([name])

        + +

        + ArcballControls 완전한 터치 지원과 고급 내비게이션 기능을 갖춘 가상 궤적구를 통해 카메라를 제어할 수 있습니다.
        + 커서/손가락 위치와 움직임은 작은 컨트롤로 표시된 가상 궤적구의 표면에 직관적이고 일관된 카메라 이동에 반사됩니다.커서를 드래그하거나 손가락을 드래그하면 사진기가 공의 중심을 중심으로 안정적으로 회전합니다. (원점으로 돌아가면 사진기가 원래 방향으로 돌아가게 됩니다).

        + + 패닝, 확대/축소, 반공 제스처 지원 외에도 arcballcontrols는 두 번의 클릭/클릭을 통해 포커싱 기능을 제공하여, 오브젝트의 관심 지점을 가상 트랙볼의 중심으로 직관적으로 이동시킨다.초점은 복잡한 환경에서도 더 나은 검사와 방향을 제공한다.그밖에 + arcballcontrols는 fov 동작 (아찔한 동작)과 z 축 회전을 허용한다.또한 클립보드를 통해 카메라 상태를 저장하고 복원할 수 있다 (ctrl+c, ctrl+v 단축키를 사용하여 복사 및 붙여넣기 상태).

        + + [page:orbitcontrols], [page:trackballcontrols]와 달리 [name]은 애니메이션이 열릴 때 외부에서 호출될 필요가 없다 [page:.update].

        + + + 이 기능을 사용하려면/examples 디렉터리에 있는 모든 파일과 마찬가지로 html 안에 별도로 포함시켜야 한다.。 +

        + +

        수입

        + +

        + [name] 는 애드온이며 명시적으로 가져와야 합니다. + See [link:#manual/introduction/Installation Installation / Addons]。 +

        + + + import { ArcballControls } from 'three/addons/controls/ArcballControls.js'; + + +

        코드 예시

        + + + const renderer = new THREE.WebGLRenderer(); + renderer.setSize( window.innerWidth, window.innerHeight ); + document.body.appendChild( renderer.domElement ); + + const scene = new THREE.Scene(); + + const camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 10000 ); + + const controls = new ArcballControls( camera, renderer.domElement, scene ); + + controls.addEventListener( 'change', function () { + + renderer.render( scene, camera ); + + } ); + + //controls.update() must be called after any manual changes to the camera's transform + camera.position.set( 0, 20, 100 ); + controls.update(); + + +

        예시

        + +

        [example:misc_controls_arcball misc / controls / arcball ]

        + +

        생성자

        + +

        [name]( [param:Camera camera], [param:HTMLDOMElement domElement], [param:Scene scene] )

        +

        + [page:Camera camera]:(필수) 제어해야 할 카메라입니다.객체가 장면 자체가 아닌 한 카메라는 다른 객체의 하위 객체가 될 수 없습니다.

        + + [page:HTMLDOMElement domElement]: 이벤트 리스너를 위한 HTML 요소.

        + + [page:Scene scene]:카메라가 렌더링한 장면입니다.표시되지 않으면 위젯을 표시할 수 없습니다. +

        + +

        이벤트

        + +

        change

        +

        + 작은 컨트롤이 카메라를 변경할 때 작동합니다. +

        + +

        start

        +

        + 상호작용이 시작될 때 촉발된다. +

        + +

        end

        +

        + 상호 작용이 완료되었을 때 촉발된다. +

        + +

        특성

        + +

        [property:Boolean adjustNearFar]

        +

        + true 라면 확대/축소할 때마다 카메라의 근거리 엔드와 원격 엔드를 조정하여 원래의 근거리 엔드와 원격 엔드의 보이는 부분을 동일하게 유지하려고 한다 ([page:PerspectiveCamera] 제한).기본값은 false입니다. + +

        + +

        [property:Camera camera]

        +

        + 카메라가 조종됩니다. +

        + +

        [property:Boolean cursorZoom]

        +

        + 크기 조정을 조정하려면 true로 설정했습니다. +

        + +

        + [property:Float dampingFactor]

        +

        + [page:.enableanimations] 가 true인 경우 감쇠 관성을 사용한다. +

        + +

        [property:HTMLDOMElement domElement]

        +

        + HTMLDOMElement 마우스/터치 이벤트를 듣는 데 사용합니다.이것은 생성자 (생성자)에 전달되어야 한다.여기에서 변경하면 새 이벤트 리스너가 설정되지 않습니다. +

        + +

        [property:Boolean enabled]

        +

        + 'false'로 설정되면 작은 컨트롤은 더 이상 사용자 상호작용에 응답하지 않습니다.기본값은'true'이다. +

        + +

        [property:Boolean enableAnimations]

        +

        + true로 설정하여 회전 (감쇠)과 초점 맞추기 동작을 위한 애니메이션을 활성화합니다.기본값은 true입니다. +

        + +

        [property:Boolean enableFocus]

        +

        + 더블 탭(또는 클릭) 시 카메라 초점을 맞추는 기능을 활성화하려면 true로 설정합니다. 기본값은 true입니다. +

        + +

        [property:Boolean enableGrid]

        +

        + true로 설정하면 패닝 동작을 할 때 모드가 나타날 것입니다 (데스크톱 상호 작용할 때만).기본값은 false입니다. +

        + +

        [property:Boolean enablePan]

        +

        + 카메라 펴기를 사용하거나 사용하지 않습니다.기본값은 true입니다. +

        + +

        [property:Boolean enableRotate]

        +

        + 카메라 회전을 사용하거나 사용하지 않습니다.기본값은 true입니다. +

        + +

        [property:Boolean enableZoom]

        +

        + 카메라 줌을 사용하거나 사용하지 않습니다. +

        + +

        [property:Float focusAnimationTime]

        +

        + 초점 애니메이션의 지속 시간입니다. +

        + +

        [property:Float maxDistance]

        +

        + 최대 이동 거리(다만 [page:PerspectiveCamera]).무한대로 묵인하다. +

        + +

        [property:Float maxZoom]

        +

        + 최대 배율 값입니다(다만 [page:OrthographicCamera]).무한대로 묵인하다. +

        + +

        [property:Float minDistance]

        +

        + 최소 이동 거리(다만 [page:PerspectiveCamera])。기본값은 0입니다。 +

        + +

        [property:Float minZoom]

        +

        + 최소 크기 조정(다만 [page:OrthographicCamera] )。기본값은 0입니다。 +

        + +

        [property:Float radiusFactor]

        +

        + 화면 너비와 높이에 대한 위젯의 크기입니다. 기본값은 0.67이다。 +

        + +

        [property:Float rotateSpeed]

        +

        + 회전 속도.기본값은 1입니다. +

        + +

        [property:Float scaleFactor]

        +

        + 확대/축소 작업을 수행할 때 사용할 확대/축소 요인입니다. +

        + +

        [property:Scene scene]

        +

        + 카메라가 렌더링한 장면입니다. +

        + +

        [property:Float wMax]

        +

        + 회전 애니메이션을 시작할 때 허용되는 최대 각속도입니다. +

        + + +

        메소드

        + +

        [method:undefined activateGizmos] ( [param:Boolean isActive] )

        +

        + 작은 컨트롤을 어느 정도 보이게 합니다. +

        + +

        [method:undefined copyState] ()

        +

        + 현재 상태를 클립보드에 복사 (읽을 수 있는 json 텍스트로). +

        + +

        [method:undefined dispose] ()

        +

        + 모든 이벤트 리스너를 삭제하고, 처리할 애니메이션을 취소하며, 장면에서 작은 컨트롤과 그리드를 지운다. +

        + +

        [method:undefined pasteState] ()

        +

        + 클립보드에서 컨트롤 상태를 설정합니다. 클립보드가 [page:.copyState]에서 저장된 json 텍스트를 저장한다고 가정한다. +

        + +

        [method:undefined reset] ()

        + 위젯을 마지막 호출 [page:. saveState] 때의 상태나 원래 상태로 초기화합니다. +

        + +

        [method:undefined saveState] ()

        +

        + 컨트롤의 현재 상태를 저장합니다.나중에 [page:.reset]을 통해 다시 시작할 수 있다. +

        + +

        [method:undefined setCamera] ( [param:Camera camera] )

        +

        + 컨트롤할 카메라를 설정합니다.새 카메라를 제어하려면 호출되어야 합니다. +

        + +

        [method:undefined setGizmosVisible] ( [param:Boolean value] )

        +

        + 작은 컨트롤의 보이는 속성을 설정합니다. +

        + +

        [method:undefined setTbRadius] ( [param:Float value] )

        +

        + `radiusFactor` 값을 업데이트하고, 작은 위젯을 다시 그리고 `changeEvent` 를 시각적으로 보낸다. +

        + +

        [method:Boolean setMouseAction] ( [param:String operation], mouse, key )

        +

        + 실행할 동작과 마우스/키 조합을 지정하여 새로운 마우스 동작을 설정합니다.충돌이 발생하면 기존 것을 대체합니다.

        + 작업은'rotate','pan','fov','zoom'으로 지정할 수 있다.
        + 마우스 입력은 마우스 버튼 0, 1, 2 또는'휠'로 지정할 수 있다.
        + 키보드 수정자는'ctrl','shift'또는 null(더 이상 필요하지 않을 경우)로 지정할 수 있다. +

        + +

        [method:Boolean unsetMouseAction] ( mouse, key )

        +

        + 마우스/키 조합을 지정하여 마우스 동작을 삭제합니다.

        + 마우스 입력은 마우스 버튼 0, 1, 2 또는'휠'로 지정할 수 있다.
        + 키보드 수정자는'ctrl','shift'또는 null(더 이상 필요하지 않을 경우)로 지정할 수 있다. +

        + +

        [method:undefined update] ()

        +

        + 컨트롤 업데이트.수동으로 카메라 변경을 한 후에 호출되어야 합니다. +

        + +

        [method:Raycaster getRaycaster] ()

        +

        + 사용자 상호 작용을 위한 [page:raycaster] 객체를 되돌려준다.[name]의 [page:object3d.layer.layers] 속성이 설정되어 있으면 일치하는 값을 사용해야 한다. [page:raycaster.layers + layers]의 [page:raycaster] 속성, 그렇지 않으면 [name]이 작동되지 않는다. +

        + +

        Source

        + +

        + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/controls/ArcballControls.js examples/jsm/controls/ArcballControls.js] +

        + + + diff --git a/docs/examples/ko/controls/DragControls.html b/docs/examples/ko/controls/DragControls.html index 0cbc3ee74224c2..c1a2d1ded33309 100644 --- a/docs/examples/ko/controls/DragControls.html +++ b/docs/examples/ko/controls/DragControls.html @@ -109,6 +109,11 @@

        [property:Boolean transformGroup]

        *true* 값으로 설정 되어있다면, [name]은 개별 객체가 아닌 전체 그룹은 드래그 합니다. 기본 값은 *false* 입니다.

        +

        [property:String mode]

        +

        + The current transformation mode. Possible values are `translate`, and `rotate`. Default is `translate`. +

        +

        메소드

        일반적인 메소드는 [page:EventDispatcher] 클래스를 참조하세요.

        @@ -138,6 +143,11 @@

        [method:Raycaster getRaycaster] ()

        Returns the internal [page:Raycaster] instance that is used for intersection tests.

        +

        [method:undefined setObjects] ( [param:Array objects] )

        +

        + Sets an array of draggable objects by overwriting the existing one. +

        +

        Source

        diff --git a/docs/examples/ko/controls/FlyControls.html b/docs/examples/ko/controls/FlyControls.html index 2e74af2f0f1ff2..0cfea2476c7554 100644 --- a/docs/examples/ko/controls/FlyControls.html +++ b/docs/examples/ko/controls/FlyControls.html @@ -70,6 +70,11 @@

        [property:Boolean dragToLook]

        *true* 으로 설정되어 있을 경우, 드래그 상호 작용을 수행해야만 주변을 둘러볼 수 있습니다. 기본값은 *false* 입니다.

        +

        [property:Boolean enabled]

        +

        + *false*로 설정할 경우, 컨트롤은 사용자 입력에 응답하지 못합니다. 기본값은 *true* 입니다. +

        +

        [property:Number movementSpeed]

        이동 속도를 설정할 수 있습니다. 기본값은 *1* 입니다. diff --git a/docs/examples/ko/controls/MapControls.html b/docs/examples/ko/controls/MapControls.html new file mode 100644 index 00000000000000..51963bd66cdcaf --- /dev/null +++ b/docs/examples/ko/controls/MapControls.html @@ -0,0 +1,122 @@ + + + + + + + + + + + + [page:OrbitControls] → + +

        [name]

        + +

        + [name]은 지도 위에서 조망자의 시각으로 카메라를 이동하기 위해 설계되었습니다. + 이 클래스는 [page:OrbitControls]와 같은 구현을 공유하지만 특정한 마우스/터치 상호작용 프리셋을 사용하고 기본적으로 화면 공간 패닝을 비활성화합니다. +

        + +

        가져오기

        + +

        + [name]은 애드온으로서 명시적으로 가져와야 합니다. + [link:#manual/introduction/Installation 설치 / 애드온]을 참조하십시오. +

        + + + import { MapControls } from 'three/addons/controls/MapControls.js'; + + +

        코드 예제

        + + + const renderer = new THREE.WebGLRenderer(); + renderer.setSize( window.innerWidth, window.innerHeight ); + document.body.appendChild( renderer.domElement ); + + const scene = new THREE.Scene(); + + const camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 10000 ); + camera.position.set( 0, 20, 100 ); + + const controls = new MapControls( camera, renderer.domElement ); + controls.enableDamping = true; + + function animate() { + + requestAnimationFrame( animate ); + + // controls.enableDamping 또는 controls.autoRotate가 true로 설정된 경우 필수 + controls.update(); + + renderer.render( scene, camera ); + + } + + +

        예제

        + +

        [example:misc_controls_map misc / controls / map ]

        + +

        생성자

        + +

        [name]( [param:Camera object], [param:HTMLDOMElement domElement] )

        +

        + [page:Camera object]: (필수) 제어할 카메라입니다. 카메라는 다른 객체의 자식이어서는 안 되며, 그 객체가 스케인 자체가 아니라면 됩니다.

        + + [page:HTMLDOMElement domElement]: 이벤트 리스너에 사용되는 HTML 요소입니다. +

        + +

        이벤트

        + +

        공통 이벤트를 보기 위해 기본 [page:OrbitControls] 클래스를 참조하십시오.

        + +

        속성

        + +

        공통 속성을 보기 위해 기본 [page:OrbitControls] 클래스를 참조하십시오.

        + +

        + [property:Object mouseButtons]

        +

        + 이 객체에는 컨트롤에 의해 사용되는 마우스 동작에 대한 참조가 포함되어 있습니다. + +controls.mouseButtons = { + LEFT: THREE.MOUSE.PAN, + MIDDLE: THREE.MOUSE.DOLLY, + RIGHT: THREE.MOUSE.ROTATE +} + +

        + +

        [property:Boolean screenSpacePanning]

        +

        + 패닝 시 카메라 위치가 어떻게 이동되는지 정의합니다. 만약 true이면, 카메라는 화면 공간에서 패닝합니다. + 그렇지 않으면, 카메라는 카메라의 위 방향에 수직인 평면에서 패닝합니다. + 기본값은 `false`입니다. +

        + +

        [property:Object touches]

        +

        + 이 객체에는 컨트롤에 의해 사용되는 터치 동작에 대한 참조가 포함되어 있습니다. + +controls.touches = { + ONE: THREE.TOUCH.PAN, + TWO: THREE.TOUCH.DOLLY_ROTATE +} + +

        + +

        메서드

        + +

        공통 메서드를 보기 위해 기본 [page:OrbitControls] 클래스를 참조하십시오.

        + +

        소스

        + +

        + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/controls/MapControls.js examples/jsm/controls/MapControls.js] +

        + + + \ No newline at end of file diff --git a/docs/examples/ko/controls/OrbitControls.html b/docs/examples/ko/controls/OrbitControls.html index 9bf86f3e03c2ed..95bd9a843d9de5 100644 --- a/docs/examples/ko/controls/OrbitControls.html +++ b/docs/examples/ko/controls/OrbitControls.html @@ -91,7 +91,8 @@

        특성

        [property:Boolean autoRotate]

        대상 주위를 자동으로 회전하려면 true로 설정합니다.
        autoRotate를 활성할 경우, 애니메이션 루프에서 [page:.update()]를 호출해야 합니다. - + Set to true to automatically rotate around the target.
        Note that if this is enabled, you must call [page:.update] + () in your animation loop. If you want the auto-rotate speed to be independent of the frame rate (the refresh rate of the display), you must pass the time `deltaTime`, in seconds, to [page:.update]().

        [property:Float autoRotateSpeed]

        @@ -269,7 +270,10 @@

        [property:Float zoomSpeed]

        줌잉(Zooming)과 달링(Dollying)의 속도를 설정합니다. 기본값은 1 입니다.

        - +

        [property:Boolean zoomToCursor]

        +

        + Setting this property to `true` allows to zoom to the cursor's position. Default is `false`. +

        메서드

        @@ -314,9 +318,12 @@

        [method:undefined stopListenToKeyEvents] ()

        Removes the key event listener previously defined with [page:.listenToKeyEvents]().

        -

        [method:Boolean update] ()

        +

        [method:Boolean update] ( [param:Number deltaTime] )

        컨트롤을 업데이트합니다. 카메라를 수동으로 변환하거나, [page:.autoRotate] 또는 [page:.enableDamping]을 설정할 경우 업데이트 루프에서 호출해야 합니다. + Update the controls. Must be called after any manual changes to the camera's transform, + or in the update loop if [page:.autoRotate] or [page:.enableDamping] are set. `deltaTime`, in seconds, is optional, + and is only required if you want the auto-rotate speed to be independent of the frame rate (the refresh rate of the display).

        Source

        diff --git a/docs/examples/ko/webxr/XREstimatedLight.html b/docs/examples/ko/webxr/XREstimatedLight.html new file mode 100644 index 00000000000000..0f55ff1c1282f1 --- /dev/null +++ b/docs/examples/ko/webxr/XREstimatedLight.html @@ -0,0 +1,117 @@ + + + + + + + + + + + + [page:Group] → + +

        [name]

        + +

        + XREstimatedLight은 WebXR의 빛 추정 기능을 사용하여 광도계(light probe), 방향광(directional light) 그리고 (선택적으로) 환경 맵(environment + map)을 생성하여 사용자의 현실 세계의 환경과 조명을 모델링합니다.
        + WebXR가 빛과 환경 추정을 업데이트할 때, XREstimatedLight는 자동으로 광도계, 방향광, 그리고 환경 맵을 업데이트합니다.

        + + WebXR 세션을 생성할 때 `light-estimation`을 선택적 또는 필수 기능으로 지정하는 것이 중요합니다. 그렇지 않으면 빛 추정 기능은 작동하지 않습니다.

        + + 브라우저 호환성 정보는 여기를 참조하십시오. 이는 아직 WebXR의 실험적 기능입니다. 여기

        + + 이를 사용하려면, /examples 디렉토리 내의 모든 파일과 마찬가지로, HTML에 별도로 파일을 포함해야 합니다. +

        + +

        가져오기

        + +

        + [name]은 애드온이므로 명시적으로 가져와야 합니다. + [link:#manual/introduction/Installation 설치 / 애드온]을 참조하십시오. +

        + + + import { XREstimatedLight } from 'three/addons/webxr/XREstimatedLight.js'; + + +

        코드 예제

        + + + renderer.xr.enabled = true; + + // XREstimatedLight를 초기 시점에 장면에 추가하지 마세요. + // AR 세션이 시작할 때까지 추정된 조명값이 없기 때문입니다. + const xrLight = new XREstimatedLight( renderer ); + + xrLight.addEventListener( 'estimationstart' , () => { + + scene.add( xrLight ); + + if ( xrLight.environment ) { + + scene.environment = xrLight.environment; + + } + + } ); + + xrLight.addEventListener( 'estimationend', () => { + + scene.remove( xrLight ); + + scene.environment = null; + + } ); + + // 조명 추정 기능을 작동하도록 하려면 'light-estimation'을 선택적 또는 필수 기능으로 포함해야 합니다. + document.body.appendChild( XRButton.createButton( renderer, { + optionalFeatures: [ 'light-estimation' ] + } ) ); + + +

        예제

        + +

        [example:webxr_ar_lighting webxr / light estimation]

        + +

        생성자

        + +

        [name]( [param:WebGLRenderer renderer], [param:Boolean environmentEstimation] )

        +

        + [page:WebGLRenderer renderer]: (필수) 장면을 렌더링하는 렌더러. 주로 WebXRManager와 상호작용하는 데 사용됩니다.

        + + environmentEstimation: `true`로 설정하면 WebXR을 사용하여 환경 맵을 추정합니다. +

        + +

        이벤트

        + +

        estimationstart

        +

        + 추정된 조명값이 업데이트 시작할 때 발생합니다. +

        + +

        estimationend

        +

        + 추정된 조명값이 업데이트 중지할 때 발생합니다. +

        + +

        속성

        + +

        [property:Texture environment]

        +

        + WebXR에 의해 추정된 환경 맵입니다. 이는 environmentEstimation이 `true`로 설정된 경우에만 사용할 수 있습니다.

        + + 이는 [page:Scene.environment], [page:MeshStandardMaterial.envMap], 또는 [page:Scene.background]로 사용할 수 있습니다. +

        + +

        소스

        + +

        + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/webxr/XREstimatedLight.js + examples/jsm/webxr/XREstimatedLight.js] +

        + + + \ No newline at end of file diff --git a/docs/examples/zh/animations/CCDIKSolver.html b/docs/examples/zh/animations/CCDIKSolver.html new file mode 100644 index 00000000000000..47fedfd48b083a --- /dev/null +++ b/docs/examples/zh/animations/CCDIKSolver.html @@ -0,0 +1,184 @@ + + + + + + + + + + + +

        CCDIK解算器([name])

        + +

        一种基于 `CCD Algorithm` 的 IK + 解算器。

        + [name] 用 CCD 算法解决逆运动学问题。 + [name] 设计用于与 [page:SkinnedMesh] 配合使用,但也可与 [page:GLTFLoader] 配合使用。 +

        + + + +

        导入

        + +

        + [name] 是一个附加组件,必须显式导入。请参阅 [link:#manual/introduction/Installation Installation / Addons]。 +

        + + + import { CCDIKSolver } from 'three/addons/animation/CCDIKSolver.js'; + + +

        代码示例

        + + + let ikSolver; + + // + // Bones hierarchy: + // + // root + // ├── bone0 + // │ └── bone1 + // │ └── bone2 + // │ └── bone3 + // └── target + // + // Positioned as follow on the cylinder: + // + // o <- target (y = 20) + // + // +----o----+ <- bone3 (y = 12) + // | | + // | o | <- bone2 (y = 4) + // | | + // | o | <- bone1 (y = -4) + // | | + // +----oo---+ <- root, bone0 (y = -12) + // + + let bones = [] + + // "root" + let rootBone = new Bone(); + rootBone.position.y = -12; + bones.push( rootBone ); + + // "bone0" + let prevBone = new Bone(); + prevBone.position.y = 0; + rootBone.add( prevBone ); + bones.push( prevBone ); + + // "bone1", "bone2", "bone3" + for ( let i = 1; i <= 3; i ++ ) { + const bone = new Bone(); + bone.position.y = 8; + bones.push( bone ); + + prevBone.add( bone ); + prevBone = bone; + } + + // "target" + const targetBone = new Bone(); + targetBone.position.y = 24 + 8 + rootBone.add( targetBone ); + bones.push( targetBone ); + + // + // skinned mesh + // + + const mesh = new SkinnedMesh( geometry, material ); + const skeleton = new Skeleton( bones ); + + mesh.add( bones[ 0 ] ); // "root" bone + mesh.bind( skeleton ); + + // + // ikSolver + // + + const iks = [ + { + target: 5, // "target" + effector: 4, // "bone3" + links: [ { index: 3 }, { index: 2 }, { index: 1 } ] // "bone2", "bone1", "bone0" + } + ]; + ikSolver = new CCDIKSolver( mesh, iks ); + + function render() { + ikSolver?.update(); + renderer.render( scene, camera ); + } + + +

        例子

        + +

        + [example:webgl_animation_skinning_ik] +

        + +

        构造函数

        + +

        [name]( [param:SkinnedMesh mesh], [param:Array iks] )

        +

        + [page:SkinnedMesh mesh] — [page:SkinnedMesh] 用于 [name] 解决 IK 问题
        + [page:Array iks] — 指定 IK 参数的对象 [page:Object] 数组。target、effector 和 link-index 是 .sculptor.bones + 中的索引整数。骨骼关系从父级到子级的顺序应为“links[ n ]、 links[ n - 1 ]、...、 links[ 0 ]、effector”。
        +

        +
          +
        • [page:Integer target] — 目标骨骼
        • +
        • [page:Integer effector] — 效应器骨
        • +
        • [page:Array links] — 指定链接骨骼的对象[page:Object] 数组 +
            +
          • [page:Integer index] — 链接骨骼
          • +
          • [page:Vector3 limitation] — (可选)旋转轴。默认值 undefined
          • +
          • [page:Vector3 rotationMin] — (可选)旋转最小限制。默认值 undefined
          • +
          • [page:Vector3 rotationMax] — (可选)旋转最大限制。默认值 undefined
          • +
          • [page:Boolean enabled] — (可选)默认值为 true。
          • +
          +
        • +
        • [page:Integer iteration] — (可选)计算的迭代次数。越小速度越快,但精度较差。默认值为 1。
        • +
        • [page:Number minAngle] — (可选)一步中的最小旋转角度。默认值 undefined
        • +
        • [page:Number maxAngle] — (可选)一步中的最大旋转角度。默认值 undefined
        • +
        +

        + 创建一个新的 [name]。 +

        + +

        属性

        + +

        [property:Array iks]

        +

        传递给构造函数的 IK 参数数组。

        + +

        [property:SkinnedMesh mesh]

        +

        [page:SkinnedMesh] 传递给构造函数。

        + +

        方法

        + +

        [method:CCDIKHelper createHelper]()

        +

        + 返回 [page:CCDIKHelper]. 。您可以通过将辅助对象添加到场景来可视化 IK 骨骼。 +

        + +

        [method:this update]()

        +

        + 通过求解 CCD 算法更新 IK 骨骼四元数。 +

        + +

        [method:this updateOne]( [param:Object ikParam] )

        +

        + 通过求解 CCD 算法来更新一个 IK 骨骼四元数。 +

        + +

        源代码

        + +

        + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/animation/CCDIKSolver.js examples/jsm/animation/CCDIKSolver.js] +

        + + + diff --git a/docs/examples/zh/controls/ArcballControls.html b/docs/examples/zh/controls/ArcballControls.html new file mode 100644 index 00000000000000..715444d51af850 --- /dev/null +++ b/docs/examples/zh/controls/ArcballControls.html @@ -0,0 +1,273 @@ + + + + + + + + + + + + [page:Controls] → + +

        弧球控制器([name])

        + +

        + ArcballControls 允许通过具有完全触摸支持和高级导航功能的虚拟轨迹球控制相机。
        + 光标/手指位置和移动被映射到由小控件表示的虚拟轨迹球表面上,并映射为直观且一致的相机移动。拖动光标/手指将使相机以保守的方式围绕轨迹球中心旋转(返回起始点将使相机返回到其起始方向)。

        + + 除了支持平移、缩放和捏合手势之外, ArcballControls 还通过双击/点击提供 聚焦 功能,以便直观地将对象的兴趣点移动到虚拟轨迹球的中心。焦点可以在复杂的环境中更好地检查和导航。此外, + ArcballControls 允许 FOV 操作(以眩晕方式)和 z 轴旋转。还支持通过剪贴板保存和恢复相机状态(使用 ctrl+c 和 ctrl+v 快捷键复制和粘贴状态)。

        + + 不同于 [page:OrbitControls] 和 [page:TrackballControls],[name] 不需要在动画打开时在动画循环中从外部调用 [page:.update]。

        + + + 要使用此功能,与 /examples 目录中的所有文件一样,您必须将该文件单独包含在 HTML 中。 +

        + +

        导入

        + +

        + [name] 是一个附加组件,必须显式导入。请参阅 [link:#manual/introduction/Installation Installation / Addons]。 +

        + + + import { ArcballControls } from 'three/addons/controls/ArcballControls.js'; + + +

        代码示例

        + + + const renderer = new THREE.WebGLRenderer(); + renderer.setSize( window.innerWidth, window.innerHeight ); + document.body.appendChild( renderer.domElement ); + + const scene = new THREE.Scene(); + + const camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 10000 ); + + const controls = new ArcballControls( camera, renderer.domElement, scene ); + + controls.addEventListener( 'change', function () { + + renderer.render( scene, camera ); + + } ); + + //controls.update() must be called after any manual changes to the camera's transform + camera.position.set( 0, 20, 100 ); + controls.update(); + + +

        例子

        + +

        [example:misc_controls_arcball misc / controls / arcball ]

        + +

        构造函数

        + +

        [name]( [param:Camera camera], [param:HTMLDOMElement domElement], [param:Scene scene] )

        +

        + [page:Camera camera]:(必填)要控制的相机。相机不能是另一个对象的子对象,除非该对象是场景本身。

        + + [page:HTMLDOMElement domElement]: 用于事件侦听器的 HTML 元素。(可选)

        + + [page:Scene scene]: 相机渲染的场景。如果未给出,则小控件无法显示。(可选) +

        + +

        事件

        + +

        change

        +

        + 当相机被小控件改变时触发。 +

        + +

        start

        +

        + 当交互开始时触发。 +

        + +

        end

        +

        + 当交互完成时触发。 +

        + +

        属性

        + +

        共有属性请参见其基类[page:Controls]。

        + +

        [property:Boolean adjustNearFar]

        +

        + 如果为 true,则每次执行缩放时都会调整相机的近端和远端值,尝试保持初始近端和远端值给出的相同可见部分(仅限 [page:PerspectiveCamera] )。默认为 false。 + +

        + +

        [property:Camera camera]

        +

        + 被控制的摄像机。 +

        + +

        [property:Boolean cursorZoom]

        +

        + 设置为 true 以使缩放变为光标居中。 +

        + +

        + [property:Float dampingFactor]

        +

        + 设置为 [page:.enableAnimations] 为true 时使用的阻尼惯性。 +

        + +

        [property:Boolean enableAnimations]

        +

        + 设置为 true 以启用旋转(阻尼)和聚焦操作的动画。默认为 true。 +

        + +

        [property:Boolean enableFocus]

        +

        + 设置为 true 以在双击(或点击)时启用相机聚焦功能。默认值为 true。 +

        + +

        [property:Boolean enableGrid]

        +

        + 设置为 true 时,执行平移操作时将出现网格(仅限桌面交互)。默认为 false。 +

        + +

        [property:Boolean enablePan]

        +

        + 启用或禁用相机平移。默认为 true。 +

        + +

        [property:Boolean enableRotate]

        +

        + 启用或禁用相机旋转。默认为 true。 +

        + +

        [property:Boolean enableZoom]

        +

        + 启用或禁用相机变焦。 +

        + +

        [property:Float focusAnimationTime]

        +

        + 焦点动画的持续时间。 +

        + +

        [property:Float maxDistance]

        +

        + 最大移动距离(仅限 [page:PerspectiveCamera])。默认为无穷大。 +

        + +

        [property:Float maxZoom]

        +

        + 最大缩放值(仅限 [page:OrthographicCamera] )。默认为无穷大。 +

        + +

        [property:Float minDistance]

        +

        + 最小移动距离(仅限 [page:PerspectiveCamera] )。默认值为 0。 +

        + +

        [property:Float minZoom]

        +

        + 最小缩放值(仅限 [page:OrthographicCamera] )。默认值为 0。 +

        + +

        [property:Float radiusFactor]

        +

        + 小控件相对于屏幕宽度和高度的大小。默认值为 0.67。 +

        + +

        [property:Float rotateSpeed]

        +

        + 旋转速度。默认值为 1。 +

        + +

        [property:Float scaleFactor]

        +

        + 执行缩放操作时使用的缩放因子。 +

        + +

        [property:Scene scene]

        +

        + 相机渲染的场景。 +

        + +

        [property:Float wMax]

        +

        + 旋转动画开始时允许的最大角速度。 +

        + +

        方法

        + +

        共有方法请参见其基类[page:Controls]。

        + +

        [method:undefined activateGizmos] ( [param:Boolean isActive] )

        +

        + 使小控件或多或少可见。 +

        + +

        [method:undefined copyState] ()

        +

        + 将当前状态复制到剪贴板(作为可读的 JSON 文本)。 +

        + +

        [method:undefined pasteState] ()

        +

        + 从剪贴板设置控件状态,假设剪贴板存储从 [page:.copyState] 保存的 JSON 文本。 +

        + +

        [method:undefined reset] ()

        +

        + 将控件重置为上次调用 [page:.saveState] 时的状态或初始状态。 +

        + +

        [method:undefined saveState] ()

        +

        + 保存控件的当前状态。稍后可以使用 [page:.reset] 恢复。 +

        + +

        [method:undefined setCamera] ( [param:Camera camera] )

        +

        + 设置要控制的摄像机。必须调用才能设置要控制的新摄像机。 +

        + +

        [method:undefined setGizmosVisible] ( [param:Boolean value] )

        +

        + 设置小控件的可见属性。 +

        + +

        [method:undefined setTbRadius] ( [param:Float value] )

        +

        + 更新 `radiusFactor` 值,重新绘制小控件并发送 `changeEvent` 让可视化更新。 +

        + +

        [method:Boolean setMouseAction] ( [param:String operation], mouse, key )

        +

        + 通过指定要执行的操作和鼠标/按键组合来设置新的鼠标操作。如果发生冲突,则替换现有的。

        + 操作可以指定为 'ROTATE'、'PAN'、'FOV' 或 'ZOOM'。
        + 鼠标输入可以指定为鼠标按钮 0、1、2 或 'WHEEL'。
        + 键盘修饰符可以指定为 'CTRL'、'SHIFT' 或 null(如果不再需要) 。 +

        + +

        [method:Boolean unsetMouseAction] ( mouse, key )

        +

        + 通过指定 鼠标/按键 组合来删除鼠标操作。

        + 鼠标输入可以指定为鼠标按钮 0、1、2 或 'WHEEL'。
        + 键盘修饰符可以指定为 'CTRL'、'SHIFT' 或 null(如果不再需要) 。 +

        + +

        [method:Raycaster getRaycaster] ()

        +

        + 返回用于用户交互的 [page:Raycaster] 对象。如果设置了 [name] 的 [page:Object3D.layers .layers] 属性,您还需要使用匹配的值设置 [page:Raycaster.layers + .layers] 的 [page:Raycaster] 属性,否则 [name] 将无法按预期工作。 +

        + +

        源代码

        + +

        + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/controls/ArcballControls.js examples/jsm/controls/ArcballControls.js] +

        + + + diff --git a/docs/examples/zh/controls/DragControls.html b/docs/examples/zh/controls/DragControls.html index f81e6efee0b4b9..b3051c45a53f56 100644 --- a/docs/examples/zh/controls/DragControls.html +++ b/docs/examples/zh/controls/DragControls.html @@ -7,7 +7,7 @@ - [page:EventDispatcher] → + [page:Controls] →

        拖放控制器([name])

        @@ -15,7 +15,7 @@

        拖放控制器([name])

        该类被用于提供一个拖放交互。

        -

        进口

        +

        导入

        [name] 是一个附加组件,必须显式导入。 @@ -50,7 +50,7 @@

        例子

        [example:misc_controls_drag misc / controls / drag ]

        -

        Constructor

        +

        构造函数

        [name]( [param:Array objects], [param:Camera camera], [param:HTMLDOMElement domElement] )

        @@ -61,7 +61,7 @@

        [name]( [param:Array objects], [param:Camera camera], [param:HTMLDOMElement [page:Camera camera]: 渲染场景的摄像机。

        - [page:HTMLDOMElement domElement]: 用于事件监听的HTML元素。 + [page:HTMLDOMElement domElement]: 用于事件监听的HTML元素。(可选)

        创建一个新的 [name] 实例。 @@ -97,47 +97,39 @@

        hoveroff

        属性

        -

        [property:Boolean enabled]

        -

        - 是否启用控制器。 -

        +

        共有属性请参见其基类[page:Controls]。

        -

        [property:Boolean transformGroup]

        +

        [property:Array objects]

        - This option only works if the [page:DragControls.objects] array contains a single draggable group object. - If set to *true*, [name] does not transform individual objects but the entire group. Default is *false*. + 可拖放对象的数组。

        -

        Methods

        - -

        共有方法请参见其基类[page:EventDispatcher]。

        - -

        [method:undefined activate] ()

        +

        [property:Raycaster raycaster]

        - 添加控制器的事件监听。 + 内部用于检测拾取对象的光线投射器。

        -

        [method:undefined deactivate] ()

        +

        [property:Boolean recursive]

        - 移除控制器的事件监听。 + 可拖放对象的子对象是否可以独立于其父对象进行拖放。 默认值为 `true`。

        -

        [method:undefined dispose] ()

        +

        [property:Float rotateSpeed]

        - 若不再需要该控制器,则应当调用此函数。 + 执行 `rotate` 时的旋转速度。该值越大旋转速度越快。 默认值为 `1`。

        -

        [method:Array getObjects] ()

        +

        [property:Boolean transformGroup]

        - Returns the array of draggable objects. + 当 [page:DragControls.objects] 数组包含一个单个可拖拽的组对象时该选项生效。 + 如果设置为`true`,[name]会转换整个组对象,而不对单个对象做转换。默认值为`false`。

        -

        [method:Raycaster getRaycaster] ()

        -

        - Returns the internal [page:Raycaster] instance that is used for intersection tests. -

        +

        方法

        + +

        共有方法请参见其基类[page:Controls]。

        -

        Source

        +

        源代码

        [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/controls/DragControls.js examples/jsm/controls/DragControls.js] diff --git a/docs/examples/zh/controls/FirstPersonControls.html b/docs/examples/zh/controls/FirstPersonControls.html index a2d504e8fc219e..457406b18d5bb6 100644 --- a/docs/examples/zh/controls/FirstPersonControls.html +++ b/docs/examples/zh/controls/FirstPersonControls.html @@ -7,6 +7,7 @@ + [page:Controls] →

        第一人称控制器([name])

        @@ -14,11 +15,11 @@

        第一人称控制器([name])

        该类是 [page:FlyControls] 的另一个实现。

        -

        进口

        +

        导入

        [name] 是一个附加组件,必须显式导入。 - See [link:#manual/introduction/Installation Installation / Addons]. + 请参考[link:#manual/introduction/Installation Installation / Addons].

        @@ -37,7 +38,7 @@

        [name]( [param:Camera object], [param:HTMLDOMElement domElement] )

        [page:Camera object]: 被控制的摄像机。

        - [page:HTMLDOMElement domElement]: 用于事件监听的HTML元素。 + [page:HTMLDOMElement domElement]: 用于事件监听的HTML元素。(可选)

        创建一个新的 [name] 实例。 @@ -46,60 +47,52 @@

        [name]( [param:Camera object], [param:HTMLDOMElement domElement] )

        属性

        +

        共有属性请参见其基类[page:Controls]。

        +

        [property:Boolean activeLook]

        - 是否能够环视四周。默认为*true*。 + 是否能够环视四周。默认为 *true*。

        [property:Boolean autoForward]

        - 摄像机是否自动向前移动。默认为*false*。 + 摄像机是否自动向前移动。默认为 *false*。

        [property:Boolean constrainVertical]

        - 垂直环视是否约束在[[page:.verticalMin], [page:.verticalMax]]之间。默认值为*false*。 -

        - -

        [property:HTMLDOMElement domElement]

        -

        - 该 HTMLDOMElement 用于监听鼠标/触摸事件,该属性必须在构造函数中传入。在此处改变它将不会设置新的事件监听。 -

        - -

        [property:Boolean enabled]

        -

        - 是否启用控制器。默认为*true*。 + 垂直环视是否约束在[[page:.verticalMin], [page:.verticalMax]]之间。默认值为 *false*。

        [property:Number heightCoef]

        - Determines how much faster the camera moves when it's y-component is near [page:.heightMax]. Default is *1*. + 当Y坐标接近[page:.heightMax]时摄像机的移动速度。默认值为 *1*。

        [property:Number heightMax]

        - Upper camera height limit used for movement speed adjusment. Default is *1*. + 用于调节移动速度的摄像机最大高度限制。默认值为 *1*。

        [property:Number heightMin]

        - Lower camera height limit used for movement speed adjusment. Default is *0*. + 用于调节移动速度的摄像机最低高度限制。默认值为 *0*。

        [property:Boolean heightSpeed]

        - 摄像机的高度是否影响向前移动的速度。默认值为*false*。 + 摄像机的高度是否影响向前移动的速度。默认值为 *false*。 使用属性 [page:.heightCoef]、 [page:.heightMin] 和 [page:.heightMax] 来进行配置。

        [property:Boolean lookVertical]

        - 是否能够垂直环视。默认为*true*。 + 是否能够垂直环视。默认为 *true*。

        [property:Number lookSpeed]

        - 环视速度。默认为*0.005*。 + 环视速度。默认为 *0.005*。

        [property:Boolean mouseDragOn]

        @@ -109,30 +102,22 @@

        [property:Boolean mouseDragOn]

        [property:Number movementSpeed]

        - 移动速度。默认为*1*。 -

        - -

        [property:Camera object]

        -

        - 被控制的摄像机。 + 移动速度。默认为 *1*。

        [property:Number verticalMax]

        - 你能够垂直环视角度的上限。范围在 0 到 Math.PI 弧度之间。默认为*Math.PI*。 + 你能够垂直环视角度的上限。范围在 0 到 Math.PI 弧度之间。默认为 *Math.PI*。

        [property:Number verticalMin]

        - 你能够垂直环视角度的下限。范围在 0 到 Math.PI 弧度之间。默认为*0*。 + 你能够垂直环视角度的下限。范围在 0 到 Math.PI 弧度之间。默认为 *0*。

        方法

        - -

        [method:undefined dispose] ()

        -

        - 若不再需要该控制器,则应当调用此函数。 -

        + +

        共有方法请参见其基类[page:Controls]。

        [method:undefined handleResize] ()

        @@ -155,16 +140,6 @@

        [method:FirstPersonControls lookAt]( [param:Vector3 vector] )

        -

        [method:undefined update] ( [param:Number delta] )

        -

        -

        - [page:Number delta]: Time delta value. -

        -

        - 更新控制器,常被用在动画循环中。 -

        -

        -

        源代码

        diff --git a/docs/examples/zh/controls/FlyControls.html b/docs/examples/zh/controls/FlyControls.html index 99691d491823bd..73f83ace88a304 100644 --- a/docs/examples/zh/controls/FlyControls.html +++ b/docs/examples/zh/controls/FlyControls.html @@ -7,6 +7,7 @@ + [page:Controls] →

        飞行控制器([name])

        @@ -15,7 +16,7 @@

        飞行控制器([name])

        你可以在3D空间中任意变换摄像机,并且无任何限制(例如,专注于一个特定的目标)。

        -

        进口

        +

        导入

        [name] 是一个附加组件,必须显式导入。 @@ -38,7 +39,7 @@

        [name]( [param:Camera object], [param:HTMLDOMElement domElement] )

        [page:Camera object]: 被控制的摄像机。

        - [page:HTMLDOMElement domElement]: 用于事件监听的HTML元素。 + [page:HTMLDOMElement domElement]: 用于事件监听的HTML元素。(可选)

        创建一个新的 [name] 实例。 @@ -49,57 +50,36 @@

        Events

        change

        - Fires when the camera has been transformed by the controls. + 当摄像机被组件改变时触发。

        属性

        + +

        共有属性请参见其基类[page:Controls]。

        [property:Boolean autoForward]

        - 若该值设为*true*,初始变换后,摄像机将自动向前移动(且不会停止)。默认为*false*。 -

        - -

        [property:HTMLDOMElement domElement]

        -

        - 该 HTMLDOMElement 用于监听鼠标/触摸事件,该属性必须在构造函数中传入。在此处改变它将不会设置新的事件监听。 + 若该值设为 *true*,初始变换后,摄像机将自动向前移动(且不会停止)。默认为 *false*。

        [property:Boolean dragToLook]

        - 若该值设为*true*,你将只能通过执行拖拽交互来环视四周。默认为*false*。 + 若该值设为 *true*,你将只能通过执行拖拽交互来环视四周。默认为 *false*。

        [property:Number movementSpeed]

        - 移动速度,默认为*1*。 -

        - -

        [property:Camera object]

        -

        - 被控制的摄像机。 + 移动速度,默认为 *1*。

        [property:Number rollSpeed]

        - 旋转速度。默认为*0.005*。 + 旋转速度。默认为 *0.005*。

        方法

        - -

        [method:undefined dispose] ()

        -

        - 若不再需要该控制器,则应当调用此函数。 -

        - -

        [method:undefined update] ( [param:Number delta] )

        -

        -

        - [page:Number delta]: Time delta value. -

        -

        - 更新控制器,常被用在动画循环中。 -

        -

        + +

        共有方法请参见其基类[page:Controls]。

        源代码

        diff --git a/docs/examples/zh/controls/MapControls.html b/docs/examples/zh/controls/MapControls.html new file mode 100644 index 00000000000000..6c634003f9677a --- /dev/null +++ b/docs/examples/zh/controls/MapControls.html @@ -0,0 +1,118 @@ + + + + + + + + + + + + [page:OrbitControls] → + +

        地图控制器([name])

        + +

        + [name] 旨在从鸟瞰角度在地图上转换相机。该类与 [page:OrbitControls] 共享其实现,但使用特定的预设进行鼠标/触摸交互,并默认禁用屏幕空间平移。 +

        + +

        导入

        + +

        + [name] 是一个附加组件,必须显式导入。请参阅 [link:#manual/introduction/Installation Installation / Addons]. +

        + + + import { MapControls } from 'three/addons/controls/MapControls.js'; + + +

        代码示例

        + + + const renderer = new THREE.WebGLRenderer(); + renderer.setSize( window.innerWidth, window.innerHeight ); + document.body.appendChild( renderer.domElement ); + + const scene = new THREE.Scene(); + + const camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 10000 ); + camera.position.set( 0, 20, 100 ); + + const controls = new MapControls( camera, renderer.domElement ); + controls.enableDamping = true; + + function animate() { + + requestAnimationFrame( animate ); + + // required if controls.enableDamping or controls.autoRotate are set to true + controls.update(); + + renderer.render( scene, camera ); + + } + + +

        例子

        + +

        [example:misc_controls_map misc / controls / map ]

        + +

        构造函数

        + +

        [name]( [param:Camera object], [param:HTMLDOMElement domElement] )

        +

        + [page:Camera object]:(必填)要控制的相机。相机不能是另一个对象的子对象,除非该对象是场景本身。

        + + [page:HTMLDOMElement domElement]: 用于事件侦听器的 HTML 元素。 +

        + +

        事件

        + +

        有关常见事件,请参阅 [page:OrbitControls] 基类。

        + +

        属性

        + +

        有关常见属性,请参阅 [page:OrbitControls] 基类。

        + +

        + [property:Object mouseButtons]

        +

        + 该对象包含对控件使用的鼠标操作的引用。 + +controls.mouseButtons = { + LEFT: THREE.MOUSE.PAN, + MIDDLE: THREE.MOUSE.DOLLY, + RIGHT: THREE.MOUSE.ROTATE +} + +

        + +

        [property:Boolean screenSpacePanning]

        +

        + 定义平移时摄像机位置的平移方式。如果为 true,相机将在屏幕空间中平移。否则,摄像机将在与摄像机向上方向正交的平面中平移。默认为 `false`。 +

        + +

        [property:Object touches]

        +

        + 该对象包含对控件使用的触摸操作的引用。 + +controls.touches = { + ONE: THREE.TOUCH.PAN, + TWO: THREE.TOUCH.DOLLY_ROTATE +} + +

        + +

        方法

        + +

        有关常用方法,请参阅 [page:OrbitControls]。

        + +

        源代码

        + +

        + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/controls/MapControls.js examples/jsm/controls/MapControls.js] +

        + + + diff --git a/docs/examples/zh/controls/OrbitControls.html b/docs/examples/zh/controls/OrbitControls.html index cfee58e37fc3bb..2d1fd24fdfd2c4 100644 --- a/docs/examples/zh/controls/OrbitControls.html +++ b/docs/examples/zh/controls/OrbitControls.html @@ -7,6 +7,8 @@ + [page:Controls] → +

        轨道控制器([name])

        @@ -17,7 +19,7 @@

        轨道控制器([name])

        -

        进口

        +

        导入

        [name] 是一个附加组件,必须显式导入。 @@ -67,32 +69,35 @@

        [name]( [param:Camera object], [param:HTMLDOMElement domElement] )

        [page:Camera object]: (必须)将要被控制的相机。该相机不允许是其他任何对象的子级,除非该对象是场景自身。

        - [page:HTMLDOMElement domElement]: 用于事件监听的HTML元素。 + [page:HTMLDOMElement domElement]: 用于事件监听的HTML元素。(可选)

        Events

        change

        - Fires when the camera has been transformed by the controls. + 当摄像机被组件改变时触发。

        start

        - Fires when an interaction was initiated. + 初始化交互时触发。

        end

        - Fires when an interaction has finished. + 当交互结束时触发。

        属性

        +

        共有属性请参见其基类[page:Controls]。

        +

        [property:Boolean autoRotate]

        将其设为true,以自动围绕目标旋转。
        请注意,如果它被启用,你必须在你的动画循环里调用[page:.update]()。 + 如果希望自动旋转速度与帧速率(显示器的刷新率)无关,则必须将时间 `deltaTime`(以秒为单位)传递给 [page:.update]()。

        [property:Float autoRotateSpeed]

        @@ -108,16 +113,6 @@

        请注意,要使得这一值生效,你必须在你的动画循环里调用[page:.update]()。

        -

        [property:HTMLDOMElement domElement]

        -

        - 该 HTMLDOMElement 用于监听鼠标/触摸事件,该属性必须在构造函数中传入。在此处改变它将不会设置新的事件监听。 -

        - -

        [property:Boolean enabled]

        -

        - 当设置为false时,控制器将不会响应用户的操作。默认值为true。 -

        -

        [property:Boolean enableDamping]

        将其设置为true以启用阻尼(惯性),这将给控制器带来重量感。默认值为false。
        @@ -175,6 +170,11 @@

        [property:Float maxPolarAngle]

        你能够垂直旋转的角度的上限,范围是0到Math.PI,其默认值为Math.PI。

        +

        [property:Float maxTargetRadius]

        +

        + 你能够让目标移动离 [page:.cursor] 有多远,其默认值为Infinity。 +

        +

        [property:Float maxZoom]

        你能够将相机缩小多少(仅适用于[page:OrthographicCamera]),其默认值为Infinity。 @@ -195,6 +195,11 @@

        [property:Float minPolarAngle]

        你能够垂直旋转的角度的下限,范围是0到Math.PI,其默认值为0。

        +

        [property:Float minTargetRadius]

        +

        + 你能够让目标移动离 [page:.cursor] 有多近,其默认值为0。 +

        +

        [property:Float minZoom]

        你能够将相机放大多少(仅适用于[page:OrthographicCamera]),其默认值为0。 @@ -213,11 +218,6 @@

        -

        [property:Camera object]

        -

        - 正被控制的摄像机。 -

        -

        [property:Float panSpeed]

        位移的速度,其默认值为1。 @@ -251,6 +251,11 @@

        [property:Vector3 target]

        它可以在任何时候被手动更新,以更改控制器的焦点。

        +

        [property:Vector3 cursor]

        +

        + 被 [page:.minTargetRadius] 和 [page:.maxTargetRadius] 限制的焦点。可随时手动更新以更改 [page:.target] 的兴趣中心。 +

        +

        [property:Object touches]

        该对象包含由控件所使用的触摸操作的引用。 @@ -272,15 +277,15 @@

        [property:Float zoomSpeed]

        摄像机缩放的速度,其默认值为1。

        - - -

        Methods

        - -

        [method:undefined dispose] ()

        +

        [property:Boolean zoomToCursor]

        - 移除所有的事件监听。 + 将此属性设置为 `true` 可以缩放至光标位置。默认值为 `false`。

        +

        方法

        + +

        共有方法请参见其基类[page:Controls]。

        +

        [method:radians getAzimuthalAngle] ()

        获得当前的水平旋转,单位为弧度。 @@ -293,7 +298,7 @@

        [method:radians getPolarAngle] ()

        [method:Float getDistance] ()

        - Returns the distance from the camera to the target. + 返回从相机到目标的距离。

        [method:undefined listenToKeyEvents] ( [param:HTMLDOMElement domElement] )

        @@ -316,10 +321,9 @@

        [method:undefined stopListenToKeyEvents] ()

        Removes the key event listener previously defined with [page:.listenToKeyEvents]().

        -

        [method:Boolean update] ()

        +

        [method:Boolean update] ( [param:Number deltaTime] )

        - 更新控制器。必须在摄像机的变换发生任何手动改变后调用, - 或如果[page:.autoRotate]或[page:.enableDamping]被设置时,在update循环里调用。 + 更新控制器。必须在摄像机的变换发生任何手动改变后调用,或如果[page:.autoRotate]或[page:.enableDamping]被设置时,在update循环里调用。`deltaTime` 以秒为单位,是可选的,并且仅当您希望自动旋转速度独立于帧速率(显示器的刷新率)时才是必需的。

        源代码

        diff --git a/docs/examples/zh/controls/PointerLockControls.html b/docs/examples/zh/controls/PointerLockControls.html index 12a8d4db54e650..08429e2c131d27 100644 --- a/docs/examples/zh/controls/PointerLockControls.html +++ b/docs/examples/zh/controls/PointerLockControls.html @@ -7,7 +7,7 @@ - [page:EventDispatcher] → + [page:Controls] →

        指针锁定控制器([name])

        @@ -16,11 +16,11 @@

        指针锁定控制器([name])

        对于第一人称3D游戏来说, [name] 是一个非常完美的选择。

        -

        进口

        +

        导入

        [name] 是一个附加组件,必须显式导入。 - See [link:#manual/introduction/Installation Installation / Addons]. + 请参阅 [link:#manual/introduction/Installation Installation / Addons].

        @@ -51,7 +51,7 @@

        例子

        [example:misc_controls_pointerlock misc / controls / pointerlock ]

        -

        Constructor

        +

        构造函数

        [name]( [param:Camera camera], [param:HTMLDOMElement domElement] )

        @@ -85,11 +85,7 @@

        unlock

        属性

        -

        [property:HTMLDOMElement domElement]

        -

        - 该 HTMLDOMElement 用于监听鼠标/触摸事件,该属性必须在构造函数中传入。在此处改变它将不会设置新的事件监听。 -

        - +

        共有属性请参见其基类[page:Controls]。

        [property:Boolean isLocked]

        @@ -98,27 +94,22 @@

        [property:Boolean isLocked]

        [property:Float maxPolarAngle]

        - Camera pitch, upper limit. Range is 0 to Math.PI radians. Default is Math.PI. + 摄像机的俯仰角上限限制。范围为0到Math.PI弧度之间。默认值为Math.PI。

        [property:Float minPolarAngle]

        - Camera pitch, lower limit. Range is 0 to Math.PI radians. Default is 0. + 摄像机的俯仰角下限限制。范围为0到Math.PI弧度之间。默认值为0。

        -

        Methods

        - -

        共有方法请参见其基类[page:EventDispatcher]。

        - -

        [method:undefined connect] ()

        +

        [property:Float pointerSpeed]

        - 添加控制器的事件监听。 + 指针移动对相机旋转的影响程度的乘数。默认值为1。

        -

        [method:undefined disconnect] ()

        -

        - 移除控制器的事件监听。 -

        +

        方法

        + +

        共有方法请参见其基类[page:Controls]。

        [method:Vector3 getDirection] ( [param:Vector3 target] )

        diff --git a/docs/examples/zh/controls/TrackballControls.html b/docs/examples/zh/controls/TrackballControls.html index 4176810317b4cc..ac4ff0bb0de291 100644 --- a/docs/examples/zh/controls/TrackballControls.html +++ b/docs/examples/zh/controls/TrackballControls.html @@ -7,7 +7,7 @@ - [page:EventDispatcher] → + [page:Controls] →

        轨迹球控制器([name])

        @@ -18,7 +18,7 @@

        轨迹球控制器([name])

        -

        进口

        +

        导入

        [name] 是一个附加组件,必须显式导入。 @@ -41,7 +41,7 @@

        [name]( [param:Camera camera], [param:HTMLDOMElement domElement] )

        [page:Camera camera]: 渲染场景的摄像机。

        - [page:HTMLDOMElement domElement]: 用于事件监听的HTML元素。 + [page:HTMLDOMElement domElement]: 用于事件监听的HTML元素。(可选)

        创建一个新的 [name] 实例。 @@ -67,21 +67,13 @@

        end

        属性

        -

        [property:HTMLDOMElement domElement]

        -

        - 该 HTMLDOMElement 用于监听鼠标/触摸事件,该属性必须在构造函数中传入。在此处改变它将不会设置新的事件监听。 -

        +

        共有属性请参见其基类[page:Controls]。

        [property:Number dynamicDampingFactor]

        设置阻尼的强度。仅在[page:.staticMoving staticMoving]设为*false*时考虑。默认为*0.2*。

        -

        [property:Boolean enabled]

        -

        - 是否启用控制器。 -

        -

        [property:Array keys]

        该数组包含用于控制交互的按键代码。 @@ -139,11 +131,6 @@

        [property:Boolean noZoom]

        是否禁用缩放,默认为*false*。

        -

        [property:Camera object]

        -

        - 正被控制的摄像机。 -

        -

        [property:Number panSpeed]

        平移的速度,其默认值为*0.3*。 @@ -170,6 +157,11 @@

        [property:Boolean staticMoving]

        阻尼是否被禁用。默认为*false*。

        +

        [property:Vector3 target]

        +

        + 控件的焦点。 +

        +

        [property:Number zoomSpeed]

        缩放的速度,其默认值为*1.2*。 @@ -177,46 +169,18 @@

        [property:Number zoomSpeed]

        方法

        -

        [method:undefined checkDistances] ()

        -

        - 确保控制器位于 [minDistance, maxDistance] 范围内。由[page:.update update]()调用。 -

        - -

        [method:undefined dispose] ()

        -

        - 若不再需要该控制器,则应当调用此函数。 -

        +

        共有方法请参见其基类[page:Controls]。

        [method:undefined handleResize] ()

        若应用程序窗口大小发生改变,则应当调用此函数。

        -

        [method:undefined panCamera] ()

        -

        - 如有必要,执行平移。由[page:.update update]()调用。 -

        -

        [method:undefined reset] ()

        重置控制器到初始状态。

        -

        [method:undefined rotateCamera] ()

        -

        - 如有必要,旋转相机。由[page:.update update]()调用。 -

        - -

        [method:undefined update] ()

        -

        - 更新控制器,常被用在动画循环中。 -

        - -

        [method:undefined zoomCamera] ()

        -

        - 如有必要,执行缩放。由[page:.update update]()调用。 -

        -

        源代码

        diff --git a/docs/examples/zh/controls/TransformControls.html b/docs/examples/zh/controls/TransformControls.html index bb72b36f499d95..f711280a799c0d 100644 --- a/docs/examples/zh/controls/TransformControls.html +++ b/docs/examples/zh/controls/TransformControls.html @@ -7,7 +7,7 @@ - [page:Object3D] → + [page:Controls] →

        变换控制器([name])

        @@ -18,7 +18,7 @@

        变换控制器([name])

        [name] 期望其所附加的3D对象是场景图的一部分。

        -

        进口

        +

        导入

        [name] 是一个附加组件,必须显式导入。 @@ -41,7 +41,7 @@

        [name]( [param:Camera camera], [param:HTMLDOMElement domElement] )

        [page:Camera camera]: 被控制的摄像机。

        - [page:HTMLDOMElement domElement]: 用于事件监听的HTML元素。 + [page:HTMLDOMElement domElement]: 用于事件监听的HTML元素。(可选)

        创建一个新的 [name] 实例。 @@ -73,7 +73,7 @@

        objectChange

        属性

        -

        共有属性请参见其基类[page:Object3D]。

        +

        共有属性请参见其基类[page:Controls]。

        [property:String axis]

        @@ -85,31 +85,16 @@

        [property:Camera camera]

        渲染场景的摄像机。

        -

        [property:HTMLDOMElement domElement]

        -

        - 该 HTMLDOMElement 用于监听鼠标/触摸事件,该属性必须在构造函数中传入。在此处改变它将不会设置新的事件监听。 -

        -

        [property:Boolean dragging]

        当前是否正在拖动。只读属性。

        -

        [property:Boolean enabled]

        -

        - 是否启用控制器。默认为*true*。 -

        -

        [property:String mode]

        当前的变换模式。可能的值包括"translate"、"rotate" 和 "scale"。默认为*translate*。

        -

        [property:Object3D object]

        -

        - 正在被控制的3D对象。 -

        -

        [property:Number rotationSnap]

        默认情况下,3D对象是可以被连续旋转的。如果你将该值设为一个数值(弧度),则你将可以定义每次旋转3D对象时的步幅。 @@ -149,7 +134,7 @@

        [property:Number translationSnap]

        方法

        -

        共有方法请参见其基类[page:Object3D]。

        +

        共有方法请参见其基类[page:Controls]。

        [method:TransformControls attach] ( [param:Object3D object] )

        @@ -166,9 +151,9 @@

        [method:TransformControls detach] ()

        从控制器中移除当前3D对象,并确保控制器UI是不可见的。

        -

        [method:undefined dispose] ()

        +

        [method:Object3D getHelper] ()

        - 若不再需要该控制器,则应当调用此函数。 + 返回控件的视觉表示。将辅助对象添加到场景中,以直观地变换附着的3D对象。

        [method:Raycaster getRaycaster] ()

        @@ -182,6 +167,11 @@

        [method:String getMode] ()

        返回变换模式。

        + +

        [method:undefined reset] ()

        +

        + 将对象的位置、旋转和缩放重置为当前变换开始时的状态。 +

        [method:undefined setMode] ( [param:String mode] )

        @@ -233,6 +223,16 @@

        [method:undefined setTranslationSnap] ( [param:Number translationSnap] )

        +

        [method:undefined setScaleSnap] ( [param:Number scaleSnap] )

        +

        +

        + [page:Number scaleSnap]: 缩放捕捉步幅。 +

        +

        + 设置缩放捕捉。 +

        +

        +

        源代码

        diff --git a/docs/examples/zh/exporters/DRACOExporter.html b/docs/examples/zh/exporters/DRACOExporter.html new file mode 100644 index 00000000000000..8234e0c80f4246 --- /dev/null +++ b/docs/examples/zh/exporters/DRACOExporter.html @@ -0,0 +1,85 @@ + + + + + + + + + + + +

        DRACO导出器([name])

        + +

        + 一个用于使用 Draco 库压缩几何体的导出器。

        + [link:https://google.github.io/draco/ Draco] 是一个用于压缩和解压缩 3D 网格和点云的开源库。压缩后的几何体可以显著减小文件大小,但在客户设备上需要额外的解码时间。 +

        + +

        + 独立的 Draco 文件具有 `.drc` 扩展名,包含顶点位置、法线、颜色和其他属性。Draco 文件不包含材质、纹理、动画或节点层次结构 - 要使用这些功能,请将 Draco 几何体嵌入到 glTF + 文件中。可以使用 [link:https://github.com/AnalyticalGraphicsInc/gltf-pipeline glTF-Pipeline] 将普通的 glTF 文件转换为经过 Draco 压缩的 + glTF 文件。 +

        + +

        导入

        + +

        + [name] 是一个附加组件,必须显式导入。请参阅 [link:#manual/introduction/Installation Installation / Addons]。 +

        + + + import { DRACOExporter } from 'three/addons/exporters/DRACOExporter.js'; + + +

        代码示例

        + + + // Instantiate a exporter + const exporter = new DRACOExporter(); + + // Parse the input and generate the DRACO encoded output + const binaryData = exporter.parse( mesh, options ); + + +

        例子

        + +

        + [example:misc_exporter_draco] +

        + +

        构造函数

        + +

        [name]()

        +

        + 创建一个新的 [name] 实例。 +

        + +

        方法

        + +

        [method:Int8Array parse]( [param:Mesh object] | [param:Points object], [param:Object options] )

        + +

        + [page:Mesh object] | [page:Points object] — 要编码的网格或点。
        + [page:Options options] — 可选的导出选项
        +

          +
        • decodeSpeed - int. 指示如何根据解码速度调整编码器(0 提供更好的速度,但质量最差)。默认值为 5。
        • +
        • encodeSpeed - int. 指示如何调整编码器参数(0 提供更好的速度,但质量最差)。默认值为 5。
        • +
        • encoderMethod - int. 顺序(很少压缩)或破边。Edgebreaker 以确定性、螺旋状的方式遍历网格的三角形,这提供了这种数据格式的大部分优点。默认值为 + DRACOExporter.MESH_EDGEBREAKER_ENCODING。
        • +
        • quantization - Array of int. 指示按照顺序(POSITION、NORMAL、COLOR、TEX_COORD、GENERIC)存储在 draco 文件中的每种数据类型的精度。默认为 [ 16, 8, + 8, 8, 8 ]
        • +
        • exportUvs - bool. 布尔值。默认为 true。
        • +
        • exportNormals - bool. 布尔值。默认为 true。
        • +
        • exportColor - bool. 布尔值。默认为 false。
        • +
        +

        + +

        源代码

        + +

        + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/exporters/DRACOExporter.js examples/jsm/exporters/DRACOExporter.js] +

        + + + diff --git a/docs/examples/zh/exporters/EXRExporter.html b/docs/examples/zh/exporters/EXRExporter.html new file mode 100644 index 00000000000000..112e4acc4bd6f2 --- /dev/null +++ b/docs/examples/zh/exporters/EXRExporter.html @@ -0,0 +1,95 @@ + + + + + + + + + + + +

        EXR导出器([name])

        + +

        + 一个用于 EXR 的导出器。 +

        + [link:https://www.openexr.com/ EXR] ( Extended Dynamic Range) 是用于电影行业专业级图像存储格式的 + [link:https://github.com/AcademySoftwareFoundation/openexr 开放格式规范]。 该格式的目的是准确有效地表示高动态范围的线性场景图像数据和相关的元数据。 + 这个库在需要准确性的主机应用软件中广泛使用,如逼真渲染、纹理访问、图像合成、深度合成和数字中间处理。 +

        + +

        导入

        + +

        + [name] 是一个附加组件,必须显式导入。请参阅 [link:#manual/introduction/Installation Installation / Addons]。 +

        + + + import { EXRExporter } from 'three/addons/exporters/EXRExporter.js'; + + +

        代码示例

        + + + // Instantiate a exporter + const exporter = new EXRExporter(); + + // Parse the input render target data and generate the EXR output + const EXR = exporter.parse( renderer, renderTarget, options ); + downloadFile( EXR ); + + +

        构造函数

        + +

        [name]()

        +

        +

        +

        + 创建一个新的 [name] 实例。 +

        + +

        方法

        + +

        [method:null parse]( [param:WebGLRenderer renderer], [param:WebGLRenderTarget renderTarget], [param:Object + options] )

        +

        + [page:Function renderTarget] — 包含用于导出 EXR 图像的数据的 WebGLRenderTarget。
        + [page:Options options] — 导出选项
        +

          +
        • type - 内部 EXR 数据的输出数据类型。可用选项:
          + +THREE.HalfFloatType // default option +THREE.FloatType + +
        • +
        • compression - 内部压缩算法。可用选项:
          + +NO_COMPRESSION +ZIP_COMPRESSION // default option +ZIPS_COMPRESSION + +
        • +
        +

        +

        + 从输入的渲染目标生成一个 .exr 输出。 +

        + +

        [method:null parse]( [param:DataTexture dataTexture], [param:Object options] )

        +

        + [page:Function dataTexture] — 包含用于导出 EXR 图像的数据的 DataTexture。
        + [page:Options options] — 导出选项(详情见上文)。
        +

        +

        + 从输入的数据纹理生成一个 .exr 输出。 +

        + +

        源代码

        + +

        + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/exporters/EXRExporter.js examples/jsm/exporters/EXRExporter.js] +

        + + + diff --git a/docs/examples/zh/exporters/GLTFExporter.html b/docs/examples/zh/exporters/GLTFExporter.html new file mode 100644 index 00000000000000..56c8eea4005a6f --- /dev/null +++ b/docs/examples/zh/exporters/GLTFExporter.html @@ -0,0 +1,159 @@ + + + + + + + + + + + +

        GLTF导出器([name])

        + +

        + 一个用于 glTF 2.0 的导出器。 +

        + [link:https://www.khronos.org/gltf glTF] (GL Transmission Format) 是一个用于高效传输和加载3D内容的 + [link:https://github.com/KhronosGroup/glTF/tree/master/specification/2.0 开放格式规范]。 + 资源可以以 JSON (.gltf) 或二进制 (.glb) 格式提供。外部文件存储纹理 (.jpg, .png) 和额外的二进制数据 (.bin)。一个 glTF + 资产可以包含一个或多个场景,包括网格、材质、纹理、蒙皮、骨骼、变形目标、动画、灯光和/或相机。 +

        + +

        导入

        + +

        + [name] 是一个附加组件,必须显式导入。请参阅 [link:#manual/introduction/Installation Installation / Addons]。 +

        + + + import { GLTFExporter } from 'three/addons/exporters/GLTFExporter.js'; + + +

        扩展

        + +

        + [name] 支持以下 [link:https://github.com/KhronosGroup/glTF/tree/master/extensions/ glTF 2.0 扩展]: +

        + +
          +
        • KHR_lights_punctual
        • +
        • KHR_materials_clearcoat
        • +
        • KHR_materials_dispersion
        • +
        • KHR_materials_emissive_strength
        • +
        • KHR_materials_ior
        • +
        • KHR_materials_iridescence
        • +
        • KHR_materials_specular
        • +
        • KHR_materials_sheen
        • +
        • KHR_materials_transmission
        • +
        • KHR_materials_unlit
        • +
        • KHR_materials_volume
        • +
        • KHR_mesh_quantization
        • +
        • KHR_texture_transform
        • +
        • EXT_materials_bump
        • +
        • EXT_mesh_gpu_instancing
        • +
        + +

        + 以下 glTF 2.0 扩展由外部用户插件支持 +

        + +
          +
        • [link:https://github.com/takahirox/three-gltf-extensions KHR_materials_variants]
        • +
        + +

        代码示例

        + + + // Instantiate a exporter + const exporter = new GLTFExporter(); + + // Parse the input and generate the glTF output + exporter.parse( + scene, + // called when the gltf has been generated + function ( gltf ) { + + console.log( gltf ); + downloadJSON( gltf ); + + }, + // called when there is an error in the generation + function ( error ) { + + console.log( 'An error happened' ); + + }, + options + ); + + +

        例子

        + +

        + [example:misc_exporter_gltf] +

        + +

        构造函数

        + +

        [name]()

        +

        +

        +

        + 创建一个新的 [name] 实例。 +

        + +

        方法

        + +

        [method:undefined parse]( [param:Object3D input], [param:Function onCompleted], [param:Function onError], + [param:Object options] )

        + +

        + [page:Object input] — 要导出的场景或对象。有效选项:
        +

          +
        • + 导出场景 + + exporter.parse( scene1, ... ) + exporter.parse( [ scene1, scene2 ], ... ) + +
        • +
        • + 导出对象(它将创建一个新场景来保存所有对象) + + exporter.parse( object1, ... ) + exporter.parse( [ object1, object2 ], ... ) + +
        • +
        • + 混合场景和对象(它将像平常一样导出场景,但会创建一个新场景来保存所有单个对象)。 + + exporter.parse( [ scene1, object1, object2, scene2 ], ... ) + +
        • +
        + + [page:Function onCompleted] — 导出完成时将被调用。参数将是生成的 glTF JSON 或二进制 ArrayBuffer。
        + [page:Function onError] — 如果在 gltf 生成过程中出现任何错误,将被调用。
        + [page:Options options] — 导出选项
        +
          +
        • trs - bool. 导出每个节点的位置、旋转和缩放而不是矩阵。默认为 false。
        • +
        • onlyVisible - bool. 仅导出可见对象。默认为 true。
        • +
        • binary - bool. 以二进制 (.glb) 格式导出,返回 ArrayBuffer。默认为 false。
        • +
        • maxTextureSize - int. 将图像最大尺寸(宽度和高度)限制为给定值。默认为无穷大。
        • +
        • animations - Array<[page:AnimationClip AnimationClip]>. 要包含在导出中的动画列表。
        • +
        • includeCustomExtensions - bool. 导出在对象属性上定义的自定义 glTF 扩展(`userData.gltfExtensions`)。默认为 false。
        • +
        +

        +

        + 从输入(场景或对象)生成一个 .gltf(JSON)或 .glb(二进制)输出。 +

        + +

        源代码

        + +

        + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/exporters/GLTFExporter.js examples/jsm/exporters/GLTFExporter.js] +

        + + + diff --git a/docs/examples/zh/exporters/OBJExporter.html b/docs/examples/zh/exporters/OBJExporter.html new file mode 100644 index 00000000000000..396d93c6e5e911 --- /dev/null +++ b/docs/examples/zh/exporters/OBJExporter.html @@ -0,0 +1,67 @@ + + + + + + + + + + + +

        OBJ导出器([name])

        + +

        + 一个用于 [link:https://en.wikipedia.org/wiki/Wavefront_.obj_file OBJ] 文件格式的导出器。 +

        + [name] 不能将材质数据导出到 MTL 文件中,因此仅支持几何数据。 +

        + +

        导入

        + +

        + [name] 是一个附加组件,必须显式导入。请参阅 [link:#manual/introduction/Installation Installation / Addons]。 +

        + + + import { OBJExporter } from 'three/addons/exporters/OBJExporter.js'; + + +

        代码示例

        + + + // Instantiate an exporter + const exporter = new OBJExporter(); + + // Parse the input and generate the OBJ output + const data = exporter.parse( scene ); + downloadFile( data ); + + +

        构造函数

        + +

        [name]()

        +

        +

        +

        + 创建一个新的 [name] 实例。 +

        + +

        方法

        + +

        [method:String parse]( [param:Object3D object] )

        +

        + [page:Object object] — 要导出的 Object3D。 +

        +

        + 生成包含 OBJ 数据的字符串。 +

        + +

        源代码

        + +

        + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/exporters/OBJExporter.js examples/jsm/exporters/OBJExporter.js] +

        + + + diff --git a/docs/examples/zh/exporters/PLYExporter.html b/docs/examples/zh/exporters/PLYExporter.html new file mode 100644 index 00000000000000..bb4a57d14e95f3 --- /dev/null +++ b/docs/examples/zh/exporters/PLYExporter.html @@ -0,0 +1,75 @@ + + + + + + + + + + + +

        PLY导出器([name])

        + +

        + 一个用于 `PLY` 文件格式的导出器。 +

        + [link:https://en.wikipedia.org/wiki/PLY_(file_format) PLY] (Polygon or Stanford Triangle Format) + 是一种用于高效传输和加载简单、静态的3D内容的文件格式,采用紧凑的格式。支持二进制和 ASCII 两种格式。PLY 可以存储顶点位置、颜色、法线和 UV 坐标。不保存纹理或纹理引用。 +

        + +

        导入

        + +

        + [name] 是一个附加组件,必须显式导入。请参阅 [link:#manual/introduction/Installation Installation / Addons]。 +

        + + + import { PLYExporter } from 'three/addons/exporters/PLYExporter.js'; + + +

        代码示例

        + + + // Instantiate an exporter + const exporter = new PLYExporter(); + + // Parse the input and generate the ply output + const data = exporter.parse( scene, options ); + downloadFile( data ); + + +

        构造函数

        + +

        [name]()

        +

        +

        +

        + 创建一个新的 [name] 实例。 +

        + +

        方法

        + +

        [method:Object parse]( [param:Object3D input], [param:Function onDone], [param:Object options] )

        +

        + [page:Object input] — Object3D
        + [page:Function onCompleted] — 将在导出完成时调用。参数将是生成的 ply ascii 或二进制 ArrayBuffer。
        + [page:Options options] — 导出选项
        +

          +
        • excludeAttributes - array. 要从导出的 PLY 文件中显式排除哪些属性。有效值为 'color'、'normal'、'uv' 和 'index'。如果排除三角形索引,则导出点云。默认是一个空数组。 +
        • +
        • binary - bool. 以二进制格式导出,返回 ArrayBuffer。默认为 false。
        • +
        +

        +

        + 从输入对象生成 PLY 文件数据作为字符串或 ArrayBuffer(ASCII 或二进制)输出。返回的数据与传递给 "onCompleted" 函数的数据相同。如果对象由多个子元素和几何体组成,它们将在文件中合并为一个单独的网格。 +

        + +

        源代码

        + +

        + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/exporters/PLYExporter.js examples/jsm/exporters/PLYExporter.js] +

        + + + diff --git a/docs/examples/zh/exporters/STLExporter.html b/docs/examples/zh/exporters/STLExporter.html new file mode 100644 index 00000000000000..a2362df42051cd --- /dev/null +++ b/docs/examples/zh/exporters/STLExporter.html @@ -0,0 +1,75 @@ + + + + + + + + + + + +

        STL导出器([name])

        + +

        + 一个用于 STL 文件格式的导出器。

        + [link:https://en.wikipedia.org/wiki/STL_(file_format) STL] 文件仅描述三维对象的表面几何,不包含颜色、纹理或其他常见的模型属性。STL 格式指定了 ASCII + 和二进制两种表示方式,其中二进制表示更加紧凑。STL 文件不包含比例信息或索引,单位是任意的。 +

        + +

        导入

        + +

        + [name] 是一个附加组件,必须显式导入。请参阅 [link:#manual/introduction/Installation Installation / Addons]。 +

        + + + import { STLExporter } from 'three/addons/exporters/STLExporter.js'; + + +

        代码示例

        + + + // Instantiate an exporter + const exporter = new STLExporter(); + + // Configure export options + const options = { binary: true } + + // Parse the input and generate the STL encoded output + const result = exporter.parse( mesh, options ); + + +

        例子

        + +

        + [example:misc_exporter_stl] +

        + +

        构造函数

        + +

        [name]()

        +

        + 创建一个新的 [name] 实例。 +

        + +

        方法

        + +

        [method:Object parse]( [param:Object3D scene], [param:Object options] )

        + +

        + [page:Object3D scene] — 场景、网格或其他包含要编码的网格的基于 Object3D 的类。
        + [page:Options options] — 可选的导出选项
        +

          +
        • binary - bool. 返回 ASCII 编码字符串或二进制数据缓冲区。默认为 `false`。
        • +
        +

        + +

        源代码

        + +

        + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/exporters/STLExporter.js examples/jsm/exporters/STLExporter.js] +

        + + + diff --git a/docs/examples/zh/geometries/ConvexGeometry.html b/docs/examples/zh/geometries/ConvexGeometry.html index 449f6d850581ca..28fd531885af53 100644 --- a/docs/examples/zh/geometries/ConvexGeometry.html +++ b/docs/examples/zh/geometries/ConvexGeometry.html @@ -14,7 +14,7 @@

        凸包几何体([name])

        [name] 可被用于为传入的一组点生成凸包。 该任务的平均时间复杂度被认为是O(nlog(n))。

        -

        进口

        +

        导入

        [name] 是一个附加组件,必须显式导入。 diff --git a/docs/examples/zh/geometries/DecalGeometry.html b/docs/examples/zh/geometries/DecalGeometry.html index 7234d3d139ea2a..364039eca5b70b 100644 --- a/docs/examples/zh/geometries/DecalGeometry.html +++ b/docs/examples/zh/geometries/DecalGeometry.html @@ -13,7 +13,7 @@

        贴花几何体([name])

        [name] 可被用于创建贴花网格物体,以达到不同的目的,例如:为模型增加独特的细节、进行动态的视觉环境改变或覆盖接缝。

        -

        进口

        +

        导入

        [name] 是一个附加组件,必须显式导入。 diff --git a/docs/examples/zh/geometries/ParametricGeometry.html b/docs/examples/zh/geometries/ParametricGeometry.html index ea92a6686e47b8..1e464a0c2c8d9a 100644 --- a/docs/examples/zh/geometries/ParametricGeometry.html +++ b/docs/examples/zh/geometries/ParametricGeometry.html @@ -13,7 +13,7 @@

        参数化缓冲几何体([name])

        生成由参数表示其表面的几何体。

        -

        进口

        +

        导入

        [name] 是一个附加组件,必须显式导入。 @@ -27,7 +27,7 @@

        进口

        代码示例

        - const geometry = new THREE.ParametricGeometry( THREE.ParametricGeometries.klein, 25, 25 ); + const geometry = new THREE.ParametricGeometry( klein, 25, 25 ); const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); const klein = new THREE.Mesh( geometry, material ); scene.add( klein ); diff --git a/docs/examples/zh/geometries/TeapotGeometry.html b/docs/examples/zh/geometries/TeapotGeometry.html new file mode 100644 index 00000000000000..b5ff451e009cb9 --- /dev/null +++ b/docs/examples/zh/geometries/TeapotGeometry.html @@ -0,0 +1,70 @@ + + + + + + + + + + + + [page:BufferGeometry] → + +

        [name]

        + +

        + [name] 通过 Martin Newell 的著名 Utah 茶壶数据库进行镶嵌。 +

        + +

        导入

        + +

        + [name] 是一个附加组件,必须显式导入。 + 参见 [link:#manual/introduction/Installation Installation / Addons]. +

        + + + import { TeapotGeometry } from 'three/addons/geometries/TeapotGeometry.js'; + + +

        代码示例

        + + + const geometry = new TeapotGeometry( 50, 18 ); + const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); + const teapot = new THREE.Mesh( geometry, material ); + scene.add( teapot ); + + +

        构造函数

        + +

        + [name]([param:Integer size], [param:Integer segments], [param:Boolean bottom], [param:Boolean lid], + [param:Boolean body], + [param:Boolean fitLid], [param:Boolean blinn])属性 +

        +

        + size — 茶壶的相对尺寸。可选;默认为 `50`。
        + segments — 每个面片边缘细分的线段数。可选;默认为 `10`。
        + bottom — 是否生成茶壶底部。可选;默认为 `true`。
        + lid — 是否生成盖子。可选;默认为 `true`。
        + body — 是否生成壶身。可选;默认为 `true`。
        + fitLid — 是否稍微拉伸盖子以防止壶身和盖子之间的间隙。可选;默认为 `true`。
        + blinn — 是否垂直缩放茶壶以获得更好的外观。可选;默认为 `true`。 +

        + +

        属性

        +

        请参阅基础 [page:BufferGeometry] 类以获取通用属性。

        + +

        方法

        +

        请参阅基础 [page:BufferGeometry] 类以获取通用方法。

        + +

        源代码

        + +

        + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/geometries/TeapotGeometry.js examples/jsm/geometries/TeapotGeometry.js] +

        + + + \ No newline at end of file diff --git a/docs/examples/zh/geometries/TextGeometry.html b/docs/examples/zh/geometries/TextGeometry.html index a400dd0299491f..d848d80dd4d728 100644 --- a/docs/examples/zh/geometries/TextGeometry.html +++ b/docs/examples/zh/geometries/TextGeometry.html @@ -17,7 +17,7 @@

        文本缓冲几何体([name])

        请参阅[page:FontLoader]页面来查看更多详细信息。

        -

        进口

        +

        导入

        [name] 是一个附加组件,必须显式导入。 @@ -38,7 +38,7 @@

        代码示例

        const geometry = new TextGeometry( 'Hello three.js!', { font: font, size: 80, - height: 5, + depth: 5, curveSegments: 12, bevelEnabled: true, bevelThickness: 10, @@ -63,7 +63,7 @@

        [name]([param:String text], [param:Object parameters])

        • font — THREE.Font的实例。
        • size — Float。字体大小,默认值为100。
        • -
        • height — Float。挤出文本的厚度。默认值为50。
        • +
        • depth — Float。挤出文本的厚度。默认值为50。
        • curveSegments — Integer。(表示文本的)曲线上点的数量。默认值为12。
        • bevelEnabled — Boolean。是否开启斜角,默认为false。
        • bevelThickness — Float。文本上斜角的深度,默认值为20。
        • diff --git a/docs/examples/zh/helpers/LightProbeHelper.html b/docs/examples/zh/helpers/LightProbeHelper.html index 56ede10b7007cb..281e9c6b9d7bef 100644 --- a/docs/examples/zh/helpers/LightProbeHelper.html +++ b/docs/examples/zh/helpers/LightProbeHelper.html @@ -15,7 +15,7 @@

          [name]

          在场景中渲染一个球来可视化光照探针。

          -

          进口

          +

          导入

          [name] 是一个附加组件,必须显式导入。 @@ -63,7 +63,7 @@

          [method:undefined dispose]()

          释放内部资源。

          -

          源码

          +

          源代码

          [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/helpers/LightProbeHelper.js examples/jsm/helpers/LightProbeHelper.js] diff --git a/docs/examples/zh/helpers/PositionalAudioHelper.html b/docs/examples/zh/helpers/PositionalAudioHelper.html index d8ce6a4d376357..2d87c8a6bbeb0d 100644 --- a/docs/examples/zh/helpers/PositionalAudioHelper.html +++ b/docs/examples/zh/helpers/PositionalAudioHelper.html @@ -13,7 +13,7 @@

          [name]

          这一辅助对象显示[page:PositionalAudio]的方向锥。

          -

          进口

          +

          导入

          [name] 是一个附加组件,必须显式导入。 diff --git a/docs/examples/zh/helpers/RectAreaLightHelper.html b/docs/examples/zh/helpers/RectAreaLightHelper.html index 10bb807444c82a..1bf30f260865fe 100644 --- a/docs/examples/zh/helpers/RectAreaLightHelper.html +++ b/docs/examples/zh/helpers/RectAreaLightHelper.html @@ -15,7 +15,7 @@

          [name]

          创建一个表示 [page:RectAreaLight] 的辅助对象.

          -

          进口

          +

          导入

          [name] 是一个附加组件,必须显式导入。 @@ -64,7 +64,7 @@

          方法

          [method:undefined dispose]()

          销毁该区域光源辅助对象.

          -

          源码

          +

          源代码

          [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/helpers/RectAreaLightHelper.js examples/jsm/helpers/RectAreaLightHelper.js] diff --git a/docs/examples/zh/helpers/VertexNormalsHelper.html b/docs/examples/zh/helpers/VertexNormalsHelper.html index a1f68c8d9ed009..aa41dbbb9eb644 100644 --- a/docs/examples/zh/helpers/VertexNormalsHelper.html +++ b/docs/examples/zh/helpers/VertexNormalsHelper.html @@ -17,7 +17,7 @@

          [name]

          使用了 [page:BufferGeometry.computeVertexNormals computeVertexNormals] 方法计算了顶点法线.

          -

          进口

          +

          导入

          [name] 是一个附加组件,必须显式导入。 @@ -82,7 +82,7 @@

          [method:undefined update]()

          基于对象的运动更新顶点法线辅助对象.

          -

          源码

          +

          源代码

          [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/helpers/VertexNormalsHelper.js examples/jsm/helpers/VertexNormalsHelper.js] diff --git a/docs/examples/zh/helpers/VertexTangentsHelper.html b/docs/examples/zh/helpers/VertexTangentsHelper.html new file mode 100644 index 00000000000000..32a0815b4908d4 --- /dev/null +++ b/docs/examples/zh/helpers/VertexTangentsHelper.html @@ -0,0 +1,94 @@ + + + + + + + + + + + + [page:Object3D] → [page:Line] → [page:LineSegments] → + +

          顶点切线辅助对象([name])

          + +

          + 可视化对象的顶点切线。要求已在 [page:BufferAttribute custom attribute] 中指定切线或已使用 [page:BufferGeometry.computeTangents + computeTangents] 计算切线。

          +

          + +

          导入

          + +

          + [name] 是一个附加组件,必须显式导入。请参阅 [link:#manual/introduction/Installation Installation / Addons]. +

          + + + import { VertexTangentsHelper } from 'three/addons/helpers/VertexTangentsHelper.js'; + + +

          代码示例

          + + + const geometry = new THREE.BoxGeometry( 10, 10, 10, 2, 2, 2 ); + const material = new THREE.MeshStandardMaterial(); + const mesh = new THREE.Mesh( geometry, material ); + + const helper = new VertexTangentsHelper( mesh, 1, 0x00ffff ); + + scene.add( mesh ); + scene.add( helper ); + + +

          例子

          +

          + [example:webgl_helpers WebGL / helpers] +

          + +

          构造函数

          + + +

          [name]( [param:Object3D object], [param:Number size], [param:Hex color] )

          +

          + [page:Object3D object] -- 要为其渲染顶点切线的对象。
          + [page:Number size] -- (可选)箭头的长度。默认值为 *1*。
          + [page:Hex color] --(可选)箭头的十六进制颜色。默认值为 *0x00ffff*。 +

          + + +

          属性

          +

          有关常见属性,请参阅 [page:LineSegments] 基类。

          + +

          [property:Object matrixAutoUpdate]

          +

          + 请参阅 [page:Object3D.matrixAutoUpdate]。默认设置为 `false`,因为正在使用对象的 [page:Object3D.matrixWorld matrixWorld]。 +

          + +

          [property:Object3D object]

          +

          对其顶点切线进行可视化的对象。

          + +

          [property:Number size]

          +

          箭头的长度。默认值为 *1*。

          + + +

          方法

          +

          有关常用方法,请参阅 [page:LineSegments] 基类。

          + + +

          [method:undefined update]()

          +

          根据对象的世界变换更新顶点切线预览。

          + +

          [method:undefined dispose]()

          +

          + 释放该实例分配的GPU相关资源。每当您的应用程序中不再使用此实例时,请调用此方法。 +

          + +

          源代码

          + +

          + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/helpers/VertexTangentsHelper.js examples/jsm/helpers/VertexTangentsHelper.js] +

          + + + diff --git a/docs/examples/zh/lights/LightProbeGenerator.html b/docs/examples/zh/lights/LightProbeGenerator.html index 8444a339fee5c7..0ca49626a83f0f 100644 --- a/docs/examples/zh/lights/LightProbeGenerator.html +++ b/docs/examples/zh/lights/LightProbeGenerator.html @@ -1,5 +1,5 @@ - + @@ -14,7 +14,7 @@

          光照探针生成器([name])

          用于创建 [page:LightProbe] 实例的工具类。

          -

          进口

          +

          导入

          [name] 是一个附加组件,必须显式导入。 @@ -47,7 +47,7 @@

          [method:LightProbe fromCubeRenderTarget] ( [param:WebGLRenderer renderer], [ 立方体渲染目标的 [page:Texture.format format] 必须被设为 *RGBA*。

          -

          源码

          +

          源代码

          [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/lights/LightProbeGenerator.js examples/jsm/lights/LightProbeGenerator.js] diff --git a/docs/examples/zh/loaders/3DMLoader.html b/docs/examples/zh/loaders/3DMLoader.html new file mode 100644 index 00000000000000..6df88cae28aa9d --- /dev/null +++ b/docs/examples/zh/loaders/3DMLoader.html @@ -0,0 +1,278 @@ + + + + + + + + + + + + [page:Loader] → +

          3DM加载器([name])

          + +

          + 用于加载 Rhinoceros 3d 模型文件。

          + Rhinoceros 是一个 3D 建模器,用于创建、编辑、分析、记录、渲染、动画和转换 NURBS 曲线、曲面、breps、挤压、点云以及多边形网格和 SubD 对象。 + [link:https://github.com/mcneel/rhino3dm rhino3dm.js] 从开源几何库 [link:https://github.com/mcneel/opennurbs openNURBS] + 编译为 WebAssembly 。加载器当前使用 [link:https://www.npmjs.com/package/rhino3dm/v/8.0.1 rhino3dm.js 8.0.1.]。 +

          + +

          导入

          + +

          + [name] 是一个附加组件,必须显式导入。请参阅 [link:#manual/introduction/Installation Installation / Addons]。 +

          + + + import { Rhino3dmLoader } from 'three/addons/loaders/3DMLoader.js'; + + +

          支持的转换

          + +

          + [name] 将 Rhino 对象转换为以下 three.js 类型: +

          + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
          3dm typethree.js type
          Point[page:Points Points]
          PointSet / PointCloud[page:Points Points]
          TextDot[page:Sprite Sprite]
          Curve[page:Line Line] 1
          Mesh[page:Mesh Mesh]
          Extrusion[page:Mesh Mesh] 2
          BREP[page:Mesh Mesh] 2
          SubD[page:Mesh Mesh] 3
          InstanceReferences[page:Object3D Object3D]
          DirectionalLight[page:DirectionalLight DirectionalLight]
          PointLight[page:PointLight PointLight]
          RectangularLight[page:RectAreaLight RectAreaLight]
          SpotLight[page:SpotLight SpotLight]
          File3dm[page:Object3D Object3D] 4
          Material / Physically Based Material[page:MeshPhysicalMaterial MeshPhysicalMaterial]
          + +

          注意:

          + +

          + 1 NURBS 曲线被离散化为硬编码分辨率。 +

          +

          + 2 基于 BREP 和 NURBS 曲面的类型用其“渲染网格”表示。如果渲染网格未在 Rhino 中以适当的显示模式显示(即“着色”、“渲染”等),或者以编程方式创建(例如通过 Grasshopper + 或直接使用 rhino3dm),则渲染网格可能不会与这些对象关联图书馆。从 rhino3dm.js@8.0.0-beta2 开始,BrepFace 和 Extrusions 可以分配网格表示,但这些必须由用户生成。 +

          +

          + 3 SubD 对象通过细分其控制网来表示。 +

          +

          + 4 无论 Rhino 文件(File3dm)被加载还是解析,返回的对象都是一个 [page:Object3D + Object3D] ,所有 Rhino 对象(File3dmObject)都是子对象。File3dm 图层和其他文件级属性将添加到生成对象的 userData 中。 +

          +

          + 5 所有生成的 Three.js 对象都具有来自填充在其 userData 对象中的 Rhino 对象的有用属性(即图层索引、名称等)。 +

          +

          + 6 Rhino 和 Three.js 有不同的坐标系。导入后,您应该将生成的 [page:Object3D Object3D] 在 x 方向上旋转 -90° 或在应用程序开始时设置 + THREE.Object3D.DEFAULT_UP: + THREE.Object3D.DEFAULT_UP.set( 0, 0, 1 ); + 请记住,这将影响应用程序中所有 Object3D 的方向。 +

          + +

          例子

          + +

          + [example:webgl_loader_3dm] +

          + +

          构造函数

          + +

          Rhino3dmLoader( [param:LoadingManager manager] )

          +

          + [page:LoadingManager manager] — 加载器使用的 [page:LoadingManager loadingManager]。默认值为 [page:LoadingManager + THREE.DefaultLoadingManager]。 +

          +

          + 创建一个新的 Rhino3dmLoader。 +

          + +

          属性

          +

          有关常用属性,请参阅 [page:Loader] 基类。

          + +

          方法

          +

          有关常用方法,请参阅 [page:Loader] 基类。

          + +

          [method:Object3D load]( [param:String url], [param:Function onLoad], [param:Function onProgress], [param:Function + onError] )

          +

          + [page:String url] — 包含 `.3dm` 文件路径/URL的字符串
          + [page:Function onLoad] — 加载成功完成后调用的函数
          + [page:Function onProgress] — (可选)加载过程中调用的函数。参数将是 XMLHttpRequest 实例,其中包含 .[page:Integer total] 和 .[page:Integer + loaded] 字节。如果服务器没有设置 Content-Length,.[page:Integer total] 将为 0。
          + [page:Function onError] — (可选)加载期间发生错误时调用的函数。该函数接收错误作为参数。
          +

          +

          + 开始从 url 加载并在解析得到 Object3d 后调用 `onLoad` 。 +

          + + + // Instantiate a loader + const loader = new Rhino3dmLoader(); + + // Specify path to a folder containing WASM/JS libraries or a CDN. + // For example, /jsm/libs/rhino3dm/ is the location of the library inside the three.js repository + // loader.setLibraryPath( '/path_to_library/rhino3dm/' ); + loader.setLibraryPath( 'https://cdn.jsdelivr.net/npm/rhino3dm@8.0.1/' ); + + // Load a 3DM file + loader.load( + // resource URL + 'model.3dm', + // called when the resource is loaded + function ( object ) { + + scene.add( object ); + + }, + // called as loading progresses + function ( xhr ) { + + console.log( ( xhr.loaded / xhr.total * 100 ) + '% loaded' ); + + }, + // called when loading has errors + function ( error ) { + + console.log( 'An error happened' ); + + } + ); + + +

          [method:Object3D parse]( [param:ArrayBuffer buffer], [param:Function onLoad], [param:Function onProgress], + [param:Function onError] )

          +

          + [page:ArrayBuffer buffer] — 代表 Rhino `File3dm` 文档的 ArrayBuffer
          + [page:Function onLoad] — 加载成功完成后调用的函数。
          + [page:Function onError] — (可选)加载期间发生错误时调用的函数。该函数接收错误作为参数。
          +

          +

          + 解析 File3dm ArrayBuffer 并在得到 Object3d 后调用 `onLoad`。 + 请参阅 [link:https://github.com/mcneel/rhino-developer-samples/tree/7/rhino3dm/js/SampleParse3dmObjects this example] + 以获取更多参考。 +

          + + + import rhino3dm from 'https://cdn.jsdelivr.net/npm/rhino3dm@8.0.1' + + // Instantiate a loader + const loader = new Rhino3dmLoader(); + + // Specify path to a folder containing WASM/JS libraries or a CDN. + loader.setLibraryPath( 'https://cdn.jsdelivr.net/npm/rhino3dm@8.0.1' ); + + const rhino = await rhino3dm(); + console.log('Loaded rhino3dm.'); + + // create Rhino Document and add a point to it + const doc = new rhino.File3dm(); + const ptA = [0, 0, 0]; + const point = new rhino.Point( ptA ); + doc.objects().add( point, null ); + + // create a copy of the doc.toByteArray data to get an ArrayBuffer + const buffer = new Uint8Array( doc.toByteArray() ).buffer; + + loader.parse( buffer, function ( object ) { + + scene.add( object ); + + } ); + + + +

          [method:this setLibraryPath]( [param:String value] )

          +

          + [page:String value] — 包含 JS 和 WASM 库的文件夹的路径。 +

          + + // Instantiate a loader + const loader = new Rhino3dmLoader(); + + // Specify path to a folder containing the WASM/JS library: + loader.setLibraryPath( '/path_to_library/rhino3dm/' ); + // or from a CDN: + loader.setLibraryPath( 'https://cdn.jsdelivr.net/npm/rhino3dm@8.0.1' ); + + +

          [method:this setWorkerLimit]( [param:Number workerLimit] )

          +

          + [page:Number workerLimit] - 要分配的最大 worker 数量。默认值为 4。
          +

          +

          + 设置解码期间要使用的 [link:https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers + Web Workers] 的最大数量。如果 worker 还负责应用程序中的其他任务,则较低的限制可能更好。 +

          + +

          [method:this dispose]()

          +

          + 处理加载程序资源并释放内存。 +

          + +

          源代码

          + +

          + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/loaders/3DMLoader.js examples/jsm/loaders/3DMLoader.js] +

          + + + diff --git a/docs/examples/zh/loaders/DRACOLoader.html b/docs/examples/zh/loaders/DRACOLoader.html new file mode 100644 index 00000000000000..3db2fd1f243617 --- /dev/null +++ b/docs/examples/zh/loaders/DRACOLoader.html @@ -0,0 +1,159 @@ + + + + + + + + + + [page:Loader] → +

          [name]

          + +

          + 一个用于加载经过Draco压缩的图形库。

          + [link:https://google.github.io/draco/ Draco]是一个开源的库,主要用于压缩和解压缩三维模型及点云。 + 以客户端上解压缩为代价,显著减少压缩的图形。 +

          + +

          + 独立的Draco文件后缀为`.drc`,其中包含顶点坐标,法线,颜色和其他的属性, + Draco文件*不*包含材质,纹理,动画和节点结构-为了能使用这些特征,需要将Draco图形 + 嵌入到GLTF文件中。使用[link:https://github.com/AnalyticalGraphicsInc/gltf-pipeline glTF-Pipeline]可以将一个普通的GLTF文件转化为经过Draco压缩的GLTF文件。 + 当使用Draco压缩的GLTF模型时,[page:GLTFLoader]内部会调用DRACOLoader。 +

          + +

          + 推荐创建一个DRACOLoader实例并重用,可以有效避免重复创建加载多个解压器实例。 +

          + +

          导入

          + +

          + [name]是一个插件,必须显示引用。 + 请参考 [link:#manual/introduction/Installation Installation / Addons]。 +

          + + + import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js'; + + +

          代码示例

          + + + // Instantiate a loader + const loader = new DRACOLoader(); + + // Specify path to a folder containing WASM/JS decoding libraries. + loader.setDecoderPath( '/examples/jsm/libs/draco/' ); + + // Optional: Pre-fetch Draco WASM/JS module. + loader.preload(); + + // Load a Draco geometry + loader.load( + // resource URL + 'model.drc', + // called when the resource is loaded + function ( geometry ) { + + const material = new THREE.MeshStandardMaterial( { color: 0x606060 } ); + const mesh = new THREE.Mesh( geometry, material ); + scene.add( mesh ); + + }, + // called as loading progresses + function ( xhr ) { + + console.log( ( xhr.loaded / xhr.total * 100 ) + '% loaded' ); + + }, + // called when loading has errors + function ( error ) { + + console.log( 'An error happened' ); + + } + ); + + +

          例子

          + +

          + [example:webgl_loader_draco] +

          + +

          浏览器兼容性

          + +

          DRACOLoader会根据浏览器的特性,自动使用JS或者WASM解码库。

          + +
          +
          + +

          构造函数

          + +

          [name]( [param:LoadingManager manager] )

          +

          + [page:LoadingManager manager] — 加载器使用的[page:LoadingManager loadingManager]. 默认值为[page:LoadingManager THREE.DefaultLoadingManager]. +

          +

          + 创建一个新的[name]. +

          + +

          属性

          +

          查看公共属性请参考基类[page:Loader]

          + +

          方法

          +

          查看公共方法请查看基类[page:Loader]

          + +

          [method:undefined load]( [param:String url], [param:Function onLoad], [param:Function onProgress], [param:Function onError] )

          +

          + [page:String url] — 一个`.drc`文件的路径或者网络地址。
          + [page:Function onLoad] — 加载成功之后调用的函数。
          + [page:Function onProgress] — (可选的) 正在加载时调用的函数。参数为XMLHttpRequest实例,包含.[page:Integer total]和.[page:Integer loaded]
          + [page:Function onError] — (可选的)加载出现错误时调用的函数。该函将错误信息作为参数。
          +

          +

          + 开始加载解压缩的图形并调用`onLoad`函数。 +

          + +

          [method:this setDecoderPath]( [param:String value] )

          +

          + [page:String value] — 包含JS和WASM解压缩库的文件夹路径。 +

          + +

          [method:this setDecoderConfig]( [param:Object config] )

          +

          + [page:String config.type] - (可选的) `"js"`或`"wasm"`。
          +

          +

          + 为解压缩库提供配置,在解压开始后不能修改。 +

          + +

          [method:this setWorkerLimit]( [param:Number workerLimit] )

          +

          + [page:Number workerLimit] - 可以分配的最大线程数。默认值为4。
          +

          +

          + 设置用于解码的[link:https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers Web Workers]的最大值。如果线程也用于其他的任务,给定更小的限制会更合理。 + +

          + +

          [method:this preload]()

          +

          + 如果还没加载完成,则请求解压库。 +

          + +

          [method:this dispose]()

          +

          + 处理解压资源和释放的内存。在处理后,解码器 + [link:https://github.com/google/draco/issues/349 不能重新加载]. +

          + +

          源代码

          + +

          + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/loaders/DRACOLoader.js examples/jsm/loaders/DRACOLoader.js] +

          + + diff --git a/docs/examples/zh/loaders/FontLoader.html b/docs/examples/zh/loaders/FontLoader.html index 427132c32abc45..e7849c8ddcedd2 100644 --- a/docs/examples/zh/loaders/FontLoader.html +++ b/docs/examples/zh/loaders/FontLoader.html @@ -18,7 +18,7 @@

          [name]

          你可以使用[link:https://gero3.github.io/facetype.js/ facetype.js]来在线转换字体。

          -

          进口

          +

          导入

          [name] 是一个附加组件,必须显式导入。 @@ -93,7 +93,7 @@

          [method:Font parse]( [param:Object json] )

          解析一个JSON>格式的对象,并返回一个font。

          -

          +

          源代码

          [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/loaders/FontLoader.js examples/jsm/loaders/FontLoader.js] diff --git a/docs/examples/zh/loaders/GLTFLoader.html b/docs/examples/zh/loaders/GLTFLoader.html index d8f58ef48354ec..f9b03a9f8359f3 100644 --- a/docs/examples/zh/loaders/GLTFLoader.html +++ b/docs/examples/zh/loaders/GLTFLoader.html @@ -24,7 +24,7 @@

          GLTF加载器([name])

          and they require special handling during the disposal process. More information in the [link:https://threejs.org/docs/#manual/en/introduction/How-to-dispose-of-objects How to dispose of objects] guide.

          -

          进口

          +

          导入

          [name] 是一个附加组件,必须显式导入。 @@ -45,6 +45,7 @@

          扩展

          • KHR_draco_mesh_compression
          • KHR_materials_clearcoat
          • +
          • KHR_materials_dispersion
          • KHR_materials_ior
          • KHR_materials_specular
          • KHR_materials_transmission
          • @@ -52,9 +53,9 @@

            扩展

          • KHR_materials_unlit
          • KHR_materials_volume
          • KHR_mesh_quantization
          • -
          • KHR_lights_punctual1
          • +
          • KHR_lights_punctual
          • KHR_texture_basisu
          • -
          • KHR_texture_transform2
          • +
          • KHR_texture_transform
          • EXT_texture_webp
          • EXT_meshopt_compression
          • EXT_mesh_gpu_instancing
          • @@ -65,25 +66,12 @@

            扩展

              -
            • [link:https://github.com/takahirox/three-gltf-extensions KHR_materials_variants]3
            • +
            • [link:https://github.com/takahirox/three-gltf-extensions KHR_materials_variants]1
            • [link:https://github.com/takahirox/three-gltf-extensions MSFT_texture_dds]

            - 1Requires [link:https://threejs.org/docs/#api/en/renderers/WebGLRenderer.useLegacyLights useLegacyLights] to be disabled. -

            -

            - 2支持UV变换,但存在一些重要的限制。 - Transforms applied to - a texture using the first UV slot (all textures except aoMap and lightMap) must share the same - transform, or no transform at all. - aoMap 和 lightMap 纹理不能被变换。每个材质最多只能使用一次变换。 - 请参阅#[link:https://github.com/mrdoob/three.js/pull/13831 13831] 和 - #[link:https://github.com/mrdoob/three.js/issues/12788 12788]。 -

            - -

            - 3You can also manually process the extension after loading in your application. See [link:https://threejs.org/examples/#webgl_loader_gltf_variants Three.js glTF materials variants example]. + 1You can also manually process the extension after loading in your application. See [link:https://threejs.org/examples/#webgl_loader_gltf_variants Three.js glTF materials variants example].

            代码示例

            @@ -209,7 +197,7 @@

            [method:undefined load]( [param:String url], [param:Function onLoad], [param

            [method:this setDRACOLoader]( [param:DRACOLoader dracoLoader] )

            - [page:DRACOLoader dracoLoader] — THREE.DRACOLoader的实例,用于解码使用KHR_draco_mesh_compression扩展压缩过的文件。 + [page:DRACOLoader dracoLoader] — DRACOLoader的实例,用于解码使用KHR_draco_mesh_compression扩展压缩过的文件。

            请参阅[link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm/libs/draco#readme readme]来了解Draco及其解码器的详细信息。 diff --git a/docs/examples/zh/loaders/KTX2Loader.html b/docs/examples/zh/loaders/KTX2Loader.html new file mode 100644 index 00000000000000..0fe2de7ad30d47 --- /dev/null +++ b/docs/examples/zh/loaders/KTX2Loader.html @@ -0,0 +1,141 @@ + + + + + + + + + + + + [page:Loader] → + +

            KTX2加载器([name])

            + +

            + KTX 2.0 GPU 纹理容器的加载程序。

            + + [link:http://github.khronos.org/KTX-Specification/ KTX 2.0] 是各种 GPU 纹理格式的容器格式。该加载器支持 Basis Universal GPU + 纹理,可以快速转码为多种 GPU 纹理压缩格式。虽然 KTX 2.0 还允许其他特定于硬件的格式,但此加载程序尚未解析它们。 +

            + +

            + 该加载程序解析 KTX 2.0 容器并将其转码为受支持的 GPU 压缩纹理格式。所需的 WASM 转码器和 JS 包装器可从 + [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm/libs/basis examples/jsm/libs/basis] 目录中获取。 +

            + +

            导入

            + +

            + [name] 是一个附加组件,必须显式导入。请参阅 [link:#manual/introduction/Installation Installation / Addons]。 +

            + + + import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js'; + + +

            代码示例

            + + + var ktx2Loader = new KTX2Loader(); + ktx2Loader.setTranscoderPath( 'examples/jsm/libs/basis/' ); + ktx2Loader.detectSupport( renderer ); + ktx2Loader.load( 'diffuse.ktx2', function ( texture ) { + + var material = new THREE.MeshStandardMaterial( { map: texture } ); + + }, function () { + + console.log( 'onProgress' ); + + }, function ( e ) { + + console.error( e ); + + } ); + + +

            例子

            + +

            + [example:webgl_loader_texture_ktx2] +

            + +

            浏览器兼容性

            + +

            + 该加载器依赖于旧版浏览器不支持的 Web Assembly。 +

            + +
            +
            + +

            构造函数

            + +

            [name]( [param:LoadingManager manager] )

            +

            + [page:LoadingManager manager] — 供加载器使用的 [page:LoadingManager] 。默认值为 [page:LoadingManager + THREE.DefaultLoadingManager]。 +

            +

            + 创建一个新的 [name]。 +

            + +

            属性

            +

            有关常用属性,请参阅 [page:Loader] 基类

            + +

            Methods

            +

            有关常用方法,请参阅 [page:Loader] 基类

            + +

            [method:CompressedTexture load]( [param:String url], [param:Function onLoad], [param:Function onProgress], + [param:Function onError] )

            +

            + [page:String url] — 包含 `.ktx2` 文件路径/URL 的字符串。
            + [page:Function onLoad] — 加载成功完成后调用的函数。
            + [page:Function onProgress] — (可选)加载过程中调用的函数。参数将是 XMLHttpRequest 实例,其中包含 .[page:Integer total] 和 .[page:Integer + loaded] 字节。如果服务器没有设置 Content-Length,.[page:Integer total] 将为 0。
            + [page:Function onError] — (可选)加载期间发生错误时调用的函数。该函数接收错误作为参数。
            +

            +

            + 从 url 加载并在转码后 [page:CompressedTexture] 调用 `onLoad` 函数。 +

            + +

            [method:this detectSupport]( [param:WebGLRenderer renderer] )

            +

            + [page:WebGLRenderer renderer] — 渲染器实例。 +

            +

            + 检测可用压缩纹理格式的硬件支持,以确定转码器的输出格式。必须在加载纹理之前调用。 +

            + +

            [method:this setTranscoderPath]( [param:String path] )

            +

            + [page:String path] — 包含 WASM 转码器和 JS 包装器的文件夹路径。 +

            +

            + WASM 转码器和 JS 包装器可从 [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm/libs/basis + examples/jsm/libs/basis] 目录中获取。 +

            + +

            [method:this setWorkerLimit]( [param:Number limit] )

            +

            + [page:Number limit] — 最大 worker 数量。默认值为 '4'。 +

            +

            + 设置此实例要分配的最大 Web Worker 数量。 +

            + +

            [method:this dispose]()

            +

            + 处置加载器对象,取消分配创建的所有 Web Worker。 +

            + +

            源代码

            + +

            + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/loaders/KTX2Loader.js examples/jsm/loaders/KTX2Loader.js] +

            + + + diff --git a/docs/examples/zh/loaders/LDrawLoader.html b/docs/examples/zh/loaders/LDrawLoader.html new file mode 100644 index 00000000000000..4e4128109c69b1 --- /dev/null +++ b/docs/examples/zh/loaders/LDrawLoader.html @@ -0,0 +1,218 @@ + + + + + + + + + + + + [page:Loader] → + +

            LDraw加载器([name])

            + +

            用于加载 `LDraw` 资源。

            + [link:https://ldraw.org LDraw] (LEGO Draw) 是一种 + [link:https://ldraw.org/article/218.html 开放格式规范] ,用于描述 LEGO 和其他建筑套装 3D 模型。

            + +

            LDraw 资源(通常扩展名为 .ldr、.dat 或 .txt 的文本文件)可以仅描述单个构造件或整个模型。对于模型,LDraw 文件可以引用其他 LDraw 文件,这些文件是从使用 [page:Function + setPartsLibraryPath] 设置的库路径加载的。您通常下载 LDraw 官方零件库,解压到一个文件夹并将 setPartsLibraryPath 指向它。 +

            + +

            库部件将通过子文件夹“parts”、“p” 和 “models” 中的反复试验来加载。这些文件访问对于 Web 环境来说并不是最佳的,因此我们制作了一个脚本工具来将 LDraw + 文件及其所有依赖项打包到一个文件中,这样加载速度会更快。请参阅“打包 LDraw 模型”部分。LDrawLoader 示例加载多个打包文件。由于官方零件库较大,因此不包含在内。

            + +

            导入

            + +

            + [name] 是一个附加组件,必须显式导入。请参阅 [link:#manual/introduction/Installation Installation / Addons]. +

            + + + import { LDrawLoader } from 'three/addons/loaders/LDrawLoader.js'; + + +

            扩展

            + +

            + [name] 支持以下扩展: +

            + +
              +
            • !COLOUR: 颜色和表面光洁度声明。
            • +
            • BFC: 背面剔除规范。
            • +
            • !CATEGORY: 模型/零件类别声明。
            • +
            • !KEYWORDS: 模型/零件关键字声明。
            • +
            + +

            代码示例

            + + + // Instantiate a loader + const loader = new LDrawLoader(); + + // Optionally set library parts path + // loader.setPartsLibraryPath( path to library ); + + // Load a LDraw resource + loader.load( + // resource URL + 'models/car.ldr_Packed.mpd', + // called when the resource is loaded + function ( group ) { + + // Optionally, use LDrawUtils.mergeObject() from + // 'examples/jsm/utils/LDrawUtils.js' to merge all + // geometries by material (it gives better runtime + // performance, but building steps are lost) + // group = LDrawUtils.mergeObject( group ); + + scene.add( group ); + + }, + // called while loading is progressing + function ( xhr ) { + + console.log( ( xhr.loaded / xhr.total * 100 ) + '% loaded' ); + + }, + // called when loading has errors + function ( error ) { + + console.log( 'An error happened' ); + + } + ); + + +

            例子

            + +

            + [example:webgl_loader_ldraw] +

            + +

            包装 LDraw 模型

            + +

            要将模型及其所有引用文件打包,请下载 [link:https://www.ldraw.org/parts/latest-parts.html 官方 LDraw 零件库] + 并使用以下节点脚本:[link:https://github.com/mrdoob/three.js/blob/master/utils/packLDrawModel.js utils/packLDrawModel.js] + 它包含有关如何设置文件和执行文件的说明。

            + +

            .userData 中的元数据

            + +

            [name] 返回一个包含对象层次结构的 [page:Group] 。根据每个子对象类型,其 .userData 成员将包含以下成员:
            + 在 [page:Group]中, userData 成员将包含:
            +

              +
            • .numBuildingSteps: 仅在 [page:Group] 根部,表示模型中构建步骤的总数。这些可用于设置对象的可见性以显示不同的构建步骤,这在示例中完成。
            • +
            • .buildingStep: 表示该步骤的构建索引。
            • +
            • .category: 如果不为空,则包含该作品或模型的 [page:String] 类别。
            • +
            • .keywords: 如果不为空,则包含该作品或模型的 [page:String] 关键字数组。
            • +
            +

            +

            在 [page:Material] 中,userData 成员将包含: +

              +
            • .code: 表示该材料的 LDraw 代码。
            • +
            • .edgeMaterial: 仅在 [page:Mesh] 材质中,表示属于相同颜色代码的边的 [page:LineBasicMaterial] (在LDraw格式中,每个表面材质也与一个边缘材质相关)
            • +
            • .conditionalEdgeMaterial: 仅在 [page:LineSegments] 材质中,表示属于相同颜色代码的条件边的 [page:Material]。
            • +
            +

            + +
            +
            + +

            构造函数

            + +

            [name]( [param:LoadingManager manager] )

            +

            + [page:LoadingManager manager] — 加载器使用的 [page:LoadingManager loadingManager] 。默认值为 [page:LoadingManager + THREE.DefaultLoadingManager]。 +

            +

            + 创建一个新的 [name]。 +

            + +

            Properties

            +

            有关公共属性,请参阅 [page:Loader] 基类。

            + +

            Methods

            +

            有关常用方法,请参阅 [page:Loader] 基类。

            + +

            [method:undefined load]( [param:String url], [param:Function onLoad], [param:Function onProgress], [param:Function + onError] )

            +

            + [page:String url] — 包含 LDraw 文件的路径/URL 的字符串。
            + [page:Function onLoad] — 加载成功完成后调用的函数。该函数接收从 [page:Function parse] 返回的加载的 JSON 响应。
            + [page:Function onProgress] — (可选)加载过程中调用的函数。参数将是 XMLHttpRequest 实例,其中包含 .[page:Integer total] 和 .[page:Integer + loaded] 字节。如果服务器没有设置 Content-Length,.[page:Integer total] 将为 0。
            + [page:Function onError] — (可选)加载期间发生错误时调用的函数。该函数接收错误作为参数。
            +

            +

            + 开始从 url 加载并使用解析的响应内容调用回调函数。 +

            + +

            [method:this setPartsLibraryPath]( [param:String path] )

            +

            + [page:String path] — 用于加载引用零件的库零件文件的路径。这与 [page:Loader.setPath] 不同,后者指示加载主资源的路径。
            +

            +

            + 必须在 [page:.load] 之前调用此方法,除非要加载的模型不引用库部件(通常它将是一个所有部件都打包在单个文件中的模型)。 +

            + +

            [method:this setFileMap]( [param:Map fileMap] )

            +

            + [page:Map map] — 设置从 [page:String] 到 [page:String] 的映射,将引用的库文件名映射到新文件名。如果未指定 + fileMap(默认值),则将通过子文件夹“parts”、“p”和“models”中的反复试验来访问库部件。 +

            + +

            [method:undefined parse]( [param:String text], [param:String path], [param:Function onLoad], [param:Function + onError] )

            +

            + [page:String text] — 要解析的 LDraw 资源,作为字符串。
            + [page:String path] — 从中查找其他引用的 LDraw 资源文件的基本路径。
            + [page:Function onLoad] — 解析完成时调用的函数。
            +

            +

            + 将 LDraw 文件内容解析为字符串,并在完成时触发 [page:Function onLoad] 回调。[page:Function onLoad] 的参数将是一个 [page:Group],其中包含 + [page:Group]、[page:Mesh] 和 + [page:LineSegments] 的层次结构(以及 .userData 字段中的其他零件数据)。 +

            + +

            [method:Material getMaterial]( [param:String colourCode] )

            +

            + [page:String colourCode] — 用于获取关联 [page:Material] 的颜色代码。 +

            + +

            [method:String getMainMaterial]()

            +

            + 返回主 LDraw 颜色的 [page:Material]。 +

            + +

            对于已加载的 LDraw 资源,返回与主颜色代码关联的 [page:Material]。此方法可用于修改模型或暴露模型的零件的主要材质。 +

            + +

            + 主要颜色代码是为 LDraw + 零件着色的标准方法。三角形为“16”,边为“24”。通常,完整的模型不会暴露主要颜色(也就是说,没有零件在顶层使用代码“16”,因为它们被分配了其他特定颜色)另一方面,LDraw零件文件将暴露代码“16”可以着色,并且可以有附加的固定颜色。 +

            + +

            [method:String getMainEdgeMaterial]()

            +

            + 返回边缘主 LDraw 颜色的 [page:Material]。 +

            + +

            [method:void preloadMaterials]( [param:String path] )

            +

            + [page:String path] — LDraw 材料资源的路径。 +

            + +

            此异步方法从单个 LDraw 文件预加载材质。在官方零件库中,有一个特殊文件,始终首先加载(LDConfig.ldr)并包含所有标准颜色代码。此方法旨在与未打包的文件一起使用,例如在预加载材料并按需加载部件的编辑器中。

            + +

            源代码

            + +

            + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/loaders/LDrawLoader.js examples/jsm/loaders/LDrawLoader.js] +

            + + + diff --git a/docs/examples/zh/loaders/LUT3dlLoader.html b/docs/examples/zh/loaders/LUT3dlLoader.html new file mode 100644 index 00000000000000..403dde139caa52 --- /dev/null +++ b/docs/examples/zh/loaders/LUT3dlLoader.html @@ -0,0 +1,84 @@ + + + + + + + + + + [page:Loader] → + +

            [name]

            + +

            + 支持.3dl文件格式的3D LUT加载器。
            + 参考资料如下: +

            + +
              +
            • [link:http://download.autodesk.com/us/systemdocs/help/2011/lustre/index.html?url=./files/WSc4e151a45a3b785a24c3d9a411df9298473-7ffd.htm,topicNumber=d0e9492]
            • +
            • [link:https://community.foundry.com/discuss/topic/103636/format-spec-for-3dl?mode=Post&postID=895258]
            • +
            + +

            导入

            + +

            + [name] 是一个附加组件,必须显式导入。 + 请参阅 [link:#manual/introduction/Installation Installation / Addons]. +

            + + + import { LUT3dlLoader } from 'three/addons/loaders/LUT3dlLoader.js'; + + +

            构造函数

            + +

            [name]( [param:LoadingManager manager] )

            +

            + [page:LoadingManager manager] — 加载器所使用的[page:LoadingManager loadingManager]. 默认值为[page:DefaultLoadingManager DefaultLoadingManager]
            +

            +

            + 创建一个新的 [name]. +

            + +

            属性

            +

            共有属性请参见其基类[page:Loader]。

            + +

            方法

            +

            共有方法请参见其基类[page:Loader]。

            + +

            [method:undefined load]( [param:String url], [param:Function onLoad], [param:Function onProgress], [param:Function onError] )

            +

            + [page:String url] — `.3dl`格式的文件URL或者路径.
            + [page:Function onLoad] — (可选) 加载成功完成后调用的函数。函数接收[page: function parse]方法的结果。
            + [page:Function onProgress] — (可选) 在加载过程中调用的函数。参数将是XMLHttpRequest实例,它包含[page:Integer total]和[page:Integer loaded]字节。如果服务器没有设置Content-Length报头,[page:Integer total]将为0。
            + [page:Function onError] — (可选) 在加载过程中发生错误时调用的函数。函数接收错误作为参数。
            +

            +

            + 开始从url加载并返回加载的LUT。 +

            + +

            [method:Object parse]( [param:String input] )

            +

            + [page:String input] — 3dl数据字符串。
            +

            +

            + 解析3dl数据字符串并在完成时触发[page:Function onLoad]回调。[page:Function onLoad]的参数将是一个[page:Object对象],包含以下LUT数据:[page:Number .size], [page:datattexture .texture]和[page:Data3DTexture .texture3d]。 +

            + +

            [method:this setType]( [param:Number type] )

            +

            + [page:Number type] - 纹理类型。详情请参阅[page:Textures纹理常量]页面。
            +

            +

            + 设置所需的纹理类型。支持[page:Textures THREE.UnsignedByteType]和[page:Textures THREE.FloatType]。默认为[page:Textures THREE.UnsignedByteType]. +

            + +

            源代码

            + +

            + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/loaders/[name].js examples/jsm/loaders/[name].js] +

            + + diff --git a/docs/examples/zh/loaders/LUTCubeLoader.html b/docs/examples/zh/loaders/LUTCubeLoader.html new file mode 100644 index 00000000000000..91bfc09c974709 --- /dev/null +++ b/docs/examples/zh/loaders/LUTCubeLoader.html @@ -0,0 +1,83 @@ + + + + + + + + + + [page:Loader] → + +

            [name]

            + +

            + 一个支持.cube文件格式的3D LUT加载器。
            + 基于以下参考: +

            + +
              +
            • [link:https://wwwimages2.adobe.com/content/dam/acom/en/products/speedgrade/cc/pdfs/cube-lut-specification-1.0.pdf]
            • +
            + +

            导入

            + +

            + [name]是一个附加组件,必须显式导入。 + 参见 [link:#manual/introduction/Installation Installation / Addons]。 +

            + + + import { LUTCubeLoader } from 'three/addons/loaders/LUTCubeLoader.js'; + + +

            构造函数

            + +

            [name]( [param:LoadingManager manager] )

            +

            + [page:LoadingManager manager] — 要使用的加载管理器。默认值为[page:DefaultLoadingManager DefaultLoadingManager]
            +

            +

            + 创建一个新的[name]。 +

            + +

            属性

            +

            参见基础[page:Loader]类以获取公共属性。

            + +

            方法

            +

            参见基础[page:Loader]类以获取公共方法。

            + +

            [method:undefined load]( [param:String url], [param:Function onLoad], [param:Function onProgress], [param:Function onError] )

            +

            + [page:String url] — 包含 `.cube` 文件路径/URL 的字符串。
            + [page:Function onLoad] — (可选的) 加载成功完成后要调用的函数。该函数接收 [page:Function parse] 方法的结果。
            + [page:Function onProgress] — (可选的)加载过程中要调用的函数。参数将是 XMLHttpRequest 实例,其中包含 [page:Integer total] 和 [page:Integer loaded] 字节。如果服务器未设置 Content-Length 头部;则 [page:Integer total] 将为 0。
            + [page:Function onError] — (可选的)加载过程中发生错误时要调用的函数。该函数将错误作为参数接收。
            +

            +

            + 从url开始加载并返回已加载的LUT。 +

            + +

            [method:Object parse]( [param:String input] )

            +

            + [page:String input] — cube数据字符串。
            +

            +

            + 解析一个 cube 数据字符串,并在解析完成时触发[page:Function onLoad]回调。传递给[page:Function onLoad]函数的参数将是一个[page:Object object],它包含以下LUT(查找表)数据:[page:String .title](标题),[page:Number .size](大小),[page:Vector3 .domainMin](域最小值),[page:Vector3 .domainMax](域最大值),[page:DataTexture .texture](纹理)和[page:Data3DTexture .texture3D](3D纹理)。 +

            + +

            [method:this setType]( [param:Number type] )

            +

            + [page:Number type] - 纹理类型。详情请参见[page:Textures texture constants]页面。
            +

            +

            + 设置所需的纹理类型。仅支持[page:Textures THREE.UnsignedByteType](无符号字节类型)和[page:Textures THREE.FloatType](浮点类型)。默认是[page:Textures THREE.UnsignedByteType](无符号字节类型)。 +

            + +

            源代码

            + +

            + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/loaders/[name].js examples/jsm/loaders/[name].js] +

            + + diff --git a/docs/examples/zh/loaders/MMDLoader.html b/docs/examples/zh/loaders/MMDLoader.html deleted file mode 100644 index 039935d8150d42..00000000000000 --- a/docs/examples/zh/loaders/MMDLoader.html +++ /dev/null @@ -1,134 +0,0 @@ - - - - - - - - - - [page:Loader] → - -

            MMD加载器([name])

            - -

            一个用于加载MMD资源的加载器。

            - [name]从MMD资源(例如PMD、PMX、VMD和VPD文件)中创建Three.js物体(对象)。 - 请参阅[page:MMDAnimationHelper]来了解MMD动画的处理,例如IK、Grant和Physics。

            - - 如果你想要MMD资源的原始内容,请使用.loadPMD/PMX/VMD/VPD方法。

            - -

            进口

            - -

            - [name] 是一个附加组件,必须显式导入。 - See [link:#manual/introduction/Installation Installation / Addons]. -

            - - - import { MMDLoader } from 'three/addons/loaders/MMDLoader.js'; - - -

            代码示例

            - - - // Instantiate a loader - const loader = new MMDLoader(); - - // Load a MMD model - loader.load( - // path to PMD/PMX file - 'models/mmd/miku.pmd', - // called when the resource is loaded - function ( mesh ) { - - scene.add( mesh ); - - }, - // called when loading is in progresses - function ( xhr ) { - - console.log( ( xhr.loaded / xhr.total * 100 ) + '% loaded' ); - - }, - // called when loading has errors - function ( error ) { - - console.log( 'An error happened' ); - - } - ); - - -

            例子

            -

            - [example:webgl_loader_mmd]
            - [example:webgl_loader_mmd_pose]
            - [example:webgl_loader_mmd_audio] -

            - -

            构造函数

            - -

            [name]( [param:LoadingManager manager] )

            -

            - [page:LoadingManager manager] — 加载器使用的[page:LoadingManager loadingManager](加载管理器),默认值是[page:LoadingManager THREE.DefaultLoadingManager]。 -

            -

            - 创建一个新的[name]。 -

            - -

            属性

            -

            共有属性请参见其基类[page:Loader]。

            - -

            方法

            -

            共有方法请参见其基类[page:Loader]。

            - -

            [method:undefined load]( [param:String url], [param:Function onLoad], [param:Function onProgress], [param:Function onError] )

            -

            - [page:String url] — 一个包含有.pmd.pmx文件的路径或URL的字符串。
            - [page:Function onLoad] — 当加载过程成功完成以后将被调用的函数。
            - [page:Function onProgress] — (可选)加载过程正在进行的时候被调用的函数。其参数是一个XMLHttpRequest实例,其包含了[page:Integer total] bytes(总的字节数)和[page:Integer loaded] bytes(已经载入的字节数)。
            - [page:Function onError] — (可选) 加载过程中若发生了错误将被调用的函数。这一函数接收错误作为参数。
            -

            -

            - 开始从URL中加载PMD/PMX模型文件,并使用包含有已被解析的[page:SkinnedMesh]和[page:MeshToonMaterial]数组的[page:BufferGeometry]对象来触发回调函数。 -

            - -

            [method:undefined loadAnimation]( [param:String url], [param:Object3D object], [param:Function onLoad], [param:Function onProgress], [param:Function onError] )

            -

            - [page:String url] — 一个包含有.vmd文件的路径或URL的字符串或字符串数组。如果两个及以上文件被指定,它们将会合并。
            - [page:Object3D object] — [page:SkinnedMesh] 或 [page:Camera]。 剪辑及其轨道将会适应到该对象。
            - [page:Function onLoad] — 成功加载完成后被调用的函数。
            - [page:Function onProgress] — (可选)当加载正在进行时被调用的函数,参数将是XMLHttpRequest实例,其包含了 .[page:Integer total] (总的)和 .[page:Integer loaded] (已加载的)字节数。
            - [page:Function onError] — (可选)如果加载过程中发生错误时被调用的函数,该函数接受一个错误来作为参数。
            -

            -

            - 开始从url(s)加载VMD动画文件(可能有多个文件),并使用已解析的[page:AnimatioinClip]触发回调函数。 -

            - -

            [method:undefined loadWithAnimation]( [param:String modelUrl], [param:String vmdUrl], [param:Function onLoad], [param:Function onProgress], [param:Function onError] )

            -

            - [page:String modelUrl] — 一个包含有.pmd.pmx文件的路径或URL的字符串。
            - [page:String vmdUrl] — 一个包含有.vmd文件的路径或URL的字符串或字符串数组。
            - [page:Function onLoad] — 成功加载完成后被调用的函数。
            - [page:Function onProgress] — (可选)当加载正在进行时被调用的函数,参数将是XMLHttpRequest实例,其包含了 .[page:Integer total] (总的)和 .[page:Integer loaded] (已加载的)字节数。
            - [page:Function onError] — (可选)如果加载过程中发生错误时被调用的函数,该函数接受一个错误来作为参数。
            -

            -

            - 开始从URL中加载PMD/PMX模型文件和VMD动画文件(可能有多个文件),并使用一个[page:Object] —— 包含有已解析的[page:SkinnedMesh]和适应[page:SkinnedMesh]的[page:AnimationClip],来触发回调函数。 -

            - -

            [method:this setAnimationPath]( [param:String animationPath] )

            -

            - [page:String animationPath] — 用于加载动画数据(VMD/VPD 文件)的基础路径。 Base path for loading animation data (VMD/VPD files). -

            -

            - 设置额外资源(例如贴图)的基础路径。 -

            - -

            源代码

            - -

            - [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/loaders/MMDLoader.js examples/jsm/loaders/MMDLoader.js] -

            - - diff --git a/docs/examples/zh/loaders/MTLLoader.html b/docs/examples/zh/loaders/MTLLoader.html index ec111a3a839367..55337afa5c6a29 100644 --- a/docs/examples/zh/loaders/MTLLoader.html +++ b/docs/examples/zh/loaders/MTLLoader.html @@ -16,7 +16,7 @@

            MTL加载器([name])

            用于描述一个或多个 .OBJ 文件中物体表面着色(材质)属性。

            -

            进口

            +

            导入

            [name] 是一个附加组件,必须显式导入。 @@ -58,11 +58,11 @@

            [method:this setMaterialOptions]( [param:Object options] )

            [page:Object options] — required

              -
            • side: Which side to apply the material. THREE.FrontSide (default), THREE.BackSide, THREE.DoubleSide
            • -
            • wrap: What type of wrapping to apply for textures. THREE.RepeatWrapping (default), THREE.ClampToEdgeWrapping, THREE.MirroredRepeatWrapping
            • -
            • normalizeRGB: RGBs need to be normalized to 0-1 from 0-255. Default: false, assumed to be already normalized
            • -
            • ignoreZeroRGBs: Ignore values of RGBs (Ka,Kd,Ks) that are all 0's. Default: false
            • -
            • invertTrProperty: Use values 1 of Tr field for fully opaque. This option is useful for obj exported from 3ds MAX, vcglib or meshlab. Default: false
            • +
            • side: 将要渲染哪一面。 THREE.FrontSide (默认), THREE.BackSide, THREE.DoubleSide
            • +
            • wrap: 纹理环绕方式。 THREE.RepeatWrapping (默认), THREE.ClampToEdgeWrapping, THREE.MirroredRepeatWrapping
            • +
            • normalizeRGB: RGBs 需要从 0-255 归一化为 0-1 默认: false, 认为已归一化
            • +
            • ignoreZeroRGBs: 忽略所有 RGB 值(Ka、Kd、Ks)均为 0 的情况。 默认: false
            • +
            • invertTrProperty: Tr字段的值为1表示完全不透明。此选项可用于从3DS Max,VCGLIB或MESHLAB导出的OBJ。 默认: false

            @@ -80,7 +80,7 @@

            [method:MTLLoaderMaterialCreator parse]( [param:String text, param:String pa

            -

            源码

            +

            源代码

            [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/loaders/[name].js examples/jsm/loaders/[name].js] diff --git a/docs/examples/zh/loaders/OBJLoader.html b/docs/examples/zh/loaders/OBJLoader.html index 759f5c144a5ded..745f66be47154e 100644 --- a/docs/examples/zh/loaders/OBJLoader.html +++ b/docs/examples/zh/loaders/OBJLoader.html @@ -17,7 +17,7 @@

            OBJ加载器([name])

            将使每个多边形定义为顶点列表的面以及纹理顶点。

            -

            进口

            +

            导入

            [name] 是一个附加组件,必须显式导入。 @@ -44,7 +44,7 @@

            代码示例

            scene.add( object ); }, - // called when loading is in progresses + // called when loading is in progress function ( xhr ) { console.log( ( xhr.loaded / xhr.total * 100 ) + '% loaded' ); @@ -109,7 +109,7 @@

            [method:this setMaterials]( [param:MTLLoader.MaterialCreator materials] ) -

            源码

            +

            源代码

            [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/loaders/OBJLoader.js examples/jsm/loaders/OBJLoader.js] diff --git a/docs/examples/zh/loaders/PCDLoader.html b/docs/examples/zh/loaders/PCDLoader.html index 03b5c5e259bfcc..64c433cc864d5a 100644 --- a/docs/examples/zh/loaders/PCDLoader.html +++ b/docs/examples/zh/loaders/PCDLoader.html @@ -22,7 +22,7 @@

            [name]

          -

          进口

          +

          导入

          [name] 是一个附加组件,必须显式导入。 @@ -50,7 +50,7 @@

          代码示例

          scene.add( points ); }, - // called when loading is in progresses + // called when loading is in progress function ( xhr ) { console.log( ( xhr.loaded / xhr.total * 100 ) + '% loaded' ); @@ -114,7 +114,7 @@

          [method:Object3D parse]( [param:Arraybuffer data],[param:String url] )

          该 Object3D 实例实际类型为 [page:Points],由一个 [page:BufferGeometry] 实例和一个 [page:PointsMaterial] 实例作为参数构造而成。

          -

          源码

          +

          源代码

          [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/loaders/PCDLoader.js examples/jsm/loaders/PCDLoader.js] diff --git a/docs/examples/zh/loaders/PDBLoader.html b/docs/examples/zh/loaders/PDBLoader.html new file mode 100644 index 00000000000000..a091c46af8de13 --- /dev/null +++ b/docs/examples/zh/loaders/PDBLoader.html @@ -0,0 +1,116 @@ + + + + + + + + + + + + [page:Loader] → + +

          PDB加载器([name])

          + +

          用于加载 `.pdb` 资源的加载器。
          + [link:http://en.wikipedia.org/wiki/Protein_Data_Bank_(file_format) 蛋白质数据库] 文件格式是描述分子三​​维结构的文本文件。 +

          + +

          导入

          + +

          + [name] 是一个附加组件,必须显式导入。请参阅 [link:#manual/introduction/Installation Installation / Addons]。 +

          + + + import { PDBLoader } from 'three/addons/loaders/PDBLoader.js'; + + +

          代码示例

          + + + // instantiate a loader + const loader = new PDBLoader(); + + // load a PDB resource + loader.load( + // resource URL + 'models/pdb/caffeine.pdb', + // called when the resource is loaded + function ( pdb ) { + + const geometryAtoms = pdb.geometryAtoms; + const geometryBonds = pdb.geometryBonds; + const json = pdb.json; + + console.log( 'This molecule has ' + json.atoms.length + ' atoms' ); + + }, + // called when loading is in progress + function ( xhr ) { + + console.log( ( xhr.loaded / xhr.total * 100 ) + '% loaded' ); + + }, + // called when loading has errors + function ( error ) { + + console.log( 'An error happened' ); + + } + ); + + +

          例子

          +

          + [example:webgl_loader_pdb] +

          + +

          构造函数

          + +

          [name]( [param:LoadingManager manager] )

          +

          + [page:LoadingManager manager] — 加载器使用的 [page:LoadingManager loadingManager] 。默认值为 [page:LoadingManager + THREE.DefaultLoadingManager]。 +

          +

          + 创建一个新的 [name]。 +

          + +

          属性

          +

          有关公共属性,请参阅 [page:Loader] 基类。

          + +

          Methods

          +

          有关常用方法,请参阅 [page:Loader] 基类。

          + +

          [method:undefined load]( [param:String url], [param:Function onLoad], [param:Function onProgress], [param:Function + onError] )

          +

          + [page:String url] — 包含 `.pdb` 文件路径/URL 的字符串。
          + [page:Function onLoad] — (可选)加载成功完成后调用的函数。该函数接收具有以下属性的对象。[page:BufferGeometry geometryAtoms]、[page:BufferGeometry + geometryBonds] 和 [page:Object JSON] 结构。
          + [page:Function onProgress] — (可选)加载过程中调用的函数。参数将是 XMLHttpRequest 实例,其中包含 .[page:Integer total] 和 .[page:Integer + loaded] 字节。如果服务器没有设置 Content-Length,.[page:Integer total] 将为 0。
          + [page:Function onError] —(可选)加载期间发生错误时调用的函数。该函数接收错误作为参数。
          +

          +

          + 开始从 url 加载并使用解析后的响应内容调用 onLoad。 +

          + +

          [method:Object parse]( [param:String text] )

          +

          + [page:String text] — 要解析的文本 `pdb` 结构。 +

          +

          + 解析 `pdb` 文本并返回JSON结构。
          +

          + +

          源代码

          + +

          + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/loaders/PDBLoader.js examples/jsm/loaders/PDBLoader.js] +

          + + + diff --git a/docs/examples/zh/loaders/SVGLoader.html b/docs/examples/zh/loaders/SVGLoader.html new file mode 100644 index 00000000000000..d6ed16d0b3ca64 --- /dev/null +++ b/docs/examples/zh/loaders/SVGLoader.html @@ -0,0 +1,135 @@ + + + + + + + + + + [page:Loader] → + +

          [name]

          + +

          用于加载`.svg`资源的加载器
          + [link:https://en.wikipedia.org/wiki/Scalable_Vector_Graphics 可伸缩向量图形]是XML形式的矢量图形格式,用来描述二维矢量图形并支持交互和动画。 +

          + +

          导入

          + +

          + [name]是附加功能,必须显示引用。 + 请参考[link:#manual/introduction/Installation Installation / Addons]. +

          + + + import { SVGLoader } from 'three/addons/loaders/SVGLoader.js'; + + +

          代码示例

          + + + // instantiate a loader + const loader = new SVGLoader(); + + // load a SVG resource + loader.load( + // resource URL + 'data/svgSample.svg', + // called when the resource is loaded + function ( data ) { + + const paths = data.paths; + const group = new THREE.Group(); + + for ( let i = 0; i < paths.length; i ++ ) { + + const path = paths[ i ]; + + const material = new THREE.MeshBasicMaterial( { + color: path.color, + side: THREE.DoubleSide, + depthWrite: false + } ); + + const shapes = SVGLoader.createShapes( path ); + + for ( let j = 0; j < shapes.length; j ++ ) { + + const shape = shapes[ j ]; + const geometry = new THREE.ShapeGeometry( shape ); + const mesh = new THREE.Mesh( geometry, material ); + group.add( mesh ); + + } + + } + + scene.add( group ); + + }, + // called when loading is in progress + function ( xhr ) { + + console.log( ( xhr.loaded / xhr.total * 100 ) + '% loaded' ); + + }, + // called when loading has errors + function ( error ) { + + console.log( 'An error happened' ); + + } + ); + + +

          示例

          +

          + [example:webgl_loader_svg] +

          + +

          构造函数

          + +

          [name]( [param:LoadingManager manager] )

          +

          + [page:LoadingManager manager] — 加载器使用的[page:LoadingManager 加载器管理]。默认值为[page:LoadingManager THREE.DefaultLoadingManager]。 +

          +

          + 创建一个新的[name]。 +

          + +

          属性

          +

          查看公共属性请参考基类[page:Loader]。

          + +

          方法

          +

          查看公共方法请参考基类[page:Loader]。

          + +

          [method:undefined load]( [param:String url], [param:Function onLoad], [param:Function onProgress], [param:Function onError] )

          +

          + [page:String url] — 一个`.svg`文件的路径或者网络地址。
          + [page:Function onLoad] — (可选的)加载成功之后调用的函数。该函数以[page:ShapePath]为参数。
          + [page:Function onProgress] — (可选的)正在加载时调用的函数。参数为XMLHttpRequest实例,包含[page:Integer total]和[page:Integer loaded]。 + 如果服务没有设置Content-Length头,.[page:Integer total]的值为0。
          + [page:Function onError] — (可选的)加载出现错误时调用的函数。该函将错误信息作为参数。
          +

          +

          + 根据请求的URL开始加载并根据返回内容调用onLoad。 +

          + +

          静态方法

          + +

          [method:Array createShapes]( [param:ShapePath shape] )

          +

          + [page:ShapePath shape] — 一个[page:ShapePath]的矢量图形数组,指定[page:SVGLoader]的onLoad函数的参数。
          +

          +

          + 返回一个或多个基于[param:ShapePath shape]的[page:Shape]对象,并作为该函数的一个参数返回。 +

          + +

          源代码

          + +

          + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/loaders/SVGLoader.js examples/jsm/loaders/SVGLoader.js] +

          + + diff --git a/docs/examples/zh/loaders/TGALoader.html b/docs/examples/zh/loaders/TGALoader.html new file mode 100644 index 00000000000000..a8aac1273d8fd7 --- /dev/null +++ b/docs/examples/zh/loaders/TGALoader.html @@ -0,0 +1,103 @@ + + + + + + + + + + [page:Loader] → + +

          [name]

          + +

          用于加载`.tga`资源的加载器。
          + [link:https://en.wikipedia.org/wiki/Truevision_TGA TGA]是光栅图形,图形文件格式。 +

          + +

          导入

          + +

          + [name]是附加项,必须显示的引用。请参考[link:#manual/introduction/Installation Installation / Addons]。 +

          + + + import { TGALoader } from 'three/addons/loaders/TGALoader.js'; + + +

          代码示例

          + + + // instantiate a loader + const loader = new TGALoader(); + + // load a resource + const texture = loader.load( + // resource URL + 'textures/crate_grey8.tga' + // called when loading is completed + function ( texture ) { + + console.log( 'Texture is loaded' ); + + }, + // called when the loading is in progress + function ( xhr ) { + + console.log( ( xhr.loaded / xhr.total * 100 ) + '% loaded' ); + + }, + // called when the loading fails + function ( error ) { + + console.log( 'An error happened' ); + + } + ); + + const material = new THREE.MeshPhongMaterial( { + color: 0xffffff, + map: texture + } ); + + +

          示例

          +

          + [example:webgl_loader_texture_tga] +

          + +

          构造函数

          + +

          [name]( [param:LoadingManager manager] )

          +

          + [page:LoadingManager manager] — 加载器使用的[page:LoadingManager loadingManager]。默认值为[page:LoadingManager THREE.DefaultLoadingManager]。 +

          +

          + 创建新的[name]. +

          + +

          属性

          +

          查看公共属性请参考基类[page:Loader]。

          + +

          方法

          +

          查看公共方法请参考基类[page:Loader]。

          + +

          [method:DataTexture load]( [param:String url], [param:Function onLoad], [param:Function onProgress], [param:Function onError] )

          +

          + [page:String url] — 一个`.tga`文件的路径或者网络地址。
          + [page:Function onLoad] — (可选的)加载成功之后调用的函数。该函数以[page:DataTexture]为参数。
          + [page:Function onProgress] — (可选的)正在加载时调用的函数。参数为XMLHttpRequest实例,包含[page:Integer total]和[page:Integer loaded]。 + 如果服务没有设置Content-Length头,.[page:Integer total]的值为0。
          + [page:Function onError] — (可选的)加载出现错误时调用的函数。该函将错误信息作为参数。
          +

          +

          + 开始加载[page:DataTexture texture]并传递给onLoad。即时引用会将[page:DataTexture texture]直接返回(不一定加载完成)。 +

          + +

          源代码

          + +

          + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/loaders/TGALoader.js examples/jsm/loaders/TGALoader.js] +

          + + diff --git a/docs/examples/zh/math/Lut.html b/docs/examples/zh/math/Lut.html new file mode 100644 index 00000000000000..482222adff7403 --- /dev/null +++ b/docs/examples/zh/math/Lut.html @@ -0,0 +1,141 @@ + + + + + + + + + + + +

          查找表([name])

          + +

          + 表示色彩映射的查找表。它用于从一系列数据值中确定颜色值。 +

          + +

          导入

          + +

          + [name] 是一个附加组件,必须显式导入。请参阅 [link:#manual/introduction/Installation Installation / Addons]。 +

          + + + import { Lut } from 'three/addons/math/Lut.js'; + + +

          代码示例

          + + + const lut = new Lut( 'rainbow', 512 ); + const color = lut.getColor( 0.5 ); + + +

          构造函数

          + + +

          [name]( [param:String colormap], [param:Number count] )

          +

          + colormap - 设置预定义色彩映射中的一个。可用的色彩映射有:`rainbow`, `cooltowarm`, `blackbody`, + `grayscale`。 默认为 `rainbow`。
          + count - 设置用于表示数据数组的颜色数量。默认为 `32`。 +

          + +

          属性

          + +

          [property:Array lut]

          +

          + 所选颜色映射的查找表,表示为 [page:Color] 数组。 +

          + +

          [property:Array map]

          +

          + 当前选择的颜色映射。默认是 `rainbow`。 +

          + +

          [property:Number minV]

          +

          + 用查找表表示的最小值。默认值为 *0*。 +

          + +

          [property:Number maxV]

          +

          + 用查找表表示的最大值。默认值为 *1*。 +

          + +

          [property:Number n]

          +

          + 当前所选颜色映射的颜色数。默认为 `32`。 +

          + +

          方法

          + +

          [method:this copy]( [param:Lut lut] ) [param:Lut this]

          +

          + color — Lut 复制。 +

          +

          + 复制给定的 lut。 +

          + +

          [method:this addColorMap]( [param:String name], [param:Array arrayOfColors] )

          +

          + name — 颜色映射的名称。
          + arrayOfColors — 颜色值数组。每个值都是一个数组,其中包含阈值和十六进制数形式的实际颜色值。 +

          +

          + 将一个颜色映射添加到此 [name] 实例。 +

          + +

          [method:HTMLCanvasElement createCanvas]()

          +

          + 创建一个画布以将查找表可视化为纹理。 +

          + +

          [method:Color getColor]( [param:Number alpha] )

          +

          + value -- 要显示为颜色的数据值。 +

          +

          + 返回给定数据值的 [page:Color] 实例。 +

          + +

          [method:this setColorMap]( [param:String colormap], [param:Number count] )

          +

          + colormap — 颜色映射的名称。
          + count — 颜色的数量。默认为 `32`。 +

          +

          + 为给定的颜色映射和颜色数量配置查找表。 +

          + +

          [method:this setMin]( [param:Number minV] )

          +

          + minV — 用查找表表示的最小值 +

          +

          + 设置此 [name] 的表示最小值。 +

          + +

          [method:this setMax]( [param:Number maxV] )

          +

          + maxV — 用查找表表示的最大值。 +

          +

          + 设置此 [name] 的表示最大值。 +

          + +

          [method:HTMLCanvasElement updateCanvas]( [param:HTMLCanvasElement canvas] )

          +

          + 使用 [name] 的数据更新画布。 +

          + +

          源代码

          + +

          + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/math/Lut.js examples/jsm/math/Lut.js] +

          + + + \ No newline at end of file diff --git a/docs/examples/zh/math/MeshSurfaceSampler.html b/docs/examples/zh/math/MeshSurfaceSampler.html new file mode 100644 index 00000000000000..511092946b9f7a --- /dev/null +++ b/docs/examples/zh/math/MeshSurfaceSampler.html @@ -0,0 +1,97 @@ + + + + + + + + + + + +

          网格表面采样器([name])

          + +

          用于在网格表面上对加权随机点进行采样的实用类。

          + +

          加权采样对于诸如地形的特定区域内更浓密的植被生长或来自网格特定部分的浓缩粒子排放等效果非常有用。顶点权重可以通过编程方式编写,也可以在 3D 工具(如 Blender)中作为顶点颜色手工绘制。

          + +

          导入

          + +

          + [name] 是一个附加组件,必须显式导入。请参阅 [link:#manual/introduction/Installation Installation / Addons]. +

          + + + import { MeshSurfaceSampler } from 'three/addons/math/MeshSurfaceSampler.js'; + + +

          代码示例

          + + + // Create a sampler for a Mesh surface. + const sampler = new MeshSurfaceSampler( surfaceMesh ) + .setWeightAttribute( 'color' ) + .build(); + + const mesh = new THREE.InstancedMesh( sampleGeometry, sampleMaterial, 100 ); + + const position = new THREE.Vector3(); + const matrix = new THREE.Matrix4(); + + // Sample randomly from the surface, creating an instance of the sample + // geometry at each sample point. + for ( let i = 0; i < 100; i ++ ) { + + sampler.sample( position ); + + matrix.makeTranslation( position.x, position.y, position.z ); + + mesh.setMatrixAt( i, matrix ); + + } + + scene.add( mesh ); + + +

          例子

          +

          + [example:webgl_instancing_scatter] +

          + +

          构造函数

          + +

          [name]( [param:Mesh mesh] )

          +

          + [page:Mesh mesh] — ​​进行采样的表面网格。 +

          +

          + 创建一个新的 [name]。如果输入的几何体是索引的,将创建一个非索引的副本。构造后,采样器无法返回样本,直到调用 [page:MeshSurfaceSampler.build build] 方法。 +

          + +

          方法

          + +

          [method:this setWeightAttribute]( [param:String name] )

          +

          + 指定从表面采样时用作权重的顶点属性。权重较高的面更有可能被采样,而权重为零的面则根本不会被采样。对于向量属性,仅使用 .x 进行采样。 +

          +

          如果没有选择权重属性,则按区域随机分布采样。

          + +

          [method:this build]()

          +

          + 处理输入几何体并准备返回样本。几何体或采样器的任何配置都必须在调用此方法之前进行。对于具有 n 个面的曲面,时间复杂度为O(n)。 +

          + +

          [method:this sample]( [param:Vector3 targetPosition], [param:Vector3 targetNormal], [param:Color targetColor], + [param:Vector2 targetUV] )

          +

          + 选择输入几何体表面上的随机点,返回该点的位置以及可选的法线向量、颜色和 UV 坐标。对于具有 n 个面的曲面,时间复杂度为O(n)。 +

          + +

          源代码

          + +

          + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/math/MeshSurfaceSampler.js examples/jsm/math/MeshSurfaceSampler.js] +

          + + + diff --git a/docs/examples/zh/math/OBB.html b/docs/examples/zh/math/OBB.html new file mode 100644 index 00000000000000..a9ecb9012c3484 --- /dev/null +++ b/docs/examples/zh/math/OBB.html @@ -0,0 +1,193 @@ + + + + + + + + + + + +

          定向包围盒([name])

          + +

          + 表示三维空间中的定向包围盒(OBB)。 +

          + +

          导入

          + +

          + [name] 是一个附加组件,必须显式导入。请参阅 [link:#manual/introduction/Installation Installation / Addons]。 +

          + + + import { OBB } from 'three/addons/math/OBB.js'; + + +

          例子

          +

          + [example:webgl_math_obb] +

          + +

          构造函数

          + +

          [name]( [param:Vector3 center], [param:Vector3 halfSize], [param:Matrix3 rotation] )

          +

          + [page:Vector3 center] — [name] 的中心。(可选)
          + [page:Vector3 halfSize] — [name] 沿每个轴的正半宽范围。(可选)
          + [page:Matrix3 rotation] — [name] 的旋转。(可选) +

          +

          + 创建一个新的 [name]。 +

          + +

          属性

          + +

          [property:Vector3 center]

          +

          + [name] 的中心。默认值为 *( 0, 0, 0 )*。 +

          + +

          [property:Vector3 halfSize]

          +

          + [name] 沿每个轴的正半宽范围。默认值为 *( 0, 0, 0 )*。 +

          + +

          [property:Matrix3 rotation]

          +

          + [name] 的旋转。默认为单位矩阵。 +

          + +

          方法

          + +

          [method:this applyMatrix4]( [param:Matrix4 matrix] )

          +

          + [page:Matrix4 matrix] — 一个 4x4 变换矩阵。 +

          +

          + 将给定的变换矩阵应用于此 [name]。此方法可用于将包围体与 3D 对象的世界矩阵进行转换,以保持两个实体同步。 +

          + +

          [method:Vector3 clampPoint]( [param:Vector3 point], [param:Vector3 clampedPoint] )

          +

          + [page:Vector3 point] — 应该被限制在该 [name] 范围内的点。
          + [page:Matrix3 clampedPoint] — 结果将被复制到此向量中。 +

          +

          + 将给定点限制在该 [name] 范围内。 +

          + +

          [method:OBB clone]()

          +

          + 创建此实例的克隆。 +

          + +

          [method:Boolean containsPoint]( [param:Vector3 point] )

          +

          + [page:Vector3 point] — 要测试的点。 +

          +

          + 给定点是否位于此 [name] 内。 +

          + +

          [method:this copy]( [param:OBB obb] )

          +

          + [page:OBB obb] — 要复制的 [name]。 +

          +

          + 将给定 [name] 的属性复制到此 [name]。 +

          + +

          [method:Boolean equals]( [param:OBB obb] )

          +

          + [page:OBB obb] — 要测试的 [name]。 +

          +

          + 给定的 [name] 是否等于此 [name]。 +

          + +

          [method:this fromBox3]( [param:Box3 box3] )

          +

          + [page:Box3 box3] — AABB +

          +

          + 根据给定的 AABB 定义 [name]。 +

          + +

          [method:Vector3 getSize]( [param:Vector3 size] )

          +

          + [page:Vector3 size] — 结果将被复制到此向量中。 +

          +

          + 将此 [name] 的大小返回到给定向量中。 +

          + +

          [method:Boolean intersectsBox3]( [param:Box3 box3] )

          +

          + [page:Box3 box3] — 要测试的 AABB。 +

          +

          + 给定的 AABB 是否与此 [name] 相交。 +

          + +

          [method:Boolean intersectsSphere]( [param:Sphere sphere] )

          +

          + [page:Sphere sphere] — 要测试的边界球体。 +

          +

          + 给定的边界球体是否与此 [name] 相交。 +

          + +

          [method:Boolean intersectsOBB]( [param:OBB obb], [param:Number epsilon] )

          +

          + [page:OBB obb] — 要测试的 [name]
          + [page:Number epsilon] — 一个可选的数值,用于抵消算术错误。默认为 `Number.EPSILON`。 +

          +

          + 给定的 [name] 是否与此 [name] 相交。 +

          + +

          [method:Boolean intersectsPlane]( [param:Plane plane] )

          +

          + [page:Plane plane] — 要测试的平面。 +

          +

          + 给定平面是否与此 [name] 相交。 +

          + +

          [method:Boolean intersectsRay]( [param:Ray ray] )

          +

          + [page:Ray ray] — 要测试的射线。 +

          +

          + 给定射线是否与此 [name] 相交。 +

          + +

          [method:Vector3 intersectRay]( [param:Ray ray], [param:Vector3 intersectionPoint] )

          +

          + [page:Ray ray] — 要测试的射线。
          + [page:Vector3 intersectionPoint] — 结果将被复制到此向量中。 +

          +

          + 执行 射线/OBB 相交测试并将相交点存储到给定的 3D 向量。如果没有检测到交叉点则返回 `null`。 +

          + +

          [method:this set]( [param:Vector3 center], [param:Vector3 halfSize], [param:Matrix3 rotation] )

          +

          + [page:Vector3 center] — [name] 的中心。
          + [page:Vector3 halfSize] — [name] 沿每个轴的正半宽范围。
          + [page:Matrix3 rotation] — [name] 的旋转。 +

          +

          + 定义给定值的 [name]。 +

          + +

          源代码

          + +

          + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/math/OBB.js examples/jsm/math/OBB.js] +

          + + + \ No newline at end of file diff --git a/docs/examples/zh/math/convexhull/ConvexHull.html b/docs/examples/zh/math/convexhull/ConvexHull.html new file mode 100644 index 00000000000000..1e40d58b1dc170 --- /dev/null +++ b/docs/examples/zh/math/convexhull/ConvexHull.html @@ -0,0 +1,237 @@ + + + + + + + + + + + +

          凸包([name])

          + +

          + 凸包类。Quickhull 算法的实现者: Dirk Gregorius。2014 年 3 月,游戏开发者大会: + [link:http://media.steampowered.com/apps/valve/2014/DirkGregorius_ImplementingQuickHull.pdf Implementing QuickHull] +

          + +

          导入

          + +

          + [name] 是一个附加组件,必须显式导入。请参阅 [link:#manual/introduction/Installation Installation / Addons]. +

          + + + import { ConvexHull } from 'three/addons/math/ConvexHull.js'; + + + +

          构造函数

          + + +

          [name]()

          +

          + 创建一个 [name] 实例。 +

          + +

          属性

          + +

          [property:VertexList assigned]

          +

          + 该 [page:VertexList vertex list] 包含分配给面的所有顶点。默认是一个空的顶点列表。 +

          + +

          [property:Array faces]

          +

          + 生成的凸包面。默认是一个空数组。 +

          + +

          [property:Array newFaces]

          +

          + 该数组保存在单次迭代中生成的面。默认是一个空数组。 +

          + +

          [property:Float tolerance]

          +

          + 用于内部比较运算的 epsilon 值。该值的计算取决于几何形状的大小。默认值为 -1。 +

          + +

          [property:VertexList unassigned]

          +

          + 该 [page:VertexList vertex list] 包含未分配给面的所有顶点。默认是一个空的顶点列表。 +

          + +

          [property:Array vertices]

          +

          + 给定几何数据的内部表示( [page:VertexNode vertices] 数组)。 +

          + +

          方法

          + +

          [method:HalfEdge addAdjoiningFace]( [param:VertexNode eyeVertex], [param:HalfEdge horizonEdge] )

          +

          + [page:VertexNode eyeVertex] - 添加到凸包的顶点。
          + [page:HalfEdge horizonEdge] - 地平线的单个边缘。

          + + + 创建一个面,顶点顺序为 'eyeVertex.point'、'horizonEdge.tail' 和 'horizonEdge.head',顺序为逆时针(CCW)。所有半边按照逆时针顺序创建,因此该面始终指向凸包的外部。 +

          + +

          [method:this addNewFaces]( [param:VertexNode eyeVertex], [param:HalfEdge horizonEdge] )

          +

          + [page:VertexNode eyeVertex] - 添加到凸包的顶点。
          + [page:HalfEdge horizon] - 形成地平线的半边数组。

          + + 将 'horizon.length' 个面添加到凸包,每个面将与地平线上的相对面以及左/右相邻的面连接。 +

          + +

          [method:this addVertexToFace]( [param:VertexNode vertex], [param:Face face] )

          +

          + [page:VertexNode vertex] - 要添加的顶点。
          + [page:Face face] - 目标面。

          + + 将一个顶点添加到 “分配(assigned)” 顶点列表,并将其分配给给定的面。 +

          + +

          [method:this addVertexToHull]( [param:VertexNode eyeVertex] )

          +

          + [page:VertexNode eyeVertex] - 添加到凸包的顶点。

          + + 使用以下算法向凸包添加顶点: +

            +
          • 计算“地平线”,这是一系列半边。要使一条边属于这个组,它必须是连接一个能够看到 'eyeVertex' 的面和一个不能看到 'eyeVertex' 的面的边。
          • +
          • 所有能够看到 'eyeVertex' 的面都会从已分配顶点列表中移除其可见的顶点。
          • +
          • 使用“地平线”和 'eyeVertex' 的每条边创建一组新的面。每个面都与相对的地平线面以及左/右相邻的面连接。
          • +
          • 从所有可见面中移除的顶点将被分配给新的面,如果可能的话。
          • +
          +

          + +

          [method:this cleanup]()

          + +

          计算凸包后清理内部属性。

          + +

          [method:this compute]()

          + +

          开始执行快速凸包算法。

          + +

          [method:Object computeExtremes]()

          + +

          计算将用于计算初始凸包的极值(最小/最大向量)。

          + +

          [method:this computeHorizon]( [param:Vector3 eyePoint], [param:HalfEdge crossEdge], [param:Face face], + [param:Array horizon] )

          +

          + [page:Vector3 eyePoint] - 点的 3D 坐标。
          + [page:HalfEdge crossEdge] - 用于跳转到当前面的边。
          + [page:Face face] - 当前正在测试的面。
          + [page:Array horizon] - 按 CCW 顺序构成地平线一部分的边。

          + + + 计算一个逆时针(CCW)顺序的半边链,称为“地平线”('horizon')。要使一条边成为地平线的一部分,它必须连接一个能够看到 'eyePoint' 的面和一个不能看到 'eyePoint' 的面。 +

          + +

          [method:this computeInitialHull]()

          + +

          计算初始的单纯形,将所有可能成为凸包一部分的点分配给其面。 +

          + +

          [method:this containsPoint]( [param:Vector3 point] )

          +

          + [page:Vector3 point] - 3D 空间中的一个点。

          + + 返回 `true` 如果定点在此凸包内。 +

          + +

          [method:this deleteFaceVertices]( [param:Face face], [param:Face absorbingFace] )

          +

          + [page:Face face] - 给定的面。
          + [page:Face absorbingFace] - 尝试吸收第一个面的顶点的可选面。

          + + 删除 “面(face)” 能够看到的所有可见顶点。 +

            +
          • 如果 'absorbingFace' 不存在,则所有已移除的顶点将被添加到 'unassigned' 顶点列表。 +
          • +
          • 如果 'absorbingFace' 存在,则该方法将分配所有 'face' 中可以看到 'absorbingFace' 的顶点。 +
          • +
          • 如果一个顶点不能看到 'absorbingFace',则将其添加到 'unassigned' 顶点列表。
          • +
          +

          + +

          [method:Vector3 intersectRay]( [param:Ray ray], [param:Vector3 target] )

          +

          + [page:Ray ray] - 给定的射线。
          + [page:Vector3 target] - 表示交点的目标向量。

          + + 与该凸包执行光线相交测试。如果没有找到交集则返回 `null`。 +

          + +

          [method:Boolean intersectsRay]( [param:Ray ray] )

          +

          + [page:Ray ray] - 给定的射线。

          + + 返回给的射线是否与此凸包相交,如果相交则返回 `true`。 +

          + +

          [method:this makeEmpty]()

          + +

          使这个凸包为空。

          + +

          [method:VertexNode nextVertexToAdd]()

          + +

          查找下一个顶点以使用当前凸包创建面。 +

            +
          • 让初始面成为存在于“已分配(assigned)”顶点列表中的第一个面。
          • +
          • 如果面不存在则返回,因为没有顶点剩下。
          • +
          • 否则,对于面能看到的每个顶点,找到距离它最远的那个。
          • +
          +

          + +

          [method:this reindexFaces]()

          + +

          从内部面列表中移除不活跃的(例如已删除的)面。

          + +

          [method:VertexNode removeAllVerticesFromFace]( [param:Face face] )

          +

          + [page:Face face] - 给定的面。

          + + 移除给定面能够看到的所有可见顶点,这些顶点存储在“已分配(assigned)”顶点列表中。 +

          + +

          [method:this removeVertexFromFace]( [param:VertexNode vertex], [param:Face face] )

          +

          + [page:VertexNode vertex] - 要删除的顶点。
          + [page:Face face] - 目标面。

          + + 从“已分配”顶点列表和给定面中移除一个顶点。在移除后,它还确保从“面”到它在“已分配”中看到的第一个顶点的链接被正确连接。 +

          + +

          [method:this resolveUnassignedPoints]( [param:Array newFaces] )

          +

          + [page:Face newFaces] - 一组新的面。

          + + 尽可能从未分配的顶点列表中重新分配顶点给新的面。 +

          + +

          [method:this setFromObject]( [param:Object3D object] )

          +

          + [page:Object3D object] - 用于计算凸包的 [page:Object3D]

          + + 计算 [page:Object3D] 的凸包(包括其子元素),考虑到对象及其子元素的世界变换。 +

          + +

          [method:this setFromPoints]( [param:Array points] )

          +

          + [page:Array points] - 生成的凸包将包含的 [page:Vector3 Vector3s] 数组

          + + 计算给定点数组的凸包。 +

          + +

          源代码

          + +

          + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/math/ConvexHull.js examples/jsm/math/ConvexHull.js] +

          + + + diff --git a/docs/examples/zh/math/convexhull/Face.html b/docs/examples/zh/math/convexhull/Face.html new file mode 100644 index 00000000000000..207e0fc9173dba --- /dev/null +++ b/docs/examples/zh/math/convexhull/Face.html @@ -0,0 +1,110 @@ + + + + + + + + + + + +

          面([name])

          + +

          + 表示由特定数量的半边界定的部分。当前的实现假设一个面始终由三个边组成。 +

          + +

          导入

          + +

          + [name] 是一个附加组件,必须显式导入。请参阅 [link:#manual/introduction/Installation Installation / Addons]. +

          + + + import { Face } from 'three/addons/math/ConvexHull.js'; + + + +

          构造函数

          + + +

          [name]()

          +

          + 创建一个 [name] 实例。 +

          + +

          属性

          + +

          [property:Vector3 normal]

          +

          + 面的法线向量。默认值为(0, 0, 0) 处的 [page:Vector3]。 +

          + +

          [property:Vector3 midpoint]

          +

          + 面的中点或质心。默认值为(0, 0, 0) 处的 [page:Vector3]。 +

          + +

          [property:Float area]

          +

          + 面的面积。默认值为 0。 +

          + +

          [property:Float constant]

          +

          + 从面到原点的有符号距离。默认值为 0。 +

          + +

          [property:VertexNode outside]

          +

          + 引用该面可以看到的顶点列表中的顶点。默认为 null。 +

          + +

          [property:Integer mark]

          +

          + 标记面部是否可见或已删除。默认为 'Visible'。 +

          + +

          [property:HalfEdge edge]

          +

          + 对面的基边的引用。要检索所有边,您可以使用当前边的 “next” 引用。默认为空。 null. +

          + +

          方法

          + +

          [method:Face create]( [param:VertexNode a], [param:VertexNode b], [param:VertexNode c] )

          +

          + [page:VertexNode a] - 面的第一个顶点。
          + [page:VertexNode b] - 面的第二个顶点。
          + [page:VertexNode c] - 面的第三个顶点。

          + + 创建一个 [name]。 +

          + +

          [method:HalfEdge getEdge]( [param:Integer i] )

          +

          + [page:Integer i] - 边的索引

          + + 返回给定索引的边。 +

          + +

          [method:this compute] ()

          + +

          计算面的所有属性。

          + +

          [method:Float distanceToPoint]( [param:Vector3 point] )

          +

          + [page:Vector3 point] - 3D 空间中的任何点。

          + + 返回从给定点到该面的平面表示的带符号距离。 +

          + +

          源代码

          + +

          + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/math/ConvexHull.js examples/jsm/math/ConvexHull.js] +

          + + + diff --git a/docs/examples/zh/math/convexhull/HalfEdge.html b/docs/examples/zh/math/convexhull/HalfEdge.html new file mode 100644 index 00000000000000..668f0c56ffdf3a --- /dev/null +++ b/docs/examples/zh/math/convexhull/HalfEdge.html @@ -0,0 +1,97 @@ + + + + + + + + + + + +

          半边([name])

          + +

          + 半边数据结构的基础,也被称为双连通边列表 (DCEL)。 +

          + +

          导入

          + +

          + [name] 是一个附加组件,必须显式导入。请参阅 [link:#manual/introduction/Installation Installation / Addons]. +

          + + + import { HalfEdge } from 'three/addons/math/ConvexHull.js'; + + + +

          构造函数

          + + +

          [name]( [param:VertexNode vertex], [param:Face face] )

          +

          + [page:VertexNode vertex] - [page:VertexNode] 对其目标顶点的引用
          + [page:Face face] - [page:Face] 对其面的引用

          + + 创建一个 [name] 实例。 +

          + +

          属性

          + +

          [property:VertexNode vertex]

          +

          + 对目标顶点的引用。可以通过查询其孪生顶点或前一个半边的目的地来获得原点。默认值 undefined。 +

          + +

          [property:HalfEdge prev]

          +

          + 对同一面的前一半边的引用。默认值为 null。 +

          + +

          [property:HalfEdge next]

          +

          + 对同一面的下一半边的引用。默认值为 null。 +

          + +

          [property:HalfEdge twin]

          +

          + 对应到达相对面的孪生半边的引用。默认值为 null。 +

          + +

          [property:Face face]

          +

          + 每个半边限定一个面,因此具有对该面的引用。默认值 undefined。 +

          + +

          方法

          + +

          [method:VertexNode head]()

          +

          返回目标顶点。

          + +

          [method:VertexNode tail]()

          +

          返回原点顶点

          + +

          [method:Float length]()

          +

          返回边的 [link:https://en.wikipedia.org/wiki/Euclidean_distance 欧几里得长度] + (直线长度)。

          + +

          [method:Float lengthSquared]()

          +

          返回边的 [link:https://en.wikipedia.org/wiki/Euclidean_distance 欧几里得长度] + (直线长度)的平方。

          + +

          [method:this setTwin]( [param:HalfEdge edge] )

          +

          + [page:HalfEdge edge] - 任何半边缘。

          + + 设置这个半边的孪生边。还确保给定半边的孪生引用被正确设置。 +

          + +

          源代码

          + +

          + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/math/ConvexHull.js examples/jsm/math/ConvexHull.js] +

          + + + diff --git a/docs/examples/zh/math/convexhull/VertexList.html b/docs/examples/zh/math/convexhull/VertexList.html new file mode 100644 index 00000000000000..443eec18efa8c8 --- /dev/null +++ b/docs/examples/zh/math/convexhull/VertexList.html @@ -0,0 +1,116 @@ + + + + + + + + + + + +

          顶点列表([name])

          + +

          + 顶点的双向链表。 +

          + +

          导入

          + +

          + [name] 是一个附加组件,必须显式导入。请参阅 [link:#manual/introduction/Installation Installation / Addons]。 +

          + + + import { VertexList } from 'three/addons/math/ConvexHull.js'; + + + +

          构造函数

          + + +

          [name]()

          +

          + 创建一个 [name] 实例。 +

          + +

          属性

          + +

          [property:VertexNode head]

          +

          + 引用链表的第一个顶点。默认为 null。 +

          + +

          [property:VertexNode tail]

          +

          + 引用链表的最后一个顶点。默认为 null。 +

          + +

          方法

          + +

          [method:VertexNode first]()

          +

          返回头引用。

          + +

          [method:VertexNode last]()

          +

          返回尾部引用。

          + +

          [method:this clear]()

          +

          清除链接列表。

          + +

          [method:this insertBefore]( [param:Vertex target], [param:Vertex vertex] )

          +

          + [page:Vertex target] - 目标顶点。假设该顶点属于链表。
          + [page:Vertex vertex] - 要插入的顶点。

          + + 在目标顶点 之前 插入一个顶点。 +

          + +

          [method:this insertAfter]( [param:Vertex target], [param:Vertex vertex] )

          +

          + [page:Vertex target] - 目标顶点。假设该顶点属于链表。
          + [page:Vertex vertex] - 要插入的顶点。

          + + 在目标顶点 之后 插入一个顶点。 +

          + +

          [method:this append]( [param:Vertex vertex] )

          +

          + [page:Vertex vertex] - 要追加的顶点。

          + + 将一个顶点追加到链表的末尾。 +

          + +

          [method:this appendChain]( [param:Vertex vertex] )

          +

          + [page:Vertex vertex] - 顶点链的头顶点。

          + + 添加一个顶点链,其中给定顶点是头。 +

          + +

          [method:this remove]( [param:Vertex vertex] )

          +

          + [page:Vertex vertex] - 要删除的顶点。

          + + 从链表中删除一个顶点。 +

          + +

          [method:this removeSubList]( [param:Vertex a], [param:Vertex b] )

          +

          + [page:Vertex a] - 子列表的头部。
          + [page:Vertex b] - 子列表的尾部。

          + + 从链接列表中删除顶点的子列表。 +

          + +

          [method:Boolean isEmpty]()

          + +

          如果链表为空则返回 true。

          + +

          源代码

          + +

          + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/math/ConvexHull.js examples/jsm/math/ConvexHull.js] +

          + + + diff --git a/docs/examples/zh/math/convexhull/VertexNode.html b/docs/examples/zh/math/convexhull/VertexNode.html new file mode 100644 index 00000000000000..68c63e15447dc1 --- /dev/null +++ b/docs/examples/zh/math/convexhull/VertexNode.html @@ -0,0 +1,68 @@ + + + + + + + + + + + +

          顶点节点([name])

          + +

          + 一个顶点作为双链表节点。 +

          + +

          导入

          + +

          + [name] 是一个附加组件,必须显式导入。请参阅 [link:#manual/introduction/Installation Installation / Addons]。 +

          + + + import { VertexNode } from 'three/addons/math/ConvexHull.js'; + + + +

          构造函数

          + + +

          [name]( [param:Vector3 point] )

          +

          + [page:Vector3 point] - [page:Vector3] 3D 空间中的点 (x, y, z)。

          + + 创建一个 [name] 实例。 +

          + +

          属性

          + +

          [property:Vector3 point]

          +

          + 3D 空间中的点 (x, y, z)。默认值 undefined。 +

          + +

          [property:VertexNode prev]

          +

          + 引用双链表中的前一个顶点。默认为 null。 +

          + +

          [property:VertexNode next]

          +

          + 引用双链表中的下一个顶点。默认为 null。 +

          + +

          [property:Face face]

          +

          + 对能够看到该顶点的面的引用。默认值为 undefined。 +

          + +

          源代码

          + +

          + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/math/ConvexHull.js examples/jsm/math/ConvexHull.js] +

          + + + diff --git a/docs/examples/zh/misc/Timer.html b/docs/examples/zh/misc/Timer.html new file mode 100644 index 00000000000000..41c3583bc27763 --- /dev/null +++ b/docs/examples/zh/misc/Timer.html @@ -0,0 +1,110 @@ + + + + + + + + + + + + +

          定时器([name])

          + +

          + 此类是 [page:Clock] 的替代品,具有不同的 API 设计和行为。目标是避免随着时间的推移 [page:Clock] 中变得明显的概念缺陷。 + +

            +
          • [name] 具有 [page:.update]() 方法,用于更新其内部状态。这使得可以在模拟步骤中多次调用 [page:.getDelta]() 和 [page:.getElapsed]() 而不会得到不同的值。 +
          • +
          • 该类使用页面可见性 API(Page Visibility API),以避免在应用程序处于非活动状态(例如切换标签或浏览器隐藏)时出现大的时间差值。
          • +
          +

          + +

          导入

          + +

          + [name] 是一个附加组件,必须显式导入。请参阅 [link:#manual/introduction/Installation Installation / Addons]. +

          + + + import { Timer } from 'three/addons/misc/Timer.js'; + + +

          代码示例

          + + + const timer = new Timer(); + + function animate( timestamp ) { + + requestAnimationFrame( animate ); + + // timestamp is optional + timer.update( timestamp ); + + const delta = timer.getDelta(); + + // do something with delta + + renderer.render( scene, camera ); + + } + + +

          例子

          + +

          + [example:webgl_morphtargets_sphere WebGL / morphtargets / sphere] +

          + +

          构造函数

          + +

          Timer()

          + +

          方法

          + +

          [method:Number getDelta]()

          +

          + 返回以秒为单位的时间增量。 +

          + +

          [method:Number getElapsed]()

          +

          + 返回经过的时间(以秒为单位)。 +

          + +

          [method:this setTimescale]( [param:Number timescale] )

          +

          + 设置一个时间刻度,缩放 [page:.update]() 中的时间增量。 +

          + +

          [method:this reset]()

          +

          + 重置当前模拟步骤的时间计算。 +

          + +

          [method:this dispose]()

          +

          + 可用于释放所有内部资源。通常在不再需要计时器实例时调用。 +

          + +

          [method:this update]( [param:Number timestamp] )

          +

          + timestamp -- (可选)当前时间(以毫秒为单位)。可以从 + [link:https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame requestAnimationFrame] + 回调参数中获取 。如果未提供,当前时间将由 [link:https://developer.mozilla.org/en-US/docs/Web/API/Performance/now performance.now] + 确定。

          + + 更新定时器的内部状态。该方法应该在每个模拟步骤以及对计时器执行查询之前调用一次(例如通过 [page:.getDelta]())。 +

          + +

          源代码

          + +

          + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/misc/Timer.js examples/jsm/misc/Timer.js] +

          + + + \ No newline at end of file diff --git a/docs/examples/zh/modifiers/EdgeSplitModifier.html b/docs/examples/zh/modifiers/EdgeSplitModifier.html new file mode 100644 index 00000000000000..b3476115a40ab1 --- /dev/null +++ b/docs/examples/zh/modifiers/EdgeSplitModifier.html @@ -0,0 +1,68 @@ + + + + + + + + + + +

          [name]

          + +

          + [name] 的设计目的是通过“溶解”边缘来修改几何体,使其看起来更加平滑。 +

          + +

          导入

          + +

          + [name]是一个附加组件,必须显式导入。 + 参见 [link:#manual/introduction/Installation Installation / Addons]. +

          + + + import { EdgeSplitModifier } from 'three/addons/modifiers/EdgeSplitModifier.js'; + + +

          代码示例

          + + + const geometry = new THREE.IcosahedronGeometry( 10, 3 );
          + const modifier = new EdgeSplitModifier();
          + const cutOffAngle = 0.5;
          + const tryKeepNormals = false;
          +
          + modifier.modify( geometry, cutOffAngle, tryKeepNormals ); +
          + +

          示例

          + +

          [example:webgl_modifier_edgesplit misc / modifiers / EdgeSplit ]

          + +

          构造函数

          + +

          [name]()

          +

          + 创建一个新的[name]对象。 +

          + +

          方法

          + +

          [method:undefined modify]( [param:geometry], [param:cutOffAngle], [param:tryKeepNormals] )

          +

          + 使用插值的顶点法线,网格的面会在边缘处变得模糊,从而呈现出平滑的外观。
          + + 您可以通过设置 `cutOffAngle` 来控制平滑度。
          + + 如果希望尝试保留原始法线,请将 `tryKeepNormals` 设置为 `true`。 + +

          + +

          源代码

          + +

          + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/modifiers/EdgeSplitModifier.js examples/jsm/modifiers/EdgeSplitModifier.js] +

          + + diff --git a/docs/examples/zh/objects/Lensflare.html b/docs/examples/zh/objects/Lensflare.html index 889bb915c97db7..7f9b1cca31d844 100644 --- a/docs/examples/zh/objects/Lensflare.html +++ b/docs/examples/zh/objects/Lensflare.html @@ -15,13 +15,15 @@

          镜头光晕([name])

          创建一个模拟追踪着灯光的镜头光晕。 [name] can only be used when setting the *alpha* context parameter of [page:WebGLRenderer] to *true*.

          +

          导入

          +

          [name] 是一个附加组件,必须显式导入。 See [link:#manual/introduction/Installation Installation / Addons].

          - import { Lensflare } from 'three/addons/objects/Lensflare.js'; + import { Lensflare, LensflareElement } from 'three/addons/objects/Lensflare.js';

          代码示例

          diff --git a/docs/examples/zh/objects/Sky.html b/docs/examples/zh/objects/Sky.html new file mode 100644 index 00000000000000..5de65f0bd753fb --- /dev/null +++ b/docs/examples/zh/objects/Sky.html @@ -0,0 +1,91 @@ + + + + + + + + + + [page:Mesh] → + +

          [name]

          + +

          + [name]为你的场景创建了一个准备就绪的天空环境。 +

          + +

          导入

          + +

          + [name] 是一个插件,因此必须明确导入。 + 参见[link:#manual/introduction/Installation Installation / Addons]. +

          + + + import { Sky } from 'three/addons/objects/Sky.js'; + + +

          代码示例

          + + + const sky = new Sky();
          + sky.scale.setScalar( 450000 );
          + + const phi = MathUtils.degToRad( 90 );
          + const theta = MathUtils.degToRad( 180 );
          + const sunPosition = new Vector3().setFromSphericalCoords( 1, phi, theta );
          + + sky.material.uniforms.sunPosition.value = sunPosition;
          + + scene.add( sky ); +
          + +

          示例

          + +

          [example:webgl_shaders_sky misc / objects / Sky ]

          + +

          构造函数

          + +

          [name]()

          +

          + 创建一个[name]实例。 +

          + +

          属性

          +

          + [name]实例是一个带有预定义的 [page:ShaderMaterial]材质的[page:Mesh]模型, 因此这里描述的每个属性都应该使用[page:Uniform]s. +

          + +

          [property:Number turbidity]

          +

          + [name]的浑浊度 +

          +

          [property:Number rayleigh]

          +

          + 更详细的解释参见: [link:https://en.wikipedia.org/wiki/Rayleigh_scattering Rayleigh scattering] . +

          +

          [property:Number mieCoefficient]

          +

          + [link:https://en.wikipedia.org/wiki/Mie_scattering Mie scattering]数量。 +

          +

          [property:Number mieDirectionalG]

          +

          + [link:https://en.wikipedia.org/wiki/Mie_scattering Mie scattering]方向 +

          +

          [property:Vector3 sunPosition]

          +

          + 太阳的位置。 +

          +

          [property:Vector3 up]

          +

          + 太阳从地平线升起的角度,以度为单位。 +

          + +

          源代码

          + +

          + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/objects/Sky.js examples/jsm/objects/Sky.js] +

          + + diff --git a/docs/examples/zh/postprocessing/EffectComposer.html b/docs/examples/zh/postprocessing/EffectComposer.html index e70af3b73aedec..c521f40eb3ae33 100644 --- a/docs/examples/zh/postprocessing/EffectComposer.html +++ b/docs/examples/zh/postprocessing/EffectComposer.html @@ -15,7 +15,7 @@

          效果合成器([name])

          后期处理过程根据它们添加/插入的顺序来执行,最后一个过程会被自动渲染到屏幕上。

          -

          进口

          +

          导入

          [name] 是一个附加组件,必须显式导入。 @@ -32,13 +32,15 @@

          例子

          [example:webgl_postprocessing postprocessing]
          [example:webgl_postprocessing_advanced postprocessing advanced]
          [example:webgl_postprocessing_backgrounds postprocessing backgrounds]
          - [example:webgl_postprocessing_crossfade postprocessing crossfade]
          + [example:webgl_postprocessing_transition postprocessing transition]
          [example:webgl_postprocessing_dof postprocessing depth-of-field]
          [example:webgl_postprocessing_dof2 postprocessing depth-of-field 2]
          [example:webgl_postprocessing_fxaa postprocessing fxaa]
          [example:webgl_postprocessing_glitch postprocessing glitch]
          [example:webgl_postprocessing_godrays postprocessing godrays]
          + [example:webgl_postprocessing_gtao postprocessing gtao]
          [example:webgl_postprocessing_masking postprocessing masking]
          + [example:webgl_postprocessing_material_ao postprocessing material ao]
          [example:webgl_postprocessing_outline postprocessing outline]
          [example:webgl_postprocessing_pixel postprocessing pixelate]
          [example:webgl_postprocessing_procedural postprocessing procedural]
          @@ -125,9 +127,9 @@

          [method:Boolean isLastEnabledPass]( [param:Integer passIndex] )

          [method:undefined removePass]( [param:Pass pass] )

          - pass -- The pass to remove from the pass chain.

          + pass -- 要移除的pass。

          - Removes the given pass from the pass chain. + 移除指定的pass。

          [method:undefined render]( [param:Float deltaTime] )

          diff --git a/docs/examples/zh/renderers/CSS2DRenderer.html b/docs/examples/zh/renderers/CSS2DRenderer.html index 29a70c39c51952..34cccd5f323993 100644 --- a/docs/examples/zh/renderers/CSS2DRenderer.html +++ b/docs/examples/zh/renderers/CSS2DRenderer.html @@ -14,7 +14,7 @@

          CSS 2D渲染器([name])

          `[name]` only supports 100% browser and display zoom.

          -

          进口

          +

          导入

          [name] 是一个附加组件,必须显式导入。 diff --git a/docs/examples/zh/renderers/CSS3DRenderer.html b/docs/examples/zh/renderers/CSS3DRenderer.html index ce80d98351b99c..6d9875a668bf4c 100644 --- a/docs/examples/zh/renderers/CSS3DRenderer.html +++ b/docs/examples/zh/renderers/CSS3DRenderer.html @@ -24,7 +24,7 @@

          CSS 3D渲染器([name])

          因此,[name]仅仅关注普通的DOM元素,这些元素被包含到了特殊的对象中(*CSS3DObject*或者*CSS3DSprite*),然后被加入到场景图中。

          -

          进口

          +

          导入

          [name] 是一个附加组件,必须显式导入。 diff --git a/docs/examples/zh/renderers/SVGRenderer.html b/docs/examples/zh/renderers/SVGRenderer.html index 869aaa95d3d557..1f1e1bccf84548 100644 --- a/docs/examples/zh/renderers/SVGRenderer.html +++ b/docs/examples/zh/renderers/SVGRenderer.html @@ -33,7 +33,7 @@

          SVG渲染器([name])

        -

        进口

        +

        导入

        [name] 是一个附加组件,必须显式导入。 diff --git a/docs/examples/zh/utils/BufferGeometryUtils.html b/docs/examples/zh/utils/BufferGeometryUtils.html index 4e8c9bbc3ad9bc..355c49296874b9 100644 --- a/docs/examples/zh/utils/BufferGeometryUtils.html +++ b/docs/examples/zh/utils/BufferGeometryUtils.html @@ -13,7 +13,7 @@

        [name]

        一个包含 [page:BufferGeometry BufferGeometry] 实例的实用方法的类。

        -

        进口

        +

        导入

        [name] 是一个附加组件,必须显式导入。 @@ -120,12 +120,19 @@

        [method:BufferGeometry mergeVertices]( [param:BufferGeometry geometry], [par

        [method:BufferGeometry toCreasedNormals]( [param:BufferGeometry geometry], [param:Number creaseAngle] )

        +
          +
        • geometry -- The input geometry.
        • +
        • creaseAngle -- The crease angle in radians.
        • +
        +

        - geometry -- The input geometry.
        - creaseAngle -- The crease angle. + Modifies the supplied geometry if it is non-indexed, otherwise creates a new, + non-indexed geometry.

        +

        - Creates a new, non-indexed geometry with smooth normals everywhere except faces that meet at an angle greater than the crease angle. + Returns the geometry with smooth normals everywhere except faces + that meet at an angle greater than the crease angle.

        [method:BufferGeometry toTrianglesDrawMode]( [param:BufferGeometry geometry], [param:TrianglesDrawMode drawMode] )

        @@ -136,7 +143,7 @@

        [method:BufferGeometry toTrianglesDrawMode]( [param:BufferGeometry geometry] Returns a new indexed geometry based on `THREE.TrianglesDrawMode` draw mode. This mode corresponds to the `gl.TRIANGLES` WebGL primitive.

        -

        Source

        +

        源代码

        [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/utils/BufferGeometryUtils.js examples/jsm/utils/BufferGeometryUtils.js] diff --git a/docs/examples/zh/utils/CameraUtils.html b/docs/examples/zh/utils/CameraUtils.html new file mode 100644 index 00000000000000..601640cda80135 --- /dev/null +++ b/docs/examples/zh/utils/CameraUtils.html @@ -0,0 +1,45 @@ + + + + + + + + + + + +

        相机工具([name])

        + +

        + 包含用于相机操作的有用实用函数的类。 +

        + +

        导入

        + +

        + [name] 是一个附加组件,必须显式导入。请参阅 [link:#manual/introduction/Installation Installation / Addons]. +

        + + + import * as CameraUtils from 'three/addons/utils/CameraUtils.js'; + + +

        方法

        + +

        [method:undefined frameCorners]( [param:PerspectiveCamera camera], [param:Vector3 bottomLeftCorner], + [param:Vector3 bottomRightCorner], [param:Vector3 topLeftCorner], [param:boolean estimateViewFrustum] )

        +

        + 使用 [link:https://web.archive.org/web/20191110002841/http://csc.lsu.edu/~kooima/articles/genperspective/index.html + Kooima 广义透视投影公式]. + 注意:此功能忽略标准参数;在此之后不要调用 updateProjectionMatrix() ! toJSON也不会捕获此函数生成的离轴矩阵。 +

        + +

        源代码

        + +

        + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/utils/CameraUtils.js examples/jsm/utils/CameraUtils.js] +

        + + + diff --git a/docs/examples/zh/utils/SceneUtils.html b/docs/examples/zh/utils/SceneUtils.html index 9042684cf787e4..844310926aaaba 100644 --- a/docs/examples/zh/utils/SceneUtils.html +++ b/docs/examples/zh/utils/SceneUtils.html @@ -11,7 +11,7 @@

        场景工具([name])

        一个用于操控场景的实用类。

        -

        进口

        +

        导入

        [name] 是一个附加组件,必须显式导入。 @@ -19,7 +19,7 @@

        进口

        - import { SceneUtils } from 'three/addons/utils/SceneUtils.js'; + import * as SceneUtils from 'three/addons/utils/SceneUtils.js';

        方法

        @@ -34,16 +34,16 @@

        [method:Group createMeshesFromInstancedMesh]( [param:InstancedMesh instanced

        [method:Group createMeshesFromMultiMaterialMesh]( [param:Mesh mesh] )

        - mesh -- A mesh with multiple materials. + mesh -- 具有多种材质的网格。

        - Converts the given multi-material mesh into an instance of [page:Group] holding for each material a separate mesh. + 为给定的具有多种材质的网格的每个材质,创建一个包含新网格的新物体组[page:Group]。

        [method:Group createMultiMaterialObject]( [param:BufferGeometry geometry], [param:Array materials] )

        - geometry -- 材料集的几何形状。
        - materials -- 为物体准备的材料。 + geometry -- 材质集的几何。
        + materials -- 为物体准备的材质。

        创建一个新组,囊括了在材质中定义的每种材质的新网格。请注意,这和为一个网格定义多种材质的材质数组不同。
        @@ -52,21 +52,41 @@

        [method:Group createMultiMaterialObject]( [param:BufferGeometry geometry], [

        [method:undefined sortInstancedMesh]( [param:InstancedMesh mesh], [param:Function compareFn] )

        - mesh -- InstancedMesh in which instances will be sorted.
        - compareFn -- Comparator function defining the sort order. + mesh -- InstancedMesh中的实例将呗排序。
        + compareFn -- 决定排序顺序的比较函数。

        - Sorts the instances within an [page:InstancedMesh], according to a user-defined - callback. The callback will be provided with two arguments, indexA - and indexB, and must return a numerical value. See - [link:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#description Array.prototype.sort] - for more information on sorting callbacks and their return values. + 根据用户定义的回调函数,对[page:InstancedMesh]中的实例进行排序。 + 回调函数会提供两个参数,indexAindexB,并且必须返回一个数值。 + 有关排序回调函数的更多信息,请参阅[link:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#description Array.prototype.sort]

        - Because of the high performance cost, three.js does not sort - [page:InstancedMesh] instances automatically. Manually sorting may be - helpful to improve display of alpha blended materials (back to front), - and to reduce overdraw in opaque materials (front to back). + 由于性能成本,three.js不会自动对[page:InstancedMesh]实例进行排序。 + 手动排序可能有助于提高透明材质的显示顺序(back to front)和不透明材质的显示顺序(front to back)。 +

        + +

        [method:Generator traverseGenerator]( [param:Object3D object] )

        +

        + object -- 遍历的对象。 +

        +

        + 使用generator实现的 [page:Object3D.traverse](). +

        + +

        [method:Generator traverseVisibleGenerator]( [param:Object3D object] )

        +

        + object -- 遍历的对象。 +

        +

        + 使用generator实现的 [page:Object3D.traverseVisible](). +

        + +

        [method:Generator traverseAncestorsGenerator]( [param:Object3D object] )

        +

        + object -- 遍历的对象。 +

        +

        + 使用generator实现的 [page:Object3D.traverseAncestors]().

        源代码

        diff --git a/docs/examples/zh/utils/SkeletonUtils.html b/docs/examples/zh/utils/SkeletonUtils.html index a84332bd5a636a..3e705633ed777f 100644 --- a/docs/examples/zh/utils/SkeletonUtils.html +++ b/docs/examples/zh/utils/SkeletonUtils.html @@ -11,7 +11,7 @@

        骨架工具([name])

        用于操控 [page:Skeleton]、 [page:SkinnedMesh]、和 [page:Bone] 的实用方法。

        -

        进口

        +

        导入

        [name] 是一个附加组件,必须显式导入。 diff --git a/docs/examples/zh/webxr/XREstimatedLight.html b/docs/examples/zh/webxr/XREstimatedLight.html new file mode 100644 index 00000000000000..a8bf8e032781cc --- /dev/null +++ b/docs/examples/zh/webxr/XREstimatedLight.html @@ -0,0 +1,116 @@ + + + + + + + + + + + + [page:Group] → + +

        XR估计光照([name])

        + +

        + [name] 使用 WebXR 的光照估计来创建光探针、定向光和(可选)模拟用户真实世界环境和照明的环境图。
        + 当 WebXR 更新光照和环境估计时,[name] 会自动更新光照探针、定向光和环境贴图。

        + + 在创建 WebXR 会话时,将光照估计指定为可选或必需的功能非常重要,否则光照估计将无法工作。

        + + 有关浏览器兼容性信息,请参阅 此处,因为这仍然是 WebXR + 中的一个实验功能。

        + + 要使用它,就像 /examples 目录中的所有文件一样,您必须在HTML中单独包含该文件。 +

        + +

        导入

        + +

        + [name] 是一个附加组件,必须显式导入。请参阅 [link:#manual/introduction/Installation Installation / Addons]。 +

        + + + import { XREstimatedLight } from 'three/addons/webxr/XREstimatedLight.js'; + + +

        代码示例

        + + + renderer.xr.enabled = true; + + // Don't add the XREstimatedLight to the scene initially. + // It doesn't have any estimated lighting values until an AR session starts. + const xrLight = new XREstimatedLight( renderer ); + + xrLight.addEventListener( 'estimationstart' , () => { + + scene.add( xrLight ); + + if ( xrLight.environment ) { + + scene.environment = xrLight.environment; + + } + + } ); + + xrLight.addEventListener( 'estimationend', () => { + + scene.remove( xrLight ); + + scene.environment = null; + + } ); + + // In order for lighting estimation to work, 'light-estimation' must be included as either + // an optional or required feature. + document.body.appendChild( XRButton.createButton( renderer, { + optionalFeatures: [ 'light-estimation' ] + } ) ); + + +

        例子

        + +

        [example:webxr_ar_lighting webxr / light estimation]

        + +

        构造函数

        + +

        [name]( [param:WebGLRenderer renderer], [param:Boolean environmentEstimation] )

        +

        + [page:WebGLRenderer renderer]: (必需)用于渲染场景的渲染器。主要用于与 WebXRManager 交互。

        + + environmentEstimation: 如果 `true`,则使用 WebXR 来估计环境地图。 +

        + +

        事件

        + +

        estimationstart

        +

        + 当估计的照明值开始更新时触发。 +

        + +

        estimationend

        +

        + 当估计的照明值停止更新时触发。 +

        + +

        Properties

        + +

        [property:Texture environment]

        +

        + WebXR 估计的环境地图。仅当 environmentEstimation 为 时,此选项才可用 `true`。

        + + 它可以用作 [page:Scene.environment] 的 [page:MeshStandardMaterial.envMap] 或 [page:Scene.background]。 +

        + +

        源代码

        + +

        + [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/webxr/XREstimatedLight.js examples/jsm/webxr/XREstimatedLight.js] +

        + + + diff --git a/docs/index.html b/docs/index.html index 31f01fdea99811..aa5696153ed3ad 100644 --- a/docs/index.html +++ b/docs/index.html @@ -21,7 +21,7 @@

        three.js

        docs - examples + manual
        @@ -38,11 +38,9 @@

        three.js

        - -
        @@ -93,7 +91,15 @@

        three.js

        if ( /^(api|manual|examples)/.test( hash ) ) { - const hashLanguage = /^(api|manual|examples)\/(en|ar|ko|zh|ja|it|pt-br|fr|ru)\//.exec( hash ); + const hashLanguage = /^(api|manual|examples)\/(en|ar|ko|zh|it|pt-br|fr)\//.exec( hash ); + + if ( hashLanguage[ 1 ] === 'manual' ) { + + // Redirect to the manual + + window.location.href = hash.replace(/^manual\/([^\/]+)\/([^\/]+)\/(.+)$/, '../manual/#$1/$3').toLowerCase(); + + } if ( hashLanguage === null ) { @@ -359,7 +365,9 @@

        three.js

        function escapeRegExp( string ) { - return string.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' ); // https://stackoverflow.com/a/6969486/5250847 + string = string.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' ); // https://stackoverflow.com/a/6969486/5250847 + + return '(?=.*' + string.split( ' ' ).join( ')(?=.*' ) + ')'; // match all words, in any order } @@ -527,17 +535,23 @@

        three.js

        const oldIframe = iframe; iframe = oldIframe.cloneNode(); + iframe.style.display = 'none'; + if ( hash && titles[ splitHash[ 0 ] ] ) { + iframe.onload = function () { + + iframe.style.display = 'unset'; + + }; + iframe.src = splitHash[ 0 ] + '.html' + splitHash[ 1 ]; subtitle = titles[ splitHash[ 0 ] ] + splitHash[ 1 ] + ' – '; - iframe.style.display = 'unset'; } else { iframe.src = ''; subtitle = ''; - iframe.style.display = 'none'; } diff --git a/docs/list.json b/docs/list.json index 63a37b65f5e050..c538b63e0755f8 100644 --- a/docs/list.json +++ b/docs/list.json @@ -2,32 +2,6 @@ "en": { - "Manual": { - - "Getting Started": { - "Installation": "manual/en/introduction/Installation", - "Creating a scene": "manual/en/introduction/Creating-a-scene", - "WebGL compatibility check": "manual/en/introduction/WebGL-compatibility-check", - "Drawing lines": "manual/en/introduction/Drawing-lines", - "Creating text": "manual/en/introduction/Creating-text", - "Loading 3D models": "manual/en/introduction/Loading-3D-models", - "Libraries and Plugins": "manual/en/introduction/Libraries-and-Plugins", - "FAQ": "manual/en/introduction/FAQ", - "Useful links": "manual/en/introduction/Useful-links" - }, - - "Next Steps": { - "Updating resources": "manual/en/introduction/How-to-update-things", - "Disposing resources": "manual/en/introduction/How-to-dispose-of-objects", - "Creating VR content": "manual/en/introduction/How-to-create-VR-content", - "Post-processing": "manual/en/introduction/How-to-use-post-processing", - "Matrix transformations": "manual/en/introduction/Matrix-transformations", - "Animation system": "manual/en/introduction/Animation-system", - "Color management": "manual/en/introduction/Color-management" - } - - }, - "Reference": { "Animation": { @@ -99,11 +73,13 @@ }, "Extras": { + "Controls": "api/en/extras/Controls", "DataUtils": "api/en/extras/DataUtils", "Earcut": "api/en/extras/Earcut", "ImageUtils": "api/en/extras/ImageUtils", "PMREMGenerator": "api/en/extras/PMREMGenerator", - "ShapeUtils": "api/en/extras/ShapeUtils" + "ShapeUtils": "api/en/extras/ShapeUtils", + "TextureUtils": "api/en/extras/TextureUtils" }, "Extras / Core": { @@ -170,10 +146,8 @@ "Lights": { "AmbientLight": "api/en/lights/AmbientLight", - "AmbientLightProbe": "api/en/lights/AmbientLightProbe", "DirectionalLight": "api/en/lights/DirectionalLight", "HemisphereLight": "api/en/lights/HemisphereLight", - "HemisphereLightProbe": "api/en/lights/HemisphereLightProbe", "Light": "api/en/lights/Light", "LightProbe": "api/en/lights/LightProbe", "PointLight": "api/en/lights/PointLight", @@ -242,6 +216,7 @@ "Interpolant": "api/en/math/Interpolant", "Line3": "api/en/math/Line3", "MathUtils": "api/en/math/MathUtils", + "Matrix2": "api/en/math/Matrix2", "Matrix3": "api/en/math/Matrix3", "Matrix4": "api/en/math/Matrix4", "Plane": "api/en/math/Plane", @@ -264,7 +239,9 @@ }, "Objects": { + "BatchedMesh": "api/en/objects/BatchedMesh", "Bone": "api/en/objects/Bone", + "ClippingGroup": "api/en/objects/ClippingGroup", "Group": "api/en/objects/Group", "InstancedMesh": "api/en/objects/InstancedMesh", "Line": "api/en/objects/Line", @@ -279,9 +256,7 @@ }, "Renderers": { - "WebGLMultipleRenderTargets": "api/en/renderers/WebGLMultipleRenderTargets", "WebGLRenderer": "api/en/renderers/WebGLRenderer", - "WebGL1Renderer": "api/en/renderers/WebGL1Renderer", "WebGLRenderTarget": "api/en/renderers/WebGLRenderTarget", "WebGL3DRenderTarget": "api/en/renderers/WebGL3DRenderTarget", "WebGLArrayRenderTarget": "api/en/renderers/WebGLArrayRenderTarget", @@ -317,6 +292,7 @@ "FramebufferTexture": "api/en/textures/FramebufferTexture", "Source": "api/en/textures/Source", "Texture": "api/en/textures/Texture", + "VideoFrameTexture": "api/en/textures/VideoFrameTexture", "VideoTexture": "api/en/textures/VideoTexture" } @@ -325,9 +301,7 @@ "Addons": { "Animations": { - "CCDIKSolver": "examples/en/animations/CCDIKSolver", - "MMDAnimationHelper": "examples/en/animations/MMDAnimationHelper", - "MMDPhysics": "examples/en/animations/MMDPhysics" + "CCDIKSolver": "examples/en/animations/CCDIKSolver" }, "Controls": { @@ -346,6 +320,7 @@ "ConvexGeometry": "examples/en/geometries/ConvexGeometry", "DecalGeometry": "examples/en/geometries/DecalGeometry", "ParametricGeometry": "examples/en/geometries/ParametricGeometry", + "TeapotGeometry": "examples/en/geometries/TeapotGeometry", "TextGeometry": "examples/en/geometries/TextGeometry" }, @@ -361,6 +336,14 @@ "LightProbeGenerator": "examples/en/lights/LightProbeGenerator" }, + "Lines": { + "Line2": "examples/en/lines/Line2", + "LineGeometry": "examples/en/lines/LineGeometry", + "LineMaterial": "examples/en/lines/LineMaterial", + "LineSegments2": "examples/en/lines/LineSegments2", + "LineSegmentsGeometry": "examples/en/lines/LineSegmentsGeometry" + }, + "Loaders": { "3DMLoader": "examples/en/loaders/3DMLoader", "DRACOLoader": "examples/en/loaders/DRACOLoader", @@ -368,18 +351,19 @@ "GLTFLoader": "examples/en/loaders/GLTFLoader", "KTX2Loader": "examples/en/loaders/KTX2Loader", "LDrawLoader": "examples/en/loaders/LDrawLoader", - "MMDLoader": "examples/en/loaders/MMDLoader", + "LUT3dlLoader": "examples/en/loaders/LUT3dlLoader", + "LUTCubeLoader": "examples/en/loaders/LUTCubeLoader", "MTLLoader": "examples/en/loaders/MTLLoader", "OBJLoader": "examples/en/loaders/OBJLoader", "PCDLoader": "examples/en/loaders/PCDLoader", "PDBLoader": "examples/en/loaders/PDBLoader", - "PRWMLoader": "examples/en/loaders/PRWMLoader", "SVGLoader": "examples/en/loaders/SVGLoader", "TGALoader": "examples/en/loaders/TGALoader" }, "Objects": { - "Lensflare": "examples/en/objects/Lensflare" + "Lensflare": "examples/en/objects/Lensflare", + "Sky": "examples/en/objects/Sky" }, "Post-Processing": { @@ -387,7 +371,6 @@ }, "Exporters": { - "ColladaExporter": "examples/en/exporters/ColladaExporter", "DRACOExporter": "examples/en/exporters/DRACOExporter", "EXRExporter": "examples/en/exporters/EXRExporter", "GLTFExporter": "examples/en/exporters/GLTFExporter", @@ -402,6 +385,14 @@ "OBB": "examples/en/math/OBB" }, + "Misc": { + "Timer": "examples/en/misc/Timer" + }, + + "Modifiers": { + "EdgeSplit": "examples/en/modifiers/EdgeSplitModifier" + }, + "ConvexHull": { "Face": "examples/en/math/convexhull/Face", "HalfEdge": "examples/en/math/convexhull/HalfEdge", @@ -422,6 +413,10 @@ "CameraUtils": "examples/en/utils/CameraUtils", "SceneUtils": "examples/en/utils/SceneUtils", "SkeletonUtils": "examples/en/utils/SkeletonUtils" + }, + + "WebXR": { + "XREstimatedLight": "examples/en/webxr/XREstimatedLight" } }, @@ -438,30 +433,6 @@ "ar": { - "الكتيب": { - - "البدء": { - "التثبيت": "manual/ar/introduction/Installation", - "إنشاء مشهد": "manual/ar/introduction/Creating-a-scene", - "فحص توافق WebGL": "manual/ar/introduction/WebGL-compatibility-check", - "رسم خطوط": "manual/ar/introduction/Drawing-lines", - "إنشاء نص": "manual/ar/introduction/Creating-text", - "تحميل نماذج ثلاثية الأبعاد": "manual/ar/introduction/Loading-3D-models", - "الأسئلة الشائعة": "manual/ar/introduction/FAQ", - "روابط مفيدة": "manual/ar/introduction/Useful-links" - }, - - "الخطوات التالية": { - "كيفية تحديث الأشياء": "manual/ar/introduction/How-to-update-things", - "كيفية التخلص من الأشياء": "manual/ar/introduction/How-to-dispose-of-objects", - "كيفية إنشاء محتوى VR": "manual/ar/introduction/How-to-create-VR-content", - "كيفية استخدام المعالجة اللاحقة (post-processing)": "manual/ar/introduction/How-to-use-post-processing", - "تحولات المصفوفة (Matrix transformations)": "manual/ar/introduction/Matrix-transformations", - "نظام الحركات": "manual/ar/introduction/Animation-system" - } - - }, - "المرجع": { "الحركات": { @@ -495,7 +466,244 @@ "Cameras": { "ArrayCamera": "api/ar/cameras/ArrayCamera", "Camera": "api/ar/cameras/Camera", - "CubeCamera": "api/ar/cameras/CubeCamera" + "CubeCamera": "api/ar/cameras/CubeCamera", + "OrthographicCamera": "api/ar/cameras/OrthographicCamera", + "PerspectiveCamera": "api/ar/cameras/PerspectiveCamera", + "StereoCamera": "api/ar/cameras/StereoCamera" + }, + + "Constants": { + "Animation": "api/ar/constants/Animation", + "Core": "api/ar/constants/Core", + "CustomBlendingEquation": "api/ar/constants/CustomBlendingEquations", + "BufferAttributeUsage": "api/ar/constants/BufferAttributeUsage", + "Materials": "api/ar/constants/Materials", + "Renderer": "api/ar/constants/Renderer", + "Textures": "api/ar/constants/Textures" + }, + + "Core": { + "BufferAttribute": "api/ar/core/BufferAttribute", + "BufferGeometry": "api/ar/core/BufferGeometry", + "Clock": "api/ar/core/Clock", + "EventDispatcher": "api/ar/core/EventDispatcher", + "GLBufferAttribute": "api/ar/core/GLBufferAttribute", + "InstancedBufferAttribute": "api/ar/core/InstancedBufferAttribute", + "InstancedBufferGeometry": "api/ar/core/InstancedBufferGeometry", + "InstancedInterleavedBuffer": "api/ar/core/InstancedInterleavedBuffer", + "InterleavedBuffer": "api/ar/core/InterleavedBuffer", + "InterleavedBufferAttribute": "api/ar/core/InterleavedBufferAttribute", + "Layers": "api/ar/core/Layers", + "Object3D": "api/ar/core/Object3D", + "Raycaster": "api/ar/core/Raycaster", + "Uniform": "api/ar/core/Uniform" + }, + + "Core / BufferAttributes": { + "BufferAttribute Types": "api/ar/core/bufferAttributeTypes/BufferAttributeTypes" + }, + + "Extras": { + "DataUtils": "api/ar/extras/DataUtils", + "Earcut": "api/ar/extras/Earcut", + "ImageUtils": "api/ar/extras/ImageUtils", + "PMREMGenerator": "api/ar/extras/PMREMGenerator", + "ShapeUtils": "api/ar/extras/ShapeUtils" + }, + + "Extras / Core": { + "Curve": "api/ar/extras/core/Curve", + "CurvePath": "api/ar/extras/core/CurvePath", + "Interpolations": "api/ar/extras/core/Interpolations", + "Path": "api/ar/extras/core/Path", + "Shape": "api/ar/extras/core/Shape", + "ShapePath": "api/ar/extras/core/ShapePath" + }, + + "Extras / Curves": { + "ArcCurve": "api/ar/extras/curves/ArcCurve", + "CatmullRomCurve3": "api/ar/extras/curves/CatmullRomCurve3", + "CubicBezierCurve": "api/ar/extras/curves/CubicBezierCurve", + "CubicBezierCurve3": "api/ar/extras/curves/CubicBezierCurve3", + "EllipseCurve": "api/ar/extras/curves/EllipseCurve", + "LineCurve": "api/ar/extras/curves/LineCurve", + "LineCurve3": "api/ar/extras/curves/LineCurve3", + "QuadraticBezierCurve": "api/ar/extras/curves/QuadraticBezierCurve", + "QuadraticBezierCurve3": "api/ar/extras/curves/QuadraticBezierCurve3", + "SplineCurve": "api/ar/extras/curves/SplineCurve" + }, + + "Geometries": { + "BoxGeometry": "api/ar/geometries/BoxGeometry", + "CapsuleGeometry": "api/ar/geometries/CapsuleGeometry", + "CircleGeometry": "api/ar/geometries/CircleGeometry", + "ConeGeometry": "api/ar/geometries/ConeGeometry", + "CylinderGeometry": "api/ar/geometries/CylinderGeometry", + "DodecahedronGeometry": "api/ar/geometries/DodecahedronGeometry", + "EdgesGeometry": "api/ar/geometries/EdgesGeometry", + "ExtrudeGeometry": "api/ar/geometries/ExtrudeGeometry", + "IcosahedronGeometry": "api/ar/geometries/IcosahedronGeometry", + "LatheGeometry": "api/ar/geometries/LatheGeometry", + "OctahedronGeometry": "api/ar/geometries/OctahedronGeometry", + "PlaneGeometry": "api/ar/geometries/PlaneGeometry", + "PolyhedronGeometry": "api/ar/geometries/PolyhedronGeometry", + "RingGeometry": "api/ar/geometries/RingGeometry", + "ShapeGeometry": "api/ar/geometries/ShapeGeometry", + "SphereGeometry": "api/ar/geometries/SphereGeometry", + "TetrahedronGeometry": "api/ar/geometries/TetrahedronGeometry", + "TorusGeometry": "api/ar/geometries/TorusGeometry", + "TorusKnotGeometry": "api/ar/geometries/TorusKnotGeometry", + "TubeGeometry": "api/ar/geometries/TubeGeometry", + "WireframeGeometry": "api/ar/geometries/WireframeGeometry" + }, + + "Helpers": { + "ArrowHelper": "api/ar/helpers/ArrowHelper", + "AxesHelper": "api/ar/helpers/AxesHelper", + "BoxHelper": "api/ar/helpers/BoxHelper", + "Box3Helper": "api/ar/helpers/Box3Helper", + "CameraHelper": "api/ar/helpers/CameraHelper", + "DirectionalLightHelper": "api/ar/helpers/DirectionalLightHelper", + "GridHelper": "api/ar/helpers/GridHelper", + "PolarGridHelper": "api/ar/helpers/PolarGridHelper", + "HemisphereLightHelper": "api/ar/helpers/HemisphereLightHelper", + "PlaneHelper": "api/ar/helpers/PlaneHelper", + "PointLightHelper": "api/ar/helpers/PointLightHelper", + "SkeletonHelper": "api/ar/helpers/SkeletonHelper", + "SpotLightHelper": "api/ar/helpers/SpotLightHelper" + }, + + "Lights": { + "AmbientLight": "api/ar/lights/AmbientLight", + "DirectionalLight": "api/ar/lights/DirectionalLight", + "HemisphereLight": "api/ar/lights/HemisphereLight", + "Light": "api/ar/lights/Light", + "LightProbe": "api/ar/lights/LightProbe", + "PointLight": "api/ar/lights/PointLight", + "RectAreaLight": "api/ar/lights/RectAreaLight", + "SpotLight": "api/ar/lights/SpotLight" + }, + + "Lights / Shadows": { + "LightShadow": "api/ar/lights/shadows/LightShadow", + "PointLightShadow": "api/ar/lights/shadows/PointLightShadow", + "DirectionalLightShadow": "api/ar/lights/shadows/DirectionalLightShadow", + "SpotLightShadow": "api/ar/lights/shadows/SpotLightShadow" + }, + + "Loaders": { + "AnimationLoader": "api/ar/loaders/AnimationLoader", + "AudioLoader": "api/ar/loaders/AudioLoader", + "BufferGeometryLoader": "api/ar/loaders/BufferGeometryLoader", + "Cache": "api/ar/loaders/Cache", + "CompressedTextureLoader": "api/ar/loaders/CompressedTextureLoader", + "CubeTextureLoader": "api/ar/loaders/CubeTextureLoader", + "DataTextureLoader": "api/ar/loaders/DataTextureLoader", + "FileLoader": "api/ar/loaders/FileLoader", + "ImageBitmapLoader": "api/ar/loaders/ImageBitmapLoader", + "ImageLoader": "api/ar/loaders/ImageLoader", + "Loader": "api/ar/loaders/Loader", + "LoaderUtils": "api/ar/loaders/LoaderUtils", + "MaterialLoader": "api/ar/loaders/MaterialLoader", + "ObjectLoader": "api/ar/loaders/ObjectLoader", + "TextureLoader": "api/ar/loaders/TextureLoader" + }, + + "Loaders / Managers": { + "DefaultLoadingManager": "api/ar/loaders/managers/DefaultLoadingManager", + "LoadingManager": "api/ar/loaders/managers/LoadingManager" + }, + + "Materials": { + "LineBasicMaterial": "api/ar/materials/LineBasicMaterial", + "LineDashedMaterial": "api/ar/materials/LineDashedMaterial", + "Material": "api/ar/materials/Material", + "MeshBasicMaterial": "api/ar/materials/MeshBasicMaterial", + "MeshDepthMaterial": "api/ar/materials/MeshDepthMaterial", + "MeshDistanceMaterial": "api/ar/materials/MeshDistanceMaterial", + "MeshLambertMaterial": "api/ar/materials/MeshLambertMaterial", + "MeshMatcapMaterial": "api/ar/materials/MeshMatcapMaterial", + "MeshNormalMaterial": "api/ar/materials/MeshNormalMaterial", + "MeshPhongMaterial": "api/ar/materials/MeshPhongMaterial", + "MeshPhysicalMaterial": "api/ar/materials/MeshPhysicalMaterial", + "MeshStandardMaterial": "api/ar/materials/MeshStandardMaterial", + "MeshToonMaterial": "api/ar/materials/MeshToonMaterial", + "PointsMaterial": "api/ar/materials/PointsMaterial", + "RawShaderMaterial": "api/ar/materials/RawShaderMaterial", + "ShaderMaterial": "api/ar/materials/ShaderMaterial", + "ShadowMaterial": "api/ar/materials/ShadowMaterial", + "SpriteMaterial": "api/ar/materials/SpriteMaterial" + }, + + "Math": { + "Box2": "api/ar/math/Box2", + "Box3": "api/ar/math/Box3", + "Color": "api/ar/math/Color", + "Cylindrical": "api/ar/math/Cylindrical", + "Euler": "api/ar/math/Euler", + "Frustum": "api/ar/math/Frustum", + "Interpolant": "api/ar/math/Interpolant", + "Line3": "api/ar/math/Line3", + "MathUtils": "api/ar/math/MathUtils", + "Matrix3": "api/ar/math/Matrix3", + "Matrix4": "api/ar/math/Matrix4", + "Plane": "api/ar/math/Plane", + "Quaternion": "api/ar/math/Quaternion", + "Ray": "api/ar/math/Ray", + "Sphere": "api/ar/math/Sphere", + "Spherical": "api/ar/math/Spherical", + "SphericalHarmonics3": "api/ar/math/SphericalHarmonics3", + "Triangle": "api/ar/math/Triangle", + "Vector2": "api/ar/math/Vector2", + "Vector3": "api/ar/math/Vector3", + "Vector4": "api/ar/math/Vector4" + }, + + "Math / Interpolants": { + "CubicInterpolant": "api/ar/math/interpolants/CubicInterpolant", + "DiscreteInterpolant": "api/ar/math/interpolants/DiscreteInterpolant", + "LinearInterpolant": "api/ar/math/interpolants/LinearInterpolant", + "QuaternionLinearInterpolant": "api/ar/math/interpolants/QuaternionLinearInterpolant" + }, + + "Objects": { + "Bone": "api/ar/objects/Bone", + "Group": "api/ar/objects/Group", + "InstancedMesh": "api/ar/objects/InstancedMesh", + "Line": "api/ar/objects/Line", + "LineLoop": "api/ar/objects/LineLoop", + "LineSegments": "api/ar/objects/LineSegments", + "LOD": "api/ar/objects/LOD", + "Mesh": "api/ar/objects/Mesh", + "Points": "api/ar/objects/Points", + "Skeleton": "api/ar/objects/Skeleton", + "SkinnedMesh": "api/ar/objects/SkinnedMesh", + "Sprite": "api/ar/objects/Sprite" + }, + + "Renderers": { + "WebGLRenderer": "api/ar/renderers/WebGLRenderer", + "WebGLRenderTarget": "api/ar/renderers/WebGLRenderTarget", + "WebGL3DRenderTarget": "api/ar/renderers/WebGL3DRenderTarget", + "WebGLArrayRenderTarget": "api/ar/renderers/WebGLArrayRenderTarget", + "WebGLCubeRenderTarget": "api/ar/renderers/WebGLCubeRenderTarget" + }, + + "Renderers / Shaders": { + "ShaderChunk": "api/ar/renderers/shaders/ShaderChunk", + "ShaderLib": "api/ar/renderers/shaders/ShaderLib", + "UniformsLib": "api/ar/renderers/shaders/UniformsLib", + "UniformsUtils": "api/ar/renderers/shaders/UniformsUtils" + }, + + "Renderers / WebXR": { + "WebXRManager": "api/ar/renderers/webxr/WebXRManager" + }, + + "Scenes": { + "Fog": "api/ar/scenes/Fog", + "FogExp2": "api/ar/scenes/FogExp2", + "Scene": "api/ar/scenes/Scene" } } @@ -504,30 +712,6 @@ "zh": { - "手册": { - - "起步": { - "安装": "manual/zh/introduction/Installation", - "创建一个场景": "manual/zh/introduction/Creating-a-scene", - "WebGL兼容性检查": "manual/zh/introduction/WebGL-compatibility-check", - "画线": "manual/zh/introduction/Drawing-lines", - "创建文字": "manual/zh/introduction/Creating-text", - "载入3D模型": "manual/zh/introduction/Loading-3D-models", - "常见问题": "manual/zh/introduction/FAQ", - "一些有用的链接": "manual/zh/introduction/Useful-links" - }, - - "进阶": { - "如何更新场景": "manual/zh/introduction/How-to-update-things", - "如何废置对象": "manual/zh/introduction/How-to-dispose-of-objects", - "如何创建VR内容": "manual/zh/introduction/How-to-create-VR-content", - "如何使用后期处理": "manual/zh/introduction/How-to-use-post-processing", - "矩阵变换": "manual/zh/introduction/Matrix-transformations", - "动画系统": "manual/zh/introduction/Animation-system" - } - - }, - "参考": { "动画": { @@ -571,6 +755,7 @@ "Animation": "api/zh/constants/Animation", "Core": "api/zh/constants/Core", "CustomBlendingEquation": "api/zh/constants/CustomBlendingEquations", + "BufferAttributeUsage": "api/zh/constants/BufferAttributeUsage", "Materials": "api/zh/constants/Materials", "Renderer": "api/zh/constants/Renderer", "Textures": "api/zh/constants/Textures" @@ -598,10 +783,13 @@ }, "附件": { + "Controls": "api/zh/extras/Controls", + "DataUtils": "api/zh/extras/DataUtils", "Earcut": "api/zh/extras/Earcut", "ImageUtils": "api/zh/extras/ImageUtils", "PMREMGenerator": "api/zh/extras/PMREMGenerator", - "ShapeUtils": "api/zh/extras/ShapeUtils" + "ShapeUtils": "api/zh/extras/ShapeUtils", + "TextureUtils": "api/zh/extras/TextureUtils" }, "附件 / 核心": { @@ -628,6 +816,7 @@ "几何体": { "BoxGeometry": "api/zh/geometries/BoxGeometry", + "CapsuleGeometry": "api/zh/geometries/CapsuleGeometry", "CircleGeometry": "api/zh/geometries/CircleGeometry", "ConeGeometry": "api/zh/geometries/ConeGeometry", "CylinderGeometry": "api/zh/geometries/CylinderGeometry", @@ -667,10 +856,8 @@ "灯光": { "AmbientLight": "api/zh/lights/AmbientLight", - "AmbientLightProbe": "api/zh/lights/AmbientLightProbe", "DirectionalLight": "api/zh/lights/DirectionalLight", "HemisphereLight": "api/zh/lights/HemisphereLight", - "HemisphereLightProbe": "api/zh/lights/HemisphereLightProbe", "Light": "api/zh/lights/Light", "LightProbe": "api/zh/lights/LightProbe", "PointLight": "api/zh/lights/PointLight", @@ -761,6 +948,7 @@ }, "物体": { + "BatchedMesh": "api/zh/objects/BatchedMesh", "Bone": "api/zh/objects/Bone", "Group": "api/zh/objects/Group", "InstancedMesh": "api/zh/objects/InstancedMesh", @@ -776,10 +964,10 @@ }, "渲染器": { - "WebGLMultipleRenderTargets": "api/zh/renderers/WebGLMultipleRenderTargets", "WebGLRenderer": "api/zh/renderers/WebGLRenderer", - "WebGL1Renderer": "api/zh/renderers/WebGL1Renderer", "WebGLRenderTarget": "api/zh/renderers/WebGLRenderTarget", + "WebGL3DRenderTarget": "api/zh/renderers/WebGL3DRenderTarget", + "WebGLArrayRenderTarget": "api/zh/renderers/WebGLArrayRenderTarget", "WebGLCubeRenderTarget": "api/zh/renderers/WebGLCubeRenderTarget" }, @@ -805,8 +993,8 @@ "CompressedTexture": "api/zh/textures/CompressedTexture", "CompressedArrayTexture": "api/zh/textures/CompressedArrayTexture", "CubeTexture": "api/zh/textures/CubeTexture", - "DataArrayTexture": "api/zh/textures/DataArrayTexture", "Data3DTexture": "api/zh/textures/Data3DTexture", + "DataArrayTexture": "api/zh/textures/DataArrayTexture", "DataTexture": "api/zh/textures/DataTexture", "DepthTexture": "api/zh/textures/DepthTexture", "FramebufferTexture": "api/zh/textures/FramebufferTexture", @@ -817,12 +1005,18 @@ }, - "Addons": { + "附加": { + + "动画": { + "CCDIKSolver": "examples/zh/animations/CCDIKSolver" + }, "控制": { + "ArcballControls": "examples/zh/controls/ArcballControls", "DragControls": "examples/zh/controls/DragControls", "FirstPersonControls": "examples/zh/controls/FirstPersonControls", "FlyControls": "examples/zh/controls/FlyControls", + "MapControls": "examples/zh/controls/MapControls", "OrbitControls": "examples/zh/controls/OrbitControls", "PointerLockControls": "examples/zh/controls/PointerLockControls", "TrackballControls": "examples/zh/controls/TrackballControls", @@ -833,6 +1027,7 @@ "ConvexGeometry": "examples/zh/geometries/ConvexGeometry", "DecalGeometry": "examples/zh/geometries/DecalGeometry", "ParametricGeometry": "examples/zh/geometries/ParametricGeometry", + "TeapotGeometry": "examples/zh/geometries/TeapotGeometry", "TextGeometry": "examples/zh/geometries/TextGeometry" }, @@ -840,7 +1035,8 @@ "LightProbeHelper": "examples/zh/helpers/LightProbeHelper", "PositionalAudioHelper": "examples/zh/helpers/PositionalAudioHelper", "RectAreaLightHelper": "examples/zh/helpers/RectAreaLightHelper", - "VertexNormalsHelper": "examples/zh/helpers/VertexNormalsHelper" + "VertexNormalsHelper": "examples/zh/helpers/VertexNormalsHelper", + "VertexTangentsHelper": "examples/zh/helpers/VertexTangentsHelper" }, "灯光": { @@ -848,22 +1044,62 @@ }, "加载器": { + "3DMLoader": "examples/zh/loaders/3DMLoader", + "DRACOLoader": "examples/zh/loaders/DRACOLoader", "FontLoader": "examples/zh/loaders/FontLoader", "GLTFLoader": "examples/zh/loaders/GLTFLoader", - "MMDLoader": "examples/zh/loaders/MMDLoader", + "KTX2Loader": "examples/zh/loaders/KTX2Loader", + "LDrawLoader": "examples/zh/loaders/LDrawLoader", + "LUT3dlLoader": "examples/zh/loaders/LUT3dlLoader", + "LUTCubeLoader": "examples/zh/loaders/LUTCubeLoader", "MTLLoader": "examples/zh/loaders/MTLLoader", "OBJLoader": "examples/zh/loaders/OBJLoader", - "PCDLoader": "examples/zh/loaders/PCDLoader" + "PCDLoader": "examples/zh/loaders/PCDLoader", + "PDBLoader": "examples/zh/loaders/PDBLoader", + "SVGLoader": "examples/zh/loaders/SVGLoader", + "TGALoader": "examples/zh/loaders/TGALoader" }, "物体": { - "Lensflare": "examples/zh/objects/Lensflare" + "Lensflare": "examples/zh/objects/Lensflare", + "Sky": "examples/zh/objects/Sky" }, "后期处理": { "EffectComposer": "examples/zh/postprocessing/EffectComposer" }, + "导出器": { + "DRACOExporter": "examples/zh/exporters/DRACOExporter", + "EXRExporter": "examples/zh/exporters/EXRExporter", + "GLTFExporter": "examples/zh/exporters/GLTFExporter", + "OBJExporter": "examples/zh/exporters/OBJExporter", + "PLYExporter": "examples/zh/exporters/PLYExporter", + "STLExporter": "examples/zh/exporters/STLExporter" + }, + + "数学库": { + "LookupTable": "examples/zh/math/Lut", + "MeshSurfaceSampler": "examples/zh/math/MeshSurfaceSampler", + "OBB": "examples/zh/math/OBB" + }, + + "修改器":{ + "EdgeSplitModifier": "examples/zh/modifiers/EdgeSplitModifier" + }, + + "杂项": { + "Timer": "examples/zh/misc/Timer" + }, + + "凸包": { + "Face": "examples/zh/math/convexhull/Face", + "HalfEdge": "examples/zh/math/convexhull/HalfEdge", + "ConvexHull": "examples/zh/math/convexhull/ConvexHull", + "VertexNode": "examples/zh/math/convexhull/VertexNode", + "VertexList": "examples/zh/math/convexhull/VertexList" + }, + "渲染器": { "CSS2DRenderer": "examples/zh/renderers/CSS2DRenderer", "CSS3DRenderer": "examples/zh/renderers/CSS3DRenderer", @@ -872,8 +1108,13 @@ "实用工具": { "BufferGeometryUtils": "examples/zh/utils/BufferGeometryUtils", + "CameraUtils": "examples/zh/utils/CameraUtils", "SceneUtils": "examples/zh/utils/SceneUtils", "SkeletonUtils": "examples/zh/utils/SkeletonUtils" + }, + + "WebXR": { + "XREstimatedLight": "examples/zh/webxr/XREstimatedLight" } }, @@ -890,30 +1131,6 @@ "ko": { - "매뉴얼": { - - "시작하기": { - "설치": "manual/ko/introduction/Installation", - "장면 만들기": "manual/ko/introduction/Creating-a-scene", - "WebGL 호환성 검사": "manual/ko/introduction/WebGL-compatibility-check", - "선 그리기": "manual/ko/introduction/Drawing-lines", - "텍스트 만들기": "manual/ko/introduction/Creating-text", - "3D 모델 불러오기": "manual/ko/introduction/Loading-3D-models", - "FAQ": "manual/ko/introduction/FAQ", - "참고 링크": "manual/ko/introduction/Useful-links" - }, - - "심화 과정": { - "오브젝트를 업데이트하는 방법": "manual/ko/introduction/How-to-update-things", - "오브젝트를 폐기하는 방법": "manual/ko/introduction/How-to-dispose-of-objects", - "VR 컨텐츠를 만드는 방법": "manual/ko/introduction/How-to-create-VR-content", - "후처리 사용 방법": "manual/ko/introduction/How-to-use-post-processing", - "행렬 변환": "manual/ko/introduction/Matrix-transformations", - "애니메이션 시스템": "manual/ko/introduction/Animation-system" - } - - }, - "레퍼런스": { "애니메이션": { @@ -1043,76 +1260,26 @@ "Addons": { "컨트롤": { + "ArcballControls": "examples/ko/controls/ArcballControls", "DragControls": "examples/ko/controls/DragControls", "FirstPersonControls": "examples/ko/controls/FirstPersonControls", "FlyControls": "examples/ko/controls/FlyControls", + "MapControls": "examples/ko/controls/MapControls", "OrbitControls": "examples/ko/controls/OrbitControls", "PointerLockControls": "examples/ko/controls/PointerLockControls", "TrackballControls": "examples/ko/controls/TrackballControls", "TransformControls": "examples/ko/controls/TransformControls" - } - - } - - }, + }, - "ja": { - - "マニュアル": { - - "はじめてみましょう": { - "インストールの方法": "manual/ja/introduction/Installation", - "シーンの作成": "manual/ja/introduction/Creating-a-scene", - "WebGLの互換性の確認": "manual/ja/introduction/WebGL-compatibility-check", - "線を引く": "manual/ja/introduction/Drawing-lines", - "テキストを作成する": "manual/ja/introduction/Creating-text", - "3Dモデルをロードする": "manual/ja/introduction/Loading-3D-models", - "ライブラリとプラグイン": "manual/ja/introduction/Libraries-and-Plugins", - "FAQ": "manual/ja/introduction/FAQ", - "役にたつリンク集": "manual/ja/introduction/Useful-links" - }, - - "次の段階": { - "更新の仕方": "manual/ja/introduction/How-to-update-things", - "オブジェクトを廃棄する方法": "manual/ja/introduction/How-to-dispose-of-objects", - "VRコンテンツの作り方": "manual/ja/introduction/How-to-create-VR-content", - "post-processingの使い方": "manual/ja/introduction/How-to-use-post-processing", - "行列の変換": "manual/ja/introduction/Matrix-transformations", - "アニメーションシステム": "manual/ja/introduction/Animation-system" + "WebXR": { + "XREstimatedLight": "examples/ko/webxr/XREstimatedLight" } - } }, "it": { - "Manuale": { - - "Per iniziare": { - "Installazione": "manual/it/introduction/Installation", - "Creare una scena": "manual/it/introduction/Creating-a-scene", - "Controllo compatibilità WebGL": "manual/it/introduction/WebGL-compatibility-check", - "Disegnare linee": "manual/it/introduction/Drawing-lines", - "Creare testo": "manual/it/introduction/Creating-text", - "Caricare modelli 3D": "manual/it/introduction/Loading-3D-models", - "Librerie e Plugins": "manual/it/introduction/Libraries-and-Plugins", - "FAQ": "manual/it/introduction/FAQ", - "Link utili": "manual/it/introduction/Useful-links" - }, - - "Prossimi passi": { - "Come aggiornare le cose": "manual/it/introduction/How-to-update-things", - "Come liberare le risorse": "manual/it/introduction/How-to-dispose-of-objects", - "Come creare contenuti VR": "manual/it/introduction/How-to-create-VR-content", - "Come utilizzare il post-processing": "manual/it/introduction/How-to-use-post-processing", - "Trasformazioni di matrici": "manual/it/introduction/Matrix-transformations", - "Sistema di animazione": "manual/it/introduction/Animation-system", - "Gestione del colore": "manual/it/introduction/Color-management" - } - - }, - "Riferimenti": { "Animazione": { @@ -1255,10 +1422,8 @@ "Luci": { "AmbientLight": "api/it/lights/AmbientLight", - "AmbientLightProbe": "api/it/lights/AmbientLightProbe", "DirectionalLight": "api/it/lights/DirectionalLight", "HemisphereLight": "api/it/lights/HemisphereLight", - "HemisphereLightProbe": "api/it/lights/HemisphereLightProbe", "Light": "api/it/lights/Light", "LightProbe": "api/it/lights/LightProbe", "PointLight": "api/it/lights/PointLight", @@ -1364,9 +1529,7 @@ }, "Renderers": { - "WebGLMultipleRenderTargets": "api/it/renderers/WebGLMultipleRenderTargets", "WebGLRenderer": "api/it/renderers/WebGLRenderer", - "WebGL1Renderer": "api/it/renderers/WebGL1Renderer", "WebGLRenderTarget": "api/it/renderers/WebGLRenderTarget", "WebGL3DRenderTarget": "api/it/renderers/WebGL3DRenderTarget", "WebGLArrayRenderTarget": "api/it/renderers/WebGLArrayRenderTarget", @@ -1408,32 +1571,6 @@ }, "pt-br": { - "Manual": { - - "Comece a usar": { - "Instalação": "manual/pt-br/introduction/Installation", - "Criando uma cena": "manual/pt-br/introduction/Creating-a-scene", - "Compatibilidade WebGL": "manual/pt-br/introduction/WebGL-compatibility-check", - "Desenhando linhas": "manual/pt-br/introduction/Drawing-lines", - "Criando texto": "manual/pt-br/introduction/Creating-text", - "Carregando modelos 3D": "manual/pt-br/introduction/Loading-3D-models", - "Bibliotecas e Plugins": "manual/pt-br/introduction/Libraries-and-Plugins", - "FAQ": "manual/pt-br/introduction/FAQ", - "Links úteis": "manual/pt-br/introduction/Useful-links" - }, - - "Próximos Passos": { - "Como atualizar as coisas": "manual/pt-br/introduction/How-to-update-things", - "Como descartar objetos": "manual/pt-br/introduction/How-to-dispose-of-objects", - "Como criar conteúdo de VR": "manual/pt-br/introduction/How-to-create-VR-content", - "Como usar o pós-processamento": "manual/pt-br/introduction/How-to-use-post-processing", - "Transformações de matriz": "manual/pt-br/introduction/Matrix-transformations", - "Sistema de animação": "manual/pt-br/introduction/Animation-system", - "Gerenciamento de cor": "manual/pt-br/introduction/Color-management" - } - - }, - "Referência": { "Animation": { @@ -1489,32 +1626,6 @@ "fr": { - "Manuel": { - - "Débuter": { - "Installation": "manual/fr/introduction/Installation", - "Créer une scène": "manual/fr/introduction/Creating-a-scene", - "Compatibilité WebGL": "manual/fr/introduction/WebGL-compatibility-check", - "Dessiner des lignes": "manual/fr/introduction/Drawing-lines", - "Créer un texte": "manual/fr/introduction/Creating-text", - "Importer des modèles 3D": "manual/fr/introduction/Loading-3D-models", - "Librairies et Plugins": "manual/fr/introduction/Libraries-and-Plugins", - "FAQ": "manual/fr/introduction/FAQ", - "Liens Utiles": "manual/fr/introduction/Useful-links" - }, - - "Étapes Suivantes": { - "Mettre les éléments à jour": "manual/fr/introduction/How-to-update-things", - "Supprimer un objet": "manual/fr/introduction/How-to-dispose-of-objects", - "Créer du contenu VR": "manual/fr/introduction/How-to-create-VR-content", - "Utiliser le post-processing": "manual/fr/introduction/How-to-use-post-processing", - "Matrices de transformation": "manual/fr/introduction/Matrix-transformations", - "Système d'animation": "manual/fr/introduction/Animation-system", - "Gestion des couleurs": "manual/fr/introduction/Color-management" - } - - }, - "Référence": { "Animation": { @@ -1613,438 +1724,6 @@ } - }, - - "ru": { - - "Руководство": { - - "Приступая к работе": { - "Установка": "manual/ru/introduction/Installation", - "Создание сцены": "manual/ru/introduction/Creating-a-scene", - "Проверка совместимости с WebGL": "manual/ru/introduction/WebGL-compatibility-check", - "Рисование линий": "manual/ru/introduction/Drawing-lines", - "Создание текста": "manual/ru/introduction/Creating-text", - "Загрузка 3D-моделей": "manual/ru/introduction/Loading-3D-models", - "Библиотеки и плагины": "manual/ru/introduction/Libraries-and-Plugins", - "Часто задаваемые вопросы": "manual/ru/introduction/FAQ", - "Полезные ссылки": "manual/ru/introduction/Useful-links" - }, - - "Next Steps": { - "How to update things": "manual/en/introduction/How-to-update-things", - "How to dispose of objects": "manual/en/introduction/How-to-dispose-of-objects", - "How to create VR content": "manual/en/introduction/How-to-create-VR-content", - "How to use post-processing": "manual/en/introduction/How-to-use-post-processing", - "Matrix transformations": "manual/en/introduction/Matrix-transformations", - "Animation system": "manual/en/introduction/Animation-system", - "Color management": "manual/en/introduction/Color-management" - } - - }, - - "Reference": { - - "Animation": { - "AnimationAction": "api/en/animation/AnimationAction", - "AnimationClip": "api/en/animation/AnimationClip", - "AnimationMixer": "api/en/animation/AnimationMixer", - "AnimationObjectGroup": "api/en/animation/AnimationObjectGroup", - "AnimationUtils": "api/en/animation/AnimationUtils", - "KeyframeTrack": "api/en/animation/KeyframeTrack", - "PropertyBinding": "api/en/animation/PropertyBinding", - "PropertyMixer": "api/en/animation/PropertyMixer" - }, - - "Animation / Tracks": { - "BooleanKeyframeTrack": "api/en/animation/tracks/BooleanKeyframeTrack", - "ColorKeyframeTrack": "api/en/animation/tracks/ColorKeyframeTrack", - "NumberKeyframeTrack": "api/en/animation/tracks/NumberKeyframeTrack", - "QuaternionKeyframeTrack": "api/en/animation/tracks/QuaternionKeyframeTrack", - "StringKeyframeTrack": "api/en/animation/tracks/StringKeyframeTrack", - "VectorKeyframeTrack": "api/en/animation/tracks/VectorKeyframeTrack" - }, - - "Audio": { - "Audio": "api/en/audio/Audio", - "AudioAnalyser": "api/en/audio/AudioAnalyser", - "AudioContext": "api/en/audio/AudioContext", - "AudioListener": "api/en/audio/AudioListener", - "PositionalAudio": "api/en/audio/PositionalAudio" - }, - - "Cameras": { - "ArrayCamera": "api/en/cameras/ArrayCamera", - "Camera": "api/en/cameras/Camera", - "CubeCamera": "api/en/cameras/CubeCamera", - "OrthographicCamera": "api/en/cameras/OrthographicCamera", - "PerspectiveCamera": "api/en/cameras/PerspectiveCamera", - "StereoCamera": "api/en/cameras/StereoCamera" - }, - - "Constants": { - "Animation": "api/en/constants/Animation", - "Core": "api/en/constants/Core", - "CustomBlendingEquation": "api/en/constants/CustomBlendingEquations", - "BufferAttributeUsage": "api/en/constants/BufferAttributeUsage", - "Materials": "api/en/constants/Materials", - "Renderer": "api/en/constants/Renderer", - "Textures": "api/en/constants/Textures" - }, - - "Core": { - "BufferAttribute": "api/en/core/BufferAttribute", - "BufferGeometry": "api/en/core/BufferGeometry", - "Clock": "api/en/core/Clock", - "EventDispatcher": "api/en/core/EventDispatcher", - "GLBufferAttribute": "api/en/core/GLBufferAttribute", - "InstancedBufferAttribute": "api/en/core/InstancedBufferAttribute", - "InstancedBufferGeometry": "api/en/core/InstancedBufferGeometry", - "InstancedInterleavedBuffer": "api/en/core/InstancedInterleavedBuffer", - "InterleavedBuffer": "api/en/core/InterleavedBuffer", - "InterleavedBufferAttribute": "api/en/core/InterleavedBufferAttribute", - "Layers": "api/en/core/Layers", - "Object3D": "api/en/core/Object3D", - "Raycaster": "api/en/core/Raycaster", - "Uniform": "api/en/core/Uniform" - }, - - "Core / BufferAttributes": { - "BufferAttribute Types": "api/en/core/bufferAttributeTypes/BufferAttributeTypes" - }, - - "Extras": { - "DataUtils": "api/en/extras/DataUtils", - "Earcut": "api/en/extras/Earcut", - "ImageUtils": "api/en/extras/ImageUtils", - "PMREMGenerator": "api/en/extras/PMREMGenerator", - "ShapeUtils": "api/en/extras/ShapeUtils" - }, - - "Extras / Core": { - "Curve": "api/en/extras/core/Curve", - "CurvePath": "api/en/extras/core/CurvePath", - "Interpolations": "api/en/extras/core/Interpolations", - "Path": "api/en/extras/core/Path", - "Shape": "api/en/extras/core/Shape", - "ShapePath": "api/en/extras/core/ShapePath" - }, - - "Extras / Curves": { - "ArcCurve": "api/en/extras/curves/ArcCurve", - "CatmullRomCurve3": "api/en/extras/curves/CatmullRomCurve3", - "CubicBezierCurve": "api/en/extras/curves/CubicBezierCurve", - "CubicBezierCurve3": "api/en/extras/curves/CubicBezierCurve3", - "EllipseCurve": "api/en/extras/curves/EllipseCurve", - "LineCurve": "api/en/extras/curves/LineCurve", - "LineCurve3": "api/en/extras/curves/LineCurve3", - "QuadraticBezierCurve": "api/en/extras/curves/QuadraticBezierCurve", - "QuadraticBezierCurve3": "api/en/extras/curves/QuadraticBezierCurve3", - "SplineCurve": "api/en/extras/curves/SplineCurve" - }, - - "Geometries": { - "BoxGeometry": "api/en/geometries/BoxGeometry", - "CapsuleGeometry": "api/en/geometries/CapsuleGeometry", - "CircleGeometry": "api/en/geometries/CircleGeometry", - "ConeGeometry": "api/en/geometries/ConeGeometry", - "CylinderGeometry": "api/en/geometries/CylinderGeometry", - "DodecahedronGeometry": "api/en/geometries/DodecahedronGeometry", - "EdgesGeometry": "api/en/geometries/EdgesGeometry", - "ExtrudeGeometry": "api/en/geometries/ExtrudeGeometry", - "IcosahedronGeometry": "api/en/geometries/IcosahedronGeometry", - "LatheGeometry": "api/en/geometries/LatheGeometry", - "OctahedronGeometry": "api/en/geometries/OctahedronGeometry", - "PlaneGeometry": "api/en/geometries/PlaneGeometry", - "PolyhedronGeometry": "api/en/geometries/PolyhedronGeometry", - "RingGeometry": "api/en/geometries/RingGeometry", - "ShapeGeometry": "api/en/geometries/ShapeGeometry", - "SphereGeometry": "api/en/geometries/SphereGeometry", - "TetrahedronGeometry": "api/en/geometries/TetrahedronGeometry", - "TorusGeometry": "api/en/geometries/TorusGeometry", - "TorusKnotGeometry": "api/en/geometries/TorusKnotGeometry", - "TubeGeometry": "api/en/geometries/TubeGeometry", - "WireframeGeometry": "api/en/geometries/WireframeGeometry" - }, + } - "Helpers": { - "ArrowHelper": "api/en/helpers/ArrowHelper", - "AxesHelper": "api/en/helpers/AxesHelper", - "BoxHelper": "api/en/helpers/BoxHelper", - "Box3Helper": "api/en/helpers/Box3Helper", - "CameraHelper": "api/en/helpers/CameraHelper", - "DirectionalLightHelper": "api/en/helpers/DirectionalLightHelper", - "GridHelper": "api/en/helpers/GridHelper", - "PolarGridHelper": "api/en/helpers/PolarGridHelper", - "HemisphereLightHelper": "api/en/helpers/HemisphereLightHelper", - "PlaneHelper": "api/en/helpers/PlaneHelper", - "PointLightHelper": "api/en/helpers/PointLightHelper", - "SkeletonHelper": "api/en/helpers/SkeletonHelper", - "SpotLightHelper": "api/en/helpers/SpotLightHelper" - }, - - "Lights": { - "AmbientLight": "api/en/lights/AmbientLight", - "AmbientLightProbe": "api/en/lights/AmbientLightProbe", - "DirectionalLight": "api/en/lights/DirectionalLight", - "HemisphereLight": "api/en/lights/HemisphereLight", - "HemisphereLightProbe": "api/en/lights/HemisphereLightProbe", - "Light": "api/en/lights/Light", - "LightProbe": "api/en/lights/LightProbe", - "PointLight": "api/en/lights/PointLight", - "RectAreaLight": "api/en/lights/RectAreaLight", - "SpotLight": "api/en/lights/SpotLight" - }, - - "Lights / Shadows": { - "LightShadow": "api/en/lights/shadows/LightShadow", - "PointLightShadow": "api/en/lights/shadows/PointLightShadow", - "DirectionalLightShadow": "api/en/lights/shadows/DirectionalLightShadow", - "SpotLightShadow": "api/en/lights/shadows/SpotLightShadow" - }, - - "Loaders": { - "AnimationLoader": "api/en/loaders/AnimationLoader", - "AudioLoader": "api/en/loaders/AudioLoader", - "BufferGeometryLoader": "api/en/loaders/BufferGeometryLoader", - "Cache": "api/en/loaders/Cache", - "CompressedTextureLoader": "api/en/loaders/CompressedTextureLoader", - "CubeTextureLoader": "api/en/loaders/CubeTextureLoader", - "DataTextureLoader": "api/en/loaders/DataTextureLoader", - "FileLoader": "api/en/loaders/FileLoader", - "ImageBitmapLoader": "api/en/loaders/ImageBitmapLoader", - "ImageLoader": "api/en/loaders/ImageLoader", - "Loader": "api/en/loaders/Loader", - "LoaderUtils": "api/en/loaders/LoaderUtils", - "MaterialLoader": "api/en/loaders/MaterialLoader", - "ObjectLoader": "api/en/loaders/ObjectLoader", - "TextureLoader": "api/en/loaders/TextureLoader" - }, - - "Loaders / Managers": { - "DefaultLoadingManager": "api/en/loaders/managers/DefaultLoadingManager", - "LoadingManager": "api/en/loaders/managers/LoadingManager" - }, - - "Materials": { - "LineBasicMaterial": "api/en/materials/LineBasicMaterial", - "LineDashedMaterial": "api/en/materials/LineDashedMaterial", - "Material": "api/en/materials/Material", - "MeshBasicMaterial": "api/en/materials/MeshBasicMaterial", - "MeshDepthMaterial": "api/en/materials/MeshDepthMaterial", - "MeshDistanceMaterial": "api/en/materials/MeshDistanceMaterial", - "MeshLambertMaterial": "api/en/materials/MeshLambertMaterial", - "MeshMatcapMaterial": "api/en/materials/MeshMatcapMaterial", - "MeshNormalMaterial": "api/en/materials/MeshNormalMaterial", - "MeshPhongMaterial": "api/en/materials/MeshPhongMaterial", - "MeshPhysicalMaterial": "api/en/materials/MeshPhysicalMaterial", - "MeshStandardMaterial": "api/en/materials/MeshStandardMaterial", - "MeshToonMaterial": "api/en/materials/MeshToonMaterial", - "PointsMaterial": "api/en/materials/PointsMaterial", - "RawShaderMaterial": "api/en/materials/RawShaderMaterial", - "ShaderMaterial": "api/en/materials/ShaderMaterial", - "ShadowMaterial": "api/en/materials/ShadowMaterial", - "SpriteMaterial": "api/en/materials/SpriteMaterial" - }, - - "Math": { - "Box2": "api/en/math/Box2", - "Box3": "api/en/math/Box3", - "Color": "api/en/math/Color", - "Cylindrical": "api/en/math/Cylindrical", - "Euler": "api/en/math/Euler", - "Frustum": "api/en/math/Frustum", - "Interpolant": "api/en/math/Interpolant", - "Line3": "api/en/math/Line3", - "MathUtils": "api/en/math/MathUtils", - "Matrix3": "api/en/math/Matrix3", - "Matrix4": "api/en/math/Matrix4", - "Plane": "api/en/math/Plane", - "Quaternion": "api/en/math/Quaternion", - "Ray": "api/en/math/Ray", - "Sphere": "api/en/math/Sphere", - "Spherical": "api/en/math/Spherical", - "SphericalHarmonics3": "api/en/math/SphericalHarmonics3", - "Triangle": "api/en/math/Triangle", - "Vector2": "api/en/math/Vector2", - "Vector3": "api/en/math/Vector3", - "Vector4": "api/en/math/Vector4" - }, - - "Math / Interpolants": { - "CubicInterpolant": "api/en/math/interpolants/CubicInterpolant", - "DiscreteInterpolant": "api/en/math/interpolants/DiscreteInterpolant", - "LinearInterpolant": "api/en/math/interpolants/LinearInterpolant", - "QuaternionLinearInterpolant": "api/en/math/interpolants/QuaternionLinearInterpolant" - }, - - "Objects": { - "Bone": "api/en/objects/Bone", - "Group": "api/en/objects/Group", - "InstancedMesh": "api/en/objects/InstancedMesh", - "Line": "api/en/objects/Line", - "LineLoop": "api/en/objects/LineLoop", - "LineSegments": "api/en/objects/LineSegments", - "LOD": "api/en/objects/LOD", - "Mesh": "api/en/objects/Mesh", - "Points": "api/en/objects/Points", - "Skeleton": "api/en/objects/Skeleton", - "SkinnedMesh": "api/en/objects/SkinnedMesh", - "Sprite": "api/en/objects/Sprite" - }, - - "Renderers": { - "WebGLMultipleRenderTargets": "api/en/renderers/WebGLMultipleRenderTargets", - "WebGLRenderer": "api/en/renderers/WebGLRenderer", - "WebGL1Renderer": "api/en/renderers/WebGL1Renderer", - "WebGLRenderTarget": "api/en/renderers/WebGLRenderTarget", - "WebGL3DRenderTarget": "api/en/renderers/WebGL3DRenderTarget", - "WebGLArrayRenderTarget": "api/en/renderers/WebGLArrayRenderTarget", - "WebGLCubeRenderTarget": "api/en/renderers/WebGLCubeRenderTarget" - }, - - "Renderers / Shaders": { - "ShaderChunk": "api/en/renderers/shaders/ShaderChunk", - "ShaderLib": "api/en/renderers/shaders/ShaderLib", - "UniformsLib": "api/en/renderers/shaders/UniformsLib", - "UniformsUtils": "api/en/renderers/shaders/UniformsUtils" - }, - - "Renderers / WebXR": { - "WebXRManager": "api/en/renderers/webxr/WebXRManager" - }, - - "Scenes": { - "Fog": "api/en/scenes/Fog", - "FogExp2": "api/en/scenes/FogExp2", - "Scene": "api/en/scenes/Scene" - }, - - "Textures": { - "CanvasTexture": "api/en/textures/CanvasTexture", - "CompressedTexture": "api/en/textures/CompressedTexture", - "CompressedArrayTexture": "api/en/textures/CompressedArrayTexture", - "CubeTexture": "api/en/textures/CubeTexture", - "Data3DTexture": "api/en/textures/Data3DTexture", - "DataArrayTexture": "api/en/textures/DataArrayTexture", - "DataTexture": "api/en/textures/DataTexture", - "DepthTexture": "api/en/textures/DepthTexture", - "FramebufferTexture": "api/en/textures/FramebufferTexture", - "Source": "api/en/textures/Source", - "Texture": "api/en/textures/Texture", - "VideoTexture": "api/en/textures/VideoTexture" - } - - }, - - "Examples": { - - "Animations": { - "CCDIKSolver": "examples/en/animations/CCDIKSolver", - "MMDAnimationHelper": "examples/en/animations/MMDAnimationHelper", - "MMDPhysics": "examples/en/animations/MMDPhysics" - }, - - "Controls": { - "ArcballControls": "examples/en/controls/ArcballControls", - "DragControls": "examples/en/controls/DragControls", - "FirstPersonControls": "examples/en/controls/FirstPersonControls", - "FlyControls": "examples/en/controls/FlyControls", - "OrbitControls": "examples/en/controls/OrbitControls", - "PointerLockControls": "examples/en/controls/PointerLockControls", - "TrackballControls": "examples/en/controls/TrackballControls", - "TransformControls": "examples/en/controls/TransformControls" - }, - - "Geometries": { - "ConvexGeometry": "examples/en/geometries/ConvexGeometry", - "DecalGeometry": "examples/en/geometries/DecalGeometry", - "ParametricGeometry": "examples/en/geometries/ParametricGeometry", - "TextGeometry": "examples/en/geometries/TextGeometry" - }, - - "Helpers": { - "LightProbeHelper": "examples/en/helpers/LightProbeHelper", - "PositionalAudioHelper": "examples/en/helpers/PositionalAudioHelper", - "RectAreaLightHelper": "examples/en/helpers/RectAreaLightHelper", - "VertexNormalsHelper": "examples/en/helpers/VertexNormalsHelper", - "VertexTangentsHelper": "examples/en/helpers/VertexTangentsHelper" - }, - - "Lights": { - "LightProbeGenerator": "examples/en/lights/LightProbeGenerator" - }, - - "Loaders": { - "3DMLoader": "examples/en/loaders/3DMLoader", - "DRACOLoader": "examples/en/loaders/DRACOLoader", - "FontLoader": "examples/en/loaders/FontLoader", - "GLTFLoader": "examples/en/loaders/GLTFLoader", - "KTX2Loader": "examples/en/loaders/KTX2Loader", - "LDrawLoader": "examples/en/loaders/LDrawLoader", - "MMDLoader": "examples/en/loaders/MMDLoader", - "MTLLoader": "examples/en/loaders/MTLLoader", - "OBJLoader": "examples/en/loaders/OBJLoader", - "PCDLoader": "examples/en/loaders/PCDLoader", - "PDBLoader": "examples/en/loaders/PDBLoader", - "PRWMLoader": "examples/en/loaders/PRWMLoader", - "SVGLoader": "examples/en/loaders/SVGLoader", - "TGALoader": "examples/en/loaders/TGALoader" - }, - - "Objects": { - "Lensflare": "examples/en/objects/Lensflare" - }, - - "Post-Processing": { - "EffectComposer": "examples/en/postprocessing/EffectComposer" - }, - - "Exporters": { - "ColladaExporter": "examples/en/exporters/ColladaExporter", - "EXRExporter": "examples/en/exporters/EXRExporter", - "GLTFExporter": "examples/en/exporters/GLTFExporter", - "OBJExporter": "examples/en/exporters/OBJExporter", - "PLYExporter": "examples/en/exporters/PLYExporter" - }, - - "Math": { - "LookupTable": "examples/en/math/Lut", - "MeshSurfaceSampler": "examples/en/math/MeshSurfaceSampler", - "OBB": "examples/en/math/OBB" - }, - - "ConvexHull": { - "Face": "examples/en/math/convexhull/Face", - "HalfEdge": "examples/en/math/convexhull/HalfEdge", - "ConvexHull": "examples/en/math/convexhull/ConvexHull", - "VertexNode": "examples/en/math/convexhull/VertexNode", - "VertexList": "examples/en/math/convexhull/VertexList" - }, - - "Renderers": { - "CSS2DRenderer": "examples/en/renderers/CSS2DRenderer", - "CSS3DRenderer": "examples/en/renderers/CSS3DRenderer", - "SVGRenderer": "examples/en/renderers/SVGRenderer" - - }, - - "Utils": { - "BufferGeometryUtils": "examples/en/utils/BufferGeometryUtils", - "CameraUtils": "examples/en/utils/CameraUtils", - "SceneUtils": "examples/en/utils/SceneUtils", - "SkeletonUtils": "examples/en/utils/SkeletonUtils" - } - - }, - - "Developer Reference": { - - "WebGLRenderer": { - "WebGLProgram": "api/en/renderers/webgl/WebGLProgram" - } - - } - - } } diff --git a/docs/manual/ar/introduction/Animation-system.html b/docs/manual/ar/introduction/Animation-system.html deleted file mode 100644 index ac5fc29787fed1..00000000000000 --- a/docs/manual/ar/introduction/Animation-system.html +++ /dev/null @@ -1,113 +0,0 @@ - - - - - - - - - -

        نظام الحركات

        - -

        نظرة عامة

        - -

        - يضمن نظام الرسوم المتحركة three.js ، إمكانية تحريك الخصائص المختلفة لنماذجك: عظام [page:SkinnedMesh skinned and rigged model] ، morph targets ، خصائص مادة مختلفة (ألوان ، عتامة ، منطقية) ، الرؤية والتحولات. يمكن أن تتلاشى الخصائص المتحركة، أو تتلاشى وتتشوه. يمكن تغيير مقاييس الوزن والوقت للرسوم المتحركة المتزامنة المختلفة على نفس الكائن وكذلك على كائنات مختلفة بشكل مستقل. يمكن مزامنة الرسوم المتحركة المختلفة على نفس الشيء وعلى كائنات مختلفة. -

        - - لتحقيق كل هذا في نظام واحد متجانس ، نظام الرسوم المتحركة three.js شعد تغييرا هامًا في عام [link:https://github.com/mrdoob/three.js/issues/6881 2015] (احذر من المعلومات القديمة!) ، ولديه الآن بنية مشابهة لـ Unity / Unreal Engine 4. تقدم هذه الصفحة لمحة موجزة عن المكونات الرئيسية للنظام وكيف تعمل معًا. - -

        - -

        مقاطع الحركات

        - -

        - - إذا قمت باستيراد كائن ثلاثي الأبعاد متحرك بنجاح (لا يهم ما إذا كان يحتوي على عظام أو أهداف تشكيل أو كليهما) - على سبيل المثال تصديره من Blender مع [link:https://github.com/KhronosGroup/glTF-Blender-IO glTF Blender exporter] وتحميله في مشهد three.js باستخدام 333 - يضمن أن توجد مصفوفة تسمى "الرسوم المتحركة" ، تحتوي على 444 لهذا النموذج (انظر قائمة برامج التحميل المتوفرة أدناه). -

        - - يحتفظ كل مقطع *AnimationClip* عادةً ببيانات نشاط معين للكائن. إذا كانت الشبكة عبارة عن شخصية ، على سبيل المثال ، فقد يكون هناك مقطع Animation واحد للمشي ، وثاني للقفز ، وثالث للتنقل وما إلى ذلك. - -

        - -

        مسارات الإطار الأساسي (Keyframe tracks)

        - -

        - - داخل ملف *AnimationClip* ، يتم تخزين البيانات الخاصة بكل خاصية متحركة في [page:KeyframeTrack]. بافتراض أن كائن حرف له هيكل عظمي [page:Skeleton skeleton] ، يمكن لمسار إطار رئيسي واحد تخزين البيانات لتغييرات موضع عظم الذراع السفلي بمرور الوقت ، وهو أمر مختلف تتبع البيانات الخاصة بتغييرات دوران نفس العظم ، وثلث موضع المسار ، دوران أو تحجيم عظم آخر ، وما إلى ذلك. يجب أن يكون واضحًا ، أن AnimationClip يمكن أن يتكون من الكثير من هذه المسارات. -

        - - بافتراض أن النموذج يحتوي على morph targets (على سبيل المثال ، أحد أهداف التشكيل يظهر وجهًا ودودًا والآخر يظهر وجهًا غاضبًا) ، فإن كل مسار يحمل المعلومات المتعلقة بكيفية تغيير الرقم [page:Mesh.morphTargetInfluences influence] لهدف تشكيل معين أثناء أداء المقطع. - -

        - -

        خالط الحركات (Animation Mixer)

        - -

        - - تشكل البيانات المخزنة أساس الرسوم المتحركة فقط - يتم التحكم في التشغيل الفعلي بواسطة [page:AnimationMixer]. يمكنك تخيل هذا ليس فقط كمشغل للرسوم المتحركة ، ولكن ك كجهاز أو مثل وحدة التحكم في المزج الحقيقي ، والتي يمكنها التحكم في العديد من الرسوم المتحركة في وقت واحد ومزجها ودمجها. - -

        - -

        أحداث الحركات (Animation Actions)

        - -

        - - يحتوي *AnimationMixer* نفسه على عدد قليل جدًا من الخصائص والطرق (العامة) ، لأنه يمكن التحكم فيه بواسطة [page:AnimationAction AnimationActions]. من خلال تكوين *AnimationAction* يمكنك تحديد وقت تشغيل *AnimationClip* معين أو إيقافه مؤقتًا أو إيقاف تشغيله أحد الخلاطات ، إذا كان يجب تكرار المقطع وعدد مرات تكرارها ، سواء كان يجب إجراؤه بتلاشي أو مقياس زمني ، وبعض الأشياء الإضافية ، مثل التلاشي أو التزامن. - -

        - -

        تحريك مجموعة من النماذج

        - -

        - - إذا كنت تريد أن تطبق على مجموعة من الكائنات حالة حركة مشتركة ، يمكنك استخدام [page:AnimationObjectGroup].. - -

        - -

        التنسيقات و عناصر التحميل المدعومة

        - -

        - لاحظ أنه لا تتضمن جميع تنسيقات النماذج الرسوم المتحركة (لا سيما OBJ) ، وأن بعض أدوات تحميل three.js فقط تدعم [page:AnimationClip AnimationClip] تسلسلًا. العديد منها يدعم هذا النوع من الرسوم المتحركة: -

        - -
          -
        • [page:ObjectLoader THREE.ObjectLoader]
        • -
        • THREE.BVHLoader
        • -
        • THREE.ColladaLoader
        • -
        • THREE.FBXLoader
        • -
        • [page:GLTFLoader THREE.GLTFLoader]
        • -
        • THREE.MMDLoader
        • -
        - -

        - لاحظ أن 3ds max و Maya لا يمكنهم حاليًا تصدير العديد من الرسوم المتحركة (بمعنى الرسوم المتحركة غير الموجودة في نفس المخطط الزمني) مباشرةً إلى ملف واحد. -

        - -

        مثال

        - - - let mesh; - - // Create an AnimationMixer, and get the list of AnimationClip instances - const mixer = new THREE.AnimationMixer( mesh ); - const clips = mesh.animations; - - // Update the mixer on each frame - function update () { - mixer.update( deltaSeconds ); - } - - // Play a specific animation - const clip = THREE.AnimationClip.findByName( clips, 'dance' ); - const action = mixer.clipAction( clip ); - action.play(); - - // Play all animations - clips.forEach( function ( clip ) { - mixer.clipAction( clip ).play(); - } ); - - - - diff --git a/docs/manual/ar/introduction/Creating-a-scene.html b/docs/manual/ar/introduction/Creating-a-scene.html deleted file mode 100644 index b7f572a01d8f1e..00000000000000 --- a/docs/manual/ar/introduction/Creating-a-scene.html +++ /dev/null @@ -1,266 +0,0 @@ - - - - - - - - - -

        إنشاء مشهد([name])

        - -

        الهدف من هذا القسم هو تقديم لمحة وجيزة عن كيفية عمل المكتبة. سنبدأ بتحضير مشهد يتمثل في مكعب ثلاثي الأبعاد .الصفحة مرفوقة بمثال أسفلها في حالة واجهتك بعض المشاكل وإحتجت إلى المساعدة.

        - -

        قبل أن نبدأ

        - -

        Before you can use three.js, you need somewhere to display it. Save the following HTML to a file on your computer and open it in your browser.

        - - - <!DOCTYPE html> - <html> - <head> - <meta charset="utf-8"> - <title>My first three.js app</title> - <style> - body { margin: 0; } - </style> - </head> - <body> - <script type="module"> - import * as THREE from 'https://unpkg.com/three/build/three.module.js'; - - // Our Javascript will go here. - </script> - </body> - </html> - - -

        - هذا كل شيء. بقية الأوامر البرمجية ستكون محتوات في وسم <script> الفارغ حاليا. -

        - -

        إنشاء مشهد

        - -

        - لنتمكن من إظهار أي شيء بإستهمال three.js، نحتاج ثلاثة عناصر أساسية: المسرح (Scene)، الكاميرا (Camera)، و العارض - (Renderer). -

        - - - const scene = new THREE.Scene(); - const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); - - const renderer = new THREE.WebGLRenderer(); - renderer.setSize( window.innerWidth, window.innerHeight ); - document.body.appendChild( renderer.domElement ); - - -

        - لنتمهل لحظة من أجل توضيح ما يحصل هنا. لقد قمنا بتحضير كل من المسرح، الكاميرا، و العارض. -

        - -

        - توفر مكتبة three.js العديد من الخيارات بخصوص نوع الكاميرا. للوقت الراهن، سنستعمل - PerspectiveCamera. -

        - -

        - أول ميزة هي مجال الرؤية - Field of view. يمكن التعبير عنها بإستعمال إختصارها كالأتي FOV. هذه - القيمة تمثل مجال - المشاهدة المتاح في أي وقت - من العرض. وحده قيسها هي الدرجة (degrees) -

        - -

        - القيمة الثانية هي نسبة العرض إلى الارتفاع - aspect ratio. من المستحسن إستعمال نتيجة قسمة عرض و - طول العنصر الحاوي، و إلا ستحصل على تجربة مماثلة - لمشاهدة فيلم قديم على تلفاز عريض حيث ستكون الصورة متغيرة. -

        - -

        - القيمتين المتبقيتين هما أقرب و أبعد سطح فاصل. نقصد بدلك أن أي عنصر في المشهد - أبعد من السطح الفاصل البعيد بالنسبة - للكاميرا أو أقرب من السطح الفاصل القريب لن يتم عرضه. - أنت لست مطالب بالقلق حيال هذا، و لكن من الممكن أن تريد إستعمال قيم أخرى من أجل الحصول على أداء أفضل. -

        - -

        - نصل الأن إلى العارض. هنا أين يكمن السحر. بالإضافة لإستعمال WebGLRenderer، المكتبة تتكفل بتمكين بعض المتصفحات - القديمة التي لا - تدعم WebGL لسبب ما من الخصائص المفقودة. -

        - -

        - إلي جانب إنشاء نموذج من مكون عارض، نحن مطالبون بتوفير قياس المشهد المراد عرضه. إنها لا فكرة جيدة أن نستعمل عرض و - طول المنطقة التي نريد ملأها في الصفحة. في هذه الحالة إستعملنا عرض و طول نافدة المتصفح. بالنسبة لتطبيقات عالية - الأداء، يمكنك توفير قيم أقل مثل window.innerWidth/2 و window.innerHeight/2، - التي - ستجعل المشهد يعرض أسرع بنصف المدة السابقة. -

        - -

        - على سبيل المثال إلغاء قيمة updateStyle كالأتي: -
        - setSize(window.innerWidth/2, window.innerHeight/2, false) -
        - ستجعل المشهد يعرض بدقة أقل بنصف - الدرجة القديمة، مع العلم أن < canvas> - الخاصة بك تم إمدادها ب100% في كلا الطول و العرض. -

        - -

        - أخيرا و ليس أخرا، سنقوم بإضافة العارض إلى ملف الـHTML. -
        - < canvas> هو وسم يستعمله العارض لإظهار المشهد من خلاله. -

        - -

        - "كل شيء جيد، و لكن أي المكعب الذي وعدتنا به؟" لنقم بإضافته الأن. -

        - - - const geometry = new THREE.BoxGeometry( 1, 1, 1 ); - const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); - const cube = new THREE.Mesh( geometry, material ); - scene.add( cube ); - - camera.position.z = 5; - - -

        - لكي نقوم بإنشاء مكعب، نحتاج مكون BoxGeometry. هذا الأخير مسؤول عن حفض أماكن كل النقاط - (vertices) و تعبئة كل الأوجه - (faces) المكونة للمكعب. سنقوم بالتعمق في هذا الموضوع مستقبلا. -

        - -

        - بالإضافة إلى الهندسة الخاصة بالمكعب، نحتاج المادة المكونة له لتعطيه لون. Three.js تأتي مع العديد من المواد، - و - لكن سنكتفي بإستعمال MeshBasicMaterial للوقت الراهن. كل المواد تأخذ مجموعة من القيم ستطبق - عليها - من أجل الوصول إلى النتيجة المرادة. لإبقاء الأشياء بسيطة، قمنا بإرفاق قيمة اللون التي تحمل - 0x00ff00، و الذي يمثل اللون الأخضر. كيفية إحتساب القيمة هي نفسها كإستعمال CSS أو Photoshop - (hex colors). -

        - -

        - الخطوة التالية هي إنشاء جسم Mesh. نقصد به الشيء الذي سيتكفل بالتعامل مع هندسة الشكل و - تطبيقها - على المادة المرفوقة، و من ثم يمكننا إدخال الشكل الجسم النهائي إلى المشهد، و التحرك بحرية حوله. -

        - -

        - عند إستعمال أمر ()scene.add، الشيء المراد إضافته للمشهد سيضاف في الإحداثيات التالية - (0,0,0). هذا يمكن له أن يشكل بعض المشاكل كون الكاميرا في هذه الحالة وسط المكعب. لتجنب هذا - نقوم - ببساطة بإبعاد الكاميرا قليلا. -

        - -

        عرض المشهد

        - -

        - إن قمت بنسخ الأوامر البرمجية الموجودة أعله وسط ملف HTML الذي قمنا بإنشائه مسبقا، لم تتمكن من رؤية أي شيء حتى - الأن. هذا بسبب أننا لم نقم بعرض أي شيء حتى اللحظة. لذلك، ما نحتاجه يدعى العرض (render) أو - حلقة - الحركات (animation loop). -

        - - - function animate() { - requestAnimationFrame( animate ); - renderer.render( scene, camera ); - } - animate(); - - -

        - هذه الشفرة البرمجية تقوم بإنشاء حلقة تجعل العارض يعيد تحديث المشهد كل مرة يحدث فيها تغيير في الصفحة (أو نظريا - هذا يعني 60 - مرة - خلال كل ثانية). إن كنت لا تملك تجربة مسبقة في صناعة ألعاب المتصفح، ستتسائل "لماذا لا نستعمل - setInterval؟" الحقيقة أننا بإمكاننا ذلك و لكن requestAnimationFrame تقدم لنا - العديد من - المزايا. من أهما أنها تقوم بإيقاف العمل عندما يقوم المستعمل بتغيير الصفحة، بالإضافة لعدم إستهلاك قدرة - الحاسب الخاص بالجهاز و عمر البطارية. -

        - -

        تحريك المكعب

        - -

        - إن قمت بإضافة الأوامر البرمجية السابقة للملف الخاص بك، من الأرجح أنك ترى الأن مكعبا أخضر اللون. لنقم بجعله - أكثر - جذابية من خلال تدويره. -

        - -

        - قم بإضافة الشفرة التالية فوق السطر الذي يحتوي أمر renderer.render في الوظيفة - (animate): -

        - - - cube.rotation.x += 0.01; - cube.rotation.y += 0.01; - - -

        - هذه الأوامر سيتم تشغيلها في كل إطار (frame). ما يعني 60 مرة في الثانية تقريبا، و بذلك ستمكن المكعب من الدوران. - في - الأساس، أي شيء تريد تحريكه أو تغيره خلال فترة عمل التطبيق يستوجب أن تكون الأوامر الخاصة بذلك قد تم تشغيلها - داخل - حلقة الحركات. يمكنك بالتأكيد مناداة وظائف أخرى، لكي لا ينتهي بك المطاف بوظيفة واحدة تحتوي على مئات السطور. -

        - -

        النتيجة

        -

        - تهانينا! لقد قمت بإكمال أول تطبيق three.js لك. الأمر ليس معقدا، يجب عليك فقد البدأ بشيء ما. -

        - -

        - الشفرة البرمجية الكاملة في الأسفل إلى جانب محرر مباشر [link:https://jsfiddle.net/0c1oqf38/ live example]. - أنت - مدعو للعب بالأوامر البرمجية لكي تصبح صورة كيفية عملها أوضح من قبل. -

        - - - <html> - <head> - <meta charset="utf-8"> - <title>My first three.js app</title> - <style> - body { margin: 0; } - </style> - </head> - <body> - <script type="module"> - import * as THREE from 'https://unpkg.com/three/build/three.module.js'; - - const scene = new THREE.Scene(); - const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); - - const renderer = new THREE.WebGLRenderer(); - renderer.setSize( window.innerWidth, window.innerHeight ); - document.body.appendChild( renderer.domElement ); - - const geometry = new THREE.BoxGeometry( 1, 1, 1 ); - const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); - const cube = new THREE.Mesh( geometry, material ); - scene.add( cube ); - - camera.position.z = 5; - - function animate() { - requestAnimationFrame( animate ); - - cube.rotation.x += 0.01; - cube.rotation.y += 0.01; - - renderer.render( scene, camera ); - } - - animate(); - </script> - </body> - </html> - - - diff --git a/docs/manual/ar/introduction/Creating-text.html b/docs/manual/ar/introduction/Creating-text.html deleted file mode 100644 index a5a57f58d7e139..00000000000000 --- a/docs/manual/ar/introduction/Creating-text.html +++ /dev/null @@ -1,110 +0,0 @@ - - - - - - - - - -

        إنشاء نص ([name])

        -
        -

        - غالبًا ما تحتاج إلى استخدام نص في تطبيق three.js الخاص بك - إليك طريقتان يمكنك القيام بذلك. -

        -
        - -

        1. DOM + CSS

        -
        -

        - يعد استخدام HTML بشكل عام أسهل وأسرع طريقة لإضافة نص. هذه هي الطريقة المستخدمة للتراكبات الوصفية في معظم الأمثلة الثلاثة. -

        -

        يمكنك إضافة محتوى إلى ملف

        - <div id="info">Description</div> - -

        - وإستخدم ترميز CSS لوضع موضع مطلق (position absolutely) في موضع فوق كل المواقع الأخرى باستخدام z-index بالأخاص إذا كنت تقوم بتشغيل على كامل الشاشة three.js. -

        - - -#info { - position: absolute; - top: 10px; - width: 100%; - text-align: center; - z-index: 100; - display:block; -} - - -
        - - - -

        2. ارسم نصًا على (canvas) واستخدمه كـ [page:Texture]

        -
        -

        استخدم هذه الطريقة إذا كنت ترغب في رسم نص بسهولة على سطح في مشهد three.js.

        -
        - - -

        3. قم بإنشاء نموذج في التطبيق ثلاثي الأبعاد المفضل لديك وقم بتصديره إلى three.js

        -
        -

        إستخدم هذه الطريقة إذا كنت تفضل العمل مع تطبيقاتك ثلاثية الأبعاد وإستيراد النماذج إلى three.js

        -
        - - - -

        4. هندسة النص الإجرائي (Procedural Text Geometry)

        -
        -

        - إذا كنت تفضل العمل فقط في THREE.js أو إنشاء أشكال هندسية إجرائية وديناميكية للنص ثلاثي الأبعاد ، فيمكنك إنشاء شبكة تعتبر هندستها مثيلًا لـ THREE.TextGeometry: -

        -

        - new THREE.TextGeometry( text, parameters ); -

        -

        - لكي يعمل هذا، ستحتاج Text Geometry إلى نموذج من THREE.Font لتعيينه لجعله إعداد "الخط" الخاصة به. - - راجع صفحة [page:TextGeometry] للحصول على مزيد من المعلومات حول كيفية القيام بذلك ، و وصف كل الخيارات المتاحة ، وقائمة بخطوط JSON التي تأتي مع توزيع THREE.js نفسه. -

        - -

        أمثلة

        - -

        - [example:webgl_geometry_text WebGL / geometry / text]
        - [example:webgl_shadowmap WebGL / shadowmap] -

        - -

        - إذا كان Typeface معطلاً ، أو كنت تريد استخدام خط غير موجود ، فهناك برنامج تعليمي مع برنامج نصي بايثون لـ bender يسمح لك بتصدير النص إلى Three.js بتنسيق JSON: -
        - [link:http://www.jaanga.com/2012/03/blender-to-threejs-create-3d-text-with.html] -

        - -
        - - - -

        5. خطوط نقطية (Bitmap Fonts)

        -
        -

        - تسمح BMFonts (الخطوط النقطية) بدمج الصور الرمزية في BufferGeometry واحد. يدعم عرض BMFont التفاف الكلمات ، وتباعد الأحرف ، وتقنين الأحرف ، وحقول المسافة الموقعة مع المشتقات القياسية ، وحقول المسافة الموقعة متعددة القنوات ، والخطوط متعددة الأنسجة ، والمزيد. - انظر [link:https://github.com/felixmariotto/three-mesh-ui three-mesh-ui] أو [link:https://github.com/Jam3/three-bmfont-text three-bmfont-text]. -

        -

        - تتوفر الخطوط في مشاريع مثل [link:https://github.com/etiennepinchon/aframe-fonts A-Frame Fonts] ، أو يمكنك إنشاء خطوطك الخاصة من أي خط .TTF ، مع التحسين لتضمين الأحرف المطلوبة للمشروع. -

        -

        - بعض الأدوات المفيدة -

        -
          -
        • [link:http://msdf-bmfont.donmccurdy.com/ msdf-bmfont-web] (على شبكة الإنترنت)
        • -
        • [link:https://github.com/soimy/msdf-bmfont-xml msdf-bmfont-xml] (سطر الأوامر)
        • -
        • [link:https://github.com/libgdx/libgdx/wiki/Hiero hiero] (تطبيق سطح مكتب)
        • -
        -
        - - - - - diff --git a/docs/manual/ar/introduction/Drawing-lines.html b/docs/manual/ar/introduction/Drawing-lines.html deleted file mode 100644 index b743caac3b7381..00000000000000 --- a/docs/manual/ar/introduction/Drawing-lines.html +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - -

        رسم خطوط ([name])

        -
        -

        - لنفترض أنك تريد رسم خط أو دائرة ، وليس إطارًا سلكيًا [page:Mesh]. نحتاج أولاً إلى إعداد العارض [page:WebGLRenderer renderer] ، المسرح [page:Scene scene] والكاميرا (انظر صفحة إنشاء مشهد). -

        - -

        هذا هو الكود الذي سنستخدمه:

        - -const renderer = new THREE.WebGLRenderer(); -renderer.setSize( window.innerWidth, window.innerHeight ); -document.body.appendChild( renderer.domElement ); - -const camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 500 ); -camera.position.set( 0, 0, 100 ); -camera.lookAt( 0, 0, 0 ); - -const scene = new THREE.Scene(); - -

        الشيء التالي الذي سنفعله هو تحديد المادة. بالنسبة للخطوط ، يتعين علينا استخدام [page:LineBasicMaterial] أو [page:LineDashedMaterial].

        - -//create a blue LineBasicMaterial -const material = new THREE.LineBasicMaterial( { color: 0x0000ff } ); - - -

        - بعد إختيار المادة سنحتاج إلى الهندسة الخاصة بها التي تحتوي بعض القمم(vertices): -

        - - -const points = []; -points.push( new THREE.Vector3( - 10, 0, 0 ) ); -points.push( new THREE.Vector3( 0, 10, 0 ) ); -points.push( new THREE.Vector3( 10, 0, 0 ) ); - -const geometry = new THREE.BufferGeometry().setFromPoints( points ); - - -

        لاحظ أنه تم رسم الخطوط بين كل زوج متتالي من الرؤوس ، ولكن ليس بين الأول والأخير (الخط غير مغلق).

        - -

        الآن بعد أن أصبح لدينا نقاط لخطين ومادة ، يمكننا تجميعها معًا لتشكيل خط.

        - -const line = new THREE.Line( geometry, material ); - -

        كل ما تبقى هو إضافته إلى المشهد و إستعمال أمر العرض [page:WebGLRenderer.render render].

        - - -scene.add( line ); -renderer.render( scene, camera ); - - -

        يجب أن ترى الآن سهمًا يشير إلى الأعلى ، مكون من خطين أزرقين.

        -
        - - diff --git a/docs/manual/ar/introduction/FAQ.html b/docs/manual/ar/introduction/FAQ.html deleted file mode 100644 index 9ac15052afca33..00000000000000 --- a/docs/manual/ar/introduction/FAQ.html +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - - - -

        الأسئلة الشائعة ([name])

        - -

        ما هو أفضل تنسيق النموذج ثلاثي الأبعاد الذي تدعمه المكتبة؟

        -
        -

        - التنسيق الموصى به لاستيراد المجسمات وتصديرها هو glTF (GL Transmission Format). نظرًا لأن glTF يركز على تسليم أصول وقت التشغيل ، فهو مضغوط للإرسال وسريع التحميل. -

        -

        - توفر three.js أدوات تحميل للعديد من التنسيقات الشائعة الأخرى مثل FBX أو Collada أو OBJ أيضًا. ومع ذلك ، يجب أن تحاول دائمًا إنشاء سير عمل قائم على glTF في مشاريعك. لمزيد من المعلومات ، انظر [link:#manual/introduction/Loading-3D-models loading 3D models]. -

        -
        - -

        لماذا توجد علامات meta viewport في الأمثلة؟

        -
        - <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> - -

        تتحكم هذه العلامات في حجم منفذ العرض ومقياسه لمتصفحات الجوال (حيث يمكن عرض محتوى الصفحة بحجم مختلف عن إطار العرض المرئي).

        - -

        [link:https://developer.apple.com/library/content/documentation/AppleApplications/Reference/SafariWebContent/UsingtheViewport/UsingtheViewport.html Safari: Using the Viewport]

        - -

        [link:https://developer.mozilla.org/en-US/docs/Web/HTML/Viewport_meta_tag MDN: Using the viewport meta tag]

        -
        - -

        كيف يمكن الحفاظ على مقياس المشهد عند تغيير الحجم؟

        -

        - نريد أن تظهر جميع النماذج ، بغض النظر عن بعدها عن الكاميرا ، بالحجم نفسه ، حتى عندما يتم تغيير حجم النافذة. - - المعادلة الأساسية لحل هذه المعادلة هي معادلة الارتفاع المرئي على مسافة معينة: - - -visible_height = 2 * Math.tan( ( Math.PI / 180 ) * camera.fov / 2 ) * distance_from_camera; - - إذا قمنا بزيادة ارتفاع النافذة بنسبة معينة ، فإن ما نريده هو زيادة الارتفاع المرئي في جميع المسافات بنفس النسبة المئوية. - - لا يمكن القيام بذلك عن طريق تغيير موضع الكاميرا. بل ما يجب أن تفعله هو تغيير مجال رؤية الكاميرا. - [link:http://jsfiddle.net/Q4Jpu/ Example]. -

        - -

        لماذا جزء من نموذجي غير مرئي؟

        -

        - يمكن أن يكون هدا بسبب إعدام الوجه, فالأوجه التي تبني النموذج لها إتجاه معين من خلاله يتم تقرير ما يتم إضهار. و هده الظاهرة تقوم بإلغاء الوجه الخلفي في الحالات العادية. - لمعرفة ما إذا كانت هذه هي مشكلتك ، قم بتغيير جانب المادة إلى THREE.DoubleSide. - - material.side = THREE.DoubleSide -

        - -

        Why does three.js sometimes return strange results for invalid inputs?

        -

        - For performance reasons, three.js doesn't validate inputs in most cases. It's your app's responsibility to make sure that all inputs are valid. -

        - - diff --git a/docs/manual/ar/introduction/How-to-create-VR-content.html b/docs/manual/ar/introduction/How-to-create-VR-content.html deleted file mode 100644 index 85f45c3b386e5a..00000000000000 --- a/docs/manual/ar/introduction/How-to-create-VR-content.html +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - - - - - -

        كيفية إنشاء محتوى VR

        - -

        - يقدم هذا الدليل لمحة موجزة عن المكونات الأساسية لتطبيق VR مخصص للويب بإستعمال three.js. -

        - -

        سير العمل

        - -

        - أولاً ، عليك ضم [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/webxr/VRButton.js VRButton.js] في مشروعك. -

        - - -import { VRButton } from 'three/addons/webxr/VRButton.js'; - - -

        - *VRButton.createButton()* يقوم بأمرين مهمين: أولا يقوم بإنشاء زر يشير إلى توافق الواقع الافتراضي. إلى جانب ذلك ، يبدأ جلسة VR إذا قام المستخدم بتنشيط الزر. كل ما عليك فعله هو إضافة السطر التالي من التعليمات البرمجية إلى تطبيقك. -

        - - -document.body.appendChild( VRButton.createButton( renderer ) ); - - -

        - بعد ذلك ، عليك توجيه نموذج *WebGLRenderer* لتمكين عرض XR. -

        - - -renderer.xr.enabled = true; - - -

        - أخيرًا ، يجب عليك ضبط حلقة الرسوم المتحركة لأننا لا نستطيع استخدام وظيفة *window.requestAnimationFrame()* المعروفة لدينا. بالنسبة لمشاريع الواقع الافتراضي ، نستخدم [page:WebGLRenderer.setAnimationLoop setAnimationLoop]. - يبدو الحد الأدنى من الكود كما يلي: -

        - - -renderer.setAnimationLoop( function () { - - renderer.render( scene, camera ); - -} ); - - -

        الخطوات التالية

        - -

        - ألق نظرة على أحد أمثلة WebVR الرسمية لرؤية سير العمل.

        - - [example:webxr_vr_ballshooter WebXR / VR / ballshooter]
        - [example:webxr_vr_cubes WebXR / VR / cubes]
        - [example:webxr_vr_dragging WebXR / VR / dragging]
        - [example:webxr_vr_paint WebXR / VR / paint]
        - [example:webxr_vr_panorama_depth WebXR / VR / panorama_depth]
        - [example:webxr_vr_panorama WebXR / VR / panorama]
        - [example:webxr_vr_rollercoaster WebXR / VR / rollercoaster]
        - [example:webxr_vr_sandbox WebXR / VR / sandbox]
        - [example:webxr_vr_sculpt WebXR / VR / sculpt]
        - [example:webxr_vr_video WebXR / VR / video] -

        - - - - diff --git a/docs/manual/ar/introduction/How-to-dispose-of-objects.html b/docs/manual/ar/introduction/How-to-dispose-of-objects.html deleted file mode 100644 index 478ab6ec33fa3c..00000000000000 --- a/docs/manual/ar/introduction/How-to-dispose-of-objects.html +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - - - - - - -

        كيفية التخلص من الأشياء

        - -

        - أحد الجوانب المهمة لتحسين الأداء وتجنب تسرب الذاكرة في تطبيقك هو التخلص من كيانات المكتبة غير المستخدمة. عندما تقوم بإنشاء مثيل من النوع * three.js * ، فإنك تخصص قدرًا معينًا من الذاكرة. - ومع ذلك ، يُنشئ * three.js * كائنات محددة مثل الأشكال الهندسية أو المواد ، كيانات ذات صلة بـ WebGL مثل المخازن المؤقتة أو برامج التظليل الضرورية للعرض. من المهم إبراز أن هذه الكائنات لا يتم تحريرها تلقائيًا ، وبدلاً من ذلك ، يجب أن يستخدم التطبيق واجهة برمجة تطبيقات خاصة لتحرير هذه الموارد. يقدم هذا الدليل نظرة عامة موجزة حول كيفية استخدام واجهة برمجة التطبيقات هذه وما هي الكائنات ذات الصلة في هذا السياق. -

        - -

        الهندسة (Geometries)

        - -

        - تمثل الهندسة عادةً معلومات قمة الرأس تُعرَّف على أنها مجموعة من السمات. تنشئ * three.js * داخليًا كائنًا من النوع [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLBuffer WebGLBuffer] لكل سمة. يتم حذف هذه الكيانات فقط إذا اتصلت بـ [page:BufferGeometry.dispose](). إذا أصبحت الهندسة قديمة في التطبيق الخاص بك ، فقم بتنفيذ الطريقة لتحرير جميع الموارد ذات الصلة. -

        - -

        المواد (Materials)

        - -

        - تحدد المادة كيفية تجسيد الكائنات. يستخدم * three.js * معلومات تعريف المادة لإنشاء برنامج تظليل للعرض. - لا يمكن حذف برامج Shader إلا إذا تم التخلص من المواد المعنية. لأسباب تتعلق بالأداء ، يحاول * three.js * إعادة استخدام القائمة برامج تظليل إن أمكن. لذلك يتم حذف برنامج shader فقط إذا تم التخلص من جميع المواد ذات الصلة. يمكنك الإشارة إلى التخلص من مادة عن طريق تنفيذ [page:Material.dispose] (). -

        - -

        الأنسجة (Textures)

        - -

        - التخلص من المواد ليس له أي تأثير على القوام. يتم التعامل معها بشكل منفصل حيث يمكن استخدام نسيج واحد بواسطة مواد متعددة في نفس الوقت. - عندما تقوم بإنشاء مثيل [page:Texture] ، فإن three.js داخليًا تنشئ مثيلًا من [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLTexture WebGLTexture]. - على غرار المخازن المؤقتة ، لا يمكن حذف هذا الكائن إلا عن طريق استدعاء [page:Texture.dispose](). -

        - -

        - If you use an *ImageBitmap* as the texture's data source, you have to call [link:https://developer.mozilla.org/en-US/docs/Web/API/ImageBitmap/close ImageBitmap.close]() at the application level to dispose of all CPU-side resources. - An automated call of *ImageBitmap.close()* in [page:Texture.dispose]() is not possible, since the image bitmap becomes unusable, and the engine has no way of knowing if the image bitmap is used elsewhere. -

        - -

        أهداف العرض

        - -

        - لا تقوم الكائنات من النوع [page:WebGLRenderTarget] فقط بتخصيص مثيل لـ [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLTexture WebGLTexture] ولكن أيضًا [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLFramebuffer WebGLFramebuffer]s و [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderbuffer WebGLRenderbuffer]s لتحقيق وجهات العرض المخصصة. لا يتم تخصيص هذه الكائنات إلا بتنفيذ [page:WebGLRenderTarget.dispose](). -

        - -

        متفرقات (Miscellaneous)

        - -

        - هناك فئات أخرى من دليل الأمثلة مثل الضوابط أو ممرات المعالجة اللاحقة التي توفر طرق * dispose () * من أجل إزالة مستمعي الأحداث الداخلية أو تقديم الأهداف. بشكل عام ، يوصى بالتحقق من واجهة برمجة التطبيقات أو وثائق الفصل الدراسي ومراقبة * dispose () *. إذا كان موجودًا ، يجب استخدامه عند تنظيف الأشياء. -

        - -

        الأسئلة الشائعة (FAQ)

        - -

        لماذا لا يمكن لـ * three.js * التخلص من الكائنات تلقائيًا؟

        - -

        - تم طرح هذا السؤال عدة مرات من قبل المجتمع ، لذا من المهم توضيح هذا الأمر. الحقيقة هي أن * three.js * لا تعرف عمر أو نطاق الكيانات التي أنشأها المستخدم مثل الأشكال الهندسية أو المواد. هذه هي مسؤولية التطبيق. على سبيل المثال ، حتى إذا لم يتم استخدام مادة حاليًا للعرض ، فقد تكون ضرورية للإطار التالي. لذلك إذا قرر التطبيق أنه يمكن حذف كائن معين ، فيجب عليه إخبار المحرك عن طريق استدعاء طريقة *dispose()* المعنية. -

        - -

        هل تؤدي إزالة الشبكة من المشهد إلى التخلص من هندستها ومادتها أيضًا؟

        - -

        - لا ، يجب عليك التخلص صراحة من الهندسة والمواد عبر *dispose()*. ضع في اعتبارك أنه يمكن مشاركة الأشكال الهندسية والمواد بين الكائنات ثلاثية الأبعاد مثل الشبكات. -

        - -

        هل توفر * three.js * معلومات حول كمية العناصر المخزنة مؤقتًا؟

        - -

        - نعم. من الممكن تقييم [page:WebGLRenderer.info] ، وهي خاصية خاصة للعارض مع سلسلة من المعلومات الإحصائية حول ذاكرة لوحة الرسومات وعملية العرض. من بين أشياء أخرى ، تخبرك أن لديك العديد من القوام والهندسة وبرامج التظليل مخزنة داخليًا. إذا لاحظت وجود مشاكل في الأداء في التطبيق الخاص بك ، فمن الأفضل تصحيح هذه الخاصية من أجل التعرف بسهولة على تسرب الذاكرة. -

        - -

        ماذا يحدث عندما تستدعي * dispose () * على نسيج لكن الصورة لم يتم تحميلها بعد؟

        - -

        - يتم تخصيص الموارد الداخلية للنسيج فقط إذا تم تحميل الصورة بالكامل. إذا تخلصت من نسيج قبل تحميل الصورة ، فلن يحدث شيء. لم يتم تخصيص أي موارد لذلك ليست هناك حاجة للتنظيف. -

        - -

        ماذا يحدث عندما تستدعي * dispose () * ثم أستخدم النموذج المعني في وقت لاحق؟

        - -

        - سيتم إنشاء الموارد الداخلية المحذوفة مرة أخرى بواسطة المحرك. لذلك لن يحدث أي خطأ في وقت التشغيل ولكن قد تلاحظ تأثيرًا سلبيًا على الأداء للإطار الحالي ، خاصةً عندما يتعين تجميع برامج shader. -

        - -

        كيف يمكنني إدارة كائنات * three.js * في تطبيقي؟ متى أعرف كيف أتخلص من الأشياء؟

        - -

        - بشكل عام ، لا توجد توصية محددة لهذا الغرض. يعتمد الأمر بشكل كبير على حالة الاستخدام المحددة عندما يكون الاتصال بالرقم *dispose()* مناسبًا. من المهم إبراز أنه ليس من الضروري دائمًا التخلص من الأشياء طوال الوقت. وخير مثال على ذلك هو اللعبة التي تتكون من مستويات متعددة. مكان جيد للتخلص من الأشياء عند تبديل المستوى. يمكن للتطبيق اجتياز المشهد القديم والتخلص من جميع المواد والأشكال الهندسية والقوام المتقادمة. كما هو مذكور في القسم السابق ، لا ينتج عنه خطأ في وقت التشغيل إذا تخلصت من كائن لا يزال قيد الاستخدام بالفعل. أسوأ شيء يمكن أن يحدث هو انخفاض الأداء لإطار واحد. -

        - -

        أمثلة توضح استخدام dispose()

        - -

        - [example:webgl_test_memory WebGL / test / memory]
        - [example:webgl_test_memory2 WebGL / test / memory2]
        -

        - - - - diff --git a/docs/manual/ar/introduction/How-to-update-things.html b/docs/manual/ar/introduction/How-to-update-things.html deleted file mode 100644 index 7eacdcf408cd41..00000000000000 --- a/docs/manual/ar/introduction/How-to-update-things.html +++ /dev/null @@ -1,210 +0,0 @@ - - - - - - - - - -

        كيفية تحديث الأشياء

        -
        -

        تقوم كل الكائنات بشكل ألي بتحديث حالتها تلقائيًا إذا تمت إضافتها إلى المشهد باستخدام

        - -const object = new THREE.Object3D(); -scene.add( object ); - -

        أو إذا كانوا أبناء كائن آخر تمت إضافته إلى المشهد:

        - -const object1 = new THREE.Object3D(); -const object2 = new THREE.Object3D(); - -object1.add( object2 ); -scene.add( object1 ); //object1 and object2 will automatically update their matrices - -
        - -

        ومع ذلك ، إذا كنت تعلم أن الكائن سيكون ثابتًا ، فيمكنك تعطيل هذا وتحديث وضيفة التحديث يدويًا عند الحاجة فقط.

        - - -object.matrixAutoUpdate = false; -object.updateMatrix(); - - -

        BufferGeometry

        -
        -

        - يخزن BufferGeometries المعلومات (مثل مواضع الرأس ومؤشرات الوجه والعلامات والألوان وUVs وأي سمات مخصصة) في [page:BufferAttribute buffers] - أي ، - [link:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays typed arrays]. - هذا يجعلها بشكل عام أسرع من الهندسة الأساسية (standard geometry) ، بحساب تكلفة العمل بها إلى حد ما. -

        -

        - فيما يتعلق بتحديث BufferGeometries ، فإن أهم شيء يجب فهمه هو أنه لا يمكنك تغيير حجم المخازن المؤقتة (هذا مكلف للغاية ، ويعادل بشكل أساسي إنشاء هندسة جديدة). ومع ذلك يمكنك تحديث محتوى المخازن المؤقتة. -

        -

        - هذا يعني أنك إذا كنت تعرف أن إحدى سمات BufferGeometry ستنمو ، لنقل عدد الرؤوس ، فيجب عليك تخصيص مخزن مؤقت كبير بما يكفي لاحتواء أي رؤوس جديدة قد يتم إنشاؤها. بالطبع ، هذا يعني أيضًا أنه سيكون هناك حد أقصى لحجم BufferGeometry - لا توجد طريقة لإنشاء BufferGeometry يمكن تمديده حجم غير محدود. -

        -

        - سنستخدم مثال السطر الذي سيتم تمديده في وقت العرض. سنخصص مساحة في المخزن المؤقت لـ 500 رأس لكننا نرسم اثنين فقط في البداية ، باستخدام [page:BufferGeometry.drawRange]. -

        - -const MAX_POINTS = 500; - -// geometry -const geometry = new THREE.BufferGeometry(); - -// attributes -const positions = new Float32Array( MAX_POINTS * 3 ); // 3 vertices per point -geometry.setAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) ); - -// draw range -const drawCount = 2; // draw the first 2 points, only -geometry.setDrawRange( 0, drawCount ); - -// material -const material = new THREE.LineBasicMaterial( { color: 0xff0000 } ); - -// line -const line = new THREE.Line( geometry, material ); -scene.add( line ); - -

        - بعد ذلك سنضيف نقاطًا بشكل عشوائي إلى الخط باستخدام نمط مثل: -

        - -const positionAttribute = line.geometry.getAttribute( 'position' ); - -let x = 0, y = 0, z = 0; - -for ( let i = 0; i < positionAttribute.count; i ++ ) { - - positionAttribute.setXYZ( i, x, y, z ); - - x += ( Math.random() - 0.5 ) * 30; - y += ( Math.random() - 0.5 ) * 30; - z += ( Math.random() - 0.5 ) * 30; - -} - -

        - إذا كنت تريد تغيير عدد النقاط التي تم إظهارها بعد العرض الأول ، فقم بما يلي: -

        - -line.geometry.setDrawRange( 0, newValue ); - -

        - إذا كنت تريد تغيير قيم بيانات الموضع بعد العرض الأول ، فأنت بحاجة إلى تعيين علامة needsUpdate على النحو التالي: -

        - -positionAttribute.needsUpdate = true; // required after the first render - - -

        - إذا قمت بتغيير قيم بيانات الموقع بعد التصيير الأولي ، فقد تحتاج إلى استدعاء `` .computeBoundingSphere () 'لإعادة حساب المجال المحيط للهندسة. -

        - -line.geometry.computeBoundingSphere(); - - -

        - هنا مثال يعرض خطًا متحركًا يمكن تكييفه مع حالة الاستخدام الخاصة بك. - [link:https://jsfiddle.net/t4m85pLr/1/ Here is a fiddle] -

        - -

        أمثلة

        - -

        - [example:webgl_custom_attributes WebGL / custom / attributes]
        - [example:webgl_buffergeometry_custom_attributes_particles WebGL / buffergeometry / custom / attributes / particles] -

        - -
        - -

        المواد (Materials)

        -
        -

        يمكن تغيير جميع قيم الزي الرسمي بحرية (على سبيل المثال ، الألوان ، والأنسجة ، والعتامة ، وما إلى ذلك) ، ويتم إرسال القيم إلى الشادر (shader) في كل إطار.

        - -

        يمكن أيضًا تغيير المعلمات ذات الصلة بـ GLstate في أي وقت (depthTest, blending, polygonOffset, etc).

        - -

        لا يمكن تغيير الخصائص التالية بسهولة في وقت التشغيل (بمجرد تقديم المادة مرة واحدة على الأقل):

        -
          -
        • numbers and types of uniforms
        • -
        • presence or not of -
            -
          • texture
          • -
          • fog
          • -
          • vertex colors
          • -
          • morphing
          • -
          • shadow map
          • -
          • alpha test
          • -
          • transparent
          • -
          -
        • -
        - -

        تتطلب التغييرات في هذه بناء برنامج شادر (shader) جديد. سوف تحتاج إلى ضبط

        - material.needsUpdate = true - -

        ضع في اعتبارك أن هذا قد يكون بطيئًا للغاية ويؤدي إلى اهتزاز في معدل الإطارات (خاصة على Windows ، حيث أن التحويل البرمجي للشادر (shader) يكون أبطأ في DirectX منه في OpenGL).

        - -

        للحصول على تجربة أكثر سلاسة ، يمكنك محاكاة التغييرات في هذه الميزات إلى حد ما من خلال الحصول على قيم "وهمية" مثل الأضواء صفر الكثافة أو الزخارف البيضاء أو الضباب الصفري.

        - -

        يمكنك تغيير المواد المستخدمة في القطع الهندسية بحرية ، ولكن لا يمكنك تغيير كيفية تقسيم الكائن إلى أجزاء (وفقًا لمواد الوجه).

        - -

        إذا كنت بحاجة إلى تكوينات مختلفة من المواد أثناء وقت التشغيل:

        -

        إذا كان عدد المواد / القطع الصغيرًة ، فيمكنك تقسيم الجسم مسبقًا (مثل الشعر / الوجه / الجسم / الملابس العلوية / السراويل للإنسان ، أمامي / جوانب / الجزء العلوي / الزجاج / الإطار / الجزء الداخلي للسيارة).

        - -

        إذا كان الرقم كبيرًا (على سبيل المثال ، من المحتمل أن يكون كل وجه مختلفًا) ، ففكر في حل مختلف ، مثل استخدام السمات / القوام للحصول على مظهر مختلف لكل وجه.

        - -

        أمثلة

        -

        - [example:webgl_materials_car WebGL / materials / car]
        - [example:webgl_postprocessing_dof WebGL / webgl_postprocessing / dof] -

        -
        - - -

        النسيج (Textures)

        -
        -

        يجب أن يتم تعيين العلامات التالية في الصورة ، canvas والفيديو والبيانات إذا تم تغييرها:

        - - texture.needsUpdate = true; - -

        العرض يستهدف التحديث تلقائيا.

        - -

        أمثلة

        -

        - [example:webgl_materials_video WebGL / materials / video]
        - [example:webgl_rtt WebGL / rtt] -

        - -
        - - -

        الكاميرات (Cameras)

        -
        -

        يتم تحديث موضع الكاميرا وهدفها تلقائيًا. إذا كنت بحاجة إلى التغيير

        -
          -
        • - fov -
        • -
        • - aspect -
        • -
        • - near -
        • -
        • - far -
        • -
        -

        - ثم ستحتاج إلى إعادة حساب مصفوفة الإسقاط(the projection matrix): -

        - -camera.aspect = window.innerWidth / window.innerHeight; -camera.updateProjectionMatrix(); - -
        - - diff --git a/docs/manual/ar/introduction/How-to-use-post-processing.html b/docs/manual/ar/introduction/How-to-use-post-processing.html deleted file mode 100644 index 7754c113567a9a..00000000000000 --- a/docs/manual/ar/introduction/How-to-use-post-processing.html +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - - - -

        كيفية استخدام المعالجة اللاحقة
        (post-processing)

        - -

        - تعرض العديد من تطبيقات three.js كائناتها ثلاثية الأبعاد مباشرة على الشاشة. ومع ذلك ، في بعض الأحيان ، تريد تطبيق واحد أو أكثر من التأثيرات الرسومية مثل Depth-Of-Field أو Bloom أو Film Grain أو أنواع مختلفة من Anti-aliasing. المعالجة اللاحقة هي طريقة مستخدمة على نطاق واسع لتنفيذ مثل هذه التأثيرات. أولاً ، يتم تحويل المشهد إلى هدف عرض يمثل مخزنًا مؤقتًا في ذاكرة بطاقة الفيديو. - في الخطوة التالية ، تقوم واحدة أو أكثر من ممرات ما بعد المعالجة بتطبيق المرشحات والتأثيرات على المخزن المؤقت للصور قبل أن يتم عرضه في النهاية على الشاشة. -

        -

        - توفر three.js حلاً كاملاً لعملية المعالجة بتوفير [page:EffectComposer] الذي يتولى تنفيذ مثل هذه الأعمال. -

        - -

        سير العمل

        - -

        - الخطوة الأولى في العملية هي استيراد جميع الملفات الضرورية من دليل الأمثلة. يفترض الدليل أنك تستخدم الرقم الرسمي [link:https://www.npmjs.com/package/three npm package] من three.js. في العرض التوضيحي الأساسي في هذا الدليل ، نحتاج إلى الملفات التالية. -

        - - - import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'; - import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'; - import { GlitchPass } from 'three/addons/postprocessing/GlitchPass.js'; - - -

        - بعد أن يتم استرداد جميع الملفات بنجاح ، يمكنك إنشاء الملحن الخاص بنا بتمرير نموذج من [page:WebGLRenderer]. -

        - - - const composer = new EffectComposer( renderer ); - - -

        - عند استخدام الملحن ، من الضروري تغيير حلقة الرسوم المتحركة للتطبيق. بدلاً من استدعاء طريقة العرض [page:WebGLRenderer] ، نستخدم الآن النظير الخاص بها [page:EffectComposer]. -

        - - - function animate() { - - requestAnimationFrame( animate ); - - composer.render(); - - } - - -

        - أصبح الملحن جاهزًا الآن ، لذا من الممكن تكوين سلسلة ممرات ما بعد المعالجة. هذه التمريرات مسؤولة عن إنشاء الإخراج المرئي النهائي للتطبيق. تتم معالجتها بترتيب الإضافة / الإدراج. في مثالنا ، تم تنفيذ *RenderPass* أولاً ثم *GlitchPass*. يتم عرض آخر تمرير تم تمكينه في السلسلة تلقائيًا على الشاشة. يبدو إعداد التصاريح كما يلي: -

        - - - const renderPass = new RenderPass( scene, camera ); - composer.addPass( renderPass ); - - const glitchPass = new GlitchPass(); - composer.addPass( glitchPass ); - - -

        - يتم وضع *RenderPass* بشكل طبيعي في بداية السلسلة من أجل توفير المشهد الذي تم عرضه كمدخل لخطوة ما بعد المعالجة التالية. في حالتنا ، ستستخدم *GlitchPass* بيانات الصورة هذه لتطبيق تأثير خلل جامح. تحقق من هذا [link:https://threejs.org/examples/webgl_postprocessing_glitch live example] لتراها في العمل. -

        - -

        تصاريح مدمجة

        - -

        - يمكنك استخدام مجموعة واسعة من تصاريح ما بعد المعالجة التي يوفرها المحرك. يتم الاحتفاظ بها في الدليل [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm/postprocessing postprocessing]. -

        - -

        تصاريح مخصصة

        - -

        - في بعض الأحيان تريد كتابة تظليل مخصص للمعالجة اللاحقة وإدراجه في سلسلة ممرات ما بعد المعالجة. في هذا السيناريو ، يمكنك استخدام *ShaderPass*. بعد استيراد الملف والتظليل المخصص الخاص بك ، يمكنك استخدام الكود التالي لإعداد المرور. -

        - - - import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js'; - import { LuminosityShader } from 'three/addons/shaders/LuminosityShader.js'; - - // later in your init routine - - const luminosityPass = new ShaderPass( LuminosityShader ); - composer.addPass( luminosityPass ); - - -

        - يوفر المستودع ملفًا يسمى [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/shaders/CopyShader.js CopyShader] وهو رمز بداية جيد للتظليل المخصص الخاص بك. *CopyShader* فقط ينسخ محتويات الصورة من مخزن قراءة [page:EffectComposer] إلى مخزن الكتابة المؤقت الخاص به دون تطبيق أي تأثيرات. -

        - - - diff --git a/docs/manual/ar/introduction/Installation.html b/docs/manual/ar/introduction/Installation.html deleted file mode 100644 index af4536afb21606..00000000000000 --- a/docs/manual/ar/introduction/Installation.html +++ /dev/null @@ -1,187 +0,0 @@ - - - - - - - - - -

        التثبيت ([name])

        - -

        - يمكنك تنصيب three.js بإستعمال [link:https://www.npmjs.com/ npm] و وسائل البناء الحديثة، أو قم بالبدأ فقط - بإستعمال إصدار ثابت متوفر عبر خدمة CDN. - بالنسبة لأغلب المستخدمين تنصيب المكتبة بإستعمال npm هو الخيار الأفضل. -

        - -

        - بغض النظر عن إختيارك، كن حريصا على أن تقوم بإستدعاء جميع الملفات التي تنتمي لنفس الإصدار. خلط ملفات من إصدارات - مختلفة قد ينتج منه بعض السلوكات الغير متوقعة. -

        - -

        - جميع طرق تنصيب three.js بناءا على ES modules (شاهد - [link:https://eloquentjavascript.net/10_modules.html#h_hF2FmOVxw7 Eloquent JavaScript: ECMAScript Modules])، - التي تمكنك من إستدعاء أجزاء معينة من المكتبة دون الحاجة لإستدعاء المصدر كله. -

        - -

        التثبيت بإستعمال npm

        - -

        - لتنصيب وحدة (npm module) [link:https://www.npmjs.com/package/three three]، قم بفتح نافذة الأوامر في المجلد الخاص - بمشروعك و شغل الأمر التالي: -

        - - - npm install three - - -

        - سيتم تحميل الرزمة و تثبيتها. و من ثم تصبح مستعد لإستدعائها كالأتي: -

        - - - // Option 1: Import the entire three.js core library. - import * as THREE from 'three'; - - const scene = new THREE.Scene(); - - - // Option 2: Import just the parts you need. - import { Scene } from 'three'; - - const scene = new Scene(); - - -

        - خلال التنصيب بإستعمال npm، لقد قمت بشكل غير مباشر بإستعمال - [link:https://eloquentjavascript.net/10_modules.html#h_zWTXAU93DC bundling tool] لجمع كل الرزم التي يحتاجها - مشروعك في ملف JavaScript واحد. - بينما يمكن لأي جامع رزم حديث العمل مع three.js، يبقى الخيار الأكثر شيوعا هو - [link:https://webpack.js.org/ webpack]. -

        - -

        - لا يمكن الوصول لكل المزايا مباشرة من خلال رزمة three (بإستعمال bare import). بقية الأجزاء المستعملة - كثيرا من المكتبة مثل - الضوابط (controls)، - المحمل (loaders)، و آثار ما بعد المعالجة (post-processing effects)، يجب إستدعائهم من مجلد - [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm examples/jsm]. لمعرفة المزيد، قم - بزيارة قسم - الأمثلة - بالأسفل. -

        - -

        - لمعرفة المزيد عن وحدات npm. - قم بزيارة [link:https://eloquentjavascript.net/20_node.html#h_J6hW/SmL/a Eloquent JavaScript: Installing with npm]. -

        - -

        التثبيت من CDN أو استضافة ثابتة

        - -

        - يمكن إستعمال مكتبة three.js دون الحاجة إلى نظام بناء، سواءا عبر تحميل الملفات إلى خادم الويب الخاص بك أو باستخدام CDN موجود. بسبب أن المكتبة تعتمد على وحدات ES (modules) يجب على أي شفرة برمجية تشير إليها أن تستخدام type="module" كما هو موضح أسفله: -

        - - - <script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script> - - <script type="importmap"> - { - "imports": { - "three": "https://unpkg.com/three@<version>/build/three.module.js" - } - } - </script> - - <script type="module"> - - import * as THREE from 'three'; - - const scene = new THREE.Scene(); - - </script> - - -

        أمثلة

        - -

        - يركز جوهر three.js على أهم المكونات المكونة لمحرك ثلاثي الأبعاد. العديد من المكونات المفيدة الأخرى - مثل الضوابط ، عناصر التحميل ، وتأثيرات ما بعد المعالجة - هي جزء من المجلد [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm examples/jsm]. .يشار إليها باسم "أمثلة" ، لأنه بينما يمكنك استخدامها ببساطة، من المفترض أيضًا إعادة دمجها وتخصيصها. هذه المكونات تضل متزامنة مع المكتبة الأساسية - بينما يتم الاحتفاظ بحزم الجهات الخارجية المماثلة على npm بواسطة أشخاص مختلفين وقد لا تكون محدثة. -

        - -

        - لا يلزم تثبيت الأمثلة بشكل منفصل ، ولكن يجب استيرادها بشكل منفصل. إذا تم تثبيت three.js بواسطة npm ، يمكنك إستدعاء مكون OrbitControls كالأتي: -

        - - - import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; - - const controls = new OrbitControls( camera, renderer.domElement ); - - -

        - إذا تم تثبيت three.js من CDN ، فمن المستحسن إستخدم نفس CDN لتثبيت المكونات الأخرى: -

        - - - <script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script> - - <script type="importmap"> - { - "imports": { - "three": "https://unpkg.com/three@<version>/build/three.module.js", - "three/addons/": "https://unpkg.com/three@<version>/examples/jsm/" - } - } - </script> - - <script type="module"> - - import * as THREE from 'three'; - import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; - - const controls = new OrbitControls( camera, renderer.domElement ); - - </script> - - -

        - من المهم أن تستخدم جميع الملفات نفس الإصدار. لا تستورد أمثلة مختلفة من إصدارات مختلفة، أو تستخدم أمثلة من إصدارات مختلفة عن مكتبة three.js نفسها. -

        - -

        التوافق

        - -

        واردات CommonJS

        - -

        - في حين أن معظم حِزم JavaScript الحديثة تدعم الآن وحدات ES افتراضيًا ، فقد لا تدعم بعض أدوات البناء القديمة. - في مثل هذه الحالات بإستطاعتك تعديل المجمع لفهم وحدات ES:
        - [link:http://browserify.org/ Browserify] يحتاج فقط إلى إضافة [link:https://github.com/babel/babelify babelify] على سبيل المثال. -

        - -

        خرائط الإستيراد

        - -

        - تختلف المسارات المستوردة عند التثبيت من npm ، مقارنة بالتثبيت من استضافة ثابتة أو CDN. نحن ندرك أن هذه مشكلة مريحة لكلا مجموعتي المستخدمين. - يفضل المطورون الذين لديهم أدوات إنشاء وحزم محددات الحزم المجردة (على سبيل المثال "three") بدلاً من المسارات النسبية ، وتستخدم الملفات في المجلد examples/ مراجع نسبية إلى three.module.js التي لا تتبع هذا التوقع. - أولئك الذين لا يستخدمون أدوات الإنشاء - للنماذج الأولية السريعة أو التعلم أو التفضيل الشخصي - قد يكرهون بالمثل تلك الواردات النسبية ، والتي تتطلب هياكل مجلدات معينة وتكون أقل تسامحًا من مساحة الاسم THREE. * العام. -

        - -

        - نأمل في إزالة هذه المسارات النسبية عندما يصبح [link:https://github.com/WICG/import-maps import maps] متاحًا على نطاق واسع ، واستبدالها بمحددات الحزمة المجردة إلى اسم الحزمة npm ، 'three'. يتطابق هذا مع توقعات أداة الإنشاء لحزم npm بشكل وثيق ، ويسمح لمجموعتي المستخدمين بكتابة نفس الرمز تمامًا عند استيراد ملف. بالنسبة للمستخدمين الذين يفضلون تجنب أدوات الإنشاء ، يمكن أن يوجه تعيين JSON البسيط جميع الواردات إلى CDN أو مجلد ملف ثابت. من الناحية التجريبية ، يمكنك محاولة استخدام عمليات استيراد أبسط اليوم مع تعبئة خريطة الاستيراد ، كما هو موضح في [link:https://glitch.com/edit/#!/three-import-map?path=index.html import map example]. -

        - -

        Node.js

        - -

        - Because three.js is built for the web, it depends on browser and DOM APIs that don't always exist in Node.js. Some of these issues can be resolved by using shims like [link:https://github.com/stackgl/headless-gl headless-gl], or by replacing components like [page:TextureLoader] with custom alternatives. Other DOM APIs may be deeply intertwined with the code that uses them, and will be harder to work around. We welcome simple and maintainable pull requests to improve Node.js support, but recommend opening an issue to discuss your improvements first. -

        - -

        - Make sure to add `{ "type": "module" }` to your `package.json` to enable ES6 modules in your node project. -

        - - - diff --git a/docs/manual/ar/introduction/Loading-3D-models.html b/docs/manual/ar/introduction/Loading-3D-models.html deleted file mode 100644 index f5a385b9276ecc..00000000000000 --- a/docs/manual/ar/introduction/Loading-3D-models.html +++ /dev/null @@ -1,134 +0,0 @@ - - - - - - - - - - - -

        تحميل نماذج ثلاثية الأبعاد

        - -

        - تتوفر النماذج ثلاثية الأبعاد في مئات من تنسيقات الملفات ، ولكل منها أغراض مختلفة وميزات متنوعة وتعقيد متفاوت. برغم من أن - - مكتبة three.js توفر العديد من عناصر التحميل، فإن اختيار التنسيق المناسب وسير العمل سيوفر الوقت والإحباط لاحقًا. يصعب العمل مع بعض التنسيقات ، أو أنها غير فعالة في تجارب الوقت الفعلي ، أو ببساطة غير مدعومة بالكامل في هذا الوقت. -

        - -

        - يوفر هذا الدليل سير عمل موصى به لمعظم المستخدمين ، واقتراحات لما يجب تجربته إذا لم تسير الأمور كما هو متوقع. -

        - -

        قبل أن نبدأ

        - -

        - إذا كنت جديدًا في تشغيل خادم محلي ، فابدأ بكيفية إدارة الأشياء محليًا [link:#manual/introduction/Installation installation] أولاً. يمكن تجنب العديد من الأخطاء الشائعة أثناء عرض النماذج ثلاثية الأبعاد عن طريق استضافة الملفات بشكل صحيح. -

        - -

        سير العمل الموصى به

        - -

        - حيثما أمكن ، نوصي باستخدام glTF (تنسيق نقل GL). كلا النسختين .GLB و .GLTF من التنسيق مدعومة بشكل جيد. نظرًا لأن glTF يركز على تسليم أصول وقت التشغيل ، فهو مضغوط للإرسال وسريع التحميل. تتضمن الميزات الشبكات والمواد والأنسجة والجلود والهياكل العظمية والأهداف المتحولة والرسوم المتحركة والأضواء والكاميرات. -

        - -

        - تتوفر ملفات glTF عامة على مواقع مثل - - Sketchfab، أو أدوات متنوعة تتضمن تصدير glTF: -

        - - - -

        - إذا كانت أدواتك المفضلة لا تدعم glTF ، ففكر في طلب تصدير glTF من المؤلفين ، أو النشر على - the glTF roadmap thread. -

        - -

        - عندما لا يكون glTF خيارًا ، تتوفر أيضًا التنسيقات الشائعة مثل FBX أو OBJ أو COLLADA ويتم صيانتها بانتظام. -

        - -

        التحميل

        - -

        - يتم تضمين عدد قليل فقط من عناصر التحميل (على سبيل المثال [page:ObjectLoader]) بشكل ألي مع - three.js - يجب عليك إضافة الآخرين إلى تطبيقك بشكل فردي. -

        - - - import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; - - -

        - بمجرد قيامك باستيراد عنصر التحميل ، فأنت جاهز لإضافة نموذج إلى المشهد الخاص بك. يختلف بناء الجملة باختلاف عناصر التحميل - عند استخدام تنسيق آخر ، تحقق من الأمثلة والوثائق الخاصة بهذا المُحمل. بالنسبة إلى glTF ، سيكون الاستخدام مع البرامج النصية العامة: -

        - - - const loader = new GLTFLoader(); - - loader.load( 'path/to/model.glb', function ( gltf ) { - - scene.add( gltf.scene ); - - }, undefined, function ( error ) { - - console.error( error ); - - } ); - - -

        - انظر [page:GLTFLoader GLTFLoader documentation] لمزيد من التفاصيل. -

        - -

        إستكشاف الأخطاء وإصلاحها

        - -

        - إن كنت قد أمضيت ساعات في تصميم تحفة فنية ، وقمت بتحميلها في صفحة الويب ، و- لا! 😭 إنه مشوه أو مفقود تمامًا. ابدأ بالخطوات التالية لاستكشاف الأخطاء وإصلاحها: -

        - -
          -
        1. - تحقق من وحدة تحكم جافا سكريبت بحثًا عن أخطاء ، وتأكد من استخدام رد اتصال onError عند استدعاء .load () لتسجيل النتيجة. -
        2. -
        3. - اعرض النموذج في تطبيق آخر. بالنسبة إلى glTF ، تتوفر عارضات السحب والإفلات لـ - three.js و - babylon.js. إذا ظهر النموذج بشكل صحيح في تطبيق واحد أو أكثر ، - إرفع إعلان وجود خطأ ضد المكتبة. - إذا تعذر عرض النموذج في أي تطبيق ، فإننا نشجع بشدة على تسجيل خطأ في التطبيق المستخدم لإنشاء النموذج. -
        4. -
        5. - حاول تكبير النموذج لأعلى أو لأسفل بعامل 1000. يتم قياس العديد من الأمثلة بشكل مختلف ، وقد لا تظهر الأمثلة الكبيرة إذا كانت الكاميرا داخل النموذج. -
        6. -
        7. - حاول إضافة مصدر ضوء وتحديد موضعه. قد يكون النموذج مخفيًا في الظلام. -
        8. -
        9. - ابحث عن طلبات النسيج الفاشلة في علامة تبويب الشبكة ، مثل C:\\Path\To\Model\texture.jpg. استخدم المسارات المتعلقة بنموذجك بدلاً من ذلك ، مثل images/texture.jpg - قد يتطلب ذلك تعديل ملف النموذج في محرر نصي. -
        10. -
        - -

        طلب المساعدة

        - -

        - إذا مررت بعملية استكشاف الأخطاء وإصلاحها أعلاه ولا يزال نموذجك لا يعمل ، فإن الطريقة الصحيحة لطلب المساعدة ستوصلك إلى حل بشكل أسرع. انشر سؤالاً على منتدى المكتبة - three.js forum وحيثما أمكن ، قم بتضمين النموذج الخاص بك (أو نموذج أبسط بنفس المشكلة) بأي تنسيقات متوفرة لديك. قم بتضمين معلومات كافية لشخص آخر لإعادة إنتاج المشكلة بسرعة - من الناحية المثالية ، عرض توضيحي مباشر. -

        - - - - diff --git a/docs/manual/ar/introduction/Matrix-transformations.html b/docs/manual/ar/introduction/Matrix-transformations.html deleted file mode 100644 index 0d6350521055fd..00000000000000 --- a/docs/manual/ar/introduction/Matrix-transformations.html +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - - -

        تحولات المصفوفة ([name])

        - -

        - يستخدم *Three.js* المصفوفات لتشفير عمليات التحويل ثلاثية الأبعاد --- الترجمات (الموضع) والتدوير والقياس. يحتوي كل مثيل [page:Object3D] على [page:Object3D.matrix matrix] يخزن موضع هذا الكائن وتدويره ومقياسه. تصف هذه الصفحة كيفية تحديث تحول الكائن. -

        - -

        خصائص الراحة و *matrixAutoUpdate*

        - -

        - هناك طريقتان لتحديث تحول الكائن(object): -

        -
          -
        1. - عدّل خصائص *position* و *quaternion* و *scale* الخاصة بالكائن ، واسمح لـ three.js بإعادة حساب مصفوفة الكائن من هذه الخصائص: - -object.position.copy( start_position ); -object.quaternion.copy( quaternion ); - - بشكل افتراضي ، يتم تعيين الخاصية *matrixAutoUpdate* على "true" ، وستتم إعادة حساب المصفوفة تلقائيًا. إذا كان الكائن ثابتًا ، أو كنت ترغب في التحكم يدويًا عند حدوث إعادة الحساب ، فيمكن الحصول على أداء أفضل من خلال تعيين الخاصية false: - -object.matrixAutoUpdate = false; - - وبعد تغيير أي خصائص ، قم بتحديث المصفوفة يدويًا: - -object.updateMatrix(); - -
        2. -
        3. - قم بتعديل مصفوفة الكائن مباشرة. تحتوي فئة [page:Matrix4] على طرق مختلفة لتعديل المصفوفة: - -object.matrix.setRotationFromQuaternion( quaternion ); -object.matrix.setPosition( start_position ); -object.matrixAutoUpdate = false; - - لاحظ أنه يجب ضبط *matrixAutoUpdate* على *false* في هذه الحالة ، ويجب عليك التأكد من عدم استدعاء *updateMatrix*. سيؤدي استدعاء *updateMatrix* إلى إعاقة التغييرات اليدوية التي تم إجراؤها على المصفوفة ، وإعادة حساب المصفوفة من *position* ، *scale* ، وما إلى ذلك. -
        4. -
        - -

        مصفوفات وعالم الكائن

        -

        - يخزن [page:Object3D.matrix matrix] كائن تحويل الكائن بالنسبة إلى [page:Object3D.parent parent] الكائن ؛ للحصول على تحول الكائن في إحداثيات العالم ، يجب عليك الوصول إلى [page:Object3D.matrixWorld] الكائن. -

        -

        - عندما تتغير حالة الكائن الأصل أو الكائن الفرعي ، يمكنك طلب تحديث [page:Object3D.matrixWorld matrixWorld] الكائن الفرعي عن طريق استدعاء [page:Object3D.updateMatrixWorld updateMatrixWorld](). -

        - -

        الدوران و Quaternion

        -

        - يوفر Three.js طريقتين لتمثيل التدوير ثلاثي الأبعاد: [page:Euler Euler angles] و [page:Quaternion Quaternions] ، بالإضافة إلى طرق التحويل بين الاثنين. تخضع زوايا أويلر لمشكلة تسمى "gimballock" ، حيث يمكن أن تفقد بعض التكوينات درجة من الحرية (تمنع تدوير الكائن حول محور واحد). لهذا السبب ، يتم تخزين استدارة الكائن دائمًا في [page:Object3D.quaternion quaternion]. -

        -

        - تضمنت الإصدارات السابقة من المكتبة خاصية *useQuaternion* والتي ، عند ضبطها على false ، ستؤدي إلى حساب [page:Object3D.matrix matrix] للكائن من زاوية أويلر. تم إيقاف هذه الممارسة - بدلاً من ذلك ، يجب عليك استخدام طريقة [page:Object3D.setRotationFromEuler setRotationFromEuler] ، والتي ستعمل على تحديث الرباعية. -

        - - - diff --git a/docs/manual/ar/introduction/Useful-links.html b/docs/manual/ar/introduction/Useful-links.html deleted file mode 100644 index 66bf7f1aa6da18..00000000000000 --- a/docs/manual/ar/introduction/Useful-links.html +++ /dev/null @@ -1,164 +0,0 @@ - - - - - - - - - -

        روابط مفيدة

        - -

        - فيما يلي مجموعة من الروابط التي قد تجدها مفيدة عند تعلم three.js.
        - إذا وجدت شيئًا ما ترغب في إضافته هنا ، أو تعتقد أن أحد الروابط أدناه لم يعد مناسبًا أو يعمل ، فلا تتردد في النقر فوق الزر "تحرير" في الجزء السفلي الأيسر وإجراء بعض التغييرات!

        - - لاحظ أيضًا أنه نظرًا لأن موقع three.js قيد التطوير السريع ، فإن الكثير من هذه الروابط سيحتوي على معلومات قديمة - إذا كان هناك شيء لا يعمل كما تتوقع أو كما يقول أحد هذه الروابط ، تحقق من وحدة تحكم المتصفح للتحذيرات أو الأخطاء. تحقق أيضًا من صفحات المستندات ذات الصلة. -

        - -

        منتديات المساعدة

        -

        - يستخدم Three.js رسميًا [link:https://discourse.threejs.org/ forum] و [link:http://stackoverflow.com/tags/three.js/info Stack Overflow] لطلبات المساعدة. إذا كنت بحاجة إلى مساعدة في شيء ما ، فهذا هو المكان المناسب لك. لا تفتح مشكلة على Github لطلبات المساعدة. -

        - -

        الدروس والدورات

        - -

        الشروع في العمل مع three.js

        -
          -
        • - [link:https://threejs.org/manual/#en/fundamentals Three.js Fundamentals starting lesson] -
        • -
        • - [link:https://codepen.io/rachsmith/post/beginning-with-3d-webgl-pt-1-the-scene Beginning with 3D WebGL] بواسطة [link:https://codepen.io/rachsmith/ Rachel Smith]. -
        • -
        • - [link:https://www.august.com.au/blog/animating-scenes-with-webgl-three-js/ Animating scenes with WebGL and three.js] -
        • -
        - -

        مقالات ودورات أكثر شمولاً / متقدمة

        -
          -
        • - [link:https://discoverthreejs.com/ Discover three.js] -
        • -
        • - [link:http://blog.cjgammon.com/ Collection of tutorials] by [link:http://www.cjgammon.com/ CJ Gammon]. -
        • -
        • - [link:https://medium.com/soffritti.pierfrancesco/glossy-spheres-in-three-js-bfd2785d4857 Glossy spheres in three.js]. -
        • -
        • - [link:https://www.udacity.com/course/cs291 Interactive 3D Graphics] - دورة مجانية على Udacity تُعلِّم أساسيات الرسومات ثلاثية الأبعاد ، وتستخدم three.js كأداة تشفير لها. -
        • -
        • - [Link:https://aerotwist.com/tutorials/ Aerotwist] tutorials by [link:https://github.com/paullewis/ Paul Lewis]. -
        • -
        • - [link:https://discourse.threejs.org/t/three-js-bookshelf/2468 Three.js Bookshelf] - هل تبحث عن مزيد من الموارد حول three.js أو رسومات الكمبيوتر بشكل عام؟ تحقق من اختيار الأدبيات التي أوصى بها مجتمع المكتبة. -
        • -
        - -

        الأخبار والتحديثات

        -
          -
        • - [link:https://twitter.com/hashtag/threejs Three.js on Twitter] -
        • -
        • - [link:http://www.reddit.com/r/threejs/ Three.js on reddit] -
        • -
        • - [link:http://www.reddit.com/r/webgl/ WebGL on reddit] -
        • -
        - -

        أمثلة

        -
          -
        • - [link:https://github.com/edwinwebb/three-seed/ three-seed] - three.js starter مع ES6 و Webpack -
        • -
        • - [link:http://stemkoski.github.io/Three.js/index.html Professor Stemkoskis Examples] - مجموعة من الأمثلة الصديقة للمبتدئين التي تم إنشاؤها باستخدام three.js r60. -
        • -
        • - [link:https://threejs.org/examples/ Official three.js examples] - يتم الاحتفاظ بهذه الأمثلة كجزء من مستودع three.js ، ودائمًا ما تستخدم أحدث إصدار من three.js. -
        • -
        • - [link:https://raw.githack.com/mrdoob/three.js/dev/examples/ Official three.js dev branch examples] - - كما هو مذكور أعلاه ، باستثناء هذه تستخدم فرع dev من three.js ، وتُستخدم للتحقق من أن كل شيء يعمل كما تم تطوير three.js. -
        • -
        - -

        أدوات

        -
          -
        • - [link:https://github.com/tbensky/physgl physgl.org] - واجهة JavaScript الأمامية مع أغلفة لـ three.js ، لجلب رسومات WebGL للطلاب الذين يتعلمون الفيزياء والرياضيات. -
        • -
        • - [link:https://whsjs.readme.io/ Whitestorm.js] – إطار عمل modular three.js مع البرنامج المساعد AmmoNext للفيزياء. -
        • - -
        • - [link:http://zz85.github.io/zz85-bookmarklets/threelabs.html Three.js Inspector] -
        • -
        • - [link:http://idflood.github.io/ThreeNodes.js/ ThreeNodes.js]. -
        • -
        • - [link:https://marketplace.visualstudio.com/items?itemName=slevesque.shader vscode shader] - Syntax highlighter for shader language. -
          - [link:https://marketplace.visualstudio.com/items?itemName=bierner.comment-tagged-templates vscode comment-tagged-templates] - Syntax highlighting for tagged template strings using comments to shader language, like: glsl.js. -
        • -
        • - [link:https://github.com/MozillaReality/WebXR-emulator-extension WebXR-emulator-extension] -
        • -
        - -

        مراجع WebGL

        -
          -
        • - [link:https://www.khronos.org/files/webgl/webgl-reference-card-1_0.pdf webgl-reference-card.pdf] - مراجع لجميع الكلمات الرئيسية والمصطلحات والنحو والتعريفات في WebGL و GLSL. -
        • -
        - -

        الروابط القديمة

        -

        - يتم الاحتفاظ بهذه الروابط لأغراض تاريخية - قد لا تزال تجدها مفيدة ، ولكن حذر من أنها قد تحتوي على معلومات تتعلق بالإصدارات القديمة جدًا من three.js. -

        - -
          -
        • - [link:https://www.youtube.com/watch?v=Dir4KO9RdhM AlterQualia at WebGL Camp 3] -
        • -
        • - [link:http://yomotsu.github.io/threejs-examples/ Yomotsus Examples] - مجموعة من الأمثلة باستخدام three.js r45. -
        • -
        • - [link:http://fhtr.org/BasicsOfThreeJS/#1 Introduction to Three.js] بواسطة [link:http://github.com/kig/ Ilmari Heikkinen] (slideshow). -
        • -
        • - [link:http://www.slideshare.net/yomotsu/webgl-and-threejs WebGL and Three.js] بواسطة [link:http://github.com/yomotsu Akihiro Oyamada] (slideshow). -
        • -
        • - [link:https://www.youtube.com/watch?v=VdQnOaolrPA Trigger Rally] بواسطة [link:https://github.com/jareiko jareiko] (video). -
        • -
        • - [link:http://blackjk3.github.io/threefab/ ThreeFab] - محرر مشاهد ، تم دعم إصداراته حتى حوالي three.js r50. -
        • -
        • - [link:http://bkcore.com/blog/3d/webgl-three-js-workflow-tips.html Max to Three.js workflow tips and tricks] بواسطة [link:https://github.com/BKcore BKcore] -
        • -
        • - [link:http://12devsofxmas.co.uk/2012/01/webgl-and-three-js/ A whirlwind look at Three.js] - بواسطة [link:http://github.com/nrocy Paul King] -
        • -
        • - [link:http://bkcore.com/blog/3d/webgl-three-js-animated-selective-glow.html Animated selective glow in Three.js] - بواسطة [link:https://github.com/BKcore BKcore] -
        • -
        • - [link:http://www.natural-science.or.jp/article/20120220155529.php Building A Physics Simulation Environment] - أمثلة تعليمية باللغة اليابانية -
        • -
        - - - diff --git a/docs/manual/ar/introduction/WebGL-compatibility-check.html b/docs/manual/ar/introduction/WebGL-compatibility-check.html deleted file mode 100644 index a0100406ffb367..00000000000000 --- a/docs/manual/ar/introduction/WebGL-compatibility-check.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - -

        فحص توافق WebGL

        -

        - على الرغم من أن هذه المشكلة أصبحت أقل وأقل ، إلا أن بعض الأجهزة أو المتصفحات قد لا تدعم WebGL. - تتيح لك الطريقة التالية التحقق مما إذا كانت مدعومة وعرض رسالة للمستخدم إذا لم تكن مدعومة. - قم باستيراد وحدة الكشف عن دعم WebGL ، وقم بتشغيل ما يلي قبل محاولة عرض أي شيء. -

        - - - import WebGL from 'three/addons/capabilities/WebGL.js'; - - if ( WebGL.isWebGLAvailable() ) { - - // Initiate function or other initializations here - animate(); - - } else { - - const warning = WebGL.getWebGLErrorMessage(); - document.getElementById( 'container' ).appendChild( warning ); - - } - - - diff --git a/docs/manual/en/introduction/Animation-system.html b/docs/manual/en/introduction/Animation-system.html deleted file mode 100644 index 98d19812f0e1ba..00000000000000 --- a/docs/manual/en/introduction/Animation-system.html +++ /dev/null @@ -1,147 +0,0 @@ - - - - - - - - - -

        [name]

        - -

        Overview

        - -

        - Within the three.js animation system you can animate various properties of your models: - the bones of a [page:SkinnedMesh skinned and rigged model], morph targets, different material properties - (colors, opacity, booleans), visibility and transforms. The animated properties can be faded in, - faded out, crossfaded and warped. The weight and time scales of different simultaneous - animations on the same object as well as on different objects can be changed - independently. Various animations on the same and on different objects can be - synchronized.

        - - To achieve all this in one homogeneous system, the three.js animation system - [link:https://github.com/mrdoob/three.js/issues/6881 has completely changed in 2015] - (beware of outdated information!), and it has now an architecture similar to - Unity/Unreal Engine 4. This page gives a short overview of the main components of the - system and how they work together. - -

        - -

        Animation Clips

        - -

        - - If you have successfully imported an animated 3D object (it doesn't matter if it has - bones or morph targets or both) — for example exporting it from Blender with the - [link:https://github.com/KhronosGroup/glTF-Blender-IO glTF Blender exporter] and - loading it into a three.js scene using [page:GLTFLoader] — one of the response fields - should be an array named "animations", containing the [page:AnimationClip AnimationClips] - for this model (see a list of possible loaders below).

        - - Each `AnimationClip` usually holds the data for a certain activity of the object. If the - mesh is a character, for example, there may be one AnimationClip for a walkcycle, a second - for a jump, a third for sidestepping and so on. - -

        - -

        Keyframe Tracks

        - -

        - - Inside of such an `AnimationClip` the data for each animated property are stored in a - separate [page:KeyframeTrack]. Assuming a character object has a [page:Skeleton skeleton], - one keyframe track could store the data for the position changes of the lower arm bone - over time, a different track the data for the rotation changes of the same bone, a third - the track position, rotation or scaling of another bone, and so on. It should be clear, - that an AnimationClip can be composed of lots of such tracks.

        - - Assuming the model has morph targets (for example one morph - target showing a friendly face and another showing an angry face), each track holds the - information as to how the [page:Mesh.morphTargetInfluences influence] of a certain morph - target changes during the performance of the clip. - -

        - -

        Animation Mixer

        - -

        - - The stored data forms only the basis for the animations - actual playback is controlled by - the [page:AnimationMixer]. You can imagine this not only as a player for animations, but - as a simulation of a hardware like a real mixer console, which can control several animations - simultaneously, blending and merging them. - -

        - -

        Animation Actions

        - -

        - - The `AnimationMixer` itself has only very few (general) properties and methods, because it - can be controlled by the [page:AnimationAction AnimationActions]. By configuring an - `AnimationAction` you can determine when a certain `AnimationClip` shall be played, paused - or stopped on one of the mixers, if and how often the clip has to be repeated, whether it - shall be performed with a fade or a time scaling, and some additional things, such crossfading - or synchronizing. - -

        - -

        Animation Object Groups

        - -

        - - If you want a group of objects to receive a shared animation state, you can use an - [page:AnimationObjectGroup]. - -

        - -

        Supported Formats and Loaders

        - -

        - Note that not all model formats include animation (OBJ notably does not), and that only some - three.js loaders support [page:AnimationClip AnimationClip] sequences. Several that do - support this animation type: -

        - -
          -
        • [page:ObjectLoader THREE.ObjectLoader]
        • -
        • THREE.BVHLoader
        • -
        • THREE.ColladaLoader
        • -
        • THREE.FBXLoader
        • -
        • [page:GLTFLoader THREE.GLTFLoader]
        • -
        • THREE.MMDLoader
        • -
        - -

        - Note that 3ds max and Maya currently can't export multiple animations (meaning animations which are not - on the same timeline) directly to a single file. -

        - -

        Example

        - - - let mesh; - - // Create an AnimationMixer, and get the list of AnimationClip instances - const mixer = new THREE.AnimationMixer( mesh ); - const clips = mesh.animations; - - // Update the mixer on each frame - function update () { - mixer.update( deltaSeconds ); - } - - // Play a specific animation - const clip = THREE.AnimationClip.findByName( clips, 'dance' ); - const action = mixer.clipAction( clip ); - action.play(); - - // Play all animations - clips.forEach( function ( clip ) { - mixer.clipAction( clip ).play(); - } ); - - - - diff --git a/docs/manual/en/introduction/Color-management.html b/docs/manual/en/introduction/Color-management.html deleted file mode 100644 index a6ba8095d431a7..00000000000000 --- a/docs/manual/en/introduction/Color-management.html +++ /dev/null @@ -1,333 +0,0 @@ - - - - - - - - - - - - -

        [name]

        - -

        What is a color space?

        - -

        - Every color space is a collection of several design decisions, chosen together to support a - large range of colors while satisfying technical constraints related to precision and display - technologies. When creating a 3D asset, or assembling 3D assets together into a scene, it is - important to know what these properties are, and how the properties of one color space relate - to other color spaces in the scene. -

        - -
        - -
        - sRGB colors and white point (D65) displayed in the reference CIE 1931 chromaticity - diagram. Colored region represents a 2D projection of the sRGB gamut, which is a 3D - volume. Source: Wikipedia -
        -
        - -
          -
        • - Color primaries: Primary colors (e.g. red, green, blue) are not absolutes; they are - selected from the visible spectrum based on constraints of limited precision and - capabilities of available display devices. Colors are expressed as a ratio of the primary colors. -
        • -
        • - White point: Most color spaces are engineered such that an equally weighted sum of - primaries R = G = B will appear to be without color, or "achromatic". The appearance - of achromatic values (like white or grey) depend on human perception, which in turn depends - heavily on the context of the observer. A color space specifies its "white point" to balance - these needs. The white point defined by the sRGB color space is - [link:https://en.wikipedia.org/wiki/Illuminant_D65 D65]. -
        • -
        • - Transfer functions: After choosing the color gamut and a color model, we still need to - define mappings ("transfer functions") of numerical values to/from the color space. Does r = 0.5 - represent 50% less physical illumination than r = 1.0? Or 50% less bright, as perceived - by an average human eye? These are different things, and that difference can be represented as - a mathematical function. Transfer functions may be linear or nonlinear, depending - on the objectives of the color space. sRGB defines nonlinear transfer functions. Those - functions are sometimes approximated as gamma functions, but the term "gamma" is - ambiguous and should be avoided in this context. -
        • -
        - - These three parameters — color primaries, white point, and transfer functions — define a color - space, with each chosen for particular goals. Having defined the parameters, a few additional terms - are helpful: - -
          -
        • - Color model: Syntax for numerically identifying colors within chosen the color gamut — - a coordinate system for colors. In three.js we're mainly concerned with the RGB color - model, having three coordinates r, g, b ∈ [0,1] ("closed domain") or - r, g, b ∈ [0,∞] ("open domain") each representing a fraction of a primary - color. Other color models (HSL, Lab, LCH) are commonly used for artistic control. -
        • -
        • - Color gamut: Once color primaries and a white point have been chosen, these represent - a volume within the visible spectrum (a "gamut"). Colors not within this volume ("out of gamut") - cannot be expressed by closed domain [0,1] RGB values. In the open domain [0,∞], the gamut is - technically infinite. -
        • -
        - -

        - Consider two very common color spaces: [page:SRGBColorSpace] ("sRGB") and - [page:LinearSRGBColorSpace] ("Linear-sRGB"). Both use the same primaries and white point, - and therefore have the same color gamut. Both use the RGB color model. They differ only in - the transfer functions — Linear-sRGB is linear with respect to physical light intensity. - sRGB uses the nonlinear sRGB transfer functions, and more closely resembles the way that - the human eye perceives light and the responsiveness of common display devices. -

        - -

        - That difference is important. Lighting calculations and other rendering operations must - generally occur in a linear color space. However, a linear colors are less efficient to - store in an image or framebuffer, and do not look correct when viewed by a human observer. - As a result, input textures and the final rendered image will generally use the nonlinear - sRGB color space. -

        - -
        -

        - ℹ️ NOTICE: While some modern displays support wider gamuts like Display-P3, - the web platform's graphics APIs largely rely on sRGB. Applications using three.js - today will typically use only the sRGB and Linear-sRGB color spaces. -

        -
        - -

        Roles of color spaces

        - -

        - Linear workflows — required for modern rendering methods — generally involve more than - one color space, each assigned to a particular role. Linear and nonlinear color spaces are - appropriate for different roles, explained below. -

        - -

        Input color space

        - -

        - Colors supplied to three.js — from color pickers, textures, 3D models, and other sources — - each have an associated color space. Those not already in the Linear-sRGB working color - space must be converted, and textures be given the correct texture.colorSpace assignment. - Certain conversions (for hexadecimal and CSS colors in sRGB) can be made automatically if - the THREE.ColorManagement API is enabled before initializing colors: -

        - - -THREE.ColorManagement.enabled = true; - - -
          -
        • - Materials, lights, and shaders: Colors in materials, lights, and shaders store - RGB components in the Linear-sRGB working color space. -
        • -
        • - Vertex colors: [page:BufferAttribute BufferAttributes] store RGB components in the - Linear-sRGB working color space. -
        • -
        • - Color textures: PNG or JPEG [page:Texture Textures] containing color information - (like .map or .emissiveMap) use the closed domain sRGB color space, and must be annotated with - texture.colorSpace = SRGBColorSpace. Formats like OpenEXR (sometimes used for .envMap or - .lightMap) use the Linear-sRGB color space indicated with texture.colorSpace = LinearSRGBColorSpace, - and may contain values in the open domain [0,∞]. -
        • -
        • - Non-color textures: Textures that do not store color information (like .normalMap - or .roughnessMap) do not have an associated color space, and generally use the (default) texture - annotation of texture.colorSpace = NoColorSpace. In rare cases, non-color data - may be represented with other nonlinear encodings for technical reasons. -
        • -
        - -
        -

        - ⚠️ WARNING: Many formats for 3D models do not correctly or consistently - define color space information. While three.js attempts to handle most cases, problems - are common with older file formats. For best results, use glTF 2.0 ([page:GLTFLoader]) - and test 3D models in online viewers early to confirm the asset itself is correct. -

        -
        - -

        Working color space

        - -

        - Rendering, interpolation, and many other operations must be performed in an open domain - linear working color space, in which RGB components are proportional to physical - illumination. In three.js, the working color space is Linear-sRGB. -

        - -

        Output color space

        - -

        - Output to a display device, image, or video may involve conversion from the open domain - Linear-sRGB working color space to another color space. This conversion may be performed in - the main render pass ([page:WebGLRenderer.outputColorSpace]), or during post-processing. -

        - - -renderer.outputColorSpace = THREE.SRGBColorSpace; // optional with post-processing - - -
          -
        • - Display: Colors written to a WebGL canvas for display should be in the sRGB - color space. -
        • -
        • - Image: Colors written to an image should use the color space appropriate for - the format and usage. Fully-rendered images written to PNG or JPEG textures generally - use the sRGB color space. Images containing emission, light maps, or other data not - confined to the [0,1] range will generally use the open domain Linear-sRGB color space, - and a compatible image format like OpenEXR. -
        • -
        - -
        -

        - ⚠️ WARNING: Render targets may use either sRGB or Linear-sRGB. sRGB makes - better use of limited precision. In the closed domain, 8 bits often suffice for sRGB - whereas ≥12 bits (half float) may be required for Linear-sRGB. If later pipeline - stages require Linear-sRGB input, the additional conversions may have a small - performance cost. -

        -
        - -

        - Custom materials based on [page:ShaderMaterial] and [page:RawShaderMaterial] have to implement their own output color space conversion. - For instances of `ShaderMaterial`, adding the `encodings_fragment` shader chunk to the fragment shader's `main()` function should be sufficient. -

        - -

        Working with THREE.Color instances

        - -

        - Methods reading or modifying [page:Color] instances assume data is already in the - three.js working color space, Linear-sRGB. RGB and HSL components are direct - representations of data stored by the Color instance, and are never converted - implicitly. Color data may be explicitly converted with .convertLinearToSRGB() - or .convertSRGBToLinear(). -

        - - - // RGB components (no change). - color.r = color.g = color.b = 0.5; - console.log( color.r ); // → 0.5 - - // Manual conversion. - color.r = 0.5; - color.convertSRGBToLinear(); - console.log( color.r ); // → 0.214041140 - - -

        - With ColorManagement.enabled = true set (recommended), certain conversions - are made automatically. Because hexadecimal and CSS colors are generally sRGB, [page:Color] - methods will automatically convert these inputs from sRGB to Linear-sRGB in setters, or - convert from Linear-sRGB to sRGB when returning hexadecimal or CSS output from getters. -

        - - - // Hexadecimal conversion. - color.setHex( 0x808080 ); - console.log( color.r ); // → 0.214041140 - console.log( color.getHex() ); // → 0x808080 - - // CSS conversion. - color.setStyle( 'rgb( 0.5, 0.5, 0.5 )' ); - console.log( color.r ); // → 0.214041140 - - // Override conversion with 'colorSpace' argument. - color.setHex( 0x808080, LinearSRGBColorSpace ); - console.log( color.r ); // → 0.5 - console.log( color.getHex( LinearSRGBColorSpace ) ); // → 0x808080 - console.log( color.getHex( SRGBColorSpace ) ); // → 0xBCBCBC - - -

        Common mistakes

        - -

        - When an individual color or texture is misconfigured, it will appear darker or lighter than - expected. When the renderer's output color space is misconfigured, the entire scene may appear - darker (e.g. missing conversion to sRGB) or lighter (e.g. a double conversion to sRGB with - post-processing). In each case the problem may not be uniform, and simply increasing/decreasing - lighting does not solve it. -

        - -

        - A more subtle issue appears when both the input color spaces and the output color - spaces are incorrect — the overall brightness levels may be fine, but colors may change - unexpectedly under different lighting, or shading may appear more blown-out and less soft - than intended. These two wrongs do not make a right, and it's important that the working - color space be linear ("scene referred") and the output color space be nonlinear - ("display referred"). -

        - -

        Further reading

        - - - - - - diff --git a/docs/manual/en/introduction/Creating-a-scene.html b/docs/manual/en/introduction/Creating-a-scene.html deleted file mode 100644 index 0f5751481e4d4e..00000000000000 --- a/docs/manual/en/introduction/Creating-a-scene.html +++ /dev/null @@ -1,153 +0,0 @@ - - - - - - - - - -

        [name]

        - -

        The goal of this section is to give a brief introduction to three.js. We will start by setting up a scene, with a spinning cube. A working example is provided at the bottom of the page in case you get stuck and need help.

        - -

        Before we start

        - -

        - If you haven't yet, go through the [link:#manual/introduction/Installation Installation] guide. We'll assume you've already set up the same project structure (including index.html and main.js), have installed three.js, and are either running a build tool, or using a local server with a CDN and import maps. -

        - -

        Creating the scene

        - -

        To actually be able to display anything with three.js, we need three things: scene, camera and renderer, so that we can render the scene with camera.

        - -

        main.js —

        - - - import * as THREE from 'three'; - - const scene = new THREE.Scene(); - const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); - - const renderer = new THREE.WebGLRenderer(); - renderer.setSize( window.innerWidth, window.innerHeight ); - document.body.appendChild( renderer.domElement ); - - -

        Let's take a moment to explain what's going on here. We have now set up the scene, our camera and the renderer.

        - -

        There are a few different cameras in three.js. For now, let's use a `PerspectiveCamera`.

        - -

        The first attribute is the `field of view`. FOV is the extent of the scene that is seen on the display at any given moment. The value is in degrees.

        - -

        The second one is the `aspect ratio`. You almost always want to use the width of the element divided by the height, or you'll get the same result as when you play old movies on a widescreen TV - the image looks squished.

        - -

        The next two attributes are the `near` and `far` clipping plane. What that means, is that objects further away from the camera than the value of `far` or closer than `near` won't be rendered. You don't have to worry about this now, but you may want to use other values in your apps to get better performance.

        - -

        Next up is the renderer. In addition to creating the renderer instance, we also need to set the size at which we want it to render our app. It's a good idea to use the width and height of the area we want to fill with our app - in this case, the width and height of the browser window. For performance intensive apps, you can also give `setSize` smaller values, like `window.innerWidth/2` and `window.innerHeight/2`, which will make the app render at quarter size.

        - -

        If you wish to keep the size of your app but render it at a lower resolution, you can do so by calling `setSize` with false as `updateStyle` (the third argument). For example, `setSize(window.innerWidth/2, window.innerHeight/2, false)` will render your app at half resolution, given that your <canvas> has 100% width and height.

        - -

        Last but not least, we add the `renderer` element to our HTML document. This is a <canvas> element the renderer uses to display the scene to us.

        - -

        "That's all good, but where's that cube you promised?" Let's add it now.

        - - - const geometry = new THREE.BoxGeometry( 1, 1, 1 ); - const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); - const cube = new THREE.Mesh( geometry, material ); - scene.add( cube ); - - camera.position.z = 5; - - -

        To create a cube, we need a `BoxGeometry`. This is an object that contains all the points (`vertices`) and fill (`faces`) of the cube. We'll explore this more in the future.

        - -

        In addition to the geometry, we need a material to color it. Three.js comes with several materials, but we'll stick to the `MeshBasicMaterial` for now. All materials take an object of properties which will be applied to them. To keep things very simple, we only supply a color attribute of `0x00ff00`, which is green. This works the same way that colors work in CSS or Photoshop (`hex colors`).

        - -

        The third thing we need is a `Mesh`. A mesh is an object that takes a geometry, and applies a material to it, which we then can insert to our scene, and move freely around.

        - -

        By default, when we call `scene.add()`, the thing we add will be added to the coordinates `(0,0,0)`. This would cause both the camera and the cube to be inside each other. To avoid this, we simply move the camera out a bit.

        - -

        Rendering the scene

        - -

        If you copied the code from above into the HTML file we created earlier, you wouldn't be able to see anything. This is because we're not actually rendering anything yet. For that, we need what's called a `render or animate loop`.

        - - - function animate() { - requestAnimationFrame( animate ); - renderer.render( scene, camera ); - } - animate(); - - -

        This will create a loop that causes the renderer to draw the scene every time the screen is refreshed (on a typical screen this means 60 times per second). If you're new to writing games in the browser, you might say "why don't we just create a setInterval ?" The thing is - we could, but `requestAnimationFrame` has a number of advantages. Perhaps the most important one is that it pauses when the user navigates to another browser tab, hence not wasting their precious processing power and battery life.

        - -

        Animating the cube

        - -

        If you insert all the code above into the file you created before we began, you should see a green box. Let's make it all a little more interesting by rotating it.

        - -

        Add the following code right above the `renderer.render` call in your `animate` function:

        - - - cube.rotation.x += 0.01; - cube.rotation.y += 0.01; - - -

        This will be run every frame (normally 60 times per second), and give the cube a nice rotation animation. Basically, anything you want to move or change while the app is running has to go through the animate loop. You can of course call other functions from there, so that you don't end up with an `animate` function that's hundreds of lines.

        - -

        The result

        -

        Congratulations! You have now completed your first three.js application. It's simple, but you have to start somewhere.

        - -

        The full code is available below and as an editable [link:https://jsfiddle.net/0c1oqf38/ live example]. Play around with it to get a better understanding of how it works.

        - -

        index.html —

        - - - <!DOCTYPE html> - <html> - <head lang="en"> - <meta charset="utf-8"> - <title>My first three.js app</title> - <style> - body { margin: 0; } - </style> - </head> - <body> - <script type="module" src="/main.js"></script> - </body> - </html> - - -

        main.js —

        - - - import * as THREE from 'three'; - - const scene = new THREE.Scene(); - const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); - - const renderer = new THREE.WebGLRenderer(); - renderer.setSize( window.innerWidth, window.innerHeight ); - document.body.appendChild( renderer.domElement ); - - const geometry = new THREE.BoxGeometry( 1, 1, 1 ); - const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); - const cube = new THREE.Mesh( geometry, material ); - scene.add( cube ); - - camera.position.z = 5; - - function animate() { - requestAnimationFrame( animate ); - - cube.rotation.x += 0.01; - cube.rotation.y += 0.01; - - renderer.render( scene, camera ); - } - - animate(); - - - diff --git a/docs/manual/en/introduction/Creating-text.html b/docs/manual/en/introduction/Creating-text.html deleted file mode 100644 index 06b2348e3dcb24..00000000000000 --- a/docs/manual/en/introduction/Creating-text.html +++ /dev/null @@ -1,142 +0,0 @@ - - - - - - - - - -

        [name]

        -
        -

        - There are often times when you might need to use text in your three.js application - here are - a couple of ways that you can do so. -

        -
        - -

        1. DOM + CSS

        -
        -

        - Using HTML is generally the easiest and fastest manner to add text. This is the method - used for descriptive overlays in most three.js examples. -

        -

        You can add content to a

        - <div id="info">Description</div> - -

        - and use CSS markup to position absolutely at a position above all others with a - z-index especially if you are running three.js full screen. -

        - - -#info { - position: absolute; - top: 10px; - width: 100%; - text-align: center; - z-index: 100; - display:block; -} - - -
        - - -

        2. Use [page:CSS2DRenderer] or [page:CSS3DRenderer]

        -
        -

        - Use these renderers to draw high-quality text contained in DOM elements to your three.js scene. - This is similar to 1. except that with these renderers elements can be integrated more tightly and dynamically into the scene. -

        -
        - - -

        3. Draw text to canvas and use as a [page:Texture]

        -
        -

        Use this method if you wish to draw text easily on a plane in your three.js scene.

        -
        - - -

        4. Create a model in your favourite 3D application and export to three.js

        -
        -

        Use this method if you prefer working with your 3d applications and importing the models to three.js.

        -
        - - -

        5. Procedural Text Geometry

        -
        -

        - If you prefer to work purely in THREE.js or to create procedural and dynamic 3D - text geometries, you can create a mesh whose geometry is an instance of THREE.TextGeometry: -

        -

        - new THREE.TextGeometry( text, parameters ); -

        -

        - In order for this to work, however, your TextGeometry will need an instance of THREE.Font - to be set on its "font" parameter. - - See the [page:TextGeometry] page for more info on how this can be done, descriptions of each - accepted parameter, and a list of the JSON fonts that come with the THREE.js distribution itself. -

        - -

        Examples

        - -

        - [example:webgl_geometry_text WebGL / geometry / text]
        - [example:webgl_shadowmap WebGL / shadowmap] -

        - -

        - If Typeface is down, or you want to use a font that is not there, there's a tutorial - with a python script for blender that allows you to export text to Three.js's JSON format: - [link:http://www.jaanga.com/2012/03/blender-to-threejs-create-3d-text-with.html] -

        - -
        - - -

        6. Bitmap Fonts

        -
        -

        - BMFonts (bitmap fonts) allow batching glyphs into a single BufferGeometry. BMFont rendering - supports word-wrapping, letter spacing, kerning, signed distance fields with standard - derivatives, multi-channel signed distance fields, multi-texture fonts, and more. - See [link:https://github.com/felixmariotto/three-mesh-ui three-mesh-ui] or [link:https://github.com/Jam3/three-bmfont-text three-bmfont-text]. -

        -

        - Stock fonts are available in projects like - [link:https://github.com/etiennepinchon/aframe-fonts A-Frame Fonts], or you can create your own - from any .TTF font, optimizing to include only characters required for a project. -

        -

        - Some helpful tools: -

        -
          -
        • [link:http://msdf-bmfont.donmccurdy.com/ msdf-bmfont-web] (web-based)
        • -
        • [link:https://github.com/soimy/msdf-bmfont-xml msdf-bmfont-xml] (commandline)
        • -
        • [link:https://github.com/libgdx/libgdx/wiki/Hiero hiero] (desktop app)
        • -
        -
        - - -

        7. Troika Text

        -
        -

        - The [link:https://www.npmjs.com/package/troika-three-text troika-three-text] package renders - quality antialiased text using a similar technique as BMFonts, but works directly with any .TTF - or .WOFF font file so you don't have to pregenerate a glyph texture offline. It also adds - capabilities including: -

        -
          -
        • Effects like strokes, drop shadows, and curvature
        • -
        • The ability to apply any three.js Material, even a custom ShaderMaterial
        • -
        • Support for font ligatures, scripts with joined letters, and right-to-left/bidirectional layout
        • -
        • Optimization for large amounts of dynamic text, performing most work off the main thread in a web worker
        • -
        -
        - - - - diff --git a/docs/manual/en/introduction/Drawing-lines.html b/docs/manual/en/introduction/Drawing-lines.html deleted file mode 100644 index 75c3207c3e0e85..00000000000000 --- a/docs/manual/en/introduction/Drawing-lines.html +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - - -

        [name]

        -
        -

        - Let's say you want to draw a line or a circle, not a wireframe [page:Mesh]. - First we need to set up the [page:WebGLRenderer renderer], [page:Scene scene] and camera (see the Creating a scene page). -

        - -

        Here is the code that we will use:

        - -const renderer = new THREE.WebGLRenderer(); -renderer.setSize( window.innerWidth, window.innerHeight ); -document.body.appendChild( renderer.domElement ); - -const camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 500 ); -camera.position.set( 0, 0, 100 ); -camera.lookAt( 0, 0, 0 ); - -const scene = new THREE.Scene(); - -

        Next thing we will do is define a material. For lines we have to use [page:LineBasicMaterial] or [page:LineDashedMaterial].

        - -//create a blue LineBasicMaterial -const material = new THREE.LineBasicMaterial( { color: 0x0000ff } ); - - -

        - After material we will need a geometry with some vertices: -

        - - -const points = []; -points.push( new THREE.Vector3( - 10, 0, 0 ) ); -points.push( new THREE.Vector3( 0, 10, 0 ) ); -points.push( new THREE.Vector3( 10, 0, 0 ) ); - -const geometry = new THREE.BufferGeometry().setFromPoints( points ); - - -

        Note that lines are drawn between each consecutive pair of vertices, but not between the first and last (the line is not closed.)

        - -

        Now that we have points for two lines and a material, we can put them together to form a line.

        - -const line = new THREE.Line( geometry, material ); - -

        All that's left is to add it to the scene and call [page:WebGLRenderer.render render].

        - - -scene.add( line ); -renderer.render( scene, camera ); - - -

        You should now be seeing an arrow pointing upwards, made from two blue lines.

        -
        - - diff --git a/docs/manual/en/introduction/FAQ.html b/docs/manual/en/introduction/FAQ.html deleted file mode 100644 index 0ac0ffe4b65975..00000000000000 --- a/docs/manual/en/introduction/FAQ.html +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - - -

        [name]

        - -

        Which 3D model format is best supported?

        -
        -

        - The recommended format for importing and exporting assets is glTF (GL Transmission Format). Because glTF is focused on runtime asset delivery, it is compact to transmit and fast to load. -

        -

        - three.js provides loaders for many other popular formats like FBX, Collada or OBJ as well. Nevertheless, you should always try to establish a glTF based workflow in your projects first. For more information, see [link:#manual/introduction/Loading-3D-models loading 3D models]. -

        -
        - -

        Why are there meta viewport tags in examples?

        -
        - <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> - -

        These tags control viewport size and scale for mobile browsers (where page content may be rendered at different size than visible viewport).

        - -

        [link:https://developer.apple.com/library/content/documentation/AppleApplications/Reference/SafariWebContent/UsingtheViewport/UsingtheViewport.html Safari: Using the Viewport]

        - -

        [link:https://developer.mozilla.org/en-US/docs/Web/HTML/Viewport_meta_tag MDN: Using the viewport meta tag]

        -
        - -

        How can scene scale be preserved on resize?

        -

        - We want all objects, regardless of their distance from the camera, to appear the same size, even as the window is resized. - - The key equation to solving this is this formula for the visible height at a given distance: - - -visible_height = 2 * Math.tan( ( Math.PI / 180 ) * camera.fov / 2 ) * distance_from_camera; - - If we increase the window height by a certain percentage, then what we want is the visible height at all distances - to increase by the same percentage. - - This can not be done by changing the camera position. Instead you have to change the camera field-of-view. - [link:http://jsfiddle.net/Q4Jpu/ Example]. -

        - -

        Why is part of my object invisible?

        -

        - This could be because of face culling. Faces have an orientation that decides which side is which. And the culling removes the backside in normal circumstances. To see if this is your problem, change the material side to THREE.DoubleSide. - material.side = THREE.DoubleSide -

        - -

        Why does three.js sometimes return strange results for invalid inputs?

        -

        - For performance reasons, three.js doesn't validate inputs in most cases. It's your app's responsibility to make sure that all inputs are valid. -

        - -

        Can I use three.js in Node.js?

        -

        - Because three.js is built for the web, it depends on browser and DOM APIs that don't always exist in Node.js. Some of these issues can be avoided by using shims like [link:https://github.com/stackgl/headless-gl headless-gl] and [link:https://github.com/rstacruz/jsdom-global jsdom-global], or by replacing components like [page:TextureLoader] with custom alternatives. Other DOM APIs may be deeply intertwined with the code that uses them, and will be harder to work around. We welcome simple and maintainable pull requests to improve Node.js support, but recommend opening an issue to discuss your improvements first. -

        - - diff --git a/docs/manual/en/introduction/How-to-create-VR-content.html b/docs/manual/en/introduction/How-to-create-VR-content.html deleted file mode 100644 index c27063612bc0ce..00000000000000 --- a/docs/manual/en/introduction/How-to-create-VR-content.html +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - - - - - -

        [name]

        - -

        - This guide provides a brief overview of the basic components of a web-based VR application - made with three.js. -

        - -

        Workflow

        - -

        - First, you have to include [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/webxr/VRButton.js VRButton.js] - into your project. -

        - - -import { VRButton } from 'three/addons/webxr/VRButton.js'; - - -

        - *VRButton.createButton()* does two important things: It creates a button which indicates - VR compatibility. Besides, it initiates a VR session if the user activates the button. The only thing you have - to do is to add the following line of code to your app. -

        - - -document.body.appendChild( VRButton.createButton( renderer ) ); - - -

        - Next, you have to tell your instance of `WebGLRenderer` to enable XR rendering. -

        - - -renderer.xr.enabled = true; - - -

        - Finally, you have to adjust your animation loop since we can't use our well known - *window.requestAnimationFrame()* function. For VR projects we use [page:WebGLRenderer.setAnimationLoop setAnimationLoop]. - The minimal code looks like this: -

        - - -renderer.setAnimationLoop( function () { - - renderer.render( scene, camera ); - -} ); - - -

        Next Steps

        - -

        - Have a look at one of the official WebVR examples to see this workflow in action.

        - - [example:webxr_vr_ballshooter WebXR / VR / ballshooter]
        - [example:webxr_vr_cubes WebXR / VR / cubes]
        - [example:webxr_vr_dragging WebXR / VR / dragging]
        - [example:webxr_vr_paint WebXR / VR / paint]
        - [example:webxr_vr_panorama_depth WebXR / VR / panorama_depth]
        - [example:webxr_vr_panorama WebXR / VR / panorama]
        - [example:webxr_vr_rollercoaster WebXR / VR / rollercoaster]
        - [example:webxr_vr_sandbox WebXR / VR / sandbox]
        - [example:webxr_vr_sculpt WebXR / VR / sculpt]
        - [example:webxr_vr_video WebXR / VR / video] -

        - - - - diff --git a/docs/manual/en/introduction/How-to-dispose-of-objects.html b/docs/manual/en/introduction/How-to-dispose-of-objects.html deleted file mode 100644 index 287bed80b91f15..00000000000000 --- a/docs/manual/en/introduction/How-to-dispose-of-objects.html +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - - - -

        [name]

        - -

        - One important aspect in order to improve performance and avoid memory leaks in your application is the disposal of unused library entities. - Whenever you create an instance of a *three.js* type, you allocate a certain amount of memory. However, *three.js* creates for specific objects - like geometries or materials WebGL related entities like buffers or shader programs which are necessary for rendering. It's important to - highlight that these objects are not released automatically. Instead, the application has to use a special API in order to free such resources. - This guide provides a brief overview about how this API is used and what objects are relevant in this context. -

        - -

        Geometries

        - -

        - A geometry usually represents vertex information defined as a collection of attributes. *three.js* internally creates an object of type [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLBuffer WebGLBuffer] - for each attribute. These entities are only deleted if you call [page:BufferGeometry.dispose](). If a geometry becomes obsolete in your application, - execute the method to free all related resources. -

        - -

        Materials

        - -

        - A material defines how objects are rendered. *three.js* uses the information of a material definition in order to construct a shader program for rendering. - Shader programs can only be deleted if the respective material is disposed. For performance reasons, *three.js* tries to reuse existing - shader programs if possible. So a shader program is only deleted if all related materials are disposed. You can indicate the disposal of a material by - executing [page:Material.dispose](). -

        - -

        Textures

        - -

        - The disposal of a material has no effect on textures. They are handled separately since a single texture can be used by multiple materials at the same time. - Whenever you create an instance of [page:Texture], three.js internally creates an instance of [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLTexture WebGLTexture]. - Similar to buffers, this object can only be deleted by calling [page:Texture.dispose](). -

        - -

        - If you use an `ImageBitmap` as the texture's data source, you have to call [link:https://developer.mozilla.org/en-US/docs/Web/API/ImageBitmap/close ImageBitmap.close]() at the application level to dispose of all CPU-side resources. - An automated call of `ImageBitmap.close()` in [page:Texture.dispose]() is not possible, since the image bitmap becomes unusable, and the engine has no way of knowing if the image bitmap is used elsewhere. -

        - -

        Render Targets

        - -

        - Objects of type [page:WebGLRenderTarget] not only allocate an instance of [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLTexture WebGLTexture] but also - [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLFramebuffer WebGLFramebuffer]s and [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderbuffer WebGLRenderbuffer]s - for realizing custom rendering destinations. These objects are only deallocated by executing [page:WebGLRenderTarget.dispose](). -

        - -

        Miscellaneous

        - -

        - There are other classes from the examples directory like controls or post processing passes which provide `dispose()` methods in order to remove internal event listeners - or render targets. In general, it's recommended to check the API or documentation of a class and watch for `dispose()`. If present, you should use it when cleaning things up. -

        - -

        FAQ

        - -

        Why can't *three.js* dispose objects automatically?

        - -

        - This question was asked many times by the community so it's important to clarify this matter. Fact is that *three.js* does not know the lifetime or scope - of user-created entities like geometries or materials. This is the responsibility of the application. For example even if a material is currently not used for rendering, - it might be necessary for the next frame. So if the application decides that a certain object can be deleted, it has to notify the engine via calling the respective - `dispose()` method. -

        - -

        Does removing a mesh from the scene also dispose its geometry and material?

        - -

        - No, you have to explicitly dispose the geometry and material via *dispose()*. Keep in mind that geometries and materials can be shared among 3D objects like meshes. -

        - -

        Does *three.js* provide information about the amount of cached objects?

        - -

        - Yes. It's possible to evaluate [page:WebGLRenderer.info], a special property of the renderer with a series of statistical information about the graphics board memory - and the rendering process. Among other things, it tells you how many textures, geometries and shader programs are internally stored. If you notice performance problems - in your application, it's a good idea to debug this property in order to easily identify a memory leak. -

        - -

        What happens when you call `dispose()` on a texture but the image is not loaded yet?

        - -

        - Internal resources for a texture are only allocated if the image has fully loaded. If you dispose a texture before the image was loaded, - nothing happens. No resources were allocated so there is also no need for clean up. -

        - -

        What happens when I call `dispose()` and then use the respective object at a later point?

        - -

        - The deleted internal resources will be created again by the engine. So no runtime error will occur but you might notice a negative performance impact for the current frame, - especially when shader programs have to be compiled. -

        - -

        How should I manage *three.js* objects in my app? When do I know how to dispose things?

        - -

        - In general, there is no definite recommendation for this. It highly depends on the specific use case when calling `dispose()` is appropriate. It's important to highlight that - it's not always necessary to dispose objects all the time. A good example for this is a game which consists of multiple levels. A good place for object disposal is when - switching the level. The app could traverse through the old scene and dispose all obsolete materials, geometries and textures. As mentioned in the previous section, it does not - produce a runtime error if you dispose an object that is actually still in use. The worst thing that can happen is performance drop for a single frame. -

        - -

        Examples that demonstrate the usage of dispose()

        - -

        - [example:webgl_test_memory WebGL / test / memory]
        - [example:webgl_test_memory2 WebGL / test / memory2]
        -

        - - - - diff --git a/docs/manual/en/introduction/How-to-update-things.html b/docs/manual/en/introduction/How-to-update-things.html deleted file mode 100644 index 69bbd04d999903..00000000000000 --- a/docs/manual/en/introduction/How-to-update-things.html +++ /dev/null @@ -1,220 +0,0 @@ - - - - - - - - - -

        [name]

        -
        -

        All objects by default automatically update their matrices if they have been added to the scene with

        - -const object = new THREE.Object3D(); -scene.add( object ); - - or if they are the child of another object that has been added to the scene: - -const object1 = new THREE.Object3D(); -const object2 = new THREE.Object3D(); - -object1.add( object2 ); -scene.add( object1 ); //object1 and object2 will automatically update their matrices - -
        - -

        However, if you know the object will be static, you can disable this and update the transform matrix manually just when needed.

        - - -object.matrixAutoUpdate = false; -object.updateMatrix(); - - -

        BufferGeometry

        -
        -

        - BufferGeometries store information (such as vertex positions, face indices, normals, colors, - UVs, and any custom attributes) in [page:BufferAttribute buffers] - that is, - [link:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays typed arrays]. - This makes them generally faster than standard Geometries, at the cost of being somewhat harder to - work with. -

        -

        - With regards to updating BufferGeometries, the most important thing to understand is that - you cannot resize buffers (this is very costly, basically the equivalent to creating a new geometry). - You can however update the content of buffers. -

        -

        - This means that if you know an attribute of your BufferGeometry will grow, say the number of vertices, - you must pre-allocate a buffer large enough to hold any new vertices that may be created. Of - course, this also means that there will be a maximum size for your BufferGeometry - there is - no way to create a BufferGeometry that can efficiently be extended indefinitely. -

        -

        - We'll use the example of a line that gets extended at render time. We'll allocate space - in the buffer for 500 vertices but draw only two at first, using [page:BufferGeometry.drawRange]. -

        - -const MAX_POINTS = 500; - -// geometry -const geometry = new THREE.BufferGeometry(); - -// attributes -const positions = new Float32Array( MAX_POINTS * 3 ); // 3 vertices per point -geometry.setAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) ); - -// draw range -const drawCount = 2; // draw the first 2 points, only -geometry.setDrawRange( 0, drawCount ); - -// material -const material = new THREE.LineBasicMaterial( { color: 0xff0000 } ); - -// line -const line = new THREE.Line( geometry, material ); -scene.add( line ); - -

        - Next we'll randomly add points to the line using a pattern like: -

        - -const positionAttribute = line.geometry.getAttribute( 'position' ); - -let x = 0, y = 0, z = 0; - -for ( let i = 0; i < positionAttribute.count; i ++ ) { - - positionAttribute.setXYZ( i, x, y, z ); - - x += ( Math.random() - 0.5 ) * 30; - y += ( Math.random() - 0.5 ) * 30; - z += ( Math.random() - 0.5 ) * 30; - -} - -

        - If you want to change the number of points rendered after the first render, do this: -

        - -line.geometry.setDrawRange( 0, newValue ); - -

        - If you want to change the position data values after the first render, you need to - set the needsUpdate flag like so: -

        - -positionAttribute.needsUpdate = true; // required after the first render - - -

        - If you change the position data values after the initial render, you may need to recompute - bounding volumes so other features of the engine like view frustum culling or helpers properly work. -

        - -line.geometry.computeBoundingBox(); -line.geometry.computeBoundingSphere(); - - -

        - [link:https://jsfiddle.net/t4m85pLr/1/ Here is a fiddle] showing an animated line which you can adapt to your use case. -

        - -

        Examples

        - -

        - [example:webgl_custom_attributes WebGL / custom / attributes]
        - [example:webgl_buffergeometry_custom_attributes_particles WebGL / buffergeometry / custom / attributes / particles] -

        - -
        - -

        Materials

        -
        -

        All uniforms values can be changed freely (e.g. colors, textures, opacity, etc), values are sent to the shader every frame.

        - -

        Also GLstate related parameters can change any time (depthTest, blending, polygonOffset, etc).

        - -

        The following properties can't be easily changed at runtime (once the material is rendered at least once):

        -
          -
        • numbers and types of uniforms
        • -
        • presence or not of -
            -
          • texture
          • -
          • fog
          • -
          • vertex colors
          • -
          • morphing
          • -
          • shadow map
          • -
          • alpha test
          • -
          • transparent
          • -
          -
        • -
        - -

        Changes in these require building of new shader program. You'll need to set

        - material.needsUpdate = true - -

        Bear in mind this might be quite slow and induce jerkiness in framerate (especially on Windows, as shader compilation is slower in DirectX than OpenGL).

        - -

        For smoother experience you can emulate changes in these features to some degree by having "dummy" values like zero intensity lights, white textures, or zero density fog.

        - -

        You can freely change the material used for geometry chunks, however you cannot change how an object is divided into chunks (according to face materials).

        - -

        If you need to have different configurations of materials during runtime:

        -

        If the number of materials / chunks is small, you could pre-divide the object beforehand (e.g. hair / face / body / upper clothes / trousers for a human, front / sides / top / glass / tire / interior for a car).

        - -

        If the number is large (e.g. each face could be potentially different), consider a different solution, such as using attributes / textures to drive different per-face look.

        - -

        Examples

        -

        - [example:webgl_materials_car WebGL / materials / car]
        - [example:webgl_postprocessing_dof WebGL / webgl_postprocessing / dof] -

        -
        - - -

        Textures

        -
        -

        Image, canvas, video and data textures need to have the following flag set if they are changed:

        - - texture.needsUpdate = true; - -

        Render targets update automatically.

        - -

        Examples

        -

        - [example:webgl_materials_video WebGL / materials / video]
        - [example:webgl_rtt WebGL / rtt] -

        - -
        - - -

        Cameras

        -
        -

        A camera's position and target is updated automatically. If you need to change

        -
          -
        • - fov -
        • -
        • - aspect -
        • -
        • - near -
        • -
        • - far -
        • -
        -

        - then you'll need to recompute the projection matrix: -

        - -camera.aspect = window.innerWidth / window.innerHeight; -camera.updateProjectionMatrix(); - -
        - - diff --git a/docs/manual/en/introduction/How-to-use-post-processing.html b/docs/manual/en/introduction/How-to-use-post-processing.html deleted file mode 100644 index 31bb893b4d9546..00000000000000 --- a/docs/manual/en/introduction/How-to-use-post-processing.html +++ /dev/null @@ -1,111 +0,0 @@ - - - - - - - - - -

        How to use post-processing

        - -

        - Many three.js applications render their 3D objects directly to the screen. Sometimes, however, you want to apply one or more graphical - effects like Depth-Of-Field, Bloom, Film Grain or various types of Anti-aliasing. Post-processing is a widely used approach - to implement such effects. First, the scene is rendered to a render target which represents a buffer in the video card's memory. - In the next step one or more post-processing passes apply filters and effects to the image buffer before it is eventually rendered to - the screen. -

        -

        - three.js provides a complete post-processing solution via [page:EffectComposer] to implement such a workflow. -

        - -

        Workflow

        - -

        - The first step in the process is to import all necessary files from the examples directory. The guide assumes you are using the official - [link:https://www.npmjs.com/package/three npm package] of three.js. For our basic demo in this guide we need the following files. -

        - - - import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'; - import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'; - import { GlitchPass } from 'three/addons/postprocessing/GlitchPass.js'; - - -

        - After all files are successfully imported, we can create our composer by passing in an instance of [page:WebGLRenderer]. -

        - - - const composer = new EffectComposer( renderer ); - - -

        - When using a composer, it's necessary to change the application's animation loop. Instead of calling the render method of - [page:WebGLRenderer], we now use the respective counterpart of [page:EffectComposer]. -

        - - - function animate() { - - requestAnimationFrame( animate ); - - composer.render(); - - } - - -

        - Our composer is now ready so it's possible to configure the chain of post-processing passes. These passes are responsible for creating - the final visual output of the application. They are processed in order of their addition/insertion. In our example, the instance of `RenderPass` - is executed first and then the instance of `GlitchPass`. The last enabled pass in the chain is automatically rendered to the screen. The setup - of the passes looks like so: -

        - - - const renderPass = new RenderPass( scene, camera ); - composer.addPass( renderPass ); - - const glitchPass = new GlitchPass(); - composer.addPass( glitchPass ); - - -

        - `RenderPass` is normally placed at the beginning of the chain in order to provide the rendered scene as an input for the next post-processing step. In our case, - `GlitchPass` is going to use these image data to apply a wild glitch effect. Check out this [link:https://threejs.org/examples/webgl_postprocessing_glitch live example] - to see it in action. -

        - -

        Built-in Passes

        - -

        - You can use a wide range of pre-defined post-processing passes provided by the engine. They are located in the - [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm/postprocessing postprocessing] directory. -

        - -

        Custom Passes

        - -

        - Sometimes you want to write a custom post-processing shader and include it into the chain of post-processing passes. For this scenario, - you can utilize `ShaderPass`. After importing the file and your custom shader, you can use the following code to setup the pass. -

        - - - import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js'; - import { LuminosityShader } from 'three/addons/shaders/LuminosityShader.js'; - - // later in your init routine - - const luminosityPass = new ShaderPass( LuminosityShader ); - composer.addPass( luminosityPass ); - - -

        - The repository provides a file called [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/shaders/CopyShader.js CopyShader] which is a - good starting code for your own custom shader. `CopyShader` just copies the image contents of the [page:EffectComposer]'s read buffer - to its write buffer without applying any effects. -

        - - - diff --git a/docs/manual/en/introduction/Installation.html b/docs/manual/en/introduction/Installation.html deleted file mode 100644 index 1d2feaf0b51ba6..00000000000000 --- a/docs/manual/en/introduction/Installation.html +++ /dev/null @@ -1,268 +0,0 @@ - - - - - - - - - -

        [name]

        - -

        Project structure

        - -

        - Every three.js project needs at least one HTML file to define the webpage, and a JavaScript file to run your three.js code. The structure and naming choices below aren't required, but will be used throughout this guide for consistency. -

        - -
          -
        • - index.html - - <!DOCTYPE html> - <html lang="en"> - <head> - <meta charset="utf-8"> - <title>My first three.js app</title> - <style> - body { margin: 0; } - </style> - </head> - <body> - <script type="module" src="/main.js"></script> - </body> - </html> - -
        • -
        • - main.js - -import * as THREE from 'three'; - -... - -
        • -
        • - public/ -
            -
          • - The public/ folder is sometimes also called a "static" folder, because the files it contains are pushed to the website unchanged. Usually textures, audio, and 3D models will go here. -
          • -
          -
        • -
        - -

        - Now that we've set up the basic project structure, we need a way to run the project locally and access it through a web browser. Installation and local development can be accomplished with npm and a build tool, or by importing three.js from a CDN. Both options are explained in the sections below. -

        - -

        Option 1: Install with NPM and a build tool

        - -

        Development

        - -

        - Installing from the [link:https://www.npmjs.com/ npm package registry] and using a [link:https://eloquentjavascript.net/10_modules.html#h_zWTXAU93DC build tool] is the recommended approach for most users — the more dependencies your project needs, the more likely you are to run into problems that the static hosting cannot easily resolve. With a build tool, importing local JavaScript files and npm packages should work out of the box, without import maps. -

        - - -
          -
        1. - Install [link:https://nodejs.org/ Node.js]. We'll need it to load manage dependencies and to run our build tool. -
        2. -
        3. -

          - Install three.js and a build tool, [link:https://vitejs.dev/ Vite], using a [link:https://www.joshwcomeau.com/javascript/terminal-for-js-devs/ terminal] in your project folder. Vite will be used during development, but it isn't part of the final webpage. If you prefer to use another build tool, that's fine — we support modern build tools that can import [link:https://eloquentjavascript.net/10_modules.html#h_zWTXAU93DC ES Modules]. -

          - -# three.js -npm install --save three - -# vite -npm install --save-dev vite - - -
        4. -
        5. - From your terminal, run: - - npx vite - - -
        6. -
        7. - If everything went well, you'll see a URL like http://localhost:5173 appear in your terminal, and can open that URL to see your web application. -
        8. -
        - -

        - The page will be blank — you're ready to [link:#manual/introduction/Creating-a-scene create a scene]. -

        - -

        - If you want to learn more about these tools before you continue, see: -

        - -
          -
        • - [link:https://threejs-journey.com/lessons/local-server three.js journey: Local Server] -
        • -
        • - [link:https://vitejs.dev/guide/cli.html Vite: Command Line Interface] -
        • -
        • - [link:https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Package_management MDN: Package management basics] -
        • -
        - -

        Production

        - -

        - Later, when you're ready to deploy your web application, you'll just need to tell Vite to run a production build — npx vite build. Everything used by the application will be compiled, optimized, and copied into the dist/ folder. The contents of that folder are ready to be hosted on your website. -

        - -

        Option 2: Import from a CDN

        - -

        Development

        - -

        Installing without build tools will require some changes to the project structure given above.

        - -
          -
        1. -

          - We imported code from 'three' (an npm package) in main.js, and web browsers don't know what that means. In index.html we'll need to add an [link:https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap import map] defining where to get the package. Put the code below inside the <head></head> tag, after the styles. -

          - -<script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script> - -<script type="importmap"> - { - "imports": { - "three": "https://unpkg.com/three@<version>/build/three.module.js", - "three/addons/": "https://unpkg.com/three@<version>/examples/jsm/" - } - } -</script> - -

          - Don't forget to replace <version> with an actual version of three.js, like "v0.149.0". The most recent version can be found on the [link:https://www.npmjs.com/package/three?activeTab=versions npm version list]. Because import maps are [link:https://caniuse.com/import-maps not yet supported] by some major browsers, we include the polyfill es-module-shims.js. -

          -
        2. -
        3. -

          - We'll also need to run a local server to host these files at URL where the web browser can access them. While it's technically possible to double-click an HTML file and open it in your browser, important features that we'll later do not work when the page is opened this way, for security reasons. -

          -

          - Install [link:https://nodejs.org/ Node.js], then run [link:https://www.npmjs.com/package/serve serve] to start a local server in the project's directory: -

          - - npx serve . - -
        4. -
        5. - If everything went well, you'll see a URL like http://localhost:3000 appear in your terminal, and can open that URL to see your web application. -
        6. -
        - -

        - The page will be blank — you're ready to [link:#manual/introduction/Creating-a-scene create a scene]. -

        - -

        - Many other local static servers are available — some use different languages instead of Node.js, and others are desktop applications. They all work basically the same way, and we've provided a few alternatives below. -

        - -
        - More local servers - -

        Command Line

        - -

        Command line local servers run from a terminal window. The associated programming language may need to be installed first.

        - -
          -
        • npx http-server (Node.js)
        • -
        • npx five-server (Node.js)
        • -
        • python -m SimpleHTTPServer (Python 2.x)
        • -
        • python -m http.server (Python 3.x)
        • -
        • php -S localhost:8000 (PHP 5.4+)
        • -
        - - -

        GUI

        - -

        GUI local servers run as an application window on your computer, and may have a user interface.

        - -
          -
        • [link:https://greggman.github.io/servez Servez]
        • -
        - -

        Code Editor Plugins

        - -

        Some code editors have plugins that spawn a simple server on demand.

        - -
          -
        • [link:https://marketplace.visualstudio.com/items?itemName=yandeu.five-server Five Server] for Visual Studio Code
        • -
        • [link:https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer Live Server] for Visual Studio Code
        • -
        • [link:https://atom.io/packages/atom-live-server Live Server] for Atom
        • -
        - - -
        - -

        Production

        - -

        - When you're ready to deploy your web application, push the source files to your web hosting provider — no need to build or compile anything. The downside of that tradeoff is that you'll need to be careful to keep the import map updated with any dependencies (and dependencies of dependencies!) that your application requires. If the CDN hosting your dependencies goes down temporarily, your website will stop working too. -

        - -

        - IMPORTANT: Import all dependencies from the same version of three.js, and from the same CDN. Mixing files from different sources may cause duplicate code to be included, or even break the application in unexpected ways. -

        - -

        Addons

        - -

        - Out of the box, three.js includes the fundamentals of a 3D engine. Other three.js components — such as controls, loaders, and post-processing effects — are part of the [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm addons/] directory. Addons do not need to be installed separately, but do need to be imported separately. -

        - -

        - The example below shows how to import three.js with the [page:OrbitControls] and [page:GLTFLoader] addons. Where necessary, this will also be mentioned in each addon's documentation or examples. -

        - - -import * as THREE from 'three'; -import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; -import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; - -const controls = new OrbitControls( camera, renderer.domElement ); -const loader = new GLTFLoader(); - - -

        - Some excellent third-party projects are available for three.js, too. These need to be installed separately — see [link:#manual/introduction/Libraries-and-Plugins Libraries and Plugins]. -

        - -

        Next Steps

        - -

        - You're now ready to [link:#manual/introduction/Creating-a-scene create a scene]. -

        - - - diff --git a/docs/manual/en/introduction/Libraries-and-Plugins.html b/docs/manual/en/introduction/Libraries-and-Plugins.html deleted file mode 100644 index 1e4979bf7e2ba6..00000000000000 --- a/docs/manual/en/introduction/Libraries-and-Plugins.html +++ /dev/null @@ -1,109 +0,0 @@ - - - - - - - - - -

        [name]

        - -

        - Listed here are externally developed compatible libraries and plugins for three.js. This - list and the associated packages are maintained by the community and not guaranteed - to be up to date. If you'd like to update this list make PR! -

        - -

        Physics

        - -
          -
        • [link:https://github.com/lo-th/Oimo.js/ Oimo.js]
        • -
        • [link:https://enable3d.io/ enable3d]
        • -
        • [link:https://github.com/kripken/ammo.js/ ammo.js]
        • -
        • [link:https://github.com/pmndrs/cannon-es cannon-es]
        • -
        - -

        Postprocessing

        - -

        - In addition to the [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm/postprocessing official three.js postprocessing effects], - support for some additional effects and frameworks are available through external libraries. -

        - -
          -
        • [link:https://github.com/vanruesc/postprocessing postprocessing]
        • -
        - -

        Intersection and Raycast Performance

        - -
          -
        • [link:https://github.com/gkjohnson/three-mesh-bvh three-mesh-bvh]
        • -
        - -

        Path Tracing

        - -
          -
        • [link:https://github.com/gkjohnson/three-gpu-pathtracer three-gpu-pathtracer]
        • -
        - -

        File Formats

        - -

        - In addition to the [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm/loaders official three.js loaders], - support for some additional formats is available through external libraries. -

        - -
          -
        • [link:https://github.com/gkjohnson/urdf-loaders/tree/master/javascript urdf-loader]
        • -
        • [link:https://github.com/NASA-AMMOS/3DTilesRendererJS 3d-tiles-renderer-js]
        • -
        • [link:https://github.com/kaisalmen/WWOBJLoader WebWorker OBJLoader]
        • -
        • [link:https://github.com/IFCjs/web-ifc-three IFC.js]
        • -
        - -

        Geometry

        - -
          -
        • [link:https://github.com/spite/THREE.MeshLine THREE.MeshLine]
        • -
        - -

        3D Text and Layout

        - -
          -
        • [link:https://github.com/protectwise/troika/tree/master/packages/troika-three-text troika-three-text]
        • -
        • [link:https://github.com/felixmariotto/three-mesh-ui three-mesh-ui]
        • -
        - -

        Particle Systems

        - -
          -
        • [link:https://github.com/Alchemist0823/three.quarks three.quarks]
        • -
        • [link:https://github.com/creativelifeform/three-nebula three-nebula]
        • -
        - -

        Inverse Kinematics

        - -
          -
        • [link:https://github.com/jsantell/THREE.IK THREE.IK]
        • -
        • [link:https://github.com/lo-th/fullik fullik]
        • -
        • [link:https://github.com/gkjohnson/closed-chain-ik-js closed-chain-ik]
        • -
        - -

        Game AI

        - -
          -
        • [link:https://mugen87.github.io/yuka/ yuka]
        • -
        • [link:https://github.com/donmccurdy/three-pathfinding three-pathfinding]
        • -
        - -

        Wrappers and Frameworks

        - -
          -
        • [link:https://aframe.io/ A-Frame]
        • -
        • [link:https://github.com/pmndrs/react-three-fiber react-three-fiber]
        • -
        • [link:https://github.com/ecsyjs/ecsy-three ECSY]
        • -
        • [link:https://threlte.xyz/ Threlte]
        • -
        - - - diff --git a/docs/manual/en/introduction/Loading-3D-models.html b/docs/manual/en/introduction/Loading-3D-models.html deleted file mode 100644 index 5c64c07a0f7f23..00000000000000 --- a/docs/manual/en/introduction/Loading-3D-models.html +++ /dev/null @@ -1,167 +0,0 @@ - - - - - - - - - - - -

        [name]

        - -

        - 3D models are available in hundreds of file formats, each with different - purposes, assorted features, and varying complexity. Although - - three.js provides many loaders, choosing the right format and - workflow will save time and frustration later on. Some formats are - difficult to work with, inefficient for realtime experiences, or simply not - fully supported at this time. -

        - -

        - This guide provides a workflow recommended for most users, and suggestions - for what to try if things don't go as expected. -

        - -

        Before we start

        - -

        - If you're new to running a local server, begin with - [link:#manual/introduction/Installation installation] - first. Many common errors viewing 3D models can be avoided by hosting files - correctly. -

        - -

        Recommended workflow

        - -

        - Where possible, we recommend using glTF (GL Transmission Format). Both - .GLB and .GLTF versions of the format are - well supported. Because glTF is focused on runtime asset delivery, it is - compact to transmit and fast to load. Features include meshes, materials, - textures, skins, skeletons, morph targets, animations, lights, and - cameras. -

        - -

        - Public-domain glTF files are available on sites like - - Sketchfab, or various tools include glTF export: -

        - - - -

        - If your preferred tools do not support glTF, consider requesting glTF - export from the authors, or posting on - the glTF roadmap thread. -

        - -

        - When glTF is not an option, popular formats such as FBX, OBJ, or COLLADA - are also available and regularly maintained. -

        - -

        Loading

        - -

        - Only a few loaders (e.g. [page:ObjectLoader]) are included by default with - three.js — others should be added to your app individually. -

        - - - import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; - - -

        - Once you've imported a loader, you're ready to add a model to your scene. Syntax varies among - different loaders — when using another format, check the examples and documentation for that - loader. For glTF, usage with global scripts would be: -

        - - - const loader = new GLTFLoader(); - - loader.load( 'path/to/model.glb', function ( gltf ) { - - scene.add( gltf.scene ); - - }, undefined, function ( error ) { - - console.error( error ); - - } ); - - -

        - See [page:GLTFLoader GLTFLoader documentation] for further details. -

        - -

        Troubleshooting

        - -

        - You've spent hours modeling an artisanal masterpiece, you load it into - the webpage, and — oh no! 😭 It's distorted, miscolored, or missing entirely. - Start with these troubleshooting steps: -

        - -
          -
        1. - Check the JavaScript console for errors, and make sure you've used an - `onError` callback when calling `.load()` to log the result. -
        2. -
        3. - View the model in another application. For glTF, drag-and-drop viewers - are available for - three.js and - babylon.js. If the model - appears correctly in one or more applications, - file a bug against three.js. - If the model cannot be shown in any application, we strongly encourage - filing a bug with the application used to create the model. -
        4. -
        5. - Try scaling the model up or down by a factor of 1000. Many models are - scaled differently, and large models may not appear if the camera is - inside the model. -
        6. -
        7. - Try to add and position a light source. The model may be hidden in the dark. -
        8. -
        9. - Look for failed texture requests in the network tab, like - `"C:\\Path\To\Model\texture.jpg"`. Use paths relative to your - model instead, such as `images/texture.jpg` — this may require - editing the model file in a text editor. -
        10. -
        - -

        Asking for help

        - -

        - If you've gone through the troubleshooting process above and your model - still isn't working, the right approach to asking for help will get you to - a solution faster. Post a question on the - three.js forum and, whenever possible, - include your model (or a simpler model with the same problem) in any formats - you have available. Include enough information for someone else to reproduce - the issue quickly — ideally, a live demo. -

        - - - - diff --git a/docs/manual/en/introduction/Matrix-transformations.html b/docs/manual/en/introduction/Matrix-transformations.html deleted file mode 100644 index bb0fbdcc4dbc8d..00000000000000 --- a/docs/manual/en/introduction/Matrix-transformations.html +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - -

        [name]

        - -

        - Three.js uses `matrices` to encode 3D transformations---translations (position), rotations, and scaling. Every instance of [page:Object3D] has a [page:Object3D.matrix matrix] which stores that object's position, rotation, and scale. This page describes how to update an object's transformation. -

        - -

        Convenience properties and `matrixAutoUpdate`

        - -

        - There are two ways to update an object's transformation: -

        -
          -
        1. - Modify the object's `position`, `quaternion`, and `scale` properties, and let three.js recompute - the object's matrix from these properties: - -object.position.copy( start_position ); -object.quaternion.copy( quaternion ); - - By default, the `matrixAutoUpdate` property is set true, and the matrix will be automatically recalculated. - If the object is static, or you wish to manually control when recalculation occurs, better performance can be obtained by setting the property false: - -object.matrixAutoUpdate = false; - - And after changing any properties, manually update the matrix: - -object.updateMatrix(); - -
        2. -
        3. - Modify the object's matrix directly. The [page:Matrix4] class has various methods for modifying the matrix: - -object.matrix.setRotationFromQuaternion( quaternion ); -object.matrix.setPosition( start_position ); -object.matrixAutoUpdate = false; - - Note that `matrixAutoUpdate` must be set to `false` in this case, and you should make sure not to call `updateMatrix`. Calling `updateMatrix` will clobber the manual changes made to the matrix, recalculating the matrix from `position`, `scale`, and so on. -
        4. -
        - -

        Object and world matrices

        -

        - An object's [page:Object3D.matrix matrix] stores the object's transformation relative to the object's [page:Object3D.parent parent]; to get the object's transformation in world coordinates, you must access the object's [page:Object3D.matrixWorld]. -

        -

        - When either the parent or the child object's transformation changes, you can request that the child object's [page:Object3D.matrixWorld matrixWorld] be updated by calling [page:Object3D.updateMatrixWorld updateMatrixWorld](). -

        - -

        Rotation and Quaternion

        -

        - Three.js provides two ways of representing 3D rotations: [page:Euler Euler angles] and [page:Quaternion Quaternions], as well as methods for converting between the two. Euler angles are subject to a problem called "gimbal lock," where certain configurations can lose a degree of freedom (preventing the object from being rotated about one axis). For this reason, object rotations are always stored in the object's [page:Object3D.quaternion quaternion]. -

        -

        - Previous versions of the library included a `useQuaternion` property which, when set to false, would cause the object's [page:Object3D.matrix matrix] to be calculated from an Euler angle. This practice is deprecated---instead, you should use the [page:Object3D.setRotationFromEuler setRotationFromEuler] method, which will update the quaternion. -

        - - - diff --git a/docs/manual/en/introduction/Useful-links.html b/docs/manual/en/introduction/Useful-links.html deleted file mode 100644 index c9821035198948..00000000000000 --- a/docs/manual/en/introduction/Useful-links.html +++ /dev/null @@ -1,177 +0,0 @@ - - - - - - - - - -

        [name]

        - -

        - The following is a collection of links that you might find useful when learning three.js.
        - If you find something that you'd like to add here, or think that one of the links below is no longer - relevant or working, feel free to click the 'edit' button in the bottom right and make some changes!

        - - Note also that as three.js is under rapid development, a lot of these links will contain information that is - out of date - if something isn't working as you'd expect or as one of these links says it should, - check the browser console for warnings or errors. Also check the relevant docs pages. -

        - -

        Help forums

        -

        - Three.js officially uses the [link:https://discourse.threejs.org/ forum] and [link:http://stackoverflow.com/tags/three.js/info Stack Overflow] for help requests. - If you need assistance with something, that's the place to go. Do NOT open an issue on Github for help requests. -

        - -

        Tutorials and courses

        - -

        Getting started with three.js

        -
          -
        • - [link:https://threejs.org/manual/#en/fundamentals Three.js Fundamentals starting lesson] -
        • -
        • - [link:https://codepen.io/rachsmith/post/beginning-with-3d-webgl-pt-1-the-scene Beginning with 3D WebGL] by [link:https://codepen.io/rachsmith/ Rachel Smith]. -
        • -
        • - [link:https://www.august.com.au/blog/animating-scenes-with-webgl-three-js/ Animating scenes with WebGL and three.js] -
        • -
        - -

        More extensive / advanced articles and courses

        -
          -
        • - [link:https://threejs-journey.com/ Three Journey] Course by [link:https://bruno-simon.com/ Bruno Simon] - Teaches beginners how to use Three.js step by step -
        • -
        • - [link:https://discoverthreejs.com/ Discover three.js] -
        • -
        • - [link:http://blog.cjgammon.com/ Collection of tutorials] by [link:http://www.cjgammon.com/ CJ Gammon]. -
        • -
        • - [link:https://medium.com/soffritti.pierfrancesco/glossy-spheres-in-three-js-bfd2785d4857 Glossy spheres in three.js]. -
        • -
        • - [link:https://www.udacity.com/course/cs291 Interactive 3D Graphics] - a free course on Udacity that teaches the fundamentals of 3D Graphics, - and uses three.js as its coding tool. -
        • -
        • - [Link:https://aerotwist.com/tutorials/ Aerotwist] tutorials by [link:https://github.com/paullewis/ Paul Lewis]. -
        • -
        • - [link:https://discourse.threejs.org/t/three-js-bookshelf/2468 Three.js Bookshelf] - Looking for more resources about three.js or computer graphics in general? - Check out the selection of literature recommended by the community. -
        • -
        - -

        News and Updates

        -
          -
        • - [link:https://twitter.com/hashtag/threejs Three.js on Twitter] -
        • -
        • - [link:http://www.reddit.com/r/threejs/ Three.js on reddit] -
        • -
        • - [link:http://www.reddit.com/r/webgl/ WebGL on reddit] -
        • -
        - -

        Examples

        -
          -
        • - [link:https://github.com/edwinwebb/three-seed/ three-seed] - three.js starter project with ES6 and Webpack -
        • -
        • - [link:http://stemkoski.github.io/Three.js/index.html Professor Stemkoskis Examples] - a collection of beginner friendly - examples built using three.js r60. -
        • -
        • - [link:https://threejs.org/examples/ Official three.js examples] - these examples are - maintained as part of the three.js repository, and always use the latest version of three.js. -
        • -
        • - [link:https://raw.githack.com/mrdoob/three.js/dev/examples/ Official three.js dev branch examples] - - Same as the above, except these use the dev branch of three.js, and are used to check that - everything is working as three.js being is developed. -
        • -
        - -

        Tools

        -
          -
        • - [link:https://github.com/tbensky/physgl physgl.org] - JavaScript front-end with wrappers to three.js, to bring WebGL - graphics to students learning physics and math. -
        • -
        • - [link:https://whsjs.readme.io/ Whitestorm.js] – Modular three.js framework with AmmoNext physics plugin. -
        • -
        • - [link:http://zz85.github.io/zz85-bookmarklets/threelabs.html Three.js Inspector] -
        • -
        • - [link:http://idflood.github.io/ThreeNodes.js/ ThreeNodes.js]. -
        • -
        • - [link:https://marketplace.visualstudio.com/items?itemName=slevesque.shader vscode shader] - Syntax highlighter for shader language. -
          - [link:https://marketplace.visualstudio.com/items?itemName=bierner.comment-tagged-templates vscode comment-tagged-templates] - Syntax highlighting for tagged template strings using comments to shader language, like: glsl.js. -
        • -
        • - [link:https://github.com/MozillaReality/WebXR-emulator-extension WebXR-emulator-extension] -
        • -
        - -

        WebGL References

        -
          -
        • - [link:https://www.khronos.org/files/webgl/webgl-reference-card-1_0.pdf webgl-reference-card.pdf] - Reference of all WebGL and GLSL keywords, terminology, syntax and definitions. -
        • -
        - -

        Old Links

        -

        - These links are kept for historical purposes - you may still find them useful, but be warned that - they may have information relating to very old versions of three.js. -

        - -
          -
        • - [link:https://www.youtube.com/watch?v=Dir4KO9RdhM AlterQualia at WebGL Camp 3] -
        • -
        • - [link:http://yomotsu.github.io/threejs-examples/ Yomotsus Examples] - a collection of examples using three.js r45. -
        • -
        • - [link:http://fhtr.org/BasicsOfThreeJS/#1 Introduction to Three.js] by [link:http://github.com/kig/ Ilmari Heikkinen] (slideshow). -
        • -
        • - [link:http://www.slideshare.net/yomotsu/webgl-and-threejs WebGL and Three.js] by [link:http://github.com/yomotsu Akihiro Oyamada] (slideshow). -
        • -
        • - [link:https://www.youtube.com/watch?v=VdQnOaolrPA Trigger Rally] by [link:https://github.com/jareiko jareiko] (video). -
        • -
        • - [link:http://blackjk3.github.io/threefab/ ThreeFab] - scene editor, maintained up until around three.js r50. -
        • -
        • - [link:http://bkcore.com/blog/3d/webgl-three-js-workflow-tips.html Max to Three.js workflow tips and tricks] by [link:https://github.com/BKcore BKcore] -
        • -
        • - [link:http://12devsofxmas.co.uk/2012/01/webgl-and-three-js/ A whirlwind look at Three.js] - by [link:http://github.com/nrocy Paul King] -
        • -
        • - [link:http://bkcore.com/blog/3d/webgl-three-js-animated-selective-glow.html Animated selective glow in Three.js] - by [link:https://github.com/BKcore BKcore] -
        • -
        • - [link:http://www.natural-science.or.jp/article/20120220155529.php Building A Physics Simulation Environment] - three.js tutorial in Japanese -
        • -
        - - - diff --git a/docs/manual/en/introduction/WebGL-compatibility-check.html b/docs/manual/en/introduction/WebGL-compatibility-check.html deleted file mode 100644 index a2c00fb88e4538..00000000000000 --- a/docs/manual/en/introduction/WebGL-compatibility-check.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - -

        [name]

        -

        - Even though this is becoming less and less of a problem, some devices or browsers may still not support WebGL. - The following method allows you to check if it is supported and display a message to the user if it is not. - Import the WebGL support detection module, and run the following before attempting to render anything. -

        - - - import WebGL from 'three/addons/capabilities/WebGL.js'; - - if ( WebGL.isWebGLAvailable() ) { - - // Initiate function or other initializations here - animate(); - - } else { - - const warning = WebGL.getWebGLErrorMessage(); - document.getElementById( 'container' ).appendChild( warning ); - - } - - - diff --git a/docs/manual/fr/introduction/Animation-system.html b/docs/manual/fr/introduction/Animation-system.html deleted file mode 100644 index babc395c6e71cc..00000000000000 --- a/docs/manual/fr/introduction/Animation-system.html +++ /dev/null @@ -1,147 +0,0 @@ - - - - - - - - - -

        Système d'animation ([name])

        - -

        Aperçu

        - -

        - Dans le système d'animation de three.js vous pouvez animer différentes propriétés de vos modèles: - les os d'un [page:SkinnedMesh skinned and rigged model], les morph targets, les différentes propriétés des matériaux - (couleurs, opacité, booléens), la visibilité et les transformations. Les propriétés animées peuvent avoir différentes animations comme un fade-in, - un fade-out, un fondu ou un warp. Le poids et l'échelle temporelle des différentes animations simultanées - sur le même objet peuvent également être changées indépendamment. - Différentes animations sur le même objet peuvent-être - synchronisées.

        - - Pour effectuer tout cela dans un système homogène, le système d'animation three.js - [link:https://github.com/mrdoob/three.js/issues/6881 a complètement changé en 2015] - (attention aux informations dépassées!), et a maintenant une architecture similaire à - Unity/Unreal Engine 4. Cette page offre un bref aperçu des principaux composants du système - et de comment ils fonctionnent ensemble. - -

        - -

        Animation Clips

        - -

        - - Si vous aveez réussi à importer un modèle 3D (peu importe qu'il ait - des os ou une morph targets ou les deux) — par exemple en l'exportant depuis Blender avec le - [link:https://github.com/KhronosGroup/glTF-Blender-IO glTF Blender exporter] et - en le chargeant dans la scène three.js en utilisant [page:GLTFLoader] — un des champs doit-être - un tableau nommé "animations", contenant les [page:AnimationClip AnimationClips] - pour ce modèle (voir une liste des loaders possibles ci-dessous).

        - - Chaque `AnimationClip` conserve les données d'une certaine activité d'un objet. Si le - mesh est un personnage, par exemple, il pourrait y avoir un AnimationClip pour une marche, un second - pour un saut, un troisième pour un pas de côté et ainsi de suite. - -

        - -

        Keyframe Tracks

        - -

        - - A l'intérieur d'un `AnimationClip` les données pour chaque propriétés animées sont stockées - dans un [page:KeyframeTrack] séparé. En considérant que l'objet personnage a un [page:Skeleton skeleton], - un keyframe track pourrait stocker les changements de valeur de la position de l'os inférieur d'un bras - à travers le temps, un track différent stockerait les changements de valeur de la rotation du même bras, un troisème - pourrait stocker la position, la rotation ou l'échelle d'un autre os, ainsi de suite. Il doit-être clair qu'un, - AnimationClip peut-être composé de beaucoup de ce genre de tracks.

        - - En considérant que le modèle a un morph targets (par exemple un morph - target pour un visage amical et un autre pour un visage énervé), chaque track conserve - l'information de comment l'[page:Mesh.morphTargetInfluences influence] d'un certain morph - change durant l'exécution du clip. - -

        - -

        Mixer d'Animations

        - -

        - - Les informations stockées représentent uniquement la base de l'animation - le playback est en réalité contrôlé par - l'[page:AnimationMixer]. Vous vous doutez bien que ce n'est pas uniquement un visualiseur d'animations, mais - une simulation d'un hardware comme une vraie console de mixage , qui peut contrôler plusieurs animations - simultanément, les mélangeant et les fusionnant. - -

        - -

        Actions d'Animations

        - -

        - - L'`AnimationMixer` en lui-même a seulement quelques propriétés (générales) et méthodes, car il - peut-être contrôlé par l'[page:AnimationAction AnimationActions]. En configurant un - `AnimationAction` vous pouvez déterminer qu'un certain `AnimationClip` doit-être joué, mis en pause - ou stoppé sur un des mixers, si et à quelle fréquence le clip doit-être répeté, si il - doit-être joué avec un fade, une mise à l'échelle temporelle, et d'autres choses, comme le fondu - ou la synchronisation. - -

        - -

        Animations de Groupes d'Objets

        - -

        - - Si vous voulez qu'un groupe d'objets reçoive un statut d'animation partagé, vous pouvez utiliser un - [page:AnimationObjectGroup]. - -

        - -

        Formats et loaders supportés

        - -

        - Notez que tous les formats de modèles n'incluent pas les animations (notamment OBJ ne les supporte pas), et que seulement certains - loaders three.js supportent les séquences d'[page:AnimationClip AnimationClip]. Plusieurs autres supportent - ce type d'animations: -

        - -
          -
        • [page:ObjectLoader THREE.ObjectLoader]
        • -
        • THREE.BVHLoader
        • -
        • THREE.ColladaLoader
        • -
        • THREE.FBXLoader
        • -
        • [page:GLTFLoader THREE.GLTFLoader]
        • -
        • THREE.MMDLoader
        • -
        - -

        - Notez que 3ds max et Maya ne peuvent actuellement pas exporter plusieurs animations (qui ne sont pas sur - la même timeline) directement dans un seul fichier. -

        - -

        Exemple

        - - - let mesh; - - // Create an AnimationMixer, and get the list of AnimationClip instances - const mixer = new THREE.AnimationMixer( mesh ); - const clips = mesh.animations; - - // Update the mixer on each frame - function update () { - mixer.update( deltaSeconds ); - } - - // Play a specific animation - const clip = THREE.AnimationClip.findByName( clips, 'dance' ); - const action = mixer.clipAction( clip ); - action.play(); - - // Play all animations - clips.forEach( function ( clip ) { - mixer.clipAction( clip ).play(); - } ); - - - - diff --git a/docs/manual/fr/introduction/Color-management.html b/docs/manual/fr/introduction/Color-management.html deleted file mode 100644 index eb4baf4b293be2..00000000000000 --- a/docs/manual/fr/introduction/Color-management.html +++ /dev/null @@ -1,333 +0,0 @@ - - - - - - - - - - - - -

        Gestion des couleurs ([name])

        - -

        Qu'est ce qu'un espace colorimétrique?

        - -

        - Chaque espace colorimétrique est un ensemble de plusieurs décisions de design, choisies ensemble pour supporter - un large éventail de couleurs tout en satisfaisant les contraites techniques liées à la précision et aux technologies - d'affichage. Lors de la création d'un asset 3D, ou l'assemblage d'assets 3D ensemble dans une scène, il est - important de savoir quelles sont ces propriétés, et comment les propriétés d'un espace colorimétrique se rapporte - aux autres espaces colorimétriques de la scène. -

        - -
        - -
        - Les couleurs sRGB et le point blanc (D65) affichées dans le modèle CIE 1931 chromaticity - diagram. Les régions colorées représentent une projection 2D de la gamme sRGB, qui est un - volume 3D. Source: Wikipedia -
        -
        - -
          -
        • - Couleurs primaires: Les couleurs primaires (e.g. rouge, vert, bleu) ne sont pas absolues; elle sont - sélectionnées depuis le spectre visible basé sur les contraintes de la précision limitée et - les capacités des appareils d'affichage disponibles. Les couleurs sont exprimées comme un ratio des couleurs primaires. -
        • -
        • - Point blanc: La plupart des espaces colorimétriques sont conçus de telle manière qu'une somme équivalente - de couleurs primaires R = G = B apparaissent comme n'ayant pas de couleurs, ou "achromatique". L'apparition - des valeurs chromatiques (comme le blanc ou le gris) dépend de la perception humaine, qui dépend elle-même - fortement du contexte d'observation. Un espace colorimétrique spécifie son "point blanc" pour équilibrer - ces besoins. Le point blanc définit par l'espace colorimétrique sRGB est - [link:https://en.wikipedia.org/wiki/Illuminant_D65 D65]. -
        • -
        • - Fonctions de transfert: Après avoir choisir la gamme de couleur et le modèle de couleur, il nous reste à toujours définir - le mapping ("fonctions de transfert") des valeurs numériques de l'espace colorimétrique. Est-ce-que r = 0.5 - représente 50% moins d'illumination physique que r = 1.0? Ou 50% de luminosité en moins, comme perçu - par l'oeil humain moyen? Ce sont différentes choses, et ces différences peuvent être représentées par - une fonction mathématique. Les fonctions de transfert peuvent être linéaires ou non-linéaires, selon - les objectifs de l'espace colorimétrique. Le sRGB définit des fonctions de transfert non-linéaires. Ces fonctions - fonctions sont parfois approximées en fonctions gamma, mais le terme "gamma" est - ambigu et doit-être évité dans ce contexte. -
        • -
        - - Ces trois paramètres — les couleurs primaires, le point blanc, et les fonctions de transfert — définissent un - espace colorimétrique, chacun est choisi pour un objectif particulier. Après avoir défini les paramètres, quelques termes supplémentaires - sont utiles: - -
          -
        • - Le modèle de couleur: La syntaxe pour identifier naturellement les couleurs au sein de la gamme de couleur choisie — - un système de coordonnées pour les couleurs. Dans three.js nous utilisons princpalement le système de couleurs RGB, - ayant trois coordonnées r, g, b ∈ [0,1] ("domaines fermés") ou - r, g, b ∈ [0,∞] ("domaine ouvert") chacune représentant une fraction d'une couleur - primaire. D'autres modèles de couleurs (HSL, Lab, LCH) sont communément utilisés pour un contrôle artistique. -
        • -
        • - La gamme de couleurs: Une fois que les couleurs primaires et le point blanc ont été choisis, ils représentent - un volume parmis le spectre visible (une "gamme"). Les couleurs qui ne sont pas dans ce volume ("hors de la gamme") - ne peuvent pas être exprimées par un domaine fermé [0,1] de valeurs RGB. Dans le domaine ouvert [0,∞], la gamme est - théoriquement infinie. -
        • -
        - -

        - Considérons deux espaces colorimétriques très communs: [page:SRGBColorSpace] ("sRGB") et - [page:LinearSRGBColorSpace] ("sRGB-Linéaire"). Les deux utilisent les mêmes primaires et point blanc, - et donc ont la même gamme de couleur. Le deux utilisent le modèle RGB. Leur seule différence sont - les fonctions de transfert — Le sRGB-Linéaire est linéaire et respecte l'intensité physique de la lumière. - Le sRGB utilise les fonctions de transfert non-linéaire du sRGB, et reproduit de manière plus proche la façon dont - l'oeil humain perçoit la lumière et la réactivité des écrans. -

        - -

        - Cette différence est imporante. Les calculs de lumières et les autres opérations de rendu doivent - généralement se produire dans un espace de lumière linéaire. Cependant, les espaces colorimétriques linéaires sont moins efficaces - dans le stockage d'images ou de framebuffer, et semblent incorrects qiuand ils sont vus par un humain. - Par conséquent, les textures d'entrée et l'image du rendu final vont généralement utiliser l'espace colorimétrique - sRGB non-linéaire. -

        - -
        -

        - ℹ️ NOTE: Alors que certains écrans modernes supportent des gammes plus larges comme Display-P3, - les APIs graphiques du web reposent largement sur le sRGB. Les applications utilisant three.js - aujourd'hui utilisent généralement uniquement le sRGB et les espaces colorimétriques sRGB-linéaires. -

        -
        - -

        Rôle des espaces colorimétriques

        - -

        - Workflows linéaires — requis pour les méthodes de rendu modernes — ils impliquent généralement - plus d'un espace de couleur, chacun assigné à un rôle particulier. Les espace colorimétriques linéaires et non-linéaires - sont appropriés pour différents usages, expliqués ci-dessous. -

        - -

        Espaces colorimétriques d'entrée

        - -

        - Les couleurs fournies à three.js — par les sélecteurs de couleurs, les textures, les modèles 3D, et d'autres sources — - ont toutes un espace colorimétrique associé. Celles qui ne sont pas déjà dans l'espace colorimétrique sRGB-Linéaire - doivent-être converties, et les textures doivent recevoir les bonnes consignes de texture.colorSpace. - Certaines conversions (pour l'héxadecimal et les couleurs CSS en sRGB) peuvent être automatisées si - l'héritage de la gestion des couleurs est désactivé avant l'initialisation des couleurs: -

        - - -THREE.ColorManagement.enabled = true; - - -
          -
        • - Matériaux, lumières, et shaders: Les couleurs des matériaux, lumières, et shaders stockent - des composantes RGB dans l'espace colorimétrique sRGB-Linéaire. -
        • -
        • - Vertex colors: [page:BufferAttribute BufferAttributes] stocke - des composantes RGB dans l'espace colorimétrique sRGB-Linéaire. -
        • -
        • - Textures colorées: PNG ou JPEG [page:Texture Textures] contiennent des informations de couleurs - (comme .map ou .emissiveMap) utilisant le domaine fermé de l'espace colorimétrique sRGB, et doivent être annotés avec - texture.colorSpace = SRGBColorSpace. Des formats comme OpenEXR (parfois utilisés par .envMap pi - .lightMap) utilisent l'espace colorimétrique sRGB-Linéaire indiqué par texture.colorSpace = LinearSRGBColorSpace, - et peuvent contenir des valeurs du domaine ouvert [0,∞]. -
        • -
        • - Textures non-colorées: Les textures qui ne stockent aucune information de couleur (comme .normalMap - ou .roughnessMap) n'ont pas d'espace colorimétrique associé, et utilisent généralement l'annotation de texture (par défaut) - texture.colorSpace = NoColorSpace. Dans de rares cas, les données ne concernant pas la couleur - peuvent être représentées par d'autres encodages non-linéaires pour des raisons techniques. -
        • -
        - -
        -

        - ⚠️ ATTENTION: Plusieurs formats de modèles 3D ne définissent par correctement ou de manière cohérente - les informations des espaces colorimétriques. Malgré le fait que three.js tente de gérer la plupart des situations, les problèmes - sont communs avec les formats de fichiers plus anciens. Pour de meilleurs résultats, utilisez glTF 2.0 ([page:GLTFLoader]) - et testez vos modèles 3D dans des visualiseurs en ligne relativement tôt pour vérifier que le modèle est correct en tant que tel. -

        -
        - -

        Espaces colorimétriques fonctionnels

        - -

        - Le rendu, l'interpolation, et plusieurs autres opérations doivent être performées dans un espace colorimétrique - au domaine ouvert, dans lequel les composantes RGB sont proportionnelles - à l'illumination physique. Dans three.js, l'espace colorimétrique est le sRGB-Linéaire. -

        - -

        L'espace colorimétrique de sortie

        - -

        - La sortie d'un écran, d'une image, ou d'une vidéo peut impliquer la conversion depuis un espace colorimétrique - sRGB-Linéaire au domaine ouvert vers un autre espace colorimétrique. Cette conversion peut être effectuée dans - le pass principal du moteur de rendu ([page:WebGLRenderer.outputColorSpace]), ou durant le post-processing. -

        - - -renderer.outputColorSpace = THREE.SRGBColorSpace; // optional with post-processing - - -
          -
        • - Affichage: Les couleurs envoyées à un canvas WebGL pour affichage doivent-être dans l'espace colorimétrique - sRGB. -
        • -
        • - Image: Les couleurs envoyées à une image doivent utiliser l'espace colorimétrique approprié au - format et à l'utilisation. Les images entièrement rendues sur des textures au format PNG ou JPEG - utilisent généralement l'espace colorimétrique sRGB. Les images contenant de l'émission, des light maps, ou d'autres données - qui ne sont pas restreintes à l'intervalle [0,1] utiliseront généralement l'espace colorimétrique sRGB à domaine ouvert, - et un format d'image compatible comme OpenEXR. -
        • -
        - -
        -

        - ⚠️ ATTENTION: Les cibles de rendu doivent utiliser soit le sRGB soit le sRGB-Linéaire. Le sRGB gère - mieux la précision limitée. Dans le domaine fermé, 8 bits suffisent généralement au sRGB - tandis que ≥12 bits (demi float) peuvent être requis pour du sRGB-Linéaire. Si les étapes ultérieures - du pipeline nécessitent une entrée en sRGB-Linéaire, les conversions additionnelles peuvent - avoir un petit impact sur les performances. -

        -
        - -

        - Custom materials based on [page:ShaderMaterial] and [page:RawShaderMaterial] have to implement their own output color space conversion. - For instances of `ShaderMaterial`, adding the `encodings_fragment` shader chunk to the fragment shader's `main()` function should be sufficient. -

        - -

        Utiliser des instances de THREE.Color

        - -

        - Les méthodes de lecture ou de modification des instances de [page:Color] partent du principe que les données sont déjà - dans l'espace colorimétrique de three.js, le sRGB-Linéaire. Les composantes RGB et HSL sont des représentations - directes de données stockées par l'instance Color, et ne sont jamais converties - implicitement. Les données Color peuvent être explicitement converties avec .convertLinearToSRGB() - ou .convertSRGBToLinear(). -

        - - - // RGB components (no change). - color.r = color.g = color.b = 0.5; - console.log( color.r ); // → 0.5 - - // Manual conversion. - color.r = 0.5; - color.convertSRGBToLinear(); - console.log( color.r ); // → 0.214041140 - - -

        - Avec ColorManagement.enabled = true d'activé (recommandé), certaines conversions - sont faites automatiquement. Parce que l'héxadécimal et les couleurs CSS sont généralement en sRGB, les méthodes [page:Color] - vont automatiquement convertir ces entrées du sRGB au sRGB-Linéaire dans des setters, ou - convertir depuis du sRGB-Linéaire au sRGB lors du renvoi de valeurs héxadécimales ou CSS depuis les getters. -

        - - - // Hexadecimal conversion. - color.setHex( 0x808080 ); - console.log( color.r ); // → 0.214041140 - console.log( color.getHex() ); // → 0x808080 - - // CSS conversion. - color.setStyle( 'rgb( 0.5, 0.5, 0.5 )' ); - console.log( color.r ); // → 0.214041140 - - // Override conversion with 'colorSpace' argument. - color.setHex( 0x808080, LinearSRGBColorSpace ); - console.log( color.r ); // → 0.5 - console.log( color.getHex( LinearSRGBColorSpace ) ); // → 0x808080 - console.log( color.getHex( SRGBColorSpace ) ); // → 0xBCBCBC - - -

        Erreurs communes

        - -

        - Quand une couleur ou une texture individuelle est mal configurée, elle apparaîtra plus lumineuse ou plus sombre - qu'attendu. Quand l'espace colorimétrique de sortie du moteur de rendu est mal configuré, la scène entière peut sembler - plus sombre (e.g. conversion manquante vers le sRGB) ou plus lumineuse (e.g. une double conversion vers le sRGB avec du - post-processing). Dans chaque cas le problème peut ne pas être uniforme, et simplement augmenter/diminuer - peut ne pas le résoudre. -

        - -

        - Un problème plus subtil peut se produire quand à la fois l'espace colorimétrique d'entrée et - l'espace colorimétrique de sortie sont incorrects — les niveaux de luminosité globaux peuvent être corrects, mais les couleurs peuvent changer - d'une manière inattendue sous différentes lumières, ou des ombres peuvent sembler plus abruptes et moins lisses - que prévu. Ces deux erreurs assemblées ne forment pas une réussite, et il est important que - l'espace colorimétrique soit linéaire ("scene referred") et que l'espace colorimétrique de sortie soit linéaire - ("display referred"). -

        - -

        Lectures additionnelles

        - - - - - - diff --git a/docs/manual/fr/introduction/Creating-a-scene.html b/docs/manual/fr/introduction/Creating-a-scene.html deleted file mode 100644 index 8548c4b31e1216..00000000000000 --- a/docs/manual/fr/introduction/Creating-a-scene.html +++ /dev/null @@ -1,163 +0,0 @@ - - - - - - - - - -

        Créer une scène ([name])

        - -

        L'objectif de cette section est d'effectuer une brève introduction à three.js. Nous commencerons par mettre en place une scène, avec un cube en rotation. Un exemple fonctionnel est fourni à la fin de la page au cas où vous seriez bloqués et que vous auriez besoin d'aide.

        - -

        Avant de commencer

        - -

        Avant de pouvoir utiliser three.js, vous aurez besoin d'un endroit pour l'afficher. Enregistrez le code HTML suivant dans un fichier sur votre ordinateur et ouvrez-le dans votre navigateur.

        - - - <!DOCTYPE html> - <html> - <head> - <meta charset="utf-8"> - <title>My first three.js app</title> - <style> - body { margin: 0; } - </style> - </head> - <body> - <script type="module"> - import * as THREE from 'https://unpkg.com/three/build/three.module.js'; - - // Our Javascript will go here. - </script> - </body> - </html> - - -

        C'est tout. Tout le code qui va suivre doit aller dans la balise <script>.

        - -

        Créer la scène

        - -

        Pour pouvoir afficher quelque chose avec three.js, nous avons besoin de trois choses: une scène, une caméra et un moteur de rendu, afin de pouvoir effectuer un rendu de la scène à travers la caméra.

        - - - const scene = new THREE.Scene(); - const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); - - const renderer = new THREE.WebGLRenderer(); - renderer.setSize( window.innerWidth, window.innerHeight ); - document.body.appendChild( renderer.domElement ); - - -

        Prenons un moment pour expliquer ce qu'il se passe. Nous avons maintenant mis en place la scène, notre caméra et le moteur de rendu.

        - -

        Il existe différentes caméras dans three.js. Pour l'instant, utilisons une `PerspectiveCamera`.

        - -

        Le premier attribut est le `field of view`. Le champ de vision (FOV) est l'étendue de la scène visible sur l'écran à un moment donné. La valeur est en degrés.

        - -

        Le second attribut est nommé `aspect ratio`. Vous devrez presque toujours utiliser la largeur de l'élément divisée par sa hauteur, ou vous aurez le même résultat que lorsque vous regardez un vieux film sur une télévision avec un écran large - l'image semble écrasée.

        - -

        Les deux attributs suivants sont le `near` et le `far` du plan de coupe. Les objets plus loins de la caméra que la valeur `far` ou plus proches que `near` ne seront pas rendus. Vous n'avez pas besoin de vous préoccuper de ça pour l'instant, mais vous devriez ajuster ces valeurs dans vos applications afin d'obtenir de meilleures performances.

        - -

        Ensuite vient le moteur de rendu. En plus d'instancier le moteur de rendu, nous avons aussi besoin de définir la taille à laquelle doit-être effectué le rendu de l'application. Il est recommandé d'utiliser la largeur et la hauteur de la zone qu'est censée occuper l'application - dans ce cas, la largeur et la hauteur de la fenêtre du navigateur. Pour les applications gourmandes en ressources, vous pouvez aussi donner à `setSize` des valeurs plus petites, comme `window.innerWidth/2` et `window.innerHeight/2`, qui permettra d'effectuer le rendu à un quart de sa taille initiale.

        - -

        Si vous souhaitez conserver la taille de votre application mais effectuer un rendu avec une résolution plus faible, vous pouvez le faire appelant `setSize` avec false comme `updateStyle` (le troisième argument). Par exemple, `setSize(window.innerWidth/2, window.innerHeight/2, false)` effectuera un rendu de votre application à demi-résolution, en considérant que votre <canvas> a 100% de largeur et de hauteur.

        - -

        Pour finir, nous ajoutons l'élement `renderer` à notre document HTML. C'est un élément <canvas> que le moteur de rendu utilise pour nous affficher la scène.

        - -

        "C'est sympa tout ça, mais où sont les cubes que tu nous avais promis?" Ajoutons-les maintenant.

        - - - const geometry = new THREE.BoxGeometry( 1, 1, 1 ); - const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); - const cube = new THREE.Mesh( geometry, material ); - scene.add( cube ); - - camera.position.z = 5; - - -

        Pour créer un cube, nous avons besoin d'une `BoxGeometry`. C'est un objet qui contient tous les points (`vertices`) et le remplissage (`faces`) du cube. Nous en verrons plus à ce propos dans le futur.

        - -

        En plus de la forme (geometry), nous avons besoin d'un matériau (material) pour le colorer. Three.js contient plusieurs matériaux, mais nous nous contenterons du `MeshBasicMaterial` pour l'instant. Tous les matériaux prennent un objet avec un ensemble de propriétés qui s'appliquent à eux. Pour rester dans la simplicité, ne renseignons qu'un attribut couleur avec la valeur `0x00ff00`, qui est du vert. Cela fonctionne de la même manière que les couleurs en CSS ou dans Photoshop (`hex colors`).

        - -

        La dernière chose dont nous avons besoin est un `Mesh`. Un mesh (maillage) est un objet qui prends une forme (geometry), et qui y applique un matériau (material), que nous pouvons ensuite insérer dans notre scène, et déplacer librement.

        - -

        Par défaut, quand nous appelons `scene.add()`, l'élément est ajouté aux coordonnées `(0,0,0)`. Cela causera la superposition du cube et de la caméra qui seront les uns à l'intérieur des autres. Pour éviter ça, nous devons simplement déplacer un peu la caméra.

        - -

        Faire un rendu de la scène

        - -

        Si vous avez copié le code du dessus dans le fichier HTML créé plus tôt, vous ne verrez rien. C'est parce que nous n'effectuons aucun rendu pour l'instant. Pour cela, nous avons besoin de ce que l'on appelle une `render or animate loop`.

        - - - function animate() { - requestAnimationFrame( animate ); - renderer.render( scene, camera ); - } - animate(); - - -

        Cela va créer une boucle qui va déclencher le moteur de rendu afin qu'il dessine la scène à chaque fois que l'écran est rafraîchi (sur un écran classique c'est 60 fois par secondes). Si l'écriture de jeux sur navigateur vous est étrangère, vous devez vous dire "Pourquoi nous ne créons pas de setInterval ?" C'est que - nous pourrions, mais `requestAnimationFrame` a plusieurs avantages. Le plus important est peut-être qu'il se met en pause lorsque l'utilisateur change d'onglet sur son navigateur, par conséquence, pas de perte de leur précieuse puissance de calcul ou de durée de vie de leur batterie.

        - -

        Animer le cube

        - -

        Si vous insérez tout le code au-dessus dans le fichier que vous avez créé avant que nous commencions, vous devriez voir un cube vert. Rendons tout ça un peu plus intéressant en le faisant tourner.

        - -

        Ajoutez le code suivant juste au dessus de l'appel `renderer.render` dans votre fonction `animate`:

        - - - cube.rotation.x += 0.01; - cube.rotation.y += 0.01; - - -

        Ceci sera exécuté à chaque frame (normalement 60 fois par secondes), et donnera au cube une belle animation de rotation. Pour résumer, tout ce que vous souhaitez déplacer ou changer pendant que l'application est en cours d'exécution doit passer par la boucle animate. Vous pouvez évidemment appeler d'autres fonctions depuis cet endroit, afin de ne pas finir avec une fonction `animate` de plusieurs centaines de lignes.

        - -

        Le résultat

        -

        Félicitations! Vous avez maintenant terminé votre première application three.js. C'est trivial, mais il faut bien commencer quelque part.

        - -

        Le code complet est disponible ci-dessous et ainsi que sous forme d'éditable [link:https://jsfiddle.net/0c1oqf38/ exemple live]. Amusez-vous avec pour avoir une meilleure idée de son fonctionnement.

        - - - <!DOCTYPE html> - <html> - <head> - <meta charset="utf-8"> - <title>My first three.js app</title> - <style> - body { margin: 0; } - </style> - </head> - <body> - <script type="module"> - import * as THREE from 'https://unpkg.com/three/build/three.module.js'; - - const scene = new THREE.Scene(); - const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); - - const renderer = new THREE.WebGLRenderer(); - renderer.setSize( window.innerWidth, window.innerHeight ); - document.body.appendChild( renderer.domElement ); - - const geometry = new THREE.BoxGeometry( 1, 1, 1 ); - const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); - const cube = new THREE.Mesh( geometry, material ); - scene.add( cube ); - - camera.position.z = 5; - - function animate() { - requestAnimationFrame( animate ); - - cube.rotation.x += 0.01; - cube.rotation.y += 0.01; - - renderer.render( scene, camera ); - } - - animate(); - </script> - </body> - </html> - - - diff --git a/docs/manual/fr/introduction/Creating-text.html b/docs/manual/fr/introduction/Creating-text.html deleted file mode 100644 index 65967cb8f4811f..00000000000000 --- a/docs/manual/fr/introduction/Creating-text.html +++ /dev/null @@ -1,142 +0,0 @@ - - - - - - - - - -

        Créer un texte ([name])

        -
        -

        - Parfois vous aurez besoin d'utiliser du texte dans votre application three.js - ici - sont présentées quelques façons de le faire. -

        -
        - -

        1. DOM + CSS

        -
        -

        - Utiliser du HTML est généralement la manière la plus simple et la plus rapide d'ajouter du texte. Ceci est la méthode - utilisée pour les overlays descriptifs de la plupart des exemples three.js. -

        -

        Vous pouvez ajouter du contenu à une

        - <div id="info">Description</div> - -

        - et utiliser CSS pour donner une position absolute située au-dessus de tout le reste du contenu grâce au - z-index plus particulièrement si vous utilisez three.js en plein-écran. -

        - - -#info { - position: absolute; - top: 10px; - width: 100%; - text-align: center; - z-index: 100; - display:block; -} - - -
        - - -

        2. Utiliser [page:CSS2DRenderer] ou [page:CSS3DRenderer]

        -
        -

        - Utilisez ces moteurs de rendu pour dessiner des textes de haute-qualité contenus dans l'élément DOM de vos scène three.js. - Cette approche est similaire à la 1. excepté qu'avec ces moteurs de rendu les éléments peuvent être intégrés plus précisément et dynamiquement à la scène. -

        -
        - - -

        3. Associer un texte au canvas et l'utiliser comme [page:Texture]

        -
        -

        Utilisez cette méthode si vous souhaitez dessiner du texte facilement sur un plane dans votre scène three.js.

        -
        - - -

        4. Créez un modèle dans votre application 3D préférée et exportez le dans three.js

        -
        -

        Utilisez cette méthode si vous préférez travailler avec vos applications 3D puis importer vos modèles dans three.js.

        -
        - - -

        5. Forme de texte procédurale

        -
        -

        - Si vous souhaitez travailler en three.js pur ou créer des formes de texte 3D procédurales et dynamiques, - vous pouvez créer un mesh qui aura pour geometry une instance de THREE.TextGeometry: -

        -

        - new THREE.TextGeometry( text, parameters ); -

        -

        - Pour que cela fonctionne, dans tous les cas, votre TextGeometry aura besoin d'une instance de THREE.Font - avec comme paramètre "font". - - Voir [page:TextGeometry] pour avoir plus d'informations sur comment cela peut-être mis en place, une description de chaque - paramètre accepté, et une liste des fonts JSON qui viennent avec la distribution THREE.js. -

        - -

        Exemples

        - -

        - [example:webgl_geometry_text WebGL / geometry / text]
        - [example:webgl_shadowmap WebGL / shadowmap] -

        - -

        - Si Typeface ne fonctionne pas, ou que vous souhaitez une font qui n'est pas ici, il y a un tutoriel - avec un script python pour blender qui vous permet d'exporter du texte dans au format JSON de Three.js: - [link:http://www.jaanga.com/2012/03/blender-to-threejs-create-3d-text-with.html] -

        - -
        - - -

        6. Fonts Bitmap

        -
        -

        - BMFonts (bitmap fonts) permet de transformer les lots de glyphs en une seule BufferGeometry. Le rendu BMFont - supporte les sauts-de-ligne, l'espacement des lettres, le crénage, les fonctions de distance signée avec - des dérivées, les fonctions de distance signée multi-channel, les fonts multi-texture, et bien plus. - Voir [link:https://github.com/felixmariotto/three-mesh-ui three-mesh-ui] ou [link:https://github.com/Jam3/three-bmfont-text three-bmfont-text]. -

        -

        - Les fonts de base sont disponibles dans des projets comme - [link:https://github.com/etiennepinchon/aframe-fonts A-Frame Fonts], ou vous pouvez créer la votre - depuis n'importe quelle font .TTF, en optimisant les performances en n'incluant que les character requis par le projet. -

        -

        - Quelques outils utiles: -

        -
          -
        • [link:http://msdf-bmfont.donmccurdy.com/ msdf-bmfont-web] (web-based)
        • -
        • [link:https://github.com/soimy/msdf-bmfont-xml msdf-bmfont-xml] (ligne de commande)
        • -
        • [link:https://github.com/libgdx/libgdx/wiki/Hiero hiero] (applciations desktop)
        • -
        -
        - - -

        7. Troika Text

        -
        -

        - Le package [link:https://www.npmjs.com/package/troika-three-text troika-three-text] effectue un rendu - de qualité et anti-alisé des textes, utilisant une technique similaire à BMFonts, mais fonctionne directement avec n'importe quel fichier de font .TTF - ou .WOFF pour que vous n'ayez pas à pré-générer une texture glyph hors-ligne. Il ajoute également des fonctionnalités - comme: -

        -
          -
        • Des effets comme les strokes, les ombres portées, et les courbures
        • -
        • La capacité d'appliquer n'importe quel matériau three.js, même un ShaderMaterial customisé
        • -
        • Le support des ligatures de fonts, des scripts pour les lettres jointes, et un layout bi-directionnel de droite-à-gauche
        • -
        • Une optimisation pour les grandes quantités de textes dynamiques, en réalisant la plupart du travail en dehors du thread principal dans un web worker
        • -
        -
        - - - - diff --git a/docs/manual/fr/introduction/Drawing-lines.html b/docs/manual/fr/introduction/Drawing-lines.html deleted file mode 100644 index 58de372f59298d..00000000000000 --- a/docs/manual/fr/introduction/Drawing-lines.html +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - - -

        Dessiner des lignes ([name])

        -
        -

        - Disons que vous voulez dessiner une ligne ou un cercle, pas un wireframe [page:Mesh]. - Premièrement nous devons préparer le [page:WebGLRenderer renderer], [page:Scene scene] et la caméra (voir la page Créer une scène). -

        - -

        Voici le code que nous allons utiliser:

        - -const renderer = new THREE.WebGLRenderer(); -renderer.setSize( window.innerWidth, window.innerHeight ); -document.body.appendChild( renderer.domElement ); - -const camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 500 ); -camera.position.set( 0, 0, 100 ); -camera.lookAt( 0, 0, 0 ); - -const scene = new THREE.Scene(); - -

        La prochaine chose que nous allons faire est définir un matériau (material). Pour les lignes, nous devons utiliser [page:LineBasicMaterial] ou [page:LineDashedMaterial].

        - -//create a blue LineBasicMaterial -const material = new THREE.LineBasicMaterial( { color: 0x0000ff } ); - - -

        - Après le matériau, nous devons utiliser une forme (geometry) avec quelques sommets: -

        - - -const points = []; -points.push( new THREE.Vector3( - 10, 0, 0 ) ); -points.push( new THREE.Vector3( 0, 10, 0 ) ); -points.push( new THREE.Vector3( 10, 0, 0 ) ); - -const geometry = new THREE.BufferGeometry().setFromPoints( points ); - - -

        Notez que les lignes sont tracées entre chaque paire consécutive de sommets, mais pas entre la première et la deuxième (la ligne n'est pas fermée).

        - -

        Maintenant que nous avons les points pour deux lignes et un matériau, nous pouvons les assembler pour former une ligne.

        - -const line = new THREE.Line( geometry, material ); - -

        Il ne manque qu'à l'ajouter à la scène et appeler [page:WebGLRenderer.render render].

        - - -scene.add( line ); -renderer.render( scene, camera ); - - -

        Vous devez maintenant voir une flèche pointant vers le haut, faite de deux lignes bleues.

        -
        - - diff --git a/docs/manual/fr/introduction/FAQ.html b/docs/manual/fr/introduction/FAQ.html deleted file mode 100644 index 04182d8a2ef412..00000000000000 --- a/docs/manual/fr/introduction/FAQ.html +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - -

        [name]

        - -

        Quel format de modèle 3D est le mieux supporté?

        -
        -

        - Le format recommandé pour l'import et l'export de modèles est glTF (GL Transmission Format). Parce que glTF est ciblé sur la rapidité du temps d'exécution, le format est compact et rapide à charger. -

        -

        - three.js fournit des loaders pour plusieurs autres formats populaires comme FBX, Collada ou OBJ. Néanmoins, vous devez toujours essayer d'établir un workflow basé sur glTF dans vos projets. Pour plus d'informations, voir [link:#manual/introduction/Loading-3D-models loading 3D models]. -

        -
        - -

        Pourquoi y a-t-il des tags meta viewport dans les exemples?

        -
        - <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> - -

        Ces tags contrôlent la taille du viewport et l'échelle pour les navigateurs mobiles (où le contenu de la page peut être rendu à des tailles différentes de celle du viewport visible).

        - -

        [link:https://developer.apple.com/library/content/documentation/AppleApplications/Reference/SafariWebContent/UsingtheViewport/UsingtheViewport.html Safari: Using the Viewport]

        - -

        [link:https://developer.mozilla.org/en-US/docs/Web/HTML/Viewport_meta_tag MDN: Using the viewport meta tag]

        -
        - -

        Comment conserver l'échelle de la scène au resize?

        -

        - Nous voulons que tous les objets, peu importe leur distance à la caméra, conservent leur taille, même si la fenêtre est redimensionnée. - - L'équation clé pour résoudre ce problème est la formule suivante, concernant la hauteur visible à une distance donnée: - - -visible_height = 2 * Math.tan( ( Math.PI / 180 ) * camera.fov / 2 ) * distance_from_camera; - - Si nous augmentons la hauteur de la fenêtre d'un certain pourcentage, alors nous souhaitons que la hauteur visible à toutes distances soit augmentée - du même pourcentage. - - Cela ne peut pas être fait en changeant la position de la camera. Il faut plutôt changer le champ de vision de celle-ci. - [link:http://jsfiddle.net/Q4Jpu/ Example]. -

        - -

        Pourquoi une partie de mon objet est invisible?

        -

        - Cela peut être causé par le face culling. Les faces ont une orientation qui décident quel côté est lequel. Et le culling retire la face arrière dans des circonstances normales. Pour voir si c'est bien votre problème, changez la propriété material side pour THREE.DoubleSide. - material.side = THREE.DoubleSide -

        - -

        Pourquoi three.js retourne quelques fois des valeurs étranges pour des inputs invalides?

        -

        - Pour des raisons de performances, three.js ne valide pas les inputs dans la plupart des cas. Il en va de la responsabilité de votre application de vérifier que tous les inputs sont valides. -

        - - diff --git a/docs/manual/fr/introduction/How-to-create-VR-content.html b/docs/manual/fr/introduction/How-to-create-VR-content.html deleted file mode 100644 index bad2a211ede43a..00000000000000 --- a/docs/manual/fr/introduction/How-to-create-VR-content.html +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - - - - - -

        Créer du contenu VR ([name])

        - -

        - Ce guide fournit une brève vue d'ensemble des composants basiques d'une application VR web - faite avec three.js. -

        - -

        Workflow

        - -

        - Premièrement, vous devez inclure [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/webxr/VRButton.js VRButton.js] - dans votre projet. -

        - - -import { VRButton } from 'three/addons/webxr/VRButton.js'; - - -

        - *VRButton.createButton()* fait deux choses importantes: Cela crée un bouton qui indique - la compatibilité VR. De plus, cela initie une session VR si l'utilisateur active le bouton. La seule chose que vous avez - à faire est d'ajouter la ligne de code suivante à votre application. -

        - - -document.body.appendChild( VRButton.createButton( renderer ) ); - - -

        - Ensuite, vous devez dire à votre instance de `WebGLRenderer` d'activer le rendu XR. -

        - - -renderer.xr.enabled = true; - - -

        - Finalement, vous n'avez plus qu'à ajuster votre boucle d'animation étant donné que nous ne pouvons pas utiliser notre fonction bien aimée - *window.requestAnimationFrame()*. Pour les projets VR nous utilisons [page:WebGLRenderer.setAnimationLoop setAnimationLoop]. - Le code minimal ressemble à cela: -

        - - -renderer.setAnimationLoop( function () { - - renderer.render( scene, camera ); - -} ); - - -

        Étapes Suivantes

        - -

        - Jetez un coup d'oeil à un des exemples officiels WebVR pour voir le workflow en action.

        - - [example:webxr_vr_ballshooter WebXR / VR / ballshooter]
        - [example:webxr_vr_cubes WebXR / VR / cubes]
        - [example:webxr_vr_dragging WebXR / VR / dragging]
        - [example:webxr_vr_paint WebXR / VR / paint]
        - [example:webxr_vr_panorama_depth WebXR / VR / panorama_depth]
        - [example:webxr_vr_panorama WebXR / VR / panorama]
        - [example:webxr_vr_rollercoaster WebXR / VR / rollercoaster]
        - [example:webxr_vr_sandbox WebXR / VR / sandbox]
        - [example:webxr_vr_sculpt WebXR / VR / sculpt]
        - [example:webxr_vr_video WebXR / VR / video] -

        - - - - diff --git a/docs/manual/fr/introduction/How-to-dispose-of-objects.html b/docs/manual/fr/introduction/How-to-dispose-of-objects.html deleted file mode 100644 index b57a78dd14b1b1..00000000000000 --- a/docs/manual/fr/introduction/How-to-dispose-of-objects.html +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - - - -

        Supprimer un objet ([name])

        - -

        - Un des aspects importants dans l'amélioration des performances et qui permet d'éviter les fuites de mémoire dans votre application est la suppression des entités non-utilisées de la librairie. - Dès que vous créez une instance d'un type *three.js*, vous allouez une certaine quantité de mémoire. Toutefois, *three.js* crée pour certains objets - comme les formes ou les matériaux, des entités WebGL associées comme des buffers ou des programmes de shaders qui sont nécessaires au rendu. Il est important de - souligner que ces objets ne sont pas automatiquement débarassés. Au contraire, l'application doit utiliser une API particulière pour libérer ce genre de ressources. - Ce guide vous fournit une brève vue d'ensemble du fonctionnement de cette API et des objets considérés comme pertinents dans ce contexte. -

        - -

        Formes

        - -

        - Une forme représente généralement des informations concernant des sommets présentés comme un ensemble d'attributs. *three.js* crée en interne un objet de type [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLBuffer WebGLBuffer] - pour chaque attribut. Ces entités sont supprimées uniquement si vous appelez [page:BufferGeometry.dispose](). Si une forme devient obsolète dans votre application, - exécutez cette méthode pour libérer toutes les ressources associées. -

        - -

        Matériaux

        - -

        - Un matériau définit comment un objet est rendu. *three.js* utilise les informations de la définition du matériau pour construire un programme de shader pour le rendu. - Les programmes de shader peuvent être supprimés seulement si leur matériau respectif est supprimé. Dans un souci de performances, *three.js* essaye de réutiliser des - programmes de shaders existants si possible. Donc un programme de shader est supprimé uniquement si tous les matériaux associés sont supprimés. Vous pouvez indiquer la suppression d'un matériau en - exécutant [page:Material.dispose](). -

        - -

        Textures

        - -

        - La suppression d'un matériau n'a aucun effet sur les textures. Elles sont gérées séparément étant donné qu'une seule texture peut-être utilisée par plusieurs matériaux au même moment. - Chaque fois que vous créez une instance de [page:Texture], three.js crée en interne une instance de [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLTexture WebGLTexture]. - Comme les buffers, cet objet ne peut-être supprimé qu'en appelant [page:Texture.dispose](). -

        - -

        - Si vous utilisez une `ImageBitmap` comme source de données pour une texture, vous devez appeler [link:https://developer.mozilla.org/en-US/docs/Web/API/ImageBitmap/close ImageBitmap.close]() au niveau de l'application pour libérer toutes les ressources côté processeur. - Un appel automatisé de `ImageBitmap.close()` dans [page:Texture.dispose]() n'est pas possible, étant donné que le bitmap devient inutilisable, et que le moteur n'a aucun moyen de savoir si l'image bitmap est utilisée ailleurs. -

        - -

        Cibles de rendu

        - -

        - Les objets de type [page:WebGLRenderTarget] n'allouent pas qu'une instance de [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLTexture WebGLTexture] mais également - [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLFramebuffer WebGLFramebuffer]s et [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderbuffer WebGLRenderbuffer]s - pour réaliser des rendus customisés. Ces objets ne sont désalloués qu'en exécutant [page:WebGLRenderTarget.dispose](). -

        - -

        Divers

        - -

        - Il y a d'autres classes du dossier example comme controls ou les effets de post processings qui fournissent la méthode `dispose()` pour retirer des event listeners internes - ou des cibles de rendu. En général, il est recommandé de jeter un coup d'oeil à l'API ou à la documentation d'une classe en cherchant un `dispose()`. Si il existe, vous devez l'utiliser pour faire votre petit ménage après utilisation. -

        - -

        FAQ

        - -

        Pourquoi *three.js* ne peut pas supprimer automatiquement les objets?

        - -

        - Cette question a été posée énormément de fois par la communauté, il est donc important de clarifier ce sujet. Le fait est que *three.js* ne connaît pas la durée de vie ou la portée des - entités comme les formes ou les matériaux créés par les utilisateurs. Il en va de la responsabilité de l'application. Par exemple, si un matériau n'est pas utilisé pour le rendu pour l'instant, - il peut être nécessaire pour le prochain frame. Donc si l'application décide qu'un certain objet peut-être supprimé, elle doit en notifier le moteur en appelant la méthode - `dispose()`. -

        - -

        Retirer un mesh de la scène supprime également sa forme et son matériau?

        - -

        - Non, vous devez explicitement supprimer la forme et le matériau via *dispose()*. Gardez à l'esprit que les formes et matériaux peuvent être partagés entre plusieurs objets 3D comme les meshes. -

        - -

        Est-ce que *three.js* fournit des informations à propos des objets en cache?

        - -

        - Oui. Il est possible d'interroger [page:WebGLRenderer.info], qui est une propriété spéciale du moteur de rendu avec une série d'informations et de statistiques à propos de l'usage graphique - et du proccessus de rendu. Entre autres, cela peut vous donner des informations comme le nombre de textures, de formes et de programmes de shaders stockés en interne. Si vous remarquez des problèmes de performance - dans votre application, une bonne idée serait de débugger cette propriété pour facilement identifier une fuite de mémoire. -

        - -

        Que se passe t-il quand on appelle `dispose()` sur une texture mais que l'image n'est pas encore chargée?

        - -

        - Des ressources internes sont allouées pour une texture uniquement si l'image est entièrement chargée. Si vous supprimez une texture avant que l'image soit chargée, - rien ne se produit. Aucune ressource n'était allouée il n'y a donc rien à libérer. -

        - -

        Que se passe t-il quand on appelle `dispose()` puis qu'on utilise l'objet plus tard?

        - -

        - Les ressources internes libérées sont réallouées par le moteur. Il n'y aura donc pas d'erreur d'exécution mais vous remarquerez probablement un impact négatif sur les performances au frame actuel, - particulièrement quand le programme de shaders doit-être compilé. -

        - -

        Comment gérer les objets *three.js* dans mon application? Quand dois-je supprimer les objets?

        - -

        - En général, il n'y a pas de recommandation universelle pour cette question. Savoir si l'utilisation de `dispose()` est appropriée dépend grandement des cas d'utilisation. Il est important de souligner qu'il - n'est pas nécessaire de toujours se débarasser des objets. Un bon exemple serait un jeu avec de multiple niveaux. Le bon moment pour supprimer les objets serait à un changement - de niveau. L'application devrait traverser les anciennes scènes et supprimer tous les matériaux, les formes et les textures obsolètes. Comme mentionné dans la section précédente, supprimer des - objets toujours utilisés ne produit pas d'erreur d'exécution. La pire chose qui pourrait se produire serait une baisse de la performance à un seul frame. -

        - -

        Exemples qui montrent l'utilisation de dispose()

        - -

        - [example:webgl_test_memory WebGL / test / memory]
        - [example:webgl_test_memory2 WebGL / test / memory2]
        -

        - - - - diff --git a/docs/manual/fr/introduction/How-to-update-things.html b/docs/manual/fr/introduction/How-to-update-things.html deleted file mode 100644 index 544e0074f2190b..00000000000000 --- a/docs/manual/fr/introduction/How-to-update-things.html +++ /dev/null @@ -1,220 +0,0 @@ - - - - - - - - - -

        Mettre les éléments à jour ([name])

        -
        -

        Tous les objets mettent par défaut automatiquement leur matrice à jour si ils ont été ajoutés dans la scène avec

        - -const object = new THREE.Object3D(); -scene.add( object ); - - ou si ils sont l'enfant d'un autre objet qui a été ajouté à la scène: - -const object1 = new THREE.Object3D(); -const object2 = new THREE.Object3D(); - -object1.add( object2 ); -scene.add( object1 ); //object1 and object2 will automatically update their matrices - -
        - -

        Cependant, si vous savez que l'objet va être statique, vous pouvez désactiver ce comportement et mettre la matrice à jour manuellement quand le besoin se présente.

        - - -object.matrixAutoUpdate = false; -object.updateMatrix(); - - -

        BufferGeometry

        -
        -

        - Les BufferGeometries stockent des informations comme (la position des sommets, l'indice des faces, les normales, les couleurs, - les UVs et tout autre attribut customisé) dans [page:BufferAttribute buffers] - c'est un, - [link:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays typed arrays]. - Ceci les rend généralement plus rapides que les Geometries standard, le contrecoup est qu'il est plus difficile de les - utiliser. -

        -

        - En ce qui concerne la mise à jour des BufferGeometries, la chose la plus importante à comprendre est que - vous ne pouvez pas changer la taille des buffers (c'est très coûteux, en réalité, c'est équivalent à créer une nouvelle forme). - Vous pouvez toutefois mettre à jour le contenu des buffers. -

        -

        - Ce qui signifie que si vous savez qu'un attribut de votre BufferGeometry va augmenter, disons le nombre de sommets, - vous devez pré-allouer un buffer assez grand pour contenir n'importe quel nouveau sommet qui pourra être créé. Évidemment, - cela signifie également qu'il y aura une taille maximale pour votre BufferGeometry - il n'y a - aucun moyen de créer une BufferGeometry qui peut être étendue à l'infini d'une manière efficiente. -

        -

        - Nous utiliserons l'exemple d'une ligne qui est étendue à chaque rendu. Nous allouerons de l'espace - dans le buffer pour 500 sommets mais nous n'en dessinerons que deux au début, en utilisant [page:BufferGeometry.drawRange]. -

        - -const MAX_POINTS = 500; - -// geometry -const geometry = new THREE.BufferGeometry(); - -// attributes -const positions = new Float32Array( MAX_POINTS * 3 ); // 3 vertices per point -geometry.setAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) ); - -// draw range -const drawCount = 2; // draw the first 2 points, only -geometry.setDrawRange( 0, drawCount ); - -// material -const material = new THREE.LineBasicMaterial( { color: 0xff0000 } ); - -// line -const line = new THREE.Line( geometry, material ); -scene.add( line ); - -

        - Ensuite nous ajouterons aléatoirement des points à l'aide d'un pattern comme: -

        - -const positionAttribute = line.geometry.getAttribute( 'position' ); - -let x = 0, y = 0, z = 0; - -for ( let i = 0; i < positionAttribute.count; i ++ ) { - - positionAttribute.setXYZ( i, x, y, z ); - - x += ( Math.random() - 0.5 ) * 30; - y += ( Math.random() - 0.5 ) * 30; - z += ( Math.random() - 0.5 ) * 30; - -} - -

        - If you want to change the number of points rendered after the first render, do this: -

        - -line.geometry.setDrawRange( 0, newValue ); - -

        - If you want to change the position data values after the first render, you need to - set the needsUpdate flag like so: -

        - -positionAttribute.needsUpdate = true; // required after the first render - - -

        - If you change the position data values after the initial render, you may need to recompute - bounding volumes so other features of the engine like view frustum culling or helpers properly work. -

        - -line.geometry.computeBoundingBox(); -line.geometry.computeBoundingSphere(); - - -

        - [link:https://jsfiddle.net/t4m85pLr/1/ Here is a fiddle] showing an animated line which you can adapt to your use case. -

        - -

        Examples

        - -

        - [example:webgl_custom_attributes WebGL / custom / attributes]
        - [example:webgl_buffergeometry_custom_attributes_particles WebGL / buffergeometry / custom / attributes / particles] -

        - -
        - -

        Materiaux

        -
        -

        Toutes les valeurs uniformes peuvent être changées librement(e.g. couleurs, textures, opacité, etc), les valeurs sont envoyées aux shaders à chaque frame.

        - -

        De plus, les paramètresen relation avec GLstate peuvent changer à tout moment (depthTest, blending, polygonOffset, etc).

        - -

        Les propriétés suivantes ne peuvent pas être changées facilement durant l'exécution (une fois que le matériau a été rendu au moins une fois):

        -
          -
        • numbers and types of uniforms
        • -
        • présence ou non de -
            -
          • texture
          • -
          • fog
          • -
          • vertex colors
          • -
          • morphing
          • -
          • shadow map
          • -
          • alpha test
          • -
          • transparent
          • -
          -
        • -
        - -

        Des changements dans ces propriétés exigent la création d'un nouveau programme de shader. Vous devrez définir

        - material.needsUpdate = true - -

        Gardez à l'esprit que cela risque d'être assez lent et de causer des saccades dans le framerate (particulièrement sur Windows, étant donné que la compilation de shader est plus lente dans DirectX que dans OpenGL).

        - -

        Pour des expériences pluis fluides vous pouvez émuler des changements dans ces fonctionnalités pour qu'elles contiennent un genre de valeurs "factices" comme zéro en intensité de lumières, des textures blanches ou zéro brouillard.

        - -

        Vous pouvez librement changer le matériau utilisé pour les différents morceaux de formes, mais pas comment un objet est divisé en morceaux (selon le matériau des faces).

        - -

        Si vous avez besoin d'avoir différentes configurations de matériaux durant l'exécution:

        -

        Si le nombre de matériaux / morceaux est petit, vous pouvez pré-diviser l'objet préalablement (e.g. cheveux / visage / corps / vêtements supérieurs / pantalon pour un humain, avant / côtés / toit / fenêtres / pneu / intérieur pour une voiture.).

        - -

        Si le nombre est grand (e.g. chaque face peut potentiellement être différente), songez à utiliser une autre solution, comme l'utilisation d'attributs / textures pour avoir une apparence différente par face.

        - -

        Exemples

        -

        - [example:webgl_materials_car WebGL / materials / car]
        - [example:webgl_postprocessing_dof WebGL / webgl_postprocessing / dof] -

        -
        - - -

        Textures

        -
        -

        Les images, canvas, vidéos et les données des textures doivent avoir le flag suivant si elles sont changées:

        - - texture.needsUpdate = true; - -

        La cible du rendu se met automatiquement à jour.

        - -

        Exemples

        -

        - [example:webgl_materials_video WebGL / materials / video]
        - [example:webgl_rtt WebGL / rtt] -

        - -
        - - -

        Caméras

        -
        -

        La position de la caméra et sa cible sont automatiquement mises à jour. Si vous devez changer

        -
          -
        • - fov -
        • -
        • - aspect -
        • -
        • - near -
        • -
        • - far -
        • -
        -

        - alors vous aurez besoin de recalculer la matrice de projection: -

        - -camera.aspect = window.innerWidth / window.innerHeight; -camera.updateProjectionMatrix(); - -
        - - diff --git a/docs/manual/fr/introduction/How-to-use-post-processing.html b/docs/manual/fr/introduction/How-to-use-post-processing.html deleted file mode 100644 index e6619029e8b289..00000000000000 --- a/docs/manual/fr/introduction/How-to-use-post-processing.html +++ /dev/null @@ -1,111 +0,0 @@ - - - - - - - - - -

        Utiliser le post-processing ([name])

        - -

        - Plusieurs applications three.js effectuent un rendu de leurs objets 3D directement dans la scène. Parfois, vous souhaitez appliquer un ou plusieurs - effects graphiques comme la profondeur de champ, le flou lumineux, du grain, ou différents types d'Anti-aliasing. Le post-processing est une approche très utilisée - pour implémenter de tels effets. Premièrement, la scène est rendue dans une cible de rendu qui représente un buffer dans la mémoire de la carte vidéo. - A la prochaine étape un ou plusieurs effets de post-processing appliquent des filtres au buffer de l'image qui est finalement rendue à - l'écran. -

        -

        - three.js fournit une solution complète de post-processing via [page:EffectComposer] pour implémenter un tel workflow. -

        - -

        Workflow

        - -

        - La première étape est d'importer tous les fichiers nécessaires du dossier exemple. Le guide part du principe que vous utilisez le - [link:https://www.npmjs.com/package/three npm package] officiel de three.js. Pour notre démo basique, nous avons besoin des fichiers suivants. -

        - - - import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'; - import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'; - import { GlitchPass } from 'three/addons/postprocessing/GlitchPass.js'; - - -

        - Après avoir importé tous les fichiers correctement, nous pouvons créer notre composer en lui passant une instance de [page:WebGLRenderer]. -

        - - - const composer = new EffectComposer( renderer ); - - -

        - Lors de l'utilisation d'un composer, il est nécessaire de changer la boucle d'animation de l'application. Au lieu d'appeler la méthode de rendu - [page:WebGLRenderer], nous devons utiliser appeler [page:EffectComposer]. -

        - - - function animate() { - - requestAnimationFrame( animate ); - - composer.render(); - - } - - -

        - Notre composer est maintenant prêt, il est donc possible de configurer la chaîne d'effets de post-processing. Ces effets (passes) sont chargés de la création - de l'apparence visuelle finale de l'application. Ils sont traités dans l'ordre de leur ajout/insertion. Dans notre example, l'instance de `RenderPass` - est exécutée en première, puis l'instance de `GlitchPass` est exécutée. Le dernier effet activé de la chaîne est automatiquement rendu dans la scène. Le setup - des effets ressemble à ça: -

        - - - const renderPass = new RenderPass( scene, camera ); - composer.addPass( renderPass ); - - const glitchPass = new GlitchPass(); - composer.addPass( glitchPass ); - - -

        - `RenderPass` est normalement placé au début de la chaîne pour fournir la scène rendue en tant qu'entrée pour les prochaines étapes de post-processing. Dans notre cas, - `GlitchPass` va utiliser les données de l'image pour appliquer un effet de glitch. Regardez cet [link:https://threejs.org/examples/webgl_postprocessing_glitch exemple live] - pour voir cela en action. -

        - -

        Effets Intégrés

        - -

        - Vous pouvez utiliser une large palette d'effets de post-processing pré-définis fournis par le moteur. Ils se trouvent dans le dossier - [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm/postprocessing postprocessing]. -

        - -

        Effets Customisés

        - -

        - Parfois vous voulez écrire un shader de post-processing customisé et l'inclure dans les effets (passes) de post-processing. Dans ce scénario, - vous pouvez utiliser `ShaderPass`. Après avoir importé le fichier et votre shader customisé, vous pouvez utiliser le code suivant pour mettre en place l'effet (pass). -

        - - - import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js'; - import { LuminosityShader } from 'three/addons/shaders/LuminosityShader.js'; - - // later in your init routine - - const luminosityPass = new ShaderPass( LuminosityShader ); - composer.addPass( luminosityPass ); - - -

        - Ce repository fournit un fichier appelé [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/shaders/CopyShader.js CopyShader] qui est - une bonne base de code pour créer votre propose shader customisé. `CopyShader` copie simplement le contenu de l'image du buffer de l'[page:EffectComposer] - à son buffer d'écriture sans y appliquer aucun effet. -

        - - - diff --git a/docs/manual/fr/introduction/Installation.html b/docs/manual/fr/introduction/Installation.html deleted file mode 100644 index 9796ba6943e428..00000000000000 --- a/docs/manual/fr/introduction/Installation.html +++ /dev/null @@ -1,160 +0,0 @@ - - - - - - - - - -

        [name]

        - -

        - Vous pouvez installer three.js avec [link:https://www.npmjs.com/ npm] et d'autres outils de build modernes, ou commencer rapidement avec just un hébergement static ou un CDN. Pour la plupart des utilisateurs, installer depuis npm est le meilleur choix. -

        - -

        - Peu importe votre choix, soyez cohérents et importez tous vos fichiers avec la même version de la librairie. Mélanger des fichiers de différentes sources peut causer une duplication de certaines parties de code, ou même casser l'application d'une manière imprédictible. -

        - -

        - Toutes les méthodes d'installation de three.js dépendent des Modules ES (voir [link:https://eloquentjavascript.net/10_modules.html#h_hF2FmOVxw7 Eloquent JavaScript: ECMAScript Modules]), ce qui vous permet d'inclure uniquement les parties requises de la librairie dans le projet final. -

        - -

        Installer depuis npm

        - -

        - Pour installer le module npm [link:https://www.npmjs.com/package/three three], ouvrez une fenêtre de terminal dans le dossier de votre projet et lancez la commande suivante: -

        - - - npm install three - - -

        - Le package sera téléchargé et installé. Puis vous pouvez l'importer dans votre projet: -

        - - - // Option 1: Import the entire three.js core library. - import * as THREE from 'three'; - - const scene = new THREE.Scene(); - - - // Option 2: Import just the parts you need. - import { Scene } from 'three'; - - const scene = new Scene(); - - -

        - En l'installant depuis npm, vous utiliserez quasiment toujours une sorte de [link:https://eloquentjavascript.net/10_modules.html#h_zWTXAU93DC bundling tool] pour combiner tous les packages dont votre projet a besoin en un seul fichier JavaScript. N'importe quel bundler JavaScript modern peut être utilisé avec three.js, le choix le plus populaire est [link:https://webpack.js.org/ webpack]. -

        - -

        - Toutes les fonctionnalités ne sont pas directement accédées depuis le module three (également appelé "bare import"). D'autres parties populaires de la librairie — comme les contrôles, les loaders, et les effets de post-processing — doivent être importés depuis le sous-dossier [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm examples/jsm]. Pour en apprendre plus, consulter Examples ci-dessous. -

        - -

        - Apprenez-en plus à propos des modules npm depuis [link:https://eloquentjavascript.net/20_node.html#h_J6hW/SmL/a Eloquent JavaScript: Installation avec npm]. -

        - -

        Installer depuis un CDN ou un hébergement statique

        - -

        - La librairie three.js peut être utilisée sans aucun build system, soit en uploadant les fichiers sur votre propre server web ou en utilisant un CDN existant. Parce que la librairie repose sur les modules ES, n'importe quel script qui y fait référence doit utiliser le type="module" comme montré ci-dessous. - Il est également requis de définir une Import Map qui effectue la résolution du bare module specifier `three`. -

        - - - <script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script> - - <script type="importmap"> - { - "imports": { - "three": "https://unpkg.com/three@<version>/build/three.module.js" - } - } - </script> - - <script type="module"> - - import * as THREE from 'three'; - - const scene = new THREE.Scene(); - - </script> - - -

        - Étant donné que les Import maps ne sont pas encore supportées par tous les navigateurs, il est nécessaire d'ajouter le polyfill *es-module-shims.js*. -

        - -

        Addons

        - -

        - Le noyau de three.js est concentré sur les composants les plus importans d'un moteur 3D. Plusieurs autres composants utiles - comme les contrôles, les loaders, et les effets de post-processing - font partie du dossier [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm examples/jsm]. Ils sont désignés comme "exemples", parce que, en plus de pouvoir les utiliser directement tel qu'ils sont, ils sont aussi supposés être remixés et customisés. Ces composants sont toujours synchronisés avec le noyau de la librarie, là où des packages tiers similaires sur npm sont maintenus par différentes personnes et peuvent ne pas être à jour. -

        - -

        - Les addons n'ont pas besoin d'être installés séparément, mais doivent être importés séparément. Si three.js a été installé avec npm, vous pouvez charger le composant [page:OrbitControls] avec: -

        - - - - import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; - - const controls = new OrbitControls( camera, renderer.domElement ); - - -

        - Si three.js a été installé depuis un CDN, utilisez le même CDN pour installer d'autres composants: -

        - - - <script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script> - - <script type="importmap"> - { - "imports": { - "three": "https://unpkg.com/three@<version>/build/three.module.js", - "three/addons/": "https://unpkg.com/three@<version>/examples/jsm/" - } - } - </script> - - <script type="module"> - - import * as THREE from 'three'; - import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; - - const controls = new OrbitControls( camera, renderer.domElement ); - - </script> - - -

        - Il est important que tous les fichiers utilisent la même version. N'importez pas différents addons de différentes versions, ou n'utilisez pas d'addons d'une version de three.js différente de celle de la librarie elle-même. -

        - -

        Compatibilité

        - -

        Imports CommonJS

        - -

        - Alors que la plupart des bundlers JavaScript modernes supportent les modules ES par défaut, cela peut ne pas être le cas de certains build tools plus anciens. Dans ce cas, vous pouvez probablement configurer le bundler pour qu'il comprenne les modules ES: [link:http://browserify.org/ Browserify] a just besoin du plugin [link:https://github.com/babel/babelify babelify], par exemple. -

        - -

        Node.js

        - -

        - Parce que three.js est construit pour le web, il dépend de navigateurs et d'APIs DOM qui n'existent pas toujours dans Node.js. Certains de ces problèmes peuvent être résolus en utilisant des morceaux de code comme [link:https://github.com/stackgl/headless-gl headless-gl], ou en remplaçant des composents comme [page:TextureLoader] avec des alternatives customisées. D'autres APIs DOM peuvent être profondément entrelacées avec le code qui les utilises, et il sera compliqué de le modifier. Nous soutenons les pull requests simples et maintenables pour améliorer le support de Node.js, mais recommendons d'ouvrir une issue pour parler de vos améliorations avant. -

        - -

        - Faites attention à bien ajouter `{ "type": "module" }` à votre `package.json` pour autoriser les modules ES6 dans votre projet Node. -

        - - - diff --git a/docs/manual/fr/introduction/Libraries-and-Plugins.html b/docs/manual/fr/introduction/Libraries-and-Plugins.html deleted file mode 100644 index 3da25d2a07e789..00000000000000 --- a/docs/manual/fr/introduction/Libraries-and-Plugins.html +++ /dev/null @@ -1,109 +0,0 @@ - - - - - - - - - -

        Librairies et Plugins ([name])

        - -

        - Ici sont listés des plugins et librairies développés en externe et compatibles avec three.js. Cette - liste et les packages associés sont maintenus par la communauté et ne sont pas forcément - à jour. Si vous souhaitez mettre à jour cette liste, faites une PR! -

        - -

        Physique

        - -
          -
        • [link:https://github.com/lo-th/Oimo.js/ Oimo.js]
        • -
        • [link:https://enable3d.io/ enable3d]
        • -
        • [link:https://github.com/kripken/ammo.js/ ammo.js]
        • -
        • [link:https://github.com/pmndrs/cannon-es cannon-es]
        • -
        - -

        Postprocessing

        - -

        - En plus de [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm/postprocessing official three.js postprocessing effects], - du support pour des effets et frameworks additionels sont disponibles à travers des librairies externes. -

        - -
          -
        • [link:https://github.com/vanruesc/postprocessing postprocessing]
        • -
        - -

        Intersections et Performance de Raycast

        - -
          -
        • [link:https://github.com/gkjohnson/three-mesh-bvh three-mesh-bvh]
        • -
        - -

        Path Tracing

        - -
          -
        • [link:https://github.com/gkjohnson/three-gpu-pathtracer three-gpu-pathtracer]
        • -
        - -

        Formats de fichiers

        - -

        - En plus de [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm/loaders official three.js loaders], - du support pour des formats additionels sont disponibles à travers des librairies externes. -

        - -
          -
        • [link:https://github.com/gkjohnson/urdf-loaders/tree/master/javascript urdf-loader]
        • -
        • [link:https://github.com/NASA-AMMOS/3DTilesRendererJS 3d-tiles-renderer-js]
        • -
        • [link:https://github.com/kaisalmen/WWOBJLoader WebWorker OBJLoader]
        • -
        • [link:https://github.com/IFCjs/web-ifc-three IFC.js]
        • -
        - -

        Géometrie

        - -
          -
        • [link:https://github.com/spite/THREE.MeshLine THREE.MeshLine]
        • -
        - -

        Texte 3D et Layout

        - -
          -
        • [link:https://github.com/protectwise/troika/tree/master/packages/troika-three-text troika-three-text]
        • -
        • [link:https://github.com/felixmariotto/three-mesh-ui three-mesh-ui]
        • -
        - -

        Systèmes de particules

        - -
          -
        • [link:https://github.com/Alchemist0823/three.quarks three.quarks]
        • -
        • [link:https://github.com/creativelifeform/three-nebula three-nebula]
        • -
        - -

        Cinématique inverse

        - -
          -
        • [link:https://github.com/jsantell/THREE.IK THREE.IK]
        • -
        • [link:https://github.com/lo-th/fullik fullik]
        • -
        • [link:https://github.com/gkjohnson/closed-chain-ik-js closed-chain-ik]
        • -
        - -

        Jeu IA

        - -
          -
        • [link:https://mugen87.github.io/yuka/ yuka]
        • -
        • [link:https://github.com/donmccurdy/three-pathfinding three-pathfinding]
        • -
        - -

        Wrappers et Frameworks

        - -
          -
        • [link:https://aframe.io/ A-Frame]
        • -
        • [link:https://github.com/pmndrs/react-three-fiber react-three-fiber]
        • -
        • [link:https://github.com/ecsyjs/ecsy-three ECSY]
        • -
        • [link:https://threlte.xyz/ Threlte]
        • -
        - - - diff --git a/docs/manual/fr/introduction/Loading-3D-models.html b/docs/manual/fr/introduction/Loading-3D-models.html deleted file mode 100644 index 09f99b9f46683d..00000000000000 --- a/docs/manual/fr/introduction/Loading-3D-models.html +++ /dev/null @@ -1,167 +0,0 @@ - - - - - - - - - - - -

        Importer des modèles 3D ([name])

        - -

        - Les modèles 3D sont disponibles dans des centaines de formats, chacun ayant des objectifs - différents, des fonctionnalités assorties, et une complexité variable. Même si - - three.js fournit plusieurs loaders, choisir le bon format et - workflow vous fera gagner du temps et vous épargnera beaucoup de frustration par la suite. Certains formats sont - difficiles à appréhender, inefficaces pour les exépriences en temps-réel, ou simplement - pas entièrement supportés pour le moment. -

        - -

        - Ce guide vous fournit un workflow recommandé pour la plupart des utilisateurs, et des suggestions - concernant quoi essayer si les choses ne se déroulent pas comme prévu. -

        - -

        Avant de commencer

        - -

        - Si vous n'êtes pas familier avec le fait de lancer un serveur local, commencez par - [link:#manual/introduction/How-to-run-things-locally how to run things locally]. - Plusieurs erreurs communes concernant les modèles 3D peuvent-être évitées en hébergeant les fichiers - correctement. -

        - -

        Workflow recommandé

        - -

        - Dans la mesure du possible, nous recommandons l'utilisation de glTF (GL Transmission Format). Les versions - .GLB et .GLTF du format sont - bien supportées. Étant-donné que glTF se concentre sur la réduction du temps d'exécution du chargement des modèles, il est - compact et rapide à transmettre. Les fonctionnalités inclusent sont les meshes, les matériaux, - les textures, les skins, les squelettes, les morph targets, les animations, les lumières, et les - caméras. -

        - -

        - Les fichiers glTF appartenant au domaine public sont disponibles sur des sites comme - - Sketchfab, différents outils incluent l'export de glTF: -

        - - - -

        - Si votre outil de prédilection n'inclut pas le support des glTF, pensez à demander - aux auteurs d'inclure l'export des glTF, ou postez sur - the glTF roadmap thread. -

        - -

        - Quand glTF n'est pas utilisable, des formats populaires comme FBX, OBJ, ou COLLADA - sont également disponibles et régulièrement maintenus. -

        - -

        Charger les modèles

        - -

        - Seulement quelques loaders (e.g. [page:ObjectLoader]) sont inclus par défaut dans - three.js — les autres doivent être ajoutés individuellement à votre application. -

        - - - import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; - - -

        - Une fois que vous avez importé un loader, vous pouvez ajouter un modèle à votre scène. La syntaxe varie selon - les loaders — quand vous utilisez un autre format, jetez un oeil à la documentation de ce - loader. Pour glTF, l'utilisation avec des scripts globaux doit-être: -

        - - - const loader = new GLTFLoader(); - - loader.load( 'path/to/model.glb', function ( gltf ) { - - scene.add( gltf.scene ); - - }, undefined, function ( error ) { - - console.error( error ); - - } ); - - -

        - Voir [page:GLTFLoader GLTFLoader documentation] pour plus de détails. -

        - -

        Dépannage

        - -

        - Vous avez passé des heures à modeler votre chef-d'oeuvre artisanal, vous le chargez sur - la page web, et — oh non! 😭 Il est tordu, mal coloré, ou tout simplement porté-disparu. - Commencez par ces étapes de dépannage: -

        - -
          -
        1. - Vérifiez la console JavaScript à la recherche d'erreurs, et assurez-vous d'utiliser un callback - `onError` à l'appel de `.load()` pour afficher le résultat. -
        2. -
        3. - Visualisez le modèle dans une autre application. Pour glTF, des visualiseurs de type cliquez-glissez - sont disponibles pour - three.js et - babylon.js. Si le modèle - apparaît correctement dans une ou plusieurs autres applications, - signalez une erreur auprès de three.js. - Si le modèle ne peut être visualisé dans n'importe quelle application, nous encourageons fortement - le signalement d'un bug auprès de l'application avec laquelle vous avez réalisé le modèle 3D. -
        4. -
        5. - Essayez de divisier ou de multiplier la taille du modèle par un facteur de 1000. Plusieurs modèles sont mis à - l'échelles différemment, et les gros modèles peuvent ne pas apparaître si la caméra est - à l'intérieur du modèle. -
        6. -
        7. - Essayez d'ajouter et de positionner une source de lumière. Le modèle peut-être caché dans le noir. -
        8. -
        9. - Cherchez des requêtes concernant des textures erronnées dans votre onglet réseau, comme - `"C:\\Path\To\Model\texture.jpg"`. Utilisez des chemins relatifs menant à votre - modèle à la place, comme `images/texture.jpg` — cela peut nécessiter - la modification du fichier du modèle dans un éditeur de texte. -
        10. -
        - -

        Demander de l'aide

        - -

        - Si vous avez effectué le processus de dépannage ci-dessus et que votre modèle - ne fonctionne toujours pas, utiliser la bonne approche pour demander de l'aide vous mènera - plus rapidement à la solution. Postez une question sur le - forum three.js et, incluez dès que possible, - votre modèle (ou un modèle plus simple avec le même problème) dans n'importe quel format - qui vous est disponible. Incluez sufisamment d'informations pour que quelqu'un puisse reproduire - ce problème rapidement — idéalement, une démo live. -

        - - - - diff --git a/docs/manual/fr/introduction/Matrix-transformations.html b/docs/manual/fr/introduction/Matrix-transformations.html deleted file mode 100644 index 547d02adb86c77..00000000000000 --- a/docs/manual/fr/introduction/Matrix-transformations.html +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - -

        Matrices de transformation ([name])

        - -

        - Three.js utilise des `matrices` pour encoder des transformations 3D---translations (position), rotations, et mise à l'échelle. Chaque instance d'un [page:Object3D] a une [page:Object3D.matrix matrix] qui stocke la position de l'objet, sa rotation, ainsi que son échelle. Cette page décrit comment mettre à jour les transformations d'un objet. -

        - -

        Propriétés de commodité et `matrixAutoUpdate`

        - -

        - Il y a deux façons de mettre à jour les transformations d'un objet: -

        -
          -
        1. - Modifier les propriétés `position`, `quaternion`, et `scale` de l'objet, et laissez three.js recalculer - la matrice de l'objet à l'aide de ces propriétés: - -object.position.copy( start_position ); -object.quaternion.copy( quaternion ); - - Par défaut, la propriété `matrixAutoUpdate` est à true, et la matrice sera automatiquement recalculée. - Si l'objet est statique, ou si vous souhaitez contrôler manuellement quand le recalcul de la matrice intervient, de meilleur performances peuvent-être obtenues en définissant la propriété comme false: - -object.matrixAutoUpdate = false; - - Et après avoir changé n'importe quelle propriété, mettez manuellement la matrice à jour: - -object.updateMatrix(); - -
        2. -
        3. - Modifier la matrice de l'objet directement. La classe [page:Matrix4] dipose de différentes méthodes pour modifier les matrices: - -object.matrix.setRotationFromQuaternion( quaternion ); -object.matrix.setPosition( start_position ); -object.matrixAutoUpdate = false; - - Notez que `matrixAutoUpdate` doit être définie comme `false` dans ce cas, et vous devez vérifier que vous n'appelez pas `updateMatrix`. Appeler `updateMatrix` écrasera les modifications manuelles apportées à la matrice, en recalculant la matrice grâce à sa `position`, `scale`, ainsi de suite. -
        4. -
        - -

        Matrices d'objets et du monde

        -

        - La [page:Object3D.matrix matrix] d'un objet stocke les transformations relatives au [page:Object3D.parent parent] de l'objet; pour obtenir les transformations de l'objets en coordonnées du monde, vous devez accéder à la [page:Object3D.matrixWorld] de l'objet. -

        -

        - Quand les transformations de l'objet parent ou enfant changent, vous pouvez demander que la [page:Object3D.matrixWorld matrixWorld] de l'objet enfant soit mise à jour en appelant [page:Object3D.updateMatrixWorld updateMatrixWorld](). -

        - -

        Rotation et Quaternions

        -

        - Three.js propose deux manières de représenter les rotations 3D: [page:Euler Euler angles] et [page:Quaternion Quaternions], ainsi que des méthodes pour effectuer des conversions entre les deux. Les angles d'Euler sont sujets à un problème nommé "gimbal lock", où certaines configurations peuvent perdre un certain degré de liberté (empêchant l'objet d'effectuer une rotation sur un axe). Pour cette raison, les rotations d'objets sont toujours stockées dans la propriété [page:Object3D.quaternion quaternion] de l'objet. -

        -

        - Des versions précédentes de la librairie incluaient une propriété `useQuaternion` qui, si réglée sur false, faisait que la [page:Object3D.matrix matrix] matrice de l'objet était calculée depuis un angle d'Euler. Cette pratique est dépassée---à la place, vous devez utiliser la méthode [page:Object3D.setRotationFromEuler setRotationFromEuler], qui mettra le quaternion à jour. -

        - - - diff --git a/docs/manual/fr/introduction/Useful-links.html b/docs/manual/fr/introduction/Useful-links.html deleted file mode 100644 index b4c80ed9ab243c..00000000000000 --- a/docs/manual/fr/introduction/Useful-links.html +++ /dev/null @@ -1,177 +0,0 @@ - - - - - - - - - -

        Liens Utiles ([name])

        - -

        - Sur cette page vous trouverez un ensemble de liens qui pourraient vous êtres utiles dans votre apprentissage de three.js.
        - Si vous souhaitez ajouter quelque chose ici, ou si vous pensez que quelque chose n'est plus - pertinant ou fonctionnel, n'hésitez pas à cliquer sur le bouton 'edit' en bas à droite de la page pour faire quelques modifications!

        - - Notez également que three.js connaît un développement rapide, beaucoup de ces liens contiennent des informations qui sont - dépassées - si quelque chose ne fonctionne pas comme vous l'attendiez, ou comme un de ces liens l'annonce, - jetez un oeil à la console du navigateur à la recherche d'erreurs ou de warnings. Regardez également la documentation appropriée. -

        - -

        Forums d'aide

        -

        - Three.js utilise officiellement le [link:https://discourse.threejs.org/ forum] et [link:http://stackoverflow.com/tags/three.js/info Stack Overflow] pour les demandes d'aide. - Si vous avez besoin d'assistance avec quelque chose, c'est là où vous devez vous rendre. N'ouvrez PAS d'issue sur Github pour les demandes d'aide. -

        - -

        Cours et tutoriels

        - -

        Commencer three.js

        -
          -
        • - [link:https://threejs.org/manual/#en/fundamentals Three.js Fundamentals starting lesson] -
        • -
        • - [link:https://codepen.io/rachsmith/post/beginning-with-3d-webgl-pt-1-the-scene Beginning with 3D WebGL] par [link:https://codepen.io/rachsmith/ Rachel Smith]. -
        • -
        • - [link:https://www.august.com.au/blog/animating-scenes-with-webgl-three-js/ Animating scenes with WebGL and three.js] -
        • -
        - -

        More extensive / advanced articles and courses

        -
          -
        • - [link:https://threejs-journey.com/ Three Journey] par [link:https://bruno-simon.com/ Bruno Simon] - Ce cours apprends aux débutants à utiliser Three.js étape par étape -
        • -
        • - [link:https://discoverthreejs.com/ Discover three.js] -
        • -
        • - [link:http://blog.cjgammon.com/ Collection of tutorials] par [link:http://www.cjgammon.com/ CJ Gammon]. -
        • -
        • - [link:https://medium.com/soffritti.pierfrancesco/glossy-spheres-in-three-js-bfd2785d4857 Glossy spheres in three.js]. -
        • -
        • - [link:https://www.udacity.com/course/cs291 Interactive 3D Graphics] - un cours gratuit sur Udacity qui enseigne les fondamentaux de l'infographie 3D, - et qui utilise three.js comme outil de code. -
        • -
        • - [Link:https://aerotwist.com/tutorials/ Aerotwist] tutoriels par [link:https://github.com/paullewis/ Paul Lewis]. -
        • -
        • - [link:https://discourse.threejs.org/t/three-js-bookshelf/2468 Three.js Bookshelf] - Vous cherchez plus de ressources concernant three.js ou sur l'infographie en général? - Jetez un oeil à la sélection de littérature recommandée par la communauté. -
        • -
        - -

        News et Mises à jour

        -
          -
        • - [link:https://twitter.com/hashtag/threejs Three.js on Twitter] -
        • -
        • - [link:http://www.reddit.com/r/threejs/ Three.js on reddit] -
        • -
        • - [link:http://www.reddit.com/r/webgl/ WebGL on reddit] -
        • -
        - -

        Examples

        -
          -
        • - [link:https://github.com/edwinwebb/three-seed/ three-seed] - un projet prêt à l'emploi three.js avec ES6 et Webpack -
        • -
        • - [link:http://stemkoski.github.io/Three.js/index.html Professor Stemkoskis Examples] - une collection d'exemples adaptés aux débutant - construits three.js r60. -
        • -
        • - [link:https://threejs.org/examples/ Official three.js examples] - ces exemples sont - conservés dans le dossier three.js, et utilisent toujours la dernière version de three.js. -
        • -
        • - [link:https://raw.githack.com/mrdoob/three.js/dev/examples/ Official three.js dev branch examples] - - Pareil qu'au dessus, sauf que ceux-ci utilisent la branche dev de three.js, et sont utilisés pour s'assurer - que tout fonctionne durant le développement de three.js. -
        • -
        - -

        Outils

        -
          -
        • - [link:https://github.com/tbensky/physgl physgl.org] - un front-end JavaScript avec des wrappers pour three.js, pour montrer des graphiques WebGL - aux étudiants qui apprennent les mathématiques et la physique. -
        • -
        • - [link:https://whsjs.readme.io/ Whitestorm.js] – Framework three.js modulaire avec le plugin de physique AmmoNext. -
        • -
        • - [link:http://zz85.github.io/zz85-bookmarklets/threelabs.html Three.js Inspector] -
        • -
        • - [link:http://idflood.github.io/ThreeNodes.js/ ThreeNodes.js]. -
        • -
        • - [link:https://marketplace.visualstudio.com/items?itemName=slevesque.shader vscode shader] - Coloration syntaxique pour le langage des shaders. -
          - [link:https://marketplace.visualstudio.com/items?itemName=bierner.comment-tagged-templates vscode comment-tagged-templates] - Coloration syntaxique pour les littéraux de gabarits, comme: glsl.js. -
        • -
        • - [link:https://github.com/MozillaReality/WebXR-emulator-extension WebXR-emulator-extension] -
        • -
        - -

        Références WebGL

        -
          -
        • - [link:https://www.khronos.org/files/webgl/webgl-reference-card-1_0.pdf webgl-reference-card.pdf] - Référentiel de tous les mots-clés de WebGL et GLSL, la terminologie, syntaxe et les définitions. -
        • -
        - -

        Anciens Liens

        -

        - Ces liens sont conservés pour des raisons d'historisation - vous pouvez les trouver encore utile, mais gardez à l'esprit qu'ils - peuvent contenir des informations qui font référence à de très anciennes versions de three.js. -

        - -
          -
        • - [link:https://www.youtube.com/watch?v=Dir4KO9RdhM AlterQualia at WebGL Camp 3] -
        • -
        • - [link:http://yomotsu.github.io/threejs-examples/ Yomotsus Examples] - une liste d'exemple utilisant three.js r45. -
        • -
        • - [link:http://fhtr.org/BasicsOfThreeJS/#1 Introduction to Three.js] par [link:http://github.com/kig/ Ilmari Heikkinen] (slideshow). -
        • -
        • - [link:http://www.slideshare.net/yomotsu/webgl-and-threejs WebGL and Three.js] par [link:http://github.com/yomotsu Akihiro Oyamada] (slideshow). -
        • -
        • - [link:https://www.youtube.com/watch?v=VdQnOaolrPA Trigger Rally] par [link:https://github.com/jareiko jareiko] (video). -
        • -
        • - [link:http://blackjk3.github.io/threefab/ ThreeFab] - éditeur de scène, maintenu jusqu'à three.js r50 environ. -
        • -
        • - [link:http://bkcore.com/blog/3d/webgl-three-js-workflow-tips.html Max to Three.js workflow tips and tricks] par [link:https://github.com/BKcore BKcore] -
        • -
        • - [link:http://12devsofxmas.co.uk/2012/01/webgl-and-three-js/ A whirlwind look at Three.js] - par [link:http://github.com/nrocy Paul King] -
        • -
        • - [link:http://bkcore.com/blog/3d/webgl-three-js-animated-selective-glow.html Animated selective glow in Three.js] - par [link:https://github.com/BKcore BKcore] -
        • -
        • - [link:http://www.natural-science.or.jp/article/20120220155529.php Building A Physics Simulation Environment] - tutoriel three.js en Japonais -
        • -
        - - - diff --git a/docs/manual/fr/introduction/WebGL-compatibility-check.html b/docs/manual/fr/introduction/WebGL-compatibility-check.html deleted file mode 100644 index 9cd96aabc79cb6..00000000000000 --- a/docs/manual/fr/introduction/WebGL-compatibility-check.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - -

        Compatibilité WebGL ([name])

        -

        - Même si le problème se présente de moins en moins, certains appareils ou navigateurs peuvent ne toujours pas supporter WebGL. - La méthode suivante vous permet de vérifier si il est supporté et d'afficher un message à l'utilisateur si il ne l'est pas. - Importez le module de détection de support WebGL et exécutez ce qui suit avant d'essayer de rendre quoi que ce soit. -

        - - - import WebGL from 'three/addons/capabilities/WebGL.js'; - - if ( WebGL.isWebGLAvailable() ) { - - // Initiate function or other initializations here - animate(); - - } else { - - const warning = WebGL.getWebGLErrorMessage(); - document.getElementById( 'container' ).appendChild( warning ); - - } - - - diff --git a/docs/manual/it/introduction/Animation-system.html b/docs/manual/it/introduction/Animation-system.html deleted file mode 100644 index afe562f31c6c9d..00000000000000 --- a/docs/manual/it/introduction/Animation-system.html +++ /dev/null @@ -1,122 +0,0 @@ - - - - - - - - - -

        Sistema di animazione ([name])

        - -

        Panoramica

        - -

        - Con il sistema di animazione di three.js si possono animare varie proprietà dei modelli: - le ossa di un [page:SkinnedMesh modello skinned], i target morph, le diverse proprietà dei materiali - (colori, opacità, booleani), la visibilità e le trasformazioni. Le proprietà di animazione possono essere - sfumate, incrociate e deformate. Il peso e la scala temporale di diverse animazioni simultanee sullo stesso - oggetto o su oggetti diversi possono essere modificate in modo indipendente. È possibile sincronizzare - diverse animazioni sullo stesso oggetto e su oggetti diversi.

        - - Per ottenere tutto questo in un sistema omogeneo, il [link:https://github.com/mrdoob/three.js/issues/6881 sistema di animazione di three.js] - è stato cambiato completamente nel 2015 (attenzione alle informazioni non aggiornate!), ed ora ha un'architettura simile a quella di Unity/Unreal Engine 4. - Questa pagina fornisce una breve panoramica dei componenti principali del sistema e di come lavorano insieme. -

        - -

        Clip di animazione (AnimationClips)

        - -

        - Se si è importato con successo un oggetto 3D animato (non importa se ha ossa o target morph o entrambi) - - per esempio esportandolo da Blender con [link:https://github.com/KhronosGroup/glTF-Blender-IO l'exporter glTF Blender] e caricandolo - nella scena three.js usando [page:GLTFLoader] - uno dei campi di risposta dovrà essere un array chiamato "animations", contenente - gli [page:AnimationClip AnimationClips] per questo modello (vedi l'elenco dei possibili loader qui sotto).

        - - Ogni `AnimationClip` contiene solitamente i dati per una certa attività dell'oggetto. Se la mesh è un personaggio, - per esempio, ci può essere un AnimationClip per la camminata, un altro per il salto e un terzo per il salto laterale e così via. -

        - -

        Keyframe Tracks

        - -

        - All'interno di un `AnimationClip` i dati, per ogni propietà di animazione, sono memorizzati in un [page:KeyframeTrack] separato. - Supponendo che un oggetto personaggio abbia uno [page:Skeleton scheletro], un keyframe track potrebbe memorizzare i dati relativi alle variazioni di - posizione dell'osso inferiore del braccio nel tempo, un'altra traccia potrebbe memorizzare i dati relativi alle variazioni di rotazione dello stesso osso, - una terza traccia la posizione, la rotazione e la scala di un altro osso e così via. Dovrebbe essere chiaro, che un AnimationClip può essere composto - da molte tracce di questo tipo.

        - - Supponendo che il modello abbia dei target morph (per esempio un target morph che mostra una faccia amichevole e un altro che mostra una faccia arrabbiata), - ogni traccia contiene le informazioni su come l'influenza di un certo target morph cambia durante l'esecuzione del clip. -

        - -

        Mixer di Animazioni

        - -

        - I dati memorizzati costituiscono solo la base per le animazioni - la riproduzione vera e propria è controllata dall'[page:AnimationMixer]. - È possibile immaginarlo non solo come un lettore di animazioni, ma come un simulatore di un hardware come una vera e propria console di mixaggio, - che può controllare diverse animazioni simultaneamente, mescolandole e fondendole. -

        - -

        Azioni di Animazioni

        - -

        - Lo stesso `AnimationMixer` ha pochissime proprietà e metodi (generali), perché può essere controllato dall'[page:AnimationAction AnimationActions]. - Per configurare un `AnimationAction` è necessario specificare quando un `AnimationClip` deve essere eseguito, messo in pausa o stoppato su uno dei mixer, - se e con quale frequenza la clip deve essere ripetuta, se deve essere eseguita con una dissolvenza o una scala temporale e altre cose aggiuntive come - dissolvenza incrociata o sincronizzazione. -

        - -

        Gruppi di oggetti Animati

        - -

        - Se si desidera che un gruppo di oggetti riceva uno stato di animazione condiviso, è possibile utilizzare un [page:AnimationObjectGroup]. -

        - -

        Formati supportati e Loader

        - -

        - Si noti che non tutti i formati includono le animazioni (in particolare OBJ non lo fa) e che solo alcuni loader di three.js - supportano le sequenze [page:AnimationClip AnimationClip]. Di seguito alcuni che supportano questo tipo di animazioni: -

        - -
          -
        • [page:ObjectLoader THREE.ObjectLoader]
        • -
        • THREE.BVHLoader
        • -
        • THREE.ColladaLoader
        • -
        • THREE.FBXLoader
        • -
        • [page:GLTFLoader THREE.GLTFLoader]
        • -
        • THREE.MMDLoader
        • -
        - -

        - Si noti che 3ds max e Maya attualmente non possono esportare più animazioni (ovvero animazioni che non si trovano nella stessa timeline) - direttamente in un singolo file. -

        - -

        Esempio

        - - - let mesh; - - // Create an AnimationMixer, and get the list of AnimationClip instances - const mixer = new THREE.AnimationMixer( mesh ); - const clips = mesh.animations; - - // Update the mixer on each frame - function update () { - mixer.update( deltaSeconds ); - } - - // Play a specific animation - const clip = THREE.AnimationClip.findByName( clips, 'dance' ); - const action = mixer.clipAction( clip ); - action.play(); - - // Play all animations - clips.forEach( function ( clip ) { - mixer.clipAction( clip ).play(); - } ); - - - - diff --git a/docs/manual/it/introduction/Color-management.html b/docs/manual/it/introduction/Color-management.html deleted file mode 100644 index b110a1b2891491..00000000000000 --- a/docs/manual/it/introduction/Color-management.html +++ /dev/null @@ -1,409 +0,0 @@ - - - - - - - - - - - -

        Gestione del colore ([name])

        - -

        Cos'è lo spazio colore?

        - -

        - Ogni spazio colore è una collezione di diverse decisioni progettuali, - scelte insieme per supportare un'ampia gamma di colori e al contempo - soddisfare i vincoli tecnici legati alla precisione e alle tecnologie di - visualizzazione. Quando si crea una risorsa 3D, o si assemblano delle - risorse 3D insieme in una scena, è importante sapere quali sono queste - proprietà e come le proprietà di uno spazio colore si relazionano con - altri spazi colore nella scena. -

        - -
        - -
        - Colori sRGB e il punto di bianco (D65) visualizzati nel diagramma - cromatico di riferimento CIE 1931. La regione colorata rappresenta una - proiezione 2D della gamma sRGB, che è un volume 3D. Fonte: - Wikipedia -
        -
        - -
          -
        • - Colori primari: I colori primari (rosso, verde, blu) non sono - assoluti; vengono selezionati dallo spettro visibile in base ai vincoli - di precisione limitata e alla capacità dei dispositivi di - visualizzazione disponibili. I colori sono espressi come rapporto tra i - colori primari. -
        • -
        • - Punto di bianco: La maggior parte degli spazi colore è progettata - in modo tale che una somma equamente ponderata di primari - R = G = B appaia priva di colore o "acromatica". L'aspetto dei - valori cromatici (come il bianco o il grigio) dipende dalla percezione - umana, che a sua volta dipende fortemente dal contesto dell'osservatore. - Uno spazio colore specifica il suo "punto di bianco" per bilanciare - queste esigenze. Il punto di bianco definito dallo spazio colore sRGB è - D65. -
        • -
        • - Funzioni di trasferimento (transfer functions): Dopo aver scelto - la gamma cromatica e un modello di colore, dobbiamo ancora definire le - mappature ("funzioni di trasferimento") dei valori numerici da/verso lo - spazio colore. R = 0,5 rappresenta il 50% in meno di illuminazione - fisica rispetto a r = 1,0? O il 50% in meno di luminosità, come - percepito da un occhio umano medio? Sono cose diverse e questa - differenza può essere rappresentata come una funzione matematica. Le - funzioni di trasferimento possono essere lineari o - non lineari, a seconda degli obiettivi dello spazio colore. sRGB - definisce funzioni di trasferimento non lineari. Queste funzioni sono - talvolta approssimate come funzioni gamma, ma il termine "gamma" - è ambiguo e dovrebbe essere evitato in questo contesto. -
        • -
        - - Questi tre parametri - colori primari, punto di bianco e funzioni di - trasferimento - definiscono uno spazio colore, ognuno scelto per obiettivi - specifici. Dopo aver definito i parametri, sono utili alcuni termini - aggiuntivi: - -
          -
        • - Modello di colore: Sintassi per identificare numericamente i - colori all'interno della gamma cromatica scelta - un sistema di - coordinate per i colori. In three.js ci occupiamo principalmente del - modello di colore RGB, con tre coordinate - r, g, b ∈ [0,1] ("dominio chiuso") o - r, g, b ∈ [0,∞] ("dominio aperto") che rappresentano ciascuna una - frazione di un colore primario. Altri modelli di colore (HSL, Lab, LCH) - sono comunemente utilizzati per il controllo artistico. -
        • -
        • - Gamma di colori: Quando i colori primari e il punto di bianco - sono stati scelti, questi rappresentano un volume all'interno dello - spettro visibile (un "gamut"). I colori che non rientrano in questo - volume ("fuori gamut") non possono essere espressi dai valori RGB del - dominio chiuso [0,1]. Nel dominio aperto [0,∞], il gamut è tecnicamente - infinito. -
        • -
        - -

        - Consideriamo due spazi colori comuni: [page:SRGBColorSpace] ("sRGB") e - [page:LinearSRGBColorSpace] ("Linear-sRGB"). Entrambi usano gli stessi - colori primari e lo stesso punto di bianco, e quindi hanno la stessa gamma - di colori. Entrambi utilizzano il modello di colore RGB. Sono diversi solo - nelle funzioni di trasferimento - Linear-sRGB è lineare rispetto - all'intensità della luce fisica, mentre sRGB utilizza le funzioni di - trasferimento non lineari di sRGB e si avvicina maggiormente al modo in - cui l'occhio umano percepisce la luce e alla reattività dei comuni - dispositivi di visualizzazione. -

        - -

        - Questa differenza è importante. I calcoli di illuminazione e altre - operazioni di rendering devono generalmente avvenire in uno spazio di - colore lineare. Tuttavia, i colori lineari sono meno efficienti da - memorizzare in un'immagine o in un framebuffer e non hanno un aspetto - corretto quando vengono osservati da un osservatore umano. Di conseguenza, - le texture di input e l'immagine finale renderizzata utilizzano - generalmente lo spazio di colore sRGB non lineare. -

        - -
        -

        - ℹ️ - ATTENZIONE: Anche se alcuni display moderni supportano gamme - più ampie come Display-P3, le API grafiche della piattaforma web si - basano in gran parte su sRGB. Le applicazioni che utilizzano three.js - oggi utilizzano in genere solo gli spazi colore sRGB e Linear-sRGB. - -

        -
        - -

        Ruoli degli spazi colore

        - -

        - Un flusso di lavoro lineare - richiesto per moderni metodi di rendering - - generalmente coinvolge più di uno spazio di colore, ognuno assegnato ad un - ruolo specifico. Spazi colore lineari o non lineari sono adatti a ruoli - diversi, come spiegato di seguito. -

        - -

        Input color space

        - -

        - I colori forniti a three.js - dai color picker, dalle texture, dai modelli - 3D e da altre risorse - hanno ciascuno uno spazio colore associato. Quelli - che non sono già nello spazio colore di lavoro Linear-sRGB devono essere - convertiti e alle texture deve essere assegnata la corretta assegnazione - texture.colorSpace. Alcune conversioni (per i colori esadecimali e - CSS in sRGB) possono essere effettuate automaticamente se la modalità di - gestione del colore legacy è disabilitata prima dell'inizializzazione dei - colori: -

        - - THREE.ColorManagement.enabled = true; - -
          -
        • - Materiali, luci, e shader: I colori nei materiali, nelle luci e - negli shader memorizzano i componenti RGB nello spazio colore di lavoro - Linear-sRGB. -
        • -
        • - Colori dei vertici: [page:BufferAttribute BufferAttributes] - memorizza i componenti RGB nello spazio colore di lavoro Linear-sRGB. -
        • - -
        • - Colori delle texture: Le [page:Texture Texture] PNG o JPEG - contenti informazioni sul colore (come .map o .emissiveMap) usano lo - spazio colore sRGB a dominio chiuso, e devono essere annotate con - texture.colorSpace = SRGBColorSpace. I formati come OpenEXR (a volte - usati per .envMap o .lightMap) utilizzano lo spazio colore Linear-sRGB - indicato con texture.colorSpace = LinearSRGBColorSpace, e possono - contenere valori nel dominio aperto [0,∞]. -
        • -
        • - Texture non a colori: Le texture che non memorizzano informazioni - relative ai colori (come .normalMap o .roughnessMap) non hanno associato - uno spazio colore, e generalmente usano l'annotazione (predefinita) - texture.colorSpace = NoColorSpace. In rari casi, i dati non a - colori possono essere rappresentati con altre codifiche non lineari per - motivi tecnici. -
        • -
        - -
        -

        - ⚠️ - ATTENZIONE: Molti formati per modelli 3D non definiscono - correttamente o in modo coerente le informazioni sullo spazio colore. - Sebbene three.js tenti di gestire la maggior parte dei casi, i - problemi sono spesso con i file con formati meno recenti. Per ottenere - un miglior risultato bisogna utilizzare il formato glTF 2.0 - ([page:GLTFLoader]) e prima bisogna testare i modelli 3D in - visualizzatori online per confermare che la risorsa stessa sia - corretta. - -

        -
        - -

        Spazio colore di lavoro

        - -

        - Rendering, interpolazione e molte altre operazioni devono essere eseguite - in uno spazio colore di lavoro lineare, nel quale i componenti RGB sono - proporzionali all'illuminazione fisica. In three.js, lo spazio colore di - lavoro è Linear-sRGB. -

        - -

        Output color space

        - -

        - L'output su un dispositivo di visualizzazione, a un'immagine o a un video, - può comportare la conversione dallo spazio colore di lavoro Linear-sRGB di - dominio aperto a un altro spazio di colore. Questa conversione può essere - eseguita nel passaggio di rendering principale - ([page:WebGLRenderer.outputColorSpace]), o durante il post-processing. -

        - - - renderer.outputColorSpace = THREE.SRGBColorSpace; // optional with post-processing - - -
          -
        • - Display: I colori scritti in un canvas WebGL per i display devono - essere nello spazio colore sRGB. -
        • -
        • - Immagine: I colori scritti su un'immagine devono utilizzare lo - spazio colore appropriato per il formato e per il suo uso. Le immagini - completamente renderizzate scritte per texture PNG o JPEG generalmente - usano lo spazio colore sRGB. Immagini contenenti emissioni, mappe di - luce, o altri dati non limitati all'intervallo [0,1] utilizzaranno - generalmente lo spazio colore Linear-sRGB a dominio aperto, e un formato - immagine compatibile come OpenEXR. -
        • -
        - -
        -

        - ⚠️ - ATTENZIONE: I target di rendering possono utilizzare sia sRGB - che Linear-sRGB. sRGB utilizza meglio la precisione limitata. Nel - dominio chiuso, 8 bit sono sufficienti per sRGB mentre ≥12 (mezzo - float) possono essere richiesti per Linear-sRGB. Se gli stadi - successivi delle pipeline richiedono un ingresso Linear-sRGB, le - conversioni aggiuntive possono avere un piccolo costo in termini di - prestazioni. -

        -
        - -

        - Custom materials based on [page:ShaderMaterial] and [page:RawShaderMaterial] have to implement their own output color space conversion. - For instances of `ShaderMaterial`, adding the `encodings_fragment` shader chunk to the fragment shader's `main()` function should be sufficient. -

        - -

        Lavorare con le istanze di THREE.Color

        - -

        - I metodi di lettura o modifica delle istanze [page:Color] presuppongono - che i dati siano già nello spazio colore di lavoro di three.js, - Linear-sRGB. I componenti RGB e HSL sono rappresentazioni dirette dei dati - memorizzati dall'istanza Color, e non sono mai convertiti implicitamente. - I dati di Color possono essere esplicitamenre convertiti con - .convertLinearToSRGB() o .convertSRGBToLinear(). -

        - - - // RGB components (no change). - color.r = color.g = color.b = 0.5; - console.log( color.r ); // → 0.5 - - // Manual conversion. - color.r = 0.5; - color.convertSRGBToLinear(); - console.log( color.r ); // → 0.214041140 - - -

        - Con ColorManagement.enabled = true impostato (consigliato), - alcune conversioni vengono effettuate automaticamente. Poiché i colori - esadecimali e CSS sono generalmente sRGB, i metodi [page:Color] - convertiranno automaticamente questi input da sRGB a Linear-sRGB nei - setter, oppure convertiranno da Linear-sRGB a sRGB quando restituiscono - output esadecimali o CSS dai getter. -

        - - - // Hexadecimal conversion. - color.setHex( 0x808080 ); - console.log( color.r ); // → 0.214041140 - console.log( color.getHex() ); // → 0x808080 - - // CSS conversion. - color.setStyle( 'rgb( 0.5, 0.5, 0.5 )' ); - console.log( color.r ); // → 0.214041140 - - // Override conversion with 'colorSpace' argument. - color.setHex( 0x808080, LinearSRGBColorSpace ); - console.log( color.r ); // → 0.5 - console.log( color.getHex( LinearSRGBColorSpace ) ); // → 0x808080 - console.log( color.getHex( SRGBColorSpace ) ); // → 0xBCBCBC - - -

        Errori comuni

        - -

        - Quando un singolo colore o una texture non sono configurati correttamente, - apparirà più scuro o più chiaro del previsto. Quando lo spazio colore di - output del renderer non è configurato correttamente, l'intera scena - potrebbe essere più scura (ad es. manca la conversione ad sRGB) o più - chiara (ad es. una doppia conversione a sRGB con post-processing). In ogni - caso il problema potrebbe non essere uniforme e semplicemente - aumentare/dimunire la luminosità non lo risolverebbe. -

        - -

        - Un problema più sottile si verifica quando entrambi lo spazio colore di - input e quello di output non sono corretti - i livelli di luminosità complessivi - potrebbero andare bene, ma i colori potrebbero cambiare in modo imprevisto in - condizioni di illuminazione diversa o l'ombreggiatura potrebbe apparire più sbiadita - e meno morbida del previsto. Questi due errori non fanno una cosa giusta, ed è importante - che lo spazio colore di lavoro sia lineare ("riferito alla scena") e che lo spazio di colore - dell'output sia non lineare ("riferito alla visualizzazione"). -

        - -

        Ulteriori letture

        - - - - diff --git a/docs/manual/it/introduction/Creating-a-scene.html b/docs/manual/it/introduction/Creating-a-scene.html deleted file mode 100644 index fc759f57d68745..00000000000000 --- a/docs/manual/it/introduction/Creating-a-scene.html +++ /dev/null @@ -1,181 +0,0 @@ - - - - - - - - - -

        Creare una scena ([name])

        - -

        Lo scopo di questa sezione è quello di fornire una breve introduzione di three.js. - Inizieremo impostando una scena con un cubo rotante. - Se sei bloccato e hai bisogno di aiuto, in fondo alla pagina troverai un esempio funzionante.

        - -

        Prima di iniziare

        - -

        Prima di poter utilizzare three.js hai bisogno di un posto dove visualizzarlo. - Salva il seguente codice HTML in un file sul tuo computer e aprilo nel browser.

        - - - <!DOCTYPE html> - <html> - <head> - <meta charset="utf-8"> - <title>My first three.js app</title> - <style> - body { margin: 0; } - </style> - </head> - <body> - <script type="module"> - import * as THREE from 'https://unpkg.com/three/build/three.module.js'; - - // Il nostro Javascript andrà qui - </script> - </body> - </html> - - -

        Questo è quanto. Tutto il codice seguente va nel tag vuoto <script>.

        - -

        Creare la scena

        - -

        Per poter visualizzare qualsiasi cosa con three.js abbiamo bisogno di tre cose: scena, - telecamera e renderer, in modo da poter renderizzare la scena con la telecamera.

        - - - const scene = new THREE.Scene(); - const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); - - const renderer = new THREE.WebGLRenderer(); - renderer.setSize( window.innerWidth, window.innerHeight ); - document.body.appendChild( renderer.domElement ); - - -

        Spieghiamo un attimo cosa sta succedendo. Abbiamo impostato la scena, la telecamera e il renderer.

        - -

        Esistono diverse telecamere in three.js. Per ora usiamo una `PerspectiveCamera`.

        - -

        Il primo attributo è il `field of view` (campo visivo). Il FOV è l'estensione della scena che - viene vista sul display in qualsiasi momento. Il valore è espresso in gradi.

        - -

        Il secondo attributo è l'`aspect ratio` (rapporto di aspetto). - È quasi sempre consigliabile usare la larghezza dell'elemento divisa per l'altezza, - altrimenti si otterrà lo stesso risultato di quando si riproducono vecchi film su un televisore widescreen - l'immagine appare schiacciata.

        - -

        I successivi due attributi sono il piano di ritaglio, `near` (il vicino) e `far` (il lontano). - Ciò significa che gli oggetti più lontani dalla telecamera rispetto al valore `far` o più vicini - rispetto al valore `near` non saranno renderizzati. Non è neccessario preoccuparsi di questo - aspetto adesso, ma potresti voler usare altri valori nella tua applicazione per avere delle prestazioni migliori.

        - -

        Il prossimo passo è il renderer. È qui che avviene la magia. Oltre a creare l'istanza del renderer, abbiamo bisogno di impostare le dimensioni con cui vogliamo che l'applicazione venga visualizzata. È una buona idea usare la larghezza e l'altezza dell'area che vogliamo riempire con la nostra applicazione - in questo caso, la larghezza e l'altezza della finestra del browser. Per le applicazioni ad alte prestazioni si possono dare a `setSize` dei valori più piccoli, come `window.innerWidth/2` e `window.innerHeight/2`, che faranno si che l'applicazione venga renderizzata ad una dimensione di un quarto.

        - -

        Se si desidera mantenere la dimensione dell'applicazione ma visualizzarla ad una risoluzione minore, è possibile farlo aggiungendo a `setSize` il valore false, corrispondente a `updateStyle` (il terzo parametro). Per esempio, `setSize(window.innerWidth/2, window.innerHeight/2, false)` visualizzerà l'applicazione a metà risoluzione, dato che il <canvas> ha larghezza e altezza del 100%.

        - -

        Infine, aggiungiamo l'elemento `renderer` al nostro documento HTML. Si tratta di un elemento <canvas> che il renderer utilizza per visualizzare la scena.

        - -

        "Va bene, ma dov'è il cubo che ci avevi promesso?" Aggiungiamolo ora.

        - - - const geometry = new THREE.BoxGeometry( 1, 1, 1 ); - const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); - const cube = new THREE.Mesh( geometry, material ); - scene.add( cube ); - - camera.position.z = 5; - - -

        Per creare un cubo abbiamo bisogno di una `BoxGeometry`. Un oggetto che contiene tutti i punti (`vertices`) e le facce (`faces`) del cubo. Lo esploreremo meglio in futuro.

        - -

        Oltre alla geometria, abbiamo bisogno di un materiale per colorarla. Three.js mette a disposizione molti materiali, ma per ora ci limiteremo al `MeshBasicMaterial`. Tutti i materiali come parametro accettano un oggetto al cui interno avrà delle proprietà che li verrano applicate. Per mantenere le cose molto semplici, impostiamo solo un attributo di colore verde `0x00ff00`. Questo funziona come i colori nei CSS o in Photoshop (`hex colors`).

        - -

        La terza cosa di cui abbiamo bisogno è una `Mesh`. Una mesh è un oggetto che prende una geometria, e le applica un materiale, che possiamo, poi, inserire nella scena e spostare liberamente.

        - -

        Per impostazione predefinita, quando chiamiamo `scene.add()`, la cosa che sarà aggiunta verrà posizionata nelle coordinate `(0,0,0)`. In questo modo la telecamera e il cubo si troveranno l'uno dentro l'altro. Per evitare questo inconveniente, è sufficiente spostare la telecamera un po' più in là.

        - -

        Renderizzare la scena

        - -

        Se si copiasse il codice qui sopra nel file HTML creato in precedenza, non si vedrebbe nulla. Questo perché ancora non stiamo visualizzando nulla. Per questo abbiamo bisogno di quello che viene chiamato un ciclo `render o animate`.

        - - - function animate() { - requestAnimationFrame( animate ); - renderer.render( scene, camera ); - } - animate(); - - -

        Questa funzione creerà un loop che fa sì che il renderer disegni la scena ogni volta che la scena viene aggiornata - (su uno schermo tipico questo vuol dire 60 volte al secondo). Se sei nuovo nella scrittura di giochi su browser, - potresti dire "perché non creiamo semplicemente un setInterval?" Il fatto è che potremmo ma `requestAnimationFrame` - ha una serie di vantaggi. Forse il più importante è che si interrompe quando l'utente passa ad un'altra scheda del browser, - così da non sprecare la preziosa potenza di elaborazione e la vita della batteria del computer.

        - -

        Animare il cubo

        - -

        Se inserisci tutto il codice sopra nel file che hai creato prima di iniziare, dovresti vedere un box verde. - Rendiamo tutto un po' più interessante ruotandolo.

        - -

        Aggiungi quanto segue proprio sopra la chiamata `renderer.render` nella tua funzione `animate`:

        - - - cube.rotation.x += 0.01; - cube.rotation.y += 0.01; - - -

        Verrà eseguito per ogni frame (normalmente 60 volte per secondo) e darà al cubo un'animazione di rotazione. - Fondamentalmente, tutto ciò che vuoi spostare o modificare mentre l'applicazione è in esecuzione deve passare - attraverso il ciclo di animazione. Ovviamente, si possono chiamare altre funzioni all'interno di `animate`, - in modo da non avere una funzione con centinaia di righe.

        - -

        Il risultato

        -

        Congratulazioni! Hai completato la tua prima applicazione three.js. È semplice, ma da qualche parte devi pur iniziare.

        - -

        Il codice completo è disponibile di seguito e come esempio modificabile [link:https://jsfiddle.net/0c1oqf38/ live example]. Giocaci per capire meglio come funziona.

        - - - <!DOCTYPE html> - <html> - <head> - <meta charset="utf-8"> - <title>My first three.js app</title> - <style> - body { margin: 0; } - </style> - </head> - <body> - <script type="module"> - import * as THREE from 'https://unpkg.com/three/build/three.module.js'; - - const scene = new THREE.Scene(); - const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); - - const renderer = new THREE.WebGLRenderer(); - renderer.setSize( window.innerWidth, window.innerHeight ); - document.body.appendChild( renderer.domElement ); - - const geometry = new THREE.BoxGeometry( 1, 1, 1 ); - const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); - const cube = new THREE.Mesh( geometry, material ); - scene.add( cube ); - - camera.position.z = 5; - - function animate() { - requestAnimationFrame( animate ); - - cube.rotation.x += 0.01; - cube.rotation.y += 0.01; - - renderer.render( scene, camera ); - } - - animate(); - </script> - </body> - </html> - - - diff --git a/docs/manual/it/introduction/Creating-text.html b/docs/manual/it/introduction/Creating-text.html deleted file mode 100644 index 5042250d68c741..00000000000000 --- a/docs/manual/it/introduction/Creating-text.html +++ /dev/null @@ -1,138 +0,0 @@ - - - - - - - - - -

        Creare testo ([name])

        -
        -

        - Spesso si ha la necessità di utilizzare del testo nella propria applicazione three.js - ecco un paio di modi per farlo. -

        -
        - -

        1. DOM + CSS

        -
        -

        - L'uso dell'HTML è generalmente il modo più facile e veloce per aggiungere testo. Questo è il metodo - usato per gli overlay descrittivi in quasi tutti gli esempi di three.js. -

        -

        È possibile aggiungere contenuto ad un div:

        - <div id="info">Description</div> - -

        - e usare il markup CSS per posizionare l'elemento in modo assoluto, in una posizione sopra tutti gli altri elementi - con la proprietà z-index, specialmente se three.js viene eseguito in modalità full screen. -

        - - -#info { - position: absolute; - top: 10px; - width: 100%; - text-align: center; - z-index: 100; - display:block; -} - - -
        - - -

        2. Usare [page:CSS2DRenderer] o [page:CSS3DRenderer]

        -
        -

        - Questi renderer vengono utilizzati per disegnare testo di alta qualità all'interno di elementi del DOM nella scena three.js. - Questo metodo è simile al punto 1 eccetto per il fatto che con questi renderer è possibile integrare più facilmente e dinamicamente gli elementi nella scena three.js. -

        -
        - - -

        3. Disegnare il testo nel canvas e usarlo come [page:Texture]

        -
        -

        Utilizza questo metodo se desideri disegnare testo facilmente su un piano nella scena three.js.

        -
        - - -

        4. Disegnare un modello nella tua applicazione 3D preferita ed esportarlo in three.js

        -
        -

        Utilizza questo metodo se preferisci lavorare con la tua applicazione 3D e importare i modelli in three.js.

        -
        - - -

        5. Text Geometry Procedurale

        -
        -

        - Se preferisci lavorare solo in three.js o creare geometrie di testo 3D dinamiche e procedurali, è possibile creare una mesh che abbia come geometria - un'istanza di THREE.TextGeometry: -

        -

        - new THREE.TextGeometry( text, parameters ); -

        -

        - In modo tale che questo funzioni correttamente, TextGeometry necessita che un'istanza di THREE.Font venga impostata come valore del parametro "font". - Per avere maggiori informazioni su come implementare questo metodo, consulta la pagina [page:TextGeometry], contenente la descrizione di ogni - parametro che la classe accetta e una lista di font JSON che vengono forniti con la distribuzione di three.js. -

        - -

        Esempi

        - -

        - [example:webgl_geometry_text WebGL / geometry / text]
        - [example:webgl_shadowmap WebGL / shadowmap] -

        - -

        - TextGeometry utilizza i font generati da typeface.json. - Se Typeface è inattivo, o si preferisce usare un font che non è presente, esiste un tutorial che mostra come con uno script python - per blender è possibile esportare il testo nel formato JSON di Three.js: - [link:http://www.jaanga.com/2012/03/blender-to-threejs-create-3d-text-with.html] -

        - -
        - - -

        6. Font Bitmap

        -
        -

        - BMFont (font bitmap) consente di raggruppare glyphs in un unico BufferGeometry. Il rendering di BMFont - supporta il word-wrapping, letter spacing, kerning, signed distance fields with standard derivatives, multi-channel signed distance fields, multi-texture fonts, ed altro ancora. - Per saperne di più consulta [link:https://github.com/felixmariotto/three-mesh-ui three-mesh-ui] o [link:https://github.com/Jam3/three-bmfont-text three-bmfont-text]. -

        -

        - I font sono disponibili in progetti come - [link:https://github.com/etiennepinchon/aframe-fonts A-Frame Fonts], o puoi creare il tuo personale partendo da qualsiasi font .TTF, - ottimizzazione per includere solo caratteri richiesti per il progetto. -

        -

        - Alcuni strumenti utili: -

        -
          -
        • [link:http://msdf-bmfont.donmccurdy.com/ msdf-bmfont-web] (web-based)
        • -
        • [link:https://github.com/soimy/msdf-bmfont-xml msdf-bmfont-xml] (commandline)
        • -
        • [link:https://github.com/libgdx/libgdx/wiki/Hiero hiero] (desktop app)
        • -
        -
        - - -

        7. Troika Text

        -
        -

        - Il pacchetto [link:https://www.npmjs.com/package/troika-three-text troika-three-text] esegue il rendering - del testo con antialias utilizzando una tecnica simile a BMFont, ma funziona direttamente con font di tipo .TTF o .WOFF - in modo da non dover pregenerare una texture glyph offline. Ha anche altre funzionalità, tra cui: -

        -
          -
        • Effetti come pennellate, ombreggiature e curvature
        • -
        • La capacità di applicare qualsiasi materiale three.js, anche un ShaderMaterial personalizzato
        • -
        • Supporto per le legature dei font, script con lettere unite e il layout da destra-a-sinistra/bidirezionale
        • -
        • Ottimizzazione per grandi quantità di testo dinamico, eseguendo la maggior parte del lavoro fuori dal thread principale in un web worker
        • -
        -
        - - - - diff --git a/docs/manual/it/introduction/Drawing-lines.html b/docs/manual/it/introduction/Drawing-lines.html deleted file mode 100644 index d7c326b336a085..00000000000000 --- a/docs/manual/it/introduction/Drawing-lines.html +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - - -

        Disegnare linee ([name])

        -
        -

        - Diciamo che tu voglia disegnare una linea o un cerchio, non un wireframe [page:Mesh]. - Prima bisogna impostare il [page:WebGLRenderer renderer], la [page:Scene scene] e la camera (vedi la pagina Creare una scena). -

        - -

        Di seguito il codice che utilizzeremo:

        - -const renderer = new THREE.WebGLRenderer(); -renderer.setSize( window.innerWidth, window.innerHeight ); -document.body.appendChild( renderer.domElement ); - -const camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 500 ); -camera.position.set( 0, 0, 100 ); -camera.lookAt( 0, 0, 0 ); - -const scene = new THREE.Scene(); - -

        La prossima cosa da fare è definire il materiale. Per le linee possiamo usare [page:LineBasicMaterial] o [page:LineDashedMaterial].

        - -//crea una LineBasicMaterial blu -const material = new THREE.LineBasicMaterial( { color: 0x0000ff } ); - - -

        - Dopo la definizione del materiale abbiamo bisogno di una geometria con alcuni vertici: -

        - - -const points = []; -points.push( new THREE.Vector3( - 10, 0, 0 ) ); -points.push( new THREE.Vector3( 0, 10, 0 ) ); -points.push( new THREE.Vector3( 10, 0, 0 ) ); - -const geometry = new THREE.BufferGeometry().setFromPoints( points ); - - -

        Da notare che le linee vengono tracciate tra ogni coppia consecutiva di vertici, ma non tra il primo e l'ultimo vertice (la linea non è chiusa).

        - -

        Ora che abbiamo i punti per due linee e un materiale, possiamo unirli per formare una linea.

        - -const line = new THREE.Line( geometry, material ); - -

        Non resta che aggiungerlo alla scena e chiamare il [page:WebGLRenderer.render render].

        - - -scene.add( line ); -renderer.render( scene, camera ); - - -

        Ora dovresti vedere una freccia che punta verso l'alto formata da due linee blu.

        -
        - - diff --git a/docs/manual/it/introduction/FAQ.html b/docs/manual/it/introduction/FAQ.html deleted file mode 100644 index ae39b91935bb89..00000000000000 --- a/docs/manual/it/introduction/FAQ.html +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - - -

        FAQ ([name])

        - -

        Qual è il formato per i modelli 3D meglio supportato?

        -
        -

        - Il formato raccomandato per importare ed esportare modelli 3D è il glTF (GL Transmission Format). - Poiché è focalizzato sulla distribuzione di risorse in runtime, è compatto da trasmettere e veloce da caricare. -

        -

        - Three.js mette a disposizione loader per molti altri formati popolari come FBX, Collada o OBJ. - Tuttavia, si dovrebbe, nei propri progetti, sempre cercare di stabilire prima un flusso di lavoro basato su glTF. - Per maggiori informazioni consultare la guida sul [link:#manual/introduction/Loading-3D-models caricamento dei modelli 3D]. -

        -
        - -

        Perché negli esempi sono presenti meta tag viewport?

        -
        - <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> - -

        Questi tag controllano le dimensioni e la scala del viewport per i browser su mobile (dove il contenuto della pagina può essere visualizzato con dimensioni diverse rispetto al viewport).

        - -

        [link:https://developer.apple.com/library/content/documentation/AppleApplications/Reference/SafariWebContent/UsingtheViewport/UsingtheViewport.html Safari: Usare il Viewport]

        - -

        [link:https://developer.mozilla.org/en-US/docs/Web/HTML/Viewport_meta_tag MDN: Usare il meta tag viewport]

        -
        - -

        Come si può conservare la scala della scena durante il ridimensionamento?

        -

        - Vogliamo che tutti gli oggetti indipendentemente dalla loro distanza dalla camera, siano visualizzati con le stesse dimensioni anche se la finestra viene ridimensionata. - - L'equazione chiave per risolvere questo problema è data da questa formula per l'altezza visibile ad una data distanza: - - -visible_height = 2 * Math.tan( ( Math.PI / 180 ) * camera.fov / 2 ) * distance_from_camera; - - Se aumentiamo l'altezza della finestra di una certa percentuale, vogliamo che l'altezza visibile a tutte le distanze aumenti della stessa percentuale. - - Questo non può essere fatto cambiando la posizione della camera. È invece necessario modificare il valore del campo visivo (fov) della camera. - [link:http://jsfiddle.net/Q4Jpu/ Esempio]. -

        - -

        Perché una parte del mio oggetto è invisibile?

        -

        - Questo comportamento può essere causato dal "face culling" (eliminazione delle facce). Le facce hanno un orientamento che decide quale lato è quello giusto. - E il culling (eliminazione) rimuove il lato posteriore in circostanze normali. Per verificare se il problema è questo cambia la proprietà side del materiale con THREE.DoubleSide. - - material.side = THREE.DoubleSide -

        - -

        Perché three.js a volte restituisce strani risultati per input non validi?

        -

        - Per ragioni di performance three.js nella maggior parte dei casi non convalida gli input. È responsabilità della tua applicazione di controllare i dati che vengono passati a three.js. -

        - - diff --git a/docs/manual/it/introduction/How-to-create-VR-content.html b/docs/manual/it/introduction/How-to-create-VR-content.html deleted file mode 100644 index e54f2478fdd5e7..00000000000000 --- a/docs/manual/it/introduction/How-to-create-VR-content.html +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - - - - -

        Come creare contenuti VR ([name])

        - -

        - Questa guida fornisce una breve panoramica sui componenti di base di un'applicazione VR basata sul web realizzata con three.js. -

        - -

        Workflow

        - -

        - Prima di tutto devi includere [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/webxr/VRButton.js VRButton.js] nel tuo progetto. -

        - - -import { VRButton } from 'three/addons/webxr/VRButton.js'; - - -

        - *VRButton.createButton()* fa due cose importanti: Crea un bottone per indicare la compatibilità con il VR. - Inoltre, se l'utente attiva il bottone, viene avviata una sessione VR. L'unica cosa che devi fare è aggiungere la seguente - linea di codice al tuo progetto. -

        - - -document.body.appendChild( VRButton.createButton( renderer ) ); - - -

        - Successivamente, hai bisogno di dire alla tua istanza di `WebGLRenderer` di abilitare il rendering XR. -

        - - -renderer.xr.enabled = true; - - -

        - Infine, devi regolare il ciclo di animazione poiché non possiamo utilizzare la nota funzione *window.requestAnimationFrame()*. - Per i progetti VR viene utilizzato il metodo [page:WebGLRenderer.setAnimationLoop setAnimationLoop]. - Il codice minimo si presenta così: -

        - - -renderer.setAnimationLoop( function () { - - renderer.render( scene, camera ); - -} ); - - -

        Prossimi passi

        - -

        - Dai un'occhiata ad uno degli esempi ufficiali di WebVR per vedere questo flusso di lavoro in azione.

        - - [example:webxr_vr_ballshooter WebXR / VR / ballshooter]
        - [example:webxr_vr_cubes WebXR / VR / cubes]
        - [example:webxr_vr_dragging WebXR / VR / dragging]
        - [example:webxr_vr_paint WebXR / VR / paint]
        - [example:webxr_vr_panorama_depth WebXR / VR / panorama_depth]
        - [example:webxr_vr_panorama WebXR / VR / panorama]
        - [example:webxr_vr_rollercoaster WebXR / VR / rollercoaster]
        - [example:webxr_vr_sandbox WebXR / VR / sandbox]
        - [example:webxr_vr_sculpt WebXR / VR / sculpt]
        - [example:webxr_vr_video WebXR / VR / video] -

        - - - - diff --git a/docs/manual/it/introduction/How-to-dispose-of-objects.html b/docs/manual/it/introduction/How-to-dispose-of-objects.html deleted file mode 100644 index c8a478fe7fe225..00000000000000 --- a/docs/manual/it/introduction/How-to-dispose-of-objects.html +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - - - -

        Come liberare le risorse ([name])

        - -

        - Un aspetto importante per migliorare le prestazioni ed evitare perdite di memoria nella tua applicazione è l'eliminazione delle entità della libreria non utilizzate. - Ogni volta che viene creata un'istanza di un tipo *three.js*, viene allocata una certa quantità di memoria. Tuttavia, *three.js* crea per oggetti specifici - come le geometrie o i materiali, entità correlate WebGL, come buffer o programmi shader, necessari per il rendering. È importante sottolineare che - questi oggetti non vengono rilasciati automaticamente. Invece, l'applicazione utilizza una speciale API per liberare tali risorse. Questa guida fornisce - una breve panoramica su come l'API viene utilizzata e quali oggetti sono rilevanti in questo contesto. -

        - -

        Geometrie

        - -

        - Una geometria solitamente rappresenta le informazioni sui vertici come una collezione di attributi. *three.js* internamente crea un oggetto di tipo [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLBuffer WebGLBuffer] - per ogni attributo. Queste entità vengono cancellate solo se viene chiamato il metodo [page:BufferGeometry.dispose](). Se una geometria, nella tua applicazione, - diventa obsoleta, esegui questo metodo per liberare tutte le risorse correlate. -

        - -

        Materiali

        - -

        - Un materiale definisce come sono visualizzati gli oggetti. *three.js* utilizza le informazioni di una definizione di materiale per costruire un programma shader per il rendering. - I programmi shader possono essere cancellati solo se il respettivo materiale è eliminato. Per ragioni di prestazioni, *three.js* prova, se possibile, a riutilizzare - il programma shader già esiste. Quindi un progamma di shader può essere cancellato solo se tutti i relativi materiali sono eliminati. Puoi eseguire l'eliminazione - di un materiale eseguendo il metodo [page:Material.dispose](). -

        - -

        Texture

        - -

        - L'eliminazione di un materiale non ha effetti sulle texture. Queste vengono gestite separatamente, poiché una singola texture può essere usata da più materiali contemporaneamente. - Ogni volta che crei un'istanza di [page:Texture], three.js internamente crea un'istanza di [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLTexture WebGLTexture]. - Come per i buffer, questo oggetto può essere eliminato solo chiamando il metodo[page:Texture.dispose](). -

        - -

        - Se stai usando `ImageBitmap` come origine dati della texture, devi chiamare il metodo [link:https://developer.mozilla.org/en-US/docs/Web/API/ImageBitmap/close ImageBitmap.close]() a livello applicativo per liberare le risorse lato CPU. - Una chiamata automatica di `ImageBitmap.close()`in [page:Texture.dispose]() non è possibile, poiché il bitmap dell'immagine diventa inutilizzabile, e l'engine non ha modo di sapere se il bitmap dell'immagine è utilizzata da altre parti. -

        - -

        Render Target

        - -

        - Oggetti di tipo [page:WebGLRenderTarget] non allocano solo un'istanza di [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLTexture WebGLTexture] ma anche - di [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLFramebuffer WebGLFramebuffer] e di [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderbuffer WebGLRenderbuffer] - per realizzare destinazioni di rendering personalizzate. Questi oggetti vengono solo deallocati eseguendo il metodo [page:WebGLRenderTarget.dispose](). -

        - -

        Varie

        - -

        - Ci sono altre classi nella cartella degli esempi come i control o passaggi di post-processing che forniscono metodi `dispose()` per rimuovere i listener di eventi interni o - renderizzare i target. In generale, viene raccomandato di controllare le API o la documentazione della classe e controllare il metodo `dispose()`. Se presente dovresti utilizzarlo per liberare le risorse. -

        - -

        FAQ

        - -

        Perché *three.js* non può eliminare gli oggetti automaticamente?

        - -

        - Questa domanda è stata fatta spesso dalla community, quindi è importante chiarire la questione. Il fatto è che *three.js* non conosce il ciclo di vita o lo scopo - delle entità create dall'utente come le geometrie o i materiali, questa è una responsabilità dell'applicazione. Per esempio, anche se un materiale non è momentamenente utilizzato per il - rendering, potrebbe essere necessario per il prossimo frame. Quindi se l'applicazione decide che un determianto oggetto può essere cancellato lo notifica all'engine tramite la chiamata - al rispettivo metodo `dispose()`. -

        - -

        La rimozione di una mesh dalla scena elimina anche la sua geometria e il suo materiale?

        - -

        - No, devi eliminare esplicitamente la geometria e il materiale chiamando il metodo *dispose()*. Tieni a mente che le geometrie e i materiali possono essere condivisi tra oggetti 3D come le mesh. -

        - -

        *three.js* fornisce informazioni sulla quantità di oggetti memorizzati nella cache?

        - -

        - Si, è possibile consultare [page:WebGLRenderer.info]. Una proprietà speciale del renderer con una serie di informazioni statistiche sulla memoria della scheda grafica - e il processo di rendering. Tra le altre cose, riporta anche quante texture, geometrie e programmi shader sono internamente memorizzati. Se noti che ci sono problemi - di performance nell'applicazione, è una buona idea debuggare questa proprietà per identificare facilmente se c'è una perdita di memoria. -

        - -

        Che cosa succede quando il metodo `dispose()` viene chiamato su una texture ma l'immagine non è ancora stata caricata?

        - -

        - Le risorse interne di una texture vengono allocate solo se l'immagine è caricata con successo. Se elimini una texture prima che l'immagine venga caricata - non succede niente. Nessuna risorsa è stata allocata, quindi niente deve essere pulito. -

        - -

        Cosa succede quando chiamo `dispose()` e poi utilizzo il rispettivo oggetto in un secondo momento?

        - -

        - Le risorse interne che sono state cancellate saranno ricreate dall'engine, in questo modo non ci saranno errori a runtime. Probabilmente, però, noterai un impatto negativo sulle performance nel - frame corrente quando il programma shader sarà compilato. -

        - -

        Come devo gestire gli oggetti *three.js* nella mia app? Quando so che devo eliminare le risorse?

        - -

        - In generale non c'è una raccomandazione definitiva per questo. Dipende molto dal caso d'uso specifico. È importante sottolineare che non è sempre necessario - eliminare oggetti tutto il tempo. Un buon esempio esemplificativo è un gioco a più livelli. Un buon momento per eliminare gli oggetti è quando viene cambiato il livello. - L'applicazione può attraversare tutta la vecchia scena (livello) ed eliminare tutti i vecchi materiali, geometrie e texture. Come accennato nella sezione precedente, - se viene cancellato un oggetto che è attualmente in uso non produce un errore a runtime. La cosa peggiore che può accadere è un calo delle prestazioni in un singolo frame. -

        - -

        Esempi che mostarno l'uso del metodo dispose()

        - -

        - [example:webgl_test_memory WebGL / test / memory]
        - [example:webgl_test_memory2 WebGL / test / memory2]
        -

        - - - - diff --git a/docs/manual/it/introduction/How-to-update-things.html b/docs/manual/it/introduction/How-to-update-things.html deleted file mode 100644 index 43973a84fcc424..00000000000000 --- a/docs/manual/it/introduction/How-to-update-things.html +++ /dev/null @@ -1,220 +0,0 @@ - - - - - - - - - -

        Come aggiornare le cose ([name])

        -
        -

        Tutti gli oggetti di default, automaticamente aggiornano le loro matrici se vengono aggiunti alla scena seguendo il codice qui sotto:

        - -const object = new THREE.Object3D(); -scene.add( object ); - - o se sono figli di un altro oggetto che è stato aggiunto alla scena: - -const object1 = new THREE.Object3D(); -const object2 = new THREE.Object3D(); - -object1.add( object2 ); -scene.add( object1 ); // object1 e object2 aggiorneranno automaticamente le loro matrici - -
        - -

        Comunque, se sai che l'oggetto sarà statico, puoi disabilitare questo automatismo e aggiornare manualmente la matrice di trasformazione, solo quando necessario.

        - - -object.matrixAutoUpdate = false; -object.updateMatrix(); - - -

        BufferGeometry

        -
        -

        - Le BufferGeometry memorizzano le informazioni (come le posizioni dei vertici, gli indici delle facce, le normali, i colori, - le coordinate UV, e qualsiasi attributo personalizzato) nel [page:BufferAttribute buffer] - cioè in - [link:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays array tipizzati]. - Ciò le rende generalmente più veloci delle Geometry standard, al costo di essere un po' più difficili da lavorare. -

        -

        - Per quanto riguarda l'aggiornamento delle BufferGeometry, la cosa più importante da capire è che - il buffer non può essere ridimensionato (questo è molto costoso e basicamente equivalente a creare una nuova geometria). - Indipendetemente da questo il contenuto dei buffer può essere comunque aggiornato. -

        -

        - Questo significa che se sai che un attributo della BufferGeometry crescerà, ad esempio il numero di vertici, - è necessario preallocare un buffer sufficientemente grande per contenere i nuovi vertici che potrebbero essere creati. - Ovviamente, questo significa anche che ci sarà una dimensione massima per la tua BufferGeometry - non è possibile - creare una BufferGeometry che possa essere estesa in modo efficiente indefinitamente. -

        -

        - Useremo l'esempio di una linea che viene estesa al momento del rendering. Allocheremo spazio nel buffer per contenere 500 vertici - ma all'inizio ne disegneremo soltanto due, usando [page:BufferGeometry.drawRange]. -

        - -const MAX_POINTS = 500; - -// geometry -const geometry = new THREE.BufferGeometry(); - -// attributes -const positions = new Float32Array( MAX_POINTS * 3 ); // 3 vertices per point -geometry.setAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) ); - -// draw range -const drawCount = 2; // draw the first 2 points, only -geometry.setDrawRange( 0, drawCount ); - -// material -const material = new THREE.LineBasicMaterial( { color: 0xff0000 } ); - -// line -const line = new THREE.Line( geometry, material ); -scene.add( line ); - -

        - Quindi, aggiungeremo punti alla linea in maniera random usando un pattern come questo: -

        - -const positionAttribute = line.geometry.getAttribute( 'position' ); - -let x = 0, y = 0, z = 0; - -for ( let i = 0; i < positionAttribute.count; i ++ ) { - - positionAttribute.setXYZ( i, x, y, z ); - - x += ( Math.random() - 0.5 ) * 30; - y += ( Math.random() - 0.5 ) * 30; - z += ( Math.random() - 0.5 ) * 30; - -} - -

        - Se vuoi cambiare il numero di punti visualizzati dopo il primo render, procedi come segue; -

        - -line.geometry.setDrawRange( 0, newValue ); - -

        - Se vuoi cambiare i valori dei dati di posizione dopo il primo render, è necessario - impostare il flag di needsUpdate come segue: -

        - -positionAttribute.needsUpdate = true; // required after the first render - - -

        - Se vuoi modificare i valori dei dati di posizione dopo il rendering iniziale, è necessario - ricalcolare i volumi di delimitazione (bounding volumes) in modo che altre funzionalità dell'engine - come l'eliminazione del frustum di visualizzazione o gli helpers possano funzionare correttamente. -

        - -line.geometry.computeBoundingBox(); -line.geometry.computeBoundingSphere(); - - -

        - [link:https://jsfiddle.net/t4m85pLr/1/ Qui un fiddle] che mostra una linea animata che può essere adattata al tuo caso d'uso. -

        - -

        Esempi

        - -

        - [example:webgl_custom_attributes WebGL / custom / attributes]
        - [example:webgl_buffergeometry_custom_attributes_particles WebGL / buffergeometry / custom / attributes / particles] -

        - -
        - -

        Materiali

        -
        -

        Tutti i valori costanti possono essere cambiati liberamente (come i colori, le texture, l'opacità, etc), valori che vengono inviati allo shader ad ogni frame.

        - -

        Anche i parametri relativi a GLstate possono essere cambiati in qualsiasi momento (depthTest, blending, polygonOffset, etc).

        - -

        Invece, le proprietà seguenti non possono essere modificare facilmente in fase di esecuzione (una volta che il materiale è stato renderizzato almeno una volta):

        -
          -
        • numero e tipi di costanti
        • -
        • presenza oppure no di -
            -
          • texture
          • -
          • fog
          • -
          • vertex colors
          • -
          • morphing
          • -
          • shadow map
          • -
          • alpha test
          • -
          • transparent
          • -
          -
        • -
        - -

        Le modifiche di questi richiedono la creazione di un nuovo programma di shader. Dovrai impostare:

        - material.needsUpdate = true - -

        Tieni presente che questa operazione potrebbe essere piuttosto lenta e causare scatti nel framerate (specialmente su Windows, poiché la compilazione degli shader è più lenta in DirectX che in OpenGL).

        - -

        Per creare un'esperienza più fluida puoi emulare in una certa misura le modifiche a queste funzionalità avendo valori "fittizi" come luci ad intensità zero, texture bianche, o fog a zero densità.

        - -

        È possibile cambiare liberamente il materiale utilizzato per i blocchi di geometria, tuttavia non è possibile modificare il modo in cui un oggetto è diviso in blocchi (in base ai materiali della faccia).

        - -

        Se è necessario disporre di diverse configurazioni dei materiali durante l'esecuzione:

        -

        Se il numero di materiali / blocchi è piccolo, puoi dividere l'oggetto in anticipo (per esempio capelli / faccia / corpo / vestiti superiori / pantaloni per un umano / davanti / dietro / parte superiore / occhiali / pneumatico / interni di una macchina).

        - -

        Se, invece, il numero è grande (per esempio, ogni faccia potrebbe essere potenzialmente diversa), considera una soluzione divera, come usare attributi / texture per ottenere un aspetto diverso per faccia.

        - -

        Esempi

        -

        - [example:webgl_materials_car WebGL / materials / car]
        - [example:webgl_postprocessing_dof WebGL / webgl_postprocessing / dof] -

        -
        - - -

        Texture

        -
        -

        Se immagini, canvas, video e texture vengono modificate devono avere il flag needsUpdate impostato come segue:

        - - texture.needsUpdate = true; - -

        Le destinazioni di rendering si aggiornano automaticamente.

        - -

        Esempi

        -

        - [example:webgl_materials_video WebGL / materials / video]
        - [example:webgl_rtt WebGL / rtt] -

        - -
        - - -

        Telecamere

        -
        -

        La posizione e il target di una camera vengono aggiornati automaticamente. Se hai bisogno di cambiare

        -
          -
        • - fov -
        • -
        • - aspect -
        • -
        • - near -
        • -
        • - far -
        • -
        -

        - dovrai ricalcolare la matrice di proiezione: -

        - -camera.aspect = window.innerWidth / window.innerHeight; -camera.updateProjectionMatrix(); - -
        - - diff --git a/docs/manual/it/introduction/How-to-use-post-processing.html b/docs/manual/it/introduction/How-to-use-post-processing.html deleted file mode 100644 index 60b3a0d0c0a69c..00000000000000 --- a/docs/manual/it/introduction/How-to-use-post-processing.html +++ /dev/null @@ -1,113 +0,0 @@ - - - - - - - - - -

        Come utilizzare il post-processing (How to use post-processing)

        - -

        - Molte applicazioni 3D visualizzano i loro oggetti 3D direttamente sullo schermo. A volte, tuttavia, si vuole applicare uno o più effetti grafici - come Depth-Of-Field, Bloom, Film Grain o vari tipi di Anti-aliasing. Il Post-processing è una soluzione ampiamente utilizzata per implementare questi effetti. - Prima di tutto, la scena viene renderizzata su un target di rendering che rappresenta un buffer nella memoria della scheda video. - Nella fase successiva, uno o più passaggi di post-processing applicano filtri ed effetti al buffer dell'immagine prima che questa venga infine renderizzata - sullo schermo. -

        -

        - three.js fornisce una soluzione di post-processing completa tramite [page:EffectComposer] per implementare tale flusso di lavoro. -

        - -

        Workflow

        - -

        - Il primo step, nel processo, è quello di importare tutti i file necessari dalla cartella degli esempi. La guida presuppone che si utilizzi - il [link:https://www.npmjs.com/package/three pacchetto npm] ufficiale di three.js. Per la nostra demo di base in questa guida abbiamo - bisogno dei seguenti file: -

        - - - import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'; - import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'; - import { GlitchPass } from 'three/addons/postprocessing/GlitchPass.js'; - - -

        - Dopo che tutti i file sono stati importati con successo, possiamo creare il nostro composer passandogli un'istanza di [page:WebGLRenderer]. -

        - - - const composer = new EffectComposer( renderer ); - - -

        - Quando viene usato un composer è necessario modificare il ciclo di animazine dell'applicazione. Invece di chiamare il metodo render di - [page:WebGLRenderer], usiamo la rispettiva controparte di [page:EffectComposer]. -

        - - - function animate() { - - requestAnimationFrame( animate ); - - composer.render(); - - } - - -

        - Il composer è pronto, ed è possibile configurare la catena di passaggi di post-processing. Questi passaggi sono i responsabili per la creazione - dell'output visivo finale dell'applicazione. Vengono elaborati nello stesso ordine in cui sono stati aggiunti/inseriti. Nel nostro esempio, l'istanza - di `RenderPass` viene eseguita per prima, poi l'istanza di `GlitchPass`. L'ultimo passaggio abilitato della catena viene automaticamente renderizzato sullo schermo. - La configurazione dei passaggi è la seguente: -

        - - - const renderPass = new RenderPass( scene, camera ); - composer.addPass( renderPass ); - - const glitchPass = new GlitchPass(); - composer.addPass( glitchPass ); - - -

        - `RenderPass` viene normalmente posizionata all'inizio della catena per fornire la scena renderizzata come input per il passaggio successivo di post-processing. - Nel nostro caso `GlitchPass` utilizzarà questi dati di immagine per applicare un effetto glitch selvaggio. Guarda questo [link:https://threejs.org/examples/webgl_postprocessing_glitch esempio live] - per vederli in azione. -

        - -

        Passi Built-in

        - -

        - È possibile utilizzare un'ampia gamma di passaggi di post-processing predefiniti forniti dall'engine. Si possono trovare nella - cartella di [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm/postprocessing postprocessing]. -

        - -

        Passi personalizzati

        - -

        - A volte si desidera scrivere uno shader di post-processing personalizzato e includerlo nella catena dei passi di post-processing. - Per questo scenario puoi utilizzare `ShaderPass`. Dopo aver importato il file e lo shader personalizzato, si può usare il seguente codice per - impostare i passaggi: -

        - - - import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js'; - import { LuminosityShader } from 'three/addons/shaders/LuminosityShader.js'; - - // later in your init routine - - const luminosityPass = new ShaderPass( LuminosityShader ); - composer.addPass( luminosityPass ); - - -

        - Il repository fornisce un file chiamato [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/shaders/CopyShader.js CopyShader], il quale è un ottimo - punto di partenza per il tuo shader personalizzato. `CopyShader` copia semplicemente il contenuto dell'immagine del buffer di lettura dell'[page:EffectComposer] - nel buffer di scrittura senza applicare alcun effetto. -

        - - - diff --git a/docs/manual/it/introduction/Installation.html b/docs/manual/it/introduction/Installation.html deleted file mode 100644 index 6c3c457b5a2ac2..00000000000000 --- a/docs/manual/it/introduction/Installation.html +++ /dev/null @@ -1,160 +0,0 @@ - - - - - - - - - -

        Installazione ([name])

        - -

        - È possibile installare three.js con [link:https://www.npmjs.com/ npm] e i moderni strumenti di compilazione, o iniziare rapidamente con un hosting statico o un CDN. Per la maggior parte degli utenti fare l'installazione usando npm è la scelta migliore. -

        - -

        - Qualsiasi soluzione venga scelta, sii coerente e importa tutti i file della stessa versione della libreria. - Mescolare file provenienti da fonti diverse può causare l'inclusione di codice duplicato o addirittura rompere l'applicazione in modo imprevisto. -

        - -

        - Tutti i metodi di installazione di three.js dipendono dai moduli ES (vedi [link:https://eloquentjavascript.net/10_modules.html#h_hF2FmOVxw7 Eloquent JavaScript: ECMAScript Modules]), i quali permettono di includere nel progetto finale solo le parti della libreria necessarie. -

        - -

        Installazione tramite npm

        - -

        - Per installare il modulo npm di [link:https://www.npmjs.com/package/three three], apri il terminale nella cartella del progetto ed esegui: -

        - - - npm install three - - -

        - Il pacchetto verrà scaricato e installato. Quindi sarà pronto per essere importato nel tuo codice: -

        - - - // Opzione 1: Importa l'intera libreria di base di three.js - import * as THREE from 'three'; - - const scene = new THREE.Scene(); - - - // Opzione 2: Importa solo le parti di cui hai bisogno - import { Scene } from 'three'; - - const scene = new Scene(); - - -

        - Quando la libreria viene installata da npm, verrà quasi sempre utilizzato uno [link:https://eloquentjavascript.net/10_modules.html#h_zWTXAU93DC strumento di bundling] per combinare tutti i pacchetti richiesti dal tuo progetto in un unico file JavaScript. Sebbene con three.js si possa utilizzare qualsiasi moderno strumento di bundling, la scelta più popolare è [link:https://webpack.js.org/ webpack]. -

        - -

        - Non tutte le funzioni sono accessibili direttamente attraverso il modulo three (chiamato anche "bare import"). Altre parti popolari della libreria — come i controls, i loaders e gli effetti di post-processing — devono essere importati dalla sottocartella [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm examples/jsm]. Per saperne di più si vedano gli Esempi qui sotto. -

        - -

        - Per saperne di più sui moduli npm, consultare [link:https://eloquentjavascript.net/20_node.html#h_J6hW/SmL/a Eloquent JavaScript: Installing with npm]. -

        - -

        Installazione da CDN o hosting statico

        - -

        - La libreria three.js può essere usata senza alcun sistema di building, sia caricando i file sul tuo server web o usando un CDN esistente. Poiché la libreria si basa su moduli ES, qualsiasi script che fa riferimento ad essa deve usare type="module" come mostrato di seguito. - Inoltre è necessario anche definire un Import Map che risolva il bare module `three`. -

        - - - <script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script> - - <script type="importmap"> - { - "imports": { - "three": "https://unpkg.com/three@<version>/build/three.module.js" - } - } - </script> - - <script type="module"> - - import * as THREE from 'three'; - - const scene = new THREE.Scene(); - - </script> - - -

        - Poiché le mappe di importazione non sono ancora supportate da tutti i browser, è necessario aggiungere il polyfill *es-module-shims.js*. -

        - -

        Addons

        - -

        - Il core di three.js è incentrato sui componenti più importanti di un engine 3D. Molti altri componenti utili - come i controls, i loaders e gli effetti post-processing - sono parte della sottocartella [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm examples/jsm]. Vengono definiti "esempi" perché, pur potendo essere utilizzati in modo immediato, sono anche destinati a essere remixati e personalizzati. Questi componenti vengono sempre mantenuti sincronizzati con la libreria principale, mentre i pacchetti di terze parti su npm sono gestiti da persone differenti e potrebbero non essere aggiornati. -

        - -

        - Non è necessario installare gli addons separatamente, ma dovranno essere importati separatamente. Se three.js è stato installato con npm, è possibile caricare il componente [page:OrbitControls] con: -

        - - - import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; - - const controls = new OrbitControls( camera, renderer.domElement ); - - -

        - Se three.js è stato installato da un CDN, usare lo stesso CDN per installare altri componenti: -

        - - - <script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script> - - <script type="importmap"> - { - "imports": { - "three": "https://unpkg.com/three@<version>/build/three.module.js", - "three/addons/": "https://unpkg.com/three@<version>/examples/jsm/" - } - } - </script> - - <script type="module"> - - import * as THREE from 'three'; - import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; - - const controls = new OrbitControls( camera, renderer.domElement ); - - </script> - - -

        - È importante che tutti i file usino la stessa versione. Non devono essere importati addons diversi con versioni diverse, o usare addons che derivano da una versione differente di three.js. -

        - -

        Compatibilità

        - -

        Import CommonJS

        - -

        - La maggior parte dei bundler JavaScript moderni supportano i moduli ES di default, ma questo non è detto per bundler più vecchi. In questo caso è possibile configurare il bundler per riconoscere i moduli ES. Ad esempio [link:http://browserify.org/ Browserify] ha solo bisogno del plugin [link:https://github.com/babel/babelify babelify]. -

        - -

        Node.js

        - -

        - Poiché three.js è stato creato per il web, dipende dal browser e dalle API del DOM che non sempre esistono in Node.js. Alcuni di questi problemi possono essere risolti usando dei "tappa buchi" come [link:https://github.com/stackgl/headless-gl headless-gl], o sostituendo i componenti come [page:TextureLoader] con alternative personalizzate. Altre API del DOM potrebbero essere profondamente intrecciate con il codice che le utilizza e potrebbe essere più difficile aggirarle. Accettiamo benvolentieri le pull request semplici e gestibili per migliorare il supporto di Node.js, ma raccomandiamo di aprire prima una issue per discutere dei tuoi miglioramenti. -

        - -

        - Assicurati di aggiungere `{ "type": "module" }` al tuo `package.json` per abilitare i moduli ES nel tuo progetto Node. -

        - - - diff --git a/docs/manual/it/introduction/Libraries-and-Plugins.html b/docs/manual/it/introduction/Libraries-and-Plugins.html deleted file mode 100644 index 4d74091c0e4162..00000000000000 --- a/docs/manual/it/introduction/Libraries-and-Plugins.html +++ /dev/null @@ -1,109 +0,0 @@ - - - - - - - - - -

        Librerie e Plugin ([name])

        - -

        - Di seguito sono elencate librerie e plugin, sviluppati esternamente, compatibili con three.js. - Questo elenco e i pacchetti associati sono mantenuti e gestiti dalla community e non è garantito che siano - aggiornati. Se vuoi aggiornare questa lista apri una PR! -

        - -

        Fisica

        - -
          -
        • [link:https://github.com/lo-th/Oimo.js/ Oimo.js]
        • -
        • [link:https://enable3d.io/ enable3d]
        • -
        • [link:https://github.com/kripken/ammo.js/ ammo.js]
        • -
        • [link:https://github.com/pmndrs/cannon-es cannon-es]
        • -
        - -

        Post-processing

        - -

        - Oltre agli [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm/postprocessing effetti di post-processing ufficiali di three.js] - è disponibile tramite librerie esterne il supporto per alcuni effetti e framework aggiuntivi. -

        - -
          -
        • [link:https://github.com/vanruesc/postprocessing postprocessing]
        • -
        - -

        Intersezione e Performance Raycast

        - -
          -
        • [link:https://github.com/gkjohnson/three-mesh-bvh three-mesh-bvh]
        • -
        - -

        Path Tracing

        - -
          -
        • [link:https://github.com/gkjohnson/three-gpu-pathtracer three-gpu-pathtracer]
        • -
        - -

        File Formats

        - -

        - Oltre [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm/loaders ai loaders ufficili di three.js], - è disponibile tramite librerie esterne il supporto per alcuni formati aggiuntivi. -

        - -
          -
        • [link:https://github.com/gkjohnson/urdf-loaders/tree/master/javascript urdf-loader]
        • -
        • [link:https://github.com/NASA-AMMOS/3DTilesRendererJS 3d-tiles-renderer-js]
        • -
        • [link:https://github.com/kaisalmen/WWOBJLoader WebWorker OBJLoader]
        • -
        • [link:https://github.com/IFCjs/web-ifc-three IFC.js]
        • -
        - -

        Geometria

        - -
          -
        • [link:https://github.com/spite/THREE.MeshLine THREE.MeshLine]
        • -
        - -

        Layout e Testo 3D

        - -
          -
        • [link:https://github.com/protectwise/troika/tree/master/packages/troika-three-text troika-three-text]
        • -
        • [link:https://github.com/felixmariotto/three-mesh-ui three-mesh-ui]
        • -
        - -

        Sistemi di particelle

        - -
          -
        • [link:https://github.com/Alchemist0823/three.quarks three.quarks]
        • -
        • [link:https://github.com/creativelifeform/three-nebula three-nebula]
        • -
        - -

        Cinematica inversa

        - -
          -
        • [link:https://github.com/jsantell/THREE.IK THREE.IK]
        • -
        • [link:https://github.com/lo-th/fullik fullik]
        • -
        • [link:https://github.com/gkjohnson/closed-chain-ik-js closed-chain-ik]
        • -
        - -

        Game AI

        - -
          -
        • [link:https://mugen87.github.io/yuka/ yuka]
        • -
        • [link:https://github.com/donmccurdy/three-pathfinding three-pathfinding]
        • -
        - -

        Wrappers e Frameworks

        - -
          -
        • [link:https://aframe.io/ A-Frame]
        • -
        • [link:https://github.com/pmndrs/react-three-fiber react-three-fiber]
        • -
        • [link:https://github.com/ecsyjs/ecsy-three ECSY]
        • -
        • [link:https://threlte.xyz/ Threlte]
        • -
        - - - diff --git a/docs/manual/it/introduction/Loading-3D-models.html b/docs/manual/it/introduction/Loading-3D-models.html deleted file mode 100644 index 6f2d29846321cc..00000000000000 --- a/docs/manual/it/introduction/Loading-3D-models.html +++ /dev/null @@ -1,155 +0,0 @@ - - - - - - - - - - - -

        Caricare modelli 3D ([name])

        - -

        - I modelli 3D sono disponibili in centinai di formati, ognuno con uno scopo differente, diverse funzioni - e complessità varie. Sebbene - three.js metta a disposizione molti loader, la scelta del formato e del flusso di lavoro giusti farà risparmiare tempo e frustrazione in seguito. - Con alcuni formati è difficile lavorare, possono essere inefficienti per le esperienze in tempo reale, o semplicemente non supportati al momento. -

        - -

        - Questa guida mette a dispozione un flusso di lavoro consigliato per la maggior parte degli utenti, e dei suggerimenti - per affrontare i malfunzionamenti nel caso in cui non funzionasse come ci si aspetta. -

        - -

        Prima di iniziare

        - -

        - Se siete alle prime armi con la gestione di un server locale, - iniziate prima di tutto a capire [link:#manual/introduction/Installation installation]. - Molti errori comuni nella visualizzazione dei modelli 3D possono essere evitati gestendo correttamente l'hosting dei file. -

        - -

        Workflow consigliato

        - -

        - Dove possibile, consigliamo di utilizzare il formato glTF (GL Transmission Format). - Entrambe le versioni .GLB e .GLTF sono supportate. - Il formato glTF è incentrato sulla distribuzione di asset in runtime, è compatto da trasmette e veloce da caricare. - Le carattestistiche includono mesh, materiali, texture, skin, skeleton, morph target, animazioni, luci, e camera. -

        - -

        - Modelli glTF pubblici sono disponibili su siti come - Sketchfab, o vari strumenti di sviluppo di modelli includono l'esportazione glTF: -

        - - - -

        - Se i tuoi strumenti preferiti di sviluppo dei modelli, non supportano il formato glTF, - considera di richiedere l'esportazione glTF agli autori, - o di fare un post sul thread della roadmap di glTF. -

        - -

        - Quando il formato glTF non è disponibile, i formati popolari come FBX, OBJ, o COLLADA - sono comunque disponibili e mantenuti regolarmente. -

        - -

        Caricamento

        - -

        - Solo alcuni loader (ad es. [page:ObjectLoader]) sono inclusi di default con three.js — altri devono essere aggiunti all'applicazione individualmente. -

        - - - import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; - - -

        - Una volta importato un loader, sei pronto per aggiungere e caricare un modello alla scena. La sintassi - varia a seconda del loader utilizzato - quando vengono utilizzati altri formati, contralla gli esempi e la - documentazione del loader. Per il formato glTF, l'utilizzo con gli script globali sarebbe: -

        - - - const loader = new GLTFLoader(); - - loader.load( 'path/to/model.glb', function ( gltf ) { - - scene.add( gltf.scene ); - - }, undefined, function ( error ) { - - console.error( error ); - - } ); - - -

        - Per maggiori dettagli consulta la [page:GLTFLoader documentazione GLTFLoader]. -

        - -

        Troubleshooting

        - -

        - Hai passato ore a modellare un capolavoro artigianale, lo carichi nella pagina web e — oh no! 😭 - è distorto, scolorito o mancante. - Inizia con questi passaggi per la risoluzione dei problemi: -

        - -
          -
        1. - Controlla la console JavaScript per vedere se ci sono errori, - ed accertati di aver usato la funzione di callback `onError` del meotodo `.load()` - per visualizzare il risultato nella console. -
        2. -
        3. - Visualizza il modello in un'altra applicazione. Per il formato glTF, sono disponibili visualizzatori drag-and-drop per - three.js e - babylon.js. - Se il modello viene visualizzato correttamente in una o più applicazioni, - segnala il bug a three.js. - Se il modello non è visualizzabile in nessuna applicazione, incoraggiamo vivamente di segnalare un bug - all'applicazione utilizzata per creare il modello. -
        4. -
        5. - Prova a scalare il modello verso l'alto o il basso di un fattore di 1000. Molti modelli sono scalati diversamente, - e molti modelli di grandi dimensioni non vengono visualizzati se la telecamera si trova all'interno del modello. -
        6. -
        7. - Prova ad aggiungere e posizionare una luce nella scena. Il modello potrebbe essere nascosto nell'oscurità. -
        8. -
        9. - Cerca le richieste delle texture fallite nel tab network degli strumenti per sviluppatori del browser, - come `"C:\\Path\To\Model\texture.jpg"`. Utilizza path relativi al tuo modello come `images/texture.jpg` - - ciò potrebbe richiedere una modifica del file del modello in un editor di testo. -
        10. -
        - -

        Chiedere aiuto

        - -

        - Se hai eseguito la procedura di risoluzione dei problemi qui sopra e il tuo modello ancora non funziona, - il giusto approccio è chiedere aiuto per una soluzione più veloce. Scrivi una domanda sul - forum three.js e, quando possibile, - includi il tuo modello (o un modello più semplice con lo stesso problema) in qualsiasi formato disponibile. - Includi informazioni sufficienti per consentire a qualcun altro di riprodurre rapidamente il problema - idealmente, una demo dal vivo. -

        - - - - diff --git a/docs/manual/it/introduction/Matrix-transformations.html b/docs/manual/it/introduction/Matrix-transformations.html deleted file mode 100644 index 2b44eee4ddb05c..00000000000000 --- a/docs/manual/it/introduction/Matrix-transformations.html +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - - - - -

        Trasformazioni di matrici ([name])

        - -

        - Three.js utilizza le `matrici` per codificare le trasformazioni 3D, traslazioni (posizione), rotazioni e ridimensionamento. Ogni istanza di [page:Object3D] - ha una [page:Object3D.matrix matrice] che memorizza la posizione, la rotazione e il ridimensionamento. Questa pagina descrive come aggiornare la trasformazione - di un oggetto. -

        - -

        Proprietà di convenienza e `matrixAutoUpdate`

        - -

        - Ci sono due modi per aggiornare la trasformazione di un oggetto: -

        -
          -
        1. - Modificare le proprietà `position`, `quaternion` e `scale` dell'oggetto e lasciare che three.js ricalcoli la - matrice dell'oggetto da queste proprietà: - -object.position.copy( start_position ); -object.quaternion.copy( quaternion ); - - Per impostazione predefinita, la proprietà `matrixAutoUpdate` è impostata su true e la matrice viene ricalcolata automaticamente. - Se l'oggetto è statico, o desideri controllare manualmente quando avviene il ricalcolo, è possibile ottenere prestazioni migliori se la - proprietà è impostata a false. - -object.matrixAutoUpdate = false; - - E dopo aver modificato le proprietà, aggiornare manualmente la matrice: - -object.updateMatrix(); - -
        2. -
        3. - Modificare direttamente la matrice dell'oggetto. La classe [page:Matrix4] possiede vari metodi per modificare la matrice: - -object.matrix.setRotationFromQuaternion( quaternion ); -object.matrix.setPosition( start_position ); -object.matrixAutoUpdate = false; - - Nota che, in questo caso, `matrixAutoUpdate` deve essere impostata a `false`, e devi essere sicuro di non chiamare `updateMatrix`. - La chiamata di `updateMatrix` eliminerà le modifiche manuali apportate alla matrice, ricalcolando la matrice da `position`, `scale`, e così via. -
        4. -
        - -

        Oggetto e matrici del mondo (world matrices)

        -

        - La [page:Object3D.matrix matrice] dell'oggetto memorizza la trsformazione dell'oggetto relativa al [page:Object3D.parent genitore] dell'oggetto: - per ottenere la trasformazione dell'oggetto nelle coordinate del mondo, è necessario accedere alla [page:Object3D.matrixWorld] dell'oggetto. -

        -

        - Quando la trasformazione dell'oggetto padre o dell'oggetto figlio cambia, puoi richiedere che la [page:Object3D.matrixWorld matrixWorld] dell'oggetto - figlio venga aggiornata chiamando il metodo [page:Object3D.updateMatrixWorld updateMatrixWorld](). -

        - -

        Rotazione e Quaternione

        -

        - Three.js fornisce due modi per rappresentare le rotazioni 3D: [page:Euler angoli di Eulero] e [page:Quaternion Quaternioni], - nonché metodi per la conversione tra i due. Gli angoli di Eulero sono soggetti ad un problema chiamato "gimbal lock", in cui alcune - configurazioni possono perdere un grado di libertà (impedendo all'oggetto di essere ruotato su un asse). Per questo motivo, le - rotazioni degli oggetti sono sempre memorizzate nei [page:Object3D.quaternion quaternioni] dell'oggetto. -

        -

        - Le versioni precedenti della libreria includevano una proprietà `useQuaternion` che, se impostata a false, faceva si che la - [page:Object3D.matrix matrix] dell'oggetto fosse calcolata da un angolo di Eulero. Questa pratica è deprecata, si deve invece - usare il metodo [page:Object3D.setRotationFromEuler setRotationFromEuler], il quale aggiornerà il quaternione. -

        - - - diff --git a/docs/manual/it/introduction/Useful-links.html b/docs/manual/it/introduction/Useful-links.html deleted file mode 100644 index a565fdb2c55636..00000000000000 --- a/docs/manual/it/introduction/Useful-links.html +++ /dev/null @@ -1,176 +0,0 @@ - - - - - - - - - -

        Link utili ([name])

        - -

        - Quella che segue è una raccolta di link che potresti trovare utili durante lo studio di three.js.
        - Se trovi qualcosa che ti piacerebbe aggiungere a questa lista o se pensi che uno dei link qui sotto non sia rilevante o non funzioni, - sentiti libero di modificare la pagina cliccando sul bottone 'edit' in basso a destra.

        - - Considera anche che, essendo three.js in rapida crescita, molti di questi link conterranno informazioni non aggiornate - - se qualcosa non funziona come ti aspetti o come uno di questi link dice che dovrebbe, controlla la console - del browser per verificare la presenza di warning o errori. Controlla anche le pagine dei documenti pertinenti. -

        - -

        Forum di supporto

        -

        - Three.js ufficialmente utilizza il [link:https://discourse.threejs.org/ forum] e [link:http://stackoverflow.com/tags/three.js/info Stack Overflow] per richieste di aiuto. - Se hai bisogno di assistenza con qualcosa, questi sono i posti dove andare a chiedere e cercare una soluzione. NON aprire una issue su Github per richieste di aiuto. -

        - -

        Corsi e tutorial

        - -

        Iniziare con three.js

        -
          -
        • - [link:https://threejs.org/manual/#en/fundamentals Three.js Fundamentals starting lesson] -
        • -
        • - [link:https://codepen.io/rachsmith/post/beginning-with-3d-webgl-pt-1-the-scene Beginning with 3D WebGL] di [link:https://codepen.io/rachsmith/ Rachel Smith]. -
        • -
        • - [link:https://www.august.com.au/blog/animating-scenes-with-webgl-three-js/ Animating scenes with WebGL and three.js] -
        • -
        - -

        Articoli e corsi avanzati

        -
          -
        • - [link:https://threejs-journey.com/ Three Journey] Corso di [link:https://bruno-simon.com/ Bruno Simon] - Insegna ai beginners come usare Three.js step by step -
        • -
        • - [link:https://discoverthreejs.com/ Discover three.js] -
        • -
        • - [link:http://blog.cjgammon.com/ Collection of tutorials] di [link:http://www.cjgammon.com/ CJ Gammon]. -
        • -
        • - [link:https://medium.com/soffritti.pierfrancesco/glossy-spheres-in-three-js-bfd2785d4857 Glossy spheres in three.js]. -
        • -
        • - [link:https://www.udacity.com/course/cs291 Interactive 3D Graphics] - un corso gratuito su Udacity che insegna i fondamenti della grafica 3D, - utilizza three.js come strumenti di coding. -
        • -
        • - [Link:https://aerotwist.com/tutorials/ Aerotwist] tutorial di [link:https://github.com/paullewis/ Paul Lewis]. -
        • -
        • - [link:https://discourse.threejs.org/t/three-js-bookshelf/2468 Three.js Bookshelf] - Stai cercando più risorse relative a three.js o alla computer graphics in generale? - Controlla la selezione di letteratura suggerita dalla community. -
        • -
        - -

        News e Aggiornamenti

        -
          -
        • - [link:https://twitter.com/hashtag/threejs Three.js on Twitter] -
        • -
        • - [link:http://www.reddit.com/r/threejs/ Three.js on reddit] -
        • -
        • - [link:http://www.reddit.com/r/webgl/ WebGL on reddit] -
        • -
        - -

        Esempi

        -
          -
        • - [link:https://github.com/edwinwebb/three-seed/ three-seed] - starter kit di un progetto three.js con ES6 e Webpack -
        • -
        • - [link:http://stemkoski.github.io/Three.js/index.html Professor Stemkoskis Examples] - una raccolta di esempi per principianti creati utilizzando three.js r60. -
        • -
        • - [link:https://threejs.org/examples/ Esempi three.js ufficiali] - questi esempi sono gestiti come parte del repository di three.js e usano sempre l'ultima versione della libreria. -
        • -
        • - [link:https://raw.githack.com/mrdoob/three.js/dev/examples/ Esempi three.js ufficiali del branch di dev] - - Come sopra, tranne che questi usano il branch di dev di three.js, e vengono utilizzati per verificare - che tutto funzioni mentre viene sviluppato three.js. -
        • -
        - -

        Strumenti

        -
          -
        • - [link:https://github.com/tbensky/physgl physgl.org] - Applicazione front-end JavaScript con un wrapper per three.js, per avvicinare la grafica WebGL - agli studenti che stanno imparando fisica e matematica. -
        • -
        • - [link:https://whsjs.readme.io/ Whitestorm.js] – Framework modulare three.js con plugin per la fisica AmmoNext. -
        • -
        • - [link:http://zz85.github.io/zz85-bookmarklets/threelabs.html Three.js Inspector] -
        • -
        • - [link:http://idflood.github.io/ThreeNodes.js/ ThreeNodes.js]. -
        • -
        • - [link:https://marketplace.visualstudio.com/items?itemName=slevesque.shader vscode shader] - Syntax highlighter per il linguaggio dello shader. -
          - [link:https://marketplace.visualstudio.com/items?itemName=bierner.comment-tagged-templates vscode comment-tagged-templates] - Syntax highlighting per le stringhe di template etichettate - usando i commenti al linguaggio shader, come: glsl.js. -
        • -
        • - [link:https://github.com/MozillaReality/WebXR-emulator-extension WebXR-emulator-extension] -
        • -
        - -

        Riferimenti WebGL

        -
          -
        • - [link:https://www.khronos.org/files/webgl/webgl-reference-card-1_0.pdf webgl-reference-card.pdf] - Riferimento di tutte le parole chiave, terminologia, sintassi e definizioni WebGL e GLSL. -
        • -
        - -

        Vecchi Link

        -

        - Questi link sono conservati per scopi storici - potresti trovarli utili, ma tieni presente che - potrebbero contenere informazioni relative a versioni molto vecchie di three.js. -

        - -
          -
        • - [link:https://www.youtube.com/watch?v=Dir4KO9RdhM AlterQualia at WebGL Camp 3] -
        • -
        • - [link:http://yomotsu.github.io/threejs-examples/ Yomotsus Examples] - una collezione di esempi che utilizza la versione r45 di three.js. -
        • -
        • - [link:http://fhtr.org/BasicsOfThreeJS/#1 Introduction to Three.js] di [link:http://github.com/kig/ Ilmari Heikkinen] (slideshow). -
        • -
        • - [link:http://www.slideshare.net/yomotsu/webgl-and-threejs WebGL and Three.js] di [link:http://github.com/yomotsu Akihiro Oyamada] (slideshow). -
        • -
        • - [link:https://www.youtube.com/watch?v=VdQnOaolrPA Trigger Rally] di [link:https://github.com/jareiko jareiko] (video). -
        • -
        • - [link:http://blackjk3.github.io/threefab/ ThreeFab] - editor di scene, mantenuto fino alla versione r50 di three.js. -
        • -
        • - [link:http://bkcore.com/blog/3d/webgl-three-js-workflow-tips.html Max to Three.js workflow tips and tricks] di [link:https://github.com/BKcore BKcore] -
        • -
        • - [link:http://12devsofxmas.co.uk/2012/01/webgl-and-three-js/ A whirlwind look at Three.js] - di [link:http://github.com/nrocy Paul King] -
        • -
        • - [link:http://bkcore.com/blog/3d/webgl-three-js-animated-selective-glow.html Animated selective glow in Three.js] - di [link:https://github.com/BKcore BKcore] -
        • -
        • - [link:http://www.natural-science.or.jp/article/20120220155529.php Building A Physics Simulation Environment] - tutorial di three.js in Giapponese -
        • -
        - - - diff --git a/docs/manual/it/introduction/WebGL-compatibility-check.html b/docs/manual/it/introduction/WebGL-compatibility-check.html deleted file mode 100644 index d6381db13769cf..00000000000000 --- a/docs/manual/it/introduction/WebGL-compatibility-check.html +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - -

        Controllo compatibilità WebGL ([name])

        -

        - Anche se questo sta diventano sempre meno un problema, alcuni dispositivi o browser potrebbero ancora non supportare WebGL. - Il seguente codice è utile per controllare se WebGL è supportato, infatti se non lo fosse viene mostrato un messaggio di errore all'utente. - Importa il modulo di rilevamento del supporto WebGL ed esegui quanto segue prima di tentare di eseguire il rendering di qualsiasi cosa. -

        - - - - import WebGL from 'three/addons/capabilities/WebGL.js'; - - if ( WebGL.isWebGLAvailable() ) { - - // Avviare qui la funzione o altre inizializzazioni - animate(); - - } else { - - const warning = WebGL.getWebGLErrorMessage(); - document.getElementById( 'container' ).appendChild( warning ); - - } - - - diff --git a/docs/manual/ja/introduction/Animation-system.html b/docs/manual/ja/introduction/Animation-system.html deleted file mode 100644 index 0aaf80441e1a18..00000000000000 --- a/docs/manual/ja/introduction/Animation-system.html +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - - - - -

        [name]

        - -

        概要

        - -

        - three.jsのアニメーションシステムの中では、モデルの様々なプロパティをアニメーション化できます。 - 例えば、装飾を行なったモデル([page:SkinnedMesh skinned and rigged model])の骨格や、morphTargets、異なるマテリアルの要素(色、不透明度、有無)、可視性、変形などを操作できます。 - アニメーションの要素は、フェードインやフェードアウト、クロスフェード、重ね合わせが可能です。 - 同一のオブジェクトであっても異なるものであっても、異なるアニメーションの比重やタイムスケールは独立して変更出来ます。また、同じオブジェクトでも、異なるオブジェクトでもアニメーションは同期出来ます。

        - - これらを一つのシステムで達成するために、three.jsのアニメーションシステムは2015に完全に変更されました([link:https://github.com/mrdoob/three.js/issues/6881 link])。古い情報に気をつけてください!three.jsのアニメーションシステムは現在はUnityやUnreal Engine 4と似た設計になっています。このページではメインの要素と、それがどうやって動くのかを簡単に説明します。 - -

        - -

        Animation Clips

        - -

        - アニメーション付きの3Dオブジェクトをインポートすることに成功した場合(そのオブジェクトが骨格なのか、モーフターゲットなのか、はたまた両方なのかには関係なく)、レスポンスフィールドのうちの一つは"animations"という配列で、そのモデルの[page:AnimationClip AnimationClips]を含んでいるはずです(下記の利用可能なローダーを参照)。 - アニメーション付きの3Dオブジェクトをインポートすることに成功する状況というのは、例えばBlenderから[link:https://github.com/KhronosGroup/glTF-Blender-IO glTF Blender exporter]でオブジェクトをエクスポートして、そのオブジェクトを[page:GLTFLoader]を使って、three.jsに読みこむ、といったことが挙げられます。

        - - - *AnimationClip*は普通、オブジェクトの特定の動作のデータを持っています。 - 例えば、メッシュの場合、1つめのAnimationClipはウォークサイクルのためのもの、2つ目のAnimationClipはジャンプ用の、3つ目はサイドステップ用という風に動作のデータをAnimationClipに持たせています。 - -

        - -

        Keyframe Tracks

        - -

        - - こういった*AnimationClip*の中では、アニメーションの要素のデータは別の[page:KeyframeTrack]に保存されています。キャラクターオブジェクトが骨格を持っていると仮定すると、あるキートラックフレームは腕の下の方の骨の、位置の時間変化のデータを保存し、別のキートラックは同じ骨の回転成分の動きのデータを保存し、また別のキートラックは他の骨の位置や回転、スケールの変化といったデータを保存します。AnimationClipは、このようなたくさんのトラックで構成できることがわかります。 - - モデルがmorph targetsを持っていると仮定すると(例えば、友好的な顔から怒った顔にモーフィングするような場合)、それぞれのトラックは特定のモーフィングターゲットの[page:Mesh.morphTargetInfluences influence]がclipのパフォーマンス中にどうやって変化するかについての情報を持っています。 - -

        - -

        Animation Mixer

        - -

        - - 保存されたデータは、アニメーションのベースを形成するだけで、実際のアニメーションの再生は[page:AnimationMixer]によってコントロールされます。AnimationMixerはアニメーションのプレイヤーであるだけでなく、本物のミキサーコンソールのようなハードウェアのシミレーションでもあります。これによって、複数のアニメーションを同時にコントロールして、ブレンドしたり、マージしたり出来ます。 - -

        - -

        Animation Actions

        - -

        - - [page:AnimationAction AnimationActions]によってコントールできるので、*AnimationMixer*自体の(一般的な)メソッドやプロパティは少ないです。*AnimationAction*を設定することで、あるミキサー上での*AnimationClip*の再生や、一時停止、停止のタイミングであったり、clipをリピートする頻度やリピートの有無、フェードや時間変化の有無、クロスフェードやシンクロなどの補助的なことを設定することが出来ます。 - -

        - -

        Animation Object Groups

        - -

        - - オブジェクトのグループに、アニメーションの状態を受け取らせたい場合は、[page:AnimationObjectGroup]が使えます。 - -

        - -

        サポートされているフォーマットとloader

        - -

        - 全てのモデル形式がアニメーションを含んでいるわけではないことに注意してください(OBJは特に含まれていません)。 - またthree.jsのloaderのうちのいくつかしか[page:AnimationClip AnimationClip]シーケンスをサポートしていないことにも注意してください。このアニメーションのタイプをサポートしているものがいくつかあります。 -

        - -
          -
        • [page:ObjectLoader THREE.ObjectLoader]
        • -
        • THREE.BVHLoader
        • -
        • THREE.ColladaLoader
        • -
        • THREE.FBXLoader
        • -
        • [page:GLTFLoader THREE.GLTFLoader]
        • -
        • THREE.MMDLoader
        • -
        - -

        - 3ds maxとMayaは今のところ、複数のアニメーション(同じタイムラインにないもの)を一つのファイルに直接エクスポートすることは出来ません -

        - -

        Example

        - - - let mesh; - - // Create an AnimationMixer, and get the list of AnimationClip instances - const mixer = new THREE.AnimationMixer( mesh ); - const clips = mesh.animations; - - // Update the mixer on each frame - function update () { - mixer.update( deltaSeconds ); - } - - // Play a specific animation - const clip = THREE.AnimationClip.findByName( clips, 'dance' ); - const action = mixer.clipAction( clip ); - action.play(); - - // Play all animations - clips.forEach( function ( clip ) { - mixer.clipAction( clip ).play(); - } ); - - - - diff --git a/docs/manual/ja/introduction/Creating-a-scene.html b/docs/manual/ja/introduction/Creating-a-scene.html deleted file mode 100644 index 45bbf763d0e45d..00000000000000 --- a/docs/manual/ja/introduction/Creating-a-scene.html +++ /dev/null @@ -1,164 +0,0 @@ - - - - - - - - - -

        [name]

        - -

        このセクションでは、three.jsを簡単に紹介します。まず、回転するキューブを描画することから始めます。困った時には、ページの下にあるサンプルコードを参考にしてみてください。

        - -

        始める前に

        - -

        three.jsを使う前に、表示するための場所が必要です。Save the following HTML to a file on your computer and open it in your browser.

        - - - <!DOCTYPE html> - <html> - <head> - <meta charset="utf-8"> - <title>My first three.js app</title> - <style> - body { margin: 0; } - </style> - </head> - <body> - <script type="module"> - import * as THREE from 'https://unpkg.com/three/build/three.module.js'; - - // Our Javascript will go here. - </script> - </body> - </html> - - -

        これで完了です。下のコードは全て空の<script>タグに挿入されます。

        - -

        シーンの作成

        - -

        実際にthree.jsで何かものを表示できるようにするには、scene、camera、rendererの3つが必要です。

        - - - const scene = new THREE.Scene(); - const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); - - const renderer = new THREE.WebGLRenderer(); - renderer.setSize( window.innerWidth, window.innerHeight ); - document.body.appendChild( renderer.domElement ); - - -

        この時点でscene、camera、rendererの設定が完了したことになります。

        - -

        three.jsにはcameraには種類がありますが、今回はPerspectiveCameraを使ってみましょう。

        - -

        最初の属性は、field of viewです。FOV(field of view)は、任意の瞬間にディスプレイ上で見られるシーンの範囲で、単位は度数です。

        - -

        2つ目はアスペクト比です。アスペクト比は要素の幅を高さで割ったもので、描画する際には、アスペクト比を使うことが多いと思います。そうしないと、ワイドスクリーンのテレビで古い映画を再生したときと同じようになってしまいます。

        - -

        次の2つの属性は、nearfarです。この二つの値を設定することで、farの値よりもカメラから離れたオブジェクトやnearの値よりも近いオブジェクトはレンダリングされなくなります。今回はこのことを気にする必要はありませんが、より良いパフォーマンスを得るために、アプリで変更するということがあるかもしれません。

        - -

        次はrendererです。ここでmagicが起きます。ここで使用しているWebGLRendererの他にも、three.jsにはいくつかの機能があり、古いブラウザを使用しているユーザーや、何らかの理由でWebGLをサポートしていないユーザーのための後方互換として使用されています。

        - -

        rendererのインスタンスを作成することに加えて、アプリを描画するサイズを設定する必要があります。アプリで塗りつぶしたい領域の幅と高さを使用することをお勧めします(この場合、ブラウザウィンドウの幅と高さ)。パフォーマンスを重視するアプリの場合、setSizewindow.innerWidth/2window.innerHeight/2のような小さな値を与えることもできます。こうすることでアプリは半分のサイズでレンダリングされます。

        - -

        アプリのサイズを維持しつつ、より低い解像度でレンダリングしたい場合は、updateStyle (3番目の引数)としてfalseを指定して setSize を呼び出すことで行うことができます。例えば、setSize(window.innerWidth/2, window.innerHeight/2, false)は、<canvas>の幅と高さが100%の場合、アプリを半分の解像度でレンダリングします。

        - -

        最後に、HTMLドキュメントに renderer 要素を追加します。これは、rendererがシーンを表示するために使用する <canvas> 要素です。

        - -

        "それはそれとして、冒頭で言っていたキューブはどこにあるの?" いますぐ追加しましょう。

        - - - const geometry = new THREE.BoxGeometry( 1, 1, 1 ); - const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); - const cube = new THREE.Mesh( geometry, material ); - scene.add( cube ); - - camera.position.z = 5; - - -

        キューブを作成するには、BoxGeometryが必要です。これは、キューブのすべての点 (頂点) および塗りつぶし()を含むオブジェクトです。これについては、今後さらに詳しく説明していきます。

        - -

        形状を表すジオメトリに加えて、色をつけるためのマテリアルが必要です。Three.jsにはいくつかのマテリアルが付属していますが、ここではMeshBasicMaterialだけを使うことにします。すべてのマテリアルは、それらに適用されるプロパティのオブジェクトを取ります。物事を非常にシンプルに保つために、0x00ff00のcolor属性を与えるだけにします。これは、CSSやPhotoshopで色が動作するのと同じ方法で動作します(hex colors)。

        - -

        3番目に必要なのは、メッシュです。メッシュとは、ジオメトリを受け取り、それにマテリアルを適用するオブジェクトのことで、シーンに挿入して自由に動作させることができます。

        - -

        デフォルトでは、scene.add() を呼び出すと、追加したものが座標 (0,0,0) に追加されます。これにより、カメラとキューブの両方が互いに内側になってしまいます。これを回避するために、カメラを少しずらします。

        - -

        sceneのレンダリング

        - -

        先ほど作成したHTMLファイルに上のコードをコピーしても何も表示されません。これは、実際にはまだ何も描画していないからです。そのためには、レンダリングまたはアニメーションループと呼ばれるものが必要です。

        - - - function animate() { - requestAnimationFrame( animate ); - renderer.render( scene, camera ); - } - animate(); - - -

        このコードでは、画面が更新されるたびにrendererがシーンを描画するループを作成しています(一般的な画面では、これは1秒間に60回を意味します)。ブラウザでゲームを書くのが初めての方は、「なぜ setIntervalでやらないのか」と言うかもしれません。おそらく最も重要なのは、ユーザーが別のブラウザタブに移動するときに一時停止し、貴重な処理能力とバッテリー寿命を無駄にしないことです。

        - -

        キューブのアニメーション

        - -

        始める前に作成したファイルに上記のコードをすべて挿入すると、緑色のボックスが表示されるはずです。これを回転させて少し面白くしてみましょう。

        - -

        animate 関数の中でrenderer.render を呼び出している箇所のすぐ上に以下のコードを追加します。

        - - - cube.rotation.x += 0.01; - cube.rotation.y += 0.01; - - -

        これはフレームごとに実行され(通常は1秒間に60回)、キューブに素敵な回転アニメーションを与えます。基本的に、アプリの実行中に移動または変更したいものはすべて、アニメーションループを通過する必要があります。もちろん、そこから他の関数を呼び出すこともできます。

        - -

        成果

        -

        おめでとうございます。これで初めてのthree.jsアプリが完成しました。簡単なことですが、誰でもはじめは初心者です。

        - -

        今回使用したコードは[link:https://jsfiddle.net/0c1oqf38/ live example]にあり編集可能です。紹介したコードがどうやって動作するかをより理解するために、それを使って遊んでみてください。

        - - - <!DOCTYPE html> - <html> - <head> - <title>My first three.js app</title> - <style> - body { margin: 0; } - </style> - </head> - <body> - <script type="module"> - import * as THREE from 'https://unpkg.com/three/build/three.module.js'; - - const scene = new THREE.Scene(); - const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); - - const renderer = new THREE.WebGLRenderer(); - renderer.setSize( window.innerWidth, window.innerHeight ); - document.body.appendChild( renderer.domElement ); - - const geometry = new THREE.BoxGeometry( 1, 1, 1 ); - const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); - const cube = new THREE.Mesh( geometry, material ); - scene.add( cube ); - - camera.position.z = 5; - - function animate() { - requestAnimationFrame( animate ); - - cube.rotation.x += 0.01; - cube.rotation.y += 0.01; - - renderer.render( scene, camera ); - } - - animate(); - </script> - </body> - </html> - - - diff --git a/docs/manual/ja/introduction/Creating-text.html b/docs/manual/ja/introduction/Creating-text.html deleted file mode 100644 index de1e3375711257..00000000000000 --- a/docs/manual/ja/introduction/Creating-text.html +++ /dev/null @@ -1,108 +0,0 @@ - - - - - - - - - -

        [name]

        -
        -

        - three.jsアプリケーションを作る上でテキストを使う必要がある場合がしばしばあります。それを実現する方法はいくつかあります。 -

        -
        - -

        1. DOM + CSS

        -
        -

        - HTMLを使用することは、一般的にテキストを追加する最も簡単で早い方法です。これは、three.jsにテキストを追加する際に主に使用されている方法です。 -

        -

        以下のようにすることでコンテンツを追加できます。

        - <div id="info">Description</div> - -

        - そして、three.jsをフルスクリーンで使用する場合は、CSSを使ってz-indexの絶対的な位置を他の要素よりも上にしておきましょう。 -

        - - -#info { - position: absolute; - top: 10px; - width: 100%; - text-align: center; - z-index: 100; - display:block; -} - - -
        - - - -

        2.テキストをcanvasに書いて、[page:Texture]として使用する

        -
        -

        three.jsのsceneで平面上にテキストを簡単に描きたい場合は、この方法を使用します。

        -
        - - -

        3.お気に入りの3Dアプリケーションでモデルを作成し、three.jsに書き出す

        -
        -

        他の3Dアプリケーションを使用して、three.jsにインポートしたい場合にこの方法を使用します。

        -
        - - - -

        4. Procedural Text Geometry

        -
        -

        - 純粋なTHREE.jsで作業したい場合や、手続き的に動的な3Dのテキストジオメトリを作成したい場合、メッシュで実現することが出来ます。そのメッシュのジオメトリはTHREE.TextGeometryインスタンスを使います。 -

        -

        - new THREE.TextGeometry( text, parameters ); -

        -

        - しかし、これを機能させるためには、TextGeometryのfontパラメータにTHREE.Fontのインスタンスを設定する必要があります。 - - これをどのように行うかについては [page:TextGeometry] ページを参照してください。また、受け入れ可能な各パラメータの説明、THREE.js ディストリビューションに付属する JSON フォントの一覧も参照してください。 -

        - -

        Examples

        - -

        - [example:webgl_geometry_text WebGL / geometry / text]
        - [example:webgl_shadowmap WebGL / shadowmap] -

        - -

        - Typefaceがダウンしている場合や、そこにないフォントを使いたい場合は、以下のリンク先のチュートリアル(blender用のPythonスクリプトでテキストをThree.jsのJSON形式に書き出す)を参考にしてください。 - [link:http://www.jaanga.com/2012/03/blender-to-threejs-create-3d-text-with.html] -

        - -
        - - - -

        5. Bitmap Fonts

        -
        -

        - BMFonts(ビットマップフォント)では、グリフを1つのBufferGeometryにバッチ処理することができます。BMFont レンダリングは、ワードラッピング、文字間隔、カーニング、標準的な導関数を持つ符号付き距離フィールド、マルチチャンネル符号付き距離フィールド、マルチテクスチャフォントなどをサポートしています。[link:https://github.com/Jam3/three-bmfont-text three-bmfont-text]を参照してください。 -

        -

        - [link:https://github.com/etiennepinchon/aframe-fonts A-Frame Fonts]のようなプロジェクトではストックフォントが利用できますが、任意の .TTF フォントから独自のフォントを作成し、プロジェクトに必要な文字のみを含めるように最適化することもできます。 -

        -

        - 役に立つツール -

        -
          -
        • [link:http://msdf-bmfont.donmccurdy.com/ msdf-bmfont-web] (web-based)
        • -
        • [link:https://github.com/soimy/msdf-bmfont-xml msdf-bmfont-xml] (commandline)
        • -
        • [link:https://github.com/libgdx/libgdx/wiki/Hiero hiero] (desktop app)
        • -
        -
        - - - - - diff --git a/docs/manual/ja/introduction/Drawing-lines.html b/docs/manual/ja/introduction/Drawing-lines.html deleted file mode 100644 index 7e45526b72f704..00000000000000 --- a/docs/manual/ja/introduction/Drawing-lines.html +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - - -

        [name]

        -
        -

        - ワイヤーフレームの[page:Mesh]ではなく、線や円が描きたいとします。 - そのためには、[page:WebGLRenderer renderer]と[page:Scene scene]そしてcameraを設定する必要があります。(詳しくはシーンを作成するページを参照) -

        - -

        これがその場合に使用するコードです。

        - -const renderer = new THREE.WebGLRenderer(); -renderer.setSize( window.innerWidth, window.innerHeight ); -document.body.appendChild( renderer.domElement ); - -const camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 500 ); -camera.position.set( 0, 0, 100 ); -camera.lookAt( 0, 0, 0 ); - -const scene = new THREE.Scene(); - -

        - 次にやるべきことはマテリアルを定義することです。線を描くには、[page:LineBasicMaterial]か [page:LineDashedMaterial]を使う必要があります。 -

        - -//create a blue LineBasicMaterial -const material = new THREE.LineBasicMaterial( { color: 0x0000ff } ); - - -

        - マテリアルの次は、複数の頂点を持つジオメトリを定義する必要があります。 -

        - - -const points = []; -points.push( new THREE.Vector3( - 10, 0, 0 ) ); -points.push( new THREE.Vector3( 0, 10, 0 ) ); -points.push( new THREE.Vector3( 10, 0, 0 ) ); - -const geometry = new THREE.BufferGeometry().setFromPoints( points ); - - -

        - 線は連続する頂点の組の間に引かれますが、最初の点と最後の点の間には引かれないことに注意してください(線は閉じません) -

        - -

        - ここで、2つの線とマテリアルが1つがあるので、これを組み合わせて一つの線にすることができます。 -

        - -const line = new THREE.Line( geometry, material ); - -

        あとはシーンに追加して[page:WebGLRenderer.render render]を呼び出すだけです。

        - - -scene.add( line ); -renderer.render( scene, camera ); - - -

        2本の青い線で出来た上向きの矢印が表示されているはずです

        -
        - - diff --git a/docs/manual/ja/introduction/FAQ.html b/docs/manual/ja/introduction/FAQ.html deleted file mode 100644 index 19296bd5fe5c41..00000000000000 --- a/docs/manual/ja/introduction/FAQ.html +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - - - - -

        [name]

        - -

        - 3Dモデルの形式はどれががおすすめでしょうか? -

        -
        -

        - アセットをimportしたり、exportしたりするのにおすすめなのは、glTF(GL Transmission Format)です。 その理由としてはglTFはランタイムでのアセットの配信に焦点を合わせているので、コンパクトに変換でき、ロードが早いからです。 -

        -

        - three.jsはFBX、Collada、OBJなどの他の多くの一般的なフォーマットにも対応したローダーを提供しています。 それでも、プロジェクトでは常に最初にglTF ベースのワークフローの作成を試してください。詳細については、[link:#manual/introduction/Loading-3D-models Loading-3D-models]を参照してください。 - -

        -
        - -

        なぜmetaタグのviewpointがexampleに入っているのですか?

        -
        - <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> - -

        - これらのタグは、モバイルブラウザのビューポートのサイズとスケールを制御します(ページの内容が表示されているビューポートとは異なるサイズでレンダリングされる場合があります)。 - -

        - -

        [link:https://developer.apple.com/library/content/documentation/AppleApplications/Reference/SafariWebContent/UsingtheViewport/UsingtheViewport.html Safari: Using the Viewport]

        - -

        [link:https://developer.mozilla.org/en-US/docs/Web/HTML/Viewport_meta_tag MDN: Using the viewport meta tag]

        -
        - -

        - リサイズをしたときに、シーンのスケールを保存しておくにはどうしたらよいですか? -

        -

        - カメラからの距離に関係なく、ウィンドウのサイズを変更しても、すべてのオブジェクトが同じサイズで表示されるようにしたいです。この問題を解くための重要な方程式は、ある距離での可視高さの計算式です。 - - -visible_height = 2 * Math.tan( ( Math.PI / 180 ) * camera.fov / 2 ) * distance_from_camera; - ウィンドウの高さを一定の割合で増加させると、すべての距離で同じ割合で表示される高さが増加することになります。これは、カメラの位置を変えてもできません。 その代わりに、カメラの視野を変更する必要があります。 具体例は[link:http://jsfiddle.net/Q4Jpu/ sample]を参照してください。 -

        - -

        オブジェクトの一部が非表示になる原因はなんですか?

        -

        - これはface cullingが原因かもしれません。Faceにはどちらの面かを決める向きがあります。 そして、cullingは通常の状況では裏面を削除します。 これが問題となって非表示になっているかどうかを確認するには、 マテリアルの面をTHREE.DoubleSideに変更してください。 - material.side = THREE.DoubleSide -

        - -

        Why does three.js sometimes return strange results for invalid inputs?

        -

        - For performance reasons, three.js doesn't validate inputs in most cases. It's your app's responsibility to make sure that all inputs are valid. -

        - - - diff --git a/docs/manual/ja/introduction/How-to-create-VR-content.html b/docs/manual/ja/introduction/How-to-create-VR-content.html deleted file mode 100644 index 9fd2dd351530df..00000000000000 --- a/docs/manual/ja/introduction/How-to-create-VR-content.html +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - - - - - -

        [name]

        - -

        - このガイドでは、three.jsを使って作成したWebベースのVRアプリケーションの基本的なコンポーネントの概要を説明しています。 -

        - -

        Workflow

        - -

        - はじめに、プロジェクトに[link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/webxr/VRButton.js VRButton.js]を追加する必要があります。 -

        - - -import { VRButton } from 'three/addons/webxr/VRButton.js'; - - -

        - *VRButton.createButton()* は2つの重要なことを行います。VR互換性であることを表すボタンを作成し、ユーザーがボタンをアクティブにするとVRセッションを開始します。この機能を追加するためには、次のコードをアプリに追加するだけで良いです。 -

        - - -document.body.appendChild( VRButton.createButton( renderer ) ); - - -

        - 次に *WebGLRenderer* インスタンスのXRレンダリングを有効にしてください。 -

        - - -renderer.xr.enabled = true; - - -

        - 最後にアニメーションループを調整する必要があります。というのも、よく知られた *window.requestAnimationFrame()* 関数を使えないからです。VRプロジェクトのためには、[page:WebGLRenderer.setAnimationLoop setAnimationLoop]を使います。最低限必要なコードは以下のようになります。 -

        - - -renderer.setAnimationLoop( function () { - - renderer.render( scene, camera ); - -} ); - - -

        Next Steps

        - -

        - このワークフローを実際に見るために、公式のWebVRの例を見てみましょう。

        - - [example:webxr_vr_ballshooter WebXR / VR / ballshooter]
        - [example:webxr_vr_cubes WebXR / VR / cubes]
        - [example:webxr_vr_dragging WebXR / VR / dragging]
        - [example:webxr_vr_paint WebXR / VR / paint]
        - [example:webxr_vr_panorama_depth WebXR / VR / panorama_depth]
        - [example:webxr_vr_panorama WebXR / VR / panorama]
        - [example:webxr_vr_rollercoaster WebXR / VR / rollercoaster]
        - [example:webxr_vr_sandbox WebXR / VR / sandbox]
        - [example:webxr_vr_sculpt WebXR / VR / sculpt]
        - [example:webxr_vr_video WebXR / VR / video] -

        - - - - diff --git a/docs/manual/ja/introduction/How-to-dispose-of-objects.html b/docs/manual/ja/introduction/How-to-dispose-of-objects.html deleted file mode 100644 index e556d1bee27e40..00000000000000 --- a/docs/manual/ja/introduction/How-to-dispose-of-objects.html +++ /dev/null @@ -1,135 +0,0 @@ - - - - - - - - - - - -

        [name]

        - -

        - アプリケーションのパフォーマンスを向上させ、メモリリークを回避するための重要なこととして、 - 未使用のライブラリエンティティの廃棄が挙げられます。 - three.js型のインスタンスを作成すると、決まった量のメモリが割り当てられます。 - しかし、*three.js*はジオメトリやマテリアルのような特定のオブジェクトのために、レンダリングに必要なバッファやshaderといったWebGL関連のエンティティを作成します。これらのオブジェクトは自動的に解放されるわけではありません。 - その代わり、アプリケーションはこのようなリソースを解放するために特別なAPIを使用する必要があります。 - このガイドでは、このAPIがどのように使用されるのか、また、このコンテキストではどのようなオブジェクトが関連しているのかについて簡単に説明します。 -

        - -

        Geometries

        - -

        - ジオメトリは普通は、属性の集合として定義された頂点の情報を表します。 - *three.js*は、内部にはそれぞれの属性に対して、[link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLBuffer WebGLBuffer]型のオブジェクトを作成します。 - これらのentityは[page:BufferGeometry.dispose]()が呼ばれた時にのみ削除されます。 - もしアプリケーションの中で、あるジオメトリが使われなくなった場合は、関連したリソースを開放するためにこの方法を実行してください。 -

        - -

        Materials

        - -

        - マテリアルは、オブジェクトをどのように描画するかを定義します。 - *three.js*はマテリアルの定義情報をレンダリング用のシェーダプログラムを構築するために使います。 - シェーダプログラムは対応するマテリアルを捨てることでのみ、削除することができます。 - パフォーマンスのために、*three.js*は可能であればすでに存在するシェーダプログラムを再利用しようとします。 - そのため、シェーダプログラムは、関連したマテリアルがすべて廃棄された場合のみ削除されます。 - [page:Material.dispose]()を実行することで、マテリアルの削除を指示できます。 -

        - -

        Textures

        - -

        - マテリアルの廃棄をしても、テクスチャには影響がありません。 - 一つのテクスチャが複数のマテリアルに同時に使用されることがあるので、 - テクスチャとマテリアルは別々に制御されています。 - テクスチャのインスタンスを作成すると、three.jsは内部に[link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLTexture WebGLTexture]を作成します。 - バッファと同様に、このオブジェクトは[page:Texture.dispose]()を呼ぶことでしか削除できません。 -

        - -

        - If you use an *ImageBitmap* as the texture's data source, you have to call [link:https://developer.mozilla.org/en-US/docs/Web/API/ImageBitmap/close ImageBitmap.close]() at the application level to dispose of all CPU-side resources. - An automated call of *ImageBitmap.close()* in [page:Texture.dispose]() is not possible, since the image bitmap becomes unusable, and the engine has no way of knowing if the image bitmap is used elsewhere. -

        - -

        Render Targets

        - -

        - [page:WebGLRenderTarget]型のオブジェクトは、描画先をカスタム出来るように、 - [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLTexture WebGLTexture]インスタンスだけでなく、[link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLFramebuffer WebGLFramebuffer]と - [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderbuffer WebGLRenderbuffer]も確保します。 - これらのオブジェクトは[page:WebGLRenderTarget.dispose]()を実行することで、解放されます。 -

        - -

        Miscellaneous

        - -

        - exampleディレクトリには、コントロールや後処理用のパスクラスといったクラスがあります。 - これらのクラスは内部のイベントリスナーや描画対象を取り除くための*dispose()*メソッドを提供します。 - 一般的には、APIやクラスのドキュメントで*dispose()*があるかどうか探すのがおすすめです。 - *dispose()*メソッドが存在するなら、片付けの際に使うとよいでしょう。 -

        - -

        FAQ

        - -

        なぜthree.jsは自動的にオブジェクトを処分できないのですか?

        - -

        - この質問はコミュニティで何度も訊かれた質問なので、この問題についてはきちんと解説します。 - 実のところ、*three.js*はライフタイムや、ユーザが作成したジオメトリやマテリアルのようなエンティティのスコープの情報をもっていません。これはアプリケーション側に責任があります。例えば、マテリアルが今のところレンダリングのために使用されていなくとも、次のフレームでは必要になるかもしれません。そのため、アプリケーションが特定のオブジェクトを削除しても良いと判断した場合、対応する*dispose()*メソッドを呼ぶことで描画エンジンに対してそのことを知らせなくてはなりません。 -

        - -

        シーンからmeshを取り除くとそのジオメトリとマテリアルも削除されますか?

        - -

        - いいえ。ジオメトリとマテリアルは*dispose()*で明示的に削除する必要があります。 - メッシュのような3Dオブジェクト間でジオメトリとマテリアルは共有できることを覚えておいてください。 -

        - -

        *three.js*でキャッシュされたオブジェクトの量について情報を知ることができますか?

        - -

        - はい。グラフィックボードのメモリとレンダリングプロセスの一連の統計情報で - レンダラの特別なプロパティである[page:WebGLRenderer.info]を評価することができます。 - また、ほかにはテクスチャやジオメトリ、シェーダプログラムがどれくらい内部に保存されているかを知ることができます。 - もし、アプリケーションにパフォーマンス上の問題があることに気づいた場合、簡単にメモリリークを見つけるために - このプロパティを調べてみることをおすすめします。 -

        - -

        画像がまだロードされていない時に、テクスチャの*dispose()*を呼び出すとどうなりますか?

        - -

        - テクスチャの内部リソースは、イメージが完全に読み込まれた場合にのみ割り当てられます。 - 画像が読み込まれる前にテクスチャを破棄しても何も起こりません。 - リソースが割り当てられていないので、クリーンアップの必要もありません。 -

        - -

        dispose()を呼び出した後に、対象のオブジェクトを使用すると何が起きますか?

        - -

        - 削除された内部のリソースは、エンジンによって再び作成されます。 - そのため、ランタイムエラーは発生しませんが、シェーダプログラムのコンパイルが必要な場合には特に - 現在のフレームでのパフォーマンスの低下が発生するかもしれません。 - -

        - -

        *three.js*のオブジェクトをどのように管理するべきでしょうか?いつオブジェクトを削除するべきでしょうか?

        - -

        - この問いに対する明確で一般的な答えはありません。いつ*dispose()*を呼ぶのが正しいのかは、ユースケースに大きく依存します。常にオブジェクトを削除する必要があるわけではないことは強調しておきます。そのよい例として、複数のレベルで構成されているゲームがあります。こういったアプリの場合、オブジェクトを削除するのに適しているのはレベルを切り替えるときです。 - そうすることで、古いシーンを通じてオブジェクトに対して処理を適用し、古いマテリアルやジオメトリ、テクスチャを削除できます。前の章で言及したように、まだ使用中のオブジェクトを削除してもランタイムエラーは発生しません。最悪でも1フレーム分のパフォーマンス低下が発生するだけです。 -

        - -

        Examples that demonstrate the usage of dispose()

        - -

        - [example:webgl_test_memory WebGL / test / memory]
        - [example:webgl_test_memory2 WebGL / test / memory2]
        -

        - - - - diff --git a/docs/manual/ja/introduction/How-to-update-things.html b/docs/manual/ja/introduction/How-to-update-things.html deleted file mode 100644 index 630bb898bc4fc0..00000000000000 --- a/docs/manual/ja/introduction/How-to-update-things.html +++ /dev/null @@ -1,233 +0,0 @@ - - - - - - - - - - - -

        [name]

        -
        -

        - 下記のコードのようにシーンに追加されたオブジェクトはデフォルトで自動的にマトリクスを更新します。 -

        - -const object = new THREE.Object3D(); -scene.add( object ); - もしくは、シーンに親オブジェクトが追加されると子オブジェクトも自動的に更新されます。 - -const object1 = new THREE.Object3D(); -const object2 = new THREE.Object3D(); - -object1.add( object2 ); -scene.add( object1 ); //object1 and object2 will automatically update their matrices - -
        - -

        - しかしながら、オブジェクトが静的なものであると分かっている場合は、自動更新を無効にして必要な時だけ、更新することが出来ます。 -

        - - -object.matrixAutoUpdate = false; -object.updateMatrix(); - - -

        BufferGeometry

        -
        -

        - BufferGeometriesは、情報(頂点の位置、面のインデックス、法線、色、UV、カスタム属性など)をバッファ、 つまり[link:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays Typed_arrays](型付の配列)に保存します。 このため、標準的なジオメトリよりも一般的に高速ですが、その反面、作業がやや難しくなっています。 -

        -

        - BufferGeometriesの更新についてバッファのサイズを変更することができないということは理解しておく必要があります。(これは非常にコストがかかり、基本的には新しいジオメトリを作成するのと同じです)。しかし、バッファの内容を更新することはできます。 - -

        -

        - つまり、BufferGeometry の属性(例えば頂点の数など)が増加することがわかっている場合、 新たに作成される可能性のある頂点を保持するのに十分な大きさのバッファを事前に確保しておく必要があります。 もちろん、これはBufferGeometryの最大サイズが存在することになります。 -

        -

        - ここでは、レンダリング時に拡張される線の例を使用します。 バッファに500頂点分のスペースを確保しますが、[page:BufferGeometry.drawRange]を使って最初は2つだけ描画します。 -

        - -const MAX_POINTS = 500; - -// geometry -const geometry = new THREE.BufferGeometry(); - -// attributes -const positions = new Float32Array( MAX_POINTS * 3 ); // 3 vertices per point -geometry.setAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) ); - -// draw range -const drawCount = 2; // draw the first 2 points, only -geometry.setDrawRange( 0, drawCount ); - -// material -const material = new THREE.LineBasicMaterial( { color: 0xff0000 } ); - -// line -const line = new THREE.Line( geometry, material ); -scene.add( line ); - -

        - 次に以下のようにして、線上にランダムに点を追加してみましょう。 -

        - -const positionAttribute = line.geometry.getAttribute( 'position' ); - -let x = 0, y = 0, z = 0; - -for ( let i = 0; i < positionAttribute.count; i ++ ) { - - positionAttribute.setXYZ( i, x, y, z ); - - x += ( Math.random() - 0.5 ) * 30; - y += ( Math.random() - 0.5 ) * 30; - z += ( Math.random() - 0.5 ) * 30; - -} - -

        - もし、最初の描画以降に点の数を変更したい場合は、以下のようにしてください。 -

        - -line.geometry.setDrawRange( 0, newValue ); - -

        - 最初の描画以降に位置データの値を変更したい場合は、needsUpdateをtrueにセットする必要があります。 -

        - -positionAttribute.needsUpdate = true; // required after the first render - - -

        - 最初の描画以降に位置データの値を変更した場合は、frustum cullingやヘルパーといったエンジンの他の機能がちゃんと機能するようにboundingボリュームを再計算する必要があるかもしれません。 -

        - -line.geometry.computeBoundingBox(); -line.geometry.computeBoundingSphere(); - - -

        - ここ([link:https://jsfiddle.net/t4m85pLr/1/ link])では、あなたのユースケースに合わせることができるアニメーションを表示しています。 -

        - -

        Examples

        - -

        - [example:webgl_custom_attributes WebGL / custom / attributes]
        [example:webgl_buffergeometry_custom_attributes_particles WebGL / buffergeometry / custom / attributes / particles] -

        - -
        - -

        Materials

        -
        -

        - ユニフォームの値(例えば、色やテクスチャや透明度)は自由に変更することが出来ます。 これらの値はフレームごとにシェーダに送られます。 -

        - -

        - GLstateに関連したパラメータ(depthTest, blending, polygonOffsetなど)もいつでも変更出来ます。 -

        - -

        - 以下に示すパラメータはランタイム(マテリアルが一度描画された後)には簡単に変更ができません。 -

        -
          -
        • ユニフォームの種類や数
        • -
        • 以下の要素の有無 -
            -
          • texture
          • -
          • fog
          • -
          • vertex colors
          • -
          • morphing
          • -
          • shadow map
          • -
          • alpha test
          • -
          • transparent
          • -
          -
        • -
        - -

        - これらの値を変更するのには新しいシェーダプログラムが必要です、そのためには以下のように設定してください。 -

        - material.needsUpdate = true - -

        - こうすることで、プログラムはとても遅くなり、フレームレートが不安定になるかもしれないことを覚えておいてください。(特にWindowsでは、シェーダのコンパイルがOpenGLよりもDirectXの方が遅いので、より顕著に現れる可能性があります)。 -

        - -

        - より滑らかな体験を提供するために、明るさ0の光や、白紙のテクスチャ、密度0の霧といった"ダミー"の値を使うことで、これらの変化をある程度模倣することができます。 -

        - -

        - ジオメトリチャンクに使用するマテリアルは自由に変更できますが、オブジェクトをチャンクに分割する方法は変更できません。オブジェクトをどのようにチャンクに分割するからは、表面のマテリアルによって決まります。 -

        - -

        - ランタイムに異なるマテリアルの設定が必要な場合 -

        -

        - マテリアル/チャンクの数が少ない場合は、あらかじめオブジェクトを分割しておくことができます(例:人間の場合は髪/顔/胴体/上着/ズボン、車の場合はフロント/サイド/トップ/ガラス/タイヤ/内装)。 - - -

        - -

        - 数が多い場合(例えば、人の顔はそれぞれ違っている可能性があります)は、顔ごとに異なる外観にするために属性/テクスチャを使用するなど、別の解決策を検討してください。 -

        - -

        Examples

        -

        - [example:webgl_materials_car WebGL / materials / car]
        [example:webgl_postprocessing_dof WebGL / webgl_postprocessing / dof] -

        -
        - - -

        Textures

        -
        -

        - 画像やcanvas、ビデオやデータのテクスチャが変化する場合は、以下のフラグを有効にする必要があります。 -

        - - texture.needsUpdate = true; - -

        描画の対象が自動的に更新されます。

        - -

        Examples

        -

        - [example:webgl_materials_video WebGL / materials / video]
        [example:webgl_rtt WebGL / rtt] -

        - -
        - - -

        Cameras

        -
        -

        カメラの位置とターゲットは自動的に更新されます。以下の要素を変更する必要がある場合、projection matrixを再計算する必要があります。

        -
          -
        • - fov -
        • -
        • - aspect -
        • -
        • - near -
        • -
        • - far -
        • -
        - -camera.aspect = window.innerWidth / window.innerHeight; -camera.updateProjectionMatrix(); - -
        - - - diff --git a/docs/manual/ja/introduction/How-to-use-post-processing.html b/docs/manual/ja/introduction/How-to-use-post-processing.html deleted file mode 100644 index fd311f4afae066..00000000000000 --- a/docs/manual/ja/introduction/How-to-use-post-processing.html +++ /dev/null @@ -1,115 +0,0 @@ - - - - - - - - - - - -

        How to use post-processing(後処理の使い方)

        - -

        - three.jsアプリケーションの多くは3Dオブジェクトをスクリーンに直接描画します。 - しかしながら時々、一つ以上のエフェクト(被写界深度、Bloom、フィルムグレイン、様々なアンチエイリアス効果など)を適用したい場合があります。こういったエフェクトを実装するためにpost-processingは広く使用されています。 - まず、シーンはビデオカードメモリのバッファを表す描画対象に対して描画されます。 - 次に、最終的なスクリーンへのレンダリングの前に、1つ以上のpost-processingで画像バッファにフィルタとエフェクトを適用します。 -

        -

        - こういったワークフローを実装するために、three.jsは[page:EffectComposer]で完全なpost-processingを提供しています。 -

        - -

        Workflow

        - -

        - プロセスの一段階目はexampleディレクトリから必要なファイルを全てimportすることです。 - このガイドではthree.jsの[link:https://www.npmjs.com/package/three npm package](npmの公式のパッケージ)を使っていると想定しています。 - このガイドのデモでは下に示したファイルが必要です。 -

        - - - import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'; - import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'; - import { GlitchPass } from 'three/addons/postprocessing/GlitchPass.js'; - - -

        - 全てのファイルのimportが成功したのちに、[page:WebGLRenderer]のインスタンスを渡すことでcomposerを作成します。 -

        - - - const composer = new EffectComposer( renderer ); - - -

        - composerを使うときにはアプリケーションのアニメーションループを変更する必要があります。 - [page:WebGLRenderer]のrenderメソッドを呼ぶ代わりに[page:EffectComposer]のそれぞれの対応するものを使います。 -

        - - - function animate() { - - requestAnimationFrame( animate ); - - composer.render(); - - } - - -

        - composerが準備できたので、post-processingパスのチェーンを設定できるようになりました。 - これらのパスはアプリケーションの最終的なビジュアルを出力することに責任を持ちます。 - これらのパスは追加/挿入の順番で処理されます。今回示している例では、*RenderPass*インスタンスがはじめに実行され、 - それから*GlitchPass*インスタンスが実行されます。チェーンの中で最後の有効なpassが自動的に画面に描画されます。 - passの設定は以下のように行います。 -

        - - - const renderPass = new RenderPass( scene, camera ); - composer.addPass( renderPass ); - - const glitchPass = new GlitchPass(); - composer.addPass( glitchPass ); - - -

        - *RenderPass*は普通チェインのはじめにあります。 - これはレンダリングされたシーンを次のpost-processingの入力とするためです。 - *GlitchPass*は、これらのイメージをワイルドなglitch effectを適用するために使います。 - 実際に動いているものを見るために、[link:https://threejs.org/examples/webgl_postprocessing_glitch sample]を見てみましょう。 -

        - -

        Built-in Passes(組み込みのpass)

        - -

        - エンジンに元から入っている定義済みの後処理passが使えます。 - このpassは[link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm/postprocessing postprocessing]ディレクトリに入っていて、広範囲に使用できます。 -

        - -

        Custom Passes(カスタムpass)

        - -

        - 独自のpostprocessing shaderを書いて、post-processing passのチェーンの中に組み込みたい場合があります。そういった場合には、*ShaderPass*を利用することが出来ます。 - ファイルと独自のshaderをインポートしたのちに、以下のコードでpassを設定することができます。 -

        - - - import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js'; - import { LuminosityShader } from 'three/addons/shaders/LuminosityShader.js'; - - // later in your init routine - - const luminosityPass = new ShaderPass( LuminosityShader ); - composer.addPass( luminosityPass ); - - -

        - リポジトリには[link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/shaders/CopyShader.js CopyShader]と呼ばれるファイルがあり、カスタムshaderを作る上での良いスタートコードです。 - *CopyShader*はエフェクトを適用せずに、EffectComposerの読み込みバッファの画像内容を書き込みバッファにコピーするだけです。 -

        - - - - \ No newline at end of file diff --git a/docs/manual/ja/introduction/Installation.html b/docs/manual/ja/introduction/Installation.html deleted file mode 100644 index 16654b7029c133..00000000000000 --- a/docs/manual/ja/introduction/Installation.html +++ /dev/null @@ -1,169 +0,0 @@ - - - - - - - - - - - -

        [name]

        - -

        - [link:https://www.npmjs.com/ npm]を使ってthree.jsとモダンなビルドツールをインストール出来ます。もしくは、静的ホスティングやCDNを使って素早く使い始めることも可能です。ほとんどの人にとっては、npmを使ってインストールするのが良いでしょう。 -

        - -

        - どちらの方法を選ぶにしても、一貫して同じバージョンのライブラリからファイルをimportしてください。異なるソースのファイルを混ぜるとコードが重複するかもしれませんし、予想外の方法でアプリケーションが壊れるかもしれません。 -

        - -

        - three.jsをインストール方法は、ES modules依存です([link:https://eloquentjavascript.net/10_modules.html#h_hF2FmOVxw7 Eloquent JavaScript: ECMAScript Modules]を参照)。そのおかげで、最新版のプロジェクトのライブラリの必要な部分だけを取り込むことができます。 -

        - -

        npmでインストールする

        - -

        - [link:https://www.npmjs.com/package/three there] npm moduleをインストールするために, プロジェクトのフォルダでターミナルを開いて以下のコードを実行してください。 -

        - - - npm install three - - -

        - パッケージがダウンロードされて、インストールされます。これで、three.jsをコードの中で使う準備が完了しました。 -

        - - - // Option 1: Import the entire three.js core library. - import * as THREE from 'three'; - - const scene = new THREE.Scene(); - - - // Option 2: Import just the parts you need. - import { Scene } from 'three'; - - const scene = new Scene(); - - -

        - npmからインストールする際に、プロジェクトで必要なパッケージを一つのJavaScriptのファイルにまとめるために、ビルドツールを使うことがあるでしょう。ほとんどのモダンなJavaScriptのビルダーでthree.jsは使えますが、最も人気なのは[link:https://webpack.js.org/ webpack]です。 -

        - -

        - すべての機能が three モジュールから直接アクセスできるわけではありません(直接アクセスすることを「ベアインポート」とも呼びます)。コントロール、ローダー、エフェクトの前処理など、ライブラリの他の一般的な部分はサプフォルダ[link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm examples/jsm]からインポートしなければなりません。詳細については、以下のExamplesを参照してください。 -

        - -

        - [link:https://eloquentjavascript.net/20_node.html#h_J6hW/SmL/a Eloquent JavaScript: Installing with npm]でnpm moduleについて詳しく学習する。 -

        - -

        CDNや静的ホスティングからインストールをする

        - -

        - three.jsは、自分の Web サーバーにファイルをアップロードするか、既存の CDN を利用することで、ビルドシステムなしで利用することができます。ライブラリはES moduleに依存しているため、ライブラリを参照するスクリプトは以下のようにtype="module"を使用する必要があります。 -

        - - -<script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script> - -<script type="importmap"> - { - "imports": { - "three": "https://unpkg.com/three@<version>/build/three.module.js" - } - } -</script> - -<script type="module"> - - import * as THREE from 'three'; - - const scene = new THREE.Scene(); - -</script> - - -

        Addons

        - -

        - three.jsのコアは、3Dエンジンの最も重要なコンポーネントに焦点を当てています。コントロール、ローダー、エフェクトの前処理といった、他の多くの便利なコンポーネントは [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm examples/jsm] ディレクトリの一部です。これらは「examples」と呼ばれています。その理由としては、ユーザーが既製品を使用でき、リミックスやカスタマイズも可能だからです。これらのコンポーネントは常にコアライブラリと同期していますが、npm上の同様のサードパーティ製パッケージは別の人によってメンテナンスされており、最新ではないかもしれません。 -

        - -

        - Addonsはそれだけ別でインストールする必要はありませんが、importは分けて行う必要があります。 three.jsをnpmでインストールしている場合、以下のようにして[page:OrbitControls]コンポーネントを読み込むことができます。 -

        - - - - import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; - - const controls = new OrbitControls( camera, renderer.domElement ); - - -

        - three.jsをCDNを使用してインストールしている場合は、同じCDNを使用して他のコンポートをインストールしてください。 -

        - - -<script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script> - -<script type="importmap"> - { - "imports": { - "three": "https://unpkg.com/three@<version>/build/three.module.js", - "three/addons/": "https://unpkg.com/three@<version>/examples/jsm/" - } - } -</script> - -<script type="module"> - - import * as THREE from 'three'; - import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; - - const controls = new OrbitControls( camera, renderer.domElement ); - -</script> - - -

        - 重要なのは、すべてのファイルで同じバージョンを使用することです。異なるバージョンの異なるAddonsをインポートしたり、three.jsライブラリ自体とは異なるバージョンのAddonsを使用したりしないでください。 -

        - -

        互換性について

        - -

        CommonJSでのimport

        - -

        - 最近のJavaScriptバンドラーはデフォルトでESモジュールをサポートしていますが、古いビルドツールの中にはサポートしていないものもあります。そのような場合は,バンドラーがESモジュールのことがわかるように設定することができます。例えば [link:http://browserify.org/ Browserify]では[link:https://github.com/babel/babelify babelify] プラグインが必要です。 -

        - -

        Import maps

        - -

        - npmからインストールする時と、静的ホスティングやCDN からインストールする時とでは、インポートに使用するパスが異なります。これは、両方のグループのユーザーが、どういったものを使いやすいと感じるかが異なるために起こります。ビルドツールやバンドルを使用している開発者は、相対パスよりも裸のパッケージ指定子(例えば、'three')を好み、examples/ フォルダ内のファイルは、この形式に従わない three.module.js への相対参照を使用しています。ビルドツールを使用しない人(高速プロトタイピング、学習、個人的な好みなど)は、同様に、特定のフォルダ構造を必要とし、グローバルなthree.*名前空間よりも厳密な相対インポートを嫌うかもしれません。 -

        - -

        - [link:https://github.com/WICG/import-maps import maps] が広く利用できるようになったら、これらの相対パスを削除して、npm パッケージ名の 'three' への裸のパッケージ指定子に置き換えたいと考えています。これにより、ビルドツールが期待するnpmパッケージとより密接にマッチし、ファイルをインポートする際に両グループのユーザが全く同じコードを書くことができるようになります。ビルドツールを避けたいユーザーのために、シンプルなJSONマッピングですべてのインポートをCDNや静的ファイルフォルダに向けることができます。試しに、私たちの - [link:https://glitch.com/edit/#!/three-import-map?path=index.html import map example] で示されているように、import mapsのPolyfillを使って、今日はよりシンプルなインポートを使ってみることができます。 -

        - -

        Node.js

        - -

        - Because three.js is built for the web, it depends on browser and DOM APIs that don't always exist in Node.js. Some of these issues can be resolved by using shims like [link:https://github.com/stackgl/headless-gl headless-gl], or by replacing components like [page:TextureLoader] with custom alternatives. Other DOM APIs may be deeply intertwined with the code that uses them, and will be harder to work around. We welcome simple and maintainable pull requests to improve Node.js support, but recommend opening an issue to discuss your improvements first. -

        - -

        - Make sure to add `{ "type": "module" }` to your `package.json` to enable ES6 modules in your node project. -

        - - - - diff --git a/docs/manual/ja/introduction/Libraries-and-Plugins.html b/docs/manual/ja/introduction/Libraries-and-Plugins.html deleted file mode 100644 index d7e8ec71dab7a4..00000000000000 --- a/docs/manual/ja/introduction/Libraries-and-Plugins.html +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - - - - - -

        [name]

        - -

        - 外部で開発された互換性のあるthree.jsのライブラリやプラグインをここでリストアップしています。 このリストと関連したパッケージはコミュニティによってメンテナンスされており、最新である保証はありません。 もしこのリストを更新したい場合はPullRequestを送ってください! -

        - -

        Physics(物理)

        - -
          -
        • [link:https://github.com/lo-th/Oimo.js/ Oimo.js]
        • -
        • [link:https://enable3d.io/ enable3d]
        • -
        • [link:https://github.com/kripken/ammo.js/ ammo.js]
        • -
        • [link:https://github.com/pmndrs/cannon-es cannon-es]
        • -
        - -

        Postprocessing(後処理)

        - -

        - [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm/postprocessing official three.js postprocessing effects]に加えて、外部のライブラリを通して、追加のエフェクトやフレームワークのサポートが利用可能です。 - - -

        - -
          -
        • [link:https://github.com/vanruesc/postprocessing postprocessing]
        • -
        - -

        Intersection and Raycast Performance

        - -
          -
        • [link:https://github.com/gkjohnson/three-mesh-bvh three-mesh-bvh]
        • -
        - -

        File Formats(ファイル形式)

        - -

        - [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm/loaders official three.js loaders]に加えて、外部のライブラリを通して、追加のフォーマットのサポートが利用できます。 -

        - -
          -
        • [link:https://github.com/gkjohnson/urdf-loaders/tree/master/javascript urdf-loader]
        • -
        • [link:https://github.com/NASA-AMMOS/3DTilesRendererJS 3d-tiles-renderer-js]
        • -
        • [link:https://github.com/kaisalmen/WWOBJLoader WebWorker OBJLoader]
        • -
        • [link:https://github.com/IFCjs/web-ifc-three IFC.js]
        • -
        - -

        Geometry

        - -
          -
        • [link:https://github.com/spite/THREE.MeshLine THREE.MeshLine]
        • -
        - -

        3D Text and Layout

        - -
          -
        • [link:https://github.com/protectwise/troika/tree/master/packages/troika-three-text troika-three-text]
        • -
        • [link:https://github.com/felixmariotto/three-mesh-ui three-mesh-ui]
        • -
        - -

        Particle Systems

        - -
          -
        • [link:https://github.com/Alchemist0823/three.quarks three.quarks]
        • -
        • [link:https://github.com/creativelifeform/three-nebula three-nebula]
        • -
        - -

        Inverse Kinematics

        - -
          -
        • [link:https://github.com/jsantell/THREE.IK THREE.IK]
        • -
        • [link:https://github.com/lo-th/fullik fullik]
        • -
        - -

        Game AI

        - -
          -
        • [link:https://mugen87.github.io/yuka/ yuka]
        • -
        • [link:https://github.com/donmccurdy/three-pathfinding three-pathfinding]
        • -
        - -

        Wrappers and Frameworks

        - -
          -
        • [link:https://aframe.io/ A-Frame]
        • -
        • [link:https://github.com/pmndrs/react-three-fiber react-three-fiber]
        • -
        • [link:https://github.com/ecsyjs/ecsy-three ECSY]
        • -
        • [link:https://threlte.xyz/ Threlte]
        • -
        - - - - diff --git a/docs/manual/ja/introduction/Loading-3D-models.html b/docs/manual/ja/introduction/Loading-3D-models.html deleted file mode 100644 index 654c3ff7adcb2c..00000000000000 --- a/docs/manual/ja/introduction/Loading-3D-models.html +++ /dev/null @@ -1,156 +0,0 @@ - - - - - - - - - - - -

        [name]

        - -

        - 3Dモデルはたくさんのファイル形式で利用可能で、それぞれに目的があり、複雑さも様々です。 - - three.jsは様々なloaderを提供しています - が、適切な形式とワークフローを選択することで、パフォーマンスを改善することができます。 - うまく動作させるのが難しいフォーマットや、リアルタイムでの体験に適さないフォーマットもあり、 - 中にはまったくサポートされていないフォーマットもあります。 -

        - -

        - このガイドでは、ほとんどのユーザにおすすめのワークフローと、予期しないことが発生したときに何を試してみればよいかを紹介します。 -

        - -

        始める前に

        - -

        - もしthree.jsをローカルサーバで動かすのが初めてなら、 - [link:#manual/introduction/Installation installation]を見てみてください。 - 3Dモデルを表示する際の多くの一般的なエラーはファイルを正しく配置することで防ぐことができます。 -

        - -

        おすすめのワークフロー

        - -

        - 可能なら、glTF(GL Transmission Format)を使うことをおすすめします。 - glTFは.GLB.GLTFの両方のフォーマットについてサポートしています。 - glTFはランタイムアセットの配信に注力しているので、変換時にはコンパクトでロードも早いです。 - 機能としては、メッシュやマテリアル、テクスチャ、スキン、スケルトン、morphターゲット、アニメーション、ライト、カメラがあります。 -

        - -

        - 公開されているglTFファイルはSketchfabのようなサイトで利用可能です。 - また様々なツールでglTF形式でexportすることが出来ます。 -

        - - - -

        - glTFをサポートしていないツールが使いたい場合は、ツールの作者にglTFのエクスポートを依頼するか、 - the glTF roadmap - threadに投稿することを検討してください。 -

        - -

        - glTFが選択肢にない場合は、FBX,OBJ,COLLADAといった一般的な形式も利用可能です。 - これらは定期的にメンテナンスされています。 -

        - -

        Loading

        - -

        - ごく一部のローダ(例えば、[page:ObjectLoader])はデフォルトでthree.jsに入っています。 - ほかのものは、ユーザがそれぞれ自分のアプリに加える必要があります。 - -

        - - - import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; - - -

        - ローダをimportした時点で、シーンにモデルを追加できるようになります。 - 文法はローダによって異なります。異なるフォーマットを使う場合は、そのローダのドキュメントやサンプルを確認してください。glTFの場合、グローバルスクリプトでは以下のように使用します。 -

        - - - const loader = new GLTFLoader(); - - loader.load( 'path/to/model.glb', function ( gltf ) { - - scene.add( gltf.scene ); - - }, undefined, function ( error ) { - - console.error( error ); - - } ); - - -

        - より詳細な機能について知りたい場合は[page:GLTFLoader GLTFLoader documentation]を見てください。 -

        - -

        Troubleshooting

        - -

        - 何時間もかけて傑作をモデリングしたのに、webpageに読み込むと、なんと!😭 - 歪んでいたり、色がおかしかったり、表示されなかったりすることがあります。 - そういったときにはトラブルシューティングを始めましょう。 -

        - -
          -
        1. - JavaScriptコンソールでエラーが発生していないか確認し、.load() を呼び出す際にonErrorコールバックを使用して結果をログに記録していることを確認してください - -
        2. -
        3. - モデルを別のアプリケーションで見てみてください。 - glTFではthree.js と - babylon.jsでドラックアンドドロップでviewerが利用できます。 - 一つ以上のアプリケーションでモデルが正しく表示された場合、three.jsにバグを報告してください。 - モデルがどのアプリケーションでも表示できない場合、モデルを作成する際に使ったアプリにバグを報告することを強くお勧めします。 -
        4. -
        5. - モデルを1000倍にスケールアップしたり、スケールダウンしたりしてみてください。 - 多くのモデルはスケールが異なります。大きなモデルだと、カメラがモデルの中に入ってしまって見えなくなっていることがあります。 -
        6. -
        7. - 光源を追加して配置してみてください。暗闇の中にモデルが隠れている可能性があります。 -
        8. -
        9. - ネットワークタブで失敗しているテクスチャのリクエスト(例えば、C:\\Path\To\Model\texture.jpg)を探してみてください。見つかったら代わりに images/texture.jpg - のようなモデルへの相対パスを使ってください。 - これはテキストエディタでモデルファイルを編集する必要があるかもしれません。 -
        10. -
        - -

        助けを求める(Asking for help)

        - -

        - 上記のトラブルシューティングのプロセスを一通りやってもまだ動かない場合、正しいやり方で助けを求めることが - 早期の解決につながります。three.js forumに質問を投稿して、可能であれば自分のモデル(もしくは同じ問題を持つよりシンプルなモデル)を利用可能な形式で添付してください。他の人が問題を迅速に再現できるように、十分な情報を含めてください。 -

        - - - - \ No newline at end of file diff --git a/docs/manual/ja/introduction/Matrix-transformations.html b/docs/manual/ja/introduction/Matrix-transformations.html deleted file mode 100644 index 08a43ecc7b51d4..00000000000000 --- a/docs/manual/ja/introduction/Matrix-transformations.html +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - - - - -

        [name]

        - -

        - Three.jsは行列(*matrices*)を使って3次元変換(位置の変換、回転、スケーリング)をエンコードします。 - すべての[page:Object3D]インスタンスは、そのオブジェクトの位置や回転、スケールを保存した行列([page:Object3D.matrix - matrix])を持っています。このページではオブジェクトの3次元変換をどうやって行うかを説明しています。 - -

        - -

        Convenience properties and *matrixAutoUpdate*(便利なプロパティとmatrixAutoUpdate)

        - -

        - オブジェクトの変換を更新するには二つの方法があります。 -

        -
          -
        1. - オブジェクトの*position*や*quaternion*(四元数)やスケールのプロパティを変更することで、three.jsに - オブジェクトの行列を再計算させます。 - -object.position.copy( start_position ); -object.quaternion.copy( quaternion ); - - デフォルトでは、*matrixAutoUpdate*のプロパティはtrueで、行列は自動的に再計算されます。 - オブジェクトが静的な場合や、再計算するタイミングを制御したい場合は、このプロパティをfalseに設定することでパフォーマンスが改善する可能性があります。 - - -object.matrixAutoUpdate = false; - - プロパティを変更した後には、行列を手動で更新してください。 - -object.updateMatrix(); - -
        2. -
        3. - オブジェクトの行列を直接修正する方法です。 - [page:Matrix4]クラスには行列を変更するための様々なメソッドがあります。 - -object.matrix.setRotationFromQuaternion( quaternion ); -object.matrix.setPosition( start_position ); -object.matrixAutoUpdate = false; - - この場合 *matrixAutoUpdate*は*false*に設定しなくてはならないことに注意してください。 - また*updateMatrix*を呼ばないようにしてください。 - *updateMatrix*を呼ぶと、行列にマニュアルで加えた変更がなくなり、位置やスケールなどから行列が再計算されます。 -
        4. -
        - -

        Object and world matrices(オブジェクトとワールド座標系での行列)

        -

        - オブジェクトの行列([page:Object3D.matrix matrix])は、[page:Object3D.parent parent](親オブジェクト)に対する相対的な変換を保持しています。 - ワールド座標系でのオブジェクトの変換を取得するには、オブジェクトの[page:Object3D.matrixWorld]にアクセスする必要があります。 - -

        -

        - 親オブジェクトや子オブジェクトの変換が変更された場合は、[page:Object3D.updateMatrixWorld updateMatrixWorld]()を呼び出すことで、子オブジェクトの[page:Object3D.matrixWorld matrixWorld]の更新を要求することができます。 -

        - -

        Rotation and Quaternion(回転と四元数)

        -

        - three.jsでは3次元の回線を表すためにオイラー角([page:Euler Euler angles])と4元数([page:Quaternion Quaternions])という2つの方法があります。 - また、three.jsではこの2つの間の変換メソッドも提供されています。 - オイラー角はgimbal lockと呼ばれる問題の影響をうけることがあります。 - gimbal lockが発生すると、特定の設定のときにオブジェクトの回転の自由度が失われます(オブジェクトが特定の軸方向に回転できなくなります) - このため、オブジェクトの回転はつねにオブジェクトの4元数([page:Object3D.quaternion quaternion])に保持されます。 -

        -

        - 以前のバージョンのライブラリには*useQuaternion*プロパティがあり、これをfalseに設定すると、 - オブジェクトの行列([page:Object3D.matrix matrix])がオイラー角から計算されるようになっていました。 - このやり方は古くなってきたので、代わりに[page:Object3D.setRotationFromEuler setRotationFromEuler]メソッドを使用して、 - 四元数を更新します。 -

        - - - - \ No newline at end of file diff --git a/docs/manual/ja/introduction/Typescript-setup.html b/docs/manual/ja/introduction/Typescript-setup.html deleted file mode 100644 index 5ae3618b173065..00000000000000 --- a/docs/manual/ja/introduction/Typescript-setup.html +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - -

        [name]

        - -

        - three.jsはJavaScriptベースのライブラリですが、[link:https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html 宣言ファイル](*d.ts* ファイル)は公開されているので、TypeScriptプロジェクトで使用することも可能です。 -

        - -

        - Typescriptのコンパイラがthree.jsの型を判別するために最小限の設定は必要です。
        - [link:https://www.typescriptlang.org/docs/handbook/module-resolution.html moduleResolution]を *node* に、[link:https://www.typescriptlang.org/docs/handbook/compiler-options.html target]を *es6* 以降に設定する必要があります。 -

        - - - // Example of minimal `tsconfig.json` file - { - "compilerOptions": { - "target": "es6", - "moduleResolution": "node", - }, - "include": [ "./src/**/*.ts" ], - } - - -

        - 注意事項 : 今のところ、この2つのオプションを使わずにthree.jsの型を使うことはできません。 -

        - -

        - 注意事項 : いくつかの宣言が間違っていたり欠けていたりすることがあります。宣言ファイルの改良に貢献することは、コミュニティにとって非常に有益であり、three.jsの型付けをより正確で良いものにします。 -

        - - diff --git a/docs/manual/ja/introduction/Useful-links.html b/docs/manual/ja/introduction/Useful-links.html deleted file mode 100644 index 70fe60806b8742..00000000000000 --- a/docs/manual/ja/introduction/Useful-links.html +++ /dev/null @@ -1,172 +0,0 @@ - - - - - - - - - -

        [name]

        - -

        - 以下は、three.jsを学ぶ際に役立つと思われるリンク集です。
        - ここに追加したいものがあったり、以下のリンクのどれかがもう関連していない、もしくは機能していないと思われる場合は、右下の「編集」ボタンをクリックして、自由に変更してください

        - - また、three.jsは急速に開発が進んでいるため、これらのリンクの多くには古い情報が含まれていることにも注意してください。 - 期待通りに動作しない場合や、これらのリンクのいずれかに記載されている通りに動作しない場合は、 - ブラウザコンソールで警告やエラーがないか確認してください。また、関連するドキュメントページもチェックしてください。 -

        - -

        ヘルプ

        -

        - Three.jsは公式の[link:https://discourse.threejs.org/ forum]と[link:http://stackoverflow.com/tags/three.js/info Stack Overflow]をヘルプリクエストに利用しています。何か支援が必要な場合は、ここを利用してください。ヘルプリクエストのためにGithubのissueを作成しないでください -

        - -

        チュートリアルと学習コース

        - -

        three.jsを始める

        -
          -
        • - [link:https://threejs.org/manual/#en/fundamentals Three.js Fundamentals starting lesson] -
        • -
        • - [link:https://codepen.io/rachsmith/post/beginning-with-3d-webgl-pt-1-the-scene Beginning with 3D WebGL] by [link:https://codepen.io/rachsmith/ Rachel Smith]. -
        • -
        • - [link:https://www.august.com.au/blog/animating-scenes-with-webgl-three-js/ Animating scenes with WebGL and three.js] -
        • -
        - -

        より先進的な内容の記事やコース

        -
          -
        • - [link:https://discoverthreejs.com/ Discover three.js] -
        • -
        • - [link:http://blog.cjgammon.com/ Collection of tutorials] by [link:http://www.cjgammon.com/ CJ Gammon]. -
        • -
        • - [link:https://medium.com/soffritti.pierfrancesco/glossy-spheres-in-three-js-bfd2785d4857 Glossy spheres in three.js]. -
        • -
        • - [link:https://www.udacity.com/course/cs291 Interactive 3D Graphics] - a free course on Udacity that teaches the fundamentals of 3D Graphics, - and uses three.js as its coding tool. -
        • -
        • - [Link:https://aerotwist.com/tutorials/ Aerotwist] tutorials by [link:https://github.com/paullewis/ Paul Lewis]. -
        • -
        • - [link:https://discourse.threejs.org/t/three-js-bookshelf/2468 Three.js Bookshelf] - Looking for more resources about three.js or computer graphics in general? - Check out the selection of literature recommended by the community. -
        • -
        - -

        ニュースとアップデート情報

        -
          -
        • - [link:https://twitter.com/hashtag/threejs Three.js on Twitter] -
        • -
        • - [link:http://www.reddit.com/r/threejs/ Three.js on reddit] -
        • -
        • - [link:http://www.reddit.com/r/webgl/ WebGL on reddit] -
        • -
        - -

        Examples

        -
          -
        • - [link:https://github.com/edwinwebb/three-seed/ three-seed] - three.js starter project with ES6 and Webpack -
        • -
        • - [link:http://stemkoski.github.io/Three.js/index.html Professor Stemkoskis Examples] - a collection of beginner friendly - examples built using three.js r60. -
        • -
        • - [link:https://threejs.org/examples/ Official three.js examples] - these examples are - maintained as part of the three.js repository, and always use the latest version of three.js. -
        • -
        • - [link:https://raw.githack.com/mrdoob/three.js/dev/examples/ Official three.js dev branch examples] - - Same as the above, except these use the dev branch of three.js, and are used to check that - everything is working as three.js being is developed. -
        • -
        - -

        ツール

        -
          -
        • - [link:https://github.com/tbensky/physgl physgl.org] - JavaScript front-end with wrappers to three.js, to bring WebGL - graphics to students learning physics and math. -
        • -
        • - [link:https://whsjs.readme.io/ Whitestorm.js] – Modular three.js framework with AmmoNext physics plugin. -
        • -
        • - [link:http://zz85.github.io/zz85-bookmarklets/threelabs.html Three.js Inspector] -
        • -
        • - [link:http://idflood.github.io/ThreeNodes.js/ ThreeNodes.js]. -
        • -
        • - [link:https://marketplace.visualstudio.com/items?itemName=slevesque.shader vscode shader] - Syntax highlighter for shader language. -
          - [link:https://marketplace.visualstudio.com/items?itemName=bierner.comment-tagged-templates vscode comment-tagged-templates] - Syntax highlighting for tagged template strings using comments to shader language, like: glsl.js. -
        • -
        • - [link:https://github.com/MozillaReality/WebXR-emulator-extension WebXR-emulator-extension] -
        • -
        - -

        WebGLのリファレンス

        -
          -
        • - [link:https://www.khronos.org/files/webgl/webgl-reference-card-1_0.pdf webgl-reference-card.pdf] - Reference of all WebGL and GLSL keywords, terminology, syntax and definitions. -
        • -
        - -

        古いリンク(Old Links)

        -

        - 以下のリンクは歴史的な理由で残っています。現在でも役に立つと思うかもしれませんが、リンク先の情報は - すごく古いバージョンのthree.jsの情報を含んでいる可能性があるので気をつけてください。 -

        - -
          -
        • - [link:https://www.youtube.com/watch?v=Dir4KO9RdhM AlterQualia at WebGL Camp 3] -
        • -
        • - [link:http://yomotsu.github.io/threejs-examples/ Yomotsus Examples] - a collection of examples using three.js r45. -
        • -
        • - [link:http://fhtr.org/BasicsOfThreeJS/#1 Introduction to Three.js] by [link:http://github.com/kig/ Ilmari Heikkinen] (slideshow). -
        • -
        • - [link:http://www.slideshare.net/yomotsu/webgl-and-threejs WebGL and Three.js] by [link:http://github.com/yomotsu Akihiro Oyamada] (slideshow). -
        • -
        • - [link:https://www.youtube.com/watch?v=VdQnOaolrPA Trigger Rally] by [link:https://github.com/jareiko jareiko] (video). -
        • -
        • - [link:http://blackjk3.github.io/threefab/ ThreeFab] - scene editor, maintained up until around three.js r50. -
        • -
        • - [link:http://bkcore.com/blog/3d/webgl-three-js-workflow-tips.html Max to Three.js workflow tips and tricks] by [link:https://github.com/BKcore BKcore] -
        • -
        • - [link:http://12devsofxmas.co.uk/2012/01/webgl-and-three-js/ A whirlwind look at Three.js] - by [link:http://github.com/nrocy Paul King] -
        • -
        • - [link:http://bkcore.com/blog/3d/webgl-three-js-animated-selective-glow.html Animated selective glow in Three.js] - by [link:https://github.com/BKcore BKcore] -
        • -
        • - [link:http://www.natural-science.or.jp/article/20120220155529.php Building A Physics Simulation Environment] - three.js tutorial in Japanese -
        • -
        - - - diff --git a/docs/manual/ja/introduction/WebGL-compatibility-check.html b/docs/manual/ja/introduction/WebGL-compatibility-check.html deleted file mode 100644 index 0e2c2b6f3aaae8..00000000000000 --- a/docs/manual/ja/introduction/WebGL-compatibility-check.html +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - -

        [name]

        -

        - 問題が少なくなってきたとはいえ、端末やブラウザによってはWebGLに対応していない場合があります。以下の方法で、ブラウザがWebGLをサポートしているかどうかを確認し、サポートされていない場合はユーザーにメッセージを表示することができます。 - WebGL サポート検出モジュールをインポートし、レンダリングを試みる前に以下を実行します。 -

        - - - import WebGL from 'three/addons/capabilities/WebGL.js'; - - if ( WebGL.isWebGLAvailable() ) { - - // Initiate function or other initializations here - animate(); - - } else { - - const warning = WebGL.getWebGLErrorMessage(); - document.getElementById( 'container' ).appendChild( warning ); - - } - - - diff --git a/docs/manual/ko/introduction/Animation-system.html b/docs/manual/ko/introduction/Animation-system.html deleted file mode 100644 index 5d1c99f73947b1..00000000000000 --- a/docs/manual/ko/introduction/Animation-system.html +++ /dev/null @@ -1,130 +0,0 @@ - - - - - - - - - -

        애니메이션 시스템([name])

        - -

        Overview

        - -

        - three.js 애니메이션 시스템에서는 모델의 다양한 속성을 설정할 수 있습니다: - [page:SkinnedMesh skinned and rigged model]의 뼈, 모프타깃, 서로 다른 재질의 속성(색상, 불투명도, 참/거짓 연산), - 가시성과 변환이 그 예입니다. 애니메이션의 속성은 페이드 아웃, 페이드 아웃, 크로스페이드, 랩이 있습니다. - 한 오브젝트에 대한 동시에 일어나는 다른 확대 시간 및 가중치 조절이나, 서로 다른 오브젝트간의 애니메이션도 전부 개별로 변화시킬 수 있습니다. - 같은, 혹은 서로 다른 오브젝트틀간의 다양한 애니메이션도 싱크를 맞출 수 있습니다. -

        - - 이를 한 시스템 안에 구현하기 위해서, three.js 애니메이션 시스템은 2015년에 완전히 변경되었으며([link:https://github.com/mrdoob/three.js/issues/6881 Link]) - 되었으며(지난 정보에 주의하세요!), 현재는 Unity/Unreal Engine 4와 유사한 구조를 가지고 있습니다. - 이 페이지에서는 어떻게 시스템 메인 컴포넌트가 구성되고 동작되는지를 간단하게 알아보겠습니다. - -

        - -

        애니메이션 클립(Animation Clips)

        - -

        - 애니메이션 3D 오브젝트를 잘 불러왔다면(구조에 골자 혹은 모프 타깃이 있는지는 상관 없습니다) — 예를 들면 - [link:https://github.com/KhronosGroup/glTF-Blender-IO glTF Blender exporter]라는 익스포터로 추출하고 - [page:GLTFLoader]를 사용해 three.js에 불러왔다면 — 응답 필드 중 하나가 "animations"라는 이름의 배열로 되어 있고 해당 모델에 대한 - [page:AnimationClip AnimationClips]를 담고 있을 것입니다(활용 가능한 로더는 아래 리스트를 확인하세요). -

        - - 각각의 *AnimationClip*은 대개 해당 오브젝트의 특정 행동에 대한 데이터를 담고 있습니다. - 예를 들어 mesh가 이름이라면, walkcycle AnimationClip 한 개와, 두 번째는 jump, 세 번째는 sidestepping 등등이 들어 있을 것입니다. - -

        - -

        키프레임 트랙(Keyframe Tracks)

        - -

        - - 이러한 *AnimationClip* 안에는 각각의 애니메이션 속성별 데이터가 별도의 [page:KeyframeTrack]에 저장되어 있습니다. - [page:Skeleton skeleton] 이라는 오브젝트가 있다고 가정하면, 한 키프레임 트랙은 하완골격의 위치가 수시로 변화하는 데이터를 저장할 수 있을 것이며 - 또 다른 트랙에는 같은 뼈의 회전, 세 번째 트랙에는 다른 뼈의 위치와 회전각, 치수 등을 저장할 것입니다. - 애니매이션 클립은 이런 트랙으로 여러 번 구성될 수 있다는 것을 잘 알아야 합니다. -

        - - 가령 모형이 모프 타깃(예를 들어 한 모프타깃은 웃는 표정, 다른 하나는 화난 표정을 나타낸다거 히면)을 가지고 있다고 하면, 각 트랙은 - 해당 모프 타깃이 변화하는 동안에 어떻게 [page:Mesh.morphTargetInfluences 영향]을 미치는지에 대한 정보를 담고 있을 것입니다. -

        - -

        애니메이션 믹서(Animation Mixer)

        - -

        - - 저장된 데이터 폼은 오직 애니메이션에 대한 기본 정보일 뿐입니다. 실제 플레이백은 [page:AnimationMixer]가 담당합니다. - 이 믹서는 단지 애니메이션 플레이어가 아닌, 리얼 믹서 콘솔처럼 여러 애니메이션을 동시재생, 혼합, 병합재생할 수 있다는 점을 알 수 있을 것입니다. -

        - -

        애니메이션 액션(Animation Actions)

        - -

        - - *AnimationMixer*는 아주 적은(일반적인) 속성과 메서드를 가지고 있는데, - [page:AnimationAction AnimationActions]으로 설정할 수 있습니다. *AnimationAction*을 설정하면 - 특정 *AnimationClip*이 언제, 어떤 믹서에서 실행, 정지, 중지되어야 하는지를 조절할 수 있으며 얼마나 반복되어야 하는지, 페이드나 타임 스케일링이 필요한지, - 크로스페이딩이나 싱크로나이징 같은 것들이 필요한지도 설정할 수 있습니다. -

        - -

        애니메이션 오브젝트 그룹(Animation Object Groups)

        - -

        - - 같은 애니메이션 효과를 공유하는 오브젝트 그룹을 만들고 싶다면, - [page:AnimationObjectGroup]을 사용하면 됩니다. - -

        - -

        지원되는 포맷 및 로더

        - -

        - 애니메이션을 포함한 모든 모델들(OBJ는 확실히 안 됩니다)이 지원되는 것은 아니며, 아주 몇몇 - three.js 로더만 [page:AnimationClip AnimationClip] 시퀀스를 지원합니다. 지원되는 - 애니메이션 타입은 다음과 같습니다: -

        - -
          -
        • [page:ObjectLoader THREE.ObjectLoader]
        • -
        • THREE.BVHLoader
        • -
        • THREE.ColladaLoader
        • -
        • THREE.FBXLoader
        • -
        • [page:GLTFLoader THREE.GLTFLoader]
        • -
        • THREE.MMDLoader
        • -
        - -

        - 3ds max와 Maya는 현재 여러 애니메이션 (같은 시간대에 있지 않은 애니메이션)을 한 파일로 내보내기 할 수 없습니다. -

        - -

        예제

        - - - let mesh; - - // AnimationMixer 만들기, AnimationClip 인스턴스 리스트 가져오기 - const mixer = new THREE.AnimationMixer( mesh ); - const clips = mesh.animations; - - // 각 프레임의 mixer 업데이트 - function update () { - mixer.update( deltaSeconds ); - } - - // 특정 애니메이션 재생 - const clip = THREE.AnimationClip.findByName( clips, 'dance' ); - const action = mixer.clipAction( clip ); - action.play(); - - // 전체 애니메이션 재생 - clips.forEach( function ( clip ) { - mixer.clipAction( clip ).play(); - } ); - - - - diff --git a/docs/manual/ko/introduction/Creating-a-scene.html b/docs/manual/ko/introduction/Creating-a-scene.html deleted file mode 100644 index bf05a0b9f36cf7..00000000000000 --- a/docs/manual/ko/introduction/Creating-a-scene.html +++ /dev/null @@ -1,167 +0,0 @@ - - - - - - - - - -

        장면 만들기([name])

        - -

        이 장의 목표는 three.js에 대한 간략한 설명을 하는 것입니다. spinning cube라는 장면을 설정하는 것부터 시작할 것입니다. 막히거나 도움이 필요할 때에는 아래쪽의 실습 예제를 확인해주세요.

        - -

        시작하기에 앞서

        - -

        three.js를 사용하려면, 표시할 수 있는 공간이 필요합니다. Save the following HTML to a file on your computer and open it in your browser.

        - - - <!DOCTYPE html> - <html> - <head> - <meta charset="utf-8"> - <title>My first three.js app</title> - <style> - body { margin: 0; } - canvas { display: block; } - </style> - </head> - <body> - <script type="module"> - import * as THREE from 'https://unpkg.com/three/build/three.module.js'; -´ - // Our Javascript will go here. - </script> - </body> - </html> - - -

        이게 전부입니다. 모든 코드들은 비어있는 <script> 태그 안에 작성될 것입니다.

        - -

        Scene 만들기

        - -

        three.js로 무언가를 표현하려면 scene, camera 그리고 renderer가 필요합니다. 이를 통해 카메라로 장면을 구현할 수 있습니다.

        - - - const scene = new THREE.Scene(); - const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); - - const renderer = new THREE.WebGLRenderer(); - renderer.setSize( window.innerWidth, window.innerHeight ); - document.body.appendChild( renderer.domElement ); - - -

        여기에서 잠깐 어떤 일들이 일어나는지 짚고 넘어가봅시다. 우선은 scene, camera renderer를 설정했습니다.

        - -

        three.js에는 몇가지 종류의 카메라가 있는데, 이번에는PerspectiveCamera를 사용해 봅시다.

        - -

        첫 번째 속성은 field of view(시야각)입니다. FOV(시야각)는 해당 시점의 화면이 보여지는 정도를 나타냅니다. 값은 각도 값으로 설정합니다.

        - -

        두 번째 속성은 aspect ratio(종횡비)입니다. 대부분의 경우 요소의 높이와 너비에 맞추어 표시하게 할텐데, 그렇지 않으면 와이드스크린에 옛날 영화를 트는 것처럼 이미지가 틀어져 보일 것입니다.

        - -

        다음 두 속성은 nearfar 절단면입니다. 무슨 뜻인가 하면, far 값 보다 멀리 있는 요소나 near 값보다 가까이 있는 오브젝트는 렌더링 되지 않는다는 뜻입니다. 지금 시점에서 이것까지 고려할 필요는 없지만, 앱 성능 향상을 위해 사용할 수 있습니다.

        - -

        다음은 renderer입니다. 마법이 일어나는 곳입니다. 같이 사용하는 WebGLRenderer와 더불어, three.js는 다른 몇가지 renderer를 사용하는데, 오래된 브라우저 혹은 모종의 사유로 WebGL을 지원 안할때의 대비용으로 사용하는 것입니다.

        - -

        renderer 인스턴스를 생섬함과 동시에, 렌더링 할 곳의 크기를 설정해줘야 합니다. 렌더링할 구역의 높이와 너비를 설정하는 것은 좋은 방법입니다. 이 경우, 높이와 너비는 각각 브라우저 윈도우의 크기가 됩니다. 성능 개선을 중시하는 앱의 경우, setSize를 사용하거나 window.innerWidth/2, window.innerHeight/2를 사용해서 화면 크기의 절반으로 구현할 수도 잇씁니다.

        - -

        사이즈는 그대로 유지하고 싶지만 더 낮은 해상도로 렌더링하고 싶을 경우, setSizeupdateStyle (세 번째 인자)를 false로 불러오면 됩니다. setSize(window.innerWidth/2, window.innerHeight/2, false)처럼 사용하면 <canvas>가 100%의 높이, 너비로 되어있다는 기준 하에 절반의 해상도로 렌더링 될 것입니다.

        - -

        마지막으로 제일 중요한 renderer 엘리먼트를 HTML 문서 안에 넣었습니다. 이는<canvas> 엘리먼트로, renderer가 scene을 나타내는 구역입니다.

        - -

        "그건 그렇고, 아까 말했던 큐브는 어디에 있죠?" 바로 추가해 봅시다.

        - - - const geometry = new THREE.BoxGeometry( 1, 1, 1 ); - const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); - const cube = new THREE.Mesh( geometry, material ); - scene.add( cube ); - - camera.position.z = 5; - - -

        큐브를 만드려면, BoxGeometry가 필요합니다. 여기에는 큐브에 필요한 모든 꼭짓점 (vertices) 와 면(faces)이 포함돼 있습니다. 여기에 대해서는 나중에 더 자세히 알아봅시다.

        - -

        geometry와 더불어, 무언가를 색칠해줄 요소가 필요합니다. Three.js에서는 여러 방법을 고려했지만, 현재로서는MeshBasicMaterial을 고수하고자 합니다. 이 속성이 적용된 오브젝트들은 모두 영향을 받을 것입니다. 가장 단순하게 알아볼 수 있도록, 여기에서는 녹색인 0x00ff00만을 속성으로 사용하겠습니다. CSS 나 Photoshop에서처럼 (hex colors)로 동일하게 작동합니다.

        - -

        세 번째로 필요한 것은Mesh입니다. mesh는 기하학을 받아 재질을 적용하고 우리가 화면 안에 삽입하고 자유롭게 움직일 수 있게 해 줍니다. - -

        기본 설정상 scene.add()를 불러오면, 추가된 모든 것들은 (0,0,0) 속성을 가집니다. 이렇게 되면 카메라와 큐브가 동일한 위치에 겹치게 되겠죠. 이를 피하기 위해, 카메라를 약간 움직여 두었습니다.

        - -

        scene 렌더링

        - -

        맨 처음에 있던 HTML 파일을 복사해서 열어놨다면, 아무것도 보이지 않을 것입니다. 왜냐하면 아직 아무것도 렌더링하지 않았기 때문입니다. 이를 해결하려면 render or animate loop라는 것이 필요합니다..

        - - - function animate() { - requestAnimationFrame( animate ); - renderer.render( scene, camera ); - } - animate(); - - -

        이 코드는 화면이 새로고침 될 때마다 계속해서 렌더링을 해 줄 것입니다. (일반적인 경우에 1초에 60번 렌더링 됩니다). 웹 브라우저에서 게임을 만들기 시작한 지 얼마 안 된 분이라면, "왜 그냥 setInterval을 쓰지 않는거죠?"라고 질문할 수도 있을 겁니다. 단적으로 말하면 가능은 합니다. 하지만 requestAnimationFrame 을 사용하는 것이 훨씬 이점이 많습니다. 아마 가장 큰 이점은 유저가 브라우저 창에서 이탈했을때 멈춰주는 기능일 것입니다. 이를 통해 소중한 전력과 배터리를 아낄 수 있죠.

        - -

        큐브 애니메이팅

        - -

        시작할 때 만들었던 파일에 이전까지의 코드를 모두 작성해서 넣었을 경우, 초록색 박스를 확인할 수 있을 것입니다. 이 박스를 회전시켜 보면서 조금 더 재미있게 만들어봅시다.

        - -

        다음 코드를 animate함수 안의 renderer.render 바로 위에 넣어주세요.

        - - - cube.rotation.x += 0.01; - cube.rotation.y += 0.01; - - -

        위 코드는 모든 프레임마다 실행되면서 (일반적으로 1초에 60번), 큐브가 멋지게 돌아가도록 만들어 줄겁니다. 기본적으로 앱을 실행하는 동안 무언가를 움직이거나 변형하고 싶을때, animate loop를 사용하면 됩니다. 물론 다른 함수를 불러올 수도 있고, animate 함수 안에 수백줄을 작성할 필요도 없습니다.

        - -

        결과 화면

        -

        축하합니다! 첫 three.js이 완성되었네요. 이제 본격적으로 시작해보면 됩니다.

        - -

        전체 코드는 아래에 나와 있고 [link:https://jsfiddle.net/0c1oqf38/ live example]로도 확인해볼 수 있습니다. 잘 살펴보고 어떻게 구동되는지 확인해 보세요.

        - - - <!DOCTYPE html> - <html> - <head> - <meta charset="utf-8"> - <title>My first three.js app</title> - <style> - body { margin: 0; } - canvas { display: block; } - </style> - </head> - <body> - <script type="module"> - import * as THREE from 'https://unpkg.com/three/build/three.module.js'; - - const scene = new THREE.Scene(); - const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); - - const renderer = new THREE.WebGLRenderer(); - renderer.setSize( window.innerWidth, window.innerHeight ); - document.body.appendChild( renderer.domElement ); - - const geometry = new THREE.BoxGeometry( 1, 1, 1 ); - const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); - const cube = new THREE.Mesh( geometry, material ); - scene.add( cube ); - - camera.position.z = 5; - - function animate() { - requestAnimationFrame( animate ); - - cube.rotation.x += 0.01; - cube.rotation.y += 0.01; - - renderer.render( scene, camera ); - } - - animate(); - </script> - </body> - </html> - - - diff --git a/docs/manual/ko/introduction/Creating-text.html b/docs/manual/ko/introduction/Creating-text.html deleted file mode 100644 index a35c2d4fdf1b96..00000000000000 --- a/docs/manual/ko/introduction/Creating-text.html +++ /dev/null @@ -1,113 +0,0 @@ - - - - - - - - - -

        텍스트 만들기([name])

        -
        -

        - three.js에서 텍스트를 활용하고 싶을 경우가 많을 것입니다. - 그 방법의 몇가지를 소개합니다. -

        -
        - -

        1. DOM + CSS

        -
        -

        - HTML을 사용하는 것은 텍스트를 추가하는 가장 쉽고 빠른 방법입니다. 대부분의 three.js 예제에서 오버레이 설명에 사용되는 방식입니다. -

        -

        내용을 추가하려면 다음과 같이 입력합니다.

        - <div id="info">Description</div> - -

        - 절대 위치를 설정하려면 CSS 마크업을 사용하고, 특히 three.js를 전체화면으로 사용한다면 z-index를 활용합니다. -

        - - -#info { - position: absolute; - top: 10px; - width: 100%; - text-align: center; - z-index: 100; - display:block; -} - - -
        - - - -

        2. 캔버스에 텍스트를 그리고 [page:Texture]로 사용

        -
        -

        three.js scene에 손쉽게 텍스트를 그리고싶은 경우에 이 메서드를 사용하세요.

        -
        - - -

        3. 본인이 가장 선호하는 3D 앱으로 만들고 three.js로 export 하세요.

        -
        -

        본인의 3d 작업 앱을 선호하는 경우 이 메서드를 사용해 three.js로 모델을 import하세요.

        -
        - - - -

        4. 절차적 텍스트 geometry

        -
        -

        - THREE.js만을 사용해 절차적 및 동적 3D 텍스트 geometry를 사용하고 싶으면, geometry이 THREE.TextGeometry의 인스턴스인 mesh를 사용하면 됩니다. -

        -

        - new THREE.TextGeometry( text, parameters ); -

        -

        - 하지만 이 작업을 수행하려면, TextGeometry의 "font" 파라미터가 THREE.Font 인스턴스로 설정되어 있어야 합니다. - - 이 과정이 어떻게 작동하는지, 각각의 파라미터에 대한 설명, THREE.js가 가지고 있는 JSON 폰트 리스트를 확인하려면 [page:TextGeometry] 페이지를 참고해 주세요. -

        - -

        예시

        - -

        - [example:webgl_geometry_text WebGL / geometry / text]
        - [example:webgl_shadowmap WebGL / shadowmap] -

        - -

        - 글꼴이 없거나, 다른 글꼴을 사용하고 싶으면 python 스크립트를 통해 Three.js의 JSON 포맷으로 파일을 export 하세요: - [link:http://www.jaanga.com/2012/03/blender-to-threejs-create-3d-text-with.html] -

        - -
        - - - -

        5. 비트맵 글꼴

        -
        -

        - BMFonts (비트맵 폰트) 는 단일 BufferGeometry에 글자들을 일괄적으로 활용할 수 있습니다. BMFont 렌더링은 - word-wrapping, letter spacing, kerning, signed distance fields with standard - derivatives, multi-channel signed distance fields, multi-texture fonts 등을 지원합니다. - [link:https://github.com/Jam3/three-bmfont-text three-bmfont-text]를 확인해 보세요. -

        -

        - Stock 폰트는 - [link:https://github.com/etiennepinchon/aframe-fonts A-Frame Fonts]처럼 사용 가능하고, - 자신만의 .TTF 폰트를 만들어서, 프로젝트에 필요한 문자들만 불러와 최적화할 수 도 있습니다. -

        -

        - 참고 자료: -

        -
          -
        • [link:http://msdf-bmfont.donmccurdy.com/ msdf-bmfont-web] (웹 기반)
        • -
        • [link:https://github.com/soimy/msdf-bmfont-xml msdf-bmfont-xml] (커맨드라인)
        • -
        • [link:https://github.com/libgdx/libgdx/wiki/Hiero hiero] (데스크탑 앱)
        • -
        -
        - - - - - \ No newline at end of file diff --git a/docs/manual/ko/introduction/Drawing-lines.html b/docs/manual/ko/introduction/Drawing-lines.html deleted file mode 100644 index 454c23b0045289..00000000000000 --- a/docs/manual/ko/introduction/Drawing-lines.html +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - -

        선 그리기([name])

        -
        -

        - 와이어프레임 [page:Mesh]를 사용하지 않고 선이나 원을 그려봅시다. - 먼저 [page:WebGLRenderer renderer], [page:Scene scene] camera를 설정합니다. (scene 페이지를 참고하세요). -

        - -

        사용할 코드는 다음과 같습니다:

        - -const renderer = new THREE.WebGLRenderer(); -renderer.setSize( window.innerWidth, window.innerHeight ); -document.body.appendChild( renderer.domElement ); - -const camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 500 ); -camera.position.set( 0, 0, 100 ); -camera.lookAt( 0, 0, 0 ); - -const scene = new THREE.Scene(); - -

        다음으로는 재질을 정의해야 합니다. 선을 그리기 위해서는 [page:LineBasicMaterial]나 [page:LineDashedMaterial]를 사용하면 됩니다.

        - -//create a blue LineBasicMaterial -const material = new THREE.LineBasicMaterial( { color: 0x0000ff } ); - - -

        - 그 다음에는 꼭짓점에 대한 기하학을 정의해야 합니다: -

        - - -const points = []; -points.push( new THREE.Vector3( - 10, 0, 0 ) ); -points.push( new THREE.Vector3( 0, 10, 0 ) ); -points.push( new THREE.Vector3( 10, 0, 0 ) ); - -const geometry = new THREE.BufferGeometry().setFromPoints( points ); - - -

        선은 연속된 꼭짓점 쌍 사이에 그려지고 첫 번재와 마지막 꼭짓점에는 그려지지 않습니다. (선은 닫혀있지 않습니다.)

        - -

        이제 두 선을 그리기 위한 점과 재질이 있으니, 합쳐서 선을 만들 수 있습니다.

        - -const line = new THREE.Line( geometry, material ); - -

        이제 남은 것은 scene에 추가하고 [page:WebGLRenderer.render render]를 하는 것입니다.

        - - -scene.add( line ); -renderer.render( scene, camera ); - - -

        위로 향하고 있는 두 개의 파란 선으로 된 화살표를 확인할 수 있습니다.

        -
        - - - \ No newline at end of file diff --git a/docs/manual/ko/introduction/FAQ.html b/docs/manual/ko/introduction/FAQ.html deleted file mode 100644 index 4b873d012f1866..00000000000000 --- a/docs/manual/ko/introduction/FAQ.html +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - -

        [name]

        - -

        어떤 3D 모델 포맷이 가장 잘 지원되나요?

        -
        -

        - 불러오기 및 내보내기 용으로 추천되는 포맷은 glTF (GL Transmission Format)입니다. glTF는 런타임 에셋 딜리버리에 초점이 맞추어져 있기 때문에, 전송에 적합하고 로딩이 빠릅니다. -

        -

        - three.js 널리 쓰이는 포맷인 FBX, Collada 나 OBJ 도 지원합니다. 하지만 첫 프로젝트에서는 glTF 기반의 워크플로우로 작업해야 합니다. 더 자세한 내용은, [link:#manual/introduction/Loading-3D-models loading 3D models]를 참고하세요. -

        -
        - -

        예제에 왜 meta viewport 태그가 있나요?

        -
        - <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> - -

        이 태그들은 모바일 브라우저들의 뷰포트 크기와 확대정도를 조절합니다(페이지 내용이 뷰포트 영역과 다른 사이즈로 렌더링 된 경우).

        - -

        [link:https://developer.apple.com/library/content/documentation/AppleApplications/Reference/SafariWebContent/UsingtheViewport/UsingtheViewport.html Safari: Using the Viewport]

        - -

        [link:https://developer.mozilla.org/en-US/docs/Web/HTML/Viewport_meta_tag MDN: Using the viewport meta tag]

        -
        - -

        화면 확대 정도가 리사이징 시에 유지될 수 있나요?

        -

        - 모든 오브젝트들이 카메라의 거리와 상관 없이, 화면 사이즈가 변경된다고 해도, 같은 크기로 보여지고 싶다고 가정합시다. - - 이 공식을 풀기 위한 핵심 방정식은 가시 높이와 거리와 관련된 아래 공식입니다. - - -visible_height = 2 * Math.tan( ( Math.PI / 180 ) * camera.fov / 2 ) * distance_from_camera; - - 화면 높이를 특정 비율로 늘리면, 모든 가시 높이와 거리가 같은 비율로 증가해야 합니다. - - 이는 카메라 위치를 변경하는 것으로는 불가능합니다. 대신에 카메라 field-of-view를 변경해야합니다. - [link:http://jsfiddle.net/Q4Jpu/ Example]. -

        - -

        왜 오브젝트 일부가 안 보일까요?

        -

        - 이는 페이스 컬링 문제일 수 있습니다. 각 면들은 어느 방향이 어느 방향인지에 대한 정보를 가지고 있습니다. 그리고 컬링은 일반적으로 뒷편의 면을 제거해 버립니다. 이 문제가 의심된다면 재질의 면을 THREE.DoubleSide로 변경해 보세요. - material.side = THREE.DoubleSide -

        - -

        Why does three.js sometimes return strange results for invalid inputs?

        -

        - For performance reasons, three.js doesn't validate inputs in most cases. It's your app's responsibility to make sure that all inputs are valid. -

        - - diff --git a/docs/manual/ko/introduction/How-to-create-VR-content.html b/docs/manual/ko/introduction/How-to-create-VR-content.html deleted file mode 100644 index 2534d9d5e19a3e..00000000000000 --- a/docs/manual/ko/introduction/How-to-create-VR-content.html +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - - - - -

        VR 컨텐츠를 만드는 방법[name]

        - -

        - 이 가이드에서는 three.js를 통한 웹 기반 VR 앱의 기본 컴포넌트를 만드는 방법을 알려드리겠습니다. -

        - -

        작업 순서

        - -

        - 먼저, [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/webxr/VRButton.js VRButton.js] - 를 프로젝트에 불러옵니다. -

        - - -import { VRButton } from 'three/addons/webxr/VRButton.js'; - - -

        - *VRButton.createButton()*은 두 가지 중요한 일을 합니다: VR에서 활용이 가능한 버튼을 만듭니다. 그리고 유저가 버튼을 누르면 - VR 세션을 실행시킵니다. 다음 코드를 삽입하기만 하면 됩니다. -

        - - -document.body.appendChild( VRButton.createButton( renderer ) ); - - -

        - 다음으로, *WebGLRenderer* 인스턴스에게 XR 렌더링을 허용해줘야합니다. -

        - - -renderer.xr.enabled = true; - - -

        - 마지막으로, 자주 쓰이는 *window.requestAnimationFrame()* 기능을 활용할 수 없기 때문에, 애니메이션 루프를 수정해주어야 합니다. - VR 프로젝트에서는 [page:WebGLRenderer.setAnimationLoop setAnimationLoop]를 사용합니다. - 가장 간소화된 코드는 다음과 같습니다: -

        - - -renderer.setAnimationLoop( function () { - - renderer.render( scene, camera ); - -} ); - - -

        다음 절차

        - -

        - 실행을 위한 작업 절차와 관련된 공식 WebVR 예제를 확인하세요.

        - - [example:webxr_vr_ballshooter WebXR / VR / ballshooter]
        - [example:webxr_vr_cubes WebXR / VR / cubes]
        - [example:webxr_vr_dragging WebXR / VR / dragging]
        - [example:webxr_vr_paint WebXR / VR / paint]
        - [example:webxr_vr_panorama_depth WebXR / VR / panorama_depth]
        - [example:webxr_vr_panorama WebXR / VR / panorama]
        - [example:webxr_vr_rollercoaster WebXR / VR / rollercoaster]
        - [example:webxr_vr_sandbox WebXR / VR / sandbox]
        - [example:webxr_vr_sculpt WebXR / VR / sculpt]
        - [example:webxr_vr_video WebXR / VR / video] -

        - - - - diff --git a/docs/manual/ko/introduction/How-to-dispose-of-objects.html b/docs/manual/ko/introduction/How-to-dispose-of-objects.html deleted file mode 100644 index ab18e557b98f26..00000000000000 --- a/docs/manual/ko/introduction/How-to-dispose-of-objects.html +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - -

        오브젝트를 폐기하는 방법([name])

        - -

        - 애플리케이션에서 성능을 개선하고 메모리 누수를 방지하기 위한 중요한 한 가지는 사용되지 않는 라이브러리 개체를 폐기하는 것입니다. - *three.js* 유형의 인스턴스는 생성될 때마다 일정량의 메모리를 할당하게 됩니다. 하지만 *three.js*는 특정 개체에 대해 기하학적 구조나 WebGL 관련 재질(예: 버퍼 또는 쉐이더 프로그램) 개체의 렌더링에 필요한 것입니다. - 이러한 오브젝트들은 자동으로 폐기되지 않는다는 점을 주의하세요. 대신, 애플리케이션에서는 메모리 자원을 확보하기 위해 특별한 API를 사용해야 합니다. - 이 가이드에서는 이 API의 사용 방법과 이와 관련해서 어떤 오브젝트가 있는지에 대한 간략한 설명을 제공해 드립니다. -

        - -

        기하학

        - -

        - 기하학은 속성 집합으로 정의된 꼭짓점 정보를 표시하는데, *three.js*는 속성마다 하나의 [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLBuffer WebGLBuffer] 유형의 대상을 만들어 내부에 저장합니다. - 이러한 개체는 [page:BufferGeometry.dispose]를 호출할 때만 폐기됩니다. - 만약 애플리케이션에서 기하학을 더이상 사용하지 않는다면, 이 방법을 실행하여 모든 관련 자원을 폐기하세요. -

        - -

        재질

        - -

        - 재질은 오브젝트가 어떻게 렌더링되는지를 정의합니다. *three.js*는 재질에 정의된 정보를 사용하여 렌더링을 위한 하나의 셰이더 프로그램을 구축합니다. - 셰이더 프로그램은 해당 재질이 폐기된 후에만 삭제할 수 있습니다. 성능상의 이유로 *three.js*는 가능하다면 활용 가능한 셰이더 프로그램을 재사용하게 되어 있습니다. 따라서 셰이더 프로그램은 모든 관련된 재질들이 사라진 후에야 삭제됩니다. - [page:Material.dispose]() 를 실행함으로써 재질을 폐기할 수 있습니다. -

        - -

        텍스쳐

        - -

        - 재질의 폐기는 텍스쳐에 영향을 미치지 않습니다. 이들은 분리되어 있어 하나의 텍스쳐를 여러 재질로 동시에 사용할 수 있습니다. - [page:Texture] 인스턴스를 만들 때마다 three.js는 내부에서 [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLTexture WebGLTexture] 인스턴스를 만듭니다. - buffer와 비슷하게, 이 오브젝트는 [page:Texture.dispose]() 호출로만 삭제가 가능합니다. -

        - -

        - If you use an *ImageBitmap* as the texture's data source, you have to call [link:https://developer.mozilla.org/en-US/docs/Web/API/ImageBitmap/close ImageBitmap.close]() at the application level to dispose of all CPU-side resources. - An automated call of *ImageBitmap.close()* in [page:Texture.dispose]() is not possible, since the image bitmap becomes unusable, and the engine has no way of knowing if the image bitmap is used elsewhere. -

        - -

        렌더링 대상

        - -

        - [page:WebGLRenderTarget] 타입의 오브젝트는 [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLTexture WebGLTexture]의 인스턴스가 할당되어 있을 뿐만 아니라, - [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLFramebuffer WebGLFramebuffer]와 [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderbuffer WebGLRenderbuffer] 도 할당되어, - 커스텀 렌더링 목표를 실체화합니다. 이러한 오브젝트는 [page:WebGLRenderTarget.dispose]()를 실행해야만 폐기할 수 있습니다. -

        - -

        그밖의 것들

        - -

        - 컨트롤러나 후기 처리 프로세스처럼 *dispose()* 메서드가 제공되어 내부 이벤트리스너나 렌더링 대상을 폐기하는 examples 폴더에서 가져온 것들이 있습니다. - 일반적으로 API나 파일을 열람하고 *dispose()* 를 확인하는 것이 좋습니다. 만약 이 메서드가 존재한다면 당연히 그 메서드를 폐기 시에 사용해야 합니다.e it when cleaning things up. -

        - -

        FAQ

        - -

        *three.js*는 왜 자동으로 오브젝트를 폐기 못하나요?

        - -

        - 이 문제가 커뮤니티에서 여러 차례 제기돼 온 만큼 확실한 대답을 해 드려야겠습니다. 사실, *three.js*는 사용자가 만든 개체(예를 들어 기하체 또는 재질)의 라이프사이클이나 역할 범위를 알지 못하며, 이는 애플리케이션의 책임입니다. - 예를 들어, 만약 하나의 재질이 현재 렌더링에 사용되고 있지 않더라도, 바로 다음 프레임에 필수적인 재질일 수 있을 것입니다. 그래서 만약 애플리케이션에서 특정 오브젝트가 삭제되어도 된다고 하면, - 해당하는 *dispose()* 메서드를 통해 엔진에 알려줘야 합니다. -

        - -

        화면에서 mesh를 지우는 것도 geometry와 material을 폐기시키나요?

        - -

        - 아니요, *dispose()*를 통해 명시적으로 geometry와 material을 폐기해야합니다. geometry와 material은 mesh와 마찬가지로 3D 오브젝트에서 활용할 수 있다는 점을 명심하세요. -

        - -

        *three.js*에서 캐시화된 오브젝트의 수를 알 수 있나요?

        - -

        - 네, 렌더러의 그래빅 보드와 렌더링 프로세스의 통계 정보를 담고 있는 특수 속성인 [page:WebGLRenderer.info]를 통해 확인할 수 있습니다. - 다른 것 보다도, 이를 통해 내부적으로 많은 텍스쳐와 기하학, 셰이더 프로그램이 저장되어 있다는 것을 알 수 있습니다. - 만약 애플리케이션에서 성능 문제를 발견했다면, 바로 이 속성을 조절하여 쉽게 메모리 누출을 확인할 수 있습니다. -

        - -

        이미지가 아직 로딩이 안 됐는데 텍스쳐에서 *dispose()*를 해버리면 어떻게 되나요?

        - -

        - 텍스쳐와 관련된 내부 자원은 이미지가 완전히 로딩됐을때밖에 할당이 되지 않습니다. 만약 이미지 로딩 전에 텍스쳐를 폐기해버렸다면, - 아무 일도 일어나지 않을 것입니다. 아무런 자원도 할당되지 않았고 지울 것도 없기 때문입니다. -

        - -

        *dispose()*를 하고 나서 나중에 다시 각각의 오브젝트를 사용하면 어떻게 되나요?

        - -

        - 삭제된 내부 자원이 엔진에서 다시 생성될 것입니다. 런타임 에러는 발생하지 않겠지만 프레임에 부정적인 영향을 미치는 것을 확인할 수 있을 것이고 - 특히 셰이더 프로그램을 컴파일할 때 더 두드러질 것입니다. -

        - -

        *three.js* 오브젝트를 앱에서 어떻게 관리해야 할까요? 언제 폐기해야되는지 어떻게 알죠?

        - -

        - 일반적으로 이 점에 대한 명확한 해답은 없습니다. *dispose()*는 구체적인 사용 방법에 따라 적절히 활용하는 방법이 좌우됩니다. 굳이 오브젝트를 자꾸 폐기할 필요는 없다는 것을 기억해 두세요. - 다양한 레벨로 구성된 게임이 좋은 예가 될 수 있을 것입니다. 레벨이 바뀌면, 폐기를 할 때입니다. 애플리케이션은 오래된 화면을 지나가면서 오래된 재질, 기하학, 텍스쳐를 모두 폐기할 수 있습니다. - 앞의 장에서 언급한 바와 같이 만약 여전히 사용하고 있는 오브젝트를 폐기해도 런타임 에러를 만들지는 않을 것입니다. 단일 프레임에서 퍼포먼스가 떨어지는 정도가 가장 안 좋은 정도일 것입니다. -

        - -

        dispose() 사용법 예제

        - -

        - [example:webgl_test_memory WebGL / test / memory]
        - [example:webgl_test_memory2 WebGL / test / memory2]
        -

        - - - - diff --git a/docs/manual/ko/introduction/How-to-update-things.html b/docs/manual/ko/introduction/How-to-update-things.html deleted file mode 100644 index 748a453658d8d8..00000000000000 --- a/docs/manual/ko/introduction/How-to-update-things.html +++ /dev/null @@ -1,216 +0,0 @@ - - - - - - - - - -

        오브젝트를 업데이트하는 방법([name])

        -
        -

        모든 오브젝트들은 기본적으로 자동으로 장면에 추가됐을 때 자신들의 행렬구조를 업데이트합니다.

        - -const object = new THREE.Object3D(); -scene.add( object ); - - 혹은 다른 오브젝트의 자식으로 장면에 추가될 때도 마찬가지입니다. - -const object1 = new THREE.Object3D(); -const object2 = new THREE.Object3D(); - -object1.add( object2 ); -scene.add( object1 ); //object1과 object2는 자동으로 자신들의 행렬구조를 업데이트할 것입니다. - -
        - -

        하지만 오브젝트가 고정되어야 한다면, 이 설정을 풀고 수동으로 행렬구조를 갱신할 수 있습니다.

        - - -object.matrixAutoUpdate = false; -object.updateMatrix(); - - -

        BufferGeometry

        -
        -

        - BufferGeometries는 [page:BufferAttribute buffers]에 다양한 정보(꼭짓점 위치, 면 순서, 법선, 색깔, - UV, 모든 커스텀 속성들)를 저장합니다. - 이는 - [link:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays typed arrays]라는 의미입니다. - 이는 기본 Geometries보다 빠르게 작동하지만, 그 대신 작업하기가 더 어렵습니다. -

        -

        - BufferGeometries를 업데이트 하는 것과 관련해서, buffers의 크기를 조절할 수 없다는 것을 이해하는 것이 가장 중요합니다. - (작업이 아주 어렵기 때문에, 새 geometry를 만드는 수준입니다). - 그 대신에 buffers의 내용을 변경할 수 있습니다. -

        -

        - 이는 곧 만약 BufferGeometry의 속성이 증가할 것이라고 예측이 된다면, 예를 들어 꼭짓점의 갯수라면, - 만들어질 수 있는 새로운 꼭짓점들을 담을 수 있는 충분한 buffer를 미리 준비해 놓아야 합니다. - 물론 이는 당신이 사용할 BufferGeometry의 상한치가 있을 것이라는 뜻이기도 합니다 - 효율적으로 무한대로 확장되는 BufferGeometry를 만드는 방법은 없습니다. -

        -

        - 렌더링 시점에 확장되는 선을 예로 들어보겠습니다. buffer에는 500 개의 꼭짓점을 할당하겠지만 처음에는 [page:BufferGeometry.drawRange]를 이용해 두 개만 그릴 것입니다.. -

        - -const MAX_POINTS = 500; - -// geometry -const geometry = new THREE.BufferGeometry(); - -// attributes -const positions = new Float32Array( MAX_POINTS * 3 ); // 3 vertices per point -geometry.setAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) ); - -// draw range -const drawCount = 2; // draw the first 2 points, only -geometry.setDrawRange( 0, drawCount ); - -// material -const material = new THREE.LineBasicMaterial( { color: 0xff0000 } ); - -// line -const line = new THREE.Line( geometry, material ); -scene.add( line ); - -

        - 그 다음, 아래와 같은 패턴으로 무작위로 선에 점을 생성해 줄 것입니다: -

        - -const positionAttribute = line.geometry.getAttribute( 'position' ); - -let x = 0, y = 0, z = 0; - -for ( let i = 0; i < positionAttribute.count; i ++ ) { - - positionAttribute.setXYZ( i, x, y, z ); - - x += ( Math.random() - 0.5 ) * 30; - y += ( Math.random() - 0.5 ) * 30; - z += ( Math.random() - 0.5 ) * 30; - -} - -

        - 첫 렌더링 이후에 점의 갯수를 변경하고 싶다면, 다음과 같이 실행하세요: -

        - -line.geometry.setDrawRange( 0, newValue ); - -

        - 첫 렌더링 이후에 position 데이터 수치를 변경하고 싶다면, needsUpdate 플래그를 다음과 같이 설정해야 합니다: -

        - -positionAttribute.needsUpdate = true; // required after the first render - - -

        - 첫 렌더링 이후에 position 데이터 수치를 변경한다면, bounding volumes를 재계산해서 - 다른 엔진의 절두체 컬링 혹은 헬퍼같은 특성들이 적절히 작동하도록 해야합니다. -

        - -line.geometry.computeBoundingBox(); -line.geometry.computeBoundingSphere(); - - -

        - [link:https://jsfiddle.net/t4m85pLr/1/ Here is a fiddle] showing an animated line which you can adapt to your use case. -

        - -

        Examples

        - -

        - [example:webgl_custom_attributes WebGL / custom / attributes]
        - [example:webgl_buffergeometry_custom_attributes_particles WebGL / buffergeometry / custom / attributes / particles] -

        - -
        - -

        재질(Materials)

        -
        -

        모든 uniforms 값들은 자유롭게 변경이 가능합니다. (예를 들면 colors, textures, opacity, etc), 값들은 shader에 매 프레임 전송됩니다.

        - -

        GLstate와 관련된 파라미터들 또한 언제나 변경 가능합니다.(depthTest, blending, polygonOffset, 등).

        - -

        아래 속성들은 런타임에서 쉽게 변경할 수 없습니다. (적어도 재질이 한 번 렌더링 된 이후):

        -
          -
        • uniforms의 갯수와 타입
        • -
        • 아래 항목들의 사용 혹은 비사용 여부 -
            -
          • texture
          • -
          • fog
          • -
          • vertex colors
          • -
          • morphing
          • -
          • shadow map
          • -
          • alpha test
          • -
          • transparent
          • -
          -
        • -
        - -

        이러한 것들은 새로운 shader 프로그램 생성이 필요합니다. 다음과 같이 설정합니다.

        - material.needsUpdate = true - -

        이 기능은 매우 느리고 프레임이 끊겨보일 수 있다는 점(특히 Windows처럼, shader 편집이 DirectX에서 OpenGL보다 느린 경우)을 명심해주세요.

        - -

        좀 더 부드럽게 하기 위해서는, 값이 0인 빛, 흰색 텍스쳐, 밀도가 0인 안개 등의 "가상" 값을 가지도록 특성들을 변경해 시뮬레이션해 볼 수 있습니다.

        - -

        기하학 블록에 사용되는 재질을 자유롭게 바꿀 수 있지만, 오브젝트를 어떻게 블록으로 나눌 지에 대한 점은 변경할 수 없습니다(재질의 면에 따라).

        - -

        런타임 중에 재질의 서로 다른 설정을 해야 할 때:

        -

        재질과 블록의 수가 적다면, 오브젝트를 미리 분리해놓을 수 있습니다. (예를 들면 사람에 있어서 머리/얼굴/상의/바지, 자동차에 있어서 앞부분/옆부분/윗부분/유리/타이어/내부).

        - -

        수가 많다면 (예를 들어 모든 얼굴들이 조금씩 다른 경우), 다른 해결 방법을 생각해봐야 하는데, 속성/텍스쳐를 사용하여 얼굴마다 다른 형태를 입히는 방법 등이 있을 것입니다..

        - -

        예제

        -

        - [example:webgl_materials_car WebGL / materials / car]
        - [example:webgl_postprocessing_dof WebGL / webgl_postprocessing / dof] -

        -
        - - -

        텍스쳐

        -
        -

        사진, 그림, 영상 및 데이터 텍스쳐를 변경했다면 다음과 같은 플래스 설정이 필요합니다:

        - - texture.needsUpdate = true; - -

        대상이 자동으로 렌더링되도록 업데이트하기.

        - -

        예제

        -

        - [example:webgl_materials_video WebGL / materials / video]
        - [example:webgl_rtt WebGL / rtt] -

        - -
        - - -

        카메라

        -
        -

        카메리 위치와 대상은 자동으로 업데이트됩니다. 만약 변경을 원한다면

        -
          -
        • - fov -
        • -
        • - aspect -
        • -
        • - near -
        • -
        • - far -
        • -
        -

        - 이후에 투영되는 행렬구조를 다시 계산하면 됩니다. -

        - -camera.aspect = window.innerWidth / window.innerHeight; -camera.updateProjectionMatrix(); - -
        - - diff --git a/docs/manual/ko/introduction/How-to-use-post-processing.html b/docs/manual/ko/introduction/How-to-use-post-processing.html deleted file mode 100644 index 6dce9e4b086378..00000000000000 --- a/docs/manual/ko/introduction/How-to-use-post-processing.html +++ /dev/null @@ -1,110 +0,0 @@ - - - - - - - - - -

        후처리 사용 방법(How to use post-processing)

        - -

        - 여러 three.js 애플리케이션들에서는 3차원 오브젝트를 직접 화면에 렌더링하고 있습니다. 하지만 가끔, DOF, 블룸 필름 그레인 등의 - 다양한 그래픽 효과나 다양한 안티 얼레이징 타입을 사용하고 싶을 수도 있습니다. 후처리 방식은 이런 효과를 위해 널리 쓰이는 방법입니다. - 먼저, 비디오 카드의 메모리 버퍼에 해당되는 대상을 렌더링하기 위해 장면이 그려집니다. 그 다음 혹은 화면이 최종적으로 렌더링되기 전에 - 하나 또는 여러 개의 후처리를 통해 필터와 효과를 이미지 버퍼에 적용합니다. -

        -

        - three.js는 완벽한 후처리 솔루션인 [page:EffectComposer]를 통해 작업 환경을 구현합니다. -

        - -

        작업 절차

        - -

        - 먼저 해야 할 일은 examples 디렉토리의 모든 필요한 파일들을 불러오는 것입니다. three.js의 공식 가이드 - [link:https://www.npmjs.com/package/three npm package]를 따르고 있다고 가정합니다. - 기본 데모 활용에는 아래와 같은 파일들이 필요합니다. -

        - - - import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'; - import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'; - import { GlitchPass } from 'three/addons/postprocessing/GlitchPass.js'; - - -

        - 모든 파일들을 잘 불러왓다면, composer를 만들어 [page:WebGLRenderer]인스턴스를 넘겨줍니다. -

        - - - const composer = new EffectComposer( renderer ); - - -

        - composer를 사용할 때, 앱의 애니메이션 루프를 변경해주는 것이 중요합니다. [page:WebGLRenderer]의 render 메서드를 호출하지 말고, - 이제부터는 각각의 [page:EffectComposer]에 대응되는 렌더링 방법을 사용합니다.. -

        - - - function animate() { - - requestAnimationFrame( animate ); - - composer.render(); - - } - - -

        - composer는 이제 준비가 다 되었으니, 후처리 과정 연결을 설정할 수 있습니다. - 이러한 과정은 앱을 만드는 최종 화면 출력을 담당하며, 첨부/삽입한 순서대로 처리합니다. - 이 예제에서 먼저 실행한 것은 *RenderPass* 인스턴스이고 그 다음이 *GlitchPass* 입니다. - 마지막 과정이 끝나면 자동으로 화면에 렌더링됩니다. 패스 설정은 아래와 같습니다. -

        - - - const renderPass = new RenderPass( scene, camera ); - composer.addPass( renderPass ); - - const glitchPass = new GlitchPass(); - composer.addPass( glitchPass ); - - -

        - *RenderPass*는 일반적으로 맨 위에 위치해서 렌더링된 장면을 후처리의 기본 입력 장면으로 활용합니다. 예제의 경우, - *GlitchPass*는 이 이미지 데이터에 거친 글리치 효과를 넣어줍니다. [link:https://threejs.org/examples/webgl_postprocessing_glitch live example]에서 작동을 - 확인해보세요. -

        - -

        기본 내장 후처리방식

        - -

        - 엔진에서 제공하는 여러 내장 후처리 방식을 활용할 수 있습니다. 이 방식들은 - [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm/postprocessing postprocessing] 디렉토리에 있습니다. -

        - -

        커스텀 방식

        - -

        - 본인만의 후처리 셰이더를 작성하고 후처리 절차 안에 넣고 싶을 때가 있습니다. 이 때에는, - *ShaderPass*를 활용합니다. 파일과 커스텀 셰이더를 불러오고 다음 코드를 통해 설정합니다. -

        - - - import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js'; - import { LuminosityShader } from 'three/addons/shaders/LuminosityShader.js'; - - // later in your init routine - - const luminosityPass = new ShaderPass( LuminosityShader ); - composer.addPass( luminosityPass ); - - -

        - [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/shaders/CopyShader.js CopyShader]에서 처음 자신만의 셰이더를 만들기에 적합한 파일을 제공하고 있습니다. - *CopyShader*는 [page:EffectComposer]의 쓰기 리드 버퍼에 아무 효과를 적용하지 않은 채 이미지 컨텐츠를 복사하기만 합니다. -

        - - - \ No newline at end of file diff --git a/docs/manual/ko/introduction/Installation.html b/docs/manual/ko/introduction/Installation.html deleted file mode 100644 index 023627d39e950d..00000000000000 --- a/docs/manual/ko/introduction/Installation.html +++ /dev/null @@ -1,188 +0,0 @@ - - - - - - - - - - - -

        설치([name])

        - -

        - three.js는 [link:https://www.npmjs.com/ npm]을 포함한 빌드 툴에서 설치가 가능하고, CDN이나 static 호스팅으로 빠르게 사용이 가능합니다. 대부분의 경우 npm을 - 통한 설치가 가장 좋은 선택입니다. -

        - -

        - 어떤 방식을 선택하든, 같은 버전의 라이브러리에서 모든 파일을 동일하게 불러와야 합니다. 여러 소스에서 파일을 혼합해 가져오면 코드 중복이 일어나거나 비정상적으로 앱이 종료될 수 있습니다. -

        - -

        - three.js의 모든 메서드들은 ES modules (see [link:https://eloquentjavascript.net/10_modules.html#h_hF2FmOVxw7 Eloquent - JavaScript: ECMAScript Modules])에 기반하고 있으며, 마지막 프로젝트에 필요한 부분만 불러오도록 할 것입니다. -

        - -

        npm으로 설치하기

        - -

        - [link:https://www.npmjs.com/package/three three] npm 모듈을 설치하려면, 프로젝트 폴더의 터미널을 열고 다음을 실행합니다. -

        - - - npm install three - - -

        - 패키지가 다운로드 및 설치 될 것이며, 다음과 같이 코드에서 불러올 수 있을 것입니다. -

        - - - /////////////////////////////////////////////////////// - // Option 1: Import the entire three.js core library. - import * as THREE from 'three'; - - const scene = new THREE.Scene(); - - - /////////////////////////////////////////////////////// - // Option 2: Import just the parts you need. - import { Scene } from 'three'; - - const scene = new Scene(); - - -

        - npm을 통해 설치할 때, 아마 대부분의 경우 모든 패키지를 한 자바스크립트 파일에 결합시켜주는 - [link:https://eloquentjavascript.net/10_modules.html#h_zWTXAU93DC bundling tool]을 사용할텐데, three.js는 모든 자바스크립트 - 번들러에 호환되지만, 가장 널리 쓰이는 것은 [link:https://webpack.js.org/ webpack]일 것입니다. -

        - -

        - 모든 속성들이 three 모듈에서 바로 불러와지는 것은 아닙니다. ("bare import"라고도 불리는). 다른 자주 쓰이는 라이브러리들, controls, loaders, post-processing effects 같은 것들은 -[link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm examples/jsm] 의 하위폴더에서 불러와야 합니다. 더 자세한 내용을 알아보려면, 아래 내용을 참고하세요. -

        - -

        - npm 모듈 [link:https://eloquentjavascript.net/20_node.html#h_J6hW/SmL/a Eloquent - JavaScript: Installing with npm]에 대해 더 알아보기. -

        - -

        static hosting 및 CDN을 통한 설치

        - -

        - three.js 라이브러리는 빌드 시스템 없이도, 본인의 웹서버나 기존의 CDN에 파일을 올리지 않고도 사용할 수 있습니다. three.js 라이브러리는 ES 모듈에 기반하고있기 때문에, 모든 스크립트는 참조할 때 아래처럼 type="module"을 사용해야합니다. -

        - - -<script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script> - -<script type="importmap"> - { - "imports": { - "three": "https://unpkg.com/three@<version>/build/three.module.js" - } - } -</script> - -<script type="module"> - - import * as THREE from 'three'; - - const scene = new THREE.Scene(); - -</script> - - -

        Addons

        - -

        - three.js는 3D 엔진의 주요 컴포넌트들에 초점을 두고 있습니다. 다른 여러 유용한 컴포넌트들 — - controls, loaders, post-processing effects — 은 - [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm examples/jsm] 폴더에 있습니다. 이들은 "예제"로 불리는데, 그대로 사용할 수도 있고, - 재조합 및 커스터마이징이 가능하기 때문입니다. 이 컴포넌트들은 언제나 코어 라이브러리와 상응하게 이루어져있으며, 이는 다른 비슷한 서드파티 패키지들이 다양한 사람들에 의해 유지보수되고, 최신버전 반영이 안되는 점과는 대비되는 점입니다. -

        - -

        - 예제들은 각각 따로 설치할 필요는 없지만, 각각 import를 해 주어야 합니다. - three.js가 npm을 통해 설치되었다면, [page:OrbitControls] 컴포넌트를 다음과 같이 불러올 수 있습니다. -

        - - - - import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; - - const controls = new OrbitControls( camera, renderer.domElement ); - - -

        - three.js를 CDN으로 설치했다면, 다른 컴포넌트들도 동일한 CDN에서 설치 가능합니다. -

        - - -<script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script> - -<script type="importmap"> - { - "imports": { - "three": "https://unpkg.com/three@<version>/build/three.module.js", - "three/addons/": "https://unpkg.com/three@<version>/examples/jsm/" - } - } -</script> - -<script type="module"> - - import * as THREE from 'three'; - import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; - - const controls = new OrbitControls( camera, renderer.domElement ); - -</script> - - -

        - 모든 파일들의 버전을 동일하게 맞추는것이 무엇보다 중요합니다. 서로 다른 버전에서 import를 하거나, three.js 라이브러리 자체가 아닌 다른 버전의 예제를 사용하지 마세요. -

        - -

        양립성

        - -

        CommonJS 불러오기

        - -

        - 대부분의 자바스크립트 번들러는 이제 ES 모듈을 기본적으로 지원하지만, 오래된 빌드 도구들은 그렇지 않을 수 있습니다. - 이 경우에, 번들러에 ES 모듈을 인식할 수 있도록 설정해줄 수 있습니다. 예를들어 [link:http://browserify.org/ Browserify] 는 [link:https://github.com/babel/babelify babelify] 플러그인을 불러오기만 하면 됩니다. -

        - -

        maps 불러오기

        - -

        - npm을 통해 설치하는 경우, 호스팅 및 CDN으로 설치했을 때와 import 경로가 다릅니다. 이 점은 양쪽의 사용자 모두에게 불편한 문제가 될 것이라는 점을 알고 있습니다. - 빌드 도구나 번들러를 사용하는 개발자들은 상대경로보다 단순한 패키지 구분자(ex: 'three')로 사용하기를 선호하지만 examples/ 폴더의 파일들은 - three.module.js에 상대 경로를 사용하기 때문에 예상을 빗나가게 됩니다. 빌드 도구를 사용하지 않는 사람들 — 빠른 프로토타입 제작, 학습용도, 개인적 취향 — 도 마찬가지로 이러한 상대 경로를 싫어할 텐데, - 특정 폴더 구조가 필요하고, 전역 THREE.*를 사용하는 것보다 엄격하기 때문입니다. -

        - -

        - [link:https://github.com/WICG/import-maps import maps] 이 후에 활발하게 지원된다면 이러한 상대 경로들을 npm 패키지 이름, 'three'처럼 단순한 패키지 식별자로 변경할 예정입니다. - 이는 npm 패키지에서 주로 쓰이는 경로 작성법과 일치하고, 두 사용자군 모두에게 파일을 불러오는 데에 동일한 코드를 사용할 수 있게 해 줄 것입니다. - 빌드 도구를 사용하지 않는 것을 선호하는 사용자들에게도, 간단한 JSON 맵핑을 통해 CDN이나 직접 파일 폴더에서 불러오는 것을 가능하게 해 줄 것입니다. - 실험적 방법으로, [link:https://glitch.com/edit/#!/three-import-map?path=index.html import map - example]처럼 map polyfill과 함께 import 해서 더 간단하게 사용해볼 수도 있습니다. -

        - -

        Node.js

        - -

        - Because three.js is built for the web, it depends on browser and DOM APIs that don't always exist in Node.js. Some of these issues can be resolved by using shims like [link:https://github.com/stackgl/headless-gl headless-gl], or by replacing components like [page:TextureLoader] with custom alternatives. Other DOM APIs may be deeply intertwined with the code that uses them, and will be harder to work around. We welcome simple and maintainable pull requests to improve Node.js support, but recommend opening an issue to discuss your improvements first. -

        - -

        - Make sure to add `{ "type": "module" }` to your `package.json` to enable ES6 modules in your node project. -

        - - - - diff --git a/docs/manual/ko/introduction/Loading-3D-models.html b/docs/manual/ko/introduction/Loading-3D-models.html deleted file mode 100644 index 12a4f05396d25f..00000000000000 --- a/docs/manual/ko/introduction/Loading-3D-models.html +++ /dev/null @@ -1,151 +0,0 @@ - - - - - - - - - - - -

        3D 모델 불러오기([name])

        - -

        - 3D 모델들은 각자 다양한 목적, 적합한 특성, 다양한 복잡성을 가지고 있는 수백개의 파일 포맷으로 존재할 수 있습니다. - - - three.js는 다양한 로더를 지원하지만, 알맞는 포맷과 워크플로우를 선택하는 것이 시간을 아끼고 이후에 생길 문제를 방지할 수 있는 방법입니다. - 몇몇 포맷들은 작업하기 어렵거나, 실시간으로 반영하기 어렵거나, 단순히 지금 시점에 전부 지원을 안하기도 합니다. -

        - -

        - 여기에서는 대부분의 사용자들에게 추천할만 한 워크플로우와, 막혔을때의 해결법을 알려드리고자 합니다. -

        - -

        시작하기에 앞서

        - -

        - 로컬 서버를 돌리는 것이 처음이라면, - [link:#manual/introduction/Installation installation] - 를 먼저 확인하세요. 3D 모델의 대부분의 에러는 파일을 알맞게 호이스팅하는 것으로 해결할 수 있습니다. -

        - -

        추천 워크플로우

        - -

        - 가능하다면, glTF(GL Transmission Format)를 사용하는 것을 추천드립니다. - .GLB.GLTF 버전 포맷 모두 잘 지원될 것입니다. - glTF는 런타임 자원 효율에 초점을 맞추고 있기 때문에 로딩을 빠르고 정확하게 할 수 있습니다. - 속성으로는 meshes, materials, textures, skins, skeletons, morph targets, animations, lights, 그리고 - cameras 가 있습니다. -

        - -

        - 퍼블릭 도메인 glTF 파일은 - - Sketchfab 사이트처럼 사용할 수 있고, 다양한 툴들에서 glTF로 출력해 사용할 수 있습니다. -

        - - - -

        - 선호하는 툴이 glTF를 지원하지 않는다면, glTF로 출력하는 것을 개발자에게 문의해보거나, - the glTF roadmap thread에 포스팅 해 보세요. -

        - -

        - glTF 이외에도, FBX, OBJ, COLLADA 같은 유명한 포맷들도 잘 지원됩니다. -

        - -

        로딩

        - -

        - three.js에는 몇몇 로더들을 제외하고는(ex: [page:ObjectLoader]) 기본적으로 include되어 있지 않습니다. 다른 로더들은 개별적으로 앱에 추가해줘야 합니다. -

        - - - import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; - - -

        - 로더를 import한 후에, scene에 모델을 로드하면 됩니다. 구문은 로더에 따라 다릅니다. — 다른 포맷을 사용할 경우에는, 해당 로더에 대한 예제와 문서를 참고하세요. - glTF의 경우 글로벌 스크립트는 다음과 같이 사용합니다. -

        - - - const loader = new GLTFLoader(); - - loader.load( 'path/to/model.glb', function ( gltf ) { - - scene.add( gltf.scene ); - - }, undefined, function ( error ) { - - console.error( error ); - - } ); - - -

        - [page:GLTFLoader GLTFLoader 문서]를 참고하세요. -

        - -

        문제 해결

        - -

        - 많은 시간을 공 들여 걸작을 모델링하고 나서, 웹 페이지에 - 업로드 했더닝, 이런! 😭 왜곡돼있거나 색이 안 칠해져있거나 아예 나오지 않을 때가 있습니다. - 다음 순서대로 문제 해결을 해 봅시다: -

        - -
          -
        1. - 자바스크립트 콘솔 에러를 체크합니다. - .load()를 사용할 때 로그 결과에 onError 콜백을 사용했는지를 확인합니다. -
        2. -
        3. - 다른 애플리케이션에서 모델을 확인합니다. glTF에서는, 드래그-앤-드롭 뷰어가 - three.js와 - babylon.js에서 사용 가능합니다. 만약 모델이 - 하나 이상의 어플리케이션에서 정상적으로 나타난다면, - three.js와 관련된 버그를 알려주세요. - 어느 애플리케이션에서도 모델이 나타나지 않는다면, 모델을 만든 어플리케이션의 버그를 확인해 보는 것을 강력하게 추천드립니다. -
        4. -
        5. - 모델을 1000배 확대 혹은 축소해 보세요. 여러 모델들은 다양하게 확대 및 축소되어 보여질 수 있으며 - 너무 큰 모델은 카메라가 모델 안에 있어 제대로 안 보일 수 있습니다. -
        6. -
        7. - 밝은 모델을 사용하거나 위치를 바꿔보세요. 모델이 가려져 있을 수 있습니다. -
        8. -
        9. - 네트워크 탭에서 failed texture requests를 확인합니다. 예를 들면, - C:\\Path\To\Model\texture.jpg. 이러한 주소 대신 상대 주소를 사용해, images/texture.jpg처럼 사용해 보세요 - — 이 작업은 텍스트 에디터에서 모델 파일을 수정해야 할 수도 있습니다. -
        10. -
        - -

        지원 요청

        - -

        - 위의 문제 해결 절차를 거치고도 모델이 제대로 나오지 않는다면, 적절한 질문을 통해 도움을 구하는 것이 가장 빠른 해결책일 것입니다. - three.js 포럼에 문제제기를 하고 가능하다면, - 아무 포맷으로 자신의 모델을 첨부(혹은 같은 문제가 있는 더 단순한 모델)을 첨부해 주세요. - 다른 사람에게 충분한 정보를 제공해 주는 것이 문제를 해결할 수 있는 빠를 방법입니다. — live demo를 사용하면 이상적입니다. -

        - - - - \ No newline at end of file diff --git a/docs/manual/ko/introduction/Matrix-transformations.html b/docs/manual/ko/introduction/Matrix-transformations.html deleted file mode 100644 index 4f98be08c9c00d..00000000000000 --- a/docs/manual/ko/introduction/Matrix-transformations.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - - - -

        행렬 변환([name])

        - -

        - Three.js는 *matrices*를 사용해 3D 변형---변환(위치), 회전, 확대 인코딩합니다. - [page:Object3D]의 모든 인스턴스는 [page:Object3D.matrix matrix]를 가지고 있어 오브젝트의 위치, 회전, 확대 정보를 담고 있습니다. - 이 페이지에서는 오브젝트의 변형과 관련해 설명합니다. -

        - -

        편리한 속성 및 *matrixAutoUpdate*

        - -

        - 오브젝트의 변형 방법에는 두 가지가 있습니다. -

        -
          -
        1. - 오브젝트의 *position*, *quaternion*, 그리고 *scale* 속성을 조절합니다, 그 후에 three.js가 오브젝트의 행렬을 이에 맞추어 재 계산합니다: - -object.position.copy( start_position ); -object.quaternion.copy( quaternion ); - - 기본적으로 *matrixAutoUpdate* 속성은 true로 되어 있으며, 행렬정보는 자동으로 재 계산 될 것입니다. - 오브젝트를 고정하고 싶거나 직접 재계산을 하고 싶다면, false로 해서 성능을 향상시킬 수 있습니다: - -object.matrixAutoUpdate = false; - - 속성을 변경한 다음에 수동으로 행렬을 업데이트해줍니다.: - -object.updateMatrix(); - -
        2. -
        3. - 오브젝트의 행렬을 직접 수정합니다. [page:Matrix4] 클래스에는 행렬 수정을 위한 여러 메서드가 있습니다. - -object.matrix.setRotationFromQuaternion( quaternion ); -object.matrix.setPosition( start_position ); -object.matrixAutoUpdate = false; - - 이 경우에 *matrixAutoUpdate*는 무조건 *false*가 되어야 하는 점을 명심하세요. 그리고 *updateMatrix* 를 사용하지 마세요. *updateMatrix*를 호출하면 수동으로 변경한 행렬을 덮어버리고*position*, *scale* 등의 행렬을 재계산 할 것입니다. -
        4. -
        - -

        오브젝트와 world 행렬

        -

        - 오브젝트의 [page:Object3D.matrix matrix]는 오브젝트의 변형을 관련된 오브젝트의 [page:Object3D.parent parent]에 저장합니다; 오브젝트의 변형 정보를 - world 좌표에서 가져오려면, 오브젝트의 [page:Object3D.matrixWorld]에 접근해야 합니다. -

        -

        - 부모 혹은 자식 오브젝트의 변형이 생기면, 자식 오브젝트의 [page:Object3D.matrixWorld matrixWorld]를 [page:Object3D.updateMatrixWorld updateMatrixWorld]()를 호출해 업데이트 합니다. -

        - -

        회전 및 사원수(Rotation and Quaternion)

        -

        - Three.js는 3D 회전을 두 가지 방법으로 나타냅니다: [page:Euler Euler angles] 와 [page:Quaternion Quaternions]이며 둘 사이의 변한 메서드도 포함입니다. - Euler angles는 "gimbal lock" 이라는 문제와 관련이 있어서, 특정 설정은 자유도가 제한됩니다(한 축에서만 오브젝트가 회전하는 것을 방지). - 이 때문에, 오브젝트 회전은 언제나 오브젝트의 [page:Object3D.quaternion quaternion]에 저장됩니다. -

        -

        - 이전 버전의 라이브러리는 *useQuaternion* 속성을 불러와서 false로 설정하면 [page:Object3D.matrix matrix]를 Euler angle로 계산하게 합니다. - 이 예제는 삭제되었습니다. 대신 [page:Object3D.setRotationFromEuler setRotationFromEuler] 메서드를 사용해 사원수를 업데이트 합니다. -

        - - - \ No newline at end of file diff --git a/docs/manual/ko/introduction/Useful-links.html b/docs/manual/ko/introduction/Useful-links.html deleted file mode 100644 index 7396971def8781..00000000000000 --- a/docs/manual/ko/introduction/Useful-links.html +++ /dev/null @@ -1,173 +0,0 @@ - - - - - - - - - -

        참고 링크([name])

        - -

        - 다음 링크들은 three.js를 배울때 유용한 링크들 모음입니다.
        - 여기에 추가하고 싶은 링크가 있거나 더 이상 작동하지 않거나 관련되지 않은 링크가 있다면 - 아래 '수정' 버튼을 클릭해 변경해주세요!

        - - three.js는 자주 업데이트 되고 있기 때문에, 많은 링크들이 지난 버전의 내용을 담고 있을 수도 있습니다. - - 만약 링크에서 나온 내용이 제대로 동작하지 않는다면 브라우저 콘솔의 경고나 에러를 확인해 보세요. 관련 문서 페에지도 확인해 보세요. -

        - -

        도움이 되는 포럼

        -

        - Three.js는 공식적으로 [link:https://discourse.threejs.org/ forum]과 [link:http://stackoverflow.com/tags/three.js/info Stack Overflow]에서 지원 요청을 받고 있습니다. - 도움이 필요하다면, 저기로 가면 됩니다. 깃허브에서 도움 요청 이슈를 생성하지 마세요. -

        - -

        예제 및 수업

        - -

        three.js 입문

        -
          -
        • - [link:https://threejs.org/manual/#en/fundamentals Three.js Fundamentals starting lesson] -
        • -
        • - [link:https://codepen.io/rachsmith/post/beginning-with-3d-webgl-pt-1-the-scene Beginning with 3D WebGL] - [link:https://codepen.io/rachsmith/ Rachel Smith]. -
        • -
        • - [link:https://www.august.com.au/blog/animating-scenes-with-webgl-three-js/ Animating scenes with WebGL and three.js] -
        • -
        - -

        심화 확장 기사 및 강의

        -
          -
        • - [link:https://discoverthreejs.com/ Discover three.js] -
        • -
        • - [link:http://blog.cjgammon.com/ Collection of tutorials] by [link:http://www.cjgammon.com/ CJ Gammon]. -
        • -
        • - [link:https://medium.com/soffritti.pierfrancesco/glossy-spheres-in-three-js-bfd2785d4857 Glossy spheres in three.js]. -
        • -
        • - [link:https://www.udacity.com/course/cs291 Interactive 3D Graphics] - a free course on Udacity that teaches the fundamentals of 3D Graphics, - and uses three.js as its coding tool. -
        • -
        • - [Link:https://aerotwist.com/tutorials/ Aerotwist] tutorials by [link:https://github.com/paullewis/ Paul Lewis]. -
        • -
        • - [link:https://discourse.threejs.org/t/three-js-bookshelf/2468 Three.js Bookshelf] - Looking for more resources about three.js or computer graphics in general? - Check out the selection of literature recommended by the community. -
        • -
        - -

        뉴스 및 업데이트

        -
          -
        • - [link:https://twitter.com/hashtag/threejs Three.js on Twitter] -
        • -
        • - [link:http://www.reddit.com/r/threejs/ Three.js on reddit] -
        • -
        • - [link:http://www.reddit.com/r/webgl/ WebGL on reddit] -
        • -
        - -

        예제

        -
          -
        • - [link:https://github.com/edwinwebb/three-seed/ three-seed] - three.js starter project with ES6 and Webpack -
        • -
        • - [link:http://stemkoski.github.io/Three.js/index.html Professor Stemkoskis Examples] - a collection of beginner friendly - examples built using three.js r60. -
        • -
        • - [link:https://threejs.org/examples/ Official three.js examples] - these examples are - maintained as part of the three.js repository, and always use the latest version of three.js. -
        • -
        • - [link:https://raw.githack.com/mrdoob/three.js/dev/examples/ Official three.js dev branch examples] - - Same as the above, except these use the dev branch of three.js, and are used to check that - everything is working as three.js being is developed. -
        • -
        - -

        도구

        -
          -
        • - [link:https://github.com/tbensky/physgl physgl.org] - JavaScript front-end with wrappers to three.js, to bring WebGL - graphics to students learning physics and math. -
        • -
        • - [link:https://whsjs.readme.io/ Whitestorm.js] – Modular three.js framework with AmmoNext physics plugin. -
        • - -
        • - [link:http://zz85.github.io/zz85-bookmarklets/threelabs.html Three.js Inspector] -
        • -
        • - [link:http://idflood.github.io/ThreeNodes.js/ ThreeNodes.js]. -
        • -
        • - [link:https://marketplace.visualstudio.com/items?itemName=slevesque.shader vscode shader] - Syntax highlighter for shader language. -
          - [link:https://marketplace.visualstudio.com/items?itemName=bierner.comment-tagged-templates vscode comment-tagged-templates] - Syntax highlighting for tagged template strings using comments to shader language, like: glsl.js. -
        • -
        • - [link:https://github.com/MozillaReality/WebXR-emulator-extension WebXR-emulator-extension] -
        • -
        - -

        WebGL 참고

        -
          -
        • - [link:https://www.khronos.org/files/webgl/webgl-reference-card-1_0.pdf webgl-reference-card.pdf] - Reference of all WebGL and GLSL keywords, terminology, syntax and definitions. -
        • -
        - -

        이전 링크들

        -

        - 이 링크들은 기록 목적으로 남겨두었습니다. - 아직 도움이 되는 링크도 있겠지만, 해당 내용들은 아주 옛날 버전의 three.js를 사용하고 있다는 점을 주의하세요. -

        - -
          -
        • - [link:https://www.youtube.com/watch?v=Dir4KO9RdhM AlterQualia at WebGL Camp 3] -
        • -
        • - [link:http://yomotsu.github.io/threejs-examples/ Yomotsus Examples] - a collection of examples using three.js r45. -
        • -
        • - [link:http://fhtr.org/BasicsOfThreeJS/#1 Introduction to Three.js] by [link:http://github.com/kig/ Ilmari Heikkinen] (slideshow). -
        • -
        • - [link:http://www.slideshare.net/yomotsu/webgl-and-threejs WebGL and Three.js] by [link:http://github.com/yomotsu Akihiro Oyamada] (slideshow). -
        • -
        • - [link:https://www.youtube.com/watch?v=VdQnOaolrPA Trigger Rally] by [link:https://github.com/jareiko jareiko] (video). -
        • -
        • - [link:http://blackjk3.github.io/threefab/ ThreeFab] - scene editor, maintained up until around three.js r50. -
        • -
        • - [link:http://bkcore.com/blog/3d/webgl-three-js-workflow-tips.html Max to Three.js workflow tips and tricks] by [link:https://github.com/BKcore BKcore] -
        • -
        • - [link:http://12devsofxmas.co.uk/2012/01/webgl-and-three-js/ A whirlwind look at Three.js] - by [link:http://github.com/nrocy Paul King] -
        • -
        • - [link:http://bkcore.com/blog/3d/webgl-three-js-animated-selective-glow.html Animated selective glow in Three.js] - by [link:https://github.com/BKcore BKcore] -
        • -
        • - [link:http://www.natural-science.or.jp/article/20120220155529.php Building A Physics Simulation Environment] - three.js tutorial in Japanese -
        • -
        - - - diff --git a/docs/manual/ko/introduction/WebGL-compatibility-check.html b/docs/manual/ko/introduction/WebGL-compatibility-check.html deleted file mode 100644 index 1743d155994932..00000000000000 --- a/docs/manual/ko/introduction/WebGL-compatibility-check.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - -

        WebGL 호환성 검사([name])

        -

        - 아마 거의 문제가 되지 않을테지만, 몇몇 디바이스나 브라우저는 아직 WebGL을 지원하지 않습니다. - 아래 메서드는 지원 여부를 체크해 가능한지 아닌지 메세지를 띄워줄 것입니다. - WebGL 지원 감지 모듈을 가져오고 렌더링을 시도하기 전에 다음을 실행하십시오. -

        - - - import WebGL from 'three/addons/capabilities/WebGL.js'; - - if ( WebGL.isWebGLAvailable() ) { - - // Initiate function or other initializations here - animate(); - - } else { - - const warning = WebGL.getWebGLErrorMessage(); - document.getElementById( 'container' ).appendChild( warning ); - - } - - - \ No newline at end of file diff --git a/docs/manual/pt-br/introduction/Animation-system.html b/docs/manual/pt-br/introduction/Animation-system.html deleted file mode 100644 index e837a4c38ad617..00000000000000 --- a/docs/manual/pt-br/introduction/Animation-system.html +++ /dev/null @@ -1,149 +0,0 @@ - - - - - - - - - -

        Sistema de animação

        - -

        Visão geral

        - -

        - - No sistema de animação three.js, você pode animar várias propriedades de seus modelos: - os ossos (bones) de um [page:SkinnedMesh skinned e rigged model], morph targets, - diferentes propriedades de material - (cores, opacidade, booleanos), visibilidade e transformações. As propriedades animadas podem ser utilizadas com fade in, - fade out, crossfaded e warped. As escalas de peso e tempo de diferentes - animações no mesmo objeto, bem como em objetos diferentes, podem ser alteradas - independentemente. Várias animações no mesmo objeto e em objetos diferentes podem ser - sincronizadas.

        - - Para conseguir tudo isso em um sistema homogêneo, o sistema de animação three.js - [link:https://github.com/mrdoob/three.js/issues/6881 mudou completamente em 2015] - (cuidado com informações desatualizadas!), e agora tem uma arquitetura semelhante à - Unity/Unreal Engine 4. Esta página fornece uma breve visão geral dos principais componentes do - sistema e como eles trabalham juntos. - -

        - -

        Clipes de animação (Animation Clips)

        - -

        - - Se você importou com sucesso um objeto 3D animado (não importa se - bones ou morph targets ou ambos) — por exemplo, exportando-o do Blender com o - [link:https://github.com/KhronosGroup/glTF-Blender-IO glTF Blender] e - carregando-o em uma cena three.js usando [page:GLTFLoader] — um dos campos da resposta - deve ser um array chamado "animations", contendo o [page:AnimationClip AnimationClips] - para este modelo (veja uma lista de carregadores possíveis abaixo).

        - - Cada `AnimationClip` geralmente contém os dados de uma determinada atividade do objeto. Se o - mesh é um personagem, por exemplo, pode haver um AnimationClip para caminhar, um segundo - para salto, um terceiro para contornar e assim por diante. - -

        - -

        Keyframe Tracks

        - -

        - - Dentro de tal 'AnimationClip' os dados para cada propriedade animada são armazenados em um - [page:KeyframeTrack] separado. Supondo que um objeto de personagem tenha um [page:Skeleton esqueleto] (skeleton), - uma keyframe track pode armazenar os dados para as mudanças de posição do osso do antebraço - ao longo do tempo, uma faixa diferente dos dados para as mudanças de rotação do mesmo osso, uma terceira - a posição da pista, rotação ou dimensionamento de outro osso, e assim por diante. Deve ficar claro, - que um AnimationClip pode ser composto de muitas dessas tracks.

        - - Supondo que o modelo tenha morph targets (por exemplo, um - morph target mostrando um rosto amigável e outro mostrando um rosto irritado), cada track contém as - informações sobre como a [page:Mesh.morphTargetInfluences influence] de um certo morph target - muda durante a execução do clipe. - -

        - -

        Animation Mixer

        - -

        - - Os dados armazenados formam apenas a base para as animações - a reprodução real é controlada pelo - [page:AnimationMixer]. Você pode imaginar isso não apenas como um player de animações, mas - como uma simulação de um hardware como um mixer real, que pode controlar várias animações - simultaneamente, misturando-os e fundindo-os. - -

        - -

        Ações de animação (Animation Actions)

        - -

        - - O próprio `AnimationMixer` tem muito poucas propriedades e métodos (gerais), porque - pode ser controlado por [page:AnimationAction AnimationActions]. Ao configurar um - `AnimationAction` você pode determinar quando um certo `AnimationClip` deve ser reproduzido, pausado - ou parado em um dos mixers, se e com que frequência o clipe deve ser repetido, seja - executado com um fade ou uma escala de tempo, e algumas coisas adicionais, como crossfading - ou sincronização. - -

        - -

        Animação de Grupos de Objetos

        - -

        - - Se você quiser que um grupo de objetos receba um estado de animação compartilhado, você pode usar um - [page:AnimationObjectGroup]. - -

        - -

        Formatos e Loaders suportados

        - -

        - Observe que nem todos os formatos de modelo incluem animação (notadamente o OBJ não inclui), e que apenas alguns - loaders do three.js suportam sequências [page:AnimationClip AnimationClip]. Vários que tem - suporte para este tipo de animação: -

        - -
          -
        • [page:ObjectLoader THREE.ObjectLoader]
        • -
        • THREE.BVHLoader
        • -
        • THREE.ColladaLoader
        • -
        • THREE.FBXLoader
        • -
        • [page:GLTFLoader THREE.GLTFLoader]
        • -
        • THREE.MMDLoader
        • -
        - -

        - Observe que o 3ds max e o Maya atualmente não podem exportar várias animações (ou seja, animações que não estão - na mesma linha do tempo) diretamente para um único arquivo. -

        - -

        Exemplo

        - - - let mesh; - - // Create an AnimationMixer, and get the list of AnimationClip instances - const mixer = new THREE.AnimationMixer( mesh ); - const clips = mesh.animations; - - // Update the mixer on each frame - function update () { - mixer.update( deltaSeconds ); - } - - // Play a specific animation - const clip = THREE.AnimationClip.findByName( clips, 'dance' ); - const action = mixer.clipAction( clip ); - action.play(); - - // Play all animations - clips.forEach( function ( clip ) { - mixer.clipAction( clip ).play(); - } ); - - - - diff --git a/docs/manual/pt-br/introduction/Color-management.html b/docs/manual/pt-br/introduction/Color-management.html deleted file mode 100644 index 01cd9526a48cda..00000000000000 --- a/docs/manual/pt-br/introduction/Color-management.html +++ /dev/null @@ -1,334 +0,0 @@ - - - - - - - - - - - - -

        Gerenciamento de cor

        - -

        O que é um espaço de cor?

        - -

        - Cada espaço de cor é uma coleção de várias decisões de design, escolhidas em conjunto para dar suporte a uma - grande variedade de cores, satisfazendo as restrições técnicas relacionadas à precisão e a tecnologia das telas. Ao criar um recurso 3D ou ao montar recursos 3D em uma cena, é - importante saber quais são essas propriedades e como as propriedades de um espaço de cores se relacionam - com outros espaços de cor na cena. -

        - -
        - -
        - Cores sRGB e ponto branco (D65) exibidos no diagrama de referência cromaticidade CIE 1931. - A região colorida representa uma projeção 2D da gama sRGB, que é um volume 3D. - Fonte: Wikipedia -
        -
        - -
          -
        • - - Cores primárias: As cores primárias (por exemplo, vermelho, verde, azul) não são absolutas; elas são - selecionadas a partir do espectro visível com base em restrições de precisão limitada e - capacidades dos dispositivos de exibição disponíveis. As cores são expressas como uma proporção das cores primárias. -
        • -
        • - Ponto branco: a maioria dos espaços de cores é projetada de forma que uma soma igualmente ponderada das - primárias R = G = B parecerão sem cor, ou "acromáticas". A aparência - de valores acromáticos (como branco ou cinza) dependem da percepção humana, que por sua vez depende - fortemente no contexto do observador. Um espaço de cor especifica seu "ponto branco" para equilibrar - essas necessidades. O ponto branco definido pelo espaço de cores sRGB é - [link:https://en.wikipedia.org/wiki/Illuminant_D65 D65]. -
        • -
        • - Transfer functions: depois de escolher a gama de cores e um modelo de cores, ainda precisamos - definir mapeamentos ("transfer functions") de valores numéricos para o espaço de cores. r = 0,5 - representa 50% menos iluminação física do que r = 1.0? Ou 50% menos brilhante, conforme percebido - por um olho humano médio? São coisas diferentes, e essa diferença pode ser representada como - uma função matemática. As transfer functions podem ser lineares ou não lineares, dependendo - dos objetivos do espaço de cores. sRGB define transfer functions não lineares. Aquelas - funções são às vezes aproximadas como funções gamma, mas o termo "gamma" é - ambíguo e deve ser evitado neste contexto. -
        • -
        - - Esses três parâmetros — cores primárias, ponto branco e transfer functions — definem um espaço de cores, - cada um escolhido para objetivos particulares. Tendo definido os parâmetros, alguns termos adicionais - são úteis: - -
          -
        • - Modelo de cores: Sintaxe para identificar numericamente as cores dentro da gama de cores escolhida — - um sistema de coordenadas para cores. No three.js estamos preocupados principalmente com o modelo de cor RGB, - tendo três coordenadas r, g, b ∈ [0,1] ("domínio fechado") ou - r, g, b ∈ [0,∞] ("domínio aberto"), cada um representando uma fração de uma cor primária. - Outros modelos de cores (HSL, Lab, LCH) são comumente usados ​​para controle artístico. -
        • -
        • - Gama de cores: uma vez que as cores primárias e um ponto branco tenham sido escolhidos, eles representam - um volume dentro do espectro visível (uma "gama"). Cores fora deste volume ("fora da gama") - não podem ser expressas por valores RGB de domínio fechado [0,1]. No domínio aberto [0,∞], a gama é - tecnicamente infinita. -
        • -
        - -

        - Considere dois espaços de cores muito comuns: [page:SRGBColorSpace] ("sRGB") e - [page:LinearSRGBColorSpace] ("Linear-sRGB"). Ambos usam as mesmas cores primárias e ponto branco, - e, portanto, têm a mesma gama de cores. Ambos usam o modelo de cores RGB. Eles diferem apenas - nas transfer functions — Linear-sRGB é linear em relação à intensidade da luz física. - sRGB usa as transfer functions sRGB não lineares e se assemelha mais à maneira que - o olho humano percebe a luz e a capacidade de resposta de dispositivos de exibição comuns. -

        - -

        - Essa diferença é importante. Cálculos de iluminação e outras operações de renderização devem - geralmente ocorrem em um espaço de cores linear. No entanto, cores lineares são menos eficientes para - armazenar em uma imagem ou framebuffer e não parecem corretas quando vistas por um observador humano. - Como resultado, as texturas de entrada e a imagem final renderizada geralmente usarão o método não linear - do espaço de cores sRGB. -

        - -
        -

        - ℹ️ AVISO: Embora alguns monitores modernos sejam compatíveis com gamas mais amplas, como Display-P3, - as APIs gráficas da plataforma web dependem em grande parte do sRGB. Aplicativos que usam three.js - hoje normalmente usarão apenas os espaços de cores sRGB e Linear-sRGB. -

        -
        - -

        Atribuições dos espaços de cores

        - -

        - Fluxos de trabalho lineares - necessários para métodos modernos de renderização - geralmente envolvem mais de - um espaço de cores, cada um atribuído a uma função específica. Espaços de cores lineares e não lineares são - apropriados para diferentes funções, como explicado abaixo. -

        - -

        Input do espaço de cores

        - -

        - Cores fornecidas ao three.js — de seletores de cores, texturas, modelos 3D e outras fontes — - cada um tem um espaço de cor associado. Aqueles que ainda não estão na cor de trabalho Linear-sRGB, - devem ser convertidos e as texturas devem receber a atribuição texture.colorSpace correta. - Certas conversões (para cores hexadecimais e CSS em sRGB) podem ser feitas automaticamente se - o modo de gerenciamento de cores herdado é desabilitado antes de inicializar as cores: -

        - - -THREE.ColorManagement.enabled = true; - - -
          -
        • - Materiais, luzes e shaders: cores nos materiais, luzes e shaders armazenam - componentes RGB no espaço de cores de trabalho Linear-sRGB. -
        • -
        • - Cores de vértices: [page:BufferAttribute BufferAttributes] armazena componentes RGB no - Espaço de cores de trabalho linear-sRGB. -
        • -
        • - Texturas de cores: PNG ou JPEG [page:Texture Textures] contendo informações de cores - (como .map ou .emissiveMap) usam o espaço de cores sRGB de domínio fechado e devem ser anotados com - texture.colorSpace = SRGBColorSpace. Formatos como OpenEXR (às vezes usado para .envMap ou - .lightMap) usam o espaço de cores Linear-sRGB indicado com texture.colorSpace = LinearSRGBColorSpace, - e podem conter valores no domínio aberto [0,∞]. -
        • -
        • - Texturas não coloridas: Texturas que não armazenam informações de cores (como .normalMap - ou .roughnessMap) não têm um espaço de cores associado e geralmente usam a textura (padrão) - como texture.colorSpace = NoColorSpace. Em casos raros, dados sem cor - podem ser representados com outras codificações não lineares por motivos técnicos. -
        • -
        - -
        -

        - ⚠️ AVISO: Muitos formatos para modelos 3D não funcionam de forma correta ou consistente - na definição das informações do espaço de cores. Enquanto o three.js tenta lidar com a maioria dos casos, problemas - são comuns com formatos de arquivo mais antigos. Para melhores resultados, use glTF 2.0 ([page:GLTFLoader]) - e teste modelos 3D em visualizadores on-line antecipadamente para confirmar que o recurso em si está correto. -

        -
        - -

        Espaço de cores de trabalho

        - -

        - Renderização, interpolação e muitas outras operações devem ser executadas em um domínio aberto - do espaço de cores de trabalho linear, no qual os componentes RGB são proporcionais a iluminação - física. No three.js, o espaço de cores de trabalho é Linear-sRGB. -

        - -

        Output do espaço de cores

        - -

        - A saída para um dispositivo de exibição, imagem ou vídeo pode envolver a conversão do domínio aberto - do espaço de cores de trabalho linear-sRGB para outro espaço de cores. Essa conversão pode ser feita em - uma passagem de renderização principal ([page:WebGLRenderer.outputColorSpace]), ou durante o pós-processamento. -

        - - -renderer.outputColorSpace = THREE.SRGBColorSpace; // optional with post-processing - - -
          -
        • - Tela: as cores gravadas em um canvas WebGL para exibição devem estar no espaço sRGB - colorido. -
        • -
        • - Imagem: as cores gravadas em uma imagem devem usar o espaço de cores apropriado para - o formato e o uso. Imagens totalmente renderizadas gravadas em texturas PNG ou JPEG geralmente - usam o espaço de cores sRGB. Imagens contendo emissão, mapas de luz ou outros dados não - confinados ao intervalo [0,1] geralmente usarão o espaço de cores Linear-sRGB de domínio aberto, - e um formato de imagem compatível como OpenEXR. -
        • -
        - -
        -

        - ⚠️ AVISO: - Os render targets podem usar sRGB ou Linear-sRGB. sRGB faz - melhor uso de precisão limitada. No domínio fechado, 8 bits geralmente são suficientes para sRGB - enquanto que ≥12 bits (meio flutuante) podem ser necessários para Linear-sRGB. Se mais tarde - os estágios pipeline precisarem de entrada Linear-sRGB, as conversões adicionais podem ter um pequeno - custo de desempenho. -

        -
        - -

        - Custom materials based on [page:ShaderMaterial] and [page:RawShaderMaterial] have to implement their own output color space conversion. - For instances of `ShaderMaterial`, adding the `encodings_fragment` shader chunk to the fragment shader's `main()` function should be sufficient. -

        - -

        Trabalhando com instâncias THREE.Color

        - -

        - Métodos de leitura ou modificação de instâncias [page:Color] assumem que os dados já estão no - espaço de cores de trabalho three.js, Linear-sRGB. Os componentes RGB e HSL são - representações diretas de dados armazenados pela instância Color e nunca são convertidos - implicitamente. Os dados de cores podem ser convertidos explicitamente com .convertLinearToSRGB() - ou .convertSRGBToLinear(). -

        - - - // RGB components (no change). - color.r = color.g = color.b = 0.5; - console.log( color.r ); // → 0.5 - - // Manual conversion. - color.r = 0.5; - color.convertSRGBToLinear(); - console.log( color.r ); // → 0.214041140 - - -

        - Com ColorManagement.enabled = true definido (recomendado), determinadas conversões - são feitas automaticamente. Como as cores hexadecimais e CSS geralmente são sRGB, métodos [page:Color] - irão converter automaticamente essas entradas de sRGB para Linear-sRGB em setters, ou - converter de Linear-sRGB para sRGB ao retornar hexadecimal ou CSS de getters. -

        - - - // Hexadecimal conversion. - color.setHex( 0x808080 ); - console.log( color.r ); // → 0.214041140 - console.log( color.getHex() ); // → 0x808080 - - // CSS conversion. - color.setStyle( 'rgb( 0.5, 0.5, 0.5 )' ); - console.log( color.r ); // → 0.214041140 - - // Override conversion with 'colorSpace' argument. - color.setHex( 0x808080, LinearSRGBColorSpace ); - console.log( color.r ); // → 0.5 - console.log( color.getHex( LinearSRGBColorSpace ) ); // → 0x808080 - console.log( color.getHex( SRGBColorSpace ) ); // → 0xBCBCBC - - -

        Erros comuns

        - -

        - Quando uma cor ou textura individual é configurada incorretamente, ela aparecerá mais escura ou mais clara do que - esperado. Quando o espaço de cores de saída do renderizador está mal configurado, a cena inteira pode aparecer - mais escura (por exemplo, conversão ausente para sRGB) ou mais clara (por exemplo, uma conversão dupla para sRGB com - pós-processamento). Em cada caso, o problema pode não ser uniforme e simplesmente aumentar/diminuir - a iluminação não resolve. -

        - -

        - Um problema mais sutil aparece quando ambos os espaços de cores de entrada e saída - estão incorretos — os níveis gerais de brilho podem ser bons, mas as cores podem mudar - inesperadamente sob iluminação diferente, ou o sombreamento pode parecer mais estourado e menos suave - do que o pretendido. Esses dois erros não fazem um acerto, e é importante que o trabalho - espaço de cores funcional seja linear ("cena referida") e o espaço de cores de saída seja não linear - ("exibição referida"). -

        - -

        Leitura adicional

        - - - - - - diff --git a/docs/manual/pt-br/introduction/Creating-a-scene.html b/docs/manual/pt-br/introduction/Creating-a-scene.html deleted file mode 100644 index 9e63cc14ff64a2..00000000000000 --- a/docs/manual/pt-br/introduction/Creating-a-scene.html +++ /dev/null @@ -1,165 +0,0 @@ - - - - - - - - - -

        Criando uma cena

        - -

        O objetivo dessa seção é dar uma breve introdução ao three.js. Nós iremos começar configurando uma cena (scene) com um cubo giratório. Um exemplo é apresentado no final dessa página, caso você precise de ajuda.

        - -

        Antes de começar

        - -

        Antes de começar usar o three.js, você precisa de algum lugar para mostrá-lo. Salve o HTML abaixo em um arquivo no seu computador e abra o arquivo no navegador.

        - - - <!DOCTYPE html> - <html> - <head> - <meta charset="utf-8"> - <title>My first three.js app</title> - <style> - body { margin: 0; } - </style> - </head> - <body> - <script type="module"> - import * as THREE from 'https://unpkg.com/three/build/three.module.js'; - - // Our Javascript will go here. - </script> - </body> - </html> - - -

        Isso é tudo. Todo o código abaixo vai dentro da tag <script> vazia.

        - -

        Criando a cena

        - -

        Para realmente ser capaz de exibir algum conteúdo com o three.js, nós precisamos de três coisas: cena (scene), câmera (camera) e renderizador (renderer), para que possamos então renderizar a cena com a câmera. -

        - - - const scene = new THREE.Scene(); - const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); - - const renderer = new THREE.WebGLRenderer(); - renderer.setSize( window.innerWidth, window.innerHeight ); - document.body.appendChild( renderer.domElement ); - - -

        Vamos tirar um momento para explicar o que está acontecendo aqui. Nós temos agora configurados a cena, nossa câmera e o renderizador.

        - -

        Existem alguns diferentes tipos de câmera no three.js. Por enquanto usaremos a `PerspectiveCamera`.

        - -

        O primeiro atributo é o `field of view`. FOV é a extensão da cena que é vista na tela em um dado momento. O valor está em graus.

        - -

        O segundo atributo é o `aspect ratio`. Você quase sempre irá usar o comprimento do elemento dividido pela sua altura, ou você terá o mesmo resultado de quando reproduz filmes antigos em uma TV widescreen - a imagem parece esmagada.

        - -

        Os próximos dois atributos são os planos de corte `near` e `far`. Isso significa que os objetos mais distantes da câmera do que o valor `far` ou mais próximos que o valor `near` não serão renderizados. Você não precisa se preocupar com isso agora, mas pode ser necessário usar outros valores em seus apps para obter uma melhor performance.

        - -

        Em seguida temos o renderizador. É aqui que a mágica acontece. Além da criação da intância do renderizador, nós também precisamos configurar o tamanho em que queremos renderizar nossa aplicação. É uma boa ideia usar o comprimento e a altura da área que queremos preencher com nossa aplicação - no nosso caso, o comprimento e altura da janela do navegador. Para aplicativos de alto desempenho, você pode fornecer valores menores para o `setSize`, como `window.innerWidth/2` e `window.innerHeight/2`, o que fará com que a aplicação seja renderizada no tamanho de um quarto do original.

        - -

        Se você deseja manter o tamanho do seu aplicativo mas renderizá-lo em uma resolução mais baixa, você pode chamar o `setSize` passando false como `updateStyle` (o terceiro argumento). Por exemplo, `setSize(window.innerWidth/2, window.innerHeight/2, false)` irá renderizar sua aplicação na metade da resolução, já que seu elemento <canvas> tem 100% de comprimento e altura.

        - -

        Por último mas não menos importante, nós adicionamos o elemento `renderer` ao nosso HTML. Este é o elemento <canvas> que o renderizador usa para exibir a cena para nós.

        - -

        "Tudo bem, mas onde está aquele cubo que você prometeu?". Vamos adicioná-lo agora.

        - - - const geometry = new THREE.BoxGeometry( 1, 1, 1 ); - const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); - const cube = new THREE.Mesh( geometry, material ); - scene.add( cube ); - - camera.position.z = 5; - - -

        Para criar um cubo, nós precisamos de um `BoxGeometry`. Este é um objeto que contém todos os pontos (`vertices`) e preenchimento (`faces`) do cubo. Nós vamos explorar mais sobre isso no futuro.

        - -

        Além da geometria, nós precisamos de um material para colorir. Three.js vem com vários materiais, mas vamos nos ater ao `MeshBasicMaterial` por enquanto. Todos os materiais têm um objeto de propriedades que serão aplicadas a eles. Para manter as coisas simples, forneceremos apenas um atributo de cor `0x00ff00`, que é verde. Isso funciona da mesma maneira que as cores no CSS ou no Photoshop (`hex colors`).

        - -

        A terceira coisa que precisamos é de um `Mesh`. Um mesh é um objeto que pega a geometria e aplica um material a ela, para que então possamos inseri-lo em nossa cena e move-lo livremente.

        - -

        Por padrão, quando nós chamamos `scene.add()`, o elemento que queremos adicionar será inserido nas coordenadas `(0,0,0)`. Isso faz com que a câmera e o cubo fiquem um dentro do outro. Para evitar isso, simplesmente movemos a câmera um pouco para fora.

        - -

        Renderizando a cena

        - -

        Se você copiou o código acima para o arquivo HTML criado anteriormente, você não será capaz de ver nada. Isso acontece porque ainda não estamos renderizando nada. Para isso, precisamos chamar um `render ou animate loop`.

        - - - function animate() { - requestAnimationFrame( animate ); - renderer.render( scene, camera ); - } - animate(); - - -

        Isso criará um loop que fará com que o renderizador desenhe a cena novamente toda vez que a tela for atualizada (em uma tela típica, isso significa 60 vezes por segundo). Se você é novato em escrever jogos no navegador, pode perguntar "por que não criamos um setInterval?". A questão é - nós poderíamos, mas `requestAnimationFrame` tem várias vantagens. Talvez a mais importante seja que ele pausa quando o usuário navega para outra aba do navegador, portanto, não desperdiçando seu precioso poder de processamento e vida útil da bateria.

        - -

        Animando o cubo

        - -

        Se você inseriu todo o código acima no arquivo que criamos no início, deve visualizar uma caixa verde. Vamos deixar isso tudo um pouco mais interessante rotacionando o cubo. -

        - -

        Adicione o seguinte trecho logo acima da chamada `renderer.render` na função `animate`:

        - - - cube.rotation.x += 0.01; - cube.rotation.y += 0.01; - - -

        Isso será executado a cada quadro (normalmento 60 vezes por segundo), e dará ao cubo uma boa animação de rotação. Basicamente, quaquer coisa que você queira mover ou alterar enquanto a aplicação está sendo executada tem que passar pelo loop de animação. É claro que você pode chamar outras funções de lá para que não acabe com uma função `animate` com centenas de linhas.

        - -

        O resultado

        -

        Parabéns! Agora você concluiu seu primeiro aplicativo three.js. É simples, mas você tem que começar de algum lugar.

        - -

        O código completo está disponível abaixo e como um [link:https://jsfiddle.net/0c1oqf38/ exemplo] editável. Brinque com ele para entender melhor como funciona.

        - - - <!DOCTYPE html> - <html> - <head> - <meta charset="utf-8"> - <title>My first three.js app</title> - <style> - body { margin: 0; } - </style> - </head> - <body> - <script type="module"> - import * as THREE from 'https://unpkg.com/three/build/three.module.js'; - - const scene = new THREE.Scene(); - const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); - - const renderer = new THREE.WebGLRenderer(); - renderer.setSize( window.innerWidth, window.innerHeight ); - document.body.appendChild( renderer.domElement ); - - const geometry = new THREE.BoxGeometry( 1, 1, 1 ); - const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); - const cube = new THREE.Mesh( geometry, material ); - scene.add( cube ); - - camera.position.z = 5; - - function animate() { - requestAnimationFrame( animate ); - - cube.rotation.x += 0.01; - cube.rotation.y += 0.01; - - renderer.render( scene, camera ); - } - - animate(); - </script> - </body> - </html> - - - diff --git a/docs/manual/pt-br/introduction/Creating-text.html b/docs/manual/pt-br/introduction/Creating-text.html deleted file mode 100644 index 406025631b4e4f..00000000000000 --- a/docs/manual/pt-br/introduction/Creating-text.html +++ /dev/null @@ -1,144 +0,0 @@ - - - - - - - - - -

        Criando texto

        -
        -

        - Muitas vezes, você pode precisar usar texto em sua aplicação three.js - aqui estão algumas maneiras de fazer isso. -

        -
        - -

        1. DOM + CSS

        -
        -

        - Usar HTML geralmente é a maneira mais fácil e rápida de adicionar texto. Este é o método - usado para sobreposições descritivas na maioria dos exemplos three.js. -

        -

        Você pode adicionar conteúdo para uma

        - <div id="info">Description</div> - -

        - e usar marcação CSS para posicionar absolutamente em uma posição acima de todas as outras com um - z-index, especialmente se você estiver executando o three.js em tela cheia. -

        - - -#info { - position: absolute; - top: 10px; - width: 100%; - text-align: center; - z-index: 100; - display:block; -} - - -
        - - -

        2. Usar [page:CSS2DRenderer] ou [page:CSS3DRenderer]

        -
        -

        - Use esses renderizadores para desenhar texto de alta qualidade contido em elementos DOM para sua cena three.js. - Isso é semelhante ao item 1. exceto que esses elementos de renderização podem ser integrados - mais firmemente e dinamicamente na cena. -

        -
        - - -

        3. Desenhe texto na tela e use como [page:Texture]

        -
        -

        - Use este método se deseja desenhar texto facilmente em um plano na sua cena three.js. -

        -
        - - -

        4. Crie um modelo em seu aplicativo 3D favorito e exporte para three.js

        -
        -

        Use este método se preferir trabalhar com seus aplicativos 3D e importar os modelos para o three.js.

        -
        - - -

        5. Geometria de Texto Procedural

        -
        -

        - Se você preferir trabalhar puramente em THREE.js ou criar geometrias de texto 3D - procedurais e dinâmicas, você pode criar um mesh cuja geometria é uma instância de THREE.TextGeometry: -

        -

        - new THREE.TextGeometry( text, parameters ); -

        -

        - Para que isso funcione, no entanto, seu TextGeometry precisará de uma instância de THREE.Font - para ser definido em seu parâmetro "fonte". - - Veja a página [page:TextGeometry] para mais informações sobre como isso pode ser feito, descrição de cada - um dos parâmetros aceitos e uma lista das fontes JSON que vêm com a própria distribuição THREE.js. -

        - -

        Exemplos

        - -

        - [example:webgl_geometry_text WebGL / geometry / text]
        - [example:webgl_shadowmap WebGL / shadowmap] -

        - -

        - Se o Typeface estiver desativado ou você quiser usar uma fonte que não está lá, há um tutorial - com um script python para blender que permite exportar texto para o formato JSON do Three.js: - [link:http://www.jaanga.com/2012/03/blender-to-threejs-create-3d-text-with.html] -

        - -
        - - -

        6. Fontes Bitmap

        -
        -

        - BMFonts (fontes Bitmap) permitem agrupar glifos em um único BufferGeometry. A renderização do - BMFont suporta quebra de palavras, espaçamento entre letras, kerning, campos de distância assinados com padrão - derivados, campos de distância com sinal multicanal, fontes com várias texturas e muito mais. - Veja [link:https://github.com/felixmariotto/three-mesh-ui three-mesh-ui] ou [link:https://github.com/Jam3/three-bmfont-text three-bmfont-text]. -

        -

        - As Stock Fonts estão disponíveis em projetos como - [link:https://github.com/etiennepinchon/aframe-fonts A-Frame Fonts], ou você pode criar o seu próprio - de qualquer fonte .TTF, otimizando para incluir apenas os caracteres necessários para um projeto. -

        -

        - Algumas ferramentas úteis: -

        -
          -
        • [link:http://msdf-bmfont.donmccurdy.com/ msdf-bmfont-web] (web)
        • -
        • [link:https://github.com/soimy/msdf-bmfont-xml msdf-bmfont-xml] (linha de comando)
        • -
        • [link:https://github.com/libgdx/libgdx/wiki/Hiero hiero] (desktop)
        • -
        -
        - - -

        7. Troika Text

        -
        -

        - O pacote [link:https://www.npmjs.com/package/troika-three-text troika-three-text] renderiza - texto com suavização de qualidade usando uma técnica semelhante ao BMFonts, mas funciona diretamente com qualquer - arquivo de fonte .TTF ou .WOFF, para que você não precise pré-gerar uma textura de glifo offline. Também adiciona - capacidades incluindo: -

        -
          -
        • Efeitos como traços, sombras e curvatura
        • -
        • A capacidade de aplicar qualquer material three.js, até mesmo um ShaderMaterial personalizado
        • -
        • Suporte para ligaduras de fonte, scripts com letras unidas e layout da direita para a esquerda/bidirecional
        • -
        • Otimização para grandes quantidades de texto dinâmico, realizando a maior parte do trabalho fora da thread principal em um web worker
        • -
        -
        - - - - diff --git a/docs/manual/pt-br/introduction/Drawing-lines.html b/docs/manual/pt-br/introduction/Drawing-lines.html deleted file mode 100644 index 578ad018008aab..00000000000000 --- a/docs/manual/pt-br/introduction/Drawing-lines.html +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - - -

        Desenhando linhas

        -
        -

        - Digamos que você queira desenhar uma linha ou um círculo, não um wireframe [page:Mesh]. - Primeiro precisamos configurar o [page:WebGLRenderer renderizador] (renderer), a [page:Scene cena] (scene) e - a câmera (camera) (veja a página Criando uma cena). -

        - -

        - Aqui está o código que vamos usar: -

        - -const renderer = new THREE.WebGLRenderer(); -renderer.setSize( window.innerWidth, window.innerHeight ); -document.body.appendChild( renderer.domElement ); - -const camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 500 ); -camera.position.set( 0, 0, 100 ); -camera.lookAt( 0, 0, 0 ); - -const scene = new THREE.Scene(); - - -

        - A próxima coisa que vamos fazer é definir um material. - Para linhas nós temos que usar [page:LineBasicMaterial] ou [page:LineDashedMaterial]. -

        - - -//create a blue LineBasicMaterial -const material = new THREE.LineBasicMaterial( { color: 0x0000ff } ); - - -

        - Depois do material, nós vamos precisar de uma geometria com alguns vértices: -

        - - -const points = []; -points.push( new THREE.Vector3( - 10, 0, 0 ) ); -points.push( new THREE.Vector3( 0, 10, 0 ) ); -points.push( new THREE.Vector3( 10, 0, 0 ) ); - -const geometry = new THREE.BufferGeometry().setFromPoints( points ); - - -

        - Note que linhas são desenhadas entre cada par consecutivo de vértices, - mas não entre o primeiro e o último (a linha não é fechada). -

        - -

        - Agora que nós temos os pontos para duas linhas e um material, - podemos juntar tudo e formar uma linha -

        - -const line = new THREE.Line( geometry, material ); - - -

        - Tudo o que falta é adicioná-la na cena e chamar o [page:WebGLRenderer.render renderizador]. -

        - - -scene.add( line ); -renderer.render( scene, camera ); - - -

        - Agora você deve estar vendo uma seta apontando para cima, feita de duas linhas azuis. -

        -
        - - diff --git a/docs/manual/pt-br/introduction/FAQ.html b/docs/manual/pt-br/introduction/FAQ.html deleted file mode 100644 index 7a453f6b9505f7..00000000000000 --- a/docs/manual/pt-br/introduction/FAQ.html +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - - - -

        FAQ

        - -

        Qual formato de modelo 3D é melhor suportado?

        -
        -

        - O formato recomendado para importar e exportar recursos é - o glTF (GL Transmission Format). Isso porque o glTF é focado na entrega de recursos em - tempo de execução, é compacto para transmitir e rápido para carregar. -

        -

        - O three.js também fornece loaders para muitos outros formatos populares como FBX, Collada ou OBJ. - No entanto, primeiro você deve sempre tentar estabelecer um fluxo de trabalho baseado em glTF em seus projetos. - Para obter mais informações, consulte [link:#manual/introduction/Loading-3D-models Carregando modelos 3D]. -

        -
        - -

        Por que existem meta tags de viewport nos exemplos?

        -
        - <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> - -

        - Essas tags controlam o tamanho e a escala da janela de visualização para navegadores móveis - (onde o conteúdo da página pode ser renderizado em tamanho diferente da janela de visualização visível). -

        - -

        [link:https://developer.apple.com/library/content/documentation/AppleApplications/Reference/SafariWebContent/UsingtheViewport/UsingtheViewport.html Safari: Using the Viewport]

        - -

        [link:https://developer.mozilla.org/en-US/docs/Web/HTML/Viewport_meta_tag MDN: Using the viewport meta tag]

        -
        - -

        Como a escala da cena pode ser preservada no redimensionamento?

        -

        - Queremos que todos os objetos, independentemente da sua distância da câmera, apareçam do mesmo tamanho, - mesmo que a janela seja redimensionada. - - A equação chave para resolver isso é esta fórmula para a altura visível dada uma determinada distância da câmera: - - -visible_height = 2 * Math.tan( ( Math.PI / 180 ) * camera.fov / 2 ) * distance_from_camera; - - Se aumentarmos a altura da janela por uma certa porcentagem, o que queremos é que a altura visível em todas as distâncias - aumentem na mesma porcentagem. - - Isso não pode ser feito alterando a posição da câmera. Em vez disso, você tem que mudar - o campo de visão da câmera (FOV). - [link:http://jsfiddle.net/Q4Jpu/ Exemplo]. -

        - -

        Por que parte do meu objeto está invisível?

        -

        - Isso pode acontecer por causa do corte de faces. As faces têm uma orientação que decide qual lado - é qual. E o corte remove a parte traseira em circunstâncias normais. - Para verificar se este é o seu problema, altere o lado do material para THREE.DoubleSide. - - material.side = THREE.DoubleSide -

        - -

        - Por que o three.js às vezes retorna resultados estranhos para entradas inválidas? -

        -

        - Por motivos de desempenho, o three.js não valida entradas na maioria dos casos. - É responsabilidade do seu aplicativo garantir que todas as entradas sejam válidas. -

        - - diff --git a/docs/manual/pt-br/introduction/How-to-create-VR-content.html b/docs/manual/pt-br/introduction/How-to-create-VR-content.html deleted file mode 100644 index 1fe33be277a179..00000000000000 --- a/docs/manual/pt-br/introduction/How-to-create-VR-content.html +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - - - - - -

        Como criar conteúdo de VR

        - -

        - Este guia fornece uma breve visão geral dos componentes básicos de uma aplicação de VR baseada na web - feita com three.js. -

        - -

        Workflow

        - -

        - Primeiro, você deve incluir [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/webxr/VRButton.js VRButton.js] - em seu projeto. -

        - - -import { VRButton } from 'three/addons/webxr/VRButton.js'; - - -

        - *VRButton.createButton()* faz duas coisas importantes: Cria um botão que indica - compatibilidade com VR. Além disso, inicia uma sessão de VR se o usuário ativar o botão. A única coisa que você tem - fazer é adicionar a seguinte linha de código ao seu aplicativo. -

        - - -document.body.appendChild( VRButton.createButton( renderer ) ); - - -

        - Em seguida, você deve informar sua instância do `WebGLRenderer` para habilitar a renderização XR. -

        - - -renderer.xr.enabled = true; - - -

        - Finalmente, você precisa ajustar seu loop de animação, pois não podemos usar nossa conhecida função - *window.requestAnimationFrame()*. Para projetos de VR usamos [page:WebGLRenderer.setAnimationLoop setAnimationLoop]. - O código mínimo fica assim: -

        - - -renderer.setAnimationLoop( function () { - - renderer.render( scene, camera ); - -} ); - - -

        Próximos Passos

        - -

        - Dê uma olhada em um dos exemplos oficiais de WebVR para ver esse workflow em ação

        - - [example:webxr_vr_ballshooter WebXR / VR / ballshooter]
        - [example:webxr_vr_cubes WebXR / VR / cubes]
        - [example:webxr_vr_dragging WebXR / VR / dragging]
        - [example:webxr_vr_paint WebXR / VR / paint]
        - [example:webxr_vr_panorama_depth WebXR / VR / panorama_depth]
        - [example:webxr_vr_panorama WebXR / VR / panorama]
        - [example:webxr_vr_rollercoaster WebXR / VR / rollercoaster]
        - [example:webxr_vr_sandbox WebXR / VR / sandbox]
        - [example:webxr_vr_sculpt WebXR / VR / sculpt]
        - [example:webxr_vr_video WebXR / VR / video] -

        - - - - diff --git a/docs/manual/pt-br/introduction/How-to-dispose-of-objects.html b/docs/manual/pt-br/introduction/How-to-dispose-of-objects.html deleted file mode 100644 index fe76a535104815..00000000000000 --- a/docs/manual/pt-br/introduction/How-to-dispose-of-objects.html +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - - - -

        Como descartar objetos

        - -

        - Um aspecto importante para aumentar o desempenho e evitar vazamentos de memória em sua aplicação é o descarte de entidades da biblioteca não utilizadas. - Sempre que você cria uma instância do tipo *three.js*, você aloca uma certa quantidade de memória. No entanto, o *three.js* cria para objetos específicos, - como geometrias ou materiais, entidades relacionadas ao WebGL como buffers ou programas de shader que são necessários para renderização. É importante - destacar que esses objetos não são liberados automaticamente. Em vez disso, o aplicativo precisa usar uma API especial para liberar esses recursos. - Este guia fornece uma breve visão geral sobre como essa API é utilizada e quais objetos são relevantes nesse contexto. -

        - -

        Geometrias (Geometries)

        - -

        - Uma geometria geralmente representa informações de vértices definidas como uma coleção de atributos. *three.js* cria internamente um objeto do tipo [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLBuffer WebGLBuffer] - para cada atributo. Essas entidades são excluídas apenas se você chamar [page:BufferGeometry.dispose](). Se uma geometria se tornar obsoleta em seu aplicativo, - execute o método para liberar todos os recursos relacionados. -

        - -

        Materiais (Materials)

        - -

        - Um material define como os objetos são renderizados. *three.js* usa as informações de uma definição de material para construir um shader para a renderização. - Os shaders só podem ser excluídos se o respectivo material for descartado. Por motivos de desempenho, o *three.js* tenta reutilizar - shaders, se possível. Portanto, um shader só é excluído se todos os materiais relacionados forem descartados. Você pode indicar o descarte de um material - executando [page:Material.dispose](). -

        - -

        Texturas (Textures)

        - -

        - O descarte de um material não afeta as texturas. Eles são manuseados separadamente, pois uma única textura pode ser usada por vários materiais ao mesmo tempo. - Sempre que você cria uma instância de [page:Texture], o three.js cria internamente uma instância de [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLTexture WebGLTexture]. - Semelhante aos buffers, este objeto só pode ser excluído chamando [page:Texture.dispose](). -

        - -

        - Se você usar uma `ImageBitmap` como fonte de dados da textura, você deve chamar [link:https://developer.mozilla.org/en-US/docs/Web/API/ImageBitmap/close ImageBitmap.close]() no nível da aplicação para descartar todos os recursos relacionados à CPU. - Uma chamada automatizada de `ImageBitmap.close()` em [page:Texture.dispose]() não é possível, pois o bitmap da imagem se torna inutilizável e o mecanismo não tem como saber se o bitmap da imagem é usado em outro lugar. -

        - -

        Render Targets

        - -

        - Objetos do tipo [page:WebGLRenderTarget] não apenas alocam uma instância de [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLTexture WebGLTexture], mas também - [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLFramebuffer WebGLFramebuffer]s e [link:https://developer.mozilla.org/en-US/docs/Web/API WebGLRenderbuffer]s - para realizar render targets personalizados. Esses objetos são desalocados apenas executando [page:WebGLRenderTarget.dispose](). -

        - -

        Diversos

        - -

        - Existem outras classes da pasta de exemplos como controles ou passes de pós-processamento que fornecem métodos `dispose()` para remover listeners de eventos internos - ou renderizar targets. Em geral, é recomendado verificar a API ou a documentação de uma classe e procurar o método `dispose()`. Se existir, você deve usá-lo ao limpar as coisas. -

        - -

        FAQ

        - -

        Por que o *three.js* não pode descartar objetos automaticamente?

        - -

        - Esta pergunta foi feita muitas vezes pela comunidade, por isso é importante esclarecer este assunto. O fato é que o *three.js* não conhece o tempo de vida ou o escopo - das entidades criadas pelo usuário, como geometrias ou materiais. Isso é responsabilidade do aplicativo. Por exemplo, mesmo que um material não seja usado atualmente para renderização, - ele pode ser necessário no próximo quadro. Portanto, se o aplicativo decidir que um determinado objeto pode ser excluído, ele deve notificar a engine chamando o respectivo - método `dispose()`. -

        - -

        A remoção de um mesh da cena também descarta sua geometria e material?

        - -

        - Não, você precisa descartar explicitamente a geometria e o material via *dispose()*. Tenha em mente que geometrias e materiais podem ser compartilhados entre objetos 3D como meshes. -

        - -

        *three.js* fornece informações sobre a quantidade de objetos em cache?

        - -

        - Sim. É possível avaliar [page:WebGLRenderer.info], uma propriedade especial do renderizador (renderer) com uma série de informações estatísticas sobre a memória da placa gráfica - e o processo de renderização. Entre outras coisas, ele informa quantas texturas, geometrias e shaders são armazenados internamente. Se você notar problemas de desempenho - em seu aplicativo, é uma boa ideia depurar essa propriedade para identificar facilmente um vazamento de memória. -

        - -

        O que acontece quando você chama `dispose()` em uma textura, mas a imagem ainda não foi carregada?

        - -

        - Os recursos internos para uma textura são alocados apenas se a imagem estiver totalmente carregada. Se você descartar uma textura antes de carregar a imagem, - nada acontece. Nenhum recurso foi alocado, portanto, também não há necessidade de limpeza. -

        - -

        O que acontece quando eu chamo `dispose()` e uso o respectivo objeto posteriormente?

        - -

        - Os recursos internos excluídos serão criados novamente pela engine. Portanto, nenhum erro em tempo de execução ocorrerá, mas você poderá notar um impacto negativo no desempenho do quadro atual, - especialmente quando shaders precisam ser compilados. -

        - -

        Como devo gerenciar objetos *three.js* em minha aplicação? Quando eu sei como descartar as coisas?

        - -

        - Em geral, não existe uma recomendação definitiva para isso. Depende muito do caso específico de uso quando será mais apropriado chamar `dispose()`. É importante destacar que - nem sempre é necessário descartar objetos o tempo todo. Um bom exemplo disso é um jogo que consiste de vários níveis. Um bom momento para descarte de objetos é quando - ocorre uma mudança de nível. A aplicação pode percorrer a cena antiga e descartar todos os materiais, geometrias e texturas obsoletos. Como mencionado na seção anterior, não - ocorrerá um erro em tempo de execução se você descartar um objeto que ainda está em uso. A pior coisa que pode acontecer é a queda de desempenho para um único quadro. -

        - -

        Exemplos que demonstram o uso de dispose()

        - -

        - [example:webgl_test_memory WebGL / test / memory]
        - [example:webgl_test_memory2 WebGL / test / memory2]
        -

        - - - - diff --git a/docs/manual/pt-br/introduction/How-to-update-things.html b/docs/manual/pt-br/introduction/How-to-update-things.html deleted file mode 100644 index ada7574e838d2a..00000000000000 --- a/docs/manual/pt-br/introduction/How-to-update-things.html +++ /dev/null @@ -1,256 +0,0 @@ - - - - - - - - - -

        Como atualizar as coisas

        -
        -

        Todos os objetos, por padrão, atualizam automaticamente suas matrizes se - forem adicionados à cena (scene) dessa forma

        - -const object = new THREE.Object3D(); -scene.add( object ); - - ou se eles são filhos de outro objeto que foi adicionado à cena: - -const object1 = new THREE.Object3D(); -const object2 = new THREE.Object3D(); - -object1.add( object2 ); -scene.add( object1 ); //object1 and object2 will automatically update their matrices - -
        - -

        - No entanto, se você sabe que o objeto será estático, pode desativar este recurso e atualizar - a matriz de transformação manualmente apenas quando necessário. -

        - - -object.matrixAutoUpdate = false; -object.updateMatrix(); - - -

        BufferGeometry

        -
        -

        - BufferGeometries armazenam informações (tais como posições de vértice, índice de faces, normais, cores, - UVs, e quaisquer outros atributos personalizados) em [page:BufferAttribute buffers] - isto é, - [link:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays arrays tipados]. - Isso os torna geralmente mais rápidos do que os Geometries padrão, ao custo de serem um pouco mais difíceis de - trabalhar. -

        -

        - Com relação à atualização dos BufferGeometries, o mais importante é entender que - você não pode redimensionar buffers (isso é muito custoso, sendo basicamente o equivalente a criar uma nova geometria). - No entanto, você pode atualizar o conteúdo dos buffers. -

        -

        - Isso significa que se você sabe que um atributo do seu BufferGeometry vai crescer, digamos o número de vértices, - você deve pré-alocar um buffer grande o suficiente para conter quaisquer novos vértices que possam ser criados. - É claro, isso também significa que haverá um tamanho máximo para o seu BufferGeometry - - não há como criar um BufferGeometry que possa ser estendido de forma eficiente indefinidamente. -

        -

        - Usaremos o exemplo de uma linha que é estendida em tempo de renderização. Vamos alocar espaço - no buffer para 500 vértices, mas desenhando apenas dois primeiro, usando [page:BufferGeometry.drawRange]. -

        - -const MAX_POINTS = 500; - -// geometry -const geometry = new THREE.BufferGeometry(); - -// attributes -const positions = new Float32Array( MAX_POINTS * 3 ); // 3 vertices per point -geometry.setAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) ); - -// draw range -const drawCount = 2; // draw the first 2 points, only -geometry.setDrawRange( 0, drawCount ); - -// material -const material = new THREE.LineBasicMaterial( { color: 0xff0000 } ); - -// line -const line = new THREE.Line( geometry, material ); -scene.add( line ); - -

        - Em seguida, adicionaremos aleatoriamente pontos à linha usando um padrão como: -

        - -const positionAttribute = line.geometry.getAttribute( 'position' ); - -let x = 0, y = 0, z = 0; - -for ( let i = 0; i < positionAttribute.count; i ++ ) { - - positionAttribute.setXYZ( i, x, y, z ); - - x += ( Math.random() - 0.5 ) * 30; - y += ( Math.random() - 0.5 ) * 30; - z += ( Math.random() - 0.5 ) * 30; - -} - -

        - Se você quiser alterar o número de pontos renderizados após a primeira renderização, faça o seguinte: -

        - -line.geometry.setDrawRange( 0, newValue ); - -

        - Se você deseja alterar os valores dos dados de posição após a primeira renderização, você precisa - definir a propriedade needsUpdate assim: -

        - -positionAttribute.needsUpdate = true; // required after the first render - - -

        - Se você alterar os valores dos dados de posição após a renderização inicial, pode ser necessário recalcular - os limites de volume para que outros recursos da engine, como view frustum culling ou helpers, - funcionem corretamente -

        - -line.geometry.computeBoundingBox(); -line.geometry.computeBoundingSphere(); - - -

        - [link:https://jsfiddle.net/t4m85pLr/1/ O fiddle] mostra uma linha animada que você pode adaptar ao seu caso de uso. -

        - -

        Exemplos

        - -

        - [example:webgl_custom_attributes WebGL / custom / attributes]
        - [example:webgl_buffergeometry_custom_attributes_particles WebGL / buffergeometry / custom / attributes / particles] -

        - -
        - -

        Materiais (Materials)

        -
        -

        - Todos os valores uniformes podem ser alterados livremente (por exemplo, cores, texturas, opacidade, etc), - os valores são enviados para o shader a cada quadro. -

        - -

        - Também os parâmetros relacionados ao GLstate podem mudar a qualquer momento - (depthTest, blending, polygonOffset, etc). -

        - -

        - As seguintes propriedades não podem ser alteradas facilmente em tempo de execução - (uma vez que o material é renderizado pelo menos uma vez): -

        -
          -
        • números e tipos de uniformes
        • -
        • presença ou não de -
            -
          • textura (texture)
          • -
          • fog
          • -
          • cores dos vértices (vertex colors)
          • -
          • morphing
          • -
          • shadow map
          • -
          • alpha test
          • -
          • transparent
          • -
          -
        • -
        - -

        - Mudanças nestas propriedades requerem a construção de um novo programa de shader. Você precisará definir

        - material.needsUpdate = true - -

        - Tenha em mente que isso pode ser bastante lento e induzir lags na taxa de quadros (especialmente no Windows, pois a compilação do shader é mais lenta no DirectX do que no OpenGL). -

        - -

        - Para uma experiência mais suave, você pode emular alterações nessas propriedades - até certo ponto, por ter valores "fictícios" como luzes de intensidade zero, texturas brancas ou fog de densidade zero. -

        - -

        - Você pode alterar livremente o material usado para partes da geometria, - no entanto, você não pode alterar como um objeto é dividido em partes (de acordo com os materiais da face). -

        - -

        Se você precisar ter diferentes configurações de materiais durante o tempo de execução:

        -

        - Se o número de materiais / partes for pequeno, você pode pré-dividir o objeto - de antemão (por exemplo, cabelo / rosto / corpo / roupa superior / calças para um humano, - frente / laterais /topo / vidro / pneu / interior para um carro). -

        - -

        - Se o número for grande (por exemplo, cada rosto pode ser potencialmente diferente), - considere uma solução diferente, como usar atributos / texturas para produzir diferenças - nos rostos. -

        - -

        Exemplos

        -

        - [example:webgl_materials_car WebGL / materials / car]
        - [example:webgl_postprocessing_dof WebGL / webgl_postprocessing / dof] -

        -
        - - -

        Texturas

        -
        -

        - Imagem, canvas, vídeo e dados de textura precisam ter a seguinte - propriedade definida se eles forem alterados: -

        - - texture.needsUpdate = true; - -

        Os alvos de renderização são atualizados automaticamente.

        - -

        Exemplos

        -

        - [example:webgl_materials_video WebGL / materials / video]
        - [example:webgl_rtt WebGL / rtt] -

        - -
        - - -

        Câmeras

        -
        -

        - A posição e o alvo de uma câmera são atualizados automaticamente. Se você precisa mudar -

        -
          -
        • - fov -
        • -
        • - aspect -
        • -
        • - near -
        • -
        • - far -
        • -
        -

        - então você precisará recalcular a matriz de projeção: -

        - -camera.aspect = window.innerWidth / window.innerHeight; -camera.updateProjectionMatrix(); - -
        - - diff --git a/docs/manual/pt-br/introduction/How-to-use-post-processing.html b/docs/manual/pt-br/introduction/How-to-use-post-processing.html deleted file mode 100644 index b91a9c9ab42fd5..00000000000000 --- a/docs/manual/pt-br/introduction/How-to-use-post-processing.html +++ /dev/null @@ -1,111 +0,0 @@ - - - - - - - - - -

        Como usar o pós-processamento

        - -

        - Muitas aplicações three.js renderizam seus objetos 3D diretamente na tela. Às vezes, no entanto, você deseja aplicar um ou mais efeitos gráficos - como Depth-Of-Field, Bloom, Film Grain ou vários tipos de Anti-aliasing. - - O pós-processamento é uma abordagem amplamente utilizada para implementar tais efeitos. Primeiro, a cena é renderizada para um render target que representa - um buffer na memória da placa de vídeo. Na próxima etapa, um ou mais passos de pós-processamento aplicam filtros e efeitos ao buffer de imagem antes que ele seja renderizado para a tela. -

        -

        - three.js fornece uma solução completa de pós-processamento via [page:EffectComposer] para implementar esse workflow. -

        - -

        Workflow

        - -

        - A primeira etapa do processo é importar todos os arquivos necessários do diretório de exemplos. Este guia assume que você está usando o - pacote [link:https://www.npmjs.com/package/three npm] do three.js. Para nossa demonstração básica, precisamos dos seguintes arquivos. -

        - - - import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'; - import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'; - import { GlitchPass } from 'three/addons/postprocessing/GlitchPass.js'; - - -

        - Depois que todos os arquivos forem importados com sucesso, podemos criar nosso composer passando uma instância de [page:WebGLRenderer]. -

        - - - const composer = new EffectComposer( renderer ); - - -

        - Ao usar um composer, é necessário alterar o loop de animação da aplicação. Em vez de chamar o método render de - [page:WebGLRenderer], agora usamos a respectiva contraparte de [page:EffectComposer]. -

        - - - function animate() { - - requestAnimationFrame( animate ); - - composer.render(); - - } - - -

        - Nosso composer já está pronto para que seja possível configurar a cadeia de passos de pós-processamento. Esses passos são responsáveis ​​por criar - a saída visual final do aplicativo. Eles são processados ​​na ordem de sua adição/inserção. Em nosso exemplo, a instância de `RenderPass` - é executada primeiro e depois a instância de `GlitchPass`. A última passagem habilitada na cadeia é renderizada automaticamente na tela. A configuração - dos passos fica assim: -

        - - - const renderPass = new RenderPass( scene, camera ); - composer.addPass( renderPass ); - - const glitchPass = new GlitchPass(); - composer.addPass( glitchPass ); - - -

        - O `RenderPass` é normalmente colocado no início da cadeia para fornecer a cena renderizada como entrada para a próxima etapa de pós-processamento. No nosso caso, - o `GlitchPass` usará esses dados de imagem para aplicar um efeito de glitch selvagem. Confira este [link:https://threejs.org/examples/webgl_postprocessing_glitch exemplo] - para vê-lo em ação. -

        - -

        Passes integrados

        - -

        - Você pode usar uma ampla variedade de passos de pós-processamento predefinidos fornecidos pela engine. Estão localizados no diretório - [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm/postprocessing postprocessing]. -

        - -

        Passos Personalizados

        - -

        - Às vezes, você deseja escrever um shader de pós-processamento personalizado e incluí-lo na cadeia de passos de pós-processamento. Para este cenário, - você pode utilizar o `ShaderPass`. Depois de importar o arquivo e seu shader personalizado, você pode usar o código a seguir para configurar o passo. -

        - - - import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js'; - import { LuminosityShader } from 'three/addons/shaders/LuminosityShader.js'; - - // later in your init routine - - const luminosityPass = new ShaderPass( LuminosityShader ); - composer.addPass( luminosityPass ); - - -

        - O repositório fornece um arquivo chamado [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/shaders/CopyShader.js CopyShader] que é um - bom código inicial para seu próprio shader personalizado. `CopyShader` apenas copia o conteúdo da imagem do buffer de leitura do [page:EffectComposer] - para seu buffer de gravação sem aplicar nenhum efeito. -

        - - - diff --git a/docs/manual/pt-br/introduction/Installation.html b/docs/manual/pt-br/introduction/Installation.html deleted file mode 100644 index f5548c24daa31a..00000000000000 --- a/docs/manual/pt-br/introduction/Installation.html +++ /dev/null @@ -1,146 +0,0 @@ - - - - - - - - - -

        Instalação

        - -

        Você pode instalar o three.js através do [link:https://www.npmjs.com/ npm] e modernas ferramentas de build ou começar rapidamente apenas com hospedagem estática ou um CDN. Para a maioria dos usuários, instalar pelo npm é a melhor opção.

        - -

        Seja qual for a maneira que escolher, seja consistente e importe todos os arquivos na mesma versão da biblioteca. A mistura de arquivos de versões diferentes pode causar a inclusão de código duplicado ou até mesmo quebrar a aplicação de maneiras inesperadas.

        - -

        Todos os métodos de instalação do three.js dependem dos ES modules (veja [link:https://eloquentjavascript.net/10_modules.html#h_hF2FmOVxw7 Eloquent JavaScript: ECMAScript Modules]), que permitem incluir somente as partes necessárias da biblioteca no projeto final.

        - -

        Instalar pelo npm

        - -

        - Para instalar o módulo do npm do [link:https://www.npmjs.com/package/three three], abra uma janela do terminal na sua pasta do projeto e execute: -

        - - - npm install three - - -

        - O pacote será baixado e instalado. Então você estará pronto para importar no seu código: -

        - - - // Option 1: Import the entire three.js core library. - import * as THREE from 'three'; - - const scene = new THREE.Scene(); - - - // Option 2: Import just the parts you need. - import { Scene } from 'three'; - - const scene = new Scene(); - - -

        Ao instalar a partir do npm, você quase sempre usará algum tipo de [link:https://eloquentjavascript.net/10_modules.html#h_zWTXAU93DC ferramenta de build] para combinar todos os pacotes que seu projeto precisa em um único arquivo JavaScript. Embora alguns modernos empacotadores JavaScript possam ser usados com o three.js, a escolha mais popular é o [link:https://webpack.js.org/ webpack].

        - -

        Nem todos os recursos são acessados diretamente pelo módulo three. Outras partes populares da biblioteca - tais como controls, loaders e post-processing effects - devem ser importados da subpasta [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm examples/jsm]. Para aprender mais, veja o Exemplo abaixo.

        - -

        Aprenda mais sobre módulos do npm em [link:https://eloquentjavascript.net/20_node.html#h_J6hW/SmL/a Eloquent JavaScript: Installing with npm].

        - -

        Instalar através de CDN ou hospedagem estática

        - -

        A biblioteca three.js pode ser utilizada sem nenhum sistema de build, seja fazendo o upload dos arquivos para seu próprio servidor web ou usando um CDN existente. Como a biblioteca depende dos ES modules, qualquer script que faça referência a eles deve usar type="module" como mostrado abaixo. Também é necessário definir um mapa de importação que resolva a importação direta do `three`.

        - - - <script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script> - - <script type="importmap"> - { - "imports": { - "three": "https://unpkg.com/three@<version>/build/three.module.js" - } - } - </script> - - <script type="module"> - - import * as THREE from 'three'; - - const scene = new THREE.Scene(); - - </script> - - -

        - Nem todos os recursos disponíveis são acessados diretamente através do módulo three. Outras partes populares da biblioteca - tais como controls, loaders e post-processing effects - devem ser importados da subpasta [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm examples/jsm]. Para aprender mais, veja o Exemplo abaixo. -

        - -

        - Como os mapas de importação ainda não são suportados por todos os navegadores, é necessário adicionar o polyfill *es-module-shims.js*. -

        - -

        Addons

        - -

        - O núcleo do three.js está focado nos componentes mais importantes de uma engine 3D. Muitos outros componentes úteis - como controls, loaders e post-processing effects - fazem parte da pasta [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm examples/jsm]. Eles são chamados de "exemplos" porque embora você possa usá-los diretamente, eles também podem ser remixados e personalizados. Esses componentes são sempre mantidos em sincronia com a biblioteca principal, enquanto pacotes semelhantes de terceiros no npm são mantidos por pessoas diferentes e podem estar desatualizados. -

        - -

        - Os addons não precisam ser instalados separadamente, mas precisam ser importados separadamente. Se o three.js foi instalado com npm, você pode carregar o componente [page:OrbitControls] com: -

        - - - import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; - - const controls = new OrbitControls( camera, renderer.domElement ); - - -

        Se o three.js foi instalado de um CDN, use o mesmo CDN para instalar outros componentes:

        - - - <script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script> - - <script type="importmap"> - { - "imports": { - "three": "https://unpkg.com/three@<version>/build/three.module.js", - "three/addons/": "https://unpkg.com/three@<version>/examples/jsm/" - } - } - </script> - - <script type="module"> - - import * as THREE from 'three'; - import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; - - const controls = new OrbitControls( camera, renderer.domElement ); - - </script> - - -

        - É importante que todos os arquivos utilizem a mesma versão. Não importe diferentes addons de diferentes versões, ou use addons de uma versão diferente que a própria biblioteca three.js. -

        - -

        Compatibilidade

        - -

        Importação CommonJS

        - -

        - Embora a maioria dos bundlers JavaScript modernos atualmente suportem os ES modules por padrão, algumas ferramentas de build mais antigas podem não suportar. Nesses casos você provavelmente pode configurar o bundler para entender os ES modules: [link:http://browserify.org/ Browserify] precisa apenas do plugin [link:https://github.com/babel/babelify babelify], por exemplo. -

        - -

        Node.js

        - -

        - Como o three.js foi desenvolvido para a web, ele depende do navegador e das APIs DOM que nem sempre existem no Node.js. Alguns desses problemas podem ser resolvidos usando ferramentas como [link:https://github.com/stackgl/headless-gl headless-gl], ou substituindo componentes como [page:TextureLoader] por alternativas personalizadas. Outras APIs DOM podem ser profundamente entrelaçadas com o código que as utiliza, e serão mais difíceis de contornar. Aceitamos solicitações de pull simples e fáceis de manter para melhorar o suporte ao Node.js, mas recomendo abrir uma issue para discutir suas melhorias primeiro. -

        - -

        - Certifique-se de adicionar `{ "type": "module" }` ao seu `package.json` para habilitar os ES6 modules em seu projeto Node.js. -

        - - - diff --git a/docs/manual/pt-br/introduction/Libraries-and-Plugins.html b/docs/manual/pt-br/introduction/Libraries-and-Plugins.html deleted file mode 100644 index 1b98593dba5007..00000000000000 --- a/docs/manual/pt-br/introduction/Libraries-and-Plugins.html +++ /dev/null @@ -1,109 +0,0 @@ - - - - - - - - - -

        Bibliotecas e Plugins

        - -

        - Aqui estão listadas bibliotecas e plugins para three.js desenvolvidos por terceiros. Esta - lista e os pacotes associados são mantidos pela comunidade e não é garantido que estejam atualizados. - Se você gostaria de atualizar esta lista faça uma PR! -

        - -

        Física

        - -
          -
        • [link:https://github.com/lo-th/Oimo.js/ Oimo.js]
        • -
        • [link:https://enable3d.io/ enable3d]
        • -
        • [link:https://github.com/kripken/ammo.js/ ammo.js]
        • -
        • [link:https://github.com/pmndrs/cannon-es cannon-es]
        • -
        - -

        Pós-processamento

        - -

        - Além do oficial [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm/postprocessing postprocessing effects], - suporte para alguns efeitos e estruturas adicionais estão disponíveis por meio de bibliotecas externas. -

        - -
          -
        • [link:https://github.com/vanruesc/postprocessing postprocessing]
        • -
        - -

        Intersecção e Performance Raycast

        - -
          -
        • [link:https://github.com/gkjohnson/three-mesh-bvh three-mesh-bvh]
        • -
        - -

        Path Tracing

        - -
          -
        • [link:https://github.com/gkjohnson/three-gpu-pathtracer three-gpu-pathtracer]
        • -
        - -

        Formatos de arquivos

        - -

        - Além do oficial [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm/loaders loaders], - suporte para alguns formatos adicionais estão disponíveis por meio de bibliotecas externas. -

        - -
          -
        • [link:https://github.com/gkjohnson/urdf-loaders/tree/master/javascript urdf-loader]
        • -
        • [link:https://github.com/NASA-AMMOS/3DTilesRendererJS 3d-tiles-renderer-js]
        • -
        • [link:https://github.com/kaisalmen/WWOBJLoader WebWorker OBJLoader]
        • -
        • [link:https://github.com/IFCjs/web-ifc-three IFC.js]
        • -
        - -

        Geometria

        - -
          -
        • [link:https://github.com/spite/THREE.MeshLine THREE.MeshLine]
        • -
        - -

        Texto e Layout 3D

        - -
          -
        • [link:https://github.com/protectwise/troika/tree/master/packages/troika-three-text troika-three-text]
        • -
        • [link:https://github.com/felixmariotto/three-mesh-ui three-mesh-ui]
        • -
        - -

        Sistema de partículas

        - -
          -
        • [link:https://github.com/Alchemist0823/three.quarks three.quarks]
        • -
        • [link:https://github.com/creativelifeform/three-nebula three-nebula]
        • -
        - -

        Inverse Kinematics

        - -
          -
        • [link:https://github.com/jsantell/THREE.IK THREE.IK]
        • -
        • [link:https://github.com/lo-th/fullik fullik]
        • -
        • [link:https://github.com/gkjohnson/closed-chain-ik-js closed-chain-ik]
        • -
        - -

        IA para Games

        - -
          -
        • [link:https://mugen87.github.io/yuka/ yuka]
        • -
        • [link:https://github.com/donmccurdy/three-pathfinding three-pathfinding]
        • -
        - -

        Wrappers e Frameworks

        - -
          -
        • [link:https://aframe.io/ A-Frame]
        • -
        • [link:https://github.com/pmndrs/react-three-fiber react-three-fiber]
        • -
        • [link:https://github.com/ecsyjs/ecsy-three ECSY]
        • -
        • [link:https://threlte.xyz/ Threlte]
        • -
        - - - diff --git a/docs/manual/pt-br/introduction/Loading-3D-models.html b/docs/manual/pt-br/introduction/Loading-3D-models.html deleted file mode 100644 index f4351f3b8b486d..00000000000000 --- a/docs/manual/pt-br/introduction/Loading-3D-models.html +++ /dev/null @@ -1,165 +0,0 @@ - - - - - - - - - - - -

        Carregando modelos 3D

        - -

        - Os modelos 3D estão disponíveis em centenas de formatos de arquivo, cada um com diferentes - propósitos, recursos variados e complexidade variável. Embora o - - three.js forneça muitos loaders, escolher o formato e o fluxo de trabalho certos economizará tempo e frustração mais tarde. - Alguns formatos são difíceis de trabalhar, ineficientes para experiências em tempo real - ou simplesmente não são totalmente suportados por enquanto. -

        - -

        - Este guia fornece um fluxo de trabalho recomendado para a maioria dos usuários e sugestões - do que tentar se as coisas não correrem como o esperado. -

        - -

        Antes de começar

        - -

        - Se você é iniciante na execução de um servidor local, comece com - [link:#manual/introduction/Installation installation] - primeiro. Muitos erros comuns de visualização de modelos 3D podem ser evitados hospedando arquivos - corretamente. -

        - -

        Fluxo de trabalho recomendado

        - -

        - Sempre que possível, recomendamos o uso do glTF (GL Transmission Format). Ambas versões do formato, - .GLB e .GLTF, são bem suportadas. - Como o glTF está focado na entrega de recursos em tempo de execução, - ele é compacto para transmitir e rápido para carregar. Seus recursos incluem meshs, materiais, - texturas, skins, skeletons, morph targets, animações, luzes e câmeras. -

        - -

        - Os arquivos glTF de domínio público estão disponíveis em sites como - - Sketchfab, ou várias ferramentas que incluem exportação glTF: -

        - - - -

        - Se suas ferramentas preferidas não suportam glTF, considere solicitar exportação de glTF para os autores, - ou postar no glTF roadmap thread. -

        - -

        - Quando o glTF não é uma opção, formatos populares como FBX, OBJ ou COLLADA - também estão disponíveis e são mantidos regularmente. -

        - -

        Carregando

        - -

        - Apenas alguns poucos loaders (por exemplo [page:ObjectLoader]) são incluídos por padrão com o - three.js — outros devem ser adicionados ao seu aplicativo individualmente. -

        - - - import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; - - -

        - Depois de importar um loader você está pronto para adicionar um modelo à sua cena. A sintaxe varia entre - loaders diferentes — ao usar outro formato, verifique os exemplos e a documentação para esse loader. - Para glTF, o uso com scripts globais ficaria: -

        - - - const loader = new GLTFLoader(); - - loader.load( 'path/to/model.glb', function ( gltf ) { - - scene.add( gltf.scene ); - - }, undefined, function ( error ) { - - console.error( error ); - - } ); - - -

        - Veja a documentação [page:GLTFLoader GLTFLoader] para mais detalhes. -

        - -

        Solução de problemas

        - -

        - Você passou horas modelando uma obra-prima artesanal, você a carrega em - uma página web e — ah, não! 😭 Está distorcido, descolorido ou totalmente ausente. - Comece com estas etapas de solução de problemas: -

        - -
          -
        1. - Verifique se há erros no console JavaScript e certifique-se de ter usado um - callback `onError` ao chamar `.load()` para logar o resultado. -
        2. -
        3. - Visualize o modelo em outro aplicativo. Para glTF, visualizadores drag-and-drop - estão disponíveis para - three.js e - babylon.js. - Se o modelo aparece corretamente em um ou mais aplicativos, - registre um bug do three.js. - Se o modelo não puder ser mostrado em nenhum aplicativo, recomendamos - registrar um bug com o aplicativo usado para criar o modelo. -
        4. -
        5. - Tente dimensionar o modelo para cima ou para baixo por um fator de 1.000. Muitos modelos são - dimensionados de forma diferente, e modelos grandes podem não aparecer se a câmera estiver - dentro do modelo. -
        6. -
        7. - Tente adicionar e posicionar uma fonte de luz. O modelo pode estar escondido no escuro. -
        8. -
        9. - Procure requisições de textura com falha na aba network, como - `"C:\\Path\To\Model\texture.jpg"`. Use caminhos relativos ao seu - modelo em vez disso, tal como `images/texture.jpg` — - isso pode exigir edição do arquivo de modelo em um editor de texto. -
        10. -
        - -

        Pedindo ajuda

        - -

        - Se você passou pelo processo de solução de problemas acima e seu modelo - ainda não está funcionando, uma abordagem correta para pedir ajuda fará com que você - tenha uma solução mais rápida. Poste uma pergunta no - fórum three.js - e, sempre que possível, - inclua seu modelo (ou um modelo mais simples com o mesmo problema) em qualquer formato que - você tiver disponível. Inclua informações suficientes para outra pessoa reproduzir - o problema rapidamente - idealmente, uma demonstração ao vivo. -

        - - - - diff --git a/docs/manual/pt-br/introduction/Matrix-transformations.html b/docs/manual/pt-br/introduction/Matrix-transformations.html deleted file mode 100644 index 4b38d09049d06a..00000000000000 --- a/docs/manual/pt-br/introduction/Matrix-transformations.html +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - - -

        Transformações de matriz

        - -

        - Three.js usa `matrizes` para codificar transformações 3D --- translações (posição), rotações e dimensionamento. - Cada instância de [page:Object3D] tem uma [page:Object3D.matrix matriz] (matrix) que armazena a - posição, rotação e escala. Esta página descreve como atualizar a transformação de um objeto. -

        - -

        Propriedades de conveniência e `matrixAutoUpdate`

        - -

        - Existem duas maneiras de atualizar a transformação de um objeto: -

        -
          -
        1. - Modifique as propriedades `position`, `quaternion` e `scale` do objeto e deixe o three.js recalcular - a matriz do objeto a partir destas propriedades: - -object.position.copy( start_position ); -object.quaternion.copy( quaternion ); - - Por padrão, a propriedade `matrixAutoUpdate` é definida como verdadeira e a matriz será recalculada automaticamente. - Se o objeto é estático, ou você deseja controlar manualmente quando ocorre o recálculo, um melhor desempenho pode ser obtido configurando a propriedade false: - -object.matrixAutoUpdate = false; - - E depois de alterar qualquer propriedade, atualize manualmente a matriz: - -object.updateMatrix(); - -
        2. -
        3. - Modifique a matriz do objeto diretamente. A classe [page:Matrix4] tem vários métodos para modificar a matriz: - -object.matrix.setRotationFromQuaternion( quaternion ); -object.matrix.setPosition( start_position ); -object.matrixAutoUpdate = false; - - Observe que `matrixAutoUpdate` deve ser definido como `false` neste caso, e você deve ter certeza de não chamar `updateMatrix`. Chamar `updateMatrix` irá destruir as alterações manuais feitas na matriz, recalculando a matriz de `position`, `scale` e assim por diante. -
        4. -
        - -

        Objeto e matrizes mundo (world matrices)

        -

        - A [page:Object3D.matrix matriz] (matrix) de um objeto armazena a transformação do objeto relativa ao objeto [page:Object3D.parent pai] (parent); para obter a transformação do objeto em coordenadas mundo, deve-se acessar a [page:Object3D.matrixWorld] do objeto. -

        -

        - Quando a transformação do objeto pai ou filho muda, você pode solicitar que a [page:Object3D.matrixWorld matrixWorld] do objeto filho seja atualizada chamando [page:Object3D.updateMatrixWorld updateMatrixWorld](). -

        - -

        Rotação e Quaternion

        -

        - Three.js fornece duas maneiras de representar rotações 3D: [page:Euler Euler angles] e [page:Quaternion Quaternions], bem como métodos para converter entre os dois. - Os ângulos de Euler estão sujeitos a um problema chamado "gimbal lock", onde certas configurações podem perder um grau de liberdade (impedindo que o objeto seja girado em torno de um eixo). Por esta razão, - as rotações do objeto são sempre armazenadas no objeto [page:Object3D.quaternion quaternion]. -

        -

        - As versões anteriores da biblioteca incluíam uma propriedade `useQuaternion` que, quando definida como false, faria com que a [page:Object3D.matrix matrix] do objeto fosse calculada a partir de um ângulo de Euler. Essa prática está obsoleta --- em vez disso, você deve usar o método [page:Object3D.setRotationFromEuler setRotationFromEuler], que atualizará o quaternion. -

        - - diff --git a/docs/manual/pt-br/introduction/Useful-links.html b/docs/manual/pt-br/introduction/Useful-links.html deleted file mode 100644 index 65ed3faedcf45f..00000000000000 --- a/docs/manual/pt-br/introduction/Useful-links.html +++ /dev/null @@ -1,185 +0,0 @@ - - - - - - - - - -

        Links úteis

        - -

        - Veja a seguir uma coleção de links que podem ser úteis para aprender three.js.
        - Se você encontrar algo que gostaria de adicionar aqui ou achar que um dos links abaixo não está mais - relevante ou funcionando, sinta-se à vontade para clicar no botão 'editar' no canto inferior direito e fazer algumas alterações!

        - - Note também que, como o three.js está em rápido desenvolvimento, muitos desses links conterão informações que estão - desatualizadas - se algo não estiver funcionando como esperado ou como um desses links diz que deveria funcionar, - verifique se há avisos ou erros no console do navegador. Verifique também as páginas de documentação relevantes. -

        - -

        Fóruns de ajuda

        -

        - Three.js utiliza oficialmente o [link:https://discourse.threejs.org/ forum] e o [link:http://stackoverflow.com/tags/three.js/info Stack Overflow] para - pedidos de ajuda. - - Se você precisar de ajuda com algo, esse é o caminho. NÃO abra uma issue no Github para pedidos de ajuda. -

        - -

        Cursos e tutoriais

        - -

        Primeiros passos com o three.js

        -
          -
        • - [link:https://threejs.org/manual/#en/fundamentals Three.js Fundamentals starting lesson] -
        • -
        • - [link:https://codepen.io/rachsmith/post/beginning-with-3d-webgl-pt-1-the-scene Beginning with 3D WebGL] de [link:https://codepen.io/rachsmith/ Rachel Smith]. -
        • -
        • - [link:https://www.august.com.au/blog/animating-scenes-with-webgl-three-js/ Animating scenes with WebGL and three.js] -
        • -
        - -

        Artigos e cursos mais extensos / avançados

        -
          -
        • - [link:https://threejs-journey.com/ Three Journey] Curso do [link:https://bruno-simon.com/ Bruno Simon] - - Ensina os iniciantes a usar o Three.js passo a passo -
        • -
        • - [link:https://discoverthreejs.com/ Discover three.js] -
        • -
        • - [link:http://blog.cjgammon.com/ Collection of tutorials] do [link:http://www.cjgammon.com/ CJ Gammon]. -
        • -
        • - [link:https://medium.com/soffritti.pierfrancesco/glossy-spheres-in-three-js-bfd2785d4857 Glossy spheres in three.js]. -
        • -
        • - [link:https://www.udacity.com/course/cs291 Interactive 3D Graphics] - um curso gratuito da Udacity que ensina os fundamentos de gráficos 3D, - e usa three.js como sua ferramenta de codificação. -
        • -
        • - [Link:https://aerotwist.com/tutorials/ Aerotwist] tutoriais do [link:https://github.com/paullewis/ Paul Lewis]. -
        • -
        • - [link:http://learningthreejs.com/ Learning Three.js] – um blog com artigos dedicados ao ensino de three.js -
        • -
        • - [link:https://discourse.threejs.org/t/three-js-bookshelf/2468 Three.js Bookshelf] - Procurando mais recursos sobre three.js ou computação gráfica em geral? - Confira a seleção de literatura recomendada pela comunidade. -
        • -
        - -

        Notícias e atualizações

        -
          -
        • - [link:https://twitter.com/hashtag/threejs Three.js no Twitter] -
        • -
        • - [link:http://www.reddit.com/r/threejs/ Three.js no reddit] -
        • -
        • - [link:http://www.reddit.com/r/webgl/ WebGL no reddit] -
        • -
        - -

        Exemplos

        -
          -
        • - [link:https://github.com/edwinwebb/three-seed/ three-seed] - Projeto inicial three.js com ES6 e Webpack -
        • -
        • - [link:http://stemkoski.github.io/Three.js/index.html Professor Stemkoskis Examples] - Uma coleção de exemplos - amigáveis para iniciantes desenvolvidos com three.js. -
        • -
        • - [link:https://threejs.org/examples/ Official three.js examples] - esses exemplos são - mantidos como parte do repositório three.js e sempre usam a versão mais recente da biblioteca. -
        • -
        • - [link:https://raw.githack.com/mrdoob/three.js/dev/examples/ Official three.js dev branch examples] - - Igual ao anterior, exceto que eles usam a branch dev do three.js e são usados ​​para verificar se - tudo está funcionando enquanto o three.js está sendo desenvolvido. -
        • -
        - -

        Ferramentas

        -
          -
        • - [link:https://github.com/tbensky/physgl physgl.org] - Front-end JavaScript com wrappers para three.js, com o intuito de trazer gráficos WebGL - para alunos que estão aprendendo física e matemática. -
        • -
        • - [link:https://whsjs.readme.io/ Whitestorm.js] – Framework modular three.js com o plugin de física AmmoNext. -
        • -
        • - [link:http://zz85.github.io/zz85-bookmarklets/threelabs.html Three.js Inspector] -
        • -
        • - [link:http://idflood.github.io/ThreeNodes.js/ ThreeNodes.js]. -
        • -
        • - [link:https://marketplace.visualstudio.com/items?itemName=slevesque.shader vscode shader] - Marcador de sintaxe para linguagem de shader. -
          - [link:https://marketplace.visualstudio.com/items?itemName=bierner.comment-tagged-templates vscode comment-tagged-templates] - - Marcador de sintaxe para modelo de strings com tags usando comentários para linguagem de shader, como: glsl.js. -
        • -
        • - [link:https://github.com/MozillaReality/WebXR-emulator-extension WebXR-emulator-extension] -
        • -
        - -

        Referências WebGL

        -
          -
        • - [link:https://www.khronos.org/files/webgl/webgl-reference-card-1_0.pdf webgl-reference-card.pdf] - Referência de todas as palavras-chave WebGL e GLSL, terminologia, sintaxe e definições. -
        • -
        - -

        Links antigos

        -

        - Esses links são mantidos para fins históricos - você ainda pode achá-los úteis, mas esteja avisado de que - eles podem ter informações relacionadas a versões muito antigas do three.js. -

        - -
          -
        • - [link:https://www.youtube.com/watch?v=Dir4KO9RdhM AlterQualia at WebGL Camp 3] -
        • -
        • - [link:http://yomotsu.github.io/threejs-examples/ Yomotsus Examples] - uma coleção de exemplos usando three.js r45 -
        • -
        • - [link:http://fhtr.org/BasicsOfThreeJS/#1 Introduction to Three.js] de [link:http://github.com/kig/ Ilmari Heikkinen] (slides). -
        • -
        • - [link:http://www.slideshare.net/yomotsu/webgl-and-threejs WebGL and Three.js] de [link:http://github.com/yomotsu Akihiro Oyamada] (slides). -
        • -
        • - [link:https://www.youtube.com/watch?v=VdQnOaolrPA Trigger Rally] de [link:https://github.com/jareiko jareiko] (vídeo). -
        • -
        • - [link:http://blackjk3.github.io/threefab/ ThreeFab] - editor de cena, mantido até cerca do three.js r50. -
        • -
        • - [link:http://bkcore.com/blog/3d/webgl-three-js-workflow-tips.html Max to Three.js workflow tips and tricks] de [link:https://github.com/BKcore BKcore] -
        • -
        • - [link:http://12devsofxmas.co.uk/2012/01/webgl-and-three-js/ A whirlwind look at Three.js] - de [link:http://github.com/nrocy Paul King] -
        • -
        • - [link:http://bkcore.com/blog/3d/webgl-three-js-animated-selective-glow.html Animated selective glow in Three.js] - de [link:https://github.com/BKcore BKcore] -
        • -
        • - [link:http://www.natural-science.or.jp/article/20120220155529.php Building A Physics Simulation Environment] - - tutorial three.js em japonês -
        • -
        - - - diff --git a/docs/manual/pt-br/introduction/WebGL-compatibility-check.html b/docs/manual/pt-br/introduction/WebGL-compatibility-check.html deleted file mode 100644 index fcb0d97cf51a91..00000000000000 --- a/docs/manual/pt-br/introduction/WebGL-compatibility-check.html +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - -

        Compatibilidade WebGL

        - -

        - Mesmo que isso esteja se tornando um problema cada vez menor, alguns dispositivos ou navegadores podem ainda não suportar WebGL. - O método a seguir permite verificar se há suporte e exibe uma mensagem para o usuário se não existir. - Importe o módulo de detecção de suporte WebGL e execute o seguinte antes de tentar renderizar qualquer coisa. -

        - - - import WebGL from 'three/addons/capabilities/WebGL.js'; - - if ( WebGL.isWebGLAvailable() ) { - - // Initiate function or other initializations here - animate(); - - } else { - - const warning = WebGL.getWebGLErrorMessage(); - document.getElementById( 'container' ).appendChild( warning ); - - } - - - diff --git a/docs/manual/ru/introduction/Creating-a-scene.html b/docs/manual/ru/introduction/Creating-a-scene.html deleted file mode 100644 index 2adc7aa3e46039..00000000000000 --- a/docs/manual/ru/introduction/Creating-a-scene.html +++ /dev/null @@ -1,164 +0,0 @@ - - - - - - - - - -

        Создание сцены

        - -

        Цель этого раздела - дать краткое введение в three.js . Мы начнем с создания сцены с вращающимся кубом. Рабочий пример приведен внизу страницы на случай, если вы где то застряли и вам понадобится помощь.

        - -

        Прежде чем мы начнем

        - -

        - Прежде чем вы сможете использовать three.js , вам нужно где-то его отобразить. Save the following HTML to a file on your computer and open it in your browser. -

        - - - <!DOCTYPE html> - <html> - <head> - <meta charset="utf-8"> - <title>My first three.js app</title> - <style> - body { margin: 0; } - </style> - </head> - <body> - <script type="module"> - import * as THREE from 'https://unpkg.com/three/build/three.module.js'; - - // Наш Javascript будет здесь.. - </script> - </body> - </html> - - -

        Пока это все. Весь приведенный ниже код помещается в пустой тег <script>.

        - -

        Создание сцены

        - -

        Чтобы на самом деле иметь возможность отображать что-либо с three.js, Нам нужны три вещи: сцена, камера и средство визуализации, чтобы мы могли визуализировать сцену с помощью камеры.

        - - - const scene = new THREE.Scene(); - const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); - - const renderer = new THREE.WebGLRenderer(); - renderer.setSize( window.innerWidth, window.innerHeight ); - document.body.appendChild( renderer.domElement ); - - -

        Давайте воспользуемся моментом, чтобы объяснить, что здесь происходит. Теперь мы настроили сцену, нашу камеру и средство визуализации.

        - -

        Есть несколько разных камер в three.js . А пока давайте воспользуемся `PerspectiveCamera`.

        -

        Первый атрибут - это `поле зрения (field of view)`. FOV - это масштаб сцены, которая видна на дисплее в любой данный момент. Значение указано в градусах.

        - -

        Второй - это `соотношение сторон (aspect ratio)`. Вы почти всегда хотите использовать ширину элемента, деленную на высоту, иначе вы получите тот же результат, что и при воспроизведении старых фильмов на широкоэкранном телевизоре - изображение выглядит сплющенным.

        - -

        Следующие два атрибута - это `ближняя (near)` и `дальняя (far)` плоскость отсечения. Это означает, что объекты, находящиеся дальше от камеры, чем значение `far`, или ближе, чем `near`, не будут отображаться. Сейчас вам не нужно беспокоиться об этом, но вы можете использовать другие значения в своих приложениях, чтобы повысить производительность.

        - -

        Далее следует средство визуализации. Вот тут-то и происходит волшебство. В дополнение к средству визуализации WebGL, которое мы используем здесь, three.js поставляется с несколькими другими, часто используемыми в качестве резервных для пользователей со старыми браузерами или для тех, у кого по какой-либо причине нет поддержки WebGL.

        -

        В дополнение к созданию экземпляра средства визуализации нам также необходимо установить размер, в котором мы хотим, чтобы оно отображало наше приложение. Рекомендуется использовать ширину и высоту области, которую мы хотим заполнить с помощью нашего приложения - в данном случае ширину и высоту окна браузера. Для приложений с высокой производительностью вы также можете задать `setSize` меньшее значения, например `window.innerWidth/2` и `window.innerHeight/2`, что приведет к рендерингу приложения в четверть размера.

        -

        Если вы хотите сохранить размер своего приложения, но отобразить его с меньшим разрешением, вы можете сделать это, вызвав `setSize` с `updateStyle`(третий аргумент) равному false. Например, `setSize(window.innerWidth/2, window.innerHeight/2, false)` будет отображать ваше приложение с половинным разрешением, учитывая, что ваш <canvas > имеет 100% ширины и высоты.

        - -

        И последнее, но не менее важное: мы добавляем элемент `renderer` в наш HTML-документ. Это элемент <canvas>, который рендерер использует для отображения сцены для нас.

        -

        "Все это хорошо, но, где тот кубик, который Вы так обещали?" Давайте добавим его сейчас.

        - - - const geometry = new THREE.BoxGeometry( 1, 1, 1 ); - const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); - const cube = new THREE.Mesh( geometry, material ); - scene.add( cube ); - - camera.position.z = 5; - - -

        Чтобы создать куб, нам нужна `BoxGeometry`. Это объект, который содержит все точки (`vertices/вершины`) и заливку (`faces/грани`) куба. Мы подробнее рассмотрим это в будущем.

        - -

        В дополнение к геометрии нам нужен материал, чтобы раскрасить его. Three.js поставляется с несколькими материалами, но пока мы будем придерживаться `MeshBasicMaterial`. Все материалы приобретают объект свойств, которые будут к ним применены. Чтобы все было очень просто, мы предоставляем только атрибут цвета `0x00ff00`, который является зеленым. Это работает так же, как цвета работают в CSS или Photoshop (`шестнадцатеричные цвета/hex colors`).

        - -

        Третья вещь, которая нам нужна, - это `Mesh(Сетка)`. Сетка - это объект, который принимает геометрию и применяет к ней материал, который затем мы можем вставить в нашу сцену и свободно перемещать по ней.

        - -

        По умолчанию, когда мы вызываем `scene.add()`, то, что мы добавляем, будет добавлено к координатам `(0,0,0)`. Это привело бы к тому, что и камера, и куб оказались бы внутри друг друга. Чтобы избежать этого, мы просто немного выдвигаем камеру.

        - -

        Рендеринг сцены

        - -

        Если бы вы скопировали приведенный выше код в HTML-файл, который мы создали ранее, вы бы ничего не смогли увидеть. Это потому, что на самом деле мы еще ничего не рендерим. Для этого нам нужно то, что называется `цикл рендеринга или анимации`.

        - - - function animate() { - requestAnimationFrame( animate ); - renderer.render( scene, camera ); - } - animate(); - - -

        Это создаст цикл, который заставляет средство визуализации отрисовывать сцену каждый раз при обновлении экрана (на обычном экране это означает 60 раз в секунду). Если вы новичок в написании игр в браузере, вы можете сказать "Почему бы нам просто не создать setInterval?" Дело в том, что мы могли бы, но `requestAnimationFrame` имеет ряд преимуществ. Возможно, самым важным из них является то, что он приостанавливается, когда пользователь переходит на другую вкладку браузера, следовательно, не тратя впустую свою драгоценную вычислительную мощность и время автономной работы.

        - -

        Анимация куба

        - -

        Если вы вставите весь приведенный выше код в файл, который вы создали до того, как мы начали, вы должны увидеть зеленое поле. Давайте сделаем все это немного интереснее, повернув его.

        - -

        Добавьте следующий код прямо над вызовом `renderer.render` в вашей функции `animate`:

        - - - cube.rotation.x += 0.01; - cube.rotation.y += 0.01; - - -

        Это будет выполняться каждый кадр (обычно 60 раз в секунду) и придаст кубу приятную анимацию вращения. По сути, все, что вы хотите переместить или изменить во время работы приложения, должно пройти через цикл анимации. Конечно, вы можете вызывать оттуда другие функции, чтобы в итоге не получить `анимированную` функцию, состоящую из сотен строк.

        - -

        Результат

        - -

        Поздравляю! Теперь вы завершили свой первый three.js применение. Это просто, но вы должны с чего-то начать.

        - -

        Полный код доступен ниже и доступен для редактирования [link:https://jsfiddle.net/0c1oqf38/ live example]. Поиграйте с ним, чтобы лучше понять, как он работает.

        - - - <!DOCTYPE html> - <html> - <head> - <meta charset="utf-8"> - <title>My first three.js app</title> - <style> - body { margin: 0; } - </style> - </head> - <body> - <script type="module"> - import * as THREE from 'https://unpkg.com/three/build/three.module.js'; - - const scene = new THREE.Scene(); - const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); - - const renderer = new THREE.WebGLRenderer(); - renderer.setSize( window.innerWidth, window.innerHeight ); - document.body.appendChild( renderer.domElement ); - - const geometry = new THREE.BoxGeometry( 1, 1, 1 ); - const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); - const cube = new THREE.Mesh( geometry, material ); - scene.add( cube ); - - camera.position.z = 5; - - function animate() { - requestAnimationFrame( animate ); - - cube.rotation.x += 0.01; - cube.rotation.y += 0.01; - - renderer.render( scene, camera ); - } - - animate(); - </script> - </body> - </html> - - - diff --git a/docs/manual/ru/introduction/Creating-text.html b/docs/manual/ru/introduction/Creating-text.html deleted file mode 100644 index 5708532389f88d..00000000000000 --- a/docs/manual/ru/introduction/Creating-text.html +++ /dev/null @@ -1,142 +0,0 @@ - - - - - - - - - -

        Создание текста ([name])

        -
        -

        - Часто бывают случаи, когда вам может понадобиться использовать текст в вашем three.js приложение - вот - несколько способов, как это сделать. -

        -
        - -

        1. DOM + CSS

        -
        -

        - Использование HTML, как правило, является самым простым и быстрым способом добавления текста. Этот метод - используется для наложения текста в большинстве three.js приложений. -

        -

        Вы можете добавлять содержимое в

        - <div id="info">Описание</div> - -

        - и использовать разметку CSS для абсолютного позиционирования над всеми остальным с - z-index, особенно если вы используете полноэкранный режим three.js. -

        - - -#info { - position: absolute; - top: 10px; - width: 100%; - text-align: center; - z-index: 100; - display:block; -} - - -
        - - -

        2. Использование [page:CSS2DRenderer] или [page:CSS3DRenderer]

        -
        -

        - Используйте эти рендереры для отрисовки высококачественного текста, содержащегося в элементах DOM, в вашу сцену three.js. - Это похоже на 1. за исключением того, что с помощью этих средств визуализации элементы могут быть более тесно и динамично интегрированы в сцену. -

        -
        - - -

        3. Нарисуйте текст на холсте используя [page:Texture]

        -
        -

        Используйте этот способ, если вы хотите легко нарисовать текст на плоскости в вашем three.js сцене.

        -
        - - -

        4. Создайте модель в вашем любимом 3D-приложении и экспортируйте в three.js

        -
        -

        Используйте этот способ, если вы предпочитаете работать со своими 3d-приложениями и импортировать модели в three.js .

        -
        - - -

        5. Процедурная геометрия текста

        -
        -

        - Если вы предпочитаете работать исключительно в three.js или создавать процедурные и динамические 3D - текстовые геометрии, вы можете создать mesh, геометрия которой является экземпляром THREE.TextGeometry: -

        -

        - new THREE.TextGeometry( text, parameters ); -

        -

        - Однако для того, чтобы это сработало, вашей текстовой геометрии потребуется экземпляр из THREE.Font - должен быть установлен в его параметре "шрифт". - - Смотрите страницу [page:TextGeometry] для получения дополнительной информации о том, как это можно сделать, описания каждого - принимаемого параметра и список шрифтов JSON, которые поставляются с THREE.js. -

        - -

        Примеры

        - -

        - [example:webgl_geometry_text WebGL / geometry / text]
        - [example:webgl_shadowmap WebGL / shadowmap] -

        - -

        - Если шрифт не работает или вы хотите использовать шрифт, которого там нет, есть учебное пособие по - скрипту python для blender, который позволит экспортировать текст в Three.js в формате JSON: - [link:http://www.jaanga.com/2012/03/blender-to-threejs-create-3d-text-with.html] -

        - -
        - - -

        6. Растровые шрифты

        -
        -

        - Растровые шрифты (bitmap fonts) позволяют группировать глифы в единую BufferGeometry. Рендеринг BMFont - поддерживает перенос слов, межбуквенный интервал, кернинг, поля расстояния со знаком со стандартными - производными, многоканальные поля расстояния со знаком, шрифты с несколькими текстурами и многое другое. - Смотрите [link:https://github.com/felixmariotto/three-mesh-ui three-mesh-ui] или [link:https://github.com/Jam3/three-bmfont-text three-bmfont-text]. -

        -

        - Стандартные шрифты доступны в таких проектах, как - [link:https://github.com/etiennepinchon/aframe-fonts A-Frame Fonts], или вы можете создать свой собственный - из любого .TTF шрифта, оптимизированный для включения только символов, необходимых для проекта. -

        -

        - Некоторые полезные инструменты: -

        -
          -
        • [link:http://msdf-bmfont.donmccurdy.com/ msdf-bmfont-web] (web-based)
        • -
        • [link:https://github.com/soimy/msdf-bmfont-xml msdf-bmfont-xml] (commandline)
        • -
        • [link:https://github.com/libgdx/libgdx/wiki/Hiero hiero] (desktop app)
        • -
        -
        - - -

        7. Текст Тройка (Troika Text)

        -
        -

        - Пакет [link:https://www.npmjs.com/package/troika-three-text troika-three-text] отображает - качественный сглаженный текст, использующий ту же технику, что и BMFonts, но работающий напрямую с любым .TTF - или .WOFF файлом шрифта, чтобы вам не приходилось восстанавливать текстуру глифа в автономном режиме. Он также добавляет - возможности, в том числе: -

        -
          -
        • Такие эффекты, как штрихи, отбрасываемые тени и кривизна
        • -
        • Возможность применять любые three.js Материал, даже пользовательский шейдерный материал
        • -
        • Поддержка лигатур шрифтов, скриптов с соединенными буквами и компоновки справа-налево/двунаправленно
        • -
        • Оптимизация для больших объемов динамического текста, выполнение большей части работы вне основного потока в веб-воркере
        • -
        -
        - - - - diff --git a/docs/manual/ru/introduction/Drawing-lines.html b/docs/manual/ru/introduction/Drawing-lines.html deleted file mode 100644 index 019e278e143c6b..00000000000000 --- a/docs/manual/ru/introduction/Drawing-lines.html +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - - -

        Рисование линий ([name])

        -
        -

        - Допустим, вы хотите нарисовать линию или круг, а не каркас [page:Mesh]. - Сначала нам нужно настроить [page:WebGLRenderer renderer](рендеринг), [page:Scene scene](сцену) и камеру (см. страницу Создания сцены). -

        - -

        Вот код, который мы будем использовать:

        - -const renderer = new THREE.WebGLRenderer(); -renderer.setSize( window.innerWidth, window.innerHeight ); -document.body.appendChild( renderer.domElement ); - -const camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 500 ); -camera.position.set( 0, 0, 100 ); -camera.lookAt( 0, 0, 0 ); - -const scene = new THREE.Scene(); - -

        Следующее, что мы сделаем, - это определим материал. Для линий мы должны использовать [page:LineBasicMaterial] или [page:LineDashedMaterial].

        - -// создаем синий LineBasicMaterial -const material = new THREE.LineBasicMaterial( { color: 0x0000ff } ); - - -

        - После материала нам понадобится геометрия с некоторыми вершинами: -

        - - -const points = []; -points.push( new THREE.Vector3( - 10, 0, 0 ) ); -points.push( new THREE.Vector3( 0, 10, 0 ) ); -points.push( new THREE.Vector3( 10, 0, 0 ) ); - -const geometry = new THREE.BufferGeometry().setFromPoints( points ); - - -

        Обратите внимание, что линии рисуются между каждой последовательной парой вершин, но не между первой и последней (линия не замкнута).

        - -

        Теперь, когда у нас есть точки для двух линий и материал, мы можем соединить их вместе, чтобы сформировать линию.

        - -const line = new THREE.Line( geometry, material ); - -

        Все, что осталось, это добавить его в сцену и вызвать [page:WebGLRenderer.render render].

        - - -scene.add( line ); -renderer.render( scene, camera ); - - -

        Теперь вы должны увидеть стрелку, направленную вверх, сделанную из двух синих линий.

        -
        - - diff --git a/docs/manual/ru/introduction/FAQ.html b/docs/manual/ru/introduction/FAQ.html deleted file mode 100644 index 5b1cbfef6470ea..00000000000000 --- a/docs/manual/ru/introduction/FAQ.html +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - -

        Часто задаваемые вопросы [name]

        - -

        Какой формат 3D-модели лучше всего поддерживается?

        -
        -

        - Рекомендуемый формат для импорта и экспорта - golf (формат передачи GL). Поскольку glTF ориентирован на доставку ресурсов во время выполнения, он компактен для передачи и быстр для загрузки. -

        -

        - three.js предоставляет загрузчики для многих других популярных форматов, таких как FBX, Collada или OBJ. Тем не менее, вы всегда должны сначала пытаться настроить рабочий процесс на основе glTF в своих проектах. Для получения дополнительной информации см. [link:#manual/introduction/Loading-3D-models loading 3D models] (загрузка 3D-моделей). -

        -
        - -

        Почему в примерах присутствуют теги meta viewport?

        -
        - <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> - -

        - Эти теги управляют размером и масштабом областью просмотра экрана для мобильных браузеров (где содержимое страницы может отображаться с размером, отличным от видимой области просмотра экрана). -

        - -

        [link:https://developer.apple.com/library/content/documentation/AppleApplications/Reference/SafariWebContent/UsingtheViewport/UsingtheViewport.html Safari: Using the Viewport] (Safari: Использование области видимости экрана)

        - -

        [link:https://developer.mozilla.org/en-US/docs/Web/HTML/Viewport_meta_tag MDN: Using the viewport meta tag] (MDN: Использование мета-тега области видимости экрана)

        -
        - -

        Как можно сохранить масштаб сцены при изменении размера?

        -

        - Мы хотим, чтобы все объекты, независимо от их расстояния от камеры, отображались одинакового размера, даже при изменении размера окна. - - Ключевым уравнением для решения этой проблемы является следующая формула для видимой высоты на заданном расстоянии: - - -visible_height = 2 * Math.tan( ( Math.PI / 180 ) * camera.fov / 2 ) * distance_from_camera; - - Если мы увеличим высоту окна на определенный процент, то мы хотим, чтобы видимая высота на всех расстояниях - увеличилась на тот же процент. Этого нельзя сделать, просто изменив положение камеры. - Вместо этого вы должны изменить поле зрения камеры. - [link:http://jsfiddle.net/Q4Jpu/ Example] (пример). -

        - -

        Почему часть моего объекта невидима?

        -

        - Это может быть связано с определением граней. Грани имеют ориентацию, которая определяет, - какая сторона является какой. И определение удаляет заднюю часть при нормальных условиях. - Чтобы увидеть, это ли ваша проблема, измените сторону материала на THREE.DoubleSide. - material.side = THREE.DoubleSide -

        - -

        Почему three.js иногда возвращает странные результаты для недопустимых входных данных?

        -

        - Из соображений производительности в большинстве случаев three.js не проверяет входные данные. Ваше приложение должно убедиться, что все входные данные действительны. -

        - - diff --git a/docs/manual/ru/introduction/Installation.html b/docs/manual/ru/introduction/Installation.html deleted file mode 100644 index 663783a29fc8b2..00000000000000 --- a/docs/manual/ru/introduction/Installation.html +++ /dev/null @@ -1,200 +0,0 @@ - - - - - - - - - -

        Установка ([name])

        - -

        - Вы можете установить three.js с помощью [link:https://www.npmjs.com/ npm] и современных инструментов сборки или - быстро начните работу со статического хостинга или CDN. Для большинства пользователей установка из npm является - лучшим выбором. -

        - -

        - Что бы вы ни выбрали, будьте последовательны и импортируйте все файлы из одной и той же версии библиотеки. - Смешивание файлов из разных источников может привести к включению дублирующегося кода или даже к неожиданной - поломке - приложения. -

        - - -

        - Все способы установки three.js зависят от модулей ES (см. - [link:https://eloquentjavascript.net/10_modules.html#h_hF2FmOVxw7 Eloquent JavaScript: ECMAScript Modules]), - которые - позволяют включать в конечный проект только те части библиотеки, которые необходимы. -

        - -

        Установка из npm

        - -

        - Чтобы установить [link:https://www.npmjs.com/package/three three] модуль npm, откройте окно терминала в папке - вашего - проекта и запустите: -

        - - - npm install three - - -

        - Пакет будет загружен и установлен. Когда вы будете готовы импортировать его в свой код: -

        - - - // Вариант 1: Импортируйте весь three.js основная библиотека. - import * as THREE from 'three'; - - const scene = new THREE.Scene(); - - - // Вариант 2: Импортируйте только те детали, которые вам нужны. - import { Scene } from 'three'; - - const scene = new Scene(); - - -

        - При установке из npm вы почти всегда будете использовать какой-либо - [link:https://eloquentjavascript.net/10_modules.html#h_zWTXAU93DC bundling tool], чтобы объединить все пакеты, - необходимые вашему проекту, в один файл JavaScript. В то время как любой современный пакет JavaScript можно - использовать с three.js , самым популярным выбором является [link:https://webpack.js.org/ webpack]. -

        - -

        - Не ко всем функциям можно получить доступ непосредственно через модуль three (также называемый "голым - импортом"). Другие популярные части библиотеки, такие как элементы управления, загрузчики и эффекты - постобработки, - должны быть импортированы из подпапки [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm - examples/jsm]. - Чтобы узнать больше, смотрите Примеры ниже. -

        - -

        - Узнайте больше о модулях npm по ссылке [link:https://eloquentjavascript.net/20_node.html#h_J6hW/SmL/a Eloquent - JavaScript: Installing with npm](Выразительный JavaScript: Установка с помощью npm). -

        - -

        Установка с CDN или статического хостинга

        - -

        - В three.js библиотеку можно использовать без какой-либо системы сборки, либо загрузив файлы на свой собственный - веб-сервер, либо используя существующий CDN. Поскольку библиотека полагается на модули ES, любой скрипт, который - ссылается на нее, должен использовать type="module", как показано ниже. - Также требуется определить карту импорта, которая разрешает пустой модуль, указанный как `three`. -

        - - - <script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script> - - <script type="importmap"> - { - "imports": { - "three": "https://unpkg.com/three@<version>/build/three.module.js" - } - } - </script> - - <script type="module"> - - import * as THREE from 'three'; - - const scene = new THREE.Scene(); - - </script> - - -

        - Поскольку импорт карт еще не поддерживается всеми браузерами, необходимо добавить полифил *es-module-shims.js*. -

        - -

        Дополнения

        - -

        - Ядро three.js сосредоточен на наиболее важных компонентах 3D-движка. Многие другие полезные компоненты, такие - как элементы управления, загрузчики и эффекты постобработки, являются частью каталога - [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm examples/jsm]. Они называются "дополнениями"(addons) (ранее назывались "примерами"(examples)), - потому что, хотя вы можете использовать их в готовом виде, они также предназначены для ремиксов и кастомизации. - Эти компоненты всегда синхронизируются с основной библиотекой, в то время как аналогичные пакеты сторонних - разработчиков в npm поддерживаются разными пользователями и могут быть устаревшими. -

        - -

        - Дополнения не нужно устанавливать отдельно, но их нужно импортировать отдельно. Если three.js был - установлен с помощью npm, вы можете загрузить компонент [page:OrbitControls](Управление орбитой) с помощью: -

        - - - - import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; - - const controls = new OrbitControls( camera, renderer.domElement ); - - -

        - Если three.js был установлен с CDN, используйте тот же код, но с `three/addons/` в карте импорта. -

        - - - <script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script> - - <script type="importmap"> - { - "imports": { - "three": "https://unpkg.com/three@<version>/build/three.module.js", - "three/addons/": "https://unpkg.com/three@<version>/examples/jsm/" - } - } - </script> - <script type="module"> - - import * as THREE from 'three'; - import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; - - const controls = new OrbitControls( camera, renderer.domElement ); - - </script> - - -

        - Важно, чтобы все файлы использовали одну и ту же версию. Не импортируйте разные дополнения(addons) из разных версий и - не используйте дополнения(addons) из версии, отличной от three.js самой библиотека. -

        - -

        Совместимость

        - -

        CommonJS импорт

        - -

        - В то время как большинство современных пакетов JavaScript теперь поддерживают модули по умолчанию, некоторые - старые - инструменты сборки могут этого не делать. В этих случаях вы, вероятно, можете настроить пакет для подключения - модулей ES: [link:http://browserify.org/ Browserify] просто нужен плагин [link:https://github.com/babel/babelify babelify] , как пример. - -

        - -

        Node.js

        - -

        - Так как three.js создан для Интернета, он зависит от браузера и DOM API, которые не всегда существуют в Node.js. - Некоторые из этих проблем могут быть решены с помощью прослоек, таких как - [link:https://github.com/stackgl/headless-gl headless-gl], или путем замены компонентов, таких как - [page:TextureLoader], пользовательскими альтернативами. Другие API-интерфейсы DOM могут быть глубоко переплетены - с использующим их кодом, и обойти их будет сложнее. - Мы приветствуем простые и поддерживаемые предложения `pull requests` для улучшения поддержки Node.js, но - рекомендуем сначала открыть `issue`(вопрос), чтобы обсудить ваши улучшения. -

        - -

        - Обязательно добавьте `{ "type": "module" }` в свой `package.json`, чтобы включить модули ES6 в ваш проект. -

        - - - diff --git a/docs/manual/ru/introduction/Libraries-and-Plugins.html b/docs/manual/ru/introduction/Libraries-and-Plugins.html deleted file mode 100644 index 21d1085d3b3887..00000000000000 --- a/docs/manual/ru/introduction/Libraries-and-Plugins.html +++ /dev/null @@ -1,109 +0,0 @@ - - - - - - - - - -

        Библиотеки и плагины ([name])

        - -

        - Здесь перечислены совместимые библиотеки и плагины, разработанные опенсорсом для three.js . Этот - список и связанные с ним пакеты поддерживаются сообществом и не гарантируют - их актуальность. Если вы хотите обновить этот список, сделайте PR! -

        - -

        Физика

        - -
          -
        • [link:https://github.com/lo-th/Oimo.js/ Oimo.js]
        • -
        • [link:https://enable3d.io/ enable3d]
        • -
        • [link:https://github.com/kripken/ammo.js/ ammo.js]
        • -
        • [link:https://github.com/pmndrs/cannon-es cannon-es]
        • -
        - -

        Постобработка

        - -

        - В дополнение к [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm/postprocessing official three.js postprocessing effects] (оф. эффекты постобработки three.js ], - поддержка некоторых дополнительных эффектов и фреймворков доступна через внешние библиотеки. -

        - -
          -
        • [link:https://github.com/vanruesc/postprocessing postprocessing]
        • -
        - -

        Пересечение и производительность Raycast

        - -
          -
        • [link:https://github.com/gkjohnson/three-mesh-bvh three-mesh-bvh]
        • -
        - -

        Трассировка лучей

        - -
          -
        • [link:https://github.com/gkjohnson/three-gpu-pathtracer three-gpu-pathtracer]
        • -
        - -

        Форматы файлов

        - -

        - В дополнение к [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm/loaders official three.js loaders] (оф. three.js загрузчики), - поддержка некоторых дополнительных форматов доступна через внешние библиотеки. -

        - -
          -
        • [link:https://github.com/gkjohnson/urdf-loaders/tree/master/javascript urdf-loader]
        • -
        • [link:https://github.com/NASA-AMMOS/3DTilesRendererJS 3d-tiles-renderer-js]
        • -
        • [link:https://github.com/kaisalmen/WWOBJLoader WebWorker OBJLoader]
        • -
        • [link:https://github.com/IFCjs/web-ifc-three IFC.js]
        • -
        - -

        Геометрия

        - -
          -
        • [link:https://github.com/spite/THREE.MeshLine THREE.MeshLine]
        • -
        - -

        3D Текст и Расположение (3D Text and Layout)

        - -
          -
        • [link:https://github.com/protectwise/troika/tree/master/packages/troika-three-text troika-three-text]
        • -
        • [link:https://github.com/felixmariotto/three-mesh-ui three-mesh-ui]
        • -
        - -

        Системы частиц

        - -
          -
        • [link:https://github.com/Alchemist0823/three.quarks three.quarks]
        • -
        • [link:https://github.com/creativelifeform/three-nebula three-nebula]
        • -
        - -

        Обратная кинематика

        - -
          -
        • [link:https://github.com/jsantell/THREE.IK THREE.IK]
        • -
        • [link:https://github.com/lo-th/fullik fullik]
        • -
        • [link:https://github.com/gkjohnson/closed-chain-ik-js closed-chain-ik]
        • -
        - -

        Игровой искусственный интеллект

        - -
          -
        • [link:https://mugen87.github.io/yuka/ yuka]
        • -
        • [link:https://github.com/donmccurdy/three-pathfinding three-pathfinding]
        • -
        - -

        Обертки и фреймворки

        - -
          -
        • [link:https://aframe.io/ A-Frame]
        • -
        • [link:https://github.com/pmndrs/react-three-fiber react-three-fiber]
        • -
        • [link:https://github.com/ecsyjs/ecsy-three ECSY]
        • -
        • [link:https://threlte.xyz/ Threlte]
        • -
        - - - diff --git a/docs/manual/ru/introduction/Loading-3D-models.html b/docs/manual/ru/introduction/Loading-3D-models.html deleted file mode 100644 index 1391f1a3ec1167..00000000000000 --- a/docs/manual/ru/introduction/Loading-3D-models.html +++ /dev/null @@ -1,165 +0,0 @@ - - - - - - - - - - - -

        Загрузка 3D-моделей ([name])

        - -

        - 3D-модели доступны в сотнях форматов, каждый из которых имеет различные - цели, разнообразные функции и различную сложность. Хотя three.js предоставляет множество - - загрузчиков, выбор правильного формата и - рабочего процесса сэкономит время и не разочарует в дальнейшем. С некоторыми форматами - сложно работать, они неэффективны для работы в реальном времени или просто не - поддерживаются в полной мере в настоящее время. -

        - -

        - В этом руководстве представлен рабочий процесс, рекомендуемый для большинства пользователей, и рекомендации - о том, что предпринять, если что-то пойдет не так, как ожидалось. -

        - -

        - -

        - Если вы новичок в управлении локальным сервером, начните с раздела - [link:#manual/introduction/Installation installation] (Локальная разработка). - Многих распространенных ошибок при просмотре 3D-моделей можно избежать, правильно разместив файлы. -

        - -

        Прежде чем начать

        - -

        - Там, где это возможно, мы рекомендуем использовать glTF (GL Transmission Format). Оба - .GLB и .GLTF версии формата - хорошо поддерживаются. Поскольку glTF ориентирован на доставку ресурсов во время выполнения, он - компактен для передачи и быстр для загрузки. Объекты включают в себя mesh, материалы, - текстуры, скины, скелеты, цели морфинга, анимацию, освещение и - камеры. -

        - -

        - Файлы glTF, находящиеся в открытом доступе, доступны на таких сайтах, как - - Sketchfab, так же есть различные инструменты включающие экспорт glTF: -

        - - - -

        - Если предпочитаемые вами инструменты не поддерживают glTF, рассмотрите возможность запроса экспорта glTF у авторов или опубликуйте - в ветке glTF roadmap. -

        - -

        - Если glTF не подходит, используйте популярные форматы, такие как FBX, OBJ или COLLADA - также доступны и регулярно обновляются. -

        - -

        Loading

        - -

        - Только несколько загрузчиков (например, [page:ObjectLoader]) включены по умолчанию с - three.js — другие должны быть добавлены в ваше приложение индивидуально. -

        - - - import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; - - -

        - После того, как вы импортировали загрузчик, вы готовы добавить модель в свою сцену. Синтаксис отличается у - разных загрузчиков — при использовании другого формата ознакомьтесь с примерами и документацией для этого - загрузчика. Для glTF использование с глобальными скриптами было бы: -

        - - - const loader = new GLTFLoader(); - - loader.load( 'path/to/model.glb', function ( gltf ) { - - scene.add( gltf.scene ); - - }, undefined, function ( error ) { - - console.error( error ); - - } ); - - -

        - См. [page:GLTFLoader GLTFLoader documentation] для получения более подробной информации. -

        - -

        Диагностика

        - -

        - Вы потратили часы на создание шедевра ручной работы, загружаете его на - веб—страницу и - о нет! 😭 Он искажен, неправильно окрашен или полностью отсутствует. - Начните с выполнения следующих действий по устранению неполадок: -

        - -
          -
        1. - Проверьте консоль JavaScript на наличие ошибок и убедитесь, что вы использовали - обратный вызов `onError` при вызове `.load()` для регистрации результата. -
        2. -
        3. - Просмотрите модель в другом приложении. Для glTF, средства просмотра с помощью перетаскивания - доступны для - three.js и - babylon.js. Если модель - отображается правильно в одном или нескольких приложениях, - сообщить об ошибке в three.js. - Если модель не может быть показана ни в одном приложении, мы настоятельно рекомендуем предоставить приложение - (можно в песочнице) для воспроизведения бага. -
        4. -
        5. - Попробуйте увеличить или уменьшить масштаб модели в 1000 раз. Многие модели - масштабируются по-разному, и большие модели могут не отображаться, если камера находится - внутри модели. -
        6. -
        7. - Попробуйте добавить и расположить источник света. Модель может быть скрыта в темноте. -
        8. -
        9. - Ищите неудачные запросы текстур на вкладке сеть, например - `"C:\\Path\To\Model\texture.jpg "`. Вместо этого используйте пути относительно вашей - модели, например `images/texture.jpg ` — для этого может потребоваться - редактирование файла модели в текстовом редакторе. -
        10. -
        - -

        Помощь

        - -

        - Если вы прошли шаги описанные выше для устранения неполадок, а ваша модель - по-прежнему не работает, правильный подход это обращение за помощью которое поможет вам - быстрее найти решение. Разместите вопрос на - форуме three.js и, по возможности, - приложите свою модель (или более простую модель с той же проблемой) в любых - доступных вам форматах. Предоставьте достаточно информации, чтобы кто-то другой мог воспроизвести - проблему быстро — в идеале, живая демонстрация. -

        - - - - diff --git a/docs/manual/ru/introduction/Useful-links.html b/docs/manual/ru/introduction/Useful-links.html deleted file mode 100644 index 199a688ce9d6b3..00000000000000 --- a/docs/manual/ru/introduction/Useful-links.html +++ /dev/null @@ -1,177 +0,0 @@ - - - - - - - - - -

        Полезные ссылки ([name])

        - -

        - Ниже приведен список ссылок, которые могут оказаться полезными при изучении three.js .
        - Если вы нашли что-то, что хотели бы добавить сюда, или считаете, что одна из приведенных ниже ссылок больше не - актуально или работает, не стесняйтесь нажать кнопку "Редактировать" в правом нижнем углу и внести некоторые изменения!

        - - Отметим также, что в качестве three.js находится в стадии быстрой разработки, многие из этих ссылок будут содержать - устаревшую информацию - если что-то работает не так, как вы ожидаете или как следует по одной из этих ссылок, - проверьте консоль браузера на наличие предупреждений или ошибок. Также проверьте соответствующие страницы документов. -

        - -

        Форумы для помощи

        -

        - Three.js официально использует [link:https://discourse.threejs.org/ forum] и [link: http://stackoverflow.com/tags/three.js/info StackOverflow] для просьб о помощи. - Если вам нужна помощь в чем-то, это то место, куда нужно обратиться. Не открывайте проблему на Github для просьб о помощи. -

        - -

        Учебники и курсы

        - -

        Начало работы с three.js

        -
          -
        • - [link:https://threejs.org/manual/#ru/fundamentals Основы Three.js фундаментальные знания] -
        • -
        • - [link:https://codepen.io/rachsmith/post/beginning-with-3d-webgl-pt-1-the-scene Beginning with 3D WebGL] (Начиная с 3D WebGL) вместе с [link:https://codepen.io/rachsmith/ Rachel Smith]. -
        • -
        • - [link:https://www.august.com.au/blog/animating-scenes-with-webgl-three-js/ Animating scenes with WebGL and three.js] (Анимация сцен с помощью WebGL и three.js) -
        • -
        - -

        Более обширные / продвинутые статьи и курсы

        -
          -
        • - [link:https://threejs-journey.com/ Three Journey] Курс [link:https://bruno-simon.com/ Bruno Simon] - Учит начинающих, как использовать Three.js шаг за шагом -
        • -
        • - [link:https://discoverthreejs.com/ Discover three.js] -
        • -
        • - [link:http://blog.cjgammon.com/ Collection of tutorials] by [link:http://www.cjgammon.com/ CJ Gammon]. -
        • -
        • - [link:https://medium.com/soffritti.pierfrancesco/glossy-spheres-in-three-js-bfd2785d4857 Glossy spheres in three.js]. -
        • -
        • - [link:https://www.udacity.com/course/cs291 Interactive 3D Graphics] - a free course on Udacity that teaches the fundamentals of 3D Graphics, - and uses three.js as its coding tool. -
        • -
        • - [Link:https://aerotwist.com/tutorials/ Aerotwist] tutorials by [link:https://github.com/paullewis/ Paul Lewis]. -
        • -
        • - [link:https://discourse.threejs.org/t/three-js-bookshelf/2468 Three.js Bookshelf] - Ищете больше ресурсов о three.js или компьютерную графику в целом? - Ознакомьтесь с подборкой литературы, рекомендованной сообществом. -
        • -
        - -

        Новости и обновления

        -
          -
        • - [link:https://twitter.com/hashtag/threejs Three.js on Twitter] -
        • -
        • - [link:http://www.reddit.com/r/threejs/ Three.js on reddit] -
        • -
        • - [link:http://www.reddit.com/r/webgl/ WebGL on reddit] -
        • -
        - -

        Примеры

        -
          -
        • - [link:https://github.com/edwinwebb/three-seed/ three-seed] - three.js стартовый проект с ES6 и Webpack -
        • -
        • - [link:http://stemkoski.github.io/Three.js/index.html Professor Stemkoskis Examples] - коллекция примеров удобных для начинающих, - построенные с использованием three.js r60. -
        • -
        • - [link:https://threejs.org/examples/ Official three.js examples] - эти примеры - поддерживаются как часть three.js репозиторий и всегда используйте последнюю версию three.js . -
        • -
        • - [link:https://raw.githack.com/mrdoob/three.js/dev/examples/ Official three.js dev branch examples] - - То же, что и выше, за исключением того, что они используют ветвь dev three.js , и используются для проверки того, что - все работает как когда разрабатывается three.js . -
        • -
        - -

        Инструменты

        -
          -
        • - [link:https://github.com/tbensky/physgl physgl.org] - Внешний интерфейс JavaScript с обертками для three.js, чтобы добавить WebGL - графику для студентов, изучающих физику и математику. -
        • -
        • - [link:https://whsjs.readme.io/ Whitestorm.js] – Модульный фреймворк three.js с физическим плагином AmmoNext. -
        • -
        • - [link:http://zz85.github.io/zz85-bookmarklets/threelabs.html Three.js Inspector] Инспектор Three.js -
        • -
        • - [link:http://idflood.github.io/ThreeNodes.js/ ThreeNodes.js]. -
        • -
        • - [link:https://marketplace.visualstudio.com/items?itemName=slevesque.shader vscode shader] - Подсветка синтаксиса для языка шейдеров. -
          - [link:https://marketplace.visualstudio.com/items?itemName=bierner.comment-tagged-templates vscode comment-tagged-templates] - Подсветка синтаксиса для помеченных строк шаблона с использованием комментариев к языку шейдеров, например: glsl.js. -
        • -
        • - [link:https://github.com/MozillaReality/WebXR-emulator-extension WebXR-emulator-extension] -
        • -
        - -

        Ссылки на WebGL

        -
          -
        • - [link:https://www.khronos.org/files/webgl/webgl-reference-card-1_0.pdf webgl-reference-card.pdf] - Ссылка на все ключевые слова WebGL и GLSL, терминологию, синтаксис и определения. -
        • -
        - -

        Старые ссылки

        -

        - Эти ссылки сохранены в исторических целях - вы все еще можете найти их полезными, но имейте в виду, что - они могут содержать информацию, относящуюся к очень старым версиям three.js . -

        - -
          -
        • - [link:https://www.youtube.com/watch?v=Dir4KO9RdhM AlterQualia at WebGL Camp 3] -
        • -
        • - [link:http://yomotsu.github.io/threejs-examples/ Yomotsus Examples] - коллекция примеров, использующих three.js r45. -
        • -
        • - [link:http://fhtr.org/BasicsOfThreeJS/#1 Introduction to Three.js] by [link:http://github.com/kig/ Ilmari Heikkinen] (слайд-шоу). -
        • -
        • - [link:http://www.slideshare.net/yomotsu/webgl-and-threejs WebGL and Three.js] by [link:http://github.com/yomotsu Akihiro Oyamada] (слайд-шоу). -
        • -
        • - [link:https://www.youtube.com/watch?v=VdQnOaolrPA Trigger Rally] by [link:https://github.com/jareiko jareiko] (видео). -
        • -
        • - [link:http://blackjk3.github.io/threefab/ ThreeFab] - редактор сцен, поддерживаемый примерно до three.js r50. -
        • -
        • - [link:http://bkcore.com/blog/3d/webgl-three-js-workflow-tips.html Max to Three.js workflow tips and tricks] by [link:https://github.com/BKcore BKcore] -
        • -
        • - [link:http://12devsofxmas.co.uk/2012/01/webgl-and-three-js/ A whirlwind look at Three.js] - by [link:http://github.com/nrocy Paul King] -
        • -
        • - [link:http://bkcore.com/blog/3d/webgl-three-js-animated-selective-glow.html Animated selective glow in Three.js] - by [link:https://github.com/BKcore BKcore] -
        • -
        • - [link:http://www.natural-science.or.jp/article/20120220155529.php Building A Physics Simulation Environment] - three.js учебное пособие на японском языке -
        • -
        - - - diff --git a/docs/manual/ru/introduction/WebGL-compatibility-check.html b/docs/manual/ru/introduction/WebGL-compatibility-check.html deleted file mode 100644 index 9852f465884f8e..00000000000000 --- a/docs/manual/ru/introduction/WebGL-compatibility-check.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - -

        Проверка совместимости с WebGL ([name])

        -

        - Несмотря на то, что это становится все менее и менее серьезной проблемой, но все еще некоторые устройства или браузеры могут не поддерживать WebGL. - Следующий метод позволяет вам проверить, поддерживается ли он, и отобразить сообщение пользователю, если это не так. - Импортируйте модуль обнаружения поддержки WebGL и выполните следующее, прежде чем пытаться что-либо отобразить. -

        - - - import WebGL from 'three/addons/capabilities/WebGL.js'; - - if ( WebGL.isWebGLAvailable() ) { - - // Инициализируйте функцию или другие инициализации здесь - animate(); - - } else { - - const warning = WebGL.getWebGLErrorMessage(); - document.getElementById( 'container' ).appendChild( warning ); - - } - - - diff --git a/docs/manual/zh/introduction/Animation-system.html b/docs/manual/zh/introduction/Animation-system.html deleted file mode 100644 index ff040a28b7b51e..00000000000000 --- a/docs/manual/zh/introduction/Animation-system.html +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - -

        动画系统([name])

        - -

        概述

        - -

        - 在three.js动画系统中,您可以为模型的各种属性设置动画: - [page:SkinnedMesh](蒙皮和装配模型)的骨骼,morph targets(变形目标), - 不同的材料属性(颜色,不透明度,布尔运算),可见性和变换。动画属性可以淡入、淡出、交叉淡化和扭曲。 - 在相同或不同物体上同时发生的动画的权重和时间比例的变化可以独立地进行。 - 相同或不同物体的动画也可以同步发生。 -

        - 为了在一个同构系统中实现所有这一切, - three.js的动画系统[link:https://github.com/mrdoob/three.js/issues/6881 在2015年彻底改变](注意过时的信息!), - 它现在有一个与Unity/虚幻4引擎类似的架构。此页面简要阐述了这个系统中的主要组件以及它们如何协同工作。 -

        - -

        动画片段(Animation Clips)

        - -

        - 如果您已成功导入3D动画对象(无论它是否有骨骼或变形目标或两者皆有都不要紧)—— - 例如使用[link:https://github.com/KhronosGroup/glTF-Blender-IO glTF Blender exporter](glTF Blender导出器) - 从Blender导出它并使用[page:GLTFLoader]将其加载到three.js场景中 - —— 其中一个响应字段应该是一个名为“animations”的数组, - 其中包含此模型的[page:AnimationClip AnimationClips](请参阅下面可用的加载器列表)。 -

        - 每个*AnimationClip*通常保存对象某个活动的数据。 - 举个例子,假如mesh是一个角色,可能有一个AnimationClip实现步行循环, - 第二个AnimationClip实现跳跃,第三个AnimationClip实现闪避等等。 -

        - -

        关键帧轨道(Keyframe Tracks)

        - -

        - 在这样的*AnimationClip*里面,每个动画属性的数据都存储在一个单独的[page:KeyframeTrack]中。 - 假设一个角色对象有[page:Skeleton](骨架), - 一个关键帧轨道可以存储下臂骨骼位置随时间变化的数据, - 另一个轨道追踪同一块骨骼的旋转变化,第三个追踪另外一块骨骼的位置、转角和尺寸,等等。 - 应该很清楚,AnimationClip可以由许多这样的轨道组成。 -

        - 假设模型具有morph Targets(变形目标)—— - 例如一个变形目标显示一个笑脸,另一个显示愤怒的脸 —— - 每个轨道都持有某个变形目标在AnimationClip运行期间产生的[page:Mesh.morphTargetInfluences](变形目标影响)如何变化的信息。 -

        - -

        动画混合器(Animation Mixer)

        - -

        - 存储的数据仅构成动画的基础 —— 实际播放由[page:AnimationMixer]控制。 - 你可以想象这不仅仅是动画的播放器,而是作为硬件的模拟,如真正的调音台,可以同时控制和混合若干动画。 -

        - -

        动画行为(Animation Actions)

        - -

        - *AnimationMixer*本身只有很少的(大体上)属性和方法, - 因为它可以通过[page:AnimationAction AnimationActions]来控制。 - 通过配置*AnimationAction*,您可以决定何时播放、暂停或停止其中一个混合器中的某个*AnimationClip*, - 这个*AnimationClip*是否需要重复播放以及重复的频率, - 是否需要使用淡入淡出或时间缩放,以及一些其他内容(例如交叉渐变和同步)。 -

        - -

        动画对象组(Animation Object Groups)

        - -

        - 如果您希望一组对象接收共享的动画状态,则可以使用[page:AnimationObjectGroup]。 -

        - -

        支持的格式和加载器(Supported Formats and Loaders)

        - -

        - 请注意,并非所有模型格式都包含动画(尤其是OBJ,没有), - 而且只有某些three.js加载器支持[page:AnimationClip AnimationClip]序列。 - 以下几个确实支持此动画类型: -

        - -
          -
        • [page:ObjectLoader THREE.ObjectLoader]
        • -
        • THREE.BVHLoader
        • -
        • THREE.ColladaLoader
        • -
        • THREE.FBXLoader
        • -
        • [page:GLTFLoader THREE.GLTFLoader]
        • -
        • THREE.MMDLoader
        • -
        - -

        - 请注意,3ds max和Maya当前无法直接导出多个动画(这意味着动画不是在同一时间线上)到一个文件中。 -

        - -

        范例

        - - - let mesh; - - // 新建一个AnimationMixer, 并取得AnimationClip实例列表 - const mixer = new THREE.AnimationMixer( mesh ); - const clips = mesh.animations; - - // 在每一帧中更新mixer - function update () { - mixer.update( deltaSeconds ); - } - - // 播放一个特定的动画 - const clip = THREE.AnimationClip.findByName( clips, 'dance' ); - const action = mixer.clipAction( clip ); - action.play(); - - // 播放所有动画 - clips.forEach( function ( clip ) { - mixer.clipAction( clip ).play(); - } ); - - - - diff --git a/docs/manual/zh/introduction/Creating-a-scene.html b/docs/manual/zh/introduction/Creating-a-scene.html deleted file mode 100644 index 2af530429d0ed3..00000000000000 --- a/docs/manual/zh/introduction/Creating-a-scene.html +++ /dev/null @@ -1,165 +0,0 @@ - - - - - - - - - -

        创建一个场景([name])

        - -

        这一部分将对three.js来做一个简要的介绍;我们将开始搭建一个场景,其中包含一个正在旋转的立方体。页面下方有一个已经完成的例子,当你遇到麻烦,或者需要帮助的时候,可以看一看。

        - -

        开始之前

        -

        - 在开始使用three.js之前,你需要一个地方来显示它。将下列HTML代码保存为你电脑上的一个HTML文件然后在你的浏览器中打开这个HTML文件。 -

        - - <!DOCTYPE html> - <html> - <head> - <meta charset="utf-8"> - <title>My first three.js app</title> - <style> - body { margin: 0; } - </style> - </head> - <body> - <script type="module"> - import * as THREE from 'https://unpkg.com/three/build/three.module.js'; - - // Our Javascript will go here. - </script> - </body> - </html> - - -

        好了,接下来的所有代码将会写入到空的<script>标签中。

        - -

        创建一个场景

        - -

        为了真正能够让你的场景借助three.js来进行显示,我们需要以下几个对象:场景、相机和渲染器,这样我们就能透过摄像机渲染出场景。

        - - - const scene = new THREE.Scene(); - const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); - - const renderer = new THREE.WebGLRenderer(); - renderer.setSize( window.innerWidth, window.innerHeight ); - document.body.appendChild( renderer.domElement ); - - -

        我们花一点点时间来解释一下这里发生了什么。我们现在建立了场景、相机和渲染器。

        - -

        three.js里有几种不同的相机,在这里,我们使用的是PerspectiveCamera(透视摄像机)。

        - -

        第一个参数是视野角度(FOV)。视野角度就是无论在什么时候,你所能在显示器上看到的场景的范围,它的单位是角度(与弧度区分开)。

        - -

        第二个参数是长宽比(aspect ratio)。 也就是你用一个物体的宽除以它的高的值。比如说,当你在一个宽屏电视上播放老电影时,可以看到图像仿佛是被压扁的。

        - -

        接下来的两个参数是近截面(near)和远截面(far)。 当物体某些部分比摄像机的远截面远或者比近截面近的时候,该这些部分将不会被渲染到场景中。或许现在你不用担心这个值的影响,但未来为了获得更好的渲染性能,你将可以在你的应用程序里去设置它。

        - -

        接下来是渲染器。这里是施展魔法的地方。除了我们在这里用到的WebGLRenderer渲染器之外,Three.js同时提供了其他几种渲染器,当用户所使用的浏览器过于老旧,或者由于其他原因不支持WebGL时,可以使用这几种渲染器进行降级。

        - -

        除了创建一个渲染器的实例之外,我们还需要在我们的应用程序里设置一个渲染器的尺寸。比如说,我们可以使用所需要的渲染区域的宽高,来让渲染器渲染出的场景填充满我们的应用程序。因此,我们可以将渲染器宽高设置为浏览器窗口宽高。对于性能比较敏感的应用程序来说,你可以使用setSize传入一个较小的值,例如window.innerWidth/2window.innerHeight/2,这将使得应用程序在渲染时,以一半的长宽尺寸渲染场景。

        - -

        如果你希望保持你的应用程序的尺寸,但是以较低的分辨率来渲染,你可以在调用setSize时,将updateStyle(第三个参数)设为false。例如,假设你的<canvas> 标签现在已经具有了100%的宽和高,调用setSize(window.innerWidth/2, window.innerHeight/2, false)将使得你的应用程序以一半的分辨率来进行渲染。

        - -

        最后一步很重要,我们将renderer(渲染器)的dom元素(renderer.domElement)添加到我们的HTML文档中。这就是渲染器用来显示场景给我们看的<canvas>元素。

        - -

        “嗯,看起来很不错,那你说的那个立方体在哪儿?”接下来,我们就来添加立方体。

        - - - const geometry = new THREE.BoxGeometry( 1, 1, 1 ); - const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); - const cube = new THREE.Mesh( geometry, material ); - scene.add( cube ); - - camera.position.z = 5; - - -

        要创建一个立方体,我们需要一个BoxGeometry(立方体)对象. 这个对象包含了一个立方体中所有的顶点(vertices)和面(faces)。未来我们将在这方面进行更多的探索。

        - -

        接下来,对于这个立方体,我们需要给它一个材质,来让它有颜色。Three.js自带了几种材质,在这里我们使用的是MeshBasicMaterial。所有的材质都存有应用于他们的属性的对象。在这里为了简单起见,我们只设置一个color属性,值为0x00ff00,也就是绿色。这里所做的事情,和在CSS或者Photoshop中使用十六进制(hex colors)颜色格式来设置颜色的方式一致。

        - -

        第三步,我们需要一个Mesh(网格)。 网格包含一个几何体以及作用在此几何体上的材质,我们可以直接将网格对象放入到我们的场景中,并让它在场景中自由移动。

        - -

        默认情况下,当我们调用scene.add()的时候,物体将会被添加到(0,0,0)坐标。但将使得摄像机和立方体彼此在一起。为了防止这种情况的发生,我们只需要将摄像机稍微向外移动一些即可。

        - -

        渲染场景

        - -

        现在,如果将之前写好的代码复制到HTML文件中,你不会在页面中看到任何东西。这是因为我们还没有对它进行真正的渲染。为此,我们需要使用一个被叫做“渲染循环”(render loop)或者“动画循环”(animate loop)的东西。

        - - - function animate() { - requestAnimationFrame( animate ); - renderer.render( scene, camera ); - } - animate(); - - -

        在这里我们创建了一个使渲染器能够在每次屏幕刷新时对场景进行绘制的循环(在大多数屏幕上,刷新率一般是60次/秒)。如果你是一个浏览器游戏开发的新手,你或许会说“为什么我们不直接用setInterval来实现刷新的功能呢?”当然啦,我们的确可以用setInterval,但是,requestAnimationFrame有很多的优点。最重要的一点或许就是当用户切换到其它的标签页时,它会暂停,因此不会浪费用户宝贵的处理器资源,也不会损耗电池的使用寿命。

        - -

        使立方体动起来

        - -

        - 在开始之前,如果你已经将上面的代码写入到了你所创建的文件中,你可以看到一个绿色的方块。让我们来做一些更加有趣的事 —— 让它旋转起来。

        - -

        将下列代码添加到animate()函数中renderer.render调用的上方:

        - - - cube.rotation.x += 0.01; - cube.rotation.y += 0.01; - - -

        这段代码每帧都会执行(正常情况下是60次/秒),这就让立方体有了一个看起来很不错的旋转动画。基本上来说,当应用程序运行时,如果你想要移动或者改变任何场景中的东西,都必须要经过这个动画循环。当然,你可以在这个动画循环里调用别的函数,这样你就不会写出有上百行代码的animate函数。

        - -

        结果

        -

        祝贺你!你现在已经成功完成了你的第一个Three.js应用程序。虽然它很简单,但现在你已经有了一个入门的起点。

        - -

        下面是完整的代码,可在[link:https://jsfiddle.net/0c1oqf38/ live example]运行、编辑;运行或者修改代码有助于你更好的理解它是如何工作的。

        - - - <html> - <head> - <meta charset="utf-8"> - <title>My first three.js app</title> - <style> - body { margin: 0; } - </style> - </head> - <body> - <script type="module"> - import * as THREE from 'https://unpkg.com/three/build/three.module.js'; - - const scene = new THREE.Scene(); - const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); - - const renderer = new THREE.WebGLRenderer(); - renderer.setSize( window.innerWidth, window.innerHeight ); - document.body.appendChild( renderer.domElement ); - - const geometry = new THREE.BoxGeometry( 1, 1, 1 ); - const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); - const cube = new THREE.Mesh( geometry, material ); - scene.add( cube ); - - camera.position.z = 5; - - function animate() { - requestAnimationFrame( animate ); - - cube.rotation.x += 0.01; - cube.rotation.y += 0.01; - - renderer.render( scene, camera ); - } - - animate(); - </script> - </body> - </html> - - - diff --git a/docs/manual/zh/introduction/Creating-text.html b/docs/manual/zh/introduction/Creating-text.html deleted file mode 100644 index 52937f27ff21df..00000000000000 --- a/docs/manual/zh/introduction/Creating-text.html +++ /dev/null @@ -1,109 +0,0 @@ - - - - - - - - - -

        创建文字([name])

        -
        -

        有时候,您可能需要在你的Three.js应用程序中使用到文本,这里有几种方法可以做到。 -

        -
        - -

        1. DOM + CSS

        -
        -

        - 使用HTML通常是最简单、最快速的添加文本的方法,这是大多数的Three.js示例中用于添加描述性叠加文字的方法。 -

        -

        你可以在这里添加内容

        - <div id="info">Description</div> - -

        - 然后使用CSS来将其绝对定位在其它具有z-index的元素之上,尤其是当你全屏运行three.js的时候。 -

        - - -#info { - position: absolute; - top: 10px; - width: 100%; - text-align: center; - z-index: 100; - display:block; -} - - -
        - - - -

        2. 将文字绘制到画布中,并将其用作[page:Texture](纹理)

        -
        -

        如果你希望在three.js的场景中的平面上轻松地绘制文本,请使用此方法。

        -
        - - -

        3. 在你所喜欢的3D软件里创建模型,并导出给three.js -

        -
        -

        如果你更喜欢使用3D建模软件来工作并导出模型到three.js,请使用这种方法。

        -
        - - - -

        4. three.js自带的文字几何体

        -
        -

        - 如果你更喜欢使用纯three.js来工作,或者创建能够由程序改变的、动态的3D文字,你可以创建一个其几何体为THREE.TextGeometry的实例的网格: -

        -

        - new THREE.TextGeometry( text, parameters ); -

        -

        - 然而,为了使得它能够工作,你的TextGeometry需要在其“font”参数上设置一个THREE.Font的实例。 -
        - 请参阅 [page:TextGeometry] 页面来阅读如何完成此操作的详细信息,以及每一个接收的参数的描述,还有由three.js分发、自带的JSON字体的列表。 -

        - -

        示例

        -

        - [example:webgl_geometry_text WebGL / geometry / text]
        - [example:webgl_shadowmap WebGL / shadowmap] -

        - -

        - 如果Typeface已经关闭,或者没有你所想使用的字体,这有一个教程:[link:http://www.jaanga.com/2012/03/blender-to-threejs-create-3d-text-with.html]
        - 这是一个在blender上运行的python脚本,能够让你将文字导出为Three.js的JSON格式。 -

        - -
        - - - -

        5. 位图字体

        -
        -

        - BMFonts (位图字体) 可以将字形批处理为单个BufferGeometry。BMFont的渲染支持自动换行、字母间距、字句调整、signed distance fields with standard derivatives、multi-channel signed distance fields、多纹理字体等特性。 - 详情请参阅 [link:https://github.com/felixmariotto/three-mesh-ui three-mesh-ui] 或 [link:https://github.com/Jam3/three-bmfont-text three-bmfont-text]。 -

        -

        - 现有库存的字体在项目中同样可用,就像[link:https://github.com/etiennepinchon/aframe-fonts A-Frame Fonts]一样, - 或者你也可以从任何TTF字体中创建你自己的字体,优化时,只需要包含项目中所需的字符即可。 -

        -

        - 这是一些有用的工具: -

        -
          -
        • [link:http://msdf-bmfont.donmccurdy.com/ msdf-bmfont-web] (web-based)
        • -
        • [link:https://github.com/soimy/msdf-bmfont-xml msdf-bmfont-xml] (commandline)
        • -
        • [link:https://github.com/libgdx/libgdx/wiki/Hiero hiero] (desktop app)
        • -
        -
        - - - - - diff --git a/docs/manual/zh/introduction/Drawing-lines.html b/docs/manual/zh/introduction/Drawing-lines.html deleted file mode 100644 index 8424f0afbdbaec..00000000000000 --- a/docs/manual/zh/introduction/Drawing-lines.html +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - -

        画线([name])

        -
        -

        - 假设你将要画一个圆或者画一条线,而不是一个线框模型,或者说不是一个[page:Mesh](网格)。 - 第一步我们要做的,是设置好[page:WebGLRenderer renderer](渲染器)、[page:Scene scene](场景)和[page:Camera camera](相机)-(如果对这里所提到的东西,还不了解,请阅读本手册第一章“创建一个场景 - Creating a scene”)。 -

        - -

        这是我们将要用到的代码:

        - -const renderer = new THREE.WebGLRenderer(); -renderer.setSize( window.innerWidth, window.innerHeight ); -document.body.appendChild( renderer.domElement ); - -const camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 500 ); -camera.position.set( 0, 0, 100 ); -camera.lookAt( 0, 0, 0 ); - -const scene = new THREE.Scene(); - -

        - 接下来我们要做的事情是定义一个材质。对于线条来说,我们能使用的材质只有[page:LineBasicMaterial] 或者 [page:LineDashedMaterial]。 -

        - -//create a blue LineBasicMaterial -const material = new THREE.LineBasicMaterial( { color: 0x0000ff } ); - - -

        - 定义好材质之后,我们需要一个带有一些顶点的geometry(几何体)。 -

        - - -const points = []; -points.push( new THREE.Vector3( - 10, 0, 0 ) ); -points.push( new THREE.Vector3( 0, 10, 0 ) ); -points.push( new THREE.Vector3( 10, 0, 0 ) ); - -const geometry = new THREE.BufferGeometry().setFromPoints( points ); - - -

        注意,线是画在每一对连续的顶点之间的,而不是在第一个顶点和最后一个顶点之间绘制线条(线条并未闭合)。

        - -

        既然我们已经有了能够画两条线的点和一个材质,那我们现在就可以将他们组合在一起,形成一条线。

        - -const line = new THREE.Line( geometry, material ); - -

        剩下的事情就是把它添加到场景中,并调用[page:WebGLRenderer.render render](渲染)函数。

        - - -scene.add( line ); -renderer.render( scene, camera ); - - -

        你现在应当已经看到了一个由两条蓝线组成的、指向上的箭头。

        -
        - - diff --git a/docs/manual/zh/introduction/FAQ.html b/docs/manual/zh/introduction/FAQ.html deleted file mode 100644 index 64f5ae228f9c97..00000000000000 --- a/docs/manual/zh/introduction/FAQ.html +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - -

        常见问题([name])

        - -

        哪一种三维物体格式能够得到最好地支持?

        -
        -

        - 推荐使用glTF(gl传输格式)来对三维物体进行导入和导出,由于glTF这种格式专注于在程序运行时呈现三维物体,因此它的传输效率非常高,且加载速度非常快。 -

        - - -

        three.js同样也为其它广受欢迎的格式(例如FBX、Collada以及OBJ等)提供了载入工具。即便如此,你应当还是首先尝试着在你的项目中建立一个基于glTF的工作流程。 - 了解更多详细信息,请查看[link:#manual/introduction/Loading-3D-models loading 3D models]。 -

        -
        - -

        为什么在示例中会有一些和viewport相关的meta标签?

        -
        - <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> - -

        这些标签用于在移动端浏览器上控制视口的大小和缩放(页面内容可能会以与可视区域不同的大小来呈现)。

        - -

        [link:https://developer.apple.com/library/content/documentation/AppleApplications/Reference/SafariWebContent/UsingtheViewport/UsingtheViewport.html Safari: Using the Viewport]

        - -

        [link:https://developer.mozilla.org/en-US/docs/Web/HTML/Viewport_meta_tag MDN: Using the viewport meta tag]

        -
        - -

        如何在窗口调整大小时保持场景比例不变?

        -

        - 我们希望所有的物体,无论它们距离摄像机有多远,都能呈现相同尺寸,即使是在窗口被重新调整大小的时候。 - 解决这个问题的关键,是一个很重要的公式:给定距离,求可见高度 - -visible_height = 2 * Math.tan( ( Math.PI / 180 ) * camera.fov / 2 ) * distance_from_camera;
        -
        - 如果我们以一定的百分比增加了窗口的高度,那我们所想要的结果便是所有距离的可见高度都增加相同的百分比。 - 这并不能通过改变摄像机的位置来实现,相反,你得改变摄像机的视野角度(FOV)。这是个示例:[link:http://jsfiddle.net/Q4Jpu/ Example]. -

        - -

        为什么我的物体的一部分是不可见的?

        -

        - 这可能是由于面剔除而导致的。面是具有朝向的,这个朝向决定了哪边是正面或者哪边是背面。 - 在正常情况下,渲染时会将背面进行剔除。要查看这是不是你所遇到的问题,请将material的side更改为THREE.DoubleSide。 - material.side = THREE.DoubleSide -

        - -

        为什么有时候无效的输入会让three.js返回奇怪的结果?

        -

        - 出于性能考虑,大多数情况下 three.js 不验证输入。确保所有输入均有效是你的应用的责任。 -

        - - diff --git a/docs/manual/zh/introduction/How-to-create-VR-content.html b/docs/manual/zh/introduction/How-to-create-VR-content.html deleted file mode 100644 index 37d6c3c7b5c5a5..00000000000000 --- a/docs/manual/zh/introduction/How-to-create-VR-content.html +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - - - - - -

        如何创建VR内容([name])

        - -

        - 本指南简要介绍了使用three.js来制作的基于Web的VR应用程序的基本组件。 -

        - -

        工作流程

        - -

        - 首先,你需要将[link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/webxr/VRButton.js VRButton.js] - 包含到你的项目中。 -

        - - -import { VRButton } from 'three/addons/webxr/VRButton.js'; - - -

        *VRButton.createButton()*做了两件重要的事情:首先,它创建了一个按钮,指示了VR的兼容性; - 此外,若用户激活了这个按钮,则它将开启一个VR会话。 - 你所要做的唯一一件事情,便是把下面的这一行代码加入到你的应用程序里。 -

        - - -document.body.appendChild( VRButton.createButton( renderer ) ); - - -

        - 接下来,你需要告诉你的*WebGLRenderer*实例来启用XR渲染。 -

        - - -renderer.xr.enabled = true; - - -

        - 最后,你需要调整你的动画循环,因为在这里我们不能使用我们所熟知的 - *window.requestAnimationFrame()*函数来更新场景。对于VR项目来说,我们使用的是[page:WebGLRenderer.setAnimationLoop setAnimationLoop]。 - - 简短的示例代码如下: -

        - - -renderer.setAnimationLoop( function () { - - renderer.render( scene, camera ); - -} ); - - -

        接下来的步骤

        - -

        - 请查看官方示例中与WebVR相关的示例,了解这一工作流程的实际使用、运行情况。 -

        - - [example:webxr_vr_ballshooter WebXR / VR / ballshooter]
        - [example:webxr_vr_cubes WebXR / VR / cubes]
        - [example:webxr_vr_dragging WebXR / VR / dragging]
        - [example:webxr_vr_paint WebXR / VR / paint]
        - [example:webxr_vr_panorama_depth WebXR / VR / panorama_depth]
        - [example:webxr_vr_panorama WebXR / VR / panorama]
        - [example:webxr_vr_rollercoaster WebXR / VR / rollercoaster]
        - [example:webxr_vr_sandbox WebXR / VR / sandbox]
        - [example:webxr_vr_sculpt WebXR / VR / sculpt]
        - [example:webxr_vr_video WebXR / VR / video] -

        - - - - diff --git a/docs/manual/zh/introduction/How-to-dispose-of-objects.html b/docs/manual/zh/introduction/How-to-dispose-of-objects.html deleted file mode 100644 index 766f9e506572af..00000000000000 --- a/docs/manual/zh/introduction/How-to-dispose-of-objects.html +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - - - -

        如何废置对象([name])

        - -

        - 为了提高性能,并避免应用程序中的内存泄露,一个重要的方面是废置未使用的类库实体。 - 每当你创建一个*three.js*中的实例时,都会分配一定数量的内存。然而,*three.js*会创建在渲染中所必需的特定对象, - 例如几何体或材质,以及与WebGL相关的实体,例如buffers或着色器程序。 - 非常值得注意的是,这些对象并不会被自动释放;相反,应用程序必须使用特殊的API来释放这些资源。 - 本指南简要概述了这一API是如何使用的,以及哪些对象是和这一环境相关的。 -

        - -

        几何体

        - -

        - 几何体常用来表示定义为属性集合的顶点信息,*three.js*在内部为每一个属性创建一个[link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLBuffer WebGLBuffer]类型的对象。 - 这些实体仅有在调用[page:BufferGeometry.dispose]()的时候才会被删除。 - 如果应用程序中的几何体已废弃,请执行该方法以释放所有相关资源。 -

        - -

        材质

        - -

        - 材质定义了物体将如何被渲染。*three.js*使用材质所定义的信息来构造一个着色器程序,以用于渲染。 - 着色器程序只有在相应材质被废置后才能被删除。由于性能的原因,*three.js*尽可能尝试复用已存在的着色器程序。 - 因此,着色器程序只有在所有相关材质被废置后才被删除。 - 你可以通过执行[page:Material.dispose]()方法来废置材质。 -

        - -

        纹理

        - -

        - 对材质的废置不会对纹理造成影响。它们是分离的,因此一个纹理可以同时被多个材质所使用。 - 每当你创建一个[page:Texture]实例的时候,three.js在内部会创建一个[link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLTexture WebGLTexture]实例。 - 和buffer相似,该对象只能通过调用[page:Texture.dispose]()来删除。 -

        - -

        - If you use an *ImageBitmap* as the texture's data source, you have to call [link:https://developer.mozilla.org/en-US/docs/Web/API/ImageBitmap/close ImageBitmap.close]() at the application level to dispose of all CPU-side resources. - An automated call of *ImageBitmap.close()* in [page:Texture.dispose]() is not possible, since the image bitmap becomes unusable, and the engine has no way of knowing if the image bitmap is used elsewhere. -

        - -

        渲染目标

        - -

        - [page:WebGLRenderTarget]类型的对象不仅分配了[link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLTexture WebGLTexture]的实例, - 还分配了[link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLFramebuffer WebGLFramebuffer]和[link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderbuffer WebGLRenderbuffer]来实现自定义渲染目标。 - 这些对象仅能通过执行[page:WebGLRenderTarget.dispose]()来解除分配。 -

        - -

        杂项

        - -

        - 有一些来自examples目录的类,例如控制器或者后期处理过程,提供了*dispose()*方法以用于移除内部事件监听器或渲染目标。 - 通常来讲,非常建议查阅类的API或者文档,并注意*dispose()*函数。如果该函数存在的话,你应当在清理时使用它。 -

        - -

        常见问题

        - -

        为何*three.js*不能够自动废置对象?

        - -

        - 这一问题在社区中多次被问到,因此澄清这件事情是十分有必要的。事实是,*three.js*并不知道用户所创建的实体(例如几何体或者材质)的生命周期或作用范围,这些是应用程序的责任。 - 比如说,即使一个材质当前没有被用于渲染,但它也可能是下一帧所必需的。 - 因此,如果应用程序决定某个对象可以被删除,则它必须通过调用对应的*dispose()*方法来通知引擎。 -

        - -

        将一个mesh(网格)从场景中移除,是否也会废置它的geometry(几何体)和material(材质)?

        - -

        - 并不会,你必须通过*dispose()*来明确地废置geometry(几何体)或material(材质)。 - 请记住,geometry(几何体)或material(材质)可以在3D物体之间(例如mesh(网格))被共享。 -

        - -

        *three.js*是否会提供被缓存对象数量的相关信息?

        - -

        - 是的,可以评估[page:WebGLRenderer.info] —— 渲染器中的一个特殊属性,具有一系列关于显存和渲染过程的统计信息。 - 除此之外,它还告诉你有多少纹理、几何体和着色器程序在内部存储。 - 如果你在你的应用程序中注意到了性能问题,一个较好的方法便是调试该属性,以便轻松识别内存泄漏。 -

        - -

        当你在纹理还没有被加载时,在纹理上调用*dispose()*,会发生什么?

        - -

        - 对于纹理的内部资源仅在图像完全被加载后才会分配。如果你在图像被加载之前废置纹理,什么都不会发生。 - 没有资源被分配,因此也没有必要进行清理。 -

        - -

        当我在调用*dispose()*之后,使用相应的对象会发生什么?

        - -

        - 被删除掉的内部资源会被引擎重新创建,因此不会有运行时错误发生,但你可能会注意到这会对当前帧的性能有一些影响,特别是当着色器程序被编译的时候。 -

        - -

        我如何在我的应用程序中管理*three.js*中的对象?我如何知道什么时候该废置事物?

        - -

        - 一般来说,对此并没有明确的建议。调用*dispose()*什么时候合适,很大程度上取决于具体的用例。 - 必须指出的是,没有必要总是废置对象。一个较好的例子便是一个由多个关卡所组成的游戏。使用到对象废置的地方就是当切换关卡的时候。 - 应用程序可以通过较老的场景,并废置所有过时的材质、几何体和纹理贴图。 - 正如在前面的章节中所提到,如果你废置一个仍然在使用的对象,并不会导致运行时错误。可能发生的最糟糕的事情便是单帧的性能会下降。 -

        - -

        演示dispose()使用方法的示例

        - -

        - [example:webgl_test_memory WebGL / test / memory]
        - [example:webgl_test_memory2 WebGL / test / memory2]
        -

        - - - - diff --git a/docs/manual/zh/introduction/How-to-update-things.html b/docs/manual/zh/introduction/How-to-update-things.html deleted file mode 100644 index bdf69d3f45dd58..00000000000000 --- a/docs/manual/zh/introduction/How-to-update-things.html +++ /dev/null @@ -1,203 +0,0 @@ - - - - - - - - - -

        如何更新场景([name])

        -
        -

        默认情况下,所有对象都会自动更新它们的矩阵(如果它们已添加到场景中)

        - -const object = new THREE.Object3D(); -scene.add( object ); - - 或者它们是已添加到场景中的另一个对象的子节点: - -const object1 = new THREE.Object3D(); -const object2 = new THREE.Object3D(); - -object1.add( object2 ); -scene.add( object1 ); //object1 和 object2 会自动更新它们的矩阵 - -
        - -

        但是,如果你知道对象将是静态的,则可以禁用此选项并在需要时手动更新转换矩阵。

        - - -object.matrixAutoUpdate = false; -object.updateMatrix(); - - -

        BufferGeometry

        -
        -

        - BufferGeometries 将信息(例如顶点位置,面索引,法线,颜色,uv和任何自定义属性)存储在[page:BufferAttribute buffers] —— 也就是, - [link:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays typed arrays]. - 这使得它们通常比标准Geometries更快,缺点是更难用。 -

        -

        - 关于更新BufferGeometries,最重要的是理解你不能调整 buffers 大小(这种操作开销很大,相当于创建了个新的geometry)。 - 但你可以更新 buffers的内容。 -

        -

        - 这意味着如果你知道BufferGeometry的一个属性会增长,比如顶点的数量, - 你必须预先分配足够大的buffer来容纳可能创建的任何新顶点。 - 当然,这也意味着BufferGeometry将有一个最大大小 —— 无法创建一个可以高效地无限扩展的BufferGeometry。 -

        -

        - 我们以在渲染时扩展的line来示例。我们将分配可容纳500个顶点的空间但起初仅绘制2个,使用 - 在500个顶点的缓冲区中,但首先只使用 [page:BufferGeometry.drawRange]。 -

        - -const MAX_POINTS = 500; - -// geometry -const geometry = new THREE.BufferGeometry(); - -// attributes -const positions = new Float32Array( MAX_POINTS * 3 ); // 3 vertices per point -geometry.setAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) ); - -// draw range -const drawCount = 2; // draw the first 2 points, only -geometry.setDrawRange( 0, drawCount ); - -// material -const material = new THREE.LineBasicMaterial( { color: 0xff0000 } ); - -// line -const line = new THREE.Line( geometry, material ); -scene.add( line ); - -

        - 然后我们随机增加顶点到line中,以这样的一种方式: -

        - -const positionAttribute = line.geometry.getAttribute( 'position' ); - -let x = 0, y = 0, z = 0; - -for ( let i = 0; i < positionAttribute.count; i ++ ) { - - positionAttribute.setXYZ( i, x, y, z ); - - x += ( Math.random() - 0.5 ) * 30; - y += ( Math.random() - 0.5 ) * 30; - z += ( Math.random() - 0.5 ) * 30; - -} - -

        - 如果要更改第一次渲染后渲染的点数,执行以下操作: -

        - -line.geometry.setDrawRange( 0, newValue ); - -

        - 如果要在第一次渲染后更改position数值,则需要像这样设置needsUpdate标志: -

        - -positionAttribute.needsUpdate = true; // 需要加在第一次渲染之后 - - -

        - [link:https://jsfiddle.net/t4m85pLr/1/ 这个fiddle]展示了一个你可以参考的运动的line。 -

        - -

        例子

        -

        - [example:webgl_custom_attributes WebGL / custom / attributes]
        - [example:webgl_buffergeometry_custom_attributes_particles WebGL / buffergeometry / custom / attributes / particles] -

        - -
        - -

        材质(Materials)

        -
        -

        所有uniforms值都可以自由改变(比如 colors, textures, opacity 等等),这些数值在每帧都发给shader。

        - -

        GL状态相关参数也可以随时改变(depthTest, blending, polygonOffset 等)。

        - -

        在运行时无法轻松更改以下属性(一旦material被渲染了一次):

        -
          -
        • uniforms的数量和类型
        • -
        • 是否存在 -
            -
          • texture
          • -
          • fog
          • -
          • vertex colors
          • -
          • morphing
          • -
          • shadow map
          • -
          • alpha test
          • -
          • transparent
          • -
          -
        • -
        - -

        这些变化需要建立新的shader程序。你需要设置

        - material.needsUpdate = true - -

        请记住,这可能会非常缓慢并导致帧率的波动。(特别是在Windows上,因为shader编译在directx中比opengl慢)。

        - -

        为了获得更流畅的体验,您可以通过“虚拟”值(如零强度光,白色纹理或零密度雾)在一定程度上模拟这些功能的变化。

        - -

        您可以自由更改用于几何块的材质,但是无法更改对象如何划分为块(根据面材料)。

        - -

        如果你需要在运行时使用不同的材料配置:

        -

        如果材料/块的数量很少,您可以事先预先划分物体(例如,人的头发/脸部/身体/上衣/裤子,汽车的前部/侧面/顶部/玻璃/轮胎/内部)。

        - -

        如果数量很大(例如,每个面可能有所不同),请考虑不同的解决方案,例如使用属性/纹理来驱动不同的每个面部外观。

        - -

        例子

        -

        - [example:webgl_materials_car WebGL / materials / car]
        - [example:webgl_postprocessing_dof WebGL / webgl_postprocessing / dof] -

        -
        - - -

        纹理(Textures)

        -
        -

        如果更改了图像,画布,视频和数据纹理,则需要设置以下标志:

        - - texture.needsUpdate = true; - -

        渲染对象就会自动更新。

        - -

        例子

        -

        - [example:webgl_materials_video WebGL / materials / video]
        - [example:webgl_rtt WebGL / rtt] -

        -
        - -

        相机(Cameras)

        -
        -

        相机的位置和目标会自动更新。 如果你需要改变

        -
          -
        • - fov -
        • -
        • - aspect -
        • -
        • - near -
        • -
        • - far -
        • -
        -

        - 那么你需要重新计算投影矩阵: -

        - -camera.aspect = window.innerWidth / window.innerHeight; -camera.updateProjectionMatrix(); - -
        - - diff --git a/docs/manual/zh/introduction/How-to-use-post-processing.html b/docs/manual/zh/introduction/How-to-use-post-processing.html deleted file mode 100644 index 5007e574870c5d..00000000000000 --- a/docs/manual/zh/introduction/How-to-use-post-processing.html +++ /dev/null @@ -1,110 +0,0 @@ - - - - - - - - - -

        如何使用后期处理(How to use post-processing)

        - -

        - 很多three.js应用程序是直接将三维物体渲染到屏幕上的。 - 有时,你或许希望应用一个或多个图形效果,例如景深、发光、胶片微粒或是各种类型的抗锯齿。 - 后期处理是一种被广泛使用、用于来实现这些效果的方式。 - 首先,场景被渲染到一个渲染目标上,渲染目标表示的是一块在显存中的缓冲区。 - 接下来,在图像最终被渲染到屏幕之前,一个或多个后期处理过程将滤镜和效果应用到图像缓冲区。 -

        -

        - three.js通过[page:EffectComposer](效果合成器),提供了一个完整的后期处理解决方案来实现这样的工作流程。 -

        - -

        工作流程

        - -

        - 首先,我们要做的是从示例(examples)文件夹导入所有必需的文件。本指南假设你正在使用three.js官方npm包([link:https://www.npmjs.com/package/three npm package])。 - 在本指南的基础示例中,我们需要下列文件。 -

        - - - import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'; - import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'; - import { GlitchPass } from 'three/addons/postprocessing/GlitchPass.js'; - - -

        - 当这些文件被成功导入后,我们便可以通过传入一个[page:WebGLRenderer]的实例,来创建我们的合成器了。 -

        - - - const composer = new EffectComposer( renderer ); - - -

        - 在使用合成器时,我们需要对应用程序的动画循环进行更改。 - 现在我们不再调用[page:WebGLRenderer]的render方法,而是使用[page:EffectComposer]中对应的render方法。 -

        - - - function animate() { - - requestAnimationFrame( animate ); - - composer.render(); - - } - - -

        - 我们的合成器已经准备好了,现在我们就可以来配置后期处理过程链了。 - 这些过程负责创建应用程序的最终视觉输出,它们按照添加/插入的顺序来进行处理。 - 在我们的示例中,首先执行的是*RenderPass*实例,然后是*GlitchPass*。在链中的最后一个过程将自动被渲染到屏幕上。 - 这些过程的设置类似这样: -

        - - - const renderPass = new RenderPass( scene, camera ); - composer.addPass( renderPass ); - - const glitchPass = new GlitchPass(); - composer.addPass( glitchPass ); - - -

        - *RenderPass*通常位于过程链的开始,以便将渲染好的场景作为输入来提供给下一个后期处理步骤。 - 在我们的示例中,*GlitchPass*将会使用这些图像数据,来应用一个疯狂的故障效果。参见这个示例: - [link:https://threejs.org/examples/webgl_postprocessing_glitch live example]来看一看它的实际效果。 -

        - -

        内置过程

        - -

        - 你可以使用由本引擎提供的各种预定义好的后期处理过程, - 它们位于[link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm/postprocessing postprocessing]目录中。 -

        - -

        自定义过程

        - -

        - 有时你或许想要自己写一个自定义后期处理着色器,并将其包含到后期处理过程链中。 - 对于这个需求,你可以使用*ShaderPass*。在引入该文件以及你的自定义着色器后,可以使用下列代码来设置该过程: -

        - - - import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js'; - import { LuminosityShader } from 'three/addons/shaders/LuminosityShader.js'; - - // later in your init routine - - const luminosityPass = new ShaderPass( LuminosityShader ); - composer.addPass( luminosityPass ); - - -

        - 本仓库中提供了一个名为[link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/shaders/CopyShader.js CopyShader]的文件, - 这是你自定义自己的着色器的一个很好的起始代码。*CopyShader*仅仅是拷贝了读缓冲区中的图像内容到写缓冲区,不会应用任何效果。 -

        - - - diff --git a/docs/manual/zh/introduction/Installation.html b/docs/manual/zh/introduction/Installation.html deleted file mode 100644 index ca8829aee1623b..00000000000000 --- a/docs/manual/zh/introduction/Installation.html +++ /dev/null @@ -1,165 +0,0 @@ - - - - - - - - - -

        安装([name])

        - -

        - 你可以使用[link:https://www.npmjs.com/ npm]以及现代构建工具来安装 three.js ,也可以只需静态主机或是 CDN 来快速上手。对于大多数用户来说,从 npm 安装是最佳选择。 -

        - -

        - 无论你选择哪种方式,请始终保持一致,并注意从相同版本的库中导入所有文件。混合不同来源的文件可能会导致包含重复代码,甚至会以意料之外的方式破坏应用程序, -

        - -

        - 所有安装 three.js 的方式都依赖于 ES modules(参见 [link:https://eloquentjavascript.net/10_modules.html#h_hF2FmOVxw7 Eloquent JavaScript: ECMAScript Modules])。ES modules使你能够在最终项目中包含所需库的一小部分。 -

        - -

        安装自 npm

        - -

        - 要安装[link:https://www.npmjs.com/package/three three] 的 npm 模块,请在你的项目文件夹里打开终端窗口,并运行: -

        - - - npm install three - - -

        - 包将会被下载并安装。然后你就可以将它导入你的代码了: -

        - - - // 方式 1: 导入整个 three.js核心库 - import * as THREE from 'three'; - - const scene = new THREE.Scene(); - - - // 方式 2: 仅导入你所需要的部分 - import { Scene } from 'three'; - - const scene = new Scene(); - - -

        - 从npm上进行安装的时候,几乎总是使用某种构建工具([link:https://eloquentjavascript.net/10_modules.html#h_zWTXAU93DC bundling tool])来将你项目中需要的所有包组合到一个独立的JavaScript软件中。虽然任何现代的 JavaScript 打包器都可以和 three.js 一起使用,但最流行的选择是 [link:https://webpack.js.org/ webpack] 。 -

        - -

        - 并非所有功能都可以通过 three 模块来直接访问(也称为“裸导入”)。three.js 中其它较为流行的部分 —— 如控制器(control)、加载器(loader)以及后期处理效果(post-processing effect) —— 必须从 [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm examples/jsm] 子目录下导入。了解更多信息,请参阅下方的示例。 -

        - -

        - 你可以从 [link:https://eloquentjavascript.net/20_node.html#h_J6hW/SmL/a Eloquent JavaScript: Installing with npm] 来了解更多有关 npm 模块的信息。 -

        - -

        从CDN或静态主机安装

        - -

        - 通过将文件上传到你自己的服务器,或是使用一个已存在的CDN,three.js 便可以不借助任何构建系统来进行使用。由于 three.js 依赖于ES module,因此任何引用它的script标签必须使用type="module"。如下所示: -

        - - - <script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script> - - <script type="importmap"> - { - "imports": { - "three": "https://unpkg.com/three@<version>/build/three.module.js" - } - } - </script> - - <script type="module"> - - import * as THREE from 'three'; - - const scene = new THREE.Scene(); - - </script> - - -

        Addons

        - -

        - three.js的核心专注于3D引擎最重要的组件。其它很多有用的组件 —— 如控制器(control)、加载器(loader)以及后期处理效果(post-processing effect) —— 是 [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm examples/jsm] 目录的一部分。它们被称为“示例”,虽然你可以直接将它们拿来使用,但它们也需要重新混合以及定制。这些组件和 three.js 的核心保持同步,而 npm 上类似的第三方包则由不同的作者进行维护,可能不是最新的。 -

        - -

        - 示例无需被单独地安装,但需要被单独地导入。如果 three.js 是使用 npm 来安装的,你可以使用如下代码来加载轨道控制器([page:OrbitControls]): -

        - - - - import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; - - const controls = new OrbitControls( camera, renderer.domElement ); - - -

        - 如果 three.js 安装自一个 CDN ,请使用相同的 CDN 来安装其他组件: -

        - - - <script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script> - - <script type="importmap"> - { - "imports": { - "three": "https://unpkg.com/three@<version>/build/three.module.js", - "three/addons/": "https://unpkg.com/three@<version>/examples/jsm/" - } - } - </script> - - <script type="module"> - - import * as THREE from 'three'; - import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; - - const controls = new OrbitControls( camera, renderer.domElement ); - - </script> - - -

        - 所有文件使用相同版本是十分重要的。请勿从不同版本导入不同的示例,也不要使用与 three.js 本身版本不一致的示例。 -

        - -

        兼容性

        - -

        CommonJS 导入

        - -

        - 虽然现代的 JavaScript 打包器已经默认支持ES module,然而也有一些较旧的构建工具并不支持。对于这些情况,你或许可以对这些打包器进行配置,让它们能够理解 ES module 。例如,[link:http://browserify.org/ Browserify] 仅需 [link:https://github.com/babel/babelify babelify] 插件。 -

        - -

        Import maps

        - -

        - 和从静态主机或CDN来进行安装的方式相比,从npm安装时,导入的路径有所不同。我们意识到,对于使用两种不同方式的用户群体来说,这是一个人体工程学问题。使用构建工具与打包器的开发者更喜欢仅使用单独的包说明符(如'three')而非相对路径,而examples/目录中的文件使用相对于 three.module.js 的引用并不符合这一期望。对于不使用构建工具的人(用于快速原型、学习或个人参考)来说,或许也会很反感这些相对导入。这些相对导入需要确定目录结构,并且比全局 THREE.* 命名空间更不宽容。 -

        - -

        - 我们希望在 [link:https://github.com/WICG/import-maps import maps] 广泛可用时,能够移除这些相对路径,将它们替换为单独的包说明符,'three'。这更加符合构建工具对npm包的期望,且使得两种用户群体在导入文件时能够编写完全相同的代码。对于更偏向于避免使用构建工具的用户来说,一个简单的 JSON 映射即可将所有的导入都定向到一个 CDN 或是静态文件夹。通过实验,目前你可以通过一个 import map 的 polyfill,来尝试更简洁的导入,如 [link:https://glitch.com/edit/#!/three-import-map?path=index.html import map example] 示例中所示。 -

        - -

        Node.js

        - -

        - 由于 three.js 是为 Web 构建的, 因此它依赖于浏览器和 DOM 的 API ,但这些 API 在 Node.js 里不一定存在。这些问题中有的可以使用 [link:https://github.com/stackgl/headless-gl headless-gl] 等 shims 来解决,或者用自定义的替代方案来替换像 [page:TextureLoader] 这样的组件。其他 DOM API 可能与使用它们的代码强相关,因此将更难以解决。我们欢迎简单且易于维护的 pull request 来改进对 Node.js 的支持,但建议先打开问题来讨论您的改进。 -

        - -

        - 确保在您的 package.json 文件中添加 { "type": "module" },以在您的 Node.js 项目中启用 ES6 模块。 -

        - - - diff --git a/docs/manual/zh/introduction/Loading-3D-models.html b/docs/manual/zh/introduction/Loading-3D-models.html deleted file mode 100644 index 04d13d07664b83..00000000000000 --- a/docs/manual/zh/introduction/Loading-3D-models.html +++ /dev/null @@ -1,144 +0,0 @@ - - - - - - - - - - - -

        载入3D模型([name])

        - -

        - 目前,3D模型的格式有成千上万种可供选择,但每一种格式都具有不同的目的、用途以及复杂性。 - 虽然 - three.js已经提供了多种导入工具, - 但是选择正确的文件格式以及工作流程将可以节省很多时间,以及避免遭受很多挫折。某些格式难以使用,或者实时体验效率低下,或者目前尚未得到完全支持。 -

        - -

        - 对大多数用户,本指南向你推荐了一个工作流程,并向你提供了一些当没有达到预期效果时的建议。 -

        - -

        在开始之前

        - -

        - 如果你是第一次运行一个本地服务器,可以先阅读[link:#manual/introduction/Installation installation]。 - 正确地托管文件,可以避免很多查看3D模型时的常见错误。 -

        - -

        推荐的工作流程

        - -

        - 如果有可能的话,我们推荐使用glTF(gl传输格式)。.GLB.GLTF是这种格式的这两种不同版本, - 都可以被很好地支持。由于glTF这种格式是专注于在程序运行时呈现三维物体的,所以它的传输效率非常高,且加载速度非常快。 - 功能方面则包括了网格、材质、纹理、皮肤、骨骼、变形目标、动画、灯光和摄像机。 -

        - -

        - 公共领域的glTF文件可以在网上找到,例如 - - Sketchfab,或者很多工具包含了glTF的导出功能: -

        - - - -

        - 倘若你所喜欢的工具不支持glTF格式,请考虑向该工具的作者请求glTF导出功能, - 或者在the glTF roadmap thread贴出你的想法。 - -

        - -

        - 当glTF不可用的时候,诸如FBX、OBJ或者COLLADA等等其它广受欢迎的格式在Three.js中也是可以使用、并且定期维护的。 -

        - -

        加载

        - -

        - 在three.js中只会内置一部分加载器(例如:[page:ObjectLoader]) —— 其它的需要在你的应用中单独引入。 -

        - - - import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; - - -

        - 一旦你引入了一个加载器,你就已经准备好为场景添加模型了。不同加载器之间可能具有不同的语法 —— - 当使用其它格式的时候请参阅该格式加载器的示例以及文档。对于glTF,使用全局script的用法类似: -

        - - - const loader = new GLTFLoader(); - - loader.load( 'path/to/model.glb', function ( gltf ) { - - scene.add( gltf.scene ); - - }, undefined, function ( error ) { - - console.error( error ); - - } ); - - -

        - 请参阅[page:GLTFLoader GLTFLoader documentation]来深入了解详细信息。 -

        - -

        故障排除

        - -

        - 你花了几个小时亲手建了一个堪称杰作的模型,现在你把它给导入到网页中—— - 哦,天呐~😭它导入以后完全失真了、材质贴图丢了、或者说整个模型完全丢失了!
        - 接下来我们来按照下面的步骤排除故障: -

        - -
          -
        1. - 在Javascript的Console中查找错误,并确定当你调用.load()的时候,使用了onError回调函数来输出结果。 -
        2. -
        3. - 请在其它的应用程序中查看3D模型。对于glTF格式的模型来说,可以直接在下面的应用程序中进行查看: - three.js和 - babylon.js。 - 如果该模型能够在一个或者多个应用程序中正确地呈现,请点击这里向three.js提交Bug报告。 - 如果模型不能在任意一个应用程序里显示,我们强烈鼓励你向我们提交Bug报告,并告知我们你的模型是使用哪一款应用程序创建的。 - -
        4. -
        5. - 尝试将模型放大或缩小到原来的1000倍。许多模型的缩放比例各不相同,如果摄像机位于模型内,则大型模型将可能不会显示。 -
        6. -
        7. - 尝试添加一个光源并改变其位置。模型或许被隐藏在黑暗中。 -
        8. -
        9. - 在网络面板中查找失败的纹理贴图请求,比如说C:\\Path\To\Model\texture.jpg。载入贴图时,请使用相对于模型文件路径,例如 - images/texture.jpg —— 这或许需要在文本编辑器中来对模型文件进行修改。 -
        10. -
        - -

        请求帮助

        - -

        - 如果你已经尝试经历了以上故障排除的过程,但是你的模型仍然无法工作,寻求正确的方法来获得帮助将使您更快地获得解决方案。 -您可以将您的问题发布到three.js forum, - 同时,尽可能将你的模型(或者一个简单的、具有相同问题的模型)包含在你能够使用的任何格式中,为其他人提供足够的信息,以便快速重现这个问题 —— 最好是一个能够现场演示的Demo。 -

        - - - - diff --git a/docs/manual/zh/introduction/Matrix-transformations.html b/docs/manual/zh/introduction/Matrix-transformations.html deleted file mode 100644 index b43f4abc6dc0d7..00000000000000 --- a/docs/manual/zh/introduction/Matrix-transformations.html +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - -

        矩阵变换([name])

        - -

        - Three.js使用*matrix*编码3D变换 —— 平移(位置),旋转和缩放。 - [page:Object3D]的每个实例都有一个[page:Object3D.matrix matrix],用于存储该对象的位置,旋转和比例。本页介绍如何更新对象的变换。 -

        - -

        便利的属性和*matrixAutoUpdate*(Convenience properties and *matrixAutoUpdate*)

        - -

        - 有两种方法可以更新对象的转换: -

        -
          -
        1. - 修改对象的*position*,*quaternion*和*scale*属性,让three.js重新计算来自这些属性的对象矩阵: - -object.position.copy( start_position ); -object.quaternion.copy( quaternion ); - - 默认情况下,*matrixAutoUpdate*属性设置为true,并且将自动重新计算矩阵。 -如果对象是静态的,或者您希望在重新计算时手动控制,则可以通过将属性设置为false来获得更好的性能: - -object.matrixAutoUpdate = false; - - 更改任何属性后,手动更新矩阵: - -object.updateMatrix(); - -
        2. -
        3. - 直接修改对象的矩阵。 [page:Matrix4]类有各种修改矩阵的方法: - -object.matrix.setRotationFromQuaternion( quaternion ); -object.matrix.setPosition( start_position ); -object.matrixAutoUpdate = false; - - 请注意,在这种情况下,*matrixAutoUpdate* 必须 设置为*false*,并且您应该确保调用*updateMatrix*。 - 调用*updateMatrix*将破坏对矩阵所做的手动更改,从*position*,*scale*重新计算矩阵,依此类推。 -
        4. -
        - -

        对象和世界矩阵(Object and world matrices)

        -

        - 一个对象的[page:Object3D.matrix matrix]存储了该对象 相对于 其[page:Object3D.parent](父节点)的变换。要在 世界 坐标系中获取对象的转换,您必须访问该对象的[page:Object3D.matrixWorld]。 -

        -

        - 当父对象或子对象的变换发生更改时,可以通过调用[page:Object3D.updateMatrixWorld updateMatrixWorld()]来请求更新子对象的[page:Object3D.matrixWorld matrixWorld]。 -

        - -

        旋转和四元数(Rotation and Quaternion)

        -

        - Three.js提供了两种表示3D旋转的方式:[page:Euler Euler angles](欧拉角)和[page:Quaternion Quaternions](四元数),以及两者之间的转换方法。 欧拉角有称为“万向节锁定”的问题,其中某些配置可能失去一定程度的自由度(防止物体绕一个轴旋转)。 因此,对象旋转 始终 存储在对象的[page:Object3D.quaternion quaternion]中。 -

        -

        - 该库的早期版本包含*useQuaternion*属性,当设置为false时,将导致对象的[page:Object3D.matrix matrix]从欧拉角计算。这种做法已被弃用 - 作为代替,您应该使用[page:Object3D.setRotationFromEuler setRotationFromEuler]方法,该方法将更新四元数。 -

        - - - diff --git a/docs/manual/zh/introduction/Useful-links.html b/docs/manual/zh/introduction/Useful-links.html deleted file mode 100644 index 9dc1839b57a2aa..00000000000000 --- a/docs/manual/zh/introduction/Useful-links.html +++ /dev/null @@ -1,178 +0,0 @@ - - - - - - - - - -

        一些有用的链接([name])

        - -

        - 以下是一些在你学习three.js过程中,可能会对你有所帮助的链接。
        - 如果你想在此添加内容,或是认为下方某个链接不再相关或无法工作, - 请随时点击右下角“编辑”按钮来进行一些更改。

        - - 需要注意的是,由于three.js处于快速发展中,很多链接会包含过时的信息 —— - 如果某个地方不能够如你所想或如链接中所说的正常工作, - 请查看浏览器控制台中的警告和报错信息,同时也请参阅相关的文档页面。 - -

        - -

        帮助论坛

        -

        - Three.js官方使用[link:https://discourse.threejs.org/ forum](官方论坛) 和 [link:http://stackoverflow.com/tags/three.js/info Stack Overflow]来处理帮助请求。 - 如果你需要一些帮助,这才是你所要去的地方。请一定不要在GitHub上提issue来寻求帮助。 - -

        - -

        教程以及课程

        - -

        three.js入门

        -
          -
        • - [link:https://threejs.org/manual/#en/fundamentals Three.js Fundamentals starting lesson] -
        • -
        • - [link:https://codepen.io/rachsmith/post/beginning-with-3d-webgl-pt-1-the-scene Beginning with 3D WebGL] by [link:https://codepen.io/rachsmith/ Rachel Smith]. -
        • -
        • - [link:https://www.august.com.au/blog/animating-scenes-with-webgl-three-js/ Animating scenes with WebGL and three.js] -
        • -
        - -

        更加广泛、高级的文章与教程

        -
          -
        • - [link:https://discoverthreejs.com/ Discover three.js] -
        • -
        • - [link:http://blog.cjgammon.com/ Collection of tutorials] by [link:http://www.cjgammon.com/ CJ Gammon]. -
        • -
        • - [link:https://medium.com/soffritti.pierfrancesco/glossy-spheres-in-three-js-bfd2785d4857 Glossy spheres in three.js]. -
        • -
        • - [link:https://www.udacity.com/course/cs291 Interactive 3D Graphics] - a free course on Udacity that teaches the fundamentals of 3D Graphics, - and uses three.js as its coding tool. -
        • -
        • - [Link:https://aerotwist.com/tutorials/ Aerotwist] tutorials by [link:https://github.com/paullewis/ Paul Lewis]. -
        • -
        • - [link:https://discourse.threejs.org/t/three-js-bookshelf/2468 Three.js Bookshelf] - Looking for more resources about three.js or computer graphics in general? - Check out the selection of literature recommended by the community. -
        • -
        - -

        新闻与更新

        -
          -
        • - [link:https://twitter.com/hashtag/threejs Three.js on Twitter] -
        • -
        • - [link:http://www.reddit.com/r/threejs/ Three.js on reddit] -
        • -
        • - [link:http://www.reddit.com/r/webgl/ WebGL on reddit] -
        • -
        -

        示例

        -
          -
        • - [link:https://github.com/edwinwebb/three-seed/ three-seed] - three.js starter project with ES6 and Webpack -
        • -
        • - [link:http://stemkoski.github.io/Three.js/index.html Professor Stemkoskis Examples] - a collection of beginner friendly - examples built using three.js r60. -
        • -
        • - [link:https://threejs.org/examples/ Official three.js examples] - these examples are - maintained as part of the three.js repository, and always use the latest version of three.js. -
        • -
        • - [link:https://raw.githack.com/mrdoob/three.js/dev/examples/ Official three.js dev branch examples] - - Same as the above, except these use the dev branch of three.js, and are used to check that - everything is working as three.js being is developed. -
        • -
        - -

        工具

        -
          -
        • - [link:https://github.com/tbensky/physgl physgl.org] - JavaScript front-end with wrappers to three.js, to bring WebGL - graphics to students learning physics and math. -
        • -
        • - [link:https://whsjs.readme.io/ Whitestorm.js] – Modular three.js framework with AmmoNext physics plugin. -
        • - -
        • - [link:http://zz85.github.io/zz85-bookmarklets/threelabs.html Three.js Inspector] -
        • -
        • - [link:http://idflood.github.io/ThreeNodes.js/ ThreeNodes.js]. -
        • -
        • - [link:https://github.com/Alchemist0823/three.quarks three.quarks] - 针对 three.js 高速粒子特效系统 -
        • -
        • - [link:https://marketplace.visualstudio.com/items?itemName=slevesque.shader vscode shader] - Syntax highlighter for shader language. -
          - [link:https://marketplace.visualstudio.com/items?itemName=bierner.comment-tagged-templates vscode comment-tagged-templates] - Syntax highlighting for tagged template strings using comments to shader language, like: glsl.js. -
        • -
        • - [link:https://github.com/MozillaReality/WebXR-emulator-extension WebXR-emulator-extension] -
        • -
        - -

        WebGL参考

        -
          -
        • - [link:https://www.khronos.org/files/webgl/webgl-reference-card-1_0.pdf webgl-reference-card.pdf] - Reference of all WebGL and GLSL keywords, terminology, syntax and definitions. -
        • -
        - -

        较旧的链接

        -

        - 这些链接是出于历史目的而保留的,你或许可以发现它们仍然很有用,它们可能含有和three.js较为早前版本的有关的信息。 -

        - -
          -
        • - [link:https://www.youtube.com/watch?v=Dir4KO9RdhM AlterQualia at WebGL Camp 3] -
        • -
        • - [link:http://yomotsu.github.io/threejs-examples/ Yomotsus Examples] - a collection of examples using three.js r45. -
        • -
        • - [link:http://fhtr.org/BasicsOfThreeJS/#1 Introduction to Three.js] by [link:http://github.com/kig/ Ilmari Heikkinen] (slideshow). -
        • -
        • - [link:http://www.slideshare.net/yomotsu/webgl-and-threejs WebGL and Three.js] by [link:http://github.com/yomotsu Akihiro Oyamada] (slideshow). -
        • -
        • - [link:https://www.youtube.com/watch?v=VdQnOaolrPA Trigger Rally] by [link:https://github.com/jareiko jareiko] (video). -
        • -
        • - [link:http://blackjk3.github.io/threefab/ ThreeFab] - scene editor, maintained up until around three.js r50. -
        • -
        • - [link:http://bkcore.com/blog/3d/webgl-three-js-workflow-tips.html Max to Three.js workflow tips and tricks] by [link:https://github.com/BKcore BKcore] -
        • -
        • - [link:http://12devsofxmas.co.uk/2012/01/webgl-and-three-js/ A whirlwind look at Three.js] - by [link:http://github.com/nrocy Paul King] -
        • -
        • - [link:http://bkcore.com/blog/3d/webgl-three-js-animated-selective-glow.html Animated selective glow in Three.js] - by [link:https://github.com/BKcore BKcore] -
        • -
        • - [link:http://www.natural-science.or.jp/article/20120220155529.php Building A Physics Simulation Environment] - three.js tutorial in Japanese -
        • -
        - - - diff --git a/docs/manual/zh/introduction/WebGL-compatibility-check.html b/docs/manual/zh/introduction/WebGL-compatibility-check.html deleted file mode 100644 index 9fafed7a07d30e..00000000000000 --- a/docs/manual/zh/introduction/WebGL-compatibility-check.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - -

        WebGL兼容性检查([name])

        -

        - 虽然这个问题现在已经变得越来不明显,但不可否定的是,某些设备以及浏览器直到现在仍然不支持WebGL。
        以下的方法可以帮助你检测当前用户所使用的环境是否支持WebGL,如果不支持,将会向用户提示一条信息。 -

        - -

        - 请将[link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/capabilities/WebGL.js]引入到你的文件,并在尝试开始渲染之前先运行该文件。 -

        - - -if (WebGL.isWebGLAvailable()) { - // Initiate function or other initializations here - animate(); -} else { - const warning = WebGL.getWebGLErrorMessage(); - document.getElementById('container').appendChild(warning); -} - - - - - diff --git a/docs/page.css b/docs/page.css index 19241e2faa86d6..ac0ff16cfa0c0d 100644 --- a/docs/page.css +++ b/docs/page.css @@ -74,7 +74,8 @@ body.rtl ol, body.rtl table { direction: rtl !important; } -body.rtl code { +body.rtl code, +body.rtl .RtlTitle { direction: ltr !important; text-align: initial; } @@ -121,6 +122,10 @@ summary { margin-bottom: 16px; } +summary:hover { + cursor: pointer; +} + p { padding-right: 16px; } @@ -151,6 +156,10 @@ ul code { margin: 16px 0; } +code { + position: relative; +} + code.inline { display: inline-block; vertical-align: middle; @@ -160,6 +169,32 @@ code.inline { margin: 0; } +.copy-btn { + cursor: pointer; + position: absolute; + top: 16px; + right: 16px; + width: 24px; + height: 24px; + background-color: transparent; + background-image: url('../files/ic_copy_grey_24dp.svg'); + background-size: contain; + background-position: center; + background-repeat: no-repeat; + opacity: 0.9; + border: none; +} + +.copy-btn:hover { + opacity: 1; +} + +.copy-btn.copied { + pointer-events: none; + opacity: 1; + background-image: url('../files/ic_tick_green_24dp.svg'); +} + table { width: 100%; border-collapse: collapse; @@ -310,4 +345,10 @@ a.param:hover { line-height: 28px; } + .copy-btn { + top: 12px; + right: 8px; + width: 20px; + height: 20px; + } } diff --git a/docs/page.js b/docs/page.js index cfed647771301a..b561f5d8666557 100644 --- a/docs/page.js +++ b/docs/page.js @@ -66,8 +66,8 @@ function onDocumentLoad() { text = text.replace( /\[link:([\w\:\/\.\-\_\(\)\?\#\=\!\~]+)\]/gi, '$1' ); // [link:url] text = text.replace( /\[link:([\w:/.\-_()?#=!~]+) ([\w\p{L}:/.\-_'\s]+)\]/giu, '$2' ); // [link:url title] - text = text.replace( /\*([\w\d\"\-\(][\w\d\ \/\+\-\(\)\=\,\."]*[\w\d\"\)]|\w)\*/gi, '$1' ); // *text* - text = text.replace( /\`(.*?)\`/gi, '$1' ); // `code` + text = text.replace( /\*([\u4e00-\u9fa5\w\d\-\(\"\(\“][\u4e00-\u9fa5\w\d\ \/\+\-\(\)\=\,\.\(\)\,\。"]*[\u4e00-\u9fa5\w\d\"\)\”\)]|\w)\*/gi, '$1' ); // *text* + text = text.replace( /\`(.*?)\`/gs, '$1' ); // `code` text = text.replace( /\[example:([\w\_]+)\]/gi, '[example:$1 $1]' ); // [example:name] to [example:name title] text = text.replace( /\[example:([\w\_]+) ([\w\:\/\.\-\_ \s]+)\]/gi, '$2' ); // [example:name title] @@ -106,16 +106,66 @@ function onDocumentLoad() { // handle code snippets formatting + function dedent( text ) { + + // ignores singleline text + const lines = text.split( '\n' ); + if ( lines.length <= 1 ) return text; + + // ignores blank text + const nonBlankLine = lines.filter( l => l.trim() )[ 0 ]; + if ( nonBlankLine === undefined ) return text; + + // strips indents if any + const m = nonBlankLine.match( /^([\t ]+)/ ); + if ( m ) text = lines.map( l => l.startsWith( m[ 1 ] ) ? l.substring( m[ 1 ].length ) : l ).join( '\n' ); + + // strips leading and trailing whitespaces finally + return text.trim(); + + } + + // create copy button for copying code snippets + + function addCopyButton( element ) { + + const copyButton = document.createElement( 'button' ); + copyButton.className = 'copy-btn'; + + element.appendChild( copyButton ); + + copyButton.addEventListener( 'click', function () { + + const codeContent = element.textContent; + navigator.clipboard.writeText( codeContent ).then( () => { + + copyButton.classList.add( 'copied' ); + + setTimeout( () => { + + copyButton.classList.remove( 'copied' ); + + }, 1000 ); + + } ); + + } ); + + } + const elements = document.getElementsByTagName( 'code' ); for ( let i = 0; i < elements.length; i ++ ) { const element = elements[ i ]; - text = element.textContent.trim(); - text = text.replace( /^\t\t/gm, '' ); + element.textContent = dedent( element.textContent ); + + if ( ! element.classList.contains( 'inline' ) ) { - element.textContent = text; + addCopyButton( element ); + + } } @@ -155,13 +205,12 @@ function onDocumentLoad() { for ( let i = 0; i < elements.length; i ++ ) { const e = elements[ i ]; + e.currentStyle = { 'whiteSpace': 'pre-wrap' }; // Workaround for Firefox, see #30008 e.className += ' prettyprint'; e.setAttribute( 'translate', 'no' ); } - prettyPrint(); // eslint-disable-line no-undef - }; document.head.appendChild( prettify ); diff --git a/docs/prettify/prettify.js b/docs/prettify/prettify.js index eef5ad7e6a0767..8fc3e48451b6bb 100644 --- a/docs/prettify/prettify.js +++ b/docs/prettify/prettify.js @@ -1,28 +1,64 @@ -var q=null;window.PR_SHOULD_USE_CONTINUATION=!0; -(function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a= -[],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;ci[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m), -l=[],p={},d=0,g=e.length;d=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/, -q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/, -q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g, -"");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a), -a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e} -for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"], -"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"], -H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"], -J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+ -I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]), -["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css", -/^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}), -["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes", -hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p=0){var k=k.match(g),f,b;if(b= -!k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p=c?parseInt(e.substring(1),8):"u"===c||"x"===c?parseInt(e.substring(2),16):e.charCodeAt(1)}function f(e){if(32>e)return(16>e?"\\x0":"\\x")+e.toString(16);e=String.fromCharCode(e); +return"\\"===e||"-"===e||"]"===e||"^"===e?"\\"+e:e}function c(e){var c=e.substring(1,e.length-1).match(RegExp("\\\\u[0-9A-Fa-f]{4}|\\\\x[0-9A-Fa-f]{2}|\\\\[0-3][0-7]{0,2}|\\\\[0-7]{1,2}|\\\\[\\s\\S]|-|[^-\\\\]","g"));e=[];var a="^"===c[0],b=["["];a&&b.push("^");for(var a=a?1:0,h=c.length;ap||122p||90p||122m[0]&&(m[1]+1>m[0]&&b.push("-"),b.push(f(m[1])));b.push("]");return b.join("")}function g(e){for(var a=e.source.match(RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)", +"g")),b=a.length,d=[],h=0,m=0;h/,null])):d.push(["com",/^#[^\r\n]*/,null,"#"]));a.cStyleComments&&(f.push(["com",/^\/\/[^\r\n]*/,null]),f.push(["com",/^\/\*[\s\S]*?(?:\*\/|$)/, +null]));if(c=a.regexLiterals){var g=(c=1|\\/=?|::?|<>?>?=?|,|;|\\?|@|\\[|~|{|\\^\\^?=?|\\|\\|?=?|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*("+("/(?=[^/*"+c+"])(?:[^/\\x5B\\x5C"+c+"]|\\x5C"+g+"|\\x5B(?:[^\\x5C\\x5D"+c+"]|\\x5C"+g+")*(?:\\x5D|$))+/")+")")])}(c=a.types)&&f.push(["typ",c]);c=(""+a.keywords).replace(/^ | $/g,"");c.length&&f.push(["kwd", +new RegExp("^(?:"+c.replace(/[\s,]+/g,"|")+")\\b"),null]);d.push(["pln",/^\s+/,null," \r\n\t\u00a0"]);c="^.[^\\s\\w.$@'\"`/\\\\]*";a.regexLiterals&&(c+="(?!s*/)");f.push(["lit",/^@[a-z_$][a-z_$@0-9]*/i,null],["typ",/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],["pln",/^[a-z_$][a-z_$@0-9]*/i,null],["lit",/^(?:0x[a-f0-9]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+\-]?\d+)?)[a-z]*/i,null,"0123456789"],["pln",/^\\[\s\S]?/,null],["pun",new RegExp(c),null]);return E(d,f)}function B(a,d,f){function c(a){var b= +a.nodeType;if(1==b&&!r.test(a.className))if("br"===a.nodeName.toLowerCase())g(a),a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)c(a);else if((3==b||4==b)&&f){var e=a.nodeValue,d=e.match(n);d&&(b=e.substring(0,d.index),a.nodeValue=b,(e=e.substring(d.index+d[0].length))&&a.parentNode.insertBefore(q.createTextNode(e),a.nextSibling),g(a),b||a.parentNode.removeChild(a))}}function g(a){function c(a,b){var e=b?a.cloneNode(!1):a,p=a.parentNode;if(p){var p=c(p,1),d=a.nextSibling; +p.appendChild(e);for(var f=d;f;f=d)d=f.nextSibling,p.appendChild(f)}return e}for(;!a.nextSibling;)if(a=a.parentNode,!a)return;a=c(a.nextSibling,0);for(var e;(e=a.parentNode)&&1===e.nodeType;)a=e;b.push(a)}for(var r=/(?:^|\s)nocode(?:\s|$)/,n=/\r\n?|\n/,q=a.ownerDocument,k=q.createElement("li");a.firstChild;)k.appendChild(a.firstChild);for(var b=[k],t=0;t=+g[1],d=/\n/g,r=a.a,k=r.length,f=0,q=a.c,n=q.length,c=0,b=a.g,t=b.length,v=0;b[t]=k;var u,e;for(e=u=0;e=m&&(c+=2);f>=p&&(v+=2)}}finally{h&&(h.style.display=a)}}catch(y){Q.console&&console.log(y&&y.stack||y)}}var Q="undefined"!==typeof window?window:{},J=["break,continue,do,else,for,if,return,while"],K=[[J,"auto,case,char,const,default,double,enum,extern,float,goto,inline,int,long,register,restrict,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"], +"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],R=[K,"alignas,alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,delegate,dynamic_cast,explicit,export,friend,generic,late_check,mutable,namespace,noexcept,noreturn,nullptr,property,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],L=[K,"abstract,assert,boolean,byte,extends,finally,final,implements,import,instanceof,interface,null,native,package,strictfp,super,synchronized,throws,transient"], +M=[K,"abstract,add,alias,as,ascending,async,await,base,bool,by,byte,checked,decimal,delegate,descending,dynamic,event,finally,fixed,foreach,from,get,global,group,implicit,in,interface,internal,into,is,join,let,lock,null,object,out,override,orderby,params,partial,readonly,ref,remove,sbyte,sealed,select,set,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,value,var,virtual,where,yield"],K=[K,"abstract,async,await,constructor,debugger,enum,eval,export,from,function,get,import,implements,instanceof,interface,let,null,of,set,undefined,var,with,yield,Infinity,NaN"], +N=[J,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"],O=[J,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],J=[J,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],P=/^(DIR|FILE|array|vector|(de|priority_)?queue|(forward_)?list|stack|(const_)?(reverse_)?iterator|(unordered_)?(multi)?(set|map)|bitset|u?(int|float)\d*)\b/, +S=/\S/,T=v({keywords:[R,M,L,K,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",N,O,J],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),V={};n(T,["default-code"]);n(E([],[["pln",/^[^]*(?:>|$)/],["com",/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-", +/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),"default-markup htm html mxml xhtml xml xsl".split(" "));n(E([["pln",/^[\s]+/,null," \t\r\n"],["atv",/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/], +["pun",/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);n(E([],[["atv",/^[\s\S]+/]]),["uq.val"]);n(v({keywords:R,hashComments:!0,cStyleComments:!0,types:P}),"c cc cpp cxx cyc m".split(" "));n(v({keywords:"null,true,false"}),["json"]);n(v({keywords:M,hashComments:!0,cStyleComments:!0, +verbatimStrings:!0,types:P}),["cs"]);n(v({keywords:L,cStyleComments:!0}),["java"]);n(v({keywords:J,hashComments:!0,multiLineStrings:!0}),["bash","bsh","csh","sh"]);n(v({keywords:N,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}),["cv","py","python"]);n(v({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:2}), +["perl","pl","pm"]);n(v({keywords:O,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb","ruby"]);n(v({keywords:K,cStyleComments:!0,regexLiterals:!0}),["javascript","js","ts","typescript"]);n(v({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,throw,true,try,unless,until,when,while,yes",hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);n(E([],[["str",/^[\s\S]+/]]), +["regex"]);var U=Q.PR={createSimpleLexer:E,registerLangHandler:n,sourceDecorator:v,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:"com",PR_DECLARATION:"dec",PR_KEYWORD:"kwd",PR_LITERAL:"lit",PR_NOCODE:"nocode",PR_PLAIN:"pln",PR_PUNCTUATION:"pun",PR_SOURCE:"src",PR_STRING:"str",PR_TAG:"tag",PR_TYPE:"typ",prettyPrintOne:function(a,d,f){f=f||!1;d=d||null;var c=document.createElement("div");c.innerHTML="
        "+a+"
        ";c=c.firstChild;f&&B(c,f,!0);H({j:d,m:f,h:c,l:1,a:null,i:null,c:null,g:null}); +return c.innerHTML},prettyPrint:g=function(a,d){function f(){for(var c=Q.PR_SHOULD_USE_CONTINUATION?b.now()+250:Infinity;t - - - - - - - - + @@ -23,7 +36,7 @@ - + @@ -45,19 +58,6 @@ - - - - - - - - - - - - - - diff --git a/examples/index.html b/examples/index.html index b1fc84f264bae6..8e45e9de1a9201 100644 --- a/examples/index.html +++ b/examples/index.html @@ -16,7 +16,6 @@

        three.js

        - docs examples
        @@ -55,12 +54,9 @@

        three.js

        const panelScrim = document.getElementById( 'panelScrim' ); const previewsToggler = document.getElementById( 'previewsToggler' ); - const sectionLink = document.querySelector( '#sections > a' ); - const sectionDefaultHref = sectionLink.href; - const links = {}; const validRedirects = new Map(); - const container = document.createElement( 'div' ); + const fragment = document.createDocumentFragment(); let selected = null; @@ -68,8 +64,6 @@

        three.js

        async function init() { - content.appendChild( container ); - viewSrcButton.style.display = 'none'; const files = await ( await fetch( 'files.json' ) ).json(); @@ -82,14 +76,14 @@

        three.js

        const header = document.createElement( 'h2' ); header.textContent = key; header.setAttribute( 'data-category', key ); - container.appendChild( header ); + fragment.appendChild( header ); for ( let i = 0; i < category.length; i ++ ) { const file = category[ i ]; const link = createLink( file, tags[ file ] ); - container.appendChild( link ); + fragment.appendChild( link ); links[ file ] = link; validRedirects.set( file, file + '.html' ); @@ -98,6 +92,8 @@

        three.js

        } + content.appendChild( fragment ); + if ( window.location.hash !== '' ) { const file = window.location.hash.substring( 1 ); @@ -129,10 +125,6 @@

        three.js

        updateFilter( files, tags ); - } else { - - updateLink( '' ); - } // Events @@ -253,7 +245,9 @@

        three.js

        function escapeRegExp( string ) { - return string.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' ); // https://stackoverflow.com/a/6969486/5250847 + string = string.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' ); // https://stackoverflow.com/a/6969486/5250847 + + return '(?=.*' + string.split( ' ' ).join( ')(?=.*' ) + ')'; // match all words, in any order } @@ -288,25 +282,6 @@

        three.js

        layoutList( files ); - updateLink( v ); - - } - - function updateLink( search ) { - - // update docs link - - if ( search ) { - - const link = sectionLink.href.split( /[?#]/ )[ 0 ]; - sectionLink.href = `${link}?q=${search}`; - - } else { - - sectionLink.href = sectionDefaultHref; - - } - } function filterExample( file, exp, tags ) { diff --git a/examples/jsm/Addons.js b/examples/jsm/Addons.js new file mode 100644 index 00000000000000..f2d8fc7f16465b --- /dev/null +++ b/examples/jsm/Addons.js @@ -0,0 +1,286 @@ +export * from './animation/AnimationClipCreator.js'; +export * from './animation/CCDIKSolver.js'; + +export { default as WebGL } from './capabilities/WebGL.js'; + +export * from './controls/ArcballControls.js'; +export * from './controls/DragControls.js'; +export * from './controls/FirstPersonControls.js'; +export * from './controls/FlyControls.js'; +export * from './controls/MapControls.js'; +export * from './controls/OrbitControls.js'; +export * from './controls/PointerLockControls.js'; +export * from './controls/TrackballControls.js'; +export * from './controls/TransformControls.js'; + +export * from './csm/CSM.js'; +export * from './csm/CSMFrustum.js'; +export * from './csm/CSMHelper.js'; +export * from './csm/CSMShader.js'; + +export * as Curves from './curves/CurveExtras.js'; +export * from './curves/NURBSCurve.js'; +export * from './curves/NURBSSurface.js'; +export * from './curves/NURBSVolume.js'; +export * as NURBSUtils from './curves/NURBSUtils.js'; + +export * from './effects/AnaglyphEffect.js'; +export * from './effects/AsciiEffect.js'; +export * from './effects/OutlineEffect.js'; +export * from './effects/ParallaxBarrierEffect.js'; +export * from './effects/StereoEffect.js'; + +export * from './environments/DebugEnvironment.js'; +export * from './environments/RoomEnvironment.js'; + +export * from './exporters/DRACOExporter.js'; +export * from './exporters/EXRExporter.js'; +export * from './exporters/GLTFExporter.js'; +export * from './exporters/KTX2Exporter.js'; +export * from './exporters/OBJExporter.js'; +export * from './exporters/PLYExporter.js'; +export * from './exporters/STLExporter.js'; +export * from './exporters/USDZExporter.js'; + +export * from './geometries/BoxLineGeometry.js'; +export * from './geometries/ConvexGeometry.js'; +export * from './geometries/DecalGeometry.js'; +export * from './geometries/ParametricFunctions.js'; +export * from './geometries/ParametricGeometry.js'; +export * from './geometries/RoundedBoxGeometry.js'; +export * from './geometries/TeapotGeometry.js'; +export * from './geometries/TextGeometry.js'; + +export * from './helpers/LightProbeHelper.js'; +export * from './helpers/OctreeHelper.js'; +export * from './helpers/PositionalAudioHelper.js'; +export * from './helpers/RectAreaLightHelper.js'; +export * from './helpers/TextureHelper.js'; +export * from './helpers/VertexNormalsHelper.js'; +export * from './helpers/VertexTangentsHelper.js'; +export * from './helpers/ViewHelper.js'; + +export * from './interactive/HTMLMesh.js'; +export * from './interactive/InteractiveGroup.js'; +export * from './interactive/SelectionBox.js'; +export * from './interactive/SelectionHelper.js'; + +export * from './lights/LightProbeGenerator.js'; +export * from './lights/RectAreaLightTexturesLib.js'; +export * from './lights/RectAreaLightUniformsLib.js'; + +export * from './lines/Line2.js'; +export * from './lines/LineGeometry.js'; +export * from './lines/LineMaterial.js'; +export * from './lines/LineSegments2.js'; +export * from './lines/LineSegmentsGeometry.js'; +export * from './lines/Wireframe.js'; +export * from './lines/WireframeGeometry2.js'; + +export * from './loaders/3DMLoader.js'; +export * from './loaders/3MFLoader.js'; +export * from './loaders/AMFLoader.js'; +export * from './loaders/BVHLoader.js'; +export * from './loaders/ColladaLoader.js'; +export * from './loaders/DDSLoader.js'; +export * from './loaders/DRACOLoader.js'; +export * from './loaders/EXRLoader.js'; +export * from './loaders/FBXLoader.js'; +export * from './loaders/FontLoader.js'; +export * from './loaders/GCodeLoader.js'; +export * from './loaders/GLTFLoader.js'; +export * from './loaders/HDRCubeTextureLoader.js'; +export * from './loaders/IESLoader.js'; +export * from './loaders/KMZLoader.js'; +export * from './loaders/KTX2Loader.js'; +export * from './loaders/KTXLoader.js'; +export * from './loaders/LDrawLoader.js'; +export * from './loaders/LUT3dlLoader.js'; +export * from './loaders/LUTCubeLoader.js'; +export * from './loaders/LWOLoader.js'; +export * from './loaders/LottieLoader.js'; +export * from './loaders/MD2Loader.js'; +export * from './loaders/MDDLoader.js'; +export * from './loaders/MTLLoader.js'; +export * from './loaders/NRRDLoader.js'; +export * from './loaders/OBJLoader.js'; +export * from './loaders/PCDLoader.js'; +export * from './loaders/PDBLoader.js'; +export * from './loaders/PLYLoader.js'; +export * from './loaders/PVRLoader.js'; +export * from './loaders/RGBELoader.js'; +export * from './loaders/UltraHDRLoader.js'; +export * from './loaders/RGBMLoader.js'; +export * from './loaders/STLLoader.js'; +export * from './loaders/SVGLoader.js'; +export * from './loaders/TDSLoader.js'; +export * from './loaders/TGALoader.js'; +export * from './loaders/TIFFLoader.js'; +export * from './loaders/TTFLoader.js'; +export * from './loaders/USDZLoader.js'; +export * from './loaders/VOXLoader.js'; +export * from './loaders/VRMLLoader.js'; +export * from './loaders/VTKLoader.js'; +export * from './loaders/XYZLoader.js'; + +export * from './materials/MeshGouraudMaterial.js'; +export * from './materials/LDrawConditionalLineMaterial.js'; +export * from './materials/MeshPostProcessingMaterial.js'; + +export * from './math/Capsule.js'; +export * from './math/ColorConverter.js'; +export * from './math/ConvexHull.js'; +export * from './math/ImprovedNoise.js'; +export * from './math/Lut.js'; +export * from './math/MeshSurfaceSampler.js'; +export * from './math/OBB.js'; +export * from './math/Octree.js'; +export * from './math/SimplexNoise.js'; + +export * from './misc/ConvexObjectBreaker.js'; +export * from './misc/GPUComputationRenderer.js'; +export * from './misc/Gyroscope.js'; +export * from './misc/MD2Character.js'; +export * from './misc/MD2CharacterComplex.js'; +export * from './misc/MorphAnimMesh.js'; +export * from './misc/MorphBlendMesh.js'; +export * from './misc/ProgressiveLightMap.js'; +export * from './misc/RollerCoaster.js'; +export * from './misc/Timer.js'; +export * from './misc/TubePainter.js'; +export * from './misc/Volume.js'; +export * from './misc/VolumeSlice.js'; + +export * from './modifiers/CurveModifier.js'; +export * from './modifiers/EdgeSplitModifier.js'; +export * from './modifiers/SimplifyModifier.js'; +export * from './modifiers/TessellateModifier.js'; + +export * from './objects/GroundedSkybox.js'; +export * from './objects/Lensflare.js'; +export * from './objects/MarchingCubes.js'; +export * from './objects/Reflector.js'; +export * from './objects/ReflectorForSSRPass.js'; +export * from './objects/Refractor.js'; +export * from './objects/ShadowMesh.js'; +export * from './objects/Sky.js'; +export * from './objects/Water.js'; +export { Water as Water2 } from './objects/Water2.js'; + +export * from './physics/AmmoPhysics.js'; +export * from './physics/RapierPhysics.js'; + +export * from './postprocessing/AfterimagePass.js'; +export * from './postprocessing/BloomPass.js'; +export * from './postprocessing/BokehPass.js'; +export * from './postprocessing/ClearPass.js'; +export * from './postprocessing/CubeTexturePass.js'; +export * from './postprocessing/DotScreenPass.js'; +export * from './postprocessing/EffectComposer.js'; +export * from './postprocessing/FilmPass.js'; +export * from './postprocessing/GlitchPass.js'; +export * from './postprocessing/GTAOPass.js'; +export * from './postprocessing/HalftonePass.js'; +export * from './postprocessing/LUTPass.js'; +export * from './postprocessing/MaskPass.js'; +export * from './postprocessing/OutlinePass.js'; +export * from './postprocessing/OutputPass.js'; +export * from './postprocessing/Pass.js'; +export * from './postprocessing/RenderPass.js'; +export * from './postprocessing/RenderPixelatedPass.js'; +export * from './postprocessing/SAOPass.js'; +export * from './postprocessing/SMAAPass.js'; +export * from './postprocessing/SSAARenderPass.js'; +export * from './postprocessing/SSAOPass.js'; +export * from './postprocessing/SSRPass.js'; +export * from './postprocessing/SavePass.js'; +export * from './postprocessing/ShaderPass.js'; +export * from './postprocessing/TAARenderPass.js'; +export * from './postprocessing/TexturePass.js'; +export * from './postprocessing/UnrealBloomPass.js'; + +export * from './renderers/CSS2DRenderer.js'; +export * from './renderers/CSS3DRenderer.js'; +export * from './renderers/Projector.js'; +export * from './renderers/SVGRenderer.js'; + +export * from './shaders/ACESFilmicToneMappingShader.js'; +export * from './shaders/AfterimageShader.js'; +export * from './shaders/BasicShader.js'; +export * from './shaders/BleachBypassShader.js'; +export * from './shaders/BlendShader.js'; +export * from './shaders/BokehShader.js'; +export { BokehShader as BokehShader2 } from './shaders/BokehShader2.js'; +export * from './shaders/BrightnessContrastShader.js'; +export * from './shaders/ColorCorrectionShader.js'; +export * from './shaders/ColorifyShader.js'; +export * from './shaders/ConvolutionShader.js'; +export * from './shaders/CopyShader.js'; +export * from './shaders/DOFMipMapShader.js'; +export * from './shaders/DepthLimitedBlurShader.js'; +export * from './shaders/DigitalGlitch.js'; +export * from './shaders/DotScreenShader.js'; +export * from './shaders/ExposureShader.js'; +export * from './shaders/FXAAShader.js'; +export * from './shaders/FilmShader.js'; +export * from './shaders/FocusShader.js'; +export * from './shaders/FreiChenShader.js'; +export * from './shaders/GammaCorrectionShader.js'; +export * from './shaders/GodRaysShader.js'; +export * from './shaders/GTAOShader.js'; +export * from './shaders/HalftoneShader.js'; +export * from './shaders/HorizontalBlurShader.js'; +export * from './shaders/HorizontalTiltShiftShader.js'; +export * from './shaders/HueSaturationShader.js'; +export * from './shaders/KaleidoShader.js'; +export * from './shaders/LuminosityHighPassShader.js'; +export * from './shaders/LuminosityShader.js'; +export * from './shaders/MirrorShader.js'; +export * from './shaders/NormalMapShader.js'; +export * from './shaders/OutputShader.js'; +export * from './shaders/RGBShiftShader.js'; +export * from './shaders/SAOShader.js'; +export * from './shaders/SMAAShader.js'; +export * from './shaders/SSAOShader.js'; +export * from './shaders/SSRShader.js'; +export * from './shaders/SepiaShader.js'; +export * from './shaders/SobelOperatorShader.js'; +export * from './shaders/SubsurfaceScatteringShader.js'; +export * from './shaders/TechnicolorShader.js'; +export * from './shaders/ToonShader.js'; +export * from './shaders/TriangleBlurShader.js'; +export * from './shaders/UnpackDepthRGBAShader.js'; +export * from './shaders/VelocityShader.js'; +export * from './shaders/VerticalBlurShader.js'; +export * from './shaders/VerticalTiltShiftShader.js'; +export * from './shaders/VignetteShader.js'; +export * from './shaders/VolumeShader.js'; +export * from './shaders/WaterRefractionShader.js'; + +export * from './textures/FlakesTexture.js'; + +export * as BufferGeometryUtils from './utils/BufferGeometryUtils.js'; +export * as CameraUtils from './utils/CameraUtils.js'; +export * as GeometryCompressionUtils from './utils/GeometryCompressionUtils.js'; +export * as GeometryUtils from './utils/GeometryUtils.js'; +export * from './utils/LDrawUtils.js'; +export * as SceneUtils from './utils/SceneUtils.js'; +export * from './utils/ShadowMapViewer.js'; +export * as SkeletonUtils from './utils/SkeletonUtils.js'; +export * as SortUtils from './utils/SortUtils.js'; +export * from './utils/WebGLTextureUtils.js'; +export * from './utils/UVsDebug.js'; +export * from './utils/WorkerPool.js'; + +export * from './webxr/ARButton.js'; +export * from './webxr/OculusHandModel.js'; +export * from './webxr/OculusHandPointerModel.js'; +export * from './webxr/Text2D.js'; +export * from './webxr/VRButton.js'; +export * from './webxr/XRButton.js'; +export * from './webxr/XRControllerModelFactory.js'; +export * from './webxr/XREstimatedLight.js'; +export * from './webxr/XRHandMeshModel.js'; +export * from './webxr/XRHandModelFactory.js'; +export * from './webxr/XRHandPrimitiveModel.js'; +export * from './webxr/XRPlanes.js'; diff --git a/examples/jsm/animation/AnimationClipCreator.js b/examples/jsm/animation/AnimationClipCreator.js index cf9ee6bb425477..806f87ee3de8aa 100644 --- a/examples/jsm/animation/AnimationClipCreator.js +++ b/examples/jsm/animation/AnimationClipCreator.js @@ -7,8 +7,22 @@ import { VectorKeyframeTrack } from 'three'; +/** + * A utility class with factory methods for creating basic animation clips. + * + * @hideconstructor + * @three_import import { AnimationClipCreator } from 'three/addons/animation/AnimationClipCreator.js'; + */ class AnimationClipCreator { + /** + * Creates an animation clip that rotates a 3D object 360 degrees + * in the given period of time around the given axis. + * + * @param {number} period - The duration of the animation. + * @param {('x'|'y'|'z')} [axis='x'] - The axis of rotation. + * @return {AnimationClip} The created animation clip. + */ static CreateRotationAnimation( period, axis = 'x' ) { const times = [ 0, period ], values = [ 0, 360 ]; @@ -17,10 +31,18 @@ class AnimationClipCreator { const track = new NumberKeyframeTrack( trackName, times, values ); - return new AnimationClip( null, period, [ track ] ); + return new AnimationClip( '', period, [ track ] ); } + /** + * Creates an animation clip that scales a 3D object from `0` to `1` + * in the given period of time along the given axis. + * + * @param {number} period - The duration of the animation. + * @param {('x'|'y'|'z')} [axis='x'] - The axis to scale the 3D object along. + * @return {AnimationClip} The created animation clip. + */ static CreateScaleAxisAnimation( period, axis = 'x' ) { const times = [ 0, period ], values = [ 0, 1 ]; @@ -29,10 +51,18 @@ class AnimationClipCreator { const track = new NumberKeyframeTrack( trackName, times, values ); - return new AnimationClip( null, period, [ track ] ); + return new AnimationClip( '', period, [ track ] ); } + /** + * Creates an animation clip that translates a 3D object in a shake pattern + * in the given period. + * + * @param {number} duration - The duration of the animation. + * @param {Vector3} shakeScale - The scale of the shake. + * @return {AnimationClip} The created animation clip. + */ static CreateShakeAnimation( duration, shakeScale ) { const times = [], values = [], tmp = new Vector3(); @@ -51,10 +81,18 @@ class AnimationClipCreator { const track = new VectorKeyframeTrack( trackName, times, values ); - return new AnimationClip( null, duration, [ track ] ); + return new AnimationClip( '', duration, [ track ] ); } + /** + * Creates an animation clip that scales a 3D object in a pulse pattern + * in the given period. + * + * @param {number} duration - The duration of the animation. + * @param {number} pulseScale - The scale of the pulse. + * @return {AnimationClip} The created animation clip. + */ static CreatePulsationAnimation( duration, pulseScale ) { const times = [], values = [], tmp = new Vector3(); @@ -73,10 +111,16 @@ class AnimationClipCreator { const track = new VectorKeyframeTrack( trackName, times, values ); - return new AnimationClip( null, duration, [ track ] ); + return new AnimationClip( '', duration, [ track ] ); } + /** + * Creates an animation clip that toggles the visibility of a 3D object. + * + * @param {number} duration - The duration of the animation. + * @return {AnimationClip} The created animation clip. + */ static CreateVisibilityAnimation( duration ) { const times = [ 0, duration / 2, duration ], values = [ true, false, true ]; @@ -85,14 +129,22 @@ class AnimationClipCreator { const track = new BooleanKeyframeTrack( trackName, times, values ); - return new AnimationClip( null, duration, [ track ] ); + return new AnimationClip( '', duration, [ track ] ); } + /** + * Creates an animation clip that animates the `color` property of a 3D object's + * material. + * + * @param {number} duration - The duration of the animation. + * @param {Array} colors - An array of colors that should be sequentially animated. + * @return {AnimationClip} The created animation clip. + */ static CreateMaterialColorAnimation( duration, colors ) { const times = [], values = [], - timeStep = duration / colors.length; + timeStep = ( colors.length > 1 ) ? duration / ( colors.length - 1 ) : 0; for ( let i = 0; i < colors.length; i ++ ) { @@ -107,7 +159,7 @@ class AnimationClipCreator { const track = new ColorKeyframeTrack( trackName, times, values ); - return new AnimationClip( null, duration, [ track ] ); + return new AnimationClip( '', duration, [ track ] ); } diff --git a/examples/jsm/animation/CCDIKSolver.js b/examples/jsm/animation/CCDIKSolver.js index 7066c99b7fc240..ab85e40ee3d13d 100644 --- a/examples/jsm/animation/CCDIKSolver.js +++ b/examples/jsm/animation/CCDIKSolver.js @@ -13,7 +13,7 @@ import { Vector3 } from 'three'; -const _q = new Quaternion(); +const _quaternion = new Quaternion(); const _targetPos = new Vector3(); const _targetVec = new Vector3(); const _effectorPos = new Vector3(); @@ -25,54 +25,68 @@ const _axis = new Vector3(); const _vector = new Vector3(); const _matrix = new Matrix4(); - /** - * CCD Algorithm - * - https://sites.google.com/site/auraliusproject/ccd-algorithm + * This class solves the Inverse Kinematics Problem with a [CCD Algorithm]{@link https://web.archive.org/web/20221206080850/https://sites.google.com/site/auraliusproject/ccd-algorithm}. + * + * `CCDIKSolver` is designed to work with instances of {@link SkinnedMesh}. * - * // ik parameter example - * // - * // target, effector, index in links are bone index in skeleton.bones. - * // the bones relation should be - * // <-- parent child --> - * // links[ n ], links[ n - 1 ], ..., links[ 0 ], effector - * iks = [ { - * target: 1, - * effector: 2, - * links: [ { index: 5, limitation: new Vector3( 1, 0, 0 ) }, { index: 4, enabled: false }, { index : 3 } ], - * iteration: 10, - * minAngle: 0.0, - * maxAngle: 1.0, - * } ]; + * @three_import import { CCDIKSolver } from 'three/addons/animation/CCDIKSolver.js'; */ - class CCDIKSolver { /** - * @param {THREE.SkinnedMesh} mesh - * @param {Array} iks + * @param {SkinnedMesh} mesh - The skinned mesh. + * @param {Array} [iks=[]] - The IK objects. */ constructor( mesh, iks = [] ) { + /** + * The skinned mesh. + * + * @type {SkinnedMesh} + */ this.mesh = mesh; + + /** + * The IK objects. + * + * @type {SkinnedMesh} + */ this.iks = iks; + this._initialQuaternions = []; + this._workingQuaternion = new Quaternion(); + + for ( const ik of iks ) { + + const chainQuats = []; + for ( let i = 0; i < ik.links.length; i ++ ) { + + chainQuats.push( new Quaternion() ); + + } + + this._initialQuaternions.push( chainQuats ); + + } + this._valid(); } /** - * Update all IK bones. + * Updates all IK bones by solving the CCD algorithm. * - * @return {CCDIKSolver} + * @param {number} [globalBlendFactor=1.0] - Blend factor applied if an IK chain doesn't have its own .blendFactor. + * @return {CCDIKSolver} A reference to this instance. */ - update() { + update( globalBlendFactor = 1.0 ) { const iks = this.iks; for ( let i = 0, il = iks.length; i < il; i ++ ) { - this.updateOne( iks[ i ] ); + this.updateOne( iks[ i ], globalBlendFactor ); } @@ -81,14 +95,18 @@ class CCDIKSolver { } /** - * Update one IK bone + * Updates one IK bone solving the CCD algorithm. * - * @param {Object} ik parameter - * @return {CCDIKSolver} + * @param {CCDIKSolver~IK} ik - The IK to update. + * @param {number} [overrideBlend=1.0] - If the IK object does not define `blendFactor`, this value is used. + * @return {CCDIKSolver} A reference to this instance. */ - updateOne( ik ) { + updateOne( ik, overrideBlend = 1.0 ) { + const chainBlend = ik.blendFactor !== undefined ? ik.blendFactor : overrideBlend; const bones = this.mesh.skeleton.bones; + const chainIndex = this.iks.indexOf( ik ); + const initialQuaternions = this._initialQuaternions[ chainIndex ]; // for reference overhead reduction in loop const math = Math; @@ -103,6 +121,17 @@ class CCDIKSolver { const links = ik.links; const iteration = ik.iteration !== undefined ? ik.iteration : 1; + if ( chainBlend < 1.0 ) { + + for ( let j = 0; j < links.length; j ++ ) { + + const linkIndex = links[ j ].index; + initialQuaternions[ j ].copy( bones[ linkIndex ].quaternion ); + + } + + } + for ( let i = 0; i < iteration; i ++ ) { let rotated = false; @@ -111,8 +140,7 @@ class CCDIKSolver { const link = bones[ links[ j ].index ]; - // skip this link and following links. - // this skip is used for MMD performance optimization. + // skip this link and following links if ( links[ j ].enabled === false ) break; const limitation = links[ j ].limitation; @@ -166,8 +194,8 @@ class CCDIKSolver { _axis.crossVectors( _effectorVec, _targetVec ); _axis.normalize(); - _q.setFromAxisAngle( _axis, angle ); - link.quaternion.multiply( _q ); + _quaternion.setFromAxisAngle( _axis, angle ); + link.quaternion.multiply( _quaternion ); // TODO: re-consider the limitation specification if ( limitation !== undefined ) { @@ -206,18 +234,35 @@ class CCDIKSolver { } - return this; + if ( chainBlend < 1.0 ) { + + for ( let j = 0; j < links.length; j ++ ) { + + const linkIndex = links[ j ].index; + const link = bones[ linkIndex ]; + + this._workingQuaternion.copy( initialQuaternions[ j ] ).slerp( link.quaternion, chainBlend ); + + link.quaternion.copy( this._workingQuaternion ); + link.updateMatrixWorld( true ); + + } + + } + + return this; } /** - * Creates Helper + * Creates a helper for visualizing the CCDIK. * - * @return {CCDIKHelper} + * @param {number} sphereSize - The sphere size. + * @return {CCDIKHelper} The created helper. */ - createHelper() { + createHelper( sphereSize ) { - return new CCDIKHelper( this.mesh, this.iks ); + return new CCDIKHelper( this.mesh, this.iks, sphereSize ); } @@ -276,25 +321,51 @@ function setPositionOfBoneToAttributeArray( array, index, bone, matrixWorldInv ) } /** - * Visualize IK bones + * Helper for visualizing IK bones. * - * @param {SkinnedMesh} mesh - * @param {Array} iks + * @augments Object3D + * @three_import import { CCDIKHelper } from 'three/addons/animation/CCDIKSolver.js'; */ class CCDIKHelper extends Object3D { + /** + * @param {SkinnedMesh} mesh - The skinned mesh. + * @param {Array} [iks=[]] - The IK objects. + * @param {number} [sphereSize=0.25] - The sphere size. + */ constructor( mesh, iks = [], sphereSize = 0.25 ) { super(); + /** + * The skinned mesh this helper refers to. + * + * @type {SkinnedMesh} + */ this.root = mesh; + + /** + * The IK objects. + * + * @type {Array} + */ this.iks = iks; this.matrix.copy( mesh.matrixWorld ); this.matrixAutoUpdate = false; + /** + * The helpers sphere geometry. + * + * @type {SkinnedMesh} + */ this.sphereGeometry = new SphereGeometry( sphereSize, 16, 8 ); + /** + * The material for the target spheres. + * + * @type {MeshBasicMaterial} + */ this.targetSphereMaterial = new MeshBasicMaterial( { color: new Color( 0xff8888 ), depthTest: false, @@ -302,6 +373,11 @@ class CCDIKHelper extends Object3D { transparent: true } ); + /** + * The material for the effector spheres. + * + * @type {MeshBasicMaterial} + */ this.effectorSphereMaterial = new MeshBasicMaterial( { color: new Color( 0x88ff88 ), depthTest: false, @@ -309,6 +385,11 @@ class CCDIKHelper extends Object3D { transparent: true } ); + /** + * The material for the link spheres. + * + * @type {MeshBasicMaterial} + */ this.linkSphereMaterial = new MeshBasicMaterial( { color: new Color( 0x8888ff ), depthTest: false, @@ -316,6 +397,11 @@ class CCDIKHelper extends Object3D { transparent: true } ); + /** + * A global line material. + * + * @type {LineBasicMaterial} + */ this.lineMaterial = new LineBasicMaterial( { color: new Color( 0xff0000 ), depthTest: false, @@ -327,9 +413,6 @@ class CCDIKHelper extends Object3D { } - /** - * Updates IK bones visualization. - */ updateMatrixWorld( force ) { const mesh = this.root; @@ -394,7 +477,8 @@ class CCDIKHelper extends Object3D { } /** - * Frees the GPU-related resources allocated by this instance. Call this method whenever this instance is no longer used in your app. + * Frees the GPU-related resources allocated by this instance. + * Call this method whenever this instance is no longer used in your app. */ dispose() { @@ -479,4 +563,29 @@ class CCDIKHelper extends Object3D { } +/** + * This type represents IK configuration objects. + * + * @typedef {Object} CCDIKSolver~IK + * @property {number} target - The target bone index which refers to a bone in the `Skeleton.bones` array. + * @property {number} effector - The effector bone index which refers to a bone in the `Skeleton.bones` array. + * @property {Array} links - An array of bone links. + * @property {number} [iteration=1] - Iteration number of calculation. Smaller is faster but less precise. + * @property {number} [minAngle] - Minimum rotation angle in a step in radians. + * @property {number} [maxAngle] - Minimum rotation angle in a step in radians. + * @property {number} [blendFactor] - The blend factor. + **/ + +/** + * This type represents bone links. + * + * @typedef {Object} CCDIKSolver~BoneLink + * @property {number} index - The index of a linked bone which refers to a bone in the `Skeleton.bones` array. + * @property {number} [limitation] - Rotation axis. + * @property {number} [rotationMin] - Rotation minimum limit. + * @property {number} [rotationMax] - Rotation maximum limit. + * @property {boolean} [enabled=true] - Whether the link is enabled or not. + **/ + + export { CCDIKSolver, CCDIKHelper }; diff --git a/examples/jsm/animation/MMDAnimationHelper.js b/examples/jsm/animation/MMDAnimationHelper.js deleted file mode 100644 index b24dea116a84fe..00000000000000 --- a/examples/jsm/animation/MMDAnimationHelper.js +++ /dev/null @@ -1,1207 +0,0 @@ -import { - AnimationMixer, - Object3D, - Quaternion, - Vector3 -} from 'three'; -import { CCDIKSolver } from '../animation/CCDIKSolver.js'; -import { MMDPhysics } from '../animation/MMDPhysics.js'; - -/** - * MMDAnimationHelper handles animation of MMD assets loaded by MMDLoader - * with MMD special features as IK, Grant, and Physics. - * - * Dependencies - * - ammo.js https://github.com/kripken/ammo.js - * - MMDPhysics - * - CCDIKSolver - * - * TODO - * - more precise grant skinning support. - */ -class MMDAnimationHelper { - - /** - * @param {Object} params - (optional) - * @param {boolean} params.sync - Whether animation durations of added objects are synched. Default is true. - * @param {Number} params.afterglow - Default is 0.0. - * @param {boolean} params.resetPhysicsOnLoop - Default is true. - */ - constructor( params = {} ) { - - this.meshes = []; - - this.camera = null; - this.cameraTarget = new Object3D(); - this.cameraTarget.name = 'target'; - - this.audio = null; - this.audioManager = null; - - this.objects = new WeakMap(); - - this.configuration = { - sync: params.sync !== undefined ? params.sync : true, - afterglow: params.afterglow !== undefined ? params.afterglow : 0.0, - resetPhysicsOnLoop: params.resetPhysicsOnLoop !== undefined ? params.resetPhysicsOnLoop : true, - pmxAnimation: params.pmxAnimation !== undefined ? params.pmxAnimation : false - }; - - this.enabled = { - animation: true, - ik: true, - grant: true, - physics: true, - cameraAnimation: true - }; - - this.onBeforePhysics = function ( /* mesh */ ) {}; - - // experimental - this.sharedPhysics = false; - this.masterPhysics = null; - - } - - /** - * Adds an Three.js Object to helper and setups animation. - * The anmation durations of added objects are synched - * if this.configuration.sync is true. - * - * @param {THREE.SkinnedMesh|THREE.Camera|THREE.Audio} object - * @param {Object} params - (optional) - * @param {THREE.AnimationClip|Array} params.animation - Only for THREE.SkinnedMesh and THREE.Camera. Default is undefined. - * @param {boolean} params.physics - Only for THREE.SkinnedMesh. Default is true. - * @param {Integer} params.warmup - Only for THREE.SkinnedMesh and physics is true. Default is 60. - * @param {Number} params.unitStep - Only for THREE.SkinnedMesh and physics is true. Default is 1 / 65. - * @param {Integer} params.maxStepNum - Only for THREE.SkinnedMesh and physics is true. Default is 3. - * @param {Vector3} params.gravity - Only for THREE.SkinnedMesh and physics is true. Default ( 0, - 9.8 * 10, 0 ). - * @param {Number} params.delayTime - Only for THREE.Audio. Default is 0.0. - * @return {MMDAnimationHelper} - */ - add( object, params = {} ) { - - if ( object.isSkinnedMesh ) { - - this._addMesh( object, params ); - - } else if ( object.isCamera ) { - - this._setupCamera( object, params ); - - } else if ( object.type === 'Audio' ) { - - this._setupAudio( object, params ); - - } else { - - throw new Error( 'THREE.MMDAnimationHelper.add: ' - + 'accepts only ' - + 'THREE.SkinnedMesh or ' - + 'THREE.Camera or ' - + 'THREE.Audio instance.' ); - - } - - if ( this.configuration.sync ) this._syncDuration(); - - return this; - - } - - /** - * Removes an Three.js Object from helper. - * - * @param {THREE.SkinnedMesh|THREE.Camera|THREE.Audio} object - * @return {MMDAnimationHelper} - */ - remove( object ) { - - if ( object.isSkinnedMesh ) { - - this._removeMesh( object ); - - } else if ( object.isCamera ) { - - this._clearCamera( object ); - - } else if ( object.type === 'Audio' ) { - - this._clearAudio( object ); - - } else { - - throw new Error( 'THREE.MMDAnimationHelper.remove: ' - + 'accepts only ' - + 'THREE.SkinnedMesh or ' - + 'THREE.Camera or ' - + 'THREE.Audio instance.' ); - - } - - if ( this.configuration.sync ) this._syncDuration(); - - return this; - - } - - /** - * Updates the animation. - * - * @param {Number} delta - * @return {MMDAnimationHelper} - */ - update( delta ) { - - if ( this.audioManager !== null ) this.audioManager.control( delta ); - - for ( let i = 0; i < this.meshes.length; i ++ ) { - - this._animateMesh( this.meshes[ i ], delta ); - - } - - if ( this.sharedPhysics ) this._updateSharedPhysics( delta ); - - if ( this.camera !== null ) this._animateCamera( this.camera, delta ); - - return this; - - } - - /** - * Changes the pose of SkinnedMesh as VPD specifies. - * - * @param {THREE.SkinnedMesh} mesh - * @param {Object} vpd - VPD content parsed MMDParser - * @param {Object} params - (optional) - * @param {boolean} params.resetPose - Default is true. - * @param {boolean} params.ik - Default is true. - * @param {boolean} params.grant - Default is true. - * @return {MMDAnimationHelper} - */ - pose( mesh, vpd, params = {} ) { - - if ( params.resetPose !== false ) mesh.pose(); - - const bones = mesh.skeleton.bones; - const boneParams = vpd.bones; - - const boneNameDictionary = {}; - - for ( let i = 0, il = bones.length; i < il; i ++ ) { - - boneNameDictionary[ bones[ i ].name ] = i; - - } - - const vector = new Vector3(); - const quaternion = new Quaternion(); - - for ( let i = 0, il = boneParams.length; i < il; i ++ ) { - - const boneParam = boneParams[ i ]; - const boneIndex = boneNameDictionary[ boneParam.name ]; - - if ( boneIndex === undefined ) continue; - - const bone = bones[ boneIndex ]; - bone.position.add( vector.fromArray( boneParam.translation ) ); - bone.quaternion.multiply( quaternion.fromArray( boneParam.quaternion ) ); - - } - - mesh.updateMatrixWorld( true ); - - // PMX animation system special path - if ( this.configuration.pmxAnimation && - mesh.geometry.userData.MMD && mesh.geometry.userData.MMD.format === 'pmx' ) { - - const sortedBonesData = this._sortBoneDataArray( mesh.geometry.userData.MMD.bones.slice() ); - const ikSolver = params.ik !== false ? this._createCCDIKSolver( mesh ) : null; - const grantSolver = params.grant !== false ? this.createGrantSolver( mesh ) : null; - this._animatePMXMesh( mesh, sortedBonesData, ikSolver, grantSolver ); - - } else { - - if ( params.ik !== false ) { - - this._createCCDIKSolver( mesh ).update(); - - } - - if ( params.grant !== false ) { - - this.createGrantSolver( mesh ).update(); - - } - - } - - return this; - - } - - /** - * Enabes/Disables an animation feature. - * - * @param {string} key - * @param {boolean} enabled - * @return {MMDAnimationHelper} - */ - enable( key, enabled ) { - - if ( this.enabled[ key ] === undefined ) { - - throw new Error( 'THREE.MMDAnimationHelper.enable: ' - + 'unknown key ' + key ); - - } - - this.enabled[ key ] = enabled; - - if ( key === 'physics' ) { - - for ( let i = 0, il = this.meshes.length; i < il; i ++ ) { - - this._optimizeIK( this.meshes[ i ], enabled ); - - } - - } - - return this; - - } - - /** - * Creates an GrantSolver instance. - * - * @param {THREE.SkinnedMesh} mesh - * @return {GrantSolver} - */ - createGrantSolver( mesh ) { - - return new GrantSolver( mesh, mesh.geometry.userData.MMD.grants ); - - } - - // private methods - - _addMesh( mesh, params ) { - - if ( this.meshes.indexOf( mesh ) >= 0 ) { - - throw new Error( 'THREE.MMDAnimationHelper._addMesh: ' - + 'SkinnedMesh \'' + mesh.name + '\' has already been added.' ); - - } - - this.meshes.push( mesh ); - this.objects.set( mesh, { looped: false } ); - - this._setupMeshAnimation( mesh, params.animation ); - - if ( params.physics !== false ) { - - this._setupMeshPhysics( mesh, params ); - - } - - return this; - - } - - _setupCamera( camera, params ) { - - if ( this.camera === camera ) { - - throw new Error( 'THREE.MMDAnimationHelper._setupCamera: ' - + 'Camera \'' + camera.name + '\' has already been set.' ); - - } - - if ( this.camera ) this.clearCamera( this.camera ); - - this.camera = camera; - - camera.add( this.cameraTarget ); - - this.objects.set( camera, {} ); - - if ( params.animation !== undefined ) { - - this._setupCameraAnimation( camera, params.animation ); - - } - - return this; - - } - - _setupAudio( audio, params ) { - - if ( this.audio === audio ) { - - throw new Error( 'THREE.MMDAnimationHelper._setupAudio: ' - + 'Audio \'' + audio.name + '\' has already been set.' ); - - } - - if ( this.audio ) this.clearAudio( this.audio ); - - this.audio = audio; - this.audioManager = new AudioManager( audio, params ); - - this.objects.set( this.audioManager, { - duration: this.audioManager.duration - } ); - - return this; - - } - - _removeMesh( mesh ) { - - let found = false; - let writeIndex = 0; - - for ( let i = 0, il = this.meshes.length; i < il; i ++ ) { - - if ( this.meshes[ i ] === mesh ) { - - this.objects.delete( mesh ); - found = true; - - continue; - - } - - this.meshes[ writeIndex ++ ] = this.meshes[ i ]; - - } - - if ( ! found ) { - - throw new Error( 'THREE.MMDAnimationHelper._removeMesh: ' - + 'SkinnedMesh \'' + mesh.name + '\' has not been added yet.' ); - - } - - this.meshes.length = writeIndex; - - return this; - - } - - _clearCamera( camera ) { - - if ( camera !== this.camera ) { - - throw new Error( 'THREE.MMDAnimationHelper._clearCamera: ' - + 'Camera \'' + camera.name + '\' has not been set yet.' ); - - } - - this.camera.remove( this.cameraTarget ); - - this.objects.delete( this.camera ); - this.camera = null; - - return this; - - } - - _clearAudio( audio ) { - - if ( audio !== this.audio ) { - - throw new Error( 'THREE.MMDAnimationHelper._clearAudio: ' - + 'Audio \'' + audio.name + '\' has not been set yet.' ); - - } - - this.objects.delete( this.audioManager ); - - this.audio = null; - this.audioManager = null; - - return this; - - } - - _setupMeshAnimation( mesh, animation ) { - - const objects = this.objects.get( mesh ); - - if ( animation !== undefined ) { - - const animations = Array.isArray( animation ) - ? animation : [ animation ]; - - objects.mixer = new AnimationMixer( mesh ); - - for ( let i = 0, il = animations.length; i < il; i ++ ) { - - objects.mixer.clipAction( animations[ i ] ).play(); - - } - - // TODO: find a workaround not to access ._clip looking like a private property - objects.mixer.addEventListener( 'loop', function ( event ) { - - const tracks = event.action._clip.tracks; - - if ( tracks.length > 0 && tracks[ 0 ].name.slice( 0, 6 ) !== '.bones' ) return; - - objects.looped = true; - - } ); - - } - - objects.ikSolver = this._createCCDIKSolver( mesh ); - objects.grantSolver = this.createGrantSolver( mesh ); - - return this; - - } - - _setupCameraAnimation( camera, animation ) { - - const animations = Array.isArray( animation ) - ? animation : [ animation ]; - - const objects = this.objects.get( camera ); - - objects.mixer = new AnimationMixer( camera ); - - for ( let i = 0, il = animations.length; i < il; i ++ ) { - - objects.mixer.clipAction( animations[ i ] ).play(); - - } - - } - - _setupMeshPhysics( mesh, params ) { - - const objects = this.objects.get( mesh ); - - // shared physics is experimental - - if ( params.world === undefined && this.sharedPhysics ) { - - const masterPhysics = this._getMasterPhysics(); - - if ( masterPhysics !== null ) world = masterPhysics.world; // eslint-disable-line no-undef - - } - - objects.physics = this._createMMDPhysics( mesh, params ); - - if ( objects.mixer && params.animationWarmup !== false ) { - - this._animateMesh( mesh, 0 ); - objects.physics.reset(); - - } - - objects.physics.warmup( params.warmup !== undefined ? params.warmup : 60 ); - - this._optimizeIK( mesh, true ); - - } - - _animateMesh( mesh, delta ) { - - const objects = this.objects.get( mesh ); - - const mixer = objects.mixer; - const ikSolver = objects.ikSolver; - const grantSolver = objects.grantSolver; - const physics = objects.physics; - const looped = objects.looped; - - if ( mixer && this.enabled.animation ) { - - // alternate solution to save/restore bones but less performant? - //mesh.pose(); - //this._updatePropertyMixersBuffer( mesh ); - - this._restoreBones( mesh ); - - mixer.update( delta ); - - this._saveBones( mesh ); - - // PMX animation system special path - if ( this.configuration.pmxAnimation && - mesh.geometry.userData.MMD && mesh.geometry.userData.MMD.format === 'pmx' ) { - - if ( ! objects.sortedBonesData ) objects.sortedBonesData = this._sortBoneDataArray( mesh.geometry.userData.MMD.bones.slice() ); - - this._animatePMXMesh( - mesh, - objects.sortedBonesData, - ikSolver && this.enabled.ik ? ikSolver : null, - grantSolver && this.enabled.grant ? grantSolver : null - ); - - } else { - - if ( ikSolver && this.enabled.ik ) { - - mesh.updateMatrixWorld( true ); - ikSolver.update(); - - } - - if ( grantSolver && this.enabled.grant ) { - - grantSolver.update(); - - } - - } - - } - - if ( looped === true && this.enabled.physics ) { - - if ( physics && this.configuration.resetPhysicsOnLoop ) physics.reset(); - - objects.looped = false; - - } - - if ( physics && this.enabled.physics && ! this.sharedPhysics ) { - - this.onBeforePhysics( mesh ); - physics.update( delta ); - - } - - } - - // Sort bones in order by 1. transformationClass and 2. bone index. - // In PMX animation system, bone transformations should be processed - // in this order. - _sortBoneDataArray( boneDataArray ) { - - return boneDataArray.sort( function ( a, b ) { - - if ( a.transformationClass !== b.transformationClass ) { - - return a.transformationClass - b.transformationClass; - - } else { - - return a.index - b.index; - - } - - } ); - - } - - // PMX Animation system is a bit too complex and doesn't great match to - // Three.js Animation system. This method attempts to simulate it as much as - // possible but doesn't perfectly simulate. - // This method is more costly than the regular one so - // you are recommended to set constructor parameter "pmxAnimation: true" - // only if your PMX model animation doesn't work well. - // If you need better method you would be required to write your own. - _animatePMXMesh( mesh, sortedBonesData, ikSolver, grantSolver ) { - - _quaternionIndex = 0; - _grantResultMap.clear(); - - for ( let i = 0, il = sortedBonesData.length; i < il; i ++ ) { - - updateOne( mesh, sortedBonesData[ i ].index, ikSolver, grantSolver ); - - } - - mesh.updateMatrixWorld( true ); - return this; - - } - - _animateCamera( camera, delta ) { - - const mixer = this.objects.get( camera ).mixer; - - if ( mixer && this.enabled.cameraAnimation ) { - - mixer.update( delta ); - - camera.updateProjectionMatrix(); - - camera.up.set( 0, 1, 0 ); - camera.up.applyQuaternion( camera.quaternion ); - camera.lookAt( this.cameraTarget.position ); - - } - - } - - _optimizeIK( mesh, physicsEnabled ) { - - const iks = mesh.geometry.userData.MMD.iks; - const bones = mesh.geometry.userData.MMD.bones; - - for ( let i = 0, il = iks.length; i < il; i ++ ) { - - const ik = iks[ i ]; - const links = ik.links; - - for ( let j = 0, jl = links.length; j < jl; j ++ ) { - - const link = links[ j ]; - - if ( physicsEnabled === true ) { - - // disable IK of the bone the corresponding rigidBody type of which is 1 or 2 - // because its rotation will be overriden by physics - link.enabled = bones[ link.index ].rigidBodyType > 0 ? false : true; - - } else { - - link.enabled = true; - - } - - } - - } - - } - - _createCCDIKSolver( mesh ) { - - if ( CCDIKSolver === undefined ) { - - throw new Error( 'THREE.MMDAnimationHelper: Import CCDIKSolver.' ); - - } - - return new CCDIKSolver( mesh, mesh.geometry.userData.MMD.iks ); - - } - - _createMMDPhysics( mesh, params ) { - - if ( MMDPhysics === undefined ) { - - throw new Error( 'THREE.MMDPhysics: Import MMDPhysics.' ); - - } - - return new MMDPhysics( - mesh, - mesh.geometry.userData.MMD.rigidBodies, - mesh.geometry.userData.MMD.constraints, - params ); - - } - - /* - * Detects the longest duration and then sets it to them to sync. - * TODO: Not to access private properties ( ._actions and ._clip ) - */ - _syncDuration() { - - let max = 0.0; - - const objects = this.objects; - const meshes = this.meshes; - const camera = this.camera; - const audioManager = this.audioManager; - - // get the longest duration - - for ( let i = 0, il = meshes.length; i < il; i ++ ) { - - const mixer = this.objects.get( meshes[ i ] ).mixer; - - if ( mixer === undefined ) continue; - - for ( let j = 0; j < mixer._actions.length; j ++ ) { - - const clip = mixer._actions[ j ]._clip; - - if ( ! objects.has( clip ) ) { - - objects.set( clip, { - duration: clip.duration - } ); - - } - - max = Math.max( max, objects.get( clip ).duration ); - - } - - } - - if ( camera !== null ) { - - const mixer = this.objects.get( camera ).mixer; - - if ( mixer !== undefined ) { - - for ( let i = 0, il = mixer._actions.length; i < il; i ++ ) { - - const clip = mixer._actions[ i ]._clip; - - if ( ! objects.has( clip ) ) { - - objects.set( clip, { - duration: clip.duration - } ); - - } - - max = Math.max( max, objects.get( clip ).duration ); - - } - - } - - } - - if ( audioManager !== null ) { - - max = Math.max( max, objects.get( audioManager ).duration ); - - } - - max += this.configuration.afterglow; - - // update the duration - - for ( let i = 0, il = this.meshes.length; i < il; i ++ ) { - - const mixer = this.objects.get( this.meshes[ i ] ).mixer; - - if ( mixer === undefined ) continue; - - for ( let j = 0, jl = mixer._actions.length; j < jl; j ++ ) { - - mixer._actions[ j ]._clip.duration = max; - - } - - } - - if ( camera !== null ) { - - const mixer = this.objects.get( camera ).mixer; - - if ( mixer !== undefined ) { - - for ( let i = 0, il = mixer._actions.length; i < il; i ++ ) { - - mixer._actions[ i ]._clip.duration = max; - - } - - } - - } - - if ( audioManager !== null ) { - - audioManager.duration = max; - - } - - } - - // workaround - - _updatePropertyMixersBuffer( mesh ) { - - const mixer = this.objects.get( mesh ).mixer; - - const propertyMixers = mixer._bindings; - const accuIndex = mixer._accuIndex; - - for ( let i = 0, il = propertyMixers.length; i < il; i ++ ) { - - const propertyMixer = propertyMixers[ i ]; - const buffer = propertyMixer.buffer; - const stride = propertyMixer.valueSize; - const offset = ( accuIndex + 1 ) * stride; - - propertyMixer.binding.getValue( buffer, offset ); - - } - - } - - /* - * Avoiding these two issues by restore/save bones before/after mixer animation. - * - * 1. PropertyMixer used by AnimationMixer holds cache value in .buffer. - * Calculating IK, Grant, and Physics after mixer animation can break - * the cache coherency. - * - * 2. Applying Grant two or more times without reset the posing breaks model. - */ - _saveBones( mesh ) { - - const objects = this.objects.get( mesh ); - - const bones = mesh.skeleton.bones; - - let backupBones = objects.backupBones; - - if ( backupBones === undefined ) { - - backupBones = new Float32Array( bones.length * 7 ); - objects.backupBones = backupBones; - - } - - for ( let i = 0, il = bones.length; i < il; i ++ ) { - - const bone = bones[ i ]; - bone.position.toArray( backupBones, i * 7 ); - bone.quaternion.toArray( backupBones, i * 7 + 3 ); - - } - - } - - _restoreBones( mesh ) { - - const objects = this.objects.get( mesh ); - - const backupBones = objects.backupBones; - - if ( backupBones === undefined ) return; - - const bones = mesh.skeleton.bones; - - for ( let i = 0, il = bones.length; i < il; i ++ ) { - - const bone = bones[ i ]; - bone.position.fromArray( backupBones, i * 7 ); - bone.quaternion.fromArray( backupBones, i * 7 + 3 ); - - } - - } - - // experimental - - _getMasterPhysics() { - - if ( this.masterPhysics !== null ) return this.masterPhysics; - - for ( let i = 0, il = this.meshes.length; i < il; i ++ ) { - - const physics = this.meshes[ i ].physics; - - if ( physics !== undefined && physics !== null ) { - - this.masterPhysics = physics; - return this.masterPhysics; - - } - - } - - return null; - - } - - _updateSharedPhysics( delta ) { - - if ( this.meshes.length === 0 || ! this.enabled.physics || ! this.sharedPhysics ) return; - - const physics = this._getMasterPhysics(); - - if ( physics === null ) return; - - for ( let i = 0, il = this.meshes.length; i < il; i ++ ) { - - const p = this.meshes[ i ].physics; - - if ( p !== null && p !== undefined ) { - - p.updateRigidBodies(); - - } - - } - - physics.stepSimulation( delta ); - - for ( let i = 0, il = this.meshes.length; i < il; i ++ ) { - - const p = this.meshes[ i ].physics; - - if ( p !== null && p !== undefined ) { - - p.updateBones(); - - } - - } - - } - -} - -// Keep working quaternions for less GC -const _quaternions = []; -let _quaternionIndex = 0; - -function getQuaternion() { - - if ( _quaternionIndex >= _quaternions.length ) { - - _quaternions.push( new Quaternion() ); - - } - - return _quaternions[ _quaternionIndex ++ ]; - -} - -// Save rotation whose grant and IK are already applied -// used by grant children -const _grantResultMap = new Map(); - -function updateOne( mesh, boneIndex, ikSolver, grantSolver ) { - - const bones = mesh.skeleton.bones; - const bonesData = mesh.geometry.userData.MMD.bones; - const boneData = bonesData[ boneIndex ]; - const bone = bones[ boneIndex ]; - - // Return if already updated by being referred as a grant parent. - if ( _grantResultMap.has( boneIndex ) ) return; - - const quaternion = getQuaternion(); - - // Initialize grant result here to prevent infinite loop. - // If it's referred before updating with actual result later - // result without applyting IK or grant is gotten - // but better than composing of infinite loop. - _grantResultMap.set( boneIndex, quaternion.copy( bone.quaternion ) ); - - // @TODO: Support global grant and grant position - if ( grantSolver && boneData.grant && - ! boneData.grant.isLocal && boneData.grant.affectRotation ) { - - const parentIndex = boneData.grant.parentIndex; - const ratio = boneData.grant.ratio; - - if ( ! _grantResultMap.has( parentIndex ) ) { - - updateOne( mesh, parentIndex, ikSolver, grantSolver ); - - } - - grantSolver.addGrantRotation( bone, _grantResultMap.get( parentIndex ), ratio ); - - } - - if ( ikSolver && boneData.ik ) { - - // @TODO: Updating world matrices every time solving an IK bone is - // costly. Optimize if possible. - mesh.updateMatrixWorld( true ); - ikSolver.updateOne( boneData.ik ); - - // No confident, but it seems the grant results with ik links should be updated? - const links = boneData.ik.links; - - for ( let i = 0, il = links.length; i < il; i ++ ) { - - const link = links[ i ]; - - if ( link.enabled === false ) continue; - - const linkIndex = link.index; - - if ( _grantResultMap.has( linkIndex ) ) { - - _grantResultMap.set( linkIndex, _grantResultMap.get( linkIndex ).copy( bones[ linkIndex ].quaternion ) ); - - } - - } - - } - - // Update with the actual result here - quaternion.copy( bone.quaternion ); - -} - -// - -class AudioManager { - - /** - * @param {THREE.Audio} audio - * @param {Object} params - (optional) - * @param {Nuumber} params.delayTime - */ - constructor( audio, params = {} ) { - - this.audio = audio; - - this.elapsedTime = 0.0; - this.currentTime = 0.0; - this.delayTime = params.delayTime !== undefined - ? params.delayTime : 0.0; - - this.audioDuration = this.audio.buffer.duration; - this.duration = this.audioDuration + this.delayTime; - - } - - /** - * @param {Number} delta - * @return {AudioManager} - */ - control( delta ) { - - this.elapsed += delta; - this.currentTime += delta; - - if ( this._shouldStopAudio() ) this.audio.stop(); - if ( this._shouldStartAudio() ) this.audio.play(); - - return this; - - } - - // private methods - - _shouldStartAudio() { - - if ( this.audio.isPlaying ) return false; - - while ( this.currentTime >= this.duration ) { - - this.currentTime -= this.duration; - - } - - if ( this.currentTime < this.delayTime ) return false; - - // 'duration' can be bigger than 'audioDuration + delayTime' because of sync configuration - if ( ( this.currentTime - this.delayTime ) > this.audioDuration ) return false; - - return true; - - } - - _shouldStopAudio() { - - return this.audio.isPlaying && - this.currentTime >= this.duration; - - } - -} - -const _q = new Quaternion(); - -/** - * Solver for Grant (Fuyo in Japanese. I just google translated because - * Fuyo may be MMD specific term and may not be common word in 3D CG terms.) - * Grant propagates a bone's transform to other bones transforms even if - * they are not children. - * @param {THREE.SkinnedMesh} mesh - * @param {Array} grants - */ -class GrantSolver { - - constructor( mesh, grants = [] ) { - - this.mesh = mesh; - this.grants = grants; - - } - - /** - * Solve all the grant bones - * @return {GrantSolver} - */ - update() { - - const grants = this.grants; - - for ( let i = 0, il = grants.length; i < il; i ++ ) { - - this.updateOne( grants[ i ] ); - - } - - return this; - - } - - /** - * Solve a grant bone - * @param {Object} grant - grant parameter - * @return {GrantSolver} - */ - updateOne( grant ) { - - const bones = this.mesh.skeleton.bones; - const bone = bones[ grant.index ]; - const parentBone = bones[ grant.parentIndex ]; - - if ( grant.isLocal ) { - - // TODO: implement - if ( grant.affectPosition ) { - - } - - // TODO: implement - if ( grant.affectRotation ) { - - } - - } else { - - // TODO: implement - if ( grant.affectPosition ) { - - } - - if ( grant.affectRotation ) { - - this.addGrantRotation( bone, parentBone.quaternion, grant.ratio ); - - } - - } - - return this; - - } - - addGrantRotation( bone, q, ratio ) { - - _q.set( 0, 0, 0, 1 ); - _q.slerp( q, ratio ); - bone.quaternion.multiply( _q ); - - return this; - - } - -} - -export { MMDAnimationHelper }; diff --git a/examples/jsm/animation/MMDPhysics.js b/examples/jsm/animation/MMDPhysics.js deleted file mode 100644 index 057090112e818b..00000000000000 --- a/examples/jsm/animation/MMDPhysics.js +++ /dev/null @@ -1,1406 +0,0 @@ -import { - Bone, - BoxGeometry, - CapsuleGeometry, - Color, - Euler, - Matrix4, - Mesh, - MeshBasicMaterial, - Object3D, - Quaternion, - SphereGeometry, - Vector3 -} from 'three'; - -/** - * Dependencies - * - Ammo.js https://github.com/kripken/ammo.js - * - * MMDPhysics calculates physics with Ammo(Bullet based JavaScript Physics engine) - * for MMD model loaded by MMDLoader. - * - * TODO - * - Physics in Worker - */ - -/* global Ammo */ - -class MMDPhysics { - - /** - * @param {THREE.SkinnedMesh} mesh - * @param {Array} rigidBodyParams - * @param {Array} (optional) constraintParams - * @param {Object} params - (optional) - * @param {Number} params.unitStep - Default is 1 / 65. - * @param {Integer} params.maxStepNum - Default is 3. - * @param {Vector3} params.gravity - Default is ( 0, - 9.8 * 10, 0 ) - */ - constructor( mesh, rigidBodyParams, constraintParams = [], params = {} ) { - - if ( typeof Ammo === 'undefined' ) { - - throw new Error( 'THREE.MMDPhysics: Import ammo.js https://github.com/kripken/ammo.js' ); - - } - - this.manager = new ResourceManager(); - - this.mesh = mesh; - - /* - * I don't know why but 1/60 unitStep easily breaks models - * so I set it 1/65 so far. - * Don't set too small unitStep because - * the smaller unitStep can make the performance worse. - */ - this.unitStep = ( params.unitStep !== undefined ) ? params.unitStep : 1 / 65; - this.maxStepNum = ( params.maxStepNum !== undefined ) ? params.maxStepNum : 3; - this.gravity = new Vector3( 0, - 9.8 * 10, 0 ); - - if ( params.gravity !== undefined ) this.gravity.copy( params.gravity ); - - this.world = params.world !== undefined ? params.world : null; // experimental - - this.bodies = []; - this.constraints = []; - - this._init( mesh, rigidBodyParams, constraintParams ); - - } - - /** - * Advances Physics calculation and updates bones. - * - * @param {Number} delta - time in second - * @return {MMDPhysics} - */ - update( delta ) { - - const manager = this.manager; - const mesh = this.mesh; - - // rigid bodies and constrains are for - // mesh's world scale (1, 1, 1). - // Convert to (1, 1, 1) if it isn't. - - let isNonDefaultScale = false; - - const position = manager.allocThreeVector3(); - const quaternion = manager.allocThreeQuaternion(); - const scale = manager.allocThreeVector3(); - - mesh.matrixWorld.decompose( position, quaternion, scale ); - - if ( scale.x !== 1 || scale.y !== 1 || scale.z !== 1 ) { - - isNonDefaultScale = true; - - } - - let parent; - - if ( isNonDefaultScale ) { - - parent = mesh.parent; - - if ( parent !== null ) mesh.parent = null; - - scale.copy( this.mesh.scale ); - - mesh.scale.set( 1, 1, 1 ); - mesh.updateMatrixWorld( true ); - - } - - // calculate physics and update bones - - this._updateRigidBodies(); - this._stepSimulation( delta ); - this._updateBones(); - - // restore mesh if converted above - - if ( isNonDefaultScale ) { - - if ( parent !== null ) mesh.parent = parent; - - mesh.scale.copy( scale ); - - } - - manager.freeThreeVector3( scale ); - manager.freeThreeQuaternion( quaternion ); - manager.freeThreeVector3( position ); - - return this; - - } - - /** - * Resets rigid bodies transorm to current bone's. - * - * @return {MMDPhysics} - */ - reset() { - - for ( let i = 0, il = this.bodies.length; i < il; i ++ ) { - - this.bodies[ i ].reset(); - - } - - return this; - - } - - /** - * Warm ups Rigid bodies. Calculates cycles steps. - * - * @param {Integer} cycles - * @return {MMDPhysics} - */ - warmup( cycles ) { - - for ( let i = 0; i < cycles; i ++ ) { - - this.update( 1 / 60 ); - - } - - return this; - - } - - /** - * Sets gravity. - * - * @param {Vector3} gravity - * @return {MMDPhysicsHelper} - */ - setGravity( gravity ) { - - this.world.setGravity( new Ammo.btVector3( gravity.x, gravity.y, gravity.z ) ); - this.gravity.copy( gravity ); - - return this; - - } - - /** - * Creates MMDPhysicsHelper - * - * @return {MMDPhysicsHelper} - */ - createHelper() { - - return new MMDPhysicsHelper( this.mesh, this ); - - } - - // private methods - - _init( mesh, rigidBodyParams, constraintParams ) { - - const manager = this.manager; - - // rigid body/constraint parameters are for - // mesh's default world transform as position(0, 0, 0), - // quaternion(0, 0, 0, 1) and scale(0, 0, 0) - - const parent = mesh.parent; - - if ( parent !== null ) mesh.parent = null; - - const currentPosition = manager.allocThreeVector3(); - const currentQuaternion = manager.allocThreeQuaternion(); - const currentScale = manager.allocThreeVector3(); - - currentPosition.copy( mesh.position ); - currentQuaternion.copy( mesh.quaternion ); - currentScale.copy( mesh.scale ); - - mesh.position.set( 0, 0, 0 ); - mesh.quaternion.set( 0, 0, 0, 1 ); - mesh.scale.set( 1, 1, 1 ); - - mesh.updateMatrixWorld( true ); - - if ( this.world === null ) { - - this.world = this._createWorld(); - this.setGravity( this.gravity ); - - } - - this._initRigidBodies( rigidBodyParams ); - this._initConstraints( constraintParams ); - - if ( parent !== null ) mesh.parent = parent; - - mesh.position.copy( currentPosition ); - mesh.quaternion.copy( currentQuaternion ); - mesh.scale.copy( currentScale ); - - mesh.updateMatrixWorld( true ); - - this.reset(); - - manager.freeThreeVector3( currentPosition ); - manager.freeThreeQuaternion( currentQuaternion ); - manager.freeThreeVector3( currentScale ); - - } - - _createWorld() { - - const config = new Ammo.btDefaultCollisionConfiguration(); - const dispatcher = new Ammo.btCollisionDispatcher( config ); - const cache = new Ammo.btDbvtBroadphase(); - const solver = new Ammo.btSequentialImpulseConstraintSolver(); - const world = new Ammo.btDiscreteDynamicsWorld( dispatcher, cache, solver, config ); - return world; - - } - - _initRigidBodies( rigidBodies ) { - - for ( let i = 0, il = rigidBodies.length; i < il; i ++ ) { - - this.bodies.push( new RigidBody( - this.mesh, this.world, rigidBodies[ i ], this.manager ) ); - - } - - } - - _initConstraints( constraints ) { - - for ( let i = 0, il = constraints.length; i < il; i ++ ) { - - const params = constraints[ i ]; - const bodyA = this.bodies[ params.rigidBodyIndex1 ]; - const bodyB = this.bodies[ params.rigidBodyIndex2 ]; - this.constraints.push( new Constraint( this.mesh, this.world, bodyA, bodyB, params, this.manager ) ); - - } - - } - - _stepSimulation( delta ) { - - const unitStep = this.unitStep; - let stepTime = delta; - let maxStepNum = ( ( delta / unitStep ) | 0 ) + 1; - - if ( stepTime < unitStep ) { - - stepTime = unitStep; - maxStepNum = 1; - - } - - if ( maxStepNum > this.maxStepNum ) { - - maxStepNum = this.maxStepNum; - - } - - this.world.stepSimulation( stepTime, maxStepNum, unitStep ); - - } - - _updateRigidBodies() { - - for ( let i = 0, il = this.bodies.length; i < il; i ++ ) { - - this.bodies[ i ].updateFromBone(); - - } - - } - - _updateBones() { - - for ( let i = 0, il = this.bodies.length; i < il; i ++ ) { - - this.bodies[ i ].updateBone(); - - } - - } - -} - -/** - * This manager's responsibilies are - * - * 1. manage Ammo.js and Three.js object resources and - * improve the performance and the memory consumption by - * reusing objects. - * - * 2. provide simple Ammo object operations. - */ -class ResourceManager { - - constructor() { - - // for Three.js - this.threeVector3s = []; - this.threeMatrix4s = []; - this.threeQuaternions = []; - this.threeEulers = []; - - // for Ammo.js - this.transforms = []; - this.quaternions = []; - this.vector3s = []; - - } - - allocThreeVector3() { - - return ( this.threeVector3s.length > 0 ) - ? this.threeVector3s.pop() - : new Vector3(); - - } - - freeThreeVector3( v ) { - - this.threeVector3s.push( v ); - - } - - allocThreeMatrix4() { - - return ( this.threeMatrix4s.length > 0 ) - ? this.threeMatrix4s.pop() - : new Matrix4(); - - } - - freeThreeMatrix4( m ) { - - this.threeMatrix4s.push( m ); - - } - - allocThreeQuaternion() { - - return ( this.threeQuaternions.length > 0 ) - ? this.threeQuaternions.pop() - : new Quaternion(); - - } - - freeThreeQuaternion( q ) { - - this.threeQuaternions.push( q ); - - } - - allocThreeEuler() { - - return ( this.threeEulers.length > 0 ) - ? this.threeEulers.pop() - : new Euler(); - - } - - freeThreeEuler( e ) { - - this.threeEulers.push( e ); - - } - - allocTransform() { - - return ( this.transforms.length > 0 ) - ? this.transforms.pop() - : new Ammo.btTransform(); - - } - - freeTransform( t ) { - - this.transforms.push( t ); - - } - - allocQuaternion() { - - return ( this.quaternions.length > 0 ) - ? this.quaternions.pop() - : new Ammo.btQuaternion(); - - } - - freeQuaternion( q ) { - - this.quaternions.push( q ); - - } - - allocVector3() { - - return ( this.vector3s.length > 0 ) - ? this.vector3s.pop() - : new Ammo.btVector3(); - - } - - freeVector3( v ) { - - this.vector3s.push( v ); - - } - - setIdentity( t ) { - - t.setIdentity(); - - } - - getBasis( t ) { - - var q = this.allocQuaternion(); - t.getBasis().getRotation( q ); - return q; - - } - - getBasisAsMatrix3( t ) { - - var q = this.getBasis( t ); - var m = this.quaternionToMatrix3( q ); - this.freeQuaternion( q ); - return m; - - } - - getOrigin( t ) { - - return t.getOrigin(); - - } - - setOrigin( t, v ) { - - t.getOrigin().setValue( v.x(), v.y(), v.z() ); - - } - - copyOrigin( t1, t2 ) { - - var o = t2.getOrigin(); - this.setOrigin( t1, o ); - - } - - setBasis( t, q ) { - - t.setRotation( q ); - - } - - setBasisFromMatrix3( t, m ) { - - var q = this.matrix3ToQuaternion( m ); - this.setBasis( t, q ); - this.freeQuaternion( q ); - - } - - setOriginFromArray3( t, a ) { - - t.getOrigin().setValue( a[ 0 ], a[ 1 ], a[ 2 ] ); - - } - - setOriginFromThreeVector3( t, v ) { - - t.getOrigin().setValue( v.x, v.y, v.z ); - - } - - setBasisFromArray3( t, a ) { - - var thQ = this.allocThreeQuaternion(); - var thE = this.allocThreeEuler(); - thE.set( a[ 0 ], a[ 1 ], a[ 2 ] ); - this.setBasisFromThreeQuaternion( t, thQ.setFromEuler( thE ) ); - - this.freeThreeEuler( thE ); - this.freeThreeQuaternion( thQ ); - - } - - setBasisFromThreeQuaternion( t, a ) { - - var q = this.allocQuaternion(); - - q.setX( a.x ); - q.setY( a.y ); - q.setZ( a.z ); - q.setW( a.w ); - this.setBasis( t, q ); - - this.freeQuaternion( q ); - - } - - multiplyTransforms( t1, t2 ) { - - var t = this.allocTransform(); - this.setIdentity( t ); - - var m1 = this.getBasisAsMatrix3( t1 ); - var m2 = this.getBasisAsMatrix3( t2 ); - - var o1 = this.getOrigin( t1 ); - var o2 = this.getOrigin( t2 ); - - var v1 = this.multiplyMatrix3ByVector3( m1, o2 ); - var v2 = this.addVector3( v1, o1 ); - this.setOrigin( t, v2 ); - - var m3 = this.multiplyMatrices3( m1, m2 ); - this.setBasisFromMatrix3( t, m3 ); - - this.freeVector3( v1 ); - this.freeVector3( v2 ); - - return t; - - } - - inverseTransform( t ) { - - var t2 = this.allocTransform(); - - var m1 = this.getBasisAsMatrix3( t ); - var o = this.getOrigin( t ); - - var m2 = this.transposeMatrix3( m1 ); - var v1 = this.negativeVector3( o ); - var v2 = this.multiplyMatrix3ByVector3( m2, v1 ); - - this.setOrigin( t2, v2 ); - this.setBasisFromMatrix3( t2, m2 ); - - this.freeVector3( v1 ); - this.freeVector3( v2 ); - - return t2; - - } - - multiplyMatrices3( m1, m2 ) { - - var m3 = []; - - var v10 = this.rowOfMatrix3( m1, 0 ); - var v11 = this.rowOfMatrix3( m1, 1 ); - var v12 = this.rowOfMatrix3( m1, 2 ); - - var v20 = this.columnOfMatrix3( m2, 0 ); - var v21 = this.columnOfMatrix3( m2, 1 ); - var v22 = this.columnOfMatrix3( m2, 2 ); - - m3[ 0 ] = this.dotVectors3( v10, v20 ); - m3[ 1 ] = this.dotVectors3( v10, v21 ); - m3[ 2 ] = this.dotVectors3( v10, v22 ); - m3[ 3 ] = this.dotVectors3( v11, v20 ); - m3[ 4 ] = this.dotVectors3( v11, v21 ); - m3[ 5 ] = this.dotVectors3( v11, v22 ); - m3[ 6 ] = this.dotVectors3( v12, v20 ); - m3[ 7 ] = this.dotVectors3( v12, v21 ); - m3[ 8 ] = this.dotVectors3( v12, v22 ); - - this.freeVector3( v10 ); - this.freeVector3( v11 ); - this.freeVector3( v12 ); - this.freeVector3( v20 ); - this.freeVector3( v21 ); - this.freeVector3( v22 ); - - return m3; - - } - - addVector3( v1, v2 ) { - - var v = this.allocVector3(); - v.setValue( v1.x() + v2.x(), v1.y() + v2.y(), v1.z() + v2.z() ); - return v; - - } - - dotVectors3( v1, v2 ) { - - return v1.x() * v2.x() + v1.y() * v2.y() + v1.z() * v2.z(); - - } - - rowOfMatrix3( m, i ) { - - var v = this.allocVector3(); - v.setValue( m[ i * 3 + 0 ], m[ i * 3 + 1 ], m[ i * 3 + 2 ] ); - return v; - - } - - columnOfMatrix3( m, i ) { - - var v = this.allocVector3(); - v.setValue( m[ i + 0 ], m[ i + 3 ], m[ i + 6 ] ); - return v; - - } - - negativeVector3( v ) { - - var v2 = this.allocVector3(); - v2.setValue( - v.x(), - v.y(), - v.z() ); - return v2; - - } - - multiplyMatrix3ByVector3( m, v ) { - - var v4 = this.allocVector3(); - - var v0 = this.rowOfMatrix3( m, 0 ); - var v1 = this.rowOfMatrix3( m, 1 ); - var v2 = this.rowOfMatrix3( m, 2 ); - var x = this.dotVectors3( v0, v ); - var y = this.dotVectors3( v1, v ); - var z = this.dotVectors3( v2, v ); - - v4.setValue( x, y, z ); - - this.freeVector3( v0 ); - this.freeVector3( v1 ); - this.freeVector3( v2 ); - - return v4; - - } - - transposeMatrix3( m ) { - - var m2 = []; - m2[ 0 ] = m[ 0 ]; - m2[ 1 ] = m[ 3 ]; - m2[ 2 ] = m[ 6 ]; - m2[ 3 ] = m[ 1 ]; - m2[ 4 ] = m[ 4 ]; - m2[ 5 ] = m[ 7 ]; - m2[ 6 ] = m[ 2 ]; - m2[ 7 ] = m[ 5 ]; - m2[ 8 ] = m[ 8 ]; - return m2; - - } - - quaternionToMatrix3( q ) { - - var m = []; - - var x = q.x(); - var y = q.y(); - var z = q.z(); - var w = q.w(); - - var xx = x * x; - var yy = y * y; - var zz = z * z; - - var xy = x * y; - var yz = y * z; - var zx = z * x; - - var xw = x * w; - var yw = y * w; - var zw = z * w; - - m[ 0 ] = 1 - 2 * ( yy + zz ); - m[ 1 ] = 2 * ( xy - zw ); - m[ 2 ] = 2 * ( zx + yw ); - m[ 3 ] = 2 * ( xy + zw ); - m[ 4 ] = 1 - 2 * ( zz + xx ); - m[ 5 ] = 2 * ( yz - xw ); - m[ 6 ] = 2 * ( zx - yw ); - m[ 7 ] = 2 * ( yz + xw ); - m[ 8 ] = 1 - 2 * ( xx + yy ); - - return m; - - } - - matrix3ToQuaternion( m ) { - - var t = m[ 0 ] + m[ 4 ] + m[ 8 ]; - var s, x, y, z, w; - - if ( t > 0 ) { - - s = Math.sqrt( t + 1.0 ) * 2; - w = 0.25 * s; - x = ( m[ 7 ] - m[ 5 ] ) / s; - y = ( m[ 2 ] - m[ 6 ] ) / s; - z = ( m[ 3 ] - m[ 1 ] ) / s; - - } else if ( ( m[ 0 ] > m[ 4 ] ) && ( m[ 0 ] > m[ 8 ] ) ) { - - s = Math.sqrt( 1.0 + m[ 0 ] - m[ 4 ] - m[ 8 ] ) * 2; - w = ( m[ 7 ] - m[ 5 ] ) / s; - x = 0.25 * s; - y = ( m[ 1 ] + m[ 3 ] ) / s; - z = ( m[ 2 ] + m[ 6 ] ) / s; - - } else if ( m[ 4 ] > m[ 8 ] ) { - - s = Math.sqrt( 1.0 + m[ 4 ] - m[ 0 ] - m[ 8 ] ) * 2; - w = ( m[ 2 ] - m[ 6 ] ) / s; - x = ( m[ 1 ] + m[ 3 ] ) / s; - y = 0.25 * s; - z = ( m[ 5 ] + m[ 7 ] ) / s; - - } else { - - s = Math.sqrt( 1.0 + m[ 8 ] - m[ 0 ] - m[ 4 ] ) * 2; - w = ( m[ 3 ] - m[ 1 ] ) / s; - x = ( m[ 2 ] + m[ 6 ] ) / s; - y = ( m[ 5 ] + m[ 7 ] ) / s; - z = 0.25 * s; - - } - - var q = this.allocQuaternion(); - q.setX( x ); - q.setY( y ); - q.setZ( z ); - q.setW( w ); - return q; - - } - -} - -/** - * @param {THREE.SkinnedMesh} mesh - * @param {Ammo.btDiscreteDynamicsWorld} world - * @param {Object} params - * @param {ResourceManager} manager - */ -class RigidBody { - - constructor( mesh, world, params, manager ) { - - this.mesh = mesh; - this.world = world; - this.params = params; - this.manager = manager; - - this.body = null; - this.bone = null; - this.boneOffsetForm = null; - this.boneOffsetFormInverse = null; - - this._init(); - - } - - /** - * Resets rigid body transform to the current bone's. - * - * @return {RigidBody} - */ - reset() { - - this._setTransformFromBone(); - return this; - - } - - /** - * Updates rigid body's transform from the current bone. - * - * @return {RidigBody} - */ - updateFromBone() { - - if ( this.params.boneIndex !== - 1 && this.params.type === 0 ) { - - this._setTransformFromBone(); - - } - - return this; - - } - - /** - * Updates bone from the current ridid body's transform. - * - * @return {RidigBody} - */ - updateBone() { - - if ( this.params.type === 0 || this.params.boneIndex === - 1 ) { - - return this; - - } - - this._updateBoneRotation(); - - if ( this.params.type === 1 ) { - - this._updateBonePosition(); - - } - - this.bone.updateMatrixWorld( true ); - - if ( this.params.type === 2 ) { - - this._setPositionFromBone(); - - } - - return this; - - } - - // private methods - - _init() { - - function generateShape( p ) { - - switch ( p.shapeType ) { - - case 0: - return new Ammo.btSphereShape( p.width ); - - case 1: - return new Ammo.btBoxShape( new Ammo.btVector3( p.width, p.height, p.depth ) ); - - case 2: - return new Ammo.btCapsuleShape( p.width, p.height ); - - default: - throw new Error( 'unknown shape type ' + p.shapeType ); - - } - - } - - const manager = this.manager; - const params = this.params; - const bones = this.mesh.skeleton.bones; - const bone = ( params.boneIndex === - 1 ) - ? new Bone() - : bones[ params.boneIndex ]; - - const shape = generateShape( params ); - const weight = ( params.type === 0 ) ? 0 : params.weight; - const localInertia = manager.allocVector3(); - localInertia.setValue( 0, 0, 0 ); - - if ( weight !== 0 ) { - - shape.calculateLocalInertia( weight, localInertia ); - - } - - const boneOffsetForm = manager.allocTransform(); - manager.setIdentity( boneOffsetForm ); - manager.setOriginFromArray3( boneOffsetForm, params.position ); - manager.setBasisFromArray3( boneOffsetForm, params.rotation ); - - const vector = manager.allocThreeVector3(); - const boneForm = manager.allocTransform(); - manager.setIdentity( boneForm ); - manager.setOriginFromThreeVector3( boneForm, bone.getWorldPosition( vector ) ); - - const form = manager.multiplyTransforms( boneForm, boneOffsetForm ); - const state = new Ammo.btDefaultMotionState( form ); - - const info = new Ammo.btRigidBodyConstructionInfo( weight, state, shape, localInertia ); - info.set_m_friction( params.friction ); - info.set_m_restitution( params.restitution ); - - const body = new Ammo.btRigidBody( info ); - - if ( params.type === 0 ) { - - body.setCollisionFlags( body.getCollisionFlags() | 2 ); - - /* - * It'd be better to comment out this line though in general I should call this method - * because I'm not sure why but physics will be more like MMD's - * if I comment out. - */ - body.setActivationState( 4 ); - - } - - body.setDamping( params.positionDamping, params.rotationDamping ); - body.setSleepingThresholds( 0, 0 ); - - this.world.addRigidBody( body, 1 << params.groupIndex, params.groupTarget ); - - this.body = body; - this.bone = bone; - this.boneOffsetForm = boneOffsetForm; - this.boneOffsetFormInverse = manager.inverseTransform( boneOffsetForm ); - - manager.freeVector3( localInertia ); - manager.freeTransform( form ); - manager.freeTransform( boneForm ); - manager.freeThreeVector3( vector ); - - } - - _getBoneTransform() { - - const manager = this.manager; - const p = manager.allocThreeVector3(); - const q = manager.allocThreeQuaternion(); - const s = manager.allocThreeVector3(); - - this.bone.matrixWorld.decompose( p, q, s ); - - const tr = manager.allocTransform(); - manager.setOriginFromThreeVector3( tr, p ); - manager.setBasisFromThreeQuaternion( tr, q ); - - const form = manager.multiplyTransforms( tr, this.boneOffsetForm ); - - manager.freeTransform( tr ); - manager.freeThreeVector3( s ); - manager.freeThreeQuaternion( q ); - manager.freeThreeVector3( p ); - - return form; - - } - - _getWorldTransformForBone() { - - const manager = this.manager; - const tr = this.body.getCenterOfMassTransform(); - return manager.multiplyTransforms( tr, this.boneOffsetFormInverse ); - - } - - _setTransformFromBone() { - - const manager = this.manager; - const form = this._getBoneTransform(); - - // TODO: check the most appropriate way to set - //this.body.setWorldTransform( form ); - this.body.setCenterOfMassTransform( form ); - this.body.getMotionState().setWorldTransform( form ); - - manager.freeTransform( form ); - - } - - _setPositionFromBone() { - - const manager = this.manager; - const form = this._getBoneTransform(); - - const tr = manager.allocTransform(); - this.body.getMotionState().getWorldTransform( tr ); - manager.copyOrigin( tr, form ); - - // TODO: check the most appropriate way to set - //this.body.setWorldTransform( tr ); - this.body.setCenterOfMassTransform( tr ); - this.body.getMotionState().setWorldTransform( tr ); - - manager.freeTransform( tr ); - manager.freeTransform( form ); - - } - - _updateBoneRotation() { - - const manager = this.manager; - - const tr = this._getWorldTransformForBone(); - const q = manager.getBasis( tr ); - - const thQ = manager.allocThreeQuaternion(); - const thQ2 = manager.allocThreeQuaternion(); - const thQ3 = manager.allocThreeQuaternion(); - - thQ.set( q.x(), q.y(), q.z(), q.w() ); - thQ2.setFromRotationMatrix( this.bone.matrixWorld ); - thQ2.conjugate(); - thQ2.multiply( thQ ); - - //this.bone.quaternion.multiply( thQ2 ); - - thQ3.setFromRotationMatrix( this.bone.matrix ); - - // Renormalizing quaternion here because repeatedly transforming - // quaternion continuously accumulates floating point error and - // can end up being overflow. See #15335 - this.bone.quaternion.copy( thQ2.multiply( thQ3 ).normalize() ); - - manager.freeThreeQuaternion( thQ ); - manager.freeThreeQuaternion( thQ2 ); - manager.freeThreeQuaternion( thQ3 ); - - manager.freeQuaternion( q ); - manager.freeTransform( tr ); - - } - - _updateBonePosition() { - - const manager = this.manager; - - const tr = this._getWorldTransformForBone(); - - const thV = manager.allocThreeVector3(); - - const o = manager.getOrigin( tr ); - thV.set( o.x(), o.y(), o.z() ); - - if ( this.bone.parent ) { - - this.bone.parent.worldToLocal( thV ); - - } - - this.bone.position.copy( thV ); - - manager.freeThreeVector3( thV ); - - manager.freeTransform( tr ); - - } - -} - -// - -class Constraint { - - /** - * @param {THREE.SkinnedMesh} mesh - * @param {Ammo.btDiscreteDynamicsWorld} world - * @param {RigidBody} bodyA - * @param {RigidBody} bodyB - * @param {Object} params - * @param {ResourceManager} manager - */ - constructor( mesh, world, bodyA, bodyB, params, manager ) { - - this.mesh = mesh; - this.world = world; - this.bodyA = bodyA; - this.bodyB = bodyB; - this.params = params; - this.manager = manager; - - this.constraint = null; - - this._init(); - - } - - // private method - - _init() { - - const manager = this.manager; - const params = this.params; - const bodyA = this.bodyA; - const bodyB = this.bodyB; - - const form = manager.allocTransform(); - manager.setIdentity( form ); - manager.setOriginFromArray3( form, params.position ); - manager.setBasisFromArray3( form, params.rotation ); - - const formA = manager.allocTransform(); - const formB = manager.allocTransform(); - - bodyA.body.getMotionState().getWorldTransform( formA ); - bodyB.body.getMotionState().getWorldTransform( formB ); - - const formInverseA = manager.inverseTransform( formA ); - const formInverseB = manager.inverseTransform( formB ); - - const formA2 = manager.multiplyTransforms( formInverseA, form ); - const formB2 = manager.multiplyTransforms( formInverseB, form ); - - const constraint = new Ammo.btGeneric6DofSpringConstraint( bodyA.body, bodyB.body, formA2, formB2, true ); - - const lll = manager.allocVector3(); - const lul = manager.allocVector3(); - const all = manager.allocVector3(); - const aul = manager.allocVector3(); - - lll.setValue( params.translationLimitation1[ 0 ], - params.translationLimitation1[ 1 ], - params.translationLimitation1[ 2 ] ); - lul.setValue( params.translationLimitation2[ 0 ], - params.translationLimitation2[ 1 ], - params.translationLimitation2[ 2 ] ); - all.setValue( params.rotationLimitation1[ 0 ], - params.rotationLimitation1[ 1 ], - params.rotationLimitation1[ 2 ] ); - aul.setValue( params.rotationLimitation2[ 0 ], - params.rotationLimitation2[ 1 ], - params.rotationLimitation2[ 2 ] ); - - constraint.setLinearLowerLimit( lll ); - constraint.setLinearUpperLimit( lul ); - constraint.setAngularLowerLimit( all ); - constraint.setAngularUpperLimit( aul ); - - for ( let i = 0; i < 3; i ++ ) { - - if ( params.springPosition[ i ] !== 0 ) { - - constraint.enableSpring( i, true ); - constraint.setStiffness( i, params.springPosition[ i ] ); - - } - - } - - for ( let i = 0; i < 3; i ++ ) { - - if ( params.springRotation[ i ] !== 0 ) { - - constraint.enableSpring( i + 3, true ); - constraint.setStiffness( i + 3, params.springRotation[ i ] ); - - } - - } - - /* - * Currently(10/31/2016) official ammo.js doesn't support - * btGeneric6DofSpringConstraint.setParam method. - * You need custom ammo.js (add the method into idl) if you wanna use. - * By setting this parameter, physics will be more like MMD's - */ - if ( constraint.setParam !== undefined ) { - - for ( let i = 0; i < 6; i ++ ) { - - constraint.setParam( 2, 0.475, i ); - - } - - } - - this.world.addConstraint( constraint, true ); - this.constraint = constraint; - - manager.freeTransform( form ); - manager.freeTransform( formA ); - manager.freeTransform( formB ); - manager.freeTransform( formInverseA ); - manager.freeTransform( formInverseB ); - manager.freeTransform( formA2 ); - manager.freeTransform( formB2 ); - manager.freeVector3( lll ); - manager.freeVector3( lul ); - manager.freeVector3( all ); - manager.freeVector3( aul ); - - } - -} - -// - -const _position = new Vector3(); -const _quaternion = new Quaternion(); -const _scale = new Vector3(); -const _matrixWorldInv = new Matrix4(); - -class MMDPhysicsHelper extends Object3D { - - /** - * Visualize Rigid bodies - * - * @param {THREE.SkinnedMesh} mesh - * @param {Physics} physics - */ - constructor( mesh, physics ) { - - super(); - - this.root = mesh; - this.physics = physics; - - this.matrix.copy( mesh.matrixWorld ); - this.matrixAutoUpdate = false; - - this.materials = []; - - this.materials.push( - new MeshBasicMaterial( { - color: new Color( 0xff8888 ), - wireframe: true, - depthTest: false, - depthWrite: false, - opacity: 0.25, - transparent: true - } ) - ); - - this.materials.push( - new MeshBasicMaterial( { - color: new Color( 0x88ff88 ), - wireframe: true, - depthTest: false, - depthWrite: false, - opacity: 0.25, - transparent: true - } ) - ); - - this.materials.push( - new MeshBasicMaterial( { - color: new Color( 0x8888ff ), - wireframe: true, - depthTest: false, - depthWrite: false, - opacity: 0.25, - transparent: true - } ) - ); - - this._init(); - - } - - - /** - * Frees the GPU-related resources allocated by this instance. Call this method whenever this instance is no longer used in your app. - */ - dispose() { - - const materials = this.materials; - const children = this.children; - - for ( let i = 0; i < materials.length; i ++ ) { - - materials[ i ].dispose(); - - } - - for ( let i = 0; i < children.length; i ++ ) { - - const child = children[ i ]; - - if ( child.isMesh ) child.geometry.dispose(); - - } - - } - - /** - * Updates Rigid Bodies visualization. - */ - updateMatrixWorld( force ) { - - var mesh = this.root; - - if ( this.visible ) { - - var bodies = this.physics.bodies; - - _matrixWorldInv - .copy( mesh.matrixWorld ) - .decompose( _position, _quaternion, _scale ) - .compose( _position, _quaternion, _scale.set( 1, 1, 1 ) ) - .invert(); - - for ( var i = 0, il = bodies.length; i < il; i ++ ) { - - var body = bodies[ i ].body; - var child = this.children[ i ]; - - var tr = body.getCenterOfMassTransform(); - var origin = tr.getOrigin(); - var rotation = tr.getRotation(); - - child.position - .set( origin.x(), origin.y(), origin.z() ) - .applyMatrix4( _matrixWorldInv ); - - child.quaternion - .setFromRotationMatrix( _matrixWorldInv ) - .multiply( - _quaternion.set( rotation.x(), rotation.y(), rotation.z(), rotation.w() ) - ); - - } - - } - - this.matrix - .copy( mesh.matrixWorld ) - .decompose( _position, _quaternion, _scale ) - .compose( _position, _quaternion, _scale.set( 1, 1, 1 ) ); - - super.updateMatrixWorld( force ); - - } - - // private method - - _init() { - - var bodies = this.physics.bodies; - - function createGeometry( param ) { - - switch ( param.shapeType ) { - - case 0: - return new SphereGeometry( param.width, 16, 8 ); - - case 1: - return new BoxGeometry( param.width * 2, param.height * 2, param.depth * 2, 8, 8, 8 ); - - case 2: - return new CapsuleGeometry( param.width, param.height, 8, 16 ); - - default: - return null; - - } - - } - - for ( var i = 0, il = bodies.length; i < il; i ++ ) { - - var param = bodies[ i ].params; - this.add( new Mesh( createGeometry( param ), this.materials[ param.type ] ) ); - - } - - } - -} - -export { MMDPhysics }; diff --git a/examples/jsm/cameras/CinematicCamera.js b/examples/jsm/cameras/CinematicCamera.js deleted file mode 100644 index 47b94e70eaca82..00000000000000 --- a/examples/jsm/cameras/CinematicCamera.js +++ /dev/null @@ -1,209 +0,0 @@ -import { - Mesh, - OrthographicCamera, - PerspectiveCamera, - PlaneGeometry, - Scene, - ShaderMaterial, - UniformsUtils, - WebGLRenderTarget -} from 'three'; - -import { BokehShader } from '../shaders/BokehShader2.js'; -import { BokehDepthShader } from '../shaders/BokehShader2.js'; - -class CinematicCamera extends PerspectiveCamera { - - constructor( fov, aspect, near, far ) { - - super( fov, aspect, near, far ); - - this.type = 'CinematicCamera'; - - this.postprocessing = { enabled: true }; - this.shaderSettings = { - rings: 3, - samples: 4 - }; - - const depthShader = BokehDepthShader; - - this.materialDepth = new ShaderMaterial( { - uniforms: depthShader.uniforms, - vertexShader: depthShader.vertexShader, - fragmentShader: depthShader.fragmentShader - } ); - - this.materialDepth.uniforms[ 'mNear' ].value = near; - this.materialDepth.uniforms[ 'mFar' ].value = far; - - // In case of cinematicCamera, having a default lens set is important - this.setLens(); - - this.initPostProcessing(); - - } - - // providing fnumber and coc(Circle of Confusion) as extra arguments - // In case of cinematicCamera, having a default lens set is important - // if fnumber and coc are not provided, cinematicCamera tries to act as a basic PerspectiveCamera - setLens( focalLength = 35, filmGauge = 35, fNumber = 8, coc = 0.019 ) { - - this.filmGauge = filmGauge; - - this.setFocalLength( focalLength ); - - this.fNumber = fNumber; - this.coc = coc; - - // fNumber is focalLength by aperture - this.aperture = focalLength / this.fNumber; - - // hyperFocal is required to calculate depthOfField when a lens tries to focus at a distance with given fNumber and focalLength - this.hyperFocal = ( focalLength * focalLength ) / ( this.aperture * this.coc ); - - } - - linearize( depth ) { - - const zfar = this.far; - const znear = this.near; - return - zfar * znear / ( depth * ( zfar - znear ) - zfar ); - - } - - smoothstep( near, far, depth ) { - - const x = this.saturate( ( depth - near ) / ( far - near ) ); - return x * x * ( 3 - 2 * x ); - - } - - saturate( x ) { - - return Math.max( 0, Math.min( 1, x ) ); - - } - - // function for focusing at a distance from the camera - focusAt( focusDistance = 20 ) { - - const focalLength = this.getFocalLength(); - - // distance from the camera (normal to frustrum) to focus on - this.focus = focusDistance; - - // the nearest point from the camera which is in focus (unused) - this.nearPoint = ( this.hyperFocal * this.focus ) / ( this.hyperFocal + ( this.focus - focalLength ) ); - - // the farthest point from the camera which is in focus (unused) - this.farPoint = ( this.hyperFocal * this.focus ) / ( this.hyperFocal - ( this.focus - focalLength ) ); - - // the gap or width of the space in which is everything is in focus (unused) - this.depthOfField = this.farPoint - this.nearPoint; - - // Considering minimum distance of focus for a standard lens (unused) - if ( this.depthOfField < 0 ) this.depthOfField = 0; - - this.sdistance = this.smoothstep( this.near, this.far, this.focus ); - - this.ldistance = this.linearize( 1 - this.sdistance ); - - this.postprocessing.bokeh_uniforms[ 'focalDepth' ].value = this.ldistance; - - } - - initPostProcessing() { - - if ( this.postprocessing.enabled ) { - - this.postprocessing.scene = new Scene(); - - this.postprocessing.camera = new OrthographicCamera( window.innerWidth / - 2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / - 2, - 10000, 10000 ); - - this.postprocessing.scene.add( this.postprocessing.camera ); - - this.postprocessing.rtTextureDepth = new WebGLRenderTarget( window.innerWidth, window.innerHeight ); - this.postprocessing.rtTextureColor = new WebGLRenderTarget( window.innerWidth, window.innerHeight ); - - const bokeh_shader = BokehShader; - - this.postprocessing.bokeh_uniforms = UniformsUtils.clone( bokeh_shader.uniforms ); - - this.postprocessing.bokeh_uniforms[ 'tColor' ].value = this.postprocessing.rtTextureColor.texture; - this.postprocessing.bokeh_uniforms[ 'tDepth' ].value = this.postprocessing.rtTextureDepth.texture; - - this.postprocessing.bokeh_uniforms[ 'manualdof' ].value = 0; - this.postprocessing.bokeh_uniforms[ 'shaderFocus' ].value = 0; - - this.postprocessing.bokeh_uniforms[ 'fstop' ].value = 2.8; - - this.postprocessing.bokeh_uniforms[ 'showFocus' ].value = 1; - - this.postprocessing.bokeh_uniforms[ 'focalDepth' ].value = 0.1; - - //console.log( this.postprocessing.bokeh_uniforms[ "focalDepth" ].value ); - - this.postprocessing.bokeh_uniforms[ 'znear' ].value = this.near; - this.postprocessing.bokeh_uniforms[ 'zfar' ].value = this.near; - - - this.postprocessing.bokeh_uniforms[ 'textureWidth' ].value = window.innerWidth; - - this.postprocessing.bokeh_uniforms[ 'textureHeight' ].value = window.innerHeight; - - this.postprocessing.materialBokeh = new ShaderMaterial( { - uniforms: this.postprocessing.bokeh_uniforms, - vertexShader: bokeh_shader.vertexShader, - fragmentShader: bokeh_shader.fragmentShader, - defines: { - RINGS: this.shaderSettings.rings, - SAMPLES: this.shaderSettings.samples, - DEPTH_PACKING: 1 - } - } ); - - this.postprocessing.quad = new Mesh( new PlaneGeometry( window.innerWidth, window.innerHeight ), this.postprocessing.materialBokeh ); - this.postprocessing.quad.position.z = - 500; - this.postprocessing.scene.add( this.postprocessing.quad ); - - } - - } - - renderCinematic( scene, renderer ) { - - if ( this.postprocessing.enabled ) { - - const currentRenderTarget = renderer.getRenderTarget(); - - renderer.clear(); - - // Render scene into texture - - scene.overrideMaterial = null; - renderer.setRenderTarget( this.postprocessing.rtTextureColor ); - renderer.clear(); - renderer.render( scene, this ); - - // Render depth into texture - - scene.overrideMaterial = this.materialDepth; - renderer.setRenderTarget( this.postprocessing.rtTextureDepth ); - renderer.clear(); - renderer.render( scene, this ); - - // Render bokeh composite - - renderer.setRenderTarget( null ); - renderer.render( this.postprocessing.scene, this.postprocessing.camera ); - - renderer.setRenderTarget( currentRenderTarget ); - - } - - } - -} - -export { CinematicCamera }; diff --git a/examples/jsm/capabilities/WebGL.js b/examples/jsm/capabilities/WebGL.js index 08666feb103f91..f1827999b90a53 100644 --- a/examples/jsm/capabilities/WebGL.js +++ b/examples/jsm/capabilities/WebGL.js @@ -1,11 +1,22 @@ +/** + * A utility module with basic WebGL 2 capability testing. + * + * @hideconstructor + * @three_import import WebGL from 'three/addons/capabilities/WebGL.js'; + */ class WebGL { - static isWebGLAvailable() { + /** + * Returns `true` if WebGL 2 is available. + * + * @return {boolean} Whether WebGL 2 is available or not. + */ + static isWebGL2Available() { try { const canvas = document.createElement( 'canvas' ); - return !! ( window.WebGLRenderingContext && ( canvas.getContext( 'webgl' ) || canvas.getContext( 'experimental-webgl' ) ) ); + return !! ( window.WebGL2RenderingContext && canvas.getContext( 'webgl2' ) ); } catch ( e ) { @@ -15,12 +26,21 @@ class WebGL { } - static isWebGL2Available() { + /** + * Returns `true` if the given color space is available. This method can only be used + * if WebGL 2 is supported. + * + * @param {string} colorSpace - The color space to test. + * @return {boolean} Whether the given color space is available or not. + */ + static isColorSpaceAvailable( colorSpace ) { try { const canvas = document.createElement( 'canvas' ); - return !! ( window.WebGL2RenderingContext && canvas.getContext( 'webgl2' ) ); + const ctx = window.WebGL2RenderingContext && canvas.getContext( 'webgl2' ); + ctx.drawingBufferColorSpace = colorSpace; + return ctx.drawingBufferColorSpace === colorSpace; // deepscan-disable-line SAME_OPERAND_VALUE } catch ( e ) { @@ -30,19 +50,21 @@ class WebGL { } - static getWebGLErrorMessage() { - - return this.getErrorMessage( 1 ); - - } - + /** + * Returns a `div` element representing a formatted error message that can be appended in + * web sites if WebGL 2 isn't supported. + * + * @return {HTMLDivElement} A `div` element representing a formatted error message that WebGL 2 isn't supported. + */ static getWebGL2ErrorMessage() { - return this.getErrorMessage( 2 ); + return this._getErrorMessage( 2 ); } - static getErrorMessage( version ) { + // private + + static _getErrorMessage( version ) { const names = { 1: 'WebGL', diff --git a/examples/jsm/capabilities/WebGPU.js b/examples/jsm/capabilities/WebGPU.js index 3587718e259ccb..9ca88b44fd6999 100644 --- a/examples/jsm/capabilities/WebGPU.js +++ b/examples/jsm/capabilities/WebGPU.js @@ -1,31 +1,36 @@ -if ( window.GPUShaderStage === undefined ) { +let isAvailable = ( typeof navigator !== 'undefined' && navigator.gpu !== undefined ); - window.GPUShaderStage = { VERTEX: 1, FRAGMENT: 2, COMPUTE: 4 }; +if ( typeof window !== 'undefined' && isAvailable ) { -} - -let isAvailable = false; - -if ( navigator.gpu !== undefined ) { - - const adapter = await navigator.gpu.requestAdapter(); - - if ( adapter !== null ) { - - isAvailable = true; - - } + isAvailable = await navigator.gpu.requestAdapter(); } +/** + * A utility module with basic WebGPU capability testing. + * + * @hideconstructor + * @three_import import WebGPU from 'three/addons/capabilities/WebGPU.js'; + */ class WebGPU { + /** + * Returns `true` if WebGPU is available. + * + * @return {boolean} Whether WebGPU is available or not. + */ static isAvailable() { - return isAvailable; + return Boolean( isAvailable ); } + /** + * Returns a `div` element representing a formatted error message that can be appended in + * web sites if WebGPU isn't supported. + * + * @return {HTMLDivElement} A `div` element representing a formatted error message that WebGPU isn't supported. + */ static getErrorMessage() { const message = 'Your browser does not support WebGPU yet'; @@ -50,4 +55,5 @@ class WebGPU { } + export default WebGPU; diff --git a/examples/jsm/controls/ArcballControls.js b/examples/jsm/controls/ArcballControls.js index ac56b8ab156190..8c5eede8cccc63 100644 --- a/examples/jsm/controls/ArcballControls.js +++ b/examples/jsm/controls/ArcballControls.js @@ -1,4 +1,5 @@ import { + Controls, GridHelper, EllipseCurve, BufferGeometry, @@ -12,8 +13,7 @@ import { Vector2, Vector3, Matrix4, - MathUtils, - EventDispatcher + MathUtils } from 'three'; //trackball state @@ -59,9 +59,28 @@ const _transformation = { }; -//events +/** + * Fires when the camera has been transformed by the controls. + * + * @event ArcballControls#change + * @type {Object} + */ const _changeEvent = { type: 'change' }; + +/** + * Fires when an interaction was initiated. + * + * @event ArcballControls#start + * @type {Object} + */ const _startEvent = { type: 'start' }; + +/** + * Fires when an interaction has finished. + * + * @event ArcballControls#end + * @type {Object} + */ const _endEvent = { type: 'end' }; const _raycaster = new Raycaster(); @@ -70,24 +89,69 @@ const _offset = new Vector3(); const _gizmoMatrixStateTemp = new Matrix4(); const _cameraMatrixStateTemp = new Matrix4(); const _scalePointTemp = new Vector3(); + +const _EPS = 0.000001; + /** + * Arcball controls allow the camera to be controlled by a virtual trackball with full touch support and advanced navigation functionality. + * Cursor/finger positions and movements are mapped over a virtual trackball surface represented by a gizmo and mapped in intuitive and + * consistent camera movements. Dragging cursor/fingers will cause camera to orbit around the center of the trackball in a conservative + * way (returning to the starting point will make the camera return to its starting orientation). + * + * In addition to supporting pan, zoom and pinch gestures, Arcball controls provide focus< functionality with a double click/tap for intuitively + * moving the object's point of interest in the center of the virtual trackball. Focus allows a much better inspection and navigation in complex + * environment. Moreover Arcball controls allow FOV manipulation (in a vertigo-style method) and z-rotation. Saving and restoring of Camera State + * is supported also through clipboard (use ctrl+c and ctrl+v shortcuts for copy and paste the state). * - * @param {Camera} camera Virtual camera used in the scene - * @param {HTMLElement} domElement Renderer's dom element - * @param {Scene} scene The scene to be rendered + * Unlike {@link OrbitControls} and {@link TrackballControls}, `ArcballControls` doesn't require `update()` to be called externally in an + * animation loop when animations are on. + * + * @augments Controls + * @three_import import { ArcballControls } from 'three/addons/controls/ArcballControls.js'; */ -class ArcballControls extends EventDispatcher { +class ArcballControls extends Controls { - constructor( camera, domElement, scene = null ) { + /** + * Constructs a new controls instance. + * + * @param {Camera} camera - The camera to be controlled. The camera must not be a child of another object, unless that object is the scene itself. + * @param {?HTMLDOMElement} [domElement=null] - The HTML element used for event listeners. + * @param {?Scene} [scene=null] The scene rendered by the camera. If not given, gizmos cannot be shown. + */ + constructor( camera, domElement = null, scene = null ) { - super(); - this.camera = null; - this.domElement = domElement; + super( camera, domElement ); + + /** + * The scene rendered by the camera. If not given, gizmos cannot be shown. + * + * @type {?Scene} + * @default null + */ this.scene = scene; + + /** + * The control's focus point. + * + * @type {Vector3} + */ this.target = new Vector3(); this._currentTarget = new Vector3(); + + /** + * The size of the gizmo relative to the screen width and height. + * + * @type {number} + * @default 0.67 + */ this.radiusFactor = 0.67; + /** + * Holds the mouse actions of this controls. This property is maintained by the methods + * `setMouseAction()` and `unsetMouseAction()`. + * + * @type {Array} + */ this.mouseActions = []; this._mouseOp = null; @@ -132,6 +196,7 @@ class ArcballControls extends EventDispatcher { this._farPos0 = 0; this._cameraMatrixState0 = new Matrix4(); this._gizmoMatrixState0 = new Matrix4(); + this._target0 = new Vector3(); //pointers array this._button = - 1; @@ -175,8 +240,13 @@ class ArcballControls extends EventDispatcher { this._timeStart = - 1; //initial time this._animationId = - 1; - //focus animation - this.focusAnimationTime = 500; //duration of focus animation in ms + /** + * Duration of focus animations in ms. + * + * @type {number} + * @default 500 + */ + this.focusAnimationTime = 500; //rotate animation this._timePrev = 0; //time at which previous rotate operation has been detected @@ -188,27 +258,161 @@ class ArcballControls extends EventDispatcher { this._wPrev = 0; //angular velocity of the previous rotate operation this._wCurr = 0; //angular velocity of the current rotate operation - //parameters + + /** + * If set to `true`, the camera's near and far values will be adjusted every time zoom is + * performed trying to maintain the same visible portion given by initial near and far + * values. Only works with perspective cameras. + * + * @type {boolean} + * @default false + */ this.adjustNearFar = false; - this.scaleFactor = 1.1; //zoom/distance multiplier + + /** + * The scaling factor used when performing zoom operation. + * + * @type {number} + * @default 1.1 + */ + this.scaleFactor = 1.1; + + /** + * The damping inertia used if 'enableAnimations` is set to `true`. + * + * @type {number} + * @default 25 + */ this.dampingFactor = 25; - this.wMax = 20; //maximum angular velocity allowed - this.enableAnimations = true; //if animations should be performed - this.enableGrid = false; //if grid should be showed during pan operation - this.cursorZoom = false; //if wheel zoom should be cursor centered + + /** + * Maximum angular velocity allowed on rotation animation start. + * + * @type {number} + * @default 20 + */ + this.wMax = 20; + + /** + * Set to `true` to enable animations for rotation (damping) and focus operation. + * + * @type {boolean} + * @default true + */ + this.enableAnimations = true; + + /** + * If set to `true`, a grid will appear when panning operation is being performed + * (desktop interaction only). + * + * @type {boolean} + * @default false + */ + this.enableGrid = false; + + /** + * Set to `true` to make zoom become cursor centered. + * + * @type {boolean} + * @default false + */ + this.cursorZoom = false; + + /** + * The minimum FOV in degrees. + * + * @type {number} + * @default 5 + */ this.minFov = 5; + + /** + * The maximum FOV in degrees. + * + * @type {number} + * @default 90 + */ this.maxFov = 90; - this.enabled = true; + /** + * Speed of rotation. + * + * @type {number} + * @default 1 + */ + this.rotateSpeed = 1; + + /** + * Enable or disable camera panning. + * + * @type {boolean} + * @default true + */ this.enablePan = true; + + /** + * Enable or disable camera rotation. + * + * @type {boolean} + * @default true + */ this.enableRotate = true; + + /** + * Enable or disable camera zoom. + * + * @type {boolean} + * @default true + */ this.enableZoom = true; + + /** + * Enable or disable gizmos. + * + * @type {boolean} + * @default true + */ this.enableGizmos = true; + /** + * Enable or disable camera focusing on double-tap (or click) operations. + * + * @type {boolean} + * @default true + */ + this.enableFocus = true; + + /** + * How far you can dolly in. For perspective cameras only. + * + * @type {number} + * @default 0 + */ this.minDistance = 0; + + /** + * How far you can dolly out. For perspective cameras only. + * + * @type {number} + * @default Infinity + */ this.maxDistance = Infinity; + + /** + * How far you can zoom in. For orthographic cameras only. + * + * @type {number} + * @default 0 + */ this.minZoom = 0; + + /** + * How far you can zoom out. For orthographic cameras only. + * + * @type {number} + * @default Infinity + */ this.maxZoom = Infinity; //trackball parameters @@ -225,11 +429,10 @@ class ArcballControls extends EventDispatcher { } - this.domElement.style.touchAction = 'none'; - this._devPxRatio = window.devicePixelRatio; - this.initializeMouseActions(); + // event listeners + this._onContextMenu = onContextMenu.bind( this ); this._onWheel = onWheel.bind( this ); this._onPointerUp = onPointerUp.bind( this ); @@ -238,8 +441,23 @@ class ArcballControls extends EventDispatcher { this._onPointerCancel = onPointerCancel.bind( this ); this._onWindowResize = onWindowResize.bind( this ); + if ( domElement !== null ) { + + this.connect( domElement ); + + } + + } + + connect( element ) { + + super.connect( element ); + + this.domElement.style.touchAction = 'none'; + this._devPxRatio = window.devicePixelRatio; + this.domElement.addEventListener( 'contextmenu', this._onContextMenu ); - this.domElement.addEventListener( 'wheel', this._onWheel ); + this.domElement.addEventListener( 'wheel', this._onWheel, { passive: false } ); this.domElement.addEventListener( 'pointerdown', this._onPointerDown ); this.domElement.addEventListener( 'pointercancel', this._onPointerCancel ); @@ -247,6 +465,20 @@ class ArcballControls extends EventDispatcher { } + disconnect() { + + this.domElement.removeEventListener( 'pointerdown', this._onPointerDown ); + this.domElement.removeEventListener( 'pointercancel', this._onPointerCancel ); + this.domElement.removeEventListener( 'wheel', this._onWheel ); + this.domElement.removeEventListener( 'contextmenu', this._onContextMenu ); + + window.removeEventListener( 'pointermove', this._onPointerMove ); + window.removeEventListener( 'pointerup', this._onPointerUp ); + + window.removeEventListener( 'resize', this._onWindowResize ); + + } + onSinglePanStart( event, operation ) { if ( this.enabled ) { @@ -277,7 +509,7 @@ class ArcballControls extends EventDispatcher { } this.updateTbState( STATE.PAN, true ); - this._startCursorPosition.copy( this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement ) ); + this._startCursorPosition.copy( this.unprojectOnTbPlane( this.object, _center.x, _center.y, this.domElement ) ); if ( this.enableGrid ) { this.drawGrid(); @@ -304,7 +536,7 @@ class ArcballControls extends EventDispatcher { } this.updateTbState( STATE.ROTATE, true ); - this._startCursorPosition.copy( this.unprojectOnTbSurface( this.camera, _center.x, _center.y, this.domElement, this._tbRadius ) ); + this._startCursorPosition.copy( this.unprojectOnTbSurface( this.object, _center.x, _center.y, this.domElement, this._tbRadius ) ); this.activateGizmos( true ); if ( this.enableAnimations ) { @@ -322,7 +554,7 @@ class ArcballControls extends EventDispatcher { case 'FOV': - if ( ! this.camera.isPerspectiveCamera || ! this.enableZoom ) { + if ( ! this.object.isPerspectiveCamera || ! this.enableZoom ) { return; @@ -395,7 +627,7 @@ class ArcballControls extends EventDispatcher { this.dispatchEvent( _startEvent ); this.updateTbState( opState, true ); - this._startCursorPosition.copy( this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement ) ); + this._startCursorPosition.copy( this.unprojectOnTbPlane( this.object, _center.x, _center.y, this.domElement ) ); if ( this.enableGrid ) { this.drawGrid(); @@ -407,7 +639,7 @@ class ArcballControls extends EventDispatcher { } else { //continue with pan operation - this._currentCursorPosition.copy( this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement ) ); + this._currentCursorPosition.copy( this.unprojectOnTbPlane( this.object, _center.x, _center.y, this.domElement ) ); this.applyTransformMatrix( this.pan( this._startCursorPosition, this._currentCursorPosition ) ); } @@ -428,7 +660,7 @@ class ArcballControls extends EventDispatcher { this.dispatchEvent( _startEvent ); this.updateTbState( opState, true ); - this._startCursorPosition.copy( this.unprojectOnTbSurface( this.camera, _center.x, _center.y, this.domElement, this._tbRadius ) ); + this._startCursorPosition.copy( this.unprojectOnTbSurface( this.object, _center.x, _center.y, this.domElement, this._tbRadius ) ); if ( this.enableGrid ) { @@ -441,11 +673,11 @@ class ArcballControls extends EventDispatcher { } else { //continue with rotate operation - this._currentCursorPosition.copy( this.unprojectOnTbSurface( this.camera, _center.x, _center.y, this.domElement, this._tbRadius ) ); + this._currentCursorPosition.copy( this.unprojectOnTbSurface( this.object, _center.x, _center.y, this.domElement, this._tbRadius ) ); const distance = this._startCursorPosition.distanceTo( this._currentCursorPosition ); const angle = this._startCursorPosition.angleTo( this._currentCursorPosition ); - const amount = Math.max( distance / this._tbRadius, angle ); //effective rotation angle + const amount = Math.max( distance / this._tbRadius, angle ) * this.rotateSpeed; //effective rotation angle this.applyTransformMatrix( this.rotate( this.calculateRotationAxis( this._startCursorPosition, this._currentCursorPosition ), amount ) ); @@ -523,7 +755,7 @@ class ArcballControls extends EventDispatcher { case STATE.FOV: - if ( this.enableZoom && this.camera.isPerspectiveCamera ) { + if ( this.enableZoom && this.object.isPerspectiveCamera ) { if ( restart ) { @@ -587,7 +819,7 @@ class ArcballControls extends EventDispatcher { this.applyTransformMatrix( this.scale( size, this._v3_2, false ) ); //adjusting distance - _offset.copy( this._gizmos.position ).sub( this.camera.position ).normalize().multiplyScalar( newDistance / x ); + _offset.copy( this._gizmos.position ).sub( this.object.position ).normalize().multiplyScalar( newDistance / x ); this._m4_1.makeTranslation( _offset.x, _offset.y, _offset.z ); } @@ -672,12 +904,12 @@ class ArcballControls extends EventDispatcher { onDoubleTap( event ) { - if ( this.enabled && this.enablePan && this.scene != null ) { + if ( this.enabled && this.enablePan && this.enableFocus && this.scene != null ) { this.dispatchEvent( _startEvent ); this.setCenter( event.clientX, event.clientY ); - const hitP = this.unprojectOnObj( this.getCursorNDC( _center.x, _center.y, this.domElement ), this.camera ); + const hitP = this.unprojectOnObj( this.getCursorNDC( _center.x, _center.y, this.domElement ), this.object ); if ( hitP != null && this.enableAnimations ) { @@ -720,7 +952,7 @@ class ArcballControls extends EventDispatcher { this.updateTbState( STATE.PAN, true ); this.setCenter( ( this._touchCurrent[ 0 ].clientX + this._touchCurrent[ 1 ].clientX ) / 2, ( this._touchCurrent[ 0 ].clientY + this._touchCurrent[ 1 ].clientY ) / 2 ); - this._startCursorPosition.copy( this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement, true ) ); + this._startCursorPosition.copy( this.unprojectOnTbPlane( this.object, _center.x, _center.y, this.domElement, true ) ); this._currentCursorPosition.copy( this._startCursorPosition ); this.activateGizmos( false ); @@ -742,7 +974,7 @@ class ArcballControls extends EventDispatcher { } - this._currentCursorPosition.copy( this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement, true ) ); + this._currentCursorPosition.copy( this.unprojectOnTbPlane( this.object, _center.x, _center.y, this.domElement, true ) ); this.applyTransformMatrix( this.pan( this._startCursorPosition, this._currentCursorPosition, true ) ); this.dispatchEvent( _changeEvent ); @@ -770,7 +1002,7 @@ class ArcballControls extends EventDispatcher { this._startFingerRotation = this.getAngle( this._touchCurrent[ 1 ], this._touchCurrent[ 0 ] ) + this.getAngle( this._touchStart[ 1 ], this._touchStart[ 0 ] ); this._currentFingerRotation = this._startFingerRotation; - this.camera.getWorldDirection( this._rotationAxis ); //rotation axis + this.object.getWorldDirection( this._rotationAxis ); //rotation axis if ( ! this.enablePan && ! this.enableZoom ) { @@ -806,7 +1038,7 @@ class ArcballControls extends EventDispatcher { } else { this._v3_2.setFromMatrixPosition( this._gizmoMatrixState ); - rotationPoint = this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement ).applyQuaternion( this.camera.quaternion ).multiplyScalar( 1 / this.camera.zoom ).add( this._v3_2 ); + rotationPoint = this.unprojectOnTbPlane( this.object, _center.x, _center.y, this.domElement ).applyQuaternion( this.object.quaternion ).multiplyScalar( 1 / this.object.zoom ).add( this._v3_2 ); } @@ -868,17 +1100,17 @@ class ArcballControls extends EventDispatcher { } else { - if ( this.camera.isOrthographicCamera ) { + if ( this.object.isOrthographicCamera ) { - scalePoint = this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement ) - .applyQuaternion( this.camera.quaternion ) - .multiplyScalar( 1 / this.camera.zoom ) + scalePoint = this.unprojectOnTbPlane( this.object, _center.x, _center.y, this.domElement ) + .applyQuaternion( this.object.quaternion ) + .multiplyScalar( 1 / this.object.zoom ) .add( this._gizmos.position ); - } else if ( this.camera.isPerspectiveCamera ) { + } else if ( this.object.isPerspectiveCamera ) { - scalePoint = this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement ) - .applyQuaternion( this.camera.quaternion ) + scalePoint = this.unprojectOnTbPlane( this.object, _center.x, _center.y, this.domElement ) + .applyQuaternion( this.object.quaternion ) .add( this._gizmos.position ); } @@ -996,7 +1228,7 @@ class ArcballControls extends EventDispatcher { this.applyTransformMatrix( this.scale( size, this._v3_2, false ) ); //adjusting distance - _offset.copy( this._gizmos.position ).sub( this.camera.position ).normalize().multiplyScalar( newDistance / x ); + _offset.copy( this._gizmos.position ).sub( this.object.position ).normalize().multiplyScalar( newDistance / x ); this._m4_1.makeTranslation( _offset.x, _offset.y, _offset.z ); this.dispatchEvent( _changeEvent ); @@ -1014,9 +1246,11 @@ class ArcballControls extends EventDispatcher { } /** - * Set _center's x/y coordinates - * @param {Number} clientX - * @param {Number} clientY + * Set _center's x/y coordinates. + * + * @private + * @param {number} clientX - The x coordinate. + * @param {number} clientY - The y coordinate. */ setCenter( clientX, clientY ) { @@ -1026,7 +1260,9 @@ class ArcballControls extends EventDispatcher { } /** - * Set default mouse actions + * Set default mouse actions. + * + * @private */ initializeMouseActions() { @@ -1045,10 +1281,12 @@ class ArcballControls extends EventDispatcher { } /** - * Compare two mouse actions - * @param {Object} action1 - * @param {Object} action2 - * @returns {Boolean} True if action1 and action 2 are the same mouse action, false otherwise + * Compare two mouse actions. + * + * @private + * @param {Object} action1 - The first mouse action. + * @param {Object} action2 - The second mouse action. + * @returns {boolean} `true` if action1 and action 2 are the same mouse action, `false` otherwise. */ compareMouseAction( action1, action2 ) { @@ -1073,11 +1311,12 @@ class ArcballControls extends EventDispatcher { } /** - * Set a new mouse action by specifying the operation to be performed and a mouse/key combination. In case of conflict, replaces the existing one - * @param {String} operation The operation to be performed ('PAN', 'ROTATE', 'ZOOM', 'FOV) - * @param {*} mouse A mouse button (0, 1, 2) or 'WHEEL' for wheel notches - * @param {*} key The keyboard modifier ('CTRL', 'SHIFT') or null if key is not needed - * @returns {Boolean} True if the mouse action has been successfully added, false otherwise + * Set a new mouse action by specifying the operation to be performed and a mouse/key combination. In case of conflict, replaces the existing one. + * + * @param {'PAN'|'ROTATE'|'ZOOM'|'FOV'} operation - The operation to be performed ('PAN', 'ROTATE', 'ZOOM', 'FOV'). + * @param {0|1|2|'WHEEL'} mouse - A mouse button (0, 1, 2) or 'WHEEL' for wheel notches. + * @param {'CTRL'|'SHIFT'|null} [key=null] - The keyboard modifier ('CTRL', 'SHIFT') or null if key is not needed. + * @returns {boolean} `true` if the mouse action has been successfully added, `false` otherwise. */ setMouseAction( operation, mouse, key = null ) { @@ -1154,10 +1393,11 @@ class ArcballControls extends EventDispatcher { } /** - * Remove a mouse action by specifying its mouse/key combination - * @param {*} mouse A mouse button (0, 1, 2) or 'WHEEL' for wheel notches - * @param {*} key The keyboard modifier ('CTRL', 'SHIFT') or null if key is not needed - * @returns {Boolean} True if the operation has been succesfully removed, false otherwise + * Remove a mouse action by specifying its mouse/key combination. + * + * @param {0|1|2|'WHEEL'} mouse - A mouse button (0, 1, 2) or 'WHEEL' for wheel notches. + * @param {'CTRL'|'SHIFT'|null} key - The keyboard modifier ('CTRL', 'SHIFT') or null if key is not needed. + * @returns {boolean} `true` if the operation has been successfully removed, `false` otherwise. */ unsetMouseAction( mouse, key = null ) { @@ -1177,10 +1417,12 @@ class ArcballControls extends EventDispatcher { } /** - * Return the operation associated to a mouse/keyboard combination - * @param {*} mouse A mouse button (0, 1, 2) or 'WHEEL' for wheel notches - * @param {*} key The keyboard modifier ('CTRL', 'SHIFT') or null if key is not needed - * @returns The operation if it has been found, null otherwise + * Return the operation associated to a mouse/keyboard combination. + * + * @private + * @param {0|1|2|'WHEEL'} mouse - Mouse button index (0, 1, 2) or 'WHEEL' for wheel notches. + * @param {'CTRL'|'SHIFT'|null} key - Keyboard modifier. + * @returns {'PAN'|'ROTATE'|'ZOOM'|'FOV'|null} The operation if it has been found, `null` otherwise. */ getOpFromAction( mouse, key ) { @@ -1217,10 +1459,12 @@ class ArcballControls extends EventDispatcher { } /** - * Get the operation associated to mouse and key combination and returns the corresponding FSA state - * @param {Number} mouse Mouse button - * @param {String} key Keyboard modifier - * @returns The FSA state obtained from the operation associated to mouse/keyboard combination + * Get the operation associated to mouse and key combination and returns the corresponding FSA state. + * + * @private + * @param {0|1|2} mouse - Mouse button index (0, 1, 2) + * @param {'CTRL'|'SHIFT'|null} key - Keyboard modifier + * @returns {?STATE} The FSA state obtained from the operation associated to mouse/keyboard combination. */ getOpStateFromAction( mouse, key ) { @@ -1257,10 +1501,12 @@ class ArcballControls extends EventDispatcher { } /** - * Calculate the angle between two pointers - * @param {PointerEvent} p1 - * @param {PointerEvent} p2 - * @returns {Number} The angle between two pointers in degrees + * Calculate the angle between two pointers. + * + * @private + * @param {PointerEvent} p1 - The first pointer event. + * @param {PointerEvent} p2 - The second pointer event. + * @returns {number} The angle between two pointers in degrees. */ getAngle( p1, p2 ) { @@ -1269,8 +1515,10 @@ class ArcballControls extends EventDispatcher { } /** - * Update a PointerEvent inside current pointerevents array - * @param {PointerEvent} event + * Updates a PointerEvent inside current pointerevents array. + * + * @private + * @param {PointerEvent} event - The pointer event. */ updateTouchEvent( event ) { @@ -1288,21 +1536,23 @@ class ArcballControls extends EventDispatcher { } /** - * Apply a transformation matrix, to the camera and gizmos - * @param {Object} transformation Object containing matrices to apply to camera and gizmos + * Applies a transformation matrix, to the camera and gizmos. + * + * @private + * @param {Object} transformation - Object containing matrices to apply to camera and gizmos. */ applyTransformMatrix( transformation ) { if ( transformation.camera != null ) { this._m4_1.copy( this._cameraMatrixState ).premultiply( transformation.camera ); - this._m4_1.decompose( this.camera.position, this.camera.quaternion, this.camera.scale ); - this.camera.updateMatrix(); + this._m4_1.decompose( this.object.position, this.object.quaternion, this.object.scale ); + this.object.updateMatrix(); //update camera up vector if ( this._state == STATE.ROTATE || this._state == STATE.ZROTATE || this._state == STATE.ANIMATION_ROTATE ) { - this.camera.up.copy( this._upState ).applyQuaternion( this.camera.quaternion ); + this.object.up.copy( this._upState ).applyQuaternion( this.object.quaternion ); } @@ -1318,11 +1568,11 @@ class ArcballControls extends EventDispatcher { if ( this._state == STATE.SCALE || this._state == STATE.FOCUS || this._state == STATE.ANIMATION_FOCUS ) { - this._tbRadius = this.calculateTbRadius( this.camera ); + this._tbRadius = this.calculateTbRadius( this.object ); if ( this.adjustNearFar ) { - const cameraDistance = this.camera.position.distanceTo( this._gizmos.position ); + const cameraDistance = this.object.position.distanceTo( this._gizmos.position ); const bb = new Box3(); bb.setFromObject( this._gizmos ); @@ -1333,38 +1583,38 @@ class ArcballControls extends EventDispatcher { const regularNearPosition = cameraDistance - this._initialNear; const minNearPos = Math.min( adjustedNearPosition, regularNearPosition ); - this.camera.near = cameraDistance - minNearPos; + this.object.near = cameraDistance - minNearPos; const adjustedFarPosition = Math.min( this._farPos0, - sphere.radius + sphere.center.length() ); const regularFarPosition = cameraDistance - this._initialFar; const minFarPos = Math.min( adjustedFarPosition, regularFarPosition ); - this.camera.far = cameraDistance - minFarPos; + this.object.far = cameraDistance - minFarPos; - this.camera.updateProjectionMatrix(); + this.object.updateProjectionMatrix(); } else { let update = false; - if ( this.camera.near != this._initialNear ) { + if ( this.object.near != this._initialNear ) { - this.camera.near = this._initialNear; + this.object.near = this._initialNear; update = true; } - if ( this.camera.far != this._initialFar ) { + if ( this.object.far != this._initialFar ) { - this.camera.far = this._initialFar; + this.object.far = this._initialFar; update = true; } if ( update ) { - this.camera.updateProjectionMatrix(); + this.object.updateProjectionMatrix(); } @@ -1375,11 +1625,14 @@ class ArcballControls extends EventDispatcher { } /** - * Calculate the angular speed - * @param {Number} p0 Position at t0 - * @param {Number} p1 Position at t1 - * @param {Number} t0 Initial time in milliseconds - * @param {Number} t1 Ending time in milliseconds + * Calculates the angular speed. + * + * @private + * @param {number} p0 - Position at t0. + * @param {number} p1 - Position at t1. + * @param {number} t0 - Initial time in milliseconds. + * @param {number} t1 - Ending time in milliseconds. + * @returns {number} The angular speed. */ calculateAngularSpeed( p0, p1, t0, t1 ) { @@ -1396,10 +1649,12 @@ class ArcballControls extends EventDispatcher { } /** - * Calculate the distance between two pointers - * @param {PointerEvent} p0 The first pointer - * @param {PointerEvent} p1 The second pointer - * @returns {number} The distance between the two pointers + * Calculates the distance between two pointers. + * + * @private + * @param {PointerEvent} p0 - The first pointer. + * @param {PointerEvent} p1 - The second pointer. + * @returns {number} The distance between the two pointers. */ calculatePointersDistance( p0, p1 ) { @@ -1408,10 +1663,12 @@ class ArcballControls extends EventDispatcher { } /** - * Calculate the rotation axis as the vector perpendicular between two vectors - * @param {Vector3} vec1 The first vector - * @param {Vector3} vec2 The second vector - * @returns {Vector3} The normalized rotation axis + * Calculates the rotation axis as the vector perpendicular between two vectors. + * + * @private + * @param {Vector3} vec1 - The first vector. + * @param {Vector3} vec2 - The second vector. + * @returns {Vector3} The normalized rotation axis. */ calculateRotationAxis( vec1, vec2 ) { @@ -1424,9 +1681,11 @@ class ArcballControls extends EventDispatcher { } /** - * Calculate the trackball radius so that gizmo's diamater will be 2/3 of the minimum side of the camera frustum - * @param {Camera} camera - * @returns {Number} The trackball radius + * Calculates the trackball radius so that gizmo's diameter will be 2/3 of the minimum side of the camera frustum. + * + * @private + * @param {Camera} camera - The camera. + * @returns {number} The trackball radius. */ calculateTbRadius( camera ) { @@ -1447,10 +1706,12 @@ class ArcballControls extends EventDispatcher { } /** - * Focus operation consist of positioning the point of interest in front of the camera and a slightly zoom in - * @param {Vector3} point The point of interest - * @param {Number} size Scale factor - * @param {Number} amount Amount of operation to be completed (used for focus animations, default is complete full operation) + * Focus operation consist of positioning the point of interest in front of the camera and a slightly zoom in. + * + * @private + * @param {Vector3} point - The point of interest. + * @param {number} size - Scale factor. + * @param {number} [amount=1] - Amount of operation to be completed (used for focus animations, default is complete full operation). */ focus( point, size, amount = 1 ) { @@ -1464,7 +1725,7 @@ class ArcballControls extends EventDispatcher { _cameraMatrixStateTemp.copy( this._cameraMatrixState ); this._cameraMatrixState.premultiply( this._translationMatrix ); - this._cameraMatrixState.decompose( this.camera.position, this.camera.quaternion, this.camera.scale ); + this._cameraMatrixState.decompose( this.object.position, this.object.quaternion, this.object.scale ); //apply zoom if ( this.enableZoom ) { @@ -1479,7 +1740,9 @@ class ArcballControls extends EventDispatcher { } /** - * Draw a grid and add it to the scene + * Creates a grid if necessary and adds it to the scene. + * + * @private */ drawGrid() { @@ -1489,22 +1752,22 @@ class ArcballControls extends EventDispatcher { const multiplier = 3; let size, divisions, maxLength, tick; - if ( this.camera.isOrthographicCamera ) { + if ( this.object.isOrthographicCamera ) { - const width = this.camera.right - this.camera.left; - const height = this.camera.bottom - this.camera.top; + const width = this.object.right - this.object.left; + const height = this.object.bottom - this.object.top; maxLength = Math.max( width, height ); tick = maxLength / 20; - size = maxLength / this.camera.zoom * multiplier; - divisions = size / tick * this.camera.zoom; + size = maxLength / this.object.zoom * multiplier; + divisions = size / tick * this.object.zoom; - } else if ( this.camera.isPerspectiveCamera ) { + } else if ( this.object.isPerspectiveCamera ) { - const distance = this.camera.position.distanceTo( this._gizmos.position ); - const halfFovV = MathUtils.DEG2RAD * this.camera.fov * 0.5; - const halfFovH = Math.atan( ( this.camera.aspect ) * Math.tan( halfFovV ) ); + const distance = this.object.position.distanceTo( this._gizmos.position ); + const halfFovV = MathUtils.DEG2RAD * this.object.fov * 0.5; + const halfFovH = Math.atan( ( this.object.aspect ) * Math.tan( halfFovV ) ); maxLength = Math.tan( Math.max( halfFovV, halfFovH ) ) * distance * 2; tick = maxLength / 20; @@ -1519,7 +1782,7 @@ class ArcballControls extends EventDispatcher { this._grid = new GridHelper( size, divisions, color, color ); this._grid.position.copy( this._gizmos.position ); this._gridPosition.copy( this._grid.position ); - this._grid.quaternion.copy( this.camera.quaternion ); + this._grid.quaternion.copy( this.object.quaternion ); this._grid.rotateX( Math.PI * 0.5 ); this.scene.add( this._grid ); @@ -1530,9 +1793,6 @@ class ArcballControls extends EventDispatcher { } - /** - * Remove all listeners, stop animations and clean scene - */ dispose() { if ( this._animationId != - 1 ) { @@ -1541,15 +1801,7 @@ class ArcballControls extends EventDispatcher { } - this.domElement.removeEventListener( 'pointerdown', this._onPointerDown ); - this.domElement.removeEventListener( 'pointercancel', this._onPointerCancel ); - this.domElement.removeEventListener( 'wheel', this._onWheel ); - this.domElement.removeEventListener( 'contextmenu', this._onContextMenu ); - - window.removeEventListener( 'pointermove', this._onPointerMove ); - window.removeEventListener( 'pointerup', this._onPointerUp ); - - window.removeEventListener( 'resize', this._onWindowResize ); + this.disconnect(); if ( this.scene !== null ) this.scene.remove( this._gizmos ); this.disposeGrid(); @@ -1557,7 +1809,7 @@ class ArcballControls extends EventDispatcher { } /** - * remove the grid from the scene + * Removes the grid from the scene. */ disposeGrid() { @@ -1571,9 +1823,11 @@ class ArcballControls extends EventDispatcher { } /** - * Compute the easing out cubic function for ease out effect in animation - * @param {Number} t The absolute progress of the animation in the bound of 0 (beginning of the) and 1 (ending of animation) - * @returns {Number} Result of easing out cubic at time t + * Computes the easing out cubic function for ease out effect in animation. + * + * @private + * @param {number} t - The absolute progress of the animation in the bound of `0` (beginning of the) and `1` (ending of animation). + * @returns {number} Result of easing out cubic at time `t`. */ easeOutCubic( t ) { @@ -1582,8 +1836,9 @@ class ArcballControls extends EventDispatcher { } /** - * Make rotation gizmos more or less visible - * @param {Boolean} isActive If true, make gizmos more visible + * Makes rotation gizmos more or less visible. + * + * @param {boolean} isActive - If set to `true`, gizmos are more visible. */ activateGizmos( isActive ) { @@ -1608,11 +1863,13 @@ class ArcballControls extends EventDispatcher { } /** - * Calculate the cursor position in NDC - * @param {number} x Cursor horizontal coordinate within the canvas - * @param {number} y Cursor vertical coordinate within the canvas - * @param {HTMLElement} canvas The canvas where the renderer draws its output - * @returns {Vector2} Cursor normalized position inside the canvas + * Calculates the cursor position in NDC. + * + * @private + * @param {number} cursorX - Cursor horizontal coordinate within the canvas. + * @param {number} cursorY - Cursor vertical coordinate within the canvas. + * @param {HTMLElement} canvas - The canvas where the renderer draws its output. + * @returns {Vector2} Cursor normalized position inside the canvas. */ getCursorNDC( cursorX, cursorY, canvas ) { @@ -1624,24 +1881,27 @@ class ArcballControls extends EventDispatcher { } /** - * Calculate the cursor position inside the canvas x/y coordinates with the origin being in the center of the canvas - * @param {Number} x Cursor horizontal coordinate within the canvas - * @param {Number} y Cursor vertical coordinate within the canvas - * @param {HTMLElement} canvas The canvas where the renderer draws its output - * @returns {Vector2} Cursor position inside the canvas + * Calculates the cursor position inside the canvas x/y coordinates with the origin being in the center of the canvas. + * + * @private + * @param {number} cursorX - Cursor horizontal coordinate within the canvas. + * @param {number} cursorY - Cursor vertical coordinate within the canvas. + * @param {HTMLElement} canvas - The canvas where the renderer draws its output. + * @returns {Vector2} Cursor position inside the canvas. */ getCursorPosition( cursorX, cursorY, canvas ) { this._v2_1.copy( this.getCursorNDC( cursorX, cursorY, canvas ) ); - this._v2_1.x *= ( this.camera.right - this.camera.left ) * 0.5; - this._v2_1.y *= ( this.camera.top - this.camera.bottom ) * 0.5; + this._v2_1.x *= ( this.object.right - this.object.left ) * 0.5; + this._v2_1.y *= ( this.object.top - this.object.bottom ) * 0.5; return this._v2_1.clone(); } /** - * Set the camera to be controlled - * @param {Camera} camera The virtual camera to be controlled + * Sets the camera to be controlled. Must be called in order to set a new camera to be controlled. + * + * @param {Camera} camera - The camera to be controlled. */ setCamera( camera ) { @@ -1673,8 +1933,8 @@ class ArcballControls extends EventDispatcher { this._up0.copy( camera.up ); this._upState.copy( camera.up ); - this.camera = camera; - this.camera.updateProjectionMatrix(); + this.object = camera; + this.object.updateProjectionMatrix(); //making gizmos this._tbRadius = this.calculateTbRadius( camera ); @@ -1683,8 +1943,9 @@ class ArcballControls extends EventDispatcher { } /** - * Set gizmos visibility - * @param {Boolean} value Value of gizmos visibility + * Sets gizmos visibility. + * + * @param {boolean} value - Value of gizmos visibility. */ setGizmosVisible( value ) { @@ -1694,13 +1955,14 @@ class ArcballControls extends EventDispatcher { } /** - * Set gizmos radius factor and redraws gizmos - * @param {Float} value Value of radius factor + * Sets gizmos radius factor and redraws gizmos. + * + * @param {number} value - Value of radius factor. */ setTbRadius( value ) { this.radiusFactor = value; - this._tbRadius = this.calculateTbRadius( this.camera ); + this._tbRadius = this.calculateTbRadius( this.object ); const curve = new EllipseCurve( 0, 0, this._tbRadius, this._tbRadius ); const points = curve.getPoints( this._curvePts ); @@ -1718,9 +1980,11 @@ class ArcballControls extends EventDispatcher { } /** - * Creates the rotation gizmos matching trackball center and radius - * @param {Vector3} tbCenter The trackball center - * @param {number} tbRadius The trackball radius + * Creates the rotation gizmos matching trackball center and radius. + * + * @private + * @param {Vector3} tbCenter - The trackball center. + * @param {number} tbRadius - The trackball radius. */ makeGizmos( tbCenter, tbRadius ) { @@ -1749,10 +2013,10 @@ class ArcballControls extends EventDispatcher { this._gizmoMatrixState0.identity().setPosition( tbCenter ); this._gizmoMatrixState.copy( this._gizmoMatrixState0 ); - if ( this.camera.zoom !== 1 ) { + if ( this.object.zoom !== 1 ) { //adapt gizmos size to camera zoom - const size = 1 / this.camera.zoom; + const size = 1 / this.object.zoom; this._scaleMatrix.makeScale( size, size, size ); this._translationMatrix.makeTranslation( - tbCenter.x, - tbCenter.y, - tbCenter.z ); @@ -1788,11 +2052,13 @@ class ArcballControls extends EventDispatcher { } /** - * Perform animation for focus operation - * @param {Number} time Instant in which this function is called as performance.now() - * @param {Vector3} point Point of interest for focus operation - * @param {Matrix4} cameraMatrix Camera matrix - * @param {Matrix4} gizmoMatrix Gizmos matrix + * Performs animation for focus operation. + * + * @private + * @param {number} time - Instant in which this function is called as performance.now(). + * @param {Vector3} point - Point of interest for focus operation. + * @param {Matrix4} cameraMatrix - Camera matrix. + * @param {Matrix4} gizmoMatrix - Gizmos matrix. */ onFocusAnim( time, point, cameraMatrix, gizmoMatrix ) { @@ -1854,10 +2120,12 @@ class ArcballControls extends EventDispatcher { } /** - * Perform animation for rotation operation - * @param {Number} time Instant in which this function is called as performance.now() - * @param {Vector3} rotationAxis Rotation axis - * @param {number} w0 Initial angular velocity + * Performs animation for rotation operation. + * + * @private + * @param {number} time - Instant in which this function is called as performance.now(). + * @param {Vector3} rotationAxis - Rotation axis. + * @param {number} w0 - Initial angular velocity. */ onRotationAnim( time, rotationAxis, w0 ) { @@ -1921,31 +2189,34 @@ class ArcballControls extends EventDispatcher { /** - * Perform pan operation moving camera between two points - * @param {Vector3} p0 Initial point - * @param {Vector3} p1 Ending point - * @param {Boolean} adjust If movement should be adjusted considering camera distance (Perspective only) + * Performs pan operation moving camera between two points. + * + * @private + * @param {Vector3} p0 - Initial point. + * @param {Vector3} p1 - Ending point. + * @param {boolean} [adjust=false] - If movement should be adjusted considering camera distance (Perspective only). + * @returns {Object} */ pan( p0, p1, adjust = false ) { const movement = p0.clone().sub( p1 ); - if ( this.camera.isOrthographicCamera ) { + if ( this.object.isOrthographicCamera ) { //adjust movement amount - movement.multiplyScalar( 1 / this.camera.zoom ); + movement.multiplyScalar( 1 / this.object.zoom ); - } else if ( this.camera.isPerspectiveCamera && adjust ) { + } else if ( this.object.isPerspectiveCamera && adjust ) { //adjust movement amount this._v3_1.setFromMatrixPosition( this._cameraMatrixState0 ); //camera's initial position this._v3_2.setFromMatrixPosition( this._gizmoMatrixState0 ); //gizmo's initial position - const distanceFactor = this._v3_1.distanceTo( this._v3_2 ) / this.camera.position.distanceTo( this._gizmos.position ); + const distanceFactor = this._v3_1.distanceTo( this._v3_2 ) / this.object.position.distanceTo( this._gizmos.position ); movement.multiplyScalar( 1 / distanceFactor ); } - this._v3_1.set( movement.x, movement.y, 0 ).applyQuaternion( this.camera.quaternion ); + this._v3_1.set( movement.x, movement.y, 0 ).applyQuaternion( this.object.quaternion ); this._m4_1.makeTranslation( this._v3_1.x, this._v3_1.y, this._v3_1.z ); @@ -1955,35 +2226,36 @@ class ArcballControls extends EventDispatcher { } /** - * Reset trackball + * Resets the controls. */ reset() { - this.camera.zoom = this._zoom0; + this.target.copy( this._target0 ); + this.object.zoom = this._zoom0; - if ( this.camera.isPerspectiveCamera ) { + if ( this.object.isPerspectiveCamera ) { - this.camera.fov = this._fov0; + this.object.fov = this._fov0; } - this.camera.near = this._nearPos; - this.camera.far = this._farPos; + this.object.near = this._nearPos; + this.object.far = this._farPos; this._cameraMatrixState.copy( this._cameraMatrixState0 ); - this._cameraMatrixState.decompose( this.camera.position, this.camera.quaternion, this.camera.scale ); - this.camera.up.copy( this._up0 ); + this._cameraMatrixState.decompose( this.object.position, this.object.quaternion, this.object.scale ); + this.object.up.copy( this._up0 ); - this.camera.updateMatrix(); - this.camera.updateProjectionMatrix(); + this.object.updateMatrix(); + this.object.updateProjectionMatrix(); this._gizmoMatrixState.copy( this._gizmoMatrixState0 ); this._gizmoMatrixState0.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale ); this._gizmos.updateMatrix(); - this._tbRadius = this.calculateTbRadius( this.camera ); + this._tbRadius = this.calculateTbRadius( this.object ); this.makeGizmos( this._gizmos.position, this._tbRadius ); - this.camera.lookAt( this._gizmos.position ); + this.object.lookAt( this._gizmos.position ); this.updateTbState( STATE.IDLE, false ); @@ -1992,10 +2264,12 @@ class ArcballControls extends EventDispatcher { } /** - * Rotate the camera around an axis passing by trackball's center - * @param {Vector3} axis Rotation axis - * @param {number} angle Angle in radians - * @returns {Object} Object with 'camera' field containing transformation matrix resulting from the operation to be applied to the camera + * Rotates the camera around an axis passing by trackball's center. + * + * @private + * @param {Vector3} axis - Rotation axis. + * @param {number} angle - Angle in radians. + * @returns {Object} Object with 'camera' field containing transformation matrix resulting from the operation to be applied to the camera. */ rotate( axis, angle ) { @@ -2014,34 +2288,42 @@ class ArcballControls extends EventDispatcher { } + /** + * Copy the current state to clipboard (as a readable JSON text). + */ copyState() { let state; - if ( this.camera.isOrthographicCamera ) { - - state = JSON.stringify( { arcballState: { - - cameraFar: this.camera.far, - cameraMatrix: this.camera.matrix, - cameraNear: this.camera.near, - cameraUp: this.camera.up, - cameraZoom: this.camera.zoom, - gizmoMatrix: this._gizmos.matrix + if ( this.object.isOrthographicCamera ) { + + state = JSON.stringify( { + arcballState: { + cameraFar: this.object.far, + cameraMatrix: this.object.matrix, + cameraNear: this.object.near, + cameraUp: this.object.up, + cameraZoom: this.object.zoom, + gizmoMatrix: this._gizmos.matrix, + target: this.target - } } ); + } + } ); - } else if ( this.camera.isPerspectiveCamera ) { + } else if ( this.object.isPerspectiveCamera ) { - state = JSON.stringify( { arcballState: { - cameraFar: this.camera.far, - cameraFov: this.camera.fov, - cameraMatrix: this.camera.matrix, - cameraNear: this.camera.near, - cameraUp: this.camera.up, - cameraZoom: this.camera.zoom, - gizmoMatrix: this._gizmos.matrix + state = JSON.stringify( { + arcballState: { + cameraFar: this.object.far, + cameraFov: this.object.fov, + cameraMatrix: this.object.matrix, + cameraNear: this.object.near, + cameraUp: this.object.up, + cameraZoom: this.object.zoom, + gizmoMatrix: this._gizmos.matrix, + target: this.target - } } ); + } + } ); } @@ -2049,6 +2331,10 @@ class ArcballControls extends EventDispatcher { } + /** + * Set the controls state from the clipboard, assumes that the clipboard stores a JSON + * text as saved from `copyState()`. + */ pasteState() { const self = this; @@ -2061,57 +2347,63 @@ class ArcballControls extends EventDispatcher { } /** - * Save the current state of the control. This can later be recover with .reset + * Saves the current state of the control. This can later be recover with `reset()`. */ saveState() { - this._cameraMatrixState0.copy( this.camera.matrix ); + this.object.updateMatrix(); + this._gizmos.updateMatrix(); + + this._target0.copy( this.target ); + this._cameraMatrixState0.copy( this.object.matrix ); this._gizmoMatrixState0.copy( this._gizmos.matrix ); - this._nearPos = this.camera.near; - this._farPos = this.camera.far; - this._zoom0 = this.camera.zoom; - this._up0.copy( this.camera.up ); + this._nearPos = this.object.near; + this._farPos = this.object.far; + this._zoom0 = this.object.zoom; + this._up0.copy( this.object.up ); - if ( this.camera.isPerspectiveCamera ) { + if ( this.object.isPerspectiveCamera ) { - this._fov0 = this.camera.fov; + this._fov0 = this.object.fov; } } /** - * Perform uniform scale operation around a given point - * @param {Number} size Scale factor - * @param {Vector3} point Point around which scale - * @param {Boolean} scaleGizmos If gizmos should be scaled (Perspective only) - * @returns {Object} Object with 'camera' and 'gizmo' fields containing transformation matrices resulting from the operation to be applied to the camera and gizmos + * Performs uniform scale operation around a given point. + * + * @private + * @param {number} size - Scale factor. + * @param {Vector3} point - Point around which scale. + * @param {boolean} scaleGizmos - If gizmos should be scaled (Perspective only). + * @returns {Object} Object with 'camera' and 'gizmo' fields containing transformation matrices resulting from the operation to be applied to the camera and gizmos. */ scale( size, point, scaleGizmos = true ) { _scalePointTemp.copy( point ); let sizeInverse = 1 / size; - if ( this.camera.isOrthographicCamera ) { + if ( this.object.isOrthographicCamera ) { //camera zoom - this.camera.zoom = this._zoomState; - this.camera.zoom *= size; + this.object.zoom = this._zoomState; + this.object.zoom *= size; //check min and max zoom - if ( this.camera.zoom > this.maxZoom ) { + if ( this.object.zoom > this.maxZoom ) { - this.camera.zoom = this.maxZoom; + this.object.zoom = this.maxZoom; sizeInverse = this._zoomState / this.maxZoom; - } else if ( this.camera.zoom < this.minZoom ) { + } else if ( this.object.zoom < this.minZoom ) { - this.camera.zoom = this.minZoom; + this.object.zoom = this.minZoom; sizeInverse = this._zoomState / this.minZoom; } - this.camera.updateProjectionMatrix(); + this.object.updateProjectionMatrix(); this._v3_1.setFromMatrixPosition( this._gizmoMatrixState ); //gizmos position @@ -2135,7 +2427,7 @@ class ArcballControls extends EventDispatcher { this.setTransformationMatrices( this._m4_1, this._m4_2 ); return _transformation; - } else if ( this.camera.isPerspectiveCamera ) { + } else if ( this.object.isPerspectiveCamera ) { this._v3_1.setFromMatrixPosition( this._cameraMatrixState ); this._v3_2.setFromMatrixPosition( this._gizmoMatrixState ); @@ -2197,26 +2489,30 @@ class ArcballControls extends EventDispatcher { } /** - * Set camera fov - * @param {Number} value fov to be setted + * Sets camera fov. + * + * @private + * @param {number} value - The FOV to be set. */ setFov( value ) { - if ( this.camera.isPerspectiveCamera ) { + if ( this.object.isPerspectiveCamera ) { - this.camera.fov = MathUtils.clamp( value, this.minFov, this.maxFov ); - this.camera.updateProjectionMatrix(); + this.object.fov = MathUtils.clamp( value, this.minFov, this.maxFov ); + this.object.updateProjectionMatrix(); } } /** - * Set values in transformation object - * @param {Matrix4} camera Transformation to be applied to the camera - * @param {Matrix4} gizmos Transformation to be applied to gizmos + * Sets values in transformation object. + * + * @private + * @param {Matrix4} [camera=null] - Transformation to be applied to the camera. + * @param {Matrix4} [gizmos=null] - Transformation to be applied to gizmos. */ - setTransformationMatrices( camera = null, gizmos = null ) { + setTransformationMatrices( camera = null, gizmos = null ) { if ( camera != null ) { @@ -2257,10 +2553,12 @@ class ArcballControls extends EventDispatcher { } /** - * Rotate camera around its direction axis passing by a given point by a given angle - * @param {Vector3} point The point where the rotation axis is passing trough - * @param {Number} angle Angle in radians - * @returns The computed transormation matix + * Rotates camera around its direction axis passing by a given point by a given angle. + * + * @private + * @param {Vector3} point - The point where the rotation axis is passing trough. + * @param {number} angle - Angle in radians. + * @returns {Object} The computed transformation matrix. */ zRotate( point, angle ) { @@ -2282,7 +2580,12 @@ class ArcballControls extends EventDispatcher { } - + /** + * Returns the raycaster that is used for user interaction. This object is shared between all + * instances of `ArcballControls`. + * + * @returns {Raycaster} The internal raycaster. + */ getRaycaster() { return _raycaster; @@ -2291,10 +2594,12 @@ class ArcballControls extends EventDispatcher { /** - * Unproject the cursor on the 3D object surface - * @param {Vector2} cursor Cursor coordinates in NDC - * @param {Camera} camera Virtual camera - * @returns {Vector3} The point of intersection with the model, if exist, null otherwise + * Unprojects the cursor on the 3D object surface. + * + * @private + * @param {Vector2} cursor - Cursor coordinates in NDC. + * @param {Camera} camera - Virtual camera. + * @returns {?Vector3} The point of intersection with the model, if exist, null otherwise. */ unprojectOnObj( cursor, camera ) { @@ -2320,13 +2625,15 @@ class ArcballControls extends EventDispatcher { } /** - * Unproject the cursor on the trackball surface - * @param {Camera} camera The virtual camera - * @param {Number} cursorX Cursor horizontal coordinate on screen - * @param {Number} cursorY Cursor vertical coordinate on screen - * @param {HTMLElement} canvas The canvas where the renderer draws its output - * @param {number} tbRadius The trackball radius - * @returns {Vector3} The unprojected point on the trackball surface + * Unproject the cursor on the trackball surface. + * + * @private + * @param {Camera} camera - The virtual camera. + * @param {number} cursorX - Cursor horizontal coordinate on screen. + * @param {number} cursorY - Cursor vertical coordinate on screen. + * @param {HTMLElement} canvas - The canvas where the renderer draws its output. + * @param {number} tbRadius - The trackball radius. + * @returns {Vector3} The unprojected point on the trackball surface. */ unprojectOnTbSurface( camera, cursorX, cursorY, canvas, tbRadius ) { @@ -2450,13 +2757,15 @@ class ArcballControls extends EventDispatcher { /** - * Unproject the cursor on the plane passing through the center of the trackball orthogonal to the camera - * @param {Camera} camera The virtual camera - * @param {Number} cursorX Cursor horizontal coordinate on screen - * @param {Number} cursorY Cursor vertical coordinate on screen - * @param {HTMLElement} canvas The canvas where the renderer draws its output - * @param {Boolean} initialDistance If initial distance between camera and gizmos should be used for calculations instead of current (Perspective only) - * @returns {Vector3} The unprojected point on the trackball plane + * Unprojects the cursor on the plane passing through the center of the trackball orthogonal to the camera. + * + * @private + * @param {Camera} camera - The virtual camera. + * @param {number} cursorX - Cursor horizontal coordinate on screen. + * @param {number} cursorY - Cursor vertical coordinate on screen. + * @param {HTMLElement} canvas - The canvas where the renderer draws its output. + * @param {boolean} [initialDistance=false] - If initial distance between camera and gizmos should be used for calculations instead of current (Perspective only). + * @returns {Vector3} The unprojected point on the trackball plane. */ unprojectOnTbPlane( camera, cursorX, cursorY, canvas, initialDistance = false ) { @@ -2530,32 +2839,36 @@ class ArcballControls extends EventDispatcher { } /** - * Update camera and gizmos state + * Updates camera and gizmos state. + * + * @private */ updateMatrixState() { //update camera and gizmos state - this._cameraMatrixState.copy( this.camera.matrix ); + this._cameraMatrixState.copy( this.object.matrix ); this._gizmoMatrixState.copy( this._gizmos.matrix ); - if ( this.camera.isOrthographicCamera ) { + if ( this.object.isOrthographicCamera ) { - this._cameraProjectionState.copy( this.camera.projectionMatrix ); - this.camera.updateProjectionMatrix(); - this._zoomState = this.camera.zoom; + this._cameraProjectionState.copy( this.object.projectionMatrix ); + this.object.updateProjectionMatrix(); + this._zoomState = this.object.zoom; - } else if ( this.camera.isPerspectiveCamera ) { + } else if ( this.object.isPerspectiveCamera ) { - this._fovState = this.camera.fov; + this._fovState = this.object.fov; } } /** - * Update the trackball FSA - * @param {STATE} newState New state of the FSA - * @param {Boolean} updateMatrices If matriices state should be updated + * Updates the trackball FSA. + * + * @private + * @param {STATE} newState - New state of the FSA. + * @param {boolean} updateMatrices - If matrices state should be updated. */ updateTbState( newState, updateMatrices ) { @@ -2570,53 +2883,51 @@ class ArcballControls extends EventDispatcher { update() { - const EPS = 0.000001; - if ( this.target.equals( this._currentTarget ) === false ) { this._gizmos.position.copy( this.target ); //for correct radius calculation - this._tbRadius = this.calculateTbRadius( this.camera ); + this._tbRadius = this.calculateTbRadius( this.object ); this.makeGizmos( this.target, this._tbRadius ); this._currentTarget.copy( this.target ); } //check min/max parameters - if ( this.camera.isOrthographicCamera ) { + if ( this.object.isOrthographicCamera ) { //check zoom - if ( this.camera.zoom > this.maxZoom || this.camera.zoom < this.minZoom ) { + if ( this.object.zoom > this.maxZoom || this.object.zoom < this.minZoom ) { - const newZoom = MathUtils.clamp( this.camera.zoom, this.minZoom, this.maxZoom ); - this.applyTransformMatrix( this.scale( newZoom / this.camera.zoom, this._gizmos.position, true ) ); + const newZoom = MathUtils.clamp( this.object.zoom, this.minZoom, this.maxZoom ); + this.applyTransformMatrix( this.scale( newZoom / this.object.zoom, this._gizmos.position, true ) ); } - } else if ( this.camera.isPerspectiveCamera ) { + } else if ( this.object.isPerspectiveCamera ) { //check distance - const distance = this.camera.position.distanceTo( this._gizmos.position ); + const distance = this.object.position.distanceTo( this._gizmos.position ); - if ( distance > this.maxDistance + EPS || distance < this.minDistance - EPS ) { + if ( distance > this.maxDistance + _EPS || distance < this.minDistance - _EPS ) { const newDistance = MathUtils.clamp( distance, this.minDistance, this.maxDistance ); this.applyTransformMatrix( this.scale( newDistance / distance, this._gizmos.position ) ); this.updateMatrixState(); - } + } //check fov - if ( this.camera.fov < this.minFov || this.camera.fov > this.maxFov ) { + if ( this.object.fov < this.minFov || this.object.fov > this.maxFov ) { - this.camera.fov = MathUtils.clamp( this.camera.fov, this.minFov, this.maxFov ); - this.camera.updateProjectionMatrix(); + this.object.fov = MathUtils.clamp( this.object.fov, this.minFov, this.maxFov ); + this.object.updateProjectionMatrix(); } const oldRadius = this._tbRadius; - this._tbRadius = this.calculateTbRadius( this.camera ); + this._tbRadius = this.calculateTbRadius( this.object ); - if ( oldRadius < this._tbRadius - EPS || oldRadius > this._tbRadius + EPS ) { + if ( oldRadius < this._tbRadius - _EPS || oldRadius > this._tbRadius + _EPS ) { const scale = ( this._gizmos.scale.x + this._gizmos.scale.y + this._gizmos.scale.z ) / 3; const newRadius = this._tbRadius / scale; @@ -2634,7 +2945,7 @@ class ArcballControls extends EventDispatcher { } - this.camera.lookAt( this._gizmos.position ); + this.object.lookAt( this._gizmos.position ); } @@ -2644,35 +2955,37 @@ class ArcballControls extends EventDispatcher { if ( state.arcballState != undefined ) { + this.target.fromArray( state.arcballState.target ); + this._cameraMatrixState.fromArray( state.arcballState.cameraMatrix.elements ); - this._cameraMatrixState.decompose( this.camera.position, this.camera.quaternion, this.camera.scale ); + this._cameraMatrixState.decompose( this.object.position, this.object.quaternion, this.object.scale ); - this.camera.up.copy( state.arcballState.cameraUp ); - this.camera.near = state.arcballState.cameraNear; - this.camera.far = state.arcballState.cameraFar; + this.object.up.copy( state.arcballState.cameraUp ); + this.object.near = state.arcballState.cameraNear; + this.object.far = state.arcballState.cameraFar; - this.camera.zoom = state.arcballState.cameraZoom; + this.object.zoom = state.arcballState.cameraZoom; - if ( this.camera.isPerspectiveCamera ) { + if ( this.object.isPerspectiveCamera ) { - this.camera.fov = state.arcballState.cameraFov; + this.object.fov = state.arcballState.cameraFov; } this._gizmoMatrixState.fromArray( state.arcballState.gizmoMatrix.elements ); this._gizmoMatrixState.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale ); - this.camera.updateMatrix(); - this.camera.updateProjectionMatrix(); + this.object.updateMatrix(); + this.object.updateProjectionMatrix(); this._gizmos.updateMatrix(); - this._tbRadius = this.calculateTbRadius( this.camera ); + this._tbRadius = this.calculateTbRadius( this.object ); const gizmoTmp = new Matrix4().copy( this._gizmoMatrixState0 ); this.makeGizmos( this._gizmos.position, this._tbRadius ); this._gizmoMatrixState0.copy( gizmoTmp ); - this.camera.lookAt( this._gizmos.position ); + this.object.lookAt( this._gizmos.position ); this.updateTbState( STATE.IDLE, false ); this.dispatchEvent( _changeEvent ); @@ -2688,7 +3001,7 @@ class ArcballControls extends EventDispatcher { function onWindowResize() { const scale = ( this._gizmos.scale.x + this._gizmos.scale.y + this._gizmos.scale.z ) / 3; - this._tbRadius = this.calculateTbRadius( this.camera ); + this._tbRadius = this.calculateTbRadius( this.object ); const newRadius = this._tbRadius / scale; const curve = new EllipseCurve( 0, 0, newRadius, newRadius ); @@ -3097,13 +3410,13 @@ function onWheel( event ) { let scalePoint; - if ( this.camera.isOrthographicCamera ) { + if ( this.object.isOrthographicCamera ) { - scalePoint = this.unprojectOnTbPlane( this.camera, event.clientX, event.clientY, this.domElement ).applyQuaternion( this.camera.quaternion ).multiplyScalar( 1 / this.camera.zoom ).add( this._gizmos.position ); + scalePoint = this.unprojectOnTbPlane( this.object, event.clientX, event.clientY, this.domElement ).applyQuaternion( this.object.quaternion ).multiplyScalar( 1 / this.object.zoom ).add( this._gizmos.position ); - } else if ( this.camera.isPerspectiveCamera ) { + } else if ( this.object.isPerspectiveCamera ) { - scalePoint = this.unprojectOnTbPlane( this.camera, event.clientX, event.clientY, this.domElement ).applyQuaternion( this.camera.quaternion ).add( this._gizmos.position ); + scalePoint = this.unprojectOnTbPlane( this.object, event.clientX, event.clientY, this.domElement ).applyQuaternion( this.object.quaternion ).add( this._gizmos.position ); } @@ -3131,7 +3444,7 @@ function onWheel( event ) { case 'FOV': - if ( this.camera.isPerspectiveCamera ) { + if ( this.object.isPerspectiveCamera ) { this.updateTbState( STATE.FOV, true ); @@ -3174,7 +3487,7 @@ function onWheel( event ) { //check min and max distance xNew = MathUtils.clamp( xNew, this.minDistance, this.maxDistance ); - const y = x * Math.tan( MathUtils.DEG2RAD * this.camera.fov * 0.5 ); + const y = x * Math.tan( MathUtils.DEG2RAD * this.object.fov * 0.5 ); //calculate new fov let newFov = MathUtils.RAD2DEG * ( Math.atan( y / xNew ) * 2 ); diff --git a/examples/jsm/controls/DragControls.js b/examples/jsm/controls/DragControls.js index 4db481327dcab5..fbb6c606c83533 100644 --- a/examples/jsm/controls/DragControls.js +++ b/examples/jsm/controls/DragControls.js @@ -1,220 +1,502 @@ import { - EventDispatcher, + Controls, Matrix4, Plane, Raycaster, Vector2, - Vector3 + Vector3, + MOUSE, + TOUCH } from 'three'; const _plane = new Plane(); -const _raycaster = new Raycaster(); const _pointer = new Vector2(); const _offset = new Vector3(); +const _diff = new Vector2(); +const _previousPointer = new Vector2(); const _intersection = new Vector3(); const _worldPosition = new Vector3(); const _inverseMatrix = new Matrix4(); -class DragControls extends EventDispatcher { +const _up = new Vector3(); +const _right = new Vector3(); + +let _selected = null, _hovered = null; +const _intersections = []; + +const STATE = { + NONE: - 1, + PAN: 0, + ROTATE: 1 +}; + +/** + * This class can be used to provide a drag'n'drop interaction. + * + * ```js + * const controls = new DragControls( objects, camera, renderer.domElement ); + * + * // add event listener to highlight dragged objects + * controls.addEventListener( 'dragstart', function ( event ) { + * + * event.object.material.emissive.set( 0xaaaaaa ); + * + * } ); + * + * controls.addEventListener( 'dragend', function ( event ) { + * + * event.object.material.emissive.set( 0x000000 ); + * + * } ); + * ``` + * + * @augments Controls + * @three_import import { DragControls } from 'three/addons/controls/DragControls.js'; + */ +class DragControls extends Controls { + + /** + * Constructs a new controls instance. + * + * @param {Array} objects - An array of draggable 3D objects. + * @param {Camera} camera - The camera of the rendered scene. + * @param {?HTMLDOMElement} [domElement=null] - The HTML DOM element used for event listeners. + */ + constructor( objects, camera, domElement = null ) { + + super( camera, domElement ); + + /** + * An array of draggable 3D objects. + * + * @type {Array} + */ + this.objects = objects; + + /** + * Whether children of draggable objects can be dragged independently from their parent. + * + * @type {boolean} + * @default true + */ + this.recursive = true; + + /** + * This option only works if the `objects` array contains a single draggable group object. + * If set to `true`, the controls does not transform individual objects but the entire group. + * + * @type {boolean} + * @default false + */ + this.transformGroup = false; - constructor( _objects, _camera, _domElement ) { + /** + * The speed at which the object will rotate when dragged in `rotate` mode. + * The higher the number the faster the rotation. + * + * @type {number} + * @default 1 + */ + this.rotateSpeed = 1; - super(); + /** + * The raycaster used for detecting 3D objects. + * + * @type {Raycaster} + */ + this.raycaster = new Raycaster(); - _domElement.style.touchAction = 'none'; // disable touch scroll + // interaction - let _selected = null, _hovered = null; + this.mouseButtons = { LEFT: MOUSE.PAN, MIDDLE: MOUSE.PAN, RIGHT: MOUSE.ROTATE }; + this.touches = { ONE: TOUCH.PAN }; - const _intersections = []; + // event listeners - // + this._onPointerMove = onPointerMove.bind( this ); + this._onPointerDown = onPointerDown.bind( this ); + this._onPointerCancel = onPointerCancel.bind( this ); + this._onContextMenu = onContextMenu.bind( this ); - const scope = this; + // - function activate() { + if ( domElement !== null ) { - _domElement.addEventListener( 'pointermove', onPointerMove ); - _domElement.addEventListener( 'pointerdown', onPointerDown ); - _domElement.addEventListener( 'pointerup', onPointerCancel ); - _domElement.addEventListener( 'pointerleave', onPointerCancel ); + this.connect( domElement ); } - function deactivate() { + } - _domElement.removeEventListener( 'pointermove', onPointerMove ); - _domElement.removeEventListener( 'pointerdown', onPointerDown ); - _domElement.removeEventListener( 'pointerup', onPointerCancel ); - _domElement.removeEventListener( 'pointerleave', onPointerCancel ); + connect( element ) { - _domElement.style.cursor = ''; + super.connect( element ); - } + this.domElement.addEventListener( 'pointermove', this._onPointerMove ); + this.domElement.addEventListener( 'pointerdown', this._onPointerDown ); + this.domElement.addEventListener( 'pointerup', this._onPointerCancel ); + this.domElement.addEventListener( 'pointerleave', this._onPointerCancel ); + this.domElement.addEventListener( 'contextmenu', this._onContextMenu ); - function dispose() { + this.domElement.style.touchAction = 'none'; // disable touch scroll - deactivate(); + } - } + disconnect() { + + this.domElement.removeEventListener( 'pointermove', this._onPointerMove ); + this.domElement.removeEventListener( 'pointerdown', this._onPointerDown ); + this.domElement.removeEventListener( 'pointerup', this._onPointerCancel ); + this.domElement.removeEventListener( 'pointerleave', this._onPointerCancel ); + this.domElement.removeEventListener( 'contextmenu', this._onContextMenu ); + + this.domElement.style.touchAction = 'auto'; + this.domElement.style.cursor = ''; + + } + + dispose() { + + this.disconnect(); + + } + + _updatePointer( event ) { + + const rect = this.domElement.getBoundingClientRect(); + + _pointer.x = ( event.clientX - rect.left ) / rect.width * 2 - 1; + _pointer.y = - ( event.clientY - rect.top ) / rect.height * 2 + 1; + + } + + _updateState( event ) { + + // determine action + + let action; + + if ( event.pointerType === 'touch' ) { + + action = this.touches.ONE; + + } else { + + switch ( event.button ) { + + case 0: - function getObjects() { + action = this.mouseButtons.LEFT; + break; - return _objects; + case 1: + + action = this.mouseButtons.MIDDLE; + break; + + case 2: + + action = this.mouseButtons.RIGHT; + break; + + default: + + action = null; + + } } - function getRaycaster() { + // determine state + + switch ( action ) { + + case MOUSE.PAN: + case TOUCH.PAN: + + this.state = STATE.PAN; + + break; + + case MOUSE.ROTATE: + case TOUCH.ROTATE: + + this.state = STATE.ROTATE; + + break; - return _raycaster; + default: + + this.state = STATE.NONE; } - function onPointerMove( event ) { + } - if ( scope.enabled === false ) return; + getRaycaster() { - updatePointer( event ); + console.warn( 'THREE.DragControls: getRaycaster() has been deprecated. Use controls.raycaster instead.' ); // @deprecated r169 - _raycaster.setFromCamera( _pointer, _camera ); + return this.raycaster; - if ( _selected ) { + } - if ( _raycaster.ray.intersectPlane( _plane, _intersection ) ) { + setObjects( objects ) { - _selected.position.copy( _intersection.sub( _offset ).applyMatrix4( _inverseMatrix ) ); + console.warn( 'THREE.DragControls: setObjects() has been deprecated. Use controls.objects instead.' ); // @deprecated r169 - } + this.objects = objects; - scope.dispatchEvent( { type: 'drag', object: _selected } ); + } - return; + getObjects() { - } + console.warn( 'THREE.DragControls: getObjects() has been deprecated. Use controls.objects instead.' ); // @deprecated r169 + + return this.objects; - // hover support + } - if ( event.pointerType === 'mouse' || event.pointerType === 'pen' ) { + activate() { - _intersections.length = 0; + console.warn( 'THREE.DragControls: activate() has been renamed to connect().' ); // @deprecated r169 + this.connect(); - _raycaster.setFromCamera( _pointer, _camera ); - _raycaster.intersectObjects( _objects, true, _intersections ); + } - if ( _intersections.length > 0 ) { + deactivate() { - const object = _intersections[ 0 ].object; + console.warn( 'THREE.DragControls: deactivate() has been renamed to disconnect().' ); // @deprecated r169 + this.disconnect(); - _plane.setFromNormalAndCoplanarPoint( _camera.getWorldDirection( _plane.normal ), _worldPosition.setFromMatrixPosition( object.matrixWorld ) ); + } - if ( _hovered !== object && _hovered !== null ) { + set mode( value ) { - scope.dispatchEvent( { type: 'hoveroff', object: _hovered } ); + console.warn( 'THREE.DragControls: The .mode property has been removed. Define the type of transformation via the .mouseButtons or .touches properties.' ); // @deprecated r169 - _domElement.style.cursor = 'auto'; - _hovered = null; + } - } + get mode() { - if ( _hovered !== object ) { + console.warn( 'THREE.DragControls: The .mode property has been removed. Define the type of transformation via the .mouseButtons or .touches properties.' ); // @deprecated r169 - scope.dispatchEvent( { type: 'hoveron', object: object } ); + } - _domElement.style.cursor = 'pointer'; - _hovered = object; +} - } +function onPointerMove( event ) { - } else { + const camera = this.object; + const domElement = this.domElement; + const raycaster = this.raycaster; - if ( _hovered !== null ) { + if ( this.enabled === false ) return; - scope.dispatchEvent( { type: 'hoveroff', object: _hovered } ); + this._updatePointer( event ); - _domElement.style.cursor = 'auto'; - _hovered = null; + raycaster.setFromCamera( _pointer, camera ); - } + if ( _selected ) { - } + if ( this.state === STATE.PAN ) { + + if ( raycaster.ray.intersectPlane( _plane, _intersection ) ) { + + _selected.position.copy( _intersection.sub( _offset ).applyMatrix4( _inverseMatrix ) ); } + } else if ( this.state === STATE.ROTATE ) { + + _diff.subVectors( _pointer, _previousPointer ).multiplyScalar( this.rotateSpeed ); + _selected.rotateOnWorldAxis( _up, _diff.x ); + _selected.rotateOnWorldAxis( _right.normalize(), - _diff.y ); + } - function onPointerDown( event ) { + this.dispatchEvent( { type: 'drag', object: _selected } ); - if ( scope.enabled === false ) return; + _previousPointer.copy( _pointer ); - updatePointer( event ); + } else { + + // hover support + + if ( event.pointerType === 'mouse' || event.pointerType === 'pen' ) { _intersections.length = 0; - _raycaster.setFromCamera( _pointer, _camera ); - _raycaster.intersectObjects( _objects, true, _intersections ); + raycaster.setFromCamera( _pointer, camera ); + raycaster.intersectObjects( this.objects, this.recursive, _intersections ); if ( _intersections.length > 0 ) { - _selected = ( scope.transformGroup === true ) ? _objects[ 0 ] : _intersections[ 0 ].object; + const object = _intersections[ 0 ].object; + + _plane.setFromNormalAndCoplanarPoint( camera.getWorldDirection( _plane.normal ), _worldPosition.setFromMatrixPosition( object.matrixWorld ) ); - _plane.setFromNormalAndCoplanarPoint( _camera.getWorldDirection( _plane.normal ), _worldPosition.setFromMatrixPosition( _selected.matrixWorld ) ); + if ( _hovered !== object && _hovered !== null ) { - if ( _raycaster.ray.intersectPlane( _plane, _intersection ) ) { + this.dispatchEvent( { type: 'hoveroff', object: _hovered } ); - _inverseMatrix.copy( _selected.parent.matrixWorld ).invert(); - _offset.copy( _intersection ).sub( _worldPosition.setFromMatrixPosition( _selected.matrixWorld ) ); + domElement.style.cursor = 'auto'; + _hovered = null; } - _domElement.style.cursor = 'move'; + if ( _hovered !== object ) { - scope.dispatchEvent( { type: 'dragstart', object: _selected } ); + this.dispatchEvent( { type: 'hoveron', object: object } ); - } + domElement.style.cursor = 'pointer'; + _hovered = object; + + } + + } else { + + if ( _hovered !== null ) { + + this.dispatchEvent( { type: 'hoveroff', object: _hovered } ); + + domElement.style.cursor = 'auto'; + _hovered = null; + } + + } } - function onPointerCancel() { + } - if ( scope.enabled === false ) return; + _previousPointer.copy( _pointer ); - if ( _selected ) { +} - scope.dispatchEvent( { type: 'dragend', object: _selected } ); +function onPointerDown( event ) { - _selected = null; + const camera = this.object; + const domElement = this.domElement; + const raycaster = this.raycaster; - } + if ( this.enabled === false ) return; + + this._updatePointer( event ); + this._updateState( event ); + + _intersections.length = 0; + + raycaster.setFromCamera( _pointer, camera ); + raycaster.intersectObjects( this.objects, this.recursive, _intersections ); + + if ( _intersections.length > 0 ) { + + if ( this.transformGroup === true ) { + + // look for the outermost group in the object's upper hierarchy + + _selected = findGroup( _intersections[ 0 ].object ); + + } else { - _domElement.style.cursor = _hovered ? 'pointer' : 'auto'; + _selected = _intersections[ 0 ].object; } - function updatePointer( event ) { + _plane.setFromNormalAndCoplanarPoint( camera.getWorldDirection( _plane.normal ), _worldPosition.setFromMatrixPosition( _selected.matrixWorld ) ); - const rect = _domElement.getBoundingClientRect(); + if ( raycaster.ray.intersectPlane( _plane, _intersection ) ) { - _pointer.x = ( event.clientX - rect.left ) / rect.width * 2 - 1; - _pointer.y = - ( event.clientY - rect.top ) / rect.height * 2 + 1; + if ( this.state === STATE.PAN ) { + + _inverseMatrix.copy( _selected.parent.matrixWorld ).invert(); + _offset.copy( _intersection ).sub( _worldPosition.setFromMatrixPosition( _selected.matrixWorld ) ); + + } else if ( this.state === STATE.ROTATE ) { + + // the controls only support Y+ up + _up.set( 0, 1, 0 ).applyQuaternion( camera.quaternion ).normalize(); + _right.set( 1, 0, 0 ).applyQuaternion( camera.quaternion ).normalize(); + + } } - activate(); + domElement.style.cursor = 'move'; - // API + this.dispatchEvent( { type: 'dragstart', object: _selected } ); - this.enabled = true; - this.transformGroup = false; + } + + _previousPointer.copy( _pointer ); + +} + +function onPointerCancel() { + + if ( this.enabled === false ) return; + + if ( _selected ) { + + this.dispatchEvent( { type: 'dragend', object: _selected } ); - this.activate = activate; - this.deactivate = deactivate; - this.dispose = dispose; - this.getObjects = getObjects; - this.getRaycaster = getRaycaster; + _selected = null; } + this.domElement.style.cursor = _hovered ? 'pointer' : 'auto'; + + this.state = STATE.NONE; + +} + +function onContextMenu( event ) { + + if ( this.enabled === false ) return; + + event.preventDefault(); + } +function findGroup( obj, group = null ) { + + if ( obj.isGroup ) group = obj; + + if ( obj.parent === null ) return group; + + return findGroup( obj.parent, group ); + +} + +/** + * Fires when the user drags a 3D object. + * + * @event DragControls#drag + * @type {Object} + */ + +/** + * Fires when the user has finished dragging a 3D object. + * + * @event DragControls#dragend + * @type {Object} + */ + +/** + * Fires when the pointer is moved onto a 3D object, or onto one of its children. + * + * @event DragControls#hoveron + * @type {Object} + */ + +/** + * Fires when the pointer is moved out of a 3D object. + * + * @event DragControls#hoveroff + * @type {Object} + */ + export { DragControls }; diff --git a/examples/jsm/controls/FirstPersonControls.js b/examples/jsm/controls/FirstPersonControls.js index 0d03421c8b8c54..a5eda5b5d3f7c8 100644 --- a/examples/jsm/controls/FirstPersonControls.js +++ b/examples/jsm/controls/FirstPersonControls.js @@ -1,4 +1,5 @@ import { + Controls, MathUtils, Spherical, Vector3 @@ -7,316 +8,437 @@ import { const _lookDirection = new Vector3(); const _spherical = new Spherical(); const _target = new Vector3(); - -class FirstPersonControls { - - constructor( object, domElement ) { - - this.object = object; - this.domElement = domElement; - - // API - - this.enabled = true; - +const _targetPosition = new Vector3(); + +/** + * This class is an alternative implementation of {@link FlyControls}. + * + * @augments Controls + * @three_import import { FirstPersonControls } from 'three/addons/controls/FirstPersonControls.js'; + */ +class FirstPersonControls extends Controls { + + /** + * Constructs a new controls instance. + * + * @param {Object3D} object - The object that is managed by the controls. + * @param {?HTMLDOMElement} domElement - The HTML element used for event listeners. + */ + constructor( object, domElement = null ) { + + super( object, domElement ); + + /** + * The movement speed. + * + * @type {number} + * @default 1 + */ this.movementSpeed = 1.0; + + /** + * The look around speed. + * + * @type {number} + * @default 0.005 + */ this.lookSpeed = 0.005; + /** + * Whether it's possible to vertically look around or not. + * + * @type {boolean} + * @default true + */ this.lookVertical = true; + + /** + * Whether the camera is automatically moved forward or not. + * + * @type {boolean} + * @default false + */ this.autoForward = false; + /** + * Whether it's possible to look around or not. + * + * @type {boolean} + * @default true + */ this.activeLook = true; + /** + * Whether or not the camera's height influences the forward movement speed. + * Use the properties `heightCoef`, `heightMin` and `heightMax` for configuration. + * + * @type {boolean} + * @default false + */ this.heightSpeed = false; + + /** + * Determines how much faster the camera moves when it's y-component is near `heightMax`. + * + * @type {number} + * @default 1 + */ this.heightCoef = 1.0; + + /** + * Lower camera height limit used for movement speed adjustment. + * + * @type {number} + * @default 0 + */ this.heightMin = 0.0; + + /** + * Upper camera height limit used for movement speed adjustment. + * + * @type {number} + * @default 1 + */ this.heightMax = 1.0; + /** + * Whether or not looking around is vertically constrained by `verticalMin` and `verticalMax`. + * + * @type {boolean} + * @default false + */ this.constrainVertical = false; + + /** + * How far you can vertically look around, lower limit. Range is `0` to `Math.PI` in radians. + * + * @type {number} + * @default 0 + */ this.verticalMin = 0; + + /** + * How far you can vertically look around, upper limit. Range is `0` to `Math.PI` in radians. + * + * @type {number} + * @default 0 + */ this.verticalMax = Math.PI; + /** + * Whether the mouse is pressed down or not. + * + * @type {boolean} + * @readonly + * @default false + */ this.mouseDragOn = false; // internals - this.autoSpeedFactor = 0.0; + this._autoSpeedFactor = 0.0; + + this._pointerX = 0; + this._pointerY = 0; - this.pointerX = 0; - this.pointerY = 0; + this._moveForward = false; + this._moveBackward = false; + this._moveLeft = false; + this._moveRight = false; - this.moveForward = false; - this.moveBackward = false; - this.moveLeft = false; - this.moveRight = false; + this._viewHalfX = 0; + this._viewHalfY = 0; - this.viewHalfX = 0; - this.viewHalfY = 0; + this._lat = 0; + this._lon = 0; - // private variables + // event listeners - let lat = 0; - let lon = 0; + this._onPointerMove = onPointerMove.bind( this ); + this._onPointerDown = onPointerDown.bind( this ); + this._onPointerUp = onPointerUp.bind( this ); + this._onContextMenu = onContextMenu.bind( this ); + this._onKeyDown = onKeyDown.bind( this ); + this._onKeyUp = onKeyUp.bind( this ); // - this.handleResize = function () { + if ( domElement !== null ) { + + this.connect( domElement ); + + this.handleResize(); + + } + + this._setOrientation(); + + } - if ( this.domElement === document ) { + connect( element ) { - this.viewHalfX = window.innerWidth / 2; - this.viewHalfY = window.innerHeight / 2; + super.connect( element ); - } else { + window.addEventListener( 'keydown', this._onKeyDown ); + window.addEventListener( 'keyup', this._onKeyUp ); - this.viewHalfX = this.domElement.offsetWidth / 2; - this.viewHalfY = this.domElement.offsetHeight / 2; + this.domElement.addEventListener( 'pointermove', this._onPointerMove ); + this.domElement.addEventListener( 'pointerdown', this._onPointerDown ); + this.domElement.addEventListener( 'pointerup', this._onPointerUp ); + this.domElement.addEventListener( 'contextmenu', this._onContextMenu ); - } + } - }; + disconnect() { - this.onPointerDown = function ( event ) { + window.removeEventListener( 'keydown', this._onKeyDown ); + window.removeEventListener( 'keyup', this._onKeyUp ); - if ( this.domElement !== document ) { + this.domElement.removeEventListener( 'pointerdown', this._onPointerMove ); + this.domElement.removeEventListener( 'pointermove', this._onPointerDown ); + this.domElement.removeEventListener( 'pointerup', this._onPointerUp ); + this.domElement.removeEventListener( 'contextmenu', this._onContextMenu ); - this.domElement.focus(); + } - } + dispose() { - if ( this.activeLook ) { + this.disconnect(); - switch ( event.button ) { + } - case 0: this.moveForward = true; break; - case 2: this.moveBackward = true; break; + /** + * Must be called if the application window is resized. + */ + handleResize() { - } + if ( this.domElement === document ) { - } + this._viewHalfX = window.innerWidth / 2; + this._viewHalfY = window.innerHeight / 2; - this.mouseDragOn = true; + } else { - }; + this._viewHalfX = this.domElement.offsetWidth / 2; + this._viewHalfY = this.domElement.offsetHeight / 2; - this.onPointerUp = function ( event ) { + } - if ( this.activeLook ) { + } - switch ( event.button ) { + /** + * Rotates the camera towards the defined target position. + * + * @param {number|Vector3} x - The x coordinate of the target position or alternatively a vector representing the target position. + * @param {number} y - The y coordinate of the target position. + * @param {number} z - The z coordinate of the target position. + * @return {FirstPersonControls} A reference to this controls. + */ + lookAt( x, y, z ) { - case 0: this.moveForward = false; break; - case 2: this.moveBackward = false; break; + if ( x.isVector3 ) { - } + _target.copy( x ); - } + } else { - this.mouseDragOn = false; + _target.set( x, y, z ); - }; + } - this.onPointerMove = function ( event ) { + this.object.lookAt( _target ); - if ( this.domElement === document ) { + this._setOrientation(); - this.pointerX = event.pageX - this.viewHalfX; - this.pointerY = event.pageY - this.viewHalfY; + return this; - } else { + } - this.pointerX = event.pageX - this.domElement.offsetLeft - this.viewHalfX; - this.pointerY = event.pageY - this.domElement.offsetTop - this.viewHalfY; + update( delta ) { - } + if ( this.enabled === false ) return; - }; + if ( this.heightSpeed ) { - this.onKeyDown = function ( event ) { + const y = MathUtils.clamp( this.object.position.y, this.heightMin, this.heightMax ); + const heightDelta = y - this.heightMin; - switch ( event.code ) { + this._autoSpeedFactor = delta * ( heightDelta * this.heightCoef ); - case 'ArrowUp': - case 'KeyW': this.moveForward = true; break; + } else { - case 'ArrowLeft': - case 'KeyA': this.moveLeft = true; break; + this._autoSpeedFactor = 0.0; - case 'ArrowDown': - case 'KeyS': this.moveBackward = true; break; + } - case 'ArrowRight': - case 'KeyD': this.moveRight = true; break; + const actualMoveSpeed = delta * this.movementSpeed; - case 'KeyR': this.moveUp = true; break; - case 'KeyF': this.moveDown = true; break; + if ( this._moveForward || ( this.autoForward && ! this._moveBackward ) ) this.object.translateZ( - ( actualMoveSpeed + this._autoSpeedFactor ) ); + if ( this._moveBackward ) this.object.translateZ( actualMoveSpeed ); - } + if ( this._moveLeft ) this.object.translateX( - actualMoveSpeed ); + if ( this._moveRight ) this.object.translateX( actualMoveSpeed ); - }; + if ( this._moveUp ) this.object.translateY( actualMoveSpeed ); + if ( this._moveDown ) this.object.translateY( - actualMoveSpeed ); - this.onKeyUp = function ( event ) { + let actualLookSpeed = delta * this.lookSpeed; - switch ( event.code ) { + if ( ! this.activeLook ) { - case 'ArrowUp': - case 'KeyW': this.moveForward = false; break; + actualLookSpeed = 0; - case 'ArrowLeft': - case 'KeyA': this.moveLeft = false; break; + } - case 'ArrowDown': - case 'KeyS': this.moveBackward = false; break; + let verticalLookRatio = 1; - case 'ArrowRight': - case 'KeyD': this.moveRight = false; break; + if ( this.constrainVertical ) { - case 'KeyR': this.moveUp = false; break; - case 'KeyF': this.moveDown = false; break; + verticalLookRatio = Math.PI / ( this.verticalMax - this.verticalMin ); - } + } - }; + this._lon -= this._pointerX * actualLookSpeed; + if ( this.lookVertical ) this._lat -= this._pointerY * actualLookSpeed * verticalLookRatio; - this.lookAt = function ( x, y, z ) { + this._lat = Math.max( - 85, Math.min( 85, this._lat ) ); - if ( x.isVector3 ) { + let phi = MathUtils.degToRad( 90 - this._lat ); + const theta = MathUtils.degToRad( this._lon ); - _target.copy( x ); + if ( this.constrainVertical ) { - } else { + phi = MathUtils.mapLinear( phi, 0, Math.PI, this.verticalMin, this.verticalMax ); - _target.set( x, y, z ); + } - } + const position = this.object.position; - this.object.lookAt( _target ); + _targetPosition.setFromSphericalCoords( 1, phi, theta ).add( position ); - setOrientation( this ); + this.object.lookAt( _targetPosition ); - return this; + } - }; + _setOrientation() { - this.update = function () { + const quaternion = this.object.quaternion; - const targetPosition = new Vector3(); + _lookDirection.set( 0, 0, - 1 ).applyQuaternion( quaternion ); + _spherical.setFromVector3( _lookDirection ); - return function update( delta ) { + this._lat = 90 - MathUtils.radToDeg( _spherical.phi ); + this._lon = MathUtils.radToDeg( _spherical.theta ); - if ( this.enabled === false ) return; + } - if ( this.heightSpeed ) { +} - const y = MathUtils.clamp( this.object.position.y, this.heightMin, this.heightMax ); - const heightDelta = y - this.heightMin; +function onPointerDown( event ) { - this.autoSpeedFactor = delta * ( heightDelta * this.heightCoef ); + if ( this.domElement !== document ) { - } else { + this.domElement.focus(); - this.autoSpeedFactor = 0.0; + } - } + if ( this.activeLook ) { - const actualMoveSpeed = delta * this.movementSpeed; + switch ( event.button ) { - if ( this.moveForward || ( this.autoForward && ! this.moveBackward ) ) this.object.translateZ( - ( actualMoveSpeed + this.autoSpeedFactor ) ); - if ( this.moveBackward ) this.object.translateZ( actualMoveSpeed ); + case 0: this._moveForward = true; break; + case 2: this._moveBackward = true; break; - if ( this.moveLeft ) this.object.translateX( - actualMoveSpeed ); - if ( this.moveRight ) this.object.translateX( actualMoveSpeed ); + } - if ( this.moveUp ) this.object.translateY( actualMoveSpeed ); - if ( this.moveDown ) this.object.translateY( - actualMoveSpeed ); + } - let actualLookSpeed = delta * this.lookSpeed; + this.mouseDragOn = true; - if ( ! this.activeLook ) { +} - actualLookSpeed = 0; +function onPointerUp( event ) { - } + if ( this.activeLook ) { - let verticalLookRatio = 1; + switch ( event.button ) { - if ( this.constrainVertical ) { + case 0: this._moveForward = false; break; + case 2: this._moveBackward = false; break; - verticalLookRatio = Math.PI / ( this.verticalMax - this.verticalMin ); + } - } + } - lon -= this.pointerX * actualLookSpeed; - if ( this.lookVertical ) lat -= this.pointerY * actualLookSpeed * verticalLookRatio; + this.mouseDragOn = false; - lat = Math.max( - 85, Math.min( 85, lat ) ); +} - let phi = MathUtils.degToRad( 90 - lat ); - const theta = MathUtils.degToRad( lon ); +function onPointerMove( event ) { - if ( this.constrainVertical ) { + if ( this.domElement === document ) { - phi = MathUtils.mapLinear( phi, 0, Math.PI, this.verticalMin, this.verticalMax ); + this._pointerX = event.pageX - this._viewHalfX; + this._pointerY = event.pageY - this._viewHalfY; - } + } else { - const position = this.object.position; + this._pointerX = event.pageX - this.domElement.offsetLeft - this._viewHalfX; + this._pointerY = event.pageY - this.domElement.offsetTop - this._viewHalfY; - targetPosition.setFromSphericalCoords( 1, phi, theta ).add( position ); + } - this.object.lookAt( targetPosition ); +} - }; +function onKeyDown( event ) { - }(); + switch ( event.code ) { - this.dispose = function () { + case 'ArrowUp': + case 'KeyW': this._moveForward = true; break; - this.domElement.removeEventListener( 'contextmenu', contextmenu ); - this.domElement.removeEventListener( 'pointerdown', _onPointerDown ); - this.domElement.removeEventListener( 'pointermove', _onPointerMove ); - this.domElement.removeEventListener( 'pointerup', _onPointerUp ); + case 'ArrowLeft': + case 'KeyA': this._moveLeft = true; break; - window.removeEventListener( 'keydown', _onKeyDown ); - window.removeEventListener( 'keyup', _onKeyUp ); + case 'ArrowDown': + case 'KeyS': this._moveBackward = true; break; - }; + case 'ArrowRight': + case 'KeyD': this._moveRight = true; break; - const _onPointerMove = this.onPointerMove.bind( this ); - const _onPointerDown = this.onPointerDown.bind( this ); - const _onPointerUp = this.onPointerUp.bind( this ); - const _onKeyDown = this.onKeyDown.bind( this ); - const _onKeyUp = this.onKeyUp.bind( this ); + case 'KeyR': this._moveUp = true; break; + case 'KeyF': this._moveDown = true; break; - this.domElement.addEventListener( 'contextmenu', contextmenu ); - this.domElement.addEventListener( 'pointerdown', _onPointerDown ); - this.domElement.addEventListener( 'pointermove', _onPointerMove ); - this.domElement.addEventListener( 'pointerup', _onPointerUp ); + } - window.addEventListener( 'keydown', _onKeyDown ); - window.addEventListener( 'keyup', _onKeyUp ); +} - function setOrientation( controls ) { +function onKeyUp( event ) { - const quaternion = controls.object.quaternion; + switch ( event.code ) { - _lookDirection.set( 0, 0, - 1 ).applyQuaternion( quaternion ); - _spherical.setFromVector3( _lookDirection ); + case 'ArrowUp': + case 'KeyW': this._moveForward = false; break; - lat = 90 - MathUtils.radToDeg( _spherical.phi ); - lon = MathUtils.radToDeg( _spherical.theta ); + case 'ArrowLeft': + case 'KeyA': this._moveLeft = false; break; - } + case 'ArrowDown': + case 'KeyS': this._moveBackward = false; break; - this.handleResize(); + case 'ArrowRight': + case 'KeyD': this._moveRight = false; break; - setOrientation( this ); + case 'KeyR': this._moveUp = false; break; + case 'KeyF': this._moveDown = false; break; } } -function contextmenu( event ) { +function onContextMenu( event ) { + + if ( this.enabled === false ) return; event.preventDefault(); diff --git a/examples/jsm/controls/FlyControls.js b/examples/jsm/controls/FlyControls.js index 0c6bb3a5442c30..d94e1ed156ffb7 100644 --- a/examples/jsm/controls/FlyControls.js +++ b/examples/jsm/controls/FlyControls.js @@ -1,281 +1,377 @@ import { - EventDispatcher, + Controls, Quaternion, Vector3 } from 'three'; +/** + * Fires when the camera has been transformed by the controls. + * + * @event FlyControls#change + * @type {Object} + */ const _changeEvent = { type: 'change' }; -class FlyControls extends EventDispatcher { +const _EPS = 0.000001; +const _tmpQuaternion = new Quaternion(); + +/** + * This class enables a navigation similar to fly modes in DCC tools like Blender. + * You can arbitrarily transform the camera in 3D space without any limitations + * (e.g. focus on a specific target). + * + * @augments Controls + * @three_import import { FlyControls } from 'three/addons/controls/FlyControls.js'; + */ +class FlyControls extends Controls { + + /** + * Constructs a new controls instance. + * + * @param {Object3D} object - The object that is managed by the controls. + * @param {?HTMLDOMElement} domElement - The HTML element used for event listeners. + */ + constructor( object, domElement = null ) { + + super( object, domElement ); + + /** + * The movement speed. + * + * @type {number} + * @default 1 + */ + this.movementSpeed = 1.0; - constructor( object, domElement ) { + /** + * The rotation speed. + * + * @type {number} + * @default 0.005 + */ + this.rollSpeed = 0.005; - super(); + /** + * If set to `true`, you can only look around by performing a drag interaction. + * + * @type {boolean} + * @default false + */ + this.dragToLook = false; - this.object = object; - this.domElement = domElement; + /** + * If set to `true`, the camera automatically moves forward (and does not stop) when initially translated. + * + * @type {boolean} + * @default false + */ + this.autoForward = false; - // API + // internals - this.movementSpeed = 1.0; - this.rollSpeed = 0.005; + this._moveState = { up: 0, down: 0, left: 0, right: 0, forward: 0, back: 0, pitchUp: 0, pitchDown: 0, yawLeft: 0, yawRight: 0, rollLeft: 0, rollRight: 0 }; + this._moveVector = new Vector3( 0, 0, 0 ); + this._rotationVector = new Vector3( 0, 0, 0 ); + this._lastQuaternion = new Quaternion(); + this._lastPosition = new Vector3(); + this._status = 0; - this.dragToLook = false; - this.autoForward = false; + // event listeners - // disable default target object behavior + this._onKeyDown = onKeyDown.bind( this ); + this._onKeyUp = onKeyUp.bind( this ); + this._onPointerMove = onPointerMove.bind( this ); + this._onPointerDown = onPointerDown.bind( this ); + this._onPointerUp = onPointerUp.bind( this ); + this._onPointerCancel = onPointerCancel.bind( this ); + this._onContextMenu = onContextMenu.bind( this ); - // internals + // + + if ( domElement !== null ) { + + this.connect( domElement ); + + } + + } + + connect( element ) { + + super.connect( element ); + + window.addEventListener( 'keydown', this._onKeyDown ); + window.addEventListener( 'keyup', this._onKeyUp ); + + this.domElement.addEventListener( 'pointermove', this._onPointerMove ); + this.domElement.addEventListener( 'pointerdown', this._onPointerDown ); + this.domElement.addEventListener( 'pointerup', this._onPointerUp ); + this.domElement.addEventListener( 'pointercancel', this._onPointerCancel ); + this.domElement.addEventListener( 'contextmenu', this._onContextMenu ); + + } + + disconnect() { + + window.removeEventListener( 'keydown', this._onKeyDown ); + window.removeEventListener( 'keyup', this._onKeyUp ); + + this.domElement.removeEventListener( 'pointermove', this._onPointerMove ); + this.domElement.removeEventListener( 'pointerdown', this._onPointerDown ); + this.domElement.removeEventListener( 'pointerup', this._onPointerUp ); + this.domElement.removeEventListener( 'pointercancel', this._onPointerCancel ); + this.domElement.removeEventListener( 'contextmenu', this._onContextMenu ); + + } - const scope = this; + dispose() { - const EPS = 0.000001; + this.disconnect(); - const lastQuaternion = new Quaternion(); - const lastPosition = new Vector3(); + } - this.tmpQuaternion = new Quaternion(); + update( delta ) { - this.status = 0; + if ( this.enabled === false ) return; - this.moveState = { up: 0, down: 0, left: 0, right: 0, forward: 0, back: 0, pitchUp: 0, pitchDown: 0, yawLeft: 0, yawRight: 0, rollLeft: 0, rollRight: 0 }; - this.moveVector = new Vector3( 0, 0, 0 ); - this.rotationVector = new Vector3( 0, 0, 0 ); + const object = this.object; - this.keydown = function ( event ) { + const moveMult = delta * this.movementSpeed; + const rotMult = delta * this.rollSpeed; - if ( event.altKey ) { + object.translateX( this._moveVector.x * moveMult ); + object.translateY( this._moveVector.y * moveMult ); + object.translateZ( this._moveVector.z * moveMult ); - return; + _tmpQuaternion.set( this._rotationVector.x * rotMult, this._rotationVector.y * rotMult, this._rotationVector.z * rotMult, 1 ).normalize(); + object.quaternion.multiply( _tmpQuaternion ); - } + if ( + this._lastPosition.distanceToSquared( object.position ) > _EPS || + 8 * ( 1 - this._lastQuaternion.dot( object.quaternion ) ) > _EPS + ) { - switch ( event.code ) { + this.dispatchEvent( _changeEvent ); + this._lastQuaternion.copy( object.quaternion ); + this._lastPosition.copy( object.position ); - case 'ShiftLeft': - case 'ShiftRight': this.movementSpeedMultiplier = .1; break; + } - case 'KeyW': this.moveState.forward = 1; break; - case 'KeyS': this.moveState.back = 1; break; + } - case 'KeyA': this.moveState.left = 1; break; - case 'KeyD': this.moveState.right = 1; break; + // private - case 'KeyR': this.moveState.up = 1; break; - case 'KeyF': this.moveState.down = 1; break; + _updateMovementVector() { - case 'ArrowUp': this.moveState.pitchUp = 1; break; - case 'ArrowDown': this.moveState.pitchDown = 1; break; + const forward = ( this._moveState.forward || ( this.autoForward && ! this._moveState.back ) ) ? 1 : 0; - case 'ArrowLeft': this.moveState.yawLeft = 1; break; - case 'ArrowRight': this.moveState.yawRight = 1; break; + this._moveVector.x = ( - this._moveState.left + this._moveState.right ); + this._moveVector.y = ( - this._moveState.down + this._moveState.up ); + this._moveVector.z = ( - forward + this._moveState.back ); - case 'KeyQ': this.moveState.rollLeft = 1; break; - case 'KeyE': this.moveState.rollRight = 1; break; + //console.log( 'move:', [ this._moveVector.x, this._moveVector.y, this._moveVector.z ] ); - } + } - this.updateMovementVector(); - this.updateRotationVector(); + _updateRotationVector() { - }; + this._rotationVector.x = ( - this._moveState.pitchDown + this._moveState.pitchUp ); + this._rotationVector.y = ( - this._moveState.yawRight + this._moveState.yawLeft ); + this._rotationVector.z = ( - this._moveState.rollRight + this._moveState.rollLeft ); - this.keyup = function ( event ) { + //console.log( 'rotate:', [ this._rotationVector.x, this._rotationVector.y, this._rotationVector.z ] ); - switch ( event.code ) { + } - case 'ShiftLeft': - case 'ShiftRight': this.movementSpeedMultiplier = 1; break; + _getContainerDimensions() { - case 'KeyW': this.moveState.forward = 0; break; - case 'KeyS': this.moveState.back = 0; break; + if ( this.domElement != document ) { - case 'KeyA': this.moveState.left = 0; break; - case 'KeyD': this.moveState.right = 0; break; + return { + size: [ this.domElement.offsetWidth, this.domElement.offsetHeight ], + offset: [ this.domElement.offsetLeft, this.domElement.offsetTop ] + }; - case 'KeyR': this.moveState.up = 0; break; - case 'KeyF': this.moveState.down = 0; break; + } else { - case 'ArrowUp': this.moveState.pitchUp = 0; break; - case 'ArrowDown': this.moveState.pitchDown = 0; break; + return { + size: [ window.innerWidth, window.innerHeight ], + offset: [ 0, 0 ] + }; - case 'ArrowLeft': this.moveState.yawLeft = 0; break; - case 'ArrowRight': this.moveState.yawRight = 0; break; + } - case 'KeyQ': this.moveState.rollLeft = 0; break; - case 'KeyE': this.moveState.rollRight = 0; break; + } - } +} - this.updateMovementVector(); - this.updateRotationVector(); +function onKeyDown( event ) { - }; + if ( event.altKey || this.enabled === false ) { - this.pointerdown = function ( event ) { + return; - if ( this.dragToLook ) { + } - this.status ++; + switch ( event.code ) { - } else { + case 'ShiftLeft': + case 'ShiftRight': this.movementSpeedMultiplier = .1; break; - switch ( event.button ) { + case 'KeyW': this._moveState.forward = 1; break; + case 'KeyS': this._moveState.back = 1; break; - case 0: this.moveState.forward = 1; break; - case 2: this.moveState.back = 1; break; + case 'KeyA': this._moveState.left = 1; break; + case 'KeyD': this._moveState.right = 1; break; - } + case 'KeyR': this._moveState.up = 1; break; + case 'KeyF': this._moveState.down = 1; break; - this.updateMovementVector(); + case 'ArrowUp': this._moveState.pitchUp = 1; break; + case 'ArrowDown': this._moveState.pitchDown = 1; break; - } + case 'ArrowLeft': this._moveState.yawLeft = 1; break; + case 'ArrowRight': this._moveState.yawRight = 1; break; - }; + case 'KeyQ': this._moveState.rollLeft = 1; break; + case 'KeyE': this._moveState.rollRight = 1; break; - this.pointermove = function ( event ) { + } - if ( ! this.dragToLook || this.status > 0 ) { + this._updateMovementVector(); + this._updateRotationVector(); - const container = this.getContainerDimensions(); - const halfWidth = container.size[ 0 ] / 2; - const halfHeight = container.size[ 1 ] / 2; +} - this.moveState.yawLeft = - ( ( event.pageX - container.offset[ 0 ] ) - halfWidth ) / halfWidth; - this.moveState.pitchDown = ( ( event.pageY - container.offset[ 1 ] ) - halfHeight ) / halfHeight; +function onKeyUp( event ) { - this.updateRotationVector(); + if ( this.enabled === false ) return; - } + switch ( event.code ) { - }; + case 'ShiftLeft': + case 'ShiftRight': this.movementSpeedMultiplier = 1; break; - this.pointerup = function ( event ) { + case 'KeyW': this._moveState.forward = 0; break; + case 'KeyS': this._moveState.back = 0; break; - if ( this.dragToLook ) { + case 'KeyA': this._moveState.left = 0; break; + case 'KeyD': this._moveState.right = 0; break; - this.status --; + case 'KeyR': this._moveState.up = 0; break; + case 'KeyF': this._moveState.down = 0; break; - this.moveState.yawLeft = this.moveState.pitchDown = 0; + case 'ArrowUp': this._moveState.pitchUp = 0; break; + case 'ArrowDown': this._moveState.pitchDown = 0; break; - } else { + case 'ArrowLeft': this._moveState.yawLeft = 0; break; + case 'ArrowRight': this._moveState.yawRight = 0; break; - switch ( event.button ) { + case 'KeyQ': this._moveState.rollLeft = 0; break; + case 'KeyE': this._moveState.rollRight = 0; break; - case 0: this.moveState.forward = 0; break; - case 2: this.moveState.back = 0; break; + } - } + this._updateMovementVector(); + this._updateRotationVector(); - this.updateMovementVector(); +} - } +function onPointerDown( event ) { - this.updateRotationVector(); + if ( this.enabled === false ) return; - }; + if ( this.dragToLook ) { - this.update = function ( delta ) { + this._status ++; - const moveMult = delta * scope.movementSpeed; - const rotMult = delta * scope.rollSpeed; + } else { - scope.object.translateX( scope.moveVector.x * moveMult ); - scope.object.translateY( scope.moveVector.y * moveMult ); - scope.object.translateZ( scope.moveVector.z * moveMult ); + switch ( event.button ) { - scope.tmpQuaternion.set( scope.rotationVector.x * rotMult, scope.rotationVector.y * rotMult, scope.rotationVector.z * rotMult, 1 ).normalize(); - scope.object.quaternion.multiply( scope.tmpQuaternion ); + case 0: this._moveState.forward = 1; break; + case 2: this._moveState.back = 1; break; - if ( - lastPosition.distanceToSquared( scope.object.position ) > EPS || - 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS - ) { + } - scope.dispatchEvent( _changeEvent ); - lastQuaternion.copy( scope.object.quaternion ); - lastPosition.copy( scope.object.position ); + this._updateMovementVector(); - } + } - }; +} + +function onPointerMove( event ) { + + if ( this.enabled === false ) return; + + if ( ! this.dragToLook || this._status > 0 ) { + + const container = this._getContainerDimensions(); + const halfWidth = container.size[ 0 ] / 2; + const halfHeight = container.size[ 1 ] / 2; + + this._moveState.yawLeft = - ( ( event.pageX - container.offset[ 0 ] ) - halfWidth ) / halfWidth; + this._moveState.pitchDown = ( ( event.pageY - container.offset[ 1 ] ) - halfHeight ) / halfHeight; - this.updateMovementVector = function () { + this._updateRotationVector(); - const forward = ( this.moveState.forward || ( this.autoForward && ! this.moveState.back ) ) ? 1 : 0; + } - this.moveVector.x = ( - this.moveState.left + this.moveState.right ); - this.moveVector.y = ( - this.moveState.down + this.moveState.up ); - this.moveVector.z = ( - forward + this.moveState.back ); +} - //console.log( 'move:', [ this.moveVector.x, this.moveVector.y, this.moveVector.z ] ); +function onPointerUp( event ) { - }; + if ( this.enabled === false ) return; - this.updateRotationVector = function () { + if ( this.dragToLook ) { - this.rotationVector.x = ( - this.moveState.pitchDown + this.moveState.pitchUp ); - this.rotationVector.y = ( - this.moveState.yawRight + this.moveState.yawLeft ); - this.rotationVector.z = ( - this.moveState.rollRight + this.moveState.rollLeft ); + this._status --; - //console.log( 'rotate:', [ this.rotationVector.x, this.rotationVector.y, this.rotationVector.z ] ); + this._moveState.yawLeft = this._moveState.pitchDown = 0; - }; + } else { - this.getContainerDimensions = function () { + switch ( event.button ) { - if ( this.domElement != document ) { + case 0: this._moveState.forward = 0; break; + case 2: this._moveState.back = 0; break; - return { - size: [ this.domElement.offsetWidth, this.domElement.offsetHeight ], - offset: [ this.domElement.offsetLeft, this.domElement.offsetTop ] - }; + } - } else { + this._updateMovementVector(); - return { - size: [ window.innerWidth, window.innerHeight ], - offset: [ 0, 0 ] - }; + } - } + this._updateRotationVector(); - }; +} - this.dispose = function () { +function onPointerCancel() { - this.domElement.removeEventListener( 'contextmenu', contextmenu ); - this.domElement.removeEventListener( 'pointerdown', _pointerdown ); - this.domElement.removeEventListener( 'pointermove', _pointermove ); - this.domElement.removeEventListener( 'pointerup', _pointerup ); + if ( this.enabled === false ) return; - window.removeEventListener( 'keydown', _keydown ); - window.removeEventListener( 'keyup', _keyup ); + if ( this.dragToLook ) { - }; + this._status = 0; - const _pointermove = this.pointermove.bind( this ); - const _pointerdown = this.pointerdown.bind( this ); - const _pointerup = this.pointerup.bind( this ); - const _keydown = this.keydown.bind( this ); - const _keyup = this.keyup.bind( this ); + this._moveState.yawLeft = this._moveState.pitchDown = 0; - this.domElement.addEventListener( 'contextmenu', contextmenu ); - this.domElement.addEventListener( 'pointerdown', _pointerdown ); - this.domElement.addEventListener( 'pointermove', _pointermove ); - this.domElement.addEventListener( 'pointerup', _pointerup ); + } else { - window.addEventListener( 'keydown', _keydown ); - window.addEventListener( 'keyup', _keyup ); + this._moveState.forward = 0; + this._moveState.back = 0; - this.updateMovementVector(); - this.updateRotationVector(); + this._updateMovementVector(); } + this._updateRotationVector(); + } -function contextmenu( event ) { +function onContextMenu( event ) { + + if ( this.enabled === false ) return; event.preventDefault(); diff --git a/examples/jsm/controls/MapControls.js b/examples/jsm/controls/MapControls.js index 5667905ade6863..e300e14b484f12 100644 --- a/examples/jsm/controls/MapControls.js +++ b/examples/jsm/controls/MapControls.js @@ -2,23 +2,57 @@ import { MOUSE, TOUCH } from 'three'; import { OrbitControls } from './OrbitControls.js'; -// MapControls performs orbiting, dollying (zooming), and panning. -// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). -// -// Orbit - right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate -// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish -// Pan - left mouse, or arrow keys / touch: one-finger move - +/** + * This class is intended for transforming a camera over a map from bird's eye perspective. + * The class shares its implementation with {@link OrbitControls} but uses a specific preset + * for mouse/touch interaction and disables screen space panning by default. + * + * - Orbit: Right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate. + * - Zoom: Middle mouse, or mousewheel / touch: two-finger spread or squish. + * - Pan: Left mouse, or arrow keys / touch: one-finger move. + * + * @augments OrbitControls + * @three_import import { MapControls } from 'three/addons/controls/MapControls.js'; + */ class MapControls extends OrbitControls { constructor( object, domElement ) { super( object, domElement ); - this.screenSpacePanning = false; // pan orthogonal to world-space direction camera.up - + /** + * Overwritten and set to `false` to pan orthogonal to world-space direction `camera.up`. + * + * @type {boolean} + * @default false + */ + this.screenSpacePanning = false; + + /** + * This object contains references to the mouse actions used by the controls. + * + * ```js + * controls.mouseButtons = { + * LEFT: THREE.MOUSE.PAN, + * MIDDLE: THREE.MOUSE.DOLLY, + * RIGHT: THREE.MOUSE.ROTATE + * } + * ``` + * @type {Object} + */ this.mouseButtons = { LEFT: MOUSE.PAN, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.ROTATE }; + /** + * This object contains references to the touch actions used by the controls. + * + * ```js + * controls.mouseButtons = { + * ONE: THREE.TOUCH.PAN, + * TWO: THREE.TOUCH.DOLLY_ROTATE + * } + * ``` + * @type {Object} + */ this.touches = { ONE: TOUCH.PAN, TWO: TOUCH.DOLLY_ROTATE }; } diff --git a/examples/jsm/controls/OrbitControls.js b/examples/jsm/controls/OrbitControls.js index e2a797cc636725..eea26706dae735 100644 --- a/examples/jsm/controls/OrbitControls.js +++ b/examples/jsm/controls/OrbitControls.js @@ -1,1263 +1,1857 @@ import { - EventDispatcher, + Controls, MOUSE, Quaternion, Spherical, TOUCH, Vector2, - Vector3 + Vector3, + Plane, + Ray, + MathUtils } from 'three'; -// OrbitControls performs orbiting, dollying (zooming), and panning. -// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). -// -// Orbit - left mouse / touch: one-finger move -// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish -// Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move - +/** + * Fires when the camera has been transformed by the controls. + * + * @event OrbitControls#change + * @type {Object} + */ const _changeEvent = { type: 'change' }; -const _startEvent = { type: 'start' }; -const _endEvent = { type: 'end' }; - -class OrbitControls extends EventDispatcher { - - constructor( object, domElement ) { - - super(); - this.object = object; - this.domElement = domElement; - this.domElement.style.touchAction = 'none'; // disable touch scroll +/** + * Fires when an interaction was initiated. + * + * @event OrbitControls#start + * @type {Object} + */ +const _startEvent = { type: 'start' }; - // Set to false to disable this control - this.enabled = true; +/** + * Fires when an interaction has finished. + * + * @event OrbitControls#end + * @type {Object} + */ +const _endEvent = { type: 'end' }; - // "target" sets the location of focus, where the object orbits around +const _ray = new Ray(); +const _plane = new Plane(); +const _TILT_LIMIT = Math.cos( 70 * MathUtils.DEG2RAD ); + +const _v = new Vector3(); +const _twoPI = 2 * Math.PI; + +const _STATE = { + NONE: - 1, + ROTATE: 0, + DOLLY: 1, + PAN: 2, + TOUCH_ROTATE: 3, + TOUCH_PAN: 4, + TOUCH_DOLLY_PAN: 5, + TOUCH_DOLLY_ROTATE: 6 +}; +const _EPS = 0.000001; + + +/** + * Orbit controls allow the camera to orbit around a target. + * + * OrbitControls performs orbiting, dollying (zooming), and panning. Unlike {@link TrackballControls}, + * it maintains the "up" direction `object.up` (+Y by default). + * + * - Orbit: Left mouse / touch: one-finger move. + * - Zoom: Middle mouse, or mousewheel / touch: two-finger spread or squish. + * - Pan: Right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move. + * + * ```js + * const controls = new OrbitControls( camera, renderer.domElement ); + * + * // controls.update() must be called after any manual changes to the camera's transform + * camera.position.set( 0, 20, 100 ); + * controls.update(); + * + * function animate() { + * + * // required if controls.enableDamping or controls.autoRotate are set to true + * controls.update(); + * + * renderer.render( scene, camera ); + * + * } + * ``` + * + * @augments Controls + * @three_import import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; + */ +class OrbitControls extends Controls { + + /** + * Constructs a new controls instance. + * + * @param {Object3D} object - The object that is managed by the controls. + * @param {?HTMLDOMElement} domElement - The HTML element used for event listeners. + */ + constructor( object, domElement = null ) { + + super( object, domElement ); + + this.state = _STATE.NONE; + + /** + * The focus point of the controls, the `object` orbits around this. + * It can be updated manually at any point to change the focus of the controls. + * + * @type {Vector3} + */ this.target = new Vector3(); - // How far you can dolly in and out ( PerspectiveCamera only ) + /** + * The focus point of the `minTargetRadius` and `maxTargetRadius` limits. + * It can be updated manually at any point to change the center of interest + * for the `target`. + * + * @type {Vector3} + */ + this.cursor = new Vector3(); + + /** + * How far you can dolly in (perspective camera only). + * + * @type {number} + * @default 0 + */ this.minDistance = 0; + + /** + * How far you can dolly out (perspective camera only). + * + * @type {number} + * @default Infinity + */ this.maxDistance = Infinity; - // How far you can zoom in and out ( OrthographicCamera only ) + /** + * How far you can zoom in (orthographic camera only). + * + * @type {number} + * @default 0 + */ this.minZoom = 0; - this.maxZoom = Infinity; - - // How far you can orbit vertically, upper and lower limits. - // Range is 0 to Math.PI radians. - this.minPolarAngle = 0; // radians - this.maxPolarAngle = Math.PI; // radians - // How far you can orbit horizontally, upper and lower limits. - // If set, the interval [ min, max ] must be a sub-interval of [ - 2 PI, 2 PI ], with ( max - min < 2 PI ) - this.minAzimuthAngle = - Infinity; // radians - this.maxAzimuthAngle = Infinity; // radians + /** + * How far you can zoom out (orthographic camera only). + * + * @type {number} + * @default Infinity + */ + this.maxZoom = Infinity; - // Set to true to enable damping (inertia) - // If damping is enabled, you must call controls.update() in your animation loop + /** + * How close you can get the target to the 3D `cursor`. + * + * @type {number} + * @default 0 + */ + this.minTargetRadius = 0; + + /** + * How far you can move the target from the 3D `cursor`. + * + * @type {number} + * @default Infinity + */ + this.maxTargetRadius = Infinity; + + /** + * How far you can orbit vertically, lower limit. Range is `[0, Math.PI]` radians. + * + * @type {number} + * @default 0 + */ + this.minPolarAngle = 0; + + /** + * How far you can orbit vertically, upper limit. Range is `[0, Math.PI]` radians. + * + * @type {number} + * @default Math.PI + */ + this.maxPolarAngle = Math.PI; + + /** + * How far you can orbit horizontally, lower limit. If set, the interval `[ min, max ]` + * must be a sub-interval of `[ - 2 PI, 2 PI ]`, with `( max - min < 2 PI )`. + * + * @type {number} + * @default -Infinity + */ + this.minAzimuthAngle = - Infinity; + + /** + * How far you can orbit horizontally, upper limit. If set, the interval `[ min, max ]` + * must be a sub-interval of `[ - 2 PI, 2 PI ]`, with `( max - min < 2 PI )`. + * + * @type {number} + * @default -Infinity + */ + this.maxAzimuthAngle = Infinity; + + /** + * Set to `true` to enable damping (inertia), which can be used to give a sense of weight + * to the controls. Note that if this is enabled, you must call `update()` in your animation + * loop. + * + * @type {boolean} + * @default false + */ this.enableDamping = false; + + /** + * The damping inertia used if `enableDamping` is set to `true`. + * + * Note that for this to work, you must call `update()` in your animation loop. + * + * @type {number} + * @default 0.05 + */ this.dampingFactor = 0.05; - // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. - // Set to false to disable zooming + /** + * Enable or disable zooming (dollying) of the camera. + * + * @type {boolean} + * @default true + */ this.enableZoom = true; + + /** + * Speed of zooming / dollying. + * + * @type {number} + * @default 1 + */ this.zoomSpeed = 1.0; - // Set to false to disable rotating + /** + * Enable or disable horizontal and vertical rotation of the camera. + * + * Note that it is possible to disable a single axis by setting the min and max of the + * `minPolarAngle` or `minAzimuthAngle` to the same value, which will cause the vertical + * or horizontal rotation to be fixed at that value. + * + * @type {boolean} + * @default true + */ this.enableRotate = true; + + /** + * Speed of rotation. + * + * @type {number} + * @default 1 + */ this.rotateSpeed = 1.0; - // Set to false to disable panning + /** + * How fast to rotate the camera when the keyboard is used. + * + * @type {number} + * @default 1 + */ + this.keyRotateSpeed = 1.0; + + /** + * Enable or disable camera panning. + * + * @type {boolean} + * @default true + */ this.enablePan = true; + + /** + * Speed of panning. + * + * @type {number} + * @default 1 + */ this.panSpeed = 1.0; - this.screenSpacePanning = true; // if false, pan orthogonal to world-space direction camera.up - this.keyPanSpeed = 7.0; // pixels moved per arrow key push - // Set to true to automatically rotate around the target - // If auto-rotate is enabled, you must call controls.update() in your animation loop + /** + * Defines how the camera's position is translated when panning. If `true`, the camera pans + * in screen space. Otherwise, the camera pans in the plane orthogonal to the camera's up + * direction. + * + * @type {boolean} + * @default true + */ + this.screenSpacePanning = true; + + /** + * How fast to pan the camera when the keyboard is used in + * pixels per keypress. + * + * @type {number} + * @default 7 + */ + this.keyPanSpeed = 7.0; + + /** + * Setting this property to `true` allows to zoom to the cursor's position. + * + * @type {boolean} + * @default false + */ + this.zoomToCursor = false; + + /** + * Set to true to automatically rotate around the target + * + * Note that if this is enabled, you must call `update()` in your animation loop. + * If you want the auto-rotate speed to be independent of the frame rate (the refresh + * rate of the display), you must pass the time `deltaTime`, in seconds, to `update()`. + * + * @type {boolean} + * @default false + */ this.autoRotate = false; - this.autoRotateSpeed = 2.0; // 30 seconds per orbit when fps is 60 - // The four arrow keys + /** + * How fast to rotate around the target if `autoRotate` is `true`. The default equates to 30 seconds + * per orbit at 60fps. + * + * Note that if `autoRotate` is enabled, you must call `update()` in your animation loop. + * + * @type {number} + * @default 2 + */ + this.autoRotateSpeed = 2.0; + + /** + * This object contains references to the keycodes for controlling camera panning. + * + * ```js + * controls.keys = { + * LEFT: 'ArrowLeft', //left arrow + * UP: 'ArrowUp', // up arrow + * RIGHT: 'ArrowRight', // right arrow + * BOTTOM: 'ArrowDown' // down arrow + * } + * ``` + * @type {Object} + */ this.keys = { LEFT: 'ArrowLeft', UP: 'ArrowUp', RIGHT: 'ArrowRight', BOTTOM: 'ArrowDown' }; - // Mouse buttons + /** + * This object contains references to the mouse actions used by the controls. + * + * ```js + * controls.mouseButtons = { + * LEFT: THREE.MOUSE.ROTATE, + * MIDDLE: THREE.MOUSE.DOLLY, + * RIGHT: THREE.MOUSE.PAN + * } + * ``` + * @type {Object} + */ this.mouseButtons = { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN }; - // Touch fingers + /** + * This object contains references to the touch actions used by the controls. + * + * ```js + * controls.mouseButtons = { + * ONE: THREE.TOUCH.ROTATE, + * TWO: THREE.TOUCH.DOLLY_PAN + * } + * ``` + * @type {Object} + */ this.touches = { ONE: TOUCH.ROTATE, TWO: TOUCH.DOLLY_PAN }; - // for reset + /** + * Used internally by `saveState()` and `reset()`. + * + * @type {Vector3} + */ this.target0 = this.target.clone(); + + /** + * Used internally by `saveState()` and `reset()`. + * + * @type {Vector3} + */ this.position0 = this.object.position.clone(); + + /** + * Used internally by `saveState()` and `reset()`. + * + * @type {number} + */ this.zoom0 = this.object.zoom; // the target DOM element for key events this._domElementKeyEvents = null; - // - // public methods - // + // internals - this.getPolarAngle = function () { + this._lastPosition = new Vector3(); + this._lastQuaternion = new Quaternion(); + this._lastTargetPosition = new Vector3(); - return spherical.phi; + // so camera.up is the orbit axis + this._quat = new Quaternion().setFromUnitVectors( object.up, new Vector3( 0, 1, 0 ) ); + this._quatInverse = this._quat.clone().invert(); - }; + // current position in spherical coordinates + this._spherical = new Spherical(); + this._sphericalDelta = new Spherical(); - this.getAzimuthalAngle = function () { + this._scale = 1; + this._panOffset = new Vector3(); - return spherical.theta; + this._rotateStart = new Vector2(); + this._rotateEnd = new Vector2(); + this._rotateDelta = new Vector2(); - }; + this._panStart = new Vector2(); + this._panEnd = new Vector2(); + this._panDelta = new Vector2(); - this.getDistance = function () { + this._dollyStart = new Vector2(); + this._dollyEnd = new Vector2(); + this._dollyDelta = new Vector2(); - return this.object.position.distanceTo( this.target ); + this._dollyDirection = new Vector3(); + this._mouse = new Vector2(); + this._performCursorZoom = false; - }; + this._pointers = []; + this._pointerPositions = {}; - this.listenToKeyEvents = function ( domElement ) { + this._controlActive = false; - domElement.addEventListener( 'keydown', onKeyDown ); - this._domElementKeyEvents = domElement; + // event listeners - }; + this._onPointerMove = onPointerMove.bind( this ); + this._onPointerDown = onPointerDown.bind( this ); + this._onPointerUp = onPointerUp.bind( this ); + this._onContextMenu = onContextMenu.bind( this ); + this._onMouseWheel = onMouseWheel.bind( this ); + this._onKeyDown = onKeyDown.bind( this ); - this.stopListenToKeyEvents = function () { + this._onTouchStart = onTouchStart.bind( this ); + this._onTouchMove = onTouchMove.bind( this ); - this._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown ); - this._domElementKeyEvents = null; + this._onMouseDown = onMouseDown.bind( this ); + this._onMouseMove = onMouseMove.bind( this ); - }; + this._interceptControlDown = interceptControlDown.bind( this ); + this._interceptControlUp = interceptControlUp.bind( this ); - this.saveState = function () { + // - scope.target0.copy( scope.target ); - scope.position0.copy( scope.object.position ); - scope.zoom0 = scope.object.zoom; + if ( this.domElement !== null ) { - }; + this.connect( this.domElement ); - this.reset = function () { + } - scope.target.copy( scope.target0 ); - scope.object.position.copy( scope.position0 ); - scope.object.zoom = scope.zoom0; + this.update(); - scope.object.updateProjectionMatrix(); - scope.dispatchEvent( _changeEvent ); + } - scope.update(); + connect( element ) { - state = STATE.NONE; + super.connect( element ); - }; + this.domElement.addEventListener( 'pointerdown', this._onPointerDown ); + this.domElement.addEventListener( 'pointercancel', this._onPointerUp ); - // this method is exposed, but perhaps it would be better if we can make it private... - this.update = function () { + this.domElement.addEventListener( 'contextmenu', this._onContextMenu ); + this.domElement.addEventListener( 'wheel', this._onMouseWheel, { passive: false } ); - const offset = new Vector3(); + const document = this.domElement.getRootNode(); // offscreen canvas compatibility + document.addEventListener( 'keydown', this._interceptControlDown, { passive: true, capture: true } ); - // so camera.up is the orbit axis - const quat = new Quaternion().setFromUnitVectors( object.up, new Vector3( 0, 1, 0 ) ); - const quatInverse = quat.clone().invert(); + this.domElement.style.touchAction = 'none'; // disable touch scroll - const lastPosition = new Vector3(); - const lastQuaternion = new Quaternion(); - const lastTargetPosition = new Vector3(); + } - const twoPI = 2 * Math.PI; + disconnect() { - return function update() { + this.domElement.removeEventListener( 'pointerdown', this._onPointerDown ); + this.domElement.removeEventListener( 'pointermove', this._onPointerMove ); + this.domElement.removeEventListener( 'pointerup', this._onPointerUp ); + this.domElement.removeEventListener( 'pointercancel', this._onPointerUp ); - const position = scope.object.position; + this.domElement.removeEventListener( 'wheel', this._onMouseWheel ); + this.domElement.removeEventListener( 'contextmenu', this._onContextMenu ); - offset.copy( position ).sub( scope.target ); + this.stopListenToKeyEvents(); - // rotate offset to "y-axis-is-up" space - offset.applyQuaternion( quat ); + const document = this.domElement.getRootNode(); // offscreen canvas compatibility + document.removeEventListener( 'keydown', this._interceptControlDown, { capture: true } ); - // angle from z-axis around y-axis - spherical.setFromVector3( offset ); + this.domElement.style.touchAction = 'auto'; - if ( scope.autoRotate && state === STATE.NONE ) { + } - rotateLeft( getAutoRotationAngle() ); + dispose() { - } + this.disconnect(); - if ( scope.enableDamping ) { + } - spherical.theta += sphericalDelta.theta * scope.dampingFactor; - spherical.phi += sphericalDelta.phi * scope.dampingFactor; + /** + * Get the current vertical rotation, in radians. + * + * @return {number} The current vertical rotation, in radians. + */ + getPolarAngle() { - } else { + return this._spherical.phi; - spherical.theta += sphericalDelta.theta; - spherical.phi += sphericalDelta.phi; + } - } + /** + * Get the current horizontal rotation, in radians. + * + * @return {number} The current horizontal rotation, in radians. + */ + getAzimuthalAngle() { - // restrict theta to be between desired limits + return this._spherical.theta; - let min = scope.minAzimuthAngle; - let max = scope.maxAzimuthAngle; + } - if ( isFinite( min ) && isFinite( max ) ) { + /** + * Returns the distance from the camera to the target. + * + * @return {number} The distance from the camera to the target. + */ + getDistance() { - if ( min < - Math.PI ) min += twoPI; else if ( min > Math.PI ) min -= twoPI; + return this.object.position.distanceTo( this.target ); - if ( max < - Math.PI ) max += twoPI; else if ( max > Math.PI ) max -= twoPI; + } - if ( min <= max ) { + /** + * Adds key event listeners to the given DOM element. + * `window` is a recommended argument for using this method. + * + * @param {HTMLDOMElement} domElement - The DOM element + */ + listenToKeyEvents( domElement ) { - spherical.theta = Math.max( min, Math.min( max, spherical.theta ) ); + domElement.addEventListener( 'keydown', this._onKeyDown ); + this._domElementKeyEvents = domElement; - } else { + } - spherical.theta = ( spherical.theta > ( min + max ) / 2 ) ? - Math.max( min, spherical.theta ) : - Math.min( max, spherical.theta ); + /** + * Removes the key event listener previously defined with `listenToKeyEvents()`. + */ + stopListenToKeyEvents() { - } + if ( this._domElementKeyEvents !== null ) { - } + this._domElementKeyEvents.removeEventListener( 'keydown', this._onKeyDown ); + this._domElementKeyEvents = null; - // restrict phi to be between desired limits - spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); + } - spherical.makeSafe(); + } + /** + * Save the current state of the controls. This can later be recovered with `reset()`. + */ + saveState() { - spherical.radius *= scale; + this.target0.copy( this.target ); + this.position0.copy( this.object.position ); + this.zoom0 = this.object.zoom; - // restrict radius to be between desired limits - spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); + } - // move target to panned location + /** + * Reset the controls to their state from either the last time the `saveState()` + * was called, or the initial state. + */ + reset() { - if ( scope.enableDamping === true ) { + this.target.copy( this.target0 ); + this.object.position.copy( this.position0 ); + this.object.zoom = this.zoom0; - scope.target.addScaledVector( panOffset, scope.dampingFactor ); + this.object.updateProjectionMatrix(); + this.dispatchEvent( _changeEvent ); - } else { + this.update(); - scope.target.add( panOffset ); + this.state = _STATE.NONE; - } + } - offset.setFromSpherical( spherical ); + update( deltaTime = null ) { - // rotate offset back to "camera-up-vector-is-up" space - offset.applyQuaternion( quatInverse ); + const position = this.object.position; - position.copy( scope.target ).add( offset ); + _v.copy( position ).sub( this.target ); - scope.object.lookAt( scope.target ); + // rotate offset to "y-axis-is-up" space + _v.applyQuaternion( this._quat ); - if ( scope.enableDamping === true ) { + // angle from z-axis around y-axis + this._spherical.setFromVector3( _v ); - sphericalDelta.theta *= ( 1 - scope.dampingFactor ); - sphericalDelta.phi *= ( 1 - scope.dampingFactor ); + if ( this.autoRotate && this.state === _STATE.NONE ) { - panOffset.multiplyScalar( 1 - scope.dampingFactor ); + this._rotateLeft( this._getAutoRotationAngle( deltaTime ) ); - } else { + } - sphericalDelta.set( 0, 0, 0 ); + if ( this.enableDamping ) { - panOffset.set( 0, 0, 0 ); + this._spherical.theta += this._sphericalDelta.theta * this.dampingFactor; + this._spherical.phi += this._sphericalDelta.phi * this.dampingFactor; - } + } else { - scale = 1; + this._spherical.theta += this._sphericalDelta.theta; + this._spherical.phi += this._sphericalDelta.phi; - // update condition is: - // min(camera displacement, camera rotation in radians)^2 > EPS - // using small-angle approximation cos(x/2) = 1 - x^2 / 8 + } - if ( zoomChanged || - lastPosition.distanceToSquared( scope.object.position ) > EPS || - 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS || - lastTargetPosition.distanceToSquared( scope.target ) > 0 ) { + // restrict theta to be between desired limits - scope.dispatchEvent( _changeEvent ); + let min = this.minAzimuthAngle; + let max = this.maxAzimuthAngle; - lastPosition.copy( scope.object.position ); - lastQuaternion.copy( scope.object.quaternion ); - lastTargetPosition.copy( scope.target ); + if ( isFinite( min ) && isFinite( max ) ) { - zoomChanged = false; + if ( min < - Math.PI ) min += _twoPI; else if ( min > Math.PI ) min -= _twoPI; - return true; + if ( max < - Math.PI ) max += _twoPI; else if ( max > Math.PI ) max -= _twoPI; - } + if ( min <= max ) { - return false; + this._spherical.theta = Math.max( min, Math.min( max, this._spherical.theta ) ); - }; + } else { - }(); + this._spherical.theta = ( this._spherical.theta > ( min + max ) / 2 ) ? + Math.max( min, this._spherical.theta ) : + Math.min( max, this._spherical.theta ); - this.dispose = function () { + } - scope.domElement.removeEventListener( 'contextmenu', onContextMenu ); + } - scope.domElement.removeEventListener( 'pointerdown', onPointerDown ); - scope.domElement.removeEventListener( 'pointercancel', onPointerUp ); - scope.domElement.removeEventListener( 'wheel', onMouseWheel ); + // restrict phi to be between desired limits + this._spherical.phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, this._spherical.phi ) ); - scope.domElement.removeEventListener( 'pointermove', onPointerMove ); - scope.domElement.removeEventListener( 'pointerup', onPointerUp ); + this._spherical.makeSafe(); - if ( scope._domElementKeyEvents !== null ) { + // move target to panned location - scope._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown ); - scope._domElementKeyEvents = null; + if ( this.enableDamping === true ) { - } + this.target.addScaledVector( this._panOffset, this.dampingFactor ); - //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? + } else { - }; + this.target.add( this._panOffset ); - // - // internals - // + } - const scope = this; - - const STATE = { - NONE: - 1, - ROTATE: 0, - DOLLY: 1, - PAN: 2, - TOUCH_ROTATE: 3, - TOUCH_PAN: 4, - TOUCH_DOLLY_PAN: 5, - TOUCH_DOLLY_ROTATE: 6 - }; + // Limit the target distance from the cursor to create a sphere around the center of interest + this.target.sub( this.cursor ); + this.target.clampLength( this.minTargetRadius, this.maxTargetRadius ); + this.target.add( this.cursor ); - let state = STATE.NONE; + let zoomChanged = false; + // adjust the camera position based on zoom only if we're not zooming to the cursor or if it's an ortho camera + // we adjust zoom later in these cases + if ( this.zoomToCursor && this._performCursorZoom || this.object.isOrthographicCamera ) { - const EPS = 0.000001; + this._spherical.radius = this._clampDistance( this._spherical.radius ); - // current position in spherical coordinates - const spherical = new Spherical(); - const sphericalDelta = new Spherical(); + } else { - let scale = 1; - const panOffset = new Vector3(); - let zoomChanged = false; + const prevRadius = this._spherical.radius; + this._spherical.radius = this._clampDistance( this._spherical.radius * this._scale ); + zoomChanged = prevRadius != this._spherical.radius; - const rotateStart = new Vector2(); - const rotateEnd = new Vector2(); - const rotateDelta = new Vector2(); + } - const panStart = new Vector2(); - const panEnd = new Vector2(); - const panDelta = new Vector2(); + _v.setFromSpherical( this._spherical ); - const dollyStart = new Vector2(); - const dollyEnd = new Vector2(); - const dollyDelta = new Vector2(); + // rotate offset back to "camera-up-vector-is-up" space + _v.applyQuaternion( this._quatInverse ); - const pointers = []; - const pointerPositions = {}; + position.copy( this.target ).add( _v ); - function getAutoRotationAngle() { + this.object.lookAt( this.target ); - return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; + if ( this.enableDamping === true ) { - } + this._sphericalDelta.theta *= ( 1 - this.dampingFactor ); + this._sphericalDelta.phi *= ( 1 - this.dampingFactor ); - function getZoomScale() { + this._panOffset.multiplyScalar( 1 - this.dampingFactor ); - return Math.pow( 0.95, scope.zoomSpeed ); + } else { + + this._sphericalDelta.set( 0, 0, 0 ); + + this._panOffset.set( 0, 0, 0 ); } - function rotateLeft( angle ) { + // adjust camera position + if ( this.zoomToCursor && this._performCursorZoom ) { - sphericalDelta.theta -= angle; + let newRadius = null; + if ( this.object.isPerspectiveCamera ) { - } + // move the camera down the pointer ray + // this method avoids floating point error + const prevRadius = _v.length(); + newRadius = this._clampDistance( prevRadius * this._scale ); - function rotateUp( angle ) { + const radiusDelta = prevRadius - newRadius; + this.object.position.addScaledVector( this._dollyDirection, radiusDelta ); + this.object.updateMatrixWorld(); - sphericalDelta.phi -= angle; + zoomChanged = !! radiusDelta; - } + } else if ( this.object.isOrthographicCamera ) { - const panLeft = function () { + // adjust the ortho camera position based on zoom changes + const mouseBefore = new Vector3( this._mouse.x, this._mouse.y, 0 ); + mouseBefore.unproject( this.object ); - const v = new Vector3(); + const prevZoom = this.object.zoom; + this.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom / this._scale ) ); + this.object.updateProjectionMatrix(); - return function panLeft( distance, objectMatrix ) { + zoomChanged = prevZoom !== this.object.zoom; - v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix - v.multiplyScalar( - distance ); + const mouseAfter = new Vector3( this._mouse.x, this._mouse.y, 0 ); + mouseAfter.unproject( this.object ); - panOffset.add( v ); + this.object.position.sub( mouseAfter ).add( mouseBefore ); + this.object.updateMatrixWorld(); - }; + newRadius = _v.length(); - }(); + } else { - const panUp = function () { + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - zoom to cursor disabled.' ); + this.zoomToCursor = false; - const v = new Vector3(); + } - return function panUp( distance, objectMatrix ) { + // handle the placement of the target + if ( newRadius !== null ) { - if ( scope.screenSpacePanning === true ) { + if ( this.screenSpacePanning ) { - v.setFromMatrixColumn( objectMatrix, 1 ); + // position the orbit target in front of the new camera position + this.target.set( 0, 0, - 1 ) + .transformDirection( this.object.matrix ) + .multiplyScalar( newRadius ) + .add( this.object.position ); } else { - v.setFromMatrixColumn( objectMatrix, 0 ); - v.crossVectors( scope.object.up, v ); + // get the ray and translation plane to compute target + _ray.origin.copy( this.object.position ); + _ray.direction.set( 0, 0, - 1 ).transformDirection( this.object.matrix ); + + // if the camera is 20 degrees above the horizon then don't adjust the focus target to avoid + // extremely large values + if ( Math.abs( this.object.up.dot( _ray.direction ) ) < _TILT_LIMIT ) { + + this.object.lookAt( this.target ); + + } else { + + _plane.setFromNormalAndCoplanarPoint( this.object.up, this.target ); + _ray.intersectPlane( _plane, this.target ); + + } } - v.multiplyScalar( distance ); + } - panOffset.add( v ); + } else if ( this.object.isOrthographicCamera ) { - }; + const prevZoom = this.object.zoom; + this.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom / this._scale ) ); - }(); + if ( prevZoom !== this.object.zoom ) { - // deltaX and deltaY are in pixels; right and down are positive - const pan = function () { + this.object.updateProjectionMatrix(); + zoomChanged = true; - const offset = new Vector3(); + } - return function pan( deltaX, deltaY ) { + } - const element = scope.domElement; + this._scale = 1; + this._performCursorZoom = false; - if ( scope.object.isPerspectiveCamera ) { + // update condition is: + // min(camera displacement, camera rotation in radians)^2 > EPS + // using small-angle approximation cos(x/2) = 1 - x^2 / 8 - // perspective - const position = scope.object.position; - offset.copy( position ).sub( scope.target ); - let targetDistance = offset.length(); + if ( zoomChanged || + this._lastPosition.distanceToSquared( this.object.position ) > _EPS || + 8 * ( 1 - this._lastQuaternion.dot( this.object.quaternion ) ) > _EPS || + this._lastTargetPosition.distanceToSquared( this.target ) > _EPS ) { - // half of the fov is center to top of screen - targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); + this.dispatchEvent( _changeEvent ); - // we use only clientHeight here so aspect ratio does not distort speed - panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); - panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); + this._lastPosition.copy( this.object.position ); + this._lastQuaternion.copy( this.object.quaternion ); + this._lastTargetPosition.copy( this.target ); - } else if ( scope.object.isOrthographicCamera ) { + return true; - // orthographic - panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); - panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); + } - } else { + return false; + + } - // camera neither orthographic nor perspective - console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); - scope.enablePan = false; + _getAutoRotationAngle( deltaTime ) { - } + if ( deltaTime !== null ) { + + return ( _twoPI / 60 * this.autoRotateSpeed ) * deltaTime; - }; + } else { - }(); + return _twoPI / 60 / 60 * this.autoRotateSpeed; - function dollyOut( dollyScale ) { + } - if ( scope.object.isPerspectiveCamera ) { + } - scale /= dollyScale; + _getZoomScale( delta ) { - } else if ( scope.object.isOrthographicCamera ) { + const normalizedDelta = Math.abs( delta * 0.01 ); + return Math.pow( 0.95, this.zoomSpeed * normalizedDelta ); - scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); - scope.object.updateProjectionMatrix(); - zoomChanged = true; + } - } else { + _rotateLeft( angle ) { - console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); - scope.enableZoom = false; + this._sphericalDelta.theta -= angle; - } + } + + _rotateUp( angle ) { + + this._sphericalDelta.phi -= angle; + + } + + _panLeft( distance, objectMatrix ) { + + _v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix + _v.multiplyScalar( - distance ); + + this._panOffset.add( _v ); + + } + + _panUp( distance, objectMatrix ) { + + if ( this.screenSpacePanning === true ) { + + _v.setFromMatrixColumn( objectMatrix, 1 ); + + } else { + + _v.setFromMatrixColumn( objectMatrix, 0 ); + _v.crossVectors( this.object.up, _v ); } - function dollyIn( dollyScale ) { + _v.multiplyScalar( distance ); - if ( scope.object.isPerspectiveCamera ) { + this._panOffset.add( _v ); - scale *= dollyScale; + } - } else if ( scope.object.isOrthographicCamera ) { + // deltaX and deltaY are in pixels; right and down are positive + _pan( deltaX, deltaY ) { - scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); - scope.object.updateProjectionMatrix(); - zoomChanged = true; + const element = this.domElement; - } else { + if ( this.object.isPerspectiveCamera ) { - console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); - scope.enableZoom = false; + // perspective + const position = this.object.position; + _v.copy( position ).sub( this.target ); + let targetDistance = _v.length(); - } + // half of the fov is center to top of screen + targetDistance *= Math.tan( ( this.object.fov / 2 ) * Math.PI / 180.0 ); + + // we use only clientHeight here so aspect ratio does not distort speed + this._panLeft( 2 * deltaX * targetDistance / element.clientHeight, this.object.matrix ); + this._panUp( 2 * deltaY * targetDistance / element.clientHeight, this.object.matrix ); + + } else if ( this.object.isOrthographicCamera ) { + + // orthographic + this._panLeft( deltaX * ( this.object.right - this.object.left ) / this.object.zoom / element.clientWidth, this.object.matrix ); + this._panUp( deltaY * ( this.object.top - this.object.bottom ) / this.object.zoom / element.clientHeight, this.object.matrix ); + + } else { + + // camera neither orthographic nor perspective + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); + this.enablePan = false; } - // - // event callbacks - update the object state - // + } + + _dollyOut( dollyScale ) { + + if ( this.object.isPerspectiveCamera || this.object.isOrthographicCamera ) { + + this._scale /= dollyScale; - function handleMouseDownRotate( event ) { + } else { - rotateStart.set( event.clientX, event.clientY ); + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + this.enableZoom = false; } - function handleMouseDownDolly( event ) { + } + + _dollyIn( dollyScale ) { + + if ( this.object.isPerspectiveCamera || this.object.isOrthographicCamera ) { + + this._scale *= dollyScale; + + } else { - dollyStart.set( event.clientX, event.clientY ); + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + this.enableZoom = false; } - function handleMouseDownPan( event ) { + } + + _updateZoomParameters( x, y ) { + + if ( ! this.zoomToCursor ) { - panStart.set( event.clientX, event.clientY ); + return; } - function handleMouseMoveRotate( event ) { + this._performCursorZoom = true; - rotateEnd.set( event.clientX, event.clientY ); + const rect = this.domElement.getBoundingClientRect(); + const dx = x - rect.left; + const dy = y - rect.top; + const w = rect.width; + const h = rect.height; - rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); + this._mouse.x = ( dx / w ) * 2 - 1; + this._mouse.y = - ( dy / h ) * 2 + 1; - const element = scope.domElement; + this._dollyDirection.set( this._mouse.x, this._mouse.y, 1 ).unproject( this.object ).sub( this.object.position ).normalize(); - rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height + } - rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); + _clampDistance( dist ) { - rotateStart.copy( rotateEnd ); + return Math.max( this.minDistance, Math.min( this.maxDistance, dist ) ); - scope.update(); + } - } + // + // event callbacks - update the object state + // - function handleMouseMoveDolly( event ) { + _handleMouseDownRotate( event ) { - dollyEnd.set( event.clientX, event.clientY ); + this._rotateStart.set( event.clientX, event.clientY ); - dollyDelta.subVectors( dollyEnd, dollyStart ); + } - if ( dollyDelta.y > 0 ) { + _handleMouseDownDolly( event ) { - dollyOut( getZoomScale() ); + this._updateZoomParameters( event.clientX, event.clientX ); + this._dollyStart.set( event.clientX, event.clientY ); - } else if ( dollyDelta.y < 0 ) { + } - dollyIn( getZoomScale() ); + _handleMouseDownPan( event ) { - } + this._panStart.set( event.clientX, event.clientY ); - dollyStart.copy( dollyEnd ); + } - scope.update(); + _handleMouseMoveRotate( event ) { - } + this._rotateEnd.set( event.clientX, event.clientY ); + + this._rotateDelta.subVectors( this._rotateEnd, this._rotateStart ).multiplyScalar( this.rotateSpeed ); - function handleMouseMovePan( event ) { + const element = this.domElement; - panEnd.set( event.clientX, event.clientY ); + this._rotateLeft( _twoPI * this._rotateDelta.x / element.clientHeight ); // yes, height - panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); + this._rotateUp( _twoPI * this._rotateDelta.y / element.clientHeight ); - pan( panDelta.x, panDelta.y ); + this._rotateStart.copy( this._rotateEnd ); - panStart.copy( panEnd ); + this.update(); + + } + + _handleMouseMoveDolly( event ) { + + this._dollyEnd.set( event.clientX, event.clientY ); + + this._dollyDelta.subVectors( this._dollyEnd, this._dollyStart ); - scope.update(); + if ( this._dollyDelta.y > 0 ) { + + this._dollyOut( this._getZoomScale( this._dollyDelta.y ) ); + + } else if ( this._dollyDelta.y < 0 ) { + + this._dollyIn( this._getZoomScale( this._dollyDelta.y ) ); } - function handleMouseWheel( event ) { + this._dollyStart.copy( this._dollyEnd ); - if ( event.deltaY < 0 ) { + this.update(); - dollyIn( getZoomScale() ); + } - } else if ( event.deltaY > 0 ) { + _handleMouseMovePan( event ) { - dollyOut( getZoomScale() ); + this._panEnd.set( event.clientX, event.clientY ); - } + this._panDelta.subVectors( this._panEnd, this._panStart ).multiplyScalar( this.panSpeed ); - scope.update(); + this._pan( this._panDelta.x, this._panDelta.y ); + + this._panStart.copy( this._panEnd ); + + this.update(); + + } + + _handleMouseWheel( event ) { + + this._updateZoomParameters( event.clientX, event.clientY ); + + if ( event.deltaY < 0 ) { + + this._dollyIn( this._getZoomScale( event.deltaY ) ); + + } else if ( event.deltaY > 0 ) { + + this._dollyOut( this._getZoomScale( event.deltaY ) ); } - function handleKeyDown( event ) { + this.update(); - let needsUpdate = false; + } - switch ( event.code ) { + _handleKeyDown( event ) { - case scope.keys.UP: + let needsUpdate = false; - if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + switch ( event.code ) { - rotateUp( 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight ); + case this.keys.UP: - } else { + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + + if ( this.enableRotate ) { - pan( 0, scope.keyPanSpeed ); + this._rotateUp( _twoPI * this.keyRotateSpeed / this.domElement.clientHeight ); } - needsUpdate = true; - break; + } else { - case scope.keys.BOTTOM: + if ( this.enablePan ) { - if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + this._pan( 0, this.keyPanSpeed ); - rotateUp( - 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight ); + } - } else { + } - pan( 0, - scope.keyPanSpeed ); + needsUpdate = true; + break; - } + case this.keys.BOTTOM: - needsUpdate = true; - break; + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + + if ( this.enableRotate ) { - case scope.keys.LEFT: + this._rotateUp( - _twoPI * this.keyRotateSpeed / this.domElement.clientHeight ); - if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + } - rotateLeft( 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight ); + } else { - } else { + if ( this.enablePan ) { - pan( scope.keyPanSpeed, 0 ); + this._pan( 0, - this.keyPanSpeed ); } - needsUpdate = true; - break; + } - case scope.keys.RIGHT: + needsUpdate = true; + break; - if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + case this.keys.LEFT: - rotateLeft( - 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight ); + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { - } else { + if ( this.enableRotate ) { - pan( - scope.keyPanSpeed, 0 ); + this._rotateLeft( _twoPI * this.keyRotateSpeed / this.domElement.clientHeight ); } - needsUpdate = true; - break; + } else { - } + if ( this.enablePan ) { - if ( needsUpdate ) { + this._pan( this.keyPanSpeed, 0 ); - // prevent the browser from scrolling on cursor keys - event.preventDefault(); + } - scope.update(); + } - } + needsUpdate = true; + break; + case this.keys.RIGHT: - } + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { - function handleTouchStartRotate() { + if ( this.enableRotate ) { - if ( pointers.length === 1 ) { + this._rotateLeft( - _twoPI * this.keyRotateSpeed / this.domElement.clientHeight ); - rotateStart.set( pointers[ 0 ].pageX, pointers[ 0 ].pageY ); + } - } else { + } else { - const x = 0.5 * ( pointers[ 0 ].pageX + pointers[ 1 ].pageX ); - const y = 0.5 * ( pointers[ 0 ].pageY + pointers[ 1 ].pageY ); + if ( this.enablePan ) { - rotateStart.set( x, y ); + this._pan( - this.keyPanSpeed, 0 ); - } + } + + } + + needsUpdate = true; + break; } - function handleTouchStartPan() { + if ( needsUpdate ) { - if ( pointers.length === 1 ) { + // prevent the browser from scrolling on cursor keys + event.preventDefault(); - panStart.set( pointers[ 0 ].pageX, pointers[ 0 ].pageY ); + this.update(); - } else { + } - const x = 0.5 * ( pointers[ 0 ].pageX + pointers[ 1 ].pageX ); - const y = 0.5 * ( pointers[ 0 ].pageY + pointers[ 1 ].pageY ); - panStart.set( x, y ); + } - } + _handleTouchStartRotate( event ) { - } + if ( this._pointers.length === 1 ) { - function handleTouchStartDolly() { + this._rotateStart.set( event.pageX, event.pageY ); - const dx = pointers[ 0 ].pageX - pointers[ 1 ].pageX; - const dy = pointers[ 0 ].pageY - pointers[ 1 ].pageY; + } else { - const distance = Math.sqrt( dx * dx + dy * dy ); + const position = this._getSecondPointerPosition( event ); - dollyStart.set( 0, distance ); + const x = 0.5 * ( event.pageX + position.x ); + const y = 0.5 * ( event.pageY + position.y ); + + this._rotateStart.set( x, y ); } - function handleTouchStartDollyPan() { + } - if ( scope.enableZoom ) handleTouchStartDolly(); + _handleTouchStartPan( event ) { - if ( scope.enablePan ) handleTouchStartPan(); + if ( this._pointers.length === 1 ) { - } + this._panStart.set( event.pageX, event.pageY ); - function handleTouchStartDollyRotate() { + } else { - if ( scope.enableZoom ) handleTouchStartDolly(); + const position = this._getSecondPointerPosition( event ); - if ( scope.enableRotate ) handleTouchStartRotate(); + const x = 0.5 * ( event.pageX + position.x ); + const y = 0.5 * ( event.pageY + position.y ); + + this._panStart.set( x, y ); } - function handleTouchMoveRotate( event ) { + } - if ( pointers.length == 1 ) { + _handleTouchStartDolly( event ) { - rotateEnd.set( event.pageX, event.pageY ); + const position = this._getSecondPointerPosition( event ); - } else { + const dx = event.pageX - position.x; + const dy = event.pageY - position.y; - const position = getSecondPointerPosition( event ); + const distance = Math.sqrt( dx * dx + dy * dy ); - const x = 0.5 * ( event.pageX + position.x ); - const y = 0.5 * ( event.pageY + position.y ); + this._dollyStart.set( 0, distance ); - rotateEnd.set( x, y ); + } - } + _handleTouchStartDollyPan( event ) { + + if ( this.enableZoom ) this._handleTouchStartDolly( event ); - rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); + if ( this.enablePan ) this._handleTouchStartPan( event ); - const element = scope.domElement; + } + + _handleTouchStartDollyRotate( event ) { + + if ( this.enableZoom ) this._handleTouchStartDolly( event ); + + if ( this.enableRotate ) this._handleTouchStartRotate( event ); + + } - rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height + _handleTouchMoveRotate( event ) { - rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); + if ( this._pointers.length == 1 ) { - rotateStart.copy( rotateEnd ); + this._rotateEnd.set( event.pageX, event.pageY ); + + } else { + + const position = this._getSecondPointerPosition( event ); + + const x = 0.5 * ( event.pageX + position.x ); + const y = 0.5 * ( event.pageY + position.y ); + + this._rotateEnd.set( x, y ); } - function handleTouchMovePan( event ) { + this._rotateDelta.subVectors( this._rotateEnd, this._rotateStart ).multiplyScalar( this.rotateSpeed ); - if ( pointers.length === 1 ) { + const element = this.domElement; - panEnd.set( event.pageX, event.pageY ); + this._rotateLeft( _twoPI * this._rotateDelta.x / element.clientHeight ); // yes, height - } else { + this._rotateUp( _twoPI * this._rotateDelta.y / element.clientHeight ); - const position = getSecondPointerPosition( event ); + this._rotateStart.copy( this._rotateEnd ); - const x = 0.5 * ( event.pageX + position.x ); - const y = 0.5 * ( event.pageY + position.y ); + } - panEnd.set( x, y ); + _handleTouchMovePan( event ) { - } + if ( this._pointers.length === 1 ) { + + this._panEnd.set( event.pageX, event.pageY ); + + } else { - panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); + const position = this._getSecondPointerPosition( event ); - pan( panDelta.x, panDelta.y ); + const x = 0.5 * ( event.pageX + position.x ); + const y = 0.5 * ( event.pageY + position.y ); - panStart.copy( panEnd ); + this._panEnd.set( x, y ); } - function handleTouchMoveDolly( event ) { + this._panDelta.subVectors( this._panEnd, this._panStart ).multiplyScalar( this.panSpeed ); - const position = getSecondPointerPosition( event ); + this._pan( this._panDelta.x, this._panDelta.y ); - const dx = event.pageX - position.x; - const dy = event.pageY - position.y; + this._panStart.copy( this._panEnd ); - const distance = Math.sqrt( dx * dx + dy * dy ); + } - dollyEnd.set( 0, distance ); + _handleTouchMoveDolly( event ) { - dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) ); + const position = this._getSecondPointerPosition( event ); - dollyOut( dollyDelta.y ); + const dx = event.pageX - position.x; + const dy = event.pageY - position.y; - dollyStart.copy( dollyEnd ); + const distance = Math.sqrt( dx * dx + dy * dy ); - } + this._dollyEnd.set( 0, distance ); - function handleTouchMoveDollyPan( event ) { + this._dollyDelta.set( 0, Math.pow( this._dollyEnd.y / this._dollyStart.y, this.zoomSpeed ) ); - if ( scope.enableZoom ) handleTouchMoveDolly( event ); + this._dollyOut( this._dollyDelta.y ); - if ( scope.enablePan ) handleTouchMovePan( event ); + this._dollyStart.copy( this._dollyEnd ); - } + const centerX = ( event.pageX + position.x ) * 0.5; + const centerY = ( event.pageY + position.y ) * 0.5; - function handleTouchMoveDollyRotate( event ) { + this._updateZoomParameters( centerX, centerY ); - if ( scope.enableZoom ) handleTouchMoveDolly( event ); + } - if ( scope.enableRotate ) handleTouchMoveRotate( event ); + _handleTouchMoveDollyPan( event ) { - } + if ( this.enableZoom ) this._handleTouchMoveDolly( event ); - // - // event handlers - FSM: listen for events and reset state - // + if ( this.enablePan ) this._handleTouchMovePan( event ); + + } - function onPointerDown( event ) { + _handleTouchMoveDollyRotate( event ) { - if ( scope.enabled === false ) return; + if ( this.enableZoom ) this._handleTouchMoveDolly( event ); - if ( pointers.length === 0 ) { + if ( this.enableRotate ) this._handleTouchMoveRotate( event ); - scope.domElement.setPointerCapture( event.pointerId ); + } - scope.domElement.addEventListener( 'pointermove', onPointerMove ); - scope.domElement.addEventListener( 'pointerup', onPointerUp ); + // pointers - } + _addPointer( event ) { + + this._pointers.push( event.pointerId ); - // + } - addPointer( event ); + _removePointer( event ) { - if ( event.pointerType === 'touch' ) { + delete this._pointerPositions[ event.pointerId ]; - onTouchStart( event ); + for ( let i = 0; i < this._pointers.length; i ++ ) { - } else { + if ( this._pointers[ i ] == event.pointerId ) { - onMouseDown( event ); + this._pointers.splice( i, 1 ); + return; } } - function onPointerMove( event ) { + } + + _isTrackingPointer( event ) { - if ( scope.enabled === false ) return; + for ( let i = 0; i < this._pointers.length; i ++ ) { - if ( event.pointerType === 'touch' ) { + if ( this._pointers[ i ] == event.pointerId ) return true; - onTouchMove( event ); + } - } else { + return false; - onMouseMove( event ); + } - } + _trackPointer( event ) { + + let position = this._pointerPositions[ event.pointerId ]; + + if ( position === undefined ) { + + position = new Vector2(); + this._pointerPositions[ event.pointerId ] = position; } - function onPointerUp( event ) { + position.set( event.pageX, event.pageY ); + + } - removePointer( event ); + _getSecondPointerPosition( event ) { - if ( pointers.length === 0 ) { + const pointerId = ( event.pointerId === this._pointers[ 0 ] ) ? this._pointers[ 1 ] : this._pointers[ 0 ]; - scope.domElement.releasePointerCapture( event.pointerId ); + return this._pointerPositions[ pointerId ]; - scope.domElement.removeEventListener( 'pointermove', onPointerMove ); - scope.domElement.removeEventListener( 'pointerup', onPointerUp ); + } - } + // - scope.dispatchEvent( _endEvent ); + _customWheelEvent( event ) { - state = STATE.NONE; + const mode = event.deltaMode; + + // minimal wheel event altered to meet delta-zoom demand + const newEvent = { + clientX: event.clientX, + clientY: event.clientY, + deltaY: event.deltaY, + }; + + switch ( mode ) { + + case 1: // LINE_MODE + newEvent.deltaY *= 16; + break; + + case 2: // PAGE_MODE + newEvent.deltaY *= 100; + break; } - function onMouseDown( event ) { + // detect if event was triggered by pinching + if ( event.ctrlKey && ! this._controlActive ) { - let mouseAction; + newEvent.deltaY *= 10; - switch ( event.button ) { + } - case 0: + return newEvent; - mouseAction = scope.mouseButtons.LEFT; - break; + } - case 1: +} - mouseAction = scope.mouseButtons.MIDDLE; - break; +function onPointerDown( event ) { - case 2: + if ( this.enabled === false ) return; - mouseAction = scope.mouseButtons.RIGHT; - break; + if ( this._pointers.length === 0 ) { - default: + this.domElement.setPointerCapture( event.pointerId ); - mouseAction = - 1; + this.domElement.addEventListener( 'pointermove', this._onPointerMove ); + this.domElement.addEventListener( 'pointerup', this._onPointerUp ); - } + } - switch ( mouseAction ) { + // - case MOUSE.DOLLY: + if ( this._isTrackingPointer( event ) ) return; - if ( scope.enableZoom === false ) return; + // - handleMouseDownDolly( event ); + this._addPointer( event ); - state = STATE.DOLLY; + if ( event.pointerType === 'touch' ) { - break; + this._onTouchStart( event ); - case MOUSE.ROTATE: + } else { - if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + this._onMouseDown( event ); - if ( scope.enablePan === false ) return; + } - handleMouseDownPan( event ); +} - state = STATE.PAN; +function onPointerMove( event ) { - } else { + if ( this.enabled === false ) return; - if ( scope.enableRotate === false ) return; + if ( event.pointerType === 'touch' ) { - handleMouseDownRotate( event ); + this._onTouchMove( event ); - state = STATE.ROTATE; + } else { - } + this._onMouseMove( event ); - break; + } - case MOUSE.PAN: +} - if ( event.ctrlKey || event.metaKey || event.shiftKey ) { +function onPointerUp( event ) { - if ( scope.enableRotate === false ) return; + this._removePointer( event ); - handleMouseDownRotate( event ); + switch ( this._pointers.length ) { - state = STATE.ROTATE; + case 0: - } else { + this.domElement.releasePointerCapture( event.pointerId ); - if ( scope.enablePan === false ) return; + this.domElement.removeEventListener( 'pointermove', this._onPointerMove ); + this.domElement.removeEventListener( 'pointerup', this._onPointerUp ); - handleMouseDownPan( event ); + this.dispatchEvent( _endEvent ); - state = STATE.PAN; + this.state = _STATE.NONE; - } + break; - break; + case 1: - default: + const pointerId = this._pointers[ 0 ]; + const position = this._pointerPositions[ pointerId ]; - state = STATE.NONE; + // minimal placeholder event - allows state correction on pointer-up + this._onTouchStart( { pointerId: pointerId, pageX: position.x, pageY: position.y } ); - } + break; - if ( state !== STATE.NONE ) { + } - scope.dispatchEvent( _startEvent ); +} - } +function onMouseDown( event ) { - } + let mouseAction; - function onMouseMove( event ) { + switch ( event.button ) { - switch ( state ) { + case 0: - case STATE.ROTATE: + mouseAction = this.mouseButtons.LEFT; + break; - if ( scope.enableRotate === false ) return; + case 1: - handleMouseMoveRotate( event ); + mouseAction = this.mouseButtons.MIDDLE; + break; - break; + case 2: - case STATE.DOLLY: + mouseAction = this.mouseButtons.RIGHT; + break; - if ( scope.enableZoom === false ) return; + default: - handleMouseMoveDolly( event ); + mouseAction = - 1; - break; + } - case STATE.PAN: + switch ( mouseAction ) { - if ( scope.enablePan === false ) return; + case MOUSE.DOLLY: - handleMouseMovePan( event ); + if ( this.enableZoom === false ) return; - break; + this._handleMouseDownDolly( event ); - } + this.state = _STATE.DOLLY; - } + break; - function onMouseWheel( event ) { + case MOUSE.ROTATE: - if ( scope.enabled === false || scope.enableZoom === false || state !== STATE.NONE ) return; + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { - event.preventDefault(); + if ( this.enablePan === false ) return; - scope.dispatchEvent( _startEvent ); + this._handleMouseDownPan( event ); - handleMouseWheel( event ); + this.state = _STATE.PAN; - scope.dispatchEvent( _endEvent ); + } else { - } + if ( this.enableRotate === false ) return; - function onKeyDown( event ) { + this._handleMouseDownRotate( event ); - if ( scope.enabled === false || scope.enablePan === false ) return; + this.state = _STATE.ROTATE; - handleKeyDown( event ); + } - } + break; - function onTouchStart( event ) { + case MOUSE.PAN: - trackPointer( event ); + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { - switch ( pointers.length ) { + if ( this.enableRotate === false ) return; - case 1: + this._handleMouseDownRotate( event ); - switch ( scope.touches.ONE ) { + this.state = _STATE.ROTATE; - case TOUCH.ROTATE: + } else { - if ( scope.enableRotate === false ) return; + if ( this.enablePan === false ) return; - handleTouchStartRotate(); + this._handleMouseDownPan( event ); - state = STATE.TOUCH_ROTATE; + this.state = _STATE.PAN; - break; + } - case TOUCH.PAN: + break; - if ( scope.enablePan === false ) return; + default: - handleTouchStartPan(); + this.state = _STATE.NONE; - state = STATE.TOUCH_PAN; + } - break; + if ( this.state !== _STATE.NONE ) { - default: + this.dispatchEvent( _startEvent ); - state = STATE.NONE; + } - } +} - break; +function onMouseMove( event ) { - case 2: + switch ( this.state ) { - switch ( scope.touches.TWO ) { + case _STATE.ROTATE: - case TOUCH.DOLLY_PAN: + if ( this.enableRotate === false ) return; - if ( scope.enableZoom === false && scope.enablePan === false ) return; + this._handleMouseMoveRotate( event ); - handleTouchStartDollyPan(); + break; - state = STATE.TOUCH_DOLLY_PAN; + case _STATE.DOLLY: - break; + if ( this.enableZoom === false ) return; - case TOUCH.DOLLY_ROTATE: + this._handleMouseMoveDolly( event ); - if ( scope.enableZoom === false && scope.enableRotate === false ) return; + break; - handleTouchStartDollyRotate(); + case _STATE.PAN: - state = STATE.TOUCH_DOLLY_ROTATE; + if ( this.enablePan === false ) return; - break; + this._handleMouseMovePan( event ); - default: + break; - state = STATE.NONE; + } - } +} - break; +function onMouseWheel( event ) { - default: + if ( this.enabled === false || this.enableZoom === false || this.state !== _STATE.NONE ) return; - state = STATE.NONE; + event.preventDefault(); - } + this.dispatchEvent( _startEvent ); - if ( state !== STATE.NONE ) { + this._handleMouseWheel( this._customWheelEvent( event ) ); - scope.dispatchEvent( _startEvent ); + this.dispatchEvent( _endEvent ); - } +} - } +function onKeyDown( event ) { + + if ( this.enabled === false ) return; + + this._handleKeyDown( event ); + +} + +function onTouchStart( event ) { + + this._trackPointer( event ); - function onTouchMove( event ) { + switch ( this._pointers.length ) { - trackPointer( event ); + case 1: - switch ( state ) { + switch ( this.touches.ONE ) { - case STATE.TOUCH_ROTATE: + case TOUCH.ROTATE: - if ( scope.enableRotate === false ) return; + if ( this.enableRotate === false ) return; - handleTouchMoveRotate( event ); + this._handleTouchStartRotate( event ); - scope.update(); + this.state = _STATE.TOUCH_ROTATE; break; - case STATE.TOUCH_PAN: + case TOUCH.PAN: - if ( scope.enablePan === false ) return; + if ( this.enablePan === false ) return; - handleTouchMovePan( event ); + this._handleTouchStartPan( event ); - scope.update(); + this.state = _STATE.TOUCH_PAN; break; - case STATE.TOUCH_DOLLY_PAN: + default: + + this.state = _STATE.NONE; + + } + + break; + + case 2: - if ( scope.enableZoom === false && scope.enablePan === false ) return; + switch ( this.touches.TWO ) { - handleTouchMoveDollyPan( event ); + case TOUCH.DOLLY_PAN: - scope.update(); + if ( this.enableZoom === false && this.enablePan === false ) return; + + this._handleTouchStartDollyPan( event ); + + this.state = _STATE.TOUCH_DOLLY_PAN; break; - case STATE.TOUCH_DOLLY_ROTATE: + case TOUCH.DOLLY_ROTATE: - if ( scope.enableZoom === false && scope.enableRotate === false ) return; + if ( this.enableZoom === false && this.enableRotate === false ) return; - handleTouchMoveDollyRotate( event ); + this._handleTouchStartDollyRotate( event ); - scope.update(); + this.state = _STATE.TOUCH_DOLLY_ROTATE; break; default: - state = STATE.NONE; + this.state = _STATE.NONE; } - } + break; - function onContextMenu( event ) { + default: - if ( scope.enabled === false ) return; + this.state = _STATE.NONE; - event.preventDefault(); + } - } + if ( this.state !== _STATE.NONE ) { - function addPointer( event ) { + this.dispatchEvent( _startEvent ); - pointers.push( event ); + } - } +} - function removePointer( event ) { +function onTouchMove( event ) { - delete pointerPositions[ event.pointerId ]; + this._trackPointer( event ); - for ( let i = 0; i < pointers.length; i ++ ) { + switch ( this.state ) { - if ( pointers[ i ].pointerId == event.pointerId ) { + case _STATE.TOUCH_ROTATE: - pointers.splice( i, 1 ); - return; + if ( this.enableRotate === false ) return; - } + this._handleTouchMoveRotate( event ); - } + this.update(); - } + break; - function trackPointer( event ) { + case _STATE.TOUCH_PAN: - let position = pointerPositions[ event.pointerId ]; + if ( this.enablePan === false ) return; - if ( position === undefined ) { + this._handleTouchMovePan( event ); - position = new Vector2(); - pointerPositions[ event.pointerId ] = position; + this.update(); - } + break; - position.set( event.pageX, event.pageY ); + case _STATE.TOUCH_DOLLY_PAN: - } + if ( this.enableZoom === false && this.enablePan === false ) return; - function getSecondPointerPosition( event ) { + this._handleTouchMoveDollyPan( event ); - const pointer = ( event.pointerId === pointers[ 0 ].pointerId ) ? pointers[ 1 ] : pointers[ 0 ]; + this.update(); - return pointerPositions[ pointer.pointerId ]; + break; - } + case _STATE.TOUCH_DOLLY_ROTATE: - // + if ( this.enableZoom === false && this.enableRotate === false ) return; - scope.domElement.addEventListener( 'contextmenu', onContextMenu ); + this._handleTouchMoveDollyRotate( event ); - scope.domElement.addEventListener( 'pointerdown', onPointerDown ); - scope.domElement.addEventListener( 'pointercancel', onPointerUp ); - scope.domElement.addEventListener( 'wheel', onMouseWheel, { passive: false } ); + this.update(); - // force an update at start + break; - this.update(); + default: + + this.state = _STATE.NONE; + + } + +} + +function onContextMenu( event ) { + + if ( this.enabled === false ) return; + + event.preventDefault(); + +} + +function interceptControlDown( event ) { + + if ( event.key === 'Control' ) { + + this._controlActive = true; + + const document = this.domElement.getRootNode(); // offscreen canvas compatibility + + document.addEventListener( 'keyup', this._interceptControlUp, { passive: true, capture: true } ); + + } + +} + +function interceptControlUp( event ) { + + if ( event.key === 'Control' ) { + + this._controlActive = false; + + const document = this.domElement.getRootNode(); // offscreen canvas compatibility + + document.removeEventListener( 'keyup', this._interceptControlUp, { passive: true, capture: true } ); } diff --git a/examples/jsm/controls/PointerLockControls.js b/examples/jsm/controls/PointerLockControls.js index 1d2856d3f6131b..15d418be9d0118 100644 --- a/examples/jsm/controls/PointerLockControls.js +++ b/examples/jsm/controls/PointerLockControls.js @@ -1,45 +1,125 @@ import { + Controls, Euler, - EventDispatcher, Vector3 } from 'three'; const _euler = new Euler( 0, 0, 0, 'YXZ' ); const _vector = new Vector3(); +/** + * Fires when the user moves the mouse. + * + * @event PointerLockControls#change + * @type {Object} + */ const _changeEvent = { type: 'change' }; + +/** + * Fires when the pointer lock status is "locked" (in other words: the mouse is captured). + * + * @event PointerLockControls#lock + * @type {Object} + */ const _lockEvent = { type: 'lock' }; + +/** + * Fires when the pointer lock status is "unlocked" (in other words: the mouse is not captured anymore). + * + * @event PointerLockControls#unlock + * @type {Object} + */ const _unlockEvent = { type: 'unlock' }; +const _MOUSE_SENSITIVITY = 0.002; const _PI_2 = Math.PI / 2; -class PointerLockControls extends EventDispatcher { - - constructor( camera, domElement ) { - - super(); - - this.camera = camera; - this.domElement = domElement; - +/** + * The implementation of this class is based on the [Pointer Lock API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Pointer_Lock_API}. + * `PointerLockControls` is a perfect choice for first person 3D games. + * + * ```js + * const controls = new PointerLockControls( camera, document.body ); + * + * // add event listener to show/hide a UI (e.g. the game's menu) + * controls.addEventListener( 'lock', function () { + * + * menu.style.display = 'none'; + * + * } ); + * + * controls.addEventListener( 'unlock', function () { + * + * menu.style.display = 'block'; + * + * } ); + * ``` + * + * @augments Controls + * @three_import import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js'; + */ +class PointerLockControls extends Controls { + + /** + * Constructs a new controls instance. + * + * @param {Camera} camera - The camera that is managed by the controls. + * @param {?HTMLDOMElement} domElement - The HTML element used for event listeners. + */ + constructor( camera, domElement = null ) { + + super( camera, domElement ); + + /** + * Whether the controls are locked or not. + * + * @type {boolean} + * @readonly + * @default false + */ this.isLocked = false; - // Set to constrain the pitch of the camera - // Range is 0 to Math.PI radians - this.minPolarAngle = 0; // radians - this.maxPolarAngle = Math.PI; // radians - + /** + * Camera pitch, lower limit. Range is '[0, Math.PI]' in radians. + * + * @type {number} + * @default 0 + */ + this.minPolarAngle = 0; + + /** + * Camera pitch, upper limit. Range is '[0, Math.PI]' in radians. + * + * @type {number} + * @default Math.PI + */ + this.maxPolarAngle = Math.PI; + + /** + * Multiplier for how much the pointer movement influences the camera rotation. + * + * @type {number} + * @default 1 + */ this.pointerSpeed = 1.0; + // event listeners + this._onMouseMove = onMouseMove.bind( this ); this._onPointerlockChange = onPointerlockChange.bind( this ); this._onPointerlockError = onPointerlockError.bind( this ); - this.connect(); + if ( this.domElement !== null ) { + + this.connect( this.domElement ); + + } } - connect() { + connect( element ) { + + super.connect( element ); this.domElement.ownerDocument.addEventListener( 'mousemove', this._onMouseMove ); this.domElement.ownerDocument.addEventListener( 'pointerlockchange', this._onPointerlockChange ); @@ -61,24 +141,39 @@ class PointerLockControls extends EventDispatcher { } - getObject() { // retaining this method for backward compatibility + getObject() { + + console.warn( 'THREE.PointerLockControls: getObject() has been deprecated. Use controls.object instead.' ); // @deprecated r169 - return this.camera; + return this.object; } + /** + * Returns the look direction of the camera. + * + * @param {Vector3} v - The target vector that is used to store the method's result. + * @return {Vector3} The normalized direction vector. + */ getDirection( v ) { - return v.set( 0, 0, - 1 ).applyQuaternion( this.camera.quaternion ); + return v.set( 0, 0, - 1 ).applyQuaternion( this.object.quaternion ); } + /** + * Moves the camera forward parallel to the xz-plane. Assumes camera.up is y-up. + * + * @param {number} distance - The signed distance. + */ moveForward( distance ) { + if ( this.enabled === false ) return; + // move forward parallel to the xz-plane // assumes camera.up is y-up - const camera = this.camera; + const camera = this.object; _vector.setFromMatrixColumn( camera.matrix, 0 ); @@ -88,9 +183,16 @@ class PointerLockControls extends EventDispatcher { } + /** + * Moves the camera sidewards parallel to the xz-plane. + * + * @param {number} distance - The signed distance. + */ moveRight( distance ) { - const camera = this.camera; + if ( this.enabled === false ) return; + + const camera = this.object; _vector.setFromMatrixColumn( camera.matrix, 0 ); @@ -98,12 +200,23 @@ class PointerLockControls extends EventDispatcher { } - lock() { + /** + * Activates the pointer lock. + * + * @param {boolean} [unadjustedMovement=false] - Disables OS-level adjustment for mouse acceleration, and accesses raw mouse input instead. + * Setting it to true will disable mouse acceleration. + */ + lock( unadjustedMovement = false ) { - this.domElement.requestPointerLock(); + this.domElement.requestPointerLock( { + unadjustedMovement + } ); } + /** + * Exits the pointer lock. + */ unlock() { this.domElement.ownerDocument.exitPointerLock(); @@ -116,16 +229,13 @@ class PointerLockControls extends EventDispatcher { function onMouseMove( event ) { - if ( this.isLocked === false ) return; - - const movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0; - const movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0; + if ( this.enabled === false || this.isLocked === false ) return; - const camera = this.camera; + const camera = this.object; _euler.setFromQuaternion( camera.quaternion ); - _euler.y -= movementX * 0.002 * this.pointerSpeed; - _euler.x -= movementY * 0.002 * this.pointerSpeed; + _euler.y -= event.movementX * _MOUSE_SENSITIVITY * this.pointerSpeed; + _euler.x -= event.movementY * _MOUSE_SENSITIVITY * this.pointerSpeed; _euler.x = Math.max( _PI_2 - this.maxPolarAngle, Math.min( _PI_2 - this.minPolarAngle, _euler.x ) ); diff --git a/examples/jsm/controls/TrackballControls.js b/examples/jsm/controls/TrackballControls.js index c4f1114837b18b..3693294c86460f 100644 --- a/examples/jsm/controls/TrackballControls.js +++ b/examples/jsm/controls/TrackballControls.js @@ -1,5 +1,5 @@ import { - EventDispatcher, + Controls, MathUtils, MOUSE, Quaternion, @@ -7,822 +7,995 @@ import { Vector3 } from 'three'; +/** + * Fires when the camera has been transformed by the controls. + * + * @event TrackballControls#change + * @type {Object} + */ const _changeEvent = { type: 'change' }; -const _startEvent = { type: 'start' }; -const _endEvent = { type: 'end' }; - -class TrackballControls extends EventDispatcher { - - constructor( object, domElement ) { - - super(); - - const scope = this; - const STATE = { NONE: - 1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM_PAN: 4 }; - this.object = object; - this.domElement = domElement; - this.domElement.style.touchAction = 'none'; // disable touch scroll - - // API +/** + * Fires when an interaction was initiated. + * + * @event TrackballControls#start + * @type {Object} + */ +const _startEvent = { type: 'start' }; - this.enabled = true; +/** + * Fires when an interaction has finished. + * + * @event TrackballControls#end + * @type {Object} + */ +const _endEvent = { type: 'end' }; +const _EPS = 0.000001; +const _STATE = { NONE: - 1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM_PAN: 4 }; + +const _v2 = new Vector2(); +const _mouseChange = new Vector2(); +const _objectUp = new Vector3(); +const _pan = new Vector3(); +const _axis = new Vector3(); +const _quaternion = new Quaternion(); +const _eyeDirection = new Vector3(); +const _objectUpDirection = new Vector3(); +const _objectSidewaysDirection = new Vector3(); +const _moveDirection = new Vector3(); + +/** + * This class is similar to {@link OrbitControls}. However, it does not maintain a constant camera + * `up` vector. That means if the camera orbits over the “north” and “south” poles, it does not flip + * to stay "right side up". + * + * @augments Controls + * @three_import import { TrackballControls } from 'three/addons/controls/TrackballControls.js'; + */ +class TrackballControls extends Controls { + + /** + * Constructs a new controls instance. + * + * @param {Object3D} object - The object that is managed by the controls. + * @param {?HTMLDOMElement} domElement - The HTML element used for event listeners. + */ + constructor( object, domElement = null ) { + + super( object, domElement ); + + /** + * Represents the properties of the screen. Automatically set when `handleResize()` is called. + * + * @type {Object} + * @readonly + */ this.screen = { left: 0, top: 0, width: 0, height: 0 }; + /** + * The rotation speed. + * + * @type {number} + * @default 1 + */ this.rotateSpeed = 1.0; + + /** + * The zoom speed. + * + * @type {number} + * @default 1.2 + */ this.zoomSpeed = 1.2; + + /** + * The pan speed. + * + * @type {number} + * @default 0.3 + */ this.panSpeed = 0.3; + /** + * Whether rotation is disabled or not. + * + * @type {boolean} + * @default false + */ this.noRotate = false; + + /** + * Whether zooming is disabled or not. + * + * @type {boolean} + * @default false + */ this.noZoom = false; + + /** + * Whether panning is disabled or not. + * + * @type {boolean} + * @default false + */ this.noPan = false; + /** + * Whether damping is disabled or not. + * + * @type {boolean} + * @default false + */ this.staticMoving = false; + + /** + * Defines the intensity of damping. Only considered if `staticMoving` is set to `false`. + * + * @type {number} + * @default 0.2 + */ this.dynamicDampingFactor = 0.2; + /** + * How far you can dolly in (perspective camera only). + * + * @type {number} + * @default 0 + */ this.minDistance = 0; + + /** + * How far you can dolly out (perspective camera only). + * + * @type {number} + * @default Infinity + */ this.maxDistance = Infinity; + /** + * How far you can zoom in (orthographic camera only). + * + * @type {number} + * @default 0 + */ this.minZoom = 0; + + /** + * How far you can zoom out (orthographic camera only). + * + * @type {number} + * @default Infinity + */ this.maxZoom = Infinity; + /** + * This array holds keycodes for controlling interactions. + * + * - When the first defined key is pressed, all mouse interactions (left, middle, right) performs orbiting. + * - When the second defined key is pressed, all mouse interactions (left, middle, right) performs zooming. + * - When the third defined key is pressed, all mouse interactions (left, middle, right) performs panning. + * + * Default is *KeyA, KeyS, KeyD* which represents A, S, D. + * + * @type {Array} + */ this.keys = [ 'KeyA' /*A*/, 'KeyS' /*S*/, 'KeyD' /*D*/ ]; + /** + * This object contains references to the mouse actions used by the controls. + * + * ```js + * controls.mouseButtons = { + * LEFT: THREE.MOUSE.ROTATE, + * MIDDLE: THREE.MOUSE.DOLLY, + * RIGHT: THREE.MOUSE.PAN + * } + * ``` + * @type {Object} + */ this.mouseButtons = { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN }; - // internals - + /** + * The focus point of the controls. + * + * @type {Vector3} + */ this.target = new Vector3(); - const EPS = 0.000001; - - const lastPosition = new Vector3(); - let lastZoom = 1; + // internals - let _state = STATE.NONE, - _keyState = STATE.NONE, + this.state = _STATE.NONE; + this.keyState = _STATE.NONE; - _touchZoomDistanceStart = 0, - _touchZoomDistanceEnd = 0, + this._lastPosition = new Vector3(); + this._lastZoom = 1; + this._touchZoomDistanceStart = 0; + this._touchZoomDistanceEnd = 0; + this._lastAngle = 0; - _lastAngle = 0; + this._eye = new Vector3(); - const _eye = new Vector3(), + this._movePrev = new Vector2(); + this._moveCurr = new Vector2(); - _movePrev = new Vector2(), - _moveCurr = new Vector2(), + this._lastAxis = new Vector3(); - _lastAxis = new Vector3(), + this._zoomStart = new Vector2(); + this._zoomEnd = new Vector2(); - _zoomStart = new Vector2(), - _zoomEnd = new Vector2(), + this._panStart = new Vector2(); + this._panEnd = new Vector2(); - _panStart = new Vector2(), - _panEnd = new Vector2(), + this._pointers = []; + this._pointerPositions = {}; - _pointers = [], - _pointerPositions = {}; + // event listeners - // for reset + this._onPointerMove = onPointerMove.bind( this ); + this._onPointerDown = onPointerDown.bind( this ); + this._onPointerUp = onPointerUp.bind( this ); + this._onPointerCancel = onPointerCancel.bind( this ); + this._onContextMenu = onContextMenu.bind( this ); + this._onMouseWheel = onMouseWheel.bind( this ); + this._onKeyDown = onKeyDown.bind( this ); + this._onKeyUp = onKeyUp.bind( this ); - this.target0 = this.target.clone(); - this.position0 = this.object.position.clone(); - this.up0 = this.object.up.clone(); - this.zoom0 = this.object.zoom; + this._onTouchStart = onTouchStart.bind( this ); + this._onTouchMove = onTouchMove.bind( this ); + this._onTouchEnd = onTouchEnd.bind( this ); - // methods + this._onMouseDown = onMouseDown.bind( this ); + this._onMouseMove = onMouseMove.bind( this ); + this._onMouseUp = onMouseUp.bind( this ); - this.handleResize = function () { + // for reset - const box = scope.domElement.getBoundingClientRect(); - // adjustments come from similar code in the jquery offset() function - const d = scope.domElement.ownerDocument.documentElement; - scope.screen.left = box.left + window.pageXOffset - d.clientLeft; - scope.screen.top = box.top + window.pageYOffset - d.clientTop; - scope.screen.width = box.width; - scope.screen.height = box.height; + this._target0 = this.target.clone(); + this._position0 = this.object.position.clone(); + this._up0 = this.object.up.clone(); + this._zoom0 = this.object.zoom; - }; + if ( domElement !== null ) { - const getMouseOnScreen = ( function () { + this.connect( domElement ); - const vector = new Vector2(); + this.handleResize(); - return function getMouseOnScreen( pageX, pageY ) { + } - vector.set( - ( pageX - scope.screen.left ) / scope.screen.width, - ( pageY - scope.screen.top ) / scope.screen.height - ); + // force an update at start + this.update(); - return vector; + } - }; + connect( element ) { - }() ); + super.connect( element ); - const getMouseOnCircle = ( function () { + window.addEventListener( 'keydown', this._onKeyDown ); + window.addEventListener( 'keyup', this._onKeyUp ); - const vector = new Vector2(); + this.domElement.addEventListener( 'pointerdown', this._onPointerDown ); + this.domElement.addEventListener( 'pointercancel', this._onPointerCancel ); + this.domElement.addEventListener( 'wheel', this._onMouseWheel, { passive: false } ); + this.domElement.addEventListener( 'contextmenu', this._onContextMenu ); - return function getMouseOnCircle( pageX, pageY ) { + this.domElement.style.touchAction = 'none'; // disable touch scroll - vector.set( - ( ( pageX - scope.screen.width * 0.5 - scope.screen.left ) / ( scope.screen.width * 0.5 ) ), - ( ( scope.screen.height + 2 * ( scope.screen.top - pageY ) ) / scope.screen.width ) // screen.width intentional - ); + } - return vector; + disconnect() { - }; + window.removeEventListener( 'keydown', this._onKeyDown ); + window.removeEventListener( 'keyup', this._onKeyUp ); - }() ); + this.domElement.removeEventListener( 'pointerdown', this._onPointerDown ); + this.domElement.removeEventListener( 'pointermove', this._onPointerMove ); + this.domElement.removeEventListener( 'pointerup', this._onPointerUp ); + this.domElement.removeEventListener( 'pointercancel', this._onPointerCancel ); + this.domElement.removeEventListener( 'wheel', this._onMouseWheel ); + this.domElement.removeEventListener( 'contextmenu', this._onContextMenu ); - this.rotateCamera = ( function () { + this.domElement.style.touchAction = 'auto'; // disable touch scroll - const axis = new Vector3(), - quaternion = new Quaternion(), - eyeDirection = new Vector3(), - objectUpDirection = new Vector3(), - objectSidewaysDirection = new Vector3(), - moveDirection = new Vector3(); + } - return function rotateCamera() { + dispose() { - moveDirection.set( _moveCurr.x - _movePrev.x, _moveCurr.y - _movePrev.y, 0 ); - let angle = moveDirection.length(); + this.disconnect(); - if ( angle ) { + } - _eye.copy( scope.object.position ).sub( scope.target ); + /** + * Must be called if the application window is resized. + */ + handleResize() { - eyeDirection.copy( _eye ).normalize(); - objectUpDirection.copy( scope.object.up ).normalize(); - objectSidewaysDirection.crossVectors( objectUpDirection, eyeDirection ).normalize(); + const box = this.domElement.getBoundingClientRect(); + // adjustments come from similar code in the jquery offset() function + const d = this.domElement.ownerDocument.documentElement; - objectUpDirection.setLength( _moveCurr.y - _movePrev.y ); - objectSidewaysDirection.setLength( _moveCurr.x - _movePrev.x ); + this.screen.left = box.left + window.pageXOffset - d.clientLeft; + this.screen.top = box.top + window.pageYOffset - d.clientTop; + this.screen.width = box.width; + this.screen.height = box.height; - moveDirection.copy( objectUpDirection.add( objectSidewaysDirection ) ); + } - axis.crossVectors( moveDirection, _eye ).normalize(); + update() { - angle *= scope.rotateSpeed; - quaternion.setFromAxisAngle( axis, angle ); + this._eye.subVectors( this.object.position, this.target ); - _eye.applyQuaternion( quaternion ); - scope.object.up.applyQuaternion( quaternion ); + if ( ! this.noRotate ) { - _lastAxis.copy( axis ); - _lastAngle = angle; + this._rotateCamera(); - } else if ( ! scope.staticMoving && _lastAngle ) { + } - _lastAngle *= Math.sqrt( 1.0 - scope.dynamicDampingFactor ); - _eye.copy( scope.object.position ).sub( scope.target ); - quaternion.setFromAxisAngle( _lastAxis, _lastAngle ); - _eye.applyQuaternion( quaternion ); - scope.object.up.applyQuaternion( quaternion ); + if ( ! this.noZoom ) { - } + this._zoomCamera(); - _movePrev.copy( _moveCurr ); + } - }; + if ( ! this.noPan ) { - }() ); + this._panCamera(); + } - this.zoomCamera = function () { + this.object.position.addVectors( this.target, this._eye ); - let factor; + if ( this.object.isPerspectiveCamera ) { - if ( _state === STATE.TOUCH_ZOOM_PAN ) { + this._checkDistances(); - factor = _touchZoomDistanceStart / _touchZoomDistanceEnd; - _touchZoomDistanceStart = _touchZoomDistanceEnd; + this.object.lookAt( this.target ); - if ( scope.object.isPerspectiveCamera ) { + if ( this._lastPosition.distanceToSquared( this.object.position ) > _EPS ) { - _eye.multiplyScalar( factor ); + this.dispatchEvent( _changeEvent ); - } else if ( scope.object.isOrthographicCamera ) { + this._lastPosition.copy( this.object.position ); - scope.object.zoom = MathUtils.clamp( scope.object.zoom / factor, scope.minZoom, scope.maxZoom ); + } - if ( lastZoom !== scope.object.zoom ) { + } else if ( this.object.isOrthographicCamera ) { - scope.object.updateProjectionMatrix(); + this.object.lookAt( this.target ); - } + if ( this._lastPosition.distanceToSquared( this.object.position ) > _EPS || this._lastZoom !== this.object.zoom ) { - } else { + this.dispatchEvent( _changeEvent ); - console.warn( 'THREE.TrackballControls: Unsupported camera type' ); + this._lastPosition.copy( this.object.position ); + this._lastZoom = this.object.zoom; - } + } - } else { + } else { - factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * scope.zoomSpeed; + console.warn( 'THREE.TrackballControls: Unsupported camera type.' ); - if ( factor !== 1.0 && factor > 0.0 ) { + } - if ( scope.object.isPerspectiveCamera ) { + } - _eye.multiplyScalar( factor ); + /** + * Resets the controls to its initial state. + */ + reset() { - } else if ( scope.object.isOrthographicCamera ) { + this.state = _STATE.NONE; + this.keyState = _STATE.NONE; - scope.object.zoom = MathUtils.clamp( scope.object.zoom / factor, scope.minZoom, scope.maxZoom ); + this.target.copy( this._target0 ); + this.object.position.copy( this._position0 ); + this.object.up.copy( this._up0 ); + this.object.zoom = this._zoom0; - if ( lastZoom !== scope.object.zoom ) { + this.object.updateProjectionMatrix(); - scope.object.updateProjectionMatrix(); + this._eye.subVectors( this.object.position, this.target ); - } + this.object.lookAt( this.target ); - } else { + this.dispatchEvent( _changeEvent ); - console.warn( 'THREE.TrackballControls: Unsupported camera type' ); + this._lastPosition.copy( this.object.position ); + this._lastZoom = this.object.zoom; - } + } - } + _panCamera() { - if ( scope.staticMoving ) { + _mouseChange.copy( this._panEnd ).sub( this._panStart ); - _zoomStart.copy( _zoomEnd ); + if ( _mouseChange.lengthSq() ) { - } else { + if ( this.object.isOrthographicCamera ) { - _zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor; + const scale_x = ( this.object.right - this.object.left ) / this.object.zoom / this.domElement.clientWidth; + const scale_y = ( this.object.top - this.object.bottom ) / this.object.zoom / this.domElement.clientWidth; - } + _mouseChange.x *= scale_x; + _mouseChange.y *= scale_y; } - }; + _mouseChange.multiplyScalar( this._eye.length() * this.panSpeed ); - this.panCamera = ( function () { + _pan.copy( this._eye ).cross( this.object.up ).setLength( _mouseChange.x ); + _pan.add( _objectUp.copy( this.object.up ).setLength( _mouseChange.y ) ); - const mouseChange = new Vector2(), - objectUp = new Vector3(), - pan = new Vector3(); + this.object.position.add( _pan ); + this.target.add( _pan ); - return function panCamera() { + if ( this.staticMoving ) { - mouseChange.copy( _panEnd ).sub( _panStart ); + this._panStart.copy( this._panEnd ); - if ( mouseChange.lengthSq() ) { + } else { - if ( scope.object.isOrthographicCamera ) { + this._panStart.add( _mouseChange.subVectors( this._panEnd, this._panStart ).multiplyScalar( this.dynamicDampingFactor ) ); - const scale_x = ( scope.object.right - scope.object.left ) / scope.object.zoom / scope.domElement.clientWidth; - const scale_y = ( scope.object.top - scope.object.bottom ) / scope.object.zoom / scope.domElement.clientWidth; + } - mouseChange.x *= scale_x; - mouseChange.y *= scale_y; + } - } + } - mouseChange.multiplyScalar( _eye.length() * scope.panSpeed ); + _rotateCamera() { - pan.copy( _eye ).cross( scope.object.up ).setLength( mouseChange.x ); - pan.add( objectUp.copy( scope.object.up ).setLength( mouseChange.y ) ); + _moveDirection.set( this._moveCurr.x - this._movePrev.x, this._moveCurr.y - this._movePrev.y, 0 ); + let angle = _moveDirection.length(); - scope.object.position.add( pan ); - scope.target.add( pan ); + if ( angle ) { - if ( scope.staticMoving ) { + this._eye.copy( this.object.position ).sub( this.target ); - _panStart.copy( _panEnd ); + _eyeDirection.copy( this._eye ).normalize(); + _objectUpDirection.copy( this.object.up ).normalize(); + _objectSidewaysDirection.crossVectors( _objectUpDirection, _eyeDirection ).normalize(); - } else { + _objectUpDirection.setLength( this._moveCurr.y - this._movePrev.y ); + _objectSidewaysDirection.setLength( this._moveCurr.x - this._movePrev.x ); - _panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( scope.dynamicDampingFactor ) ); + _moveDirection.copy( _objectUpDirection.add( _objectSidewaysDirection ) ); - } + _axis.crossVectors( _moveDirection, this._eye ).normalize(); - } + angle *= this.rotateSpeed; + _quaternion.setFromAxisAngle( _axis, angle ); - }; + this._eye.applyQuaternion( _quaternion ); + this.object.up.applyQuaternion( _quaternion ); - }() ); + this._lastAxis.copy( _axis ); + this._lastAngle = angle; - this.checkDistances = function () { + } else if ( ! this.staticMoving && this._lastAngle ) { - if ( ! scope.noZoom || ! scope.noPan ) { + this._lastAngle *= Math.sqrt( 1.0 - this.dynamicDampingFactor ); + this._eye.copy( this.object.position ).sub( this.target ); + _quaternion.setFromAxisAngle( this._lastAxis, this._lastAngle ); + this._eye.applyQuaternion( _quaternion ); + this.object.up.applyQuaternion( _quaternion ); - if ( _eye.lengthSq() > scope.maxDistance * scope.maxDistance ) { + } - scope.object.position.addVectors( scope.target, _eye.setLength( scope.maxDistance ) ); - _zoomStart.copy( _zoomEnd ); + this._movePrev.copy( this._moveCurr ); - } + } - if ( _eye.lengthSq() < scope.minDistance * scope.minDistance ) { + _zoomCamera() { - scope.object.position.addVectors( scope.target, _eye.setLength( scope.minDistance ) ); - _zoomStart.copy( _zoomEnd ); + let factor; - } + if ( this.state === _STATE.TOUCH_ZOOM_PAN ) { - } + factor = this._touchZoomDistanceStart / this._touchZoomDistanceEnd; + this._touchZoomDistanceStart = this._touchZoomDistanceEnd; - }; + if ( this.object.isPerspectiveCamera ) { - this.update = function () { + this._eye.multiplyScalar( factor ); - _eye.subVectors( scope.object.position, scope.target ); + } else if ( this.object.isOrthographicCamera ) { - if ( ! scope.noRotate ) { + this.object.zoom = MathUtils.clamp( this.object.zoom / factor, this.minZoom, this.maxZoom ); - scope.rotateCamera(); + if ( this._lastZoom !== this.object.zoom ) { - } + this.object.updateProjectionMatrix(); - if ( ! scope.noZoom ) { + } - scope.zoomCamera(); + } else { - } + console.warn( 'THREE.TrackballControls: Unsupported camera type' ); - if ( ! scope.noPan ) { + } - scope.panCamera(); + } else { - } + factor = 1.0 + ( this._zoomEnd.y - this._zoomStart.y ) * this.zoomSpeed; - scope.object.position.addVectors( scope.target, _eye ); + if ( factor !== 1.0 && factor > 0.0 ) { - if ( scope.object.isPerspectiveCamera ) { + if ( this.object.isPerspectiveCamera ) { - scope.checkDistances(); + this._eye.multiplyScalar( factor ); - scope.object.lookAt( scope.target ); + } else if ( this.object.isOrthographicCamera ) { - if ( lastPosition.distanceToSquared( scope.object.position ) > EPS ) { + this.object.zoom = MathUtils.clamp( this.object.zoom / factor, this.minZoom, this.maxZoom ); - scope.dispatchEvent( _changeEvent ); + if ( this._lastZoom !== this.object.zoom ) { - lastPosition.copy( scope.object.position ); + this.object.updateProjectionMatrix(); - } + } - } else if ( scope.object.isOrthographicCamera ) { + } else { - scope.object.lookAt( scope.target ); + console.warn( 'THREE.TrackballControls: Unsupported camera type' ); - if ( lastPosition.distanceToSquared( scope.object.position ) > EPS || lastZoom !== scope.object.zoom ) { + } - scope.dispatchEvent( _changeEvent ); + } - lastPosition.copy( scope.object.position ); - lastZoom = scope.object.zoom; + if ( this.staticMoving ) { - } + this._zoomStart.copy( this._zoomEnd ); } else { - console.warn( 'THREE.TrackballControls: Unsupported camera type' ); + this._zoomStart.y += ( this._zoomEnd.y - this._zoomStart.y ) * this.dynamicDampingFactor; } - }; + } + + } - this.reset = function () { + _getMouseOnScreen( pageX, pageY ) { - _state = STATE.NONE; - _keyState = STATE.NONE; + _v2.set( + ( pageX - this.screen.left ) / this.screen.width, + ( pageY - this.screen.top ) / this.screen.height + ); - scope.target.copy( scope.target0 ); - scope.object.position.copy( scope.position0 ); - scope.object.up.copy( scope.up0 ); - scope.object.zoom = scope.zoom0; + return _v2; + + } - scope.object.updateProjectionMatrix(); + _getMouseOnCircle( pageX, pageY ) { - _eye.subVectors( scope.object.position, scope.target ); + _v2.set( + ( ( pageX - this.screen.width * 0.5 - this.screen.left ) / ( this.screen.width * 0.5 ) ), + ( ( this.screen.height + 2 * ( this.screen.top - pageY ) ) / this.screen.width ) // screen.width intentional + ); - scope.object.lookAt( scope.target ); + return _v2; - scope.dispatchEvent( _changeEvent ); + } - lastPosition.copy( scope.object.position ); - lastZoom = scope.object.zoom; + _addPointer( event ) { - }; + this._pointers.push( event ); - // listeners + } - function onPointerDown( event ) { + _removePointer( event ) { - if ( scope.enabled === false ) return; + delete this._pointerPositions[ event.pointerId ]; - if ( _pointers.length === 0 ) { + for ( let i = 0; i < this._pointers.length; i ++ ) { - scope.domElement.setPointerCapture( event.pointerId ); + if ( this._pointers[ i ].pointerId == event.pointerId ) { - scope.domElement.addEventListener( 'pointermove', onPointerMove ); - scope.domElement.addEventListener( 'pointerup', onPointerUp ); + this._pointers.splice( i, 1 ); + return; } - // - - addPointer( event ); + } - if ( event.pointerType === 'touch' ) { + } - onTouchStart( event ); + _trackPointer( event ) { - } else { + let position = this._pointerPositions[ event.pointerId ]; - onMouseDown( event ); + if ( position === undefined ) { - } + position = new Vector2(); + this._pointerPositions[ event.pointerId ] = position; } - function onPointerMove( event ) { + position.set( event.pageX, event.pageY ); - if ( scope.enabled === false ) return; - - if ( event.pointerType === 'touch' ) { + } - onTouchMove( event ); + _getSecondPointerPosition( event ) { - } else { + const pointer = ( event.pointerId === this._pointers[ 0 ].pointerId ) ? this._pointers[ 1 ] : this._pointers[ 0 ]; - onMouseMove( event ); + return this._pointerPositions[ pointer.pointerId ]; - } + } - } + _checkDistances() { - function onPointerUp( event ) { + if ( ! this.noZoom || ! this.noPan ) { - if ( scope.enabled === false ) return; + if ( this._eye.lengthSq() > this.maxDistance * this.maxDistance ) { - if ( event.pointerType === 'touch' ) { + this.object.position.addVectors( this.target, this._eye.setLength( this.maxDistance ) ); + this._zoomStart.copy( this._zoomEnd ); - onTouchEnd( event ); + } - } else { + if ( this._eye.lengthSq() < this.minDistance * this.minDistance ) { - onMouseUp(); + this.object.position.addVectors( this.target, this._eye.setLength( this.minDistance ) ); + this._zoomStart.copy( this._zoomEnd ); } - // + } - removePointer( event ); + } - if ( _pointers.length === 0 ) { +} - scope.domElement.releasePointerCapture( event.pointerId ); +function onPointerDown( event ) { - scope.domElement.removeEventListener( 'pointermove', onPointerMove ); - scope.domElement.removeEventListener( 'pointerup', onPointerUp ); + if ( this.enabled === false ) return; - } + if ( this._pointers.length === 0 ) { + this.domElement.setPointerCapture( event.pointerId ); - } + this.domElement.addEventListener( 'pointermove', this._onPointerMove ); + this.domElement.addEventListener( 'pointerup', this._onPointerUp ); - function onPointerCancel( event ) { + } - removePointer( event ); + // - } + this._addPointer( event ); - function keydown( event ) { + if ( event.pointerType === 'touch' ) { - if ( scope.enabled === false ) return; + this._onTouchStart( event ); - window.removeEventListener( 'keydown', keydown ); + } else { - if ( _keyState !== STATE.NONE ) { + this._onMouseDown( event ); - return; + } - } else if ( event.code === scope.keys[ STATE.ROTATE ] && ! scope.noRotate ) { +} - _keyState = STATE.ROTATE; +function onPointerMove( event ) { - } else if ( event.code === scope.keys[ STATE.ZOOM ] && ! scope.noZoom ) { + if ( this.enabled === false ) return; - _keyState = STATE.ZOOM; + if ( event.pointerType === 'touch' ) { - } else if ( event.code === scope.keys[ STATE.PAN ] && ! scope.noPan ) { + this._onTouchMove( event ); - _keyState = STATE.PAN; + } else { - } + this._onMouseMove( event ); - } + } - function keyup() { +} - if ( scope.enabled === false ) return; +function onPointerUp( event ) { - _keyState = STATE.NONE; + if ( this.enabled === false ) return; - window.addEventListener( 'keydown', keydown ); + if ( event.pointerType === 'touch' ) { - } + this._onTouchEnd( event ); - function onMouseDown( event ) { + } else { - if ( _state === STATE.NONE ) { + this._onMouseUp(); - switch ( event.button ) { + } - case scope.mouseButtons.LEFT: - _state = STATE.ROTATE; - break; + // - case scope.mouseButtons.MIDDLE: - _state = STATE.ZOOM; - break; + this._removePointer( event ); - case scope.mouseButtons.RIGHT: - _state = STATE.PAN; - break; + if ( this._pointers.length === 0 ) { - } + this.domElement.releasePointerCapture( event.pointerId ); - } + this.domElement.removeEventListener( 'pointermove', this._onPointerMove ); + this.domElement.removeEventListener( 'pointerup', this._onPointerUp ); - const state = ( _keyState !== STATE.NONE ) ? _keyState : _state; + } - if ( state === STATE.ROTATE && ! scope.noRotate ) { +} - _moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) ); - _movePrev.copy( _moveCurr ); +function onPointerCancel( event ) { - } else if ( state === STATE.ZOOM && ! scope.noZoom ) { + this._removePointer( event ); - _zoomStart.copy( getMouseOnScreen( event.pageX, event.pageY ) ); - _zoomEnd.copy( _zoomStart ); +} - } else if ( state === STATE.PAN && ! scope.noPan ) { +function onKeyUp() { - _panStart.copy( getMouseOnScreen( event.pageX, event.pageY ) ); - _panEnd.copy( _panStart ); + if ( this.enabled === false ) return; - } + this.keyState = _STATE.NONE; - scope.dispatchEvent( _startEvent ); + window.addEventListener( 'keydown', this._onKeyDown ); - } +} - function onMouseMove( event ) { +function onKeyDown( event ) { - const state = ( _keyState !== STATE.NONE ) ? _keyState : _state; + if ( this.enabled === false ) return; - if ( state === STATE.ROTATE && ! scope.noRotate ) { + window.removeEventListener( 'keydown', this._onKeyDown ); - _movePrev.copy( _moveCurr ); - _moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) ); + if ( this.keyState !== _STATE.NONE ) { - } else if ( state === STATE.ZOOM && ! scope.noZoom ) { + return; - _zoomEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) ); + } else if ( event.code === this.keys[ _STATE.ROTATE ] && ! this.noRotate ) { - } else if ( state === STATE.PAN && ! scope.noPan ) { + this.keyState = _STATE.ROTATE; - _panEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) ); + } else if ( event.code === this.keys[ _STATE.ZOOM ] && ! this.noZoom ) { - } + this.keyState = _STATE.ZOOM; - } + } else if ( event.code === this.keys[ _STATE.PAN ] && ! this.noPan ) { - function onMouseUp() { + this.keyState = _STATE.PAN; - _state = STATE.NONE; + } - scope.dispatchEvent( _endEvent ); +} - } +function onMouseDown( event ) { - function onMouseWheel( event ) { + let mouseAction; - if ( scope.enabled === false ) return; + switch ( event.button ) { - if ( scope.noZoom === true ) return; + case 0: + mouseAction = this.mouseButtons.LEFT; + break; - event.preventDefault(); + case 1: + mouseAction = this.mouseButtons.MIDDLE; + break; - switch ( event.deltaMode ) { + case 2: + mouseAction = this.mouseButtons.RIGHT; + break; - case 2: - // Zoom in pages - _zoomStart.y -= event.deltaY * 0.025; - break; + default: + mouseAction = - 1; - case 1: - // Zoom in lines - _zoomStart.y -= event.deltaY * 0.01; - break; + } - default: - // undefined, 0, assume pixels - _zoomStart.y -= event.deltaY * 0.00025; - break; + switch ( mouseAction ) { - } + case MOUSE.DOLLY: + this.state = _STATE.ZOOM; + break; - scope.dispatchEvent( _startEvent ); - scope.dispatchEvent( _endEvent ); + case MOUSE.ROTATE: + this.state = _STATE.ROTATE; + break; - } + case MOUSE.PAN: + this.state = _STATE.PAN; + break; - function onTouchStart( event ) { + default: + this.state = _STATE.NONE; - trackPointer( event ); + } - switch ( _pointers.length ) { + const state = ( this.keyState !== _STATE.NONE ) ? this.keyState : this.state; - case 1: - _state = STATE.TOUCH_ROTATE; - _moveCurr.copy( getMouseOnCircle( _pointers[ 0 ].pageX, _pointers[ 0 ].pageY ) ); - _movePrev.copy( _moveCurr ); - break; + if ( state === _STATE.ROTATE && ! this.noRotate ) { - default: // 2 or more - _state = STATE.TOUCH_ZOOM_PAN; - const dx = _pointers[ 0 ].pageX - _pointers[ 1 ].pageX; - const dy = _pointers[ 0 ].pageY - _pointers[ 1 ].pageY; - _touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy ); + this._moveCurr.copy( this._getMouseOnCircle( event.pageX, event.pageY ) ); + this._movePrev.copy( this._moveCurr ); - const x = ( _pointers[ 0 ].pageX + _pointers[ 1 ].pageX ) / 2; - const y = ( _pointers[ 0 ].pageY + _pointers[ 1 ].pageY ) / 2; - _panStart.copy( getMouseOnScreen( x, y ) ); - _panEnd.copy( _panStart ); - break; + } else if ( state === _STATE.ZOOM && ! this.noZoom ) { - } + this._zoomStart.copy( this._getMouseOnScreen( event.pageX, event.pageY ) ); + this._zoomEnd.copy( this._zoomStart ); - scope.dispatchEvent( _startEvent ); + } else if ( state === _STATE.PAN && ! this.noPan ) { - } + this._panStart.copy( this._getMouseOnScreen( event.pageX, event.pageY ) ); + this._panEnd.copy( this._panStart ); - function onTouchMove( event ) { + } - trackPointer( event ); + this.dispatchEvent( _startEvent ); - switch ( _pointers.length ) { +} - case 1: - _movePrev.copy( _moveCurr ); - _moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) ); - break; +function onMouseMove( event ) { - default: // 2 or more + const state = ( this.keyState !== _STATE.NONE ) ? this.keyState : this.state; - const position = getSecondPointerPosition( event ); + if ( state === _STATE.ROTATE && ! this.noRotate ) { - const dx = event.pageX - position.x; - const dy = event.pageY - position.y; - _touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy ); + this._movePrev.copy( this._moveCurr ); + this._moveCurr.copy( this._getMouseOnCircle( event.pageX, event.pageY ) ); - const x = ( event.pageX + position.x ) / 2; - const y = ( event.pageY + position.y ) / 2; - _panEnd.copy( getMouseOnScreen( x, y ) ); - break; + } else if ( state === _STATE.ZOOM && ! this.noZoom ) { - } + this._zoomEnd.copy( this._getMouseOnScreen( event.pageX, event.pageY ) ); - } + } else if ( state === _STATE.PAN && ! this.noPan ) { - function onTouchEnd( event ) { + this._panEnd.copy( this._getMouseOnScreen( event.pageX, event.pageY ) ); - switch ( _pointers.length ) { + } - case 0: - _state = STATE.NONE; - break; +} - case 1: - _state = STATE.TOUCH_ROTATE; - _moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) ); - _movePrev.copy( _moveCurr ); - break; +function onMouseUp() { - case 2: - _state = STATE.TOUCH_ZOOM_PAN; + this.state = _STATE.NONE; - for ( let i = 0; i < _pointers.length; i ++ ) { + this.dispatchEvent( _endEvent ); - if ( _pointers[ i ].pointerId !== event.pointerId ) { +} - const position = _pointerPositions[ _pointers[ i ].pointerId ]; - _moveCurr.copy( getMouseOnCircle( position.x, position.y ) ); - _movePrev.copy( _moveCurr ); - break; +function onMouseWheel( event ) { - } + if ( this.enabled === false ) return; - } + if ( this.noZoom === true ) return; - break; + event.preventDefault(); - } + switch ( event.deltaMode ) { - scope.dispatchEvent( _endEvent ); + case 2: + // Zoom in pages + this._zoomStart.y -= event.deltaY * 0.025; + break; - } + case 1: + // Zoom in lines + this._zoomStart.y -= event.deltaY * 0.01; + break; - function contextmenu( event ) { + default: + // undefined, 0, assume pixels + this._zoomStart.y -= event.deltaY * 0.00025; + break; - if ( scope.enabled === false ) return; + } - event.preventDefault(); + this.dispatchEvent( _startEvent ); + this.dispatchEvent( _endEvent ); - } +} - function addPointer( event ) { +function onContextMenu( event ) { - _pointers.push( event ); + if ( this.enabled === false ) return; - } + event.preventDefault(); - function removePointer( event ) { +} - delete _pointerPositions[ event.pointerId ]; +function onTouchStart( event ) { - for ( let i = 0; i < _pointers.length; i ++ ) { + this._trackPointer( event ); - if ( _pointers[ i ].pointerId == event.pointerId ) { + switch ( this._pointers.length ) { - _pointers.splice( i, 1 ); - return; + case 1: + this.state = _STATE.TOUCH_ROTATE; + this._moveCurr.copy( this._getMouseOnCircle( this._pointers[ 0 ].pageX, this._pointers[ 0 ].pageY ) ); + this._movePrev.copy( this._moveCurr ); + break; - } + default: // 2 or more + this.state = _STATE.TOUCH_ZOOM_PAN; + const dx = this._pointers[ 0 ].pageX - this._pointers[ 1 ].pageX; + const dy = this._pointers[ 0 ].pageY - this._pointers[ 1 ].pageY; + this._touchZoomDistanceEnd = this._touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy ); - } + const x = ( this._pointers[ 0 ].pageX + this._pointers[ 1 ].pageX ) / 2; + const y = ( this._pointers[ 0 ].pageY + this._pointers[ 1 ].pageY ) / 2; + this._panStart.copy( this._getMouseOnScreen( x, y ) ); + this._panEnd.copy( this._panStart ); + break; - } + } - function trackPointer( event ) { + this.dispatchEvent( _startEvent ); - let position = _pointerPositions[ event.pointerId ]; +} - if ( position === undefined ) { +function onTouchMove( event ) { - position = new Vector2(); - _pointerPositions[ event.pointerId ] = position; + this._trackPointer( event ); - } + switch ( this._pointers.length ) { - position.set( event.pageX, event.pageY ); + case 1: + this._movePrev.copy( this._moveCurr ); + this._moveCurr.copy( this._getMouseOnCircle( event.pageX, event.pageY ) ); + break; - } + default: // 2 or more - function getSecondPointerPosition( event ) { + const position = this._getSecondPointerPosition( event ); - const pointer = ( event.pointerId === _pointers[ 0 ].pointerId ) ? _pointers[ 1 ] : _pointers[ 0 ]; + const dx = event.pageX - position.x; + const dy = event.pageY - position.y; + this._touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy ); - return _pointerPositions[ pointer.pointerId ]; + const x = ( event.pageX + position.x ) / 2; + const y = ( event.pageY + position.y ) / 2; + this._panEnd.copy( this._getMouseOnScreen( x, y ) ); + break; - } + } - this.dispose = function () { +} - scope.domElement.removeEventListener( 'contextmenu', contextmenu ); +function onTouchEnd( event ) { - scope.domElement.removeEventListener( 'pointerdown', onPointerDown ); - scope.domElement.removeEventListener( 'pointercancel', onPointerCancel ); - scope.domElement.removeEventListener( 'wheel', onMouseWheel ); + switch ( this._pointers.length ) { - scope.domElement.removeEventListener( 'pointermove', onPointerMove ); - scope.domElement.removeEventListener( 'pointerup', onPointerUp ); + case 0: + this.state = _STATE.NONE; + break; - window.removeEventListener( 'keydown', keydown ); - window.removeEventListener( 'keyup', keyup ); + case 1: + this.state = _STATE.TOUCH_ROTATE; + this._moveCurr.copy( this._getMouseOnCircle( event.pageX, event.pageY ) ); + this._movePrev.copy( this._moveCurr ); + break; - }; + case 2: + this.state = _STATE.TOUCH_ZOOM_PAN; - this.domElement.addEventListener( 'contextmenu', contextmenu ); + for ( let i = 0; i < this._pointers.length; i ++ ) { - this.domElement.addEventListener( 'pointerdown', onPointerDown ); - this.domElement.addEventListener( 'pointercancel', onPointerCancel ); - this.domElement.addEventListener( 'wheel', onMouseWheel, { passive: false } ); + if ( this._pointers[ i ].pointerId !== event.pointerId ) { + const position = this._pointerPositions[ this._pointers[ i ].pointerId ]; + this._moveCurr.copy( this._getMouseOnCircle( position.x, position.y ) ); + this._movePrev.copy( this._moveCurr ); + break; - window.addEventListener( 'keydown', keydown ); - window.addEventListener( 'keyup', keyup ); + } - this.handleResize(); + } - // force an update at start - this.update(); + break; } + this.dispatchEvent( _endEvent ); + } export { TrackballControls }; diff --git a/examples/jsm/controls/TransformControls.js b/examples/jsm/controls/TransformControls.js index 4beff97a5e0eba..4370e062ce1190 100644 --- a/examples/jsm/controls/TransformControls.js +++ b/examples/jsm/controls/TransformControls.js @@ -1,6 +1,7 @@ import { BoxGeometry, BufferGeometry, + Controls, CylinderGeometry, DoubleSide, Euler, @@ -31,37 +32,70 @@ const _unit = { Z: new Vector3( 0, 0, 1 ) }; +/** + * Fires if any type of change (object or property change) is performed. Property changes + * are separate events you can add event listeners to. The event type is "propertyname-changed". + * + * @event TransformControls#change + * @type {Object} + */ const _changeEvent = { type: 'change' }; -const _mouseDownEvent = { type: 'mouseDown' }; -const _mouseUpEvent = { type: 'mouseUp', mode: null }; -const _objectChangeEvent = { type: 'objectChange' }; - -class TransformControls extends Object3D { - - constructor( camera, domElement ) { - - super(); - - if ( domElement === undefined ) { - - console.warn( 'THREE.TransformControls: The second parameter "domElement" is now mandatory.' ); - domElement = document; - - } - this.isTransformControls = true; - - this.visible = false; - this.domElement = domElement; - this.domElement.style.touchAction = 'none'; // disable touch scroll +/** + * Fires if a pointer (mouse/touch) becomes active. + * + * @event TransformControls#mouseDown + * @type {Object} + */ +const _mouseDownEvent = { type: 'mouseDown', mode: null }; + +/** + * Fires if a pointer (mouse/touch) is no longer active. + * + * @event TransformControls#mouseUp + * @type {Object} + */ +const _mouseUpEvent = { type: 'mouseUp', mode: null }; - const _gizmo = new TransformControlsGizmo(); - this._gizmo = _gizmo; - this.add( _gizmo ); +/** + * Fires if the controlled 3D object is changed. + * + * @event TransformControls#objectChange + * @type {Object} + */ +const _objectChangeEvent = { type: 'objectChange' }; - const _plane = new TransformControlsPlane(); - this._plane = _plane; - this.add( _plane ); +/** + * This class can be used to transform objects in 3D space by adapting a similar interaction model + * of DCC tools like Blender. Unlike other controls, it is not intended to transform the scene's camera. + * + * `TransformControls` expects that its attached 3D object is part of the scene graph. + * + * @augments Controls + * @three_import import { TransformControls } from 'three/addons/controls/TransformControls.js'; + */ +class TransformControls extends Controls { + + /** + * Constructs a new controls instance. + * + * @param {Camera} camera - The camera of the rendered scene. + * @param {?HTMLDOMElement} domElement - The HTML element used for event listeners. + */ + constructor( camera, domElement = null ) { + + super( undefined, domElement ); + + const root = new TransformControlsRoot( this ); + this._root = root; + + const gizmo = new TransformControlsGizmo(); + this._gizmo = gizmo; + root.add( gizmo ); + + const plane = new TransformControlsPlane(); + this._plane = plane; + root.add( plane ); const scope = this; @@ -83,8 +117,8 @@ class TransformControls extends Object3D { if ( propValue !== value ) { propValue = value; - _plane[ propName ] = value; - _gizmo[ propName ] = value; + plane[ propName ] = value; + gizmo[ propName ] = value; scope.dispatchEvent( { type: propName + '-changed', value: value } ); scope.dispatchEvent( _changeEvent ); @@ -96,8 +130,8 @@ class TransformControls extends Object3D { } ); scope[ propName ] = defaultValue; - _plane[ propName ] = defaultValue; - _gizmo[ propName ] = defaultValue; + plane[ propName ] = defaultValue; + gizmo[ propName ] = defaultValue; } @@ -105,21 +139,172 @@ class TransformControls extends Object3D { // Setting the defined property will automatically trigger change event // Defined properties are passed down to gizmo and plane + /** + * The camera of the rendered scene. + * + * @name TransformControls#camera + * @type {Camera} + */ defineProperty( 'camera', camera ); defineProperty( 'object', undefined ); defineProperty( 'enabled', true ); + + /** + * The current transformation axis. + * + * @name TransformControls#axis + * @type {string} + */ defineProperty( 'axis', null ); + + /** + * The current transformation axis. + * + * @name TransformControls#mode + * @type {('translate'|'rotate'|'scale')} + * @default 'translate' + */ defineProperty( 'mode', 'translate' ); + + /** + * By default, 3D objects are continuously translated. If you set this property to a numeric + * value (world units), you can define in which steps the 3D object should be translated. + * + * @name TransformControls#translationSnap + * @type {?number} + * @default null + */ defineProperty( 'translationSnap', null ); + + /** + * By default, 3D objects are continuously rotated. If you set this property to a numeric + * value (radians), you can define in which steps the 3D object should be rotated. + * + * @name TransformControls#rotationSnap + * @type {?number} + * @default null + */ defineProperty( 'rotationSnap', null ); + + /** + * By default, 3D objects are continuously scaled. If you set this property to a numeric + * value, you can define in which steps the 3D object should be scaled. + * + * @name TransformControls#scaleSnap + * @type {?number} + * @default null + */ defineProperty( 'scaleSnap', null ); + + /** + * Defines in which coordinate space transformations should be performed. + * + * @name TransformControls#space + * @type {('world'|'local')} + * @default 'world' + */ defineProperty( 'space', 'world' ); + + /** + * The size of the helper UI (axes/planes). + * + * @name TransformControls#size + * @type {number} + * @default 1 + */ defineProperty( 'size', 1 ); + + /** + * Whether dragging is currently performed or not. + * + * @name TransformControls#dragging + * @type {boolean} + * @readonly + * @default false + */ defineProperty( 'dragging', false ); + + /** + * Whether the x-axis helper should be visible or not. + * + * @name TransformControls#showX + * @type {boolean} + * @default true + */ defineProperty( 'showX', true ); + + /** + * Whether the y-axis helper should be visible or not. + * + * @name TransformControls#showY + * @type {boolean} + * @default true + */ defineProperty( 'showY', true ); + + /** + * Whether the z-axis helper should be visible or not. + * + * @name TransformControls#showZ + * @type {boolean} + * @default true + */ defineProperty( 'showZ', true ); + /** + * The minimum allowed X position during translation. + * + * @name TransformControls#minX + * @type {number} + * @default -Infinity + */ + defineProperty( 'minX', - Infinity ); + + /** + * The maximum allowed X position during translation. + * + * @name TransformControls#maxX + * @type {number} + * @default Infinity + */ + defineProperty( 'maxX', Infinity ); + + /** + * The minimum allowed y position during translation. + * + * @name TransformControls#minY + * @type {number} + * @default -Infinity + */ + defineProperty( 'minY', - Infinity ); + + /** + * The maximum allowed Y position during translation. + * + * @name TransformControls#maxY + * @type {number} + * @default Infinity + */ + defineProperty( 'maxY', Infinity ); + + /** + * The minimum allowed z position during translation. + * + * @name TransformControls#minZ + * @type {number} + * @default -Infinity + */ + defineProperty( 'minZ', - Infinity ); + + /** + * The maximum allowed Z position during translation. + * + * @name TransformControls#maxZ + * @type {number} + * @default Infinity + */ + defineProperty( 'maxZ', Infinity ); + // Reusable utility variables const worldPosition = new Vector3(); @@ -172,50 +357,46 @@ class TransformControls extends Object3D { this._onPointerMove = onPointerMove.bind( this ); this._onPointerUp = onPointerUp.bind( this ); - this.domElement.addEventListener( 'pointerdown', this._onPointerDown ); - this.domElement.addEventListener( 'pointermove', this._onPointerHover ); - this.domElement.addEventListener( 'pointerup', this._onPointerUp ); + if ( domElement !== null ) { - } - - // updateMatrixWorld updates key transformation variables - updateMatrixWorld() { - - if ( this.object !== undefined ) { - - this.object.updateMatrixWorld(); - - if ( this.object.parent === null ) { + this.connect( domElement ); - console.error( 'TransformControls: The attached 3D object must be a part of the scene graph.' ); - - } else { + } - this.object.parent.matrixWorld.decompose( this._parentPosition, this._parentQuaternion, this._parentScale ); + } - } + connect( element ) { - this.object.matrixWorld.decompose( this.worldPosition, this.worldQuaternion, this._worldScale ); + super.connect( element ); - this._parentQuaternionInv.copy( this._parentQuaternion ).invert(); - this._worldQuaternionInv.copy( this.worldQuaternion ).invert(); + this.domElement.addEventListener( 'pointerdown', this._onPointerDown ); + this.domElement.addEventListener( 'pointermove', this._onPointerHover ); + this.domElement.addEventListener( 'pointerup', this._onPointerUp ); - } + this.domElement.style.touchAction = 'none'; // disable touch scroll - this.camera.updateMatrixWorld(); - this.camera.matrixWorld.decompose( this.cameraPosition, this.cameraQuaternion, this._cameraScale ); + } - if ( this.camera.isOrthographicCamera ) { + disconnect() { - this.camera.getWorldDirection( this.eye ).negate(); + this.domElement.removeEventListener( 'pointerdown', this._onPointerDown ); + this.domElement.removeEventListener( 'pointermove', this._onPointerHover ); + this.domElement.removeEventListener( 'pointermove', this._onPointerMove ); + this.domElement.removeEventListener( 'pointerup', this._onPointerUp ); - } else { + this.domElement.style.touchAction = 'auto'; - this.eye.copy( this.cameraPosition ).sub( this.worldPosition ).normalize(); + } - } + /** + * Returns the visual representation of the controls. Add the helper to your scene to + * visually transform the attached 3D object. + * + * @return {TransformControlsRoot} The helper. + */ + getHelper() { - super.updateMatrixWorld( this ); + return this._root; } @@ -223,7 +404,7 @@ class TransformControls extends Object3D { if ( this.object === undefined || this.dragging === true ) return; - _raycaster.setFromCamera( pointer, this.camera ); + if ( pointer !== null ) _raycaster.setFromCamera( pointer, this.camera ); const intersect = intersectObjectWithRay( this._gizmo.picker[ this.mode ], _raycaster ); @@ -241,11 +422,11 @@ class TransformControls extends Object3D { pointerDown( pointer ) { - if ( this.object === undefined || this.dragging === true || pointer.button !== 0 ) return; + if ( this.object === undefined || this.dragging === true || ( pointer != null && pointer.button !== 0 ) ) return; if ( this.axis !== null ) { - _raycaster.setFromCamera( pointer, this.camera ); + if ( pointer !== null ) _raycaster.setFromCamera( pointer, this.camera ); const planeIntersect = intersectObjectWithRay( this._plane, _raycaster, true ); @@ -289,9 +470,9 @@ class TransformControls extends Object3D { } - if ( object === undefined || axis === null || this.dragging === false || pointer.button !== - 1 ) return; + if ( object === undefined || axis === null || this.dragging === false || ( pointer !== null && pointer.button !== - 1 ) ) return; - _raycaster.setFromCamera( pointer, this.camera ); + if ( pointer !== null ) _raycaster.setFromCamera( pointer, this.camera ); const planeIntersect = intersectObjectWithRay( this._plane, _raycaster, true ); @@ -393,6 +574,10 @@ class TransformControls extends Object3D { } + object.position.x = Math.max( this.minX, Math.min( this.maxX, object.position.x ) ); + object.position.y = Math.max( this.minY, Math.min( this.maxY, object.position.y ) ); + object.position.z = Math.max( this.minZ, Math.min( this.maxZ, object.position.z ) ); + } else if ( mode === 'scale' ) { if ( axis.search( 'XYZ' ) !== - 1 ) { @@ -465,17 +650,9 @@ class TransformControls extends Object3D { const ROTATION_SPEED = 20 / this.worldPosition.distanceTo( _tempVector.setFromMatrixPosition( this.camera.matrixWorld ) ); - if ( axis === 'E' ) { - - this.rotationAxis.copy( this.eye ); - this.rotationAngle = this.pointEnd.angleTo( this.pointStart ); - - this._startNorm.copy( this.pointStart ).normalize(); - this._endNorm.copy( this.pointEnd ).normalize(); - - this.rotationAngle *= ( this._endNorm.cross( this._startNorm ).dot( this.eye ) < 0 ? 1 : - 1 ); + let _inPlaneRotation = false; - } else if ( axis === 'XYZE' ) { + if ( axis === 'XYZE' ) { this.rotationAxis.copy( this._offset ).cross( this.eye ).normalize(); this.rotationAngle = this._offset.dot( _tempVector.copy( this.rotationAxis ).cross( this.eye ) ) * ROTATION_SPEED; @@ -492,7 +669,31 @@ class TransformControls extends Object3D { } - this.rotationAngle = this._offset.dot( _tempVector.cross( this.eye ).normalize() ) * ROTATION_SPEED; + _tempVector.cross( this.eye ); + + // When _tempVector is 0 after cross with this.eye the vectors are parallel and should use in-plane rotation logic. + if ( _tempVector.length() === 0 ) { + + _inPlaneRotation = true; + + } else { + + this.rotationAngle = this._offset.dot( _tempVector.normalize() ) * ROTATION_SPEED; + + } + + + } + + if ( axis === 'E' || _inPlaneRotation ) { + + this.rotationAxis.copy( this.eye ); + this.rotationAngle = this.pointEnd.angleTo( this.pointStart ); + + this._startNorm.copy( this.pointStart ).normalize(); + this._endNorm.copy( this.pointEnd ).normalize(); + + this.rotationAngle *= ( this._endNorm.cross( this._startNorm ).dot( this.eye ) < 0 ? 1 : - 1 ); } @@ -523,7 +724,7 @@ class TransformControls extends Object3D { pointerUp( pointer ) { - if ( pointer.button !== 0 ) return; + if ( pointer !== null && pointer.button !== 0 ) return; if ( this.dragging && ( this.axis !== null ) ) { @@ -539,41 +740,46 @@ class TransformControls extends Object3D { dispose() { - this.domElement.removeEventListener( 'pointerdown', this._onPointerDown ); - this.domElement.removeEventListener( 'pointermove', this._onPointerHover ); - this.domElement.removeEventListener( 'pointermove', this._onPointerMove ); - this.domElement.removeEventListener( 'pointerup', this._onPointerUp ); - - this.traverse( function ( child ) { + this.disconnect(); - if ( child.geometry ) child.geometry.dispose(); - if ( child.material ) child.material.dispose(); - - } ); + this._root.dispose(); } - // Set current object + /** + * Sets the 3D object that should be transformed and ensures the controls UI is visible. + * + * @param {Object3D} object - The 3D object that should be transformed. + * @return {TransformControls} A reference to this controls. + */ attach( object ) { this.object = object; - this.visible = true; + this._root.visible = true; return this; } - // Detach from object + /** + * Removes the current 3D object from the controls and makes the helper UI invisible. + * + * @return {TransformControls} A reference to this controls. + */ detach() { this.object = undefined; - this.visible = false; this.axis = null; + this._root.visible = false; + return this; } + /** + * Resets the object's position, rotation and scale to when the current transform began. + */ reset() { if ( ! this.enabled ) return; @@ -593,56 +799,129 @@ class TransformControls extends Object3D { } + /** + * Returns the raycaster that is used for user interaction. This object is shared between all + * instances of `TransformControls`. + * + * @returns {Raycaster} The internal raycaster. + */ getRaycaster() { return _raycaster; } - // TODO: deprecate - + /** + * Returns the transformation mode. + * + * @returns {'translate'|'rotate'|'scale'} The transformation mode. + */ getMode() { return this.mode; } + /** + * Sets the given transformation mode. + * + * @param {'translate'|'rotate'|'scale'} mode - The transformation mode to set. + */ setMode( mode ) { this.mode = mode; } + /** + * Sets the translation snap. + * + * @param {?number} translationSnap - The translation snap to set. + */ setTranslationSnap( translationSnap ) { this.translationSnap = translationSnap; } + /** + * Sets the rotation snap. + * + * @param {?number} rotationSnap - The rotation snap to set. + */ setRotationSnap( rotationSnap ) { this.rotationSnap = rotationSnap; } + /** + * Sets the scale snap. + * + * @param {?number} scaleSnap - The scale snap to set. + */ setScaleSnap( scaleSnap ) { this.scaleSnap = scaleSnap; } + /** + * Sets the size of the helper UI. + * + * @param {number} size - The size to set. + */ setSize( size ) { this.size = size; } + /** + * Sets the coordinate space in which transformations are applied. + * + * @param {'world'|'local'} space - The space to set. + */ setSpace( space ) { this.space = space; } + /** + * Sets the colors of the control's gizmo. + * + * @param {number|Color|string} xAxis - The x-axis color. + * @param {number|Color|string} yAxis - The y-axis color. + * @param {number|Color|string} zAxis - The z-axis color. + * @param {number|Color|string} active - The color for active elements. + */ + setColors( xAxis, yAxis, zAxis, active ) { + + const materialLib = this._gizmo.materialLib; + + materialLib.xAxis.color.set( xAxis ); + materialLib.yAxis.color.set( yAxis ); + materialLib.zAxis.color.set( zAxis ); + materialLib.active.color.set( active ); + materialLib.xAxisTransparent.color.set( xAxis ); + materialLib.yAxisTransparent.color.set( yAxis ); + materialLib.zAxisTransparent.color.set( zAxis ); + materialLib.activeTransparent.color.set( active ); + + // update color caches + + if ( materialLib.xAxis._color ) materialLib.xAxis._color.set( xAxis ); + if ( materialLib.yAxis._color ) materialLib.yAxis._color.set( yAxis ); + if ( materialLib.zAxis._color ) materialLib.zAxis._color.set( zAxis ); + if ( materialLib.active._color ) materialLib.active._color.set( active ); + if ( materialLib.xAxisTransparent._color ) materialLib.xAxisTransparent._color.set( xAxis ); + if ( materialLib.yAxisTransparent._color ) materialLib.yAxisTransparent._color.set( yAxis ); + if ( materialLib.zAxisTransparent._color ) materialLib.zAxisTransparent._color.set( zAxis ); + if ( materialLib.activeTransparent._color ) materialLib.activeTransparent._color.set( active ); + + } + } // mouse / touch event handlers @@ -762,6 +1041,75 @@ const _v1 = new Vector3(); const _v2 = new Vector3(); const _v3 = new Vector3(); +class TransformControlsRoot extends Object3D { + + constructor( controls ) { + + super(); + + this.isTransformControlsRoot = true; + + this.controls = controls; + this.visible = false; + + } + + // updateMatrixWorld updates key transformation variables + updateMatrixWorld( force ) { + + const controls = this.controls; + + if ( controls.object !== undefined ) { + + controls.object.updateMatrixWorld(); + + if ( controls.object.parent === null ) { + + console.error( 'TransformControls: The attached 3D object must be a part of the scene graph.' ); + + } else { + + controls.object.parent.matrixWorld.decompose( controls._parentPosition, controls._parentQuaternion, controls._parentScale ); + + } + + controls.object.matrixWorld.decompose( controls.worldPosition, controls.worldQuaternion, controls._worldScale ); + + controls._parentQuaternionInv.copy( controls._parentQuaternion ).invert(); + controls._worldQuaternionInv.copy( controls.worldQuaternion ).invert(); + + } + + controls.camera.updateMatrixWorld(); + controls.camera.matrixWorld.decompose( controls.cameraPosition, controls.cameraQuaternion, controls._cameraScale ); + + if ( controls.camera.isOrthographicCamera ) { + + controls.camera.getWorldDirection( controls.eye ).negate(); + + } else { + + controls.eye.copy( controls.cameraPosition ).sub( controls.worldPosition ).normalize(); + + } + + super.updateMatrixWorld( force ); + + } + + dispose() { + + this.traverse( function ( child ) { + + if ( child.geometry ) child.geometry.dispose(); + if ( child.material ) child.material.dispose(); + + } ); + + } + +} + class TransformControlsGizmo extends Object3D { constructor() { @@ -832,6 +1180,19 @@ class TransformControlsGizmo extends Object3D { const matGray = gizmoMaterial.clone(); matGray.color.setHex( 0x787878 ); + // materials in the below property are configurable via setColors() + + this.materialLib = { + xAxis: matRed, + yAxis: matGreen, + zAxis: matBlue, + active: matYellow, + xAxisTransparent: matRedTransparent, + yAxisTransparent: matGreenTransparent, + zAxisTransparent: matBlueTransparent, + activeTransparent: matYellowTransparent + }; + // reusable geometry const arrowGeometry = new CylinderGeometry( 0, 0.04, 0.1, 12 ); @@ -886,16 +1247,16 @@ class TransformControlsGizmo extends Object3D { [ new Mesh( lineGeometry2, matBlue ), null, [ Math.PI / 2, 0, 0 ]] ], XYZ: [ - [ new Mesh( new OctahedronGeometry( 0.1, 0 ), matWhiteTransparent.clone() ), [ 0, 0, 0 ]] + [ new Mesh( new OctahedronGeometry( 0.1, 0 ), matWhiteTransparent ), [ 0, 0, 0 ]] ], XY: [ - [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matBlueTransparent.clone() ), [ 0.15, 0.15, 0 ]] + [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matBlueTransparent ), [ 0.15, 0.15, 0 ]] ], YZ: [ - [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matRedTransparent.clone() ), [ 0, 0.15, 0.15 ], [ 0, Math.PI / 2, 0 ]] + [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matRedTransparent ), [ 0, 0.15, 0.15 ], [ 0, Math.PI / 2, 0 ]] ], XZ: [ - [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matGreenTransparent.clone() ), [ 0.15, 0, 0.15 ], [ - Math.PI / 2, 0, 0 ]] + [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matGreenTransparent ), [ 0.15, 0, 0.15 ], [ - Math.PI / 2, 0, 0 ]] ] }; @@ -937,13 +1298,13 @@ class TransformControlsGizmo extends Object3D { [ new Line( TranslateHelperGeometry(), matHelper ), null, null, null, 'helper' ] ], X: [ - [ new Line( lineGeometry, matHelper.clone() ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ] + [ new Line( lineGeometry, matHelper ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ] ], Y: [ - [ new Line( lineGeometry, matHelper.clone() ), [ 0, - 1e3, 0 ], [ 0, 0, Math.PI / 2 ], [ 1e6, 1, 1 ], 'helper' ] + [ new Line( lineGeometry, matHelper ), [ 0, - 1e3, 0 ], [ 0, 0, Math.PI / 2 ], [ 1e6, 1, 1 ], 'helper' ] ], Z: [ - [ new Line( lineGeometry, matHelper.clone() ), [ 0, 0, - 1e3 ], [ 0, - Math.PI / 2, 0 ], [ 1e6, 1, 1 ], 'helper' ] + [ new Line( lineGeometry, matHelper ), [ 0, 0, - 1e3 ], [ 0, - Math.PI / 2, 0 ], [ 1e6, 1, 1 ], 'helper' ] ] }; @@ -967,7 +1328,7 @@ class TransformControlsGizmo extends Object3D { const helperRotate = { AXIS: [ - [ new Line( lineGeometry, matHelper.clone() ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ] + [ new Line( lineGeometry, matHelper ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ] ] }; @@ -1015,7 +1376,7 @@ class TransformControlsGizmo extends Object3D { [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matGreenTransparent ), [ 0.15, 0, 0.15 ], [ - Math.PI / 2, 0, 0 ]] ], XYZ: [ - [ new Mesh( new BoxGeometry( 0.1, 0.1, 0.1 ), matWhiteTransparent.clone() ) ], + [ new Mesh( new BoxGeometry( 0.1, 0.1, 0.1 ), matWhiteTransparent ) ], ] }; @@ -1048,13 +1409,13 @@ class TransformControlsGizmo extends Object3D { const helperScale = { X: [ - [ new Line( lineGeometry, matHelper.clone() ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ] + [ new Line( lineGeometry, matHelper ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ] ], Y: [ - [ new Line( lineGeometry, matHelper.clone() ), [ 0, - 1e3, 0 ], [ 0, 0, Math.PI / 2 ], [ 1e6, 1, 1 ], 'helper' ] + [ new Line( lineGeometry, matHelper ), [ 0, - 1e3, 0 ], [ 0, 0, Math.PI / 2 ], [ 1e6, 1, 1 ], 'helper' ] ], Z: [ - [ new Line( lineGeometry, matHelper.clone() ), [ 0, 0, - 1e3 ], [ 0, - Math.PI / 2, 0 ], [ 1e6, 1, 1 ], 'helper' ] + [ new Line( lineGeometry, matHelper ), [ 0, 0, - 1e3 ], [ 0, - Math.PI / 2, 0 ], [ 1e6, 1, 1 ], 'helper' ] ] }; @@ -1435,7 +1796,7 @@ class TransformControlsGizmo extends Object3D { if ( handle.name === this.axis ) { - handle.material.color.setHex( 0xffff00 ); + handle.material.color.copy( this.materialLib.active.color ); handle.material.opacity = 1.0; } else if ( this.axis.split( '' ).some( function ( a ) { @@ -1444,7 +1805,7 @@ class TransformControlsGizmo extends Object3D { } ) ) { - handle.material.color.setHex( 0xffff00 ); + handle.material.color.copy( this.materialLib.active.color ); handle.material.opacity = 1.0; } diff --git a/examples/jsm/csm/CSM.js b/examples/jsm/csm/CSM.js index 72ea9831d7c979..0d3c4fafd6e133 100644 --- a/examples/jsm/csm/CSM.js +++ b/examples/jsm/csm/CSM.js @@ -11,7 +11,7 @@ import { CSMFrustum } from './CSMFrustum.js'; import { CSMShader } from './CSMShader.js'; const _cameraToLightMatrix = new Matrix4(); -const _lightSpaceFrustum = new CSMFrustum(); +const _lightSpaceFrustum = new CSMFrustum( { webGL: true } ); const _center = new Vector3(); const _bbox = new Box3(); const _uniformArray = []; @@ -20,38 +20,181 @@ const _lightOrientationMatrix = new Matrix4(); const _lightOrientationMatrixInverse = new Matrix4(); const _up = new Vector3( 0, 1, 0 ); +/** + * An implementation of Cascade Shadow Maps (CSM). + * + * This module can only be used with {@link WebGLRenderer}. When using {@link WebGPURenderer}, + * use {@link CSMShadowNode} instead. + * + * @three_import import { CSM } from 'three/addons/csm/CSM.js'; + */ export class CSM { + /** + * Constructs a new CSM instance. + * + * @param {CSM~Data} data - The CSM data. + */ constructor( data ) { + /** + * The scene's camera. + * + * @type {Camera} + */ this.camera = data.camera; + + /** + * The parent object, usually the scene. + * + * @type {Object3D} + */ this.parent = data.parent; + + /** + * The number of cascades. + * + * @type {number} + * @default 3 + */ this.cascades = data.cascades || 3; + + /** + * The maximum far value. + * + * @type {number} + * @default 100000 + */ this.maxFar = data.maxFar || 100000; + + /** + * The frustum split mode. + * + * @type {('practical'|'uniform'|'logarithmic'|'custom')} + * @default 'practical' + */ this.mode = data.mode || 'practical'; + + /** + * The shadow map size. + * + * @type {number} + * @default 2048 + */ this.shadowMapSize = data.shadowMapSize || 2048; + + /** + * The shadow bias. + * + * @type {number} + * @default 0.000001 + */ this.shadowBias = data.shadowBias || 0.000001; + + /** + * The light direction. + * + * @type {Vector3} + */ this.lightDirection = data.lightDirection || new Vector3( 1, - 1, 1 ).normalize(); - this.lightIntensity = data.lightIntensity || 1; + + /** + * The light intensity. + * + * @type {number} + * @default 3 + */ + this.lightIntensity = data.lightIntensity || 3; + + /** + * The light near value. + * + * @type {number} + * @default 1 + */ this.lightNear = data.lightNear || 1; + + /** + * The light far value. + * + * @type {number} + * @default 2000 + */ this.lightFar = data.lightFar || 2000; + + /** + * The light margin. + * + * @type {number} + * @default 200 + */ this.lightMargin = data.lightMargin || 200; + + /** + * Custom split callback when using `mode='custom'`. + * + * @type {Function} + */ this.customSplitsCallback = data.customSplitsCallback; + + /** + * Whether to fade between cascades or not. + * + * @type {boolean} + * @default false + */ this.fade = false; - this.mainFrustum = new CSMFrustum(); + + /** + * The main frustum. + * + * @type {CSMFrustum} + */ + this.mainFrustum = new CSMFrustum( { webGL: true } ); + + /** + * An array of frustums representing the cascades. + * + * @type {Array} + */ this.frustums = []; + + /** + * An array of numbers in the range `[0,1]` the defines how the + * mainCSM frustum should be split up. + * + * @type {Array} + */ this.breaks = []; + /** + * An array of directional lights which cast the shadows for + * the different cascades. There is one directional light for each + * cascade. + * + * @type {Array} + */ this.lights = []; + + /** + * A Map holding enhanced material shaders. + * + * @type {Map} + */ this.shaders = new Map(); - this.createLights(); + this._createLights(); this.updateFrustums(); - this.injectInclude(); + this._injectInclude(); } - createLights() { + /** + * Creates the directional lights of this CSM instance. + * + * @private + */ + _createLights() { for ( let i = 0; i < this.cascades; i ++ ) { @@ -72,7 +215,12 @@ export class CSM { } - initCascades() { + /** + * Inits the cascades according to the scene's camera and breaks configuration. + * + * @private + */ + _initCascades() { const camera = this.camera; camera.updateProjectionMatrix(); @@ -81,7 +229,12 @@ export class CSM { } - updateShadowBounds() { + /** + * Updates the shadow bounds of this CSM instance. + * + * @private + */ + _updateShadowBounds() { const frustums = this.frustums; for ( let i = 0; i < frustums.length; i ++ ) { @@ -130,7 +283,13 @@ export class CSM { } - getBreaks() { + /** + * Computes the breaks of this CSM instance based on the scene's camera, number of cascades + * and the selected split mode. + * + * @private + */ + _getBreaks() { const camera = this.camera; const far = Math.min( camera.far, this.maxFar ); @@ -197,6 +356,10 @@ export class CSM { } + /** + * Updates the CSM. This method must be called in your animation loop before + * calling `renderer.render()`. + */ update() { const camera = this.camera; @@ -243,13 +406,23 @@ export class CSM { } - injectInclude() { + /** + * Injects the CSM shader enhancements into the built-in materials. + * + * @private + */ + _injectInclude() { ShaderChunk.lights_fragment_begin = CSMShader.lights_fragment_begin; ShaderChunk.lights_pars_begin = CSMShader.lights_pars_begin; } + /** + * Applications must call this method for all materials that should be affected by CSM. + * + * @param {Material} material - The material to setup for CSM support. + */ setupMaterial( material ) { material.defines = material.defines || {}; @@ -269,7 +442,7 @@ export class CSM { material.onBeforeCompile = function ( shader ) { const far = Math.min( scope.camera.far, scope.maxFar ); - scope.getExtendedBreaks( breaksVec2 ); + scope._getExtendedBreaks( breaksVec2 ); shader.uniforms.CSM_cascades = { value: breaksVec2 }; shader.uniforms.cameraNear = { value: scope.camera.near }; @@ -283,7 +456,12 @@ export class CSM { } - updateUniforms() { + /** + * Updates the CSM uniforms. + * + * @private + */ + _updateUniforms() { const far = Math.min( this.camera.far, this.maxFar ); const shaders = this.shaders; @@ -293,7 +471,7 @@ export class CSM { if ( shader !== null ) { const uniforms = shader.uniforms; - this.getExtendedBreaks( uniforms.CSM_cascades.value ); + this._getExtendedBreaks( uniforms.CSM_cascades.value ); uniforms.cameraNear.value = this.camera.near; uniforms.shadowFar.value = far; @@ -315,7 +493,13 @@ export class CSM { } - getExtendedBreaks( target ) { + /** + * Computes the extended breaks for the CSM uniforms. + * + * @private + * @param {Array} target - The target array that holds the extended breaks. + */ + _getExtendedBreaks( target ) { while ( target.length < this.breaks.length ) { @@ -336,15 +520,21 @@ export class CSM { } + /** + * Applications must call this method every time they change camera or CSM settings. + */ updateFrustums() { - this.getBreaks(); - this.initCascades(); - this.updateShadowBounds(); - this.updateUniforms(); + this._getBreaks(); + this._initCascades(); + this._updateShadowBounds(); + this._updateUniforms(); } + /** + * Applications must call this method when they remove the CSM usage from their scene. + */ remove() { for ( let i = 0; i < this.lights.length; i ++ ) { @@ -356,6 +546,10 @@ export class CSM { } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ dispose() { const shaders = this.shaders; @@ -382,3 +576,22 @@ export class CSM { } } + +/** + * Constructor data of `CSM`. + * + * @typedef {Object} CSM~Data + * @property {Camera} camera - The scene's camera. + * @property {Object3D} parent - The parent object, usually the scene. + * @property {number} [cascades=3] - The number of cascades. + * @property {number} [maxFar=100000] - The maximum far value. + * @property {('practical'|'uniform'|'logarithmic'|'custom')} [mode='practical'] - The frustum split mode. + * @property {Function} [customSplitsCallback] - Custom split callback when using `mode='custom'`. + * @property {number} [shadowMapSize=2048] - The shadow map size. + * @property {number} [shadowBias=0.000001] - The shadow bias. + * @property {Vector3} [lightDirection] - The light direction. + * @property {number} [lightIntensity=3] - The light intensity. + * @property {number} [lightNear=1] - The light near value. + * @property {number} [lightNear=2000] - The light far value. + * @property {number} [lightMargin=200] - The light margin. + **/ diff --git a/examples/jsm/csm/CSMFrustum.js b/examples/jsm/csm/CSMFrustum.js index 2d968bebdb929f..8e8a97df6e1217 100644 --- a/examples/jsm/csm/CSMFrustum.js +++ b/examples/jsm/csm/CSMFrustum.js @@ -2,12 +2,37 @@ import { Vector3, Matrix4 } from 'three'; const inverseProjectionMatrix = new Matrix4(); +/** + * Represents the frustum of a CSM instance. + * + * @three_import import { CSMFrustum } from 'three/addons/csm/CSMFrustum.js'; + */ class CSMFrustum { + /** + * Constructs a new CSM frustum. + * + * @param {CSMFrustum~Data} [data] - The CSM data. + */ constructor( data ) { data = data || {}; + /** + * The zNear value. This value depends on whether the CSM + * is used with WebGL or WebGPU. Both API use different + * conventions for their projection matrices. + * + * @type {number} + */ + this.zNear = data.webGL === true ? - 1 : 0; + + /** + * An object representing the vertices of the near and + * far plane in view space. + * + * @type {Object} + */ this.vertices = { near: [ new Vector3(), @@ -31,8 +56,16 @@ class CSMFrustum { } + /** + * Setups this CSM frustum from the given projection matrix and max far value. + * + * @param {Matrix4} projectionMatrix - The projection matrix, usually of the scene's camera. + * @param {number} maxFar - The maximum far value. + * @returns {Object} An object representing the vertices of the near and far plane in view space. + */ setFromProjectionMatrix( projectionMatrix, maxFar ) { + const zNear = this.zNear; const isOrthographic = projectionMatrix.elements[ 2 * 4 + 3 ] === 0; inverseProjectionMatrix.copy( projectionMatrix ).invert(); @@ -42,10 +75,10 @@ class CSMFrustum { // 2 --- 1 // clip space spans from [-1, 1] - this.vertices.near[ 0 ].set( 1, 1, - 1 ); - this.vertices.near[ 1 ].set( 1, - 1, - 1 ); - this.vertices.near[ 2 ].set( - 1, - 1, - 1 ); - this.vertices.near[ 3 ].set( - 1, 1, - 1 ); + this.vertices.near[ 0 ].set( 1, 1, zNear ); + this.vertices.near[ 1 ].set( 1, - 1, zNear ); + this.vertices.near[ 2 ].set( - 1, - 1, zNear ); + this.vertices.near[ 3 ].set( - 1, 1, zNear ); this.vertices.near.forEach( function ( v ) { v.applyMatrix4( inverseProjectionMatrix ); @@ -77,6 +110,14 @@ class CSMFrustum { } + /** + * Splits the CSM frustum by the given array. The new CSM frustum are pushed into the given + * target array. + * + * @param {Array} breaks - An array of numbers in the range `[0,1]` the defines how the + * CSM frustum should be split up. + * @param {Array} target - The target array that holds the new CSM frustums. + */ split( breaks, target ) { while ( breaks.length > target.length ) { @@ -131,6 +172,13 @@ class CSMFrustum { } + /** + * Transforms the given target CSM frustum into the different coordinate system defined by the + * given camera matrix. + * + * @param {Matrix4} cameraMatrix - The matrix that defines the new coordinate system. + * @param {CSMFrustum} target - The CSM to convert. + */ toSpace( cameraMatrix, target ) { for ( let i = 0; i < 4; i ++ ) { @@ -149,4 +197,13 @@ class CSMFrustum { } +/** + * Constructor data of `CSMFrustum`. + * + * @typedef {Object} CSMFrustum~Data + * @property {boolean} [webGL] - Whether this CSM frustum is used with WebGL or WebGPU. + * @property {Matrix4} [projectionMatrix] - A projection matrix usually of the scene's camera. + * @property {number} [maxFar] - The maximum far value. + **/ + export { CSMFrustum }; diff --git a/examples/jsm/csm/CSMHelper.js b/examples/jsm/csm/CSMHelper.js index d1cfed874bb836..caac9a3c4929d9 100644 --- a/examples/jsm/csm/CSMHelper.js +++ b/examples/jsm/csm/CSMHelper.js @@ -12,14 +12,52 @@ import { DoubleSide } from 'three'; +/** + * A helper for visualizing the cascades of a CSM instance. + * + * @augments Group + * @three_import import { CSMHelper } from 'three/addons/csm/CSMHelper.js'; + */ class CSMHelper extends Group { + /** + * Constructs a new CSM helper. + * + * @param {CSM|CSMShadowNode} csm - The CSM instance to visualize. + */ constructor( csm ) { super(); + + /** + * The CSM instance to visualize. + * + * @type {CSM|CSMShadowNode} + */ this.csm = csm; + + /** + * Whether to display the CSM frustum or not. + * + * @type {boolean} + * @default true + */ this.displayFrustum = true; + + /** + * Whether to display the cascade planes or not. + * + * @type {boolean} + * @default true + */ this.displayPlanes = true; + + /** + * Whether to display the shadow bounds or not. + * + * @type {boolean} + * @default true + */ this.displayShadowBounds = true; const indices = new Uint16Array( [ 0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7 ] ); @@ -37,6 +75,9 @@ class CSMHelper extends Group { } + /** + * This method must be called if one of the `display*` properties is changed at runtime. + */ updateVisibility() { const displayFrustum = this.displayFrustum; @@ -63,6 +104,9 @@ class CSMHelper extends Group { } + /** + * Updates the helper. This method should be called in the app's animation loop. + */ update() { const csm = this.csm; @@ -78,6 +122,8 @@ class CSMHelper extends Group { const cascadePlanes = this.cascadePlanes; const shadowLines = this.shadowLines; + if ( camera === null ) return; + this.position.copy( camera.position ); this.quaternion.copy( camera.quaternion ); this.scale.copy( camera.scale ); @@ -158,6 +204,10 @@ class CSMHelper extends Group { } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ dispose() { const frustumLines = this.frustumLines; diff --git a/examples/jsm/csm/CSMShader.js b/examples/jsm/csm/CSMShader.js index 40fa71fdddbd2d..297b196ec581be 100644 --- a/examples/jsm/csm/CSMShader.js +++ b/examples/jsm/csm/CSMShader.js @@ -1,19 +1,44 @@ import { ShaderChunk } from 'three'; +/** + * @module CSMShader + * @three_import import { CSMShader } from 'three/addons/csm/CSMShader.js'; + */ + +/** + * The object that holds the GLSL enhancements to enable CSM. This + * code is injected into the built-in material shaders by {@link CSM}. + * + * @type {Object} + */ const CSMShader = { lights_fragment_begin: /* glsl */` -GeometricContext geometry; +vec3 geometryPosition = - vViewPosition; +vec3 geometryNormal = normal; +vec3 geometryViewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( vViewPosition ); -geometry.position = - vViewPosition; -geometry.normal = normal; -geometry.viewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( vViewPosition ); +vec3 geometryClearcoatNormal = vec3( 0.0 ); -#ifdef CLEARCOAT +#ifdef USE_CLEARCOAT - geometry.clearcoatNormal = clearcoatNormal; + geometryClearcoatNormal = clearcoatNormal; #endif +#ifdef USE_IRIDESCENCE + float dotNVi = saturate( dot( normal, geometryViewDir ) ); + if ( material.iridescenceThickness == 0.0 ) { + material.iridescence = 0.0; + } else { + material.iridescence = saturate( material.iridescence ); + } + if ( material.iridescence > 0.0 ) { + material.iridescenceFresnel = evalIridescence( 1.0, material.iridescenceIOR, dotNVi, material.iridescenceThickness, material.specularColor ); + // Iridescence F0 approximation + material.iridescenceF0 = Schlick_to_F0( material.iridescenceFresnel, 1.0, dotNVi ); + } +#endif + IncidentLight directLight; #if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct ) @@ -28,14 +53,15 @@ IncidentLight directLight; pointLight = pointLights[ i ]; - getPointLightInfo( pointLight, geometry, directLight ); + getPointLightInfo( pointLight, geometryPosition, directLight ); #if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_POINT_LIGHT_SHADOWS ) pointLightShadow = pointLightShadows[ i ]; - directLight.color *= ( directLight.visible && receiveShadow ) ? getPointShadow( pointShadowMap[ i ], pointLightShadow.shadowMapSize, pointLightShadow.shadowBias, pointLightShadow.shadowRadius, vPointShadowCoord[ i ], pointLightShadow.shadowCameraNear, pointLightShadow.shadowCameraFar ) : 1.0; + directLight.color *= ( directLight.visible && receiveShadow ) ? getPointShadow( pointShadowMap[ i ], pointLightShadow.shadowMapSize, pointLightShadow.shadowIntensity, pointLightShadow.shadowBias, pointLightShadow.shadowRadius, vPointShadowCoord[ i ], pointLightShadow.shadowCameraNear, pointLightShadow.shadowCameraFar ) : 1.0; + #endif - RE_Direct( directLight, geometry, material, reflectedLight ); + RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight ); } #pragma unroll_loop_end @@ -45,6 +71,10 @@ IncidentLight directLight; #if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct ) SpotLight spotLight; + vec4 spotColor; + vec3 spotLightCoord; + bool inSpotLightMap; + #if defined( USE_SHADOWMAP ) && NUM_SPOT_LIGHT_SHADOWS > 0 SpotLightShadow spotLightShadow; #endif @@ -54,15 +84,31 @@ IncidentLight directLight; spotLight = spotLights[ i ]; - getSpotLightInfo( spotLight, geometry, directLight ); + getSpotLightInfo( spotLight, geometryPosition, directLight ); + + // spot lights are ordered [shadows with maps, shadows without maps, maps without shadows, none] + #if ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS ) + #define SPOT_LIGHT_MAP_INDEX UNROLLED_LOOP_INDEX + #elif ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS ) + #define SPOT_LIGHT_MAP_INDEX NUM_SPOT_LIGHT_MAPS + #else + #define SPOT_LIGHT_MAP_INDEX ( UNROLLED_LOOP_INDEX - NUM_SPOT_LIGHT_SHADOWS + NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS ) + #endif + #if ( SPOT_LIGHT_MAP_INDEX < NUM_SPOT_LIGHT_MAPS ) + spotLightCoord = vSpotLightCoord[ i ].xyz / vSpotLightCoord[ i ].w; + inSpotLightMap = all( lessThan( abs( spotLightCoord * 2. - 1. ), vec3( 1.0 ) ) ); + spotColor = texture2D( spotLightMap[ SPOT_LIGHT_MAP_INDEX ], spotLightCoord.xy ); + directLight.color = inSpotLightMap ? directLight.color * spotColor.rgb : directLight.color; + #endif + #undef SPOT_LIGHT_MAP_INDEX #if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS ) spotLightShadow = spotLightShadows[ i ]; - directLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( spotShadowMap[ i ], spotLightShadow.shadowMapSize, spotLightShadow.shadowBias, spotLightShadow.shadowRadius, vSpotLightCoord[ i ] ) : 1.0; + directLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( spotShadowMap[ i ], spotLightShadow.shadowMapSize, spotLightShadow.shadowIntensity, spotLightShadow.shadowBias, spotLightShadow.shadowRadius, vSpotLightCoord[ i ] ) : 1.0; #endif - RE_Direct( directLight, geometry, material, reflectedLight ); + RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight ); } #pragma unroll_loop_end @@ -78,76 +124,81 @@ IncidentLight directLight; #endif #if defined( USE_SHADOWMAP ) && defined( CSM_FADE ) - vec2 cascade; - float cascadeCenter; - float closestEdge; - float margin; - float csmx; - float csmy; + vec2 cascade; + float cascadeCenter; + float closestEdge; + float margin; + float csmx; + float csmy; - #pragma unroll_loop_start - for ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) { + #pragma unroll_loop_start + for ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) { - directionalLight = directionalLights[ i ]; - getDirectionalLightInfo( directionalLight, geometry, directLight ); - - #if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS ) - // NOTE: Depth gets larger away from the camera. - // cascade.x is closer, cascade.y is further - cascade = CSM_cascades[ i ]; - cascadeCenter = ( cascade.x + cascade.y ) / 2.0; - closestEdge = linearDepth < cascadeCenter ? cascade.x : cascade.y; - margin = 0.25 * pow( closestEdge, 2.0 ); - csmx = cascade.x - margin / 2.0; - csmy = cascade.y + margin / 2.0; - if( linearDepth >= csmx && ( linearDepth < csmy || UNROLLED_LOOP_INDEX == CSM_CASCADES - 1 ) ) { - - float dist = min( linearDepth - csmx, csmy - linearDepth ); - float ratio = clamp( dist / margin, 0.0, 1.0 ); - - vec3 prevColor = directLight.color; - directionalLightShadow = directionalLightShadows[ i ]; - directLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0; + directionalLight = directionalLights[ i ]; + getDirectionalLightInfo( directionalLight, directLight ); - bool shouldFadeLastCascade = UNROLLED_LOOP_INDEX == CSM_CASCADES - 1 && linearDepth > cascadeCenter; - directLight.color = mix( prevColor, directLight.color, shouldFadeLastCascade ? ratio : 1.0 ); + #if ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS ) + // NOTE: Depth gets larger away from the camera. + // cascade.x is closer, cascade.y is further + cascade = CSM_cascades[ i ]; + cascadeCenter = ( cascade.x + cascade.y ) / 2.0; + closestEdge = linearDepth < cascadeCenter ? cascade.x : cascade.y; + margin = 0.25 * pow( closestEdge, 2.0 ); + csmx = cascade.x - margin / 2.0; + csmy = cascade.y + margin / 2.0; + if( linearDepth >= csmx && ( linearDepth < csmy || UNROLLED_LOOP_INDEX == CSM_CASCADES - 1 ) ) { - ReflectedLight prevLight = reflectedLight; - RE_Direct( directLight, geometry, material, reflectedLight ); + float dist = min( linearDepth - csmx, csmy - linearDepth ); + float ratio = clamp( dist / margin, 0.0, 1.0 ); - bool shouldBlend = UNROLLED_LOOP_INDEX != CSM_CASCADES - 1 || UNROLLED_LOOP_INDEX == CSM_CASCADES - 1 && linearDepth < cascadeCenter; - float blendRatio = shouldBlend ? ratio : 1.0; + vec3 prevColor = directLight.color; + directionalLightShadow = directionalLightShadows[ i ]; + directLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowIntensity, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0; - reflectedLight.directDiffuse = mix( prevLight.directDiffuse, reflectedLight.directDiffuse, blendRatio ); - reflectedLight.directSpecular = mix( prevLight.directSpecular, reflectedLight.directSpecular, blendRatio ); - reflectedLight.indirectDiffuse = mix( prevLight.indirectDiffuse, reflectedLight.indirectDiffuse, blendRatio ); - reflectedLight.indirectSpecular = mix( prevLight.indirectSpecular, reflectedLight.indirectSpecular, blendRatio ); + bool shouldFadeLastCascade = UNROLLED_LOOP_INDEX == CSM_CASCADES - 1 && linearDepth > cascadeCenter; + directLight.color = mix( prevColor, directLight.color, shouldFadeLastCascade ? ratio : 1.0 ); - } - #endif + ReflectedLight prevLight = reflectedLight; + RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight ); - } - #pragma unroll_loop_end - #else + bool shouldBlend = UNROLLED_LOOP_INDEX != CSM_CASCADES - 1 || UNROLLED_LOOP_INDEX == CSM_CASCADES - 1 && linearDepth < cascadeCenter; + float blendRatio = shouldBlend ? ratio : 1.0; + + reflectedLight.directDiffuse = mix( prevLight.directDiffuse, reflectedLight.directDiffuse, blendRatio ); + reflectedLight.directSpecular = mix( prevLight.directSpecular, reflectedLight.directSpecular, blendRatio ); + reflectedLight.indirectDiffuse = mix( prevLight.indirectDiffuse, reflectedLight.indirectDiffuse, blendRatio ); + reflectedLight.indirectSpecular = mix( prevLight.indirectSpecular, reflectedLight.indirectSpecular, blendRatio ); + + } + #endif + + } + #pragma unroll_loop_end + #elif defined (USE_SHADOWMAP) #pragma unroll_loop_start for ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) { directionalLight = directionalLights[ i ]; - getDirectionalLightInfo( directionalLight, geometry, directLight ); + getDirectionalLightInfo( directionalLight, directLight ); - #if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS ) + #if ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS ) - directionalLightShadow = directionalLightShadows[ i ]; - if(linearDepth >= CSM_cascades[UNROLLED_LOOP_INDEX].x && linearDepth < CSM_cascades[UNROLLED_LOOP_INDEX].y) directLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0; + directionalLightShadow = directionalLightShadows[ i ]; + if(linearDepth >= CSM_cascades[UNROLLED_LOOP_INDEX].x && linearDepth < CSM_cascades[UNROLLED_LOOP_INDEX].y) directLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowIntensity, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0; - if(linearDepth >= CSM_cascades[UNROLLED_LOOP_INDEX].x && (linearDepth < CSM_cascades[UNROLLED_LOOP_INDEX].y || UNROLLED_LOOP_INDEX == CSM_CASCADES - 1)) RE_Direct( directLight, geometry, material, reflectedLight ); + if(linearDepth >= CSM_cascades[UNROLLED_LOOP_INDEX].x && (linearDepth < CSM_cascades[UNROLLED_LOOP_INDEX].y || UNROLLED_LOOP_INDEX == CSM_CASCADES - 1)) RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight ); #endif } #pragma unroll_loop_end + #elif ( NUM_DIR_LIGHT_SHADOWS > 0 ) + // note: no loop here - all CSM lights are in fact one light only + getDirectionalLightInfo( directionalLights[0], directLight ); + RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight ); + #endif #if ( NUM_DIR_LIGHTS > NUM_DIR_LIGHT_SHADOWS) @@ -158,9 +209,9 @@ IncidentLight directLight; directionalLight = directionalLights[ i ]; - getDirectionalLightInfo( directionalLight, geometry, directLight ); + getDirectionalLightInfo( directionalLight, directLight ); - RE_Direct( directLight, geometry, material, reflectedLight ); + RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight ); } #pragma unroll_loop_end @@ -182,14 +233,14 @@ IncidentLight directLight; directionalLight = directionalLights[ i ]; - getDirectionalLightInfo( directionalLight, geometry, directLight ); + getDirectionalLightInfo( directionalLight, directLight ); #if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS ) directionalLightShadow = directionalLightShadows[ i ]; - directLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0; + directLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowIntensity, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0; #endif - RE_Direct( directLight, geometry, material, reflectedLight ); + RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight ); } #pragma unroll_loop_end @@ -204,7 +255,7 @@ IncidentLight directLight; for ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) { rectAreaLight = rectAreaLights[ i ]; - RE_Direct_RectArea( rectAreaLight, geometry, material, reflectedLight ); + RE_Direct_RectArea( rectAreaLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight ); } #pragma unroll_loop_end @@ -217,14 +268,18 @@ IncidentLight directLight; vec3 irradiance = getAmbientLightIrradiance( ambientLightColor ); - irradiance += getLightProbeIrradiance( lightProbe, geometry.normal ); + #if defined( USE_LIGHT_PROBES ) + + irradiance += getLightProbeIrradiance( lightProbe, geometryNormal ); + + #endif #if ( NUM_HEMI_LIGHTS > 0 ) #pragma unroll_loop_start for ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) { - irradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry.normal ); + irradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometryNormal ); } #pragma unroll_loop_end diff --git a/examples/jsm/csm/CSMShadowNode.js b/examples/jsm/csm/CSMShadowNode.js new file mode 100644 index 00000000000000..9eb2e0c6c9a9bc --- /dev/null +++ b/examples/jsm/csm/CSMShadowNode.js @@ -0,0 +1,599 @@ +import { + Vector2, + Vector3, + MathUtils, + Matrix4, + Box3, + Object3D, + WebGLCoordinateSystem, + ShadowBaseNode +} from 'three/webgpu'; + +import { CSMFrustum } from './CSMFrustum.js'; +import { viewZToOrthographicDepth, reference, uniform, float, vec4, vec2, If, Fn, min, renderGroup, positionView, shadow } from 'three/tsl'; + +const _cameraToLightMatrix = new Matrix4(); +const _lightSpaceFrustum = new CSMFrustum(); +const _center = new Vector3(); +const _bbox = new Box3(); +const _uniformArray = []; +const _logArray = []; +const _lightDirection = new Vector3(); +const _lightOrientationMatrix = new Matrix4(); +const _lightOrientationMatrixInverse = new Matrix4(); +const _up = new Vector3( 0, 1, 0 ); + +class LwLight extends Object3D { + + constructor() { + + super(); + + this.target = new Object3D(); + + } + +} + +/** + * An implementation of Cascade Shadow Maps (CSM). + * + * This module can only be used with {@link WebGPURenderer}. When using {@link WebGLRenderer}, + * use {@link CSM} instead. + * + * @augments ShadowBaseNode + * @three_import import { CSMShadowNode } from 'three/addons/csm/CSMShadowNode.js'; + */ +class CSMShadowNode extends ShadowBaseNode { + + /** + * Constructs a new CSM shadow node. + * + * @param {DirectionalLight} light - The CSM light. + * @param {CSMShadowNode~Data} [data={}] - The CSM data. + */ + constructor( light, data = {} ) { + + super( light ); + + /** + * The scene's camera. + * + * @type {?Camera} + * @default null + */ + this.camera = null; + + /** + * The number of cascades. + * + * @type {number} + * @default 3 + */ + this.cascades = data.cascades || 3; + + /** + * The maximum far value. + * + * @type {number} + * @default 100000 + */ + this.maxFar = data.maxFar || 100000; + + /** + * The frustum split mode. + * + * @type {('practical'|'uniform'|'logarithmic'|'custom')} + * @default 'practical' + */ + this.mode = data.mode || 'practical'; + + /** + * The light margin. + * + * @type {number} + * @default 200 + */ + this.lightMargin = data.lightMargin || 200; + + /** + * Custom split callback when using `mode='custom'`. + * + * @type {Function} + */ + this.customSplitsCallback = data.customSplitsCallback; + + /** + * Whether to fade between cascades or not. + * + * @type {boolean} + * @default false + */ + this.fade = false; + + /** + * An array of numbers in the range `[0,1]` the defines how the + * mainCSM frustum should be split up. + * + * @type {Array} + */ + this.breaks = []; + + this._cascades = []; + + /** + * The main frustum. + * + * @type {?CSMFrustum} + * @default null + */ + this.mainFrustum = null; + + /** + * An array of frustums representing the cascades. + * + * @type {Array} + */ + this.frustums = []; + + /** + * An array of directional lights which cast the shadows for + * the different cascades. There is one directional light for each + * cascade. + * + * @type {Array} + */ + this.lights = []; + + this._shadowNodes = []; + + } + + /** + * Inits the CSM shadow node. + * + * @private + * @param {NodeBuilder} builder - The node builder. + */ + _init( { camera, renderer } ) { + + this.camera = camera; + + const data = { webGL: renderer.coordinateSystem === WebGLCoordinateSystem }; + this.mainFrustum = new CSMFrustum( data ); + + const light = this.light; + + for ( let i = 0; i < this.cascades; i ++ ) { + + const lwLight = new LwLight(); + lwLight.castShadow = true; + + const lShadow = light.shadow.clone(); + lShadow.bias = lShadow.bias * ( i + 1 ); + + this.lights.push( lwLight ); + + lwLight.shadow = lShadow; + + this._shadowNodes.push( shadow( lwLight, lShadow ) ); + + this._cascades.push( new Vector2() ); + + } + + this.updateFrustums(); + + } + + /** + * Inits the cascades according to the scene's camera and breaks configuration. + * + * @private + */ + _initCascades() { + + const camera = this.camera; + camera.updateProjectionMatrix(); + + this.mainFrustum.setFromProjectionMatrix( camera.projectionMatrix, this.maxFar ); + this.mainFrustum.split( this.breaks, this.frustums ); + + } + + /** + * Computes the breaks of this CSM instance based on the scene's camera, number of cascades + * and the selected split mode. + * + * @private + */ + _getBreaks() { + + const camera = this.camera; + const far = Math.min( camera.far, this.maxFar ); + + this.breaks.length = 0; + + switch ( this.mode ) { + + case 'uniform': + uniformSplit( this.cascades, camera.near, far, this.breaks ); + break; + + case 'logarithmic': + logarithmicSplit( this.cascades, camera.near, far, this.breaks ); + break; + + case 'practical': + practicalSplit( this.cascades, camera.near, far, 0.5, this.breaks ); + break; + + case 'custom': + if ( this.customSplitsCallback === undefined ) console.error( 'CSM: Custom split scheme callback not defined.' ); + this.customSplitsCallback( this.cascades, camera.near, far, this.breaks ); + break; + + } + + function uniformSplit( amount, near, far, target ) { + + for ( let i = 1; i < amount; i ++ ) { + + target.push( ( near + ( far - near ) * i / amount ) / far ); + + } + + target.push( 1 ); + + } + + function logarithmicSplit( amount, near, far, target ) { + + for ( let i = 1; i < amount; i ++ ) { + + target.push( ( near * ( far / near ) ** ( i / amount ) ) / far ); + + } + + target.push( 1 ); + + } + + function practicalSplit( amount, near, far, lambda, target ) { + + _uniformArray.length = 0; + _logArray.length = 0; + logarithmicSplit( amount, near, far, _logArray ); + uniformSplit( amount, near, far, _uniformArray ); + + for ( let i = 1; i < amount; i ++ ) { + + target.push( MathUtils.lerp( _uniformArray[ i - 1 ], _logArray[ i - 1 ], lambda ) ); + + } + + target.push( 1 ); + + } + + } + + /** + * Sets the light breaks. + * + * @private + */ + _setLightBreaks() { + + for ( let i = 0, l = this.cascades; i < l; i ++ ) { + + const amount = this.breaks[ i ]; + const prev = this.breaks[ i - 1 ] || 0; + + this._cascades[ i ].set( prev, amount ); + + } + + } + + /** + * Updates the shadow bounds of this CSM instance. + * + * @private + */ + _updateShadowBounds() { + + const frustums = this.frustums; + + for ( let i = 0; i < frustums.length; i ++ ) { + + const shadowCam = this.lights[ i ].shadow.camera; + const frustum = this.frustums[ i ]; + + // Get the two points that represent that furthest points on the frustum assuming + // that's either the diagonal across the far plane or the diagonal across the whole + // frustum itself. + const nearVerts = frustum.vertices.near; + const farVerts = frustum.vertices.far; + const point1 = farVerts[ 0 ]; + + let point2; + + if ( point1.distanceTo( farVerts[ 2 ] ) > point1.distanceTo( nearVerts[ 2 ] ) ) { + + point2 = farVerts[ 2 ]; + + } else { + + point2 = nearVerts[ 2 ]; + + } + + let squaredBBWidth = point1.distanceTo( point2 ); + + if ( this.fade ) { + + // expand the shadow extents by the fade margin if fade is enabled. + const camera = this.camera; + const far = Math.max( camera.far, this.maxFar ); + const linearDepth = frustum.vertices.far[ 0 ].z / ( far - camera.near ); + const margin = 0.25 * Math.pow( linearDepth, 2.0 ) * ( far - camera.near ); + + squaredBBWidth += margin; + + } + + shadowCam.left = - squaredBBWidth / 2; + shadowCam.right = squaredBBWidth / 2; + shadowCam.top = squaredBBWidth / 2; + shadowCam.bottom = - squaredBBWidth / 2; + shadowCam.updateProjectionMatrix(); + + } + + } + + /** + * Applications must call this method every time they change camera or CSM settings. + */ + updateFrustums() { + + this._getBreaks(); + this._initCascades(); + this._updateShadowBounds(); + this._setLightBreaks(); + + } + + /** + * Setups the TSL when using fading. + * + * @private + * @return {ShaderCallNodeInternal} + */ + _setupFade() { + + const cameraNear = reference( 'camera.near', 'float', this ).setGroup( renderGroup ); + const cascades = reference( '_cascades', 'vec2', this ).setGroup( renderGroup ).label( 'cascades' ); + + const shadowFar = uniform( 'float' ).setGroup( renderGroup ).label( 'shadowFar' ) + .onRenderUpdate( () => Math.min( this.maxFar, this.camera.far ) ); + + const linearDepth = viewZToOrthographicDepth( positionView.z, cameraNear, shadowFar ).toVar( 'linearDepth' ); + const lastCascade = this.cascades - 1; + + return Fn( ( builder ) => { + + this.setupShadowPosition( builder ); + + const ret = vec4( 1, 1, 1, 1 ).toVar( 'shadowValue' ); + + const cascade = vec2().toVar( 'cascade' ); + const cascadeCenter = float().toVar( 'cascadeCenter' ); + + const margin = float().toVar( 'margin' ); + + const csmX = float().toVar( 'csmX' ); + const csmY = float().toVar( 'csmY' ); + + for ( let i = 0; i < this.cascades; i ++ ) { + + const isLastCascade = i === lastCascade; + + cascade.assign( cascades.element( i ) ); + + cascadeCenter.assign( cascade.x.add( cascade.y ).div( 2.0 ) ); + + const closestEdge = linearDepth.lessThan( cascadeCenter ).select( cascade.x, cascade.y ); + + margin.assign( float( 0.25 ).mul( closestEdge.pow( 2.0 ) ) ); + + csmX.assign( cascade.x.sub( margin.div( 2.0 ) ) ); + + if ( isLastCascade ) { + + csmY.assign( cascade.y ); + + } else { + + csmY.assign( cascade.y.add( margin.div( 2.0 ) ) ); + + } + + const inRange = linearDepth.greaterThanEqual( csmX ).and( linearDepth.lessThanEqual( csmY ) ); + + If( inRange, () => { + + const dist = min( linearDepth.sub( csmX ), csmY.sub( linearDepth ) ).toVar(); + + let ratio = dist.div( margin ).clamp( 0.0, 1.0 ); + + if ( i === 0 ) { + + // don't fade at nearest edge + ratio = linearDepth.greaterThan( cascadeCenter ).select( ratio, 1 ); + + } + + ret.subAssign( this._shadowNodes[ i ].oneMinus().mul( ratio ) ); + + } ); + + } + + return ret; + + } )(); + + } + + /** + * Setups the TSL when no fading (default). + * + * @private + * @return {ShaderCallNodeInternal} + */ + _setupStandard() { + + const cameraNear = reference( 'camera.near', 'float', this ).setGroup( renderGroup ); + const cascades = reference( '_cascades', 'vec2', this ).setGroup( renderGroup ).label( 'cascades' ); + + const shadowFar = uniform( 'float' ).setGroup( renderGroup ).label( 'shadowFar' ) + .onRenderUpdate( () => Math.min( this.maxFar, this.camera.far ) ); + + const linearDepth = viewZToOrthographicDepth( positionView.z, cameraNear, shadowFar ).toVar( 'linearDepth' ); + + return Fn( ( builder ) => { + + this.setupShadowPosition( builder ); + + const ret = vec4( 1, 1, 1, 1 ).toVar( 'shadowValue' ); + const cascade = vec2().toVar( 'cascade' ); + + for ( let i = 0; i < this.cascades; i ++ ) { + + cascade.assign( cascades.element( i ) ); + + If( linearDepth.greaterThanEqual( cascade.x ).and( linearDepth.lessThanEqual( cascade.y ) ), () => { + + ret.assign( this._shadowNodes[ i ] ); + + } ); + + } + + return ret; + + } )(); + + } + + setup( builder ) { + + if ( this.camera === null ) this._init( builder ); + + return this.fade === true ? this._setupFade() : this._setupStandard(); + + } + + updateBefore( /*builder*/ ) { + + const light = this.light; + const parent = light.parent; + const camera = this.camera; + const frustums = this.frustums; + + // make sure the placeholder light objects which represent the + // multiple cascade shadow casters are part of the scene graph + + for ( let i = 0; i < this.lights.length; i ++ ) { + + const lwLight = this.lights[ i ]; + + if ( lwLight.parent === null ) { + + parent.add( lwLight.target ); + parent.add( lwLight ); + + } + + } + + _lightDirection.subVectors( light.target.position, light.position ).normalize(); + + // for each frustum we need to find its min-max box aligned with the light orientation + // the position in _lightOrientationMatrix does not matter, as we transform there and back + _lightOrientationMatrix.lookAt( light.position, light.target.position, _up ); + _lightOrientationMatrixInverse.copy( _lightOrientationMatrix ).invert(); + + for ( let i = 0; i < frustums.length; i ++ ) { + + const lwLight = this.lights[ i ]; + const shadow = lwLight.shadow; + const shadowCam = shadow.camera; + const texelWidth = ( shadowCam.right - shadowCam.left ) / shadow.mapSize.width; + const texelHeight = ( shadowCam.top - shadowCam.bottom ) / shadow.mapSize.height; + + _cameraToLightMatrix.multiplyMatrices( _lightOrientationMatrixInverse, camera.matrixWorld ); + frustums[ i ].toSpace( _cameraToLightMatrix, _lightSpaceFrustum ); + + const nearVerts = _lightSpaceFrustum.vertices.near; + const farVerts = _lightSpaceFrustum.vertices.far; + + _bbox.makeEmpty(); + + for ( let j = 0; j < 4; j ++ ) { + + _bbox.expandByPoint( nearVerts[ j ] ); + _bbox.expandByPoint( farVerts[ j ] ); + + } + + _bbox.getCenter( _center ); + _center.z = _bbox.max.z + this.lightMargin; + _center.x = Math.floor( _center.x / texelWidth ) * texelWidth; + _center.y = Math.floor( _center.y / texelHeight ) * texelHeight; + _center.applyMatrix4( _lightOrientationMatrix ); + + lwLight.position.copy( _center ); + lwLight.target.position.copy( _center ); + lwLight.target.position.add( _lightDirection ); + + } + + } + + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose() { + + for ( let i = 0; i < this.lights.length; i ++ ) { + + const light = this.lights[ i ]; + const parent = light.parent; + + parent.remove( light.target ); + parent.remove( light ); + + } + + super.dispose(); + + } + +} + +/** + * Constructor data of `CSMShadowNode`. + * + * @typedef {Object} CSMShadowNode~Data + * @property {number} [cascades=3] - The number of cascades. + * @property {number} [maxFar=100000] - The maximum far value. + * @property {('practical'|'uniform'|'logarithmic'|'custom')} [mode='practical'] - The frustum split mode. + * @property {Function} [customSplitsCallback] - Custom split callback when using `mode='custom'`. + * @property {number} [lightMargin=200] - The light margin. + **/ + +export { CSMShadowNode }; diff --git a/examples/jsm/curves/CurveExtras.js b/examples/jsm/curves/CurveExtras.js index 51efb84591f0f2..7bcd3f76a479cc 100644 --- a/examples/jsm/curves/CurveExtras.js +++ b/examples/jsm/curves/CurveExtras.js @@ -13,10 +13,21 @@ import { * https://prideout.net/blog/old/blog/index.html@p=44.html */ -// GrannyKnot - +/** + * A Granny Knot curve. + * + * @augments Curve + * @three_import import { GrannyKnot } from 'three/addons/curves/CurveExtras.js'; + */ class GrannyKnot extends Curve { + /** + * This method returns a vector in 3D space for the given interpolation factor. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector3} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector3} The position on the curve. + */ getPoint( t, optionalTarget = new Vector3() ) { const point = optionalTarget; @@ -33,18 +44,40 @@ class GrannyKnot extends Curve { } -// HeartCurve - +/** + * A heart curve. + * + * @augments Curve + * @three_import import { HeartCurve } from 'three/addons/curves/CurveExtras.js'; + */ class HeartCurve extends Curve { + /** + * Constructs a new heart curve. + * + * @param {number} [scale=5] - The curve's scale. + */ constructor( scale = 5 ) { super(); + /** + * The curve's scale. + * + * @type {number} + * @default 5 + */ this.scale = scale; } + /** + * This method returns a vector in 3D space for the given interpolation factor. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector3} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector3} The position on the curve. + */ getPoint( t, optionalTarget = new Vector3() ) { const point = optionalTarget; @@ -61,18 +94,40 @@ class HeartCurve extends Curve { } -// Viviani's Curve - +/** + * A Viviani curve. + * + * @augments Curve + * @three_import import { VivianiCurve } from 'three/addons/curves/CurveExtras.js'; + */ class VivianiCurve extends Curve { + /** + * Constructs a new Viviani curve. + * + * @param {number} [scale=70] - The curve's scale. + */ constructor( scale = 70 ) { super(); + /** + * The curve's scale. + * + * @type {number} + * @default 70 + */ this.scale = scale; } + /** + * This method returns a vector in 3D space for the given interpolation factor. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector3} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector3} The position on the curve. + */ getPoint( t, optionalTarget = new Vector3() ) { const point = optionalTarget; @@ -90,10 +145,21 @@ class VivianiCurve extends Curve { } -// KnotCurve - +/** + * A knot curve. + * + * @augments Curve + * @three_import import { KnotCurve } from 'three/addons/curves/CurveExtras.js'; + */ class KnotCurve extends Curve { + /** + * This method returns a vector in 3D space for the given interpolation factor. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector3} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector3} The position on the curve. + */ getPoint( t, optionalTarget = new Vector3() ) { const point = optionalTarget; @@ -113,11 +179,21 @@ class KnotCurve extends Curve { } - -// HelixCurve - +/** + * A helix curve. + * + * @augments Curve + * @three_import import { HelixCurve } from 'three/addons/curves/CurveExtras.js'; + */ class HelixCurve extends Curve { + /** + * This method returns a vector in 3D space for the given interpolation factor. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector3} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector3} The position on the curve. + */ getPoint( t, optionalTarget = new Vector3() ) { const point = optionalTarget; @@ -137,18 +213,40 @@ class HelixCurve extends Curve { } -// TrefoilKnot - +/** + * A Trefoil Knot. + * + * @augments Curve + * @three_import import { TrefoilKnot } from 'three/addons/curves/CurveExtras.js'; + */ class TrefoilKnot extends Curve { + /** + * Constructs a new Trefoil Knot. + * + * @param {number} [scale=10] - The curve's scale. + */ constructor( scale = 10 ) { super(); + /** + * The curve's scale. + * + * @type {number} + * @default 10 + */ this.scale = scale; } + /** + * This method returns a vector in 3D space for the given interpolation factor. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector3} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector3} The position on the curve. + */ getPoint( t, optionalTarget = new Vector3() ) { const point = optionalTarget; @@ -165,18 +263,40 @@ class TrefoilKnot extends Curve { } -// TorusKnot - +/** + * A torus knot. + * + * @augments Curve + * @three_import import { TorusKnot } from 'three/addons/curves/CurveExtras.js'; + */ class TorusKnot extends Curve { + /** + * Constructs a new torus knot. + * + * @param {number} [scale=10] - The curve's scale. + */ constructor( scale = 10 ) { super(); + /** + * The curve's scale. + * + * @type {number} + * @default 10 + */ this.scale = scale; } + /** + * This method returns a vector in 3D space for the given interpolation factor. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector3} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector3} The position on the curve. + */ getPoint( t, optionalTarget = new Vector3() ) { const point = optionalTarget; @@ -196,18 +316,40 @@ class TorusKnot extends Curve { } -// CinquefoilKnot - +/** + * A Cinquefoil Knot. + * + * @augments Curve + * @three_import import { CinquefoilKnot } from 'three/addons/curves/CurveExtras.js'; + */ class CinquefoilKnot extends Curve { + /** + * Constructs a new Cinquefoil Knot. + * + * @param {number} [scale=10] - The curve's scale. + */ constructor( scale = 10 ) { super(); + /** + * The curve's scale. + * + * @type {number} + * @default 10 + */ this.scale = scale; } + /** + * This method returns a vector in 3D space for the given interpolation factor. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector3} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector3} The position on the curve. + */ getPoint( t, optionalTarget = new Vector3() ) { const point = optionalTarget; @@ -227,19 +369,40 @@ class CinquefoilKnot extends Curve { } - -// TrefoilPolynomialKnot - +/** + * A Trefoil Polynomial Knot. + * + * @augments Curve + * @three_import import { TrefoilPolynomialKnot } from 'three/addons/curves/CurveExtras.js'; + */ class TrefoilPolynomialKnot extends Curve { + /** + * Constructs a new Trefoil Polynomial Knot. + * + * @param {number} [scale=10] - The curve's scale. + */ constructor( scale = 10 ) { super(); + /** + * The curve's scale. + * + * @type {number} + * @default 10 + */ this.scale = scale; } + /** + * This method returns a vector in 3D space for the given interpolation factor. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector3} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector3} The position on the curve. + */ getPoint( t, optionalTarget = new Vector3() ) { const point = optionalTarget; @@ -263,18 +426,40 @@ function scaleTo( x, y, t ) { } -// FigureEightPolynomialKnot - +/** + * A Figure Eight Polynomial Knot. + * + * @augments Curve + * @three_import import { FigureEightPolynomialKnot } from 'three/addons/curves/CurveExtras.js'; + */ class FigureEightPolynomialKnot extends Curve { + /** + * Constructs a new Figure Eight Polynomial Knot. + * + * @param {number} [scale=1] - The curve's scale. + */ constructor( scale = 1 ) { super(); + /** + * The curve's scale. + * + * @type {number} + * @default 1 + */ this.scale = scale; } + /** + * This method returns a vector in 3D space for the given interpolation factor. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector3} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector3} The position on the curve. + */ getPoint( t, optionalTarget = new Vector3() ) { const point = optionalTarget; @@ -291,18 +476,40 @@ class FigureEightPolynomialKnot extends Curve { } -// DecoratedTorusKnot4a - +/** + * A Decorated Torus Knot 4a. + * + * @augments Curve + * @three_import import { DecoratedTorusKnot4a } from 'three/addons/curves/CurveExtras.js'; + */ class DecoratedTorusKnot4a extends Curve { + /** + * Constructs a new Decorated Torus Knot 4a. + * + * @param {number} [scale=1] - The curve's scale. + */ constructor( scale = 40 ) { super(); + /** + * The curve's scale. + * + * @type {number} + * @default 40 + */ this.scale = scale; } + /** + * This method returns a vector in 3D space for the given interpolation factor. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector3} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector3} The position on the curve. + */ getPoint( t, optionalTarget = new Vector3() ) { const point = optionalTarget; @@ -319,18 +526,40 @@ class DecoratedTorusKnot4a extends Curve { } -// DecoratedTorusKnot4b - +/** + * A Decorated Torus Knot 4b. + * + * @augments Curve + * @three_import import { DecoratedTorusKnot4b } from 'three/addons/curves/CurveExtras.js'; + */ class DecoratedTorusKnot4b extends Curve { + /** + * Constructs a new Decorated Torus Knot 4b. + * + * @param {number} [scale=1] - The curve's scale. + */ constructor( scale = 40 ) { super(); + /** + * The curve's scale. + * + * @type {number} + * @default 40 + */ this.scale = scale; } + /** + * This method returns a vector in 3D space for the given interpolation factor. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector3} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector3} The position on the curve. + */ getPoint( t, optionalTarget = new Vector3() ) { const point = optionalTarget; @@ -347,19 +576,40 @@ class DecoratedTorusKnot4b extends Curve { } - -// DecoratedTorusKnot5a - +/** + * A Decorated Torus Knot 5a. + * + * @augments Curve + * @three_import import { DecoratedTorusKnot5a } from 'three/addons/curves/CurveExtras.js'; + */ class DecoratedTorusKnot5a extends Curve { + /** + * Constructs a new Decorated Torus Knot 5a. + * + * @param {number} [scale=1] - The curve's scale. + */ constructor( scale = 40 ) { super(); + /** + * The curve's scale. + * + * @type {number} + * @default 40 + */ this.scale = scale; } + /** + * This method returns a vector in 3D space for the given interpolation factor. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector3} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector3} The position on the curve. + */ getPoint( t, optionalTarget = new Vector3() ) { const point = optionalTarget; @@ -376,18 +626,40 @@ class DecoratedTorusKnot5a extends Curve { } -// DecoratedTorusKnot5c - +/** + * A Decorated Torus Knot 5c. + * + * @augments Curve + * @three_import import { DecoratedTorusKnot5c } from 'three/addons/curves/CurveExtras.js'; + */ class DecoratedTorusKnot5c extends Curve { + /** + * Constructs a new Decorated Torus Knot 5c. + * + * @param {number} [scale=1] - The curve's scale. + */ constructor( scale = 40 ) { super(); + /** + * The curve's scale. + * + * @type {number} + * @default 40 + */ this.scale = scale; } + /** + * This method returns a vector in 3D space for the given interpolation factor. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector3} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector3} The position on the curve. + */ getPoint( t, optionalTarget = new Vector3() ) { const point = optionalTarget; diff --git a/examples/jsm/curves/NURBSCurve.js b/examples/jsm/curves/NURBSCurve.js index 8be8dde2d61b4b..0969f096aaddf2 100644 --- a/examples/jsm/curves/NURBSCurve.js +++ b/examples/jsm/curves/NURBSCurve.js @@ -6,34 +6,67 @@ import { import * as NURBSUtils from '../curves/NURBSUtils.js'; /** - * NURBS curve object + * This class represents a NURBS curve. * - * Derives from Curve, overriding getPoint and getTangent. + * Implementation is based on `(x, y [, z=0 [, w=1]])` control points with `w=weight`. * - * Implementation is based on (x, y [, z=0 [, w=1]]) control points with w=weight. - * - **/ - + * @augments Curve + * @three_import import { NURBSCurve } from 'three/addons/curves/NURBSCurve.js'; + */ class NURBSCurve extends Curve { - constructor( - degree, - knots /* array of reals */, - controlPoints /* array of Vector(2|3|4) */, - startKnot /* index in knots */, - endKnot /* index in knots */ - ) { + /** + * Constructs a new NURBS curve. + * + * @param {number} degree - The NURBS degree. + * @param {Array} knots - The knots as a flat array of numbers. + * @param {Array} controlPoints - An array holding control points. + * @param {number} [startKnot] - Index of the start knot into the `knots` array. + * @param {number} [endKnot] - Index of the end knot into the `knots` array. + */ + constructor( degree, knots, controlPoints, startKnot, endKnot ) { super(); + const knotsLength = knots ? knots.length - 1 : 0; + const pointsLength = controlPoints ? controlPoints.length : 0; + + /** + * The NURBS degree. + * + * @type {number} + */ this.degree = degree; + + /** + * The knots as a flat array of numbers. + * + * @type {Array} + */ this.knots = knots; + + /** + * An array of control points. + * + * @type {Array} + */ this.controlPoints = []; - // Used by periodic NURBS to remove hidden spans + + /** + * Index of the start knot into the `knots` array. + * + * @type {number} + */ this.startKnot = startKnot || 0; - this.endKnot = endKnot || ( this.knots.length - 1 ); - for ( let i = 0; i < controlPoints.length; ++ i ) { + /** + * Index of the end knot into the `knots` array. + * + * @type {number} + */ + this.endKnot = endKnot || knotsLength; + + for ( let i = 0; i < pointsLength; ++ i ) { // ensure Vector4 for control points const point = controlPoints[ i ]; @@ -43,6 +76,13 @@ class NURBSCurve extends Curve { } + /** + * This method returns a vector in 3D space for the given interpolation factor. + * + * @param {number} t - A interpolation factor representing a position on the curve. Must be in the range `[0,1]`. + * @param {Vector3} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector3} The position on the curve. + */ getPoint( t, optionalTarget = new Vector3() ) { const point = optionalTarget; @@ -63,6 +103,13 @@ class NURBSCurve extends Curve { } + /** + * Returns a unit vector tangent for the given interpolation factor. + * + * @param {number} t - The interpolation factor. + * @param {Vector3} [optionalTarget] - The optional target vector the result is written to. + * @return {Vector3} The tangent vector. + */ getTangent( t, optionalTarget = new Vector3() ) { const tangent = optionalTarget; @@ -75,6 +122,34 @@ class NURBSCurve extends Curve { } + toJSON() { + + const data = super.toJSON(); + + data.degree = this.degree; + data.knots = [ ...this.knots ]; + data.controlPoints = this.controlPoints.map( p => p.toArray() ); + data.startKnot = this.startKnot; + data.endKnot = this.endKnot; + + return data; + + } + + fromJSON( json ) { + + super.fromJSON( json ); + + this.degree = json.degree; + this.knots = [ ...json.knots ]; + this.controlPoints = json.controlPoints.map( p => new Vector4( p[ 0 ], p[ 1 ], p[ 2 ], p[ 3 ] ) ); + this.startKnot = json.startKnot; + this.endKnot = json.endKnot; + + return this; + + } + } export { NURBSCurve }; diff --git a/examples/jsm/curves/NURBSSurface.js b/examples/jsm/curves/NURBSSurface.js index 577ecfceecb895..67da9f2bff7717 100644 --- a/examples/jsm/curves/NURBSSurface.js +++ b/examples/jsm/curves/NURBSSurface.js @@ -4,19 +4,58 @@ import { import * as NURBSUtils from '../curves/NURBSUtils.js'; /** - * NURBS surface object + * This class represents a NURBS surface. * - * Implementation is based on (x, y [, z=0 [, w=1]]) control points with w=weight. - **/ - + * Implementation is based on `(x, y [, z=0 [, w=1]])` control points with `w=weight`. + * + * @three_import import { NURBSSurface } from 'three/addons/curves/NURBSSurface.js'; + */ class NURBSSurface { - constructor( degree1, degree2, knots1, knots2 /* arrays of reals */, controlPoints /* array^2 of Vector(2|3|4) */ ) { - + /** + * Constructs a new NURBS surface. + * + * @param {number} degree1 - The first NURBS degree. + * @param {number} degree2 - The second NURBS degree. + * @param {Array} knots1 - The first knots as a flat array of numbers. + * @param {Array} knots2 - The second knots as a flat array of numbers. + * @param {Array>} controlPoints - An array^2 holding control points. + */ + constructor( degree1, degree2, knots1, knots2, controlPoints ) { + + /** + * The first NURBS degree. + * + * @type {number} + */ this.degree1 = degree1; + + /** + * The second NURBS degree. + * + * @type {number} + */ this.degree2 = degree2; + + /** + * The first knots as a flat array of numbers. + * + * @type {Array} + */ this.knots1 = knots1; + + /** + * The second knots as a flat array of numbers. + * + * @type {Array} + */ this.knots2 = knots2; + + /** + * An array holding arrays of control points. + * + * @type {Array>} + */ this.controlPoints = []; const len1 = knots1.length - degree1 - 1; @@ -38,6 +77,13 @@ class NURBSSurface { } + /** + * This method returns a vector in 3D space for the given interpolation factor. This vector lies on the NURBS surface. + * + * @param {number} t1 - The first interpolation factor representing the `u` position on the surface. Must be in the range `[0,1]`. + * @param {number} t2 - The second interpolation factor representing the `v` position on the surface. Must be in the range `[0,1]`. + * @param {Vector3} target - The target vector the result is written to. + */ getPoint( t1, t2, target ) { const u = this.knots1[ 0 ] + t1 * ( this.knots1[ this.knots1.length - 1 ] - this.knots1[ 0 ] ); // linear mapping t1->u diff --git a/examples/jsm/curves/NURBSUtils.js b/examples/jsm/curves/NURBSUtils.js index fc77fdb58f06b1..3c1649937b4a3f 100644 --- a/examples/jsm/curves/NURBSUtils.js +++ b/examples/jsm/curves/NURBSUtils.js @@ -4,25 +4,18 @@ import { } from 'three'; /** - * NURBS utils - * - * See NURBSCurve and NURBSSurface. - **/ - - -/************************************************************** - * NURBS Utils - **************************************************************/ - -/* -Finds knot vector span. - -p : degree -u : parametric value -U : knot vector + * @module NURBSUtils + * @three_import import * as NURBSUtils from 'three/addons/curves/NURBSUtils.js'; + */ -returns the span -*/ +/** + * Finds knot vector span. + * + * @param {number} p - The degree. + * @param {number} u - The parametric value. + * @param {Array} U - The knot vector. + * @return {number} The span. + */ function findSpan( p, u, U ) { const n = U.length - p - 1; @@ -63,17 +56,15 @@ function findSpan( p, u, U ) { } - -/* -Calculate basis functions. See The NURBS Book, page 70, algorithm A2.2 - -span : span in which u lies -u : parametric point -p : degree -U : knot vector - -returns array[p+1] with basis functions values. -*/ +/** + * Calculates basis functions. See The NURBS Book, page 70, algorithm A2.2. + * + * @param {number} span - The span in which `u` lies. + * @param {number} u - The parametric value. + * @param {number} p - The degree. + * @param {Array} U - The knot vector. + * @return {Array} Array[p+1] with basis functions values. + */ function calcBasisFunctions( span, u, p, U ) { const N = []; @@ -106,17 +97,15 @@ function calcBasisFunctions( span, u, p, U ) { } - -/* -Calculate B-Spline curve points. See The NURBS Book, page 82, algorithm A3.1. - -p : degree of B-Spline -U : knot vector -P : control points (x, y, z, w) -u : parametric point - -returns point for given u -*/ +/** + * Calculates B-Spline curve points. See The NURBS Book, page 82, algorithm A3.1. + * + * @param {number} p - The degree of the B-Spline. + * @param {Array} U - The knot vector. + * @param {Array} P - The control points + * @param {number} u - The parametric point. + * @return {Vector4} The point for given `u`. + */ function calcBSplinePoint( p, U, P, u ) { const span = findSpan( p, u, U ); @@ -139,18 +128,16 @@ function calcBSplinePoint( p, U, P, u ) { } - -/* -Calculate basis functions derivatives. See The NURBS Book, page 72, algorithm A2.3. - -span : span in which u lies -u : parametric point -p : degree -n : number of derivatives to calculate -U : knot vector - -returns array[n+1][p+1] with basis functions derivatives -*/ +/** + * Calculates basis functions derivatives. See The NURBS Book, page 72, algorithm A2.3. + * + * @param {number} span - The span in which `u` lies. + * @param {number} u - The parametric point. + * @param {number} p - The degree. + * @param {number} n - number of derivatives to calculate + * @param {Array} U - The knot vector. + * @return {Array>} An array[n+1][p+1] with basis functions derivatives. + */ function calcBasisFunctionDerivatives( span, u, p, n, U ) { const zeroArr = []; @@ -273,18 +260,16 @@ function calcBasisFunctionDerivatives( span, u, p, n, U ) { } - -/* - Calculate derivatives of a B-Spline. See The NURBS Book, page 93, algorithm A3.2. - - p : degree - U : knot vector - P : control points - u : Parametric points - nd : number of derivatives - - returns array[d+1] with derivatives - */ +/** + * Calculates derivatives of a B-Spline. See The NURBS Book, page 93, algorithm A3.2. + * + * @param {number} p - The degree. + * @param {Array} U - The knot vector. + * @param {Array} P - The control points + * @param {number} u - The parametric point. + * @param {number} nd - The number of derivatives. + * @return {Array} An array[d+1] with derivatives. + */ function calcBSplineDerivatives( p, U, P, u, nd ) { const du = nd < p ? nd : p; @@ -330,12 +315,13 @@ function calcBSplineDerivatives( p, U, P, u, nd ) { } - -/* -Calculate "K over I" - -returns k!/(i!(k-i)!) -*/ +/** + * Calculates "K over I". + * + * @param {number} k - The K value. + * @param {number} i - The I value. + * @return {number} k!/(i!(k-i)!) + */ function calcKoverI( k, i ) { let nom = 1; @@ -364,14 +350,12 @@ function calcKoverI( k, i ) { } - -/* -Calculate derivatives (0-nd) of rational curve. See The NURBS Book, page 127, algorithm A4.2. - -Pders : result of function calcBSplineDerivatives - -returns array with derivatives for rational curve. -*/ +/** + * Calculates derivatives (0-nd) of rational curve. See The NURBS Book, page 127, algorithm A4.2. + * + * @param {Array} Pders - Array with derivatives. + * @return {Array} An array with derivatives for rational curve. + */ function calcRationalCurveDerivatives( Pders ) { const nd = Pders.length; @@ -406,18 +390,16 @@ function calcRationalCurveDerivatives( Pders ) { } - -/* -Calculate NURBS curve derivatives. See The NURBS Book, page 127, algorithm A4.2. - -p : degree -U : knot vector -P : control points in homogeneous space -u : parametric points -nd : number of derivatives - -returns array with derivatives. -*/ +/** + * Calculates NURBS curve derivatives. See The NURBS Book, page 127, algorithm A4.2. + * + * @param {number} p - The degree. + * @param {Array} U - The knot vector. + * @param {Array} P - The control points in homogeneous space. + * @param {number} u - The parametric point. + * @param {number} nd - The number of derivatives. + * @return {Array} array with derivatives for rational curve. + */ function calcNURBSDerivatives( p, U, P, u, nd ) { const Pders = calcBSplineDerivatives( p, U, P, u, nd ); @@ -425,17 +407,18 @@ function calcNURBSDerivatives( p, U, P, u, nd ) { } - -/* -Calculate rational B-Spline surface point. See The NURBS Book, page 134, algorithm A4.3. - -p1, p2 : degrees of B-Spline surface -U1, U2 : knot vectors -P : control points (x, y, z, w) -u, v : parametric values - -returns point for given (u, v) -*/ +/** + * Calculates a rational B-Spline surface point. See The NURBS Book, page 134, algorithm A4.3. + * + * @param {number} p - The first degree of B-Spline surface. + * @param {number} q - The second degree of B-Spline surface. + * @param {Array} U - The first knot vector. + * @param {Array} V - The second knot vector. + * @param {Array>} P - The control points in homogeneous space. + * @param {number} u - The first parametric point. + * @param {number} v - The second parametric point. + * @param {Vector3} target - The target vector. + */ function calcSurfacePoint( p, q, U, V, P, u, v, target ) { const uspan = findSpan( p, u, U ); @@ -472,7 +455,68 @@ function calcSurfacePoint( p, q, U, V, P, u, v, target ) { } +/** + * Calculates a rational B-Spline volume point. See The NURBS Book, page 134, algorithm A4.3. + * + * @param {number} p - The first degree of B-Spline surface. + * @param {number} q - The second degree of B-Spline surface. + * @param {number} r - The third degree of B-Spline surface. + * @param {Array} U - The first knot vector. + * @param {Array} V - The second knot vector. + * @param {Array} W - The third knot vector. + * @param {Array>>} P - The control points in homogeneous space. + * @param {number} u - The first parametric point. + * @param {number} v - The second parametric point. + * @param {number} w - The third parametric point. + * @param {Vector3} target - The target vector. + */ +function calcVolumePoint( p, q, r, U, V, W, P, u, v, w, target ) { + + const uspan = findSpan( p, u, U ); + const vspan = findSpan( q, v, V ); + const wspan = findSpan( r, w, W ); + const Nu = calcBasisFunctions( uspan, u, p, U ); + const Nv = calcBasisFunctions( vspan, v, q, V ); + const Nw = calcBasisFunctions( wspan, w, r, W ); + const temp = []; + + for ( let m = 0; m <= r; ++ m ) { + + temp[ m ] = []; + + for ( let l = 0; l <= q; ++ l ) { + + temp[ m ][ l ] = new Vector4( 0, 0, 0, 0 ); + for ( let k = 0; k <= p; ++ k ) { + const point = P[ uspan - p + k ][ vspan - q + l ][ wspan - r + m ].clone(); + const w = point.w; + point.x *= w; + point.y *= w; + point.z *= w; + temp[ m ][ l ].add( point.multiplyScalar( Nu[ k ] ) ); + + } + + } + + } + + const Sw = new Vector4( 0, 0, 0, 0 ); + for ( let m = 0; m <= r; ++ m ) { + + for ( let l = 0; l <= q; ++ l ) { + + Sw.add( temp[ m ][ l ].multiplyScalar( Nw[ m ] ).multiplyScalar( Nv[ l ] ) ); + + } + + } + + Sw.divideScalar( Sw.w ); + target.set( Sw.x, Sw.y, Sw.z ); + +} export { findSpan, @@ -484,4 +528,5 @@ export { calcRationalCurveDerivatives, calcNURBSDerivatives, calcSurfacePoint, + calcVolumePoint, }; diff --git a/examples/jsm/curves/NURBSVolume.js b/examples/jsm/curves/NURBSVolume.js new file mode 100644 index 00000000000000..70ed12ba61fc1a --- /dev/null +++ b/examples/jsm/curves/NURBSVolume.js @@ -0,0 +1,82 @@ +import { + Vector4 +} from 'three'; +import * as NURBSUtils from '../curves/NURBSUtils.js'; + +/** + * This class represents a NURBS volume. + * + * Implementation is based on `(x, y [, z=0 [, w=1]])` control points with `w=weight`. + * + * @three_import import { NURBSVolume } from 'three/addons/curves/NURBSVolume.js'; + */ +class NURBSVolume { + + /** + * Constructs a new NURBS surface. + * + * @param {number} degree1 - The first NURBS degree. + * @param {number} degree2 - The second NURBS degree. + * @param {number} degree3 - The third NURBS degree. + * @param {Array} knots1 - The first knots as a flat array of numbers. + * @param {Array} knots2 - The second knots as a flat array of numbers. + * @param {Array} knots3 - The third knots as a flat array of numbers. + * @param {Array>>} controlPoints - An array^3 holding control points. + */ + constructor( degree1, degree2, degree3, knots1, knots2, knots3 /* arrays of reals */, controlPoints /* array^3 of Vector(2|3|4) */ ) { + + this.degree1 = degree1; + this.degree2 = degree2; + this.degree3 = degree3; + this.knots1 = knots1; + this.knots2 = knots2; + this.knots3 = knots3; + this.controlPoints = []; + + const len1 = knots1.length - degree1 - 1; + const len2 = knots2.length - degree2 - 1; + const len3 = knots3.length - degree3 - 1; + + // ensure Vector4 for control points + for ( let i = 0; i < len1; ++ i ) { + + this.controlPoints[ i ] = []; + + for ( let j = 0; j < len2; ++ j ) { + + this.controlPoints[ i ][ j ] = []; + + for ( let k = 0; k < len3; ++ k ) { + + const point = controlPoints[ i ][ j ][ k ]; + this.controlPoints[ i ][ j ][ k ] = new Vector4( point.x, point.y, point.z, point.w ); + + } + + } + + } + + } + + /** + * This method returns a vector in 3D space for the given interpolation factor. This vector lies within the NURBS volume. + * + * @param {number} t1 - The first interpolation factor representing the `u` position within the volume. Must be in the range `[0,1]`. + * @param {number} t2 - The second interpolation factor representing the `v` position within the volume. Must be in the range `[0,1]`. + * @param {number} t3 - The third interpolation factor representing the `w` position within the volume. Must be in the range `[0,1]`. + * @param {Vector3} target - The target vector the result is written to. + */ + getPoint( t1, t2, t3, target ) { + + const u = this.knots1[ 0 ] + t1 * ( this.knots1[ this.knots1.length - 1 ] - this.knots1[ 0 ] ); // linear mapping t1->u + const v = this.knots2[ 0 ] + t2 * ( this.knots2[ this.knots2.length - 1 ] - this.knots2[ 0 ] ); // linear mapping t2->v + const w = this.knots3[ 0 ] + t3 * ( this.knots3[ this.knots3.length - 1 ] - this.knots3[ 0 ] ); // linear mapping t3->w + + NURBSUtils.calcVolumePoint( this.degree1, this.degree2, this.degree3, this.knots1, this.knots2, this.knots3, this.controlPoints, u, v, w, target ); + + } + +} + +export { NURBSVolume }; diff --git a/examples/jsm/effects/AnaglyphEffect.js b/examples/jsm/effects/AnaglyphEffect.js index a3118cbe0c65db..a33bf044fb37b3 100644 --- a/examples/jsm/effects/AnaglyphEffect.js +++ b/examples/jsm/effects/AnaglyphEffect.js @@ -1,19 +1,31 @@ import { LinearFilter, Matrix3, - Mesh, NearestFilter, - OrthographicCamera, - PlaneGeometry, RGBAFormat, - Scene, ShaderMaterial, StereoCamera, WebGLRenderTarget } from 'three'; - +import { FullScreenQuad } from '../postprocessing/Pass.js'; + +/** + * A class that creates an anaglyph effect. + * + * Note that this class can only be used with {@link WebGLRenderer}. + * When using {@link WebGPURenderer}, use {@link AnaglyphPassNode}. + * + * @three_import import { AnaglyphEffect } from 'three/addons/effects/AnaglyphEffect.js'; + */ class AnaglyphEffect { + /** + * Constructs a new anaglyph effect. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {number} width - The width of the effect in physical pixels. + * @param {number} height - The height of the effect in physical pixels. + */ constructor( renderer, width = 512, height = 512 ) { // Dubois matrices from https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.7.6968&rep=rep1&type=pdf#page=4 @@ -30,10 +42,6 @@ class AnaglyphEffect { - 0.00155529, - 0.0184503, 1.2264 ] ); - const _camera = new OrthographicCamera( - 1, 1, 1, - 1, 0, 1 ); - - const _scene = new Scene(); - const _stereo = new StereoCamera(); const _params = { minFilter: LinearFilter, magFilter: NearestFilter, format: RGBAFormat }; @@ -75,47 +83,38 @@ class AnaglyphEffect { 'uniform mat3 colorMatrixLeft;', 'uniform mat3 colorMatrixRight;', - // These functions implement sRGB linearization and gamma correction - - 'float lin( float c ) {', - ' return c <= 0.04045 ? c * 0.0773993808 :', - ' pow( c * 0.9478672986 + 0.0521327014, 2.4 );', - '}', - - 'vec4 lin( vec4 c ) {', - ' return vec4( lin( c.r ), lin( c.g ), lin( c.b ), c.a );', - '}', - - 'float dev( float c ) {', - ' return c <= 0.0031308 ? c * 12.92', - ' : pow( c, 0.41666 ) * 1.055 - 0.055;', - '}', - - 'void main() {', ' vec2 uv = vUv;', - ' vec4 colorL = lin( texture2D( mapLeft, uv ) );', - ' vec4 colorR = lin( texture2D( mapRight, uv ) );', + ' vec4 colorL = texture2D( mapLeft, uv );', + ' vec4 colorR = texture2D( mapRight, uv );', ' vec3 color = clamp(', ' colorMatrixLeft * colorL.rgb +', ' colorMatrixRight * colorR.rgb, 0., 1. );', ' gl_FragColor = vec4(', - ' dev( color.r ), dev( color.g ), dev( color.b ),', + ' color.r, color.g, color.b,', ' max( colorL.a, colorR.a ) );', + ' #include ', + ' #include ', + '}' ].join( '\n' ) } ); - const _mesh = new Mesh( new PlaneGeometry( 2, 2 ), _material ); - _scene.add( _mesh ); + const _quad = new FullScreenQuad( _material ); + /** + * Resizes the effect. + * + * @param {number} width - The width of the effect in logical pixels. + * @param {number} height - The height of the effect in logical pixels. + */ this.setSize = function ( width, height ) { renderer.setSize( width, height ); @@ -127,6 +126,13 @@ class AnaglyphEffect { }; + /** + * When using this effect, this method should be called instead of the + * default {@link WebGLRenderer#render}. + * + * @param {Object3D} scene - The scene to render. + * @param {Camera} camera - The camera. + */ this.render = function ( scene, camera ) { const currentRenderTarget = renderer.getRenderTarget(); @@ -146,18 +152,23 @@ class AnaglyphEffect { renderer.render( scene, _stereo.cameraR ); renderer.setRenderTarget( null ); - renderer.render( _scene, _camera ); + _quad.render( renderer ); renderer.setRenderTarget( currentRenderTarget ); }; + /** + * Frees internal resources. This method should be called + * when the effect is no longer required. + */ this.dispose = function () { _renderTargetL.dispose(); _renderTargetR.dispose(); - _mesh.geometry.dispose(); - _mesh.material.dispose(); + + _material.dispose(); + _quad.dispose(); }; diff --git a/examples/jsm/effects/AsciiEffect.js b/examples/jsm/effects/AsciiEffect.js index af5eb7f02ef43a..641606a080a6a3 100644 --- a/examples/jsm/effects/AsciiEffect.js +++ b/examples/jsm/effects/AsciiEffect.js @@ -1,11 +1,19 @@ /** - * Ascii generation is based on https://github.com/hassadee/jsascii/blob/master/jsascii.js + * A class that creates an ASCII effect. * - * 16 April 2012 - @blurspline + * The ASCII generation is based on [jsascii]{@link https://github.com/hassadee/jsascii/blob/master/jsascii.js}. + * + * @three_import import { AsciiEffect } from 'three/addons/effects/AsciiEffect.js'; */ - class AsciiEffect { + /** + * Constructs a new ASCII effect. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {string} [charSet=' .:-=+*#%@'] - The char set. + * @param {AsciiEffect~Options} [options] - The configuration parameter. + */ constructor( renderer, charSet = ' .:-=+*#%@', options = {} ) { // ' .,:;=|iI+hHOE#`$'; @@ -14,12 +22,12 @@ class AsciiEffect { // Some ASCII settings - const fResolution = options[ 'resolution' ] || 0.15; // Higher for more details + const fResolution = options[ 'resolution' ] || 0.15; const iScale = options[ 'scale' ] || 1; - const bColor = options[ 'color' ] || false; // nice but slows down rendering! - const bAlpha = options[ 'alpha' ] || false; // Transparency - const bBlock = options[ 'block' ] || false; // blocked characters. like good O dos - const bInvert = options[ 'invert' ] || false; // black is white, white is black + const bColor = options[ 'color' ] || false; + const bAlpha = options[ 'alpha' ] || false; + const bBlock = options[ 'block' ] || false; + const bInvert = options[ 'invert' ] || false; const strResolution = options[ 'strResolution' ] || 'low'; let width, height; @@ -33,6 +41,12 @@ class AsciiEffect { let iWidth, iHeight; let oImg; + /** + * Resizes the effect. + * + * @param {number} w - The width of the effect in logical pixels. + * @param {number} h - The height of the effect in logical pixels. + */ this.setSize = function ( w, h ) { width = w; @@ -44,7 +58,13 @@ class AsciiEffect { }; - + /** + * When using this effect, this method should be called instead of the + * default {@link WebGLRenderer#render}. + * + * @param {Object3D} scene - The scene to render. + * @param {Camera} camera - The camera. + */ this.render = function ( scene, camera ) { renderer.render( scene, camera ); @@ -52,6 +72,12 @@ class AsciiEffect { }; + /** + * The DOM element of the effect. This element must be used instead of the + * default {@link WebGLRenderer#domElement}. + * + * @type {HTMLDivElement} + */ this.domElement = domElement; @@ -77,8 +103,8 @@ class AsciiEffect { } - oAscii.cellSpacing = 0; - oAscii.cellPadding = 0; + oAscii.cellSpacing = '0'; + oAscii.cellPadding = '0'; const oStyle = oAscii.style; oStyle.whiteSpace = 'pre'; @@ -94,8 +120,6 @@ class AsciiEffect { } - const aDefaultCharList = ( ' .,:;i1tfLCG08@' ).split( '' ); - const aDefaultColorCharList = ( ' CGO08@' ).split( '' ); const strFont = 'courier new, monospace'; const oCanvasImg = renderer.domElement; @@ -114,9 +138,19 @@ class AsciiEffect { } - let aCharList = ( bColor ? aDefaultColorCharList : aDefaultCharList ); + let aCharList; + if ( charSet ) { + + aCharList = ( charSet ).split( '' ); + + } else { + + const aDefaultCharList = ( ' .,:;i1tfLCG08@' ).split( '' ); + const aDefaultColorCharList = ( ' CGO08@' ).split( '' ); + aCharList = ( bColor ? aDefaultColorCharList : aDefaultCharList ); + + } - if ( charSet ) aCharList = charSet; // Setup dom @@ -260,4 +294,17 @@ class AsciiEffect { } +/** + * This type represents configuration settings of `AsciiEffect`. + * + * @typedef {Object} AsciiEffect~Options + * @property {number} [resolution=0.15] - A higher value leads to more details. + * @property {number} [scale=1] - The scale of the effect. + * @property {boolean} [color=false] - Whether colors should be enabled or not. Better quality but slows down rendering. + * @property {boolean} [alpha=false] - Whether transparency should be enabled or not. + * @property {boolean} [block=false] - Whether blocked characters should be enabled or not. + * @property {boolean} [invert=false] - Whether colors should be inverted or not. + * @property {('low'|'medium'|'high')} [strResolution='low'] - The string resolution. + **/ + export { AsciiEffect }; diff --git a/examples/jsm/effects/OutlineEffect.js b/examples/jsm/effects/OutlineEffect.js index 1ce4f3aa76b837..e2fd11417fe433 100644 --- a/examples/jsm/effects/OutlineEffect.js +++ b/examples/jsm/effects/OutlineEffect.js @@ -7,12 +7,12 @@ import { } from 'three'; /** - * Reference: https://en.wikipedia.org/wiki/Cel_shading + * An outline effect for toon shaders. * - * API - * - * 1. Traditional + * Note that this class can only be used with {@link WebGLRenderer}. + * When using {@link WebGPURenderer}, use {@link ToonOutlinePassNode}. * + * ```js * const effect = new OutlineEffect( renderer ); * * function render() { @@ -20,50 +20,18 @@ import { * effect.render( scene, camera ); * * } + * ``` * - * 2. VR compatible - * - * const effect = new OutlineEffect( renderer ); - * let renderingOutline = false; - * - * scene.onAfterRender = function () { - * - * if ( renderingOutline ) return; - * - * renderingOutline = true; - * - * effect.renderOutline( scene, camera ); - * - * renderingOutline = false; - * - * }; - * - * function render() { - * - * renderer.render( scene, camera ); - * - * } - * - * // How to set default outline parameters - * new OutlineEffect( renderer, { - * defaultThickness: 0.01, - * defaultColor: [ 0, 0, 0 ], - * defaultAlpha: 0.8, - * defaultKeepAlive: true // keeps outline material in cache even if material is removed from scene - * } ); - * - * // How to set outline parameters for each material - * material.userData.outlineParameters = { - * thickness: 0.01, - * color: [ 0, 0, 0 ], - * alpha: 0.8, - * visible: true, - * keepAlive: true - * }; + * @three_import import { OutlineEffect } from 'three/addons/effects/OutlineEffect.js'; */ - class OutlineEffect { + /** + * Constructs a new outline effect. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {OutlineEffect~Options} [parameters] - The configuration parameter. + */ constructor( renderer, parameters = {} ) { this.enabled = true; @@ -165,7 +133,7 @@ class OutlineEffect { ' gl_FragColor = vec4( outlineColor, outlineAlpha );', ' #include ', - ' #include ', + ' #include ', ' #include ', ' #include ', @@ -367,7 +335,7 @@ class OutlineEffect { let keys; - // clear originialMaterials + // clear originalMaterials keys = Object.keys( originalMaterials ); for ( let i = 0, il = keys.length; i < il; i ++ ) { @@ -413,6 +381,13 @@ class OutlineEffect { } + /** + * When using this effect, this method should be called instead of the + * default {@link WebGLRenderer#render}. + * + * @param {Object3D} scene - The scene to render. + * @param {Camera} camera - The camera. + */ this.render = function ( scene, camera ) { if ( this.enabled === false ) { @@ -433,6 +408,30 @@ class OutlineEffect { }; + /** + * This method can be used to render outlines in VR. + * + * ```js + * const effect = new OutlineEffect( renderer ); + * let renderingOutline = false; + * + * scene.onAfterRender = function () { + * + * if ( renderingOutline ) return; + * + * renderingOutline = true; + * effect.renderOutline( scene, camera ); + * renderingOutline = false; + * }; + * + * function render() { + * renderer.render( scene, camera ); + * } + * ``` + * + * @param {Object3D} scene - The scene to render. + * @param {Camera} camera - The camera. + */ this.renderOutline = function ( scene, camera ) { const currentAutoClear = renderer.autoClear; @@ -460,75 +459,15 @@ class OutlineEffect { }; - /* - * See #9918 + /** + * Resizes the effect. * - * The following property copies and wrapper methods enable - * OutlineEffect to be called from other *Effect, like - * - * effect = new StereoEffect( new OutlineEffect( renderer ) ); - * - * function render () { - * - * effect.render( scene, camera ); - * - * } + * @param {number} width - The width of the effect in logical pixels. + * @param {number} height - The height of the effect in logical pixels. */ - this.autoClear = renderer.autoClear; - this.domElement = renderer.domElement; - this.shadowMap = renderer.shadowMap; - - this.clear = function ( color, depth, stencil ) { + this.setSize = function ( width, height ) { - renderer.clear( color, depth, stencil ); - - }; - - this.getPixelRatio = function () { - - return renderer.getPixelRatio(); - - }; - - this.setPixelRatio = function ( value ) { - - renderer.setPixelRatio( value ); - - }; - - this.getSize = function ( target ) { - - return renderer.getSize( target ); - - }; - - this.setSize = function ( width, height, updateStyle ) { - - renderer.setSize( width, height, updateStyle ); - - }; - - this.setViewport = function ( x, y, width, height ) { - - renderer.setViewport( x, y, width, height ); - - }; - - this.setScissor = function ( x, y, width, height ) { - - renderer.setScissor( x, y, width, height ); - - }; - - this.setScissorTest = function ( boolean ) { - - renderer.setScissorTest( boolean ); - - }; - - this.setRenderTarget = function ( renderTarget ) { - - renderer.setRenderTarget( renderTarget ); + renderer.setSize( width, height ); }; @@ -536,4 +475,15 @@ class OutlineEffect { } +/** + * This type represents configuration settings of `OutlineEffect`. + * + * @typedef {Object} OutlineEffect~Options + * @property {number} [defaultThickness=0.003] - The outline thickness. + * @property {Array} [defaultColor=[0,0,0]] - The outline color. + * @property {number} [defaultAlpha=1] - The outline alpha value. + * @property {boolean} [defaultKeepAlive=false] - Whether to keep alive cached internal materials or not. + **/ + + export { OutlineEffect }; diff --git a/examples/jsm/effects/ParallaxBarrierEffect.js b/examples/jsm/effects/ParallaxBarrierEffect.js index 3ad41f78fa2ca0..90573306be731a 100644 --- a/examples/jsm/effects/ParallaxBarrierEffect.js +++ b/examples/jsm/effects/ParallaxBarrierEffect.js @@ -1,24 +1,30 @@ import { LinearFilter, - Mesh, NearestFilter, - OrthographicCamera, - PlaneGeometry, RGBAFormat, - Scene, ShaderMaterial, StereoCamera, WebGLRenderTarget } from 'three'; - +import { FullScreenQuad } from '../postprocessing/Pass.js'; + +/** + * A class that creates an parallax barrier effect. + * + * Note that this class can only be used with {@link WebGLRenderer}. + * When using {@link WebGPURenderer}, use {@link ParallaxBarrierPassNode}. + * + * @three_import import { ParallaxBarrierEffect } from 'three/addons/effects/ParallaxBarrierEffect.js'; + */ class ParallaxBarrierEffect { + /** + * Constructs a new parallax barrier effect. + * + * @param {WebGLRenderer} renderer - The renderer. + */ constructor( renderer ) { - const _camera = new OrthographicCamera( - 1, 1, 1, - 1, 0, 1 ); - - const _scene = new Scene(); - const _stereo = new StereoCamera(); const _params = { minFilter: LinearFilter, magFilter: NearestFilter, format: RGBAFormat }; @@ -68,15 +74,23 @@ class ParallaxBarrierEffect { ' }', + ' #include ', + ' #include ', + '}' ].join( '\n' ) } ); - const mesh = new Mesh( new PlaneGeometry( 2, 2 ), _material ); - _scene.add( mesh ); + const _quad = new FullScreenQuad( _material ); + /** + * Resizes the effect. + * + * @param {number} width - The width of the effect in logical pixels. + * @param {number} height - The height of the effect in logical pixels. + */ this.setSize = function ( width, height ) { renderer.setSize( width, height ); @@ -88,8 +102,17 @@ class ParallaxBarrierEffect { }; + /** + * When using this effect, this method should be called instead of the + * default {@link WebGLRenderer#render}. + * + * @param {Object3D} scene - The scene to render. + * @param {Camera} camera - The camera. + */ this.render = function ( scene, camera ) { + const currentRenderTarget = renderer.getRenderTarget(); + if ( scene.matrixWorldAutoUpdate === true ) scene.updateMatrixWorld(); if ( camera.parent === null && camera.matrixWorldAutoUpdate === true ) camera.updateMatrixWorld(); @@ -105,7 +128,23 @@ class ParallaxBarrierEffect { renderer.render( scene, _stereo.cameraR ); renderer.setRenderTarget( null ); - renderer.render( _scene, _camera ); + _quad.render( renderer ); + + renderer.setRenderTarget( currentRenderTarget ); + + }; + + /** + * Frees internal resources. This method should be called + * when the effect is no longer required. + */ + this.dispose = function () { + + _renderTargetL.dispose(); + _renderTargetR.dispose(); + + _material.dispose(); + _quad.dispose(); }; diff --git a/examples/jsm/effects/PeppersGhostEffect.js b/examples/jsm/effects/PeppersGhostEffect.js deleted file mode 100644 index 15720964949e3c..00000000000000 --- a/examples/jsm/effects/PeppersGhostEffect.js +++ /dev/null @@ -1,153 +0,0 @@ -import { - PerspectiveCamera, - Quaternion, - Vector3 -} from 'three'; - -/** - * peppers ghost effect based on http://www.instructables.com/id/Reflective-Prism/?ALLSTEPS - */ - -class PeppersGhostEffect { - - constructor( renderer ) { - - const scope = this; - - scope.cameraDistance = 15; - scope.reflectFromAbove = false; - - // Internals - let _halfWidth, _width, _height; - - const _cameraF = new PerspectiveCamera(); //front - const _cameraB = new PerspectiveCamera(); //back - const _cameraL = new PerspectiveCamera(); //left - const _cameraR = new PerspectiveCamera(); //right - - const _position = new Vector3(); - const _quaternion = new Quaternion(); - const _scale = new Vector3(); - - // Initialization - renderer.autoClear = false; - - this.setSize = function ( width, height ) { - - _halfWidth = width / 2; - if ( width < height ) { - - _width = width / 3; - _height = width / 3; - - } else { - - _width = height / 3; - _height = height / 3; - - } - - renderer.setSize( width, height ); - - }; - - this.render = function ( scene, camera ) { - - if ( scene.matrixWorldAutoUpdate === true ) scene.updateMatrixWorld(); - - if ( camera.parent === null && camera.matrixWorldAutoUpdate === true ) camera.updateMatrixWorld(); - - camera.matrixWorld.decompose( _position, _quaternion, _scale ); - - // front - _cameraF.position.copy( _position ); - _cameraF.quaternion.copy( _quaternion ); - _cameraF.translateZ( scope.cameraDistance ); - _cameraF.lookAt( scene.position ); - - // back - _cameraB.position.copy( _position ); - _cameraB.quaternion.copy( _quaternion ); - _cameraB.translateZ( - ( scope.cameraDistance ) ); - _cameraB.lookAt( scene.position ); - _cameraB.rotation.z += 180 * ( Math.PI / 180 ); - - // left - _cameraL.position.copy( _position ); - _cameraL.quaternion.copy( _quaternion ); - _cameraL.translateX( - ( scope.cameraDistance ) ); - _cameraL.lookAt( scene.position ); - _cameraL.rotation.x += 90 * ( Math.PI / 180 ); - - // right - _cameraR.position.copy( _position ); - _cameraR.quaternion.copy( _quaternion ); - _cameraR.translateX( scope.cameraDistance ); - _cameraR.lookAt( scene.position ); - _cameraR.rotation.x += 90 * ( Math.PI / 180 ); - - - renderer.clear(); - renderer.setScissorTest( true ); - - renderer.setScissor( _halfWidth - ( _width / 2 ), ( _height * 2 ), _width, _height ); - renderer.setViewport( _halfWidth - ( _width / 2 ), ( _height * 2 ), _width, _height ); - - if ( scope.reflectFromAbove ) { - - renderer.render( scene, _cameraB ); - - } else { - - renderer.render( scene, _cameraF ); - - } - - renderer.setScissor( _halfWidth - ( _width / 2 ), 0, _width, _height ); - renderer.setViewport( _halfWidth - ( _width / 2 ), 0, _width, _height ); - - if ( scope.reflectFromAbove ) { - - renderer.render( scene, _cameraF ); - - } else { - - renderer.render( scene, _cameraB ); - - } - - renderer.setScissor( _halfWidth - ( _width / 2 ) - _width, _height, _width, _height ); - renderer.setViewport( _halfWidth - ( _width / 2 ) - _width, _height, _width, _height ); - - if ( scope.reflectFromAbove ) { - - renderer.render( scene, _cameraR ); - - } else { - - renderer.render( scene, _cameraL ); - - } - - renderer.setScissor( _halfWidth + ( _width / 2 ), _height, _width, _height ); - renderer.setViewport( _halfWidth + ( _width / 2 ), _height, _width, _height ); - - if ( scope.reflectFromAbove ) { - - renderer.render( scene, _cameraL ); - - } else { - - renderer.render( scene, _cameraR ); - - } - - renderer.setScissorTest( false ); - - }; - - } - -} - -export { PeppersGhostEffect }; diff --git a/examples/jsm/effects/StereoEffect.js b/examples/jsm/effects/StereoEffect.js index e6e1b44767e6a9..e5056f7173e573 100644 --- a/examples/jsm/effects/StereoEffect.js +++ b/examples/jsm/effects/StereoEffect.js @@ -3,26 +3,57 @@ import { Vector2 } from 'three'; +/** + * A class that creates an stereo effect. + * + * Note that this class can only be used with {@link WebGLRenderer}. + * When using {@link WebGPURenderer}, use {@link StereoPassNode}. + * + * @three_import import { StereoEffect } from 'three/addons/effects/StereoEffect.js'; + */ class StereoEffect { + /** + * Constructs a new stereo effect. + * + * @param {WebGLRenderer} renderer - The renderer. + */ constructor( renderer ) { const _stereo = new StereoCamera(); _stereo.aspect = 0.5; const size = new Vector2(); + /** + * Sets the given eye separation. + * + * @param {number} eyeSep - The eye separation to set. + */ this.setEyeSeparation = function ( eyeSep ) { _stereo.eyeSep = eyeSep; }; + /** + * Resizes the effect. + * + * @param {number} width - The width of the effect in logical pixels. + * @param {number} height - The height of the effect in logical pixels. + */ this.setSize = function ( width, height ) { renderer.setSize( width, height ); }; + /** + * When using this effect, this method should be called instead of the + * default {@link WebGLRenderer#render}. + * + * @param {Object3D} scene - The scene to render. + * @param {Camera} camera - The camera. + */ this.render = function ( scene, camera ) { if ( scene.matrixWorldAutoUpdate === true ) scene.updateMatrixWorld(); @@ -31,9 +62,12 @@ class StereoEffect { _stereo.update( camera ); + const currentAutoClear = renderer.autoClear; renderer.getSize( size ); - if ( renderer.autoClear ) renderer.clear(); + renderer.autoClear = false; + renderer.clear(); + renderer.setScissorTest( true ); renderer.setScissor( 0, 0, size.width / 2, size.height ); @@ -46,6 +80,8 @@ class StereoEffect { renderer.setScissorTest( false ); + renderer.autoClear = currentAutoClear; + }; } diff --git a/examples/jsm/environments/DebugEnvironment.js b/examples/jsm/environments/DebugEnvironment.js index ce3db065ff3b36..fd7db25fbc73aa 100644 --- a/examples/jsm/environments/DebugEnvironment.js +++ b/examples/jsm/environments/DebugEnvironment.js @@ -8,8 +8,31 @@ import { Scene, } from 'three'; +/** + * This class represents a scene with a very basic room setup that can be used as + * input for {@link PMREMGenerator#fromScene}. The resulting PMREM represents the room's + * lighting and can be used for Image Based Lighting by assigning it to {@link Scene#environment} + * or directly as an environment map to PBR materials. + * + * This class uses a simple room setup and should only be used for development purposes. + * A more appropriate setup for production is {@link RoomEnvironment}. + * + * ```js + * const environment = new DebugEnvironment(); + * const pmremGenerator = new THREE.PMREMGenerator( renderer ); + * + * const envMap = pmremGenerator.fromScene( environment ).texture; + * scene.environment = envMap; + * ``` + * + * @augments Scene + * @three_import import { DebugEnvironment } from 'three/addons/environments/DebugEnvironment.js'; + */ class DebugEnvironment extends Scene { + /** + * Constructs a new debug environment. + */ constructor() { super(); @@ -47,6 +70,33 @@ class DebugEnvironment extends Scene { } + /** + * Frees internal resources. This method should be called + * when the environment is no longer required. + */ + dispose() { + + const resources = new Set(); + + this.traverse( ( object ) => { + + if ( object.isMesh ) { + + resources.add( object.geometry ); + resources.add( object.material ); + + } + + } ); + + for ( const resource of resources ) { + + resource.dispose(); + + } + + } + } export { DebugEnvironment }; diff --git a/examples/jsm/environments/RoomEnvironment.js b/examples/jsm/environments/RoomEnvironment.js index 1846dfabb79235..125443deff8926 100644 --- a/examples/jsm/environments/RoomEnvironment.js +++ b/examples/jsm/environments/RoomEnvironment.js @@ -1,17 +1,35 @@ -/** - * https://github.com/google/model-viewer/blob/master/packages/model-viewer/src/three-components/EnvironmentScene.ts - */ - import { BackSide, BoxGeometry, + InstancedMesh, Mesh, MeshBasicMaterial, MeshStandardMaterial, PointLight, Scene, + Object3D, } from 'three'; +/** + * This class represents a scene with a basic room setup that can be used as + * input for {@link PMREMGenerator#fromScene}. The resulting PMREM represents the room's + * lighting and can be used for Image Based Lighting by assigning it to {@link Scene#environment} + * or directly as an environment map to PBR materials. + * + * The implementation is based on the [EnvironmentScene](https://github.com/google/model-viewer/blob/master/packages/model-viewer/src/three-components/EnvironmentScene.ts) + * component from the `model-viewer` project. + * + * ```js + * const environment = new RoomEnvironment(); + * const pmremGenerator = new THREE.PMREMGenerator( renderer ); + * + * const envMap = pmremGenerator.fromScene( environment ).texture; + * scene.environment = envMap; + * ``` + * + * @augments Scene + * @three_import import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js'; + */ class RoomEnvironment extends Scene { constructor() { @@ -24,7 +42,7 @@ class RoomEnvironment extends Scene { const roomMaterial = new MeshStandardMaterial( { side: BackSide } ); const boxMaterial = new MeshStandardMaterial(); - const mainLight = new PointLight( 0xffffff, 5.0, 28, 2 ); + const mainLight = new PointLight( 0xffffff, 900, 28, 2 ); mainLight.position.set( 0.418, 16.199, 0.300 ); this.add( mainLight ); @@ -33,41 +51,52 @@ class RoomEnvironment extends Scene { room.scale.set( 31.713, 28.305, 28.591 ); this.add( room ); - const box1 = new Mesh( geometry, boxMaterial ); - box1.position.set( - 10.906, 2.009, 1.846 ); - box1.rotation.set( 0, - 0.195, 0 ); - box1.scale.set( 2.328, 7.905, 4.651 ); - this.add( box1 ); - - const box2 = new Mesh( geometry, boxMaterial ); - box2.position.set( - 5.607, - 0.754, - 0.758 ); - box2.rotation.set( 0, 0.994, 0 ); - box2.scale.set( 1.970, 1.534, 3.955 ); - this.add( box2 ); - - const box3 = new Mesh( geometry, boxMaterial ); - box3.position.set( 6.167, 0.857, 7.803 ); - box3.rotation.set( 0, 0.561, 0 ); - box3.scale.set( 3.927, 6.285, 3.687 ); - this.add( box3 ); - - const box4 = new Mesh( geometry, boxMaterial ); - box4.position.set( - 2.017, 0.018, 6.124 ); - box4.rotation.set( 0, 0.333, 0 ); - box4.scale.set( 2.002, 4.566, 2.064 ); - this.add( box4 ); - - const box5 = new Mesh( geometry, boxMaterial ); - box5.position.set( 2.291, - 0.756, - 2.621 ); - box5.rotation.set( 0, - 0.286, 0 ); - box5.scale.set( 1.546, 1.552, 1.496 ); - this.add( box5 ); - - const box6 = new Mesh( geometry, boxMaterial ); - box6.position.set( - 2.193, - 0.369, - 5.547 ); - box6.rotation.set( 0, 0.516, 0 ); - box6.scale.set( 3.875, 3.487, 2.986 ); - this.add( box6 ); + const boxes = new InstancedMesh( geometry, boxMaterial, 6 ); + const transform = new Object3D(); + + // box1 + transform.position.set( - 10.906, 2.009, 1.846 ); + transform.rotation.set( 0, - 0.195, 0 ); + transform.scale.set( 2.328, 7.905, 4.651 ); + transform.updateMatrix(); + boxes.setMatrixAt( 0, transform.matrix ); + + // box2 + transform.position.set( - 5.607, - 0.754, - 0.758 ); + transform.rotation.set( 0, 0.994, 0 ); + transform.scale.set( 1.970, 1.534, 3.955 ); + transform.updateMatrix(); + boxes.setMatrixAt( 1, transform.matrix ); + + // box3 + transform.position.set( 6.167, 0.857, 7.803 ); + transform.rotation.set( 0, 0.561, 0 ); + transform.scale.set( 3.927, 6.285, 3.687 ); + transform.updateMatrix(); + boxes.setMatrixAt( 2, transform.matrix ); + + // box4 + transform.position.set( - 2.017, 0.018, 6.124 ); + transform.rotation.set( 0, 0.333, 0 ); + transform.scale.set( 2.002, 4.566, 2.064 ); + transform.updateMatrix(); + boxes.setMatrixAt( 3, transform.matrix ); + + // box5 + transform.position.set( 2.291, - 0.756, - 2.621 ); + transform.rotation.set( 0, - 0.286, 0 ); + transform.scale.set( 1.546, 1.552, 1.496 ); + transform.updateMatrix(); + boxes.setMatrixAt( 4, transform.matrix ); + + // box6 + transform.position.set( - 2.193, - 0.369, - 5.547 ); + transform.rotation.set( 0, 0.516, 0 ); + transform.scale.set( 3.875, 3.487, 2.986 ); + transform.updateMatrix(); + boxes.setMatrixAt( 5, transform.matrix ); + + this.add( boxes ); // -x right @@ -108,6 +137,10 @@ class RoomEnvironment extends Scene { } + /** + * Frees internal resources. This method should be called + * when the environment is no longer required. + */ dispose() { const resources = new Set(); diff --git a/examples/jsm/exporters/ColladaExporter.js b/examples/jsm/exporters/ColladaExporter.js deleted file mode 100644 index a4ff5dd875bb34..00000000000000 --- a/examples/jsm/exporters/ColladaExporter.js +++ /dev/null @@ -1,725 +0,0 @@ -import { - Color, - DoubleSide, - Matrix4, - MeshBasicMaterial -} from 'three'; - -/** - * https://github.com/gkjohnson/collada-exporter-js - * - * Usage: - * const exporter = new ColladaExporter(); - * - * const data = exporter.parse(mesh); - * - * Format Definition: - * https://www.khronos.org/collada/ - */ - -class ColladaExporter { - - parse( object, onDone, options = {} ) { - - options = Object.assign( { - version: '1.4.1', - author: null, - textureDirectory: '', - upAxis: 'Y_UP', - unitName: null, - unitMeter: null, - }, options ); - - if ( options.upAxis.match( /^[XYZ]_UP$/ ) === null ) { - - console.error( 'ColladaExporter: Invalid upAxis: valid values are X_UP, Y_UP or Z_UP.' ); - return null; - - } - - if ( options.unitName !== null && options.unitMeter === null ) { - - console.error( 'ColladaExporter: unitMeter needs to be specified if unitName is specified.' ); - return null; - - } - - if ( options.unitMeter !== null && options.unitName === null ) { - - console.error( 'ColladaExporter: unitName needs to be specified if unitMeter is specified.' ); - return null; - - } - - if ( options.textureDirectory !== '' ) { - - options.textureDirectory = `${ options.textureDirectory }/` - .replace( /\\/g, '/' ) - .replace( /\/+/g, '/' ); - - } - - const version = options.version; - - if ( version !== '1.4.1' && version !== '1.5.0' ) { - - console.warn( `ColladaExporter : Version ${ version } not supported for export. Only 1.4.1 and 1.5.0.` ); - return null; - - } - - // Convert the urdf xml into a well-formatted, indented format - function format( urdf ) { - - const IS_END_TAG = /^<\//; - const IS_SELF_CLOSING = /(\?>$)|(\/>$)/; - const HAS_TEXT = /<[^>]+>[^<]*<\/[^<]+>/; - - const pad = ( ch, num ) => ( num > 0 ? ch + pad( ch, num - 1 ) : '' ); - - let tagnum = 0; - - return urdf - .match( /(<[^>]+>[^<]+<\/[^<]+>)|(<[^>]+>)/g ) - .map( tag => { - - if ( ! HAS_TEXT.test( tag ) && ! IS_SELF_CLOSING.test( tag ) && IS_END_TAG.test( tag ) ) { - - tagnum --; - - } - - const res = `${ pad( ' ', tagnum ) }${ tag }`; - - if ( ! HAS_TEXT.test( tag ) && ! IS_SELF_CLOSING.test( tag ) && ! IS_END_TAG.test( tag ) ) { - - tagnum ++; - - } - - return res; - - } ) - .join( '\n' ); - - } - - // Convert an image into a png format for saving - function base64ToBuffer( str ) { - - const b = atob( str ); - const buf = new Uint8Array( b.length ); - - for ( let i = 0, l = buf.length; i < l; i ++ ) { - - buf[ i ] = b.charCodeAt( i ); - - } - - return buf; - - } - - let canvas, ctx; - - function imageToData( image, ext ) { - - if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || - ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) || - ( typeof OffscreenCanvas !== 'undefined' && image instanceof OffscreenCanvas ) || - ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) { - - canvas = canvas || document.createElement( 'canvas' ); - ctx = ctx || canvas.getContext( '2d' ); - - canvas.width = image.width; - canvas.height = image.height; - - ctx.drawImage( image, 0, 0 ); - - // Get the base64 encoded data - const base64data = canvas - .toDataURL( `image/${ ext }`, 1 ) - .replace( /^data:image\/(png|jpg);base64,/, '' ); - - // Convert to a uint8 array - return base64ToBuffer( base64data ); - - } else { - - throw new Error( 'THREE.ColladaExporter: No valid image data found. Unable to process texture.' ); - - } - - } - - // gets the attribute array. Generate a new array if the attribute is interleaved - const getFuncs = [ 'getX', 'getY', 'getZ', 'getW' ]; - const tempColor = new Color(); - - function attrBufferToArray( attr, isColor = false ) { - - if ( isColor ) { - - // convert the colors to srgb before export - // colors are always written as floats - const arr = new Float32Array( attr.count * 3 ); - for ( let i = 0, l = attr.count; i < l; i ++ ) { - - tempColor - .fromBufferAttribute( attr, i ) - .convertLinearToSRGB(); - - arr[ 3 * i + 0 ] = tempColor.r; - arr[ 3 * i + 1 ] = tempColor.g; - arr[ 3 * i + 2 ] = tempColor.b; - - } - - return arr; - - } else if ( attr.isInterleavedBufferAttribute ) { - - // use the typed array constructor to save on memory - const arr = new attr.array.constructor( attr.count * attr.itemSize ); - const size = attr.itemSize; - - for ( let i = 0, l = attr.count; i < l; i ++ ) { - - for ( let j = 0; j < size; j ++ ) { - - arr[ i * size + j ] = attr[ getFuncs[ j ] ]( i ); - - } - - } - - return arr; - - } else { - - return attr.array; - - } - - } - - // Returns an array of the same type starting at the `st` index, - // and `ct` length - function subArray( arr, st, ct ) { - - if ( Array.isArray( arr ) ) return arr.slice( st, st + ct ); - else return new arr.constructor( arr.buffer, st * arr.BYTES_PER_ELEMENT, ct ); - - } - - // Returns the string for a geometry's attribute - function getAttribute( attr, name, params, type, isColor = false ) { - - const array = attrBufferToArray( attr, isColor ); - const res = - `` + - - `` + - array.join( ' ' ) + - '' + - - '' + - `` + - - params.map( n => `` ).join( '' ) + - - '' + - '' + - ''; - - return res; - - } - - // Returns the string for a node's transform information - let transMat; - function getTransform( o ) { - - // ensure the object's matrix is up to date - // before saving the transform - o.updateMatrix(); - - transMat = transMat || new Matrix4(); - transMat.copy( o.matrix ); - transMat.transpose(); - return `${ transMat.toArray().join( ' ' ) }`; - - } - - // Process the given piece of geometry into the geometry library - // Returns the mesh id - function processGeometry( bufferGeometry ) { - - let info = geometryInfo.get( bufferGeometry ); - - if ( ! info ) { - - const meshid = `Mesh${ libraryGeometries.length + 1 }`; - - const indexCount = - bufferGeometry.index ? - bufferGeometry.index.count * bufferGeometry.index.itemSize : - bufferGeometry.attributes.position.count; - - const groups = - bufferGeometry.groups != null && bufferGeometry.groups.length !== 0 ? - bufferGeometry.groups : - [ { start: 0, count: indexCount, materialIndex: 0 } ]; - - - const gname = bufferGeometry.name ? ` name="${ bufferGeometry.name }"` : ''; - let gnode = ``; - - // define the geometry node and the vertices for the geometry - const posName = `${ meshid }-position`; - const vertName = `${ meshid }-vertices`; - gnode += getAttribute( bufferGeometry.attributes.position, posName, [ 'X', 'Y', 'Z' ], 'float' ); - gnode += ``; - - // NOTE: We're not optimizing the attribute arrays here, so they're all the same length and - // can therefore share the same triangle indices. However, MeshLab seems to have trouble opening - // models with attributes that share an offset. - // MeshLab Bug#424: https://sourceforge.net/p/meshlab/bugs/424/ - - // serialize normals - let triangleInputs = ``; - if ( 'normal' in bufferGeometry.attributes ) { - - const normName = `${ meshid }-normal`; - gnode += getAttribute( bufferGeometry.attributes.normal, normName, [ 'X', 'Y', 'Z' ], 'float' ); - triangleInputs += ``; - - } - - // serialize uvs - if ( 'uv' in bufferGeometry.attributes ) { - - const uvName = `${ meshid }-texcoord`; - gnode += getAttribute( bufferGeometry.attributes.uv, uvName, [ 'S', 'T' ], 'float' ); - triangleInputs += ``; - - } - - // serialize lightmap uvs - if ( 'uv1' in bufferGeometry.attributes ) { - - const uvName = `${ meshid }-texcoord2`; - gnode += getAttribute( bufferGeometry.attributes.uv1, uvName, [ 'S', 'T' ], 'float' ); - triangleInputs += ``; - - } - - // serialize colors - if ( 'color' in bufferGeometry.attributes ) { - - // colors are always written as floats - const colName = `${ meshid }-color`; - gnode += getAttribute( bufferGeometry.attributes.color, colName, [ 'R', 'G', 'B' ], 'float', true ); - triangleInputs += ``; - - } - - let indexArray = null; - if ( bufferGeometry.index ) { - - indexArray = attrBufferToArray( bufferGeometry.index ); - - } else { - - indexArray = new Array( indexCount ); - for ( let i = 0, l = indexArray.length; i < l; i ++ ) indexArray[ i ] = i; - - } - - for ( let i = 0, l = groups.length; i < l; i ++ ) { - - const group = groups[ i ]; - const subarr = subArray( indexArray, group.start, group.count ); - const polycount = subarr.length / 3; - gnode += ``; - gnode += triangleInputs; - - gnode += `

        ${ subarr.join( ' ' ) }

        `; - gnode += '
        '; - - } - - gnode += '
        '; - - libraryGeometries.push( gnode ); - - info = { meshid: meshid, bufferGeometry: bufferGeometry }; - geometryInfo.set( bufferGeometry, info ); - - } - - return info; - - } - - // Process the given texture into the image library - // Returns the image library - function processTexture( tex ) { - - let texid = imageMap.get( tex ); - - if ( texid === undefined ) { - - texid = `image-${ libraryImages.length + 1 }`; - - const ext = 'png'; - const name = tex.name || texid; - let imageNode = ``; - - if ( version === '1.5.0' ) { - - imageNode += `${ options.textureDirectory }${ name }.${ ext }`; - - } else { - - // version image node 1.4.1 - imageNode += `${ options.textureDirectory }${ name }.${ ext }`; - - } - - imageNode += ''; - - libraryImages.push( imageNode ); - imageMap.set( tex, texid ); - textures.push( { - directory: options.textureDirectory, - name, - ext, - data: imageToData( tex.image, ext ), - original: tex - } ); - - } - - return texid; - - } - - // Process the given material into the material and effect libraries - // Returns the material id - function processMaterial( m ) { - - let matid = materialMap.get( m ); - - if ( matid == null ) { - - matid = `Mat${ libraryEffects.length + 1 }`; - - let type = 'phong'; - - if ( m.isMeshLambertMaterial === true ) { - - type = 'lambert'; - - } else if ( m.isMeshBasicMaterial === true ) { - - type = 'constant'; - - if ( m.map !== null ) { - - // The Collada spec does not support diffuse texture maps with the - // constant shader type. - // mrdoob/three.js#15469 - console.warn( 'ColladaExporter: Texture maps not supported with MeshBasicMaterial.' ); - - } - - } - - const emissive = m.emissive ? m.emissive : new Color( 0, 0, 0 ); - const diffuse = m.color ? m.color : new Color( 0, 0, 0 ); - const specular = m.specular ? m.specular : new Color( 1, 1, 1 ); - const shininess = m.shininess || 0; - const reflectivity = m.reflectivity || 0; - - emissive.convertLinearToSRGB(); - specular.convertLinearToSRGB(); - diffuse.convertLinearToSRGB(); - - // Do not export and alpha map for the reasons mentioned in issue (#13792) - // in three.js alpha maps are black and white, but collada expects the alpha - // channel to specify the transparency - let transparencyNode = ''; - if ( m.transparent === true ) { - - transparencyNode += - '' + - ( - m.map ? - '' : - '1' - ) + - ''; - - if ( m.opacity < 1 ) { - - transparencyNode += `${ m.opacity }`; - - } - - } - - const techniqueNode = `<${ type }>` + - - '' + - - ( - m.emissiveMap ? - '' : - `${ emissive.r } ${ emissive.g } ${ emissive.b } 1` - ) + - - '' + - - ( - type !== 'constant' ? - '' + - - ( - m.map ? - '' : - `${ diffuse.r } ${ diffuse.g } ${ diffuse.b } 1` - ) + - '' - : '' - ) + - - ( - type !== 'constant' ? - '' + - - ( - m.normalMap ? '' : '' - ) + - '' - : '' - ) + - - ( - type === 'phong' ? - `${ specular.r } ${ specular.g } ${ specular.b } 1` + - - '' + - - ( - m.specularMap ? - '' : - `${ shininess }` - ) + - - '' - : '' - ) + - - `${ diffuse.r } ${ diffuse.g } ${ diffuse.b } 1` + - - `${ reflectivity }` + - - transparencyNode + - - ``; - - const effectnode = - `` + - '' + - - ( - m.map ? - '' + - `${ processTexture( m.map ) }` + - '' + - 'diffuse-surface' : - '' - ) + - - ( - m.specularMap ? - '' + - `${ processTexture( m.specularMap ) }` + - '' + - 'specular-surface' : - '' - ) + - - ( - m.emissiveMap ? - '' + - `${ processTexture( m.emissiveMap ) }` + - '' + - 'emissive-surface' : - '' - ) + - - ( - m.normalMap ? - '' + - `${ processTexture( m.normalMap ) }` + - '' + - 'bump-surface' : - '' - ) + - - techniqueNode + - - ( - m.side === DoubleSide ? - '1' : - '' - ) + - - '' + - - ''; - - const materialName = m.name ? ` name="${ m.name }"` : ''; - const materialNode = ``; - - libraryMaterials.push( materialNode ); - libraryEffects.push( effectnode ); - materialMap.set( m, matid ); - - } - - return matid; - - } - - // Recursively process the object into a scene - function processObject( o ) { - - let node = ``; - - node += getTransform( o ); - - if ( o.isMesh === true && o.geometry !== null ) { - - // function returns the id associated with the mesh and a "BufferGeometry" version - // of the geometry in case it's not a geometry. - const geomInfo = processGeometry( o.geometry ); - const meshid = geomInfo.meshid; - const geometry = geomInfo.bufferGeometry; - - // ids of the materials to bind to the geometry - let matids = null; - let matidsArray; - - // get a list of materials to bind to the sub groups of the geometry. - // If the amount of subgroups is greater than the materials, than reuse - // the materials. - const mat = o.material || new MeshBasicMaterial(); - const materials = Array.isArray( mat ) ? mat : [ mat ]; - - if ( geometry.groups.length > materials.length ) { - - matidsArray = new Array( geometry.groups.length ); - - } else { - - matidsArray = new Array( materials.length ); - - } - - matids = matidsArray.fill().map( ( v, i ) => processMaterial( materials[ i % materials.length ] ) ); - - node += - `` + - - ( - matids.length > 0 ? - '' + - matids.map( ( id, i ) => - - `` + - - '' + - - '' - ).join( '' ) + - '' : - '' - ) + - - ''; - - } - - o.children.forEach( c => node += processObject( c ) ); - - node += ''; - - return node; - - } - - const geometryInfo = new WeakMap(); - const materialMap = new WeakMap(); - const imageMap = new WeakMap(); - const textures = []; - - const libraryImages = []; - const libraryGeometries = []; - const libraryEffects = []; - const libraryMaterials = []; - const libraryVisualScenes = processObject( object ); - - const specLink = version === '1.4.1' ? 'http://www.collada.org/2005/11/COLLADASchema' : 'https://www.khronos.org/collada/'; - let dae = - '' + - `` + - '' + - ( - '' + - 'three.js Collada Exporter' + - ( options.author !== null ? `${ options.author }` : '' ) + - '' + - `${ ( new Date() ).toISOString() }` + - `${ ( new Date() ).toISOString() }` + - ( options.unitName !== null ? `` : '' ) + - `${ options.upAxis }` - ) + - ''; - - dae += `${ libraryImages.join( '' ) }`; - - dae += `${ libraryEffects.join( '' ) }`; - - dae += `${ libraryMaterials.join( '' ) }`; - - dae += `${ libraryGeometries.join( '' ) }`; - - dae += `${ libraryVisualScenes }`; - - dae += ''; - - dae += ''; - - const res = { - data: format( dae ), - textures - }; - - if ( typeof onDone === 'function' ) { - - requestAnimationFrame( () => onDone( res ) ); - - } - - return res; - - } - -} - - -export { ColladaExporter }; diff --git a/examples/jsm/exporters/DRACOExporter.js b/examples/jsm/exporters/DRACOExporter.js index 85a2e15adc4d6d..792f2f57e7191d 100644 --- a/examples/jsm/exporters/DRACOExporter.js +++ b/examples/jsm/exporters/DRACOExporter.js @@ -1,24 +1,36 @@ -import { Color } from 'three'; +import { Color, ColorManagement, SRGBColorSpace } from 'three'; + +/* global DracoEncoderModule */ /** - * Export draco compressed files from threejs geometry objects. + * An exporter to compress geometry with the Draco library. + * + * [Draco]{@link https://google.github.io/draco/} is an open source library for compressing and + * decompressing 3D meshes and point clouds. Compressed geometry can be significantly smaller, + * at the cost of additional decoding time on the client device. * - * Draco files are compressed and usually are smaller than conventional 3D file formats. + * Standalone Draco files have a `.drc` extension, and contain vertex positions, + * normals, colors, and other attributes. Draco files *do not* contain materials, + * textures, animation, or node hierarchies – to use these features, embed Draco geometry + * inside of a glTF file. A normal glTF file can be converted to a Draco-compressed glTF file + * using [glTF-Pipeline]{@link https://github.com/AnalyticalGraphicsInc/gltf-pipeline}. * - * The exporter receives a options object containing - * - decodeSpeed, indicates how to tune the encoder regarding decode speed (0 gives better speed but worst quality) - * - encodeSpeed, indicates how to tune the encoder parameters (0 gives better speed but worst quality) - * - encoderMethod - * - quantization, indicates the presision of each type of data stored in the draco file in the order (POSITION, NORMAL, COLOR, TEX_COORD, GENERIC) - * - exportUvs - * - exportNormals - * - exportColor + * ```js + * const exporter = new DRACOExporter(); + * const data = exporter.parse( mesh, options ); + * ``` + * + * @three_import import { DRACOExporter } from 'three/addons/exporters/DRACOExporter.js'; */ - -/* global DracoEncoderModule */ - class DRACOExporter { + /** + * Parses the given mesh or point cloud and generates the Draco output. + * + * @param {(Mesh|Points)} object - The mesh or point cloud to export. + * @param {DRACOExporter~Options} options - The export options. + * @return {Int8Array} The exported Draco. + */ parse( object, options = {} ) { options = Object.assign( { @@ -227,7 +239,9 @@ function createVertexColorSRGBArray( attribute ) { for ( let i = 0, il = count; i < il; i ++ ) { - _color.fromBufferAttribute( attribute, i ).convertLinearToSRGB(); + _color.fromBufferAttribute( attribute, i ); + + ColorManagement.workingToColorSpace( _color, SRGBColorSpace ); array[ i * itemSize ] = _color.r; array[ i * itemSize + 1 ] = _color.g; @@ -247,7 +261,24 @@ function createVertexColorSRGBArray( attribute ) { // Encoder methods +/** + * Edgebreaker encoding. + * + * @static + * @constant + * @type {number} + * @default 1 + */ DRACOExporter.MESH_EDGEBREAKER_ENCODING = 1; + +/** + * Sequential encoding. + * + * @static + * @constant + * @type {number} + * @default 0 + */ DRACOExporter.MESH_SEQUENTIAL_ENCODING = 0; // Geometry type @@ -264,4 +295,17 @@ DRACOExporter.COLOR = 2; DRACOExporter.TEX_COORD = 3; DRACOExporter.GENERIC = 4; +/** + * Export options of `DRACOExporter`. + * + * @typedef {Object} DRACOExporter~Options + * @property {number} [decodeSpeed=5] - Indicates how to tune the encoder regarding decode speed (0 gives better speed but worst quality). + * @property {number} [encodeSpeed=5] - Indicates how to tune the encoder parameters (0 gives better speed but worst quality). + * @property {number} [encoderMethod=1] - Either sequential (very little compression) or Edgebreaker. Edgebreaker traverses the triangles of the mesh in a deterministic, spiral-like way which provides most of the benefits of this data format. + * @property {Array} [quantization=[ 16, 8, 8, 8, 8 ]] - Indicates the precision of each type of data stored in the draco file in the order (POSITION, NORMAL, COLOR, TEX_COORD, GENERIC). + * @property {boolean} [exportUvs=true] - Whether to export UVs or not. + * @property {boolean} [exportNormals=true] - Whether to export normals or not. + * @property {boolean} [exportColor=false] - Whether to export colors or not. + **/ + export { DRACOExporter }; diff --git a/examples/jsm/exporters/EXRExporter.js b/examples/jsm/exporters/EXRExporter.js index 388dfdf1a2a166..8bfa9ddd526e4a 100644 --- a/examples/jsm/exporters/EXRExporter.js +++ b/examples/jsm/exporters/EXRExporter.js @@ -1,10 +1,3 @@ -/** - * @author sciecode / https://github.com/sciecode - * - * EXR format references: - * https://www.openexr.com/documentation/openexrfilelayout.pdf - */ - import { FloatType, HalfFloatType, @@ -19,63 +12,139 @@ const NO_COMPRESSION = 0; const ZIPS_COMPRESSION = 2; const ZIP_COMPRESSION = 3; +/** + * An exporter for EXR. + * + * EXR ( Extended Dynamic Range) is an [open format specification]{@link https://github.com/AcademySoftwareFoundation/openexr} + * for professional-grade image storage format of the motion picture industry. The purpose of + * format is to accurately and efficiently represent high-dynamic-range scene-linear image data + * and associated metadata. The library is widely used in host application software where accuracy + * is critical, such as photorealistic rendering, texture access, image compositing, deep compositing, + * and DI. + * + * ```js + * const exporter = new EXRExporter(); + * const result = await exporter.parse( renderer, options ); + * ``` + * + * @three_import import { EXRExporter } from 'three/addons/exporters/EXRExporter.js'; + */ class EXRExporter { - parse( renderer, renderTarget, options ) { + /** + * This method has two variants. + * + * - When exporting a data texture, it receives two parameters. The texture and the exporter options. + * - When exporting a render target (e.g. a PMREM), it receives three parameters. The renderer, the + * render target and the exporter options. + * + * @async + * @param {(DataTexture|WebGPURenderer|WebGLRenderer)} arg1 - The data texture to export or a renderer. + * @param {(EXRExporter~Options|RenderTarget)} arg2 - The exporter options or a render target. + * @param {EXRExporter~Options} [arg3] - The exporter options. + * @return {Promise} A Promise that resolves with the exported EXR. + */ + async parse( arg1, arg2, arg3 ) { + + if ( ! arg1 || ! ( arg1.isWebGLRenderer || arg1.isWebGPURenderer || arg1.isDataTexture ) ) { - if ( ! supported( renderer, renderTarget ) ) return undefined; + throw Error( 'EXRExporter.parse: Unsupported first parameter, expected instance of WebGLRenderer, WebGPURenderer or DataTexture.' ); - const info = buildInfo( renderTarget, options ), - dataBuffer = getPixelData( renderer, renderTarget, info ), - rawContentBuffer = reorganizeDataBuffer( dataBuffer, info ), - chunks = compressData( rawContentBuffer, info ); + } else if ( arg1.isWebGLRenderer || arg1.isWebGPURenderer ) { - return fillData( chunks, info ); + const renderer = arg1, renderTarget = arg2, options = arg3; + + supportedRTT( renderTarget ); + + const info = buildInfoRTT( renderTarget, options ), + dataBuffer = await getPixelData( renderer, renderTarget, info ), + rawContentBuffer = reorganizeDataBuffer( dataBuffer, info ), + chunks = compressData( rawContentBuffer, info ); + + return fillData( chunks, info ); + + } else if ( arg1.isDataTexture ) { + + const texture = arg1, options = arg2; + + supportedDT( texture ); + + const info = buildInfoDT( texture, options ), + dataBuffer = texture.image.data, + rawContentBuffer = reorganizeDataBuffer( dataBuffer, info ), + chunks = compressData( rawContentBuffer, info ); + + return fillData( chunks, info ); + + } } } -function supported( renderer, renderTarget ) { +function supportedRTT( renderTarget ) { - if ( ! renderer || ! renderer.isWebGLRenderer ) { + if ( ! renderTarget || ! renderTarget.isRenderTarget ) { - console.error( 'EXRExporter.parse: Unsupported first parameter, expected instance of WebGLRenderer.' ); + throw Error( 'EXRExporter.parse: Unsupported second parameter, expected instance of WebGLRenderTarget.' ); - return false; + } + + if ( renderTarget.isWebGLCubeRenderTarget || renderTarget.isWebGL3DRenderTarget || renderTarget.isWebGLArrayRenderTarget ) { + + throw Error( 'EXRExporter.parse: Unsupported render target type, expected instance of WebGLRenderTarget.' ); } - if ( ! renderTarget || ! renderTarget.isWebGLRenderTarget ) { + if ( renderTarget.texture.type !== FloatType && renderTarget.texture.type !== HalfFloatType ) { + + throw Error( 'EXRExporter.parse: Unsupported WebGLRenderTarget texture type.' ); - console.error( 'EXRExporter.parse: Unsupported second parameter, expected instance of WebGLRenderTarget.' ); + } + + if ( renderTarget.texture.format !== RGBAFormat ) { - return false; + throw Error( 'EXRExporter.parse: Unsupported WebGLRenderTarget texture format, expected RGBAFormat.' ); } - if ( renderTarget.texture.type !== FloatType && renderTarget.texture.type !== HalfFloatType ) { +} - console.error( 'EXRExporter.parse: Unsupported WebGLRenderTarget texture type.' ); +function supportedDT( texture ) { - return false; + if ( texture.type !== FloatType && texture.type !== HalfFloatType ) { + + throw Error( 'EXRExporter.parse: Unsupported DataTexture texture type.' ); } - if ( renderTarget.texture.format !== RGBAFormat ) { + if ( texture.format !== RGBAFormat ) { + + throw Error( 'EXRExporter.parse: Unsupported DataTexture texture format, expected RGBAFormat.' ); + + } - console.error( 'EXRExporter.parse: Unsupported WebGLRenderTarget texture format, expected RGBAFormat.' ); + if ( ! texture.image.data ) { - return false; + throw Error( 'EXRExporter.parse: Invalid DataTexture image data.' ); } + if ( texture.type === FloatType && texture.image.data.constructor.name !== 'Float32Array' ) { + + throw Error( 'EXRExporter.parse: DataTexture image data doesn\'t match type, expected \'Float32Array\'.' ); + + } + + if ( texture.type === HalfFloatType && texture.image.data.constructor.name !== 'Uint16Array' ) { + + throw Error( 'EXRExporter.parse: DataTexture image data doesn\'t match type, expected \'Uint16Array\'.' ); - return true; + } } -function buildInfo( renderTarget, options = {} ) { +function buildInfoRTT( renderTarget, options = {} ) { const compressionSizes = { 0: 1, @@ -87,7 +156,6 @@ function buildInfo( renderTarget, options = {} ) { HEIGHT = renderTarget.height, TYPE = renderTarget.texture.type, FORMAT = renderTarget.texture.format, - COLOR_SPACE = renderTarget.texture.colorSpace, COMPRESSION = ( options.compression !== undefined ) ? options.compression : ZIP_COMPRESSION, EXPORTER_TYPE = ( options.type !== undefined ) ? options.type : HalfFloatType, OUT_TYPE = ( EXPORTER_TYPE === FloatType ) ? 2 : 1, @@ -99,7 +167,6 @@ function buildInfo( renderTarget, options = {} ) { height: HEIGHT, type: TYPE, format: FORMAT, - colorSpace: COLOR_SPACE, compression: COMPRESSION, blockLines: COMPRESSION_SIZE, dataType: OUT_TYPE, @@ -111,22 +178,64 @@ function buildInfo( renderTarget, options = {} ) { } -function getPixelData( renderer, rtt, info ) { +function buildInfoDT( texture, options = {} ) { + + const compressionSizes = { + 0: 1, + 2: 1, + 3: 16 + }; + + const WIDTH = texture.image.width, + HEIGHT = texture.image.height, + TYPE = texture.type, + FORMAT = texture.format, + COMPRESSION = ( options.compression !== undefined ) ? options.compression : ZIP_COMPRESSION, + EXPORTER_TYPE = ( options.type !== undefined ) ? options.type : HalfFloatType, + OUT_TYPE = ( EXPORTER_TYPE === FloatType ) ? 2 : 1, + COMPRESSION_SIZE = compressionSizes[ COMPRESSION ], + NUM_CHANNELS = 4; + + return { + width: WIDTH, + height: HEIGHT, + type: TYPE, + format: FORMAT, + compression: COMPRESSION, + blockLines: COMPRESSION_SIZE, + dataType: OUT_TYPE, + dataSize: 2 * OUT_TYPE, + numBlocks: Math.ceil( HEIGHT / COMPRESSION_SIZE ), + numInputChannels: 4, + numOutputChannels: NUM_CHANNELS, + }; + +} + +async function getPixelData( renderer, rtt, info ) { let dataBuffer; - if ( info.type === FloatType ) { + if ( renderer.isWebGLRenderer ) { - dataBuffer = new Float32Array( info.width * info.height * info.numInputChannels ); + if ( info.type === FloatType ) { + + dataBuffer = new Float32Array( info.width * info.height * info.numInputChannels ); + + } else { + + dataBuffer = new Uint16Array( info.width * info.height * info.numInputChannels ); + + } + + await renderer.readRenderTargetPixelsAsync( rtt, 0, 0, info.width, info.height, dataBuffer ); } else { - dataBuffer = new Uint16Array( info.width * info.height * info.numInputChannels ); + dataBuffer = await renderer.readRenderTargetPixelsAsync( rtt, 0, 0, info.width, info.height ); } - renderer.readRenderTargetPixels( rtt, 0, 0, info.width, info.height, dataBuffer ); - return dataBuffer; } @@ -498,4 +607,12 @@ function getFloat32( arr, i ) { } +/** + * Export options of `EXRExporter`. + * + * @typedef {Object} EXRExporter~Options + * @property {(HalfFloatType|FloatType)} [type=HalfFloatType] - Output data type. + * @property {(NO_COMPRESSION|ZIP_COMPRESSION|ZIPS_COMPRESSION)} [type=ZIP_COMPRESSION] - The compression algorithm. + **/ + export { EXRExporter, NO_COMPRESSION, ZIP_COMPRESSION, ZIPS_COMPRESSION }; diff --git a/examples/jsm/exporters/GLTFExporter.js b/examples/jsm/exporters/GLTFExporter.js index 64de28f49f9e97..b7500a602743ce 100644 --- a/examples/jsm/exporters/GLTFExporter.js +++ b/examples/jsm/exporters/GLTFExporter.js @@ -21,10 +21,13 @@ import { Scene, Source, SRGBColorSpace, - Vector3 + CompressedTexture, + Vector3, + Quaternion, + REVISION, + ImageUtils } from 'three'; - /** * The KHR_mesh_quantization extension allows these extra attribute component types * @@ -59,11 +62,59 @@ const KHR_mesh_quantization_ExtraAttrTypes = { ], }; - +/** + * An exporter for `glTF` 2.0. + * + * glTF (GL Transmission Format) is an [open format specification]{@link https://github.com/KhronosGroup/glTF/tree/master/specification/2.0} + * for efficient delivery and loading of 3D content. Assets may be provided either in JSON (.gltf) + * or binary (.glb) format. External files store textures (.jpg, .png) and additional binary + * data (.bin). A glTF asset may deliver one or more scenes, including meshes, materials, + * textures, skins, skeletons, morph targets, animations, lights, and/or cameras. + * + * GLTFExporter supports the [glTF 2.0 extensions]{@link https://github.com/KhronosGroup/glTF/tree/master/extensions/}: + * + * - KHR_lights_punctual + * - KHR_materials_clearcoat + * - KHR_materials_dispersion + * - KHR_materials_emissive_strength + * - KHR_materials_ior + * - KHR_materials_iridescence + * - KHR_materials_specular + * - KHR_materials_sheen + * - KHR_materials_transmission + * - KHR_materials_unlit + * - KHR_materials_volume + * - KHR_mesh_quantization + * - KHR_texture_transform + * - EXT_materials_bump + * - EXT_mesh_gpu_instancing + * + * The following glTF 2.0 extension is supported by an external user plugin: + * + * - [KHR_materials_variants]{@link https://github.com/takahirox/three-gltf-extensions} + * + * ```js + * const exporter = new GLTFExporter(); + * const data = await exporter.parseAsync( scene, options ); + * ``` + * + * @three_import import { GLTFExporter } from 'three/addons/exporters/GLTFExporter.js'; + */ class GLTFExporter { + /** + * Constructs a new glTF exporter. + */ constructor() { + /** + * A reference to a texture utils module. + * + * @type {?(WebGLTextureUtils|WebGPUTextureUtils)} + * @default null + */ + this.textureUtils = null; + this.pluginCallbacks = []; this.register( function ( writer ) { @@ -108,6 +159,12 @@ class GLTFExporter { } ); + this.register( function ( writer ) { + + return new GLTFMaterialsDispersionExtension( writer ); + + } ); + this.register( function ( writer ) { return new GLTFMaterialsIridescenceExtension( writer ); @@ -132,8 +189,28 @@ class GLTFExporter { } ); + this.register( function ( writer ) { + + return new GLTFMaterialsBumpExtension( writer ); + + } ); + + this.register( function ( writer ) { + + return new GLTFMeshGpuInstancing( writer ); + + } ); + } + /** + * Registers a plugin callback. This API is internally used to implement the various + * glTF extensions but can also used by third-party code to add additional logic + * to the exporter. + * + * @param {function(writer:GLTFWriter)} callback - The callback function to register. + * @return {GLTFExporter} A reference to this exporter. + */ register( callback ) { if ( this.pluginCallbacks.indexOf( callback ) === - 1 ) { @@ -146,6 +223,12 @@ class GLTFExporter { } + /** + * Unregisters a plugin callback. + * + * @param {Function} callback - The callback function to unregister. + * @return {GLTFExporter} A reference to this exporter. + */ unregister( callback ) { if ( this.pluginCallbacks.indexOf( callback ) !== - 1 ) { @@ -159,11 +242,29 @@ class GLTFExporter { } /** - * Parse scenes and generate GLTF output - * @param {Scene or [THREE.Scenes]} input Scene or Array of THREE.Scenes - * @param {Function} onDone Callback on completed - * @param {Function} onError Callback on errors - * @param {Object} options options + * Sets the texture utils for this exporter. Only relevant when compressed textures have to be exported. + * + * Depending on whether you use {@link WebGLRenderer} or {@link WebGPURenderer}, you must inject the + * corresponding texture utils {@link WebGLTextureUtils} or {@link WebGPUTextureUtils}. + * + * @param {WebGLTextureUtils|WebGPUTextureUtils} utils - The texture utils. + * @return {GLTFExporter} A reference to this exporter. + */ + setTextureUtils( utils ) { + + this.textureUtils = utils; + + return this; + + } + + /** + * Parses the given scenes and generates the glTF output. + * + * @param {Scene|Array} input - A scene or an array of scenes. + * @param {GLTFExporter~OnDone} onDone - A callback function that is executed when the export has finished. + * @param {GLTFExporter~OnError} onError - A callback function that is executed when an error happens. + * @param {GLTFExporter~Options} options - options */ parse( input, onDone, onError, options ) { @@ -177,10 +278,18 @@ class GLTFExporter { } writer.setPlugins( plugins ); - writer.write( input, onDone, options ).catch( onError ); + writer.setTextureUtils( this.textureUtils ); + writer.writeAsync( input, onDone, options ).catch( onError ); } + /** + * Async version of {@link GLTFExporter#parse}. + * + * @param {Scene|Array} input - A scene or an array of scenes. + * @param {GLTFExporter~Options} options - options. + * @return {Promise} A Promise that resolved with the exported glTF data. + */ parseAsync( input, options ) { const scope = this; @@ -272,9 +381,11 @@ const GLB_CHUNK_TYPE_BIN = 0x004E4942; /** * Compare two arrays - * @param {Array} array1 Array 1 to compare - * @param {Array} array2 Array 2 to compare - * @return {Boolean} Returns true if both arrays are equal + * + * @private + * @param {Array} array1 Array 1 to compare + * @param {Array} array2 Array 2 to compare + * @return {boolean} Returns true if both arrays are equal */ function equalArray( array1, array2 ) { @@ -288,7 +399,9 @@ function equalArray( array1, array2 ) { /** * Converts a string to an ArrayBuffer. - * @param {string} text + * + * @private + * @param {string} text * @return {ArrayBuffer} */ function stringToArrayBuffer( text ) { @@ -300,8 +413,9 @@ function stringToArrayBuffer( text ) { /** * Is identity matrix * + * @private * @param {Matrix4} matrix - * @returns {Boolean} Returns true, if parameter is identity matrix + * @returns {boolean} Returns true, if parameter is identity matrix */ function isIdentityMatrix( matrix ) { @@ -311,9 +425,11 @@ function isIdentityMatrix( matrix ) { /** * Get the min and max vectors from the given attribute - * @param {BufferAttribute} attribute Attribute to find the min/max in range from start to start + count - * @param {Integer} start - * @param {Integer} count + * + * @private + * @param {BufferAttribute} attribute Attribute to find the min/max in range from start to start + count + * @param {number} start Start index + * @param {number} count Range to cover * @return {Object} Object containing the `min` and `max` values (As an array of attribute.itemSize components) */ function getMinMax( attribute, start, count ) { @@ -367,8 +483,9 @@ function getMinMax( attribute, start, count ) { * Get the required size + padding for a buffer, rounded to the next 4-byte boundary. * https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#data-alignment * - * @param {Integer} bufferSize The size the original buffer. - * @returns {Integer} new buffer size with required padding. + * @private + * @param {number} bufferSize The size the original buffer. Should be an integer. + * @returns {number} new buffer size with required padding as an integer. * */ function getPaddedBufferSize( bufferSize ) { @@ -380,8 +497,9 @@ function getPaddedBufferSize( bufferSize ) { /** * Returns a buffer aligned to 4-byte boundary. * + * @private * @param {ArrayBuffer} arrayBuffer Buffer to pad - * @param {Integer} paddingByte (Optional) + * @param {number} [paddingByte=0] Should be an integer * @returns {ArrayBuffer} The same buffer if it's already aligned to 4-byte boundary or a new buffer */ function getPaddedArrayBuffer( arrayBuffer, paddingByte = 0 ) { @@ -456,6 +574,8 @@ function getToBlobPromise( canvas, mimeType ) { /** * Writer + * + * @private */ class GLTFWriter { @@ -481,7 +601,7 @@ class GLTFWriter { this.json = { asset: { version: '2.0', - generator: 'THREE.GLTFExporter' + generator: 'THREE.GLTFExporter r' + REVISION } }; @@ -494,6 +614,8 @@ class GLTFWriter { images: new Map() }; + this.textureUtils = null; + } setPlugins( plugins ) { @@ -502,13 +624,20 @@ class GLTFWriter { } + setTextureUtils( utils ) { + + this.textureUtils = utils; + + } + /** * Parse scenes and generate GLTF output - * @param {Scene or [THREE.Scenes]} input Scene or Array of THREE.Scenes - * @param {Function} onDone Callback on completed - * @param {Object} options options + * + * @param {Scene|Array} input Scene or Array of THREE.Scenes + * @param {Function} onDone Callback on completed + * @param {Object} options options */ - async write( input, onDone, options = {} ) { + async writeAsync( input, onDone, options = {} ) { this.options = Object.assign( { // default options @@ -527,7 +656,7 @@ class GLTFWriter { } - this.processInput( input ); + await this.processInputAsync( input ); await Promise.all( this.pending ); @@ -670,8 +799,10 @@ class GLTFWriter { /** * Returns ids for buffer attributes. - * @param {Object} object - * @return {Integer} + * + * @param {Object} attribute + * @param {boolean} [isRelativeCopy=false] + * @return {number} An integer */ getUID( attribute, isRelativeCopy = false ) { @@ -696,7 +827,7 @@ class GLTFWriter { * Checks if normal attribute values are normalized. * * @param {BufferAttribute} normal - * @returns {Boolean} + * @returns {boolean} */ isNormalizedNormalAttribute( normal ) { @@ -801,7 +932,7 @@ class GLTFWriter { } - buildMetalRoughTexture( metalnessMap, roughnessMap ) { + async buildMetalRoughTextureAsync( metalnessMap, roughnessMap ) { if ( metalnessMap === roughnessMap ) return metalnessMap; @@ -825,7 +956,17 @@ class GLTFWriter { } - console.warn( 'THREE.GLTFExporter: Merged metalnessMap and roughnessMap textures.' ); + if ( metalnessMap instanceof CompressedTexture ) { + + metalnessMap = await this.decompressTextureAsync( metalnessMap ); + + } + + if ( roughnessMap instanceof CompressedTexture ) { + + roughnessMap = await this.decompressTextureAsync( roughnessMap ); + + } const metalness = metalnessMap ? metalnessMap.image : null; const roughness = roughnessMap ? roughnessMap.image : null; @@ -837,7 +978,9 @@ class GLTFWriter { canvas.width = width; canvas.height = height; - const context = canvas.getContext( '2d' ); + const context = canvas.getContext( '2d', { + willReadFrequently: true, + } ); context.fillStyle = '#00ffff'; context.fillRect( 0, 0, width, height ); @@ -891,14 +1034,29 @@ class GLTFWriter { } + console.warn( 'THREE.GLTFExporter: Merged metalnessMap and roughnessMap textures.' ); + return texture; } + + async decompressTextureAsync( texture, maxTextureSize = Infinity ) { + + if ( this.textureUtils === null ) { + + throw new Error( 'THREE.GLTFExporter: setTextureUtils() must be called to process compressed textures.' ); + + } + + return await this.textureUtils.decompress( texture, maxTextureSize ); + + } + /** * Process a buffer to append to the default one. - * @param {ArrayBuffer} buffer - * @return {Integer} + * @param {ArrayBuffer} buffer + * @return {0} */ processBuffer( buffer ) { @@ -916,11 +1074,11 @@ class GLTFWriter { /** * Process and generate a BufferView - * @param {BufferAttribute} attribute - * @param {number} componentType - * @param {number} start - * @param {number} count - * @param {number} target (Optional) Target usage of the BufferView + * @param {BufferAttribute} attribute + * @param {number} componentType + * @param {number} start + * @param {number} count + * @param {number} [target] Target usage of the BufferView * @return {Object} */ processBufferView( attribute, componentType, start, count, target ) { @@ -955,7 +1113,17 @@ class GLTFWriter { } - const byteLength = getPaddedBufferSize( count * attribute.itemSize * componentSize ); + let byteStride = attribute.itemSize * componentSize; + + if ( target === WEBGL_CONSTANTS.ARRAY_BUFFER ) { + + // Each element of a vertex attribute MUST be aligned to 4-byte boundaries + // inside a bufferView + byteStride = Math.ceil( byteStride / 4 ) * 4; + + } + + const byteLength = getPaddedBufferSize( count * byteStride ); const dataView = new DataView( new ArrayBuffer( byteLength ) ); let offset = 0; @@ -1020,6 +1188,12 @@ class GLTFWriter { } + if ( ( offset % byteStride ) !== 0 ) { + + offset += byteStride - ( offset % byteStride ); + + } + } const bufferViewDef = { @@ -1035,7 +1209,7 @@ class GLTFWriter { if ( target === WEBGL_CONSTANTS.ARRAY_BUFFER ) { // Only define byteStride for vertex attributes. - bufferViewDef.byteStride = attribute.itemSize * componentSize; + bufferViewDef.byteStride = byteStride; } @@ -1058,7 +1232,7 @@ class GLTFWriter { /** * Process and generate a BufferView from an image Blob. * @param {Blob} blob - * @return {Promise} + * @return {Promise} An integer */ processBufferViewImage( blob ) { @@ -1092,11 +1266,11 @@ class GLTFWriter { /** * Process attribute to generate an accessor - * @param {BufferAttribute} attribute Attribute to process - * @param {THREE.BufferGeometry} geometry (Optional) Geometry used for truncated draw range - * @param {Integer} start (Optional) - * @param {Integer} count (Optional) - * @return {Integer|null} Index of the processed accessor on the "accessors" array + * @param {BufferAttribute} attribute Attribute to process + * @param {?BufferGeometry} [geometry] Geometry used for truncated draw range + * @param {number} [start=0] + * @param {number} [count=Infinity] + * @return {?number} Index of the processed accessor on the "accessors" array */ processAccessor( attribute, geometry, start, count ) { @@ -1146,12 +1320,12 @@ class GLTFWriter { } else { - throw new Error( 'THREE.GLTFExporter: Unsupported bufferAttribute component type.' ); + throw new Error( 'THREE.GLTFExporter: Unsupported bufferAttribute component type: ' + attribute.array.constructor.name ); } if ( start === undefined ) start = 0; - if ( count === undefined ) count = attribute.count; + if ( count === undefined || count === Infinity ) count = attribute.count; // Skip creating an accessor if the attribute doesn't have data to export if ( count === 0 ) return null; @@ -1190,11 +1364,11 @@ class GLTFWriter { /** * Process image - * @param {Image} image to process - * @param {Integer} format of the image (RGBAFormat) - * @param {Boolean} flipY before writing out the image - * @param {String} mimeType export format - * @return {Integer} Index of the processed texture in the "images" array + * @param {Image} image to process + * @param {number} format Identifier of the format (RGBAFormat) + * @param {boolean} flipY before writing out the image + * @param {string} mimeType export format + * @return {number} Index of the processed texture in the "images" array */ processImage( image, format, flipY, mimeType = 'image/png' ) { @@ -1223,7 +1397,9 @@ class GLTFWriter { canvas.width = Math.min( image.width, options.maxTextureSize ); canvas.height = Math.min( image.height, options.maxTextureSize ); - const ctx = canvas.getContext( '2d' ); + const ctx = canvas.getContext( '2d', { + willReadFrequently: true, + } ); if ( flipY === true ) { @@ -1236,7 +1412,7 @@ class GLTFWriter { if ( format !== RGBAFormat ) { - console.error( 'GLTFExporter: Only RGBAFormat is supported.' ); + console.error( 'GLTFExporter: Only RGBAFormat is supported.', format ); } @@ -1261,7 +1437,18 @@ class GLTFWriter { } else { - ctx.drawImage( image, 0, 0, canvas.width, canvas.height ); + if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || + ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) || + ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) || + ( typeof OffscreenCanvas !== 'undefined' && image instanceof OffscreenCanvas ) ) { + + ctx.drawImage( image, 0, 0, canvas.width, canvas.height ); + + } else { + + throw new Error( 'THREE.GLTFExporter: Invalid image type. Use HTMLImageElement, HTMLCanvasElement, ImageBitmap or OffscreenCanvas.' ); + + } } @@ -1281,25 +1468,7 @@ class GLTFWriter { } else { - if ( canvas.toDataURL !== undefined ) { - - imageDef.uri = canvas.toDataURL( mimeType ); - - } else { - - pending.push( - - getToBlobPromise( canvas, mimeType ) - .then( blob => new FileReader().readAsDataURL( blob ) ) - .then( dataURL => { - - imageDef.uri = dataURL; - - } ) - - ); - - } + imageDef.uri = ImageUtils.getDataURL( canvas, mimeType ); } @@ -1317,8 +1486,8 @@ class GLTFWriter { /** * Process sampler - * @param {Texture} map Texture to process - * @return {Integer} Index of the processed texture in the "samplers" array + * @param {Texture} map Texture to process + * @return {number} Index of the processed texture in the "samplers" array */ processSampler( map ) { @@ -1339,11 +1508,13 @@ class GLTFWriter { /** * Process texture - * @param {Texture} map Map to process - * @return {Integer} Index of the processed texture in the "textures" array + * @param {Texture} map Map to process + * @return {Promise} Index of the processed texture in the "textures" array */ - processTexture( map ) { + async processTextureAsync( map ) { + const writer = this; + const options = writer.options; const cache = this.cache; const json = this.json; @@ -1351,6 +1522,13 @@ class GLTFWriter { if ( ! json.textures ) json.textures = []; + // make non-readable textures (e.g. CompressedTexture) readable by blitting them into a new texture + if ( map instanceof CompressedTexture ) { + + map = await this.decompressTextureAsync( map, options.maxTextureSize ); + + } + let mimeType = map.userData.mimeType; if ( mimeType === 'image/webp' ) mimeType = 'image/png'; @@ -1362,9 +1540,9 @@ class GLTFWriter { if ( map.name ) textureDef.name = map.name; - this._invokeAll( function ( ext ) { + await this._invokeAllAsync( async function ( ext ) { - ext.writeTexture && ext.writeTexture( map, textureDef ); + ext.writeTexture && await ext.writeTexture( map, textureDef ); } ); @@ -1376,10 +1554,10 @@ class GLTFWriter { /** * Process material - * @param {THREE.Material} material Material to process - * @return {Integer|null} Index of the processed material in the "materials" array + * @param {THREE.Material} material Material to process + * @return {Promise} Index of the processed material in the "materials" array */ - processMaterial( material ) { + async processMaterialAsync( material ) { const cache = this.cache; const json = this.json; @@ -1420,19 +1598,19 @@ class GLTFWriter { } else { - materialDef.pbrMetallicRoughness.metallicFactor = 0.5; - materialDef.pbrMetallicRoughness.roughnessFactor = 0.5; + materialDef.pbrMetallicRoughness.metallicFactor = 0; + materialDef.pbrMetallicRoughness.roughnessFactor = 1; } // pbrMetallicRoughness.metallicRoughnessTexture if ( material.metalnessMap || material.roughnessMap ) { - const metalRoughTexture = this.buildMetalRoughTexture( material.metalnessMap, material.roughnessMap ); + const metalRoughTexture = await this.buildMetalRoughTextureAsync( material.metalnessMap, material.roughnessMap ); const metalRoughMapDef = { - index: this.processTexture( metalRoughTexture ), - channel: metalRoughTexture.channel + index: await this.processTextureAsync( metalRoughTexture ), + texCoord: metalRoughTexture.channel }; this.applyTextureTransform( metalRoughMapDef, metalRoughTexture ); materialDef.pbrMetallicRoughness.metallicRoughnessTexture = metalRoughMapDef; @@ -1443,7 +1621,7 @@ class GLTFWriter { if ( material.map ) { const baseColorMapDef = { - index: this.processTexture( material.map ), + index: await this.processTextureAsync( material.map ), texCoord: material.map.channel }; this.applyTextureTransform( baseColorMapDef, material.map ); @@ -1466,7 +1644,7 @@ class GLTFWriter { if ( material.emissiveMap ) { const emissiveMapDef = { - index: this.processTexture( material.emissiveMap ), + index: await this.processTextureAsync( material.emissiveMap ), texCoord: material.emissiveMap.channel }; this.applyTextureTransform( emissiveMapDef, material.emissiveMap ); @@ -1480,7 +1658,7 @@ class GLTFWriter { if ( material.normalMap ) { const normalMapDef = { - index: this.processTexture( material.normalMap ), + index: await this.processTextureAsync( material.normalMap ), texCoord: material.normalMap.channel }; @@ -1501,7 +1679,7 @@ class GLTFWriter { if ( material.aoMap ) { const occlusionMapDef = { - index: this.processTexture( material.aoMap ), + index: await this.processTextureAsync( material.aoMap ), texCoord: material.aoMap.channel }; @@ -1538,9 +1716,9 @@ class GLTFWriter { this.serializeUserData( material, materialDef ); - this._invokeAll( function ( ext ) { + await this._invokeAllAsync( async function ( ext ) { - ext.writeMaterial && ext.writeMaterial( material, materialDef ); + ext.writeMaterialAsync && await ext.writeMaterialAsync( material, materialDef ); } ); @@ -1552,10 +1730,10 @@ class GLTFWriter { /** * Process mesh - * @param {THREE.Mesh} mesh Mesh to process - * @return {Integer|null} Index of the processed mesh in the "meshes" array + * @param {THREE.Mesh} mesh Mesh to process + * @return {Promise} Index of the processed mesh in the "meshes" array */ - processMesh( mesh ) { + async processMeshAsync( mesh ) { const cache = this.cache; const json = this.json; @@ -1616,6 +1794,8 @@ class GLTFWriter { const nameConversion = { uv: 'TEXCOORD_0', uv1: 'TEXCOORD_1', + uv2: 'TEXCOORD_2', + uv3: 'TEXCOORD_3', color: 'COLOR_0', skinWeight: 'WEIGHTS_0', skinIndex: 'JOINTS_0' @@ -1657,7 +1837,9 @@ class GLTFWriter { } - // JOINTS_0 must be UNSIGNED_BYTE or UNSIGNED_SHORT. + // Enforce glTF vertex attribute requirements: + // - JOINTS_0 must be UNSIGNED_BYTE or UNSIGNED_SHORT + // - Only custom attributes may be INT or UNSIGNED_INT modifiedAttribute = null; const array = attribute.array; @@ -1668,6 +1850,11 @@ class GLTFWriter { console.warn( 'GLTFExporter: Attribute "skinIndex" converted to type UNSIGNED_SHORT.' ); modifiedAttribute = new BufferAttribute( new Uint16Array( array ), attribute.itemSize, attribute.normalized ); + } else if ( ( array instanceof Uint32Array || array instanceof Int32Array ) && ! attributeName.startsWith( '_' ) ) { + + console.warn( `GLTFExporter: Attribute "${ attributeName }" converted to type FLOAT.` ); + modifiedAttribute = GLTFExporter.Utils.toFloat32BufferAttribute( attribute ); + } const accessor = this.processAccessor( modifiedAttribute || attribute, geometry ); @@ -1797,6 +1984,24 @@ class GLTFWriter { if ( isMultiMaterial && geometry.groups.length === 0 ) return null; + let didForceIndices = false; + + if ( isMultiMaterial && geometry.index === null ) { + + const indices = []; + + for ( let i = 0, il = geometry.attributes.position.count; i < il; i ++ ) { + + indices[ i ] = i; + + } + + geometry.setIndex( indices ); + + didForceIndices = true; + + } + const materials = isMultiMaterial ? mesh.material : [ mesh.material ]; const groups = isMultiMaterial ? geometry.groups : [ { materialIndex: 0, start: undefined, count: undefined } ]; @@ -1836,7 +2041,7 @@ class GLTFWriter { } - const material = this.processMaterial( materials[ groups[ i ].materialIndex ] ); + const material = await this.processMaterialAsync( materials[ groups[ i ].materialIndex ] ); if ( material !== null ) primitive.material = material; @@ -1844,11 +2049,17 @@ class GLTFWriter { } + if ( didForceIndices === true ) { + + geometry.setIndex( null ); + + } + meshDef.primitives = primitives; if ( ! json.meshes ) json.meshes = []; - this._invokeAll( function ( ext ) { + await this._invokeAllAsync( function ( ext ) { ext.writeMesh && ext.writeMesh( mesh, meshDef ); @@ -1924,8 +2135,8 @@ class GLTFWriter { /** * Process camera - * @param {THREE.Camera} camera Camera to process - * @return {Integer} Index of the processed mesh in the "camera" array + * @param {THREE.Camera} camera Camera to process + * @return {number} Index of the processed mesh in the "camera" array */ processCamera( camera ) { @@ -2013,7 +2224,7 @@ class GLTFWriter { if ( ! trackNode || ! trackProperty ) { console.warn( 'THREE.GLTFExporter: Could not export animation track "%s".', track.name ); - return null; + continue; } @@ -2125,10 +2336,10 @@ class GLTFWriter { /** * Process Object3D node - * @param {THREE.Object3D} node Object3D to processNode - * @return {Integer} Index of the node in the nodes list + * @param {THREE.Object3D} object Object3D to processNodeAsync + * @return {Promise} Index of the node in the nodes list */ - processNode( object ) { + async processNodeAsync( object ) { const json = this.json; const options = this.options; @@ -2185,7 +2396,7 @@ class GLTFWriter { if ( object.isMesh || object.isLine || object.isPoints ) { - const meshIndex = this.processMesh( object ); + const meshIndex = await this.processMeshAsync( object ); if ( meshIndex !== null ) nodeDef.mesh = meshIndex; @@ -2197,6 +2408,9 @@ class GLTFWriter { if ( object.isSkinnedMesh ) this.skins.push( object ); + const nodeIndex = json.nodes.push( nodeDef ) - 1; + nodeMap.set( object, nodeIndex ); + if ( object.children.length > 0 ) { const children = []; @@ -2207,9 +2421,9 @@ class GLTFWriter { if ( child.visible || options.onlyVisible === false ) { - const nodeIndex = this.processNode( child ); + const childNodeIndex = await this.processNodeAsync( child ); - if ( nodeIndex !== null ) children.push( nodeIndex ); + if ( childNodeIndex !== null ) children.push( childNodeIndex ); } @@ -2219,23 +2433,21 @@ class GLTFWriter { } - this._invokeAll( function ( ext ) { + await this._invokeAllAsync( function ( ext ) { ext.writeNode && ext.writeNode( object, nodeDef ); } ); - const nodeIndex = json.nodes.push( nodeDef ) - 1; - nodeMap.set( object, nodeIndex ); return nodeIndex; } /** * Process Scene - * @param {Scene} node Scene to process + * @param {Scene} scene Scene to process */ - processScene( scene ) { + async processSceneAsync( scene ) { const json = this.json; const options = this.options; @@ -2261,7 +2473,7 @@ class GLTFWriter { if ( child.visible || options.onlyVisible === false ) { - const nodeIndex = this.processNode( child ); + const nodeIndex = await this.processNodeAsync( child ); if ( nodeIndex !== null ) nodes.push( nodeIndex ); @@ -2277,9 +2489,9 @@ class GLTFWriter { /** * Creates a Scene to hold a list of objects and parse it - * @param {Array} objects List of objects to process + * @param {Array} objects List of objects to process */ - processObjects( objects ) { + async processObjectsAsync( objects ) { const scene = new Scene(); scene.name = 'AuxScene'; @@ -2292,20 +2504,20 @@ class GLTFWriter { } - this.processScene( scene ); + await this.processSceneAsync( scene ); } /** * @param {THREE.Object3D|Array} input */ - processInput( input ) { + async processInputAsync( input ) { const options = this.options; input = input instanceof Array ? input : [ input ]; - this._invokeAll( function ( ext ) { + await this._invokeAllAsync( function ( ext ) { ext.beforeParse && ext.beforeParse( input ); @@ -2317,7 +2529,7 @@ class GLTFWriter { if ( input[ i ] instanceof Scene ) { - this.processScene( input[ i ] ); + await this.processSceneAsync( input[ i ] ); } else { @@ -2327,7 +2539,11 @@ class GLTFWriter { } - if ( objectsWithoutScene.length > 0 ) this.processObjects( objectsWithoutScene ); + if ( objectsWithoutScene.length > 0 ) { + + await this.processObjectsAsync( objectsWithoutScene ); + + } for ( let i = 0; i < this.skins.length; ++ i ) { @@ -2341,7 +2557,7 @@ class GLTFWriter { } - this._invokeAll( function ( ext ) { + await this._invokeAllAsync( function ( ext ) { ext.afterParse && ext.afterParse( input ); @@ -2349,11 +2565,11 @@ class GLTFWriter { } - _invokeAll( func ) { + async _invokeAllAsync( func ) { for ( let i = 0, il = this.plugins.length; i < il; i ++ ) { - func( this.plugins[ i ] ); + await func( this.plugins[ i ] ); } @@ -2365,6 +2581,8 @@ class GLTFWriter { * Punctual Lights Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual + * + * @private */ class GLTFLightExtension { @@ -2415,7 +2633,7 @@ class GLTFLightExtension { if ( light.distance > 0 ) lightDef.range = light.distance; lightDef.spot = {}; - lightDef.spot.innerConeAngle = ( light.penumbra - 1.0 ) * light.angle * - 1.0; + lightDef.spot.innerConeAngle = ( 1.0 - light.penumbra ) * light.angle; lightDef.spot.outerConeAngle = light.angle; } @@ -2460,6 +2678,8 @@ class GLTFLightExtension { * Unlit Materials Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit + * + * @private */ class GLTFMaterialsUnlitExtension { @@ -2470,7 +2690,7 @@ class GLTFMaterialsUnlitExtension { } - writeMaterial( material, materialDef ) { + async writeMaterialAsync( material, materialDef ) { if ( ! material.isMeshBasicMaterial ) return; @@ -2493,6 +2713,8 @@ class GLTFMaterialsUnlitExtension { * Clearcoat Materials Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_clearcoat + * + * @private */ class GLTFMaterialsClearcoatExtension { @@ -2503,7 +2725,7 @@ class GLTFMaterialsClearcoatExtension { } - writeMaterial( material, materialDef ) { + async writeMaterialAsync( material, materialDef ) { if ( ! material.isMeshPhysicalMaterial || material.clearcoat === 0 ) return; @@ -2517,7 +2739,7 @@ class GLTFMaterialsClearcoatExtension { if ( material.clearcoatMap ) { const clearcoatMapDef = { - index: writer.processTexture( material.clearcoatMap ), + index: await writer.processTextureAsync( material.clearcoatMap ), texCoord: material.clearcoatMap.channel }; writer.applyTextureTransform( clearcoatMapDef, material.clearcoatMap ); @@ -2530,7 +2752,7 @@ class GLTFMaterialsClearcoatExtension { if ( material.clearcoatRoughnessMap ) { const clearcoatRoughnessMapDef = { - index: writer.processTexture( material.clearcoatRoughnessMap ), + index: await writer.processTextureAsync( material.clearcoatRoughnessMap ), texCoord: material.clearcoatRoughnessMap.channel }; writer.applyTextureTransform( clearcoatRoughnessMapDef, material.clearcoatRoughnessMap ); @@ -2541,9 +2763,12 @@ class GLTFMaterialsClearcoatExtension { if ( material.clearcoatNormalMap ) { const clearcoatNormalMapDef = { - index: writer.processTexture( material.clearcoatNormalMap ), + index: await writer.processTextureAsync( material.clearcoatNormalMap ), texCoord: material.clearcoatNormalMap.channel }; + + if ( material.clearcoatNormalScale.x !== 1 ) clearcoatNormalMapDef.scale = material.clearcoatNormalScale.x; + writer.applyTextureTransform( clearcoatNormalMapDef, material.clearcoatNormalMap ); extensionDef.clearcoatNormalTexture = clearcoatNormalMapDef; @@ -2559,10 +2784,48 @@ class GLTFMaterialsClearcoatExtension { } +/** + * Materials dispersion Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_dispersion + * + * @private + */ +class GLTFMaterialsDispersionExtension { + + constructor( writer ) { + + this.writer = writer; + this.name = 'KHR_materials_dispersion'; + + } + + async writeMaterialAsync( material, materialDef ) { + + if ( ! material.isMeshPhysicalMaterial || material.dispersion === 0 ) return; + + const writer = this.writer; + const extensionsUsed = writer.extensionsUsed; + + const extensionDef = {}; + + extensionDef.dispersion = material.dispersion; + + materialDef.extensions = materialDef.extensions || {}; + materialDef.extensions[ this.name ] = extensionDef; + + extensionsUsed[ this.name ] = true; + + } + +} + /** * Iridescence Materials Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_iridescence + * + * @private */ class GLTFMaterialsIridescenceExtension { @@ -2573,7 +2836,7 @@ class GLTFMaterialsIridescenceExtension { } - writeMaterial( material, materialDef ) { + async writeMaterialAsync( material, materialDef ) { if ( ! material.isMeshPhysicalMaterial || material.iridescence === 0 ) return; @@ -2587,7 +2850,7 @@ class GLTFMaterialsIridescenceExtension { if ( material.iridescenceMap ) { const iridescenceMapDef = { - index: writer.processTexture( material.iridescenceMap ), + index: await writer.processTextureAsync( material.iridescenceMap ), texCoord: material.iridescenceMap.channel }; writer.applyTextureTransform( iridescenceMapDef, material.iridescenceMap ); @@ -2602,7 +2865,7 @@ class GLTFMaterialsIridescenceExtension { if ( material.iridescenceThicknessMap ) { const iridescenceThicknessMapDef = { - index: writer.processTexture( material.iridescenceThicknessMap ), + index: await writer.processTextureAsync( material.iridescenceThicknessMap ), texCoord: material.iridescenceThicknessMap.channel }; writer.applyTextureTransform( iridescenceThicknessMapDef, material.iridescenceThicknessMap ); @@ -2623,6 +2886,8 @@ class GLTFMaterialsIridescenceExtension { * Transmission Materials Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_transmission + * + * @private */ class GLTFMaterialsTransmissionExtension { @@ -2633,7 +2898,7 @@ class GLTFMaterialsTransmissionExtension { } - writeMaterial( material, materialDef ) { + async writeMaterialAsync( material, materialDef ) { if ( ! material.isMeshPhysicalMaterial || material.transmission === 0 ) return; @@ -2647,7 +2912,7 @@ class GLTFMaterialsTransmissionExtension { if ( material.transmissionMap ) { const transmissionMapDef = { - index: writer.processTexture( material.transmissionMap ), + index: await writer.processTextureAsync( material.transmissionMap ), texCoord: material.transmissionMap.channel }; writer.applyTextureTransform( transmissionMapDef, material.transmissionMap ); @@ -2668,6 +2933,8 @@ class GLTFMaterialsTransmissionExtension { * Materials Volume Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_volume + * + * @private */ class GLTFMaterialsVolumeExtension { @@ -2678,7 +2945,7 @@ class GLTFMaterialsVolumeExtension { } - writeMaterial( material, materialDef ) { + async writeMaterialAsync( material, materialDef ) { if ( ! material.isMeshPhysicalMaterial || material.transmission === 0 ) return; @@ -2692,7 +2959,7 @@ class GLTFMaterialsVolumeExtension { if ( material.thicknessMap ) { const thicknessMapDef = { - index: writer.processTexture( material.thicknessMap ), + index: await writer.processTextureAsync( material.thicknessMap ), texCoord: material.thicknessMap.channel }; writer.applyTextureTransform( thicknessMapDef, material.thicknessMap ); @@ -2700,7 +2967,12 @@ class GLTFMaterialsVolumeExtension { } - extensionDef.attenuationDistance = material.attenuationDistance; + if ( material.attenuationDistance !== Infinity ) { + + extensionDef.attenuationDistance = material.attenuationDistance; + + } + extensionDef.attenuationColor = material.attenuationColor.toArray(); materialDef.extensions = materialDef.extensions || {}; @@ -2716,6 +2988,8 @@ class GLTFMaterialsVolumeExtension { * Materials ior Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_ior + * + * @private */ class GLTFMaterialsIorExtension { @@ -2726,7 +3000,7 @@ class GLTFMaterialsIorExtension { } - writeMaterial( material, materialDef ) { + async writeMaterialAsync( material, materialDef ) { if ( ! material.isMeshPhysicalMaterial || material.ior === 1.5 ) return; @@ -2750,6 +3024,8 @@ class GLTFMaterialsIorExtension { * Materials specular Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_specular + * + * @private */ class GLTFMaterialsSpecularExtension { @@ -2760,11 +3036,11 @@ class GLTFMaterialsSpecularExtension { } - writeMaterial( material, materialDef ) { + async writeMaterialAsync( material, materialDef ) { if ( ! material.isMeshPhysicalMaterial || ( material.specularIntensity === 1.0 && material.specularColor.equals( DEFAULT_SPECULAR_COLOR ) && - ! material.specularIntensityMap && ! material.specularColorTexture ) ) return; + ! material.specularIntensityMap && ! material.specularColorMap ) ) return; const writer = this.writer; const extensionsUsed = writer.extensionsUsed; @@ -2774,7 +3050,7 @@ class GLTFMaterialsSpecularExtension { if ( material.specularIntensityMap ) { const specularIntensityMapDef = { - index: writer.processTexture( material.specularIntensityMap ), + index: await writer.processTextureAsync( material.specularIntensityMap ), texCoord: material.specularIntensityMap.channel }; writer.applyTextureTransform( specularIntensityMapDef, material.specularIntensityMap ); @@ -2785,7 +3061,7 @@ class GLTFMaterialsSpecularExtension { if ( material.specularColorMap ) { const specularColorMapDef = { - index: writer.processTexture( material.specularColorMap ), + index: await writer.processTextureAsync( material.specularColorMap ), texCoord: material.specularColorMap.channel }; writer.applyTextureTransform( specularColorMapDef, material.specularColorMap ); @@ -2809,6 +3085,8 @@ class GLTFMaterialsSpecularExtension { * Sheen Materials Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_sheen + * + * @private */ class GLTFMaterialsSheenExtension { @@ -2819,7 +3097,7 @@ class GLTFMaterialsSheenExtension { } - writeMaterial( material, materialDef ) { + async writeMaterialAsync( material, materialDef ) { if ( ! material.isMeshPhysicalMaterial || material.sheen == 0.0 ) return; @@ -2831,7 +3109,7 @@ class GLTFMaterialsSheenExtension { if ( material.sheenRoughnessMap ) { const sheenRoughnessMapDef = { - index: writer.processTexture( material.sheenRoughnessMap ), + index: await writer.processTextureAsync( material.sheenRoughnessMap ), texCoord: material.sheenRoughnessMap.channel }; writer.applyTextureTransform( sheenRoughnessMapDef, material.sheenRoughnessMap ); @@ -2842,7 +3120,7 @@ class GLTFMaterialsSheenExtension { if ( material.sheenColorMap ) { const sheenColorMapDef = { - index: writer.processTexture( material.sheenColorMap ), + index: await writer.processTextureAsync( material.sheenColorMap ), texCoord: material.sheenColorMap.channel }; writer.applyTextureTransform( sheenColorMapDef, material.sheenColorMap ); @@ -2866,6 +3144,8 @@ class GLTFMaterialsSheenExtension { * Anisotropy Materials Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_anisotropy + * + * @private */ class GLTFMaterialsAnisotropyExtension { @@ -2876,7 +3156,7 @@ class GLTFMaterialsAnisotropyExtension { } - writeMaterial( material, materialDef ) { + async writeMaterialAsync( material, materialDef ) { if ( ! material.isMeshPhysicalMaterial || material.anisotropy == 0.0 ) return; @@ -2887,7 +3167,7 @@ class GLTFMaterialsAnisotropyExtension { if ( material.anisotropyMap ) { - const anisotropyMapDef = { index: writer.processTexture( material.anisotropyMap ) }; + const anisotropyMapDef = { index: await writer.processTextureAsync( material.anisotropyMap ) }; writer.applyTextureTransform( anisotropyMapDef, material.anisotropyMap ); extensionDef.anisotropyTexture = anisotropyMapDef; @@ -2909,6 +3189,8 @@ class GLTFMaterialsAnisotropyExtension { * Materials Emissive Strength Extension * * Specification: https://github.com/KhronosGroup/glTF/blob/5768b3ce0ef32bc39cdf1bef10b948586635ead3/extensions/2.0/Khronos/KHR_materials_emissive_strength/README.md + * + * @private */ class GLTFMaterialsEmissiveStrengthExtension { @@ -2919,7 +3201,7 @@ class GLTFMaterialsEmissiveStrengthExtension { } - writeMaterial( material, materialDef ) { + async writeMaterialAsync( material, materialDef ) { if ( ! material.isMeshStandardMaterial || material.emissiveIntensity === 1.0 ) return; @@ -2939,8 +3221,123 @@ class GLTFMaterialsEmissiveStrengthExtension { } + +/** + * Materials bump Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/EXT_materials_bump + * + * @private + */ +class GLTFMaterialsBumpExtension { + + constructor( writer ) { + + this.writer = writer; + this.name = 'EXT_materials_bump'; + + } + + async writeMaterialAsync( material, materialDef ) { + + if ( ! material.isMeshStandardMaterial || ( + material.bumpScale === 1 && + ! material.bumpMap ) ) return; + + const writer = this.writer; + const extensionsUsed = writer.extensionsUsed; + + const extensionDef = {}; + + if ( material.bumpMap ) { + + const bumpMapDef = { + index: await writer.processTextureAsync( material.bumpMap ), + texCoord: material.bumpMap.channel + }; + writer.applyTextureTransform( bumpMapDef, material.bumpMap ); + extensionDef.bumpTexture = bumpMapDef; + + } + + extensionDef.bumpFactor = material.bumpScale; + + materialDef.extensions = materialDef.extensions || {}; + materialDef.extensions[ this.name ] = extensionDef; + + extensionsUsed[ this.name ] = true; + + } + +} + +/** + * GPU Instancing Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_mesh_gpu_instancing + * + * @private + */ +class GLTFMeshGpuInstancing { + + constructor( writer ) { + + this.writer = writer; + this.name = 'EXT_mesh_gpu_instancing'; + + } + + writeNode( object, nodeDef ) { + + if ( ! object.isInstancedMesh ) return; + + const writer = this.writer; + + const mesh = object; + + const translationAttr = new Float32Array( mesh.count * 3 ); + const rotationAttr = new Float32Array( mesh.count * 4 ); + const scaleAttr = new Float32Array( mesh.count * 3 ); + + const matrix = new Matrix4(); + const position = new Vector3(); + const quaternion = new Quaternion(); + const scale = new Vector3(); + + for ( let i = 0; i < mesh.count; i ++ ) { + + mesh.getMatrixAt( i, matrix ); + matrix.decompose( position, quaternion, scale ); + + position.toArray( translationAttr, i * 3 ); + quaternion.toArray( rotationAttr, i * 4 ); + scale.toArray( scaleAttr, i * 3 ); + + } + + const attributes = { + TRANSLATION: writer.processAccessor( new BufferAttribute( translationAttr, 3 ) ), + ROTATION: writer.processAccessor( new BufferAttribute( rotationAttr, 4 ) ), + SCALE: writer.processAccessor( new BufferAttribute( scaleAttr, 3 ) ), + }; + + if ( mesh.instanceColor ) + attributes._COLOR_0 = writer.processAccessor( mesh.instanceColor ); + + nodeDef.extensions = nodeDef.extensions || {}; + nodeDef.extensions[ this.name ] = { attributes }; + + writer.extensionsUsed[ this.name ] = true; + writer.extensionsRequired[ this.name ] = true; + + } + +} + /** * Static utility functions + * + * @private */ GLTFExporter.Utils = { @@ -3131,8 +3528,60 @@ GLTFExporter.Utils = { return clip; + }, + + toFloat32BufferAttribute: function ( srcAttribute ) { + + const dstAttribute = new BufferAttribute( new Float32Array( srcAttribute.count * srcAttribute.itemSize ), srcAttribute.itemSize, false ); + + if ( ! srcAttribute.normalized && ! srcAttribute.isInterleavedBufferAttribute ) { + + dstAttribute.array.set( srcAttribute.array ); + + return dstAttribute; + + } + + for ( let i = 0, il = srcAttribute.count; i < il; i ++ ) { + + for ( let j = 0; j < srcAttribute.itemSize; j ++ ) { + + dstAttribute.setComponent( i, j, srcAttribute.getComponent( i, j ) ); + + } + + } + + return dstAttribute; + } }; +/** + * Export options of `GLTFExporter`. + * + * @typedef {Object} GLTFExporter~Options + * @property {boolean} [trs=false] - Export position, rotation and scale instead of matrix per node. + * @property {boolean} [onlyVisible=true] - Export only visible 3D objects. + * @property {boolean} [binary=false] - Export in binary (.glb) format, returning an ArrayBuffer. + * @property {number} [maxTextureSize=Infinity] - Restricts the image maximum size (both width and height) to the given value. + * @property {Array} [animations=[]] - List of animations to be included in the export. + * @property {boolean} [includeCustomExtensions=false] - Export custom glTF extensions defined on an object's `userData.gltfExtensions` property. + **/ + +/** + * onDone callback of `GLTFExporter`. + * + * @callback GLTFExporter~OnDone + * @param {ArrayBuffer|string} result - The generated .gltf (JSON) or .glb (binary). + */ + +/** + * onError callback of `GLTFExporter`. + * + * @callback GLTFExporter~OnError + * @param {Error} error - The error object. + */ + export { GLTFExporter }; diff --git a/examples/jsm/exporters/KTX2Exporter.js b/examples/jsm/exporters/KTX2Exporter.js index bfc24d8dda08b2..27961fde827b6a 100644 --- a/examples/jsm/exporters/KTX2Exporter.js +++ b/examples/jsm/exporters/KTX2Exporter.js @@ -1,4 +1,5 @@ import { + ColorManagement, FloatType, HalfFloatType, UnsignedByteType, @@ -10,6 +11,7 @@ import { NoColorSpace, LinearSRGBColorSpace, SRGBColorSpace, + SRGBTransfer, DataTexture, REVISION, } from 'three'; @@ -43,6 +45,13 @@ import { VK_FORMAT_R8G8B8A8_UNORM, } from '../libs/ktx-parse.module.js'; +/** + * References: + * - https://github.khronos.org/KTX-Specification/ktxspec.v2.html + * - https://registry.khronos.org/DataFormat/specs/1.3/dataformat.1.3.html + * - https://github.com/donmccurdy/KTX-Parse + */ + const VK_FORMAT_MAP = { [ RGBAFormat ]: { @@ -95,12 +104,21 @@ const VK_FORMAT_MAP = { }; -const KHR_DF_CHANNEL_MAP = { +const KHR_DF_CHANNEL_MAP = [ - 0: KHR_DF_CHANNEL_RGBSDA_RED, - 1: KHR_DF_CHANNEL_RGBSDA_GREEN, - 2: KHR_DF_CHANNEL_RGBSDA_BLUE, - 3: KHR_DF_CHANNEL_RGBSDA_ALPHA, + KHR_DF_CHANNEL_RGBSDA_RED, + KHR_DF_CHANNEL_RGBSDA_GREEN, + KHR_DF_CHANNEL_RGBSDA_BLUE, + KHR_DF_CHANNEL_RGBSDA_ALPHA, + +]; + +// TODO: sampleLower and sampleUpper may change based on color space. +const KHR_DF_CHANNEL_SAMPLE_LOWER_UPPER = { + + [ FloatType ]: [ 0xbf800000, 0x3f800000 ], + [ HalfFloatType ]: [ 0xbf800000, 0x3f800000 ], + [ UnsignedByteType ]: [ 0, 255 ], }; @@ -109,9 +127,31 @@ const ERROR_FORMAT = 'THREE.KTX2Exporter: Supported formats are RGBAFormat, RGFo const ERROR_TYPE = 'THREE.KTX2Exporter: Supported types are FloatType, HalfFloatType, or UnsignedByteType."'; const ERROR_COLOR_SPACE = 'THREE.KTX2Exporter: Supported color spaces are SRGBColorSpace (UnsignedByteType only), LinearSRGBColorSpace, or NoColorSpace.'; +/** + * An exporter for KTX2. + * + * ```js + * const exporter = new KTX2Exporter(); + * const result = await exporter.parse( dataTexture ); + * ``` + * + * @three_import import { KTX2Exporter } from 'three/addons/exporters/KTX2Exporter.js'; + */ export class KTX2Exporter { - parse( arg1, arg2 ) { + /** + * This method has two variants. + * + * - When exporting a data texture, it receives one parameter. The data or 3D data texture. + * - When exporting a render target (e.g. a PMREM), it receives two parameters. The renderer and the + * render target. + * + * @async + * @param {(DataTexture|Data3DTexture|WebGPURenderer|WebGLRenderer)} arg1 - The data texture to export or a renderer. + * @param {RenderTarget} [arg2] - The render target that should be exported + * @return {Promise} A Promise that resolves with the exported KTX2. + */ + async parse( arg1, arg2 ) { let texture; @@ -119,9 +159,9 @@ export class KTX2Exporter { texture = arg1; - } else if ( arg1.isWebGLRenderer && arg2.isWebGLRenderTarget ) { + } else if ( ( arg1.isWebGLRenderer || arg1.isWebGPURenderer ) && arg2.isRenderTarget ) { - texture = toDataTexture( arg1, arg2 ); + texture = await toDataTexture( arg1, arg2 ); } else { @@ -172,7 +212,7 @@ export class KTX2Exporter { basicDesc.colorPrimaries = texture.colorSpace === NoColorSpace ? KHR_DF_PRIMARIES_UNSPECIFIED : KHR_DF_PRIMARIES_BT709; - basicDesc.transferFunction = texture.colorSpace === SRGBColorSpace + basicDesc.transferFunction = ColorManagement.getTransfer( texture.colorSpace ) === SRGBTransfer ? KHR_DF_TRANSFER_SRGB : KHR_DF_TRANSFER_LINEAR; @@ -188,7 +228,8 @@ export class KTX2Exporter { let channelType = KHR_DF_CHANNEL_MAP[ i ]; - if ( texture.colorSpace === LinearSRGBColorSpace || texture.colorSpace === NoColorSpace ) { + // Assign KHR_DF_SAMPLE_DATATYPE_LINEAR if the channel is linear _and_ differs from the transfer function. + if ( channelType === KHR_DF_CHANNEL_RGBSDA_ALPHA && basicDesc.transferFunction !== KHR_DF_TRANSFER_LINEAR ) { channelType |= KHR_DF_SAMPLE_DATATYPE_LINEAR; @@ -204,11 +245,11 @@ export class KTX2Exporter { basicDesc.samples.push( { channelType: channelType, - bitOffset: i * array.BYTES_PER_ELEMENT, + bitOffset: i * array.BYTES_PER_ELEMENT * 8, bitLength: array.BYTES_PER_ELEMENT * 8 - 1, samplePosition: [ 0, 0, 0, 0 ], - sampleLower: texture.type === UnsignedByteType ? 0 : - 1, - sampleUpper: texture.type === UnsignedByteType ? 255 : 1, + sampleLower: KHR_DF_CHANNEL_SAMPLE_LOWER_UPPER[ texture.type ][ 0 ], + sampleUpper: KHR_DF_CHANNEL_SAMPLE_LOWER_UPPER[ texture.type ][ 1 ], } ); @@ -235,33 +276,45 @@ export class KTX2Exporter { } -function toDataTexture( renderer, rtt ) { +async function toDataTexture( renderer, rtt ) { const channelCount = getChannelCount( rtt.texture ); let view; - if ( rtt.texture.type === FloatType ) { + if ( renderer.isWebGLRenderer ) { + + if ( rtt.texture.type === FloatType ) { - view = new Float32Array( rtt.width * rtt.height * channelCount ); + view = new Float32Array( rtt.width * rtt.height * channelCount ); - } else if ( rtt.texture.type === HalfFloatType ) { + } else if ( rtt.texture.type === HalfFloatType ) { - view = new Uint16Array( rtt.width * rtt.height * channelCount ); + view = new Uint16Array( rtt.width * rtt.height * channelCount ); - } else if ( rtt.texture.type === UnsignedByteType ) { + } else if ( rtt.texture.type === UnsignedByteType ) { - view = new Uint8Array( rtt.width * rtt.height * channelCount ); + view = new Uint8Array( rtt.width * rtt.height * channelCount ); + + } else { + + throw new Error( ERROR_TYPE ); + + } + + await renderer.readRenderTargetPixelsAsync( rtt, 0, 0, rtt.width, rtt.height, view ); } else { - throw new Error( ERROR_TYPE ); + view = await renderer.readRenderTargetPixelsAsync( rtt, 0, 0, rtt.width, rtt.height ); } - renderer.readRenderTargetPixels( rtt, 0, 0, rtt.width, rtt.height, view ); + const texture = new DataTexture( view, rtt.width, rtt.height, rtt.texture.format, rtt.texture.type ); + + texture.colorSpace = rtt.texture.colorSpace; - return new DataTexture( view, rtt.width, rtt.height, rtt.texture.format, rtt.texture.type ); + return texture; } diff --git a/examples/jsm/exporters/MMDExporter.js b/examples/jsm/exporters/MMDExporter.js deleted file mode 100644 index bcef712e81b1b4..00000000000000 --- a/examples/jsm/exporters/MMDExporter.js +++ /dev/null @@ -1,217 +0,0 @@ -import { - Matrix4, - Quaternion, - Vector3 -} from 'three'; -import { MMDParser } from '../libs/mmdparser.module.js'; - -/** - * Dependencies - * - mmd-parser https://github.com/takahirox/mmd-parser - */ - -class MMDExporter { - - /* TODO: implement - // mesh -> pmd - this.parsePmd = function ( object ) { - - }; - */ - - /* TODO: implement - // mesh -> pmx - this.parsePmx = function ( object ) { - - }; - */ - - /* TODO: implement - // animation + skeleton -> vmd - this.parseVmd = function ( object ) { - - }; - */ - - /* - * skeleton -> vpd - * Returns Shift_JIS encoded Uint8Array. Otherwise return strings. - */ - parseVpd( skin, outputShiftJis, useOriginalBones ) { - - if ( skin.isSkinnedMesh !== true ) { - - console.warn( 'THREE.MMDExporter: parseVpd() requires SkinnedMesh instance.' ); - return null; - - } - - function toStringsFromNumber( num ) { - - if ( Math.abs( num ) < 1e-6 ) num = 0; - - let a = num.toString(); - - if ( a.indexOf( '.' ) === - 1 ) { - - a += '.'; - - } - - a += '000000'; - - const index = a.indexOf( '.' ); - - const d = a.slice( 0, index ); - const p = a.slice( index + 1, index + 7 ); - - return d + '.' + p; - - } - - function toStringsFromArray( array ) { - - const a = []; - - for ( let i = 0, il = array.length; i < il; i ++ ) { - - a.push( toStringsFromNumber( array[ i ] ) ); - - } - - return a.join( ',' ); - - } - - skin.updateMatrixWorld( true ); - - const bones = skin.skeleton.bones; - const bones2 = getBindBones( skin ); - - const position = new Vector3(); - const quaternion = new Quaternion(); - const quaternion2 = new Quaternion(); - const matrix = new Matrix4(); - - const array = []; - array.push( 'Vocaloid Pose Data file' ); - array.push( '' ); - array.push( ( skin.name !== '' ? skin.name.replace( /\s/g, '_' ) : 'skin' ) + '.osm;' ); - array.push( bones.length + ';' ); - array.push( '' ); - - for ( let i = 0, il = bones.length; i < il; i ++ ) { - - const bone = bones[ i ]; - const bone2 = bones2[ i ]; - - /* - * use the bone matrix saved before solving IK. - * see CCDIKSolver for the detail. - */ - if ( useOriginalBones === true && - bone.userData.ik !== undefined && - bone.userData.ik.originalMatrix !== undefined ) { - - matrix.fromArray( bone.userData.ik.originalMatrix ); - - } else { - - matrix.copy( bone.matrix ); - - } - - position.setFromMatrixPosition( matrix ); - quaternion.setFromRotationMatrix( matrix ); - - const pArray = position.sub( bone2.position ).toArray(); - const qArray = quaternion2.copy( bone2.quaternion ).conjugate().multiply( quaternion ).toArray(); - - // right to left - pArray[ 2 ] = - pArray[ 2 ]; - qArray[ 0 ] = - qArray[ 0 ]; - qArray[ 1 ] = - qArray[ 1 ]; - - array.push( 'Bone' + i + '{' + bone.name ); - array.push( ' ' + toStringsFromArray( pArray ) + ';' ); - array.push( ' ' + toStringsFromArray( qArray ) + ';' ); - array.push( '}' ); - array.push( '' ); - - } - - array.push( '' ); - - const lines = array.join( '\n' ); - - return ( outputShiftJis === true ) ? unicodeToShiftjis( lines ) : lines; - - } - -} - -// Unicode to Shift_JIS table -let u2sTable; - -function unicodeToShiftjis( str ) { - - if ( u2sTable === undefined ) { - - const encoder = new MMDParser.CharsetEncoder(); - const table = encoder.s2uTable; - u2sTable = {}; - - const keys = Object.keys( table ); - - for ( let i = 0, il = keys.length; i < il; i ++ ) { - - let key = keys[ i ]; - - const value = table[ key ]; - key = parseInt( key ); - - u2sTable[ value ] = key; - - } - - } - - const array = []; - - for ( let i = 0, il = str.length; i < il; i ++ ) { - - const code = str.charCodeAt( i ); - - const value = u2sTable[ code ]; - - if ( value === undefined ) { - - throw new Error( 'cannot convert charcode 0x' + code.toString( 16 ) ); - - } else if ( value > 0xff ) { - - array.push( ( value >> 8 ) & 0xff ); - array.push( value & 0xff ); - - } else { - - array.push( value & 0xff ); - - } - - } - - return new Uint8Array( array ); - -} - -function getBindBones( skin ) { - - // any more efficient ways? - const poseSkin = skin.clone(); - poseSkin.pose(); - return poseSkin.skeleton.bones; - -} - -export { MMDExporter }; diff --git a/examples/jsm/exporters/OBJExporter.js b/examples/jsm/exporters/OBJExporter.js index 63a483e2b7c2e7..9960b903bdc9e8 100644 --- a/examples/jsm/exporters/OBJExporter.js +++ b/examples/jsm/exporters/OBJExporter.js @@ -1,12 +1,34 @@ import { Color, + ColorManagement, Matrix3, + SRGBColorSpace, Vector2, Vector3 } from 'three'; +/** + * An exporter for OBJ. + * + * `OBJExporter` is not able to export material data into MTL files so only geometry data are supported. + * + * ```js + * const exporter = new OBJExporter(); + * const data = exporter.parse( scene ); + * ``` + * + * @three_import import { OBJExporter } from 'three/addons/exporters/OBJExporter.js'; + */ class OBJExporter { + /** + * Parses the given 3D object and generates the OBJ output. + * + * If the 3D object is composed of multiple children and geometry, they are merged into a single mesh in the file. + * + * @param {Object3D} object - The 3D object to export. + * @return {string} The exported OBJ. + */ parse( object ) { let output = ''; @@ -226,7 +248,9 @@ class OBJExporter { if ( colors !== undefined ) { - color.fromBufferAttribute( colors, i ).convertLinearToSRGB(); + color.fromBufferAttribute( colors, i ); + + ColorManagement.workingToColorSpace( color, SRGBColorSpace ); output += ' ' + color.r + ' ' + color.g + ' ' + color.b; diff --git a/examples/jsm/exporters/PLYExporter.js b/examples/jsm/exporters/PLYExporter.js index 2e46a8c45b5358..d5a2fb6ac6aa2a 100644 --- a/examples/jsm/exporters/PLYExporter.js +++ b/examples/jsm/exporters/PLYExporter.js @@ -1,26 +1,42 @@ import { Matrix3, Vector3, - Color + Color, + ColorManagement, + SRGBColorSpace } from 'three'; /** - * https://github.com/gkjohnson/ply-exporter-js + * An exporter for PLY. * - * Usage: - * const exporter = new PLYExporter(); + * PLY (Polygon or Stanford Triangle Format) is a file format for efficient delivery and + * loading of simple, static 3D content in a dense format. Both binary and ascii formats are + * supported. PLY can store vertex positions, colors, normals and uv coordinates. No textures + * or texture references are saved. * - * // second argument is a list of options - * exporter.parse(mesh, data => console.log(data), { binary: true, excludeAttributes: [ 'color' ], littleEndian: true }); + * ```js + * const exporter = new PLYExporter(); + * const data = exporter.parse( scene, options ); + * ``` * - * Format Definition: - * http://paulbourke.net/dataformats/ply/ + * @three_import import { PLYExporter } from 'three/addons/exporters/PLYExporter.js'; */ - class PLYExporter { + /** + * Parses the given 3D object and generates the PLY output. + * + * If the 3D object is composed of multiple children and geometry, they are merged into a single mesh in the file. + * + * @param {Object3D} object - The 3D object to export. + * @param {PLYExporter~OnDone} onDone - A callback function that is executed when the export has finished. + * @param {PLYExporter~Options} options - The export options. + * @return {?string|ArrayBuffer} The exported PLY. + */ parse( object, onDone, options = {} ) { + // reference https://github.com/gkjohnson/ply-exporter-js + // Iterate over the valid meshes in the object function traverseMeshes( cb ) { @@ -122,7 +138,7 @@ class PLYExporter { if ( includeIndices && faceCount !== Math.floor( faceCount ) ) { // point cloud meshes will not have an index array and may not have a - // number of vertices that is divisble by 3 (and therefore representable + // number of vertices that is divisible by 3 (and therefore representable // as triangles) console.error( @@ -204,7 +220,7 @@ class PLYExporter { // 2 uv values at 4 bytes const vertexListLength = vertexCount * ( 4 * 3 + ( includeNormals ? 4 * 3 : 0 ) + ( includeColors ? 3 : 0 ) + ( includeUVs ? 4 * 2 : 0 ) ); - // 1 byte shape desciptor + // 1 byte shape descriptor // 3 vertex indices at ${indexByteCount} bytes const faceListLength = includeIndices ? faceCount * ( indexByteCount * 3 + 1 ) : 0; const output = new DataView( new ArrayBuffer( headerBin.length + vertexListLength + faceListLength ) ); @@ -302,9 +318,9 @@ class PLYExporter { if ( colors != null ) { - tempColor - .fromBufferAttribute( colors, i ) - .convertLinearToSRGB(); + tempColor.fromBufferAttribute( colors, i ); + + ColorManagement.workingToColorSpace( tempColor, SRGBColorSpace ); output.setUint8( vOffset, Math.floor( tempColor.r * 255 ) ); vOffset += 1; @@ -461,9 +477,9 @@ class PLYExporter { if ( colors != null ) { - tempColor - .fromBufferAttribute( colors, i ) - .convertLinearToSRGB(); + tempColor.fromBufferAttribute( colors, i ); + + ColorManagement.workingToColorSpace( tempColor, SRGBColorSpace ); line += ' ' + Math.floor( tempColor.r * 255 ) + ' ' + @@ -525,4 +541,22 @@ class PLYExporter { } +/** + * Export options of `PLYExporter`. + * + * @typedef {Object} PLYExporter~Options + * @property {boolean} [binary=false] - Whether to export in binary format or ASCII. + * @property {Array} [excludeAttributes] - Which properties to explicitly exclude from + * the exported PLY file. Valid values are `'color'`, `'normal'`, `'uv'`, and `'index'`. If triangle + * indices are excluded, then a point cloud is exported. + * @property {boolean} [littleEndian=false] - Whether the binary export uses little or big endian. + **/ + +/** + * onDone callback of `PLYExporter`. + * + * @callback PLYExporter~OnDone + * @param {string|ArrayBuffer} result - The generated PLY ascii or binary. + */ + export { PLYExporter }; diff --git a/examples/jsm/exporters/STLExporter.js b/examples/jsm/exporters/STLExporter.js index a97b4082d51826..7408c96efa650b 100644 --- a/examples/jsm/exporters/STLExporter.js +++ b/examples/jsm/exporters/STLExporter.js @@ -1,16 +1,31 @@ import { Vector3 } from 'three'; /** - * Usage: - * const exporter = new STLExporter(); + * An exporter for STL. * - * // second argument is a list of options - * const data = exporter.parse( mesh, { binary: true } ); + * STL files describe only the surface geometry of a three-dimensional object without + * any representation of color, texture or other common model attributes. The STL format + * specifies both ASCII and binary representations, with binary being more compact. + * STL files contain no scale information or indexes, and the units are arbitrary. * + * ```js + * const exporter = new STLExporter(); + * const data = exporter.parse( mesh, { binary: true } ); + * ``` + * + * @three_import import { STLExporter } from 'three/addons/exporters/STLExporter.js'; */ - class STLExporter { + /** + * Parses the given 3D object and generates the STL output. + * + * If the 3D object is composed of multiple children and geometry, they are merged into a single mesh in the file. + * + * @param {Object3D} scene - A scene, mesh or any other 3D object containing meshes to encode. + * @param {STLExporter~Options} options - The export options. + * @return {string|ArrayBuffer} The exported STL. + */ parse( scene, options = {} ) { options = Object.assign( { @@ -196,4 +211,11 @@ class STLExporter { } +/** + * Export options of `STLExporter`. + * + * @typedef {Object} STLExporter~Options + * @property {boolean} [binary=false] - Whether to export in binary format or ASCII. + **/ + export { STLExporter }; diff --git a/examples/jsm/exporters/USDZExporter.js b/examples/jsm/exporters/USDZExporter.js index 6429cb102879b4..ef5a1fcbe339f4 100644 --- a/examples/jsm/exporters/USDZExporter.js +++ b/examples/jsm/exporters/USDZExporter.js @@ -1,16 +1,87 @@ -import * as THREE from 'three'; -import * as fflate from '../libs/fflate.module.js'; - +import { + NoColorSpace, + DoubleSide, + Color, +} from 'three'; + +import { + strToU8, + zipSync, +} from '../libs/fflate.module.js'; + +/** + * An exporter for USDZ. + * + * ```js + * const exporter = new USDZExporter(); + * const arraybuffer = await exporter.parseAsync( scene ); + * ``` + * + * @three_import import { USDZExporter } from 'three/addons/exporters/USDZExporter.js'; + */ class USDZExporter { - async parse( scene, options = {} ) { + /** + * Constructs a new USDZ exporter. + */ + constructor() { + + /** + * A reference to a texture utils module. + * + * @type {?(WebGLTextureUtils|WebGPUTextureUtils)} + * @default null + */ + this.textureUtils = null; + + } + + /** + * Sets the texture utils for this exporter. Only relevant when compressed textures have to be exported. + * + * Depending on whether you use {@link WebGLRenderer} or {@link WebGPURenderer}, you must inject the + * corresponding texture utils {@link WebGLTextureUtils} or {@link WebGPUTextureUtils}. + * + * @param {WebGLTextureUtils|WebGPUTextureUtils} utils - The texture utils. + */ + setTextureUtils( utils ) { + + this.textureUtils = utils; + + } + + /** + * Parse the given 3D object and generates the USDZ output. + * + * @param {Object3D} scene - The 3D object to export. + * @param {USDZExporter~OnDone} onDone - A callback function that is executed when the export has finished. + * @param {USDZExporter~OnError} onError - A callback function that is executed when an error happens. + * @param {USDZExporter~Options} options - The export options. + */ + parse( scene, onDone, onError, options ) { + + this.parseAsync( scene, options ).then( onDone ).catch( onError ); + + } + + /** + * Async version of {@link USDZExporter#parse}. + * + * @async + * @param {Object3D} scene - The 3D object to export. + * @param {USDZExporter~Options} options - The export options. + * @return {Promise} A Promise that resolved with the exported USDZ data. + */ + async parseAsync( scene, options = {} ) { options = Object.assign( { ar: { anchoring: { type: 'plane' }, planeAnchoring: { alignment: 'horizontal' } }, + includeAnchoringProperties: true, quickLookCompatible: false, + maxTextureSize: 1024, }, options ); const files = {}; @@ -50,7 +121,7 @@ class USDZExporter { } - output += buildXform( object, geometry, material ); + output += buildXform( object, geometry, materials[ material.uuid ] ); } else { @@ -71,14 +142,28 @@ class USDZExporter { output += buildMaterials( materials, textures, options.quickLookCompatible ); - files[ modelFileName ] = fflate.strToU8( output ); + files[ modelFileName ] = strToU8( output ); output = null; for ( const id in textures ) { - const texture = textures[ id ]; + let texture = textures[ id ]; + + if ( texture.isCompressedTexture === true ) { + + if ( this.textureUtils === null ) { + + throw new Error( 'THREE.USDZExporter: setTextureUtils() must be called to process compressed textures.' ); - const canvas = imageToCanvas( texture.image, texture.flipY ); + } else { + + texture = await this.textureUtils.decompress( texture ); + + } + + } + + const canvas = imageToCanvas( texture.image, texture.flipY, options.maxTextureSize ); const blob = await new Promise( resolve => canvas.toBlob( resolve, 'image/png', 1 ) ); files[ `textures/Texture_${ id }.png` ] = new Uint8Array( await blob.arrayBuffer() ); @@ -112,20 +197,20 @@ class USDZExporter { } - return fflate.zipSync( files, { level: 0 } ); + return zipSync( files, { level: 0 } ); } } -function imageToCanvas( image, flipY ) { +function imageToCanvas( image, flipY, maxTextureSize ) { if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) || ( typeof OffscreenCanvas !== 'undefined' && image instanceof OffscreenCanvas ) || ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) { - const scale = 1024 / Math.max( image.width, image.height ); + const scale = maxTextureSize / Math.max( image.width, image.height ); const canvas = document.createElement( 'canvas' ); canvas.width = image.width * Math.min( 1, scale ); @@ -165,6 +250,7 @@ function buildHeader() { customLayerData = { string creator = "Three.js USDZExporter" } + defaultPrim = "Root" metersPerUnit = 1 upAxis = "Y" ) @@ -175,6 +261,10 @@ function buildHeader() { function buildSceneStart( options ) { + const alignment = options.includeAnchoringProperties === true ? ` + token preliminary:anchoring:type = "${options.ar.anchoring.type}" + token preliminary:planeAnchoring:alignment = "${options.ar.planeAnchoring.alignment}" + ` : ''; return `def Xform "Root" { def Scope "Scenes" ( @@ -188,10 +278,7 @@ function buildSceneStart( options ) { } sceneName = "Scene" ) - { - token preliminary:anchoring:type = "${options.ar.anchoring.type}" - token preliminary:planeAnchoring:alignment = "${options.ar.planeAnchoring.alignment}" - + {${alignment} `; } @@ -211,7 +298,7 @@ function buildUSDFileAsString( dataToInsert ) { let output = buildHeader(); output += dataToInsert; - return fflate.strToU8( output ); + return strToU8( output ); } @@ -228,16 +315,16 @@ function buildXform( object, geometry, material ) { } - return `def Xform "${ name }" ( - prepend references = @./geometries/Geometry_${ geometry.id }.usda@ - prepend apiSchemas = ["MaterialBindingAPI"] -) -{ - matrix4d xformOp:transform = ${ transform } - uniform token[] xformOpOrder = ["xformOp:transform"] + return ` def Xform "${ name }" ( + prepend references = @./geometries/Geometry_${ geometry.id }.usda@ + prepend apiSchemas = ["MaterialBindingAPI"] + ) + { + matrix4d xformOp:transform = ${ transform } + uniform token[] xformOpOrder = ["xformOp:transform"] - rel material:binding = -} + rel material:binding = + } `; @@ -286,7 +373,7 @@ function buildMesh( geometry ) { interpolation = "vertex" ) point3f[] points = [${ buildVector3Array( attributes.position, count )}] -${ buildPrimvars( attributes, count ) } +${ buildPrimvars( attributes ) } uniform token subdivisionScheme = "none" } `; @@ -355,14 +442,7 @@ function buildVector3Array( attribute, count ) { } -function buildVector2Array( attribute, count ) { - - if ( attribute === undefined ) { - - console.warn( 'USDZExporter: UVs missing.' ); - return Array( count ).fill( '(0, 0)' ).join( ', ' ); - - } +function buildVector2Array( attribute ) { const array = []; @@ -379,7 +459,7 @@ function buildVector2Array( attribute, count ) { } -function buildPrimvars( attributes, count ) { +function buildPrimvars( attributes ) { let string = ''; @@ -391,7 +471,7 @@ function buildPrimvars( attributes, count ) { if ( attribute !== undefined ) { string += ` - texCoord2f[] primvars:st${ id } = [${ buildVector2Array( attribute, count )}] ( + texCoord2f[] primvars:st${ id } = [${ buildVector2Array( attribute )}] ( interpolation = "vertex" )`; @@ -399,6 +479,21 @@ function buildPrimvars( attributes, count ) { } + // vertex colors + + const colorAttribute = attributes.color; + + if ( colorAttribute !== undefined ) { + + const count = colorAttribute.count; + + string += ` + color3f[] primvars:displayColor = [${buildVector3Array( colorAttribute, count )}] ( + interpolation = "vertex" + )`; + + } + return string; } @@ -505,7 +600,7 @@ function buildMaterial( material, textures, quickLookCompatible = false ) { asset inputs:file = @textures/Texture_${ id }.png@ float2 inputs:st.connect = ${ color !== undefined ? 'float4 inputs:scale = ' + buildColor4( color ) : '' } - token inputs:sourceColorSpace = "${ texture.colorSpace === THREE.NoColorSpace ? 'raw' : 'sRGB' }" + token inputs:sourceColorSpace = "${ texture.colorSpace === NoColorSpace ? 'raw' : 'sRGB' }" token inputs:wrapS = "${ WRAPPINGS[ texture.wrapS ] }" token inputs:wrapT = "${ WRAPPINGS[ texture.wrapT ] }" float outputs:r @@ -518,7 +613,7 @@ function buildMaterial( material, textures, quickLookCompatible = false ) { } - if ( material.side === THREE.DoubleSide ) { + if ( material.side === DoubleSide ) { console.warn( 'THREE.USDZExporter: USDZ does not support double sided materials', material ); @@ -551,7 +646,7 @@ function buildMaterial( material, textures, quickLookCompatible = false ) { inputs.push( `${ pad }color3f inputs:emissiveColor.connect = ` ); - samplers.push( buildTexture( material.emissiveMap, 'emissive' ) ); + samplers.push( buildTexture( material.emissiveMap, 'emissive', new Color( material.emissive.r * material.emissiveIntensity, material.emissive.g * material.emissiveIntensity, material.emissive.b * material.emissiveIntensity ) ) ); } else if ( material.emissive.getHex() > 0 ) { @@ -571,15 +666,15 @@ function buildMaterial( material, textures, quickLookCompatible = false ) { inputs.push( `${ pad }float inputs:occlusion.connect = ` ); - samplers.push( buildTexture( material.aoMap, 'occlusion' ) ); + samplers.push( buildTexture( material.aoMap, 'occlusion', new Color( material.aoMapIntensity, material.aoMapIntensity, material.aoMapIntensity ) ) ); } - if ( material.roughnessMap !== null && material.roughness === 1 ) { + if ( material.roughnessMap !== null ) { inputs.push( `${ pad }float inputs:roughness.connect = ` ); - samplers.push( buildTexture( material.roughnessMap, 'roughness' ) ); + samplers.push( buildTexture( material.roughnessMap, 'roughness', new Color( material.roughness, material.roughness, material.roughness ) ) ); } else { @@ -587,11 +682,11 @@ function buildMaterial( material, textures, quickLookCompatible = false ) { } - if ( material.metalnessMap !== null && material.metalness === 1 ) { + if ( material.metalnessMap !== null ) { inputs.push( `${ pad }float inputs:metallic.connect = ` ); - samplers.push( buildTexture( material.metalnessMap, 'metallic' ) ); + samplers.push( buildTexture( material.metalnessMap, 'metallic', new Color( material.metalness, material.metalness, material.metalness ) ) ); } else { @@ -614,8 +709,28 @@ function buildMaterial( material, textures, quickLookCompatible = false ) { if ( material.isMeshPhysicalMaterial ) { - inputs.push( `${ pad }float inputs:clearcoat = ${ material.clearcoat }` ); - inputs.push( `${ pad }float inputs:clearcoatRoughness = ${ material.clearcoatRoughness }` ); + if ( material.clearcoatMap !== null ) { + + inputs.push( `${pad}float inputs:clearcoat.connect = ` ); + samplers.push( buildTexture( material.clearcoatMap, 'clearcoat', new Color( material.clearcoat, material.clearcoat, material.clearcoat ) ) ); + + } else { + + inputs.push( `${pad}float inputs:clearcoat = ${material.clearcoat}` ); + + } + + if ( material.clearcoatRoughnessMap !== null ) { + + inputs.push( `${pad}float inputs:clearcoatRoughness.connect = ` ); + samplers.push( buildTexture( material.clearcoatRoughnessMap, 'clearcoatRoughness', new Color( material.clearcoatRoughness, material.clearcoatRoughness, material.clearcoatRoughness ) ) ); + + } else { + + inputs.push( `${pad}float inputs:clearcoatRoughness = ${material.clearcoatRoughness}` ); + + } + inputs.push( `${ pad }float inputs:ior = ${ material.ior }` ); } @@ -683,7 +798,7 @@ function buildCamera( camera ) { float verticalAperture = ${ ( ( Math.abs( camera.top ) + Math.abs( camera.bottom ) ) * 10 ).toPrecision( PRECISION ) } token projection = "orthographic" } - + `; } else { @@ -700,11 +815,37 @@ function buildCamera( camera ) { token projection = "perspective" float verticalAperture = ${ camera.getFilmHeight().toPrecision( PRECISION ) } } - + `; } } +/** + * Export options of `USDZExporter`. + * + * @typedef {Object} USDZExporter~Options + * @property {number} [maxTextureSize=1024] - The maximum texture size that is going to be exported. + * @property {boolean} [includeAnchoringProperties=true] - Whether to include anchoring properties or not. + * @property {Object} [ar] - If `includeAnchoringProperties` is set to `true`, the anchoring type and alignment + * can be configured via `ar.anchoring.type` and `ar.planeAnchoring.alignment`. + * @property {boolean} [quickLookCompatible=false] - Whether to make the exported USDZ compatible to QuickLook + * which means the asset is modified to accommodate the bugs FB10036297 and FB11442287 (Apple Feedback). + **/ + +/** + * onDone callback of `USDZExporter`. + * + * @callback USDZExporter~OnDone + * @param {ArrayBuffer} result - The generated USDZ. + */ + +/** + * onError callback of `USDZExporter`. + * + * @callback USDZExporter~OnError + * @param {Error} error - The error object. + */ + export { USDZExporter }; diff --git a/examples/jsm/geometries/BoxLineGeometry.js b/examples/jsm/geometries/BoxLineGeometry.js index 8f0583322d0ab0..9ee5476bd33c49 100644 --- a/examples/jsm/geometries/BoxLineGeometry.js +++ b/examples/jsm/geometries/BoxLineGeometry.js @@ -3,8 +3,31 @@ import { Float32BufferAttribute } from 'three'; +/** + * A special type of box geometry intended for {@link LineSegments}. + * + * ```js + * const geometry = new THREE.BoxLineGeometry(); + * const material = new THREE.LineBasicMaterial( { color: 0x00ff00 } ); + * const lines = new THREE.LineSegments( geometry, material ); + * scene.add( lines ); + * ``` + * + * @augments BufferGeometry + * @three_import import { BoxLineGeometry } from 'three/addons/geometries/BoxLineGeometry.js'; + */ class BoxLineGeometry extends BufferGeometry { + /** + * Constructs a new box line geometry. + * + * @param {number} [width=1] - The width. That is, the length of the edges parallel to the X axis. + * @param {number} [height=1] - The height. That is, the length of the edges parallel to the Y axis. + * @param {number} [depth=1] - The depth. That is, the length of the edges parallel to the Z axis. + * @param {number} [widthSegments=1] - Number of segmented rectangular sections along the width of the sides. + * @param {number} [heightSegments=1] - Number of segmented rectangular sections along the height of the sides. + * @param {number} [depthSegments=1] - Number of segmented rectangular sections along the depth of the sides. + */ constructor( width = 1, height = 1, depth = 1, widthSegments = 1, heightSegments = 1, depthSegments = 1 ) { super(); diff --git a/examples/jsm/geometries/ConvexGeometry.js b/examples/jsm/geometries/ConvexGeometry.js index 3aa65c5aadf1e1..c35db77fdb6929 100644 --- a/examples/jsm/geometries/ConvexGeometry.js +++ b/examples/jsm/geometries/ConvexGeometry.js @@ -4,8 +4,27 @@ import { } from 'three'; import { ConvexHull } from '../math/ConvexHull.js'; +/** + * This class can be used to generate a convex hull for a given array of 3D points. + * The average time complexity for this task is considered to be O(nlog(n)). + * + * ```js + * const geometry = new ConvexGeometry( points ); + * const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); + * const mesh = new THREE.Mesh( geometry, material ); + * scene.add( mesh ); + * ``` + * + * @augments BufferGeometry + * @three_import import { ConvexGeometry } from 'three/addons/geometries/ConvexGeometry.js'; + */ class ConvexGeometry extends BufferGeometry { + /** + * Constructs a new convex geometry. + * + * @param {Array} points - An array of points in 3D space which should be enclosed by the convex hull. + */ constructor( points = [] ) { super(); diff --git a/examples/jsm/geometries/DecalGeometry.js b/examples/jsm/geometries/DecalGeometry.js index 5e712f23c65bf0..e672f1b04d61fb 100644 --- a/examples/jsm/geometries/DecalGeometry.js +++ b/examples/jsm/geometries/DecalGeometry.js @@ -1,28 +1,43 @@ import { BufferGeometry, + Euler, Float32BufferAttribute, + Matrix3, Matrix4, + Mesh, Vector3 } from 'three'; /** - * You can use this geometry to create a decal mesh, that serves different kinds of purposes. - * e.g. adding unique details to models, performing dynamic visual environmental changes or covering seams. + * This class can be used to create a decal mesh that serves different kinds of purposes e.g. + * adding unique details to models, performing dynamic visual environmental changes or covering seams. * - * Constructor parameter: + * Please not that decal projections can be distorted when used around corners. More information at + * this GitHub issue: [Decal projections without distortions]{@link https://github.com/mrdoob/three.js/issues/21187}. * - * mesh — Any mesh object - * position — Position of the decal projector - * orientation — Orientation of the decal projector - * size — Size of the decal projector + * Reference: [How to project decals]{@link http://blog.wolfire.com/2009/06/how-to-project-decals/} * - * reference: http://blog.wolfire.com/2009/06/how-to-project-decals/ + * ```js + * const geometry = new DecalGeometry( mesh, position, orientation, size ); + * const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); + * const mesh = new THREE.Mesh( geometry, material ); + * scene.add( mesh ); + * ``` * + * @augments BufferGeometry + * @three_import import { DecalGeometry } from 'three/addons/geometries/DecalGeometry.js'; */ - class DecalGeometry extends BufferGeometry { - constructor( mesh, position, orientation, size ) { + /** + * Constructs a new decal geometry. + * + * @param {Mesh} [mesh] - The base mesh the decal should be projected on. + * @param {Vector3} [position] - The position of the decal projector. + * @param {Euler} [orientation] - The orientation of the decal projector. + * @param {Vector3} [size] - Tje scale of the decal projector. + */ + constructor( mesh = new Mesh(), position = new Vector3(), orientation = new Euler(), size = new Vector3( 1, 1, 1 ) ) { super(); @@ -36,6 +51,8 @@ class DecalGeometry extends BufferGeometry { const plane = new Vector3(); + const normalMatrix = new Matrix3().getNormalMatrix( mesh.matrixWorld ); + // this matrix represents the transformation of the decal projector const projectorMatrix = new Matrix4(); @@ -52,9 +69,16 @@ class DecalGeometry extends BufferGeometry { // build geometry this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + if ( normals.length > 0 ) { + + this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + + } + + // + function generate() { let decalVertices = []; @@ -83,22 +107,40 @@ class DecalGeometry extends BufferGeometry { for ( let i = 0; i < index.count; i ++ ) { vertex.fromBufferAttribute( positionAttribute, index.getX( i ) ); - normal.fromBufferAttribute( normalAttribute, index.getX( i ) ); - pushDecalVertex( decalVertices, vertex, normal ); + if ( normalAttribute ) { + + normal.fromBufferAttribute( normalAttribute, index.getX( i ) ); + pushDecalVertex( decalVertices, vertex, normal ); + + } else { + + pushDecalVertex( decalVertices, vertex ); + + } } } else { + if ( positionAttribute === undefined ) return; // empty geometry + // non-indexed BufferGeometry for ( let i = 0; i < positionAttribute.count; i ++ ) { vertex.fromBufferAttribute( positionAttribute, i ); - normal.fromBufferAttribute( normalAttribute, i ); - pushDecalVertex( decalVertices, vertex, normal ); + if ( normalAttribute ) { + + normal.fromBufferAttribute( normalAttribute, i ); + pushDecalVertex( decalVertices, vertex, normal ); + + } else { + + pushDecalVertex( decalVertices, vertex ); + + } } @@ -133,22 +175,34 @@ class DecalGeometry extends BufferGeometry { // now create vertex and normal buffer data vertices.push( decalVertex.position.x, decalVertex.position.y, decalVertex.position.z ); - normals.push( decalVertex.normal.x, decalVertex.normal.y, decalVertex.normal.z ); + + if ( decalVertex.normal !== null ) { + + normals.push( decalVertex.normal.x, decalVertex.normal.y, decalVertex.normal.z ); + + } } } - function pushDecalVertex( decalVertices, vertex, normal ) { + function pushDecalVertex( decalVertices, vertex, normal = null ) { // transform the vertex to world space, then to projector space vertex.applyMatrix4( mesh.matrixWorld ); vertex.applyMatrix4( projectorMatrixInverse ); - normal.transformDirection( mesh.matrixWorld ); + if ( normal ) { - decalVertices.push( new DecalVertex( vertex.clone(), normal.clone() ) ); + normal.applyNormalMatrix( normalMatrix ); + decalVertices.push( new DecalVertex( vertex.clone(), normal.clone() ) ); + + } else { + + decalVertices.push( new DecalVertex( vertex.clone() ) ); + + } } @@ -310,18 +364,25 @@ class DecalGeometry extends BufferGeometry { const s0 = d0 / ( d0 - d1 ); - const v = new DecalVertex( - new Vector3( - v0.position.x + s0 * ( v1.position.x - v0.position.x ), - v0.position.y + s0 * ( v1.position.y - v0.position.y ), - v0.position.z + s0 * ( v1.position.z - v0.position.z ) - ), - new Vector3( + const position = new Vector3( + v0.position.x + s0 * ( v1.position.x - v0.position.x ), + v0.position.y + s0 * ( v1.position.y - v0.position.y ), + v0.position.z + s0 * ( v1.position.z - v0.position.z ) + ); + + let normal = null; + + if ( v0.normal !== null && v1.normal !== null ) { + + normal = new Vector3( v0.normal.x + s0 * ( v1.normal.x - v0.normal.x ), v0.normal.y + s0 * ( v1.normal.y - v0.normal.y ), v0.normal.z + s0 * ( v1.normal.z - v0.normal.z ) - ) - ); + ); + + } + + const v = new DecalVertex( position, normal ); // need to clip more values (texture coordinates)? do it this way: // intersectpoint.value = a.value + s * ( b.value - a.value ); @@ -338,7 +399,7 @@ class DecalGeometry extends BufferGeometry { class DecalVertex { - constructor( position, normal ) { + constructor( position, normal = null ) { this.position = position; this.normal = normal; @@ -347,7 +408,10 @@ class DecalVertex { clone() { - return new this.constructor( this.position.clone(), this.normal.clone() ); + const position = this.position.clone(); + const normal = ( this.normal !== null ) ? this.normal.clone() : null; + + return new this.constructor( position, normal ); } diff --git a/examples/jsm/geometries/LightningStrike.js b/examples/jsm/geometries/LightningStrike.js deleted file mode 100644 index 0d29a41283a616..00000000000000 --- a/examples/jsm/geometries/LightningStrike.js +++ /dev/null @@ -1,1017 +0,0 @@ -import { - BufferGeometry, - DynamicDrawUsage, - Float32BufferAttribute, - MathUtils, - Uint32BufferAttribute, - Vector3 -} from 'three'; -import { SimplexNoise } from '../math/SimplexNoise.js'; - -/** - * @fileoverview LightningStrike object for creating lightning strikes and voltaic arcs. - * - * - * Usage - * - * var myRay = new LightningStrike( paramsObject ); - * var myRayMesh = new THREE.Mesh( myRay, myMaterial ); - * scene.add( myRayMesh ); - * ... - * myRay.update( currentTime ); - * - * The "currentTime" can vary its rate, go forwards, backwards or even jump, but it cannot be negative. - * - * You should normally leave the ray position to (0, 0, 0). You should control it by changing the sourceOffset and destOffset parameters. - * - * - * LightningStrike parameters - * - * The paramsObject can contain any of the following parameters. - * - * Legend: - * 'LightningStrike' (also called 'ray'): An independent voltaic arc with its ramifications and defined with a set of parameters. - * 'Subray': A ramification of the ray. It is not a LightningStrike object. - * 'Segment': A linear segment piece of a subray. - * 'Leaf segment': A ray segment which cannot be smaller. - * - * - * The following parameters can be changed any time and if they vary smoothly, the ray form will also change smoothly: - * - * @param {Vector3} sourceOffset The point where the ray starts. - * - * @param {Vector3} destOffset The point where the ray ends. - * - * @param {double} timeScale The rate at wich the ray form changes in time. Default: 1 - * - * @param {double} roughness From 0 to 1. The higher the value, the more wrinkled is the ray. Default: 0.9 - * - * @param {double} straightness From 0 to 1. The higher the value, the more straight will be a subray path. Default: 0.7 - * - * @param {Vector3} up0 Ray 'up' direction at the ray starting point. Must be normalized. It should be perpendicular to the ray forward direction but it doesn't matter much. - * - * @param {Vector3} up1 Like the up0 parameter but at the end of the ray. Must be normalized. - * - * @param {double} radius0 Radius of the main ray trunk at the start point. Default: 1 - * - * @param {double} radius1 Radius of the main ray trunk at the end point. Default: 1 - * - * @param {double} radius0Factor The radius0 of a subray is this factor times the radius0 of its parent subray. Default: 0.5 - * - * @param {double} radius1Factor The radius1 of a subray is this factor times the radius1 of its parent subray. Default: 0.2 - * - * @param {minRadius} Minimum value a subray radius0 or radius1 can get. Default: 0.1 - * - * - * The following parameters should not be changed after lightning creation. They can be changed but the ray will change its form abruptly: - * - * @param {boolean} isEternal If true the ray never extinguishes. Otherwise its life is controlled by the 'birthTime' and 'deathTime' parameters. Default: true if any of those two parameters is undefined. - * - * @param {double} birthTime The time at which the ray starts its life and begins propagating. Only if isEternal is false. Default: None. - * - * @param {double} deathTime The time at which the ray ends vanishing and its life. Only if isEternal is false. Default: None. - * - * @param {double} propagationTimeFactor From 0 to 1. Lifetime factor at which the ray ends propagating and enters the steady phase. For example, 0.1 means it is propagating 1/10 of its lifetime. Default: 0.1 - * - * @param {double} vanishingTimeFactor From 0 to 1. Lifetime factor at which the ray ends the steady phase and begins vanishing. For example, 0.9 means it is vanishing 1/10 of its lifetime. Default: 0.9 - * - * @param {double} subrayPeriod Subrays cycle periodically. This is their time period. Default: 4 - * - * @param {double} subrayDutyCycle From 0 to 1. This is the fraction of time a subray is active. Default: 0.6 - * - * - * These parameters cannot change after lightning creation: - * - * @param {integer} maxIterations: Greater than 0. The number of ray's leaf segments is 2**maxIterations. Default: 9 - * - * @param {boolean} isStatic Set to true only for rays which won't change over time and are not attached to moving objects (Rare case). It is used to set the vertex buffers non-dynamic. You can omit calling update() for these rays. - * - * @param {integer} ramification Greater than 0. Maximum number of child subrays a subray can have. Default: 5 - * - * @param {integer} maxSubrayRecursion Greater than 0. Maximum level of recursion (subray descendant generations). Default: 3 - * - * @param {double} recursionProbability From 0 to 1. The lower the value, the less chance each new generation of subrays has to generate new subrays. Default: 0.6 - * - * @param {boolean} generateUVs If true, the ray geometry will have uv coordinates generated. u runs along the ray, and v across its perimeter. Default: false. - * - * @param {Object} randomGenerator Set here your random number generator which will seed the SimplexNoise and other decisions during ray tree creation. - * It can be used to generate repeatable rays. For that, set also the noiseSeed parameter, and each ray created with that generator and seed pair will be identical in time. - * The randomGenerator parameter should be an object with a random() function similar to Math.random, but seedable. - * It must have also a getSeed() method, which returns the current seed, and a setSeed( seed ) method, which accepts as seed a fractional number from 0 to 1, as well as any other number. - * The default value is an internal generator for some uses and Math.random for others (It is non-repeatable even if noiseSeed is supplied) - * - * @param {double} noiseSeed Seed used to make repeatable rays (see the randomGenerator) - * - * @param {function} onDecideSubrayCreation Set this to change the callback which decides subray creation. You can look at the default callback in the code (createDefaultSubrayCreationCallbacks)for more info. - * - * @param {function} onSubrayCreation This is another callback, more simple than the previous one. It can be used to adapt the form of subrays or other parameters once a subray has been created and initialized. It is used in the examples to adapt subrays to a sphere or to a plane. - * - * -*/ - -class LightningStrike extends BufferGeometry { - - constructor( rayParameters = {} ) { - - super(); - - this.isLightningStrike = true; - - this.type = 'LightningStrike'; - - // Set parameters, and set undefined parameters to default values - this.init( LightningStrike.copyParameters( rayParameters, rayParameters ) ); - - // Creates and populates the mesh - this.createMesh(); - - } - - static createRandomGenerator() { - - const numSeeds = 2053; - const seeds = []; - - for ( let i = 0; i < numSeeds; i ++ ) { - - seeds.push( Math.random() ); - - } - - const generator = { - - currentSeed: 0, - - random: function () { - - const value = seeds[ generator.currentSeed ]; - - generator.currentSeed = ( generator.currentSeed + 1 ) % numSeeds; - - return value; - - }, - - getSeed: function () { - - return generator.currentSeed / numSeeds; - - }, - - setSeed: function ( seed ) { - - generator.currentSeed = Math.floor( seed * numSeeds ) % numSeeds; - - } - - }; - - return generator; - - } - - static copyParameters( dest = {}, source = {} ) { - - const vecCopy = function ( v ) { - - if ( source === dest ) { - - return v; - - } else { - - return v.clone(); - - } - - }; - - dest.sourceOffset = source.sourceOffset !== undefined ? vecCopy( source.sourceOffset ) : new Vector3( 0, 100, 0 ), - dest.destOffset = source.destOffset !== undefined ? vecCopy( source.destOffset ) : new Vector3( 0, 0, 0 ), - - dest.timeScale = source.timeScale !== undefined ? source.timeScale : 1, - dest.roughness = source.roughness !== undefined ? source.roughness : 0.9, - dest.straightness = source.straightness !== undefined ? source.straightness : 0.7, - - dest.up0 = source.up0 !== undefined ? vecCopy( source.up0 ) : new Vector3( 0, 0, 1 ); - dest.up1 = source.up1 !== undefined ? vecCopy( source.up1 ) : new Vector3( 0, 0, 1 ), - dest.radius0 = source.radius0 !== undefined ? source.radius0 : 1, - dest.radius1 = source.radius1 !== undefined ? source.radius1 : 1, - dest.radius0Factor = source.radius0Factor !== undefined ? source.radius0Factor : 0.5, - dest.radius1Factor = source.radius1Factor !== undefined ? source.radius1Factor : 0.2, - dest.minRadius = source.minRadius !== undefined ? source.minRadius : 0.2, - - // These parameters should not be changed after lightning creation. They can be changed but the ray will change its form abruptly: - - dest.isEternal = source.isEternal !== undefined ? source.isEternal : ( source.birthTime === undefined || source.deathTime === undefined ), - dest.birthTime = source.birthTime, - dest.deathTime = source.deathTime, - dest.propagationTimeFactor = source.propagationTimeFactor !== undefined ? source.propagationTimeFactor : 0.1, - dest.vanishingTimeFactor = source.vanishingTimeFactor !== undefined ? source.vanishingTimeFactor : 0.9, - dest.subrayPeriod = source.subrayPeriod !== undefined ? source.subrayPeriod : 4, - dest.subrayDutyCycle = source.subrayDutyCycle !== undefined ? source.subrayDutyCycle : 0.6; - - // These parameters cannot change after lightning creation: - - dest.maxIterations = source.maxIterations !== undefined ? source.maxIterations : 9; - dest.isStatic = source.isStatic !== undefined ? source.isStatic : false; - dest.ramification = source.ramification !== undefined ? source.ramification : 5; - dest.maxSubrayRecursion = source.maxSubrayRecursion !== undefined ? source.maxSubrayRecursion : 3; - dest.recursionProbability = source.recursionProbability !== undefined ? source.recursionProbability : 0.6; - dest.generateUVs = source.generateUVs !== undefined ? source.generateUVs : false; - dest.randomGenerator = source.randomGenerator, - dest.noiseSeed = source.noiseSeed, - dest.onDecideSubrayCreation = source.onDecideSubrayCreation, - dest.onSubrayCreation = source.onSubrayCreation; - - return dest; - - } - - update( time ) { - - if ( this.isStatic ) return; - - if ( this.rayParameters.isEternal || ( this.rayParameters.birthTime <= time && time <= this.rayParameters.deathTime ) ) { - - this.updateMesh( time ); - - if ( time < this.subrays[ 0 ].endPropagationTime ) { - - this.state = LightningStrike.RAY_PROPAGATING; - - } else if ( time > this.subrays[ 0 ].beginVanishingTime ) { - - this.state = LightningStrike.RAY_VANISHING; - - } else { - - this.state = LightningStrike.RAY_STEADY; - - } - - this.visible = true; - - } else { - - this.visible = false; - - if ( time < this.rayParameters.birthTime ) { - - this.state = LightningStrike.RAY_UNBORN; - - } else { - - this.state = LightningStrike.RAY_EXTINGUISHED; - - } - - } - - } - - init( rayParameters ) { - - // Init all the state from the parameters - - this.rayParameters = rayParameters; - - // These parameters cannot change after lightning creation: - - this.maxIterations = rayParameters.maxIterations !== undefined ? Math.floor( rayParameters.maxIterations ) : 9; - rayParameters.maxIterations = this.maxIterations; - this.isStatic = rayParameters.isStatic !== undefined ? rayParameters.isStatic : false; - rayParameters.isStatic = this.isStatic; - this.ramification = rayParameters.ramification !== undefined ? Math.floor( rayParameters.ramification ) : 5; - rayParameters.ramification = this.ramification; - this.maxSubrayRecursion = rayParameters.maxSubrayRecursion !== undefined ? Math.floor( rayParameters.maxSubrayRecursion ) : 3; - rayParameters.maxSubrayRecursion = this.maxSubrayRecursion; - this.recursionProbability = rayParameters.recursionProbability !== undefined ? rayParameters.recursionProbability : 0.6; - rayParameters.recursionProbability = this.recursionProbability; - this.generateUVs = rayParameters.generateUVs !== undefined ? rayParameters.generateUVs : false; - rayParameters.generateUVs = this.generateUVs; - - // Random generator - if ( rayParameters.randomGenerator !== undefined ) { - - this.randomGenerator = rayParameters.randomGenerator; - this.seedGenerator = rayParameters.randomGenerator; - - if ( rayParameters.noiseSeed !== undefined ) { - - this.seedGenerator.setSeed( rayParameters.noiseSeed ); - - } - - } else { - - this.randomGenerator = LightningStrike.createRandomGenerator(); - this.seedGenerator = Math; - - } - - // Ray creation callbacks - if ( rayParameters.onDecideSubrayCreation !== undefined ) { - - this.onDecideSubrayCreation = rayParameters.onDecideSubrayCreation; - - } else { - - this.createDefaultSubrayCreationCallbacks(); - - if ( rayParameters.onSubrayCreation !== undefined ) { - - this.onSubrayCreation = rayParameters.onSubrayCreation; - - } - - } - - // Internal state - - this.state = LightningStrike.RAY_INITIALIZED; - - this.maxSubrays = Math.ceil( 1 + Math.pow( this.ramification, Math.max( 0, this.maxSubrayRecursion - 1 ) ) ); - rayParameters.maxSubrays = this.maxSubrays; - - this.maxRaySegments = 2 * ( 1 << this.maxIterations ); - - this.subrays = []; - - for ( let i = 0; i < this.maxSubrays; i ++ ) { - - this.subrays.push( this.createSubray() ); - - } - - this.raySegments = []; - - for ( let i = 0; i < this.maxRaySegments; i ++ ) { - - this.raySegments.push( this.createSegment() ); - - } - - this.time = 0; - this.timeFraction = 0; - this.currentSegmentCallback = null; - this.currentCreateTriangleVertices = this.generateUVs ? this.createTriangleVerticesWithUVs : this.createTriangleVerticesWithoutUVs; - this.numSubrays = 0; - this.currentSubray = null; - this.currentSegmentIndex = 0; - this.isInitialSegment = false; - this.subrayProbability = 0; - - this.currentVertex = 0; - this.currentIndex = 0; - this.currentCoordinate = 0; - this.currentUVCoordinate = 0; - this.vertices = null; - this.uvs = null; - this.indices = null; - this.positionAttribute = null; - this.uvsAttribute = null; - - this.simplexX = new SimplexNoise( this.seedGenerator ); - this.simplexY = new SimplexNoise( this.seedGenerator ); - this.simplexZ = new SimplexNoise( this.seedGenerator ); - - // Temp vectors - this.forwards = new Vector3(); - this.forwardsFill = new Vector3(); - this.side = new Vector3(); - this.down = new Vector3(); - this.middlePos = new Vector3(); - this.middleLinPos = new Vector3(); - this.newPos = new Vector3(); - this.vPos = new Vector3(); - this.cross1 = new Vector3(); - - } - - createMesh() { - - const maxDrawableSegmentsPerSubRay = 1 << this.maxIterations; - - const maxVerts = 3 * ( maxDrawableSegmentsPerSubRay + 1 ) * this.maxSubrays; - const maxIndices = 18 * maxDrawableSegmentsPerSubRay * this.maxSubrays; - - this.vertices = new Float32Array( maxVerts * 3 ); - this.indices = new Uint32Array( maxIndices ); - - if ( this.generateUVs ) { - - this.uvs = new Float32Array( maxVerts * 2 ); - - } - - // Populate the mesh - this.fillMesh( 0 ); - - this.setIndex( new Uint32BufferAttribute( this.indices, 1 ) ); - - this.positionAttribute = new Float32BufferAttribute( this.vertices, 3 ); - this.setAttribute( 'position', this.positionAttribute ); - - if ( this.generateUVs ) { - - this.uvsAttribute = new Float32BufferAttribute( new Float32Array( this.uvs ), 2 ); - this.setAttribute( 'uv', this.uvsAttribute ); - - } - - if ( ! this.isStatic ) { - - this.index.usage = DynamicDrawUsage; - this.positionAttribute.usage = DynamicDrawUsage; - - if ( this.generateUVs ) { - - this.uvsAttribute.usage = DynamicDrawUsage; - - } - - } - - // Store buffers for later modification - this.vertices = this.positionAttribute.array; - this.indices = this.index.array; - - if ( this.generateUVs ) { - - this.uvs = this.uvsAttribute.array; - - } - - } - - updateMesh( time ) { - - this.fillMesh( time ); - - this.drawRange.count = this.currentIndex; - - this.index.needsUpdate = true; - - this.positionAttribute.needsUpdate = true; - - if ( this.generateUVs ) { - - this.uvsAttribute.needsUpdate = true; - - } - - } - - fillMesh( time ) { - - const scope = this; - - this.currentVertex = 0; - this.currentIndex = 0; - this.currentCoordinate = 0; - this.currentUVCoordinate = 0; - - this.fractalRay( time, function fillVertices( segment ) { - - const subray = scope.currentSubray; - - if ( time < subray.birthTime ) { //&& ( ! this.rayParameters.isEternal || scope.currentSubray.recursion > 0 ) ) { - - return; - - } else if ( this.rayParameters.isEternal && scope.currentSubray.recursion == 0 ) { - - // Eternal rays don't propagate nor vanish, but its subrays do - - scope.createPrism( segment ); - - scope.onDecideSubrayCreation( segment, scope ); - - } else if ( time < subray.endPropagationTime ) { - - if ( scope.timeFraction >= segment.fraction0 * subray.propagationTimeFactor ) { - - // Ray propagation has arrived to this segment - - scope.createPrism( segment ); - - scope.onDecideSubrayCreation( segment, scope ); - - } - - } else if ( time < subray.beginVanishingTime ) { - - // Ray is steady (nor propagating nor vanishing) - - scope.createPrism( segment ); - - scope.onDecideSubrayCreation( segment, scope ); - - } else { - - if ( scope.timeFraction <= subray.vanishingTimeFactor + segment.fraction1 * ( 1 - subray.vanishingTimeFactor ) ) { - - // Segment has not yet vanished - - scope.createPrism( segment ); - - } - - scope.onDecideSubrayCreation( segment, scope ); - - } - - } ); - - } - - addNewSubray( /*rayParameters*/ ) { - - return this.subrays[ this.numSubrays ++ ]; - - } - - initSubray( subray, rayParameters ) { - - subray.pos0.copy( rayParameters.sourceOffset ); - subray.pos1.copy( rayParameters.destOffset ); - subray.up0.copy( rayParameters.up0 ); - subray.up1.copy( rayParameters.up1 ); - subray.radius0 = rayParameters.radius0; - subray.radius1 = rayParameters.radius1; - subray.birthTime = rayParameters.birthTime; - subray.deathTime = rayParameters.deathTime; - subray.timeScale = rayParameters.timeScale; - subray.roughness = rayParameters.roughness; - subray.straightness = rayParameters.straightness; - subray.propagationTimeFactor = rayParameters.propagationTimeFactor; - subray.vanishingTimeFactor = rayParameters.vanishingTimeFactor; - - subray.maxIterations = this.maxIterations; - subray.seed = rayParameters.noiseSeed !== undefined ? rayParameters.noiseSeed : 0; - subray.recursion = 0; - - } - - fractalRay( time, segmentCallback ) { - - this.time = time; - this.currentSegmentCallback = segmentCallback; - this.numSubrays = 0; - - // Add the top level subray - this.initSubray( this.addNewSubray(), this.rayParameters ); - - // Process all subrays that are being generated until consuming all of them - for ( let subrayIndex = 0; subrayIndex < this.numSubrays; subrayIndex ++ ) { - - const subray = this.subrays[ subrayIndex ]; - this.currentSubray = subray; - - this.randomGenerator.setSeed( subray.seed ); - - subray.endPropagationTime = MathUtils.lerp( subray.birthTime, subray.deathTime, subray.propagationTimeFactor ); - subray.beginVanishingTime = MathUtils.lerp( subray.deathTime, subray.birthTime, 1 - subray.vanishingTimeFactor ); - - const random1 = this.randomGenerator.random; - subray.linPos0.set( random1(), random1(), random1() ).multiplyScalar( 1000 ); - subray.linPos1.set( random1(), random1(), random1() ).multiplyScalar( 1000 ); - - this.timeFraction = ( time - subray.birthTime ) / ( subray.deathTime - subray.birthTime ); - - this.currentSegmentIndex = 0; - this.isInitialSegment = true; - - const segment = this.getNewSegment(); - segment.iteration = 0; - segment.pos0.copy( subray.pos0 ); - segment.pos1.copy( subray.pos1 ); - segment.linPos0.copy( subray.linPos0 ); - segment.linPos1.copy( subray.linPos1 ); - segment.up0.copy( subray.up0 ); - segment.up1.copy( subray.up1 ); - segment.radius0 = subray.radius0; - segment.radius1 = subray.radius1; - segment.fraction0 = 0; - segment.fraction1 = 1; - segment.positionVariationFactor = 1 - subray.straightness; - - this.subrayProbability = this.ramification * Math.pow( this.recursionProbability, subray.recursion ) / ( 1 << subray.maxIterations ); - - this.fractalRayRecursive( segment ); - - } - - this.currentSegmentCallback = null; - this.currentSubray = null; - - } - - fractalRayRecursive( segment ) { - - // Leave recursion condition - if ( segment.iteration >= this.currentSubray.maxIterations ) { - - this.currentSegmentCallback( segment ); - - return; - - } - - // Interpolation - this.forwards.subVectors( segment.pos1, segment.pos0 ); - let lForwards = this.forwards.length(); - - if ( lForwards < 0.000001 ) { - - this.forwards.set( 0, 0, 0.01 ); - lForwards = this.forwards.length(); - - } - - const middleRadius = ( segment.radius0 + segment.radius1 ) * 0.5; - const middleFraction = ( segment.fraction0 + segment.fraction1 ) * 0.5; - - const timeDimension = this.time * this.currentSubray.timeScale * Math.pow( 2, segment.iteration ); - - this.middlePos.lerpVectors( segment.pos0, segment.pos1, 0.5 ); - this.middleLinPos.lerpVectors( segment.linPos0, segment.linPos1, 0.5 ); - const p = this.middleLinPos; - - // Noise - this.newPos.set( this.simplexX.noise4d( p.x, p.y, p.z, timeDimension ), - this.simplexY.noise4d( p.x, p.y, p.z, timeDimension ), - this.simplexZ.noise4d( p.x, p.y, p.z, timeDimension ) ); - - this.newPos.multiplyScalar( segment.positionVariationFactor * lForwards ); - this.newPos.add( this.middlePos ); - - // Recursion - - const newSegment1 = this.getNewSegment(); - newSegment1.pos0.copy( segment.pos0 ); - newSegment1.pos1.copy( this.newPos ); - newSegment1.linPos0.copy( segment.linPos0 ); - newSegment1.linPos1.copy( this.middleLinPos ); - newSegment1.up0.copy( segment.up0 ); - newSegment1.up1.copy( segment.up1 ); - newSegment1.radius0 = segment.radius0; - newSegment1.radius1 = middleRadius; - newSegment1.fraction0 = segment.fraction0; - newSegment1.fraction1 = middleFraction; - newSegment1.positionVariationFactor = segment.positionVariationFactor * this.currentSubray.roughness; - newSegment1.iteration = segment.iteration + 1; - - const newSegment2 = this.getNewSegment(); - newSegment2.pos0.copy( this.newPos ); - newSegment2.pos1.copy( segment.pos1 ); - newSegment2.linPos0.copy( this.middleLinPos ); - newSegment2.linPos1.copy( segment.linPos1 ); - this.cross1.crossVectors( segment.up0, this.forwards.normalize() ); - newSegment2.up0.crossVectors( this.forwards, this.cross1 ).normalize(); - newSegment2.up1.copy( segment.up1 ); - newSegment2.radius0 = middleRadius; - newSegment2.radius1 = segment.radius1; - newSegment2.fraction0 = middleFraction; - newSegment2.fraction1 = segment.fraction1; - newSegment2.positionVariationFactor = segment.positionVariationFactor * this.currentSubray.roughness; - newSegment2.iteration = segment.iteration + 1; - - this.fractalRayRecursive( newSegment1 ); - - this.fractalRayRecursive( newSegment2 ); - - } - - createPrism( segment ) { - - // Creates one triangular prism and its vertices at the segment - - this.forwardsFill.subVectors( segment.pos1, segment.pos0 ).normalize(); - - if ( this.isInitialSegment ) { - - this.currentCreateTriangleVertices( segment.pos0, segment.up0, this.forwardsFill, segment.radius0, 0 ); - - this.isInitialSegment = false; - - } - - this.currentCreateTriangleVertices( segment.pos1, segment.up0, this.forwardsFill, segment.radius1, segment.fraction1 ); - - this.createPrismFaces(); - - } - - createTriangleVerticesWithoutUVs( pos, up, forwards, radius ) { - - // Create an equilateral triangle (only vertices) - - this.side.crossVectors( up, forwards ).multiplyScalar( radius * LightningStrike.COS30DEG ); - this.down.copy( up ).multiplyScalar( - radius * LightningStrike.SIN30DEG ); - - const p = this.vPos; - const v = this.vertices; - - p.copy( pos ).sub( this.side ).add( this.down ); - - v[ this.currentCoordinate ++ ] = p.x; - v[ this.currentCoordinate ++ ] = p.y; - v[ this.currentCoordinate ++ ] = p.z; - - p.copy( pos ).add( this.side ).add( this.down ); - - v[ this.currentCoordinate ++ ] = p.x; - v[ this.currentCoordinate ++ ] = p.y; - v[ this.currentCoordinate ++ ] = p.z; - - p.copy( up ).multiplyScalar( radius ).add( pos ); - - v[ this.currentCoordinate ++ ] = p.x; - v[ this.currentCoordinate ++ ] = p.y; - v[ this.currentCoordinate ++ ] = p.z; - - this.currentVertex += 3; - - } - - createTriangleVerticesWithUVs( pos, up, forwards, radius, u ) { - - // Create an equilateral triangle (only vertices) - - this.side.crossVectors( up, forwards ).multiplyScalar( radius * LightningStrike.COS30DEG ); - this.down.copy( up ).multiplyScalar( - radius * LightningStrike.SIN30DEG ); - - const p = this.vPos; - const v = this.vertices; - const uv = this.uvs; - - p.copy( pos ).sub( this.side ).add( this.down ); - - v[ this.currentCoordinate ++ ] = p.x; - v[ this.currentCoordinate ++ ] = p.y; - v[ this.currentCoordinate ++ ] = p.z; - - uv[ this.currentUVCoordinate ++ ] = u; - uv[ this.currentUVCoordinate ++ ] = 0; - - p.copy( pos ).add( this.side ).add( this.down ); - - v[ this.currentCoordinate ++ ] = p.x; - v[ this.currentCoordinate ++ ] = p.y; - v[ this.currentCoordinate ++ ] = p.z; - - uv[ this.currentUVCoordinate ++ ] = u; - uv[ this.currentUVCoordinate ++ ] = 0.5; - - p.copy( up ).multiplyScalar( radius ).add( pos ); - - v[ this.currentCoordinate ++ ] = p.x; - v[ this.currentCoordinate ++ ] = p.y; - v[ this.currentCoordinate ++ ] = p.z; - - uv[ this.currentUVCoordinate ++ ] = u; - uv[ this.currentUVCoordinate ++ ] = 1; - - this.currentVertex += 3; - - } - - createPrismFaces( vertex/*, index*/ ) { - - const indices = this.indices; - vertex = this.currentVertex - 6; - - indices[ this.currentIndex ++ ] = vertex + 1; - indices[ this.currentIndex ++ ] = vertex + 2; - indices[ this.currentIndex ++ ] = vertex + 5; - indices[ this.currentIndex ++ ] = vertex + 1; - indices[ this.currentIndex ++ ] = vertex + 5; - indices[ this.currentIndex ++ ] = vertex + 4; - indices[ this.currentIndex ++ ] = vertex + 0; - indices[ this.currentIndex ++ ] = vertex + 1; - indices[ this.currentIndex ++ ] = vertex + 4; - indices[ this.currentIndex ++ ] = vertex + 0; - indices[ this.currentIndex ++ ] = vertex + 4; - indices[ this.currentIndex ++ ] = vertex + 3; - indices[ this.currentIndex ++ ] = vertex + 2; - indices[ this.currentIndex ++ ] = vertex + 0; - indices[ this.currentIndex ++ ] = vertex + 3; - indices[ this.currentIndex ++ ] = vertex + 2; - indices[ this.currentIndex ++ ] = vertex + 3; - indices[ this.currentIndex ++ ] = vertex + 5; - - } - - createDefaultSubrayCreationCallbacks() { - - const random1 = this.randomGenerator.random; - - this.onDecideSubrayCreation = function ( segment, lightningStrike ) { - - // Decide subrays creation at parent (sub)ray segment - - const subray = lightningStrike.currentSubray; - - const period = lightningStrike.rayParameters.subrayPeriod; - const dutyCycle = lightningStrike.rayParameters.subrayDutyCycle; - - const phase0 = ( lightningStrike.rayParameters.isEternal && subray.recursion == 0 ) ? - random1() * period : MathUtils.lerp( subray.birthTime, subray.endPropagationTime, segment.fraction0 ) - random1() * period; - - const phase = lightningStrike.time - phase0; - const currentCycle = Math.floor( phase / period ); - - const childSubraySeed = random1() * ( currentCycle + 1 ); - - const isActive = phase % period <= dutyCycle * period; - - let probability = 0; - - if ( isActive ) { - - probability = lightningStrike.subrayProbability; - // Distribution test: probability *= segment.fraction0 > 0.5 && segment.fraction0 < 0.9 ? 1 / 0.4 : 0; - - } - - if ( subray.recursion < lightningStrike.maxSubrayRecursion && lightningStrike.numSubrays < lightningStrike.maxSubrays && random1() < probability ) { - - const childSubray = lightningStrike.addNewSubray(); - - const parentSeed = lightningStrike.randomGenerator.getSeed(); - childSubray.seed = childSubraySeed; - lightningStrike.randomGenerator.setSeed( childSubraySeed ); - - childSubray.recursion = subray.recursion + 1; - childSubray.maxIterations = Math.max( 1, subray.maxIterations - 1 ); - - childSubray.linPos0.set( random1(), random1(), random1() ).multiplyScalar( 1000 ); - childSubray.linPos1.set( random1(), random1(), random1() ).multiplyScalar( 1000 ); - childSubray.up0.copy( subray.up0 ); - childSubray.up1.copy( subray.up1 ); - childSubray.radius0 = segment.radius0 * lightningStrike.rayParameters.radius0Factor; - childSubray.radius1 = Math.min( lightningStrike.rayParameters.minRadius, segment.radius1 * lightningStrike.rayParameters.radius1Factor ); - - childSubray.birthTime = phase0 + ( currentCycle ) * period; - childSubray.deathTime = childSubray.birthTime + period * dutyCycle; - - if ( ! lightningStrike.rayParameters.isEternal && subray.recursion == 0 ) { - - childSubray.birthTime = Math.max( childSubray.birthTime, subray.birthTime ); - childSubray.deathTime = Math.min( childSubray.deathTime, subray.deathTime ); - - } - - childSubray.timeScale = subray.timeScale * 2; - childSubray.roughness = subray.roughness; - childSubray.straightness = subray.straightness; - childSubray.propagationTimeFactor = subray.propagationTimeFactor; - childSubray.vanishingTimeFactor = subray.vanishingTimeFactor; - - lightningStrike.onSubrayCreation( segment, subray, childSubray, lightningStrike ); - - lightningStrike.randomGenerator.setSeed( parentSeed ); - - } - - }; - - const vec1Pos = new Vector3(); - const vec2Forward = new Vector3(); - const vec3Side = new Vector3(); - const vec4Up = new Vector3(); - - this.onSubrayCreation = function ( segment, parentSubray, childSubray, lightningStrike ) { - - // Decide childSubray origin and destination positions (pos0 and pos1) and possibly other properties of childSubray - - // Just use the default cone position generator - lightningStrike.subrayCylinderPosition( segment, parentSubray, childSubray, 0.5, 0.6, 0.2 ); - - }; - - this.subrayConePosition = function ( segment, parentSubray, childSubray, heightFactor, sideWidthFactor, minSideWidthFactor ) { - - // Sets childSubray pos0 and pos1 in a cone - - childSubray.pos0.copy( segment.pos0 ); - - vec1Pos.subVectors( parentSubray.pos1, parentSubray.pos0 ); - vec2Forward.copy( vec1Pos ).normalize(); - vec1Pos.multiplyScalar( segment.fraction0 + ( 1 - segment.fraction0 ) * ( random1() * heightFactor ) ); - const length = vec1Pos.length(); - vec3Side.crossVectors( parentSubray.up0, vec2Forward ); - const angle = 2 * Math.PI * random1(); - vec3Side.multiplyScalar( Math.cos( angle ) ); - vec4Up.copy( parentSubray.up0 ).multiplyScalar( Math.sin( angle ) ); - - childSubray.pos1.copy( vec3Side ).add( vec4Up ).multiplyScalar( length * sideWidthFactor * ( minSideWidthFactor + random1() * ( 1 - minSideWidthFactor ) ) ).add( vec1Pos ).add( parentSubray.pos0 ); - - }; - - this.subrayCylinderPosition = function ( segment, parentSubray, childSubray, heightFactor, sideWidthFactor, minSideWidthFactor ) { - - // Sets childSubray pos0 and pos1 in a cylinder - - childSubray.pos0.copy( segment.pos0 ); - - vec1Pos.subVectors( parentSubray.pos1, parentSubray.pos0 ); - vec2Forward.copy( vec1Pos ).normalize(); - vec1Pos.multiplyScalar( segment.fraction0 + ( 1 - segment.fraction0 ) * ( ( 2 * random1() - 1 ) * heightFactor ) ); - const length = vec1Pos.length(); - vec3Side.crossVectors( parentSubray.up0, vec2Forward ); - const angle = 2 * Math.PI * random1(); - vec3Side.multiplyScalar( Math.cos( angle ) ); - vec4Up.copy( parentSubray.up0 ).multiplyScalar( Math.sin( angle ) ); - - childSubray.pos1.copy( vec3Side ).add( vec4Up ).multiplyScalar( length * sideWidthFactor * ( minSideWidthFactor + random1() * ( 1 - minSideWidthFactor ) ) ).add( vec1Pos ).add( parentSubray.pos0 ); - - }; - - } - - createSubray() { - - return { - - seed: 0, - maxIterations: 0, - recursion: 0, - pos0: new Vector3(), - pos1: new Vector3(), - linPos0: new Vector3(), - linPos1: new Vector3(), - up0: new Vector3(), - up1: new Vector3(), - radius0: 0, - radius1: 0, - birthTime: 0, - deathTime: 0, - timeScale: 0, - roughness: 0, - straightness: 0, - propagationTimeFactor: 0, - vanishingTimeFactor: 0, - endPropagationTime: 0, - beginVanishingTime: 0 - - }; - - } - - createSegment() { - - return { - iteration: 0, - pos0: new Vector3(), - pos1: new Vector3(), - linPos0: new Vector3(), - linPos1: new Vector3(), - up0: new Vector3(), - up1: new Vector3(), - radius0: 0, - radius1: 0, - fraction0: 0, - fraction1: 0, - positionVariationFactor: 0 - }; - - } - - getNewSegment() { - - return this.raySegments[ this.currentSegmentIndex ++ ]; - - } - - copy( source ) { - - super.copy( source ); - - this.init( LightningStrike.copyParameters( {}, source.rayParameters ) ); - - return this; - - } - - clone() { - - return new this.constructor( LightningStrike.copyParameters( {}, this.rayParameters ) ); - - } - -} - -// Ray states -LightningStrike.RAY_INITIALIZED = 0; -LightningStrike.RAY_UNBORN = 1; -LightningStrike.RAY_PROPAGATING = 2; -LightningStrike.RAY_STEADY = 3; -LightningStrike.RAY_VANISHING = 4; -LightningStrike.RAY_EXTINGUISHED = 5; - -LightningStrike.COS30DEG = Math.cos( 30 * Math.PI / 180 ); -LightningStrike.SIN30DEG = Math.sin( 30 * Math.PI / 180 ); - -export { LightningStrike }; diff --git a/examples/jsm/geometries/ParametricFunctions.js b/examples/jsm/geometries/ParametricFunctions.js new file mode 100644 index 00000000000000..9173fad3c67b40 --- /dev/null +++ b/examples/jsm/geometries/ParametricFunctions.js @@ -0,0 +1,100 @@ + +/** + * @module ParametricFunctions + * @three_import import * as ParametricFunctions from 'three/addons/geometries/ParametricFunctions.js'; + */ + +/** + * A parametric function representing the Klein bottle. + * + * @param {number} v - The `v` coordinate on the surface in the range `[0,1]`. + * @param {number} u - The `u` coordinate on the surface in the range `[0,1]`. + * @param {Vector3} target - The target vector that is used to store the method's result. + */ +function klein( v, u, target ) { + + u *= Math.PI; + v *= 2 * Math.PI; + + u = u * 2; + let x, z; + if ( u < Math.PI ) { + + x = 3 * Math.cos( u ) * ( 1 + Math.sin( u ) ) + ( 2 * ( 1 - Math.cos( u ) / 2 ) ) * Math.cos( u ) * Math.cos( v ); + z = - 8 * Math.sin( u ) - 2 * ( 1 - Math.cos( u ) / 2 ) * Math.sin( u ) * Math.cos( v ); + + } else { + + x = 3 * Math.cos( u ) * ( 1 + Math.sin( u ) ) + ( 2 * ( 1 - Math.cos( u ) / 2 ) ) * Math.cos( v + Math.PI ); + z = - 8 * Math.sin( u ); + + } + + const y = - 2 * ( 1 - Math.cos( u ) / 2 ) * Math.sin( v ); + + target.set( x, y, z ); + +} + +/** + * A parametric function representing a flat plane. + * + * @param {number} u - The `u` coordinate on the surface in the range `[0,1]`. + * @param {number} v - The `v` coordinate on the surface in the range `[0,1]`. + * @param {Vector3} target - The target vector that is used to store the method's result. + */ +function plane( u, v, target ) { + + target.set( u, 0, v ); + +} + +/** + * A parametric function representing a flat mobius strip. + * + * @param {number} u - The `u` coordinate on the surface in the range `[0,1]`. + * @param {number} t - The `v` coordinate on the surface in the range `[0,1]`. + * @param {Vector3} target - The target vector that is used to store the method's result. + */ +function mobius( u, t, target ) { + + // http://www.wolframalpha.com/input/?i=M%C3%B6bius+strip+parametric+equations&lk=1&a=ClashPrefs_*Surface.MoebiusStrip.SurfaceProperty.ParametricEquations- + u = u - 0.5; + const v = 2 * Math.PI * t; + + const a = 2; + + const x = Math.cos( v ) * ( a + u * Math.cos( v / 2 ) ); + const y = Math.sin( v ) * ( a + u * Math.cos( v / 2 ) ); + const z = u * Math.sin( v / 2 ); + + target.set( x, y, z ); + +} + +/** + * A parametric function representing a volumetric mobius strip. + * + * @param {number} u - The `u` coordinate on the surface in the range `[0,1]`. + * @param {number} t - The `v` coordinate on the surface in the range `[0,1]`. + * @param {Vector3} target - The target vector that is used to store the method's result. + */ +function mobius3d( u, t, target ) { + + u *= Math.PI; + t *= 2 * Math.PI; + + u = u * 2; + const phi = u / 2; + const major = 2.25, a = 0.125, b = 0.65; + + let x = a * Math.cos( t ) * Math.cos( phi ) - b * Math.sin( t ) * Math.sin( phi ); + const z = a * Math.cos( t ) * Math.sin( phi ) + b * Math.sin( t ) * Math.cos( phi ); + const y = ( major + x ) * Math.sin( u ); + x = ( major + x ) * Math.cos( u ); + + target.set( x, y, z ); + +} + +export { klein, plane, mobius, mobius3d }; diff --git a/examples/jsm/geometries/ParametricGeometries.js b/examples/jsm/geometries/ParametricGeometries.js deleted file mode 100644 index 6716735704cb66..00000000000000 --- a/examples/jsm/geometries/ParametricGeometries.js +++ /dev/null @@ -1,254 +0,0 @@ -import { - Curve, - Vector3 -} from 'three'; - -import { ParametricGeometry } from './ParametricGeometry.js'; - -/** - * Experimenting of primitive geometry creation using Surface Parametric equations - */ - -const ParametricGeometries = { - - klein: function ( v, u, target ) { - - u *= Math.PI; - v *= 2 * Math.PI; - - u = u * 2; - let x, z; - if ( u < Math.PI ) { - - x = 3 * Math.cos( u ) * ( 1 + Math.sin( u ) ) + ( 2 * ( 1 - Math.cos( u ) / 2 ) ) * Math.cos( u ) * Math.cos( v ); - z = - 8 * Math.sin( u ) - 2 * ( 1 - Math.cos( u ) / 2 ) * Math.sin( u ) * Math.cos( v ); - - } else { - - x = 3 * Math.cos( u ) * ( 1 + Math.sin( u ) ) + ( 2 * ( 1 - Math.cos( u ) / 2 ) ) * Math.cos( v + Math.PI ); - z = - 8 * Math.sin( u ); - - } - - const y = - 2 * ( 1 - Math.cos( u ) / 2 ) * Math.sin( v ); - - target.set( x, y, z ); - - }, - - plane: function ( width, height ) { - - return function ( u, v, target ) { - - const x = u * width; - const y = 0; - const z = v * height; - - target.set( x, y, z ); - - }; - - }, - - mobius: function ( u, t, target ) { - - // flat mobius strip - // http://www.wolframalpha.com/input/?i=M%C3%B6bius+strip+parametric+equations&lk=1&a=ClashPrefs_*Surface.MoebiusStrip.SurfaceProperty.ParametricEquations- - u = u - 0.5; - const v = 2 * Math.PI * t; - - const a = 2; - - const x = Math.cos( v ) * ( a + u * Math.cos( v / 2 ) ); - const y = Math.sin( v ) * ( a + u * Math.cos( v / 2 ) ); - const z = u * Math.sin( v / 2 ); - - target.set( x, y, z ); - - }, - - mobius3d: function ( u, t, target ) { - - // volumetric mobius strip - - u *= Math.PI; - t *= 2 * Math.PI; - - u = u * 2; - const phi = u / 2; - const major = 2.25, a = 0.125, b = 0.65; - - let x = a * Math.cos( t ) * Math.cos( phi ) - b * Math.sin( t ) * Math.sin( phi ); - const z = a * Math.cos( t ) * Math.sin( phi ) + b * Math.sin( t ) * Math.cos( phi ); - const y = ( major + x ) * Math.sin( u ); - x = ( major + x ) * Math.cos( u ); - - target.set( x, y, z ); - - } - -}; - - -/********************************************* - * - * Parametric Replacement for TubeGeometry - * - *********************************************/ - -ParametricGeometries.TubeGeometry = class TubeGeometry extends ParametricGeometry { - - constructor( path, segments = 64, radius = 1, segmentsRadius = 8, closed = false ) { - - const numpoints = segments + 1; - - const frames = path.computeFrenetFrames( segments, closed ), - tangents = frames.tangents, - normals = frames.normals, - binormals = frames.binormals; - - const position = new Vector3(); - - function ParametricTube( u, v, target ) { - - v *= 2 * Math.PI; - - const i = Math.floor( u * ( numpoints - 1 ) ); - - path.getPointAt( u, position ); - - const normal = normals[ i ]; - const binormal = binormals[ i ]; - - const cx = - radius * Math.cos( v ); // TODO: Hack: Negating it so it faces outside. - const cy = radius * Math.sin( v ); - - position.x += cx * normal.x + cy * binormal.x; - position.y += cx * normal.y + cy * binormal.y; - position.z += cx * normal.z + cy * binormal.z; - - target.copy( position ); - - } - - super( ParametricTube, segments, segmentsRadius ); - - // proxy internals - - this.tangents = tangents; - this.normals = normals; - this.binormals = binormals; - - this.path = path; - this.segments = segments; - this.radius = radius; - this.segmentsRadius = segmentsRadius; - this.closed = closed; - - } - -}; - - -/********************************************* - * - * Parametric Replacement for TorusKnotGeometry - * - *********************************************/ -ParametricGeometries.TorusKnotGeometry = class TorusKnotGeometry extends ParametricGeometries.TubeGeometry { - - constructor( radius = 200, tube = 40, segmentsT = 64, segmentsR = 8, p = 2, q = 3 ) { - - class TorusKnotCurve extends Curve { - - getPoint( t, optionalTarget = new Vector3() ) { - - const point = optionalTarget; - - t *= Math.PI * 2; - - const r = 0.5; - - const x = ( 1 + r * Math.cos( q * t ) ) * Math.cos( p * t ); - const y = ( 1 + r * Math.cos( q * t ) ) * Math.sin( p * t ); - const z = r * Math.sin( q * t ); - - return point.set( x, y, z ).multiplyScalar( radius ); - - } - - } - - const segments = segmentsT; - const radiusSegments = segmentsR; - const extrudePath = new TorusKnotCurve(); - - super( extrudePath, segments, tube, radiusSegments, true, false ); - - this.radius = radius; - this.tube = tube; - this.segmentsT = segmentsT; - this.segmentsR = segmentsR; - this.p = p; - this.q = q; - - } - -}; - -/********************************************* - * - * Parametric Replacement for SphereGeometry - * - *********************************************/ -ParametricGeometries.SphereGeometry = class SphereGeometry extends ParametricGeometry { - - constructor( size, u, v ) { - - function sphere( u, v, target ) { - - u *= Math.PI; - v *= 2 * Math.PI; - - const x = size * Math.sin( u ) * Math.cos( v ); - const y = size * Math.sin( u ) * Math.sin( v ); - const z = size * Math.cos( u ); - - target.set( x, y, z ); - - } - - super( sphere, u, v ); - - } - -}; - - -/********************************************* - * - * Parametric Replacement for PlaneGeometry - * - *********************************************/ - -ParametricGeometries.PlaneGeometry = class PlaneGeometry extends ParametricGeometry { - - constructor( width, depth, segmentsWidth, segmentsDepth ) { - - function plane( u, v, target ) { - - const x = u * width; - const y = 0; - const z = v * depth; - - target.set( x, y, z ); - - } - - super( plane, segmentsWidth, segmentsDepth ); - - } - -}; - -export { ParametricGeometries }; diff --git a/examples/jsm/geometries/ParametricGeometry.js b/examples/jsm/geometries/ParametricGeometry.js index 8f726b24704358..f87a6e39ce8f21 100644 --- a/examples/jsm/geometries/ParametricGeometry.js +++ b/examples/jsm/geometries/ParametricGeometry.js @@ -1,22 +1,46 @@ -/** - * Parametric Surfaces Geometry - * based on the brilliant article by @prideout https://prideout.net/blog/old/blog/index.html@p=44.html - */ - import { BufferGeometry, Float32BufferAttribute, Vector3 } from 'three'; +/** + * This class can be used to generate a geometry based on a parametric surface. + * + * Reference: [Mesh Generation with Python]{@link https://prideout.net/blog/old/blog/index.html@p=44.html} + * + * ```js + * const geometry = new THREE.ParametricGeometry( klein, 25, 25 ); + * const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); + * const klein = new THREE.Mesh( geometry, material ); + * scene.add( klein ); + * ``` + * + * @augments BufferGeometry + * @three_import import { ParametricGeometry } from 'three/addons/geometries/ParametricGeometry.js'; + */ class ParametricGeometry extends BufferGeometry { + /** + * Constructs a new parametric geometry. + * + * @param {ParametricGeometry~Func} func - The parametric function. Default is a function that generates a curved plane surface. + * @param {number} [slices=8] - The number of slices to use for the parametric function. + * @param {number} [stacks=8] - The stacks of slices to use for the parametric function. + */ constructor( func = ( u, v, target ) => target.set( u, v, Math.cos( u ) * Math.sin( v ) ), slices = 8, stacks = 8 ) { super(); this.type = 'ParametricGeometry'; + /** + * Holds the constructor parameters that have been + * used to generate the geometry. Any modification + * after instantiation does not change the geometry. + * + * @type {Object} + */ this.parameters = { func: func, slices: slices, @@ -136,4 +160,13 @@ class ParametricGeometry extends BufferGeometry { } +/** + * Parametric function definition of `ParametricGeometry`. + * + * @callback ParametricGeometry~Func + * @param {number} u - The `u` coordinate on the surface in the range `[0,1]`. + * @param {number} v - The `v` coordinate on the surface in the range `[0,1]`. + * @param {Vector3} target - The target vector that is used to store the method's result. + */ + export { ParametricGeometry }; diff --git a/examples/jsm/geometries/RoundedBoxGeometry.js b/examples/jsm/geometries/RoundedBoxGeometry.js index 8baa16822365c4..a8cb9643a2a45d 100644 --- a/examples/jsm/geometries/RoundedBoxGeometry.js +++ b/examples/jsm/geometries/RoundedBoxGeometry.js @@ -38,8 +38,30 @@ function getUv( faceDirVector, normal, uvAxis, projectionAxis, radius, sideLengt } +/** + * A special type of box geometry with rounded corners and edges. + * + * ```js + * const geometry = new THREE.RoundedBoxGeometry(); + * const material = new THREE.MeshStandardMaterial( { color: 0x00ff00 } ); + * const cube = new THREE.Mesh( geometry, material ); + * scene.add( cube ); + * ``` + * + * @augments BoxGeometry + * @three_import import { RoundedBoxGeometry } from 'three/addons/geometries/RoundedBoxGeometry.js'; + */ class RoundedBoxGeometry extends BoxGeometry { + /** + * Constructs a new rounded box geometry. + * + * @param {number} [width=1] - The width. That is, the length of the edges parallel to the X axis. + * @param {number} [height=1] - The height. That is, the length of the edges parallel to the Y axis. + * @param {number} [depth=1] - The depth. That is, the length of the edges parallel to the Z axis. + * @param {number} [segments=2] - Number of segmented that form the rounded corners. + * @param {number} [radius=0.1] - The radius of the rounded corners. + */ constructor( width = 1, height = 1, depth = 1, segments = 2, radius = 0.1 ) { // ensure segments is odd so we have a plane connecting the rounded corners @@ -48,7 +70,7 @@ class RoundedBoxGeometry extends BoxGeometry { // ensure radius isn't bigger than shortest side radius = Math.min( width / 2, height / 2, depth / 2, radius ); - super( 1, 1, 1, segments, segments, segments ); + super( width, height, depth, segments, segments, segments ); // if we just have one segment we're the same as a regular box if ( segments === 1 ) return; diff --git a/examples/jsm/geometries/TeapotGeometry.js b/examples/jsm/geometries/TeapotGeometry.js index b6b5ff120330de..72cc009e25b258 100644 --- a/examples/jsm/geometries/TeapotGeometry.js +++ b/examples/jsm/geometries/TeapotGeometry.js @@ -9,53 +9,38 @@ import { /** * Tessellates the famous Utah teapot database by Martin Newell into triangles. * - * Parameters: size = 50, segments = 10, bottom = true, lid = true, body = true, - * fitLid = false, blinn = true - * - * size is a relative scale: I've scaled the teapot to fit vertically between -1 and 1. - * Think of it as a "radius". - * segments - number of line segments to subdivide each patch edge; - * 1 is possible but gives degenerates, so two is the real minimum. - * bottom - boolean, if true (default) then the bottom patches are added. Some consider - * adding the bottom heresy, so set this to "false" to adhere to the One True Way. - * lid - to remove the lid and look inside, set to true. - * body - to remove the body and leave the lid, set this and "bottom" to false. - * fitLid - the lid is a tad small in the original. This stretches it a bit so you can't - * see the teapot's insides through the gap. - * blinn - Jim Blinn scaled the original data vertically by dividing by about 1.3 to look - * nicer. If you want to see the original teapot, similar to the real-world model, set - * this to false. True by default. - * See http://en.wikipedia.org/wiki/File:Original_Utah_Teapot.jpg for the original - * real-world teapot (from http://en.wikipedia.org/wiki/Utah_teapot). - * - * Note that the bottom (the last four patches) is not flat - blame Frank Crow, not me. - * * The teapot should normally be rendered as a double sided object, since for some * patches both sides can be seen, e.g., the gap around the lid and inside the spout. * - * Segments 'n' determines the number of triangles output. - * Total triangles = 32*2*n*n - 8*n [degenerates at the top and bottom cusps are deleted] + * Segments 'n' determines the number of triangles output. Total triangles = 32*2*n*n - 8*n + * (degenerates at the top and bottom cusps are deleted). * - * size_factor # triangles - * 1 56 - * 2 240 - * 3 552 - * 4 992 + * Code based on [SPD software]{@link http://tog.acm.org/resources/SPD/} + * Created for the Udacity course [Interactive Rendering]{@link http://bit.ly/ericity} * - * 10 6320 - * 20 25440 - * 30 57360 - * - * Code converted from my ancient SPD software, http://tog.acm.org/resources/SPD/ - * Created for the Udacity course "Interactive Rendering", http://bit.ly/ericity - * YouTube video on teapot history: https://www.youtube.com/watch?v=DxMfblPzFNc - * - * See https://en.wikipedia.org/wiki/Utah_teapot for the history of the teapot + * ```js + * const geometry = new TeapotGeometry( 50, 18 ); + * const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); + * const teapot = new THREE.Mesh( geometry, material ); + * scene.add( teapot ); + * ``` * + * @augments BufferGeometry + * @three_import import { TeapotGeometry } from 'three/addons/geometries/TeapotGeometry.js'; */ - class TeapotGeometry extends BufferGeometry { + /** + * Constructs a new teapot geometry. + * + * @param {number} [size=50] - Relative scale of the teapot. + * @param {number} [segments=10] - Number of line segments to subdivide each patch edge. + * @param {boolean} [bottom=true] - Whether the bottom of the teapot is generated or not. + * @param {boolean} [lid=true] - Whether the lid is generated or not. + * @param {boolean} [body=true] - Whether the body is generated or not. + * @param {boolean} [fitLid=true] - Whether the lid is slightly stretched to prevent gaps between the body and lid or not. + * @param {boolean} [blinn=true] - Whether the teapot is scaled vertically for better aesthetics or not. + */ constructor( size = 50, segments = 10, bottom = true, lid = true, body = true, fitLid = true, blinn = true ) { // 32 * 4 * 4 Bezier spline patches diff --git a/examples/jsm/geometries/TextGeometry.js b/examples/jsm/geometries/TextGeometry.js index 5275c03be78f24..9b253733b77ce9 100644 --- a/examples/jsm/geometries/TextGeometry.js +++ b/examples/jsm/geometries/TextGeometry.js @@ -1,26 +1,38 @@ +import { + ExtrudeGeometry +} from 'three'; + /** - * Text = 3D Text + * A class for generating text as a single geometry. It is constructed by providing a string of text, and a set of + * parameters consisting of a loaded font and extrude settings. * - * parameters = { - * font: , // font + * See the {@link FontLoader} page for additional details. * - * size: , // size of the text - * height: , // thickness to extrude text - * curveSegments: , // number of points on the curves + * `TextGeometry` uses [typeface.json]{@link http://gero3.github.io/facetype.js/} generated fonts. + * Some existing fonts can be found located in `/examples/fonts`. * - * bevelEnabled: , // turn on bevel - * bevelThickness: , // how deep into text bevel goes - * bevelSize: , // how far from text outline (including bevelOffset) is bevel - * bevelOffset: // how far from text outline does bevel start - * } + * ```js + * const loader = new FontLoader(); + * const font = await loader.loadAsync( 'fonts/helvetiker_regular.typeface.json' ); + * const geometry = new TextGeometry( 'Hello three.js!', { + * font: font, + * size: 80, + * depth: 5, + * curveSegments: 12 + * } ); + * ``` + * + * @augments ExtrudeGeometry + * @three_import import { TextGeometry } from 'three/addons/geometries/TextGeometry.js'; */ - -import { - ExtrudeGeometry -} from 'three'; - class TextGeometry extends ExtrudeGeometry { + /** + * Constructs a new text geometry. + * + * @param {string} text - The text that should be transformed into a geometry. + * @param {TextGeometry~Options} [parameters] - The text settings. + */ constructor( text, parameters = {} ) { const font = parameters.font; @@ -33,12 +45,9 @@ class TextGeometry extends ExtrudeGeometry { const shapes = font.generateShapes( text, parameters.size ); - // translate parameters to ExtrudeGeometry API - - parameters.depth = parameters.height !== undefined ? parameters.height : 50; - // defaults + if ( parameters.depth === undefined ) parameters.depth = 50; if ( parameters.bevelThickness === undefined ) parameters.bevelThickness = 10; if ( parameters.bevelSize === undefined ) parameters.bevelSize = 8; if ( parameters.bevelEnabled === undefined ) parameters.bevelEnabled = false; @@ -53,5 +62,22 @@ class TextGeometry extends ExtrudeGeometry { } +/** + * Represents the `options` type of the geometry's constructor. + * + * @typedef {Object} TextGeometry~Options + * @property {Font} [font] - The font. + * @property {number} [size=100] - The text size. + * @property {number} [depth=50] - Depth to extrude the shape. + * @property {number} [curveSegments=12] - Number of points on the curves. + * @property {number} [steps=1] - Number of points used for subdividing segments along the depth of the extruded spline. + * @property {boolean} [bevelEnabled=false] - Whether to beveling to the shape or not. + * @property {number} [bevelThickness=10] - How deep into the original shape the bevel goes. + * @property {number} [bevelSize=8] - Distance from the shape outline that the bevel extends. + * @property {number} [bevelOffset=0] - Distance from the shape outline that the bevel starts. + * @property {number} [bevelSegments=3] - Number of bevel layers. + * @property {?Curve} [extrudePath=null] - A 3D spline path along which the shape should be extruded. Bevels not supported for path extrusion. + * @property {Object} [UVGenerator] - An object that provides UV generator functions for custom UV generation. + **/ export { TextGeometry }; diff --git a/examples/jsm/helpers/LightProbeHelper.js b/examples/jsm/helpers/LightProbeHelper.js index 812048828cf108..e6da80faa45945 100644 --- a/examples/jsm/helpers/LightProbeHelper.js +++ b/examples/jsm/helpers/LightProbeHelper.js @@ -4,9 +4,29 @@ import { SphereGeometry } from 'three'; +/** + * Renders a sphere to visualize a light probe in the scene. + * + * This helper can only be used with {@link WebGLRenderer}. + * When using {@link WebGPURenderer}, import from `LightProbeHelperGPU.js`. + * + * ```js + * const helper = new LightProbeHelper( lightProbe ); + * scene.add( helper ); + * ``` + * + * @augments Mesh + * @three_import import { LightProbeHelper } from 'three/addons/helpers/LightProbeHelper.js'; + */ class LightProbeHelper extends Mesh { - constructor( lightProbe, size ) { + /** + * Constructs a new light probe helper. + * + * @param {LightProbe} lightProbe - The light probe to visualize. + * @param {number} [size=1] - The size of the helper. + */ + constructor( lightProbe, size = 1 ) { const material = new ShaderMaterial( { @@ -20,79 +40,78 @@ class LightProbeHelper extends Mesh { }, - vertexShader: [ + vertexShader: /* glsl */` - 'varying vec3 vNormal;', + varying vec3 vNormal; - 'void main() {', + void main() { - ' vNormal = normalize( normalMatrix * normal );', + vNormal = normalize( normalMatrix * normal ); - ' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - '}', + } - ].join( '\n' ), + `, - fragmentShader: [ + fragmentShader: /* glsl */` - '#define RECIPROCAL_PI 0.318309886', + #define RECIPROCAL_PI 0.318309886 - 'vec3 inverseTransformDirection( in vec3 normal, in mat4 matrix ) {', + vec3 inverseTransformDirection( in vec3 normal, in mat4 matrix ) { - ' // matrix is assumed to be orthogonal', + // matrix is assumed to be orthogonal - ' return normalize( ( vec4( normal, 0.0 ) * matrix ).xyz );', + return normalize( ( vec4( normal, 0.0 ) * matrix ).xyz ); - '}', + } - '// source: https://graphics.stanford.edu/papers/envmap/envmap.pdf', - 'vec3 shGetIrradianceAt( in vec3 normal, in vec3 shCoefficients[ 9 ] ) {', + // source: https://graphics.stanford.edu/papers/envmap/envmap.pdf, + vec3 shGetIrradianceAt( in vec3 normal, in vec3 shCoefficients[ 9 ] ) { - ' // normal is assumed to have unit length', + // normal is assumed to have unit length, - ' float x = normal.x, y = normal.y, z = normal.z;', + float x = normal.x, y = normal.y, z = normal.z; - ' // band 0', - ' vec3 result = shCoefficients[ 0 ] * 0.886227;', + // band 0, + vec3 result = shCoefficients[ 0 ] * 0.886227; - ' // band 1', - ' result += shCoefficients[ 1 ] * 2.0 * 0.511664 * y;', - ' result += shCoefficients[ 2 ] * 2.0 * 0.511664 * z;', - ' result += shCoefficients[ 3 ] * 2.0 * 0.511664 * x;', + // band 1, + result += shCoefficients[ 1 ] * 2.0 * 0.511664 * y; + result += shCoefficients[ 2 ] * 2.0 * 0.511664 * z; + result += shCoefficients[ 3 ] * 2.0 * 0.511664 * x; - ' // band 2', - ' result += shCoefficients[ 4 ] * 2.0 * 0.429043 * x * y;', - ' result += shCoefficients[ 5 ] * 2.0 * 0.429043 * y * z;', - ' result += shCoefficients[ 6 ] * ( 0.743125 * z * z - 0.247708 );', - ' result += shCoefficients[ 7 ] * 2.0 * 0.429043 * x * z;', - ' result += shCoefficients[ 8 ] * 0.429043 * ( x * x - y * y );', + // band 2, + result += shCoefficients[ 4 ] * 2.0 * 0.429043 * x * y; + result += shCoefficients[ 5 ] * 2.0 * 0.429043 * y * z; + result += shCoefficients[ 6 ] * ( 0.743125 * z * z - 0.247708 ); + result += shCoefficients[ 7 ] * 2.0 * 0.429043 * x * z; + result += shCoefficients[ 8 ] * 0.429043 * ( x * x - y * y ); + return result; - ' return result;', + } - '}', + uniform vec3 sh[ 9 ]; // sh coefficients - 'uniform vec3 sh[ 9 ]; // sh coefficients', + uniform float intensity; // light probe intensity - 'uniform float intensity; // light probe intensity', + varying vec3 vNormal; - 'varying vec3 vNormal;', + void main() { - 'void main() {', + vec3 normal = normalize( vNormal ); - ' vec3 normal = normalize( vNormal );', + vec3 worldNormal = inverseTransformDirection( normal, viewMatrix ); - ' vec3 worldNormal = inverseTransformDirection( normal, viewMatrix );', + vec3 irradiance = shGetIrradianceAt( worldNormal, sh ); - ' vec3 irradiance = shGetIrradianceAt( worldNormal, sh );', + vec3 outgoingLight = RECIPROCAL_PI * irradiance * intensity; - ' vec3 outgoingLight = RECIPROCAL_PI * irradiance * intensity;', + gl_FragColor = linearToOutputTexel( vec4( outgoingLight, 1.0 ) ); - ' gl_FragColor = linearToOutputTexel( vec4( outgoingLight, 1.0 ) );', + } - '}' - - ].join( '\n' ) + `, } ); @@ -100,7 +119,19 @@ class LightProbeHelper extends Mesh { super( geometry, material ); + /** + * The light probe to visualize. + * + * @type {LightProbe} + */ this.lightProbe = lightProbe; + + /** + * The size of the helper. + * + * @type {number} + * @default 1 + */ this.size = size; this.type = 'LightProbeHelper'; @@ -108,6 +139,10 @@ class LightProbeHelper extends Mesh { } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ dispose() { this.geometry.dispose(); diff --git a/examples/jsm/helpers/LightProbeHelperGPU.js b/examples/jsm/helpers/LightProbeHelperGPU.js new file mode 100644 index 00000000000000..6d6d45b431b71a --- /dev/null +++ b/examples/jsm/helpers/LightProbeHelperGPU.js @@ -0,0 +1,102 @@ +import { + Mesh, + NodeMaterial, + SphereGeometry +} from 'three'; +import { float, Fn, getShIrradianceAt, normalWorld, uniformArray, uniform, vec4 } from 'three/tsl'; + +/** + * Renders a sphere to visualize a light probe in the scene. + * + * This helper can only be used with {@link WebGPURenderer}. + * When using {@link WebGLRenderer}, import from `LightProbeHelper.js`. + * + * ```js + * const helper = new LightProbeHelper( lightProbe ); + * scene.add( helper ); + * ``` + * + * @private + * @augments Mesh + * @three_import import { LightProbeHelper } from 'three/addons/helpers/LightProbeHelperGPU.js'; + */ +class LightProbeHelper extends Mesh { + + /** + * Constructs a new light probe helper. + * + * @param {LightProbe} lightProbe - The light probe to visualize. + * @param {number} [size=1] - The size of the helper. + */ + constructor( lightProbe, size = 1 ) { + + const sh = uniformArray( lightProbe.sh.coefficients ); + const intensity = uniform( lightProbe.intensity ); + + const RECIPROCAL_PI = float( 1 / Math.PI ); + + const fragmentNode = Fn( () => { + + const irradiance = getShIrradianceAt( normalWorld, sh ); + + const outgoingLight = RECIPROCAL_PI.mul( irradiance ).mul( intensity ); + + return vec4( outgoingLight, 1.0 ); + + } )(); + + const material = new NodeMaterial(); + material.fragmentNode = fragmentNode; + + const geometry = new SphereGeometry( 1, 32, 16 ); + + super( geometry, material ); + + /** + * The light probe to visualize. + * + * @type {LightProbe} + */ + this.lightProbe = lightProbe; + + /** + * The size of the helper. + * + * @type {number} + * @default 1 + */ + this.size = size; + this.type = 'LightProbeHelper'; + + this._intensity = intensity; + this._sh = sh; + + this.onBeforeRender(); + + } + + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose() { + + this.geometry.dispose(); + this.material.dispose(); + + } + + onBeforeRender() { + + this.position.copy( this.lightProbe.position ); + + this.scale.set( 1, 1, 1 ).multiplyScalar( this.size ); + + this._intensity.value = this.lightProbe.intensity; + this._sh.array = this.lightProbe.sh.coefficients; + + } + +} + +export { LightProbeHelper }; diff --git a/examples/jsm/helpers/OctreeHelper.js b/examples/jsm/helpers/OctreeHelper.js index 972942c178ed3f..076abf1685a86e 100644 --- a/examples/jsm/helpers/OctreeHelper.js +++ b/examples/jsm/helpers/OctreeHelper.js @@ -5,13 +5,41 @@ import { LineBasicMaterial } from 'three'; +/** + * A helper for visualizing an Octree. + * + * ```js + * const helper = new OctreeHelper( octree ); + * scene.add( helper ); + * ``` + * + * @augments LineSegments + * @three_import import { OctreeHelper } from 'three/addons/helpers/OctreeHelper.js'; + */ class OctreeHelper extends LineSegments { + /** + * Constructs a new Octree helper. + * + * @param {Octree} octree - The octree to visualize. + * @param {number|Color|string} [color=0xffff00] - The helper's color. + */ constructor( octree, color = 0xffff00 ) { super( new BufferGeometry(), new LineBasicMaterial( { color: color, toneMapped: false } ) ); + /** + * The octree to visualize. + * + * @type {Octree} + */ this.octree = octree; + + /** + * The helper's color. + * + * @type {number|Color|string} + */ this.color = color; this.type = 'OctreeHelper'; @@ -20,6 +48,10 @@ class OctreeHelper extends LineSegments { } + /** + * Updates the helper. This method must be called whenever the Octree's + * structure is changed. + */ update() { const vertices = []; @@ -61,6 +93,10 @@ class OctreeHelper extends LineSegments { } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ dispose() { this.geometry.dispose(); diff --git a/examples/jsm/helpers/PositionalAudioHelper.js b/examples/jsm/helpers/PositionalAudioHelper.js index 0a20ea5ecc6bc5..dfd9ca8dcabde2 100644 --- a/examples/jsm/helpers/PositionalAudioHelper.js +++ b/examples/jsm/helpers/PositionalAudioHelper.js @@ -6,8 +6,33 @@ import { MathUtils } from 'three'; +/** + * This helper displays the directional cone of a positional audio. + * + * `PositionalAudioHelper` must be added as a child of the positional audio. + * + * ```js + * const positionalAudio = new THREE.PositionalAudio( listener ); + * positionalAudio.setDirectionalCone( 180, 230, 0.1 ); + * scene.add( positionalAudio ); + * + * const helper = new PositionalAudioHelper( positionalAudio ); + * positionalAudio.add( helper ); + * ``` + * + * @augments Line + * @three_import import { PositionalAudioHelper } from 'three/addons/helpers/PositionalAudioHelper.js'; + */ class PositionalAudioHelper extends Line { + /** + * Constructs a new positional audio helper. + * + * @param {PositionalAudio} audio - The audio to visualize. + * @param {number} [range=1] - The range of the directional cone. + * @param {number} [divisionsInnerAngle=16] - The number of divisions of the inner part of the directional cone. + * @param {number} [divisionsOuterAngle=2] The number of divisions of the outer part of the directional cone. + */ constructor( audio, range = 1, divisionsInnerAngle = 16, divisionsOuterAngle = 2 ) { const geometry = new BufferGeometry(); @@ -20,16 +45,47 @@ class PositionalAudioHelper extends Line { super( geometry, [ materialOuterAngle, materialInnerAngle ] ); + /** + * The audio to visualize. + * + * @type {PositionalAudio} + */ this.audio = audio; + + /** + * The range of the directional cone. + * + * @type {number} + * @default 1 + */ this.range = range; + + /** + * The number of divisions of the inner part of the directional cone. + * + * @type {number} + * @default 16 + */ this.divisionsInnerAngle = divisionsInnerAngle; + + /** + * The number of divisions of the outer part of the directional cone. + * + * @type {number} + * @default 2 + */ this.divisionsOuterAngle = divisionsOuterAngle; + this.type = 'PositionalAudioHelper'; this.update(); } + /** + * Updates the helper. This method must be called whenever the directional cone + * of the positional audio is changed. + */ update() { const audio = this.audio; @@ -95,6 +151,10 @@ class PositionalAudioHelper extends Line { } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ dispose() { this.geometry.dispose(); diff --git a/examples/jsm/helpers/RapierHelper.js b/examples/jsm/helpers/RapierHelper.js new file mode 100644 index 00000000000000..f3b37b682944f3 --- /dev/null +++ b/examples/jsm/helpers/RapierHelper.js @@ -0,0 +1,59 @@ +import { LineSegments, LineBasicMaterial, BufferAttribute } from 'three'; +/** + * This class displays all Rapier Colliders in outline. + * + * @augments LineSegments + * @three_import import { RapierHelper } from 'three/addons/helpers/RapierHelper.js'; + */ +class RapierHelper extends LineSegments { + + /** + * Constructs a new Rapier debug helper. + * + * @param {RAPIER.world} world - The Rapier world to visualize. + */ + constructor( world ) { + + super(); + + /** + * The Rapier world to visualize. + * + * @type {RAPIER.world} + */ + this.world = world; + + this.material = new LineBasicMaterial( { vertexColors: true } ); + this.frustumCulled = false; + + } + + /** + * Call this in the render loop to update the outlines. + */ + update() { + + const { vertices, colors } = this.world.debugRender(); + + this.geometry.deleteAttribute( 'position' ); + this.geometry.deleteAttribute( 'color' ); + + this.geometry.setAttribute( 'position', new BufferAttribute( vertices, 3 ) ); + this.geometry.setAttribute( 'color', new BufferAttribute( colors, 4 ) ); + + } + + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose() { + + this.geometry.dispose(); + this.material.dispose(); + + } + +} + +export { RapierHelper }; diff --git a/examples/jsm/helpers/RectAreaLightHelper.js b/examples/jsm/helpers/RectAreaLightHelper.js index 416fe1bd4554e6..e5b820578b25b2 100644 --- a/examples/jsm/helpers/RectAreaLightHelper.js +++ b/examples/jsm/helpers/RectAreaLightHelper.js @@ -9,11 +9,28 @@ import { } from 'three'; /** - * This helper must be added as a child of the light + * Creates a visual aid for rect area lights. + * + * `RectAreaLightHelper` must be added as a child of the light. + * + * ```js + * const light = new THREE.RectAreaLight( 0xffffbb, 1.0, 5, 5 ); + * const helper = new RectAreaLightHelper( light ); + * light.add( helper ); + * ``` + * + * @augments Line + * @three_import import { RectAreaLightHelper } from 'three/addons/helpers/RectAreaLightHelper.js'; */ - class RectAreaLightHelper extends Line { + /** + * Constructs a new rect area light helper. + * + * @param {RectAreaLight} light - The light to visualize. + * @param {number|Color|string} [color] - The helper's color. + * If this is not the set, the helper will take the color of the light. + */ constructor( light, color ) { const positions = [ 1, 1, 0, - 1, 1, 0, - 1, - 1, 0, 1, - 1, 0, 1, 1, 0 ]; @@ -26,8 +43,20 @@ class RectAreaLightHelper extends Line { super( geometry, material ); + /** + * The light to visualize. + * + * @type {RectAreaLight} + */ this.light = light; - this.color = color; // optional hardwired color for the helper + + /** + * The helper's color. If `undefined`, the helper will take the color of the light. + * + * @type {number|Color|string|undefined} + */ + this.color = color; + this.type = 'RectAreaLightHelper'; // @@ -71,6 +100,10 @@ class RectAreaLightHelper extends Line { } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ dispose() { this.geometry.dispose(); diff --git a/examples/jsm/helpers/TextureHelper.js b/examples/jsm/helpers/TextureHelper.js new file mode 100644 index 00000000000000..8ea9ee6ea95c2f --- /dev/null +++ b/examples/jsm/helpers/TextureHelper.js @@ -0,0 +1,265 @@ +import { + BoxGeometry, + BufferAttribute, + DoubleSide, + Mesh, + PlaneGeometry, + ShaderMaterial, + Vector3, +} from 'three'; +import { mergeGeometries } from '../utils/BufferGeometryUtils.js'; + +/** + * A helper that can be used to display any type of texture for + * debugging purposes. Depending on the type of texture (2D, 3D, Array), + * the helper becomes a plane or box mesh. + * + * This helper can only be used with {@link WebGLRenderer}. + * When using {@link WebGPURenderer}, import from `TextureHelperGPU.js`. + * + * @augments Mesh + * @three_import import { TextureHelper } from 'three/addons/helpers/TextureHelper.js'; + */ +class TextureHelper extends Mesh { + + /** + * Constructs a new texture helper. + * + * @param {Texture} texture - The texture to visualize. + * @param {number} [width=1] - The helper's width. + * @param {number} [height=1] - The helper's height. + * @param {number} [depth=1] - The helper's depth. + */ + constructor( texture, width = 1, height = 1, depth = 1 ) { + + const material = new ShaderMaterial( { + + type: 'TextureHelperMaterial', + + side: DoubleSide, + transparent: true, + + uniforms: { + + map: { value: texture }, + alpha: { value: getAlpha( texture ) }, + + }, + + vertexShader: [ + + 'attribute vec3 uvw;', + + 'varying vec3 vUvw;', + + 'void main() {', + + ' vUvw = uvw;', + + ' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', + + '}', + + ].join( '\n' ), + + fragmentShader: [ + + 'precision highp float;', + + 'precision highp sampler2DArray;', + + 'precision highp sampler3D;', + + 'uniform {samplerType} map;', + + 'uniform float alpha;', + + 'varying vec3 vUvw;', + + 'vec4 textureHelper( in sampler2D map ) { return texture( map, vUvw.xy ); }', + + 'vec4 textureHelper( in sampler2DArray map ) { return texture( map, vUvw ); }', + + 'vec4 textureHelper( in sampler3D map ) { return texture( map, vUvw ); }', + + 'vec4 textureHelper( in samplerCube map ) { return texture( map, vUvw ); }', + + 'void main() {', + + ' gl_FragColor = linearToOutputTexel( vec4( textureHelper( map ).xyz, alpha ) );', + + '}' + + ].join( '\n' ).replace( '{samplerType}', getSamplerType( texture ) ) + + } ); + + const geometry = texture.isCubeTexture + ? createCubeGeometry( width, height, depth ) + : createSliceGeometry( texture, width, height, depth ); + + super( geometry, material ); + + /** + * The texture to visualize. + * + * @type {Texture} + */ + this.texture = texture; + this.type = 'TextureHelper'; + + } + + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose() { + + this.geometry.dispose(); + this.material.dispose(); + + } + +} + +function getSamplerType( texture ) { + + if ( texture.isCubeTexture ) { + + return 'samplerCube'; + + } else if ( texture.isDataArrayTexture || texture.isCompressedArrayTexture ) { + + return 'sampler2DArray'; + + } else if ( texture.isData3DTexture || texture.isCompressed3DTexture ) { + + return 'sampler3D'; + + } else { + + return 'sampler2D'; + + } + +} + +function getImageCount( texture ) { + + if ( texture.isCubeTexture ) { + + return 6; + + } else if ( texture.isDataArrayTexture || texture.isCompressedArrayTexture ) { + + return texture.image.depth; + + } else if ( texture.isData3DTexture || texture.isCompressed3DTexture ) { + + return texture.image.depth; + + } else { + + return 1; + + } + +} + +function getAlpha( texture ) { + + if ( texture.isCubeTexture ) { + + return 1; + + } else if ( texture.isDataArrayTexture || texture.isCompressedArrayTexture ) { + + return Math.max( 1 / texture.image.depth, 0.25 ); + + } else if ( texture.isData3DTexture || texture.isCompressed3DTexture ) { + + return Math.max( 1 / texture.image.depth, 0.25 ); + + } else { + + return 1; + + } + +} + +function createCubeGeometry( width, height, depth ) { + + const geometry = new BoxGeometry( width, height, depth ); + + const position = geometry.attributes.position; + const uv = geometry.attributes.uv; + const uvw = new BufferAttribute( new Float32Array( uv.count * 3 ), 3 ); + + const _direction = new Vector3(); + + for ( let j = 0, jl = uv.count; j < jl; ++ j ) { + + _direction.fromBufferAttribute( position, j ).normalize(); + + const u = _direction.x; + const v = _direction.y; + const w = _direction.z; + + uvw.setXYZ( j, u, v, w ); + + } + + geometry.deleteAttribute( 'uv' ); + geometry.setAttribute( 'uvw', uvw ); + + return geometry; + +} + +function createSliceGeometry( texture, width, height, depth ) { + + const sliceCount = getImageCount( texture ); + + const geometries = []; + + for ( let i = 0; i < sliceCount; ++ i ) { + + const geometry = new PlaneGeometry( width, height ); + + if ( sliceCount > 1 ) { + + geometry.translate( 0, 0, depth * ( i / ( sliceCount - 1 ) - 0.5 ) ); + + } + + const uv = geometry.attributes.uv; + const uvw = new BufferAttribute( new Float32Array( uv.count * 3 ), 3 ); + + for ( let j = 0, jl = uv.count; j < jl; ++ j ) { + + const u = uv.getX( j ); + const v = texture.flipY ? uv.getY( j ) : 1 - uv.getY( j ); + const w = sliceCount === 1 + ? 1 + : texture.isDataArrayTexture || texture.isCompressedArrayTexture + ? i + : i / ( sliceCount - 1 ); + + uvw.setXYZ( j, u, v, w ); + + } + + geometry.deleteAttribute( 'uv' ); + geometry.setAttribute( 'uvw', uvw ); + + geometries.push( geometry ); + + } + + return mergeGeometries( geometries ); + +} + +export { TextureHelper }; diff --git a/examples/jsm/helpers/TextureHelperGPU.js b/examples/jsm/helpers/TextureHelperGPU.js new file mode 100644 index 00000000000000..fba2b965a80866 --- /dev/null +++ b/examples/jsm/helpers/TextureHelperGPU.js @@ -0,0 +1,214 @@ +import { + NodeMaterial, + BoxGeometry, + BufferAttribute, + Mesh, + PlaneGeometry, + DoubleSide, + Vector3, +} from 'three'; +import { texture as textureNode, cubeTexture, texture3D, float, vec4, attribute } from 'three/tsl'; +import { mergeGeometries } from '../utils/BufferGeometryUtils.js'; + +/** + * A helper that can be used to display any type of texture for + * debugging purposes. Depending on the type of texture (2D, 3D, Array), + * the helper becomes a plane or box mesh. + * + * This helper can only be used with {@link WebGPURenderer}. + * When using {@link WebGLRenderer}, import from `TextureHelper.js`. + * + * @private + * @augments Mesh + * @three_import import { TextureHelper } from 'three/addons/helpers/TextureHelperGPU.js'; + */ +class TextureHelper extends Mesh { + + /** + * Constructs a new texture helper. + * + * @param {Texture} texture - The texture to visualize. + * @param {number} [width=1] - The helper's width. + * @param {number} [height=1] - The helper's height. + * @param {number} [depth=1] - The helper's depth. + */ + constructor( texture, width = 1, height = 1, depth = 1 ) { + + const material = new NodeMaterial(); + material.side = DoubleSide; + material.transparent = true; + material.name = 'TextureHelper'; + + let colorNode; + + const uvw = attribute( 'uvw' ); + + if ( texture.isCubeTexture ) { + + colorNode = cubeTexture( texture ).sample( uvw ); + + } else if ( texture.isData3DTexture || texture.isCompressed3DTexture ) { + + colorNode = texture3D( texture ).sample( uvw ); + + } else if ( texture.isArrayTexture || texture.isDataArrayTexture || texture.isCompressedArrayTexture ) { + + colorNode = textureNode( texture ).sample( uvw.xy ).depth( uvw.z ); + + } else { + + colorNode = textureNode( texture ); + + } + + const alphaNode = float( getAlpha( texture ) ); + + material.colorNode = vec4( colorNode.rgb, alphaNode ); + + const geometry = texture.isCubeTexture + ? createCubeGeometry( width, height, depth ) + : createSliceGeometry( texture, width, height, depth ); + + super( geometry, material ); + + /** + * The texture to visualize. + * + * @type {Texture} + */ + this.texture = texture; + this.type = 'TextureHelper'; + + } + + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose() { + + this.geometry.dispose(); + this.material.dispose(); + + } + +} + +function getImageCount( texture ) { + + if ( texture.isCubeTexture ) { + + return 6; + + } else if ( texture.isArrayTexture || texture.isDataArrayTexture || texture.isCompressedArrayTexture ) { + + return texture.image.depth; + + } else if ( texture.isData3DTexture || texture.isCompressed3DTexture ) { + + return texture.image.depth; + + } else { + + return 1; + + } + +} + +function getAlpha( texture ) { + + if ( texture.isCubeTexture ) { + + return 1; + + } else if ( texture.isArrayTexture || texture.isDataArrayTexture || texture.isCompressedArrayTexture ) { + + return Math.max( 1 / texture.image.depth, 0.25 ); + + } else if ( texture.isData3DTexture || texture.isCompressed3DTexture ) { + + return Math.max( 1 / texture.image.depth, 0.25 ); + + } else { + + return 1; + + } + +} + +function createCubeGeometry( width, height, depth ) { + + const geometry = new BoxGeometry( width, height, depth ); + + const position = geometry.attributes.position; + const uv = geometry.attributes.uv; + const uvw = new BufferAttribute( new Float32Array( uv.count * 3 ), 3 ); + + const _direction = new Vector3(); + + for ( let j = 0, jl = uv.count; j < jl; ++ j ) { + + _direction.fromBufferAttribute( position, j ).normalize(); + + const u = _direction.x; + const v = _direction.y; + const w = _direction.z; + + uvw.setXYZ( j, u, v, w ); + + } + + geometry.deleteAttribute( 'uv' ); + geometry.setAttribute( 'uvw', uvw ); + + return geometry; + +} + +function createSliceGeometry( texture, width, height, depth ) { + + const sliceCount = getImageCount( texture ); + + const geometries = []; + + for ( let i = 0; i < sliceCount; ++ i ) { + + const geometry = new PlaneGeometry( width, height ); + + if ( sliceCount > 1 ) { + + geometry.translate( 0, 0, depth * ( i / ( sliceCount - 1 ) - 0.5 ) ); + + } + + const uv = geometry.attributes.uv; + const uvw = new BufferAttribute( new Float32Array( uv.count * 3 ), 3 ); + + for ( let j = 0, jl = uv.count; j < jl; ++ j ) { + + const u = uv.getX( j ); + const v = texture.flipY ? uv.getY( j ) : 1 - uv.getY( j ); + const w = sliceCount === 1 + ? 1 + : texture.isArrayTexture || texture.isDataArrayTexture || texture.isCompressedArrayTexture + ? i + : i / ( sliceCount - 1 ); + + uvw.setXYZ( j, u, v, w ); + + } + + geometry.deleteAttribute( 'uv' ); + geometry.setAttribute( 'uvw', uvw ); + + geometries.push( geometry ); + + } + + return mergeGeometries( geometries ); + +} + +export { TextureHelper }; diff --git a/examples/jsm/helpers/VertexNormalsHelper.js b/examples/jsm/helpers/VertexNormalsHelper.js index bfe41aba8644d1..284ce1edefe45f 100644 --- a/examples/jsm/helpers/VertexNormalsHelper.js +++ b/examples/jsm/helpers/VertexNormalsHelper.js @@ -11,8 +11,33 @@ const _v1 = new Vector3(); const _v2 = new Vector3(); const _normalMatrix = new Matrix3(); +/** + * Visualizes an object's vertex normals. + * + * Requires that normals have been specified in the geometry as a buffer attribute or + * have been calculated using {@link BufferGeometry#computeVertexNormals}. + * ```js + * const geometry = new THREE.BoxGeometry( 10, 10, 10, 2, 2, 2 ); + * const material = new THREE.MeshStandardMaterial(); + * const mesh = new THREE.Mesh( geometry, material ); + * scene.add( mesh ); + * + * const helper = new VertexNormalsHelper( mesh, 1, 0xff0000 ); + * scene.add( helper ); + * ``` + * + * @augments LineSegments + * @three_import import { VertexNormalsHelper } from 'three/addons/helpers/VertexNormalsHelper.js'; + */ class VertexNormalsHelper extends LineSegments { + /** + * Constructs a new vertex normals helper. + * + * @param {Object3D} object - The object for which to visualize vertex normals. + * @param {number} [size=1] - The helper's size. + * @param {number|Color|string} [color=0xff0000] - The helper's color. + */ constructor( object, size = 1, color = 0xff0000 ) { const geometry = new BufferGeometry(); @@ -24,18 +49,48 @@ class VertexNormalsHelper extends LineSegments { super( geometry, new LineBasicMaterial( { color, toneMapped: false } ) ); + /** + * The object for which to visualize vertex normals. + * + * @type {Object3D} + */ this.object = object; + + /** + * The helper's size. + * + * @type {number} + * @default 1 + */ this.size = size; - this.type = 'VertexNormalsHelper'; - // + this.type = 'VertexNormalsHelper'; + /** + * Overwritten and set to `false` since the object's world transformation + * is encoded in the helper's geometry data. + * + * @type {boolean} + * @default false + */ this.matrixAutoUpdate = false; + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isVertexNormalsHelper = true; + this.update(); } + /** + * Updates the vertex normals preview based on the object's world transform. + */ update() { this.object.updateMatrixWorld( true ); @@ -84,6 +139,10 @@ class VertexNormalsHelper extends LineSegments { } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ dispose() { this.geometry.dispose(); diff --git a/examples/jsm/helpers/VertexTangentsHelper.js b/examples/jsm/helpers/VertexTangentsHelper.js index 1ad413f65e95c7..41325432e0dc65 100644 --- a/examples/jsm/helpers/VertexTangentsHelper.js +++ b/examples/jsm/helpers/VertexTangentsHelper.js @@ -9,8 +9,28 @@ import { const _v1 = new Vector3(); const _v2 = new Vector3(); +/** + * Visualizes an object's vertex tangents. + * + * Requires that tangents have been specified in the geometry as a buffer attribute or + * have been calculated using {@link BufferGeometry#computeTangents}. + * ```js + * const helper = new VertexTangentsHelper( mesh, 1, 0xff0000 ); + * scene.add( helper ); + * ``` + * + * @augments LineSegments + * @three_import import { VertexTangentsHelper } from 'three/addons/helpers/VertexTangentsHelper.js'; + */ class VertexTangentsHelper extends LineSegments { + /** + * Constructs a new vertex tangents helper. + * + * @param {Object3D} object - The object for which to visualize vertex tangents. + * @param {number} [size=1] - The helper's size. + * @param {number|Color|string} [color=0xff0000] - The helper's color. + */ constructor( object, size = 1, color = 0x00ffff ) { const geometry = new BufferGeometry(); @@ -22,18 +42,39 @@ class VertexTangentsHelper extends LineSegments { super( geometry, new LineBasicMaterial( { color, toneMapped: false } ) ); + /** + * The object for which to visualize vertex tangents. + * + * @type {Object3D} + */ this.object = object; + + /** + * The helper's size. + * + * @type {number} + * @default 1 + */ this.size = size; - this.type = 'VertexTangentsHelper'; - // + this.type = 'VertexTangentsHelper'; + /** + * Overwritten and set to `false` since the object's world transformation + * is encoded in the helper's geometry data. + * + * @type {boolean} + * @default false + */ this.matrixAutoUpdate = false; this.update(); } + /** + * Updates the vertex normals preview based on the object's world transform. + */ update() { this.object.updateMatrixWorld( true ); @@ -76,6 +117,10 @@ class VertexTangentsHelper extends LineSegments { } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ dispose() { this.geometry.dispose(); diff --git a/examples/jsm/helpers/ViewHelper.js b/examples/jsm/helpers/ViewHelper.js index ef4188167f73e3..5194c974799c92 100644 --- a/examples/jsm/helpers/ViewHelper.js +++ b/examples/jsm/helpers/ViewHelper.js @@ -1,5 +1,5 @@ import { - BoxGeometry, + CylinderGeometry, CanvasTexture, Color, Euler, @@ -11,25 +11,66 @@ import { Raycaster, Sprite, SpriteMaterial, + SRGBColorSpace, Vector2, Vector3, Vector4 } from 'three'; +/** + * A special type of helper that visualizes the camera's transformation + * in a small viewport area as an axes helper. Such a helper is often wanted + * in 3D modeling tools or scene editors like the [three.js editor]{@link https://threejs.org/editor}. + * + * The helper allows to click on the X, Y and Z axes which animates the camera + * so it looks along the selected axis. + * + * @augments Object3D + * @three_import import { ViewHelper } from 'three/addons/helpers/ViewHelper.js'; + */ class ViewHelper extends Object3D { + /** + * Constructs a new view helper. + * + * @param {Camera} camera - The camera whose transformation should be visualized. + * @param {HTMLDOMElement} [domElement] - The DOM element that is used to render the view. + */ constructor( camera, domElement ) { super(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isViewHelper = true; + /** + * Whether the helper is currently animating or not. + * + * @type {boolean} + * @readonly + * @default false + */ this.animating = false; + + /** + * The helper's center point. + * + * @type {Vector3} + */ this.center = new Vector3(); - const color1 = new Color( '#ff3653' ); - const color2 = new Color( '#8adb00' ); - const color3 = new Color( '#2c8fff' ); + const color1 = new Color( '#ff4466' ); + const color2 = new Color( '#88ff44' ); + const color3 = new Color( '#4488ff' ); + const color4 = new Color( '#000000' ); + + const options = {}; const interactiveObjects = []; const raycaster = new Raycaster(); @@ -39,7 +80,7 @@ class ViewHelper extends Object3D { const orthoCamera = new OrthographicCamera( - 2, 2, 2, - 2, 0, 4 ); orthoCamera.position.set( 0, 0, 2 ); - const geometry = new BoxGeometry( 0.8, 0.05, 0.05 ).translate( 0.4, 0, 0 ); + const geometry = new CylinderGeometry( 0.04, 0.04, 0.8, 5 ).rotateZ( - Math.PI / 2 ).translate( 0.4, 0, 0 ); const xAxis = new Mesh( geometry, getAxisMaterial( color1 ) ); const yAxis = new Mesh( geometry, getAxisMaterial( color2 ) ); @@ -52,28 +93,35 @@ class ViewHelper extends Object3D { this.add( zAxis ); this.add( yAxis ); - const posXAxisHelper = new Sprite( getSpriteMaterial( color1, 'X' ) ); - posXAxisHelper.userData.type = 'posX'; - const posYAxisHelper = new Sprite( getSpriteMaterial( color2, 'Y' ) ); - posYAxisHelper.userData.type = 'posY'; - const posZAxisHelper = new Sprite( getSpriteMaterial( color3, 'Z' ) ); - posZAxisHelper.userData.type = 'posZ'; - const negXAxisHelper = new Sprite( getSpriteMaterial( color1 ) ); - negXAxisHelper.userData.type = 'negX'; - const negYAxisHelper = new Sprite( getSpriteMaterial( color2 ) ); - negYAxisHelper.userData.type = 'negY'; - const negZAxisHelper = new Sprite( getSpriteMaterial( color3 ) ); - negZAxisHelper.userData.type = 'negZ'; + const spriteMaterial1 = getSpriteMaterial( color1 ); + const spriteMaterial2 = getSpriteMaterial( color2 ); + const spriteMaterial3 = getSpriteMaterial( color3 ); + const spriteMaterial4 = getSpriteMaterial( color4 ); + + const posXAxisHelper = new Sprite( spriteMaterial1 ); + const posYAxisHelper = new Sprite( spriteMaterial2 ); + const posZAxisHelper = new Sprite( spriteMaterial3 ); + const negXAxisHelper = new Sprite( spriteMaterial4 ); + const negYAxisHelper = new Sprite( spriteMaterial4 ); + const negZAxisHelper = new Sprite( spriteMaterial4 ); posXAxisHelper.position.x = 1; posYAxisHelper.position.y = 1; posZAxisHelper.position.z = 1; negXAxisHelper.position.x = - 1; - negXAxisHelper.scale.setScalar( 0.8 ); negYAxisHelper.position.y = - 1; - negYAxisHelper.scale.setScalar( 0.8 ); negZAxisHelper.position.z = - 1; - negZAxisHelper.scale.setScalar( 0.8 ); + + negXAxisHelper.material.opacity = 0.2; + negYAxisHelper.material.opacity = 0.2; + negZAxisHelper.material.opacity = 0.2; + + posXAxisHelper.userData.type = 'posX'; + posYAxisHelper.userData.type = 'posY'; + posZAxisHelper.userData.type = 'posZ'; + negXAxisHelper.userData.type = 'negX'; + negYAxisHelper.userData.type = 'negY'; + negZAxisHelper.userData.type = 'negZ'; this.add( posXAxisHelper ); this.add( posYAxisHelper ); @@ -93,6 +141,12 @@ class ViewHelper extends Object3D { const dim = 128; const turnRate = 2 * Math.PI; // turn rate in angles per second + /** + * Renders the helper in a separate view in the bottom-right corner + * of the viewport. + * + * @param {WebGLRenderer|WebGPURenderer} renderer - The renderer. + */ this.render = function ( renderer ) { this.quaternion.copy( camera.quaternion ).invert(); @@ -101,50 +155,15 @@ class ViewHelper extends Object3D { point.set( 0, 0, 1 ); point.applyQuaternion( camera.quaternion ); - if ( point.x >= 0 ) { - - posXAxisHelper.material.opacity = 1; - negXAxisHelper.material.opacity = 0.5; - - } else { - - posXAxisHelper.material.opacity = 0.5; - negXAxisHelper.material.opacity = 1; - - } - - if ( point.y >= 0 ) { - - posYAxisHelper.material.opacity = 1; - negYAxisHelper.material.opacity = 0.5; - - } else { - - posYAxisHelper.material.opacity = 0.5; - negYAxisHelper.material.opacity = 1; - - } - - if ( point.z >= 0 ) { - - posZAxisHelper.material.opacity = 1; - negZAxisHelper.material.opacity = 0.5; - - } else { - - posZAxisHelper.material.opacity = 0.5; - negZAxisHelper.material.opacity = 1; - - } - // const x = domElement.offsetWidth - dim; + const y = renderer.isWebGPURenderer ? domElement.offsetHeight - dim : 0; renderer.clearDepth(); renderer.getViewport( viewport ); - renderer.setViewport( x, 0, dim, dim ); + renderer.setViewport( x, y, dim, dim ); renderer.render( this, orthoCamera ); @@ -160,6 +179,13 @@ class ViewHelper extends Object3D { const viewport = new Vector4(); let radius = 0; + /** + * This method should be called when a click or pointer event + * has happened in the app. + * + * @param {PointerEvent} event - The event to process. + * @return {boolean} Whether an intersection with the helper has been detected or not. + */ this.handleClick = function ( event ) { if ( this.animating === true ) return false; @@ -167,7 +193,7 @@ class ViewHelper extends Object3D { const rect = domElement.getBoundingClientRect(); const offsetX = rect.left + ( domElement.offsetWidth - dim ); const offsetY = rect.top + ( domElement.offsetHeight - dim ); - mouse.x = ( ( event.clientX - offsetX ) / ( rect.width - offsetX ) ) * 2 - 1; + mouse.x = ( ( event.clientX - offsetX ) / ( rect.right - offsetX ) ) * 2 - 1; mouse.y = - ( ( event.clientY - offsetY ) / ( rect.bottom - offsetY ) ) * 2 + 1; raycaster.setFromCamera( mouse, orthoCamera ); @@ -193,6 +219,46 @@ class ViewHelper extends Object3D { }; + /** + * Sets labels for each axis. By default, they are unlabeled. + * + * @param {string|undefined} labelX - The label for the x-axis. + * @param {string|undefined} labelY - The label for the y-axis. + * @param {string|undefined} labelZ - The label for the z-axis. + */ + this.setLabels = function ( labelX, labelY, labelZ ) { + + options.labelX = labelX; + options.labelY = labelY; + options.labelZ = labelZ; + + updateLabels(); + + }; + + /** + * Sets the label style. Has no effect when the axes are unlabeled. + * + * @param {string} [font='24px Arial'] - The label font. + * @param {string} [color='#000000'] - The label color. + * @param {number} [radius=14] - The label radius. + */ + this.setLabelStyle = function ( font, color, radius ) { + + options.font = font; + options.color = color; + options.radius = radius; + + updateLabels(); + + }; + + /** + * Updates the helper. This method should be called in the app's animation + * loop. + * + * @param {number} delta - The delta time in seconds. + */ this.update = function ( delta ) { const step = delta * turnRate; @@ -214,6 +280,10 @@ class ViewHelper extends Object3D { }; + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ this.dispose = function () { geometry.dispose(); @@ -298,7 +368,9 @@ class ViewHelper extends Object3D { } - function getSpriteMaterial( color, text = null ) { + function getSpriteMaterial( color, text ) { + + const { font = '24px Arial', color: labelColor = '#000000', radius = 14 } = options; const canvas = document.createElement( 'canvas' ); canvas.width = 64; @@ -306,26 +378,43 @@ class ViewHelper extends Object3D { const context = canvas.getContext( '2d' ); context.beginPath(); - context.arc( 32, 32, 16, 0, 2 * Math.PI ); + context.arc( 32, 32, radius, 0, 2 * Math.PI ); context.closePath(); context.fillStyle = color.getStyle(); context.fill(); - if ( text !== null ) { + if ( text ) { - context.font = '24px Arial'; + context.font = font; context.textAlign = 'center'; - context.fillStyle = '#000000'; + context.fillStyle = labelColor; context.fillText( text, 32, 41 ); } const texture = new CanvasTexture( canvas ); + texture.colorSpace = SRGBColorSpace; return new SpriteMaterial( { map: texture, toneMapped: false } ); } + function updateLabels() { + + posXAxisHelper.material.map.dispose(); + posYAxisHelper.material.map.dispose(); + posZAxisHelper.material.map.dispose(); + + posXAxisHelper.material.dispose(); + posYAxisHelper.material.dispose(); + posZAxisHelper.material.dispose(); + + posXAxisHelper.material = getSpriteMaterial( color1, options.labelX ); + posYAxisHelper.material = getSpriteMaterial( color2, options.labelY ); + posZAxisHelper.material = getSpriteMaterial( color3, options.labelZ ); + + } + } } diff --git a/examples/jsm/interactive/HTMLMesh.js b/examples/jsm/interactive/HTMLMesh.js index f331be79d8975d..a234f20a006023 100644 --- a/examples/jsm/interactive/HTMLMesh.js +++ b/examples/jsm/interactive/HTMLMesh.js @@ -8,8 +8,30 @@ import { Color } from 'three'; +/** + * This class can be used to render a DOM element onto a canvas and use it as a texture + * for a plane mesh. + * + * A typical use case for this class is to render the GUI of `lil-gui` as a texture so it + * is compatible for VR. + * + * ```js + * const gui = new GUI( { width: 300 } ); // create lil-gui instance + * + * const mesh = new HTMLMesh( gui.domElement ); + * scene.add( mesh ); + * ``` + * + * @augments Mesh + * @three_import import { HTMLMesh } from 'three/addons/interactive/HTMLMesh.js'; + */ class HTMLMesh extends Mesh { + /** + * Constructs a new HTML mesh. + * + * @param {HTMLElement} dom - The DOM element to display as a plane mesh. + */ constructor( dom ) { const texture = new HTMLTexture( dom ); @@ -30,6 +52,10 @@ class HTMLMesh extends Mesh { this.addEventListener( 'mouseup', onEvent ); this.addEventListener( 'click', onEvent ); + /** + * Frees the GPU-related resources allocated by this instance and removes all event listeners. + * Call this method whenever this instance is no longer used in your app. + */ this.dispose = function () { geometry.dispose(); @@ -62,6 +88,7 @@ class HTMLTexture extends CanvasTexture { this.colorSpace = SRGBColorSpace; this.minFilter = LinearFilter; this.magFilter = LinearFilter; + this.generateMipmaps = false; // Create an observer on the DOM, and run html2canvas update in the next loop const observer = new MutationObserver( () => { @@ -241,6 +268,13 @@ function html2canvas( element ) { function drawElement( element, style ) { + // Do not render invisible elements, comments and scripts. + if ( element.nodeType === Node.COMMENT_NODE || element.nodeName === 'SCRIPT' || ( element.style && element.style.display === 'none' ) ) { + + return; + + } + let x = 0, y = 0, width = 0, height = 0; if ( element.nodeType === Node.TEXT_NODE ) { @@ -258,25 +292,23 @@ function html2canvas( element ) { drawText( style, x, y, element.nodeValue.trim() ); - } else if ( element.nodeType === Node.COMMENT_NODE ) { - - return; - } else if ( element instanceof HTMLCanvasElement ) { // Canvas element - if ( element.style.display === 'none' ) return; - context.save(); + const rect = element.getBoundingClientRect(); + + x = rect.left - offset.left - 0.5; + y = rect.top - offset.top - 0.5; + + context.save(); const dpr = window.devicePixelRatio; context.scale( 1 / dpr, 1 / dpr ); - context.drawImage( element, 0, 0 ); + context.drawImage( element, x, y ); context.restore(); } else if ( element instanceof HTMLImageElement ) { - if ( element.style.display === 'none' ) return; - const rect = element.getBoundingClientRect(); x = rect.left - offset.left - 0.5; @@ -288,8 +320,6 @@ function html2canvas( element ) { } else { - if ( element.style.display === 'none' ) return; - const rect = element.getBoundingClientRect(); x = rect.left - offset.left - 0.5; @@ -501,6 +531,8 @@ function html2canvas( element ) { // console.time( 'drawElement' ); + context.clearRect( 0, 0, canvas.width, canvas.height ); + drawElement( element ); // console.timeEnd( 'drawElement' ); @@ -546,6 +578,12 @@ function htmlevent( element, event, x, y ) { } + if ( element instanceof HTMLInputElement && ( element.type === 'text' || element.type === 'number' ) && ( event === 'mousedown' || event === 'click' ) ) { + + element.focus(); + + } + } for ( let i = 0; i < element.childNodes.length; i ++ ) { diff --git a/examples/jsm/interactive/InteractiveGroup.js b/examples/jsm/interactive/InteractiveGroup.js index a8d7b30f59e279..57fe724dcec797 100644 --- a/examples/jsm/interactive/InteractiveGroup.js +++ b/examples/jsm/interactive/InteractiveGroup.js @@ -1,6 +1,5 @@ import { Group, - Matrix4, Raycaster, Vector2 } from 'three'; @@ -8,106 +7,215 @@ import { const _pointer = new Vector2(); const _event = { type: '', data: _pointer }; +// The XR events that are mapped to "standard" pointer events. +const _events = { + 'move': 'mousemove', + 'select': 'click', + 'selectstart': 'mousedown', + 'selectend': 'mouseup' +}; + +const _raycaster = new Raycaster(); + +/** + * This class can be used to group 3D objects in an interactive group. + * The group itself can listen to Pointer, Mouse or XR controller events to + * detect selections of descendant 3D objects. If a 3D object is selected, + * the respective event is going to dispatched to it. + * + * ```js + * const group = new InteractiveGroup(); + * group.listenToPointerEvents( renderer, camera ); + * group.listenToXRControllerEvents( controller1 ); + * group.listenToXRControllerEvents( controller2 ); + * scene.add( group ); + * + * // now add objects that should be interactive + * group.add( mesh1, mesh2, mesh3 ); + * ``` + * @augments Group + * @three_import import { InteractiveGroup } from 'three/addons/interactive/InteractiveGroup.js'; + */ class InteractiveGroup extends Group { - constructor( renderer, camera ) { + constructor() { super(); - const scope = this; + /** + * The internal raycaster. + * + * @type {Raycaster} + */ + this.raycaster = new Raycaster(); + + /** + * The internal raycaster. + * + * @type {?HTMLDOMElement} + * @default null + */ + this.element = null; + + /** + * The camera used for raycasting. + * + * @type {?Camera} + * @default null + */ + this.camera = null; + + /** + * An array of XR controllers. + * + * @type {Array} + */ + this.controllers = []; + + this._onPointerEvent = this.onPointerEvent.bind( this ); + this._onXRControllerEvent = this.onXRControllerEvent.bind( this ); - const raycaster = new Raycaster(); - const tempMatrix = new Matrix4(); + } + + onPointerEvent( event ) { + + event.stopPropagation(); + + const rect = this.element.getBoundingClientRect(); + + _pointer.x = ( event.clientX - rect.left ) / rect.width * 2 - 1; + _pointer.y = - ( event.clientY - rect.top ) / rect.height * 2 + 1; + + this.raycaster.setFromCamera( _pointer, this.camera ); - // Pointer Events + const intersects = this.raycaster.intersectObjects( this.children, false ); - const element = renderer.domElement; + if ( intersects.length > 0 ) { - function onPointerEvent( event ) { + const intersection = intersects[ 0 ]; - event.stopPropagation(); + const object = intersection.object; + const uv = intersection.uv; - const rect = renderer.domElement.getBoundingClientRect(); + _event.type = event.type; + _event.data.set( uv.x, 1 - uv.y ); + + object.dispatchEvent( _event ); + + } + + } - _pointer.x = ( event.clientX - rect.left ) / rect.width * 2 - 1; - _pointer.y = - ( event.clientY - rect.top ) / rect.height * 2 + 1; + onXRControllerEvent( event ) { - raycaster.setFromCamera( _pointer, camera ); + const controller = event.target; - const intersects = raycaster.intersectObjects( scope.children, false ); + _raycaster.setFromXRController( controller ); - if ( intersects.length > 0 ) { + const intersections = _raycaster.intersectObjects( this.children, false ); - const intersection = intersects[ 0 ]; + if ( intersections.length > 0 ) { - const object = intersection.object; - const uv = intersection.uv; + const intersection = intersections[ 0 ]; - _event.type = event.type; - _event.data.set( uv.x, 1 - uv.y ); + const object = intersection.object; + const uv = intersection.uv; - object.dispatchEvent( _event ); + _event.type = _events[ event.type ]; + _event.data.set( uv.x, 1 - uv.y ); - } + object.dispatchEvent( _event ); } - element.addEventListener( 'pointerdown', onPointerEvent ); - element.addEventListener( 'pointerup', onPointerEvent ); - element.addEventListener( 'pointermove', onPointerEvent ); - element.addEventListener( 'mousedown', onPointerEvent ); - element.addEventListener( 'mouseup', onPointerEvent ); - element.addEventListener( 'mousemove', onPointerEvent ); - element.addEventListener( 'click', onPointerEvent ); + } - // WebXR Controller Events - // TODO: Dispatch pointerevents too + /** + * Calling this method makes sure the interactive group listens to Pointer and Mouse events. + * The target is the `domElement` of the given renderer. The camera is required for the internal + * raycasting so 3D objects can be detected based on the events. + * + * @param {(WebGPURenderer|WebGLRenderer)} renderer - The renderer. + * @param {Camera} camera - The camera. + */ + listenToPointerEvents( renderer, camera ) { + + this.camera = camera; + this.element = renderer.domElement; + + this.element.addEventListener( 'pointerdown', this._onPointerEvent ); + this.element.addEventListener( 'pointerup', this._onPointerEvent ); + this.element.addEventListener( 'pointermove', this._onPointerEvent ); + this.element.addEventListener( 'mousedown', this._onPointerEvent ); + this.element.addEventListener( 'mouseup', this._onPointerEvent ); + this.element.addEventListener( 'mousemove', this._onPointerEvent ); + this.element.addEventListener( 'click', this._onPointerEvent ); - const events = { - 'move': 'mousemove', - 'select': 'click', - 'selectstart': 'mousedown', - 'selectend': 'mouseup' - }; + } - function onXRControllerEvent( event ) { + /** + * Disconnects this interactive group from all Pointer and Mouse Events. + */ + disconnectionPointerEvents() { - const controller = event.target; + if ( this.element !== null ) { - tempMatrix.identity().extractRotation( controller.matrixWorld ); + this.element.removeEventListener( 'pointerdown', this._onPointerEvent ); + this.element.removeEventListener( 'pointerup', this._onPointerEvent ); + this.element.removeEventListener( 'pointermove', this._onPointerEvent ); + this.element.removeEventListener( 'mousedown', this._onPointerEvent ); + this.element.removeEventListener( 'mouseup', this._onPointerEvent ); + this.element.removeEventListener( 'mousemove', this._onPointerEvent ); + this.element.removeEventListener( 'click', this._onPointerEvent ); - raycaster.ray.origin.setFromMatrixPosition( controller.matrixWorld ); - raycaster.ray.direction.set( 0, 0, - 1 ).applyMatrix4( tempMatrix ); + } - const intersections = raycaster.intersectObjects( scope.children, false ); + } - if ( intersections.length > 0 ) { + /** + * Calling this method makes sure the interactive group listens to events of + * the given XR controller. + * + * @param {Group} controller - The XR controller. + */ + listenToXRControllerEvents( controller ) { - const intersection = intersections[ 0 ]; + this.controllers.push( controller ); + controller.addEventListener( 'move', this._onXRControllerEvent ); + controller.addEventListener( 'select', this._onXRControllerEvent ); + controller.addEventListener( 'selectstart', this._onXRControllerEvent ); + controller.addEventListener( 'selectend', this._onXRControllerEvent ); - const object = intersection.object; - const uv = intersection.uv; + } - _event.type = events[ event.type ]; - _event.data.set( uv.x, 1 - uv.y ); + /** + * Disconnects this interactive group from all XR controllers. + */ + disconnectXrControllerEvents() { - object.dispatchEvent( _event ); + for ( const controller of this.controllers ) { - } + controller.removeEventListener( 'move', this._onXRControllerEvent ); + controller.removeEventListener( 'select', this._onXRControllerEvent ); + controller.removeEventListener( 'selectstart', this._onXRControllerEvent ); + controller.removeEventListener( 'selectend', this._onXRControllerEvent ); } - const controller1 = renderer.xr.getController( 0 ); - controller1.addEventListener( 'move', onXRControllerEvent ); - controller1.addEventListener( 'select', onXRControllerEvent ); - controller1.addEventListener( 'selectstart', onXRControllerEvent ); - controller1.addEventListener( 'selectend', onXRControllerEvent ); - - const controller2 = renderer.xr.getController( 1 ); - controller2.addEventListener( 'move', onXRControllerEvent ); - controller2.addEventListener( 'select', onXRControllerEvent ); - controller2.addEventListener( 'selectstart', onXRControllerEvent ); - controller2.addEventListener( 'selectend', onXRControllerEvent ); + } + + /** + * Disconnects this interactive group from the DOM and all XR controllers. + */ + disconnect() { + + this.disconnectionPointerEvents(); + this.disconnectXrControllerEvents(); + + this.camera = null; + this.element = null; + + this.controllers = []; } diff --git a/examples/jsm/interactive/SelectionBox.js b/examples/jsm/interactive/SelectionBox.js index 597b9b2f101f66..93bb0c3e3f61d7 100644 --- a/examples/jsm/interactive/SelectionBox.js +++ b/examples/jsm/interactive/SelectionBox.js @@ -5,10 +5,6 @@ import { Quaternion, } from 'three'; -/** - * This is a class to check whether objects are in a selection area in 3D space - */ - const _frustum = new Frustum(); const _center = new Vector3(); @@ -33,34 +29,105 @@ const _matrix = new Matrix4(); const _quaternion = new Quaternion(); const _scale = new Vector3(); +/** + * This class can be used to select 3D objects in a scene with a selection box. + * It is recommended to visualize the selected area with the help of {@link SelectionHelper}. + * + * ```js + * const selectionBox = new SelectionBox( camera, scene ); + * const selectedObjects = selectionBox.select( startPoint, endPoint ); + * ``` + * + * @three_import import { SelectionBox } from 'three/addons/interactive/SelectionBox.js'; + */ class SelectionBox { + /** + * Constructs a new selection box. + * + * @param {Camera} camera - The camera the scene is rendered with. + * @param {Scene} scene - The scene. + * @param {number} [deep=Number.MAX_VALUE] - How deep the selection frustum of perspective cameras should extend. + */ constructor( camera, scene, deep = Number.MAX_VALUE ) { + /** + * The camera the scene is rendered with. + * + * @type {Camera} + */ this.camera = camera; + + /** + * The camera the scene is rendered with. + * + * @type {Scene} + */ this.scene = scene; + + /** + * The start point of the selection. + * + * @type {Vector3} + */ this.startPoint = new Vector3(); + + /** + * The end point of the selection. + * + * @type {Vector3} + */ this.endPoint = new Vector3(); + + /** + * The selected 3D objects. + * + * @type {Array} + */ this.collection = []; + + /** + * The selected instance IDs of instanced meshes. + * + * @type {Object} + */ this.instances = {}; + + /** + * How deep the selection frustum of perspective cameras should extend. + * + * @type {number} + * @default Number.MAX_VALUE + */ this.deep = deep; } + /** + * This method selects 3D objects in the scene based on the given start + * and end point. If no parameters are provided, the method uses the start + * and end values of the respective members. + * + * @param {Vector3} [startPoint] - The start point. + * @param {Vector3} [endPoint] - The end point. + * @return {Array} The selected 3D objects. + */ select( startPoint, endPoint ) { this.startPoint = startPoint || this.startPoint; this.endPoint = endPoint || this.endPoint; this.collection = []; - this.updateFrustum( this.startPoint, this.endPoint ); - this.searchChildInFrustum( _frustum, this.scene ); + this._updateFrustum( this.startPoint, this.endPoint ); + this._searchChildInFrustum( _frustum, this.scene ); return this.collection; } - updateFrustum( startPoint, endPoint ) { + // private + + _updateFrustum( startPoint, endPoint ) { startPoint = startPoint || this.startPoint; endPoint = endPoint || this.endPoint; @@ -170,7 +237,7 @@ class SelectionBox { } - searchChildInFrustum( frustum, object ) { + _searchChildInFrustum( frustum, object ) { if ( object.isMesh || object.isLine || object.isPoints ) { @@ -214,7 +281,7 @@ class SelectionBox { for ( let x = 0; x < object.children.length; x ++ ) { - this.searchChildInFrustum( frustum, object.children[ x ] ); + this._searchChildInFrustum( frustum, object.children[ x ] ); } diff --git a/examples/jsm/interactive/SelectionHelper.js b/examples/jsm/interactive/SelectionHelper.js index cfa54226ce6e6d..8e02f3a4fb9c39 100644 --- a/examples/jsm/interactive/SelectionHelper.js +++ b/examples/jsm/interactive/SelectionHelper.js @@ -1,60 +1,113 @@ import { Vector2 } from 'three'; +/** + * A helper for {@link SelectionBox}. + * + * It visualizes the current selection box with a `div` container element. + * + * @three_import import { SelectionHelper } from 'three/addons/interactive/SelectionHelper.js'; + */ class SelectionHelper { + /** + * Constructs a new selection helper. + * + * @param {(WebGPURenderer|WebGLRenderer)} renderer - The renderer. + * @param {string} cssClassName - The CSS class name of the `div`. + */ constructor( renderer, cssClassName ) { + /** + * The visualization of the selection box. + * + * @type {HTMLDivElement} + */ this.element = document.createElement( 'div' ); this.element.classList.add( cssClassName ); this.element.style.pointerEvents = 'none'; + /** + * A reference to the renderer. + * + * @type {(WebGPURenderer|WebGLRenderer)} + */ this.renderer = renderer; - this.startPoint = new Vector2(); - this.pointTopLeft = new Vector2(); - this.pointBottomRight = new Vector2(); - + /** + * Whether the mouse or pointer is pressed down. + * + * @type {boolean} + * @default false + */ this.isDown = false; - this.onPointerDown = function ( event ) { + /** + * Whether helper is enabled or not. + * + * @type {boolean} + * @default true + */ + this.enabled = true; + + // private + + this._startPoint = new Vector2(); + this._pointTopLeft = new Vector2(); + this._pointBottomRight = new Vector2(); + + this._onPointerDown = function ( event ) { + + if ( this.enabled === false ) return; this.isDown = true; - this.onSelectStart( event ); + this._onSelectStart( event ); }.bind( this ); - this.onPointerMove = function ( event ) { + this._onPointerMove = function ( event ) { + + if ( this.enabled === false ) return; if ( this.isDown ) { - this.onSelectMove( event ); + this._onSelectMove( event ); } }.bind( this ); - this.onPointerUp = function ( ) { + this._onPointerUp = function ( ) { + + if ( this.enabled === false ) return; this.isDown = false; - this.onSelectOver(); + this._onSelectOver(); }.bind( this ); - this.renderer.domElement.addEventListener( 'pointerdown', this.onPointerDown ); - this.renderer.domElement.addEventListener( 'pointermove', this.onPointerMove ); - this.renderer.domElement.addEventListener( 'pointerup', this.onPointerUp ); + this.renderer.domElement.addEventListener( 'pointerdown', this._onPointerDown ); + this.renderer.domElement.addEventListener( 'pointermove', this._onPointerMove ); + this.renderer.domElement.addEventListener( 'pointerup', this._onPointerUp ); } + /** + * Call this method if you no longer want use to the controls. It frees all internal + * resources and removes all event listeners. + */ dispose() { - this.renderer.domElement.removeEventListener( 'pointerdown', this.onPointerDown ); - this.renderer.domElement.removeEventListener( 'pointermove', this.onPointerMove ); - this.renderer.domElement.removeEventListener( 'pointerup', this.onPointerUp ); + this.renderer.domElement.removeEventListener( 'pointerdown', this._onPointerDown ); + this.renderer.domElement.removeEventListener( 'pointermove', this._onPointerMove ); + this.renderer.domElement.removeEventListener( 'pointerup', this._onPointerUp ); + + this.element.remove(); // in case disposal happens while dragging } - onSelectStart( event ) { + // private + + _onSelectStart( event ) { this.element.style.display = 'none'; @@ -65,30 +118,30 @@ class SelectionHelper { this.element.style.width = '0px'; this.element.style.height = '0px'; - this.startPoint.x = event.clientX; - this.startPoint.y = event.clientY; + this._startPoint.x = event.clientX; + this._startPoint.y = event.clientY; } - onSelectMove( event ) { + _onSelectMove( event ) { this.element.style.display = 'block'; - this.pointBottomRight.x = Math.max( this.startPoint.x, event.clientX ); - this.pointBottomRight.y = Math.max( this.startPoint.y, event.clientY ); - this.pointTopLeft.x = Math.min( this.startPoint.x, event.clientX ); - this.pointTopLeft.y = Math.min( this.startPoint.y, event.clientY ); + this._pointBottomRight.x = Math.max( this._startPoint.x, event.clientX ); + this._pointBottomRight.y = Math.max( this._startPoint.y, event.clientY ); + this._pointTopLeft.x = Math.min( this._startPoint.x, event.clientX ); + this._pointTopLeft.y = Math.min( this._startPoint.y, event.clientY ); - this.element.style.left = this.pointTopLeft.x + 'px'; - this.element.style.top = this.pointTopLeft.y + 'px'; - this.element.style.width = ( this.pointBottomRight.x - this.pointTopLeft.x ) + 'px'; - this.element.style.height = ( this.pointBottomRight.y - this.pointTopLeft.y ) + 'px'; + this.element.style.left = this._pointTopLeft.x + 'px'; + this.element.style.top = this._pointTopLeft.y + 'px'; + this.element.style.width = ( this._pointBottomRight.x - this._pointTopLeft.x ) + 'px'; + this.element.style.height = ( this._pointBottomRight.y - this._pointTopLeft.y ) + 'px'; } - onSelectOver() { + _onSelectOver() { - this.element.parentElement.removeChild( this.element ); + this.element.remove(); } diff --git a/examples/jsm/libs/basis/basis_transcoder.js b/examples/jsm/libs/basis/basis_transcoder.js index 9e285ddc9515bd..6f02a4ac8504ea 100644 --- a/examples/jsm/libs/basis/basis_transcoder.js +++ b/examples/jsm/libs/basis/basis_transcoder.js @@ -1,21 +1,19 @@ -var BASIS = (function() { - var _scriptDir = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined; - if (typeof __filename !== 'undefined') _scriptDir = _scriptDir || __filename; +var BASIS = (() => { + var _scriptName = typeof document != 'undefined' ? document.currentScript?.src : undefined; + if (typeof __filename != 'undefined') _scriptName ||= __filename; return ( -function(BASIS) { - BASIS = BASIS || {}; +function(moduleArg = {}) { + var moduleRtn; -var Module=typeof BASIS!=="undefined"?BASIS:{};var readyPromiseResolve,readyPromiseReject;Module["ready"]=new Promise(function(resolve,reject){readyPromiseResolve=resolve;readyPromiseReject=reject});var moduleOverrides={};var key;for(key in Module){if(Module.hasOwnProperty(key)){moduleOverrides[key]=Module[key]}}var arguments_=[];var thisProgram="./this.program";var quit_=function(status,toThrow){throw toThrow};var ENVIRONMENT_IS_WEB=false;var ENVIRONMENT_IS_WORKER=false;var ENVIRONMENT_IS_NODE=false;var ENVIRONMENT_IS_SHELL=false;ENVIRONMENT_IS_WEB=typeof window==="object";ENVIRONMENT_IS_WORKER=typeof importScripts==="function";ENVIRONMENT_IS_NODE=typeof process==="object"&&typeof process.versions==="object"&&typeof process.versions.node==="string";ENVIRONMENT_IS_SHELL=!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_NODE&&!ENVIRONMENT_IS_WORKER;var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var read_,readAsync,readBinary,setWindowTitle;var nodeFS;var nodePath;if(ENVIRONMENT_IS_NODE){if(ENVIRONMENT_IS_WORKER){scriptDirectory=require("path").dirname(scriptDirectory)+"/"}else{scriptDirectory=__dirname+"/"}read_=function shell_read(filename,binary){if(!nodeFS)nodeFS=require("fs");if(!nodePath)nodePath=require("path");filename=nodePath["normalize"](filename);return nodeFS["readFileSync"](filename,binary?null:"utf8")};readBinary=function readBinary(filename){var ret=read_(filename,true);if(!ret.buffer){ret=new Uint8Array(ret)}assert(ret.buffer);return ret};if(process["argv"].length>1){thisProgram=process["argv"][1].replace(/\\/g,"/")}arguments_=process["argv"].slice(2);process["on"]("uncaughtException",function(ex){if(!(ex instanceof ExitStatus)){throw ex}});process["on"]("unhandledRejection",abort);quit_=function(status){process["exit"](status)};Module["inspect"]=function(){return"[Emscripten Module object]"}}else if(ENVIRONMENT_IS_SHELL){if(typeof read!="undefined"){read_=function shell_read(f){return read(f)}}readBinary=function readBinary(f){var data;if(typeof readbuffer==="function"){return new Uint8Array(readbuffer(f))}data=read(f,"binary");assert(typeof data==="object");return data};if(typeof scriptArgs!="undefined"){arguments_=scriptArgs}else if(typeof arguments!="undefined"){arguments_=arguments}if(typeof quit==="function"){quit_=function(status){quit(status)}}if(typeof print!=="undefined"){if(typeof console==="undefined")console={};console.log=print;console.warn=console.error=typeof printErr!=="undefined"?printErr:print}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!=="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(_scriptDir){scriptDirectory=_scriptDir}if(scriptDirectory.indexOf("blob:")!==0){scriptDirectory=scriptDirectory.substr(0,scriptDirectory.lastIndexOf("/")+1)}else{scriptDirectory=""}{read_=function shell_read(url){var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(ENVIRONMENT_IS_WORKER){readBinary=function readBinary(url){var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=function readAsync(url,onload,onerror){var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=function xhr_onload(){if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}onerror()};xhr.onerror=onerror;xhr.send(null)}}setWindowTitle=function(title){document.title=title}}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.warn.bind(console);for(key in moduleOverrides){if(moduleOverrides.hasOwnProperty(key)){Module[key]=moduleOverrides[key]}}moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var tempRet0=0;var setTempRet0=function(value){tempRet0=value};var wasmBinary;if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];var noExitRuntime;if(Module["noExitRuntime"])noExitRuntime=Module["noExitRuntime"];if(typeof WebAssembly!=="object"){abort("no native wasm support detected")}var wasmMemory;var ABORT=false;var EXITSTATUS;function assert(condition,text){if(!condition){abort("Assertion failed: "+text)}}var UTF8Decoder=typeof TextDecoder!=="undefined"?new TextDecoder("utf8"):undefined;function UTF8ArrayToString(heap,idx,maxBytesToRead){var endIdx=idx+maxBytesToRead;var endPtr=idx;while(heap[endPtr]&&!(endPtr>=endIdx))++endPtr;if(endPtr-idx>16&&heap.subarray&&UTF8Decoder){return UTF8Decoder.decode(heap.subarray(idx,endPtr))}else{var str="";while(idx>10,56320|ch&1023)}}}return str}function UTF8ToString(ptr,maxBytesToRead){return ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):""}function stringToUTF8Array(str,heap,outIdx,maxBytesToWrite){if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}}heap[outIdx]=0;return outIdx-startIdx}function stringToUTF8(str,outPtr,maxBytesToWrite){return stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite)}function lengthBytesUTF8(str){var len=0;for(var i=0;i=55296&&u<=57343)u=65536+((u&1023)<<10)|str.charCodeAt(++i)&1023;if(u<=127)++len;else if(u<=2047)len+=2;else if(u<=65535)len+=3;else len+=4}return len}var UTF16Decoder=typeof TextDecoder!=="undefined"?new TextDecoder("utf-16le"):undefined;function UTF16ToString(ptr,maxBytesToRead){var endPtr=ptr;var idx=endPtr>>1;var maxIdx=idx+maxBytesToRead/2;while(!(idx>=maxIdx)&&HEAPU16[idx])++idx;endPtr=idx<<1;if(endPtr-ptr>32&&UTF16Decoder){return UTF16Decoder.decode(HEAPU8.subarray(ptr,endPtr))}else{var str="";for(var i=0;!(i>=maxBytesToRead/2);++i){var codeUnit=HEAP16[ptr+i*2>>1];if(codeUnit==0)break;str+=String.fromCharCode(codeUnit)}return str}}function stringToUTF16(str,outPtr,maxBytesToWrite){if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<2)return 0;maxBytesToWrite-=2;var startPtr=outPtr;var numCharsToWrite=maxBytesToWrite>1]=codeUnit;outPtr+=2}HEAP16[outPtr>>1]=0;return outPtr-startPtr}function lengthBytesUTF16(str){return str.length*2}function UTF32ToString(ptr,maxBytesToRead){var i=0;var str="";while(!(i>=maxBytesToRead/4)){var utf32=HEAP32[ptr+i*4>>2];if(utf32==0)break;++i;if(utf32>=65536){var ch=utf32-65536;str+=String.fromCharCode(55296|ch>>10,56320|ch&1023)}else{str+=String.fromCharCode(utf32)}}return str}function stringToUTF32(str,outPtr,maxBytesToWrite){if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<4)return 0;var startPtr=outPtr;var endPtr=startPtr+maxBytesToWrite-4;for(var i=0;i=55296&&codeUnit<=57343){var trailSurrogate=str.charCodeAt(++i);codeUnit=65536+((codeUnit&1023)<<10)|trailSurrogate&1023}HEAP32[outPtr>>2]=codeUnit;outPtr+=4;if(outPtr+4>endPtr)break}HEAP32[outPtr>>2]=0;return outPtr-startPtr}function lengthBytesUTF32(str){var len=0;for(var i=0;i=55296&&codeUnit<=57343)++i;len+=4}return len}function alignUp(x,multiple){if(x%multiple>0){x+=multiple-x%multiple}return x}var buffer,HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateGlobalBufferAndViews(buf){buffer=buf;Module["HEAP8"]=HEAP8=new Int8Array(buf);Module["HEAP16"]=HEAP16=new Int16Array(buf);Module["HEAP32"]=HEAP32=new Int32Array(buf);Module["HEAPU8"]=HEAPU8=new Uint8Array(buf);Module["HEAPU16"]=HEAPU16=new Uint16Array(buf);Module["HEAPU32"]=HEAPU32=new Uint32Array(buf);Module["HEAPF32"]=HEAPF32=new Float32Array(buf);Module["HEAPF64"]=HEAPF64=new Float64Array(buf)}var INITIAL_MEMORY=Module["INITIAL_MEMORY"]||16777216;var wasmTable;var __ATPRERUN__=[];var __ATINIT__=[];var __ATMAIN__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;callRuntimeCallbacks(__ATINIT__)}function preMain(){callRuntimeCallbacks(__ATMAIN__)}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function addRunDependency(id){runDependencies++;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}}function removeRunDependency(id){runDependencies--;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}Module["preloadedImages"]={};Module["preloadedAudios"]={};function abort(what){if(Module["onAbort"]){Module["onAbort"](what)}what+="";err(what);ABORT=true;EXITSTATUS=1;what="abort("+what+"). Build with -s ASSERTIONS=1 for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject(e);throw e}function hasPrefix(str,prefix){return String.prototype.startsWith?str.startsWith(prefix):str.indexOf(prefix)===0}var dataURIPrefix="data:application/octet-stream;base64,";function isDataURI(filename){return hasPrefix(filename,dataURIPrefix)}var fileURIPrefix="file://";function isFileURI(filename){return hasPrefix(filename,fileURIPrefix)}var wasmBinaryFile="basis_transcoder.wasm";if(!isDataURI(wasmBinaryFile)){wasmBinaryFile=locateFile(wasmBinaryFile)}function getBinary(){try{if(wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(wasmBinaryFile)}else{throw"both async and sync fetching of the wasm failed"}}catch(err){abort(err)}}function getBinaryPromise(){if(!wasmBinary&&(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER)&&typeof fetch==="function"&&!isFileURI(wasmBinaryFile)){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){if(!response["ok"]){throw"failed to load wasm binary file at '"+wasmBinaryFile+"'"}return response["arrayBuffer"]()}).catch(function(){return getBinary()})}return Promise.resolve().then(getBinary)}function createWasm(){var info={"a":asmLibraryArg};function receiveInstance(instance,module){var exports=instance.exports;Module["asm"]=exports;wasmMemory=Module["asm"]["K"];updateGlobalBufferAndViews(wasmMemory.buffer);wasmTable=Module["asm"]["L"];removeRunDependency("wasm-instantiate")}addRunDependency("wasm-instantiate");function receiveInstantiatedSource(output){receiveInstance(output["instance"])}function instantiateArrayBuffer(receiver){return getBinaryPromise().then(function(binary){return WebAssembly.instantiate(binary,info)}).then(receiver,function(reason){err("failed to asynchronously prepare wasm: "+reason);abort(reason)})}function instantiateAsync(){if(!wasmBinary&&typeof WebAssembly.instantiateStreaming==="function"&&!isDataURI(wasmBinaryFile)&&!isFileURI(wasmBinaryFile)&&typeof fetch==="function"){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){var result=WebAssembly.instantiateStreaming(response,info);return result.then(receiveInstantiatedSource,function(reason){err("wasm streaming compile failed: "+reason);err("falling back to ArrayBuffer instantiation");return instantiateArrayBuffer(receiveInstantiatedSource)})})}else{return instantiateArrayBuffer(receiveInstantiatedSource)}}if(Module["instantiateWasm"]){try{var exports=Module["instantiateWasm"](info,receiveInstance);return exports}catch(e){err("Module.instantiateWasm callback failed with error: "+e);return false}}instantiateAsync().catch(readyPromiseReject);return{}}function callRuntimeCallbacks(callbacks){while(callbacks.length>0){var callback=callbacks.shift();if(typeof callback=="function"){callback(Module);continue}var func=callback.func;if(typeof func==="number"){if(callback.arg===undefined){wasmTable.get(func)()}else{wasmTable.get(func)(callback.arg)}}else{func(callback.arg===undefined?null:callback.arg)}}}var structRegistrations={};function runDestructors(destructors){while(destructors.length){var ptr=destructors.pop();var del=destructors.pop();del(ptr)}}function simpleReadValueFromPointer(pointer){return this["fromWireType"](HEAPU32[pointer>>2])}var awaitingDependencies={};var registeredTypes={};var typeDependencies={};var char_0=48;var char_9=57;function makeLegalFunctionName(name){if(undefined===name){return"_unknown"}name=name.replace(/[^a-zA-Z0-9_]/g,"$");var f=name.charCodeAt(0);if(f>=char_0&&f<=char_9){return"_"+name}else{return name}}function createNamedFunction(name,body){name=makeLegalFunctionName(name);return new Function("body","return function "+name+"() {\n"+' "use strict";'+" return body.apply(this, arguments);\n"+"};\n")(body)}function extendError(baseErrorType,errorName){var errorClass=createNamedFunction(errorName,function(message){this.name=errorName;this.message=message;var stack=new Error(message).stack;if(stack!==undefined){this.stack=this.toString()+"\n"+stack.replace(/^Error(:[^\n]*)?\n/,"")}});errorClass.prototype=Object.create(baseErrorType.prototype);errorClass.prototype.constructor=errorClass;errorClass.prototype.toString=function(){if(this.message===undefined){return this.name}else{return this.name+": "+this.message}};return errorClass}var InternalError=undefined;function throwInternalError(message){throw new InternalError(message)}function whenDependentTypesAreResolved(myTypes,dependentTypes,getTypeConverters){myTypes.forEach(function(type){typeDependencies[type]=dependentTypes});function onComplete(typeConverters){var myTypeConverters=getTypeConverters(typeConverters);if(myTypeConverters.length!==myTypes.length){throwInternalError("Mismatched type converter count")}for(var i=0;i>shift])},destructorFunction:null})}function ClassHandle_isAliasOf(other){if(!(this instanceof ClassHandle)){return false}if(!(other instanceof ClassHandle)){return false}var leftClass=this.$$.ptrType.registeredClass;var left=this.$$.ptr;var rightClass=other.$$.ptrType.registeredClass;var right=other.$$.ptr;while(leftClass.baseClass){left=leftClass.upcast(left);leftClass=leftClass.baseClass}while(rightClass.baseClass){right=rightClass.upcast(right);rightClass=rightClass.baseClass}return leftClass===rightClass&&left===right}function shallowCopyInternalPointer(o){return{count:o.count,deleteScheduled:o.deleteScheduled,preservePointerOnDelete:o.preservePointerOnDelete,ptr:o.ptr,ptrType:o.ptrType,smartPtr:o.smartPtr,smartPtrType:o.smartPtrType}}function throwInstanceAlreadyDeleted(obj){function getInstanceTypeName(handle){return handle.$$.ptrType.registeredClass.name}throwBindingError(getInstanceTypeName(obj)+" instance already deleted")}var finalizationGroup=false;function detachFinalizer(handle){}function runDestructor($$){if($$.smartPtr){$$.smartPtrType.rawDestructor($$.smartPtr)}else{$$.ptrType.registeredClass.rawDestructor($$.ptr)}}function releaseClassHandle($$){$$.count.value-=1;var toDelete=0===$$.count.value;if(toDelete){runDestructor($$)}}function attachFinalizer(handle){if("undefined"===typeof FinalizationGroup){attachFinalizer=function(handle){return handle};return handle}finalizationGroup=new FinalizationGroup(function(iter){for(var result=iter.next();!result.done;result=iter.next()){var $$=result.value;if(!$$.ptr){console.warn("object already deleted: "+$$.ptr)}else{releaseClassHandle($$)}}});attachFinalizer=function(handle){finalizationGroup.register(handle,handle.$$,handle.$$);return handle};detachFinalizer=function(handle){finalizationGroup.unregister(handle.$$)};return attachFinalizer(handle)}function ClassHandle_clone(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.preservePointerOnDelete){this.$$.count.value+=1;return this}else{var clone=attachFinalizer(Object.create(Object.getPrototypeOf(this),{$$:{value:shallowCopyInternalPointer(this.$$)}}));clone.$$.count.value+=1;clone.$$.deleteScheduled=false;return clone}}function ClassHandle_delete(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}detachFinalizer(this);releaseClassHandle(this.$$);if(!this.$$.preservePointerOnDelete){this.$$.smartPtr=undefined;this.$$.ptr=undefined}}function ClassHandle_isDeleted(){return!this.$$.ptr}var delayFunction=undefined;var deletionQueue=[];function flushPendingDeletes(){while(deletionQueue.length){var obj=deletionQueue.pop();obj.$$.deleteScheduled=false;obj["delete"]()}}function ClassHandle_deleteLater(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}deletionQueue.push(this);if(deletionQueue.length===1&&delayFunction){delayFunction(flushPendingDeletes)}this.$$.deleteScheduled=true;return this}function init_ClassHandle(){ClassHandle.prototype["isAliasOf"]=ClassHandle_isAliasOf;ClassHandle.prototype["clone"]=ClassHandle_clone;ClassHandle.prototype["delete"]=ClassHandle_delete;ClassHandle.prototype["isDeleted"]=ClassHandle_isDeleted;ClassHandle.prototype["deleteLater"]=ClassHandle_deleteLater}function ClassHandle(){}var registeredPointers={};function ensureOverloadTable(proto,methodName,humanName){if(undefined===proto[methodName].overloadTable){var prevFunc=proto[methodName];proto[methodName]=function(){if(!proto[methodName].overloadTable.hasOwnProperty(arguments.length)){throwBindingError("Function '"+humanName+"' called with an invalid number of arguments ("+arguments.length+") - expects one of ("+proto[methodName].overloadTable+")!")}return proto[methodName].overloadTable[arguments.length].apply(this,arguments)};proto[methodName].overloadTable=[];proto[methodName].overloadTable[prevFunc.argCount]=prevFunc}}function exposePublicSymbol(name,value,numArguments){if(Module.hasOwnProperty(name)){if(undefined===numArguments||undefined!==Module[name].overloadTable&&undefined!==Module[name].overloadTable[numArguments]){throwBindingError("Cannot register public name '"+name+"' twice")}ensureOverloadTable(Module,name,name);if(Module.hasOwnProperty(numArguments)){throwBindingError("Cannot register multiple overloads of a function with the same number of arguments ("+numArguments+")!")}Module[name].overloadTable[numArguments]=value}else{Module[name]=value;if(undefined!==numArguments){Module[name].numArguments=numArguments}}}function RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast){this.name=name;this.constructor=constructor;this.instancePrototype=instancePrototype;this.rawDestructor=rawDestructor;this.baseClass=baseClass;this.getActualType=getActualType;this.upcast=upcast;this.downcast=downcast;this.pureVirtualFunctions=[]}function upcastPointer(ptr,ptrClass,desiredClass){while(ptrClass!==desiredClass){if(!ptrClass.upcast){throwBindingError("Expected null or instance of "+desiredClass.name+", got an instance of "+ptrClass.name)}ptr=ptrClass.upcast(ptr);ptrClass=ptrClass.baseClass}return ptr}function constNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError("null is not a valid "+this.name)}return 0}if(!handle.$$){throwBindingError('Cannot pass "'+_embind_repr(handle)+'" as a '+this.name)}if(!handle.$$.ptr){throwBindingError("Cannot pass deleted object as a pointer of type "+this.name)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function genericPointerToWireType(destructors,handle){var ptr;if(handle===null){if(this.isReference){throwBindingError("null is not a valid "+this.name)}if(this.isSmartPointer){ptr=this.rawConstructor();if(destructors!==null){destructors.push(this.rawDestructor,ptr)}return ptr}else{return 0}}if(!handle.$$){throwBindingError('Cannot pass "'+_embind_repr(handle)+'" as a '+this.name)}if(!handle.$$.ptr){throwBindingError("Cannot pass deleted object as a pointer of type "+this.name)}if(!this.isConst&&handle.$$.ptrType.isConst){throwBindingError("Cannot convert argument of type "+(handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name)+" to parameter type "+this.name)}var handleClass=handle.$$.ptrType.registeredClass;ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);if(this.isSmartPointer){if(undefined===handle.$$.smartPtr){throwBindingError("Passing raw pointer to smart pointer is illegal")}switch(this.sharingPolicy){case 0:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{throwBindingError("Cannot convert argument of type "+(handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name)+" to parameter type "+this.name)}break;case 1:ptr=handle.$$.smartPtr;break;case 2:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{var clonedHandle=handle["clone"]();ptr=this.rawShare(ptr,__emval_register(function(){clonedHandle["delete"]()}));if(destructors!==null){destructors.push(this.rawDestructor,ptr)}}break;default:throwBindingError("Unsupporting sharing policy")}}return ptr}function nonConstNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError("null is not a valid "+this.name)}return 0}if(!handle.$$){throwBindingError('Cannot pass "'+_embind_repr(handle)+'" as a '+this.name)}if(!handle.$$.ptr){throwBindingError("Cannot pass deleted object as a pointer of type "+this.name)}if(handle.$$.ptrType.isConst){throwBindingError("Cannot convert argument of type "+handle.$$.ptrType.name+" to parameter type "+this.name)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function RegisteredPointer_getPointee(ptr){if(this.rawGetPointee){ptr=this.rawGetPointee(ptr)}return ptr}function RegisteredPointer_destructor(ptr){if(this.rawDestructor){this.rawDestructor(ptr)}}function RegisteredPointer_deleteObject(handle){if(handle!==null){handle["delete"]()}}function downcastPointer(ptr,ptrClass,desiredClass){if(ptrClass===desiredClass){return ptr}if(undefined===desiredClass.baseClass){return null}var rv=downcastPointer(ptr,ptrClass,desiredClass.baseClass);if(rv===null){return null}return desiredClass.downcast(rv)}function getInheritedInstanceCount(){return Object.keys(registeredInstances).length}function getLiveInheritedInstances(){var rv=[];for(var k in registeredInstances){if(registeredInstances.hasOwnProperty(k)){rv.push(registeredInstances[k])}}return rv}function setDelayFunction(fn){delayFunction=fn;if(deletionQueue.length&&delayFunction){delayFunction(flushPendingDeletes)}}function init_embind(){Module["getInheritedInstanceCount"]=getInheritedInstanceCount;Module["getLiveInheritedInstances"]=getLiveInheritedInstances;Module["flushPendingDeletes"]=flushPendingDeletes;Module["setDelayFunction"]=setDelayFunction}var registeredInstances={};function getBasestPointer(class_,ptr){if(ptr===undefined){throwBindingError("ptr should not be undefined")}while(class_.baseClass){ptr=class_.upcast(ptr);class_=class_.baseClass}return ptr}function getInheritedInstance(class_,ptr){ptr=getBasestPointer(class_,ptr);return registeredInstances[ptr]}function makeClassHandle(prototype,record){if(!record.ptrType||!record.ptr){throwInternalError("makeClassHandle requires ptr and ptrType")}var hasSmartPtrType=!!record.smartPtrType;var hasSmartPtr=!!record.smartPtr;if(hasSmartPtrType!==hasSmartPtr){throwInternalError("Both smartPtrType and smartPtr must be specified")}record.count={value:1};return attachFinalizer(Object.create(prototype,{$$:{value:record}}))}function RegisteredPointer_fromWireType(ptr){var rawPointer=this.getPointee(ptr);if(!rawPointer){this.destructor(ptr);return null}var registeredInstance=getInheritedInstance(this.registeredClass,rawPointer);if(undefined!==registeredInstance){if(0===registeredInstance.$$.count.value){registeredInstance.$$.ptr=rawPointer;registeredInstance.$$.smartPtr=ptr;return registeredInstance["clone"]()}else{var rv=registeredInstance["clone"]();this.destructor(ptr);return rv}}function makeDefaultHandle(){if(this.isSmartPointer){return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this.pointeeType,ptr:rawPointer,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this,ptr:ptr})}}var actualType=this.registeredClass.getActualType(rawPointer);var registeredPointerRecord=registeredPointers[actualType];if(!registeredPointerRecord){return makeDefaultHandle.call(this)}var toType;if(this.isConst){toType=registeredPointerRecord.constPointerType}else{toType=registeredPointerRecord.pointerType}var dp=downcastPointer(rawPointer,this.registeredClass,toType.registeredClass);if(dp===null){return makeDefaultHandle.call(this)}if(this.isSmartPointer){return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp})}}function init_RegisteredPointer(){RegisteredPointer.prototype.getPointee=RegisteredPointer_getPointee;RegisteredPointer.prototype.destructor=RegisteredPointer_destructor;RegisteredPointer.prototype["argPackAdvance"]=8;RegisteredPointer.prototype["readValueFromPointer"]=simpleReadValueFromPointer;RegisteredPointer.prototype["deleteObject"]=RegisteredPointer_deleteObject;RegisteredPointer.prototype["fromWireType"]=RegisteredPointer_fromWireType}function RegisteredPointer(name,registeredClass,isReference,isConst,isSmartPointer,pointeeType,sharingPolicy,rawGetPointee,rawConstructor,rawShare,rawDestructor){this.name=name;this.registeredClass=registeredClass;this.isReference=isReference;this.isConst=isConst;this.isSmartPointer=isSmartPointer;this.pointeeType=pointeeType;this.sharingPolicy=sharingPolicy;this.rawGetPointee=rawGetPointee;this.rawConstructor=rawConstructor;this.rawShare=rawShare;this.rawDestructor=rawDestructor;if(!isSmartPointer&®isteredClass.baseClass===undefined){if(isConst){this["toWireType"]=constNoSmartPtrRawPointerToWireType;this.destructorFunction=null}else{this["toWireType"]=nonConstNoSmartPtrRawPointerToWireType;this.destructorFunction=null}}else{this["toWireType"]=genericPointerToWireType}}function replacePublicSymbol(name,value,numArguments){if(!Module.hasOwnProperty(name)){throwInternalError("Replacing nonexistant public symbol")}if(undefined!==Module[name].overloadTable&&undefined!==numArguments){Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}}function dynCallLegacy(sig,ptr,args){if(args&&args.length){return Module["dynCall_"+sig].apply(null,[ptr].concat(args))}return Module["dynCall_"+sig].call(null,ptr)}function dynCall(sig,ptr,args){if(sig.indexOf("j")!=-1){return dynCallLegacy(sig,ptr,args)}return wasmTable.get(ptr).apply(null,args)}function getDynCaller(sig,ptr){assert(sig.indexOf("j")>=0,"getDynCaller should only be called with i64 sigs");var argCache=[];return function(){argCache.length=arguments.length;for(var i=0;i>2)+i])}return array}function __embind_register_class_constructor(rawClassType,argCount,rawArgTypesAddr,invokerSignature,invoker,rawConstructor){assert(argCount>0);var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);invoker=embind__requireFunction(invokerSignature,invoker);var args=[rawConstructor];var destructors=[];whenDependentTypesAreResolved([],[rawClassType],function(classType){classType=classType[0];var humanName="constructor "+classType.name;if(undefined===classType.registeredClass.constructor_body){classType.registeredClass.constructor_body=[]}if(undefined!==classType.registeredClass.constructor_body[argCount-1]){throw new BindingError("Cannot register multiple constructors with identical number of parameters ("+(argCount-1)+") for class '"+classType.name+"'! Overload resolution is currently only performed using the parameter count, not actual type info!")}classType.registeredClass.constructor_body[argCount-1]=function unboundTypeHandler(){throwUnboundTypeError("Cannot construct "+classType.name+" due to unbound types",rawArgTypes)};whenDependentTypesAreResolved([],rawArgTypes,function(argTypes){classType.registeredClass.constructor_body[argCount-1]=function constructor_body(){if(arguments.length!==argCount-1){throwBindingError(humanName+" called with "+arguments.length+" arguments, expected "+(argCount-1))}destructors.length=0;args.length=argCount;for(var i=1;i0?", ":"")+argsListWired}invokerFnBody+=(returns?"var rv = ":"")+"invoker(fn"+(argsListWired.length>0?", ":"")+argsListWired+");\n";if(needsDestructorStack){invokerFnBody+="runDestructors(destructors);\n"}else{for(var i=isClassMethodFunc?1:2;i4&&0===--emval_handle_array[handle].refcount){emval_handle_array[handle]=undefined;emval_free_list.push(handle)}}function count_emval_handles(){var count=0;for(var i=5;i>1])};case 2:return function(pointer){var heap=signed?HEAP32:HEAPU32;return this["fromWireType"](heap[pointer>>2])};default:throw new TypeError("Unknown integer type: "+name)}}function __embind_register_enum(rawType,name,size,isSigned){var shift=getShiftFromSize(size);name=readLatin1String(name);function ctor(){}ctor.values={};registerType(rawType,{name:name,constructor:ctor,"fromWireType":function(c){return this.constructor.values[c]},"toWireType":function(destructors,c){return c.value},"argPackAdvance":8,"readValueFromPointer":enumReadValueFromPointer(name,shift,isSigned),destructorFunction:null});exposePublicSymbol(name,ctor)}function requireRegisteredType(rawType,humanName){var impl=registeredTypes[rawType];if(undefined===impl){throwBindingError(humanName+" has unknown type "+getTypeName(rawType))}return impl}function __embind_register_enum_value(rawEnumType,name,enumValue){var enumType=requireRegisteredType(rawEnumType,"enum");name=readLatin1String(name);var Enum=enumType.constructor;var Value=Object.create(enumType.constructor.prototype,{value:{value:enumValue},constructor:{value:createNamedFunction(enumType.name+"_"+name,function(){})}});Enum.values[enumValue]=Value;Enum[name]=Value}function _embind_repr(v){if(v===null){return"null"}var t=typeof v;if(t==="object"||t==="array"||t==="function"){return v.toString()}else{return""+v}}function floatReadValueFromPointer(name,shift){switch(shift){case 2:return function(pointer){return this["fromWireType"](HEAPF32[pointer>>2])};case 3:return function(pointer){return this["fromWireType"](HEAPF64[pointer>>3])};default:throw new TypeError("Unknown float type: "+name)}}function __embind_register_float(rawType,name,size){var shift=getShiftFromSize(size);name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":function(value){return value},"toWireType":function(destructors,value){if(typeof value!=="number"&&typeof value!=="boolean"){throw new TypeError('Cannot convert "'+_embind_repr(value)+'" to '+this.name)}return value},"argPackAdvance":8,"readValueFromPointer":floatReadValueFromPointer(name,shift),destructorFunction:null})}function __embind_register_function(name,argCount,rawArgTypesAddr,signature,rawInvoker,fn){var argTypes=heap32VectorToArray(argCount,rawArgTypesAddr);name=readLatin1String(name);rawInvoker=embind__requireFunction(signature,rawInvoker);exposePublicSymbol(name,function(){throwUnboundTypeError("Cannot call "+name+" due to unbound types",argTypes)},argCount-1);whenDependentTypesAreResolved([],argTypes,function(argTypes){var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));replacePublicSymbol(name,craftInvokerFunction(name,invokerArgsArray,null,rawInvoker,fn),argCount-1);return[]})}function integerReadValueFromPointer(name,shift,signed){switch(shift){case 0:return signed?function readS8FromPointer(pointer){return HEAP8[pointer]}:function readU8FromPointer(pointer){return HEAPU8[pointer]};case 1:return signed?function readS16FromPointer(pointer){return HEAP16[pointer>>1]}:function readU16FromPointer(pointer){return HEAPU16[pointer>>1]};case 2:return signed?function readS32FromPointer(pointer){return HEAP32[pointer>>2]}:function readU32FromPointer(pointer){return HEAPU32[pointer>>2]};default:throw new TypeError("Unknown integer type: "+name)}}function __embind_register_integer(primitiveType,name,size,minRange,maxRange){name=readLatin1String(name);if(maxRange===-1){maxRange=4294967295}var shift=getShiftFromSize(size);var fromWireType=function(value){return value};if(minRange===0){var bitshift=32-8*size;fromWireType=function(value){return value<>>bitshift}}var isUnsignedType=name.indexOf("unsigned")!=-1;registerType(primitiveType,{name:name,"fromWireType":fromWireType,"toWireType":function(destructors,value){if(typeof value!=="number"&&typeof value!=="boolean"){throw new TypeError('Cannot convert "'+_embind_repr(value)+'" to '+this.name)}if(valuemaxRange){throw new TypeError('Passing a number "'+_embind_repr(value)+'" from JS side to C/C++ side to an argument of type "'+name+'", which is outside the valid range ['+minRange+", "+maxRange+"]!")}return isUnsignedType?value>>>0:value|0},"argPackAdvance":8,"readValueFromPointer":integerReadValueFromPointer(name,shift,minRange!==0),destructorFunction:null})}function __embind_register_memory_view(rawType,dataTypeIndex,name){var typeMapping=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array];var TA=typeMapping[dataTypeIndex];function decodeMemoryView(handle){handle=handle>>2;var heap=HEAPU32;var size=heap[handle];var data=heap[handle+1];return new TA(buffer,data,size)}name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":decodeMemoryView,"argPackAdvance":8,"readValueFromPointer":decodeMemoryView},{ignoreDuplicateRegistrations:true})}function __embind_register_std_string(rawType,name){name=readLatin1String(name);var stdStringIsUTF8=name==="std::string";registerType(rawType,{name:name,"fromWireType":function(value){var length=HEAPU32[value>>2];var str;if(stdStringIsUTF8){var decodeStartPtr=value+4;for(var i=0;i<=length;++i){var currentBytePtr=value+4+i;if(i==length||HEAPU8[currentBytePtr]==0){var maxRead=currentBytePtr-decodeStartPtr;var stringSegment=UTF8ToString(decodeStartPtr,maxRead);if(str===undefined){str=stringSegment}else{str+=String.fromCharCode(0);str+=stringSegment}decodeStartPtr=currentBytePtr+1}}}else{var a=new Array(length);for(var i=0;i>2]=length;if(stdStringIsUTF8&&valueIsOfTypeString){stringToUTF8(value,ptr+4,length+1)}else{if(valueIsOfTypeString){for(var i=0;i255){_free(ptr);throwBindingError("String has UTF-16 code units that do not fit in 8 bits")}HEAPU8[ptr+4+i]=charCode}}else{for(var i=0;i>2];var HEAP=getHeap();var str;var decodeStartPtr=value+4;for(var i=0;i<=length;++i){var currentBytePtr=value+4+i*charSize;if(i==length||HEAP[currentBytePtr>>shift]==0){var maxReadBytes=currentBytePtr-decodeStartPtr;var stringSegment=decodeString(decodeStartPtr,maxReadBytes);if(str===undefined){str=stringSegment}else{str+=String.fromCharCode(0);str+=stringSegment}decodeStartPtr=currentBytePtr+charSize}}_free(value);return str},"toWireType":function(destructors,value){if(!(typeof value==="string")){throwBindingError("Cannot pass non-string to C++ string type "+name)}var length=lengthBytesUTF(value);var ptr=_malloc(4+length+charSize);HEAPU32[ptr>>2]=length>>shift;encodeString(value,ptr+4,length+charSize);if(destructors!==null){destructors.push(_free,ptr)}return ptr},"argPackAdvance":8,"readValueFromPointer":simpleReadValueFromPointer,destructorFunction:function(ptr){_free(ptr)}})}function __embind_register_value_object(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor){structRegistrations[rawType]={name:readLatin1String(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),fields:[]}}function __embind_register_value_object_field(structType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext){structRegistrations[structType].fields.push({fieldName:readLatin1String(fieldName),getterReturnType:getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext:getterContext,setterArgumentType:setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext:setterContext})}function __embind_register_void(rawType,name){name=readLatin1String(name);registerType(rawType,{isVoid:true,name:name,"argPackAdvance":0,"fromWireType":function(){return undefined},"toWireType":function(destructors,o){return undefined}})}function requireHandle(handle){if(!handle){throwBindingError("Cannot use deleted val. handle = "+handle)}return emval_handle_array[handle].value}function __emval_as(handle,returnType,destructorsRef){handle=requireHandle(handle);returnType=requireRegisteredType(returnType,"emval::as");var destructors=[];var rd=__emval_register(destructors);HEAP32[destructorsRef>>2]=rd;return returnType["toWireType"](destructors,handle)}var emval_symbols={};function getStringOrSymbol(address){var symbol=emval_symbols[address];if(symbol===undefined){return readLatin1String(address)}else{return symbol}}var emval_methodCallers=[];function __emval_call_void_method(caller,handle,methodName,args){caller=emval_methodCallers[caller];handle=requireHandle(handle);methodName=getStringOrSymbol(methodName);caller(handle,methodName,null,args)}function emval_get_global(){if(typeof globalThis==="object"){return globalThis}return function(){return Function}()("return this")()}function __emval_get_global(name){if(name===0){return __emval_register(emval_get_global())}else{name=getStringOrSymbol(name);return __emval_register(emval_get_global()[name])}}function __emval_addMethodCaller(caller){var id=emval_methodCallers.length;emval_methodCallers.push(caller);return id}function __emval_lookupTypes(argCount,argTypes){var a=new Array(argCount);for(var i=0;i>2)+i],"parameter "+i)}return a}function __emval_get_method_caller(argCount,argTypes){var types=__emval_lookupTypes(argCount,argTypes);var retType=types[0];var signatureName=retType.name+"_$"+types.slice(1).map(function(t){return t.name}).join("_")+"$";var params=["retType"];var args=[retType];var argsList="";for(var i=0;i4){emval_handle_array[handle].refcount+=1}}function craftEmvalAllocator(argCount){var argsList="";for(var i=0;i>> 2) + "+i+'], "parameter '+i+'");\n'+"var arg"+i+" = argType"+i+".readValueFromPointer(args);\n"+"args += argType"+i+"['argPackAdvance'];\n"}functionBody+="var obj = new constructor("+argsList+");\n"+"return __emval_register(obj);\n"+"}\n";return new Function("requireRegisteredType","Module","__emval_register",functionBody)(requireRegisteredType,Module,__emval_register)}var emval_newers={};function __emval_new(handle,argCount,argTypes,args){handle=requireHandle(handle);var newer=emval_newers[argCount];if(!newer){newer=craftEmvalAllocator(argCount);emval_newers[argCount]=newer}return newer(handle,argTypes,args)}function __emval_new_cstring(v){return __emval_register(getStringOrSymbol(v))}function __emval_run_destructors(handle){var destructors=emval_handle_array[handle].value;runDestructors(destructors);__emval_decref(handle)}function _abort(){abort()}function _emscripten_memcpy_big(dest,src,num){HEAPU8.copyWithin(dest,src,src+num)}function _emscripten_get_heap_size(){return HEAPU8.length}function emscripten_realloc_buffer(size){try{wasmMemory.grow(size-buffer.byteLength+65535>>>16);updateGlobalBufferAndViews(wasmMemory.buffer);return 1}catch(e){}}function _emscripten_resize_heap(requestedSize){requestedSize=requestedSize>>>0;var oldSize=_emscripten_get_heap_size();var maxHeapSize=2147483648;if(requestedSize>maxHeapSize){return false}var minHeapSize=16777216;for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignUp(Math.max(minHeapSize,requestedSize,overGrownHeapSize),65536));var replacement=emscripten_realloc_buffer(newSize);if(replacement){return true}}return false}var SYSCALLS={mappings:{},buffers:[null,[],[]],printChar:function(stream,curr){var buffer=SYSCALLS.buffers[stream];if(curr===0||curr===10){(stream===1?out:err)(UTF8ArrayToString(buffer,0));buffer.length=0}else{buffer.push(curr)}},varargs:undefined,get:function(){SYSCALLS.varargs+=4;var ret=HEAP32[SYSCALLS.varargs-4>>2];return ret},getStr:function(ptr){var ret=UTF8ToString(ptr);return ret},get64:function(low,high){return low}};function _fd_close(fd){return 0}function _fd_seek(fd,offset_low,offset_high,whence,newOffset){}function _fd_write(fd,iov,iovcnt,pnum){var num=0;for(var i=0;i>2];var len=HEAP32[iov+(i*8+4)>>2];for(var j=0;j>2]=num;return 0}function _setTempRet0($i){setTempRet0($i|0)}InternalError=Module["InternalError"]=extendError(Error,"InternalError");embind_init_charCodes();BindingError=Module["BindingError"]=extendError(Error,"BindingError");init_ClassHandle();init_RegisteredPointer();init_embind();UnboundTypeError=Module["UnboundTypeError"]=extendError(Error,"UnboundTypeError");init_emval();__ATINIT__.push({func:function(){___wasm_call_ctors()}});var asmLibraryArg={"t":__embind_finalize_value_object,"I":__embind_register_bool,"x":__embind_register_class,"w":__embind_register_class_constructor,"d":__embind_register_class_function,"k":__embind_register_constant,"H":__embind_register_emval,"n":__embind_register_enum,"a":__embind_register_enum_value,"A":__embind_register_float,"i":__embind_register_function,"j":__embind_register_integer,"h":__embind_register_memory_view,"B":__embind_register_std_string,"v":__embind_register_std_wstring,"u":__embind_register_value_object,"c":__embind_register_value_object_field,"J":__embind_register_void,"m":__emval_as,"s":__emval_call_void_method,"b":__emval_decref,"y":__emval_get_global,"p":__emval_get_method_caller,"r":__emval_get_module_property,"e":__emval_get_property,"g":__emval_incref,"q":__emval_new,"f":__emval_new_cstring,"l":__emval_run_destructors,"o":_abort,"E":_emscripten_memcpy_big,"F":_emscripten_resize_heap,"G":_fd_close,"C":_fd_seek,"z":_fd_write,"D":_setTempRet0};var asm=createWasm();var ___wasm_call_ctors=Module["___wasm_call_ctors"]=function(){return(___wasm_call_ctors=Module["___wasm_call_ctors"]=Module["asm"]["M"]).apply(null,arguments)};var _malloc=Module["_malloc"]=function(){return(_malloc=Module["_malloc"]=Module["asm"]["N"]).apply(null,arguments)};var _free=Module["_free"]=function(){return(_free=Module["_free"]=Module["asm"]["O"]).apply(null,arguments)};var ___getTypeName=Module["___getTypeName"]=function(){return(___getTypeName=Module["___getTypeName"]=Module["asm"]["P"]).apply(null,arguments)};var ___embind_register_native_and_builtin_types=Module["___embind_register_native_and_builtin_types"]=function(){return(___embind_register_native_and_builtin_types=Module["___embind_register_native_and_builtin_types"]=Module["asm"]["Q"]).apply(null,arguments)};var dynCall_jiji=Module["dynCall_jiji"]=function(){return(dynCall_jiji=Module["dynCall_jiji"]=Module["asm"]["R"]).apply(null,arguments)};var calledRun;function ExitStatus(status){this.name="ExitStatus";this.message="Program terminated with exit("+status+")";this.status=status}dependenciesFulfilled=function runCaller(){if(!calledRun)run();if(!calledRun)dependenciesFulfilled=runCaller};function run(args){args=args||arguments_;if(runDependencies>0){return}preRun();if(runDependencies>0)return;function doRun(){if(calledRun)return;calledRun=true;Module["calledRun"]=true;if(ABORT)return;initRuntime();preMain();readyPromiseResolve(Module);if(Module["onRuntimeInitialized"])Module["onRuntimeInitialized"]();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(function(){setTimeout(function(){Module["setStatus"]("")},1);doRun()},1)}else{doRun()}}Module["run"]=run;if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}noExitRuntime=true;run(); +var Module=moduleArg;var readyPromiseResolve,readyPromiseReject;var readyPromise=new Promise((resolve,reject)=>{readyPromiseResolve=resolve;readyPromiseReject=reject});var ENVIRONMENT_IS_WEB=typeof window=="object";var ENVIRONMENT_IS_WORKER=typeof importScripts=="function";var ENVIRONMENT_IS_NODE=typeof process=="object"&&typeof process.versions=="object"&&typeof process.versions.node=="string";if(ENVIRONMENT_IS_NODE){}var moduleOverrides=Object.assign({},Module);var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_NODE){var fs=require("fs");var nodePath=require("path");scriptDirectory=__dirname+"/";readBinary=filename=>{filename=isFileURI(filename)?new URL(filename):nodePath.normalize(filename);var ret=fs.readFileSync(filename);return ret};readAsync=(filename,binary=true)=>{filename=isFileURI(filename)?new URL(filename):nodePath.normalize(filename);return new Promise((resolve,reject)=>{fs.readFile(filename,binary?undefined:"utf8",(err,data)=>{if(err)reject(err);else resolve(binary?data.buffer:data)})})};if(!Module["thisProgram"]&&process.argv.length>1){thisProgram=process.argv[1].replace(/\\/g,"/")}arguments_=process.argv.slice(2);quit_=(status,toThrow)=>{process.exitCode=status;throw toThrow}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(_scriptName){scriptDirectory=_scriptName}if(scriptDirectory.startsWith("blob:")){scriptDirectory=""}else{scriptDirectory=scriptDirectory.substr(0,scriptDirectory.replace(/[?#].*/,"").lastIndexOf("/")+1)}{if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=url=>{if(isFileURI(url)){return new Promise((reject,resolve)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){resolve(xhr.response)}reject(xhr.status)};xhr.onerror=reject;xhr.send(null)})}return fetch(url,{credentials:"same-origin"}).then(response=>{if(response.ok){return response.arrayBuffer()}return Promise.reject(new Error(response.status+" : "+response.url))})}}}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.error.bind(console);Object.assign(Module,moduleOverrides);moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var wasmBinary;if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];var wasmMemory;var ABORT=false;var EXITSTATUS;var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateMemoryViews(){var b=wasmMemory.buffer;Module["HEAP8"]=HEAP8=new Int8Array(b);Module["HEAP16"]=HEAP16=new Int16Array(b);Module["HEAPU8"]=HEAPU8=new Uint8Array(b);Module["HEAPU16"]=HEAPU16=new Uint16Array(b);Module["HEAP32"]=HEAP32=new Int32Array(b);Module["HEAPU32"]=HEAPU32=new Uint32Array(b);Module["HEAPF32"]=HEAPF32=new Float32Array(b);Module["HEAPF64"]=HEAPF64=new Float64Array(b)}var __ATPRERUN__=[];var __ATINIT__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;callRuntimeCallbacks(__ATINIT__)}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnInit(cb){__ATINIT__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function addRunDependency(id){runDependencies++;Module["monitorRunDependencies"]?.(runDependencies)}function removeRunDependency(id){runDependencies--;Module["monitorRunDependencies"]?.(runDependencies);if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}function abort(what){Module["onAbort"]?.(what);what="Aborted("+what+")";err(what);ABORT=true;EXITSTATUS=1;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject(e);throw e}var dataURIPrefix="data:application/octet-stream;base64,";var isDataURI=filename=>filename.startsWith(dataURIPrefix);var isFileURI=filename=>filename.startsWith("file://");function findWasmBinary(){var f="basis_transcoder.wasm";if(!isDataURI(f)){return locateFile(f)}return f}var wasmBinaryFile;function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}function getBinaryPromise(binaryFile){if(!wasmBinary){return readAsync(binaryFile).then(response=>new Uint8Array(response),()=>getBinarySync(binaryFile))}return Promise.resolve().then(()=>getBinarySync(binaryFile))}function instantiateArrayBuffer(binaryFile,imports,receiver){return getBinaryPromise(binaryFile).then(binary=>WebAssembly.instantiate(binary,imports)).then(receiver,reason=>{err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)})}function instantiateAsync(binary,binaryFile,imports,callback){if(!binary&&typeof WebAssembly.instantiateStreaming=="function"&&!isDataURI(binaryFile)&&!isFileURI(binaryFile)&&!ENVIRONMENT_IS_NODE&&typeof fetch=="function"){return fetch(binaryFile,{credentials:"same-origin"}).then(response=>{var result=WebAssembly.instantiateStreaming(response,imports);return result.then(callback,function(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation");return instantiateArrayBuffer(binaryFile,imports,callback)})})}return instantiateArrayBuffer(binaryFile,imports,callback)}function getWasmImports(){return{a:wasmImports}}function createWasm(){var info=getWasmImports();function receiveInstance(instance,module){wasmExports=instance.exports;wasmMemory=wasmExports["L"];updateMemoryViews();wasmTable=wasmExports["P"];addOnInit(wasmExports["M"]);removeRunDependency("wasm-instantiate");return wasmExports}addRunDependency("wasm-instantiate");function receiveInstantiationResult(result){receiveInstance(result["instance"])}if(Module["instantiateWasm"]){try{return Module["instantiateWasm"](info,receiveInstance)}catch(e){err(`Module.instantiateWasm callback failed with error: ${e}`);readyPromiseReject(e)}}if(!wasmBinaryFile)wasmBinaryFile=findWasmBinary();instantiateAsync(wasmBinary,wasmBinaryFile,info,receiveInstantiationResult).catch(readyPromiseReject);return{}}var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};var noExitRuntime=Module["noExitRuntime"]||true;class ExceptionInfo{constructor(excPtr){this.excPtr=excPtr;this.ptr=excPtr-24}set_type(type){HEAPU32[this.ptr+4>>2]=type}get_type(){return HEAPU32[this.ptr+4>>2]}set_destructor(destructor){HEAPU32[this.ptr+8>>2]=destructor}get_destructor(){return HEAPU32[this.ptr+8>>2]}set_caught(caught){caught=caught?1:0;HEAP8[this.ptr+12]=caught}get_caught(){return HEAP8[this.ptr+12]!=0}set_rethrown(rethrown){rethrown=rethrown?1:0;HEAP8[this.ptr+13]=rethrown}get_rethrown(){return HEAP8[this.ptr+13]!=0}init(type,destructor){this.set_adjusted_ptr(0);this.set_type(type);this.set_destructor(destructor)}set_adjusted_ptr(adjustedPtr){HEAPU32[this.ptr+16>>2]=adjustedPtr}get_adjusted_ptr(){return HEAPU32[this.ptr+16>>2]}get_exception_ptr(){var isPointer=___cxa_is_pointer_type(this.get_type());if(isPointer){return HEAPU32[this.excPtr>>2]}var adjusted=this.get_adjusted_ptr();if(adjusted!==0)return adjusted;return this.excPtr}}var exceptionLast=0;var uncaughtExceptionCount=0;var ___cxa_throw=(ptr,type,destructor)=>{var info=new ExceptionInfo(ptr);info.init(type,destructor);exceptionLast=ptr;uncaughtExceptionCount++;throw exceptionLast};var __abort_js=()=>{abort("")};var structRegistrations={};var runDestructors=destructors=>{while(destructors.length){var ptr=destructors.pop();var del=destructors.pop();del(ptr)}};function readPointer(pointer){return this["fromWireType"](HEAPU32[pointer>>2])}var awaitingDependencies={};var registeredTypes={};var typeDependencies={};var InternalError;var throwInternalError=message=>{throw new InternalError(message)};var whenDependentTypesAreResolved=(myTypes,dependentTypes,getTypeConverters)=>{myTypes.forEach(function(type){typeDependencies[type]=dependentTypes});function onComplete(typeConverters){var myTypeConverters=getTypeConverters(typeConverters);if(myTypeConverters.length!==myTypes.length){throwInternalError("Mismatched type converter count")}for(var i=0;i{if(registeredTypes.hasOwnProperty(dt)){typeConverters[i]=registeredTypes[dt]}else{unregisteredTypes.push(dt);if(!awaitingDependencies.hasOwnProperty(dt)){awaitingDependencies[dt]=[]}awaitingDependencies[dt].push(()=>{typeConverters[i]=registeredTypes[dt];++registered;if(registered===unregisteredTypes.length){onComplete(typeConverters)}})}});if(0===unregisteredTypes.length){onComplete(typeConverters)}};var __embind_finalize_value_object=structType=>{var reg=structRegistrations[structType];delete structRegistrations[structType];var rawConstructor=reg.rawConstructor;var rawDestructor=reg.rawDestructor;var fieldRecords=reg.fields;var fieldTypes=fieldRecords.map(field=>field.getterReturnType).concat(fieldRecords.map(field=>field.setterArgumentType));whenDependentTypesAreResolved([structType],fieldTypes,fieldTypes=>{var fields={};fieldRecords.forEach((field,i)=>{var fieldName=field.fieldName;var getterReturnType=fieldTypes[i];var getter=field.getter;var getterContext=field.getterContext;var setterArgumentType=fieldTypes[i+fieldRecords.length];var setter=field.setter;var setterContext=field.setterContext;fields[fieldName]={read:ptr=>getterReturnType["fromWireType"](getter(getterContext,ptr)),write:(ptr,o)=>{var destructors=[];setter(setterContext,ptr,setterArgumentType["toWireType"](destructors,o));runDestructors(destructors)}}});return[{name:reg.name,fromWireType:ptr=>{var rv={};for(var i in fields){rv[i]=fields[i].read(ptr)}rawDestructor(ptr);return rv},toWireType:(destructors,o)=>{for(var fieldName in fields){if(!(fieldName in o)){throw new TypeError(`Missing field: "${fieldName}"`)}}var ptr=rawConstructor();for(fieldName in fields){fields[fieldName].write(ptr,o[fieldName])}if(destructors!==null){destructors.push(rawDestructor,ptr)}return ptr},argPackAdvance:GenericWireTypeSize,readValueFromPointer:readPointer,destructorFunction:rawDestructor}]})};var __embind_register_bigint=(primitiveType,name,size,minRange,maxRange)=>{};var embind_init_charCodes=()=>{var codes=new Array(256);for(var i=0;i<256;++i){codes[i]=String.fromCharCode(i)}embind_charCodes=codes};var embind_charCodes;var readLatin1String=ptr=>{var ret="";var c=ptr;while(HEAPU8[c]){ret+=embind_charCodes[HEAPU8[c++]]}return ret};var BindingError;var throwBindingError=message=>{throw new BindingError(message)};function sharedRegisterType(rawType,registeredInstance,options={}){var name=registeredInstance.name;if(!rawType){throwBindingError(`type "${name}" must have a positive integer typeid pointer`)}if(registeredTypes.hasOwnProperty(rawType)){if(options.ignoreDuplicateRegistrations){return}else{throwBindingError(`Cannot register type '${name}' twice`)}}registeredTypes[rawType]=registeredInstance;delete typeDependencies[rawType];if(awaitingDependencies.hasOwnProperty(rawType)){var callbacks=awaitingDependencies[rawType];delete awaitingDependencies[rawType];callbacks.forEach(cb=>cb())}}function registerType(rawType,registeredInstance,options={}){if(!("argPackAdvance"in registeredInstance)){throw new TypeError("registerType registeredInstance requires argPackAdvance")}return sharedRegisterType(rawType,registeredInstance,options)}var GenericWireTypeSize=8;var __embind_register_bool=(rawType,name,trueValue,falseValue)=>{name=readLatin1String(name);registerType(rawType,{name:name,fromWireType:function(wt){return!!wt},toWireType:function(destructors,o){return o?trueValue:falseValue},argPackAdvance:GenericWireTypeSize,readValueFromPointer:function(pointer){return this["fromWireType"](HEAPU8[pointer])},destructorFunction:null})};var shallowCopyInternalPointer=o=>({count:o.count,deleteScheduled:o.deleteScheduled,preservePointerOnDelete:o.preservePointerOnDelete,ptr:o.ptr,ptrType:o.ptrType,smartPtr:o.smartPtr,smartPtrType:o.smartPtrType});var throwInstanceAlreadyDeleted=obj=>{function getInstanceTypeName(handle){return handle.$$.ptrType.registeredClass.name}throwBindingError(getInstanceTypeName(obj)+" instance already deleted")};var finalizationRegistry=false;var detachFinalizer=handle=>{};var runDestructor=$$=>{if($$.smartPtr){$$.smartPtrType.rawDestructor($$.smartPtr)}else{$$.ptrType.registeredClass.rawDestructor($$.ptr)}};var releaseClassHandle=$$=>{$$.count.value-=1;var toDelete=0===$$.count.value;if(toDelete){runDestructor($$)}};var downcastPointer=(ptr,ptrClass,desiredClass)=>{if(ptrClass===desiredClass){return ptr}if(undefined===desiredClass.baseClass){return null}var rv=downcastPointer(ptr,ptrClass,desiredClass.baseClass);if(rv===null){return null}return desiredClass.downcast(rv)};var registeredPointers={};var getInheritedInstanceCount=()=>Object.keys(registeredInstances).length;var getLiveInheritedInstances=()=>{var rv=[];for(var k in registeredInstances){if(registeredInstances.hasOwnProperty(k)){rv.push(registeredInstances[k])}}return rv};var deletionQueue=[];var flushPendingDeletes=()=>{while(deletionQueue.length){var obj=deletionQueue.pop();obj.$$.deleteScheduled=false;obj["delete"]()}};var delayFunction;var setDelayFunction=fn=>{delayFunction=fn;if(deletionQueue.length&&delayFunction){delayFunction(flushPendingDeletes)}};var init_embind=()=>{Module["getInheritedInstanceCount"]=getInheritedInstanceCount;Module["getLiveInheritedInstances"]=getLiveInheritedInstances;Module["flushPendingDeletes"]=flushPendingDeletes;Module["setDelayFunction"]=setDelayFunction};var registeredInstances={};var getBasestPointer=(class_,ptr)=>{if(ptr===undefined){throwBindingError("ptr should not be undefined")}while(class_.baseClass){ptr=class_.upcast(ptr);class_=class_.baseClass}return ptr};var getInheritedInstance=(class_,ptr)=>{ptr=getBasestPointer(class_,ptr);return registeredInstances[ptr]};var makeClassHandle=(prototype,record)=>{if(!record.ptrType||!record.ptr){throwInternalError("makeClassHandle requires ptr and ptrType")}var hasSmartPtrType=!!record.smartPtrType;var hasSmartPtr=!!record.smartPtr;if(hasSmartPtrType!==hasSmartPtr){throwInternalError("Both smartPtrType and smartPtr must be specified")}record.count={value:1};return attachFinalizer(Object.create(prototype,{$$:{value:record,writable:true}}))};function RegisteredPointer_fromWireType(ptr){var rawPointer=this.getPointee(ptr);if(!rawPointer){this.destructor(ptr);return null}var registeredInstance=getInheritedInstance(this.registeredClass,rawPointer);if(undefined!==registeredInstance){if(0===registeredInstance.$$.count.value){registeredInstance.$$.ptr=rawPointer;registeredInstance.$$.smartPtr=ptr;return registeredInstance["clone"]()}else{var rv=registeredInstance["clone"]();this.destructor(ptr);return rv}}function makeDefaultHandle(){if(this.isSmartPointer){return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this.pointeeType,ptr:rawPointer,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this,ptr:ptr})}}var actualType=this.registeredClass.getActualType(rawPointer);var registeredPointerRecord=registeredPointers[actualType];if(!registeredPointerRecord){return makeDefaultHandle.call(this)}var toType;if(this.isConst){toType=registeredPointerRecord.constPointerType}else{toType=registeredPointerRecord.pointerType}var dp=downcastPointer(rawPointer,this.registeredClass,toType.registeredClass);if(dp===null){return makeDefaultHandle.call(this)}if(this.isSmartPointer){return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp})}}var attachFinalizer=handle=>{if("undefined"===typeof FinalizationRegistry){attachFinalizer=handle=>handle;return handle}finalizationRegistry=new FinalizationRegistry(info=>{releaseClassHandle(info.$$)});attachFinalizer=handle=>{var $$=handle.$$;var hasSmartPtr=!!$$.smartPtr;if(hasSmartPtr){var info={$$:$$};finalizationRegistry.register(handle,info,handle)}return handle};detachFinalizer=handle=>finalizationRegistry.unregister(handle);return attachFinalizer(handle)};var init_ClassHandle=()=>{Object.assign(ClassHandle.prototype,{isAliasOf(other){if(!(this instanceof ClassHandle)){return false}if(!(other instanceof ClassHandle)){return false}var leftClass=this.$$.ptrType.registeredClass;var left=this.$$.ptr;other.$$=other.$$;var rightClass=other.$$.ptrType.registeredClass;var right=other.$$.ptr;while(leftClass.baseClass){left=leftClass.upcast(left);leftClass=leftClass.baseClass}while(rightClass.baseClass){right=rightClass.upcast(right);rightClass=rightClass.baseClass}return leftClass===rightClass&&left===right},clone(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.preservePointerOnDelete){this.$$.count.value+=1;return this}else{var clone=attachFinalizer(Object.create(Object.getPrototypeOf(this),{$$:{value:shallowCopyInternalPointer(this.$$)}}));clone.$$.count.value+=1;clone.$$.deleteScheduled=false;return clone}},delete(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}detachFinalizer(this);releaseClassHandle(this.$$);if(!this.$$.preservePointerOnDelete){this.$$.smartPtr=undefined;this.$$.ptr=undefined}},isDeleted(){return!this.$$.ptr},deleteLater(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}deletionQueue.push(this);if(deletionQueue.length===1&&delayFunction){delayFunction(flushPendingDeletes)}this.$$.deleteScheduled=true;return this}})};function ClassHandle(){}var createNamedFunction=(name,body)=>Object.defineProperty(body,"name",{value:name});var ensureOverloadTable=(proto,methodName,humanName)=>{if(undefined===proto[methodName].overloadTable){var prevFunc=proto[methodName];proto[methodName]=function(...args){if(!proto[methodName].overloadTable.hasOwnProperty(args.length)){throwBindingError(`Function '${humanName}' called with an invalid number of arguments (${args.length}) - expects one of (${proto[methodName].overloadTable})!`)}return proto[methodName].overloadTable[args.length].apply(this,args)};proto[methodName].overloadTable=[];proto[methodName].overloadTable[prevFunc.argCount]=prevFunc}};var exposePublicSymbol=(name,value,numArguments)=>{if(Module.hasOwnProperty(name)){if(undefined===numArguments||undefined!==Module[name].overloadTable&&undefined!==Module[name].overloadTable[numArguments]){throwBindingError(`Cannot register public name '${name}' twice`)}ensureOverloadTable(Module,name,name);if(Module.hasOwnProperty(numArguments)){throwBindingError(`Cannot register multiple overloads of a function with the same number of arguments (${numArguments})!`)}Module[name].overloadTable[numArguments]=value}else{Module[name]=value;if(undefined!==numArguments){Module[name].numArguments=numArguments}}};var char_0=48;var char_9=57;var makeLegalFunctionName=name=>{if(undefined===name){return"_unknown"}name=name.replace(/[^a-zA-Z0-9_]/g,"$");var f=name.charCodeAt(0);if(f>=char_0&&f<=char_9){return`_${name}`}return name};function RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast){this.name=name;this.constructor=constructor;this.instancePrototype=instancePrototype;this.rawDestructor=rawDestructor;this.baseClass=baseClass;this.getActualType=getActualType;this.upcast=upcast;this.downcast=downcast;this.pureVirtualFunctions=[]}var upcastPointer=(ptr,ptrClass,desiredClass)=>{while(ptrClass!==desiredClass){if(!ptrClass.upcast){throwBindingError(`Expected null or instance of ${desiredClass.name}, got an instance of ${ptrClass.name}`)}ptr=ptrClass.upcast(ptr);ptrClass=ptrClass.baseClass}return ptr};function constNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError(`null is not a valid ${this.name}`)}return 0}if(!handle.$$){throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`)}if(!handle.$$.ptr){throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function genericPointerToWireType(destructors,handle){var ptr;if(handle===null){if(this.isReference){throwBindingError(`null is not a valid ${this.name}`)}if(this.isSmartPointer){ptr=this.rawConstructor();if(destructors!==null){destructors.push(this.rawDestructor,ptr)}return ptr}else{return 0}}if(!handle||!handle.$$){throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`)}if(!handle.$$.ptr){throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`)}if(!this.isConst&&handle.$$.ptrType.isConst){throwBindingError(`Cannot convert argument of type ${handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name} to parameter type ${this.name}`)}var handleClass=handle.$$.ptrType.registeredClass;ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);if(this.isSmartPointer){if(undefined===handle.$$.smartPtr){throwBindingError("Passing raw pointer to smart pointer is illegal")}switch(this.sharingPolicy){case 0:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{throwBindingError(`Cannot convert argument of type ${handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name} to parameter type ${this.name}`)}break;case 1:ptr=handle.$$.smartPtr;break;case 2:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{var clonedHandle=handle["clone"]();ptr=this.rawShare(ptr,Emval.toHandle(()=>clonedHandle["delete"]()));if(destructors!==null){destructors.push(this.rawDestructor,ptr)}}break;default:throwBindingError("Unsupporting sharing policy")}}return ptr}function nonConstNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError(`null is not a valid ${this.name}`)}return 0}if(!handle.$$){throwBindingError(`Cannot pass "${embindRepr(handle)}" as a ${this.name}`)}if(!handle.$$.ptr){throwBindingError(`Cannot pass deleted object as a pointer of type ${this.name}`)}if(handle.$$.ptrType.isConst){throwBindingError(`Cannot convert argument of type ${handle.$$.ptrType.name} to parameter type ${this.name}`)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}var init_RegisteredPointer=()=>{Object.assign(RegisteredPointer.prototype,{getPointee(ptr){if(this.rawGetPointee){ptr=this.rawGetPointee(ptr)}return ptr},destructor(ptr){this.rawDestructor?.(ptr)},argPackAdvance:GenericWireTypeSize,readValueFromPointer:readPointer,fromWireType:RegisteredPointer_fromWireType})};function RegisteredPointer(name,registeredClass,isReference,isConst,isSmartPointer,pointeeType,sharingPolicy,rawGetPointee,rawConstructor,rawShare,rawDestructor){this.name=name;this.registeredClass=registeredClass;this.isReference=isReference;this.isConst=isConst;this.isSmartPointer=isSmartPointer;this.pointeeType=pointeeType;this.sharingPolicy=sharingPolicy;this.rawGetPointee=rawGetPointee;this.rawConstructor=rawConstructor;this.rawShare=rawShare;this.rawDestructor=rawDestructor;if(!isSmartPointer&®isteredClass.baseClass===undefined){if(isConst){this["toWireType"]=constNoSmartPtrRawPointerToWireType;this.destructorFunction=null}else{this["toWireType"]=nonConstNoSmartPtrRawPointerToWireType;this.destructorFunction=null}}else{this["toWireType"]=genericPointerToWireType}}var replacePublicSymbol=(name,value,numArguments)=>{if(!Module.hasOwnProperty(name)){throwInternalError("Replacing nonexistent public symbol")}if(undefined!==Module[name].overloadTable&&undefined!==numArguments){Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}};var dynCallLegacy=(sig,ptr,args)=>{sig=sig.replace(/p/g,"i");var f=Module["dynCall_"+sig];return f(ptr,...args)};var wasmTableMirror=[];var wasmTable;var getWasmTableEntry=funcPtr=>{var func=wasmTableMirror[funcPtr];if(!func){if(funcPtr>=wasmTableMirror.length)wasmTableMirror.length=funcPtr+1;wasmTableMirror[funcPtr]=func=wasmTable.get(funcPtr)}return func};var dynCall=(sig,ptr,args=[])=>{if(sig.includes("j")){return dynCallLegacy(sig,ptr,args)}var rtn=getWasmTableEntry(ptr)(...args);return rtn};var getDynCaller=(sig,ptr)=>(...args)=>dynCall(sig,ptr,args);var embind__requireFunction=(signature,rawFunction)=>{signature=readLatin1String(signature);function makeDynCaller(){if(signature.includes("j")){return getDynCaller(signature,rawFunction)}return getWasmTableEntry(rawFunction)}var fp=makeDynCaller();if(typeof fp!="function"){throwBindingError(`unknown function pointer with signature ${signature}: ${rawFunction}`)}return fp};var extendError=(baseErrorType,errorName)=>{var errorClass=createNamedFunction(errorName,function(message){this.name=errorName;this.message=message;var stack=new Error(message).stack;if(stack!==undefined){this.stack=this.toString()+"\n"+stack.replace(/^Error(:[^\n]*)?\n/,"")}});errorClass.prototype=Object.create(baseErrorType.prototype);errorClass.prototype.constructor=errorClass;errorClass.prototype.toString=function(){if(this.message===undefined){return this.name}else{return`${this.name}: ${this.message}`}};return errorClass};var UnboundTypeError;var getTypeName=type=>{var ptr=___getTypeName(type);var rv=readLatin1String(ptr);_free(ptr);return rv};var throwUnboundTypeError=(message,types)=>{var unboundTypes=[];var seen={};function visit(type){if(seen[type]){return}if(registeredTypes[type]){return}if(typeDependencies[type]){typeDependencies[type].forEach(visit);return}unboundTypes.push(type);seen[type]=true}types.forEach(visit);throw new UnboundTypeError(`${message}: `+unboundTypes.map(getTypeName).join([", "]))};var __embind_register_class=(rawType,rawPointerType,rawConstPointerType,baseClassRawType,getActualTypeSignature,getActualType,upcastSignature,upcast,downcastSignature,downcast,name,destructorSignature,rawDestructor)=>{name=readLatin1String(name);getActualType=embind__requireFunction(getActualTypeSignature,getActualType);upcast&&=embind__requireFunction(upcastSignature,upcast);downcast&&=embind__requireFunction(downcastSignature,downcast);rawDestructor=embind__requireFunction(destructorSignature,rawDestructor);var legalFunctionName=makeLegalFunctionName(name);exposePublicSymbol(legalFunctionName,function(){throwUnboundTypeError(`Cannot construct ${name} due to unbound types`,[baseClassRawType])});whenDependentTypesAreResolved([rawType,rawPointerType,rawConstPointerType],baseClassRawType?[baseClassRawType]:[],base=>{base=base[0];var baseClass;var basePrototype;if(baseClassRawType){baseClass=base.registeredClass;basePrototype=baseClass.instancePrototype}else{basePrototype=ClassHandle.prototype}var constructor=createNamedFunction(name,function(...args){if(Object.getPrototypeOf(this)!==instancePrototype){throw new BindingError("Use 'new' to construct "+name)}if(undefined===registeredClass.constructor_body){throw new BindingError(name+" has no accessible constructor")}var body=registeredClass.constructor_body[args.length];if(undefined===body){throw new BindingError(`Tried to invoke ctor of ${name} with invalid number of parameters (${args.length}) - expected (${Object.keys(registeredClass.constructor_body).toString()}) parameters instead!`)}return body.apply(this,args)});var instancePrototype=Object.create(basePrototype,{constructor:{value:constructor}});constructor.prototype=instancePrototype;var registeredClass=new RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast);if(registeredClass.baseClass){registeredClass.baseClass.__derivedClasses??=[];registeredClass.baseClass.__derivedClasses.push(registeredClass)}var referenceConverter=new RegisteredPointer(name,registeredClass,true,false,false);var pointerConverter=new RegisteredPointer(name+"*",registeredClass,false,false,false);var constPointerConverter=new RegisteredPointer(name+" const*",registeredClass,false,true,false);registeredPointers[rawType]={pointerType:pointerConverter,constPointerType:constPointerConverter};replacePublicSymbol(legalFunctionName,constructor);return[referenceConverter,pointerConverter,constPointerConverter]})};var heap32VectorToArray=(count,firstElement)=>{var array=[];for(var i=0;i>2])}return array};function usesDestructorStack(argTypes){for(var i=1;i0?", ":"")+argsListWired}invokerFnBody+=(returns||isAsync?"var rv = ":"")+"invoker(fn"+(argsListWired.length>0?", ":"")+argsListWired+");\n";if(needsDestructorStack){invokerFnBody+="runDestructors(destructors);\n"}else{for(var i=isClassMethodFunc?1:2;i{var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);invoker=embind__requireFunction(invokerSignature,invoker);whenDependentTypesAreResolved([],[rawClassType],classType=>{classType=classType[0];var humanName=`constructor ${classType.name}`;if(undefined===classType.registeredClass.constructor_body){classType.registeredClass.constructor_body=[]}if(undefined!==classType.registeredClass.constructor_body[argCount-1]){throw new BindingError(`Cannot register multiple constructors with identical number of parameters (${argCount-1}) for class '${classType.name}'! Overload resolution is currently only performed using the parameter count, not actual type info!`)}classType.registeredClass.constructor_body[argCount-1]=()=>{throwUnboundTypeError(`Cannot construct ${classType.name} due to unbound types`,rawArgTypes)};whenDependentTypesAreResolved([],rawArgTypes,argTypes=>{argTypes.splice(1,0,null);classType.registeredClass.constructor_body[argCount-1]=craftInvokerFunction(humanName,argTypes,null,invoker,rawConstructor);return[]});return[]})};var getFunctionName=signature=>{signature=signature.trim();const argsIndex=signature.indexOf("(");if(argsIndex!==-1){return signature.substr(0,argsIndex)}else{return signature}};var __embind_register_class_function=(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,context,isPureVirtual,isAsync)=>{var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=readLatin1String(methodName);methodName=getFunctionName(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker);whenDependentTypesAreResolved([],[rawClassType],classType=>{classType=classType[0];var humanName=`${classType.name}.${methodName}`;if(methodName.startsWith("@@")){methodName=Symbol[methodName.substring(2)]}if(isPureVirtual){classType.registeredClass.pureVirtualFunctions.push(methodName)}function unboundTypesHandler(){throwUnboundTypeError(`Cannot call ${humanName} due to unbound types`,rawArgTypes)}var proto=classType.registeredClass.instancePrototype;var method=proto[methodName];if(undefined===method||undefined===method.overloadTable&&method.className!==classType.name&&method.argCount===argCount-2){unboundTypesHandler.argCount=argCount-2;unboundTypesHandler.className=classType.name;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-2]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,argTypes=>{var memberFunction=craftInvokerFunction(humanName,argTypes,classType,rawInvoker,context,isAsync);if(undefined===proto[methodName].overloadTable){memberFunction.argCount=argCount-2;proto[methodName]=memberFunction}else{proto[methodName].overloadTable[argCount-2]=memberFunction}return[]});return[]})};var __embind_register_constant=(name,type,value)=>{name=readLatin1String(name);whenDependentTypesAreResolved([],[type],type=>{type=type[0];Module[name]=type["fromWireType"](value);return[]})};var emval_freelist=[];var emval_handles=[];var __emval_decref=handle=>{if(handle>9&&0===--emval_handles[handle+1]){emval_handles[handle]=undefined;emval_freelist.push(handle)}};var count_emval_handles=()=>emval_handles.length/2-5-emval_freelist.length;var init_emval=()=>{emval_handles.push(0,1,undefined,1,null,1,true,1,false,1);Module["count_emval_handles"]=count_emval_handles};var Emval={toValue:handle=>{if(!handle){throwBindingError("Cannot use deleted val. handle = "+handle)}return emval_handles[handle]},toHandle:value=>{switch(value){case undefined:return 2;case null:return 4;case true:return 6;case false:return 8;default:{const handle=emval_freelist.pop()||emval_handles.length;emval_handles[handle]=value;emval_handles[handle+1]=1;return handle}}}};var EmValType={name:"emscripten::val",fromWireType:handle=>{var rv=Emval.toValue(handle);__emval_decref(handle);return rv},toWireType:(destructors,value)=>Emval.toHandle(value),argPackAdvance:GenericWireTypeSize,readValueFromPointer:readPointer,destructorFunction:null};var __embind_register_emval=rawType=>registerType(rawType,EmValType);var enumReadValueFromPointer=(name,width,signed)=>{switch(width){case 1:return signed?function(pointer){return this["fromWireType"](HEAP8[pointer])}:function(pointer){return this["fromWireType"](HEAPU8[pointer])};case 2:return signed?function(pointer){return this["fromWireType"](HEAP16[pointer>>1])}:function(pointer){return this["fromWireType"](HEAPU16[pointer>>1])};case 4:return signed?function(pointer){return this["fromWireType"](HEAP32[pointer>>2])}:function(pointer){return this["fromWireType"](HEAPU32[pointer>>2])};default:throw new TypeError(`invalid integer width (${width}): ${name}`)}};var __embind_register_enum=(rawType,name,size,isSigned)=>{name=readLatin1String(name);function ctor(){}ctor.values={};registerType(rawType,{name:name,constructor:ctor,fromWireType:function(c){return this.constructor.values[c]},toWireType:(destructors,c)=>c.value,argPackAdvance:GenericWireTypeSize,readValueFromPointer:enumReadValueFromPointer(name,size,isSigned),destructorFunction:null});exposePublicSymbol(name,ctor)};var requireRegisteredType=(rawType,humanName)=>{var impl=registeredTypes[rawType];if(undefined===impl){throwBindingError(`${humanName} has unknown type ${getTypeName(rawType)}`)}return impl};var __embind_register_enum_value=(rawEnumType,name,enumValue)=>{var enumType=requireRegisteredType(rawEnumType,"enum");name=readLatin1String(name);var Enum=enumType.constructor;var Value=Object.create(enumType.constructor.prototype,{value:{value:enumValue},constructor:{value:createNamedFunction(`${enumType.name}_${name}`,function(){})}});Enum.values[enumValue]=Value;Enum[name]=Value};var embindRepr=v=>{if(v===null){return"null"}var t=typeof v;if(t==="object"||t==="array"||t==="function"){return v.toString()}else{return""+v}};var floatReadValueFromPointer=(name,width)=>{switch(width){case 4:return function(pointer){return this["fromWireType"](HEAPF32[pointer>>2])};case 8:return function(pointer){return this["fromWireType"](HEAPF64[pointer>>3])};default:throw new TypeError(`invalid float width (${width}): ${name}`)}};var __embind_register_float=(rawType,name,size)=>{name=readLatin1String(name);registerType(rawType,{name:name,fromWireType:value=>value,toWireType:(destructors,value)=>value,argPackAdvance:GenericWireTypeSize,readValueFromPointer:floatReadValueFromPointer(name,size),destructorFunction:null})};var __embind_register_function=(name,argCount,rawArgTypesAddr,signature,rawInvoker,fn,isAsync)=>{var argTypes=heap32VectorToArray(argCount,rawArgTypesAddr);name=readLatin1String(name);name=getFunctionName(name);rawInvoker=embind__requireFunction(signature,rawInvoker);exposePublicSymbol(name,function(){throwUnboundTypeError(`Cannot call ${name} due to unbound types`,argTypes)},argCount-1);whenDependentTypesAreResolved([],argTypes,argTypes=>{var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));replacePublicSymbol(name,craftInvokerFunction(name,invokerArgsArray,null,rawInvoker,fn,isAsync),argCount-1);return[]})};var integerReadValueFromPointer=(name,width,signed)=>{switch(width){case 1:return signed?pointer=>HEAP8[pointer]:pointer=>HEAPU8[pointer];case 2:return signed?pointer=>HEAP16[pointer>>1]:pointer=>HEAPU16[pointer>>1];case 4:return signed?pointer=>HEAP32[pointer>>2]:pointer=>HEAPU32[pointer>>2];default:throw new TypeError(`invalid integer width (${width}): ${name}`)}};var __embind_register_integer=(primitiveType,name,size,minRange,maxRange)=>{name=readLatin1String(name);if(maxRange===-1){maxRange=4294967295}var fromWireType=value=>value;if(minRange===0){var bitshift=32-8*size;fromWireType=value=>value<>>bitshift}var isUnsignedType=name.includes("unsigned");var checkAssertions=(value,toTypeName)=>{};var toWireType;if(isUnsignedType){toWireType=function(destructors,value){checkAssertions(value,this.name);return value>>>0}}else{toWireType=function(destructors,value){checkAssertions(value,this.name);return value}}registerType(primitiveType,{name:name,fromWireType:fromWireType,toWireType:toWireType,argPackAdvance:GenericWireTypeSize,readValueFromPointer:integerReadValueFromPointer(name,size,minRange!==0),destructorFunction:null})};var __embind_register_memory_view=(rawType,dataTypeIndex,name)=>{var typeMapping=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array];var TA=typeMapping[dataTypeIndex];function decodeMemoryView(handle){var size=HEAPU32[handle>>2];var data=HEAPU32[handle+4>>2];return new TA(HEAP8.buffer,data,size)}name=readLatin1String(name);registerType(rawType,{name:name,fromWireType:decodeMemoryView,argPackAdvance:GenericWireTypeSize,readValueFromPointer:decodeMemoryView},{ignoreDuplicateRegistrations:true})};var stringToUTF8Array=(str,heap,outIdx,maxBytesToWrite)=>{if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}}heap[outIdx]=0;return outIdx-startIdx};var stringToUTF8=(str,outPtr,maxBytesToWrite)=>stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite);var lengthBytesUTF8=str=>{var len=0;for(var i=0;i=55296&&c<=57343){len+=4;++i}else{len+=3}}return len};var UTF8Decoder=typeof TextDecoder!="undefined"?new TextDecoder:undefined;var UTF8ArrayToString=(heapOrArray,idx,maxBytesToRead)=>{var endIdx=idx+maxBytesToRead;var endPtr=idx;while(heapOrArray[endPtr]&&!(endPtr>=endIdx))++endPtr;if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx>10,56320|ch&1023)}}return str};var UTF8ToString=(ptr,maxBytesToRead)=>ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):"";var __embind_register_std_string=(rawType,name)=>{name=readLatin1String(name);var stdStringIsUTF8=name==="std::string";registerType(rawType,{name:name,fromWireType(value){var length=HEAPU32[value>>2];var payload=value+4;var str;if(stdStringIsUTF8){var decodeStartPtr=payload;for(var i=0;i<=length;++i){var currentBytePtr=payload+i;if(i==length||HEAPU8[currentBytePtr]==0){var maxRead=currentBytePtr-decodeStartPtr;var stringSegment=UTF8ToString(decodeStartPtr,maxRead);if(str===undefined){str=stringSegment}else{str+=String.fromCharCode(0);str+=stringSegment}decodeStartPtr=currentBytePtr+1}}}else{var a=new Array(length);for(var i=0;i>2]=length;if(stdStringIsUTF8&&valueIsOfTypeString){stringToUTF8(value,ptr,length+1)}else{if(valueIsOfTypeString){for(var i=0;i255){_free(ptr);throwBindingError("String has UTF-16 code units that do not fit in 8 bits")}HEAPU8[ptr+i]=charCode}}else{for(var i=0;i{var endPtr=ptr;var idx=endPtr>>1;var maxIdx=idx+maxBytesToRead/2;while(!(idx>=maxIdx)&&HEAPU16[idx])++idx;endPtr=idx<<1;if(endPtr-ptr>32&&UTF16Decoder)return UTF16Decoder.decode(HEAPU8.subarray(ptr,endPtr));var str="";for(var i=0;!(i>=maxBytesToRead/2);++i){var codeUnit=HEAP16[ptr+i*2>>1];if(codeUnit==0)break;str+=String.fromCharCode(codeUnit)}return str};var stringToUTF16=(str,outPtr,maxBytesToWrite)=>{maxBytesToWrite??=2147483647;if(maxBytesToWrite<2)return 0;maxBytesToWrite-=2;var startPtr=outPtr;var numCharsToWrite=maxBytesToWrite>1]=codeUnit;outPtr+=2}HEAP16[outPtr>>1]=0;return outPtr-startPtr};var lengthBytesUTF16=str=>str.length*2;var UTF32ToString=(ptr,maxBytesToRead)=>{var i=0;var str="";while(!(i>=maxBytesToRead/4)){var utf32=HEAP32[ptr+i*4>>2];if(utf32==0)break;++i;if(utf32>=65536){var ch=utf32-65536;str+=String.fromCharCode(55296|ch>>10,56320|ch&1023)}else{str+=String.fromCharCode(utf32)}}return str};var stringToUTF32=(str,outPtr,maxBytesToWrite)=>{maxBytesToWrite??=2147483647;if(maxBytesToWrite<4)return 0;var startPtr=outPtr;var endPtr=startPtr+maxBytesToWrite-4;for(var i=0;i=55296&&codeUnit<=57343){var trailSurrogate=str.charCodeAt(++i);codeUnit=65536+((codeUnit&1023)<<10)|trailSurrogate&1023}HEAP32[outPtr>>2]=codeUnit;outPtr+=4;if(outPtr+4>endPtr)break}HEAP32[outPtr>>2]=0;return outPtr-startPtr};var lengthBytesUTF32=str=>{var len=0;for(var i=0;i=55296&&codeUnit<=57343)++i;len+=4}return len};var __embind_register_std_wstring=(rawType,charSize,name)=>{name=readLatin1String(name);var decodeString,encodeString,readCharAt,lengthBytesUTF;if(charSize===2){decodeString=UTF16ToString;encodeString=stringToUTF16;lengthBytesUTF=lengthBytesUTF16;readCharAt=pointer=>HEAPU16[pointer>>1]}else if(charSize===4){decodeString=UTF32ToString;encodeString=stringToUTF32;lengthBytesUTF=lengthBytesUTF32;readCharAt=pointer=>HEAPU32[pointer>>2]}registerType(rawType,{name:name,fromWireType:value=>{var length=HEAPU32[value>>2];var str;var decodeStartPtr=value+4;for(var i=0;i<=length;++i){var currentBytePtr=value+4+i*charSize;if(i==length||readCharAt(currentBytePtr)==0){var maxReadBytes=currentBytePtr-decodeStartPtr;var stringSegment=decodeString(decodeStartPtr,maxReadBytes);if(str===undefined){str=stringSegment}else{str+=String.fromCharCode(0);str+=stringSegment}decodeStartPtr=currentBytePtr+charSize}}_free(value);return str},toWireType:(destructors,value)=>{if(!(typeof value=="string")){throwBindingError(`Cannot pass non-string to C++ string type ${name}`)}var length=lengthBytesUTF(value);var ptr=_malloc(4+length+charSize);HEAPU32[ptr>>2]=length/charSize;encodeString(value,ptr+4,length+charSize);if(destructors!==null){destructors.push(_free,ptr)}return ptr},argPackAdvance:GenericWireTypeSize,readValueFromPointer:readPointer,destructorFunction(ptr){_free(ptr)}})};var __embind_register_value_object=(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor)=>{structRegistrations[rawType]={name:readLatin1String(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),fields:[]}};var __embind_register_value_object_field=(structType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext)=>{structRegistrations[structType].fields.push({fieldName:readLatin1String(fieldName),getterReturnType:getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext:getterContext,setterArgumentType:setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext:setterContext})};var __embind_register_void=(rawType,name)=>{name=readLatin1String(name);registerType(rawType,{isVoid:true,name:name,argPackAdvance:0,fromWireType:()=>undefined,toWireType:(destructors,o)=>undefined})};var __emscripten_memcpy_js=(dest,src,num)=>HEAPU8.copyWithin(dest,src,src+num);var emval_returnValue=(returnType,destructorsRef,handle)=>{var destructors=[];var result=returnType["toWireType"](destructors,handle);if(destructors.length){HEAPU32[destructorsRef>>2]=Emval.toHandle(destructors)}return result};var __emval_as=(handle,returnType,destructorsRef)=>{handle=Emval.toValue(handle);returnType=requireRegisteredType(returnType,"emval::as");return emval_returnValue(returnType,destructorsRef,handle)};var emval_methodCallers=[];var __emval_call=(caller,handle,destructorsRef,args)=>{caller=emval_methodCallers[caller];handle=Emval.toValue(handle);return caller(null,handle,destructorsRef,args)};var emval_symbols={};var getStringOrSymbol=address=>{var symbol=emval_symbols[address];if(symbol===undefined){return readLatin1String(address)}return symbol};var __emval_call_method=(caller,objHandle,methodName,destructorsRef,args)=>{caller=emval_methodCallers[caller];objHandle=Emval.toValue(objHandle);methodName=getStringOrSymbol(methodName);return caller(objHandle,objHandle[methodName],destructorsRef,args)};var emval_get_global=()=>{if(typeof globalThis=="object"){return globalThis}return function(){return Function}()("return this")()};var __emval_get_global=name=>{if(name===0){return Emval.toHandle(emval_get_global())}else{name=getStringOrSymbol(name);return Emval.toHandle(emval_get_global()[name])}};var emval_addMethodCaller=caller=>{var id=emval_methodCallers.length;emval_methodCallers.push(caller);return id};var emval_lookupTypes=(argCount,argTypes)=>{var a=new Array(argCount);for(var i=0;i>2],"parameter "+i)}return a};var reflectConstruct=Reflect.construct;var __emval_get_method_caller=(argCount,argTypes,kind)=>{var types=emval_lookupTypes(argCount,argTypes);var retType=types.shift();argCount--;var functionBody=`return function (obj, func, destructorsRef, args) {\n`;var offset=0;var argsList=[];if(kind===0){argsList.push("obj")}var params=["retType"];var args=[retType];for(var i=0;it.name).join(", ")}) => ${retType.name}>`;return emval_addMethodCaller(createNamedFunction(functionName,invokerFunction))};var __emval_get_module_property=name=>{name=getStringOrSymbol(name);return Emval.toHandle(Module[name])};var __emval_get_property=(handle,key)=>{handle=Emval.toValue(handle);key=Emval.toValue(key);return Emval.toHandle(handle[key])};var __emval_incref=handle=>{if(handle>9){emval_handles[handle+1]+=1}};var __emval_new_cstring=v=>Emval.toHandle(getStringOrSymbol(v));var __emval_run_destructors=handle=>{var destructors=Emval.toValue(handle);runDestructors(destructors);__emval_decref(handle)};var getHeapMax=()=>2147483648;var growMemory=size=>{var b=wasmMemory.buffer;var pages=(size-b.byteLength+65535)/65536;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}var alignUp=(x,multiple)=>x+(multiple-x%multiple)%multiple;for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignUp(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};var _fd_close=fd=>52;var convertI32PairToI53Checked=(lo,hi)=>hi+2097152>>>0<4194305-!!lo?(lo>>>0)+hi*4294967296:NaN;function _fd_seek(fd,offset_low,offset_high,whence,newOffset){var offset=convertI32PairToI53Checked(offset_low,offset_high);return 70}var printCharBuffers=[null,[],[]];var printChar=(stream,curr)=>{var buffer=printCharBuffers[stream];if(curr===0||curr===10){(stream===1?out:err)(UTF8ArrayToString(buffer,0));buffer.length=0}else{buffer.push(curr)}};var _fd_write=(fd,iov,iovcnt,pnum)=>{var num=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;for(var j=0;j>2]=num;return 0};InternalError=Module["InternalError"]=class InternalError extends Error{constructor(message){super(message);this.name="InternalError"}};embind_init_charCodes();BindingError=Module["BindingError"]=class BindingError extends Error{constructor(message){super(message);this.name="BindingError"}};init_ClassHandle();init_embind();init_RegisteredPointer();UnboundTypeError=Module["UnboundTypeError"]=extendError(Error,"UnboundTypeError");init_emval();var wasmImports={K:___cxa_throw,G:__abort_js,s:__embind_finalize_value_object,C:__embind_register_bigint,I:__embind_register_bool,w:__embind_register_class,v:__embind_register_class_constructor,d:__embind_register_class_function,m:__embind_register_constant,H:__embind_register_emval,o:__embind_register_enum,a:__embind_register_enum_value,A:__embind_register_float,i:__embind_register_function,l:__embind_register_integer,f:__embind_register_memory_view,z:__embind_register_std_string,u:__embind_register_std_wstring,t:__embind_register_value_object,c:__embind_register_value_object_field,J:__embind_register_void,F:__emscripten_memcpy_js,n:__emval_as,q:__emval_call,p:__emval_call_method,b:__emval_decref,x:__emval_get_global,j:__emval_get_method_caller,r:__emval_get_module_property,g:__emval_get_property,k:__emval_incref,h:__emval_new_cstring,e:__emval_run_destructors,D:_emscripten_resize_heap,E:_fd_close,B:_fd_seek,y:_fd_write};var wasmExports=createWasm();var ___wasm_call_ctors=()=>(___wasm_call_ctors=wasmExports["M"])();var ___getTypeName=a0=>(___getTypeName=wasmExports["N"])(a0);var _malloc=a0=>(_malloc=wasmExports["O"])(a0);var _free=a0=>(_free=wasmExports["Q"])(a0);var ___cxa_is_pointer_type=a0=>(___cxa_is_pointer_type=wasmExports["R"])(a0);var dynCall_jiji=Module["dynCall_jiji"]=(a0,a1,a2,a3,a4)=>(dynCall_jiji=Module["dynCall_jiji"]=wasmExports["S"])(a0,a1,a2,a3,a4);var calledRun;dependenciesFulfilled=function runCaller(){if(!calledRun)run();if(!calledRun)dependenciesFulfilled=runCaller};function run(){if(runDependencies>0){return}preRun();if(runDependencies>0){return}function doRun(){if(calledRun)return;calledRun=true;Module["calledRun"]=true;if(ABORT)return;initRuntime();readyPromiseResolve(Module);Module["onRuntimeInitialized"]?.();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(function(){setTimeout(function(){Module["setStatus"]("")},1);doRun()},1)}else{doRun()}}if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}run();moduleRtn=readyPromise; - return BASIS.ready + return moduleRtn; } ); })(); if (typeof exports === 'object' && typeof module === 'object') module.exports = BASIS; else if (typeof define === 'function' && define['amd']) - define([], function() { return BASIS; }); -else if (typeof exports === 'object') - exports["BASIS"] = BASIS; + define([], () => BASIS); diff --git a/examples/jsm/libs/basis/basis_transcoder.wasm b/examples/jsm/libs/basis/basis_transcoder.wasm index 4b9c3437170cf2..bc646825614381 100644 Binary files a/examples/jsm/libs/basis/basis_transcoder.wasm and b/examples/jsm/libs/basis/basis_transcoder.wasm differ diff --git a/examples/jsm/libs/demuxer_mp4.js b/examples/jsm/libs/demuxer_mp4.js new file mode 100644 index 00000000000000..d5cd68de2526c0 --- /dev/null +++ b/examples/jsm/libs/demuxer_mp4.js @@ -0,0 +1,109 @@ +import MP4Box from 'https://cdn.jsdelivr.net/npm/mp4box@0.5.3/+esm'; + +// From: https://w3c.github.io/webcodecs/samples/video-decode-display/ + +// Wraps an MP4Box File as a WritableStream underlying sink. +class MP4FileSink { + #setStatus = null; + #file = null; + #offset = 0; + + constructor(file, setStatus) { + this.#file = file; + this.#setStatus = setStatus; + } + + write(chunk) { + // MP4Box.js requires buffers to be ArrayBuffers, but we have a Uint8Array. + const buffer = new ArrayBuffer(chunk.byteLength); + new Uint8Array(buffer).set(chunk); + + // Inform MP4Box where in the file this chunk is from. + buffer.fileStart = this.#offset; + this.#offset += buffer.byteLength; + + // Append chunk. + this.#setStatus("fetch", (this.#offset / (1024 ** 2)).toFixed(1) + " MiB"); + this.#file.appendBuffer(buffer); + } + + close() { + this.#setStatus("fetch", "Done"); + this.#file.flush(); + } +} + +// Demuxes the first video track of an MP4 file using MP4Box, calling +// `onConfig()` and `onChunk()` with appropriate WebCodecs objects. +export class MP4Demuxer { + #onConfig = null; + #onChunk = null; + #setStatus = null; + #file = null; + + constructor(uri, {onConfig, onChunk, setStatus}) { + this.#onConfig = onConfig; + this.#onChunk = onChunk; + this.#setStatus = setStatus; + + // Configure an MP4Box File for demuxing. + this.#file = MP4Box.createFile(); + this.#file.onError = error => setStatus("demux", error); + this.#file.onReady = this.#onReady.bind(this); + this.#file.onSamples = this.#onSamples.bind(this); + + // Fetch the file and pipe the data through. + const fileSink = new MP4FileSink(this.#file, setStatus); + fetch(uri).then(response => { + // highWaterMark should be large enough for smooth streaming, but lower is + // better for memory usage. + response.body.pipeTo(new WritableStream(fileSink, {highWaterMark: 2})); + }); + } + + // Get the appropriate `description` for a specific track. Assumes that the + // track is H.264, H.265, VP8, VP9, or AV1. + #description(track) { + const trak = this.#file.getTrackById(track.id); + for (const entry of trak.mdia.minf.stbl.stsd.entries) { + const box = entry.avcC || entry.hvcC || entry.vpcC || entry.av1C; + if (box) { + const stream = new MP4Box.DataStream(undefined, 0, MP4Box.DataStream.BIG_ENDIAN); + box.write(stream); + return new Uint8Array(stream.buffer, 8); // Remove the box header. + } + } + throw new Error("avcC, hvcC, vpcC, or av1C box not found"); + } + + #onReady(info) { + this.#setStatus("demux", "Ready"); + const track = info.videoTracks[0]; + + // Generate and emit an appropriate VideoDecoderConfig. + this.#onConfig({ + // Browser doesn't support parsing full vp8 codec (eg: `vp08.00.41.08`), + // they only support `vp8`. + codec: track.codec.startsWith('vp08') ? 'vp8' : track.codec, + codedHeight: track.video.height, + codedWidth: track.video.width, + description: this.#description(track), + }); + + // Start demuxing. + this.#file.setExtractionOptions(track.id); + this.#file.start(); + } + + #onSamples(track_id, ref, samples) { + // Generate and emit an EncodedVideoChunk for each demuxed sample. + for (const sample of samples) { + this.#onChunk(new EncodedVideoChunk({ + type: sample.is_sync ? "key" : "delta", + timestamp: 1e6 * sample.cts / sample.timescale, + duration: 1e6 * sample.duration / sample.timescale, + data: sample.data + })); + } + } +} diff --git a/examples/jsm/libs/draco/README.md b/examples/jsm/libs/draco/README.md index 6dfa1d3a9b603f..4d891646db2fad 100644 --- a/examples/jsm/libs/draco/README.md +++ b/examples/jsm/libs/draco/README.md @@ -17,10 +17,10 @@ Each file is provided in two variations: * **Default:** Latest stable builds, tracking the project's [master branch](https://github.com/google/draco). * **glTF:** Builds targeted by the [glTF mesh compression extension](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_draco_mesh_compression), tracking the [corresponding Draco branch](https://github.com/google/draco/tree/gltf_2.0_draco_extension). -Either variation may be used with `THREE.DRACOLoader`: +Either variation may be used with `DRACOLoader`: ```js -var dracoLoader = new THREE.DRACOLoader(); +var dracoLoader = new DRACOLoader(); dracoLoader.setDecoderPath('path/to/decoders/'); dracoLoader.setDecoderConfig({type: 'js'}); // (Optional) Override detection of WASM support. ``` diff --git a/examples/jsm/libs/fflate.module.js b/examples/jsm/libs/fflate.module.js index 808000a503cf63..06383895963036 100644 --- a/examples/jsm/libs/fflate.module.js +++ b/examples/jsm/libs/fflate.module.js @@ -2,7 +2,7 @@ fflate - fast JavaScript compression/decompression Licensed under MIT. https://github.com/101arrowz/fflate/blob/master/LICENSE -version 0.6.9 +version 0.8.2 */ // DEFLATE is a complex format; to read this code, you should probably check the RFC first: @@ -15,31 +15,30 @@ version 0.6.9 // Sometimes 0 will appear where -1 would be more appropriate. This is because using a uint // is better for memory in most engines (I *think*). var ch2 = {}; -var durl = function (c) { return URL.createObjectURL(new Blob([c], { type: 'text/javascript' })); }; -var cwk = function (u) { return new Worker(u); }; -try { - URL.revokeObjectURL(durl('')); -} -catch (e) { - // We're in Deno or a very old browser - durl = function (c) { return 'data:application/javascript;charset=UTF-8,' + encodeURI(c); }; - // If Deno, this is necessary; if not, this changes nothing - cwk = function (u) { return new Worker(u, { type: 'module' }); }; -} var wk = (function (c, id, msg, transfer, cb) { - var w = cwk(ch2[id] || (ch2[id] = durl(c))); - w.onerror = function (e) { return cb(e.error, null); }; - w.onmessage = function (e) { return cb(null, e.data); }; + var w = new Worker(ch2[id] || (ch2[id] = URL.createObjectURL(new Blob([ + c + ';addEventListener("error",function(e){e=e.error;postMessage({$e$:[e.message,e.code,e.stack]})})' + ], { type: 'text/javascript' })))); + w.onmessage = function (e) { + var d = e.data, ed = d.$e$; + if (ed) { + var err = new Error(ed[0]); + err['code'] = ed[1]; + err.stack = ed[2]; + cb(err, null); + } + else + cb(null, d); + }; w.postMessage(msg, transfer); return w; }); // aliases for shorter compressed code (most minifers don't do this) -var u8 = Uint8Array, u16 = Uint16Array, u32 = Uint32Array; +var u8 = Uint8Array, u16 = Uint16Array, i32 = Int32Array; // fixed length extra bits var fleb = new u8([0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, /* unused */ 0, 0, /* impossible */ 0]); // fixed distance extra bits -// see fleb note var fdeb = new u8([0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, /* unused */ 0, 0]); // code length index map var clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]); @@ -50,26 +49,26 @@ var freb = function (eb, start) { b[i] = start += 1 << eb[i - 1]; } // numbers here are at max 18 bits - var r = new u32(b[30]); + var r = new i32(b[30]); for (var i = 1; i < 30; ++i) { for (var j = b[i]; j < b[i + 1]; ++j) { r[j] = ((j - b[i]) << 5) | i; } } - return [b, r]; + return { b: b, r: r }; }; -var _a = freb(fleb, 2), fl = _a[0], revfl = _a[1]; +var _a = freb(fleb, 2), fl = _a.b, revfl = _a.r; // we can ignore the fact that the other numbers are wrong; they never happen anyway fl[28] = 258, revfl[258] = 28; -var _b = freb(fdeb, 0), fd = _b[0], revfd = _b[1]; +var _b = freb(fdeb, 0), fd = _b.b, revfd = _b.r; // map of value to reverse (assuming 16 bits) var rev = new u16(32768); for (var i = 0; i < 32768; ++i) { // reverse table algorithm from SO - var x = ((i & 0xAAAA) >>> 1) | ((i & 0x5555) << 1); - x = ((x & 0xCCCC) >>> 2) | ((x & 0x3333) << 2); - x = ((x & 0xF0F0) >>> 4) | ((x & 0x0F0F) << 4); - rev[i] = (((x & 0xFF00) >>> 8) | ((x & 0x00FF) << 8)) >>> 1; + var x = ((i & 0xAAAA) >> 1) | ((i & 0x5555) << 1); + x = ((x & 0xCCCC) >> 2) | ((x & 0x3333) << 2); + x = ((x & 0xF0F0) >> 4) | ((x & 0x0F0F) << 4); + rev[i] = (((x & 0xFF00) >> 8) | ((x & 0x00FF) << 8)) >> 1; } // create huffman tree from u8 "map": index -> code length for code index // mb (max bits) must be at most 15 @@ -81,11 +80,13 @@ var hMap = (function (cd, mb, r) { // u16 "map": index -> # of codes with bit length = index var l = new u16(mb); // length of cd must be 288 (total # of codes) - for (; i < s; ++i) - ++l[cd[i] - 1]; + for (; i < s; ++i) { + if (cd[i]) + ++l[cd[i] - 1]; + } // u16 "map": index -> minimum code for bit length = index var le = new u16(mb); - for (i = 0; i < mb; ++i) { + for (i = 1; i < mb; ++i) { le[i] = (le[i - 1] + l[i - 1]) << 1; } var co; @@ -106,7 +107,7 @@ var hMap = (function (cd, mb, r) { // m is end value for (var m = v | ((1 << r_1) - 1); v <= m; ++v) { // every 16 bit value starting with the code yields the same result - co[rev[v] >>> rvb] = sv; + co[rev[v] >> rvb] = sv; } } } @@ -115,7 +116,7 @@ var hMap = (function (cd, mb, r) { co = new u16(s); for (i = 0; i < s; ++i) { if (cd[i]) { - co[i] = rev[le[cd[i] - 1]++] >>> (15 - cd[i]); + co[i] = rev[le[cd[i] - 1]++] >> (15 - cd[i]); } } } @@ -159,7 +160,7 @@ var bits16 = function (d, p) { return ((d[o] | (d[o + 1] << 8) | (d[o + 2] << 16)) >> (p & 7)); }; // get end of byte -var shft = function (p) { return ((p / 8) | 0) + (p & 7 && 1); }; +var shft = function (p) { return ((p + 7) / 8) | 0; }; // typed array slice - allows garbage collector to free original reference, // while being more compatible than .slice var slc = function (v, s, e) { @@ -168,24 +169,69 @@ var slc = function (v, s, e) { if (e == null || e > v.length) e = v.length; // can't use .constructor in case user-supplied - var n = new (v instanceof u16 ? u16 : v instanceof u32 ? u32 : u8)(e - s); - n.set(v.subarray(s, e)); - return n; + return new u8(v.subarray(s, e)); +}; +/** + * Codes for errors generated within this library + */ +export var FlateErrorCode = { + UnexpectedEOF: 0, + InvalidBlockType: 1, + InvalidLengthLiteral: 2, + InvalidDistance: 3, + StreamFinished: 4, + NoStreamHandler: 5, + InvalidHeader: 6, + NoCallback: 7, + InvalidUTF8: 8, + ExtraFieldTooLong: 9, + InvalidDate: 10, + FilenameTooLong: 11, + StreamFinishing: 12, + InvalidZipData: 13, + UnknownCompressionMethod: 14 +}; +// error codes +var ec = [ + 'unexpected EOF', + 'invalid block type', + 'invalid length/literal', + 'invalid distance', + 'stream finished', + 'no stream handler', + , + 'no callback', + 'invalid UTF-8 data', + 'extra field too long', + 'date not in range 1980-2099', + 'filename too long', + 'stream finishing', + 'invalid zip data' + // determined by unknown compression method +]; +; +var err = function (ind, msg, nt) { + var e = new Error(msg || ec[ind]); + e.code = ind; + if (Error.captureStackTrace) + Error.captureStackTrace(e, err); + if (!nt) + throw e; + return e; }; // expands raw DEFLATE data -var inflt = function (dat, buf, st) { - // source length - var sl = dat.length; - if (!sl || (st && !st.l && sl < 5)) +var inflt = function (dat, st, buf, dict) { + // source length dict length + var sl = dat.length, dl = dict ? dict.length : 0; + if (!sl || st.f && !st.l) return buf || new u8(0); + var noBuf = !buf; // have to estimate size - var noBuf = !buf || st; + var resize = noBuf || st.i != 2; // no state - var noSt = !st || st.i; - if (!st) - st = {}; + var noSt = st.i; // Assumes roughly 33% compression ratio average - if (!buf) + if (noBuf) buf = new u8(sl * 3); // ensure buffer can fit at least l elements var cbuf = function (l) { @@ -205,7 +251,7 @@ var inflt = function (dat, buf, st) { do { if (!lm) { // BFINAL - this is only 1 when last chunk is next - st.f = final = bits(dat, pos, 1); + final = bits(dat, pos, 1); // type: 0 = no compression, 1 = fixed huffman, 2 = dynamic huffman var type = bits(dat, pos + 1, 3); pos += 3; @@ -214,16 +260,16 @@ var inflt = function (dat, buf, st) { var s = shft(pos) + 4, l = dat[s - 4] | (dat[s - 3] << 8), t = s + l; if (t > sl) { if (noSt) - throw 'unexpected EOF'; + err(0); break; } // ensure size - if (noBuf) + if (resize) cbuf(bt + l); // Copy over uncompressed data buf.set(dat.subarray(s, t), bt); // Get new bitpos, update byte count - st.b = bt += l, st.p = pos = t * 8; + st.b = bt += l, st.p = pos = t * 8, st.f = final; continue; } else if (type == 1) @@ -251,7 +297,7 @@ var inflt = function (dat, buf, st) { // bits read pos += r & 15; // symbol - var s = r >>> 4; + var s = r >> 4; // code length to copy if (s < 16) { ldt[i++] = s; @@ -279,30 +325,30 @@ var inflt = function (dat, buf, st) { dm = hMap(dt, dbt, 1); } else - throw 'invalid block type'; + err(1); if (pos > tbts) { if (noSt) - throw 'unexpected EOF'; + err(0); break; } } // Make sure the buffer can hold this + the largest possible addition - // Maximum chunk size (practically, theoretically infinite) is 2^17; - if (noBuf) + // Maximum chunk size (practically, theoretically infinite) is 2^17 + if (resize) cbuf(bt + 131072); var lms = (1 << lbt) - 1, dms = (1 << dbt) - 1; var lpos = pos; for (;; lpos = pos) { // bits read, code - var c = lm[bits16(dat, pos) & lms], sym = c >>> 4; + var c = lm[bits16(dat, pos) & lms], sym = c >> 4; pos += c & 15; if (pos > tbts) { if (noSt) - throw 'unexpected EOF'; + err(0); break; } if (!c) - throw 'invalid length/literal'; + err(2); if (sym < 256) buf[bt++] = sym; else if (sym == 256) { @@ -319,52 +365,55 @@ var inflt = function (dat, buf, st) { pos += b; } // dist - var d = dm[bits16(dat, pos) & dms], dsym = d >>> 4; + var d = dm[bits16(dat, pos) & dms], dsym = d >> 4; if (!d) - throw 'invalid distance'; + err(3); pos += d & 15; var dt = fd[dsym]; if (dsym > 3) { var b = fdeb[dsym]; - dt += bits16(dat, pos) & ((1 << b) - 1), pos += b; + dt += bits16(dat, pos) & (1 << b) - 1, pos += b; } if (pos > tbts) { if (noSt) - throw 'unexpected EOF'; + err(0); break; } - if (noBuf) + if (resize) cbuf(bt + 131072); var end = bt + add; - for (; bt < end; bt += 4) { - buf[bt] = buf[bt - dt]; - buf[bt + 1] = buf[bt + 1 - dt]; - buf[bt + 2] = buf[bt + 2 - dt]; - buf[bt + 3] = buf[bt + 3 - dt]; + if (bt < dt) { + var shift = dl - dt, dend = Math.min(dt, end); + if (shift + bt < 0) + err(3); + for (; bt < dend; ++bt) + buf[bt] = dict[shift + bt]; } - bt = end; + for (; bt < end; ++bt) + buf[bt] = buf[bt - dt]; } } - st.l = lm, st.p = lpos, st.b = bt; + st.l = lm, st.p = lpos, st.b = bt, st.f = final; if (lm) final = 1, st.m = lbt, st.d = dm, st.n = dbt; } while (!final); - return bt == buf.length ? buf : slc(buf, 0, bt); + // don't reallocate for streams or user buffers + return bt != buf.length && noBuf ? slc(buf, 0, bt) : buf.subarray(0, bt); }; // starting at p, write the minimum number of bits that can hold v to d var wbits = function (d, p, v) { v <<= p & 7; var o = (p / 8) | 0; d[o] |= v; - d[o + 1] |= v >>> 8; + d[o + 1] |= v >> 8; }; // starting at p, write the minimum number of bits (>8) that can hold v to d var wbits16 = function (d, p, v) { v <<= p & 7; var o = (p / 8) | 0; d[o] |= v; - d[o + 1] |= v >>> 8; - d[o + 2] |= v >>> 16; + d[o + 1] |= v >> 8; + d[o + 2] |= v >> 16; }; // creates code lengths from a frequency table var hTree = function (d, mb) { @@ -377,11 +426,11 @@ var hTree = function (d, mb) { var s = t.length; var t2 = t.slice(); if (!s) - return [et, 0]; + return { t: et, l: 0 }; if (s == 1) { var v = new u8(t[0].s + 1); v[t[0].s] = 1; - return [v, 1]; + return { t: v, l: 1 }; } t.sort(function (a, b) { return a.f - b.f; }); // after i2 reaches last ind, will be stopped @@ -425,7 +474,7 @@ var hTree = function (d, mb) { else break; } - dt >>>= lft; + dt >>= lft; while (dt > 0) { var i2_2 = t2[i].s; if (tr[i2_2] < mb) @@ -442,7 +491,7 @@ var hTree = function (d, mb) { } mbt = mb; } - return [new u8(tr), mbt]; + return { t: new u8(tr), l: mbt }; }; // get the max length and assign length codes var ln = function (n, l, d) { @@ -485,7 +534,7 @@ var lc = function (c) { cln = c[i]; } } - return [cl.subarray(0, cli), s]; + return { c: cl.subarray(0, cli), n: s }; }; // calculate the length of output from tree, code lengths var clen = function (cf, cl) { @@ -501,7 +550,7 @@ var wfblk = function (out, pos, dat) { var s = dat.length; var o = shft(pos + 2); out[o] = s & 255; - out[o + 1] = s >>> 8; + out[o + 1] = s >> 8; out[o + 2] = out[o] ^ 255; out[o + 3] = out[o + 1] ^ 255; for (var i = 0; i < s; ++i) @@ -512,23 +561,23 @@ var wfblk = function (out, pos, dat) { var wblk = function (dat, out, final, syms, lf, df, eb, li, bs, bl, p) { wbits(out, p++, final); ++lf[256]; - var _a = hTree(lf, 15), dlt = _a[0], mlb = _a[1]; - var _b = hTree(df, 15), ddt = _b[0], mdb = _b[1]; - var _c = lc(dlt), lclt = _c[0], nlc = _c[1]; - var _d = lc(ddt), lcdt = _d[0], ndc = _d[1]; + var _a = hTree(lf, 15), dlt = _a.t, mlb = _a.l; + var _b = hTree(df, 15), ddt = _b.t, mdb = _b.l; + var _c = lc(dlt), lclt = _c.c, nlc = _c.n; + var _d = lc(ddt), lcdt = _d.c, ndc = _d.n; var lcfreq = new u16(19); for (var i = 0; i < lclt.length; ++i) - lcfreq[lclt[i] & 31]++; + ++lcfreq[lclt[i] & 31]; for (var i = 0; i < lcdt.length; ++i) - lcfreq[lcdt[i] & 31]++; - var _e = hTree(lcfreq, 7), lct = _e[0], mlcb = _e[1]; + ++lcfreq[lcdt[i] & 31]; + var _e = hTree(lcfreq, 7), lct = _e.t, mlcb = _e.l; var nlcc = 19; for (; nlcc > 4 && !lct[clim[nlcc - 1]]; --nlcc) ; var flen = (bl + 5) << 3; var ftlen = clen(lf, flt) + clen(df, fdt) + eb; - var dtlen = clen(lf, dlt) + clen(df, ddt) + eb + 14 + 3 * nlcc + clen(lcfreq, lct) + (2 * lcfreq[16] + 3 * lcfreq[17] + 7 * lcfreq[18]); - if (flen <= ftlen && flen <= dtlen) + var dtlen = clen(lf, dlt) + clen(df, ddt) + eb + 14 + 3 * nlcc + clen(lcfreq, lct) + 2 * lcfreq[16] + 3 * lcfreq[17] + 7 * lcfreq[18]; + if (bs >= 0 && flen <= ftlen && flen <= dtlen) return wfblk(out, p, dat.subarray(bs, bs + bl)); var lm, ll, dm, dl; wbits(out, p, 1 + (dtlen < ftlen)), p += 2; @@ -549,7 +598,7 @@ var wblk = function (dat, out, final, syms, lf, df, eb, li, bs, bl, p) { var len = clct[i] & 31; wbits(out, p, llm[len]), p += lct[len]; if (len > 15) - wbits(out, p, (clct[i] >>> 5) & 127), p += clct[i] >>> 12; + wbits(out, p, (clct[i] >> 5) & 127), p += clct[i] >> 12; } } } @@ -557,67 +606,55 @@ var wblk = function (dat, out, final, syms, lf, df, eb, li, bs, bl, p) { lm = flm, ll = flt, dm = fdm, dl = fdt; } for (var i = 0; i < li; ++i) { - if (syms[i] > 255) { - var len = (syms[i] >>> 18) & 31; + var sym = syms[i]; + if (sym > 255) { + var len = (sym >> 18) & 31; wbits16(out, p, lm[len + 257]), p += ll[len + 257]; if (len > 7) - wbits(out, p, (syms[i] >>> 23) & 31), p += fleb[len]; - var dst = syms[i] & 31; + wbits(out, p, (sym >> 23) & 31), p += fleb[len]; + var dst = sym & 31; wbits16(out, p, dm[dst]), p += dl[dst]; if (dst > 3) - wbits16(out, p, (syms[i] >>> 5) & 8191), p += fdeb[dst]; + wbits16(out, p, (sym >> 5) & 8191), p += fdeb[dst]; } else { - wbits16(out, p, lm[syms[i]]), p += ll[syms[i]]; + wbits16(out, p, lm[sym]), p += ll[sym]; } } wbits16(out, p, lm[256]); return p + ll[256]; }; // deflate options (nice << 13) | chain -var deo = /*#__PURE__*/ new u32([65540, 131080, 131088, 131104, 262176, 1048704, 1048832, 2114560, 2117632]); +var deo = /*#__PURE__*/ new i32([65540, 131080, 131088, 131104, 262176, 1048704, 1048832, 2114560, 2117632]); // empty var et = /*#__PURE__*/ new u8(0); // compresses data into a raw DEFLATE buffer -var dflt = function (dat, lvl, plvl, pre, post, lst) { - var s = dat.length; +var dflt = function (dat, lvl, plvl, pre, post, st) { + var s = st.z || dat.length; var o = new u8(pre + s + 5 * (1 + Math.ceil(s / 7000)) + post); // writing to this writes to the output buffer var w = o.subarray(pre, o.length - post); - var pos = 0; - if (!lvl || s < 8) { - for (var i = 0; i <= s; i += 65535) { - // end - var e = i + 65535; - if (e < s) { - // write full block - pos = wfblk(w, pos, dat.subarray(i, e)); - } - else { - // write final block - w[i] = lst; - pos = wfblk(w, pos, dat.subarray(i, s)); - } - } - } - else { + var lst = st.l; + var pos = (st.r || 0) & 7; + if (lvl) { + if (pos) + w[0] = st.r >> 3; var opt = deo[lvl - 1]; - var n = opt >>> 13, c = opt & 8191; + var n = opt >> 13, c = opt & 8191; var msk_1 = (1 << plvl) - 1; // prev 2-byte val map curr 2-byte val map - var prev = new u16(32768), head = new u16(msk_1 + 1); + var prev = st.p || new u16(32768), head = st.h || new u16(msk_1 + 1); var bs1_1 = Math.ceil(plvl / 3), bs2_1 = 2 * bs1_1; var hsh = function (i) { return (dat[i] ^ (dat[i + 1] << bs1_1) ^ (dat[i + 2] << bs2_1)) & msk_1; }; // 24576 is an arbitrary number of maximum symbols per block // 424 buffer for last block - var syms = new u32(25000); + var syms = new i32(25000); // length/literal freq distance freq var lf = new u16(288), df = new u16(32); - // l/lcnt exbits index l/lind waitdx bitpos - var lc_1 = 0, eb = 0, i = 0, li = 0, wi = 0, bs = 0; - for (; i < s; ++i) { + // l/lcnt exbits index l/lind waitdx blkpos + var lc_1 = 0, eb = 0, i = st.i || 0, li = 0, wi = st.w || 0, bs = 0; + for (; i + 2 < s; ++i) { // hash value - // deopt when i > s - 3 - at end, deopt acceptable var hv = hsh(i); // index mod 32768 previous index mod var imod = i & 32767, pimod = head[hv]; @@ -628,7 +665,7 @@ var dflt = function (dat, lvl, plvl, pre, post, lst) { if (wi <= i) { // bytes remaining var rem = s - i; - if ((lc_1 > 7000 || li > 24576) && rem > 423) { + if ((lc_1 > 7000 || li > 24576) && (rem > 423 || !lst)) { pos = wblk(dat, w, 0, syms, lf, df, eb, li, bs, i - bs, pos); li = lc_1 = eb = 0, bs = i; for (var j = 0; j < 286; ++j) @@ -637,7 +674,7 @@ var dflt = function (dat, lvl, plvl, pre, post, lst) { df[j] = 0; } // len dist chain - var l = 2, d = 0, ch_1 = c, dif = (imod - pimod) & 32767; + var l = 2, d = 0, ch_1 = c, dif = imod - pimod & 32767; if (rem > 2 && hv == hsh(i - dif)) { var maxn = Math.min(n, rem) - 1; var maxd = Math.min(32767, i); @@ -660,9 +697,9 @@ var dflt = function (dat, lvl, plvl, pre, post, lst) { var mmd = Math.min(dif, nl - 2); var md = 0; for (var j = 0; j < mmd; ++j) { - var ti = (i - dif + j + 32768) & 32767; + var ti = i - dif + j & 32767; var pti = prev[ti]; - var cd = (ti - pti + 32768) & 32767; + var cd = ti - pti & 32767; if (cd > md) md = cd, pimod = ti; } @@ -670,12 +707,12 @@ var dflt = function (dat, lvl, plvl, pre, post, lst) { } // check the previous match imod = pimod, pimod = prev[imod]; - dif += (imod - pimod + 32768) & 32767; + dif += imod - pimod & 32767; } } // d will be nonzero only when a match was found if (d) { - // store both dist and len data in one Uint32 + // store both dist and len data in one int32 // Make sure this is recognized as a len/dist with 28th bit (2^28) syms[li++] = 268435456 | (revfl[l] << 18) | revfd[d]; var lin = revfl[l] & 31, din = revfd[d] & 31; @@ -691,20 +728,40 @@ var dflt = function (dat, lvl, plvl, pre, post, lst) { } } } + for (i = Math.max(i, wi); i < s; ++i) { + syms[li++] = dat[i]; + ++lf[dat[i]]; + } pos = wblk(dat, w, lst, syms, lf, df, eb, li, bs, i - bs, pos); - // this is the easiest way to avoid needing to maintain state - if (!lst && pos & 7) - pos = wfblk(w, pos + 1, et); + if (!lst) { + st.r = (pos & 7) | w[(pos / 8) | 0] << 3; + // shft(pos) now 1 less if pos & 7 != 0 + pos -= 7; + st.h = head, st.p = prev, st.i = i, st.w = wi; + } + } + else { + for (var i = st.w || 0; i < s + lst; i += 65535) { + // end + var e = i + 65535; + if (e >= s) { + // write final block + w[(pos / 8) | 0] = lst; + e = s; + } + pos = wfblk(w, pos + 1, dat.subarray(i, e)); + } + st.i = s; } return slc(o, 0, pre + shft(pos) + post); }; // CRC32 table var crct = /*#__PURE__*/ (function () { - var t = new u32(256); + var t = new Int32Array(256); for (var i = 0; i < 256; ++i) { var c = i, k = 9; while (--k) - c = ((c & 1) && 0xEDB88320) ^ (c >>> 1); + c = ((c & 1) && -306674912) ^ (c >>> 1); t[i] = c; } return t; @@ -723,14 +780,14 @@ var crc = function () { d: function () { return ~c; } }; }; -// Alder32 +// Adler32 var adler = function () { var a = 1, b = 0; return { p: function (d) { // closures have awful performance var n = a, m = b; - var l = d.length; + var l = d.length | 0; for (var i = 0; i != l;) { var e = Math.min(i + 2655, l); for (; i < e; ++i) @@ -741,14 +798,25 @@ var adler = function () { }, d: function () { a %= 65521, b %= 65521; - return (a & 255) << 24 | (a >>> 8) << 16 | (b & 255) << 8 | (b >>> 8); + return (a & 255) << 24 | (a & 0xFF00) << 8 | (b & 255) << 8 | (b >> 8); } }; }; ; // deflate with opts var dopt = function (dat, opt, pre, post, st) { - return dflt(dat, opt.level == null ? 6 : opt.level, opt.mem == null ? Math.ceil(Math.max(8, Math.min(13, Math.log(dat.length))) * 1.5) : (12 + opt.mem), pre, post, !st); + if (!st) { + st = { l: 1 }; + if (opt.dictionary) { + var dict = opt.dictionary.subarray(-32768); + var newDat = new u8(dict.length + dat.length); + newDat.set(dict); + newDat.set(dat, dict.length); + dat = newDat; + st.w = dict.length; + } + } + return dflt(dat, opt.level == null ? 6 : opt.level, opt.mem == null ? (st.l ? Math.ceil(Math.max(8, Math.min(13, Math.log(dat.length))) * 1.5) : 20) : (12 + opt.mem), pre, post, st); }; // Walmart object spread var mrg = function (a, b) { @@ -769,7 +837,7 @@ var mrg = function (a, b) { var wcln = function (fn, fnStr, td) { var dt = fn(); var st = fn.toString(); - var ks = st.slice(st.indexOf('[') + 1, st.lastIndexOf(']')).replace(/ /g, '').split(','); + var ks = st.slice(st.indexOf('[') + 1, st.lastIndexOf(']')).replace(/\s+/g, '').split(','); for (var i = 0; i < dt.length; ++i) { var v = dt[i], k = ks[i]; if (typeof v == 'function') { @@ -793,33 +861,33 @@ var wcln = function (fn, fnStr, td) { else td[k] = v; } - return [fnStr, td]; + return fnStr; }; var ch = []; // clone bufs var cbfs = function (v) { var tl = []; for (var k in v) { - if (v[k] instanceof u8 || v[k] instanceof u16 || v[k] instanceof u32) + if (v[k].buffer) { tl.push((v[k] = new v[k].constructor(v[k])).buffer); + } } return tl; }; // use a worker to execute code var wrkr = function (fns, init, id, cb) { - var _a; if (!ch[id]) { var fnStr = '', td_1 = {}, m = fns.length - 1; for (var i = 0; i < m; ++i) - _a = wcln(fns[i], fnStr, td_1), fnStr = _a[0], td_1 = _a[1]; - ch[id] = wcln(fns[m], fnStr, td_1); + fnStr = wcln(fns[i], fnStr, td_1); + ch[id] = { c: wcln(fns[m], fnStr, td_1), e: td_1 }; } - var td = mrg({}, ch[id][1]); - return wk(ch[id][0] + ';onmessage=function(e){for(var k in e.data)self[k]=e.data[k];onmessage=' + init.toString() + '}', id, td, cbfs(td), cb); + var td = mrg({}, ch[id].e); + return wk(ch[id].c + ';onmessage=function(e){for(var k in e.data)self[k]=e.data[k];onmessage=' + init.toString() + '}', id, td, cbfs(td), cb); }; // base async inflate fn -var bInflt = function () { return [u8, u16, u32, fleb, fdeb, clim, fl, fd, flrm, fdrm, rev, hMap, max, bits, bits16, shft, slc, inflt, inflateSync, pbf, gu8]; }; -var bDflt = function () { return [u8, u16, u32, fleb, fdeb, clim, revfl, revfd, flm, flt, fdm, fdt, rev, deo, et, hMap, wbits, wbits16, hTree, ln, lc, clen, wfblk, wblk, shft, slc, dflt, dopt, deflateSync, pbf]; }; +var bInflt = function () { return [u8, u16, i32, fleb, fdeb, clim, fl, fd, flrm, fdrm, rev, ec, hMap, max, bits, bits16, shft, slc, err, inflt, inflateSync, pbf, gopt]; }; +var bDflt = function () { return [u8, u16, i32, fleb, fdeb, clim, revfl, revfd, flm, flt, fdm, fdt, rev, deo, et, hMap, wbits, wbits16, hTree, ln, lc, clen, wfblk, wblk, shft, slc, dflt, dopt, deflateSync, pbf]; }; // gzip extra var gze = function () { return [gzh, gzhl, wbytes, crc, crct]; }; // gunzip extra @@ -827,11 +895,14 @@ var guze = function () { return [gzs, gzl]; }; // zlib extra var zle = function () { return [zlh, wbytes, adler]; }; // unzlib extra -var zule = function () { return [zlv]; }; +var zule = function () { return [zls]; }; // post buf var pbf = function (msg) { return postMessage(msg, [msg.buffer]); }; -// get u8 -var gu8 = function (o) { return o && o.size && new u8(o.size); }; +// get opts +var gopt = function (o) { return o && { + out: o.size && new u8(o.size), + dictionary: o.dictionary +}; }; // async helper var cbify = function (dat, opts, fns, init, id, cb) { var w = wrkr(fns, init, id, function (err, dat) { @@ -844,14 +915,28 @@ var cbify = function (dat, opts, fns, init, id, cb) { // auto stream var astrm = function (strm) { strm.ondata = function (dat, final) { return postMessage([dat, final], [dat.buffer]); }; - return function (ev) { return strm.push(ev.data[0], ev.data[1]); }; + return function (ev) { + if (ev.data.length) { + strm.push(ev.data[0], ev.data[1]); + postMessage([ev.data[0].length]); + } + else + strm.flush(); + }; }; // async stream attach -var astrmify = function (fns, strm, opts, init, id) { +var astrmify = function (fns, strm, opts, init, id, flush, ext) { var t; var w = wrkr(fns, init, id, function (err, dat) { if (err) w.terminate(), strm.ondata.call(strm, err); + else if (!Array.isArray(dat)) + ext(dat); + else if (dat.length == 1) { + strm.queuedSize -= dat[0]; + if (strm.ondrain) + strm.ondrain(dat[0]); + } else { if (dat[1]) w.terminate(); @@ -859,14 +944,19 @@ var astrmify = function (fns, strm, opts, init, id) { } }); w.postMessage(opts); + strm.queuedSize = 0; strm.push = function (d, f) { - if (t) - throw 'stream finished'; if (!strm.ondata) - throw 'no stream handler'; + err(5); + if (t) + strm.ondata(err(4, 0, 1), null, !!f); + strm.queuedSize += d.length; w.postMessage([d, t = f], [d.buffer]); }; strm.terminate = function () { w.terminate(); }; + if (flush) { + strm.flush = function () { w.postMessage([]); }; + } }; // read 2 bytes var b2 = function (d, b) { return d[b] | (d[b + 1] << 8); }; @@ -894,11 +984,11 @@ var gzh = function (c, o) { // gzip start var gzs = function (d) { if (d[0] != 31 || d[1] != 139 || d[2] != 8) - throw 'invalid gzip data'; + err(6, 'invalid gzip data'); var flg = d[3]; var st = 10; if (flg & 4) - st += d[10] | (d[11] << 8) + 2; + st += (d[10] | d[11] << 8) + 2; for (var zs = (flg >> 3 & 1) + (flg >> 4 & 1); zs > 0; zs -= !d[st++]) ; return st + (flg & 2); @@ -906,41 +996,56 @@ var gzs = function (d) { // gzip length var gzl = function (d) { var l = d.length; - return ((d[l - 4] | d[l - 3] << 8 | d[l - 2] << 16) | (d[l - 1] << 24)) >>> 0; + return (d[l - 4] | d[l - 3] << 8 | d[l - 2] << 16 | d[l - 1] << 24) >>> 0; }; // gzip header length -var gzhl = function (o) { return 10 + ((o.filename && (o.filename.length + 1)) || 0); }; +var gzhl = function (o) { return 10 + (o.filename ? o.filename.length + 1 : 0); }; // zlib header var zlh = function (c, o) { var lv = o.level, fl = lv == 0 ? 0 : lv < 6 ? 1 : lv == 9 ? 3 : 2; - c[0] = 120, c[1] = (fl << 6) | (fl ? (32 - 2 * fl) : 1); + c[0] = 120, c[1] = (fl << 6) | (o.dictionary && 32); + c[1] |= 31 - ((c[0] << 8) | c[1]) % 31; + if (o.dictionary) { + var h = adler(); + h.p(o.dictionary); + wbytes(c, 2, h.d()); + } }; -// zlib valid -var zlv = function (d) { - if ((d[0] & 15) != 8 || (d[0] >>> 4) > 7 || ((d[0] << 8 | d[1]) % 31)) - throw 'invalid zlib data'; - if (d[1] & 32) - throw 'invalid zlib data: preset dictionaries not supported'; +// zlib start +var zls = function (d, dict) { + if ((d[0] & 15) != 8 || (d[0] >> 4) > 7 || ((d[0] << 8 | d[1]) % 31)) + err(6, 'invalid zlib data'); + if ((d[1] >> 5 & 1) == +!dict) + err(6, 'invalid zlib data: ' + (d[1] & 32 ? 'need' : 'unexpected') + ' dictionary'); + return (d[1] >> 3 & 4) + 2; }; -function AsyncCmpStrm(opts, cb) { - if (!cb && typeof opts == 'function') +function StrmOpt(opts, cb) { + if (typeof opts == 'function') cb = opts, opts = {}; this.ondata = cb; return opts; } -// zlib footer: -4 to -0 is Adler32 /** * Streaming DEFLATE compression */ var Deflate = /*#__PURE__*/ (function () { function Deflate(opts, cb) { - if (!cb && typeof opts == 'function') + if (typeof opts == 'function') cb = opts, opts = {}; this.ondata = cb; this.o = opts || {}; + this.s = { l: 0, i: 32768, w: 32768, z: 32768 }; + // Buffer length must always be 0 mod 32768 for index calculations to be correct when modifying head and prev + // 98304 = 32768 (lookback) + 65536 (common chunk size) + this.b = new u8(98304); + if (this.o.dictionary) { + var dict = this.o.dictionary.subarray(-32768); + this.b.set(dict, 32768 - dict.length); + this.s.i = 32768 - dict.length; + } } Deflate.prototype.p = function (c, f) { - this.ondata(dopt(c, this.o, 0, 0, !f), f); + this.ondata(dopt(c, this.o, 0, 0, this.s), f); }; /** * Pushes a chunk to be deflated @@ -948,12 +1053,47 @@ var Deflate = /*#__PURE__*/ (function () { * @param final Whether this is the last chunk */ Deflate.prototype.push = function (chunk, final) { - if (this.d) - throw 'stream finished'; if (!this.ondata) - throw 'no stream handler'; - this.d = final; - this.p(chunk, final || false); + err(5); + if (this.s.l) + err(4); + var endLen = chunk.length + this.s.z; + if (endLen > this.b.length) { + if (endLen > 2 * this.b.length - 32768) { + var newBuf = new u8(endLen & -32768); + newBuf.set(this.b.subarray(0, this.s.z)); + this.b = newBuf; + } + var split = this.b.length - this.s.z; + this.b.set(chunk.subarray(0, split), this.s.z); + this.s.z = this.b.length; + this.p(this.b, false); + this.b.set(this.b.subarray(-32768)); + this.b.set(chunk.subarray(split), 32768); + this.s.z = chunk.length - split + 32768; + this.s.i = 32766, this.s.w = 32768; + } + else { + this.b.set(chunk, this.s.z); + this.s.z += chunk.length; + } + this.s.l = final & 1; + if (this.s.z > this.s.w + 8191 || final) { + this.p(this.b, final || false); + this.s.w = this.s.i, this.s.i -= 2; + } + }; + /** + * Flushes buffered uncompressed data. Useful to immediately retrieve the + * deflated output for small inputs. + */ + Deflate.prototype.flush = function () { + if (!this.ondata) + err(5); + if (this.s.l) + err(4); + this.p(this.b, false); + this.s.w = this.s.i, this.s.i -= 2; }; return Deflate; }()); @@ -966,10 +1106,10 @@ var AsyncDeflate = /*#__PURE__*/ (function () { astrmify([ bDflt, function () { return [astrm, Deflate]; } - ], this, AsyncCmpStrm.call(this, opts, cb), function (ev) { + ], this, StrmOpt.call(this, opts, cb), function (ev) { var strm = new Deflate(ev.data); onmessage = astrm(strm); - }, 6); + }, 6, 1); } return AsyncDeflate; }()); @@ -978,7 +1118,7 @@ export function deflate(data, opts, cb) { if (!cb) cb = opts, opts = {}; if (typeof cb != 'function') - throw 'no callback'; + err(7); return cbify(data, opts, [ bDflt, ], function (ev) { return pbf(deflateSync(ev.data[0], ev.data[1])); }, 0, cb); @@ -996,28 +1136,34 @@ export function deflateSync(data, opts) { * Streaming DEFLATE decompression */ var Inflate = /*#__PURE__*/ (function () { - /** - * Creates an inflation stream - * @param cb The callback to call whenever data is inflated - */ - function Inflate(cb) { - this.s = {}; - this.p = new u8(0); + function Inflate(opts, cb) { + // no StrmOpt here to avoid adding to workerizer + if (typeof opts == 'function') + cb = opts, opts = {}; this.ondata = cb; + var dict = opts && opts.dictionary && opts.dictionary.subarray(-32768); + this.s = { i: 0, b: dict ? dict.length : 0 }; + this.o = new u8(32768); + this.p = new u8(0); + if (dict) + this.o.set(dict); } Inflate.prototype.e = function (c) { - if (this.d) - throw 'stream finished'; if (!this.ondata) - throw 'no stream handler'; - var l = this.p.length; - var n = new u8(l + c.length); - n.set(this.p), n.set(c, l), this.p = n; + err(5); + if (this.d) + err(4); + if (!this.p.length) + this.p = c; + else if (c.length) { + var n = new u8(this.p.length + c.length); + n.set(this.p), n.set(c, this.p.length), this.p = n; + } }; Inflate.prototype.c = function (final) { - this.d = this.s.i = final || false; + this.s.i = +(this.d = final || false); var bts = this.s.b; - var dt = inflt(this.p, this.o, this.s); + var dt = inflt(this.p, this.s, this.o); this.ondata(slc(dt, bts, this.s.b), this.d); this.o = slc(dt, this.s.b - 32768), this.s.b = this.o.length; this.p = slc(this.p, (this.s.p / 8) | 0), this.s.p &= 7; @@ -1037,19 +1183,14 @@ export { Inflate }; * Asynchronous streaming DEFLATE decompression */ var AsyncInflate = /*#__PURE__*/ (function () { - /** - * Creates an asynchronous inflation stream - * @param cb The callback to call whenever data is deflated - */ - function AsyncInflate(cb) { - this.ondata = cb; + function AsyncInflate(opts, cb) { astrmify([ bInflt, function () { return [astrm, Inflate]; } - ], this, 0, function () { - var strm = new Inflate(); + ], this, StrmOpt.call(this, opts, cb), function (ev) { + var strm = new Inflate(ev.data); onmessage = astrm(strm); - }, 7); + }, 7, 0); } return AsyncInflate; }()); @@ -1058,19 +1199,19 @@ export function inflate(data, opts, cb) { if (!cb) cb = opts, opts = {}; if (typeof cb != 'function') - throw 'no callback'; + err(7); return cbify(data, opts, [ bInflt - ], function (ev) { return pbf(inflateSync(ev.data[0], gu8(ev.data[1]))); }, 1, cb); + ], function (ev) { return pbf(inflateSync(ev.data[0], gopt(ev.data[1]))); }, 1, cb); } /** * Expands DEFLATE data with no wrapper * @param data The data to decompress - * @param out Where to write the data. Saves memory if you know the decompressed size and provide an output buffer of that length. + * @param opts The decompression options * @returns The decompressed version of the data */ -export function inflateSync(data, out) { - return inflt(data, out); +export function inflateSync(data, opts) { + return inflt(data, { i: 2 }, opts && opts.out, opts && opts.dictionary); } // before you yell at me for not just using extends, my reason is that TS inheritance is hard to workerize. /** @@ -1089,18 +1230,25 @@ var Gzip = /*#__PURE__*/ (function () { * @param final Whether this is the last chunk */ Gzip.prototype.push = function (chunk, final) { + this.c.p(chunk); + this.l += chunk.length; Deflate.prototype.push.call(this, chunk, final); }; Gzip.prototype.p = function (c, f) { - this.c.p(c); - this.l += c.length; - var raw = dopt(c, this.o, this.v && gzhl(this.o), f && 8, !f); + var raw = dopt(c, this.o, this.v && gzhl(this.o), f && 8, this.s); if (this.v) gzh(raw, this.o), this.v = 0; if (f) wbytes(raw, raw.length - 8, this.c.d()), wbytes(raw, raw.length - 4, this.l); this.ondata(raw, f); }; + /** + * Flushes buffered uncompressed data. Useful to immediately retrieve the + * GZIPped output for small inputs. + */ + Gzip.prototype.flush = function () { + Deflate.prototype.flush.call(this); + }; return Gzip; }()); export { Gzip }; @@ -1113,10 +1261,10 @@ var AsyncGzip = /*#__PURE__*/ (function () { bDflt, gze, function () { return [astrm, Deflate, Gzip]; } - ], this, AsyncCmpStrm.call(this, opts, cb), function (ev) { + ], this, StrmOpt.call(this, opts, cb), function (ev) { var strm = new Gzip(ev.data); onmessage = astrm(strm); - }, 8); + }, 8, 1); } return AsyncGzip; }()); @@ -1125,7 +1273,7 @@ export function gzip(data, opts, cb) { if (!cb) cb = opts, opts = {}; if (typeof cb != 'function') - throw 'no callback'; + err(7); return cbify(data, opts, [ bDflt, gze, @@ -1147,16 +1295,13 @@ export function gzipSync(data, opts) { return gzh(d, opts), wbytes(d, s - 8, c.d()), wbytes(d, s - 4, l), d; } /** - * Streaming GZIP decompression + * Streaming single or multi-member GZIP decompression */ var Gunzip = /*#__PURE__*/ (function () { - /** - * Creates a GUNZIP stream - * @param cb The callback to call whenever data is inflated - */ - function Gunzip(cb) { + function Gunzip(opts, cb) { this.v = 1; - Inflate.call(this, cb); + this.r = 0; + Inflate.call(this, opts, cb); } /** * Pushes a chunk to be GUNZIPped @@ -1165,42 +1310,48 @@ var Gunzip = /*#__PURE__*/ (function () { */ Gunzip.prototype.push = function (chunk, final) { Inflate.prototype.e.call(this, chunk); + this.r += chunk.length; if (this.v) { - var s = this.p.length > 3 ? gzs(this.p) : 4; - if (s >= this.p.length && !final) - return; - this.p = this.p.subarray(s), this.v = 0; - } - if (final) { - if (this.p.length < 8) - throw 'invalid gzip stream'; - this.p = this.p.subarray(0, -8); + var p = this.p.subarray(this.v - 1); + var s = p.length > 3 ? gzs(p) : 4; + if (s > p.length) { + if (!final) + return; + } + else if (this.v > 1 && this.onmember) { + this.onmember(this.r - p.length); + } + this.p = p.subarray(s), this.v = 0; } // necessary to prevent TS from using the closure value // This allows for workerization to function correctly Inflate.prototype.c.call(this, final); + // process concatenated GZIP + if (this.s.f && !this.s.l && !final) { + this.v = shft(this.s.p) + 9; + this.s = { i: 0 }; + this.o = new u8(0); + this.push(new u8(0), final); + } }; return Gunzip; }()); export { Gunzip }; /** - * Asynchronous streaming GZIP decompression + * Asynchronous streaming single or multi-member GZIP decompression */ var AsyncGunzip = /*#__PURE__*/ (function () { - /** - * Creates an asynchronous GUNZIP stream - * @param cb The callback to call whenever data is deflated - */ - function AsyncGunzip(cb) { - this.ondata = cb; + function AsyncGunzip(opts, cb) { + var _this = this; astrmify([ bInflt, guze, function () { return [astrm, Inflate, Gunzip]; } - ], this, 0, function () { - var strm = new Gunzip(); + ], this, StrmOpt.call(this, opts, cb), function (ev) { + var strm = new Gunzip(ev.data); + strm.onmember = function (offset) { return postMessage(offset); }; onmessage = astrm(strm); - }, 9); + }, 9, 0, function (offset) { return _this.onmember && _this.onmember(offset); }); } return AsyncGunzip; }()); @@ -1209,21 +1360,24 @@ export function gunzip(data, opts, cb) { if (!cb) cb = opts, opts = {}; if (typeof cb != 'function') - throw 'no callback'; + err(7); return cbify(data, opts, [ bInflt, guze, function () { return [gunzipSync]; } - ], function (ev) { return pbf(gunzipSync(ev.data[0])); }, 3, cb); + ], function (ev) { return pbf(gunzipSync(ev.data[0], ev.data[1])); }, 3, cb); } /** * Expands GZIP data * @param data The data to decompress - * @param out Where to write the data. GZIP already encodes the output size, so providing this doesn't save memory. + * @param opts The decompression options * @returns The decompressed version of the data */ -export function gunzipSync(data, out) { - return inflt(data.subarray(gzs(data), -8), out || new u8(gzl(data))); +export function gunzipSync(data, opts) { + var st = gzs(data); + if (st + 8 > data.length) + err(6, 'invalid gzip data'); + return inflt(data.subarray(st, -8), { i: 2 }, opts && opts.out || new u8(gzl(data)), opts && opts.dictionary); } /** * Streaming Zlib compression @@ -1240,17 +1394,24 @@ var Zlib = /*#__PURE__*/ (function () { * @param final Whether this is the last chunk */ Zlib.prototype.push = function (chunk, final) { + this.c.p(chunk); Deflate.prototype.push.call(this, chunk, final); }; Zlib.prototype.p = function (c, f) { - this.c.p(c); - var raw = dopt(c, this.o, this.v && 2, f && 4, !f); + var raw = dopt(c, this.o, this.v && (this.o.dictionary ? 6 : 2), f && 4, this.s); if (this.v) zlh(raw, this.o), this.v = 0; if (f) wbytes(raw, raw.length - 4, this.c.d()); this.ondata(raw, f); }; + /** + * Flushes buffered uncompressed data. Useful to immediately retrieve the + * zlibbed output for small inputs. + */ + Zlib.prototype.flush = function () { + Deflate.prototype.flush.call(this); + }; return Zlib; }()); export { Zlib }; @@ -1263,10 +1424,10 @@ var AsyncZlib = /*#__PURE__*/ (function () { bDflt, zle, function () { return [astrm, Deflate, Zlib]; } - ], this, AsyncCmpStrm.call(this, opts, cb), function (ev) { + ], this, StrmOpt.call(this, opts, cb), function (ev) { var strm = new Zlib(ev.data); onmessage = astrm(strm); - }, 10); + }, 10, 1); } return AsyncZlib; }()); @@ -1275,7 +1436,7 @@ export function zlib(data, opts, cb) { if (!cb) cb = opts, opts = {}; if (typeof cb != 'function') - throw 'no callback'; + err(7); return cbify(data, opts, [ bDflt, zle, @@ -1293,20 +1454,16 @@ export function zlibSync(data, opts) { opts = {}; var a = adler(); a.p(data); - var d = dopt(data, opts, 2, 4); + var d = dopt(data, opts, opts.dictionary ? 6 : 2, 4); return zlh(d, opts), wbytes(d, d.length - 4, a.d()), d; } /** * Streaming Zlib decompression */ var Unzlib = /*#__PURE__*/ (function () { - /** - * Creates a Zlib decompression stream - * @param cb The callback to call whenever data is inflated - */ - function Unzlib(cb) { - this.v = 1; - Inflate.call(this, cb); + function Unzlib(opts, cb) { + Inflate.call(this, opts, cb); + this.v = opts && opts.dictionary ? 2 : 1; } /** * Pushes a chunk to be unzlibbed @@ -1316,13 +1473,13 @@ var Unzlib = /*#__PURE__*/ (function () { Unzlib.prototype.push = function (chunk, final) { Inflate.prototype.e.call(this, chunk); if (this.v) { - if (this.p.length < 2 && !final) + if (this.p.length < 6 && !final) return; - this.p = this.p.subarray(2), this.v = 0; + this.p = this.p.subarray(zls(this.p, this.v - 1)), this.v = 0; } if (final) { if (this.p.length < 4) - throw 'invalid zlib stream'; + err(6, 'invalid zlib data'); this.p = this.p.subarray(0, -4); } // necessary to prevent TS from using the closure value @@ -1336,20 +1493,15 @@ export { Unzlib }; * Asynchronous streaming Zlib decompression */ var AsyncUnzlib = /*#__PURE__*/ (function () { - /** - * Creates an asynchronous Zlib decompression stream - * @param cb The callback to call whenever data is deflated - */ - function AsyncUnzlib(cb) { - this.ondata = cb; + function AsyncUnzlib(opts, cb) { astrmify([ bInflt, zule, function () { return [astrm, Inflate, Unzlib]; } - ], this, 0, function () { - var strm = new Unzlib(); + ], this, StrmOpt.call(this, opts, cb), function (ev) { + var strm = new Unzlib(ev.data); onmessage = astrm(strm); - }, 11); + }, 11, 0); } return AsyncUnzlib; }()); @@ -1358,40 +1510,43 @@ export function unzlib(data, opts, cb) { if (!cb) cb = opts, opts = {}; if (typeof cb != 'function') - throw 'no callback'; + err(7); return cbify(data, opts, [ bInflt, zule, function () { return [unzlibSync]; } - ], function (ev) { return pbf(unzlibSync(ev.data[0], gu8(ev.data[1]))); }, 5, cb); + ], function (ev) { return pbf(unzlibSync(ev.data[0], gopt(ev.data[1]))); }, 5, cb); } /** * Expands Zlib data * @param data The data to decompress - * @param out Where to write the data. Saves memory if you know the decompressed size and provide an output buffer of that length. + * @param opts The decompression options * @returns The decompressed version of the data */ -export function unzlibSync(data, out) { - return inflt((zlv(data), data.subarray(2, -4)), out); +export function unzlibSync(data, opts) { + return inflt(data.subarray(zls(data, opts && opts.dictionary), -4), { i: 2 }, opts && opts.out, opts && opts.dictionary); } // Default algorithm for compression (used because having a known output size allows faster decompression) export { gzip as compress, AsyncGzip as AsyncCompress }; -// Default algorithm for compression (used because having a known output size allows faster decompression) export { gzipSync as compressSync, Gzip as Compress }; /** * Streaming GZIP, Zlib, or raw DEFLATE decompression */ var Decompress = /*#__PURE__*/ (function () { - /** - * Creates a decompression stream - * @param cb The callback to call whenever data is decompressed - */ - function Decompress(cb) { + function Decompress(opts, cb) { + this.o = StrmOpt.call(this, opts, cb) || {}; this.G = Gunzip; this.I = Inflate; this.Z = Unzlib; - this.ondata = cb; } + // init substream + // overriden by AsyncDecompress + Decompress.prototype.i = function () { + var _this = this; + this.s.ondata = function (dat, final) { + _this.ondata(dat, final); + }; + }; /** * Pushes a chunk to be decompressed * @param chunk The chunk to push @@ -1399,7 +1554,7 @@ var Decompress = /*#__PURE__*/ (function () { */ Decompress.prototype.push = function (chunk, final) { if (!this.ondata) - throw 'no stream handler'; + err(5); if (!this.s) { if (this.p && this.p.length) { var n = new u8(this.p.length + chunk.length); @@ -1408,13 +1563,12 @@ var Decompress = /*#__PURE__*/ (function () { else this.p = chunk; if (this.p.length > 2) { - var _this_1 = this; - var cb = function () { _this_1.ondata.apply(_this_1, arguments); }; this.s = (this.p[0] == 31 && this.p[1] == 139 && this.p[2] == 8) - ? new this.G(cb) + ? new this.G(this.o) : ((this.p[0] & 15) != 8 || (this.p[0] >> 4) > 7 || ((this.p[0] << 8 | this.p[1]) % 31)) - ? new this.I(cb) - : new this.Z(cb); + ? new this.I(this.o) + : new this.Z(this.o); + this.i(); this.s.push(this.p, final); this.p = null; } @@ -1429,22 +1583,31 @@ export { Decompress }; * Asynchronous streaming GZIP, Zlib, or raw DEFLATE decompression */ var AsyncDecompress = /*#__PURE__*/ (function () { - /** - * Creates an asynchronous decompression stream - * @param cb The callback to call whenever data is decompressed - */ - function AsyncDecompress(cb) { + function AsyncDecompress(opts, cb) { + Decompress.call(this, opts, cb); + this.queuedSize = 0; this.G = AsyncGunzip; this.I = AsyncInflate; this.Z = AsyncUnzlib; - this.ondata = cb; } + AsyncDecompress.prototype.i = function () { + var _this = this; + this.s.ondata = function (err, dat, final) { + _this.ondata(err, dat, final); + }; + this.s.ondrain = function (size) { + _this.queuedSize -= size; + if (_this.ondrain) + _this.ondrain(size); + }; + }; /** * Pushes a chunk to be decompressed * @param chunk The chunk to push * @param final Whether this is the last chunk */ AsyncDecompress.prototype.push = function (chunk, final) { + this.queuedSize += chunk.length; Decompress.prototype.push.call(this, chunk, final); }; return AsyncDecompress; @@ -1454,7 +1617,7 @@ export function decompress(data, opts, cb) { if (!cb) cb = opts, opts = {}; if (typeof cb != 'function') - throw 'no callback'; + err(7); return (data[0] == 31 && data[1] == 139 && data[2] == 8) ? gunzip(data, opts, cb) : ((data[0] & 15) != 8 || (data[0] >> 4) > 7 || ((data[0] << 8 | data[1]) % 31)) @@ -1464,26 +1627,28 @@ export function decompress(data, opts, cb) { /** * Expands compressed GZIP, Zlib, or raw DEFLATE data, automatically detecting the format * @param data The data to decompress - * @param out Where to write the data. Saves memory if you know the decompressed size and provide an output buffer of that length. + * @param opts The decompression options * @returns The decompressed version of the data */ -export function decompressSync(data, out) { +export function decompressSync(data, opts) { return (data[0] == 31 && data[1] == 139 && data[2] == 8) - ? gunzipSync(data, out) + ? gunzipSync(data, opts) : ((data[0] & 15) != 8 || (data[0] >> 4) > 7 || ((data[0] << 8 | data[1]) % 31)) - ? inflateSync(data, out) - : unzlibSync(data, out); + ? inflateSync(data, opts) + : unzlibSync(data, opts); } // flatten a directory structure var fltn = function (d, p, t, o) { for (var k in d) { - var val = d[k], n = p + k; + var val = d[k], n = p + k, op = o; + if (Array.isArray(val)) + op = mrg(o, val[1]), val = val[0]; if (val instanceof u8) - t[n] = [val, o]; - else if (Array.isArray(val)) - t[n] = [val[0], mrg(o, val[1])]; - else - fltn(val, n + '/', t, o); + t[n] = [val, op]; + else { + t[n += '/'] = [new u8(0), op]; + fltn(val, n, t, o); + } } }; // text encoder @@ -1503,7 +1668,7 @@ var dutf8 = function (d) { var c = d[i++]; var eb = (c > 127) + (c > 223) + (c > 239); if (i + eb > d.length) - return [r, slc(d, i - 1)]; + return { s: r, r: slc(d, i - 1) }; if (!eb) r += String.fromCharCode(c); else if (eb == 3) { @@ -1538,31 +1703,31 @@ var DecodeUTF8 = /*#__PURE__*/ (function () { */ DecodeUTF8.prototype.push = function (chunk, final) { if (!this.ondata) - throw 'no callback'; + err(5); final = !!final; if (this.t) { this.ondata(this.t.decode(chunk, { stream: true }), final); if (final) { if (this.t.decode().length) - throw 'invalid utf-8 data'; + err(8); this.t = null; } return; } if (!this.p) - throw 'stream finished'; + err(4); var dat = new u8(this.p.length + chunk.length); dat.set(this.p); dat.set(chunk, this.p.length); - var _a = dutf8(dat), ch = _a[0], np = _a[1]; + var _a = dutf8(dat), s = _a.s, r = _a.r; if (final) { - if (np.length) - throw 'invalid utf-8 data'; + if (r.length) + err(8); this.p = null; } else - this.p = np; - this.ondata(ch, final); + this.p = r; + this.ondata(s, final); }; return DecodeUTF8; }()); @@ -1585,9 +1750,9 @@ var EncodeUTF8 = /*#__PURE__*/ (function () { */ EncodeUTF8.prototype.push = function (chunk, final) { if (!this.ondata) - throw 'no callback'; + err(5); if (this.d) - throw 'stream finished'; + err(4); this.ondata(strToU8(chunk), this.d = final || false); }; return EncodeUTF8; @@ -1646,13 +1811,14 @@ export function strFromU8(dat, latin1) { r += String.fromCharCode.apply(null, dat.subarray(i, i + 16384)); return r; } - else if (td) + else if (td) { return td.decode(dat); + } else { - var _a = dutf8(dat), out = _a[0], ext = _a[1]; - if (ext.length) - throw 'invalid utf-8 data'; - return out; + var _a = dutf8(dat), s = _a.s, r = _a.r; + if (r.length) + err(8); + return s; } } ; @@ -1679,7 +1845,7 @@ var exfl = function (ex) { for (var k in ex) { var l = ex[k].length; if (l > 65535) - throw 'extra field too long'; + err(9); le += l + 4; } } @@ -1693,15 +1859,15 @@ var wzh = function (d, b, f, fn, u, c, ce, co) { if (ce != null) d[b++] = 20, d[b++] = f.os; d[b] = 20, b += 2; // spec compliance? what's that? - d[b++] = (f.flag << 1) | (c == null && 8), d[b++] = u && 8; + d[b++] = (f.flag << 1) | (c < 0 && 8), d[b++] = u && 8; d[b++] = f.compression & 255, d[b++] = f.compression >> 8; var dt = new Date(f.mtime == null ? Date.now() : f.mtime), y = dt.getFullYear() - 1980; if (y < 0 || y > 119) - throw 'date not in range 1980-2099'; - wbytes(d, b, (y << 25) | ((dt.getMonth() + 1) << 21) | (dt.getDate() << 16) | (dt.getHours() << 11) | (dt.getMinutes() << 5) | (dt.getSeconds() >>> 1)), b += 4; - if (c != null) { + err(10); + wbytes(d, b, (y << 25) | ((dt.getMonth() + 1) << 21) | (dt.getDate() << 16) | (dt.getHours() << 11) | (dt.getMinutes() << 5) | (dt.getSeconds() >> 1)), b += 4; + if (c != -1) { wbytes(d, b, f.crc); - wbytes(d, b + 4, c); + wbytes(d, b + 4, c < 0 ? -c - 2 : c); wbytes(d, b + 8, f.size); } wbytes(d, b + 12, fl); @@ -1767,7 +1933,7 @@ var ZipPassThrough = /*#__PURE__*/ (function () { */ ZipPassThrough.prototype.push = function (chunk, final) { if (!this.ondata) - throw 'no callback - add to ZIP archive before pushing'; + err(5); this.c.p(chunk); this.size += chunk.length; if (final) @@ -1789,12 +1955,12 @@ var ZipDeflate = /*#__PURE__*/ (function () { * @param opts The compression options */ function ZipDeflate(filename, opts) { - var _this_1 = this; + var _this = this; if (!opts) opts = {}; ZipPassThrough.call(this, filename); this.d = new Deflate(opts, function (dat, final) { - _this_1.ondata(null, dat, final); + _this.ondata(null, dat, final); }); this.compression = 8; this.flag = dbf(opts.level); @@ -1823,17 +1989,17 @@ export { ZipDeflate }; */ var AsyncZipDeflate = /*#__PURE__*/ (function () { /** - * Creates a DEFLATE stream that can be added to ZIP archives + * Creates an asynchronous DEFLATE stream that can be added to ZIP archives * @param filename The filename to associate with this data stream * @param opts The compression options */ function AsyncZipDeflate(filename, opts) { - var _this_1 = this; + var _this = this; if (!opts) opts = {}; ZipPassThrough.call(this, filename); this.d = new AsyncDeflate(opts, function (err, dat, final) { - _this_1.ondata(err, dat, final); + _this.ondata(err, dat, final); }); this.compression = 8; this.flag = dbf(opts.level); @@ -1873,74 +2039,79 @@ var Zip = /*#__PURE__*/ (function () { * @param file The file stream to add */ Zip.prototype.add = function (file) { - var _this_1 = this; + var _this = this; + if (!this.ondata) + err(5); + // finishing or finished if (this.d & 2) - throw 'stream finished'; - var f = strToU8(file.filename), fl = f.length; - var com = file.comment, o = com && strToU8(com); - var u = fl != file.filename.length || (o && (com.length != o.length)); - var hl = fl + exfl(file.extra) + 30; - if (fl > 65535) - throw 'filename too long'; - var header = new u8(hl); - wzh(header, 0, file, f, u); - var chks = [header]; - var pAll = function () { - for (var _i = 0, chks_1 = chks; _i < chks_1.length; _i++) { - var chk = chks_1[_i]; - _this_1.ondata(null, chk, false); - } - chks = []; - }; - var tr = this.d; - this.d = 0; - var ind = this.u.length; - var uf = mrg(file, { - f: f, - u: u, - o: o, - t: function () { - if (file.terminate) - file.terminate(); - }, - r: function () { - pAll(); - if (tr) { - var nxt = _this_1.u[ind + 1]; - if (nxt) - nxt.r(); - else - _this_1.d = 1; + this.ondata(err(4 + (this.d & 1) * 8, 0, 1), null, false); + else { + var f = strToU8(file.filename), fl_1 = f.length; + var com = file.comment, o = com && strToU8(com); + var u = fl_1 != file.filename.length || (o && (com.length != o.length)); + var hl_1 = fl_1 + exfl(file.extra) + 30; + if (fl_1 > 65535) + this.ondata(err(11, 0, 1), null, false); + var header = new u8(hl_1); + wzh(header, 0, file, f, u, -1); + var chks_1 = [header]; + var pAll_1 = function () { + for (var _i = 0, chks_2 = chks_1; _i < chks_2.length; _i++) { + var chk = chks_2[_i]; + _this.ondata(null, chk, false); } - tr = 1; - } - }); - var cl = 0; - file.ondata = function (err, dat, final) { - if (err) { - _this_1.ondata(err, dat, final); - _this_1.terminate(); - } - else { - cl += dat.length; - chks.push(dat); - if (final) { - var dd = new u8(16); - wbytes(dd, 0, 0x8074B50); - wbytes(dd, 4, file.crc); - wbytes(dd, 8, cl); - wbytes(dd, 12, file.size); - chks.push(dd); - uf.c = cl, uf.b = hl + cl + 16, uf.crc = file.crc, uf.size = file.size; - if (tr) - uf.r(); - tr = 1; + chks_1 = []; + }; + var tr_1 = this.d; + this.d = 0; + var ind_1 = this.u.length; + var uf_1 = mrg(file, { + f: f, + u: u, + o: o, + t: function () { + if (file.terminate) + file.terminate(); + }, + r: function () { + pAll_1(); + if (tr_1) { + var nxt = _this.u[ind_1 + 1]; + if (nxt) + nxt.r(); + else + _this.d = 1; + } + tr_1 = 1; } - else if (tr) - pAll(); - } - }; - this.u.push(uf); + }); + var cl_1 = 0; + file.ondata = function (err, dat, final) { + if (err) { + _this.ondata(err, dat, final); + _this.terminate(); + } + else { + cl_1 += dat.length; + chks_1.push(dat); + if (final) { + var dd = new u8(16); + wbytes(dd, 0, 0x8074B50); + wbytes(dd, 4, file.crc); + wbytes(dd, 8, cl_1); + wbytes(dd, 12, file.size); + chks_1.push(dd); + uf_1.c = cl_1, uf_1.b = hl_1 + cl_1 + 16, uf_1.crc = file.crc, uf_1.size = file.size; + if (tr_1) + uf_1.r(); + tr_1 = 1; + } + else if (tr_1) + pAll_1(); + } + }; + this.u.push(uf_1); + } }; /** * Ends the process of adding files and prepares to emit the final chunks. @@ -1948,21 +2119,20 @@ var Zip = /*#__PURE__*/ (function () { * ZIP file to work properly. */ Zip.prototype.end = function () { - var _this_1 = this; + var _this = this; if (this.d & 2) { - if (this.d & 1) - throw 'stream finishing'; - throw 'stream finished'; + this.ondata(err(4 + (this.d & 1) * 8, 0, 1), null, true); + return; } if (this.d) this.e(); else this.u.push({ r: function () { - if (!(_this_1.d & 1)) + if (!(_this.d & 1)) return; - _this_1.u.splice(-1, 1); - _this_1.e(); + _this.u.splice(-1, 1); + _this.e(); }, t: function () { } }); @@ -1977,7 +2147,7 @@ var Zip = /*#__PURE__*/ (function () { var out = new u8(tl + 22); for (var _b = 0, _c = this.u; _b < _c.length; _b++) { var f = _c[_b]; - wzh(out, bt, f, f.f, f.u, f.c, l, f.o); + wzh(out, bt, f, f.f, f.u, -f.c - 2, l, f.o); bt += 46 + f.f.length + exfl(f.extra) + (f.o ? f.o.length : 0), l += f.b; } wzf(out, bt, this.u.length, tl, l); @@ -2002,7 +2172,7 @@ export function zip(data, opts, cb) { if (!cb) cb = opts, opts = {}; if (typeof cb != 'function') - throw 'no callback'; + err(7); var r = {}; fltn(data, '', r, opts); var k = Object.keys(r); @@ -2013,6 +2183,10 @@ export function zip(data, opts, cb) { for (var i = 0; i < term.length; ++i) term[i](); }; + var cbd = function (a, b) { + mt(function () { cb(a, b); }); + }; + mt(function () { cbd = cb; }); var cbf = function () { var out = new u8(tot + 22), oe = o, cdl = tot - o; tot = 0; @@ -2027,11 +2201,11 @@ export function zip(data, opts, cb) { wzh(out, o, f, f.f, f.u, l, tot, f.m), o += 16 + badd + (f.m ? f.m.length : 0), tot = loc + l; } catch (e) { - return cb(e, null); + return cbd(e, null); } } wzf(out, o, files.length, cdl, oe); - cb(null, out); + cbd(null, out); }; if (!lft) cbf(); @@ -2047,7 +2221,7 @@ export function zip(data, opts, cb) { var cbl = function (e, d) { if (e) { tAll(); - cb(e, null); + cbd(e, null); } else { var l = d.length; @@ -2067,7 +2241,7 @@ export function zip(data, opts, cb) { } }; if (s > 65535) - cbl('filename too long', null); + cbl(err(11, 0, 1), null); if (!compression) cbl(null, file); else if (size < 160000) { @@ -2109,7 +2283,7 @@ export function zipSync(data, opts) { var com = p.comment, m = com && strToU8(com), ms = m && m.length; var exl = exfl(p.extra); if (s > 65535) - throw 'filename too long'; + err(11); var d = compression ? deflateSync(file, p) : file, l = d.length; var c = crc(); c.p(file); @@ -2159,9 +2333,9 @@ var UnzipInflate = /*#__PURE__*/ (function () { * Creates a DEFLATE decompression that can be used in ZIP archives */ function UnzipInflate() { - var _this_1 = this; + var _this = this; this.i = new Inflate(function (dat, final) { - _this_1.ondata(null, dat, final); + _this.ondata(null, dat, final); }); } UnzipInflate.prototype.push = function (data, final) { @@ -2169,7 +2343,7 @@ var UnzipInflate = /*#__PURE__*/ (function () { this.i.push(data, final); } catch (e) { - this.ondata(e, data, final); + this.ondata(e, null, final); } }; UnzipInflate.compression = 8; @@ -2184,15 +2358,15 @@ var AsyncUnzipInflate = /*#__PURE__*/ (function () { * Creates a DEFLATE decompression that can be used in ZIP archives */ function AsyncUnzipInflate(_, sz) { - var _this_1 = this; + var _this = this; if (sz < 320000) { this.i = new Inflate(function (dat, final) { - _this_1.ondata(null, dat, final); + _this.ondata(null, dat, final); }); } else { this.i = new AsyncInflate(function (err, dat, final) { - _this_1.ondata(err, dat, final); + _this.ondata(err, dat, final); }); this.terminate = this.i.terminate; } @@ -2228,11 +2402,11 @@ var Unzip = /*#__PURE__*/ (function () { * @param final Whether this is the last chunk */ Unzip.prototype.push = function (chunk, final) { - var _this_1 = this; + var _this = this; if (!this.onfile) - throw 'no callback'; + err(5); if (!this.p) - throw 'stream finished'; + err(4); if (this.c > 0) { var len = Math.min(this.c, chunk.length); var toAdd = chunk.subarray(0, len); @@ -2265,8 +2439,8 @@ var Unzip = /*#__PURE__*/ (function () { this_1.c = 0; var bf = b2(buf, i + 6), cmp_1 = b2(buf, i + 8), u = bf & 2048, dd = bf & 8, fnl = b2(buf, i + 26), es = b2(buf, i + 28); if (l > i + 30 + fnl + es) { - var chks_2 = []; - this_1.k.unshift(chks_2); + var chks_3 = []; + this_1.k.unshift(chks_3); f = 2; var sc_1 = b4(buf, i + 18), su_1 = b4(buf, i + 22); var fn_1 = strFromU8(buf.subarray(i + 30, i += 30 + fnl), !u); @@ -2283,21 +2457,21 @@ var Unzip = /*#__PURE__*/ (function () { compression: cmp_1, start: function () { if (!file_1.ondata) - throw 'no callback'; + err(5); if (!sc_1) file_1.ondata(null, et, true); else { - var ctr = _this_1.o[cmp_1]; + var ctr = _this.o[cmp_1]; if (!ctr) - throw 'unknown compression type ' + cmp_1; + file_1.ondata(err(14, 'unknown compression type ' + cmp_1, 1), null, false); d_1 = sc_1 < 0 ? new ctr(fn_1) : new ctr(fn_1, sc_1, su_1); d_1.ondata = function (err, dat, final) { file_1.ondata(err, dat, final); }; - for (var _i = 0, chks_3 = chks_2; _i < chks_3.length; _i++) { - var dat = chks_3[_i]; + for (var _i = 0, chks_4 = chks_3; _i < chks_4.length; _i++) { + var dat = chks_4[_i]; d_1.push(dat, false); } - if (_this_1.k[0] == chks_2 && _this_1.c) - _this_1.d = d_1; + if (_this.k[0] == chks_3 && _this.c) + _this.d = d_1; else d_1.push(et, true); } @@ -2344,7 +2518,7 @@ var Unzip = /*#__PURE__*/ (function () { } if (final) { if (this.c) - throw 'invalid zip file'; + err(13); this.p = null; } }; @@ -2359,116 +2533,140 @@ var Unzip = /*#__PURE__*/ (function () { return Unzip; }()); export { Unzip }; -/** - * Asynchronously decompresses a ZIP archive - * @param data The raw compressed ZIP file - * @param cb The callback to call with the decompressed files - * @returns A function that can be used to immediately terminate the unzipping - */ -export function unzip(data, cb) { +var mt = typeof queueMicrotask == 'function' ? queueMicrotask : typeof setTimeout == 'function' ? setTimeout : function (fn) { fn(); }; +export function unzip(data, opts, cb) { + if (!cb) + cb = opts, opts = {}; if (typeof cb != 'function') - throw 'no callback'; + err(7); var term = []; var tAll = function () { for (var i = 0; i < term.length; ++i) term[i](); }; var files = {}; + var cbd = function (a, b) { + mt(function () { cb(a, b); }); + }; + mt(function () { cbd = cb; }); var e = data.length - 22; for (; b4(data, e) != 0x6054B50; --e) { if (!e || data.length - e > 65558) { - cb('invalid zip file', null); - return; + cbd(err(13, 0, 1), null); + return tAll; } } ; var lft = b2(data, e + 8); - if (!lft) - cb(null, {}); - var c = lft; - var o = b4(data, e + 16); - var z = o == 4294967295; - if (z) { - e = b4(data, e - 12); - if (b4(data, e) != 0x6064B50) { - cb('invalid zip file', null); - return; - } - c = lft = b4(data, e + 32); - o = b4(data, e + 48); - } - var _loop_3 = function (i) { - var _a = zh(data, o, z), c_1 = _a[0], sc = _a[1], su = _a[2], fn = _a[3], no = _a[4], off = _a[5], b = slzh(data, off); - o = no; - var cbl = function (e, d) { - if (e) { - tAll(); - cb(e, null); + if (lft) { + var c = lft; + var o = b4(data, e + 16); + var z = o == 4294967295 || c == 65535; + if (z) { + var ze = b4(data, e - 12); + z = b4(data, ze) == 0x6064B50; + if (z) { + c = lft = b4(data, ze + 32); + o = b4(data, ze + 48); } - else { - files[fn] = d; - if (!--lft) - cb(null, files); - } - }; - if (!c_1) - cbl(null, slc(data, b, b + sc)); - else if (c_1 == 8) { - var infl = data.subarray(b, b + sc); - if (sc < 320000) { - try { - cbl(null, inflateSync(infl, new u8(su))); + } + var fltr = opts && opts.filter; + var _loop_3 = function (i) { + var _a = zh(data, o, z), c_1 = _a[0], sc = _a[1], su = _a[2], fn = _a[3], no = _a[4], off = _a[5], b = slzh(data, off); + o = no; + var cbl = function (e, d) { + if (e) { + tAll(); + cbd(e, null); } - catch (e) { - cbl(e, null); + else { + if (d) + files[fn] = d; + if (!--lft) + cbd(null, files); } + }; + if (!fltr || fltr({ + name: fn, + size: sc, + originalSize: su, + compression: c_1 + })) { + if (!c_1) + cbl(null, slc(data, b, b + sc)); + else if (c_1 == 8) { + var infl = data.subarray(b, b + sc); + // Synchronously decompress under 512KB, or barely-compressed data + if (su < 524288 || sc > 0.8 * su) { + try { + cbl(null, inflateSync(infl, { out: new u8(su) })); + } + catch (e) { + cbl(e, null); + } + } + else + term.push(inflate(infl, { size: su }, cbl)); + } + else + cbl(err(14, 'unknown compression type ' + c_1, 1), null); } else - term.push(inflate(infl, { size: su }, cbl)); + cbl(null, null); + }; + for (var i = 0; i < c; ++i) { + _loop_3(i); } - else - cbl('unknown compression type ' + c_1, null); - }; - for (var i = 0; i < c; ++i) { - _loop_3(i); } + else + cbd(null, {}); return tAll; } /** * Synchronously decompresses a ZIP archive. Prefer using `unzip` for better * performance with more than one file. * @param data The raw compressed ZIP file + * @param opts The ZIP extraction options * @returns The decompressed files */ -export function unzipSync(data) { +export function unzipSync(data, opts) { var files = {}; var e = data.length - 22; for (; b4(data, e) != 0x6054B50; --e) { if (!e || data.length - e > 65558) - throw 'invalid zip file'; + err(13); } ; var c = b2(data, e + 8); if (!c) return {}; var o = b4(data, e + 16); - var z = o == 4294967295; + var z = o == 4294967295 || c == 65535; if (z) { - e = b4(data, e - 12); - if (b4(data, e) != 0x6064B50) - throw 'invalid zip file'; - c = b4(data, e + 32); - o = b4(data, e + 48); + var ze = b4(data, e - 12); + z = b4(data, ze) == 0x6064B50; + if (z) { + c = b4(data, ze + 32); + o = b4(data, ze + 48); + } } + var fltr = opts && opts.filter; for (var i = 0; i < c; ++i) { var _a = zh(data, o, z), c_2 = _a[0], sc = _a[1], su = _a[2], fn = _a[3], no = _a[4], off = _a[5], b = slzh(data, off); o = no; - if (!c_2) - files[fn] = slc(data, b, b + sc); - else if (c_2 == 8) - files[fn] = inflateSync(data.subarray(b, b + sc), new u8(su)); - else - throw 'unknown compression type ' + c_2; + if (!fltr || fltr({ + name: fn, + size: sc, + originalSize: su, + compression: c_2 + })) { + if (!c_2) + files[fn] = slc(data, b, b + sc); + else if (c_2 == 8) + files[fn] = inflateSync(data.subarray(b, b + sc), { out: new u8(su) }); + else + err(14, 'unknown compression type ' + c_2); + } } return files; } diff --git a/examples/jsm/libs/ktx-parse.module.js b/examples/jsm/libs/ktx-parse.module.js index c1d66bc7462119..3593d4378b02e0 100644 --- a/examples/jsm/libs/ktx-parse.module.js +++ b/examples/jsm/libs/ktx-parse.module.js @@ -1 +1 @@ -const t=0,e=1,n=2,i=3,s=0,a=0,r=2,o=0,l=1,f=160,U=161,c=162,h=163,_=0,p=1,g=0,y=1,x=2,u=3,b=4,d=5,m=6,w=7,D=8,B=9,L=10,A=11,k=12,v=13,S=14,I=15,O=16,T=17,V=18,E=0,F=1,P=2,C=3,z=4,M=5,W=6,N=7,H=8,K=9,X=10,j=11,R=0,Y=1,q=2,G=13,J=14,Q=15,Z=128,$=64,tt=32,et=16,nt=0,it=1,st=2,at=3,rt=4,ot=5,lt=6,ft=7,Ut=8,ct=9,ht=10,_t=13,pt=14,gt=15,yt=16,xt=17,ut=20,bt=21,dt=22,mt=23,wt=24,Dt=27,Bt=28,Lt=29,At=30,kt=31,vt=34,St=35,It=36,Ot=37,Tt=38,Vt=41,Et=42,Ft=43,Pt=44,Ct=45,zt=48,Mt=49,Wt=50,Nt=58,Ht=59,Kt=62,Xt=63,jt=64,Rt=65,Yt=68,qt=69,Gt=70,Jt=71,Qt=74,Zt=75,$t=76,te=77,ee=78,ne=81,ie=82,se=83,ae=84,re=85,oe=88,le=89,fe=90,Ue=91,ce=92,he=95,_e=96,pe=97,ge=98,ye=99,xe=100,ue=101,be=102,de=103,me=104,we=105,De=106,Be=107,Le=108,Ae=109,ke=110,ve=111,Se=112,Ie=113,Oe=114,Te=115,Ve=116,Ee=117,Fe=118,Pe=119,Ce=120,ze=121,Me=122,We=123,Ne=124,He=125,Ke=126,Xe=127,je=128,Re=129,Ye=130,qe=131,Ge=132,Je=133,Qe=134,Ze=135,$e=136,tn=137,en=138,nn=139,sn=140,an=141,rn=142,on=143,ln=144,fn=145,Un=146,cn=147,hn=148,_n=149,pn=150,gn=151,yn=152,xn=153,un=154,bn=155,dn=156,mn=157,wn=158,Dn=159,Bn=160,Ln=161,An=162,kn=163,vn=164,Sn=165,In=166,On=167,Tn=168,Vn=169,En=170,Fn=171,Pn=172,Cn=173,zn=174,Mn=175,Wn=176,Nn=177,Hn=178,Kn=179,Xn=180,jn=181,Rn=182,Yn=183,qn=184,Gn=1000156007,Jn=1000156008,Qn=1000156009,Zn=1000156010,$n=1000156011,ti=1000156017,ei=1000156018,ni=1000156019,ii=1000156020,si=1000156021,ai=1000054e3,ri=1000054001,oi=1000054002,li=1000054003,fi=1000054004,Ui=1000054005,ci=1000054006,hi=1000054007,_i=1000066e3,pi=1000066001,gi=1000066002,yi=1000066003,xi=1000066004,ui=1000066005,bi=1000066006,di=1000066007,mi=1000066008,wi=1000066009,Di=1000066010,Bi=1000066011,Li=1000066012,Ai=1000066013,ki=100034e4,vi=1000340001;class Si{constructor(){this.vkFormat=0,this.typeSize=1,this.pixelWidth=0,this.pixelHeight=0,this.pixelDepth=0,this.layerCount=0,this.faceCount=1,this.supercompressionScheme=0,this.levels=[],this.dataFormatDescriptor=[{vendorId:0,descriptorType:0,descriptorBlockSize:0,versionNumber:2,colorModel:0,colorPrimaries:1,transferFunction:2,flags:0,texelBlockDimension:[0,0,0,0],bytesPlane:[0,0,0,0,0,0,0,0],samples:[]}],this.keyValue={},this.globalData=null}}class Ii{constructor(t,e,n,i){this._dataView=new DataView(t.buffer,t.byteOffset+e,n),this._littleEndian=i,this._offset=0}_nextUint8(){const t=this._dataView.getUint8(this._offset);return this._offset+=1,t}_nextUint16(){const t=this._dataView.getUint16(this._offset,this._littleEndian);return this._offset+=2,t}_nextUint32(){const t=this._dataView.getUint32(this._offset,this._littleEndian);return this._offset+=4,t}_nextUint64(){const t=this._dataView.getUint32(this._offset,this._littleEndian)+2**32*this._dataView.getUint32(this._offset+4,this._littleEndian);return this._offset+=8,t}_nextInt32(){const t=this._dataView.getInt32(this._offset,this._littleEndian);return this._offset+=4,t}_skip(t){return this._offset+=t,this}_scan(t,e=0){const n=this._offset;let i=0;for(;this._dataView.getUint8(this._offset)!==e&&i0?c+a.byteLength:0;h%8&&(h+=8-h%8);const _=[],p=new DataView(new ArrayBuffer(3*t.levels.length*8));let g=(h||c+a.byteLength)+n.byteLength;for(let e=0;e0?h:0),!0),x.setBigUint64(60,BigInt(n.byteLength),!0),new Uint8Array(Fi([new Uint8Array(Ti).buffer,y,p.buffer,o,a,h>0?new ArrayBuffer(h-(c+a.byteLength)):new ArrayBuffer(0),n,..._]))}export{Q as KHR_DF_CHANNEL_RGBSDA_ALPHA,q as KHR_DF_CHANNEL_RGBSDA_BLUE,J as KHR_DF_CHANNEL_RGBSDA_DEPTH,Y as KHR_DF_CHANNEL_RGBSDA_GREEN,R as KHR_DF_CHANNEL_RGBSDA_RED,G as KHR_DF_CHANNEL_RGBSDA_STENCIL,p as KHR_DF_FLAG_ALPHA_PREMULTIPLIED,_ as KHR_DF_FLAG_ALPHA_STRAIGHT,s as KHR_DF_KHR_DESCRIPTORTYPE_BASICFORMAT,c as KHR_DF_MODEL_ASTC,f as KHR_DF_MODEL_ETC1,h as KHR_DF_MODEL_ETC1S,U as KHR_DF_MODEL_ETC2,l as KHR_DF_MODEL_RGBSDA,o as KHR_DF_MODEL_UNSPECIFIED,W as KHR_DF_PRIMARIES_ACES,N as KHR_DF_PRIMARIES_ACESCC,j as KHR_DF_PRIMARIES_ADOBERGB,z as KHR_DF_PRIMARIES_BT2020,P as KHR_DF_PRIMARIES_BT601_EBU,C as KHR_DF_PRIMARIES_BT601_SMPTE,F as KHR_DF_PRIMARIES_BT709,M as KHR_DF_PRIMARIES_CIEXYZ,X as KHR_DF_PRIMARIES_DISPLAYP3,H as KHR_DF_PRIMARIES_NTSC1953,K as KHR_DF_PRIMARIES_PAL525,E as KHR_DF_PRIMARIES_UNSPECIFIED,tt as KHR_DF_SAMPLE_DATATYPE_EXPONENT,Z as KHR_DF_SAMPLE_DATATYPE_FLOAT,et as KHR_DF_SAMPLE_DATATYPE_LINEAR,$ as KHR_DF_SAMPLE_DATATYPE_SIGNED,O as KHR_DF_TRANSFER_ACESCC,T as KHR_DF_TRANSFER_ACESCCT,V as KHR_DF_TRANSFER_ADOBERGB,w as KHR_DF_TRANSFER_BT1886,k as KHR_DF_TRANSFER_DCIP3,B as KHR_DF_TRANSFER_HLG_EOTF,D as KHR_DF_TRANSFER_HLG_OETF,u as KHR_DF_TRANSFER_ITU,y as KHR_DF_TRANSFER_LINEAR,b as KHR_DF_TRANSFER_NTSC,S as KHR_DF_TRANSFER_PAL625_EOTF,v as KHR_DF_TRANSFER_PAL_OETF,L as KHR_DF_TRANSFER_PQ_EOTF,A as KHR_DF_TRANSFER_PQ_OETF,d as KHR_DF_TRANSFER_SLOG,m as KHR_DF_TRANSFER_SLOG2,x as KHR_DF_TRANSFER_SRGB,I as KHR_DF_TRANSFER_ST240,g as KHR_DF_TRANSFER_UNSPECIFIED,a as KHR_DF_VENDORID_KHRONOS,r as KHR_DF_VERSION,e as KHR_SUPERCOMPRESSION_BASISLZ,t as KHR_SUPERCOMPRESSION_NONE,i as KHR_SUPERCOMPRESSION_ZLIB,n as KHR_SUPERCOMPRESSION_ZSTD,Si as KTX2Container,Ut as VK_FORMAT_A1R5G5B5_UNORM_PACK16,qt as VK_FORMAT_A2B10G10R10_SINT_PACK32,Rt as VK_FORMAT_A2B10G10R10_SNORM_PACK32,Yt as VK_FORMAT_A2B10G10R10_UINT_PACK32,jt as VK_FORMAT_A2B10G10R10_UNORM_PACK32,Xt as VK_FORMAT_A2R10G10B10_SINT_PACK32,Ht as VK_FORMAT_A2R10G10B10_SNORM_PACK32,Kt as VK_FORMAT_A2R10G10B10_UINT_PACK32,Nt as VK_FORMAT_A2R10G10B10_UNORM_PACK32,vi as VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT,ki as VK_FORMAT_A4R4G4B4_UNORM_PACK16_EXT,Bi as VK_FORMAT_ASTC_10x10_SFLOAT_BLOCK_EXT,Xn as VK_FORMAT_ASTC_10x10_SRGB_BLOCK,Kn as VK_FORMAT_ASTC_10x10_UNORM_BLOCK,mi as VK_FORMAT_ASTC_10x5_SFLOAT_BLOCK_EXT,zn as VK_FORMAT_ASTC_10x5_SRGB_BLOCK,Cn as VK_FORMAT_ASTC_10x5_UNORM_BLOCK,wi as VK_FORMAT_ASTC_10x6_SFLOAT_BLOCK_EXT,Wn as VK_FORMAT_ASTC_10x6_SRGB_BLOCK,Mn as VK_FORMAT_ASTC_10x6_UNORM_BLOCK,Di as VK_FORMAT_ASTC_10x8_SFLOAT_BLOCK_EXT,Hn as VK_FORMAT_ASTC_10x8_SRGB_BLOCK,Nn as VK_FORMAT_ASTC_10x8_UNORM_BLOCK,Li as VK_FORMAT_ASTC_12x10_SFLOAT_BLOCK_EXT,Rn as VK_FORMAT_ASTC_12x10_SRGB_BLOCK,jn as VK_FORMAT_ASTC_12x10_UNORM_BLOCK,Ai as VK_FORMAT_ASTC_12x12_SFLOAT_BLOCK_EXT,qn as VK_FORMAT_ASTC_12x12_SRGB_BLOCK,Yn as VK_FORMAT_ASTC_12x12_UNORM_BLOCK,_i as VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK_EXT,wn as VK_FORMAT_ASTC_4x4_SRGB_BLOCK,mn as VK_FORMAT_ASTC_4x4_UNORM_BLOCK,pi as VK_FORMAT_ASTC_5x4_SFLOAT_BLOCK_EXT,Bn as VK_FORMAT_ASTC_5x4_SRGB_BLOCK,Dn as VK_FORMAT_ASTC_5x4_UNORM_BLOCK,gi as VK_FORMAT_ASTC_5x5_SFLOAT_BLOCK_EXT,An as VK_FORMAT_ASTC_5x5_SRGB_BLOCK,Ln as VK_FORMAT_ASTC_5x5_UNORM_BLOCK,yi as VK_FORMAT_ASTC_6x5_SFLOAT_BLOCK_EXT,vn as VK_FORMAT_ASTC_6x5_SRGB_BLOCK,kn as VK_FORMAT_ASTC_6x5_UNORM_BLOCK,xi as VK_FORMAT_ASTC_6x6_SFLOAT_BLOCK_EXT,In as VK_FORMAT_ASTC_6x6_SRGB_BLOCK,Sn as VK_FORMAT_ASTC_6x6_UNORM_BLOCK,ui as VK_FORMAT_ASTC_8x5_SFLOAT_BLOCK_EXT,Tn as VK_FORMAT_ASTC_8x5_SRGB_BLOCK,On as VK_FORMAT_ASTC_8x5_UNORM_BLOCK,bi as VK_FORMAT_ASTC_8x6_SFLOAT_BLOCK_EXT,En as VK_FORMAT_ASTC_8x6_SRGB_BLOCK,Vn as VK_FORMAT_ASTC_8x6_UNORM_BLOCK,di as VK_FORMAT_ASTC_8x8_SFLOAT_BLOCK_EXT,Pn as VK_FORMAT_ASTC_8x8_SRGB_BLOCK,Fn as VK_FORMAT_ASTC_8x8_UNORM_BLOCK,Me as VK_FORMAT_B10G11R11_UFLOAT_PACK32,$n as VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16,si as VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16,at as VK_FORMAT_B4G4R4A4_UNORM_PACK16,ft as VK_FORMAT_B5G5R5A1_UNORM_PACK16,ot as VK_FORMAT_B5G6R5_UNORM_PACK16,Mt as VK_FORMAT_B8G8R8A8_SINT,Ct as VK_FORMAT_B8G8R8A8_SNORM,Wt as VK_FORMAT_B8G8R8A8_SRGB,zt as VK_FORMAT_B8G8R8A8_UINT,Pt as VK_FORMAT_B8G8R8A8_UNORM,St as VK_FORMAT_B8G8R8_SINT,kt as VK_FORMAT_B8G8R8_SNORM,It as VK_FORMAT_B8G8R8_SRGB,vt as VK_FORMAT_B8G8R8_UINT,At as VK_FORMAT_B8G8R8_UNORM,Qe as VK_FORMAT_BC1_RGBA_SRGB_BLOCK,Je as VK_FORMAT_BC1_RGBA_UNORM_BLOCK,Ge as VK_FORMAT_BC1_RGB_SRGB_BLOCK,qe as VK_FORMAT_BC1_RGB_UNORM_BLOCK,$e as VK_FORMAT_BC2_SRGB_BLOCK,Ze as VK_FORMAT_BC2_UNORM_BLOCK,en as VK_FORMAT_BC3_SRGB_BLOCK,tn as VK_FORMAT_BC3_UNORM_BLOCK,sn as VK_FORMAT_BC4_SNORM_BLOCK,nn as VK_FORMAT_BC4_UNORM_BLOCK,rn as VK_FORMAT_BC5_SNORM_BLOCK,an as VK_FORMAT_BC5_UNORM_BLOCK,ln as VK_FORMAT_BC6H_SFLOAT_BLOCK,on as VK_FORMAT_BC6H_UFLOAT_BLOCK,Un as VK_FORMAT_BC7_SRGB_BLOCK,fn as VK_FORMAT_BC7_UNORM_BLOCK,Ne as VK_FORMAT_D16_UNORM,je as VK_FORMAT_D16_UNORM_S8_UINT,Re as VK_FORMAT_D24_UNORM_S8_UINT,Ke as VK_FORMAT_D32_SFLOAT,Ye as VK_FORMAT_D32_SFLOAT_S8_UINT,We as VK_FORMAT_E5B9G9R9_UFLOAT_PACK32,dn as VK_FORMAT_EAC_R11G11_SNORM_BLOCK,bn as VK_FORMAT_EAC_R11G11_UNORM_BLOCK,un as VK_FORMAT_EAC_R11_SNORM_BLOCK,xn as VK_FORMAT_EAC_R11_UNORM_BLOCK,pn as VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK,_n as VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK,yn as VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK,gn as VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK,hn as VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK,cn as VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK,Zn as VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16,ii as VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16,fi as VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG,ai as VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG,Ui as VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG,ri as VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG,ci as VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG,oi as VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG,hi as VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG,li as VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG,Qn as VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16,Jn as VK_FORMAT_R10X6G10X6_UNORM_2PACK16,Gn as VK_FORMAT_R10X6_UNORM_PACK16,ni as VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16,ei as VK_FORMAT_R12X4G12X4_UNORM_2PACK16,ti as VK_FORMAT_R12X4_UNORM_PACK16,pe as VK_FORMAT_R16G16B16A16_SFLOAT,_e as VK_FORMAT_R16G16B16A16_SINT,ce as VK_FORMAT_R16G16B16A16_SNORM,he as VK_FORMAT_R16G16B16A16_UINT,Ue as VK_FORMAT_R16G16B16A16_UNORM,fe as VK_FORMAT_R16G16B16_SFLOAT,le as VK_FORMAT_R16G16B16_SINT,re as VK_FORMAT_R16G16B16_SNORM,oe as VK_FORMAT_R16G16B16_UINT,ae as VK_FORMAT_R16G16B16_UNORM,se as VK_FORMAT_R16G16_SFLOAT,ie as VK_FORMAT_R16G16_SINT,ee as VK_FORMAT_R16G16_SNORM,ne as VK_FORMAT_R16G16_UINT,te as VK_FORMAT_R16G16_UNORM,$t as VK_FORMAT_R16_SFLOAT,Zt as VK_FORMAT_R16_SINT,Jt as VK_FORMAT_R16_SNORM,Qt as VK_FORMAT_R16_UINT,Gt as VK_FORMAT_R16_UNORM,Ae as VK_FORMAT_R32G32B32A32_SFLOAT,Le as VK_FORMAT_R32G32B32A32_SINT,Be as VK_FORMAT_R32G32B32A32_UINT,De as VK_FORMAT_R32G32B32_SFLOAT,we as VK_FORMAT_R32G32B32_SINT,me as VK_FORMAT_R32G32B32_UINT,de as VK_FORMAT_R32G32_SFLOAT,be as VK_FORMAT_R32G32_SINT,ue as VK_FORMAT_R32G32_UINT,xe as VK_FORMAT_R32_SFLOAT,ye as VK_FORMAT_R32_SINT,ge as VK_FORMAT_R32_UINT,st as VK_FORMAT_R4G4B4A4_UNORM_PACK16,it as VK_FORMAT_R4G4_UNORM_PACK8,lt as VK_FORMAT_R5G5B5A1_UNORM_PACK16,rt as VK_FORMAT_R5G6B5_UNORM_PACK16,ze as VK_FORMAT_R64G64B64A64_SFLOAT,Ce as VK_FORMAT_R64G64B64A64_SINT,Pe as VK_FORMAT_R64G64B64A64_UINT,Fe as VK_FORMAT_R64G64B64_SFLOAT,Ee as VK_FORMAT_R64G64B64_SINT,Ve as VK_FORMAT_R64G64B64_UINT,Te as VK_FORMAT_R64G64_SFLOAT,Oe as VK_FORMAT_R64G64_SINT,Ie as VK_FORMAT_R64G64_UINT,Se as VK_FORMAT_R64_SFLOAT,ve as VK_FORMAT_R64_SINT,ke as VK_FORMAT_R64_UINT,Et as VK_FORMAT_R8G8B8A8_SINT,Tt as VK_FORMAT_R8G8B8A8_SNORM,Ft as VK_FORMAT_R8G8B8A8_SRGB,Vt as VK_FORMAT_R8G8B8A8_UINT,Ot as VK_FORMAT_R8G8B8A8_UNORM,Bt as VK_FORMAT_R8G8B8_SINT,wt as VK_FORMAT_R8G8B8_SNORM,Lt as VK_FORMAT_R8G8B8_SRGB,Dt as VK_FORMAT_R8G8B8_UINT,mt as VK_FORMAT_R8G8B8_UNORM,bt as VK_FORMAT_R8G8_SINT,xt as VK_FORMAT_R8G8_SNORM,dt as VK_FORMAT_R8G8_SRGB,ut as VK_FORMAT_R8G8_UINT,yt as VK_FORMAT_R8G8_UNORM,pt as VK_FORMAT_R8_SINT,ht as VK_FORMAT_R8_SNORM,gt as VK_FORMAT_R8_SRGB,_t as VK_FORMAT_R8_UINT,ct as VK_FORMAT_R8_UNORM,Xe as VK_FORMAT_S8_UINT,nt as VK_FORMAT_UNDEFINED,He as VK_FORMAT_X8_D24_UNORM_PACK32,Pi as read,Mi as write}; +const t=0,e=1,n=2,i=3,s=0,a=0,r=2,o=0,l=1,f=160,h=161,U=162,c=163,_=166,p=0,g=1,y=0,x=1,u=2,b=3,d=4,w=5,m=6,D=7,B=8,L=9,v=10,A=11,k=12,V=13,I=14,S=15,F=16,O=17,E=18,T=0,C=1,M=2,P=3,z=4,W=5,H=6,N=7,K=8,X=9,R=10,Y=11,j=0,q=1,G=2,J=13,Q=14,Z=15,$=128,tt=64,et=32,nt=16,it=0,st=1,at=2,rt=3,ot=4,lt=5,ft=6,ht=7,Ut=8,ct=9,_t=10,pt=13,gt=14,yt=15,xt=16,ut=17,bt=20,dt=21,wt=22,mt=23,Dt=24,Bt=27,Lt=28,vt=29,At=30,kt=31,Vt=34,It=35,St=36,Ft=37,Ot=38,Et=41,Tt=42,Ct=43,Mt=44,Pt=45,zt=48,Wt=49,Ht=50,Nt=58,Kt=59,Xt=62,Rt=63,Yt=64,jt=65,qt=68,Gt=69,Jt=70,Qt=71,Zt=74,$t=75,te=76,ee=77,ne=78,ie=81,se=82,ae=83,re=84,oe=85,le=88,fe=89,he=90,Ue=91,ce=92,_e=95,pe=96,ge=97,ye=98,xe=99,ue=100,be=101,de=102,we=103,me=104,De=105,Be=106,Le=107,ve=108,Ae=109,ke=110,Ve=111,Ie=112,Se=113,Fe=114,Oe=115,Ee=116,Te=117,Ce=118,Me=119,Pe=120,ze=121,We=122,He=123,Ne=124,Ke=125,Xe=126,Re=127,Ye=128,je=129,qe=130,Ge=131,Je=132,Qe=133,Ze=134,$e=135,tn=136,en=137,nn=138,sn=139,an=140,rn=141,on=142,ln=143,fn=144,hn=145,Un=146,cn=147,_n=148,pn=149,gn=150,yn=151,xn=152,un=153,bn=154,dn=155,wn=156,mn=157,Dn=158,Bn=159,Ln=160,vn=161,An=162,kn=163,Vn=164,In=165,Sn=166,Fn=167,On=168,En=169,Tn=170,Cn=171,Mn=172,Pn=173,zn=174,Wn=175,Hn=176,Nn=177,Kn=178,Xn=179,Rn=180,Yn=181,jn=182,qn=183,Gn=184,Jn=1000156007,Qn=1000156008,Zn=1000156009,$n=1000156010,ti=1000156011,ei=1000156017,ni=1000156018,ii=1000156019,si=1000156020,ai=1000156021,ri=1000054e3,oi=1000054001,li=1000054002,fi=1000054003,hi=1000054004,Ui=1000054005,ci=1000054006,_i=1000054007,pi=1000066e3,gi=1000066001,yi=1000066002,xi=1000066003,ui=1000066004,bi=1000066005,di=1000066006,wi=1000066007,mi=1000066008,Di=1000066009,Bi=1000066010,Li=1000066011,vi=1000066012,Ai=1000066013,ki=100034e4,Vi=1000340001;class Ii{constructor(){this.vkFormat=0,this.typeSize=1,this.pixelWidth=0,this.pixelHeight=0,this.pixelDepth=0,this.layerCount=0,this.faceCount=1,this.supercompressionScheme=0,this.levels=[],this.dataFormatDescriptor=[{vendorId:0,descriptorType:0,descriptorBlockSize:0,versionNumber:2,colorModel:0,colorPrimaries:1,transferFunction:2,flags:0,texelBlockDimension:[0,0,0,0],bytesPlane:[0,0,0,0,0,0,0,0],samples:[]}],this.keyValue={},this.globalData=null}}class Si{constructor(t,e,n,i){this._dataView=void 0,this._littleEndian=void 0,this._offset=void 0,this._dataView=new DataView(t.buffer,t.byteOffset+e,n),this._littleEndian=i,this._offset=0}_nextUint8(){const t=this._dataView.getUint8(this._offset);return this._offset+=1,t}_nextUint16(){const t=this._dataView.getUint16(this._offset,this._littleEndian);return this._offset+=2,t}_nextUint32(){const t=this._dataView.getUint32(this._offset,this._littleEndian);return this._offset+=4,t}_nextUint64(){const t=this._dataView.getUint32(this._offset,this._littleEndian)+2**32*this._dataView.getUint32(this._offset+4,this._littleEndian);return this._offset+=8,t}_nextInt32(){const t=this._dataView.getInt32(this._offset,this._littleEndian);return this._offset+=4,t}_nextUint8Array(t){const e=new Uint8Array(this._dataView.buffer,this._dataView.byteOffset+this._offset,t);return this._offset+=t,e}_skip(t){return this._offset+=t,this}_scan(t,e){void 0===e&&(e=0);const n=this._offset;let i=0;for(;this._dataView.getUint8(this._offset)!==e&&i0?U+a.byteLength:0;c%8&&(c+=8-c%8);const _=[],p=new DataView(new ArrayBuffer(3*t.levels.length*8)),g=new Uint32Array(t.levels.length);let y=0;0===t.supercompressionScheme&&(y=function(t,e){const n=Math.max(t,4),i=Math.min(t,4);let s=n;for(;s%i!=0;)s+=n;return s}(function(t){return t.levels[0].levelData.byteLength/function(t,e){let n=1;const i=[t.pixelWidth,t.pixelHeight,t.pixelDepth],s=function(t){const[e,n,i]=t.dataFormatDescriptor[0].texelBlockDimension;return[e+1,n+1,i+1]}(t);for(let t=0;t<3;t++)if(i[t]>0){const e=Math.ceil(Math.floor(i[t]*Math.pow(2,-0))/s[t]);n*=Math.max(1,e)}return t.layerCount>0&&(n*=t.layerCount),t.faceCount>0&&(n*=t.faceCount),n}(t)}(t)));let x=(c||U+a.byteLength)+n.byteLength;for(let e=t.levels.length-1;e>=0;e--){if(x%y){const t=Mi(x,y);_.push(new Uint8Array(t)),x+=t}const n=t.levels[e];_.push(n.levelData),g[e]=x,x+=n.levelData.byteLength}for(let e=0;e0?c:0),!0),b.setBigUint64(60,BigInt(n.byteLength),!0),new Uint8Array(Ci([new Uint8Array(Oi).buffer,u,p.buffer,o,a,c>0?new ArrayBuffer(c-(U+a.byteLength)):new ArrayBuffer(0),n,..._]))}export{Z as KHR_DF_CHANNEL_RGBSDA_ALPHA,G as KHR_DF_CHANNEL_RGBSDA_BLUE,Q as KHR_DF_CHANNEL_RGBSDA_DEPTH,q as KHR_DF_CHANNEL_RGBSDA_GREEN,j as KHR_DF_CHANNEL_RGBSDA_RED,J as KHR_DF_CHANNEL_RGBSDA_STENCIL,g as KHR_DF_FLAG_ALPHA_PREMULTIPLIED,p as KHR_DF_FLAG_ALPHA_STRAIGHT,s as KHR_DF_KHR_DESCRIPTORTYPE_BASICFORMAT,U as KHR_DF_MODEL_ASTC,f as KHR_DF_MODEL_ETC1,c as KHR_DF_MODEL_ETC1S,h as KHR_DF_MODEL_ETC2,l as KHR_DF_MODEL_RGBSDA,_ as KHR_DF_MODEL_UASTC,o as KHR_DF_MODEL_UNSPECIFIED,H as KHR_DF_PRIMARIES_ACES,N as KHR_DF_PRIMARIES_ACESCC,Y as KHR_DF_PRIMARIES_ADOBERGB,z as KHR_DF_PRIMARIES_BT2020,M as KHR_DF_PRIMARIES_BT601_EBU,P as KHR_DF_PRIMARIES_BT601_SMPTE,C as KHR_DF_PRIMARIES_BT709,W as KHR_DF_PRIMARIES_CIEXYZ,R as KHR_DF_PRIMARIES_DISPLAYP3,K as KHR_DF_PRIMARIES_NTSC1953,X as KHR_DF_PRIMARIES_PAL525,T as KHR_DF_PRIMARIES_UNSPECIFIED,et as KHR_DF_SAMPLE_DATATYPE_EXPONENT,$ as KHR_DF_SAMPLE_DATATYPE_FLOAT,nt as KHR_DF_SAMPLE_DATATYPE_LINEAR,tt as KHR_DF_SAMPLE_DATATYPE_SIGNED,F as KHR_DF_TRANSFER_ACESCC,O as KHR_DF_TRANSFER_ACESCCT,E as KHR_DF_TRANSFER_ADOBERGB,D as KHR_DF_TRANSFER_BT1886,k as KHR_DF_TRANSFER_DCIP3,L as KHR_DF_TRANSFER_HLG_EOTF,B as KHR_DF_TRANSFER_HLG_OETF,b as KHR_DF_TRANSFER_ITU,x as KHR_DF_TRANSFER_LINEAR,d as KHR_DF_TRANSFER_NTSC,I as KHR_DF_TRANSFER_PAL625_EOTF,V as KHR_DF_TRANSFER_PAL_OETF,v as KHR_DF_TRANSFER_PQ_EOTF,A as KHR_DF_TRANSFER_PQ_OETF,w as KHR_DF_TRANSFER_SLOG,m as KHR_DF_TRANSFER_SLOG2,u as KHR_DF_TRANSFER_SRGB,S as KHR_DF_TRANSFER_ST240,y as KHR_DF_TRANSFER_UNSPECIFIED,a as KHR_DF_VENDORID_KHRONOS,r as KHR_DF_VERSION,e as KHR_SUPERCOMPRESSION_BASISLZ,t as KHR_SUPERCOMPRESSION_NONE,i as KHR_SUPERCOMPRESSION_ZLIB,n as KHR_SUPERCOMPRESSION_ZSTD,Ii as KTX2Container,Ut as VK_FORMAT_A1R5G5B5_UNORM_PACK16,Gt as VK_FORMAT_A2B10G10R10_SINT_PACK32,jt as VK_FORMAT_A2B10G10R10_SNORM_PACK32,qt as VK_FORMAT_A2B10G10R10_UINT_PACK32,Yt as VK_FORMAT_A2B10G10R10_UNORM_PACK32,Rt as VK_FORMAT_A2R10G10B10_SINT_PACK32,Kt as VK_FORMAT_A2R10G10B10_SNORM_PACK32,Xt as VK_FORMAT_A2R10G10B10_UINT_PACK32,Nt as VK_FORMAT_A2R10G10B10_UNORM_PACK32,Vi as VK_FORMAT_A4B4G4R4_UNORM_PACK16_EXT,ki as VK_FORMAT_A4R4G4B4_UNORM_PACK16_EXT,Li as VK_FORMAT_ASTC_10x10_SFLOAT_BLOCK_EXT,Rn as VK_FORMAT_ASTC_10x10_SRGB_BLOCK,Xn as VK_FORMAT_ASTC_10x10_UNORM_BLOCK,mi as VK_FORMAT_ASTC_10x5_SFLOAT_BLOCK_EXT,zn as VK_FORMAT_ASTC_10x5_SRGB_BLOCK,Pn as VK_FORMAT_ASTC_10x5_UNORM_BLOCK,Di as VK_FORMAT_ASTC_10x6_SFLOAT_BLOCK_EXT,Hn as VK_FORMAT_ASTC_10x6_SRGB_BLOCK,Wn as VK_FORMAT_ASTC_10x6_UNORM_BLOCK,Bi as VK_FORMAT_ASTC_10x8_SFLOAT_BLOCK_EXT,Kn as VK_FORMAT_ASTC_10x8_SRGB_BLOCK,Nn as VK_FORMAT_ASTC_10x8_UNORM_BLOCK,vi as VK_FORMAT_ASTC_12x10_SFLOAT_BLOCK_EXT,jn as VK_FORMAT_ASTC_12x10_SRGB_BLOCK,Yn as VK_FORMAT_ASTC_12x10_UNORM_BLOCK,Ai as VK_FORMAT_ASTC_12x12_SFLOAT_BLOCK_EXT,Gn as VK_FORMAT_ASTC_12x12_SRGB_BLOCK,qn as VK_FORMAT_ASTC_12x12_UNORM_BLOCK,pi as VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK_EXT,Dn as VK_FORMAT_ASTC_4x4_SRGB_BLOCK,mn as VK_FORMAT_ASTC_4x4_UNORM_BLOCK,gi as VK_FORMAT_ASTC_5x4_SFLOAT_BLOCK_EXT,Ln as VK_FORMAT_ASTC_5x4_SRGB_BLOCK,Bn as VK_FORMAT_ASTC_5x4_UNORM_BLOCK,yi as VK_FORMAT_ASTC_5x5_SFLOAT_BLOCK_EXT,An as VK_FORMAT_ASTC_5x5_SRGB_BLOCK,vn as VK_FORMAT_ASTC_5x5_UNORM_BLOCK,xi as VK_FORMAT_ASTC_6x5_SFLOAT_BLOCK_EXT,Vn as VK_FORMAT_ASTC_6x5_SRGB_BLOCK,kn as VK_FORMAT_ASTC_6x5_UNORM_BLOCK,ui as VK_FORMAT_ASTC_6x6_SFLOAT_BLOCK_EXT,Sn as VK_FORMAT_ASTC_6x6_SRGB_BLOCK,In as VK_FORMAT_ASTC_6x6_UNORM_BLOCK,bi as VK_FORMAT_ASTC_8x5_SFLOAT_BLOCK_EXT,On as VK_FORMAT_ASTC_8x5_SRGB_BLOCK,Fn as VK_FORMAT_ASTC_8x5_UNORM_BLOCK,di as VK_FORMAT_ASTC_8x6_SFLOAT_BLOCK_EXT,Tn as VK_FORMAT_ASTC_8x6_SRGB_BLOCK,En as VK_FORMAT_ASTC_8x6_UNORM_BLOCK,wi as VK_FORMAT_ASTC_8x8_SFLOAT_BLOCK_EXT,Mn as VK_FORMAT_ASTC_8x8_SRGB_BLOCK,Cn as VK_FORMAT_ASTC_8x8_UNORM_BLOCK,We as VK_FORMAT_B10G11R11_UFLOAT_PACK32,ti as VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16,ai as VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16,rt as VK_FORMAT_B4G4R4A4_UNORM_PACK16,ht as VK_FORMAT_B5G5R5A1_UNORM_PACK16,lt as VK_FORMAT_B5G6R5_UNORM_PACK16,Wt as VK_FORMAT_B8G8R8A8_SINT,Pt as VK_FORMAT_B8G8R8A8_SNORM,Ht as VK_FORMAT_B8G8R8A8_SRGB,zt as VK_FORMAT_B8G8R8A8_UINT,Mt as VK_FORMAT_B8G8R8A8_UNORM,It as VK_FORMAT_B8G8R8_SINT,kt as VK_FORMAT_B8G8R8_SNORM,St as VK_FORMAT_B8G8R8_SRGB,Vt as VK_FORMAT_B8G8R8_UINT,At as VK_FORMAT_B8G8R8_UNORM,Ze as VK_FORMAT_BC1_RGBA_SRGB_BLOCK,Qe as VK_FORMAT_BC1_RGBA_UNORM_BLOCK,Je as VK_FORMAT_BC1_RGB_SRGB_BLOCK,Ge as VK_FORMAT_BC1_RGB_UNORM_BLOCK,tn as VK_FORMAT_BC2_SRGB_BLOCK,$e as VK_FORMAT_BC2_UNORM_BLOCK,nn as VK_FORMAT_BC3_SRGB_BLOCK,en as VK_FORMAT_BC3_UNORM_BLOCK,an as VK_FORMAT_BC4_SNORM_BLOCK,sn as VK_FORMAT_BC4_UNORM_BLOCK,on as VK_FORMAT_BC5_SNORM_BLOCK,rn as VK_FORMAT_BC5_UNORM_BLOCK,fn as VK_FORMAT_BC6H_SFLOAT_BLOCK,ln as VK_FORMAT_BC6H_UFLOAT_BLOCK,Un as VK_FORMAT_BC7_SRGB_BLOCK,hn as VK_FORMAT_BC7_UNORM_BLOCK,Ne as VK_FORMAT_D16_UNORM,Ye as VK_FORMAT_D16_UNORM_S8_UINT,je as VK_FORMAT_D24_UNORM_S8_UINT,Xe as VK_FORMAT_D32_SFLOAT,qe as VK_FORMAT_D32_SFLOAT_S8_UINT,He as VK_FORMAT_E5B9G9R9_UFLOAT_PACK32,wn as VK_FORMAT_EAC_R11G11_SNORM_BLOCK,dn as VK_FORMAT_EAC_R11G11_UNORM_BLOCK,bn as VK_FORMAT_EAC_R11_SNORM_BLOCK,un as VK_FORMAT_EAC_R11_UNORM_BLOCK,gn as VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK,pn as VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK,xn as VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK,yn as VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK,_n as VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK,cn as VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK,$n as VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16,si as VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16,hi as VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG,ri as VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG,Ui as VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG,oi as VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG,ci as VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG,li as VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG,_i as VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG,fi as VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG,Zn as VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16,Qn as VK_FORMAT_R10X6G10X6_UNORM_2PACK16,Jn as VK_FORMAT_R10X6_UNORM_PACK16,ii as VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16,ni as VK_FORMAT_R12X4G12X4_UNORM_2PACK16,ei as VK_FORMAT_R12X4_UNORM_PACK16,ge as VK_FORMAT_R16G16B16A16_SFLOAT,pe as VK_FORMAT_R16G16B16A16_SINT,ce as VK_FORMAT_R16G16B16A16_SNORM,_e as VK_FORMAT_R16G16B16A16_UINT,Ue as VK_FORMAT_R16G16B16A16_UNORM,he as VK_FORMAT_R16G16B16_SFLOAT,fe as VK_FORMAT_R16G16B16_SINT,oe as VK_FORMAT_R16G16B16_SNORM,le as VK_FORMAT_R16G16B16_UINT,re as VK_FORMAT_R16G16B16_UNORM,ae as VK_FORMAT_R16G16_SFLOAT,se as VK_FORMAT_R16G16_SINT,ne as VK_FORMAT_R16G16_SNORM,ie as VK_FORMAT_R16G16_UINT,ee as VK_FORMAT_R16G16_UNORM,te as VK_FORMAT_R16_SFLOAT,$t as VK_FORMAT_R16_SINT,Qt as VK_FORMAT_R16_SNORM,Zt as VK_FORMAT_R16_UINT,Jt as VK_FORMAT_R16_UNORM,Ae as VK_FORMAT_R32G32B32A32_SFLOAT,ve as VK_FORMAT_R32G32B32A32_SINT,Le as VK_FORMAT_R32G32B32A32_UINT,Be as VK_FORMAT_R32G32B32_SFLOAT,De as VK_FORMAT_R32G32B32_SINT,me as VK_FORMAT_R32G32B32_UINT,we as VK_FORMAT_R32G32_SFLOAT,de as VK_FORMAT_R32G32_SINT,be as VK_FORMAT_R32G32_UINT,ue as VK_FORMAT_R32_SFLOAT,xe as VK_FORMAT_R32_SINT,ye as VK_FORMAT_R32_UINT,at as VK_FORMAT_R4G4B4A4_UNORM_PACK16,st as VK_FORMAT_R4G4_UNORM_PACK8,ft as VK_FORMAT_R5G5B5A1_UNORM_PACK16,ot as VK_FORMAT_R5G6B5_UNORM_PACK16,ze as VK_FORMAT_R64G64B64A64_SFLOAT,Pe as VK_FORMAT_R64G64B64A64_SINT,Me as VK_FORMAT_R64G64B64A64_UINT,Ce as VK_FORMAT_R64G64B64_SFLOAT,Te as VK_FORMAT_R64G64B64_SINT,Ee as VK_FORMAT_R64G64B64_UINT,Oe as VK_FORMAT_R64G64_SFLOAT,Fe as VK_FORMAT_R64G64_SINT,Se as VK_FORMAT_R64G64_UINT,Ie as VK_FORMAT_R64_SFLOAT,Ve as VK_FORMAT_R64_SINT,ke as VK_FORMAT_R64_UINT,Tt as VK_FORMAT_R8G8B8A8_SINT,Ot as VK_FORMAT_R8G8B8A8_SNORM,Ct as VK_FORMAT_R8G8B8A8_SRGB,Et as VK_FORMAT_R8G8B8A8_UINT,Ft as VK_FORMAT_R8G8B8A8_UNORM,Lt as VK_FORMAT_R8G8B8_SINT,Dt as VK_FORMAT_R8G8B8_SNORM,vt as VK_FORMAT_R8G8B8_SRGB,Bt as VK_FORMAT_R8G8B8_UINT,mt as VK_FORMAT_R8G8B8_UNORM,dt as VK_FORMAT_R8G8_SINT,ut as VK_FORMAT_R8G8_SNORM,wt as VK_FORMAT_R8G8_SRGB,bt as VK_FORMAT_R8G8_UINT,xt as VK_FORMAT_R8G8_UNORM,gt as VK_FORMAT_R8_SINT,_t as VK_FORMAT_R8_SNORM,yt as VK_FORMAT_R8_SRGB,pt as VK_FORMAT_R8_UINT,ct as VK_FORMAT_R8_UNORM,Re as VK_FORMAT_S8_UINT,it as VK_FORMAT_UNDEFINED,Ke as VK_FORMAT_X8_D24_UNORM_PACK32,Pi as read,Wi as write}; diff --git a/examples/jsm/libs/lottie_canvas.module.js b/examples/jsm/libs/lottie_canvas.module.js index cbe3a964e5c90d..512a84c1198bbc 100644 --- a/examples/jsm/libs/lottie_canvas.module.js +++ b/examples/jsm/libs/lottie_canvas.module.js @@ -1,3 +1,7 @@ +const lottie = {}; + +if (typeof document !== 'undefined') { + const svgNS = 'http://www.w3.org/2000/svg'; let locationHref = ''; @@ -4547,7 +4551,6 @@ const Matrix = (function () { }; }()); -const lottie = {}; var standalone = '__[STANDALONE]__'; var animationData = '__[ANIMATIONDATA]__'; var renderer = ''; @@ -14841,4 +14844,6 @@ setExpressionsPlugin(Expressions); initialize$1(); initialize(); +} + export { lottie as default }; diff --git a/examples/jsm/libs/mmdparser.module.js b/examples/jsm/libs/mmdparser.module.js deleted file mode 100644 index 5a587f10dc43c6..00000000000000 --- a/examples/jsm/libs/mmdparser.module.js +++ /dev/null @@ -1,11530 +0,0 @@ -/** - * @author Takahiro / https://github.com/takahirox - * - * Simple CharsetEncoder. - */ - -function CharsetEncoder() { -} - -/* - * Converts from Shift_JIS Uint8Array data to Unicode strings. - */ -CharsetEncoder.prototype.s2u = function(uint8Array) { - var t = this.s2uTable; - var str = ''; - var p = 0; - - while(p < uint8Array.length) { - var key = uint8Array[p++]; - - if(! ((key >= 0x00 && key <= 0x7e) || - (key >= 0xa1 && key <= 0xdf)) && - p < uint8Array.length) { - key = (key << 8) | uint8Array[p++]; - } - - if(t[key] === undefined) { - console.error('unknown char code ' + key + '.'); - return str; - } - - str += String.fromCharCode(t[key]); - } - - return str; -}; - -CharsetEncoder.prototype.s2uTable = { -0:0, -1:1, -2:2, -3:3, -4:4, -5:5, -6:6, -7:7, -8:8, -9:9, -10:10, -11:11, -12:12, -13:13, -14:14, -15:15, -16:16, -17:17, -18:18, -19:19, -20:20, -21:21, -22:22, -23:23, -24:24, -25:25, -26:26, -27:27, -28:28, -29:29, -30:30, -31:31, -32:32, -33:33, -34:34, -35:35, -36:36, -37:37, -38:38, -39:39, -40:40, -41:41, -42:42, -43:43, -44:44, -45:45, -46:46, -47:47, -48:48, -49:49, -50:50, -51:51, -52:52, -53:53, -54:54, -55:55, -56:56, -57:57, -58:58, -59:59, -60:60, -61:61, -62:62, -63:63, -64:64, -65:65, -66:66, -67:67, -68:68, -69:69, -70:70, -71:71, -72:72, -73:73, -74:74, -75:75, -76:76, -77:77, -78:78, -79:79, -80:80, -81:81, -82:82, -83:83, -84:84, -85:85, -86:86, -87:87, -88:88, -89:89, -90:90, -91:91, -92:92, -93:93, -94:94, -95:95, -96:96, -97:97, -98:98, -99:99, -100:100, -101:101, -102:102, -103:103, -104:104, -105:105, -106:106, -107:107, -108:108, -109:109, -110:110, -111:111, -112:112, -113:113, -114:114, -115:115, -116:116, -117:117, -118:118, -119:119, -120:120, -121:121, -122:122, -123:123, -124:124, -125:125, -126:126, -161:65377, -162:65378, -163:65379, -164:65380, -165:65381, -166:65382, -167:65383, -168:65384, -169:65385, -170:65386, -171:65387, -172:65388, -173:65389, -174:65390, -175:65391, -176:65392, -177:65393, -178:65394, -179:65395, -180:65396, -181:65397, -182:65398, -183:65399, -184:65400, -185:65401, -186:65402, -187:65403, -188:65404, -189:65405, -190:65406, -191:65407, -192:65408, -193:65409, -194:65410, -195:65411, -196:65412, -197:65413, -198:65414, -199:65415, -200:65416, -201:65417, -202:65418, -203:65419, -204:65420, -205:65421, -206:65422, -207:65423, -208:65424, -209:65425, -210:65426, -211:65427, -212:65428, -213:65429, -214:65430, -215:65431, -216:65432, -217:65433, -218:65434, -219:65435, -220:65436, -221:65437, -222:65438, -223:65439, -33088:12288, -33089:12289, -33090:12290, -33091:65292, -33092:65294, -33093:12539, -33094:65306, -33095:65307, -33096:65311, -33097:65281, -33098:12443, -33099:12444, -33100:180, -33101:65344, -33102:168, -33103:65342, -33104:65507, -33105:65343, -33106:12541, -33107:12542, -33108:12445, -33109:12446, -33110:12291, -33111:20189, -33112:12293, -33113:12294, -33114:12295, -33115:12540, -33116:8213, -33117:8208, -33118:65295, -33119:65340, -33120:65374, -33121:8741, -33122:65372, -33123:8230, -33124:8229, -33125:8216, -33126:8217, -33127:8220, -33128:8221, -33129:65288, -33130:65289, -33131:12308, -33132:12309, -33133:65339, -33134:65341, -33135:65371, -33136:65373, -33137:12296, -33138:12297, -33139:12298, -33140:12299, -33141:12300, -33142:12301, -33143:12302, -33144:12303, -33145:12304, -33146:12305, -33147:65291, -33148:65293, -33149:177, -33150:215, -33152:247, -33153:65309, -33154:8800, -33155:65308, -33156:65310, -33157:8806, -33158:8807, -33159:8734, -33160:8756, -33161:9794, -33162:9792, -33163:176, -33164:8242, -33165:8243, -33166:8451, -33167:65509, -33168:65284, -33169:65504, -33170:65505, -33171:65285, -33172:65283, -33173:65286, -33174:65290, -33175:65312, -33176:167, -33177:9734, -33178:9733, -33179:9675, -33180:9679, -33181:9678, -33182:9671, -33183:9670, -33184:9633, -33185:9632, -33186:9651, -33187:9650, -33188:9661, -33189:9660, -33190:8251, -33191:12306, -33192:8594, -33193:8592, -33194:8593, -33195:8595, -33196:12307, -33208:8712, -33209:8715, -33210:8838, -33211:8839, -33212:8834, -33213:8835, -33214:8746, -33215:8745, -33224:8743, -33225:8744, -33226:65506, -33227:8658, -33228:8660, -33229:8704, -33230:8707, -33242:8736, -33243:8869, -33244:8978, -33245:8706, -33246:8711, -33247:8801, -33248:8786, -33249:8810, -33250:8811, -33251:8730, -33252:8765, -33253:8733, -33254:8757, -33255:8747, -33256:8748, -33264:8491, -33265:8240, -33266:9839, -33267:9837, -33268:9834, -33269:8224, -33270:8225, -33271:182, -33276:9711, -33359:65296, -33360:65297, -33361:65298, -33362:65299, -33363:65300, -33364:65301, -33365:65302, -33366:65303, -33367:65304, -33368:65305, -33376:65313, -33377:65314, -33378:65315, -33379:65316, -33380:65317, -33381:65318, -33382:65319, -33383:65320, -33384:65321, -33385:65322, -33386:65323, -33387:65324, -33388:65325, -33389:65326, -33390:65327, -33391:65328, -33392:65329, -33393:65330, -33394:65331, -33395:65332, -33396:65333, -33397:65334, -33398:65335, -33399:65336, -33400:65337, -33401:65338, -33409:65345, -33410:65346, -33411:65347, -33412:65348, -33413:65349, -33414:65350, -33415:65351, -33416:65352, -33417:65353, -33418:65354, -33419:65355, -33420:65356, -33421:65357, -33422:65358, -33423:65359, -33424:65360, -33425:65361, -33426:65362, -33427:65363, -33428:65364, -33429:65365, -33430:65366, -33431:65367, -33432:65368, -33433:65369, -33434:65370, -33439:12353, -33440:12354, -33441:12355, -33442:12356, -33443:12357, -33444:12358, -33445:12359, -33446:12360, -33447:12361, -33448:12362, -33449:12363, -33450:12364, -33451:12365, -33452:12366, -33453:12367, -33454:12368, -33455:12369, -33456:12370, -33457:12371, -33458:12372, -33459:12373, -33460:12374, -33461:12375, -33462:12376, -33463:12377, -33464:12378, -33465:12379, -33466:12380, -33467:12381, -33468:12382, -33469:12383, -33470:12384, -33471:12385, -33472:12386, -33473:12387, -33474:12388, -33475:12389, -33476:12390, -33477:12391, -33478:12392, -33479:12393, -33480:12394, -33481:12395, -33482:12396, -33483:12397, -33484:12398, -33485:12399, -33486:12400, -33487:12401, -33488:12402, -33489:12403, -33490:12404, -33491:12405, -33492:12406, -33493:12407, -33494:12408, -33495:12409, -33496:12410, -33497:12411, -33498:12412, -33499:12413, -33500:12414, -33501:12415, -33502:12416, -33503:12417, -33504:12418, -33505:12419, -33506:12420, -33507:12421, -33508:12422, -33509:12423, -33510:12424, -33511:12425, -33512:12426, -33513:12427, -33514:12428, -33515:12429, -33516:12430, -33517:12431, -33518:12432, -33519:12433, -33520:12434, -33521:12435, -33600:12449, -33601:12450, -33602:12451, -33603:12452, -33604:12453, -33605:12454, -33606:12455, -33607:12456, -33608:12457, -33609:12458, -33610:12459, -33611:12460, -33612:12461, -33613:12462, -33614:12463, -33615:12464, -33616:12465, -33617:12466, -33618:12467, -33619:12468, -33620:12469, -33621:12470, -33622:12471, -33623:12472, -33624:12473, -33625:12474, -33626:12475, -33627:12476, -33628:12477, -33629:12478, -33630:12479, -33631:12480, -33632:12481, -33633:12482, -33634:12483, -33635:12484, -33636:12485, -33637:12486, -33638:12487, -33639:12488, -33640:12489, -33641:12490, -33642:12491, -33643:12492, -33644:12493, -33645:12494, -33646:12495, -33647:12496, -33648:12497, -33649:12498, -33650:12499, -33651:12500, -33652:12501, -33653:12502, -33654:12503, -33655:12504, -33656:12505, -33657:12506, -33658:12507, -33659:12508, -33660:12509, -33661:12510, -33662:12511, -33664:12512, -33665:12513, -33666:12514, -33667:12515, -33668:12516, -33669:12517, -33670:12518, -33671:12519, -33672:12520, -33673:12521, -33674:12522, -33675:12523, -33676:12524, -33677:12525, -33678:12526, -33679:12527, -33680:12528, -33681:12529, -33682:12530, -33683:12531, -33684:12532, -33685:12533, -33686:12534, -33695:913, -33696:914, -33697:915, -33698:916, -33699:917, -33700:918, -33701:919, -33702:920, -33703:921, -33704:922, -33705:923, -33706:924, -33707:925, -33708:926, -33709:927, -33710:928, -33711:929, -33712:931, -33713:932, -33714:933, -33715:934, -33716:935, -33717:936, -33718:937, -33727:945, -33728:946, -33729:947, -33730:948, -33731:949, -33732:950, -33733:951, -33734:952, -33735:953, -33736:954, -33737:955, -33738:956, -33739:957, -33740:958, -33741:959, -33742:960, -33743:961, -33744:963, -33745:964, -33746:965, -33747:966, -33748:967, -33749:968, -33750:969, -33856:1040, -33857:1041, -33858:1042, -33859:1043, -33860:1044, -33861:1045, -33862:1025, -33863:1046, -33864:1047, -33865:1048, -33866:1049, -33867:1050, -33868:1051, -33869:1052, -33870:1053, -33871:1054, -33872:1055, -33873:1056, -33874:1057, -33875:1058, -33876:1059, -33877:1060, -33878:1061, -33879:1062, -33880:1063, -33881:1064, -33882:1065, -33883:1066, -33884:1067, -33885:1068, -33886:1069, -33887:1070, -33888:1071, -33904:1072, -33905:1073, -33906:1074, -33907:1075, -33908:1076, -33909:1077, -33910:1105, -33911:1078, -33912:1079, -33913:1080, -33914:1081, -33915:1082, -33916:1083, -33917:1084, -33918:1085, -33920:1086, -33921:1087, -33922:1088, -33923:1089, -33924:1090, -33925:1091, -33926:1092, -33927:1093, -33928:1094, -33929:1095, -33930:1096, -33931:1097, -33932:1098, -33933:1099, -33934:1100, -33935:1101, -33936:1102, -33937:1103, -33951:9472, -33952:9474, -33953:9484, -33954:9488, -33955:9496, -33956:9492, -33957:9500, -33958:9516, -33959:9508, -33960:9524, -33961:9532, -33962:9473, -33963:9475, -33964:9487, -33965:9491, -33966:9499, -33967:9495, -33968:9507, -33969:9523, -33970:9515, -33971:9531, -33972:9547, -33973:9504, -33974:9519, -33975:9512, -33976:9527, -33977:9535, -33978:9501, -33979:9520, -33980:9509, -33981:9528, -33982:9538, -34624:9312, -34625:9313, -34626:9314, -34627:9315, -34628:9316, -34629:9317, -34630:9318, -34631:9319, -34632:9320, -34633:9321, -34634:9322, -34635:9323, -34636:9324, -34637:9325, -34638:9326, -34639:9327, -34640:9328, -34641:9329, -34642:9330, -34643:9331, -34644:8544, -34645:8545, -34646:8546, -34647:8547, -34648:8548, -34649:8549, -34650:8550, -34651:8551, -34652:8552, -34653:8553, -34655:13129, -34656:13076, -34657:13090, -34658:13133, -34659:13080, -34660:13095, -34661:13059, -34662:13110, -34663:13137, -34664:13143, -34665:13069, -34666:13094, -34667:13091, -34668:13099, -34669:13130, -34670:13115, -34671:13212, -34672:13213, -34673:13214, -34674:13198, -34675:13199, -34676:13252, -34677:13217, -34686:13179, -34688:12317, -34689:12319, -34690:8470, -34691:13261, -34692:8481, -34693:12964, -34694:12965, -34695:12966, -34696:12967, -34697:12968, -34698:12849, -34699:12850, -34700:12857, -34701:13182, -34702:13181, -34703:13180, -34704:8786, -34705:8801, -34706:8747, -34707:8750, -34708:8721, -34709:8730, -34710:8869, -34711:8736, -34712:8735, -34713:8895, -34714:8757, -34715:8745, -34716:8746, -34975:20124, -34976:21782, -34977:23043, -34978:38463, -34979:21696, -34980:24859, -34981:25384, -34982:23030, -34983:36898, -34984:33909, -34985:33564, -34986:31312, -34987:24746, -34988:25569, -34989:28197, -34990:26093, -34991:33894, -34992:33446, -34993:39925, -34994:26771, -34995:22311, -34996:26017, -34997:25201, -34998:23451, -34999:22992, -35000:34427, -35001:39156, -35002:32098, -35003:32190, -35004:39822, -35005:25110, -35006:31903, -35007:34999, -35008:23433, -35009:24245, -35010:25353, -35011:26263, -35012:26696, -35013:38343, -35014:38797, -35015:26447, -35016:20197, -35017:20234, -35018:20301, -35019:20381, -35020:20553, -35021:22258, -35022:22839, -35023:22996, -35024:23041, -35025:23561, -35026:24799, -35027:24847, -35028:24944, -35029:26131, -35030:26885, -35031:28858, -35032:30031, -35033:30064, -35034:31227, -35035:32173, -35036:32239, -35037:32963, -35038:33806, -35039:34915, -35040:35586, -35041:36949, -35042:36986, -35043:21307, -35044:20117, -35045:20133, -35046:22495, -35047:32946, -35048:37057, -35049:30959, -35050:19968, -35051:22769, -35052:28322, -35053:36920, -35054:31282, -35055:33576, -35056:33419, -35057:39983, -35058:20801, -35059:21360, -35060:21693, -35061:21729, -35062:22240, -35063:23035, -35064:24341, -35065:39154, -35066:28139, -35067:32996, -35068:34093, -35136:38498, -35137:38512, -35138:38560, -35139:38907, -35140:21515, -35141:21491, -35142:23431, -35143:28879, -35144:32701, -35145:36802, -35146:38632, -35147:21359, -35148:40284, -35149:31418, -35150:19985, -35151:30867, -35152:33276, -35153:28198, -35154:22040, -35155:21764, -35156:27421, -35157:34074, -35158:39995, -35159:23013, -35160:21417, -35161:28006, -35162:29916, -35163:38287, -35164:22082, -35165:20113, -35166:36939, -35167:38642, -35168:33615, -35169:39180, -35170:21473, -35171:21942, -35172:23344, -35173:24433, -35174:26144, -35175:26355, -35176:26628, -35177:27704, -35178:27891, -35179:27945, -35180:29787, -35181:30408, -35182:31310, -35183:38964, -35184:33521, -35185:34907, -35186:35424, -35187:37613, -35188:28082, -35189:30123, -35190:30410, -35191:39365, -35192:24742, -35193:35585, -35194:36234, -35195:38322, -35196:27022, -35197:21421, -35198:20870, -35200:22290, -35201:22576, -35202:22852, -35203:23476, -35204:24310, -35205:24616, -35206:25513, -35207:25588, -35208:27839, -35209:28436, -35210:28814, -35211:28948, -35212:29017, -35213:29141, -35214:29503, -35215:32257, -35216:33398, -35217:33489, -35218:34199, -35219:36960, -35220:37467, -35221:40219, -35222:22633, -35223:26044, -35224:27738, -35225:29989, -35226:20985, -35227:22830, -35228:22885, -35229:24448, -35230:24540, -35231:25276, -35232:26106, -35233:27178, -35234:27431, -35235:27572, -35236:29579, -35237:32705, -35238:35158, -35239:40236, -35240:40206, -35241:40644, -35242:23713, -35243:27798, -35244:33659, -35245:20740, -35246:23627, -35247:25014, -35248:33222, -35249:26742, -35250:29281, -35251:20057, -35252:20474, -35253:21368, -35254:24681, -35255:28201, -35256:31311, -35257:38899, -35258:19979, -35259:21270, -35260:20206, -35261:20309, -35262:20285, -35263:20385, -35264:20339, -35265:21152, -35266:21487, -35267:22025, -35268:22799, -35269:23233, -35270:23478, -35271:23521, -35272:31185, -35273:26247, -35274:26524, -35275:26550, -35276:27468, -35277:27827, -35278:28779, -35279:29634, -35280:31117, -35281:31166, -35282:31292, -35283:31623, -35284:33457, -35285:33499, -35286:33540, -35287:33655, -35288:33775, -35289:33747, -35290:34662, -35291:35506, -35292:22057, -35293:36008, -35294:36838, -35295:36942, -35296:38686, -35297:34442, -35298:20420, -35299:23784, -35300:25105, -35301:29273, -35302:30011, -35303:33253, -35304:33469, -35305:34558, -35306:36032, -35307:38597, -35308:39187, -35309:39381, -35310:20171, -35311:20250, -35312:35299, -35313:22238, -35314:22602, -35315:22730, -35316:24315, -35317:24555, -35318:24618, -35319:24724, -35320:24674, -35321:25040, -35322:25106, -35323:25296, -35324:25913, -35392:39745, -35393:26214, -35394:26800, -35395:28023, -35396:28784, -35397:30028, -35398:30342, -35399:32117, -35400:33445, -35401:34809, -35402:38283, -35403:38542, -35404:35997, -35405:20977, -35406:21182, -35407:22806, -35408:21683, -35409:23475, -35410:23830, -35411:24936, -35412:27010, -35413:28079, -35414:30861, -35415:33995, -35416:34903, -35417:35442, -35418:37799, -35419:39608, -35420:28012, -35421:39336, -35422:34521, -35423:22435, -35424:26623, -35425:34510, -35426:37390, -35427:21123, -35428:22151, -35429:21508, -35430:24275, -35431:25313, -35432:25785, -35433:26684, -35434:26680, -35435:27579, -35436:29554, -35437:30906, -35438:31339, -35439:35226, -35440:35282, -35441:36203, -35442:36611, -35443:37101, -35444:38307, -35445:38548, -35446:38761, -35447:23398, -35448:23731, -35449:27005, -35450:38989, -35451:38990, -35452:25499, -35453:31520, -35454:27179, -35456:27263, -35457:26806, -35458:39949, -35459:28511, -35460:21106, -35461:21917, -35462:24688, -35463:25324, -35464:27963, -35465:28167, -35466:28369, -35467:33883, -35468:35088, -35469:36676, -35470:19988, -35471:39993, -35472:21494, -35473:26907, -35474:27194, -35475:38788, -35476:26666, -35477:20828, -35478:31427, -35479:33970, -35480:37340, -35481:37772, -35482:22107, -35483:40232, -35484:26658, -35485:33541, -35486:33841, -35487:31909, -35488:21000, -35489:33477, -35490:29926, -35491:20094, -35492:20355, -35493:20896, -35494:23506, -35495:21002, -35496:21208, -35497:21223, -35498:24059, -35499:21914, -35500:22570, -35501:23014, -35502:23436, -35503:23448, -35504:23515, -35505:24178, -35506:24185, -35507:24739, -35508:24863, -35509:24931, -35510:25022, -35511:25563, -35512:25954, -35513:26577, -35514:26707, -35515:26874, -35516:27454, -35517:27475, -35518:27735, -35519:28450, -35520:28567, -35521:28485, -35522:29872, -35523:29976, -35524:30435, -35525:30475, -35526:31487, -35527:31649, -35528:31777, -35529:32233, -35530:32566, -35531:32752, -35532:32925, -35533:33382, -35534:33694, -35535:35251, -35536:35532, -35537:36011, -35538:36996, -35539:37969, -35540:38291, -35541:38289, -35542:38306, -35543:38501, -35544:38867, -35545:39208, -35546:33304, -35547:20024, -35548:21547, -35549:23736, -35550:24012, -35551:29609, -35552:30284, -35553:30524, -35554:23721, -35555:32747, -35556:36107, -35557:38593, -35558:38929, -35559:38996, -35560:39000, -35561:20225, -35562:20238, -35563:21361, -35564:21916, -35565:22120, -35566:22522, -35567:22855, -35568:23305, -35569:23492, -35570:23696, -35571:24076, -35572:24190, -35573:24524, -35574:25582, -35575:26426, -35576:26071, -35577:26082, -35578:26399, -35579:26827, -35580:26820, -35648:27231, -35649:24112, -35650:27589, -35651:27671, -35652:27773, -35653:30079, -35654:31048, -35655:23395, -35656:31232, -35657:32000, -35658:24509, -35659:35215, -35660:35352, -35661:36020, -35662:36215, -35663:36556, -35664:36637, -35665:39138, -35666:39438, -35667:39740, -35668:20096, -35669:20605, -35670:20736, -35671:22931, -35672:23452, -35673:25135, -35674:25216, -35675:25836, -35676:27450, -35677:29344, -35678:30097, -35679:31047, -35680:32681, -35681:34811, -35682:35516, -35683:35696, -35684:25516, -35685:33738, -35686:38816, -35687:21513, -35688:21507, -35689:21931, -35690:26708, -35691:27224, -35692:35440, -35693:30759, -35694:26485, -35695:40653, -35696:21364, -35697:23458, -35698:33050, -35699:34384, -35700:36870, -35701:19992, -35702:20037, -35703:20167, -35704:20241, -35705:21450, -35706:21560, -35707:23470, -35708:24339, -35709:24613, -35710:25937, -35712:26429, -35713:27714, -35714:27762, -35715:27875, -35716:28792, -35717:29699, -35718:31350, -35719:31406, -35720:31496, -35721:32026, -35722:31998, -35723:32102, -35724:26087, -35725:29275, -35726:21435, -35727:23621, -35728:24040, -35729:25298, -35730:25312, -35731:25369, -35732:28192, -35733:34394, -35734:35377, -35735:36317, -35736:37624, -35737:28417, -35738:31142, -35739:39770, -35740:20136, -35741:20139, -35742:20140, -35743:20379, -35744:20384, -35745:20689, -35746:20807, -35747:31478, -35748:20849, -35749:20982, -35750:21332, -35751:21281, -35752:21375, -35753:21483, -35754:21932, -35755:22659, -35756:23777, -35757:24375, -35758:24394, -35759:24623, -35760:24656, -35761:24685, -35762:25375, -35763:25945, -35764:27211, -35765:27841, -35766:29378, -35767:29421, -35768:30703, -35769:33016, -35770:33029, -35771:33288, -35772:34126, -35773:37111, -35774:37857, -35775:38911, -35776:39255, -35777:39514, -35778:20208, -35779:20957, -35780:23597, -35781:26241, -35782:26989, -35783:23616, -35784:26354, -35785:26997, -35786:29577, -35787:26704, -35788:31873, -35789:20677, -35790:21220, -35791:22343, -35792:24062, -35793:37670, -35794:26020, -35795:27427, -35796:27453, -35797:29748, -35798:31105, -35799:31165, -35800:31563, -35801:32202, -35802:33465, -35803:33740, -35804:34943, -35805:35167, -35806:35641, -35807:36817, -35808:37329, -35809:21535, -35810:37504, -35811:20061, -35812:20534, -35813:21477, -35814:21306, -35815:29399, -35816:29590, -35817:30697, -35818:33510, -35819:36527, -35820:39366, -35821:39368, -35822:39378, -35823:20855, -35824:24858, -35825:34398, -35826:21936, -35827:31354, -35828:20598, -35829:23507, -35830:36935, -35831:38533, -35832:20018, -35833:27355, -35834:37351, -35835:23633, -35836:23624, -35904:25496, -35905:31391, -35906:27795, -35907:38772, -35908:36705, -35909:31402, -35910:29066, -35911:38536, -35912:31874, -35913:26647, -35914:32368, -35915:26705, -35916:37740, -35917:21234, -35918:21531, -35919:34219, -35920:35347, -35921:32676, -35922:36557, -35923:37089, -35924:21350, -35925:34952, -35926:31041, -35927:20418, -35928:20670, -35929:21009, -35930:20804, -35931:21843, -35932:22317, -35933:29674, -35934:22411, -35935:22865, -35936:24418, -35937:24452, -35938:24693, -35939:24950, -35940:24935, -35941:25001, -35942:25522, -35943:25658, -35944:25964, -35945:26223, -35946:26690, -35947:28179, -35948:30054, -35949:31293, -35950:31995, -35951:32076, -35952:32153, -35953:32331, -35954:32619, -35955:33550, -35956:33610, -35957:34509, -35958:35336, -35959:35427, -35960:35686, -35961:36605, -35962:38938, -35963:40335, -35964:33464, -35965:36814, -35966:39912, -35968:21127, -35969:25119, -35970:25731, -35971:28608, -35972:38553, -35973:26689, -35974:20625, -35975:27424, -35976:27770, -35977:28500, -35978:31348, -35979:32080, -35980:34880, -35981:35363, -35982:26376, -35983:20214, -35984:20537, -35985:20518, -35986:20581, -35987:20860, -35988:21048, -35989:21091, -35990:21927, -35991:22287, -35992:22533, -35993:23244, -35994:24314, -35995:25010, -35996:25080, -35997:25331, -35998:25458, -35999:26908, -36000:27177, -36001:29309, -36002:29356, -36003:29486, -36004:30740, -36005:30831, -36006:32121, -36007:30476, -36008:32937, -36009:35211, -36010:35609, -36011:36066, -36012:36562, -36013:36963, -36014:37749, -36015:38522, -36016:38997, -36017:39443, -36018:40568, -36019:20803, -36020:21407, -36021:21427, -36022:24187, -36023:24358, -36024:28187, -36025:28304, -36026:29572, -36027:29694, -36028:32067, -36029:33335, -36030:35328, -36031:35578, -36032:38480, -36033:20046, -36034:20491, -36035:21476, -36036:21628, -36037:22266, -36038:22993, -36039:23396, -36040:24049, -36041:24235, -36042:24359, -36043:25144, -36044:25925, -36045:26543, -36046:28246, -36047:29392, -36048:31946, -36049:34996, -36050:32929, -36051:32993, -36052:33776, -36053:34382, -36054:35463, -36055:36328, -36056:37431, -36057:38599, -36058:39015, -36059:40723, -36060:20116, -36061:20114, -36062:20237, -36063:21320, -36064:21577, -36065:21566, -36066:23087, -36067:24460, -36068:24481, -36069:24735, -36070:26791, -36071:27278, -36072:29786, -36073:30849, -36074:35486, -36075:35492, -36076:35703, -36077:37264, -36078:20062, -36079:39881, -36080:20132, -36081:20348, -36082:20399, -36083:20505, -36084:20502, -36085:20809, -36086:20844, -36087:21151, -36088:21177, -36089:21246, -36090:21402, -36091:21475, -36092:21521, -36160:21518, -36161:21897, -36162:22353, -36163:22434, -36164:22909, -36165:23380, -36166:23389, -36167:23439, -36168:24037, -36169:24039, -36170:24055, -36171:24184, -36172:24195, -36173:24218, -36174:24247, -36175:24344, -36176:24658, -36177:24908, -36178:25239, -36179:25304, -36180:25511, -36181:25915, -36182:26114, -36183:26179, -36184:26356, -36185:26477, -36186:26657, -36187:26775, -36188:27083, -36189:27743, -36190:27946, -36191:28009, -36192:28207, -36193:28317, -36194:30002, -36195:30343, -36196:30828, -36197:31295, -36198:31968, -36199:32005, -36200:32024, -36201:32094, -36202:32177, -36203:32789, -36204:32771, -36205:32943, -36206:32945, -36207:33108, -36208:33167, -36209:33322, -36210:33618, -36211:34892, -36212:34913, -36213:35611, -36214:36002, -36215:36092, -36216:37066, -36217:37237, -36218:37489, -36219:30783, -36220:37628, -36221:38308, -36222:38477, -36224:38917, -36225:39321, -36226:39640, -36227:40251, -36228:21083, -36229:21163, -36230:21495, -36231:21512, -36232:22741, -36233:25335, -36234:28640, -36235:35946, -36236:36703, -36237:40633, -36238:20811, -36239:21051, -36240:21578, -36241:22269, -36242:31296, -36243:37239, -36244:40288, -36245:40658, -36246:29508, -36247:28425, -36248:33136, -36249:29969, -36250:24573, -36251:24794, -36252:39592, -36253:29403, -36254:36796, -36255:27492, -36256:38915, -36257:20170, -36258:22256, -36259:22372, -36260:22718, -36261:23130, -36262:24680, -36263:25031, -36264:26127, -36265:26118, -36266:26681, -36267:26801, -36268:28151, -36269:30165, -36270:32058, -36271:33390, -36272:39746, -36273:20123, -36274:20304, -36275:21449, -36276:21766, -36277:23919, -36278:24038, -36279:24046, -36280:26619, -36281:27801, -36282:29811, -36283:30722, -36284:35408, -36285:37782, -36286:35039, -36287:22352, -36288:24231, -36289:25387, -36290:20661, -36291:20652, -36292:20877, -36293:26368, -36294:21705, -36295:22622, -36296:22971, -36297:23472, -36298:24425, -36299:25165, -36300:25505, -36301:26685, -36302:27507, -36303:28168, -36304:28797, -36305:37319, -36306:29312, -36307:30741, -36308:30758, -36309:31085, -36310:25998, -36311:32048, -36312:33756, -36313:35009, -36314:36617, -36315:38555, -36316:21092, -36317:22312, -36318:26448, -36319:32618, -36320:36001, -36321:20916, -36322:22338, -36323:38442, -36324:22586, -36325:27018, -36326:32948, -36327:21682, -36328:23822, -36329:22524, -36330:30869, -36331:40442, -36332:20316, -36333:21066, -36334:21643, -36335:25662, -36336:26152, -36337:26388, -36338:26613, -36339:31364, -36340:31574, -36341:32034, -36342:37679, -36343:26716, -36344:39853, -36345:31545, -36346:21273, -36347:20874, -36348:21047, -36416:23519, -36417:25334, -36418:25774, -36419:25830, -36420:26413, -36421:27578, -36422:34217, -36423:38609, -36424:30352, -36425:39894, -36426:25420, -36427:37638, -36428:39851, -36429:30399, -36430:26194, -36431:19977, -36432:20632, -36433:21442, -36434:23665, -36435:24808, -36436:25746, -36437:25955, -36438:26719, -36439:29158, -36440:29642, -36441:29987, -36442:31639, -36443:32386, -36444:34453, -36445:35715, -36446:36059, -36447:37240, -36448:39184, -36449:26028, -36450:26283, -36451:27531, -36452:20181, -36453:20180, -36454:20282, -36455:20351, -36456:21050, -36457:21496, -36458:21490, -36459:21987, -36460:22235, -36461:22763, -36462:22987, -36463:22985, -36464:23039, -36465:23376, -36466:23629, -36467:24066, -36468:24107, -36469:24535, -36470:24605, -36471:25351, -36472:25903, -36473:23388, -36474:26031, -36475:26045, -36476:26088, -36477:26525, -36478:27490, -36480:27515, -36481:27663, -36482:29509, -36483:31049, -36484:31169, -36485:31992, -36486:32025, -36487:32043, -36488:32930, -36489:33026, -36490:33267, -36491:35222, -36492:35422, -36493:35433, -36494:35430, -36495:35468, -36496:35566, -36497:36039, -36498:36060, -36499:38604, -36500:39164, -36501:27503, -36502:20107, -36503:20284, -36504:20365, -36505:20816, -36506:23383, -36507:23546, -36508:24904, -36509:25345, -36510:26178, -36511:27425, -36512:28363, -36513:27835, -36514:29246, -36515:29885, -36516:30164, -36517:30913, -36518:31034, -36519:32780, -36520:32819, -36521:33258, -36522:33940, -36523:36766, -36524:27728, -36525:40575, -36526:24335, -36527:35672, -36528:40235, -36529:31482, -36530:36600, -36531:23437, -36532:38635, -36533:19971, -36534:21489, -36535:22519, -36536:22833, -36537:23241, -36538:23460, -36539:24713, -36540:28287, -36541:28422, -36542:30142, -36543:36074, -36544:23455, -36545:34048, -36546:31712, -36547:20594, -36548:26612, -36549:33437, -36550:23649, -36551:34122, -36552:32286, -36553:33294, -36554:20889, -36555:23556, -36556:25448, -36557:36198, -36558:26012, -36559:29038, -36560:31038, -36561:32023, -36562:32773, -36563:35613, -36564:36554, -36565:36974, -36566:34503, -36567:37034, -36568:20511, -36569:21242, -36570:23610, -36571:26451, -36572:28796, -36573:29237, -36574:37196, -36575:37320, -36576:37675, -36577:33509, -36578:23490, -36579:24369, -36580:24825, -36581:20027, -36582:21462, -36583:23432, -36584:25163, -36585:26417, -36586:27530, -36587:29417, -36588:29664, -36589:31278, -36590:33131, -36591:36259, -36592:37202, -36593:39318, -36594:20754, -36595:21463, -36596:21610, -36597:23551, -36598:25480, -36599:27193, -36600:32172, -36601:38656, -36602:22234, -36603:21454, -36604:21608, -36672:23447, -36673:23601, -36674:24030, -36675:20462, -36676:24833, -36677:25342, -36678:27954, -36679:31168, -36680:31179, -36681:32066, -36682:32333, -36683:32722, -36684:33261, -36685:33311, -36686:33936, -36687:34886, -36688:35186, -36689:35728, -36690:36468, -36691:36655, -36692:36913, -36693:37195, -36694:37228, -36695:38598, -36696:37276, -36697:20160, -36698:20303, -36699:20805, -36700:21313, -36701:24467, -36702:25102, -36703:26580, -36704:27713, -36705:28171, -36706:29539, -36707:32294, -36708:37325, -36709:37507, -36710:21460, -36711:22809, -36712:23487, -36713:28113, -36714:31069, -36715:32302, -36716:31899, -36717:22654, -36718:29087, -36719:20986, -36720:34899, -36721:36848, -36722:20426, -36723:23803, -36724:26149, -36725:30636, -36726:31459, -36727:33308, -36728:39423, -36729:20934, -36730:24490, -36731:26092, -36732:26991, -36733:27529, -36734:28147, -36736:28310, -36737:28516, -36738:30462, -36739:32020, -36740:24033, -36741:36981, -36742:37255, -36743:38918, -36744:20966, -36745:21021, -36746:25152, -36747:26257, -36748:26329, -36749:28186, -36750:24246, -36751:32210, -36752:32626, -36753:26360, -36754:34223, -36755:34295, -36756:35576, -36757:21161, -36758:21465, -36759:22899, -36760:24207, -36761:24464, -36762:24661, -36763:37604, -36764:38500, -36765:20663, -36766:20767, -36767:21213, -36768:21280, -36769:21319, -36770:21484, -36771:21736, -36772:21830, -36773:21809, -36774:22039, -36775:22888, -36776:22974, -36777:23100, -36778:23477, -36779:23558, -36780:23567, -36781:23569, -36782:23578, -36783:24196, -36784:24202, -36785:24288, -36786:24432, -36787:25215, -36788:25220, -36789:25307, -36790:25484, -36791:25463, -36792:26119, -36793:26124, -36794:26157, -36795:26230, -36796:26494, -36797:26786, -36798:27167, -36799:27189, -36800:27836, -36801:28040, -36802:28169, -36803:28248, -36804:28988, -36805:28966, -36806:29031, -36807:30151, -36808:30465, -36809:30813, -36810:30977, -36811:31077, -36812:31216, -36813:31456, -36814:31505, -36815:31911, -36816:32057, -36817:32918, -36818:33750, -36819:33931, -36820:34121, -36821:34909, -36822:35059, -36823:35359, -36824:35388, -36825:35412, -36826:35443, -36827:35937, -36828:36062, -36829:37284, -36830:37478, -36831:37758, -36832:37912, -36833:38556, -36834:38808, -36835:19978, -36836:19976, -36837:19998, -36838:20055, -36839:20887, -36840:21104, -36841:22478, -36842:22580, -36843:22732, -36844:23330, -36845:24120, -36846:24773, -36847:25854, -36848:26465, -36849:26454, -36850:27972, -36851:29366, -36852:30067, -36853:31331, -36854:33976, -36855:35698, -36856:37304, -36857:37664, -36858:22065, -36859:22516, -36860:39166, -36928:25325, -36929:26893, -36930:27542, -36931:29165, -36932:32340, -36933:32887, -36934:33394, -36935:35302, -36936:39135, -36937:34645, -36938:36785, -36939:23611, -36940:20280, -36941:20449, -36942:20405, -36943:21767, -36944:23072, -36945:23517, -36946:23529, -36947:24515, -36948:24910, -36949:25391, -36950:26032, -36951:26187, -36952:26862, -36953:27035, -36954:28024, -36955:28145, -36956:30003, -36957:30137, -36958:30495, -36959:31070, -36960:31206, -36961:32051, -36962:33251, -36963:33455, -36964:34218, -36965:35242, -36966:35386, -36967:36523, -36968:36763, -36969:36914, -36970:37341, -36971:38663, -36972:20154, -36973:20161, -36974:20995, -36975:22645, -36976:22764, -36977:23563, -36978:29978, -36979:23613, -36980:33102, -36981:35338, -36982:36805, -36983:38499, -36984:38765, -36985:31525, -36986:35535, -36987:38920, -36988:37218, -36989:22259, -36990:21416, -36992:36887, -36993:21561, -36994:22402, -36995:24101, -36996:25512, -36997:27700, -36998:28810, -36999:30561, -37000:31883, -37001:32736, -37002:34928, -37003:36930, -37004:37204, -37005:37648, -37006:37656, -37007:38543, -37008:29790, -37009:39620, -37010:23815, -37011:23913, -37012:25968, -37013:26530, -37014:36264, -37015:38619, -37016:25454, -37017:26441, -37018:26905, -37019:33733, -37020:38935, -37021:38592, -37022:35070, -37023:28548, -37024:25722, -37025:23544, -37026:19990, -37027:28716, -37028:30045, -37029:26159, -37030:20932, -37031:21046, -37032:21218, -37033:22995, -37034:24449, -37035:24615, -37036:25104, -37037:25919, -37038:25972, -37039:26143, -37040:26228, -37041:26866, -37042:26646, -37043:27491, -37044:28165, -37045:29298, -37046:29983, -37047:30427, -37048:31934, -37049:32854, -37050:22768, -37051:35069, -37052:35199, -37053:35488, -37054:35475, -37055:35531, -37056:36893, -37057:37266, -37058:38738, -37059:38745, -37060:25993, -37061:31246, -37062:33030, -37063:38587, -37064:24109, -37065:24796, -37066:25114, -37067:26021, -37068:26132, -37069:26512, -37070:30707, -37071:31309, -37072:31821, -37073:32318, -37074:33034, -37075:36012, -37076:36196, -37077:36321, -37078:36447, -37079:30889, -37080:20999, -37081:25305, -37082:25509, -37083:25666, -37084:25240, -37085:35373, -37086:31363, -37087:31680, -37088:35500, -37089:38634, -37090:32118, -37091:33292, -37092:34633, -37093:20185, -37094:20808, -37095:21315, -37096:21344, -37097:23459, -37098:23554, -37099:23574, -37100:24029, -37101:25126, -37102:25159, -37103:25776, -37104:26643, -37105:26676, -37106:27849, -37107:27973, -37108:27927, -37109:26579, -37110:28508, -37111:29006, -37112:29053, -37113:26059, -37114:31359, -37115:31661, -37116:32218, -37184:32330, -37185:32680, -37186:33146, -37187:33307, -37188:33337, -37189:34214, -37190:35438, -37191:36046, -37192:36341, -37193:36984, -37194:36983, -37195:37549, -37196:37521, -37197:38275, -37198:39854, -37199:21069, -37200:21892, -37201:28472, -37202:28982, -37203:20840, -37204:31109, -37205:32341, -37206:33203, -37207:31950, -37208:22092, -37209:22609, -37210:23720, -37211:25514, -37212:26366, -37213:26365, -37214:26970, -37215:29401, -37216:30095, -37217:30094, -37218:30990, -37219:31062, -37220:31199, -37221:31895, -37222:32032, -37223:32068, -37224:34311, -37225:35380, -37226:38459, -37227:36961, -37228:40736, -37229:20711, -37230:21109, -37231:21452, -37232:21474, -37233:20489, -37234:21930, -37235:22766, -37236:22863, -37237:29245, -37238:23435, -37239:23652, -37240:21277, -37241:24803, -37242:24819, -37243:25436, -37244:25475, -37245:25407, -37246:25531, -37248:25805, -37249:26089, -37250:26361, -37251:24035, -37252:27085, -37253:27133, -37254:28437, -37255:29157, -37256:20105, -37257:30185, -37258:30456, -37259:31379, -37260:31967, -37261:32207, -37262:32156, -37263:32865, -37264:33609, -37265:33624, -37266:33900, -37267:33980, -37268:34299, -37269:35013, -37270:36208, -37271:36865, -37272:36973, -37273:37783, -37274:38684, -37275:39442, -37276:20687, -37277:22679, -37278:24974, -37279:33235, -37280:34101, -37281:36104, -37282:36896, -37283:20419, -37284:20596, -37285:21063, -37286:21363, -37287:24687, -37288:25417, -37289:26463, -37290:28204, -37291:36275, -37292:36895, -37293:20439, -37294:23646, -37295:36042, -37296:26063, -37297:32154, -37298:21330, -37299:34966, -37300:20854, -37301:25539, -37302:23384, -37303:23403, -37304:23562, -37305:25613, -37306:26449, -37307:36956, -37308:20182, -37309:22810, -37310:22826, -37311:27760, -37312:35409, -37313:21822, -37314:22549, -37315:22949, -37316:24816, -37317:25171, -37318:26561, -37319:33333, -37320:26965, -37321:38464, -37322:39364, -37323:39464, -37324:20307, -37325:22534, -37326:23550, -37327:32784, -37328:23729, -37329:24111, -37330:24453, -37331:24608, -37332:24907, -37333:25140, -37334:26367, -37335:27888, -37336:28382, -37337:32974, -37338:33151, -37339:33492, -37340:34955, -37341:36024, -37342:36864, -37343:36910, -37344:38538, -37345:40667, -37346:39899, -37347:20195, -37348:21488, -37349:22823, -37350:31532, -37351:37261, -37352:38988, -37353:40441, -37354:28381, -37355:28711, -37356:21331, -37357:21828, -37358:23429, -37359:25176, -37360:25246, -37361:25299, -37362:27810, -37363:28655, -37364:29730, -37365:35351, -37366:37944, -37367:28609, -37368:35582, -37369:33592, -37370:20967, -37371:34552, -37372:21482, -37440:21481, -37441:20294, -37442:36948, -37443:36784, -37444:22890, -37445:33073, -37446:24061, -37447:31466, -37448:36799, -37449:26842, -37450:35895, -37451:29432, -37452:40008, -37453:27197, -37454:35504, -37455:20025, -37456:21336, -37457:22022, -37458:22374, -37459:25285, -37460:25506, -37461:26086, -37462:27470, -37463:28129, -37464:28251, -37465:28845, -37466:30701, -37467:31471, -37468:31658, -37469:32187, -37470:32829, -37471:32966, -37472:34507, -37473:35477, -37474:37723, -37475:22243, -37476:22727, -37477:24382, -37478:26029, -37479:26262, -37480:27264, -37481:27573, -37482:30007, -37483:35527, -37484:20516, -37485:30693, -37486:22320, -37487:24347, -37488:24677, -37489:26234, -37490:27744, -37491:30196, -37492:31258, -37493:32622, -37494:33268, -37495:34584, -37496:36933, -37497:39347, -37498:31689, -37499:30044, -37500:31481, -37501:31569, -37502:33988, -37504:36880, -37505:31209, -37506:31378, -37507:33590, -37508:23265, -37509:30528, -37510:20013, -37511:20210, -37512:23449, -37513:24544, -37514:25277, -37515:26172, -37516:26609, -37517:27880, -37518:34411, -37519:34935, -37520:35387, -37521:37198, -37522:37619, -37523:39376, -37524:27159, -37525:28710, -37526:29482, -37527:33511, -37528:33879, -37529:36015, -37530:19969, -37531:20806, -37532:20939, -37533:21899, -37534:23541, -37535:24086, -37536:24115, -37537:24193, -37538:24340, -37539:24373, -37540:24427, -37541:24500, -37542:25074, -37543:25361, -37544:26274, -37545:26397, -37546:28526, -37547:29266, -37548:30010, -37549:30522, -37550:32884, -37551:33081, -37552:33144, -37553:34678, -37554:35519, -37555:35548, -37556:36229, -37557:36339, -37558:37530, -37559:38263, -37560:38914, -37561:40165, -37562:21189, -37563:25431, -37564:30452, -37565:26389, -37566:27784, -37567:29645, -37568:36035, -37569:37806, -37570:38515, -37571:27941, -37572:22684, -37573:26894, -37574:27084, -37575:36861, -37576:37786, -37577:30171, -37578:36890, -37579:22618, -37580:26626, -37581:25524, -37582:27131, -37583:20291, -37584:28460, -37585:26584, -37586:36795, -37587:34086, -37588:32180, -37589:37716, -37590:26943, -37591:28528, -37592:22378, -37593:22775, -37594:23340, -37595:32044, -37596:29226, -37597:21514, -37598:37347, -37599:40372, -37600:20141, -37601:20302, -37602:20572, -37603:20597, -37604:21059, -37605:35998, -37606:21576, -37607:22564, -37608:23450, -37609:24093, -37610:24213, -37611:24237, -37612:24311, -37613:24351, -37614:24716, -37615:25269, -37616:25402, -37617:25552, -37618:26799, -37619:27712, -37620:30855, -37621:31118, -37622:31243, -37623:32224, -37624:33351, -37625:35330, -37626:35558, -37627:36420, -37628:36883, -37696:37048, -37697:37165, -37698:37336, -37699:40718, -37700:27877, -37701:25688, -37702:25826, -37703:25973, -37704:28404, -37705:30340, -37706:31515, -37707:36969, -37708:37841, -37709:28346, -37710:21746, -37711:24505, -37712:25764, -37713:36685, -37714:36845, -37715:37444, -37716:20856, -37717:22635, -37718:22825, -37719:23637, -37720:24215, -37721:28155, -37722:32399, -37723:29980, -37724:36028, -37725:36578, -37726:39003, -37727:28857, -37728:20253, -37729:27583, -37730:28593, -37731:30000, -37732:38651, -37733:20814, -37734:21520, -37735:22581, -37736:22615, -37737:22956, -37738:23648, -37739:24466, -37740:26007, -37741:26460, -37742:28193, -37743:30331, -37744:33759, -37745:36077, -37746:36884, -37747:37117, -37748:37709, -37749:30757, -37750:30778, -37751:21162, -37752:24230, -37753:22303, -37754:22900, -37755:24594, -37756:20498, -37757:20826, -37758:20908, -37760:20941, -37761:20992, -37762:21776, -37763:22612, -37764:22616, -37765:22871, -37766:23445, -37767:23798, -37768:23947, -37769:24764, -37770:25237, -37771:25645, -37772:26481, -37773:26691, -37774:26812, -37775:26847, -37776:30423, -37777:28120, -37778:28271, -37779:28059, -37780:28783, -37781:29128, -37782:24403, -37783:30168, -37784:31095, -37785:31561, -37786:31572, -37787:31570, -37788:31958, -37789:32113, -37790:21040, -37791:33891, -37792:34153, -37793:34276, -37794:35342, -37795:35588, -37796:35910, -37797:36367, -37798:36867, -37799:36879, -37800:37913, -37801:38518, -37802:38957, -37803:39472, -37804:38360, -37805:20685, -37806:21205, -37807:21516, -37808:22530, -37809:23566, -37810:24999, -37811:25758, -37812:27934, -37813:30643, -37814:31461, -37815:33012, -37816:33796, -37817:36947, -37818:37509, -37819:23776, -37820:40199, -37821:21311, -37822:24471, -37823:24499, -37824:28060, -37825:29305, -37826:30563, -37827:31167, -37828:31716, -37829:27602, -37830:29420, -37831:35501, -37832:26627, -37833:27233, -37834:20984, -37835:31361, -37836:26932, -37837:23626, -37838:40182, -37839:33515, -37840:23493, -37841:37193, -37842:28702, -37843:22136, -37844:23663, -37845:24775, -37846:25958, -37847:27788, -37848:35930, -37849:36929, -37850:38931, -37851:21585, -37852:26311, -37853:37389, -37854:22856, -37855:37027, -37856:20869, -37857:20045, -37858:20970, -37859:34201, -37860:35598, -37861:28760, -37862:25466, -37863:37707, -37864:26978, -37865:39348, -37866:32260, -37867:30071, -37868:21335, -37869:26976, -37870:36575, -37871:38627, -37872:27741, -37873:20108, -37874:23612, -37875:24336, -37876:36841, -37877:21250, -37878:36049, -37879:32905, -37880:34425, -37881:24319, -37882:26085, -37883:20083, -37884:20837, -37952:22914, -37953:23615, -37954:38894, -37955:20219, -37956:22922, -37957:24525, -37958:35469, -37959:28641, -37960:31152, -37961:31074, -37962:23527, -37963:33905, -37964:29483, -37965:29105, -37966:24180, -37967:24565, -37968:25467, -37969:25754, -37970:29123, -37971:31896, -37972:20035, -37973:24316, -37974:20043, -37975:22492, -37976:22178, -37977:24745, -37978:28611, -37979:32013, -37980:33021, -37981:33075, -37982:33215, -37983:36786, -37984:35223, -37985:34468, -37986:24052, -37987:25226, -37988:25773, -37989:35207, -37990:26487, -37991:27874, -37992:27966, -37993:29750, -37994:30772, -37995:23110, -37996:32629, -37997:33453, -37998:39340, -37999:20467, -38000:24259, -38001:25309, -38002:25490, -38003:25943, -38004:26479, -38005:30403, -38006:29260, -38007:32972, -38008:32954, -38009:36649, -38010:37197, -38011:20493, -38012:22521, -38013:23186, -38014:26757, -38016:26995, -38017:29028, -38018:29437, -38019:36023, -38020:22770, -38021:36064, -38022:38506, -38023:36889, -38024:34687, -38025:31204, -38026:30695, -38027:33833, -38028:20271, -38029:21093, -38030:21338, -38031:25293, -38032:26575, -38033:27850, -38034:30333, -38035:31636, -38036:31893, -38037:33334, -38038:34180, -38039:36843, -38040:26333, -38041:28448, -38042:29190, -38043:32283, -38044:33707, -38045:39361, -38046:40614, -38047:20989, -38048:31665, -38049:30834, -38050:31672, -38051:32903, -38052:31560, -38053:27368, -38054:24161, -38055:32908, -38056:30033, -38057:30048, -38058:20843, -38059:37474, -38060:28300, -38061:30330, -38062:37271, -38063:39658, -38064:20240, -38065:32624, -38066:25244, -38067:31567, -38068:38309, -38069:40169, -38070:22138, -38071:22617, -38072:34532, -38073:38588, -38074:20276, -38075:21028, -38076:21322, -38077:21453, -38078:21467, -38079:24070, -38080:25644, -38081:26001, -38082:26495, -38083:27710, -38084:27726, -38085:29256, -38086:29359, -38087:29677, -38088:30036, -38089:32321, -38090:33324, -38091:34281, -38092:36009, -38093:31684, -38094:37318, -38095:29033, -38096:38930, -38097:39151, -38098:25405, -38099:26217, -38100:30058, -38101:30436, -38102:30928, -38103:34115, -38104:34542, -38105:21290, -38106:21329, -38107:21542, -38108:22915, -38109:24199, -38110:24444, -38111:24754, -38112:25161, -38113:25209, -38114:25259, -38115:26000, -38116:27604, -38117:27852, -38118:30130, -38119:30382, -38120:30865, -38121:31192, -38122:32203, -38123:32631, -38124:32933, -38125:34987, -38126:35513, -38127:36027, -38128:36991, -38129:38750, -38130:39131, -38131:27147, -38132:31800, -38133:20633, -38134:23614, -38135:24494, -38136:26503, -38137:27608, -38138:29749, -38139:30473, -38140:32654, -38208:40763, -38209:26570, -38210:31255, -38211:21305, -38212:30091, -38213:39661, -38214:24422, -38215:33181, -38216:33777, -38217:32920, -38218:24380, -38219:24517, -38220:30050, -38221:31558, -38222:36924, -38223:26727, -38224:23019, -38225:23195, -38226:32016, -38227:30334, -38228:35628, -38229:20469, -38230:24426, -38231:27161, -38232:27703, -38233:28418, -38234:29922, -38235:31080, -38236:34920, -38237:35413, -38238:35961, -38239:24287, -38240:25551, -38241:30149, -38242:31186, -38243:33495, -38244:37672, -38245:37618, -38246:33948, -38247:34541, -38248:39981, -38249:21697, -38250:24428, -38251:25996, -38252:27996, -38253:28693, -38254:36007, -38255:36051, -38256:38971, -38257:25935, -38258:29942, -38259:19981, -38260:20184, -38261:22496, -38262:22827, -38263:23142, -38264:23500, -38265:20904, -38266:24067, -38267:24220, -38268:24598, -38269:25206, -38270:25975, -38272:26023, -38273:26222, -38274:28014, -38275:29238, -38276:31526, -38277:33104, -38278:33178, -38279:33433, -38280:35676, -38281:36000, -38282:36070, -38283:36212, -38284:38428, -38285:38468, -38286:20398, -38287:25771, -38288:27494, -38289:33310, -38290:33889, -38291:34154, -38292:37096, -38293:23553, -38294:26963, -38295:39080, -38296:33914, -38297:34135, -38298:20239, -38299:21103, -38300:24489, -38301:24133, -38302:26381, -38303:31119, -38304:33145, -38305:35079, -38306:35206, -38307:28149, -38308:24343, -38309:25173, -38310:27832, -38311:20175, -38312:29289, -38313:39826, -38314:20998, -38315:21563, -38316:22132, -38317:22707, -38318:24996, -38319:25198, -38320:28954, -38321:22894, -38322:31881, -38323:31966, -38324:32027, -38325:38640, -38326:25991, -38327:32862, -38328:19993, -38329:20341, -38330:20853, -38331:22592, -38332:24163, -38333:24179, -38334:24330, -38335:26564, -38336:20006, -38337:34109, -38338:38281, -38339:38491, -38340:31859, -38341:38913, -38342:20731, -38343:22721, -38344:30294, -38345:30887, -38346:21029, -38347:30629, -38348:34065, -38349:31622, -38350:20559, -38351:22793, -38352:29255, -38353:31687, -38354:32232, -38355:36794, -38356:36820, -38357:36941, -38358:20415, -38359:21193, -38360:23081, -38361:24321, -38362:38829, -38363:20445, -38364:33303, -38365:37610, -38366:22275, -38367:25429, -38368:27497, -38369:29995, -38370:35036, -38371:36628, -38372:31298, -38373:21215, -38374:22675, -38375:24917, -38376:25098, -38377:26286, -38378:27597, -38379:31807, -38380:33769, -38381:20515, -38382:20472, -38383:21253, -38384:21574, -38385:22577, -38386:22857, -38387:23453, -38388:23792, -38389:23791, -38390:23849, -38391:24214, -38392:25265, -38393:25447, -38394:25918, -38395:26041, -38396:26379, -38464:27861, -38465:27873, -38466:28921, -38467:30770, -38468:32299, -38469:32990, -38470:33459, -38471:33804, -38472:34028, -38473:34562, -38474:35090, -38475:35370, -38476:35914, -38477:37030, -38478:37586, -38479:39165, -38480:40179, -38481:40300, -38482:20047, -38483:20129, -38484:20621, -38485:21078, -38486:22346, -38487:22952, -38488:24125, -38489:24536, -38490:24537, -38491:25151, -38492:26292, -38493:26395, -38494:26576, -38495:26834, -38496:20882, -38497:32033, -38498:32938, -38499:33192, -38500:35584, -38501:35980, -38502:36031, -38503:37502, -38504:38450, -38505:21536, -38506:38956, -38507:21271, -38508:20693, -38509:21340, -38510:22696, -38511:25778, -38512:26420, -38513:29287, -38514:30566, -38515:31302, -38516:37350, -38517:21187, -38518:27809, -38519:27526, -38520:22528, -38521:24140, -38522:22868, -38523:26412, -38524:32763, -38525:20961, -38526:30406, -38528:25705, -38529:30952, -38530:39764, -38531:40635, -38532:22475, -38533:22969, -38534:26151, -38535:26522, -38536:27598, -38537:21737, -38538:27097, -38539:24149, -38540:33180, -38541:26517, -38542:39850, -38543:26622, -38544:40018, -38545:26717, -38546:20134, -38547:20451, -38548:21448, -38549:25273, -38550:26411, -38551:27819, -38552:36804, -38553:20397, -38554:32365, -38555:40639, -38556:19975, -38557:24930, -38558:28288, -38559:28459, -38560:34067, -38561:21619, -38562:26410, -38563:39749, -38564:24051, -38565:31637, -38566:23724, -38567:23494, -38568:34588, -38569:28234, -38570:34001, -38571:31252, -38572:33032, -38573:22937, -38574:31885, -38575:27665, -38576:30496, -38577:21209, -38578:22818, -38579:28961, -38580:29279, -38581:30683, -38582:38695, -38583:40289, -38584:26891, -38585:23167, -38586:23064, -38587:20901, -38588:21517, -38589:21629, -38590:26126, -38591:30431, -38592:36855, -38593:37528, -38594:40180, -38595:23018, -38596:29277, -38597:28357, -38598:20813, -38599:26825, -38600:32191, -38601:32236, -38602:38754, -38603:40634, -38604:25720, -38605:27169, -38606:33538, -38607:22916, -38608:23391, -38609:27611, -38610:29467, -38611:30450, -38612:32178, -38613:32791, -38614:33945, -38615:20786, -38616:26408, -38617:40665, -38618:30446, -38619:26466, -38620:21247, -38621:39173, -38622:23588, -38623:25147, -38624:31870, -38625:36016, -38626:21839, -38627:24758, -38628:32011, -38629:38272, -38630:21249, -38631:20063, -38632:20918, -38633:22812, -38634:29242, -38635:32822, -38636:37326, -38637:24357, -38638:30690, -38639:21380, -38640:24441, -38641:32004, -38642:34220, -38643:35379, -38644:36493, -38645:38742, -38646:26611, -38647:34222, -38648:37971, -38649:24841, -38650:24840, -38651:27833, -38652:30290, -38720:35565, -38721:36664, -38722:21807, -38723:20305, -38724:20778, -38725:21191, -38726:21451, -38727:23461, -38728:24189, -38729:24736, -38730:24962, -38731:25558, -38732:26377, -38733:26586, -38734:28263, -38735:28044, -38736:29494, -38737:29495, -38738:30001, -38739:31056, -38740:35029, -38741:35480, -38742:36938, -38743:37009, -38744:37109, -38745:38596, -38746:34701, -38747:22805, -38748:20104, -38749:20313, -38750:19982, -38751:35465, -38752:36671, -38753:38928, -38754:20653, -38755:24188, -38756:22934, -38757:23481, -38758:24248, -38759:25562, -38760:25594, -38761:25793, -38762:26332, -38763:26954, -38764:27096, -38765:27915, -38766:28342, -38767:29076, -38768:29992, -38769:31407, -38770:32650, -38771:32768, -38772:33865, -38773:33993, -38774:35201, -38775:35617, -38776:36362, -38777:36965, -38778:38525, -38779:39178, -38780:24958, -38781:25233, -38782:27442, -38784:27779, -38785:28020, -38786:32716, -38787:32764, -38788:28096, -38789:32645, -38790:34746, -38791:35064, -38792:26469, -38793:33713, -38794:38972, -38795:38647, -38796:27931, -38797:32097, -38798:33853, -38799:37226, -38800:20081, -38801:21365, -38802:23888, -38803:27396, -38804:28651, -38805:34253, -38806:34349, -38807:35239, -38808:21033, -38809:21519, -38810:23653, -38811:26446, -38812:26792, -38813:29702, -38814:29827, -38815:30178, -38816:35023, -38817:35041, -38818:37324, -38819:38626, -38820:38520, -38821:24459, -38822:29575, -38823:31435, -38824:33870, -38825:25504, -38826:30053, -38827:21129, -38828:27969, -38829:28316, -38830:29705, -38831:30041, -38832:30827, -38833:31890, -38834:38534, -38835:31452, -38836:40845, -38837:20406, -38838:24942, -38839:26053, -38840:34396, -38841:20102, -38842:20142, -38843:20698, -38844:20001, -38845:20940, -38846:23534, -38847:26009, -38848:26753, -38849:28092, -38850:29471, -38851:30274, -38852:30637, -38853:31260, -38854:31975, -38855:33391, -38856:35538, -38857:36988, -38858:37327, -38859:38517, -38860:38936, -38861:21147, -38862:32209, -38863:20523, -38864:21400, -38865:26519, -38866:28107, -38867:29136, -38868:29747, -38869:33256, -38870:36650, -38871:38563, -38872:40023, -38873:40607, -38874:29792, -38875:22593, -38876:28057, -38877:32047, -38878:39006, -38879:20196, -38880:20278, -38881:20363, -38882:20919, -38883:21169, -38884:23994, -38885:24604, -38886:29618, -38887:31036, -38888:33491, -38889:37428, -38890:38583, -38891:38646, -38892:38666, -38893:40599, -38894:40802, -38895:26278, -38896:27508, -38897:21015, -38898:21155, -38899:28872, -38900:35010, -38901:24265, -38902:24651, -38903:24976, -38904:28451, -38905:29001, -38906:31806, -38907:32244, -38908:32879, -38976:34030, -38977:36899, -38978:37676, -38979:21570, -38980:39791, -38981:27347, -38982:28809, -38983:36034, -38984:36335, -38985:38706, -38986:21172, -38987:23105, -38988:24266, -38989:24324, -38990:26391, -38991:27004, -38992:27028, -38993:28010, -38994:28431, -38995:29282, -38996:29436, -38997:31725, -38998:32769, -38999:32894, -39000:34635, -39001:37070, -39002:20845, -39003:40595, -39004:31108, -39005:32907, -39006:37682, -39007:35542, -39008:20525, -39009:21644, -39010:35441, -39011:27498, -39012:36036, -39013:33031, -39014:24785, -39015:26528, -39016:40434, -39017:20121, -39018:20120, -39019:39952, -39020:35435, -39021:34241, -39022:34152, -39023:26880, -39024:28286, -39025:30871, -39026:33109, -39071:24332, -39072:19984, -39073:19989, -39074:20010, -39075:20017, -39076:20022, -39077:20028, -39078:20031, -39079:20034, -39080:20054, -39081:20056, -39082:20098, -39083:20101, -39084:35947, -39085:20106, -39086:33298, -39087:24333, -39088:20110, -39089:20126, -39090:20127, -39091:20128, -39092:20130, -39093:20144, -39094:20147, -39095:20150, -39096:20174, -39097:20173, -39098:20164, -39099:20166, -39100:20162, -39101:20183, -39102:20190, -39103:20205, -39104:20191, -39105:20215, -39106:20233, -39107:20314, -39108:20272, -39109:20315, -39110:20317, -39111:20311, -39112:20295, -39113:20342, -39114:20360, -39115:20367, -39116:20376, -39117:20347, -39118:20329, -39119:20336, -39120:20369, -39121:20335, -39122:20358, -39123:20374, -39124:20760, -39125:20436, -39126:20447, -39127:20430, -39128:20440, -39129:20443, -39130:20433, -39131:20442, -39132:20432, -39133:20452, -39134:20453, -39135:20506, -39136:20520, -39137:20500, -39138:20522, -39139:20517, -39140:20485, -39141:20252, -39142:20470, -39143:20513, -39144:20521, -39145:20524, -39146:20478, -39147:20463, -39148:20497, -39149:20486, -39150:20547, -39151:20551, -39152:26371, -39153:20565, -39154:20560, -39155:20552, -39156:20570, -39157:20566, -39158:20588, -39159:20600, -39160:20608, -39161:20634, -39162:20613, -39163:20660, -39164:20658, -39232:20681, -39233:20682, -39234:20659, -39235:20674, -39236:20694, -39237:20702, -39238:20709, -39239:20717, -39240:20707, -39241:20718, -39242:20729, -39243:20725, -39244:20745, -39245:20737, -39246:20738, -39247:20758, -39248:20757, -39249:20756, -39250:20762, -39251:20769, -39252:20794, -39253:20791, -39254:20796, -39255:20795, -39256:20799, -39257:20800, -39258:20818, -39259:20812, -39260:20820, -39261:20834, -39262:31480, -39263:20841, -39264:20842, -39265:20846, -39266:20864, -39267:20866, -39268:22232, -39269:20876, -39270:20873, -39271:20879, -39272:20881, -39273:20883, -39274:20885, -39275:20886, -39276:20900, -39277:20902, -39278:20898, -39279:20905, -39280:20906, -39281:20907, -39282:20915, -39283:20913, -39284:20914, -39285:20912, -39286:20917, -39287:20925, -39288:20933, -39289:20937, -39290:20955, -39291:20960, -39292:34389, -39293:20969, -39294:20973, -39296:20976, -39297:20981, -39298:20990, -39299:20996, -39300:21003, -39301:21012, -39302:21006, -39303:21031, -39304:21034, -39305:21038, -39306:21043, -39307:21049, -39308:21071, -39309:21060, -39310:21067, -39311:21068, -39312:21086, -39313:21076, -39314:21098, -39315:21108, -39316:21097, -39317:21107, -39318:21119, -39319:21117, -39320:21133, -39321:21140, -39322:21138, -39323:21105, -39324:21128, -39325:21137, -39326:36776, -39327:36775, -39328:21164, -39329:21165, -39330:21180, -39331:21173, -39332:21185, -39333:21197, -39334:21207, -39335:21214, -39336:21219, -39337:21222, -39338:39149, -39339:21216, -39340:21235, -39341:21237, -39342:21240, -39343:21241, -39344:21254, -39345:21256, -39346:30008, -39347:21261, -39348:21264, -39349:21263, -39350:21269, -39351:21274, -39352:21283, -39353:21295, -39354:21297, -39355:21299, -39356:21304, -39357:21312, -39358:21318, -39359:21317, -39360:19991, -39361:21321, -39362:21325, -39363:20950, -39364:21342, -39365:21353, -39366:21358, -39367:22808, -39368:21371, -39369:21367, -39370:21378, -39371:21398, -39372:21408, -39373:21414, -39374:21413, -39375:21422, -39376:21424, -39377:21430, -39378:21443, -39379:31762, -39380:38617, -39381:21471, -39382:26364, -39383:29166, -39384:21486, -39385:21480, -39386:21485, -39387:21498, -39388:21505, -39389:21565, -39390:21568, -39391:21548, -39392:21549, -39393:21564, -39394:21550, -39395:21558, -39396:21545, -39397:21533, -39398:21582, -39399:21647, -39400:21621, -39401:21646, -39402:21599, -39403:21617, -39404:21623, -39405:21616, -39406:21650, -39407:21627, -39408:21632, -39409:21622, -39410:21636, -39411:21648, -39412:21638, -39413:21703, -39414:21666, -39415:21688, -39416:21669, -39417:21676, -39418:21700, -39419:21704, -39420:21672, -39488:21675, -39489:21698, -39490:21668, -39491:21694, -39492:21692, -39493:21720, -39494:21733, -39495:21734, -39496:21775, -39497:21780, -39498:21757, -39499:21742, -39500:21741, -39501:21754, -39502:21730, -39503:21817, -39504:21824, -39505:21859, -39506:21836, -39507:21806, -39508:21852, -39509:21829, -39510:21846, -39511:21847, -39512:21816, -39513:21811, -39514:21853, -39515:21913, -39516:21888, -39517:21679, -39518:21898, -39519:21919, -39520:21883, -39521:21886, -39522:21912, -39523:21918, -39524:21934, -39525:21884, -39526:21891, -39527:21929, -39528:21895, -39529:21928, -39530:21978, -39531:21957, -39532:21983, -39533:21956, -39534:21980, -39535:21988, -39536:21972, -39537:22036, -39538:22007, -39539:22038, -39540:22014, -39541:22013, -39542:22043, -39543:22009, -39544:22094, -39545:22096, -39546:29151, -39547:22068, -39548:22070, -39549:22066, -39550:22072, -39552:22123, -39553:22116, -39554:22063, -39555:22124, -39556:22122, -39557:22150, -39558:22144, -39559:22154, -39560:22176, -39561:22164, -39562:22159, -39563:22181, -39564:22190, -39565:22198, -39566:22196, -39567:22210, -39568:22204, -39569:22209, -39570:22211, -39571:22208, -39572:22216, -39573:22222, -39574:22225, -39575:22227, -39576:22231, -39577:22254, -39578:22265, -39579:22272, -39580:22271, -39581:22276, -39582:22281, -39583:22280, -39584:22283, -39585:22285, -39586:22291, -39587:22296, -39588:22294, -39589:21959, -39590:22300, -39591:22310, -39592:22327, -39593:22328, -39594:22350, -39595:22331, -39596:22336, -39597:22351, -39598:22377, -39599:22464, -39600:22408, -39601:22369, -39602:22399, -39603:22409, -39604:22419, -39605:22432, -39606:22451, -39607:22436, -39608:22442, -39609:22448, -39610:22467, -39611:22470, -39612:22484, -39613:22482, -39614:22483, -39615:22538, -39616:22486, -39617:22499, -39618:22539, -39619:22553, -39620:22557, -39621:22642, -39622:22561, -39623:22626, -39624:22603, -39625:22640, -39626:27584, -39627:22610, -39628:22589, -39629:22649, -39630:22661, -39631:22713, -39632:22687, -39633:22699, -39634:22714, -39635:22750, -39636:22715, -39637:22712, -39638:22702, -39639:22725, -39640:22739, -39641:22737, -39642:22743, -39643:22745, -39644:22744, -39645:22757, -39646:22748, -39647:22756, -39648:22751, -39649:22767, -39650:22778, -39651:22777, -39652:22779, -39653:22780, -39654:22781, -39655:22786, -39656:22794, -39657:22800, -39658:22811, -39659:26790, -39660:22821, -39661:22828, -39662:22829, -39663:22834, -39664:22840, -39665:22846, -39666:31442, -39667:22869, -39668:22864, -39669:22862, -39670:22874, -39671:22872, -39672:22882, -39673:22880, -39674:22887, -39675:22892, -39676:22889, -39744:22904, -39745:22913, -39746:22941, -39747:20318, -39748:20395, -39749:22947, -39750:22962, -39751:22982, -39752:23016, -39753:23004, -39754:22925, -39755:23001, -39756:23002, -39757:23077, -39758:23071, -39759:23057, -39760:23068, -39761:23049, -39762:23066, -39763:23104, -39764:23148, -39765:23113, -39766:23093, -39767:23094, -39768:23138, -39769:23146, -39770:23194, -39771:23228, -39772:23230, -39773:23243, -39774:23234, -39775:23229, -39776:23267, -39777:23255, -39778:23270, -39779:23273, -39780:23254, -39781:23290, -39782:23291, -39783:23308, -39784:23307, -39785:23318, -39786:23346, -39787:23248, -39788:23338, -39789:23350, -39790:23358, -39791:23363, -39792:23365, -39793:23360, -39794:23377, -39795:23381, -39796:23386, -39797:23387, -39798:23397, -39799:23401, -39800:23408, -39801:23411, -39802:23413, -39803:23416, -39804:25992, -39805:23418, -39806:23424, -39808:23427, -39809:23462, -39810:23480, -39811:23491, -39812:23495, -39813:23497, -39814:23508, -39815:23504, -39816:23524, -39817:23526, -39818:23522, -39819:23518, -39820:23525, -39821:23531, -39822:23536, -39823:23542, -39824:23539, -39825:23557, -39826:23559, -39827:23560, -39828:23565, -39829:23571, -39830:23584, -39831:23586, -39832:23592, -39833:23608, -39834:23609, -39835:23617, -39836:23622, -39837:23630, -39838:23635, -39839:23632, -39840:23631, -39841:23409, -39842:23660, -39843:23662, -39844:20066, -39845:23670, -39846:23673, -39847:23692, -39848:23697, -39849:23700, -39850:22939, -39851:23723, -39852:23739, -39853:23734, -39854:23740, -39855:23735, -39856:23749, -39857:23742, -39858:23751, -39859:23769, -39860:23785, -39861:23805, -39862:23802, -39863:23789, -39864:23948, -39865:23786, -39866:23819, -39867:23829, -39868:23831, -39869:23900, -39870:23839, -39871:23835, -39872:23825, -39873:23828, -39874:23842, -39875:23834, -39876:23833, -39877:23832, -39878:23884, -39879:23890, -39880:23886, -39881:23883, -39882:23916, -39883:23923, -39884:23926, -39885:23943, -39886:23940, -39887:23938, -39888:23970, -39889:23965, -39890:23980, -39891:23982, -39892:23997, -39893:23952, -39894:23991, -39895:23996, -39896:24009, -39897:24013, -39898:24019, -39899:24018, -39900:24022, -39901:24027, -39902:24043, -39903:24050, -39904:24053, -39905:24075, -39906:24090, -39907:24089, -39908:24081, -39909:24091, -39910:24118, -39911:24119, -39912:24132, -39913:24131, -39914:24128, -39915:24142, -39916:24151, -39917:24148, -39918:24159, -39919:24162, -39920:24164, -39921:24135, -39922:24181, -39923:24182, -39924:24186, -39925:40636, -39926:24191, -39927:24224, -39928:24257, -39929:24258, -39930:24264, -39931:24272, -39932:24271, -40000:24278, -40001:24291, -40002:24285, -40003:24282, -40004:24283, -40005:24290, -40006:24289, -40007:24296, -40008:24297, -40009:24300, -40010:24305, -40011:24307, -40012:24304, -40013:24308, -40014:24312, -40015:24318, -40016:24323, -40017:24329, -40018:24413, -40019:24412, -40020:24331, -40021:24337, -40022:24342, -40023:24361, -40024:24365, -40025:24376, -40026:24385, -40027:24392, -40028:24396, -40029:24398, -40030:24367, -40031:24401, -40032:24406, -40033:24407, -40034:24409, -40035:24417, -40036:24429, -40037:24435, -40038:24439, -40039:24451, -40040:24450, -40041:24447, -40042:24458, -40043:24456, -40044:24465, -40045:24455, -40046:24478, -40047:24473, -40048:24472, -40049:24480, -40050:24488, -40051:24493, -40052:24508, -40053:24534, -40054:24571, -40055:24548, -40056:24568, -40057:24561, -40058:24541, -40059:24755, -40060:24575, -40061:24609, -40062:24672, -40064:24601, -40065:24592, -40066:24617, -40067:24590, -40068:24625, -40069:24603, -40070:24597, -40071:24619, -40072:24614, -40073:24591, -40074:24634, -40075:24666, -40076:24641, -40077:24682, -40078:24695, -40079:24671, -40080:24650, -40081:24646, -40082:24653, -40083:24675, -40084:24643, -40085:24676, -40086:24642, -40087:24684, -40088:24683, -40089:24665, -40090:24705, -40091:24717, -40092:24807, -40093:24707, -40094:24730, -40095:24708, -40096:24731, -40097:24726, -40098:24727, -40099:24722, -40100:24743, -40101:24715, -40102:24801, -40103:24760, -40104:24800, -40105:24787, -40106:24756, -40107:24560, -40108:24765, -40109:24774, -40110:24757, -40111:24792, -40112:24909, -40113:24853, -40114:24838, -40115:24822, -40116:24823, -40117:24832, -40118:24820, -40119:24826, -40120:24835, -40121:24865, -40122:24827, -40123:24817, -40124:24845, -40125:24846, -40126:24903, -40127:24894, -40128:24872, -40129:24871, -40130:24906, -40131:24895, -40132:24892, -40133:24876, -40134:24884, -40135:24893, -40136:24898, -40137:24900, -40138:24947, -40139:24951, -40140:24920, -40141:24921, -40142:24922, -40143:24939, -40144:24948, -40145:24943, -40146:24933, -40147:24945, -40148:24927, -40149:24925, -40150:24915, -40151:24949, -40152:24985, -40153:24982, -40154:24967, -40155:25004, -40156:24980, -40157:24986, -40158:24970, -40159:24977, -40160:25003, -40161:25006, -40162:25036, -40163:25034, -40164:25033, -40165:25079, -40166:25032, -40167:25027, -40168:25030, -40169:25018, -40170:25035, -40171:32633, -40172:25037, -40173:25062, -40174:25059, -40175:25078, -40176:25082, -40177:25076, -40178:25087, -40179:25085, -40180:25084, -40181:25086, -40182:25088, -40183:25096, -40184:25097, -40185:25101, -40186:25100, -40187:25108, -40188:25115, -40256:25118, -40257:25121, -40258:25130, -40259:25134, -40260:25136, -40261:25138, -40262:25139, -40263:25153, -40264:25166, -40265:25182, -40266:25187, -40267:25179, -40268:25184, -40269:25192, -40270:25212, -40271:25218, -40272:25225, -40273:25214, -40274:25234, -40275:25235, -40276:25238, -40277:25300, -40278:25219, -40279:25236, -40280:25303, -40281:25297, -40282:25275, -40283:25295, -40284:25343, -40285:25286, -40286:25812, -40287:25288, -40288:25308, -40289:25292, -40290:25290, -40291:25282, -40292:25287, -40293:25243, -40294:25289, -40295:25356, -40296:25326, -40297:25329, -40298:25383, -40299:25346, -40300:25352, -40301:25327, -40302:25333, -40303:25424, -40304:25406, -40305:25421, -40306:25628, -40307:25423, -40308:25494, -40309:25486, -40310:25472, -40311:25515, -40312:25462, -40313:25507, -40314:25487, -40315:25481, -40316:25503, -40317:25525, -40318:25451, -40320:25449, -40321:25534, -40322:25577, -40323:25536, -40324:25542, -40325:25571, -40326:25545, -40327:25554, -40328:25590, -40329:25540, -40330:25622, -40331:25652, -40332:25606, -40333:25619, -40334:25638, -40335:25654, -40336:25885, -40337:25623, -40338:25640, -40339:25615, -40340:25703, -40341:25711, -40342:25718, -40343:25678, -40344:25898, -40345:25749, -40346:25747, -40347:25765, -40348:25769, -40349:25736, -40350:25788, -40351:25818, -40352:25810, -40353:25797, -40354:25799, -40355:25787, -40356:25816, -40357:25794, -40358:25841, -40359:25831, -40360:33289, -40361:25824, -40362:25825, -40363:25260, -40364:25827, -40365:25839, -40366:25900, -40367:25846, -40368:25844, -40369:25842, -40370:25850, -40371:25856, -40372:25853, -40373:25880, -40374:25884, -40375:25861, -40376:25892, -40377:25891, -40378:25899, -40379:25908, -40380:25909, -40381:25911, -40382:25910, -40383:25912, -40384:30027, -40385:25928, -40386:25942, -40387:25941, -40388:25933, -40389:25944, -40390:25950, -40391:25949, -40392:25970, -40393:25976, -40394:25986, -40395:25987, -40396:35722, -40397:26011, -40398:26015, -40399:26027, -40400:26039, -40401:26051, -40402:26054, -40403:26049, -40404:26052, -40405:26060, -40406:26066, -40407:26075, -40408:26073, -40409:26080, -40410:26081, -40411:26097, -40412:26482, -40413:26122, -40414:26115, -40415:26107, -40416:26483, -40417:26165, -40418:26166, -40419:26164, -40420:26140, -40421:26191, -40422:26180, -40423:26185, -40424:26177, -40425:26206, -40426:26205, -40427:26212, -40428:26215, -40429:26216, -40430:26207, -40431:26210, -40432:26224, -40433:26243, -40434:26248, -40435:26254, -40436:26249, -40437:26244, -40438:26264, -40439:26269, -40440:26305, -40441:26297, -40442:26313, -40443:26302, -40444:26300, -40512:26308, -40513:26296, -40514:26326, -40515:26330, -40516:26336, -40517:26175, -40518:26342, -40519:26345, -40520:26352, -40521:26357, -40522:26359, -40523:26383, -40524:26390, -40525:26398, -40526:26406, -40527:26407, -40528:38712, -40529:26414, -40530:26431, -40531:26422, -40532:26433, -40533:26424, -40534:26423, -40535:26438, -40536:26462, -40537:26464, -40538:26457, -40539:26467, -40540:26468, -40541:26505, -40542:26480, -40543:26537, -40544:26492, -40545:26474, -40546:26508, -40547:26507, -40548:26534, -40549:26529, -40550:26501, -40551:26551, -40552:26607, -40553:26548, -40554:26604, -40555:26547, -40556:26601, -40557:26552, -40558:26596, -40559:26590, -40560:26589, -40561:26594, -40562:26606, -40563:26553, -40564:26574, -40565:26566, -40566:26599, -40567:27292, -40568:26654, -40569:26694, -40570:26665, -40571:26688, -40572:26701, -40573:26674, -40574:26702, -40576:26803, -40577:26667, -40578:26713, -40579:26723, -40580:26743, -40581:26751, -40582:26783, -40583:26767, -40584:26797, -40585:26772, -40586:26781, -40587:26779, -40588:26755, -40589:27310, -40590:26809, -40591:26740, -40592:26805, -40593:26784, -40594:26810, -40595:26895, -40596:26765, -40597:26750, -40598:26881, -40599:26826, -40600:26888, -40601:26840, -40602:26914, -40603:26918, -40604:26849, -40605:26892, -40606:26829, -40607:26836, -40608:26855, -40609:26837, -40610:26934, -40611:26898, -40612:26884, -40613:26839, -40614:26851, -40615:26917, -40616:26873, -40617:26848, -40618:26863, -40619:26920, -40620:26922, -40621:26906, -40622:26915, -40623:26913, -40624:26822, -40625:27001, -40626:26999, -40627:26972, -40628:27000, -40629:26987, -40630:26964, -40631:27006, -40632:26990, -40633:26937, -40634:26996, -40635:26941, -40636:26969, -40637:26928, -40638:26977, -40639:26974, -40640:26973, -40641:27009, -40642:26986, -40643:27058, -40644:27054, -40645:27088, -40646:27071, -40647:27073, -40648:27091, -40649:27070, -40650:27086, -40651:23528, -40652:27082, -40653:27101, -40654:27067, -40655:27075, -40656:27047, -40657:27182, -40658:27025, -40659:27040, -40660:27036, -40661:27029, -40662:27060, -40663:27102, -40664:27112, -40665:27138, -40666:27163, -40667:27135, -40668:27402, -40669:27129, -40670:27122, -40671:27111, -40672:27141, -40673:27057, -40674:27166, -40675:27117, -40676:27156, -40677:27115, -40678:27146, -40679:27154, -40680:27329, -40681:27171, -40682:27155, -40683:27204, -40684:27148, -40685:27250, -40686:27190, -40687:27256, -40688:27207, -40689:27234, -40690:27225, -40691:27238, -40692:27208, -40693:27192, -40694:27170, -40695:27280, -40696:27277, -40697:27296, -40698:27268, -40699:27298, -40700:27299, -40768:27287, -40769:34327, -40770:27323, -40771:27331, -40772:27330, -40773:27320, -40774:27315, -40775:27308, -40776:27358, -40777:27345, -40778:27359, -40779:27306, -40780:27354, -40781:27370, -40782:27387, -40783:27397, -40784:34326, -40785:27386, -40786:27410, -40787:27414, -40788:39729, -40789:27423, -40790:27448, -40791:27447, -40792:30428, -40793:27449, -40794:39150, -40795:27463, -40796:27459, -40797:27465, -40798:27472, -40799:27481, -40800:27476, -40801:27483, -40802:27487, -40803:27489, -40804:27512, -40805:27513, -40806:27519, -40807:27520, -40808:27524, -40809:27523, -40810:27533, -40811:27544, -40812:27541, -40813:27550, -40814:27556, -40815:27562, -40816:27563, -40817:27567, -40818:27570, -40819:27569, -40820:27571, -40821:27575, -40822:27580, -40823:27590, -40824:27595, -40825:27603, -40826:27615, -40827:27628, -40828:27627, -40829:27635, -40830:27631, -40832:40638, -40833:27656, -40834:27667, -40835:27668, -40836:27675, -40837:27684, -40838:27683, -40839:27742, -40840:27733, -40841:27746, -40842:27754, -40843:27778, -40844:27789, -40845:27802, -40846:27777, -40847:27803, -40848:27774, -40849:27752, -40850:27763, -40851:27794, -40852:27792, -40853:27844, -40854:27889, -40855:27859, -40856:27837, -40857:27863, -40858:27845, -40859:27869, -40860:27822, -40861:27825, -40862:27838, -40863:27834, -40864:27867, -40865:27887, -40866:27865, -40867:27882, -40868:27935, -40869:34893, -40870:27958, -40871:27947, -40872:27965, -40873:27960, -40874:27929, -40875:27957, -40876:27955, -40877:27922, -40878:27916, -40879:28003, -40880:28051, -40881:28004, -40882:27994, -40883:28025, -40884:27993, -40885:28046, -40886:28053, -40887:28644, -40888:28037, -40889:28153, -40890:28181, -40891:28170, -40892:28085, -40893:28103, -40894:28134, -40895:28088, -40896:28102, -40897:28140, -40898:28126, -40899:28108, -40900:28136, -40901:28114, -40902:28101, -40903:28154, -40904:28121, -40905:28132, -40906:28117, -40907:28138, -40908:28142, -40909:28205, -40910:28270, -40911:28206, -40912:28185, -40913:28274, -40914:28255, -40915:28222, -40916:28195, -40917:28267, -40918:28203, -40919:28278, -40920:28237, -40921:28191, -40922:28227, -40923:28218, -40924:28238, -40925:28196, -40926:28415, -40927:28189, -40928:28216, -40929:28290, -40930:28330, -40931:28312, -40932:28361, -40933:28343, -40934:28371, -40935:28349, -40936:28335, -40937:28356, -40938:28338, -40939:28372, -40940:28373, -40941:28303, -40942:28325, -40943:28354, -40944:28319, -40945:28481, -40946:28433, -40947:28748, -40948:28396, -40949:28408, -40950:28414, -40951:28479, -40952:28402, -40953:28465, -40954:28399, -40955:28466, -40956:28364, -57408:28478, -57409:28435, -57410:28407, -57411:28550, -57412:28538, -57413:28536, -57414:28545, -57415:28544, -57416:28527, -57417:28507, -57418:28659, -57419:28525, -57420:28546, -57421:28540, -57422:28504, -57423:28558, -57424:28561, -57425:28610, -57426:28518, -57427:28595, -57428:28579, -57429:28577, -57430:28580, -57431:28601, -57432:28614, -57433:28586, -57434:28639, -57435:28629, -57436:28652, -57437:28628, -57438:28632, -57439:28657, -57440:28654, -57441:28635, -57442:28681, -57443:28683, -57444:28666, -57445:28689, -57446:28673, -57447:28687, -57448:28670, -57449:28699, -57450:28698, -57451:28532, -57452:28701, -57453:28696, -57454:28703, -57455:28720, -57456:28734, -57457:28722, -57458:28753, -57459:28771, -57460:28825, -57461:28818, -57462:28847, -57463:28913, -57464:28844, -57465:28856, -57466:28851, -57467:28846, -57468:28895, -57469:28875, -57470:28893, -57472:28889, -57473:28937, -57474:28925, -57475:28956, -57476:28953, -57477:29029, -57478:29013, -57479:29064, -57480:29030, -57481:29026, -57482:29004, -57483:29014, -57484:29036, -57485:29071, -57486:29179, -57487:29060, -57488:29077, -57489:29096, -57490:29100, -57491:29143, -57492:29113, -57493:29118, -57494:29138, -57495:29129, -57496:29140, -57497:29134, -57498:29152, -57499:29164, -57500:29159, -57501:29173, -57502:29180, -57503:29177, -57504:29183, -57505:29197, -57506:29200, -57507:29211, -57508:29224, -57509:29229, -57510:29228, -57511:29232, -57512:29234, -57513:29243, -57514:29244, -57515:29247, -57516:29248, -57517:29254, -57518:29259, -57519:29272, -57520:29300, -57521:29310, -57522:29314, -57523:29313, -57524:29319, -57525:29330, -57526:29334, -57527:29346, -57528:29351, -57529:29369, -57530:29362, -57531:29379, -57532:29382, -57533:29380, -57534:29390, -57535:29394, -57536:29410, -57537:29408, -57538:29409, -57539:29433, -57540:29431, -57541:20495, -57542:29463, -57543:29450, -57544:29468, -57545:29462, -57546:29469, -57547:29492, -57548:29487, -57549:29481, -57550:29477, -57551:29502, -57552:29518, -57553:29519, -57554:40664, -57555:29527, -57556:29546, -57557:29544, -57558:29552, -57559:29560, -57560:29557, -57561:29563, -57562:29562, -57563:29640, -57564:29619, -57565:29646, -57566:29627, -57567:29632, -57568:29669, -57569:29678, -57570:29662, -57571:29858, -57572:29701, -57573:29807, -57574:29733, -57575:29688, -57576:29746, -57577:29754, -57578:29781, -57579:29759, -57580:29791, -57581:29785, -57582:29761, -57583:29788, -57584:29801, -57585:29808, -57586:29795, -57587:29802, -57588:29814, -57589:29822, -57590:29835, -57591:29854, -57592:29863, -57593:29898, -57594:29903, -57595:29908, -57596:29681, -57664:29920, -57665:29923, -57666:29927, -57667:29929, -57668:29934, -57669:29938, -57670:29936, -57671:29937, -57672:29944, -57673:29943, -57674:29956, -57675:29955, -57676:29957, -57677:29964, -57678:29966, -57679:29965, -57680:29973, -57681:29971, -57682:29982, -57683:29990, -57684:29996, -57685:30012, -57686:30020, -57687:30029, -57688:30026, -57689:30025, -57690:30043, -57691:30022, -57692:30042, -57693:30057, -57694:30052, -57695:30055, -57696:30059, -57697:30061, -57698:30072, -57699:30070, -57700:30086, -57701:30087, -57702:30068, -57703:30090, -57704:30089, -57705:30082, -57706:30100, -57707:30106, -57708:30109, -57709:30117, -57710:30115, -57711:30146, -57712:30131, -57713:30147, -57714:30133, -57715:30141, -57716:30136, -57717:30140, -57718:30129, -57719:30157, -57720:30154, -57721:30162, -57722:30169, -57723:30179, -57724:30174, -57725:30206, -57726:30207, -57728:30204, -57729:30209, -57730:30192, -57731:30202, -57732:30194, -57733:30195, -57734:30219, -57735:30221, -57736:30217, -57737:30239, -57738:30247, -57739:30240, -57740:30241, -57741:30242, -57742:30244, -57743:30260, -57744:30256, -57745:30267, -57746:30279, -57747:30280, -57748:30278, -57749:30300, -57750:30296, -57751:30305, -57752:30306, -57753:30312, -57754:30313, -57755:30314, -57756:30311, -57757:30316, -57758:30320, -57759:30322, -57760:30326, -57761:30328, -57762:30332, -57763:30336, -57764:30339, -57765:30344, -57766:30347, -57767:30350, -57768:30358, -57769:30355, -57770:30361, -57771:30362, -57772:30384, -57773:30388, -57774:30392, -57775:30393, -57776:30394, -57777:30402, -57778:30413, -57779:30422, -57780:30418, -57781:30430, -57782:30433, -57783:30437, -57784:30439, -57785:30442, -57786:34351, -57787:30459, -57788:30472, -57789:30471, -57790:30468, -57791:30505, -57792:30500, -57793:30494, -57794:30501, -57795:30502, -57796:30491, -57797:30519, -57798:30520, -57799:30535, -57800:30554, -57801:30568, -57802:30571, -57803:30555, -57804:30565, -57805:30591, -57806:30590, -57807:30585, -57808:30606, -57809:30603, -57810:30609, -57811:30624, -57812:30622, -57813:30640, -57814:30646, -57815:30649, -57816:30655, -57817:30652, -57818:30653, -57819:30651, -57820:30663, -57821:30669, -57822:30679, -57823:30682, -57824:30684, -57825:30691, -57826:30702, -57827:30716, -57828:30732, -57829:30738, -57830:31014, -57831:30752, -57832:31018, -57833:30789, -57834:30862, -57835:30836, -57836:30854, -57837:30844, -57838:30874, -57839:30860, -57840:30883, -57841:30901, -57842:30890, -57843:30895, -57844:30929, -57845:30918, -57846:30923, -57847:30932, -57848:30910, -57849:30908, -57850:30917, -57851:30922, -57852:30956, -57920:30951, -57921:30938, -57922:30973, -57923:30964, -57924:30983, -57925:30994, -57926:30993, -57927:31001, -57928:31020, -57929:31019, -57930:31040, -57931:31072, -57932:31063, -57933:31071, -57934:31066, -57935:31061, -57936:31059, -57937:31098, -57938:31103, -57939:31114, -57940:31133, -57941:31143, -57942:40779, -57943:31146, -57944:31150, -57945:31155, -57946:31161, -57947:31162, -57948:31177, -57949:31189, -57950:31207, -57951:31212, -57952:31201, -57953:31203, -57954:31240, -57955:31245, -57956:31256, -57957:31257, -57958:31264, -57959:31263, -57960:31104, -57961:31281, -57962:31291, -57963:31294, -57964:31287, -57965:31299, -57966:31319, -57967:31305, -57968:31329, -57969:31330, -57970:31337, -57971:40861, -57972:31344, -57973:31353, -57974:31357, -57975:31368, -57976:31383, -57977:31381, -57978:31384, -57979:31382, -57980:31401, -57981:31432, -57982:31408, -57984:31414, -57985:31429, -57986:31428, -57987:31423, -57988:36995, -57989:31431, -57990:31434, -57991:31437, -57992:31439, -57993:31445, -57994:31443, -57995:31449, -57996:31450, -57997:31453, -57998:31457, -57999:31458, -58000:31462, -58001:31469, -58002:31472, -58003:31490, -58004:31503, -58005:31498, -58006:31494, -58007:31539, -58008:31512, -58009:31513, -58010:31518, -58011:31541, -58012:31528, -58013:31542, -58014:31568, -58015:31610, -58016:31492, -58017:31565, -58018:31499, -58019:31564, -58020:31557, -58021:31605, -58022:31589, -58023:31604, -58024:31591, -58025:31600, -58026:31601, -58027:31596, -58028:31598, -58029:31645, -58030:31640, -58031:31647, -58032:31629, -58033:31644, -58034:31642, -58035:31627, -58036:31634, -58037:31631, -58038:31581, -58039:31641, -58040:31691, -58041:31681, -58042:31692, -58043:31695, -58044:31668, -58045:31686, -58046:31709, -58047:31721, -58048:31761, -58049:31764, -58050:31718, -58051:31717, -58052:31840, -58053:31744, -58054:31751, -58055:31763, -58056:31731, -58057:31735, -58058:31767, -58059:31757, -58060:31734, -58061:31779, -58062:31783, -58063:31786, -58064:31775, -58065:31799, -58066:31787, -58067:31805, -58068:31820, -58069:31811, -58070:31828, -58071:31823, -58072:31808, -58073:31824, -58074:31832, -58075:31839, -58076:31844, -58077:31830, -58078:31845, -58079:31852, -58080:31861, -58081:31875, -58082:31888, -58083:31908, -58084:31917, -58085:31906, -58086:31915, -58087:31905, -58088:31912, -58089:31923, -58090:31922, -58091:31921, -58092:31918, -58093:31929, -58094:31933, -58095:31936, -58096:31941, -58097:31938, -58098:31960, -58099:31954, -58100:31964, -58101:31970, -58102:39739, -58103:31983, -58104:31986, -58105:31988, -58106:31990, -58107:31994, -58108:32006, -58176:32002, -58177:32028, -58178:32021, -58179:32010, -58180:32069, -58181:32075, -58182:32046, -58183:32050, -58184:32063, -58185:32053, -58186:32070, -58187:32115, -58188:32086, -58189:32078, -58190:32114, -58191:32104, -58192:32110, -58193:32079, -58194:32099, -58195:32147, -58196:32137, -58197:32091, -58198:32143, -58199:32125, -58200:32155, -58201:32186, -58202:32174, -58203:32163, -58204:32181, -58205:32199, -58206:32189, -58207:32171, -58208:32317, -58209:32162, -58210:32175, -58211:32220, -58212:32184, -58213:32159, -58214:32176, -58215:32216, -58216:32221, -58217:32228, -58218:32222, -58219:32251, -58220:32242, -58221:32225, -58222:32261, -58223:32266, -58224:32291, -58225:32289, -58226:32274, -58227:32305, -58228:32287, -58229:32265, -58230:32267, -58231:32290, -58232:32326, -58233:32358, -58234:32315, -58235:32309, -58236:32313, -58237:32323, -58238:32311, -58240:32306, -58241:32314, -58242:32359, -58243:32349, -58244:32342, -58245:32350, -58246:32345, -58247:32346, -58248:32377, -58249:32362, -58250:32361, -58251:32380, -58252:32379, -58253:32387, -58254:32213, -58255:32381, -58256:36782, -58257:32383, -58258:32392, -58259:32393, -58260:32396, -58261:32402, -58262:32400, -58263:32403, -58264:32404, -58265:32406, -58266:32398, -58267:32411, -58268:32412, -58269:32568, -58270:32570, -58271:32581, -58272:32588, -58273:32589, -58274:32590, -58275:32592, -58276:32593, -58277:32597, -58278:32596, -58279:32600, -58280:32607, -58281:32608, -58282:32616, -58283:32617, -58284:32615, -58285:32632, -58286:32642, -58287:32646, -58288:32643, -58289:32648, -58290:32647, -58291:32652, -58292:32660, -58293:32670, -58294:32669, -58295:32666, -58296:32675, -58297:32687, -58298:32690, -58299:32697, -58300:32686, -58301:32694, -58302:32696, -58303:35697, -58304:32709, -58305:32710, -58306:32714, -58307:32725, -58308:32724, -58309:32737, -58310:32742, -58311:32745, -58312:32755, -58313:32761, -58314:39132, -58315:32774, -58316:32772, -58317:32779, -58318:32786, -58319:32792, -58320:32793, -58321:32796, -58322:32801, -58323:32808, -58324:32831, -58325:32827, -58326:32842, -58327:32838, -58328:32850, -58329:32856, -58330:32858, -58331:32863, -58332:32866, -58333:32872, -58334:32883, -58335:32882, -58336:32880, -58337:32886, -58338:32889, -58339:32893, -58340:32895, -58341:32900, -58342:32902, -58343:32901, -58344:32923, -58345:32915, -58346:32922, -58347:32941, -58348:20880, -58349:32940, -58350:32987, -58351:32997, -58352:32985, -58353:32989, -58354:32964, -58355:32986, -58356:32982, -58357:33033, -58358:33007, -58359:33009, -58360:33051, -58361:33065, -58362:33059, -58363:33071, -58364:33099, -58432:38539, -58433:33094, -58434:33086, -58435:33107, -58436:33105, -58437:33020, -58438:33137, -58439:33134, -58440:33125, -58441:33126, -58442:33140, -58443:33155, -58444:33160, -58445:33162, -58446:33152, -58447:33154, -58448:33184, -58449:33173, -58450:33188, -58451:33187, -58452:33119, -58453:33171, -58454:33193, -58455:33200, -58456:33205, -58457:33214, -58458:33208, -58459:33213, -58460:33216, -58461:33218, -58462:33210, -58463:33225, -58464:33229, -58465:33233, -58466:33241, -58467:33240, -58468:33224, -58469:33242, -58470:33247, -58471:33248, -58472:33255, -58473:33274, -58474:33275, -58475:33278, -58476:33281, -58477:33282, -58478:33285, -58479:33287, -58480:33290, -58481:33293, -58482:33296, -58483:33302, -58484:33321, -58485:33323, -58486:33336, -58487:33331, -58488:33344, -58489:33369, -58490:33368, -58491:33373, -58492:33370, -58493:33375, -58494:33380, -58496:33378, -58497:33384, -58498:33386, -58499:33387, -58500:33326, -58501:33393, -58502:33399, -58503:33400, -58504:33406, -58505:33421, -58506:33426, -58507:33451, -58508:33439, -58509:33467, -58510:33452, -58511:33505, -58512:33507, -58513:33503, -58514:33490, -58515:33524, -58516:33523, -58517:33530, -58518:33683, -58519:33539, -58520:33531, -58521:33529, -58522:33502, -58523:33542, -58524:33500, -58525:33545, -58526:33497, -58527:33589, -58528:33588, -58529:33558, -58530:33586, -58531:33585, -58532:33600, -58533:33593, -58534:33616, -58535:33605, -58536:33583, -58537:33579, -58538:33559, -58539:33560, -58540:33669, -58541:33690, -58542:33706, -58543:33695, -58544:33698, -58545:33686, -58546:33571, -58547:33678, -58548:33671, -58549:33674, -58550:33660, -58551:33717, -58552:33651, -58553:33653, -58554:33696, -58555:33673, -58556:33704, -58557:33780, -58558:33811, -58559:33771, -58560:33742, -58561:33789, -58562:33795, -58563:33752, -58564:33803, -58565:33729, -58566:33783, -58567:33799, -58568:33760, -58569:33778, -58570:33805, -58571:33826, -58572:33824, -58573:33725, -58574:33848, -58575:34054, -58576:33787, -58577:33901, -58578:33834, -58579:33852, -58580:34138, -58581:33924, -58582:33911, -58583:33899, -58584:33965, -58585:33902, -58586:33922, -58587:33897, -58588:33862, -58589:33836, -58590:33903, -58591:33913, -58592:33845, -58593:33994, -58594:33890, -58595:33977, -58596:33983, -58597:33951, -58598:34009, -58599:33997, -58600:33979, -58601:34010, -58602:34000, -58603:33985, -58604:33990, -58605:34006, -58606:33953, -58607:34081, -58608:34047, -58609:34036, -58610:34071, -58611:34072, -58612:34092, -58613:34079, -58614:34069, -58615:34068, -58616:34044, -58617:34112, -58618:34147, -58619:34136, -58620:34120, -58688:34113, -58689:34306, -58690:34123, -58691:34133, -58692:34176, -58693:34212, -58694:34184, -58695:34193, -58696:34186, -58697:34216, -58698:34157, -58699:34196, -58700:34203, -58701:34282, -58702:34183, -58703:34204, -58704:34167, -58705:34174, -58706:34192, -58707:34249, -58708:34234, -58709:34255, -58710:34233, -58711:34256, -58712:34261, -58713:34269, -58714:34277, -58715:34268, -58716:34297, -58717:34314, -58718:34323, -58719:34315, -58720:34302, -58721:34298, -58722:34310, -58723:34338, -58724:34330, -58725:34352, -58726:34367, -58727:34381, -58728:20053, -58729:34388, -58730:34399, -58731:34407, -58732:34417, -58733:34451, -58734:34467, -58735:34473, -58736:34474, -58737:34443, -58738:34444, -58739:34486, -58740:34479, -58741:34500, -58742:34502, -58743:34480, -58744:34505, -58745:34851, -58746:34475, -58747:34516, -58748:34526, -58749:34537, -58750:34540, -58752:34527, -58753:34523, -58754:34543, -58755:34578, -58756:34566, -58757:34568, -58758:34560, -58759:34563, -58760:34555, -58761:34577, -58762:34569, -58763:34573, -58764:34553, -58765:34570, -58766:34612, -58767:34623, -58768:34615, -58769:34619, -58770:34597, -58771:34601, -58772:34586, -58773:34656, -58774:34655, -58775:34680, -58776:34636, -58777:34638, -58778:34676, -58779:34647, -58780:34664, -58781:34670, -58782:34649, -58783:34643, -58784:34659, -58785:34666, -58786:34821, -58787:34722, -58788:34719, -58789:34690, -58790:34735, -58791:34763, -58792:34749, -58793:34752, -58794:34768, -58795:38614, -58796:34731, -58797:34756, -58798:34739, -58799:34759, -58800:34758, -58801:34747, -58802:34799, -58803:34802, -58804:34784, -58805:34831, -58806:34829, -58807:34814, -58808:34806, -58809:34807, -58810:34830, -58811:34770, -58812:34833, -58813:34838, -58814:34837, -58815:34850, -58816:34849, -58817:34865, -58818:34870, -58819:34873, -58820:34855, -58821:34875, -58822:34884, -58823:34882, -58824:34898, -58825:34905, -58826:34910, -58827:34914, -58828:34923, -58829:34945, -58830:34942, -58831:34974, -58832:34933, -58833:34941, -58834:34997, -58835:34930, -58836:34946, -58837:34967, -58838:34962, -58839:34990, -58840:34969, -58841:34978, -58842:34957, -58843:34980, -58844:34992, -58845:35007, -58846:34993, -58847:35011, -58848:35012, -58849:35028, -58850:35032, -58851:35033, -58852:35037, -58853:35065, -58854:35074, -58855:35068, -58856:35060, -58857:35048, -58858:35058, -58859:35076, -58860:35084, -58861:35082, -58862:35091, -58863:35139, -58864:35102, -58865:35109, -58866:35114, -58867:35115, -58868:35137, -58869:35140, -58870:35131, -58871:35126, -58872:35128, -58873:35148, -58874:35101, -58875:35168, -58876:35166, -58944:35174, -58945:35172, -58946:35181, -58947:35178, -58948:35183, -58949:35188, -58950:35191, -58951:35198, -58952:35203, -58953:35208, -58954:35210, -58955:35219, -58956:35224, -58957:35233, -58958:35241, -58959:35238, -58960:35244, -58961:35247, -58962:35250, -58963:35258, -58964:35261, -58965:35263, -58966:35264, -58967:35290, -58968:35292, -58969:35293, -58970:35303, -58971:35316, -58972:35320, -58973:35331, -58974:35350, -58975:35344, -58976:35340, -58977:35355, -58978:35357, -58979:35365, -58980:35382, -58981:35393, -58982:35419, -58983:35410, -58984:35398, -58985:35400, -58986:35452, -58987:35437, -58988:35436, -58989:35426, -58990:35461, -58991:35458, -58992:35460, -58993:35496, -58994:35489, -58995:35473, -58996:35493, -58997:35494, -58998:35482, -58999:35491, -59000:35524, -59001:35533, -59002:35522, -59003:35546, -59004:35563, -59005:35571, -59006:35559, -59008:35556, -59009:35569, -59010:35604, -59011:35552, -59012:35554, -59013:35575, -59014:35550, -59015:35547, -59016:35596, -59017:35591, -59018:35610, -59019:35553, -59020:35606, -59021:35600, -59022:35607, -59023:35616, -59024:35635, -59025:38827, -59026:35622, -59027:35627, -59028:35646, -59029:35624, -59030:35649, -59031:35660, -59032:35663, -59033:35662, -59034:35657, -59035:35670, -59036:35675, -59037:35674, -59038:35691, -59039:35679, -59040:35692, -59041:35695, -59042:35700, -59043:35709, -59044:35712, -59045:35724, -59046:35726, -59047:35730, -59048:35731, -59049:35734, -59050:35737, -59051:35738, -59052:35898, -59053:35905, -59054:35903, -59055:35912, -59056:35916, -59057:35918, -59058:35920, -59059:35925, -59060:35938, -59061:35948, -59062:35960, -59063:35962, -59064:35970, -59065:35977, -59066:35973, -59067:35978, -59068:35981, -59069:35982, -59070:35988, -59071:35964, -59072:35992, -59073:25117, -59074:36013, -59075:36010, -59076:36029, -59077:36018, -59078:36019, -59079:36014, -59080:36022, -59081:36040, -59082:36033, -59083:36068, -59084:36067, -59085:36058, -59086:36093, -59087:36090, -59088:36091, -59089:36100, -59090:36101, -59091:36106, -59092:36103, -59093:36111, -59094:36109, -59095:36112, -59096:40782, -59097:36115, -59098:36045, -59099:36116, -59100:36118, -59101:36199, -59102:36205, -59103:36209, -59104:36211, -59105:36225, -59106:36249, -59107:36290, -59108:36286, -59109:36282, -59110:36303, -59111:36314, -59112:36310, -59113:36300, -59114:36315, -59115:36299, -59116:36330, -59117:36331, -59118:36319, -59119:36323, -59120:36348, -59121:36360, -59122:36361, -59123:36351, -59124:36381, -59125:36382, -59126:36368, -59127:36383, -59128:36418, -59129:36405, -59130:36400, -59131:36404, -59132:36426, -59200:36423, -59201:36425, -59202:36428, -59203:36432, -59204:36424, -59205:36441, -59206:36452, -59207:36448, -59208:36394, -59209:36451, -59210:36437, -59211:36470, -59212:36466, -59213:36476, -59214:36481, -59215:36487, -59216:36485, -59217:36484, -59218:36491, -59219:36490, -59220:36499, -59221:36497, -59222:36500, -59223:36505, -59224:36522, -59225:36513, -59226:36524, -59227:36528, -59228:36550, -59229:36529, -59230:36542, -59231:36549, -59232:36552, -59233:36555, -59234:36571, -59235:36579, -59236:36604, -59237:36603, -59238:36587, -59239:36606, -59240:36618, -59241:36613, -59242:36629, -59243:36626, -59244:36633, -59245:36627, -59246:36636, -59247:36639, -59248:36635, -59249:36620, -59250:36646, -59251:36659, -59252:36667, -59253:36665, -59254:36677, -59255:36674, -59256:36670, -59257:36684, -59258:36681, -59259:36678, -59260:36686, -59261:36695, -59262:36700, -59264:36706, -59265:36707, -59266:36708, -59267:36764, -59268:36767, -59269:36771, -59270:36781, -59271:36783, -59272:36791, -59273:36826, -59274:36837, -59275:36834, -59276:36842, -59277:36847, -59278:36999, -59279:36852, -59280:36869, -59281:36857, -59282:36858, -59283:36881, -59284:36885, -59285:36897, -59286:36877, -59287:36894, -59288:36886, -59289:36875, -59290:36903, -59291:36918, -59292:36917, -59293:36921, -59294:36856, -59295:36943, -59296:36944, -59297:36945, -59298:36946, -59299:36878, -59300:36937, -59301:36926, -59302:36950, -59303:36952, -59304:36958, -59305:36968, -59306:36975, -59307:36982, -59308:38568, -59309:36978, -59310:36994, -59311:36989, -59312:36993, -59313:36992, -59314:37002, -59315:37001, -59316:37007, -59317:37032, -59318:37039, -59319:37041, -59320:37045, -59321:37090, -59322:37092, -59323:25160, -59324:37083, -59325:37122, -59326:37138, -59327:37145, -59328:37170, -59329:37168, -59330:37194, -59331:37206, -59332:37208, -59333:37219, -59334:37221, -59335:37225, -59336:37235, -59337:37234, -59338:37259, -59339:37257, -59340:37250, -59341:37282, -59342:37291, -59343:37295, -59344:37290, -59345:37301, -59346:37300, -59347:37306, -59348:37312, -59349:37313, -59350:37321, -59351:37323, -59352:37328, -59353:37334, -59354:37343, -59355:37345, -59356:37339, -59357:37372, -59358:37365, -59359:37366, -59360:37406, -59361:37375, -59362:37396, -59363:37420, -59364:37397, -59365:37393, -59366:37470, -59367:37463, -59368:37445, -59369:37449, -59370:37476, -59371:37448, -59372:37525, -59373:37439, -59374:37451, -59375:37456, -59376:37532, -59377:37526, -59378:37523, -59379:37531, -59380:37466, -59381:37583, -59382:37561, -59383:37559, -59384:37609, -59385:37647, -59386:37626, -59387:37700, -59388:37678, -59456:37657, -59457:37666, -59458:37658, -59459:37667, -59460:37690, -59461:37685, -59462:37691, -59463:37724, -59464:37728, -59465:37756, -59466:37742, -59467:37718, -59468:37808, -59469:37804, -59470:37805, -59471:37780, -59472:37817, -59473:37846, -59474:37847, -59475:37864, -59476:37861, -59477:37848, -59478:37827, -59479:37853, -59480:37840, -59481:37832, -59482:37860, -59483:37914, -59484:37908, -59485:37907, -59486:37891, -59487:37895, -59488:37904, -59489:37942, -59490:37931, -59491:37941, -59492:37921, -59493:37946, -59494:37953, -59495:37970, -59496:37956, -59497:37979, -59498:37984, -59499:37986, -59500:37982, -59501:37994, -59502:37417, -59503:38000, -59504:38005, -59505:38007, -59506:38013, -59507:37978, -59508:38012, -59509:38014, -59510:38017, -59511:38015, -59512:38274, -59513:38279, -59514:38282, -59515:38292, -59516:38294, -59517:38296, -59518:38297, -59520:38304, -59521:38312, -59522:38311, -59523:38317, -59524:38332, -59525:38331, -59526:38329, -59527:38334, -59528:38346, -59529:28662, -59530:38339, -59531:38349, -59532:38348, -59533:38357, -59534:38356, -59535:38358, -59536:38364, -59537:38369, -59538:38373, -59539:38370, -59540:38433, -59541:38440, -59542:38446, -59543:38447, -59544:38466, -59545:38476, -59546:38479, -59547:38475, -59548:38519, -59549:38492, -59550:38494, -59551:38493, -59552:38495, -59553:38502, -59554:38514, -59555:38508, -59556:38541, -59557:38552, -59558:38549, -59559:38551, -59560:38570, -59561:38567, -59562:38577, -59563:38578, -59564:38576, -59565:38580, -59566:38582, -59567:38584, -59568:38585, -59569:38606, -59570:38603, -59571:38601, -59572:38605, -59573:35149, -59574:38620, -59575:38669, -59576:38613, -59577:38649, -59578:38660, -59579:38662, -59580:38664, -59581:38675, -59582:38670, -59583:38673, -59584:38671, -59585:38678, -59586:38681, -59587:38692, -59588:38698, -59589:38704, -59590:38713, -59591:38717, -59592:38718, -59593:38724, -59594:38726, -59595:38728, -59596:38722, -59597:38729, -59598:38748, -59599:38752, -59600:38756, -59601:38758, -59602:38760, -59603:21202, -59604:38763, -59605:38769, -59606:38777, -59607:38789, -59608:38780, -59609:38785, -59610:38778, -59611:38790, -59612:38795, -59613:38799, -59614:38800, -59615:38812, -59616:38824, -59617:38822, -59618:38819, -59619:38835, -59620:38836, -59621:38851, -59622:38854, -59623:38856, -59624:38859, -59625:38876, -59626:38893, -59627:40783, -59628:38898, -59629:31455, -59630:38902, -59631:38901, -59632:38927, -59633:38924, -59634:38968, -59635:38948, -59636:38945, -59637:38967, -59638:38973, -59639:38982, -59640:38991, -59641:38987, -59642:39019, -59643:39023, -59644:39024, -59712:39025, -59713:39028, -59714:39027, -59715:39082, -59716:39087, -59717:39089, -59718:39094, -59719:39108, -59720:39107, -59721:39110, -59722:39145, -59723:39147, -59724:39171, -59725:39177, -59726:39186, -59727:39188, -59728:39192, -59729:39201, -59730:39197, -59731:39198, -59732:39204, -59733:39200, -59734:39212, -59735:39214, -59736:39229, -59737:39230, -59738:39234, -59739:39241, -59740:39237, -59741:39248, -59742:39243, -59743:39249, -59744:39250, -59745:39244, -59746:39253, -59747:39319, -59748:39320, -59749:39333, -59750:39341, -59751:39342, -59752:39356, -59753:39391, -59754:39387, -59755:39389, -59756:39384, -59757:39377, -59758:39405, -59759:39406, -59760:39409, -59761:39410, -59762:39419, -59763:39416, -59764:39425, -59765:39439, -59766:39429, -59767:39394, -59768:39449, -59769:39467, -59770:39479, -59771:39493, -59772:39490, -59773:39488, -59774:39491, -59776:39486, -59777:39509, -59778:39501, -59779:39515, -59780:39511, -59781:39519, -59782:39522, -59783:39525, -59784:39524, -59785:39529, -59786:39531, -59787:39530, -59788:39597, -59789:39600, -59790:39612, -59791:39616, -59792:39631, -59793:39633, -59794:39635, -59795:39636, -59796:39646, -59797:39647, -59798:39650, -59799:39651, -59800:39654, -59801:39663, -59802:39659, -59803:39662, -59804:39668, -59805:39665, -59806:39671, -59807:39675, -59808:39686, -59809:39704, -59810:39706, -59811:39711, -59812:39714, -59813:39715, -59814:39717, -59815:39719, -59816:39720, -59817:39721, -59818:39722, -59819:39726, -59820:39727, -59821:39730, -59822:39748, -59823:39747, -59824:39759, -59825:39757, -59826:39758, -59827:39761, -59828:39768, -59829:39796, -59830:39827, -59831:39811, -59832:39825, -59833:39830, -59834:39831, -59835:39839, -59836:39840, -59837:39848, -59838:39860, -59839:39872, -59840:39882, -59841:39865, -59842:39878, -59843:39887, -59844:39889, -59845:39890, -59846:39907, -59847:39906, -59848:39908, -59849:39892, -59850:39905, -59851:39994, -59852:39922, -59853:39921, -59854:39920, -59855:39957, -59856:39956, -59857:39945, -59858:39955, -59859:39948, -59860:39942, -59861:39944, -59862:39954, -59863:39946, -59864:39940, -59865:39982, -59866:39963, -59867:39973, -59868:39972, -59869:39969, -59870:39984, -59871:40007, -59872:39986, -59873:40006, -59874:39998, -59875:40026, -59876:40032, -59877:40039, -59878:40054, -59879:40056, -59880:40167, -59881:40172, -59882:40176, -59883:40201, -59884:40200, -59885:40171, -59886:40195, -59887:40198, -59888:40234, -59889:40230, -59890:40367, -59891:40227, -59892:40223, -59893:40260, -59894:40213, -59895:40210, -59896:40257, -59897:40255, -59898:40254, -59899:40262, -59900:40264, -59968:40285, -59969:40286, -59970:40292, -59971:40273, -59972:40272, -59973:40281, -59974:40306, -59975:40329, -59976:40327, -59977:40363, -59978:40303, -59979:40314, -59980:40346, -59981:40356, -59982:40361, -59983:40370, -59984:40388, -59985:40385, -59986:40379, -59987:40376, -59988:40378, -59989:40390, -59990:40399, -59991:40386, -59992:40409, -59993:40403, -59994:40440, -59995:40422, -59996:40429, -59997:40431, -59998:40445, -59999:40474, -60000:40475, -60001:40478, -60002:40565, -60003:40569, -60004:40573, -60005:40577, -60006:40584, -60007:40587, -60008:40588, -60009:40594, -60010:40597, -60011:40593, -60012:40605, -60013:40613, -60014:40617, -60015:40632, -60016:40618, -60017:40621, -60018:38753, -60019:40652, -60020:40654, -60021:40655, -60022:40656, -60023:40660, -60024:40668, -60025:40670, -60026:40669, -60027:40672, -60028:40677, -60029:40680, -60030:40687, -60032:40692, -60033:40694, -60034:40695, -60035:40697, -60036:40699, -60037:40700, -60038:40701, -60039:40711, -60040:40712, -60041:30391, -60042:40725, -60043:40737, -60044:40748, -60045:40766, -60046:40778, -60047:40786, -60048:40788, -60049:40803, -60050:40799, -60051:40800, -60052:40801, -60053:40806, -60054:40807, -60055:40812, -60056:40810, -60057:40823, -60058:40818, -60059:40822, -60060:40853, -60061:40860, -60062:40864, -60063:22575, -60064:27079, -60065:36953, -60066:29796, -60067:20956, -60068:29081, -60736:32394, -60737:35100, -60738:37704, -60739:37512, -60740:34012, -60741:20425, -60742:28859, -60743:26161, -60744:26824, -60745:37625, -60746:26363, -60747:24389, -60748:20008, -60749:20193, -60750:20220, -60751:20224, -60752:20227, -60753:20281, -60754:20310, -60755:20370, -60756:20362, -60757:20378, -60758:20372, -60759:20429, -60760:20544, -60761:20514, -60762:20479, -60763:20510, -60764:20550, -60765:20592, -60766:20546, -60767:20628, -60768:20724, -60769:20696, -60770:20810, -60771:20836, -60772:20893, -60773:20926, -60774:20972, -60775:21013, -60776:21148, -60777:21158, -60778:21184, -60779:21211, -60780:21248, -60781:21255, -60782:21284, -60783:21362, -60784:21395, -60785:21426, -60786:21469, -60787:64014, -60788:21660, -60789:21642, -60790:21673, -60791:21759, -60792:21894, -60793:22361, -60794:22373, -60795:22444, -60796:22472, -60797:22471, -60798:64015, -60800:64016, -60801:22686, -60802:22706, -60803:22795, -60804:22867, -60805:22875, -60806:22877, -60807:22883, -60808:22948, -60809:22970, -60810:23382, -60811:23488, -60812:29999, -60813:23512, -60814:23532, -60815:23582, -60816:23718, -60817:23738, -60818:23797, -60819:23847, -60820:23891, -60821:64017, -60822:23874, -60823:23917, -60824:23992, -60825:23993, -60826:24016, -60827:24353, -60828:24372, -60829:24423, -60830:24503, -60831:24542, -60832:24669, -60833:24709, -60834:24714, -60835:24798, -60836:24789, -60837:24864, -60838:24818, -60839:24849, -60840:24887, -60841:24880, -60842:24984, -60843:25107, -60844:25254, -60845:25589, -60846:25696, -60847:25757, -60848:25806, -60849:25934, -60850:26112, -60851:26133, -60852:26171, -60853:26121, -60854:26158, -60855:26142, -60856:26148, -60857:26213, -60858:26199, -60859:26201, -60860:64018, -60861:26227, -60862:26265, -60863:26272, -60864:26290, -60865:26303, -60866:26362, -60867:26382, -60868:63785, -60869:26470, -60870:26555, -60871:26706, -60872:26560, -60873:26625, -60874:26692, -60875:26831, -60876:64019, -60877:26984, -60878:64020, -60879:27032, -60880:27106, -60881:27184, -60882:27243, -60883:27206, -60884:27251, -60885:27262, -60886:27362, -60887:27364, -60888:27606, -60889:27711, -60890:27740, -60891:27782, -60892:27759, -60893:27866, -60894:27908, -60895:28039, -60896:28015, -60897:28054, -60898:28076, -60899:28111, -60900:28152, -60901:28146, -60902:28156, -60903:28217, -60904:28252, -60905:28199, -60906:28220, -60907:28351, -60908:28552, -60909:28597, -60910:28661, -60911:28677, -60912:28679, -60913:28712, -60914:28805, -60915:28843, -60916:28943, -60917:28932, -60918:29020, -60919:28998, -60920:28999, -60921:64021, -60922:29121, -60923:29182, -60924:29361, -60992:29374, -60993:29476, -60994:64022, -60995:29559, -60996:29629, -60997:29641, -60998:29654, -60999:29667, -61000:29650, -61001:29703, -61002:29685, -61003:29734, -61004:29738, -61005:29737, -61006:29742, -61007:29794, -61008:29833, -61009:29855, -61010:29953, -61011:30063, -61012:30338, -61013:30364, -61014:30366, -61015:30363, -61016:30374, -61017:64023, -61018:30534, -61019:21167, -61020:30753, -61021:30798, -61022:30820, -61023:30842, -61024:31024, -61025:64024, -61026:64025, -61027:64026, -61028:31124, -61029:64027, -61030:31131, -61031:31441, -61032:31463, -61033:64028, -61034:31467, -61035:31646, -61036:64029, -61037:32072, -61038:32092, -61039:32183, -61040:32160, -61041:32214, -61042:32338, -61043:32583, -61044:32673, -61045:64030, -61046:33537, -61047:33634, -61048:33663, -61049:33735, -61050:33782, -61051:33864, -61052:33972, -61053:34131, -61054:34137, -61056:34155, -61057:64031, -61058:34224, -61059:64032, -61060:64033, -61061:34823, -61062:35061, -61063:35346, -61064:35383, -61065:35449, -61066:35495, -61067:35518, -61068:35551, -61069:64034, -61070:35574, -61071:35667, -61072:35711, -61073:36080, -61074:36084, -61075:36114, -61076:36214, -61077:64035, -61078:36559, -61079:64036, -61080:64037, -61081:36967, -61082:37086, -61083:64038, -61084:37141, -61085:37159, -61086:37338, -61087:37335, -61088:37342, -61089:37357, -61090:37358, -61091:37348, -61092:37349, -61093:37382, -61094:37392, -61095:37386, -61096:37434, -61097:37440, -61098:37436, -61099:37454, -61100:37465, -61101:37457, -61102:37433, -61103:37479, -61104:37543, -61105:37495, -61106:37496, -61107:37607, -61108:37591, -61109:37593, -61110:37584, -61111:64039, -61112:37589, -61113:37600, -61114:37587, -61115:37669, -61116:37665, -61117:37627, -61118:64040, -61119:37662, -61120:37631, -61121:37661, -61122:37634, -61123:37744, -61124:37719, -61125:37796, -61126:37830, -61127:37854, -61128:37880, -61129:37937, -61130:37957, -61131:37960, -61132:38290, -61133:63964, -61134:64041, -61135:38557, -61136:38575, -61137:38707, -61138:38715, -61139:38723, -61140:38733, -61141:38735, -61142:38737, -61143:38741, -61144:38999, -61145:39013, -61146:64042, -61147:64043, -61148:39207, -61149:64044, -61150:39326, -61151:39502, -61152:39641, -61153:39644, -61154:39797, -61155:39794, -61156:39823, -61157:39857, -61158:39867, -61159:39936, -61160:40304, -61161:40299, -61162:64045, -61163:40473, -61164:40657, -61167:8560, -61168:8561, -61169:8562, -61170:8563, -61171:8564, -61172:8565, -61173:8566, -61174:8567, -61175:8568, -61176:8569, -61177:65506, -61178:65508, -61179:65287, -61180:65282, -61504:57344, -61505:57345, -61506:57346, -61507:57347, -61508:57348, -61509:57349, -61510:57350, -61511:57351, -61512:57352, -61513:57353, -61514:57354, -61515:57355, -61516:57356, -61517:57357, -61518:57358, -61519:57359, -61520:57360, -61521:57361, -61522:57362, -61523:57363, -61524:57364, -61525:57365, -61526:57366, -61527:57367, -61528:57368, -61529:57369, -61530:57370, -61531:57371, -61532:57372, -61533:57373, -61534:57374, -61535:57375, -61536:57376, -61537:57377, -61538:57378, -61539:57379, -61540:57380, -61541:57381, -61542:57382, -61543:57383, -61544:57384, -61545:57385, -61546:57386, -61547:57387, -61548:57388, -61549:57389, -61550:57390, -61551:57391, -61552:57392, -61553:57393, -61554:57394, -61555:57395, -61556:57396, -61557:57397, -61558:57398, -61559:57399, -61560:57400, -61561:57401, -61562:57402, -61563:57403, -61564:57404, -61565:57405, -61566:57406, -61568:57407, -61569:57408, -61570:57409, -61571:57410, -61572:57411, -61573:57412, -61574:57413, -61575:57414, -61576:57415, -61577:57416, -61578:57417, -61579:57418, -61580:57419, -61581:57420, -61582:57421, -61583:57422, -61584:57423, -61585:57424, -61586:57425, -61587:57426, -61588:57427, -61589:57428, -61590:57429, -61591:57430, -61592:57431, -61593:57432, -61594:57433, -61595:57434, -61596:57435, -61597:57436, -61598:57437, -61599:57438, -61600:57439, -61601:57440, -61602:57441, -61603:57442, -61604:57443, -61605:57444, -61606:57445, -61607:57446, -61608:57447, -61609:57448, -61610:57449, -61611:57450, -61612:57451, -61613:57452, -61614:57453, -61615:57454, -61616:57455, -61617:57456, -61618:57457, -61619:57458, -61620:57459, -61621:57460, -61622:57461, -61623:57462, -61624:57463, -61625:57464, -61626:57465, -61627:57466, -61628:57467, -61629:57468, -61630:57469, -61631:57470, -61632:57471, -61633:57472, -61634:57473, -61635:57474, -61636:57475, -61637:57476, -61638:57477, -61639:57478, -61640:57479, -61641:57480, -61642:57481, -61643:57482, -61644:57483, -61645:57484, -61646:57485, -61647:57486, -61648:57487, -61649:57488, -61650:57489, -61651:57490, -61652:57491, -61653:57492, -61654:57493, -61655:57494, -61656:57495, -61657:57496, -61658:57497, -61659:57498, -61660:57499, -61661:57500, -61662:57501, -61663:57502, -61664:57503, -61665:57504, -61666:57505, -61667:57506, -61668:57507, -61669:57508, -61670:57509, -61671:57510, -61672:57511, -61673:57512, -61674:57513, -61675:57514, -61676:57515, -61677:57516, -61678:57517, -61679:57518, -61680:57519, -61681:57520, -61682:57521, -61683:57522, -61684:57523, -61685:57524, -61686:57525, -61687:57526, -61688:57527, -61689:57528, -61690:57529, -61691:57530, -61692:57531, -61760:57532, -61761:57533, -61762:57534, -61763:57535, -61764:57536, -61765:57537, -61766:57538, -61767:57539, -61768:57540, -61769:57541, -61770:57542, -61771:57543, -61772:57544, -61773:57545, -61774:57546, -61775:57547, -61776:57548, -61777:57549, -61778:57550, -61779:57551, -61780:57552, -61781:57553, -61782:57554, -61783:57555, -61784:57556, -61785:57557, -61786:57558, -61787:57559, -61788:57560, -61789:57561, -61790:57562, -61791:57563, -61792:57564, -61793:57565, -61794:57566, -61795:57567, -61796:57568, -61797:57569, -61798:57570, -61799:57571, -61800:57572, -61801:57573, -61802:57574, -61803:57575, -61804:57576, -61805:57577, -61806:57578, -61807:57579, -61808:57580, -61809:57581, -61810:57582, -61811:57583, -61812:57584, -61813:57585, -61814:57586, -61815:57587, -61816:57588, -61817:57589, -61818:57590, -61819:57591, -61820:57592, -61821:57593, -61822:57594, -61824:57595, -61825:57596, -61826:57597, -61827:57598, -61828:57599, -61829:57600, -61830:57601, -61831:57602, -61832:57603, -61833:57604, -61834:57605, -61835:57606, -61836:57607, -61837:57608, -61838:57609, -61839:57610, -61840:57611, -61841:57612, -61842:57613, -61843:57614, -61844:57615, -61845:57616, -61846:57617, -61847:57618, -61848:57619, -61849:57620, -61850:57621, -61851:57622, -61852:57623, -61853:57624, -61854:57625, -61855:57626, -61856:57627, -61857:57628, -61858:57629, -61859:57630, -61860:57631, -61861:57632, -61862:57633, -61863:57634, -61864:57635, -61865:57636, -61866:57637, -61867:57638, -61868:57639, -61869:57640, -61870:57641, -61871:57642, -61872:57643, -61873:57644, -61874:57645, -61875:57646, -61876:57647, -61877:57648, -61878:57649, -61879:57650, -61880:57651, -61881:57652, -61882:57653, -61883:57654, -61884:57655, -61885:57656, -61886:57657, -61887:57658, -61888:57659, -61889:57660, -61890:57661, -61891:57662, -61892:57663, -61893:57664, -61894:57665, -61895:57666, -61896:57667, -61897:57668, -61898:57669, -61899:57670, -61900:57671, -61901:57672, -61902:57673, -61903:57674, -61904:57675, -61905:57676, -61906:57677, -61907:57678, -61908:57679, -61909:57680, -61910:57681, -61911:57682, -61912:57683, -61913:57684, -61914:57685, -61915:57686, -61916:57687, -61917:57688, -61918:57689, -61919:57690, -61920:57691, -61921:57692, -61922:57693, -61923:57694, -61924:57695, -61925:57696, -61926:57697, -61927:57698, -61928:57699, -61929:57700, -61930:57701, -61931:57702, -61932:57703, -61933:57704, -61934:57705, -61935:57706, -61936:57707, -61937:57708, -61938:57709, -61939:57710, -61940:57711, -61941:57712, -61942:57713, -61943:57714, -61944:57715, -61945:57716, -61946:57717, -61947:57718, -61948:57719, -62016:57720, -62017:57721, -62018:57722, -62019:57723, -62020:57724, -62021:57725, -62022:57726, -62023:57727, -62024:57728, -62025:57729, -62026:57730, -62027:57731, -62028:57732, -62029:57733, -62030:57734, -62031:57735, -62032:57736, -62033:57737, -62034:57738, -62035:57739, -62036:57740, -62037:57741, -62038:57742, -62039:57743, -62040:57744, -62041:57745, -62042:57746, -62043:57747, -62044:57748, -62045:57749, -62046:57750, -62047:57751, -62048:57752, -62049:57753, -62050:57754, -62051:57755, -62052:57756, -62053:57757, -62054:57758, -62055:57759, -62056:57760, -62057:57761, -62058:57762, -62059:57763, -62060:57764, -62061:57765, -62062:57766, -62063:57767, -62064:57768, -62065:57769, -62066:57770, -62067:57771, -62068:57772, -62069:57773, -62070:57774, -62071:57775, -62072:57776, -62073:57777, -62074:57778, -62075:57779, -62076:57780, -62077:57781, -62078:57782, -62080:57783, -62081:57784, -62082:57785, -62083:57786, -62084:57787, -62085:57788, -62086:57789, -62087:57790, -62088:57791, -62089:57792, -62090:57793, -62091:57794, -62092:57795, -62093:57796, -62094:57797, -62095:57798, -62096:57799, -62097:57800, -62098:57801, -62099:57802, -62100:57803, -62101:57804, -62102:57805, -62103:57806, -62104:57807, -62105:57808, -62106:57809, -62107:57810, -62108:57811, -62109:57812, -62110:57813, -62111:57814, -62112:57815, -62113:57816, -62114:57817, -62115:57818, -62116:57819, -62117:57820, -62118:57821, -62119:57822, -62120:57823, -62121:57824, -62122:57825, -62123:57826, -62124:57827, -62125:57828, -62126:57829, -62127:57830, -62128:57831, -62129:57832, -62130:57833, -62131:57834, -62132:57835, -62133:57836, -62134:57837, -62135:57838, -62136:57839, -62137:57840, -62138:57841, -62139:57842, -62140:57843, -62141:57844, -62142:57845, -62143:57846, -62144:57847, -62145:57848, -62146:57849, -62147:57850, -62148:57851, -62149:57852, -62150:57853, -62151:57854, -62152:57855, -62153:57856, -62154:57857, -62155:57858, -62156:57859, -62157:57860, -62158:57861, -62159:57862, -62160:57863, -62161:57864, -62162:57865, -62163:57866, -62164:57867, -62165:57868, -62166:57869, -62167:57870, -62168:57871, -62169:57872, -62170:57873, -62171:57874, -62172:57875, -62173:57876, -62174:57877, -62175:57878, -62176:57879, -62177:57880, -62178:57881, -62179:57882, -62180:57883, -62181:57884, -62182:57885, -62183:57886, -62184:57887, -62185:57888, -62186:57889, -62187:57890, -62188:57891, -62189:57892, -62190:57893, -62191:57894, -62192:57895, -62193:57896, -62194:57897, -62195:57898, -62196:57899, -62197:57900, -62198:57901, -62199:57902, -62200:57903, -62201:57904, -62202:57905, -62203:57906, -62204:57907, -62272:57908, -62273:57909, -62274:57910, -62275:57911, -62276:57912, -62277:57913, -62278:57914, -62279:57915, -62280:57916, -62281:57917, -62282:57918, -62283:57919, -62284:57920, -62285:57921, -62286:57922, -62287:57923, -62288:57924, -62289:57925, -62290:57926, -62291:57927, -62292:57928, -62293:57929, -62294:57930, -62295:57931, -62296:57932, -62297:57933, -62298:57934, -62299:57935, -62300:57936, -62301:57937, -62302:57938, -62303:57939, -62304:57940, -62305:57941, -62306:57942, -62307:57943, -62308:57944, -62309:57945, -62310:57946, -62311:57947, -62312:57948, -62313:57949, -62314:57950, -62315:57951, -62316:57952, -62317:57953, -62318:57954, -62319:57955, -62320:57956, -62321:57957, -62322:57958, -62323:57959, -62324:57960, -62325:57961, -62326:57962, -62327:57963, -62328:57964, -62329:57965, -62330:57966, -62331:57967, -62332:57968, -62333:57969, -62334:57970, -62336:57971, -62337:57972, -62338:57973, -62339:57974, -62340:57975, -62341:57976, -62342:57977, -62343:57978, -62344:57979, -62345:57980, -62346:57981, -62347:57982, -62348:57983, -62349:57984, -62350:57985, -62351:57986, -62352:57987, -62353:57988, -62354:57989, -62355:57990, -62356:57991, -62357:57992, -62358:57993, -62359:57994, -62360:57995, -62361:57996, -62362:57997, -62363:57998, -62364:57999, -62365:58000, -62366:58001, -62367:58002, -62368:58003, -62369:58004, -62370:58005, -62371:58006, -62372:58007, -62373:58008, -62374:58009, -62375:58010, -62376:58011, -62377:58012, -62378:58013, -62379:58014, -62380:58015, -62381:58016, -62382:58017, -62383:58018, -62384:58019, -62385:58020, -62386:58021, -62387:58022, -62388:58023, -62389:58024, -62390:58025, -62391:58026, -62392:58027, -62393:58028, -62394:58029, -62395:58030, -62396:58031, -62397:58032, -62398:58033, -62399:58034, -62400:58035, -62401:58036, -62402:58037, -62403:58038, -62404:58039, -62405:58040, -62406:58041, -62407:58042, -62408:58043, -62409:58044, -62410:58045, -62411:58046, -62412:58047, -62413:58048, -62414:58049, -62415:58050, -62416:58051, -62417:58052, -62418:58053, -62419:58054, -62420:58055, -62421:58056, -62422:58057, -62423:58058, -62424:58059, -62425:58060, -62426:58061, -62427:58062, -62428:58063, -62429:58064, -62430:58065, -62431:58066, -62432:58067, -62433:58068, -62434:58069, -62435:58070, -62436:58071, -62437:58072, -62438:58073, -62439:58074, -62440:58075, -62441:58076, -62442:58077, -62443:58078, -62444:58079, -62445:58080, -62446:58081, -62447:58082, -62448:58083, -62449:58084, -62450:58085, -62451:58086, -62452:58087, -62453:58088, -62454:58089, -62455:58090, -62456:58091, -62457:58092, -62458:58093, -62459:58094, -62460:58095, -62528:58096, -62529:58097, -62530:58098, -62531:58099, -62532:58100, -62533:58101, -62534:58102, -62535:58103, -62536:58104, -62537:58105, -62538:58106, -62539:58107, -62540:58108, -62541:58109, -62542:58110, -62543:58111, -62544:58112, -62545:58113, -62546:58114, -62547:58115, -62548:58116, -62549:58117, -62550:58118, -62551:58119, -62552:58120, -62553:58121, -62554:58122, -62555:58123, -62556:58124, -62557:58125, -62558:58126, -62559:58127, -62560:58128, -62561:58129, -62562:58130, -62563:58131, -62564:58132, -62565:58133, -62566:58134, -62567:58135, -62568:58136, -62569:58137, -62570:58138, -62571:58139, -62572:58140, -62573:58141, -62574:58142, -62575:58143, -62576:58144, -62577:58145, -62578:58146, -62579:58147, -62580:58148, -62581:58149, -62582:58150, -62583:58151, -62584:58152, -62585:58153, -62586:58154, -62587:58155, -62588:58156, -62589:58157, -62590:58158, -62592:58159, -62593:58160, -62594:58161, -62595:58162, -62596:58163, -62597:58164, -62598:58165, -62599:58166, -62600:58167, -62601:58168, -62602:58169, -62603:58170, -62604:58171, -62605:58172, -62606:58173, -62607:58174, -62608:58175, -62609:58176, -62610:58177, -62611:58178, -62612:58179, -62613:58180, -62614:58181, -62615:58182, -62616:58183, -62617:58184, -62618:58185, -62619:58186, -62620:58187, -62621:58188, -62622:58189, -62623:58190, -62624:58191, -62625:58192, -62626:58193, -62627:58194, -62628:58195, -62629:58196, -62630:58197, -62631:58198, -62632:58199, -62633:58200, -62634:58201, -62635:58202, -62636:58203, -62637:58204, -62638:58205, -62639:58206, -62640:58207, -62641:58208, -62642:58209, -62643:58210, -62644:58211, -62645:58212, -62646:58213, -62647:58214, -62648:58215, -62649:58216, -62650:58217, -62651:58218, -62652:58219, -62653:58220, -62654:58221, -62655:58222, -62656:58223, -62657:58224, -62658:58225, -62659:58226, -62660:58227, -62661:58228, -62662:58229, -62663:58230, -62664:58231, -62665:58232, -62666:58233, -62667:58234, -62668:58235, -62669:58236, -62670:58237, -62671:58238, -62672:58239, -62673:58240, -62674:58241, -62675:58242, -62676:58243, -62677:58244, -62678:58245, -62679:58246, -62680:58247, -62681:58248, -62682:58249, -62683:58250, -62684:58251, -62685:58252, -62686:58253, -62687:58254, -62688:58255, -62689:58256, -62690:58257, -62691:58258, -62692:58259, -62693:58260, -62694:58261, -62695:58262, -62696:58263, -62697:58264, -62698:58265, -62699:58266, -62700:58267, -62701:58268, -62702:58269, -62703:58270, -62704:58271, -62705:58272, -62706:58273, -62707:58274, -62708:58275, -62709:58276, -62710:58277, -62711:58278, -62712:58279, -62713:58280, -62714:58281, -62715:58282, -62716:58283, -62784:58284, -62785:58285, -62786:58286, -62787:58287, -62788:58288, -62789:58289, -62790:58290, -62791:58291, -62792:58292, -62793:58293, -62794:58294, -62795:58295, -62796:58296, -62797:58297, -62798:58298, -62799:58299, -62800:58300, -62801:58301, -62802:58302, -62803:58303, -62804:58304, -62805:58305, -62806:58306, -62807:58307, -62808:58308, -62809:58309, -62810:58310, -62811:58311, -62812:58312, -62813:58313, -62814:58314, -62815:58315, -62816:58316, -62817:58317, -62818:58318, -62819:58319, -62820:58320, -62821:58321, -62822:58322, -62823:58323, -62824:58324, -62825:58325, -62826:58326, -62827:58327, -62828:58328, -62829:58329, -62830:58330, -62831:58331, -62832:58332, -62833:58333, -62834:58334, -62835:58335, -62836:58336, -62837:58337, -62838:58338, -62839:58339, -62840:58340, -62841:58341, -62842:58342, -62843:58343, -62844:58344, -62845:58345, -62846:58346, -62848:58347, -62849:58348, -62850:58349, -62851:58350, -62852:58351, -62853:58352, -62854:58353, -62855:58354, -62856:58355, -62857:58356, -62858:58357, -62859:58358, -62860:58359, -62861:58360, -62862:58361, -62863:58362, -62864:58363, -62865:58364, -62866:58365, -62867:58366, -62868:58367, -62869:58368, -62870:58369, -62871:58370, -62872:58371, -62873:58372, -62874:58373, -62875:58374, -62876:58375, -62877:58376, -62878:58377, -62879:58378, -62880:58379, -62881:58380, -62882:58381, -62883:58382, -62884:58383, -62885:58384, -62886:58385, -62887:58386, -62888:58387, -62889:58388, -62890:58389, -62891:58390, -62892:58391, -62893:58392, -62894:58393, -62895:58394, -62896:58395, -62897:58396, -62898:58397, -62899:58398, -62900:58399, -62901:58400, -62902:58401, -62903:58402, -62904:58403, -62905:58404, -62906:58405, -62907:58406, -62908:58407, -62909:58408, -62910:58409, -62911:58410, -62912:58411, -62913:58412, -62914:58413, -62915:58414, -62916:58415, -62917:58416, -62918:58417, -62919:58418, -62920:58419, -62921:58420, -62922:58421, -62923:58422, -62924:58423, -62925:58424, -62926:58425, -62927:58426, -62928:58427, -62929:58428, -62930:58429, -62931:58430, -62932:58431, -62933:58432, -62934:58433, -62935:58434, -62936:58435, -62937:58436, -62938:58437, -62939:58438, -62940:58439, -62941:58440, -62942:58441, -62943:58442, -62944:58443, -62945:58444, -62946:58445, -62947:58446, -62948:58447, -62949:58448, -62950:58449, -62951:58450, -62952:58451, -62953:58452, -62954:58453, -62955:58454, -62956:58455, -62957:58456, -62958:58457, -62959:58458, -62960:58459, -62961:58460, -62962:58461, -62963:58462, -62964:58463, -62965:58464, -62966:58465, -62967:58466, -62968:58467, -62969:58468, -62970:58469, -62971:58470, -62972:58471, -63040:58472, -63041:58473, -63042:58474, -63043:58475, -63044:58476, -63045:58477, -63046:58478, -63047:58479, -63048:58480, -63049:58481, -63050:58482, -63051:58483, -63052:58484, -63053:58485, -63054:58486, -63055:58487, -63056:58488, -63057:58489, -63058:58490, -63059:58491, -63060:58492, -63061:58493, -63062:58494, -63063:58495, -63064:58496, -63065:58497, -63066:58498, -63067:58499, -63068:58500, -63069:58501, -63070:58502, -63071:58503, -63072:58504, -63073:58505, -63074:58506, -63075:58507, -63076:58508, -63077:58509, -63078:58510, -63079:58511, -63080:58512, -63081:58513, -63082:58514, -63083:58515, -63084:58516, -63085:58517, -63086:58518, -63087:58519, -63088:58520, -63089:58521, -63090:58522, -63091:58523, -63092:58524, -63093:58525, -63094:58526, -63095:58527, -63096:58528, -63097:58529, -63098:58530, -63099:58531, -63100:58532, -63101:58533, -63102:58534, -63104:58535, -63105:58536, -63106:58537, -63107:58538, -63108:58539, -63109:58540, -63110:58541, -63111:58542, -63112:58543, -63113:58544, -63114:58545, -63115:58546, -63116:58547, -63117:58548, -63118:58549, -63119:58550, -63120:58551, -63121:58552, -63122:58553, -63123:58554, -63124:58555, -63125:58556, -63126:58557, -63127:58558, -63128:58559, -63129:58560, -63130:58561, -63131:58562, -63132:58563, -63133:58564, -63134:58565, -63135:58566, -63136:58567, -63137:58568, -63138:58569, -63139:58570, -63140:58571, -63141:58572, -63142:58573, -63143:58574, -63144:58575, -63145:58576, -63146:58577, -63147:58578, -63148:58579, -63149:58580, -63150:58581, -63151:58582, -63152:58583, -63153:58584, -63154:58585, -63155:58586, -63156:58587, -63157:58588, -63158:58589, -63159:58590, -63160:58591, -63161:58592, -63162:58593, -63163:58594, -63164:58595, -63165:58596, -63166:58597, -63167:58598, -63168:58599, -63169:58600, -63170:58601, -63171:58602, -63172:58603, -63173:58604, -63174:58605, -63175:58606, -63176:58607, -63177:58608, -63178:58609, -63179:58610, -63180:58611, -63181:58612, -63182:58613, -63183:58614, -63184:58615, -63185:58616, -63186:58617, -63187:58618, -63188:58619, -63189:58620, -63190:58621, -63191:58622, -63192:58623, -63193:58624, -63194:58625, -63195:58626, -63196:58627, -63197:58628, -63198:58629, -63199:58630, -63200:58631, -63201:58632, -63202:58633, -63203:58634, -63204:58635, -63205:58636, -63206:58637, -63207:58638, -63208:58639, -63209:58640, -63210:58641, -63211:58642, -63212:58643, -63213:58644, -63214:58645, -63215:58646, -63216:58647, -63217:58648, -63218:58649, -63219:58650, -63220:58651, -63221:58652, -63222:58653, -63223:58654, -63224:58655, -63225:58656, -63226:58657, -63227:58658, -63228:58659, -63296:58660, -63297:58661, -63298:58662, -63299:58663, -63300:58664, -63301:58665, -63302:58666, -63303:58667, -63304:58668, -63305:58669, -63306:58670, -63307:58671, -63308:58672, -63309:58673, -63310:58674, -63311:58675, -63312:58676, -63313:58677, -63314:58678, -63315:58679, -63316:58680, -63317:58681, -63318:58682, -63319:58683, -63320:58684, -63321:58685, -63322:58686, -63323:58687, -63324:58688, -63325:58689, -63326:58690, -63327:58691, -63328:58692, -63329:58693, -63330:58694, -63331:58695, -63332:58696, -63333:58697, -63334:58698, -63335:58699, -63336:58700, -63337:58701, -63338:58702, -63339:58703, -63340:58704, -63341:58705, -63342:58706, -63343:58707, -63344:58708, -63345:58709, -63346:58710, -63347:58711, -63348:58712, -63349:58713, -63350:58714, -63351:58715, -63352:58716, -63353:58717, -63354:58718, -63355:58719, -63356:58720, -63357:58721, -63358:58722, -63360:58723, -63361:58724, -63362:58725, -63363:58726, -63364:58727, -63365:58728, -63366:58729, -63367:58730, -63368:58731, -63369:58732, -63370:58733, -63371:58734, -63372:58735, -63373:58736, -63374:58737, -63375:58738, -63376:58739, -63377:58740, -63378:58741, -63379:58742, -63380:58743, -63381:58744, -63382:58745, -63383:58746, -63384:58747, -63385:58748, -63386:58749, -63387:58750, -63388:58751, -63389:58752, -63390:58753, -63391:58754, -63392:58755, -63393:58756, -63394:58757, -63395:58758, -63396:58759, -63397:58760, -63398:58761, -63399:58762, -63400:58763, -63401:58764, -63402:58765, -63403:58766, -63404:58767, -63405:58768, -63406:58769, -63407:58770, -63408:58771, -63409:58772, -63410:58773, -63411:58774, -63412:58775, -63413:58776, -63414:58777, -63415:58778, -63416:58779, -63417:58780, -63418:58781, -63419:58782, -63420:58783, -63421:58784, -63422:58785, -63423:58786, -63424:58787, -63425:58788, -63426:58789, -63427:58790, -63428:58791, -63429:58792, -63430:58793, -63431:58794, -63432:58795, -63433:58796, -63434:58797, -63435:58798, -63436:58799, -63437:58800, -63438:58801, -63439:58802, -63440:58803, -63441:58804, -63442:58805, -63443:58806, -63444:58807, -63445:58808, -63446:58809, -63447:58810, -63448:58811, -63449:58812, -63450:58813, -63451:58814, -63452:58815, -63453:58816, -63454:58817, -63455:58818, -63456:58819, -63457:58820, -63458:58821, -63459:58822, -63460:58823, -63461:58824, -63462:58825, -63463:58826, -63464:58827, -63465:58828, -63466:58829, -63467:58830, -63468:58831, -63469:58832, -63470:58833, -63471:58834, -63472:58835, -63473:58836, -63474:58837, -63475:58838, -63476:58839, -63477:58840, -63478:58841, -63479:58842, -63480:58843, -63481:58844, -63482:58845, -63483:58846, -63484:58847, -63552:58848, -63553:58849, -63554:58850, -63555:58851, -63556:58852, -63557:58853, -63558:58854, -63559:58855, -63560:58856, -63561:58857, -63562:58858, -63563:58859, -63564:58860, -63565:58861, -63566:58862, -63567:58863, -63568:58864, -63569:58865, -63570:58866, -63571:58867, -63572:58868, -63573:58869, -63574:58870, -63575:58871, -63576:58872, -63577:58873, -63578:58874, -63579:58875, -63580:58876, -63581:58877, -63582:58878, -63583:58879, -63584:58880, -63585:58881, -63586:58882, -63587:58883, -63588:58884, -63589:58885, -63590:58886, -63591:58887, -63592:58888, -63593:58889, -63594:58890, -63595:58891, -63596:58892, -63597:58893, -63598:58894, -63599:58895, -63600:58896, -63601:58897, -63602:58898, -63603:58899, -63604:58900, -63605:58901, -63606:58902, -63607:58903, -63608:58904, -63609:58905, -63610:58906, -63611:58907, -63612:58908, -63613:58909, -63614:58910, -63616:58911, -63617:58912, -63618:58913, -63619:58914, -63620:58915, -63621:58916, -63622:58917, -63623:58918, -63624:58919, -63625:58920, -63626:58921, -63627:58922, -63628:58923, -63629:58924, -63630:58925, -63631:58926, -63632:58927, -63633:58928, -63634:58929, -63635:58930, -63636:58931, -63637:58932, -63638:58933, -63639:58934, -63640:58935, -63641:58936, -63642:58937, -63643:58938, -63644:58939, -63645:58940, -63646:58941, -63647:58942, -63648:58943, -63649:58944, -63650:58945, -63651:58946, -63652:58947, -63653:58948, -63654:58949, -63655:58950, -63656:58951, -63657:58952, -63658:58953, -63659:58954, -63660:58955, -63661:58956, -63662:58957, -63663:58958, -63664:58959, -63665:58960, -63666:58961, -63667:58962, -63668:58963, -63669:58964, -63670:58965, -63671:58966, -63672:58967, -63673:58968, -63674:58969, -63675:58970, -63676:58971, -63677:58972, -63678:58973, -63679:58974, -63680:58975, -63681:58976, -63682:58977, -63683:58978, -63684:58979, -63685:58980, -63686:58981, -63687:58982, -63688:58983, -63689:58984, -63690:58985, -63691:58986, -63692:58987, -63693:58988, -63694:58989, -63695:58990, -63696:58991, -63697:58992, -63698:58993, -63699:58994, -63700:58995, -63701:58996, -63702:58997, -63703:58998, -63704:58999, -63705:59000, -63706:59001, -63707:59002, -63708:59003, -63709:59004, -63710:59005, -63711:59006, -63712:59007, -63713:59008, -63714:59009, -63715:59010, -63716:59011, -63717:59012, -63718:59013, -63719:59014, -63720:59015, -63721:59016, -63722:59017, -63723:59018, -63724:59019, -63725:59020, -63726:59021, -63727:59022, -63728:59023, -63729:59024, -63730:59025, -63731:59026, -63732:59027, -63733:59028, -63734:59029, -63735:59030, -63736:59031, -63737:59032, -63738:59033, -63739:59034, -63740:59035, -64064:8560, -64065:8561, -64066:8562, -64067:8563, -64068:8564, -64069:8565, -64070:8566, -64071:8567, -64072:8568, -64073:8569, -64074:8544, -64075:8545, -64076:8546, -64077:8547, -64078:8548, -64079:8549, -64080:8550, -64081:8551, -64082:8552, -64083:8553, -64084:65506, -64085:65508, -64086:65287, -64087:65282, -64088:12849, -64089:8470, -64090:8481, -64091:8757, -64092:32394, -64093:35100, -64094:37704, -64095:37512, -64096:34012, -64097:20425, -64098:28859, -64099:26161, -64100:26824, -64101:37625, -64102:26363, -64103:24389, -64104:20008, -64105:20193, -64106:20220, -64107:20224, -64108:20227, -64109:20281, -64110:20310, -64111:20370, -64112:20362, -64113:20378, -64114:20372, -64115:20429, -64116:20544, -64117:20514, -64118:20479, -64119:20510, -64120:20550, -64121:20592, -64122:20546, -64123:20628, -64124:20724, -64125:20696, -64126:20810, -64128:20836, -64129:20893, -64130:20926, -64131:20972, -64132:21013, -64133:21148, -64134:21158, -64135:21184, -64136:21211, -64137:21248, -64138:21255, -64139:21284, -64140:21362, -64141:21395, -64142:21426, -64143:21469, -64144:64014, -64145:21660, -64146:21642, -64147:21673, -64148:21759, -64149:21894, -64150:22361, -64151:22373, -64152:22444, -64153:22472, -64154:22471, -64155:64015, -64156:64016, -64157:22686, -64158:22706, -64159:22795, -64160:22867, -64161:22875, -64162:22877, -64163:22883, -64164:22948, -64165:22970, -64166:23382, -64167:23488, -64168:29999, -64169:23512, -64170:23532, -64171:23582, -64172:23718, -64173:23738, -64174:23797, -64175:23847, -64176:23891, -64177:64017, -64178:23874, -64179:23917, -64180:23992, -64181:23993, -64182:24016, -64183:24353, -64184:24372, -64185:24423, -64186:24503, -64187:24542, -64188:24669, -64189:24709, -64190:24714, -64191:24798, -64192:24789, -64193:24864, -64194:24818, -64195:24849, -64196:24887, -64197:24880, -64198:24984, -64199:25107, -64200:25254, -64201:25589, -64202:25696, -64203:25757, -64204:25806, -64205:25934, -64206:26112, -64207:26133, -64208:26171, -64209:26121, -64210:26158, -64211:26142, -64212:26148, -64213:26213, -64214:26199, -64215:26201, -64216:64018, -64217:26227, -64218:26265, -64219:26272, -64220:26290, -64221:26303, -64222:26362, -64223:26382, -64224:63785, -64225:26470, -64226:26555, -64227:26706, -64228:26560, -64229:26625, -64230:26692, -64231:26831, -64232:64019, -64233:26984, -64234:64020, -64235:27032, -64236:27106, -64237:27184, -64238:27243, -64239:27206, -64240:27251, -64241:27262, -64242:27362, -64243:27364, -64244:27606, -64245:27711, -64246:27740, -64247:27782, -64248:27759, -64249:27866, -64250:27908, -64251:28039, -64252:28015, -64320:28054, -64321:28076, -64322:28111, -64323:28152, -64324:28146, -64325:28156, -64326:28217, -64327:28252, -64328:28199, -64329:28220, -64330:28351, -64331:28552, -64332:28597, -64333:28661, -64334:28677, -64335:28679, -64336:28712, -64337:28805, -64338:28843, -64339:28943, -64340:28932, -64341:29020, -64342:28998, -64343:28999, -64344:64021, -64345:29121, -64346:29182, -64347:29361, -64348:29374, -64349:29476, -64350:64022, -64351:29559, -64352:29629, -64353:29641, -64354:29654, -64355:29667, -64356:29650, -64357:29703, -64358:29685, -64359:29734, -64360:29738, -64361:29737, -64362:29742, -64363:29794, -64364:29833, -64365:29855, -64366:29953, -64367:30063, -64368:30338, -64369:30364, -64370:30366, -64371:30363, -64372:30374, -64373:64023, -64374:30534, -64375:21167, -64376:30753, -64377:30798, -64378:30820, -64379:30842, -64380:31024, -64381:64024, -64382:64025, -64384:64026, -64385:31124, -64386:64027, -64387:31131, -64388:31441, -64389:31463, -64390:64028, -64391:31467, -64392:31646, -64393:64029, -64394:32072, -64395:32092, -64396:32183, -64397:32160, -64398:32214, -64399:32338, -64400:32583, -64401:32673, -64402:64030, -64403:33537, -64404:33634, -64405:33663, -64406:33735, -64407:33782, -64408:33864, -64409:33972, -64410:34131, -64411:34137, -64412:34155, -64413:64031, -64414:34224, -64415:64032, -64416:64033, -64417:34823, -64418:35061, -64419:35346, -64420:35383, -64421:35449, -64422:35495, -64423:35518, -64424:35551, -64425:64034, -64426:35574, -64427:35667, -64428:35711, -64429:36080, -64430:36084, -64431:36114, -64432:36214, -64433:64035, -64434:36559, -64435:64036, -64436:64037, -64437:36967, -64438:37086, -64439:64038, -64440:37141, -64441:37159, -64442:37338, -64443:37335, -64444:37342, -64445:37357, -64446:37358, -64447:37348, -64448:37349, -64449:37382, -64450:37392, -64451:37386, -64452:37434, -64453:37440, -64454:37436, -64455:37454, -64456:37465, -64457:37457, -64458:37433, -64459:37479, -64460:37543, -64461:37495, -64462:37496, -64463:37607, -64464:37591, -64465:37593, -64466:37584, -64467:64039, -64468:37589, -64469:37600, -64470:37587, -64471:37669, -64472:37665, -64473:37627, -64474:64040, -64475:37662, -64476:37631, -64477:37661, -64478:37634, -64479:37744, -64480:37719, -64481:37796, -64482:37830, -64483:37854, -64484:37880, -64485:37937, -64486:37957, -64487:37960, -64488:38290, -64489:63964, -64490:64041, -64491:38557, -64492:38575, -64493:38707, -64494:38715, -64495:38723, -64496:38733, -64497:38735, -64498:38737, -64499:38741, -64500:38999, -64501:39013, -64502:64042, -64503:64043, -64504:39207, -64505:64044, -64506:39326, -64507:39502, -64508:39641, -64576:39644, -64577:39797, -64578:39794, -64579:39823, -64580:39857, -64581:39867, -64582:39936, -64583:40304, -64584:40299, -64585:64045, -64586:40473, -64587:40657 -}; - -/** - * @author takahiro / https://github.com/takahirox - */ - -function DataViewEx ( buffer, littleEndian ) { - - this.dv = new DataView( buffer ); - this.offset = 0; - this.littleEndian = ( littleEndian !== undefined ) ? littleEndian : true; - this.encoder = new CharsetEncoder(); - -} - -DataViewEx.prototype = { - - constructor: DataViewEx, - - getInt8: function () { - - var value = this.dv.getInt8( this.offset ); - this.offset += 1; - return value; - - }, - - getInt8Array: function ( size ) { - - var a = []; - - for ( var i = 0; i < size; i++ ) { - - a.push( this.getInt8() ); - - } - - return a; - - }, - - getUint8: function () { - - var value = this.dv.getUint8( this.offset ); - this.offset += 1; - return value; - - }, - - getUint8Array: function ( size ) { - - var a = []; - - for ( var i = 0; i < size; i++ ) { - - a.push( this.getUint8() ); - - } - - return a; - - }, - - - getInt16: function () { - - var value = this.dv.getInt16( this.offset, this.littleEndian ); - this.offset += 2; - return value; - - }, - - getInt16Array: function ( size ) { - - var a = []; - - for ( var i = 0; i < size; i++ ) { - - a.push( this.getInt16() ); - - } - - return a; - - }, - - getUint16: function () { - - var value = this.dv.getUint16( this.offset, this.littleEndian ); - this.offset += 2; - return value; - - }, - - getUint16Array: function ( size ) { - - var a = []; - - for ( var i = 0; i < size; i++ ) { - - a.push( this.getUint16() ); - - } - - return a; - - }, - - getInt32: function () { - - var value = this.dv.getInt32( this.offset, this.littleEndian ); - this.offset += 4; - return value; - - }, - - getInt32Array: function ( size ) { - - var a = []; - - for ( var i = 0; i < size; i++ ) { - - a.push( this.getInt32() ); - - } - - return a; - - }, - - getUint32: function () { - - var value = this.dv.getUint32( this.offset, this.littleEndian ); - this.offset += 4; - return value; - - }, - - getUint32Array: function ( size ) { - - var a = []; - - for ( var i = 0; i < size; i++ ) { - - a.push( this.getUint32() ); - - } - - return a; - - }, - - getFloat32: function () { - - var value = this.dv.getFloat32( this.offset, this.littleEndian ); - this.offset += 4; - return value; - - }, - - getFloat32Array: function( size ) { - - var a = []; - - for ( var i = 0; i < size; i++ ) { - - a.push( this.getFloat32() ); - - } - - return a; - - }, - - getFloat64: function () { - - var value = this.dv.getFloat64( this.offset, this.littleEndian ); - this.offset += 8; - return value; - - }, - - getFloat64Array: function( size ) { - - var a = []; - - for ( var i = 0; i < size; i++ ) { - - a.push( this.getFloat64() ); - - } - - return a; - - }, - - getIndex: function ( type, isUnsigned ) { - - switch ( type ) { - - case 1: - return ( isUnsigned === true ) ? this.getUint8() : this.getInt8(); - - case 2: - return ( isUnsigned === true ) ? this.getUint16() : this.getInt16(); - - case 4: - return this.getInt32(); // No Uint32 - - default: - throw 'unknown number type ' + type + ' exception.'; - - } - - }, - - getIndexArray: function ( type, size, isUnsigned ) { - - var a = []; - - for ( var i = 0; i < size; i++ ) { - - a.push( this.getIndex( type, isUnsigned ) ); - - } - - return a; - - }, - - getChars: function ( size ) { - - var str = ''; - - while ( size > 0 ) { - - var value = this.getUint8(); - size--; - - if ( value === 0 ) { - - break; - - } - - str += String.fromCharCode( value ); - - } - - while ( size > 0 ) { - - this.getUint8(); - size--; - - } - - return str; - - }, - - getSjisStringsAsUnicode: function ( size ) { - - var a = []; - - while ( size > 0 ) { - - var value = this.getUint8(); - size--; - - if ( value === 0 ) { - - break; - - } - - a.push( value ); - - } - - while ( size > 0 ) { - - this.getUint8(); - size--; - - } - - return this.encoder.s2u( new Uint8Array( a ) ); - - }, - - getUnicodeStrings: function ( size ) { - - var str = ''; - - while ( size > 0 ) { - - var value = this.getUint16(); - size -= 2; - - if ( value === 0 ) { - - break; - - } - - str += String.fromCharCode( value ); - - } - - while ( size > 0 ) { - - this.getUint8(); - size--; - - } - - return str; - - }, - - getTextBuffer: function () { - - var size = this.getUint32(); - return this.getUnicodeStrings( size ); - - } - -}; - -/** - * @author takahiro / https://github.com/takahirox - */ - -function DataCreationHelper () { -} - -DataCreationHelper.prototype = { - - constructor: DataCreationHelper, - - leftToRightVector3: function ( v ) { - - v[ 2 ] = -v[ 2 ]; - - }, - - leftToRightQuaternion: function ( q ) { - - q[ 0 ] = -q[ 0 ]; - q[ 1 ] = -q[ 1 ]; - - }, - - leftToRightEuler: function ( r ) { - - r[ 0 ] = -r[ 0 ]; - r[ 1 ] = -r[ 1 ]; - - }, - - leftToRightIndexOrder: function ( p ) { - - var tmp = p[ 2 ]; - p[ 2 ] = p[ 0 ]; - p[ 0 ] = tmp; - - }, - - leftToRightVector3Range: function ( v1, v2 ) { - - var tmp = -v2[ 2 ]; - v2[ 2 ] = -v1[ 2 ]; - v1[ 2 ] = tmp; - - }, - - leftToRightEulerRange: function ( r1, r2 ) { - - var tmp1 = -r2[ 0 ]; - var tmp2 = -r2[ 1 ]; - r2[ 0 ] = -r1[ 0 ]; - r2[ 1 ] = -r1[ 1 ]; - r1[ 0 ] = tmp1; - r1[ 1 ] = tmp2; - - } - -}; - -/** - * @author takahiro / https://github.com/takahirox - */ - -function Parser() { -} - -Parser.prototype.parsePmd = function ( buffer, leftToRight ) { - - var pmd = {}; - var dv = new DataViewEx( buffer ); - - pmd.metadata = {}; - pmd.metadata.format = 'pmd'; - pmd.metadata.coordinateSystem = 'left'; - - var parseHeader = function () { - - var metadata = pmd.metadata; - metadata.magic = dv.getChars( 3 ); - - if ( metadata.magic !== 'Pmd' ) { - - throw 'PMD file magic is not Pmd, but ' + metadata.magic; - - } - - metadata.version = dv.getFloat32(); - metadata.modelName = dv.getSjisStringsAsUnicode( 20 ); - metadata.comment = dv.getSjisStringsAsUnicode( 256 ); - - }; - - var parseVertices = function () { - - var parseVertex = function () { - - var p = {}; - p.position = dv.getFloat32Array( 3 ); - p.normal = dv.getFloat32Array( 3 ); - p.uv = dv.getFloat32Array( 2 ); - p.skinIndices = dv.getUint16Array( 2 ); - p.skinWeights = [ dv.getUint8() / 100 ]; - p.skinWeights.push( 1.0 - p.skinWeights[ 0 ] ); - p.edgeFlag = dv.getUint8(); - return p; - - }; - - var metadata = pmd.metadata; - metadata.vertexCount = dv.getUint32(); - - pmd.vertices = []; - - for ( var i = 0; i < metadata.vertexCount; i++ ) { - - pmd.vertices.push( parseVertex() ); - - } - - }; - - var parseFaces = function () { - - var parseFace = function () { - - var p = {}; - p.indices = dv.getUint16Array( 3 ); - return p; - - }; - - var metadata = pmd.metadata; - metadata.faceCount = dv.getUint32() / 3; - - pmd.faces = []; - - for ( var i = 0; i < metadata.faceCount; i++ ) { - - pmd.faces.push( parseFace() ); - - } - - }; - - var parseMaterials = function () { - - var parseMaterial = function () { - - var p = {}; - p.diffuse = dv.getFloat32Array( 4 ); - p.shininess = dv.getFloat32(); - p.specular = dv.getFloat32Array( 3 ); - p.ambient = dv.getFloat32Array( 3 ); - p.toonIndex = dv.getInt8(); - p.edgeFlag = dv.getUint8(); - p.faceCount = dv.getUint32() / 3; - p.fileName = dv.getSjisStringsAsUnicode( 20 ); - return p; - - }; - - var metadata = pmd.metadata; - metadata.materialCount = dv.getUint32(); - - pmd.materials = []; - - for ( var i = 0; i < metadata.materialCount; i++ ) { - - pmd.materials.push( parseMaterial() ); - - } - - }; - - var parseBones = function () { - - var parseBone = function () { - - var p = {}; - p.name = dv.getSjisStringsAsUnicode( 20 ); - p.parentIndex = dv.getInt16(); - p.tailIndex = dv.getInt16(); - p.type = dv.getUint8(); - p.ikIndex = dv.getInt16(); - p.position = dv.getFloat32Array( 3 ); - return p; - - }; - - var metadata = pmd.metadata; - metadata.boneCount = dv.getUint16(); - - pmd.bones = []; - - for ( var i = 0; i < metadata.boneCount; i++ ) { - - pmd.bones.push( parseBone() ); - - } - - }; - - var parseIks = function () { - - var parseIk = function () { - - var p = {}; - p.target = dv.getUint16(); - p.effector = dv.getUint16(); - p.linkCount = dv.getUint8(); - p.iteration = dv.getUint16(); - p.maxAngle = dv.getFloat32(); - - p.links = []; - for ( var i = 0; i < p.linkCount; i++ ) { - - var link = {}; - link.index = dv.getUint16(); - p.links.push( link ); - - } - - return p; - - }; - - var metadata = pmd.metadata; - metadata.ikCount = dv.getUint16(); - - pmd.iks = []; - - for ( var i = 0; i < metadata.ikCount; i++ ) { - - pmd.iks.push( parseIk() ); - - } - - }; - - var parseMorphs = function () { - - var parseMorph = function () { - - var p = {}; - p.name = dv.getSjisStringsAsUnicode( 20 ); - p.elementCount = dv.getUint32(); - p.type = dv.getUint8(); - - p.elements = []; - for ( var i = 0; i < p.elementCount; i++ ) { - - p.elements.push( { - index: dv.getUint32(), - position: dv.getFloat32Array( 3 ) - } ) ; - - } - - return p; - - }; - - var metadata = pmd.metadata; - metadata.morphCount = dv.getUint16(); - - pmd.morphs = []; - - for ( var i = 0; i < metadata.morphCount; i++ ) { - - pmd.morphs.push( parseMorph() ); - - } - - - }; - - var parseMorphFrames = function () { - - var parseMorphFrame = function () { - - var p = {}; - p.index = dv.getUint16(); - return p; - - }; - - var metadata = pmd.metadata; - metadata.morphFrameCount = dv.getUint8(); - - pmd.morphFrames = []; - - for ( var i = 0; i < metadata.morphFrameCount; i++ ) { - - pmd.morphFrames.push( parseMorphFrame() ); - - } - - }; - - var parseBoneFrameNames = function () { - - var parseBoneFrameName = function () { - - var p = {}; - p.name = dv.getSjisStringsAsUnicode( 50 ); - return p; - - }; - - var metadata = pmd.metadata; - metadata.boneFrameNameCount = dv.getUint8(); - - pmd.boneFrameNames = []; - - for ( var i = 0; i < metadata.boneFrameNameCount; i++ ) { - - pmd.boneFrameNames.push( parseBoneFrameName() ); - - } - - }; - - var parseBoneFrames = function () { - - var parseBoneFrame = function () { - - var p = {}; - p.boneIndex = dv.getInt16(); - p.frameIndex = dv.getUint8(); - return p; - - }; - - var metadata = pmd.metadata; - metadata.boneFrameCount = dv.getUint32(); - - pmd.boneFrames = []; - - for ( var i = 0; i < metadata.boneFrameCount; i++ ) { - - pmd.boneFrames.push( parseBoneFrame() ); - - } - - }; - - var parseEnglishHeader = function () { - - var metadata = pmd.metadata; - metadata.englishCompatibility = dv.getUint8(); - - if ( metadata.englishCompatibility > 0 ) { - - metadata.englishModelName = dv.getSjisStringsAsUnicode( 20 ); - metadata.englishComment = dv.getSjisStringsAsUnicode( 256 ); - - } - - }; - - var parseEnglishBoneNames = function () { - - var parseEnglishBoneName = function () { - - var p = {}; - p.name = dv.getSjisStringsAsUnicode( 20 ); - return p; - - }; - - var metadata = pmd.metadata; - - if ( metadata.englishCompatibility === 0 ) { - - return; - - } - - pmd.englishBoneNames = []; - - for ( var i = 0; i < metadata.boneCount; i++ ) { - - pmd.englishBoneNames.push( parseEnglishBoneName() ); - - } - - }; - - var parseEnglishMorphNames = function () { - - var parseEnglishMorphName = function () { - - var p = {}; - p.name = dv.getSjisStringsAsUnicode( 20 ); - return p; - - }; - - var metadata = pmd.metadata; - - if ( metadata.englishCompatibility === 0 ) { - - return; - - } - - pmd.englishMorphNames = []; - - for ( var i = 0; i < metadata.morphCount - 1; i++ ) { - - pmd.englishMorphNames.push( parseEnglishMorphName() ); - - } - - }; - - var parseEnglishBoneFrameNames = function () { - - var parseEnglishBoneFrameName = function () { - - var p = {}; - p.name = dv.getSjisStringsAsUnicode( 50 ); - return p; - - }; - - var metadata = pmd.metadata; - - if ( metadata.englishCompatibility === 0 ) { - - return; - - } - - pmd.englishBoneFrameNames = []; - - for ( var i = 0; i < metadata.boneFrameNameCount; i++ ) { - - pmd.englishBoneFrameNames.push( parseEnglishBoneFrameName() ); - - } - - }; - - var parseToonTextures = function () { - - var parseToonTexture = function () { - - var p = {}; - p.fileName = dv.getSjisStringsAsUnicode( 100 ); - return p; - - }; - - pmd.toonTextures = []; - - for ( var i = 0; i < 10; i++ ) { - - pmd.toonTextures.push( parseToonTexture() ); - - } - - }; - - var parseRigidBodies = function () { - - var parseRigidBody = function () { - - var p = {}; - p.name = dv.getSjisStringsAsUnicode( 20 ); - p.boneIndex = dv.getInt16(); - p.groupIndex = dv.getUint8(); - p.groupTarget = dv.getUint16(); - p.shapeType = dv.getUint8(); - p.width = dv.getFloat32(); - p.height = dv.getFloat32(); - p.depth = dv.getFloat32(); - p.position = dv.getFloat32Array( 3 ); - p.rotation = dv.getFloat32Array( 3 ); - p.weight = dv.getFloat32(); - p.positionDamping = dv.getFloat32(); - p.rotationDamping = dv.getFloat32(); - p.restitution = dv.getFloat32(); - p.friction = dv.getFloat32(); - p.type = dv.getUint8(); - return p; - - }; - - var metadata = pmd.metadata; - metadata.rigidBodyCount = dv.getUint32(); - - pmd.rigidBodies = []; - - for ( var i = 0; i < metadata.rigidBodyCount; i++ ) { - - pmd.rigidBodies.push( parseRigidBody() ); - - } - - }; - - var parseConstraints = function () { - - var parseConstraint = function () { - - var p = {}; - p.name = dv.getSjisStringsAsUnicode( 20 ); - p.rigidBodyIndex1 = dv.getUint32(); - p.rigidBodyIndex2 = dv.getUint32(); - p.position = dv.getFloat32Array( 3 ); - p.rotation = dv.getFloat32Array( 3 ); - p.translationLimitation1 = dv.getFloat32Array( 3 ); - p.translationLimitation2 = dv.getFloat32Array( 3 ); - p.rotationLimitation1 = dv.getFloat32Array( 3 ); - p.rotationLimitation2 = dv.getFloat32Array( 3 ); - p.springPosition = dv.getFloat32Array( 3 ); - p.springRotation = dv.getFloat32Array( 3 ); - return p; - - }; - - var metadata = pmd.metadata; - metadata.constraintCount = dv.getUint32(); - - pmd.constraints = []; - - for ( var i = 0; i < metadata.constraintCount; i++ ) { - - pmd.constraints.push( parseConstraint() ); - - } - - }; - - parseHeader(); - parseVertices(); - parseFaces(); - parseMaterials(); - parseBones(); - parseIks(); - parseMorphs(); - parseMorphFrames(); - parseBoneFrameNames(); - parseBoneFrames(); - parseEnglishHeader(); - parseEnglishBoneNames(); - parseEnglishMorphNames(); - parseEnglishBoneFrameNames(); - parseToonTextures(); - parseRigidBodies(); - parseConstraints(); - - if ( leftToRight === true ) this.leftToRightModel( pmd ); - - // console.log( pmd ); // for console debug - - return pmd; - -}; - -Parser.prototype.parsePmx = function ( buffer, leftToRight ) { - - var pmx = {}; - var dv = new DataViewEx( buffer ); - - pmx.metadata = {}; - pmx.metadata.format = 'pmx'; - pmx.metadata.coordinateSystem = 'left'; - - var parseHeader = function () { - - var metadata = pmx.metadata; - metadata.magic = dv.getChars( 4 ); - - // Note: don't remove the last blank space. - if ( metadata.magic !== 'PMX ' ) { - - throw 'PMX file magic is not PMX , but ' + metadata.magic; - - } - - metadata.version = dv.getFloat32(); - - if ( metadata.version !== 2.0 && metadata.version !== 2.1 ) { - - throw 'PMX version ' + metadata.version + ' is not supported.'; - - } - - metadata.headerSize = dv.getUint8(); - metadata.encoding = dv.getUint8(); - metadata.additionalUvNum = dv.getUint8(); - metadata.vertexIndexSize = dv.getUint8(); - metadata.textureIndexSize = dv.getUint8(); - metadata.materialIndexSize = dv.getUint8(); - metadata.boneIndexSize = dv.getUint8(); - metadata.morphIndexSize = dv.getUint8(); - metadata.rigidBodyIndexSize = dv.getUint8(); - metadata.modelName = dv.getTextBuffer(); - metadata.englishModelName = dv.getTextBuffer(); - metadata.comment = dv.getTextBuffer(); - metadata.englishComment = dv.getTextBuffer(); - - }; - - var parseVertices = function () { - - var parseVertex = function () { - - var p = {}; - p.position = dv.getFloat32Array( 3 ); - p.normal = dv.getFloat32Array( 3 ); - p.uv = dv.getFloat32Array( 2 ); - - p.auvs = []; - - for ( var i = 0; i < pmx.metadata.additionalUvNum; i++ ) { - - p.auvs.push( dv.getFloat32Array( 4 ) ); - - } - - p.type = dv.getUint8(); - - var indexSize = metadata.boneIndexSize; - - if ( p.type === 0 ) { // BDEF1 - - p.skinIndices = dv.getIndexArray( indexSize, 1 ); - p.skinWeights = [ 1.0 ]; - - } else if ( p.type === 1 ) { // BDEF2 - - p.skinIndices = dv.getIndexArray( indexSize, 2 ); - p.skinWeights = dv.getFloat32Array( 1 ); - p.skinWeights.push( 1.0 - p.skinWeights[ 0 ] ); - - } else if ( p.type === 2 ) { // BDEF4 - - p.skinIndices = dv.getIndexArray( indexSize, 4 ); - p.skinWeights = dv.getFloat32Array( 4 ); - - } else if ( p.type === 3 ) { // SDEF - - p.skinIndices = dv.getIndexArray( indexSize, 2 ); - p.skinWeights = dv.getFloat32Array( 1 ); - p.skinWeights.push( 1.0 - p.skinWeights[ 0 ] ); - - p.skinC = dv.getFloat32Array( 3 ); - p.skinR0 = dv.getFloat32Array( 3 ); - p.skinR1 = dv.getFloat32Array( 3 ); - - // SDEF is not supported yet and is handled as BDEF2 so far. - // TODO: SDEF support - p.type = 1; - - } else { - - throw 'unsupport bone type ' + p.type + ' exception.'; - - } - - p.edgeRatio = dv.getFloat32(); - return p; - - }; - - var metadata = pmx.metadata; - metadata.vertexCount = dv.getUint32(); - - pmx.vertices = []; - - for ( var i = 0; i < metadata.vertexCount; i++ ) { - - pmx.vertices.push( parseVertex() ); - - } - - }; - - var parseFaces = function () { - - var parseFace = function () { - - var p = {}; - p.indices = dv.getIndexArray( metadata.vertexIndexSize, 3, true ); - return p; - - }; - - var metadata = pmx.metadata; - metadata.faceCount = dv.getUint32() / 3; - - pmx.faces = []; - - for ( var i = 0; i < metadata.faceCount; i++ ) { - - pmx.faces.push( parseFace() ); - - } - - }; - - var parseTextures = function () { - - var parseTexture = function () { - - return dv.getTextBuffer(); - - }; - - var metadata = pmx.metadata; - metadata.textureCount = dv.getUint32(); - - pmx.textures = []; - - for ( var i = 0; i < metadata.textureCount; i++ ) { - - pmx.textures.push( parseTexture() ); - - } - - }; - - var parseMaterials = function () { - - var parseMaterial = function () { - - var p = {}; - p.name = dv.getTextBuffer(); - p.englishName = dv.getTextBuffer(); - p.diffuse = dv.getFloat32Array( 4 ); - p.specular = dv.getFloat32Array( 3 ); - p.shininess = dv.getFloat32(); - p.ambient = dv.getFloat32Array( 3 ); - p.flag = dv.getUint8(); - p.edgeColor = dv.getFloat32Array( 4 ); - p.edgeSize = dv.getFloat32(); - p.textureIndex = dv.getIndex( pmx.metadata.textureIndexSize ); - p.envTextureIndex = dv.getIndex( pmx.metadata.textureIndexSize ); - p.envFlag = dv.getUint8(); - p.toonFlag = dv.getUint8(); - - if ( p.toonFlag === 0 ) { - - p.toonIndex = dv.getIndex( pmx.metadata.textureIndexSize ); - - } else if ( p.toonFlag === 1 ) { - - p.toonIndex = dv.getInt8(); - - } else { - - throw 'unknown toon flag ' + p.toonFlag + ' exception.'; - - } - - p.comment = dv.getTextBuffer(); - p.faceCount = dv.getUint32() / 3; - return p; - - }; - - var metadata = pmx.metadata; - metadata.materialCount = dv.getUint32(); - - pmx.materials = []; - - for ( var i = 0; i < metadata.materialCount; i++ ) { - - pmx.materials.push( parseMaterial() ); - - } - - }; - - var parseBones = function () { - - var parseBone = function () { - - var p = {}; - p.name = dv.getTextBuffer(); - p.englishName = dv.getTextBuffer(); - p.position = dv.getFloat32Array( 3 ); - p.parentIndex = dv.getIndex( pmx.metadata.boneIndexSize ); - p.transformationClass = dv.getUint32(); - p.flag = dv.getUint16(); - - if ( p.flag & 0x1 ) { - - p.connectIndex = dv.getIndex( pmx.metadata.boneIndexSize ); - - } else { - - p.offsetPosition = dv.getFloat32Array( 3 ); - - } - - if ( p.flag & 0x100 || p.flag & 0x200 ) { - - // Note: I don't think Grant is an appropriate name - // but I found that some English translated MMD tools use this term - // so I've named it Grant so far. - // I'd rename to more appropriate name from Grant later. - var grant = {}; - - grant.isLocal = ( p.flag & 0x80 ) !== 0 ? true : false; - grant.affectRotation = ( p.flag & 0x100 ) !== 0 ? true : false; - grant.affectPosition = ( p.flag & 0x200 ) !== 0 ? true : false; - grant.parentIndex = dv.getIndex( pmx.metadata.boneIndexSize ); - grant.ratio = dv.getFloat32(); - - p.grant = grant; - - } - - if ( p.flag & 0x400 ) { - - p.fixAxis = dv.getFloat32Array( 3 ); - - } - - if ( p.flag & 0x800 ) { - - p.localXVector = dv.getFloat32Array( 3 ); - p.localZVector = dv.getFloat32Array( 3 ); - - } - - if ( p.flag & 0x2000 ) { - - p.key = dv.getUint32(); - - } - - if ( p.flag & 0x20 ) { - - var ik = {}; - - ik.effector = dv.getIndex( pmx.metadata.boneIndexSize ); - ik.target = null; - ik.iteration = dv.getUint32(); - ik.maxAngle = dv.getFloat32(); - ik.linkCount = dv.getUint32(); - ik.links = []; - - for ( var i = 0; i < ik.linkCount; i++ ) { - - var link = {}; - link.index = dv.getIndex( pmx.metadata.boneIndexSize ); - link.angleLimitation = dv.getUint8(); - - if ( link.angleLimitation === 1 ) { - - link.lowerLimitationAngle = dv.getFloat32Array( 3 ); - link.upperLimitationAngle = dv.getFloat32Array( 3 ); - - } - - ik.links.push( link ); - - } - - p.ik = ik; - } - - return p; - - }; - - var metadata = pmx.metadata; - metadata.boneCount = dv.getUint32(); - - pmx.bones = []; - - for ( var i = 0; i < metadata.boneCount; i++ ) { - - pmx.bones.push( parseBone() ); - - } - - }; - - var parseMorphs = function () { - - var parseMorph = function () { - - var p = {}; - p.name = dv.getTextBuffer(); - p.englishName = dv.getTextBuffer(); - p.panel = dv.getUint8(); - p.type = dv.getUint8(); - p.elementCount = dv.getUint32(); - p.elements = []; - - for ( var i = 0; i < p.elementCount; i++ ) { - - if ( p.type === 0 ) { // group morph - - var m = {}; - m.index = dv.getIndex( pmx.metadata.morphIndexSize ); - m.ratio = dv.getFloat32(); - p.elements.push( m ); - - } else if ( p.type === 1 ) { // vertex morph - - var m = {}; - m.index = dv.getIndex( pmx.metadata.vertexIndexSize, true ); - m.position = dv.getFloat32Array( 3 ); - p.elements.push( m ); - - } else if ( p.type === 2 ) { // bone morph - - var m = {}; - m.index = dv.getIndex( pmx.metadata.boneIndexSize ); - m.position = dv.getFloat32Array( 3 ); - m.rotation = dv.getFloat32Array( 4 ); - p.elements.push( m ); - - } else if ( p.type === 3 ) { // uv morph - - var m = {}; - m.index = dv.getIndex( pmx.metadata.vertexIndexSize, true ); - m.uv = dv.getFloat32Array( 4 ); - p.elements.push( m ); - - } else if ( p.type === 4 ) { // additional uv1 - - // TODO: implement - - } else if ( p.type === 5 ) { // additional uv2 - - // TODO: implement - - } else if ( p.type === 6 ) { // additional uv3 - - // TODO: implement - - } else if ( p.type === 7 ) { // additional uv4 - - // TODO: implement - - } else if ( p.type === 8 ) { // material morph - - var m = {}; - m.index = dv.getIndex( pmx.metadata.materialIndexSize ); - m.type = dv.getUint8(); - m.diffuse = dv.getFloat32Array( 4 ); - m.specular = dv.getFloat32Array( 3 ); - m.shininess = dv.getFloat32(); - m.ambient = dv.getFloat32Array( 3 ); - m.edgeColor = dv.getFloat32Array( 4 ); - m.edgeSize = dv.getFloat32(); - m.textureColor = dv.getFloat32Array( 4 ); - m.sphereTextureColor = dv.getFloat32Array( 4 ); - m.toonColor = dv.getFloat32Array( 4 ); - p.elements.push( m ); - - } - - } - - return p; - - }; - - var metadata = pmx.metadata; - metadata.morphCount = dv.getUint32(); - - pmx.morphs = []; - - for ( var i = 0; i < metadata.morphCount; i++ ) { - - pmx.morphs.push( parseMorph() ); - - } - - }; - - var parseFrames = function () { - - var parseFrame = function () { - - var p = {}; - p.name = dv.getTextBuffer(); - p.englishName = dv.getTextBuffer(); - p.type = dv.getUint8(); - p.elementCount = dv.getUint32(); - p.elements = []; - - for ( var i = 0; i < p.elementCount; i++ ) { - - var e = {}; - e.target = dv.getUint8(); - e.index = ( e.target === 0 ) ? dv.getIndex( pmx.metadata.boneIndexSize ) : dv.getIndex( pmx.metadata.morphIndexSize ); - p.elements.push( e ); - - } - - return p; - - }; - - var metadata = pmx.metadata; - metadata.frameCount = dv.getUint32(); - - pmx.frames = []; - - for ( var i = 0; i < metadata.frameCount; i++ ) { - - pmx.frames.push( parseFrame() ); - - } - - }; - - var parseRigidBodies = function () { - - var parseRigidBody = function () { - - var p = {}; - p.name = dv.getTextBuffer(); - p.englishName = dv.getTextBuffer(); - p.boneIndex = dv.getIndex( pmx.metadata.boneIndexSize ); - p.groupIndex = dv.getUint8(); - p.groupTarget = dv.getUint16(); - p.shapeType = dv.getUint8(); - p.width = dv.getFloat32(); - p.height = dv.getFloat32(); - p.depth = dv.getFloat32(); - p.position = dv.getFloat32Array( 3 ); - p.rotation = dv.getFloat32Array( 3 ); - p.weight = dv.getFloat32(); - p.positionDamping = dv.getFloat32(); - p.rotationDamping = dv.getFloat32(); - p.restitution = dv.getFloat32(); - p.friction = dv.getFloat32(); - p.type = dv.getUint8(); - return p; - - }; - - var metadata = pmx.metadata; - metadata.rigidBodyCount = dv.getUint32(); - - pmx.rigidBodies = []; - - for ( var i = 0; i < metadata.rigidBodyCount; i++ ) { - - pmx.rigidBodies.push( parseRigidBody() ); - - } - - }; - - var parseConstraints = function () { - - var parseConstraint = function () { - - var p = {}; - p.name = dv.getTextBuffer(); - p.englishName = dv.getTextBuffer(); - p.type = dv.getUint8(); - p.rigidBodyIndex1 = dv.getIndex( pmx.metadata.rigidBodyIndexSize ); - p.rigidBodyIndex2 = dv.getIndex( pmx.metadata.rigidBodyIndexSize ); - p.position = dv.getFloat32Array( 3 ); - p.rotation = dv.getFloat32Array( 3 ); - p.translationLimitation1 = dv.getFloat32Array( 3 ); - p.translationLimitation2 = dv.getFloat32Array( 3 ); - p.rotationLimitation1 = dv.getFloat32Array( 3 ); - p.rotationLimitation2 = dv.getFloat32Array( 3 ); - p.springPosition = dv.getFloat32Array( 3 ); - p.springRotation = dv.getFloat32Array( 3 ); - return p; - - }; - - var metadata = pmx.metadata; - metadata.constraintCount = dv.getUint32(); - - pmx.constraints = []; - - for ( var i = 0; i < metadata.constraintCount; i++ ) { - - pmx.constraints.push( parseConstraint() ); - - } - - }; - - parseHeader(); - parseVertices(); - parseFaces(); - parseTextures(); - parseMaterials(); - parseBones(); - parseMorphs(); - parseFrames(); - parseRigidBodies(); - parseConstraints(); - - if ( leftToRight === true ) this.leftToRightModel( pmx ); - - // console.log( pmx ); // for console debug - - return pmx; - -}; - -Parser.prototype.parseVmd = function ( buffer, leftToRight ) { - - var vmd = {}; - var dv = new DataViewEx( buffer ); - - vmd.metadata = {}; - vmd.metadata.coordinateSystem = 'left'; - - var parseHeader = function () { - - var metadata = vmd.metadata; - metadata.magic = dv.getChars( 30 ); - - if ( metadata.magic !== 'Vocaloid Motion Data 0002' ) { - - throw 'VMD file magic is not Vocaloid Motion Data 0002, but ' + metadata.magic; - - } - - metadata.name = dv.getSjisStringsAsUnicode( 20 ); - - }; - - var parseMotions = function () { - - var parseMotion = function () { - - var p = {}; - p.boneName = dv.getSjisStringsAsUnicode( 15 ); - p.frameNum = dv.getUint32(); - p.position = dv.getFloat32Array( 3 ); - p.rotation = dv.getFloat32Array( 4 ); - p.interpolation = dv.getUint8Array( 64 ); - return p; - - }; - - var metadata = vmd.metadata; - metadata.motionCount = dv.getUint32(); - - vmd.motions = []; - for ( var i = 0; i < metadata.motionCount; i++ ) { - - vmd.motions.push( parseMotion() ); - - } - - }; - - var parseMorphs = function () { - - var parseMorph = function () { - - var p = {}; - p.morphName = dv.getSjisStringsAsUnicode( 15 ); - p.frameNum = dv.getUint32(); - p.weight = dv.getFloat32(); - return p; - - }; - - var metadata = vmd.metadata; - metadata.morphCount = dv.getUint32(); - - vmd.morphs = []; - for ( var i = 0; i < metadata.morphCount; i++ ) { - - vmd.morphs.push( parseMorph() ); - - } - - }; - - var parseCameras = function () { - - var parseCamera = function () { - - var p = {}; - p.frameNum = dv.getUint32(); - p.distance = dv.getFloat32(); - p.position = dv.getFloat32Array( 3 ); - p.rotation = dv.getFloat32Array( 3 ); - p.interpolation = dv.getUint8Array( 24 ); - p.fov = dv.getUint32(); - p.perspective = dv.getUint8(); - return p; - - }; - - var metadata = vmd.metadata; - metadata.cameraCount = dv.getUint32(); - - vmd.cameras = []; - for ( var i = 0; i < metadata.cameraCount; i++ ) { - - vmd.cameras.push( parseCamera() ); - - } - - }; - - parseHeader(); - parseMotions(); - parseMorphs(); - parseCameras(); - - if ( leftToRight === true ) this.leftToRightVmd( vmd ); - - // console.log( vmd ); // for console debug - - return vmd; - -}; - -Parser.prototype.parseVpd = function ( text, leftToRight ) { - - var vpd = {}; - - vpd.metadata = {}; - vpd.metadata.coordinateSystem = 'left'; - - vpd.bones = []; - - var commentPatternG = /\/\/\w*(\r|\n|\r\n)/g; - var newlinePattern = /\r|\n|\r\n/; - - var lines = text.replace( commentPatternG, '' ).split( newlinePattern ); - - function throwError () { - - throw 'the file seems not vpd file.'; - - } - - function checkMagic () { - - if ( lines[ 0 ] !== 'Vocaloid Pose Data file' ) { - - throwError(); - - } - - } - - function parseHeader () { - - if ( lines.length < 4 ) { - - throwError(); - - } - - vpd.metadata.parentFile = lines[ 2 ]; - vpd.metadata.boneCount = parseInt( lines[ 3 ] ); - - } - - function parseBones () { - - var boneHeaderPattern = /^\s*(Bone[0-9]+)\s*\{\s*(.*)$/; - var boneVectorPattern = /^\s*(-?[0-9]+\.[0-9]+)\s*,\s*(-?[0-9]+\.[0-9]+)\s*,\s*(-?[0-9]+\.[0-9]+)\s*;/; - var boneQuaternionPattern = /^\s*(-?[0-9]+\.[0-9]+)\s*,\s*(-?[0-9]+\.[0-9]+)\s*,\s*(-?[0-9]+\.[0-9]+)\s*,\s*(-?[0-9]+\.[0-9]+)\s*;/; - var boneFooterPattern = /^\s*}/; - - var bones = vpd.bones; - var n = null; - var v = null; - var q = null; - - for ( var i = 4; i < lines.length; i++ ) { - - var line = lines[ i ]; - - var result; - - result = line.match( boneHeaderPattern ); - - if ( result !== null ) { - - if ( n !== null ) { - - throwError(); - - } - - n = result[ 2 ]; - - } - - result = line.match( boneVectorPattern ); - - if ( result !== null ) { - - if ( v !== null ) { - - throwError(); - - } - - v = [ - - parseFloat( result[ 1 ] ), - parseFloat( result[ 2 ] ), - parseFloat( result[ 3 ] ) - - ]; - - } - - result = line.match( boneQuaternionPattern ); - - if ( result !== null ) { - - if ( q !== null ) { - - throwError(); - - } - - q = [ - - parseFloat( result[ 1 ] ), - parseFloat( result[ 2 ] ), - parseFloat( result[ 3 ] ), - parseFloat( result[ 4 ] ) - - ]; - - - } - - result = line.match( boneFooterPattern ); - - if ( result !== null ) { - - if ( n === null || v === null || q === null ) { - - throwError(); - - } - - bones.push( { - - name: n, - translation: v, - quaternion: q - - } ); - - n = null; - v = null; - q = null; - - } - - } - - if ( n !== null || v !== null || q !== null ) { - - throwError(); - - } - - } - - checkMagic(); - parseHeader(); - parseBones(); - - if ( leftToRight === true ) this.leftToRightVpd( vpd ); - - // console.log( vpd ); // for console debug - - return vpd; - -}; - -Parser.prototype.mergeVmds = function ( vmds ) { - - var v = {}; - v.metadata = {}; - v.metadata.name = vmds[ 0 ].metadata.name; - v.metadata.coordinateSystem = vmds[ 0 ].metadata.coordinateSystem; - v.metadata.motionCount = 0; - v.metadata.morphCount = 0; - v.metadata.cameraCount = 0; - v.motions = []; - v.morphs = []; - v.cameras = []; - - for ( var i = 0; i < vmds.length; i++ ) { - - var v2 = vmds[ i ]; - - v.metadata.motionCount += v2.metadata.motionCount; - v.metadata.morphCount += v2.metadata.morphCount; - v.metadata.cameraCount += v2.metadata.cameraCount; - - for ( var j = 0; j < v2.metadata.motionCount; j++ ) { - - v.motions.push( v2.motions[ j ] ); - - } - - for ( var j = 0; j < v2.metadata.morphCount; j++ ) { - - v.morphs.push( v2.morphs[ j ] ); - - } - - for ( var j = 0; j < v2.metadata.cameraCount; j++ ) { - - v.cameras.push( v2.cameras[ j ] ); - - } - - } - - return v; - -}; - -Parser.prototype.leftToRightModel = function ( model ) { - - if ( model.metadata.coordinateSystem === 'right' ) { - - return; - - } - - model.metadata.coordinateSystem = 'right'; - - var helper = new DataCreationHelper(); - - for ( var i = 0; i < model.metadata.vertexCount; i++ ) { - - helper.leftToRightVector3( model.vertices[ i ].position ); - helper.leftToRightVector3( model.vertices[ i ].normal ); - - } - - for ( var i = 0; i < model.metadata.faceCount; i++ ) { - - helper.leftToRightIndexOrder( model.faces[ i ].indices ); - - } - - for ( var i = 0; i < model.metadata.boneCount; i++ ) { - - helper.leftToRightVector3( model.bones[ i ].position ); - - } - - // TODO: support other morph for PMX - for ( var i = 0; i < model.metadata.morphCount; i++ ) { - - var m = model.morphs[ i ]; - - if ( model.metadata.format === 'pmx' && m.type !== 1 ) { - - // TODO: implement - continue; - - } - - for ( var j = 0; j < m.elements.length; j++ ) { - - helper.leftToRightVector3( m.elements[ j ].position ); - - } - - } - - for ( var i = 0; i < model.metadata.rigidBodyCount; i++ ) { - - helper.leftToRightVector3( model.rigidBodies[ i ].position ); - helper.leftToRightEuler( model.rigidBodies[ i ].rotation ); - - } - - for ( var i = 0; i < model.metadata.constraintCount; i++ ) { - - helper.leftToRightVector3( model.constraints[ i ].position ); - helper.leftToRightEuler( model.constraints[ i ].rotation ); - helper.leftToRightVector3Range( model.constraints[ i ].translationLimitation1, model.constraints[ i ].translationLimitation2 ); - helper.leftToRightEulerRange( model.constraints[ i ].rotationLimitation1, model.constraints[ i ].rotationLimitation2 ); - - } - -}; - -Parser.prototype.leftToRightVmd = function ( vmd ) { - - if ( vmd.metadata.coordinateSystem === 'right' ) { - - return; - - } - - vmd.metadata.coordinateSystem = 'right'; - - var helper = new DataCreationHelper(); - - for ( var i = 0; i < vmd.metadata.motionCount; i++ ) { - - helper.leftToRightVector3( vmd.motions[ i ].position ); - helper.leftToRightQuaternion( vmd.motions[ i ].rotation ); - - } - - for ( var i = 0; i < vmd.metadata.cameraCount; i++ ) { - - helper.leftToRightVector3( vmd.cameras[ i ].position ); - helper.leftToRightEuler( vmd.cameras[ i ].rotation ); - - } - -}; - -Parser.prototype.leftToRightVpd = function ( vpd ) { - - if ( vpd.metadata.coordinateSystem === 'right' ) { - - return; - - } - - vpd.metadata.coordinateSystem = 'right'; - - var helper = new DataCreationHelper(); - - for ( var i = 0; i < vpd.bones.length; i++ ) { - - helper.leftToRightVector3( vpd.bones[ i ].translation ); - helper.leftToRightQuaternion( vpd.bones[ i ].quaternion ); - - } - -}; - -var MMDParser = { - CharsetEncoder: CharsetEncoder, - Parser: Parser -}; - -export { MMDParser, CharsetEncoder, Parser }; diff --git a/examples/jsm/libs/motion-controllers.module.js b/examples/jsm/libs/motion-controllers.module.js index 9b2caaee6d1c4c..ef36613c5d3e58 100644 --- a/examples/jsm/libs/motion-controllers.module.js +++ b/examples/jsm/libs/motion-controllers.module.js @@ -338,7 +338,7 @@ class MotionController { /** * @param {Object} xrInputSource - The XRInputSource to build the MotionController around * @param {Object} profile - The best matched profile description for the supplied xrInputSource - * @param {Object} assetUrl + * @param {string} assetUrl */ constructor(xrInputSource, profile, assetUrl) { if (!xrInputSource) { diff --git a/examples/jsm/libs/opentype.module.js b/examples/jsm/libs/opentype.module.js index 71032872dbe7e4..7b5d64ea797057 100644 --- a/examples/jsm/libs/opentype.module.js +++ b/examples/jsm/libs/opentype.module.js @@ -8115,30 +8115,6 @@ Substitution.prototype.add = function(feature, sub, script, language) { return undefined; }; -function isBrowser() { - return typeof window !== 'undefined'; -} - -function nodeBufferToArrayBuffer(buffer) { - var ab = new ArrayBuffer(buffer.length); - var view = new Uint8Array(ab); - for (var i = 0; i < buffer.length; ++i) { - view[i] = buffer[i]; - } - - return ab; -} - -function arrayBufferToNodeBuffer(ab) { - var buffer = new Buffer(ab.byteLength); - var view = new Uint8Array(ab); - for (var i = 0; i < buffer.length; ++i) { - buffer[i] = view[i]; - } - - return buffer; -} - function checkArgument(expression, message) { if (!expression) { throw message; @@ -13671,27 +13647,21 @@ Font.prototype.download = function(fileName) { fileName = fileName || familyName.replace(/\s/g, '') + '-' + styleName + '.otf'; var arrayBuffer = this.toArrayBuffer(); - if (isBrowser()) { - window.URL = window.URL || window.webkitURL; + window.URL = window.URL || window.webkitURL; - if (window.URL) { - var dataView = new DataView(arrayBuffer); - var blob = new Blob([dataView], {type: 'font/opentype'}); + if (window.URL) { + var dataView = new DataView(arrayBuffer); + var blob = new Blob([dataView], {type: 'font/opentype'}); - var link = document.createElement('a'); - link.href = window.URL.createObjectURL(blob); - link.download = fileName; + var link = document.createElement('a'); + link.href = window.URL.createObjectURL(blob); + link.download = fileName; - var event = document.createEvent('MouseEvents'); - event.initEvent('click', true, false); - link.dispatchEvent(event); - } else { - console.warn('Font file could not be downloaded. Try using a different browser.'); - } + var event = document.createEvent('MouseEvents'); + event.initEvent('click', true, false); + link.dispatchEvent(event); } else { - var fs = require('fs'); - var buffer = arrayBufferToNodeBuffer(arrayBuffer); - fs.writeFileSync(fileName, buffer); + console.warn('Font file could not be downloaded. Try using a different browser.'); } }; /** @@ -14155,22 +14125,7 @@ var loca = { parse: parseLocaTable }; */ // File loaders ///////////////////////////////////////////////////////// -/** - * Loads a font from a file. The callback throws an error message as the first parameter if it fails - * and the font as an ArrayBuffer in the second parameter if it succeeds. - * @param {string} path - The path of the file - * @param {Function} callback - The function to call when the font load completes - */ -function loadFromFile(path, callback) { - var fs = require('fs'); - fs.readFile(path, function(err, buffer) { - if (err) { - return callback(err.message); - } - callback(null, nodeBufferToArrayBuffer(buffer)); - }); -} /** * Loads a font from a URL. The callback throws an error message as the first parameter if it fails * and the font as an ArrayBuffer in the second parameter if it succeeds. @@ -14507,11 +14462,9 @@ function parseBuffer(buffer, opt) { */ function load(url, callback, opt) { opt = (opt === undefined || opt === null) ? {} : opt; - var isNode = typeof window === 'undefined'; - var loadFn = isNode && !opt.isUrl ? loadFromFile : loadFromUrl; return new Promise(function (resolve, reject) { - loadFn(url, function(err, arrayBuffer) { + loadFromUrl(url, function(err, arrayBuffer) { if (err) { if (callback) { return callback(err); @@ -14538,20 +14491,6 @@ function load(url, callback, opt) { }); } -/** - * Synchronously load the font from a URL or file. - * When done, returns the font object or throws an error. - * @alias opentype.loadSync - * @param {string} url - The URL of the font to load. - * @param {Object} opt - opt.lowMemory - * @return {opentype.Font} - */ -function loadSync(url, opt) { - var fs = require('fs'); - var buffer = fs.readFileSync(url); - return parseBuffer(nodeBufferToArrayBuffer(buffer), opt); -} - var opentype = /*#__PURE__*/Object.freeze({ __proto__: null, Font: Font, @@ -14561,8 +14500,7 @@ var opentype = /*#__PURE__*/Object.freeze({ _parse: parse, parse: parseBuffer, load: load, - loadSync: loadSync }); export default opentype; -export { BoundingBox, Font, Glyph, Path, parse as _parse, load, loadSync, parseBuffer as parse }; +export { BoundingBox, Font, Glyph, Path, parse as _parse, load, parseBuffer as parse }; diff --git a/examples/jsm/libs/rhino3dm/rhino3dm.js b/examples/jsm/libs/rhino3dm/rhino3dm.js index 8cf7ae3a9256a4..8b4170570eaf84 100644 --- a/examples/jsm/libs/rhino3dm/rhino3dm.js +++ b/examples/jsm/libs/rhino3dm/rhino3dm.js @@ -1,12 +1,12 @@ -var rhino3dm = (function() { +var rhino3dm = (() => { var _scriptDir = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined; if (typeof __filename !== 'undefined') _scriptDir = _scriptDir || __filename; return ( -function(rhino3dm) { - rhino3dm = rhino3dm || {}; +function(config) { + var rhino3dm = config || {}; -var Module=typeof rhino3dm!=="undefined"?rhino3dm:{};var readyPromiseResolve,readyPromiseReject;Module["ready"]=new Promise(function(resolve,reject){readyPromiseResolve=resolve;readyPromiseReject=reject});var moduleOverrides={};var key;for(key in Module){if(Module.hasOwnProperty(key)){moduleOverrides[key]=Module[key]}}var arguments_=[];var thisProgram="./this.program";var quit_=function(status,toThrow){throw toThrow};var ENVIRONMENT_IS_WEB=false;var ENVIRONMENT_IS_WORKER=false;var ENVIRONMENT_IS_NODE=false;var ENVIRONMENT_IS_SHELL=false;ENVIRONMENT_IS_WEB=typeof window==="object";ENVIRONMENT_IS_WORKER=typeof importScripts==="function";ENVIRONMENT_IS_NODE=typeof process==="object"&&typeof process.versions==="object"&&typeof process.versions.node==="string";ENVIRONMENT_IS_SHELL=!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_NODE&&!ENVIRONMENT_IS_WORKER;var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var read_,readAsync,readBinary,setWindowTitle;var nodeFS;var nodePath;if(ENVIRONMENT_IS_NODE){if(ENVIRONMENT_IS_WORKER){scriptDirectory=require("path").dirname(scriptDirectory)+"/"}else{scriptDirectory=__dirname+"/"}read_=function shell_read(filename,binary){if(!nodeFS)nodeFS=require("fs");if(!nodePath)nodePath=require("path");filename=nodePath["normalize"](filename);return nodeFS["readFileSync"](filename,binary?null:"utf8")};readBinary=function readBinary(filename){var ret=read_(filename,true);if(!ret.buffer){ret=new Uint8Array(ret)}assert(ret.buffer);return ret};if(process["argv"].length>1){thisProgram=process["argv"][1].replace(/\\/g,"/")}arguments_=process["argv"].slice(2);process["on"]("uncaughtException",function(ex){if(!(ex instanceof ExitStatus)){throw ex}});process["on"]("unhandledRejection",abort);quit_=function(status){process["exit"](status)};Module["inspect"]=function(){return"[Emscripten Module object]"}}else if(ENVIRONMENT_IS_SHELL){if(typeof read!="undefined"){read_=function shell_read(f){return read(f)}}readBinary=function readBinary(f){var data;if(typeof readbuffer==="function"){return new Uint8Array(readbuffer(f))}data=read(f,"binary");assert(typeof data==="object");return data};if(typeof scriptArgs!="undefined"){arguments_=scriptArgs}else if(typeof arguments!="undefined"){arguments_=arguments}if(typeof quit==="function"){quit_=function(status){quit(status)}}if(typeof print!=="undefined"){if(typeof console==="undefined")console={};console.log=print;console.warn=console.error=typeof printErr!=="undefined"?printErr:print}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!=="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(_scriptDir){scriptDirectory=_scriptDir}if(scriptDirectory.indexOf("blob:")!==0){scriptDirectory=scriptDirectory.substr(0,scriptDirectory.lastIndexOf("/")+1)}else{scriptDirectory=""}{read_=function shell_read(url){var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(ENVIRONMENT_IS_WORKER){readBinary=function readBinary(url){var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=function readAsync(url,onload,onerror){var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=function xhr_onload(){if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}onerror()};xhr.onerror=onerror;xhr.send(null)}}setWindowTitle=function(title){document.title=title}}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.warn.bind(console);for(key in moduleOverrides){if(moduleOverrides.hasOwnProperty(key)){Module[key]=moduleOverrides[key]}}moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var STACK_ALIGN=16;function alignMemory(size,factor){if(!factor)factor=STACK_ALIGN;return Math.ceil(size/factor)*factor}function warnOnce(text){if(!warnOnce.shown)warnOnce.shown={};if(!warnOnce.shown[text]){warnOnce.shown[text]=1;err(text)}}function convertJsFunctionToWasm(func,sig){if(typeof WebAssembly.Function==="function"){var typeNames={"i":"i32","j":"i64","f":"f32","d":"f64"};var type={parameters:[],results:sig[0]=="v"?[]:[typeNames[sig[0]]]};for(var i=1;i=endIdx))++endPtr;if(endPtr-idx>16&&heap.subarray&&UTF8Decoder){return UTF8Decoder.decode(heap.subarray(idx,endPtr))}else{var str="";while(idx>10,56320|ch&1023)}}}return str}function UTF8ToString(ptr,maxBytesToRead){return ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):""}function stringToUTF8Array(str,heap,outIdx,maxBytesToWrite){if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}}heap[outIdx]=0;return outIdx-startIdx}function stringToUTF8(str,outPtr,maxBytesToWrite){return stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite)}function lengthBytesUTF8(str){var len=0;for(var i=0;i=55296&&u<=57343)u=65536+((u&1023)<<10)|str.charCodeAt(++i)&1023;if(u<=127)++len;else if(u<=2047)len+=2;else if(u<=65535)len+=3;else len+=4}return len}var UTF16Decoder=typeof TextDecoder!=="undefined"?new TextDecoder("utf-16le"):undefined;function UTF16ToString(ptr,maxBytesToRead){var endPtr=ptr;var idx=endPtr>>1;var maxIdx=idx+maxBytesToRead/2;while(!(idx>=maxIdx)&&HEAPU16[idx])++idx;endPtr=idx<<1;if(endPtr-ptr>32&&UTF16Decoder){return UTF16Decoder.decode(HEAPU8.subarray(ptr,endPtr))}else{var str="";for(var i=0;!(i>=maxBytesToRead/2);++i){var codeUnit=HEAP16[ptr+i*2>>1];if(codeUnit==0)break;str+=String.fromCharCode(codeUnit)}return str}}function stringToUTF16(str,outPtr,maxBytesToWrite){if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<2)return 0;maxBytesToWrite-=2;var startPtr=outPtr;var numCharsToWrite=maxBytesToWrite>1]=codeUnit;outPtr+=2}HEAP16[outPtr>>1]=0;return outPtr-startPtr}function lengthBytesUTF16(str){return str.length*2}function UTF32ToString(ptr,maxBytesToRead){var i=0;var str="";while(!(i>=maxBytesToRead/4)){var utf32=HEAP32[ptr+i*4>>2];if(utf32==0)break;++i;if(utf32>=65536){var ch=utf32-65536;str+=String.fromCharCode(55296|ch>>10,56320|ch&1023)}else{str+=String.fromCharCode(utf32)}}return str}function stringToUTF32(str,outPtr,maxBytesToWrite){if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<4)return 0;var startPtr=outPtr;var endPtr=startPtr+maxBytesToWrite-4;for(var i=0;i=55296&&codeUnit<=57343){var trailSurrogate=str.charCodeAt(++i);codeUnit=65536+((codeUnit&1023)<<10)|trailSurrogate&1023}HEAP32[outPtr>>2]=codeUnit;outPtr+=4;if(outPtr+4>endPtr)break}HEAP32[outPtr>>2]=0;return outPtr-startPtr}function lengthBytesUTF32(str){var len=0;for(var i=0;i=55296&&codeUnit<=57343)++i;len+=4}return len}function allocateUTF8(str){var size=lengthBytesUTF8(str)+1;var ret=_malloc(size);if(ret)stringToUTF8Array(str,HEAP8,ret,size);return ret}function writeArrayToMemory(array,buffer){HEAP8.set(array,buffer)}function writeAsciiToMemory(str,buffer,dontAddNull){for(var i=0;i>0]=str.charCodeAt(i)}if(!dontAddNull)HEAP8[buffer>>0]=0}function alignUp(x,multiple){if(x%multiple>0){x+=multiple-x%multiple}return x}var buffer,HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateGlobalBufferAndViews(buf){buffer=buf;Module["HEAP8"]=HEAP8=new Int8Array(buf);Module["HEAP16"]=HEAP16=new Int16Array(buf);Module["HEAP32"]=HEAP32=new Int32Array(buf);Module["HEAPU8"]=HEAPU8=new Uint8Array(buf);Module["HEAPU16"]=HEAPU16=new Uint16Array(buf);Module["HEAPU32"]=HEAPU32=new Uint32Array(buf);Module["HEAPF32"]=HEAPF32=new Float32Array(buf);Module["HEAPF64"]=HEAPF64=new Float64Array(buf)}var INITIAL_MEMORY=Module["INITIAL_MEMORY"]||16777216;var wasmTable;var __ATPRERUN__=[];var __ATINIT__=[];var __ATMAIN__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;var runtimeExited=false;function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;if(!Module["noFSInit"]&&!FS.init.initialized)FS.init();TTY.init();callRuntimeCallbacks(__ATINIT__)}function preMain(){FS.ignorePermissions=false;callRuntimeCallbacks(__ATMAIN__)}function exitRuntime(){runtimeExited=true}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function getUniqueRunDependency(id){return id}function addRunDependency(id){runDependencies++;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}}function removeRunDependency(id){runDependencies--;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}Module["preloadedImages"]={};Module["preloadedAudios"]={};function abort(what){if(Module["onAbort"]){Module["onAbort"](what)}what+="";err(what);ABORT=true;EXITSTATUS=1;what="abort("+what+"). Build with -s ASSERTIONS=1 for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject(e);throw e}function hasPrefix(str,prefix){return String.prototype.startsWith?str.startsWith(prefix):str.indexOf(prefix)===0}var dataURIPrefix="data:application/octet-stream;base64,";function isDataURI(filename){return hasPrefix(filename,dataURIPrefix)}var fileURIPrefix="file://";function isFileURI(filename){return hasPrefix(filename,fileURIPrefix)}var wasmBinaryFile="rhino3dm.wasm";if(!isDataURI(wasmBinaryFile)){wasmBinaryFile=locateFile(wasmBinaryFile)}function getBinary(){try{if(wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(wasmBinaryFile)}else{throw"both async and sync fetching of the wasm failed"}}catch(err){abort(err)}}function getBinaryPromise(){if(!wasmBinary&&(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER)&&typeof fetch==="function"&&!isFileURI(wasmBinaryFile)){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){if(!response["ok"]){throw"failed to load wasm binary file at '"+wasmBinaryFile+"'"}return response["arrayBuffer"]()}).catch(function(){return getBinary()})}return Promise.resolve().then(getBinary)}function createWasm(){var info={"env":asmLibraryArg,"wasi_snapshot_preview1":asmLibraryArg};function receiveInstance(instance,module){var exports=instance.exports;Module["asm"]=exports;wasmMemory=Module["asm"]["memory"];updateGlobalBufferAndViews(wasmMemory.buffer);wasmTable=Module["asm"]["__indirect_function_table"];removeRunDependency("wasm-instantiate")}addRunDependency("wasm-instantiate");function receiveInstantiatedSource(output){receiveInstance(output["instance"])}function instantiateArrayBuffer(receiver){return getBinaryPromise().then(function(binary){return WebAssembly.instantiate(binary,info)}).then(receiver,function(reason){err("failed to asynchronously prepare wasm: "+reason);abort(reason)})}function instantiateAsync(){if(!wasmBinary&&typeof WebAssembly.instantiateStreaming==="function"&&!isDataURI(wasmBinaryFile)&&!isFileURI(wasmBinaryFile)&&typeof fetch==="function"){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){var result=WebAssembly.instantiateStreaming(response,info);return result.then(receiveInstantiatedSource,function(reason){err("wasm streaming compile failed: "+reason);err("falling back to ArrayBuffer instantiation");return instantiateArrayBuffer(receiveInstantiatedSource)})})}else{return instantiateArrayBuffer(receiveInstantiatedSource)}}if(Module["instantiateWasm"]){try{var exports=Module["instantiateWasm"](info,receiveInstance);return exports}catch(e){err("Module.instantiateWasm callback failed with error: "+e);return false}}instantiateAsync().catch(readyPromiseReject);return{}}var tempDouble;var tempI64;function callRuntimeCallbacks(callbacks){while(callbacks.length>0){var callback=callbacks.shift();if(typeof callback=="function"){callback(Module);continue}var func=callback.func;if(typeof func==="number"){if(callback.arg===undefined){wasmTable.get(func)()}else{wasmTable.get(func)(callback.arg)}}else{func(callback.arg===undefined?null:callback.arg)}}}function demangle(func){return func}function demangleAll(text){var regex=/\b_Z[\w\d_]+/g;return text.replace(regex,function(x){var y=demangle(x);return x===y?x:y+" ["+x+"]"})}function jsStackTrace(){var error=new Error;if(!error.stack){try{throw new Error}catch(e){error=e}if(!error.stack){return"(no stack trace available)"}}return error.stack.toString()}var ExceptionInfoAttrs={DESTRUCTOR_OFFSET:0,REFCOUNT_OFFSET:4,TYPE_OFFSET:8,CAUGHT_OFFSET:12,RETHROWN_OFFSET:13,SIZE:16};function ___cxa_allocate_exception(size){return _malloc(size+ExceptionInfoAttrs.SIZE)+ExceptionInfoAttrs.SIZE}function _atexit(func,arg){}function ___cxa_atexit(a0,a1){return _atexit(a0,a1)}function ExceptionInfo(excPtr){this.excPtr=excPtr;this.ptr=excPtr-ExceptionInfoAttrs.SIZE;this.set_type=function(type){HEAP32[this.ptr+ExceptionInfoAttrs.TYPE_OFFSET>>2]=type};this.get_type=function(){return HEAP32[this.ptr+ExceptionInfoAttrs.TYPE_OFFSET>>2]};this.set_destructor=function(destructor){HEAP32[this.ptr+ExceptionInfoAttrs.DESTRUCTOR_OFFSET>>2]=destructor};this.get_destructor=function(){return HEAP32[this.ptr+ExceptionInfoAttrs.DESTRUCTOR_OFFSET>>2]};this.set_refcount=function(refcount){HEAP32[this.ptr+ExceptionInfoAttrs.REFCOUNT_OFFSET>>2]=refcount};this.set_caught=function(caught){caught=caught?1:0;HEAP8[this.ptr+ExceptionInfoAttrs.CAUGHT_OFFSET>>0]=caught};this.get_caught=function(){return HEAP8[this.ptr+ExceptionInfoAttrs.CAUGHT_OFFSET>>0]!=0};this.set_rethrown=function(rethrown){rethrown=rethrown?1:0;HEAP8[this.ptr+ExceptionInfoAttrs.RETHROWN_OFFSET>>0]=rethrown};this.get_rethrown=function(){return HEAP8[this.ptr+ExceptionInfoAttrs.RETHROWN_OFFSET>>0]!=0};this.init=function(type,destructor){this.set_type(type);this.set_destructor(destructor);this.set_refcount(0);this.set_caught(false);this.set_rethrown(false)};this.add_ref=function(){var value=HEAP32[this.ptr+ExceptionInfoAttrs.REFCOUNT_OFFSET>>2];HEAP32[this.ptr+ExceptionInfoAttrs.REFCOUNT_OFFSET>>2]=value+1};this.release_ref=function(){var prev=HEAP32[this.ptr+ExceptionInfoAttrs.REFCOUNT_OFFSET>>2];HEAP32[this.ptr+ExceptionInfoAttrs.REFCOUNT_OFFSET>>2]=prev-1;return prev===1}}var exceptionLast=0;var uncaughtExceptionCount=0;function ___cxa_throw(ptr,type,destructor){var info=new ExceptionInfo(ptr);info.init(type,destructor);exceptionLast=ptr;uncaughtExceptionCount++;throw ptr}function _gmtime_r(time,tmPtr){var date=new Date(HEAP32[time>>2]*1e3);HEAP32[tmPtr>>2]=date.getUTCSeconds();HEAP32[tmPtr+4>>2]=date.getUTCMinutes();HEAP32[tmPtr+8>>2]=date.getUTCHours();HEAP32[tmPtr+12>>2]=date.getUTCDate();HEAP32[tmPtr+16>>2]=date.getUTCMonth();HEAP32[tmPtr+20>>2]=date.getUTCFullYear()-1900;HEAP32[tmPtr+24>>2]=date.getUTCDay();HEAP32[tmPtr+36>>2]=0;HEAP32[tmPtr+32>>2]=0;var start=Date.UTC(date.getUTCFullYear(),0,1,0,0,0,0);var yday=(date.getTime()-start)/(1e3*60*60*24)|0;HEAP32[tmPtr+28>>2]=yday;if(!_gmtime_r.GMTString)_gmtime_r.GMTString=allocateUTF8("GMT");HEAP32[tmPtr+40>>2]=_gmtime_r.GMTString;return tmPtr}function ___gmtime_r(a0,a1){return _gmtime_r(a0,a1)}function setErrNo(value){HEAP32[___errno_location()>>2]=value;return value}var PATH={splitPath:function(filename){var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;return splitPathRe.exec(filename).slice(1)},normalizeArray:function(parts,allowAboveRoot){var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up;up--){parts.unshift("..")}}return parts},normalize:function(path){var isAbsolute=path.charAt(0)==="/",trailingSlash=path.substr(-1)==="/";path=PATH.normalizeArray(path.split("/").filter(function(p){return!!p}),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path},dirname:function(path){var result=PATH.splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return"."}if(dir){dir=dir.substr(0,dir.length-1)}return root+dir},basename:function(path){if(path==="/")return"/";path=PATH.normalize(path);path=path.replace(/\/$/,"");var lastSlash=path.lastIndexOf("/");if(lastSlash===-1)return path;return path.substr(lastSlash+1)},extname:function(path){return PATH.splitPath(path)[3]},join:function(){var paths=Array.prototype.slice.call(arguments,0);return PATH.normalize(paths.join("/"))},join2:function(l,r){return PATH.normalize(l+"/"+r)}};function getRandomDevice(){if(typeof crypto==="object"&&typeof crypto["getRandomValues"]==="function"){var randomBuffer=new Uint8Array(1);return function(){crypto.getRandomValues(randomBuffer);return randomBuffer[0]}}else if(ENVIRONMENT_IS_NODE){try{var crypto_module=require("crypto");return function(){return crypto_module["randomBytes"](1)[0]}}catch(e){}}return function(){abort("randomDevice")}}var PATH_FS={resolve:function(){var resolvedPath="",resolvedAbsolute=false;for(var i=arguments.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?arguments[i]:FS.cwd();if(typeof path!=="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){return""}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=path.charAt(0)==="/"}resolvedPath=PATH.normalizeArray(resolvedPath.split("/").filter(function(p){return!!p}),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."},relative:function(from,to){from=PATH_FS.resolve(from).substr(1);to=PATH_FS.resolve(to).substr(1);function trim(arr){var start=0;for(;start=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i0){result=buf.slice(0,bytesRead).toString("utf-8")}else{result=null}}else if(typeof window!="undefined"&&typeof window.prompt=="function"){result=window.prompt("Input: ");if(result!==null){result+="\n"}}else if(typeof readline=="function"){result=readline();if(result!==null){result+="\n"}}if(!result){return null}tty.input=intArrayFromString(result,true)}return tty.input.shift()},put_char:function(tty,val){if(val===null||val===10){out(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},flush:function(tty){if(tty.output&&tty.output.length>0){out(UTF8ArrayToString(tty.output,0));tty.output=[]}}},default_tty1_ops:{put_char:function(tty,val){if(val===null||val===10){err(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},flush:function(tty){if(tty.output&&tty.output.length>0){err(UTF8ArrayToString(tty.output,0));tty.output=[]}}}};function mmapAlloc(size){var alignedSize=alignMemory(size,16384);var ptr=_malloc(alignedSize);while(size=newCapacity)return;var CAPACITY_DOUBLING_MAX=1024*1024;newCapacity=Math.max(newCapacity,prevCapacity*(prevCapacity>>0);if(prevCapacity!=0)newCapacity=Math.max(newCapacity,256);var oldContents=node.contents;node.contents=new Uint8Array(newCapacity);if(node.usedBytes>0)node.contents.set(oldContents.subarray(0,node.usedBytes),0);return},resizeFileStorage:function(node,newSize){if(node.usedBytes==newSize)return;if(newSize==0){node.contents=null;node.usedBytes=0;return}if(!node.contents||node.contents.subarray){var oldContents=node.contents;node.contents=new Uint8Array(newSize);if(oldContents){node.contents.set(oldContents.subarray(0,Math.min(newSize,node.usedBytes)))}node.usedBytes=newSize;return}if(!node.contents)node.contents=[];if(node.contents.length>newSize)node.contents.length=newSize;else while(node.contents.length=stream.node.usedBytes)return 0;var size=Math.min(stream.node.usedBytes-position,length);if(size>8&&contents.subarray){buffer.set(contents.subarray(position,position+size),offset)}else{for(var i=0;i0||position+length8){throw new FS.ErrnoError(32)}var parts=PATH.normalizeArray(path.split("/").filter(function(p){return!!p}),false);var current=FS.root;var current_path="/";for(var i=0;i40){throw new FS.ErrnoError(32)}}}}return{path:current_path,node:current}},getPath:function(node){var path;while(true){if(FS.isRoot(node)){var mount=node.mount.mountpoint;if(!path)return mount;return mount[mount.length-1]!=="/"?mount+"/"+path:mount+path}path=path?node.name+"/"+path:node.name;node=node.parent}},hashName:function(parentid,name){var hash=0;for(var i=0;i>>0)%FS.nameTable.length},hashAddNode:function(node){var hash=FS.hashName(node.parent.id,node.name);node.name_next=FS.nameTable[hash];FS.nameTable[hash]=node},hashRemoveNode:function(node){var hash=FS.hashName(node.parent.id,node.name);if(FS.nameTable[hash]===node){FS.nameTable[hash]=node.name_next}else{var current=FS.nameTable[hash];while(current){if(current.name_next===node){current.name_next=node.name_next;break}current=current.name_next}}},lookupNode:function(parent,name){var errCode=FS.mayLookup(parent);if(errCode){throw new FS.ErrnoError(errCode,parent)}var hash=FS.hashName(parent.id,name);for(var node=FS.nameTable[hash];node;node=node.name_next){var nodeName=node.name;if(node.parent.id===parent.id&&nodeName===name){return node}}return FS.lookup(parent,name)},createNode:function(parent,name,mode,rdev){var node=new FS.FSNode(parent,name,mode,rdev);FS.hashAddNode(node);return node},destroyNode:function(node){FS.hashRemoveNode(node)},isRoot:function(node){return node===node.parent},isMountpoint:function(node){return!!node.mounted},isFile:function(mode){return(mode&61440)===32768},isDir:function(mode){return(mode&61440)===16384},isLink:function(mode){return(mode&61440)===40960},isChrdev:function(mode){return(mode&61440)===8192},isBlkdev:function(mode){return(mode&61440)===24576},isFIFO:function(mode){return(mode&61440)===4096},isSocket:function(mode){return(mode&49152)===49152},flagModes:{"r":0,"r+":2,"w":577,"w+":578,"a":1089,"a+":1090},modeStringToFlags:function(str){var flags=FS.flagModes[str];if(typeof flags==="undefined"){throw new Error("Unknown file open mode: "+str)}return flags},flagsToPermissionString:function(flag){var perms=["r","w","rw"][flag&3];if(flag&512){perms+="w"}return perms},nodePermissions:function(node,perms){if(FS.ignorePermissions){return 0}if(perms.indexOf("r")!==-1&&!(node.mode&292)){return 2}else if(perms.indexOf("w")!==-1&&!(node.mode&146)){return 2}else if(perms.indexOf("x")!==-1&&!(node.mode&73)){return 2}return 0},mayLookup:function(dir){var errCode=FS.nodePermissions(dir,"x");if(errCode)return errCode;if(!dir.node_ops.lookup)return 2;return 0},mayCreate:function(dir,name){try{var node=FS.lookupNode(dir,name);return 20}catch(e){}return FS.nodePermissions(dir,"wx")},mayDelete:function(dir,name,isdir){var node;try{node=FS.lookupNode(dir,name)}catch(e){return e.errno}var errCode=FS.nodePermissions(dir,"wx");if(errCode){return errCode}if(isdir){if(!FS.isDir(node.mode)){return 54}if(FS.isRoot(node)||FS.getPath(node)===FS.cwd()){return 10}}else{if(FS.isDir(node.mode)){return 31}}return 0},mayOpen:function(node,flags){if(!node){return 44}if(FS.isLink(node.mode)){return 32}else if(FS.isDir(node.mode)){if(FS.flagsToPermissionString(flags)!=="r"||flags&512){return 31}}return FS.nodePermissions(node,FS.flagsToPermissionString(flags))},MAX_OPEN_FDS:4096,nextfd:function(fd_start,fd_end){fd_start=fd_start||0;fd_end=fd_end||FS.MAX_OPEN_FDS;for(var fd=fd_start;fd<=fd_end;fd++){if(!FS.streams[fd]){return fd}}throw new FS.ErrnoError(33)},getStream:function(fd){return FS.streams[fd]},createStream:function(stream,fd_start,fd_end){if(!FS.FSStream){FS.FSStream=function(){};FS.FSStream.prototype={object:{get:function(){return this.node},set:function(val){this.node=val}},isRead:{get:function(){return(this.flags&2097155)!==1}},isWrite:{get:function(){return(this.flags&2097155)!==0}},isAppend:{get:function(){return this.flags&1024}}}}var newStream=new FS.FSStream;for(var p in stream){newStream[p]=stream[p]}stream=newStream;var fd=FS.nextfd(fd_start,fd_end);stream.fd=fd;FS.streams[fd]=stream;return stream},closeStream:function(fd){FS.streams[fd]=null},chrdev_stream_ops:{open:function(stream){var device=FS.getDevice(stream.node.rdev);stream.stream_ops=device.stream_ops;if(stream.stream_ops.open){stream.stream_ops.open(stream)}},llseek:function(){throw new FS.ErrnoError(70)}},major:function(dev){return dev>>8},minor:function(dev){return dev&255},makedev:function(ma,mi){return ma<<8|mi},registerDevice:function(dev,ops){FS.devices[dev]={stream_ops:ops}},getDevice:function(dev){return FS.devices[dev]},getMounts:function(mount){var mounts=[];var check=[mount];while(check.length){var m=check.pop();mounts.push(m);check.push.apply(check,m.mounts)}return mounts},syncfs:function(populate,callback){if(typeof populate==="function"){callback=populate;populate=false}FS.syncFSRequests++;if(FS.syncFSRequests>1){err("warning: "+FS.syncFSRequests+" FS.syncfs operations in flight at once, probably just doing extra work")}var mounts=FS.getMounts(FS.root.mount);var completed=0;function doCallback(errCode){FS.syncFSRequests--;return callback(errCode)}function done(errCode){if(errCode){if(!done.errored){done.errored=true;return doCallback(errCode)}return}if(++completed>=mounts.length){doCallback(null)}}mounts.forEach(function(mount){if(!mount.type.syncfs){return done(null)}mount.type.syncfs(mount,populate,done)})},mount:function(type,opts,mountpoint){var root=mountpoint==="/";var pseudo=!mountpoint;var node;if(root&&FS.root){throw new FS.ErrnoError(10)}else if(!root&&!pseudo){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});mountpoint=lookup.path;node=lookup.node;if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}if(!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}}var mount={type:type,opts:opts,mountpoint:mountpoint,mounts:[]};var mountRoot=type.mount(mount);mountRoot.mount=mount;mount.root=mountRoot;if(root){FS.root=mountRoot}else if(node){node.mounted=mount;if(node.mount){node.mount.mounts.push(mount)}}return mountRoot},unmount:function(mountpoint){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});if(!FS.isMountpoint(lookup.node)){throw new FS.ErrnoError(28)}var node=lookup.node;var mount=node.mounted;var mounts=FS.getMounts(mount);Object.keys(FS.nameTable).forEach(function(hash){var current=FS.nameTable[hash];while(current){var next=current.name_next;if(mounts.indexOf(current.mount)!==-1){FS.destroyNode(current)}current=next}});node.mounted=null;var idx=node.mount.mounts.indexOf(mount);node.mount.mounts.splice(idx,1)},lookup:function(parent,name){return parent.node_ops.lookup(parent,name)},mknod:function(path,mode,dev){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);if(!name||name==="."||name===".."){throw new FS.ErrnoError(28)}var errCode=FS.mayCreate(parent,name);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.mknod){throw new FS.ErrnoError(63)}return parent.node_ops.mknod(parent,name,mode,dev)},create:function(path,mode){mode=mode!==undefined?mode:438;mode&=4095;mode|=32768;return FS.mknod(path,mode,0)},mkdir:function(path,mode){mode=mode!==undefined?mode:511;mode&=511|512;mode|=16384;return FS.mknod(path,mode,0)},mkdirTree:function(path,mode){var dirs=path.split("/");var d="";for(var i=0;ithis.length-1||idx<0){return undefined}var chunkOffset=idx%this.chunkSize;var chunkNum=idx/this.chunkSize|0;return this.getter(chunkNum)[chunkOffset]};LazyUint8Array.prototype.setDataGetter=function LazyUint8Array_setDataGetter(getter){this.getter=getter};LazyUint8Array.prototype.cacheLength=function LazyUint8Array_cacheLength(){var xhr=new XMLHttpRequest;xhr.open("HEAD",url,false);xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);var datalength=Number(xhr.getResponseHeader("Content-length"));var header;var hasByteServing=(header=xhr.getResponseHeader("Accept-Ranges"))&&header==="bytes";var usesGzip=(header=xhr.getResponseHeader("Content-Encoding"))&&header==="gzip";var chunkSize=1024*1024;if(!hasByteServing)chunkSize=datalength;var doXHR=function(from,to){if(from>to)throw new Error("invalid range ("+from+", "+to+") or no bytes requested!");if(to>datalength-1)throw new Error("only "+datalength+" bytes available! programmer error!");var xhr=new XMLHttpRequest;xhr.open("GET",url,false);if(datalength!==chunkSize)xhr.setRequestHeader("Range","bytes="+from+"-"+to);if(typeof Uint8Array!="undefined")xhr.responseType="arraybuffer";if(xhr.overrideMimeType){xhr.overrideMimeType("text/plain; charset=x-user-defined")}xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);if(xhr.response!==undefined){return new Uint8Array(xhr.response||[])}else{return intArrayFromString(xhr.responseText||"",true)}};var lazyArray=this;lazyArray.setDataGetter(function(chunkNum){var start=chunkNum*chunkSize;var end=(chunkNum+1)*chunkSize-1;end=Math.min(end,datalength-1);if(typeof lazyArray.chunks[chunkNum]==="undefined"){lazyArray.chunks[chunkNum]=doXHR(start,end)}if(typeof lazyArray.chunks[chunkNum]==="undefined")throw new Error("doXHR failed!");return lazyArray.chunks[chunkNum]});if(usesGzip||!datalength){chunkSize=datalength=1;datalength=this.getter(0).length;chunkSize=datalength;out("LazyFiles on gzip forces download of the whole file when length is accessed")}this._length=datalength;this._chunkSize=chunkSize;this.lengthKnown=true};if(typeof XMLHttpRequest!=="undefined"){if(!ENVIRONMENT_IS_WORKER)throw"Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc";var lazyArray=new LazyUint8Array;Object.defineProperties(lazyArray,{length:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._length}},chunkSize:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._chunkSize}}});var properties={isDevice:false,contents:lazyArray}}else{var properties={isDevice:false,url:url}}var node=FS.createFile(parent,name,properties,canRead,canWrite);if(properties.contents){node.contents=properties.contents}else if(properties.url){node.contents=null;node.url=properties.url}Object.defineProperties(node,{usedBytes:{get:function(){return this.contents.length}}});var stream_ops={};var keys=Object.keys(node.stream_ops);keys.forEach(function(key){var fn=node.stream_ops[key];stream_ops[key]=function forceLoadLazyFile(){FS.forceLoadFile(node);return fn.apply(null,arguments)}});stream_ops.read=function stream_ops_read(stream,buffer,offset,length,position){FS.forceLoadFile(node);var contents=stream.node.contents;if(position>=contents.length)return 0;var size=Math.min(contents.length-position,length);if(contents.slice){for(var i=0;i>2]=stat.dev;HEAP32[buf+4>>2]=0;HEAP32[buf+8>>2]=stat.ino;HEAP32[buf+12>>2]=stat.mode;HEAP32[buf+16>>2]=stat.nlink;HEAP32[buf+20>>2]=stat.uid;HEAP32[buf+24>>2]=stat.gid;HEAP32[buf+28>>2]=stat.rdev;HEAP32[buf+32>>2]=0;tempI64=[stat.size>>>0,(tempDouble=stat.size,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+40>>2]=tempI64[0],HEAP32[buf+44>>2]=tempI64[1];HEAP32[buf+48>>2]=4096;HEAP32[buf+52>>2]=stat.blocks;HEAP32[buf+56>>2]=stat.atime.getTime()/1e3|0;HEAP32[buf+60>>2]=0;HEAP32[buf+64>>2]=stat.mtime.getTime()/1e3|0;HEAP32[buf+68>>2]=0;HEAP32[buf+72>>2]=stat.ctime.getTime()/1e3|0;HEAP32[buf+76>>2]=0;tempI64=[stat.ino>>>0,(tempDouble=stat.ino,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+80>>2]=tempI64[0],HEAP32[buf+84>>2]=tempI64[1];return 0},doMsync:function(addr,stream,len,flags,offset){var buffer=HEAPU8.slice(addr,addr+len);FS.msync(stream,buffer,offset,len,flags)},doMkdir:function(path,mode){path=PATH.normalize(path);if(path[path.length-1]==="/")path=path.substr(0,path.length-1);FS.mkdir(path,mode,0);return 0},doMknod:function(path,mode,dev){switch(mode&61440){case 32768:case 8192:case 24576:case 4096:case 49152:break;default:return-28}FS.mknod(path,mode,dev);return 0},doReadlink:function(path,buf,bufsize){if(bufsize<=0)return-28;var ret=FS.readlink(path);var len=Math.min(bufsize,lengthBytesUTF8(ret));var endChar=HEAP8[buf+len];stringToUTF8(ret,buf,bufsize+1);HEAP8[buf+len]=endChar;return len},doAccess:function(path,amode){if(amode&~7){return-28}var node;var lookup=FS.lookupPath(path,{follow:true});node=lookup.node;if(!node){return-44}var perms="";if(amode&4)perms+="r";if(amode&2)perms+="w";if(amode&1)perms+="x";if(perms&&FS.nodePermissions(node,perms)){return-2}return 0},doDup:function(path,flags,suggestFD){var suggest=FS.getStream(suggestFD);if(suggest)FS.close(suggest);return FS.open(path,flags,0,suggestFD,suggestFD).fd},doReadv:function(stream,iov,iovcnt,offset){var ret=0;for(var i=0;i>2];var len=HEAP32[iov+(i*8+4)>>2];var curr=FS.read(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>2];var len=HEAP32[iov+(i*8+4)>>2];var curr=FS.write(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr}return ret},varargs:undefined,get:function(){SYSCALLS.varargs+=4;var ret=HEAP32[SYSCALLS.varargs-4>>2];return ret},getStr:function(ptr){var ret=UTF8ToString(ptr);return ret},getStreamFromFD:function(fd){var stream=FS.getStream(fd);if(!stream)throw new FS.ErrnoError(8);return stream},get64:function(low,high){return low}};function ___sys_fcntl64(fd,cmd,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(cmd){case 0:{var arg=SYSCALLS.get();if(arg<0){return-28}var newStream;newStream=FS.open(stream.path,stream.flags,0,arg);return newStream.fd}case 1:case 2:return 0;case 3:return stream.flags;case 4:{var arg=SYSCALLS.get();stream.flags|=arg;return 0}case 12:{var arg=SYSCALLS.get();var offset=0;HEAP16[arg+offset>>1]=2;return 0}case 13:case 14:return 0;case 16:case 8:return-28;case 9:setErrNo(28);return-1;default:{return-28}}}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___sys_fstat64(fd,buf){try{var stream=SYSCALLS.getStreamFromFD(fd);return SYSCALLS.doStat(FS.stat,stream.path,buf)}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___sys_ioctl(fd,op,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(op){case 21509:case 21505:{if(!stream.tty)return-59;return 0}case 21510:case 21511:case 21512:case 21506:case 21507:case 21508:{if(!stream.tty)return-59;return 0}case 21519:{if(!stream.tty)return-59;var argp=SYSCALLS.get();HEAP32[argp>>2]=0;return 0}case 21520:{if(!stream.tty)return-59;return-28}case 21531:{var argp=SYSCALLS.get();return FS.ioctl(stream,op,argp)}case 21523:{if(!stream.tty)return-59;return 0}case 21524:{if(!stream.tty)return-59;return 0}default:abort("bad ioctl syscall "+op)}}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___sys_open(path,flags,varargs){SYSCALLS.varargs=varargs;try{var pathname=SYSCALLS.getStr(path);var mode=SYSCALLS.get();var stream=FS.open(pathname,flags,mode);return stream.fd}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___sys_stat64(path,buf){try{path=SYSCALLS.getStr(path);return SYSCALLS.doStat(FS.stat,path,buf)}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}var tupleRegistrations={};function runDestructors(destructors){while(destructors.length){var ptr=destructors.pop();var del=destructors.pop();del(ptr)}}function simpleReadValueFromPointer(pointer){return this["fromWireType"](HEAPU32[pointer>>2])}var awaitingDependencies={};var registeredTypes={};var typeDependencies={};var char_0=48;var char_9=57;function makeLegalFunctionName(name){if(undefined===name){return"_unknown"}name=name.replace(/[^a-zA-Z0-9_]/g,"$");var f=name.charCodeAt(0);if(f>=char_0&&f<=char_9){return"_"+name}else{return name}}function createNamedFunction(name,body){name=makeLegalFunctionName(name);return new Function("body","return function "+name+"() {\n"+' "use strict";'+" return body.apply(this, arguments);\n"+"};\n")(body)}function extendError(baseErrorType,errorName){var errorClass=createNamedFunction(errorName,function(message){this.name=errorName;this.message=message;var stack=new Error(message).stack;if(stack!==undefined){this.stack=this.toString()+"\n"+stack.replace(/^Error(:[^\n]*)?\n/,"")}});errorClass.prototype=Object.create(baseErrorType.prototype);errorClass.prototype.constructor=errorClass;errorClass.prototype.toString=function(){if(this.message===undefined){return this.name}else{return this.name+": "+this.message}};return errorClass}var InternalError=undefined;function throwInternalError(message){throw new InternalError(message)}function whenDependentTypesAreResolved(myTypes,dependentTypes,getTypeConverters){myTypes.forEach(function(type){typeDependencies[type]=dependentTypes});function onComplete(typeConverters){var myTypeConverters=getTypeConverters(typeConverters);if(myTypeConverters.length!==myTypes.length){throwInternalError("Mismatched type converter count")}for(var i=0;i>shift])},destructorFunction:null})}function ClassHandle_isAliasOf(other){if(!(this instanceof ClassHandle)){return false}if(!(other instanceof ClassHandle)){return false}var leftClass=this.$$.ptrType.registeredClass;var left=this.$$.ptr;var rightClass=other.$$.ptrType.registeredClass;var right=other.$$.ptr;while(leftClass.baseClass){left=leftClass.upcast(left);leftClass=leftClass.baseClass}while(rightClass.baseClass){right=rightClass.upcast(right);rightClass=rightClass.baseClass}return leftClass===rightClass&&left===right}function shallowCopyInternalPointer(o){return{count:o.count,deleteScheduled:o.deleteScheduled,preservePointerOnDelete:o.preservePointerOnDelete,ptr:o.ptr,ptrType:o.ptrType,smartPtr:o.smartPtr,smartPtrType:o.smartPtrType}}function throwInstanceAlreadyDeleted(obj){function getInstanceTypeName(handle){return handle.$$.ptrType.registeredClass.name}throwBindingError(getInstanceTypeName(obj)+" instance already deleted")}var finalizationGroup=false;function detachFinalizer(handle){}function runDestructor($$){if($$.smartPtr){$$.smartPtrType.rawDestructor($$.smartPtr)}else{$$.ptrType.registeredClass.rawDestructor($$.ptr)}}function releaseClassHandle($$){$$.count.value-=1;var toDelete=0===$$.count.value;if(toDelete){runDestructor($$)}}function attachFinalizer(handle){if("undefined"===typeof FinalizationGroup){attachFinalizer=function(handle){return handle};return handle}finalizationGroup=new FinalizationGroup(function(iter){for(var result=iter.next();!result.done;result=iter.next()){var $$=result.value;if(!$$.ptr){console.warn("object already deleted: "+$$.ptr)}else{releaseClassHandle($$)}}});attachFinalizer=function(handle){finalizationGroup.register(handle,handle.$$,handle.$$);return handle};detachFinalizer=function(handle){finalizationGroup.unregister(handle.$$)};return attachFinalizer(handle)}function ClassHandle_clone(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.preservePointerOnDelete){this.$$.count.value+=1;return this}else{var clone=attachFinalizer(Object.create(Object.getPrototypeOf(this),{$$:{value:shallowCopyInternalPointer(this.$$)}}));clone.$$.count.value+=1;clone.$$.deleteScheduled=false;return clone}}function ClassHandle_delete(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}detachFinalizer(this);releaseClassHandle(this.$$);if(!this.$$.preservePointerOnDelete){this.$$.smartPtr=undefined;this.$$.ptr=undefined}}function ClassHandle_isDeleted(){return!this.$$.ptr}var delayFunction=undefined;var deletionQueue=[];function flushPendingDeletes(){while(deletionQueue.length){var obj=deletionQueue.pop();obj.$$.deleteScheduled=false;obj["delete"]()}}function ClassHandle_deleteLater(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}deletionQueue.push(this);if(deletionQueue.length===1&&delayFunction){delayFunction(flushPendingDeletes)}this.$$.deleteScheduled=true;return this}function init_ClassHandle(){ClassHandle.prototype["isAliasOf"]=ClassHandle_isAliasOf;ClassHandle.prototype["clone"]=ClassHandle_clone;ClassHandle.prototype["delete"]=ClassHandle_delete;ClassHandle.prototype["isDeleted"]=ClassHandle_isDeleted;ClassHandle.prototype["deleteLater"]=ClassHandle_deleteLater}function ClassHandle(){}var registeredPointers={};function ensureOverloadTable(proto,methodName,humanName){if(undefined===proto[methodName].overloadTable){var prevFunc=proto[methodName];proto[methodName]=function(){if(!proto[methodName].overloadTable.hasOwnProperty(arguments.length)){throwBindingError("Function '"+humanName+"' called with an invalid number of arguments ("+arguments.length+") - expects one of ("+proto[methodName].overloadTable+")!")}return proto[methodName].overloadTable[arguments.length].apply(this,arguments)};proto[methodName].overloadTable=[];proto[methodName].overloadTable[prevFunc.argCount]=prevFunc}}function exposePublicSymbol(name,value,numArguments){if(Module.hasOwnProperty(name)){if(undefined===numArguments||undefined!==Module[name].overloadTable&&undefined!==Module[name].overloadTable[numArguments]){throwBindingError("Cannot register public name '"+name+"' twice")}ensureOverloadTable(Module,name,name);if(Module.hasOwnProperty(numArguments)){throwBindingError("Cannot register multiple overloads of a function with the same number of arguments ("+numArguments+")!")}Module[name].overloadTable[numArguments]=value}else{Module[name]=value;if(undefined!==numArguments){Module[name].numArguments=numArguments}}}function RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast){this.name=name;this.constructor=constructor;this.instancePrototype=instancePrototype;this.rawDestructor=rawDestructor;this.baseClass=baseClass;this.getActualType=getActualType;this.upcast=upcast;this.downcast=downcast;this.pureVirtualFunctions=[]}function upcastPointer(ptr,ptrClass,desiredClass){while(ptrClass!==desiredClass){if(!ptrClass.upcast){throwBindingError("Expected null or instance of "+desiredClass.name+", got an instance of "+ptrClass.name)}ptr=ptrClass.upcast(ptr);ptrClass=ptrClass.baseClass}return ptr}function constNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError("null is not a valid "+this.name)}return 0}if(!handle.$$){throwBindingError('Cannot pass "'+_embind_repr(handle)+'" as a '+this.name)}if(!handle.$$.ptr){throwBindingError("Cannot pass deleted object as a pointer of type "+this.name)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function genericPointerToWireType(destructors,handle){var ptr;if(handle===null){if(this.isReference){throwBindingError("null is not a valid "+this.name)}if(this.isSmartPointer){ptr=this.rawConstructor();if(destructors!==null){destructors.push(this.rawDestructor,ptr)}return ptr}else{return 0}}if(!handle.$$){throwBindingError('Cannot pass "'+_embind_repr(handle)+'" as a '+this.name)}if(!handle.$$.ptr){throwBindingError("Cannot pass deleted object as a pointer of type "+this.name)}if(!this.isConst&&handle.$$.ptrType.isConst){throwBindingError("Cannot convert argument of type "+(handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name)+" to parameter type "+this.name)}var handleClass=handle.$$.ptrType.registeredClass;ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);if(this.isSmartPointer){if(undefined===handle.$$.smartPtr){throwBindingError("Passing raw pointer to smart pointer is illegal")}switch(this.sharingPolicy){case 0:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{throwBindingError("Cannot convert argument of type "+(handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name)+" to parameter type "+this.name)}break;case 1:ptr=handle.$$.smartPtr;break;case 2:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{var clonedHandle=handle["clone"]();ptr=this.rawShare(ptr,__emval_register(function(){clonedHandle["delete"]()}));if(destructors!==null){destructors.push(this.rawDestructor,ptr)}}break;default:throwBindingError("Unsupporting sharing policy")}}return ptr}function nonConstNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError("null is not a valid "+this.name)}return 0}if(!handle.$$){throwBindingError('Cannot pass "'+_embind_repr(handle)+'" as a '+this.name)}if(!handle.$$.ptr){throwBindingError("Cannot pass deleted object as a pointer of type "+this.name)}if(handle.$$.ptrType.isConst){throwBindingError("Cannot convert argument of type "+handle.$$.ptrType.name+" to parameter type "+this.name)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function RegisteredPointer_getPointee(ptr){if(this.rawGetPointee){ptr=this.rawGetPointee(ptr)}return ptr}function RegisteredPointer_destructor(ptr){if(this.rawDestructor){this.rawDestructor(ptr)}}function RegisteredPointer_deleteObject(handle){if(handle!==null){handle["delete"]()}}function downcastPointer(ptr,ptrClass,desiredClass){if(ptrClass===desiredClass){return ptr}if(undefined===desiredClass.baseClass){return null}var rv=downcastPointer(ptr,ptrClass,desiredClass.baseClass);if(rv===null){return null}return desiredClass.downcast(rv)}function getInheritedInstanceCount(){return Object.keys(registeredInstances).length}function getLiveInheritedInstances(){var rv=[];for(var k in registeredInstances){if(registeredInstances.hasOwnProperty(k)){rv.push(registeredInstances[k])}}return rv}function setDelayFunction(fn){delayFunction=fn;if(deletionQueue.length&&delayFunction){delayFunction(flushPendingDeletes)}}function init_embind(){Module["getInheritedInstanceCount"]=getInheritedInstanceCount;Module["getLiveInheritedInstances"]=getLiveInheritedInstances;Module["flushPendingDeletes"]=flushPendingDeletes;Module["setDelayFunction"]=setDelayFunction}var registeredInstances={};function getBasestPointer(class_,ptr){if(ptr===undefined){throwBindingError("ptr should not be undefined")}while(class_.baseClass){ptr=class_.upcast(ptr);class_=class_.baseClass}return ptr}function getInheritedInstance(class_,ptr){ptr=getBasestPointer(class_,ptr);return registeredInstances[ptr]}function makeClassHandle(prototype,record){if(!record.ptrType||!record.ptr){throwInternalError("makeClassHandle requires ptr and ptrType")}var hasSmartPtrType=!!record.smartPtrType;var hasSmartPtr=!!record.smartPtr;if(hasSmartPtrType!==hasSmartPtr){throwInternalError("Both smartPtrType and smartPtr must be specified")}record.count={value:1};return attachFinalizer(Object.create(prototype,{$$:{value:record}}))}function RegisteredPointer_fromWireType(ptr){var rawPointer=this.getPointee(ptr);if(!rawPointer){this.destructor(ptr);return null}var registeredInstance=getInheritedInstance(this.registeredClass,rawPointer);if(undefined!==registeredInstance){if(0===registeredInstance.$$.count.value){registeredInstance.$$.ptr=rawPointer;registeredInstance.$$.smartPtr=ptr;return registeredInstance["clone"]()}else{var rv=registeredInstance["clone"]();this.destructor(ptr);return rv}}function makeDefaultHandle(){if(this.isSmartPointer){return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this.pointeeType,ptr:rawPointer,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this,ptr:ptr})}}var actualType=this.registeredClass.getActualType(rawPointer);var registeredPointerRecord=registeredPointers[actualType];if(!registeredPointerRecord){return makeDefaultHandle.call(this)}var toType;if(this.isConst){toType=registeredPointerRecord.constPointerType}else{toType=registeredPointerRecord.pointerType}var dp=downcastPointer(rawPointer,this.registeredClass,toType.registeredClass);if(dp===null){return makeDefaultHandle.call(this)}if(this.isSmartPointer){return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp})}}function init_RegisteredPointer(){RegisteredPointer.prototype.getPointee=RegisteredPointer_getPointee;RegisteredPointer.prototype.destructor=RegisteredPointer_destructor;RegisteredPointer.prototype["argPackAdvance"]=8;RegisteredPointer.prototype["readValueFromPointer"]=simpleReadValueFromPointer;RegisteredPointer.prototype["deleteObject"]=RegisteredPointer_deleteObject;RegisteredPointer.prototype["fromWireType"]=RegisteredPointer_fromWireType}function RegisteredPointer(name,registeredClass,isReference,isConst,isSmartPointer,pointeeType,sharingPolicy,rawGetPointee,rawConstructor,rawShare,rawDestructor){this.name=name;this.registeredClass=registeredClass;this.isReference=isReference;this.isConst=isConst;this.isSmartPointer=isSmartPointer;this.pointeeType=pointeeType;this.sharingPolicy=sharingPolicy;this.rawGetPointee=rawGetPointee;this.rawConstructor=rawConstructor;this.rawShare=rawShare;this.rawDestructor=rawDestructor;if(!isSmartPointer&®isteredClass.baseClass===undefined){if(isConst){this["toWireType"]=constNoSmartPtrRawPointerToWireType;this.destructorFunction=null}else{this["toWireType"]=nonConstNoSmartPtrRawPointerToWireType;this.destructorFunction=null}}else{this["toWireType"]=genericPointerToWireType}}function replacePublicSymbol(name,value,numArguments){if(!Module.hasOwnProperty(name)){throwInternalError("Replacing nonexistant public symbol")}if(undefined!==Module[name].overloadTable&&undefined!==numArguments){Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}}function dynCallLegacy(sig,ptr,args){if(args&&args.length){return Module["dynCall_"+sig].apply(null,[ptr].concat(args))}return Module["dynCall_"+sig].call(null,ptr)}function dynCall(sig,ptr,args){if(sig.indexOf("j")!=-1){return dynCallLegacy(sig,ptr,args)}return wasmTable.get(ptr).apply(null,args)}function getDynCaller(sig,ptr){assert(sig.indexOf("j")>=0,"getDynCaller should only be called with i64 sigs");var argCache=[];return function(){argCache.length=arguments.length;for(var i=0;i0?", ":"")+argsListWired}invokerFnBody+=(returns?"var rv = ":"")+"invoker(fn"+(argsListWired.length>0?", ":"")+argsListWired+");\n";if(needsDestructorStack){invokerFnBody+="runDestructors(destructors);\n"}else{for(var i=isClassMethodFunc?1:2;i>2)+i])}return array}function __embind_register_class_class_function(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,fn){var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=readLatin1String(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker);whenDependentTypesAreResolved([],[rawClassType],function(classType){classType=classType[0];var humanName=classType.name+"."+methodName;function unboundTypesHandler(){throwUnboundTypeError("Cannot call "+humanName+" due to unbound types",rawArgTypes)}var proto=classType.registeredClass.constructor;if(undefined===proto[methodName]){unboundTypesHandler.argCount=argCount-1;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-1]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,function(argTypes){var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));var func=craftInvokerFunction(humanName,invokerArgsArray,null,rawInvoker,fn);if(undefined===proto[methodName].overloadTable){func.argCount=argCount-1;proto[methodName]=func}else{proto[methodName].overloadTable[argCount-1]=func}return[]});return[]})}function __embind_register_class_constructor(rawClassType,argCount,rawArgTypesAddr,invokerSignature,invoker,rawConstructor){assert(argCount>0);var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);invoker=embind__requireFunction(invokerSignature,invoker);var args=[rawConstructor];var destructors=[];whenDependentTypesAreResolved([],[rawClassType],function(classType){classType=classType[0];var humanName="constructor "+classType.name;if(undefined===classType.registeredClass.constructor_body){classType.registeredClass.constructor_body=[]}if(undefined!==classType.registeredClass.constructor_body[argCount-1]){throw new BindingError("Cannot register multiple constructors with identical number of parameters ("+(argCount-1)+") for class '"+classType.name+"'! Overload resolution is currently only performed using the parameter count, not actual type info!")}classType.registeredClass.constructor_body[argCount-1]=function unboundTypeHandler(){throwUnboundTypeError("Cannot construct "+classType.name+" due to unbound types",rawArgTypes)};whenDependentTypesAreResolved([],rawArgTypes,function(argTypes){classType.registeredClass.constructor_body[argCount-1]=function constructor_body(){if(arguments.length!==argCount-1){throwBindingError(humanName+" called with "+arguments.length+" arguments, expected "+(argCount-1))}destructors.length=0;args.length=argCount;for(var i=1;i4&&0===--emval_handle_array[handle].refcount){emval_handle_array[handle]=undefined;emval_free_list.push(handle)}}function count_emval_handles(){var count=0;for(var i=5;i>1])};case 2:return function(pointer){var heap=signed?HEAP32:HEAPU32;return this["fromWireType"](heap[pointer>>2])};default:throw new TypeError("Unknown integer type: "+name)}}function __embind_register_enum(rawType,name,size,isSigned){var shift=getShiftFromSize(size);name=readLatin1String(name);function ctor(){}ctor.values={};registerType(rawType,{name:name,constructor:ctor,"fromWireType":function(c){return this.constructor.values[c]},"toWireType":function(destructors,c){return c.value},"argPackAdvance":8,"readValueFromPointer":enumReadValueFromPointer(name,shift,isSigned),destructorFunction:null});exposePublicSymbol(name,ctor)}function requireRegisteredType(rawType,humanName){var impl=registeredTypes[rawType];if(undefined===impl){throwBindingError(humanName+" has unknown type "+getTypeName(rawType))}return impl}function __embind_register_enum_value(rawEnumType,name,enumValue){var enumType=requireRegisteredType(rawEnumType,"enum");name=readLatin1String(name);var Enum=enumType.constructor;var Value=Object.create(enumType.constructor.prototype,{value:{value:enumValue},constructor:{value:createNamedFunction(enumType.name+"_"+name,function(){})}});Enum.values[enumValue]=Value;Enum[name]=Value}function _embind_repr(v){if(v===null){return"null"}var t=typeof v;if(t==="object"||t==="array"||t==="function"){return v.toString()}else{return""+v}}function floatReadValueFromPointer(name,shift){switch(shift){case 2:return function(pointer){return this["fromWireType"](HEAPF32[pointer>>2])};case 3:return function(pointer){return this["fromWireType"](HEAPF64[pointer>>3])};default:throw new TypeError("Unknown float type: "+name)}}function __embind_register_float(rawType,name,size){var shift=getShiftFromSize(size);name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":function(value){return value},"toWireType":function(destructors,value){if(typeof value!=="number"&&typeof value!=="boolean"){throw new TypeError('Cannot convert "'+_embind_repr(value)+'" to '+this.name)}return value},"argPackAdvance":8,"readValueFromPointer":floatReadValueFromPointer(name,shift),destructorFunction:null})}function integerReadValueFromPointer(name,shift,signed){switch(shift){case 0:return signed?function readS8FromPointer(pointer){return HEAP8[pointer]}:function readU8FromPointer(pointer){return HEAPU8[pointer]};case 1:return signed?function readS16FromPointer(pointer){return HEAP16[pointer>>1]}:function readU16FromPointer(pointer){return HEAPU16[pointer>>1]};case 2:return signed?function readS32FromPointer(pointer){return HEAP32[pointer>>2]}:function readU32FromPointer(pointer){return HEAPU32[pointer>>2]};default:throw new TypeError("Unknown integer type: "+name)}}function __embind_register_integer(primitiveType,name,size,minRange,maxRange){name=readLatin1String(name);if(maxRange===-1){maxRange=4294967295}var shift=getShiftFromSize(size);var fromWireType=function(value){return value};if(minRange===0){var bitshift=32-8*size;fromWireType=function(value){return value<>>bitshift}}var isUnsignedType=name.indexOf("unsigned")!=-1;registerType(primitiveType,{name:name,"fromWireType":fromWireType,"toWireType":function(destructors,value){if(typeof value!=="number"&&typeof value!=="boolean"){throw new TypeError('Cannot convert "'+_embind_repr(value)+'" to '+this.name)}if(valuemaxRange){throw new TypeError('Passing a number "'+_embind_repr(value)+'" from JS side to C/C++ side to an argument of type "'+name+'", which is outside the valid range ['+minRange+", "+maxRange+"]!")}return isUnsignedType?value>>>0:value|0},"argPackAdvance":8,"readValueFromPointer":integerReadValueFromPointer(name,shift,minRange!==0),destructorFunction:null})}function __embind_register_memory_view(rawType,dataTypeIndex,name){var typeMapping=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array];var TA=typeMapping[dataTypeIndex];function decodeMemoryView(handle){handle=handle>>2;var heap=HEAPU32;var size=heap[handle];var data=heap[handle+1];return new TA(buffer,data,size)}name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":decodeMemoryView,"argPackAdvance":8,"readValueFromPointer":decodeMemoryView},{ignoreDuplicateRegistrations:true})}function __embind_register_std_string(rawType,name){name=readLatin1String(name);var stdStringIsUTF8=name==="std::string";registerType(rawType,{name:name,"fromWireType":function(value){var length=HEAPU32[value>>2];var str;if(stdStringIsUTF8){var decodeStartPtr=value+4;for(var i=0;i<=length;++i){var currentBytePtr=value+4+i;if(i==length||HEAPU8[currentBytePtr]==0){var maxRead=currentBytePtr-decodeStartPtr;var stringSegment=UTF8ToString(decodeStartPtr,maxRead);if(str===undefined){str=stringSegment}else{str+=String.fromCharCode(0);str+=stringSegment}decodeStartPtr=currentBytePtr+1}}}else{var a=new Array(length);for(var i=0;i>2]=length;if(stdStringIsUTF8&&valueIsOfTypeString){stringToUTF8(value,ptr+4,length+1)}else{if(valueIsOfTypeString){for(var i=0;i255){_free(ptr);throwBindingError("String has UTF-16 code units that do not fit in 8 bits")}HEAPU8[ptr+4+i]=charCode}}else{for(var i=0;i>2];var HEAP=getHeap();var str;var decodeStartPtr=value+4;for(var i=0;i<=length;++i){var currentBytePtr=value+4+i*charSize;if(i==length||HEAP[currentBytePtr>>shift]==0){var maxReadBytes=currentBytePtr-decodeStartPtr;var stringSegment=decodeString(decodeStartPtr,maxReadBytes);if(str===undefined){str=stringSegment}else{str+=String.fromCharCode(0);str+=stringSegment}decodeStartPtr=currentBytePtr+charSize}}_free(value);return str},"toWireType":function(destructors,value){if(!(typeof value==="string")){throwBindingError("Cannot pass non-string to C++ string type "+name)}var length=lengthBytesUTF(value);var ptr=_malloc(4+length+charSize);HEAPU32[ptr>>2]=length>>shift;encodeString(value,ptr+4,length+charSize);if(destructors!==null){destructors.push(_free,ptr)}return ptr},"argPackAdvance":8,"readValueFromPointer":simpleReadValueFromPointer,destructorFunction:function(ptr){_free(ptr)}})}function __embind_register_value_array(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor){tupleRegistrations[rawType]={name:readLatin1String(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),elements:[]}}function __embind_register_value_array_element(rawTupleType,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext){tupleRegistrations[rawTupleType].elements.push({getterReturnType:getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext:getterContext,setterArgumentType:setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext:setterContext})}function __embind_register_value_object(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor){structRegistrations[rawType]={name:readLatin1String(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),fields:[]}}function __embind_register_value_object_field(structType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext){structRegistrations[structType].fields.push({fieldName:readLatin1String(fieldName),getterReturnType:getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext:getterContext,setterArgumentType:setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext:setterContext})}function __embind_register_void(rawType,name){name=readLatin1String(name);registerType(rawType,{isVoid:true,name:name,"argPackAdvance":0,"fromWireType":function(){return undefined},"toWireType":function(destructors,o){return undefined}})}function requireHandle(handle){if(!handle){throwBindingError("Cannot use deleted val. handle = "+handle)}return emval_handle_array[handle].value}function __emval_as(handle,returnType,destructorsRef){handle=requireHandle(handle);returnType=requireRegisteredType(returnType,"emval::as");var destructors=[];var rd=__emval_register(destructors);HEAP32[destructorsRef>>2]=rd;return returnType["toWireType"](destructors,handle)}function __emval_allocateDestructors(destructorsRef){var destructors=[];HEAP32[destructorsRef>>2]=__emval_register(destructors);return destructors}var emval_symbols={};function getStringOrSymbol(address){var symbol=emval_symbols[address];if(symbol===undefined){return readLatin1String(address)}else{return symbol}}var emval_methodCallers=[];function __emval_call_method(caller,handle,methodName,destructorsRef,args){caller=emval_methodCallers[caller];handle=requireHandle(handle);methodName=getStringOrSymbol(methodName);return caller(handle,methodName,__emval_allocateDestructors(destructorsRef),args)}function __emval_call_void_method(caller,handle,methodName,args){caller=emval_methodCallers[caller];handle=requireHandle(handle);methodName=getStringOrSymbol(methodName);caller(handle,methodName,null,args)}function __emval_equals(first,second){first=requireHandle(first);second=requireHandle(second);return first==second}function emval_get_global(){if(typeof globalThis==="object"){return globalThis}return function(){return Function}()("return this")()}function __emval_get_global(name){if(name===0){return __emval_register(emval_get_global())}else{name=getStringOrSymbol(name);return __emval_register(emval_get_global()[name])}}function __emval_addMethodCaller(caller){var id=emval_methodCallers.length;emval_methodCallers.push(caller);return id}function __emval_lookupTypes(argCount,argTypes){var a=new Array(argCount);for(var i=0;i>2)+i],"parameter "+i)}return a}function __emval_get_method_caller(argCount,argTypes){var types=__emval_lookupTypes(argCount,argTypes);var retType=types[0];var signatureName=retType.name+"_$"+types.slice(1).map(function(t){return t.name}).join("_")+"$";var params=["retType"];var args=[retType];var argsList="";for(var i=0;i4){emval_handle_array[handle].refcount+=1}}function __emval_instanceof(object,constructor){object=requireHandle(object);constructor=requireHandle(constructor);return object instanceof constructor}function __emval_is_number(handle){handle=requireHandle(handle);return typeof handle==="number"}function __emval_is_string(handle){handle=requireHandle(handle);return typeof handle==="string"}function craftEmvalAllocator(argCount){var argsList="";for(var i=0;i>> 2) + "+i+'], "parameter '+i+'");\n'+"var arg"+i+" = argType"+i+".readValueFromPointer(args);\n"+"args += argType"+i+"['argPackAdvance'];\n"}functionBody+="var obj = new constructor("+argsList+");\n"+"return __emval_register(obj);\n"+"}\n";return new Function("requireRegisteredType","Module","__emval_register",functionBody)(requireRegisteredType,Module,__emval_register)}var emval_newers={};function __emval_new(handle,argCount,argTypes,args){handle=requireHandle(handle);var newer=emval_newers[argCount];if(!newer){newer=craftEmvalAllocator(argCount);emval_newers[argCount]=newer}return newer(handle,argTypes,args)}function __emval_new_array(){return __emval_register([])}function __emval_new_cstring(v){return __emval_register(getStringOrSymbol(v))}function __emval_new_object(){return __emval_register({})}function __emval_run_destructors(handle){var destructors=emval_handle_array[handle].value;runDestructors(destructors);__emval_decref(handle)}function __emval_set_property(handle,key,value){handle=requireHandle(handle);key=requireHandle(key);value=requireHandle(value);handle[key]=value}function __emval_take_value(type,argv){type=requireRegisteredType(type,"_emval_take_value");var v=type["readValueFromPointer"](argv);return __emval_register(v)}function _abort(){abort()}function _emscripten_memcpy_big(dest,src,num){HEAPU8.copyWithin(dest,src,src+num)}function _emscripten_get_heap_size(){return HEAPU8.length}function emscripten_realloc_buffer(size){try{wasmMemory.grow(size-buffer.byteLength+65535>>>16);updateGlobalBufferAndViews(wasmMemory.buffer);return 1}catch(e){}}function _emscripten_resize_heap(requestedSize){requestedSize=requestedSize>>>0;var oldSize=_emscripten_get_heap_size();var maxHeapSize=2147483648;if(requestedSize>maxHeapSize){return false}var minHeapSize=16777216;for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignUp(Math.max(minHeapSize,requestedSize,overGrownHeapSize),65536));var replacement=emscripten_realloc_buffer(newSize);if(replacement){return true}}return false}var _emscripten_get_now;if(ENVIRONMENT_IS_NODE){_emscripten_get_now=function(){var t=process["hrtime"]();return t[0]*1e3+t[1]/1e6}}else if(typeof dateNow!=="undefined"){_emscripten_get_now=dateNow}else _emscripten_get_now=function(){return performance.now()};function _emscripten_thread_sleep(msecs){var start=_emscripten_get_now();while(_emscripten_get_now()-start>2]=strings.length;var bufSize=0;strings.forEach(function(string){bufSize+=string.length+1});HEAP32[penviron_buf_size>>2]=bufSize;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _fd_close(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);FS.close(stream);return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _fd_fdstat_get(fd,pbuf){try{var stream=SYSCALLS.getStreamFromFD(fd);var type=stream.tty?2:FS.isDir(stream.mode)?3:FS.isLink(stream.mode)?7:4;HEAP8[pbuf>>0]=type;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _fd_read(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=SYSCALLS.doReadv(stream,iov,iovcnt);HEAP32[pnum>>2]=num;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _fd_seek(fd,offset_low,offset_high,whence,newOffset){try{var stream=SYSCALLS.getStreamFromFD(fd);var HIGH_OFFSET=4294967296;var offset=offset_high*HIGH_OFFSET+(offset_low>>>0);var DOUBLE_LIMIT=9007199254740992;if(offset<=-DOUBLE_LIMIT||offset>=DOUBLE_LIMIT){return-61}FS.llseek(stream,offset,whence);tempI64=[stream.position>>>0,(tempDouble=stream.position,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[newOffset>>2]=tempI64[0],HEAP32[newOffset+4>>2]=tempI64[1];if(stream.getdents&&offset===0&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _fd_write(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=SYSCALLS.doWritev(stream,iov,iovcnt);HEAP32[pnum>>2]=num;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _setTempRet0($i){setTempRet0($i|0)}function _time(ptr){var ret=Date.now()/1e3|0;if(ptr){HEAP32[ptr>>2]=ret}return ret}function _uuid_generate(out){var uuid=null;if(ENVIRONMENT_IS_NODE){try{var rb=require("crypto")["randomBytes"];uuid=rb(16)}catch(e){}}else if(ENVIRONMENT_IS_WEB&&typeof window.crypto!=="undefined"&&typeof window.crypto.getRandomValues!=="undefined"){uuid=new Uint8Array(16);window.crypto.getRandomValues(uuid)}if(!uuid){uuid=new Array(16);var d=(new Date).getTime();for(var i=0;i<16;i++){var r=(d+Math.random()*256)%256|0;d=d/256|0;uuid[i]=r}}uuid[6]=uuid[6]&15|64;uuid[8]=uuid[8]&127|128;writeArrayToMemory(uuid,out)}var FSNode=function(parent,name,mode,rdev){if(!parent){parent=this}this.parent=parent;this.mount=parent.mount;this.mounted=null;this.id=FS.nextInode++;this.name=name;this.mode=mode;this.node_ops={};this.stream_ops={};this.rdev=rdev};var readMode=292|73;var writeMode=146;Object.defineProperties(FSNode.prototype,{read:{get:function(){return(this.mode&readMode)===readMode},set:function(val){val?this.mode|=readMode:this.mode&=~readMode}},write:{get:function(){return(this.mode&writeMode)===writeMode},set:function(val){val?this.mode|=writeMode:this.mode&=~writeMode}},isFolder:{get:function(){return FS.isDir(this.mode)}},isDevice:{get:function(){return FS.isChrdev(this.mode)}}});FS.FSNode=FSNode;FS.staticInit();InternalError=Module["InternalError"]=extendError(Error,"InternalError");embind_init_charCodes();BindingError=Module["BindingError"]=extendError(Error,"BindingError");init_ClassHandle();init_RegisteredPointer();init_embind();UnboundTypeError=Module["UnboundTypeError"]=extendError(Error,"UnboundTypeError");init_emval();var ASSERTIONS=false;function intArrayFromString(stringy,dontAddNull,length){var len=length>0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array}__ATINIT__.push({func:function(){___wasm_call_ctors()}});var asmLibraryArg={"__cxa_allocate_exception":___cxa_allocate_exception,"__cxa_atexit":___cxa_atexit,"__cxa_throw":___cxa_throw,"__gmtime_r":___gmtime_r,"__sys_fcntl64":___sys_fcntl64,"__sys_fstat64":___sys_fstat64,"__sys_ioctl":___sys_ioctl,"__sys_open":___sys_open,"__sys_stat64":___sys_stat64,"_embind_finalize_value_array":__embind_finalize_value_array,"_embind_finalize_value_object":__embind_finalize_value_object,"_embind_register_bool":__embind_register_bool,"_embind_register_class":__embind_register_class,"_embind_register_class_class_function":__embind_register_class_class_function,"_embind_register_class_constructor":__embind_register_class_constructor,"_embind_register_class_function":__embind_register_class_function,"_embind_register_class_property":__embind_register_class_property,"_embind_register_emval":__embind_register_emval,"_embind_register_enum":__embind_register_enum,"_embind_register_enum_value":__embind_register_enum_value,"_embind_register_float":__embind_register_float,"_embind_register_integer":__embind_register_integer,"_embind_register_memory_view":__embind_register_memory_view,"_embind_register_std_string":__embind_register_std_string,"_embind_register_std_wstring":__embind_register_std_wstring,"_embind_register_value_array":__embind_register_value_array,"_embind_register_value_array_element":__embind_register_value_array_element,"_embind_register_value_object":__embind_register_value_object,"_embind_register_value_object_field":__embind_register_value_object_field,"_embind_register_void":__embind_register_void,"_emval_as":__emval_as,"_emval_call_method":__emval_call_method,"_emval_call_void_method":__emval_call_void_method,"_emval_decref":__emval_decref,"_emval_equals":__emval_equals,"_emval_get_global":__emval_get_global,"_emval_get_method_caller":__emval_get_method_caller,"_emval_get_module_property":__emval_get_module_property,"_emval_get_property":__emval_get_property,"_emval_incref":__emval_incref,"_emval_instanceof":__emval_instanceof,"_emval_is_number":__emval_is_number,"_emval_is_string":__emval_is_string,"_emval_new":__emval_new,"_emval_new_array":__emval_new_array,"_emval_new_cstring":__emval_new_cstring,"_emval_new_object":__emval_new_object,"_emval_run_destructors":__emval_run_destructors,"_emval_set_property":__emval_set_property,"_emval_take_value":__emval_take_value,"abort":_abort,"emscripten_memcpy_big":_emscripten_memcpy_big,"emscripten_resize_heap":_emscripten_resize_heap,"emscripten_thread_sleep":_emscripten_thread_sleep,"environ_get":_environ_get,"environ_sizes_get":_environ_sizes_get,"fd_close":_fd_close,"fd_fdstat_get":_fd_fdstat_get,"fd_read":_fd_read,"fd_seek":_fd_seek,"fd_write":_fd_write,"setTempRet0":_setTempRet0,"time":_time,"uuid_generate":_uuid_generate};var asm=createWasm();var ___wasm_call_ctors=Module["___wasm_call_ctors"]=function(){return(___wasm_call_ctors=Module["___wasm_call_ctors"]=Module["asm"]["__wasm_call_ctors"]).apply(null,arguments)};var _malloc=Module["_malloc"]=function(){return(_malloc=Module["_malloc"]=Module["asm"]["malloc"]).apply(null,arguments)};var _free=Module["_free"]=function(){return(_free=Module["_free"]=Module["asm"]["free"]).apply(null,arguments)};var ___getTypeName=Module["___getTypeName"]=function(){return(___getTypeName=Module["___getTypeName"]=Module["asm"]["__getTypeName"]).apply(null,arguments)};var ___embind_register_native_and_builtin_types=Module["___embind_register_native_and_builtin_types"]=function(){return(___embind_register_native_and_builtin_types=Module["___embind_register_native_and_builtin_types"]=Module["asm"]["__embind_register_native_and_builtin_types"]).apply(null,arguments)};var ___errno_location=Module["___errno_location"]=function(){return(___errno_location=Module["___errno_location"]=Module["asm"]["__errno_location"]).apply(null,arguments)};var stackSave=Module["stackSave"]=function(){return(stackSave=Module["stackSave"]=Module["asm"]["stackSave"]).apply(null,arguments)};var stackRestore=Module["stackRestore"]=function(){return(stackRestore=Module["stackRestore"]=Module["asm"]["stackRestore"]).apply(null,arguments)};var stackAlloc=Module["stackAlloc"]=function(){return(stackAlloc=Module["stackAlloc"]=Module["asm"]["stackAlloc"]).apply(null,arguments)};var _setThrew=Module["_setThrew"]=function(){return(_setThrew=Module["_setThrew"]=Module["asm"]["setThrew"]).apply(null,arguments)};var dynCall_ji=Module["dynCall_ji"]=function(){return(dynCall_ji=Module["dynCall_ji"]=Module["asm"]["dynCall_ji"]).apply(null,arguments)};var dynCall_jiji=Module["dynCall_jiji"]=function(){return(dynCall_jiji=Module["dynCall_jiji"]=Module["asm"]["dynCall_jiji"]).apply(null,arguments)};var calledRun;function ExitStatus(status){this.name="ExitStatus";this.message="Program terminated with exit("+status+")";this.status=status}dependenciesFulfilled=function runCaller(){if(!calledRun)run();if(!calledRun)dependenciesFulfilled=runCaller};function run(args){args=args||arguments_;if(runDependencies>0){return}preRun();if(runDependencies>0)return;function doRun(){if(calledRun)return;calledRun=true;Module["calledRun"]=true;if(ABORT)return;initRuntime();preMain();readyPromiseResolve(Module);if(Module["onRuntimeInitialized"])Module["onRuntimeInitialized"]();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(function(){setTimeout(function(){Module["setStatus"]("")},1);doRun()},1)}else{doRun()}}Module["run"]=run;if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}noExitRuntime=true;run(); +var Module=typeof rhino3dm!="undefined"?rhino3dm:{};var readyPromiseResolve,readyPromiseReject;Module["ready"]=new Promise(function(resolve,reject){readyPromiseResolve=resolve;readyPromiseReject=reject});var moduleOverrides=Object.assign({},Module);var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var ENVIRONMENT_IS_WEB=typeof window=="object";var ENVIRONMENT_IS_WORKER=typeof importScripts=="function";var ENVIRONMENT_IS_NODE=typeof process=="object"&&typeof process.versions=="object"&&typeof process.versions.node=="string";var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var read_,readAsync,readBinary,setWindowTitle;function logExceptionOnExit(e){if(e instanceof ExitStatus)return;let toLog=e;err("exiting due to exception: "+toLog)}if(ENVIRONMENT_IS_NODE){var fs=require("fs");var nodePath=require("path");if(ENVIRONMENT_IS_WORKER){scriptDirectory=nodePath.dirname(scriptDirectory)+"/"}else{scriptDirectory=__dirname+"/"}read_=(filename,binary)=>{filename=isFileURI(filename)?new URL(filename):nodePath.normalize(filename);return fs.readFileSync(filename,binary?undefined:"utf8")};readBinary=filename=>{var ret=read_(filename,true);if(!ret.buffer){ret=new Uint8Array(ret)}return ret};readAsync=(filename,onload,onerror)=>{filename=isFileURI(filename)?new URL(filename):nodePath.normalize(filename);fs.readFile(filename,function(err,data){if(err)onerror(err);else onload(data.buffer)})};if(process["argv"].length>1){thisProgram=process["argv"][1].replace(/\\/g,"/")}arguments_=process["argv"].slice(2);process["on"]("uncaughtException",function(ex){if(!(ex instanceof ExitStatus)){throw ex}});process["on"]("unhandledRejection",function(reason){throw reason});quit_=(status,toThrow)=>{if(keepRuntimeAlive()){process["exitCode"]=status;throw toThrow}logExceptionOnExit(toThrow);process["exit"](status)};Module["inspect"]=function(){return"[Emscripten Module object]"}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(_scriptDir){scriptDirectory=_scriptDir}if(scriptDirectory.indexOf("blob:")!==0){scriptDirectory=scriptDirectory.substr(0,scriptDirectory.replace(/[?#].*/,"").lastIndexOf("/")+1)}else{scriptDirectory=""}{read_=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=(url,onload,onerror)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}onerror()};xhr.onerror=onerror;xhr.send(null)}}setWindowTitle=title=>document.title=title}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.warn.bind(console);Object.assign(Module,moduleOverrides);moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var POINTER_SIZE=4;var wasmBinary;if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];var noExitRuntime=Module["noExitRuntime"]||true;if(typeof WebAssembly!="object"){abort("no native wasm support detected")}var wasmMemory;var ABORT=false;var EXITSTATUS;function assert(condition,text){if(!condition){abort(text)}}var UTF8Decoder=typeof TextDecoder!="undefined"?new TextDecoder("utf8"):undefined;function UTF8ArrayToString(heapOrArray,idx,maxBytesToRead){var endIdx=idx+maxBytesToRead;var endPtr=idx;while(heapOrArray[endPtr]&&!(endPtr>=endIdx))++endPtr;if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx>10,56320|ch&1023)}}return str}function UTF8ToString(ptr,maxBytesToRead){return ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):""}function stringToUTF8Array(str,heap,outIdx,maxBytesToWrite){if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}}heap[outIdx]=0;return outIdx-startIdx}function stringToUTF8(str,outPtr,maxBytesToWrite){return stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite)}function lengthBytesUTF8(str){var len=0;for(var i=0;i=55296&&c<=57343){len+=4;++i}else{len+=3}}return len}var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateMemoryViews(){var b=wasmMemory.buffer;Module["HEAP8"]=HEAP8=new Int8Array(b);Module["HEAP16"]=HEAP16=new Int16Array(b);Module["HEAP32"]=HEAP32=new Int32Array(b);Module["HEAPU8"]=HEAPU8=new Uint8Array(b);Module["HEAPU16"]=HEAPU16=new Uint16Array(b);Module["HEAPU32"]=HEAPU32=new Uint32Array(b);Module["HEAPF32"]=HEAPF32=new Float32Array(b);Module["HEAPF64"]=HEAPF64=new Float64Array(b)}var INITIAL_MEMORY=Module["INITIAL_MEMORY"]||16777216;var wasmTable;var __ATPRERUN__=[];var __ATINIT__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;function keepRuntimeAlive(){return noExitRuntime}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;SOCKFS.root=FS.mount(SOCKFS,{},null);if(!Module["noFSInit"]&&!FS.init.initialized)FS.init();FS.ignorePermissions=false;TTY.init();callRuntimeCallbacks(__ATINIT__)}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnInit(cb){__ATINIT__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function getUniqueRunDependency(id){return id}function addRunDependency(id){runDependencies++;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}}function removeRunDependency(id){runDependencies--;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}function abort(what){if(Module["onAbort"]){Module["onAbort"](what)}what="Aborted("+what+")";err(what);ABORT=true;EXITSTATUS=1;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject(e);throw e}var dataURIPrefix="data:application/octet-stream;base64,";function isDataURI(filename){return filename.startsWith(dataURIPrefix)}function isFileURI(filename){return filename.startsWith("file://")}var wasmBinaryFile;wasmBinaryFile="rhino3dm.wasm";if(!isDataURI(wasmBinaryFile)){wasmBinaryFile=locateFile(wasmBinaryFile)}function getBinary(file){try{if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}catch(err){abort(err)}}function getBinaryPromise(){if(!wasmBinary&&(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER)){if(typeof fetch=="function"&&!isFileURI(wasmBinaryFile)){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){if(!response["ok"]){throw"failed to load wasm binary file at '"+wasmBinaryFile+"'"}return response["arrayBuffer"]()}).catch(function(){return getBinary(wasmBinaryFile)})}else{if(readAsync){return new Promise(function(resolve,reject){readAsync(wasmBinaryFile,function(response){resolve(new Uint8Array(response))},reject)})}}}return Promise.resolve().then(function(){return getBinary(wasmBinaryFile)})}function createWasm(){var info={"a":asmLibraryArg};function receiveInstance(instance,module){var exports=instance.exports;Module["asm"]=exports;wasmMemory=Module["asm"]["oa"];updateMemoryViews();wasmTable=Module["asm"]["qa"];addOnInit(Module["asm"]["pa"]);removeRunDependency("wasm-instantiate")}addRunDependency("wasm-instantiate");function receiveInstantiationResult(result){receiveInstance(result["instance"])}function instantiateArrayBuffer(receiver){return getBinaryPromise().then(function(binary){return WebAssembly.instantiate(binary,info)}).then(function(instance){return instance}).then(receiver,function(reason){err("failed to asynchronously prepare wasm: "+reason);abort(reason)})}function instantiateAsync(){if(!wasmBinary&&typeof WebAssembly.instantiateStreaming=="function"&&!isDataURI(wasmBinaryFile)&&!isFileURI(wasmBinaryFile)&&!ENVIRONMENT_IS_NODE&&typeof fetch=="function"){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){var result=WebAssembly.instantiateStreaming(response,info);return result.then(receiveInstantiationResult,function(reason){err("wasm streaming compile failed: "+reason);err("falling back to ArrayBuffer instantiation");return instantiateArrayBuffer(receiveInstantiationResult)})})}else{return instantiateArrayBuffer(receiveInstantiationResult)}}if(Module["instantiateWasm"]){try{var exports=Module["instantiateWasm"](info,receiveInstance);return exports}catch(e){err("Module.instantiateWasm callback failed with error: "+e);readyPromiseReject(e)}}instantiateAsync().catch(readyPromiseReject);return{}}var tempDouble;var tempI64;function ExitStatus(status){this.name="ExitStatus";this.message="Program terminated with exit("+status+")";this.status=status}function callRuntimeCallbacks(callbacks){while(callbacks.length>0){callbacks.shift()(Module)}}function ExceptionInfo(excPtr){this.excPtr=excPtr;this.ptr=excPtr-24;this.set_type=function(type){HEAPU32[this.ptr+4>>2]=type};this.get_type=function(){return HEAPU32[this.ptr+4>>2]};this.set_destructor=function(destructor){HEAPU32[this.ptr+8>>2]=destructor};this.get_destructor=function(){return HEAPU32[this.ptr+8>>2]};this.set_refcount=function(refcount){HEAP32[this.ptr>>2]=refcount};this.set_caught=function(caught){caught=caught?1:0;HEAP8[this.ptr+12>>0]=caught};this.get_caught=function(){return HEAP8[this.ptr+12>>0]!=0};this.set_rethrown=function(rethrown){rethrown=rethrown?1:0;HEAP8[this.ptr+13>>0]=rethrown};this.get_rethrown=function(){return HEAP8[this.ptr+13>>0]!=0};this.init=function(type,destructor){this.set_adjusted_ptr(0);this.set_type(type);this.set_destructor(destructor);this.set_refcount(0);this.set_caught(false);this.set_rethrown(false)};this.add_ref=function(){var value=HEAP32[this.ptr>>2];HEAP32[this.ptr>>2]=value+1};this.release_ref=function(){var prev=HEAP32[this.ptr>>2];HEAP32[this.ptr>>2]=prev-1;return prev===1};this.set_adjusted_ptr=function(adjustedPtr){HEAPU32[this.ptr+16>>2]=adjustedPtr};this.get_adjusted_ptr=function(){return HEAPU32[this.ptr+16>>2]};this.get_exception_ptr=function(){var isPointer=___cxa_is_pointer_type(this.get_type());if(isPointer){return HEAPU32[this.excPtr>>2]}var adjusted=this.get_adjusted_ptr();if(adjusted!==0)return adjusted;return this.excPtr}}var exceptionLast=0;var uncaughtExceptionCount=0;function ___cxa_throw(ptr,type,destructor){var info=new ExceptionInfo(ptr);info.init(type,destructor);exceptionLast=ptr;uncaughtExceptionCount++;throw ptr}function getRandomDevice(){if(typeof crypto=="object"&&typeof crypto["getRandomValues"]=="function"){var randomBuffer=new Uint8Array(1);return()=>{crypto.getRandomValues(randomBuffer);return randomBuffer[0]}}else if(ENVIRONMENT_IS_NODE){try{var crypto_module=require("crypto");return()=>crypto_module["randomBytes"](1)[0]}catch(e){}}return()=>abort("randomDevice")}var PATH={isAbs:path=>path.charAt(0)==="/",splitPath:filename=>{var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;return splitPathRe.exec(filename).slice(1)},normalizeArray:(parts,allowAboveRoot)=>{var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up;up--){parts.unshift("..")}}return parts},normalize:path=>{var isAbsolute=PATH.isAbs(path),trailingSlash=path.substr(-1)==="/";path=PATH.normalizeArray(path.split("/").filter(p=>!!p),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path},dirname:path=>{var result=PATH.splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return"."}if(dir){dir=dir.substr(0,dir.length-1)}return root+dir},basename:path=>{if(path==="/")return"/";path=PATH.normalize(path);path=path.replace(/\/$/,"");var lastSlash=path.lastIndexOf("/");if(lastSlash===-1)return path;return path.substr(lastSlash+1)},join:function(){var paths=Array.prototype.slice.call(arguments);return PATH.normalize(paths.join("/"))},join2:(l,r)=>{return PATH.normalize(l+"/"+r)}};var PATH_FS={resolve:function(){var resolvedPath="",resolvedAbsolute=false;for(var i=arguments.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?arguments[i]:FS.cwd();if(typeof path!="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){return""}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=PATH.isAbs(path)}resolvedPath=PATH.normalizeArray(resolvedPath.split("/").filter(p=>!!p),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."},relative:(from,to)=>{from=PATH_FS.resolve(from).substr(1);to=PATH_FS.resolve(to).substr(1);function trim(arr){var start=0;for(;start=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array}var TTY={ttys:[],init:function(){},shutdown:function(){},register:function(dev,ops){TTY.ttys[dev]={input:[],output:[],ops:ops};FS.registerDevice(dev,TTY.stream_ops)},stream_ops:{open:function(stream){var tty=TTY.ttys[stream.node.rdev];if(!tty){throw new FS.ErrnoError(43)}stream.tty=tty;stream.seekable=false},close:function(stream){stream.tty.ops.fsync(stream.tty)},fsync:function(stream){stream.tty.ops.fsync(stream.tty)},read:function(stream,buffer,offset,length,pos){if(!stream.tty||!stream.tty.ops.get_char){throw new FS.ErrnoError(60)}var bytesRead=0;for(var i=0;i0){result=buf.slice(0,bytesRead).toString("utf-8")}else{result=null}}else if(typeof window!="undefined"&&typeof window.prompt=="function"){result=window.prompt("Input: ");if(result!==null){result+="\n"}}else if(typeof readline=="function"){result=readline();if(result!==null){result+="\n"}}if(!result){return null}tty.input=intArrayFromString(result,true)}return tty.input.shift()},put_char:function(tty,val){if(val===null||val===10){out(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},fsync:function(tty){if(tty.output&&tty.output.length>0){out(UTF8ArrayToString(tty.output,0));tty.output=[]}}},default_tty1_ops:{put_char:function(tty,val){if(val===null||val===10){err(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},fsync:function(tty){if(tty.output&&tty.output.length>0){err(UTF8ArrayToString(tty.output,0));tty.output=[]}}}};function mmapAlloc(size){abort()}var MEMFS={ops_table:null,mount:function(mount){return MEMFS.createNode(null,"/",16384|511,0)},createNode:function(parent,name,mode,dev){if(FS.isBlkdev(mode)||FS.isFIFO(mode)){throw new FS.ErrnoError(63)}if(!MEMFS.ops_table){MEMFS.ops_table={dir:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,lookup:MEMFS.node_ops.lookup,mknod:MEMFS.node_ops.mknod,rename:MEMFS.node_ops.rename,unlink:MEMFS.node_ops.unlink,rmdir:MEMFS.node_ops.rmdir,readdir:MEMFS.node_ops.readdir,symlink:MEMFS.node_ops.symlink},stream:{llseek:MEMFS.stream_ops.llseek}},file:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:{llseek:MEMFS.stream_ops.llseek,read:MEMFS.stream_ops.read,write:MEMFS.stream_ops.write,allocate:MEMFS.stream_ops.allocate,mmap:MEMFS.stream_ops.mmap,msync:MEMFS.stream_ops.msync}},link:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,readlink:MEMFS.node_ops.readlink},stream:{}},chrdev:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:FS.chrdev_stream_ops}}}var node=FS.createNode(parent,name,mode,dev);if(FS.isDir(node.mode)){node.node_ops=MEMFS.ops_table.dir.node;node.stream_ops=MEMFS.ops_table.dir.stream;node.contents={}}else if(FS.isFile(node.mode)){node.node_ops=MEMFS.ops_table.file.node;node.stream_ops=MEMFS.ops_table.file.stream;node.usedBytes=0;node.contents=null}else if(FS.isLink(node.mode)){node.node_ops=MEMFS.ops_table.link.node;node.stream_ops=MEMFS.ops_table.link.stream}else if(FS.isChrdev(node.mode)){node.node_ops=MEMFS.ops_table.chrdev.node;node.stream_ops=MEMFS.ops_table.chrdev.stream}node.timestamp=Date.now();if(parent){parent.contents[name]=node;parent.timestamp=node.timestamp}return node},getFileDataAsTypedArray:function(node){if(!node.contents)return new Uint8Array(0);if(node.contents.subarray)return node.contents.subarray(0,node.usedBytes);return new Uint8Array(node.contents)},expandFileStorage:function(node,newCapacity){var prevCapacity=node.contents?node.contents.length:0;if(prevCapacity>=newCapacity)return;var CAPACITY_DOUBLING_MAX=1024*1024;newCapacity=Math.max(newCapacity,prevCapacity*(prevCapacity>>0);if(prevCapacity!=0)newCapacity=Math.max(newCapacity,256);var oldContents=node.contents;node.contents=new Uint8Array(newCapacity);if(node.usedBytes>0)node.contents.set(oldContents.subarray(0,node.usedBytes),0)},resizeFileStorage:function(node,newSize){if(node.usedBytes==newSize)return;if(newSize==0){node.contents=null;node.usedBytes=0}else{var oldContents=node.contents;node.contents=new Uint8Array(newSize);if(oldContents){node.contents.set(oldContents.subarray(0,Math.min(newSize,node.usedBytes)))}node.usedBytes=newSize}},node_ops:{getattr:function(node){var attr={};attr.dev=FS.isChrdev(node.mode)?node.id:1;attr.ino=node.id;attr.mode=node.mode;attr.nlink=1;attr.uid=0;attr.gid=0;attr.rdev=node.rdev;if(FS.isDir(node.mode)){attr.size=4096}else if(FS.isFile(node.mode)){attr.size=node.usedBytes}else if(FS.isLink(node.mode)){attr.size=node.link.length}else{attr.size=0}attr.atime=new Date(node.timestamp);attr.mtime=new Date(node.timestamp);attr.ctime=new Date(node.timestamp);attr.blksize=4096;attr.blocks=Math.ceil(attr.size/attr.blksize);return attr},setattr:function(node,attr){if(attr.mode!==undefined){node.mode=attr.mode}if(attr.timestamp!==undefined){node.timestamp=attr.timestamp}if(attr.size!==undefined){MEMFS.resizeFileStorage(node,attr.size)}},lookup:function(parent,name){throw FS.genericErrors[44]},mknod:function(parent,name,mode,dev){return MEMFS.createNode(parent,name,mode,dev)},rename:function(old_node,new_dir,new_name){if(FS.isDir(old_node.mode)){var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(new_node){for(var i in new_node.contents){throw new FS.ErrnoError(55)}}}delete old_node.parent.contents[old_node.name];old_node.parent.timestamp=Date.now();old_node.name=new_name;new_dir.contents[new_name]=old_node;new_dir.timestamp=old_node.parent.timestamp;old_node.parent=new_dir},unlink:function(parent,name){delete parent.contents[name];parent.timestamp=Date.now()},rmdir:function(parent,name){var node=FS.lookupNode(parent,name);for(var i in node.contents){throw new FS.ErrnoError(55)}delete parent.contents[name];parent.timestamp=Date.now()},readdir:function(node){var entries=[".",".."];for(var key in node.contents){if(!node.contents.hasOwnProperty(key)){continue}entries.push(key)}return entries},symlink:function(parent,newname,oldpath){var node=MEMFS.createNode(parent,newname,511|40960,0);node.link=oldpath;return node},readlink:function(node){if(!FS.isLink(node.mode)){throw new FS.ErrnoError(28)}return node.link}},stream_ops:{read:function(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=stream.node.usedBytes)return 0;var size=Math.min(stream.node.usedBytes-position,length);if(size>8&&contents.subarray){buffer.set(contents.subarray(position,position+size),offset)}else{for(var i=0;i0||position+length{assert(arrayBuffer,'Loading data file "'+url+'" failed (no arrayBuffer).');onload(new Uint8Array(arrayBuffer));if(dep)removeRunDependency(dep)},event=>{if(onerror){onerror()}else{throw'Loading data file "'+url+'" failed.'}});if(dep)addRunDependency(dep)}var FS={root:null,mounts:[],devices:{},streams:[],nextInode:1,nameTable:null,currentPath:"/",initialized:false,ignorePermissions:true,ErrnoError:null,genericErrors:{},filesystems:null,syncFSRequests:0,lookupPath:(path,opts={})=>{path=PATH_FS.resolve(path);if(!path)return{path:"",node:null};var defaults={follow_mount:true,recurse_count:0};opts=Object.assign(defaults,opts);if(opts.recurse_count>8){throw new FS.ErrnoError(32)}var parts=path.split("/").filter(p=>!!p);var current=FS.root;var current_path="/";for(var i=0;i40){throw new FS.ErrnoError(32)}}}}return{path:current_path,node:current}},getPath:node=>{var path;while(true){if(FS.isRoot(node)){var mount=node.mount.mountpoint;if(!path)return mount;return mount[mount.length-1]!=="/"?mount+"/"+path:mount+path}path=path?node.name+"/"+path:node.name;node=node.parent}},hashName:(parentid,name)=>{var hash=0;for(var i=0;i>>0)%FS.nameTable.length},hashAddNode:node=>{var hash=FS.hashName(node.parent.id,node.name);node.name_next=FS.nameTable[hash];FS.nameTable[hash]=node},hashRemoveNode:node=>{var hash=FS.hashName(node.parent.id,node.name);if(FS.nameTable[hash]===node){FS.nameTable[hash]=node.name_next}else{var current=FS.nameTable[hash];while(current){if(current.name_next===node){current.name_next=node.name_next;break}current=current.name_next}}},lookupNode:(parent,name)=>{var errCode=FS.mayLookup(parent);if(errCode){throw new FS.ErrnoError(errCode,parent)}var hash=FS.hashName(parent.id,name);for(var node=FS.nameTable[hash];node;node=node.name_next){var nodeName=node.name;if(node.parent.id===parent.id&&nodeName===name){return node}}return FS.lookup(parent,name)},createNode:(parent,name,mode,rdev)=>{var node=new FS.FSNode(parent,name,mode,rdev);FS.hashAddNode(node);return node},destroyNode:node=>{FS.hashRemoveNode(node)},isRoot:node=>{return node===node.parent},isMountpoint:node=>{return!!node.mounted},isFile:mode=>{return(mode&61440)===32768},isDir:mode=>{return(mode&61440)===16384},isLink:mode=>{return(mode&61440)===40960},isChrdev:mode=>{return(mode&61440)===8192},isBlkdev:mode=>{return(mode&61440)===24576},isFIFO:mode=>{return(mode&61440)===4096},isSocket:mode=>{return(mode&49152)===49152},flagModes:{"r":0,"r+":2,"w":577,"w+":578,"a":1089,"a+":1090},modeStringToFlags:str=>{var flags=FS.flagModes[str];if(typeof flags=="undefined"){throw new Error("Unknown file open mode: "+str)}return flags},flagsToPermissionString:flag=>{var perms=["r","w","rw"][flag&3];if(flag&512){perms+="w"}return perms},nodePermissions:(node,perms)=>{if(FS.ignorePermissions){return 0}if(perms.includes("r")&&!(node.mode&292)){return 2}else if(perms.includes("w")&&!(node.mode&146)){return 2}else if(perms.includes("x")&&!(node.mode&73)){return 2}return 0},mayLookup:dir=>{var errCode=FS.nodePermissions(dir,"x");if(errCode)return errCode;if(!dir.node_ops.lookup)return 2;return 0},mayCreate:(dir,name)=>{try{var node=FS.lookupNode(dir,name);return 20}catch(e){}return FS.nodePermissions(dir,"wx")},mayDelete:(dir,name,isdir)=>{var node;try{node=FS.lookupNode(dir,name)}catch(e){return e.errno}var errCode=FS.nodePermissions(dir,"wx");if(errCode){return errCode}if(isdir){if(!FS.isDir(node.mode)){return 54}if(FS.isRoot(node)||FS.getPath(node)===FS.cwd()){return 10}}else{if(FS.isDir(node.mode)){return 31}}return 0},mayOpen:(node,flags)=>{if(!node){return 44}if(FS.isLink(node.mode)){return 32}else if(FS.isDir(node.mode)){if(FS.flagsToPermissionString(flags)!=="r"||flags&512){return 31}}return FS.nodePermissions(node,FS.flagsToPermissionString(flags))},MAX_OPEN_FDS:4096,nextfd:(fd_start=0,fd_end=FS.MAX_OPEN_FDS)=>{for(var fd=fd_start;fd<=fd_end;fd++){if(!FS.streams[fd]){return fd}}throw new FS.ErrnoError(33)},getStream:fd=>FS.streams[fd],createStream:(stream,fd_start,fd_end)=>{if(!FS.FSStream){FS.FSStream=function(){this.shared={}};FS.FSStream.prototype={};Object.defineProperties(FS.FSStream.prototype,{object:{get:function(){return this.node},set:function(val){this.node=val}},isRead:{get:function(){return(this.flags&2097155)!==1}},isWrite:{get:function(){return(this.flags&2097155)!==0}},isAppend:{get:function(){return this.flags&1024}},flags:{get:function(){return this.shared.flags},set:function(val){this.shared.flags=val}},position:{get:function(){return this.shared.position},set:function(val){this.shared.position=val}}})}stream=Object.assign(new FS.FSStream,stream);var fd=FS.nextfd(fd_start,fd_end);stream.fd=fd;FS.streams[fd]=stream;return stream},closeStream:fd=>{FS.streams[fd]=null},chrdev_stream_ops:{open:stream=>{var device=FS.getDevice(stream.node.rdev);stream.stream_ops=device.stream_ops;if(stream.stream_ops.open){stream.stream_ops.open(stream)}},llseek:()=>{throw new FS.ErrnoError(70)}},major:dev=>dev>>8,minor:dev=>dev&255,makedev:(ma,mi)=>ma<<8|mi,registerDevice:(dev,ops)=>{FS.devices[dev]={stream_ops:ops}},getDevice:dev=>FS.devices[dev],getMounts:mount=>{var mounts=[];var check=[mount];while(check.length){var m=check.pop();mounts.push(m);check.push.apply(check,m.mounts)}return mounts},syncfs:(populate,callback)=>{if(typeof populate=="function"){callback=populate;populate=false}FS.syncFSRequests++;if(FS.syncFSRequests>1){err("warning: "+FS.syncFSRequests+" FS.syncfs operations in flight at once, probably just doing extra work")}var mounts=FS.getMounts(FS.root.mount);var completed=0;function doCallback(errCode){FS.syncFSRequests--;return callback(errCode)}function done(errCode){if(errCode){if(!done.errored){done.errored=true;return doCallback(errCode)}return}if(++completed>=mounts.length){doCallback(null)}}mounts.forEach(mount=>{if(!mount.type.syncfs){return done(null)}mount.type.syncfs(mount,populate,done)})},mount:(type,opts,mountpoint)=>{var root=mountpoint==="/";var pseudo=!mountpoint;var node;if(root&&FS.root){throw new FS.ErrnoError(10)}else if(!root&&!pseudo){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});mountpoint=lookup.path;node=lookup.node;if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}if(!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}}var mount={type:type,opts:opts,mountpoint:mountpoint,mounts:[]};var mountRoot=type.mount(mount);mountRoot.mount=mount;mount.root=mountRoot;if(root){FS.root=mountRoot}else if(node){node.mounted=mount;if(node.mount){node.mount.mounts.push(mount)}}return mountRoot},unmount:mountpoint=>{var lookup=FS.lookupPath(mountpoint,{follow_mount:false});if(!FS.isMountpoint(lookup.node)){throw new FS.ErrnoError(28)}var node=lookup.node;var mount=node.mounted;var mounts=FS.getMounts(mount);Object.keys(FS.nameTable).forEach(hash=>{var current=FS.nameTable[hash];while(current){var next=current.name_next;if(mounts.includes(current.mount)){FS.destroyNode(current)}current=next}});node.mounted=null;var idx=node.mount.mounts.indexOf(mount);node.mount.mounts.splice(idx,1)},lookup:(parent,name)=>{return parent.node_ops.lookup(parent,name)},mknod:(path,mode,dev)=>{var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);if(!name||name==="."||name===".."){throw new FS.ErrnoError(28)}var errCode=FS.mayCreate(parent,name);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.mknod){throw new FS.ErrnoError(63)}return parent.node_ops.mknod(parent,name,mode,dev)},create:(path,mode)=>{mode=mode!==undefined?mode:438;mode&=4095;mode|=32768;return FS.mknod(path,mode,0)},mkdir:(path,mode)=>{mode=mode!==undefined?mode:511;mode&=511|512;mode|=16384;return FS.mknod(path,mode,0)},mkdirTree:(path,mode)=>{var dirs=path.split("/");var d="";for(var i=0;i{if(typeof dev=="undefined"){dev=mode;mode=438}mode|=8192;return FS.mknod(path,mode,dev)},symlink:(oldpath,newpath)=>{if(!PATH_FS.resolve(oldpath)){throw new FS.ErrnoError(44)}var lookup=FS.lookupPath(newpath,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var newname=PATH.basename(newpath);var errCode=FS.mayCreate(parent,newname);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.symlink){throw new FS.ErrnoError(63)}return parent.node_ops.symlink(parent,newname,oldpath)},rename:(old_path,new_path)=>{var old_dirname=PATH.dirname(old_path);var new_dirname=PATH.dirname(new_path);var old_name=PATH.basename(old_path);var new_name=PATH.basename(new_path);var lookup,old_dir,new_dir;lookup=FS.lookupPath(old_path,{parent:true});old_dir=lookup.node;lookup=FS.lookupPath(new_path,{parent:true});new_dir=lookup.node;if(!old_dir||!new_dir)throw new FS.ErrnoError(44);if(old_dir.mount!==new_dir.mount){throw new FS.ErrnoError(75)}var old_node=FS.lookupNode(old_dir,old_name);var relative=PATH_FS.relative(old_path,new_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(28)}relative=PATH_FS.relative(new_path,old_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(55)}var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(old_node===new_node){return}var isdir=FS.isDir(old_node.mode);var errCode=FS.mayDelete(old_dir,old_name,isdir);if(errCode){throw new FS.ErrnoError(errCode)}errCode=new_node?FS.mayDelete(new_dir,new_name,isdir):FS.mayCreate(new_dir,new_name);if(errCode){throw new FS.ErrnoError(errCode)}if(!old_dir.node_ops.rename){throw new FS.ErrnoError(63)}if(FS.isMountpoint(old_node)||new_node&&FS.isMountpoint(new_node)){throw new FS.ErrnoError(10)}if(new_dir!==old_dir){errCode=FS.nodePermissions(old_dir,"w");if(errCode){throw new FS.ErrnoError(errCode)}}FS.hashRemoveNode(old_node);try{old_dir.node_ops.rename(old_node,new_dir,new_name)}catch(e){throw e}finally{FS.hashAddNode(old_node)}},rmdir:path=>{var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,true);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.rmdir){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.rmdir(parent,name);FS.destroyNode(node)},readdir:path=>{var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;if(!node.node_ops.readdir){throw new FS.ErrnoError(54)}return node.node_ops.readdir(node)},unlink:path=>{var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,false);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.unlink){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.unlink(parent,name);FS.destroyNode(node)},readlink:path=>{var lookup=FS.lookupPath(path);var link=lookup.node;if(!link){throw new FS.ErrnoError(44)}if(!link.node_ops.readlink){throw new FS.ErrnoError(28)}return PATH_FS.resolve(FS.getPath(link.parent),link.node_ops.readlink(link))},stat:(path,dontFollow)=>{var lookup=FS.lookupPath(path,{follow:!dontFollow});var node=lookup.node;if(!node){throw new FS.ErrnoError(44)}if(!node.node_ops.getattr){throw new FS.ErrnoError(63)}return node.node_ops.getattr(node)},lstat:path=>{return FS.stat(path,true)},chmod:(path,mode,dontFollow)=>{var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}node.node_ops.setattr(node,{mode:mode&4095|node.mode&~4095,timestamp:Date.now()})},lchmod:(path,mode)=>{FS.chmod(path,mode,true)},fchmod:(fd,mode)=>{var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}FS.chmod(stream.node,mode)},chown:(path,uid,gid,dontFollow)=>{var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}node.node_ops.setattr(node,{timestamp:Date.now()})},lchown:(path,uid,gid)=>{FS.chown(path,uid,gid,true)},fchown:(fd,uid,gid)=>{var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}FS.chown(stream.node,uid,gid)},truncate:(path,len)=>{if(len<0){throw new FS.ErrnoError(28)}var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:true});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}if(FS.isDir(node.mode)){throw new FS.ErrnoError(31)}if(!FS.isFile(node.mode)){throw new FS.ErrnoError(28)}var errCode=FS.nodePermissions(node,"w");if(errCode){throw new FS.ErrnoError(errCode)}node.node_ops.setattr(node,{size:len,timestamp:Date.now()})},ftruncate:(fd,len)=>{var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(28)}FS.truncate(stream.node,len)},utime:(path,atime,mtime)=>{var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;node.node_ops.setattr(node,{timestamp:Math.max(atime,mtime)})},open:(path,flags,mode)=>{if(path===""){throw new FS.ErrnoError(44)}flags=typeof flags=="string"?FS.modeStringToFlags(flags):flags;mode=typeof mode=="undefined"?438:mode;if(flags&64){mode=mode&4095|32768}else{mode=0}var node;if(typeof path=="object"){node=path}else{path=PATH.normalize(path);try{var lookup=FS.lookupPath(path,{follow:!(flags&131072)});node=lookup.node}catch(e){}}var created=false;if(flags&64){if(node){if(flags&128){throw new FS.ErrnoError(20)}}else{node=FS.mknod(path,mode,0);created=true}}if(!node){throw new FS.ErrnoError(44)}if(FS.isChrdev(node.mode)){flags&=~512}if(flags&65536&&!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}if(!created){var errCode=FS.mayOpen(node,flags);if(errCode){throw new FS.ErrnoError(errCode)}}if(flags&512&&!created){FS.truncate(node,0)}flags&=~(128|512|131072);var stream=FS.createStream({node:node,path:FS.getPath(node),flags:flags,seekable:true,position:0,stream_ops:node.stream_ops,ungotten:[],error:false});if(stream.stream_ops.open){stream.stream_ops.open(stream)}if(Module["logReadFiles"]&&!(flags&1)){if(!FS.readFiles)FS.readFiles={};if(!(path in FS.readFiles)){FS.readFiles[path]=1}}return stream},close:stream=>{if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(stream.getdents)stream.getdents=null;try{if(stream.stream_ops.close){stream.stream_ops.close(stream)}}catch(e){throw e}finally{FS.closeStream(stream.fd)}stream.fd=null},isClosed:stream=>{return stream.fd===null},llseek:(stream,offset,whence)=>{if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(!stream.seekable||!stream.stream_ops.llseek){throw new FS.ErrnoError(70)}if(whence!=0&&whence!=1&&whence!=2){throw new FS.ErrnoError(28)}stream.position=stream.stream_ops.llseek(stream,offset,whence);stream.ungotten=[];return stream.position},read:(stream,buffer,offset,length,position)=>{if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.read){throw new FS.ErrnoError(28)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesRead=stream.stream_ops.read(stream,buffer,offset,length,position);if(!seeking)stream.position+=bytesRead;return bytesRead},write:(stream,buffer,offset,length,position,canOwn)=>{if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.write){throw new FS.ErrnoError(28)}if(stream.seekable&&stream.flags&1024){FS.llseek(stream,0,2)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesWritten=stream.stream_ops.write(stream,buffer,offset,length,position,canOwn);if(!seeking)stream.position+=bytesWritten;return bytesWritten},allocate:(stream,offset,length)=>{if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(offset<0||length<=0){throw new FS.ErrnoError(28)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(8)}if(!FS.isFile(stream.node.mode)&&!FS.isDir(stream.node.mode)){throw new FS.ErrnoError(43)}if(!stream.stream_ops.allocate){throw new FS.ErrnoError(138)}stream.stream_ops.allocate(stream,offset,length)},mmap:(stream,length,position,prot,flags)=>{if((prot&2)!==0&&(flags&2)===0&&(stream.flags&2097155)!==2){throw new FS.ErrnoError(2)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(2)}if(!stream.stream_ops.mmap){throw new FS.ErrnoError(43)}return stream.stream_ops.mmap(stream,length,position,prot,flags)},msync:(stream,buffer,offset,length,mmapFlags)=>{if(!stream.stream_ops.msync){return 0}return stream.stream_ops.msync(stream,buffer,offset,length,mmapFlags)},munmap:stream=>0,ioctl:(stream,cmd,arg)=>{if(!stream.stream_ops.ioctl){throw new FS.ErrnoError(59)}return stream.stream_ops.ioctl(stream,cmd,arg)},readFile:(path,opts={})=>{opts.flags=opts.flags||0;opts.encoding=opts.encoding||"binary";if(opts.encoding!=="utf8"&&opts.encoding!=="binary"){throw new Error('Invalid encoding type "'+opts.encoding+'"')}var ret;var stream=FS.open(path,opts.flags);var stat=FS.stat(path);var length=stat.size;var buf=new Uint8Array(length);FS.read(stream,buf,0,length,0);if(opts.encoding==="utf8"){ret=UTF8ArrayToString(buf,0)}else if(opts.encoding==="binary"){ret=buf}FS.close(stream);return ret},writeFile:(path,data,opts={})=>{opts.flags=opts.flags||577;var stream=FS.open(path,opts.flags,opts.mode);if(typeof data=="string"){var buf=new Uint8Array(lengthBytesUTF8(data)+1);var actualNumBytes=stringToUTF8Array(data,buf,0,buf.length);FS.write(stream,buf,0,actualNumBytes,undefined,opts.canOwn)}else if(ArrayBuffer.isView(data)){FS.write(stream,data,0,data.byteLength,undefined,opts.canOwn)}else{throw new Error("Unsupported data type")}FS.close(stream)},cwd:()=>FS.currentPath,chdir:path=>{var lookup=FS.lookupPath(path,{follow:true});if(lookup.node===null){throw new FS.ErrnoError(44)}if(!FS.isDir(lookup.node.mode)){throw new FS.ErrnoError(54)}var errCode=FS.nodePermissions(lookup.node,"x");if(errCode){throw new FS.ErrnoError(errCode)}FS.currentPath=lookup.path},createDefaultDirectories:()=>{FS.mkdir("/tmp");FS.mkdir("/home");FS.mkdir("/home/web_user")},createDefaultDevices:()=>{FS.mkdir("/dev");FS.registerDevice(FS.makedev(1,3),{read:()=>0,write:(stream,buffer,offset,length,pos)=>length});FS.mkdev("/dev/null",FS.makedev(1,3));TTY.register(FS.makedev(5,0),TTY.default_tty_ops);TTY.register(FS.makedev(6,0),TTY.default_tty1_ops);FS.mkdev("/dev/tty",FS.makedev(5,0));FS.mkdev("/dev/tty1",FS.makedev(6,0));var random_device=getRandomDevice();FS.createDevice("/dev","random",random_device);FS.createDevice("/dev","urandom",random_device);FS.mkdir("/dev/shm");FS.mkdir("/dev/shm/tmp")},createSpecialDirectories:()=>{FS.mkdir("/proc");var proc_self=FS.mkdir("/proc/self");FS.mkdir("/proc/self/fd");FS.mount({mount:()=>{var node=FS.createNode(proc_self,"fd",16384|511,73);node.node_ops={lookup:(parent,name)=>{var fd=+name;var stream=FS.getStream(fd);if(!stream)throw new FS.ErrnoError(8);var ret={parent:null,mount:{mountpoint:"fake"},node_ops:{readlink:()=>stream.path}};ret.parent=ret;return ret}};return node}},{},"/proc/self/fd")},createStandardStreams:()=>{if(Module["stdin"]){FS.createDevice("/dev","stdin",Module["stdin"])}else{FS.symlink("/dev/tty","/dev/stdin")}if(Module["stdout"]){FS.createDevice("/dev","stdout",null,Module["stdout"])}else{FS.symlink("/dev/tty","/dev/stdout")}if(Module["stderr"]){FS.createDevice("/dev","stderr",null,Module["stderr"])}else{FS.symlink("/dev/tty1","/dev/stderr")}var stdin=FS.open("/dev/stdin",0);var stdout=FS.open("/dev/stdout",1);var stderr=FS.open("/dev/stderr",1)},ensureErrnoError:()=>{if(FS.ErrnoError)return;FS.ErrnoError=function ErrnoError(errno,node){this.node=node;this.setErrno=function(errno){this.errno=errno};this.setErrno(errno);this.message="FS error"};FS.ErrnoError.prototype=new Error;FS.ErrnoError.prototype.constructor=FS.ErrnoError;[44].forEach(code=>{FS.genericErrors[code]=new FS.ErrnoError(code);FS.genericErrors[code].stack=""})},staticInit:()=>{FS.ensureErrnoError();FS.nameTable=new Array(4096);FS.mount(MEMFS,{},"/");FS.createDefaultDirectories();FS.createDefaultDevices();FS.createSpecialDirectories();FS.filesystems={"MEMFS":MEMFS}},init:(input,output,error)=>{FS.init.initialized=true;FS.ensureErrnoError();Module["stdin"]=input||Module["stdin"];Module["stdout"]=output||Module["stdout"];Module["stderr"]=error||Module["stderr"];FS.createStandardStreams()},quit:()=>{FS.init.initialized=false;for(var i=0;i{var mode=0;if(canRead)mode|=292|73;if(canWrite)mode|=146;return mode},findObject:(path,dontResolveLastLink)=>{var ret=FS.analyzePath(path,dontResolveLastLink);if(!ret.exists){return null}return ret.object},analyzePath:(path,dontResolveLastLink)=>{try{var lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});path=lookup.path}catch(e){}var ret={isRoot:false,exists:false,error:0,name:null,path:null,object:null,parentExists:false,parentPath:null,parentObject:null};try{var lookup=FS.lookupPath(path,{parent:true});ret.parentExists=true;ret.parentPath=lookup.path;ret.parentObject=lookup.node;ret.name=PATH.basename(path);lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});ret.exists=true;ret.path=lookup.path;ret.object=lookup.node;ret.name=lookup.node.name;ret.isRoot=lookup.path==="/"}catch(e){ret.error=e.errno}return ret},createPath:(parent,path,canRead,canWrite)=>{parent=typeof parent=="string"?parent:FS.getPath(parent);var parts=path.split("/").reverse();while(parts.length){var part=parts.pop();if(!part)continue;var current=PATH.join2(parent,part);try{FS.mkdir(current)}catch(e){}parent=current}return current},createFile:(parent,name,properties,canRead,canWrite)=>{var path=PATH.join2(typeof parent=="string"?parent:FS.getPath(parent),name);var mode=FS.getMode(canRead,canWrite);return FS.create(path,mode)},createDataFile:(parent,name,data,canRead,canWrite,canOwn)=>{var path=name;if(parent){parent=typeof parent=="string"?parent:FS.getPath(parent);path=name?PATH.join2(parent,name):parent}var mode=FS.getMode(canRead,canWrite);var node=FS.create(path,mode);if(data){if(typeof data=="string"){var arr=new Array(data.length);for(var i=0,len=data.length;i{var path=PATH.join2(typeof parent=="string"?parent:FS.getPath(parent),name);var mode=FS.getMode(!!input,!!output);if(!FS.createDevice.major)FS.createDevice.major=64;var dev=FS.makedev(FS.createDevice.major++,0);FS.registerDevice(dev,{open:stream=>{stream.seekable=false},close:stream=>{if(output&&output.buffer&&output.buffer.length){output(10)}},read:(stream,buffer,offset,length,pos)=>{var bytesRead=0;for(var i=0;i{for(var i=0;i{if(obj.isDevice||obj.isFolder||obj.link||obj.contents)return true;if(typeof XMLHttpRequest!="undefined"){throw new Error("Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread.")}else if(read_){try{obj.contents=intArrayFromString(read_(obj.url),true);obj.usedBytes=obj.contents.length}catch(e){throw new FS.ErrnoError(29)}}else{throw new Error("Cannot load without read() or XMLHttpRequest.")}},createLazyFile:(parent,name,url,canRead,canWrite)=>{function LazyUint8Array(){this.lengthKnown=false;this.chunks=[]}LazyUint8Array.prototype.get=function LazyUint8Array_get(idx){if(idx>this.length-1||idx<0){return undefined}var chunkOffset=idx%this.chunkSize;var chunkNum=idx/this.chunkSize|0;return this.getter(chunkNum)[chunkOffset]};LazyUint8Array.prototype.setDataGetter=function LazyUint8Array_setDataGetter(getter){this.getter=getter};LazyUint8Array.prototype.cacheLength=function LazyUint8Array_cacheLength(){var xhr=new XMLHttpRequest;xhr.open("HEAD",url,false);xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);var datalength=Number(xhr.getResponseHeader("Content-length"));var header;var hasByteServing=(header=xhr.getResponseHeader("Accept-Ranges"))&&header==="bytes";var usesGzip=(header=xhr.getResponseHeader("Content-Encoding"))&&header==="gzip";var chunkSize=1024*1024;if(!hasByteServing)chunkSize=datalength;var doXHR=(from,to)=>{if(from>to)throw new Error("invalid range ("+from+", "+to+") or no bytes requested!");if(to>datalength-1)throw new Error("only "+datalength+" bytes available! programmer error!");var xhr=new XMLHttpRequest;xhr.open("GET",url,false);if(datalength!==chunkSize)xhr.setRequestHeader("Range","bytes="+from+"-"+to);xhr.responseType="arraybuffer";if(xhr.overrideMimeType){xhr.overrideMimeType("text/plain; charset=x-user-defined")}xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);if(xhr.response!==undefined){return new Uint8Array(xhr.response||[])}return intArrayFromString(xhr.responseText||"",true)};var lazyArray=this;lazyArray.setDataGetter(chunkNum=>{var start=chunkNum*chunkSize;var end=(chunkNum+1)*chunkSize-1;end=Math.min(end,datalength-1);if(typeof lazyArray.chunks[chunkNum]=="undefined"){lazyArray.chunks[chunkNum]=doXHR(start,end)}if(typeof lazyArray.chunks[chunkNum]=="undefined")throw new Error("doXHR failed!");return lazyArray.chunks[chunkNum]});if(usesGzip||!datalength){chunkSize=datalength=1;datalength=this.getter(0).length;chunkSize=datalength;out("LazyFiles on gzip forces download of the whole file when length is accessed")}this._length=datalength;this._chunkSize=chunkSize;this.lengthKnown=true};if(typeof XMLHttpRequest!="undefined"){if(!ENVIRONMENT_IS_WORKER)throw"Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc";var lazyArray=new LazyUint8Array;Object.defineProperties(lazyArray,{length:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._length}},chunkSize:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._chunkSize}}});var properties={isDevice:false,contents:lazyArray}}else{var properties={isDevice:false,url:url}}var node=FS.createFile(parent,name,properties,canRead,canWrite);if(properties.contents){node.contents=properties.contents}else if(properties.url){node.contents=null;node.url=properties.url}Object.defineProperties(node,{usedBytes:{get:function(){return this.contents.length}}});var stream_ops={};var keys=Object.keys(node.stream_ops);keys.forEach(key=>{var fn=node.stream_ops[key];stream_ops[key]=function forceLoadLazyFile(){FS.forceLoadFile(node);return fn.apply(null,arguments)}});function writeChunks(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=contents.length)return 0;var size=Math.min(contents.length-position,length);if(contents.slice){for(var i=0;i{FS.forceLoadFile(node);return writeChunks(stream,buffer,offset,length,position)};stream_ops.mmap=(stream,length,position,prot,flags)=>{FS.forceLoadFile(node);var ptr=mmapAlloc(length);if(!ptr){throw new FS.ErrnoError(48)}writeChunks(stream,HEAP8,ptr,length,position);return{ptr:ptr,allocated:true}};node.stream_ops=stream_ops;return node},createPreloadedFile:(parent,name,url,canRead,canWrite,onload,onerror,dontCreateFile,canOwn,preFinish)=>{var fullname=name?PATH_FS.resolve(PATH.join2(parent,name)):parent;var dep=getUniqueRunDependency("cp "+fullname);function processData(byteArray){function finish(byteArray){if(preFinish)preFinish();if(!dontCreateFile){FS.createDataFile(parent,name,byteArray,canRead,canWrite,canOwn)}if(onload)onload();removeRunDependency(dep)}if(Browser.handledByPreloadPlugin(byteArray,fullname,finish,()=>{if(onerror)onerror();removeRunDependency(dep)})){return}finish(byteArray)}addRunDependency(dep);if(typeof url=="string"){asyncLoad(url,byteArray=>processData(byteArray),onerror)}else{processData(url)}},indexedDB:()=>{return window.indexedDB||window.mozIndexedDB||window.webkitIndexedDB||window.msIndexedDB},DB_NAME:()=>{return"EM_FS_"+window.location.pathname},DB_VERSION:20,DB_STORE_NAME:"FILE_DATA",saveFilesToDB:(paths,onload,onerror)=>{onload=onload||(()=>{});onerror=onerror||(()=>{});var indexedDB=FS.indexedDB();try{var openRequest=indexedDB.open(FS.DB_NAME(),FS.DB_VERSION)}catch(e){return onerror(e)}openRequest.onupgradeneeded=()=>{out("creating db");var db=openRequest.result;db.createObjectStore(FS.DB_STORE_NAME)};openRequest.onsuccess=()=>{var db=openRequest.result;var transaction=db.transaction([FS.DB_STORE_NAME],"readwrite");var files=transaction.objectStore(FS.DB_STORE_NAME);var ok=0,fail=0,total=paths.length;function finish(){if(fail==0)onload();else onerror()}paths.forEach(path=>{var putRequest=files.put(FS.analyzePath(path).object.contents,path);putRequest.onsuccess=()=>{ok++;if(ok+fail==total)finish()};putRequest.onerror=()=>{fail++;if(ok+fail==total)finish()}});transaction.onerror=onerror};openRequest.onerror=onerror},loadFilesFromDB:(paths,onload,onerror)=>{onload=onload||(()=>{});onerror=onerror||(()=>{});var indexedDB=FS.indexedDB();try{var openRequest=indexedDB.open(FS.DB_NAME(),FS.DB_VERSION)}catch(e){return onerror(e)}openRequest.onupgradeneeded=onerror;openRequest.onsuccess=()=>{var db=openRequest.result;try{var transaction=db.transaction([FS.DB_STORE_NAME],"readonly")}catch(e){onerror(e);return}var files=transaction.objectStore(FS.DB_STORE_NAME);var ok=0,fail=0,total=paths.length;function finish(){if(fail==0)onload();else onerror()}paths.forEach(path=>{var getRequest=files.get(path);getRequest.onsuccess=()=>{if(FS.analyzePath(path).exists){FS.unlink(path)}FS.createDataFile(PATH.dirname(path),PATH.basename(path),getRequest.result,true,true,true);ok++;if(ok+fail==total)finish()};getRequest.onerror=()=>{fail++;if(ok+fail==total)finish()}});transaction.onerror=onerror};openRequest.onerror=onerror}};var SOCKFS={mount:function(mount){Module["websocket"]=Module["websocket"]&&"object"===typeof Module["websocket"]?Module["websocket"]:{};Module["websocket"]._callbacks={};Module["websocket"]["on"]=function(event,callback){if("function"===typeof callback){this._callbacks[event]=callback}return this};Module["websocket"].emit=function(event,param){if("function"===typeof this._callbacks[event]){this._callbacks[event].call(this,param)}};return FS.createNode(null,"/",16384|511,0)},createSocket:function(family,type,protocol){type&=~526336;var streaming=type==1;if(streaming&&protocol&&protocol!=6){throw new FS.ErrnoError(66)}var sock={family:family,type:type,protocol:protocol,server:null,error:null,peers:{},pending:[],recv_queue:[],sock_ops:SOCKFS.websocket_sock_ops};var name=SOCKFS.nextname();var node=FS.createNode(SOCKFS.root,name,49152,0);node.sock=sock;var stream=FS.createStream({path:name,node:node,flags:2,seekable:false,stream_ops:SOCKFS.stream_ops});sock.stream=stream;return sock},getSocket:function(fd){var stream=FS.getStream(fd);if(!stream||!FS.isSocket(stream.node.mode)){return null}return stream.node.sock},stream_ops:{poll:function(stream){var sock=stream.node.sock;return sock.sock_ops.poll(sock)},ioctl:function(stream,request,varargs){var sock=stream.node.sock;return sock.sock_ops.ioctl(sock,request,varargs)},read:function(stream,buffer,offset,length,position){var sock=stream.node.sock;var msg=sock.sock_ops.recvmsg(sock,length);if(!msg){return 0}buffer.set(msg.buffer,offset);return msg.buffer.length},write:function(stream,buffer,offset,length,position){var sock=stream.node.sock;return sock.sock_ops.sendmsg(sock,buffer,offset,length)},close:function(stream){var sock=stream.node.sock;sock.sock_ops.close(sock)}},nextname:function(){if(!SOCKFS.nextname.current){SOCKFS.nextname.current=0}return"socket["+SOCKFS.nextname.current+++"]"},websocket_sock_ops:{createPeer:function(sock,addr,port){var ws;if(typeof addr=="object"){ws=addr;addr=null;port=null}if(ws){if(ws._socket){addr=ws._socket.remoteAddress;port=ws._socket.remotePort}else{var result=/ws[s]?:\/\/([^:]+):(\d+)/.exec(ws.url);if(!result){throw new Error("WebSocket URL must be in the format ws(s)://address:port")}addr=result[1];port=parseInt(result[2],10)}}else{try{var runtimeConfig=Module["websocket"]&&"object"===typeof Module["websocket"];var url="ws:#".replace("#","//");if(runtimeConfig){if("string"===typeof Module["websocket"]["url"]){url=Module["websocket"]["url"]}}if(url==="ws://"||url==="wss://"){var parts=addr.split("/");url=url+parts[0]+":"+port+"/"+parts.slice(1).join("/")}var subProtocols="binary";if(runtimeConfig){if("string"===typeof Module["websocket"]["subprotocol"]){subProtocols=Module["websocket"]["subprotocol"]}}var opts=undefined;if(subProtocols!=="null"){subProtocols=subProtocols.replace(/^ +| +$/g,"").split(/ *, */);opts=subProtocols}if(runtimeConfig&&null===Module["websocket"]["subprotocol"]){subProtocols="null";opts=undefined}var WebSocketConstructor;if(ENVIRONMENT_IS_NODE){WebSocketConstructor=require("ws")}else{WebSocketConstructor=WebSocket}ws=new WebSocketConstructor(url,opts);ws.binaryType="arraybuffer"}catch(e){throw new FS.ErrnoError(23)}}var peer={addr:addr,port:port,socket:ws,dgram_send_queue:[]};SOCKFS.websocket_sock_ops.addPeer(sock,peer);SOCKFS.websocket_sock_ops.handlePeerEvents(sock,peer);if(sock.type===2&&typeof sock.sport!="undefined"){peer.dgram_send_queue.push(new Uint8Array([255,255,255,255,"p".charCodeAt(0),"o".charCodeAt(0),"r".charCodeAt(0),"t".charCodeAt(0),(sock.sport&65280)>>8,sock.sport&255]))}return peer},getPeer:function(sock,addr,port){return sock.peers[addr+":"+port]},addPeer:function(sock,peer){sock.peers[peer.addr+":"+peer.port]=peer},removePeer:function(sock,peer){delete sock.peers[peer.addr+":"+peer.port]},handlePeerEvents:function(sock,peer){var first=true;var handleOpen=function(){Module["websocket"].emit("open",sock.stream.fd);try{var queued=peer.dgram_send_queue.shift();while(queued){peer.socket.send(queued);queued=peer.dgram_send_queue.shift()}}catch(e){peer.socket.close()}};function handleMessage(data){if(typeof data=="string"){var encoder=new TextEncoder;data=encoder.encode(data)}else{assert(data.byteLength!==undefined);if(data.byteLength==0){return}data=new Uint8Array(data)}var wasfirst=first;first=false;if(wasfirst&&data.length===10&&data[0]===255&&data[1]===255&&data[2]===255&&data[3]===255&&data[4]==="p".charCodeAt(0)&&data[5]==="o".charCodeAt(0)&&data[6]==="r".charCodeAt(0)&&data[7]==="t".charCodeAt(0)){var newport=data[8]<<8|data[9];SOCKFS.websocket_sock_ops.removePeer(sock,peer);peer.port=newport;SOCKFS.websocket_sock_ops.addPeer(sock,peer);return}sock.recv_queue.push({addr:peer.addr,port:peer.port,data:data});Module["websocket"].emit("message",sock.stream.fd)}if(ENVIRONMENT_IS_NODE){peer.socket.on("open",handleOpen);peer.socket.on("message",function(data,isBinary){if(!isBinary){return}handleMessage(new Uint8Array(data).buffer)});peer.socket.on("close",function(){Module["websocket"].emit("close",sock.stream.fd)});peer.socket.on("error",function(error){sock.error=14;Module["websocket"].emit("error",[sock.stream.fd,sock.error,"ECONNREFUSED: Connection refused"])})}else{peer.socket.onopen=handleOpen;peer.socket.onclose=function(){Module["websocket"].emit("close",sock.stream.fd)};peer.socket.onmessage=function peer_socket_onmessage(event){handleMessage(event.data)};peer.socket.onerror=function(error){sock.error=14;Module["websocket"].emit("error",[sock.stream.fd,sock.error,"ECONNREFUSED: Connection refused"])}}},poll:function(sock){if(sock.type===1&&sock.server){return sock.pending.length?64|1:0}var mask=0;var dest=sock.type===1?SOCKFS.websocket_sock_ops.getPeer(sock,sock.daddr,sock.dport):null;if(sock.recv_queue.length||!dest||dest&&dest.socket.readyState===dest.socket.CLOSING||dest&&dest.socket.readyState===dest.socket.CLOSED){mask|=64|1}if(!dest||dest&&dest.socket.readyState===dest.socket.OPEN){mask|=4}if(dest&&dest.socket.readyState===dest.socket.CLOSING||dest&&dest.socket.readyState===dest.socket.CLOSED){mask|=16}return mask},ioctl:function(sock,request,arg){switch(request){case 21531:var bytes=0;if(sock.recv_queue.length){bytes=sock.recv_queue[0].data.length}HEAP32[arg>>2]=bytes;return 0;default:return 28}},close:function(sock){if(sock.server){try{sock.server.close()}catch(e){}sock.server=null}var peers=Object.keys(sock.peers);for(var i=0;i>2]=value;return value}function inetNtop4(addr){return(addr&255)+"."+(addr>>8&255)+"."+(addr>>16&255)+"."+(addr>>24&255)}function inetNtop6(ints){var str="";var word=0;var longest=0;var lastzero=0;var zstart=0;var len=0;var i=0;var parts=[ints[0]&65535,ints[0]>>16,ints[1]&65535,ints[1]>>16,ints[2]&65535,ints[2]>>16,ints[3]&65535,ints[3]>>16];var hasipv4=true;var v4part="";for(i=0;i<5;i++){if(parts[i]!==0){hasipv4=false;break}}if(hasipv4){v4part=inetNtop4(parts[6]|parts[7]<<16);if(parts[5]===-1){str="::ffff:";str+=v4part;return str}if(parts[5]===0){str="::";if(v4part==="0.0.0.0")v4part="";if(v4part==="0.0.0.1")v4part="1";str+=v4part;return str}}for(word=0;word<8;word++){if(parts[word]===0){if(word-lastzero>1){len=0}lastzero=word;len++}if(len>longest){longest=len;zstart=word-longest+1}}for(word=0;word<8;word++){if(longest>1){if(parts[word]===0&&word>=zstart&&word>1];var port=_ntohs(HEAPU16[sa+2>>1]);var addr;switch(family){case 2:if(salen!==16){return{errno:28}}addr=HEAP32[sa+4>>2];addr=inetNtop4(addr);break;case 10:if(salen!==28){return{errno:28}}addr=[HEAP32[sa+8>>2],HEAP32[sa+12>>2],HEAP32[sa+16>>2],HEAP32[sa+20>>2]];addr=inetNtop6(addr);break;default:return{errno:5}}return{family:family,addr:addr,port:port}}function inetPton4(str){var b=str.split(".");for(var i=0;i<4;i++){var tmp=Number(b[i]);if(isNaN(tmp))return null;b[i]=tmp}return(b[0]|b[1]<<8|b[2]<<16|b[3]<<24)>>>0}function jstoi_q(str){return parseInt(str)}function inetPton6(str){var words;var w,offset,z;var valid6regx=/^((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\3)::|:\b|$))|(?!\2\3)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})$/i;var parts=[];if(!valid6regx.test(str)){return null}if(str==="::"){return[0,0,0,0,0,0,0,0]}if(str.startsWith("::")){str=str.replace("::","Z:")}else{str=str.replace("::",":Z:")}if(str.indexOf(".")>0){str=str.replace(new RegExp("[.]","g"),":");words=str.split(":");words[words.length-4]=jstoi_q(words[words.length-4])+jstoi_q(words[words.length-3])*256;words[words.length-3]=jstoi_q(words[words.length-2])+jstoi_q(words[words.length-1])*256;words=words.slice(0,words.length-2)}else{words=str.split(":")}offset=0;z=0;for(w=0;w>2]=stat.dev;HEAP32[buf+8>>2]=stat.ino;HEAP32[buf+12>>2]=stat.mode;HEAPU32[buf+16>>2]=stat.nlink;HEAP32[buf+20>>2]=stat.uid;HEAP32[buf+24>>2]=stat.gid;HEAP32[buf+28>>2]=stat.rdev;tempI64=[stat.size>>>0,(tempDouble=stat.size,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+40>>2]=tempI64[0],HEAP32[buf+44>>2]=tempI64[1];HEAP32[buf+48>>2]=4096;HEAP32[buf+52>>2]=stat.blocks;var atime=stat.atime.getTime();var mtime=stat.mtime.getTime();var ctime=stat.ctime.getTime();tempI64=[Math.floor(atime/1e3)>>>0,(tempDouble=Math.floor(atime/1e3),+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+56>>2]=tempI64[0],HEAP32[buf+60>>2]=tempI64[1];HEAPU32[buf+64>>2]=atime%1e3*1e3;tempI64=[Math.floor(mtime/1e3)>>>0,(tempDouble=Math.floor(mtime/1e3),+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+72>>2]=tempI64[0],HEAP32[buf+76>>2]=tempI64[1];HEAPU32[buf+80>>2]=mtime%1e3*1e3;tempI64=[Math.floor(ctime/1e3)>>>0,(tempDouble=Math.floor(ctime/1e3),+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+88>>2]=tempI64[0],HEAP32[buf+92>>2]=tempI64[1];HEAPU32[buf+96>>2]=ctime%1e3*1e3;tempI64=[stat.ino>>>0,(tempDouble=stat.ino,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+104>>2]=tempI64[0],HEAP32[buf+108>>2]=tempI64[1];return 0},doMsync:function(addr,stream,len,flags,offset){if(!FS.isFile(stream.node.mode)){throw new FS.ErrnoError(43)}if(flags&2){return 0}var buffer=HEAPU8.slice(addr,addr+len);FS.msync(stream,buffer,offset,len,flags)},varargs:undefined,get:function(){SYSCALLS.varargs+=4;var ret=HEAP32[SYSCALLS.varargs-4>>2];return ret},getStr:function(ptr){var ret=UTF8ToString(ptr);return ret},getStreamFromFD:function(fd){var stream=FS.getStream(fd);if(!stream)throw new FS.ErrnoError(8);return stream}};function ___syscall_connect(fd,addr,addrlen){try{var sock=getSocketFromFD(fd);var info=getSocketAddress(addr,addrlen);sock.sock_ops.connect(sock,info.addr,info.port);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_faccessat(dirfd,path,amode,flags){try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);if(amode&~7){return-28}var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;if(!node){return-44}var perms="";if(amode&4)perms+="r";if(amode&2)perms+="w";if(amode&1)perms+="x";if(perms&&FS.nodePermissions(node,perms)){return-2}return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_fcntl64(fd,cmd,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(cmd){case 0:{var arg=SYSCALLS.get();if(arg<0){return-28}var newStream;newStream=FS.createStream(stream,arg);return newStream.fd}case 1:case 2:return 0;case 3:return stream.flags;case 4:{var arg=SYSCALLS.get();stream.flags|=arg;return 0}case 5:{var arg=SYSCALLS.get();var offset=0;HEAP16[arg+offset>>1]=2;return 0}case 6:case 7:return 0;case 16:case 8:return-28;case 9:setErrNo(28);return-1;default:{return-28}}}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_fstat64(fd,buf){try{var stream=SYSCALLS.getStreamFromFD(fd);return SYSCALLS.doStat(FS.stat,stream.path,buf)}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function convertI32PairToI53Checked(lo,hi){return hi+2097152>>>0<4194305-!!lo?(lo>>>0)+hi*4294967296:NaN}function ___syscall_ftruncate64(fd,length_low,length_high){try{var length=convertI32PairToI53Checked(length_low,length_high);if(isNaN(length))return-61;FS.ftruncate(fd,length);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_ioctl(fd,op,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(op){case 21509:case 21505:{if(!stream.tty)return-59;return 0}case 21510:case 21511:case 21512:case 21506:case 21507:case 21508:{if(!stream.tty)return-59;return 0}case 21519:{if(!stream.tty)return-59;var argp=SYSCALLS.get();HEAP32[argp>>2]=0;return 0}case 21520:{if(!stream.tty)return-59;return-28}case 21531:{var argp=SYSCALLS.get();return FS.ioctl(stream,op,argp)}case 21523:{if(!stream.tty)return-59;return 0}case 21524:{if(!stream.tty)return-59;return 0}default:return-28}}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_lstat64(path,buf){try{path=SYSCALLS.getStr(path);return SYSCALLS.doStat(FS.lstat,path,buf)}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_newfstatat(dirfd,path,buf,flags){try{path=SYSCALLS.getStr(path);var nofollow=flags&256;var allowEmpty=flags&4096;flags=flags&~6400;path=SYSCALLS.calculateAt(dirfd,path,allowEmpty);return SYSCALLS.doStat(nofollow?FS.lstat:FS.stat,path,buf)}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_openat(dirfd,path,flags,varargs){SYSCALLS.varargs=varargs;try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);var mode=varargs?SYSCALLS.get():0;return FS.open(path,flags,mode).fd}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_socket(domain,type,protocol){try{var sock=SOCKFS.createSocket(domain,type,protocol);return sock.stream.fd}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_stat64(path,buf){try{path=SYSCALLS.getStr(path);return SYSCALLS.doStat(FS.stat,path,buf)}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}var tupleRegistrations={};function runDestructors(destructors){while(destructors.length){var ptr=destructors.pop();var del=destructors.pop();del(ptr)}}function simpleReadValueFromPointer(pointer){return this["fromWireType"](HEAP32[pointer>>2])}var awaitingDependencies={};var registeredTypes={};var typeDependencies={};var char_0=48;var char_9=57;function makeLegalFunctionName(name){if(undefined===name){return"_unknown"}name=name.replace(/[^a-zA-Z0-9_]/g,"$");var f=name.charCodeAt(0);if(f>=char_0&&f<=char_9){return"_"+name}return name}function createNamedFunction(name,body){name=makeLegalFunctionName(name);return new Function("body","return function "+name+"() {\n"+' "use strict";'+" return body.apply(this, arguments);\n"+"};\n")(body)}function extendError(baseErrorType,errorName){var errorClass=createNamedFunction(errorName,function(message){this.name=errorName;this.message=message;var stack=new Error(message).stack;if(stack!==undefined){this.stack=this.toString()+"\n"+stack.replace(/^Error(:[^\n]*)?\n/,"")}});errorClass.prototype=Object.create(baseErrorType.prototype);errorClass.prototype.constructor=errorClass;errorClass.prototype.toString=function(){if(this.message===undefined){return this.name}else{return this.name+": "+this.message}};return errorClass}var InternalError=undefined;function throwInternalError(message){throw new InternalError(message)}function whenDependentTypesAreResolved(myTypes,dependentTypes,getTypeConverters){myTypes.forEach(function(type){typeDependencies[type]=dependentTypes});function onComplete(typeConverters){var myTypeConverters=getTypeConverters(typeConverters);if(myTypeConverters.length!==myTypes.length){throwInternalError("Mismatched type converter count")}for(var i=0;i{if(registeredTypes.hasOwnProperty(dt)){typeConverters[i]=registeredTypes[dt]}else{unregisteredTypes.push(dt);if(!awaitingDependencies.hasOwnProperty(dt)){awaitingDependencies[dt]=[]}awaitingDependencies[dt].push(()=>{typeConverters[i]=registeredTypes[dt];++registered;if(registered===unregisteredTypes.length){onComplete(typeConverters)}})}});if(0===unregisteredTypes.length){onComplete(typeConverters)}}function __embind_finalize_value_array(rawTupleType){var reg=tupleRegistrations[rawTupleType];delete tupleRegistrations[rawTupleType];var elements=reg.elements;var elementsLength=elements.length;var elementTypes=elements.map(function(elt){return elt.getterReturnType}).concat(elements.map(function(elt){return elt.setterArgumentType}));var rawConstructor=reg.rawConstructor;var rawDestructor=reg.rawDestructor;whenDependentTypesAreResolved([rawTupleType],elementTypes,function(elementTypes){elements.forEach((elt,i)=>{var getterReturnType=elementTypes[i];var getter=elt.getter;var getterContext=elt.getterContext;var setterArgumentType=elementTypes[i+elementsLength];var setter=elt.setter;var setterContext=elt.setterContext;elt.read=ptr=>{return getterReturnType["fromWireType"](getter(getterContext,ptr))};elt.write=(ptr,o)=>{var destructors=[];setter(setterContext,ptr,setterArgumentType["toWireType"](destructors,o));runDestructors(destructors)}});return[{name:reg.name,"fromWireType":function(ptr){var rv=new Array(elementsLength);for(var i=0;ifield.getterReturnType).concat(fieldRecords.map(field=>field.setterArgumentType));whenDependentTypesAreResolved([structType],fieldTypes,fieldTypes=>{var fields={};fieldRecords.forEach((field,i)=>{var fieldName=field.fieldName;var getterReturnType=fieldTypes[i];var getter=field.getter;var getterContext=field.getterContext;var setterArgumentType=fieldTypes[i+fieldRecords.length];var setter=field.setter;var setterContext=field.setterContext;fields[fieldName]={read:ptr=>{return getterReturnType["fromWireType"](getter(getterContext,ptr))},write:(ptr,o)=>{var destructors=[];setter(setterContext,ptr,setterArgumentType["toWireType"](destructors,o));runDestructors(destructors)}}});return[{name:reg.name,"fromWireType":function(ptr){var rv={};for(var i in fields){rv[i]=fields[i].read(ptr)}rawDestructor(ptr);return rv},"toWireType":function(destructors,o){for(var fieldName in fields){if(!(fieldName in o)){throw new TypeError('Missing field: "'+fieldName+'"')}}var ptr=rawConstructor();for(fieldName in fields){fields[fieldName].write(ptr,o[fieldName])}if(destructors!==null){destructors.push(rawDestructor,ptr)}return ptr},"argPackAdvance":8,"readValueFromPointer":simpleReadValueFromPointer,destructorFunction:rawDestructor}]})}function __embind_register_bigint(primitiveType,name,size,minRange,maxRange){}function getShiftFromSize(size){switch(size){case 1:return 0;case 2:return 1;case 4:return 2;case 8:return 3;default:throw new TypeError("Unknown type size: "+size)}}function embind_init_charCodes(){var codes=new Array(256);for(var i=0;i<256;++i){codes[i]=String.fromCharCode(i)}embind_charCodes=codes}var embind_charCodes=undefined;function readLatin1String(ptr){var ret="";var c=ptr;while(HEAPU8[c]){ret+=embind_charCodes[HEAPU8[c++]]}return ret}var BindingError=undefined;function throwBindingError(message){throw new BindingError(message)}function registerType(rawType,registeredInstance,options={}){if(!("argPackAdvance"in registeredInstance)){throw new TypeError("registerType registeredInstance requires argPackAdvance")}var name=registeredInstance.name;if(!rawType){throwBindingError('type "'+name+'" must have a positive integer typeid pointer')}if(registeredTypes.hasOwnProperty(rawType)){if(options.ignoreDuplicateRegistrations){return}else{throwBindingError("Cannot register type '"+name+"' twice")}}registeredTypes[rawType]=registeredInstance;delete typeDependencies[rawType];if(awaitingDependencies.hasOwnProperty(rawType)){var callbacks=awaitingDependencies[rawType];delete awaitingDependencies[rawType];callbacks.forEach(cb=>cb())}}function __embind_register_bool(rawType,name,size,trueValue,falseValue){var shift=getShiftFromSize(size);name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":function(wt){return!!wt},"toWireType":function(destructors,o){return o?trueValue:falseValue},"argPackAdvance":8,"readValueFromPointer":function(pointer){var heap;if(size===1){heap=HEAP8}else if(size===2){heap=HEAP16}else if(size===4){heap=HEAP32}else{throw new TypeError("Unknown boolean type size: "+name)}return this["fromWireType"](heap[pointer>>shift])},destructorFunction:null})}function ClassHandle_isAliasOf(other){if(!(this instanceof ClassHandle)){return false}if(!(other instanceof ClassHandle)){return false}var leftClass=this.$$.ptrType.registeredClass;var left=this.$$.ptr;var rightClass=other.$$.ptrType.registeredClass;var right=other.$$.ptr;while(leftClass.baseClass){left=leftClass.upcast(left);leftClass=leftClass.baseClass}while(rightClass.baseClass){right=rightClass.upcast(right);rightClass=rightClass.baseClass}return leftClass===rightClass&&left===right}function shallowCopyInternalPointer(o){return{count:o.count,deleteScheduled:o.deleteScheduled,preservePointerOnDelete:o.preservePointerOnDelete,ptr:o.ptr,ptrType:o.ptrType,smartPtr:o.smartPtr,smartPtrType:o.smartPtrType}}function throwInstanceAlreadyDeleted(obj){function getInstanceTypeName(handle){return handle.$$.ptrType.registeredClass.name}throwBindingError(getInstanceTypeName(obj)+" instance already deleted")}var finalizationRegistry=false;function detachFinalizer(handle){}function runDestructor($$){if($$.smartPtr){$$.smartPtrType.rawDestructor($$.smartPtr)}else{$$.ptrType.registeredClass.rawDestructor($$.ptr)}}function releaseClassHandle($$){$$.count.value-=1;var toDelete=0===$$.count.value;if(toDelete){runDestructor($$)}}function downcastPointer(ptr,ptrClass,desiredClass){if(ptrClass===desiredClass){return ptr}if(undefined===desiredClass.baseClass){return null}var rv=downcastPointer(ptr,ptrClass,desiredClass.baseClass);if(rv===null){return null}return desiredClass.downcast(rv)}var registeredPointers={};function getInheritedInstanceCount(){return Object.keys(registeredInstances).length}function getLiveInheritedInstances(){var rv=[];for(var k in registeredInstances){if(registeredInstances.hasOwnProperty(k)){rv.push(registeredInstances[k])}}return rv}var deletionQueue=[];function flushPendingDeletes(){while(deletionQueue.length){var obj=deletionQueue.pop();obj.$$.deleteScheduled=false;obj["delete"]()}}var delayFunction=undefined;function setDelayFunction(fn){delayFunction=fn;if(deletionQueue.length&&delayFunction){delayFunction(flushPendingDeletes)}}function init_embind(){Module["getInheritedInstanceCount"]=getInheritedInstanceCount;Module["getLiveInheritedInstances"]=getLiveInheritedInstances;Module["flushPendingDeletes"]=flushPendingDeletes;Module["setDelayFunction"]=setDelayFunction}var registeredInstances={};function getBasestPointer(class_,ptr){if(ptr===undefined){throwBindingError("ptr should not be undefined")}while(class_.baseClass){ptr=class_.upcast(ptr);class_=class_.baseClass}return ptr}function getInheritedInstance(class_,ptr){ptr=getBasestPointer(class_,ptr);return registeredInstances[ptr]}function makeClassHandle(prototype,record){if(!record.ptrType||!record.ptr){throwInternalError("makeClassHandle requires ptr and ptrType")}var hasSmartPtrType=!!record.smartPtrType;var hasSmartPtr=!!record.smartPtr;if(hasSmartPtrType!==hasSmartPtr){throwInternalError("Both smartPtrType and smartPtr must be specified")}record.count={value:1};return attachFinalizer(Object.create(prototype,{$$:{value:record}}))}function RegisteredPointer_fromWireType(ptr){var rawPointer=this.getPointee(ptr);if(!rawPointer){this.destructor(ptr);return null}var registeredInstance=getInheritedInstance(this.registeredClass,rawPointer);if(undefined!==registeredInstance){if(0===registeredInstance.$$.count.value){registeredInstance.$$.ptr=rawPointer;registeredInstance.$$.smartPtr=ptr;return registeredInstance["clone"]()}else{var rv=registeredInstance["clone"]();this.destructor(ptr);return rv}}function makeDefaultHandle(){if(this.isSmartPointer){return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this.pointeeType,ptr:rawPointer,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this,ptr:ptr})}}var actualType=this.registeredClass.getActualType(rawPointer);var registeredPointerRecord=registeredPointers[actualType];if(!registeredPointerRecord){return makeDefaultHandle.call(this)}var toType;if(this.isConst){toType=registeredPointerRecord.constPointerType}else{toType=registeredPointerRecord.pointerType}var dp=downcastPointer(rawPointer,this.registeredClass,toType.registeredClass);if(dp===null){return makeDefaultHandle.call(this)}if(this.isSmartPointer){return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp})}}function attachFinalizer(handle){if("undefined"===typeof FinalizationRegistry){attachFinalizer=handle=>handle;return handle}finalizationRegistry=new FinalizationRegistry(info=>{releaseClassHandle(info.$$)});attachFinalizer=handle=>{var $$=handle.$$;var hasSmartPtr=!!$$.smartPtr;if(hasSmartPtr){var info={$$:$$};finalizationRegistry.register(handle,info,handle)}return handle};detachFinalizer=handle=>finalizationRegistry.unregister(handle);return attachFinalizer(handle)}function ClassHandle_clone(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.preservePointerOnDelete){this.$$.count.value+=1;return this}else{var clone=attachFinalizer(Object.create(Object.getPrototypeOf(this),{$$:{value:shallowCopyInternalPointer(this.$$)}}));clone.$$.count.value+=1;clone.$$.deleteScheduled=false;return clone}}function ClassHandle_delete(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}detachFinalizer(this);releaseClassHandle(this.$$);if(!this.$$.preservePointerOnDelete){this.$$.smartPtr=undefined;this.$$.ptr=undefined}}function ClassHandle_isDeleted(){return!this.$$.ptr}function ClassHandle_deleteLater(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}deletionQueue.push(this);if(deletionQueue.length===1&&delayFunction){delayFunction(flushPendingDeletes)}this.$$.deleteScheduled=true;return this}function init_ClassHandle(){ClassHandle.prototype["isAliasOf"]=ClassHandle_isAliasOf;ClassHandle.prototype["clone"]=ClassHandle_clone;ClassHandle.prototype["delete"]=ClassHandle_delete;ClassHandle.prototype["isDeleted"]=ClassHandle_isDeleted;ClassHandle.prototype["deleteLater"]=ClassHandle_deleteLater}function ClassHandle(){}function ensureOverloadTable(proto,methodName,humanName){if(undefined===proto[methodName].overloadTable){var prevFunc=proto[methodName];proto[methodName]=function(){if(!proto[methodName].overloadTable.hasOwnProperty(arguments.length)){throwBindingError("Function '"+humanName+"' called with an invalid number of arguments ("+arguments.length+") - expects one of ("+proto[methodName].overloadTable+")!")}return proto[methodName].overloadTable[arguments.length].apply(this,arguments)};proto[methodName].overloadTable=[];proto[methodName].overloadTable[prevFunc.argCount]=prevFunc}}function exposePublicSymbol(name,value,numArguments){if(Module.hasOwnProperty(name)){if(undefined===numArguments||undefined!==Module[name].overloadTable&&undefined!==Module[name].overloadTable[numArguments]){throwBindingError("Cannot register public name '"+name+"' twice")}ensureOverloadTable(Module,name,name);if(Module.hasOwnProperty(numArguments)){throwBindingError("Cannot register multiple overloads of a function with the same number of arguments ("+numArguments+")!")}Module[name].overloadTable[numArguments]=value}else{Module[name]=value;if(undefined!==numArguments){Module[name].numArguments=numArguments}}}function RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast){this.name=name;this.constructor=constructor;this.instancePrototype=instancePrototype;this.rawDestructor=rawDestructor;this.baseClass=baseClass;this.getActualType=getActualType;this.upcast=upcast;this.downcast=downcast;this.pureVirtualFunctions=[]}function upcastPointer(ptr,ptrClass,desiredClass){while(ptrClass!==desiredClass){if(!ptrClass.upcast){throwBindingError("Expected null or instance of "+desiredClass.name+", got an instance of "+ptrClass.name)}ptr=ptrClass.upcast(ptr);ptrClass=ptrClass.baseClass}return ptr}function constNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError("null is not a valid "+this.name)}return 0}if(!handle.$$){throwBindingError('Cannot pass "'+embindRepr(handle)+'" as a '+this.name)}if(!handle.$$.ptr){throwBindingError("Cannot pass deleted object as a pointer of type "+this.name)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function genericPointerToWireType(destructors,handle){var ptr;if(handle===null){if(this.isReference){throwBindingError("null is not a valid "+this.name)}if(this.isSmartPointer){ptr=this.rawConstructor();if(destructors!==null){destructors.push(this.rawDestructor,ptr)}return ptr}else{return 0}}if(!handle.$$){throwBindingError('Cannot pass "'+embindRepr(handle)+'" as a '+this.name)}if(!handle.$$.ptr){throwBindingError("Cannot pass deleted object as a pointer of type "+this.name)}if(!this.isConst&&handle.$$.ptrType.isConst){throwBindingError("Cannot convert argument of type "+(handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name)+" to parameter type "+this.name)}var handleClass=handle.$$.ptrType.registeredClass;ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);if(this.isSmartPointer){if(undefined===handle.$$.smartPtr){throwBindingError("Passing raw pointer to smart pointer is illegal")}switch(this.sharingPolicy){case 0:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{throwBindingError("Cannot convert argument of type "+(handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name)+" to parameter type "+this.name)}break;case 1:ptr=handle.$$.smartPtr;break;case 2:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{var clonedHandle=handle["clone"]();ptr=this.rawShare(ptr,Emval.toHandle(function(){clonedHandle["delete"]()}));if(destructors!==null){destructors.push(this.rawDestructor,ptr)}}break;default:throwBindingError("Unsupporting sharing policy")}}return ptr}function nonConstNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError("null is not a valid "+this.name)}return 0}if(!handle.$$){throwBindingError('Cannot pass "'+embindRepr(handle)+'" as a '+this.name)}if(!handle.$$.ptr){throwBindingError("Cannot pass deleted object as a pointer of type "+this.name)}if(handle.$$.ptrType.isConst){throwBindingError("Cannot convert argument of type "+handle.$$.ptrType.name+" to parameter type "+this.name)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function RegisteredPointer_getPointee(ptr){if(this.rawGetPointee){ptr=this.rawGetPointee(ptr)}return ptr}function RegisteredPointer_destructor(ptr){if(this.rawDestructor){this.rawDestructor(ptr)}}function RegisteredPointer_deleteObject(handle){if(handle!==null){handle["delete"]()}}function init_RegisteredPointer(){RegisteredPointer.prototype.getPointee=RegisteredPointer_getPointee;RegisteredPointer.prototype.destructor=RegisteredPointer_destructor;RegisteredPointer.prototype["argPackAdvance"]=8;RegisteredPointer.prototype["readValueFromPointer"]=simpleReadValueFromPointer;RegisteredPointer.prototype["deleteObject"]=RegisteredPointer_deleteObject;RegisteredPointer.prototype["fromWireType"]=RegisteredPointer_fromWireType}function RegisteredPointer(name,registeredClass,isReference,isConst,isSmartPointer,pointeeType,sharingPolicy,rawGetPointee,rawConstructor,rawShare,rawDestructor){this.name=name;this.registeredClass=registeredClass;this.isReference=isReference;this.isConst=isConst;this.isSmartPointer=isSmartPointer;this.pointeeType=pointeeType;this.sharingPolicy=sharingPolicy;this.rawGetPointee=rawGetPointee;this.rawConstructor=rawConstructor;this.rawShare=rawShare;this.rawDestructor=rawDestructor;if(!isSmartPointer&®isteredClass.baseClass===undefined){if(isConst){this["toWireType"]=constNoSmartPtrRawPointerToWireType;this.destructorFunction=null}else{this["toWireType"]=nonConstNoSmartPtrRawPointerToWireType;this.destructorFunction=null}}else{this["toWireType"]=genericPointerToWireType}}function replacePublicSymbol(name,value,numArguments){if(!Module.hasOwnProperty(name)){throwInternalError("Replacing nonexistant public symbol")}if(undefined!==Module[name].overloadTable&&undefined!==numArguments){Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}}function dynCallLegacy(sig,ptr,args){var f=Module["dynCall_"+sig];return args&&args.length?f.apply(null,[ptr].concat(args)):f.call(null,ptr)}function getWasmTableEntry(funcPtr){return wasmTable.get(funcPtr)}function dynCall(sig,ptr,args){if(sig.includes("j")){return dynCallLegacy(sig,ptr,args)}var rtn=getWasmTableEntry(ptr).apply(null,args);return rtn}function getDynCaller(sig,ptr){var argCache=[];return function(){argCache.length=0;Object.assign(argCache,arguments);return dynCall(sig,ptr,argCache)}}function embind__requireFunction(signature,rawFunction){signature=readLatin1String(signature);function makeDynCaller(){if(signature.includes("j")){return getDynCaller(signature,rawFunction)}return getWasmTableEntry(rawFunction)}var fp=makeDynCaller();if(typeof fp!="function"){throwBindingError("unknown function pointer with signature "+signature+": "+rawFunction)}return fp}var UnboundTypeError=undefined;function getTypeName(type){var ptr=___getTypeName(type);var rv=readLatin1String(ptr);_free(ptr);return rv}function throwUnboundTypeError(message,types){var unboundTypes=[];var seen={};function visit(type){if(seen[type]){return}if(registeredTypes[type]){return}if(typeDependencies[type]){typeDependencies[type].forEach(visit);return}unboundTypes.push(type);seen[type]=true}types.forEach(visit);throw new UnboundTypeError(message+": "+unboundTypes.map(getTypeName).join([", "]))}function __embind_register_class(rawType,rawPointerType,rawConstPointerType,baseClassRawType,getActualTypeSignature,getActualType,upcastSignature,upcast,downcastSignature,downcast,name,destructorSignature,rawDestructor){name=readLatin1String(name);getActualType=embind__requireFunction(getActualTypeSignature,getActualType);if(upcast){upcast=embind__requireFunction(upcastSignature,upcast)}if(downcast){downcast=embind__requireFunction(downcastSignature,downcast)}rawDestructor=embind__requireFunction(destructorSignature,rawDestructor);var legalFunctionName=makeLegalFunctionName(name);exposePublicSymbol(legalFunctionName,function(){throwUnboundTypeError("Cannot construct "+name+" due to unbound types",[baseClassRawType])});whenDependentTypesAreResolved([rawType,rawPointerType,rawConstPointerType],baseClassRawType?[baseClassRawType]:[],function(base){base=base[0];var baseClass;var basePrototype;if(baseClassRawType){baseClass=base.registeredClass;basePrototype=baseClass.instancePrototype}else{basePrototype=ClassHandle.prototype}var constructor=createNamedFunction(legalFunctionName,function(){if(Object.getPrototypeOf(this)!==instancePrototype){throw new BindingError("Use 'new' to construct "+name)}if(undefined===registeredClass.constructor_body){throw new BindingError(name+" has no accessible constructor")}var body=registeredClass.constructor_body[arguments.length];if(undefined===body){throw new BindingError("Tried to invoke ctor of "+name+" with invalid number of parameters ("+arguments.length+") - expected ("+Object.keys(registeredClass.constructor_body).toString()+") parameters instead!")}return body.apply(this,arguments)});var instancePrototype=Object.create(basePrototype,{constructor:{value:constructor}});constructor.prototype=instancePrototype;var registeredClass=new RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast);var referenceConverter=new RegisteredPointer(name,registeredClass,true,false,false);var pointerConverter=new RegisteredPointer(name+"*",registeredClass,false,false,false);var constPointerConverter=new RegisteredPointer(name+" const*",registeredClass,false,true,false);registeredPointers[rawType]={pointerType:pointerConverter,constPointerType:constPointerConverter};replacePublicSymbol(legalFunctionName,constructor);return[referenceConverter,pointerConverter,constPointerConverter]})}function new_(constructor,argumentList){if(!(constructor instanceof Function)){throw new TypeError("new_ called with constructor type "+typeof constructor+" which is not a function")}var dummy=createNamedFunction(constructor.name||"unknownFunctionName",function(){});dummy.prototype=constructor.prototype;var obj=new dummy;var r=constructor.apply(obj,argumentList);return r instanceof Object?r:obj}function craftInvokerFunction(humanName,argTypes,classType,cppInvokerFunc,cppTargetFunc){var argCount=argTypes.length;if(argCount<2){throwBindingError("argTypes array size mismatch! Must at least get return value and 'this' types!")}var isClassMethodFunc=argTypes[1]!==null&&classType!==null;var needsDestructorStack=false;for(var i=1;i0?", ":"")+argsListWired}invokerFnBody+=(returns?"var rv = ":"")+"invoker(fn"+(argsListWired.length>0?", ":"")+argsListWired+");\n";if(needsDestructorStack){invokerFnBody+="runDestructors(destructors);\n"}else{for(var i=isClassMethodFunc?1:2;i>2])}return array}function __embind_register_class_class_function(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,fn){var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=readLatin1String(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker);whenDependentTypesAreResolved([],[rawClassType],function(classType){classType=classType[0];var humanName=classType.name+"."+methodName;function unboundTypesHandler(){throwUnboundTypeError("Cannot call "+humanName+" due to unbound types",rawArgTypes)}if(methodName.startsWith("@@")){methodName=Symbol[methodName.substring(2)]}var proto=classType.registeredClass.constructor;if(undefined===proto[methodName]){unboundTypesHandler.argCount=argCount-1;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-1]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,function(argTypes){var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));var func=craftInvokerFunction(humanName,invokerArgsArray,null,rawInvoker,fn);if(undefined===proto[methodName].overloadTable){func.argCount=argCount-1;proto[methodName]=func}else{proto[methodName].overloadTable[argCount-1]=func}return[]});return[]})}function __embind_register_class_constructor(rawClassType,argCount,rawArgTypesAddr,invokerSignature,invoker,rawConstructor){assert(argCount>0);var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);invoker=embind__requireFunction(invokerSignature,invoker);whenDependentTypesAreResolved([],[rawClassType],function(classType){classType=classType[0];var humanName="constructor "+classType.name;if(undefined===classType.registeredClass.constructor_body){classType.registeredClass.constructor_body=[]}if(undefined!==classType.registeredClass.constructor_body[argCount-1]){throw new BindingError("Cannot register multiple constructors with identical number of parameters ("+(argCount-1)+") for class '"+classType.name+"'! Overload resolution is currently only performed using the parameter count, not actual type info!")}classType.registeredClass.constructor_body[argCount-1]=()=>{throwUnboundTypeError("Cannot construct "+classType.name+" due to unbound types",rawArgTypes)};whenDependentTypesAreResolved([],rawArgTypes,function(argTypes){argTypes.splice(1,0,null);classType.registeredClass.constructor_body[argCount-1]=craftInvokerFunction(humanName,argTypes,null,invoker,rawConstructor);return[]});return[]})}function __embind_register_class_function(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,context,isPureVirtual){var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=readLatin1String(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker);whenDependentTypesAreResolved([],[rawClassType],function(classType){classType=classType[0];var humanName=classType.name+"."+methodName;if(methodName.startsWith("@@")){methodName=Symbol[methodName.substring(2)]}if(isPureVirtual){classType.registeredClass.pureVirtualFunctions.push(methodName)}function unboundTypesHandler(){throwUnboundTypeError("Cannot call "+humanName+" due to unbound types",rawArgTypes)}var proto=classType.registeredClass.instancePrototype;var method=proto[methodName];if(undefined===method||undefined===method.overloadTable&&method.className!==classType.name&&method.argCount===argCount-2){unboundTypesHandler.argCount=argCount-2;unboundTypesHandler.className=classType.name;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-2]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,function(argTypes){var memberFunction=craftInvokerFunction(humanName,argTypes,classType,rawInvoker,context);if(undefined===proto[methodName].overloadTable){memberFunction.argCount=argCount-2;proto[methodName]=memberFunction}else{proto[methodName].overloadTable[argCount-2]=memberFunction}return[]});return[]})}function validateThis(this_,classType,humanName){if(!(this_ instanceof Object)){throwBindingError(humanName+' with invalid "this": '+this_)}if(!(this_ instanceof classType.registeredClass.constructor)){throwBindingError(humanName+' incompatible with "this" of type '+this_.constructor.name)}if(!this_.$$.ptr){throwBindingError("cannot call emscripten binding method "+humanName+" on deleted object")}return upcastPointer(this_.$$.ptr,this_.$$.ptrType.registeredClass,classType.registeredClass)}function __embind_register_class_property(classType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext){fieldName=readLatin1String(fieldName);getter=embind__requireFunction(getterSignature,getter);whenDependentTypesAreResolved([],[classType],function(classType){classType=classType[0];var humanName=classType.name+"."+fieldName;var desc={get:function(){throwUnboundTypeError("Cannot access "+humanName+" due to unbound types",[getterReturnType,setterArgumentType])},enumerable:true,configurable:true};if(setter){desc.set=()=>{throwUnboundTypeError("Cannot access "+humanName+" due to unbound types",[getterReturnType,setterArgumentType])}}else{desc.set=v=>{throwBindingError(humanName+" is a read-only property")}}Object.defineProperty(classType.registeredClass.instancePrototype,fieldName,desc);whenDependentTypesAreResolved([],setter?[getterReturnType,setterArgumentType]:[getterReturnType],function(types){var getterReturnType=types[0];var desc={get:function(){var ptr=validateThis(this,classType,humanName+" getter");return getterReturnType["fromWireType"](getter(getterContext,ptr))},enumerable:true};if(setter){setter=embind__requireFunction(setterSignature,setter);var setterArgumentType=types[1];desc.set=function(v){var ptr=validateThis(this,classType,humanName+" setter");var destructors=[];setter(setterContext,ptr,setterArgumentType["toWireType"](destructors,v));runDestructors(destructors)}}Object.defineProperty(classType.registeredClass.instancePrototype,fieldName,desc);return[]});return[]})}var emval_free_list=[];var emval_handle_array=[{},{value:undefined},{value:null},{value:true},{value:false}];function __emval_decref(handle){if(handle>4&&0===--emval_handle_array[handle].refcount){emval_handle_array[handle]=undefined;emval_free_list.push(handle)}}function count_emval_handles(){var count=0;for(var i=5;i{if(!handle){throwBindingError("Cannot use deleted val. handle = "+handle)}return emval_handle_array[handle].value},toHandle:value=>{switch(value){case undefined:return 1;case null:return 2;case true:return 3;case false:return 4;default:{var handle=emval_free_list.length?emval_free_list.pop():emval_handle_array.length;emval_handle_array[handle]={refcount:1,value:value};return handle}}}};function __embind_register_emval(rawType,name){name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":function(handle){var rv=Emval.toValue(handle);__emval_decref(handle);return rv},"toWireType":function(destructors,value){return Emval.toHandle(value)},"argPackAdvance":8,"readValueFromPointer":simpleReadValueFromPointer,destructorFunction:null})}function enumReadValueFromPointer(name,shift,signed){switch(shift){case 0:return function(pointer){var heap=signed?HEAP8:HEAPU8;return this["fromWireType"](heap[pointer])};case 1:return function(pointer){var heap=signed?HEAP16:HEAPU16;return this["fromWireType"](heap[pointer>>1])};case 2:return function(pointer){var heap=signed?HEAP32:HEAPU32;return this["fromWireType"](heap[pointer>>2])};default:throw new TypeError("Unknown integer type: "+name)}}function __embind_register_enum(rawType,name,size,isSigned){var shift=getShiftFromSize(size);name=readLatin1String(name);function ctor(){}ctor.values={};registerType(rawType,{name:name,constructor:ctor,"fromWireType":function(c){return this.constructor.values[c]},"toWireType":function(destructors,c){return c.value},"argPackAdvance":8,"readValueFromPointer":enumReadValueFromPointer(name,shift,isSigned),destructorFunction:null});exposePublicSymbol(name,ctor)}function requireRegisteredType(rawType,humanName){var impl=registeredTypes[rawType];if(undefined===impl){throwBindingError(humanName+" has unknown type "+getTypeName(rawType))}return impl}function __embind_register_enum_value(rawEnumType,name,enumValue){var enumType=requireRegisteredType(rawEnumType,"enum");name=readLatin1String(name);var Enum=enumType.constructor;var Value=Object.create(enumType.constructor.prototype,{value:{value:enumValue},constructor:{value:createNamedFunction(enumType.name+"_"+name,function(){})}});Enum.values[enumValue]=Value;Enum[name]=Value}function embindRepr(v){if(v===null){return"null"}var t=typeof v;if(t==="object"||t==="array"||t==="function"){return v.toString()}else{return""+v}}function floatReadValueFromPointer(name,shift){switch(shift){case 2:return function(pointer){return this["fromWireType"](HEAPF32[pointer>>2])};case 3:return function(pointer){return this["fromWireType"](HEAPF64[pointer>>3])};default:throw new TypeError("Unknown float type: "+name)}}function __embind_register_float(rawType,name,size){var shift=getShiftFromSize(size);name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":function(value){return value},"toWireType":function(destructors,value){return value},"argPackAdvance":8,"readValueFromPointer":floatReadValueFromPointer(name,shift),destructorFunction:null})}function integerReadValueFromPointer(name,shift,signed){switch(shift){case 0:return signed?function readS8FromPointer(pointer){return HEAP8[pointer]}:function readU8FromPointer(pointer){return HEAPU8[pointer]};case 1:return signed?function readS16FromPointer(pointer){return HEAP16[pointer>>1]}:function readU16FromPointer(pointer){return HEAPU16[pointer>>1]};case 2:return signed?function readS32FromPointer(pointer){return HEAP32[pointer>>2]}:function readU32FromPointer(pointer){return HEAPU32[pointer>>2]};default:throw new TypeError("Unknown integer type: "+name)}}function __embind_register_integer(primitiveType,name,size,minRange,maxRange){name=readLatin1String(name);if(maxRange===-1){maxRange=4294967295}var shift=getShiftFromSize(size);var fromWireType=value=>value;if(minRange===0){var bitshift=32-8*size;fromWireType=value=>value<>>bitshift}var isUnsignedType=name.includes("unsigned");var checkAssertions=(value,toTypeName)=>{};var toWireType;if(isUnsignedType){toWireType=function(destructors,value){checkAssertions(value,this.name);return value>>>0}}else{toWireType=function(destructors,value){checkAssertions(value,this.name);return value}}registerType(primitiveType,{name:name,"fromWireType":fromWireType,"toWireType":toWireType,"argPackAdvance":8,"readValueFromPointer":integerReadValueFromPointer(name,shift,minRange!==0),destructorFunction:null})}function __embind_register_memory_view(rawType,dataTypeIndex,name){var typeMapping=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array];var TA=typeMapping[dataTypeIndex];function decodeMemoryView(handle){handle=handle>>2;var heap=HEAPU32;var size=heap[handle];var data=heap[handle+1];return new TA(heap.buffer,data,size)}name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":decodeMemoryView,"argPackAdvance":8,"readValueFromPointer":decodeMemoryView},{ignoreDuplicateRegistrations:true})}function __embind_register_std_string(rawType,name){name=readLatin1String(name);var stdStringIsUTF8=name==="std::string";registerType(rawType,{name:name,"fromWireType":function(value){var length=HEAPU32[value>>2];var payload=value+4;var str;if(stdStringIsUTF8){var decodeStartPtr=payload;for(var i=0;i<=length;++i){var currentBytePtr=payload+i;if(i==length||HEAPU8[currentBytePtr]==0){var maxRead=currentBytePtr-decodeStartPtr;var stringSegment=UTF8ToString(decodeStartPtr,maxRead);if(str===undefined){str=stringSegment}else{str+=String.fromCharCode(0);str+=stringSegment}decodeStartPtr=currentBytePtr+1}}}else{var a=new Array(length);for(var i=0;i>2]=length;if(stdStringIsUTF8&&valueIsOfTypeString){stringToUTF8(value,ptr,length+1)}else{if(valueIsOfTypeString){for(var i=0;i255){_free(ptr);throwBindingError("String has UTF-16 code units that do not fit in 8 bits")}HEAPU8[ptr+i]=charCode}}else{for(var i=0;i>1;var maxIdx=idx+maxBytesToRead/2;while(!(idx>=maxIdx)&&HEAPU16[idx])++idx;endPtr=idx<<1;if(endPtr-ptr>32&&UTF16Decoder)return UTF16Decoder.decode(HEAPU8.subarray(ptr,endPtr));var str="";for(var i=0;!(i>=maxBytesToRead/2);++i){var codeUnit=HEAP16[ptr+i*2>>1];if(codeUnit==0)break;str+=String.fromCharCode(codeUnit)}return str}function stringToUTF16(str,outPtr,maxBytesToWrite){if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<2)return 0;maxBytesToWrite-=2;var startPtr=outPtr;var numCharsToWrite=maxBytesToWrite>1]=codeUnit;outPtr+=2}HEAP16[outPtr>>1]=0;return outPtr-startPtr}function lengthBytesUTF16(str){return str.length*2}function UTF32ToString(ptr,maxBytesToRead){var i=0;var str="";while(!(i>=maxBytesToRead/4)){var utf32=HEAP32[ptr+i*4>>2];if(utf32==0)break;++i;if(utf32>=65536){var ch=utf32-65536;str+=String.fromCharCode(55296|ch>>10,56320|ch&1023)}else{str+=String.fromCharCode(utf32)}}return str}function stringToUTF32(str,outPtr,maxBytesToWrite){if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<4)return 0;var startPtr=outPtr;var endPtr=startPtr+maxBytesToWrite-4;for(var i=0;i=55296&&codeUnit<=57343){var trailSurrogate=str.charCodeAt(++i);codeUnit=65536+((codeUnit&1023)<<10)|trailSurrogate&1023}HEAP32[outPtr>>2]=codeUnit;outPtr+=4;if(outPtr+4>endPtr)break}HEAP32[outPtr>>2]=0;return outPtr-startPtr}function lengthBytesUTF32(str){var len=0;for(var i=0;i=55296&&codeUnit<=57343)++i;len+=4}return len}function __embind_register_std_wstring(rawType,charSize,name){name=readLatin1String(name);var decodeString,encodeString,getHeap,lengthBytesUTF,shift;if(charSize===2){decodeString=UTF16ToString;encodeString=stringToUTF16;lengthBytesUTF=lengthBytesUTF16;getHeap=()=>HEAPU16;shift=1}else if(charSize===4){decodeString=UTF32ToString;encodeString=stringToUTF32;lengthBytesUTF=lengthBytesUTF32;getHeap=()=>HEAPU32;shift=2}registerType(rawType,{name:name,"fromWireType":function(value){var length=HEAPU32[value>>2];var HEAP=getHeap();var str;var decodeStartPtr=value+4;for(var i=0;i<=length;++i){var currentBytePtr=value+4+i*charSize;if(i==length||HEAP[currentBytePtr>>shift]==0){var maxReadBytes=currentBytePtr-decodeStartPtr;var stringSegment=decodeString(decodeStartPtr,maxReadBytes);if(str===undefined){str=stringSegment}else{str+=String.fromCharCode(0);str+=stringSegment}decodeStartPtr=currentBytePtr+charSize}}_free(value);return str},"toWireType":function(destructors,value){if(!(typeof value=="string")){throwBindingError("Cannot pass non-string to C++ string type "+name)}var length=lengthBytesUTF(value);var ptr=_malloc(4+length+charSize);HEAPU32[ptr>>2]=length>>shift;encodeString(value,ptr+4,length+charSize);if(destructors!==null){destructors.push(_free,ptr)}return ptr},"argPackAdvance":8,"readValueFromPointer":simpleReadValueFromPointer,destructorFunction:function(ptr){_free(ptr)}})}function __embind_register_value_array(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor){tupleRegistrations[rawType]={name:readLatin1String(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),elements:[]}}function __embind_register_value_array_element(rawTupleType,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext){tupleRegistrations[rawTupleType].elements.push({getterReturnType:getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext:getterContext,setterArgumentType:setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext:setterContext})}function __embind_register_value_object(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor){structRegistrations[rawType]={name:readLatin1String(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),fields:[]}}function __embind_register_value_object_field(structType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext){structRegistrations[structType].fields.push({fieldName:readLatin1String(fieldName),getterReturnType:getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext:getterContext,setterArgumentType:setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext:setterContext})}function __embind_register_void(rawType,name){name=readLatin1String(name);registerType(rawType,{isVoid:true,name:name,"argPackAdvance":0,"fromWireType":function(){return undefined},"toWireType":function(destructors,o){return undefined}})}function __emval_as(handle,returnType,destructorsRef){handle=Emval.toValue(handle);returnType=requireRegisteredType(returnType,"emval::as");var destructors=[];var rd=Emval.toHandle(destructors);HEAPU32[destructorsRef>>2]=rd;return returnType["toWireType"](destructors,handle)}function emval_allocateDestructors(destructorsRef){var destructors=[];HEAPU32[destructorsRef>>2]=Emval.toHandle(destructors);return destructors}var emval_symbols={};function getStringOrSymbol(address){var symbol=emval_symbols[address];if(symbol===undefined){return readLatin1String(address)}return symbol}var emval_methodCallers=[];function __emval_call_method(caller,handle,methodName,destructorsRef,args){caller=emval_methodCallers[caller];handle=Emval.toValue(handle);methodName=getStringOrSymbol(methodName);return caller(handle,methodName,emval_allocateDestructors(destructorsRef),args)}function __emval_call_void_method(caller,handle,methodName,args){caller=emval_methodCallers[caller];handle=Emval.toValue(handle);methodName=getStringOrSymbol(methodName);caller(handle,methodName,null,args)}function __emval_equals(first,second){first=Emval.toValue(first);second=Emval.toValue(second);return first==second}function emval_get_global(){if(typeof globalThis=="object"){return globalThis}return function(){return Function}()("return this")()}function __emval_get_global(name){if(name===0){return Emval.toHandle(emval_get_global())}else{name=getStringOrSymbol(name);return Emval.toHandle(emval_get_global()[name])}}function emval_addMethodCaller(caller){var id=emval_methodCallers.length;emval_methodCallers.push(caller);return id}function emval_lookupTypes(argCount,argTypes){var a=new Array(argCount);for(var i=0;i>2],"parameter "+i)}return a}var emval_registeredMethods=[];function __emval_get_method_caller(argCount,argTypes){var types=emval_lookupTypes(argCount,argTypes);var retType=types[0];var signatureName=retType.name+"_$"+types.slice(1).map(function(t){return t.name}).join("_")+"$";var returnId=emval_registeredMethods[signatureName];if(returnId!==undefined){return returnId}var params=["retType"];var args=[retType];var argsList="";for(var i=0;i4){emval_handle_array[handle].refcount+=1}}function __emval_instanceof(object,constructor){object=Emval.toValue(object);constructor=Emval.toValue(constructor);return object instanceof constructor}function __emval_is_number(handle){handle=Emval.toValue(handle);return typeof handle=="number"}function __emval_is_string(handle){handle=Emval.toValue(handle);return typeof handle=="string"}function craftEmvalAllocator(argCount){var argsList="";for(var i=0;iHEAPU32;var functionBody="return function emval_allocator_"+argCount+"(constructor, argTypes, args) {\n"+" var HEAPU32 = getMemory();\n";for(var i=0;i>2)], 'parameter "+i+"');\n"+"var arg"+i+" = argType"+i+".readValueFromPointer(args);\n"+"args += argType"+i+"['argPackAdvance'];\n"+"argTypes += 4;\n"}functionBody+="var obj = new constructor("+argsList+");\n"+"return valueToHandle(obj);\n"+"}\n";return new Function("requireRegisteredType","Module","valueToHandle","getMemory",functionBody)(requireRegisteredType,Module,Emval.toHandle,getMemory)}var emval_newers={};function __emval_new(handle,argCount,argTypes,args){handle=Emval.toValue(handle);var newer=emval_newers[argCount];if(!newer){newer=craftEmvalAllocator(argCount);emval_newers[argCount]=newer}return newer(handle,argTypes,args)}function __emval_new_array(){return Emval.toHandle([])}function __emval_new_cstring(v){return Emval.toHandle(getStringOrSymbol(v))}function __emval_new_object(){return Emval.toHandle({})}function __emval_run_destructors(handle){var destructors=Emval.toValue(handle);runDestructors(destructors);__emval_decref(handle)}function __emval_set_property(handle,key,value){handle=Emval.toValue(handle);key=Emval.toValue(key);value=Emval.toValue(value);handle[key]=value}function __emval_take_value(type,arg){type=requireRegisteredType(type,"_emval_take_value");var v=type["readValueFromPointer"](arg);return Emval.toHandle(v)}function readI53FromI64(ptr){return HEAPU32[ptr>>2]+HEAP32[ptr+4>>2]*4294967296}function __gmtime_js(time,tmPtr){var date=new Date(readI53FromI64(time)*1e3);HEAP32[tmPtr>>2]=date.getUTCSeconds();HEAP32[tmPtr+4>>2]=date.getUTCMinutes();HEAP32[tmPtr+8>>2]=date.getUTCHours();HEAP32[tmPtr+12>>2]=date.getUTCDate();HEAP32[tmPtr+16>>2]=date.getUTCMonth();HEAP32[tmPtr+20>>2]=date.getUTCFullYear()-1900;HEAP32[tmPtr+24>>2]=date.getUTCDay();var start=Date.UTC(date.getUTCFullYear(),0,1,0,0,0,0);var yday=(date.getTime()-start)/(1e3*60*60*24)|0;HEAP32[tmPtr+28>>2]=yday}function __isLeapYear(year){return year%4===0&&(year%100!==0||year%400===0)}var __MONTH_DAYS_LEAP_CUMULATIVE=[0,31,60,91,121,152,182,213,244,274,305,335];var __MONTH_DAYS_REGULAR_CUMULATIVE=[0,31,59,90,120,151,181,212,243,273,304,334];function __yday_from_date(date){var isLeapYear=__isLeapYear(date.getFullYear());var monthDaysCumulative=isLeapYear?__MONTH_DAYS_LEAP_CUMULATIVE:__MONTH_DAYS_REGULAR_CUMULATIVE;var yday=monthDaysCumulative[date.getMonth()]+date.getDate()-1;return yday}function __localtime_js(time,tmPtr){var date=new Date(readI53FromI64(time)*1e3);HEAP32[tmPtr>>2]=date.getSeconds();HEAP32[tmPtr+4>>2]=date.getMinutes();HEAP32[tmPtr+8>>2]=date.getHours();HEAP32[tmPtr+12>>2]=date.getDate();HEAP32[tmPtr+16>>2]=date.getMonth();HEAP32[tmPtr+20>>2]=date.getFullYear()-1900;HEAP32[tmPtr+24>>2]=date.getDay();var yday=__yday_from_date(date)|0;HEAP32[tmPtr+28>>2]=yday;HEAP32[tmPtr+36>>2]=-(date.getTimezoneOffset()*60);var start=new Date(date.getFullYear(),0,1);var summerOffset=new Date(date.getFullYear(),6,1).getTimezoneOffset();var winterOffset=start.getTimezoneOffset();var dst=(summerOffset!=winterOffset&&date.getTimezoneOffset()==Math.min(winterOffset,summerOffset))|0;HEAP32[tmPtr+32>>2]=dst}function __mktime_js(tmPtr){var date=new Date(HEAP32[tmPtr+20>>2]+1900,HEAP32[tmPtr+16>>2],HEAP32[tmPtr+12>>2],HEAP32[tmPtr+8>>2],HEAP32[tmPtr+4>>2],HEAP32[tmPtr>>2],0);var dst=HEAP32[tmPtr+32>>2];var guessedOffset=date.getTimezoneOffset();var start=new Date(date.getFullYear(),0,1);var summerOffset=new Date(date.getFullYear(),6,1).getTimezoneOffset();var winterOffset=start.getTimezoneOffset();var dstOffset=Math.min(winterOffset,summerOffset);if(dst<0){HEAP32[tmPtr+32>>2]=Number(summerOffset!=winterOffset&&dstOffset==guessedOffset)}else if(dst>0!=(dstOffset==guessedOffset)){var nonDstOffset=Math.max(winterOffset,summerOffset);var trueOffset=dst>0?dstOffset:nonDstOffset;date.setTime(date.getTime()+(trueOffset-guessedOffset)*6e4)}HEAP32[tmPtr+24>>2]=date.getDay();var yday=__yday_from_date(date)|0;HEAP32[tmPtr+28>>2]=yday;HEAP32[tmPtr>>2]=date.getSeconds();HEAP32[tmPtr+4>>2]=date.getMinutes();HEAP32[tmPtr+8>>2]=date.getHours();HEAP32[tmPtr+12>>2]=date.getDate();HEAP32[tmPtr+16>>2]=date.getMonth();HEAP32[tmPtr+20>>2]=date.getYear();return date.getTime()/1e3|0}function allocateUTF8(str){var size=lengthBytesUTF8(str)+1;var ret=_malloc(size);if(ret)stringToUTF8Array(str,HEAP8,ret,size);return ret}function __tzset_js(timezone,daylight,tzname){var currentYear=(new Date).getFullYear();var winter=new Date(currentYear,0,1);var summer=new Date(currentYear,6,1);var winterOffset=winter.getTimezoneOffset();var summerOffset=summer.getTimezoneOffset();var stdTimezoneOffset=Math.max(winterOffset,summerOffset);HEAPU32[timezone>>2]=stdTimezoneOffset*60;HEAP32[daylight>>2]=Number(winterOffset!=summerOffset);function extractZone(date){var match=date.toTimeString().match(/\(([A-Za-z ]+)\)$/);return match?match[1]:"GMT"}var winterName=extractZone(winter);var summerName=extractZone(summer);var winterNamePtr=allocateUTF8(winterName);var summerNamePtr=allocateUTF8(summerName);if(summerOffset>2]=winterNamePtr;HEAPU32[tzname+4>>2]=summerNamePtr}else{HEAPU32[tzname>>2]=summerNamePtr;HEAPU32[tzname+4>>2]=winterNamePtr}}function _abort(){abort("")}function _emscripten_date_now(){return Date.now()}function getHeapMax(){return 2147483648}var _emscripten_get_now;if(ENVIRONMENT_IS_NODE){_emscripten_get_now=()=>{var t=process["hrtime"]();return t[0]*1e3+t[1]/1e6}}else _emscripten_get_now=()=>performance.now();function emscripten_realloc_buffer(size){var b=wasmMemory.buffer;try{wasmMemory.grow(size-b.byteLength+65535>>>16);updateMemoryViews();return 1}catch(e){}}function _emscripten_resize_heap(requestedSize){var oldSize=HEAPU8.length;requestedSize=requestedSize>>>0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}let alignUp=(x,multiple)=>x+(multiple-x%multiple)%multiple;for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignUp(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=emscripten_realloc_buffer(newSize);if(replacement){return true}}return false}var ENV={};function getExecutableName(){return thisProgram||"./this.program"}function getEnvStrings(){if(!getEnvStrings.strings){var lang=(typeof navigator=="object"&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8";var env={"USER":"web_user","LOGNAME":"web_user","PATH":"/","PWD":"/","HOME":"/home/web_user","LANG":lang,"_":getExecutableName()};for(var x in ENV){if(ENV[x]===undefined)delete env[x];else env[x]=ENV[x]}var strings=[];for(var x in env){strings.push(x+"="+env[x])}getEnvStrings.strings=strings}return getEnvStrings.strings}function writeAsciiToMemory(str,buffer,dontAddNull){for(var i=0;i>0]=str.charCodeAt(i)}if(!dontAddNull)HEAP8[buffer>>0]=0}function _environ_get(__environ,environ_buf){var bufSize=0;getEnvStrings().forEach(function(string,i){var ptr=environ_buf+bufSize;HEAPU32[__environ+i*4>>2]=ptr;writeAsciiToMemory(string,ptr);bufSize+=string.length+1});return 0}function _environ_sizes_get(penviron_count,penviron_buf_size){var strings=getEnvStrings();HEAPU32[penviron_count>>2]=strings.length;var bufSize=0;strings.forEach(function(string){bufSize+=string.length+1});HEAPU32[penviron_buf_size>>2]=bufSize;return 0}function _fd_close(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);FS.close(stream);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}function doReadv(stream,iov,iovcnt,offset){var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.read(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}function _fd_seek(fd,offset_low,offset_high,whence,newOffset){try{var offset=convertI32PairToI53Checked(offset_low,offset_high);if(isNaN(offset))return 61;var stream=SYSCALLS.getStreamFromFD(fd);FS.llseek(stream,offset,whence);tempI64=[stream.position>>>0,(tempDouble=stream.position,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[newOffset>>2]=tempI64[0],HEAP32[newOffset+4>>2]=tempI64[1];if(stream.getdents&&offset===0&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}function doWritev(stream,iov,iovcnt,offset){var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.write(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(typeof offset!=="undefined"){offset+=curr}}return ret}function _fd_write(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=doWritev(stream,iov,iovcnt);HEAPU32[pnum>>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}var FSNode=function(parent,name,mode,rdev){if(!parent){parent=this}this.parent=parent;this.mount=parent.mount;this.mounted=null;this.id=FS.nextInode++;this.name=name;this.mode=mode;this.node_ops={};this.stream_ops={};this.rdev=rdev};var readMode=292|73;var writeMode=146;Object.defineProperties(FSNode.prototype,{read:{get:function(){return(this.mode&readMode)===readMode},set:function(val){val?this.mode|=readMode:this.mode&=~readMode}},write:{get:function(){return(this.mode&writeMode)===writeMode},set:function(val){val?this.mode|=writeMode:this.mode&=~writeMode}},isFolder:{get:function(){return FS.isDir(this.mode)}},isDevice:{get:function(){return FS.isChrdev(this.mode)}}});FS.FSNode=FSNode;FS.staticInit();InternalError=Module["InternalError"]=extendError(Error,"InternalError");embind_init_charCodes();BindingError=Module["BindingError"]=extendError(Error,"BindingError");init_ClassHandle();init_embind();init_RegisteredPointer();UnboundTypeError=Module["UnboundTypeError"]=extendError(Error,"UnboundTypeError");init_emval();var asmLibraryArg={"v":___cxa_throw,"U":___syscall_connect,"ea":___syscall_faccessat,"o":___syscall_fcntl64,"Z":___syscall_fstat64,"S":___syscall_ftruncate64,"K":___syscall_ioctl,"W":___syscall_lstat64,"X":___syscall_newfstatat,"G":___syscall_openat,"F":___syscall_socket,"Y":___syscall_stat64,"r":__embind_finalize_value_array,"ka":__embind_finalize_value_object,"T":__embind_register_bigint,"ga":__embind_register_bool,"e":__embind_register_class,"f":__embind_register_class_class_function,"g":__embind_register_class_constructor,"b":__embind_register_class_function,"a":__embind_register_class_property,"fa":__embind_register_emval,"i":__embind_register_enum,"d":__embind_register_enum_value,"M":__embind_register_float,"p":__embind_register_integer,"l":__embind_register_memory_view,"L":__embind_register_std_string,"B":__embind_register_std_wstring,"q":__embind_register_value_array,"ia":__embind_register_value_array_element,"ja":__embind_register_value_object,"N":__embind_register_value_object_field,"ha":__embind_register_void,"t":__emval_as,"la":__emval_call_method,"P":__emval_call_void_method,"c":__emval_decref,"Q":__emval_equals,"y":__emval_get_global,"C":__emval_get_method_caller,"E":__emval_get_module_property,"w":__emval_get_property,"m":__emval_incref,"D":__emval_instanceof,"na":__emval_is_number,"ma":__emval_is_string,"O":__emval_new,"n":__emval_new_array,"u":__emval_new_cstring,"k":__emval_new_object,"s":__emval_run_destructors,"j":__emval_set_property,"h":__emval_take_value,"aa":__gmtime_js,"ba":__localtime_js,"ca":__mktime_js,"da":__tzset_js,"z":_abort,"I":_emscripten_date_now,"H":_emscripten_get_now,"V":_emscripten_resize_heap,"_":_environ_get,"$":_environ_sizes_get,"x":_fd_close,"J":_fd_read,"R":_fd_seek,"A":_fd_write};var asm=createWasm();var ___wasm_call_ctors=Module["___wasm_call_ctors"]=function(){return(___wasm_call_ctors=Module["___wasm_call_ctors"]=Module["asm"]["pa"]).apply(null,arguments)};var _malloc=Module["_malloc"]=function(){return(_malloc=Module["_malloc"]=Module["asm"]["ra"]).apply(null,arguments)};var _free=Module["_free"]=function(){return(_free=Module["_free"]=Module["asm"]["sa"]).apply(null,arguments)};var ___errno_location=Module["___errno_location"]=function(){return(___errno_location=Module["___errno_location"]=Module["asm"]["ta"]).apply(null,arguments)};var ___getTypeName=Module["___getTypeName"]=function(){return(___getTypeName=Module["___getTypeName"]=Module["asm"]["ua"]).apply(null,arguments)};var __embind_initialize_bindings=Module["__embind_initialize_bindings"]=function(){return(__embind_initialize_bindings=Module["__embind_initialize_bindings"]=Module["asm"]["va"]).apply(null,arguments)};var _htons=Module["_htons"]=function(){return(_htons=Module["_htons"]=Module["asm"]["wa"]).apply(null,arguments)};var _ntohs=Module["_ntohs"]=function(){return(_ntohs=Module["_ntohs"]=Module["asm"]["xa"]).apply(null,arguments)};var ___cxa_is_pointer_type=Module["___cxa_is_pointer_type"]=function(){return(___cxa_is_pointer_type=Module["___cxa_is_pointer_type"]=Module["asm"]["ya"]).apply(null,arguments)};var dynCall_ji=Module["dynCall_ji"]=function(){return(dynCall_ji=Module["dynCall_ji"]=Module["asm"]["za"]).apply(null,arguments)};var dynCall_vij=Module["dynCall_vij"]=function(){return(dynCall_vij=Module["dynCall_vij"]=Module["asm"]["Aa"]).apply(null,arguments)};var dynCall_jiji=Module["dynCall_jiji"]=function(){return(dynCall_jiji=Module["dynCall_jiji"]=Module["asm"]["Ba"]).apply(null,arguments)};var calledRun;dependenciesFulfilled=function runCaller(){if(!calledRun)run();if(!calledRun)dependenciesFulfilled=runCaller};function run(args){args=args||arguments_;if(runDependencies>0){return}preRun();if(runDependencies>0){return}function doRun(){if(calledRun)return;calledRun=true;Module["calledRun"]=true;if(ABORT)return;initRuntime();readyPromiseResolve(Module);if(Module["onRuntimeInitialized"])Module["onRuntimeInitialized"]();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(function(){setTimeout(function(){Module["setStatus"]("")},1);doRun()},1)}else{doRun()}}if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}run(); return rhino3dm.ready diff --git a/examples/jsm/libs/rhino3dm/rhino3dm.module.js b/examples/jsm/libs/rhino3dm/rhino3dm.module.js index 57a6f564c3b225..14ec13305173a1 100644 --- a/examples/jsm/libs/rhino3dm/rhino3dm.module.js +++ b/examples/jsm/libs/rhino3dm/rhino3dm.module.js @@ -1,12 +1,12 @@ -var rhino3dm = (function() { +var rhino3dm = (() => { var _scriptDir = import.meta.url; return ( -function(rhino3dm) { - rhino3dm = rhino3dm || {}; +async function(config) { + var rhino3dm = config || {}; -var Module=typeof rhino3dm!=="undefined"?rhino3dm:{};var readyPromiseResolve,readyPromiseReject;Module["ready"]=new Promise(function(resolve,reject){readyPromiseResolve=resolve;readyPromiseReject=reject});var moduleOverrides={};var key;for(key in Module){if(Module.hasOwnProperty(key)){moduleOverrides[key]=Module[key]}}var arguments_=[];var thisProgram="./this.program";var quit_=function(status,toThrow){throw toThrow};var ENVIRONMENT_IS_WEB=false;var ENVIRONMENT_IS_WORKER=false;var ENVIRONMENT_IS_NODE=false;var ENVIRONMENT_IS_SHELL=false;ENVIRONMENT_IS_WEB=typeof window==="object";ENVIRONMENT_IS_WORKER=typeof importScripts==="function";ENVIRONMENT_IS_NODE=typeof process==="object"&&typeof process.versions==="object"&&typeof process.versions.node==="string";ENVIRONMENT_IS_SHELL=!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_NODE&&!ENVIRONMENT_IS_WORKER;var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var read_,readAsync,readBinary,setWindowTitle;var nodeFS;var nodePath;if(ENVIRONMENT_IS_NODE){if(ENVIRONMENT_IS_WORKER){scriptDirectory=require("path").dirname(scriptDirectory)+"/"}else{scriptDirectory=__dirname+"/"}read_=function shell_read(filename,binary){if(!nodeFS)nodeFS=require("fs");if(!nodePath)nodePath=require("path");filename=nodePath["normalize"](filename);return nodeFS["readFileSync"](filename,binary?null:"utf8")};readBinary=function readBinary(filename){var ret=read_(filename,true);if(!ret.buffer){ret=new Uint8Array(ret)}assert(ret.buffer);return ret};if(process["argv"].length>1){thisProgram=process["argv"][1].replace(/\\/g,"/")}arguments_=process["argv"].slice(2);process["on"]("uncaughtException",function(ex){if(!(ex instanceof ExitStatus)){throw ex}});process["on"]("unhandledRejection",abort);quit_=function(status){process["exit"](status)};Module["inspect"]=function(){return"[Emscripten Module object]"}}else if(ENVIRONMENT_IS_SHELL){if(typeof read!="undefined"){read_=function shell_read(f){return read(f)}}readBinary=function readBinary(f){var data;if(typeof readbuffer==="function"){return new Uint8Array(readbuffer(f))}data=read(f,"binary");assert(typeof data==="object");return data};if(typeof scriptArgs!="undefined"){arguments_=scriptArgs}else if(typeof arguments!="undefined"){arguments_=arguments}if(typeof quit==="function"){quit_=function(status){quit(status)}}if(typeof print!=="undefined"){if(typeof console==="undefined")console={};console.log=print;console.warn=console.error=typeof printErr!=="undefined"?printErr:print}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!=="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(_scriptDir){scriptDirectory=_scriptDir}if(scriptDirectory.indexOf("blob:")!==0){scriptDirectory=scriptDirectory.substr(0,scriptDirectory.lastIndexOf("/")+1)}else{scriptDirectory=""}{read_=function shell_read(url){var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(ENVIRONMENT_IS_WORKER){readBinary=function readBinary(url){var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=function readAsync(url,onload,onerror){var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=function xhr_onload(){if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}onerror()};xhr.onerror=onerror;xhr.send(null)}}setWindowTitle=function(title){document.title=title}}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.warn.bind(console);for(key in moduleOverrides){if(moduleOverrides.hasOwnProperty(key)){Module[key]=moduleOverrides[key]}}moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var STACK_ALIGN=16;function alignMemory(size,factor){if(!factor)factor=STACK_ALIGN;return Math.ceil(size/factor)*factor}function warnOnce(text){if(!warnOnce.shown)warnOnce.shown={};if(!warnOnce.shown[text]){warnOnce.shown[text]=1;err(text)}}function convertJsFunctionToWasm(func,sig){if(typeof WebAssembly.Function==="function"){var typeNames={"i":"i32","j":"i64","f":"f32","d":"f64"};var type={parameters:[],results:sig[0]=="v"?[]:[typeNames[sig[0]]]};for(var i=1;i=endIdx))++endPtr;if(endPtr-idx>16&&heap.subarray&&UTF8Decoder){return UTF8Decoder.decode(heap.subarray(idx,endPtr))}else{var str="";while(idx>10,56320|ch&1023)}}}return str}function UTF8ToString(ptr,maxBytesToRead){return ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):""}function stringToUTF8Array(str,heap,outIdx,maxBytesToWrite){if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}}heap[outIdx]=0;return outIdx-startIdx}function stringToUTF8(str,outPtr,maxBytesToWrite){return stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite)}function lengthBytesUTF8(str){var len=0;for(var i=0;i=55296&&u<=57343)u=65536+((u&1023)<<10)|str.charCodeAt(++i)&1023;if(u<=127)++len;else if(u<=2047)len+=2;else if(u<=65535)len+=3;else len+=4}return len}var UTF16Decoder=typeof TextDecoder!=="undefined"?new TextDecoder("utf-16le"):undefined;function UTF16ToString(ptr,maxBytesToRead){var endPtr=ptr;var idx=endPtr>>1;var maxIdx=idx+maxBytesToRead/2;while(!(idx>=maxIdx)&&HEAPU16[idx])++idx;endPtr=idx<<1;if(endPtr-ptr>32&&UTF16Decoder){return UTF16Decoder.decode(HEAPU8.subarray(ptr,endPtr))}else{var str="";for(var i=0;!(i>=maxBytesToRead/2);++i){var codeUnit=HEAP16[ptr+i*2>>1];if(codeUnit==0)break;str+=String.fromCharCode(codeUnit)}return str}}function stringToUTF16(str,outPtr,maxBytesToWrite){if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<2)return 0;maxBytesToWrite-=2;var startPtr=outPtr;var numCharsToWrite=maxBytesToWrite>1]=codeUnit;outPtr+=2}HEAP16[outPtr>>1]=0;return outPtr-startPtr}function lengthBytesUTF16(str){return str.length*2}function UTF32ToString(ptr,maxBytesToRead){var i=0;var str="";while(!(i>=maxBytesToRead/4)){var utf32=HEAP32[ptr+i*4>>2];if(utf32==0)break;++i;if(utf32>=65536){var ch=utf32-65536;str+=String.fromCharCode(55296|ch>>10,56320|ch&1023)}else{str+=String.fromCharCode(utf32)}}return str}function stringToUTF32(str,outPtr,maxBytesToWrite){if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<4)return 0;var startPtr=outPtr;var endPtr=startPtr+maxBytesToWrite-4;for(var i=0;i=55296&&codeUnit<=57343){var trailSurrogate=str.charCodeAt(++i);codeUnit=65536+((codeUnit&1023)<<10)|trailSurrogate&1023}HEAP32[outPtr>>2]=codeUnit;outPtr+=4;if(outPtr+4>endPtr)break}HEAP32[outPtr>>2]=0;return outPtr-startPtr}function lengthBytesUTF32(str){var len=0;for(var i=0;i=55296&&codeUnit<=57343)++i;len+=4}return len}function allocateUTF8(str){var size=lengthBytesUTF8(str)+1;var ret=_malloc(size);if(ret)stringToUTF8Array(str,HEAP8,ret,size);return ret}function writeArrayToMemory(array,buffer){HEAP8.set(array,buffer)}function writeAsciiToMemory(str,buffer,dontAddNull){for(var i=0;i>0]=str.charCodeAt(i)}if(!dontAddNull)HEAP8[buffer>>0]=0}function alignUp(x,multiple){if(x%multiple>0){x+=multiple-x%multiple}return x}var buffer,HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateGlobalBufferAndViews(buf){buffer=buf;Module["HEAP8"]=HEAP8=new Int8Array(buf);Module["HEAP16"]=HEAP16=new Int16Array(buf);Module["HEAP32"]=HEAP32=new Int32Array(buf);Module["HEAPU8"]=HEAPU8=new Uint8Array(buf);Module["HEAPU16"]=HEAPU16=new Uint16Array(buf);Module["HEAPU32"]=HEAPU32=new Uint32Array(buf);Module["HEAPF32"]=HEAPF32=new Float32Array(buf);Module["HEAPF64"]=HEAPF64=new Float64Array(buf)}var INITIAL_MEMORY=Module["INITIAL_MEMORY"]||16777216;var wasmTable;var __ATPRERUN__=[];var __ATINIT__=[];var __ATMAIN__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;var runtimeExited=false;function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;if(!Module["noFSInit"]&&!FS.init.initialized)FS.init();TTY.init();callRuntimeCallbacks(__ATINIT__)}function preMain(){FS.ignorePermissions=false;callRuntimeCallbacks(__ATMAIN__)}function exitRuntime(){runtimeExited=true}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function getUniqueRunDependency(id){return id}function addRunDependency(id){runDependencies++;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}}function removeRunDependency(id){runDependencies--;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}Module["preloadedImages"]={};Module["preloadedAudios"]={};function abort(what){if(Module["onAbort"]){Module["onAbort"](what)}what+="";err(what);ABORT=true;EXITSTATUS=1;what="abort("+what+"). Build with -s ASSERTIONS=1 for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject(e);throw e}function hasPrefix(str,prefix){return String.prototype.startsWith?str.startsWith(prefix):str.indexOf(prefix)===0}var dataURIPrefix="data:application/octet-stream;base64,";function isDataURI(filename){return hasPrefix(filename,dataURIPrefix)}var fileURIPrefix="file://";function isFileURI(filename){return hasPrefix(filename,fileURIPrefix)}var wasmBinaryFile="rhino3dm.wasm";if(!isDataURI(wasmBinaryFile)){wasmBinaryFile=locateFile(wasmBinaryFile)}function getBinary(){try{if(wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(wasmBinaryFile)}else{throw"both async and sync fetching of the wasm failed"}}catch(err){abort(err)}}function getBinaryPromise(){if(!wasmBinary&&(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER)&&typeof fetch==="function"&&!isFileURI(wasmBinaryFile)){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){if(!response["ok"]){throw"failed to load wasm binary file at '"+wasmBinaryFile+"'"}return response["arrayBuffer"]()}).catch(function(){return getBinary()})}return Promise.resolve().then(getBinary)}function createWasm(){var info={"env":asmLibraryArg,"wasi_snapshot_preview1":asmLibraryArg};function receiveInstance(instance,module){var exports=instance.exports;Module["asm"]=exports;wasmMemory=Module["asm"]["memory"];updateGlobalBufferAndViews(wasmMemory.buffer);wasmTable=Module["asm"]["__indirect_function_table"];removeRunDependency("wasm-instantiate")}addRunDependency("wasm-instantiate");function receiveInstantiatedSource(output){receiveInstance(output["instance"])}function instantiateArrayBuffer(receiver){return getBinaryPromise().then(function(binary){return WebAssembly.instantiate(binary,info)}).then(receiver,function(reason){err("failed to asynchronously prepare wasm: "+reason);abort(reason)})}function instantiateAsync(){if(!wasmBinary&&typeof WebAssembly.instantiateStreaming==="function"&&!isDataURI(wasmBinaryFile)&&!isFileURI(wasmBinaryFile)&&typeof fetch==="function"){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){var result=WebAssembly.instantiateStreaming(response,info);return result.then(receiveInstantiatedSource,function(reason){err("wasm streaming compile failed: "+reason);err("falling back to ArrayBuffer instantiation");return instantiateArrayBuffer(receiveInstantiatedSource)})})}else{return instantiateArrayBuffer(receiveInstantiatedSource)}}if(Module["instantiateWasm"]){try{var exports=Module["instantiateWasm"](info,receiveInstance);return exports}catch(e){err("Module.instantiateWasm callback failed with error: "+e);return false}}instantiateAsync().catch(readyPromiseReject);return{}}var tempDouble;var tempI64;function callRuntimeCallbacks(callbacks){while(callbacks.length>0){var callback=callbacks.shift();if(typeof callback=="function"){callback(Module);continue}var func=callback.func;if(typeof func==="number"){if(callback.arg===undefined){wasmTable.get(func)()}else{wasmTable.get(func)(callback.arg)}}else{func(callback.arg===undefined?null:callback.arg)}}}function demangle(func){return func}function demangleAll(text){var regex=/\b_Z[\w\d_]+/g;return text.replace(regex,function(x){var y=demangle(x);return x===y?x:y+" ["+x+"]"})}function jsStackTrace(){var error=new Error;if(!error.stack){try{throw new Error}catch(e){error=e}if(!error.stack){return"(no stack trace available)"}}return error.stack.toString()}var ExceptionInfoAttrs={DESTRUCTOR_OFFSET:0,REFCOUNT_OFFSET:4,TYPE_OFFSET:8,CAUGHT_OFFSET:12,RETHROWN_OFFSET:13,SIZE:16};function ___cxa_allocate_exception(size){return _malloc(size+ExceptionInfoAttrs.SIZE)+ExceptionInfoAttrs.SIZE}function _atexit(func,arg){}function ___cxa_atexit(a0,a1){return _atexit(a0,a1)}function ExceptionInfo(excPtr){this.excPtr=excPtr;this.ptr=excPtr-ExceptionInfoAttrs.SIZE;this.set_type=function(type){HEAP32[this.ptr+ExceptionInfoAttrs.TYPE_OFFSET>>2]=type};this.get_type=function(){return HEAP32[this.ptr+ExceptionInfoAttrs.TYPE_OFFSET>>2]};this.set_destructor=function(destructor){HEAP32[this.ptr+ExceptionInfoAttrs.DESTRUCTOR_OFFSET>>2]=destructor};this.get_destructor=function(){return HEAP32[this.ptr+ExceptionInfoAttrs.DESTRUCTOR_OFFSET>>2]};this.set_refcount=function(refcount){HEAP32[this.ptr+ExceptionInfoAttrs.REFCOUNT_OFFSET>>2]=refcount};this.set_caught=function(caught){caught=caught?1:0;HEAP8[this.ptr+ExceptionInfoAttrs.CAUGHT_OFFSET>>0]=caught};this.get_caught=function(){return HEAP8[this.ptr+ExceptionInfoAttrs.CAUGHT_OFFSET>>0]!=0};this.set_rethrown=function(rethrown){rethrown=rethrown?1:0;HEAP8[this.ptr+ExceptionInfoAttrs.RETHROWN_OFFSET>>0]=rethrown};this.get_rethrown=function(){return HEAP8[this.ptr+ExceptionInfoAttrs.RETHROWN_OFFSET>>0]!=0};this.init=function(type,destructor){this.set_type(type);this.set_destructor(destructor);this.set_refcount(0);this.set_caught(false);this.set_rethrown(false)};this.add_ref=function(){var value=HEAP32[this.ptr+ExceptionInfoAttrs.REFCOUNT_OFFSET>>2];HEAP32[this.ptr+ExceptionInfoAttrs.REFCOUNT_OFFSET>>2]=value+1};this.release_ref=function(){var prev=HEAP32[this.ptr+ExceptionInfoAttrs.REFCOUNT_OFFSET>>2];HEAP32[this.ptr+ExceptionInfoAttrs.REFCOUNT_OFFSET>>2]=prev-1;return prev===1}}var exceptionLast=0;var uncaughtExceptionCount=0;function ___cxa_throw(ptr,type,destructor){var info=new ExceptionInfo(ptr);info.init(type,destructor);exceptionLast=ptr;uncaughtExceptionCount++;throw ptr}function _gmtime_r(time,tmPtr){var date=new Date(HEAP32[time>>2]*1e3);HEAP32[tmPtr>>2]=date.getUTCSeconds();HEAP32[tmPtr+4>>2]=date.getUTCMinutes();HEAP32[tmPtr+8>>2]=date.getUTCHours();HEAP32[tmPtr+12>>2]=date.getUTCDate();HEAP32[tmPtr+16>>2]=date.getUTCMonth();HEAP32[tmPtr+20>>2]=date.getUTCFullYear()-1900;HEAP32[tmPtr+24>>2]=date.getUTCDay();HEAP32[tmPtr+36>>2]=0;HEAP32[tmPtr+32>>2]=0;var start=Date.UTC(date.getUTCFullYear(),0,1,0,0,0,0);var yday=(date.getTime()-start)/(1e3*60*60*24)|0;HEAP32[tmPtr+28>>2]=yday;if(!_gmtime_r.GMTString)_gmtime_r.GMTString=allocateUTF8("GMT");HEAP32[tmPtr+40>>2]=_gmtime_r.GMTString;return tmPtr}function ___gmtime_r(a0,a1){return _gmtime_r(a0,a1)}function setErrNo(value){HEAP32[___errno_location()>>2]=value;return value}var PATH={splitPath:function(filename){var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;return splitPathRe.exec(filename).slice(1)},normalizeArray:function(parts,allowAboveRoot){var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up;up--){parts.unshift("..")}}return parts},normalize:function(path){var isAbsolute=path.charAt(0)==="/",trailingSlash=path.substr(-1)==="/";path=PATH.normalizeArray(path.split("/").filter(function(p){return!!p}),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path},dirname:function(path){var result=PATH.splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return"."}if(dir){dir=dir.substr(0,dir.length-1)}return root+dir},basename:function(path){if(path==="/")return"/";path=PATH.normalize(path);path=path.replace(/\/$/,"");var lastSlash=path.lastIndexOf("/");if(lastSlash===-1)return path;return path.substr(lastSlash+1)},extname:function(path){return PATH.splitPath(path)[3]},join:function(){var paths=Array.prototype.slice.call(arguments,0);return PATH.normalize(paths.join("/"))},join2:function(l,r){return PATH.normalize(l+"/"+r)}};function getRandomDevice(){if(typeof crypto==="object"&&typeof crypto["getRandomValues"]==="function"){var randomBuffer=new Uint8Array(1);return function(){crypto.getRandomValues(randomBuffer);return randomBuffer[0]}}else if(ENVIRONMENT_IS_NODE){try{var crypto_module=require("crypto");return function(){return crypto_module["randomBytes"](1)[0]}}catch(e){}}return function(){abort("randomDevice")}}var PATH_FS={resolve:function(){var resolvedPath="",resolvedAbsolute=false;for(var i=arguments.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?arguments[i]:FS.cwd();if(typeof path!=="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){return""}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=path.charAt(0)==="/"}resolvedPath=PATH.normalizeArray(resolvedPath.split("/").filter(function(p){return!!p}),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."},relative:function(from,to){from=PATH_FS.resolve(from).substr(1);to=PATH_FS.resolve(to).substr(1);function trim(arr){var start=0;for(;start=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i0){result=buf.slice(0,bytesRead).toString("utf-8")}else{result=null}}else if(typeof window!="undefined"&&typeof window.prompt=="function"){result=window.prompt("Input: ");if(result!==null){result+="\n"}}else if(typeof readline=="function"){result=readline();if(result!==null){result+="\n"}}if(!result){return null}tty.input=intArrayFromString(result,true)}return tty.input.shift()},put_char:function(tty,val){if(val===null||val===10){out(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},flush:function(tty){if(tty.output&&tty.output.length>0){out(UTF8ArrayToString(tty.output,0));tty.output=[]}}},default_tty1_ops:{put_char:function(tty,val){if(val===null||val===10){err(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},flush:function(tty){if(tty.output&&tty.output.length>0){err(UTF8ArrayToString(tty.output,0));tty.output=[]}}}};function mmapAlloc(size){var alignedSize=alignMemory(size,16384);var ptr=_malloc(alignedSize);while(size=newCapacity)return;var CAPACITY_DOUBLING_MAX=1024*1024;newCapacity=Math.max(newCapacity,prevCapacity*(prevCapacity>>0);if(prevCapacity!=0)newCapacity=Math.max(newCapacity,256);var oldContents=node.contents;node.contents=new Uint8Array(newCapacity);if(node.usedBytes>0)node.contents.set(oldContents.subarray(0,node.usedBytes),0);return},resizeFileStorage:function(node,newSize){if(node.usedBytes==newSize)return;if(newSize==0){node.contents=null;node.usedBytes=0;return}if(!node.contents||node.contents.subarray){var oldContents=node.contents;node.contents=new Uint8Array(newSize);if(oldContents){node.contents.set(oldContents.subarray(0,Math.min(newSize,node.usedBytes)))}node.usedBytes=newSize;return}if(!node.contents)node.contents=[];if(node.contents.length>newSize)node.contents.length=newSize;else while(node.contents.length=stream.node.usedBytes)return 0;var size=Math.min(stream.node.usedBytes-position,length);if(size>8&&contents.subarray){buffer.set(contents.subarray(position,position+size),offset)}else{for(var i=0;i0||position+length8){throw new FS.ErrnoError(32)}var parts=PATH.normalizeArray(path.split("/").filter(function(p){return!!p}),false);var current=FS.root;var current_path="/";for(var i=0;i40){throw new FS.ErrnoError(32)}}}}return{path:current_path,node:current}},getPath:function(node){var path;while(true){if(FS.isRoot(node)){var mount=node.mount.mountpoint;if(!path)return mount;return mount[mount.length-1]!=="/"?mount+"/"+path:mount+path}path=path?node.name+"/"+path:node.name;node=node.parent}},hashName:function(parentid,name){var hash=0;for(var i=0;i>>0)%FS.nameTable.length},hashAddNode:function(node){var hash=FS.hashName(node.parent.id,node.name);node.name_next=FS.nameTable[hash];FS.nameTable[hash]=node},hashRemoveNode:function(node){var hash=FS.hashName(node.parent.id,node.name);if(FS.nameTable[hash]===node){FS.nameTable[hash]=node.name_next}else{var current=FS.nameTable[hash];while(current){if(current.name_next===node){current.name_next=node.name_next;break}current=current.name_next}}},lookupNode:function(parent,name){var errCode=FS.mayLookup(parent);if(errCode){throw new FS.ErrnoError(errCode,parent)}var hash=FS.hashName(parent.id,name);for(var node=FS.nameTable[hash];node;node=node.name_next){var nodeName=node.name;if(node.parent.id===parent.id&&nodeName===name){return node}}return FS.lookup(parent,name)},createNode:function(parent,name,mode,rdev){var node=new FS.FSNode(parent,name,mode,rdev);FS.hashAddNode(node);return node},destroyNode:function(node){FS.hashRemoveNode(node)},isRoot:function(node){return node===node.parent},isMountpoint:function(node){return!!node.mounted},isFile:function(mode){return(mode&61440)===32768},isDir:function(mode){return(mode&61440)===16384},isLink:function(mode){return(mode&61440)===40960},isChrdev:function(mode){return(mode&61440)===8192},isBlkdev:function(mode){return(mode&61440)===24576},isFIFO:function(mode){return(mode&61440)===4096},isSocket:function(mode){return(mode&49152)===49152},flagModes:{"r":0,"r+":2,"w":577,"w+":578,"a":1089,"a+":1090},modeStringToFlags:function(str){var flags=FS.flagModes[str];if(typeof flags==="undefined"){throw new Error("Unknown file open mode: "+str)}return flags},flagsToPermissionString:function(flag){var perms=["r","w","rw"][flag&3];if(flag&512){perms+="w"}return perms},nodePermissions:function(node,perms){if(FS.ignorePermissions){return 0}if(perms.indexOf("r")!==-1&&!(node.mode&292)){return 2}else if(perms.indexOf("w")!==-1&&!(node.mode&146)){return 2}else if(perms.indexOf("x")!==-1&&!(node.mode&73)){return 2}return 0},mayLookup:function(dir){var errCode=FS.nodePermissions(dir,"x");if(errCode)return errCode;if(!dir.node_ops.lookup)return 2;return 0},mayCreate:function(dir,name){try{var node=FS.lookupNode(dir,name);return 20}catch(e){}return FS.nodePermissions(dir,"wx")},mayDelete:function(dir,name,isdir){var node;try{node=FS.lookupNode(dir,name)}catch(e){return e.errno}var errCode=FS.nodePermissions(dir,"wx");if(errCode){return errCode}if(isdir){if(!FS.isDir(node.mode)){return 54}if(FS.isRoot(node)||FS.getPath(node)===FS.cwd()){return 10}}else{if(FS.isDir(node.mode)){return 31}}return 0},mayOpen:function(node,flags){if(!node){return 44}if(FS.isLink(node.mode)){return 32}else if(FS.isDir(node.mode)){if(FS.flagsToPermissionString(flags)!=="r"||flags&512){return 31}}return FS.nodePermissions(node,FS.flagsToPermissionString(flags))},MAX_OPEN_FDS:4096,nextfd:function(fd_start,fd_end){fd_start=fd_start||0;fd_end=fd_end||FS.MAX_OPEN_FDS;for(var fd=fd_start;fd<=fd_end;fd++){if(!FS.streams[fd]){return fd}}throw new FS.ErrnoError(33)},getStream:function(fd){return FS.streams[fd]},createStream:function(stream,fd_start,fd_end){if(!FS.FSStream){FS.FSStream=function(){};FS.FSStream.prototype={object:{get:function(){return this.node},set:function(val){this.node=val}},isRead:{get:function(){return(this.flags&2097155)!==1}},isWrite:{get:function(){return(this.flags&2097155)!==0}},isAppend:{get:function(){return this.flags&1024}}}}var newStream=new FS.FSStream;for(var p in stream){newStream[p]=stream[p]}stream=newStream;var fd=FS.nextfd(fd_start,fd_end);stream.fd=fd;FS.streams[fd]=stream;return stream},closeStream:function(fd){FS.streams[fd]=null},chrdev_stream_ops:{open:function(stream){var device=FS.getDevice(stream.node.rdev);stream.stream_ops=device.stream_ops;if(stream.stream_ops.open){stream.stream_ops.open(stream)}},llseek:function(){throw new FS.ErrnoError(70)}},major:function(dev){return dev>>8},minor:function(dev){return dev&255},makedev:function(ma,mi){return ma<<8|mi},registerDevice:function(dev,ops){FS.devices[dev]={stream_ops:ops}},getDevice:function(dev){return FS.devices[dev]},getMounts:function(mount){var mounts=[];var check=[mount];while(check.length){var m=check.pop();mounts.push(m);check.push.apply(check,m.mounts)}return mounts},syncfs:function(populate,callback){if(typeof populate==="function"){callback=populate;populate=false}FS.syncFSRequests++;if(FS.syncFSRequests>1){err("warning: "+FS.syncFSRequests+" FS.syncfs operations in flight at once, probably just doing extra work")}var mounts=FS.getMounts(FS.root.mount);var completed=0;function doCallback(errCode){FS.syncFSRequests--;return callback(errCode)}function done(errCode){if(errCode){if(!done.errored){done.errored=true;return doCallback(errCode)}return}if(++completed>=mounts.length){doCallback(null)}}mounts.forEach(function(mount){if(!mount.type.syncfs){return done(null)}mount.type.syncfs(mount,populate,done)})},mount:function(type,opts,mountpoint){var root=mountpoint==="/";var pseudo=!mountpoint;var node;if(root&&FS.root){throw new FS.ErrnoError(10)}else if(!root&&!pseudo){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});mountpoint=lookup.path;node=lookup.node;if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}if(!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}}var mount={type:type,opts:opts,mountpoint:mountpoint,mounts:[]};var mountRoot=type.mount(mount);mountRoot.mount=mount;mount.root=mountRoot;if(root){FS.root=mountRoot}else if(node){node.mounted=mount;if(node.mount){node.mount.mounts.push(mount)}}return mountRoot},unmount:function(mountpoint){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});if(!FS.isMountpoint(lookup.node)){throw new FS.ErrnoError(28)}var node=lookup.node;var mount=node.mounted;var mounts=FS.getMounts(mount);Object.keys(FS.nameTable).forEach(function(hash){var current=FS.nameTable[hash];while(current){var next=current.name_next;if(mounts.indexOf(current.mount)!==-1){FS.destroyNode(current)}current=next}});node.mounted=null;var idx=node.mount.mounts.indexOf(mount);node.mount.mounts.splice(idx,1)},lookup:function(parent,name){return parent.node_ops.lookup(parent,name)},mknod:function(path,mode,dev){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);if(!name||name==="."||name===".."){throw new FS.ErrnoError(28)}var errCode=FS.mayCreate(parent,name);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.mknod){throw new FS.ErrnoError(63)}return parent.node_ops.mknod(parent,name,mode,dev)},create:function(path,mode){mode=mode!==undefined?mode:438;mode&=4095;mode|=32768;return FS.mknod(path,mode,0)},mkdir:function(path,mode){mode=mode!==undefined?mode:511;mode&=511|512;mode|=16384;return FS.mknod(path,mode,0)},mkdirTree:function(path,mode){var dirs=path.split("/");var d="";for(var i=0;ithis.length-1||idx<0){return undefined}var chunkOffset=idx%this.chunkSize;var chunkNum=idx/this.chunkSize|0;return this.getter(chunkNum)[chunkOffset]};LazyUint8Array.prototype.setDataGetter=function LazyUint8Array_setDataGetter(getter){this.getter=getter};LazyUint8Array.prototype.cacheLength=function LazyUint8Array_cacheLength(){var xhr=new XMLHttpRequest;xhr.open("HEAD",url,false);xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);var datalength=Number(xhr.getResponseHeader("Content-length"));var header;var hasByteServing=(header=xhr.getResponseHeader("Accept-Ranges"))&&header==="bytes";var usesGzip=(header=xhr.getResponseHeader("Content-Encoding"))&&header==="gzip";var chunkSize=1024*1024;if(!hasByteServing)chunkSize=datalength;var doXHR=function(from,to){if(from>to)throw new Error("invalid range ("+from+", "+to+") or no bytes requested!");if(to>datalength-1)throw new Error("only "+datalength+" bytes available! programmer error!");var xhr=new XMLHttpRequest;xhr.open("GET",url,false);if(datalength!==chunkSize)xhr.setRequestHeader("Range","bytes="+from+"-"+to);if(typeof Uint8Array!="undefined")xhr.responseType="arraybuffer";if(xhr.overrideMimeType){xhr.overrideMimeType("text/plain; charset=x-user-defined")}xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);if(xhr.response!==undefined){return new Uint8Array(xhr.response||[])}else{return intArrayFromString(xhr.responseText||"",true)}};var lazyArray=this;lazyArray.setDataGetter(function(chunkNum){var start=chunkNum*chunkSize;var end=(chunkNum+1)*chunkSize-1;end=Math.min(end,datalength-1);if(typeof lazyArray.chunks[chunkNum]==="undefined"){lazyArray.chunks[chunkNum]=doXHR(start,end)}if(typeof lazyArray.chunks[chunkNum]==="undefined")throw new Error("doXHR failed!");return lazyArray.chunks[chunkNum]});if(usesGzip||!datalength){chunkSize=datalength=1;datalength=this.getter(0).length;chunkSize=datalength;out("LazyFiles on gzip forces download of the whole file when length is accessed")}this._length=datalength;this._chunkSize=chunkSize;this.lengthKnown=true};if(typeof XMLHttpRequest!=="undefined"){if(!ENVIRONMENT_IS_WORKER)throw"Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc";var lazyArray=new LazyUint8Array;Object.defineProperties(lazyArray,{length:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._length}},chunkSize:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._chunkSize}}});var properties={isDevice:false,contents:lazyArray}}else{var properties={isDevice:false,url:url}}var node=FS.createFile(parent,name,properties,canRead,canWrite);if(properties.contents){node.contents=properties.contents}else if(properties.url){node.contents=null;node.url=properties.url}Object.defineProperties(node,{usedBytes:{get:function(){return this.contents.length}}});var stream_ops={};var keys=Object.keys(node.stream_ops);keys.forEach(function(key){var fn=node.stream_ops[key];stream_ops[key]=function forceLoadLazyFile(){FS.forceLoadFile(node);return fn.apply(null,arguments)}});stream_ops.read=function stream_ops_read(stream,buffer,offset,length,position){FS.forceLoadFile(node);var contents=stream.node.contents;if(position>=contents.length)return 0;var size=Math.min(contents.length-position,length);if(contents.slice){for(var i=0;i>2]=stat.dev;HEAP32[buf+4>>2]=0;HEAP32[buf+8>>2]=stat.ino;HEAP32[buf+12>>2]=stat.mode;HEAP32[buf+16>>2]=stat.nlink;HEAP32[buf+20>>2]=stat.uid;HEAP32[buf+24>>2]=stat.gid;HEAP32[buf+28>>2]=stat.rdev;HEAP32[buf+32>>2]=0;tempI64=[stat.size>>>0,(tempDouble=stat.size,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+40>>2]=tempI64[0],HEAP32[buf+44>>2]=tempI64[1];HEAP32[buf+48>>2]=4096;HEAP32[buf+52>>2]=stat.blocks;HEAP32[buf+56>>2]=stat.atime.getTime()/1e3|0;HEAP32[buf+60>>2]=0;HEAP32[buf+64>>2]=stat.mtime.getTime()/1e3|0;HEAP32[buf+68>>2]=0;HEAP32[buf+72>>2]=stat.ctime.getTime()/1e3|0;HEAP32[buf+76>>2]=0;tempI64=[stat.ino>>>0,(tempDouble=stat.ino,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+80>>2]=tempI64[0],HEAP32[buf+84>>2]=tempI64[1];return 0},doMsync:function(addr,stream,len,flags,offset){var buffer=HEAPU8.slice(addr,addr+len);FS.msync(stream,buffer,offset,len,flags)},doMkdir:function(path,mode){path=PATH.normalize(path);if(path[path.length-1]==="/")path=path.substr(0,path.length-1);FS.mkdir(path,mode,0);return 0},doMknod:function(path,mode,dev){switch(mode&61440){case 32768:case 8192:case 24576:case 4096:case 49152:break;default:return-28}FS.mknod(path,mode,dev);return 0},doReadlink:function(path,buf,bufsize){if(bufsize<=0)return-28;var ret=FS.readlink(path);var len=Math.min(bufsize,lengthBytesUTF8(ret));var endChar=HEAP8[buf+len];stringToUTF8(ret,buf,bufsize+1);HEAP8[buf+len]=endChar;return len},doAccess:function(path,amode){if(amode&~7){return-28}var node;var lookup=FS.lookupPath(path,{follow:true});node=lookup.node;if(!node){return-44}var perms="";if(amode&4)perms+="r";if(amode&2)perms+="w";if(amode&1)perms+="x";if(perms&&FS.nodePermissions(node,perms)){return-2}return 0},doDup:function(path,flags,suggestFD){var suggest=FS.getStream(suggestFD);if(suggest)FS.close(suggest);return FS.open(path,flags,0,suggestFD,suggestFD).fd},doReadv:function(stream,iov,iovcnt,offset){var ret=0;for(var i=0;i>2];var len=HEAP32[iov+(i*8+4)>>2];var curr=FS.read(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>2];var len=HEAP32[iov+(i*8+4)>>2];var curr=FS.write(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr}return ret},varargs:undefined,get:function(){SYSCALLS.varargs+=4;var ret=HEAP32[SYSCALLS.varargs-4>>2];return ret},getStr:function(ptr){var ret=UTF8ToString(ptr);return ret},getStreamFromFD:function(fd){var stream=FS.getStream(fd);if(!stream)throw new FS.ErrnoError(8);return stream},get64:function(low,high){return low}};function ___sys_fcntl64(fd,cmd,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(cmd){case 0:{var arg=SYSCALLS.get();if(arg<0){return-28}var newStream;newStream=FS.open(stream.path,stream.flags,0,arg);return newStream.fd}case 1:case 2:return 0;case 3:return stream.flags;case 4:{var arg=SYSCALLS.get();stream.flags|=arg;return 0}case 12:{var arg=SYSCALLS.get();var offset=0;HEAP16[arg+offset>>1]=2;return 0}case 13:case 14:return 0;case 16:case 8:return-28;case 9:setErrNo(28);return-1;default:{return-28}}}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___sys_fstat64(fd,buf){try{var stream=SYSCALLS.getStreamFromFD(fd);return SYSCALLS.doStat(FS.stat,stream.path,buf)}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___sys_ioctl(fd,op,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(op){case 21509:case 21505:{if(!stream.tty)return-59;return 0}case 21510:case 21511:case 21512:case 21506:case 21507:case 21508:{if(!stream.tty)return-59;return 0}case 21519:{if(!stream.tty)return-59;var argp=SYSCALLS.get();HEAP32[argp>>2]=0;return 0}case 21520:{if(!stream.tty)return-59;return-28}case 21531:{var argp=SYSCALLS.get();return FS.ioctl(stream,op,argp)}case 21523:{if(!stream.tty)return-59;return 0}case 21524:{if(!stream.tty)return-59;return 0}default:abort("bad ioctl syscall "+op)}}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___sys_open(path,flags,varargs){SYSCALLS.varargs=varargs;try{var pathname=SYSCALLS.getStr(path);var mode=SYSCALLS.get();var stream=FS.open(pathname,flags,mode);return stream.fd}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___sys_stat64(path,buf){try{path=SYSCALLS.getStr(path);return SYSCALLS.doStat(FS.stat,path,buf)}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}var tupleRegistrations={};function runDestructors(destructors){while(destructors.length){var ptr=destructors.pop();var del=destructors.pop();del(ptr)}}function simpleReadValueFromPointer(pointer){return this["fromWireType"](HEAPU32[pointer>>2])}var awaitingDependencies={};var registeredTypes={};var typeDependencies={};var char_0=48;var char_9=57;function makeLegalFunctionName(name){if(undefined===name){return"_unknown"}name=name.replace(/[^a-zA-Z0-9_]/g,"$");var f=name.charCodeAt(0);if(f>=char_0&&f<=char_9){return"_"+name}else{return name}}function createNamedFunction(name,body){name=makeLegalFunctionName(name);return new Function("body","return function "+name+"() {\n"+' "use strict";'+" return body.apply(this, arguments);\n"+"};\n")(body)}function extendError(baseErrorType,errorName){var errorClass=createNamedFunction(errorName,function(message){this.name=errorName;this.message=message;var stack=new Error(message).stack;if(stack!==undefined){this.stack=this.toString()+"\n"+stack.replace(/^Error(:[^\n]*)?\n/,"")}});errorClass.prototype=Object.create(baseErrorType.prototype);errorClass.prototype.constructor=errorClass;errorClass.prototype.toString=function(){if(this.message===undefined){return this.name}else{return this.name+": "+this.message}};return errorClass}var InternalError=undefined;function throwInternalError(message){throw new InternalError(message)}function whenDependentTypesAreResolved(myTypes,dependentTypes,getTypeConverters){myTypes.forEach(function(type){typeDependencies[type]=dependentTypes});function onComplete(typeConverters){var myTypeConverters=getTypeConverters(typeConverters);if(myTypeConverters.length!==myTypes.length){throwInternalError("Mismatched type converter count")}for(var i=0;i>shift])},destructorFunction:null})}function ClassHandle_isAliasOf(other){if(!(this instanceof ClassHandle)){return false}if(!(other instanceof ClassHandle)){return false}var leftClass=this.$$.ptrType.registeredClass;var left=this.$$.ptr;var rightClass=other.$$.ptrType.registeredClass;var right=other.$$.ptr;while(leftClass.baseClass){left=leftClass.upcast(left);leftClass=leftClass.baseClass}while(rightClass.baseClass){right=rightClass.upcast(right);rightClass=rightClass.baseClass}return leftClass===rightClass&&left===right}function shallowCopyInternalPointer(o){return{count:o.count,deleteScheduled:o.deleteScheduled,preservePointerOnDelete:o.preservePointerOnDelete,ptr:o.ptr,ptrType:o.ptrType,smartPtr:o.smartPtr,smartPtrType:o.smartPtrType}}function throwInstanceAlreadyDeleted(obj){function getInstanceTypeName(handle){return handle.$$.ptrType.registeredClass.name}throwBindingError(getInstanceTypeName(obj)+" instance already deleted")}var finalizationGroup=false;function detachFinalizer(handle){}function runDestructor($$){if($$.smartPtr){$$.smartPtrType.rawDestructor($$.smartPtr)}else{$$.ptrType.registeredClass.rawDestructor($$.ptr)}}function releaseClassHandle($$){$$.count.value-=1;var toDelete=0===$$.count.value;if(toDelete){runDestructor($$)}}function attachFinalizer(handle){if("undefined"===typeof FinalizationGroup){attachFinalizer=function(handle){return handle};return handle}finalizationGroup=new FinalizationGroup(function(iter){for(var result=iter.next();!result.done;result=iter.next()){var $$=result.value;if(!$$.ptr){console.warn("object already deleted: "+$$.ptr)}else{releaseClassHandle($$)}}});attachFinalizer=function(handle){finalizationGroup.register(handle,handle.$$,handle.$$);return handle};detachFinalizer=function(handle){finalizationGroup.unregister(handle.$$)};return attachFinalizer(handle)}function ClassHandle_clone(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.preservePointerOnDelete){this.$$.count.value+=1;return this}else{var clone=attachFinalizer(Object.create(Object.getPrototypeOf(this),{$$:{value:shallowCopyInternalPointer(this.$$)}}));clone.$$.count.value+=1;clone.$$.deleteScheduled=false;return clone}}function ClassHandle_delete(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}detachFinalizer(this);releaseClassHandle(this.$$);if(!this.$$.preservePointerOnDelete){this.$$.smartPtr=undefined;this.$$.ptr=undefined}}function ClassHandle_isDeleted(){return!this.$$.ptr}var delayFunction=undefined;var deletionQueue=[];function flushPendingDeletes(){while(deletionQueue.length){var obj=deletionQueue.pop();obj.$$.deleteScheduled=false;obj["delete"]()}}function ClassHandle_deleteLater(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}deletionQueue.push(this);if(deletionQueue.length===1&&delayFunction){delayFunction(flushPendingDeletes)}this.$$.deleteScheduled=true;return this}function init_ClassHandle(){ClassHandle.prototype["isAliasOf"]=ClassHandle_isAliasOf;ClassHandle.prototype["clone"]=ClassHandle_clone;ClassHandle.prototype["delete"]=ClassHandle_delete;ClassHandle.prototype["isDeleted"]=ClassHandle_isDeleted;ClassHandle.prototype["deleteLater"]=ClassHandle_deleteLater}function ClassHandle(){}var registeredPointers={};function ensureOverloadTable(proto,methodName,humanName){if(undefined===proto[methodName].overloadTable){var prevFunc=proto[methodName];proto[methodName]=function(){if(!proto[methodName].overloadTable.hasOwnProperty(arguments.length)){throwBindingError("Function '"+humanName+"' called with an invalid number of arguments ("+arguments.length+") - expects one of ("+proto[methodName].overloadTable+")!")}return proto[methodName].overloadTable[arguments.length].apply(this,arguments)};proto[methodName].overloadTable=[];proto[methodName].overloadTable[prevFunc.argCount]=prevFunc}}function exposePublicSymbol(name,value,numArguments){if(Module.hasOwnProperty(name)){if(undefined===numArguments||undefined!==Module[name].overloadTable&&undefined!==Module[name].overloadTable[numArguments]){throwBindingError("Cannot register public name '"+name+"' twice")}ensureOverloadTable(Module,name,name);if(Module.hasOwnProperty(numArguments)){throwBindingError("Cannot register multiple overloads of a function with the same number of arguments ("+numArguments+")!")}Module[name].overloadTable[numArguments]=value}else{Module[name]=value;if(undefined!==numArguments){Module[name].numArguments=numArguments}}}function RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast){this.name=name;this.constructor=constructor;this.instancePrototype=instancePrototype;this.rawDestructor=rawDestructor;this.baseClass=baseClass;this.getActualType=getActualType;this.upcast=upcast;this.downcast=downcast;this.pureVirtualFunctions=[]}function upcastPointer(ptr,ptrClass,desiredClass){while(ptrClass!==desiredClass){if(!ptrClass.upcast){throwBindingError("Expected null or instance of "+desiredClass.name+", got an instance of "+ptrClass.name)}ptr=ptrClass.upcast(ptr);ptrClass=ptrClass.baseClass}return ptr}function constNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError("null is not a valid "+this.name)}return 0}if(!handle.$$){throwBindingError('Cannot pass "'+_embind_repr(handle)+'" as a '+this.name)}if(!handle.$$.ptr){throwBindingError("Cannot pass deleted object as a pointer of type "+this.name)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function genericPointerToWireType(destructors,handle){var ptr;if(handle===null){if(this.isReference){throwBindingError("null is not a valid "+this.name)}if(this.isSmartPointer){ptr=this.rawConstructor();if(destructors!==null){destructors.push(this.rawDestructor,ptr)}return ptr}else{return 0}}if(!handle.$$){throwBindingError('Cannot pass "'+_embind_repr(handle)+'" as a '+this.name)}if(!handle.$$.ptr){throwBindingError("Cannot pass deleted object as a pointer of type "+this.name)}if(!this.isConst&&handle.$$.ptrType.isConst){throwBindingError("Cannot convert argument of type "+(handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name)+" to parameter type "+this.name)}var handleClass=handle.$$.ptrType.registeredClass;ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);if(this.isSmartPointer){if(undefined===handle.$$.smartPtr){throwBindingError("Passing raw pointer to smart pointer is illegal")}switch(this.sharingPolicy){case 0:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{throwBindingError("Cannot convert argument of type "+(handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name)+" to parameter type "+this.name)}break;case 1:ptr=handle.$$.smartPtr;break;case 2:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{var clonedHandle=handle["clone"]();ptr=this.rawShare(ptr,__emval_register(function(){clonedHandle["delete"]()}));if(destructors!==null){destructors.push(this.rawDestructor,ptr)}}break;default:throwBindingError("Unsupporting sharing policy")}}return ptr}function nonConstNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError("null is not a valid "+this.name)}return 0}if(!handle.$$){throwBindingError('Cannot pass "'+_embind_repr(handle)+'" as a '+this.name)}if(!handle.$$.ptr){throwBindingError("Cannot pass deleted object as a pointer of type "+this.name)}if(handle.$$.ptrType.isConst){throwBindingError("Cannot convert argument of type "+handle.$$.ptrType.name+" to parameter type "+this.name)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function RegisteredPointer_getPointee(ptr){if(this.rawGetPointee){ptr=this.rawGetPointee(ptr)}return ptr}function RegisteredPointer_destructor(ptr){if(this.rawDestructor){this.rawDestructor(ptr)}}function RegisteredPointer_deleteObject(handle){if(handle!==null){handle["delete"]()}}function downcastPointer(ptr,ptrClass,desiredClass){if(ptrClass===desiredClass){return ptr}if(undefined===desiredClass.baseClass){return null}var rv=downcastPointer(ptr,ptrClass,desiredClass.baseClass);if(rv===null){return null}return desiredClass.downcast(rv)}function getInheritedInstanceCount(){return Object.keys(registeredInstances).length}function getLiveInheritedInstances(){var rv=[];for(var k in registeredInstances){if(registeredInstances.hasOwnProperty(k)){rv.push(registeredInstances[k])}}return rv}function setDelayFunction(fn){delayFunction=fn;if(deletionQueue.length&&delayFunction){delayFunction(flushPendingDeletes)}}function init_embind(){Module["getInheritedInstanceCount"]=getInheritedInstanceCount;Module["getLiveInheritedInstances"]=getLiveInheritedInstances;Module["flushPendingDeletes"]=flushPendingDeletes;Module["setDelayFunction"]=setDelayFunction}var registeredInstances={};function getBasestPointer(class_,ptr){if(ptr===undefined){throwBindingError("ptr should not be undefined")}while(class_.baseClass){ptr=class_.upcast(ptr);class_=class_.baseClass}return ptr}function getInheritedInstance(class_,ptr){ptr=getBasestPointer(class_,ptr);return registeredInstances[ptr]}function makeClassHandle(prototype,record){if(!record.ptrType||!record.ptr){throwInternalError("makeClassHandle requires ptr and ptrType")}var hasSmartPtrType=!!record.smartPtrType;var hasSmartPtr=!!record.smartPtr;if(hasSmartPtrType!==hasSmartPtr){throwInternalError("Both smartPtrType and smartPtr must be specified")}record.count={value:1};return attachFinalizer(Object.create(prototype,{$$:{value:record}}))}function RegisteredPointer_fromWireType(ptr){var rawPointer=this.getPointee(ptr);if(!rawPointer){this.destructor(ptr);return null}var registeredInstance=getInheritedInstance(this.registeredClass,rawPointer);if(undefined!==registeredInstance){if(0===registeredInstance.$$.count.value){registeredInstance.$$.ptr=rawPointer;registeredInstance.$$.smartPtr=ptr;return registeredInstance["clone"]()}else{var rv=registeredInstance["clone"]();this.destructor(ptr);return rv}}function makeDefaultHandle(){if(this.isSmartPointer){return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this.pointeeType,ptr:rawPointer,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this,ptr:ptr})}}var actualType=this.registeredClass.getActualType(rawPointer);var registeredPointerRecord=registeredPointers[actualType];if(!registeredPointerRecord){return makeDefaultHandle.call(this)}var toType;if(this.isConst){toType=registeredPointerRecord.constPointerType}else{toType=registeredPointerRecord.pointerType}var dp=downcastPointer(rawPointer,this.registeredClass,toType.registeredClass);if(dp===null){return makeDefaultHandle.call(this)}if(this.isSmartPointer){return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp})}}function init_RegisteredPointer(){RegisteredPointer.prototype.getPointee=RegisteredPointer_getPointee;RegisteredPointer.prototype.destructor=RegisteredPointer_destructor;RegisteredPointer.prototype["argPackAdvance"]=8;RegisteredPointer.prototype["readValueFromPointer"]=simpleReadValueFromPointer;RegisteredPointer.prototype["deleteObject"]=RegisteredPointer_deleteObject;RegisteredPointer.prototype["fromWireType"]=RegisteredPointer_fromWireType}function RegisteredPointer(name,registeredClass,isReference,isConst,isSmartPointer,pointeeType,sharingPolicy,rawGetPointee,rawConstructor,rawShare,rawDestructor){this.name=name;this.registeredClass=registeredClass;this.isReference=isReference;this.isConst=isConst;this.isSmartPointer=isSmartPointer;this.pointeeType=pointeeType;this.sharingPolicy=sharingPolicy;this.rawGetPointee=rawGetPointee;this.rawConstructor=rawConstructor;this.rawShare=rawShare;this.rawDestructor=rawDestructor;if(!isSmartPointer&®isteredClass.baseClass===undefined){if(isConst){this["toWireType"]=constNoSmartPtrRawPointerToWireType;this.destructorFunction=null}else{this["toWireType"]=nonConstNoSmartPtrRawPointerToWireType;this.destructorFunction=null}}else{this["toWireType"]=genericPointerToWireType}}function replacePublicSymbol(name,value,numArguments){if(!Module.hasOwnProperty(name)){throwInternalError("Replacing nonexistant public symbol")}if(undefined!==Module[name].overloadTable&&undefined!==numArguments){Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}}function dynCallLegacy(sig,ptr,args){if(args&&args.length){return Module["dynCall_"+sig].apply(null,[ptr].concat(args))}return Module["dynCall_"+sig].call(null,ptr)}function dynCall(sig,ptr,args){if(sig.indexOf("j")!=-1){return dynCallLegacy(sig,ptr,args)}return wasmTable.get(ptr).apply(null,args)}function getDynCaller(sig,ptr){assert(sig.indexOf("j")>=0,"getDynCaller should only be called with i64 sigs");var argCache=[];return function(){argCache.length=arguments.length;for(var i=0;i0?", ":"")+argsListWired}invokerFnBody+=(returns?"var rv = ":"")+"invoker(fn"+(argsListWired.length>0?", ":"")+argsListWired+");\n";if(needsDestructorStack){invokerFnBody+="runDestructors(destructors);\n"}else{for(var i=isClassMethodFunc?1:2;i>2)+i])}return array}function __embind_register_class_class_function(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,fn){var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=readLatin1String(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker);whenDependentTypesAreResolved([],[rawClassType],function(classType){classType=classType[0];var humanName=classType.name+"."+methodName;function unboundTypesHandler(){throwUnboundTypeError("Cannot call "+humanName+" due to unbound types",rawArgTypes)}var proto=classType.registeredClass.constructor;if(undefined===proto[methodName]){unboundTypesHandler.argCount=argCount-1;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-1]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,function(argTypes){var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));var func=craftInvokerFunction(humanName,invokerArgsArray,null,rawInvoker,fn);if(undefined===proto[methodName].overloadTable){func.argCount=argCount-1;proto[methodName]=func}else{proto[methodName].overloadTable[argCount-1]=func}return[]});return[]})}function __embind_register_class_constructor(rawClassType,argCount,rawArgTypesAddr,invokerSignature,invoker,rawConstructor){assert(argCount>0);var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);invoker=embind__requireFunction(invokerSignature,invoker);var args=[rawConstructor];var destructors=[];whenDependentTypesAreResolved([],[rawClassType],function(classType){classType=classType[0];var humanName="constructor "+classType.name;if(undefined===classType.registeredClass.constructor_body){classType.registeredClass.constructor_body=[]}if(undefined!==classType.registeredClass.constructor_body[argCount-1]){throw new BindingError("Cannot register multiple constructors with identical number of parameters ("+(argCount-1)+") for class '"+classType.name+"'! Overload resolution is currently only performed using the parameter count, not actual type info!")}classType.registeredClass.constructor_body[argCount-1]=function unboundTypeHandler(){throwUnboundTypeError("Cannot construct "+classType.name+" due to unbound types",rawArgTypes)};whenDependentTypesAreResolved([],rawArgTypes,function(argTypes){classType.registeredClass.constructor_body[argCount-1]=function constructor_body(){if(arguments.length!==argCount-1){throwBindingError(humanName+" called with "+arguments.length+" arguments, expected "+(argCount-1))}destructors.length=0;args.length=argCount;for(var i=1;i4&&0===--emval_handle_array[handle].refcount){emval_handle_array[handle]=undefined;emval_free_list.push(handle)}}function count_emval_handles(){var count=0;for(var i=5;i>1])};case 2:return function(pointer){var heap=signed?HEAP32:HEAPU32;return this["fromWireType"](heap[pointer>>2])};default:throw new TypeError("Unknown integer type: "+name)}}function __embind_register_enum(rawType,name,size,isSigned){var shift=getShiftFromSize(size);name=readLatin1String(name);function ctor(){}ctor.values={};registerType(rawType,{name:name,constructor:ctor,"fromWireType":function(c){return this.constructor.values[c]},"toWireType":function(destructors,c){return c.value},"argPackAdvance":8,"readValueFromPointer":enumReadValueFromPointer(name,shift,isSigned),destructorFunction:null});exposePublicSymbol(name,ctor)}function requireRegisteredType(rawType,humanName){var impl=registeredTypes[rawType];if(undefined===impl){throwBindingError(humanName+" has unknown type "+getTypeName(rawType))}return impl}function __embind_register_enum_value(rawEnumType,name,enumValue){var enumType=requireRegisteredType(rawEnumType,"enum");name=readLatin1String(name);var Enum=enumType.constructor;var Value=Object.create(enumType.constructor.prototype,{value:{value:enumValue},constructor:{value:createNamedFunction(enumType.name+"_"+name,function(){})}});Enum.values[enumValue]=Value;Enum[name]=Value}function _embind_repr(v){if(v===null){return"null"}var t=typeof v;if(t==="object"||t==="array"||t==="function"){return v.toString()}else{return""+v}}function floatReadValueFromPointer(name,shift){switch(shift){case 2:return function(pointer){return this["fromWireType"](HEAPF32[pointer>>2])};case 3:return function(pointer){return this["fromWireType"](HEAPF64[pointer>>3])};default:throw new TypeError("Unknown float type: "+name)}}function __embind_register_float(rawType,name,size){var shift=getShiftFromSize(size);name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":function(value){return value},"toWireType":function(destructors,value){if(typeof value!=="number"&&typeof value!=="boolean"){throw new TypeError('Cannot convert "'+_embind_repr(value)+'" to '+this.name)}return value},"argPackAdvance":8,"readValueFromPointer":floatReadValueFromPointer(name,shift),destructorFunction:null})}function integerReadValueFromPointer(name,shift,signed){switch(shift){case 0:return signed?function readS8FromPointer(pointer){return HEAP8[pointer]}:function readU8FromPointer(pointer){return HEAPU8[pointer]};case 1:return signed?function readS16FromPointer(pointer){return HEAP16[pointer>>1]}:function readU16FromPointer(pointer){return HEAPU16[pointer>>1]};case 2:return signed?function readS32FromPointer(pointer){return HEAP32[pointer>>2]}:function readU32FromPointer(pointer){return HEAPU32[pointer>>2]};default:throw new TypeError("Unknown integer type: "+name)}}function __embind_register_integer(primitiveType,name,size,minRange,maxRange){name=readLatin1String(name);if(maxRange===-1){maxRange=4294967295}var shift=getShiftFromSize(size);var fromWireType=function(value){return value};if(minRange===0){var bitshift=32-8*size;fromWireType=function(value){return value<>>bitshift}}var isUnsignedType=name.indexOf("unsigned")!=-1;registerType(primitiveType,{name:name,"fromWireType":fromWireType,"toWireType":function(destructors,value){if(typeof value!=="number"&&typeof value!=="boolean"){throw new TypeError('Cannot convert "'+_embind_repr(value)+'" to '+this.name)}if(valuemaxRange){throw new TypeError('Passing a number "'+_embind_repr(value)+'" from JS side to C/C++ side to an argument of type "'+name+'", which is outside the valid range ['+minRange+", "+maxRange+"]!")}return isUnsignedType?value>>>0:value|0},"argPackAdvance":8,"readValueFromPointer":integerReadValueFromPointer(name,shift,minRange!==0),destructorFunction:null})}function __embind_register_memory_view(rawType,dataTypeIndex,name){var typeMapping=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array];var TA=typeMapping[dataTypeIndex];function decodeMemoryView(handle){handle=handle>>2;var heap=HEAPU32;var size=heap[handle];var data=heap[handle+1];return new TA(buffer,data,size)}name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":decodeMemoryView,"argPackAdvance":8,"readValueFromPointer":decodeMemoryView},{ignoreDuplicateRegistrations:true})}function __embind_register_std_string(rawType,name){name=readLatin1String(name);var stdStringIsUTF8=name==="std::string";registerType(rawType,{name:name,"fromWireType":function(value){var length=HEAPU32[value>>2];var str;if(stdStringIsUTF8){var decodeStartPtr=value+4;for(var i=0;i<=length;++i){var currentBytePtr=value+4+i;if(i==length||HEAPU8[currentBytePtr]==0){var maxRead=currentBytePtr-decodeStartPtr;var stringSegment=UTF8ToString(decodeStartPtr,maxRead);if(str===undefined){str=stringSegment}else{str+=String.fromCharCode(0);str+=stringSegment}decodeStartPtr=currentBytePtr+1}}}else{var a=new Array(length);for(var i=0;i>2]=length;if(stdStringIsUTF8&&valueIsOfTypeString){stringToUTF8(value,ptr+4,length+1)}else{if(valueIsOfTypeString){for(var i=0;i255){_free(ptr);throwBindingError("String has UTF-16 code units that do not fit in 8 bits")}HEAPU8[ptr+4+i]=charCode}}else{for(var i=0;i>2];var HEAP=getHeap();var str;var decodeStartPtr=value+4;for(var i=0;i<=length;++i){var currentBytePtr=value+4+i*charSize;if(i==length||HEAP[currentBytePtr>>shift]==0){var maxReadBytes=currentBytePtr-decodeStartPtr;var stringSegment=decodeString(decodeStartPtr,maxReadBytes);if(str===undefined){str=stringSegment}else{str+=String.fromCharCode(0);str+=stringSegment}decodeStartPtr=currentBytePtr+charSize}}_free(value);return str},"toWireType":function(destructors,value){if(!(typeof value==="string")){throwBindingError("Cannot pass non-string to C++ string type "+name)}var length=lengthBytesUTF(value);var ptr=_malloc(4+length+charSize);HEAPU32[ptr>>2]=length>>shift;encodeString(value,ptr+4,length+charSize);if(destructors!==null){destructors.push(_free,ptr)}return ptr},"argPackAdvance":8,"readValueFromPointer":simpleReadValueFromPointer,destructorFunction:function(ptr){_free(ptr)}})}function __embind_register_value_array(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor){tupleRegistrations[rawType]={name:readLatin1String(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),elements:[]}}function __embind_register_value_array_element(rawTupleType,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext){tupleRegistrations[rawTupleType].elements.push({getterReturnType:getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext:getterContext,setterArgumentType:setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext:setterContext})}function __embind_register_value_object(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor){structRegistrations[rawType]={name:readLatin1String(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),fields:[]}}function __embind_register_value_object_field(structType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext){structRegistrations[structType].fields.push({fieldName:readLatin1String(fieldName),getterReturnType:getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext:getterContext,setterArgumentType:setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext:setterContext})}function __embind_register_void(rawType,name){name=readLatin1String(name);registerType(rawType,{isVoid:true,name:name,"argPackAdvance":0,"fromWireType":function(){return undefined},"toWireType":function(destructors,o){return undefined}})}function requireHandle(handle){if(!handle){throwBindingError("Cannot use deleted val. handle = "+handle)}return emval_handle_array[handle].value}function __emval_as(handle,returnType,destructorsRef){handle=requireHandle(handle);returnType=requireRegisteredType(returnType,"emval::as");var destructors=[];var rd=__emval_register(destructors);HEAP32[destructorsRef>>2]=rd;return returnType["toWireType"](destructors,handle)}function __emval_allocateDestructors(destructorsRef){var destructors=[];HEAP32[destructorsRef>>2]=__emval_register(destructors);return destructors}var emval_symbols={};function getStringOrSymbol(address){var symbol=emval_symbols[address];if(symbol===undefined){return readLatin1String(address)}else{return symbol}}var emval_methodCallers=[];function __emval_call_method(caller,handle,methodName,destructorsRef,args){caller=emval_methodCallers[caller];handle=requireHandle(handle);methodName=getStringOrSymbol(methodName);return caller(handle,methodName,__emval_allocateDestructors(destructorsRef),args)}function __emval_call_void_method(caller,handle,methodName,args){caller=emval_methodCallers[caller];handle=requireHandle(handle);methodName=getStringOrSymbol(methodName);caller(handle,methodName,null,args)}function __emval_equals(first,second){first=requireHandle(first);second=requireHandle(second);return first==second}function emval_get_global(){if(typeof globalThis==="object"){return globalThis}return function(){return Function}()("return this")()}function __emval_get_global(name){if(name===0){return __emval_register(emval_get_global())}else{name=getStringOrSymbol(name);return __emval_register(emval_get_global()[name])}}function __emval_addMethodCaller(caller){var id=emval_methodCallers.length;emval_methodCallers.push(caller);return id}function __emval_lookupTypes(argCount,argTypes){var a=new Array(argCount);for(var i=0;i>2)+i],"parameter "+i)}return a}function __emval_get_method_caller(argCount,argTypes){var types=__emval_lookupTypes(argCount,argTypes);var retType=types[0];var signatureName=retType.name+"_$"+types.slice(1).map(function(t){return t.name}).join("_")+"$";var params=["retType"];var args=[retType];var argsList="";for(var i=0;i4){emval_handle_array[handle].refcount+=1}}function __emval_instanceof(object,constructor){object=requireHandle(object);constructor=requireHandle(constructor);return object instanceof constructor}function __emval_is_number(handle){handle=requireHandle(handle);return typeof handle==="number"}function __emval_is_string(handle){handle=requireHandle(handle);return typeof handle==="string"}function craftEmvalAllocator(argCount){var argsList="";for(var i=0;i>> 2) + "+i+'], "parameter '+i+'");\n'+"var arg"+i+" = argType"+i+".readValueFromPointer(args);\n"+"args += argType"+i+"['argPackAdvance'];\n"}functionBody+="var obj = new constructor("+argsList+");\n"+"return __emval_register(obj);\n"+"}\n";return new Function("requireRegisteredType","Module","__emval_register",functionBody)(requireRegisteredType,Module,__emval_register)}var emval_newers={};function __emval_new(handle,argCount,argTypes,args){handle=requireHandle(handle);var newer=emval_newers[argCount];if(!newer){newer=craftEmvalAllocator(argCount);emval_newers[argCount]=newer}return newer(handle,argTypes,args)}function __emval_new_array(){return __emval_register([])}function __emval_new_cstring(v){return __emval_register(getStringOrSymbol(v))}function __emval_new_object(){return __emval_register({})}function __emval_run_destructors(handle){var destructors=emval_handle_array[handle].value;runDestructors(destructors);__emval_decref(handle)}function __emval_set_property(handle,key,value){handle=requireHandle(handle);key=requireHandle(key);value=requireHandle(value);handle[key]=value}function __emval_take_value(type,argv){type=requireRegisteredType(type,"_emval_take_value");var v=type["readValueFromPointer"](argv);return __emval_register(v)}function _abort(){abort()}function _emscripten_memcpy_big(dest,src,num){HEAPU8.copyWithin(dest,src,src+num)}function _emscripten_get_heap_size(){return HEAPU8.length}function emscripten_realloc_buffer(size){try{wasmMemory.grow(size-buffer.byteLength+65535>>>16);updateGlobalBufferAndViews(wasmMemory.buffer);return 1}catch(e){}}function _emscripten_resize_heap(requestedSize){requestedSize=requestedSize>>>0;var oldSize=_emscripten_get_heap_size();var maxHeapSize=2147483648;if(requestedSize>maxHeapSize){return false}var minHeapSize=16777216;for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignUp(Math.max(minHeapSize,requestedSize,overGrownHeapSize),65536));var replacement=emscripten_realloc_buffer(newSize);if(replacement){return true}}return false}var _emscripten_get_now;if(ENVIRONMENT_IS_NODE){_emscripten_get_now=function(){var t=process["hrtime"]();return t[0]*1e3+t[1]/1e6}}else if(typeof dateNow!=="undefined"){_emscripten_get_now=dateNow}else _emscripten_get_now=function(){return performance.now()};function _emscripten_thread_sleep(msecs){var start=_emscripten_get_now();while(_emscripten_get_now()-start>2]=strings.length;var bufSize=0;strings.forEach(function(string){bufSize+=string.length+1});HEAP32[penviron_buf_size>>2]=bufSize;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _fd_close(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);FS.close(stream);return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _fd_fdstat_get(fd,pbuf){try{var stream=SYSCALLS.getStreamFromFD(fd);var type=stream.tty?2:FS.isDir(stream.mode)?3:FS.isLink(stream.mode)?7:4;HEAP8[pbuf>>0]=type;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _fd_read(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=SYSCALLS.doReadv(stream,iov,iovcnt);HEAP32[pnum>>2]=num;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _fd_seek(fd,offset_low,offset_high,whence,newOffset){try{var stream=SYSCALLS.getStreamFromFD(fd);var HIGH_OFFSET=4294967296;var offset=offset_high*HIGH_OFFSET+(offset_low>>>0);var DOUBLE_LIMIT=9007199254740992;if(offset<=-DOUBLE_LIMIT||offset>=DOUBLE_LIMIT){return-61}FS.llseek(stream,offset,whence);tempI64=[stream.position>>>0,(tempDouble=stream.position,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[newOffset>>2]=tempI64[0],HEAP32[newOffset+4>>2]=tempI64[1];if(stream.getdents&&offset===0&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _fd_write(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=SYSCALLS.doWritev(stream,iov,iovcnt);HEAP32[pnum>>2]=num;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _setTempRet0($i){setTempRet0($i|0)}function _time(ptr){var ret=Date.now()/1e3|0;if(ptr){HEAP32[ptr>>2]=ret}return ret}function _uuid_generate(out){var uuid=null;if(ENVIRONMENT_IS_NODE){try{var rb=require("crypto")["randomBytes"];uuid=rb(16)}catch(e){}}else if(ENVIRONMENT_IS_WEB&&typeof window.crypto!=="undefined"&&typeof window.crypto.getRandomValues!=="undefined"){uuid=new Uint8Array(16);window.crypto.getRandomValues(uuid)}if(!uuid){uuid=new Array(16);var d=(new Date).getTime();for(var i=0;i<16;i++){var r=(d+Math.random()*256)%256|0;d=d/256|0;uuid[i]=r}}uuid[6]=uuid[6]&15|64;uuid[8]=uuid[8]&127|128;writeArrayToMemory(uuid,out)}var FSNode=function(parent,name,mode,rdev){if(!parent){parent=this}this.parent=parent;this.mount=parent.mount;this.mounted=null;this.id=FS.nextInode++;this.name=name;this.mode=mode;this.node_ops={};this.stream_ops={};this.rdev=rdev};var readMode=292|73;var writeMode=146;Object.defineProperties(FSNode.prototype,{read:{get:function(){return(this.mode&readMode)===readMode},set:function(val){val?this.mode|=readMode:this.mode&=~readMode}},write:{get:function(){return(this.mode&writeMode)===writeMode},set:function(val){val?this.mode|=writeMode:this.mode&=~writeMode}},isFolder:{get:function(){return FS.isDir(this.mode)}},isDevice:{get:function(){return FS.isChrdev(this.mode)}}});FS.FSNode=FSNode;FS.staticInit();InternalError=Module["InternalError"]=extendError(Error,"InternalError");embind_init_charCodes();BindingError=Module["BindingError"]=extendError(Error,"BindingError");init_ClassHandle();init_RegisteredPointer();init_embind();UnboundTypeError=Module["UnboundTypeError"]=extendError(Error,"UnboundTypeError");init_emval();var ASSERTIONS=false;function intArrayFromString(stringy,dontAddNull,length){var len=length>0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array}__ATINIT__.push({func:function(){___wasm_call_ctors()}});var asmLibraryArg={"__cxa_allocate_exception":___cxa_allocate_exception,"__cxa_atexit":___cxa_atexit,"__cxa_throw":___cxa_throw,"__gmtime_r":___gmtime_r,"__sys_fcntl64":___sys_fcntl64,"__sys_fstat64":___sys_fstat64,"__sys_ioctl":___sys_ioctl,"__sys_open":___sys_open,"__sys_stat64":___sys_stat64,"_embind_finalize_value_array":__embind_finalize_value_array,"_embind_finalize_value_object":__embind_finalize_value_object,"_embind_register_bool":__embind_register_bool,"_embind_register_class":__embind_register_class,"_embind_register_class_class_function":__embind_register_class_class_function,"_embind_register_class_constructor":__embind_register_class_constructor,"_embind_register_class_function":__embind_register_class_function,"_embind_register_class_property":__embind_register_class_property,"_embind_register_emval":__embind_register_emval,"_embind_register_enum":__embind_register_enum,"_embind_register_enum_value":__embind_register_enum_value,"_embind_register_float":__embind_register_float,"_embind_register_integer":__embind_register_integer,"_embind_register_memory_view":__embind_register_memory_view,"_embind_register_std_string":__embind_register_std_string,"_embind_register_std_wstring":__embind_register_std_wstring,"_embind_register_value_array":__embind_register_value_array,"_embind_register_value_array_element":__embind_register_value_array_element,"_embind_register_value_object":__embind_register_value_object,"_embind_register_value_object_field":__embind_register_value_object_field,"_embind_register_void":__embind_register_void,"_emval_as":__emval_as,"_emval_call_method":__emval_call_method,"_emval_call_void_method":__emval_call_void_method,"_emval_decref":__emval_decref,"_emval_equals":__emval_equals,"_emval_get_global":__emval_get_global,"_emval_get_method_caller":__emval_get_method_caller,"_emval_get_module_property":__emval_get_module_property,"_emval_get_property":__emval_get_property,"_emval_incref":__emval_incref,"_emval_instanceof":__emval_instanceof,"_emval_is_number":__emval_is_number,"_emval_is_string":__emval_is_string,"_emval_new":__emval_new,"_emval_new_array":__emval_new_array,"_emval_new_cstring":__emval_new_cstring,"_emval_new_object":__emval_new_object,"_emval_run_destructors":__emval_run_destructors,"_emval_set_property":__emval_set_property,"_emval_take_value":__emval_take_value,"abort":_abort,"emscripten_memcpy_big":_emscripten_memcpy_big,"emscripten_resize_heap":_emscripten_resize_heap,"emscripten_thread_sleep":_emscripten_thread_sleep,"environ_get":_environ_get,"environ_sizes_get":_environ_sizes_get,"fd_close":_fd_close,"fd_fdstat_get":_fd_fdstat_get,"fd_read":_fd_read,"fd_seek":_fd_seek,"fd_write":_fd_write,"setTempRet0":_setTempRet0,"time":_time,"uuid_generate":_uuid_generate};var asm=createWasm();var ___wasm_call_ctors=Module["___wasm_call_ctors"]=function(){return(___wasm_call_ctors=Module["___wasm_call_ctors"]=Module["asm"]["__wasm_call_ctors"]).apply(null,arguments)};var _malloc=Module["_malloc"]=function(){return(_malloc=Module["_malloc"]=Module["asm"]["malloc"]).apply(null,arguments)};var _free=Module["_free"]=function(){return(_free=Module["_free"]=Module["asm"]["free"]).apply(null,arguments)};var ___getTypeName=Module["___getTypeName"]=function(){return(___getTypeName=Module["___getTypeName"]=Module["asm"]["__getTypeName"]).apply(null,arguments)};var ___embind_register_native_and_builtin_types=Module["___embind_register_native_and_builtin_types"]=function(){return(___embind_register_native_and_builtin_types=Module["___embind_register_native_and_builtin_types"]=Module["asm"]["__embind_register_native_and_builtin_types"]).apply(null,arguments)};var ___errno_location=Module["___errno_location"]=function(){return(___errno_location=Module["___errno_location"]=Module["asm"]["__errno_location"]).apply(null,arguments)};var stackSave=Module["stackSave"]=function(){return(stackSave=Module["stackSave"]=Module["asm"]["stackSave"]).apply(null,arguments)};var stackRestore=Module["stackRestore"]=function(){return(stackRestore=Module["stackRestore"]=Module["asm"]["stackRestore"]).apply(null,arguments)};var stackAlloc=Module["stackAlloc"]=function(){return(stackAlloc=Module["stackAlloc"]=Module["asm"]["stackAlloc"]).apply(null,arguments)};var _setThrew=Module["_setThrew"]=function(){return(_setThrew=Module["_setThrew"]=Module["asm"]["setThrew"]).apply(null,arguments)};var dynCall_ji=Module["dynCall_ji"]=function(){return(dynCall_ji=Module["dynCall_ji"]=Module["asm"]["dynCall_ji"]).apply(null,arguments)};var dynCall_jiji=Module["dynCall_jiji"]=function(){return(dynCall_jiji=Module["dynCall_jiji"]=Module["asm"]["dynCall_jiji"]).apply(null,arguments)};var calledRun;function ExitStatus(status){this.name="ExitStatus";this.message="Program terminated with exit("+status+")";this.status=status}dependenciesFulfilled=function runCaller(){if(!calledRun)run();if(!calledRun)dependenciesFulfilled=runCaller};function run(args){args=args||arguments_;if(runDependencies>0){return}preRun();if(runDependencies>0)return;function doRun(){if(calledRun)return;calledRun=true;Module["calledRun"]=true;if(ABORT)return;initRuntime();preMain();readyPromiseResolve(Module);if(Module["onRuntimeInitialized"])Module["onRuntimeInitialized"]();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(function(){setTimeout(function(){Module["setStatus"]("")},1);doRun()},1)}else{doRun()}}Module["run"]=run;if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}noExitRuntime=true;run(); +var Module=typeof rhino3dm!="undefined"?rhino3dm:{};var readyPromiseResolve,readyPromiseReject;Module["ready"]=new Promise(function(resolve,reject){readyPromiseResolve=resolve;readyPromiseReject=reject});var moduleOverrides=Object.assign({},Module);var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var ENVIRONMENT_IS_WEB=typeof window=="object";var ENVIRONMENT_IS_WORKER=typeof importScripts=="function";var ENVIRONMENT_IS_NODE=typeof process=="object"&&typeof process.versions=="object"&&typeof process.versions.node=="string";var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var read_,readAsync,readBinary,setWindowTitle;function logExceptionOnExit(e){if(e instanceof ExitStatus)return;let toLog=e;err("exiting due to exception: "+toLog)}if(ENVIRONMENT_IS_NODE){const{createRequire:createRequire}=await import("module");var require=createRequire(import.meta.url);var fs=require("fs");var nodePath=require("path");if(ENVIRONMENT_IS_WORKER){scriptDirectory=nodePath.dirname(scriptDirectory)+"/"}else{scriptDirectory=require("url").fileURLToPath(new URL("./",import.meta.url))}read_=(filename,binary)=>{filename=isFileURI(filename)?new URL(filename):nodePath.normalize(filename);return fs.readFileSync(filename,binary?undefined:"utf8")};readBinary=filename=>{var ret=read_(filename,true);if(!ret.buffer){ret=new Uint8Array(ret)}return ret};readAsync=(filename,onload,onerror)=>{filename=isFileURI(filename)?new URL(filename):nodePath.normalize(filename);fs.readFile(filename,function(err,data){if(err)onerror(err);else onload(data.buffer)})};if(process["argv"].length>1){thisProgram=process["argv"][1].replace(/\\/g,"/")}arguments_=process["argv"].slice(2);process["on"]("uncaughtException",function(ex){if(!(ex instanceof ExitStatus)){throw ex}});process["on"]("unhandledRejection",function(reason){throw reason});quit_=(status,toThrow)=>{if(keepRuntimeAlive()){process["exitCode"]=status;throw toThrow}logExceptionOnExit(toThrow);process["exit"](status)};Module["inspect"]=function(){return"[Emscripten Module object]"}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(_scriptDir){scriptDirectory=_scriptDir}if(scriptDirectory.indexOf("blob:")!==0){scriptDirectory=scriptDirectory.substr(0,scriptDirectory.replace(/[?#].*/,"").lastIndexOf("/")+1)}else{scriptDirectory=""}{read_=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=(url,onload,onerror)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}onerror()};xhr.onerror=onerror;xhr.send(null)}}setWindowTitle=title=>document.title=title}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.warn.bind(console);Object.assign(Module,moduleOverrides);moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var POINTER_SIZE=4;var wasmBinary;if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];var noExitRuntime=Module["noExitRuntime"]||true;if(typeof WebAssembly!="object"){abort("no native wasm support detected")}var wasmMemory;var ABORT=false;var EXITSTATUS;function assert(condition,text){if(!condition){abort(text)}}var UTF8Decoder=typeof TextDecoder!="undefined"?new TextDecoder("utf8"):undefined;function UTF8ArrayToString(heapOrArray,idx,maxBytesToRead){var endIdx=idx+maxBytesToRead;var endPtr=idx;while(heapOrArray[endPtr]&&!(endPtr>=endIdx))++endPtr;if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx>10,56320|ch&1023)}}return str}function UTF8ToString(ptr,maxBytesToRead){return ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):""}function stringToUTF8Array(str,heap,outIdx,maxBytesToWrite){if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}}heap[outIdx]=0;return outIdx-startIdx}function stringToUTF8(str,outPtr,maxBytesToWrite){return stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite)}function lengthBytesUTF8(str){var len=0;for(var i=0;i=55296&&c<=57343){len+=4;++i}else{len+=3}}return len}var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateMemoryViews(){var b=wasmMemory.buffer;Module["HEAP8"]=HEAP8=new Int8Array(b);Module["HEAP16"]=HEAP16=new Int16Array(b);Module["HEAP32"]=HEAP32=new Int32Array(b);Module["HEAPU8"]=HEAPU8=new Uint8Array(b);Module["HEAPU16"]=HEAPU16=new Uint16Array(b);Module["HEAPU32"]=HEAPU32=new Uint32Array(b);Module["HEAPF32"]=HEAPF32=new Float32Array(b);Module["HEAPF64"]=HEAPF64=new Float64Array(b)}var INITIAL_MEMORY=Module["INITIAL_MEMORY"]||16777216;var wasmTable;var __ATPRERUN__=[];var __ATINIT__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;function keepRuntimeAlive(){return noExitRuntime}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;SOCKFS.root=FS.mount(SOCKFS,{},null);if(!Module["noFSInit"]&&!FS.init.initialized)FS.init();FS.ignorePermissions=false;TTY.init();callRuntimeCallbacks(__ATINIT__)}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnInit(cb){__ATINIT__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function getUniqueRunDependency(id){return id}function addRunDependency(id){runDependencies++;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}}function removeRunDependency(id){runDependencies--;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}function abort(what){if(Module["onAbort"]){Module["onAbort"](what)}what="Aborted("+what+")";err(what);ABORT=true;EXITSTATUS=1;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject(e);throw e}var dataURIPrefix="data:application/octet-stream;base64,";function isDataURI(filename){return filename.startsWith(dataURIPrefix)}function isFileURI(filename){return filename.startsWith("file://")}var wasmBinaryFile;if(Module["locateFile"]){wasmBinaryFile="rhino3dm.wasm";if(!isDataURI(wasmBinaryFile)){wasmBinaryFile=locateFile(wasmBinaryFile)}}else{wasmBinaryFile=new URL("rhino3dm.wasm",import.meta.url).href}function getBinary(file){try{if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}catch(err){abort(err)}}function getBinaryPromise(){if(!wasmBinary&&(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER)){if(typeof fetch=="function"&&!isFileURI(wasmBinaryFile)){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){if(!response["ok"]){throw"failed to load wasm binary file at '"+wasmBinaryFile+"'"}return response["arrayBuffer"]()}).catch(function(){return getBinary(wasmBinaryFile)})}else{if(readAsync){return new Promise(function(resolve,reject){readAsync(wasmBinaryFile,function(response){resolve(new Uint8Array(response))},reject)})}}}return Promise.resolve().then(function(){return getBinary(wasmBinaryFile)})}function createWasm(){var info={"a":asmLibraryArg};function receiveInstance(instance,module){var exports=instance.exports;Module["asm"]=exports;wasmMemory=Module["asm"]["oa"];updateMemoryViews();wasmTable=Module["asm"]["qa"];addOnInit(Module["asm"]["pa"]);removeRunDependency("wasm-instantiate")}addRunDependency("wasm-instantiate");function receiveInstantiationResult(result){receiveInstance(result["instance"])}function instantiateArrayBuffer(receiver){return getBinaryPromise().then(function(binary){return WebAssembly.instantiate(binary,info)}).then(function(instance){return instance}).then(receiver,function(reason){err("failed to asynchronously prepare wasm: "+reason);abort(reason)})}function instantiateAsync(){if(!wasmBinary&&typeof WebAssembly.instantiateStreaming=="function"&&!isDataURI(wasmBinaryFile)&&!isFileURI(wasmBinaryFile)&&!ENVIRONMENT_IS_NODE&&typeof fetch=="function"){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){var result=WebAssembly.instantiateStreaming(response,info);return result.then(receiveInstantiationResult,function(reason){err("wasm streaming compile failed: "+reason);err("falling back to ArrayBuffer instantiation");return instantiateArrayBuffer(receiveInstantiationResult)})})}else{return instantiateArrayBuffer(receiveInstantiationResult)}}if(Module["instantiateWasm"]){try{var exports=Module["instantiateWasm"](info,receiveInstance);return exports}catch(e){err("Module.instantiateWasm callback failed with error: "+e);readyPromiseReject(e)}}instantiateAsync().catch(readyPromiseReject);return{}}var tempDouble;var tempI64;function ExitStatus(status){this.name="ExitStatus";this.message="Program terminated with exit("+status+")";this.status=status}function callRuntimeCallbacks(callbacks){while(callbacks.length>0){callbacks.shift()(Module)}}function ExceptionInfo(excPtr){this.excPtr=excPtr;this.ptr=excPtr-24;this.set_type=function(type){HEAPU32[this.ptr+4>>2]=type};this.get_type=function(){return HEAPU32[this.ptr+4>>2]};this.set_destructor=function(destructor){HEAPU32[this.ptr+8>>2]=destructor};this.get_destructor=function(){return HEAPU32[this.ptr+8>>2]};this.set_refcount=function(refcount){HEAP32[this.ptr>>2]=refcount};this.set_caught=function(caught){caught=caught?1:0;HEAP8[this.ptr+12>>0]=caught};this.get_caught=function(){return HEAP8[this.ptr+12>>0]!=0};this.set_rethrown=function(rethrown){rethrown=rethrown?1:0;HEAP8[this.ptr+13>>0]=rethrown};this.get_rethrown=function(){return HEAP8[this.ptr+13>>0]!=0};this.init=function(type,destructor){this.set_adjusted_ptr(0);this.set_type(type);this.set_destructor(destructor);this.set_refcount(0);this.set_caught(false);this.set_rethrown(false)};this.add_ref=function(){var value=HEAP32[this.ptr>>2];HEAP32[this.ptr>>2]=value+1};this.release_ref=function(){var prev=HEAP32[this.ptr>>2];HEAP32[this.ptr>>2]=prev-1;return prev===1};this.set_adjusted_ptr=function(adjustedPtr){HEAPU32[this.ptr+16>>2]=adjustedPtr};this.get_adjusted_ptr=function(){return HEAPU32[this.ptr+16>>2]};this.get_exception_ptr=function(){var isPointer=___cxa_is_pointer_type(this.get_type());if(isPointer){return HEAPU32[this.excPtr>>2]}var adjusted=this.get_adjusted_ptr();if(adjusted!==0)return adjusted;return this.excPtr}}var exceptionLast=0;var uncaughtExceptionCount=0;function ___cxa_throw(ptr,type,destructor){var info=new ExceptionInfo(ptr);info.init(type,destructor);exceptionLast=ptr;uncaughtExceptionCount++;throw ptr}function getRandomDevice(){if(typeof crypto=="object"&&typeof crypto["getRandomValues"]=="function"){var randomBuffer=new Uint8Array(1);return()=>{crypto.getRandomValues(randomBuffer);return randomBuffer[0]}}else if(ENVIRONMENT_IS_NODE){try{var crypto_module=require("crypto");return()=>crypto_module["randomBytes"](1)[0]}catch(e){}}return()=>abort("randomDevice")}var PATH={isAbs:path=>path.charAt(0)==="/",splitPath:filename=>{var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;return splitPathRe.exec(filename).slice(1)},normalizeArray:(parts,allowAboveRoot)=>{var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up;up--){parts.unshift("..")}}return parts},normalize:path=>{var isAbsolute=PATH.isAbs(path),trailingSlash=path.substr(-1)==="/";path=PATH.normalizeArray(path.split("/").filter(p=>!!p),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path},dirname:path=>{var result=PATH.splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return"."}if(dir){dir=dir.substr(0,dir.length-1)}return root+dir},basename:path=>{if(path==="/")return"/";path=PATH.normalize(path);path=path.replace(/\/$/,"");var lastSlash=path.lastIndexOf("/");if(lastSlash===-1)return path;return path.substr(lastSlash+1)},join:function(){var paths=Array.prototype.slice.call(arguments);return PATH.normalize(paths.join("/"))},join2:(l,r)=>{return PATH.normalize(l+"/"+r)}};var PATH_FS={resolve:function(){var resolvedPath="",resolvedAbsolute=false;for(var i=arguments.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?arguments[i]:FS.cwd();if(typeof path!="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){return""}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=PATH.isAbs(path)}resolvedPath=PATH.normalizeArray(resolvedPath.split("/").filter(p=>!!p),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."},relative:(from,to)=>{from=PATH_FS.resolve(from).substr(1);to=PATH_FS.resolve(to).substr(1);function trim(arr){var start=0;for(;start=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array}var TTY={ttys:[],init:function(){},shutdown:function(){},register:function(dev,ops){TTY.ttys[dev]={input:[],output:[],ops:ops};FS.registerDevice(dev,TTY.stream_ops)},stream_ops:{open:function(stream){var tty=TTY.ttys[stream.node.rdev];if(!tty){throw new FS.ErrnoError(43)}stream.tty=tty;stream.seekable=false},close:function(stream){stream.tty.ops.fsync(stream.tty)},fsync:function(stream){stream.tty.ops.fsync(stream.tty)},read:function(stream,buffer,offset,length,pos){if(!stream.tty||!stream.tty.ops.get_char){throw new FS.ErrnoError(60)}var bytesRead=0;for(var i=0;i0){result=buf.slice(0,bytesRead).toString("utf-8")}else{result=null}}else if(typeof window!="undefined"&&typeof window.prompt=="function"){result=window.prompt("Input: ");if(result!==null){result+="\n"}}else if(typeof readline=="function"){result=readline();if(result!==null){result+="\n"}}if(!result){return null}tty.input=intArrayFromString(result,true)}return tty.input.shift()},put_char:function(tty,val){if(val===null||val===10){out(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},fsync:function(tty){if(tty.output&&tty.output.length>0){out(UTF8ArrayToString(tty.output,0));tty.output=[]}}},default_tty1_ops:{put_char:function(tty,val){if(val===null||val===10){err(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},fsync:function(tty){if(tty.output&&tty.output.length>0){err(UTF8ArrayToString(tty.output,0));tty.output=[]}}}};function mmapAlloc(size){abort()}var MEMFS={ops_table:null,mount:function(mount){return MEMFS.createNode(null,"/",16384|511,0)},createNode:function(parent,name,mode,dev){if(FS.isBlkdev(mode)||FS.isFIFO(mode)){throw new FS.ErrnoError(63)}if(!MEMFS.ops_table){MEMFS.ops_table={dir:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,lookup:MEMFS.node_ops.lookup,mknod:MEMFS.node_ops.mknod,rename:MEMFS.node_ops.rename,unlink:MEMFS.node_ops.unlink,rmdir:MEMFS.node_ops.rmdir,readdir:MEMFS.node_ops.readdir,symlink:MEMFS.node_ops.symlink},stream:{llseek:MEMFS.stream_ops.llseek}},file:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:{llseek:MEMFS.stream_ops.llseek,read:MEMFS.stream_ops.read,write:MEMFS.stream_ops.write,allocate:MEMFS.stream_ops.allocate,mmap:MEMFS.stream_ops.mmap,msync:MEMFS.stream_ops.msync}},link:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,readlink:MEMFS.node_ops.readlink},stream:{}},chrdev:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:FS.chrdev_stream_ops}}}var node=FS.createNode(parent,name,mode,dev);if(FS.isDir(node.mode)){node.node_ops=MEMFS.ops_table.dir.node;node.stream_ops=MEMFS.ops_table.dir.stream;node.contents={}}else if(FS.isFile(node.mode)){node.node_ops=MEMFS.ops_table.file.node;node.stream_ops=MEMFS.ops_table.file.stream;node.usedBytes=0;node.contents=null}else if(FS.isLink(node.mode)){node.node_ops=MEMFS.ops_table.link.node;node.stream_ops=MEMFS.ops_table.link.stream}else if(FS.isChrdev(node.mode)){node.node_ops=MEMFS.ops_table.chrdev.node;node.stream_ops=MEMFS.ops_table.chrdev.stream}node.timestamp=Date.now();if(parent){parent.contents[name]=node;parent.timestamp=node.timestamp}return node},getFileDataAsTypedArray:function(node){if(!node.contents)return new Uint8Array(0);if(node.contents.subarray)return node.contents.subarray(0,node.usedBytes);return new Uint8Array(node.contents)},expandFileStorage:function(node,newCapacity){var prevCapacity=node.contents?node.contents.length:0;if(prevCapacity>=newCapacity)return;var CAPACITY_DOUBLING_MAX=1024*1024;newCapacity=Math.max(newCapacity,prevCapacity*(prevCapacity>>0);if(prevCapacity!=0)newCapacity=Math.max(newCapacity,256);var oldContents=node.contents;node.contents=new Uint8Array(newCapacity);if(node.usedBytes>0)node.contents.set(oldContents.subarray(0,node.usedBytes),0)},resizeFileStorage:function(node,newSize){if(node.usedBytes==newSize)return;if(newSize==0){node.contents=null;node.usedBytes=0}else{var oldContents=node.contents;node.contents=new Uint8Array(newSize);if(oldContents){node.contents.set(oldContents.subarray(0,Math.min(newSize,node.usedBytes)))}node.usedBytes=newSize}},node_ops:{getattr:function(node){var attr={};attr.dev=FS.isChrdev(node.mode)?node.id:1;attr.ino=node.id;attr.mode=node.mode;attr.nlink=1;attr.uid=0;attr.gid=0;attr.rdev=node.rdev;if(FS.isDir(node.mode)){attr.size=4096}else if(FS.isFile(node.mode)){attr.size=node.usedBytes}else if(FS.isLink(node.mode)){attr.size=node.link.length}else{attr.size=0}attr.atime=new Date(node.timestamp);attr.mtime=new Date(node.timestamp);attr.ctime=new Date(node.timestamp);attr.blksize=4096;attr.blocks=Math.ceil(attr.size/attr.blksize);return attr},setattr:function(node,attr){if(attr.mode!==undefined){node.mode=attr.mode}if(attr.timestamp!==undefined){node.timestamp=attr.timestamp}if(attr.size!==undefined){MEMFS.resizeFileStorage(node,attr.size)}},lookup:function(parent,name){throw FS.genericErrors[44]},mknod:function(parent,name,mode,dev){return MEMFS.createNode(parent,name,mode,dev)},rename:function(old_node,new_dir,new_name){if(FS.isDir(old_node.mode)){var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(new_node){for(var i in new_node.contents){throw new FS.ErrnoError(55)}}}delete old_node.parent.contents[old_node.name];old_node.parent.timestamp=Date.now();old_node.name=new_name;new_dir.contents[new_name]=old_node;new_dir.timestamp=old_node.parent.timestamp;old_node.parent=new_dir},unlink:function(parent,name){delete parent.contents[name];parent.timestamp=Date.now()},rmdir:function(parent,name){var node=FS.lookupNode(parent,name);for(var i in node.contents){throw new FS.ErrnoError(55)}delete parent.contents[name];parent.timestamp=Date.now()},readdir:function(node){var entries=[".",".."];for(var key in node.contents){if(!node.contents.hasOwnProperty(key)){continue}entries.push(key)}return entries},symlink:function(parent,newname,oldpath){var node=MEMFS.createNode(parent,newname,511|40960,0);node.link=oldpath;return node},readlink:function(node){if(!FS.isLink(node.mode)){throw new FS.ErrnoError(28)}return node.link}},stream_ops:{read:function(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=stream.node.usedBytes)return 0;var size=Math.min(stream.node.usedBytes-position,length);if(size>8&&contents.subarray){buffer.set(contents.subarray(position,position+size),offset)}else{for(var i=0;i0||position+length{assert(arrayBuffer,'Loading data file "'+url+'" failed (no arrayBuffer).');onload(new Uint8Array(arrayBuffer));if(dep)removeRunDependency(dep)},event=>{if(onerror){onerror()}else{throw'Loading data file "'+url+'" failed.'}});if(dep)addRunDependency(dep)}var FS={root:null,mounts:[],devices:{},streams:[],nextInode:1,nameTable:null,currentPath:"/",initialized:false,ignorePermissions:true,ErrnoError:null,genericErrors:{},filesystems:null,syncFSRequests:0,lookupPath:(path,opts={})=>{path=PATH_FS.resolve(path);if(!path)return{path:"",node:null};var defaults={follow_mount:true,recurse_count:0};opts=Object.assign(defaults,opts);if(opts.recurse_count>8){throw new FS.ErrnoError(32)}var parts=path.split("/").filter(p=>!!p);var current=FS.root;var current_path="/";for(var i=0;i40){throw new FS.ErrnoError(32)}}}}return{path:current_path,node:current}},getPath:node=>{var path;while(true){if(FS.isRoot(node)){var mount=node.mount.mountpoint;if(!path)return mount;return mount[mount.length-1]!=="/"?mount+"/"+path:mount+path}path=path?node.name+"/"+path:node.name;node=node.parent}},hashName:(parentid,name)=>{var hash=0;for(var i=0;i>>0)%FS.nameTable.length},hashAddNode:node=>{var hash=FS.hashName(node.parent.id,node.name);node.name_next=FS.nameTable[hash];FS.nameTable[hash]=node},hashRemoveNode:node=>{var hash=FS.hashName(node.parent.id,node.name);if(FS.nameTable[hash]===node){FS.nameTable[hash]=node.name_next}else{var current=FS.nameTable[hash];while(current){if(current.name_next===node){current.name_next=node.name_next;break}current=current.name_next}}},lookupNode:(parent,name)=>{var errCode=FS.mayLookup(parent);if(errCode){throw new FS.ErrnoError(errCode,parent)}var hash=FS.hashName(parent.id,name);for(var node=FS.nameTable[hash];node;node=node.name_next){var nodeName=node.name;if(node.parent.id===parent.id&&nodeName===name){return node}}return FS.lookup(parent,name)},createNode:(parent,name,mode,rdev)=>{var node=new FS.FSNode(parent,name,mode,rdev);FS.hashAddNode(node);return node},destroyNode:node=>{FS.hashRemoveNode(node)},isRoot:node=>{return node===node.parent},isMountpoint:node=>{return!!node.mounted},isFile:mode=>{return(mode&61440)===32768},isDir:mode=>{return(mode&61440)===16384},isLink:mode=>{return(mode&61440)===40960},isChrdev:mode=>{return(mode&61440)===8192},isBlkdev:mode=>{return(mode&61440)===24576},isFIFO:mode=>{return(mode&61440)===4096},isSocket:mode=>{return(mode&49152)===49152},flagModes:{"r":0,"r+":2,"w":577,"w+":578,"a":1089,"a+":1090},modeStringToFlags:str=>{var flags=FS.flagModes[str];if(typeof flags=="undefined"){throw new Error("Unknown file open mode: "+str)}return flags},flagsToPermissionString:flag=>{var perms=["r","w","rw"][flag&3];if(flag&512){perms+="w"}return perms},nodePermissions:(node,perms)=>{if(FS.ignorePermissions){return 0}if(perms.includes("r")&&!(node.mode&292)){return 2}else if(perms.includes("w")&&!(node.mode&146)){return 2}else if(perms.includes("x")&&!(node.mode&73)){return 2}return 0},mayLookup:dir=>{var errCode=FS.nodePermissions(dir,"x");if(errCode)return errCode;if(!dir.node_ops.lookup)return 2;return 0},mayCreate:(dir,name)=>{try{var node=FS.lookupNode(dir,name);return 20}catch(e){}return FS.nodePermissions(dir,"wx")},mayDelete:(dir,name,isdir)=>{var node;try{node=FS.lookupNode(dir,name)}catch(e){return e.errno}var errCode=FS.nodePermissions(dir,"wx");if(errCode){return errCode}if(isdir){if(!FS.isDir(node.mode)){return 54}if(FS.isRoot(node)||FS.getPath(node)===FS.cwd()){return 10}}else{if(FS.isDir(node.mode)){return 31}}return 0},mayOpen:(node,flags)=>{if(!node){return 44}if(FS.isLink(node.mode)){return 32}else if(FS.isDir(node.mode)){if(FS.flagsToPermissionString(flags)!=="r"||flags&512){return 31}}return FS.nodePermissions(node,FS.flagsToPermissionString(flags))},MAX_OPEN_FDS:4096,nextfd:(fd_start=0,fd_end=FS.MAX_OPEN_FDS)=>{for(var fd=fd_start;fd<=fd_end;fd++){if(!FS.streams[fd]){return fd}}throw new FS.ErrnoError(33)},getStream:fd=>FS.streams[fd],createStream:(stream,fd_start,fd_end)=>{if(!FS.FSStream){FS.FSStream=function(){this.shared={}};FS.FSStream.prototype={};Object.defineProperties(FS.FSStream.prototype,{object:{get:function(){return this.node},set:function(val){this.node=val}},isRead:{get:function(){return(this.flags&2097155)!==1}},isWrite:{get:function(){return(this.flags&2097155)!==0}},isAppend:{get:function(){return this.flags&1024}},flags:{get:function(){return this.shared.flags},set:function(val){this.shared.flags=val}},position:{get:function(){return this.shared.position},set:function(val){this.shared.position=val}}})}stream=Object.assign(new FS.FSStream,stream);var fd=FS.nextfd(fd_start,fd_end);stream.fd=fd;FS.streams[fd]=stream;return stream},closeStream:fd=>{FS.streams[fd]=null},chrdev_stream_ops:{open:stream=>{var device=FS.getDevice(stream.node.rdev);stream.stream_ops=device.stream_ops;if(stream.stream_ops.open){stream.stream_ops.open(stream)}},llseek:()=>{throw new FS.ErrnoError(70)}},major:dev=>dev>>8,minor:dev=>dev&255,makedev:(ma,mi)=>ma<<8|mi,registerDevice:(dev,ops)=>{FS.devices[dev]={stream_ops:ops}},getDevice:dev=>FS.devices[dev],getMounts:mount=>{var mounts=[];var check=[mount];while(check.length){var m=check.pop();mounts.push(m);check.push.apply(check,m.mounts)}return mounts},syncfs:(populate,callback)=>{if(typeof populate=="function"){callback=populate;populate=false}FS.syncFSRequests++;if(FS.syncFSRequests>1){err("warning: "+FS.syncFSRequests+" FS.syncfs operations in flight at once, probably just doing extra work")}var mounts=FS.getMounts(FS.root.mount);var completed=0;function doCallback(errCode){FS.syncFSRequests--;return callback(errCode)}function done(errCode){if(errCode){if(!done.errored){done.errored=true;return doCallback(errCode)}return}if(++completed>=mounts.length){doCallback(null)}}mounts.forEach(mount=>{if(!mount.type.syncfs){return done(null)}mount.type.syncfs(mount,populate,done)})},mount:(type,opts,mountpoint)=>{var root=mountpoint==="/";var pseudo=!mountpoint;var node;if(root&&FS.root){throw new FS.ErrnoError(10)}else if(!root&&!pseudo){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});mountpoint=lookup.path;node=lookup.node;if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}if(!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}}var mount={type:type,opts:opts,mountpoint:mountpoint,mounts:[]};var mountRoot=type.mount(mount);mountRoot.mount=mount;mount.root=mountRoot;if(root){FS.root=mountRoot}else if(node){node.mounted=mount;if(node.mount){node.mount.mounts.push(mount)}}return mountRoot},unmount:mountpoint=>{var lookup=FS.lookupPath(mountpoint,{follow_mount:false});if(!FS.isMountpoint(lookup.node)){throw new FS.ErrnoError(28)}var node=lookup.node;var mount=node.mounted;var mounts=FS.getMounts(mount);Object.keys(FS.nameTable).forEach(hash=>{var current=FS.nameTable[hash];while(current){var next=current.name_next;if(mounts.includes(current.mount)){FS.destroyNode(current)}current=next}});node.mounted=null;var idx=node.mount.mounts.indexOf(mount);node.mount.mounts.splice(idx,1)},lookup:(parent,name)=>{return parent.node_ops.lookup(parent,name)},mknod:(path,mode,dev)=>{var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);if(!name||name==="."||name===".."){throw new FS.ErrnoError(28)}var errCode=FS.mayCreate(parent,name);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.mknod){throw new FS.ErrnoError(63)}return parent.node_ops.mknod(parent,name,mode,dev)},create:(path,mode)=>{mode=mode!==undefined?mode:438;mode&=4095;mode|=32768;return FS.mknod(path,mode,0)},mkdir:(path,mode)=>{mode=mode!==undefined?mode:511;mode&=511|512;mode|=16384;return FS.mknod(path,mode,0)},mkdirTree:(path,mode)=>{var dirs=path.split("/");var d="";for(var i=0;i{if(typeof dev=="undefined"){dev=mode;mode=438}mode|=8192;return FS.mknod(path,mode,dev)},symlink:(oldpath,newpath)=>{if(!PATH_FS.resolve(oldpath)){throw new FS.ErrnoError(44)}var lookup=FS.lookupPath(newpath,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var newname=PATH.basename(newpath);var errCode=FS.mayCreate(parent,newname);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.symlink){throw new FS.ErrnoError(63)}return parent.node_ops.symlink(parent,newname,oldpath)},rename:(old_path,new_path)=>{var old_dirname=PATH.dirname(old_path);var new_dirname=PATH.dirname(new_path);var old_name=PATH.basename(old_path);var new_name=PATH.basename(new_path);var lookup,old_dir,new_dir;lookup=FS.lookupPath(old_path,{parent:true});old_dir=lookup.node;lookup=FS.lookupPath(new_path,{parent:true});new_dir=lookup.node;if(!old_dir||!new_dir)throw new FS.ErrnoError(44);if(old_dir.mount!==new_dir.mount){throw new FS.ErrnoError(75)}var old_node=FS.lookupNode(old_dir,old_name);var relative=PATH_FS.relative(old_path,new_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(28)}relative=PATH_FS.relative(new_path,old_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(55)}var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(old_node===new_node){return}var isdir=FS.isDir(old_node.mode);var errCode=FS.mayDelete(old_dir,old_name,isdir);if(errCode){throw new FS.ErrnoError(errCode)}errCode=new_node?FS.mayDelete(new_dir,new_name,isdir):FS.mayCreate(new_dir,new_name);if(errCode){throw new FS.ErrnoError(errCode)}if(!old_dir.node_ops.rename){throw new FS.ErrnoError(63)}if(FS.isMountpoint(old_node)||new_node&&FS.isMountpoint(new_node)){throw new FS.ErrnoError(10)}if(new_dir!==old_dir){errCode=FS.nodePermissions(old_dir,"w");if(errCode){throw new FS.ErrnoError(errCode)}}FS.hashRemoveNode(old_node);try{old_dir.node_ops.rename(old_node,new_dir,new_name)}catch(e){throw e}finally{FS.hashAddNode(old_node)}},rmdir:path=>{var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,true);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.rmdir){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.rmdir(parent,name);FS.destroyNode(node)},readdir:path=>{var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;if(!node.node_ops.readdir){throw new FS.ErrnoError(54)}return node.node_ops.readdir(node)},unlink:path=>{var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,false);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.unlink){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.unlink(parent,name);FS.destroyNode(node)},readlink:path=>{var lookup=FS.lookupPath(path);var link=lookup.node;if(!link){throw new FS.ErrnoError(44)}if(!link.node_ops.readlink){throw new FS.ErrnoError(28)}return PATH_FS.resolve(FS.getPath(link.parent),link.node_ops.readlink(link))},stat:(path,dontFollow)=>{var lookup=FS.lookupPath(path,{follow:!dontFollow});var node=lookup.node;if(!node){throw new FS.ErrnoError(44)}if(!node.node_ops.getattr){throw new FS.ErrnoError(63)}return node.node_ops.getattr(node)},lstat:path=>{return FS.stat(path,true)},chmod:(path,mode,dontFollow)=>{var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}node.node_ops.setattr(node,{mode:mode&4095|node.mode&~4095,timestamp:Date.now()})},lchmod:(path,mode)=>{FS.chmod(path,mode,true)},fchmod:(fd,mode)=>{var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}FS.chmod(stream.node,mode)},chown:(path,uid,gid,dontFollow)=>{var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}node.node_ops.setattr(node,{timestamp:Date.now()})},lchown:(path,uid,gid)=>{FS.chown(path,uid,gid,true)},fchown:(fd,uid,gid)=>{var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}FS.chown(stream.node,uid,gid)},truncate:(path,len)=>{if(len<0){throw new FS.ErrnoError(28)}var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:true});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}if(FS.isDir(node.mode)){throw new FS.ErrnoError(31)}if(!FS.isFile(node.mode)){throw new FS.ErrnoError(28)}var errCode=FS.nodePermissions(node,"w");if(errCode){throw new FS.ErrnoError(errCode)}node.node_ops.setattr(node,{size:len,timestamp:Date.now()})},ftruncate:(fd,len)=>{var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(28)}FS.truncate(stream.node,len)},utime:(path,atime,mtime)=>{var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;node.node_ops.setattr(node,{timestamp:Math.max(atime,mtime)})},open:(path,flags,mode)=>{if(path===""){throw new FS.ErrnoError(44)}flags=typeof flags=="string"?FS.modeStringToFlags(flags):flags;mode=typeof mode=="undefined"?438:mode;if(flags&64){mode=mode&4095|32768}else{mode=0}var node;if(typeof path=="object"){node=path}else{path=PATH.normalize(path);try{var lookup=FS.lookupPath(path,{follow:!(flags&131072)});node=lookup.node}catch(e){}}var created=false;if(flags&64){if(node){if(flags&128){throw new FS.ErrnoError(20)}}else{node=FS.mknod(path,mode,0);created=true}}if(!node){throw new FS.ErrnoError(44)}if(FS.isChrdev(node.mode)){flags&=~512}if(flags&65536&&!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}if(!created){var errCode=FS.mayOpen(node,flags);if(errCode){throw new FS.ErrnoError(errCode)}}if(flags&512&&!created){FS.truncate(node,0)}flags&=~(128|512|131072);var stream=FS.createStream({node:node,path:FS.getPath(node),flags:flags,seekable:true,position:0,stream_ops:node.stream_ops,ungotten:[],error:false});if(stream.stream_ops.open){stream.stream_ops.open(stream)}if(Module["logReadFiles"]&&!(flags&1)){if(!FS.readFiles)FS.readFiles={};if(!(path in FS.readFiles)){FS.readFiles[path]=1}}return stream},close:stream=>{if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(stream.getdents)stream.getdents=null;try{if(stream.stream_ops.close){stream.stream_ops.close(stream)}}catch(e){throw e}finally{FS.closeStream(stream.fd)}stream.fd=null},isClosed:stream=>{return stream.fd===null},llseek:(stream,offset,whence)=>{if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(!stream.seekable||!stream.stream_ops.llseek){throw new FS.ErrnoError(70)}if(whence!=0&&whence!=1&&whence!=2){throw new FS.ErrnoError(28)}stream.position=stream.stream_ops.llseek(stream,offset,whence);stream.ungotten=[];return stream.position},read:(stream,buffer,offset,length,position)=>{if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.read){throw new FS.ErrnoError(28)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesRead=stream.stream_ops.read(stream,buffer,offset,length,position);if(!seeking)stream.position+=bytesRead;return bytesRead},write:(stream,buffer,offset,length,position,canOwn)=>{if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.write){throw new FS.ErrnoError(28)}if(stream.seekable&&stream.flags&1024){FS.llseek(stream,0,2)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesWritten=stream.stream_ops.write(stream,buffer,offset,length,position,canOwn);if(!seeking)stream.position+=bytesWritten;return bytesWritten},allocate:(stream,offset,length)=>{if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(offset<0||length<=0){throw new FS.ErrnoError(28)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(8)}if(!FS.isFile(stream.node.mode)&&!FS.isDir(stream.node.mode)){throw new FS.ErrnoError(43)}if(!stream.stream_ops.allocate){throw new FS.ErrnoError(138)}stream.stream_ops.allocate(stream,offset,length)},mmap:(stream,length,position,prot,flags)=>{if((prot&2)!==0&&(flags&2)===0&&(stream.flags&2097155)!==2){throw new FS.ErrnoError(2)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(2)}if(!stream.stream_ops.mmap){throw new FS.ErrnoError(43)}return stream.stream_ops.mmap(stream,length,position,prot,flags)},msync:(stream,buffer,offset,length,mmapFlags)=>{if(!stream.stream_ops.msync){return 0}return stream.stream_ops.msync(stream,buffer,offset,length,mmapFlags)},munmap:stream=>0,ioctl:(stream,cmd,arg)=>{if(!stream.stream_ops.ioctl){throw new FS.ErrnoError(59)}return stream.stream_ops.ioctl(stream,cmd,arg)},readFile:(path,opts={})=>{opts.flags=opts.flags||0;opts.encoding=opts.encoding||"binary";if(opts.encoding!=="utf8"&&opts.encoding!=="binary"){throw new Error('Invalid encoding type "'+opts.encoding+'"')}var ret;var stream=FS.open(path,opts.flags);var stat=FS.stat(path);var length=stat.size;var buf=new Uint8Array(length);FS.read(stream,buf,0,length,0);if(opts.encoding==="utf8"){ret=UTF8ArrayToString(buf,0)}else if(opts.encoding==="binary"){ret=buf}FS.close(stream);return ret},writeFile:(path,data,opts={})=>{opts.flags=opts.flags||577;var stream=FS.open(path,opts.flags,opts.mode);if(typeof data=="string"){var buf=new Uint8Array(lengthBytesUTF8(data)+1);var actualNumBytes=stringToUTF8Array(data,buf,0,buf.length);FS.write(stream,buf,0,actualNumBytes,undefined,opts.canOwn)}else if(ArrayBuffer.isView(data)){FS.write(stream,data,0,data.byteLength,undefined,opts.canOwn)}else{throw new Error("Unsupported data type")}FS.close(stream)},cwd:()=>FS.currentPath,chdir:path=>{var lookup=FS.lookupPath(path,{follow:true});if(lookup.node===null){throw new FS.ErrnoError(44)}if(!FS.isDir(lookup.node.mode)){throw new FS.ErrnoError(54)}var errCode=FS.nodePermissions(lookup.node,"x");if(errCode){throw new FS.ErrnoError(errCode)}FS.currentPath=lookup.path},createDefaultDirectories:()=>{FS.mkdir("/tmp");FS.mkdir("/home");FS.mkdir("/home/web_user")},createDefaultDevices:()=>{FS.mkdir("/dev");FS.registerDevice(FS.makedev(1,3),{read:()=>0,write:(stream,buffer,offset,length,pos)=>length});FS.mkdev("/dev/null",FS.makedev(1,3));TTY.register(FS.makedev(5,0),TTY.default_tty_ops);TTY.register(FS.makedev(6,0),TTY.default_tty1_ops);FS.mkdev("/dev/tty",FS.makedev(5,0));FS.mkdev("/dev/tty1",FS.makedev(6,0));var random_device=getRandomDevice();FS.createDevice("/dev","random",random_device);FS.createDevice("/dev","urandom",random_device);FS.mkdir("/dev/shm");FS.mkdir("/dev/shm/tmp")},createSpecialDirectories:()=>{FS.mkdir("/proc");var proc_self=FS.mkdir("/proc/self");FS.mkdir("/proc/self/fd");FS.mount({mount:()=>{var node=FS.createNode(proc_self,"fd",16384|511,73);node.node_ops={lookup:(parent,name)=>{var fd=+name;var stream=FS.getStream(fd);if(!stream)throw new FS.ErrnoError(8);var ret={parent:null,mount:{mountpoint:"fake"},node_ops:{readlink:()=>stream.path}};ret.parent=ret;return ret}};return node}},{},"/proc/self/fd")},createStandardStreams:()=>{if(Module["stdin"]){FS.createDevice("/dev","stdin",Module["stdin"])}else{FS.symlink("/dev/tty","/dev/stdin")}if(Module["stdout"]){FS.createDevice("/dev","stdout",null,Module["stdout"])}else{FS.symlink("/dev/tty","/dev/stdout")}if(Module["stderr"]){FS.createDevice("/dev","stderr",null,Module["stderr"])}else{FS.symlink("/dev/tty1","/dev/stderr")}var stdin=FS.open("/dev/stdin",0);var stdout=FS.open("/dev/stdout",1);var stderr=FS.open("/dev/stderr",1)},ensureErrnoError:()=>{if(FS.ErrnoError)return;FS.ErrnoError=function ErrnoError(errno,node){this.node=node;this.setErrno=function(errno){this.errno=errno};this.setErrno(errno);this.message="FS error"};FS.ErrnoError.prototype=new Error;FS.ErrnoError.prototype.constructor=FS.ErrnoError;[44].forEach(code=>{FS.genericErrors[code]=new FS.ErrnoError(code);FS.genericErrors[code].stack=""})},staticInit:()=>{FS.ensureErrnoError();FS.nameTable=new Array(4096);FS.mount(MEMFS,{},"/");FS.createDefaultDirectories();FS.createDefaultDevices();FS.createSpecialDirectories();FS.filesystems={"MEMFS":MEMFS}},init:(input,output,error)=>{FS.init.initialized=true;FS.ensureErrnoError();Module["stdin"]=input||Module["stdin"];Module["stdout"]=output||Module["stdout"];Module["stderr"]=error||Module["stderr"];FS.createStandardStreams()},quit:()=>{FS.init.initialized=false;for(var i=0;i{var mode=0;if(canRead)mode|=292|73;if(canWrite)mode|=146;return mode},findObject:(path,dontResolveLastLink)=>{var ret=FS.analyzePath(path,dontResolveLastLink);if(!ret.exists){return null}return ret.object},analyzePath:(path,dontResolveLastLink)=>{try{var lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});path=lookup.path}catch(e){}var ret={isRoot:false,exists:false,error:0,name:null,path:null,object:null,parentExists:false,parentPath:null,parentObject:null};try{var lookup=FS.lookupPath(path,{parent:true});ret.parentExists=true;ret.parentPath=lookup.path;ret.parentObject=lookup.node;ret.name=PATH.basename(path);lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});ret.exists=true;ret.path=lookup.path;ret.object=lookup.node;ret.name=lookup.node.name;ret.isRoot=lookup.path==="/"}catch(e){ret.error=e.errno}return ret},createPath:(parent,path,canRead,canWrite)=>{parent=typeof parent=="string"?parent:FS.getPath(parent);var parts=path.split("/").reverse();while(parts.length){var part=parts.pop();if(!part)continue;var current=PATH.join2(parent,part);try{FS.mkdir(current)}catch(e){}parent=current}return current},createFile:(parent,name,properties,canRead,canWrite)=>{var path=PATH.join2(typeof parent=="string"?parent:FS.getPath(parent),name);var mode=FS.getMode(canRead,canWrite);return FS.create(path,mode)},createDataFile:(parent,name,data,canRead,canWrite,canOwn)=>{var path=name;if(parent){parent=typeof parent=="string"?parent:FS.getPath(parent);path=name?PATH.join2(parent,name):parent}var mode=FS.getMode(canRead,canWrite);var node=FS.create(path,mode);if(data){if(typeof data=="string"){var arr=new Array(data.length);for(var i=0,len=data.length;i{var path=PATH.join2(typeof parent=="string"?parent:FS.getPath(parent),name);var mode=FS.getMode(!!input,!!output);if(!FS.createDevice.major)FS.createDevice.major=64;var dev=FS.makedev(FS.createDevice.major++,0);FS.registerDevice(dev,{open:stream=>{stream.seekable=false},close:stream=>{if(output&&output.buffer&&output.buffer.length){output(10)}},read:(stream,buffer,offset,length,pos)=>{var bytesRead=0;for(var i=0;i{for(var i=0;i{if(obj.isDevice||obj.isFolder||obj.link||obj.contents)return true;if(typeof XMLHttpRequest!="undefined"){throw new Error("Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread.")}else if(read_){try{obj.contents=intArrayFromString(read_(obj.url),true);obj.usedBytes=obj.contents.length}catch(e){throw new FS.ErrnoError(29)}}else{throw new Error("Cannot load without read() or XMLHttpRequest.")}},createLazyFile:(parent,name,url,canRead,canWrite)=>{function LazyUint8Array(){this.lengthKnown=false;this.chunks=[]}LazyUint8Array.prototype.get=function LazyUint8Array_get(idx){if(idx>this.length-1||idx<0){return undefined}var chunkOffset=idx%this.chunkSize;var chunkNum=idx/this.chunkSize|0;return this.getter(chunkNum)[chunkOffset]};LazyUint8Array.prototype.setDataGetter=function LazyUint8Array_setDataGetter(getter){this.getter=getter};LazyUint8Array.prototype.cacheLength=function LazyUint8Array_cacheLength(){var xhr=new XMLHttpRequest;xhr.open("HEAD",url,false);xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);var datalength=Number(xhr.getResponseHeader("Content-length"));var header;var hasByteServing=(header=xhr.getResponseHeader("Accept-Ranges"))&&header==="bytes";var usesGzip=(header=xhr.getResponseHeader("Content-Encoding"))&&header==="gzip";var chunkSize=1024*1024;if(!hasByteServing)chunkSize=datalength;var doXHR=(from,to)=>{if(from>to)throw new Error("invalid range ("+from+", "+to+") or no bytes requested!");if(to>datalength-1)throw new Error("only "+datalength+" bytes available! programmer error!");var xhr=new XMLHttpRequest;xhr.open("GET",url,false);if(datalength!==chunkSize)xhr.setRequestHeader("Range","bytes="+from+"-"+to);xhr.responseType="arraybuffer";if(xhr.overrideMimeType){xhr.overrideMimeType("text/plain; charset=x-user-defined")}xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);if(xhr.response!==undefined){return new Uint8Array(xhr.response||[])}return intArrayFromString(xhr.responseText||"",true)};var lazyArray=this;lazyArray.setDataGetter(chunkNum=>{var start=chunkNum*chunkSize;var end=(chunkNum+1)*chunkSize-1;end=Math.min(end,datalength-1);if(typeof lazyArray.chunks[chunkNum]=="undefined"){lazyArray.chunks[chunkNum]=doXHR(start,end)}if(typeof lazyArray.chunks[chunkNum]=="undefined")throw new Error("doXHR failed!");return lazyArray.chunks[chunkNum]});if(usesGzip||!datalength){chunkSize=datalength=1;datalength=this.getter(0).length;chunkSize=datalength;out("LazyFiles on gzip forces download of the whole file when length is accessed")}this._length=datalength;this._chunkSize=chunkSize;this.lengthKnown=true};if(typeof XMLHttpRequest!="undefined"){if(!ENVIRONMENT_IS_WORKER)throw"Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc";var lazyArray=new LazyUint8Array;Object.defineProperties(lazyArray,{length:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._length}},chunkSize:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._chunkSize}}});var properties={isDevice:false,contents:lazyArray}}else{var properties={isDevice:false,url:url}}var node=FS.createFile(parent,name,properties,canRead,canWrite);if(properties.contents){node.contents=properties.contents}else if(properties.url){node.contents=null;node.url=properties.url}Object.defineProperties(node,{usedBytes:{get:function(){return this.contents.length}}});var stream_ops={};var keys=Object.keys(node.stream_ops);keys.forEach(key=>{var fn=node.stream_ops[key];stream_ops[key]=function forceLoadLazyFile(){FS.forceLoadFile(node);return fn.apply(null,arguments)}});function writeChunks(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=contents.length)return 0;var size=Math.min(contents.length-position,length);if(contents.slice){for(var i=0;i{FS.forceLoadFile(node);return writeChunks(stream,buffer,offset,length,position)};stream_ops.mmap=(stream,length,position,prot,flags)=>{FS.forceLoadFile(node);var ptr=mmapAlloc(length);if(!ptr){throw new FS.ErrnoError(48)}writeChunks(stream,HEAP8,ptr,length,position);return{ptr:ptr,allocated:true}};node.stream_ops=stream_ops;return node},createPreloadedFile:(parent,name,url,canRead,canWrite,onload,onerror,dontCreateFile,canOwn,preFinish)=>{var fullname=name?PATH_FS.resolve(PATH.join2(parent,name)):parent;var dep=getUniqueRunDependency("cp "+fullname);function processData(byteArray){function finish(byteArray){if(preFinish)preFinish();if(!dontCreateFile){FS.createDataFile(parent,name,byteArray,canRead,canWrite,canOwn)}if(onload)onload();removeRunDependency(dep)}if(Browser.handledByPreloadPlugin(byteArray,fullname,finish,()=>{if(onerror)onerror();removeRunDependency(dep)})){return}finish(byteArray)}addRunDependency(dep);if(typeof url=="string"){asyncLoad(url,byteArray=>processData(byteArray),onerror)}else{processData(url)}},indexedDB:()=>{return window.indexedDB||window.mozIndexedDB||window.webkitIndexedDB||window.msIndexedDB},DB_NAME:()=>{return"EM_FS_"+window.location.pathname},DB_VERSION:20,DB_STORE_NAME:"FILE_DATA",saveFilesToDB:(paths,onload,onerror)=>{onload=onload||(()=>{});onerror=onerror||(()=>{});var indexedDB=FS.indexedDB();try{var openRequest=indexedDB.open(FS.DB_NAME(),FS.DB_VERSION)}catch(e){return onerror(e)}openRequest.onupgradeneeded=()=>{out("creating db");var db=openRequest.result;db.createObjectStore(FS.DB_STORE_NAME)};openRequest.onsuccess=()=>{var db=openRequest.result;var transaction=db.transaction([FS.DB_STORE_NAME],"readwrite");var files=transaction.objectStore(FS.DB_STORE_NAME);var ok=0,fail=0,total=paths.length;function finish(){if(fail==0)onload();else onerror()}paths.forEach(path=>{var putRequest=files.put(FS.analyzePath(path).object.contents,path);putRequest.onsuccess=()=>{ok++;if(ok+fail==total)finish()};putRequest.onerror=()=>{fail++;if(ok+fail==total)finish()}});transaction.onerror=onerror};openRequest.onerror=onerror},loadFilesFromDB:(paths,onload,onerror)=>{onload=onload||(()=>{});onerror=onerror||(()=>{});var indexedDB=FS.indexedDB();try{var openRequest=indexedDB.open(FS.DB_NAME(),FS.DB_VERSION)}catch(e){return onerror(e)}openRequest.onupgradeneeded=onerror;openRequest.onsuccess=()=>{var db=openRequest.result;try{var transaction=db.transaction([FS.DB_STORE_NAME],"readonly")}catch(e){onerror(e);return}var files=transaction.objectStore(FS.DB_STORE_NAME);var ok=0,fail=0,total=paths.length;function finish(){if(fail==0)onload();else onerror()}paths.forEach(path=>{var getRequest=files.get(path);getRequest.onsuccess=()=>{if(FS.analyzePath(path).exists){FS.unlink(path)}FS.createDataFile(PATH.dirname(path),PATH.basename(path),getRequest.result,true,true,true);ok++;if(ok+fail==total)finish()};getRequest.onerror=()=>{fail++;if(ok+fail==total)finish()}});transaction.onerror=onerror};openRequest.onerror=onerror}};var SOCKFS={mount:function(mount){Module["websocket"]=Module["websocket"]&&"object"===typeof Module["websocket"]?Module["websocket"]:{};Module["websocket"]._callbacks={};Module["websocket"]["on"]=function(event,callback){if("function"===typeof callback){this._callbacks[event]=callback}return this};Module["websocket"].emit=function(event,param){if("function"===typeof this._callbacks[event]){this._callbacks[event].call(this,param)}};return FS.createNode(null,"/",16384|511,0)},createSocket:function(family,type,protocol){type&=~526336;var streaming=type==1;if(streaming&&protocol&&protocol!=6){throw new FS.ErrnoError(66)}var sock={family:family,type:type,protocol:protocol,server:null,error:null,peers:{},pending:[],recv_queue:[],sock_ops:SOCKFS.websocket_sock_ops};var name=SOCKFS.nextname();var node=FS.createNode(SOCKFS.root,name,49152,0);node.sock=sock;var stream=FS.createStream({path:name,node:node,flags:2,seekable:false,stream_ops:SOCKFS.stream_ops});sock.stream=stream;return sock},getSocket:function(fd){var stream=FS.getStream(fd);if(!stream||!FS.isSocket(stream.node.mode)){return null}return stream.node.sock},stream_ops:{poll:function(stream){var sock=stream.node.sock;return sock.sock_ops.poll(sock)},ioctl:function(stream,request,varargs){var sock=stream.node.sock;return sock.sock_ops.ioctl(sock,request,varargs)},read:function(stream,buffer,offset,length,position){var sock=stream.node.sock;var msg=sock.sock_ops.recvmsg(sock,length);if(!msg){return 0}buffer.set(msg.buffer,offset);return msg.buffer.length},write:function(stream,buffer,offset,length,position){var sock=stream.node.sock;return sock.sock_ops.sendmsg(sock,buffer,offset,length)},close:function(stream){var sock=stream.node.sock;sock.sock_ops.close(sock)}},nextname:function(){if(!SOCKFS.nextname.current){SOCKFS.nextname.current=0}return"socket["+SOCKFS.nextname.current+++"]"},websocket_sock_ops:{createPeer:function(sock,addr,port){var ws;if(typeof addr=="object"){ws=addr;addr=null;port=null}if(ws){if(ws._socket){addr=ws._socket.remoteAddress;port=ws._socket.remotePort}else{var result=/ws[s]?:\/\/([^:]+):(\d+)/.exec(ws.url);if(!result){throw new Error("WebSocket URL must be in the format ws(s)://address:port")}addr=result[1];port=parseInt(result[2],10)}}else{try{var runtimeConfig=Module["websocket"]&&"object"===typeof Module["websocket"];var url="ws:#".replace("#","//");if(runtimeConfig){if("string"===typeof Module["websocket"]["url"]){url=Module["websocket"]["url"]}}if(url==="ws://"||url==="wss://"){var parts=addr.split("/");url=url+parts[0]+":"+port+"/"+parts.slice(1).join("/")}var subProtocols="binary";if(runtimeConfig){if("string"===typeof Module["websocket"]["subprotocol"]){subProtocols=Module["websocket"]["subprotocol"]}}var opts=undefined;if(subProtocols!=="null"){subProtocols=subProtocols.replace(/^ +| +$/g,"").split(/ *, */);opts=subProtocols}if(runtimeConfig&&null===Module["websocket"]["subprotocol"]){subProtocols="null";opts=undefined}var WebSocketConstructor;if(ENVIRONMENT_IS_NODE){WebSocketConstructor=require("ws")}else{WebSocketConstructor=WebSocket}ws=new WebSocketConstructor(url,opts);ws.binaryType="arraybuffer"}catch(e){throw new FS.ErrnoError(23)}}var peer={addr:addr,port:port,socket:ws,dgram_send_queue:[]};SOCKFS.websocket_sock_ops.addPeer(sock,peer);SOCKFS.websocket_sock_ops.handlePeerEvents(sock,peer);if(sock.type===2&&typeof sock.sport!="undefined"){peer.dgram_send_queue.push(new Uint8Array([255,255,255,255,"p".charCodeAt(0),"o".charCodeAt(0),"r".charCodeAt(0),"t".charCodeAt(0),(sock.sport&65280)>>8,sock.sport&255]))}return peer},getPeer:function(sock,addr,port){return sock.peers[addr+":"+port]},addPeer:function(sock,peer){sock.peers[peer.addr+":"+peer.port]=peer},removePeer:function(sock,peer){delete sock.peers[peer.addr+":"+peer.port]},handlePeerEvents:function(sock,peer){var first=true;var handleOpen=function(){Module["websocket"].emit("open",sock.stream.fd);try{var queued=peer.dgram_send_queue.shift();while(queued){peer.socket.send(queued);queued=peer.dgram_send_queue.shift()}}catch(e){peer.socket.close()}};function handleMessage(data){if(typeof data=="string"){var encoder=new TextEncoder;data=encoder.encode(data)}else{assert(data.byteLength!==undefined);if(data.byteLength==0){return}data=new Uint8Array(data)}var wasfirst=first;first=false;if(wasfirst&&data.length===10&&data[0]===255&&data[1]===255&&data[2]===255&&data[3]===255&&data[4]==="p".charCodeAt(0)&&data[5]==="o".charCodeAt(0)&&data[6]==="r".charCodeAt(0)&&data[7]==="t".charCodeAt(0)){var newport=data[8]<<8|data[9];SOCKFS.websocket_sock_ops.removePeer(sock,peer);peer.port=newport;SOCKFS.websocket_sock_ops.addPeer(sock,peer);return}sock.recv_queue.push({addr:peer.addr,port:peer.port,data:data});Module["websocket"].emit("message",sock.stream.fd)}if(ENVIRONMENT_IS_NODE){peer.socket.on("open",handleOpen);peer.socket.on("message",function(data,isBinary){if(!isBinary){return}handleMessage(new Uint8Array(data).buffer)});peer.socket.on("close",function(){Module["websocket"].emit("close",sock.stream.fd)});peer.socket.on("error",function(error){sock.error=14;Module["websocket"].emit("error",[sock.stream.fd,sock.error,"ECONNREFUSED: Connection refused"])})}else{peer.socket.onopen=handleOpen;peer.socket.onclose=function(){Module["websocket"].emit("close",sock.stream.fd)};peer.socket.onmessage=function peer_socket_onmessage(event){handleMessage(event.data)};peer.socket.onerror=function(error){sock.error=14;Module["websocket"].emit("error",[sock.stream.fd,sock.error,"ECONNREFUSED: Connection refused"])}}},poll:function(sock){if(sock.type===1&&sock.server){return sock.pending.length?64|1:0}var mask=0;var dest=sock.type===1?SOCKFS.websocket_sock_ops.getPeer(sock,sock.daddr,sock.dport):null;if(sock.recv_queue.length||!dest||dest&&dest.socket.readyState===dest.socket.CLOSING||dest&&dest.socket.readyState===dest.socket.CLOSED){mask|=64|1}if(!dest||dest&&dest.socket.readyState===dest.socket.OPEN){mask|=4}if(dest&&dest.socket.readyState===dest.socket.CLOSING||dest&&dest.socket.readyState===dest.socket.CLOSED){mask|=16}return mask},ioctl:function(sock,request,arg){switch(request){case 21531:var bytes=0;if(sock.recv_queue.length){bytes=sock.recv_queue[0].data.length}HEAP32[arg>>2]=bytes;return 0;default:return 28}},close:function(sock){if(sock.server){try{sock.server.close()}catch(e){}sock.server=null}var peers=Object.keys(sock.peers);for(var i=0;i>2]=value;return value}function inetNtop4(addr){return(addr&255)+"."+(addr>>8&255)+"."+(addr>>16&255)+"."+(addr>>24&255)}function inetNtop6(ints){var str="";var word=0;var longest=0;var lastzero=0;var zstart=0;var len=0;var i=0;var parts=[ints[0]&65535,ints[0]>>16,ints[1]&65535,ints[1]>>16,ints[2]&65535,ints[2]>>16,ints[3]&65535,ints[3]>>16];var hasipv4=true;var v4part="";for(i=0;i<5;i++){if(parts[i]!==0){hasipv4=false;break}}if(hasipv4){v4part=inetNtop4(parts[6]|parts[7]<<16);if(parts[5]===-1){str="::ffff:";str+=v4part;return str}if(parts[5]===0){str="::";if(v4part==="0.0.0.0")v4part="";if(v4part==="0.0.0.1")v4part="1";str+=v4part;return str}}for(word=0;word<8;word++){if(parts[word]===0){if(word-lastzero>1){len=0}lastzero=word;len++}if(len>longest){longest=len;zstart=word-longest+1}}for(word=0;word<8;word++){if(longest>1){if(parts[word]===0&&word>=zstart&&word>1];var port=_ntohs(HEAPU16[sa+2>>1]);var addr;switch(family){case 2:if(salen!==16){return{errno:28}}addr=HEAP32[sa+4>>2];addr=inetNtop4(addr);break;case 10:if(salen!==28){return{errno:28}}addr=[HEAP32[sa+8>>2],HEAP32[sa+12>>2],HEAP32[sa+16>>2],HEAP32[sa+20>>2]];addr=inetNtop6(addr);break;default:return{errno:5}}return{family:family,addr:addr,port:port}}function inetPton4(str){var b=str.split(".");for(var i=0;i<4;i++){var tmp=Number(b[i]);if(isNaN(tmp))return null;b[i]=tmp}return(b[0]|b[1]<<8|b[2]<<16|b[3]<<24)>>>0}function jstoi_q(str){return parseInt(str)}function inetPton6(str){var words;var w,offset,z;var valid6regx=/^((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\3)::|:\b|$))|(?!\2\3)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})$/i;var parts=[];if(!valid6regx.test(str)){return null}if(str==="::"){return[0,0,0,0,0,0,0,0]}if(str.startsWith("::")){str=str.replace("::","Z:")}else{str=str.replace("::",":Z:")}if(str.indexOf(".")>0){str=str.replace(new RegExp("[.]","g"),":");words=str.split(":");words[words.length-4]=jstoi_q(words[words.length-4])+jstoi_q(words[words.length-3])*256;words[words.length-3]=jstoi_q(words[words.length-2])+jstoi_q(words[words.length-1])*256;words=words.slice(0,words.length-2)}else{words=str.split(":")}offset=0;z=0;for(w=0;w>2]=stat.dev;HEAP32[buf+8>>2]=stat.ino;HEAP32[buf+12>>2]=stat.mode;HEAPU32[buf+16>>2]=stat.nlink;HEAP32[buf+20>>2]=stat.uid;HEAP32[buf+24>>2]=stat.gid;HEAP32[buf+28>>2]=stat.rdev;tempI64=[stat.size>>>0,(tempDouble=stat.size,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+40>>2]=tempI64[0],HEAP32[buf+44>>2]=tempI64[1];HEAP32[buf+48>>2]=4096;HEAP32[buf+52>>2]=stat.blocks;var atime=stat.atime.getTime();var mtime=stat.mtime.getTime();var ctime=stat.ctime.getTime();tempI64=[Math.floor(atime/1e3)>>>0,(tempDouble=Math.floor(atime/1e3),+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+56>>2]=tempI64[0],HEAP32[buf+60>>2]=tempI64[1];HEAPU32[buf+64>>2]=atime%1e3*1e3;tempI64=[Math.floor(mtime/1e3)>>>0,(tempDouble=Math.floor(mtime/1e3),+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+72>>2]=tempI64[0],HEAP32[buf+76>>2]=tempI64[1];HEAPU32[buf+80>>2]=mtime%1e3*1e3;tempI64=[Math.floor(ctime/1e3)>>>0,(tempDouble=Math.floor(ctime/1e3),+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+88>>2]=tempI64[0],HEAP32[buf+92>>2]=tempI64[1];HEAPU32[buf+96>>2]=ctime%1e3*1e3;tempI64=[stat.ino>>>0,(tempDouble=stat.ino,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+104>>2]=tempI64[0],HEAP32[buf+108>>2]=tempI64[1];return 0},doMsync:function(addr,stream,len,flags,offset){if(!FS.isFile(stream.node.mode)){throw new FS.ErrnoError(43)}if(flags&2){return 0}var buffer=HEAPU8.slice(addr,addr+len);FS.msync(stream,buffer,offset,len,flags)},varargs:undefined,get:function(){SYSCALLS.varargs+=4;var ret=HEAP32[SYSCALLS.varargs-4>>2];return ret},getStr:function(ptr){var ret=UTF8ToString(ptr);return ret},getStreamFromFD:function(fd){var stream=FS.getStream(fd);if(!stream)throw new FS.ErrnoError(8);return stream}};function ___syscall_connect(fd,addr,addrlen){try{var sock=getSocketFromFD(fd);var info=getSocketAddress(addr,addrlen);sock.sock_ops.connect(sock,info.addr,info.port);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_faccessat(dirfd,path,amode,flags){try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);if(amode&~7){return-28}var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;if(!node){return-44}var perms="";if(amode&4)perms+="r";if(amode&2)perms+="w";if(amode&1)perms+="x";if(perms&&FS.nodePermissions(node,perms)){return-2}return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_fcntl64(fd,cmd,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(cmd){case 0:{var arg=SYSCALLS.get();if(arg<0){return-28}var newStream;newStream=FS.createStream(stream,arg);return newStream.fd}case 1:case 2:return 0;case 3:return stream.flags;case 4:{var arg=SYSCALLS.get();stream.flags|=arg;return 0}case 5:{var arg=SYSCALLS.get();var offset=0;HEAP16[arg+offset>>1]=2;return 0}case 6:case 7:return 0;case 16:case 8:return-28;case 9:setErrNo(28);return-1;default:{return-28}}}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_fstat64(fd,buf){try{var stream=SYSCALLS.getStreamFromFD(fd);return SYSCALLS.doStat(FS.stat,stream.path,buf)}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function convertI32PairToI53Checked(lo,hi){return hi+2097152>>>0<4194305-!!lo?(lo>>>0)+hi*4294967296:NaN}function ___syscall_ftruncate64(fd,length_low,length_high){try{var length=convertI32PairToI53Checked(length_low,length_high);if(isNaN(length))return-61;FS.ftruncate(fd,length);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_ioctl(fd,op,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(op){case 21509:case 21505:{if(!stream.tty)return-59;return 0}case 21510:case 21511:case 21512:case 21506:case 21507:case 21508:{if(!stream.tty)return-59;return 0}case 21519:{if(!stream.tty)return-59;var argp=SYSCALLS.get();HEAP32[argp>>2]=0;return 0}case 21520:{if(!stream.tty)return-59;return-28}case 21531:{var argp=SYSCALLS.get();return FS.ioctl(stream,op,argp)}case 21523:{if(!stream.tty)return-59;return 0}case 21524:{if(!stream.tty)return-59;return 0}default:return-28}}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_lstat64(path,buf){try{path=SYSCALLS.getStr(path);return SYSCALLS.doStat(FS.lstat,path,buf)}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_newfstatat(dirfd,path,buf,flags){try{path=SYSCALLS.getStr(path);var nofollow=flags&256;var allowEmpty=flags&4096;flags=flags&~6400;path=SYSCALLS.calculateAt(dirfd,path,allowEmpty);return SYSCALLS.doStat(nofollow?FS.lstat:FS.stat,path,buf)}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_openat(dirfd,path,flags,varargs){SYSCALLS.varargs=varargs;try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);var mode=varargs?SYSCALLS.get():0;return FS.open(path,flags,mode).fd}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_socket(domain,type,protocol){try{var sock=SOCKFS.createSocket(domain,type,protocol);return sock.stream.fd}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_stat64(path,buf){try{path=SYSCALLS.getStr(path);return SYSCALLS.doStat(FS.stat,path,buf)}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}var tupleRegistrations={};function runDestructors(destructors){while(destructors.length){var ptr=destructors.pop();var del=destructors.pop();del(ptr)}}function simpleReadValueFromPointer(pointer){return this["fromWireType"](HEAP32[pointer>>2])}var awaitingDependencies={};var registeredTypes={};var typeDependencies={};var char_0=48;var char_9=57;function makeLegalFunctionName(name){if(undefined===name){return"_unknown"}name=name.replace(/[^a-zA-Z0-9_]/g,"$");var f=name.charCodeAt(0);if(f>=char_0&&f<=char_9){return"_"+name}return name}function createNamedFunction(name,body){name=makeLegalFunctionName(name);return new Function("body","return function "+name+"() {\n"+' "use strict";'+" return body.apply(this, arguments);\n"+"};\n")(body)}function extendError(baseErrorType,errorName){var errorClass=createNamedFunction(errorName,function(message){this.name=errorName;this.message=message;var stack=new Error(message).stack;if(stack!==undefined){this.stack=this.toString()+"\n"+stack.replace(/^Error(:[^\n]*)?\n/,"")}});errorClass.prototype=Object.create(baseErrorType.prototype);errorClass.prototype.constructor=errorClass;errorClass.prototype.toString=function(){if(this.message===undefined){return this.name}else{return this.name+": "+this.message}};return errorClass}var InternalError=undefined;function throwInternalError(message){throw new InternalError(message)}function whenDependentTypesAreResolved(myTypes,dependentTypes,getTypeConverters){myTypes.forEach(function(type){typeDependencies[type]=dependentTypes});function onComplete(typeConverters){var myTypeConverters=getTypeConverters(typeConverters);if(myTypeConverters.length!==myTypes.length){throwInternalError("Mismatched type converter count")}for(var i=0;i{if(registeredTypes.hasOwnProperty(dt)){typeConverters[i]=registeredTypes[dt]}else{unregisteredTypes.push(dt);if(!awaitingDependencies.hasOwnProperty(dt)){awaitingDependencies[dt]=[]}awaitingDependencies[dt].push(()=>{typeConverters[i]=registeredTypes[dt];++registered;if(registered===unregisteredTypes.length){onComplete(typeConverters)}})}});if(0===unregisteredTypes.length){onComplete(typeConverters)}}function __embind_finalize_value_array(rawTupleType){var reg=tupleRegistrations[rawTupleType];delete tupleRegistrations[rawTupleType];var elements=reg.elements;var elementsLength=elements.length;var elementTypes=elements.map(function(elt){return elt.getterReturnType}).concat(elements.map(function(elt){return elt.setterArgumentType}));var rawConstructor=reg.rawConstructor;var rawDestructor=reg.rawDestructor;whenDependentTypesAreResolved([rawTupleType],elementTypes,function(elementTypes){elements.forEach((elt,i)=>{var getterReturnType=elementTypes[i];var getter=elt.getter;var getterContext=elt.getterContext;var setterArgumentType=elementTypes[i+elementsLength];var setter=elt.setter;var setterContext=elt.setterContext;elt.read=ptr=>{return getterReturnType["fromWireType"](getter(getterContext,ptr))};elt.write=(ptr,o)=>{var destructors=[];setter(setterContext,ptr,setterArgumentType["toWireType"](destructors,o));runDestructors(destructors)}});return[{name:reg.name,"fromWireType":function(ptr){var rv=new Array(elementsLength);for(var i=0;ifield.getterReturnType).concat(fieldRecords.map(field=>field.setterArgumentType));whenDependentTypesAreResolved([structType],fieldTypes,fieldTypes=>{var fields={};fieldRecords.forEach((field,i)=>{var fieldName=field.fieldName;var getterReturnType=fieldTypes[i];var getter=field.getter;var getterContext=field.getterContext;var setterArgumentType=fieldTypes[i+fieldRecords.length];var setter=field.setter;var setterContext=field.setterContext;fields[fieldName]={read:ptr=>{return getterReturnType["fromWireType"](getter(getterContext,ptr))},write:(ptr,o)=>{var destructors=[];setter(setterContext,ptr,setterArgumentType["toWireType"](destructors,o));runDestructors(destructors)}}});return[{name:reg.name,"fromWireType":function(ptr){var rv={};for(var i in fields){rv[i]=fields[i].read(ptr)}rawDestructor(ptr);return rv},"toWireType":function(destructors,o){for(var fieldName in fields){if(!(fieldName in o)){throw new TypeError('Missing field: "'+fieldName+'"')}}var ptr=rawConstructor();for(fieldName in fields){fields[fieldName].write(ptr,o[fieldName])}if(destructors!==null){destructors.push(rawDestructor,ptr)}return ptr},"argPackAdvance":8,"readValueFromPointer":simpleReadValueFromPointer,destructorFunction:rawDestructor}]})}function __embind_register_bigint(primitiveType,name,size,minRange,maxRange){}function getShiftFromSize(size){switch(size){case 1:return 0;case 2:return 1;case 4:return 2;case 8:return 3;default:throw new TypeError("Unknown type size: "+size)}}function embind_init_charCodes(){var codes=new Array(256);for(var i=0;i<256;++i){codes[i]=String.fromCharCode(i)}embind_charCodes=codes}var embind_charCodes=undefined;function readLatin1String(ptr){var ret="";var c=ptr;while(HEAPU8[c]){ret+=embind_charCodes[HEAPU8[c++]]}return ret}var BindingError=undefined;function throwBindingError(message){throw new BindingError(message)}function registerType(rawType,registeredInstance,options={}){if(!("argPackAdvance"in registeredInstance)){throw new TypeError("registerType registeredInstance requires argPackAdvance")}var name=registeredInstance.name;if(!rawType){throwBindingError('type "'+name+'" must have a positive integer typeid pointer')}if(registeredTypes.hasOwnProperty(rawType)){if(options.ignoreDuplicateRegistrations){return}else{throwBindingError("Cannot register type '"+name+"' twice")}}registeredTypes[rawType]=registeredInstance;delete typeDependencies[rawType];if(awaitingDependencies.hasOwnProperty(rawType)){var callbacks=awaitingDependencies[rawType];delete awaitingDependencies[rawType];callbacks.forEach(cb=>cb())}}function __embind_register_bool(rawType,name,size,trueValue,falseValue){var shift=getShiftFromSize(size);name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":function(wt){return!!wt},"toWireType":function(destructors,o){return o?trueValue:falseValue},"argPackAdvance":8,"readValueFromPointer":function(pointer){var heap;if(size===1){heap=HEAP8}else if(size===2){heap=HEAP16}else if(size===4){heap=HEAP32}else{throw new TypeError("Unknown boolean type size: "+name)}return this["fromWireType"](heap[pointer>>shift])},destructorFunction:null})}function ClassHandle_isAliasOf(other){if(!(this instanceof ClassHandle)){return false}if(!(other instanceof ClassHandle)){return false}var leftClass=this.$$.ptrType.registeredClass;var left=this.$$.ptr;var rightClass=other.$$.ptrType.registeredClass;var right=other.$$.ptr;while(leftClass.baseClass){left=leftClass.upcast(left);leftClass=leftClass.baseClass}while(rightClass.baseClass){right=rightClass.upcast(right);rightClass=rightClass.baseClass}return leftClass===rightClass&&left===right}function shallowCopyInternalPointer(o){return{count:o.count,deleteScheduled:o.deleteScheduled,preservePointerOnDelete:o.preservePointerOnDelete,ptr:o.ptr,ptrType:o.ptrType,smartPtr:o.smartPtr,smartPtrType:o.smartPtrType}}function throwInstanceAlreadyDeleted(obj){function getInstanceTypeName(handle){return handle.$$.ptrType.registeredClass.name}throwBindingError(getInstanceTypeName(obj)+" instance already deleted")}var finalizationRegistry=false;function detachFinalizer(handle){}function runDestructor($$){if($$.smartPtr){$$.smartPtrType.rawDestructor($$.smartPtr)}else{$$.ptrType.registeredClass.rawDestructor($$.ptr)}}function releaseClassHandle($$){$$.count.value-=1;var toDelete=0===$$.count.value;if(toDelete){runDestructor($$)}}function downcastPointer(ptr,ptrClass,desiredClass){if(ptrClass===desiredClass){return ptr}if(undefined===desiredClass.baseClass){return null}var rv=downcastPointer(ptr,ptrClass,desiredClass.baseClass);if(rv===null){return null}return desiredClass.downcast(rv)}var registeredPointers={};function getInheritedInstanceCount(){return Object.keys(registeredInstances).length}function getLiveInheritedInstances(){var rv=[];for(var k in registeredInstances){if(registeredInstances.hasOwnProperty(k)){rv.push(registeredInstances[k])}}return rv}var deletionQueue=[];function flushPendingDeletes(){while(deletionQueue.length){var obj=deletionQueue.pop();obj.$$.deleteScheduled=false;obj["delete"]()}}var delayFunction=undefined;function setDelayFunction(fn){delayFunction=fn;if(deletionQueue.length&&delayFunction){delayFunction(flushPendingDeletes)}}function init_embind(){Module["getInheritedInstanceCount"]=getInheritedInstanceCount;Module["getLiveInheritedInstances"]=getLiveInheritedInstances;Module["flushPendingDeletes"]=flushPendingDeletes;Module["setDelayFunction"]=setDelayFunction}var registeredInstances={};function getBasestPointer(class_,ptr){if(ptr===undefined){throwBindingError("ptr should not be undefined")}while(class_.baseClass){ptr=class_.upcast(ptr);class_=class_.baseClass}return ptr}function getInheritedInstance(class_,ptr){ptr=getBasestPointer(class_,ptr);return registeredInstances[ptr]}function makeClassHandle(prototype,record){if(!record.ptrType||!record.ptr){throwInternalError("makeClassHandle requires ptr and ptrType")}var hasSmartPtrType=!!record.smartPtrType;var hasSmartPtr=!!record.smartPtr;if(hasSmartPtrType!==hasSmartPtr){throwInternalError("Both smartPtrType and smartPtr must be specified")}record.count={value:1};return attachFinalizer(Object.create(prototype,{$$:{value:record}}))}function RegisteredPointer_fromWireType(ptr){var rawPointer=this.getPointee(ptr);if(!rawPointer){this.destructor(ptr);return null}var registeredInstance=getInheritedInstance(this.registeredClass,rawPointer);if(undefined!==registeredInstance){if(0===registeredInstance.$$.count.value){registeredInstance.$$.ptr=rawPointer;registeredInstance.$$.smartPtr=ptr;return registeredInstance["clone"]()}else{var rv=registeredInstance["clone"]();this.destructor(ptr);return rv}}function makeDefaultHandle(){if(this.isSmartPointer){return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this.pointeeType,ptr:rawPointer,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this,ptr:ptr})}}var actualType=this.registeredClass.getActualType(rawPointer);var registeredPointerRecord=registeredPointers[actualType];if(!registeredPointerRecord){return makeDefaultHandle.call(this)}var toType;if(this.isConst){toType=registeredPointerRecord.constPointerType}else{toType=registeredPointerRecord.pointerType}var dp=downcastPointer(rawPointer,this.registeredClass,toType.registeredClass);if(dp===null){return makeDefaultHandle.call(this)}if(this.isSmartPointer){return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp})}}function attachFinalizer(handle){if("undefined"===typeof FinalizationRegistry){attachFinalizer=handle=>handle;return handle}finalizationRegistry=new FinalizationRegistry(info=>{releaseClassHandle(info.$$)});attachFinalizer=handle=>{var $$=handle.$$;var hasSmartPtr=!!$$.smartPtr;if(hasSmartPtr){var info={$$:$$};finalizationRegistry.register(handle,info,handle)}return handle};detachFinalizer=handle=>finalizationRegistry.unregister(handle);return attachFinalizer(handle)}function ClassHandle_clone(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.preservePointerOnDelete){this.$$.count.value+=1;return this}else{var clone=attachFinalizer(Object.create(Object.getPrototypeOf(this),{$$:{value:shallowCopyInternalPointer(this.$$)}}));clone.$$.count.value+=1;clone.$$.deleteScheduled=false;return clone}}function ClassHandle_delete(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}detachFinalizer(this);releaseClassHandle(this.$$);if(!this.$$.preservePointerOnDelete){this.$$.smartPtr=undefined;this.$$.ptr=undefined}}function ClassHandle_isDeleted(){return!this.$$.ptr}function ClassHandle_deleteLater(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}deletionQueue.push(this);if(deletionQueue.length===1&&delayFunction){delayFunction(flushPendingDeletes)}this.$$.deleteScheduled=true;return this}function init_ClassHandle(){ClassHandle.prototype["isAliasOf"]=ClassHandle_isAliasOf;ClassHandle.prototype["clone"]=ClassHandle_clone;ClassHandle.prototype["delete"]=ClassHandle_delete;ClassHandle.prototype["isDeleted"]=ClassHandle_isDeleted;ClassHandle.prototype["deleteLater"]=ClassHandle_deleteLater}function ClassHandle(){}function ensureOverloadTable(proto,methodName,humanName){if(undefined===proto[methodName].overloadTable){var prevFunc=proto[methodName];proto[methodName]=function(){if(!proto[methodName].overloadTable.hasOwnProperty(arguments.length)){throwBindingError("Function '"+humanName+"' called with an invalid number of arguments ("+arguments.length+") - expects one of ("+proto[methodName].overloadTable+")!")}return proto[methodName].overloadTable[arguments.length].apply(this,arguments)};proto[methodName].overloadTable=[];proto[methodName].overloadTable[prevFunc.argCount]=prevFunc}}function exposePublicSymbol(name,value,numArguments){if(Module.hasOwnProperty(name)){if(undefined===numArguments||undefined!==Module[name].overloadTable&&undefined!==Module[name].overloadTable[numArguments]){throwBindingError("Cannot register public name '"+name+"' twice")}ensureOverloadTable(Module,name,name);if(Module.hasOwnProperty(numArguments)){throwBindingError("Cannot register multiple overloads of a function with the same number of arguments ("+numArguments+")!")}Module[name].overloadTable[numArguments]=value}else{Module[name]=value;if(undefined!==numArguments){Module[name].numArguments=numArguments}}}function RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast){this.name=name;this.constructor=constructor;this.instancePrototype=instancePrototype;this.rawDestructor=rawDestructor;this.baseClass=baseClass;this.getActualType=getActualType;this.upcast=upcast;this.downcast=downcast;this.pureVirtualFunctions=[]}function upcastPointer(ptr,ptrClass,desiredClass){while(ptrClass!==desiredClass){if(!ptrClass.upcast){throwBindingError("Expected null or instance of "+desiredClass.name+", got an instance of "+ptrClass.name)}ptr=ptrClass.upcast(ptr);ptrClass=ptrClass.baseClass}return ptr}function constNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError("null is not a valid "+this.name)}return 0}if(!handle.$$){throwBindingError('Cannot pass "'+embindRepr(handle)+'" as a '+this.name)}if(!handle.$$.ptr){throwBindingError("Cannot pass deleted object as a pointer of type "+this.name)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function genericPointerToWireType(destructors,handle){var ptr;if(handle===null){if(this.isReference){throwBindingError("null is not a valid "+this.name)}if(this.isSmartPointer){ptr=this.rawConstructor();if(destructors!==null){destructors.push(this.rawDestructor,ptr)}return ptr}else{return 0}}if(!handle.$$){throwBindingError('Cannot pass "'+embindRepr(handle)+'" as a '+this.name)}if(!handle.$$.ptr){throwBindingError("Cannot pass deleted object as a pointer of type "+this.name)}if(!this.isConst&&handle.$$.ptrType.isConst){throwBindingError("Cannot convert argument of type "+(handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name)+" to parameter type "+this.name)}var handleClass=handle.$$.ptrType.registeredClass;ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);if(this.isSmartPointer){if(undefined===handle.$$.smartPtr){throwBindingError("Passing raw pointer to smart pointer is illegal")}switch(this.sharingPolicy){case 0:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{throwBindingError("Cannot convert argument of type "+(handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name)+" to parameter type "+this.name)}break;case 1:ptr=handle.$$.smartPtr;break;case 2:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{var clonedHandle=handle["clone"]();ptr=this.rawShare(ptr,Emval.toHandle(function(){clonedHandle["delete"]()}));if(destructors!==null){destructors.push(this.rawDestructor,ptr)}}break;default:throwBindingError("Unsupporting sharing policy")}}return ptr}function nonConstNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError("null is not a valid "+this.name)}return 0}if(!handle.$$){throwBindingError('Cannot pass "'+embindRepr(handle)+'" as a '+this.name)}if(!handle.$$.ptr){throwBindingError("Cannot pass deleted object as a pointer of type "+this.name)}if(handle.$$.ptrType.isConst){throwBindingError("Cannot convert argument of type "+handle.$$.ptrType.name+" to parameter type "+this.name)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function RegisteredPointer_getPointee(ptr){if(this.rawGetPointee){ptr=this.rawGetPointee(ptr)}return ptr}function RegisteredPointer_destructor(ptr){if(this.rawDestructor){this.rawDestructor(ptr)}}function RegisteredPointer_deleteObject(handle){if(handle!==null){handle["delete"]()}}function init_RegisteredPointer(){RegisteredPointer.prototype.getPointee=RegisteredPointer_getPointee;RegisteredPointer.prototype.destructor=RegisteredPointer_destructor;RegisteredPointer.prototype["argPackAdvance"]=8;RegisteredPointer.prototype["readValueFromPointer"]=simpleReadValueFromPointer;RegisteredPointer.prototype["deleteObject"]=RegisteredPointer_deleteObject;RegisteredPointer.prototype["fromWireType"]=RegisteredPointer_fromWireType}function RegisteredPointer(name,registeredClass,isReference,isConst,isSmartPointer,pointeeType,sharingPolicy,rawGetPointee,rawConstructor,rawShare,rawDestructor){this.name=name;this.registeredClass=registeredClass;this.isReference=isReference;this.isConst=isConst;this.isSmartPointer=isSmartPointer;this.pointeeType=pointeeType;this.sharingPolicy=sharingPolicy;this.rawGetPointee=rawGetPointee;this.rawConstructor=rawConstructor;this.rawShare=rawShare;this.rawDestructor=rawDestructor;if(!isSmartPointer&®isteredClass.baseClass===undefined){if(isConst){this["toWireType"]=constNoSmartPtrRawPointerToWireType;this.destructorFunction=null}else{this["toWireType"]=nonConstNoSmartPtrRawPointerToWireType;this.destructorFunction=null}}else{this["toWireType"]=genericPointerToWireType}}function replacePublicSymbol(name,value,numArguments){if(!Module.hasOwnProperty(name)){throwInternalError("Replacing nonexistant public symbol")}if(undefined!==Module[name].overloadTable&&undefined!==numArguments){Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}}function dynCallLegacy(sig,ptr,args){var f=Module["dynCall_"+sig];return args&&args.length?f.apply(null,[ptr].concat(args)):f.call(null,ptr)}function getWasmTableEntry(funcPtr){return wasmTable.get(funcPtr)}function dynCall(sig,ptr,args){if(sig.includes("j")){return dynCallLegacy(sig,ptr,args)}var rtn=getWasmTableEntry(ptr).apply(null,args);return rtn}function getDynCaller(sig,ptr){var argCache=[];return function(){argCache.length=0;Object.assign(argCache,arguments);return dynCall(sig,ptr,argCache)}}function embind__requireFunction(signature,rawFunction){signature=readLatin1String(signature);function makeDynCaller(){if(signature.includes("j")){return getDynCaller(signature,rawFunction)}return getWasmTableEntry(rawFunction)}var fp=makeDynCaller();if(typeof fp!="function"){throwBindingError("unknown function pointer with signature "+signature+": "+rawFunction)}return fp}var UnboundTypeError=undefined;function getTypeName(type){var ptr=___getTypeName(type);var rv=readLatin1String(ptr);_free(ptr);return rv}function throwUnboundTypeError(message,types){var unboundTypes=[];var seen={};function visit(type){if(seen[type]){return}if(registeredTypes[type]){return}if(typeDependencies[type]){typeDependencies[type].forEach(visit);return}unboundTypes.push(type);seen[type]=true}types.forEach(visit);throw new UnboundTypeError(message+": "+unboundTypes.map(getTypeName).join([", "]))}function __embind_register_class(rawType,rawPointerType,rawConstPointerType,baseClassRawType,getActualTypeSignature,getActualType,upcastSignature,upcast,downcastSignature,downcast,name,destructorSignature,rawDestructor){name=readLatin1String(name);getActualType=embind__requireFunction(getActualTypeSignature,getActualType);if(upcast){upcast=embind__requireFunction(upcastSignature,upcast)}if(downcast){downcast=embind__requireFunction(downcastSignature,downcast)}rawDestructor=embind__requireFunction(destructorSignature,rawDestructor);var legalFunctionName=makeLegalFunctionName(name);exposePublicSymbol(legalFunctionName,function(){throwUnboundTypeError("Cannot construct "+name+" due to unbound types",[baseClassRawType])});whenDependentTypesAreResolved([rawType,rawPointerType,rawConstPointerType],baseClassRawType?[baseClassRawType]:[],function(base){base=base[0];var baseClass;var basePrototype;if(baseClassRawType){baseClass=base.registeredClass;basePrototype=baseClass.instancePrototype}else{basePrototype=ClassHandle.prototype}var constructor=createNamedFunction(legalFunctionName,function(){if(Object.getPrototypeOf(this)!==instancePrototype){throw new BindingError("Use 'new' to construct "+name)}if(undefined===registeredClass.constructor_body){throw new BindingError(name+" has no accessible constructor")}var body=registeredClass.constructor_body[arguments.length];if(undefined===body){throw new BindingError("Tried to invoke ctor of "+name+" with invalid number of parameters ("+arguments.length+") - expected ("+Object.keys(registeredClass.constructor_body).toString()+") parameters instead!")}return body.apply(this,arguments)});var instancePrototype=Object.create(basePrototype,{constructor:{value:constructor}});constructor.prototype=instancePrototype;var registeredClass=new RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast);var referenceConverter=new RegisteredPointer(name,registeredClass,true,false,false);var pointerConverter=new RegisteredPointer(name+"*",registeredClass,false,false,false);var constPointerConverter=new RegisteredPointer(name+" const*",registeredClass,false,true,false);registeredPointers[rawType]={pointerType:pointerConverter,constPointerType:constPointerConverter};replacePublicSymbol(legalFunctionName,constructor);return[referenceConverter,pointerConverter,constPointerConverter]})}function new_(constructor,argumentList){if(!(constructor instanceof Function)){throw new TypeError("new_ called with constructor type "+typeof constructor+" which is not a function")}var dummy=createNamedFunction(constructor.name||"unknownFunctionName",function(){});dummy.prototype=constructor.prototype;var obj=new dummy;var r=constructor.apply(obj,argumentList);return r instanceof Object?r:obj}function craftInvokerFunction(humanName,argTypes,classType,cppInvokerFunc,cppTargetFunc){var argCount=argTypes.length;if(argCount<2){throwBindingError("argTypes array size mismatch! Must at least get return value and 'this' types!")}var isClassMethodFunc=argTypes[1]!==null&&classType!==null;var needsDestructorStack=false;for(var i=1;i0?", ":"")+argsListWired}invokerFnBody+=(returns?"var rv = ":"")+"invoker(fn"+(argsListWired.length>0?", ":"")+argsListWired+");\n";if(needsDestructorStack){invokerFnBody+="runDestructors(destructors);\n"}else{for(var i=isClassMethodFunc?1:2;i>2])}return array}function __embind_register_class_class_function(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,fn){var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=readLatin1String(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker);whenDependentTypesAreResolved([],[rawClassType],function(classType){classType=classType[0];var humanName=classType.name+"."+methodName;function unboundTypesHandler(){throwUnboundTypeError("Cannot call "+humanName+" due to unbound types",rawArgTypes)}if(methodName.startsWith("@@")){methodName=Symbol[methodName.substring(2)]}var proto=classType.registeredClass.constructor;if(undefined===proto[methodName]){unboundTypesHandler.argCount=argCount-1;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-1]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,function(argTypes){var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));var func=craftInvokerFunction(humanName,invokerArgsArray,null,rawInvoker,fn);if(undefined===proto[methodName].overloadTable){func.argCount=argCount-1;proto[methodName]=func}else{proto[methodName].overloadTable[argCount-1]=func}return[]});return[]})}function __embind_register_class_constructor(rawClassType,argCount,rawArgTypesAddr,invokerSignature,invoker,rawConstructor){assert(argCount>0);var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);invoker=embind__requireFunction(invokerSignature,invoker);whenDependentTypesAreResolved([],[rawClassType],function(classType){classType=classType[0];var humanName="constructor "+classType.name;if(undefined===classType.registeredClass.constructor_body){classType.registeredClass.constructor_body=[]}if(undefined!==classType.registeredClass.constructor_body[argCount-1]){throw new BindingError("Cannot register multiple constructors with identical number of parameters ("+(argCount-1)+") for class '"+classType.name+"'! Overload resolution is currently only performed using the parameter count, not actual type info!")}classType.registeredClass.constructor_body[argCount-1]=()=>{throwUnboundTypeError("Cannot construct "+classType.name+" due to unbound types",rawArgTypes)};whenDependentTypesAreResolved([],rawArgTypes,function(argTypes){argTypes.splice(1,0,null);classType.registeredClass.constructor_body[argCount-1]=craftInvokerFunction(humanName,argTypes,null,invoker,rawConstructor);return[]});return[]})}function __embind_register_class_function(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,context,isPureVirtual){var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=readLatin1String(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker);whenDependentTypesAreResolved([],[rawClassType],function(classType){classType=classType[0];var humanName=classType.name+"."+methodName;if(methodName.startsWith("@@")){methodName=Symbol[methodName.substring(2)]}if(isPureVirtual){classType.registeredClass.pureVirtualFunctions.push(methodName)}function unboundTypesHandler(){throwUnboundTypeError("Cannot call "+humanName+" due to unbound types",rawArgTypes)}var proto=classType.registeredClass.instancePrototype;var method=proto[methodName];if(undefined===method||undefined===method.overloadTable&&method.className!==classType.name&&method.argCount===argCount-2){unboundTypesHandler.argCount=argCount-2;unboundTypesHandler.className=classType.name;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-2]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,function(argTypes){var memberFunction=craftInvokerFunction(humanName,argTypes,classType,rawInvoker,context);if(undefined===proto[methodName].overloadTable){memberFunction.argCount=argCount-2;proto[methodName]=memberFunction}else{proto[methodName].overloadTable[argCount-2]=memberFunction}return[]});return[]})}function validateThis(this_,classType,humanName){if(!(this_ instanceof Object)){throwBindingError(humanName+' with invalid "this": '+this_)}if(!(this_ instanceof classType.registeredClass.constructor)){throwBindingError(humanName+' incompatible with "this" of type '+this_.constructor.name)}if(!this_.$$.ptr){throwBindingError("cannot call emscripten binding method "+humanName+" on deleted object")}return upcastPointer(this_.$$.ptr,this_.$$.ptrType.registeredClass,classType.registeredClass)}function __embind_register_class_property(classType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext){fieldName=readLatin1String(fieldName);getter=embind__requireFunction(getterSignature,getter);whenDependentTypesAreResolved([],[classType],function(classType){classType=classType[0];var humanName=classType.name+"."+fieldName;var desc={get:function(){throwUnboundTypeError("Cannot access "+humanName+" due to unbound types",[getterReturnType,setterArgumentType])},enumerable:true,configurable:true};if(setter){desc.set=()=>{throwUnboundTypeError("Cannot access "+humanName+" due to unbound types",[getterReturnType,setterArgumentType])}}else{desc.set=v=>{throwBindingError(humanName+" is a read-only property")}}Object.defineProperty(classType.registeredClass.instancePrototype,fieldName,desc);whenDependentTypesAreResolved([],setter?[getterReturnType,setterArgumentType]:[getterReturnType],function(types){var getterReturnType=types[0];var desc={get:function(){var ptr=validateThis(this,classType,humanName+" getter");return getterReturnType["fromWireType"](getter(getterContext,ptr))},enumerable:true};if(setter){setter=embind__requireFunction(setterSignature,setter);var setterArgumentType=types[1];desc.set=function(v){var ptr=validateThis(this,classType,humanName+" setter");var destructors=[];setter(setterContext,ptr,setterArgumentType["toWireType"](destructors,v));runDestructors(destructors)}}Object.defineProperty(classType.registeredClass.instancePrototype,fieldName,desc);return[]});return[]})}var emval_free_list=[];var emval_handle_array=[{},{value:undefined},{value:null},{value:true},{value:false}];function __emval_decref(handle){if(handle>4&&0===--emval_handle_array[handle].refcount){emval_handle_array[handle]=undefined;emval_free_list.push(handle)}}function count_emval_handles(){var count=0;for(var i=5;i{if(!handle){throwBindingError("Cannot use deleted val. handle = "+handle)}return emval_handle_array[handle].value},toHandle:value=>{switch(value){case undefined:return 1;case null:return 2;case true:return 3;case false:return 4;default:{var handle=emval_free_list.length?emval_free_list.pop():emval_handle_array.length;emval_handle_array[handle]={refcount:1,value:value};return handle}}}};function __embind_register_emval(rawType,name){name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":function(handle){var rv=Emval.toValue(handle);__emval_decref(handle);return rv},"toWireType":function(destructors,value){return Emval.toHandle(value)},"argPackAdvance":8,"readValueFromPointer":simpleReadValueFromPointer,destructorFunction:null})}function enumReadValueFromPointer(name,shift,signed){switch(shift){case 0:return function(pointer){var heap=signed?HEAP8:HEAPU8;return this["fromWireType"](heap[pointer])};case 1:return function(pointer){var heap=signed?HEAP16:HEAPU16;return this["fromWireType"](heap[pointer>>1])};case 2:return function(pointer){var heap=signed?HEAP32:HEAPU32;return this["fromWireType"](heap[pointer>>2])};default:throw new TypeError("Unknown integer type: "+name)}}function __embind_register_enum(rawType,name,size,isSigned){var shift=getShiftFromSize(size);name=readLatin1String(name);function ctor(){}ctor.values={};registerType(rawType,{name:name,constructor:ctor,"fromWireType":function(c){return this.constructor.values[c]},"toWireType":function(destructors,c){return c.value},"argPackAdvance":8,"readValueFromPointer":enumReadValueFromPointer(name,shift,isSigned),destructorFunction:null});exposePublicSymbol(name,ctor)}function requireRegisteredType(rawType,humanName){var impl=registeredTypes[rawType];if(undefined===impl){throwBindingError(humanName+" has unknown type "+getTypeName(rawType))}return impl}function __embind_register_enum_value(rawEnumType,name,enumValue){var enumType=requireRegisteredType(rawEnumType,"enum");name=readLatin1String(name);var Enum=enumType.constructor;var Value=Object.create(enumType.constructor.prototype,{value:{value:enumValue},constructor:{value:createNamedFunction(enumType.name+"_"+name,function(){})}});Enum.values[enumValue]=Value;Enum[name]=Value}function embindRepr(v){if(v===null){return"null"}var t=typeof v;if(t==="object"||t==="array"||t==="function"){return v.toString()}else{return""+v}}function floatReadValueFromPointer(name,shift){switch(shift){case 2:return function(pointer){return this["fromWireType"](HEAPF32[pointer>>2])};case 3:return function(pointer){return this["fromWireType"](HEAPF64[pointer>>3])};default:throw new TypeError("Unknown float type: "+name)}}function __embind_register_float(rawType,name,size){var shift=getShiftFromSize(size);name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":function(value){return value},"toWireType":function(destructors,value){return value},"argPackAdvance":8,"readValueFromPointer":floatReadValueFromPointer(name,shift),destructorFunction:null})}function integerReadValueFromPointer(name,shift,signed){switch(shift){case 0:return signed?function readS8FromPointer(pointer){return HEAP8[pointer]}:function readU8FromPointer(pointer){return HEAPU8[pointer]};case 1:return signed?function readS16FromPointer(pointer){return HEAP16[pointer>>1]}:function readU16FromPointer(pointer){return HEAPU16[pointer>>1]};case 2:return signed?function readS32FromPointer(pointer){return HEAP32[pointer>>2]}:function readU32FromPointer(pointer){return HEAPU32[pointer>>2]};default:throw new TypeError("Unknown integer type: "+name)}}function __embind_register_integer(primitiveType,name,size,minRange,maxRange){name=readLatin1String(name);if(maxRange===-1){maxRange=4294967295}var shift=getShiftFromSize(size);var fromWireType=value=>value;if(minRange===0){var bitshift=32-8*size;fromWireType=value=>value<>>bitshift}var isUnsignedType=name.includes("unsigned");var checkAssertions=(value,toTypeName)=>{};var toWireType;if(isUnsignedType){toWireType=function(destructors,value){checkAssertions(value,this.name);return value>>>0}}else{toWireType=function(destructors,value){checkAssertions(value,this.name);return value}}registerType(primitiveType,{name:name,"fromWireType":fromWireType,"toWireType":toWireType,"argPackAdvance":8,"readValueFromPointer":integerReadValueFromPointer(name,shift,minRange!==0),destructorFunction:null})}function __embind_register_memory_view(rawType,dataTypeIndex,name){var typeMapping=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array];var TA=typeMapping[dataTypeIndex];function decodeMemoryView(handle){handle=handle>>2;var heap=HEAPU32;var size=heap[handle];var data=heap[handle+1];return new TA(heap.buffer,data,size)}name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":decodeMemoryView,"argPackAdvance":8,"readValueFromPointer":decodeMemoryView},{ignoreDuplicateRegistrations:true})}function __embind_register_std_string(rawType,name){name=readLatin1String(name);var stdStringIsUTF8=name==="std::string";registerType(rawType,{name:name,"fromWireType":function(value){var length=HEAPU32[value>>2];var payload=value+4;var str;if(stdStringIsUTF8){var decodeStartPtr=payload;for(var i=0;i<=length;++i){var currentBytePtr=payload+i;if(i==length||HEAPU8[currentBytePtr]==0){var maxRead=currentBytePtr-decodeStartPtr;var stringSegment=UTF8ToString(decodeStartPtr,maxRead);if(str===undefined){str=stringSegment}else{str+=String.fromCharCode(0);str+=stringSegment}decodeStartPtr=currentBytePtr+1}}}else{var a=new Array(length);for(var i=0;i>2]=length;if(stdStringIsUTF8&&valueIsOfTypeString){stringToUTF8(value,ptr,length+1)}else{if(valueIsOfTypeString){for(var i=0;i255){_free(ptr);throwBindingError("String has UTF-16 code units that do not fit in 8 bits")}HEAPU8[ptr+i]=charCode}}else{for(var i=0;i>1;var maxIdx=idx+maxBytesToRead/2;while(!(idx>=maxIdx)&&HEAPU16[idx])++idx;endPtr=idx<<1;if(endPtr-ptr>32&&UTF16Decoder)return UTF16Decoder.decode(HEAPU8.subarray(ptr,endPtr));var str="";for(var i=0;!(i>=maxBytesToRead/2);++i){var codeUnit=HEAP16[ptr+i*2>>1];if(codeUnit==0)break;str+=String.fromCharCode(codeUnit)}return str}function stringToUTF16(str,outPtr,maxBytesToWrite){if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<2)return 0;maxBytesToWrite-=2;var startPtr=outPtr;var numCharsToWrite=maxBytesToWrite>1]=codeUnit;outPtr+=2}HEAP16[outPtr>>1]=0;return outPtr-startPtr}function lengthBytesUTF16(str){return str.length*2}function UTF32ToString(ptr,maxBytesToRead){var i=0;var str="";while(!(i>=maxBytesToRead/4)){var utf32=HEAP32[ptr+i*4>>2];if(utf32==0)break;++i;if(utf32>=65536){var ch=utf32-65536;str+=String.fromCharCode(55296|ch>>10,56320|ch&1023)}else{str+=String.fromCharCode(utf32)}}return str}function stringToUTF32(str,outPtr,maxBytesToWrite){if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<4)return 0;var startPtr=outPtr;var endPtr=startPtr+maxBytesToWrite-4;for(var i=0;i=55296&&codeUnit<=57343){var trailSurrogate=str.charCodeAt(++i);codeUnit=65536+((codeUnit&1023)<<10)|trailSurrogate&1023}HEAP32[outPtr>>2]=codeUnit;outPtr+=4;if(outPtr+4>endPtr)break}HEAP32[outPtr>>2]=0;return outPtr-startPtr}function lengthBytesUTF32(str){var len=0;for(var i=0;i=55296&&codeUnit<=57343)++i;len+=4}return len}function __embind_register_std_wstring(rawType,charSize,name){name=readLatin1String(name);var decodeString,encodeString,getHeap,lengthBytesUTF,shift;if(charSize===2){decodeString=UTF16ToString;encodeString=stringToUTF16;lengthBytesUTF=lengthBytesUTF16;getHeap=()=>HEAPU16;shift=1}else if(charSize===4){decodeString=UTF32ToString;encodeString=stringToUTF32;lengthBytesUTF=lengthBytesUTF32;getHeap=()=>HEAPU32;shift=2}registerType(rawType,{name:name,"fromWireType":function(value){var length=HEAPU32[value>>2];var HEAP=getHeap();var str;var decodeStartPtr=value+4;for(var i=0;i<=length;++i){var currentBytePtr=value+4+i*charSize;if(i==length||HEAP[currentBytePtr>>shift]==0){var maxReadBytes=currentBytePtr-decodeStartPtr;var stringSegment=decodeString(decodeStartPtr,maxReadBytes);if(str===undefined){str=stringSegment}else{str+=String.fromCharCode(0);str+=stringSegment}decodeStartPtr=currentBytePtr+charSize}}_free(value);return str},"toWireType":function(destructors,value){if(!(typeof value=="string")){throwBindingError("Cannot pass non-string to C++ string type "+name)}var length=lengthBytesUTF(value);var ptr=_malloc(4+length+charSize);HEAPU32[ptr>>2]=length>>shift;encodeString(value,ptr+4,length+charSize);if(destructors!==null){destructors.push(_free,ptr)}return ptr},"argPackAdvance":8,"readValueFromPointer":simpleReadValueFromPointer,destructorFunction:function(ptr){_free(ptr)}})}function __embind_register_value_array(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor){tupleRegistrations[rawType]={name:readLatin1String(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),elements:[]}}function __embind_register_value_array_element(rawTupleType,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext){tupleRegistrations[rawTupleType].elements.push({getterReturnType:getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext:getterContext,setterArgumentType:setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext:setterContext})}function __embind_register_value_object(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor){structRegistrations[rawType]={name:readLatin1String(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),fields:[]}}function __embind_register_value_object_field(structType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext){structRegistrations[structType].fields.push({fieldName:readLatin1String(fieldName),getterReturnType:getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext:getterContext,setterArgumentType:setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext:setterContext})}function __embind_register_void(rawType,name){name=readLatin1String(name);registerType(rawType,{isVoid:true,name:name,"argPackAdvance":0,"fromWireType":function(){return undefined},"toWireType":function(destructors,o){return undefined}})}function __emval_as(handle,returnType,destructorsRef){handle=Emval.toValue(handle);returnType=requireRegisteredType(returnType,"emval::as");var destructors=[];var rd=Emval.toHandle(destructors);HEAPU32[destructorsRef>>2]=rd;return returnType["toWireType"](destructors,handle)}function emval_allocateDestructors(destructorsRef){var destructors=[];HEAPU32[destructorsRef>>2]=Emval.toHandle(destructors);return destructors}var emval_symbols={};function getStringOrSymbol(address){var symbol=emval_symbols[address];if(symbol===undefined){return readLatin1String(address)}return symbol}var emval_methodCallers=[];function __emval_call_method(caller,handle,methodName,destructorsRef,args){caller=emval_methodCallers[caller];handle=Emval.toValue(handle);methodName=getStringOrSymbol(methodName);return caller(handle,methodName,emval_allocateDestructors(destructorsRef),args)}function __emval_call_void_method(caller,handle,methodName,args){caller=emval_methodCallers[caller];handle=Emval.toValue(handle);methodName=getStringOrSymbol(methodName);caller(handle,methodName,null,args)}function __emval_equals(first,second){first=Emval.toValue(first);second=Emval.toValue(second);return first==second}function emval_get_global(){if(typeof globalThis=="object"){return globalThis}return function(){return Function}()("return this")()}function __emval_get_global(name){if(name===0){return Emval.toHandle(emval_get_global())}else{name=getStringOrSymbol(name);return Emval.toHandle(emval_get_global()[name])}}function emval_addMethodCaller(caller){var id=emval_methodCallers.length;emval_methodCallers.push(caller);return id}function emval_lookupTypes(argCount,argTypes){var a=new Array(argCount);for(var i=0;i>2],"parameter "+i)}return a}var emval_registeredMethods=[];function __emval_get_method_caller(argCount,argTypes){var types=emval_lookupTypes(argCount,argTypes);var retType=types[0];var signatureName=retType.name+"_$"+types.slice(1).map(function(t){return t.name}).join("_")+"$";var returnId=emval_registeredMethods[signatureName];if(returnId!==undefined){return returnId}var params=["retType"];var args=[retType];var argsList="";for(var i=0;i4){emval_handle_array[handle].refcount+=1}}function __emval_instanceof(object,constructor){object=Emval.toValue(object);constructor=Emval.toValue(constructor);return object instanceof constructor}function __emval_is_number(handle){handle=Emval.toValue(handle);return typeof handle=="number"}function __emval_is_string(handle){handle=Emval.toValue(handle);return typeof handle=="string"}function craftEmvalAllocator(argCount){var argsList="";for(var i=0;iHEAPU32;var functionBody="return function emval_allocator_"+argCount+"(constructor, argTypes, args) {\n"+" var HEAPU32 = getMemory();\n";for(var i=0;i>2)], 'parameter "+i+"');\n"+"var arg"+i+" = argType"+i+".readValueFromPointer(args);\n"+"args += argType"+i+"['argPackAdvance'];\n"+"argTypes += 4;\n"}functionBody+="var obj = new constructor("+argsList+");\n"+"return valueToHandle(obj);\n"+"}\n";return new Function("requireRegisteredType","Module","valueToHandle","getMemory",functionBody)(requireRegisteredType,Module,Emval.toHandle,getMemory)}var emval_newers={};function __emval_new(handle,argCount,argTypes,args){handle=Emval.toValue(handle);var newer=emval_newers[argCount];if(!newer){newer=craftEmvalAllocator(argCount);emval_newers[argCount]=newer}return newer(handle,argTypes,args)}function __emval_new_array(){return Emval.toHandle([])}function __emval_new_cstring(v){return Emval.toHandle(getStringOrSymbol(v))}function __emval_new_object(){return Emval.toHandle({})}function __emval_run_destructors(handle){var destructors=Emval.toValue(handle);runDestructors(destructors);__emval_decref(handle)}function __emval_set_property(handle,key,value){handle=Emval.toValue(handle);key=Emval.toValue(key);value=Emval.toValue(value);handle[key]=value}function __emval_take_value(type,arg){type=requireRegisteredType(type,"_emval_take_value");var v=type["readValueFromPointer"](arg);return Emval.toHandle(v)}function readI53FromI64(ptr){return HEAPU32[ptr>>2]+HEAP32[ptr+4>>2]*4294967296}function __gmtime_js(time,tmPtr){var date=new Date(readI53FromI64(time)*1e3);HEAP32[tmPtr>>2]=date.getUTCSeconds();HEAP32[tmPtr+4>>2]=date.getUTCMinutes();HEAP32[tmPtr+8>>2]=date.getUTCHours();HEAP32[tmPtr+12>>2]=date.getUTCDate();HEAP32[tmPtr+16>>2]=date.getUTCMonth();HEAP32[tmPtr+20>>2]=date.getUTCFullYear()-1900;HEAP32[tmPtr+24>>2]=date.getUTCDay();var start=Date.UTC(date.getUTCFullYear(),0,1,0,0,0,0);var yday=(date.getTime()-start)/(1e3*60*60*24)|0;HEAP32[tmPtr+28>>2]=yday}function __isLeapYear(year){return year%4===0&&(year%100!==0||year%400===0)}var __MONTH_DAYS_LEAP_CUMULATIVE=[0,31,60,91,121,152,182,213,244,274,305,335];var __MONTH_DAYS_REGULAR_CUMULATIVE=[0,31,59,90,120,151,181,212,243,273,304,334];function __yday_from_date(date){var isLeapYear=__isLeapYear(date.getFullYear());var monthDaysCumulative=isLeapYear?__MONTH_DAYS_LEAP_CUMULATIVE:__MONTH_DAYS_REGULAR_CUMULATIVE;var yday=monthDaysCumulative[date.getMonth()]+date.getDate()-1;return yday}function __localtime_js(time,tmPtr){var date=new Date(readI53FromI64(time)*1e3);HEAP32[tmPtr>>2]=date.getSeconds();HEAP32[tmPtr+4>>2]=date.getMinutes();HEAP32[tmPtr+8>>2]=date.getHours();HEAP32[tmPtr+12>>2]=date.getDate();HEAP32[tmPtr+16>>2]=date.getMonth();HEAP32[tmPtr+20>>2]=date.getFullYear()-1900;HEAP32[tmPtr+24>>2]=date.getDay();var yday=__yday_from_date(date)|0;HEAP32[tmPtr+28>>2]=yday;HEAP32[tmPtr+36>>2]=-(date.getTimezoneOffset()*60);var start=new Date(date.getFullYear(),0,1);var summerOffset=new Date(date.getFullYear(),6,1).getTimezoneOffset();var winterOffset=start.getTimezoneOffset();var dst=(summerOffset!=winterOffset&&date.getTimezoneOffset()==Math.min(winterOffset,summerOffset))|0;HEAP32[tmPtr+32>>2]=dst}function __mktime_js(tmPtr){var date=new Date(HEAP32[tmPtr+20>>2]+1900,HEAP32[tmPtr+16>>2],HEAP32[tmPtr+12>>2],HEAP32[tmPtr+8>>2],HEAP32[tmPtr+4>>2],HEAP32[tmPtr>>2],0);var dst=HEAP32[tmPtr+32>>2];var guessedOffset=date.getTimezoneOffset();var start=new Date(date.getFullYear(),0,1);var summerOffset=new Date(date.getFullYear(),6,1).getTimezoneOffset();var winterOffset=start.getTimezoneOffset();var dstOffset=Math.min(winterOffset,summerOffset);if(dst<0){HEAP32[tmPtr+32>>2]=Number(summerOffset!=winterOffset&&dstOffset==guessedOffset)}else if(dst>0!=(dstOffset==guessedOffset)){var nonDstOffset=Math.max(winterOffset,summerOffset);var trueOffset=dst>0?dstOffset:nonDstOffset;date.setTime(date.getTime()+(trueOffset-guessedOffset)*6e4)}HEAP32[tmPtr+24>>2]=date.getDay();var yday=__yday_from_date(date)|0;HEAP32[tmPtr+28>>2]=yday;HEAP32[tmPtr>>2]=date.getSeconds();HEAP32[tmPtr+4>>2]=date.getMinutes();HEAP32[tmPtr+8>>2]=date.getHours();HEAP32[tmPtr+12>>2]=date.getDate();HEAP32[tmPtr+16>>2]=date.getMonth();HEAP32[tmPtr+20>>2]=date.getYear();return date.getTime()/1e3|0}function allocateUTF8(str){var size=lengthBytesUTF8(str)+1;var ret=_malloc(size);if(ret)stringToUTF8Array(str,HEAP8,ret,size);return ret}function __tzset_js(timezone,daylight,tzname){var currentYear=(new Date).getFullYear();var winter=new Date(currentYear,0,1);var summer=new Date(currentYear,6,1);var winterOffset=winter.getTimezoneOffset();var summerOffset=summer.getTimezoneOffset();var stdTimezoneOffset=Math.max(winterOffset,summerOffset);HEAPU32[timezone>>2]=stdTimezoneOffset*60;HEAP32[daylight>>2]=Number(winterOffset!=summerOffset);function extractZone(date){var match=date.toTimeString().match(/\(([A-Za-z ]+)\)$/);return match?match[1]:"GMT"}var winterName=extractZone(winter);var summerName=extractZone(summer);var winterNamePtr=allocateUTF8(winterName);var summerNamePtr=allocateUTF8(summerName);if(summerOffset>2]=winterNamePtr;HEAPU32[tzname+4>>2]=summerNamePtr}else{HEAPU32[tzname>>2]=summerNamePtr;HEAPU32[tzname+4>>2]=winterNamePtr}}function _abort(){abort("")}function _emscripten_date_now(){return Date.now()}function getHeapMax(){return 2147483648}var _emscripten_get_now;if(ENVIRONMENT_IS_NODE){_emscripten_get_now=()=>{var t=process["hrtime"]();return t[0]*1e3+t[1]/1e6}}else _emscripten_get_now=()=>performance.now();function emscripten_realloc_buffer(size){var b=wasmMemory.buffer;try{wasmMemory.grow(size-b.byteLength+65535>>>16);updateMemoryViews();return 1}catch(e){}}function _emscripten_resize_heap(requestedSize){var oldSize=HEAPU8.length;requestedSize=requestedSize>>>0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}let alignUp=(x,multiple)=>x+(multiple-x%multiple)%multiple;for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignUp(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=emscripten_realloc_buffer(newSize);if(replacement){return true}}return false}var ENV={};function getExecutableName(){return thisProgram||"./this.program"}function getEnvStrings(){if(!getEnvStrings.strings){var lang=(typeof navigator=="object"&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8";var env={"USER":"web_user","LOGNAME":"web_user","PATH":"/","PWD":"/","HOME":"/home/web_user","LANG":lang,"_":getExecutableName()};for(var x in ENV){if(ENV[x]===undefined)delete env[x];else env[x]=ENV[x]}var strings=[];for(var x in env){strings.push(x+"="+env[x])}getEnvStrings.strings=strings}return getEnvStrings.strings}function writeAsciiToMemory(str,buffer,dontAddNull){for(var i=0;i>0]=str.charCodeAt(i)}if(!dontAddNull)HEAP8[buffer>>0]=0}function _environ_get(__environ,environ_buf){var bufSize=0;getEnvStrings().forEach(function(string,i){var ptr=environ_buf+bufSize;HEAPU32[__environ+i*4>>2]=ptr;writeAsciiToMemory(string,ptr);bufSize+=string.length+1});return 0}function _environ_sizes_get(penviron_count,penviron_buf_size){var strings=getEnvStrings();HEAPU32[penviron_count>>2]=strings.length;var bufSize=0;strings.forEach(function(string){bufSize+=string.length+1});HEAPU32[penviron_buf_size>>2]=bufSize;return 0}function _fd_close(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);FS.close(stream);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}function doReadv(stream,iov,iovcnt,offset){var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.read(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}function _fd_seek(fd,offset_low,offset_high,whence,newOffset){try{var offset=convertI32PairToI53Checked(offset_low,offset_high);if(isNaN(offset))return 61;var stream=SYSCALLS.getStreamFromFD(fd);FS.llseek(stream,offset,whence);tempI64=[stream.position>>>0,(tempDouble=stream.position,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[newOffset>>2]=tempI64[0],HEAP32[newOffset+4>>2]=tempI64[1];if(stream.getdents&&offset===0&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}function doWritev(stream,iov,iovcnt,offset){var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.write(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(typeof offset!=="undefined"){offset+=curr}}return ret}function _fd_write(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=doWritev(stream,iov,iovcnt);HEAPU32[pnum>>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}var FSNode=function(parent,name,mode,rdev){if(!parent){parent=this}this.parent=parent;this.mount=parent.mount;this.mounted=null;this.id=FS.nextInode++;this.name=name;this.mode=mode;this.node_ops={};this.stream_ops={};this.rdev=rdev};var readMode=292|73;var writeMode=146;Object.defineProperties(FSNode.prototype,{read:{get:function(){return(this.mode&readMode)===readMode},set:function(val){val?this.mode|=readMode:this.mode&=~readMode}},write:{get:function(){return(this.mode&writeMode)===writeMode},set:function(val){val?this.mode|=writeMode:this.mode&=~writeMode}},isFolder:{get:function(){return FS.isDir(this.mode)}},isDevice:{get:function(){return FS.isChrdev(this.mode)}}});FS.FSNode=FSNode;FS.staticInit();InternalError=Module["InternalError"]=extendError(Error,"InternalError");embind_init_charCodes();BindingError=Module["BindingError"]=extendError(Error,"BindingError");init_ClassHandle();init_embind();init_RegisteredPointer();UnboundTypeError=Module["UnboundTypeError"]=extendError(Error,"UnboundTypeError");init_emval();var asmLibraryArg={"v":___cxa_throw,"U":___syscall_connect,"ea":___syscall_faccessat,"o":___syscall_fcntl64,"Z":___syscall_fstat64,"S":___syscall_ftruncate64,"K":___syscall_ioctl,"W":___syscall_lstat64,"X":___syscall_newfstatat,"G":___syscall_openat,"F":___syscall_socket,"Y":___syscall_stat64,"r":__embind_finalize_value_array,"ka":__embind_finalize_value_object,"T":__embind_register_bigint,"ga":__embind_register_bool,"e":__embind_register_class,"f":__embind_register_class_class_function,"g":__embind_register_class_constructor,"b":__embind_register_class_function,"a":__embind_register_class_property,"fa":__embind_register_emval,"i":__embind_register_enum,"d":__embind_register_enum_value,"M":__embind_register_float,"p":__embind_register_integer,"l":__embind_register_memory_view,"L":__embind_register_std_string,"B":__embind_register_std_wstring,"q":__embind_register_value_array,"ia":__embind_register_value_array_element,"ja":__embind_register_value_object,"N":__embind_register_value_object_field,"ha":__embind_register_void,"t":__emval_as,"la":__emval_call_method,"P":__emval_call_void_method,"c":__emval_decref,"Q":__emval_equals,"y":__emval_get_global,"C":__emval_get_method_caller,"E":__emval_get_module_property,"w":__emval_get_property,"m":__emval_incref,"D":__emval_instanceof,"na":__emval_is_number,"ma":__emval_is_string,"O":__emval_new,"n":__emval_new_array,"u":__emval_new_cstring,"k":__emval_new_object,"s":__emval_run_destructors,"j":__emval_set_property,"h":__emval_take_value,"aa":__gmtime_js,"ba":__localtime_js,"ca":__mktime_js,"da":__tzset_js,"z":_abort,"I":_emscripten_date_now,"H":_emscripten_get_now,"V":_emscripten_resize_heap,"_":_environ_get,"$":_environ_sizes_get,"x":_fd_close,"J":_fd_read,"R":_fd_seek,"A":_fd_write};var asm=createWasm();var ___wasm_call_ctors=Module["___wasm_call_ctors"]=function(){return(___wasm_call_ctors=Module["___wasm_call_ctors"]=Module["asm"]["pa"]).apply(null,arguments)};var _malloc=Module["_malloc"]=function(){return(_malloc=Module["_malloc"]=Module["asm"]["ra"]).apply(null,arguments)};var _free=Module["_free"]=function(){return(_free=Module["_free"]=Module["asm"]["sa"]).apply(null,arguments)};var ___errno_location=Module["___errno_location"]=function(){return(___errno_location=Module["___errno_location"]=Module["asm"]["ta"]).apply(null,arguments)};var ___getTypeName=Module["___getTypeName"]=function(){return(___getTypeName=Module["___getTypeName"]=Module["asm"]["ua"]).apply(null,arguments)};var __embind_initialize_bindings=Module["__embind_initialize_bindings"]=function(){return(__embind_initialize_bindings=Module["__embind_initialize_bindings"]=Module["asm"]["va"]).apply(null,arguments)};var _htons=Module["_htons"]=function(){return(_htons=Module["_htons"]=Module["asm"]["wa"]).apply(null,arguments)};var _ntohs=Module["_ntohs"]=function(){return(_ntohs=Module["_ntohs"]=Module["asm"]["xa"]).apply(null,arguments)};var ___cxa_is_pointer_type=Module["___cxa_is_pointer_type"]=function(){return(___cxa_is_pointer_type=Module["___cxa_is_pointer_type"]=Module["asm"]["ya"]).apply(null,arguments)};var dynCall_ji=Module["dynCall_ji"]=function(){return(dynCall_ji=Module["dynCall_ji"]=Module["asm"]["za"]).apply(null,arguments)};var dynCall_vij=Module["dynCall_vij"]=function(){return(dynCall_vij=Module["dynCall_vij"]=Module["asm"]["Aa"]).apply(null,arguments)};var dynCall_jiji=Module["dynCall_jiji"]=function(){return(dynCall_jiji=Module["dynCall_jiji"]=Module["asm"]["Ba"]).apply(null,arguments)};var calledRun;dependenciesFulfilled=function runCaller(){if(!calledRun)run();if(!calledRun)dependenciesFulfilled=runCaller};function run(args){args=args||arguments_;if(runDependencies>0){return}preRun();if(runDependencies>0){return}function doRun(){if(calledRun)return;calledRun=true;Module["calledRun"]=true;if(ABORT)return;initRuntime();readyPromiseResolve(Module);if(Module["onRuntimeInitialized"])Module["onRuntimeInitialized"]();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(function(){setTimeout(function(){Module["setStatus"]("")},1);doRun()},1)}else{doRun()}}if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}run(); return rhino3dm.ready diff --git a/examples/jsm/libs/rhino3dm/rhino3dm.wasm b/examples/jsm/libs/rhino3dm/rhino3dm.wasm index fbbf481911e526..7b305791acecfa 100644 Binary files a/examples/jsm/libs/rhino3dm/rhino3dm.wasm and b/examples/jsm/libs/rhino3dm/rhino3dm.wasm differ diff --git a/examples/jsm/libs/surfaceNet.js b/examples/jsm/libs/surfaceNet.js new file mode 100644 index 00000000000000..527c3eef6eeaa7 --- /dev/null +++ b/examples/jsm/libs/surfaceNet.js @@ -0,0 +1,201 @@ +/** + * SurfaceNets in JavaScript + * + * Written by Mikola Lysenko (C) 2012 + * + * MIT License + * + * Based on: S.F. Gibson, 'Constrained Elastic Surface Nets'. (1998) MERL Tech Report. + * from https://github.com/mikolalysenko/isosurface/tree/master + * + */ + +let surfaceNet = ( dims, potential, bounds ) => { + + + //Precompute edge table, like Paul Bourke does. + // This saves a bit of time when computing the centroid of each boundary cell + var cube_edges = new Int32Array(24) , edge_table = new Int32Array(256); + (function() { + + //Initialize the cube_edges table + // This is just the vertex number of each cube + var k = 0; + for(var i=0; i<8; ++i) { + for(var j=1; j<=4; j<<=1) { + var p = i^j; + if(i <= p) { + cube_edges[k++] = i; + cube_edges[k++] = p; + } + } + } + + //Initialize the intersection table. + // This is a 2^(cube configuration) -> 2^(edge configuration) map + // There is one entry for each possible cube configuration, and the output is a 12-bit vector enumerating all edges crossing the 0-level. + for(var i=0; i<256; ++i) { + var em = 0; + for(var j=0; j<24; j+=2) { + var a = !!(i & (1<> 1)) : 0; + } + edge_table[i] = em; + } + })(); + + //Internal buffer, this may get resized at run time + var buffer = new Array(4096); + (function() { + for(var i=0; i buffer.length) { + var ol = buffer.length; + buffer.length = R[2] * 2; + while(ol < buffer.length) { + buffer[ol++] = 0; + } + } + + //March over the voxel grid + for(x[2]=0; x[2] 1e-6) { + t = g0 / t; + } else { + continue; + } + + //Interpolate vertices and add up intersections (this can be done without multiplying) + for(var j=0, k=1; j<3; ++j, k<<=1) { + var a = e0 & k + , b = e1 & k; + if(a !== b) { + v[j] += a ? 1.0 - t : t; + } else { + v[j] += a ? 1.0 : 0; + } + } + } + + //Now we just average the edge intersections and add them to coordinate + var s = 1.0 / e_count; + for(var i=0; i<3; ++i) { + v[i] = scale[i] * (x[i] + s * v[i]) + shift[i]; + } + + //Add vertex to buffer, store pointer to vertex index in buffer + buffer[m] = vertices.length; + vertices.push(v); + + //Now we need to add faces together, to do this we just loop over 3 basis components + for(var i=0; i<3; ++i) { + //The first three entries of the edge_mask count the crossings along the edge + if(!(edge_mask & (1< 10000 ? 10000 : power; + return { + In: function (amount) { + return Math.pow(amount, power); + }, + Out: function (amount) { + return 1 - Math.pow((1 - amount), power); + }, + InOut: function (amount) { + if (amount < 0.5) { + return Math.pow((amount * 2), power) / 2; + } + return (1 - Math.pow((2 - amount * 2), power)) / 2 + 0.5; + }, + }; + }, +}); -var now; -// Include a performance.now polyfill. -// In node.js, use process.hrtime. -// eslint-disable-next-line -// @ts-ignore -if (typeof self === 'undefined' && typeof process !== 'undefined' && process.hrtime) { - now = function () { - // eslint-disable-next-line - // @ts-ignore - var time = process.hrtime(); - // Convert [seconds, nanoseconds] to milliseconds. - return time[0] * 1000 + time[1] / 1000000; - }; -} -// In a browser, use self.performance.now if it is available. -else if (typeof self !== 'undefined' && self.performance !== undefined && self.performance.now !== undefined) { - // This must be bound, because directly assigning this function - // leads to an invocation exception in Chrome. - now = self.performance.now.bind(self.performance); -} -// Use Date.now if it is available. -else if (Date.now !== undefined) { - now = Date.now; -} -// Otherwise, use 'new Date().getTime()'. -else { - now = function () { - return new Date().getTime(); - }; -} -var now$1 = now; +var now = function () { return performance.now(); }; /** * Controlling groups of tweens @@ -224,146 +222,146 @@ var now$1 = now; * In these cases, you may want to create your own smaller groups of tween */ var Group = /** @class */ (function () { - function Group() { - this._tweens = {}; - this._tweensAddedDuringUpdate = {}; - } - Group.prototype.getAll = function () { - var _this = this; - return Object.keys(this._tweens).map(function (tweenId) { - return _this._tweens[tweenId]; - }); - }; - Group.prototype.removeAll = function () { - this._tweens = {}; - }; - Group.prototype.add = function (tween) { - this._tweens[tween.getId()] = tween; - this._tweensAddedDuringUpdate[tween.getId()] = tween; - }; - Group.prototype.remove = function (tween) { - delete this._tweens[tween.getId()]; - delete this._tweensAddedDuringUpdate[tween.getId()]; - }; - Group.prototype.update = function (time, preserve) { - if (time === void 0) { time = now$1(); } - if (preserve === void 0) { preserve = false; } - var tweenIds = Object.keys(this._tweens); - if (tweenIds.length === 0) { - return false; - } - // Tweens are updated in "batches". If you add a new tween during an - // update, then the new tween will be updated in the next batch. - // If you remove a tween during an update, it may or may not be updated. - // However, if the removed tween was added during the current batch, - // then it will not be updated. - while (tweenIds.length > 0) { - this._tweensAddedDuringUpdate = {}; - for (var i = 0; i < tweenIds.length; i++) { - var tween = this._tweens[tweenIds[i]]; - var autoStart = !preserve; - if (tween && tween.update(time, autoStart) === false && !preserve) { - delete this._tweens[tweenIds[i]]; - } - } - tweenIds = Object.keys(this._tweensAddedDuringUpdate); - } - return true; - }; - return Group; + function Group() { + this._tweens = {}; + this._tweensAddedDuringUpdate = {}; + } + Group.prototype.getAll = function () { + var _this = this; + return Object.keys(this._tweens).map(function (tweenId) { + return _this._tweens[tweenId]; + }); + }; + Group.prototype.removeAll = function () { + this._tweens = {}; + }; + Group.prototype.add = function (tween) { + this._tweens[tween.getId()] = tween; + this._tweensAddedDuringUpdate[tween.getId()] = tween; + }; + Group.prototype.remove = function (tween) { + delete this._tweens[tween.getId()]; + delete this._tweensAddedDuringUpdate[tween.getId()]; + }; + Group.prototype.update = function (time, preserve) { + if (time === void 0) { time = now(); } + if (preserve === void 0) { preserve = false; } + var tweenIds = Object.keys(this._tweens); + if (tweenIds.length === 0) { + return false; + } + // Tweens are updated in "batches". If you add a new tween during an + // update, then the new tween will be updated in the next batch. + // If you remove a tween during an update, it may or may not be updated. + // However, if the removed tween was added during the current batch, + // then it will not be updated. + while (tweenIds.length > 0) { + this._tweensAddedDuringUpdate = {}; + for (var i = 0; i < tweenIds.length; i++) { + var tween = this._tweens[tweenIds[i]]; + var autoStart = !preserve; + if (tween && tween.update(time, autoStart) === false && !preserve) { + delete this._tweens[tweenIds[i]]; + } + } + tweenIds = Object.keys(this._tweensAddedDuringUpdate); + } + return true; + }; + return Group; }()); /** * */ var Interpolation = { - Linear: function (v, k) { - var m = v.length - 1; - var f = m * k; - var i = Math.floor(f); - var fn = Interpolation.Utils.Linear; - if (k < 0) { - return fn(v[0], v[1], f); - } - if (k > 1) { - return fn(v[m], v[m - 1], m - f); - } - return fn(v[i], v[i + 1 > m ? m : i + 1], f - i); - }, - Bezier: function (v, k) { - var b = 0; - var n = v.length - 1; - var pw = Math.pow; - var bn = Interpolation.Utils.Bernstein; - for (var i = 0; i <= n; i++) { - b += pw(1 - k, n - i) * pw(k, i) * v[i] * bn(n, i); - } - return b; - }, - CatmullRom: function (v, k) { - var m = v.length - 1; - var f = m * k; - var i = Math.floor(f); - var fn = Interpolation.Utils.CatmullRom; - if (v[0] === v[m]) { - if (k < 0) { - i = Math.floor((f = m * (1 + k))); - } - return fn(v[(i - 1 + m) % m], v[i], v[(i + 1) % m], v[(i + 2) % m], f - i); - } - else { - if (k < 0) { - return v[0] - (fn(v[0], v[0], v[1], v[1], -f) - v[0]); - } - if (k > 1) { - return v[m] - (fn(v[m], v[m], v[m - 1], v[m - 1], f - m) - v[m]); - } - return fn(v[i ? i - 1 : 0], v[i], v[m < i + 1 ? m : i + 1], v[m < i + 2 ? m : i + 2], f - i); - } - }, - Utils: { - Linear: function (p0, p1, t) { - return (p1 - p0) * t + p0; - }, - Bernstein: function (n, i) { - var fc = Interpolation.Utils.Factorial; - return fc(n) / fc(i) / fc(n - i); - }, - Factorial: (function () { - var a = [1]; - return function (n) { - var s = 1; - if (a[n]) { - return a[n]; - } - for (var i = n; i > 1; i--) { - s *= i; - } - a[n] = s; - return s; - }; - })(), - CatmullRom: function (p0, p1, p2, p3, t) { - var v0 = (p2 - p0) * 0.5; - var v1 = (p3 - p1) * 0.5; - var t2 = t * t; - var t3 = t * t2; - return (2 * p1 - 2 * p2 + v0 + v1) * t3 + (-3 * p1 + 3 * p2 - 2 * v0 - v1) * t2 + v0 * t + p1; - }, - }, + Linear: function (v, k) { + var m = v.length - 1; + var f = m * k; + var i = Math.floor(f); + var fn = Interpolation.Utils.Linear; + if (k < 0) { + return fn(v[0], v[1], f); + } + if (k > 1) { + return fn(v[m], v[m - 1], m - f); + } + return fn(v[i], v[i + 1 > m ? m : i + 1], f - i); + }, + Bezier: function (v, k) { + var b = 0; + var n = v.length - 1; + var pw = Math.pow; + var bn = Interpolation.Utils.Bernstein; + for (var i = 0; i <= n; i++) { + b += pw(1 - k, n - i) * pw(k, i) * v[i] * bn(n, i); + } + return b; + }, + CatmullRom: function (v, k) { + var m = v.length - 1; + var f = m * k; + var i = Math.floor(f); + var fn = Interpolation.Utils.CatmullRom; + if (v[0] === v[m]) { + if (k < 0) { + i = Math.floor((f = m * (1 + k))); + } + return fn(v[(i - 1 + m) % m], v[i], v[(i + 1) % m], v[(i + 2) % m], f - i); + } + else { + if (k < 0) { + return v[0] - (fn(v[0], v[0], v[1], v[1], -f) - v[0]); + } + if (k > 1) { + return v[m] - (fn(v[m], v[m], v[m - 1], v[m - 1], f - m) - v[m]); + } + return fn(v[i ? i - 1 : 0], v[i], v[m < i + 1 ? m : i + 1], v[m < i + 2 ? m : i + 2], f - i); + } + }, + Utils: { + Linear: function (p0, p1, t) { + return (p1 - p0) * t + p0; + }, + Bernstein: function (n, i) { + var fc = Interpolation.Utils.Factorial; + return fc(n) / fc(i) / fc(n - i); + }, + Factorial: (function () { + var a = [1]; + return function (n) { + var s = 1; + if (a[n]) { + return a[n]; + } + for (var i = n; i > 1; i--) { + s *= i; + } + a[n] = s; + return s; + }; + })(), + CatmullRom: function (p0, p1, p2, p3, t) { + var v0 = (p2 - p0) * 0.5; + var v1 = (p3 - p1) * 0.5; + var t2 = t * t; + var t3 = t * t2; + return (2 * p1 - 2 * p2 + v0 + v1) * t3 + (-3 * p1 + 3 * p2 - 2 * v0 - v1) * t2 + v0 * t + p1; + }, + }, }; /** * Utils */ var Sequence = /** @class */ (function () { - function Sequence() { - } - Sequence.nextId = function () { - return Sequence._nextId++; - }; - Sequence._nextId = 0; - return Sequence; + function Sequence() { + } + Sequence.nextId = function () { + return Sequence._nextId++; + }; + Sequence._nextId = 0; + return Sequence; }()); var mainGroup = new Group(); @@ -377,386 +375,462 @@ var mainGroup = new Group(); * Thank you all, you're awesome! */ var Tween = /** @class */ (function () { - function Tween(_object, _group) { - if (_group === void 0) { _group = mainGroup; } - this._object = _object; - this._group = _group; - this._isPaused = false; - this._pauseStart = 0; - this._valuesStart = {}; - this._valuesEnd = {}; - this._valuesStartRepeat = {}; - this._duration = 1000; - this._initialRepeat = 0; - this._repeat = 0; - this._yoyo = false; - this._isPlaying = false; - this._reversed = false; - this._delayTime = 0; - this._startTime = 0; - this._easingFunction = Easing.Linear.None; - this._interpolationFunction = Interpolation.Linear; - this._chainedTweens = []; - this._onStartCallbackFired = false; - this._id = Sequence.nextId(); - this._isChainStopped = false; - this._goToEnd = false; - } - Tween.prototype.getId = function () { - return this._id; - }; - Tween.prototype.isPlaying = function () { - return this._isPlaying; - }; - Tween.prototype.isPaused = function () { - return this._isPaused; - }; - Tween.prototype.to = function (properties, duration) { - // TODO? restore this, then update the 07_dynamic_to example to set fox - // tween's to on each update. That way the behavior is opt-in (there's - // currently no opt-out). - // for (const prop in properties) this._valuesEnd[prop] = properties[prop] - this._valuesEnd = Object.create(properties); - if (duration !== undefined) { - this._duration = duration; - } - return this; - }; - Tween.prototype.duration = function (d) { - this._duration = d; - return this; - }; - Tween.prototype.start = function (time) { - if (this._isPlaying) { - return this; - } - // eslint-disable-next-line - this._group && this._group.add(this); - this._repeat = this._initialRepeat; - if (this._reversed) { - // If we were reversed (f.e. using the yoyo feature) then we need to - // flip the tween direction back to forward. - this._reversed = false; - for (var property in this._valuesStartRepeat) { - this._swapEndStartRepeatValues(property); - this._valuesStart[property] = this._valuesStartRepeat[property]; - } - } - this._isPlaying = true; - this._isPaused = false; - this._onStartCallbackFired = false; - this._isChainStopped = false; - this._startTime = time !== undefined ? (typeof time === 'string' ? now$1() + parseFloat(time) : time) : now$1(); - this._startTime += this._delayTime; - this._setupProperties(this._object, this._valuesStart, this._valuesEnd, this._valuesStartRepeat); - return this; - }; - Tween.prototype._setupProperties = function (_object, _valuesStart, _valuesEnd, _valuesStartRepeat) { - for (var property in _valuesEnd) { - var startValue = _object[property]; - var startValueIsArray = Array.isArray(startValue); - var propType = startValueIsArray ? 'array' : typeof startValue; - var isInterpolationList = !startValueIsArray && Array.isArray(_valuesEnd[property]); - // If `to()` specifies a property that doesn't exist in the source object, - // we should not set that property in the object - if (propType === 'undefined' || propType === 'function') { - continue; - } - // Check if an Array was provided as property value - if (isInterpolationList) { - var endValues = _valuesEnd[property]; - if (endValues.length === 0) { - continue; - } - // handle an array of relative values - endValues = endValues.map(this._handleRelativeValue.bind(this, startValue)); - // Create a local copy of the Array with the start value at the front - _valuesEnd[property] = [startValue].concat(endValues); - } - // handle the deepness of the values - if ((propType === 'object' || startValueIsArray) && startValue && !isInterpolationList) { - _valuesStart[property] = startValueIsArray ? [] : {}; - // eslint-disable-next-line - for (var prop in startValue) { - // eslint-disable-next-line - // @ts-ignore FIXME? - _valuesStart[property][prop] = startValue[prop]; - } - _valuesStartRepeat[property] = startValueIsArray ? [] : {}; // TODO? repeat nested values? And yoyo? And array values? - // eslint-disable-next-line - // @ts-ignore FIXME? - this._setupProperties(startValue, _valuesStart[property], _valuesEnd[property], _valuesStartRepeat[property]); - } - else { - // Save the starting value, but only once. - if (typeof _valuesStart[property] === 'undefined') { - _valuesStart[property] = startValue; - } - if (!startValueIsArray) { - // eslint-disable-next-line - // @ts-ignore FIXME? - _valuesStart[property] *= 1.0; // Ensures we're using numbers, not strings - } - if (isInterpolationList) { - // eslint-disable-next-line - // @ts-ignore FIXME? - _valuesStartRepeat[property] = _valuesEnd[property].slice().reverse(); - } - else { - _valuesStartRepeat[property] = _valuesStart[property] || 0; - } - } - } - }; - Tween.prototype.stop = function () { - if (!this._isChainStopped) { - this._isChainStopped = true; - this.stopChainedTweens(); - } - if (!this._isPlaying) { - return this; - } - // eslint-disable-next-line - this._group && this._group.remove(this); - this._isPlaying = false; - this._isPaused = false; - if (this._onStopCallback) { - this._onStopCallback(this._object); - } - return this; - }; - Tween.prototype.end = function () { - this._goToEnd = true; - this.update(Infinity); - return this; - }; - Tween.prototype.pause = function (time) { - if (time === void 0) { time = now$1(); } - if (this._isPaused || !this._isPlaying) { - return this; - } - this._isPaused = true; - this._pauseStart = time; - // eslint-disable-next-line - this._group && this._group.remove(this); - return this; - }; - Tween.prototype.resume = function (time) { - if (time === void 0) { time = now$1(); } - if (!this._isPaused || !this._isPlaying) { - return this; - } - this._isPaused = false; - this._startTime += time - this._pauseStart; - this._pauseStart = 0; - // eslint-disable-next-line - this._group && this._group.add(this); - return this; - }; - Tween.prototype.stopChainedTweens = function () { - for (var i = 0, numChainedTweens = this._chainedTweens.length; i < numChainedTweens; i++) { - this._chainedTweens[i].stop(); - } - return this; - }; - Tween.prototype.group = function (group) { - this._group = group; - return this; - }; - Tween.prototype.delay = function (amount) { - this._delayTime = amount; - return this; - }; - Tween.prototype.repeat = function (times) { - this._initialRepeat = times; - this._repeat = times; - return this; - }; - Tween.prototype.repeatDelay = function (amount) { - this._repeatDelayTime = amount; - return this; - }; - Tween.prototype.yoyo = function (yoyo) { - this._yoyo = yoyo; - return this; - }; - Tween.prototype.easing = function (easingFunction) { - this._easingFunction = easingFunction; - return this; - }; - Tween.prototype.interpolation = function (interpolationFunction) { - this._interpolationFunction = interpolationFunction; - return this; - }; - Tween.prototype.chain = function () { - var tweens = []; - for (var _i = 0; _i < arguments.length; _i++) { - tweens[_i] = arguments[_i]; - } - this._chainedTweens = tweens; - return this; - }; - Tween.prototype.onStart = function (callback) { - this._onStartCallback = callback; - return this; - }; - Tween.prototype.onUpdate = function (callback) { - this._onUpdateCallback = callback; - return this; - }; - Tween.prototype.onRepeat = function (callback) { - this._onRepeatCallback = callback; - return this; - }; - Tween.prototype.onComplete = function (callback) { - this._onCompleteCallback = callback; - return this; - }; - Tween.prototype.onStop = function (callback) { - this._onStopCallback = callback; - return this; - }; - /** - * @returns true if the tween is still playing after the update, false - * otherwise (calling update on a paused tween still returns true because - * it is still playing, just paused). - */ - Tween.prototype.update = function (time, autoStart) { - if (time === void 0) { time = now$1(); } - if (autoStart === void 0) { autoStart = true; } - if (this._isPaused) - return true; - var property; - var elapsed; - var endTime = this._startTime + this._duration; - if (!this._goToEnd && !this._isPlaying) { - if (time > endTime) - return false; - if (autoStart) - this.start(time); - } - this._goToEnd = false; - if (time < this._startTime) { - return true; - } - if (this._onStartCallbackFired === false) { - if (this._onStartCallback) { - this._onStartCallback(this._object); - } - this._onStartCallbackFired = true; - } - elapsed = (time - this._startTime) / this._duration; - elapsed = this._duration === 0 || elapsed > 1 ? 1 : elapsed; - var value = this._easingFunction(elapsed); - // properties transformations - this._updateProperties(this._object, this._valuesStart, this._valuesEnd, value); - if (this._onUpdateCallback) { - this._onUpdateCallback(this._object, elapsed); - } - if (elapsed === 1) { - if (this._repeat > 0) { - if (isFinite(this._repeat)) { - this._repeat--; - } - // Reassign starting values, restart by making startTime = now - for (property in this._valuesStartRepeat) { - if (!this._yoyo && typeof this._valuesEnd[property] === 'string') { - this._valuesStartRepeat[property] = - // eslint-disable-next-line - // @ts-ignore FIXME? - this._valuesStartRepeat[property] + parseFloat(this._valuesEnd[property]); - } - if (this._yoyo) { - this._swapEndStartRepeatValues(property); - } - this._valuesStart[property] = this._valuesStartRepeat[property]; - } - if (this._yoyo) { - this._reversed = !this._reversed; - } - if (this._repeatDelayTime !== undefined) { - this._startTime = time + this._repeatDelayTime; - } - else { - this._startTime = time + this._delayTime; - } - if (this._onRepeatCallback) { - this._onRepeatCallback(this._object); - } - return true; - } - else { - if (this._onCompleteCallback) { - this._onCompleteCallback(this._object); - } - for (var i = 0, numChainedTweens = this._chainedTweens.length; i < numChainedTweens; i++) { - // Make the chained tweens start exactly at the time they should, - // even if the `update()` method was called way past the duration of the tween - this._chainedTweens[i].start(this._startTime + this._duration); - } - this._isPlaying = false; - return false; - } - } - return true; - }; - Tween.prototype._updateProperties = function (_object, _valuesStart, _valuesEnd, value) { - for (var property in _valuesEnd) { - // Don't update properties that do not exist in the source object - if (_valuesStart[property] === undefined) { - continue; - } - var start = _valuesStart[property] || 0; - var end = _valuesEnd[property]; - var startIsArray = Array.isArray(_object[property]); - var endIsArray = Array.isArray(end); - var isInterpolationList = !startIsArray && endIsArray; - if (isInterpolationList) { - _object[property] = this._interpolationFunction(end, value); - } - else if (typeof end === 'object' && end) { - // eslint-disable-next-line - // @ts-ignore FIXME? - this._updateProperties(_object[property], start, end, value); - } - else { - // Parses relative end values with start as base (e.g.: +10, -3) - end = this._handleRelativeValue(start, end); - // Protect against non numeric properties. - if (typeof end === 'number') { - // eslint-disable-next-line - // @ts-ignore FIXME? - _object[property] = start + (end - start) * value; - } - } - } - }; - Tween.prototype._handleRelativeValue = function (start, end) { - if (typeof end !== 'string') { - return end; - } - if (end.charAt(0) === '+' || end.charAt(0) === '-') { - return start + parseFloat(end); - } - else { - return parseFloat(end); - } - }; - Tween.prototype._swapEndStartRepeatValues = function (property) { - var tmp = this._valuesStartRepeat[property]; - var endValue = this._valuesEnd[property]; - if (typeof endValue === 'string') { - this._valuesStartRepeat[property] = this._valuesStartRepeat[property] + parseFloat(endValue); - } - else { - this._valuesStartRepeat[property] = this._valuesEnd[property]; - } - this._valuesEnd[property] = tmp; - }; - return Tween; + function Tween(_object, _group) { + if (_group === void 0) { _group = mainGroup; } + this._object = _object; + this._group = _group; + this._isPaused = false; + this._pauseStart = 0; + this._valuesStart = {}; + this._valuesEnd = {}; + this._valuesStartRepeat = {}; + this._duration = 1000; + this._isDynamic = false; + this._initialRepeat = 0; + this._repeat = 0; + this._yoyo = false; + this._isPlaying = false; + this._reversed = false; + this._delayTime = 0; + this._startTime = 0; + this._easingFunction = Easing.Linear.None; + this._interpolationFunction = Interpolation.Linear; + // eslint-disable-next-line + this._chainedTweens = []; + this._onStartCallbackFired = false; + this._onEveryStartCallbackFired = false; + this._id = Sequence.nextId(); + this._isChainStopped = false; + this._propertiesAreSetUp = false; + this._goToEnd = false; + } + Tween.prototype.getId = function () { + return this._id; + }; + Tween.prototype.isPlaying = function () { + return this._isPlaying; + }; + Tween.prototype.isPaused = function () { + return this._isPaused; + }; + Tween.prototype.getDuration = function () { + return this._duration; + }; + Tween.prototype.to = function (target, duration) { + if (duration === void 0) { duration = 1000; } + if (this._isPlaying) + throw new Error('Can not call Tween.to() while Tween is already started or paused. Stop the Tween first.'); + this._valuesEnd = target; + this._propertiesAreSetUp = false; + this._duration = duration < 0 ? 0 : duration; + return this; + }; + Tween.prototype.duration = function (duration) { + if (duration === void 0) { duration = 1000; } + this._duration = duration < 0 ? 0 : duration; + return this; + }; + Tween.prototype.dynamic = function (dynamic) { + if (dynamic === void 0) { dynamic = false; } + this._isDynamic = dynamic; + return this; + }; + Tween.prototype.start = function (time, overrideStartingValues) { + if (time === void 0) { time = now(); } + if (overrideStartingValues === void 0) { overrideStartingValues = false; } + if (this._isPlaying) { + return this; + } + // eslint-disable-next-line + this._group && this._group.add(this); + this._repeat = this._initialRepeat; + if (this._reversed) { + // If we were reversed (f.e. using the yoyo feature) then we need to + // flip the tween direction back to forward. + this._reversed = false; + for (var property in this._valuesStartRepeat) { + this._swapEndStartRepeatValues(property); + this._valuesStart[property] = this._valuesStartRepeat[property]; + } + } + this._isPlaying = true; + this._isPaused = false; + this._onStartCallbackFired = false; + this._onEveryStartCallbackFired = false; + this._isChainStopped = false; + this._startTime = time; + this._startTime += this._delayTime; + if (!this._propertiesAreSetUp || overrideStartingValues) { + this._propertiesAreSetUp = true; + // If dynamic is not enabled, clone the end values instead of using the passed-in end values. + if (!this._isDynamic) { + var tmp = {}; + for (var prop in this._valuesEnd) + tmp[prop] = this._valuesEnd[prop]; + this._valuesEnd = tmp; + } + this._setupProperties(this._object, this._valuesStart, this._valuesEnd, this._valuesStartRepeat, overrideStartingValues); + } + return this; + }; + Tween.prototype.startFromCurrentValues = function (time) { + return this.start(time, true); + }; + Tween.prototype._setupProperties = function (_object, _valuesStart, _valuesEnd, _valuesStartRepeat, overrideStartingValues) { + for (var property in _valuesEnd) { + var startValue = _object[property]; + var startValueIsArray = Array.isArray(startValue); + var propType = startValueIsArray ? 'array' : typeof startValue; + var isInterpolationList = !startValueIsArray && Array.isArray(_valuesEnd[property]); + // If `to()` specifies a property that doesn't exist in the source object, + // we should not set that property in the object + if (propType === 'undefined' || propType === 'function') { + continue; + } + // Check if an Array was provided as property value + if (isInterpolationList) { + var endValues = _valuesEnd[property]; + if (endValues.length === 0) { + continue; + } + // Handle an array of relative values. + // Creates a local copy of the Array with the start value at the front + var temp = [startValue]; + for (var i = 0, l = endValues.length; i < l; i += 1) { + var value = this._handleRelativeValue(startValue, endValues[i]); + if (isNaN(value)) { + isInterpolationList = false; + console.warn('Found invalid interpolation list. Skipping.'); + break; + } + temp.push(value); + } + if (isInterpolationList) { + // if (_valuesStart[property] === undefined) { // handle end values only the first time. NOT NEEDED? setupProperties is now guarded by _propertiesAreSetUp. + _valuesEnd[property] = temp; + // } + } + } + // handle the deepness of the values + if ((propType === 'object' || startValueIsArray) && startValue && !isInterpolationList) { + _valuesStart[property] = startValueIsArray ? [] : {}; + var nestedObject = startValue; + for (var prop in nestedObject) { + _valuesStart[property][prop] = nestedObject[prop]; + } + // TODO? repeat nested values? And yoyo? And array values? + _valuesStartRepeat[property] = startValueIsArray ? [] : {}; + var endValues = _valuesEnd[property]; + // If dynamic is not enabled, clone the end values instead of using the passed-in end values. + if (!this._isDynamic) { + var tmp = {}; + for (var prop in endValues) + tmp[prop] = endValues[prop]; + _valuesEnd[property] = endValues = tmp; + } + this._setupProperties(nestedObject, _valuesStart[property], endValues, _valuesStartRepeat[property], overrideStartingValues); + } + else { + // Save the starting value, but only once unless override is requested. + if (typeof _valuesStart[property] === 'undefined' || overrideStartingValues) { + _valuesStart[property] = startValue; + } + if (!startValueIsArray) { + // eslint-disable-next-line + // @ts-ignore FIXME? + _valuesStart[property] *= 1.0; // Ensures we're using numbers, not strings + } + if (isInterpolationList) { + // eslint-disable-next-line + // @ts-ignore FIXME? + _valuesStartRepeat[property] = _valuesEnd[property].slice().reverse(); + } + else { + _valuesStartRepeat[property] = _valuesStart[property] || 0; + } + } + } + }; + Tween.prototype.stop = function () { + if (!this._isChainStopped) { + this._isChainStopped = true; + this.stopChainedTweens(); + } + if (!this._isPlaying) { + return this; + } + // eslint-disable-next-line + this._group && this._group.remove(this); + this._isPlaying = false; + this._isPaused = false; + if (this._onStopCallback) { + this._onStopCallback(this._object); + } + return this; + }; + Tween.prototype.end = function () { + this._goToEnd = true; + this.update(Infinity); + return this; + }; + Tween.prototype.pause = function (time) { + if (time === void 0) { time = now(); } + if (this._isPaused || !this._isPlaying) { + return this; + } + this._isPaused = true; + this._pauseStart = time; + // eslint-disable-next-line + this._group && this._group.remove(this); + return this; + }; + Tween.prototype.resume = function (time) { + if (time === void 0) { time = now(); } + if (!this._isPaused || !this._isPlaying) { + return this; + } + this._isPaused = false; + this._startTime += time - this._pauseStart; + this._pauseStart = 0; + // eslint-disable-next-line + this._group && this._group.add(this); + return this; + }; + Tween.prototype.stopChainedTweens = function () { + for (var i = 0, numChainedTweens = this._chainedTweens.length; i < numChainedTweens; i++) { + this._chainedTweens[i].stop(); + } + return this; + }; + Tween.prototype.group = function (group) { + if (group === void 0) { group = mainGroup; } + this._group = group; + return this; + }; + Tween.prototype.delay = function (amount) { + if (amount === void 0) { amount = 0; } + this._delayTime = amount; + return this; + }; + Tween.prototype.repeat = function (times) { + if (times === void 0) { times = 0; } + this._initialRepeat = times; + this._repeat = times; + return this; + }; + Tween.prototype.repeatDelay = function (amount) { + this._repeatDelayTime = amount; + return this; + }; + Tween.prototype.yoyo = function (yoyo) { + if (yoyo === void 0) { yoyo = false; } + this._yoyo = yoyo; + return this; + }; + Tween.prototype.easing = function (easingFunction) { + if (easingFunction === void 0) { easingFunction = Easing.Linear.None; } + this._easingFunction = easingFunction; + return this; + }; + Tween.prototype.interpolation = function (interpolationFunction) { + if (interpolationFunction === void 0) { interpolationFunction = Interpolation.Linear; } + this._interpolationFunction = interpolationFunction; + return this; + }; + // eslint-disable-next-line + Tween.prototype.chain = function () { + var tweens = []; + for (var _i = 0; _i < arguments.length; _i++) { + tweens[_i] = arguments[_i]; + } + this._chainedTweens = tweens; + return this; + }; + Tween.prototype.onStart = function (callback) { + this._onStartCallback = callback; + return this; + }; + Tween.prototype.onEveryStart = function (callback) { + this._onEveryStartCallback = callback; + return this; + }; + Tween.prototype.onUpdate = function (callback) { + this._onUpdateCallback = callback; + return this; + }; + Tween.prototype.onRepeat = function (callback) { + this._onRepeatCallback = callback; + return this; + }; + Tween.prototype.onComplete = function (callback) { + this._onCompleteCallback = callback; + return this; + }; + Tween.prototype.onStop = function (callback) { + this._onStopCallback = callback; + return this; + }; + /** + * @returns true if the tween is still playing after the update, false + * otherwise (calling update on a paused tween still returns true because + * it is still playing, just paused). + */ + Tween.prototype.update = function (time, autoStart) { + var _this = this; + var _a; + if (time === void 0) { time = now(); } + if (autoStart === void 0) { autoStart = true; } + if (this._isPaused) + return true; + var property; + var endTime = this._startTime + this._duration; + if (!this._goToEnd && !this._isPlaying) { + if (time > endTime) + return false; + if (autoStart) + this.start(time, true); + } + this._goToEnd = false; + if (time < this._startTime) { + return true; + } + if (this._onStartCallbackFired === false) { + if (this._onStartCallback) { + this._onStartCallback(this._object); + } + this._onStartCallbackFired = true; + } + if (this._onEveryStartCallbackFired === false) { + if (this._onEveryStartCallback) { + this._onEveryStartCallback(this._object); + } + this._onEveryStartCallbackFired = true; + } + var elapsedTime = time - this._startTime; + var durationAndDelay = this._duration + ((_a = this._repeatDelayTime) !== null && _a !== void 0 ? _a : this._delayTime); + var totalTime = this._duration + this._repeat * durationAndDelay; + var calculateElapsedPortion = function () { + if (_this._duration === 0) + return 1; + if (elapsedTime > totalTime) { + return 1; + } + var timesRepeated = Math.trunc(elapsedTime / durationAndDelay); + var timeIntoCurrentRepeat = elapsedTime - timesRepeated * durationAndDelay; + // TODO use %? + // const timeIntoCurrentRepeat = elapsedTime % durationAndDelay + var portion = Math.min(timeIntoCurrentRepeat / _this._duration, 1); + if (portion === 0 && elapsedTime === _this._duration) { + return 1; + } + return portion; + }; + var elapsed = calculateElapsedPortion(); + var value = this._easingFunction(elapsed); + // properties transformations + this._updateProperties(this._object, this._valuesStart, this._valuesEnd, value); + if (this._onUpdateCallback) { + this._onUpdateCallback(this._object, elapsed); + } + if (this._duration === 0 || elapsedTime >= this._duration) { + if (this._repeat > 0) { + var completeCount = Math.min(Math.trunc((elapsedTime - this._duration) / durationAndDelay) + 1, this._repeat); + if (isFinite(this._repeat)) { + this._repeat -= completeCount; + } + // Reassign starting values, restart by making startTime = now + for (property in this._valuesStartRepeat) { + if (!this._yoyo && typeof this._valuesEnd[property] === 'string') { + this._valuesStartRepeat[property] = + // eslint-disable-next-line + // @ts-ignore FIXME? + this._valuesStartRepeat[property] + parseFloat(this._valuesEnd[property]); + } + if (this._yoyo) { + this._swapEndStartRepeatValues(property); + } + this._valuesStart[property] = this._valuesStartRepeat[property]; + } + if (this._yoyo) { + this._reversed = !this._reversed; + } + this._startTime += durationAndDelay * completeCount; + if (this._onRepeatCallback) { + this._onRepeatCallback(this._object); + } + this._onEveryStartCallbackFired = false; + return true; + } + else { + if (this._onCompleteCallback) { + this._onCompleteCallback(this._object); + } + for (var i = 0, numChainedTweens = this._chainedTweens.length; i < numChainedTweens; i++) { + // Make the chained tweens start exactly at the time they should, + // even if the `update()` method was called way past the duration of the tween + this._chainedTweens[i].start(this._startTime + this._duration, false); + } + this._isPlaying = false; + return false; + } + } + return true; + }; + Tween.prototype._updateProperties = function (_object, _valuesStart, _valuesEnd, value) { + for (var property in _valuesEnd) { + // Don't update properties that do not exist in the source object + if (_valuesStart[property] === undefined) { + continue; + } + var start = _valuesStart[property] || 0; + var end = _valuesEnd[property]; + var startIsArray = Array.isArray(_object[property]); + var endIsArray = Array.isArray(end); + var isInterpolationList = !startIsArray && endIsArray; + if (isInterpolationList) { + _object[property] = this._interpolationFunction(end, value); + } + else if (typeof end === 'object' && end) { + // eslint-disable-next-line + // @ts-ignore FIXME? + this._updateProperties(_object[property], start, end, value); + } + else { + // Parses relative end values with start as base (e.g.: +10, -3) + end = this._handleRelativeValue(start, end); + // Protect against non numeric properties. + if (typeof end === 'number') { + // eslint-disable-next-line + // @ts-ignore FIXME? + _object[property] = start + (end - start) * value; + } + } + } + }; + Tween.prototype._handleRelativeValue = function (start, end) { + if (typeof end !== 'string') { + return end; + } + if (end.charAt(0) === '+' || end.charAt(0) === '-') { + return start + parseFloat(end); + } + return parseFloat(end); + }; + Tween.prototype._swapEndStartRepeatValues = function (property) { + var tmp = this._valuesStartRepeat[property]; + var endValue = this._valuesEnd[property]; + if (typeof endValue === 'string') { + this._valuesStartRepeat[property] = this._valuesStartRepeat[property] + parseFloat(endValue); + } + else { + this._valuesStartRepeat[property] = this._valuesEnd[property]; + } + this._valuesEnd[property] = tmp; + }; + return Tween; }()); -var VERSION = '18.6.4'; +var VERSION = '23.1.1'; /** * Tween.js - Licensed under the MIT license @@ -784,20 +858,19 @@ var add = TWEEN.add.bind(TWEEN); var remove = TWEEN.remove.bind(TWEEN); var update = TWEEN.update.bind(TWEEN); var exports = { - Easing: Easing, - Group: Group, - Interpolation: Interpolation, - now: now$1, - Sequence: Sequence, - nextId: nextId, - Tween: Tween, - VERSION: VERSION, - getAll: getAll, - removeAll: removeAll, - add: add, - remove: remove, - update: update, + Easing: Easing, + Group: Group, + Interpolation: Interpolation, + now: now, + Sequence: Sequence, + nextId: nextId, + Tween: Tween, + VERSION: VERSION, + getAll: getAll, + removeAll: removeAll, + add: add, + remove: remove, + update: update, }; -export default exports; -export { Easing, Group, Interpolation, Sequence, Tween, VERSION, add, getAll, nextId, now$1 as now, remove, removeAll, update }; +export { Easing, Group, Interpolation, Sequence, Tween, VERSION, add, exports as default, getAll, nextId, now, remove, removeAll, update }; diff --git a/examples/jsm/libs/utif.module.js b/examples/jsm/libs/utif.module.js index 7050714f950d23..9a655bd26d1dfc 100644 --- a/examples/jsm/libs/utif.module.js +++ b/examples/jsm/libs/utif.module.js @@ -1,1579 +1,1665 @@ -;(function(){ - var UTIF = {}; - - // Make available for import by `require()` - if (typeof module == "object") {module.exports = UTIF;} - else {self.UTIF = UTIF;} - - var pako = (typeof require === "function") ? require("pako") : self.pako; - - function log() { if (typeof process=="undefined" || process.env.NODE_ENV=="development") console.log.apply(console, arguments); } - - (function(UTIF, pako){ - - // Following lines add a JPEG decoder to UTIF.JpegDecoder - (function(){"use strict";var W=function a1(){function W(p){this.message="JPEG error: "+p}W.prototype=new Error;W.prototype.name="JpegError";W.constructor=W;return W}(),ak=function ag(){var p=new Uint8Array([0,1,8,16,9,2,3,10,17,24,32,25,18,11,4,5,12,19,26,33,40,48,41,34,27,20,13,6,7,14,21,28,35,42,49,56,57,50,43,36,29,22,15,23,30,37,44,51,58,59,52,45,38,31,39,46,53,60,61,54,47,55,62,63]),t=4017,ac=799,ah=3406,ao=2276,ar=1567,ai=3784,s=5793,ad=2896;function ak(Q){if(Q==null)Q={};if(Q.w==null)Q.w=-1;this.V=Q.n;this.N=Q.w}function a5(Q,h){var f=0,G=[],n,E,a=16,F;while(a>0&&!Q[a-1]){a--}G.push({children:[],index:0});var C=G[0];for(n=0;n0){C=G.pop()}C.index++;G.push(C);while(G.length<=n){G.push(F={children:[],index:0});C.children[C.index]=F.children;C=F}f++}if(n+10){V--;return J>>V&1}J=Q[h++];if(J===255){var I=Q[h++];if(I){if(I===220&&d){h+=2;var l=Z(Q,h);h+=2;if(l>0&&l!==f.s){throw new DNLMarkerError("Found DNL marker (0xFFDC) while parsing scan data",l)}}else if(I===217){if(d){var M=q*8; - if(M>0&&M>>7}function u(I){var l=I;while(!0){l=l[Y()];switch(typeof l){case"number":return l;case"object":continue}throw new W("invalid huffman sequence")}}function m(I){var e=0;while(I>0){e=e<<1|Y();I--}return e}function j(I){if(I===1){return Y()===1?1:-1}var e=m(I);if(e>=1<>4;if(i===0){if(A<15){break}N+=16;continue}N+=A;var o=p[N];X.D[I+o]=j(i);N++}}function $(X,I){var l=u(X.J),M=l===0?0:j(l)<0){r--;return}var N=E,l=a;while(N<=l){var M=u(X.i),S=M&15,i=M>>4;if(S===0){if(i<15){r=m(i)+(1<>4;if(S===0){if(M<15){r=m(M)+(1<0){for(O=0;O0?"unexpected":"excessive";h=k.offset}if(k.M>=65488&&k.M<=65495){h+=2}else{break}}return h-z}function al(Q,h,f){var G=Q.$,n=Q.D,E,a,C,F,d,T,U,z,J,V,Y,u,m,j,v,$,b;if(!G){throw new W("missing required Quantization Table.")}for(var r=0;r<64;r+=8){J=n[h+r];V=n[h+r+1];Y=n[h+r+2];u=n[h+r+3];m=n[h+r+4];j=n[h+r+5];v=n[h+r+6];$=n[h+r+7];J*=G[r];if((V|Y|u|m|j|v|$)===0){b=s*J+512>>10;f[r]=b;f[r+1]=b;f[r+2]=b;f[r+3]=b;f[r+4]=b;f[r+5]=b;f[r+6]=b;f[r+7]=b;continue}V*=G[r+1];Y*=G[r+2];u*=G[r+3];m*=G[r+4];j*=G[r+5];v*=G[r+6];$*=G[r+7];E=s*J+128>>8;a=s*m+128>>8;C=Y;F=v;d=ad*(V-$)+128>>8;z=ad*(V+$)+128>>8; - T=u<<4;U=j<<4;E=E+a+1>>1;a=E-a;b=C*ai+F*ar+128>>8;C=C*ar-F*ai+128>>8;F=b;d=d+U+1>>1;U=d-U;z=z+T+1>>1;T=z-T;E=E+F+1>>1;F=E-F;a=a+C+1>>1;C=a-C;b=d*ao+z*ah+2048>>12;d=d*ah-z*ao+2048>>12;z=b;b=T*ac+U*t+2048>>12;T=T*t-U*ac+2048>>12;U=b;f[r]=E+z;f[r+7]=E-z;f[r+1]=a+U;f[r+6]=a-U;f[r+2]=C+T;f[r+5]=C-T;f[r+3]=F+d;f[r+4]=F-d}for(var P=0;P<8;++P){J=f[P];V=f[P+8];Y=f[P+16];u=f[P+24];m=f[P+32];j=f[P+40];v=f[P+48];$=f[P+56];if((V|Y|u|m|j|v|$)===0){b=s*J+8192>>14;if(b<-2040){b=0}else if(b>=2024){b=255}else{b=b+2056>>4}n[h+P]=b;n[h+P+8]=b;n[h+P+16]=b;n[h+P+24]=b;n[h+P+32]=b;n[h+P+40]=b;n[h+P+48]=b;n[h+P+56]=b;continue}E=s*J+2048>>12;a=s*m+2048>>12;C=Y;F=v;d=ad*(V-$)+2048>>12;z=ad*(V+$)+2048>>12;T=u;U=j;E=(E+a+1>>1)+4112;a=E-a;b=C*ai+F*ar+2048>>12;C=C*ar-F*ai+2048>>12;F=b;d=d+U+1>>1;U=d-U;z=z+T+1>>1;T=z-T;E=E+F+1>>1;F=E-F;a=a+C+1>>1;C=a-C;b=d*ao+z*ah+2048>>12;d=d*ah-z*ao+2048>>12;z=b; - b=T*ac+U*t+2048>>12;T=T*t-U*ac+2048>>12;U=b;J=E+z;$=E-z;V=a+U;v=a-U;Y=C+T;j=C-T;u=F+d;m=F-d;if(J<16){J=0}else if(J>=4080){J=255}else{J>>=4}if(V<16){V=0}else if(V>=4080){V=255}else{V>>=4}if(Y<16){Y=0}else if(Y>=4080){Y=255}else{Y>>=4}if(u<16){u=0}else if(u>=4080){u=255}else{u>>=4}if(m<16){m=0}else if(m>=4080){m=255}else{m>>=4}if(j<16){j=0}else if(j>=4080){j=255}else{j>>=4}if(v<16){v=0}else if(v>=4080){v=255}else{v>>=4}if($<16){$=0}else if($>=4080){$=255}else{$>>=4}n[h+P]=J; - n[h+P+8]=V;n[h+P+16]=Y;n[h+P+24]=u;n[h+P+32]=m;n[h+P+40]=j;n[h+P+48]=v;n[h+P+56]=$}}function a0(Q,h){var f=h.P,G=h.c,n=new Int16Array(64);for(var E=0;E=G){return null}var E=Z(Q,h);if(E>=65472&&E<=65534){return{u:null,M:E,offset:h}}var a=Z(Q,n);while(!(a>=65472&&a<=65534)){if(++n>=G){return null}a=Z(Q,n)}return{u:E.toString(16),M:a,offset:n}}ak.prototype={parse(Q,h){if(h==null)h={}; - var f=h.F,E=0,a=null,C=null,F,d,T=0;function G(){var o=Z(Q,E);E+=2;var B=E+o-2,V=an(Q,B,E);if(V&&V.u){B=V.offset}var ab=Q.subarray(E,B);E+=ab.length;return ab}function n(F){var o=Math.ceil(F.o/8/F.X),B=Math.ceil(F.s/8/F.B);for(var Y=0;Y>4===0){for(u=0;u<64;u++){b=p[u];P[b]=Q[E++]}}else if(r>>4===1){for(u=0;u<64;u++){b=p[u];P[b]=Z(Q,E);E+=2}}else{throw new W("DQT - invalid table spec")}U[r&15]=P}break;case 65472:case 65473:case 65474:if(F){throw new W("Only single frame JPEGs supported")}E+=2;F={};F.G=V===65473;F.Z=V===65474;F.precision=Q[E++];var D=Z(Q,E),a4,q=0,H=0;E+=2;F.s=f||D;F.o=Z(Q,E);E+=2;F.W=[];F._={};var a8=Q[E++];for(Y=0;Y>4,y=Q[E+1]&15;if(q>4===0?J:z)[_&15]=a5(N,K)}break;case 65501:E+=2;d=Z(Q,E);E+=2;break;case 65498:var x=++T===1&&!f,R;E+=2;var k=Q[E++],g=[];for(Y=0;Y>4];R.i=z[a6&15];g.push(R)}var I=Q[E++],l=Q[E++],M=Q[E++];try{var S=a7(Q,E,F,g,d,I,l,M>>4,M&15,x);E+=S}catch(ex){if(ex instanceof DNLMarkerError){return this.parse(Q,{F:ex.s})}else if(ex instanceof EOIMarkerError){break markerLoop}throw ex}break;case 65500:E+=4;break;case 65535:if(Q[E]!==255){E--}break;default:var i=an(Q,E-2,E-3);if(i&&i.u){E=i.offset;break}if(E>=Q.length-1){break markerLoop}throw new W("JpegImage.parse - unknown marker: "+V.toString(16))}V=Z(Q,E);E+=2}this.width=F.o;this.height=F.s;this.g=a;this.b=C;this.W=[];for(Y=0;Y>8)+P[J+1]}}}return v},get f(){if(this.b){return!!this.b.a}if(this.p===3){if(this.N===0){return!1}else if(this.W[0].index===82&&this.W[1].index===71&&this.W[2].index===66){return!1}return!0}if(this.N===1){return!0}return!1},z:function aj(Q){var h,f,G; - for(var n=0,E=Q.length;n4){throw new W("Unsupported color mode")}var E=this.Y(h,f,n);if(this.p===1&&G){var a=E.length,C=new Uint8ClampedArray(a*3),F=0;for(var d=0;d>24}function Z(p,t){return p[t]<<8|p[t+1]}function am(p,t){return(p[t]<<24|p[t+1]<<16|p[t+2]<<8|p[t+3])>>>0}UTIF.JpegDecoder=ak}()); - - //UTIF.JpegDecoder = PDFJS.JpegImage; - - - UTIF.encodeImage = function(rgba, w, h, metadata) - { - var idf = { "t256":[w], "t257":[h], "t258":[8,8,8,8], "t259":[1], "t262":[2], "t273":[1000], // strips offset - "t277":[4], "t278":[h], /* rows per strip */ "t279":[w*h*4], // strip byte counts - "t282":[[72,1]], "t283":[[72,1]], "t284":[1], "t286":[[0,1]], "t287":[[0,1]], "t296":[1], "t305": ["Photopea (UTIF.js)"], "t338":[1] - }; - if (metadata) for (var i in metadata) idf[i] = metadata[i]; - - var prfx = new Uint8Array(UTIF.encode([idf])); - var img = new Uint8Array(rgba); - var data = new Uint8Array(1000+w*h*4); - for(var i=0; i probably not an image - img.isLE = id=="II"; - img.width = img["t256"][0]; //delete img["t256"]; - img.height = img["t257"][0]; //delete img["t257"]; - - var cmpr = img["t259"] ? img["t259"][0] : 1; //delete img["t259"]; - var fo = img["t266"] ? img["t266"][0] : 1; //delete img["t266"]; - if(img["t284"] && img["t284"][0]==2) log("PlanarConfiguration 2 should not be used!"); - if(cmpr==7 && img["t258"] && img["t258"].length>3) img["t258"]=img["t258"].slice(0,3); - - var spp = img["t277"]?img["t277"][0]:1; - var bps = img["t258"]?img["t258"][0]:1; - var bipp = bps*spp; // bits per pixel - /* - var bipp; // bits per pixel - if(img["t258"]) bipp = Math.min(32,img["t258"][0])*img["t258"].length; - else bipp = (img["t277"]?img["t277"][0]:1); - */ - // Some .NEF files have t258==14, even though they use 16 bits per pixel - if(cmpr==1 && img["t279"]!=null && img["t278"] && img["t262"][0]==32803) { - bipp = Math.round((img["t279"][0]*8)/(img.width*img["t278"][0])); - } - if(img["t50885"] && img["t50885"][0]==4) bipp = img["t258"][0]*3; // RAW_CANON_40D_SRAW_V103.CR2 - var bipl = Math.ceil(img.width*bipp/8)*8; - var soff = img["t273"]; if(soff==null || img["t322"]) soff = img["t324"]; - var bcnt = img["t279"]; if(cmpr==1 && soff.length==1) bcnt = [img.height*(bipl>>>3)]; if(bcnt==null || img["t322"]) bcnt = img["t325"]; - //bcnt[0] = Math.min(bcnt[0], data.length); // Hasselblad, "RAW_HASSELBLAD_H3D39II.3FR" - var bytes = new Uint8Array(img.height*(bipl>>>3)), bilen = 0; - - if(img["t322"]!=null) // tiled - { - var tw = img["t322"][0], th = img["t323"][0]; - var tx = Math.floor((img.width + tw - 1) / tw); - var ty = Math.floor((img.height + th - 1) / th); - var tbuff = new Uint8Array(Math.ceil(tw*th*bipp/8)|0); - console.log("====", tx,ty); - for(var y=0; y0&&!Q[a-1]){a--}G.push({children:[],index:0});var C=G[0];for(n=0;n0){C=G.pop()}C.index++;G.push(C);while(G.length<=n){G.push(F={children:[],index:0});C.children[C.index]=F.children;C=F}f++}if(n+10){V--;return J>>V&1}J=Q[h++];if(J===255){var I=Q[h++];if(I){if(I===220&&d){h+=2;var l=Z(Q,h);h+=2;if(l>0&&l!==f.s){throw new DNLMarkerError("Found DNL marker (0xFFDC) while parsing scan data",l)}}else if(I===217){if(d){var M=q*8; +if(M>0&&M>>7}function u(I){var l=I;while(!0){l=l[Y()];switch(typeof l){case"number":return l;case"object":continue}throw new W("invalid huffman sequence")}}function m(I){var e=0;while(I>0){e=e<<1|Y();I--}return e}function j(I){if(I===1){return Y()===1?1:-1}var e=m(I);if(e>=1<>4;if(i===0){if(A<15){break}N+=16;continue}N+=A;var o=p[N];X.D[I+o]=j(i);N++}}function $(X,I){var l=u(X.J),M=l===0?0:j(l)<0){r--;return}var N=E,l=a;while(N<=l){var M=u(X.i),S=M&15,i=M>>4;if(S===0){if(i<15){r=m(i)+(1<>4;if(S===0){if(M<15){r=m(M)+(1<0){for(O=0;O0?"unexpected":"excessive";h=k.offset}if(k.M>=65488&&k.M<=65495){h+=2}else{break}}return h-z}function al(Q,h,f){var G=Q.$,n=Q.D,E,a,C,F,d,T,U,z,J,V,Y,u,m,j,v,$,b;if(!G){throw new W("missing required Quantization Table.")}for(var r=0;r<64;r+=8){J=n[h+r];V=n[h+r+1];Y=n[h+r+2];u=n[h+r+3];m=n[h+r+4];j=n[h+r+5];v=n[h+r+6];$=n[h+r+7];J*=G[r];if((V|Y|u|m|j|v|$)===0){b=s*J+512>>10;f[r]=b;f[r+1]=b;f[r+2]=b;f[r+3]=b;f[r+4]=b;f[r+5]=b;f[r+6]=b;f[r+7]=b;continue}V*=G[r+1];Y*=G[r+2];u*=G[r+3];m*=G[r+4];j*=G[r+5];v*=G[r+6];$*=G[r+7];E=s*J+128>>8;a=s*m+128>>8;C=Y;F=v;d=ad*(V-$)+128>>8;z=ad*(V+$)+128>>8; +T=u<<4;U=j<<4;E=E+a+1>>1;a=E-a;b=C*ai+F*ar+128>>8;C=C*ar-F*ai+128>>8;F=b;d=d+U+1>>1;U=d-U;z=z+T+1>>1;T=z-T;E=E+F+1>>1;F=E-F;a=a+C+1>>1;C=a-C;b=d*ao+z*ah+2048>>12;d=d*ah-z*ao+2048>>12;z=b;b=T*ac+U*t+2048>>12;T=T*t-U*ac+2048>>12;U=b;f[r]=E+z;f[r+7]=E-z;f[r+1]=a+U;f[r+6]=a-U;f[r+2]=C+T;f[r+5]=C-T;f[r+3]=F+d;f[r+4]=F-d}for(var P=0;P<8;++P){J=f[P];V=f[P+8];Y=f[P+16];u=f[P+24];m=f[P+32];j=f[P+40];v=f[P+48];$=f[P+56];if((V|Y|u|m|j|v|$)===0){b=s*J+8192>>14;if(b<-2040){b=0}else if(b>=2024){b=255}else{b=b+2056>>4}n[h+P]=b;n[h+P+8]=b;n[h+P+16]=b;n[h+P+24]=b;n[h+P+32]=b;n[h+P+40]=b;n[h+P+48]=b;n[h+P+56]=b;continue}E=s*J+2048>>12;a=s*m+2048>>12;C=Y;F=v;d=ad*(V-$)+2048>>12;z=ad*(V+$)+2048>>12;T=u;U=j;E=(E+a+1>>1)+4112;a=E-a;b=C*ai+F*ar+2048>>12;C=C*ar-F*ai+2048>>12;F=b;d=d+U+1>>1;U=d-U;z=z+T+1>>1;T=z-T;E=E+F+1>>1;F=E-F;a=a+C+1>>1;C=a-C;b=d*ao+z*ah+2048>>12;d=d*ah-z*ao+2048>>12;z=b; +b=T*ac+U*t+2048>>12;T=T*t-U*ac+2048>>12;U=b;J=E+z;$=E-z;V=a+U;v=a-U;Y=C+T;j=C-T;u=F+d;m=F-d;if(J<16){J=0}else if(J>=4080){J=255}else{J>>=4}if(V<16){V=0}else if(V>=4080){V=255}else{V>>=4}if(Y<16){Y=0}else if(Y>=4080){Y=255}else{Y>>=4}if(u<16){u=0}else if(u>=4080){u=255}else{u>>=4}if(m<16){m=0}else if(m>=4080){m=255}else{m>>=4}if(j<16){j=0}else if(j>=4080){j=255}else{j>>=4}if(v<16){v=0}else if(v>=4080){v=255}else{v>>=4}if($<16){$=0}else if($>=4080){$=255}else{$>>=4}n[h+P]=J; +n[h+P+8]=V;n[h+P+16]=Y;n[h+P+24]=u;n[h+P+32]=m;n[h+P+40]=j;n[h+P+48]=v;n[h+P+56]=$}}function a0(Q,h){var f=h.P,G=h.c,n=new Int16Array(64);for(var E=0;E=G){return null}var E=Z(Q,h);if(E>=65472&&E<=65534){return{u:null,M:E,offset:h}}var a=Z(Q,n);while(!(a>=65472&&a<=65534)){if(++n>=G){return null}a=Z(Q,n)}return{u:E.toString(16),M:a,offset:n}}ak.prototype={parse(Q,h){if(h==null)h={}; +var f=h.F,E=0,a=null,C=null,F,d,T=0;function G(){var o=Z(Q,E);E+=2;var B=E+o-2,V=an(Q,B,E);if(V&&V.u){B=V.offset}var ab=Q.subarray(E,B);E+=ab.length;return ab}function n(F){var o=Math.ceil(F.o/8/F.X),B=Math.ceil(F.s/8/F.B);for(var Y=0;Y>4===0){for(u=0;u<64;u++){b=p[u];P[b]=Q[E++]}}else if(r>>4===1){for(u=0;u<64;u++){b=p[u];P[b]=Z(Q,E);E+=2}}else{throw new W("DQT - invalid table spec")}U[r&15]=P}break;case 65472:case 65473:case 65474:if(F){throw new W("Only single frame JPEGs supported")}E+=2;F={};F.G=V===65473;F.Z=V===65474;F.precision=Q[E++];var D=Z(Q,E),a4,q=0,H=0;E+=2;F.s=f||D;F.o=Z(Q,E);E+=2;F.W=[];F._={};var a8=Q[E++];for(Y=0;Y>4,y=Q[E+1]&15;if(q>4===0?J:z)[_&15]=a5(N,K)}break;case 65501:E+=2;d=Z(Q,E);E+=2;break;case 65498:var x=++T===1&&!f,R;E+=2;var k=Q[E++],g=[];for(Y=0;Y>4];R.i=z[a6&15];g.push(R)}var I=Q[E++],l=Q[E++],M=Q[E++];try{var S=a7(Q,E,F,g,d,I,l,M>>4,M&15,x);E+=S}catch(ex){if(ex instanceof DNLMarkerError){return this.parse(Q,{F:ex.s})}else if(ex instanceof EOIMarkerError){break markerLoop}throw ex}break;case 65500:E+=4;break;case 65535:if(Q[E]!==255){E--}break;default:var i=an(Q,E-2,E-3);if(i&&i.u){E=i.offset;break}if(E>=Q.length-1){break markerLoop}throw new W("JpegImage.parse - unknown marker: "+V.toString(16))}V=Z(Q,E);E+=2}this.width=F.o;this.height=F.s;this.g=a;this.b=C;this.W=[];for(Y=0;Y>8)+P[J+1]}}}return v},get f(){if(this.b){return!!this.b.a}if(this.p===3){if(this.N===0){return!1}else if(this.W[0].index===82&&this.W[1].index===71&&this.W[2].index===66){return!1}return!0}if(this.N===1){return!0}return!1},z:function aj(Q){var h,f,G; +for(var n=0,E=Q.length;n4){throw new W("Unsupported color mode")}var E=this.Y(h,f,n);if(this.p===1&&G){var a=E.length,C=new Uint8ClampedArray(a*3),F=0;for(var d=0;d>24}function Z(p,t){return p[t]<<8|p[t+1]}function am(p,t){return(p[t]<<24|p[t+1]<<16|p[t+2]<<8|p[t+3])>>>0}UTIF.JpegDecoder=ak}()); + +//UTIF.JpegDecoder = PDFJS.JpegImage; + + +UTIF.encodeImage = function(rgba, w, h, metadata) +{ + var idf = { "t256":[w], "t257":[h], "t258":[8,8,8,8], "t259":[1], "t262":[2], "t273":[1000], // strips offset + "t277":[4], "t278":[h], /* rows per strip */ "t279":[w*h*4], // strip byte counts + "t282":[[72,1]], "t283":[[72,1]], "t284":[1], "t286":[[0,1]], "t287":[[0,1]], "t296":[1], "t305": ["Photopea (UTIF.js)"], "t338":[1] + }; + if (metadata) for (var i in metadata) idf[i] = metadata[i]; + + var prfx = new Uint8Array(UTIF.encode([idf])); + var img = new Uint8Array(rgba); + var data = new Uint8Array(1000+w*h*4); + for(var i=0; i probably not an image + img.isLE = id=="II"; + img.width = img["t256"][0]; //delete img["t256"]; + img.height = img["t257"][0]; //delete img["t257"]; + + var cmpr = img["t259"] ? img["t259"][0] : 1; //delete img["t259"]; + var fo = img["t266"] ? img["t266"][0] : 1; //delete img["t266"]; + if(img["t284"] && img["t284"][0]==2) log("PlanarConfiguration 2 should not be used!"); + if(cmpr==7 && img["t258"] && img["t258"].length>3) img["t258"]=img["t258"].slice(0,3); + + var spp = img["t277"]?img["t277"][0]:1; + var bps = img["t258"]?img["t258"][0]:1; + var bipp = bps*spp; // bits per pixel + /* + var bipp; // bits per pixel + if(img["t258"]) bipp = Math.min(32,img["t258"][0])*img["t258"].length; + else bipp = (img["t277"]?img["t277"][0]:1); + */ + // Some .NEF files have t258==14, even though they use 16 bits per pixel + if(cmpr==1 && img["t279"]!=null && img["t278"] && img["t262"][0]==32803) { + bipp = Math.round((img["t279"][0]*8)/(img.width*img["t278"][0])); + } + if(img["t50885"] && img["t50885"][0]==4) bipp = img["t258"][0]*3; // RAW_CANON_40D_SRAW_V103.CR2 + var bipl = Math.ceil(img.width*bipp/8)*8; + var soff = img["t273"]; if(soff==null || img["t322"]) soff = img["t324"]; + var bcnt = img["t279"]; if(cmpr==1 && soff.length==1) bcnt = [img.height*(bipl>>>3)]; if(bcnt==null || img["t322"]) bcnt = img["t325"]; + //bcnt[0] = Math.min(bcnt[0], data.length); // Hasselblad, "RAW_HASSELBLAD_H3D39II.3FR" + var bytes = new Uint8Array(img.height*(bipl>>>3)), bilen = 0; + + if(img["t322"]!=null) // tiled + { + var tw = img["t322"][0], th = img["t323"][0]; + var tx = Math.floor((img.width + tw - 1) / tw); + var ty = Math.floor((img.height + th - 1) / th); + var tbuff = new Uint8Array(Math.ceil(tw*th*bipp/8)|0); + console.log("====", tx,ty); + for(var y=0; y>>3, bpl = Math.ceil(bps*noc*w/8); + + // convert to Little Endian /* + if(bps==16 && !img.isLE && img["t33422"]==null) // not DNG + for(var y=0; y>>8)&255; + } + else if(noc==3) for(var j= 3; j> 3 ^ 0x3ff0; + return (buffer[byte] | buffer[byte + 1] << 8) >> (vpos & 7) & ~((-1) << bits); + } } - bilen = bytes.length*8; - } - else // stripped - { - if(soff==null) return; - var rps = img["t278"] ? img["t278"][0] : img.height; rps = Math.min(rps, img.height); - console.log("====", img.width, rps); - for(var i=0; i>>3, h = (img["t278"] ? img["t278"][0] : img.height), bpl = Math.ceil(bps*noc*img.width/8); - - // convert to Little Endian /* - if(bps==16 && !img.isLE && img["t33422"]==null) // not DNG - for(var y=0; y>>8)&255; + // Raw Format 6 + function getBufferDataRW6(i) { + return buffer[vpos + 15 - i]; } - else if(noc==3) for(var j= 3; j> 2); // 14 bit + bytes[1] = (((getBufferDataRW6(1) & 0x3) << 12) | (getBufferDataRW6(2) << 4) | (getBufferDataRW6(3) >> 4)) & 0x3fff; + bytes[2] = (getBufferDataRW6(3) >> 2) & 0x3; + bytes[3] = ((getBufferDataRW6(3) & 0x3) << 8) | getBufferDataRW6(4); + bytes[4] = (getBufferDataRW6(5) << 2) | (getBufferDataRW6(6) >> 6); + bytes[5] = ((getBufferDataRW6(6) & 0x3f) << 4) | (getBufferDataRW6(7) >> 4); + bytes[6] = (getBufferDataRW6(7) >> 2) & 0x3; + bytes[7] = ((getBufferDataRW6(7) & 0x3) << 8) | getBufferDataRW6(8); + bytes[8] = ((getBufferDataRW6(9) << 2) & 0x3fc) | (getBufferDataRW6(10) >> 6); + bytes[9] = ((getBufferDataRW6(10) << 4) | (getBufferDataRW6(11) >> 4)) & 0x3ff; + bytes[10] = (getBufferDataRW6(11) >> 2) & 0x3; + bytes[11] = ((getBufferDataRW6(11) & 0x3) << 8) | getBufferDataRW6(12); + bytes[12] = (((getBufferDataRW6(13) << 2) & 0x3fc) | getBufferDataRW6(14) >> 6) & 0x3ff; + bytes[13] = ((getBufferDataRW6(14) << 4) | (getBufferDataRW6(15) >> 4)) & 0x3ff; + vpos += 16; + byte = 0; } - else for(var j=bpp; j> 4); + bytes[1] = (((getBufferDataRW6(1) & 0xf) << 8) | (getBufferDataRW6(2))) & 0xfff; + bytes[2] = (getBufferDataRW6(3) >> 6) & 0x3; + bytes[3] = ((getBufferDataRW6(3) & 0x3f) << 2) | (getBufferDataRW6(4) >> 6); + bytes[4] = ((getBufferDataRW6(4) & 0x3f) << 2) | (getBufferDataRW6(5) >> 6); + bytes[5] = ((getBufferDataRW6(5) & 0x3f) << 2) | (getBufferDataRW6(6) >> 6); + bytes[6] = (getBufferDataRW6(6) >> 4) & 0x3; + bytes[7] = ((getBufferDataRW6(6) & 0xf) << 4) | (getBufferDataRW6(7) >> 4); + bytes[8] = ((getBufferDataRW6(7) & 0xf) << 4) | (getBufferDataRW6(8) >> 4); + bytes[9] = ((getBufferDataRW6(8) & 0xf) << 4) | (getBufferDataRW6(9) >> 4); + bytes[10] = (getBufferDataRW6(9) >> 2) & 0x3; + bytes[11] = ((getBufferDataRW6(9) & 0x3) << 6) | (getBufferDataRW6(10) >> 2); + bytes[12] = ((getBufferDataRW6(10) & 0x3) << 6) | (getBufferDataRW6(11) >> 2); + bytes[13] = ((getBufferDataRW6(11) & 0x3) << 6) | (getBufferDataRW6(12) >> 2); + bytes[14] = getBufferDataRW6(12) & 0x3; + bytes[15] = getBufferDataRW6(13); + bytes[16] = getBufferDataRW6(14); + bytes[17] = getBufferDataRW6(15); + + vpos += 16; + byte = 0; + } + // Main loop + function resetPredNonzeros(){ + pred[0]=0; pred[1]=0; + nonz[0]=0; nonz[1]=0; + } + if (RW2_Format == 7) { + throw RW2_Format; + + // Skatch of version 7 + /* + var pixels_per_block = bitsPerSample == 14 ? 9 : 10; + rowbytes = 0|(rawWidth / pixels_per_block * 16); + for (row = 0; row < rawHeight - 15; row += 16) { + var rowstoread = Math.min(16, rawHeight - row); + var readlen = rowbytes*rowstoread; + buffer = new Uint8Array(image.slice(bidx, bidx+readlen)); + vpos = 0; + bidx += readlen; + i = 0; + for (crow = 0; crow < rowstoread; crow++) { + idx = (row + crow) * rawWidth; + for (col = 0; col <= rawWidth - pixels_per_block; col += pixels_per_block) { + for(j=0; j < pixels_per_block; j++) bytes[j] = buffer[i++]; + if (bitsPerSample == 12) { + result[idx ] = ((bytes[1] & 0xF) << 8) + bytes[0]; + result[idx + 1] = 16 * bytes[2] + (bytes[1] >> 4); + result[idx + 2] = ((bytes[4] & 0xF) << 8) + bytes[3]; + result[idx + 3] = 16 * bytes[5] + (bytes[4] >> 4); + result[idx + 4] = ((bytes[7] & 0xF) << 8) + bytes[6]; + result[idx + 5] = 16 * bytes[8] + (bytes[7] >> 4); + result[idx + 6] = ((bytes[10] & 0xF) << 8) + bytes[9]; + result[idx + 7] = 16 * bytes[11] + (bytes[10] >> 4); + result[idx + 8] = ((bytes[13] & 0xF) << 8) + bytes[12]; + result[idx + 9] = 16 * bytes[14] + (bytes[13] >> 4); + } else if (bitsPerSample == 14) { + result[idx] = bytes[0] + ((bytes[1] & 0x3F) << 8); + result[idx + 1] = (bytes[1] >> 6) + 4 * (bytes[2]) + ((bytes[3] & 0xF) << 10); + result[idx + 2] = (bytes[3] >> 4) + 16 * (bytes[4]) + ((bytes[5] & 3) << 12); + result[idx + 3] = ((bytes[5] & 0xFC) >> 2) + (bytes[6] << 6); + result[idx + 4] = bytes[7] + ((bytes[8] & 0x3F) << 8); + result[idx + 5] = (bytes[8] >> 6) + 4 * bytes[9] + ((bytes[10] & 0xF) << 10); + result[idx + 6] = (bytes[10] >> 4) + 16 * bytes[11] + ((bytes[12] & 3) << 12); + result[idx + 7] = ((bytes[12] & 0xFC) >> 2) + (bytes[13] << 6); + result[idx + 8] = bytes[14] + ((bytes[15] & 0x3F) << 8); + } } - } else { - vpos = (vpos - bits) & 0x1ffff; - byte = vpos >> 3 ^ 0x3ff0; - return (buffer[byte] | buffer[byte + 1] << 8) >> (vpos & 7) & ~((-1) << bits); } } - // Raw Format 6 - function getBufferDataRW6(i) { - return buffer[vpos + 15 - i]; - } - function readPageRW6() { - bytes[0] = (getBufferDataRW6(0) << 6) | (getBufferDataRW6(1) >> 2); // 14 bit - bytes[1] = (((getBufferDataRW6(1) & 0x3) << 12) | (getBufferDataRW6(2) << 4) | (getBufferDataRW6(3) >> 4)) & 0x3fff; - bytes[2] = (getBufferDataRW6(3) >> 2) & 0x3; - bytes[3] = ((getBufferDataRW6(3) & 0x3) << 8) | getBufferDataRW6(4); - bytes[4] = (getBufferDataRW6(5) << 2) | (getBufferDataRW6(6) >> 6); - bytes[5] = ((getBufferDataRW6(6) & 0x3f) << 4) | (getBufferDataRW6(7) >> 4); - bytes[6] = (getBufferDataRW6(7) >> 2) & 0x3; - bytes[7] = ((getBufferDataRW6(7) & 0x3) << 8) | getBufferDataRW6(8); - bytes[8] = ((getBufferDataRW6(9) << 2) & 0x3fc) | (getBufferDataRW6(10) >> 6); - bytes[9] = ((getBufferDataRW6(10) << 4) | (getBufferDataRW6(11) >> 4)) & 0x3ff; - bytes[10] = (getBufferDataRW6(11) >> 2) & 0x3; - bytes[11] = ((getBufferDataRW6(11) & 0x3) << 8) | getBufferDataRW6(12); - bytes[12] = (((getBufferDataRW6(13) << 2) & 0x3fc) | getBufferDataRW6(14) >> 6) & 0x3ff; - bytes[13] = ((getBufferDataRW6(14) << 4) | (getBufferDataRW6(15) >> 4)) & 0x3ff; - vpos += 16; - byte = 0; - } - // Main loop - function resetPredNonzeros(){ - pred[0]=0; pred[1]=0; - nonz[0]=0; nonz[1]=0; - } - if (RW2_Format == 7) { - throw RW2_Format; - - // Skatch of version 7 - /* - var pixels_per_block = bitsPerSample == 14 ? 9 : 10; - rowbytes = 0|(rawWidth / pixels_per_block * 16); - for (row = 0; row < rawHeight - 15; row += 16) { - var rowstoread = Math.min(16, rawHeight - row); - var readlen = rowbytes*rowstoread; - buffer = new Uint8Array(image.slice(bidx, bidx+readlen)); - vpos = 0; - bidx += readlen; - i = 0; - for (crow = 0; crow < rowstoread; crow++) { - idx = (row + crow) * rawWidth; - for (col = 0; col <= rawWidth - pixels_per_block; col += pixels_per_block) { - for(j=0; j < pixels_per_block; j++) bytes[j] = buffer[i++]; - if (bitsPerSample == 12) { - result[idx ] = ((bytes[1] & 0xF) << 8) + bytes[0]; - result[idx + 1] = 16 * bytes[2] + (bytes[1] >> 4); - result[idx + 2] = ((bytes[4] & 0xF) << 8) + bytes[3]; - result[idx + 3] = 16 * bytes[5] + (bytes[4] >> 4); - result[idx + 4] = ((bytes[7] & 0xF) << 8) + bytes[6]; - result[idx + 5] = 16 * bytes[8] + (bytes[7] >> 4); - result[idx + 6] = ((bytes[10] & 0xF) << 8) + bytes[9]; - result[idx + 7] = 16 * bytes[11] + (bytes[10] >> 4); - result[idx + 8] = ((bytes[13] & 0xF) << 8) + bytes[12]; - result[idx + 9] = 16 * bytes[14] + (bytes[13] >> 4); - } else if (bitsPerSample == 14) { - result[idx] = bytes[0] + ((bytes[1] & 0x3F) << 8); - result[idx + 1] = (bytes[1] >> 6) + 4 * (bytes[2]) + ((bytes[3] & 0xF) << 10); - result[idx + 2] = (bytes[3] >> 4) + 16 * (bytes[4]) + ((bytes[5] & 3) << 12); - result[idx + 3] = ((bytes[5] & 0xFC) >> 2) + (bytes[6] << 6); - result[idx + 4] = bytes[7] + ((bytes[8] & 0x3F) << 8); - result[idx + 5] = (bytes[8] >> 6) + 4 * bytes[9] + ((bytes[10] & 0xF) << 10); - result[idx + 6] = (bytes[10] >> 4) + 16 * bytes[11] + ((bytes[12] & 3) << 12); - result[idx + 7] = ((bytes[12] & 0xFC) >> 2) + (bytes[13] << 6); - result[idx + 8] = bytes[14] + ((bytes[15] & 0x3F) << 8); + */ + } + else if(RW2_Format == 6) { + var is12bit = bitsPerSample == 12, + readPageRw6Fn = is12bit ? readPageRw6_bps12 : readPageRW6, + pixelsPerBlock = is12bit ? 14 : 11, + pixelbase0 = is12bit ? 0x80 : 0x200, + pixelbase_compare = is12bit ? 0x800 : 0x2000, + spix_compare = is12bit ? 0x3fff : 0xffff, + pixel_mask = is12bit ? 0xfff : 0x3fff, + blocksperrow = rawWidth / pixelsPerBlock, + rowbytes = blocksperrow * 16, + bufferSize = is12bit ? 18 : 14; + + for (row = 0; row < rawHeight - 15; row += 16) { + var rowstoread = Math.min(16, rawHeight - row); + var readlen = rowbytes*rowstoread; + buffer = new Uint8Array(img_buffer, off+bidx, readlen);//new Uint8Array(image.slice(bidx, bidx+readlen)); + vpos = 0; + bidx += readlen; + for (crow = 0, col = 0; crow < rowstoread; crow++, col = 0) { + idx = (row + crow) * rawWidth; + for (var rblock = 0; rblock < blocksperrow; rblock++) { + readPageRw6Fn(); + resetPredNonzeros(); + sh=0; pixel_base=0; + for (i = 0; i < pixelsPerBlock; i++){ + isOdd = i & 1; + if (i % 3 == 2) { + var base = byte < bufferSize ? bytes[byte++] : 0; + if (base == 3) base = 4; + pixel_base = pixelbase0 << base; + sh = 1 << base; } - } - } - } - */ - } - else if(RW2_Format == 6) { - var blocksperrow = Math.floor(rawWidth / 11), - rowbytes = blocksperrow * 16; - for (row = 0; row < rawHeight - 15; row += 16) { - var rowstoread = Math.min(16, rawHeight - row); - var readlen = rowbytes*rowstoread; - buffer = new Uint8Array(img_buffer, off+bidx, readlen);//new Uint8Array(image.slice(bidx, bidx+readlen)); - vpos = 0; - bidx += readlen; - for (crow = 0, col = 0; crow < rowstoread; crow++, col = 0) { - idx = (row + crow) * rawWidth; - for (var rblock = 0; rblock < blocksperrow; rblock++) { - readPageRW6(); - resetPredNonzeros(); - sh=0; pixel_base=0; - for (i = 0; i < 11; i++){ - isOdd = i & 1; - if (i % 3 == 2) { - var base = byte < 14 ? bytes[byte++] : 0; - if (base == 3) base = 4; - pixel_base = 0x200 << base; - sh = 1 << base; - } - var epixel = byte < 14 ? bytes[byte++] : 0; - if (pred[isOdd]) { - epixel *= sh; - if (pixel_base < 0x2000 && nonz[isOdd] > pixel_base) - epixel += nonz[isOdd] - pixel_base; + var epixel = byte < bufferSize ? bytes[byte++] : 0; + if (pred[isOdd]) { + epixel *= sh; + if (pixel_base < pixelbase_compare && nonz[isOdd] > pixel_base) + epixel += nonz[isOdd] - pixel_base; + nonz[isOdd] = epixel; + } else { + pred[isOdd] = epixel; + if (epixel) nonz[isOdd] = epixel; - } else { - pred[isOdd] = epixel; - if (epixel) - nonz[isOdd] = epixel; - else - epixel = nonz[isOdd]; - } - result[idx + col++] = (epixel - 0xf) <= 0xffff ? (epixel - 0xf) & 0xffff : ((epixel + 0x7ffffff1) >> 0x1f) & 0x3fff; + else + epixel = nonz[isOdd]; } + result[idx + col++] = (epixel - 0xf) <= spix_compare ? (epixel - 0xf) & spix_compare : ((epixel + 0x7ffffff1) >> 0x1f) & pixel_mask; } } } - } - else if (RW2_Format == 5) { - var blockSize = bitsPerSample == 12 ? 10 : 9; - for (row = 0; row < rawHeight; row++) { - for (col = 0; col < rawWidth; col+=blockSize) { - getDataRaw(0); - // Tuhle podminku pouziva i RW2_Format 7 - if (bitsPerSample == 12) { - result[idx++] = ((bytes[1] & 0xF) << 8) + bytes[0]; - result[idx++] = 16 * bytes[2] + (bytes[1] >> 4); - result[idx++] = ((bytes[4] & 0xF) << 8) + bytes[3]; - result[idx++] = 16 * bytes[5] + (bytes[4] >> 4); - result[idx++] = ((bytes[7] & 0xF) << 8) + bytes[6]; - result[idx++] = 16 * bytes[8] + (bytes[7] >> 4); - result[idx++] = ((bytes[10] & 0xF) << 8) + bytes[9]; - result[idx++] = 16 * bytes[11] + (bytes[10] >> 4); - result[idx++] = ((bytes[13] & 0xF) << 8) + bytes[12]; - result[idx++] = 16 * bytes[14] + (bytes[13] >> 4); - } else if (bitsPerSample == 14) { - result[idx++] = bytes[0] + ((bytes[1] & 0x3F) << 8); - result[idx++] = (bytes[1] >> 6) + 4 * (bytes[2]) + ((bytes[3] & 0xF) << 10); - result[idx++] = (bytes[3] >> 4) + 16 * (bytes[4]) + ((bytes[5] & 3) << 12); - result[idx++] = ((bytes[5] & 0xFC) >> 2) + (bytes[6] << 6); - result[idx++] = bytes[7] + ((bytes[8] & 0x3F) << 8); - result[idx++] = (bytes[8] >> 6) + 4 * bytes[9] + ((bytes[10] & 0xF) << 10); - result[idx++] = (bytes[10] >> 4) + 16 * bytes[11] + ((bytes[12] & 3) << 12); - result[idx++] = ((bytes[12] & 0xFC) >> 2) + (bytes[13] << 6); - result[idx++] = bytes[14] + ((bytes[15] & 0x3F) << 8); - } + } + } + else if (RW2_Format == 5) { + var blockSize = bitsPerSample == 12 ? 10 : 9; + for (row = 0; row < rawHeight; row++) { + for (col = 0; col < rawWidth; col+=blockSize) { + getDataRaw(0); + // Tuhle podminku pouziva i RW2_Format 7 + if (bitsPerSample == 12) { + result[idx++] = ((bytes[1] & 0xF) << 8) + bytes[0]; + result[idx++] = 16 * bytes[2] + (bytes[1] >> 4); + result[idx++] = ((bytes[4] & 0xF) << 8) + bytes[3]; + result[idx++] = 16 * bytes[5] + (bytes[4] >> 4); + result[idx++] = ((bytes[7] & 0xF) << 8) + bytes[6]; + result[idx++] = 16 * bytes[8] + (bytes[7] >> 4); + result[idx++] = ((bytes[10] & 0xF) << 8) + bytes[9]; + result[idx++] = 16 * bytes[11] + (bytes[10] >> 4); + result[idx++] = ((bytes[13] & 0xF) << 8) + bytes[12]; + result[idx++] = 16 * bytes[14] + (bytes[13] >> 4); + } else if (bitsPerSample == 14) { + result[idx++] = bytes[0] + ((bytes[1] & 0x3F) << 8); + result[idx++] = (bytes[1] >> 6) + 4 * (bytes[2]) + ((bytes[3] & 0xF) << 10); + result[idx++] = (bytes[3] >> 4) + 16 * (bytes[4]) + ((bytes[5] & 3) << 12); + result[idx++] = ((bytes[5] & 0xFC) >> 2) + (bytes[6] << 6); + result[idx++] = bytes[7] + ((bytes[8] & 0x3F) << 8); + result[idx++] = (bytes[8] >> 6) + 4 * bytes[9] + ((bytes[10] & 0xF) << 10); + result[idx++] = (bytes[10] >> 4) + 16 * bytes[11] + ((bytes[12] & 3) << 12); + result[idx++] = ((bytes[12] & 0xFC) >> 2) + (bytes[13] << 6); + result[idx++] = bytes[14] + ((bytes[15] & 0x3F) << 8); } } - //console.log(result[1000000 - 1]) - } else if(RW2_Format == 4) { - for (row = 0; row < rawHeight; row++){ - for(col = 0; col < rawWidth; col++){ - i = col % 14; - isOdd = i & 1; - if (i==0) resetPredNonzeros(); - if (i%3 == 2) - sh = 4 >> (3 - getDataRaw(2)); - if (nonz[isOdd]) { - j = getDataRaw(8); - if(j != 0){ - pred[isOdd] -= 0x80 << sh; - if (pred[isOdd] < 0 || sh == 4) - pred[isOdd] &= ~((-1) << sh); - pred[isOdd] += j << sh; - } - } else { - nonz[isOdd] = getDataRaw(8); - if(nonz[isOdd] || i > 11) - pred[isOdd] = nonz[isOdd] << 4 | getDataRaw(4); + } + //console.log(result[1000000 - 1]) + } else if(RW2_Format == 4) { + for (row = 0; row < rawHeight; row++){ + for(col = 0; col < rawWidth; col++){ + i = col % 14; + isOdd = i & 1; + if (i==0) resetPredNonzeros(); + if (i%3 == 2) + sh = 4 >> (3 - getDataRaw(2)); + if (nonz[isOdd]) { + j = getDataRaw(8); + if(j != 0){ + pred[isOdd] -= 0x80 << sh; + if (pred[isOdd] < 0 || sh == 4) + pred[isOdd] &= ~((-1) << sh); + pred[isOdd] += j << sh; } - result[idx++] = pred[col & 1]; + } else { + nonz[isOdd] = getDataRaw(8); + if(nonz[isOdd] || i > 11) + pred[isOdd] = nonz[isOdd] << 4 | getDataRaw(4); } + result[idx++] = pred[col & 1]; } - } - else throw RW2_Format; - } - - - UTIF.decode._decodeVC5 = UTIF.decode._decodeVC5=function(){var e=[1,0,1,0,2,2,1,1,3,7,1,2,5,25,1,3,6,48,1,4,6,54,1,5,7,111,1,8,7,99,1,6,7,105,12,0,7,107,1,7,8,209,20,0,8,212,1,9,8,220,1,10,9,393,1,11,9,394,32,0,9,416,1,12,9,427,1,13,10,887,1,18,10,784,1,14,10,790,1,15,10,835,60,0,10,852,1,16,10,885,1,17,11,1571,1,19,11,1668,1,20,11,1669,100,0,11,1707,1,21,11,1772,1,22,12,3547,1,29,12,3164,1,24,12,3166,1,25,12,3140,1,23,12,3413,1,26,12,3537,1,27,12,3539,1,28,13,7093,1,35,13,6283,1,30,13,6331,1,31,13,6335,180,0,13,6824,1,32,13,7072,1,33,13,7077,320,0,13,7076,1,34,14,12565,1,36,14,12661,1,37,14,12669,1,38,14,13651,1,39,14,14184,1,40,15,28295,1,46,15,28371,1,47,15,25320,1,42,15,25336,1,43,15,25128,1,41,15,27300,1,44,15,28293,1,45,16,50259,1,48,16,50643,1,49,16,50675,1,50,16,56740,1,53,16,56584,1,51,16,56588,1,52,17,113483,1,61,17,113482,1,60,17,101285,1,55,17,101349,1,56,17,109205,1,57,17,109207,1,58,17,100516,1,54,17,113171,1,59,18,202568,1,62,18,202696,1,63,18,218408,1,64,18,218412,1,65,18,226340,1,66,18,226356,1,67,18,226358,1,68,19,402068,1,69,19,405138,1,70,19,405394,1,71,19,436818,1,72,19,436826,1,73,19,452714,1,75,19,452718,1,76,19,452682,1,74,20,804138,1,77,20,810279,1,78,20,810790,1,79,20,873638,1,80,20,873654,1,81,20,905366,1,82,20,905430,1,83,20,905438,1,84,21,1608278,1,85,21,1620557,1,86,21,1621582,1,87,21,1621583,1,88,21,1747310,1,89,21,1810734,1,90,21,1810735,1,91,21,1810863,1,92,21,1810879,1,93,22,3621725,1,99,22,3621757,1,100,22,3241112,1,94,22,3494556,1,95,22,3494557,1,96,22,3494622,1,97,22,3494623,1,98,23,6482227,1,102,23,6433117,1,101,23,6989117,1,103,23,6989119,1,105,23,6989118,1,104,23,7243449,1,106,23,7243512,1,107,24,13978233,1,111,24,12964453,1,109,24,12866232,1,108,24,14486897,1,113,24,13978232,1,110,24,14486896,1,112,24,14487026,1,114,24,14487027,1,115,25,25732598,1,225,25,25732597,1,189,25,25732596,1,188,25,25732595,1,203,25,25732594,1,202,25,25732593,1,197,25,25732592,1,207,25,25732591,1,169,25,25732590,1,223,25,25732589,1,159,25,25732522,1,235,25,25732579,1,152,25,25732575,1,192,25,25732489,1,179,25,25732573,1,201,25,25732472,1,172,25,25732576,1,149,25,25732488,1,178,25,25732566,1,120,25,25732571,1,219,25,25732577,1,150,25,25732487,1,127,25,25732506,1,211,25,25732548,1,125,25,25732588,1,158,25,25732486,1,247,25,25732467,1,238,25,25732508,1,163,25,25732552,1,228,25,25732603,1,183,25,25732513,1,217,25,25732587,1,168,25,25732520,1,122,25,25732484,1,128,25,25732562,1,249,25,25732505,1,187,25,25732504,1,186,25,25732483,1,136,25,25928905,1,181,25,25732560,1,255,25,25732500,1,230,25,25732482,1,135,25,25732555,1,233,25,25732568,1,222,25,25732583,1,145,25,25732481,1,134,25,25732586,1,167,25,25732521,1,248,25,25732518,1,209,25,25732480,1,243,25,25732512,1,216,25,25732509,1,164,25,25732547,1,140,25,25732479,1,157,25,25732544,1,239,25,25732574,1,191,25,25732564,1,251,25,25732478,1,156,25,25732546,1,139,25,25732498,1,242,25,25732557,1,133,25,25732477,1,162,25,25732515,1,213,25,25732584,1,165,25,25732514,1,212,25,25732476,1,227,25,25732494,1,198,25,25732531,1,236,25,25732530,1,234,25,25732529,1,117,25,25732528,1,215,25,25732527,1,124,25,25732526,1,123,25,25732525,1,254,25,25732524,1,253,25,25732523,1,148,25,25732570,1,218,25,25732580,1,146,25,25732581,1,147,25,25732569,1,224,25,25732533,1,143,25,25732540,1,184,25,25732541,1,185,25,25732585,1,166,25,25732556,1,132,25,25732485,1,129,25,25732563,1,250,25,25732578,1,151,25,25732501,1,119,25,25732502,1,193,25,25732536,1,176,25,25732496,1,245,25,25732553,1,229,25,25732516,1,206,25,25732582,1,144,25,25732517,1,208,25,25732558,1,137,25,25732543,1,241,25,25732466,1,237,25,25732507,1,190,25,25732542,1,240,25,25732551,1,131,25,25732554,1,232,25,25732565,1,252,25,25732475,1,171,25,25732493,1,205,25,25732492,1,204,25,25732491,1,118,25,25732490,1,214,25,25928904,1,180,25,25732549,1,126,25,25732602,1,182,25,25732539,1,175,25,25732545,1,141,25,25732559,1,138,25,25732537,1,177,25,25732534,1,153,25,25732503,1,194,25,25732606,1,160,25,25732567,1,121,25,25732538,1,174,25,25732497,1,246,25,25732550,1,130,25,25732572,1,200,25,25732474,1,170,25,25732511,1,221,25,25732601,1,196,25,25732532,1,142,25,25732519,1,210,25,25732495,1,199,25,25732605,1,155,25,25732535,1,154,25,25732499,1,244,25,25732510,1,220,25,25732600,1,195,25,25732607,1,161,25,25732604,1,231,25,25732473,1,173,25,25732599,1,226,26,51465122,1,116,26,51465123,0,1],x,u,H,d=[3,3,3,3,2,2,2,1,1,1],a=24576,a7=16384,K=8192,ai=a7|K; - function A(B){var P=B[1],D=B[0][P>>>3]>>>7-(P&7)&1;B[1]++;return D}function aj(B,P){if(x==null){x={}; - for(var D=0;D>>1}return B}function c(B,P){return B>>P}function N(B,P,D,U,X,y){P[D]=c(c(11*B[X]-4*B[X+y]+B[X+y+y]+4,3)+B[U],1); - P[D+y]=c(c(5*B[X]+4*B[X+y]-B[X+y+y]+4,3)-B[U],1)}function g(B,P,D,U,X,y){var n=B[X-y]-B[X+y],S=B[X],O=B[U]; - P[D]=c(c(n+4,3)+S+O,1);P[D+y]=c(c(-n+4,3)+S-O,1)}function L(B,P,D,U,X,y){P[D]=c(c(5*B[X]+4*B[X-y]-B[X-y-y]+4,3)+B[U],1); - P[D+y]=c(c(11*B[X]-4*B[X-y]+B[X-y-y]+4,3)-B[U],1)}function t(B){B=B<0?0:B>4095?4095:B;B=H[B]>>>2;return B}function ab(B,P,D,U,X){U=new Uint16Array(U.buffer); - var y=Date.now(),n=UTIF._binBE,S=P+D,O,q,i,M,m,aA,T,a8,a0,am,au,a3,aw,ao,v,ax,p,k;P+=4;while(P>>1)*(i>>>1));k=new Int16Array((q>>>1)*(i>>>1));u=new Int16Array(1024); - for(var f=0;f<1024;f++){var aF=f-512,j=Math.abs(aF),O=Math.floor(768*j*j*j/(255*255*255))+j;u[f]=Math.sign(aF)*O}H=new Uint16Array(4096); - var al=(1<<16)-1;for(var f=0;f<4096;f++){var ad=f,az=al*(Math.pow(113,ad/4095)-1)/112;H[f]=Math.min(az,al)}}var Z=p[T],V=Q(q,1+d[M]),z=Q(i,1+d[M]); - if(M==0){for(var b=0;b>>1)+G]=B[w]<<8|B[w+1]}}else{var aC=[B,P*8],aq=[],a5=0,ae=V*z,I=[0,0],s=0,E=0; - while(a50){aq[a5++]=E;s--}}var $=(M-1)%3,aE=$!=1?V:0,as=$!=0?z:0; - for(var b=0;b>>1)+aE,aa=b*V;for(var G=0;G>>1,an=V*2,at=z*2; - for(var b=0;b>14-r*2&3;var af=a6[aD];if(af!=0)for(var b=0;b>>1)*(q>>>1)+(G>>>1),R=a2[w],ak=ar[w]-2048,aB=ah[w]-2048,av=a1[w]-2048,a4=(ak<<1)+R,a9=(aB<<1)+R,ap=R+av,ag=R-av; - U[J]=t(a4);U[J+1]=t(ap);U[J+q]=t(ag);U[J+q+1]=t(a9)}}P+=o*4}else if(C==16388){P+=o*4}else if(F==8192||F==8448||F==9216){}else throw C.toString(16)}}console.log(Date.now()-y)}return ab}() - - UTIF.decode._decodeLogLuv32 = function(img, data, off, len, tgt, toff) { - var w = img.width, qw=w*4; - var io = 0, out = new Uint8Array(qw); - - while(io>> (tab[i] >>> 8); - for(var c=0; c>>4); tgt[toff+i+1]=(b0<<4)|(b2>>>4); tgt[toff+i+2]=(b2<<4)|(b1>>>4); } - return; - } - - var pix = new Uint16Array(16); - var row, col, val, max, min, imax, imin, sh, bit, i, dp; - - var data = new Uint8Array(raw_width+1); - for (row=0; row < height; row++) { - //fread (data, 1, raw_width, ifp); - for(var j=0; j>> 11); - imax = 0x0f & (val >>> 22); - imin = 0x0f & (val >>> 26); - for (sh=0; sh < 4 && 0x80 << sh <= max-min; sh++); - for (bit=30, i=0; i < 16; i++) - if (i == imax) pix[i] = max; - else if (i == imin) pix[i] = min; - else { - pix[i] = ((bin.readUshort(data, dp+(bit >> 3)) >>> (bit & 7) & 0x7f) << sh) + min; - if (pix[i] > 0x7ff) pix[i] = 0x7ff; - bit += 7; } - for (i=0; i < 16; i++, col+=2) { - //RAW(row,col) = curve[pix[i] << 1] >> 2; - var clr = pix[i]<<1; //clr = 0xffff; - UTIF.decode._putsF(tgt, (row*raw_width+col)*tiff_bps, clr<<(16-tiff_bps)); - } - col -= col & 1 ? 1:31; - } - } - } - - UTIF.decode._decodeNikon = function(img,imgs, data, off, src_length, tgt, toff) - { - var nikon_tree = [ - [ 0, 0,1,5,1,1,1,1,1,1,2,0,0,0,0,0,0, /* 12-bit lossy */ - 5,4,3,6,2,7,1,0,8,9,11,10,12 ], - [ 0, 0,1,5,1,1,1,1,1,1,2,0,0,0,0,0,0, /* 12-bit lossy after split */ - 0x39,0x5a,0x38,0x27,0x16,5,4,3,2,1,0,11,12,12 ], - [ 0, 0,1,4,2,3,1,2,0,0,0,0,0,0,0,0,0, /* 12-bit lossless */ - 5,4,6,3,7,2,8,1,9,0,10,11,12 ], - [ 0, 0,1,4,3,1,1,1,1,1,2,0,0,0,0,0,0, /* 14-bit lossy */ - 5,6,4,7,8,3,9,2,1,0,10,11,12,13,14 ], - [ 0, 0,1,5,1,1,1,1,1,1,1,2,0,0,0,0,0, /* 14-bit lossy after split */ - 8,0x5c,0x4b,0x3a,0x29,7,6,5,4,3,2,1,0,13,14 ], - [ 0, 0,1,4,2,2,3,1,2,0,0,0,0,0,0,0,0, /* 14-bit lossless */ - 7,6,8,5,9,4,10,3,11,12,2,0,1,13,14 ] ]; - - var raw_width = img["t256"][0], height=img["t257"][0], tiff_bps=img["t258"][0]; - - var tree = 0, split = 0; - var make_decoder = UTIF.decode._make_decoder; - var getbithuff = UTIF.decode._getbithuff; - - var mn = imgs[0].exifIFD.makerNote, md = mn["t150"]?mn["t150"]:mn["t140"], mdo=0; //console.log(mn,md); - //console.log(md[0].toString(16), md[1].toString(16), tiff_bps); - var ver0 = md[mdo++], ver1 = md[mdo++]; - if (ver0 == 0x49 || ver1 == 0x58) mdo+=2110; - if (ver0 == 0x46) tree = 2; - if (tiff_bps == 14) tree += 3; - - var vpred = [[0,0],[0,0]], bin=(img.isLE ? UTIF._binLE : UTIF._binBE); - for(var i=0; i<2; i++) for(var j=0; j<2; j++) { vpred[i][j] = bin.readShort(md,mdo); mdo+=2; } // not sure here ... [i][j] or [j][i] - //console.log(vpred); - - - var max = 1 << tiff_bps & 0x7fff, step=0; - var csize = bin.readShort(md,mdo); mdo+=2; - if (csize > 1) step = Math.floor(max / (csize-1)); - if (ver0 == 0x44 && ver1 == 0x20 && step > 0) split = bin.readShort(md,562); - - - var i; - var row, col; - var len, shl, diff; - var min_v = 0; - var hpred = [0,0]; - var huff = make_decoder(nikon_tree[tree]); - - //var g_input_offset=0, bitbuf=0, vbits=0, reset=0; - var prm = [off,0,0,0]; - //console.log(split); split = 170; - - for (min_v=row=0; row < height; row++) { - if (split && row == split) { - //free (huff); - huff = make_decoder (nikon_tree[tree+1]); - //max_v += (min_v = 16) << 1; - } - for (col=0; col < raw_width; col++) { - i = getbithuff(data,prm,huff[0],huff); - len = i & 15; - shl = i >>> 4; - diff = (((getbithuff(data,prm,len-shl,0) << 1) + 1) << shl) >>> 1; - if ((diff & (1 << (len-1))) == 0) - diff -= (1 << len) - (shl==0?1:0); - if (col < 2) hpred[col] = vpred[row & 1][col] += diff; - else hpred[col & 1] += diff; - - var clr = Math.min(Math.max(hpred[col & 1],0),(1<>>3); dt[o]|=val>>>16; dt[o+1]|=val>>>8; dt[o+2]|=val; } - - - UTIF.decode._getbithuff = function(data,prm,nbits, huff) { - var zero_after_ff = 0; - var get_byte = UTIF.decode._get_byte; - var c; - - var off=prm[0], bitbuf=prm[1], vbits=prm[2], reset=prm[3]; - - //if (nbits > 25) return 0; - //if (nbits < 0) return bitbuf = vbits = reset = 0; - if (nbits == 0 || vbits < 0) return 0; - while (!reset && vbits < nbits && (c = data[off++]) != -1 && - !(reset = zero_after_ff && c == 0xff && data[off++])) { - //console.log("byte read into c"); - bitbuf = (bitbuf << 8) + c; - vbits += 8; - } - c = (bitbuf << (32-vbits)) >>> (32-nbits); - if (huff) { - vbits -= huff[c+1] >>> 8; //console.log(c, huff[c]>>8); - c = huff[c+1]&255; - } else - vbits -= nbits; - if (vbits < 0) throw "e"; - - prm[0]=off; prm[1]=bitbuf; prm[2]=vbits; prm[3]=reset; - - return c; - } - - UTIF.decode._make_decoder = function(source) { - var max, len, h, i, j; - var huff = []; - - for (max=16; max!=0 && !source[max]; max--); - var si=17; - - huff[0] = max; - for (h=len=1; len <= max; len++) - for (i=0; i < source[len]; i++, ++si) - for (j=0; j < 1 << (max-len); j++) - if (h <= 1 << max) - huff[h++] = (len << 8) | source[si]; - return huff; - } - - UTIF.decode._decodeNewJPEG = function(img, data, off, len, tgt, toff) - { - len = Math.min(len, data.length-off); - var tables = img["t347"], tlen = tables ? tables.length : 0, buff = new Uint8Array(tlen + len); - - if (tables) { - var SOI = 216, EOI = 217, boff = 0; - for (var i=0; i<(tlen-1); i++) - { - // Skip EOI marker from JPEGTables - if (tables[i]==255 && tables[i+1]==EOI) break; - buff[boff++] = tables[i]; - } - - // Skip SOI marker from data - var byte1 = data[off], byte2 = data[off + 1]; - if (byte1!=255 || byte2!=SOI) - { - buff[boff++] = byte1; - buff[boff++] = byte2; - } - for (var i=2; i>>8); } - else for(var i=0; i>>8); tgt[toff+(i<<1)+1] = (out[i]&255); } - } - else if(bps==14 || bps==12) { // 4 * 14 == 56 == 7 * 8 - var rst = 16-bps; - for(var i=0; i 1); - } - - if(!isTiled) - { - if(data[off]==255 && data[off+1]==SOI) return { jpegOffset: off }; - if(jpgIchgFmt!=null) - { - if(data[off+jifoff]==255 && data[off+jifoff+1]==SOI) joff = off+jifoff; - else log("JPEGInterchangeFormat does not point to SOI"); - - if(jpgIchgFmtLen==null) log("JPEGInterchangeFormatLength field is missing"); - else if(jifoff >= soff || (jifoff+jiflen) <= soff) log("JPEGInterchangeFormatLength field value is invalid"); - - if(joff != null) return { jpegOffset: joff }; - } - } - - if(ycbcrss!=null) { ssx = ycbcrss[0]; ssy = ycbcrss[1]; } - - if(jpgIchgFmt!=null) - if(jpgIchgFmtLen!=null) - if(jiflen >= 2 && (jifoff+jiflen) <= soff) - { - if(data[off+jifoff+jiflen-2]==255 && data[off+jifoff+jiflen-1]==SOI) tables = new Uint8Array(jiflen-2); - else tables = new Uint8Array(jiflen); - - for(i=0; i offset to first strip or tile"); - - if(tables == null) - { - var ooff = 0, out = []; - out[ooff++] = 255; out[ooff++] = SOI; - - var qtables = img["t519"]; - if(qtables==null) throw new Error("JPEGQTables tag is missing"); - for(i=0; i>> 8); out[ooff++] = nc & 255; - out[ooff++] = (i | (k << 4)); - for(j=0; j<16; j++) out[ooff++] = data[off+htables[i]+j]; - for(j=0; j>> 8) & 255; out[ooff++] = img.height & 255; - out[ooff++] = (img.width >>> 8) & 255; out[ooff++] = img.width & 255; - out[ooff++] = spp; - if(spp==1) { out[ooff++] = 1; out[ooff++] = 17; out[ooff++] = 0; } - else for(i=0; i<3; i++) - { - out[ooff++] = i + 1; - out[ooff++] = (i != 0) ? 17 : (((ssx & 15) << 4) | (ssy & 15)); - out[ooff++] = i; - } - - if(jpgresint!=null && jpgresint[0]!=0) - { - out[ooff++] = 255; out[ooff++] = DRI; out[ooff++] = 0; out[ooff++] = 4; - out[ooff++] = (jpgresint[0] >>> 8) & 255; - out[ooff++] = jpgresint[0] & 255; - } - - tables = new Uint8Array(out); - } - - var sofpos = -1; - i = 0; - while(i < (tables.length - 1)) { - if(tables[i]==255 && tables[i+1]==SOF0) { sofpos = i; break; } - i++; - } - - if(sofpos == -1) - { - var tmptab = new Uint8Array(tables.length + 10 + 3*spp); - tmptab.set(tables); - var tmpoff = tables.length; - sofpos = tables.length; - tables = tmptab; - - tables[tmpoff++] = 255; tables[tmpoff++] = SOF0; - tables[tmpoff++] = 0; tables[tmpoff++] = 8 + 3*spp; tables[tmpoff++] = 8; - tables[tmpoff++] = (img.height >>> 8) & 255; tables[tmpoff++] = img.height & 255; - tables[tmpoff++] = (img.width >>> 8) & 255; tables[tmpoff++] = img.width & 255; - tables[tmpoff++] = spp; - if(spp==1) { tables[tmpoff++] = 1; tables[tmpoff++] = 17; tables[tmpoff++] = 0; } - else for(i=0; i<3; i++) - { - tables[tmpoff++] = i + 1; - tables[tmpoff++] = (i != 0) ? 17 : (((ssx & 15) << 4) | (ssy & 15)); - tables[tmpoff++] = i; - } - } - - if(data[soff]==255 && data[soff+1]==SOS) - { - var soslen = (data[soff+2]<<8) | data[soff+3]; - sosMarker = new Uint8Array(soslen+2); - sosMarker[0] = data[soff]; sosMarker[1] = data[soff+1]; sosMarker[2] = data[soff+2]; sosMarker[3] = data[soff+3]; - for(i=0; i<(soslen-2); i++) sosMarker[i+4] = data[soff+i+4]; - } - else - { - sosMarker = new Uint8Array(2 + 6 + 2*spp); - var sosoff = 0; - sosMarker[sosoff++] = 255; sosMarker[sosoff++] = SOS; - sosMarker[sosoff++] = 0; sosMarker[sosoff++] = 6 + 2*spp; sosMarker[sosoff++] = spp; - if(spp==1) { sosMarker[sosoff++] = 1; sosMarker[sosoff++] = 0; } - else for(i=0; i<3; i++) - { - sosMarker[sosoff++] = i+1; sosMarker[sosoff++] = (i << 4) | i; - } - sosMarker[sosoff++] = 0; sosMarker[sosoff++] = 63; sosMarker[sosoff++] = 0; - } - - return { jpegOffset: off, tables: tables, sosMarker: sosMarker, sofPosition: sofpos }; - } - - UTIF.decode._decodeOldJPEG = function(img, data, off, len, tgt, toff) - { - var i, dlen, tlen, buff, buffoff; - var jpegData = UTIF.decode._decodeOldJPEGInit(img, data, off, len); - - if(jpegData.jpegOffset!=null) - { - dlen = off+len-jpegData.jpegOffset; - buff = new Uint8Array(dlen); - for(i=0; i>> 8) & 255; buff[jpegData.sofPosition+6] = img.height & 255; - buff[jpegData.sofPosition+7] = (img.width >>> 8) & 255; buff[jpegData.sofPosition+8] = img.width & 255; - - if(data[off]!=255 || data[off+1]!=SOS) - { - buff.set(jpegData.sosMarker, buffoff); - buffoff += sosMarker.length; - } - for(i=0; i=0 && n<128) for(var i=0; i< n+1; i++) { ta[toff]=sa[off]; toff++; off++; } - if(n>=-127 && n<0) { for(var i=0; i<-n+1; i++) { ta[toff]=sa[off]; toff++; } off++; } - } - return toff; - } - - UTIF.decode._decodeThunder = function(data, off, len, tgt, toff) - { - var d2 = [ 0, 1, 0, -1 ], d3 = [ 0, 1, 2, 3, 0, -3, -2, -1 ]; - var lim = off+len, qoff = toff*2, px = 0; - while(off>>6), n = (b&63); off++; - if(msk==3) { px=(n&15); tgt[qoff>>>1] |= (px<<(4*(1-qoff&1))); qoff++; } - if(msk==0) for(var i=0; i>>1] |= (px<<(4*(1-qoff&1))); qoff++; } - if(msk==2) for(var i=0; i<2; i++) { var d=(n>>>(3*(1-i)))&7; if(d!=4) { px+=d3[d]; tgt[qoff>>>1] |= (px<<(4*(1-qoff&1))); qoff++; } } - if(msk==1) for(var i=0; i<3; i++) { var d=(n>>>(2*(2-i)))&3; if(d!=2) { px+=d2[d]; tgt[qoff>>>1] |= (px<<(4*(1-qoff&1))); qoff++; } } - } - } - - UTIF.decode._dmap = { "1":0,"011":1,"000011":2,"0000011":3, "010":-1,"000010":-2,"0000010":-3 }; - UTIF.decode._lens = ( function() - { - var addKeys = function(lens, arr, i0, inc) { for(var i=0; i>>3)>>3]>>>(7-(boff&7)))&1; - if(fo==2) bit = (data[boff>>>3]>>>( (boff&7)))&1; - boff++; wrd+=bit; - if(mode=="H") - { - if(U._lens[clr][wrd]!=null) - { - var dl=U._lens[clr][wrd]; wrd=""; len+=dl; - if(dl<64) { U._addNtimes(line,len,clr); a0+=len; clr=1-clr; len=0; toRead--; if(toRead==0) mode=""; } - } - } - else - { - if(wrd=="0001") { wrd=""; U._addNtimes(line,b2-a0,clr); a0=b2; } - if(wrd=="001" ) { wrd=""; mode="H"; toRead=2; } - if(U._dmap[wrd]!=null) { a1 = b1+U._dmap[wrd]; U._addNtimes(line, a1-a0, clr); a0=a1; wrd=""; clr=1-clr; } - } - if(line.length==w && mode=="") - { - U._writeBits(line, tgt, toff*8+y*bipl); - clr=0; y++; a0=0; - pline=U._makeDiff(line); line=[]; - } - //if(wrd.length>150) { log(wrd); break; throw "e"; } - } - } - - UTIF.decode._findDiff = function(line, x, clr) { for(var i=0; i=x && line[i+1]==clr) return line[i]; } - - UTIF.decode._makeDiff = function(line) - { - var out = []; if(line[0]==1) out.push(0,1); - for(var i=1; i>>3)>>3]>>>(7-(boff&7)))&1; - if(fo==2) bit = (data[boff>>>3]>>>( (boff&7)))&1; - boff++; wrd+=bit; - - len = U._lens[clr][wrd]; - if(len!=null) { - U._addNtimes(line,len,clr); wrd=""; - if(len<64) clr = 1-clr; - if(line.length==w) { U._writeBits(line, tgt, toff*8+y*bipl); line=[]; y++; clr=0; if((boff&7)!=0) boff+=8-(boff&7); if(len>=64) boff+=8; } - } - } - } - - UTIF.decode._decodeG3 = function(data, off, slen, tgt, toff, w, fo, twoDim) - { - var U = UTIF.decode, boff=off<<3, len=0, wrd=""; - var line=[], pline=[]; for(var i=0; i>>3)>>3]>>>(7-(boff&7)))&1; - if(fo==2) bit = (data[boff>>>3]>>>( (boff&7)))&1; - boff++; wrd+=bit; - - if(is1D) - { - if(U._lens[clr][wrd]!=null) - { - var dl=U._lens[clr][wrd]; wrd=""; len+=dl; - if(dl<64) { U._addNtimes(line,len,clr); clr=1-clr; len=0; } - } - } - else - { - if(mode=="H") - { - if(U._lens[clr][wrd]!=null) - { - var dl=U._lens[clr][wrd]; wrd=""; len+=dl; - if(dl<64) { U._addNtimes(line,len,clr); a0+=len; clr=1-clr; len=0; toRead--; if(toRead==0) mode=""; } - } - } - else - { - if(wrd=="0001") { wrd=""; U._addNtimes(line,b2-a0,clr); a0=b2; } - if(wrd=="001" ) { wrd=""; mode="H"; toRead=2; } - if(U._dmap[wrd]!=null) { a1 = b1+U._dmap[wrd]; U._addNtimes(line, a1-a0, clr); a0=a1; wrd=""; clr=1-clr; } - } - } - if(wrd.endsWith("000000000001")) // needed for some files - { - if(y>=0) U._writeBits(line, tgt, toff*8+y*bipl); - if(twoDim) { - if(fo==1) is1D = ((data[boff>>>3]>>>(7-(boff&7)))&1)==1; - if(fo==2) is1D = ((data[boff>>>3]>>>( (boff&7)))&1)==1; - boff++; - } - //log("EOL",y, "next 1D:", is1D); - wrd=""; clr=0; y++; a0=0; - pline=U._makeDiff(line); line=[]; - } - } - if(line.length==w) U._writeBits(line, tgt, toff*8+y*bipl); - } - - UTIF.decode._addNtimes = function(arr, n, val) { for(var i=0; i>>3] |= (bits[i]<<(7-((boff+i)&7))); - } - - UTIF.decode._decodeLZW=UTIF.decode._decodeLZW=function(){var e,U,Z,u,K=0,V=0,g=0,N=0,O=function(){var S=e>>>3,A=U[S]<<16|U[S+1]<<8|U[S+2],j=A>>>24-(e&7)-V&(1<>>----------------"); - for(var i=0; i4) { bin.writeUint(data, offset, eoff); toff=eoff; } - - if (type== 1 || type==7) { for(var i=0; i4) { dlen += (dlen&1); eoff += dlen; } - offset += 4; - } - return [offset, eoff]; - } - - UTIF.toRGBA8 = function(out, scl) - { - var w = out.width, h = out.height, area = w*h, qarea = area*4, data = out.data; - var img = new Uint8Array(area*4); - //console.log(out); - // 0: WhiteIsZero, 1: BlackIsZero, 2: RGB, 3: Palette color, 4: Transparency mask, 5: CMYK - var intp = (out["t262"] ? out["t262"][0]: 2), bps = (out["t258"]?Math.min(32,out["t258"][0]):1); - if(out["t262"]==null && bps==1) intp=0; - //log("interpretation: ", intp, "bps", bps, out); - - if(false) {} - else if(intp==0) - { - var bpl = Math.ceil(bps*w/8); - for(var y=0; y>3)])>>(7- (i&7)))& 1; img[qi]=img[qi+1]=img[qi+2]=( 1-px)*255; img[qi+3]=255; } - if(bps== 4) for(var i=0; i>1)])>>(4-4*(i&1)))&15; img[qi]=img[qi+1]=img[qi+2]=(15-px)* 17; img[qi+3]=255; } - if(bps== 8) for(var i=0; i>3)])>>(7- (i&7)))&1; img[qi]=img[qi+1]=img[qi+2]=(px)*255; img[qi+3]=255; } - if(bps== 2) for(var i=0; i>2)])>>(6-2*(i&3)))&3; img[qi]=img[qi+1]=img[qi+2]=(px)* 85; img[qi+3]=255; } - if(bps== 8) for(var i=0; i1 && out["t338"] && out["t338"][0]!=0; - - for(var y=0; y>>3)]>>>(7- (x&7)))& 1; - else if(bps==2) mi=(data[dof+(x>>>2)]>>>(6-2*(x&3)))& 3; - else if(bps==4) mi=(data[dof+(x>>>1)]>>>(4-4*(x&1)))&15; - else if(bps==8) mi= data[dof+x*smpls]; - else throw bps; - img[qi]=(map[mi]>>8); img[qi+1]=(map[cn+mi]>>8); img[qi+2]=(map[cn+cn+mi]>>8); img[qi+3]=nexta ? data[dof+x*smpls+1] : 255; - } - } - else if(intp==5) - { - var smpls = out["t258"]?out["t258"].length : 4; - var gotAlpha = smpls>4 ? 1 : 0; - for(var i=0; i>>1); - var Y = data[si+(j&1)], Cb=data[si+2]-128, Cr=data[si+3]-128; - - var r = Y + ( (Cr >> 2) + (Cr >> 3) + (Cr >> 5) ) ; - var g = Y - ( (Cb >> 2) + (Cb >> 4) + (Cb >> 5)) - ( (Cr >> 1) + (Cr >> 3) + (Cr >> 4) + (Cr >> 5)) ; - var b = Y + ( Cb + (Cb >> 1) + (Cb >> 2) + (Cb >> 6)) ; - - img[qi ]=Math.max(0,Math.min(255,r)); - img[qi+1]=Math.max(0,Math.min(255,g)); - img[qi+2]=Math.max(0,Math.min(255,b)); - img[qi+3]=255; - } - } - } - else if(intp==32845) { - - function gamma(x) { return x < 0.0031308 ? 12.92 * x : 1.055 * Math.pow(x, 1.0 / 2.4) - 0.055; } - - for(var y=0; yma) { ma=ar; page=img; } - } - UTIF.decodeImage(buff, page, ifds); - var rgba = UTIF.toRGBA8(page), w=page.width, h=page.height; - - var cnv = document.createElement("canvas"); cnv.width=w; cnv.height=h; - var ctx = cnv.getContext("2d"); - var imgd = new ImageData(new Uint8ClampedArray(rgba.buffer),w,h); - ctx.putImageData(imgd,0,0); - return cnv.toDataURL(); - } - - - UTIF._binBE = - { - nextZero : function(data, o) { while(data[o]!=0) o++; return o; }, - readUshort : function(buff, p) { return (buff[p]<< 8) | buff[p+1]; }, - readShort : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+1]; a[1]=buff[p+0]; return UTIF._binBE. i16[0]; }, - readInt : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+3]; a[1]=buff[p+2]; a[2]=buff[p+1]; a[3]=buff[p+0]; return UTIF._binBE. i32[0]; }, - readUint : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+3]; a[1]=buff[p+2]; a[2]=buff[p+1]; a[3]=buff[p+0]; return UTIF._binBE.ui32[0]; }, - readASCII : function(buff, p, l) { var s = ""; for(var i=0; i> 8)&255; buff[p+1] = n&255; }, - writeInt : function(buff, p, n) { var a=UTIF._binBE.ui8; UTIF._binBE.i32[0]=n; buff[p+3]=a[0]; buff[p+2]=a[1]; buff[p+1]=a[2]; buff[p+0]=a[3]; }, - writeUint : function(buff, p, n) { buff[p] = (n>>24)&255; buff[p+1] = (n>>16)&255; buff[p+2] = (n>>8)&255; buff[p+3] = (n>>0)&255; }, - writeASCII : function(buff, p, s) { for(var i = 0; i < s.length; i++) buff[p+i] = s.charCodeAt(i); }, - writeDouble: function(buff, p, n) - { - UTIF._binBE.fl64[0] = n; - for (var i = 0; i < 8; i++) buff[p + i] = UTIF._binBE.ui8[7 - i]; - } - } - UTIF._binBE.ui8 = new Uint8Array (8); - UTIF._binBE.i16 = new Int16Array (UTIF._binBE.ui8.buffer); - UTIF._binBE.i32 = new Int32Array (UTIF._binBE.ui8.buffer); - UTIF._binBE.ui32 = new Uint32Array (UTIF._binBE.ui8.buffer); - UTIF._binBE.fl32 = new Float32Array(UTIF._binBE.ui8.buffer); - UTIF._binBE.fl64 = new Float64Array(UTIF._binBE.ui8.buffer); - - UTIF._binLE = - { - nextZero : UTIF._binBE.nextZero, - readUshort : function(buff, p) { return (buff[p+1]<< 8) | buff[p]; }, - readShort : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+0]; a[1]=buff[p+1]; return UTIF._binBE. i16[0]; }, - readInt : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+0]; a[1]=buff[p+1]; a[2]=buff[p+2]; a[3]=buff[p+3]; return UTIF._binBE. i32[0]; }, - readUint : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+0]; a[1]=buff[p+1]; a[2]=buff[p+2]; a[3]=buff[p+3]; return UTIF._binBE.ui32[0]; }, - readASCII : UTIF._binBE.readASCII, - readFloat : function(buff, p) { var a=UTIF._binBE.ui8; for(var i=0;i<4;i++) a[i]=buff[p+ i]; return UTIF._binBE.fl32[0]; }, - readDouble : function(buff, p) { var a=UTIF._binBE.ui8; for(var i=0;i<8;i++) a[i]=buff[p+ i]; return UTIF._binBE.fl64[0]; }, - - writeUshort: function(buff, p, n) { buff[p] = (n)&255; buff[p+1] = (n>>8)&255; }, - writeInt : function(buff, p, n) { var a=UTIF._binBE.ui8; UTIF._binBE.i32[0]=n; buff[p+0]=a[0]; buff[p+1]=a[1]; buff[p+2]=a[2]; buff[p+3]=a[3]; }, - writeUint : function(buff, p, n) { buff[p] = (n>>>0)&255; buff[p+1] = (n>>>8)&255; buff[p+2] = (n>>>16)&255; buff[p+3] = (n>>>24)&255; }, - writeASCII : UTIF._binBE.writeASCII - } - UTIF._copyTile = function(tb, tw, th, b, w, h, xoff, yoff) - { - //log("copyTile", tw, th, w, h, xoff, yoff); - var xlim = Math.min(tw, w-xoff); - var ylim = Math.min(th, h-yoff); - for(var y=0; y>--r&1; - X=g[X+y]}w[t]=X}}function P(l,A,g,K){if(l[A+3]!=255)return 0;if(g==0)return A;for(var t=0;t<2;t++){if(l[A+t]==0){l[A+t]=l.length; - l.push(0,0,K,255)}var F=P(l,l[A+t],g-1,K+1);if(F!=0)return F}return 0}function f(l){var A=l.b,g=l.a; - while(A<25&&l.e>>8;g=g<<8|K;A+=8}if(A<0)throw"e";l.b=A;l.a=g}function O(l,A){if(A.b>(A.b-=l)&65535>>16-l}function B(l,A){var g=l[0],K=0,t=255,F=0;if(A.b<16)f(A);var $=A.a>>A.b-8&255; - K=l[1][$];t=g[K+3];A.b-=g[K+2];while(t==255){F=A.a>>--A.b&1;K=g[K+F];t=g[K+3]}return t}function D(l,A){if(l<32768>>16-A)l+=-(1<>4,q&15]}}else if(M==65476){var m=_+X-2;while(_>>4];t[i[0]]=i.slice(1)}g=u();_+=2;break}else{_+=X-2}}var T=K>8?Uint16Array:Uint8Array,Y=new T(w*r*L),d={b:0,a:0,c:g==8,e:_,data:o,d:o.length}; - if(d.c)b(Y,r*L,d,$[0],w);else{var v=[],Q=0,C=0;for(var y=0;yQ)Q=s; - if(H>C)C=H;v.push(s*H)}if(Q!=1||C!=1){var E=[],c=0;for(var y=0;y>>1);else if(A==6)S=l[n]+(m-l[n-L]>>>1);else if(A==7)S=m+l[n]>>>1;else throw A; - l[q]+=S}}}}return x}(); - - - (function(){var G=0,F=1,i=2,b=3,J=4,N=5,E=6,s=7,c=8,T=9,a3=10,f=11,q=12,M=13,m=14,x=15,L=16,$=17,p=18; - function a5(t){var Z=UTIF._binBE.readUshort,u={b:Z(t,0),i:t[2],C:t[3],u:t[4],q:Z(t,5),k:Z(t,7),e:Z(t,9),l:Z(t,11),s:t[13],d:Z(t,14)}; - if(u.b!=18771||u.i>1||u.q<6||u.q%6||u.e<768||u.e%24||u.l!=768||u.k=u.l||u.s>16||u.s!=u.k/u.l||u.s!=Math.ceil(u.e/u.l)||u.d!=u.q/6||u.u!=12&&u.u!=14&&u.u!=16||u.C!=16&&u.C!=0){throw"Invalid data"}if(u.i==0){throw"Not implemented. We need this file!"}u.h=u.C==16; - u.m=(u.h?u.l*2/3:u.l>>>1)|0;u.A=u.m+2;u.f=64;u.g=(1<>>6);for(var e=0;e<3;e++){for(var Q=0; - Q<41;Q++){Z[e][Q]=[u,1]}}return Z}function a4(t){for(var Z=-1,u=0;!u;Z++){u=t[t.j]>>>7-t.a&1;t.a++;t.a&=7; - if(!t.a)t.j++}return Z}function K(t,Z){var u=0,e=8-t.a,Q=t.j,V=t.a;if(Z){if(Z>=e){do{u<<=e;Z-=e;u|=t[t.j]&(1<=8)}if(Z){u<<=Z;e-=Z;u|=t[t.j]>>>e&(1<n&&C>>2;if(o){w[X]=h;return}l=Z.t*Z.c[t.g+Y-H]+Z.c[t.g+g-Y]}else{h=Y>g&&Y>P||Y>>2:A+v>>>1; - l=Z.t*Z.c[t.g+Y-g]+Z.c[t.g+g-A]}R=y(l);var W=a4(u);if(W>>1):a>>>1; - O[R][0]+=y(a);if(O[R][1]==t.f){O[R][0]>>>=1;O[R][1]>>>=1}O[R][1]++;h=l<0?h-a:h+a;if(t.i){if(h<0)h+=Z.w; - else if(h>t.g)h-=Z.w}w[X]=h>=0?Math.min(h,t.g):0}function U(t,Z,u){var e=t[0].length;for(var Q=Z;Q<=u; - Q++){t[Q][0]=t[Q-1][1];t[Q][e-1]=t[Q-1][e-2]}}function B(t){U(t,s,q);U(t,i,J);U(t,x,$)}function _(t,Z,u,e,Q,V,O,o,X,k,j,I,a){var l=0,R=1,w=QJ; - while(R8){r(t,Z,u,e,Q,R,o[X]);r(t,Z,u,e,V,R,o[X]);R+=2}}B(e)}function a8(t,Z,u,e,Q,V){_(t,Z,u,e,i,s,Q,V,0,0,1,0,8); - _(t,Z,u,e,c,x,Q,V,1,0,1,0,8);_(t,Z,u,e,b,T,Q,V,2,1,0,3,0);_(t,Z,u,e,a3,L,Q,V,0,0,0,3,2);_(t,Z,u,e,J,f,Q,V,1,0,0,3,2); - _(t,Z,u,e,q,$,Q,V,2,1,0,3,0)}function a9(t,Z,u,e,Q,V){var O=V.length,o=t.l;if(Q+1==t.s)o=t.e-Q*t.l;var X=6*t.e*e+Q*t.l; - for(var k=0;k<6;k++){for(var j=0;j>>1)}else if(I==2){a=x+(k>>>1)}else{a=s+k}var l=t.h?(j*2/3&2147483646|j%3&1)+(j%3>>>1):j>>>1; - Z[X+j]=u[a][l+1]}X+=t.e}}UTIF._decompressRAF=function(t,Z){var u=a5(t),e=a7(t,u),Q=a2(u),V=new Int16Array(u.e*u.q); - if(Z==null){Z=u.h?[[1,1,0,1,1,2],[1,1,2,1,1,0],[2,0,1,0,2,1],[1,1,2,1,1,0],[1,1,0,1,1,2],[0,2,1,2,0,1]]:[[0,1],[3,2]]}var O=[[G,b],[F,J],[N,f],[E,q],[M,L],[m,$]],o=[]; - for(var X=0;X>>3]>>>7-(E&7)&1;t[1]++;return h}function ag(t,E){if(o==null){o={}; +for(var h=0;h>>1}return t}function A(t,E){return t>>E}function O(t,E,h,L,g,n){E[h]=A(A(11*t[g]-4*t[g+n]+t[g+n+n]+4,3)+t[L],1); +E[h+n]=A(A(5*t[g]+4*t[g+n]-t[g+n+n]+4,3)-t[L],1)}function J(t,E,h,L,g,n){var W=t[g-n]-t[g+n],j=t[g],$=t[L]; +E[h]=A(A(W+4,3)+j+$,1);E[h+n]=A(A(-W+4,3)+j-$,1)}function y(t,E,h,L,g,n){E[h]=A(A(5*t[g]+4*t[g-n]-t[g-n-n]+4,3)+t[L],1); +E[h+n]=A(A(11*t[g]-4*t[g-n]+t[g-n-n]+4,3)-t[L],1)}function q(t){t=t<0?0:t>4095?4095:t;t=k[t]>>>2;return t}function av(t,E,h,L,g,n){L=new Uint16Array(L.buffer); +var W=Date.now(),j=UTIF._binBE,$=E+h,r,u,X,I,ax,a3,R,ai,aa,ap,ah,ae,aD,al,i,aE,T,B;E+=4;var a5=n[0]==1; +while(E<$){var S=j.readShort(t,E),s=j.readUshort(t,E+2);E+=4;if(S==12)r=s;else if(S==20)u=s;else if(S==21)X=s; +else if(S==48)I=s;else if(S==53)ax=s;else if(S==35)a3=s;else if(S==62)R=s;else if(S==101)ai=s;else if(S==109)aa=s; +else if(S==84)ap=s;else if(S==106)ah=s;else if(S==107)ae=s;else if(S==108)aD=s;else if(S==102)al=s;else if(S==104)i=s; +else if(S==105)aE=s;else{var F=S<0?-S:S,D=F&65280,_=0;if(F&az){if(F&H){_=s&65535;_+=(F&255)<<16}else{_=s&65535}}if((F&V)==V){if(T==null){T=[]; +for(var M=0;M<4;M++)T[M]=new Int16Array((u>>>1)*(X>>>1));B=new Int16Array((u>>>1)*(X>>>1));C=new Int16Array(1024); +for(var M=0;M<1024;M++){var aG=M-512,p=Math.abs(aG),r=Math.floor(768*p*p*p/(255*255*255))+p;C[M]=Math.sign(aG)*r}k=new Uint16Array(4096); +var aA=(1<<16)-1;for(var M=0;M<4096;M++){var at=M,a1=aA*(Math.pow(113,at/4095)-1)/112;k[M]=Math.min(a1,aA)}}var w=T[R],v=m(u,1+P[I]),N=m(X,1+P[I]); +if(I==0){for(var b=0;b>>1)+G]=t[c]<<8|t[c+1]}}else{var a7=[t,E*8],a4=[],ay=0,aw=v*N,f=[0,0],Q=0,s=0; +while(ay0){a4[ay++]=s;Q--}}var l=(I-1)%3,aF=l!=1?v:0,a2=l!=0?N:0; +for(var b=0;b>>1)+aF,au=b*v;for(var G=0;G>>1,an=v*2,a9=N*2; +for(var b=0;b>14-K*2&3;var a6=aC[aB];if(a6!=0)for(var b=0;b>>1)*(u>>>1)+(G>>>1),z=a8[c],ao=ab[c]-2048,ak=aq[c]-2048,ad=as[c]-2048,aj=(ao<<1)+z,a0=(ak<<1)+z,aH=z+ad,am=z-ad; +if(a5){L[U]=q(aH);L[U+1]=q(a0);L[U+u]=q(aj);L[U+u+1]=q(am)}else{L[U]=q(aj);L[U+1]=q(aH);L[U+u]=q(am); +L[U+u+1]=q(a0)}}}E+=_*4}else if(F==16388){E+=_*4}else if(D==8192||D==8448||D==9216){}else throw F.toString(16)}}console.log(Date.now()-W)}return av}() + + + +UTIF.decode._decodeLogLuv32 = function(img, data, off, len, tgt, toff) { + var w = img.width, qw=w*4; + var io = 0, out = new Uint8Array(qw); + + while(io>> (tab[i] >>> 8); + for(var c=0; c>>4); tgt[toff+i+1]=(b0<<4)|(b2>>>4); tgt[toff+i+2]=(b2<<4)|(b1>>>4); } + return; + } + + var pix = new Uint16Array(16); + var row, col, val, max, min, imax, imin, sh, bit, i, dp; + + var data = new Uint8Array(raw_width+1); + for (row=0; row < height; row++) { + //fread (data, 1, raw_width, ifp); + for(var j=0; j>> 11); + imax = 0x0f & (val >>> 22); + imin = 0x0f & (val >>> 26); + for (sh=0; sh < 4 && 0x80 << sh <= max-min; sh++); + for (bit=30, i=0; i < 16; i++) + if (i == imax) pix[i] = max; + else if (i == imin) pix[i] = min; + else { + pix[i] = ((bin.readUshort(data, dp+(bit >> 3)) >>> (bit & 7) & 0x7f) << sh) + min; + if (pix[i] > 0x7ff) pix[i] = 0x7ff; + bit += 7; + } + for (i=0; i < 16; i++, col+=2) { + //RAW(row,col) = curve[pix[i] << 1] >> 2; + var clr = pix[i]<<1; //clr = 0xffff; + UTIF.decode._putsF(tgt, (row*raw_width+col)*tiff_bps, clr<<(16-tiff_bps)); + } + col -= col & 1 ? 1:31; + } + } +} + +UTIF.decode._decodeNikon = function(img,imgs, data, off, src_length, tgt, toff) +{ + var nikon_tree = [ + [ 0, 0,1,5,1,1,1,1,1,1,2,0,0,0,0,0,0, /* 12-bit lossy */ + 5,4,3,6,2,7,1,0,8,9,11,10,12 ], + [ 0, 0,1,5,1,1,1,1,1,1,2,0,0,0,0,0,0, /* 12-bit lossy after split */ + 0x39,0x5a,0x38,0x27,0x16,5,4,3,2,1,0,11,12,12 ], + [ 0, 0,1,4,2,3,1,2,0,0,0,0,0,0,0,0,0, /* 12-bit lossless */ + 5,4,6,3,7,2,8,1,9,0,10,11,12 ], + [ 0, 0,1,4,3,1,1,1,1,1,2,0,0,0,0,0,0, /* 14-bit lossy */ + 5,6,4,7,8,3,9,2,1,0,10,11,12,13,14 ], + [ 0, 0,1,5,1,1,1,1,1,1,1,2,0,0,0,0,0, /* 14-bit lossy after split */ + 8,0x5c,0x4b,0x3a,0x29,7,6,5,4,3,2,1,0,13,14 ], + [ 0, 0,1,4,2,2,3,1,2,0,0,0,0,0,0,0,0, /* 14-bit lossless */ + 7,6,8,5,9,4,10,3,11,12,2,0,1,13,14 ] ]; + + var raw_width = img["t256"][0], height=img["t257"][0], tiff_bps=img["t258"][0]; + + var tree = 0, split = 0; + var make_decoder = UTIF.decode._make_decoder; + var getbithuff = UTIF.decode._getbithuff; + + var mn = imgs[0].exifIFD.makerNote, md = mn["t150"]?mn["t150"]:mn["t140"], mdo=0; //console.log(mn,md); + //console.log(md[0].toString(16), md[1].toString(16), tiff_bps); + var ver0 = md[mdo++], ver1 = md[mdo++]; + if (ver0 == 0x49 || ver1 == 0x58) mdo+=2110; + if (ver0 == 0x46) tree = 2; + if (tiff_bps == 14) tree += 3; + + var vpred = [[0,0],[0,0]], bin=(img.isLE ? UTIF._binLE : UTIF._binBE); + for(var i=0; i<2; i++) for(var j=0; j<2; j++) { vpred[i][j] = bin.readShort(md,mdo); mdo+=2; } // not sure here ... [i][j] or [j][i] + //console.log(vpred); + + + var max = 1 << tiff_bps & 0x7fff, step=0; + var csize = bin.readShort(md,mdo); mdo+=2; + if (csize > 1) step = Math.floor(max / (csize-1)); + if (ver0 == 0x44 && ver1 == 0x20 && step > 0) split = bin.readShort(md,562); + + + var i; + var row, col; + var len, shl, diff; + var min_v = 0; + var hpred = [0,0]; + var huff = make_decoder(nikon_tree[tree]); + + //var g_input_offset=0, bitbuf=0, vbits=0, reset=0; + var prm = [off,0,0,0]; + //console.log(split); split = 170; + + for (min_v=row=0; row < height; row++) { + if (split && row == split) { + //free (huff); + huff = make_decoder (nikon_tree[tree+1]); + //max_v += (min_v = 16) << 1; + } + for (col=0; col < raw_width; col++) { + i = getbithuff(data,prm,huff[0],huff); + len = i & 15; + shl = i >>> 4; + diff = (((getbithuff(data,prm,len-shl,0) << 1) + 1) << shl) >>> 1; + if ((diff & (1 << (len-1))) == 0) + diff -= (1 << len) - (shl==0?1:0); + if (col < 2) hpred[col] = vpred[row & 1][col] += diff; + else hpred[col & 1] += diff; + + var clr = Math.min(Math.max(hpred[col & 1],0),(1<>>3); dt[o]|=val>>>16; dt[o+1]|=val>>>8; dt[o+2]|=val; } + + +UTIF.decode._getbithuff = function(data,prm,nbits, huff) { + var zero_after_ff = 0; + var get_byte = UTIF.decode._get_byte; + var c; + + var off=prm[0], bitbuf=prm[1], vbits=prm[2], reset=prm[3]; + + //if (nbits > 25) return 0; + //if (nbits < 0) return bitbuf = vbits = reset = 0; + if (nbits == 0 || vbits < 0) return 0; + while (!reset && vbits < nbits && (c = data[off++]) != -1 && + !(reset = zero_after_ff && c == 0xff && data[off++])) { + //console.log("byte read into c"); + bitbuf = (bitbuf << 8) + c; + vbits += 8; + } + c = (bitbuf << (32-vbits)) >>> (32-nbits); + if (huff) { + vbits -= huff[c+1] >>> 8; //console.log(c, huff[c]>>8); + c = huff[c+1]&255; + } else + vbits -= nbits; + if (vbits < 0) throw "e"; + + prm[0]=off; prm[1]=bitbuf; prm[2]=vbits; prm[3]=reset; + + return c; +} + +UTIF.decode._make_decoder = function(source) { + var max, len, h, i, j; + var huff = []; + + for (max=16; max!=0 && !source[max]; max--); + var si=17; + + huff[0] = max; + for (h=len=1; len <= max; len++) + for (i=0; i < source[len]; i++, ++si) + for (j=0; j < 1 << (max-len); j++) + if (h <= 1 << max) + huff[h++] = (len << 8) | source[si]; + return huff; +} + +UTIF.decode._decodeNewJPEG = function(img, data, off, len, tgt, toff) +{ + len = Math.min(len, data.length-off); + var tables = img["t347"], tlen = tables ? tables.length : 0, buff = new Uint8Array(tlen + len); + + if (tables) { + var SOI = 216, EOI = 217, boff = 0; + for (var i=0; i<(tlen-1); i++) + { + // Skip EOI marker from JPEGTables + if (tables[i]==255 && tables[i+1]==EOI) break; + buff[boff++] = tables[i]; + } + + // Skip SOI marker from data + var byte1 = data[off], byte2 = data[off + 1]; + if (byte1!=255 || byte2!=SOI) + { + buff[boff++] = byte1; + buff[boff++] = byte2; + } + for (var i=2; i>>8); } + else for(var i=0; i>>8); tgt[toff+(i<<1)+1] = (out[i]&255); } + } + else if(bps==14 || bps==12 || bps==10) { // 4 * 14 == 56 == 7 * 8 + var rst = 16-bps; + for(var i=0; i 1); + } + + if(!isTiled) + { + if(data[off]==255 && data[off+1]==SOI) return { jpegOffset: off }; + if(jpgIchgFmt!=null) + { + if(data[off+jifoff]==255 && data[off+jifoff+1]==SOI) joff = off+jifoff; + else log("JPEGInterchangeFormat does not point to SOI"); + + if(jpgIchgFmtLen==null) log("JPEGInterchangeFormatLength field is missing"); + else if(jifoff >= soff || (jifoff+jiflen) <= soff) log("JPEGInterchangeFormatLength field value is invalid"); + + if(joff != null) return { jpegOffset: joff }; + } + } + + if(ycbcrss!=null) { ssx = ycbcrss[0]; ssy = ycbcrss[1]; } + + if(jpgIchgFmt!=null) + if(jpgIchgFmtLen!=null) + if(jiflen >= 2 && (jifoff+jiflen) <= soff) + { + if(data[off+jifoff+jiflen-2]==255 && data[off+jifoff+jiflen-1]==SOI) tables = new Uint8Array(jiflen-2); + else tables = new Uint8Array(jiflen); + + for(i=0; i offset to first strip or tile"); + + if(tables == null) + { + var ooff = 0, out = []; + out[ooff++] = 255; out[ooff++] = SOI; + + var qtables = img["t519"]; + if(qtables==null) throw new Error("JPEGQTables tag is missing"); + for(i=0; i>> 8); out[ooff++] = nc & 255; + out[ooff++] = (i | (k << 4)); + for(j=0; j<16; j++) out[ooff++] = data[off+htables[i]+j]; + for(j=0; j>> 8) & 255; out[ooff++] = img.height & 255; + out[ooff++] = (img.width >>> 8) & 255; out[ooff++] = img.width & 255; + out[ooff++] = spp; + if(spp==1) { out[ooff++] = 1; out[ooff++] = 17; out[ooff++] = 0; } + else for(i=0; i<3; i++) + { + out[ooff++] = i + 1; + out[ooff++] = (i != 0) ? 17 : (((ssx & 15) << 4) | (ssy & 15)); + out[ooff++] = i; + } + + if(jpgresint!=null && jpgresint[0]!=0) + { + out[ooff++] = 255; out[ooff++] = DRI; out[ooff++] = 0; out[ooff++] = 4; + out[ooff++] = (jpgresint[0] >>> 8) & 255; + out[ooff++] = jpgresint[0] & 255; + } + + tables = new Uint8Array(out); + } + + var sofpos = -1; + i = 0; + while(i < (tables.length - 1)) { + if(tables[i]==255 && tables[i+1]==SOF0) { sofpos = i; break; } + i++; + } + + if(sofpos == -1) + { + var tmptab = new Uint8Array(tables.length + 10 + 3*spp); + tmptab.set(tables); + var tmpoff = tables.length; + sofpos = tables.length; + tables = tmptab; + + tables[tmpoff++] = 255; tables[tmpoff++] = SOF0; + tables[tmpoff++] = 0; tables[tmpoff++] = 8 + 3*spp; tables[tmpoff++] = 8; + tables[tmpoff++] = (img.height >>> 8) & 255; tables[tmpoff++] = img.height & 255; + tables[tmpoff++] = (img.width >>> 8) & 255; tables[tmpoff++] = img.width & 255; + tables[tmpoff++] = spp; + if(spp==1) { tables[tmpoff++] = 1; tables[tmpoff++] = 17; tables[tmpoff++] = 0; } + else for(i=0; i<3; i++) + { + tables[tmpoff++] = i + 1; + tables[tmpoff++] = (i != 0) ? 17 : (((ssx & 15) << 4) | (ssy & 15)); + tables[tmpoff++] = i; + } + } + + if(data[soff]==255 && data[soff+1]==SOS) + { + var soslen = (data[soff+2]<<8) | data[soff+3]; + sosMarker = new Uint8Array(soslen+2); + sosMarker[0] = data[soff]; sosMarker[1] = data[soff+1]; sosMarker[2] = data[soff+2]; sosMarker[3] = data[soff+3]; + for(i=0; i<(soslen-2); i++) sosMarker[i+4] = data[soff+i+4]; + } + else + { + sosMarker = new Uint8Array(2 + 6 + 2*spp); + var sosoff = 0; + sosMarker[sosoff++] = 255; sosMarker[sosoff++] = SOS; + sosMarker[sosoff++] = 0; sosMarker[sosoff++] = 6 + 2*spp; sosMarker[sosoff++] = spp; + if(spp==1) { sosMarker[sosoff++] = 1; sosMarker[sosoff++] = 0; } + else for(i=0; i<3; i++) + { + sosMarker[sosoff++] = i+1; sosMarker[sosoff++] = (i << 4) | i; + } + sosMarker[sosoff++] = 0; sosMarker[sosoff++] = 63; sosMarker[sosoff++] = 0; + } + + return { jpegOffset: off, tables: tables, sosMarker: sosMarker, sofPosition: sofpos }; +} + +UTIF.decode._decodeOldJPEG = function(img, data, off, len, tgt, toff) +{ + var i, dlen, tlen, buff, buffoff; + var jpegData = UTIF.decode._decodeOldJPEGInit(img, data, off, len); + + if(jpegData.jpegOffset!=null) + { + dlen = off+len-jpegData.jpegOffset; + buff = new Uint8Array(dlen); + for(i=0; i>> 8) & 255; buff[jpegData.sofPosition+6] = img.height & 255; + buff[jpegData.sofPosition+7] = (img.width >>> 8) & 255; buff[jpegData.sofPosition+8] = img.width & 255; + + if(data[off]!=255 || data[off+1]!=SOS) + { + buff.set(jpegData.sosMarker, buffoff); + buffoff += sosMarker.length; + } + for(i=0; i=0 && n<128) for(var i=0; i< n+1; i++) { ta[toff]=sa[off]; toff++; off++; } + if(n>=-127 && n<0) { for(var i=0; i<-n+1; i++) { ta[toff]=sa[off]; toff++; } off++; } + } + return toff; +} + +UTIF.decode._decodeThunder = function(data, off, len, tgt, toff) +{ + var d2 = [ 0, 1, 0, -1 ], d3 = [ 0, 1, 2, 3, 0, -3, -2, -1 ]; + var lim = off+len, qoff = toff*2, px = 0; + while(off>>6), n = (b&63); off++; + if(msk==3) { px=(n&15); tgt[qoff>>>1] |= (px<<(4*(1-qoff&1))); qoff++; } + if(msk==0) for(var i=0; i>>1] |= (px<<(4*(1-qoff&1))); qoff++; } + if(msk==2) for(var i=0; i<2; i++) { var d=(n>>>(3*(1-i)))&7; if(d!=4) { px+=d3[d]; tgt[qoff>>>1] |= (px<<(4*(1-qoff&1))); qoff++; } } + if(msk==1) for(var i=0; i<3; i++) { var d=(n>>>(2*(2-i)))&3; if(d!=2) { px+=d2[d]; tgt[qoff>>>1] |= (px<<(4*(1-qoff&1))); qoff++; } } + } +} + +UTIF.decode._dmap = { "1":0,"011":1,"000011":2,"0000011":3, "010":-1,"000010":-2,"0000010":-3 }; +UTIF.decode._lens = ( function() +{ + var addKeys = function(lens, arr, i0, inc) { for(var i=0; i>>3)>>3]>>>(7-(boff&7)))&1; + if(fo==2) bit = (data[boff>>>3]>>>( (boff&7)))&1; + boff++; wrd+=bit; + if(mode=="H") + { + if(U._lens[clr][wrd]!=null) + { + var dl=U._lens[clr][wrd]; wrd=""; len+=dl; + if(dl<64) { U._addNtimes(line,len,clr); a0+=len; clr=1-clr; len=0; toRead--; if(toRead==0) mode=""; } + } + } + else + { + if(wrd=="0001") { wrd=""; U._addNtimes(line,b2-a0,clr); a0=b2; } + if(wrd=="001" ) { wrd=""; mode="H"; toRead=2; } + if(U._dmap[wrd]!=null) { a1 = b1+U._dmap[wrd]; U._addNtimes(line, a1-a0, clr); a0=a1; wrd=""; clr=1-clr; } + } + if(line.length==w && mode=="") + { + U._writeBits(line, tgt, toff*8+y*bipl); + clr=0; y++; a0=0; + pline=U._makeDiff(line); line=[]; + } + //if(wrd.length>150) { log(wrd); break; throw "e"; } + } +} + +UTIF.decode._findDiff = function(line, x, clr) { for(var i=0; i=x && line[i+1]==clr) return line[i]; } + +UTIF.decode._makeDiff = function(line) +{ + var out = []; if(line[0]==1) out.push(0,1); + for(var i=1; i>>3)>>3]>>>(7-(boff&7)))&1; + if(fo==2) bit = (data[boff>>>3]>>>( (boff&7)))&1; + boff++; wrd+=bit; + + len = U._lens[clr][wrd]; + if(len!=null) { + U._addNtimes(line,len,clr); wrd=""; + if(len<64) clr = 1-clr; + if(line.length==w) { U._writeBits(line, tgt, toff*8+y*bipl); line=[]; y++; clr=0; if((boff&7)!=0) boff+=8-(boff&7); if(len>=64) boff+=8; } + } + } +} + +UTIF.decode._decodeG3 = function(data, off, slen, tgt, toff, w, fo, twoDim) +{ + var U = UTIF.decode, boff=off<<3, len=0, wrd=""; + var line=[], pline=[]; for(var i=0; i>>3)>>3]>>>(7-(boff&7)))&1; + if(fo==2) bit = (data[boff>>>3]>>>( (boff&7)))&1; + boff++; wrd+=bit; + + if(is1D) + { + if(U._lens[clr][wrd]!=null) + { + var dl=U._lens[clr][wrd]; wrd=""; len+=dl; + if(dl<64) { U._addNtimes(line,len,clr); clr=1-clr; len=0; } + } + } + else + { + if(mode=="H") + { + if(U._lens[clr][wrd]!=null) + { + var dl=U._lens[clr][wrd]; wrd=""; len+=dl; + if(dl<64) { U._addNtimes(line,len,clr); a0+=len; clr=1-clr; len=0; toRead--; if(toRead==0) mode=""; } + } + } + else + { + if(wrd=="0001") { wrd=""; U._addNtimes(line,b2-a0,clr); a0=b2; } + if(wrd=="001" ) { wrd=""; mode="H"; toRead=2; } + if(U._dmap[wrd]!=null) { a1 = b1+U._dmap[wrd]; U._addNtimes(line, a1-a0, clr); a0=a1; wrd=""; clr=1-clr; } + } + } + if(wrd.endsWith("000000000001")) // needed for some files + { + if(y>=0) U._writeBits(line, tgt, toff*8+y*bipl); + if(twoDim) { + if(fo==1) is1D = ((data[boff>>>3]>>>(7-(boff&7)))&1)==1; + if(fo==2) is1D = ((data[boff>>>3]>>>( (boff&7)))&1)==1; + boff++; + } + //log("EOL",y, "next 1D:", is1D); + wrd=""; clr=0; y++; a0=0; + pline=U._makeDiff(line); line=[]; + } + } + if(line.length==w) U._writeBits(line, tgt, toff*8+y*bipl); +} + +UTIF.decode._addNtimes = function(arr, n, val) { for(var i=0; i>>3] |= (bits[i]<<(7-((boff+i)&7))); +} + +UTIF.decode._decodeLZW=UTIF.decode._decodeLZW=function(){var e,U,Z,u,K=0,V=0,g=0,N=0,O=function(){var S=e>>>3,A=U[S]<<16|U[S+1]<<8|U[S+2],j=A>>>24-(e&7)-V&(1<>>----------------"); + for(var i=0; idata.buffer.byteLength) num=data.buffer.byteLength-no; arr = new Uint8Array(data.buffer, no, num); } + if(type== 2) { var o0 = (num<5 ? offset-4 : voff), c=data[o0], len=Math.max(0, Math.min(num-1,data.length-o0)); + if(c<128 || len==0) arr.push( bin.readASCII(data, o0, len) ); + else arr = new Uint8Array(data.buffer, o0, len); } + if(type== 3) { for(var j=0; j4) { bin.writeUint(data, offset, eoff); toff=eoff; } + + if (type== 1 || type==7) { for(var i=0; i4) { dlen += (dlen&1); eoff += dlen; } + offset += 4; + } + return [offset, eoff]; +} + +UTIF.toRGBA8 = function(out, scl) +{ + function gamma(x) { return x < 0.0031308 ? 12.92 * x : 1.055 * Math.pow(x, 1.0 / 2.4) - 0.055; } + + + var w = out.width, h = out.height, area = w*h, qarea = area*4, data = out.data; + var img = new Uint8Array(area*4); + //console.log(out); + // 0: WhiteIsZero, 1: BlackIsZero, 2: RGB, 3: Palette color, 4: Transparency mask, 5: CMYK + var intp = (out["t262"] ? out["t262"][0]: 2), bps = (out["t258"]?Math.min(32,out["t258"][0]):1); + if(out["t262"]==null && bps==1) intp=0; + + var smpls = out["t277"]?out["t277"][0] : (out["t258"]?out["t258"].length : [1,1,3,1,1,4,3][intp]); + var sfmt = out["t339"]?out["t339"][0] : null; if(intp==1 && bps==32 && sfmt!=3) throw "e"; // sample format + var bpl = Math.ceil(smpls*bps*w/8); + + //log("interpretation: ", intp, "smpls", smpls, "bps", bps, "sample format",sfmt, out); + + if(false) {} + else if(intp==0) + { + scl = 1/256; // "Photopeatest.tif" + for(var y=0; y>3)])>>(7- (i&7)))& 1; img[qi]=img[qi+1]=img[qi+2]=( 1-px)*255; img[qi+3]=255; } + if(bps== 4) for(var i=0; i>1)])>>(4-4*(i&1)))&15; img[qi]=img[qi+1]=img[qi+2]=(15-px)* 17; img[qi+3]=255; } + if(bps== 8) for(var i=0; i>3)])>>(7- (i&7)))&1; img[qi]=img[qi+1]=img[qi+2]=(px)*255; img[qi+3]=255; } + if(bps== 2) for(var i=0; i>2)])>>(6-2*(i&3)))&3; img[qi]=img[qi+1]=img[qi+2]=(px)* 85; img[qi+3]=255; } + if(bps== 8) for(var i=0; i>>2)+i, px=f32[o]; img[qi]=img[qi+1]=img[qi+2]= ~~(0.5+255*px); img[qi+3]=255; } + } + } + else if(intp==2) + { + if(bps== 8) + { + if(smpls==1) for(var i=0; i=4) for(var i=0; i1 && out["t338"] && out["t338"][0]!=0; + + for(var y=0; y>>3)]>>>(7- (x&7)))& 1; + else if(bps==2) mi=(data[dof+(x>>>2)]>>>(6-2*(x&3)))& 3; + else if(bps==4) mi=(data[dof+(x>>>1)]>>>(4-4*(x&1)))&15; + else if(bps==8) mi= data[dof+x*smpls]; + else throw bps; + img[qi]=(map[mi]>>8); img[qi+1]=(map[cn+mi]>>8); img[qi+2]=(map[cn+cn+mi]>>8); img[qi+3]=nexta ? data[dof+x*smpls+1] : 255; + } + } + else if(intp==5) + { + var gotAlpha = smpls>4 ? 1 : 0; + for(var i=0; i>>1); + var Y = data[si+(j&1)], Cb=data[si+2]-128, Cr=data[si+3]-128; + + var r = Y + ( (Cr >> 2) + (Cr >> 3) + (Cr >> 5) ) ; + var g = Y - ( (Cb >> 2) + (Cb >> 4) + (Cb >> 5)) - ( (Cr >> 1) + (Cr >> 3) + (Cr >> 4) + (Cr >> 5)) ; + var b = Y + ( Cb + (Cb >> 1) + (Cb >> 2) + (Cb >> 6)) ; + + img[qi ]=Math.max(0,Math.min(255,r)); + img[qi+1]=Math.max(0,Math.min(255,g)); + img[qi+2]=Math.max(0,Math.min(255,b)); + img[qi+3]=255; + } + } + } + else if(intp==32845) { + + for(var y=0; yma) { ma=ar; page=img; } + } + UTIF.decodeImage(buff, page, ifds); + var rgba = UTIF.toRGBA8(page), w=page.width, h=page.height; + + var cnv = document.createElement("canvas"); cnv.width=w; cnv.height=h; + var ctx = cnv.getContext("2d"); + var imgd = new ImageData(new Uint8ClampedArray(rgba.buffer),w,h); + ctx.putImageData(imgd,0,0); + return cnv.toDataURL(); +} + + +UTIF._binBE = +{ + nextZero : function(data, o) { while(data[o]!=0) o++; return o; }, + readUshort : function(buff, p) { return (buff[p]<< 8) | buff[p+1]; }, + readShort : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+1]; a[1]=buff[p+0]; return UTIF._binBE. i16[0]; }, + readInt : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+3]; a[1]=buff[p+2]; a[2]=buff[p+1]; a[3]=buff[p+0]; return UTIF._binBE. i32[0]; }, + readUint : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+3]; a[1]=buff[p+2]; a[2]=buff[p+1]; a[3]=buff[p+0]; return UTIF._binBE.ui32[0]; }, + readASCII : function(buff, p, l) { var s = ""; for(var i=0; i> 8)&255; buff[p+1] = n&255; }, + writeInt : function(buff, p, n) { var a=UTIF._binBE.ui8; UTIF._binBE.i32[0]=n; buff[p+3]=a[0]; buff[p+2]=a[1]; buff[p+1]=a[2]; buff[p+0]=a[3]; }, + writeUint : function(buff, p, n) { buff[p] = (n>>24)&255; buff[p+1] = (n>>16)&255; buff[p+2] = (n>>8)&255; buff[p+3] = (n>>0)&255; }, + writeASCII : function(buff, p, s) { for(var i = 0; i < s.length; i++) buff[p+i] = s.charCodeAt(i); }, + writeDouble: function(buff, p, n) + { + UTIF._binBE.fl64[0] = n; + for (var i = 0; i < 8; i++) buff[p + i] = UTIF._binBE.ui8[7 - i]; + } +} +UTIF._binBE.ui8 = new Uint8Array (8); +UTIF._binBE.i16 = new Int16Array (UTIF._binBE.ui8.buffer); +UTIF._binBE.i32 = new Int32Array (UTIF._binBE.ui8.buffer); +UTIF._binBE.ui32 = new Uint32Array (UTIF._binBE.ui8.buffer); +UTIF._binBE.fl32 = new Float32Array(UTIF._binBE.ui8.buffer); +UTIF._binBE.fl64 = new Float64Array(UTIF._binBE.ui8.buffer); + +UTIF._binLE = +{ + nextZero : UTIF._binBE.nextZero, + readUshort : function(buff, p) { return (buff[p+1]<< 8) | buff[p]; }, + readShort : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+0]; a[1]=buff[p+1]; return UTIF._binBE. i16[0]; }, + readInt : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+0]; a[1]=buff[p+1]; a[2]=buff[p+2]; a[3]=buff[p+3]; return UTIF._binBE. i32[0]; }, + readUint : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+0]; a[1]=buff[p+1]; a[2]=buff[p+2]; a[3]=buff[p+3]; return UTIF._binBE.ui32[0]; }, + readASCII : UTIF._binBE.readASCII, + readFloat : function(buff, p) { var a=UTIF._binBE.ui8; for(var i=0;i<4;i++) a[i]=buff[p+ i]; return UTIF._binBE.fl32[0]; }, + readDouble : function(buff, p) { var a=UTIF._binBE.ui8; for(var i=0;i<8;i++) a[i]=buff[p+ i]; return UTIF._binBE.fl64[0]; }, + + writeUshort: function(buff, p, n) { buff[p] = (n)&255; buff[p+1] = (n>>8)&255; }, + writeInt : function(buff, p, n) { var a=UTIF._binBE.ui8; UTIF._binBE.i32[0]=n; buff[p+0]=a[0]; buff[p+1]=a[1]; buff[p+2]=a[2]; buff[p+3]=a[3]; }, + writeUint : function(buff, p, n) { buff[p] = (n>>>0)&255; buff[p+1] = (n>>>8)&255; buff[p+2] = (n>>>16)&255; buff[p+3] = (n>>>24)&255; }, + writeASCII : UTIF._binBE.writeASCII +} +UTIF._copyTile = function(tb, tw, th, b, w, h, xoff, yoff) +{ + //log("copyTile", tw, th, w, h, xoff, yoff); + var xlim = Math.min(tw, w-xoff); + var ylim = Math.min(th, h-yoff); + for(var y=0; y>>2<<5);while(i==0){i=n(N,d,1);m=n(N,d+1,2);d+=3;if(m==0){if((d&7)!=0)d+=8-(d&7); + var D=(d>>>3)+4,q=N[D-4]|N[D-3]<<8;if(Z)W=H.H.W(W,w+q);W.set(new R(N.buffer,N.byteOffset+D,q),w);d=D+q<<3; + w+=q;continue}if(Z)W=H.H.W(W,w+(1<<17));if(m==1){v=b.J;C=b.h;X=(1<<9)-1;u=(1<<5)-1}if(m==2){J=A(N,d,5)+257; + h=A(N,d+5,5)+1;Q=A(N,d+10,4)+4;d+=14;var E=d,j=1;for(var c=0;c<38;c+=2){b.Q[c]=0;b.Q[c+1]=0}for(var c=0; + cj)j=K}d+=3*Q;M(b.Q,j);I(b.Q,j,b.u);v=b.w;C=b.d; + d=l(b.u,(1<>>4;if(p>>>8==0){W[w++]=p}else if(p==256){break}else{var z=w+p-254; + if(p>264){var _=b.q[p-257];z=w+(_>>>3)+A(N,d,_&7);d+=_&7}var $=C[e(N,d)&u];d+=$&15;var s=$>>>4,Y=b.c[s],a=(Y>>>4)+n(N,d,Y&15); + d+=Y&15;while(w>>4; + if(b<=15){A[I]=b;I++}else{var Z=0,m=0;if(b==16){m=3+l(V,n,2);n+=2;Z=A[I-1]}else if(b==17){m=3+l(V,n,3); + n+=3}else if(b==18){m=11+l(V,n,7);n+=7}var J=I+m;while(I>>1; + while(An)n=M;A++}while(A>1,I=N[l+1],e=M<<4|I,b=W-I,Z=N[l]<>>15-W;R[J]=e;Z++}}};H.H.l=function(N,W){var R=H.H.m.r,V=15-W;for(var n=0;n>>V}};H.H.M=function(N,W,R){R=R<<(W&7);var V=W>>>3;N[V]|=R;N[V+1]|=R>>>8}; + H.H.I=function(N,W,R){R=R<<(W&7);var V=W>>>3;N[V]|=R;N[V+1]|=R>>>8;N[V+2]|=R>>>16};H.H.e=function(N,W,R){return(N[W>>>3]|N[(W>>>3)+1]<<8)>>>(W&7)&(1<>>3]|N[(W>>>3)+1]<<8|N[(W>>>3)+2]<<16)>>>(W&7)&(1<>>3]|N[(W>>>3)+1]<<8|N[(W>>>3)+2]<<16)>>>(W&7)}; + H.H.i=function(N,W){return(N[W>>>3]|N[(W>>>3)+1]<<8|N[(W>>>3)+2]<<16|N[(W>>>3)+3]<<24)>>>(W&7)};H.H.m=function(){var N=Uint16Array,W=Uint32Array; + return{K:new N(16),j:new N(16),X:[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],S:[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,999,999,999],T:[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0],q:new N(32),p:[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,65535,65535],z:[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0],c:new W(32),J:new N(512),_:[],h:new N(32),$:[],w:new N(32768),C:[],v:[],d:new N(32768),D:[],u:new N(512),Q:[],r:new N(1<<15),s:new W(286),Y:new W(30),a:new W(19),t:new W(15e3),k:new N(1<<16),g:new N(1<<15)}}(); + (function(){var N=H.H.m,W=1<<15;for(var R=0;R>>1|(V&1431655765)<<1; + V=(V&3435973836)>>>2|(V&858993459)<<2;V=(V&4042322160)>>>4|(V&252645135)<<4;V=(V&4278255360)>>>8|(V&16711935)<<8; + N.r[R]=(V>>>16|V<<16)>>>17}function n(A,l,M){while(l--!=0)A.push(0,M)}for(var R=0;R<32;R++){N.q[R]=N.S[R]<<3|N.T[R]; + N.c[R]=N.p[R]<<4|N.z[R]}n(N._,144,8);n(N._,255-143,9);n(N._,279-255,7);n(N._,287-279,8);H.H.n(N._,9); + H.H.A(N._,9,N.J);H.H.l(N._,9);n(N.$,32,5);H.H.n(N.$,5);H.H.A(N.$,5,N.h);H.H.l(N.$,5);n(N.Q,19,0);n(N.C,286,0); + n(N.D,30,0);n(N.v,320,0)}());return H.H.N}(); + + + +UTIF.LosslessJpegDecode =function(){var b,O;function l(){return b[O++]}function m(){return b[O++]<<8|b[O++]}function a0(h){var V=l(),I=[0,0,0,255],f=[],G=8; +for(var w=0;w<16;w++)f[w]=l();for(var w=0;w<16;w++){for(var x=0;x>--s&1; +Y=I[Y+F]}E[w]=Y}}function z(h,V,I,f){if(h[V+3]!=255)return 0;if(I==0)return V;for(var w=0;w<2;w++){if(h[V+w]==0){h[V+w]=h.length; +h.push(0,0,f,255)}var x=z(h,h[V+w],I-1,f+1);if(x!=0)return x}return 0}function i(h){var V=h.b,I=h.f; +while(V<25&&h.a>(V.b-=h)&65535>>16-h}function g(h,V){var I=h[0],f=0,w=255,x=0;if(V.b<16)i(V);var T=V.f>>V.b-8&255; +f=h[1][T];w=I[f+3];V.b-=I[f+2];while(w==255){x=V.f>>--V.b&1;f=I[f+x];w=I[f+3]}return w}function P(h,V){if(h<32768>>16-V)h+=-(1<>4,J&15]}}else if(Y==65476){var a3=O+F-2;while(O>>4];x[v[0]]=v.slice(1)}I=l();O+=2;break}else if(Y==65501){w=m()}else{O+=F-2}}var a4=f>8?Uint16Array:Uint8Array,$=new a4(s*_*E),M={b:0,f:0,c:I==8,a:O,data:b,d:b.length,e:w}; +if(M.c)a1($,_*E,M,G[0],s);else{var c=[],p=0,D=0;for(var t=0;tp)p=S; +if(K>D)D=K;c.push(S*K)}if(p!=1||D!=1){if(E!=3||c[1]!=1||c[2]!=1)throw"e";if(p!=2||D!=1&&D!=2)throw"e"; +var u=[],Z=0;for(var t=0;t>>1)*B+(S>>>1))*Z,y=(K&1)*2+(S&1); +$[q]=n[k+y];$[q+1]=n[k+4];$[q+2]=n[k+5]}else for(var S=0;S<_;S++){var q=(K*_+S)*E,k=(K*B+(S>>>1))*Z,y=S&1; +$[q]=n[k+y];$[q+1]=n[k+2];$[q+2]=n[k+3]}}}else{X($,_*E,M,G,E,s);if(w==0)j($,I,_,s,0,E,E,f);else{var U=Math.floor(w/_); +for(var K=0;K>>1);else if(V==6)Q=h[J]+(r-h[J-G]>>>1);else if(V==7)Q=r+h[J]>>>1;else throw V; +h[a]+=Q}}}}return C}(); + + +(function(){var G=0,F=1,i=2,b=3,J=4,N=5,E=6,s=7,c=8,T=9,a3=10,f=11,q=12,M=13,m=14,x=15,L=16,$=17,p=18; +function a5(t){var Z=UTIF._binBE.readUshort,u={b:Z(t,0),i:t[2],C:t[3],u:t[4],q:Z(t,5),k:Z(t,7),e:Z(t,9),l:Z(t,11),s:t[13],d:Z(t,14)}; +if(u.b!=18771||u.i>1||u.q<6||u.q%6||u.e<768||u.e%24||u.l!=768||u.k=u.l||u.s>16||u.s!=u.k/u.l||u.s!=Math.ceil(u.e/u.l)||u.d!=u.q/6||u.u!=12&&u.u!=14&&u.u!=16||u.C!=16&&u.C!=0){throw"Invalid data"}if(u.i==0){throw"Not implemented. We need this file!"}u.h=u.C==16; +u.m=(u.h?u.l*2/3:u.l>>>1)|0;u.A=u.m+2;u.f=64;u.g=(1<>>6);for(var e=0;e<3;e++){for(var Q=0; +Q<41;Q++){Z[e][Q]=[u,1]}}return Z}function a4(t){for(var Z=-1,u=0;!u;Z++){u=t[t.j]>>>7-t.a&1;t.a++;t.a&=7; +if(!t.a)t.j++}return Z}function K(t,Z){var u=0,e=8-t.a,Q=t.j,V=t.a;if(Z){if(Z>=e){do{u<<=e;Z-=e;u|=t[t.j]&(1<=8)}if(Z){u<<=Z;e-=Z;u|=t[t.j]>>>e&(1<n&&C>>2;if(o){w[X]=h;return}l=Z.t*Z.c[t.g+Y-H]+Z.c[t.g+g-Y]}else{h=Y>g&&Y>P||Y>>2:A+v>>>1; +l=Z.t*Z.c[t.g+Y-g]+Z.c[t.g+g-A]}R=y(l);var W=a4(u);if(W>>1):a>>>1; +O[R][0]+=y(a);if(O[R][1]==t.f){O[R][0]>>>=1;O[R][1]>>>=1}O[R][1]++;h=l<0?h-a:h+a;if(t.i){if(h<0)h+=Z.w; +else if(h>t.g)h-=Z.w}w[X]=h>=0?Math.min(h,t.g):0}function U(t,Z,u){var e=t[0].length;for(var Q=Z;Q<=u; +Q++){t[Q][0]=t[Q-1][1];t[Q][e-1]=t[Q-1][e-2]}}function B(t){U(t,s,q);U(t,i,J);U(t,x,$)}function _(t,Z,u,e,Q,V,O,o,X,k,j,I,a){var l=0,R=1,w=QJ; +while(R8){r(t,Z,u,e,Q,R,o[X]);r(t,Z,u,e,V,R,o[X]);R+=2}}B(e)}function a8(t,Z,u,e,Q,V){_(t,Z,u,e,i,s,Q,V,0,0,1,0,8); +_(t,Z,u,e,c,x,Q,V,1,0,1,0,8);_(t,Z,u,e,b,T,Q,V,2,1,0,3,0);_(t,Z,u,e,a3,L,Q,V,0,0,0,3,2);_(t,Z,u,e,J,f,Q,V,1,0,0,3,2); +_(t,Z,u,e,q,$,Q,V,2,1,0,3,0)}function a9(t,Z,u,e,Q,V){var O=V.length,o=t.l;if(Q+1==t.s)o=t.e-Q*t.l;var X=6*t.e*e+Q*t.l; +for(var k=0;k<6;k++){for(var j=0;j>>1)}else if(I==2){a=x+(k>>>1)}else{a=s+k}var l=t.h?(j*2/3&2147483646|j%3&1)+(j%3>>>1):j>>>1; +Z[X+j]=u[a][l+1]}X+=t.e}}UTIF._decompressRAF=function(t,Z){var u=a5(t),e=a7(t,u),Q=a2(u),V=new Int16Array(u.e*u.q); +if(Z==null){Z=u.h?[[1,1,0,1,1,2],[1,1,2,1,1,0],[2,0,1,0,2,1],[1,1,2,1,1,0],[1,1,0,1,1,2],[0,2,1,2,0,1]]:[[0,1],[3,2]]}var O=[[G,b],[F,J],[N,f],[E,q],[M,L],[m,$]],o=[]; +for(var X=0;X} lights - The render object. + * @return {TiledLightsNode} The tiled lights node. + */ + createNode( lights = [] ) { + + return tiledLights().setLights( lights ); + + } + +} diff --git a/examples/jsm/lights/IESSpotLight.js b/examples/jsm/lights/IESSpotLight.js deleted file mode 100644 index 817f378e22a02a..00000000000000 --- a/examples/jsm/lights/IESSpotLight.js +++ /dev/null @@ -1,25 +0,0 @@ -import { SpotLight } from 'three'; - -class IESSpotLight extends SpotLight { - - constructor( color, intensity, distance, angle, penumbra, decay ) { - - super( color, intensity, distance, angle, penumbra, decay ); - - this.iesMap = null; - - } - - copy( source, recursive ) { - - super.copy( source, recursive ); - - this.iesMap = source.iesMap; - - return this; - - } - -} - -export default IESSpotLight; diff --git a/examples/jsm/lights/LightProbeGenerator.js b/examples/jsm/lights/LightProbeGenerator.js index 3fcc0a67e2c82d..7c2420c6a291c4 100644 --- a/examples/jsm/lights/LightProbeGenerator.js +++ b/examples/jsm/lights/LightProbeGenerator.js @@ -5,14 +5,31 @@ import { SphericalHarmonics3, Vector3, SRGBColorSpace, - NoColorSpace + NoColorSpace, + HalfFloatType, + DataUtils, + WebGLCoordinateSystem } from 'three'; +/** + * Utility class for creating instances of {@link LightProbe}. + * + * @hideconstructor + * @three_import import { LightProbeGenerator } from 'three/addons/lights/LightProbeGenerator.js'; + */ class LightProbeGenerator { - // https://www.ppsloan.org/publications/StupidSH36.pdf + /** + * Creates a light probe from the given (radiance) environment map. + * The method expects that the environment map is represented as a cube texture. + * + * @param {CubeTexture} cubeTexture - The environment map. + * @return {LightProbe} The created light probe. + */ static fromCubeTexture( cubeTexture ) { + // https://www.ppsloan.org/publications/StupidSH36.pdf + let totalWeight = 0; const coord = new Vector3(); @@ -96,7 +113,7 @@ class LightProbeGenerator { // evaluate SH basis functions in direction dir SphericalHarmonics3.getBasisAt( dir, shBasis ); - // accummuulate + // accumulate for ( let j = 0; j < 9; j ++ ) { shCoefficients[ j ].x += shBasis[ j ] * color.r * weight; @@ -124,7 +141,21 @@ class LightProbeGenerator { } - static fromCubeRenderTarget( renderer, cubeRenderTarget ) { + /** + * Creates a light probe from the given (radiance) environment map. + * The method expects that the environment map is represented as a cube render target. + * + * The cube render target must be in RGBA so `cubeRenderTarget.texture.format` must be + * set to {@link RGBAFormat}. + * + * @async + * @param {WebGPURenderer|WebGLRenderer} renderer - The renderer. + * @param {CubeRenderTarget|WebGLCubeRenderTarget} cubeRenderTarget - The environment map. + * @return {Promise} A Promise that resolves with the created light probe. + */ + static async fromCubeRenderTarget( renderer, cubeRenderTarget ) { + + const flip = renderer.coordinateSystem === WebGLCoordinateSystem ? - 1 : 1; // The renderTarget must be set to RGBA in order to make readRenderTargetPixels works let totalWeight = 0; @@ -140,18 +171,61 @@ class LightProbeGenerator { const sh = new SphericalHarmonics3(); const shCoefficients = sh.coefficients; + const dataType = cubeRenderTarget.texture.type; + const imageWidth = cubeRenderTarget.width; // assumed to be square + + let data; + + if ( renderer.isWebGLRenderer ) { + + if ( dataType === HalfFloatType ) { + + data = new Uint16Array( imageWidth * imageWidth * 4 ); + + } else { + + // assuming UnsignedByteType + + data = new Uint8Array( imageWidth * imageWidth * 4 ); + + } + + } + for ( let faceIndex = 0; faceIndex < 6; faceIndex ++ ) { - const imageWidth = cubeRenderTarget.width; // assumed to be square - const data = new Uint8Array( imageWidth * imageWidth * 4 ); - renderer.readRenderTargetPixels( cubeRenderTarget, 0, 0, imageWidth, imageWidth, data, faceIndex ); + if ( renderer.isWebGLRenderer ) { + + await renderer.readRenderTargetPixelsAsync( cubeRenderTarget, 0, 0, imageWidth, imageWidth, data, faceIndex ); + + } else { + + data = await renderer.readRenderTargetPixelsAsync( cubeRenderTarget, 0, 0, imageWidth, imageWidth, 0, faceIndex ); + + } const pixelSize = 2 / imageWidth; for ( let i = 0, il = data.length; i < il; i += 4 ) { // RGBA assumed + let r, g, b; + + if ( dataType === HalfFloatType ) { + + r = DataUtils.fromHalfFloat( data[ i ] ); + g = DataUtils.fromHalfFloat( data[ i + 1 ] ); + b = DataUtils.fromHalfFloat( data[ i + 2 ] ); + + } else { + + r = data[ i ] / 255; + g = data[ i + 1 ] / 255; + b = data[ i + 2 ] / 255; + + } + // pixel color - color.setRGB( data[ i ] / 255, data[ i + 1 ] / 255, data[ i + 2 ] / 255 ); + color.setRGB( r, g, b ); // convert to linear color space convertColorToLinear( color, cubeRenderTarget.texture.colorSpace ); @@ -160,15 +234,15 @@ class LightProbeGenerator { const pixelIndex = i / 4; - const col = - 1 + ( pixelIndex % imageWidth + 0.5 ) * pixelSize; + const col = ( 1 - ( pixelIndex % imageWidth + 0.5 ) * pixelSize ) * flip; const row = 1 - ( Math.floor( pixelIndex / imageWidth ) + 0.5 ) * pixelSize; switch ( faceIndex ) { - case 0: coord.set( 1, row, - col ); break; + case 0: coord.set( - 1 * flip, row, col * flip ); break; - case 1: coord.set( - 1, row, col ); break; + case 1: coord.set( 1 * flip, row, - col * flip ); break; case 2: coord.set( col, 1, - row ); break; @@ -194,7 +268,7 @@ class LightProbeGenerator { // evaluate SH basis functions in direction dir SphericalHarmonics3.getBasisAt( dir, shBasis ); - // accummuulate + // accumulate for ( let j = 0; j < 9; j ++ ) { shCoefficients[ j ].x += shBasis[ j ] * color.r * weight; diff --git a/examples/jsm/lights/RectAreaLightTexturesLib.js b/examples/jsm/lights/RectAreaLightTexturesLib.js new file mode 100644 index 00000000000000..c1cb661ce2af4f --- /dev/null +++ b/examples/jsm/lights/RectAreaLightTexturesLib.js @@ -0,0 +1,127 @@ +import { + ClampToEdgeWrapping, + DataTexture, + DataUtils, + FloatType, + HalfFloatType, + LinearFilter, + NearestFilter, + RGBAFormat, + UVMapping +} from 'three'; + +/** + * Texture library for {@link RectAreaLight}. This class holds the LTC BRDF data + * in data textures for further use in the renderer. + * + * Reference: Real-Time Polygonal-Light Shading with Linearly Transformed Cosines + * by Eric Heitz, Jonathan Dupuy, Stephen Hill and David Neubelt. [Code]{@link https://github.com/selfshadow/ltc_code/}. + * + * NOTE: This is a temporary location for the BRDF approximation texture data + * based off of Eric Heitz's work (see citation). BRDF data for + * `RectAreaLight` is currently approximated using a precomputed texture + * of roughly 80kb in size. The hope is to find a better way to include + * the large texture data before including the full RectAreaLight implementation + * in the main build files. + * + * @hideconstructor + * @three_import import { RectAreaLightTexturesLib } from 'three/addons/lights/RectAreaLightTexturesLib.js'; + */ +class RectAreaLightTexturesLib { + + /** + * Inits the texture library. + * + * @return {RectAreaLightTexturesLib} + */ + static init() { + + // source: https://github.com/selfshadow/ltc_code/tree/master/fit/results/ltc.js + + const LTC_MAT_1 = [ 1, 0, 0, 2e-05, 1, 0, 0, 0.000503905, 1, 0, 0, 0.00201562, 1, 0, 0, 0.00453516, 1, 0, 0, 0.00806253, 1, 0, 0, 0.0125978, 1, 0, 0, 0.018141, 1, 0, 0, 0.0246924, 1, 0, 0, 0.0322525, 1, 0, 0, 0.0408213, 1, 0, 0, 0.0503999, 1, 0, 0, 0.0609894, 1, 0, 0, 0.0725906, 1, 0, 0, 0.0852058, 1, 0, 0, 0.0988363, 1, 0, 0, 0.113484, 1, 0, 0, 0.129153, 1, 0, 0, 0.145839, 1, 0, 0, 0.163548, 1, 0, 0, 0.182266, 1, 0, 0, 0.201942, 1, 0, 0, 0.222314, 1, 0, 0, 0.241906, 1, 0, 0, 0.262314, 1, 0, 0, 0.285754, 1, 0, 0, 0.310159, 1, 0, 0, 0.335426, 1, 0, 0, 0.361341, 1, 0, 0, 0.387445, 1, 0, 0, 0.412784, 1, 0, 0, 0.438197, 1, 0, 0, 0.466966, 1, 0, 0, 0.49559, 1, 0, 0, 0.523448, 1, 0, 0, 0.549938, 1, 0, 0, 0.57979, 1, 0, 0, 0.608746, 1, 0, 0, 0.636185, 1, 0, 0, 0.664748, 1, 0, 0, 0.69313, 1, 0, 0, 0.71966, 1, 0, 0, 0.747662, 1, 0, 0, 0.774023, 1, 0, 0, 0.799775, 1, 0, 0, 0.825274, 1, 0, 0, 0.849156, 1, 0, 0, 0.873248, 1, 0, 0, 0.89532, 1, 0, 0, 0.917565, 1, 0, 0, 0.937863, 1, 0, 0, 0.958139, 1, 0, 0, 0.976563, 1, 0, 0, 0.994658, 1, 0, 0, 1.0112, 1, 0, 0, 1.02712, 1, 0, 0, 1.04189, 1, 0, 0, 1.05568, 1, 0, 0, 1.06877, 1, 0, 0, 1.08058, 1, 0, 0, 1.09194, 1, 0, 0, 1.10191, 1, 0, 0, 1.11161, 1, 0, 0, 1.1199, 1, 0, 0, 1.12813, 0.999547, - 4.48815e-07, 0.0224417, 1.99902e-05, 0.999495, - 1.13079e-05, 0.0224406, 0.000503651, 0.999496, - 4.52317e-05, 0.0224406, 0.00201461, 0.999496, - 0.000101772, 0.0224406, 0.00453287, 0.999495, - 0.000180928, 0.0224406, 0.00805845, 0.999497, - 0.000282702, 0.0224406, 0.0125914, 0.999496, - 0.000407096, 0.0224406, 0.0181319, 0.999498, - 0.000554114, 0.0224406, 0.02468, 0.999499, - 0.000723768, 0.0224406, 0.0322363, 0.999495, - 0.000916058, 0.0224405, 0.0408009, 0.999499, - 0.00113101, 0.0224408, 0.050375, 0.999494, - 0.00136863, 0.0224405, 0.0609586, 0.999489, - 0.00162896, 0.0224401, 0.0725537, 0.999489, - 0.00191201, 0.0224414, 0.0851619, 0.999498, - 0.00221787, 0.0224413, 0.0987867, 0.999492, - 0.00254642, 0.0224409, 0.113426, 0.999507, - 0.00289779, 0.0224417, 0.129088, 0.999494, - 0.0032716, 0.0224386, 0.145767, 0.999546, - 0.0036673, 0.0224424, 0.163472, 0.999543, - 0.00408166, 0.0224387, 0.182182, 0.999499, - 0.00450056, 0.0224338, 0.201843, 0.999503, - 0.00483661, 0.0224203, 0.222198, 0.999546, - 0.00452928, 0.022315, 0.241714, 0.999508, - 0.00587403, 0.0224329, 0.262184, 0.999509, - 0.00638806, 0.0224271, 0.285609, 0.999501, - 0.00691028, 0.0224166, 0.309998, 0.999539, - 0.00741979, 0.0223989, 0.335262, 0.999454, - 0.00786282, 0.0223675, 0.361154, 0.999529, - 0.00811928, 0.0222828, 0.387224, 0.999503, - 0.00799941, 0.0221063, 0.41252, 0.999561, - 0.00952753, 0.0223057, 0.438006, 0.999557, - 0.0099134, 0.0222065, 0.466735, 0.999541, - 0.0100935, 0.0220402, 0.495332, 0.999562, - 0.00996821, 0.0218067, 0.523197, 0.999556, - 0.0105031, 0.0217096, 0.550223, 0.999561, - 0.0114191, 0.0217215, 0.579498, 0.999588, - 0.0111818, 0.0213357, 0.608416, 0.999633, - 0.0107725, 0.0208689, 0.635965, 0.999527, - 0.0121671, 0.0210149, 0.664476, 0.999508, - 0.0116005, 0.020431, 0.692786, 0.999568, - 0.0115604, 0.0199791, 0.719709, 0.999671, - 0.0121117, 0.0197415, 0.74737, 0.999688, - 0.0110769, 0.0188846, 0.773692, 0.99962, - 0.0122368, 0.0188452, 0.799534, 0.999823, - 0.0110325, 0.0178001, 0.825046, 0.999599, - 0.0114923, 0.0174221, 0.849075, 0.999619, - 0.0105923, 0.0164345, 0.872999, 0.999613, - 0.0105988, 0.0158227, 0.895371, 0.99964, - 0.00979861, 0.0148131, 0.917364, 0.99977, - 0.00967238, 0.0140721, 0.938002, 0.999726, - 0.00869175, 0.0129543, 0.957917, 0.99973, - 0.00866872, 0.0122329, 0.976557, 0.999773, - 0.00731956, 0.0108958, 0.994459, 0.999811, - 0.00756027, 0.0102715, 1.01118, 0.999862, - 0.00583732, 0.00878781, 1.02701, 0.999835, - 0.00631438, 0.00827529, 1.04186, 0.999871, - 0.00450785, 0.00674583, 1.05569, 0.999867, - 0.00486079, 0.00621041, 1.06861, 0.999939, - 0.00322072, 0.00478301, 1.08064, 0.999918, - 0.00318199, 0.00406395, 1.09181, 1.00003, - 0.00193348, 0.00280682, 1.10207, 0.999928, - 0.00153729, 0.00198741, 1.11152, 0.999933, - 0.000623666, 0.000917714, 1.12009, 1, - 1.02387e-06, 9.07581e-07, 1.12813, 0.997866, - 8.96716e-07, 0.0448334, 1.99584e-05, 0.997987, - 2.25945e-05, 0.0448389, 0.000502891, 0.997987, - 9.03781e-05, 0.0448388, 0.00201156, 0.997985, - 0.000203351, 0.0448388, 0.00452602, 0.997986, - 0.000361514, 0.0448388, 0.00804629, 0.997987, - 0.00056487, 0.0448389, 0.0125724, 0.997988, - 0.000813423, 0.0448389, 0.0181045, 0.997984, - 0.00110718, 0.0448387, 0.0246427, 0.997985, - 0.00144616, 0.0448388, 0.0321875, 0.997987, - 0.00183038, 0.044839, 0.0407392, 0.997983, - 0.00225987, 0.0448387, 0.0502986, 0.997991, - 0.00273467, 0.0448389, 0.0608667, 0.997984, - 0.00325481, 0.0448384, 0.0724444, 0.998002, - 0.00382043, 0.044839, 0.0850348, 0.997997, - 0.00443145, 0.0448396, 0.0986372, 0.998007, - 0.00508796, 0.0448397, 0.113255, 0.998008, - 0.00578985, 0.04484, 0.128891, 0.998003, - 0.00653683, 0.0448384, 0.145548, 0.997983, - 0.00732713, 0.0448358, 0.163221, 0.997985, - 0.00815454, 0.0448358, 0.181899, 0.998005, - 0.00898985, 0.0448286, 0.201533, 0.998026, - 0.00964404, 0.0447934, 0.221821, 0.998055, - 0.00922677, 0.044611, 0.241282, 0.99804, - 0.0117361, 0.0448245, 0.261791, 0.998048, - 0.0127628, 0.0448159, 0.285181, 0.998088, - 0.0138055, 0.0447996, 0.30954, 0.998058, - 0.0148206, 0.0447669, 0.334751, 0.998099, - 0.0156998, 0.044697, 0.36061, 0.998116, - 0.0161976, 0.0445122, 0.386603, 0.998195, - 0.015945, 0.0441711, 0.411844, 0.998168, - 0.0183947, 0.0444255, 0.43773, 0.998184, - 0.0197913, 0.0443809, 0.466009, 0.998251, - 0.0201426, 0.0440689, 0.494574, 0.998305, - 0.0198847, 0.0435632, 0.522405, 0.998273, - 0.0210577, 0.043414, 0.549967, 0.998254, - 0.0227901, 0.0433943, 0.578655, 0.998349, - 0.0223108, 0.0426529, 0.60758, 0.99843, - 0.0223088, 0.042, 0.635524, 0.998373, - 0.0241141, 0.0418987, 0.663621, 0.998425, - 0.0231446, 0.0408118, 0.691906, 0.998504, - 0.0233684, 0.0400565, 0.719339, 0.998443, - 0.0241652, 0.0394634, 0.74643, 0.99848, - 0.0228715, 0.0380002, 0.773086, 0.998569, - 0.023519, 0.0372322, 0.798988, 0.998619, - 0.0223108, 0.0356468, 0.824249, 0.998594, - 0.0223105, 0.034523, 0.848808, 0.998622, - 0.0213426, 0.0328887, 0.87227, 0.998669, - 0.0207912, 0.0314374, 0.895157, 0.998705, - 0.0198416, 0.0296925, 0.916769, 0.998786, - 0.0189168, 0.0279634, 0.937773, 0.998888, - 0.0178811, 0.0261597, 0.957431, 0.99906, - 0.0166845, 0.0242159, 0.976495, 0.999038, - 0.0155464, 0.0222638, 0.994169, 0.999237, - 0.0141349, 0.0201967, 1.01112, 0.999378, - 0.0129324, 0.0181744, 1.02692, 0.999433, - 0.0113192, 0.0159898, 1.04174, 0.999439, - 0.0101244, 0.0140385, 1.05559, 0.999614, - 0.00837456, 0.0117826, 1.06852, 0.999722, - 0.00721769, 0.00983745, 1.08069, 0.999817, - 0.00554067, 0.00769002, 1.09176, 0.99983, - 0.00426961, 0.005782, 1.10211, 0.999964, - 0.00273904, 0.00374503, 1.11152, 1.00001, - 0.00136739, 0.00187176, 1.12031, 0.999946, 3.93227e-05, - 2.8919e-05, 1.12804, 0.995847, - 1.3435e-06, 0.0671785, 1.9916e-05, 0.995464, - 3.38387e-05, 0.0671527, 0.000501622, 0.99547, - 0.000135355, 0.0671531, 0.00200649, 0.995471, - 0.00030455, 0.0671532, 0.00451461, 0.99547, - 0.000541423, 0.0671531, 0.008026, 0.995471, - 0.00084598, 0.0671531, 0.0125407, 0.99547, - 0.00121823, 0.0671531, 0.0180589, 0.99547, - 0.00165817, 0.0671531, 0.0245806, 0.995463, - 0.00216583, 0.0671526, 0.0321062, 0.995468, - 0.00274127, 0.0671527, 0.0406366, 0.995474, - 0.00338447, 0.0671534, 0.0501717, 0.995473, - 0.00409554, 0.0671533, 0.0607131, 0.995478, - 0.00487451, 0.0671531, 0.0722618, 0.995476, - 0.00572148, 0.0671532, 0.0848191, 0.995477, - 0.00663658, 0.0671539, 0.0983882, 0.995498, - 0.00761986, 0.0671541, 0.112972, 0.995509, - 0.00867094, 0.0671542, 0.128568, 0.995509, - 0.00978951, 0.0671531, 0.145183, 0.995503, - 0.0109725, 0.0671491, 0.162808, 0.995501, - 0.012211, 0.0671465, 0.181441, 0.99553, - 0.0134565, 0.0671371, 0.201015, 0.99555, - 0.014391, 0.0670831, 0.221206, 0.99558, - 0.014351, 0.0668883, 0.240813, 0.995577, - 0.0173997, 0.0671055, 0.261257, 0.995602, - 0.0191111, 0.0671178, 0.284467, 0.995623, - 0.0206705, 0.0670946, 0.308765, 0.995658, - 0.022184, 0.0670472, 0.333905, 0.995705, - 0.0234832, 0.0669417, 0.359677, 0.995719, - 0.0241933, 0.0666714, 0.385554, 0.995786, - 0.0243539, 0.066266, 0.410951, 0.995887, - 0.0271866, 0.0664367, 0.437163, 0.995944, - 0.0296012, 0.0664931, 0.464842, 0.996004, - 0.0301045, 0.0660105, 0.49332, 0.996128, - 0.0298311, 0.0652694, 0.521131, 0.996253, - 0.0316426, 0.0650739, 0.549167, 0.996244, - 0.0339043, 0.0649433, 0.57737, 0.996309, - 0.033329, 0.0638926, 0.606073, 0.996417, - 0.0338935, 0.0630849, 0.634527, 0.996372, - 0.0353104, 0.0625083, 0.66256, 0.996542, - 0.0348942, 0.0611986, 0.690516, 0.996568, - 0.0351614, 0.060069, 0.718317, 0.996711, - 0.0354317, 0.0588522, 0.74528, 0.996671, - 0.0349513, 0.0571902, 0.772061, 0.996865, - 0.0345622, 0.0555321, 0.798089, 0.996802, - 0.0342566, 0.0537816, 0.823178, 0.996992, - 0.0330862, 0.0516095, 0.847949, 0.996944, - 0.0324666, 0.0495537, 0.871431, 0.997146, - 0.0309544, 0.0470302, 0.894357, 0.997189, - 0.0299372, 0.0446043, 0.916142, 0.997471, - 0.0281389, 0.0418812, 0.937193, 0.997515, - 0.0268702, 0.0391823, 0.957, 0.997812, - 0.0247166, 0.0361338, 0.975936, 0.998027, - 0.0233525, 0.0333945, 0.99391, 0.998233, - 0.0209839, 0.0301917, 1.01075, 0.998481, - 0.0194309, 0.027271, 1.02669, 0.998859, - 0.0169728, 0.0240162, 1.04173, 0.99894, - 0.0152322, 0.0210517, 1.05551, 0.999132, - 0.0127497, 0.0178632, 1.06856, 0.999369, - 0.0108282, 0.014787, 1.08054, 0.999549, - 0.00845886, 0.0116185, 1.09185, 0.999805, - 0.0063937, 0.00867209, 1.10207, 0.99985, - 0.00414582, 0.00566823, 1.1117, 0.999912, - 0.00207443, 0.00277562, 1.12022, 1.00001, 8.70226e-05, - 5.3766e-05, 1.12832, 0.991943, - 1.78672e-06, 0.0893382, 1.98384e-05, 0.991952, - 4.50183e-05, 0.089339, 0.000499849, 0.991956, - 0.000180074, 0.0893394, 0.0019994, 0.991955, - 0.000405167, 0.0893393, 0.00449867, 0.991953, - 0.000720298, 0.0893391, 0.00799764, 0.991955, - 0.00112548, 0.0893393, 0.0124964, 0.991957, - 0.0016207, 0.0893395, 0.0179951, 0.991958, - 0.00220601, 0.0893396, 0.0244939, 0.991947, - 0.00288137, 0.0893385, 0.0319929, 0.991962, - 0.00364693, 0.0893399, 0.0404933, 0.991965, - 0.00450264, 0.0893399, 0.049995, 0.99198, - 0.00544862, 0.0893411, 0.0604995, 0.99197, - 0.00648491, 0.0893397, 0.0720074, 0.991976, - 0.00761164, 0.089341, 0.0845207, 0.99198, - 0.00882891, 0.0893405, 0.0980413, 0.991982, - 0.0101367, 0.0893396, 0.112571, 0.992008, - 0.011535, 0.0893415, 0.128115, 0.992026, - 0.0130228, 0.0893414, 0.144672, 0.992064, - 0.0145966, 0.0893418, 0.162241, 0.992041, - 0.0162421, 0.0893359, 0.180801, 0.992086, - 0.0178888, 0.0893214, 0.200302, 0.992157, - 0.0190368, 0.0892401, 0.220332, 0.992181, - 0.0195584, 0.0890525, 0.240144, 0.992175, - 0.0227257, 0.0892153, 0.260728, 0.99221, - 0.0254195, 0.089304, 0.283473, 0.99222, - 0.0274883, 0.0892703, 0.307673, 0.992317, - 0.0294905, 0.0892027, 0.332729, 0.992374, - 0.0311861, 0.0890577, 0.358387, 0.992505, - 0.0320656, 0.0886994, 0.384102, 0.992568, - 0.0329715, 0.0883198, 0.409767, 0.992675, - 0.036006, 0.0883602, 0.436145, 0.992746, - 0.0392897, 0.0884591, 0.463217, 0.992873, - 0.0399337, 0.0878287, 0.491557, 0.992934, - 0.040231, 0.0870108, 0.519516, 0.993091, - 0.0422013, 0.0865857, 0.547741, 0.993259, - 0.0443503, 0.0861937, 0.575792, 0.993455, - 0.0446368, 0.0851187, 0.604233, 0.993497, - 0.0454299, 0.0840576, 0.632925, 0.993694, - 0.0463296, 0.0829671, 0.660985, 0.993718, - 0.0470619, 0.0817185, 0.688714, 0.993973, - 0.0468838, 0.0800294, 0.716743, 0.994207, - 0.046705, 0.0781286, 0.74377, 0.994168, - 0.0469698, 0.0763337, 0.77042, 0.9945, - 0.0456816, 0.0738184, 0.796659, 0.994356, - 0.0455518, 0.0715545, 0.821868, 0.994747, - 0.0439488, 0.0686085, 0.846572, 0.994937, - 0.0430056, 0.065869, 0.870435, 0.995142, - 0.0413414, 0.0626446, 0.893272, 0.995451, - 0.0396521, 0.05929, 0.915376, 0.995445, - 0.0378453, 0.0558503, 0.936196, 0.995967, - 0.0355219, 0.0520949, 0.956376, 0.996094, - 0.0335146, 0.048377, 0.975327, 0.996622, - 0.030682, 0.0442575, 0.993471, 0.996938, - 0.0285504, 0.0404693, 1.01052, 0.997383, - 0.0253399, 0.0360903, 1.02637, 0.997714, - 0.0231651, 0.0322176, 1.04139, 0.998249, - 0.0198138, 0.0278433, 1.05542, 0.998596, - 0.0174337, 0.0238759, 1.06846, 0.998946, - 0.0141349, 0.0195944, 1.08056, 0.99928, - 0.0115603, 0.0156279, 1.09181, 0.999507, - 0.00839065, 0.0114607, 1.10213, 0.999697, - 0.005666, 0.00763325, 1.11169, 0.999869, - 0.00269902, 0.00364946, 1.12042, 1.00001, 6.23836e-05, - 3.19288e-05, 1.12832, 0.987221, - 2.22675e-06, 0.111332, 1.97456e-05, 0.98739, - 5.61116e-05, 0.111351, 0.000497563, 0.987448, - 0.000224453, 0.111357, 0.00199031, 0.987441, - 0.000505019, 0.111357, 0.0044782, 0.987442, - 0.000897816, 0.111357, 0.00796129, 0.987442, - 0.00140284, 0.111357, 0.0124396, 0.987444, - 0.00202012, 0.111357, 0.0179132, 0.987442, - 0.00274964, 0.111357, 0.0243824, 0.987446, - 0.00359147, 0.111357, 0.0318474, 0.987435, - 0.00454562, 0.111356, 0.0403086, 0.987461, - 0.00561225, 0.111358, 0.0497678, 0.987458, - 0.00679125, 0.111358, 0.0602239, 0.987443, - 0.0080828, 0.111356, 0.0716792, 0.987476, - 0.0094872, 0.111358, 0.0841364, 0.98749, - 0.0110044, 0.111361, 0.097597, 0.987508, - 0.0126344, 0.111362, 0.112062, 0.987494, - 0.0143767, 0.111357, 0.127533, 0.987526, - 0.0162307, 0.111359, 0.144015, 0.987558, - 0.0181912, 0.111361, 0.161502, 0.987602, - 0.0202393, 0.111355, 0.179979, 0.987692, - 0.022273, 0.111346, 0.199386, 0.987702, - 0.0235306, 0.111215, 0.219183, 0.987789, - 0.0247628, 0.111061, 0.239202, 0.987776, - 0.0280668, 0.111171, 0.259957, 0.987856, - 0.0316751, 0.111327, 0.282198, 0.987912, - 0.0342468, 0.111282, 0.306294, 0.988, - 0.0367205, 0.111198, 0.331219, 0.988055, - 0.0387766, 0.110994, 0.356708, 0.988241, - 0.0397722, 0.110547, 0.382234, 0.988399, - 0.0416076, 0.110198, 0.408227, 0.988539, - 0.0448192, 0.110137, 0.434662, 0.988661, - 0.0483793, 0.110143, 0.461442, 0.988967, - 0.0495895, 0.109453, 0.489318, 0.989073, - 0.0506797, 0.108628, 0.517516, 0.989274, - 0.0526953, 0.108003, 0.545844, 0.989528, - 0.054578, 0.107255, 0.573823, 0.989709, - 0.0561503, 0.106294, 0.601944, 0.989991, - 0.056866, 0.104896, 0.630855, 0.990392, - 0.0572914, 0.103336, 0.658925, 0.990374, - 0.0586224, 0.10189, 0.686661, 0.990747, - 0.0584764, 0.099783, 0.714548, 0.991041, - 0.0582662, 0.0974309, 0.74186, 0.991236, - 0.0584118, 0.0951678, 0.768422, 0.991585, - 0.0573055, 0.0921581, 0.794817, 0.991984, - 0.0564241, 0.0891167, 0.820336, 0.9921, - 0.0553608, 0.085805, 0.84493, 0.992749, - 0.0533816, 0.0820354, 0.868961, 0.99288, - 0.0518661, 0.0782181, 0.891931, 0.993511, - 0.0492492, 0.0738935, 0.914186, 0.993617, - 0.0471956, 0.0696402, 0.93532, 0.99411, - 0.044216, 0.0649659, 0.95543, 0.994595, - 0.0416654, 0.0603177, 0.974685, 0.994976, - 0.0384314, 0.0553493, 0.992807, 0.995579, - 0.0353491, 0.0503942, 1.00996, 0.996069, - 0.0319787, 0.0452123, 1.02606, 0.996718, - 0.028472, 0.0400112, 1.04114, 0.997173, - 0.0250789, 0.0349456, 1.05517, 0.997818, - 0.0213326, 0.029653, 1.0683, 0.998318, - 0.0178509, 0.024549, 1.0805, 0.998853, - 0.0141118, 0.0194197, 1.09177, 0.999218, - 0.0105914, 0.0143869, 1.1022, 0.999594, - 0.00693474, 0.00943517, 1.11175, 0.99975, - 0.00340478, 0.00464051, 1.12056, 1.00001, 0.000109172, - 0.000112821, 1.12853, 0.983383, - 2.66524e-06, 0.133358, 1.96534e-05, 0.981942, - 6.71009e-05, 0.133162, 0.000494804, 0.981946, - 0.000268405, 0.133163, 0.00197923, 0.981944, - 0.000603912, 0.133163, 0.00445326, 0.981941, - 0.00107362, 0.133162, 0.00791693, 0.981946, - 0.00167755, 0.133163, 0.0123703, 0.981944, - 0.00241569, 0.133162, 0.0178135, 0.981945, - 0.00328807, 0.133163, 0.0242466, 0.981945, - 0.00429472, 0.133162, 0.03167, 0.981955, - 0.00543573, 0.133164, 0.0400846, 0.981951, - 0.00671105, 0.133163, 0.0494901, 0.981968, - 0.00812092, 0.133165, 0.0598886, 0.981979, - 0.00966541, 0.133166, 0.0712811, 0.981996, - 0.0113446, 0.133168, 0.083669, 0.982014, - 0.0131585, 0.133169, 0.0970533, 0.982011, - 0.0151073, 0.133167, 0.111438, 0.982062, - 0.0171906, 0.133172, 0.126826, 0.9821, - 0.0194067, 0.133175, 0.143215, 0.982149, - 0.0217502, 0.133176, 0.160609, 0.982163, - 0.0241945, 0.133173, 0.178981, 0.982247, - 0.0265907, 0.133148, 0.198249, 0.982291, - 0.027916, 0.132974, 0.217795, 0.982396, - 0.0299663, 0.132868, 0.238042, 0.982456, - 0.0334544, 0.132934, 0.258901, 0.982499, - 0.0378636, 0.133137, 0.280639, 0.982617, - 0.0409274, 0.133085, 0.304604, 0.98274, - 0.0438523, 0.132985, 0.329376, 0.982944, - 0.0462288, 0.132728, 0.354697, 0.98308, - 0.0475995, 0.132228, 0.380102, 0.983391, - 0.0501901, 0.131924, 0.406256, 0.983514, - 0.0535899, 0.131737, 0.432735, 0.98373, - 0.0571858, 0.131567, 0.459359, 0.984056, - 0.0592353, 0.130932, 0.486637, 0.984234, - 0.0610488, 0.130092, 0.51509, 0.984748, - 0.0630758, 0.12923, 0.543461, 0.985073, - 0.0647398, 0.128174, 0.571376, 0.985195, - 0.0671941, 0.127133, 0.599414, 0.985734, - 0.0681345, 0.125576, 0.628134, 0.986241, - 0.0686089, 0.123639, 0.656399, 0.986356, - 0.0698511, 0.121834, 0.684258, 0.986894, - 0.0700931, 0.119454, 0.711818, 0.987382, - 0.0698321, 0.116718, 0.739511, 0.988109, - 0.0693975, 0.113699, 0.766267, 0.988363, - 0.0689584, 0.110454, 0.792456, 0.989112, - 0.0672353, 0.106602, 0.81813, 0.989241, - 0.0662034, 0.10267, 0.842889, 0.990333, - 0.0638938, 0.0981381, 0.867204, 0.990591, - 0.0618534, 0.0935388, 0.89038, 0.991106, - 0.0593117, 0.088553, 0.912576, 0.991919, - 0.0562676, 0.0832187, 0.934118, 0.992111, - 0.0534085, 0.0778302, 0.954254, 0.992997, - 0.0495459, 0.0720453, 0.973722, 0.993317, - 0.0463707, 0.0663458, 0.991949, 0.994133, - 0.0421245, 0.0601883, 1.00936, 0.994705, - 0.0384977, 0.0542501, 1.02559, 0.995495, - 0.0340956, 0.0479862, 1.04083, 0.996206, - 0.030105, 0.041887, 1.05497, 0.996971, - 0.0256095, 0.0355355, 1.06824, 0.997796, - 0.0213932, 0.0293655, 1.08056, 0.998272, - 0.0169612, 0.0232926, 1.09182, 0.998857, - 0.0126756, 0.0172786, 1.10219, 0.99939, - 0.00832486, 0.0113156, 1.11192, 0.999752, - 0.00410826, 0.00557892, 1.12075, 1, 0.000150957, - 0.000119101, 1.12885, 0.975169, - 3.09397e-06, 0.154669, 1.95073e-05, 0.975439, - 7.79608e-05, 0.154712, 0.000491534, 0.975464, - 0.000311847, 0.154716, 0.00196617, 0.975464, - 0.000701656, 0.154716, 0.00442387, 0.975462, - 0.0012474, 0.154715, 0.0078647, 0.975461, - 0.00194906, 0.154715, 0.0122886, 0.975464, - 0.00280667, 0.154715, 0.0176959, 0.975468, - 0.00382025, 0.154716, 0.0240867, 0.975471, - 0.00498985, 0.154716, 0.0314612, 0.975472, - 0.00631541, 0.154717, 0.0398199, 0.975486, - 0.00779719, 0.154718, 0.0491639, 0.975489, - 0.00943505, 0.154718, 0.0594932, 0.975509, - 0.0112295, 0.154721, 0.0708113, 0.97554, - 0.0131802, 0.154724, 0.0831176, 0.975557, - 0.0152876, 0.154726, 0.096415, 0.975585, - 0.0175512, 0.154728, 0.110705, 0.975605, - 0.0199713, 0.154729, 0.125992, 0.975645, - 0.0225447, 0.154729, 0.142272, 0.975711, - 0.0252649, 0.154735, 0.159549, 0.975788, - 0.0280986, 0.154736, 0.177805, 0.975872, - 0.0308232, 0.154704, 0.196911, 0.975968, - 0.0324841, 0.154525, 0.216324, 0.976063, - 0.0351281, 0.154432, 0.236628, 0.976157, - 0.0388618, 0.15446, 0.257539, 0.976204, - 0.0437704, 0.154665, 0.278975, 0.976358, - 0.047514, 0.154652, 0.302606, 0.976571, - 0.0508638, 0.154535, 0.327204, 0.976725, - 0.0534995, 0.154221, 0.352276, 0.977013, - 0.0555547, 0.153737, 0.377696, 0.977294, - 0.0586728, 0.153403, 0.403855, 0.977602, - 0.0622715, 0.15312, 0.430333, 0.977932, - 0.0658166, 0.152755, 0.456855, 0.978241, - 0.0689877, 0.152233, 0.483668, 0.978602, - 0.0712805, 0.15132, 0.512097, 0.979234, - 0.0732775, 0.150235, 0.540455, 0.97977, - 0.075163, 0.148978, 0.568486, 0.979995, - 0.0778026, 0.147755, 0.596524, 0.98078, - 0.0791854, 0.146019, 0.624825, 0.981628, - 0.0799666, 0.143906, 0.653403, 0.982067, - 0.0808532, 0.141561, 0.681445, 0.98271, - 0.0816024, 0.139025, 0.708918, 0.983734, - 0.0812511, 0.135764, 0.736594, 0.98431, - 0.0806201, 0.132152, 0.763576, 0.985071, - 0.0801605, 0.12846, 0.789797, 0.98618, - 0.0784208, 0.124084, 0.815804, 0.986886, - 0.0766643, 0.1193, 0.840869, 0.987485, - 0.0747744, 0.114236, 0.864952, 0.988431, - 0.0716701, 0.108654, 0.888431, 0.988886, - 0.0691609, 0.102994, 0.910963, 0.990024, - 0.0654048, 0.0967278, 0.932629, 0.990401, - 0.0619765, 0.090384, 0.95313, 0.991093, - 0.0579296, 0.0837885, 0.972587, 0.992018, - 0.0536576, 0.0770171, 0.991184, 0.992536, - 0.0493719, 0.0701486, 1.00863, 0.993421, - 0.0444813, 0.062953, 1.02494, 0.993928, - 0.040008, 0.0560455, 1.04017, 0.994994, - 0.0347982, 0.04856, 1.05463, 0.995866, - 0.0301017, 0.0416152, 1.06807, 0.996916, - 0.0248225, 0.0342597, 1.08039, 0.997766, - 0.0199229, 0.0271668, 1.09177, 0.998479, - 0.0147422, 0.0201387, 1.10235, 0.99921, - 0.00980173, 0.0131944, 1.11206, 0.999652, - 0.0047426, 0.00640712, 1.12104, 0.999998, 8.91673e-05, - 0.00010379, 1.12906, 0.967868, - 3.51885e-06, 0.175947, 1.93569e-05, 0.968001, - 8.86733e-05, 0.175972, 0.000487782, 0.96801, - 0.000354697, 0.175973, 0.00195115, 0.968012, - 0.000798063, 0.175974, 0.00439006, 0.968011, - 0.00141879, 0.175973, 0.00780461, 0.968011, - 0.00221686, 0.175973, 0.0121948, 0.968016, - 0.00319231, 0.175974, 0.0175607, 0.968019, - 0.00434515, 0.175974, 0.0239027, 0.968018, - 0.00567538, 0.175974, 0.0312208, 0.968033, - 0.00718308, 0.175977, 0.0395158, 0.968049, - 0.00886836, 0.175979, 0.0487885, 0.968047, - 0.0107312, 0.175978, 0.0590394, 0.968072, - 0.0127719, 0.175981, 0.0702705, 0.968108, - 0.0149905, 0.175986, 0.0824836, 0.968112, - 0.0173866, 0.175985, 0.0956783, 0.968173, - 0.0199611, 0.175993, 0.109862, 0.96827, - 0.0227128, 0.176008, 0.125033, 0.968292, - 0.025639, 0.17601, 0.141193, 0.968339, - 0.0287299, 0.176007, 0.158336, 0.968389, - 0.0319399, 0.176001, 0.176441, 0.968501, - 0.034941, 0.175962, 0.195359, 0.968646, - 0.0370812, 0.175793, 0.214686, 0.968789, - 0.0402329, 0.175708, 0.234973, 0.96886, - 0.0442601, 0.1757, 0.255871, 0.969013, - 0.049398, 0.175876, 0.277238, 0.969242, - 0.0539932, 0.17594, 0.300326, 0.969419, - 0.0577299, 0.175781, 0.324702, 0.969763, - 0.0605643, 0.175432, 0.349527, 0.970093, - 0.0634488, 0.174992, 0.374976, 0.970361, - 0.0670589, 0.174611, 0.401097, 0.970825, - 0.0708246, 0.174226, 0.427496, 0.971214, - 0.0742871, 0.173684, 0.453858, 0.971622, - 0.0782608, 0.173186, 0.480637, 0.972175, - 0.0813151, 0.172288, 0.508655, 0.972944, - 0.0832678, 0.170979, 0.536973, 0.973595, - 0.0855964, 0.169573, 0.565138, 0.974345, - 0.0882163, 0.168152, 0.593222, 0.975233, - 0.0901671, 0.166314, 0.621201, 0.976239, - 0.0912111, 0.163931, 0.649919, 0.977289, - 0.0916959, 0.161106, 0.678011, 0.978076, - 0.0927061, 0.158272, 0.705717, 0.979533, - 0.0925562, 0.15475, 0.733228, 0.980335, - 0.0918159, 0.150638, 0.760454, 0.981808, - 0.0908508, 0.146201, 0.786918, 0.983061, - 0.0896172, 0.141386, 0.812953, 0.984148, - 0.0871588, 0.135837, 0.838281, 0.985047, - 0.0850624, 0.130135, 0.862594, 0.986219, - 0.0818541, 0.123882, 0.88633, 0.987043, - 0.0784523, 0.117126, 0.908952, 0.988107, - 0.0749601, 0.110341, 0.930744, 0.988955, - 0.0703548, 0.102885, 0.951728, 0.989426, - 0.0662798, 0.0954167, 0.971166, 0.990421, - 0.0610834, 0.0876331, 0.989984, 0.991032, - 0.0562936, 0.0797785, 1.00765, 0.992041, - 0.0508154, 0.0718166, 1.02434, 0.992794, - 0.0454045, 0.0637125, 1.03976, 0.993691, - 0.0398194, 0.0555338, 1.05418, 0.994778, - 0.0341482, 0.0473388, 1.06772, 0.995915, - 0.028428, 0.0391016, 1.08028, 0.997109, - 0.022642, 0.0309953, 1.09185, 0.998095, - 0.0168738, 0.0230288, 1.10247, 0.998985, - 0.0111274, 0.0150722, 1.11229, 0.999581, - 0.00543881, 0.00740605, 1.12131, 1.00003, 0.000162239, - 0.000105549, 1.12946, 0.959505, - 3.93734e-06, 0.196876, 1.91893e-05, 0.959599, - 9.92157e-05, 0.196895, 0.000483544, 0.959641, - 0.000396868, 0.196903, 0.0019342, 0.959599, - 0.000892948, 0.196895, 0.00435193, 0.959603, - 0.00158747, 0.196896, 0.0077368, 0.959604, - 0.00248042, 0.196896, 0.0120888, 0.959605, - 0.00357184, 0.196896, 0.0174082, 0.959605, - 0.00486169, 0.196896, 0.0236949, 0.959613, - 0.00635008, 0.196897, 0.0309497, 0.959619, - 0.00803696, 0.196898, 0.0391725, 0.959636, - 0.00992255, 0.196901, 0.0483649, 0.959634, - 0.0120067, 0.1969, 0.0585266, 0.959675, - 0.0142898, 0.196906, 0.0696609, 0.959712, - 0.0167717, 0.196911, 0.0817678, 0.959752, - 0.0194524, 0.196918, 0.0948494, 0.959807, - 0.0223321, 0.196925, 0.10891, 0.959828, - 0.0254091, 0.196924, 0.123947, 0.959906, - 0.0286815, 0.196934, 0.139968, 0.960005, - 0.0321371, 0.196944, 0.156968, 0.960071, - 0.0357114, 0.196936, 0.17491, 0.960237, - 0.0389064, 0.196882, 0.193597, 0.960367, - 0.041623, 0.196731, 0.21285, 0.960562, - 0.0452655, 0.196654, 0.233075, 0.960735, - 0.0496207, 0.196643, 0.253941, 0.960913, - 0.0549379, 0.196774, 0.275278, 0.961121, - 0.0603414, 0.196893, 0.297733, 0.96139, - 0.0644244, 0.196717, 0.321877, 0.961818, - 0.067556, 0.196314, 0.346476, 0.962175, - 0.0712709, 0.195917, 0.371907, 0.96255, - 0.0752848, 0.1955, 0.397916, 0.963164, - 0.0792073, 0.195026, 0.424229, 0.963782, - 0.0828225, 0.194424, 0.450637, 0.964306, - 0.0873119, 0.193831, 0.477288, 0.964923, - 0.0911051, 0.192973, 0.504716, 0.966048, - 0.093251, 0.19151, 0.533053, 0.967024, - 0.0958983, 0.190013, 0.561366, 0.968038, - 0.09835, 0.188253, 0.589464, 0.969152, - 0.100754, 0.186257, 0.617433, 0.970557, - 0.102239, 0.183775, 0.645801, 0.972104, - 0.102767, 0.180645, 0.674278, 0.973203, - 0.103492, 0.177242, 0.702004, 0.975123, - 0.103793, 0.17345, 0.729529, 0.97641, - 0.102839, 0.168886, 0.756712, 0.978313, - 0.101687, 0.163892, 0.783801, 0.980036, - 0.100314, 0.158439, 0.809671, 0.981339, - 0.097836, 0.152211, 0.835402, 0.982794, - 0.0950006, 0.145679, 0.860081, 0.984123, - 0.0920994, 0.138949, 0.883757, 0.984918, - 0.0878641, 0.131283, 0.90685, 0.985999, - 0.083939, 0.123464, 0.928786, 0.987151, - 0.0791234, 0.115324, 0.94983, 0.987827, - 0.0739332, 0.106854, 0.96962, 0.988806, - 0.0688088, 0.0982691, 0.98861, 0.989588, - 0.0628962, 0.0893456, 1.00667, 0.990438, - 0.0573146, 0.0805392, 1.02344, 0.991506, - 0.0509433, 0.0713725, 1.03933, 0.992492, - 0.0448724, 0.0623732, 1.05378, 0.993663, - 0.0383497, 0.0530838, 1.06747, 0.994956, - 0.0319593, 0.0439512, 1.08007, 0.99634, - 0.025401, 0.0347803, 1.09182, 0.99761, - 0.0189687, 0.0257954, 1.1025, 0.99863, - 0.0124441, 0.0169893, 1.11247, 0.99947, - 0.00614003, 0.00829498, 1.12151, 1.00008, 0.000216624, - 0.000146107, 1.12993, 0.950129, - 4.34955e-06, 0.217413, 1.90081e-05, 0.950264, - 0.00010957, 0.217444, 0.00047884, 0.9503, - 0.000438299, 0.217451, 0.00191543, 0.950246, - 0.000986124, 0.21744, 0.00430951, 0.950246, - 0.00175311, 0.21744, 0.00766137, 0.950245, - 0.00273923, 0.21744, 0.011971, 0.950253, - 0.00394453, 0.217441, 0.0172385, 0.950258, - 0.00536897, 0.217442, 0.0234641, 0.950267, - 0.00701262, 0.217444, 0.030648, 0.950277, - 0.00887551, 0.217446, 0.038791, 0.950284, - 0.0109576, 0.217446, 0.0478931, 0.950312, - 0.0132591, 0.217451, 0.0579568, 0.950334, - 0.01578, 0.217454, 0.0689821, 0.950378, - 0.0185204, 0.217462, 0.0809714, 0.950417, - 0.0214803, 0.217467, 0.0939265, 0.950488, - 0.0246594, 0.217479, 0.10785, 0.950534, - 0.0280565, 0.217483, 0.122743, 0.950633, - 0.0316685, 0.217498, 0.138611, 0.950698, - 0.0354787, 0.217499, 0.155442, 0.950844, - 0.0394003, 0.217507, 0.173208, 0.950999, - 0.0426812, 0.217419, 0.191605, 0.951221, - 0.0461302, 0.217317, 0.21084, 0.951412, - 0.0502131, 0.217238, 0.230945, 0.951623, - 0.0549183, 0.21722, 0.251745, 0.951867, - 0.0604493, 0.217306, 0.273001, 0.952069, - 0.0665189, 0.217466, 0.294874, 0.952459, - 0.0709179, 0.217266, 0.318732, 0.952996, - 0.0746112, 0.216891, 0.34318, 0.953425, - 0.0789252, 0.216503, 0.36849, 0.953885, - 0.0833293, 0.216042, 0.394373, 0.954617, - 0.087371, 0.215469, 0.420505, 0.955429, - 0.0914054, 0.214802, 0.446907, 0.956068, - 0.0961671, 0.214146, 0.473522, 0.957094, - 0.10048, 0.213286, 0.50052, 0.958372, - 0.103248, 0.211796, 0.528715, 0.959654, - 0.106033, 0.21016, 0.557065, 0.961305, - 0.108384, 0.208149, 0.585286, 0.962785, - 0.111122, 0.206024, 0.613334, 0.964848, - 0.112981, 0.203442, 0.641334, 0.966498, - 0.113717, 0.19996, 0.669955, 0.968678, - 0.114121, 0.196105, 0.698094, 0.970489, - 0.114524, 0.191906, 0.725643, 0.972903, - 0.113792, 0.186963, 0.752856, 0.974701, - 0.112406, 0.181343, 0.780013, 0.976718, - 0.110685, 0.175185, 0.806268, 0.978905, - 0.108468, 0.168535, 0.832073, 0.980267, - 0.105061, 0.161106, 0.857149, 0.981967, - 0.101675, 0.153387, 0.881145, 0.983063, - 0.0974492, 0.145199, 0.904255, 0.984432, - 0.0925815, 0.136527, 0.926686, 0.985734, - 0.0877983, 0.127584, 0.947901, 0.986228, - 0.081884, 0.118125, 0.968111, 0.98719, - 0.0761208, 0.108594, 0.98719, 0.988228, - 0.0698196, 0.0989996, 1.00559, 0.989046, - 0.0632739, 0.0890074, 1.02246, 0.990242, - 0.056522, 0.0790832, 1.03841, 0.991252, - 0.0495272, 0.0689182, 1.05347, 0.992542, - 0.0425373, 0.0588592, 1.06724, 0.994096, - 0.0353198, 0.0486833, 1.08009, 0.995593, - 0.028235, 0.0385977, 1.09177, 0.99711, - 0.0209511, 0.0286457, 1.10274, 0.998263, - 0.0139289, 0.0188497, 1.11262, 0.999254, - 0.0067359, 0.009208, 1.12191, 0.999967, 0.000141846, - 6.57764e-05, 1.13024, 0.935608, - 4.74692e-06, 0.236466, 1.87817e-05, 0.93996, - 0.00011971, 0.237568, 0.000473646, 0.939959, - 0.000478845, 0.237567, 0.0018946, 0.939954, - 0.0010774, 0.237566, 0.00426284, 0.939956, - 0.00191538, 0.237566, 0.00757842, 0.939954, - 0.00299277, 0.237566, 0.0118413, 0.93996, - 0.00430961, 0.237567, 0.0170518, 0.939969, - 0.00586589, 0.237569, 0.02321, 0.939982, - 0.00766166, 0.237572, 0.0303164, 0.939987, - 0.00969686, 0.237572, 0.0383711, 0.939997, - 0.0119715, 0.237574, 0.0473751, 0.940031, - 0.0144858, 0.237581, 0.0573298, 0.940073, - 0.0172399, 0.237589, 0.0682366, 0.94012, - 0.0202335, 0.237598, 0.080097, 0.940162, - 0.0234663, 0.237604, 0.0929116, 0.940237, - 0.0269387, 0.237615, 0.106686, 0.940328, - 0.0306489, 0.237632, 0.121421, 0.940419, - 0.0345917, 0.237645, 0.137115, 0.940522, - 0.0387481, 0.237654, 0.153766, 0.940702, - 0.0429906, 0.237661, 0.17133, 0.940871, - 0.0465089, 0.237561, 0.189502, 0.941103, - 0.050531, 0.23748, 0.208616, 0.941369, - 0.0550657, 0.237423, 0.228595, 0.941641, - 0.0601337, 0.237399, 0.249287, 0.941903, - 0.0658804, 0.237443, 0.270467, 0.942224, - 0.0722674, 0.237597, 0.292024, 0.942633, - 0.0771788, 0.237419, 0.315272, 0.943172, - 0.0815623, 0.237068, 0.339579, 0.943691, - 0.0863973, 0.236682, 0.364717, 0.944382, - 0.0911536, 0.236213, 0.390435, 0.945392, - 0.0952967, 0.235562, 0.416425, 0.946185, - 0.0998948, 0.234832, 0.442772, 0.947212, - 0.104796, 0.234114, 0.469347, 0.948778, - 0.10928, 0.233222, 0.496162, 0.950149, - 0.113081, 0.231845, 0.523978, 0.951989, - 0.115893, 0.230005, 0.552295, 0.953921, - 0.11846, 0.227862, 0.580569, 0.955624, - 0.12115, 0.225439, 0.608698, 0.958234, - 0.123373, 0.222635, 0.636696, 0.960593, - 0.124519, 0.219093, 0.665208, 0.963201, - 0.124736, 0.214749, 0.693557, 0.965642, - 0.125012, 0.210059, 0.721334, 0.968765, - 0.124661, 0.204935, 0.748613, 0.971753, - 0.122996, 0.198661, 0.776224, 0.973751, - 0.120998, 0.191823, 0.802461, 0.976709, - 0.118583, 0.184359, 0.828399, 0.977956, - 0.115102, 0.176437, 0.853693, 0.979672, - 0.111077, 0.167681, 0.877962, 0.981816, - 0.10688, 0.158872, 0.901564, 0.98238, - 0.101469, 0.149398, 0.924057, 0.983964, - 0.0960013, 0.139436, 0.945751, 0.984933, - 0.0899626, 0.12943, 0.966272, 0.985694, - 0.0832973, 0.11894, 0.985741, 0.986822, - 0.0767082, 0.108349, 1.00407, 0.987725, - 0.0693614, 0.0976026, 1.02154, 0.98877, - 0.06211, 0.086652, 1.03757, 0.990129, - 0.0544143, 0.0756182, 1.05296, 0.991337, - 0.046744, 0.0645753, 1.06683, 0.992978, - 0.0387931, 0.0534683, 1.0798, 0.994676, - 0.030973, 0.0424137, 1.09181, 0.99645, - 0.0230311, 0.0314035, 1.10286, 0.997967, - 0.0152065, 0.0206869, 1.11291, 0.99922, - 0.00744837, 0.010155, 1.12237, 1.00002, 0.000240209, - 7.52767e-05, 1.13089, 0.922948, - 5.15351e-06, 0.255626, 1.86069e-05, 0.928785, - 0.000129623, 0.257244, 0.000468009, 0.928761, - 0.00051849, 0.257237, 0.00187202, 0.928751, - 0.0011666, 0.257235, 0.00421204, 0.928751, - 0.00207395, 0.257234, 0.0074881, 0.928754, - 0.00324055, 0.257235, 0.0117002, 0.92876, - 0.00466639, 0.257236, 0.0168486, 0.928763, - 0.00635149, 0.257237, 0.0229334, 0.928774, - 0.00829584, 0.257239, 0.029955, 0.928791, - 0.0104995, 0.257243, 0.0379139, 0.928804, - 0.0129623, 0.257245, 0.0468108, 0.928847, - 0.0156846, 0.257255, 0.0566473, 0.92889, - 0.0186661, 0.257263, 0.0674246, 0.928924, - 0.0219067, 0.257268, 0.0791433, 0.928989, - 0.0254066, 0.257282, 0.0918076, 0.92909, - 0.0291651, 0.257301, 0.105419, 0.92918, - 0.0331801, 0.257316, 0.119978, 0.92929, - 0.0374469, 0.257332, 0.135491, 0.929453, - 0.041939, 0.257357, 0.151948, 0.929586, - 0.0464612, 0.257347, 0.169275, 0.929858, - 0.0503426, 0.257269, 0.187257, 0.930125, - 0.0548409, 0.257199, 0.206204, 0.930403, - 0.0598063, 0.257149, 0.22601, 0.930726, - 0.0652437, 0.257122, 0.246561, 0.931098, - 0.0712376, 0.257153, 0.267618, 0.931396, - 0.0777506, 0.257237, 0.288993, 0.931947, - 0.0832374, 0.257124, 0.311527, 0.932579, - 0.0883955, 0.25683, 0.335697, 0.933194, - 0.0937037, 0.256444, 0.360634, 0.934013, - 0.0987292, 0.255939, 0.386126, 0.935307, - 0.103215, 0.255282, 0.412018, 0.936374, - 0.108234, 0.254538, 0.438292, 0.93776, - 0.113234, 0.253728, 0.464805, 0.939599, - 0.118013, 0.25275, 0.491464, 0.941036, - 0.122661, 0.251404, 0.518751, 0.94337, - 0.125477, 0.249435, 0.547133, 0.945318, - 0.128374, 0.247113, 0.575456, 0.947995, - 0.130996, 0.244441, 0.60372, 0.950818, - 0.133438, 0.241352, 0.63174, 0.954378, - 0.135004, 0.237849, 0.659971, 0.957151, - 0.135313, 0.233188, 0.688478, 0.960743, - 0.13521, 0.228001, 0.716767, 0.964352, - 0.135007, 0.222249, 0.744349, 0.967273, - 0.133523, 0.21542, 0.771786, 0.969767, - 0.131155, 0.208039, 0.798639, 0.973195, - 0.128492, 0.200076, 0.824774, 0.975557, - 0.125094, 0.191451, 0.850222, 0.977692, - 0.120578, 0.18184, 0.874761, 0.98026, - 0.115882, 0.172102, 0.898497, 0.981394, - 0.110372, 0.161859, 0.921636, 0.982386, - 0.10415, 0.15108, 0.943467, 0.983783, - 0.0978128, 0.140407, 0.964045, 0.98422, - 0.0906171, 0.129058, 0.98398, 0.985447, - 0.0832921, 0.117614, 1.00276, 0.986682, - 0.0754412, 0.10585, 1.02047, 0.987326, - 0.0673885, 0.0940943, 1.03678, 0.988707, - 0.0592565, 0.0822093, 1.05218, 0.990185, - 0.050717, 0.070192, 1.06652, 0.991866, - 0.0423486, 0.0582081, 1.07965, 0.993897, - 0.0336118, 0.0460985, 1.09188, 0.995841, - 0.0252178, 0.0342737, 1.10307, 0.997605, - 0.0164893, 0.0224829, 1.11324, 0.999037, - 0.00817112, 0.0110647, 1.12262, 1.00003, 0.000291686, - 0.000168673, 1.13139, 0.915304, - 5.52675e-06, 0.275999, 1.83285e-05, 0.91668, - 0.000139285, 0.276414, 0.000461914, 0.916664, - 0.00055713, 0.276409, 0.00184763, 0.916653, - 0.00125354, 0.276406, 0.00415715, 0.916651, - 0.00222851, 0.276405, 0.00739053, 0.916655, - 0.00348205, 0.276406, 0.0115478, 0.916653, - 0.00501414, 0.276405, 0.0166291, 0.916667, - 0.00682478, 0.276409, 0.0226346, 0.91668, - 0.00891398, 0.276412, 0.0295648, 0.91669, - 0.0112817, 0.276413, 0.0374199, 0.916727, - 0.013928, 0.276422, 0.0462016, 0.916759, - 0.0168528, 0.276429, 0.0559101, 0.916793, - 0.0200558, 0.276436, 0.0665466, 0.916849, - 0.0235373, 0.276448, 0.0781139, 0.916964, - 0.0272973, 0.276474, 0.0906156, 0.917047, - 0.0313344, 0.276491, 0.104051, 0.917152, - 0.0356465, 0.276511, 0.118424, 0.917286, - 0.0402271, 0.276533, 0.133736, 0.917469, - 0.0450408, 0.276564, 0.149978, 0.917686, - 0.0497872, 0.276563, 0.167057, 0.917953, - 0.0540937, 0.276493, 0.184846, 0.918228, - 0.0590709, 0.276437, 0.203614, 0.918572, - 0.0644277, 0.276398, 0.223212, 0.918918, - 0.0702326, 0.276362, 0.243584, 0.919356, - 0.076484, 0.276383, 0.264465, 0.919842, - 0.0830808, 0.276434, 0.285701, 0.920451, - 0.0892972, 0.276407, 0.307559, 0.921113, - 0.095016, 0.276128, 0.331501, 0.921881, - 0.100771, 0.275754, 0.356207, 0.923027, - 0.106029, 0.275254, 0.381477, 0.924364, - 0.111029, 0.274595, 0.40722, 0.925818, - 0.116345, 0.273841, 0.433385, 0.92746, - 0.121424, 0.272913, 0.459848, 0.929167, - 0.12657, 0.271837, 0.486493, 0.931426, - 0.131581, 0.270575, 0.513432, 0.934001, - 0.135038, 0.268512, 0.541502, 0.936296, - 0.138039, 0.266135, 0.569658, 0.939985, - 0.140687, 0.263271, 0.598375, 0.943516, - 0.143247, 0.260058, 0.626563, 0.94782, - 0.145135, 0.256138, 0.654711, 0.951023, - 0.145733, 0.251154, 0.683285, 0.955338, - 0.145554, 0.245562, 0.711831, 0.959629, - 0.145008, 0.239265, 0.739573, 0.963123, - 0.144003, 0.232064, 0.767027, 0.966742, - 0.141289, 0.224036, 0.794359, 0.969991, - 0.138247, 0.215305, 0.820361, 0.973403, - 0.134786, 0.206051, 0.846548, 0.975317, - 0.129966, 0.195914, 0.871541, 0.977647, - 0.12471, 0.185184, 0.895313, 0.980137, - 0.119086, 0.174161, 0.918398, 0.981031, - 0.112297, 0.162792, 0.940679, 0.982037, - 0.105372, 0.150952, 0.961991, 0.983164, - 0.097821, 0.138921, 0.981913, 0.983757, - 0.0897245, 0.126611, 1.00109, 0.985036, - 0.0815974, 0.114228, 1.01902, 0.986289, - 0.0727725, 0.101389, 1.03604, 0.987329, - 0.0639323, 0.0886476, 1.05149, 0.989193, - 0.0548109, 0.0756837, 1.06619, 0.990716, - 0.045687, 0.0627581, 1.07948, 0.992769, - 0.0364315, 0.0498337, 1.09172, 0.99524, - 0.0271761, 0.0370305, 1.1033, 0.997154, - 0.0179609, 0.0243959, 1.11353, 0.998845, - 0.00878063, 0.0119567, 1.12319, 1.00002, 0.000259038, - 0.000108146, 1.13177, 0.903945, - 5.91681e-06, 0.295126, 1.81226e-05, 0.903668, - 0.000148672, 0.295037, 0.000455367, 0.903677, - 0.000594683, 0.29504, 0.00182145, 0.903673, - 0.00133805, 0.295039, 0.00409831, 0.903666, - 0.00237872, 0.295036, 0.00728584, 0.903668, - 0.00371676, 0.295037, 0.0113842, 0.903679, - 0.00535212, 0.29504, 0.0163936, 0.903684, - 0.00728479, 0.295041, 0.0223141, 0.903698, - 0.00951473, 0.295044, 0.0291462, 0.903718, - 0.0120419, 0.295049, 0.0368904, 0.903754, - 0.0148664, 0.295058, 0.0455477, 0.903801, - 0.017988, 0.29507, 0.0551194, 0.903851, - 0.0214064, 0.295082, 0.0656058, 0.903921, - 0.0251219, 0.295097, 0.0770109, 0.904002, - 0.0291337, 0.295116, 0.0893354, 0.904111, - 0.033441, 0.29514, 0.102583, 0.904246, - 0.0380415, 0.295169, 0.116755, 0.904408, - 0.0429258, 0.295202, 0.131853, 0.904637, - 0.0480468, 0.295245, 0.147869, 0.904821, - 0.0529208, 0.295214, 0.164658, 0.905163, - 0.0577748, 0.295185, 0.182274, 0.905469, - 0.0631763, 0.295143, 0.200828, 0.905851, - 0.068917, 0.295112, 0.2202, 0.906322, - 0.0750861, 0.295104, 0.240372, 0.906761, - 0.0815855, 0.295086, 0.261082, 0.90735, - 0.0882138, 0.295095, 0.282123, 0.908087, - 0.095082, 0.295139, 0.303563, 0.908826, - 0.101488, 0.29492, 0.327028, 0.909832, - 0.107577, 0.294577, 0.351464, 0.911393, - 0.113033, 0.294115, 0.376497, 0.912804, - 0.118629, 0.293446, 0.402115, 0.914081, - 0.124232, 0.292581, 0.428111, 0.91637, - 0.129399, 0.29166, 0.454442, 0.91814, - 0.134892, 0.290422, 0.481024, 0.921179, - 0.140069, 0.289194, 0.507924, 0.924544, - 0.144431, 0.287421, 0.535557, 0.927995, - 0.147498, 0.284867, 0.563984, 0.931556, - 0.150197, 0.281722, 0.5923, 0.935777, - 0.152711, 0.278207, 0.620832, 0.940869, - 0.154836, 0.274148, 0.649069, 0.945994, - 0.155912, 0.269057, 0.677746, 0.949634, - 0.155641, 0.262799, 0.706293, 0.955032, - 0.154809, 0.256097, 0.734278, 0.95917, - 0.153678, 0.248618, 0.761751, 0.962931, - 0.151253, 0.239794, 0.789032, 0.966045, - 0.147625, 0.230281, 0.815422, 0.96971, - 0.143964, 0.220382, 0.841787, 0.972747, - 0.139464, 0.209846, 0.867446, 0.975545, - 0.133459, 0.198189, 0.892004, 0.978381, - 0.127424, 0.186362, 0.915458, 0.979935, - 0.120506, 0.173964, 0.937948, 0.980948, - 0.11282, 0.161429, 0.959732, 0.982234, - 0.104941, 0.148557, 0.980118, 0.982767, - 0.0962905, 0.135508, 0.999463, 0.983544, - 0.0873625, 0.122338, 1.01756, 0.984965, - 0.0783447, 0.108669, 1.03492, 0.986233, - 0.0684798, 0.0949911, 1.05087, 0.987796, - 0.0590867, 0.0811386, 1.0656, 0.989885, - 0.0489145, 0.0673099, 1.0794, 0.991821, - 0.0391, 0.0535665, 1.09174, 0.99448, - 0.029087, 0.0397529, 1.10341, 0.996769, - 0.019114, 0.0261463, 1.11383, 0.998641, - 0.00947007, 0.0128731, 1.1237, 0.999978, 0.000446316, - 0.000169093, 1.13253, 0.888362, - 6.27064e-06, 0.312578, 1.78215e-05, 0.889988, - 0.000157791, 0.313148, 0.000448451, 0.889825, - 0.000631076, 0.313092, 0.00179356, 0.88984, - 0.00141994, 0.313097, 0.00403554, 0.889828, - 0.0025243, 0.313092, 0.00717429, 0.889831, - 0.00394421, 0.313093, 0.0112099, 0.889831, - 0.00567962, 0.313093, 0.0161425, 0.889844, - 0.00773051, 0.313096, 0.0219724, 0.889858, - 0.0100968, 0.3131, 0.0286999, 0.889882, - 0.0127786, 0.313106, 0.0363256, 0.889918, - 0.0157757, 0.313116, 0.0448509, 0.889967, - 0.0190878, 0.313129, 0.0542758, 0.89003, - 0.022715, 0.313145, 0.0646032, 0.890108, - 0.0266566, 0.313165, 0.0758339, 0.890218, - 0.0309131, 0.313193, 0.0879729, 0.890351, - 0.0354819, 0.313226, 0.101019, 0.89051, - 0.0403613, 0.313263, 0.114979, 0.890672, - 0.0455385, 0.313294, 0.129848, 0.890882, - 0.0509444, 0.313333, 0.145616, 0.891189, - 0.0559657, 0.313324, 0.162122, 0.891457, - 0.0613123, 0.313281, 0.179524, 0.891856, - 0.0671488, 0.313281, 0.197855, 0.892312, - 0.0732732, 0.313268, 0.216991, 0.892819, - 0.0797865, 0.313263, 0.236924, 0.893369, - 0.0865269, 0.313247, 0.257433, 0.894045, - 0.0931592, 0.313205, 0.278215, 0.894884, - 0.100532, 0.313276, 0.299467, 0.895832, - 0.107716, 0.313205, 0.322276, 0.897043, - 0.114099, 0.312873, 0.34642, 0.898515, - 0.119941, 0.312331, 0.371187, 0.900191, - 0.126044, 0.311731, 0.396656, 0.90188, - 0.131808, 0.310859, 0.422488, 0.904359, - 0.137289, 0.309857, 0.448744, 0.906923, - 0.142991, 0.308714, 0.475239, 0.910634, - 0.148253, 0.307465, 0.501983, 0.914502, - 0.153332, 0.305774, 0.529254, 0.919046, - 0.156646, 0.303156, 0.557709, 0.923194, - 0.159612, 0.299928, 0.586267, 0.928858, - 0.162027, 0.296245, 0.614925, 0.934464, - 0.164203, 0.291832, 0.643187, 0.939824, - 0.165602, 0.286565, 0.671601, 0.944582, - 0.165383, 0.280073, 0.700213, 0.949257, - 0.164439, 0.272891, 0.728432, 0.954389, - 0.162953, 0.264771, 0.756082, 0.958595, - 0.161007, 0.255927, 0.78369, 0.962138, - 0.157243, 0.245769, 0.810769, 0.966979, - 0.152872, 0.235127, 0.836999, 0.969566, - 0.148209, 0.22347, 0.862684, 0.972372, - 0.142211, 0.211147, 0.887847, 0.975916, - 0.135458, 0.198606, 0.911843, 0.978026, - 0.128398, 0.185498, 0.934795, 0.979686, - 0.120313, 0.17171, 0.956787, 0.980748, - 0.11166, 0.158159, 0.978046, 0.981622, - 0.103035, 0.144399, 0.997693, 0.982356, - 0.0930328, 0.13001, 1.01642, 0.983308, - 0.0834627, 0.115778, 1.03366, 0.985037, - 0.0732249, 0.101327, 1.05014, 0.986493, - 0.0628145, 0.086554, 1.06507, 0.988484, - 0.0526556, 0.0720413, 1.07907, 0.991051, - 0.0415744, 0.0571151, 1.09189, 0.993523, - 0.0314275, 0.0426643, 1.10369, 0.99628, - 0.0203603, 0.0279325, 1.11423, 0.998344, - 0.0102446, 0.0138182, 1.12421, 0.999997, 0.00042612, - 0.000193628, 1.1333, 0.871555, - 6.60007e-06, 0.329176, 1.74749e-05, 0.875255, - 0.000166579, 0.330571, 0.000441051, 0.875644, - 0.000666394, 0.330718, 0.00176441, 0.875159, - 0.00149903, 0.330536, 0.00396899, 0.87516, - 0.00266493, 0.330536, 0.007056, 0.875158, - 0.00416393, 0.330535, 0.0110251, 0.87516, - 0.00599598, 0.330535, 0.0158764, 0.875163, - 0.00816108, 0.330536, 0.0216101, 0.875174, - 0.0106591, 0.330538, 0.0282266, 0.875199, - 0.0134899, 0.330545, 0.0357266, 0.875257, - 0.0166538, 0.330563, 0.0441117, 0.875304, - 0.0201501, 0.330575, 0.0533821, 0.875373, - 0.0239785, 0.330595, 0.0635395, 0.875464, - 0.0281389, 0.330619, 0.0745872, 0.875565, - 0.0326301, 0.330645, 0.0865255, 0.875691, - 0.0374516, 0.330676, 0.0993599, 0.875897, - 0.0425993, 0.330733, 0.113093, 0.876091, - 0.0480576, 0.330776, 0.127722, 0.876353, - 0.0537216, 0.330826, 0.143227, 0.876649, - 0.0589807, 0.330809, 0.159462, 0.877034, - 0.0647865, 0.330819, 0.176642, 0.877443, - 0.0709789, 0.330817, 0.194702, 0.877956, - 0.0774782, 0.330832, 0.213577, 0.878499, - 0.0843175, 0.330822, 0.233246, 0.879144, - 0.0912714, 0.330804, 0.253512, 0.879982, - 0.0980824, 0.330766, 0.274137, 0.88097, - 0.105823, 0.330864, 0.295209, 0.882051, - 0.113671, 0.330896, 0.317226, 0.883397, - 0.120303, 0.330545, 0.341068, 0.884987, - 0.12667, 0.330068, 0.365613, 0.886789, - 0.133118, 0.329418, 0.390807, 0.889311, - 0.139024, 0.328683, 0.416494, 0.891995, - 0.144971, 0.327729, 0.442618, 0.895106, - 0.150747, 0.326521, 0.469131, 0.899527, - 0.156283, 0.325229, 0.495921, 0.90504, - 0.161707, 0.32378, 0.523162, 0.909875, - 0.165661, 0.32122, 0.55092, 0.91561, - 0.168755, 0.317942, 0.579928, 0.921225, - 0.171193, 0.313983, 0.608539, 0.927308, - 0.17319, 0.309636, 0.636854, 0.933077, - 0.174819, 0.304262, 0.66523, 0.938766, - 0.175002, 0.297563, 0.693609, 0.943667, - 0.173946, 0.289613, 0.722157, 0.949033, - 0.172221, 0.281227, 0.750021, 0.953765, - 0.169869, 0.271545, 0.777466, 0.95804, - 0.166578, 0.261034, 0.804853, 0.962302, - 0.161761, 0.249434, 0.831569, 0.966544, - 0.156636, 0.237484, 0.857779, 0.969372, - 0.150784, 0.224395, 0.883051, 0.972486, - 0.143672, 0.210786, 0.907864, 0.975853, - 0.135772, 0.196556, 0.931223, 0.977975, - 0.127942, 0.182307, 0.954061, 0.979122, - 0.118347, 0.167607, 0.97531, 0.980719, - 0.109112, 0.152739, 0.995666, 0.981223, - 0.0991789, 0.137932, 1.01475, 0.98216, - 0.0883553, 0.122692, 1.03253, 0.983379, - 0.0780825, 0.107493, 1.04917, 0.985434, - 0.0665646, 0.0917791, 1.06464, 0.987332, - 0.0557714, 0.0764949, 1.07896, 0.990004, - 0.0442805, 0.060721, 1.09199, 0.992975, - 0.0331676, 0.0452284, 1.10393, 0.995811, - 0.0219547, 0.0297934, 1.11476, 0.9982, - 0.0107613, 0.0146415, 1.12484, 1.00002, 0.000248678, - 0.00014555, 1.13413, 0.859519, - 6.93595e-06, 0.347264, 1.71673e-05, 0.859843, - 0.00017503, 0.347394, 0.000433219, 0.859656, - 0.000700076, 0.347319, 0.00173277, 0.859671, - 0.00157517, 0.347325, 0.00389875, 0.859669, - 0.00280028, 0.347324, 0.00693112, 0.85967, - 0.0043754, 0.347324, 0.01083, 0.859665, - 0.00630049, 0.347321, 0.0155954, 0.859685, - 0.0085755, 0.347328, 0.0212278, 0.859694, - 0.0112003, 0.347329, 0.0277273, 0.859718, - 0.0141747, 0.347336, 0.0350946, 0.85976, - 0.0174988, 0.347348, 0.0433314, 0.85982, - 0.0211722, 0.347366, 0.0524384, 0.859892, - 0.0251941, 0.347387, 0.0624168, 0.860006, - 0.0295649, 0.347422, 0.0732708, 0.860122, - 0.0342825, 0.347453, 0.0849999, 0.860282, - 0.0393462, 0.347499, 0.0976102, 0.860482, - 0.0447513, 0.347554, 0.111104, 0.860719, - 0.0504775, 0.347614, 0.125479, 0.860998, - 0.0563577, 0.347666, 0.140703, 0.861322, - 0.0619473, 0.347662, 0.156681, 0.861724, - 0.0681277, 0.347684, 0.173597, 0.862198, - 0.0746567, 0.347709, 0.191371, 0.862733, - 0.0815234, 0.347727, 0.209976, 0.863371, - 0.0886643, 0.347744, 0.229351, 0.86414, - 0.0957908, 0.347734, 0.24934, 0.865138, - 0.102912, 0.34772, 0.269797, 0.866182, - 0.110924, 0.3478, 0.290654, 0.867436, - 0.119223, 0.347911, 0.312074, 0.869087, - 0.126197, 0.347649, 0.335438, 0.870859, - 0.133145, 0.347222, 0.359732, 0.872997, - 0.139869, 0.346645, 0.38467, 0.875939, - 0.146089, 0.345935, 0.41019, 0.879012, - 0.152334, 0.345012, 0.436218, 0.883353, - 0.15821, 0.343924, 0.462641, 0.888362, - 0.164097, 0.342636, 0.489449, 0.895026, - 0.169528, 0.341351, 0.516629, 0.900753, - 0.174408, 0.339115, 0.544109, 0.906814, - 0.17751, 0.335809, 0.572857, 0.912855, - 0.180101, 0.331597, 0.601554, 0.919438, - 0.182116, 0.32698, 0.630198, 0.925962, - 0.183494, 0.321449, 0.658404, 0.931734, - 0.184159, 0.314595, 0.686625, 0.93762, - 0.18304, 0.306462, 0.71531, 0.943858, - 0.181323, 0.297514, 0.744272, 0.948662, - 0.178683, 0.287447, 0.771462, 0.953299, - 0.175379, 0.276166, 0.798593, 0.957346, - 0.170395, 0.263758, 0.8256, 0.962565, - 0.165042, 0.251019, 0.852575, 0.966075, - 0.158655, 0.237011, 0.878316, 0.969048, - 0.151707, 0.222518, 0.90329, 0.972423, - 0.143271, 0.207848, 0.927745, 0.975833, - 0.134824, 0.192463, 0.950859, 0.977629, - 0.125444, 0.1768, 0.972947, 0.978995, - 0.114949, 0.161033, 0.993263, 0.980533, - 0.104936, 0.145523, 1.01337, 0.980745, - 0.0935577, 0.129799, 1.03128, 0.981814, - 0.0822956, 0.113486, 1.04825, 0.983943, - 0.0710082, 0.0972925, 1.06405, 0.986141, - 0.0587931, 0.0808138, 1.0785, 0.988878, - 0.0472755, 0.0644915, 1.09204, 0.992132, - 0.0349128, 0.0478128, 1.10413, 0.9953, - 0.0232407, 0.031621, 1.11527, 0.998117, - 0.0112713, 0.0154935, 1.12551, 1.00003, 0.000339743, - 0.000195763, 1.13504, 0.845441, - 7.29126e-06, 0.364305, 1.69208e-05, 0.843588, - 0.000183164, 0.363506, 0.000425067, 0.843412, - 0.00073253, 0.36343, 0.00169999, 0.843401, - 0.00164818, 0.363426, 0.00382495, 0.843399, - 0.00293008, 0.363425, 0.00679993, 0.843401, - 0.00457822, 0.363425, 0.010625, 0.843394, - 0.00659249, 0.363421, 0.0153002, 0.843398, - 0.00897282, 0.363421, 0.0208258, 0.843415, - 0.0117191, 0.363426, 0.0272024, 0.843438, - 0.0148312, 0.363432, 0.0344305, 0.843483, - 0.018309, 0.363447, 0.0425116, 0.84356, - 0.0221521, 0.363472, 0.0514471, 0.843646, - 0.0263597, 0.363499, 0.061238, 0.843743, - 0.0309315, 0.363527, 0.0718873, 0.84388, - 0.0358658, 0.363569, 0.0833969, 0.844079, - 0.0411624, 0.363631, 0.0957742, 0.844279, - 0.0468128, 0.363688, 0.109015, 0.844549, - 0.0527923, 0.363761, 0.123124, 0.844858, - 0.0588204, 0.363817, 0.138044, 0.84522, - 0.0647573, 0.36383, 0.153755, 0.845669, - 0.0713181, 0.363879, 0.170394, 0.846155, - 0.0781697, 0.363908, 0.187861, 0.846789, - 0.0853913, 0.363969, 0.206176, 0.847502, - 0.0928086, 0.363999, 0.225244, 0.8484, - 0.10005, 0.363997, 0.244926, 0.849461, - 0.107615, 0.364008, 0.265188, 0.850562, - 0.115814, 0.364055, 0.28587, 0.851962, - 0.124334, 0.364179, 0.306926, 0.854326, - 0.131995, 0.364233, 0.329605, 0.856295, - 0.139338, 0.363856, 0.35359, 0.858857, - 0.146346, 0.363347, 0.37831, 0.862428, - 0.152994, 0.362807, 0.403722, 0.866203, - 0.159463, 0.361963, 0.429537, 0.871629, - 0.165623, 0.36112, 0.456, 0.877365, - 0.171649, 0.359917, 0.482773, 0.883744, - 0.177151, 0.35848, 0.509705, 0.890693, - 0.182381, 0.356523, 0.537215, 0.897278, - 0.186076, 0.3533, 0.565493, 0.903958, - 0.188602, 0.349095, 0.594293, 0.910908, - 0.190755, 0.344215, 0.623165, 0.918117, - 0.192063, 0.338606, 0.651573, 0.924644, - 0.192758, 0.331544, 0.679869, 0.931054, - 0.192238, 0.323163, 0.708668, 0.937303, - 0.190035, 0.313529, 0.737201, 0.943387, - 0.187162, 0.303152, 0.764977, 0.948494, - 0.183876, 0.29146, 0.792683, 0.952546, - 0.178901, 0.277917, 0.819228, 0.958077, - 0.173173, 0.264753, 0.846559, 0.962462, - 0.16645, 0.25002, 0.872962, 0.966569, - 0.159452, 0.234873, 0.898729, 0.969108, - 0.15074, 0.218752, 0.923126, 0.973072, - 0.141523, 0.202673, 0.947278, 0.975452, - 0.132075, 0.186326, 0.969938, 0.977784, - 0.121257, 0.169396, 0.991325, 0.97899, - 0.110182, 0.153044, 1.01123, 0.979777, - 0.0989634, 0.136485, 1.0299, 0.980865, - 0.0865894, 0.119343, 1.04727, 0.982432, - 0.0746115, 0.102452, 1.06341, 0.984935, - 0.0621822, 0.0852423, 1.07834, 0.987776, - 0.0495694, 0.0678546, 1.092, 0.99103, - 0.0372386, 0.0506917, 1.1043, 0.99474, - 0.0244353, 0.0333316, 1.11576, 0.997768, - 0.0121448, 0.0164348, 1.12617, 1.00003, 0.00031774, - 0.000169504, 1.13598, 0.825551, - 7.56799e-06, 0.378425, 1.65099e-05, 0.82664, - 0.000190922, 0.378923, 0.000416504, 0.826323, - 0.000763495, 0.378779, 0.0016656, 0.826359, - 0.00171789, 0.378795, 0.00374768, 0.82636, - 0.00305402, 0.378795, 0.00666259, 0.826368, - 0.00477185, 0.378798, 0.0104104, 0.826364, - 0.00687131, 0.378795, 0.0149912, 0.826368, - 0.00935232, 0.378795, 0.0204054, 0.826376, - 0.0122146, 0.378797, 0.0266532, 0.826399, - 0.0154581, 0.378803, 0.0337355, 0.82646, - 0.0190825, 0.378824, 0.0416537, 0.826525, - 0.0230873, 0.378846, 0.0504091, 0.826614, - 0.0274719, 0.378876, 0.0600032, 0.82674, - 0.0322355, 0.378917, 0.0704393, 0.826888, - 0.0373766, 0.378964, 0.0817195, 0.827078, - 0.0428936, 0.379024, 0.0938492, 0.827318, - 0.0487778, 0.379099, 0.106828, 0.82764, - 0.0549935, 0.379199, 0.120659, 0.827926, - 0.0611058, 0.379227, 0.13526, 0.828325, - 0.0675054, 0.379275, 0.150713, 0.828801, - 0.0743455, 0.379332, 0.167034, 0.8294, - 0.0815523, 0.379415, 0.184209, 0.830094, - 0.0890779, 0.379495, 0.202203, 0.8309, - 0.096736, 0.379555, 0.220945, 0.831943, - 0.104135, 0.379577, 0.240306, 0.833037, - 0.112106, 0.379604, 0.260317, 0.834278, - 0.120554, 0.379668, 0.2808, 0.836192, - 0.129128, 0.3799, 0.301654, 0.838671, - 0.137541, 0.380109, 0.323502, 0.840939, - 0.14523, 0.379809, 0.347176, 0.844575, - 0.15248, 0.379593, 0.371706, 0.848379, - 0.159607, 0.37909, 0.39688, 0.853616, - 0.166267, 0.378617, 0.422702, 0.858921, - 0.172698, 0.377746, 0.448919, 0.865324, - 0.178823, 0.376749, 0.475661, 0.872207, - 0.184542, 0.375363, 0.502599, 0.880018, - 0.189836, 0.373657, 0.529914, 0.88694, - 0.194294, 0.370673, 0.557683, 0.894779, - 0.197022, 0.36662, 0.586848, 0.902242, - 0.199108, 0.36138, 0.615831, 0.909914, - 0.200398, 0.355434, 0.644478, 0.917088, - 0.20094, 0.348173, 0.672905, 0.923888, - 0.200671, 0.339482, 0.701327, 0.930495, - 0.198773, 0.32956, 0.730101, 0.937247, - 0.195394, 0.318363, 0.758383, 0.943108, - 0.191956, 0.306323, 0.786539, 0.948296, - 0.187227, 0.292576, 0.813637, 0.953472, - 0.181165, 0.278234, 0.840793, 0.958485, - 0.174119, 0.263054, 0.867712, 0.962714, - 0.166564, 0.246756, 0.893635, 0.966185, - 0.158181, 0.229945, 0.919028, 0.970146, - 0.148275, 0.212633, 0.943413, 0.973491, - 0.138157, 0.195229, 0.966627, 0.975741, - 0.127574, 0.178048, 0.988817, 0.977238, - 0.11554, 0.160312, 1.00924, 0.978411, - 0.10364, 0.142857, 1.02845, 0.979811, - 0.0913122, 0.125317, 1.04648, 0.98116, - 0.0782558, 0.107627, 1.06284, 0.983543, - 0.0655957, 0.0895862, 1.07798, 0.986789, - 0.0520411, 0.0713756, 1.092, 0.990292, - 0.0389727, 0.053228, 1.10484, 0.994187, - 0.025808, 0.0351945, 1.11642, 0.997499, - 0.0126071, 0.0173198, 1.12703, 0.999999, 0.000275604, - 0.000148602, 1.13674, 0.81075, - 7.8735e-06, 0.394456, 1.61829e-05, 0.808692, - 0.000198293, 0.393453, 0.000407564, 0.80846, - 0.000792877, 0.39334, 0.00162965, 0.808595, - 0.00178416, 0.393407, 0.00366711, 0.808597, - 0.00317182, 0.393408, 0.00651934, 0.808598, - 0.00495589, 0.393408, 0.0101866, 0.808591, - 0.00713627, 0.393403, 0.0146689, 0.808592, - 0.00971285, 0.393402, 0.0199667, 0.80861, - 0.0126855, 0.393407, 0.0260803, 0.808633, - 0.0160538, 0.393413, 0.0330107, 0.80868, - 0.0198175, 0.393429, 0.0407589, 0.808748, - 0.0239758, 0.393453, 0.0493264, 0.808854, - 0.0285286, 0.39349, 0.0587161, 0.808992, - 0.0334748, 0.39354, 0.0689304, 0.809141, - 0.0388116, 0.393588, 0.0799707, 0.809352, - 0.0445375, 0.39366, 0.0918432, 0.809608, - 0.0506427, 0.393742, 0.104549, 0.809915, - 0.0570708, 0.393834, 0.118085, 0.810253, - 0.0633526, 0.393885, 0.132377, 0.810687, - 0.0700966, 0.393953, 0.147537, 0.811233, - 0.0772274, 0.394047, 0.163543, 0.811865, - 0.0847629, 0.394148, 0.180394, 0.812648, - 0.0925663, 0.394265, 0.198051, 0.813583, - 0.100416, 0.394363, 0.216443, 0.814683, - 0.108119, 0.394402, 0.235502, 0.815948, - 0.11644, 0.394489, 0.255242, 0.817278, - 0.125036, 0.394542, 0.275441, 0.819605, - 0.133655, 0.39486, 0.296094, 0.822256, - 0.142682, 0.395248, 0.317309, 0.825349, - 0.150756, 0.395241, 0.340516, 0.829605, - 0.158392, 0.395285, 0.364819, 0.83391, - 0.165801, 0.394922, 0.389736, 0.839808, - 0.172677, 0.394691, 0.415409, 0.845708, - 0.179448, 0.394006, 0.441546, 0.853025, - 0.185746, 0.393279, 0.46832, 0.859666, - 0.191684, 0.391655, 0.495302, 0.86789, - 0.197146, 0.390068, 0.52262, 0.875845, - 0.201904, 0.38727, 0.550336, 0.882634, - 0.205023, 0.382688, 0.578825, 0.891076, - 0.207098, 0.377543, 0.608103, 0.900589, - 0.208474, 0.371752, 0.63723, 0.90791, - 0.209068, 0.364016, 0.665769, 0.915971, - 0.208655, 0.355593, 0.694428, 0.923455, - 0.20729, 0.345439, 0.723224, 0.931514, - 0.203821, 0.334099, 0.751925, 0.937885, - 0.19986, 0.321069, 0.780249, 0.943136, - 0.194993, 0.306571, 0.8077, 0.948818, - 0.189132, 0.291556, 0.83497, 0.954433, - 0.181617, 0.275745, 0.86188, 0.959078, - 0.173595, 0.258695, 0.888562, 0.962705, - 0.164855, 0.240825, 0.914008, 0.966753, - 0.155129, 0.22268, 0.939145, 0.970704, - 0.144241, 0.204542, 0.963393, 0.973367, - 0.133188, 0.185927, 0.985983, 0.975984, - 0.121146, 0.167743, 1.00704, 0.976994, - 0.108366, 0.149218, 1.02715, 0.978485, - 0.0956746, 0.13131, 1.0455, 0.980074, - 0.0820733, 0.112513, 1.06221, 0.98225, - 0.0684061, 0.0938323, 1.07782, 0.98553, - 0.0549503, 0.0749508, 1.09199, 0.989529, - 0.0407857, 0.055848, 1.10508, 0.993536, - 0.0271978, 0.0368581, 1.11684, 0.997247, - 0.0132716, 0.0181845, 1.12789, 1, 0.000431817, - 0.000198809, 1.13792, 0.785886, - 8.12608e-06, 0.405036, 1.57669e-05, 0.790388, - 0.000205278, 0.407355, 0.000398297, 0.790145, - 0.000820824, 0.407231, 0.00159263, 0.790135, - 0.00184681, 0.407226, 0.00358336, 0.790119, - 0.00328316, 0.407218, 0.00637039, 0.790126, - 0.00512988, 0.40722, 0.0099539, 0.79013, - 0.00738684, 0.407221, 0.0143339, 0.790135, - 0.0100538, 0.407221, 0.0195107, 0.790134, - 0.0131306, 0.407217, 0.0254848, 0.79016, - 0.0166169, 0.407224, 0.0322572, 0.790197, - 0.020512, 0.407236, 0.0398284, 0.790273, - 0.0248157, 0.407263, 0.0482014, 0.790381, - 0.029527, 0.407304, 0.0573777, 0.790521, - 0.0346446, 0.407355, 0.0673602, 0.790704, - 0.0401665, 0.40742, 0.0781522, 0.790925, - 0.0460896, 0.407499, 0.0897582, 0.791195, - 0.0524017, 0.407589, 0.10218, 0.791522, - 0.0590121, 0.407691, 0.11541, 0.791878, - 0.0654876, 0.407748, 0.12939, 0.792361, - 0.0725207, 0.407849, 0.144237, 0.792942, - 0.0799844, 0.407963, 0.159924, 0.79362, - 0.0877896, 0.408087, 0.176425, 0.794529, - 0.0958451, 0.408259, 0.193733, 0.795521, - 0.103827, 0.408362, 0.211756, 0.796778, - 0.111937, 0.408482, 0.230524, 0.798027, - 0.120521, 0.408547, 0.249967, 0.799813, - 0.129242, 0.408721, 0.269926, 0.802387, - 0.138048, 0.409148, 0.290338, 0.805279, - 0.147301, 0.409641, 0.311193, 0.809251, - 0.155895, 0.410154, 0.333611, 0.813733, - 0.163942, 0.410297, 0.357615, 0.819081, - 0.171666, 0.410373, 0.382339, 0.825427, - 0.178905, 0.410348, 0.407828, 0.83172, - 0.185812, 0.409486, 0.434034, 0.83877, - 0.192318, 0.408776, 0.460493, 0.845817, - 0.198249, 0.407176, 0.487346, 0.854664, - 0.204034, 0.405719, 0.514832, 0.863495, - 0.208908, 0.403282, 0.542401, 0.871883, - 0.212765, 0.399293, 0.570683, 0.88065, - 0.214911, 0.393803, 0.599947, 0.89004, - 0.216214, 0.387536, 0.62932, 0.898476, - 0.216745, 0.379846, 0.658319, 0.906738, - 0.216387, 0.370625, 0.687138, 0.914844, - 0.215053, 0.360139, 0.71601, 0.923877, - 0.212007, 0.348849, 0.745124, 0.931925, - 0.207481, 0.335639, 0.773366, 0.938054, - 0.202418, 0.320798, 0.801636, 0.943895, - 0.196507, 0.304772, 0.829055, 0.949468, - 0.189009, 0.288033, 0.856097, 0.955152, - 0.180539, 0.270532, 0.88301, 0.959403, - 0.171437, 0.251639, 0.909296, 0.963309, - 0.161661, 0.232563, 0.934868, 0.967399, - 0.150425, 0.213231, 0.959662, 0.972009, - 0.138659, 0.194247, 0.98302, 0.97433, - 0.126595, 0.174718, 1.00517, 0.975823, - 0.113205, 0.155518, 1.02566, 0.976371, - 0.0996096, 0.136709, 1.04418, 0.978705, - 0.0860754, 0.117571, 1.06146, 0.981477, - 0.0714438, 0.0980046, 1.07777, 0.984263, - 0.0572304, 0.0782181, 1.09214, 0.988423, - 0.0428875, 0.0584052, 1.10553, 0.993, - 0.0282442, 0.038522, 1.11758, 0.99704, - 0.0140183, 0.0190148, 1.12864, 0.999913, 0.000369494, - 0.000145203, 1.13901, 0.777662, - 8.4153e-06, 0.423844, 1.54403e-05, 0.770458, - 0.000211714, 0.419915, 0.00038845, 0.770716, - 0.000846888, 0.420055, 0.00155386, 0.770982, - 0.00190567, 0.420202, 0.00349653, 0.770981, - 0.00338782, 0.420201, 0.00621606, 0.77098, - 0.00529338, 0.4202, 0.00971274, 0.770983, - 0.00762223, 0.4202, 0.0139867, 0.770985, - 0.0103741, 0.420198, 0.0190381, 0.770996, - 0.0135489, 0.4202, 0.0248677, 0.771029, - 0.0171461, 0.420212, 0.0314764, 0.771052, - 0.0211647, 0.420215, 0.0388648, 0.771131, - 0.0256048, 0.420245, 0.047036, 0.771235, - 0.0304647, 0.420284, 0.0559911, 0.771383, - 0.0357436, 0.420341, 0.0657346, 0.771591, - 0.0414392, 0.420423, 0.0762694, 0.771819, - 0.0475462, 0.420506, 0.0875984, 0.772123, - 0.0540506, 0.420617, 0.099727, 0.772464, - 0.060797, 0.42072, 0.112637, 0.772855, - 0.0675393, 0.420799, 0.126313, 0.773317, - 0.0748323, 0.420893, 0.140824, 0.773981, - 0.0825681, 0.421058, 0.15617, 0.774746, - 0.0906307, 0.421226, 0.172322, 0.77566, - 0.0988982, 0.421397, 0.189253, 0.776837, - 0.106994, 0.421569, 0.206912, 0.778097, - 0.115528, 0.421704, 0.225359, 0.779588, - 0.124317, 0.421849, 0.24447, 0.781574, - 0.133139, 0.422097, 0.264156, 0.784451, - 0.142179, 0.422615, 0.284318, 0.787682, - 0.15165, 0.423269, 0.304902, 0.792433, - 0.160771, 0.424396, 0.3265, 0.797359, - 0.169166, 0.424772, 0.35014, 0.803986, - 0.177149, 0.425475, 0.374768, 0.809504, - 0.184745, 0.424996, 0.399928, 0.815885, - 0.19173, 0.424247, 0.425796, 0.823513, - 0.198525, 0.423515, 0.452287, 0.832549, - 0.204709, 0.422787, 0.479321, 0.841653, - 0.210447, 0.421187, 0.506718, 0.850401, - 0.215501, 0.418519, 0.53432, 0.859854, - 0.219752, 0.414715, 0.56242, 0.869364, - 0.222305, 0.409462, 0.591558, 0.878837, - 0.223744, 0.402926, 0.621074, 0.888636, - 0.224065, 0.395043, 0.650538, 0.898132, - 0.223742, 0.38564, 0.679538, 0.907181, - 0.222308, 0.375378, 0.708674, 0.915621, - 0.219837, 0.363212, 0.737714, 0.9239, - 0.215233, 0.349313, 0.767014, 0.931644, - 0.209592, 0.334162, 0.795133, 0.938887, - 0.203644, 0.317943, 0.823228, 0.945282, - 0.196349, 0.300581, 0.850822, 0.950758, - 0.18742, 0.282195, 0.877594, 0.956146, - 0.177879, 0.262481, 0.904564, 0.960355, - 0.167643, 0.242487, 0.930741, 0.965256, - 0.156671, 0.222668, 0.955868, 0.968029, - 0.144123, 0.201907, 0.979869, 0.97251, - 0.131305, 0.18202, 1.00291, 0.974925, - 0.118335, 0.161909, 1.02392, 0.975402, - 0.103714, 0.142129, 1.0433, 0.976987, - 0.089415, 0.122447, 1.06089, 0.979677, - 0.0748858, 0.102248, 1.07713, 0.983184, - 0.0596086, 0.0814851, 1.09218, 0.987466, - 0.0447671, 0.0609484, 1.10585, 0.992348, - 0.0295217, 0.0401835, 1.11829, 0.996674, - 0.0143917, 0.0198163, 1.12966, 1.00003, 0.000321364, - 0.000149983, 1.1402, 0.757901, - 8.69074e-06, 0.436176, 1.51011e-05, 0.751195, - 0.000217848, 0.432317, 0.000378533, 0.751178, - 0.000871373, 0.432307, 0.0015141, 0.751195, - 0.00196061, 0.432317, 0.0034068, 0.751198, - 0.00348552, 0.432318, 0.00605659, 0.751195, - 0.00544599, 0.432315, 0.00946353, 0.751207, - 0.00784203, 0.43232, 0.013628, 0.751213, - 0.0106732, 0.43232, 0.0185499, 0.751221, - 0.0139393, 0.432319, 0.0242302, 0.751244, - 0.0176398, 0.432325, 0.0306694, 0.7513, - 0.0217743, 0.432348, 0.0378698, 0.751358, - 0.0263412, 0.432367, 0.0458321, 0.751458, - 0.0313396, 0.432404, 0.0545587, 0.751608, - 0.0367682, 0.432464, 0.0640543, 0.7518, - 0.0426246, 0.43254, 0.0743222, 0.752065, - 0.0489031, 0.432645, 0.0853668, 0.752376, - 0.0555828, 0.432762, 0.0971911, 0.752715, - 0.0623861, 0.432859, 0.109768, 0.753137, - 0.069415, 0.432958, 0.123126, 0.753676, - 0.0770039, 0.433099, 0.137308, 0.754345, - 0.084971, 0.433272, 0.15229, 0.755235, - 0.0932681, 0.433504, 0.168075, 0.756186, - 0.10171, 0.433693, 0.184625, 0.757363, - 0.110019, 0.433857, 0.201897, 0.75884, - 0.11887, 0.434102, 0.220014, 0.760467, - 0.127881, 0.434306, 0.238778, 0.762969, - 0.136766, 0.434751, 0.258172, 0.765823, - 0.14612, 0.43529, 0.278062, 0.769676, - 0.15566, 0.436236, 0.298437, 0.774909, - 0.165177, 0.437754, 0.319532, 0.77994, - 0.17402, 0.438343, 0.342505, 0.785757, - 0.182201, 0.438609, 0.366693, 0.792487, - 0.190104, 0.438762, 0.391668, 0.80038, - 0.197438, 0.438795, 0.417494, 0.808494, - 0.204365, 0.438226, 0.443933, 0.817695, - 0.210714, 0.437283, 0.470929, 0.828111, - 0.216651, 0.436087, 0.498569, 0.837901, - 0.221804, 0.433717, 0.526165, 0.847813, - 0.226318, 0.430133, 0.554155, 0.858314, - 0.229297, 0.425213, 0.582822, 0.868891, - 0.230999, 0.418576, 0.612847, 0.878941, - 0.231155, 0.410405, 0.642445, 0.888809, - 0.230935, 0.400544, 0.672024, 0.898089, - 0.229343, 0.389613, 0.701366, 0.908081, - 0.226886, 0.377197, 0.730763, 0.916819, - 0.222676, 0.363397, 0.759642, 0.924968, - 0.216835, 0.347437, 0.788775, 0.932906, - 0.210245, 0.32995, 0.817135, 0.940025, - 0.202992, 0.312262, 0.844912, 0.946101, - 0.19436, 0.293313, 0.872164, 0.952835, - 0.184125, 0.273638, 0.899443, 0.957347, - 0.173657, 0.252385, 0.926389, 0.961434, - 0.162204, 0.231038, 0.951947, 0.965522, - 0.14979, 0.209834, 0.976751, 0.969412, - 0.136307, 0.188821, 1.00022, 0.973902, - 0.122527, 0.168013, 1.02229, 0.974045, - 0.108213, 0.147634, 1.04199, 0.975775, - 0.0927397, 0.12705, 1.06019, 0.978383, - 0.0778212, 0.106309, 1.07711, 0.98211, - 0.0621216, 0.0849279, 1.09245, 0.986517, - 0.0463847, 0.0633519, 1.10651, 0.991696, - 0.0309353, 0.0419698, 1.11903, 0.996349, - 0.0150914, 0.0206272, 1.13073, 1.00003, 0.000442449, - 0.000231396, 1.14146, 0.727498, - 8.85074e-06, 0.441528, 1.45832e-05, 0.730897, - 0.000223525, 0.443589, 0.000368298, 0.730796, - 0.000893996, 0.443528, 0.00147303, 0.730805, - 0.00201149, 0.443533, 0.00331433, 0.730814, - 0.00357596, 0.443538, 0.00589222, 0.730815, - 0.00558734, 0.443538, 0.00920678, 0.730822, - 0.00804544, 0.44354, 0.0132582, 0.730836, - 0.0109501, 0.443545, 0.0180468, 0.730848, - 0.0143008, 0.443546, 0.0235732, 0.730871, - 0.0180969, 0.443552, 0.0298382, 0.730915, - 0.022338, 0.443567, 0.0368438, 0.730982, - 0.0270225, 0.443591, 0.044591, 0.731076, - 0.0321491, 0.443627, 0.0530831, 0.731245, - 0.0377166, 0.443699, 0.0623243, 0.73144, - 0.0437216, 0.443777, 0.0723181, 0.7317, - 0.0501576, 0.443881, 0.0830691, 0.732034, - 0.0569942, 0.444014, 0.0945809, 0.732388, - 0.0638756, 0.444113, 0.106825, 0.732853, - 0.071203, 0.444247, 0.119859, 0.733473, - 0.0790076, 0.444442, 0.13369, 0.734195, - 0.0871937, 0.444645, 0.148304, 0.735069, - 0.095696, 0.444877, 0.163702, 0.736169, - 0.10426, 0.445133, 0.179861, 0.73747, - 0.112853, 0.44537, 0.196778, 0.738991, - 0.12199, 0.445651, 0.214496, 0.740865, - 0.131153, 0.445958, 0.232913, 0.743637, - 0.140245, 0.446548, 0.251977, 0.746797, - 0.149722, 0.447246, 0.271551, 0.751517, - 0.159341, 0.448656, 0.291774, 0.756156, - 0.169106, 0.449866, 0.312455, 0.761519, - 0.178436, 0.450919, 0.334552, 0.768295, - 0.186904, 0.451776, 0.358491, 0.776613, - 0.195117, 0.452832, 0.383446, 0.783966, - 0.202695, 0.45249, 0.408945, 0.793542, - 0.20985, 0.452587, 0.435364, 0.803192, - 0.216403, 0.451852, 0.462336, 0.813892, - 0.22251, 0.450708, 0.48987, 0.824968, - 0.227676, 0.4486, 0.517697, 0.835859, - 0.232443, 0.445156, 0.545975, 0.846825, - 0.235775, 0.440351, 0.574483, 0.858085, - 0.237897, 0.433641, 0.604246, 0.868825, - 0.238074, 0.425354, 0.634101, 0.879638, - 0.237661, 0.415383, 0.664201, 0.889966, - 0.236186, 0.404136, 0.693918, 0.899479, - 0.233599, 0.390917, 0.723481, 0.908769, - 0.229737, 0.376352, 0.75258, 0.917966, - 0.223836, 0.360372, 0.781764, 0.926304, - 0.217067, 0.342551, 0.811139, 0.934626, - 0.209309, 0.324238, 0.839585, 0.941841, - 0.20071, 0.304484, 0.867044, 0.94789, - 0.190602, 0.283607, 0.894579, 0.954196, - 0.179253, 0.262205, 0.921743, 0.958383, - 0.167646, 0.239847, 0.948026, 0.963119, - 0.155073, 0.218078, 0.973296, 0.966941, - 0.141426, 0.195899, 0.998135, 0.970836, - 0.126849, 0.174121, 1.02021, 0.973301, - 0.112296, 0.153052, 1.04085, 0.97448, - 0.0964965, 0.131733, 1.05946, 0.977045, - 0.080489, 0.10997, 1.07693, 0.980751, - 0.064844, 0.0881657, 1.09254, 0.985475, - 0.0481938, 0.0657987, 1.10697, 0.991089, - 0.0319185, 0.0435215, 1.12004, 0.996122, - 0.0158088, 0.0214779, 1.13173, 1.00001, 0.000372455, - 0.000200295, 1.14291, 0.708622, - 9.07597e-06, 0.45304, 1.41962e-05, 0.711162, - 0.000228911, 0.454662, 0.000358052, 0.709812, - 0.000914446, 0.453797, 0.00143034, 0.709865, - 0.00205819, 0.453834, 0.00321935, 0.709864, - 0.00365894, 0.453833, 0.00572331, 0.709855, - 0.00571692, 0.453826, 0.00894278, 0.709862, - 0.00823201, 0.453828, 0.012878, 0.709875, - 0.011204, 0.453832, 0.0175295, 0.709896, - 0.0146323, 0.453839, 0.0228978, 0.709925, - 0.0185163, 0.453847, 0.0289839, 0.709974, - 0.0228551, 0.453866, 0.0357894, 0.710045, - 0.0276473, 0.453892, 0.0433161, 0.710133, - 0.032891, 0.453924, 0.0515665, 0.710292, - 0.0385851, 0.453992, 0.0605458, 0.710485, - 0.0447254, 0.45407, 0.0702574, 0.710769, - 0.0513051, 0.454192, 0.0807077, 0.711106, - 0.0582733, 0.454329, 0.091896, 0.711516, - 0.0652866, 0.45446, 0.103814, 0.712071, - 0.0728426, 0.454653, 0.116508, 0.712676, - 0.0808307, 0.45484, 0.129968, 0.713476, - 0.0892216, 0.455096, 0.144206, 0.714377, - 0.0979047, 0.455346, 0.159212, 0.715579, - 0.106531, 0.455647, 0.174973, 0.716977, - 0.115492, 0.455961, 0.191504, 0.71862, - 0.124821, 0.456315, 0.208835, 0.72084, - 0.134079, 0.4568, 0.226869, 0.723786, - 0.143427, 0.457521, 0.245582, 0.727464, - 0.153061, 0.458475, 0.264957, 0.732771, - 0.162768, 0.460239, 0.284948, 0.736515, - 0.172627, 0.460899, 0.30522, 0.743519, - 0.182487, 0.463225, 0.326717, 0.750041, - 0.191295, 0.464027, 0.350113, 0.758589, - 0.199746, 0.465227, 0.374782, 0.767703, - 0.207584, 0.465877, 0.400226, 0.777484, - 0.214973, 0.465996, 0.426442, 0.788792, - 0.221796, 0.466019, 0.453688, 0.800194, - 0.228038, 0.465083, 0.481246, 0.811234, - 0.233346, 0.462506, 0.509086, 0.822859, - 0.238073, 0.459257, 0.537338, 0.835082, - 0.241764, 0.454863, 0.566108, 0.846332, - 0.244241, 0.448163, 0.595126, 0.858355, - 0.244736, 0.439709, 0.625574, 0.87034, - 0.244278, 0.429837, 0.65617, 0.881027, - 0.24255, 0.418002, 0.686029, 0.891007, - 0.239912, 0.404325, 0.716039, 0.900874, - 0.236133, 0.389222, 0.745518, 0.911072, - 0.230672, 0.373269, 0.775026, 0.920359, - 0.22356, 0.355083, 0.804521, 0.928604, - 0.215591, 0.335533, 0.834045, 0.937175, - 0.206503, 0.315278, 0.861612, 0.942825, - 0.196684, 0.293653, 0.889131, 0.949805, - 0.185116, 0.271503, 0.916853, 0.955535, - 0.172703, 0.248821, 0.943541, 0.959843, - 0.159978, 0.225591, 0.970132, 0.964393, - 0.146375, 0.202719, 0.994709, 0.968008, - 0.131269, 0.179928, 1.0186, 0.971013, - 0.11569, 0.158007, 1.03928, 0.973334, - 0.1003, 0.13624, 1.05887, 0.975775, - 0.0833352, 0.1138, 1.07652, 0.979579, - 0.0668981, 0.0913141, 1.09297, 0.984323, - 0.0500902, 0.0683051, 1.10734, 0.990351, - 0.0332377, 0.0451771, 1.12084, 0.995823, - 0.0161491, 0.0221705, 1.13296, 1.0001, 0.000234083, - 0.000108712, 1.14441, 0.683895, - 9.24677e-06, 0.46015, 1.37429e-05, 0.68833, - 0.000233383, 0.463134, 0.000346865, 0.688368, - 0.000933547, 0.463159, 0.00138748, 0.688367, - 0.00210049, 0.463159, 0.00312187, 0.688369, - 0.00373415, 0.463159, 0.00555004, 0.688377, - 0.00583449, 0.463163, 0.00867216, 0.688386, - 0.00840128, 0.463166, 0.0124884, 0.688398, - 0.0114343, 0.463169, 0.0169993, 0.688418, - 0.0149329, 0.463175, 0.0222054, 0.688453, - 0.0188964, 0.463188, 0.028108, 0.688515, - 0.0233239, 0.463214, 0.0347085, 0.68857, - 0.0282136, 0.463231, 0.0420091, 0.688679, - 0.033564, 0.463276, 0.0500132, 0.688854, - 0.0393733, 0.463356, 0.0587255, 0.689038, - 0.0456354, 0.46343, 0.0681476, 0.689321, - 0.0523433, 0.463553, 0.0782897, 0.689662, - 0.059412, 0.463693, 0.0891501, 0.690188, - 0.0665736, 0.4639, 0.100735, 0.690755, - 0.0743106, 0.464107, 0.113074, 0.691405, - 0.0824722, 0.464329, 0.126161, 0.692198, - 0.0910484, 0.464585, 0.140007, 0.693196, - 0.0998778, 0.464893, 0.154612, 0.69454, - 0.108651, 0.465285, 0.169984, 0.695921, - 0.117855, 0.465596, 0.186106, 0.697749, - 0.12734, 0.466056, 0.203034, 0.700375, - 0.136714, 0.466771, 0.220703, 0.703395, - 0.146386, 0.467579, 0.239062, 0.707904, - 0.156096, 0.469067, 0.258188, 0.711673, - 0.165904, 0.469851, 0.277759, 0.717489, - 0.175812, 0.471815, 0.297935, 0.724051, - 0.185931, 0.47389, 0.318916, 0.731965, - 0.195238, 0.47587, 0.341591, 0.741151, - 0.204021, 0.477523, 0.366062, 0.751416, - 0.212113, 0.478881, 0.391396, 0.761848, - 0.21979, 0.479226, 0.417599, 0.771886, - 0.2267, 0.478495, 0.444401, 0.783998, - 0.232991, 0.477622, 0.472084, 0.796523, - 0.238645, 0.475833, 0.500193, 0.808851, - 0.243396, 0.472568, 0.52865, 0.821191, - 0.247226, 0.467857, 0.557362, 0.834261, - 0.250102, 0.461871, 0.586768, 0.846762, - 0.251056, 0.453543, 0.617085, 0.859867, - 0.250604, 0.443494, 0.647659, 0.871948, - 0.248783, 0.431711, 0.678119, 0.882967, - 0.245855, 0.417911, 0.708399, 0.892826, - 0.242168, 0.401993, 0.738256, 0.90332, - 0.237062, 0.385371, 0.767999, 0.913633, - 0.22997, 0.366837, 0.798191, 0.922774, - 0.221687, 0.346372, 0.827756, 0.931371, - 0.212345, 0.325682, 0.856425, 0.938929, - 0.20206, 0.303665, 0.884299, 0.944821, - 0.190981, 0.280786, 0.912023, 0.951792, - 0.178065, 0.2573, 0.939669, 0.957712, - 0.164634, 0.233448, 0.96655, 0.961912, - 0.150863, 0.209504, 0.992366, 0.966382, - 0.13577, 0.18597, 1.01633, 0.969588, - 0.119593, 0.162905, 1.03843, 0.971777, - 0.103203, 0.14053, 1.05841, 0.97433, - 0.0865888, 0.117909, 1.07632, 0.978686, - 0.0690829, 0.0944101, 1.09326, 0.983281, - 0.0516568, 0.0705671, 1.10796, 0.989562, - 0.034558, 0.0468592, 1.12182, 0.995465, - 0.0167808, 0.0229846, 1.1342, 0.999991, 0.000373016, - 0.000235606, 1.1459, 0.662251, - 9.39016e-06, 0.468575, 1.32714e-05, 0.666634, - 0.000237624, 0.471675, 0.000335842, 0.666411, - 0.000950385, 0.471516, 0.00134321, 0.666399, - 0.00213833, 0.471509, 0.00302221, 0.666386, - 0.0038014, 0.471499, 0.00537283, 0.666405, - 0.00593958, 0.471511, 0.00839533, 0.666406, - 0.00855253, 0.471508, 0.0120898, 0.666428, - 0.0116401, 0.471519, 0.0164569, 0.666444, - 0.0152015, 0.471522, 0.0214971, 0.66649, - 0.0192362, 0.471543, 0.027212, 0.666537, - 0.0237428, 0.471558, 0.033603, 0.666617, - 0.0287198, 0.471591, 0.0406728, 0.666718, - 0.0341647, 0.471631, 0.0484238, 0.666889, - 0.0400759, 0.47171, 0.0568621, 0.667104, - 0.0464479, 0.471805, 0.0659915, 0.667374, - 0.0532677, 0.471923, 0.0758178, 0.667772, - 0.0603805, 0.472098, 0.0863425, 0.668371, - 0.0677392, 0.472363, 0.0975917, 0.668971, - 0.0756028, 0.472596, 0.109567, 0.669696, - 0.0839293, 0.472869, 0.122272, 0.670481, - 0.0926683, 0.473126, 0.135718, 0.6715, - 0.1016, 0.473442, 0.149914, 0.672911, - 0.110566, 0.47389, 0.164882, 0.674512, - 0.119984, 0.474354, 0.180602, 0.67651, - 0.129574, 0.474922, 0.19711, 0.679292, - 0.139106, 0.475764, 0.214371, 0.682798, - 0.148993, 0.476886, 0.232405, 0.686955, - 0.158737, 0.478179, 0.251153, 0.691406, - 0.168754, 0.479432, 0.270436, 0.697438, - 0.178703, 0.481481, 0.290374, 0.704761, - 0.188955, 0.484143, 0.311044, 0.713599, - 0.198814, 0.487007, 0.333003, 0.723194, - 0.207869, 0.488962, 0.357144, 0.732601, - 0.216189, 0.489815, 0.382169, 0.744193, - 0.22398, 0.490888, 0.408227, 0.754907, - 0.231156, 0.490355, 0.434928, 0.767403, - 0.23747, 0.489548, 0.462599, 0.78107, - 0.243503, 0.488274, 0.490908, 0.793893, - 0.248114, 0.484843, 0.519421, 0.807296, - 0.25222, 0.4803, 0.548561, 0.820529, - 0.255265, 0.474097, 0.577772, 0.833716, - 0.256741, 0.466041, 0.607782, 0.848403, - 0.25637, 0.456547, 0.638807, 0.860755, - 0.254804, 0.443946, 0.670058, 0.874012, - 0.251834, 0.430852, 0.700749, 0.885619, - 0.247867, 0.414903, 0.731446, 0.896069, - 0.242634, 0.397276, 0.761191, 0.906266, - 0.236093, 0.378535, 0.791053, 0.916759, - 0.227543, 0.358038, 0.821298, 0.92523, - 0.21783, 0.335705, 0.850747, 0.93436, - 0.207534, 0.313797, 0.879258, 0.941631, - 0.195983, 0.289671, 0.907734, 0.947564, - 0.183567, 0.265319, 0.935206, 0.953681, - 0.169345, 0.240815, 0.962739, 0.960008, - 0.154909, 0.216119, 0.989227, 0.964145, - 0.140161, 0.192096, 1.01465, 0.968171, - 0.123411, 0.167855, 1.03737, 0.969859, - 0.106525, 0.144817, 1.05767, 0.972666, - 0.0891023, 0.12149, 1.0761, 0.977055, - 0.0718094, 0.0975306, 1.09336, 0.982527, - 0.0534213, 0.0730217, 1.10878, 0.989001, - 0.0355579, 0.0483366, 1.12285, 0.99512, - 0.0176383, 0.023938, 1.13548, 1.00007, 0.000368831, - 0.000211581, 1.14744, 0.651047, - 9.60845e-06, 0.484101, 1.2922e-05, 0.644145, - 0.000241347, 0.478968, 0.000324578, 0.64396, - 0.000965142, 0.478831, 0.00129798, 0.64396, - 0.00217154, 0.47883, 0.00292046, 0.643968, - 0.00386049, 0.478835, 0.00519202, 0.643974, - 0.00603186, 0.478838, 0.0081128, 0.643977, - 0.0086854, 0.478836, 0.011683, 0.643982, - 0.0118207, 0.478834, 0.0159031, 0.644024, - 0.0154374, 0.478856, 0.0207743, 0.644059, - 0.0195343, 0.478868, 0.0262975, 0.644122, - 0.0241103, 0.478896, 0.0324747, 0.644207, - 0.0291638, 0.478933, 0.039309, 0.64432, - 0.0346919, 0.478981, 0.0468029, 0.644481, - 0.0406919, 0.479053, 0.0549614, 0.644722, - 0.047159, 0.479169, 0.0637909, 0.645013, - 0.0540748, 0.479302, 0.0732974, 0.645503, - 0.0612001, 0.479541, 0.0834898, 0.646117, - 0.0687303, 0.479829, 0.0943873, 0.646707, - 0.0767846, 0.480061, 0.105991, 0.647431, - 0.0852465, 0.480343, 0.11831, 0.64831, - 0.0940719, 0.48066, 0.131348, 0.649486, - 0.103056, 0.481083, 0.14514, 0.650864, - 0.112261, 0.481528, 0.159676, 0.652604, - 0.121852, 0.482102, 0.174979, 0.654825, - 0.131505, 0.482813, 0.191079, 0.657876, - 0.141189, 0.483876, 0.207927, 0.661339, - 0.151239, 0.48499, 0.225586, 0.665463, - 0.161091, 0.486279, 0.243947, 0.670542, - 0.171235, 0.487968, 0.262957, 0.677361, - 0.181347, 0.49053, 0.282781, 0.685672, - 0.191679, 0.493862, 0.303311, 0.694551, - 0.201781, 0.49699, 0.324607, 0.703753, - 0.211164, 0.498884, 0.347916, 0.713703, - 0.219675, 0.500086, 0.372628, 0.725911, - 0.227836, 0.501554, 0.398694, 0.73862, - 0.23533, 0.502193, 0.425529, 0.752118, - 0.241786, 0.501811, 0.453209, 0.76579, - 0.247865, 0.500185, 0.481381, 0.779568, - 0.252696, 0.497159, 0.51011, 0.793991, - 0.256802, 0.492765, 0.539322, 0.808182, - 0.259942, 0.486827, 0.569078, 0.821698, - 0.261703, 0.478386, 0.598818, 0.836009, - 0.262006, 0.468772, 0.629762, 0.849824, - 0.260333, 0.456352, 0.661366, 0.863888, - 0.257398, 0.442533, 0.69295, 0.876585, - 0.253264, 0.426573, 0.723608, 0.888665, - 0.248026, 0.408964, 0.754378, 0.899537, - 0.241487, 0.389677, 0.784761, 0.9094, - 0.233463, 0.368516, 0.814688, 0.920166, - 0.223397, 0.346624, 0.845009, 0.928899, - 0.21255, 0.322717, 0.874431, 0.937156, - 0.200869, 0.298698, 0.902922, 0.943861, - 0.188387, 0.273491, 0.931356, 0.949557, - 0.174341, 0.247866, 0.958854, 0.955862, - 0.158994, 0.222496, 0.986098, 0.961721, - 0.143664, 0.197522, 1.01229, 0.965976, - 0.127412, 0.17302, 1.03571, 0.968652, - 0.109798, 0.148954, 1.05699, 0.971084, - 0.0916787, 0.125044, 1.07587, 0.975584, - 0.0739634, 0.100577, 1.09372, 0.98122, - 0.055322, 0.0753666, 1.10948, 0.988253, - 0.0366825, 0.0498899, 1.12394, 0.99482, - 0.0180389, 0.024611, 1.13694, 1.00001, 0.000229839, - 0.000188283, 1.14919, 0.613867, - 9.64198e-06, 0.479449, 1.23452e-05, 0.621485, - 0.000244534, 0.485399, 0.000313091, 0.621429, - 0.000978202, 0.485353, 0.00125245, 0.62112, - 0.00220004, 0.485114, 0.00281687, 0.621119, - 0.0039111, 0.485112, 0.00500783, 0.621122, - 0.00611091, 0.485112, 0.00782498, 0.621133, - 0.00879922, 0.485117, 0.0112687, 0.621152, - 0.0119756, 0.485125, 0.0153394, 0.621183, - 0.0156396, 0.485139, 0.0200382, 0.621227, - 0.0197898, 0.485158, 0.0253663, 0.621298, - 0.0244253, 0.485192, 0.0313261, 0.621388, - 0.0295441, 0.485233, 0.0379204, 0.621507, - 0.0351432, 0.485286, 0.0451523, 0.621693, - 0.0412198, 0.485378, 0.0530277, 0.621933, - 0.0477673, 0.485495, 0.0615522, 0.622232, - 0.0547574, 0.485635, 0.0707316, 0.622809, - 0.0619417, 0.485943, 0.0805883, 0.623407, - 0.069625, 0.486232, 0.0911267, 0.62406, - 0.077796, 0.486516, 0.102354, 0.624835, - 0.0863731, 0.486838, 0.114279, 0.625758, - 0.095251, 0.487188, 0.126902, 0.627043, - 0.104299, 0.487695, 0.140285, 0.628438, - 0.113724, 0.488163, 0.154397, 0.630325, - 0.123417, 0.488858, 0.169267, 0.632801, - 0.133137, 0.489754, 0.184941, 0.635784, - 0.143052, 0.490815, 0.20136, 0.639406, - 0.153132, 0.492048, 0.218643, 0.643872, - 0.163143, 0.49363, 0.236615, 0.6499, - 0.17333, 0.496009, 0.255449, 0.657201, - 0.183622, 0.498994, 0.275006, 0.666221, - 0.194019, 0.502888, 0.295354, 0.674419, - 0.204192, 0.505459, 0.316244, 0.683729, - 0.21406, 0.507771, 0.33849, 0.695584, - 0.222854, 0.510245, 0.363166, 0.708583, - 0.231315, 0.512293, 0.389071, 0.721233, - 0.238911, 0.512747, 0.415737, 0.735134, - 0.245657, 0.512482, 0.443331, 0.750179, - 0.251879, 0.511526, 0.471891, 0.765073, - 0.256911, 0.508935, 0.500892, 0.779794, - 0.261144, 0.504341, 0.530294, 0.794801, - 0.264316, 0.498515, 0.560144, 0.810339, - 0.266276, 0.491015, 0.590213, 0.824818, - 0.266981, 0.481126, 0.620865, 0.839375, - 0.265778, 0.468685, 0.652687, 0.853043, - 0.262748, 0.453925, 0.684759, 0.867335, - 0.258474, 0.437912, 0.716209, 0.88037, - 0.253187, 0.419648, 0.747508, 0.891711, - 0.246476, 0.39982, 0.77797, 0.902896, - 0.238735, 0.37879, 0.808586, 0.913601, - 0.22885, 0.355891, 0.838843, 0.923019, - 0.217656, 0.331773, 0.869014, 0.933432, - 0.205539, 0.307356, 0.898512, 0.939691, - 0.192595, 0.281321, 0.9269, 0.946938, - 0.178945, 0.255441, 0.955297, 0.952372, - 0.163587, 0.229013, 0.983231, 0.95909, - 0.147214, 0.203179, 1.00971, 0.963675, - 0.13064, 0.17792, 1.03438, 0.968247, - 0.113121, 0.152898, 1.05625, 0.97001, - 0.0945824, 0.128712, 1.07598, 0.974458, - 0.0755648, 0.103349, 1.094, 0.980168, - 0.0571998, 0.0776731, 1.1104, 0.987295, - 0.0377994, 0.0514445, 1.12491, 0.994432, - 0.0186417, 0.025429, 1.13851, 0.999975, 0.000542714, - 0.000282356, 1.15108, 0.592656, - 9.80249e-06, 0.486018, 1.19532e-05, 0.598467, - 0.000247275, 0.490781, 0.000301531, 0.597934, - 0.000988317, 0.490343, 0.00120517, 0.597903, - 0.00222366, 0.490319, 0.0027116, 0.597913, - 0.00395315, 0.490327, 0.00482077, 0.597919, - 0.00617653, 0.490329, 0.00753264, 0.597936, - 0.00889375, 0.490339, 0.0108478, 0.597956, - 0.0121043, 0.490347, 0.0147668, 0.597992, - 0.0158073, 0.490365, 0.0192905, 0.598032, - 0.0200017, 0.490382, 0.0244204, 0.598109, - 0.0246865, 0.49042, 0.0301593, 0.598215, - 0.0298594, 0.490474, 0.03651, 0.59833, - 0.0355167, 0.490524, 0.0434757, 0.598525, - 0.0416559, 0.490624, 0.0510629, 0.598778, - 0.0482692, 0.490753, 0.0592781, 0.599135, - 0.0553114, 0.49094, 0.0681304, 0.599802, - 0.062542, 0.491328, 0.0776467, 0.600361, - 0.0703638, 0.491598, 0.0878184, 0.60101, - 0.0786256, 0.491882, 0.0986573, 0.601811, - 0.0872962, 0.492232, 0.11018, 0.602861, - 0.0962284, 0.492684, 0.1224, 0.604167, - 0.10538, 0.493213, 0.135354, 0.605693, - 0.114896, 0.493799, 0.149034, 0.607682, - 0.124654, 0.494576, 0.163469, 0.610672, - 0.13456, 0.4959, 0.178747, 0.613313, - 0.144581, 0.496713, 0.194723, 0.617603, - 0.154703, 0.498499, 0.211617, 0.622174, - 0.16489, 0.500188, 0.229183, 0.628855, - 0.175164, 0.503072, 0.247786, 0.636963, - 0.185565, 0.506798, 0.267116, 0.644866, - 0.195911, 0.509719, 0.28702, 0.653741, - 0.206104, 0.512776, 0.307763, 0.664942, - 0.216447, 0.516812, 0.329631, 0.67633, - 0.22552, 0.519181, 0.353515, 0.690012, - 0.234316, 0.521681, 0.379226, 0.704243, - 0.242032, 0.523129, 0.405901, 0.719396, - 0.249172, 0.523768, 0.433585, 0.734471, - 0.255543, 0.522541, 0.462085, 0.750539, - 0.260697, 0.520217, 0.491233, 0.766365, - 0.26501, 0.516293, 0.521094, 0.781677, - 0.268409, 0.509708, 0.551014, 0.797132, - 0.270399, 0.501944, 0.581463, 0.812655, - 0.271247, 0.492025, 0.612402, 0.828592, - 0.270708, 0.480424, 0.643798, 0.844044, - 0.268085, 0.465955, 0.67682, 0.857305, - 0.263459, 0.448425, 0.708496, 0.87114, - 0.258151, 0.430243, 0.74046, 0.884936, - 0.251171, 0.410578, 0.771583, 0.895772, - 0.243305, 0.38862, 0.802234, 0.906961, - 0.234037, 0.365214, 0.833179, 0.917775, - 0.222714, 0.34116, 0.86353, 0.927883, - 0.210175, 0.31572, 0.893557, 0.936617, - 0.196925, 0.289159, 0.922976, 0.943384, - 0.182788, 0.261996, 0.951606, 0.949713, - 0.167965, 0.235324, 0.979958, 0.955818, - 0.151109, 0.208408, 1.00765, 0.961344, - 0.133834, 0.182591, 1.03329, 0.965469, - 0.115987, 0.156958, 1.0557, 0.968693, - 0.09746, 0.132239, 1.07583, 0.973165, - 0.0778514, 0.106195, 1.09451, 0.979387, - 0.0585067, 0.0797669, 1.11137, 0.98671, - 0.0390409, 0.0530263, 1.12643, 0.994093, - 0.019408, 0.0263163, 1.14016, 1.00002, 0.000540029, - 0.000194487, 1.15299, 0.574483, - 9.89066e-06, 0.494533, 1.14896e-05, 0.574478, - 0.000249127, 0.494528, 0.000289403, 0.574607, - 0.000996811, 0.494637, 0.00115797, 0.574396, - 0.00224241, 0.494458, 0.00260498, 0.574377, - 0.00398632, 0.49444, 0.00463102, 0.574386, - 0.00622836, 0.494445, 0.00723623, 0.574401, - 0.0089683, 0.494453, 0.010421, 0.574419, - 0.0122056, 0.49446, 0.0141859, 0.574459, - 0.0159396, 0.494481, 0.0185322, 0.574525, - 0.0201692, 0.49452, 0.0234617, 0.574587, - 0.0248924, 0.494547, 0.0289762, 0.574697, - 0.0301074, 0.494604, 0.0350797, 0.574853, - 0.0358114, 0.494688, 0.0417767, 0.575027, - 0.041999, 0.494772, 0.0490718, 0.575294, - 0.0486618, 0.494915, 0.0569728, 0.575733, - 0.0557148, 0.495173, 0.0654955, 0.576356, - 0.0630489, 0.495537, 0.0746612, 0.576944, - 0.0709285, 0.495836, 0.0844615, 0.57765, - 0.0792723, 0.496177, 0.0949142, 0.578491, - 0.0880167, 0.496563, 0.10603, 0.579639, - 0.0969462, 0.497096, 0.117841, 0.580989, - 0.10622, 0.497684, 0.130367, 0.582587, - 0.115861, 0.498337, 0.143609, 0.584951, - 0.125605, 0.499414, 0.157625, 0.587602, - 0.135608, 0.500518, 0.172413, 0.59076, - 0.145742, 0.501767, 0.187999, 0.594992, - 0.155934, 0.503542, 0.20445, 0.600656, - 0.166303, 0.506135, 0.221764, 0.607816, - 0.176681, 0.509542, 0.24002, 0.61522, - 0.187071, 0.51263, 0.258992, 0.623702, - 0.197465, 0.516021, 0.278773, 0.634192, - 0.207816, 0.520422, 0.299377, 0.644936, - 0.218183, 0.524073, 0.320802, 0.657888, - 0.2278, 0.528049, 0.34384, 0.670666, - 0.236747, 0.52986, 0.36916, 0.685626, - 0.24484, 0.531892, 0.395867, 0.701304, - 0.252071, 0.532727, 0.423488, 0.717727, - 0.258714, 0.532146, 0.452201, 0.733914, - 0.264211, 0.529883, 0.481579, 0.750529, - 0.26859, 0.5259, 0.511558, 0.76747, - 0.272046, 0.51999, 0.542042, 0.785189, - 0.274225, 0.513083, 0.572799, 0.800954, - 0.275189, 0.502936, 0.603816, 0.816962, - 0.274946, 0.490921, 0.635461, 0.83336, - 0.272695, 0.47684, 0.6676, 0.848143, - 0.268223, 0.459405, 0.70051, 0.861818, - 0.262768, 0.440319, 0.732902, 0.876828, - 0.255872, 0.420123, 0.765084, 0.889312, - 0.247703, 0.398379, 0.796391, 0.900412, - 0.238381, 0.374496, 0.827333, 0.912251, - 0.227783, 0.349874, 0.858385, 0.921792, - 0.214832, 0.323181, 0.888652, 0.931273, - 0.200949, 0.296624, 0.917763, 0.940295, - 0.186537, 0.269211, 0.947878, 0.946812, - 0.171538, 0.241447, 0.977016, 0.953588, - 0.155254, 0.213829, 1.00501, 0.958841, - 0.137156, 0.186807, 1.03179, 0.963746, - 0.118699, 0.160706, 1.05502, 0.966468, - 0.0998358, 0.135504, 1.07568, 0.971178, - 0.0805186, 0.109131, 1.09479, 0.97831, - 0.0599348, 0.0818293, 1.1123, 0.985886, - 0.0399661, 0.0545872, 1.12771, 0.994021, - 0.0198682, 0.0269405, 1.14186, 1.00009, 0.000271022, - 0.00012989, 1.15514, 0.538716, - 9.90918e-06, 0.486732, 1.09675e-05, 0.550656, - 0.000250642, 0.497518, 0.000277412, 0.55057, - 0.00100265, 0.497441, 0.00110974, 0.550903, - 0.00225672, 0.497733, 0.00249779, 0.550568, - 0.00401046, 0.497438, 0.00443906, 0.550574, - 0.00626613, 0.49744, 0.00693637, 0.550591, - 0.0090226, 0.497449, 0.00998921, 0.550623, - 0.0122795, 0.497469, 0.0135984, 0.550667, - 0.0160361, 0.497495, 0.0177654, 0.550724, - 0.0202908, 0.497526, 0.0224915, 0.550792, - 0.0250421, 0.497557, 0.0277795, 0.550918, - 0.0302878, 0.49763, 0.0336334, 0.551058, - 0.0360241, 0.497701, 0.0400573, 0.551276, - 0.0422473, 0.497824, 0.0470585, 0.551551, - 0.0489441, 0.497977, 0.0546433, 0.552074, - 0.0559596, 0.498312, 0.0628367, 0.552681, - 0.0633978, 0.498679, 0.071646, 0.553324, - 0.0713176, 0.499031, 0.0810746, 0.554011, - 0.0797268, 0.499365, 0.091129, 0.55488, - 0.0885238, 0.499779, 0.101837, 0.556171, - 0.0974417, 0.500444, 0.113239, 0.557498, - 0.106841, 0.501025, 0.125316, 0.559299, - 0.116533, 0.501864, 0.138128, 0.561647, - 0.126298, 0.502967, 0.151695, 0.564347, - 0.136388, 0.504129, 0.16604, 0.567863, - 0.146576, 0.505713, 0.181207, 0.572569, - 0.156832, 0.507953, 0.197259, 0.578919, - 0.167323, 0.511186, 0.214258, 0.585387, - 0.177712, 0.514042, 0.232038, 0.593134, - 0.188184, 0.517484, 0.250733, 0.603295, - 0.198717, 0.522345, 0.270454, 0.613854, - 0.209177, 0.526751, 0.290807, 0.626092, - 0.219644, 0.531595, 0.312202, 0.637868, - 0.229494, 0.534721, 0.334435, 0.652458, - 0.238718, 0.538304, 0.359184, 0.666985, - 0.247061, 0.539875, 0.385637, 0.683301, - 0.254652, 0.541042, 0.41328, 0.69998, - 0.261376, 0.540735, 0.441903, 0.717824, - 0.267085, 0.539139, 0.471609, 0.734617, - 0.271465, 0.534958, 0.501446, 0.753663, - 0.27528, 0.53032, 0.532571, 0.770512, - 0.277617, 0.522134, 0.563641, 0.787356, - 0.278525, 0.51206, 0.595067, 0.806252, - 0.278512, 0.50119, 0.627226, 0.822061, - 0.277023, 0.486791, 0.659402, 0.838959, - 0.273175, 0.470467, 0.692874, 0.85379, - 0.267238, 0.450688, 0.725702, 0.868268, - 0.260327, 0.429741, 0.75832, 0.881994, - 0.251946, 0.407223, 0.790189, 0.893885, - 0.242432, 0.383214, 0.821625, 0.905118, - 0.231904, 0.357297, 0.853011, 0.916045, - 0.219545, 0.330733, 0.883773, 0.927614, - 0.205378, 0.303916, 0.914435, 0.936005, - 0.190388, 0.275941, 0.944502, 0.944533, - 0.1749, 0.247493, 0.974439, 0.950758, - 0.158588, 0.218996, 1.00286, 0.957078, - 0.141027, 0.191559, 1.0304, 0.962448, - 0.121507, 0.164457, 1.05466, 0.964993, - 0.102068, 0.138636, 1.0761, 0.970017, - 0.0822598, 0.111861, 1.09541, 0.97661, - 0.062033, 0.0843438, 1.11317, 0.985073, - 0.0409832, 0.0558496, 1.12911, 0.993515, - 0.020146, 0.0275331, 1.1438, 1.00006, 0.00027329, - 0.000107883, 1.15736, 0.525324, - 9.99341e-06, 0.498153, 1.05385e-05, 0.526513, - 0.000251605, 0.499277, 0.000265329, 0.526517, - 0.00100641, 0.499282, 0.0010613, 0.526588, - 0.00226466, 0.499337, 0.00238823, 0.526539, - 0.0040255, 0.499302, 0.00424535, 0.526547, - 0.00628954, 0.499306, 0.00663364, 0.526561, - 0.00905628, 0.499313, 0.00955337, 0.526593, - 0.0123253, 0.499334, 0.0130054, 0.526642, - 0.0160957, 0.499365, 0.0169911, 0.5267, - 0.0203661, 0.499396, 0.0215122, 0.526792, - 0.0251347, 0.499451, 0.0265718, 0.526904, - 0.0303985, 0.499511, 0.0321732, 0.527079, - 0.0361554, 0.499617, 0.0383231, 0.527285, - 0.0423982, 0.499731, 0.045026, 0.527602, - 0.0491121, 0.499924, 0.0522936, 0.528166, - 0.0561127, 0.500306, 0.0601528, 0.52879, - 0.0635988, 0.5007, 0.0686059, 0.529421, - 0.071581, 0.501048, 0.0776518, 0.530144, - 0.0799854, 0.501421, 0.0873148, 0.531062, - 0.0888032, 0.501884, 0.0976084, 0.532374, - 0.0977643, 0.50259, 0.108588, 0.533828, - 0.107197, 0.50329, 0.120234, 0.53581, - 0.116887, 0.504312, 0.132602, 0.538063, - 0.126755, 0.505365, 0.145721, 0.5409, - 0.136819, 0.506668, 0.159617, 0.544882, - 0.147117, 0.508731, 0.174369, 0.550238, - 0.157446, 0.511601, 0.190028, 0.556038, - 0.167988, 0.514431, 0.206587, 0.563031, - 0.178364, 0.517808, 0.224046, 0.571543, - 0.189007, 0.521937, 0.242503, 0.582255, - 0.199546, 0.527415, 0.261977, 0.59272, - 0.210084, 0.531682, 0.282162, 0.605648, - 0.220448, 0.537123, 0.303426, 0.61785, - 0.230593, 0.540664, 0.325323, 0.632223, - 0.240238, 0.544467, 0.348993, 0.648819, - 0.24887, 0.547594, 0.375462, 0.665825, - 0.256657, 0.54912, 0.403024, 0.683389, - 0.263711, 0.549294, 0.431773, 0.701495, - 0.269666, 0.547649, 0.461494, 0.719197, - 0.274169, 0.543786, 0.491623, 0.737906, - 0.278124, 0.538644, 0.522994, 0.756652, - 0.280632, 0.531057, 0.554775, 0.775279, - 0.281741, 0.521972, 0.586441, 0.792688, - 0.281652, 0.509613, 0.618596, 0.811894, - 0.280345, 0.496497, 0.651462, 0.827938, - 0.277128, 0.47968, 0.684023, 0.844837, - 0.271646, 0.460688, 0.718024, 0.859239, - 0.264397, 0.438872, 0.751207, 0.874088, - 0.256144, 0.41577, 0.784232, 0.887693, - 0.246311, 0.391369, 0.816191, 0.899402, - 0.235497, 0.365872, 0.847828, 0.910973, - 0.223631, 0.338618, 0.87934, 0.92204, - 0.209874, 0.310803, 0.910325, 0.930987, - 0.194265, 0.281802, 0.940695, 0.94, - 0.178125, 0.252836, 0.970958, 0.948018, - 0.161479, 0.224239, 1.00078, 0.955141, - 0.144038, 0.195857, 1.0288, 0.960513, - 0.124915, 0.168487, 1.05371, 0.963964, - 0.104284, 0.141495, 1.07596, 0.968713, - 0.0838732, 0.114437, 1.09628, 0.975524, - 0.0635579, 0.0863105, 1.11448, 0.98431, - 0.042291, 0.0574774, 1.13069, 0.992916, - 0.0209131, 0.0284343, 1.14568, 0.999926, 0.000743097, - 0.000379265, 1.15955, 0.501042, - 9.98428e-06, 0.498726, 1.00306e-05, 0.502992, - 0.000252112, 0.500665, 0.000253283, 0.502417, - 0.00100791, 0.500092, 0.00101259, 0.502965, - 0.00226919, 0.500621, 0.00227978, 0.502318, - 0.00403109, 0.499994, 0.00405011, 0.502333, - 0.00629832, 0.500005, 0.00632868, 0.502362, - 0.00906907, 0.500027, 0.00911446, 0.502369, - 0.0123423, 0.500023, 0.0124078, 0.50243, - 0.0161178, 0.500066, 0.016211, 0.502493, - 0.0203937, 0.500103, 0.0205256, 0.502592, - 0.0251684, 0.500166, 0.0253548, 0.502707, - 0.0304389, 0.50023, 0.0307029, 0.502881, - 0.0362015, 0.500335, 0.0365753, 0.503124, - 0.0424507, 0.500488, 0.0429798, 0.503443, - 0.0491582, 0.500686, 0.0499268, 0.504083, - 0.0561476, 0.501155, 0.0574541, 0.504668, - 0.0636846, 0.501524, 0.0655408, 0.505319, - 0.0716834, 0.501904, 0.0742072, 0.50609, - 0.0800925, 0.502321, 0.0834699, 0.507122, - 0.0888425, 0.502896, 0.0933603, 0.508414, - 0.097855, 0.503603, 0.10391, 0.509955, - 0.107304, 0.504416, 0.115113, 0.512061, - 0.116921, 0.505565, 0.127054, 0.514419, - 0.12689, 0.506732, 0.139709, 0.517529, - 0.136934, 0.508338, 0.153173, 0.522085, - 0.147327, 0.510987, 0.167528, 0.526986, - 0.157612, 0.513527, 0.182708, 0.533122, - 0.168213, 0.516717, 0.198881, 0.540807, - 0.178688, 0.520832, 0.215986, 0.550687, - 0.189511, 0.52632, 0.234335, 0.560567, - 0.199998, 0.531009, 0.253375, 0.571698, - 0.210652, 0.535839, 0.273499, 0.584364, - 0.220917, 0.541091, 0.294355, 0.599066, - 0.23137, 0.546875, 0.316525, 0.614148, - 0.241206, 0.551306, 0.339671, 0.631157, - 0.250379, 0.555187, 0.36531, 0.647919, - 0.258397, 0.556595, 0.392767, 0.666112, - 0.265528, 0.556949, 0.421397, 0.686158, - 0.271827, 0.556617, 0.451433, 0.704838, - 0.27674, 0.552975, 0.482131, 0.723957, - 0.280733, 0.547814, 0.513458, 0.74262, - 0.283359, 0.53997, 0.545446, 0.762009, - 0.284541, 0.530422, 0.57775, 0.781314, - 0.284507, 0.518546, 0.610434, 0.799116, - 0.283309, 0.504178, 0.643178, 0.817604, - 0.280378, 0.48843, 0.676248, 0.83459, - 0.275619, 0.469457, 0.709698, 0.850974, - 0.26856, 0.447698, 0.744245, 0.866747, - 0.260094, 0.424791, 0.777695, 0.881412, - 0.249929, 0.399913, 0.810392, 0.8936, - 0.239137, 0.37308, 0.842872, 0.905943, - 0.226818, 0.345705, 0.874677, 0.916408, - 0.213699, 0.31706, 0.906257, 0.927215, - 0.198428, 0.288444, 0.936881, 0.935625, - 0.181643, 0.258329, 0.96795, 0.944076, - 0.164386, 0.228488, 0.998216, 0.951229, - 0.146339, 0.199763, 1.02689, 0.958793, - 0.127709, 0.172153, 1.0535, 0.963219, - 0.107244, 0.144989, 1.07646, 0.967562, - 0.0857764, 0.11685, 1.09675, 0.974866, - 0.0645377, 0.0880571, 1.11576, 0.983353, - 0.0431732, 0.0587352, 1.13227, 0.992503, - 0.0218356, 0.0294181, 1.1478, 1.00003, 0.000605203, - 0.000231013, 1.16207, 0.482935, - 1.01177e-05, 0.504695, 9.68142e-06, 0.477554, - 0.000251521, 0.499071, 0.000240676, 0.477904, - 0.00100683, 0.499436, 0.00096342, 0.478368, - 0.00226636, 0.499899, 0.0021687, 0.477977, - 0.00402719, 0.499513, 0.00385384, 0.477993, - 0.00629226, 0.499525, 0.0060221, 0.478011, - 0.00906011, 0.499536, 0.00867289, 0.478051, - 0.0123305, 0.499566, 0.0118074, 0.478089, - 0.016102, 0.499587, 0.0154269, 0.478171, - 0.0203736, 0.499645, 0.0195341, 0.478254, - 0.025143, 0.499692, 0.0241318, 0.47839, - 0.0304071, 0.499779, 0.0292247, 0.478588, - 0.0361631, 0.499911, 0.0348196, 0.478812, - 0.0424023, 0.500046, 0.0409231, 0.479208, - 0.0490724, 0.500326, 0.047552, 0.479841, - 0.0560722, 0.500805, 0.0547377, 0.480392, - 0.0636125, 0.501152, 0.0624607, 0.481068, - 0.0716134, 0.501561, 0.0707473, 0.481898, - 0.0800062, 0.502054, 0.0796118, 0.483022, - 0.0886568, 0.502728, 0.0890974, 0.484332, - 0.0977553, 0.503479, 0.0992099, 0.486126, - 0.107173, 0.504546, 0.10999, 0.488066, - 0.11677, 0.50557, 0.121476, 0.490521, - 0.126725, 0.506849, 0.133672, 0.494232, - 0.136793, 0.50911, 0.146731, 0.498302, - 0.147116, 0.511345, 0.160577, 0.503565, - 0.157446, 0.514344, 0.175335, 0.510902, - 0.168121, 0.518824, 0.191207, 0.519263, - 0.178799, 0.523666, 0.208058, 0.528204, - 0.189407, 0.528296, 0.225875, 0.538854, - 0.200145, 0.533724, 0.244782, 0.551278, - 0.210701, 0.539833, 0.264753, 0.565222, - 0.221303, 0.546131, 0.285745, 0.579403, - 0.231688, 0.551496, 0.307592, 0.595469, - 0.241718, 0.556809, 0.330582, 0.610929, - 0.250992, 0.559641, 0.354995, 0.629433, - 0.259602, 0.562379, 0.382471, 0.648504, - 0.267038, 0.563676, 0.411126, 0.66756, - 0.273388, 0.562092, 0.440924, 0.689143, - 0.278788, 0.560807, 0.472118, 0.709056, - 0.282783, 0.555701, 0.503774, 0.729855, - 0.285836, 0.548698, 0.536364, 0.748954, - 0.287078, 0.538544, 0.56895, 0.768373, - 0.287133, 0.526711, 0.601991, 0.78827, - 0.285839, 0.512511, 0.635403, 0.807465, - 0.283238, 0.496323, 0.668797, 0.825194, - 0.27906, 0.477638, 0.702584, 0.842203, - 0.272286, 0.456253, 0.736393, 0.857749, - 0.263854, 0.432412, 0.77096, 0.874799, - 0.253943, 0.407806, 0.80489, 0.887497, - 0.24237, 0.38033, 0.83771, 0.89966, - 0.230278, 0.352446, 0.870376, 0.911753, - 0.21646, 0.323268, 0.902256, 0.923011, - 0.202071, 0.294314, 0.933306, 0.932375, - 0.185519, 0.264104, 0.965177, 0.940537, - 0.167604, 0.234035, 0.996303, 0.948904, - 0.149068, 0.20412, 1.0261, 0.955263, - 0.129539, 0.175431, 1.05304, 0.960303, - 0.109932, 0.148116, 1.07617, 0.965512, - 0.0880572, 0.119693, 1.09742, 0.973466, - 0.0660548, 0.0901619, 1.11721, 0.98284, - 0.0439228, 0.0599875, 1.13436, 0.992216, - 0.0219588, 0.0298975, 1.15006, 0.999946, 0.000119402, - 2.08547e-05, 1.16471, 0.447827, - 1.00414e-05, 0.491543, 9.14833e-06, 0.454778, - 0.000251257, 0.499172, 0.00022891, 0.453519, - 0.00100342, 0.497787, 0.000914184, 0.45357, - 0.00225776, 0.497847, 0.00205701, 0.453578, - 0.00401371, 0.497855, 0.00365705, 0.45357, - 0.00627107, 0.497841, 0.00571453, 0.453598, - 0.00902968, 0.497864, 0.00823019, 0.453627, - 0.0122888, 0.497882, 0.0112049, 0.453684, - 0.0160475, 0.497923, 0.0146405, 0.453764, - 0.0203044, 0.49798, 0.0185394, 0.453866, - 0.0250576, 0.498049, 0.0229054, 0.453996, - 0.0303028, 0.49813, 0.0277424, 0.454196, - 0.0360379, 0.498267, 0.0330587, 0.454457, - 0.0422521, 0.498445, 0.0388613, 0.454926, - 0.0488393, 0.498812, 0.0451767, 0.455525, - 0.0558653, 0.499272, 0.0520153, 0.456074, - 0.0633772, 0.499625, 0.0593754, 0.456752, - 0.0713606, 0.500049, 0.0672751, 0.457648, - 0.07971, 0.500615, 0.0757447, 0.458849, - 0.0883032, 0.501399, 0.0848231, 0.46029, - 0.0974095, 0.502293, 0.0945135, 0.462, - 0.106729, 0.503301, 0.104848, 0.464121, - 0.116354, 0.504533, 0.115884, 0.466889, - 0.126214, 0.506172, 0.127652, 0.470744, - 0.136324, 0.508667, 0.14024, 0.47488, - 0.146595, 0.510995, 0.153673, 0.480845, - 0.157027, 0.514832, 0.168053, 0.488262, - 0.167658, 0.519506, 0.183508, 0.496547, - 0.178343, 0.524347, 0.199948, 0.506254, - 0.188916, 0.52983, 0.217503, 0.517961, - 0.199975, 0.536357, 0.236272, 0.531484, - 0.210624, 0.543641, 0.256096, 0.545496, - 0.221227, 0.550048, 0.277085, 0.559497, - 0.231568, 0.555076, 0.298615, 0.575752, - 0.241698, 0.560541, 0.321547, 0.591999, - 0.251172, 0.564156, 0.345602, 0.610654, - 0.260178, 0.567607, 0.371851, 0.630484, - 0.268094, 0.56923, 0.40076, 0.651807, - 0.274661, 0.569779, 0.430801, 0.67239, - 0.280331, 0.566791, 0.461939, 0.693024, - 0.284501, 0.562007, 0.493854, 0.715473, - 0.287852, 0.555791, 0.526992, 0.736323, - 0.28929, 0.546345, 0.560102, 0.755771, - 0.289405, 0.534, 0.593543, 0.775424, - 0.2881, 0.519114, 0.627256, 0.795447, - 0.285562, 0.502543, 0.661464, 0.815319, - 0.281416, 0.484773, 0.695206, 0.831769, - 0.275523, 0.463445, 0.729044, 0.849464, - 0.267516, 0.440269, 0.764069, 0.866775, - 0.257584, 0.415049, 0.799089, 0.881252, - 0.245817, 0.388049, 0.831948, 0.894209, - 0.233127, 0.35889, 0.865526, 0.906922, - 0.219579, 0.329915, 0.89818, 0.919686, - 0.204491, 0.300441, 0.930013, 0.929044, - 0.188962, 0.269445, 0.962061, 0.938393, - 0.171079, 0.238402, 0.994214, 0.94661, - 0.15199, 0.208204, 1.02533, 0.953095, - 0.131953, 0.178653, 1.0529, 0.958644, - 0.111233, 0.150684, 1.0771, 0.963925, - 0.0903098, 0.122359, 1.09855, 0.971995, - 0.0680505, 0.0923342, 1.11874, 0.981658, - 0.0448512, 0.0614195, 1.13635, 0.991649, - 0.0221931, 0.0303582, 1.15238, 0.999985, 0.000393403, - 0.000111086, 1.16772, 0.396806, - 9.71563e-06, 0.457671, 8.42355e-06, 0.429186, - 0.000249421, 0.495017, 0.00021625, 0.429324, - 0.000998052, 0.495173, 0.000865322, 0.429175, - 0.00224487, 0.494999, 0.00194637, 0.429129, - 0.00399041, 0.494952, 0.00346004, 0.429153, - 0.00623476, 0.494974, 0.00540684, 0.429168, - 0.0089773, 0.494983, 0.00778714, 0.429207, - 0.0122175, 0.495012, 0.0106022, 0.429257, - 0.0159542, 0.495047, 0.0138535, 0.429338, - 0.0201864, 0.495106, 0.0175443, 0.429431, - 0.0249104, 0.495165, 0.0216774, 0.429587, - 0.0301252, 0.495279, 0.0262594, 0.429796, - 0.0358249, 0.495432, 0.0312968, 0.430065, - 0.0419972, 0.495621, 0.0367985, 0.430588, - 0.0485144, 0.496061, 0.042798, 0.43113, - 0.0555028, 0.496472, 0.0492914, 0.431743, - 0.0629852, 0.496904, 0.0562907, 0.432448, - 0.0709256, 0.497369, 0.0638056, 0.433414, - 0.0791942, 0.498032, 0.071885, 0.434638, - 0.0877346, 0.498854, 0.0805517, 0.43611, - 0.0968056, 0.499812, 0.0898047, 0.437859, - 0.106002, 0.500891, 0.0997142, 0.440017, - 0.115648, 0.502198, 0.110289, 0.443236, - 0.125427, 0.504389, 0.121644, 0.44697, - 0.135492, 0.506809, 0.133769, 0.451689, - 0.145746, 0.509858, 0.146787, 0.45811, - 0.156219, 0.514247, 0.160793, 0.465305, - 0.166834, 0.518816, 0.175791, 0.474085, - 0.177546, 0.524331, 0.191906, 0.484808, - 0.188262, 0.53104, 0.209199, 0.49732, - 0.199346, 0.538511, 0.227825, 0.509693, - 0.209951, 0.544554, 0.247269, 0.524367, - 0.220533, 0.551616, 0.267978, 0.539228, - 0.231082, 0.557368, 0.289672, 0.55644, - 0.241342, 0.563782, 0.31268, 0.574204, - 0.250964, 0.568851, 0.33651, 0.593388, - 0.260306, 0.57312, 0.362219, 0.613358, - 0.268667, 0.574916, 0.390322, 0.634512, - 0.275591, 0.575053, 0.420478, 0.65563, - 0.281328, 0.572404, 0.451614, 0.678265, - 0.285948, 0.568893, 0.484112, 0.70011, - 0.289408, 0.561878, 0.517348, 0.723005, - 0.291328, 0.55359, 0.551355, 0.743744, - 0.291418, 0.541099, 0.585109, 0.763949, - 0.290252, 0.526489, 0.619487, 0.784186, - 0.287648, 0.509496, 0.65404, 0.804304, - 0.283782, 0.491484, 0.688649, 0.823629, - 0.278067, 0.470517, 0.723133, 0.84094, - 0.270588, 0.44705, 0.757163, 0.857852, - 0.261188, 0.421252, 0.792816, 0.874934, - 0.249313, 0.394191, 0.827248, 0.888709, - 0.236492, 0.365359, 0.861074, 0.902589, - 0.222185, 0.336016, 0.894417, 0.914201, - 0.207314, 0.30527, 0.926825, 0.925978, - 0.191146, 0.274532, 0.9595, 0.93512, - 0.174135, 0.243393, 0.991583, 0.943656, - 0.155231, 0.212414, 1.02356, 0.951719, - 0.134403, 0.182005, 1.05239, 0.957164, - 0.113023, 0.153043, 1.07754, 0.962656, - 0.0914493, 0.124186, 1.09984, 0.970695, - 0.0694179, 0.0941654, 1.12, 0.980749, - 0.0466199, 0.0629671, 1.13849, 0.991205, - 0.0227032, 0.0311146, 1.15494, 0.999884, 0.000632388, - 0.000254483, 1.1706, 0.379821, - 9.57289e-06, 0.460637, 7.89337e-06, 0.405188, - 0.000247483, 0.491396, 0.000204064, 0.404796, - 0.000989434, 0.490914, 0.000815853, 0.40483, - 0.00222607, 0.490949, 0.00183559, 0.40473, - 0.00395723, 0.49084, 0.00326332, 0.404731, - 0.00618287, 0.490836, 0.00509945, 0.404768, - 0.00890258, 0.490871, 0.00734463, 0.404791, - 0.0121156, 0.490883, 0.00999992, 0.404857, - 0.0158214, 0.490938, 0.0130676, 0.404943, - 0.0200178, 0.491004, 0.0165503, 0.405059, - 0.0247027, 0.491093, 0.0204521, 0.405213, - 0.0298729, 0.491205, 0.0247788, 0.405399, - 0.0355226, 0.491333, 0.0295373, 0.405731, - 0.0416352, 0.491604, 0.034741, 0.406303, - 0.0480807, 0.492116, 0.0404255, 0.406814, - 0.0550458, 0.492506, 0.0465732, 0.407404, - 0.0624652, 0.492926, 0.0532058, 0.408149, - 0.0702958, 0.493442, 0.0603442, 0.409128, - 0.0784623, 0.494136, 0.0680297, 0.410408, - 0.087007, 0.495054, 0.0762786, 0.411813, - 0.0959639, 0.495962, 0.0851046, 0.413735, - 0.105075, 0.497257, 0.0945878, 0.416137, - 0.114646, 0.498882, 0.104725, 0.41934, - 0.124394, 0.501132, 0.11563, 0.423326, - 0.134328, 0.503883, 0.127325, 0.428419, - 0.14458, 0.50747, 0.139911, 0.43484, - 0.154979, 0.511964, 0.153481, 0.442641, - 0.165628, 0.517328, 0.168114, 0.452511, - 0.176365, 0.524258, 0.183995, 0.463473, - 0.187298, 0.531248, 0.200953, 0.475564, - 0.198244, 0.538367, 0.219176, 0.488664, - 0.208938, 0.545175, 0.238514, 0.504073, - 0.219599, 0.553227, 0.259129, 0.520832, - 0.230378, 0.560653, 0.280997, 0.538455, - 0.240703, 0.567523, 0.303821, 0.55709, - 0.250548, 0.573287, 0.327948, 0.576646, - 0.259964, 0.577795, 0.353362, 0.596705, - 0.268721, 0.580077, 0.380336, 0.618053, - 0.276054, 0.58018, 0.4101, 0.640303, - 0.282176, 0.578747, 0.44161, 0.662365, - 0.286931, 0.574294, 0.474106, 0.684542, - 0.290521, 0.567035, 0.507549, 0.707984, - 0.292672, 0.558687, 0.541853, 0.730913, - 0.293189, 0.547606, 0.576581, 0.752948, - 0.292199, 0.533471, 0.61172, 0.773452, - 0.289508, 0.516395, 0.646339, 0.794715, - 0.285716, 0.497873, 0.682131, 0.814251, - 0.280051, 0.476845, 0.716396, 0.833057, - 0.272873, 0.453449, 0.751503, 0.84959, - 0.263982, 0.427857, 0.786085, 0.867022, - 0.252745, 0.400335, 0.821355, 0.882277, - 0.239655, 0.371304, 0.85646, 0.895375, - 0.225386, 0.340397, 0.890828, 0.909347, - 0.209587, 0.310005, 0.923532, 0.921885, - 0.193433, 0.2796, 0.956419, 0.932127, - 0.176135, 0.247276, 0.989445, 0.941869, - 0.157872, 0.216186, 1.02221, 0.949735, - 0.137577, 0.185602, 1.05195, 0.956617, - 0.115285, 0.155767, 1.07822, 0.961974, - 0.0928418, 0.126103, 1.10149, 0.96972, - 0.0700592, 0.0956758, 1.12207, 0.98012, - 0.0474671, 0.0643269, 1.1408, 0.990825, - 0.0238113, 0.0320863, 1.1577, 0.999876, 0.000381574, - 8.12203e-05, 1.17403, 0.367636, - 9.61342e-06, 0.469176, 7.53287e-06, 0.380377, - 0.000244772, 0.485434, 0.000191797, 0.380416, - 0.000978857, 0.485475, 0.000767015, 0.380376, - 0.00220165, 0.485435, 0.00172522, 0.380419, - 0.00391408, 0.485487, 0.00306734, 0.380438, - 0.00611549, 0.485505, 0.00479332, 0.380462, - 0.00880558, 0.485525, 0.00690391, 0.380496, - 0.0119837, 0.485551, 0.00940039, 0.38056, - 0.0156487, 0.485605, 0.0122848, 0.38064, - 0.0197988, 0.485666, 0.0155601, 0.380767, - 0.0244324, 0.48577, 0.0192313, 0.380909, - 0.0295444, 0.485871, 0.0233032, 0.381142, - 0.0351321, 0.48606, 0.0277861, 0.381472, - 0.0411535, 0.486336, 0.0326939, 0.382015, - 0.0475408, 0.486833, 0.0380565, 0.382523, - 0.0544395, 0.487231, 0.0438615, 0.383129, - 0.061784, 0.487683, 0.0501332, 0.383952, - 0.0695085, 0.488313, 0.0568996, 0.38498, - 0.0775819, 0.489077, 0.0641952, 0.386331, - 0.0860443, 0.490113, 0.0720324, 0.387788, - 0.0948406, 0.491099, 0.0804379, 0.389808, - 0.103899, 0.492566, 0.0894899, 0.39252, - 0.113313, 0.494601, 0.0992098, 0.395493, - 0.123007, 0.496619, 0.109641, 0.399826, - 0.132859, 0.499912, 0.120919, 0.405341, - 0.143077, 0.504061, 0.133107, 0.411932, - 0.153465, 0.508905, 0.146263, 0.420591, - 0.164108, 0.515482, 0.160544, 0.43101, - 0.174893, 0.523191, 0.176123, 0.441881, - 0.185839, 0.53026, 0.192757, 0.453919, - 0.196633, 0.537295, 0.210535, 0.468715, - 0.207611, 0.546156, 0.229886, 0.485182, - 0.218517, 0.555173, 0.250543, 0.501926, - 0.229249, 0.562728, 0.27221, 0.51785, - 0.239481, 0.567494, 0.294892, 0.536947, - 0.249395, 0.573889, 0.318987, 0.557115, - 0.259, 0.578831, 0.344348, 0.577966, - 0.268075, 0.582055, 0.371223, 0.599489, - 0.276115, 0.583307, 0.399834, 0.62479, - 0.282523, 0.583902, 0.431415, 0.647504, - 0.287663, 0.57953, 0.464301, 0.670601, - 0.291538, 0.573103, 0.498123, 0.693539, - 0.293842, 0.563731, 0.532662, 0.717385, - 0.294681, 0.553169, 0.567925, 0.741533, - 0.293717, 0.539908, 0.603502, 0.762142, - 0.291156, 0.521902, 0.639074, 0.783014, - 0.28719, 0.502815, 0.674439, 0.805158, - 0.281773, 0.482598, 0.710497, 0.823646, - 0.274682, 0.458949, 0.7456, 0.841879, - 0.266184, 0.433129, 0.781085, 0.859515, - 0.255682, 0.406064, 0.816, 0.875335, - 0.242849, 0.376509, 0.851074, 0.890147, - 0.228329, 0.345502, 0.886473, 0.903144, - 0.212491, 0.31428, 0.920751, 0.916618, - 0.195695, 0.282994, 0.954606, 0.927953, - 0.178267, 0.251091, 0.988402, 0.937414, - 0.159549, 0.219107, 1.02141, 0.946823, - 0.140022, 0.18896, 1.05167, 0.954651, - 0.118154, 0.158667, 1.07819, 0.959955, - 0.0946636, 0.128808, 1.1025, 0.96858, - 0.0711792, 0.0973787, 1.12391, 0.97938, - 0.0475046, 0.0650965, 1.14322, 0.990498, - 0.024059, 0.0326267, 1.16077, 0.999844, - 5.12408e-05, 0.000112444, 1.17727, 0.316912, - 9.34977e-06, 0.425996, 6.95559e-06, 0.356423, - 0.000241372, 0.479108, 0.000179562, 0.356272, - 0.000965292, 0.478897, 0.00071811, 0.356262, - 0.00217182, 0.478894, 0.00161574, 0.356265, - 0.00386092, 0.478895, 0.00287261, 0.356278, - 0.0060324, 0.478905, 0.00448907, 0.356293, - 0.00868565, 0.478914, 0.00646572, 0.356346, - 0.0118207, 0.478965, 0.00880438, 0.356395, - 0.0154355, 0.479001, 0.0115066, 0.356484, - 0.019529, 0.479075, 0.0145762, 0.356609, - 0.0240991, 0.47918, 0.018018, 0.356766, - 0.0291413, 0.479305, 0.0218379, 0.357009, - 0.0346498, 0.479512, 0.0260454, 0.357424, - 0.0405462, 0.479909, 0.0306657, 0.357899, - 0.0468825, 0.480337, 0.0357054, 0.358424, - 0.0536887, 0.480771, 0.0411728, 0.359041, - 0.0609416, 0.481242, 0.0470841, 0.359903, - 0.0685239, 0.481943, 0.0534831, 0.360932, - 0.0764883, 0.482741, 0.0603795, 0.362196, - 0.0848364, 0.483688, 0.0678028, 0.363847, - 0.0935002, 0.484947, 0.0758086, 0.365972, - 0.102471, 0.486588, 0.0844173, 0.368741, - 0.111751, 0.488787, 0.0937199, 0.372146, - 0.121334, 0.491405, 0.103732, 0.377114, - 0.131147, 0.495604, 0.114608, 0.38226, - 0.141213, 0.499436, 0.126345, 0.389609, - 0.151632, 0.505334, 0.139116, 0.397925, - 0.162073, 0.51168, 0.152995, 0.407824, - 0.172819, 0.518876, 0.168071, 0.420014, - 0.183929, 0.527639, 0.184495, 0.434266, - 0.195032, 0.537588, 0.20232, 0.447352, - 0.205792, 0.544379, 0.221189, 0.463726, - 0.216704, 0.553422, 0.241616, 0.481406, - 0.227531, 0.562074, 0.263298, 0.498707, - 0.238017, 0.568227, 0.286116, 0.518039, - 0.247936, 0.574473, 0.3101, 0.538277, - 0.257437, 0.579191, 0.335401, 0.561166, - 0.266829, 0.584807, 0.362246, 0.583189, - 0.275329, 0.586476, 0.390609, 0.606024, - 0.28234, 0.585578, 0.420998, 0.632419, - 0.287924, 0.584496, 0.454357, 0.656128, - 0.291972, 0.577766, 0.488233, 0.679953, - 0.29456, 0.56875, 0.523248, 0.704654, - 0.295816, 0.558388, 0.559168, 0.729016, - 0.295157, 0.544826, 0.595326, 0.752062, - 0.292779, 0.528273, 0.631864, 0.773138, - 0.288681, 0.508482, 0.667793, 0.794869, - 0.283358, 0.487341, 0.704035, 0.815101, - 0.27608, 0.46354, 0.739925, 0.834212, - 0.26767, 0.438672, 0.775539, 0.852368, - 0.257397, 0.411239, 0.810895, 0.870207, - 0.245689, 0.3829, 0.846472, 0.884063, - 0.231452, 0.351496, 0.881788, 0.898284, - 0.215561, 0.31895, 0.917438, 0.912964, - 0.198208, 0.287367, 0.952422, 0.924666, - 0.180426, 0.254487, 0.987551, 0.934429, - 0.161525, 0.222226, 1.02142, 0.943485, - 0.141197, 0.191143, 1.05218, 0.9521, - 0.120085, 0.161112, 1.07937, 0.957876, - 0.0975881, 0.130982, 1.10403, 0.966943, - 0.0726842, 0.0990553, 1.12616, 0.978313, - 0.0483705, 0.0662818, 1.14619, 0.990048, - 0.0239072, 0.0329243, 1.16413, 0.999984, 0.000461885, - 7.72859e-05, 1.18099, 0.321287, - 9.35049e-06, 0.455413, 6.59662e-06, 0.332595, - 0.000237513, 0.471437, 0.000167562, 0.332729, - 0.000949964, 0.471618, 0.000670192, 0.332305, - 0.00213618, 0.471028, 0.00150712, 0.332326, - 0.00379765, 0.471055, 0.00267959, 0.332344, - 0.00593353, 0.471072, 0.00418751, 0.332356, - 0.00854349, 0.471077, 0.00603172, 0.332403, - 0.0116268, 0.471121, 0.00821362, 0.332461, - 0.0151824, 0.47117, 0.0107357, 0.332552, - 0.0192088, 0.471251, 0.0136014, 0.332657, - 0.0237024, 0.47133, 0.0168152, 0.332835, - 0.0286615, 0.471487, 0.0203853, 0.333083, - 0.0340765, 0.471708, 0.0243212, 0.333547, - 0.0398563, 0.47219, 0.0286518, 0.333989, - 0.0460916, 0.472587, 0.0333763, 0.334532, - 0.0527897, 0.473054, 0.0385084, 0.335167, - 0.0599284, 0.473568, 0.0440638, 0.33608, - 0.0673514, 0.474362, 0.0500962, 0.337146, - 0.0752237, 0.475231, 0.0566022, 0.338462, - 0.083418, 0.476282, 0.0636272, 0.34014, - 0.0919382, 0.477615, 0.0712153, 0.342341, - 0.100741, 0.479404, 0.079417, 0.345088, - 0.109905, 0.481618, 0.0882631, 0.349049, - 0.119369, 0.485081, 0.0978851, 0.353939, - 0.129033, 0.489317, 0.108336, 0.359893, - 0.139038, 0.494309, 0.119698, 0.366945, - 0.149411, 0.499983, 0.132024, 0.375814, - 0.159843, 0.507185, 0.145558, 0.387112, - 0.170664, 0.516392, 0.160433, 0.40023, - 0.181897, 0.526519, 0.176648, 0.412555, - 0.192785, 0.53423, 0.193922, 0.427023, - 0.203663, 0.542741, 0.212662, 0.443685, - 0.214695, 0.552066, 0.232944, 0.461499, - 0.225561, 0.560762, 0.254495, 0.480975, - 0.236257, 0.569421, 0.277531, 0.501, - 0.24639, 0.576101, 0.301724, 0.521691, - 0.256101, 0.581493, 0.327112, 0.543478, - 0.265289, 0.585221, 0.353917, 0.566094, - 0.273938, 0.587614, 0.381941, 0.589578, - 0.281679, 0.587991, 0.41172, 0.614583, - 0.287655, 0.585928, 0.444148, 0.641813, - 0.292228, 0.582092, 0.478617, 0.666189, - 0.295172, 0.57398, 0.51397, 0.690475, - 0.29648, 0.561676, 0.550118, 0.715543, - 0.296203, 0.548758, 0.586933, 0.740405, - 0.293999, 0.532792, 0.62384, 0.762183, - 0.28998, 0.512735, 0.660723, 0.786069, - 0.28478, 0.492402, 0.69807, 0.806812, - 0.277568, 0.469058, 0.734422, 0.826987, - 0.268951, 0.443017, 0.770946, 0.844588, - 0.259049, 0.415501, 0.80699, 0.863725, - 0.2471, 0.387328, 0.842107, 0.879137, - 0.234157, 0.356108, 0.878078, 0.894634, - 0.218719, 0.324315, 0.914058, 0.909162, - 0.201293, 0.291813, 0.949922, 0.92072, - 0.18267, 0.258474, 0.985337, 0.93158, - 0.163212, 0.225593, 1.0205, 0.941238, - 0.142771, 0.193986, 1.05273, 0.949293, - 0.120956, 0.163392, 1.08075, 0.956226, - 0.0985743, 0.132934, 1.10559, 0.96546, - 0.075118, 0.101255, 1.12823, 0.977403, - 0.0497921, 0.0675441, 1.149, 0.989648, - 0.0241574, 0.0334681, 1.16765, 1.00001, 0.0005762, - 0.000184807, 1.18519, 0.303474, - 9.16603e-06, 0.4542, 6.1243e-06, 0.308894, - 0.000232869, 0.462306, 0.000155592, 0.309426, - 0.000931661, 0.463093, 0.000622499, 0.308643, - 0.0020949, 0.461933, 0.00139979, 0.308651, - 0.0037242, 0.461941, 0.00248874, 0.308662, - 0.00581873, 0.46195, 0.00388933, 0.308687, - 0.00837818, 0.461974, 0.00560247, 0.308728, - 0.0114016, 0.462011, 0.00762948, 0.308789, - 0.0148884, 0.462067, 0.00997326, 0.308882, - 0.0188369, 0.462151, 0.0126375, 0.309007, - 0.0232436, 0.462263, 0.0156271, 0.30918, - 0.0281054, 0.462417, 0.0189498, 0.309442, - 0.0334065, 0.462667, 0.0226167, 0.309901, - 0.0390589, 0.463162, 0.0266614, 0.310331, - 0.0452042, 0.463555, 0.0310715, 0.310858, - 0.0517735, 0.464019, 0.0358698, 0.311576, - 0.0587359, 0.464669, 0.0410848, 0.312436, - 0.0660383, 0.465406, 0.0467453, 0.313526, - 0.0737266, 0.466339, 0.0528718, 0.314903, - 0.0817574, 0.467504, 0.0595039, 0.316814, - 0.090167, 0.469226, 0.0666888, 0.318965, - 0.0987555, 0.470981, 0.0744658, 0.322077, - 0.107792, 0.473814, 0.082912, 0.325947, - 0.117098, 0.477241, 0.0920846, 0.331008, - 0.126602, 0.48184, 0.102137, 0.337893, - 0.136619, 0.488334, 0.113135, 0.345106, - 0.146838, 0.494415, 0.12511, 0.355111, - 0.157357, 0.503275, 0.138356, 0.365095, - 0.167955, 0.510966, 0.152686, 0.378344, - 0.179157, 0.521508, 0.16856, 0.391599, - 0.190143, 0.530455, 0.18561, 0.407786, - 0.20123, 0.541275, 0.204308, 0.425294, - 0.212456, 0.551784, 0.224623, 0.444021, - 0.223568, 0.561493, 0.246172, 0.463418, - 0.234154, 0.569886, 0.268979, 0.484077, - 0.244546, 0.577116, 0.293411, 0.505513, - 0.254301, 0.582914, 0.318936, 0.527672, - 0.263564, 0.587208, 0.345856, 0.550565, - 0.272332, 0.589277, 0.374054, 0.573656, - 0.280011, 0.588426, 0.403276, 0.59827, - 0.286924, 0.587504, 0.43474, 0.624731, - 0.291994, 0.583401, 0.468767, 0.652396, - 0.295159, 0.576997, 0.504411, 0.67732, - 0.296954, 0.565863, 0.54114, 0.703147, - 0.296877, 0.552316, 0.57816, 0.728715, - 0.295147, 0.536773, 0.616124, 0.752448, - 0.291275, 0.51771, 0.653885, 0.775169, - 0.285905, 0.496087, 0.691537, 0.799307, - 0.279064, 0.474232, 0.729251, 0.819482, - 0.270294, 0.447676, 0.766267, 0.837659, - 0.260032, 0.419656, 0.802616, 0.856903, - 0.248497, 0.391328, 0.838583, 0.873325, - 0.235252, 0.360285, 0.874711, 0.889788, - 0.221126, 0.329215, 0.91077, 0.904486, - 0.204304, 0.296392, 0.94653, 0.917711, - 0.185562, 0.262159, 0.983828, 0.928969, - 0.165635, 0.229142, 1.01955, 0.939707, - 0.14442, 0.19673, 1.05317, 0.948167, - 0.122147, 0.165095, 1.0823, 0.955222, - 0.099098, 0.13451, 1.10791, 0.964401, - 0.0755332, 0.102476, 1.1312, 0.976605, - 0.0513817, 0.0689667, 1.15218, 0.989085, - 0.0258499, 0.034506, 1.17129, 0.999908, 0.000617773, - 0.000271268, 1.18961, 0.285803, - 9.05752e-06, 0.452348, 5.72272e-06, 0.284689, - 0.00022732, 0.450581, 0.000143626, 0.285263, - 0.000910214, 0.451482, 0.000575099, 0.285302, - 0.00204784, 0.451553, 0.00129395, 0.285318, - 0.00364057, 0.451574, 0.0023006, 0.28533, - 0.00568813, 0.451585, 0.00359547, 0.285361, - 0.00819001, 0.451618, 0.00517934, 0.285397, - 0.0111458, 0.45165, 0.007054, 0.285447, - 0.0145536, 0.451688, 0.00922167, 0.285527, - 0.0184127, 0.451758, 0.0116869, 0.285688, - 0.0227207, 0.451929, 0.0144555, 0.28584, - 0.0274712, 0.452055, 0.0175341, 0.286136, - 0.0326278, 0.452369, 0.0209406, 0.286574, - 0.0381792, 0.452853, 0.0246965, 0.287012, - 0.0441879, 0.453272, 0.0287996, 0.287542, - 0.0506096, 0.453752, 0.033268, 0.288299, - 0.0573634, 0.454488, 0.0381504, 0.289186, - 0.0645458, 0.455294, 0.0434447, 0.290302, - 0.0720405, 0.456301, 0.0491973, 0.291776, - 0.0799046, 0.457648, 0.0554453, 0.29372, - 0.088117, 0.459483, 0.0622311, 0.296052, - 0.0965328, 0.461571, 0.0695992, 0.299563, - 0.105409, 0.465085, 0.077658, 0.30335, - 0.114553, 0.468506, 0.0864176, 0.309167, - 0.123917, 0.474423, 0.0961078, 0.31529, - 0.13381, 0.47995, 0.106643, 0.324163, - 0.144021, 0.488592, 0.118322, 0.333272, - 0.154382, 0.496461, 0.131133, 0.344224, - 0.165015, 0.50562, 0.145208, 0.357733, - 0.176168, 0.516719, 0.16073, 0.373046, - 0.187468, 0.528513, 0.177807, 0.38788, - 0.198488, 0.537713, 0.196072, 0.405133, - 0.209545, 0.547999, 0.21605, 0.423845, - 0.220724, 0.55759, 0.237484, 0.443777, - 0.231518, 0.566246, 0.26039, 0.464824, - 0.242035, 0.574326, 0.284835, 0.486635, - 0.251898, 0.58037, 0.310518, 0.51012, - 0.261304, 0.58568, 0.337678, 0.535301, - 0.270384, 0.590197, 0.366242, 0.559193, - 0.27841, 0.590569, 0.395873, 0.583544, - 0.285325, 0.588161, 0.426857, 0.608834, - 0.291113, 0.584249, 0.459477, 0.635753, - 0.294882, 0.57763, 0.494734, 0.664367, - 0.297088, 0.569479, 0.532023, 0.689688, - 0.297364, 0.555064, 0.569629, 0.715732, - 0.295949, 0.539522, 0.608124, 0.741307, - 0.292259, 0.521613, 0.646231, 0.764949, - 0.287063, 0.49969, 0.684938, 0.788599, - 0.28012, 0.476747, 0.723548, 0.81048, - 0.27153, 0.45116, 0.761135, 0.831372, - 0.261289, 0.424101, 0.798916, 0.850092, - 0.249559, 0.39443, 0.835952, 0.867777, - 0.236348, 0.363849, 0.871606, 0.884632, - 0.221569, 0.332477, 0.907843, 0.90047, - 0.20618, 0.300667, 0.944187, 0.914524, - 0.188771, 0.266552, 0.981371, 0.926892, - 0.168362, 0.232349, 1.01841, 0.937951, - 0.146761, 0.199359, 1.05308, 0.947236, - 0.123813, 0.1675, 1.0839, 0.954367, - 0.099984, 0.136166, 1.11047, 0.963907, - 0.0759278, 0.103808, 1.13414, 0.976218, - 0.0511367, 0.0697061, 1.15575, 0.988772, - 0.0267415, 0.0352529, 1.17531, 0.999888, - 0.000520778, 0.000289926, 1.19389, 0.263546, - 8.83274e-06, 0.441896, 5.26783e-06, 0.262352, - 0.000221849, 0.439889, 0.000132311, 0.262325, - 0.000886683, 0.439848, 0.000528824, 0.26228, - 0.00199476, 0.439765, 0.00118975, 0.262372, - 0.00354671, 0.439922, 0.00211568, 0.26239, - 0.00554141, 0.439941, 0.00330652, 0.262412, - 0.00797888, 0.439961, 0.00476346, 0.262453, - 0.0108584, 0.440002, 0.00648818, 0.262528, - 0.0141788, 0.440085, 0.0084835, 0.262615, - 0.017938, 0.440166, 0.0107533, 0.262744, - 0.0221346, 0.440291, 0.0133044, 0.262939, - 0.026762, 0.440493, 0.0161445, 0.263277, - 0.0317573, 0.440889, 0.0192974, 0.26368, - 0.0371832, 0.441338, 0.0227699, 0.264106, - 0.0430371, 0.441753, 0.0265698, 0.264624, - 0.0493035, 0.442227, 0.0307178, 0.265378, - 0.0558669, 0.442985, 0.0352616, 0.266253, - 0.0628718, 0.443795, 0.0401968, 0.267478, - 0.0701569, 0.445008, 0.04559, 0.269062, - 0.077845, 0.446599, 0.0514539, 0.270926, - 0.0857941, 0.448349, 0.0578382, 0.273693, - 0.0940773, 0.451221, 0.0648363, 0.276746, - 0.102704, 0.454097, 0.0724389, 0.281693, - 0.111735, 0.459517, 0.0808744, 0.287335, - 0.121004, 0.46531, 0.0901551, 0.29448, - 0.130734, 0.472605, 0.100371, 0.30257, - 0.140777, 0.480251, 0.111644, 0.312465, - 0.15111, 0.489444, 0.124111, 0.324856, - 0.16189, 0.500919, 0.137979, 0.33774, - 0.172946, 0.511317, 0.153163, 0.35255, - 0.184152, 0.522684, 0.169817, 0.367786, - 0.19522, 0.53248, 0.187886, 0.385474, - 0.20632, 0.543326, 0.207634, 0.404976, - 0.217744, 0.554109, 0.229165, 0.425203, - 0.228691, 0.563395, 0.252068, 0.446704, - 0.239299, 0.571565, 0.276471, 0.468951, - 0.249348, 0.577935, 0.302323, 0.493487, - 0.258933, 0.584309, 0.329882, 0.517861, - 0.268009, 0.58773, 0.358525, 0.543309, - 0.276238, 0.589612, 0.388585, 0.569704, - 0.28356, 0.589294, 0.419787, 0.594871, - 0.289497, 0.585137, 0.452114, 0.622555, - 0.294452, 0.580356, 0.486466, 0.651167, - 0.296918, 0.57185, 0.523079, 0.677332, - 0.297647, 0.558428, 0.5611, 0.703718, - 0.296321, 0.542232, 0.599592, 0.730262, - 0.293339, 0.524541, 0.639138, 0.754304, - 0.288036, 0.502691, 0.677978, 0.778051, - 0.281018, 0.479212, 0.716537, 0.801557, - 0.272414, 0.454071, 0.75586, 0.822559, - 0.262419, 0.425952, 0.794477, 0.843051, - 0.250702, 0.397313, 0.832664, 0.86232, - 0.237264, 0.366534, 0.869876, 0.879044, - 0.222716, 0.334816, 0.906973, 0.896362, - 0.206827, 0.303143, 0.943558, 0.910342, - 0.189659, 0.269699, 0.979759, 0.924119, - 0.171108, 0.236411, 1.01718, 0.935374, - 0.149579, 0.202224, 1.05289, 0.944295, - 0.126295, 0.16989, 1.08496, 0.952227, - 0.101511, 0.138089, 1.11256, 0.962041, - 0.0766392, 0.105053, 1.1375, 0.97528, - 0.0511967, 0.070329, 1.15983, 0.988476, - 0.025463, 0.0351268, 1.17987, 0.999962, 2.86808e-05, 1.45564e-05, 1.19901, 0.227089, - 8.41413e-06, 0.404216, 4.72707e-06, 0.239725, - 0.000215083, 0.426708, 0.000120833, 0.239904, - 0.000860718, 0.427028, 0.000483555, 0.239911, - 0.00193661, 0.427039, 0.00108806, 0.239914, - 0.00344276, 0.42704, 0.00193457, 0.239933, - 0.00537907, 0.427064, 0.00302363, 0.239944, - 0.00774482, 0.427065, 0.00435604, 0.239993, - 0.01054, 0.427122, 0.00593398, 0.240052, - 0.0137626, 0.427179, 0.00775987, 0.240148, - 0.0174115, 0.427279, 0.00983854, 0.240278, - 0.021484, 0.42741, 0.0121763, 0.240472, - 0.0259729, 0.427618, 0.0147827, 0.240839, - 0.0308131, 0.428086, 0.0176837, 0.241201, - 0.0360893, 0.428482, 0.0208775, 0.241626, - 0.0417723, 0.428907, 0.0243821, 0.242207, - 0.0478337, 0.42952, 0.0282228, 0.24298, - 0.0542199, 0.430332, 0.0324333, 0.243881, - 0.0610015, 0.431222, 0.0370252, 0.245123, - 0.0680874, 0.432512, 0.0420535, 0.24667, - 0.0755482, 0.434088, 0.0475414, 0.248779, - 0.0832873, 0.436323, 0.0535542, 0.251665, - 0.0913546, 0.439509, 0.0601716, 0.255305, - 0.0998489, 0.443478, 0.0674282, 0.260049, - 0.108576, 0.448713, 0.0754673, 0.266192, - 0.117754, 0.455524, 0.084339, 0.273158, - 0.127294, 0.4627, 0.0941683, 0.282131, - 0.137311, 0.472068, 0.10515, 0.293332, - 0.147736, 0.483565, 0.117402, 0.304667, - 0.158357, 0.493702, 0.130824, 0.317785, - 0.169274, 0.504708, 0.145724, 0.333245, - 0.180595, 0.517107, 0.16215, 0.349843, - 0.191892, 0.528849, 0.180149, 0.367944, - 0.203168, 0.540301, 0.199746, 0.387579, - 0.214443, 0.551514, 0.221047, 0.408247, - 0.225624, 0.560906, 0.243981, 0.43014, - 0.236422, 0.56959, 0.268513, 0.452669, - 0.24654, 0.576098, 0.294409, 0.476196, - 0.256157, 0.580925, 0.322002, 0.501157, - 0.265289, 0.584839, 0.351052, 0.527632, - 0.273671, 0.587614, 0.3812, 0.555754, - 0.281254, 0.589119, 0.412994, 0.581682, - 0.287448, 0.585204, 0.445498, 0.608196, - 0.292614, 0.579006, 0.479505, 0.635661, - 0.296068, 0.571297, 0.514643, 0.664999, - 0.297395, 0.560855, 0.552213, 0.691039, - 0.296645, 0.544525, 0.591365, 0.7179, - 0.293785, 0.526535, 0.630883, 0.744059, - 0.289089, 0.50545, 0.670932, 0.76863, - 0.282239, 0.482514, 0.710904, 0.793273, - 0.273688, 0.457246, 0.750259, 0.814731, - 0.26328, 0.428872, 0.78948, 0.835603, - 0.251526, 0.399384, 0.828597, 0.85489, - 0.238339, 0.368811, 0.866892, 0.872828, - 0.223607, 0.336617, 0.90563, 0.889462, - 0.207538, 0.303997, 0.943538, 0.904929, - 0.190297, 0.270812, 0.980591, 0.919101, - 0.172034, 0.237453, 1.01935, 0.930536, - 0.152058, 0.204431, 1.05498, 0.941223, - 0.129515, 0.172495, 1.08717, 0.94982, - 0.104263, 0.140175, 1.11551, 0.960592, - 0.0781944, 0.106465, 1.14098, 0.974629, - 0.051688, 0.0711592, 1.16418, 0.98811, - 0.0253929, 0.0354432, 1.18465, 1.00004, 0.000804378, - 0.000330876, 1.20462, 0.214668, - 8.21282e-06, 0.406619, 4.33582e-06, 0.218053, - 0.000208144, 0.413025, 0.000109887, 0.217987, - 0.000832212, 0.412901, 0.000439362, 0.217971, - 0.00187246, 0.412876, 0.000988623, 0.217968, - 0.00332855, 0.41286, 0.00175772, 0.217985, - 0.00520055, 0.412882, 0.00274729, 0.218014, - 0.00748814, 0.412916, 0.00395842, 0.218054, - 0.0101901, 0.412957, 0.00539274, 0.218106, - 0.0133057, 0.413005, 0.00705348, 0.218217, - 0.0168342, 0.413139, 0.00894581, 0.218338, - 0.0207707, 0.413258, 0.0110754, 0.21855, - 0.0251001, 0.413509, 0.0134551, 0.218913, - 0.0297861, 0.413992, 0.0161081, 0.219265, - 0.0348956, 0.414383, 0.0190307, 0.219696, - 0.0403909, 0.414839, 0.0222458, 0.220329, - 0.0462003, 0.415567, 0.025792, 0.220989, - 0.0524208, 0.41621, 0.0296637, 0.222027, - 0.058948, 0.417385, 0.0339323, 0.223301, - 0.0658208, 0.418779, 0.0386055, 0.224988, - 0.0730347, 0.420665, 0.0437355, 0.227211, - 0.0805274, 0.423198, 0.0493844, 0.230131, - 0.088395, 0.426566, 0.0556135, 0.233908, - 0.0966208, 0.43091, 0.0624829, 0.239092, - 0.105223, 0.437148, 0.0701636, 0.245315, - 0.11424, 0.444302, 0.0786949, 0.253166, - 0.12368, 0.453262, 0.0882382, 0.262374, - 0.133569, 0.463211, 0.0988682, 0.273145, - 0.143836, 0.474271, 0.110727, 0.285512, - 0.154577, 0.4863, 0.123945, 0.299512, - 0.165501, 0.498817, 0.138581, 0.314287, - 0.176698, 0.510341, 0.154676, 0.331083, - 0.188066, 0.522583, 0.172459, 0.349615, - 0.199597, 0.534879, 0.191979, 0.369318, - 0.210843, 0.546083, 0.21309, 0.390377, - 0.222068, 0.5562, 0.235998, 0.412411, - 0.233059, 0.564704, 0.260518, 0.435715, - 0.24357, 0.572314, 0.286795, 0.461196, - 0.253356, 0.579395, 0.314559, 0.485587, - 0.262362, 0.581985, 0.343581, 0.511908, - 0.270895, 0.584347, 0.374367, 0.539798, - 0.278452, 0.58505, 0.406015, 0.567974, - 0.284877, 0.583344, 0.439168, 0.594303, - 0.290124, 0.577348, 0.473005, 0.622951, - 0.294183, 0.570751, 0.508534, 0.652404, - 0.296389, 0.561541, 0.544764, 0.679291, - 0.296605, 0.546426, 0.582927, 0.706437, - 0.294095, 0.528599, 0.622681, 0.734485, - 0.28978, 0.508676, 0.663567, 0.758841, - 0.283363, 0.484768, 0.704092, 0.78537, - 0.275015, 0.460434, 0.745101, 0.807315, - 0.264689, 0.432166, 0.784712, 0.8271, - 0.252597, 0.401807, 0.824241, 0.849191, - 0.239154, 0.371458, 0.863803, 0.867046, - 0.224451, 0.338873, 0.903063, 0.8852, - 0.208342, 0.306175, 0.942763, 0.901771, - 0.190684, 0.272759, 0.981559, 0.915958, - 0.172105, 0.239306, 1.02048, 0.928046, - 0.152214, 0.206071, 1.05765, 0.939961, - 0.130247, 0.17367, 1.08999, 0.948711, - 0.10672, 0.142201, 1.11829, 0.959305, - 0.0808688, 0.108454, 1.14467, 0.973009, - 0.0539145, 0.0728109, 1.16839, 0.987631, - 0.0262947, 0.0360625, 1.19004, 0.999978, 0.00132758, - 0.000559424, 1.21058, 0.193925, - 7.93421e-06, 0.391974, 3.92537e-06, 0.196746, - 0.000200315, 0.397675, 9.91033e-05, 0.19667, - 0.000801099, 0.397521, 0.000396342, 0.196633, - 0.00180246, 0.397445, 0.000891829, 0.196654, - 0.00320443, 0.397482, 0.00158582, 0.196659, - 0.00500647, 0.39748, 0.00247867, 0.196683, - 0.0072086, 0.397506, 0.00357167, 0.196728, - 0.00981001, 0.397562, 0.00486675, 0.196792, - 0.0128096, 0.397633, 0.00636707, 0.19689, - 0.0162055, 0.397746, 0.00807752, 0.197017, - 0.0199943, 0.397884, 0.0100052, 0.19729, - 0.024139, 0.39827, 0.0121691, 0.197583, - 0.0286671, 0.398639, 0.0145755, 0.197927, - 0.0335858, 0.399034, 0.0172355, 0.198383, - 0.0388806, 0.399554, 0.0201718, 0.199002, - 0.0444736, 0.400289, 0.0234194, 0.199739, - 0.0504583, 0.401111, 0.026984, 0.200784, - 0.056729, 0.402349, 0.0309217, 0.202075, - 0.0633643, 0.403841, 0.0352496, 0.203898, - 0.0703247, 0.406076, 0.0400313, 0.206199, - 0.0775565, 0.408841, 0.0453282, 0.209252, - 0.085184, 0.41259, 0.0511794, 0.213638, - 0.0931994, 0.418288, 0.0577459, 0.21881, - 0.101617, 0.424681, 0.0650508, 0.225642, - 0.11052, 0.433429, 0.0732759, 0.233717, - 0.119772, 0.442897, 0.0824683, 0.242823, - 0.129505, 0.452888, 0.0927484, 0.254772, - 0.139906, 0.466407, 0.104417, 0.266603, - 0.150402, 0.477413, 0.117211, 0.28073, - 0.161395, 0.490519, 0.131598, 0.295399, - 0.172465, 0.50201, 0.147407, 0.312705, - 0.183982, 0.515311, 0.165031, 0.331335, - 0.195532, 0.52786, 0.184336, 0.351037, - 0.206971, 0.5392, 0.205361, 0.372175, - 0.218117, 0.54941, 0.228043, 0.394548, - 0.229327, 0.558642, 0.25267, 0.419598, - 0.240052, 0.567861, 0.279071, 0.443922, - 0.249937, 0.573332, 0.306882, 0.471495, - 0.259407, 0.58013, 0.33661, 0.496769, - 0.267749, 0.580564, 0.367328, 0.524951, - 0.275524, 0.581696, 0.399753, 0.55318, - 0.282148, 0.579885, 0.433134, 0.581577, - 0.287533, 0.575471, 0.467534, 0.609231, - 0.291612, 0.567445, 0.502943, 0.637478, - 0.293911, 0.557657, 0.53871, 0.667795, - 0.295096, 0.546535, 0.576568, 0.694272, - 0.294073, 0.529561, 0.614929, 0.722937, - 0.290386, 0.510561, 0.655909, 0.749682, - 0.284481, 0.487846, 0.697663, 0.774754, - 0.276188, 0.462487, 0.738515, 0.799301, - 0.266215, 0.43481, 0.779802, 0.820762, - 0.254116, 0.404879, 0.820045, 0.843231, - 0.240393, 0.374559, 0.860294, 0.861857, - 0.225503, 0.341582, 0.900965, 0.880815, - 0.209382, 0.308778, 0.941727, 0.89766, - 0.19155, 0.275232, 0.980916, 0.912926, - 0.172346, 0.240938, 1.02162, 0.926391, - 0.151799, 0.207223, 1.0597, 0.938429, - 0.129968, 0.17484, 1.09291, 0.947834, - 0.10651, 0.142984, 1.12248, 0.958432, - 0.0824098, 0.109902, 1.149, 0.972402, - 0.0565242, 0.0744454, 1.1733, 0.987191, - 0.028427, 0.0373794, 1.19538, 0.999975, 3.85685e-05, - 4.203e-05, 1.21676, 0.178114, - 7.66075e-06, 0.385418, 3.54027e-06, 0.176074, - 0.000191966, 0.381002, 8.87135e-05, 0.17601, - 0.000767549, 0.380861, 0.000354715, 0.17598, - 0.00172696, 0.380798, 0.000798168, 0.175994, - 0.00307012, 0.380824, 0.00141928, 0.176017, - 0.00479684, 0.380858, 0.00221859, 0.176019, - 0.00690648, 0.380839, 0.00319714, 0.176072, - 0.00939888, 0.380913, 0.0043572, 0.176131, - 0.0122726, 0.380979, 0.005702, 0.176239, - 0.0155264, 0.38112, 0.00723689, 0.176371, - 0.0191551, 0.381272, 0.00896907, 0.176638, - 0.023117, 0.381669, 0.0109194, 0.176912, - 0.0274633, 0.382015, 0.0130903, 0.177279, - 0.032173, 0.382476, 0.0154949, 0.17774, - 0.0372219, 0.383041, 0.0181669, 0.178344, - 0.0426132, 0.38378, 0.0211209, 0.179153, - 0.0483309, 0.384773, 0.0243899, 0.180197, - 0.0543447, 0.386076, 0.0280062, 0.181581, - 0.0607122, 0.387809, 0.032004, 0.18344, - 0.0673855, 0.390205, 0.036453, 0.186139, - 0.0743989, 0.393944, 0.0414162, 0.189432, - 0.0817731, 0.39832, 0.0469394, 0.193795, - 0.0895464, 0.404188, 0.0531442, 0.199641, - 0.0978264, 0.4121, 0.0601374, 0.206679, - 0.106499, 0.421425, 0.0680078, 0.214865, - 0.115654, 0.431504, 0.076919, 0.224406, - 0.125268, 0.442526, 0.0868835, 0.235876, - 0.135475, 0.455465, 0.0981875, 0.248335, - 0.146023, 0.4681, 0.110759, 0.262868, - 0.157016, 0.482069, 0.124885, 0.278962, - 0.168245, 0.496182, 0.140645, 0.295082, - 0.17958, 0.507401, 0.157838, 0.313738, - 0.191227, 0.520252, 0.17695, 0.333573, - 0.202718, 0.531708, 0.197817, 0.356433, - 0.214424, 0.544509, 0.220785, 0.378853, - 0.225492, 0.55373, 0.245306, 0.402717, - 0.236236, 0.561348, 0.271593, 0.428375, - 0.246568, 0.568538, 0.299776, 0.454724, - 0.255941, 0.573462, 0.329433, 0.482291, - 0.264511, 0.576356, 0.360598, 0.509706, - 0.272129, 0.576446, 0.393204, 0.538805, - 0.278979, 0.575298, 0.427227, 0.568919, - 0.284528, 0.572154, 0.462157, 0.596804, - 0.288801, 0.564691, 0.497997, 0.625987, - 0.291334, 0.555134, 0.534467, 0.656414, - 0.292722, 0.545051, 0.571736, 0.683916, - 0.292185, 0.528813, 0.610158, 0.711809, - 0.290043, 0.51106, 0.649061, 0.739547, - 0.285246, 0.490103, 0.690081, 0.766914, - 0.277647, 0.465523, 0.732554, 0.791375, - 0.267603, 0.437718, 0.773982, 0.814772, - 0.256109, 0.40882, 0.81609, 0.836691, - 0.242281, 0.377823, 0.856849, 0.856984, - 0.227155, 0.34496, 0.898363, 0.876332, - 0.210395, 0.311335, 0.939471, 0.894988, - 0.192612, 0.277703, 0.980799, 0.911113, - 0.173236, 0.243019, 1.02215, 0.924092, - 0.152258, 0.209037, 1.06139, 0.936828, - 0.129575, 0.175909, 1.09635, 0.946869, - 0.10594, 0.143852, 1.12707, 0.958284, - 0.081318, 0.110289, 1.15419, 0.972325, - 0.0556133, 0.0747232, 1.17909, 0.986878, - 0.0297899, 0.0383149, 1.20163, 0.999936, - 0.00197169, 0.000912402, 1.22338, 0.151174, - 7.20365e-06, 0.351531, 3.09789e-06, 0.155594, - 0.00018279, 0.361806, 7.8608e-05, 0.156099, - 0.000731569, 0.362982, 0.000314615, 0.156053, - 0.00164578, 0.362869, 0.000707845, 0.156093, - 0.0029261, 0.362961, 0.00125884, 0.156099, - 0.00457155, 0.362959, 0.00196783, 0.15612, - 0.00658224, 0.362982, 0.00283622, 0.156168, - 0.00895774, 0.363048, 0.00386625, 0.156221, - 0.0116962, 0.363101, 0.00506109, 0.156324, - 0.0147973, 0.363241, 0.00642675, 0.156476, - 0.0182503, 0.363448, 0.00797175, 0.156731, - 0.0220266, 0.36384, 0.00971484, 0.156994, - 0.026176, 0.364179, 0.0116575, 0.157341, - 0.0306701, 0.36462, 0.0138207, 0.157867, - 0.0354591, 0.365364, 0.0162356, 0.15846, - 0.0406141, 0.366111, 0.0189092, 0.159308, - 0.0460519, 0.367248, 0.021885, 0.160426, - 0.0518096, 0.368767, 0.0252004, 0.161877, - 0.0578906, 0.370745, 0.0288825, 0.163995, - 0.0642812, 0.373831, 0.0330139, 0.16655, - 0.0710067, 0.377366, 0.0376283, 0.170237, - 0.0781522, 0.382799, 0.0428493, 0.175096, - 0.0857172, 0.389915, 0.0487324, 0.181069, - 0.0938025, 0.398487, 0.0554214, 0.188487, - 0.102363, 0.408799, 0.0630189, 0.197029, - 0.111343, 0.419991, 0.071634, 0.206684, - 0.120812, 0.431455, 0.0812797, 0.218698, - 0.131033, 0.445746, 0.0923651, 0.230726, - 0.141373, 0.457471, 0.104545, 0.245516, - 0.152387, 0.472388, 0.118449, 0.261551, - 0.163628, 0.486671, 0.133923, 0.277437, - 0.174814, 0.49762, 0.150849, 0.296662, - 0.186713, 0.51162, 0.169924, 0.31795, - 0.198513, 0.525435, 0.190848, 0.339422, - 0.210119, 0.536267, 0.213504, 0.362143, - 0.221354, 0.545982, 0.237947, 0.387198, - 0.23224, 0.555364, 0.264427, 0.412349, - 0.24257, 0.561489, 0.292519, 0.439274, - 0.252284, 0.566903, 0.322561, 0.466779, - 0.261023, 0.569614, 0.353952, 0.496011, - 0.26899, 0.571589, 0.387278, 0.524964, - 0.275498, 0.570325, 0.421356, 0.556518, - 0.281449, 0.568792, 0.457314, 0.584363, - 0.285526, 0.560268, 0.493199, 0.614214, - 0.28844, 0.55205, 0.530276, 0.645684, - 0.289777, 0.541906, 0.56855, 0.673446, - 0.289722, 0.526464, 0.606927, 0.701924, - 0.287792, 0.509872, 0.645945, 0.73037, - 0.284315, 0.490649, 0.685564, 0.757405, - 0.278804, 0.467964, 0.726511, 0.784025, - 0.269543, 0.441468, 0.768601, 0.808255, - 0.258117, 0.41216, 0.811321, 0.830739, - 0.244728, 0.380606, 0.853496, 0.851914, - 0.229428, 0.348111, 0.895374, 0.872586, - 0.212508, 0.314732, 0.937674, 0.891581, - 0.194025, 0.280338, 0.979869, 0.907641, - 0.174711, 0.245203, 1.02253, 0.922233, - 0.153509, 0.21077, 1.06371, 0.935878, - 0.130418, 0.177399, 1.09972, 0.946338, - 0.105558, 0.144507, 1.13124, 0.957265, - 0.080059, 0.110508, 1.15973, 0.971668, - 0.0539766, 0.0742311, 1.18515, 0.9866, - 0.0277101, 0.0375224, 1.20858, 1.00021, - 0.000515531, 0.000135226, 1.23135, 0.137468, - 6.86011e-06, 0.345041, 2.73315e-06, 0.13703, - 0.000173378, 0.343936, 6.90761e-05, 0.136986, - 0.000693048, 0.34383, 0.000276126, 0.136964, - 0.00155931, 0.343761, 0.000621337, 0.137003, - 0.00277211, 0.343863, 0.00110494, 0.137012, - 0.00433103, 0.343868, 0.00172744, 0.137043, - 0.00623606, 0.343916, 0.00249022, 0.13709, - 0.0084868, 0.343986, 0.00339559, 0.137145, - 0.0110814, 0.344045, 0.00444687, 0.137242, - 0.0140187, 0.344177, 0.00565007, 0.137431, - 0.0172713, 0.344491, 0.00701868, 0.137644, - 0.0208605, 0.344805, 0.00856042, 0.13791, - 0.024792, 0.345172, 0.0102863, 0.138295, - 0.0290461, 0.345734, 0.0122185, 0.138764, - 0.0335957, 0.346371, 0.0143771, 0.139415, - 0.038467, 0.347298, 0.0167894, 0.140272, - 0.0436176, 0.348527, 0.0194895, 0.141457, - 0.0491016, 0.350276, 0.0225043, 0.14303, - 0.0548764, 0.352646, 0.0258962, 0.145289, - 0.0610096, 0.356206, 0.0297168, 0.148502, - 0.0674777, 0.361488, 0.0340562, 0.152188, - 0.074345, 0.367103, 0.0389534, 0.157359, - 0.0817442, 0.375247, 0.0445541, 0.16379, - 0.0896334, 0.385064, 0.0509535, 0.171376, - 0.098005, 0.396082, 0.0582611, 0.179901, - 0.106817, 0.407418, 0.06654, 0.189892, - 0.116239, 0.420031, 0.075994, 0.201838, - 0.12627, 0.434321, 0.0867239, 0.214311, - 0.136701, 0.447631, 0.0987517, 0.228902, - 0.147616, 0.462046, 0.112353, 0.245107, - 0.158871, 0.476942, 0.127605, 0.262292, - 0.170261, 0.490285, 0.144469, 0.281215, - 0.182017, 0.503783, 0.163282, 0.301058, - 0.193729, 0.515505, 0.183873, 0.322752, - 0.205512, 0.52682, 0.206466, 0.347547, - 0.217214, 0.539473, 0.231194, 0.370969, - 0.227966, 0.546625, 0.257288, 0.397533, - 0.238555, 0.55472, 0.285789, 0.42398, - 0.248278, 0.559468, 0.315746, 0.452928, - 0.257422, 0.564095, 0.347724, 0.482121, - 0.265306, 0.565426, 0.380922, 0.510438, - 0.272043, 0.563205, 0.415639, 0.541188, - 0.277614, 0.561087, 0.451702, 0.571667, - 0.281927, 0.554922, 0.48845, 0.602432, - 0.285015, 0.546838, 0.526442, 0.634126, - 0.286512, 0.537415, 0.564896, 0.662816, - 0.286388, 0.522906, 0.604037, 0.692411, - 0.284734, 0.507003, 0.643795, 0.720946, - 0.281297, 0.488398, 0.68298, 0.748293, - 0.276262, 0.466353, 0.723466, 0.776931, - 0.269978, 0.443573, 0.764565, 0.801065, - 0.260305, 0.415279, 0.805838, 0.825843, - 0.247426, 0.384773, 0.849985, 0.84807, - 0.232437, 0.352555, 0.893174, 0.869122, - 0.215806, 0.318642, 0.936564, 0.888963, - 0.197307, 0.28381, 0.980253, 0.905547, - 0.177203, 0.247888, 1.02463, 0.918554, - 0.155542, 0.212904, 1.06714, 0.931395, - 0.131948, 0.1787, 1.10451, 0.941749, - 0.106723, 0.145902, 1.13694, 0.954551, - 0.0804939, 0.111193, 1.1666, 0.970279, - 0.0534239, 0.0744697, 1.19249, 0.986117, - 0.0257452, 0.0368788, 1.21665, 0.999938, 0.00190634, - 0.0010291, 1.23981, 0.118493, - 6.47439e-06, 0.32272, 2.3772e-06, 0.118765, - 0.000163023, 0.323456, 5.98573e-05, 0.118772, - 0.00065212, 0.323477, 0.000239447, 0.118843, - 0.00146741, 0.323657, 0.000538881, 0.118804, - 0.00260846, 0.323553, 0.00095826, 0.118826, - 0.00407576, 0.323595, 0.00149845, 0.118846, - 0.00586826, 0.323617, 0.00216047, 0.118886, - 0.00798578, 0.32367, 0.00294679, 0.118947, - 0.0104273, 0.323753, 0.00386124, 0.119055, - 0.0131909, 0.323922, 0.00490999, 0.119241, - 0.0162444, 0.324251, 0.00610804, 0.11944, - 0.0196339, 0.324544, 0.00745805, 0.119739, - 0.0233378, 0.325026, 0.00897805, 0.12011, - 0.0273179, 0.325586, 0.0106895, 0.120571, - 0.0316143, 0.326231, 0.0126073, 0.12124, - 0.0361939, 0.327264, 0.0147654, 0.122162, - 0.0410511, 0.328733, 0.0172001, 0.123378, - 0.0462233, 0.330659, 0.0199375, 0.125183, - 0.0517109, 0.333754, 0.0230498, 0.127832, - 0.0575652, 0.338507, 0.026597, 0.130909, - 0.0637441, 0.343666, 0.0306345, 0.135221, - 0.0704302, 0.351063, 0.035273, 0.14082, - 0.0776364, 0.360604, 0.0406137, 0.146781, - 0.0852293, 0.369638, 0.0466788, 0.155121, - 0.0935351, 0.3827, 0.0537628, 0.16398, - 0.102234, 0.39522, 0.0617985, 0.173926, - 0.111465, 0.40793, 0.07097, 0.185137, - 0.121296, 0.42105, 0.0813426, 0.19826, - 0.13169, 0.435735, 0.0931596, 0.212938, - 0.142614, 0.450932, 0.106547, 0.229046, - 0.153884, 0.465726, 0.121575, 0.246246, - 0.165382, 0.479461, 0.138286, 0.264637, - 0.176806, 0.492106, 0.15666, 0.284959, - 0.188793, 0.504774, 0.17728, 0.308157, - 0.200763, 0.518805, 0.19988, 0.330951, - 0.21239, 0.528231, 0.224293, 0.3549, - 0.223521, 0.536376, 0.250541, 0.381502, - 0.234169, 0.544846, 0.278902, 0.409529, - 0.244077, 0.551717, 0.309227, 0.437523, - 0.253363, 0.55517, 0.341426, 0.467624, - 0.261659, 0.557772, 0.37518, 0.497268, - 0.268498, 0.556442, 0.41007, 0.528294, - 0.274018, 0.553915, 0.446445, 0.559053, - 0.278169, 0.549153, 0.483779, 0.589329, - 0.281229, 0.539878, 0.522249, 0.622503, - 0.282902, 0.53162, 0.561754, 0.652382, - 0.282815, 0.518119, 0.601544, 0.681847, - 0.281247, 0.502187, 0.641574, 0.712285, - 0.277986, 0.484824, 0.682633, 0.740094, - 0.273017, 0.463483, 0.723426, 0.768478, - 0.266692, 0.441299, 0.763747, 0.794556, - 0.258358, 0.415238, 0.805565, 0.819408, - 0.248807, 0.386912, 0.847254, 0.843411, - 0.236214, 0.356165, 0.891091, 0.862397, - 0.219794, 0.320562, 0.936174, 0.883113, - 0.201768, 0.285322, 0.982562, 0.90023, - 0.181672, 0.249713, 1.02862, 0.915192, - 0.159279, 0.214546, 1.07163, 0.928458, - 0.134725, 0.180285, 1.10995, 0.94069, - 0.10913, 0.147119, 1.14354, 0.953409, - 0.0821315, 0.112492, 1.17372, 0.969537, - 0.0542677, 0.0752014, 1.20043, 0.985612, - 0.0259096, 0.0370361, 1.22528, 0.999835, 0.00298198, - 0.00151801, 1.24959, 0.10097, - 6.02574e-06, 0.300277, 2.02619e-06, 0.101577, - 0.000152164, 0.302077, 5.11662e-05, 0.101572, - 0.000608889, 0.302066, 0.000204751, 0.101566, - 0.00136997, 0.302047, 0.000460753, 0.101592, - 0.00243557, 0.302114, 0.000819497, 0.101608, - 0.0038053, 0.30214, 0.00128154, 0.101627, - 0.00547906, 0.30216, 0.0018483, 0.101669, - 0.00745647, 0.302224, 0.00252223, 0.101732, - 0.00973615, 0.302318, 0.00330716, 0.101844, - 0.0123097, 0.302513, 0.00421061, 0.102025, - 0.0151681, 0.30285, 0.00524481, 0.102224, - 0.0183334, 0.303166, 0.0064154, 0.102515, - 0.0217819, 0.303654, 0.00774063, 0.102886, - 0.0255067, 0.304243, 0.0092398, 0.103395, - 0.029514, 0.305089, 0.0109339, 0.104109, - 0.0337912, 0.306301, 0.0128561, 0.105074, - 0.0383565, 0.30798, 0.0150338, 0.10654, - 0.0432132, 0.310726, 0.0175228, 0.108478, - 0.0484244, 0.314351, 0.0203648, 0.111015, - 0.0539339, 0.319032, 0.0236325, 0.114682, - 0.0598885, 0.32605, 0.0274188, 0.11911, - 0.0663375, 0.334109, 0.0317905, 0.124736, - 0.0733011, 0.344013, 0.0368502, 0.131479, - 0.0807744, 0.355358, 0.0427104, 0.139283, - 0.0888204, 0.367614, 0.0494788, 0.148054, - 0.0973394, 0.380072, 0.0572367, 0.159037, - 0.10665, 0.395678, 0.0662704, 0.169794, - 0.116221, 0.40795, 0.0763192, 0.18314, - 0.126632, 0.423546, 0.087956, 0.197515, - 0.137383, 0.438213, 0.101042, 0.213514, - 0.148641, 0.453248, 0.115827, 0.23065, - 0.160117, 0.46688, 0.132283, 0.249148, - 0.171807, 0.479962, 0.150644, 0.270219, - 0.183695, 0.494618, 0.171073, 0.292338, - 0.195574, 0.506937, 0.193378, 0.314999, - 0.207205, 0.516463, 0.217585, 0.340991, - 0.218955, 0.528123, 0.24428, 0.367982, - 0.229917, 0.537025, 0.272784, 0.39432, - 0.239737, 0.541627, 0.302742, 0.423364, - 0.249048, 0.546466, 0.335112, 0.453751, - 0.257329, 0.549466, 0.369032, 0.48416, - 0.264623, 0.549503, 0.404577, 0.515262, - 0.270411, 0.547008, 0.441337, 0.547036, - 0.274581, 0.542249, 0.479162, 0.576614, - 0.277266, 0.533015, 0.517904, 0.611143, - 0.279144, 0.525512, 0.558508, 0.640989, - 0.279001, 0.51154, 0.598995, 0.671182, - 0.277324, 0.495641, 0.639935, 0.700848, - 0.273908, 0.477526, 0.681017, 0.729862, - 0.269063, 0.457955, 0.722764, 0.758273, - 0.262282, 0.434846, 0.764349, 0.784121, - 0.254281, 0.409203, 0.806206, 0.809798, - 0.24505, 0.382694, 0.848617, 0.834953, - 0.233861, 0.354034, 0.892445, 0.856817, - 0.221308, 0.321764, 0.936263, 0.877609, - 0.205996, 0.288118, 0.982401, 0.897489, - 0.186702, 0.253277, 1.02975, 0.913792, - 0.164618, 0.217963, 1.07488, 0.92785, - 0.140023, 0.183221, 1.11487, 0.940378, - 0.11328, 0.149385, 1.14947, 0.95273, - 0.0853958, 0.114152, 1.1807, 0.969059, - 0.0568698, 0.0769845, 1.20912, 0.985574, - 0.0276502, 0.0381186, 1.23498, 0.999943, 0.00239052, - 0.00126861, 1.25987, 0.0852715, - 5.60067e-06, 0.279021, 1.71162e-06, 0.0854143, - 0.000140871, 0.279483, 4.30516e-05, 0.0854191, - 0.000563385, 0.2795, 0.000172184, 0.0854188, - 0.00126753, 0.279493, 0.000387464, 0.0854229, - 0.00225337, 0.279501, 0.00068918, 0.0854443, - 0.00352086, 0.279549, 0.00107803, 0.0854697, - 0.00506962, 0.279591, 0.00155536, 0.0855093, - 0.00689873, 0.279652, 0.00212354, 0.0855724, - 0.00900821, 0.279752, 0.00278703, 0.0856991, - 0.0113799, 0.280011, 0.0035551, 0.085855, - 0.0140314, 0.280297, 0.00443449, 0.0860682, - 0.016963, 0.280682, 0.00543636, 0.086344, - 0.0201438, 0.281159, 0.0065788, 0.0867426, - 0.0235999, 0.281886, 0.00787977, 0.087239, - 0.0273069, 0.282745, 0.0093606, 0.0879815, - 0.031269, 0.284139, 0.011056, 0.0891258, - 0.035531, 0.28647, 0.0130065, 0.0906909, - 0.0400947, 0.289708, 0.0152495, 0.0927624, - 0.0449638, 0.293904, 0.0178454, 0.0958376, - 0.0502427, 0.300471, 0.0208915, 0.0995827, - 0.0559514, 0.30806, 0.0244247, 0.104526, - 0.0622152, 0.317874, 0.0285721, 0.110532, - 0.0690046, 0.329332, 0.0334227, 0.117385, - 0.0763068, 0.341217, 0.0390466, 0.12522, - 0.084184, 0.353968, 0.0455786, 0.134037, - 0.0925248, 0.366797, 0.0530773, 0.144014, - 0.101487, 0.380209, 0.0617424, 0.156013, - 0.111273, 0.395956, 0.071777, 0.168872, - 0.121431, 0.41053, 0.0830905, 0.183089, - 0.132105, 0.425073, 0.0959341, 0.198763, - 0.143286, 0.439833, 0.110448, 0.216159, - 0.154841, 0.454507, 0.126769, 0.234859, - 0.166588, 0.468368, 0.14495, 0.255879, - 0.178626, 0.482846, 0.165233, 0.27677, - 0.190218, 0.493489, 0.187217, 0.301184, - 0.202227, 0.506549, 0.211659, 0.325852, - 0.213764, 0.5158, 0.237922, 0.352824, - 0.22487, 0.525442, 0.26632, 0.380882, - 0.235246, 0.532487, 0.296691, 0.410137, - 0.244847, 0.537703, 0.329179, 0.439787, - 0.253122, 0.540361, 0.363135, 0.472291, - 0.260517, 0.542734, 0.399222, 0.501856, - 0.266519, 0.538826, 0.436352, 0.534816, - 0.270905, 0.535152, 0.474505, 0.565069, - 0.273826, 0.525979, 0.513988, 0.597154, - 0.275333, 0.516394, 0.554852, 0.630473, - 0.275314, 0.506206, 0.596592, 0.660574, - 0.273323, 0.489769, 0.638117, 0.692015, - 0.270008, 0.472578, 0.680457, 0.720647, - 0.265001, 0.452134, 0.723008, 0.750528, - 0.258311, 0.430344, 0.765954, 0.777568, - 0.250046, 0.405624, 0.809012, 0.80387, - 0.240114, 0.378339, 0.852425, 0.828439, - 0.228737, 0.349877, 0.895346, 0.851472, - 0.216632, 0.318968, 0.940695, 0.873906, - 0.202782, 0.287489, 0.987235, 0.89467, - 0.187059, 0.254394, 1.03348, 0.912281, - 0.168818, 0.221294, 1.07812, 0.927358, - 0.146494, 0.18675, 1.11928, 0.940385, - 0.120009, 0.152322, 1.15609, 0.952672, - 0.0917183, 0.117514, 1.18875, 0.968496, - 0.0620321, 0.0797405, 1.21821, 0.985236, - 0.0314945, 0.0402383, 1.24523, 0.99998, - 0.000575153, 0.000110644, 1.27133, 0.0702429, - 5.12222e-06, 0.255273, 1.40947e-06, 0.0702981, - 0.000128826, 0.255469, 3.54488e-05, 0.0703691, - 0.000515562, 0.255727, 0.000141874, 0.0703805, - 0.00116, 0.255754, 0.00031929, 0.0703961, - 0.00206224, 0.255813, 0.000567999, 0.0704102, - 0.00322223, 0.255839, 0.00088871, 0.0704298, - 0.00463928, 0.255863, 0.00128272, 0.0704759, - 0.00631375, 0.255953, 0.00175283, 0.0705434, - 0.00824317, 0.256079, 0.00230342, 0.0706693, - 0.010412, 0.25636, 0.0029443, 0.0708189, - 0.0128439, 0.256647, 0.00368031, 0.0710364, - 0.0155177, 0.257084, 0.00452614, 0.0713223, - 0.0184374, 0.257637, 0.00549706, 0.0717182, - 0.0216002, 0.258416, 0.00661246, 0.072321, - 0.0249966, 0.259699, 0.00790147, 0.0731446, - 0.0286566, 0.261475, 0.0093884, 0.0743352, - 0.0325888, 0.264132, 0.0111186, 0.0760676, - 0.036843, 0.26815, 0.013145, 0.078454, - 0.0414292, 0.273636, 0.0155251, 0.0818618, - 0.0464634, 0.281653, 0.0183525, 0.0857382, - 0.0519478, 0.289992, 0.0216642, 0.0908131, - 0.0579836, 0.30066, 0.0255956, 0.0967512, - 0.0645124, 0.312204, 0.0301954, 0.103717, - 0.0716505, 0.325001, 0.0356017, 0.111596, - 0.0793232, 0.338129, 0.041896, 0.120933, - 0.087645, 0.352853, 0.0492447, 0.130787, - 0.096492, 0.366192, 0.0576749, 0.142311, - 0.105973, 0.380864, 0.0673969, 0.155344, - 0.116182, 0.396575, 0.0785899, 0.169535, - 0.126815, 0.411443, 0.0912377, 0.185173, - 0.138015, 0.426256, 0.105607, 0.201755, - 0.149325, 0.439607, 0.121551, 0.221334, - 0.161207, 0.455467, 0.139608, 0.241461, - 0.173162, 0.469096, 0.159591, 0.26294, - 0.18504, 0.481014, 0.18156, 0.286776, - 0.196881, 0.493291, 0.205781, 0.311596, - 0.208311, 0.503556, 0.231819, 0.338667, - 0.219671, 0.513268, 0.260274, 0.366021, - 0.230451, 0.519414, 0.290862, 0.395875, - 0.240131, 0.526766, 0.323196, 0.425564, - 0.248566, 0.52905, 0.357071, 0.457094, - 0.256195, 0.530796, 0.393262, 0.488286, - 0.262331, 0.528703, 0.430797, 0.522291, - 0.267141, 0.52727, 0.470231, 0.554172, - 0.270411, 0.519848, 0.510477, 0.586427, - 0.271986, 0.510307, 0.551594, 0.619638, - 0.27192, 0.499158, 0.593849, 0.650656, - 0.269817, 0.483852, 0.636314, 0.68284, - 0.266267, 0.467515, 0.679679, 0.714356, - 0.26113, 0.44931, 0.723884, 0.742717, - 0.254067, 0.425789, 0.767245, 0.770894, - 0.245652, 0.401144, 0.811819, 0.797358, - 0.235554, 0.374224, 0.856315, 0.823377, - 0.223896, 0.346167, 0.901077, 0.847456, - 0.210865, 0.316056, 0.946502, 0.870697, - 0.196574, 0.284503, 0.993711, 0.891068, - 0.180814, 0.251628, 1.04134, 0.909267, - 0.163314, 0.219065, 1.08609, 0.925653, - 0.143304, 0.186446, 1.12702, 0.940017, - 0.121322, 0.153416, 1.16371, 0.952398, - 0.0973872, 0.120334, 1.19712, 0.967568, - 0.0698785, 0.08352, 1.22791, 0.984772, - 0.0390031, 0.0439209, 1.25672, 1.00026, - 0.0070087, 0.00315668, 1.28428, 0.0556653, - 4.59654e-06, 0.227325, 1.12556e-06, 0.0565238, - 0.000116382, 0.230826, 2.84985e-05, 0.0565717, - 0.000465666, 0.231026, 0.000114036, 0.0565859, - 0.00104773, 0.231079, 0.000256656, 0.0565761, - 0.00186255, 0.231025, 0.00045663, 0.0565913, - 0.00291002, 0.231058, 0.000714664, 0.0566108, - 0.00418998, 0.231085, 0.00103224, 0.0566532, - 0.00570206, 0.231169, 0.00141202, 0.0567473, - 0.00743666, 0.231417, 0.00186018, 0.0568567, - 0.00940298, 0.231661, 0.00238264, 0.0569859, - 0.0115991, 0.231895, 0.00298699, 0.0572221, - 0.0140096, 0.232456, 0.00368957, 0.057519, - 0.0166508, 0.233096, 0.00450303, 0.0579534, - 0.01951, 0.234094, 0.00544945, 0.0585922, - 0.0225991, 0.235629, 0.00655564, 0.0595647, - 0.0259416, 0.238106, 0.00785724, 0.0609109, - 0.0295661, 0.241557, 0.00939127, 0.0628751, - 0.0335126, 0.246652, 0.0112198, 0.0656908, - 0.0378604, 0.254091, 0.0134168, 0.0691347, - 0.0426543, 0.262666, 0.0160374, 0.0732165, - 0.0478967, 0.272029, 0.0191514, 0.0782863, - 0.0536716, 0.283007, 0.0228597, 0.0843973, - 0.0600683, 0.295732, 0.0272829, 0.0913598, - 0.0670095, 0.308779, 0.032484, 0.0994407, - 0.0745516, 0.322886, 0.0385886, 0.108189, - 0.082712, 0.336408, 0.0457133, 0.118574, - 0.0914927, 0.351692, 0.0539832, 0.129989, - 0.100854, 0.366502, 0.0635162, 0.142722, - 0.110837, 0.381675, 0.0744386, 0.156654, - 0.121353, 0.3963, 0.0868483, 0.172151, - 0.132414, 0.411477, 0.100963, 0.188712, - 0.143809, 0.42508, 0.116795, 0.208093, - 0.155765, 0.441328, 0.134715, 0.227936, - 0.167608, 0.454328, 0.154396, 0.249495, - 0.179579, 0.467235, 0.176179, 0.27362, - 0.191488, 0.480248, 0.200193, 0.296371, - 0.202618, 0.487886, 0.225775, 0.324234, - 0.214133, 0.499632, 0.25441, 0.353049, - 0.225212, 0.509532, 0.285077, 0.381785, - 0.234875, 0.514265, 0.317047, 0.414038, - 0.244205, 0.521282, 0.351874, 0.445251, - 0.252145, 0.522931, 0.388279, 0.476819, - 0.258433, 0.520947, 0.425825, 0.509209, - 0.263411, 0.517669, 0.465104, 0.542759, - 0.266732, 0.512841, 0.505741, 0.574822, - 0.268263, 0.503317, 0.547611, 0.609324, - 0.268489, 0.493035, 0.590953, 0.641772, - 0.266941, 0.478816, 0.63488, 0.674049, - 0.263297, 0.462863, 0.679072, 0.705071, - 0.257618, 0.442931, 0.723487, 0.734709, - 0.250625, 0.421299, 0.768708, 0.763704, - 0.24179, 0.397085, 0.814375, 0.791818, - 0.231115, 0.370577, 0.859907, 0.817439, - 0.21922, 0.34232, 0.906715, 0.843202, - 0.205658, 0.312627, 0.953943, 0.866639, - 0.190563, 0.280933, 1.00185, 0.888129, - 0.173978, 0.248393, 1.05105, 0.907239, - 0.155485, 0.216007, 1.09704, 0.923893, - 0.134782, 0.183233, 1.13857, 0.938882, - 0.11249, 0.150376, 1.17539, 0.952464, - 0.0890706, 0.117177, 1.20924, 0.968529, - 0.0646523, 0.0813095, 1.24055, 0.984763, - 0.038606, 0.0439378, 1.27018, 1.00053, - 0.01238, 0.00598668, 1.29873, 0.0437928, - 4.09594e-06, 0.204012, 8.79224e-07, 0.0440166, - 0.000103395, 0.205049, 2.21946e-05, 0.0440529, - 0.000413633, 0.205225, 8.87981e-05, 0.0440493, - 0.000930594, 0.2052, 0.000199858, 0.0439884, - 0.00165352, 0.204901, 0.000355495, 0.0440716, - 0.0025849, 0.205255, 0.000556983, 0.0440968, - 0.00372222, 0.205311, 0.000805326, 0.0441359, - 0.00506478, 0.205391, 0.00110333, 0.0442231, - 0.00660384, 0.205638, 0.00145768, 0.0443254, - 0.00835246, 0.205877, 0.00187275, 0.0444832, - 0.0102992, 0.20627, 0.00235938, 0.0447001, - 0.0124449, 0.206796, 0.0029299, 0.0450168, - 0.0147935, 0.207593, 0.0036005, 0.0454816, - 0.017336, 0.208819, 0.00439246, 0.0462446, - 0.0201156, 0.211036, 0.00533864, 0.0473694, - 0.0231568, 0.214388, 0.00646984, 0.0490191, - 0.0264941, 0.219357, 0.00783856, 0.0512776, - 0.030184, 0.226061, 0.00950182, 0.0541279, - 0.0342661, 0.234094, 0.0115156, 0.0578989, - 0.0388539, 0.244297, 0.0139687, 0.0620835, - 0.0438735, 0.254457, 0.0169015, 0.0673497, - 0.04951, 0.266706, 0.0204554, 0.0731759, - 0.0556263, 0.278753, 0.0246606, 0.0803937, - 0.0624585, 0.29309, 0.0297126, 0.0879287, - 0.0697556, 0.305856, 0.0355868, 0.0970669, - 0.0778795, 0.321059, 0.0425768, 0.106508, - 0.0863541, 0.333873, 0.05056, 0.11776, - 0.0955935, 0.349008, 0.0598972, 0.130081, - 0.105438, 0.363776, 0.0706314, 0.144454, - 0.115899, 0.380112, 0.0828822, 0.1596, - 0.126827, 0.394843, 0.0967611, 0.176097, - 0.138161, 0.409033, 0.112381, 0.194726, - 0.149904, 0.424257, 0.129952, 0.213944, - 0.161675, 0.436945, 0.149333, 0.235516, - 0.173659, 0.450176, 0.170892, 0.260564, - 0.185963, 0.466305, 0.194984, 0.285183, - 0.197582, 0.477328, 0.220805, 0.311095, - 0.208697, 0.486566, 0.248694, 0.338924, - 0.219519, 0.494811, 0.279015, 0.369757, - 0.229766, 0.504065, 0.311725, 0.3996, - 0.238879, 0.507909, 0.345844, 0.430484, - 0.246802, 0.509805, 0.381749, 0.46413, - 0.253924, 0.511436, 0.420251, 0.497077, - 0.259319, 0.508787, 0.459957, 0.530434, - 0.263297, 0.50394, 0.501356, 0.565725, - 0.265619, 0.49804, 0.544252, 0.599254, - 0.265842, 0.487346, 0.587856, 0.631251, - 0.263978, 0.472975, 0.631969, 0.663972, - 0.26043, 0.457135, 0.677471, 0.697724, - 0.255358, 0.439844, 0.723744, 0.727725, - 0.248308, 0.417872, 0.770653, 0.756417, - 0.239181, 0.39273, 0.817357, 0.785419, - 0.22814, 0.367839, 0.864221, 0.81266, - 0.215681, 0.339449, 0.912701, 0.839391, - 0.201623, 0.309279, 0.962419, 0.86366, - 0.185624, 0.278029, 1.0122, 0.885028, - 0.16797, 0.245294, 1.06186, 0.904639, - 0.148336, 0.212689, 1.10934, 0.922048, - 0.12637, 0.179616, 1.15063, 0.936952, - 0.102928, 0.146749, 1.18885, 0.951895, - 0.0785268, 0.112733, 1.22352, 0.967198, - 0.0530153, 0.0760056, 1.25681, 0.984405, - 0.02649, 0.0383183, 1.28762, 1.00021, 0.00070019, - 0.00020039, 1.31656, 0.0325964, - 3.55447e-06, 0.176706, 6.55682e-07, 0.0329333, - 8.99174e-05, 0.178527, 1.65869e-05, 0.0329181, - 0.000359637, 0.178453, 6.63498e-05, 0.0329085, - 0.000808991, 0.178383, 0.000149332, 0.0329181, - 0.00143826, 0.178394, 0.000265873, 0.0329425, - 0.00224678, 0.178517, 0.000416597, 0.0329511, - 0.00323575, 0.17849, 0.000603299, 0.033011, - 0.00439875, 0.178695, 0.000829422, 0.0330733, - 0.00574059, 0.178843, 0.00109908, 0.0331857, - 0.00725896, 0.179176, 0.00141933, 0.0333445, - 0.00895289, 0.179618, 0.0017999, 0.0335674, - 0.0108219, 0.180238, 0.00225316, 0.033939, - 0.0128687, 0.181417, 0.00279765, 0.0345239, - 0.015114, 0.183395, 0.0034564, 0.0354458, - 0.017596, 0.186616, 0.00425864, 0.0368313, - 0.0203524, 0.191547, 0.00524936, 0.0386115, - 0.0234105, 0.197508, 0.00647033, 0.0410303, - 0.0268509, 0.205395, 0.00798121, 0.0442245, - 0.0307481, 0.215365, 0.0098557, 0.0478659, - 0.0350863, 0.225595, 0.0121417, 0.0522416, - 0.0399506, 0.236946, 0.0149385, 0.0574513, - 0.045357, 0.249442, 0.0183189, 0.0631208, - 0.0512863, 0.261222, 0.0223644, 0.0701124, - 0.0579273, 0.275418, 0.0272418, 0.0777331, - 0.0650652, 0.288989, 0.0329458, 0.0862709, - 0.0728813, 0.302546, 0.0396819, 0.096103, - 0.081363, 0.317164, 0.04757, 0.106976, - 0.0904463, 0.331733, 0.0567012, 0.119175, - 0.100105, 0.34661, 0.067202, 0.132919, - 0.110375, 0.362249, 0.0792588, 0.147727, - 0.121115, 0.376978, 0.0928672, 0.163618, - 0.132299, 0.390681, 0.108228, 0.182234, - 0.143887, 0.406571, 0.125502, 0.201809, - 0.155827, 0.42042, 0.144836, 0.225041, - 0.168357, 0.438411, 0.166706, 0.247621, - 0.18004, 0.450368, 0.189909, 0.27097, - 0.191536, 0.460083, 0.215251, 0.296658, - 0.203024, 0.469765, 0.243164, 0.325892, - 0.214056, 0.481837, 0.273388, 0.35406, - 0.224104, 0.487474, 0.305344, 0.384372, - 0.233489, 0.492773, 0.339741, 0.41749, - 0.241874, 0.498451, 0.376287, 0.45013, - 0.248834, 0.499632, 0.414195, 0.481285, - 0.254658, 0.495233, 0.454077, 0.519183, - 0.259367, 0.496401, 0.496352, 0.551544, - 0.261818, 0.487686, 0.538798, 0.587349, - 0.262964, 0.479453, 0.583626, 0.621679, - 0.262128, 0.467709, 0.629451, 0.654991, - 0.258998, 0.452123, 0.67566, 0.686873, - 0.254119, 0.433495, 0.723248, 0.719801, - 0.246946, 0.413657, 0.771156, 0.750355, - 0.237709, 0.390366, 0.81989, 0.780033, - 0.226549, 0.364947, 0.868601, 0.809254, - 0.214186, 0.337256, 0.920034, 0.836576, - 0.199639, 0.307395, 0.971706, 0.861774, - 0.183169, 0.275431, 1.02479, 0.885707, - 0.165111, 0.243431, 1.07837, 0.904742, - 0.144363, 0.210921, 1.12783, 0.915604, - 0.121305, 0.17647, 1.17254, 0.930959, - 0.0962119, 0.143106, 1.21012, 0.948404, - 0.069969, 0.108112, 1.24474, 0.967012, - 0.0427586, 0.0708478, 1.27718, 0.984183, - 0.0147043, 0.032335, 1.3083, 0.999577, 0.0142165, - 0.00726867, 1.3382, 0.0229227, - 2.99799e-06, 0.148623, 4.62391e-07, 0.0232194, - 7.58796e-05, 0.15054, 1.17033e-05, 0.0232315, - 0.000303636, 0.15063, 4.68397e-05, 0.0232354, - 0.000683189, 0.150624, 0.000105472, 0.0232092, - 0.0012136, 0.150445, 0.000187744, 0.0232523, - 0.00189765, 0.150679, 0.000294847, 0.0232828, - 0.00273247, 0.150789, 0.000428013, 0.0233371, - 0.00371287, 0.150995, 0.000591134, 0.0234015, - 0.00484794, 0.15118, 0.000787642, 0.023514, - 0.00612877, 0.151562, 0.00102547, 0.023679, - 0.00756125, 0.152116, 0.00131351, 0.0239559, - 0.00914651, 0.153162, 0.00166594, 0.0244334, - 0.010904, 0.155133, 0.00210182, 0.025139, - 0.0128615, 0.158035, 0.00264406, 0.0262598, - 0.0150628, 0.162751, 0.00332923, 0.0277875, - 0.0175532, 0.168944, 0.00419773, 0.0298472, - 0.0203981, 0.176835, 0.00530034, 0.0325444, - 0.023655, 0.186686, 0.00669777, 0.0355581, - 0.0272982, 0.196248, 0.00842661, 0.0392841, - 0.0314457, 0.207352, 0.0105854, 0.0436815, - 0.0361157, 0.219279, 0.0132458, 0.0485272, - 0.0412932, 0.230728, 0.0164736, 0.0541574, - 0.0470337, 0.242994, 0.0203715, 0.0609479, - 0.0535002, 0.257042, 0.0250953, 0.0685228, - 0.0605409, 0.27102, 0.0306856, 0.0768042, - 0.0680553, 0.28406, 0.037193, 0.0864844, - 0.0765011, 0.299186, 0.0449795, 0.0969415, - 0.0852674, 0.3132, 0.0538316, 0.108478, - 0.0947333, 0.327138, 0.0641149, 0.121705, - 0.10481, 0.342345, 0.0759185, 0.136743, - 0.115474, 0.358472, 0.0894116, 0.152986, - 0.126536, 0.374067, 0.104562, 0.170397, - 0.138061, 0.388267, 0.121632, 0.191392, - 0.150203, 0.406467, 0.140996, 0.211566, - 0.161751, 0.418641, 0.161696, 0.233567, - 0.173407, 0.430418, 0.184557, 0.257769, - 0.185397, 0.44277, 0.210092, 0.28531, - 0.197048, 0.457191, 0.237827, 0.311726, - 0.20784, 0.464712, 0.267253, 0.340537, - 0.218345, 0.472539, 0.299332, 0.372921, - 0.228306, 0.482331, 0.333988, 0.402924, - 0.236665, 0.484378, 0.369722, 0.434475, - 0.244097, 0.484717, 0.407836, 0.469736, - 0.250547, 0.487093, 0.448465, 0.505045, - 0.25511, 0.485575, 0.490263, 0.540262, - 0.258444, 0.481225, 0.534495, 0.576347, - 0.259903, 0.473481, 0.579451, 0.608656, - 0.259572, 0.4603, 0.625604, 0.646679, - 0.257908, 0.450341, 0.674511, 0.679902, - 0.253663, 0.431561, 0.723269, 0.714159, - 0.247419, 0.412684, 0.773263, 0.745345, - 0.239122, 0.389388, 0.824182, 0.778248, - 0.228837, 0.365361, 0.876634, 0.807208, - 0.216197, 0.337667, 0.92945, 0.835019, - 0.201772, 0.307197, 0.985261, 0.860261, - 0.185291, 0.274205, 1.04299, 0.877601, - 0.165809, 0.240178, 1.09816, 0.898211, - 0.143897, 0.207571, 1.14694, 0.915789, - 0.119513, 0.174904, 1.19008, 0.931831, - 0.0932919, 0.141423, 1.2297, 0.949244, - 0.0656528, 0.105603, 1.26553, 0.967527, - 0.0370262, 0.0679551, 1.29986, 0.984139, - 0.00730117, 0.0283133, 1.33252, 0.999713, 0.0234648, - 0.0121785, 1.36397, 0.0152135, - 2.45447e-06, 0.122795, 3.04092e-07, 0.0151652, - 6.15778e-05, 0.122399, 7.6292e-06, 0.0151181, - 0.000245948, 0.122023, 3.04802e-05, 0.0151203, - 0.000553394, 0.12203, 6.86634e-05, 0.015125, - 0.000983841, 0.122037, 0.000122463, 0.0151427, - 0.00153774, 0.12214, 0.000192706, 0.0151708, - 0.0022103, 0.122237, 0.000281219, 0.0152115, - 0.00300741, 0.12238, 0.000390804, 0.0152877, - 0.00392494, 0.1227, 0.000526317, 0.015412, - 0.00496597, 0.123244, 0.00069443, 0.0156201, - 0.00613314, 0.124228, 0.00090547, 0.0159658, - 0.00744113, 0.125945, 0.0011732, 0.0165674, - 0.00892546, 0.129098, 0.00151888, 0.017487, - 0.010627, 0.133865, 0.00197007, 0.018839, - 0.0126043, 0.140682, 0.0025637, 0.020554, - 0.0148814, 0.148534, 0.00333637, 0.0226727, - 0.0175123, 0.157381, 0.00433738, 0.0251879, - 0.0205266, 0.166685, 0.00561664, 0.0283635, - 0.0240319, 0.177796, 0.00725563, 0.0318694, - 0.0279432, 0.188251, 0.00928811, 0.0361044, - 0.0324313, 0.200038, 0.011835, 0.0406656, - 0.0373527, 0.210685, 0.0149146, 0.0463846, - 0.0430132, 0.224182, 0.0187254, 0.0525696, - 0.0491013, 0.23634, 0.0232283, 0.0598083, - 0.0559175, 0.250013, 0.0286521, 0.0679437, - 0.0633657, 0.263981, 0.0350634, 0.0771181, - 0.0714602, 0.278072, 0.0425882, 0.0881273, - 0.0803502, 0.29511, 0.0514487, 0.0996628, - 0.0896903, 0.309976, 0.0615766, 0.112702, - 0.099644, 0.325611, 0.0732139, 0.126488, - 0.109829, 0.339321, 0.0862324, 0.142625, - 0.120859, 0.35574, 0.101275, 0.15953, - 0.131956, 0.369845, 0.117892, 0.176991, - 0.143145, 0.38146, 0.136205, 0.199715, - 0.155292, 0.40052, 0.157252, 0.220787, - 0.167066, 0.412055, 0.179966, 0.243697, - 0.178396, 0.423133, 0.204418, 0.272106, - 0.190433, 0.439524, 0.232141, 0.297637, - 0.201265, 0.447041, 0.261109, 0.325273, - 0.211834, 0.454488, 0.292627, 0.357219, - 0.221889, 0.465004, 0.326669, 0.387362, - 0.230729, 0.468527, 0.362426, 0.423131, - 0.23924, 0.475836, 0.401533, 0.45543, - 0.246067, 0.475017, 0.441902, 0.493393, - 0.251557, 0.478017, 0.484239, 0.526253, - 0.255571, 0.4709, 0.528586, 0.560554, - 0.257752, 0.463167, 0.574346, 0.599306, - 0.258076, 0.456452, 0.621655, 0.634541, - 0.256471, 0.443725, 0.670492, 0.668907, - 0.253283, 0.428719, 0.721943, 0.705619, - 0.247562, 0.411348, 0.772477, 0.739034, - 0.240626, 0.388939, 0.8264, 0.771408, - 0.231493, 0.36425, 0.881702, 0.803312, - 0.220125, 0.337321, 0.9385, 0.828457, - 0.206645, 0.305364, 0.997437, 0.854819, - 0.190664, 0.273715, 1.05693, 0.878666, - 0.171429, 0.242218, 1.11251, 0.898404, - 0.149235, 0.209556, 1.16398, 0.917416, - 0.12435, 0.176863, 1.21014, 0.933133, - 0.0972703, 0.142775, 1.25178, 0.95066, - 0.0683607, 0.106735, 1.29028, 0.968589, - 0.0378724, 0.0681609, 1.32703, 0.984776, - 0.00605712, 0.0273966, 1.36158, 0.99994, 0.0263276, - 0.0138124, 1.3943, 0.00867437, - 1.86005e-06, 0.0928979, 1.73682e-07, 0.00864003, - 4.66389e-05, 0.0925237, 4.35505e-06, 0.00864593, - 0.000186594, 0.0925806, 1.74322e-05, 0.00864095, - 0.000419639, 0.0924903, 3.92862e-05, 0.00863851, - 0.000746272, 0.0924589, 7.02598e-05, 0.00868531, - 0.00116456, 0.0929, 0.000111188, 0.00869667, - 0.00167711, 0.0928529, 0.000163867, 0.00874332, - 0.00228051, 0.0930914, 0.00023104, 0.00882709, - 0.00297864, 0.0935679, 0.00031741, 0.00898874, - 0.00377557, 0.0946165, 0.000430186, 0.00929346, - 0.00469247, 0.0967406, 0.000580383, 0.00978271, - 0.00575491, 0.100084, 0.000783529, 0.0105746, - 0.00701514, 0.105447, 0.00106304, 0.0116949, - 0.00851797, 0.112494, 0.00144685, 0.0130419, - 0.0102757, 0.119876, 0.00196439, 0.0148375, - 0.012381, 0.129034, 0.00266433, 0.0168725, - 0.01482, 0.137812, 0.00358364, 0.0193689, - 0.0176563, 0.147696, 0.00478132, 0.0222691, - 0.0209211, 0.157795, 0.00631721, 0.0256891, - 0.0246655, 0.168431, 0.00826346, 0.0294686, - 0.0288597, 0.178587, 0.0106714, 0.0340412, - 0.0336441, 0.190251, 0.0136629, 0.0393918, - 0.039033, 0.202999, 0.0173272, 0.0453947, - 0.0450087, 0.215655, 0.0217448, 0.0521936, - 0.0515461, 0.228686, 0.0269941, 0.0600279, - 0.058817, 0.242838, 0.033272, 0.0692398, - 0.0667228, 0.258145, 0.0406457, 0.0793832, - 0.0752401, 0.273565, 0.0492239, 0.0902297, - 0.0841851, 0.287735, 0.0590105, 0.102014, - 0.0936479, 0.301161, 0.0702021, 0.116054, - 0.103967, 0.317438, 0.0832001, 0.13191, - 0.114622, 0.334166, 0.0977951, 0.148239, - 0.125452, 0.348192, 0.113985, 0.165809, - 0.136453, 0.361094, 0.131928, 0.184616, - 0.147648, 0.373534, 0.151811, 0.207491, - 0.159607, 0.39101, 0.174476, 0.230106, - 0.171119, 0.402504, 0.198798, 0.257036, - 0.182906, 0.418032, 0.225796, 0.281172, - 0.193605, 0.425468, 0.254027, 0.312034, - 0.204771, 0.440379, 0.285713, 0.340402, - 0.214988, 0.445406, 0.319196, 0.370231, - 0.224711, 0.44968, 0.35537, 0.407105, - 0.233516, 0.460747, 0.393838, 0.439037, - 0.240801, 0.460624, 0.433747, 0.47781, - 0.24762, 0.465957, 0.477234, 0.510655, - 0.251823, 0.460054, 0.52044, 0.550584, - 0.255552, 0.459172, 0.567853, 0.585872, - 0.257036, 0.450311, 0.615943, 0.620466, - 0.257535, 0.437763, 0.667693, 0.660496, - 0.255248, 0.426639, 0.718988, 0.695578, - 0.251141, 0.409185, 0.772503, 0.732176, - 0.244718, 0.39015, 0.827023, 0.760782, - 0.236782, 0.362594, 0.885651, 0.79422, - 0.225923, 0.33711, 0.943756, 0.824521, - 0.213855, 0.308272, 1.00874, 0.854964, - 0.197723, 0.278529, 1.06764, 0.878065, - 0.179209, 0.246208, 1.12836, 0.899834, - 0.157569, 0.21329, 1.18318, 0.918815, - 0.133206, 0.181038, 1.23161, 0.934934, - 0.106545, 0.146993, 1.27644, 0.952115, - 0.0780574, 0.111175, 1.31842, 0.96906, - 0.0478279, 0.0728553, 1.35839, 0.985178, - 0.0160014, 0.032579, 1.39697, 1.00039, 0.0173126, - 0.0095256, 1.43312, 0.00384146, - 1.24311e-06, 0.0613583, 7.78271e-08, 0.00390023, - 3.14043e-05, 0.0622919, 1.96626e-06, 0.00389971, - 0.000125622, 0.0622632, 7.87379e-06, 0.00389491, - 0.000282352, 0.0620659, 1.778e-05, 0.00391618, - 0.000502512, 0.0624687, 3.20918e-05, 0.00392662, - 0.000784458, 0.0625113, 5.15573e-05, 0.00396053, - 0.00112907, 0.0628175, 7.78668e-05, 0.00401911, - 0.00153821, 0.0633286, 0.000113811, 0.00414994, - 0.0020208, 0.0646443, 0.00016445, 0.00441223, - 0.00260007, 0.0673886, 0.000237734, 0.00484427, - 0.0033097, 0.0716528, 0.000345929, 0.00549109, - 0.00418966, 0.0774998, 0.000505987, 0.00636293, - 0.00527331, 0.0844758, 0.000739208, 0.00746566, - 0.00660428, 0.0921325, 0.00107347, 0.00876625, - 0.00818826, 0.0997067, 0.00153691, 0.0103125, - 0.0100811, 0.107433, 0.00217153, 0.0123309, - 0.0123643, 0.117088, 0.00303427, 0.0146274, - 0.0150007, 0.126438, 0.00416018, 0.0172295, - 0.0180531, 0.135672, 0.00561513, 0.0204248, - 0.0215962, 0.146244, 0.007478, 0.0241597, - 0.0256234, 0.157481, 0.00981046, 0.0284693, - 0.0302209, 0.169125, 0.0127148, 0.033445, - 0.0353333, 0.181659, 0.0162453, 0.0391251, - 0.0410845, 0.1944, 0.0205417, 0.0454721, - 0.0473451, 0.207082, 0.0256333, 0.0530983, - 0.0542858, 0.221656, 0.0317036, 0.0615356, - 0.0618384, 0.236036, 0.0388319, 0.0703363, - 0.0697631, 0.248398, 0.046974, 0.0810391, - 0.0784757, 0.263611, 0.0565246, 0.0920144, - 0.0873488, 0.275857, 0.0671724, 0.105584, - 0.0973652, 0.292555, 0.0798105, 0.119506, - 0.107271, 0.306333, 0.0935945, 0.134434, - 0.117608, 0.318888, 0.109106, 0.153399, - 0.128938, 0.337552, 0.127074, 0.171258, - 0.139944, 0.349955, 0.14643, 0.191059, - 0.151288, 0.361545, 0.168, 0.215069, - 0.163018, 0.378421, 0.192082, 0.237838, - 0.174226, 0.38879, 0.217838, 0.266965, - 0.186063, 0.405857, 0.246931, 0.292827, - 0.196909, 0.414146, 0.277505, 0.324352, - 0.207473, 0.426955, 0.310711, 0.354427, - 0.217713, 0.433429, 0.346794, 0.389854, - 0.227183, 0.443966, 0.385237, 0.420749, - 0.235131, 0.44471, 0.424955, 0.459597, - 0.242786, 0.451729, 0.468446, 0.495316, - 0.248767, 0.45072, 0.513422, 0.534903, - 0.253351, 0.450924, 0.560618, 0.572369, - 0.256277, 0.445266, 0.609677, 0.612383, - 0.2576, 0.438798, 0.660995, 0.644037, - 0.256931, 0.421693, 0.713807, 0.686749, - 0.254036, 0.4109, 0.767616, 0.719814, - 0.249785, 0.390151, 0.82533, 0.754719, - 0.244283, 0.367847, 0.888311, 0.792022, - 0.235076, 0.345013, 0.948177, 0.822404, - 0.225061, 0.316193, 1.01661, 0.853084, - 0.211113, 0.287013, 1.08075, 0.879871, - 0.19449, 0.255424, 1.14501, 0.901655, - 0.174023, 0.222879, 1.20203, 0.919957, - 0.1509, 0.18989, 1.25698, 0.938412, - 0.124923, 0.15606, 1.30588, 0.953471, - 0.0968139, 0.120512, 1.3529, 0.970451, - 0.066734, 0.0828515, 1.3986, 0.985522, - 0.034734, 0.0424458, 1.44148, 1.00099, - 0.00102222, 0.000678929, 1.48398, 0.000965494, - 6.27338e-07, 0.0306409, 1.97672e-08, 0.00099168, - 1.58573e-05, 0.0314638, 4.99803e-07, 0.000991068, - 6.34012e-05, 0.031363, 2.00682e-06, 0.000974567, - 0.00014144, 0.03036, 4.57312e-06, 0.000998079, - 0.000252812, 0.031496, 8.60131e-06, 0.00102243, - 0.000396506, 0.0319955, 1.48288e-05, 0.00107877, - 0.000577593, 0.0331376, 2.49141e-05, 0.00121622, - 0.000816816, 0.0359396, 4.23011e-05, 0.0014455, - 0.00113761, 0.0399652, 7.24613e-05, 0.00178791, - 0.00156959, 0.0450556, 0.000123929, 0.00225668, - 0.00214064, 0.0508025, 0.000208531, 0.00285627, - 0.00287655, 0.0568443, 0.000341969, 0.0035991, - 0.00380271, 0.0630892, 0.000544158, 0.00455524, - 0.00496264, 0.0702204, 0.000842423, 0.00569143, - 0.0063793, 0.0773426, 0.00126704, 0.00716928, - 0.00813531, 0.0860839, 0.00186642, 0.00885307, - 0.0101946, 0.0944079, 0.00267014, 0.0109316, - 0.0126386, 0.103951, 0.00374033, 0.0133704, - 0.0154876, 0.113786, 0.0051304, 0.0161525, - 0.0187317, 0.123477, 0.00688858, 0.0194267, - 0.0224652, 0.133986, 0.00910557, 0.0230967, - 0.0265976, 0.143979, 0.0118074, 0.0273627, - 0.0312848, 0.154645, 0.0151266, 0.0323898, - 0.0365949, 0.166765, 0.0191791, 0.0379225, - 0.0422914, 0.177932, 0.0239236, 0.0447501, - 0.0487469, 0.19167, 0.0296568, 0.0519391, - 0.0556398, 0.203224, 0.0362924, 0.0599464, - 0.0631646, 0.215652, 0.0440585, 0.0702427, - 0.0714308, 0.232089, 0.0531619, 0.0806902, - 0.0800605, 0.245258, 0.0634564, 0.0923194, - 0.0892815, 0.258609, 0.0752481, 0.106938, - 0.09931, 0.276654, 0.0888914, 0.121238, - 0.109575, 0.289847, 0.104055, 0.138817, - 0.120461, 0.307566, 0.121266, 0.15595, - 0.131209, 0.320117, 0.139944, 0.178418, - 0.143049, 0.339677, 0.161591, 0.197875, - 0.154074, 0.349886, 0.184303, 0.224368, - 0.166307, 0.369352, 0.210669, 0.252213, - 0.178051, 0.386242, 0.238895, 0.277321, - 0.189335, 0.395294, 0.269182, 0.310332, - 0.200683, 0.412148, 0.302508, 0.338809, - 0.210856, 0.418266, 0.337264, 0.372678, - 0.220655, 0.428723, 0.374881, 0.405632, - 0.230053, 0.433887, 0.415656, 0.442293, - 0.237993, 0.439911, 0.457982, 0.477256, - 0.244897, 0.440175, 0.502831, 0.515592, - 0.250657, 0.441079, 0.550277, 0.550969, - 0.255459, 0.435219, 0.601102, 0.592883, - 0.257696, 0.432882, 0.651785, 0.629092, - 0.259894, 0.421054, 0.708961, 0.672033, - 0.258592, 0.41177, 0.763806, 0.709147, - 0.256525, 0.395267, 0.824249, 0.745367, - 0.254677, 0.375013, 0.8951, 0.784715, - 0.247892, 0.353906, 0.959317, 0.818107, - 0.240162, 0.327801, 1.03153, 0.847895, - 0.229741, 0.298821, 1.10601, 0.879603, - 0.213084, 0.269115, 1.164, 0.902605, - 0.195242, 0.236606, 1.22854, 0.922788, - 0.174505, 0.203442, 1.29017, 0.944831, - 0.150169, 0.169594, 1.34157, 0.959656, - 0.124099, 0.135909, 1.3956, 0.972399, - 0.0960626, 0.0990563, 1.45128, 0.986549, - 0.0657097, 0.0602348, 1.50312, 1.00013, - 0.0333558, 0.0186694, 1.55364, 6.19747e-06, - 1e-07, 0.00778326, 7.96756e-11, 2.37499e-08, - 9.99999e-08, 2.82592e-05, 1.14596e-10, 1.00292e-06, - 1.66369e-06, 0.000250354, 6.77492e-09, 3.50752e-06, - 6.37769e-06, 0.000357289, 6.31655e-08, 8.26445e-06, - 1.74689e-05, 0.000516179, 3.1851e-07, 2.42481e-05, - 4.50868e-05, 0.0010223, 1.30577e-06, 4.55631e-05, - 8.9044e-05, 0.00144302, 3.74587e-06, 9.71222e-05, - 0.000178311, 0.00241912, 1.02584e-05, 0.000171403, - 0.000313976, 0.00354938, 2.36481e-05, 0.000292747, - 0.000520026, 0.00513765, 4.96014e-05, 0.000789827, - 0.00118187, 0.0238621, 0.000139056, 0.00114093, - 0.00171827, 0.0286691, 0.000244093, 0.00176119, - 0.00249667, 0.0368565, 0.000420623, 0.0022233, - 0.00333742, 0.0400469, 0.00065673, 0.00343382, - 0.00481976, 0.0535751, 0.00109323, 0.00427602, - 0.00600755, 0.057099, 0.00155268, 0.00461435, - 0.00737637, 0.0551084, 0.00215031, 0.00695698, - 0.00971401, 0.0715767, 0.00316529, 0.00867619, - 0.0120943, 0.0793314, 0.00436995, 0.0106694, - 0.0148202, 0.0869391, 0.0058959, 0.0140351, - 0.0183501, 0.101572, 0.00798757, 0.0168939, - 0.022006, 0.11018, 0.0104233, 0.020197, - 0.0261568, 0.119041, 0.0134167, 0.0254702, - 0.0312778, 0.135404, 0.0173009, 0.0298384, - 0.0362469, 0.1437, 0.0215428, 0.035159, - 0.042237, 0.15512, 0.0268882, 0.0427685, - 0.0488711, 0.17128, 0.033235, 0.0494848, - 0.0557997, 0.181813, 0.0404443, 0.0592394, - 0.0635578, 0.198745, 0.0490043, 0.0681463, - 0.071838, 0.210497, 0.0588239, 0.0804753, - 0.0809297, 0.228864, 0.0702835, 0.0942205, - 0.0906488, 0.247008, 0.0834012, 0.106777, - 0.100216, 0.258812, 0.0975952, 0.124471, - 0.110827, 0.278617, 0.114162, 0.138389, - 0.121193, 0.287049, 0.131983, 0.159543, - 0.13253, 0.307151, 0.152541, 0.176432, - 0.143611, 0.31564, 0.174673, 0.201723, - 0.15548, 0.33538, 0.199842, 0.229721, - 0.167166, 0.355256, 0.227097, 0.250206, - 0.178238, 0.360047, 0.256014, 0.282118, - 0.189905, 0.378761, 0.28855, 0.312821, - 0.201033, 0.39181, 0.323348, 0.341482, - 0.211584, 0.397716, 0.360564, 0.377368, - 0.221314, 0.410141, 0.400004, 0.418229, - 0.230474, 0.423485, 0.442371, 0.444881, - 0.239443, 0.418874, 0.488796, 0.488899, - 0.245987, 0.427545, 0.535012, 0.520317, - 0.253948, 0.422147, 0.589678, 0.568566, - 0.256616, 0.42719, 0.637683, 0.599607, - 0.26376, 0.415114, 0.703363, 0.64222, - 0.268687, 0.408715, 0.771363, 0.685698, - 0.2694, 0.399722, 0.83574, 0.732327, - 0.266642, 0.388651, 0.897764, 0.769873, - 0.267712, 0.369198, 0.983312, 0.806733, - 0.263479, 0.346802, 1.06222, 0.843466, - 0.254575, 0.321368, 1.13477, 0.873008, - 0.242749, 0.29211, 1.20712, 0.908438, - 0.22725, 0.262143, 1.27465, 0.936321, - 0.207621, 0.228876, 1.33203, 0.950353, - 0.187932, 0.19484, 1.40439, 0.96442, - 0.165154, 0.163178, 1.4732, 0.979856, - 0.139302, 0.127531, 1.53574, 0.982561, - 0.11134, 0.0903457, 1.59982, 0.996389, - 0.0808124, 0.0489007, 1.6577 ]; + + const LTC_MAT_2 = [ 1, 0, 0, 0, 1, 7.91421e-31, 0, 0, 1, 1.04392e-24, 0, 0, 1, 3.49405e-21, 0, 0, 1, 1.09923e-18, 0, 0, 1, 9.47414e-17, 0, 0, 1, 3.59627e-15, 0, 0, 1, 7.72053e-14, 0, 0, 1, 1.08799e-12, 0, 0, 1, 1.10655e-11, 0, 0, 1, 8.65818e-11, 0, 0, 0.999998, 5.45037e-10, 0, 0, 0.999994, 2.85095e-09, 0, 0, 0.999989, 1.26931e-08, 0, 0, 0.999973, 4.89938e-08, 0, 0, 0.999947, 1.66347e-07, 0, 0, 0.999894, 5.02694e-07, 0, 0, 0.999798, 1.36532e-06, 0, 0, 0.999617, 3.35898e-06, 0, 0, 0.999234, 7.52126e-06, 0, 0, 0.998258, 1.52586e-05, 0, 0, 0.99504, 2.66207e-05, 0, 0, 0.980816, 2.36802e-05, 0, 0, 0.967553, 2.07684e-06, 0, 0, 0.966877, 4.03733e-06, 0, 0, 0.965752, 7.41174e-06, 0, 0, 0.96382, 1.27746e-05, 0, 0, 0.960306, 2.02792e-05, 0, 0, 0.953619, 2.80232e-05, 0, 0, 0.941103, 2.78816e-05, 0, 0, 0.926619, 1.60221e-05, 0, 0, 0.920983, 2.35164e-05, 0, 0, 0.912293, 3.11924e-05, 0, 0.0158731, 0.899277, 3.48118e-05, 0, 0.0476191, 0.880884, 2.6041e-05, 0, 0.0793651, 0.870399, 3.38726e-05, 0, 0.111111, 0.856138, 3.92906e-05, 0, 0.142857, 0.837436, 3.72874e-05, 0, 0.174603, 0.820973, 3.92558e-05, 0, 0.206349, 0.803583, 4.34658e-05, 0, 0.238095, 0.782168, 4.0256e-05, 0, 0.269841, 0.764107, 4.48159e-05, 0, 0.301587, 0.743092, 4.57627e-05, 0, 0.333333, 0.721626, 4.55314e-05, 0, 0.365079, 0.700375, 4.77335e-05, 0, 0.396825, 0.677334, 4.61072e-05, 0, 0.428571, 0.655702, 4.84393e-05, 0, 0.460317, 0.632059, 4.64583e-05, 0, 0.492064, 0.610125, 4.83923e-05, 0, 0.52381, 0.58653, 4.64342e-05, 0, 0.555556, 0.564508, 4.77033e-05, 0, 0.587302, 0.541405, 4.59263e-05, 0, 0.619048, 0.519556, 4.6412e-05, 0, 0.650794, 0.497292, 4.48913e-05, 0, 0.68254, 0.475898, 4.45789e-05, 0, 0.714286, 0.454722, 4.33496e-05, 0, 0.746032, 0.434042, 4.23054e-05, 0, 0.777778, 0.414126, 4.13737e-05, 0, 0.809524, 0.394387, 3.97265e-05, 0, 0.84127, 0.375841, 3.90709e-05, 0, 0.873016, 0.357219, 3.69938e-05, 0, 0.904762, 0.340084, 3.65618e-05, 0, 0.936508, 0.322714, 3.42533e-05, 0, 0.968254, 0.306974, 3.39596e-05, 0, 1, 1, 1.01524e-18, 0, 0, 1, 1.0292e-18, 0, 0, 1, 1.30908e-18, 0, 0, 1, 4.73331e-18, 0, 0, 1, 6.25319e-17, 0, 0, 1, 1.07932e-15, 0, 0, 1, 1.63779e-14, 0, 0, 1, 2.03198e-13, 0, 0, 1, 2.04717e-12, 0, 0, 0.999999, 1.68995e-11, 0, 0, 0.999998, 1.15855e-10, 0, 0, 0.999996, 6.6947e-10, 0, 0, 0.999991, 3.30863e-09, 0, 0, 0.999983, 1.41737e-08, 0, 0, 0.999968, 5.32626e-08, 0, 0, 0.99994, 1.77431e-07, 0, 0, 0.999891, 5.28835e-07, 0, 0, 0.999797, 1.42169e-06, 0, 0, 0.999617, 3.47057e-06, 0, 0, 0.999227, 7.7231e-06, 0, 0, 0.998239, 1.55753e-05, 0, 0, 0.994937, 2.68495e-05, 0, 0, 0.980225, 2.13742e-05, 0, 0, 0.967549, 2.1631e-06, 0, 0, 0.966865, 4.17989e-06, 0, 0, 0.965739, 7.63341e-06, 0, 0, 0.963794, 1.30892e-05, 0, 0, 0.960244, 2.06456e-05, 0, 0, 0.953495, 2.82016e-05, 0, 0.000148105, 0.940876, 2.71581e-05, 0, 0.002454, 0.926569, 1.64159e-05, 0, 0.00867491, 0.920905, 2.39521e-05, 0, 0.01956, 0.912169, 3.15127e-05, 0, 0.035433, 0.899095, 3.46626e-05, 0, 0.056294, 0.882209, 2.90223e-05, 0, 0.0818191, 0.870272, 3.42992e-05, 0, 0.111259, 0.855977, 3.94164e-05, 0, 0.142857, 0.837431, 3.72343e-05, 0, 0.174603, 0.820826, 3.96691e-05, 0, 0.206349, 0.803408, 4.35395e-05, 0, 0.238095, 0.782838, 4.19579e-05, 0, 0.269841, 0.763941, 4.50953e-05, 0, 0.301587, 0.742904, 4.55847e-05, 0, 0.333333, 0.721463, 4.58833e-05, 0, 0.365079, 0.700197, 4.77159e-05, 0, 0.396825, 0.677501, 4.70641e-05, 0, 0.428571, 0.655527, 4.84732e-05, 0, 0.460317, 0.6324, 4.76834e-05, 0, 0.492064, 0.609964, 4.84213e-05, 0, 0.52381, 0.586839, 4.75541e-05, 0, 0.555556, 0.564353, 4.76951e-05, 0, 0.587302, 0.541589, 4.67611e-05, 0, 0.619048, 0.519413, 4.63493e-05, 0, 0.650794, 0.497337, 4.53994e-05, 0, 0.68254, 0.475797, 4.45308e-05, 0, 0.714286, 0.454659, 4.35787e-05, 0, 0.746032, 0.434065, 4.24839e-05, 0, 0.777778, 0.414018, 4.1436e-05, 0, 0.809524, 0.39455, 4.01902e-05, 0, 0.84127, 0.375742, 3.90813e-05, 0, 0.873016, 0.357501, 3.77116e-05, 0, 0.904762, 0.339996, 3.6535e-05, 0, 0.936508, 0.323069, 3.51265e-05, 0, 0.968254, 0.306897, 3.39112e-05, 0, 1, 1, 1.0396e-15, 0, 0, 1, 1.04326e-15, 0, 0, 1, 1.10153e-15, 0, 0, 1, 1.44668e-15, 0, 0, 1, 3.4528e-15, 0, 0, 1, 1.75958e-14, 0, 0, 1, 1.2627e-13, 0, 0, 1, 9.36074e-13, 0, 0, 1, 6.45742e-12, 0, 0, 0.999998, 4.01228e-11, 0, 0, 0.999997, 2.22338e-10, 0, 0, 0.999995, 1.0967e-09, 0, 0, 0.999991, 4.82132e-09, 0, 0, 0.999981, 1.89434e-08, 0, 0, 0.999967, 6.67716e-08, 0, 0, 0.999938, 2.12066e-07, 0, 0, 0.999886, 6.0977e-07, 0, 0, 0.999792, 1.59504e-06, 0, 0, 0.999608, 3.81191e-06, 0, 0, 0.999209, 8.33727e-06, 0, 0, 0.998179, 1.65288e-05, 0, 0, 0.994605, 2.74387e-05, 0, 0, 0.979468, 1.67316e-05, 0, 0, 0.967529, 2.42877e-06, 0, 0, 0.966836, 4.61696e-06, 0, 0, 0.96569, 8.30977e-06, 0, 0, 0.963706, 1.40427e-05, 0, 2.44659e-06, 0.960063, 2.17353e-05, 0, 0.000760774, 0.953113, 2.86606e-05, 0, 0.00367261, 0.940192, 2.47691e-05, 0, 0.00940263, 0.927731, 1.95814e-05, 0, 0.018333, 0.920669, 2.52531e-05, 0, 0.0306825, 0.911799, 3.24277e-05, 0, 0.0465556, 0.89857, 3.40982e-05, 0, 0.0659521, 0.883283, 3.19622e-05, 0, 0.0887677, 0.86989, 3.5548e-05, 0, 0.114784, 0.855483, 3.97143e-05, 0, 0.143618, 0.837987, 3.91665e-05, 0, 0.174606, 0.820546, 4.11306e-05, 0, 0.206349, 0.802878, 4.36753e-05, 0, 0.238095, 0.783402, 4.44e-05, 0, 0.269841, 0.763439, 4.58726e-05, 0, 0.301587, 0.742925, 4.67097e-05, 0, 0.333333, 0.721633, 4.78887e-05, 0, 0.365079, 0.69985, 4.81251e-05, 0, 0.396825, 0.67783, 4.91811e-05, 0, 0.428571, 0.655126, 4.88199e-05, 0, 0.460318, 0.632697, 4.96025e-05, 0, 0.492064, 0.609613, 4.8829e-05, 0, 0.52381, 0.587098, 4.92754e-05, 0, 0.555556, 0.564119, 4.82625e-05, 0, 0.587302, 0.541813, 4.82807e-05, 0, 0.619048, 0.519342, 4.71552e-05, 0, 0.650794, 0.497514, 4.66765e-05, 0, 0.68254, 0.475879, 4.55582e-05, 0, 0.714286, 0.454789, 4.46007e-05, 0, 0.746032, 0.434217, 4.35382e-05, 0, 0.777778, 0.414086, 4.21753e-05, 0, 0.809524, 0.394744, 4.12093e-05, 0, 0.84127, 0.375782, 3.96634e-05, 0, 0.873016, 0.357707, 3.86419e-05, 0, 0.904762, 0.340038, 3.70345e-05, 0, 0.936508, 0.323284, 3.59725e-05, 0, 0.968254, 0.306954, 3.436e-05, 0, 1, 1, 5.99567e-14, 0, 0, 1, 6.00497e-14, 0, 0, 1, 6.14839e-14, 0, 0, 1, 6.86641e-14, 0, 0, 1, 9.72658e-14, 0, 0, 1, 2.21271e-13, 0, 0, 1, 8.33195e-13, 0, 0, 1, 4.03601e-12, 0, 0, 0.999999, 2.06001e-11, 0, 0, 0.999998, 1.01739e-10, 0, 0, 0.999997, 4.70132e-10, 0, 0, 0.999993, 2.00436e-09, 0, 0, 0.999988, 7.83682e-09, 0, 0, 0.999979, 2.80338e-08, 0, 0, 0.999962, 9.17033e-08, 0, 0, 0.999933, 2.74514e-07, 0, 0, 0.999881, 7.53201e-07, 0, 0, 0.999783, 1.89826e-06, 0, 0, 0.999594, 4.40279e-06, 0, 0, 0.999178, 9.3898e-06, 0, 0, 0.998073, 1.81265e-05, 0, 0, 0.993993, 2.80487e-05, 0, 0, 0.979982, 1.49422e-05, 0, 0, 0.968145, 3.78481e-06, 0, 0, 0.966786, 5.3771e-06, 0, 0, 0.965611, 9.47508e-06, 0, 3.88934e-05, 0.963557, 1.56616e-05, 0, 0.0009693, 0.959752, 2.35144e-05, 0, 0.00370329, 0.952461, 2.91568e-05, 0, 0.00868428, 0.940193, 2.40102e-05, 0, 0.0161889, 0.929042, 2.31235e-05, 0, 0.0263948, 0.920266, 2.73968e-05, 0, 0.0394088, 0.911178, 3.37915e-05, 0, 0.0552818, 0.897873, 3.33629e-05, 0, 0.0740138, 0.884053, 3.51405e-05, 0, 0.0955539, 0.869455, 3.78034e-05, 0, 0.119795, 0.854655, 3.99378e-05, 0, 0.14656, 0.838347, 4.19108e-05, 0, 0.175573, 0.820693, 4.40831e-05, 0, 0.206388, 0.802277, 4.45599e-05, 0, 0.238095, 0.783634, 4.72691e-05, 0, 0.269841, 0.763159, 4.76984e-05, 0, 0.301587, 0.742914, 4.91487e-05, 0, 0.333333, 0.721662, 5.02312e-05, 0, 0.365079, 0.699668, 5.02817e-05, 0, 0.396825, 0.677839, 5.1406e-05, 0, 0.428571, 0.655091, 5.11095e-05, 0, 0.460317, 0.632665, 5.16067e-05, 0, 0.492064, 0.609734, 5.12255e-05, 0, 0.52381, 0.587043, 5.10263e-05, 0, 0.555556, 0.564298, 5.0565e-05, 0, 0.587302, 0.541769, 4.97951e-05, 0, 0.619048, 0.519529, 4.92698e-05, 0, 0.650794, 0.497574, 4.82066e-05, 0, 0.68254, 0.476028, 4.73689e-05, 0, 0.714286, 0.454961, 4.61941e-05, 0, 0.746032, 0.434341, 4.50618e-05, 0, 0.777778, 0.414364, 4.38355e-05, 0, 0.809524, 0.394832, 4.24196e-05, 0, 0.84127, 0.376109, 4.12563e-05, 0, 0.873016, 0.35779, 3.96226e-05, 0, 0.904762, 0.340379, 3.84886e-05, 0, 0.936508, 0.323385, 3.68214e-05, 0, 0.968254, 0.307295, 3.56636e-05, 0, 1, 1, 1.06465e-12, 0, 0, 1, 1.06555e-12, 0, 0, 1, 1.07966e-12, 0, 0, 1, 1.14601e-12, 0, 0, 1, 1.37123e-12, 0, 0, 1, 2.1243e-12, 0, 0, 0.999999, 4.89653e-12, 0, 0, 0.999999, 1.60283e-11, 0, 0, 0.999998, 6.2269e-11, 0, 0, 0.999997, 2.51859e-10, 0, 0, 0.999996, 9.96192e-10, 0, 0, 0.999992, 3.74531e-09, 0, 0, 0.999986, 1.32022e-08, 0, 0, 0.999975, 4.33315e-08, 0, 0, 0.999959, 1.31956e-07, 0, 0, 0.999927, 3.72249e-07, 0, 0, 0.999871, 9.72461e-07, 0, 0, 0.999771, 2.35343e-06, 0, 0, 0.999572, 5.2768e-06, 0, 0, 0.999133, 1.09237e-05, 0, 0, 0.997912, 2.03675e-05, 0, 0, 0.993008, 2.79396e-05, 0, 0, 0.980645, 1.39604e-05, 0, 0, 0.970057, 6.46596e-06, 0, 0, 0.966717, 6.5089e-06, 0, 4.74145e-05, 0.965497, 1.11863e-05, 0, 0.00089544, 0.96334, 1.79857e-05, 0, 0.0032647, 0.959294, 2.59045e-05, 0, 0.0075144, 0.951519, 2.92327e-05, 0, 0.0138734, 0.940517, 2.49769e-05, 0, 0.0224952, 0.93014, 2.6803e-05, 0, 0.0334828, 0.91972, 3.03656e-05, 0, 0.0468973, 0.910294, 3.53323e-05, 0, 0.0627703, 0.897701, 3.51002e-05, 0, 0.0811019, 0.884522, 3.88104e-05, 0, 0.10186, 0.869489, 4.12932e-05, 0, 0.124985, 0.853983, 4.15781e-05, 0, 0.150372, 0.838425, 4.54066e-05, 0, 0.177868, 0.820656, 4.71624e-05, 0, 0.207245, 0.801875, 4.75243e-05, 0, 0.238143, 0.783521, 5.05621e-05, 0, 0.269841, 0.763131, 5.0721e-05, 0, 0.301587, 0.74261, 5.23293e-05, 0, 0.333333, 0.72148, 5.28699e-05, 0, 0.365079, 0.699696, 5.38677e-05, 0, 0.396825, 0.677592, 5.39255e-05, 0, 0.428571, 0.65525, 5.46367e-05, 0, 0.460317, 0.632452, 5.41348e-05, 0, 0.492064, 0.609903, 5.44976e-05, 0, 0.52381, 0.586928, 5.36201e-05, 0, 0.555556, 0.564464, 5.35185e-05, 0, 0.587302, 0.541801, 5.24949e-05, 0, 0.619048, 0.519681, 5.1812e-05, 0, 0.650794, 0.497685, 5.07687e-05, 0, 0.68254, 0.47622, 4.96243e-05, 0, 0.714286, 0.455135, 4.85714e-05, 0, 0.746032, 0.4346, 4.71847e-05, 0, 0.777778, 0.414564, 4.59294e-05, 0, 0.809524, 0.395165, 4.44705e-05, 0, 0.84127, 0.376333, 4.30772e-05, 0, 0.873016, 0.358197, 4.16229e-05, 0, 0.904762, 0.34064, 4.01019e-05, 0, 0.936508, 0.323816, 3.86623e-05, 0, 0.968254, 0.307581, 3.70933e-05, 0, 1, 1, 9.91541e-12, 0, 0, 1, 9.92077e-12, 0, 0, 1, 1.00041e-11, 0, 0, 1, 1.0385e-11, 0, 0, 1, 1.15777e-11, 0, 0, 1, 1.50215e-11, 0, 0, 0.999999, 2.54738e-11, 0, 0, 0.999999, 5.98822e-11, 0, 0, 0.999998, 1.79597e-10, 0, 0, 0.999997, 6.02367e-10, 0, 0, 0.999994, 2.06835e-09, 0, 0, 0.99999, 6.94952e-09, 0, 0, 0.999984, 2.23363e-08, 0, 0, 0.999972, 6.78578e-08, 0, 0, 0.999952, 1.93571e-07, 0, 0, 0.999919, 5.16594e-07, 0, 0, 0.99986, 1.28739e-06, 0, 0, 0.999753, 2.99298e-06, 0, 0, 0.999546, 6.48258e-06, 0, 0, 0.999074, 1.29985e-05, 0, 0, 0.997671, 2.32176e-05, 0, 0, 0.991504, 2.56701e-05, 0, 0, 0.981148, 1.31141e-05, 0, 0, 0.971965, 8.69048e-06, 0, 2.80182e-05, 0.966624, 8.08301e-06, 0, 0.000695475, 0.965344, 1.35235e-05, 0, 0.00265522, 0.963048, 2.10592e-05, 0, 0.00622975, 0.958673, 2.87473e-05, 0, 0.0116234, 0.950262, 2.81379e-05, 0, 0.018976, 0.940836, 2.71089e-05, 0, 0.0283844, 0.930996, 3.0926e-05, 0, 0.0399151, 0.919848, 3.48359e-05, 0, 0.0536063, 0.909136, 3.66092e-05, 0, 0.0694793, 0.897554, 3.84162e-05, 0, 0.0875342, 0.884691, 4.30971e-05, 0, 0.107749, 0.869414, 4.47803e-05, 0, 0.130087, 0.853462, 4.52858e-05, 0, 0.154481, 0.838187, 4.95769e-05, 0, 0.180833, 0.820381, 5.02709e-05, 0, 0.209005, 0.801844, 5.22713e-05, 0, 0.238791, 0.783061, 5.41505e-05, 0, 0.269869, 0.763205, 5.53712e-05, 0, 0.301587, 0.742362, 5.64909e-05, 0, 0.333333, 0.721393, 5.72646e-05, 0, 0.365079, 0.699676, 5.81012e-05, 0, 0.396825, 0.677395, 5.8096e-05, 0, 0.428571, 0.655208, 5.85766e-05, 0, 0.460317, 0.632451, 5.83602e-05, 0, 0.492064, 0.609839, 5.80234e-05, 0, 0.52381, 0.587093, 5.77161e-05, 0, 0.555556, 0.564467, 5.68447e-05, 0, 0.587302, 0.542043, 5.63166e-05, 0, 0.619048, 0.519826, 5.5156e-05, 0, 0.650794, 0.497952, 5.41682e-05, 0, 0.68254, 0.476477, 5.28971e-05, 0, 0.714286, 0.455412, 5.14952e-05, 0, 0.746032, 0.434926, 5.02222e-05, 0, 0.777778, 0.4149, 4.85779e-05, 0, 0.809524, 0.395552, 4.72242e-05, 0, 0.84127, 0.376712, 4.54891e-05, 0, 0.873016, 0.358622, 4.40924e-05, 0, 0.904762, 0.341048, 4.22984e-05, 0, 0.936508, 0.324262, 4.08582e-05, 0, 0.968254, 0.308013, 3.90839e-05, 0, 1, 1, 6.13913e-11, 0, 0, 1, 6.14145e-11, 0, 0, 1, 6.17708e-11, 0, 0, 1, 6.33717e-11, 0, 0, 1, 6.81648e-11, 0, 0, 1, 8.08291e-11, 0, 0, 1, 1.14608e-10, 0, 0, 0.999998, 2.10507e-10, 0, 0, 0.999997, 4.99595e-10, 0, 0, 0.999995, 1.39897e-09, 0, 0, 0.999994, 4.19818e-09, 0, 0, 0.999988, 1.27042e-08, 0, 0, 0.999979, 3.75153e-08, 0, 0, 0.999965, 1.06206e-07, 0, 0, 0.999945, 2.85381e-07, 0, 0, 0.999908, 7.23611e-07, 0, 0, 0.999846, 1.7255e-06, 0, 0, 0.999733, 3.86104e-06, 0, 0, 0.999511, 8.08493e-06, 0, 0, 0.998993, 1.56884e-05, 0, 0, 0.997326, 2.65538e-05, 0, 0, 0.989706, 2.06466e-05, 0, 0, 0.981713, 1.30756e-05, 0, 7.0005e-06, 0.973636, 1.06473e-05, 0, 0.000464797, 0.966509, 1.0194e-05, 0, 0.00201743, 0.965149, 1.65881e-05, 0, 0.00497549, 0.962669, 2.49147e-05, 0, 0.00953262, 0.95786, 3.17449e-05, 0, 0.0158211, 0.949334, 2.81045e-05, 0, 0.0239343, 0.941041, 3.03263e-05, 0, 0.0339372, 0.931575, 3.56754e-05, 0, 0.0458738, 0.920102, 3.97075e-05, 0, 0.059772, 0.908002, 3.84886e-05, 0, 0.075645, 0.897269, 4.3027e-05, 0, 0.0934929, 0.884559, 4.79925e-05, 0, 0.113302, 0.869161, 4.8246e-05, 0, 0.135045, 0.853342, 5.09505e-05, 0, 0.158678, 0.837633, 5.42846e-05, 0, 0.184136, 0.820252, 5.54139e-05, 0, 0.211325, 0.801872, 5.81412e-05, 0, 0.240113, 0.782418, 5.85535e-05, 0, 0.270306, 0.7631, 6.10923e-05, 0, 0.301594, 0.742183, 6.13678e-05, 0, 0.333333, 0.721098, 6.27275e-05, 0, 0.365079, 0.699512, 6.29413e-05, 0, 0.396825, 0.677372, 6.36351e-05, 0, 0.428571, 0.655059, 6.33555e-05, 0, 0.460317, 0.632567, 6.36513e-05, 0, 0.492064, 0.609784, 6.28965e-05, 0, 0.52381, 0.587237, 6.25546e-05, 0, 0.555556, 0.564525, 6.15825e-05, 0, 0.587302, 0.542181, 6.05048e-05, 0, 0.619048, 0.520017, 5.96329e-05, 0, 0.650794, 0.498204, 5.81516e-05, 0, 0.68254, 0.476742, 5.69186e-05, 0, 0.714286, 0.455803, 5.53833e-05, 0, 0.746032, 0.435251, 5.37807e-05, 0, 0.777778, 0.415374, 5.22025e-05, 0, 0.809524, 0.395921, 5.03421e-05, 0, 0.84127, 0.377253, 4.88211e-05, 0, 0.873016, 0.359021, 4.68234e-05, 0, 0.904762, 0.341637, 4.53269e-05, 0, 0.936508, 0.3247, 4.33014e-05, 0, 0.968254, 0.308625, 4.18007e-05, 0, 1, 1, 2.86798e-10, 0, 0, 1, 2.86877e-10, 0, 0, 1, 2.88094e-10, 0, 0, 1, 2.93506e-10, 0, 0, 1, 3.09262e-10, 0, 0, 0.999999, 3.48593e-10, 0, 0, 0.999999, 4.44582e-10, 0, 0, 0.999998, 6.88591e-10, 0, 0, 0.999996, 1.34391e-09, 0, 0, 0.999993, 3.17438e-09, 0, 0, 0.999989, 8.35609e-09, 0, 0, 0.999983, 2.28677e-08, 0, 0, 0.999974, 6.23361e-08, 0, 0, 0.999959, 1.65225e-07, 0, 0, 0.999936, 4.19983e-07, 0, 0, 0.999896, 1.01546e-06, 0, 0, 0.99983, 2.32376e-06, 0, 0, 0.999709, 5.0156e-06, 0, 0, 0.999469, 1.0167e-05, 0, 0, 0.998886, 1.90775e-05, 0, 0, 0.996819, 3.00511e-05, 0, 0, 0.988837, 1.85092e-05, 0, 1.68222e-07, 0.982178, 1.34622e-05, 0, 0.000259622, 0.975017, 1.25961e-05, 0, 0.00142595, 0.967101, 1.3507e-05, 0, 0.00382273, 0.964905, 2.05003e-05, 0, 0.00764164, 0.96218, 2.9546e-05, 0, 0.0130121, 0.956821, 3.43738e-05, 0, 0.0200253, 0.948829, 3.05063e-05, 0, 0.0287452, 0.941092, 3.46487e-05, 0, 0.039218, 0.931883, 4.12061e-05, 0, 0.0514748, 0.920211, 4.44651e-05, 0, 0.0655351, 0.907307, 4.31252e-05, 0, 0.0814082, 0.89684, 4.90382e-05, 0, 0.0990939, 0.884119, 5.3334e-05, 0, 0.118583, 0.869148, 5.4114e-05, 0, 0.139856, 0.853377, 5.78536e-05, 0, 0.162882, 0.836753, 5.92285e-05, 0, 0.187615, 0.820063, 6.22787e-05, 0, 0.213991, 0.801694, 6.45492e-05, 0, 0.241918, 0.782116, 6.5353e-05, 0, 0.271267, 0.762673, 6.74344e-05, 0, 0.301847, 0.742133, 6.82788e-05, 0, 0.333333, 0.720779, 6.91959e-05, 0, 0.365079, 0.699386, 6.96817e-05, 0, 0.396826, 0.67732, 6.99583e-05, 0, 0.428572, 0.654888, 6.98447e-05, 0, 0.460318, 0.632499, 6.94063e-05, 0, 0.492064, 0.609825, 6.91612e-05, 0, 0.52381, 0.587287, 6.81576e-05, 0, 0.555556, 0.564743, 6.74138e-05, 0, 0.587302, 0.542409, 6.61617e-05, 0, 0.619048, 0.520282, 6.47785e-05, 0, 0.650794, 0.498506, 6.33836e-05, 0, 0.68254, 0.477102, 6.15905e-05, 0, 0.714286, 0.456167, 6.01013e-05, 0, 0.746032, 0.435728, 5.81457e-05, 0, 0.777778, 0.415809, 5.64215e-05, 0, 0.809524, 0.396517, 5.44997e-05, 0, 0.84127, 0.377737, 5.25061e-05, 0, 0.873016, 0.359698, 5.06831e-05, 0, 0.904762, 0.342164, 4.8568e-05, 0, 0.936508, 0.325417, 4.67826e-05, 0, 0.968254, 0.309186, 4.46736e-05, 0, 1, 1, 1.09018e-09, 0, 0, 1, 1.0904e-09, 0, 0, 1, 1.09393e-09, 0, 0, 1, 1.1095e-09, 0, 0, 1, 1.154e-09, 0, 0, 1, 1.26089e-09, 0, 0, 0.999999, 1.5059e-09, 0, 0, 0.999997, 2.07899e-09, 0, 0, 0.999994, 3.48164e-09, 0, 0, 0.999993, 7.05728e-09, 0, 0, 0.999987, 1.63692e-08, 0, 0, 0.999981, 4.06033e-08, 0, 0, 0.999969, 1.0245e-07, 0, 0, 0.999953, 2.55023e-07, 0, 0, 0.999925, 6.1511e-07, 0, 0, 0.999881, 1.42218e-06, 0, 0, 0.99981, 3.13086e-06, 0, 0, 0.99968, 6.53119e-06, 0, 0, 0.999418, 1.2832e-05, 0, 0, 0.998748, 2.32497e-05, 0, 0, 0.996066, 3.29522e-05, 0, 0, 0.988379, 1.79613e-05, 0, 0.000108799, 0.982567, 1.43715e-05, 0, 0.000921302, 0.976097, 1.48096e-05, 0, 0.00280738, 0.968475, 1.78905e-05, 0, 0.00596622, 0.964606, 2.53921e-05, 0, 0.0105284, 0.961564, 3.48623e-05, 0, 0.0165848, 0.955517, 3.57612e-05, 0, 0.0242, 0.948381, 3.43493e-05, 0, 0.03342, 0.941095, 4.05849e-05, 0, 0.0442777, 0.931923, 4.75394e-05, 0, 0.0567958, 0.91996, 4.84328e-05, 0, 0.0709879, 0.907419, 5.02146e-05, 0, 0.086861, 0.89618, 5.61654e-05, 0, 0.104415, 0.88337, 5.87612e-05, 0, 0.123643, 0.869046, 6.18057e-05, 0, 0.144531, 0.853278, 6.57392e-05, 0, 0.167057, 0.836091, 6.6303e-05, 0, 0.191188, 0.819644, 7.04445e-05, 0, 0.216878, 0.801246, 7.14071e-05, 0, 0.244062, 0.782031, 7.40093e-05, 0, 0.272649, 0.762066, 7.4685e-05, 0, 0.302509, 0.741964, 7.66647e-05, 0, 0.333442, 0.720554, 7.66328e-05, 0, 0.365079, 0.699098, 7.77857e-05, 0, 0.396826, 0.677189, 7.74633e-05, 0, 0.428572, 0.65484, 7.76235e-05, 0, 0.460318, 0.632496, 7.70316e-05, 0, 0.492064, 0.609908, 7.62669e-05, 0, 0.52381, 0.587312, 7.53972e-05, 0, 0.555556, 0.564938, 7.39994e-05, 0, 0.587302, 0.542577, 7.28382e-05, 0, 0.619048, 0.52062, 7.1112e-05, 0, 0.650794, 0.498819, 6.94004e-05, 0, 0.68254, 0.477555, 6.75575e-05, 0, 0.714286, 0.456568, 6.53449e-05, 0, 0.746032, 0.436278, 6.36068e-05, 0, 0.777778, 0.41637, 6.13466e-05, 0, 0.809524, 0.397144, 5.94177e-05, 0, 0.84127, 0.378412, 5.70987e-05, 0, 0.873016, 0.360376, 5.50419e-05, 0, 0.904762, 0.342906, 5.27422e-05, 0, 0.936508, 0.326136, 5.06544e-05, 0, 0.968254, 0.30997, 4.84307e-05, 0, 1, 1, 3.54014e-09, 0, 0, 1, 3.54073e-09, 0, 0, 1, 3.54972e-09, 0, 0, 1, 3.58929e-09, 0, 0, 1, 3.70093e-09, 0, 0, 0.999999, 3.96194e-09, 0, 0, 0.999998, 4.53352e-09, 0, 0, 0.999997, 5.78828e-09, 0, 0, 0.999994, 8.63812e-09, 0, 0, 0.999991, 1.53622e-08, 0, 0, 0.999985, 3.16356e-08, 0, 0, 0.999977, 7.12781e-08, 0, 0, 0.999964, 1.66725e-07, 0, 0, 0.999945, 3.90501e-07, 0, 0, 0.999912, 8.95622e-07, 0, 0, 0.999866, 1.98428e-06, 0, 0, 0.999786, 4.21038e-06, 0, 0, 0.999647, 8.50239e-06, 0, 0, 0.999356, 1.62059e-05, 0, 0, 0.998563, 2.82652e-05, 0, 0, 0.994928, 3.36309e-05, 0, 2.44244e-05, 0.987999, 1.78458e-05, 0, 0.000523891, 0.982893, 1.59162e-05, 0, 0.00194729, 0.977044, 1.78056e-05, 0, 0.00451099, 0.969972, 2.30624e-05, 0, 0.00835132, 0.964237, 3.13922e-05, 0, 0.013561, 0.960791, 4.06145e-05, 0, 0.0202056, 0.954292, 3.72796e-05, 0, 0.0283321, 0.948052, 4.03199e-05, 0, 0.0379739, 0.940938, 4.79537e-05, 0, 0.0491551, 0.931689, 5.45292e-05, 0, 0.0618918, 0.91987, 5.4038e-05, 0, 0.0761941, 0.907665, 5.89909e-05, 0, 0.0920672, 0.895281, 6.42651e-05, 0, 0.109511, 0.882621, 6.59707e-05, 0, 0.12852, 0.86873, 7.09973e-05, 0, 0.149085, 0.853008, 7.42221e-05, 0, 0.171189, 0.835944, 7.61754e-05, 0, 0.194809, 0.818949, 7.97052e-05, 0, 0.21991, 0.800951, 8.12434e-05, 0, 0.246447, 0.781847, 8.38075e-05, 0, 0.274352, 0.761649, 8.4501e-05, 0, 0.303535, 0.74152, 8.60258e-05, 0, 0.333857, 0.720495, 8.66233e-05, 0, 0.365104, 0.698742, 8.68326e-05, 0, 0.396826, 0.677096, 8.7133e-05, 0, 0.428572, 0.654782, 8.63497e-05, 0, 0.460318, 0.632335, 8.60206e-05, 0, 0.492064, 0.610031, 8.49337e-05, 0, 0.52381, 0.587457, 8.38279e-05, 0, 0.555556, 0.56513, 8.2309e-05, 0, 0.587302, 0.542877, 8.03542e-05, 0, 0.619048, 0.5209, 7.86928e-05, 0, 0.650794, 0.499291, 7.65171e-05, 0, 0.68254, 0.477971, 7.44753e-05, 0, 0.714286, 0.457221, 7.2209e-05, 0, 0.746032, 0.436803, 6.97448e-05, 0, 0.777778, 0.417083, 6.75333e-05, 0, 0.809524, 0.397749, 6.48058e-05, 0, 0.84127, 0.379177, 6.25759e-05, 0, 0.873016, 0.361061, 5.98584e-05, 0, 0.904762, 0.343713, 5.75797e-05, 0, 0.936508, 0.326894, 5.49999e-05, 0, 0.968254, 0.310816, 5.27482e-05, 0, 1, 1, 1.0153e-08, 0, 0, 1, 1.01544e-08, 0, 0, 1, 1.01751e-08, 0, 0, 1, 1.02662e-08, 0, 0, 1, 1.0521e-08, 0, 0, 0.999999, 1.11049e-08, 0, 0, 0.999999, 1.23408e-08, 0, 0, 0.999996, 1.4924e-08, 0, 0, 0.999992, 2.04471e-08, 0, 0, 0.999989, 3.26539e-08, 0, 0, 0.99998, 6.03559e-08, 0, 0, 0.999971, 1.23936e-07, 0, 0, 0.999955, 2.69058e-07, 0, 0, 0.999933, 5.93604e-07, 0, 0, 0.999901, 1.29633e-06, 0, 0, 0.999847, 2.75621e-06, 0, 0, 0.999761, 5.64494e-06, 0, 0, 0.999607, 1.10485e-05, 0, 0, 0.999282, 2.04388e-05, 0, 0, 0.99831, 3.41084e-05, 0, 2.2038e-07, 0.993288, 2.94949e-05, 0, 0.000242388, 0.987855, 1.92736e-05, 0, 0.0012503, 0.983167, 1.82383e-05, 0, 0.0032745, 0.977908, 2.18633e-05, 0, 0.00646321, 0.971194, 2.90662e-05, 0, 0.0109133, 0.963867, 3.86401e-05, 0, 0.0166927, 0.95982, 4.62827e-05, 0, 0.0238494, 0.953497, 4.20705e-05, 0, 0.0324178, 0.947621, 4.77743e-05, 0, 0.0424225, 0.940611, 5.68258e-05, 0, 0.0538808, 0.931174, 6.18061e-05, 0, 0.0668047, 0.919919, 6.27098e-05, 0, 0.0812014, 0.907856, 6.94714e-05, 0, 0.0970745, 0.894509, 7.35008e-05, 0, 0.114424, 0.881954, 7.63369e-05, 0, 0.133246, 0.868309, 8.21896e-05, 0, 0.153534, 0.852511, 8.3769e-05, 0, 0.175275, 0.835821, 8.81615e-05, 0, 0.198453, 0.817981, 8.96368e-05, 0, 0.223042, 0.800504, 9.30906e-05, 0, 0.249009, 0.78141, 9.45056e-05, 0, 0.276304, 0.761427, 9.63605e-05, 0, 0.304862, 0.74094, 9.68088e-05, 0, 0.334584, 0.720233, 9.81481e-05, 0, 0.365322, 0.698592, 9.79122e-05, 0, 0.396826, 0.676763, 9.81057e-05, 0, 0.428571, 0.654808, 9.73956e-05, 0, 0.460318, 0.632326, 9.62619e-05, 0, 0.492064, 0.610049, 9.52996e-05, 0, 0.52381, 0.58763, 9.33334e-05, 0, 0.555556, 0.565261, 9.17573e-05, 0, 0.587302, 0.543244, 8.96636e-05, 0, 0.619048, 0.521273, 8.73304e-05, 0, 0.650794, 0.499818, 8.52648e-05, 0, 0.68254, 0.478536, 8.23961e-05, 0, 0.714286, 0.457826, 7.9939e-05, 0, 0.746032, 0.437549, 7.7126e-05, 0, 0.777778, 0.41776, 7.43043e-05, 0, 0.809524, 0.39863, 7.16426e-05, 0, 0.84127, 0.379954, 6.86456e-05, 0, 0.873016, 0.362025, 6.60514e-05, 0, 0.904762, 0.344581, 6.30755e-05, 0, 0.936508, 0.327909, 6.05439e-05, 0, 0.968254, 0.311736, 5.76345e-05, 0, 1, 1, 2.63344e-08, 0, 0, 1, 2.63373e-08, 0, 0, 1, 2.63815e-08, 0, 0, 1, 2.65753e-08, 0, 0, 1, 2.71132e-08, 0, 0, 0.999999, 2.83279e-08, 0, 0, 0.999997, 3.0833e-08, 0, 0, 0.999995, 3.58711e-08, 0, 0, 0.999992, 4.61266e-08, 0, 0, 0.999985, 6.7574e-08, 0, 0, 0.999977, 1.1358e-07, 0, 0, 0.999966, 2.13657e-07, 0, 0, 0.999948, 4.31151e-07, 0, 0, 0.999923, 8.96656e-07, 0, 0, 0.999884, 1.86603e-06, 0, 0, 0.999826, 3.81115e-06, 0, 0, 0.999732, 7.54184e-06, 0, 0, 0.999561, 1.43192e-05, 0, 0, 0.999191, 2.57061e-05, 0, 0, 0.997955, 4.05724e-05, 0, 7.44132e-05, 0.992228, 2.76537e-05, 0, 0.000716477, 0.987638, 2.08885e-05, 0, 0.0022524, 0.983395, 2.15226e-05, 0, 0.00484816, 0.978614, 2.70795e-05, 0, 0.00860962, 0.972389, 3.65282e-05, 0, 0.0136083, 0.964392, 4.74747e-05, 0, 0.0198941, 0.95861, 5.09141e-05, 0, 0.0275023, 0.952806, 4.8963e-05, 0, 0.0364584, 0.94712, 5.71119e-05, 0, 0.04678, 0.940104, 6.71704e-05, 0, 0.0584799, 0.930398, 6.87586e-05, 0, 0.0715665, 0.919866, 7.38161e-05, 0, 0.086045, 0.907853, 8.13235e-05, 0, 0.101918, 0.894078, 8.34582e-05, 0, 0.119186, 0.881177, 8.92093e-05, 0, 0.137845, 0.867575, 9.44548e-05, 0, 0.157891, 0.852107, 9.69607e-05, 0, 0.179316, 0.835502, 0.000101456, 0, 0.202106, 0.81756, 0.000103256, 0, 0.226243, 0.79984, 0.000106954, 0, 0.251704, 0.780998, 0.000108066, 0, 0.278451, 0.761132, 0.000110111, 0, 0.306436, 0.740429, 0.000110459, 0, 0.335586, 0.719836, 0.000111219, 0, 0.365796, 0.698467, 0.00011145, 0, 0.3969, 0.676446, 0.000110393, 0, 0.428571, 0.654635, 0.000110035, 0, 0.460318, 0.632411, 0.000108548, 0, 0.492064, 0.609986, 0.000106963, 0, 0.52381, 0.587872, 0.000105238, 0, 0.555556, 0.565528, 0.000102665, 0, 0.587302, 0.543563, 0.000100543, 0, 0.619048, 0.52176, 9.76182e-05, 0, 0.650794, 0.500188, 9.47099e-05, 0, 0.68254, 0.479204, 9.19929e-05, 0, 0.714286, 0.458413, 8.86139e-05, 0, 0.746032, 0.438314, 8.57839e-05, 0, 0.777778, 0.418573, 8.2411e-05, 0, 0.809524, 0.39947, 7.92211e-05, 0, 0.84127, 0.380892, 7.59546e-05, 0, 0.873016, 0.362953, 7.27571e-05, 0, 0.904762, 0.345601, 6.95738e-05, 0, 0.936508, 0.328895, 6.64907e-05, 0, 0.968254, 0.312808, 6.34277e-05, 0, 1, 1, 6.28647e-08, 0, 0, 1, 6.28705e-08, 0, 0, 1, 6.29587e-08, 0, 0, 1, 6.33441e-08, 0, 0, 0.999999, 6.44087e-08, 0, 0, 0.999998, 6.67856e-08, 0, 0, 0.999997, 7.15889e-08, 0, 0, 0.999995, 8.09577e-08, 0, 0, 0.999989, 9.92764e-08, 0, 0, 0.999983, 1.35834e-07, 0, 0, 0.999974, 2.10482e-07, 0, 0, 0.999959, 3.65215e-07, 0, 0, 0.999939, 6.86693e-07, 0, 0, 0.999911, 1.3472e-06, 0, 0, 0.999868, 2.6731e-06, 0, 0, 0.999804, 5.24756e-06, 0, 0, 0.9997, 1.00403e-05, 0, 0, 0.99951, 1.85019e-05, 0, 0, 0.999078, 3.22036e-05, 0, 6.20676e-06, 0.997428, 4.70002e-05, 0, 0.000341552, 0.99162, 2.87123e-05, 0, 0.00143727, 0.987479, 2.34706e-05, 0, 0.00349201, 0.983582, 2.60083e-05, 0, 0.0066242, 0.979186, 3.37927e-05, 0, 0.0109113, 0.97325, 4.54689e-05, 0, 0.0164064, 0.965221, 5.73759e-05, 0, 0.0231463, 0.957262, 5.44114e-05, 0, 0.0311571, 0.952211, 5.87006e-05, 0, 0.0404572, 0.946631, 6.92256e-05, 0, 0.0510592, 0.939391, 7.87819e-05, 0, 0.0629723, 0.929795, 7.92368e-05, 0, 0.0762025, 0.91965, 8.75075e-05, 0, 0.090753, 0.907737, 9.50903e-05, 0, 0.106626, 0.893899, 9.72963e-05, 0, 0.123822, 0.880239, 0.00010459, 0, 0.142337, 0.866562, 0.000107689, 0, 0.16217, 0.85164, 0.000113081, 0, 0.183314, 0.835021, 0.000116636, 0, 0.20576, 0.817311, 0.000120074, 0, 0.229496, 0.798845, 0.000121921, 0, 0.254502, 0.780479, 0.00012475, 0, 0.280753, 0.760694, 0.000125255, 0, 0.308212, 0.740142, 0.000126719, 0, 0.336825, 0.719248, 0.00012636, 0, 0.366517, 0.698209, 0.000126712, 0, 0.397167, 0.676398, 0.000125769, 0, 0.428578, 0.654378, 0.000124432, 0, 0.460318, 0.632484, 0.000123272, 0, 0.492064, 0.610113, 0.00012085, 0, 0.52381, 0.587931, 0.000118411, 0, 0.555556, 0.565872, 0.00011569, 0, 0.587302, 0.543814, 0.000112521, 0, 0.619048, 0.522265, 0.000109737, 0, 0.650794, 0.500835, 0.000106228, 0, 0.68254, 0.479818, 0.000102591, 0, 0.714286, 0.459258, 9.91288e-05, 0, 0.746032, 0.439061, 9.52325e-05, 0, 0.777778, 0.419552, 9.1895e-05, 0, 0.809524, 0.400399, 8.79051e-05, 0, 0.84127, 0.381976, 8.44775e-05, 0, 0.873016, 0.364009, 8.06316e-05, 0, 0.904762, 0.346761, 7.71848e-05, 0, 0.936508, 0.330049, 7.35429e-05, 0, 0.968254, 0.314018, 7.02103e-05, 0, 1, 1, 1.39968e-07, 0, 0, 1, 1.39979e-07, 0, 0, 1, 1.40145e-07, 0, 0, 1, 1.4087e-07, 0, 0, 0.999999, 1.42865e-07, 0, 0, 0.999998, 1.47279e-07, 0, 0, 0.999997, 1.56057e-07, 0, 0, 0.999992, 1.7276e-07, 0, 0, 0.999989, 2.04352e-07, 0, 0, 0.99998, 2.6494e-07, 0, 0, 0.999969, 3.83435e-07, 0, 0, 0.999953, 6.18641e-07, 0, 0, 0.999929, 1.08755e-06, 0, 0, 0.999898, 2.01497e-06, 0, 0, 0.999849, 3.81346e-06, 0, 0, 0.999778, 7.19815e-06, 0, 0, 0.999661, 1.33215e-05, 0, 0, 0.999451, 2.38313e-05, 0, 0, 0.998936, 4.01343e-05, 0, 0.000113724, 0.99662, 5.17346e-05, 0, 0.000820171, 0.991094, 3.04323e-05, 0, 0.00238143, 0.987487, 2.81757e-05, 0, 0.00493527, 0.983731, 3.20048e-05, 0, 0.00856859, 0.979647, 4.23905e-05, 0, 0.0133393, 0.973837, 5.62935e-05, 0, 0.0192863, 0.96584, 6.77442e-05, 0, 0.0264369, 0.956309, 6.23073e-05, 0, 0.03481, 0.951523, 7.04131e-05, 0, 0.0444184, 0.946003, 8.36594e-05, 0, 0.0552713, 0.938454, 9.11736e-05, 0, 0.0673749, 0.929279, 9.38264e-05, 0, 0.0807329, 0.919239, 0.000103754, 0, 0.0953479, 0.907293, 0.000109928, 0, 0.111221, 0.893936, 0.000115257, 0, 0.128352, 0.879674, 0.000122265, 0, 0.14674, 0.865668, 0.000125733, 0, 0.166382, 0.850998, 0.000132305, 0, 0.187276, 0.834498, 0.000134844, 0, 0.209413, 0.816903, 0.000139276, 0, 0.232786, 0.798235, 0.000140984, 0, 0.257382, 0.779724, 0.00014378, 0, 0.283181, 0.760251, 0.000144623, 0, 0.310156, 0.739808, 0.000145228, 0, 0.338269, 0.718762, 0.00014539, 0, 0.367461, 0.697815, 0.000144432, 0, 0.397646, 0.67631, 0.000143893, 0, 0.428685, 0.654278, 0.000141846, 0, 0.460318, 0.632347, 0.00013935, 0, 0.492064, 0.610296, 0.000137138, 0, 0.52381, 0.588039, 0.000133806, 0, 0.555556, 0.566218, 0.000130755, 0, 0.587302, 0.544346, 0.000127128, 0, 0.619048, 0.522701, 0.000123002, 0, 0.650794, 0.501542, 0.000119443, 0, 0.68254, 0.480508, 0.000115055, 0, 0.714286, 0.460092, 0.000111032, 0, 0.746032, 0.440021, 0.000106635, 0, 0.777778, 0.420446, 0.000102162, 0, 0.809524, 0.401512, 9.8184e-05, 0, 0.84127, 0.38299, 9.36497e-05, 0, 0.873016, 0.365232, 8.9813e-05, 0, 0.904762, 0.347865, 8.53073e-05, 0, 0.936508, 0.331342, 8.17068e-05, 0, 0.968254, 0.315202, 7.73818e-05, 0, 1, 1, 2.9368e-07, 0, 0, 1, 2.937e-07, 0, 0, 1, 2.93998e-07, 0, 0, 1, 2.95298e-07, 0, 0, 0.999999, 2.98865e-07, 0, 0, 0.999998, 3.067e-07, 0, 0, 0.999995, 3.22082e-07, 0, 0, 0.999992, 3.50767e-07, 0, 0, 0.999986, 4.03538e-07, 0, 0, 0.999976, 5.01372e-07, 0, 0, 0.999964, 6.8562e-07, 0, 0, 0.999945, 1.0374e-06, 0, 0, 0.999919, 1.71269e-06, 0, 0, 0.999882, 3.00175e-06, 0, 0, 0.999829, 5.42144e-06, 0, 0, 0.999749, 9.84182e-06, 0, 0, 0.99962, 1.76213e-05, 0, 0, 0.999382, 3.05995e-05, 0, 1.38418e-05, 0.998751, 4.96686e-05, 0, 0.000389844, 0.995344, 5.10733e-05, 0, 0.00150343, 0.990768, 3.45829e-05, 0, 0.00352451, 0.987464, 3.42841e-05, 0, 0.00655379, 0.983846, 3.99072e-05, 0, 0.0106554, 0.980007, 5.33219e-05, 0, 0.0158723, 0.974494, 6.96992e-05, 0, 0.0222333, 0.96622, 7.76754e-05, 0, 0.029758, 0.956273, 7.47718e-05, 0, 0.0384596, 0.950952, 8.64611e-05, 0, 0.0483473, 0.945215, 0.000100464, 0, 0.0594266, 0.937287, 0.000103729, 0, 0.0717019, 0.928649, 0.000111665, 0, 0.0851752, 0.918791, 0.00012353, 0, 0.0998479, 0.906685, 0.000127115, 0, 0.115721, 0.893706, 0.00013628, 0, 0.132794, 0.879248, 0.000142427, 0, 0.151067, 0.864685, 0.000148091, 0, 0.170538, 0.850032, 0.000153517, 0, 0.191204, 0.833853, 0.000157322, 0, 0.213063, 0.816353, 0.000161086, 0, 0.236107, 0.797834, 0.000164111, 0, 0.260329, 0.778831, 0.000165446, 0, 0.285714, 0.759756, 0.000167492, 0, 0.312243, 0.739419, 0.000166928, 0, 0.339887, 0.718491, 0.000167, 0, 0.368604, 0.697392, 0.000165674, 0, 0.398329, 0.676102, 0.000163815, 0, 0.428961, 0.654243, 0.000162003, 0, 0.460331, 0.632176, 0.000158831, 0, 0.492064, 0.610407, 0.000155463, 0, 0.52381, 0.588394, 0.000152062, 0, 0.555556, 0.56645, 0.000147665, 0, 0.587302, 0.5449, 0.00014375, 0, 0.619048, 0.523276, 0.000138905, 0, 0.650794, 0.502179, 0.000134189, 0, 0.68254, 0.481359, 0.000129392, 0, 0.714286, 0.46092, 0.000124556, 0, 0.746032, 0.441084, 0.00011957, 0, 0.777778, 0.421517, 0.000114652, 0, 0.809524, 0.402721, 0.000109688, 0, 0.84127, 0.384222, 0.000104667, 0, 0.873016, 0.366534, 9.99633e-05, 0, 0.904762, 0.349205, 9.50177e-05, 0, 0.936508, 0.332702, 9.07301e-05, 0, 0.968254, 0.316599, 8.59769e-05, 0, 1, 1, 5.85473e-07, 0, 0, 1, 5.85507e-07, 0, 0, 1, 5.8602e-07, 0, 0, 0.999999, 5.88259e-07, 0, 0, 0.999999, 5.94381e-07, 0, 0, 0.999998, 6.07754e-07, 0, 0, 0.999995, 6.33729e-07, 0, 0, 0.99999, 6.8137e-07, 0, 0, 0.999984, 7.67003e-07, 0, 0, 0.999973, 9.21212e-07, 0, 0, 0.999959, 1.20218e-06, 0, 0, 0.999936, 1.72024e-06, 0, 0, 0.999907, 2.68088e-06, 0, 0, 0.999866, 4.45512e-06, 0, 0, 0.999806, 7.68481e-06, 0, 0, 0.999716, 1.342e-05, 0, 0, 0.999576, 2.32473e-05, 0, 0, 0.9993, 3.91694e-05, 0, 0.000129917, 0.998498, 6.08429e-05, 0, 0.000845035, 0.994132, 4.89743e-05, 0, 0.00237616, 0.99031, 3.84644e-05, 0, 0.00484456, 0.987409, 4.21768e-05, 0, 0.00832472, 0.983981, 5.04854e-05, 0, 0.0128643, 0.980268, 6.71028e-05, 0, 0.0184947, 0.974875, 8.52749e-05, 0, 0.025237, 0.966063, 8.5531e-05, 0, 0.0331046, 0.956779, 9.00588e-05, 0, 0.0421067, 0.950259, 0.00010577, 0, 0.0522487, 0.944239, 0.000119458, 0, 0.0635343, 0.936341, 0.000122164, 0, 0.0759654, 0.928047, 0.000134929, 0, 0.0895434, 0.918065, 0.000145544, 0, 0.104269, 0.906267, 0.000150531, 0, 0.120142, 0.893419, 0.000161652, 0, 0.137163, 0.878758, 0.00016593, 0, 0.15533, 0.863699, 0.000174014, 0, 0.174645, 0.848876, 0.000177877, 0, 0.195106, 0.833032, 0.000184049, 0, 0.21671, 0.815557, 0.000186088, 0, 0.239454, 0.797323, 0.00019054, 0, 0.263332, 0.778124, 0.000191765, 0, 0.288336, 0.758929, 0.000192535, 0, 0.314451, 0.738979, 0.000192688, 0, 0.341658, 0.718213, 0.000191522, 0, 0.369924, 0.696947, 0.000190491, 0, 0.399202, 0.675807, 0.000187913, 0, 0.429416, 0.654147, 0.000184451, 0, 0.460447, 0.63229, 0.000181442, 0, 0.492064, 0.610499, 0.000177139, 0, 0.523809, 0.588747, 0.000172596, 0, 0.555555, 0.566783, 0.000167457, 0, 0.587301, 0.545359, 0.000162518, 0, 0.619048, 0.523984, 0.000156818, 0, 0.650794, 0.502917, 0.000151884, 0, 0.68254, 0.482294, 0.000145514, 0, 0.714286, 0.461945, 0.000140199, 0, 0.746032, 0.442133, 0.000134101, 0, 0.777778, 0.422705, 0.000128374, 0, 0.809524, 0.403916, 0.000122996, 0, 0.84127, 0.38554, 0.000116808, 0, 0.873016, 0.367909, 0.000111973, 0, 0.904762, 0.350651, 0.000105938, 0, 0.936508, 0.334208, 0.000101355, 0, 0.968254, 0.318123, 9.57629e-05, 0, 1, 1, 1.11633e-06, 0, 0, 1, 1.11639e-06, 0, 0, 1, 1.11725e-06, 0, 0, 1, 1.12096e-06, 0, 0, 0.999999, 1.1311e-06, 0, 0, 0.999997, 1.15315e-06, 0, 0, 0.999995, 1.1956e-06, 0, 0, 0.999989, 1.27239e-06, 0, 0, 0.999981, 1.40772e-06, 0, 0, 0.999969, 1.64541e-06, 0, 0, 0.999952, 2.06607e-06, 0, 0, 0.999928, 2.81783e-06, 0, 0, 0.999895, 4.16835e-06, 0, 0, 0.999848, 6.58728e-06, 0, 0, 0.999781, 1.08648e-05, 0, 0, 0.999682, 1.82579e-05, 0, 0, 0.999523, 3.06003e-05, 0, 1.59122e-05, 0.999205, 4.99862e-05, 0, 0.000391184, 0.998131, 7.3306e-05, 0, 0.00147534, 0.993334, 5.13229e-05, 0, 0.0034227, 0.99016, 4.67783e-05, 0, 0.00632232, 0.987321, 5.23413e-05, 0, 0.0102295, 0.984099, 6.4267e-05, 0, 0.0151794, 0.980432, 8.43042e-05, 0, 0.0211947, 0.974976, 0.000102819, 0, 0.0282899, 0.966429, 9.96234e-05, 0, 0.0364739, 0.957633, 0.000111074, 0, 0.0457522, 0.949422, 0.000128644, 0, 0.0561278, 0.943045, 0.000140076, 0, 0.0676023, 0.935448, 0.000146349, 0, 0.0801762, 0.927225, 0.000161854, 0, 0.0938499, 0.917033, 0.000169135, 0, 0.108623, 0.905762, 0.000179987, 0, 0.124496, 0.892879, 0.000189832, 0, 0.141469, 0.878435, 0.000195881, 0, 0.159541, 0.863114, 0.00020466, 0, 0.178713, 0.84776, 0.000209473, 0, 0.198985, 0.832084, 0.000214861, 0, 0.220355, 0.814915, 0.000217695, 0, 0.242823, 0.796711, 0.000220313, 0, 0.266385, 0.777603, 0.00022313, 0, 0.291036, 0.757991, 0.000222471, 0, 0.316767, 0.738371, 0.000222869, 0, 0.343563, 0.717872, 0.000221243, 0, 0.371402, 0.696619, 0.000218089, 0, 0.400248, 0.675379, 0.00021562, 0, 0.430047, 0.65411, 0.00021169, 0, 0.460709, 0.63241, 0.000206947, 0, 0.492079, 0.61046, 0.000201709, 0, 0.52381, 0.58903, 0.000196753, 0, 0.555556, 0.567267, 0.000189637, 0, 0.587302, 0.545886, 0.000184735, 0, 0.619048, 0.524714, 0.000177257, 0, 0.650794, 0.503789, 0.000171424, 0, 0.68254, 0.483204, 0.000164688, 0, 0.714286, 0.462976, 0.000157172, 0, 0.746032, 0.443294, 0.000151341, 0, 0.777778, 0.423988, 0.000143737, 0, 0.809524, 0.405325, 0.000138098, 0, 0.84127, 0.386981, 0.000130698, 0, 0.873016, 0.369436, 0.000125276, 0, 0.904762, 0.35219, 0.000118349, 0, 0.936508, 0.335804, 0.00011312, 0, 0.968254, 0.319749, 0.000106687, 0, 1, 1, 2.04685e-06, 0, 0, 1, 2.04694e-06, 0, 0, 1, 2.04831e-06, 0, 0, 0.999999, 2.05428e-06, 0, 0, 0.999999, 2.07056e-06, 0, 0, 0.999997, 2.10581e-06, 0, 0, 0.999993, 2.1732e-06, 0, 0, 0.999987, 2.29365e-06, 0, 0, 0.999979, 2.50243e-06, 0, 0, 0.999965, 2.86127e-06, 0, 0, 0.999947, 3.48028e-06, 0, 0, 0.999918, 4.55588e-06, 0, 0, 0.999881, 6.43303e-06, 0, 0, 0.999828, 9.70064e-06, 0, 0, 0.999753, 1.53233e-05, 0, 0, 0.999642, 2.4793e-05, 0, 0, 0.999464, 4.02032e-05, 0, 0.000122947, 0.999089, 6.35852e-05, 0, 0.000807414, 0.997567, 8.57026e-05, 0, 0.00227206, 0.992903, 5.94912e-05, 0, 0.00462812, 0.990011, 5.78515e-05, 0, 0.00794162, 0.987192, 6.5399e-05, 0, 0.0122534, 0.98418, 8.19675e-05, 0, 0.0175888, 0.980491, 0.000105514, 0, 0.0239635, 0.974779, 0.000121532, 0, 0.031387, 0.96675, 0.000119144, 0, 0.0398644, 0.958248, 0.000136125, 0, 0.0493982, 0.948884, 0.000155408, 0, 0.0599896, 0.941673, 0.000162281, 0, 0.0716382, 0.934521, 0.000176754, 0, 0.0843437, 0.926205, 0.000192873, 0, 0.0981056, 0.916089, 0.000200038, 0, 0.112923, 0.904963, 0.000213624, 0, 0.128796, 0.892089, 0.000221834, 0, 0.145725, 0.878028, 0.000232619, 0, 0.163709, 0.86249, 0.000238632, 0, 0.182749, 0.846587, 0.000247002, 0, 0.202847, 0.830988, 0.000250702, 0, 0.224001, 0.814165, 0.000255562, 0, 0.246214, 0.796135, 0.000257505, 0, 0.269482, 0.777052, 0.000258625, 0, 0.293805, 0.757201, 0.000258398, 0, 0.319176, 0.737655, 0.000256714, 0, 0.345587, 0.717477, 0.000255187, 0, 0.373021, 0.696433, 0.000251792, 0, 0.401454, 0.675084, 0.000247223, 0, 0.430844, 0.653907, 0.000242213, 0, 0.461125, 0.632561, 0.000237397, 0, 0.492187, 0.610658, 0.000229313, 0, 0.52381, 0.589322, 0.000224402, 0, 0.555556, 0.567857, 0.000216116, 0, 0.587302, 0.54652, 0.000209124, 0, 0.619048, 0.525433, 0.000201601, 0, 0.650794, 0.504679, 0.000192957, 0, 0.68254, 0.484203, 0.000186052, 0, 0.714286, 0.464203, 0.000177672, 0, 0.746032, 0.444549, 0.000170005, 0, 0.777778, 0.425346, 0.000162401, 0, 0.809524, 0.406706, 0.0001544, 0, 0.84127, 0.388576, 0.000147437, 0, 0.873016, 0.37094, 0.000139493, 0, 0.904762, 0.353996, 0.000133219, 0, 0.936508, 0.337391, 0.000125573, 0, 0.968254, 0.321648, 0.000119867, 0, 1, 1, 3.62511e-06, 0, 0, 1, 3.62525e-06, 0, 0, 1, 3.62739e-06, 0, 0, 0.999999, 3.63673e-06, 0, 0, 0.999998, 3.66214e-06, 0, 0, 0.999996, 3.71698e-06, 0, 0, 0.999992, 3.82116e-06, 0, 0, 0.999986, 4.00554e-06, 0, 0, 0.999976, 4.32058e-06, 0, 0, 0.999961, 4.85194e-06, 0, 0, 0.999938, 5.74808e-06, 0, 0, 0.999908, 7.26643e-06, 0, 0, 0.999865, 9.84707e-06, 0, 0, 0.999807, 1.42217e-05, 0, 0, 0.999723, 2.15581e-05, 0, 0, 0.999602, 3.36114e-05, 0, 1.19113e-05, 0.999398, 5.27353e-05, 0, 0.000355813, 0.998946, 8.05809e-05, 0, 0.00137768, 0.996647, 9.42908e-05, 0, 0.00322469, 0.992298, 6.68733e-05, 0, 0.00597897, 0.989802, 7.16564e-05, 0, 0.00968903, 0.987019, 8.21355e-05, 0, 0.0143845, 0.984219, 0.000104555, 0, 0.0200831, 0.980425, 0.000131245, 0, 0.0267948, 0.974241, 0.000139613, 0, 0.034525, 0.967006, 0.000145931, 0, 0.0432757, 0.95893, 0.000167153, 0, 0.0530471, 0.949157, 0.000188146, 0, 0.0638386, 0.94062, 0.000194625, 0, 0.0756487, 0.933509, 0.000213721, 0, 0.0884762, 0.925088, 0.000229616, 0, 0.10232, 0.915178, 0.000239638, 0, 0.117178, 0.904093, 0.000254814, 0, 0.133051, 0.891337, 0.000263685, 0, 0.149939, 0.877326, 0.000274789, 0, 0.167841, 0.861794, 0.000280534, 0, 0.18676, 0.845758, 0.000289534, 0, 0.206696, 0.829792, 0.000294446, 0, 0.22765, 0.813037, 0.000296877, 0, 0.249625, 0.795285, 0.000300217, 0, 0.27262, 0.776323, 0.000299826, 0, 0.296636, 0.756673, 0.000299787, 0, 0.321671, 0.736856, 0.000297867, 0, 0.347718, 0.716883, 0.000294052, 0, 0.374768, 0.696089, 0.000289462, 0, 0.402804, 0.67505, 0.000285212, 0, 0.431796, 0.653509, 0.00027653, 0, 0.461695, 0.63258, 0.000271759, 0, 0.49242, 0.61104, 0.000262811, 0, 0.523822, 0.589567, 0.000255151, 0, 0.555556, 0.568322, 0.000246434, 0, 0.587302, 0.547235, 0.000237061, 0, 0.619048, 0.52616, 0.000228343, 0, 0.650794, 0.505716, 0.000219236, 0, 0.68254, 0.485274, 0.000209595, 0, 0.714286, 0.465411, 0.000201011, 0, 0.746032, 0.445854, 0.00019109, 0, 0.777778, 0.426911, 0.000182897, 0, 0.809524, 0.408222, 0.000173569, 0, 0.84127, 0.390307, 0.000165496, 0, 0.873016, 0.372624, 0.000156799, 0, 0.904762, 0.355804, 0.00014917, 0, 0.936508, 0.33924, 0.000140907, 0, 0.968254, 0.323534, 0.000134062, 0, 1, 1, 6.22487e-06, 0, 0, 1, 6.2251e-06, 0, 0, 1, 6.22837e-06, 0, 0, 0.999999, 6.24259e-06, 0, 0, 0.999998, 6.28127e-06, 0, 0, 0.999996, 6.36451e-06, 0, 0, 0.999991, 6.5218e-06, 0, 0, 0.999984, 6.79782e-06, 0, 0, 0.999973, 7.26361e-06, 0, 0, 0.999955, 8.03644e-06, 0, 0, 0.999931, 9.31397e-06, 0, 0, 0.999896, 1.14299e-05, 0, 0, 0.999847, 1.49402e-05, 0, 0, 0.999784, 2.07461e-05, 0, 0, 0.999692, 3.02493e-05, 0, 0, 0.999554, 4.54957e-05, 0, 9.97275e-05, 0.999326, 6.90762e-05, 0, 0.000724813, 0.998757, 0.000101605, 0, 0.0020972, 0.995367, 9.58745e-05, 0, 0.00432324, 0.99209, 8.32808e-05, 0, 0.00746347, 0.989517, 8.87601e-05, 0, 0.0115534, 0.987008, 0.00010564, 0, 0.0166134, 0.98421, 0.000133179, 0, 0.0226552, 0.98021, 0.000161746, 0, 0.0296838, 0.973676, 0.000161821, 0, 0.0377016, 0.967052, 0.000178635, 0, 0.0467079, 0.959385, 0.000206765, 0, 0.0567013, 0.949461, 0.00022476, 0, 0.0676796, 0.939578, 0.00023574, 0, 0.0796403, 0.932416, 0.00025893, 0, 0.0925812, 0.923759, 0.000271228, 0, 0.106501, 0.914223, 0.000289165, 0, 0.121397, 0.902942, 0.000301156, 0, 0.13727, 0.890419, 0.000313852, 0, 0.15412, 0.876639, 0.000324408, 0, 0.171946, 0.861316, 0.00033249, 0, 0.190751, 0.84496, 0.000338497, 0, 0.210537, 0.828427, 0.000345861, 0, 0.231305, 0.811871, 0.000347863, 0, 0.253057, 0.794397, 0.000350225, 0, 0.275797, 0.775726, 0.000349915, 0, 0.299525, 0.75617, 0.000347297, 0, 0.324242, 0.736091, 0.000344232, 0, 0.349947, 0.716213, 0.000340835, 0, 0.376633, 0.695736, 0.000332369, 0, 0.404289, 0.674961, 0.000327943, 0, 0.432895, 0.653518, 0.000318533, 0, 0.462415, 0.632574, 0.000310391, 0, 0.492788, 0.61134, 0.000300755, 0, 0.523909, 0.590017, 0.000290506, 0, 0.555556, 0.568752, 0.000280446, 0, 0.587302, 0.548061, 0.000269902, 0, 0.619048, 0.52711, 0.000258815, 0, 0.650794, 0.506682, 0.000248481, 0, 0.68254, 0.486524, 0.000237141, 0, 0.714286, 0.466812, 0.000226872, 0, 0.746032, 0.44732, 0.000216037, 0, 0.777778, 0.428473, 0.000205629, 0, 0.809524, 0.409921, 0.000195691, 0, 0.84127, 0.392028, 0.000185457, 0, 0.873016, 0.374606, 0.000176436, 0, 0.904762, 0.357601, 0.000166508, 0, 0.936508, 0.341348, 0.000158385, 0, 0.968254, 0.32542, 0.000149203, 0, 1, 1, 1.03967e-05, 0, 0, 1, 1.0397e-05, 0, 0, 1, 1.04019e-05, 0, 0, 0.999999, 1.04231e-05, 0, 0, 0.999998, 1.04806e-05, 0, 0, 0.999995, 1.06042e-05, 0, 0, 0.999991, 1.08366e-05, 0, 0, 0.999982, 1.12415e-05, 0, 0, 0.999968, 1.19174e-05, 0, 0, 0.99995, 1.30227e-05, 0, 0, 0.999922, 1.48176e-05, 0, 0, 0.999884, 1.77303e-05, 0, 0, 0.99983, 2.24564e-05, 0, 0, 0.999758, 3.00966e-05, 0, 0, 0.999654, 4.23193e-05, 0, 5.49083e-06, 0.999503, 6.14848e-05, 0, 0.000296087, 0.999237, 9.03576e-05, 0, 0.00123144, 0.998491, 0.0001271, 0, 0.00295954, 0.994594, 0.000107754, 0, 0.00555829, 0.99178, 0.000103025, 0, 0.00907209, 0.989265, 0.00011154, 0, 0.0135257, 0.986998, 0.000136296, 0, 0.0189327, 0.984137, 0.000169154, 0, 0.0252993, 0.979798, 0.000196671, 0, 0.0326272, 0.97337, 0.000196678, 0, 0.0409157, 0.967239, 0.000223121, 0, 0.0501623, 0.959543, 0.000253809, 0, 0.0603638, 0.949466, 0.000265972, 0, 0.0715171, 0.939074, 0.000288372, 0, 0.0836187, 0.931118, 0.000310983, 0, 0.0966657, 0.922525, 0.000325561, 0, 0.110656, 0.912983, 0.000345725, 0, 0.125588, 0.901617, 0.0003556, 0, 0.141461, 0.889487, 0.000374012, 0, 0.158275, 0.875787, 0.000383445, 0, 0.176031, 0.860654, 0.000393972, 0, 0.19473, 0.844417, 0.000400311, 0, 0.214374, 0.82741, 0.000405004, 0, 0.234967, 0.810545, 0.000407378, 0, 0.256512, 0.793312, 0.000407351, 0, 0.279011, 0.774847, 0.000406563, 0, 0.302468, 0.755621, 0.000404903, 0, 0.326887, 0.735511, 0.000397486, 0, 0.352266, 0.715435, 0.00039357, 0, 0.378605, 0.695403, 0.000384739, 0, 0.405897, 0.674681, 0.000376108, 0, 0.43413, 0.65359, 0.000365997, 0, 0.463277, 0.632471, 0.000354957, 0, 0.493295, 0.61151, 0.000343593, 0, 0.524106, 0.59064, 0.000331841, 0, 0.555561, 0.569386, 0.000318891, 0, 0.587302, 0.548785, 0.0003072, 0, 0.619048, 0.528146, 0.00029361, 0, 0.650794, 0.507872, 0.000281709, 0, 0.68254, 0.487805, 0.000268627, 0, 0.714286, 0.468196, 0.000255887, 0, 0.746032, 0.448922, 0.000243997, 0, 0.777778, 0.430093, 0.000231662, 0, 0.809524, 0.411845, 0.000220339, 0, 0.84127, 0.393808, 0.000208694, 0, 0.873016, 0.376615, 0.000198045, 0, 0.904762, 0.359655, 0.000187375, 0, 0.936508, 0.343452, 0.000177371, 0, 0.968254, 0.32765, 0.000167525, 0, 1, 1, 1.69351e-05, 0, 0, 1, 1.69356e-05, 0, 0, 1, 1.69427e-05, 0, 0, 0.999999, 1.69736e-05, 0, 0, 0.999998, 1.70575e-05, 0, 0, 0.999995, 1.72372e-05, 0, 0, 0.99999, 1.75739e-05, 0, 0, 0.999979, 1.81568e-05, 0, 0, 0.999966, 1.91206e-05, 0, 0, 0.999944, 2.0677e-05, 0, 0, 0.999912, 2.31644e-05, 0, 0, 0.999869, 2.71268e-05, 0, 0, 0.999811, 3.34272e-05, 0, 0, 0.99973, 4.33979e-05, 0, 0, 0.999617, 5.90083e-05, 0, 6.80315e-05, 0.999445, 8.29497e-05, 0, 0.000612796, 0.999138, 0.000118019, 0, 0.00187408, 0.998095, 0.000156712, 0, 0.00395791, 0.993919, 0.000125054, 0, 0.00692144, 0.991333, 0.000126091, 0, 0.0107962, 0.989226, 0.000144912, 0, 0.0155986, 0.986954, 0.000175737, 0, 0.0213364, 0.983982, 0.000213883, 0, 0.0280114, 0.979128, 0.000234526, 0, 0.0356226, 0.973327, 0.000243725, 0, 0.0441668, 0.967416, 0.0002773, 0, 0.0536399, 0.959729, 0.000308799, 0, 0.0640376, 0.949758, 0.000322447, 0, 0.0753554, 0.939173, 0.000350021, 0, 0.0875893, 0.9296, 0.000370089, 0, 0.100736, 0.921181, 0.000391365, 0, 0.114793, 0.91164, 0.000413636, 0, 0.129759, 0.900435, 0.000427068, 0, 0.145632, 0.888183, 0.000441046, 0, 0.162412, 0.874772, 0.000454968, 0, 0.180101, 0.859566, 0.000461882, 0, 0.1987, 0.843579, 0.000471556, 0, 0.218213, 0.826453, 0.000474335, 0, 0.238641, 0.809164, 0.000477078, 0, 0.259989, 0.792179, 0.00047755, 0, 0.282262, 0.773866, 0.000472573, 0, 0.305464, 0.754944, 0.000469765, 0, 0.329599, 0.735133, 0.000462371, 0, 0.35467, 0.714858, 0.000453674, 0, 0.380678, 0.694829, 0.000443888, 0, 0.407622, 0.674453, 0.000432052, 0, 0.435493, 0.653685, 0.000420315, 0, 0.464275, 0.632666, 0.000406829, 0, 0.493938, 0.611676, 0.000392234, 0, 0.524422, 0.591193, 0.000379208, 0, 0.555624, 0.570145, 0.00036319, 0, 0.587302, 0.549566, 0.000349111, 0, 0.619048, 0.529278, 0.000334166, 0, 0.650794, 0.509026, 0.000318456, 0, 0.68254, 0.489186, 0.00030449, 0, 0.714286, 0.469662, 0.000289051, 0, 0.746032, 0.450691, 0.000275494, 0, 0.777778, 0.431841, 0.000261437, 0, 0.809524, 0.413752, 0.000247846, 0, 0.84127, 0.395951, 0.000235085, 0, 0.873016, 0.378633, 0.000222245, 0, 0.904762, 0.36194, 0.000210533, 0, 0.936508, 0.345599, 0.000198494, 0, 0.968254, 0.329999, 0.000188133, 0, 1, 1, 2.69663e-05, 0, 0, 1, 2.6967e-05, 0, 0, 1, 2.69772e-05, 0, 0, 0.999999, 2.70214e-05, 0, 0, 0.999998, 2.71415e-05, 0, 0, 0.999994, 2.7398e-05, 0, 0, 0.999988, 2.78771e-05, 0, 0, 0.999977, 2.87019e-05, 0, 0, 0.999961, 3.00544e-05, 0, 0, 0.999937, 3.22138e-05, 0, 0, 0.999904, 3.56163e-05, 0, 0, 0.999854, 4.09465e-05, 0, 0, 0.99979, 4.92651e-05, 0, 0, 0.999699, 6.21722e-05, 0, 8.8288e-07, 0.999572, 8.19715e-05, 0, 0.000223369, 0.999381, 0.000111689, 0, 0.00105414, 0.999016, 0.000153862, 0, 0.0026493, 0.997437, 0.000187667, 0, 0.00508608, 0.993545, 0.000155672, 0, 0.00840554, 0.991135, 0.000161455, 0, 0.012629, 0.989157, 0.000188241, 0, 0.0177661, 0.986874, 0.000226229, 0, 0.0238198, 0.983714, 0.000268668, 0, 0.0307887, 0.978301, 0.000277109, 0, 0.0386688, 0.973227, 0.000303446, 0, 0.0474554, 0.967317, 0.000341851, 0, 0.0571428, 0.959477, 0.000370885, 0, 0.0677256, 0.950012, 0.000392753, 0, 0.0791988, 0.939484, 0.00042781, 0, 0.0915576, 0.928135, 0.000443866, 0, 0.104798, 0.919819, 0.000472959, 0, 0.118918, 0.910049, 0.000491551, 0, 0.133915, 0.899181, 0.000512616, 0, 0.149788, 0.886881, 0.000523563, 0, 0.166537, 0.87359, 0.000540183, 0, 0.184164, 0.858613, 0.000547386, 0, 0.202669, 0.842809, 0.000554809, 0, 0.222056, 0.825727, 0.000558316, 0, 0.242329, 0.808086, 0.000557824, 0, 0.263492, 0.790728, 0.000556346, 0, 0.285551, 0.772987, 0.000552672, 0, 0.30851, 0.7541, 0.000543738, 0, 0.332376, 0.734669, 0.000536107, 0, 0.357153, 0.714411, 0.000523342, 0, 0.382845, 0.694196, 0.000512238, 0, 0.409454, 0.674252, 0.000497465, 0, 0.436977, 0.65357, 0.000481096, 0, 0.465404, 0.632999, 0.000467054, 0, 0.494713, 0.611994, 0.000448771, 0, 0.524864, 0.591604, 0.000431889, 0, 0.555779, 0.571134, 0.000415238, 0, 0.587302, 0.550528, 0.000396369, 0, 0.619048, 0.530292, 0.000379477, 0, 0.650794, 0.510364, 0.000361488, 0, 0.68254, 0.490749, 0.000343787, 0, 0.714286, 0.471266, 0.000327822, 0, 0.746032, 0.452462, 0.000310626, 0, 0.777778, 0.433907, 0.000295352, 0, 0.809524, 0.415659, 0.000279179, 0, 0.84127, 0.398138, 0.000264685, 0, 0.873016, 0.380833, 0.000249905, 0, 0.904762, 0.364247, 0.000236282, 0, 0.936508, 0.348041, 0.000222905, 0, 0.968254, 0.332389, 0.000210522, 0, 1, 1, 4.20604e-05, 0, 0, 1, 4.20614e-05, 0, 0, 1, 4.20757e-05, 0, 0, 0.999999, 4.2138e-05, 0, 0, 0.999997, 4.23067e-05, 0, 0, 0.999993, 4.26668e-05, 0, 0, 0.999986, 4.33372e-05, 0, 0, 0.999974, 4.44857e-05, 0, 0, 0.999956, 4.63554e-05, 0, 0, 0.99993, 4.93105e-05, 0, 0, 0.999892, 5.39077e-05, 0, 0, 0.999838, 6.10005e-05, 0, 0, 0.999767, 7.18822e-05, 0, 0, 0.999666, 8.84581e-05, 0, 3.65471e-05, 0.999525, 0.000113398, 0, 0.000485623, 0.999311, 0.000150043, 0, 0.00162096, 0.998865, 0.000200063, 0, 0.00355319, 0.996278, 0.000211014, 0, 0.00633818, 0.992956, 0.000189672, 0, 0.0100043, 0.991017, 0.000210262, 0, 0.0145648, 0.989055, 0.000244292, 0, 0.0200237, 0.986741, 0.000290481, 0, 0.0263798, 0.983288, 0.000334303, 0, 0.033629, 0.977784, 0.000340307, 0, 0.0417652, 0.973037, 0.000377864, 0, 0.0507821, 0.967181, 0.0004239, 0, 0.060673, 0.958971, 0.000443854, 0, 0.0714314, 0.950093, 0.000483039, 0, 0.0830518, 0.939552, 0.000517934, 0, 0.0955288, 0.927678, 0.000539449, 0, 0.108859, 0.918278, 0.000568604, 0, 0.123038, 0.908449, 0.000588505, 0, 0.138065, 0.897713, 0.000612473, 0, 0.153938, 0.885533, 0.000625575, 0, 0.170657, 0.872131, 0.00063854, 0, 0.188224, 0.857517, 0.000647034, 0, 0.20664, 0.841796, 0.00065209, 0, 0.225909, 0.824726, 0.0006544, 0, 0.246035, 0.807297, 0.000655744, 0, 0.267022, 0.789058, 0.000646716, 0, 0.288878, 0.77189, 0.000643898, 0, 0.311607, 0.753082, 0.000629973, 0, 0.335216, 0.7341, 0.000621564, 0, 0.359713, 0.714094, 0.000605171, 0, 0.385103, 0.693839, 0.000588752, 0, 0.41139, 0.673891, 0.000573294, 0, 0.438576, 0.653565, 0.000552682, 0, 0.466656, 0.633326, 0.000533446, 0, 0.495617, 0.612582, 0.000514635, 0, 0.525431, 0.59205, 0.00049303, 0, 0.556041, 0.571918, 0.000471842, 0, 0.587338, 0.551572, 0.000451713, 0, 0.619048, 0.531553, 0.000430049, 0, 0.650794, 0.51175, 0.000410445, 0, 0.68254, 0.49238, 0.000390098, 0, 0.714286, 0.473143, 0.000370033, 0, 0.746032, 0.45423, 0.000351205, 0, 0.777778, 0.435963, 0.000332049, 0, 0.809524, 0.41787, 0.000315021, 0, 0.84127, 0.400387, 0.000297315, 0, 0.873016, 0.383332, 0.000281385, 0, 0.904762, 0.366665, 0.000265397, 0, 0.936508, 0.350633, 0.000250601, 0, 0.968254, 0.334964, 0.00023589, 0, 1, 1, 6.43736e-05, 0, 0, 1, 6.4375e-05, 0, 0, 1, 6.43947e-05, 0, 0, 0.999999, 6.4481e-05, 0, 0, 0.999997, 6.47143e-05, 0, 0, 0.999994, 6.52119e-05, 0, 0, 0.999985, 6.61359e-05, 0, 0, 0.999972, 6.77116e-05, 0, 0, 0.999952, 7.02599e-05, 0, 0, 0.999922, 7.42517e-05, 0, 0, 0.99988, 8.03906e-05, 0, 0, 0.99982, 8.97315e-05, 0, 0, 0.999741, 0.000103838, 0, 0, 0.999629, 0.00012496, 0, 0.000149024, 0.999474, 0.000156161, 0, 0.000861027, 0.999229, 0.000201034, 0, 0.00231198, 0.998662, 0.000259069, 0, 0.00458147, 0.995299, 0.000245439, 0, 0.00770895, 0.992732, 0.00024498, 0, 0.0117126, 0.990847, 0.000273211, 0, 0.0165989, 0.988911, 0.000316492, 0, 0.0223674, 0.98654, 0.00037161, 0, 0.0290135, 0.982636, 0.000410352, 0, 0.0365309, 0.977346, 0.000421756, 0, 0.0449117, 0.972909, 0.000475578, 0, 0.0541481, 0.966821, 0.000522482, 0, 0.0642326, 0.958686, 0.000545008, 0, 0.075158, 0.949754, 0.000589286, 0, 0.0869181, 0.939184, 0.000619995, 0, 0.0995074, 0.927505, 0.000654266, 0, 0.112922, 0.916606, 0.000682362, 0, 0.127157, 0.906707, 0.000704286, 0, 0.142212, 0.895937, 0.000725909, 0, 0.158085, 0.883913, 0.000743939, 0, 0.174776, 0.870642, 0.000755157, 0, 0.192287, 0.856241, 0.000764387, 0, 0.210619, 0.84069, 0.000771032, 0, 0.229775, 0.823728, 0.000765906, 0, 0.249761, 0.806481, 0.000767604, 0, 0.270582, 0.787924, 0.000754385, 0, 0.292243, 0.770588, 0.000749668, 0, 0.314753, 0.751991, 0.000731613, 0, 0.338118, 0.733407, 0.000717655, 0, 0.362347, 0.713688, 0.000700604, 0, 0.387447, 0.693595, 0.000678765, 0, 0.413424, 0.673426, 0.000657042, 0, 0.440284, 0.65359, 0.000635892, 0, 0.468027, 0.633576, 0.000611569, 0, 0.496645, 0.613144, 0.000586011, 0, 0.526122, 0.592711, 0.000563111, 0, 0.556417, 0.572722, 0.000537699, 0, 0.587451, 0.552762, 0.000512556, 0, 0.619048, 0.532985, 0.000489757, 0, 0.650794, 0.513219, 0.000464139, 0, 0.68254, 0.493992, 0.000442193, 0, 0.714286, 0.47509, 0.000418629, 0, 0.746032, 0.456287, 0.000397045, 0, 0.777778, 0.438152, 0.000375504, 0, 0.809524, 0.420294, 0.00035492, 0, 0.84127, 0.402749, 0.000335327, 0, 0.873016, 0.385879, 0.000316422, 0, 0.904762, 0.369352, 0.000298333, 0, 0.936508, 0.353301, 0.000281417, 0, 0.968254, 0.337781, 0.000265203, 0, 1, 1, 9.68267e-05, 0, 0, 1, 9.68284e-05, 0, 0, 1, 9.68556e-05, 0, 0, 0.999999, 9.69733e-05, 0, 0, 0.999997, 9.72913e-05, 0, 0, 0.999993, 9.79688e-05, 0, 0, 0.999984, 9.92239e-05, 0, 0, 0.999969, 0.000101356, 0, 0, 0.999946, 0.000104784, 0, 0, 0.999913, 0.000110111, 0, 0, 0.999868, 0.000118217, 0, 0, 0.999801, 0.000130396, 0, 0, 0.999712, 0.000148523, 0, 1.24907e-05, 0.999589, 0.000175233, 0, 0.000355405, 0.999416, 0.000213999, 0, 0.0013528, 0.999136, 0.000268529, 0, 0.00312557, 0.998367, 0.000333088, 0, 0.00573045, 0.994701, 0.000304757, 0, 0.00919397, 0.992497, 0.000318031, 0, 0.0135261, 0.990608, 0.000353863, 0, 0.0187278, 0.988715, 0.000409044, 0, 0.0247947, 0.986241, 0.000472967, 0, 0.0317196, 0.981696, 0.000495104, 0, 0.039494, 0.977097, 0.000532873, 0, 0.0481087, 0.972583, 0.000594447, 0, 0.0575549, 0.966142, 0.000636867, 0, 0.0678242, 0.95823, 0.000669899, 0, 0.0789089, 0.949677, 0.000719499, 0, 0.0908023, 0.939226, 0.000750584, 0, 0.103499, 0.927501, 0.000793183, 0, 0.116993, 0.915199, 0.00081995, 0, 0.131282, 0.90498, 0.000847654, 0, 0.146364, 0.894243, 0.000868929, 0, 0.162237, 0.882154, 0.000884278, 0, 0.178902, 0.869161, 0.000898108, 0, 0.196358, 0.854751, 0.000901254, 0, 0.21461, 0.839368, 0.00090679, 0, 0.23366, 0.822874, 0.000901541, 0, 0.253512, 0.805514, 0.000897297, 0, 0.274174, 0.78716, 0.000881856, 0, 0.29565, 0.769061, 0.000870032, 0, 0.31795, 0.751, 0.000851719, 0, 0.341081, 0.732614, 0.000830671, 0, 0.365053, 0.713171, 0.000806569, 0, 0.389874, 0.693472, 0.00078338, 0, 0.415553, 0.673528, 0.000756404, 0, 0.442098, 0.653397, 0.000726872, 0, 0.469512, 0.633781, 0.000700494, 0, 0.497794, 0.613877, 0.00067105, 0, 0.526935, 0.593506, 0.000640361, 0, 0.556908, 0.573667, 0.000613502, 0, 0.587657, 0.553932, 0.000583177, 0, 0.61906, 0.534345, 0.000554375, 0, 0.650794, 0.515042, 0.000527811, 0, 0.68254, 0.495674, 0.000499367, 0, 0.714286, 0.477132, 0.00047429, 0, 0.746032, 0.458609, 0.000447726, 0, 0.777778, 0.440354, 0.000424205, 0, 0.809524, 0.422765, 0.000399549, 0, 0.84127, 0.405472, 0.000378315, 0, 0.873016, 0.388482, 0.000355327, 0, 0.904762, 0.372191, 0.000336122, 0, 0.936508, 0.356099, 0.000315247, 0, 0.968254, 0.340737, 0.00029794, 0, 1, 1, 0.000143327, 0, 0, 1, 0.00014333, 0, 0, 1, 0.000143366, 0, 0, 0.999999, 0.000143524, 0, 0, 0.999996, 0.000143952, 0, 0, 0.999991, 0.000144862, 0, 0, 0.999981, 0.000146544, 0, 0, 0.999966, 0.000149391, 0, 0, 0.999941, 0.000153946, 0, 0, 0.999905, 0.000160971, 0, 0, 0.999852, 0.000171562, 0, 0, 0.99978, 0.00018729, 0, 0, 0.999681, 0.000210386, 0, 8.26239e-05, 0.999546, 0.000243906, 0, 0.000664807, 0.999352, 0.000291739, 0, 0.00196192, 0.999027, 0.000357419, 0, 0.00405941, 0.997886, 0.000422349, 0, 0.00699664, 0.99419, 0.000385008, 0, 0.0107896, 0.99214, 0.000409775, 0, 0.0154415, 0.990274, 0.000456418, 0, 0.0209488, 0.988455, 0.000527008, 0, 0.0273037, 0.985804, 0.000597685, 0, 0.0344969, 0.98103, 0.000613124, 0, 0.0425183, 0.976674, 0.000668321, 0, 0.0513575, 0.972021, 0.000736985, 0, 0.0610046, 0.965274, 0.000773789, 0, 0.0714508, 0.958046, 0.000830852, 0, 0.0826877, 0.949333, 0.000875766, 0, 0.0947085, 0.939135, 0.000917088, 0, 0.107507, 0.927119, 0.000952244, 0, 0.121078, 0.91469, 0.000990626, 0, 0.135419, 0.903006, 0.00101304, 0, 0.150526, 0.892368, 0.00103834, 0, 0.166399, 0.880231, 0.00105002, 0, 0.183038, 0.867432, 0.00106331, 0, 0.200443, 0.853208, 0.00106783, 0, 0.218618, 0.837956, 0.00106458, 0, 0.237566, 0.821772, 0.00105945, 0, 0.257291, 0.804328, 0.00104685, 0, 0.2778, 0.786465, 0.00103178, 0, 0.2991, 0.768004, 0.00101077, 0, 0.321199, 0.74972, 0.000985504, 0, 0.344106, 0.731682, 0.000962893, 0, 0.36783, 0.712813, 0.000932146, 0, 0.392383, 0.693139, 0.00089871, 0, 0.417774, 0.673566, 0.000869678, 0, 0.444013, 0.653483, 0.000835525, 0, 0.471107, 0.633891, 0.000799853, 0, 0.49906, 0.614433, 0.000766838, 0, 0.527869, 0.594586, 0.000732227, 0, 0.557517, 0.574769, 0.000696442, 0, 0.587966, 0.555149, 0.000663935, 0, 0.61913, 0.535898, 0.000629826, 0, 0.650794, 0.516753, 0.000596486, 0, 0.68254, 0.497816, 0.000567078, 0, 0.714286, 0.479034, 0.000534399, 0, 0.746032, 0.460975, 0.000507013, 0, 0.777778, 0.442935, 0.000477421, 0, 0.809524, 0.425263, 0.000451101, 0, 0.84127, 0.408248, 0.000424964, 0, 0.873016, 0.391339, 0.00039993, 0, 0.904762, 0.37513, 0.000377619, 0, 0.936508, 0.359172, 0.000354418, 0, 0.968254, 0.343876, 0.000334823, 0, 1, 1, 0.000209042, 0, 0, 1, 0.000209045, 0, 0, 1, 0.000209093, 0, 0, 0.999999, 0.000209304, 0, 0, 0.999996, 0.000209871, 0, 0, 0.999991, 0.000211078, 0, 0, 0.999979, 0.000213304, 0, 0, 0.999963, 0.000217061, 0, 0, 0.999933, 0.000223042, 0, 0, 0.999894, 0.000232206, 0, 0, 0.999837, 0.000245901, 0, 0, 0.999756, 0.000266023, 0, 1.02927e-06, 0.999648, 0.000295204, 0, 0.000233468, 0.999499, 0.000336958, 0, 0.00108237, 0.999283, 0.000395563, 0, 0.00268832, 0.998896, 0.000473785, 0, 0.00511138, 0.997006, 0.000520008, 0, 0.00837705, 0.993819, 0.000497261, 0, 0.0124928, 0.991632, 0.000523722, 0, 0.0174561, 0.989875, 0.000587258, 0, 0.0232596, 0.988109, 0.000676329, 0, 0.0298932, 0.985155, 0.000747701, 0, 0.0373453, 0.980479, 0.000768803, 0, 0.0456045, 0.976271, 0.000841054, 0, 0.0546593, 0.971347, 0.000911469, 0, 0.0644994, 0.964528, 0.000953057, 0, 0.0751152, 0.957632, 0.00102221, 0, 0.0864981, 0.948681, 0.00106122, 0, 0.0986407, 0.938716, 0.00111857, 0, 0.111537, 0.926629, 0.00114762, 0, 0.125182, 0.914025, 0.00118995, 0, 0.139571, 0.901026, 0.00121228, 0, 0.154703, 0.890358, 0.00123946, 0, 0.170576, 0.878283, 0.0012527, 0, 0.18719, 0.865459, 0.00125536, 0, 0.204547, 0.851407, 0.00126134, 0, 0.222648, 0.836276, 0.00124759, 0, 0.241498, 0.820436, 0.00124443, 0, 0.261101, 0.803253, 0.00122071, 0, 0.281465, 0.785562, 0.00120107, 0, 0.302595, 0.76718, 0.00117762, 0, 0.324501, 0.748551, 0.00114289, 0, 0.347192, 0.730564, 0.00110872, 0, 0.370679, 0.712253, 0.00107636, 0, 0.394973, 0.692867, 0.00103646, 0, 0.420085, 0.673695, 0.000996793, 0, 0.446027, 0.653912, 0.00095675, 0, 0.47281, 0.634129, 0.000916739, 0, 0.500441, 0.615004, 0.000874401, 0, 0.528921, 0.595587, 0.000833411, 0, 0.558244, 0.575965, 0.000794556, 0, 0.588384, 0.5566, 0.00075196, 0, 0.619281, 0.537428, 0.000716381, 0, 0.650795, 0.518623, 0.000676558, 0, 0.68254, 0.499964, 0.00064074, 0, 0.714286, 0.481356, 0.000605984, 0, 0.746032, 0.463279, 0.000570256, 0, 0.777778, 0.445673, 0.000540138, 0, 0.809524, 0.428032, 0.000507299, 0, 0.84127, 0.411112, 0.000479553, 0, 0.873016, 0.394444, 0.000450737, 0, 0.904762, 0.378247, 0.000424269, 0, 0.936508, 0.362415, 0.000399111, 0, 0.968254, 0.347103, 0.000375274, 0, 1, 1, 0.000300729, 0, 0, 1, 0.000300733, 0, 0, 1, 0.000300797, 0, 0, 0.999998, 0.000301072, 0, 0, 0.999996, 0.000301817, 0, 0, 0.999989, 0.000303398, 0, 0, 0.999977, 0.000306309, 0, 0, 0.999958, 0.000311209, 0, 0, 0.999927, 0.000318975, 0, 0, 0.999884, 0.000330804, 0, 0, 0.99982, 0.00034834, 0, 0, 0.999733, 0.000373854, 0, 3.26995e-05, 0.999613, 0.000410424, 0, 0.000477174, 0.999447, 0.000462047, 0, 0.00161099, 0.999204, 0.000533322, 0, 0.00353153, 0.998725, 0.000624964, 0, 0.00627965, 0.995871, 0.000631786, 0, 0.0098693, 0.993194, 0.000632017, 0, 0.0143011, 0.991541, 0.00068923, 0, 0.019568, 0.989773, 0.000766892, 0, 0.0256593, 0.987647, 0.000863668, 0, 0.0325625, 0.984193, 0.000922089, 0, 0.0402647, 0.980016, 0.000970749, 0, 0.0487532, 0.975859, 0.00106027, 0, 0.058016, 0.970514, 0.00112239, 0, 0.0680419, 0.963625, 0.00117212, 0, 0.0788208, 0.956959, 0.00125211, 0, 0.0903439, 0.947956, 0.00129411, 0, 0.102604, 0.93809, 0.00135879, 0, 0.115594, 0.92659, 0.00139309, 0, 0.129309, 0.913829, 0.00143253, 0, 0.143745, 0.90005, 0.00145809, 0, 0.158901, 0.888129, 0.0014748, 0, 0.174774, 0.87607, 0.00148756, 0, 0.191365, 0.863461, 0.00148714, 0, 0.208674, 0.849594, 0.00148892, 0, 0.226705, 0.834531, 0.00146496, 0, 0.245461, 0.81903, 0.0014579, 0, 0.264947, 0.802122, 0.00143039, 0, 0.28517, 0.78445, 0.00139717, 0, 0.306137, 0.766434, 0.00136312, 0, 0.327857, 0.747816, 0.00132597, 0, 0.350341, 0.729519, 0.00128323, 0, 0.373598, 0.711454, 0.00123803, 0, 0.397642, 0.692699, 0.00119097, 0, 0.422485, 0.673723, 0.00114565, 0, 0.448139, 0.654386, 0.00109552, 0, 0.474619, 0.634673, 0.00104553, 0, 0.501933, 0.615554, 0.00099985, 0, 0.530089, 0.596462, 0.000948207, 0, 0.559087, 0.577385, 0.000902299, 0, 0.588913, 0.558257, 0.000856448, 0, 0.619525, 0.5392, 0.000810395, 0, 0.650826, 0.520543, 0.000768558, 0, 0.68254, 0.502206, 0.0007239, 0, 0.714286, 0.48402, 0.000685794, 0, 0.746032, 0.465779, 0.00064471, 0, 0.777778, 0.448455, 0.000609583, 0, 0.809524, 0.431091, 0.00057227, 0, 0.84127, 0.414147, 0.00054042, 0, 0.873016, 0.39765, 0.000506545, 0, 0.904762, 0.381576, 0.000477635, 0, 0.936508, 0.365881, 0.000448446, 0, 0.968254, 0.350582, 0.000421424, 0, 1, 1, 0.000427144, 0, 0, 1, 0.000427151, 0, 0, 1, 0.000427232, 0, 0, 0.999998, 0.00042759, 0, 0, 0.999995, 0.000428555, 0, 0, 0.999988, 0.000430603, 0, 0, 0.999976, 0.000434368, 0, 0, 0.999952, 0.000440688, 0, 0, 0.999919, 0.000450667, 0, 0, 0.999871, 0.00046578, 0, 0, 0.999801, 0.000488024, 0, 0, 0.999704, 0.000520092, 0, 0.000129791, 0.999572, 0.000565553, 0, 0.000821056, 0.999389, 0.000628906, 0, 0.00225241, 0.999114, 0.000714911, 0, 0.00449109, 0.998488, 0.000819218, 0, 0.00756249, 0.995234, 0.00080415, 0, 0.0114716, 0.993021, 0.000830181, 0, 0.0162131, 0.991407, 0.000902645, 0, 0.021776, 0.989625, 0.000996934, 0, 0.0281471, 0.987064, 0.00109707, 0, 0.0353118, 0.983265, 0.00114353, 0, 0.0432562, 0.979535, 0.0012272, 0, 0.0519665, 0.975224, 0.00132642, 0, 0.0614298, 0.969574, 0.00138092, 0, 0.0716348, 0.963021, 0.00145896, 0, 0.0825709, 0.956046, 0.00152834, 0, 0.094229, 0.947136, 0.00158217, 0, 0.106602, 0.937313, 0.0016347, 0, 0.119682, 0.926073, 0.00168383, 0, 0.133465, 0.913121, 0.00171627, 0, 0.147947, 0.899165, 0.00174229, 0, 0.163125, 0.885891, 0.00176137, 0, 0.178998, 0.873783, 0.00176406, 0, 0.195566, 0.861331, 0.00176156, 0, 0.21283, 0.847569, 0.00175346, 0, 0.230793, 0.832785, 0.00172753, 0, 0.249459, 0.817442, 0.00170204, 0, 0.268832, 0.800613, 0.00166576, 0, 0.28892, 0.783597, 0.00162909, 0, 0.30973, 0.76571, 0.0015826, 0, 0.331271, 0.747021, 0.00153106, 0, 0.353554, 0.728593, 0.00148036, 0, 0.37659, 0.710661, 0.00142808, 0, 0.400391, 0.692426, 0.00136906, 0, 0.424973, 0.673623, 0.00131066, 0, 0.450347, 0.65494, 0.00125569, 0, 0.476531, 0.635448, 0.00119517, 0, 0.503535, 0.616221, 0.00113828, 0, 0.531372, 0.597531, 0.0010816, 0, 0.560047, 0.578795, 0.00102673, 0, 0.589554, 0.559892, 0.000970985, 0, 0.619869, 0.541307, 0.000919773, 0, 0.650923, 0.522608, 0.000868479, 0, 0.68254, 0.504484, 0.00082137, 0, 0.714286, 0.486603, 0.000772916, 0, 0.746032, 0.468802, 0.000730353, 0, 0.777778, 0.451172, 0.000684955, 0, 0.809524, 0.434348, 0.000647565, 0, 0.84127, 0.417445, 0.000605863, 0, 0.873016, 0.401077, 0.000571885, 0, 0.904762, 0.385039, 0.000536034, 0, 0.936508, 0.369483, 0.000504227, 0, 0.968254, 0.354272, 0.000473165, 0, 1, 1, 0.000599525, 0, 0, 1, 0.000599533, 0, 0, 1, 0.000599639, 0, 0, 0.999998, 0.000600097, 0, 0, 0.999994, 0.000601336, 0, 0, 0.999987, 0.000603958, 0, 0, 0.999972, 0.000608775, 0, 0, 0.999949, 0.000616842, 0, 0, 0.999912, 0.000629534, 0, 0, 0.999857, 0.000648658, 0, 0, 0.999781, 0.000676615, 0, 5.38873e-06, 0.999674, 0.000716574, 0, 0.000308602, 0.999528, 0.000772641, 0, 0.00127003, 0.999326, 0.000849806, 0, 0.00300783, 0.999009, 0.000952682, 0, 0.00556637, 0.998112, 0.00106394, 0, 0.00895889, 0.994496, 0.00102228, 0, 0.0131827, 0.992806, 0.00108586, 0, 0.0182277, 0.991211, 0.0011759, 0, 0.0240795, 0.989415, 0.00128955, 0, 0.030723, 0.986499, 0.00139038, 0, 0.0381418, 0.982679, 0.00144539, 0, 0.046321, 0.978839, 0.00153954, 0, 0.0552459, 0.974295, 0.00164417, 0, 0.0649034, 0.968784, 0.00171517, 0, 0.0752814, 0.962324, 0.00180282, 0, 0.0863693, 0.954956, 0.00186387, 0, 0.0981578, 0.94624, 0.00193817, 0, 0.110639, 0.936517, 0.00198156, 0, 0.123806, 0.925186, 0.00203042, 0, 0.137655, 0.91252, 0.0020664, 0, 0.15218, 0.898441, 0.00207822, 0, 0.16738, 0.884394, 0.0020992, 0, 0.183253, 0.871273, 0.00208748, 0, 0.199799, 0.859057, 0.00208686, 0, 0.21702, 0.845243, 0.00205519, 0, 0.234918, 0.830723, 0.00202868, 0, 0.253496, 0.815801, 0.00199501, 0, 0.272761, 0.79914, 0.00194193, 0, 0.292719, 0.782372, 0.00188824, 0, 0.313377, 0.76482, 0.00183695, 0, 0.334745, 0.746586, 0.00177418, 0, 0.356833, 0.7281, 0.00170628, 0, 0.379654, 0.709842, 0.00164063, 0, 0.403221, 0.692019, 0.00157355, 0, 0.427548, 0.67364, 0.00150262, 0, 0.452651, 0.655277, 0.00143473, 0, 0.478545, 0.636438, 0.00136371, 0, 0.505246, 0.617364, 0.00129911, 0, 0.532768, 0.598603, 0.00123014, 0, 0.561122, 0.580195, 0.00116587, 0, 0.590309, 0.561786, 0.00110398, 0, 0.620318, 0.543377, 0.00104148, 0, 0.651102, 0.525093, 0.000983984, 0, 0.682545, 0.506791, 0.00092667, 0, 0.714286, 0.489291, 0.000874326, 0, 0.746032, 0.471811, 0.000821734, 0, 0.777778, 0.454435, 0.000774698, 0, 0.809524, 0.437493, 0.000727302, 0, 0.84127, 0.420977, 0.000684039, 0, 0.873016, 0.404729, 0.00064373, 0, 0.904762, 0.388756, 0.00060285, 0, 0.936508, 0.373344, 0.00056765, 0, 0.968254, 0.358191, 0.000531929, 0, 1, 1, 0.000832169, 0, 0, 1, 0.000832178, 0, 0, 1, 0.00083231, 0, 0, 0.999998, 0.000832893, 0, 0, 0.999995, 0.000834465, 0, 0, 0.999985, 0.000837791, 0, 0, 0.999969, 0.000843893, 0, 0, 0.999944, 0.000854086, 0, 0, 0.999903, 0.000870071, 0, 0, 0.999843, 0.000894042, 0, 0, 0.999759, 0.000928865, 0, 5.31805e-05, 0.999643, 0.000978242, 0, 0.000579365, 0.99948, 0.00104684, 0, 0.00182774, 0.999255, 0.00114012, 0, 0.00387804, 0.998885, 0.00126188, 0, 0.00675709, 0.997405, 0.00135888, 0, 0.010468, 0.99424, 0.00133626, 0, 0.0150018, 0.992458, 0.00140905, 0, 0.0203443, 0.990929, 0.00152305, 0, 0.0264786, 0.989116, 0.00165882, 0, 0.0333875, 0.985624, 0.00174128, 0, 0.0410536, 0.982003, 0.00182108, 0, 0.0494609, 0.978336, 0.00194498, 0, 0.0585941, 0.973184, 0.00202708, 0, 0.0684396, 0.9678, 0.00212166, 0, 0.0789851, 0.961348, 0.00221366, 0, 0.0902199, 0.953841, 0.00228219, 0, 0.102134, 0.94534, 0.00235662, 0, 0.114721, 0.935552, 0.00240572, 0, 0.127972, 0.924064, 0.00244405, 0, 0.141884, 0.911827, 0.00247557, 0, 0.156451, 0.897731, 0.00248374, 0, 0.171672, 0.883409, 0.00249863, 0, 0.187545, 0.868625, 0.00246688, 0, 0.20407, 0.856529, 0.00246523, 0, 0.221249, 0.842999, 0.00242368, 0, 0.239083, 0.828505, 0.00237354, 0, 0.257578, 0.813825, 0.00232588, 0, 0.276738, 0.797813, 0.00226731, 0, 0.296569, 0.781097, 0.00219704, 0, 0.31708, 0.764038, 0.00212394, 0, 0.338281, 0.746067, 0.00204786, 0, 0.360181, 0.727687, 0.00196728, 0, 0.382794, 0.709571, 0.00188779, 0, 0.406133, 0.691503, 0.00180532, 0, 0.430213, 0.673673, 0.00171849, 0, 0.45505, 0.655732, 0.00164147, 0, 0.480662, 0.637399, 0.00155858, 0, 0.507065, 0.618616, 0.00147641, 0, 0.534278, 0.60005, 0.00140125, 0, 0.562313, 0.581713, 0.00132441, 0, 0.59118, 0.563546, 0.00125014, 0, 0.620875, 0.545605, 0.00118249, 0, 0.651373, 0.527559, 0.0011116, 0, 0.682593, 0.509764, 0.00104979, 0, 0.714286, 0.49193, 0.000985977, 0, 0.746032, 0.475011, 0.000928592, 0, 0.777778, 0.457878, 0.000873466, 0, 0.809524, 0.440979, 0.000819585, 0, 0.84127, 0.424613, 0.000772365, 0, 0.873016, 0.408549, 0.000722195, 0, 0.904762, 0.392771, 0.000680014, 0, 0.936508, 0.377317, 0.000636797, 0, 0.968254, 0.362352, 0.000598318, 0, 1, 1, 0.00114313, 0, 0, 1, 0.00114314, 0, 0, 0.999999, 0.00114331, 0, 0, 0.999998, 0.00114404, 0, 0, 0.999994, 0.00114601, 0, 0, 0.999984, 0.00115019, 0, 0, 0.999967, 0.00115784, 0, 0, 0.999937, 0.0011706, 0, 0, 0.999894, 0.00119054, 0, 0, 0.999828, 0.00122031, 0, 0, 0.999735, 0.00126331, 0, 0.000169263, 0.999606, 0.00132382, 0, 0.000949167, 0.999426, 0.0014071, 0, 0.00249668, 0.999173, 0.00151895, 0, 0.00486392, 0.99873, 0.00166102, 0, 0.00806323, 0.996243, 0.0017023, 0, 0.0120895, 0.993779, 0.00172782, 0, 0.0169288, 0.9919, 0.0018108, 0, 0.0225633, 0.990524, 0.00196028, 0, 0.028974, 0.98868, 0.00212014, 0, 0.036142, 0.984663, 0.00217598, 0, 0.044049, 0.981457, 0.00230563, 0, 0.0526781, 0.977608, 0.00243966, 0, 0.0620137, 0.972215, 0.00251336, 0, 0.0720418, 0.966798, 0.0026285, 0, 0.0827499, 0.960241, 0.00271409, 0, 0.0941271, 0.952489, 0.00278381, 0, 0.106164, 0.944127, 0.00285399, 0, 0.118852, 0.934282, 0.00290994, 0, 0.132185, 0.923271, 0.00294558, 0, 0.146157, 0.910803, 0.00296269, 0, 0.160766, 0.896705, 0.00296803, 0, 0.176007, 0.88238, 0.00296637, 0, 0.19188, 0.867116, 0.00293163, 0, 0.208385, 0.853636, 0.00289418, 0, 0.225523, 0.840469, 0.00284663, 0, 0.243296, 0.82639, 0.00278594, 0, 0.261709, 0.811759, 0.00271618, 0, 0.280767, 0.796113, 0.00263187, 0, 0.300476, 0.779518, 0.00254589, 0, 0.320845, 0.763142, 0.00246003, 0, 0.341883, 0.745464, 0.00236529, 0, 0.363601, 0.727491, 0.00226536, 0, 0.386011, 0.709414, 0.00216375, 0, 0.409128, 0.691396, 0.00207127, 0, 0.432967, 0.67368, 0.00197106, 0, 0.457545, 0.656049, 0.00187022, 0, 0.482881, 0.638188, 0.00177605, 0, 0.508992, 0.620177, 0.00168482, 0, 0.535899, 0.601506, 0.00158909, 0, 0.563619, 0.58362, 0.00150583, 0, 0.592165, 0.565496, 0.00141791, 0, 0.621544, 0.54789, 0.00133693, 0, 0.651743, 0.530323, 0.00126038, 0, 0.682709, 0.512795, 0.00118556, 0, 0.714286, 0.495199, 0.00111527, 0, 0.746032, 0.478101, 0.0010489, 0, 0.777778, 0.461511, 0.000984264, 0, 0.809524, 0.444879, 0.00092591, 0, 0.84127, 0.428424, 0.000866582, 0, 0.873016, 0.412495, 0.000814463, 0, 0.904762, 0.396975, 0.000764498, 0, 0.936508, 0.381614, 0.000715967, 0, 0.968254, 0.366732, 0.000672483, 0, 1, 1, 0.00155501, 0, 0, 1, 0.00155503, 0, 0, 1, 0.00155524, 0, 0, 0.999998, 0.00155615, 0, 0, 0.999994, 0.0015586, 0, 0, 0.999983, 0.00156379, 0, 0, 0.999963, 0.0015733, 0, 0, 0.999932, 0.00158911, 0, 0, 0.999882, 0.00161376, 0, 0, 0.99981, 0.00165041, 0, 1.00875e-05, 0.999708, 0.00170304, 0, 0.000367658, 0.999565, 0.00177658, 0, 0.0014234, 0.999368, 0.00187688, 0, 0.00327939, 0.999081, 0.00200989, 0, 0.00596629, 0.99852, 0.00217177, 0, 0.0094852, 0.99549, 0.0021745, 0, 0.013824, 0.993252, 0.00222357, 0, 0.0189642, 0.991727, 0.00235022, 0, 0.0248856, 0.989951, 0.00250561, 0, 0.0315669, 0.988029, 0.00268829, 0, 0.0389882, 0.984029, 0.0027496, 0, 0.0471302, 0.980683, 0.00289793, 0, 0.0559754, 0.976554, 0.00303315, 0, 0.0655081, 0.97139, 0.00313257, 0, 0.0757138, 0.965544, 0.00323656, 0, 0.08658, 0.95912, 0.00333432, 0, 0.0980954, 0.951183, 0.0034039, 0, 0.110251, 0.942974, 0.00347515, 0, 0.123038, 0.932642, 0.00350381, 0, 0.13645, 0.922158, 0.00354519, 0, 0.150482, 0.909404, 0.00353851, 0, 0.165129, 0.896071, 0.0035435, 0, 0.18039, 0.881206, 0.00349936, 0, 0.196263, 0.866077, 0.00347256, 0, 0.212748, 0.85093, 0.003415, 0, 0.229847, 0.837703, 0.00333367, 0, 0.247561, 0.823878, 0.003249, 0, 0.265895, 0.809449, 0.00316347, 0, 0.284854, 0.794379, 0.00306351, 0, 0.304445, 0.778138, 0.0029499, 0, 0.324675, 0.761997, 0.00284099, 0, 0.345555, 0.744938, 0.00272104, 0, 0.367095, 0.727212, 0.00260715, 0, 0.389309, 0.709549, 0.00248855, 0, 0.41221, 0.691704, 0.00236783, 0, 0.435814, 0.673689, 0.00225178, 0, 0.460138, 0.656453, 0.00213765, 0, 0.485203, 0.639128, 0.00202178, 0, 0.511028, 0.621512, 0.00191443, 0, 0.537634, 0.603598, 0.00180977, 0, 0.565041, 0.58559, 0.00170456, 0, 0.593268, 0.567852, 0.00160927, 0, 0.622327, 0.5503, 0.00151395, 0, 0.652217, 0.533033, 0.00142499, 0, 0.682907, 0.515942, 0.00133955, 0, 0.714296, 0.498814, 0.0012602, 0, 0.746032, 0.481595, 0.00118188, 0, 0.777778, 0.465117, 0.00111171, 0, 0.809524, 0.448865, 0.00104091, 0, 0.84127, 0.432711, 0.000976618, 0, 0.873016, 0.416822, 0.00091859, 0, 0.904762, 0.401272, 0.000857704, 0, 0.936508, 0.386226, 0.000807172, 0, 0.968254, 0.371321, 0.00075464, 0, 1, 1, 0.00209596, 0, 0, 1, 0.00209598, 0, 0, 1, 0.00209624, 0, 0, 0.999997, 0.00209736, 0, 0, 0.999991, 0.00210039, 0, 0, 0.999979, 0.00210678, 0, 0, 0.999959, 0.00211847, 0, 0, 0.999925, 0.0021379, 0, 0, 0.99987, 0.00216809, 0, 0, 0.999791, 0.00221281, 0, 6.81487e-05, 0.999677, 0.00227669, 0, 0.000658161, 0.999521, 0.00236533, 0, 0.00200635, 0.999301, 0.00248514, 0, 0.0041779, 0.998977, 0.00264185, 0, 0.00718648, 0.998191, 0.00281695, 0, 0.0110239, 0.994801, 0.00278518, 0, 0.015672, 0.993091, 0.00288774, 0, 0.0211091, 0.991571, 0.00303931, 0, 0.0273123, 0.9897, 0.00321643, 0, 0.034259, 0.987023, 0.00337332, 0, 0.0419282, 0.983289, 0.00346146, 0, 0.0502998, 0.979892, 0.00363704, 0, 0.0593562, 0.975111, 0.00373601, 0, 0.069081, 0.970351, 0.0038842, 0, 0.0794598, 0.964131, 0.00397053, 0, 0.0904798, 0.957747, 0.00408078, 0, 0.10213, 0.949536, 0.00413533, 0, 0.1144, 0.941372, 0.00420305, 0, 0.127284, 0.931049, 0.00422815, 0, 0.140772, 0.920647, 0.00425048, 0, 0.154862, 0.908033, 0.0042281, 0, 0.169548, 0.895028, 0.00422026, 0, 0.184828, 0.879968, 0.00415042, 0, 0.200701, 0.864875, 0.00408821, 0, 0.217167, 0.84918, 0.00400909, 0, 0.234227, 0.834934, 0.00391178, 0, 0.251884, 0.821397, 0.00380066, 0, 0.270141, 0.807135, 0.00367974, 0, 0.289004, 0.792363, 0.00355172, 0, 0.308479, 0.776661, 0.003411, 0, 0.328575, 0.760705, 0.00328123, 0, 0.349301, 0.744408, 0.00314003, 0, 0.370668, 0.726994, 0.0029906, 0, 0.392689, 0.709598, 0.00285034, 0, 0.415379, 0.692112, 0.00271179, 0, 0.438754, 0.674435, 0.00257185, 0, 0.46283, 0.65676, 0.00243425, 0, 0.48763, 0.639982, 0.00230351, 0, 0.513173, 0.622983, 0.0021777, 0, 0.539482, 0.605471, 0.00204991, 0, 0.566579, 0.58796, 0.00193759, 0, 0.594488, 0.570463, 0.00181976, 0, 0.623226, 0.553058, 0.00171497, 0, 0.6528, 0.535894, 0.00161109, 0, 0.683198, 0.519089, 0.00151394, 0, 0.714354, 0.502454, 0.00142122, 0, 0.746032, 0.485681, 0.00133488, 0, 0.777778, 0.468935, 0.00124975, 0, 0.809524, 0.452951, 0.00117309, 0, 0.84127, 0.437139, 0.00110155, 0, 0.873016, 0.421446, 0.00103124, 0, 0.904762, 0.405951, 0.000966387, 0, 0.936508, 0.391003, 0.000908119, 0, 0.968254, 0.376198, 0.000848057, 0, 1, 1, 0.00280076, 0, 0, 1, 0.00280078, 0, 0, 0.999999, 0.00280109, 0, 0, 0.999997, 0.00280246, 0, 0, 0.999992, 0.00280616, 0, 0, 0.999979, 0.00281396, 0, 0, 0.999956, 0.00282822, 0, 0, 0.999916, 0.00285186, 0, 0, 0.999857, 0.0028885, 0, 0, 0.999768, 0.00294259, 0, 0.000196026, 0.999645, 0.00301946, 0, 0.00104842, 0.99947, 0.00312541, 0, 0.00270199, 0.999229, 0.00326733, 0, 0.00519449, 0.998852, 0.00344992, 0, 0.00852602, 0.997558, 0.00361052, 0, 0.0126804, 0.994417, 0.0035898, 0, 0.017635, 0.992824, 0.00372393, 0, 0.023365, 0.991344, 0.00390695, 0, 0.0298456, 0.989337, 0.00410392, 0, 0.0370529, 0.985811, 0.00420987, 0, 0.0449651, 0.982772, 0.00437488, 0, 0.0535615, 0.979001, 0.00455069, 0, 0.0628243, 0.974102, 0.00464462, 0, 0.0727368, 0.969197, 0.00480577, 0, 0.0832844, 0.962759, 0.00487818, 0, 0.0944545, 0.956207, 0.00498176, 0, 0.106236, 0.947909, 0.00503392, 0, 0.118619, 0.939596, 0.00507474, 0, 0.131595, 0.929642, 0.00509798, 0, 0.145159, 0.918807, 0.00508476, 0, 0.159305, 0.906921, 0.00505634, 0, 0.174028, 0.893312, 0.00498845, 0, 0.189327, 0.878933, 0.0049133, 0, 0.2052, 0.863986, 0.0048259, 0, 0.221647, 0.847936, 0.00470848, 0, 0.23867, 0.832253, 0.00456889, 0, 0.25627, 0.818619, 0.00442726, 0, 0.274453, 0.804788, 0.00427677, 0, 0.293222, 0.790241, 0.00411906, 0, 0.312585, 0.775162, 0.00394833, 0, 0.33255, 0.759463, 0.00377366, 0, 0.353126, 0.743598, 0.00361026, 0, 0.374324, 0.72697, 0.00343627, 0, 0.396158, 0.709646, 0.00326422, 0, 0.418641, 0.69277, 0.00309717, 0, 0.44179, 0.675371, 0.0029356, 0, 0.465624, 0.657863, 0.00277712, 0, 0.490163, 0.640772, 0.00261738, 0, 0.515429, 0.624441, 0.0024737, 0, 0.541445, 0.607497, 0.00233125, 0, 0.568236, 0.590438, 0.00218994, 0, 0.595828, 0.573224, 0.0020664, 0, 0.624242, 0.556168, 0.00193526, 0, 0.653496, 0.539232, 0.00182463, 0, 0.683588, 0.522352, 0.00170735, 0, 0.714482, 0.506172, 0.00160555, 0, 0.746032, 0.489842, 0.00150451, 0, 0.777778, 0.473463, 0.00140938, 0, 0.809524, 0.457266, 0.00132568, 0, 0.84127, 0.441609, 0.0012376, 0, 0.873016, 0.426348, 0.00116265, 0, 0.904762, 0.411002, 0.00108935, 0, 0.936508, 0.396045, 0.00101946, 0, 0.968254, 0.381448, 0.000955665, 0, 1, 1, 0.0037121, 0, 0, 1, 0.00371213, 0, 0, 1, 0.00371251, 0, 0, 0.999997, 0.00371417, 0, 0, 0.99999, 0.00371863, 0, 0, 0.999977, 0.00372807, 0, 0, 0.99995, 0.00374529, 0, 0, 0.999908, 0.0037738, 0, 0, 0.999843, 0.00381789, 0, 1.23596e-05, 0.999745, 0.00388273, 0, 0.000407442, 0.999608, 0.00397443, 0, 0.0015447, 0.999415, 0.00409998, 0, 0.00351385, 0.999143, 0.00426662, 0, 0.0063316, 0.9987, 0.00447625, 0, 0.00998679, 0.996363, 0.00455323, 0, 0.0144569, 0.994021, 0.00461052, 0, 0.0197151, 0.992372, 0.00476359, 0, 0.0257344, 0.991007, 0.00499101, 0, 0.0324882, 0.988767, 0.0051972, 0, 0.0399517, 0.984872, 0.00528407, 0, 0.0481022, 0.982004, 0.00548926, 0, 0.0569191, 0.977714, 0.00564385, 0, 0.0663839, 0.973076, 0.0057693, 0, 0.0764801, 0.967565, 0.0058924, 0, 0.0871928, 0.961384, 0.00599629, 0, 0.0985095, 0.954435, 0.00605998, 0, 0.110419, 0.946303, 0.0061133, 0, 0.122912, 0.937662, 0.00612028, 0, 0.13598, 0.927867, 0.00612209, 0, 0.149617, 0.916475, 0.00604813, 0, 0.163817, 0.90541, 0.00603088, 0, 0.178577, 0.891591, 0.00592218, 0, 0.193894, 0.877573, 0.00578854, 0, 0.209767, 0.862511, 0.00566648, 0, 0.226196, 0.846861, 0.00551481, 0, 0.243182, 0.83068, 0.00533754, 0, 0.260728, 0.815725, 0.00515487, 0, 0.278837, 0.802321, 0.0049655, 0, 0.297515, 0.787826, 0.00475421, 0, 0.316768, 0.773454, 0.00456002, 0, 0.336605, 0.758224, 0.00434727, 0, 0.357034, 0.74265, 0.00414444, 0, 0.378067, 0.726729, 0.00393738, 0, 0.399717, 0.710155, 0.00373575, 0, 0.421998, 0.693312, 0.00353736, 0, 0.444928, 0.67653, 0.00334368, 0, 0.468523, 0.659444, 0.00315981, 0, 0.492806, 0.642051, 0.00297809, 0, 0.517798, 0.625758, 0.00280592, 0, 0.543525, 0.609615, 0.00264254, 0, 0.570012, 0.592919, 0.00248459, 0, 0.597288, 0.576298, 0.00233327, 0, 0.625379, 0.559489, 0.00219519, 0, 0.654307, 0.542891, 0.00205441, 0, 0.684084, 0.526255, 0.00193385, 0, 0.714693, 0.509853, 0.00180745, 0, 0.746044, 0.494131, 0.00169817, 0, 0.777778, 0.478114, 0.0015913, 0, 0.809524, 0.462274, 0.00148981, 0, 0.84127, 0.446412, 0.00139537, 0, 0.873016, 0.431274, 0.00130984, 0, 0.904762, 0.41635, 0.00122403, 0, 0.936508, 0.401476, 0.00114809, 0, 0.968254, 0.386993, 0.00107563, 0, 1, 1, 0.00488216, 0, 0, 1, 0.0048822, 0, 0, 1, 0.00488265, 0, 0, 0.999997, 0.00488463, 0, 0, 0.999988, 0.00488999, 0, 0, 0.999974, 0.00490129, 0, 0, 0.999946, 0.00492191, 0, 0, 0.999897, 0.00495598, 0, 0, 0.999825, 0.00500855, 0, 7.44791e-05, 0.999718, 0.00508559, 0, 0.000712744, 0.999565, 0.005194, 0, 0.00215249, 0.999352, 0.00534147, 0, 0.00444576, 0.999046, 0.00553523, 0, 0.00759218, 0.998492, 0.00577016, 0, 0.0115714, 0.995564, 0.00578487, 0, 0.0163557, 0.993339, 0.00586414, 0, 0.021915, 0.991834, 0.00606002, 0, 0.0282201, 0.990496, 0.00633312, 0, 0.0352433, 0.987826, 0.00651941, 0, 0.042959, 0.98383, 0.00660842, 0, 0.0513439, 0.98109, 0.00685523, 0, 0.0603772, 0.976131, 0.00695778, 0, 0.0700402, 0.971922, 0.00714236, 0, 0.0803163, 0.965901, 0.00721437, 0, 0.0911908, 0.959606, 0.00732017, 0, 0.102651, 0.952504, 0.00735788, 0, 0.114686, 0.944365, 0.00738493, 0, 0.127286, 0.935652, 0.00737969, 0, 0.140443, 0.925813, 0.00733612, 0, 0.154151, 0.914397, 0.00723094, 0, 0.168405, 0.903257, 0.00714002, 0, 0.183201, 0.890015, 0.00700149, 0, 0.198536, 0.876014, 0.00682813, 0, 0.214409, 0.861436, 0.00665567, 0, 0.23082, 0.845752, 0.00644526, 0, 0.24777, 0.829169, 0.00621635, 0, 0.265263, 0.813435, 0.00597789, 0, 0.283301, 0.799701, 0.00575694, 0, 0.301889, 0.785726, 0.00549866, 0, 0.321035, 0.77152, 0.0052503, 0, 0.340746, 0.75683, 0.00499619, 0, 0.361032, 0.741951, 0.0047543, 0, 0.381904, 0.726367, 0.0045084, 0, 0.403374, 0.710537, 0.00426784, 0, 0.425457, 0.693965, 0.00403487, 0, 0.448169, 0.677724, 0.0038075, 0, 0.47153, 0.66117, 0.00359431, 0, 0.495561, 0.644274, 0.00338354, 0, 0.520284, 0.627449, 0.00318163, 0, 0.545725, 0.611645, 0.00299672, 0, 0.571911, 0.595614, 0.00281016, 0, 0.598873, 0.579426, 0.00264252, 0, 0.62664, 0.563016, 0.00247509, 0, 0.655239, 0.546728, 0.00232647, 0, 0.684692, 0.530539, 0.00217803, 0, 0.714999, 0.514164, 0.00204216, 0, 0.746106, 0.498344, 0.00191403, 0, 0.777778, 0.482957, 0.00179203, 0, 0.809524, 0.467336, 0.00167695, 0, 0.84127, 0.451994, 0.00157567, 0, 0.873016, 0.436514, 0.00147113, 0, 0.904762, 0.42178, 0.00138034, 0, 0.936508, 0.407271, 0.00129219, 0, 0.968254, 0.392822, 0.0012098, 0, 1, 1, 0.00637427, 0, 0, 1, 0.00637431, 0, 0, 0.999999, 0.00637485, 0, 0, 0.999996, 0.00637721, 0, 0, 0.999987, 0.00638357, 0, 0, 0.999971, 0.006397, 0, 0, 0.999939, 0.00642142, 0, 0, 0.999888, 0.00646177, 0, 0, 0.999807, 0.00652387, 0, 0.000207916, 0.999689, 0.00661454, 0, 0.00112051, 0.99952, 0.00674155, 0, 0.00287719, 0.999283, 0.00691313, 0, 0.00550145, 0.998936, 0.00713598, 0, 0.00897928, 0.998165, 0.00738501, 0, 0.0132829, 0.994847, 0.00734388, 0, 0.01838, 0.993182, 0.00749991, 0, 0.0242381, 0.991665, 0.0077246, 0, 0.030826, 0.989708, 0.00797579, 0, 0.0381152, 0.986663, 0.00813011, 0, 0.0460794, 0.983288, 0.00830365, 0, 0.0546951, 0.980104, 0.00853496, 0, 0.0639411, 0.974855, 0.00861045, 0, 0.0737988, 0.97045, 0.00879133, 0, 0.0842516, 0.964509, 0.00886377, 0, 0.0952848, 0.957594, 0.00890346, 0, 0.106886, 0.950546, 0.00893289, 0, 0.119044, 0.942225, 0.00890074, 0, 0.131749, 0.933365, 0.00886826, 0, 0.144994, 0.923202, 0.0087316, 0, 0.158772, 0.912605, 0.00863082, 0, 0.173078, 0.901099, 0.00847403, 0, 0.187908, 0.888177, 0.00825838, 0, 0.203261, 0.873955, 0.00801834, 0, 0.219134, 0.860091, 0.00779026, 0, 0.235527, 0.84434, 0.00752478, 0, 0.252443, 0.828517, 0.00724074, 0, 0.269883, 0.81239, 0.00693769, 0, 0.287851, 0.79721, 0.00664817, 0, 0.306352, 0.783489, 0.00634763, 0, 0.325393, 0.769514, 0.00604221, 0, 0.344981, 0.755419, 0.00573568, 0, 0.365126, 0.741083, 0.00544359, 0, 0.385839, 0.726059, 0.00515515, 0, 0.407132, 0.710809, 0.00487139, 0, 0.42902, 0.695052, 0.00459846, 0, 0.45152, 0.678886, 0.00433412, 0, 0.474651, 0.663042, 0.00407981, 0, 0.498433, 0.646634, 0.00384264, 0, 0.52289, 0.630117, 0.00360897, 0, 0.548048, 0.613804, 0.00338863, 0, 0.573936, 0.598338, 0.00318486, 0, 0.600584, 0.582687, 0.00298377, 0, 0.628027, 0.566809, 0.00280082, 0, 0.656295, 0.550817, 0.00262255, 0, 0.685417, 0.534937, 0.00245835, 0, 0.715406, 0.519151, 0.00230574, 0, 0.74624, 0.503118, 0.0021549, 0, 0.777778, 0.487723, 0.00202008, 0, 0.809524, 0.472725, 0.00189355, 0, 0.84127, 0.457599, 0.00177108, 0, 0.873016, 0.442558, 0.00165843, 0, 0.904762, 0.427624, 0.00155494, 0, 0.936508, 0.413171, 0.00145273, 0, 0.968254, 0.399122, 0.00136454, 0, 1, 1, 0.00826496, 0, 0, 1, 0.00826499, 0, 0, 1, 0.00826564, 0, 0, 0.999996, 0.00826842, 0, 0, 0.999987, 0.00827589, 0, 0, 0.999967, 0.00829167, 0, 0, 0.999933, 0.00832037, 0, 0, 0.999876, 0.00836768, 0, 1.09338e-05, 0.999786, 0.00844031, 0, 0.000427145, 0.999655, 0.00854603, 0, 0.0016384, 0.999468, 0.00869337, 0, 0.00372392, 0.999203, 0.008891, 0, 0.00668513, 0.998803, 0.00914387, 0, 0.0104968, 0.99748, 0.00935838, 0, 0.015125, 0.994446, 0.00933309, 0, 0.0205338, 0.99292, 0.00953084, 0, 0.0266884, 0.991414, 0.0097893, 0, 0.0335565, 0.989049, 0.0100228, 0, 0.0411086, 0.98582, 0.0101664, 0, 0.0493181, 0.982441, 0.0103582, 0, 0.0581613, 0.978595, 0.0105292, 0, 0.0676169, 0.973495, 0.0106274, 0, 0.0776661, 0.968405, 0.0107261, 0, 0.0882926, 0.962717, 0.0108234, 0, 0.0994817, 0.955478, 0.0108102, 0, 0.111221, 0.948275, 0.0107914, 0, 0.123499, 0.940006, 0.0107161, 0, 0.136308, 0.930831, 0.0106309, 0, 0.149639, 0.920648, 0.0104083, 0, 0.163485, 0.910205, 0.0102312, 0, 0.177843, 0.898445, 0.0100051, 0, 0.192707, 0.885986, 0.00971928, 0, 0.208077, 0.872204, 0.00940747, 0, 0.22395, 0.858436, 0.0091085, 0, 0.240326, 0.843454, 0.00876595, 0, 0.257208, 0.827437, 0.00839794, 0, 0.274596, 0.811488, 0.00803692, 0, 0.292496, 0.796039, 0.00767352, 0, 0.310911, 0.781083, 0.0073097, 0, 0.329849, 0.767642, 0.00694032, 0, 0.349316, 0.753901, 0.00657476, 0, 0.369323, 0.740131, 0.00622699, 0, 0.38988, 0.725845, 0.0058838, 0, 0.410999, 0.710991, 0.00555586, 0, 0.432696, 0.696002, 0.00523089, 0, 0.454987, 0.680461, 0.00492494, 0, 0.47789, 0.664875, 0.00463464, 0, 0.501426, 0.649273, 0.00435422, 0, 0.52562, 0.63302, 0.0040875, 0, 0.550498, 0.61705, 0.00384075, 0, 0.576089, 0.601154, 0.00359557, 0, 0.602427, 0.586008, 0.00337636, 0, 0.629544, 0.570699, 0.00316019, 0, 0.657479, 0.555166, 0.00296033, 0, 0.686264, 0.539645, 0.00277552, 0, 0.715924, 0.524159, 0.00259499, 0, 0.746459, 0.508682, 0.00243257, 0, 0.777789, 0.493163, 0.00227851, 0, 0.809524, 0.478004, 0.00213083, 0, 0.84127, 0.46347, 0.00199502, 0, 0.873016, 0.448778, 0.00186967, 0, 0.904762, 0.434105, 0.00174732, 0, 0.936508, 0.419576, 0.00163861, 0, 0.968254, 0.405541, 0.00153341, 0, 1, 1, 0.0106462, 0, 0, 1, 0.0106462, 0, 0, 0.999999, 0.010647, 0, 0, 0.999995, 0.0106502, 0, 0, 0.999985, 0.0106589, 0, 0, 0.999964, 0.0106773, 0, 0, 0.999925, 0.0107106, 0, 0, 0.999861, 0.0107655, 0, 7.12986e-05, 0.999763, 0.0108497, 0, 0.000743959, 0.999616, 0.0109716, 0, 0.00227361, 0.999408, 0.0111408, 0, 0.0046983, 0.999112, 0.0113659, 0, 0.00800158, 0.998637, 0.0116475, 0, 0.0121493, 0.996223, 0.0117231, 0, 0.0171023, 0.994006, 0.0118064, 0, 0.0228218, 0.992444, 0.0120254, 0, 0.0292711, 0.991028, 0.0123314, 0, 0.036417, 0.98803, 0.0124954, 0, 0.0442295, 0.984816, 0.0126538, 0, 0.0526815, 0.981399, 0.0128537, 0, 0.0617492, 0.977085, 0.0129694, 0, 0.0714114, 0.972154, 0.013091, 0, 0.0816495, 0.966617, 0.0131166, 0, 0.0924472, 0.960628, 0.0131583, 0, 0.10379, 0.953295, 0.0131094, 0, 0.115665, 0.94575, 0.0129966, 0, 0.128062, 0.937654, 0.0128796, 0, 0.140972, 0.927716, 0.0126477, 0, 0.154387, 0.917932, 0.0123889, 0, 0.168301, 0.907719, 0.012131, 0, 0.182709, 0.89584, 0.0118013, 0, 0.197608, 0.883526, 0.0114145, 0, 0.212994, 0.870301, 0.0110075, 0, 0.228867, 0.856272, 0.0106019, 0, 0.245227, 0.842251, 0.0101938, 0, 0.262074, 0.826466, 0.00973254, 0, 0.279412, 0.810859, 0.0092846, 0, 0.297244, 0.795051, 0.00883304, 0, 0.315575, 0.780053, 0.00840272, 0, 0.334412, 0.76575, 0.00796438, 0, 0.35376, 0.752298, 0.00752526, 0, 0.373631, 0.739153, 0.00711486, 0, 0.394034, 0.725514, 0.00670361, 0, 0.414983, 0.711473, 0.00632656, 0, 0.436491, 0.696936, 0.00595206, 0, 0.458575, 0.682126, 0.00559191, 0, 0.481253, 0.667027, 0.00525362, 0, 0.504547, 0.651875, 0.00493805, 0, 0.528481, 0.636463, 0.00462848, 0, 0.553081, 0.620641, 0.00433936, 0, 0.578377, 0.604931, 0.00407, 0, 0.604404, 0.589549, 0.00380864, 0, 0.631197, 0.574712, 0.00357049, 0, 0.658795, 0.559775, 0.00334466, 0, 0.687238, 0.544514, 0.00312505, 0, 0.716559, 0.529555, 0.00293199, 0, 0.746776, 0.514402, 0.00274204, 0, 0.777849, 0.499302, 0.00256647, 0, 0.809524, 0.484114, 0.00239901, 0, 0.84127, 0.469308, 0.00225148, 0, 0.873016, 0.455133, 0.00210178, 0, 0.904762, 0.440939, 0.0019727, 0, 0.936508, 0.426627, 0.00184382, 0, 0.968254, 0.412509, 0.00172548, 0, 1, 1, 0.013628, 0, 0, 1, 0.0136281, 0, 0, 0.999999, 0.0136289, 0, 0, 0.999995, 0.0136327, 0, 0, 0.999983, 0.0136427, 0, 0, 0.99996, 0.0136638, 0, 0, 0.999917, 0.0137022, 0, 0, 0.999846, 0.0137652, 0, 0.000204597, 0.999736, 0.0138615, 0, 0.00116837, 0.999573, 0.0140007, 0, 0.00303325, 0.99934, 0.0141927, 0, 0.00580613, 0.999004, 0.0144457, 0, 0.00945626, 0.998407, 0.0147489, 0, 0.0139421, 0.995464, 0.014731, 0, 0.0192202, 0.993328, 0.0148283, 0, 0.0252495, 0.991799, 0.0150797, 0, 0.0319921, 0.990397, 0.0154316, 0, 0.0394138, 0.986835, 0.0155005, 0, 0.0474843, 0.983938, 0.0157308, 0, 0.0561763, 0.980154, 0.0158753, 0, 0.0654661, 0.975659, 0.0159581, 0, 0.0753326, 0.970171, 0.0159832, 0, 0.0857571, 0.964803, 0.0160084, 0, 0.0967236, 0.958366, 0.0159484, 0, 0.108218, 0.950613, 0.0158001, 0, 0.120227, 0.942874, 0.0155845, 0, 0.132741, 0.935005, 0.0154292, 0, 0.145751, 0.924991, 0.0150742, 0, 0.159249, 0.914814, 0.0146757, 0, 0.17323, 0.904743, 0.0143097, 0, 0.187687, 0.893216, 0.0138695, 0, 0.202619, 0.880769, 0.0133706, 0, 0.218021, 0.868136, 0.0128606, 0, 0.233894, 0.85469, 0.0123403, 0, 0.250238, 0.840593, 0.0118091, 0, 0.267052, 0.825808, 0.011253, 0, 0.284341, 0.81009, 0.0107099, 0, 0.302106, 0.79504, 0.0101636, 0, 0.320354, 0.779757, 0.00964041, 0, 0.33909, 0.764697, 0.00911896, 0, 0.358322, 0.750913, 0.00859533, 0, 0.378059, 0.738175, 0.00811592, 0, 0.398311, 0.725242, 0.00764504, 0, 0.41909, 0.711864, 0.00718885, 0, 0.440412, 0.698009, 0.00675843, 0, 0.462292, 0.683841, 0.00634984, 0, 0.484748, 0.669391, 0.00595502, 0, 0.507802, 0.654731, 0.00558671, 0, 0.531477, 0.639805, 0.00523578, 0, 0.555802, 0.624789, 0.00490834, 0, 0.580805, 0.609325, 0.00459448, 0, 0.606522, 0.593975, 0.00430342, 0, 0.63299, 0.578983, 0.00403019, 0, 0.66025, 0.564442, 0.0037707, 0, 0.688346, 0.549835, 0.0035316, 0, 0.717319, 0.535039, 0.00330255, 0, 0.7472, 0.520403, 0.00308932, 0, 0.777982, 0.505687, 0.00289335, 0, 0.809524, 0.490939, 0.00270818, 0, 0.84127, 0.476233, 0.0025343, 0, 0.873016, 0.461624, 0.00237097, 0, 0.904762, 0.447833, 0.00222065, 0, 0.936508, 0.433992, 0.00207561, 0, 0.968254, 0.420147, 0.00194955, 0, 1, 1, 0.0173415, 0, 0, 1, 0.0173416, 0, 0, 0.999999, 0.0173426, 0, 0, 0.999995, 0.0173468, 0, 0, 0.999983, 0.0173582, 0, 0, 0.999954, 0.0173822, 0, 0, 0.999908, 0.0174258, 0, 6.69501e-06, 0.999828, 0.0174973, 0, 0.000427399, 0.999705, 0.0176063, 0, 0.00171019, 0.999524, 0.0177631, 0, 0.0039248, 0.999263, 0.0179781, 0, 0.00705382, 0.998878, 0.018258, 0, 0.0110552, 0.998012, 0.0185551, 0, 0.0158812, 0.994614, 0.0184264, 0, 0.0214852, 0.993132, 0.0186385, 0, 0.0278239, 0.991563, 0.0189067, 0, 0.0348585, 0.989298, 0.0191577, 0, 0.0425544, 0.986036, 0.0192522, 0, 0.050881, 0.982558, 0.0194063, 0, 0.059811, 0.978531, 0.019486, 0, 0.0693209, 0.974198, 0.0195847, 0, 0.0793895, 0.968148, 0.0194749, 0, 0.0899984, 0.962565, 0.0194277, 0, 0.101132, 0.956041, 0.0192991, 0, 0.112775, 0.947749, 0.0189893, 0, 0.124917, 0.94018, 0.018704, 0, 0.137547, 0.93165, 0.0183458, 0, 0.150655, 0.921798, 0.0178775, 0, 0.164236, 0.911573, 0.0173618, 0, 0.178281, 0.901569, 0.0168482, 0, 0.192788, 0.890341, 0.016265, 0, 0.207752, 0.877835, 0.0156199, 0, 0.223171, 0.865472, 0.0149516, 0, 0.239044, 0.852905, 0.0143274, 0, 0.255371, 0.838906, 0.0136643, 0, 0.272153, 0.824888, 0.0129903, 0, 0.289393, 0.809977, 0.0123218, 0, 0.307093, 0.794697, 0.0116572, 0, 0.325259, 0.780028, 0.0110307, 0, 0.343896, 0.765124, 0.0104236, 0, 0.363012, 0.750411, 0.0098219, 0, 0.382617, 0.737264, 0.00924397, 0, 0.402719, 0.724799, 0.00868719, 0, 0.423332, 0.712253, 0.00816476, 0, 0.444469, 0.699267, 0.00767262, 0, 0.466146, 0.685618, 0.00719746, 0, 0.488383, 0.671736, 0.00673916, 0, 0.511199, 0.657777, 0.00631937, 0, 0.534618, 0.643497, 0.00592411, 0, 0.558668, 0.62889, 0.00553928, 0, 0.58338, 0.614299, 0.0051934, 0, 0.608787, 0.599197, 0.00485985, 0, 0.634929, 0.584175, 0.00454357, 0, 0.661849, 0.569541, 0.00425787, 0, 0.689594, 0.555193, 0.00397905, 0, 0.718211, 0.540947, 0.00372364, 0, 0.747742, 0.526593, 0.00348599, 0, 0.778205, 0.512335, 0.00326103, 0, 0.80953, 0.498017, 0.00305137, 0, 0.84127, 0.483609, 0.00285485, 0, 0.873016, 0.469368, 0.00267472, 0, 0.904762, 0.455037, 0.00249945, 0, 0.936508, 0.441493, 0.00234792, 0, 0.968254, 0.428147, 0.00219936, 0, 1, 1, 0.0219422, 0, 0, 1, 0.0219423, 0, 0, 0.999998, 0.0219434, 0, 0, 0.999993, 0.0219481, 0, 0, 0.999981, 0.021961, 0, 0, 0.999949, 0.0219879, 0, 0, 0.999896, 0.0220367, 0, 5.93194e-05, 0.999808, 0.0221167, 0, 0.00075364, 0.99967, 0.0222383, 0, 0.00237884, 0.999466, 0.0224125, 0, 0.00495612, 0.999174, 0.0226495, 0, 0.00844887, 0.998725, 0.0229525, 0, 0.0128058, 0.996979, 0.0231123, 0, 0.0179742, 0.994317, 0.0230742, 0, 0.0239047, 0.992781, 0.0232895, 0, 0.0305526, 0.991191, 0.0235734, 0, 0.0378786, 0.987787, 0.0236152, 0, 0.0458475, 0.985092, 0.0237994, 0, 0.0544287, 0.981121, 0.0238553, 0, 0.0635952, 0.976924, 0.0238706, 0, 0.0733233, 0.97218, 0.0238704, 0, 0.0835922, 0.965956, 0.0236598, 0, 0.0943839, 0.959998, 0.0234735, 0, 0.105682, 0.953245, 0.0232277, 0, 0.117474, 0.944445, 0.0226973, 0, 0.129747, 0.937087, 0.0223527, 0, 0.142491, 0.928341, 0.0218144, 0, 0.155697, 0.9184, 0.0211516, 0, 0.169358, 0.907959, 0.0204553, 0, 0.183469, 0.89808, 0.0197673, 0, 0.198024, 0.887047, 0.0189915, 0, 0.21302, 0.875221, 0.0182082, 0, 0.228455, 0.86269, 0.0173584, 0, 0.244329, 0.850735, 0.0165718, 0, 0.260639, 0.837545, 0.0157524, 0, 0.277389, 0.823639, 0.0149482, 0, 0.29458, 0.809699, 0.0141431, 0, 0.312216, 0.794797, 0.0133527, 0, 0.3303, 0.780578, 0.0126193, 0, 0.34884, 0.766019, 0.0118914, 0, 0.367842, 0.751447, 0.0111839, 0, 0.387315, 0.737275, 0.010514, 0, 0.40727, 0.724545, 0.00987277, 0, 0.427717, 0.712644, 0.00926569, 0, 0.448671, 0.700432, 0.00869029, 0, 0.470149, 0.687664, 0.00814691, 0, 0.492167, 0.674288, 0.00763012, 0, 0.514746, 0.660966, 0.00714437, 0, 0.537911, 0.647264, 0.00668457, 0, 0.561688, 0.633431, 0.00626581, 0, 0.586108, 0.619133, 0.00585593, 0, 0.611206, 0.604935, 0.00548188, 0, 0.637022, 0.590236, 0.00513288, 0, 0.663599, 0.575473, 0.0047906, 0, 0.690989, 0.561228, 0.00448895, 0, 0.719242, 0.547054, 0.00420233, 0, 0.748411, 0.533175, 0.00392869, 0, 0.778531, 0.519163, 0.00367445, 0, 0.809583, 0.505328, 0.00344097, 0, 0.84127, 0.491446, 0.00322003, 0, 0.873016, 0.477356, 0.00301283, 0, 0.904762, 0.46356, 0.00282592, 0, 0.936508, 0.449623, 0.00264956, 0, 0.968254, 0.436068, 0.00246956, 0, 1, 1, 0.0276135, 0, 0, 1, 0.0276136, 0, 0, 0.999998, 0.0276148, 0, 0, 0.999993, 0.0276201, 0, 0, 0.999976, 0.0276342, 0, 0, 0.999945, 0.027664, 0, 0, 0.999884, 0.0277179, 0, 0.00018679, 0.999784, 0.027806, 0, 0.00119607, 0.99963, 0.0279394, 0, 0.00318407, 0.999401, 0.0281295, 0, 0.00613601, 0.999066, 0.0283858, 0, 0.00999963, 0.998524, 0.0287027, 0, 0.0147164, 0.995702, 0.0286256, 0, 0.0202295, 0.993593, 0.0286733, 0, 0.0264876, 0.992067, 0.0288989, 0, 0.0334452, 0.990548, 0.0292135, 0, 0.0410621, 0.986775, 0.0291296, 0, 0.0493032, 0.984054, 0.0293099, 0, 0.0581381, 0.979481, 0.0291881, 0, 0.0675397, 0.975297, 0.0291598, 0, 0.0774848, 0.96981, 0.028954, 0, 0.0879528, 0.963524, 0.028628, 0, 0.0989258, 0.957398, 0.0283135, 0, 0.110388, 0.950088, 0.0278469, 0, 0.122327, 0.941538, 0.0271798, 0, 0.134729, 0.933332, 0.0265388, 0, 0.147587, 0.924392, 0.0257776, 0, 0.160889, 0.914581, 0.024916, 0, 0.174631, 0.904347, 0.0240242, 0, 0.188806, 0.894324, 0.0231229, 0, 0.203409, 0.883724, 0.022153, 0, 0.218437, 0.872207, 0.0211355, 0, 0.233888, 0.859927, 0.0201048, 0, 0.249761, 0.848373, 0.0191263, 0, 0.266056, 0.836023, 0.0181306, 0, 0.282774, 0.82289, 0.0171718, 0, 0.299917, 0.809324, 0.0162196, 0, 0.317488, 0.795361, 0.0152622, 0, 0.335493, 0.781253, 0.01439, 0, 0.353936, 0.767338, 0.013533, 0, 0.372825, 0.753156, 0.0127244, 0, 0.392168, 0.739122, 0.0119454, 0, 0.411976, 0.725358, 0.0112054, 0, 0.432259, 0.712949, 0.010487, 0, 0.453032, 0.701621, 0.00984032, 0, 0.47431, 0.689703, 0.00921495, 0, 0.496111, 0.677216, 0.00862492, 0, 0.518456, 0.664217, 0.00806882, 0, 0.541367, 0.65137, 0.00755922, 0, 0.564872, 0.638, 0.00705705, 0, 0.589001, 0.62453, 0.00661266, 0, 0.613789, 0.610601, 0.00618432, 0, 0.639277, 0.59676, 0.00578033, 0, 0.66551, 0.582433, 0.00540927, 0, 0.692539, 0.568026, 0.00506104, 0, 0.720422, 0.55414, 0.0047353, 0, 0.749216, 0.540178, 0.00442889, 0, 0.778974, 0.526513, 0.00414363, 0, 0.809711, 0.512954, 0.00388237, 0, 0.84127, 0.499403, 0.00362875, 0, 0.873016, 0.486026, 0.00340827, 0, 0.904762, 0.472345, 0.00318598, 0, 0.936508, 0.458828, 0.00297635, 0, 0.968254, 0.445379, 0.00279447, 0, 1, 1, 0.0345716, 0, 0, 1, 0.0345717, 0, 0, 0.999999, 0.034573, 0, 0, 0.999991, 0.0345787, 0, 0, 0.999974, 0.0345941, 0, 0, 0.999937, 0.0346263, 0, 1.88589e-06, 0.999869, 0.0346847, 0, 0.000409238, 0.999757, 0.0347798, 0, 0.0017674, 0.999582, 0.0349233, 0, 0.00413658, 0.999322, 0.0351265, 0, 0.00747408, 0.998939, 0.0353967, 0, 0.0117157, 0.998219, 0.0357018, 0, 0.0167966, 0.994974, 0.0354726, 0, 0.0226572, 0.993201, 0.0355621, 0, 0.0292445, 0.991573, 0.0357641, 0, 0.0365123, 0.989301, 0.0359252, 0, 0.0444203, 0.985712, 0.0358017, 0, 0.0529334, 0.982411, 0.0358353, 0, 0.0620214, 0.977827, 0.035617, 0, 0.0716574, 0.973278, 0.0354398, 0, 0.0818186, 0.967397, 0.0350483, 0, 0.0924846, 0.960696, 0.0344795, 0, 0.103638, 0.954349, 0.0339861, 0, 0.115263, 0.946066, 0.0331323, 0, 0.127348, 0.938012, 0.032359, 0, 0.13988, 0.929413, 0.0314413, 0, 0.152849, 0.920355, 0.0304103, 0, 0.166248, 0.910586, 0.0292785, 0, 0.18007, 0.900609, 0.0281391, 0, 0.194308, 0.890093, 0.0269103, 0, 0.208958, 0.880013, 0.0257269, 0, 0.224018, 0.869001, 0.0244671, 0, 0.239485, 0.85751, 0.0232252, 0, 0.255359, 0.84582, 0.0220117, 0, 0.271638, 0.834383, 0.0208274, 0, 0.288324, 0.822158, 0.0196628, 0, 0.305419, 0.809056, 0.0185306, 0, 0.322927, 0.795832, 0.0174174, 0, 0.340851, 0.782547, 0.0163758, 0, 0.359199, 0.7689, 0.015391, 0, 0.377975, 0.755526, 0.0144488, 0, 0.397189, 0.741681, 0.0135372, 0, 0.416851, 0.728178, 0.0126957, 0, 0.436971, 0.714642, 0.0118812, 0, 0.457564, 0.702756, 0.0111165, 0, 0.478644, 0.69175, 0.0104145, 0, 0.500229, 0.680159, 0.00974439, 0, 0.522339, 0.668073, 0.00911926, 0, 0.544997, 0.655405, 0.00851393, 0, 0.56823, 0.642921, 0.00797637, 0, 0.592068, 0.629993, 0.00745119, 0, 0.616546, 0.616828, 0.00696972, 0, 0.641705, 0.603305, 0.00652425, 0, 0.66759, 0.589833, 0.00610188, 0, 0.694255, 0.575945, 0.00570834, 0, 0.72176, 0.561745, 0.00533384, 0, 0.750168, 0.548277, 0.00500001, 0, 0.779545, 0.534467, 0.00467582, 0, 0.809933, 0.521032, 0.00438092, 0, 0.841272, 0.507877, 0.00410348, 0, 0.873016, 0.494654, 0.00383618, 0, 0.904762, 0.481592, 0.00358699, 0, 0.936508, 0.468509, 0.00337281, 0, 0.968254, 0.455293, 0.00316196, 0, 1, 1, 0.0430698, 0, 0, 1, 0.0430699, 0, 0, 0.999998, 0.0430713, 0, 0, 0.999991, 0.0430773, 0, 0, 0.99997, 0.0430936, 0, 0, 0.999928, 0.0431277, 0, 4.06396e-05, 0.999852, 0.0431893, 0, 0.000744376, 0.999724, 0.0432895, 0, 0.0024806, 0.999527, 0.0434397, 0, 0.00524779, 0.99923, 0.0436507, 0, 0.00898164, 0.998783, 0.0439255, 0, 0.0136083, 0.997507, 0.0441104, 0, 0.0190582, 0.994418, 0.0438225, 0, 0.0252694, 0.992864, 0.0439396, 0, 0.0321879, 0.991127, 0.0440962, 0, 0.039767, 0.987331, 0.0438408, 0, 0.0479667, 0.984819, 0.0438991, 0, 0.056752, 0.980384, 0.0435906, 0, 0.0660929, 0.975846, 0.0432543, 0, 0.075963, 0.970748, 0.0428293, 0, 0.0863398, 0.964303, 0.042153, 0, 0.0972035, 0.95772, 0.0414111, 0, 0.108537, 0.950747, 0.0405893, 0, 0.120325, 0.942533, 0.0394887, 0, 0.132554, 0.934045, 0.0383544, 0, 0.145215, 0.924942, 0.037057, 0, 0.158296, 0.915811, 0.0356993, 0, 0.17179, 0.90612, 0.0342401, 0, 0.185691, 0.896434, 0.0328078, 0, 0.199993, 0.886021, 0.031288, 0, 0.214691, 0.876081, 0.0297776, 0, 0.229782, 0.865608, 0.0282334, 0, 0.245265, 0.854924, 0.026749, 0, 0.261138, 0.843607, 0.02526, 0, 0.277401, 0.832456, 0.0238214, 0, 0.294056, 0.821342, 0.0224682, 0, 0.311104, 0.809303, 0.0211297, 0, 0.328548, 0.796468, 0.0198387, 0, 0.346394, 0.784046, 0.0186227, 0, 0.364645, 0.771262, 0.0174561, 0, 0.38331, 0.758118, 0.0163806, 0, 0.402396, 0.745075, 0.0153287, 0, 0.421912, 0.731926, 0.0143647, 0, 0.44187, 0.71863, 0.0134363, 0, 0.462283, 0.705414, 0.0125603, 0, 0.483165, 0.693792, 0.0117508, 0, 0.504535, 0.683108, 0.0110016, 0, 0.52641, 0.67183, 0.0102757, 0, 0.548816, 0.66015, 0.00962044, 0, 0.571776, 0.647907, 0.00898031, 0, 0.595323, 0.635734, 0.00840811, 0, 0.619489, 0.623208, 0.00786211, 0, 0.644317, 0.610438, 0.00734953, 0, 0.669852, 0.597345, 0.00687688, 0, 0.696148, 0.584138, 0.00643469, 0, 0.723267, 0.5707, 0.00602236, 0, 0.75128, 0.556966, 0.0056324, 0, 0.780258, 0.543607, 0.00528277, 0, 0.810268, 0.530213, 0.00493999, 0, 0.841311, 0.516912, 0.00462265, 0, 0.873016, 0.503916, 0.0043307, 0, 0.904762, 0.491146, 0.00406858, 0, 0.936508, 0.478439, 0.00381436, 0, 0.968254, 0.465834, 0.00358003, 0, 1, 1, 0.0534039, 0, 0, 1, 0.053404, 0, 0, 0.999998, 0.0534055, 0, 0, 0.999989, 0.0534116, 0, 0, 0.999968, 0.0534283, 0, 0, 0.999918, 0.0534633, 0, 0.000155895, 0.99983, 0.0535262, 0, 0.00120914, 0.999685, 0.0536281, 0, 0.00334944, 0.999461, 0.0537799, 0, 0.00653077, 0.999119, 0.0539902, 0, 0.0106718, 0.998582, 0.0542524, 0, 0.0156907, 0.995919, 0.0540318, 0, 0.0215147, 0.993735, 0.0538914, 0, 0.0280801, 0.992126, 0.0539557, 0, 0.0353323, 0.990266, 0.0540401, 0, 0.0432247, 0.986317, 0.0536064, 0, 0.0517172, 0.983213, 0.0534425, 0, 0.0607754, 0.978303, 0.0528622, 0, 0.0703698, 0.973665, 0.0523363, 0, 0.0804742, 0.968091, 0.0516165, 0, 0.0910667, 0.961026, 0.0505434, 0, 0.102128, 0.954333, 0.049523, 0, 0.113641, 0.946372, 0.0481698, 0, 0.125591, 0.938254, 0.0467674, 0, 0.137965, 0.929516, 0.0452341, 0, 0.150754, 0.920106, 0.0435083, 0, 0.163947, 0.910899, 0.0417399, 0, 0.177537, 0.901532, 0.0399389, 0, 0.191516, 0.891919, 0.0380901, 0, 0.205881, 0.882006, 0.0362341, 0, 0.220626, 0.871965, 0.0343444, 0, 0.235749, 0.862145, 0.0324832, 0, 0.251248, 0.852058, 0.0306681, 0, 0.267121, 0.84161, 0.0289097, 0, 0.283368, 0.830806, 0.0272079, 0, 0.299992, 0.820476, 0.0256089, 0, 0.316992, 0.809514, 0.0240394, 0, 0.334374, 0.797865, 0.0225379, 0, 0.35214, 0.785621, 0.0211235, 0, 0.370296, 0.773765, 0.0197908, 0, 0.388849, 0.761629, 0.0185235, 0, 0.407807, 0.748891, 0.0173358, 0, 0.427178, 0.736437, 0.0162305, 0, 0.446974, 0.723707, 0.0151778, 0, 0.467207, 0.710606, 0.0141791, 0, 0.487892, 0.698019, 0.0132592, 0, 0.509046, 0.686203, 0.0123887, 0, 0.530687, 0.675692, 0.0115976, 0, 0.552839, 0.664826, 0.0108325, 0, 0.575527, 0.65349, 0.0101348, 0, 0.59878, 0.641774, 0.00947756, 0, 0.622634, 0.629794, 0.00886058, 0, 0.647128, 0.617647, 0.00828526, 0, 0.672308, 0.60534, 0.00775312, 0, 0.698231, 0.592718, 0.00726033, 0, 0.724958, 0.579746, 0.00679731, 0, 0.752563, 0.566763, 0.00636111, 0, 0.781127, 0.553515, 0.00595228, 0, 0.810733, 0.540118, 0.00556876, 0, 0.841426, 0.527325, 0.00523051, 0, 0.873016, 0.514265, 0.00490712, 0, 0.904762, 0.501406, 0.00460297, 0, 0.936508, 0.488922, 0.00431247, 0, 0.968254, 0.476541, 0.0040472, 0, 1, 1, 0.0659184, 0, 0, 1, 0.0659185, 0, 0, 0.999998, 0.06592, 0, 0, 0.999988, 0.0659259, 0, 0, 0.999963, 0.0659423, 0, 0, 0.999907, 0.0659764, 0, 0.000374198, 0.999806, 0.0660376, 0, 0.00182071, 0.999639, 0.0661361, 0, 0.0043894, 0.999378, 0.0662814, 0, 0.00800055, 0.998985, 0.0664779, 0, 0.0125594, 0.998285, 0.0666914, 0, 0.0179786, 0.995071, 0.0661989, 0, 0.0241822, 0.993172, 0.0660454, 0, 0.031106, 0.991438, 0.0660105, 0, 0.0386952, 0.988428, 0.0656875, 0, 0.0469032, 0.985218, 0.0652913, 0, 0.0556905, 0.981128, 0.0647107, 0, 0.065023, 0.976015, 0.0638491, 0, 0.0748717, 0.97097, 0.062993, 0, 0.0852112, 0.964582, 0.0617927, 0, 0.0960199, 0.957383, 0.0603626, 0, 0.107279, 0.949969, 0.0588128, 0, 0.118971, 0.941843, 0.0570274, 0, 0.131084, 0.933624, 0.0551885, 0, 0.143604, 0.924543, 0.053122, 0, 0.156521, 0.914919, 0.0508897, 0, 0.169825, 0.905773, 0.0486418, 0, 0.18351, 0.896434, 0.0463364, 0, 0.197569, 0.887195, 0.0440623, 0, 0.211997, 0.877706, 0.0417799, 0, 0.226789, 0.867719, 0.03945, 0, 0.241944, 0.858587, 0.037243, 0, 0.257458, 0.849317, 0.0350956, 0, 0.273331, 0.839585, 0.0329852, 0, 0.289563, 0.829856, 0.0310028, 0, 0.306154, 0.819589, 0.0290953, 0, 0.323108, 0.809714, 0.0272738, 0, 0.340426, 0.79934, 0.0255631, 0, 0.358113, 0.788224, 0.0239175, 0, 0.376175, 0.776619, 0.0223831, 0, 0.394616, 0.76521, 0.0209298, 0, 0.413445, 0.753716, 0.0195786, 0, 0.432671, 0.741564, 0.0183001, 0, 0.452305, 0.729413, 0.0171259, 0, 0.472358, 0.717146, 0.0159933, 0, 0.492845, 0.70436, 0.0149495, 0, 0.513783, 0.69219, 0.0139681, 0, 0.535189, 0.680289, 0.0130577, 0, 0.557087, 0.669611, 0.0122198, 0, 0.5795, 0.659113, 0.0114174, 0, 0.602459, 0.648148, 0.0106729, 0, 0.625997, 0.636905, 0.00998997, 0, 0.650154, 0.625154, 0.00934313, 0, 0.674976, 0.613481, 0.00874839, 0, 0.700518, 0.60154, 0.00818265, 0, 0.726845, 0.58943, 0.00766889, 0, 0.754032, 0.576828, 0.00717153, 0, 0.782167, 0.564194, 0.00672696, 0, 0.811344, 0.551501, 0.00630863, 0, 0.841644, 0.538635, 0.00592177, 0, 0.873016, 0.525724, 0.00554888, 0, 0.904762, 0.513209, 0.00520225, 0, 0.936508, 0.500457, 0.00488231, 0, 0.968254, 0.48799, 0.00457153, 0, 1, 1, 0.0810131, 0, 0, 1, 0.0810133, 0, 0, 0.999997, 0.0810145, 0, 0, 0.999985, 0.08102, 0, 0, 0.999956, 0.0810347, 0, 1.95026e-05, 0.999893, 0.0810656, 0, 0.000719316, 0.999777, 0.0811205, 0, 0.00259774, 0.999583, 0.081208, 0, 0.00561807, 0.999281, 0.0813343, 0, 0.00967472, 0.998813, 0.0814969, 0, 0.0146627, 0.997597, 0.0815217, 0, 0.0204902, 0.994379, 0.0808502, 0, 0.0270802, 0.992744, 0.0806792, 0, 0.0343674, 0.990745, 0.0804589, 0, 0.0422974, 0.986646, 0.0796107, 0, 0.0508242, 0.983611, 0.0790913, 0, 0.0599087, 0.978869, 0.0780746, 0, 0.0695175, 0.973475, 0.0768218, 0, 0.0796223, 0.967845, 0.0754926, 0, 0.0901983, 0.960778, 0.0737063, 0, 0.101224, 0.953333, 0.0718052, 0, 0.112682, 0.945274, 0.0695946, 0, 0.124555, 0.936955, 0.0672492, 0, 0.136831, 0.928319, 0.0647732, 0, 0.149496, 0.919075, 0.0620947, 0, 0.162542, 0.909114, 0.0591816, 0, 0.175958, 0.900137, 0.0563917, 0, 0.189739, 0.891069, 0.0535392, 0, 0.203877, 0.882262, 0.0507642, 0, 0.218368, 0.873232, 0.0479793, 0, 0.233208, 0.864042, 0.045226, 0, 0.248393, 0.855002, 0.0425413, 0, 0.263923, 0.846569, 0.0400126, 0, 0.279796, 0.837714, 0.0375269, 0, 0.296012, 0.828918, 0.0352027, 0, 0.312573, 0.819783, 0.0330011, 0, 0.329479, 0.810129, 0.0308908, 0, 0.346734, 0.800866, 0.0289112, 0, 0.364342, 0.79093, 0.0270255, 0, 0.382307, 0.780593, 0.0252758, 0, 0.400637, 0.769511, 0.0236178, 0, 0.419337, 0.758558, 0.0220652, 0, 0.438418, 0.747632, 0.0206289, 0, 0.457889, 0.736146, 0.0192873, 0, 0.477761, 0.724093, 0.0180333, 0, 0.49805, 0.71234, 0.0168264, 0, 0.51877, 0.700201, 0.015746, 0, 0.53994, 0.687949, 0.0147027, 0, 0.561581, 0.676163, 0.0137512, 0, 0.583718, 0.665001, 0.0128655, 0, 0.60638, 0.65472, 0.0120366, 0, 0.629599, 0.644213, 0.0112604, 0, 0.653415, 0.633382, 0.0105413, 0, 0.677874, 0.62212, 0.00986498, 0, 0.70303, 0.610631, 0.00923308, 0, 0.728948, 0.599078, 0.00864206, 0, 0.755706, 0.587519, 0.00811784, 0, 0.783396, 0.575505, 0.00761237, 0, 0.812121, 0.563148, 0.00713949, 0, 0.841989, 0.550828, 0.00668379, 0, 0.873035, 0.538458, 0.00627715, 0, 0.904762, 0.525905, 0.00588336, 0, 0.936508, 0.513517, 0.00552687, 0, 0.968254, 0.501395, 0.00519681, 0, 1, 1, 0.0991506, 0, 0, 1, 0.0991504, 0, 0, 0.999996, 0.0991515, 0, 0, 0.999984, 0.0991558, 0, 0, 0.999947, 0.0991672, 0, 0.000114389, 0.999874, 0.0991912, 0, 0.00121503, 0.999739, 0.0992331, 0, 0.00356108, 0.999514, 0.0992983, 0, 0.00705578, 0.999159, 0.0993877, 0, 0.011574, 0.998586, 0.0994837, 0, 0.017003, 0.995731, 0.0988425, 0, 0.0232484, 0.993384, 0.098276, 0, 0.0302318, 0.991615, 0.0979269, 0, 0.0378884, 0.989029, 0.0973432, 0, 0.0461641, 0.985373, 0.0963539, 0, 0.0550136, 0.981278, 0.0952306, 0, 0.0643988, 0.975777, 0.0936233, 0, 0.0742868, 0.970526, 0.0920219, 0, 0.0846501, 0.963755, 0.0898912, 0, 0.0954644, 0.956676, 0.0876064, 0, 0.106709, 0.948099, 0.0847751, 0, 0.118367, 0.939718, 0.0818638, 0, 0.130423, 0.931305, 0.078857, 0, 0.142862, 0.922342, 0.0756127, 0, 0.155674, 0.912842, 0.0721473, 0, 0.168849, 0.903304, 0.0686195, 0, 0.182378, 0.89411, 0.0650589, 0, 0.196255, 0.885512, 0.0616022, 0, 0.210473, 0.877193, 0.0582434, 0, 0.225027, 0.86877, 0.0548979, 0, 0.239915, 0.860267, 0.0516095, 0, 0.255132, 0.851915, 0.048468, 0, 0.270678, 0.843912, 0.0454447, 0, 0.286551, 0.83604, 0.0425612, 0, 0.302751, 0.828245, 0.0398752, 0, 0.31928, 0.820159, 0.0373198, 0, 0.336138, 0.81167, 0.034916, 0, 0.35333, 0.802659, 0.0326402, 0, 0.370858, 0.793921, 0.0304901, 0, 0.388728, 0.784713, 0.0284857, 0, 0.406944, 0.774946, 0.0266186, 0, 0.425515, 0.76448, 0.0248593, 0, 0.444449, 0.753793, 0.0232114, 0, 0.463756, 0.743506, 0.0217039, 0, 0.483447, 0.732555, 0.0202841, 0, 0.503535, 0.720965, 0.0189648, 0, 0.524036, 0.709422, 0.0177189, 0, 0.544968, 0.697756, 0.0165626, 0, 0.56635, 0.685565, 0.015483, 0, 0.588208, 0.673987, 0.0144892, 0, 0.610569, 0.66244, 0.0135607, 0, 0.633466, 0.651675, 0.0126956, 0, 0.656936, 0.641598, 0.0118788, 0, 0.681025, 0.63121, 0.0111261, 0, 0.705788, 0.620514, 0.010437, 0, 0.731289, 0.609366, 0.00978747, 0, 0.757606, 0.598137, 0.00917257, 0, 0.784834, 0.586966, 0.00859778, 0, 0.813085, 0.575549, 0.00806803, 0, 0.842485, 0.563797, 0.00757294, 0, 0.87313, 0.551758, 0.00710592, 0, 0.904762, 0.539894, 0.0066841, 0, 0.936508, 0.527901, 0.00627901, 0, 0.968254, 0.515819, 0.00590506, 0, 1, 1, 0.120864, 0, 0, 1, 0.120864, 0, 0, 0.999996, 0.120864, 0, 0, 0.99998, 0.120867, 0, 0, 0.99994, 0.120872, 0, 0.000323781, 0.999852, 0.120884, 0, 0.00188693, 0.999693, 0.120903, 0, 0.00473489, 0.999426, 0.120929, 0, 0.00872704, 0.999002, 0.120955, 0, 0.0137237, 0.998235, 0.120918, 0, 0.0196068, 0.994608, 0.119764, 0, 0.0262803, 0.992997, 0.119265, 0, 0.0336657, 0.990968, 0.11863, 0, 0.0416987, 0.987002, 0.117261, 0, 0.0503261, 0.983524, 0.116009, 0, 0.0595035, 0.97875, 0.114252, 0, 0.0691935, 0.972652, 0.11193, 0, 0.0793645, 0.966613, 0.109555, 0, 0.0899894, 0.959275, 0.106612, 0, 0.101045, 0.951272, 0.103375, 0, 0.112512, 0.942323, 0.0996594, 0, 0.124372, 0.933679, 0.0958841, 0, 0.136611, 0.924822, 0.0919265, 0, 0.149216, 0.915742, 0.0878061, 0, 0.162176, 0.906348, 0.0834894, 0, 0.175482, 0.896883, 0.079085, 0, 0.189125, 0.88774, 0.0746745, 0, 0.203098, 0.87986, 0.0705773, 0, 0.217396, 0.871998, 0.0665005, 0, 0.232015, 0.864325, 0.0625413, 0, 0.24695, 0.856685, 0.0586781, 0, 0.2622, 0.84925, 0.0550063, 0, 0.277761, 0.841719, 0.0514727, 0, 0.293634, 0.834755, 0.0481398, 0, 0.309819, 0.827853, 0.0450172, 0, 0.326315, 0.820888, 0.0420969, 0, 0.343126, 0.813616, 0.0393702, 0, 0.360254, 0.805767, 0.0367771, 0, 0.377701, 0.797338, 0.0343274, 0, 0.395474, 0.789122, 0.0320529, 0, 0.413577, 0.780601, 0.0299485, 0, 0.432018, 0.771424, 0.0279812, 0, 0.450804, 0.761502, 0.0261054, 0, 0.469944, 0.751166, 0.0243942, 0, 0.489451, 0.741276, 0.0228087, 0, 0.509337, 0.730898, 0.0213265, 0, 0.529617, 0.719878, 0.0199307, 0, 0.550307, 0.708379, 0.0186574, 0, 0.571428, 0.697165, 0.0174446, 0, 0.593003, 0.685554, 0.0163144, 0, 0.615059, 0.673631, 0.015276, 0, 0.637628, 0.662385, 0.0143003, 0, 0.660746, 0.651059, 0.0134112, 0, 0.68446, 0.640451, 0.0125794, 0, 0.70882, 0.630536, 0.011793, 0, 0.733893, 0.620316, 0.0110547, 0, 0.759756, 0.609722, 0.0103668, 0, 0.786505, 0.598804, 0.00973009, 0, 0.814259, 0.587871, 0.00912812, 0, 0.843157, 0.577121, 0.00858916, 0, 0.87334, 0.566019, 0.00807333, 0, 0.904762, 0.554664, 0.00759687, 0, 0.936508, 0.543101, 0.00714759, 0, 0.968254, 0.531558, 0.00673418, 0, 1, 1, 0.146767, 0, 0, 1, 0.146767, 0, 0, 0.999997, 0.146767, 0, 0, 0.999977, 0.146765, 0, 3.20658e-06, 0.999929, 0.146762, 0, 0.000682576, 0.999823, 0.146753, 0, 0.00276402, 0.999633, 0.146735, 0, 0.00614771, 0.999314, 0.146699, 0, 0.0106613, 0.998796, 0.14662, 0, 0.0161546, 0.997124, 0.146107, 0, 0.0225063, 0.994062, 0.144857, 0, 0.0296198, 0.992154, 0.144011, 0, 0.037417, 0.989186, 0.142712, 0, 0.0458348, 0.985279, 0.140926, 0, 0.0548211, 0.980826, 0.13885, 0, 0.0643326, 0.975056, 0.136168, 0, 0.074333, 0.969005, 0.133217, 0, 0.0847917, 0.961554, 0.12959, 0, 0.0956828, 0.954206, 0.125886, 0, 0.106984, 0.945046, 0.121335, 0, 0.118675, 0.935678, 0.116492, 0, 0.130741, 0.926748, 0.111635, 0, 0.143166, 0.917764, 0.106625, 0, 0.155939, 0.908358, 0.101325, 0, 0.169049, 0.899219, 0.0960249, 0, 0.182487, 0.890089, 0.0906527, 0, 0.196245, 0.881488, 0.0853905, 0, 0.210317, 0.874031, 0.0804177, 0, 0.224697, 0.866932, 0.0756005, 0, 0.23938, 0.859976, 0.0709019, 0, 0.254364, 0.853375, 0.0664391, 0, 0.269646, 0.846971, 0.0622012, 0, 0.285223, 0.840483, 0.058129, 0, 0.301096, 0.833969, 0.0542762, 0, 0.317265, 0.82806, 0.0507042, 0, 0.333729, 0.822128, 0.047368, 0, 0.350491, 0.815989, 0.044272, 0, 0.367554, 0.809336, 0.0413444, 0, 0.38492, 0.802177, 0.038601, 0, 0.402594, 0.79441, 0.0360227, 0, 0.420582, 0.786573, 0.0336383, 0, 0.438891, 0.778619, 0.0314321, 0, 0.457527, 0.77, 0.029362, 0, 0.476499, 0.760698, 0.0274102, 0, 0.49582, 0.750932, 0.0256146, 0, 0.5155, 0.740993, 0.023974, 0, 0.535555, 0.731159, 0.0224182, 0, 0.556, 0.720836, 0.0209889, 0, 0.576855, 0.709913, 0.0196411, 0, 0.598143, 0.698415, 0.0183824, 0, 0.619888, 0.68745, 0.0172222, 0, 0.642123, 0.676154, 0.0161509, 0, 0.664883, 0.664383, 0.0151397, 0, 0.688211, 0.6533, 0.0141873, 0, 0.71216, 0.642072, 0.0133105, 0, 0.736792, 0.631412, 0.0124932, 0, 0.762186, 0.621622, 0.0117408, 0, 0.788439, 0.611681, 0.0110358, 0, 0.815672, 0.60142, 0.0103775, 0, 0.844034, 0.59083, 0.00975623, 0, 0.873699, 0.580254, 0.00918084, 0, 0.904765, 0.569841, 0.00864721, 0, 0.936508, 0.559224, 0.00815731, 0, 0.968254, 0.548315, 0.00767924, 0, 1, 1, 0.177563, 0, 0, 1, 0.177563, 0, 0, 0.999994, 0.177562, 0, 0, 0.999972, 0.177555, 0, 6.64171e-05, 0.999914, 0.177536, 0, 0.0012276, 0.999787, 0.177496, 0, 0.00388025, 0.999556, 0.17742, 0, 0.00783463, 0.999165, 0.177285, 0, 0.0128953, 0.9985, 0.177037, 0, 0.0189053, 0.995388, 0.175634, 0, 0.025742, 0.993102, 0.174375, 0, 0.033309, 0.990992, 0.173121, 0, 0.0415298, 0.986932, 0.170896, 0, 0.0503425, 0.982786, 0.16847, 0, 0.0596964, 0.977592, 0.165455, 0, 0.0695498, 0.971075, 0.161676, 0, 0.0798676, 0.963967, 0.157458, 0, 0.0906201, 0.956397, 0.152836, 0, 0.101783, 0.947489, 0.147467, 0, 0.113333, 0.937564, 0.14145, 0, 0.125254, 0.928182, 0.135383, 0, 0.137529, 0.919027, 0.129212, 0, 0.150144, 0.909618, 0.12276, 0, 0.163088, 0.900492, 0.116273, 0, 0.176351, 0.891671, 0.1098, 0, 0.189924, 0.883146, 0.103362, 0, 0.203799, 0.875151, 0.0970799, 0, 0.21797, 0.868338, 0.0911732, 0, 0.232433, 0.862033, 0.0854966, 0, 0.247182, 0.856107, 0.0800691, 0, 0.262216, 0.850644, 0.0749618, 0, 0.27753, 0.845261, 0.070079, 0, 0.293124, 0.839885, 0.0654321, 0, 0.308997, 0.834609, 0.0610975, 0, 0.325149, 0.829083, 0.0569741, 0, 0.341581, 0.82404, 0.0531736, 0, 0.358294, 0.818968, 0.049665, 0, 0.37529, 0.813496, 0.0463856, 0, 0.392573, 0.807533, 0.0433217, 0, 0.410148, 0.80099, 0.0404402, 0, 0.428019, 0.793891, 0.0377578, 0, 0.446192, 0.786281, 0.0352616, 0, 0.464676, 0.778773, 0.0329577, 0, 0.483478, 0.770737, 0.030808, 0, 0.502608, 0.762094, 0.0287964, 0, 0.522079, 0.752898, 0.0269254, 0, 0.541905, 0.743306, 0.0251926, 0, 0.5621, 0.733416, 0.023595, 0, 0.582684, 0.723742, 0.0221155, 0, 0.603677, 0.713542, 0.0207435, 0, 0.625106, 0.702755, 0.019434, 0, 0.646998, 0.691484, 0.0182046, 0, 0.66939, 0.680531, 0.0170771, 0, 0.692324, 0.66953, 0.0160339, 0, 0.715849, 0.658126, 0.0150677, 0, 0.740028, 0.646933, 0.0141551, 0, 0.764937, 0.636107, 0.0133179, 0, 0.790673, 0.625271, 0.0125284, 0, 0.817358, 0.615225, 0.0117937, 0, 0.84515, 0.605678, 0.0111181, 0, 0.874244, 0.59583, 0.0104759, 0, 0.904828, 0.585704, 0.00986672, 0, 0.936508, 0.575413, 0.00929712, 0, 0.968254, 0.565373, 0.00876713, 0, 1, 1, 0.214058, 0, 0, 0.999999, 0.214058, 0, 0, 0.999994, 0.214055, 0, 0, 0.999966, 0.214039, 0, 0.000259642, 0.999893, 0.213998, 0, 0.00200075, 0.999737, 0.21391, 0, 0.00527775, 0.999449, 0.213745, 0, 0.00983959, 0.99896, 0.213458, 0, 0.0154755, 0.9979, 0.212855, 0, 0.0220249, 0.994278, 0.210779, 0, 0.0293654, 0.992254, 0.20926, 0, 0.0374021, 0.98881, 0.206908, 0, 0.0460604, 0.984715, 0.204009, 0, 0.0552802, 0.979738, 0.200471, 0, 0.0650127, 0.972884, 0.195813, 0, 0.0752175, 0.965996, 0.190856, 0, 0.0858612, 0.957974, 0.185077, 0, 0.0969155, 0.949155, 0.17868, 0, 0.108356, 0.939288, 0.171513, 0, 0.120163, 0.928996, 0.163838, 0, 0.132319, 0.919563, 0.156246, 0, 0.144808, 0.910004, 0.148359, 0, 0.157618, 0.900791, 0.140417, 0, 0.170737, 0.892135, 0.132569, 0, 0.184155, 0.883803, 0.124741, 0, 0.197866, 0.876034, 0.117091, 0, 0.211861, 0.869219, 0.109835, 0, 0.226134, 0.863062, 0.102859, 0, 0.240682, 0.857795, 0.0962928, 0, 0.255499, 0.853009, 0.0900725, 0, 0.270583, 0.848603, 0.0842101, 0, 0.285931, 0.844335, 0.0786527, 0, 0.301542, 0.840208, 0.0734397, 0, 0.317415, 0.836035, 0.0685334, 0, 0.33355, 0.83172, 0.0639275, 0, 0.349948, 0.827135, 0.0595909, 0, 0.36661, 0.822797, 0.0556204, 0, 0.383539, 0.818387, 0.0519394, 0, 0.400738, 0.813565, 0.0485317, 0, 0.41821, 0.808142, 0.0453138, 0, 0.435961, 0.802212, 0.0423354, 0, 0.453997, 0.79573, 0.0395553, 0, 0.472324, 0.788741, 0.036988, 0, 0.490951, 0.781093, 0.0345688, 0, 0.509887, 0.773597, 0.0323297, 0, 0.529144, 0.765622, 0.0302719, 0, 0.548735, 0.757083, 0.0283477, 0, 0.568674, 0.747992, 0.0265562, 0, 0.588979, 0.738591, 0.0248844, 0, 0.609671, 0.728719, 0.0233342, 0, 0.630773, 0.719146, 0.0219081, 0, 0.652314, 0.709165, 0.0205711, 0, 0.674328, 0.69875, 0.0193248, 0, 0.696854, 0.687884, 0.0181582, 0, 0.719942, 0.676818, 0.0170746, 0, 0.743651, 0.666247, 0.0160718, 0, 0.768057, 0.655284, 0.0151262, 0, 0.793253, 0.64401, 0.0142561, 0, 0.819363, 0.633353, 0.0134327, 0, 0.846547, 0.622674, 0.012653, 0, 0.875017, 0.612265, 0.0119354, 0, 0.905021, 0.602455, 0.0112533, 0, 0.936508, 0.593147, 0.0106234, 0, 0.968254, 0.583592, 0.0100213, 0, 1, 1, 0.25717, 0, 0, 1, 0.25717, 0, 0, 0.999992, 0.257164, 0, 0, 0.999958, 0.257135, 0, 0.000641715, 0.999864, 0.25706, 0, 0.00305314, 0.999666, 0.256897, 0, 0.00700975, 0.999302, 0.256596, 0, 0.0122194, 0.998663, 0.25607, 0, 0.0184622, 0.995607, 0.254123, 0, 0.0255773, 0.993094, 0.252081, 0, 0.0334439, 0.9907, 0.249867, 0, 0.0419696, 0.98594, 0.246118, 0, 0.0510823, 0.981214, 0.242049, 0, 0.0607242, 0.974966, 0.236869, 0, 0.0708486, 0.967589, 0.230724, 0, 0.081417, 0.95915, 0.223635, 0, 0.0923974, 0.950257, 0.21596, 0, 0.103763, 0.940165, 0.207296, 0, 0.115491, 0.929396, 0.197901, 0, 0.127562, 0.919288, 0.188437, 0, 0.13996, 0.909428, 0.178762, 0, 0.15267, 0.900105, 0.169072, 0, 0.165679, 0.891418, 0.159478, 0, 0.178979, 0.883347, 0.15002, 0, 0.192558, 0.875992, 0.140813, 0, 0.20641, 0.869466, 0.13196, 0, 0.220529, 0.863699, 0.123501, 0, 0.234907, 0.858553, 0.115436, 0, 0.249542, 0.854379, 0.107901, 0, 0.264428, 0.850894, 0.10088, 0, 0.279564, 0.847632, 0.0942296, 0, 0.294947, 0.844571, 0.0879861, 0, 0.310575, 0.84163, 0.0821534, 0, 0.326448, 0.838542, 0.0766409, 0, 0.342566, 0.835412, 0.0715322, 0, 0.358929, 0.831899, 0.0666883, 0, 0.37554, 0.828177, 0.0622175, 0, 0.392399, 0.82416, 0.0580452, 0, 0.409511, 0.820393, 0.054267, 0, 0.426878, 0.816068, 0.0507172, 0, 0.444506, 0.811201, 0.0474041, 0, 0.4624, 0.805785, 0.0443174, 0, 0.480566, 0.799878, 0.0414562, 0, 0.499013, 0.793469, 0.0388147, 0, 0.517749, 0.786473, 0.0363453, 0, 0.536785, 0.778874, 0.0340225, 0, 0.556134, 0.771277, 0.0318599, 0, 0.575809, 0.763426, 0.0298859, 0, 0.595827, 0.755044, 0.0280357, 0, 0.616207, 0.746161, 0.0262979, 0, 0.636973, 0.737124, 0.0247295, 0, 0.65815, 0.72761, 0.0232514, 0, 0.679772, 0.717822, 0.0218755, 0, 0.701876, 0.708279, 0.0205942, 0, 0.724509, 0.698333, 0.0193947, 0, 0.74773, 0.68802, 0.0182717, 0, 0.771609, 0.677321, 0.0172044, 0, 0.79624, 0.666504, 0.0162122, 0, 0.821743, 0.656184, 0.0152924, 0, 0.84828, 0.64556, 0.0144326, 0, 0.876069, 0.634636, 0.0136157, 0, 0.905404, 0.624124, 0.0128612, 0, 0.936508, 0.613914, 0.0121435, 0, 0.968254, 0.603589, 0.0114887, 0, 1, 1, 0.307946, 0, 0, 0.999999, 0.307945, 0, 0, 0.999988, 0.307934, 0, 2.04479e-05, 0.999944, 0.307886, 0, 0.00127833, 0.999824, 0.307756, 0, 0.00445047, 0.999565, 0.30748, 0, 0.00914673, 0.999085, 0.306966, 0, 0.0150498, 0.998103, 0.306004, 0, 0.0219367, 0.994249, 0.303028, 0, 0.0296485, 0.991807, 0.300435, 0, 0.038068, 0.987773, 0.296554, 0, 0.0471062, 0.982673, 0.2916, 0, 0.0566942, 0.976623, 0.285641, 0, 0.0667768, 0.968757, 0.27815, 0, 0.0773099, 0.959849, 0.269529, 0, 0.088257, 0.950663, 0.260248, 0, 0.0995879, 0.940129, 0.249704, 0, 0.111277, 0.92895, 0.238291, 0, 0.123304, 0.917996, 0.226501, 0, 0.13565, 0.907813, 0.214669, 0, 0.148299, 0.898305, 0.202835, 0, 0.161237, 0.889626, 0.191158, 0, 0.174455, 0.88175, 0.179695, 0, 0.187941, 0.874715, 0.168548, 0, 0.201687, 0.868746, 0.15792, 0, 0.215687, 0.863703, 0.147807, 0, 0.229933, 0.859315, 0.138149, 0, 0.24442, 0.855538, 0.128993, 0, 0.259145, 0.852428, 0.120414, 0, 0.274103, 0.850168, 0.112498, 0, 0.289293, 0.848132, 0.105054, 0, 0.304711, 0.846291, 0.0981087, 0, 0.320357, 0.844431, 0.0915942, 0, 0.33623, 0.842493, 0.0855056, 0, 0.35233, 0.840368, 0.0798204, 0, 0.368658, 0.83798, 0.0745097, 0, 0.385214, 0.83523, 0.0695424, 0, 0.402002, 0.832091, 0.0649092, 0, 0.419023, 0.828667, 0.0606291, 0, 0.436282, 0.824805, 0.0566523, 0, 0.453782, 0.820988, 0.0530229, 0, 0.471529, 0.816635, 0.0496364, 0, 0.489528, 0.811725, 0.0464658, 0, 0.507788, 0.806316, 0.0435082, 0, 0.526317, 0.800469, 0.0407873, 0, 0.545124, 0.794107, 0.038255, 0, 0.564221, 0.787218, 0.0358825, 0, 0.583621, 0.779872, 0.0336785, 0, 0.603341, 0.772097, 0.0316379, 0, 0.623397, 0.764484, 0.0297379, 0, 0.643812, 0.756428, 0.0279581, 0, 0.664611, 0.748022, 0.0263153, 0, 0.685824, 0.739268, 0.0247799, 0, 0.707488, 0.73024, 0.0233385, 0, 0.729646, 0.720893, 0.0220035, 0, 0.752354, 0.71119, 0.0207555, 0, 0.77568, 0.701791, 0.0195843, 0, 0.799715, 0.692184, 0.0184891, 0, 0.824574, 0.682258, 0.0174541, 0, 0.850417, 0.67206, 0.0164873, 0, 0.877466, 0.661717, 0.0155959, 0, 0.90604, 0.651462, 0.0147519, 0, 0.936528, 0.641467, 0.0139727, 0, 0.968254, 0.631229, 0.0132363, 0, 1, 1, 0.367573, 0, 0, 0.999999, 0.367571, 0, 0, 0.999984, 0.367553, 0, 0.000183382, 0.999925, 0.367473, 0, 0.00225254, 0.999759, 0.367259, 0, 0.00628165, 0.99941, 0.366801, 0, 0.0117858, 0.998739, 0.365946, 0, 0.0184359, 0.995529, 0.363191, 0, 0.0260114, 0.992875, 0.360171, 0, 0.0343581, 0.989135, 0.355981, 0, 0.0433637, 0.984166, 0.350401, 0, 0.0529438, 0.977871, 0.343348, 0, 0.0630334, 0.96951, 0.334341, 0, 0.0735805, 0.959964, 0.323862, 0, 0.0845437, 0.950162, 0.312521, 0, 0.095889, 0.938882, 0.299577, 0, 0.107588, 0.926992, 0.285573, 0, 0.119617, 0.915589, 0.271212, 0, 0.131957, 0.904791, 0.256611, 0, 0.144591, 0.895177, 0.242224, 0, 0.157503, 0.886403, 0.227952, 0, 0.170682, 0.878957, 0.214192, 0, 0.184117, 0.872418, 0.200795, 0, 0.197799, 0.867029, 0.188015, 0, 0.21172, 0.862835, 0.175975, 0, 0.225873, 0.859411, 0.164526, 0, 0.240253, 0.856655, 0.153693, 0, 0.254854, 0.854519, 0.14352, 0, 0.269673, 0.852828, 0.13397, 0, 0.284707, 0.851412, 0.124984, 0, 0.299953, 0.850609, 0.116748, 0, 0.315408, 0.849855, 0.10905, 0, 0.331073, 0.849017, 0.101839, 0, 0.346946, 0.848079, 0.0951359, 0, 0.363028, 0.846911, 0.0888774, 0, 0.379318, 0.845445, 0.0830375, 0, 0.395818, 0.84362, 0.0775844, 0, 0.41253, 0.841411, 0.0725054, 0, 0.429457, 0.838768, 0.0677691, 0, 0.446602, 0.835801, 0.0634016, 0, 0.463968, 0.832341, 0.0593095, 0, 0.481561, 0.828424, 0.0555121, 0, 0.499386, 0.824312, 0.052024, 0, 0.51745, 0.819918, 0.0487865, 0, 0.535761, 0.815072, 0.0457801, 0, 0.554328, 0.809863, 0.0430184, 0, 0.573162, 0.804164, 0.0404245, 0, 0.592275, 0.798034, 0.0380146, 0, 0.611681, 0.791436, 0.0357436, 0, 0.631398, 0.784498, 0.0336475, 0, 0.651445, 0.777125, 0.0316666, 0, 0.671845, 0.769365, 0.0298122, 0, 0.692628, 0.761579, 0.0281001, 0, 0.713827, 0.753746, 0.0265049, 0, 0.735484, 0.745573, 0.0250067, 0, 0.75765, 0.737083, 0.0236026, 0, 0.78039, 0.728545, 0.0223302, 0, 0.803789, 0.719691, 0.0211243, 0, 0.82796, 0.710569, 0.0199983, 0, 0.853056, 0.701216, 0.0189569, 0, 0.879298, 0.692094, 0.0179702, 0, 0.907014, 0.682909, 0.0170418, 0, 0.936691, 0.673509, 0.0161732, 0, 0.968254, 0.663863, 0.0153406, 0, 1, 1, 0.437395, 0, 0, 0.999998, 0.437394, 0, 0, 0.99998, 0.437363, 0, 0.000616704, 0.999891, 0.437232, 0, 0.00367925, 0.999656, 0.436877, 0, 0.00867446, 0.999148, 0.436121, 0, 0.0150679, 0.997959, 0.434564, 0, 0.022531, 0.993464, 0.430134, 0, 0.0308507, 0.990606, 0.426077, 0, 0.0398805, 0.985027, 0.419397, 0, 0.0495148, 0.978491, 0.41118, 0, 0.0596749, 0.969643, 0.40048, 0, 0.0703001, 0.959189, 0.38769, 0, 0.0813427, 0.948223, 0.373575, 0, 0.0927641, 0.935955, 0.357622, 0, 0.104533, 0.923237, 0.34043, 0, 0.116624, 0.911074, 0.322735, 0, 0.129015, 0.899724, 0.30479, 0, 0.141687, 0.890189, 0.287392, 0, 0.154626, 0.881796, 0.270248, 0, 0.167818, 0.874781, 0.253659, 0, 0.181252, 0.869166, 0.237786, 0, 0.194918, 0.864725, 0.222618, 0, 0.208807, 0.861565, 0.208356, 0, 0.222913, 0.859284, 0.194867, 0, 0.237229, 0.857677, 0.18212, 0, 0.25175, 0.856714, 0.17018, 0, 0.266473, 0.856155, 0.158969, 0, 0.281392, 0.8558, 0.148413, 0, 0.296505, 0.855672, 0.138578, 0, 0.311811, 0.855538, 0.129345, 0, 0.327306, 0.855689, 0.120861, 0, 0.342991, 0.855767, 0.112969, 0, 0.358864, 0.855618, 0.105593, 0, 0.374925, 0.85525, 0.0987451, 0, 0.391176, 0.854583, 0.0923727, 0, 0.407616, 0.853534, 0.0864143, 0, 0.424249, 0.852061, 0.0808338, 0, 0.441076, 0.850253, 0.0756771, 0, 0.4581, 0.848004, 0.0708612, 0, 0.475324, 0.845333, 0.0663784, 0, 0.492754, 0.842376, 0.0622631, 0, 0.510394, 0.838956, 0.0584112, 0, 0.528251, 0.835121, 0.0548328, 0, 0.546331, 0.830842, 0.0514838, 0, 0.564644, 0.826212, 0.048355, 0, 0.583198, 0.821522, 0.0454714, 0, 0.602005, 0.816551, 0.0428263, 0, 0.621078, 0.811211, 0.0403612, 0, 0.640434, 0.805479, 0.038039, 0, 0.660089, 0.799409, 0.0358739, 0, 0.680066, 0.79306, 0.0338727, 0, 0.70039, 0.786395, 0.0319985, 0, 0.721094, 0.779416, 0.030241, 0, 0.742215, 0.77214, 0.0285951, 0, 0.7638, 0.764636, 0.0270747, 0, 0.785912, 0.756836, 0.0256354, 0, 0.808628, 0.749315, 0.0243027, 0, 0.832055, 0.741561, 0.0230497, 0, 0.856338, 0.733589, 0.0218801, 0, 0.88169, 0.725479, 0.020784, 0, 0.908441, 0.717255, 0.0197702, 0, 0.937125, 0.708829, 0.0188168, 0, 0.968254, 0.700191, 0.0179113, 0, 1, 1, 0.518937, 0, 0, 0.999998, 0.518933, 0, 0, 0.999967, 0.518883, 0, 0.00147741, 0.999832, 0.51866, 0, 0.00573221, 0.999466, 0.518057, 0, 0.011826, 0.998644, 0.516752, 0, 0.0192116, 0.994458, 0.512347, 0, 0.027573, 0.991223, 0.507675, 0, 0.0367099, 0.985515, 0.500188, 0, 0.046487, 0.978308, 0.490408, 0, 0.0568071, 0.968359, 0.477357, 0, 0.0675984, 0.95682, 0.461752, 0, 0.0788059, 0.943929, 0.443796, 0, 0.090386, 0.930224, 0.423893, 0, 0.102304, 0.916514, 0.402682, 0, 0.114532, 0.903653, 0.380914, 0, 0.127047, 0.892315, 0.359212, 0, 0.139828, 0.882942, 0.338102, 0, 0.152861, 0.875438, 0.31773, 0, 0.16613, 0.869642, 0.298186, 0, 0.179624, 0.865304, 0.279491, 0, 0.193332, 0.862382, 0.261804, 0, 0.207247, 0.860666, 0.245146, 0, 0.22136, 0.859788, 0.229406, 0, 0.235666, 0.859608, 0.214605, 0, 0.250158, 0.859912, 0.200691, 0, 0.264832, 0.86053, 0.187623, 0, 0.279684, 0.861368, 0.17539, 0, 0.294711, 0.862237, 0.163901, 0, 0.309911, 0.863127, 0.153175, 0, 0.32528, 0.863923, 0.143147, 0, 0.340819, 0.864567, 0.133781, 0, 0.356524, 0.865013, 0.125042, 0, 0.372397, 0.86539, 0.116952, 0, 0.388438, 0.865591, 0.109476, 0, 0.404645, 0.865517, 0.102542, 0, 0.421022, 0.865084, 0.0960688, 0, 0.437569, 0.864309, 0.0900499, 0, 0.454287, 0.863151, 0.0844328, 0, 0.471181, 0.861649, 0.0792218, 0, 0.488253, 0.859742, 0.0743482, 0, 0.505507, 0.857446, 0.0697963, 0, 0.522947, 0.854757, 0.0655364, 0, 0.54058, 0.851783, 0.061608, 0, 0.558412, 0.848516, 0.0579701, 0, 0.576449, 0.844897, 0.0545742, 0, 0.594701, 0.840956, 0.0514167, 0, 0.613178, 0.836676, 0.0484598, 0, 0.631892, 0.832075, 0.0456934, 0, 0.650856, 0.827191, 0.0431178, 0, 0.670088, 0.822295, 0.0407718, 0, 0.689606, 0.817294, 0.0386032, 0, 0.709434, 0.812013, 0.0365675, 0, 0.7296, 0.806465, 0.0346547, 0, 0.750138, 0.800691, 0.0328717, 0, 0.771093, 0.794709, 0.031211, 0, 0.792519, 0.788493, 0.0296504, 0, 0.814488, 0.782049, 0.0281782, 0, 0.837097, 0.775403, 0.0267965, 0, 0.860481, 0.76857, 0.0255002, 0, 0.884842, 0.761536, 0.0242759, 0, 0.910494, 0.754303, 0.0231142, 0, 0.937985, 0.74692, 0.0220305, 0, 0.968254, 0.739745, 0.0210192, 0, 1, 1, 0.613914, 0, 0, 0.999996, 0.613907, 0, 9.63597e-05, 0.999942, 0.613814, 0, 0.00301247, 0.999704, 0.613407, 0, 0.00870385, 0.999046, 0.612302, 0, 0.0160714, 0.995516, 0.608266, 0, 0.0245899, 0.991726, 0.602863, 0, 0.0339681, 0.985157, 0.593956, 0, 0.0440254, 0.97642, 0.581748, 0, 0.0546409, 0.964404, 0.565183, 0, 0.0657284, 0.950601, 0.545273, 0, 0.0772246, 0.935158, 0.522129, 0, 0.0890812, 0.919364, 0.496782, 0, 0.10126, 0.904754, 0.470571, 0, 0.113731, 0.89176, 0.444037, 0, 0.126469, 0.881492, 0.418322, 0, 0.139454, 0.873656, 0.393522, 0, 0.15267, 0.868053, 0.369795, 0, 0.166101, 0.864336, 0.347171, 0, 0.179736, 0.862259, 0.325737, 0, 0.193565, 0.861556, 0.305532, 0, 0.207578, 0.861776, 0.286416, 0, 0.221769, 0.862661, 0.268355, 0, 0.23613, 0.864015, 0.251334, 0, 0.250656, 0.865711, 0.235352, 0, 0.265343, 0.867519, 0.220302, 0, 0.280187, 0.869351, 0.206161, 0, 0.295183, 0.871144, 0.192908, 0, 0.31033, 0.872839, 0.180505, 0, 0.325624, 0.874307, 0.168848, 0, 0.341065, 0.875667, 0.158021, 0, 0.35665, 0.876758, 0.147877, 0, 0.37238, 0.87764, 0.138441, 0, 0.388253, 0.878237, 0.129627, 0, 0.404269, 0.878563, 0.121415, 0, 0.42043, 0.878572, 0.113741, 0, 0.436735, 0.87842, 0.106652, 0, 0.453187, 0.878057, 0.100097, 0, 0.469786, 0.877413, 0.0940128, 0, 0.486536, 0.87646, 0.0883462, 0, 0.503439, 0.875233, 0.0830924, 0, 0.520498, 0.8737, 0.0781975, 0, 0.537717, 0.871873, 0.07364, 0, 0.555102, 0.86978, 0.0694103, 0, 0.572657, 0.867405, 0.0654696, 0, 0.59039, 0.864751, 0.0617914, 0, 0.608307, 0.861818, 0.0583491, 0, 0.626419, 0.858645, 0.0551443, 0, 0.644733, 0.855307, 0.0521894, 0, 0.663264, 0.851736, 0.0494334, 0, 0.682025, 0.847927, 0.0468504, 0, 0.701032, 0.843888, 0.0444261, 0, 0.720308, 0.839629, 0.0421497, 0, 0.739875, 0.835158, 0.0400082, 0, 0.759764, 0.830509, 0.0380076, 0, 0.780014, 0.825714, 0.0361488, 0, 0.800673, 0.820729, 0.0343956, 0, 0.821803, 0.815751, 0.0327781, 0, 0.843492, 0.810752, 0.031275, 0, 0.86586, 0.805587, 0.0298542, 0, 0.889087, 0.800317, 0.0285397, 0, 0.913466, 0.79489, 0.0272948, 0, 0.93952, 0.789314, 0.0261139, 0, 0.96835, 0.783593, 0.0249938, 0, 1, 1, 0.724258, 0, 0, 0.999992, 0.724243, 0, 0.000726889, 0.99987, 0.724044, 0, 0.00569574, 0.999336, 0.72317, 0, 0.0131702, 0.996271, 0.719432, 0, 0.0220738, 0.991159, 0.712576, 0, 0.0319405, 0.982465, 0.700927, 0, 0.0425202, 0.97049, 0.684297, 0, 0.0536599, 0.953973, 0.661244, 0, 0.065258, 0.935546, 0.633804, 0, 0.0772427, 0.916596, 0.603071, 0, 0.0895616, 0.899353, 0.57105, 0, 0.102175, 0.885216, 0.539206, 0, 0.11505, 0.875076, 0.508714, 0, 0.128164, 0.868334, 0.479571, 0, 0.141495, 0.864414, 0.451796, 0, 0.155026, 0.862678, 0.425328, 0, 0.168745, 0.862835, 0.400352, 0, 0.182639, 0.864067, 0.376532, 0, 0.196699, 0.866086, 0.35391, 0, 0.210915, 0.868557, 0.332424, 0, 0.225282, 0.871271, 0.312053, 0, 0.239792, 0.874058, 0.292764, 0, 0.25444, 0.8768, 0.27453, 0, 0.269223, 0.87939, 0.257297, 0, 0.284135, 0.8819, 0.24114, 0, 0.299174, 0.884187, 0.225934, 0, 0.314337, 0.886262, 0.211669, 0, 0.329622, 0.888119, 0.198311, 0, 0.345026, 0.889709, 0.185783, 0, 0.360549, 0.891054, 0.174063, 0, 0.376189, 0.892196, 0.163143, 0, 0.391946, 0.893101, 0.152952, 0, 0.407819, 0.893803, 0.143475, 0, 0.423808, 0.894277, 0.134647, 0, 0.439914, 0.894532, 0.126434, 0, 0.456137, 0.894576, 0.1188, 0, 0.472479, 0.894393, 0.111694, 0, 0.48894, 0.893976, 0.105069, 0, 0.505523, 0.893346, 0.0989077, 0, 0.52223, 0.892502, 0.0931724, 0, 0.539064, 0.891441, 0.0878276, 0, 0.556028, 0.890276, 0.082903, 0, 0.573125, 0.888972, 0.0783505, 0, 0.590361, 0.887469, 0.0741083, 0, 0.607741, 0.885785, 0.0701633, 0, 0.62527, 0.883914, 0.0664835, 0, 0.642957, 0.881872, 0.0630567, 0, 0.660809, 0.879651, 0.0598527, 0, 0.678836, 0.877267, 0.0568615, 0, 0.69705, 0.874717, 0.05406, 0, 0.715465, 0.872012, 0.0514378, 0, 0.734098, 0.869157, 0.0489805, 0, 0.752968, 0.866155, 0.0466727, 0, 0.772101, 0.863014, 0.0445056, 0, 0.791529, 0.859748, 0.0424733, 0, 0.81129, 0.856416, 0.0405957, 0, 0.831438, 0.852958, 0.0388273, 0, 0.852044, 0.849382, 0.0371619, 0, 0.87321, 0.845694, 0.0355959, 0, 0.89509, 0.841893, 0.0341155, 0, 0.917932, 0.837981, 0.0327141, 0, 0.942204, 0.833963, 0.0313856, 0, 0.968981, 0.829847, 0.0301275, 0, 1, 1, 0.85214, 0, 0, 0.999969, 0.852095, 0, 0.00279627, 0.999483, 0.851408, 0, 0.0107635, 0.994545, 0.84579, 0, 0.0206454, 0.986188, 0.835231, 0, 0.0315756, 0.969847, 0.814687, 0, 0.0432021, 0.945951, 0.783735, 0, 0.0553396, 0.91917, 0.746074, 0, 0.0678766, 0.895488, 0.706938, 0, 0.0807395, 0.878232, 0.669534, 0, 0.0938767, 0.868252, 0.635168, 0, 0.10725, 0.863873, 0.603069, 0, 0.120832, 0.863369, 0.572514, 0, 0.134598, 0.86545, 0.543169, 0, 0.148533, 0.868803, 0.514578, 0, 0.16262, 0.872794, 0.486762, 0, 0.176849, 0.87702, 0.459811, 0, 0.19121, 0.881054, 0.433654, 0, 0.205694, 0.884974, 0.408574, 0, 0.220294, 0.888587, 0.384525, 0, 0.235005, 0.891877, 0.36156, 0, 0.24982, 0.894793, 0.339661, 0, 0.264737, 0.89743, 0.318913, 0, 0.279751, 0.899796, 0.299302, 0, 0.294859, 0.901943, 0.280843, 0, 0.310058, 0.903858, 0.263481, 0, 0.325346, 0.905574, 0.247197, 0, 0.340721, 0.907069, 0.231915, 0, 0.356181, 0.908379, 0.217614, 0, 0.371725, 0.90952, 0.20425, 0, 0.387353, 0.910483, 0.191758, 0, 0.403063, 0.91128, 0.180092, 0, 0.418854, 0.911936, 0.169222, 0, 0.434727, 0.912454, 0.159098, 0, 0.450682, 0.912835, 0.149668, 0, 0.466718, 0.913078, 0.140884, 0, 0.482837, 0.913192, 0.132709, 0, 0.499038, 0.913175, 0.125095, 0, 0.515324, 0.91304, 0.118012, 0, 0.531695, 0.912781, 0.111417, 0, 0.548153, 0.91241, 0.105281, 0, 0.5647, 0.911924, 0.0995691, 0, 0.581338, 0.911331, 0.0942531, 0, 0.59807, 0.910637, 0.0893076, 0, 0.6149, 0.90984, 0.0846998, 0, 0.63183, 0.908941, 0.0804044, 0, 0.648865, 0.907944, 0.0763984, 0, 0.666011, 0.906857, 0.0726638, 0, 0.683273, 0.90568, 0.0691783, 0, 0.700659, 0.904416, 0.0659222, 0, 0.718176, 0.903067, 0.0628782, 0, 0.735834, 0.901637, 0.0600307, 0, 0.753646, 0.900128, 0.0573647, 0, 0.771625, 0.898544, 0.0548668, 0, 0.78979, 0.89689, 0.052527, 0, 0.808162, 0.895165, 0.0503306, 0, 0.826771, 0.893371, 0.0482668, 0, 0.845654, 0.891572, 0.0463605, 0, 0.864863, 0.889763, 0.0445998, 0, 0.884472, 0.887894, 0.0429451, 0, 0.904592, 0.885967, 0.0413884, 0, 0.925407, 0.883984, 0.0399225, 0, 0.947271, 0.881945, 0.0385405, 0, 0.97105, 0.879854, 0.0372362, 0, 1, 0.999804, 0.995833, 0, 0, 0.938155, 0.933611, 0, 0.0158731, 0.864755, 0.854311, 0, 0.0317461, 0.888594, 0.865264, 0, 0.0476191, 0.905575, 0.863922, 0, 0.0634921, 0.915125, 0.850558, 0, 0.0793651, 0.920665, 0.829254, 0, 0.0952381, 0.924073, 0.802578, 0, 0.111111, 0.926304, 0.772211, 0, 0.126984, 0.927829, 0.739366, 0, 0.142857, 0.928924, 0.705033, 0, 0.15873, 0.92973, 0.670019, 0, 0.174603, 0.930339, 0.634993, 0, 0.190476, 0.930811, 0.600485, 0, 0.206349, 0.931191, 0.566897, 0, 0.222222, 0.93149, 0.534485, 0, 0.238095, 0.931737, 0.503429, 0, 0.253968, 0.931939, 0.473811, 0, 0.269841, 0.932108, 0.445668, 0, 0.285714, 0.93225, 0.418993, 0, 0.301587, 0.932371, 0.393762, 0, 0.31746, 0.932474, 0.369939, 0, 0.333333, 0.932562, 0.347479, 0, 0.349206, 0.932638, 0.326336, 0, 0.365079, 0.932703, 0.306462, 0, 0.380952, 0.93276, 0.287805, 0, 0.396825, 0.932809, 0.270313, 0, 0.412698, 0.932851, 0.253933, 0, 0.428571, 0.932887, 0.23861, 0, 0.444444, 0.932917, 0.224289, 0, 0.460317, 0.932943, 0.210917, 0, 0.47619, 0.932965, 0.19844, 0, 0.492063, 0.932982, 0.186807, 0, 0.507937, 0.932995, 0.175966, 0, 0.52381, 0.933005, 0.165869, 0, 0.539683, 0.933011, 0.156468, 0, 0.555556, 0.933013, 0.147719, 0, 0.571429, 0.933013, 0.139579, 0, 0.587302, 0.93301, 0.132007, 0, 0.603175, 0.933004, 0.124965, 0, 0.619048, 0.932994, 0.118416, 0, 0.634921, 0.932982, 0.112326, 0, 0.650794, 0.932968, 0.106663, 0, 0.666667, 0.93295, 0.101397, 0, 0.68254, 0.932931, 0.0964993, 0, 0.698413, 0.932908, 0.0919438, 0, 0.714286, 0.932883, 0.0877057, 0, 0.730159, 0.932856, 0.0837623, 0, 0.746032, 0.932827, 0.0800921, 0, 0.761905, 0.932796, 0.0766754, 0, 0.777778, 0.932762, 0.0734936, 0, 0.793651, 0.932727, 0.0705296, 0, 0.809524, 0.932689, 0.0677676, 0, 0.825397, 0.93265, 0.0651929, 0, 0.84127, 0.932609, 0.0627917, 0, 0.857143, 0.932565, 0.0605515, 0, 0.873016, 0.932521, 0.0584606, 0, 0.888889, 0.932474, 0.0565082, 0, 0.904762, 0.932427, 0.0546841, 0, 0.920635, 0.932377, 0.0529793, 0, 0.936508, 0.932326, 0.0513851, 0, 0.952381, 0.932274, 0.0498936, 0, 0.968254, 0.93222, 0.0484975, 0, 0.984127, 0.932164, 0.0471899, 0, 1 ]; + + // data textures + // TODO: figure out a way to compress the LTC BRDF data + + const ltc_float_1 = new Float32Array( LTC_MAT_1 ); + const ltc_float_2 = new Float32Array( LTC_MAT_2 ); + + const LTC_FLOAT_1 = new DataTexture( ltc_float_1, 64, 64, RGBAFormat, FloatType, UVMapping, ClampToEdgeWrapping, ClampToEdgeWrapping, LinearFilter, NearestFilter, 1 ); + const LTC_FLOAT_2 = new DataTexture( ltc_float_2, 64, 64, RGBAFormat, FloatType, UVMapping, ClampToEdgeWrapping, ClampToEdgeWrapping, LinearFilter, NearestFilter, 1 ); + + LTC_FLOAT_1.needsUpdate = true; + LTC_FLOAT_2.needsUpdate = true; + + const ltc_half_1 = new Uint16Array( LTC_MAT_1.length ); + + LTC_MAT_1.forEach( function ( x, index ) { + + ltc_half_1[ index ] = DataUtils.toHalfFloat( x ); + + } ); + + const ltc_half_2 = new Uint16Array( LTC_MAT_2.length ); + + LTC_MAT_2.forEach( function ( x, index ) { + + ltc_half_2[ index ] = DataUtils.toHalfFloat( x ); + + } ); + + const LTC_HALF_1 = new DataTexture( ltc_half_1, 64, 64, RGBAFormat, HalfFloatType, UVMapping, ClampToEdgeWrapping, ClampToEdgeWrapping, LinearFilter, NearestFilter, 1 ); + const LTC_HALF_2 = new DataTexture( ltc_half_2, 64, 64, RGBAFormat, HalfFloatType, UVMapping, ClampToEdgeWrapping, ClampToEdgeWrapping, LinearFilter, NearestFilter, 1 ); + + LTC_HALF_1.needsUpdate = true; + LTC_HALF_2.needsUpdate = true; + + this.LTC_HALF_1 = LTC_HALF_1; + this.LTC_HALF_2 = LTC_HALF_2; + + this.LTC_FLOAT_1 = LTC_FLOAT_1; + this.LTC_FLOAT_2 = LTC_FLOAT_2; + + return this; + + } + +} + +/** + * The first LTC FP16 data texture. + * + * @static + * @type {?DataTexture} + * @default null + */ +RectAreaLightTexturesLib.LTC_HALF_1 = null; + +/** + * The second LTC FP16 data texture. + * + * @static + * @type {?DataTexture} + * @default null + */ +RectAreaLightTexturesLib.LTC_HALF_2 = null; + +/** + * The first LTC FP32 data texture. + * + * @static + * @type {?DataTexture} + * @default null + */ +RectAreaLightTexturesLib.LTC_FLOAT_1 = null; + +/** + * The second LTC FP32 data texture. + * + * @static + * @type {?DataTexture} + * @default null + */ +RectAreaLightTexturesLib.LTC_FLOAT_2 = null; + +export { RectAreaLightTexturesLib }; diff --git a/examples/jsm/lights/RectAreaLightUniformsLib.js b/examples/jsm/lights/RectAreaLightUniformsLib.js index cf916b31ab251e..b9a6a51a6cf3b8 100644 --- a/examples/jsm/lights/RectAreaLightUniformsLib.js +++ b/examples/jsm/lights/RectAreaLightUniformsLib.js @@ -1,76 +1,37 @@ -import { - ClampToEdgeWrapping, - DataTexture, - DataUtils, - FloatType, - HalfFloatType, - LinearFilter, - NearestFilter, - RGBAFormat, - UVMapping, - UniformsLib -} from 'three'; +import { UniformsLib } from 'three'; +import { RectAreaLightTexturesLib } from './RectAreaLightTexturesLib.js'; /** - * Uniforms library for RectAreaLight shared webgl shaders + * This class is only relevant when using {@link RectAreaLight} with {@link WebGLRenderer}. * - * NOTE: This is a temporary location for the BRDF approximation texture data - * based off of Eric Heitz's work (see citation below). BRDF data for - * RectAreaLight is currently approximated using a precomputed texture - * of roughly 80kb in size. The hope is to find a better way to include - * the large texture data before including the full RectAreaLight implementation - * in the main build files. + * Before rect area lights can be used, the internal uniform library of the renderer must be + * enhanced with the following code. * - * TODO: figure out a way to compress the LTC BRDF data + * ```js + * RectAreaLightUniformsLib.init(); + * ``` + * + * @hideconstructor + * @three_import import { RectAreaLightUniformsLib } from 'three/addons/lights/RectAreaLightUniformsLib.js'; */ - -// Real-Time Polygonal-Light Shading with Linearly Transformed Cosines -// by Eric Heitz, Jonathan Dupuy, Stephen Hill and David Neubelt -// code: https://github.com/selfshadow/ltc_code/ - class RectAreaLightUniformsLib { + /** + * Inits the uniform library required when using rect area lights. + */ static init() { - // source: https://github.com/selfshadow/ltc_code/tree/master/fit/results/ltc.js - - const LTC_MAT_1 = [ 1, 0, 0, 2e-05, 1, 0, 0, 0.000503905, 1, 0, 0, 0.00201562, 1, 0, 0, 0.00453516, 1, 0, 0, 0.00806253, 1, 0, 0, 0.0125978, 1, 0, 0, 0.018141, 1, 0, 0, 0.0246924, 1, 0, 0, 0.0322525, 1, 0, 0, 0.0408213, 1, 0, 0, 0.0503999, 1, 0, 0, 0.0609894, 1, 0, 0, 0.0725906, 1, 0, 0, 0.0852058, 1, 0, 0, 0.0988363, 1, 0, 0, 0.113484, 1, 0, 0, 0.129153, 1, 0, 0, 0.145839, 1, 0, 0, 0.163548, 1, 0, 0, 0.182266, 1, 0, 0, 0.201942, 1, 0, 0, 0.222314, 1, 0, 0, 0.241906, 1, 0, 0, 0.262314, 1, 0, 0, 0.285754, 1, 0, 0, 0.310159, 1, 0, 0, 0.335426, 1, 0, 0, 0.361341, 1, 0, 0, 0.387445, 1, 0, 0, 0.412784, 1, 0, 0, 0.438197, 1, 0, 0, 0.466966, 1, 0, 0, 0.49559, 1, 0, 0, 0.523448, 1, 0, 0, 0.549938, 1, 0, 0, 0.57979, 1, 0, 0, 0.608746, 1, 0, 0, 0.636185, 1, 0, 0, 0.664748, 1, 0, 0, 0.69313, 1, 0, 0, 0.71966, 1, 0, 0, 0.747662, 1, 0, 0, 0.774023, 1, 0, 0, 0.799775, 1, 0, 0, 0.825274, 1, 0, 0, 0.849156, 1, 0, 0, 0.873248, 1, 0, 0, 0.89532, 1, 0, 0, 0.917565, 1, 0, 0, 0.937863, 1, 0, 0, 0.958139, 1, 0, 0, 0.976563, 1, 0, 0, 0.994658, 1, 0, 0, 1.0112, 1, 0, 0, 1.02712, 1, 0, 0, 1.04189, 1, 0, 0, 1.05568, 1, 0, 0, 1.06877, 1, 0, 0, 1.08058, 1, 0, 0, 1.09194, 1, 0, 0, 1.10191, 1, 0, 0, 1.11161, 1, 0, 0, 1.1199, 1, 0, 0, 1.12813, 0.999547, - 4.48815e-07, 0.0224417, 1.99902e-05, 0.999495, - 1.13079e-05, 0.0224406, 0.000503651, 0.999496, - 4.52317e-05, 0.0224406, 0.00201461, 0.999496, - 0.000101772, 0.0224406, 0.00453287, 0.999495, - 0.000180928, 0.0224406, 0.00805845, 0.999497, - 0.000282702, 0.0224406, 0.0125914, 0.999496, - 0.000407096, 0.0224406, 0.0181319, 0.999498, - 0.000554114, 0.0224406, 0.02468, 0.999499, - 0.000723768, 0.0224406, 0.0322363, 0.999495, - 0.000916058, 0.0224405, 0.0408009, 0.999499, - 0.00113101, 0.0224408, 0.050375, 0.999494, - 0.00136863, 0.0224405, 0.0609586, 0.999489, - 0.00162896, 0.0224401, 0.0725537, 0.999489, - 0.00191201, 0.0224414, 0.0851619, 0.999498, - 0.00221787, 0.0224413, 0.0987867, 0.999492, - 0.00254642, 0.0224409, 0.113426, 0.999507, - 0.00289779, 0.0224417, 0.129088, 0.999494, - 0.0032716, 0.0224386, 0.145767, 0.999546, - 0.0036673, 0.0224424, 0.163472, 0.999543, - 0.00408166, 0.0224387, 0.182182, 0.999499, - 0.00450056, 0.0224338, 0.201843, 0.999503, - 0.00483661, 0.0224203, 0.222198, 0.999546, - 0.00452928, 0.022315, 0.241714, 0.999508, - 0.00587403, 0.0224329, 0.262184, 0.999509, - 0.00638806, 0.0224271, 0.285609, 0.999501, - 0.00691028, 0.0224166, 0.309998, 0.999539, - 0.00741979, 0.0223989, 0.335262, 0.999454, - 0.00786282, 0.0223675, 0.361154, 0.999529, - 0.00811928, 0.0222828, 0.387224, 0.999503, - 0.00799941, 0.0221063, 0.41252, 0.999561, - 0.00952753, 0.0223057, 0.438006, 0.999557, - 0.0099134, 0.0222065, 0.466735, 0.999541, - 0.0100935, 0.0220402, 0.495332, 0.999562, - 0.00996821, 0.0218067, 0.523197, 0.999556, - 0.0105031, 0.0217096, 0.550223, 0.999561, - 0.0114191, 0.0217215, 0.579498, 0.999588, - 0.0111818, 0.0213357, 0.608416, 0.999633, - 0.0107725, 0.0208689, 0.635965, 0.999527, - 0.0121671, 0.0210149, 0.664476, 0.999508, - 0.0116005, 0.020431, 0.692786, 0.999568, - 0.0115604, 0.0199791, 0.719709, 0.999671, - 0.0121117, 0.0197415, 0.74737, 0.999688, - 0.0110769, 0.0188846, 0.773692, 0.99962, - 0.0122368, 0.0188452, 0.799534, 0.999823, - 0.0110325, 0.0178001, 0.825046, 0.999599, - 0.0114923, 0.0174221, 0.849075, 0.999619, - 0.0105923, 0.0164345, 0.872999, 0.999613, - 0.0105988, 0.0158227, 0.895371, 0.99964, - 0.00979861, 0.0148131, 0.917364, 0.99977, - 0.00967238, 0.0140721, 0.938002, 0.999726, - 0.00869175, 0.0129543, 0.957917, 0.99973, - 0.00866872, 0.0122329, 0.976557, 0.999773, - 0.00731956, 0.0108958, 0.994459, 0.999811, - 0.00756027, 0.0102715, 1.01118, 0.999862, - 0.00583732, 0.00878781, 1.02701, 0.999835, - 0.00631438, 0.00827529, 1.04186, 0.999871, - 0.00450785, 0.00674583, 1.05569, 0.999867, - 0.00486079, 0.00621041, 1.06861, 0.999939, - 0.00322072, 0.00478301, 1.08064, 0.999918, - 0.00318199, 0.00406395, 1.09181, 1.00003, - 0.00193348, 0.00280682, 1.10207, 0.999928, - 0.00153729, 0.00198741, 1.11152, 0.999933, - 0.000623666, 0.000917714, 1.12009, 1, - 1.02387e-06, 9.07581e-07, 1.12813, 0.997866, - 8.96716e-07, 0.0448334, 1.99584e-05, 0.997987, - 2.25945e-05, 0.0448389, 0.000502891, 0.997987, - 9.03781e-05, 0.0448388, 0.00201156, 0.997985, - 0.000203351, 0.0448388, 0.00452602, 0.997986, - 0.000361514, 0.0448388, 0.00804629, 0.997987, - 0.00056487, 0.0448389, 0.0125724, 0.997988, - 0.000813423, 0.0448389, 0.0181045, 0.997984, - 0.00110718, 0.0448387, 0.0246427, 0.997985, - 0.00144616, 0.0448388, 0.0321875, 0.997987, - 0.00183038, 0.044839, 0.0407392, 0.997983, - 0.00225987, 0.0448387, 0.0502986, 0.997991, - 0.00273467, 0.0448389, 0.0608667, 0.997984, - 0.00325481, 0.0448384, 0.0724444, 0.998002, - 0.00382043, 0.044839, 0.0850348, 0.997997, - 0.00443145, 0.0448396, 0.0986372, 0.998007, - 0.00508796, 0.0448397, 0.113255, 0.998008, - 0.00578985, 0.04484, 0.128891, 0.998003, - 0.00653683, 0.0448384, 0.145548, 0.997983, - 0.00732713, 0.0448358, 0.163221, 0.997985, - 0.00815454, 0.0448358, 0.181899, 0.998005, - 0.00898985, 0.0448286, 0.201533, 0.998026, - 0.00964404, 0.0447934, 0.221821, 0.998055, - 0.00922677, 0.044611, 0.241282, 0.99804, - 0.0117361, 0.0448245, 0.261791, 0.998048, - 0.0127628, 0.0448159, 0.285181, 0.998088, - 0.0138055, 0.0447996, 0.30954, 0.998058, - 0.0148206, 0.0447669, 0.334751, 0.998099, - 0.0156998, 0.044697, 0.36061, 0.998116, - 0.0161976, 0.0445122, 0.386603, 0.998195, - 0.015945, 0.0441711, 0.411844, 0.998168, - 0.0183947, 0.0444255, 0.43773, 0.998184, - 0.0197913, 0.0443809, 0.466009, 0.998251, - 0.0201426, 0.0440689, 0.494574, 0.998305, - 0.0198847, 0.0435632, 0.522405, 0.998273, - 0.0210577, 0.043414, 0.549967, 0.998254, - 0.0227901, 0.0433943, 0.578655, 0.998349, - 0.0223108, 0.0426529, 0.60758, 0.99843, - 0.0223088, 0.042, 0.635524, 0.998373, - 0.0241141, 0.0418987, 0.663621, 0.998425, - 0.0231446, 0.0408118, 0.691906, 0.998504, - 0.0233684, 0.0400565, 0.719339, 0.998443, - 0.0241652, 0.0394634, 0.74643, 0.99848, - 0.0228715, 0.0380002, 0.773086, 0.998569, - 0.023519, 0.0372322, 0.798988, 0.998619, - 0.0223108, 0.0356468, 0.824249, 0.998594, - 0.0223105, 0.034523, 0.848808, 0.998622, - 0.0213426, 0.0328887, 0.87227, 0.998669, - 0.0207912, 0.0314374, 0.895157, 0.998705, - 0.0198416, 0.0296925, 0.916769, 0.998786, - 0.0189168, 0.0279634, 0.937773, 0.998888, - 0.0178811, 0.0261597, 0.957431, 0.99906, - 0.0166845, 0.0242159, 0.976495, 0.999038, - 0.0155464, 0.0222638, 0.994169, 0.999237, - 0.0141349, 0.0201967, 1.01112, 0.999378, - 0.0129324, 0.0181744, 1.02692, 0.999433, - 0.0113192, 0.0159898, 1.04174, 0.999439, - 0.0101244, 0.0140385, 1.05559, 0.999614, - 0.00837456, 0.0117826, 1.06852, 0.999722, - 0.00721769, 0.00983745, 1.08069, 0.999817, - 0.00554067, 0.00769002, 1.09176, 0.99983, - 0.00426961, 0.005782, 1.10211, 0.999964, - 0.00273904, 0.00374503, 1.11152, 1.00001, - 0.00136739, 0.00187176, 1.12031, 0.999946, 3.93227e-05, - 2.8919e-05, 1.12804, 0.995847, - 1.3435e-06, 0.0671785, 1.9916e-05, 0.995464, - 3.38387e-05, 0.0671527, 0.000501622, 0.99547, - 0.000135355, 0.0671531, 0.00200649, 0.995471, - 0.00030455, 0.0671532, 0.00451461, 0.99547, - 0.000541423, 0.0671531, 0.008026, 0.995471, - 0.00084598, 0.0671531, 0.0125407, 0.99547, - 0.00121823, 0.0671531, 0.0180589, 0.99547, - 0.00165817, 0.0671531, 0.0245806, 0.995463, - 0.00216583, 0.0671526, 0.0321062, 0.995468, - 0.00274127, 0.0671527, 0.0406366, 0.995474, - 0.00338447, 0.0671534, 0.0501717, 0.995473, - 0.00409554, 0.0671533, 0.0607131, 0.995478, - 0.00487451, 0.0671531, 0.0722618, 0.995476, - 0.00572148, 0.0671532, 0.0848191, 0.995477, - 0.00663658, 0.0671539, 0.0983882, 0.995498, - 0.00761986, 0.0671541, 0.112972, 0.995509, - 0.00867094, 0.0671542, 0.128568, 0.995509, - 0.00978951, 0.0671531, 0.145183, 0.995503, - 0.0109725, 0.0671491, 0.162808, 0.995501, - 0.012211, 0.0671465, 0.181441, 0.99553, - 0.0134565, 0.0671371, 0.201015, 0.99555, - 0.014391, 0.0670831, 0.221206, 0.99558, - 0.014351, 0.0668883, 0.240813, 0.995577, - 0.0173997, 0.0671055, 0.261257, 0.995602, - 0.0191111, 0.0671178, 0.284467, 0.995623, - 0.0206705, 0.0670946, 0.308765, 0.995658, - 0.022184, 0.0670472, 0.333905, 0.995705, - 0.0234832, 0.0669417, 0.359677, 0.995719, - 0.0241933, 0.0666714, 0.385554, 0.995786, - 0.0243539, 0.066266, 0.410951, 0.995887, - 0.0271866, 0.0664367, 0.437163, 0.995944, - 0.0296012, 0.0664931, 0.464842, 0.996004, - 0.0301045, 0.0660105, 0.49332, 0.996128, - 0.0298311, 0.0652694, 0.521131, 0.996253, - 0.0316426, 0.0650739, 0.549167, 0.996244, - 0.0339043, 0.0649433, 0.57737, 0.996309, - 0.033329, 0.0638926, 0.606073, 0.996417, - 0.0338935, 0.0630849, 0.634527, 0.996372, - 0.0353104, 0.0625083, 0.66256, 0.996542, - 0.0348942, 0.0611986, 0.690516, 0.996568, - 0.0351614, 0.060069, 0.718317, 0.996711, - 0.0354317, 0.0588522, 0.74528, 0.996671, - 0.0349513, 0.0571902, 0.772061, 0.996865, - 0.0345622, 0.0555321, 0.798089, 0.996802, - 0.0342566, 0.0537816, 0.823178, 0.996992, - 0.0330862, 0.0516095, 0.847949, 0.996944, - 0.0324666, 0.0495537, 0.871431, 0.997146, - 0.0309544, 0.0470302, 0.894357, 0.997189, - 0.0299372, 0.0446043, 0.916142, 0.997471, - 0.0281389, 0.0418812, 0.937193, 0.997515, - 0.0268702, 0.0391823, 0.957, 0.997812, - 0.0247166, 0.0361338, 0.975936, 0.998027, - 0.0233525, 0.0333945, 0.99391, 0.998233, - 0.0209839, 0.0301917, 1.01075, 0.998481, - 0.0194309, 0.027271, 1.02669, 0.998859, - 0.0169728, 0.0240162, 1.04173, 0.99894, - 0.0152322, 0.0210517, 1.05551, 0.999132, - 0.0127497, 0.0178632, 1.06856, 0.999369, - 0.0108282, 0.014787, 1.08054, 0.999549, - 0.00845886, 0.0116185, 1.09185, 0.999805, - 0.0063937, 0.00867209, 1.10207, 0.99985, - 0.00414582, 0.00566823, 1.1117, 0.999912, - 0.00207443, 0.00277562, 1.12022, 1.00001, 8.70226e-05, - 5.3766e-05, 1.12832, 0.991943, - 1.78672e-06, 0.0893382, 1.98384e-05, 0.991952, - 4.50183e-05, 0.089339, 0.000499849, 0.991956, - 0.000180074, 0.0893394, 0.0019994, 0.991955, - 0.000405167, 0.0893393, 0.00449867, 0.991953, - 0.000720298, 0.0893391, 0.00799764, 0.991955, - 0.00112548, 0.0893393, 0.0124964, 0.991957, - 0.0016207, 0.0893395, 0.0179951, 0.991958, - 0.00220601, 0.0893396, 0.0244939, 0.991947, - 0.00288137, 0.0893385, 0.0319929, 0.991962, - 0.00364693, 0.0893399, 0.0404933, 0.991965, - 0.00450264, 0.0893399, 0.049995, 0.99198, - 0.00544862, 0.0893411, 0.0604995, 0.99197, - 0.00648491, 0.0893397, 0.0720074, 0.991976, - 0.00761164, 0.089341, 0.0845207, 0.99198, - 0.00882891, 0.0893405, 0.0980413, 0.991982, - 0.0101367, 0.0893396, 0.112571, 0.992008, - 0.011535, 0.0893415, 0.128115, 0.992026, - 0.0130228, 0.0893414, 0.144672, 0.992064, - 0.0145966, 0.0893418, 0.162241, 0.992041, - 0.0162421, 0.0893359, 0.180801, 0.992086, - 0.0178888, 0.0893214, 0.200302, 0.992157, - 0.0190368, 0.0892401, 0.220332, 0.992181, - 0.0195584, 0.0890525, 0.240144, 0.992175, - 0.0227257, 0.0892153, 0.260728, 0.99221, - 0.0254195, 0.089304, 0.283473, 0.99222, - 0.0274883, 0.0892703, 0.307673, 0.992317, - 0.0294905, 0.0892027, 0.332729, 0.992374, - 0.0311861, 0.0890577, 0.358387, 0.992505, - 0.0320656, 0.0886994, 0.384102, 0.992568, - 0.0329715, 0.0883198, 0.409767, 0.992675, - 0.036006, 0.0883602, 0.436145, 0.992746, - 0.0392897, 0.0884591, 0.463217, 0.992873, - 0.0399337, 0.0878287, 0.491557, 0.992934, - 0.040231, 0.0870108, 0.519516, 0.993091, - 0.0422013, 0.0865857, 0.547741, 0.993259, - 0.0443503, 0.0861937, 0.575792, 0.993455, - 0.0446368, 0.0851187, 0.604233, 0.993497, - 0.0454299, 0.0840576, 0.632925, 0.993694, - 0.0463296, 0.0829671, 0.660985, 0.993718, - 0.0470619, 0.0817185, 0.688714, 0.993973, - 0.0468838, 0.0800294, 0.716743, 0.994207, - 0.046705, 0.0781286, 0.74377, 0.994168, - 0.0469698, 0.0763337, 0.77042, 0.9945, - 0.0456816, 0.0738184, 0.796659, 0.994356, - 0.0455518, 0.0715545, 0.821868, 0.994747, - 0.0439488, 0.0686085, 0.846572, 0.994937, - 0.0430056, 0.065869, 0.870435, 0.995142, - 0.0413414, 0.0626446, 0.893272, 0.995451, - 0.0396521, 0.05929, 0.915376, 0.995445, - 0.0378453, 0.0558503, 0.936196, 0.995967, - 0.0355219, 0.0520949, 0.956376, 0.996094, - 0.0335146, 0.048377, 0.975327, 0.996622, - 0.030682, 0.0442575, 0.993471, 0.996938, - 0.0285504, 0.0404693, 1.01052, 0.997383, - 0.0253399, 0.0360903, 1.02637, 0.997714, - 0.0231651, 0.0322176, 1.04139, 0.998249, - 0.0198138, 0.0278433, 1.05542, 0.998596, - 0.0174337, 0.0238759, 1.06846, 0.998946, - 0.0141349, 0.0195944, 1.08056, 0.99928, - 0.0115603, 0.0156279, 1.09181, 0.999507, - 0.00839065, 0.0114607, 1.10213, 0.999697, - 0.005666, 0.00763325, 1.11169, 0.999869, - 0.00269902, 0.00364946, 1.12042, 1.00001, 6.23836e-05, - 3.19288e-05, 1.12832, 0.987221, - 2.22675e-06, 0.111332, 1.97456e-05, 0.98739, - 5.61116e-05, 0.111351, 0.000497563, 0.987448, - 0.000224453, 0.111357, 0.00199031, 0.987441, - 0.000505019, 0.111357, 0.0044782, 0.987442, - 0.000897816, 0.111357, 0.00796129, 0.987442, - 0.00140284, 0.111357, 0.0124396, 0.987444, - 0.00202012, 0.111357, 0.0179132, 0.987442, - 0.00274964, 0.111357, 0.0243824, 0.987446, - 0.00359147, 0.111357, 0.0318474, 0.987435, - 0.00454562, 0.111356, 0.0403086, 0.987461, - 0.00561225, 0.111358, 0.0497678, 0.987458, - 0.00679125, 0.111358, 0.0602239, 0.987443, - 0.0080828, 0.111356, 0.0716792, 0.987476, - 0.0094872, 0.111358, 0.0841364, 0.98749, - 0.0110044, 0.111361, 0.097597, 0.987508, - 0.0126344, 0.111362, 0.112062, 0.987494, - 0.0143767, 0.111357, 0.127533, 0.987526, - 0.0162307, 0.111359, 0.144015, 0.987558, - 0.0181912, 0.111361, 0.161502, 0.987602, - 0.0202393, 0.111355, 0.179979, 0.987692, - 0.022273, 0.111346, 0.199386, 0.987702, - 0.0235306, 0.111215, 0.219183, 0.987789, - 0.0247628, 0.111061, 0.239202, 0.987776, - 0.0280668, 0.111171, 0.259957, 0.987856, - 0.0316751, 0.111327, 0.282198, 0.987912, - 0.0342468, 0.111282, 0.306294, 0.988, - 0.0367205, 0.111198, 0.331219, 0.988055, - 0.0387766, 0.110994, 0.356708, 0.988241, - 0.0397722, 0.110547, 0.382234, 0.988399, - 0.0416076, 0.110198, 0.408227, 0.988539, - 0.0448192, 0.110137, 0.434662, 0.988661, - 0.0483793, 0.110143, 0.461442, 0.988967, - 0.0495895, 0.109453, 0.489318, 0.989073, - 0.0506797, 0.108628, 0.517516, 0.989274, - 0.0526953, 0.108003, 0.545844, 0.989528, - 0.054578, 0.107255, 0.573823, 0.989709, - 0.0561503, 0.106294, 0.601944, 0.989991, - 0.056866, 0.104896, 0.630855, 0.990392, - 0.0572914, 0.103336, 0.658925, 0.990374, - 0.0586224, 0.10189, 0.686661, 0.990747, - 0.0584764, 0.099783, 0.714548, 0.991041, - 0.0582662, 0.0974309, 0.74186, 0.991236, - 0.0584118, 0.0951678, 0.768422, 0.991585, - 0.0573055, 0.0921581, 0.794817, 0.991984, - 0.0564241, 0.0891167, 0.820336, 0.9921, - 0.0553608, 0.085805, 0.84493, 0.992749, - 0.0533816, 0.0820354, 0.868961, 0.99288, - 0.0518661, 0.0782181, 0.891931, 0.993511, - 0.0492492, 0.0738935, 0.914186, 0.993617, - 0.0471956, 0.0696402, 0.93532, 0.99411, - 0.044216, 0.0649659, 0.95543, 0.994595, - 0.0416654, 0.0603177, 0.974685, 0.994976, - 0.0384314, 0.0553493, 0.992807, 0.995579, - 0.0353491, 0.0503942, 1.00996, 0.996069, - 0.0319787, 0.0452123, 1.02606, 0.996718, - 0.028472, 0.0400112, 1.04114, 0.997173, - 0.0250789, 0.0349456, 1.05517, 0.997818, - 0.0213326, 0.029653, 1.0683, 0.998318, - 0.0178509, 0.024549, 1.0805, 0.998853, - 0.0141118, 0.0194197, 1.09177, 0.999218, - 0.0105914, 0.0143869, 1.1022, 0.999594, - 0.00693474, 0.00943517, 1.11175, 0.99975, - 0.00340478, 0.00464051, 1.12056, 1.00001, 0.000109172, - 0.000112821, 1.12853, 0.983383, - 2.66524e-06, 0.133358, 1.96534e-05, 0.981942, - 6.71009e-05, 0.133162, 0.000494804, 0.981946, - 0.000268405, 0.133163, 0.00197923, 0.981944, - 0.000603912, 0.133163, 0.00445326, 0.981941, - 0.00107362, 0.133162, 0.00791693, 0.981946, - 0.00167755, 0.133163, 0.0123703, 0.981944, - 0.00241569, 0.133162, 0.0178135, 0.981945, - 0.00328807, 0.133163, 0.0242466, 0.981945, - 0.00429472, 0.133162, 0.03167, 0.981955, - 0.00543573, 0.133164, 0.0400846, 0.981951, - 0.00671105, 0.133163, 0.0494901, 0.981968, - 0.00812092, 0.133165, 0.0598886, 0.981979, - 0.00966541, 0.133166, 0.0712811, 0.981996, - 0.0113446, 0.133168, 0.083669, 0.982014, - 0.0131585, 0.133169, 0.0970533, 0.982011, - 0.0151073, 0.133167, 0.111438, 0.982062, - 0.0171906, 0.133172, 0.126826, 0.9821, - 0.0194067, 0.133175, 0.143215, 0.982149, - 0.0217502, 0.133176, 0.160609, 0.982163, - 0.0241945, 0.133173, 0.178981, 0.982247, - 0.0265907, 0.133148, 0.198249, 0.982291, - 0.027916, 0.132974, 0.217795, 0.982396, - 0.0299663, 0.132868, 0.238042, 0.982456, - 0.0334544, 0.132934, 0.258901, 0.982499, - 0.0378636, 0.133137, 0.280639, 0.982617, - 0.0409274, 0.133085, 0.304604, 0.98274, - 0.0438523, 0.132985, 0.329376, 0.982944, - 0.0462288, 0.132728, 0.354697, 0.98308, - 0.0475995, 0.132228, 0.380102, 0.983391, - 0.0501901, 0.131924, 0.406256, 0.983514, - 0.0535899, 0.131737, 0.432735, 0.98373, - 0.0571858, 0.131567, 0.459359, 0.984056, - 0.0592353, 0.130932, 0.486637, 0.984234, - 0.0610488, 0.130092, 0.51509, 0.984748, - 0.0630758, 0.12923, 0.543461, 0.985073, - 0.0647398, 0.128174, 0.571376, 0.985195, - 0.0671941, 0.127133, 0.599414, 0.985734, - 0.0681345, 0.125576, 0.628134, 0.986241, - 0.0686089, 0.123639, 0.656399, 0.986356, - 0.0698511, 0.121834, 0.684258, 0.986894, - 0.0700931, 0.119454, 0.711818, 0.987382, - 0.0698321, 0.116718, 0.739511, 0.988109, - 0.0693975, 0.113699, 0.766267, 0.988363, - 0.0689584, 0.110454, 0.792456, 0.989112, - 0.0672353, 0.106602, 0.81813, 0.989241, - 0.0662034, 0.10267, 0.842889, 0.990333, - 0.0638938, 0.0981381, 0.867204, 0.990591, - 0.0618534, 0.0935388, 0.89038, 0.991106, - 0.0593117, 0.088553, 0.912576, 0.991919, - 0.0562676, 0.0832187, 0.934118, 0.992111, - 0.0534085, 0.0778302, 0.954254, 0.992997, - 0.0495459, 0.0720453, 0.973722, 0.993317, - 0.0463707, 0.0663458, 0.991949, 0.994133, - 0.0421245, 0.0601883, 1.00936, 0.994705, - 0.0384977, 0.0542501, 1.02559, 0.995495, - 0.0340956, 0.0479862, 1.04083, 0.996206, - 0.030105, 0.041887, 1.05497, 0.996971, - 0.0256095, 0.0355355, 1.06824, 0.997796, - 0.0213932, 0.0293655, 1.08056, 0.998272, - 0.0169612, 0.0232926, 1.09182, 0.998857, - 0.0126756, 0.0172786, 1.10219, 0.99939, - 0.00832486, 0.0113156, 1.11192, 0.999752, - 0.00410826, 0.00557892, 1.12075, 1, 0.000150957, - 0.000119101, 1.12885, 0.975169, - 3.09397e-06, 0.154669, 1.95073e-05, 0.975439, - 7.79608e-05, 0.154712, 0.000491534, 0.975464, - 0.000311847, 0.154716, 0.00196617, 0.975464, - 0.000701656, 0.154716, 0.00442387, 0.975462, - 0.0012474, 0.154715, 0.0078647, 0.975461, - 0.00194906, 0.154715, 0.0122886, 0.975464, - 0.00280667, 0.154715, 0.0176959, 0.975468, - 0.00382025, 0.154716, 0.0240867, 0.975471, - 0.00498985, 0.154716, 0.0314612, 0.975472, - 0.00631541, 0.154717, 0.0398199, 0.975486, - 0.00779719, 0.154718, 0.0491639, 0.975489, - 0.00943505, 0.154718, 0.0594932, 0.975509, - 0.0112295, 0.154721, 0.0708113, 0.97554, - 0.0131802, 0.154724, 0.0831176, 0.975557, - 0.0152876, 0.154726, 0.096415, 0.975585, - 0.0175512, 0.154728, 0.110705, 0.975605, - 0.0199713, 0.154729, 0.125992, 0.975645, - 0.0225447, 0.154729, 0.142272, 0.975711, - 0.0252649, 0.154735, 0.159549, 0.975788, - 0.0280986, 0.154736, 0.177805, 0.975872, - 0.0308232, 0.154704, 0.196911, 0.975968, - 0.0324841, 0.154525, 0.216324, 0.976063, - 0.0351281, 0.154432, 0.236628, 0.976157, - 0.0388618, 0.15446, 0.257539, 0.976204, - 0.0437704, 0.154665, 0.278975, 0.976358, - 0.047514, 0.154652, 0.302606, 0.976571, - 0.0508638, 0.154535, 0.327204, 0.976725, - 0.0534995, 0.154221, 0.352276, 0.977013, - 0.0555547, 0.153737, 0.377696, 0.977294, - 0.0586728, 0.153403, 0.403855, 0.977602, - 0.0622715, 0.15312, 0.430333, 0.977932, - 0.0658166, 0.152755, 0.456855, 0.978241, - 0.0689877, 0.152233, 0.483668, 0.978602, - 0.0712805, 0.15132, 0.512097, 0.979234, - 0.0732775, 0.150235, 0.540455, 0.97977, - 0.075163, 0.148978, 0.568486, 0.979995, - 0.0778026, 0.147755, 0.596524, 0.98078, - 0.0791854, 0.146019, 0.624825, 0.981628, - 0.0799666, 0.143906, 0.653403, 0.982067, - 0.0808532, 0.141561, 0.681445, 0.98271, - 0.0816024, 0.139025, 0.708918, 0.983734, - 0.0812511, 0.135764, 0.736594, 0.98431, - 0.0806201, 0.132152, 0.763576, 0.985071, - 0.0801605, 0.12846, 0.789797, 0.98618, - 0.0784208, 0.124084, 0.815804, 0.986886, - 0.0766643, 0.1193, 0.840869, 0.987485, - 0.0747744, 0.114236, 0.864952, 0.988431, - 0.0716701, 0.108654, 0.888431, 0.988886, - 0.0691609, 0.102994, 0.910963, 0.990024, - 0.0654048, 0.0967278, 0.932629, 0.990401, - 0.0619765, 0.090384, 0.95313, 0.991093, - 0.0579296, 0.0837885, 0.972587, 0.992018, - 0.0536576, 0.0770171, 0.991184, 0.992536, - 0.0493719, 0.0701486, 1.00863, 0.993421, - 0.0444813, 0.062953, 1.02494, 0.993928, - 0.040008, 0.0560455, 1.04017, 0.994994, - 0.0347982, 0.04856, 1.05463, 0.995866, - 0.0301017, 0.0416152, 1.06807, 0.996916, - 0.0248225, 0.0342597, 1.08039, 0.997766, - 0.0199229, 0.0271668, 1.09177, 0.998479, - 0.0147422, 0.0201387, 1.10235, 0.99921, - 0.00980173, 0.0131944, 1.11206, 0.999652, - 0.0047426, 0.00640712, 1.12104, 0.999998, 8.91673e-05, - 0.00010379, 1.12906, 0.967868, - 3.51885e-06, 0.175947, 1.93569e-05, 0.968001, - 8.86733e-05, 0.175972, 0.000487782, 0.96801, - 0.000354697, 0.175973, 0.00195115, 0.968012, - 0.000798063, 0.175974, 0.00439006, 0.968011, - 0.00141879, 0.175973, 0.00780461, 0.968011, - 0.00221686, 0.175973, 0.0121948, 0.968016, - 0.00319231, 0.175974, 0.0175607, 0.968019, - 0.00434515, 0.175974, 0.0239027, 0.968018, - 0.00567538, 0.175974, 0.0312208, 0.968033, - 0.00718308, 0.175977, 0.0395158, 0.968049, - 0.00886836, 0.175979, 0.0487885, 0.968047, - 0.0107312, 0.175978, 0.0590394, 0.968072, - 0.0127719, 0.175981, 0.0702705, 0.968108, - 0.0149905, 0.175986, 0.0824836, 0.968112, - 0.0173866, 0.175985, 0.0956783, 0.968173, - 0.0199611, 0.175993, 0.109862, 0.96827, - 0.0227128, 0.176008, 0.125033, 0.968292, - 0.025639, 0.17601, 0.141193, 0.968339, - 0.0287299, 0.176007, 0.158336, 0.968389, - 0.0319399, 0.176001, 0.176441, 0.968501, - 0.034941, 0.175962, 0.195359, 0.968646, - 0.0370812, 0.175793, 0.214686, 0.968789, - 0.0402329, 0.175708, 0.234973, 0.96886, - 0.0442601, 0.1757, 0.255871, 0.969013, - 0.049398, 0.175876, 0.277238, 0.969242, - 0.0539932, 0.17594, 0.300326, 0.969419, - 0.0577299, 0.175781, 0.324702, 0.969763, - 0.0605643, 0.175432, 0.349527, 0.970093, - 0.0634488, 0.174992, 0.374976, 0.970361, - 0.0670589, 0.174611, 0.401097, 0.970825, - 0.0708246, 0.174226, 0.427496, 0.971214, - 0.0742871, 0.173684, 0.453858, 0.971622, - 0.0782608, 0.173186, 0.480637, 0.972175, - 0.0813151, 0.172288, 0.508655, 0.972944, - 0.0832678, 0.170979, 0.536973, 0.973595, - 0.0855964, 0.169573, 0.565138, 0.974345, - 0.0882163, 0.168152, 0.593222, 0.975233, - 0.0901671, 0.166314, 0.621201, 0.976239, - 0.0912111, 0.163931, 0.649919, 0.977289, - 0.0916959, 0.161106, 0.678011, 0.978076, - 0.0927061, 0.158272, 0.705717, 0.979533, - 0.0925562, 0.15475, 0.733228, 0.980335, - 0.0918159, 0.150638, 0.760454, 0.981808, - 0.0908508, 0.146201, 0.786918, 0.983061, - 0.0896172, 0.141386, 0.812953, 0.984148, - 0.0871588, 0.135837, 0.838281, 0.985047, - 0.0850624, 0.130135, 0.862594, 0.986219, - 0.0818541, 0.123882, 0.88633, 0.987043, - 0.0784523, 0.117126, 0.908952, 0.988107, - 0.0749601, 0.110341, 0.930744, 0.988955, - 0.0703548, 0.102885, 0.951728, 0.989426, - 0.0662798, 0.0954167, 0.971166, 0.990421, - 0.0610834, 0.0876331, 0.989984, 0.991032, - 0.0562936, 0.0797785, 1.00765, 0.992041, - 0.0508154, 0.0718166, 1.02434, 0.992794, - 0.0454045, 0.0637125, 1.03976, 0.993691, - 0.0398194, 0.0555338, 1.05418, 0.994778, - 0.0341482, 0.0473388, 1.06772, 0.995915, - 0.028428, 0.0391016, 1.08028, 0.997109, - 0.022642, 0.0309953, 1.09185, 0.998095, - 0.0168738, 0.0230288, 1.10247, 0.998985, - 0.0111274, 0.0150722, 1.11229, 0.999581, - 0.00543881, 0.00740605, 1.12131, 1.00003, 0.000162239, - 0.000105549, 1.12946, 0.959505, - 3.93734e-06, 0.196876, 1.91893e-05, 0.959599, - 9.92157e-05, 0.196895, 0.000483544, 0.959641, - 0.000396868, 0.196903, 0.0019342, 0.959599, - 0.000892948, 0.196895, 0.00435193, 0.959603, - 0.00158747, 0.196896, 0.0077368, 0.959604, - 0.00248042, 0.196896, 0.0120888, 0.959605, - 0.00357184, 0.196896, 0.0174082, 0.959605, - 0.00486169, 0.196896, 0.0236949, 0.959613, - 0.00635008, 0.196897, 0.0309497, 0.959619, - 0.00803696, 0.196898, 0.0391725, 0.959636, - 0.00992255, 0.196901, 0.0483649, 0.959634, - 0.0120067, 0.1969, 0.0585266, 0.959675, - 0.0142898, 0.196906, 0.0696609, 0.959712, - 0.0167717, 0.196911, 0.0817678, 0.959752, - 0.0194524, 0.196918, 0.0948494, 0.959807, - 0.0223321, 0.196925, 0.10891, 0.959828, - 0.0254091, 0.196924, 0.123947, 0.959906, - 0.0286815, 0.196934, 0.139968, 0.960005, - 0.0321371, 0.196944, 0.156968, 0.960071, - 0.0357114, 0.196936, 0.17491, 0.960237, - 0.0389064, 0.196882, 0.193597, 0.960367, - 0.041623, 0.196731, 0.21285, 0.960562, - 0.0452655, 0.196654, 0.233075, 0.960735, - 0.0496207, 0.196643, 0.253941, 0.960913, - 0.0549379, 0.196774, 0.275278, 0.961121, - 0.0603414, 0.196893, 0.297733, 0.96139, - 0.0644244, 0.196717, 0.321877, 0.961818, - 0.067556, 0.196314, 0.346476, 0.962175, - 0.0712709, 0.195917, 0.371907, 0.96255, - 0.0752848, 0.1955, 0.397916, 0.963164, - 0.0792073, 0.195026, 0.424229, 0.963782, - 0.0828225, 0.194424, 0.450637, 0.964306, - 0.0873119, 0.193831, 0.477288, 0.964923, - 0.0911051, 0.192973, 0.504716, 0.966048, - 0.093251, 0.19151, 0.533053, 0.967024, - 0.0958983, 0.190013, 0.561366, 0.968038, - 0.09835, 0.188253, 0.589464, 0.969152, - 0.100754, 0.186257, 0.617433, 0.970557, - 0.102239, 0.183775, 0.645801, 0.972104, - 0.102767, 0.180645, 0.674278, 0.973203, - 0.103492, 0.177242, 0.702004, 0.975123, - 0.103793, 0.17345, 0.729529, 0.97641, - 0.102839, 0.168886, 0.756712, 0.978313, - 0.101687, 0.163892, 0.783801, 0.980036, - 0.100314, 0.158439, 0.809671, 0.981339, - 0.097836, 0.152211, 0.835402, 0.982794, - 0.0950006, 0.145679, 0.860081, 0.984123, - 0.0920994, 0.138949, 0.883757, 0.984918, - 0.0878641, 0.131283, 0.90685, 0.985999, - 0.083939, 0.123464, 0.928786, 0.987151, - 0.0791234, 0.115324, 0.94983, 0.987827, - 0.0739332, 0.106854, 0.96962, 0.988806, - 0.0688088, 0.0982691, 0.98861, 0.989588, - 0.0628962, 0.0893456, 1.00667, 0.990438, - 0.0573146, 0.0805392, 1.02344, 0.991506, - 0.0509433, 0.0713725, 1.03933, 0.992492, - 0.0448724, 0.0623732, 1.05378, 0.993663, - 0.0383497, 0.0530838, 1.06747, 0.994956, - 0.0319593, 0.0439512, 1.08007, 0.99634, - 0.025401, 0.0347803, 1.09182, 0.99761, - 0.0189687, 0.0257954, 1.1025, 0.99863, - 0.0124441, 0.0169893, 1.11247, 0.99947, - 0.00614003, 0.00829498, 1.12151, 1.00008, 0.000216624, - 0.000146107, 1.12993, 0.950129, - 4.34955e-06, 0.217413, 1.90081e-05, 0.950264, - 0.00010957, 0.217444, 0.00047884, 0.9503, - 0.000438299, 0.217451, 0.00191543, 0.950246, - 0.000986124, 0.21744, 0.00430951, 0.950246, - 0.00175311, 0.21744, 0.00766137, 0.950245, - 0.00273923, 0.21744, 0.011971, 0.950253, - 0.00394453, 0.217441, 0.0172385, 0.950258, - 0.00536897, 0.217442, 0.0234641, 0.950267, - 0.00701262, 0.217444, 0.030648, 0.950277, - 0.00887551, 0.217446, 0.038791, 0.950284, - 0.0109576, 0.217446, 0.0478931, 0.950312, - 0.0132591, 0.217451, 0.0579568, 0.950334, - 0.01578, 0.217454, 0.0689821, 0.950378, - 0.0185204, 0.217462, 0.0809714, 0.950417, - 0.0214803, 0.217467, 0.0939265, 0.950488, - 0.0246594, 0.217479, 0.10785, 0.950534, - 0.0280565, 0.217483, 0.122743, 0.950633, - 0.0316685, 0.217498, 0.138611, 0.950698, - 0.0354787, 0.217499, 0.155442, 0.950844, - 0.0394003, 0.217507, 0.173208, 0.950999, - 0.0426812, 0.217419, 0.191605, 0.951221, - 0.0461302, 0.217317, 0.21084, 0.951412, - 0.0502131, 0.217238, 0.230945, 0.951623, - 0.0549183, 0.21722, 0.251745, 0.951867, - 0.0604493, 0.217306, 0.273001, 0.952069, - 0.0665189, 0.217466, 0.294874, 0.952459, - 0.0709179, 0.217266, 0.318732, 0.952996, - 0.0746112, 0.216891, 0.34318, 0.953425, - 0.0789252, 0.216503, 0.36849, 0.953885, - 0.0833293, 0.216042, 0.394373, 0.954617, - 0.087371, 0.215469, 0.420505, 0.955429, - 0.0914054, 0.214802, 0.446907, 0.956068, - 0.0961671, 0.214146, 0.473522, 0.957094, - 0.10048, 0.213286, 0.50052, 0.958372, - 0.103248, 0.211796, 0.528715, 0.959654, - 0.106033, 0.21016, 0.557065, 0.961305, - 0.108384, 0.208149, 0.585286, 0.962785, - 0.111122, 0.206024, 0.613334, 0.964848, - 0.112981, 0.203442, 0.641334, 0.966498, - 0.113717, 0.19996, 0.669955, 0.968678, - 0.114121, 0.196105, 0.698094, 0.970489, - 0.114524, 0.191906, 0.725643, 0.972903, - 0.113792, 0.186963, 0.752856, 0.974701, - 0.112406, 0.181343, 0.780013, 0.976718, - 0.110685, 0.175185, 0.806268, 0.978905, - 0.108468, 0.168535, 0.832073, 0.980267, - 0.105061, 0.161106, 0.857149, 0.981967, - 0.101675, 0.153387, 0.881145, 0.983063, - 0.0974492, 0.145199, 0.904255, 0.984432, - 0.0925815, 0.136527, 0.926686, 0.985734, - 0.0877983, 0.127584, 0.947901, 0.986228, - 0.081884, 0.118125, 0.968111, 0.98719, - 0.0761208, 0.108594, 0.98719, 0.988228, - 0.0698196, 0.0989996, 1.00559, 0.989046, - 0.0632739, 0.0890074, 1.02246, 0.990242, - 0.056522, 0.0790832, 1.03841, 0.991252, - 0.0495272, 0.0689182, 1.05347, 0.992542, - 0.0425373, 0.0588592, 1.06724, 0.994096, - 0.0353198, 0.0486833, 1.08009, 0.995593, - 0.028235, 0.0385977, 1.09177, 0.99711, - 0.0209511, 0.0286457, 1.10274, 0.998263, - 0.0139289, 0.0188497, 1.11262, 0.999254, - 0.0067359, 0.009208, 1.12191, 0.999967, 0.000141846, - 6.57764e-05, 1.13024, 0.935608, - 4.74692e-06, 0.236466, 1.87817e-05, 0.93996, - 0.00011971, 0.237568, 0.000473646, 0.939959, - 0.000478845, 0.237567, 0.0018946, 0.939954, - 0.0010774, 0.237566, 0.00426284, 0.939956, - 0.00191538, 0.237566, 0.00757842, 0.939954, - 0.00299277, 0.237566, 0.0118413, 0.93996, - 0.00430961, 0.237567, 0.0170518, 0.939969, - 0.00586589, 0.237569, 0.02321, 0.939982, - 0.00766166, 0.237572, 0.0303164, 0.939987, - 0.00969686, 0.237572, 0.0383711, 0.939997, - 0.0119715, 0.237574, 0.0473751, 0.940031, - 0.0144858, 0.237581, 0.0573298, 0.940073, - 0.0172399, 0.237589, 0.0682366, 0.94012, - 0.0202335, 0.237598, 0.080097, 0.940162, - 0.0234663, 0.237604, 0.0929116, 0.940237, - 0.0269387, 0.237615, 0.106686, 0.940328, - 0.0306489, 0.237632, 0.121421, 0.940419, - 0.0345917, 0.237645, 0.137115, 0.940522, - 0.0387481, 0.237654, 0.153766, 0.940702, - 0.0429906, 0.237661, 0.17133, 0.940871, - 0.0465089, 0.237561, 0.189502, 0.941103, - 0.050531, 0.23748, 0.208616, 0.941369, - 0.0550657, 0.237423, 0.228595, 0.941641, - 0.0601337, 0.237399, 0.249287, 0.941903, - 0.0658804, 0.237443, 0.270467, 0.942224, - 0.0722674, 0.237597, 0.292024, 0.942633, - 0.0771788, 0.237419, 0.315272, 0.943172, - 0.0815623, 0.237068, 0.339579, 0.943691, - 0.0863973, 0.236682, 0.364717, 0.944382, - 0.0911536, 0.236213, 0.390435, 0.945392, - 0.0952967, 0.235562, 0.416425, 0.946185, - 0.0998948, 0.234832, 0.442772, 0.947212, - 0.104796, 0.234114, 0.469347, 0.948778, - 0.10928, 0.233222, 0.496162, 0.950149, - 0.113081, 0.231845, 0.523978, 0.951989, - 0.115893, 0.230005, 0.552295, 0.953921, - 0.11846, 0.227862, 0.580569, 0.955624, - 0.12115, 0.225439, 0.608698, 0.958234, - 0.123373, 0.222635, 0.636696, 0.960593, - 0.124519, 0.219093, 0.665208, 0.963201, - 0.124736, 0.214749, 0.693557, 0.965642, - 0.125012, 0.210059, 0.721334, 0.968765, - 0.124661, 0.204935, 0.748613, 0.971753, - 0.122996, 0.198661, 0.776224, 0.973751, - 0.120998, 0.191823, 0.802461, 0.976709, - 0.118583, 0.184359, 0.828399, 0.977956, - 0.115102, 0.176437, 0.853693, 0.979672, - 0.111077, 0.167681, 0.877962, 0.981816, - 0.10688, 0.158872, 0.901564, 0.98238, - 0.101469, 0.149398, 0.924057, 0.983964, - 0.0960013, 0.139436, 0.945751, 0.984933, - 0.0899626, 0.12943, 0.966272, 0.985694, - 0.0832973, 0.11894, 0.985741, 0.986822, - 0.0767082, 0.108349, 1.00407, 0.987725, - 0.0693614, 0.0976026, 1.02154, 0.98877, - 0.06211, 0.086652, 1.03757, 0.990129, - 0.0544143, 0.0756182, 1.05296, 0.991337, - 0.046744, 0.0645753, 1.06683, 0.992978, - 0.0387931, 0.0534683, 1.0798, 0.994676, - 0.030973, 0.0424137, 1.09181, 0.99645, - 0.0230311, 0.0314035, 1.10286, 0.997967, - 0.0152065, 0.0206869, 1.11291, 0.99922, - 0.00744837, 0.010155, 1.12237, 1.00002, 0.000240209, - 7.52767e-05, 1.13089, 0.922948, - 5.15351e-06, 0.255626, 1.86069e-05, 0.928785, - 0.000129623, 0.257244, 0.000468009, 0.928761, - 0.00051849, 0.257237, 0.00187202, 0.928751, - 0.0011666, 0.257235, 0.00421204, 0.928751, - 0.00207395, 0.257234, 0.0074881, 0.928754, - 0.00324055, 0.257235, 0.0117002, 0.92876, - 0.00466639, 0.257236, 0.0168486, 0.928763, - 0.00635149, 0.257237, 0.0229334, 0.928774, - 0.00829584, 0.257239, 0.029955, 0.928791, - 0.0104995, 0.257243, 0.0379139, 0.928804, - 0.0129623, 0.257245, 0.0468108, 0.928847, - 0.0156846, 0.257255, 0.0566473, 0.92889, - 0.0186661, 0.257263, 0.0674246, 0.928924, - 0.0219067, 0.257268, 0.0791433, 0.928989, - 0.0254066, 0.257282, 0.0918076, 0.92909, - 0.0291651, 0.257301, 0.105419, 0.92918, - 0.0331801, 0.257316, 0.119978, 0.92929, - 0.0374469, 0.257332, 0.135491, 0.929453, - 0.041939, 0.257357, 0.151948, 0.929586, - 0.0464612, 0.257347, 0.169275, 0.929858, - 0.0503426, 0.257269, 0.187257, 0.930125, - 0.0548409, 0.257199, 0.206204, 0.930403, - 0.0598063, 0.257149, 0.22601, 0.930726, - 0.0652437, 0.257122, 0.246561, 0.931098, - 0.0712376, 0.257153, 0.267618, 0.931396, - 0.0777506, 0.257237, 0.288993, 0.931947, - 0.0832374, 0.257124, 0.311527, 0.932579, - 0.0883955, 0.25683, 0.335697, 0.933194, - 0.0937037, 0.256444, 0.360634, 0.934013, - 0.0987292, 0.255939, 0.386126, 0.935307, - 0.103215, 0.255282, 0.412018, 0.936374, - 0.108234, 0.254538, 0.438292, 0.93776, - 0.113234, 0.253728, 0.464805, 0.939599, - 0.118013, 0.25275, 0.491464, 0.941036, - 0.122661, 0.251404, 0.518751, 0.94337, - 0.125477, 0.249435, 0.547133, 0.945318, - 0.128374, 0.247113, 0.575456, 0.947995, - 0.130996, 0.244441, 0.60372, 0.950818, - 0.133438, 0.241352, 0.63174, 0.954378, - 0.135004, 0.237849, 0.659971, 0.957151, - 0.135313, 0.233188, 0.688478, 0.960743, - 0.13521, 0.228001, 0.716767, 0.964352, - 0.135007, 0.222249, 0.744349, 0.967273, - 0.133523, 0.21542, 0.771786, 0.969767, - 0.131155, 0.208039, 0.798639, 0.973195, - 0.128492, 0.200076, 0.824774, 0.975557, - 0.125094, 0.191451, 0.850222, 0.977692, - 0.120578, 0.18184, 0.874761, 0.98026, - 0.115882, 0.172102, 0.898497, 0.981394, - 0.110372, 0.161859, 0.921636, 0.982386, - 0.10415, 0.15108, 0.943467, 0.983783, - 0.0978128, 0.140407, 0.964045, 0.98422, - 0.0906171, 0.129058, 0.98398, 0.985447, - 0.0832921, 0.117614, 1.00276, 0.986682, - 0.0754412, 0.10585, 1.02047, 0.987326, - 0.0673885, 0.0940943, 1.03678, 0.988707, - 0.0592565, 0.0822093, 1.05218, 0.990185, - 0.050717, 0.070192, 1.06652, 0.991866, - 0.0423486, 0.0582081, 1.07965, 0.993897, - 0.0336118, 0.0460985, 1.09188, 0.995841, - 0.0252178, 0.0342737, 1.10307, 0.997605, - 0.0164893, 0.0224829, 1.11324, 0.999037, - 0.00817112, 0.0110647, 1.12262, 1.00003, 0.000291686, - 0.000168673, 1.13139, 0.915304, - 5.52675e-06, 0.275999, 1.83285e-05, 0.91668, - 0.000139285, 0.276414, 0.000461914, 0.916664, - 0.00055713, 0.276409, 0.00184763, 0.916653, - 0.00125354, 0.276406, 0.00415715, 0.916651, - 0.00222851, 0.276405, 0.00739053, 0.916655, - 0.00348205, 0.276406, 0.0115478, 0.916653, - 0.00501414, 0.276405, 0.0166291, 0.916667, - 0.00682478, 0.276409, 0.0226346, 0.91668, - 0.00891398, 0.276412, 0.0295648, 0.91669, - 0.0112817, 0.276413, 0.0374199, 0.916727, - 0.013928, 0.276422, 0.0462016, 0.916759, - 0.0168528, 0.276429, 0.0559101, 0.916793, - 0.0200558, 0.276436, 0.0665466, 0.916849, - 0.0235373, 0.276448, 0.0781139, 0.916964, - 0.0272973, 0.276474, 0.0906156, 0.917047, - 0.0313344, 0.276491, 0.104051, 0.917152, - 0.0356465, 0.276511, 0.118424, 0.917286, - 0.0402271, 0.276533, 0.133736, 0.917469, - 0.0450408, 0.276564, 0.149978, 0.917686, - 0.0497872, 0.276563, 0.167057, 0.917953, - 0.0540937, 0.276493, 0.184846, 0.918228, - 0.0590709, 0.276437, 0.203614, 0.918572, - 0.0644277, 0.276398, 0.223212, 0.918918, - 0.0702326, 0.276362, 0.243584, 0.919356, - 0.076484, 0.276383, 0.264465, 0.919842, - 0.0830808, 0.276434, 0.285701, 0.920451, - 0.0892972, 0.276407, 0.307559, 0.921113, - 0.095016, 0.276128, 0.331501, 0.921881, - 0.100771, 0.275754, 0.356207, 0.923027, - 0.106029, 0.275254, 0.381477, 0.924364, - 0.111029, 0.274595, 0.40722, 0.925818, - 0.116345, 0.273841, 0.433385, 0.92746, - 0.121424, 0.272913, 0.459848, 0.929167, - 0.12657, 0.271837, 0.486493, 0.931426, - 0.131581, 0.270575, 0.513432, 0.934001, - 0.135038, 0.268512, 0.541502, 0.936296, - 0.138039, 0.266135, 0.569658, 0.939985, - 0.140687, 0.263271, 0.598375, 0.943516, - 0.143247, 0.260058, 0.626563, 0.94782, - 0.145135, 0.256138, 0.654711, 0.951023, - 0.145733, 0.251154, 0.683285, 0.955338, - 0.145554, 0.245562, 0.711831, 0.959629, - 0.145008, 0.239265, 0.739573, 0.963123, - 0.144003, 0.232064, 0.767027, 0.966742, - 0.141289, 0.224036, 0.794359, 0.969991, - 0.138247, 0.215305, 0.820361, 0.973403, - 0.134786, 0.206051, 0.846548, 0.975317, - 0.129966, 0.195914, 0.871541, 0.977647, - 0.12471, 0.185184, 0.895313, 0.980137, - 0.119086, 0.174161, 0.918398, 0.981031, - 0.112297, 0.162792, 0.940679, 0.982037, - 0.105372, 0.150952, 0.961991, 0.983164, - 0.097821, 0.138921, 0.981913, 0.983757, - 0.0897245, 0.126611, 1.00109, 0.985036, - 0.0815974, 0.114228, 1.01902, 0.986289, - 0.0727725, 0.101389, 1.03604, 0.987329, - 0.0639323, 0.0886476, 1.05149, 0.989193, - 0.0548109, 0.0756837, 1.06619, 0.990716, - 0.045687, 0.0627581, 1.07948, 0.992769, - 0.0364315, 0.0498337, 1.09172, 0.99524, - 0.0271761, 0.0370305, 1.1033, 0.997154, - 0.0179609, 0.0243959, 1.11353, 0.998845, - 0.00878063, 0.0119567, 1.12319, 1.00002, 0.000259038, - 0.000108146, 1.13177, 0.903945, - 5.91681e-06, 0.295126, 1.81226e-05, 0.903668, - 0.000148672, 0.295037, 0.000455367, 0.903677, - 0.000594683, 0.29504, 0.00182145, 0.903673, - 0.00133805, 0.295039, 0.00409831, 0.903666, - 0.00237872, 0.295036, 0.00728584, 0.903668, - 0.00371676, 0.295037, 0.0113842, 0.903679, - 0.00535212, 0.29504, 0.0163936, 0.903684, - 0.00728479, 0.295041, 0.0223141, 0.903698, - 0.00951473, 0.295044, 0.0291462, 0.903718, - 0.0120419, 0.295049, 0.0368904, 0.903754, - 0.0148664, 0.295058, 0.0455477, 0.903801, - 0.017988, 0.29507, 0.0551194, 0.903851, - 0.0214064, 0.295082, 0.0656058, 0.903921, - 0.0251219, 0.295097, 0.0770109, 0.904002, - 0.0291337, 0.295116, 0.0893354, 0.904111, - 0.033441, 0.29514, 0.102583, 0.904246, - 0.0380415, 0.295169, 0.116755, 0.904408, - 0.0429258, 0.295202, 0.131853, 0.904637, - 0.0480468, 0.295245, 0.147869, 0.904821, - 0.0529208, 0.295214, 0.164658, 0.905163, - 0.0577748, 0.295185, 0.182274, 0.905469, - 0.0631763, 0.295143, 0.200828, 0.905851, - 0.068917, 0.295112, 0.2202, 0.906322, - 0.0750861, 0.295104, 0.240372, 0.906761, - 0.0815855, 0.295086, 0.261082, 0.90735, - 0.0882138, 0.295095, 0.282123, 0.908087, - 0.095082, 0.295139, 0.303563, 0.908826, - 0.101488, 0.29492, 0.327028, 0.909832, - 0.107577, 0.294577, 0.351464, 0.911393, - 0.113033, 0.294115, 0.376497, 0.912804, - 0.118629, 0.293446, 0.402115, 0.914081, - 0.124232, 0.292581, 0.428111, 0.91637, - 0.129399, 0.29166, 0.454442, 0.91814, - 0.134892, 0.290422, 0.481024, 0.921179, - 0.140069, 0.289194, 0.507924, 0.924544, - 0.144431, 0.287421, 0.535557, 0.927995, - 0.147498, 0.284867, 0.563984, 0.931556, - 0.150197, 0.281722, 0.5923, 0.935777, - 0.152711, 0.278207, 0.620832, 0.940869, - 0.154836, 0.274148, 0.649069, 0.945994, - 0.155912, 0.269057, 0.677746, 0.949634, - 0.155641, 0.262799, 0.706293, 0.955032, - 0.154809, 0.256097, 0.734278, 0.95917, - 0.153678, 0.248618, 0.761751, 0.962931, - 0.151253, 0.239794, 0.789032, 0.966045, - 0.147625, 0.230281, 0.815422, 0.96971, - 0.143964, 0.220382, 0.841787, 0.972747, - 0.139464, 0.209846, 0.867446, 0.975545, - 0.133459, 0.198189, 0.892004, 0.978381, - 0.127424, 0.186362, 0.915458, 0.979935, - 0.120506, 0.173964, 0.937948, 0.980948, - 0.11282, 0.161429, 0.959732, 0.982234, - 0.104941, 0.148557, 0.980118, 0.982767, - 0.0962905, 0.135508, 0.999463, 0.983544, - 0.0873625, 0.122338, 1.01756, 0.984965, - 0.0783447, 0.108669, 1.03492, 0.986233, - 0.0684798, 0.0949911, 1.05087, 0.987796, - 0.0590867, 0.0811386, 1.0656, 0.989885, - 0.0489145, 0.0673099, 1.0794, 0.991821, - 0.0391, 0.0535665, 1.09174, 0.99448, - 0.029087, 0.0397529, 1.10341, 0.996769, - 0.019114, 0.0261463, 1.11383, 0.998641, - 0.00947007, 0.0128731, 1.1237, 0.999978, 0.000446316, - 0.000169093, 1.13253, 0.888362, - 6.27064e-06, 0.312578, 1.78215e-05, 0.889988, - 0.000157791, 0.313148, 0.000448451, 0.889825, - 0.000631076, 0.313092, 0.00179356, 0.88984, - 0.00141994, 0.313097, 0.00403554, 0.889828, - 0.0025243, 0.313092, 0.00717429, 0.889831, - 0.00394421, 0.313093, 0.0112099, 0.889831, - 0.00567962, 0.313093, 0.0161425, 0.889844, - 0.00773051, 0.313096, 0.0219724, 0.889858, - 0.0100968, 0.3131, 0.0286999, 0.889882, - 0.0127786, 0.313106, 0.0363256, 0.889918, - 0.0157757, 0.313116, 0.0448509, 0.889967, - 0.0190878, 0.313129, 0.0542758, 0.89003, - 0.022715, 0.313145, 0.0646032, 0.890108, - 0.0266566, 0.313165, 0.0758339, 0.890218, - 0.0309131, 0.313193, 0.0879729, 0.890351, - 0.0354819, 0.313226, 0.101019, 0.89051, - 0.0403613, 0.313263, 0.114979, 0.890672, - 0.0455385, 0.313294, 0.129848, 0.890882, - 0.0509444, 0.313333, 0.145616, 0.891189, - 0.0559657, 0.313324, 0.162122, 0.891457, - 0.0613123, 0.313281, 0.179524, 0.891856, - 0.0671488, 0.313281, 0.197855, 0.892312, - 0.0732732, 0.313268, 0.216991, 0.892819, - 0.0797865, 0.313263, 0.236924, 0.893369, - 0.0865269, 0.313247, 0.257433, 0.894045, - 0.0931592, 0.313205, 0.278215, 0.894884, - 0.100532, 0.313276, 0.299467, 0.895832, - 0.107716, 0.313205, 0.322276, 0.897043, - 0.114099, 0.312873, 0.34642, 0.898515, - 0.119941, 0.312331, 0.371187, 0.900191, - 0.126044, 0.311731, 0.396656, 0.90188, - 0.131808, 0.310859, 0.422488, 0.904359, - 0.137289, 0.309857, 0.448744, 0.906923, - 0.142991, 0.308714, 0.475239, 0.910634, - 0.148253, 0.307465, 0.501983, 0.914502, - 0.153332, 0.305774, 0.529254, 0.919046, - 0.156646, 0.303156, 0.557709, 0.923194, - 0.159612, 0.299928, 0.586267, 0.928858, - 0.162027, 0.296245, 0.614925, 0.934464, - 0.164203, 0.291832, 0.643187, 0.939824, - 0.165602, 0.286565, 0.671601, 0.944582, - 0.165383, 0.280073, 0.700213, 0.949257, - 0.164439, 0.272891, 0.728432, 0.954389, - 0.162953, 0.264771, 0.756082, 0.958595, - 0.161007, 0.255927, 0.78369, 0.962138, - 0.157243, 0.245769, 0.810769, 0.966979, - 0.152872, 0.235127, 0.836999, 0.969566, - 0.148209, 0.22347, 0.862684, 0.972372, - 0.142211, 0.211147, 0.887847, 0.975916, - 0.135458, 0.198606, 0.911843, 0.978026, - 0.128398, 0.185498, 0.934795, 0.979686, - 0.120313, 0.17171, 0.956787, 0.980748, - 0.11166, 0.158159, 0.978046, 0.981622, - 0.103035, 0.144399, 0.997693, 0.982356, - 0.0930328, 0.13001, 1.01642, 0.983308, - 0.0834627, 0.115778, 1.03366, 0.985037, - 0.0732249, 0.101327, 1.05014, 0.986493, - 0.0628145, 0.086554, 1.06507, 0.988484, - 0.0526556, 0.0720413, 1.07907, 0.991051, - 0.0415744, 0.0571151, 1.09189, 0.993523, - 0.0314275, 0.0426643, 1.10369, 0.99628, - 0.0203603, 0.0279325, 1.11423, 0.998344, - 0.0102446, 0.0138182, 1.12421, 0.999997, 0.00042612, - 0.000193628, 1.1333, 0.871555, - 6.60007e-06, 0.329176, 1.74749e-05, 0.875255, - 0.000166579, 0.330571, 0.000441051, 0.875644, - 0.000666394, 0.330718, 0.00176441, 0.875159, - 0.00149903, 0.330536, 0.00396899, 0.87516, - 0.00266493, 0.330536, 0.007056, 0.875158, - 0.00416393, 0.330535, 0.0110251, 0.87516, - 0.00599598, 0.330535, 0.0158764, 0.875163, - 0.00816108, 0.330536, 0.0216101, 0.875174, - 0.0106591, 0.330538, 0.0282266, 0.875199, - 0.0134899, 0.330545, 0.0357266, 0.875257, - 0.0166538, 0.330563, 0.0441117, 0.875304, - 0.0201501, 0.330575, 0.0533821, 0.875373, - 0.0239785, 0.330595, 0.0635395, 0.875464, - 0.0281389, 0.330619, 0.0745872, 0.875565, - 0.0326301, 0.330645, 0.0865255, 0.875691, - 0.0374516, 0.330676, 0.0993599, 0.875897, - 0.0425993, 0.330733, 0.113093, 0.876091, - 0.0480576, 0.330776, 0.127722, 0.876353, - 0.0537216, 0.330826, 0.143227, 0.876649, - 0.0589807, 0.330809, 0.159462, 0.877034, - 0.0647865, 0.330819, 0.176642, 0.877443, - 0.0709789, 0.330817, 0.194702, 0.877956, - 0.0774782, 0.330832, 0.213577, 0.878499, - 0.0843175, 0.330822, 0.233246, 0.879144, - 0.0912714, 0.330804, 0.253512, 0.879982, - 0.0980824, 0.330766, 0.274137, 0.88097, - 0.105823, 0.330864, 0.295209, 0.882051, - 0.113671, 0.330896, 0.317226, 0.883397, - 0.120303, 0.330545, 0.341068, 0.884987, - 0.12667, 0.330068, 0.365613, 0.886789, - 0.133118, 0.329418, 0.390807, 0.889311, - 0.139024, 0.328683, 0.416494, 0.891995, - 0.144971, 0.327729, 0.442618, 0.895106, - 0.150747, 0.326521, 0.469131, 0.899527, - 0.156283, 0.325229, 0.495921, 0.90504, - 0.161707, 0.32378, 0.523162, 0.909875, - 0.165661, 0.32122, 0.55092, 0.91561, - 0.168755, 0.317942, 0.579928, 0.921225, - 0.171193, 0.313983, 0.608539, 0.927308, - 0.17319, 0.309636, 0.636854, 0.933077, - 0.174819, 0.304262, 0.66523, 0.938766, - 0.175002, 0.297563, 0.693609, 0.943667, - 0.173946, 0.289613, 0.722157, 0.949033, - 0.172221, 0.281227, 0.750021, 0.953765, - 0.169869, 0.271545, 0.777466, 0.95804, - 0.166578, 0.261034, 0.804853, 0.962302, - 0.161761, 0.249434, 0.831569, 0.966544, - 0.156636, 0.237484, 0.857779, 0.969372, - 0.150784, 0.224395, 0.883051, 0.972486, - 0.143672, 0.210786, 0.907864, 0.975853, - 0.135772, 0.196556, 0.931223, 0.977975, - 0.127942, 0.182307, 0.954061, 0.979122, - 0.118347, 0.167607, 0.97531, 0.980719, - 0.109112, 0.152739, 0.995666, 0.981223, - 0.0991789, 0.137932, 1.01475, 0.98216, - 0.0883553, 0.122692, 1.03253, 0.983379, - 0.0780825, 0.107493, 1.04917, 0.985434, - 0.0665646, 0.0917791, 1.06464, 0.987332, - 0.0557714, 0.0764949, 1.07896, 0.990004, - 0.0442805, 0.060721, 1.09199, 0.992975, - 0.0331676, 0.0452284, 1.10393, 0.995811, - 0.0219547, 0.0297934, 1.11476, 0.9982, - 0.0107613, 0.0146415, 1.12484, 1.00002, 0.000248678, - 0.00014555, 1.13413, 0.859519, - 6.93595e-06, 0.347264, 1.71673e-05, 0.859843, - 0.00017503, 0.347394, 0.000433219, 0.859656, - 0.000700076, 0.347319, 0.00173277, 0.859671, - 0.00157517, 0.347325, 0.00389875, 0.859669, - 0.00280028, 0.347324, 0.00693112, 0.85967, - 0.0043754, 0.347324, 0.01083, 0.859665, - 0.00630049, 0.347321, 0.0155954, 0.859685, - 0.0085755, 0.347328, 0.0212278, 0.859694, - 0.0112003, 0.347329, 0.0277273, 0.859718, - 0.0141747, 0.347336, 0.0350946, 0.85976, - 0.0174988, 0.347348, 0.0433314, 0.85982, - 0.0211722, 0.347366, 0.0524384, 0.859892, - 0.0251941, 0.347387, 0.0624168, 0.860006, - 0.0295649, 0.347422, 0.0732708, 0.860122, - 0.0342825, 0.347453, 0.0849999, 0.860282, - 0.0393462, 0.347499, 0.0976102, 0.860482, - 0.0447513, 0.347554, 0.111104, 0.860719, - 0.0504775, 0.347614, 0.125479, 0.860998, - 0.0563577, 0.347666, 0.140703, 0.861322, - 0.0619473, 0.347662, 0.156681, 0.861724, - 0.0681277, 0.347684, 0.173597, 0.862198, - 0.0746567, 0.347709, 0.191371, 0.862733, - 0.0815234, 0.347727, 0.209976, 0.863371, - 0.0886643, 0.347744, 0.229351, 0.86414, - 0.0957908, 0.347734, 0.24934, 0.865138, - 0.102912, 0.34772, 0.269797, 0.866182, - 0.110924, 0.3478, 0.290654, 0.867436, - 0.119223, 0.347911, 0.312074, 0.869087, - 0.126197, 0.347649, 0.335438, 0.870859, - 0.133145, 0.347222, 0.359732, 0.872997, - 0.139869, 0.346645, 0.38467, 0.875939, - 0.146089, 0.345935, 0.41019, 0.879012, - 0.152334, 0.345012, 0.436218, 0.883353, - 0.15821, 0.343924, 0.462641, 0.888362, - 0.164097, 0.342636, 0.489449, 0.895026, - 0.169528, 0.341351, 0.516629, 0.900753, - 0.174408, 0.339115, 0.544109, 0.906814, - 0.17751, 0.335809, 0.572857, 0.912855, - 0.180101, 0.331597, 0.601554, 0.919438, - 0.182116, 0.32698, 0.630198, 0.925962, - 0.183494, 0.321449, 0.658404, 0.931734, - 0.184159, 0.314595, 0.686625, 0.93762, - 0.18304, 0.306462, 0.71531, 0.943858, - 0.181323, 0.297514, 0.744272, 0.948662, - 0.178683, 0.287447, 0.771462, 0.953299, - 0.175379, 0.276166, 0.798593, 0.957346, - 0.170395, 0.263758, 0.8256, 0.962565, - 0.165042, 0.251019, 0.852575, 0.966075, - 0.158655, 0.237011, 0.878316, 0.969048, - 0.151707, 0.222518, 0.90329, 0.972423, - 0.143271, 0.207848, 0.927745, 0.975833, - 0.134824, 0.192463, 0.950859, 0.977629, - 0.125444, 0.1768, 0.972947, 0.978995, - 0.114949, 0.161033, 0.993263, 0.980533, - 0.104936, 0.145523, 1.01337, 0.980745, - 0.0935577, 0.129799, 1.03128, 0.981814, - 0.0822956, 0.113486, 1.04825, 0.983943, - 0.0710082, 0.0972925, 1.06405, 0.986141, - 0.0587931, 0.0808138, 1.0785, 0.988878, - 0.0472755, 0.0644915, 1.09204, 0.992132, - 0.0349128, 0.0478128, 1.10413, 0.9953, - 0.0232407, 0.031621, 1.11527, 0.998117, - 0.0112713, 0.0154935, 1.12551, 1.00003, 0.000339743, - 0.000195763, 1.13504, 0.845441, - 7.29126e-06, 0.364305, 1.69208e-05, 0.843588, - 0.000183164, 0.363506, 0.000425067, 0.843412, - 0.00073253, 0.36343, 0.00169999, 0.843401, - 0.00164818, 0.363426, 0.00382495, 0.843399, - 0.00293008, 0.363425, 0.00679993, 0.843401, - 0.00457822, 0.363425, 0.010625, 0.843394, - 0.00659249, 0.363421, 0.0153002, 0.843398, - 0.00897282, 0.363421, 0.0208258, 0.843415, - 0.0117191, 0.363426, 0.0272024, 0.843438, - 0.0148312, 0.363432, 0.0344305, 0.843483, - 0.018309, 0.363447, 0.0425116, 0.84356, - 0.0221521, 0.363472, 0.0514471, 0.843646, - 0.0263597, 0.363499, 0.061238, 0.843743, - 0.0309315, 0.363527, 0.0718873, 0.84388, - 0.0358658, 0.363569, 0.0833969, 0.844079, - 0.0411624, 0.363631, 0.0957742, 0.844279, - 0.0468128, 0.363688, 0.109015, 0.844549, - 0.0527923, 0.363761, 0.123124, 0.844858, - 0.0588204, 0.363817, 0.138044, 0.84522, - 0.0647573, 0.36383, 0.153755, 0.845669, - 0.0713181, 0.363879, 0.170394, 0.846155, - 0.0781697, 0.363908, 0.187861, 0.846789, - 0.0853913, 0.363969, 0.206176, 0.847502, - 0.0928086, 0.363999, 0.225244, 0.8484, - 0.10005, 0.363997, 0.244926, 0.849461, - 0.107615, 0.364008, 0.265188, 0.850562, - 0.115814, 0.364055, 0.28587, 0.851962, - 0.124334, 0.364179, 0.306926, 0.854326, - 0.131995, 0.364233, 0.329605, 0.856295, - 0.139338, 0.363856, 0.35359, 0.858857, - 0.146346, 0.363347, 0.37831, 0.862428, - 0.152994, 0.362807, 0.403722, 0.866203, - 0.159463, 0.361963, 0.429537, 0.871629, - 0.165623, 0.36112, 0.456, 0.877365, - 0.171649, 0.359917, 0.482773, 0.883744, - 0.177151, 0.35848, 0.509705, 0.890693, - 0.182381, 0.356523, 0.537215, 0.897278, - 0.186076, 0.3533, 0.565493, 0.903958, - 0.188602, 0.349095, 0.594293, 0.910908, - 0.190755, 0.344215, 0.623165, 0.918117, - 0.192063, 0.338606, 0.651573, 0.924644, - 0.192758, 0.331544, 0.679869, 0.931054, - 0.192238, 0.323163, 0.708668, 0.937303, - 0.190035, 0.313529, 0.737201, 0.943387, - 0.187162, 0.303152, 0.764977, 0.948494, - 0.183876, 0.29146, 0.792683, 0.952546, - 0.178901, 0.277917, 0.819228, 0.958077, - 0.173173, 0.264753, 0.846559, 0.962462, - 0.16645, 0.25002, 0.872962, 0.966569, - 0.159452, 0.234873, 0.898729, 0.969108, - 0.15074, 0.218752, 0.923126, 0.973072, - 0.141523, 0.202673, 0.947278, 0.975452, - 0.132075, 0.186326, 0.969938, 0.977784, - 0.121257, 0.169396, 0.991325, 0.97899, - 0.110182, 0.153044, 1.01123, 0.979777, - 0.0989634, 0.136485, 1.0299, 0.980865, - 0.0865894, 0.119343, 1.04727, 0.982432, - 0.0746115, 0.102452, 1.06341, 0.984935, - 0.0621822, 0.0852423, 1.07834, 0.987776, - 0.0495694, 0.0678546, 1.092, 0.99103, - 0.0372386, 0.0506917, 1.1043, 0.99474, - 0.0244353, 0.0333316, 1.11576, 0.997768, - 0.0121448, 0.0164348, 1.12617, 1.00003, 0.00031774, - 0.000169504, 1.13598, 0.825551, - 7.56799e-06, 0.378425, 1.65099e-05, 0.82664, - 0.000190922, 0.378923, 0.000416504, 0.826323, - 0.000763495, 0.378779, 0.0016656, 0.826359, - 0.00171789, 0.378795, 0.00374768, 0.82636, - 0.00305402, 0.378795, 0.00666259, 0.826368, - 0.00477185, 0.378798, 0.0104104, 0.826364, - 0.00687131, 0.378795, 0.0149912, 0.826368, - 0.00935232, 0.378795, 0.0204054, 0.826376, - 0.0122146, 0.378797, 0.0266532, 0.826399, - 0.0154581, 0.378803, 0.0337355, 0.82646, - 0.0190825, 0.378824, 0.0416537, 0.826525, - 0.0230873, 0.378846, 0.0504091, 0.826614, - 0.0274719, 0.378876, 0.0600032, 0.82674, - 0.0322355, 0.378917, 0.0704393, 0.826888, - 0.0373766, 0.378964, 0.0817195, 0.827078, - 0.0428936, 0.379024, 0.0938492, 0.827318, - 0.0487778, 0.379099, 0.106828, 0.82764, - 0.0549935, 0.379199, 0.120659, 0.827926, - 0.0611058, 0.379227, 0.13526, 0.828325, - 0.0675054, 0.379275, 0.150713, 0.828801, - 0.0743455, 0.379332, 0.167034, 0.8294, - 0.0815523, 0.379415, 0.184209, 0.830094, - 0.0890779, 0.379495, 0.202203, 0.8309, - 0.096736, 0.379555, 0.220945, 0.831943, - 0.104135, 0.379577, 0.240306, 0.833037, - 0.112106, 0.379604, 0.260317, 0.834278, - 0.120554, 0.379668, 0.2808, 0.836192, - 0.129128, 0.3799, 0.301654, 0.838671, - 0.137541, 0.380109, 0.323502, 0.840939, - 0.14523, 0.379809, 0.347176, 0.844575, - 0.15248, 0.379593, 0.371706, 0.848379, - 0.159607, 0.37909, 0.39688, 0.853616, - 0.166267, 0.378617, 0.422702, 0.858921, - 0.172698, 0.377746, 0.448919, 0.865324, - 0.178823, 0.376749, 0.475661, 0.872207, - 0.184542, 0.375363, 0.502599, 0.880018, - 0.189836, 0.373657, 0.529914, 0.88694, - 0.194294, 0.370673, 0.557683, 0.894779, - 0.197022, 0.36662, 0.586848, 0.902242, - 0.199108, 0.36138, 0.615831, 0.909914, - 0.200398, 0.355434, 0.644478, 0.917088, - 0.20094, 0.348173, 0.672905, 0.923888, - 0.200671, 0.339482, 0.701327, 0.930495, - 0.198773, 0.32956, 0.730101, 0.937247, - 0.195394, 0.318363, 0.758383, 0.943108, - 0.191956, 0.306323, 0.786539, 0.948296, - 0.187227, 0.292576, 0.813637, 0.953472, - 0.181165, 0.278234, 0.840793, 0.958485, - 0.174119, 0.263054, 0.867712, 0.962714, - 0.166564, 0.246756, 0.893635, 0.966185, - 0.158181, 0.229945, 0.919028, 0.970146, - 0.148275, 0.212633, 0.943413, 0.973491, - 0.138157, 0.195229, 0.966627, 0.975741, - 0.127574, 0.178048, 0.988817, 0.977238, - 0.11554, 0.160312, 1.00924, 0.978411, - 0.10364, 0.142857, 1.02845, 0.979811, - 0.0913122, 0.125317, 1.04648, 0.98116, - 0.0782558, 0.107627, 1.06284, 0.983543, - 0.0655957, 0.0895862, 1.07798, 0.986789, - 0.0520411, 0.0713756, 1.092, 0.990292, - 0.0389727, 0.053228, 1.10484, 0.994187, - 0.025808, 0.0351945, 1.11642, 0.997499, - 0.0126071, 0.0173198, 1.12703, 0.999999, 0.000275604, - 0.000148602, 1.13674, 0.81075, - 7.8735e-06, 0.394456, 1.61829e-05, 0.808692, - 0.000198293, 0.393453, 0.000407564, 0.80846, - 0.000792877, 0.39334, 0.00162965, 0.808595, - 0.00178416, 0.393407, 0.00366711, 0.808597, - 0.00317182, 0.393408, 0.00651934, 0.808598, - 0.00495589, 0.393408, 0.0101866, 0.808591, - 0.00713627, 0.393403, 0.0146689, 0.808592, - 0.00971285, 0.393402, 0.0199667, 0.80861, - 0.0126855, 0.393407, 0.0260803, 0.808633, - 0.0160538, 0.393413, 0.0330107, 0.80868, - 0.0198175, 0.393429, 0.0407589, 0.808748, - 0.0239758, 0.393453, 0.0493264, 0.808854, - 0.0285286, 0.39349, 0.0587161, 0.808992, - 0.0334748, 0.39354, 0.0689304, 0.809141, - 0.0388116, 0.393588, 0.0799707, 0.809352, - 0.0445375, 0.39366, 0.0918432, 0.809608, - 0.0506427, 0.393742, 0.104549, 0.809915, - 0.0570708, 0.393834, 0.118085, 0.810253, - 0.0633526, 0.393885, 0.132377, 0.810687, - 0.0700966, 0.393953, 0.147537, 0.811233, - 0.0772274, 0.394047, 0.163543, 0.811865, - 0.0847629, 0.394148, 0.180394, 0.812648, - 0.0925663, 0.394265, 0.198051, 0.813583, - 0.100416, 0.394363, 0.216443, 0.814683, - 0.108119, 0.394402, 0.235502, 0.815948, - 0.11644, 0.394489, 0.255242, 0.817278, - 0.125036, 0.394542, 0.275441, 0.819605, - 0.133655, 0.39486, 0.296094, 0.822256, - 0.142682, 0.395248, 0.317309, 0.825349, - 0.150756, 0.395241, 0.340516, 0.829605, - 0.158392, 0.395285, 0.364819, 0.83391, - 0.165801, 0.394922, 0.389736, 0.839808, - 0.172677, 0.394691, 0.415409, 0.845708, - 0.179448, 0.394006, 0.441546, 0.853025, - 0.185746, 0.393279, 0.46832, 0.859666, - 0.191684, 0.391655, 0.495302, 0.86789, - 0.197146, 0.390068, 0.52262, 0.875845, - 0.201904, 0.38727, 0.550336, 0.882634, - 0.205023, 0.382688, 0.578825, 0.891076, - 0.207098, 0.377543, 0.608103, 0.900589, - 0.208474, 0.371752, 0.63723, 0.90791, - 0.209068, 0.364016, 0.665769, 0.915971, - 0.208655, 0.355593, 0.694428, 0.923455, - 0.20729, 0.345439, 0.723224, 0.931514, - 0.203821, 0.334099, 0.751925, 0.937885, - 0.19986, 0.321069, 0.780249, 0.943136, - 0.194993, 0.306571, 0.8077, 0.948818, - 0.189132, 0.291556, 0.83497, 0.954433, - 0.181617, 0.275745, 0.86188, 0.959078, - 0.173595, 0.258695, 0.888562, 0.962705, - 0.164855, 0.240825, 0.914008, 0.966753, - 0.155129, 0.22268, 0.939145, 0.970704, - 0.144241, 0.204542, 0.963393, 0.973367, - 0.133188, 0.185927, 0.985983, 0.975984, - 0.121146, 0.167743, 1.00704, 0.976994, - 0.108366, 0.149218, 1.02715, 0.978485, - 0.0956746, 0.13131, 1.0455, 0.980074, - 0.0820733, 0.112513, 1.06221, 0.98225, - 0.0684061, 0.0938323, 1.07782, 0.98553, - 0.0549503, 0.0749508, 1.09199, 0.989529, - 0.0407857, 0.055848, 1.10508, 0.993536, - 0.0271978, 0.0368581, 1.11684, 0.997247, - 0.0132716, 0.0181845, 1.12789, 1, 0.000431817, - 0.000198809, 1.13792, 0.785886, - 8.12608e-06, 0.405036, 1.57669e-05, 0.790388, - 0.000205278, 0.407355, 0.000398297, 0.790145, - 0.000820824, 0.407231, 0.00159263, 0.790135, - 0.00184681, 0.407226, 0.00358336, 0.790119, - 0.00328316, 0.407218, 0.00637039, 0.790126, - 0.00512988, 0.40722, 0.0099539, 0.79013, - 0.00738684, 0.407221, 0.0143339, 0.790135, - 0.0100538, 0.407221, 0.0195107, 0.790134, - 0.0131306, 0.407217, 0.0254848, 0.79016, - 0.0166169, 0.407224, 0.0322572, 0.790197, - 0.020512, 0.407236, 0.0398284, 0.790273, - 0.0248157, 0.407263, 0.0482014, 0.790381, - 0.029527, 0.407304, 0.0573777, 0.790521, - 0.0346446, 0.407355, 0.0673602, 0.790704, - 0.0401665, 0.40742, 0.0781522, 0.790925, - 0.0460896, 0.407499, 0.0897582, 0.791195, - 0.0524017, 0.407589, 0.10218, 0.791522, - 0.0590121, 0.407691, 0.11541, 0.791878, - 0.0654876, 0.407748, 0.12939, 0.792361, - 0.0725207, 0.407849, 0.144237, 0.792942, - 0.0799844, 0.407963, 0.159924, 0.79362, - 0.0877896, 0.408087, 0.176425, 0.794529, - 0.0958451, 0.408259, 0.193733, 0.795521, - 0.103827, 0.408362, 0.211756, 0.796778, - 0.111937, 0.408482, 0.230524, 0.798027, - 0.120521, 0.408547, 0.249967, 0.799813, - 0.129242, 0.408721, 0.269926, 0.802387, - 0.138048, 0.409148, 0.290338, 0.805279, - 0.147301, 0.409641, 0.311193, 0.809251, - 0.155895, 0.410154, 0.333611, 0.813733, - 0.163942, 0.410297, 0.357615, 0.819081, - 0.171666, 0.410373, 0.382339, 0.825427, - 0.178905, 0.410348, 0.407828, 0.83172, - 0.185812, 0.409486, 0.434034, 0.83877, - 0.192318, 0.408776, 0.460493, 0.845817, - 0.198249, 0.407176, 0.487346, 0.854664, - 0.204034, 0.405719, 0.514832, 0.863495, - 0.208908, 0.403282, 0.542401, 0.871883, - 0.212765, 0.399293, 0.570683, 0.88065, - 0.214911, 0.393803, 0.599947, 0.89004, - 0.216214, 0.387536, 0.62932, 0.898476, - 0.216745, 0.379846, 0.658319, 0.906738, - 0.216387, 0.370625, 0.687138, 0.914844, - 0.215053, 0.360139, 0.71601, 0.923877, - 0.212007, 0.348849, 0.745124, 0.931925, - 0.207481, 0.335639, 0.773366, 0.938054, - 0.202418, 0.320798, 0.801636, 0.943895, - 0.196507, 0.304772, 0.829055, 0.949468, - 0.189009, 0.288033, 0.856097, 0.955152, - 0.180539, 0.270532, 0.88301, 0.959403, - 0.171437, 0.251639, 0.909296, 0.963309, - 0.161661, 0.232563, 0.934868, 0.967399, - 0.150425, 0.213231, 0.959662, 0.972009, - 0.138659, 0.194247, 0.98302, 0.97433, - 0.126595, 0.174718, 1.00517, 0.975823, - 0.113205, 0.155518, 1.02566, 0.976371, - 0.0996096, 0.136709, 1.04418, 0.978705, - 0.0860754, 0.117571, 1.06146, 0.981477, - 0.0714438, 0.0980046, 1.07777, 0.984263, - 0.0572304, 0.0782181, 1.09214, 0.988423, - 0.0428875, 0.0584052, 1.10553, 0.993, - 0.0282442, 0.038522, 1.11758, 0.99704, - 0.0140183, 0.0190148, 1.12864, 0.999913, 0.000369494, - 0.000145203, 1.13901, 0.777662, - 8.4153e-06, 0.423844, 1.54403e-05, 0.770458, - 0.000211714, 0.419915, 0.00038845, 0.770716, - 0.000846888, 0.420055, 0.00155386, 0.770982, - 0.00190567, 0.420202, 0.00349653, 0.770981, - 0.00338782, 0.420201, 0.00621606, 0.77098, - 0.00529338, 0.4202, 0.00971274, 0.770983, - 0.00762223, 0.4202, 0.0139867, 0.770985, - 0.0103741, 0.420198, 0.0190381, 0.770996, - 0.0135489, 0.4202, 0.0248677, 0.771029, - 0.0171461, 0.420212, 0.0314764, 0.771052, - 0.0211647, 0.420215, 0.0388648, 0.771131, - 0.0256048, 0.420245, 0.047036, 0.771235, - 0.0304647, 0.420284, 0.0559911, 0.771383, - 0.0357436, 0.420341, 0.0657346, 0.771591, - 0.0414392, 0.420423, 0.0762694, 0.771819, - 0.0475462, 0.420506, 0.0875984, 0.772123, - 0.0540506, 0.420617, 0.099727, 0.772464, - 0.060797, 0.42072, 0.112637, 0.772855, - 0.0675393, 0.420799, 0.126313, 0.773317, - 0.0748323, 0.420893, 0.140824, 0.773981, - 0.0825681, 0.421058, 0.15617, 0.774746, - 0.0906307, 0.421226, 0.172322, 0.77566, - 0.0988982, 0.421397, 0.189253, 0.776837, - 0.106994, 0.421569, 0.206912, 0.778097, - 0.115528, 0.421704, 0.225359, 0.779588, - 0.124317, 0.421849, 0.24447, 0.781574, - 0.133139, 0.422097, 0.264156, 0.784451, - 0.142179, 0.422615, 0.284318, 0.787682, - 0.15165, 0.423269, 0.304902, 0.792433, - 0.160771, 0.424396, 0.3265, 0.797359, - 0.169166, 0.424772, 0.35014, 0.803986, - 0.177149, 0.425475, 0.374768, 0.809504, - 0.184745, 0.424996, 0.399928, 0.815885, - 0.19173, 0.424247, 0.425796, 0.823513, - 0.198525, 0.423515, 0.452287, 0.832549, - 0.204709, 0.422787, 0.479321, 0.841653, - 0.210447, 0.421187, 0.506718, 0.850401, - 0.215501, 0.418519, 0.53432, 0.859854, - 0.219752, 0.414715, 0.56242, 0.869364, - 0.222305, 0.409462, 0.591558, 0.878837, - 0.223744, 0.402926, 0.621074, 0.888636, - 0.224065, 0.395043, 0.650538, 0.898132, - 0.223742, 0.38564, 0.679538, 0.907181, - 0.222308, 0.375378, 0.708674, 0.915621, - 0.219837, 0.363212, 0.737714, 0.9239, - 0.215233, 0.349313, 0.767014, 0.931644, - 0.209592, 0.334162, 0.795133, 0.938887, - 0.203644, 0.317943, 0.823228, 0.945282, - 0.196349, 0.300581, 0.850822, 0.950758, - 0.18742, 0.282195, 0.877594, 0.956146, - 0.177879, 0.262481, 0.904564, 0.960355, - 0.167643, 0.242487, 0.930741, 0.965256, - 0.156671, 0.222668, 0.955868, 0.968029, - 0.144123, 0.201907, 0.979869, 0.97251, - 0.131305, 0.18202, 1.00291, 0.974925, - 0.118335, 0.161909, 1.02392, 0.975402, - 0.103714, 0.142129, 1.0433, 0.976987, - 0.089415, 0.122447, 1.06089, 0.979677, - 0.0748858, 0.102248, 1.07713, 0.983184, - 0.0596086, 0.0814851, 1.09218, 0.987466, - 0.0447671, 0.0609484, 1.10585, 0.992348, - 0.0295217, 0.0401835, 1.11829, 0.996674, - 0.0143917, 0.0198163, 1.12966, 1.00003, 0.000321364, - 0.000149983, 1.1402, 0.757901, - 8.69074e-06, 0.436176, 1.51011e-05, 0.751195, - 0.000217848, 0.432317, 0.000378533, 0.751178, - 0.000871373, 0.432307, 0.0015141, 0.751195, - 0.00196061, 0.432317, 0.0034068, 0.751198, - 0.00348552, 0.432318, 0.00605659, 0.751195, - 0.00544599, 0.432315, 0.00946353, 0.751207, - 0.00784203, 0.43232, 0.013628, 0.751213, - 0.0106732, 0.43232, 0.0185499, 0.751221, - 0.0139393, 0.432319, 0.0242302, 0.751244, - 0.0176398, 0.432325, 0.0306694, 0.7513, - 0.0217743, 0.432348, 0.0378698, 0.751358, - 0.0263412, 0.432367, 0.0458321, 0.751458, - 0.0313396, 0.432404, 0.0545587, 0.751608, - 0.0367682, 0.432464, 0.0640543, 0.7518, - 0.0426246, 0.43254, 0.0743222, 0.752065, - 0.0489031, 0.432645, 0.0853668, 0.752376, - 0.0555828, 0.432762, 0.0971911, 0.752715, - 0.0623861, 0.432859, 0.109768, 0.753137, - 0.069415, 0.432958, 0.123126, 0.753676, - 0.0770039, 0.433099, 0.137308, 0.754345, - 0.084971, 0.433272, 0.15229, 0.755235, - 0.0932681, 0.433504, 0.168075, 0.756186, - 0.10171, 0.433693, 0.184625, 0.757363, - 0.110019, 0.433857, 0.201897, 0.75884, - 0.11887, 0.434102, 0.220014, 0.760467, - 0.127881, 0.434306, 0.238778, 0.762969, - 0.136766, 0.434751, 0.258172, 0.765823, - 0.14612, 0.43529, 0.278062, 0.769676, - 0.15566, 0.436236, 0.298437, 0.774909, - 0.165177, 0.437754, 0.319532, 0.77994, - 0.17402, 0.438343, 0.342505, 0.785757, - 0.182201, 0.438609, 0.366693, 0.792487, - 0.190104, 0.438762, 0.391668, 0.80038, - 0.197438, 0.438795, 0.417494, 0.808494, - 0.204365, 0.438226, 0.443933, 0.817695, - 0.210714, 0.437283, 0.470929, 0.828111, - 0.216651, 0.436087, 0.498569, 0.837901, - 0.221804, 0.433717, 0.526165, 0.847813, - 0.226318, 0.430133, 0.554155, 0.858314, - 0.229297, 0.425213, 0.582822, 0.868891, - 0.230999, 0.418576, 0.612847, 0.878941, - 0.231155, 0.410405, 0.642445, 0.888809, - 0.230935, 0.400544, 0.672024, 0.898089, - 0.229343, 0.389613, 0.701366, 0.908081, - 0.226886, 0.377197, 0.730763, 0.916819, - 0.222676, 0.363397, 0.759642, 0.924968, - 0.216835, 0.347437, 0.788775, 0.932906, - 0.210245, 0.32995, 0.817135, 0.940025, - 0.202992, 0.312262, 0.844912, 0.946101, - 0.19436, 0.293313, 0.872164, 0.952835, - 0.184125, 0.273638, 0.899443, 0.957347, - 0.173657, 0.252385, 0.926389, 0.961434, - 0.162204, 0.231038, 0.951947, 0.965522, - 0.14979, 0.209834, 0.976751, 0.969412, - 0.136307, 0.188821, 1.00022, 0.973902, - 0.122527, 0.168013, 1.02229, 0.974045, - 0.108213, 0.147634, 1.04199, 0.975775, - 0.0927397, 0.12705, 1.06019, 0.978383, - 0.0778212, 0.106309, 1.07711, 0.98211, - 0.0621216, 0.0849279, 1.09245, 0.986517, - 0.0463847, 0.0633519, 1.10651, 0.991696, - 0.0309353, 0.0419698, 1.11903, 0.996349, - 0.0150914, 0.0206272, 1.13073, 1.00003, 0.000442449, - 0.000231396, 1.14146, 0.727498, - 8.85074e-06, 0.441528, 1.45832e-05, 0.730897, - 0.000223525, 0.443589, 0.000368298, 0.730796, - 0.000893996, 0.443528, 0.00147303, 0.730805, - 0.00201149, 0.443533, 0.00331433, 0.730814, - 0.00357596, 0.443538, 0.00589222, 0.730815, - 0.00558734, 0.443538, 0.00920678, 0.730822, - 0.00804544, 0.44354, 0.0132582, 0.730836, - 0.0109501, 0.443545, 0.0180468, 0.730848, - 0.0143008, 0.443546, 0.0235732, 0.730871, - 0.0180969, 0.443552, 0.0298382, 0.730915, - 0.022338, 0.443567, 0.0368438, 0.730982, - 0.0270225, 0.443591, 0.044591, 0.731076, - 0.0321491, 0.443627, 0.0530831, 0.731245, - 0.0377166, 0.443699, 0.0623243, 0.73144, - 0.0437216, 0.443777, 0.0723181, 0.7317, - 0.0501576, 0.443881, 0.0830691, 0.732034, - 0.0569942, 0.444014, 0.0945809, 0.732388, - 0.0638756, 0.444113, 0.106825, 0.732853, - 0.071203, 0.444247, 0.119859, 0.733473, - 0.0790076, 0.444442, 0.13369, 0.734195, - 0.0871937, 0.444645, 0.148304, 0.735069, - 0.095696, 0.444877, 0.163702, 0.736169, - 0.10426, 0.445133, 0.179861, 0.73747, - 0.112853, 0.44537, 0.196778, 0.738991, - 0.12199, 0.445651, 0.214496, 0.740865, - 0.131153, 0.445958, 0.232913, 0.743637, - 0.140245, 0.446548, 0.251977, 0.746797, - 0.149722, 0.447246, 0.271551, 0.751517, - 0.159341, 0.448656, 0.291774, 0.756156, - 0.169106, 0.449866, 0.312455, 0.761519, - 0.178436, 0.450919, 0.334552, 0.768295, - 0.186904, 0.451776, 0.358491, 0.776613, - 0.195117, 0.452832, 0.383446, 0.783966, - 0.202695, 0.45249, 0.408945, 0.793542, - 0.20985, 0.452587, 0.435364, 0.803192, - 0.216403, 0.451852, 0.462336, 0.813892, - 0.22251, 0.450708, 0.48987, 0.824968, - 0.227676, 0.4486, 0.517697, 0.835859, - 0.232443, 0.445156, 0.545975, 0.846825, - 0.235775, 0.440351, 0.574483, 0.858085, - 0.237897, 0.433641, 0.604246, 0.868825, - 0.238074, 0.425354, 0.634101, 0.879638, - 0.237661, 0.415383, 0.664201, 0.889966, - 0.236186, 0.404136, 0.693918, 0.899479, - 0.233599, 0.390917, 0.723481, 0.908769, - 0.229737, 0.376352, 0.75258, 0.917966, - 0.223836, 0.360372, 0.781764, 0.926304, - 0.217067, 0.342551, 0.811139, 0.934626, - 0.209309, 0.324238, 0.839585, 0.941841, - 0.20071, 0.304484, 0.867044, 0.94789, - 0.190602, 0.283607, 0.894579, 0.954196, - 0.179253, 0.262205, 0.921743, 0.958383, - 0.167646, 0.239847, 0.948026, 0.963119, - 0.155073, 0.218078, 0.973296, 0.966941, - 0.141426, 0.195899, 0.998135, 0.970836, - 0.126849, 0.174121, 1.02021, 0.973301, - 0.112296, 0.153052, 1.04085, 0.97448, - 0.0964965, 0.131733, 1.05946, 0.977045, - 0.080489, 0.10997, 1.07693, 0.980751, - 0.064844, 0.0881657, 1.09254, 0.985475, - 0.0481938, 0.0657987, 1.10697, 0.991089, - 0.0319185, 0.0435215, 1.12004, 0.996122, - 0.0158088, 0.0214779, 1.13173, 1.00001, 0.000372455, - 0.000200295, 1.14291, 0.708622, - 9.07597e-06, 0.45304, 1.41962e-05, 0.711162, - 0.000228911, 0.454662, 0.000358052, 0.709812, - 0.000914446, 0.453797, 0.00143034, 0.709865, - 0.00205819, 0.453834, 0.00321935, 0.709864, - 0.00365894, 0.453833, 0.00572331, 0.709855, - 0.00571692, 0.453826, 0.00894278, 0.709862, - 0.00823201, 0.453828, 0.012878, 0.709875, - 0.011204, 0.453832, 0.0175295, 0.709896, - 0.0146323, 0.453839, 0.0228978, 0.709925, - 0.0185163, 0.453847, 0.0289839, 0.709974, - 0.0228551, 0.453866, 0.0357894, 0.710045, - 0.0276473, 0.453892, 0.0433161, 0.710133, - 0.032891, 0.453924, 0.0515665, 0.710292, - 0.0385851, 0.453992, 0.0605458, 0.710485, - 0.0447254, 0.45407, 0.0702574, 0.710769, - 0.0513051, 0.454192, 0.0807077, 0.711106, - 0.0582733, 0.454329, 0.091896, 0.711516, - 0.0652866, 0.45446, 0.103814, 0.712071, - 0.0728426, 0.454653, 0.116508, 0.712676, - 0.0808307, 0.45484, 0.129968, 0.713476, - 0.0892216, 0.455096, 0.144206, 0.714377, - 0.0979047, 0.455346, 0.159212, 0.715579, - 0.106531, 0.455647, 0.174973, 0.716977, - 0.115492, 0.455961, 0.191504, 0.71862, - 0.124821, 0.456315, 0.208835, 0.72084, - 0.134079, 0.4568, 0.226869, 0.723786, - 0.143427, 0.457521, 0.245582, 0.727464, - 0.153061, 0.458475, 0.264957, 0.732771, - 0.162768, 0.460239, 0.284948, 0.736515, - 0.172627, 0.460899, 0.30522, 0.743519, - 0.182487, 0.463225, 0.326717, 0.750041, - 0.191295, 0.464027, 0.350113, 0.758589, - 0.199746, 0.465227, 0.374782, 0.767703, - 0.207584, 0.465877, 0.400226, 0.777484, - 0.214973, 0.465996, 0.426442, 0.788792, - 0.221796, 0.466019, 0.453688, 0.800194, - 0.228038, 0.465083, 0.481246, 0.811234, - 0.233346, 0.462506, 0.509086, 0.822859, - 0.238073, 0.459257, 0.537338, 0.835082, - 0.241764, 0.454863, 0.566108, 0.846332, - 0.244241, 0.448163, 0.595126, 0.858355, - 0.244736, 0.439709, 0.625574, 0.87034, - 0.244278, 0.429837, 0.65617, 0.881027, - 0.24255, 0.418002, 0.686029, 0.891007, - 0.239912, 0.404325, 0.716039, 0.900874, - 0.236133, 0.389222, 0.745518, 0.911072, - 0.230672, 0.373269, 0.775026, 0.920359, - 0.22356, 0.355083, 0.804521, 0.928604, - 0.215591, 0.335533, 0.834045, 0.937175, - 0.206503, 0.315278, 0.861612, 0.942825, - 0.196684, 0.293653, 0.889131, 0.949805, - 0.185116, 0.271503, 0.916853, 0.955535, - 0.172703, 0.248821, 0.943541, 0.959843, - 0.159978, 0.225591, 0.970132, 0.964393, - 0.146375, 0.202719, 0.994709, 0.968008, - 0.131269, 0.179928, 1.0186, 0.971013, - 0.11569, 0.158007, 1.03928, 0.973334, - 0.1003, 0.13624, 1.05887, 0.975775, - 0.0833352, 0.1138, 1.07652, 0.979579, - 0.0668981, 0.0913141, 1.09297, 0.984323, - 0.0500902, 0.0683051, 1.10734, 0.990351, - 0.0332377, 0.0451771, 1.12084, 0.995823, - 0.0161491, 0.0221705, 1.13296, 1.0001, 0.000234083, - 0.000108712, 1.14441, 0.683895, - 9.24677e-06, 0.46015, 1.37429e-05, 0.68833, - 0.000233383, 0.463134, 0.000346865, 0.688368, - 0.000933547, 0.463159, 0.00138748, 0.688367, - 0.00210049, 0.463159, 0.00312187, 0.688369, - 0.00373415, 0.463159, 0.00555004, 0.688377, - 0.00583449, 0.463163, 0.00867216, 0.688386, - 0.00840128, 0.463166, 0.0124884, 0.688398, - 0.0114343, 0.463169, 0.0169993, 0.688418, - 0.0149329, 0.463175, 0.0222054, 0.688453, - 0.0188964, 0.463188, 0.028108, 0.688515, - 0.0233239, 0.463214, 0.0347085, 0.68857, - 0.0282136, 0.463231, 0.0420091, 0.688679, - 0.033564, 0.463276, 0.0500132, 0.688854, - 0.0393733, 0.463356, 0.0587255, 0.689038, - 0.0456354, 0.46343, 0.0681476, 0.689321, - 0.0523433, 0.463553, 0.0782897, 0.689662, - 0.059412, 0.463693, 0.0891501, 0.690188, - 0.0665736, 0.4639, 0.100735, 0.690755, - 0.0743106, 0.464107, 0.113074, 0.691405, - 0.0824722, 0.464329, 0.126161, 0.692198, - 0.0910484, 0.464585, 0.140007, 0.693196, - 0.0998778, 0.464893, 0.154612, 0.69454, - 0.108651, 0.465285, 0.169984, 0.695921, - 0.117855, 0.465596, 0.186106, 0.697749, - 0.12734, 0.466056, 0.203034, 0.700375, - 0.136714, 0.466771, 0.220703, 0.703395, - 0.146386, 0.467579, 0.239062, 0.707904, - 0.156096, 0.469067, 0.258188, 0.711673, - 0.165904, 0.469851, 0.277759, 0.717489, - 0.175812, 0.471815, 0.297935, 0.724051, - 0.185931, 0.47389, 0.318916, 0.731965, - 0.195238, 0.47587, 0.341591, 0.741151, - 0.204021, 0.477523, 0.366062, 0.751416, - 0.212113, 0.478881, 0.391396, 0.761848, - 0.21979, 0.479226, 0.417599, 0.771886, - 0.2267, 0.478495, 0.444401, 0.783998, - 0.232991, 0.477622, 0.472084, 0.796523, - 0.238645, 0.475833, 0.500193, 0.808851, - 0.243396, 0.472568, 0.52865, 0.821191, - 0.247226, 0.467857, 0.557362, 0.834261, - 0.250102, 0.461871, 0.586768, 0.846762, - 0.251056, 0.453543, 0.617085, 0.859867, - 0.250604, 0.443494, 0.647659, 0.871948, - 0.248783, 0.431711, 0.678119, 0.882967, - 0.245855, 0.417911, 0.708399, 0.892826, - 0.242168, 0.401993, 0.738256, 0.90332, - 0.237062, 0.385371, 0.767999, 0.913633, - 0.22997, 0.366837, 0.798191, 0.922774, - 0.221687, 0.346372, 0.827756, 0.931371, - 0.212345, 0.325682, 0.856425, 0.938929, - 0.20206, 0.303665, 0.884299, 0.944821, - 0.190981, 0.280786, 0.912023, 0.951792, - 0.178065, 0.2573, 0.939669, 0.957712, - 0.164634, 0.233448, 0.96655, 0.961912, - 0.150863, 0.209504, 0.992366, 0.966382, - 0.13577, 0.18597, 1.01633, 0.969588, - 0.119593, 0.162905, 1.03843, 0.971777, - 0.103203, 0.14053, 1.05841, 0.97433, - 0.0865888, 0.117909, 1.07632, 0.978686, - 0.0690829, 0.0944101, 1.09326, 0.983281, - 0.0516568, 0.0705671, 1.10796, 0.989562, - 0.034558, 0.0468592, 1.12182, 0.995465, - 0.0167808, 0.0229846, 1.1342, 0.999991, 0.000373016, - 0.000235606, 1.1459, 0.662251, - 9.39016e-06, 0.468575, 1.32714e-05, 0.666634, - 0.000237624, 0.471675, 0.000335842, 0.666411, - 0.000950385, 0.471516, 0.00134321, 0.666399, - 0.00213833, 0.471509, 0.00302221, 0.666386, - 0.0038014, 0.471499, 0.00537283, 0.666405, - 0.00593958, 0.471511, 0.00839533, 0.666406, - 0.00855253, 0.471508, 0.0120898, 0.666428, - 0.0116401, 0.471519, 0.0164569, 0.666444, - 0.0152015, 0.471522, 0.0214971, 0.66649, - 0.0192362, 0.471543, 0.027212, 0.666537, - 0.0237428, 0.471558, 0.033603, 0.666617, - 0.0287198, 0.471591, 0.0406728, 0.666718, - 0.0341647, 0.471631, 0.0484238, 0.666889, - 0.0400759, 0.47171, 0.0568621, 0.667104, - 0.0464479, 0.471805, 0.0659915, 0.667374, - 0.0532677, 0.471923, 0.0758178, 0.667772, - 0.0603805, 0.472098, 0.0863425, 0.668371, - 0.0677392, 0.472363, 0.0975917, 0.668971, - 0.0756028, 0.472596, 0.109567, 0.669696, - 0.0839293, 0.472869, 0.122272, 0.670481, - 0.0926683, 0.473126, 0.135718, 0.6715, - 0.1016, 0.473442, 0.149914, 0.672911, - 0.110566, 0.47389, 0.164882, 0.674512, - 0.119984, 0.474354, 0.180602, 0.67651, - 0.129574, 0.474922, 0.19711, 0.679292, - 0.139106, 0.475764, 0.214371, 0.682798, - 0.148993, 0.476886, 0.232405, 0.686955, - 0.158737, 0.478179, 0.251153, 0.691406, - 0.168754, 0.479432, 0.270436, 0.697438, - 0.178703, 0.481481, 0.290374, 0.704761, - 0.188955, 0.484143, 0.311044, 0.713599, - 0.198814, 0.487007, 0.333003, 0.723194, - 0.207869, 0.488962, 0.357144, 0.732601, - 0.216189, 0.489815, 0.382169, 0.744193, - 0.22398, 0.490888, 0.408227, 0.754907, - 0.231156, 0.490355, 0.434928, 0.767403, - 0.23747, 0.489548, 0.462599, 0.78107, - 0.243503, 0.488274, 0.490908, 0.793893, - 0.248114, 0.484843, 0.519421, 0.807296, - 0.25222, 0.4803, 0.548561, 0.820529, - 0.255265, 0.474097, 0.577772, 0.833716, - 0.256741, 0.466041, 0.607782, 0.848403, - 0.25637, 0.456547, 0.638807, 0.860755, - 0.254804, 0.443946, 0.670058, 0.874012, - 0.251834, 0.430852, 0.700749, 0.885619, - 0.247867, 0.414903, 0.731446, 0.896069, - 0.242634, 0.397276, 0.761191, 0.906266, - 0.236093, 0.378535, 0.791053, 0.916759, - 0.227543, 0.358038, 0.821298, 0.92523, - 0.21783, 0.335705, 0.850747, 0.93436, - 0.207534, 0.313797, 0.879258, 0.941631, - 0.195983, 0.289671, 0.907734, 0.947564, - 0.183567, 0.265319, 0.935206, 0.953681, - 0.169345, 0.240815, 0.962739, 0.960008, - 0.154909, 0.216119, 0.989227, 0.964145, - 0.140161, 0.192096, 1.01465, 0.968171, - 0.123411, 0.167855, 1.03737, 0.969859, - 0.106525, 0.144817, 1.05767, 0.972666, - 0.0891023, 0.12149, 1.0761, 0.977055, - 0.0718094, 0.0975306, 1.09336, 0.982527, - 0.0534213, 0.0730217, 1.10878, 0.989001, - 0.0355579, 0.0483366, 1.12285, 0.99512, - 0.0176383, 0.023938, 1.13548, 1.00007, 0.000368831, - 0.000211581, 1.14744, 0.651047, - 9.60845e-06, 0.484101, 1.2922e-05, 0.644145, - 0.000241347, 0.478968, 0.000324578, 0.64396, - 0.000965142, 0.478831, 0.00129798, 0.64396, - 0.00217154, 0.47883, 0.00292046, 0.643968, - 0.00386049, 0.478835, 0.00519202, 0.643974, - 0.00603186, 0.478838, 0.0081128, 0.643977, - 0.0086854, 0.478836, 0.011683, 0.643982, - 0.0118207, 0.478834, 0.0159031, 0.644024, - 0.0154374, 0.478856, 0.0207743, 0.644059, - 0.0195343, 0.478868, 0.0262975, 0.644122, - 0.0241103, 0.478896, 0.0324747, 0.644207, - 0.0291638, 0.478933, 0.039309, 0.64432, - 0.0346919, 0.478981, 0.0468029, 0.644481, - 0.0406919, 0.479053, 0.0549614, 0.644722, - 0.047159, 0.479169, 0.0637909, 0.645013, - 0.0540748, 0.479302, 0.0732974, 0.645503, - 0.0612001, 0.479541, 0.0834898, 0.646117, - 0.0687303, 0.479829, 0.0943873, 0.646707, - 0.0767846, 0.480061, 0.105991, 0.647431, - 0.0852465, 0.480343, 0.11831, 0.64831, - 0.0940719, 0.48066, 0.131348, 0.649486, - 0.103056, 0.481083, 0.14514, 0.650864, - 0.112261, 0.481528, 0.159676, 0.652604, - 0.121852, 0.482102, 0.174979, 0.654825, - 0.131505, 0.482813, 0.191079, 0.657876, - 0.141189, 0.483876, 0.207927, 0.661339, - 0.151239, 0.48499, 0.225586, 0.665463, - 0.161091, 0.486279, 0.243947, 0.670542, - 0.171235, 0.487968, 0.262957, 0.677361, - 0.181347, 0.49053, 0.282781, 0.685672, - 0.191679, 0.493862, 0.303311, 0.694551, - 0.201781, 0.49699, 0.324607, 0.703753, - 0.211164, 0.498884, 0.347916, 0.713703, - 0.219675, 0.500086, 0.372628, 0.725911, - 0.227836, 0.501554, 0.398694, 0.73862, - 0.23533, 0.502193, 0.425529, 0.752118, - 0.241786, 0.501811, 0.453209, 0.76579, - 0.247865, 0.500185, 0.481381, 0.779568, - 0.252696, 0.497159, 0.51011, 0.793991, - 0.256802, 0.492765, 0.539322, 0.808182, - 0.259942, 0.486827, 0.569078, 0.821698, - 0.261703, 0.478386, 0.598818, 0.836009, - 0.262006, 0.468772, 0.629762, 0.849824, - 0.260333, 0.456352, 0.661366, 0.863888, - 0.257398, 0.442533, 0.69295, 0.876585, - 0.253264, 0.426573, 0.723608, 0.888665, - 0.248026, 0.408964, 0.754378, 0.899537, - 0.241487, 0.389677, 0.784761, 0.9094, - 0.233463, 0.368516, 0.814688, 0.920166, - 0.223397, 0.346624, 0.845009, 0.928899, - 0.21255, 0.322717, 0.874431, 0.937156, - 0.200869, 0.298698, 0.902922, 0.943861, - 0.188387, 0.273491, 0.931356, 0.949557, - 0.174341, 0.247866, 0.958854, 0.955862, - 0.158994, 0.222496, 0.986098, 0.961721, - 0.143664, 0.197522, 1.01229, 0.965976, - 0.127412, 0.17302, 1.03571, 0.968652, - 0.109798, 0.148954, 1.05699, 0.971084, - 0.0916787, 0.125044, 1.07587, 0.975584, - 0.0739634, 0.100577, 1.09372, 0.98122, - 0.055322, 0.0753666, 1.10948, 0.988253, - 0.0366825, 0.0498899, 1.12394, 0.99482, - 0.0180389, 0.024611, 1.13694, 1.00001, 0.000229839, - 0.000188283, 1.14919, 0.613867, - 9.64198e-06, 0.479449, 1.23452e-05, 0.621485, - 0.000244534, 0.485399, 0.000313091, 0.621429, - 0.000978202, 0.485353, 0.00125245, 0.62112, - 0.00220004, 0.485114, 0.00281687, 0.621119, - 0.0039111, 0.485112, 0.00500783, 0.621122, - 0.00611091, 0.485112, 0.00782498, 0.621133, - 0.00879922, 0.485117, 0.0112687, 0.621152, - 0.0119756, 0.485125, 0.0153394, 0.621183, - 0.0156396, 0.485139, 0.0200382, 0.621227, - 0.0197898, 0.485158, 0.0253663, 0.621298, - 0.0244253, 0.485192, 0.0313261, 0.621388, - 0.0295441, 0.485233, 0.0379204, 0.621507, - 0.0351432, 0.485286, 0.0451523, 0.621693, - 0.0412198, 0.485378, 0.0530277, 0.621933, - 0.0477673, 0.485495, 0.0615522, 0.622232, - 0.0547574, 0.485635, 0.0707316, 0.622809, - 0.0619417, 0.485943, 0.0805883, 0.623407, - 0.069625, 0.486232, 0.0911267, 0.62406, - 0.077796, 0.486516, 0.102354, 0.624835, - 0.0863731, 0.486838, 0.114279, 0.625758, - 0.095251, 0.487188, 0.126902, 0.627043, - 0.104299, 0.487695, 0.140285, 0.628438, - 0.113724, 0.488163, 0.154397, 0.630325, - 0.123417, 0.488858, 0.169267, 0.632801, - 0.133137, 0.489754, 0.184941, 0.635784, - 0.143052, 0.490815, 0.20136, 0.639406, - 0.153132, 0.492048, 0.218643, 0.643872, - 0.163143, 0.49363, 0.236615, 0.6499, - 0.17333, 0.496009, 0.255449, 0.657201, - 0.183622, 0.498994, 0.275006, 0.666221, - 0.194019, 0.502888, 0.295354, 0.674419, - 0.204192, 0.505459, 0.316244, 0.683729, - 0.21406, 0.507771, 0.33849, 0.695584, - 0.222854, 0.510245, 0.363166, 0.708583, - 0.231315, 0.512293, 0.389071, 0.721233, - 0.238911, 0.512747, 0.415737, 0.735134, - 0.245657, 0.512482, 0.443331, 0.750179, - 0.251879, 0.511526, 0.471891, 0.765073, - 0.256911, 0.508935, 0.500892, 0.779794, - 0.261144, 0.504341, 0.530294, 0.794801, - 0.264316, 0.498515, 0.560144, 0.810339, - 0.266276, 0.491015, 0.590213, 0.824818, - 0.266981, 0.481126, 0.620865, 0.839375, - 0.265778, 0.468685, 0.652687, 0.853043, - 0.262748, 0.453925, 0.684759, 0.867335, - 0.258474, 0.437912, 0.716209, 0.88037, - 0.253187, 0.419648, 0.747508, 0.891711, - 0.246476, 0.39982, 0.77797, 0.902896, - 0.238735, 0.37879, 0.808586, 0.913601, - 0.22885, 0.355891, 0.838843, 0.923019, - 0.217656, 0.331773, 0.869014, 0.933432, - 0.205539, 0.307356, 0.898512, 0.939691, - 0.192595, 0.281321, 0.9269, 0.946938, - 0.178945, 0.255441, 0.955297, 0.952372, - 0.163587, 0.229013, 0.983231, 0.95909, - 0.147214, 0.203179, 1.00971, 0.963675, - 0.13064, 0.17792, 1.03438, 0.968247, - 0.113121, 0.152898, 1.05625, 0.97001, - 0.0945824, 0.128712, 1.07598, 0.974458, - 0.0755648, 0.103349, 1.094, 0.980168, - 0.0571998, 0.0776731, 1.1104, 0.987295, - 0.0377994, 0.0514445, 1.12491, 0.994432, - 0.0186417, 0.025429, 1.13851, 0.999975, 0.000542714, - 0.000282356, 1.15108, 0.592656, - 9.80249e-06, 0.486018, 1.19532e-05, 0.598467, - 0.000247275, 0.490781, 0.000301531, 0.597934, - 0.000988317, 0.490343, 0.00120517, 0.597903, - 0.00222366, 0.490319, 0.0027116, 0.597913, - 0.00395315, 0.490327, 0.00482077, 0.597919, - 0.00617653, 0.490329, 0.00753264, 0.597936, - 0.00889375, 0.490339, 0.0108478, 0.597956, - 0.0121043, 0.490347, 0.0147668, 0.597992, - 0.0158073, 0.490365, 0.0192905, 0.598032, - 0.0200017, 0.490382, 0.0244204, 0.598109, - 0.0246865, 0.49042, 0.0301593, 0.598215, - 0.0298594, 0.490474, 0.03651, 0.59833, - 0.0355167, 0.490524, 0.0434757, 0.598525, - 0.0416559, 0.490624, 0.0510629, 0.598778, - 0.0482692, 0.490753, 0.0592781, 0.599135, - 0.0553114, 0.49094, 0.0681304, 0.599802, - 0.062542, 0.491328, 0.0776467, 0.600361, - 0.0703638, 0.491598, 0.0878184, 0.60101, - 0.0786256, 0.491882, 0.0986573, 0.601811, - 0.0872962, 0.492232, 0.11018, 0.602861, - 0.0962284, 0.492684, 0.1224, 0.604167, - 0.10538, 0.493213, 0.135354, 0.605693, - 0.114896, 0.493799, 0.149034, 0.607682, - 0.124654, 0.494576, 0.163469, 0.610672, - 0.13456, 0.4959, 0.178747, 0.613313, - 0.144581, 0.496713, 0.194723, 0.617603, - 0.154703, 0.498499, 0.211617, 0.622174, - 0.16489, 0.500188, 0.229183, 0.628855, - 0.175164, 0.503072, 0.247786, 0.636963, - 0.185565, 0.506798, 0.267116, 0.644866, - 0.195911, 0.509719, 0.28702, 0.653741, - 0.206104, 0.512776, 0.307763, 0.664942, - 0.216447, 0.516812, 0.329631, 0.67633, - 0.22552, 0.519181, 0.353515, 0.690012, - 0.234316, 0.521681, 0.379226, 0.704243, - 0.242032, 0.523129, 0.405901, 0.719396, - 0.249172, 0.523768, 0.433585, 0.734471, - 0.255543, 0.522541, 0.462085, 0.750539, - 0.260697, 0.520217, 0.491233, 0.766365, - 0.26501, 0.516293, 0.521094, 0.781677, - 0.268409, 0.509708, 0.551014, 0.797132, - 0.270399, 0.501944, 0.581463, 0.812655, - 0.271247, 0.492025, 0.612402, 0.828592, - 0.270708, 0.480424, 0.643798, 0.844044, - 0.268085, 0.465955, 0.67682, 0.857305, - 0.263459, 0.448425, 0.708496, 0.87114, - 0.258151, 0.430243, 0.74046, 0.884936, - 0.251171, 0.410578, 0.771583, 0.895772, - 0.243305, 0.38862, 0.802234, 0.906961, - 0.234037, 0.365214, 0.833179, 0.917775, - 0.222714, 0.34116, 0.86353, 0.927883, - 0.210175, 0.31572, 0.893557, 0.936617, - 0.196925, 0.289159, 0.922976, 0.943384, - 0.182788, 0.261996, 0.951606, 0.949713, - 0.167965, 0.235324, 0.979958, 0.955818, - 0.151109, 0.208408, 1.00765, 0.961344, - 0.133834, 0.182591, 1.03329, 0.965469, - 0.115987, 0.156958, 1.0557, 0.968693, - 0.09746, 0.132239, 1.07583, 0.973165, - 0.0778514, 0.106195, 1.09451, 0.979387, - 0.0585067, 0.0797669, 1.11137, 0.98671, - 0.0390409, 0.0530263, 1.12643, 0.994093, - 0.019408, 0.0263163, 1.14016, 1.00002, 0.000540029, - 0.000194487, 1.15299, 0.574483, - 9.89066e-06, 0.494533, 1.14896e-05, 0.574478, - 0.000249127, 0.494528, 0.000289403, 0.574607, - 0.000996811, 0.494637, 0.00115797, 0.574396, - 0.00224241, 0.494458, 0.00260498, 0.574377, - 0.00398632, 0.49444, 0.00463102, 0.574386, - 0.00622836, 0.494445, 0.00723623, 0.574401, - 0.0089683, 0.494453, 0.010421, 0.574419, - 0.0122056, 0.49446, 0.0141859, 0.574459, - 0.0159396, 0.494481, 0.0185322, 0.574525, - 0.0201692, 0.49452, 0.0234617, 0.574587, - 0.0248924, 0.494547, 0.0289762, 0.574697, - 0.0301074, 0.494604, 0.0350797, 0.574853, - 0.0358114, 0.494688, 0.0417767, 0.575027, - 0.041999, 0.494772, 0.0490718, 0.575294, - 0.0486618, 0.494915, 0.0569728, 0.575733, - 0.0557148, 0.495173, 0.0654955, 0.576356, - 0.0630489, 0.495537, 0.0746612, 0.576944, - 0.0709285, 0.495836, 0.0844615, 0.57765, - 0.0792723, 0.496177, 0.0949142, 0.578491, - 0.0880167, 0.496563, 0.10603, 0.579639, - 0.0969462, 0.497096, 0.117841, 0.580989, - 0.10622, 0.497684, 0.130367, 0.582587, - 0.115861, 0.498337, 0.143609, 0.584951, - 0.125605, 0.499414, 0.157625, 0.587602, - 0.135608, 0.500518, 0.172413, 0.59076, - 0.145742, 0.501767, 0.187999, 0.594992, - 0.155934, 0.503542, 0.20445, 0.600656, - 0.166303, 0.506135, 0.221764, 0.607816, - 0.176681, 0.509542, 0.24002, 0.61522, - 0.187071, 0.51263, 0.258992, 0.623702, - 0.197465, 0.516021, 0.278773, 0.634192, - 0.207816, 0.520422, 0.299377, 0.644936, - 0.218183, 0.524073, 0.320802, 0.657888, - 0.2278, 0.528049, 0.34384, 0.670666, - 0.236747, 0.52986, 0.36916, 0.685626, - 0.24484, 0.531892, 0.395867, 0.701304, - 0.252071, 0.532727, 0.423488, 0.717727, - 0.258714, 0.532146, 0.452201, 0.733914, - 0.264211, 0.529883, 0.481579, 0.750529, - 0.26859, 0.5259, 0.511558, 0.76747, - 0.272046, 0.51999, 0.542042, 0.785189, - 0.274225, 0.513083, 0.572799, 0.800954, - 0.275189, 0.502936, 0.603816, 0.816962, - 0.274946, 0.490921, 0.635461, 0.83336, - 0.272695, 0.47684, 0.6676, 0.848143, - 0.268223, 0.459405, 0.70051, 0.861818, - 0.262768, 0.440319, 0.732902, 0.876828, - 0.255872, 0.420123, 0.765084, 0.889312, - 0.247703, 0.398379, 0.796391, 0.900412, - 0.238381, 0.374496, 0.827333, 0.912251, - 0.227783, 0.349874, 0.858385, 0.921792, - 0.214832, 0.323181, 0.888652, 0.931273, - 0.200949, 0.296624, 0.917763, 0.940295, - 0.186537, 0.269211, 0.947878, 0.946812, - 0.171538, 0.241447, 0.977016, 0.953588, - 0.155254, 0.213829, 1.00501, 0.958841, - 0.137156, 0.186807, 1.03179, 0.963746, - 0.118699, 0.160706, 1.05502, 0.966468, - 0.0998358, 0.135504, 1.07568, 0.971178, - 0.0805186, 0.109131, 1.09479, 0.97831, - 0.0599348, 0.0818293, 1.1123, 0.985886, - 0.0399661, 0.0545872, 1.12771, 0.994021, - 0.0198682, 0.0269405, 1.14186, 1.00009, 0.000271022, - 0.00012989, 1.15514, 0.538716, - 9.90918e-06, 0.486732, 1.09675e-05, 0.550656, - 0.000250642, 0.497518, 0.000277412, 0.55057, - 0.00100265, 0.497441, 0.00110974, 0.550903, - 0.00225672, 0.497733, 0.00249779, 0.550568, - 0.00401046, 0.497438, 0.00443906, 0.550574, - 0.00626613, 0.49744, 0.00693637, 0.550591, - 0.0090226, 0.497449, 0.00998921, 0.550623, - 0.0122795, 0.497469, 0.0135984, 0.550667, - 0.0160361, 0.497495, 0.0177654, 0.550724, - 0.0202908, 0.497526, 0.0224915, 0.550792, - 0.0250421, 0.497557, 0.0277795, 0.550918, - 0.0302878, 0.49763, 0.0336334, 0.551058, - 0.0360241, 0.497701, 0.0400573, 0.551276, - 0.0422473, 0.497824, 0.0470585, 0.551551, - 0.0489441, 0.497977, 0.0546433, 0.552074, - 0.0559596, 0.498312, 0.0628367, 0.552681, - 0.0633978, 0.498679, 0.071646, 0.553324, - 0.0713176, 0.499031, 0.0810746, 0.554011, - 0.0797268, 0.499365, 0.091129, 0.55488, - 0.0885238, 0.499779, 0.101837, 0.556171, - 0.0974417, 0.500444, 0.113239, 0.557498, - 0.106841, 0.501025, 0.125316, 0.559299, - 0.116533, 0.501864, 0.138128, 0.561647, - 0.126298, 0.502967, 0.151695, 0.564347, - 0.136388, 0.504129, 0.16604, 0.567863, - 0.146576, 0.505713, 0.181207, 0.572569, - 0.156832, 0.507953, 0.197259, 0.578919, - 0.167323, 0.511186, 0.214258, 0.585387, - 0.177712, 0.514042, 0.232038, 0.593134, - 0.188184, 0.517484, 0.250733, 0.603295, - 0.198717, 0.522345, 0.270454, 0.613854, - 0.209177, 0.526751, 0.290807, 0.626092, - 0.219644, 0.531595, 0.312202, 0.637868, - 0.229494, 0.534721, 0.334435, 0.652458, - 0.238718, 0.538304, 0.359184, 0.666985, - 0.247061, 0.539875, 0.385637, 0.683301, - 0.254652, 0.541042, 0.41328, 0.69998, - 0.261376, 0.540735, 0.441903, 0.717824, - 0.267085, 0.539139, 0.471609, 0.734617, - 0.271465, 0.534958, 0.501446, 0.753663, - 0.27528, 0.53032, 0.532571, 0.770512, - 0.277617, 0.522134, 0.563641, 0.787356, - 0.278525, 0.51206, 0.595067, 0.806252, - 0.278512, 0.50119, 0.627226, 0.822061, - 0.277023, 0.486791, 0.659402, 0.838959, - 0.273175, 0.470467, 0.692874, 0.85379, - 0.267238, 0.450688, 0.725702, 0.868268, - 0.260327, 0.429741, 0.75832, 0.881994, - 0.251946, 0.407223, 0.790189, 0.893885, - 0.242432, 0.383214, 0.821625, 0.905118, - 0.231904, 0.357297, 0.853011, 0.916045, - 0.219545, 0.330733, 0.883773, 0.927614, - 0.205378, 0.303916, 0.914435, 0.936005, - 0.190388, 0.275941, 0.944502, 0.944533, - 0.1749, 0.247493, 0.974439, 0.950758, - 0.158588, 0.218996, 1.00286, 0.957078, - 0.141027, 0.191559, 1.0304, 0.962448, - 0.121507, 0.164457, 1.05466, 0.964993, - 0.102068, 0.138636, 1.0761, 0.970017, - 0.0822598, 0.111861, 1.09541, 0.97661, - 0.062033, 0.0843438, 1.11317, 0.985073, - 0.0409832, 0.0558496, 1.12911, 0.993515, - 0.020146, 0.0275331, 1.1438, 1.00006, 0.00027329, - 0.000107883, 1.15736, 0.525324, - 9.99341e-06, 0.498153, 1.05385e-05, 0.526513, - 0.000251605, 0.499277, 0.000265329, 0.526517, - 0.00100641, 0.499282, 0.0010613, 0.526588, - 0.00226466, 0.499337, 0.00238823, 0.526539, - 0.0040255, 0.499302, 0.00424535, 0.526547, - 0.00628954, 0.499306, 0.00663364, 0.526561, - 0.00905628, 0.499313, 0.00955337, 0.526593, - 0.0123253, 0.499334, 0.0130054, 0.526642, - 0.0160957, 0.499365, 0.0169911, 0.5267, - 0.0203661, 0.499396, 0.0215122, 0.526792, - 0.0251347, 0.499451, 0.0265718, 0.526904, - 0.0303985, 0.499511, 0.0321732, 0.527079, - 0.0361554, 0.499617, 0.0383231, 0.527285, - 0.0423982, 0.499731, 0.045026, 0.527602, - 0.0491121, 0.499924, 0.0522936, 0.528166, - 0.0561127, 0.500306, 0.0601528, 0.52879, - 0.0635988, 0.5007, 0.0686059, 0.529421, - 0.071581, 0.501048, 0.0776518, 0.530144, - 0.0799854, 0.501421, 0.0873148, 0.531062, - 0.0888032, 0.501884, 0.0976084, 0.532374, - 0.0977643, 0.50259, 0.108588, 0.533828, - 0.107197, 0.50329, 0.120234, 0.53581, - 0.116887, 0.504312, 0.132602, 0.538063, - 0.126755, 0.505365, 0.145721, 0.5409, - 0.136819, 0.506668, 0.159617, 0.544882, - 0.147117, 0.508731, 0.174369, 0.550238, - 0.157446, 0.511601, 0.190028, 0.556038, - 0.167988, 0.514431, 0.206587, 0.563031, - 0.178364, 0.517808, 0.224046, 0.571543, - 0.189007, 0.521937, 0.242503, 0.582255, - 0.199546, 0.527415, 0.261977, 0.59272, - 0.210084, 0.531682, 0.282162, 0.605648, - 0.220448, 0.537123, 0.303426, 0.61785, - 0.230593, 0.540664, 0.325323, 0.632223, - 0.240238, 0.544467, 0.348993, 0.648819, - 0.24887, 0.547594, 0.375462, 0.665825, - 0.256657, 0.54912, 0.403024, 0.683389, - 0.263711, 0.549294, 0.431773, 0.701495, - 0.269666, 0.547649, 0.461494, 0.719197, - 0.274169, 0.543786, 0.491623, 0.737906, - 0.278124, 0.538644, 0.522994, 0.756652, - 0.280632, 0.531057, 0.554775, 0.775279, - 0.281741, 0.521972, 0.586441, 0.792688, - 0.281652, 0.509613, 0.618596, 0.811894, - 0.280345, 0.496497, 0.651462, 0.827938, - 0.277128, 0.47968, 0.684023, 0.844837, - 0.271646, 0.460688, 0.718024, 0.859239, - 0.264397, 0.438872, 0.751207, 0.874088, - 0.256144, 0.41577, 0.784232, 0.887693, - 0.246311, 0.391369, 0.816191, 0.899402, - 0.235497, 0.365872, 0.847828, 0.910973, - 0.223631, 0.338618, 0.87934, 0.92204, - 0.209874, 0.310803, 0.910325, 0.930987, - 0.194265, 0.281802, 0.940695, 0.94, - 0.178125, 0.252836, 0.970958, 0.948018, - 0.161479, 0.224239, 1.00078, 0.955141, - 0.144038, 0.195857, 1.0288, 0.960513, - 0.124915, 0.168487, 1.05371, 0.963964, - 0.104284, 0.141495, 1.07596, 0.968713, - 0.0838732, 0.114437, 1.09628, 0.975524, - 0.0635579, 0.0863105, 1.11448, 0.98431, - 0.042291, 0.0574774, 1.13069, 0.992916, - 0.0209131, 0.0284343, 1.14568, 0.999926, 0.000743097, - 0.000379265, 1.15955, 0.501042, - 9.98428e-06, 0.498726, 1.00306e-05, 0.502992, - 0.000252112, 0.500665, 0.000253283, 0.502417, - 0.00100791, 0.500092, 0.00101259, 0.502965, - 0.00226919, 0.500621, 0.00227978, 0.502318, - 0.00403109, 0.499994, 0.00405011, 0.502333, - 0.00629832, 0.500005, 0.00632868, 0.502362, - 0.00906907, 0.500027, 0.00911446, 0.502369, - 0.0123423, 0.500023, 0.0124078, 0.50243, - 0.0161178, 0.500066, 0.016211, 0.502493, - 0.0203937, 0.500103, 0.0205256, 0.502592, - 0.0251684, 0.500166, 0.0253548, 0.502707, - 0.0304389, 0.50023, 0.0307029, 0.502881, - 0.0362015, 0.500335, 0.0365753, 0.503124, - 0.0424507, 0.500488, 0.0429798, 0.503443, - 0.0491582, 0.500686, 0.0499268, 0.504083, - 0.0561476, 0.501155, 0.0574541, 0.504668, - 0.0636846, 0.501524, 0.0655408, 0.505319, - 0.0716834, 0.501904, 0.0742072, 0.50609, - 0.0800925, 0.502321, 0.0834699, 0.507122, - 0.0888425, 0.502896, 0.0933603, 0.508414, - 0.097855, 0.503603, 0.10391, 0.509955, - 0.107304, 0.504416, 0.115113, 0.512061, - 0.116921, 0.505565, 0.127054, 0.514419, - 0.12689, 0.506732, 0.139709, 0.517529, - 0.136934, 0.508338, 0.153173, 0.522085, - 0.147327, 0.510987, 0.167528, 0.526986, - 0.157612, 0.513527, 0.182708, 0.533122, - 0.168213, 0.516717, 0.198881, 0.540807, - 0.178688, 0.520832, 0.215986, 0.550687, - 0.189511, 0.52632, 0.234335, 0.560567, - 0.199998, 0.531009, 0.253375, 0.571698, - 0.210652, 0.535839, 0.273499, 0.584364, - 0.220917, 0.541091, 0.294355, 0.599066, - 0.23137, 0.546875, 0.316525, 0.614148, - 0.241206, 0.551306, 0.339671, 0.631157, - 0.250379, 0.555187, 0.36531, 0.647919, - 0.258397, 0.556595, 0.392767, 0.666112, - 0.265528, 0.556949, 0.421397, 0.686158, - 0.271827, 0.556617, 0.451433, 0.704838, - 0.27674, 0.552975, 0.482131, 0.723957, - 0.280733, 0.547814, 0.513458, 0.74262, - 0.283359, 0.53997, 0.545446, 0.762009, - 0.284541, 0.530422, 0.57775, 0.781314, - 0.284507, 0.518546, 0.610434, 0.799116, - 0.283309, 0.504178, 0.643178, 0.817604, - 0.280378, 0.48843, 0.676248, 0.83459, - 0.275619, 0.469457, 0.709698, 0.850974, - 0.26856, 0.447698, 0.744245, 0.866747, - 0.260094, 0.424791, 0.777695, 0.881412, - 0.249929, 0.399913, 0.810392, 0.8936, - 0.239137, 0.37308, 0.842872, 0.905943, - 0.226818, 0.345705, 0.874677, 0.916408, - 0.213699, 0.31706, 0.906257, 0.927215, - 0.198428, 0.288444, 0.936881, 0.935625, - 0.181643, 0.258329, 0.96795, 0.944076, - 0.164386, 0.228488, 0.998216, 0.951229, - 0.146339, 0.199763, 1.02689, 0.958793, - 0.127709, 0.172153, 1.0535, 0.963219, - 0.107244, 0.144989, 1.07646, 0.967562, - 0.0857764, 0.11685, 1.09675, 0.974866, - 0.0645377, 0.0880571, 1.11576, 0.983353, - 0.0431732, 0.0587352, 1.13227, 0.992503, - 0.0218356, 0.0294181, 1.1478, 1.00003, 0.000605203, - 0.000231013, 1.16207, 0.482935, - 1.01177e-05, 0.504695, 9.68142e-06, 0.477554, - 0.000251521, 0.499071, 0.000240676, 0.477904, - 0.00100683, 0.499436, 0.00096342, 0.478368, - 0.00226636, 0.499899, 0.0021687, 0.477977, - 0.00402719, 0.499513, 0.00385384, 0.477993, - 0.00629226, 0.499525, 0.0060221, 0.478011, - 0.00906011, 0.499536, 0.00867289, 0.478051, - 0.0123305, 0.499566, 0.0118074, 0.478089, - 0.016102, 0.499587, 0.0154269, 0.478171, - 0.0203736, 0.499645, 0.0195341, 0.478254, - 0.025143, 0.499692, 0.0241318, 0.47839, - 0.0304071, 0.499779, 0.0292247, 0.478588, - 0.0361631, 0.499911, 0.0348196, 0.478812, - 0.0424023, 0.500046, 0.0409231, 0.479208, - 0.0490724, 0.500326, 0.047552, 0.479841, - 0.0560722, 0.500805, 0.0547377, 0.480392, - 0.0636125, 0.501152, 0.0624607, 0.481068, - 0.0716134, 0.501561, 0.0707473, 0.481898, - 0.0800062, 0.502054, 0.0796118, 0.483022, - 0.0886568, 0.502728, 0.0890974, 0.484332, - 0.0977553, 0.503479, 0.0992099, 0.486126, - 0.107173, 0.504546, 0.10999, 0.488066, - 0.11677, 0.50557, 0.121476, 0.490521, - 0.126725, 0.506849, 0.133672, 0.494232, - 0.136793, 0.50911, 0.146731, 0.498302, - 0.147116, 0.511345, 0.160577, 0.503565, - 0.157446, 0.514344, 0.175335, 0.510902, - 0.168121, 0.518824, 0.191207, 0.519263, - 0.178799, 0.523666, 0.208058, 0.528204, - 0.189407, 0.528296, 0.225875, 0.538854, - 0.200145, 0.533724, 0.244782, 0.551278, - 0.210701, 0.539833, 0.264753, 0.565222, - 0.221303, 0.546131, 0.285745, 0.579403, - 0.231688, 0.551496, 0.307592, 0.595469, - 0.241718, 0.556809, 0.330582, 0.610929, - 0.250992, 0.559641, 0.354995, 0.629433, - 0.259602, 0.562379, 0.382471, 0.648504, - 0.267038, 0.563676, 0.411126, 0.66756, - 0.273388, 0.562092, 0.440924, 0.689143, - 0.278788, 0.560807, 0.472118, 0.709056, - 0.282783, 0.555701, 0.503774, 0.729855, - 0.285836, 0.548698, 0.536364, 0.748954, - 0.287078, 0.538544, 0.56895, 0.768373, - 0.287133, 0.526711, 0.601991, 0.78827, - 0.285839, 0.512511, 0.635403, 0.807465, - 0.283238, 0.496323, 0.668797, 0.825194, - 0.27906, 0.477638, 0.702584, 0.842203, - 0.272286, 0.456253, 0.736393, 0.857749, - 0.263854, 0.432412, 0.77096, 0.874799, - 0.253943, 0.407806, 0.80489, 0.887497, - 0.24237, 0.38033, 0.83771, 0.89966, - 0.230278, 0.352446, 0.870376, 0.911753, - 0.21646, 0.323268, 0.902256, 0.923011, - 0.202071, 0.294314, 0.933306, 0.932375, - 0.185519, 0.264104, 0.965177, 0.940537, - 0.167604, 0.234035, 0.996303, 0.948904, - 0.149068, 0.20412, 1.0261, 0.955263, - 0.129539, 0.175431, 1.05304, 0.960303, - 0.109932, 0.148116, 1.07617, 0.965512, - 0.0880572, 0.119693, 1.09742, 0.973466, - 0.0660548, 0.0901619, 1.11721, 0.98284, - 0.0439228, 0.0599875, 1.13436, 0.992216, - 0.0219588, 0.0298975, 1.15006, 0.999946, 0.000119402, - 2.08547e-05, 1.16471, 0.447827, - 1.00414e-05, 0.491543, 9.14833e-06, 0.454778, - 0.000251257, 0.499172, 0.00022891, 0.453519, - 0.00100342, 0.497787, 0.000914184, 0.45357, - 0.00225776, 0.497847, 0.00205701, 0.453578, - 0.00401371, 0.497855, 0.00365705, 0.45357, - 0.00627107, 0.497841, 0.00571453, 0.453598, - 0.00902968, 0.497864, 0.00823019, 0.453627, - 0.0122888, 0.497882, 0.0112049, 0.453684, - 0.0160475, 0.497923, 0.0146405, 0.453764, - 0.0203044, 0.49798, 0.0185394, 0.453866, - 0.0250576, 0.498049, 0.0229054, 0.453996, - 0.0303028, 0.49813, 0.0277424, 0.454196, - 0.0360379, 0.498267, 0.0330587, 0.454457, - 0.0422521, 0.498445, 0.0388613, 0.454926, - 0.0488393, 0.498812, 0.0451767, 0.455525, - 0.0558653, 0.499272, 0.0520153, 0.456074, - 0.0633772, 0.499625, 0.0593754, 0.456752, - 0.0713606, 0.500049, 0.0672751, 0.457648, - 0.07971, 0.500615, 0.0757447, 0.458849, - 0.0883032, 0.501399, 0.0848231, 0.46029, - 0.0974095, 0.502293, 0.0945135, 0.462, - 0.106729, 0.503301, 0.104848, 0.464121, - 0.116354, 0.504533, 0.115884, 0.466889, - 0.126214, 0.506172, 0.127652, 0.470744, - 0.136324, 0.508667, 0.14024, 0.47488, - 0.146595, 0.510995, 0.153673, 0.480845, - 0.157027, 0.514832, 0.168053, 0.488262, - 0.167658, 0.519506, 0.183508, 0.496547, - 0.178343, 0.524347, 0.199948, 0.506254, - 0.188916, 0.52983, 0.217503, 0.517961, - 0.199975, 0.536357, 0.236272, 0.531484, - 0.210624, 0.543641, 0.256096, 0.545496, - 0.221227, 0.550048, 0.277085, 0.559497, - 0.231568, 0.555076, 0.298615, 0.575752, - 0.241698, 0.560541, 0.321547, 0.591999, - 0.251172, 0.564156, 0.345602, 0.610654, - 0.260178, 0.567607, 0.371851, 0.630484, - 0.268094, 0.56923, 0.40076, 0.651807, - 0.274661, 0.569779, 0.430801, 0.67239, - 0.280331, 0.566791, 0.461939, 0.693024, - 0.284501, 0.562007, 0.493854, 0.715473, - 0.287852, 0.555791, 0.526992, 0.736323, - 0.28929, 0.546345, 0.560102, 0.755771, - 0.289405, 0.534, 0.593543, 0.775424, - 0.2881, 0.519114, 0.627256, 0.795447, - 0.285562, 0.502543, 0.661464, 0.815319, - 0.281416, 0.484773, 0.695206, 0.831769, - 0.275523, 0.463445, 0.729044, 0.849464, - 0.267516, 0.440269, 0.764069, 0.866775, - 0.257584, 0.415049, 0.799089, 0.881252, - 0.245817, 0.388049, 0.831948, 0.894209, - 0.233127, 0.35889, 0.865526, 0.906922, - 0.219579, 0.329915, 0.89818, 0.919686, - 0.204491, 0.300441, 0.930013, 0.929044, - 0.188962, 0.269445, 0.962061, 0.938393, - 0.171079, 0.238402, 0.994214, 0.94661, - 0.15199, 0.208204, 1.02533, 0.953095, - 0.131953, 0.178653, 1.0529, 0.958644, - 0.111233, 0.150684, 1.0771, 0.963925, - 0.0903098, 0.122359, 1.09855, 0.971995, - 0.0680505, 0.0923342, 1.11874, 0.981658, - 0.0448512, 0.0614195, 1.13635, 0.991649, - 0.0221931, 0.0303582, 1.15238, 0.999985, 0.000393403, - 0.000111086, 1.16772, 0.396806, - 9.71563e-06, 0.457671, 8.42355e-06, 0.429186, - 0.000249421, 0.495017, 0.00021625, 0.429324, - 0.000998052, 0.495173, 0.000865322, 0.429175, - 0.00224487, 0.494999, 0.00194637, 0.429129, - 0.00399041, 0.494952, 0.00346004, 0.429153, - 0.00623476, 0.494974, 0.00540684, 0.429168, - 0.0089773, 0.494983, 0.00778714, 0.429207, - 0.0122175, 0.495012, 0.0106022, 0.429257, - 0.0159542, 0.495047, 0.0138535, 0.429338, - 0.0201864, 0.495106, 0.0175443, 0.429431, - 0.0249104, 0.495165, 0.0216774, 0.429587, - 0.0301252, 0.495279, 0.0262594, 0.429796, - 0.0358249, 0.495432, 0.0312968, 0.430065, - 0.0419972, 0.495621, 0.0367985, 0.430588, - 0.0485144, 0.496061, 0.042798, 0.43113, - 0.0555028, 0.496472, 0.0492914, 0.431743, - 0.0629852, 0.496904, 0.0562907, 0.432448, - 0.0709256, 0.497369, 0.0638056, 0.433414, - 0.0791942, 0.498032, 0.071885, 0.434638, - 0.0877346, 0.498854, 0.0805517, 0.43611, - 0.0968056, 0.499812, 0.0898047, 0.437859, - 0.106002, 0.500891, 0.0997142, 0.440017, - 0.115648, 0.502198, 0.110289, 0.443236, - 0.125427, 0.504389, 0.121644, 0.44697, - 0.135492, 0.506809, 0.133769, 0.451689, - 0.145746, 0.509858, 0.146787, 0.45811, - 0.156219, 0.514247, 0.160793, 0.465305, - 0.166834, 0.518816, 0.175791, 0.474085, - 0.177546, 0.524331, 0.191906, 0.484808, - 0.188262, 0.53104, 0.209199, 0.49732, - 0.199346, 0.538511, 0.227825, 0.509693, - 0.209951, 0.544554, 0.247269, 0.524367, - 0.220533, 0.551616, 0.267978, 0.539228, - 0.231082, 0.557368, 0.289672, 0.55644, - 0.241342, 0.563782, 0.31268, 0.574204, - 0.250964, 0.568851, 0.33651, 0.593388, - 0.260306, 0.57312, 0.362219, 0.613358, - 0.268667, 0.574916, 0.390322, 0.634512, - 0.275591, 0.575053, 0.420478, 0.65563, - 0.281328, 0.572404, 0.451614, 0.678265, - 0.285948, 0.568893, 0.484112, 0.70011, - 0.289408, 0.561878, 0.517348, 0.723005, - 0.291328, 0.55359, 0.551355, 0.743744, - 0.291418, 0.541099, 0.585109, 0.763949, - 0.290252, 0.526489, 0.619487, 0.784186, - 0.287648, 0.509496, 0.65404, 0.804304, - 0.283782, 0.491484, 0.688649, 0.823629, - 0.278067, 0.470517, 0.723133, 0.84094, - 0.270588, 0.44705, 0.757163, 0.857852, - 0.261188, 0.421252, 0.792816, 0.874934, - 0.249313, 0.394191, 0.827248, 0.888709, - 0.236492, 0.365359, 0.861074, 0.902589, - 0.222185, 0.336016, 0.894417, 0.914201, - 0.207314, 0.30527, 0.926825, 0.925978, - 0.191146, 0.274532, 0.9595, 0.93512, - 0.174135, 0.243393, 0.991583, 0.943656, - 0.155231, 0.212414, 1.02356, 0.951719, - 0.134403, 0.182005, 1.05239, 0.957164, - 0.113023, 0.153043, 1.07754, 0.962656, - 0.0914493, 0.124186, 1.09984, 0.970695, - 0.0694179, 0.0941654, 1.12, 0.980749, - 0.0466199, 0.0629671, 1.13849, 0.991205, - 0.0227032, 0.0311146, 1.15494, 0.999884, 0.000632388, - 0.000254483, 1.1706, 0.379821, - 9.57289e-06, 0.460637, 7.89337e-06, 0.405188, - 0.000247483, 0.491396, 0.000204064, 0.404796, - 0.000989434, 0.490914, 0.000815853, 0.40483, - 0.00222607, 0.490949, 0.00183559, 0.40473, - 0.00395723, 0.49084, 0.00326332, 0.404731, - 0.00618287, 0.490836, 0.00509945, 0.404768, - 0.00890258, 0.490871, 0.00734463, 0.404791, - 0.0121156, 0.490883, 0.00999992, 0.404857, - 0.0158214, 0.490938, 0.0130676, 0.404943, - 0.0200178, 0.491004, 0.0165503, 0.405059, - 0.0247027, 0.491093, 0.0204521, 0.405213, - 0.0298729, 0.491205, 0.0247788, 0.405399, - 0.0355226, 0.491333, 0.0295373, 0.405731, - 0.0416352, 0.491604, 0.034741, 0.406303, - 0.0480807, 0.492116, 0.0404255, 0.406814, - 0.0550458, 0.492506, 0.0465732, 0.407404, - 0.0624652, 0.492926, 0.0532058, 0.408149, - 0.0702958, 0.493442, 0.0603442, 0.409128, - 0.0784623, 0.494136, 0.0680297, 0.410408, - 0.087007, 0.495054, 0.0762786, 0.411813, - 0.0959639, 0.495962, 0.0851046, 0.413735, - 0.105075, 0.497257, 0.0945878, 0.416137, - 0.114646, 0.498882, 0.104725, 0.41934, - 0.124394, 0.501132, 0.11563, 0.423326, - 0.134328, 0.503883, 0.127325, 0.428419, - 0.14458, 0.50747, 0.139911, 0.43484, - 0.154979, 0.511964, 0.153481, 0.442641, - 0.165628, 0.517328, 0.168114, 0.452511, - 0.176365, 0.524258, 0.183995, 0.463473, - 0.187298, 0.531248, 0.200953, 0.475564, - 0.198244, 0.538367, 0.219176, 0.488664, - 0.208938, 0.545175, 0.238514, 0.504073, - 0.219599, 0.553227, 0.259129, 0.520832, - 0.230378, 0.560653, 0.280997, 0.538455, - 0.240703, 0.567523, 0.303821, 0.55709, - 0.250548, 0.573287, 0.327948, 0.576646, - 0.259964, 0.577795, 0.353362, 0.596705, - 0.268721, 0.580077, 0.380336, 0.618053, - 0.276054, 0.58018, 0.4101, 0.640303, - 0.282176, 0.578747, 0.44161, 0.662365, - 0.286931, 0.574294, 0.474106, 0.684542, - 0.290521, 0.567035, 0.507549, 0.707984, - 0.292672, 0.558687, 0.541853, 0.730913, - 0.293189, 0.547606, 0.576581, 0.752948, - 0.292199, 0.533471, 0.61172, 0.773452, - 0.289508, 0.516395, 0.646339, 0.794715, - 0.285716, 0.497873, 0.682131, 0.814251, - 0.280051, 0.476845, 0.716396, 0.833057, - 0.272873, 0.453449, 0.751503, 0.84959, - 0.263982, 0.427857, 0.786085, 0.867022, - 0.252745, 0.400335, 0.821355, 0.882277, - 0.239655, 0.371304, 0.85646, 0.895375, - 0.225386, 0.340397, 0.890828, 0.909347, - 0.209587, 0.310005, 0.923532, 0.921885, - 0.193433, 0.2796, 0.956419, 0.932127, - 0.176135, 0.247276, 0.989445, 0.941869, - 0.157872, 0.216186, 1.02221, 0.949735, - 0.137577, 0.185602, 1.05195, 0.956617, - 0.115285, 0.155767, 1.07822, 0.961974, - 0.0928418, 0.126103, 1.10149, 0.96972, - 0.0700592, 0.0956758, 1.12207, 0.98012, - 0.0474671, 0.0643269, 1.1408, 0.990825, - 0.0238113, 0.0320863, 1.1577, 0.999876, 0.000381574, - 8.12203e-05, 1.17403, 0.367636, - 9.61342e-06, 0.469176, 7.53287e-06, 0.380377, - 0.000244772, 0.485434, 0.000191797, 0.380416, - 0.000978857, 0.485475, 0.000767015, 0.380376, - 0.00220165, 0.485435, 0.00172522, 0.380419, - 0.00391408, 0.485487, 0.00306734, 0.380438, - 0.00611549, 0.485505, 0.00479332, 0.380462, - 0.00880558, 0.485525, 0.00690391, 0.380496, - 0.0119837, 0.485551, 0.00940039, 0.38056, - 0.0156487, 0.485605, 0.0122848, 0.38064, - 0.0197988, 0.485666, 0.0155601, 0.380767, - 0.0244324, 0.48577, 0.0192313, 0.380909, - 0.0295444, 0.485871, 0.0233032, 0.381142, - 0.0351321, 0.48606, 0.0277861, 0.381472, - 0.0411535, 0.486336, 0.0326939, 0.382015, - 0.0475408, 0.486833, 0.0380565, 0.382523, - 0.0544395, 0.487231, 0.0438615, 0.383129, - 0.061784, 0.487683, 0.0501332, 0.383952, - 0.0695085, 0.488313, 0.0568996, 0.38498, - 0.0775819, 0.489077, 0.0641952, 0.386331, - 0.0860443, 0.490113, 0.0720324, 0.387788, - 0.0948406, 0.491099, 0.0804379, 0.389808, - 0.103899, 0.492566, 0.0894899, 0.39252, - 0.113313, 0.494601, 0.0992098, 0.395493, - 0.123007, 0.496619, 0.109641, 0.399826, - 0.132859, 0.499912, 0.120919, 0.405341, - 0.143077, 0.504061, 0.133107, 0.411932, - 0.153465, 0.508905, 0.146263, 0.420591, - 0.164108, 0.515482, 0.160544, 0.43101, - 0.174893, 0.523191, 0.176123, 0.441881, - 0.185839, 0.53026, 0.192757, 0.453919, - 0.196633, 0.537295, 0.210535, 0.468715, - 0.207611, 0.546156, 0.229886, 0.485182, - 0.218517, 0.555173, 0.250543, 0.501926, - 0.229249, 0.562728, 0.27221, 0.51785, - 0.239481, 0.567494, 0.294892, 0.536947, - 0.249395, 0.573889, 0.318987, 0.557115, - 0.259, 0.578831, 0.344348, 0.577966, - 0.268075, 0.582055, 0.371223, 0.599489, - 0.276115, 0.583307, 0.399834, 0.62479, - 0.282523, 0.583902, 0.431415, 0.647504, - 0.287663, 0.57953, 0.464301, 0.670601, - 0.291538, 0.573103, 0.498123, 0.693539, - 0.293842, 0.563731, 0.532662, 0.717385, - 0.294681, 0.553169, 0.567925, 0.741533, - 0.293717, 0.539908, 0.603502, 0.762142, - 0.291156, 0.521902, 0.639074, 0.783014, - 0.28719, 0.502815, 0.674439, 0.805158, - 0.281773, 0.482598, 0.710497, 0.823646, - 0.274682, 0.458949, 0.7456, 0.841879, - 0.266184, 0.433129, 0.781085, 0.859515, - 0.255682, 0.406064, 0.816, 0.875335, - 0.242849, 0.376509, 0.851074, 0.890147, - 0.228329, 0.345502, 0.886473, 0.903144, - 0.212491, 0.31428, 0.920751, 0.916618, - 0.195695, 0.282994, 0.954606, 0.927953, - 0.178267, 0.251091, 0.988402, 0.937414, - 0.159549, 0.219107, 1.02141, 0.946823, - 0.140022, 0.18896, 1.05167, 0.954651, - 0.118154, 0.158667, 1.07819, 0.959955, - 0.0946636, 0.128808, 1.1025, 0.96858, - 0.0711792, 0.0973787, 1.12391, 0.97938, - 0.0475046, 0.0650965, 1.14322, 0.990498, - 0.024059, 0.0326267, 1.16077, 0.999844, - 5.12408e-05, 0.000112444, 1.17727, 0.316912, - 9.34977e-06, 0.425996, 6.95559e-06, 0.356423, - 0.000241372, 0.479108, 0.000179562, 0.356272, - 0.000965292, 0.478897, 0.00071811, 0.356262, - 0.00217182, 0.478894, 0.00161574, 0.356265, - 0.00386092, 0.478895, 0.00287261, 0.356278, - 0.0060324, 0.478905, 0.00448907, 0.356293, - 0.00868565, 0.478914, 0.00646572, 0.356346, - 0.0118207, 0.478965, 0.00880438, 0.356395, - 0.0154355, 0.479001, 0.0115066, 0.356484, - 0.019529, 0.479075, 0.0145762, 0.356609, - 0.0240991, 0.47918, 0.018018, 0.356766, - 0.0291413, 0.479305, 0.0218379, 0.357009, - 0.0346498, 0.479512, 0.0260454, 0.357424, - 0.0405462, 0.479909, 0.0306657, 0.357899, - 0.0468825, 0.480337, 0.0357054, 0.358424, - 0.0536887, 0.480771, 0.0411728, 0.359041, - 0.0609416, 0.481242, 0.0470841, 0.359903, - 0.0685239, 0.481943, 0.0534831, 0.360932, - 0.0764883, 0.482741, 0.0603795, 0.362196, - 0.0848364, 0.483688, 0.0678028, 0.363847, - 0.0935002, 0.484947, 0.0758086, 0.365972, - 0.102471, 0.486588, 0.0844173, 0.368741, - 0.111751, 0.488787, 0.0937199, 0.372146, - 0.121334, 0.491405, 0.103732, 0.377114, - 0.131147, 0.495604, 0.114608, 0.38226, - 0.141213, 0.499436, 0.126345, 0.389609, - 0.151632, 0.505334, 0.139116, 0.397925, - 0.162073, 0.51168, 0.152995, 0.407824, - 0.172819, 0.518876, 0.168071, 0.420014, - 0.183929, 0.527639, 0.184495, 0.434266, - 0.195032, 0.537588, 0.20232, 0.447352, - 0.205792, 0.544379, 0.221189, 0.463726, - 0.216704, 0.553422, 0.241616, 0.481406, - 0.227531, 0.562074, 0.263298, 0.498707, - 0.238017, 0.568227, 0.286116, 0.518039, - 0.247936, 0.574473, 0.3101, 0.538277, - 0.257437, 0.579191, 0.335401, 0.561166, - 0.266829, 0.584807, 0.362246, 0.583189, - 0.275329, 0.586476, 0.390609, 0.606024, - 0.28234, 0.585578, 0.420998, 0.632419, - 0.287924, 0.584496, 0.454357, 0.656128, - 0.291972, 0.577766, 0.488233, 0.679953, - 0.29456, 0.56875, 0.523248, 0.704654, - 0.295816, 0.558388, 0.559168, 0.729016, - 0.295157, 0.544826, 0.595326, 0.752062, - 0.292779, 0.528273, 0.631864, 0.773138, - 0.288681, 0.508482, 0.667793, 0.794869, - 0.283358, 0.487341, 0.704035, 0.815101, - 0.27608, 0.46354, 0.739925, 0.834212, - 0.26767, 0.438672, 0.775539, 0.852368, - 0.257397, 0.411239, 0.810895, 0.870207, - 0.245689, 0.3829, 0.846472, 0.884063, - 0.231452, 0.351496, 0.881788, 0.898284, - 0.215561, 0.31895, 0.917438, 0.912964, - 0.198208, 0.287367, 0.952422, 0.924666, - 0.180426, 0.254487, 0.987551, 0.934429, - 0.161525, 0.222226, 1.02142, 0.943485, - 0.141197, 0.191143, 1.05218, 0.9521, - 0.120085, 0.161112, 1.07937, 0.957876, - 0.0975881, 0.130982, 1.10403, 0.966943, - 0.0726842, 0.0990553, 1.12616, 0.978313, - 0.0483705, 0.0662818, 1.14619, 0.990048, - 0.0239072, 0.0329243, 1.16413, 0.999984, 0.000461885, - 7.72859e-05, 1.18099, 0.321287, - 9.35049e-06, 0.455413, 6.59662e-06, 0.332595, - 0.000237513, 0.471437, 0.000167562, 0.332729, - 0.000949964, 0.471618, 0.000670192, 0.332305, - 0.00213618, 0.471028, 0.00150712, 0.332326, - 0.00379765, 0.471055, 0.00267959, 0.332344, - 0.00593353, 0.471072, 0.00418751, 0.332356, - 0.00854349, 0.471077, 0.00603172, 0.332403, - 0.0116268, 0.471121, 0.00821362, 0.332461, - 0.0151824, 0.47117, 0.0107357, 0.332552, - 0.0192088, 0.471251, 0.0136014, 0.332657, - 0.0237024, 0.47133, 0.0168152, 0.332835, - 0.0286615, 0.471487, 0.0203853, 0.333083, - 0.0340765, 0.471708, 0.0243212, 0.333547, - 0.0398563, 0.47219, 0.0286518, 0.333989, - 0.0460916, 0.472587, 0.0333763, 0.334532, - 0.0527897, 0.473054, 0.0385084, 0.335167, - 0.0599284, 0.473568, 0.0440638, 0.33608, - 0.0673514, 0.474362, 0.0500962, 0.337146, - 0.0752237, 0.475231, 0.0566022, 0.338462, - 0.083418, 0.476282, 0.0636272, 0.34014, - 0.0919382, 0.477615, 0.0712153, 0.342341, - 0.100741, 0.479404, 0.079417, 0.345088, - 0.109905, 0.481618, 0.0882631, 0.349049, - 0.119369, 0.485081, 0.0978851, 0.353939, - 0.129033, 0.489317, 0.108336, 0.359893, - 0.139038, 0.494309, 0.119698, 0.366945, - 0.149411, 0.499983, 0.132024, 0.375814, - 0.159843, 0.507185, 0.145558, 0.387112, - 0.170664, 0.516392, 0.160433, 0.40023, - 0.181897, 0.526519, 0.176648, 0.412555, - 0.192785, 0.53423, 0.193922, 0.427023, - 0.203663, 0.542741, 0.212662, 0.443685, - 0.214695, 0.552066, 0.232944, 0.461499, - 0.225561, 0.560762, 0.254495, 0.480975, - 0.236257, 0.569421, 0.277531, 0.501, - 0.24639, 0.576101, 0.301724, 0.521691, - 0.256101, 0.581493, 0.327112, 0.543478, - 0.265289, 0.585221, 0.353917, 0.566094, - 0.273938, 0.587614, 0.381941, 0.589578, - 0.281679, 0.587991, 0.41172, 0.614583, - 0.287655, 0.585928, 0.444148, 0.641813, - 0.292228, 0.582092, 0.478617, 0.666189, - 0.295172, 0.57398, 0.51397, 0.690475, - 0.29648, 0.561676, 0.550118, 0.715543, - 0.296203, 0.548758, 0.586933, 0.740405, - 0.293999, 0.532792, 0.62384, 0.762183, - 0.28998, 0.512735, 0.660723, 0.786069, - 0.28478, 0.492402, 0.69807, 0.806812, - 0.277568, 0.469058, 0.734422, 0.826987, - 0.268951, 0.443017, 0.770946, 0.844588, - 0.259049, 0.415501, 0.80699, 0.863725, - 0.2471, 0.387328, 0.842107, 0.879137, - 0.234157, 0.356108, 0.878078, 0.894634, - 0.218719, 0.324315, 0.914058, 0.909162, - 0.201293, 0.291813, 0.949922, 0.92072, - 0.18267, 0.258474, 0.985337, 0.93158, - 0.163212, 0.225593, 1.0205, 0.941238, - 0.142771, 0.193986, 1.05273, 0.949293, - 0.120956, 0.163392, 1.08075, 0.956226, - 0.0985743, 0.132934, 1.10559, 0.96546, - 0.075118, 0.101255, 1.12823, 0.977403, - 0.0497921, 0.0675441, 1.149, 0.989648, - 0.0241574, 0.0334681, 1.16765, 1.00001, 0.0005762, - 0.000184807, 1.18519, 0.303474, - 9.16603e-06, 0.4542, 6.1243e-06, 0.308894, - 0.000232869, 0.462306, 0.000155592, 0.309426, - 0.000931661, 0.463093, 0.000622499, 0.308643, - 0.0020949, 0.461933, 0.00139979, 0.308651, - 0.0037242, 0.461941, 0.00248874, 0.308662, - 0.00581873, 0.46195, 0.00388933, 0.308687, - 0.00837818, 0.461974, 0.00560247, 0.308728, - 0.0114016, 0.462011, 0.00762948, 0.308789, - 0.0148884, 0.462067, 0.00997326, 0.308882, - 0.0188369, 0.462151, 0.0126375, 0.309007, - 0.0232436, 0.462263, 0.0156271, 0.30918, - 0.0281054, 0.462417, 0.0189498, 0.309442, - 0.0334065, 0.462667, 0.0226167, 0.309901, - 0.0390589, 0.463162, 0.0266614, 0.310331, - 0.0452042, 0.463555, 0.0310715, 0.310858, - 0.0517735, 0.464019, 0.0358698, 0.311576, - 0.0587359, 0.464669, 0.0410848, 0.312436, - 0.0660383, 0.465406, 0.0467453, 0.313526, - 0.0737266, 0.466339, 0.0528718, 0.314903, - 0.0817574, 0.467504, 0.0595039, 0.316814, - 0.090167, 0.469226, 0.0666888, 0.318965, - 0.0987555, 0.470981, 0.0744658, 0.322077, - 0.107792, 0.473814, 0.082912, 0.325947, - 0.117098, 0.477241, 0.0920846, 0.331008, - 0.126602, 0.48184, 0.102137, 0.337893, - 0.136619, 0.488334, 0.113135, 0.345106, - 0.146838, 0.494415, 0.12511, 0.355111, - 0.157357, 0.503275, 0.138356, 0.365095, - 0.167955, 0.510966, 0.152686, 0.378344, - 0.179157, 0.521508, 0.16856, 0.391599, - 0.190143, 0.530455, 0.18561, 0.407786, - 0.20123, 0.541275, 0.204308, 0.425294, - 0.212456, 0.551784, 0.224623, 0.444021, - 0.223568, 0.561493, 0.246172, 0.463418, - 0.234154, 0.569886, 0.268979, 0.484077, - 0.244546, 0.577116, 0.293411, 0.505513, - 0.254301, 0.582914, 0.318936, 0.527672, - 0.263564, 0.587208, 0.345856, 0.550565, - 0.272332, 0.589277, 0.374054, 0.573656, - 0.280011, 0.588426, 0.403276, 0.59827, - 0.286924, 0.587504, 0.43474, 0.624731, - 0.291994, 0.583401, 0.468767, 0.652396, - 0.295159, 0.576997, 0.504411, 0.67732, - 0.296954, 0.565863, 0.54114, 0.703147, - 0.296877, 0.552316, 0.57816, 0.728715, - 0.295147, 0.536773, 0.616124, 0.752448, - 0.291275, 0.51771, 0.653885, 0.775169, - 0.285905, 0.496087, 0.691537, 0.799307, - 0.279064, 0.474232, 0.729251, 0.819482, - 0.270294, 0.447676, 0.766267, 0.837659, - 0.260032, 0.419656, 0.802616, 0.856903, - 0.248497, 0.391328, 0.838583, 0.873325, - 0.235252, 0.360285, 0.874711, 0.889788, - 0.221126, 0.329215, 0.91077, 0.904486, - 0.204304, 0.296392, 0.94653, 0.917711, - 0.185562, 0.262159, 0.983828, 0.928969, - 0.165635, 0.229142, 1.01955, 0.939707, - 0.14442, 0.19673, 1.05317, 0.948167, - 0.122147, 0.165095, 1.0823, 0.955222, - 0.099098, 0.13451, 1.10791, 0.964401, - 0.0755332, 0.102476, 1.1312, 0.976605, - 0.0513817, 0.0689667, 1.15218, 0.989085, - 0.0258499, 0.034506, 1.17129, 0.999908, 0.000617773, - 0.000271268, 1.18961, 0.285803, - 9.05752e-06, 0.452348, 5.72272e-06, 0.284689, - 0.00022732, 0.450581, 0.000143626, 0.285263, - 0.000910214, 0.451482, 0.000575099, 0.285302, - 0.00204784, 0.451553, 0.00129395, 0.285318, - 0.00364057, 0.451574, 0.0023006, 0.28533, - 0.00568813, 0.451585, 0.00359547, 0.285361, - 0.00819001, 0.451618, 0.00517934, 0.285397, - 0.0111458, 0.45165, 0.007054, 0.285447, - 0.0145536, 0.451688, 0.00922167, 0.285527, - 0.0184127, 0.451758, 0.0116869, 0.285688, - 0.0227207, 0.451929, 0.0144555, 0.28584, - 0.0274712, 0.452055, 0.0175341, 0.286136, - 0.0326278, 0.452369, 0.0209406, 0.286574, - 0.0381792, 0.452853, 0.0246965, 0.287012, - 0.0441879, 0.453272, 0.0287996, 0.287542, - 0.0506096, 0.453752, 0.033268, 0.288299, - 0.0573634, 0.454488, 0.0381504, 0.289186, - 0.0645458, 0.455294, 0.0434447, 0.290302, - 0.0720405, 0.456301, 0.0491973, 0.291776, - 0.0799046, 0.457648, 0.0554453, 0.29372, - 0.088117, 0.459483, 0.0622311, 0.296052, - 0.0965328, 0.461571, 0.0695992, 0.299563, - 0.105409, 0.465085, 0.077658, 0.30335, - 0.114553, 0.468506, 0.0864176, 0.309167, - 0.123917, 0.474423, 0.0961078, 0.31529, - 0.13381, 0.47995, 0.106643, 0.324163, - 0.144021, 0.488592, 0.118322, 0.333272, - 0.154382, 0.496461, 0.131133, 0.344224, - 0.165015, 0.50562, 0.145208, 0.357733, - 0.176168, 0.516719, 0.16073, 0.373046, - 0.187468, 0.528513, 0.177807, 0.38788, - 0.198488, 0.537713, 0.196072, 0.405133, - 0.209545, 0.547999, 0.21605, 0.423845, - 0.220724, 0.55759, 0.237484, 0.443777, - 0.231518, 0.566246, 0.26039, 0.464824, - 0.242035, 0.574326, 0.284835, 0.486635, - 0.251898, 0.58037, 0.310518, 0.51012, - 0.261304, 0.58568, 0.337678, 0.535301, - 0.270384, 0.590197, 0.366242, 0.559193, - 0.27841, 0.590569, 0.395873, 0.583544, - 0.285325, 0.588161, 0.426857, 0.608834, - 0.291113, 0.584249, 0.459477, 0.635753, - 0.294882, 0.57763, 0.494734, 0.664367, - 0.297088, 0.569479, 0.532023, 0.689688, - 0.297364, 0.555064, 0.569629, 0.715732, - 0.295949, 0.539522, 0.608124, 0.741307, - 0.292259, 0.521613, 0.646231, 0.764949, - 0.287063, 0.49969, 0.684938, 0.788599, - 0.28012, 0.476747, 0.723548, 0.81048, - 0.27153, 0.45116, 0.761135, 0.831372, - 0.261289, 0.424101, 0.798916, 0.850092, - 0.249559, 0.39443, 0.835952, 0.867777, - 0.236348, 0.363849, 0.871606, 0.884632, - 0.221569, 0.332477, 0.907843, 0.90047, - 0.20618, 0.300667, 0.944187, 0.914524, - 0.188771, 0.266552, 0.981371, 0.926892, - 0.168362, 0.232349, 1.01841, 0.937951, - 0.146761, 0.199359, 1.05308, 0.947236, - 0.123813, 0.1675, 1.0839, 0.954367, - 0.099984, 0.136166, 1.11047, 0.963907, - 0.0759278, 0.103808, 1.13414, 0.976218, - 0.0511367, 0.0697061, 1.15575, 0.988772, - 0.0267415, 0.0352529, 1.17531, 0.999888, - 0.000520778, 0.000289926, 1.19389, 0.263546, - 8.83274e-06, 0.441896, 5.26783e-06, 0.262352, - 0.000221849, 0.439889, 0.000132311, 0.262325, - 0.000886683, 0.439848, 0.000528824, 0.26228, - 0.00199476, 0.439765, 0.00118975, 0.262372, - 0.00354671, 0.439922, 0.00211568, 0.26239, - 0.00554141, 0.439941, 0.00330652, 0.262412, - 0.00797888, 0.439961, 0.00476346, 0.262453, - 0.0108584, 0.440002, 0.00648818, 0.262528, - 0.0141788, 0.440085, 0.0084835, 0.262615, - 0.017938, 0.440166, 0.0107533, 0.262744, - 0.0221346, 0.440291, 0.0133044, 0.262939, - 0.026762, 0.440493, 0.0161445, 0.263277, - 0.0317573, 0.440889, 0.0192974, 0.26368, - 0.0371832, 0.441338, 0.0227699, 0.264106, - 0.0430371, 0.441753, 0.0265698, 0.264624, - 0.0493035, 0.442227, 0.0307178, 0.265378, - 0.0558669, 0.442985, 0.0352616, 0.266253, - 0.0628718, 0.443795, 0.0401968, 0.267478, - 0.0701569, 0.445008, 0.04559, 0.269062, - 0.077845, 0.446599, 0.0514539, 0.270926, - 0.0857941, 0.448349, 0.0578382, 0.273693, - 0.0940773, 0.451221, 0.0648363, 0.276746, - 0.102704, 0.454097, 0.0724389, 0.281693, - 0.111735, 0.459517, 0.0808744, 0.287335, - 0.121004, 0.46531, 0.0901551, 0.29448, - 0.130734, 0.472605, 0.100371, 0.30257, - 0.140777, 0.480251, 0.111644, 0.312465, - 0.15111, 0.489444, 0.124111, 0.324856, - 0.16189, 0.500919, 0.137979, 0.33774, - 0.172946, 0.511317, 0.153163, 0.35255, - 0.184152, 0.522684, 0.169817, 0.367786, - 0.19522, 0.53248, 0.187886, 0.385474, - 0.20632, 0.543326, 0.207634, 0.404976, - 0.217744, 0.554109, 0.229165, 0.425203, - 0.228691, 0.563395, 0.252068, 0.446704, - 0.239299, 0.571565, 0.276471, 0.468951, - 0.249348, 0.577935, 0.302323, 0.493487, - 0.258933, 0.584309, 0.329882, 0.517861, - 0.268009, 0.58773, 0.358525, 0.543309, - 0.276238, 0.589612, 0.388585, 0.569704, - 0.28356, 0.589294, 0.419787, 0.594871, - 0.289497, 0.585137, 0.452114, 0.622555, - 0.294452, 0.580356, 0.486466, 0.651167, - 0.296918, 0.57185, 0.523079, 0.677332, - 0.297647, 0.558428, 0.5611, 0.703718, - 0.296321, 0.542232, 0.599592, 0.730262, - 0.293339, 0.524541, 0.639138, 0.754304, - 0.288036, 0.502691, 0.677978, 0.778051, - 0.281018, 0.479212, 0.716537, 0.801557, - 0.272414, 0.454071, 0.75586, 0.822559, - 0.262419, 0.425952, 0.794477, 0.843051, - 0.250702, 0.397313, 0.832664, 0.86232, - 0.237264, 0.366534, 0.869876, 0.879044, - 0.222716, 0.334816, 0.906973, 0.896362, - 0.206827, 0.303143, 0.943558, 0.910342, - 0.189659, 0.269699, 0.979759, 0.924119, - 0.171108, 0.236411, 1.01718, 0.935374, - 0.149579, 0.202224, 1.05289, 0.944295, - 0.126295, 0.16989, 1.08496, 0.952227, - 0.101511, 0.138089, 1.11256, 0.962041, - 0.0766392, 0.105053, 1.1375, 0.97528, - 0.0511967, 0.070329, 1.15983, 0.988476, - 0.025463, 0.0351268, 1.17987, 0.999962, 2.86808e-05, 1.45564e-05, 1.19901, 0.227089, - 8.41413e-06, 0.404216, 4.72707e-06, 0.239725, - 0.000215083, 0.426708, 0.000120833, 0.239904, - 0.000860718, 0.427028, 0.000483555, 0.239911, - 0.00193661, 0.427039, 0.00108806, 0.239914, - 0.00344276, 0.42704, 0.00193457, 0.239933, - 0.00537907, 0.427064, 0.00302363, 0.239944, - 0.00774482, 0.427065, 0.00435604, 0.239993, - 0.01054, 0.427122, 0.00593398, 0.240052, - 0.0137626, 0.427179, 0.00775987, 0.240148, - 0.0174115, 0.427279, 0.00983854, 0.240278, - 0.021484, 0.42741, 0.0121763, 0.240472, - 0.0259729, 0.427618, 0.0147827, 0.240839, - 0.0308131, 0.428086, 0.0176837, 0.241201, - 0.0360893, 0.428482, 0.0208775, 0.241626, - 0.0417723, 0.428907, 0.0243821, 0.242207, - 0.0478337, 0.42952, 0.0282228, 0.24298, - 0.0542199, 0.430332, 0.0324333, 0.243881, - 0.0610015, 0.431222, 0.0370252, 0.245123, - 0.0680874, 0.432512, 0.0420535, 0.24667, - 0.0755482, 0.434088, 0.0475414, 0.248779, - 0.0832873, 0.436323, 0.0535542, 0.251665, - 0.0913546, 0.439509, 0.0601716, 0.255305, - 0.0998489, 0.443478, 0.0674282, 0.260049, - 0.108576, 0.448713, 0.0754673, 0.266192, - 0.117754, 0.455524, 0.084339, 0.273158, - 0.127294, 0.4627, 0.0941683, 0.282131, - 0.137311, 0.472068, 0.10515, 0.293332, - 0.147736, 0.483565, 0.117402, 0.304667, - 0.158357, 0.493702, 0.130824, 0.317785, - 0.169274, 0.504708, 0.145724, 0.333245, - 0.180595, 0.517107, 0.16215, 0.349843, - 0.191892, 0.528849, 0.180149, 0.367944, - 0.203168, 0.540301, 0.199746, 0.387579, - 0.214443, 0.551514, 0.221047, 0.408247, - 0.225624, 0.560906, 0.243981, 0.43014, - 0.236422, 0.56959, 0.268513, 0.452669, - 0.24654, 0.576098, 0.294409, 0.476196, - 0.256157, 0.580925, 0.322002, 0.501157, - 0.265289, 0.584839, 0.351052, 0.527632, - 0.273671, 0.587614, 0.3812, 0.555754, - 0.281254, 0.589119, 0.412994, 0.581682, - 0.287448, 0.585204, 0.445498, 0.608196, - 0.292614, 0.579006, 0.479505, 0.635661, - 0.296068, 0.571297, 0.514643, 0.664999, - 0.297395, 0.560855, 0.552213, 0.691039, - 0.296645, 0.544525, 0.591365, 0.7179, - 0.293785, 0.526535, 0.630883, 0.744059, - 0.289089, 0.50545, 0.670932, 0.76863, - 0.282239, 0.482514, 0.710904, 0.793273, - 0.273688, 0.457246, 0.750259, 0.814731, - 0.26328, 0.428872, 0.78948, 0.835603, - 0.251526, 0.399384, 0.828597, 0.85489, - 0.238339, 0.368811, 0.866892, 0.872828, - 0.223607, 0.336617, 0.90563, 0.889462, - 0.207538, 0.303997, 0.943538, 0.904929, - 0.190297, 0.270812, 0.980591, 0.919101, - 0.172034, 0.237453, 1.01935, 0.930536, - 0.152058, 0.204431, 1.05498, 0.941223, - 0.129515, 0.172495, 1.08717, 0.94982, - 0.104263, 0.140175, 1.11551, 0.960592, - 0.0781944, 0.106465, 1.14098, 0.974629, - 0.051688, 0.0711592, 1.16418, 0.98811, - 0.0253929, 0.0354432, 1.18465, 1.00004, 0.000804378, - 0.000330876, 1.20462, 0.214668, - 8.21282e-06, 0.406619, 4.33582e-06, 0.218053, - 0.000208144, 0.413025, 0.000109887, 0.217987, - 0.000832212, 0.412901, 0.000439362, 0.217971, - 0.00187246, 0.412876, 0.000988623, 0.217968, - 0.00332855, 0.41286, 0.00175772, 0.217985, - 0.00520055, 0.412882, 0.00274729, 0.218014, - 0.00748814, 0.412916, 0.00395842, 0.218054, - 0.0101901, 0.412957, 0.00539274, 0.218106, - 0.0133057, 0.413005, 0.00705348, 0.218217, - 0.0168342, 0.413139, 0.00894581, 0.218338, - 0.0207707, 0.413258, 0.0110754, 0.21855, - 0.0251001, 0.413509, 0.0134551, 0.218913, - 0.0297861, 0.413992, 0.0161081, 0.219265, - 0.0348956, 0.414383, 0.0190307, 0.219696, - 0.0403909, 0.414839, 0.0222458, 0.220329, - 0.0462003, 0.415567, 0.025792, 0.220989, - 0.0524208, 0.41621, 0.0296637, 0.222027, - 0.058948, 0.417385, 0.0339323, 0.223301, - 0.0658208, 0.418779, 0.0386055, 0.224988, - 0.0730347, 0.420665, 0.0437355, 0.227211, - 0.0805274, 0.423198, 0.0493844, 0.230131, - 0.088395, 0.426566, 0.0556135, 0.233908, - 0.0966208, 0.43091, 0.0624829, 0.239092, - 0.105223, 0.437148, 0.0701636, 0.245315, - 0.11424, 0.444302, 0.0786949, 0.253166, - 0.12368, 0.453262, 0.0882382, 0.262374, - 0.133569, 0.463211, 0.0988682, 0.273145, - 0.143836, 0.474271, 0.110727, 0.285512, - 0.154577, 0.4863, 0.123945, 0.299512, - 0.165501, 0.498817, 0.138581, 0.314287, - 0.176698, 0.510341, 0.154676, 0.331083, - 0.188066, 0.522583, 0.172459, 0.349615, - 0.199597, 0.534879, 0.191979, 0.369318, - 0.210843, 0.546083, 0.21309, 0.390377, - 0.222068, 0.5562, 0.235998, 0.412411, - 0.233059, 0.564704, 0.260518, 0.435715, - 0.24357, 0.572314, 0.286795, 0.461196, - 0.253356, 0.579395, 0.314559, 0.485587, - 0.262362, 0.581985, 0.343581, 0.511908, - 0.270895, 0.584347, 0.374367, 0.539798, - 0.278452, 0.58505, 0.406015, 0.567974, - 0.284877, 0.583344, 0.439168, 0.594303, - 0.290124, 0.577348, 0.473005, 0.622951, - 0.294183, 0.570751, 0.508534, 0.652404, - 0.296389, 0.561541, 0.544764, 0.679291, - 0.296605, 0.546426, 0.582927, 0.706437, - 0.294095, 0.528599, 0.622681, 0.734485, - 0.28978, 0.508676, 0.663567, 0.758841, - 0.283363, 0.484768, 0.704092, 0.78537, - 0.275015, 0.460434, 0.745101, 0.807315, - 0.264689, 0.432166, 0.784712, 0.8271, - 0.252597, 0.401807, 0.824241, 0.849191, - 0.239154, 0.371458, 0.863803, 0.867046, - 0.224451, 0.338873, 0.903063, 0.8852, - 0.208342, 0.306175, 0.942763, 0.901771, - 0.190684, 0.272759, 0.981559, 0.915958, - 0.172105, 0.239306, 1.02048, 0.928046, - 0.152214, 0.206071, 1.05765, 0.939961, - 0.130247, 0.17367, 1.08999, 0.948711, - 0.10672, 0.142201, 1.11829, 0.959305, - 0.0808688, 0.108454, 1.14467, 0.973009, - 0.0539145, 0.0728109, 1.16839, 0.987631, - 0.0262947, 0.0360625, 1.19004, 0.999978, 0.00132758, - 0.000559424, 1.21058, 0.193925, - 7.93421e-06, 0.391974, 3.92537e-06, 0.196746, - 0.000200315, 0.397675, 9.91033e-05, 0.19667, - 0.000801099, 0.397521, 0.000396342, 0.196633, - 0.00180246, 0.397445, 0.000891829, 0.196654, - 0.00320443, 0.397482, 0.00158582, 0.196659, - 0.00500647, 0.39748, 0.00247867, 0.196683, - 0.0072086, 0.397506, 0.00357167, 0.196728, - 0.00981001, 0.397562, 0.00486675, 0.196792, - 0.0128096, 0.397633, 0.00636707, 0.19689, - 0.0162055, 0.397746, 0.00807752, 0.197017, - 0.0199943, 0.397884, 0.0100052, 0.19729, - 0.024139, 0.39827, 0.0121691, 0.197583, - 0.0286671, 0.398639, 0.0145755, 0.197927, - 0.0335858, 0.399034, 0.0172355, 0.198383, - 0.0388806, 0.399554, 0.0201718, 0.199002, - 0.0444736, 0.400289, 0.0234194, 0.199739, - 0.0504583, 0.401111, 0.026984, 0.200784, - 0.056729, 0.402349, 0.0309217, 0.202075, - 0.0633643, 0.403841, 0.0352496, 0.203898, - 0.0703247, 0.406076, 0.0400313, 0.206199, - 0.0775565, 0.408841, 0.0453282, 0.209252, - 0.085184, 0.41259, 0.0511794, 0.213638, - 0.0931994, 0.418288, 0.0577459, 0.21881, - 0.101617, 0.424681, 0.0650508, 0.225642, - 0.11052, 0.433429, 0.0732759, 0.233717, - 0.119772, 0.442897, 0.0824683, 0.242823, - 0.129505, 0.452888, 0.0927484, 0.254772, - 0.139906, 0.466407, 0.104417, 0.266603, - 0.150402, 0.477413, 0.117211, 0.28073, - 0.161395, 0.490519, 0.131598, 0.295399, - 0.172465, 0.50201, 0.147407, 0.312705, - 0.183982, 0.515311, 0.165031, 0.331335, - 0.195532, 0.52786, 0.184336, 0.351037, - 0.206971, 0.5392, 0.205361, 0.372175, - 0.218117, 0.54941, 0.228043, 0.394548, - 0.229327, 0.558642, 0.25267, 0.419598, - 0.240052, 0.567861, 0.279071, 0.443922, - 0.249937, 0.573332, 0.306882, 0.471495, - 0.259407, 0.58013, 0.33661, 0.496769, - 0.267749, 0.580564, 0.367328, 0.524951, - 0.275524, 0.581696, 0.399753, 0.55318, - 0.282148, 0.579885, 0.433134, 0.581577, - 0.287533, 0.575471, 0.467534, 0.609231, - 0.291612, 0.567445, 0.502943, 0.637478, - 0.293911, 0.557657, 0.53871, 0.667795, - 0.295096, 0.546535, 0.576568, 0.694272, - 0.294073, 0.529561, 0.614929, 0.722937, - 0.290386, 0.510561, 0.655909, 0.749682, - 0.284481, 0.487846, 0.697663, 0.774754, - 0.276188, 0.462487, 0.738515, 0.799301, - 0.266215, 0.43481, 0.779802, 0.820762, - 0.254116, 0.404879, 0.820045, 0.843231, - 0.240393, 0.374559, 0.860294, 0.861857, - 0.225503, 0.341582, 0.900965, 0.880815, - 0.209382, 0.308778, 0.941727, 0.89766, - 0.19155, 0.275232, 0.980916, 0.912926, - 0.172346, 0.240938, 1.02162, 0.926391, - 0.151799, 0.207223, 1.0597, 0.938429, - 0.129968, 0.17484, 1.09291, 0.947834, - 0.10651, 0.142984, 1.12248, 0.958432, - 0.0824098, 0.109902, 1.149, 0.972402, - 0.0565242, 0.0744454, 1.1733, 0.987191, - 0.028427, 0.0373794, 1.19538, 0.999975, 3.85685e-05, - 4.203e-05, 1.21676, 0.178114, - 7.66075e-06, 0.385418, 3.54027e-06, 0.176074, - 0.000191966, 0.381002, 8.87135e-05, 0.17601, - 0.000767549, 0.380861, 0.000354715, 0.17598, - 0.00172696, 0.380798, 0.000798168, 0.175994, - 0.00307012, 0.380824, 0.00141928, 0.176017, - 0.00479684, 0.380858, 0.00221859, 0.176019, - 0.00690648, 0.380839, 0.00319714, 0.176072, - 0.00939888, 0.380913, 0.0043572, 0.176131, - 0.0122726, 0.380979, 0.005702, 0.176239, - 0.0155264, 0.38112, 0.00723689, 0.176371, - 0.0191551, 0.381272, 0.00896907, 0.176638, - 0.023117, 0.381669, 0.0109194, 0.176912, - 0.0274633, 0.382015, 0.0130903, 0.177279, - 0.032173, 0.382476, 0.0154949, 0.17774, - 0.0372219, 0.383041, 0.0181669, 0.178344, - 0.0426132, 0.38378, 0.0211209, 0.179153, - 0.0483309, 0.384773, 0.0243899, 0.180197, - 0.0543447, 0.386076, 0.0280062, 0.181581, - 0.0607122, 0.387809, 0.032004, 0.18344, - 0.0673855, 0.390205, 0.036453, 0.186139, - 0.0743989, 0.393944, 0.0414162, 0.189432, - 0.0817731, 0.39832, 0.0469394, 0.193795, - 0.0895464, 0.404188, 0.0531442, 0.199641, - 0.0978264, 0.4121, 0.0601374, 0.206679, - 0.106499, 0.421425, 0.0680078, 0.214865, - 0.115654, 0.431504, 0.076919, 0.224406, - 0.125268, 0.442526, 0.0868835, 0.235876, - 0.135475, 0.455465, 0.0981875, 0.248335, - 0.146023, 0.4681, 0.110759, 0.262868, - 0.157016, 0.482069, 0.124885, 0.278962, - 0.168245, 0.496182, 0.140645, 0.295082, - 0.17958, 0.507401, 0.157838, 0.313738, - 0.191227, 0.520252, 0.17695, 0.333573, - 0.202718, 0.531708, 0.197817, 0.356433, - 0.214424, 0.544509, 0.220785, 0.378853, - 0.225492, 0.55373, 0.245306, 0.402717, - 0.236236, 0.561348, 0.271593, 0.428375, - 0.246568, 0.568538, 0.299776, 0.454724, - 0.255941, 0.573462, 0.329433, 0.482291, - 0.264511, 0.576356, 0.360598, 0.509706, - 0.272129, 0.576446, 0.393204, 0.538805, - 0.278979, 0.575298, 0.427227, 0.568919, - 0.284528, 0.572154, 0.462157, 0.596804, - 0.288801, 0.564691, 0.497997, 0.625987, - 0.291334, 0.555134, 0.534467, 0.656414, - 0.292722, 0.545051, 0.571736, 0.683916, - 0.292185, 0.528813, 0.610158, 0.711809, - 0.290043, 0.51106, 0.649061, 0.739547, - 0.285246, 0.490103, 0.690081, 0.766914, - 0.277647, 0.465523, 0.732554, 0.791375, - 0.267603, 0.437718, 0.773982, 0.814772, - 0.256109, 0.40882, 0.81609, 0.836691, - 0.242281, 0.377823, 0.856849, 0.856984, - 0.227155, 0.34496, 0.898363, 0.876332, - 0.210395, 0.311335, 0.939471, 0.894988, - 0.192612, 0.277703, 0.980799, 0.911113, - 0.173236, 0.243019, 1.02215, 0.924092, - 0.152258, 0.209037, 1.06139, 0.936828, - 0.129575, 0.175909, 1.09635, 0.946869, - 0.10594, 0.143852, 1.12707, 0.958284, - 0.081318, 0.110289, 1.15419, 0.972325, - 0.0556133, 0.0747232, 1.17909, 0.986878, - 0.0297899, 0.0383149, 1.20163, 0.999936, - 0.00197169, 0.000912402, 1.22338, 0.151174, - 7.20365e-06, 0.351531, 3.09789e-06, 0.155594, - 0.00018279, 0.361806, 7.8608e-05, 0.156099, - 0.000731569, 0.362982, 0.000314615, 0.156053, - 0.00164578, 0.362869, 0.000707845, 0.156093, - 0.0029261, 0.362961, 0.00125884, 0.156099, - 0.00457155, 0.362959, 0.00196783, 0.15612, - 0.00658224, 0.362982, 0.00283622, 0.156168, - 0.00895774, 0.363048, 0.00386625, 0.156221, - 0.0116962, 0.363101, 0.00506109, 0.156324, - 0.0147973, 0.363241, 0.00642675, 0.156476, - 0.0182503, 0.363448, 0.00797175, 0.156731, - 0.0220266, 0.36384, 0.00971484, 0.156994, - 0.026176, 0.364179, 0.0116575, 0.157341, - 0.0306701, 0.36462, 0.0138207, 0.157867, - 0.0354591, 0.365364, 0.0162356, 0.15846, - 0.0406141, 0.366111, 0.0189092, 0.159308, - 0.0460519, 0.367248, 0.021885, 0.160426, - 0.0518096, 0.368767, 0.0252004, 0.161877, - 0.0578906, 0.370745, 0.0288825, 0.163995, - 0.0642812, 0.373831, 0.0330139, 0.16655, - 0.0710067, 0.377366, 0.0376283, 0.170237, - 0.0781522, 0.382799, 0.0428493, 0.175096, - 0.0857172, 0.389915, 0.0487324, 0.181069, - 0.0938025, 0.398487, 0.0554214, 0.188487, - 0.102363, 0.408799, 0.0630189, 0.197029, - 0.111343, 0.419991, 0.071634, 0.206684, - 0.120812, 0.431455, 0.0812797, 0.218698, - 0.131033, 0.445746, 0.0923651, 0.230726, - 0.141373, 0.457471, 0.104545, 0.245516, - 0.152387, 0.472388, 0.118449, 0.261551, - 0.163628, 0.486671, 0.133923, 0.277437, - 0.174814, 0.49762, 0.150849, 0.296662, - 0.186713, 0.51162, 0.169924, 0.31795, - 0.198513, 0.525435, 0.190848, 0.339422, - 0.210119, 0.536267, 0.213504, 0.362143, - 0.221354, 0.545982, 0.237947, 0.387198, - 0.23224, 0.555364, 0.264427, 0.412349, - 0.24257, 0.561489, 0.292519, 0.439274, - 0.252284, 0.566903, 0.322561, 0.466779, - 0.261023, 0.569614, 0.353952, 0.496011, - 0.26899, 0.571589, 0.387278, 0.524964, - 0.275498, 0.570325, 0.421356, 0.556518, - 0.281449, 0.568792, 0.457314, 0.584363, - 0.285526, 0.560268, 0.493199, 0.614214, - 0.28844, 0.55205, 0.530276, 0.645684, - 0.289777, 0.541906, 0.56855, 0.673446, - 0.289722, 0.526464, 0.606927, 0.701924, - 0.287792, 0.509872, 0.645945, 0.73037, - 0.284315, 0.490649, 0.685564, 0.757405, - 0.278804, 0.467964, 0.726511, 0.784025, - 0.269543, 0.441468, 0.768601, 0.808255, - 0.258117, 0.41216, 0.811321, 0.830739, - 0.244728, 0.380606, 0.853496, 0.851914, - 0.229428, 0.348111, 0.895374, 0.872586, - 0.212508, 0.314732, 0.937674, 0.891581, - 0.194025, 0.280338, 0.979869, 0.907641, - 0.174711, 0.245203, 1.02253, 0.922233, - 0.153509, 0.21077, 1.06371, 0.935878, - 0.130418, 0.177399, 1.09972, 0.946338, - 0.105558, 0.144507, 1.13124, 0.957265, - 0.080059, 0.110508, 1.15973, 0.971668, - 0.0539766, 0.0742311, 1.18515, 0.9866, - 0.0277101, 0.0375224, 1.20858, 1.00021, - 0.000515531, 0.000135226, 1.23135, 0.137468, - 6.86011e-06, 0.345041, 2.73315e-06, 0.13703, - 0.000173378, 0.343936, 6.90761e-05, 0.136986, - 0.000693048, 0.34383, 0.000276126, 0.136964, - 0.00155931, 0.343761, 0.000621337, 0.137003, - 0.00277211, 0.343863, 0.00110494, 0.137012, - 0.00433103, 0.343868, 0.00172744, 0.137043, - 0.00623606, 0.343916, 0.00249022, 0.13709, - 0.0084868, 0.343986, 0.00339559, 0.137145, - 0.0110814, 0.344045, 0.00444687, 0.137242, - 0.0140187, 0.344177, 0.00565007, 0.137431, - 0.0172713, 0.344491, 0.00701868, 0.137644, - 0.0208605, 0.344805, 0.00856042, 0.13791, - 0.024792, 0.345172, 0.0102863, 0.138295, - 0.0290461, 0.345734, 0.0122185, 0.138764, - 0.0335957, 0.346371, 0.0143771, 0.139415, - 0.038467, 0.347298, 0.0167894, 0.140272, - 0.0436176, 0.348527, 0.0194895, 0.141457, - 0.0491016, 0.350276, 0.0225043, 0.14303, - 0.0548764, 0.352646, 0.0258962, 0.145289, - 0.0610096, 0.356206, 0.0297168, 0.148502, - 0.0674777, 0.361488, 0.0340562, 0.152188, - 0.074345, 0.367103, 0.0389534, 0.157359, - 0.0817442, 0.375247, 0.0445541, 0.16379, - 0.0896334, 0.385064, 0.0509535, 0.171376, - 0.098005, 0.396082, 0.0582611, 0.179901, - 0.106817, 0.407418, 0.06654, 0.189892, - 0.116239, 0.420031, 0.075994, 0.201838, - 0.12627, 0.434321, 0.0867239, 0.214311, - 0.136701, 0.447631, 0.0987517, 0.228902, - 0.147616, 0.462046, 0.112353, 0.245107, - 0.158871, 0.476942, 0.127605, 0.262292, - 0.170261, 0.490285, 0.144469, 0.281215, - 0.182017, 0.503783, 0.163282, 0.301058, - 0.193729, 0.515505, 0.183873, 0.322752, - 0.205512, 0.52682, 0.206466, 0.347547, - 0.217214, 0.539473, 0.231194, 0.370969, - 0.227966, 0.546625, 0.257288, 0.397533, - 0.238555, 0.55472, 0.285789, 0.42398, - 0.248278, 0.559468, 0.315746, 0.452928, - 0.257422, 0.564095, 0.347724, 0.482121, - 0.265306, 0.565426, 0.380922, 0.510438, - 0.272043, 0.563205, 0.415639, 0.541188, - 0.277614, 0.561087, 0.451702, 0.571667, - 0.281927, 0.554922, 0.48845, 0.602432, - 0.285015, 0.546838, 0.526442, 0.634126, - 0.286512, 0.537415, 0.564896, 0.662816, - 0.286388, 0.522906, 0.604037, 0.692411, - 0.284734, 0.507003, 0.643795, 0.720946, - 0.281297, 0.488398, 0.68298, 0.748293, - 0.276262, 0.466353, 0.723466, 0.776931, - 0.269978, 0.443573, 0.764565, 0.801065, - 0.260305, 0.415279, 0.805838, 0.825843, - 0.247426, 0.384773, 0.849985, 0.84807, - 0.232437, 0.352555, 0.893174, 0.869122, - 0.215806, 0.318642, 0.936564, 0.888963, - 0.197307, 0.28381, 0.980253, 0.905547, - 0.177203, 0.247888, 1.02463, 0.918554, - 0.155542, 0.212904, 1.06714, 0.931395, - 0.131948, 0.1787, 1.10451, 0.941749, - 0.106723, 0.145902, 1.13694, 0.954551, - 0.0804939, 0.111193, 1.1666, 0.970279, - 0.0534239, 0.0744697, 1.19249, 0.986117, - 0.0257452, 0.0368788, 1.21665, 0.999938, 0.00190634, - 0.0010291, 1.23981, 0.118493, - 6.47439e-06, 0.32272, 2.3772e-06, 0.118765, - 0.000163023, 0.323456, 5.98573e-05, 0.118772, - 0.00065212, 0.323477, 0.000239447, 0.118843, - 0.00146741, 0.323657, 0.000538881, 0.118804, - 0.00260846, 0.323553, 0.00095826, 0.118826, - 0.00407576, 0.323595, 0.00149845, 0.118846, - 0.00586826, 0.323617, 0.00216047, 0.118886, - 0.00798578, 0.32367, 0.00294679, 0.118947, - 0.0104273, 0.323753, 0.00386124, 0.119055, - 0.0131909, 0.323922, 0.00490999, 0.119241, - 0.0162444, 0.324251, 0.00610804, 0.11944, - 0.0196339, 0.324544, 0.00745805, 0.119739, - 0.0233378, 0.325026, 0.00897805, 0.12011, - 0.0273179, 0.325586, 0.0106895, 0.120571, - 0.0316143, 0.326231, 0.0126073, 0.12124, - 0.0361939, 0.327264, 0.0147654, 0.122162, - 0.0410511, 0.328733, 0.0172001, 0.123378, - 0.0462233, 0.330659, 0.0199375, 0.125183, - 0.0517109, 0.333754, 0.0230498, 0.127832, - 0.0575652, 0.338507, 0.026597, 0.130909, - 0.0637441, 0.343666, 0.0306345, 0.135221, - 0.0704302, 0.351063, 0.035273, 0.14082, - 0.0776364, 0.360604, 0.0406137, 0.146781, - 0.0852293, 0.369638, 0.0466788, 0.155121, - 0.0935351, 0.3827, 0.0537628, 0.16398, - 0.102234, 0.39522, 0.0617985, 0.173926, - 0.111465, 0.40793, 0.07097, 0.185137, - 0.121296, 0.42105, 0.0813426, 0.19826, - 0.13169, 0.435735, 0.0931596, 0.212938, - 0.142614, 0.450932, 0.106547, 0.229046, - 0.153884, 0.465726, 0.121575, 0.246246, - 0.165382, 0.479461, 0.138286, 0.264637, - 0.176806, 0.492106, 0.15666, 0.284959, - 0.188793, 0.504774, 0.17728, 0.308157, - 0.200763, 0.518805, 0.19988, 0.330951, - 0.21239, 0.528231, 0.224293, 0.3549, - 0.223521, 0.536376, 0.250541, 0.381502, - 0.234169, 0.544846, 0.278902, 0.409529, - 0.244077, 0.551717, 0.309227, 0.437523, - 0.253363, 0.55517, 0.341426, 0.467624, - 0.261659, 0.557772, 0.37518, 0.497268, - 0.268498, 0.556442, 0.41007, 0.528294, - 0.274018, 0.553915, 0.446445, 0.559053, - 0.278169, 0.549153, 0.483779, 0.589329, - 0.281229, 0.539878, 0.522249, 0.622503, - 0.282902, 0.53162, 0.561754, 0.652382, - 0.282815, 0.518119, 0.601544, 0.681847, - 0.281247, 0.502187, 0.641574, 0.712285, - 0.277986, 0.484824, 0.682633, 0.740094, - 0.273017, 0.463483, 0.723426, 0.768478, - 0.266692, 0.441299, 0.763747, 0.794556, - 0.258358, 0.415238, 0.805565, 0.819408, - 0.248807, 0.386912, 0.847254, 0.843411, - 0.236214, 0.356165, 0.891091, 0.862397, - 0.219794, 0.320562, 0.936174, 0.883113, - 0.201768, 0.285322, 0.982562, 0.90023, - 0.181672, 0.249713, 1.02862, 0.915192, - 0.159279, 0.214546, 1.07163, 0.928458, - 0.134725, 0.180285, 1.10995, 0.94069, - 0.10913, 0.147119, 1.14354, 0.953409, - 0.0821315, 0.112492, 1.17372, 0.969537, - 0.0542677, 0.0752014, 1.20043, 0.985612, - 0.0259096, 0.0370361, 1.22528, 0.999835, 0.00298198, - 0.00151801, 1.24959, 0.10097, - 6.02574e-06, 0.300277, 2.02619e-06, 0.101577, - 0.000152164, 0.302077, 5.11662e-05, 0.101572, - 0.000608889, 0.302066, 0.000204751, 0.101566, - 0.00136997, 0.302047, 0.000460753, 0.101592, - 0.00243557, 0.302114, 0.000819497, 0.101608, - 0.0038053, 0.30214, 0.00128154, 0.101627, - 0.00547906, 0.30216, 0.0018483, 0.101669, - 0.00745647, 0.302224, 0.00252223, 0.101732, - 0.00973615, 0.302318, 0.00330716, 0.101844, - 0.0123097, 0.302513, 0.00421061, 0.102025, - 0.0151681, 0.30285, 0.00524481, 0.102224, - 0.0183334, 0.303166, 0.0064154, 0.102515, - 0.0217819, 0.303654, 0.00774063, 0.102886, - 0.0255067, 0.304243, 0.0092398, 0.103395, - 0.029514, 0.305089, 0.0109339, 0.104109, - 0.0337912, 0.306301, 0.0128561, 0.105074, - 0.0383565, 0.30798, 0.0150338, 0.10654, - 0.0432132, 0.310726, 0.0175228, 0.108478, - 0.0484244, 0.314351, 0.0203648, 0.111015, - 0.0539339, 0.319032, 0.0236325, 0.114682, - 0.0598885, 0.32605, 0.0274188, 0.11911, - 0.0663375, 0.334109, 0.0317905, 0.124736, - 0.0733011, 0.344013, 0.0368502, 0.131479, - 0.0807744, 0.355358, 0.0427104, 0.139283, - 0.0888204, 0.367614, 0.0494788, 0.148054, - 0.0973394, 0.380072, 0.0572367, 0.159037, - 0.10665, 0.395678, 0.0662704, 0.169794, - 0.116221, 0.40795, 0.0763192, 0.18314, - 0.126632, 0.423546, 0.087956, 0.197515, - 0.137383, 0.438213, 0.101042, 0.213514, - 0.148641, 0.453248, 0.115827, 0.23065, - 0.160117, 0.46688, 0.132283, 0.249148, - 0.171807, 0.479962, 0.150644, 0.270219, - 0.183695, 0.494618, 0.171073, 0.292338, - 0.195574, 0.506937, 0.193378, 0.314999, - 0.207205, 0.516463, 0.217585, 0.340991, - 0.218955, 0.528123, 0.24428, 0.367982, - 0.229917, 0.537025, 0.272784, 0.39432, - 0.239737, 0.541627, 0.302742, 0.423364, - 0.249048, 0.546466, 0.335112, 0.453751, - 0.257329, 0.549466, 0.369032, 0.48416, - 0.264623, 0.549503, 0.404577, 0.515262, - 0.270411, 0.547008, 0.441337, 0.547036, - 0.274581, 0.542249, 0.479162, 0.576614, - 0.277266, 0.533015, 0.517904, 0.611143, - 0.279144, 0.525512, 0.558508, 0.640989, - 0.279001, 0.51154, 0.598995, 0.671182, - 0.277324, 0.495641, 0.639935, 0.700848, - 0.273908, 0.477526, 0.681017, 0.729862, - 0.269063, 0.457955, 0.722764, 0.758273, - 0.262282, 0.434846, 0.764349, 0.784121, - 0.254281, 0.409203, 0.806206, 0.809798, - 0.24505, 0.382694, 0.848617, 0.834953, - 0.233861, 0.354034, 0.892445, 0.856817, - 0.221308, 0.321764, 0.936263, 0.877609, - 0.205996, 0.288118, 0.982401, 0.897489, - 0.186702, 0.253277, 1.02975, 0.913792, - 0.164618, 0.217963, 1.07488, 0.92785, - 0.140023, 0.183221, 1.11487, 0.940378, - 0.11328, 0.149385, 1.14947, 0.95273, - 0.0853958, 0.114152, 1.1807, 0.969059, - 0.0568698, 0.0769845, 1.20912, 0.985574, - 0.0276502, 0.0381186, 1.23498, 0.999943, 0.00239052, - 0.00126861, 1.25987, 0.0852715, - 5.60067e-06, 0.279021, 1.71162e-06, 0.0854143, - 0.000140871, 0.279483, 4.30516e-05, 0.0854191, - 0.000563385, 0.2795, 0.000172184, 0.0854188, - 0.00126753, 0.279493, 0.000387464, 0.0854229, - 0.00225337, 0.279501, 0.00068918, 0.0854443, - 0.00352086, 0.279549, 0.00107803, 0.0854697, - 0.00506962, 0.279591, 0.00155536, 0.0855093, - 0.00689873, 0.279652, 0.00212354, 0.0855724, - 0.00900821, 0.279752, 0.00278703, 0.0856991, - 0.0113799, 0.280011, 0.0035551, 0.085855, - 0.0140314, 0.280297, 0.00443449, 0.0860682, - 0.016963, 0.280682, 0.00543636, 0.086344, - 0.0201438, 0.281159, 0.0065788, 0.0867426, - 0.0235999, 0.281886, 0.00787977, 0.087239, - 0.0273069, 0.282745, 0.0093606, 0.0879815, - 0.031269, 0.284139, 0.011056, 0.0891258, - 0.035531, 0.28647, 0.0130065, 0.0906909, - 0.0400947, 0.289708, 0.0152495, 0.0927624, - 0.0449638, 0.293904, 0.0178454, 0.0958376, - 0.0502427, 0.300471, 0.0208915, 0.0995827, - 0.0559514, 0.30806, 0.0244247, 0.104526, - 0.0622152, 0.317874, 0.0285721, 0.110532, - 0.0690046, 0.329332, 0.0334227, 0.117385, - 0.0763068, 0.341217, 0.0390466, 0.12522, - 0.084184, 0.353968, 0.0455786, 0.134037, - 0.0925248, 0.366797, 0.0530773, 0.144014, - 0.101487, 0.380209, 0.0617424, 0.156013, - 0.111273, 0.395956, 0.071777, 0.168872, - 0.121431, 0.41053, 0.0830905, 0.183089, - 0.132105, 0.425073, 0.0959341, 0.198763, - 0.143286, 0.439833, 0.110448, 0.216159, - 0.154841, 0.454507, 0.126769, 0.234859, - 0.166588, 0.468368, 0.14495, 0.255879, - 0.178626, 0.482846, 0.165233, 0.27677, - 0.190218, 0.493489, 0.187217, 0.301184, - 0.202227, 0.506549, 0.211659, 0.325852, - 0.213764, 0.5158, 0.237922, 0.352824, - 0.22487, 0.525442, 0.26632, 0.380882, - 0.235246, 0.532487, 0.296691, 0.410137, - 0.244847, 0.537703, 0.329179, 0.439787, - 0.253122, 0.540361, 0.363135, 0.472291, - 0.260517, 0.542734, 0.399222, 0.501856, - 0.266519, 0.538826, 0.436352, 0.534816, - 0.270905, 0.535152, 0.474505, 0.565069, - 0.273826, 0.525979, 0.513988, 0.597154, - 0.275333, 0.516394, 0.554852, 0.630473, - 0.275314, 0.506206, 0.596592, 0.660574, - 0.273323, 0.489769, 0.638117, 0.692015, - 0.270008, 0.472578, 0.680457, 0.720647, - 0.265001, 0.452134, 0.723008, 0.750528, - 0.258311, 0.430344, 0.765954, 0.777568, - 0.250046, 0.405624, 0.809012, 0.80387, - 0.240114, 0.378339, 0.852425, 0.828439, - 0.228737, 0.349877, 0.895346, 0.851472, - 0.216632, 0.318968, 0.940695, 0.873906, - 0.202782, 0.287489, 0.987235, 0.89467, - 0.187059, 0.254394, 1.03348, 0.912281, - 0.168818, 0.221294, 1.07812, 0.927358, - 0.146494, 0.18675, 1.11928, 0.940385, - 0.120009, 0.152322, 1.15609, 0.952672, - 0.0917183, 0.117514, 1.18875, 0.968496, - 0.0620321, 0.0797405, 1.21821, 0.985236, - 0.0314945, 0.0402383, 1.24523, 0.99998, - 0.000575153, 0.000110644, 1.27133, 0.0702429, - 5.12222e-06, 0.255273, 1.40947e-06, 0.0702981, - 0.000128826, 0.255469, 3.54488e-05, 0.0703691, - 0.000515562, 0.255727, 0.000141874, 0.0703805, - 0.00116, 0.255754, 0.00031929, 0.0703961, - 0.00206224, 0.255813, 0.000567999, 0.0704102, - 0.00322223, 0.255839, 0.00088871, 0.0704298, - 0.00463928, 0.255863, 0.00128272, 0.0704759, - 0.00631375, 0.255953, 0.00175283, 0.0705434, - 0.00824317, 0.256079, 0.00230342, 0.0706693, - 0.010412, 0.25636, 0.0029443, 0.0708189, - 0.0128439, 0.256647, 0.00368031, 0.0710364, - 0.0155177, 0.257084, 0.00452614, 0.0713223, - 0.0184374, 0.257637, 0.00549706, 0.0717182, - 0.0216002, 0.258416, 0.00661246, 0.072321, - 0.0249966, 0.259699, 0.00790147, 0.0731446, - 0.0286566, 0.261475, 0.0093884, 0.0743352, - 0.0325888, 0.264132, 0.0111186, 0.0760676, - 0.036843, 0.26815, 0.013145, 0.078454, - 0.0414292, 0.273636, 0.0155251, 0.0818618, - 0.0464634, 0.281653, 0.0183525, 0.0857382, - 0.0519478, 0.289992, 0.0216642, 0.0908131, - 0.0579836, 0.30066, 0.0255956, 0.0967512, - 0.0645124, 0.312204, 0.0301954, 0.103717, - 0.0716505, 0.325001, 0.0356017, 0.111596, - 0.0793232, 0.338129, 0.041896, 0.120933, - 0.087645, 0.352853, 0.0492447, 0.130787, - 0.096492, 0.366192, 0.0576749, 0.142311, - 0.105973, 0.380864, 0.0673969, 0.155344, - 0.116182, 0.396575, 0.0785899, 0.169535, - 0.126815, 0.411443, 0.0912377, 0.185173, - 0.138015, 0.426256, 0.105607, 0.201755, - 0.149325, 0.439607, 0.121551, 0.221334, - 0.161207, 0.455467, 0.139608, 0.241461, - 0.173162, 0.469096, 0.159591, 0.26294, - 0.18504, 0.481014, 0.18156, 0.286776, - 0.196881, 0.493291, 0.205781, 0.311596, - 0.208311, 0.503556, 0.231819, 0.338667, - 0.219671, 0.513268, 0.260274, 0.366021, - 0.230451, 0.519414, 0.290862, 0.395875, - 0.240131, 0.526766, 0.323196, 0.425564, - 0.248566, 0.52905, 0.357071, 0.457094, - 0.256195, 0.530796, 0.393262, 0.488286, - 0.262331, 0.528703, 0.430797, 0.522291, - 0.267141, 0.52727, 0.470231, 0.554172, - 0.270411, 0.519848, 0.510477, 0.586427, - 0.271986, 0.510307, 0.551594, 0.619638, - 0.27192, 0.499158, 0.593849, 0.650656, - 0.269817, 0.483852, 0.636314, 0.68284, - 0.266267, 0.467515, 0.679679, 0.714356, - 0.26113, 0.44931, 0.723884, 0.742717, - 0.254067, 0.425789, 0.767245, 0.770894, - 0.245652, 0.401144, 0.811819, 0.797358, - 0.235554, 0.374224, 0.856315, 0.823377, - 0.223896, 0.346167, 0.901077, 0.847456, - 0.210865, 0.316056, 0.946502, 0.870697, - 0.196574, 0.284503, 0.993711, 0.891068, - 0.180814, 0.251628, 1.04134, 0.909267, - 0.163314, 0.219065, 1.08609, 0.925653, - 0.143304, 0.186446, 1.12702, 0.940017, - 0.121322, 0.153416, 1.16371, 0.952398, - 0.0973872, 0.120334, 1.19712, 0.967568, - 0.0698785, 0.08352, 1.22791, 0.984772, - 0.0390031, 0.0439209, 1.25672, 1.00026, - 0.0070087, 0.00315668, 1.28428, 0.0556653, - 4.59654e-06, 0.227325, 1.12556e-06, 0.0565238, - 0.000116382, 0.230826, 2.84985e-05, 0.0565717, - 0.000465666, 0.231026, 0.000114036, 0.0565859, - 0.00104773, 0.231079, 0.000256656, 0.0565761, - 0.00186255, 0.231025, 0.00045663, 0.0565913, - 0.00291002, 0.231058, 0.000714664, 0.0566108, - 0.00418998, 0.231085, 0.00103224, 0.0566532, - 0.00570206, 0.231169, 0.00141202, 0.0567473, - 0.00743666, 0.231417, 0.00186018, 0.0568567, - 0.00940298, 0.231661, 0.00238264, 0.0569859, - 0.0115991, 0.231895, 0.00298699, 0.0572221, - 0.0140096, 0.232456, 0.00368957, 0.057519, - 0.0166508, 0.233096, 0.00450303, 0.0579534, - 0.01951, 0.234094, 0.00544945, 0.0585922, - 0.0225991, 0.235629, 0.00655564, 0.0595647, - 0.0259416, 0.238106, 0.00785724, 0.0609109, - 0.0295661, 0.241557, 0.00939127, 0.0628751, - 0.0335126, 0.246652, 0.0112198, 0.0656908, - 0.0378604, 0.254091, 0.0134168, 0.0691347, - 0.0426543, 0.262666, 0.0160374, 0.0732165, - 0.0478967, 0.272029, 0.0191514, 0.0782863, - 0.0536716, 0.283007, 0.0228597, 0.0843973, - 0.0600683, 0.295732, 0.0272829, 0.0913598, - 0.0670095, 0.308779, 0.032484, 0.0994407, - 0.0745516, 0.322886, 0.0385886, 0.108189, - 0.082712, 0.336408, 0.0457133, 0.118574, - 0.0914927, 0.351692, 0.0539832, 0.129989, - 0.100854, 0.366502, 0.0635162, 0.142722, - 0.110837, 0.381675, 0.0744386, 0.156654, - 0.121353, 0.3963, 0.0868483, 0.172151, - 0.132414, 0.411477, 0.100963, 0.188712, - 0.143809, 0.42508, 0.116795, 0.208093, - 0.155765, 0.441328, 0.134715, 0.227936, - 0.167608, 0.454328, 0.154396, 0.249495, - 0.179579, 0.467235, 0.176179, 0.27362, - 0.191488, 0.480248, 0.200193, 0.296371, - 0.202618, 0.487886, 0.225775, 0.324234, - 0.214133, 0.499632, 0.25441, 0.353049, - 0.225212, 0.509532, 0.285077, 0.381785, - 0.234875, 0.514265, 0.317047, 0.414038, - 0.244205, 0.521282, 0.351874, 0.445251, - 0.252145, 0.522931, 0.388279, 0.476819, - 0.258433, 0.520947, 0.425825, 0.509209, - 0.263411, 0.517669, 0.465104, 0.542759, - 0.266732, 0.512841, 0.505741, 0.574822, - 0.268263, 0.503317, 0.547611, 0.609324, - 0.268489, 0.493035, 0.590953, 0.641772, - 0.266941, 0.478816, 0.63488, 0.674049, - 0.263297, 0.462863, 0.679072, 0.705071, - 0.257618, 0.442931, 0.723487, 0.734709, - 0.250625, 0.421299, 0.768708, 0.763704, - 0.24179, 0.397085, 0.814375, 0.791818, - 0.231115, 0.370577, 0.859907, 0.817439, - 0.21922, 0.34232, 0.906715, 0.843202, - 0.205658, 0.312627, 0.953943, 0.866639, - 0.190563, 0.280933, 1.00185, 0.888129, - 0.173978, 0.248393, 1.05105, 0.907239, - 0.155485, 0.216007, 1.09704, 0.923893, - 0.134782, 0.183233, 1.13857, 0.938882, - 0.11249, 0.150376, 1.17539, 0.952464, - 0.0890706, 0.117177, 1.20924, 0.968529, - 0.0646523, 0.0813095, 1.24055, 0.984763, - 0.038606, 0.0439378, 1.27018, 1.00053, - 0.01238, 0.00598668, 1.29873, 0.0437928, - 4.09594e-06, 0.204012, 8.79224e-07, 0.0440166, - 0.000103395, 0.205049, 2.21946e-05, 0.0440529, - 0.000413633, 0.205225, 8.87981e-05, 0.0440493, - 0.000930594, 0.2052, 0.000199858, 0.0439884, - 0.00165352, 0.204901, 0.000355495, 0.0440716, - 0.0025849, 0.205255, 0.000556983, 0.0440968, - 0.00372222, 0.205311, 0.000805326, 0.0441359, - 0.00506478, 0.205391, 0.00110333, 0.0442231, - 0.00660384, 0.205638, 0.00145768, 0.0443254, - 0.00835246, 0.205877, 0.00187275, 0.0444832, - 0.0102992, 0.20627, 0.00235938, 0.0447001, - 0.0124449, 0.206796, 0.0029299, 0.0450168, - 0.0147935, 0.207593, 0.0036005, 0.0454816, - 0.017336, 0.208819, 0.00439246, 0.0462446, - 0.0201156, 0.211036, 0.00533864, 0.0473694, - 0.0231568, 0.214388, 0.00646984, 0.0490191, - 0.0264941, 0.219357, 0.00783856, 0.0512776, - 0.030184, 0.226061, 0.00950182, 0.0541279, - 0.0342661, 0.234094, 0.0115156, 0.0578989, - 0.0388539, 0.244297, 0.0139687, 0.0620835, - 0.0438735, 0.254457, 0.0169015, 0.0673497, - 0.04951, 0.266706, 0.0204554, 0.0731759, - 0.0556263, 0.278753, 0.0246606, 0.0803937, - 0.0624585, 0.29309, 0.0297126, 0.0879287, - 0.0697556, 0.305856, 0.0355868, 0.0970669, - 0.0778795, 0.321059, 0.0425768, 0.106508, - 0.0863541, 0.333873, 0.05056, 0.11776, - 0.0955935, 0.349008, 0.0598972, 0.130081, - 0.105438, 0.363776, 0.0706314, 0.144454, - 0.115899, 0.380112, 0.0828822, 0.1596, - 0.126827, 0.394843, 0.0967611, 0.176097, - 0.138161, 0.409033, 0.112381, 0.194726, - 0.149904, 0.424257, 0.129952, 0.213944, - 0.161675, 0.436945, 0.149333, 0.235516, - 0.173659, 0.450176, 0.170892, 0.260564, - 0.185963, 0.466305, 0.194984, 0.285183, - 0.197582, 0.477328, 0.220805, 0.311095, - 0.208697, 0.486566, 0.248694, 0.338924, - 0.219519, 0.494811, 0.279015, 0.369757, - 0.229766, 0.504065, 0.311725, 0.3996, - 0.238879, 0.507909, 0.345844, 0.430484, - 0.246802, 0.509805, 0.381749, 0.46413, - 0.253924, 0.511436, 0.420251, 0.497077, - 0.259319, 0.508787, 0.459957, 0.530434, - 0.263297, 0.50394, 0.501356, 0.565725, - 0.265619, 0.49804, 0.544252, 0.599254, - 0.265842, 0.487346, 0.587856, 0.631251, - 0.263978, 0.472975, 0.631969, 0.663972, - 0.26043, 0.457135, 0.677471, 0.697724, - 0.255358, 0.439844, 0.723744, 0.727725, - 0.248308, 0.417872, 0.770653, 0.756417, - 0.239181, 0.39273, 0.817357, 0.785419, - 0.22814, 0.367839, 0.864221, 0.81266, - 0.215681, 0.339449, 0.912701, 0.839391, - 0.201623, 0.309279, 0.962419, 0.86366, - 0.185624, 0.278029, 1.0122, 0.885028, - 0.16797, 0.245294, 1.06186, 0.904639, - 0.148336, 0.212689, 1.10934, 0.922048, - 0.12637, 0.179616, 1.15063, 0.936952, - 0.102928, 0.146749, 1.18885, 0.951895, - 0.0785268, 0.112733, 1.22352, 0.967198, - 0.0530153, 0.0760056, 1.25681, 0.984405, - 0.02649, 0.0383183, 1.28762, 1.00021, 0.00070019, - 0.00020039, 1.31656, 0.0325964, - 3.55447e-06, 0.176706, 6.55682e-07, 0.0329333, - 8.99174e-05, 0.178527, 1.65869e-05, 0.0329181, - 0.000359637, 0.178453, 6.63498e-05, 0.0329085, - 0.000808991, 0.178383, 0.000149332, 0.0329181, - 0.00143826, 0.178394, 0.000265873, 0.0329425, - 0.00224678, 0.178517, 0.000416597, 0.0329511, - 0.00323575, 0.17849, 0.000603299, 0.033011, - 0.00439875, 0.178695, 0.000829422, 0.0330733, - 0.00574059, 0.178843, 0.00109908, 0.0331857, - 0.00725896, 0.179176, 0.00141933, 0.0333445, - 0.00895289, 0.179618, 0.0017999, 0.0335674, - 0.0108219, 0.180238, 0.00225316, 0.033939, - 0.0128687, 0.181417, 0.00279765, 0.0345239, - 0.015114, 0.183395, 0.0034564, 0.0354458, - 0.017596, 0.186616, 0.00425864, 0.0368313, - 0.0203524, 0.191547, 0.00524936, 0.0386115, - 0.0234105, 0.197508, 0.00647033, 0.0410303, - 0.0268509, 0.205395, 0.00798121, 0.0442245, - 0.0307481, 0.215365, 0.0098557, 0.0478659, - 0.0350863, 0.225595, 0.0121417, 0.0522416, - 0.0399506, 0.236946, 0.0149385, 0.0574513, - 0.045357, 0.249442, 0.0183189, 0.0631208, - 0.0512863, 0.261222, 0.0223644, 0.0701124, - 0.0579273, 0.275418, 0.0272418, 0.0777331, - 0.0650652, 0.288989, 0.0329458, 0.0862709, - 0.0728813, 0.302546, 0.0396819, 0.096103, - 0.081363, 0.317164, 0.04757, 0.106976, - 0.0904463, 0.331733, 0.0567012, 0.119175, - 0.100105, 0.34661, 0.067202, 0.132919, - 0.110375, 0.362249, 0.0792588, 0.147727, - 0.121115, 0.376978, 0.0928672, 0.163618, - 0.132299, 0.390681, 0.108228, 0.182234, - 0.143887, 0.406571, 0.125502, 0.201809, - 0.155827, 0.42042, 0.144836, 0.225041, - 0.168357, 0.438411, 0.166706, 0.247621, - 0.18004, 0.450368, 0.189909, 0.27097, - 0.191536, 0.460083, 0.215251, 0.296658, - 0.203024, 0.469765, 0.243164, 0.325892, - 0.214056, 0.481837, 0.273388, 0.35406, - 0.224104, 0.487474, 0.305344, 0.384372, - 0.233489, 0.492773, 0.339741, 0.41749, - 0.241874, 0.498451, 0.376287, 0.45013, - 0.248834, 0.499632, 0.414195, 0.481285, - 0.254658, 0.495233, 0.454077, 0.519183, - 0.259367, 0.496401, 0.496352, 0.551544, - 0.261818, 0.487686, 0.538798, 0.587349, - 0.262964, 0.479453, 0.583626, 0.621679, - 0.262128, 0.467709, 0.629451, 0.654991, - 0.258998, 0.452123, 0.67566, 0.686873, - 0.254119, 0.433495, 0.723248, 0.719801, - 0.246946, 0.413657, 0.771156, 0.750355, - 0.237709, 0.390366, 0.81989, 0.780033, - 0.226549, 0.364947, 0.868601, 0.809254, - 0.214186, 0.337256, 0.920034, 0.836576, - 0.199639, 0.307395, 0.971706, 0.861774, - 0.183169, 0.275431, 1.02479, 0.885707, - 0.165111, 0.243431, 1.07837, 0.904742, - 0.144363, 0.210921, 1.12783, 0.915604, - 0.121305, 0.17647, 1.17254, 0.930959, - 0.0962119, 0.143106, 1.21012, 0.948404, - 0.069969, 0.108112, 1.24474, 0.967012, - 0.0427586, 0.0708478, 1.27718, 0.984183, - 0.0147043, 0.032335, 1.3083, 0.999577, 0.0142165, - 0.00726867, 1.3382, 0.0229227, - 2.99799e-06, 0.148623, 4.62391e-07, 0.0232194, - 7.58796e-05, 0.15054, 1.17033e-05, 0.0232315, - 0.000303636, 0.15063, 4.68397e-05, 0.0232354, - 0.000683189, 0.150624, 0.000105472, 0.0232092, - 0.0012136, 0.150445, 0.000187744, 0.0232523, - 0.00189765, 0.150679, 0.000294847, 0.0232828, - 0.00273247, 0.150789, 0.000428013, 0.0233371, - 0.00371287, 0.150995, 0.000591134, 0.0234015, - 0.00484794, 0.15118, 0.000787642, 0.023514, - 0.00612877, 0.151562, 0.00102547, 0.023679, - 0.00756125, 0.152116, 0.00131351, 0.0239559, - 0.00914651, 0.153162, 0.00166594, 0.0244334, - 0.010904, 0.155133, 0.00210182, 0.025139, - 0.0128615, 0.158035, 0.00264406, 0.0262598, - 0.0150628, 0.162751, 0.00332923, 0.0277875, - 0.0175532, 0.168944, 0.00419773, 0.0298472, - 0.0203981, 0.176835, 0.00530034, 0.0325444, - 0.023655, 0.186686, 0.00669777, 0.0355581, - 0.0272982, 0.196248, 0.00842661, 0.0392841, - 0.0314457, 0.207352, 0.0105854, 0.0436815, - 0.0361157, 0.219279, 0.0132458, 0.0485272, - 0.0412932, 0.230728, 0.0164736, 0.0541574, - 0.0470337, 0.242994, 0.0203715, 0.0609479, - 0.0535002, 0.257042, 0.0250953, 0.0685228, - 0.0605409, 0.27102, 0.0306856, 0.0768042, - 0.0680553, 0.28406, 0.037193, 0.0864844, - 0.0765011, 0.299186, 0.0449795, 0.0969415, - 0.0852674, 0.3132, 0.0538316, 0.108478, - 0.0947333, 0.327138, 0.0641149, 0.121705, - 0.10481, 0.342345, 0.0759185, 0.136743, - 0.115474, 0.358472, 0.0894116, 0.152986, - 0.126536, 0.374067, 0.104562, 0.170397, - 0.138061, 0.388267, 0.121632, 0.191392, - 0.150203, 0.406467, 0.140996, 0.211566, - 0.161751, 0.418641, 0.161696, 0.233567, - 0.173407, 0.430418, 0.184557, 0.257769, - 0.185397, 0.44277, 0.210092, 0.28531, - 0.197048, 0.457191, 0.237827, 0.311726, - 0.20784, 0.464712, 0.267253, 0.340537, - 0.218345, 0.472539, 0.299332, 0.372921, - 0.228306, 0.482331, 0.333988, 0.402924, - 0.236665, 0.484378, 0.369722, 0.434475, - 0.244097, 0.484717, 0.407836, 0.469736, - 0.250547, 0.487093, 0.448465, 0.505045, - 0.25511, 0.485575, 0.490263, 0.540262, - 0.258444, 0.481225, 0.534495, 0.576347, - 0.259903, 0.473481, 0.579451, 0.608656, - 0.259572, 0.4603, 0.625604, 0.646679, - 0.257908, 0.450341, 0.674511, 0.679902, - 0.253663, 0.431561, 0.723269, 0.714159, - 0.247419, 0.412684, 0.773263, 0.745345, - 0.239122, 0.389388, 0.824182, 0.778248, - 0.228837, 0.365361, 0.876634, 0.807208, - 0.216197, 0.337667, 0.92945, 0.835019, - 0.201772, 0.307197, 0.985261, 0.860261, - 0.185291, 0.274205, 1.04299, 0.877601, - 0.165809, 0.240178, 1.09816, 0.898211, - 0.143897, 0.207571, 1.14694, 0.915789, - 0.119513, 0.174904, 1.19008, 0.931831, - 0.0932919, 0.141423, 1.2297, 0.949244, - 0.0656528, 0.105603, 1.26553, 0.967527, - 0.0370262, 0.0679551, 1.29986, 0.984139, - 0.00730117, 0.0283133, 1.33252, 0.999713, 0.0234648, - 0.0121785, 1.36397, 0.0152135, - 2.45447e-06, 0.122795, 3.04092e-07, 0.0151652, - 6.15778e-05, 0.122399, 7.6292e-06, 0.0151181, - 0.000245948, 0.122023, 3.04802e-05, 0.0151203, - 0.000553394, 0.12203, 6.86634e-05, 0.015125, - 0.000983841, 0.122037, 0.000122463, 0.0151427, - 0.00153774, 0.12214, 0.000192706, 0.0151708, - 0.0022103, 0.122237, 0.000281219, 0.0152115, - 0.00300741, 0.12238, 0.000390804, 0.0152877, - 0.00392494, 0.1227, 0.000526317, 0.015412, - 0.00496597, 0.123244, 0.00069443, 0.0156201, - 0.00613314, 0.124228, 0.00090547, 0.0159658, - 0.00744113, 0.125945, 0.0011732, 0.0165674, - 0.00892546, 0.129098, 0.00151888, 0.017487, - 0.010627, 0.133865, 0.00197007, 0.018839, - 0.0126043, 0.140682, 0.0025637, 0.020554, - 0.0148814, 0.148534, 0.00333637, 0.0226727, - 0.0175123, 0.157381, 0.00433738, 0.0251879, - 0.0205266, 0.166685, 0.00561664, 0.0283635, - 0.0240319, 0.177796, 0.00725563, 0.0318694, - 0.0279432, 0.188251, 0.00928811, 0.0361044, - 0.0324313, 0.200038, 0.011835, 0.0406656, - 0.0373527, 0.210685, 0.0149146, 0.0463846, - 0.0430132, 0.224182, 0.0187254, 0.0525696, - 0.0491013, 0.23634, 0.0232283, 0.0598083, - 0.0559175, 0.250013, 0.0286521, 0.0679437, - 0.0633657, 0.263981, 0.0350634, 0.0771181, - 0.0714602, 0.278072, 0.0425882, 0.0881273, - 0.0803502, 0.29511, 0.0514487, 0.0996628, - 0.0896903, 0.309976, 0.0615766, 0.112702, - 0.099644, 0.325611, 0.0732139, 0.126488, - 0.109829, 0.339321, 0.0862324, 0.142625, - 0.120859, 0.35574, 0.101275, 0.15953, - 0.131956, 0.369845, 0.117892, 0.176991, - 0.143145, 0.38146, 0.136205, 0.199715, - 0.155292, 0.40052, 0.157252, 0.220787, - 0.167066, 0.412055, 0.179966, 0.243697, - 0.178396, 0.423133, 0.204418, 0.272106, - 0.190433, 0.439524, 0.232141, 0.297637, - 0.201265, 0.447041, 0.261109, 0.325273, - 0.211834, 0.454488, 0.292627, 0.357219, - 0.221889, 0.465004, 0.326669, 0.387362, - 0.230729, 0.468527, 0.362426, 0.423131, - 0.23924, 0.475836, 0.401533, 0.45543, - 0.246067, 0.475017, 0.441902, 0.493393, - 0.251557, 0.478017, 0.484239, 0.526253, - 0.255571, 0.4709, 0.528586, 0.560554, - 0.257752, 0.463167, 0.574346, 0.599306, - 0.258076, 0.456452, 0.621655, 0.634541, - 0.256471, 0.443725, 0.670492, 0.668907, - 0.253283, 0.428719, 0.721943, 0.705619, - 0.247562, 0.411348, 0.772477, 0.739034, - 0.240626, 0.388939, 0.8264, 0.771408, - 0.231493, 0.36425, 0.881702, 0.803312, - 0.220125, 0.337321, 0.9385, 0.828457, - 0.206645, 0.305364, 0.997437, 0.854819, - 0.190664, 0.273715, 1.05693, 0.878666, - 0.171429, 0.242218, 1.11251, 0.898404, - 0.149235, 0.209556, 1.16398, 0.917416, - 0.12435, 0.176863, 1.21014, 0.933133, - 0.0972703, 0.142775, 1.25178, 0.95066, - 0.0683607, 0.106735, 1.29028, 0.968589, - 0.0378724, 0.0681609, 1.32703, 0.984776, - 0.00605712, 0.0273966, 1.36158, 0.99994, 0.0263276, - 0.0138124, 1.3943, 0.00867437, - 1.86005e-06, 0.0928979, 1.73682e-07, 0.00864003, - 4.66389e-05, 0.0925237, 4.35505e-06, 0.00864593, - 0.000186594, 0.0925806, 1.74322e-05, 0.00864095, - 0.000419639, 0.0924903, 3.92862e-05, 0.00863851, - 0.000746272, 0.0924589, 7.02598e-05, 0.00868531, - 0.00116456, 0.0929, 0.000111188, 0.00869667, - 0.00167711, 0.0928529, 0.000163867, 0.00874332, - 0.00228051, 0.0930914, 0.00023104, 0.00882709, - 0.00297864, 0.0935679, 0.00031741, 0.00898874, - 0.00377557, 0.0946165, 0.000430186, 0.00929346, - 0.00469247, 0.0967406, 0.000580383, 0.00978271, - 0.00575491, 0.100084, 0.000783529, 0.0105746, - 0.00701514, 0.105447, 0.00106304, 0.0116949, - 0.00851797, 0.112494, 0.00144685, 0.0130419, - 0.0102757, 0.119876, 0.00196439, 0.0148375, - 0.012381, 0.129034, 0.00266433, 0.0168725, - 0.01482, 0.137812, 0.00358364, 0.0193689, - 0.0176563, 0.147696, 0.00478132, 0.0222691, - 0.0209211, 0.157795, 0.00631721, 0.0256891, - 0.0246655, 0.168431, 0.00826346, 0.0294686, - 0.0288597, 0.178587, 0.0106714, 0.0340412, - 0.0336441, 0.190251, 0.0136629, 0.0393918, - 0.039033, 0.202999, 0.0173272, 0.0453947, - 0.0450087, 0.215655, 0.0217448, 0.0521936, - 0.0515461, 0.228686, 0.0269941, 0.0600279, - 0.058817, 0.242838, 0.033272, 0.0692398, - 0.0667228, 0.258145, 0.0406457, 0.0793832, - 0.0752401, 0.273565, 0.0492239, 0.0902297, - 0.0841851, 0.287735, 0.0590105, 0.102014, - 0.0936479, 0.301161, 0.0702021, 0.116054, - 0.103967, 0.317438, 0.0832001, 0.13191, - 0.114622, 0.334166, 0.0977951, 0.148239, - 0.125452, 0.348192, 0.113985, 0.165809, - 0.136453, 0.361094, 0.131928, 0.184616, - 0.147648, 0.373534, 0.151811, 0.207491, - 0.159607, 0.39101, 0.174476, 0.230106, - 0.171119, 0.402504, 0.198798, 0.257036, - 0.182906, 0.418032, 0.225796, 0.281172, - 0.193605, 0.425468, 0.254027, 0.312034, - 0.204771, 0.440379, 0.285713, 0.340402, - 0.214988, 0.445406, 0.319196, 0.370231, - 0.224711, 0.44968, 0.35537, 0.407105, - 0.233516, 0.460747, 0.393838, 0.439037, - 0.240801, 0.460624, 0.433747, 0.47781, - 0.24762, 0.465957, 0.477234, 0.510655, - 0.251823, 0.460054, 0.52044, 0.550584, - 0.255552, 0.459172, 0.567853, 0.585872, - 0.257036, 0.450311, 0.615943, 0.620466, - 0.257535, 0.437763, 0.667693, 0.660496, - 0.255248, 0.426639, 0.718988, 0.695578, - 0.251141, 0.409185, 0.772503, 0.732176, - 0.244718, 0.39015, 0.827023, 0.760782, - 0.236782, 0.362594, 0.885651, 0.79422, - 0.225923, 0.33711, 0.943756, 0.824521, - 0.213855, 0.308272, 1.00874, 0.854964, - 0.197723, 0.278529, 1.06764, 0.878065, - 0.179209, 0.246208, 1.12836, 0.899834, - 0.157569, 0.21329, 1.18318, 0.918815, - 0.133206, 0.181038, 1.23161, 0.934934, - 0.106545, 0.146993, 1.27644, 0.952115, - 0.0780574, 0.111175, 1.31842, 0.96906, - 0.0478279, 0.0728553, 1.35839, 0.985178, - 0.0160014, 0.032579, 1.39697, 1.00039, 0.0173126, - 0.0095256, 1.43312, 0.00384146, - 1.24311e-06, 0.0613583, 7.78271e-08, 0.00390023, - 3.14043e-05, 0.0622919, 1.96626e-06, 0.00389971, - 0.000125622, 0.0622632, 7.87379e-06, 0.00389491, - 0.000282352, 0.0620659, 1.778e-05, 0.00391618, - 0.000502512, 0.0624687, 3.20918e-05, 0.00392662, - 0.000784458, 0.0625113, 5.15573e-05, 0.00396053, - 0.00112907, 0.0628175, 7.78668e-05, 0.00401911, - 0.00153821, 0.0633286, 0.000113811, 0.00414994, - 0.0020208, 0.0646443, 0.00016445, 0.00441223, - 0.00260007, 0.0673886, 0.000237734, 0.00484427, - 0.0033097, 0.0716528, 0.000345929, 0.00549109, - 0.00418966, 0.0774998, 0.000505987, 0.00636293, - 0.00527331, 0.0844758, 0.000739208, 0.00746566, - 0.00660428, 0.0921325, 0.00107347, 0.00876625, - 0.00818826, 0.0997067, 0.00153691, 0.0103125, - 0.0100811, 0.107433, 0.00217153, 0.0123309, - 0.0123643, 0.117088, 0.00303427, 0.0146274, - 0.0150007, 0.126438, 0.00416018, 0.0172295, - 0.0180531, 0.135672, 0.00561513, 0.0204248, - 0.0215962, 0.146244, 0.007478, 0.0241597, - 0.0256234, 0.157481, 0.00981046, 0.0284693, - 0.0302209, 0.169125, 0.0127148, 0.033445, - 0.0353333, 0.181659, 0.0162453, 0.0391251, - 0.0410845, 0.1944, 0.0205417, 0.0454721, - 0.0473451, 0.207082, 0.0256333, 0.0530983, - 0.0542858, 0.221656, 0.0317036, 0.0615356, - 0.0618384, 0.236036, 0.0388319, 0.0703363, - 0.0697631, 0.248398, 0.046974, 0.0810391, - 0.0784757, 0.263611, 0.0565246, 0.0920144, - 0.0873488, 0.275857, 0.0671724, 0.105584, - 0.0973652, 0.292555, 0.0798105, 0.119506, - 0.107271, 0.306333, 0.0935945, 0.134434, - 0.117608, 0.318888, 0.109106, 0.153399, - 0.128938, 0.337552, 0.127074, 0.171258, - 0.139944, 0.349955, 0.14643, 0.191059, - 0.151288, 0.361545, 0.168, 0.215069, - 0.163018, 0.378421, 0.192082, 0.237838, - 0.174226, 0.38879, 0.217838, 0.266965, - 0.186063, 0.405857, 0.246931, 0.292827, - 0.196909, 0.414146, 0.277505, 0.324352, - 0.207473, 0.426955, 0.310711, 0.354427, - 0.217713, 0.433429, 0.346794, 0.389854, - 0.227183, 0.443966, 0.385237, 0.420749, - 0.235131, 0.44471, 0.424955, 0.459597, - 0.242786, 0.451729, 0.468446, 0.495316, - 0.248767, 0.45072, 0.513422, 0.534903, - 0.253351, 0.450924, 0.560618, 0.572369, - 0.256277, 0.445266, 0.609677, 0.612383, - 0.2576, 0.438798, 0.660995, 0.644037, - 0.256931, 0.421693, 0.713807, 0.686749, - 0.254036, 0.4109, 0.767616, 0.719814, - 0.249785, 0.390151, 0.82533, 0.754719, - 0.244283, 0.367847, 0.888311, 0.792022, - 0.235076, 0.345013, 0.948177, 0.822404, - 0.225061, 0.316193, 1.01661, 0.853084, - 0.211113, 0.287013, 1.08075, 0.879871, - 0.19449, 0.255424, 1.14501, 0.901655, - 0.174023, 0.222879, 1.20203, 0.919957, - 0.1509, 0.18989, 1.25698, 0.938412, - 0.124923, 0.15606, 1.30588, 0.953471, - 0.0968139, 0.120512, 1.3529, 0.970451, - 0.066734, 0.0828515, 1.3986, 0.985522, - 0.034734, 0.0424458, 1.44148, 1.00099, - 0.00102222, 0.000678929, 1.48398, 0.000965494, - 6.27338e-07, 0.0306409, 1.97672e-08, 0.00099168, - 1.58573e-05, 0.0314638, 4.99803e-07, 0.000991068, - 6.34012e-05, 0.031363, 2.00682e-06, 0.000974567, - 0.00014144, 0.03036, 4.57312e-06, 0.000998079, - 0.000252812, 0.031496, 8.60131e-06, 0.00102243, - 0.000396506, 0.0319955, 1.48288e-05, 0.00107877, - 0.000577593, 0.0331376, 2.49141e-05, 0.00121622, - 0.000816816, 0.0359396, 4.23011e-05, 0.0014455, - 0.00113761, 0.0399652, 7.24613e-05, 0.00178791, - 0.00156959, 0.0450556, 0.000123929, 0.00225668, - 0.00214064, 0.0508025, 0.000208531, 0.00285627, - 0.00287655, 0.0568443, 0.000341969, 0.0035991, - 0.00380271, 0.0630892, 0.000544158, 0.00455524, - 0.00496264, 0.0702204, 0.000842423, 0.00569143, - 0.0063793, 0.0773426, 0.00126704, 0.00716928, - 0.00813531, 0.0860839, 0.00186642, 0.00885307, - 0.0101946, 0.0944079, 0.00267014, 0.0109316, - 0.0126386, 0.103951, 0.00374033, 0.0133704, - 0.0154876, 0.113786, 0.0051304, 0.0161525, - 0.0187317, 0.123477, 0.00688858, 0.0194267, - 0.0224652, 0.133986, 0.00910557, 0.0230967, - 0.0265976, 0.143979, 0.0118074, 0.0273627, - 0.0312848, 0.154645, 0.0151266, 0.0323898, - 0.0365949, 0.166765, 0.0191791, 0.0379225, - 0.0422914, 0.177932, 0.0239236, 0.0447501, - 0.0487469, 0.19167, 0.0296568, 0.0519391, - 0.0556398, 0.203224, 0.0362924, 0.0599464, - 0.0631646, 0.215652, 0.0440585, 0.0702427, - 0.0714308, 0.232089, 0.0531619, 0.0806902, - 0.0800605, 0.245258, 0.0634564, 0.0923194, - 0.0892815, 0.258609, 0.0752481, 0.106938, - 0.09931, 0.276654, 0.0888914, 0.121238, - 0.109575, 0.289847, 0.104055, 0.138817, - 0.120461, 0.307566, 0.121266, 0.15595, - 0.131209, 0.320117, 0.139944, 0.178418, - 0.143049, 0.339677, 0.161591, 0.197875, - 0.154074, 0.349886, 0.184303, 0.224368, - 0.166307, 0.369352, 0.210669, 0.252213, - 0.178051, 0.386242, 0.238895, 0.277321, - 0.189335, 0.395294, 0.269182, 0.310332, - 0.200683, 0.412148, 0.302508, 0.338809, - 0.210856, 0.418266, 0.337264, 0.372678, - 0.220655, 0.428723, 0.374881, 0.405632, - 0.230053, 0.433887, 0.415656, 0.442293, - 0.237993, 0.439911, 0.457982, 0.477256, - 0.244897, 0.440175, 0.502831, 0.515592, - 0.250657, 0.441079, 0.550277, 0.550969, - 0.255459, 0.435219, 0.601102, 0.592883, - 0.257696, 0.432882, 0.651785, 0.629092, - 0.259894, 0.421054, 0.708961, 0.672033, - 0.258592, 0.41177, 0.763806, 0.709147, - 0.256525, 0.395267, 0.824249, 0.745367, - 0.254677, 0.375013, 0.8951, 0.784715, - 0.247892, 0.353906, 0.959317, 0.818107, - 0.240162, 0.327801, 1.03153, 0.847895, - 0.229741, 0.298821, 1.10601, 0.879603, - 0.213084, 0.269115, 1.164, 0.902605, - 0.195242, 0.236606, 1.22854, 0.922788, - 0.174505, 0.203442, 1.29017, 0.944831, - 0.150169, 0.169594, 1.34157, 0.959656, - 0.124099, 0.135909, 1.3956, 0.972399, - 0.0960626, 0.0990563, 1.45128, 0.986549, - 0.0657097, 0.0602348, 1.50312, 1.00013, - 0.0333558, 0.0186694, 1.55364, 6.19747e-06, - 1e-07, 0.00778326, 7.96756e-11, 2.37499e-08, - 9.99999e-08, 2.82592e-05, 1.14596e-10, 1.00292e-06, - 1.66369e-06, 0.000250354, 6.77492e-09, 3.50752e-06, - 6.37769e-06, 0.000357289, 6.31655e-08, 8.26445e-06, - 1.74689e-05, 0.000516179, 3.1851e-07, 2.42481e-05, - 4.50868e-05, 0.0010223, 1.30577e-06, 4.55631e-05, - 8.9044e-05, 0.00144302, 3.74587e-06, 9.71222e-05, - 0.000178311, 0.00241912, 1.02584e-05, 0.000171403, - 0.000313976, 0.00354938, 2.36481e-05, 0.000292747, - 0.000520026, 0.00513765, 4.96014e-05, 0.000789827, - 0.00118187, 0.0238621, 0.000139056, 0.00114093, - 0.00171827, 0.0286691, 0.000244093, 0.00176119, - 0.00249667, 0.0368565, 0.000420623, 0.0022233, - 0.00333742, 0.0400469, 0.00065673, 0.00343382, - 0.00481976, 0.0535751, 0.00109323, 0.00427602, - 0.00600755, 0.057099, 0.00155268, 0.00461435, - 0.00737637, 0.0551084, 0.00215031, 0.00695698, - 0.00971401, 0.0715767, 0.00316529, 0.00867619, - 0.0120943, 0.0793314, 0.00436995, 0.0106694, - 0.0148202, 0.0869391, 0.0058959, 0.0140351, - 0.0183501, 0.101572, 0.00798757, 0.0168939, - 0.022006, 0.11018, 0.0104233, 0.020197, - 0.0261568, 0.119041, 0.0134167, 0.0254702, - 0.0312778, 0.135404, 0.0173009, 0.0298384, - 0.0362469, 0.1437, 0.0215428, 0.035159, - 0.042237, 0.15512, 0.0268882, 0.0427685, - 0.0488711, 0.17128, 0.033235, 0.0494848, - 0.0557997, 0.181813, 0.0404443, 0.0592394, - 0.0635578, 0.198745, 0.0490043, 0.0681463, - 0.071838, 0.210497, 0.0588239, 0.0804753, - 0.0809297, 0.228864, 0.0702835, 0.0942205, - 0.0906488, 0.247008, 0.0834012, 0.106777, - 0.100216, 0.258812, 0.0975952, 0.124471, - 0.110827, 0.278617, 0.114162, 0.138389, - 0.121193, 0.287049, 0.131983, 0.159543, - 0.13253, 0.307151, 0.152541, 0.176432, - 0.143611, 0.31564, 0.174673, 0.201723, - 0.15548, 0.33538, 0.199842, 0.229721, - 0.167166, 0.355256, 0.227097, 0.250206, - 0.178238, 0.360047, 0.256014, 0.282118, - 0.189905, 0.378761, 0.28855, 0.312821, - 0.201033, 0.39181, 0.323348, 0.341482, - 0.211584, 0.397716, 0.360564, 0.377368, - 0.221314, 0.410141, 0.400004, 0.418229, - 0.230474, 0.423485, 0.442371, 0.444881, - 0.239443, 0.418874, 0.488796, 0.488899, - 0.245987, 0.427545, 0.535012, 0.520317, - 0.253948, 0.422147, 0.589678, 0.568566, - 0.256616, 0.42719, 0.637683, 0.599607, - 0.26376, 0.415114, 0.703363, 0.64222, - 0.268687, 0.408715, 0.771363, 0.685698, - 0.2694, 0.399722, 0.83574, 0.732327, - 0.266642, 0.388651, 0.897764, 0.769873, - 0.267712, 0.369198, 0.983312, 0.806733, - 0.263479, 0.346802, 1.06222, 0.843466, - 0.254575, 0.321368, 1.13477, 0.873008, - 0.242749, 0.29211, 1.20712, 0.908438, - 0.22725, 0.262143, 1.27465, 0.936321, - 0.207621, 0.228876, 1.33203, 0.950353, - 0.187932, 0.19484, 1.40439, 0.96442, - 0.165154, 0.163178, 1.4732, 0.979856, - 0.139302, 0.127531, 1.53574, 0.982561, - 0.11134, 0.0903457, 1.59982, 0.996389, - 0.0808124, 0.0489007, 1.6577 ]; + RectAreaLightTexturesLib.init(); - const LTC_MAT_2 = [ 1, 0, 0, 0, 1, 7.91421e-31, 0, 0, 1, 1.04392e-24, 0, 0, 1, 3.49405e-21, 0, 0, 1, 1.09923e-18, 0, 0, 1, 9.47414e-17, 0, 0, 1, 3.59627e-15, 0, 0, 1, 7.72053e-14, 0, 0, 1, 1.08799e-12, 0, 0, 1, 1.10655e-11, 0, 0, 1, 8.65818e-11, 0, 0, 0.999998, 5.45037e-10, 0, 0, 0.999994, 2.85095e-09, 0, 0, 0.999989, 1.26931e-08, 0, 0, 0.999973, 4.89938e-08, 0, 0, 0.999947, 1.66347e-07, 0, 0, 0.999894, 5.02694e-07, 0, 0, 0.999798, 1.36532e-06, 0, 0, 0.999617, 3.35898e-06, 0, 0, 0.999234, 7.52126e-06, 0, 0, 0.998258, 1.52586e-05, 0, 0, 0.99504, 2.66207e-05, 0, 0, 0.980816, 2.36802e-05, 0, 0, 0.967553, 2.07684e-06, 0, 0, 0.966877, 4.03733e-06, 0, 0, 0.965752, 7.41174e-06, 0, 0, 0.96382, 1.27746e-05, 0, 0, 0.960306, 2.02792e-05, 0, 0, 0.953619, 2.80232e-05, 0, 0, 0.941103, 2.78816e-05, 0, 0, 0.926619, 1.60221e-05, 0, 0, 0.920983, 2.35164e-05, 0, 0, 0.912293, 3.11924e-05, 0, 0.0158731, 0.899277, 3.48118e-05, 0, 0.0476191, 0.880884, 2.6041e-05, 0, 0.0793651, 0.870399, 3.38726e-05, 0, 0.111111, 0.856138, 3.92906e-05, 0, 0.142857, 0.837436, 3.72874e-05, 0, 0.174603, 0.820973, 3.92558e-05, 0, 0.206349, 0.803583, 4.34658e-05, 0, 0.238095, 0.782168, 4.0256e-05, 0, 0.269841, 0.764107, 4.48159e-05, 0, 0.301587, 0.743092, 4.57627e-05, 0, 0.333333, 0.721626, 4.55314e-05, 0, 0.365079, 0.700375, 4.77335e-05, 0, 0.396825, 0.677334, 4.61072e-05, 0, 0.428571, 0.655702, 4.84393e-05, 0, 0.460317, 0.632059, 4.64583e-05, 0, 0.492064, 0.610125, 4.83923e-05, 0, 0.52381, 0.58653, 4.64342e-05, 0, 0.555556, 0.564508, 4.77033e-05, 0, 0.587302, 0.541405, 4.59263e-05, 0, 0.619048, 0.519556, 4.6412e-05, 0, 0.650794, 0.497292, 4.48913e-05, 0, 0.68254, 0.475898, 4.45789e-05, 0, 0.714286, 0.454722, 4.33496e-05, 0, 0.746032, 0.434042, 4.23054e-05, 0, 0.777778, 0.414126, 4.13737e-05, 0, 0.809524, 0.394387, 3.97265e-05, 0, 0.84127, 0.375841, 3.90709e-05, 0, 0.873016, 0.357219, 3.69938e-05, 0, 0.904762, 0.340084, 3.65618e-05, 0, 0.936508, 0.322714, 3.42533e-05, 0, 0.968254, 0.306974, 3.39596e-05, 0, 1, 1, 1.01524e-18, 0, 0, 1, 1.0292e-18, 0, 0, 1, 1.30908e-18, 0, 0, 1, 4.73331e-18, 0, 0, 1, 6.25319e-17, 0, 0, 1, 1.07932e-15, 0, 0, 1, 1.63779e-14, 0, 0, 1, 2.03198e-13, 0, 0, 1, 2.04717e-12, 0, 0, 0.999999, 1.68995e-11, 0, 0, 0.999998, 1.15855e-10, 0, 0, 0.999996, 6.6947e-10, 0, 0, 0.999991, 3.30863e-09, 0, 0, 0.999983, 1.41737e-08, 0, 0, 0.999968, 5.32626e-08, 0, 0, 0.99994, 1.77431e-07, 0, 0, 0.999891, 5.28835e-07, 0, 0, 0.999797, 1.42169e-06, 0, 0, 0.999617, 3.47057e-06, 0, 0, 0.999227, 7.7231e-06, 0, 0, 0.998239, 1.55753e-05, 0, 0, 0.994937, 2.68495e-05, 0, 0, 0.980225, 2.13742e-05, 0, 0, 0.967549, 2.1631e-06, 0, 0, 0.966865, 4.17989e-06, 0, 0, 0.965739, 7.63341e-06, 0, 0, 0.963794, 1.30892e-05, 0, 0, 0.960244, 2.06456e-05, 0, 0, 0.953495, 2.82016e-05, 0, 0.000148105, 0.940876, 2.71581e-05, 0, 0.002454, 0.926569, 1.64159e-05, 0, 0.00867491, 0.920905, 2.39521e-05, 0, 0.01956, 0.912169, 3.15127e-05, 0, 0.035433, 0.899095, 3.46626e-05, 0, 0.056294, 0.882209, 2.90223e-05, 0, 0.0818191, 0.870272, 3.42992e-05, 0, 0.111259, 0.855977, 3.94164e-05, 0, 0.142857, 0.837431, 3.72343e-05, 0, 0.174603, 0.820826, 3.96691e-05, 0, 0.206349, 0.803408, 4.35395e-05, 0, 0.238095, 0.782838, 4.19579e-05, 0, 0.269841, 0.763941, 4.50953e-05, 0, 0.301587, 0.742904, 4.55847e-05, 0, 0.333333, 0.721463, 4.58833e-05, 0, 0.365079, 0.700197, 4.77159e-05, 0, 0.396825, 0.677501, 4.70641e-05, 0, 0.428571, 0.655527, 4.84732e-05, 0, 0.460317, 0.6324, 4.76834e-05, 0, 0.492064, 0.609964, 4.84213e-05, 0, 0.52381, 0.586839, 4.75541e-05, 0, 0.555556, 0.564353, 4.76951e-05, 0, 0.587302, 0.541589, 4.67611e-05, 0, 0.619048, 0.519413, 4.63493e-05, 0, 0.650794, 0.497337, 4.53994e-05, 0, 0.68254, 0.475797, 4.45308e-05, 0, 0.714286, 0.454659, 4.35787e-05, 0, 0.746032, 0.434065, 4.24839e-05, 0, 0.777778, 0.414018, 4.1436e-05, 0, 0.809524, 0.39455, 4.01902e-05, 0, 0.84127, 0.375742, 3.90813e-05, 0, 0.873016, 0.357501, 3.77116e-05, 0, 0.904762, 0.339996, 3.6535e-05, 0, 0.936508, 0.323069, 3.51265e-05, 0, 0.968254, 0.306897, 3.39112e-05, 0, 1, 1, 1.0396e-15, 0, 0, 1, 1.04326e-15, 0, 0, 1, 1.10153e-15, 0, 0, 1, 1.44668e-15, 0, 0, 1, 3.4528e-15, 0, 0, 1, 1.75958e-14, 0, 0, 1, 1.2627e-13, 0, 0, 1, 9.36074e-13, 0, 0, 1, 6.45742e-12, 0, 0, 0.999998, 4.01228e-11, 0, 0, 0.999997, 2.22338e-10, 0, 0, 0.999995, 1.0967e-09, 0, 0, 0.999991, 4.82132e-09, 0, 0, 0.999981, 1.89434e-08, 0, 0, 0.999967, 6.67716e-08, 0, 0, 0.999938, 2.12066e-07, 0, 0, 0.999886, 6.0977e-07, 0, 0, 0.999792, 1.59504e-06, 0, 0, 0.999608, 3.81191e-06, 0, 0, 0.999209, 8.33727e-06, 0, 0, 0.998179, 1.65288e-05, 0, 0, 0.994605, 2.74387e-05, 0, 0, 0.979468, 1.67316e-05, 0, 0, 0.967529, 2.42877e-06, 0, 0, 0.966836, 4.61696e-06, 0, 0, 0.96569, 8.30977e-06, 0, 0, 0.963706, 1.40427e-05, 0, 2.44659e-06, 0.960063, 2.17353e-05, 0, 0.000760774, 0.953113, 2.86606e-05, 0, 0.00367261, 0.940192, 2.47691e-05, 0, 0.00940263, 0.927731, 1.95814e-05, 0, 0.018333, 0.920669, 2.52531e-05, 0, 0.0306825, 0.911799, 3.24277e-05, 0, 0.0465556, 0.89857, 3.40982e-05, 0, 0.0659521, 0.883283, 3.19622e-05, 0, 0.0887677, 0.86989, 3.5548e-05, 0, 0.114784, 0.855483, 3.97143e-05, 0, 0.143618, 0.837987, 3.91665e-05, 0, 0.174606, 0.820546, 4.11306e-05, 0, 0.206349, 0.802878, 4.36753e-05, 0, 0.238095, 0.783402, 4.44e-05, 0, 0.269841, 0.763439, 4.58726e-05, 0, 0.301587, 0.742925, 4.67097e-05, 0, 0.333333, 0.721633, 4.78887e-05, 0, 0.365079, 0.69985, 4.81251e-05, 0, 0.396825, 0.67783, 4.91811e-05, 0, 0.428571, 0.655126, 4.88199e-05, 0, 0.460318, 0.632697, 4.96025e-05, 0, 0.492064, 0.609613, 4.8829e-05, 0, 0.52381, 0.587098, 4.92754e-05, 0, 0.555556, 0.564119, 4.82625e-05, 0, 0.587302, 0.541813, 4.82807e-05, 0, 0.619048, 0.519342, 4.71552e-05, 0, 0.650794, 0.497514, 4.66765e-05, 0, 0.68254, 0.475879, 4.55582e-05, 0, 0.714286, 0.454789, 4.46007e-05, 0, 0.746032, 0.434217, 4.35382e-05, 0, 0.777778, 0.414086, 4.21753e-05, 0, 0.809524, 0.394744, 4.12093e-05, 0, 0.84127, 0.375782, 3.96634e-05, 0, 0.873016, 0.357707, 3.86419e-05, 0, 0.904762, 0.340038, 3.70345e-05, 0, 0.936508, 0.323284, 3.59725e-05, 0, 0.968254, 0.306954, 3.436e-05, 0, 1, 1, 5.99567e-14, 0, 0, 1, 6.00497e-14, 0, 0, 1, 6.14839e-14, 0, 0, 1, 6.86641e-14, 0, 0, 1, 9.72658e-14, 0, 0, 1, 2.21271e-13, 0, 0, 1, 8.33195e-13, 0, 0, 1, 4.03601e-12, 0, 0, 0.999999, 2.06001e-11, 0, 0, 0.999998, 1.01739e-10, 0, 0, 0.999997, 4.70132e-10, 0, 0, 0.999993, 2.00436e-09, 0, 0, 0.999988, 7.83682e-09, 0, 0, 0.999979, 2.80338e-08, 0, 0, 0.999962, 9.17033e-08, 0, 0, 0.999933, 2.74514e-07, 0, 0, 0.999881, 7.53201e-07, 0, 0, 0.999783, 1.89826e-06, 0, 0, 0.999594, 4.40279e-06, 0, 0, 0.999178, 9.3898e-06, 0, 0, 0.998073, 1.81265e-05, 0, 0, 0.993993, 2.80487e-05, 0, 0, 0.979982, 1.49422e-05, 0, 0, 0.968145, 3.78481e-06, 0, 0, 0.966786, 5.3771e-06, 0, 0, 0.965611, 9.47508e-06, 0, 3.88934e-05, 0.963557, 1.56616e-05, 0, 0.0009693, 0.959752, 2.35144e-05, 0, 0.00370329, 0.952461, 2.91568e-05, 0, 0.00868428, 0.940193, 2.40102e-05, 0, 0.0161889, 0.929042, 2.31235e-05, 0, 0.0263948, 0.920266, 2.73968e-05, 0, 0.0394088, 0.911178, 3.37915e-05, 0, 0.0552818, 0.897873, 3.33629e-05, 0, 0.0740138, 0.884053, 3.51405e-05, 0, 0.0955539, 0.869455, 3.78034e-05, 0, 0.119795, 0.854655, 3.99378e-05, 0, 0.14656, 0.838347, 4.19108e-05, 0, 0.175573, 0.820693, 4.40831e-05, 0, 0.206388, 0.802277, 4.45599e-05, 0, 0.238095, 0.783634, 4.72691e-05, 0, 0.269841, 0.763159, 4.76984e-05, 0, 0.301587, 0.742914, 4.91487e-05, 0, 0.333333, 0.721662, 5.02312e-05, 0, 0.365079, 0.699668, 5.02817e-05, 0, 0.396825, 0.677839, 5.1406e-05, 0, 0.428571, 0.655091, 5.11095e-05, 0, 0.460317, 0.632665, 5.16067e-05, 0, 0.492064, 0.609734, 5.12255e-05, 0, 0.52381, 0.587043, 5.10263e-05, 0, 0.555556, 0.564298, 5.0565e-05, 0, 0.587302, 0.541769, 4.97951e-05, 0, 0.619048, 0.519529, 4.92698e-05, 0, 0.650794, 0.497574, 4.82066e-05, 0, 0.68254, 0.476028, 4.73689e-05, 0, 0.714286, 0.454961, 4.61941e-05, 0, 0.746032, 0.434341, 4.50618e-05, 0, 0.777778, 0.414364, 4.38355e-05, 0, 0.809524, 0.394832, 4.24196e-05, 0, 0.84127, 0.376109, 4.12563e-05, 0, 0.873016, 0.35779, 3.96226e-05, 0, 0.904762, 0.340379, 3.84886e-05, 0, 0.936508, 0.323385, 3.68214e-05, 0, 0.968254, 0.307295, 3.56636e-05, 0, 1, 1, 1.06465e-12, 0, 0, 1, 1.06555e-12, 0, 0, 1, 1.07966e-12, 0, 0, 1, 1.14601e-12, 0, 0, 1, 1.37123e-12, 0, 0, 1, 2.1243e-12, 0, 0, 0.999999, 4.89653e-12, 0, 0, 0.999999, 1.60283e-11, 0, 0, 0.999998, 6.2269e-11, 0, 0, 0.999997, 2.51859e-10, 0, 0, 0.999996, 9.96192e-10, 0, 0, 0.999992, 3.74531e-09, 0, 0, 0.999986, 1.32022e-08, 0, 0, 0.999975, 4.33315e-08, 0, 0, 0.999959, 1.31956e-07, 0, 0, 0.999927, 3.72249e-07, 0, 0, 0.999871, 9.72461e-07, 0, 0, 0.999771, 2.35343e-06, 0, 0, 0.999572, 5.2768e-06, 0, 0, 0.999133, 1.09237e-05, 0, 0, 0.997912, 2.03675e-05, 0, 0, 0.993008, 2.79396e-05, 0, 0, 0.980645, 1.39604e-05, 0, 0, 0.970057, 6.46596e-06, 0, 0, 0.966717, 6.5089e-06, 0, 4.74145e-05, 0.965497, 1.11863e-05, 0, 0.00089544, 0.96334, 1.79857e-05, 0, 0.0032647, 0.959294, 2.59045e-05, 0, 0.0075144, 0.951519, 2.92327e-05, 0, 0.0138734, 0.940517, 2.49769e-05, 0, 0.0224952, 0.93014, 2.6803e-05, 0, 0.0334828, 0.91972, 3.03656e-05, 0, 0.0468973, 0.910294, 3.53323e-05, 0, 0.0627703, 0.897701, 3.51002e-05, 0, 0.0811019, 0.884522, 3.88104e-05, 0, 0.10186, 0.869489, 4.12932e-05, 0, 0.124985, 0.853983, 4.15781e-05, 0, 0.150372, 0.838425, 4.54066e-05, 0, 0.177868, 0.820656, 4.71624e-05, 0, 0.207245, 0.801875, 4.75243e-05, 0, 0.238143, 0.783521, 5.05621e-05, 0, 0.269841, 0.763131, 5.0721e-05, 0, 0.301587, 0.74261, 5.23293e-05, 0, 0.333333, 0.72148, 5.28699e-05, 0, 0.365079, 0.699696, 5.38677e-05, 0, 0.396825, 0.677592, 5.39255e-05, 0, 0.428571, 0.65525, 5.46367e-05, 0, 0.460317, 0.632452, 5.41348e-05, 0, 0.492064, 0.609903, 5.44976e-05, 0, 0.52381, 0.586928, 5.36201e-05, 0, 0.555556, 0.564464, 5.35185e-05, 0, 0.587302, 0.541801, 5.24949e-05, 0, 0.619048, 0.519681, 5.1812e-05, 0, 0.650794, 0.497685, 5.07687e-05, 0, 0.68254, 0.47622, 4.96243e-05, 0, 0.714286, 0.455135, 4.85714e-05, 0, 0.746032, 0.4346, 4.71847e-05, 0, 0.777778, 0.414564, 4.59294e-05, 0, 0.809524, 0.395165, 4.44705e-05, 0, 0.84127, 0.376333, 4.30772e-05, 0, 0.873016, 0.358197, 4.16229e-05, 0, 0.904762, 0.34064, 4.01019e-05, 0, 0.936508, 0.323816, 3.86623e-05, 0, 0.968254, 0.307581, 3.70933e-05, 0, 1, 1, 9.91541e-12, 0, 0, 1, 9.92077e-12, 0, 0, 1, 1.00041e-11, 0, 0, 1, 1.0385e-11, 0, 0, 1, 1.15777e-11, 0, 0, 1, 1.50215e-11, 0, 0, 0.999999, 2.54738e-11, 0, 0, 0.999999, 5.98822e-11, 0, 0, 0.999998, 1.79597e-10, 0, 0, 0.999997, 6.02367e-10, 0, 0, 0.999994, 2.06835e-09, 0, 0, 0.99999, 6.94952e-09, 0, 0, 0.999984, 2.23363e-08, 0, 0, 0.999972, 6.78578e-08, 0, 0, 0.999952, 1.93571e-07, 0, 0, 0.999919, 5.16594e-07, 0, 0, 0.99986, 1.28739e-06, 0, 0, 0.999753, 2.99298e-06, 0, 0, 0.999546, 6.48258e-06, 0, 0, 0.999074, 1.29985e-05, 0, 0, 0.997671, 2.32176e-05, 0, 0, 0.991504, 2.56701e-05, 0, 0, 0.981148, 1.31141e-05, 0, 0, 0.971965, 8.69048e-06, 0, 2.80182e-05, 0.966624, 8.08301e-06, 0, 0.000695475, 0.965344, 1.35235e-05, 0, 0.00265522, 0.963048, 2.10592e-05, 0, 0.00622975, 0.958673, 2.87473e-05, 0, 0.0116234, 0.950262, 2.81379e-05, 0, 0.018976, 0.940836, 2.71089e-05, 0, 0.0283844, 0.930996, 3.0926e-05, 0, 0.0399151, 0.919848, 3.48359e-05, 0, 0.0536063, 0.909136, 3.66092e-05, 0, 0.0694793, 0.897554, 3.84162e-05, 0, 0.0875342, 0.884691, 4.30971e-05, 0, 0.107749, 0.869414, 4.47803e-05, 0, 0.130087, 0.853462, 4.52858e-05, 0, 0.154481, 0.838187, 4.95769e-05, 0, 0.180833, 0.820381, 5.02709e-05, 0, 0.209005, 0.801844, 5.22713e-05, 0, 0.238791, 0.783061, 5.41505e-05, 0, 0.269869, 0.763205, 5.53712e-05, 0, 0.301587, 0.742362, 5.64909e-05, 0, 0.333333, 0.721393, 5.72646e-05, 0, 0.365079, 0.699676, 5.81012e-05, 0, 0.396825, 0.677395, 5.8096e-05, 0, 0.428571, 0.655208, 5.85766e-05, 0, 0.460317, 0.632451, 5.83602e-05, 0, 0.492064, 0.609839, 5.80234e-05, 0, 0.52381, 0.587093, 5.77161e-05, 0, 0.555556, 0.564467, 5.68447e-05, 0, 0.587302, 0.542043, 5.63166e-05, 0, 0.619048, 0.519826, 5.5156e-05, 0, 0.650794, 0.497952, 5.41682e-05, 0, 0.68254, 0.476477, 5.28971e-05, 0, 0.714286, 0.455412, 5.14952e-05, 0, 0.746032, 0.434926, 5.02222e-05, 0, 0.777778, 0.4149, 4.85779e-05, 0, 0.809524, 0.395552, 4.72242e-05, 0, 0.84127, 0.376712, 4.54891e-05, 0, 0.873016, 0.358622, 4.40924e-05, 0, 0.904762, 0.341048, 4.22984e-05, 0, 0.936508, 0.324262, 4.08582e-05, 0, 0.968254, 0.308013, 3.90839e-05, 0, 1, 1, 6.13913e-11, 0, 0, 1, 6.14145e-11, 0, 0, 1, 6.17708e-11, 0, 0, 1, 6.33717e-11, 0, 0, 1, 6.81648e-11, 0, 0, 1, 8.08291e-11, 0, 0, 1, 1.14608e-10, 0, 0, 0.999998, 2.10507e-10, 0, 0, 0.999997, 4.99595e-10, 0, 0, 0.999995, 1.39897e-09, 0, 0, 0.999994, 4.19818e-09, 0, 0, 0.999988, 1.27042e-08, 0, 0, 0.999979, 3.75153e-08, 0, 0, 0.999965, 1.06206e-07, 0, 0, 0.999945, 2.85381e-07, 0, 0, 0.999908, 7.23611e-07, 0, 0, 0.999846, 1.7255e-06, 0, 0, 0.999733, 3.86104e-06, 0, 0, 0.999511, 8.08493e-06, 0, 0, 0.998993, 1.56884e-05, 0, 0, 0.997326, 2.65538e-05, 0, 0, 0.989706, 2.06466e-05, 0, 0, 0.981713, 1.30756e-05, 0, 7.0005e-06, 0.973636, 1.06473e-05, 0, 0.000464797, 0.966509, 1.0194e-05, 0, 0.00201743, 0.965149, 1.65881e-05, 0, 0.00497549, 0.962669, 2.49147e-05, 0, 0.00953262, 0.95786, 3.17449e-05, 0, 0.0158211, 0.949334, 2.81045e-05, 0, 0.0239343, 0.941041, 3.03263e-05, 0, 0.0339372, 0.931575, 3.56754e-05, 0, 0.0458738, 0.920102, 3.97075e-05, 0, 0.059772, 0.908002, 3.84886e-05, 0, 0.075645, 0.897269, 4.3027e-05, 0, 0.0934929, 0.884559, 4.79925e-05, 0, 0.113302, 0.869161, 4.8246e-05, 0, 0.135045, 0.853342, 5.09505e-05, 0, 0.158678, 0.837633, 5.42846e-05, 0, 0.184136, 0.820252, 5.54139e-05, 0, 0.211325, 0.801872, 5.81412e-05, 0, 0.240113, 0.782418, 5.85535e-05, 0, 0.270306, 0.7631, 6.10923e-05, 0, 0.301594, 0.742183, 6.13678e-05, 0, 0.333333, 0.721098, 6.27275e-05, 0, 0.365079, 0.699512, 6.29413e-05, 0, 0.396825, 0.677372, 6.36351e-05, 0, 0.428571, 0.655059, 6.33555e-05, 0, 0.460317, 0.632567, 6.36513e-05, 0, 0.492064, 0.609784, 6.28965e-05, 0, 0.52381, 0.587237, 6.25546e-05, 0, 0.555556, 0.564525, 6.15825e-05, 0, 0.587302, 0.542181, 6.05048e-05, 0, 0.619048, 0.520017, 5.96329e-05, 0, 0.650794, 0.498204, 5.81516e-05, 0, 0.68254, 0.476742, 5.69186e-05, 0, 0.714286, 0.455803, 5.53833e-05, 0, 0.746032, 0.435251, 5.37807e-05, 0, 0.777778, 0.415374, 5.22025e-05, 0, 0.809524, 0.395921, 5.03421e-05, 0, 0.84127, 0.377253, 4.88211e-05, 0, 0.873016, 0.359021, 4.68234e-05, 0, 0.904762, 0.341637, 4.53269e-05, 0, 0.936508, 0.3247, 4.33014e-05, 0, 0.968254, 0.308625, 4.18007e-05, 0, 1, 1, 2.86798e-10, 0, 0, 1, 2.86877e-10, 0, 0, 1, 2.88094e-10, 0, 0, 1, 2.93506e-10, 0, 0, 1, 3.09262e-10, 0, 0, 0.999999, 3.48593e-10, 0, 0, 0.999999, 4.44582e-10, 0, 0, 0.999998, 6.88591e-10, 0, 0, 0.999996, 1.34391e-09, 0, 0, 0.999993, 3.17438e-09, 0, 0, 0.999989, 8.35609e-09, 0, 0, 0.999983, 2.28677e-08, 0, 0, 0.999974, 6.23361e-08, 0, 0, 0.999959, 1.65225e-07, 0, 0, 0.999936, 4.19983e-07, 0, 0, 0.999896, 1.01546e-06, 0, 0, 0.99983, 2.32376e-06, 0, 0, 0.999709, 5.0156e-06, 0, 0, 0.999469, 1.0167e-05, 0, 0, 0.998886, 1.90775e-05, 0, 0, 0.996819, 3.00511e-05, 0, 0, 0.988837, 1.85092e-05, 0, 1.68222e-07, 0.982178, 1.34622e-05, 0, 0.000259622, 0.975017, 1.25961e-05, 0, 0.00142595, 0.967101, 1.3507e-05, 0, 0.00382273, 0.964905, 2.05003e-05, 0, 0.00764164, 0.96218, 2.9546e-05, 0, 0.0130121, 0.956821, 3.43738e-05, 0, 0.0200253, 0.948829, 3.05063e-05, 0, 0.0287452, 0.941092, 3.46487e-05, 0, 0.039218, 0.931883, 4.12061e-05, 0, 0.0514748, 0.920211, 4.44651e-05, 0, 0.0655351, 0.907307, 4.31252e-05, 0, 0.0814082, 0.89684, 4.90382e-05, 0, 0.0990939, 0.884119, 5.3334e-05, 0, 0.118583, 0.869148, 5.4114e-05, 0, 0.139856, 0.853377, 5.78536e-05, 0, 0.162882, 0.836753, 5.92285e-05, 0, 0.187615, 0.820063, 6.22787e-05, 0, 0.213991, 0.801694, 6.45492e-05, 0, 0.241918, 0.782116, 6.5353e-05, 0, 0.271267, 0.762673, 6.74344e-05, 0, 0.301847, 0.742133, 6.82788e-05, 0, 0.333333, 0.720779, 6.91959e-05, 0, 0.365079, 0.699386, 6.96817e-05, 0, 0.396826, 0.67732, 6.99583e-05, 0, 0.428572, 0.654888, 6.98447e-05, 0, 0.460318, 0.632499, 6.94063e-05, 0, 0.492064, 0.609825, 6.91612e-05, 0, 0.52381, 0.587287, 6.81576e-05, 0, 0.555556, 0.564743, 6.74138e-05, 0, 0.587302, 0.542409, 6.61617e-05, 0, 0.619048, 0.520282, 6.47785e-05, 0, 0.650794, 0.498506, 6.33836e-05, 0, 0.68254, 0.477102, 6.15905e-05, 0, 0.714286, 0.456167, 6.01013e-05, 0, 0.746032, 0.435728, 5.81457e-05, 0, 0.777778, 0.415809, 5.64215e-05, 0, 0.809524, 0.396517, 5.44997e-05, 0, 0.84127, 0.377737, 5.25061e-05, 0, 0.873016, 0.359698, 5.06831e-05, 0, 0.904762, 0.342164, 4.8568e-05, 0, 0.936508, 0.325417, 4.67826e-05, 0, 0.968254, 0.309186, 4.46736e-05, 0, 1, 1, 1.09018e-09, 0, 0, 1, 1.0904e-09, 0, 0, 1, 1.09393e-09, 0, 0, 1, 1.1095e-09, 0, 0, 1, 1.154e-09, 0, 0, 1, 1.26089e-09, 0, 0, 0.999999, 1.5059e-09, 0, 0, 0.999997, 2.07899e-09, 0, 0, 0.999994, 3.48164e-09, 0, 0, 0.999993, 7.05728e-09, 0, 0, 0.999987, 1.63692e-08, 0, 0, 0.999981, 4.06033e-08, 0, 0, 0.999969, 1.0245e-07, 0, 0, 0.999953, 2.55023e-07, 0, 0, 0.999925, 6.1511e-07, 0, 0, 0.999881, 1.42218e-06, 0, 0, 0.99981, 3.13086e-06, 0, 0, 0.99968, 6.53119e-06, 0, 0, 0.999418, 1.2832e-05, 0, 0, 0.998748, 2.32497e-05, 0, 0, 0.996066, 3.29522e-05, 0, 0, 0.988379, 1.79613e-05, 0, 0.000108799, 0.982567, 1.43715e-05, 0, 0.000921302, 0.976097, 1.48096e-05, 0, 0.00280738, 0.968475, 1.78905e-05, 0, 0.00596622, 0.964606, 2.53921e-05, 0, 0.0105284, 0.961564, 3.48623e-05, 0, 0.0165848, 0.955517, 3.57612e-05, 0, 0.0242, 0.948381, 3.43493e-05, 0, 0.03342, 0.941095, 4.05849e-05, 0, 0.0442777, 0.931923, 4.75394e-05, 0, 0.0567958, 0.91996, 4.84328e-05, 0, 0.0709879, 0.907419, 5.02146e-05, 0, 0.086861, 0.89618, 5.61654e-05, 0, 0.104415, 0.88337, 5.87612e-05, 0, 0.123643, 0.869046, 6.18057e-05, 0, 0.144531, 0.853278, 6.57392e-05, 0, 0.167057, 0.836091, 6.6303e-05, 0, 0.191188, 0.819644, 7.04445e-05, 0, 0.216878, 0.801246, 7.14071e-05, 0, 0.244062, 0.782031, 7.40093e-05, 0, 0.272649, 0.762066, 7.4685e-05, 0, 0.302509, 0.741964, 7.66647e-05, 0, 0.333442, 0.720554, 7.66328e-05, 0, 0.365079, 0.699098, 7.77857e-05, 0, 0.396826, 0.677189, 7.74633e-05, 0, 0.428572, 0.65484, 7.76235e-05, 0, 0.460318, 0.632496, 7.70316e-05, 0, 0.492064, 0.609908, 7.62669e-05, 0, 0.52381, 0.587312, 7.53972e-05, 0, 0.555556, 0.564938, 7.39994e-05, 0, 0.587302, 0.542577, 7.28382e-05, 0, 0.619048, 0.52062, 7.1112e-05, 0, 0.650794, 0.498819, 6.94004e-05, 0, 0.68254, 0.477555, 6.75575e-05, 0, 0.714286, 0.456568, 6.53449e-05, 0, 0.746032, 0.436278, 6.36068e-05, 0, 0.777778, 0.41637, 6.13466e-05, 0, 0.809524, 0.397144, 5.94177e-05, 0, 0.84127, 0.378412, 5.70987e-05, 0, 0.873016, 0.360376, 5.50419e-05, 0, 0.904762, 0.342906, 5.27422e-05, 0, 0.936508, 0.326136, 5.06544e-05, 0, 0.968254, 0.30997, 4.84307e-05, 0, 1, 1, 3.54014e-09, 0, 0, 1, 3.54073e-09, 0, 0, 1, 3.54972e-09, 0, 0, 1, 3.58929e-09, 0, 0, 1, 3.70093e-09, 0, 0, 0.999999, 3.96194e-09, 0, 0, 0.999998, 4.53352e-09, 0, 0, 0.999997, 5.78828e-09, 0, 0, 0.999994, 8.63812e-09, 0, 0, 0.999991, 1.53622e-08, 0, 0, 0.999985, 3.16356e-08, 0, 0, 0.999977, 7.12781e-08, 0, 0, 0.999964, 1.66725e-07, 0, 0, 0.999945, 3.90501e-07, 0, 0, 0.999912, 8.95622e-07, 0, 0, 0.999866, 1.98428e-06, 0, 0, 0.999786, 4.21038e-06, 0, 0, 0.999647, 8.50239e-06, 0, 0, 0.999356, 1.62059e-05, 0, 0, 0.998563, 2.82652e-05, 0, 0, 0.994928, 3.36309e-05, 0, 2.44244e-05, 0.987999, 1.78458e-05, 0, 0.000523891, 0.982893, 1.59162e-05, 0, 0.00194729, 0.977044, 1.78056e-05, 0, 0.00451099, 0.969972, 2.30624e-05, 0, 0.00835132, 0.964237, 3.13922e-05, 0, 0.013561, 0.960791, 4.06145e-05, 0, 0.0202056, 0.954292, 3.72796e-05, 0, 0.0283321, 0.948052, 4.03199e-05, 0, 0.0379739, 0.940938, 4.79537e-05, 0, 0.0491551, 0.931689, 5.45292e-05, 0, 0.0618918, 0.91987, 5.4038e-05, 0, 0.0761941, 0.907665, 5.89909e-05, 0, 0.0920672, 0.895281, 6.42651e-05, 0, 0.109511, 0.882621, 6.59707e-05, 0, 0.12852, 0.86873, 7.09973e-05, 0, 0.149085, 0.853008, 7.42221e-05, 0, 0.171189, 0.835944, 7.61754e-05, 0, 0.194809, 0.818949, 7.97052e-05, 0, 0.21991, 0.800951, 8.12434e-05, 0, 0.246447, 0.781847, 8.38075e-05, 0, 0.274352, 0.761649, 8.4501e-05, 0, 0.303535, 0.74152, 8.60258e-05, 0, 0.333857, 0.720495, 8.66233e-05, 0, 0.365104, 0.698742, 8.68326e-05, 0, 0.396826, 0.677096, 8.7133e-05, 0, 0.428572, 0.654782, 8.63497e-05, 0, 0.460318, 0.632335, 8.60206e-05, 0, 0.492064, 0.610031, 8.49337e-05, 0, 0.52381, 0.587457, 8.38279e-05, 0, 0.555556, 0.56513, 8.2309e-05, 0, 0.587302, 0.542877, 8.03542e-05, 0, 0.619048, 0.5209, 7.86928e-05, 0, 0.650794, 0.499291, 7.65171e-05, 0, 0.68254, 0.477971, 7.44753e-05, 0, 0.714286, 0.457221, 7.2209e-05, 0, 0.746032, 0.436803, 6.97448e-05, 0, 0.777778, 0.417083, 6.75333e-05, 0, 0.809524, 0.397749, 6.48058e-05, 0, 0.84127, 0.379177, 6.25759e-05, 0, 0.873016, 0.361061, 5.98584e-05, 0, 0.904762, 0.343713, 5.75797e-05, 0, 0.936508, 0.326894, 5.49999e-05, 0, 0.968254, 0.310816, 5.27482e-05, 0, 1, 1, 1.0153e-08, 0, 0, 1, 1.01544e-08, 0, 0, 1, 1.01751e-08, 0, 0, 1, 1.02662e-08, 0, 0, 1, 1.0521e-08, 0, 0, 0.999999, 1.11049e-08, 0, 0, 0.999999, 1.23408e-08, 0, 0, 0.999996, 1.4924e-08, 0, 0, 0.999992, 2.04471e-08, 0, 0, 0.999989, 3.26539e-08, 0, 0, 0.99998, 6.03559e-08, 0, 0, 0.999971, 1.23936e-07, 0, 0, 0.999955, 2.69058e-07, 0, 0, 0.999933, 5.93604e-07, 0, 0, 0.999901, 1.29633e-06, 0, 0, 0.999847, 2.75621e-06, 0, 0, 0.999761, 5.64494e-06, 0, 0, 0.999607, 1.10485e-05, 0, 0, 0.999282, 2.04388e-05, 0, 0, 0.99831, 3.41084e-05, 0, 2.2038e-07, 0.993288, 2.94949e-05, 0, 0.000242388, 0.987855, 1.92736e-05, 0, 0.0012503, 0.983167, 1.82383e-05, 0, 0.0032745, 0.977908, 2.18633e-05, 0, 0.00646321, 0.971194, 2.90662e-05, 0, 0.0109133, 0.963867, 3.86401e-05, 0, 0.0166927, 0.95982, 4.62827e-05, 0, 0.0238494, 0.953497, 4.20705e-05, 0, 0.0324178, 0.947621, 4.77743e-05, 0, 0.0424225, 0.940611, 5.68258e-05, 0, 0.0538808, 0.931174, 6.18061e-05, 0, 0.0668047, 0.919919, 6.27098e-05, 0, 0.0812014, 0.907856, 6.94714e-05, 0, 0.0970745, 0.894509, 7.35008e-05, 0, 0.114424, 0.881954, 7.63369e-05, 0, 0.133246, 0.868309, 8.21896e-05, 0, 0.153534, 0.852511, 8.3769e-05, 0, 0.175275, 0.835821, 8.81615e-05, 0, 0.198453, 0.817981, 8.96368e-05, 0, 0.223042, 0.800504, 9.30906e-05, 0, 0.249009, 0.78141, 9.45056e-05, 0, 0.276304, 0.761427, 9.63605e-05, 0, 0.304862, 0.74094, 9.68088e-05, 0, 0.334584, 0.720233, 9.81481e-05, 0, 0.365322, 0.698592, 9.79122e-05, 0, 0.396826, 0.676763, 9.81057e-05, 0, 0.428571, 0.654808, 9.73956e-05, 0, 0.460318, 0.632326, 9.62619e-05, 0, 0.492064, 0.610049, 9.52996e-05, 0, 0.52381, 0.58763, 9.33334e-05, 0, 0.555556, 0.565261, 9.17573e-05, 0, 0.587302, 0.543244, 8.96636e-05, 0, 0.619048, 0.521273, 8.73304e-05, 0, 0.650794, 0.499818, 8.52648e-05, 0, 0.68254, 0.478536, 8.23961e-05, 0, 0.714286, 0.457826, 7.9939e-05, 0, 0.746032, 0.437549, 7.7126e-05, 0, 0.777778, 0.41776, 7.43043e-05, 0, 0.809524, 0.39863, 7.16426e-05, 0, 0.84127, 0.379954, 6.86456e-05, 0, 0.873016, 0.362025, 6.60514e-05, 0, 0.904762, 0.344581, 6.30755e-05, 0, 0.936508, 0.327909, 6.05439e-05, 0, 0.968254, 0.311736, 5.76345e-05, 0, 1, 1, 2.63344e-08, 0, 0, 1, 2.63373e-08, 0, 0, 1, 2.63815e-08, 0, 0, 1, 2.65753e-08, 0, 0, 1, 2.71132e-08, 0, 0, 0.999999, 2.83279e-08, 0, 0, 0.999997, 3.0833e-08, 0, 0, 0.999995, 3.58711e-08, 0, 0, 0.999992, 4.61266e-08, 0, 0, 0.999985, 6.7574e-08, 0, 0, 0.999977, 1.1358e-07, 0, 0, 0.999966, 2.13657e-07, 0, 0, 0.999948, 4.31151e-07, 0, 0, 0.999923, 8.96656e-07, 0, 0, 0.999884, 1.86603e-06, 0, 0, 0.999826, 3.81115e-06, 0, 0, 0.999732, 7.54184e-06, 0, 0, 0.999561, 1.43192e-05, 0, 0, 0.999191, 2.57061e-05, 0, 0, 0.997955, 4.05724e-05, 0, 7.44132e-05, 0.992228, 2.76537e-05, 0, 0.000716477, 0.987638, 2.08885e-05, 0, 0.0022524, 0.983395, 2.15226e-05, 0, 0.00484816, 0.978614, 2.70795e-05, 0, 0.00860962, 0.972389, 3.65282e-05, 0, 0.0136083, 0.964392, 4.74747e-05, 0, 0.0198941, 0.95861, 5.09141e-05, 0, 0.0275023, 0.952806, 4.8963e-05, 0, 0.0364584, 0.94712, 5.71119e-05, 0, 0.04678, 0.940104, 6.71704e-05, 0, 0.0584799, 0.930398, 6.87586e-05, 0, 0.0715665, 0.919866, 7.38161e-05, 0, 0.086045, 0.907853, 8.13235e-05, 0, 0.101918, 0.894078, 8.34582e-05, 0, 0.119186, 0.881177, 8.92093e-05, 0, 0.137845, 0.867575, 9.44548e-05, 0, 0.157891, 0.852107, 9.69607e-05, 0, 0.179316, 0.835502, 0.000101456, 0, 0.202106, 0.81756, 0.000103256, 0, 0.226243, 0.79984, 0.000106954, 0, 0.251704, 0.780998, 0.000108066, 0, 0.278451, 0.761132, 0.000110111, 0, 0.306436, 0.740429, 0.000110459, 0, 0.335586, 0.719836, 0.000111219, 0, 0.365796, 0.698467, 0.00011145, 0, 0.3969, 0.676446, 0.000110393, 0, 0.428571, 0.654635, 0.000110035, 0, 0.460318, 0.632411, 0.000108548, 0, 0.492064, 0.609986, 0.000106963, 0, 0.52381, 0.587872, 0.000105238, 0, 0.555556, 0.565528, 0.000102665, 0, 0.587302, 0.543563, 0.000100543, 0, 0.619048, 0.52176, 9.76182e-05, 0, 0.650794, 0.500188, 9.47099e-05, 0, 0.68254, 0.479204, 9.19929e-05, 0, 0.714286, 0.458413, 8.86139e-05, 0, 0.746032, 0.438314, 8.57839e-05, 0, 0.777778, 0.418573, 8.2411e-05, 0, 0.809524, 0.39947, 7.92211e-05, 0, 0.84127, 0.380892, 7.59546e-05, 0, 0.873016, 0.362953, 7.27571e-05, 0, 0.904762, 0.345601, 6.95738e-05, 0, 0.936508, 0.328895, 6.64907e-05, 0, 0.968254, 0.312808, 6.34277e-05, 0, 1, 1, 6.28647e-08, 0, 0, 1, 6.28705e-08, 0, 0, 1, 6.29587e-08, 0, 0, 1, 6.33441e-08, 0, 0, 0.999999, 6.44087e-08, 0, 0, 0.999998, 6.67856e-08, 0, 0, 0.999997, 7.15889e-08, 0, 0, 0.999995, 8.09577e-08, 0, 0, 0.999989, 9.92764e-08, 0, 0, 0.999983, 1.35834e-07, 0, 0, 0.999974, 2.10482e-07, 0, 0, 0.999959, 3.65215e-07, 0, 0, 0.999939, 6.86693e-07, 0, 0, 0.999911, 1.3472e-06, 0, 0, 0.999868, 2.6731e-06, 0, 0, 0.999804, 5.24756e-06, 0, 0, 0.9997, 1.00403e-05, 0, 0, 0.99951, 1.85019e-05, 0, 0, 0.999078, 3.22036e-05, 0, 6.20676e-06, 0.997428, 4.70002e-05, 0, 0.000341552, 0.99162, 2.87123e-05, 0, 0.00143727, 0.987479, 2.34706e-05, 0, 0.00349201, 0.983582, 2.60083e-05, 0, 0.0066242, 0.979186, 3.37927e-05, 0, 0.0109113, 0.97325, 4.54689e-05, 0, 0.0164064, 0.965221, 5.73759e-05, 0, 0.0231463, 0.957262, 5.44114e-05, 0, 0.0311571, 0.952211, 5.87006e-05, 0, 0.0404572, 0.946631, 6.92256e-05, 0, 0.0510592, 0.939391, 7.87819e-05, 0, 0.0629723, 0.929795, 7.92368e-05, 0, 0.0762025, 0.91965, 8.75075e-05, 0, 0.090753, 0.907737, 9.50903e-05, 0, 0.106626, 0.893899, 9.72963e-05, 0, 0.123822, 0.880239, 0.00010459, 0, 0.142337, 0.866562, 0.000107689, 0, 0.16217, 0.85164, 0.000113081, 0, 0.183314, 0.835021, 0.000116636, 0, 0.20576, 0.817311, 0.000120074, 0, 0.229496, 0.798845, 0.000121921, 0, 0.254502, 0.780479, 0.00012475, 0, 0.280753, 0.760694, 0.000125255, 0, 0.308212, 0.740142, 0.000126719, 0, 0.336825, 0.719248, 0.00012636, 0, 0.366517, 0.698209, 0.000126712, 0, 0.397167, 0.676398, 0.000125769, 0, 0.428578, 0.654378, 0.000124432, 0, 0.460318, 0.632484, 0.000123272, 0, 0.492064, 0.610113, 0.00012085, 0, 0.52381, 0.587931, 0.000118411, 0, 0.555556, 0.565872, 0.00011569, 0, 0.587302, 0.543814, 0.000112521, 0, 0.619048, 0.522265, 0.000109737, 0, 0.650794, 0.500835, 0.000106228, 0, 0.68254, 0.479818, 0.000102591, 0, 0.714286, 0.459258, 9.91288e-05, 0, 0.746032, 0.439061, 9.52325e-05, 0, 0.777778, 0.419552, 9.1895e-05, 0, 0.809524, 0.400399, 8.79051e-05, 0, 0.84127, 0.381976, 8.44775e-05, 0, 0.873016, 0.364009, 8.06316e-05, 0, 0.904762, 0.346761, 7.71848e-05, 0, 0.936508, 0.330049, 7.35429e-05, 0, 0.968254, 0.314018, 7.02103e-05, 0, 1, 1, 1.39968e-07, 0, 0, 1, 1.39979e-07, 0, 0, 1, 1.40145e-07, 0, 0, 1, 1.4087e-07, 0, 0, 0.999999, 1.42865e-07, 0, 0, 0.999998, 1.47279e-07, 0, 0, 0.999997, 1.56057e-07, 0, 0, 0.999992, 1.7276e-07, 0, 0, 0.999989, 2.04352e-07, 0, 0, 0.99998, 2.6494e-07, 0, 0, 0.999969, 3.83435e-07, 0, 0, 0.999953, 6.18641e-07, 0, 0, 0.999929, 1.08755e-06, 0, 0, 0.999898, 2.01497e-06, 0, 0, 0.999849, 3.81346e-06, 0, 0, 0.999778, 7.19815e-06, 0, 0, 0.999661, 1.33215e-05, 0, 0, 0.999451, 2.38313e-05, 0, 0, 0.998936, 4.01343e-05, 0, 0.000113724, 0.99662, 5.17346e-05, 0, 0.000820171, 0.991094, 3.04323e-05, 0, 0.00238143, 0.987487, 2.81757e-05, 0, 0.00493527, 0.983731, 3.20048e-05, 0, 0.00856859, 0.979647, 4.23905e-05, 0, 0.0133393, 0.973837, 5.62935e-05, 0, 0.0192863, 0.96584, 6.77442e-05, 0, 0.0264369, 0.956309, 6.23073e-05, 0, 0.03481, 0.951523, 7.04131e-05, 0, 0.0444184, 0.946003, 8.36594e-05, 0, 0.0552713, 0.938454, 9.11736e-05, 0, 0.0673749, 0.929279, 9.38264e-05, 0, 0.0807329, 0.919239, 0.000103754, 0, 0.0953479, 0.907293, 0.000109928, 0, 0.111221, 0.893936, 0.000115257, 0, 0.128352, 0.879674, 0.000122265, 0, 0.14674, 0.865668, 0.000125733, 0, 0.166382, 0.850998, 0.000132305, 0, 0.187276, 0.834498, 0.000134844, 0, 0.209413, 0.816903, 0.000139276, 0, 0.232786, 0.798235, 0.000140984, 0, 0.257382, 0.779724, 0.00014378, 0, 0.283181, 0.760251, 0.000144623, 0, 0.310156, 0.739808, 0.000145228, 0, 0.338269, 0.718762, 0.00014539, 0, 0.367461, 0.697815, 0.000144432, 0, 0.397646, 0.67631, 0.000143893, 0, 0.428685, 0.654278, 0.000141846, 0, 0.460318, 0.632347, 0.00013935, 0, 0.492064, 0.610296, 0.000137138, 0, 0.52381, 0.588039, 0.000133806, 0, 0.555556, 0.566218, 0.000130755, 0, 0.587302, 0.544346, 0.000127128, 0, 0.619048, 0.522701, 0.000123002, 0, 0.650794, 0.501542, 0.000119443, 0, 0.68254, 0.480508, 0.000115055, 0, 0.714286, 0.460092, 0.000111032, 0, 0.746032, 0.440021, 0.000106635, 0, 0.777778, 0.420446, 0.000102162, 0, 0.809524, 0.401512, 9.8184e-05, 0, 0.84127, 0.38299, 9.36497e-05, 0, 0.873016, 0.365232, 8.9813e-05, 0, 0.904762, 0.347865, 8.53073e-05, 0, 0.936508, 0.331342, 8.17068e-05, 0, 0.968254, 0.315202, 7.73818e-05, 0, 1, 1, 2.9368e-07, 0, 0, 1, 2.937e-07, 0, 0, 1, 2.93998e-07, 0, 0, 1, 2.95298e-07, 0, 0, 0.999999, 2.98865e-07, 0, 0, 0.999998, 3.067e-07, 0, 0, 0.999995, 3.22082e-07, 0, 0, 0.999992, 3.50767e-07, 0, 0, 0.999986, 4.03538e-07, 0, 0, 0.999976, 5.01372e-07, 0, 0, 0.999964, 6.8562e-07, 0, 0, 0.999945, 1.0374e-06, 0, 0, 0.999919, 1.71269e-06, 0, 0, 0.999882, 3.00175e-06, 0, 0, 0.999829, 5.42144e-06, 0, 0, 0.999749, 9.84182e-06, 0, 0, 0.99962, 1.76213e-05, 0, 0, 0.999382, 3.05995e-05, 0, 1.38418e-05, 0.998751, 4.96686e-05, 0, 0.000389844, 0.995344, 5.10733e-05, 0, 0.00150343, 0.990768, 3.45829e-05, 0, 0.00352451, 0.987464, 3.42841e-05, 0, 0.00655379, 0.983846, 3.99072e-05, 0, 0.0106554, 0.980007, 5.33219e-05, 0, 0.0158723, 0.974494, 6.96992e-05, 0, 0.0222333, 0.96622, 7.76754e-05, 0, 0.029758, 0.956273, 7.47718e-05, 0, 0.0384596, 0.950952, 8.64611e-05, 0, 0.0483473, 0.945215, 0.000100464, 0, 0.0594266, 0.937287, 0.000103729, 0, 0.0717019, 0.928649, 0.000111665, 0, 0.0851752, 0.918791, 0.00012353, 0, 0.0998479, 0.906685, 0.000127115, 0, 0.115721, 0.893706, 0.00013628, 0, 0.132794, 0.879248, 0.000142427, 0, 0.151067, 0.864685, 0.000148091, 0, 0.170538, 0.850032, 0.000153517, 0, 0.191204, 0.833853, 0.000157322, 0, 0.213063, 0.816353, 0.000161086, 0, 0.236107, 0.797834, 0.000164111, 0, 0.260329, 0.778831, 0.000165446, 0, 0.285714, 0.759756, 0.000167492, 0, 0.312243, 0.739419, 0.000166928, 0, 0.339887, 0.718491, 0.000167, 0, 0.368604, 0.697392, 0.000165674, 0, 0.398329, 0.676102, 0.000163815, 0, 0.428961, 0.654243, 0.000162003, 0, 0.460331, 0.632176, 0.000158831, 0, 0.492064, 0.610407, 0.000155463, 0, 0.52381, 0.588394, 0.000152062, 0, 0.555556, 0.56645, 0.000147665, 0, 0.587302, 0.5449, 0.00014375, 0, 0.619048, 0.523276, 0.000138905, 0, 0.650794, 0.502179, 0.000134189, 0, 0.68254, 0.481359, 0.000129392, 0, 0.714286, 0.46092, 0.000124556, 0, 0.746032, 0.441084, 0.00011957, 0, 0.777778, 0.421517, 0.000114652, 0, 0.809524, 0.402721, 0.000109688, 0, 0.84127, 0.384222, 0.000104667, 0, 0.873016, 0.366534, 9.99633e-05, 0, 0.904762, 0.349205, 9.50177e-05, 0, 0.936508, 0.332702, 9.07301e-05, 0, 0.968254, 0.316599, 8.59769e-05, 0, 1, 1, 5.85473e-07, 0, 0, 1, 5.85507e-07, 0, 0, 1, 5.8602e-07, 0, 0, 0.999999, 5.88259e-07, 0, 0, 0.999999, 5.94381e-07, 0, 0, 0.999998, 6.07754e-07, 0, 0, 0.999995, 6.33729e-07, 0, 0, 0.99999, 6.8137e-07, 0, 0, 0.999984, 7.67003e-07, 0, 0, 0.999973, 9.21212e-07, 0, 0, 0.999959, 1.20218e-06, 0, 0, 0.999936, 1.72024e-06, 0, 0, 0.999907, 2.68088e-06, 0, 0, 0.999866, 4.45512e-06, 0, 0, 0.999806, 7.68481e-06, 0, 0, 0.999716, 1.342e-05, 0, 0, 0.999576, 2.32473e-05, 0, 0, 0.9993, 3.91694e-05, 0, 0.000129917, 0.998498, 6.08429e-05, 0, 0.000845035, 0.994132, 4.89743e-05, 0, 0.00237616, 0.99031, 3.84644e-05, 0, 0.00484456, 0.987409, 4.21768e-05, 0, 0.00832472, 0.983981, 5.04854e-05, 0, 0.0128643, 0.980268, 6.71028e-05, 0, 0.0184947, 0.974875, 8.52749e-05, 0, 0.025237, 0.966063, 8.5531e-05, 0, 0.0331046, 0.956779, 9.00588e-05, 0, 0.0421067, 0.950259, 0.00010577, 0, 0.0522487, 0.944239, 0.000119458, 0, 0.0635343, 0.936341, 0.000122164, 0, 0.0759654, 0.928047, 0.000134929, 0, 0.0895434, 0.918065, 0.000145544, 0, 0.104269, 0.906267, 0.000150531, 0, 0.120142, 0.893419, 0.000161652, 0, 0.137163, 0.878758, 0.00016593, 0, 0.15533, 0.863699, 0.000174014, 0, 0.174645, 0.848876, 0.000177877, 0, 0.195106, 0.833032, 0.000184049, 0, 0.21671, 0.815557, 0.000186088, 0, 0.239454, 0.797323, 0.00019054, 0, 0.263332, 0.778124, 0.000191765, 0, 0.288336, 0.758929, 0.000192535, 0, 0.314451, 0.738979, 0.000192688, 0, 0.341658, 0.718213, 0.000191522, 0, 0.369924, 0.696947, 0.000190491, 0, 0.399202, 0.675807, 0.000187913, 0, 0.429416, 0.654147, 0.000184451, 0, 0.460447, 0.63229, 0.000181442, 0, 0.492064, 0.610499, 0.000177139, 0, 0.523809, 0.588747, 0.000172596, 0, 0.555555, 0.566783, 0.000167457, 0, 0.587301, 0.545359, 0.000162518, 0, 0.619048, 0.523984, 0.000156818, 0, 0.650794, 0.502917, 0.000151884, 0, 0.68254, 0.482294, 0.000145514, 0, 0.714286, 0.461945, 0.000140199, 0, 0.746032, 0.442133, 0.000134101, 0, 0.777778, 0.422705, 0.000128374, 0, 0.809524, 0.403916, 0.000122996, 0, 0.84127, 0.38554, 0.000116808, 0, 0.873016, 0.367909, 0.000111973, 0, 0.904762, 0.350651, 0.000105938, 0, 0.936508, 0.334208, 0.000101355, 0, 0.968254, 0.318123, 9.57629e-05, 0, 1, 1, 1.11633e-06, 0, 0, 1, 1.11639e-06, 0, 0, 1, 1.11725e-06, 0, 0, 1, 1.12096e-06, 0, 0, 0.999999, 1.1311e-06, 0, 0, 0.999997, 1.15315e-06, 0, 0, 0.999995, 1.1956e-06, 0, 0, 0.999989, 1.27239e-06, 0, 0, 0.999981, 1.40772e-06, 0, 0, 0.999969, 1.64541e-06, 0, 0, 0.999952, 2.06607e-06, 0, 0, 0.999928, 2.81783e-06, 0, 0, 0.999895, 4.16835e-06, 0, 0, 0.999848, 6.58728e-06, 0, 0, 0.999781, 1.08648e-05, 0, 0, 0.999682, 1.82579e-05, 0, 0, 0.999523, 3.06003e-05, 0, 1.59122e-05, 0.999205, 4.99862e-05, 0, 0.000391184, 0.998131, 7.3306e-05, 0, 0.00147534, 0.993334, 5.13229e-05, 0, 0.0034227, 0.99016, 4.67783e-05, 0, 0.00632232, 0.987321, 5.23413e-05, 0, 0.0102295, 0.984099, 6.4267e-05, 0, 0.0151794, 0.980432, 8.43042e-05, 0, 0.0211947, 0.974976, 0.000102819, 0, 0.0282899, 0.966429, 9.96234e-05, 0, 0.0364739, 0.957633, 0.000111074, 0, 0.0457522, 0.949422, 0.000128644, 0, 0.0561278, 0.943045, 0.000140076, 0, 0.0676023, 0.935448, 0.000146349, 0, 0.0801762, 0.927225, 0.000161854, 0, 0.0938499, 0.917033, 0.000169135, 0, 0.108623, 0.905762, 0.000179987, 0, 0.124496, 0.892879, 0.000189832, 0, 0.141469, 0.878435, 0.000195881, 0, 0.159541, 0.863114, 0.00020466, 0, 0.178713, 0.84776, 0.000209473, 0, 0.198985, 0.832084, 0.000214861, 0, 0.220355, 0.814915, 0.000217695, 0, 0.242823, 0.796711, 0.000220313, 0, 0.266385, 0.777603, 0.00022313, 0, 0.291036, 0.757991, 0.000222471, 0, 0.316767, 0.738371, 0.000222869, 0, 0.343563, 0.717872, 0.000221243, 0, 0.371402, 0.696619, 0.000218089, 0, 0.400248, 0.675379, 0.00021562, 0, 0.430047, 0.65411, 0.00021169, 0, 0.460709, 0.63241, 0.000206947, 0, 0.492079, 0.61046, 0.000201709, 0, 0.52381, 0.58903, 0.000196753, 0, 0.555556, 0.567267, 0.000189637, 0, 0.587302, 0.545886, 0.000184735, 0, 0.619048, 0.524714, 0.000177257, 0, 0.650794, 0.503789, 0.000171424, 0, 0.68254, 0.483204, 0.000164688, 0, 0.714286, 0.462976, 0.000157172, 0, 0.746032, 0.443294, 0.000151341, 0, 0.777778, 0.423988, 0.000143737, 0, 0.809524, 0.405325, 0.000138098, 0, 0.84127, 0.386981, 0.000130698, 0, 0.873016, 0.369436, 0.000125276, 0, 0.904762, 0.35219, 0.000118349, 0, 0.936508, 0.335804, 0.00011312, 0, 0.968254, 0.319749, 0.000106687, 0, 1, 1, 2.04685e-06, 0, 0, 1, 2.04694e-06, 0, 0, 1, 2.04831e-06, 0, 0, 0.999999, 2.05428e-06, 0, 0, 0.999999, 2.07056e-06, 0, 0, 0.999997, 2.10581e-06, 0, 0, 0.999993, 2.1732e-06, 0, 0, 0.999987, 2.29365e-06, 0, 0, 0.999979, 2.50243e-06, 0, 0, 0.999965, 2.86127e-06, 0, 0, 0.999947, 3.48028e-06, 0, 0, 0.999918, 4.55588e-06, 0, 0, 0.999881, 6.43303e-06, 0, 0, 0.999828, 9.70064e-06, 0, 0, 0.999753, 1.53233e-05, 0, 0, 0.999642, 2.4793e-05, 0, 0, 0.999464, 4.02032e-05, 0, 0.000122947, 0.999089, 6.35852e-05, 0, 0.000807414, 0.997567, 8.57026e-05, 0, 0.00227206, 0.992903, 5.94912e-05, 0, 0.00462812, 0.990011, 5.78515e-05, 0, 0.00794162, 0.987192, 6.5399e-05, 0, 0.0122534, 0.98418, 8.19675e-05, 0, 0.0175888, 0.980491, 0.000105514, 0, 0.0239635, 0.974779, 0.000121532, 0, 0.031387, 0.96675, 0.000119144, 0, 0.0398644, 0.958248, 0.000136125, 0, 0.0493982, 0.948884, 0.000155408, 0, 0.0599896, 0.941673, 0.000162281, 0, 0.0716382, 0.934521, 0.000176754, 0, 0.0843437, 0.926205, 0.000192873, 0, 0.0981056, 0.916089, 0.000200038, 0, 0.112923, 0.904963, 0.000213624, 0, 0.128796, 0.892089, 0.000221834, 0, 0.145725, 0.878028, 0.000232619, 0, 0.163709, 0.86249, 0.000238632, 0, 0.182749, 0.846587, 0.000247002, 0, 0.202847, 0.830988, 0.000250702, 0, 0.224001, 0.814165, 0.000255562, 0, 0.246214, 0.796135, 0.000257505, 0, 0.269482, 0.777052, 0.000258625, 0, 0.293805, 0.757201, 0.000258398, 0, 0.319176, 0.737655, 0.000256714, 0, 0.345587, 0.717477, 0.000255187, 0, 0.373021, 0.696433, 0.000251792, 0, 0.401454, 0.675084, 0.000247223, 0, 0.430844, 0.653907, 0.000242213, 0, 0.461125, 0.632561, 0.000237397, 0, 0.492187, 0.610658, 0.000229313, 0, 0.52381, 0.589322, 0.000224402, 0, 0.555556, 0.567857, 0.000216116, 0, 0.587302, 0.54652, 0.000209124, 0, 0.619048, 0.525433, 0.000201601, 0, 0.650794, 0.504679, 0.000192957, 0, 0.68254, 0.484203, 0.000186052, 0, 0.714286, 0.464203, 0.000177672, 0, 0.746032, 0.444549, 0.000170005, 0, 0.777778, 0.425346, 0.000162401, 0, 0.809524, 0.406706, 0.0001544, 0, 0.84127, 0.388576, 0.000147437, 0, 0.873016, 0.37094, 0.000139493, 0, 0.904762, 0.353996, 0.000133219, 0, 0.936508, 0.337391, 0.000125573, 0, 0.968254, 0.321648, 0.000119867, 0, 1, 1, 3.62511e-06, 0, 0, 1, 3.62525e-06, 0, 0, 1, 3.62739e-06, 0, 0, 0.999999, 3.63673e-06, 0, 0, 0.999998, 3.66214e-06, 0, 0, 0.999996, 3.71698e-06, 0, 0, 0.999992, 3.82116e-06, 0, 0, 0.999986, 4.00554e-06, 0, 0, 0.999976, 4.32058e-06, 0, 0, 0.999961, 4.85194e-06, 0, 0, 0.999938, 5.74808e-06, 0, 0, 0.999908, 7.26643e-06, 0, 0, 0.999865, 9.84707e-06, 0, 0, 0.999807, 1.42217e-05, 0, 0, 0.999723, 2.15581e-05, 0, 0, 0.999602, 3.36114e-05, 0, 1.19113e-05, 0.999398, 5.27353e-05, 0, 0.000355813, 0.998946, 8.05809e-05, 0, 0.00137768, 0.996647, 9.42908e-05, 0, 0.00322469, 0.992298, 6.68733e-05, 0, 0.00597897, 0.989802, 7.16564e-05, 0, 0.00968903, 0.987019, 8.21355e-05, 0, 0.0143845, 0.984219, 0.000104555, 0, 0.0200831, 0.980425, 0.000131245, 0, 0.0267948, 0.974241, 0.000139613, 0, 0.034525, 0.967006, 0.000145931, 0, 0.0432757, 0.95893, 0.000167153, 0, 0.0530471, 0.949157, 0.000188146, 0, 0.0638386, 0.94062, 0.000194625, 0, 0.0756487, 0.933509, 0.000213721, 0, 0.0884762, 0.925088, 0.000229616, 0, 0.10232, 0.915178, 0.000239638, 0, 0.117178, 0.904093, 0.000254814, 0, 0.133051, 0.891337, 0.000263685, 0, 0.149939, 0.877326, 0.000274789, 0, 0.167841, 0.861794, 0.000280534, 0, 0.18676, 0.845758, 0.000289534, 0, 0.206696, 0.829792, 0.000294446, 0, 0.22765, 0.813037, 0.000296877, 0, 0.249625, 0.795285, 0.000300217, 0, 0.27262, 0.776323, 0.000299826, 0, 0.296636, 0.756673, 0.000299787, 0, 0.321671, 0.736856, 0.000297867, 0, 0.347718, 0.716883, 0.000294052, 0, 0.374768, 0.696089, 0.000289462, 0, 0.402804, 0.67505, 0.000285212, 0, 0.431796, 0.653509, 0.00027653, 0, 0.461695, 0.63258, 0.000271759, 0, 0.49242, 0.61104, 0.000262811, 0, 0.523822, 0.589567, 0.000255151, 0, 0.555556, 0.568322, 0.000246434, 0, 0.587302, 0.547235, 0.000237061, 0, 0.619048, 0.52616, 0.000228343, 0, 0.650794, 0.505716, 0.000219236, 0, 0.68254, 0.485274, 0.000209595, 0, 0.714286, 0.465411, 0.000201011, 0, 0.746032, 0.445854, 0.00019109, 0, 0.777778, 0.426911, 0.000182897, 0, 0.809524, 0.408222, 0.000173569, 0, 0.84127, 0.390307, 0.000165496, 0, 0.873016, 0.372624, 0.000156799, 0, 0.904762, 0.355804, 0.00014917, 0, 0.936508, 0.33924, 0.000140907, 0, 0.968254, 0.323534, 0.000134062, 0, 1, 1, 6.22487e-06, 0, 0, 1, 6.2251e-06, 0, 0, 1, 6.22837e-06, 0, 0, 0.999999, 6.24259e-06, 0, 0, 0.999998, 6.28127e-06, 0, 0, 0.999996, 6.36451e-06, 0, 0, 0.999991, 6.5218e-06, 0, 0, 0.999984, 6.79782e-06, 0, 0, 0.999973, 7.26361e-06, 0, 0, 0.999955, 8.03644e-06, 0, 0, 0.999931, 9.31397e-06, 0, 0, 0.999896, 1.14299e-05, 0, 0, 0.999847, 1.49402e-05, 0, 0, 0.999784, 2.07461e-05, 0, 0, 0.999692, 3.02493e-05, 0, 0, 0.999554, 4.54957e-05, 0, 9.97275e-05, 0.999326, 6.90762e-05, 0, 0.000724813, 0.998757, 0.000101605, 0, 0.0020972, 0.995367, 9.58745e-05, 0, 0.00432324, 0.99209, 8.32808e-05, 0, 0.00746347, 0.989517, 8.87601e-05, 0, 0.0115534, 0.987008, 0.00010564, 0, 0.0166134, 0.98421, 0.000133179, 0, 0.0226552, 0.98021, 0.000161746, 0, 0.0296838, 0.973676, 0.000161821, 0, 0.0377016, 0.967052, 0.000178635, 0, 0.0467079, 0.959385, 0.000206765, 0, 0.0567013, 0.949461, 0.00022476, 0, 0.0676796, 0.939578, 0.00023574, 0, 0.0796403, 0.932416, 0.00025893, 0, 0.0925812, 0.923759, 0.000271228, 0, 0.106501, 0.914223, 0.000289165, 0, 0.121397, 0.902942, 0.000301156, 0, 0.13727, 0.890419, 0.000313852, 0, 0.15412, 0.876639, 0.000324408, 0, 0.171946, 0.861316, 0.00033249, 0, 0.190751, 0.84496, 0.000338497, 0, 0.210537, 0.828427, 0.000345861, 0, 0.231305, 0.811871, 0.000347863, 0, 0.253057, 0.794397, 0.000350225, 0, 0.275797, 0.775726, 0.000349915, 0, 0.299525, 0.75617, 0.000347297, 0, 0.324242, 0.736091, 0.000344232, 0, 0.349947, 0.716213, 0.000340835, 0, 0.376633, 0.695736, 0.000332369, 0, 0.404289, 0.674961, 0.000327943, 0, 0.432895, 0.653518, 0.000318533, 0, 0.462415, 0.632574, 0.000310391, 0, 0.492788, 0.61134, 0.000300755, 0, 0.523909, 0.590017, 0.000290506, 0, 0.555556, 0.568752, 0.000280446, 0, 0.587302, 0.548061, 0.000269902, 0, 0.619048, 0.52711, 0.000258815, 0, 0.650794, 0.506682, 0.000248481, 0, 0.68254, 0.486524, 0.000237141, 0, 0.714286, 0.466812, 0.000226872, 0, 0.746032, 0.44732, 0.000216037, 0, 0.777778, 0.428473, 0.000205629, 0, 0.809524, 0.409921, 0.000195691, 0, 0.84127, 0.392028, 0.000185457, 0, 0.873016, 0.374606, 0.000176436, 0, 0.904762, 0.357601, 0.000166508, 0, 0.936508, 0.341348, 0.000158385, 0, 0.968254, 0.32542, 0.000149203, 0, 1, 1, 1.03967e-05, 0, 0, 1, 1.0397e-05, 0, 0, 1, 1.04019e-05, 0, 0, 0.999999, 1.04231e-05, 0, 0, 0.999998, 1.04806e-05, 0, 0, 0.999995, 1.06042e-05, 0, 0, 0.999991, 1.08366e-05, 0, 0, 0.999982, 1.12415e-05, 0, 0, 0.999968, 1.19174e-05, 0, 0, 0.99995, 1.30227e-05, 0, 0, 0.999922, 1.48176e-05, 0, 0, 0.999884, 1.77303e-05, 0, 0, 0.99983, 2.24564e-05, 0, 0, 0.999758, 3.00966e-05, 0, 0, 0.999654, 4.23193e-05, 0, 5.49083e-06, 0.999503, 6.14848e-05, 0, 0.000296087, 0.999237, 9.03576e-05, 0, 0.00123144, 0.998491, 0.0001271, 0, 0.00295954, 0.994594, 0.000107754, 0, 0.00555829, 0.99178, 0.000103025, 0, 0.00907209, 0.989265, 0.00011154, 0, 0.0135257, 0.986998, 0.000136296, 0, 0.0189327, 0.984137, 0.000169154, 0, 0.0252993, 0.979798, 0.000196671, 0, 0.0326272, 0.97337, 0.000196678, 0, 0.0409157, 0.967239, 0.000223121, 0, 0.0501623, 0.959543, 0.000253809, 0, 0.0603638, 0.949466, 0.000265972, 0, 0.0715171, 0.939074, 0.000288372, 0, 0.0836187, 0.931118, 0.000310983, 0, 0.0966657, 0.922525, 0.000325561, 0, 0.110656, 0.912983, 0.000345725, 0, 0.125588, 0.901617, 0.0003556, 0, 0.141461, 0.889487, 0.000374012, 0, 0.158275, 0.875787, 0.000383445, 0, 0.176031, 0.860654, 0.000393972, 0, 0.19473, 0.844417, 0.000400311, 0, 0.214374, 0.82741, 0.000405004, 0, 0.234967, 0.810545, 0.000407378, 0, 0.256512, 0.793312, 0.000407351, 0, 0.279011, 0.774847, 0.000406563, 0, 0.302468, 0.755621, 0.000404903, 0, 0.326887, 0.735511, 0.000397486, 0, 0.352266, 0.715435, 0.00039357, 0, 0.378605, 0.695403, 0.000384739, 0, 0.405897, 0.674681, 0.000376108, 0, 0.43413, 0.65359, 0.000365997, 0, 0.463277, 0.632471, 0.000354957, 0, 0.493295, 0.61151, 0.000343593, 0, 0.524106, 0.59064, 0.000331841, 0, 0.555561, 0.569386, 0.000318891, 0, 0.587302, 0.548785, 0.0003072, 0, 0.619048, 0.528146, 0.00029361, 0, 0.650794, 0.507872, 0.000281709, 0, 0.68254, 0.487805, 0.000268627, 0, 0.714286, 0.468196, 0.000255887, 0, 0.746032, 0.448922, 0.000243997, 0, 0.777778, 0.430093, 0.000231662, 0, 0.809524, 0.411845, 0.000220339, 0, 0.84127, 0.393808, 0.000208694, 0, 0.873016, 0.376615, 0.000198045, 0, 0.904762, 0.359655, 0.000187375, 0, 0.936508, 0.343452, 0.000177371, 0, 0.968254, 0.32765, 0.000167525, 0, 1, 1, 1.69351e-05, 0, 0, 1, 1.69356e-05, 0, 0, 1, 1.69427e-05, 0, 0, 0.999999, 1.69736e-05, 0, 0, 0.999998, 1.70575e-05, 0, 0, 0.999995, 1.72372e-05, 0, 0, 0.99999, 1.75739e-05, 0, 0, 0.999979, 1.81568e-05, 0, 0, 0.999966, 1.91206e-05, 0, 0, 0.999944, 2.0677e-05, 0, 0, 0.999912, 2.31644e-05, 0, 0, 0.999869, 2.71268e-05, 0, 0, 0.999811, 3.34272e-05, 0, 0, 0.99973, 4.33979e-05, 0, 0, 0.999617, 5.90083e-05, 0, 6.80315e-05, 0.999445, 8.29497e-05, 0, 0.000612796, 0.999138, 0.000118019, 0, 0.00187408, 0.998095, 0.000156712, 0, 0.00395791, 0.993919, 0.000125054, 0, 0.00692144, 0.991333, 0.000126091, 0, 0.0107962, 0.989226, 0.000144912, 0, 0.0155986, 0.986954, 0.000175737, 0, 0.0213364, 0.983982, 0.000213883, 0, 0.0280114, 0.979128, 0.000234526, 0, 0.0356226, 0.973327, 0.000243725, 0, 0.0441668, 0.967416, 0.0002773, 0, 0.0536399, 0.959729, 0.000308799, 0, 0.0640376, 0.949758, 0.000322447, 0, 0.0753554, 0.939173, 0.000350021, 0, 0.0875893, 0.9296, 0.000370089, 0, 0.100736, 0.921181, 0.000391365, 0, 0.114793, 0.91164, 0.000413636, 0, 0.129759, 0.900435, 0.000427068, 0, 0.145632, 0.888183, 0.000441046, 0, 0.162412, 0.874772, 0.000454968, 0, 0.180101, 0.859566, 0.000461882, 0, 0.1987, 0.843579, 0.000471556, 0, 0.218213, 0.826453, 0.000474335, 0, 0.238641, 0.809164, 0.000477078, 0, 0.259989, 0.792179, 0.00047755, 0, 0.282262, 0.773866, 0.000472573, 0, 0.305464, 0.754944, 0.000469765, 0, 0.329599, 0.735133, 0.000462371, 0, 0.35467, 0.714858, 0.000453674, 0, 0.380678, 0.694829, 0.000443888, 0, 0.407622, 0.674453, 0.000432052, 0, 0.435493, 0.653685, 0.000420315, 0, 0.464275, 0.632666, 0.000406829, 0, 0.493938, 0.611676, 0.000392234, 0, 0.524422, 0.591193, 0.000379208, 0, 0.555624, 0.570145, 0.00036319, 0, 0.587302, 0.549566, 0.000349111, 0, 0.619048, 0.529278, 0.000334166, 0, 0.650794, 0.509026, 0.000318456, 0, 0.68254, 0.489186, 0.00030449, 0, 0.714286, 0.469662, 0.000289051, 0, 0.746032, 0.450691, 0.000275494, 0, 0.777778, 0.431841, 0.000261437, 0, 0.809524, 0.413752, 0.000247846, 0, 0.84127, 0.395951, 0.000235085, 0, 0.873016, 0.378633, 0.000222245, 0, 0.904762, 0.36194, 0.000210533, 0, 0.936508, 0.345599, 0.000198494, 0, 0.968254, 0.329999, 0.000188133, 0, 1, 1, 2.69663e-05, 0, 0, 1, 2.6967e-05, 0, 0, 1, 2.69772e-05, 0, 0, 0.999999, 2.70214e-05, 0, 0, 0.999998, 2.71415e-05, 0, 0, 0.999994, 2.7398e-05, 0, 0, 0.999988, 2.78771e-05, 0, 0, 0.999977, 2.87019e-05, 0, 0, 0.999961, 3.00544e-05, 0, 0, 0.999937, 3.22138e-05, 0, 0, 0.999904, 3.56163e-05, 0, 0, 0.999854, 4.09465e-05, 0, 0, 0.99979, 4.92651e-05, 0, 0, 0.999699, 6.21722e-05, 0, 8.8288e-07, 0.999572, 8.19715e-05, 0, 0.000223369, 0.999381, 0.000111689, 0, 0.00105414, 0.999016, 0.000153862, 0, 0.0026493, 0.997437, 0.000187667, 0, 0.00508608, 0.993545, 0.000155672, 0, 0.00840554, 0.991135, 0.000161455, 0, 0.012629, 0.989157, 0.000188241, 0, 0.0177661, 0.986874, 0.000226229, 0, 0.0238198, 0.983714, 0.000268668, 0, 0.0307887, 0.978301, 0.000277109, 0, 0.0386688, 0.973227, 0.000303446, 0, 0.0474554, 0.967317, 0.000341851, 0, 0.0571428, 0.959477, 0.000370885, 0, 0.0677256, 0.950012, 0.000392753, 0, 0.0791988, 0.939484, 0.00042781, 0, 0.0915576, 0.928135, 0.000443866, 0, 0.104798, 0.919819, 0.000472959, 0, 0.118918, 0.910049, 0.000491551, 0, 0.133915, 0.899181, 0.000512616, 0, 0.149788, 0.886881, 0.000523563, 0, 0.166537, 0.87359, 0.000540183, 0, 0.184164, 0.858613, 0.000547386, 0, 0.202669, 0.842809, 0.000554809, 0, 0.222056, 0.825727, 0.000558316, 0, 0.242329, 0.808086, 0.000557824, 0, 0.263492, 0.790728, 0.000556346, 0, 0.285551, 0.772987, 0.000552672, 0, 0.30851, 0.7541, 0.000543738, 0, 0.332376, 0.734669, 0.000536107, 0, 0.357153, 0.714411, 0.000523342, 0, 0.382845, 0.694196, 0.000512238, 0, 0.409454, 0.674252, 0.000497465, 0, 0.436977, 0.65357, 0.000481096, 0, 0.465404, 0.632999, 0.000467054, 0, 0.494713, 0.611994, 0.000448771, 0, 0.524864, 0.591604, 0.000431889, 0, 0.555779, 0.571134, 0.000415238, 0, 0.587302, 0.550528, 0.000396369, 0, 0.619048, 0.530292, 0.000379477, 0, 0.650794, 0.510364, 0.000361488, 0, 0.68254, 0.490749, 0.000343787, 0, 0.714286, 0.471266, 0.000327822, 0, 0.746032, 0.452462, 0.000310626, 0, 0.777778, 0.433907, 0.000295352, 0, 0.809524, 0.415659, 0.000279179, 0, 0.84127, 0.398138, 0.000264685, 0, 0.873016, 0.380833, 0.000249905, 0, 0.904762, 0.364247, 0.000236282, 0, 0.936508, 0.348041, 0.000222905, 0, 0.968254, 0.332389, 0.000210522, 0, 1, 1, 4.20604e-05, 0, 0, 1, 4.20614e-05, 0, 0, 1, 4.20757e-05, 0, 0, 0.999999, 4.2138e-05, 0, 0, 0.999997, 4.23067e-05, 0, 0, 0.999993, 4.26668e-05, 0, 0, 0.999986, 4.33372e-05, 0, 0, 0.999974, 4.44857e-05, 0, 0, 0.999956, 4.63554e-05, 0, 0, 0.99993, 4.93105e-05, 0, 0, 0.999892, 5.39077e-05, 0, 0, 0.999838, 6.10005e-05, 0, 0, 0.999767, 7.18822e-05, 0, 0, 0.999666, 8.84581e-05, 0, 3.65471e-05, 0.999525, 0.000113398, 0, 0.000485623, 0.999311, 0.000150043, 0, 0.00162096, 0.998865, 0.000200063, 0, 0.00355319, 0.996278, 0.000211014, 0, 0.00633818, 0.992956, 0.000189672, 0, 0.0100043, 0.991017, 0.000210262, 0, 0.0145648, 0.989055, 0.000244292, 0, 0.0200237, 0.986741, 0.000290481, 0, 0.0263798, 0.983288, 0.000334303, 0, 0.033629, 0.977784, 0.000340307, 0, 0.0417652, 0.973037, 0.000377864, 0, 0.0507821, 0.967181, 0.0004239, 0, 0.060673, 0.958971, 0.000443854, 0, 0.0714314, 0.950093, 0.000483039, 0, 0.0830518, 0.939552, 0.000517934, 0, 0.0955288, 0.927678, 0.000539449, 0, 0.108859, 0.918278, 0.000568604, 0, 0.123038, 0.908449, 0.000588505, 0, 0.138065, 0.897713, 0.000612473, 0, 0.153938, 0.885533, 0.000625575, 0, 0.170657, 0.872131, 0.00063854, 0, 0.188224, 0.857517, 0.000647034, 0, 0.20664, 0.841796, 0.00065209, 0, 0.225909, 0.824726, 0.0006544, 0, 0.246035, 0.807297, 0.000655744, 0, 0.267022, 0.789058, 0.000646716, 0, 0.288878, 0.77189, 0.000643898, 0, 0.311607, 0.753082, 0.000629973, 0, 0.335216, 0.7341, 0.000621564, 0, 0.359713, 0.714094, 0.000605171, 0, 0.385103, 0.693839, 0.000588752, 0, 0.41139, 0.673891, 0.000573294, 0, 0.438576, 0.653565, 0.000552682, 0, 0.466656, 0.633326, 0.000533446, 0, 0.495617, 0.612582, 0.000514635, 0, 0.525431, 0.59205, 0.00049303, 0, 0.556041, 0.571918, 0.000471842, 0, 0.587338, 0.551572, 0.000451713, 0, 0.619048, 0.531553, 0.000430049, 0, 0.650794, 0.51175, 0.000410445, 0, 0.68254, 0.49238, 0.000390098, 0, 0.714286, 0.473143, 0.000370033, 0, 0.746032, 0.45423, 0.000351205, 0, 0.777778, 0.435963, 0.000332049, 0, 0.809524, 0.41787, 0.000315021, 0, 0.84127, 0.400387, 0.000297315, 0, 0.873016, 0.383332, 0.000281385, 0, 0.904762, 0.366665, 0.000265397, 0, 0.936508, 0.350633, 0.000250601, 0, 0.968254, 0.334964, 0.00023589, 0, 1, 1, 6.43736e-05, 0, 0, 1, 6.4375e-05, 0, 0, 1, 6.43947e-05, 0, 0, 0.999999, 6.4481e-05, 0, 0, 0.999997, 6.47143e-05, 0, 0, 0.999994, 6.52119e-05, 0, 0, 0.999985, 6.61359e-05, 0, 0, 0.999972, 6.77116e-05, 0, 0, 0.999952, 7.02599e-05, 0, 0, 0.999922, 7.42517e-05, 0, 0, 0.99988, 8.03906e-05, 0, 0, 0.99982, 8.97315e-05, 0, 0, 0.999741, 0.000103838, 0, 0, 0.999629, 0.00012496, 0, 0.000149024, 0.999474, 0.000156161, 0, 0.000861027, 0.999229, 0.000201034, 0, 0.00231198, 0.998662, 0.000259069, 0, 0.00458147, 0.995299, 0.000245439, 0, 0.00770895, 0.992732, 0.00024498, 0, 0.0117126, 0.990847, 0.000273211, 0, 0.0165989, 0.988911, 0.000316492, 0, 0.0223674, 0.98654, 0.00037161, 0, 0.0290135, 0.982636, 0.000410352, 0, 0.0365309, 0.977346, 0.000421756, 0, 0.0449117, 0.972909, 0.000475578, 0, 0.0541481, 0.966821, 0.000522482, 0, 0.0642326, 0.958686, 0.000545008, 0, 0.075158, 0.949754, 0.000589286, 0, 0.0869181, 0.939184, 0.000619995, 0, 0.0995074, 0.927505, 0.000654266, 0, 0.112922, 0.916606, 0.000682362, 0, 0.127157, 0.906707, 0.000704286, 0, 0.142212, 0.895937, 0.000725909, 0, 0.158085, 0.883913, 0.000743939, 0, 0.174776, 0.870642, 0.000755157, 0, 0.192287, 0.856241, 0.000764387, 0, 0.210619, 0.84069, 0.000771032, 0, 0.229775, 0.823728, 0.000765906, 0, 0.249761, 0.806481, 0.000767604, 0, 0.270582, 0.787924, 0.000754385, 0, 0.292243, 0.770588, 0.000749668, 0, 0.314753, 0.751991, 0.000731613, 0, 0.338118, 0.733407, 0.000717655, 0, 0.362347, 0.713688, 0.000700604, 0, 0.387447, 0.693595, 0.000678765, 0, 0.413424, 0.673426, 0.000657042, 0, 0.440284, 0.65359, 0.000635892, 0, 0.468027, 0.633576, 0.000611569, 0, 0.496645, 0.613144, 0.000586011, 0, 0.526122, 0.592711, 0.000563111, 0, 0.556417, 0.572722, 0.000537699, 0, 0.587451, 0.552762, 0.000512556, 0, 0.619048, 0.532985, 0.000489757, 0, 0.650794, 0.513219, 0.000464139, 0, 0.68254, 0.493992, 0.000442193, 0, 0.714286, 0.47509, 0.000418629, 0, 0.746032, 0.456287, 0.000397045, 0, 0.777778, 0.438152, 0.000375504, 0, 0.809524, 0.420294, 0.00035492, 0, 0.84127, 0.402749, 0.000335327, 0, 0.873016, 0.385879, 0.000316422, 0, 0.904762, 0.369352, 0.000298333, 0, 0.936508, 0.353301, 0.000281417, 0, 0.968254, 0.337781, 0.000265203, 0, 1, 1, 9.68267e-05, 0, 0, 1, 9.68284e-05, 0, 0, 1, 9.68556e-05, 0, 0, 0.999999, 9.69733e-05, 0, 0, 0.999997, 9.72913e-05, 0, 0, 0.999993, 9.79688e-05, 0, 0, 0.999984, 9.92239e-05, 0, 0, 0.999969, 0.000101356, 0, 0, 0.999946, 0.000104784, 0, 0, 0.999913, 0.000110111, 0, 0, 0.999868, 0.000118217, 0, 0, 0.999801, 0.000130396, 0, 0, 0.999712, 0.000148523, 0, 1.24907e-05, 0.999589, 0.000175233, 0, 0.000355405, 0.999416, 0.000213999, 0, 0.0013528, 0.999136, 0.000268529, 0, 0.00312557, 0.998367, 0.000333088, 0, 0.00573045, 0.994701, 0.000304757, 0, 0.00919397, 0.992497, 0.000318031, 0, 0.0135261, 0.990608, 0.000353863, 0, 0.0187278, 0.988715, 0.000409044, 0, 0.0247947, 0.986241, 0.000472967, 0, 0.0317196, 0.981696, 0.000495104, 0, 0.039494, 0.977097, 0.000532873, 0, 0.0481087, 0.972583, 0.000594447, 0, 0.0575549, 0.966142, 0.000636867, 0, 0.0678242, 0.95823, 0.000669899, 0, 0.0789089, 0.949677, 0.000719499, 0, 0.0908023, 0.939226, 0.000750584, 0, 0.103499, 0.927501, 0.000793183, 0, 0.116993, 0.915199, 0.00081995, 0, 0.131282, 0.90498, 0.000847654, 0, 0.146364, 0.894243, 0.000868929, 0, 0.162237, 0.882154, 0.000884278, 0, 0.178902, 0.869161, 0.000898108, 0, 0.196358, 0.854751, 0.000901254, 0, 0.21461, 0.839368, 0.00090679, 0, 0.23366, 0.822874, 0.000901541, 0, 0.253512, 0.805514, 0.000897297, 0, 0.274174, 0.78716, 0.000881856, 0, 0.29565, 0.769061, 0.000870032, 0, 0.31795, 0.751, 0.000851719, 0, 0.341081, 0.732614, 0.000830671, 0, 0.365053, 0.713171, 0.000806569, 0, 0.389874, 0.693472, 0.00078338, 0, 0.415553, 0.673528, 0.000756404, 0, 0.442098, 0.653397, 0.000726872, 0, 0.469512, 0.633781, 0.000700494, 0, 0.497794, 0.613877, 0.00067105, 0, 0.526935, 0.593506, 0.000640361, 0, 0.556908, 0.573667, 0.000613502, 0, 0.587657, 0.553932, 0.000583177, 0, 0.61906, 0.534345, 0.000554375, 0, 0.650794, 0.515042, 0.000527811, 0, 0.68254, 0.495674, 0.000499367, 0, 0.714286, 0.477132, 0.00047429, 0, 0.746032, 0.458609, 0.000447726, 0, 0.777778, 0.440354, 0.000424205, 0, 0.809524, 0.422765, 0.000399549, 0, 0.84127, 0.405472, 0.000378315, 0, 0.873016, 0.388482, 0.000355327, 0, 0.904762, 0.372191, 0.000336122, 0, 0.936508, 0.356099, 0.000315247, 0, 0.968254, 0.340737, 0.00029794, 0, 1, 1, 0.000143327, 0, 0, 1, 0.00014333, 0, 0, 1, 0.000143366, 0, 0, 0.999999, 0.000143524, 0, 0, 0.999996, 0.000143952, 0, 0, 0.999991, 0.000144862, 0, 0, 0.999981, 0.000146544, 0, 0, 0.999966, 0.000149391, 0, 0, 0.999941, 0.000153946, 0, 0, 0.999905, 0.000160971, 0, 0, 0.999852, 0.000171562, 0, 0, 0.99978, 0.00018729, 0, 0, 0.999681, 0.000210386, 0, 8.26239e-05, 0.999546, 0.000243906, 0, 0.000664807, 0.999352, 0.000291739, 0, 0.00196192, 0.999027, 0.000357419, 0, 0.00405941, 0.997886, 0.000422349, 0, 0.00699664, 0.99419, 0.000385008, 0, 0.0107896, 0.99214, 0.000409775, 0, 0.0154415, 0.990274, 0.000456418, 0, 0.0209488, 0.988455, 0.000527008, 0, 0.0273037, 0.985804, 0.000597685, 0, 0.0344969, 0.98103, 0.000613124, 0, 0.0425183, 0.976674, 0.000668321, 0, 0.0513575, 0.972021, 0.000736985, 0, 0.0610046, 0.965274, 0.000773789, 0, 0.0714508, 0.958046, 0.000830852, 0, 0.0826877, 0.949333, 0.000875766, 0, 0.0947085, 0.939135, 0.000917088, 0, 0.107507, 0.927119, 0.000952244, 0, 0.121078, 0.91469, 0.000990626, 0, 0.135419, 0.903006, 0.00101304, 0, 0.150526, 0.892368, 0.00103834, 0, 0.166399, 0.880231, 0.00105002, 0, 0.183038, 0.867432, 0.00106331, 0, 0.200443, 0.853208, 0.00106783, 0, 0.218618, 0.837956, 0.00106458, 0, 0.237566, 0.821772, 0.00105945, 0, 0.257291, 0.804328, 0.00104685, 0, 0.2778, 0.786465, 0.00103178, 0, 0.2991, 0.768004, 0.00101077, 0, 0.321199, 0.74972, 0.000985504, 0, 0.344106, 0.731682, 0.000962893, 0, 0.36783, 0.712813, 0.000932146, 0, 0.392383, 0.693139, 0.00089871, 0, 0.417774, 0.673566, 0.000869678, 0, 0.444013, 0.653483, 0.000835525, 0, 0.471107, 0.633891, 0.000799853, 0, 0.49906, 0.614433, 0.000766838, 0, 0.527869, 0.594586, 0.000732227, 0, 0.557517, 0.574769, 0.000696442, 0, 0.587966, 0.555149, 0.000663935, 0, 0.61913, 0.535898, 0.000629826, 0, 0.650794, 0.516753, 0.000596486, 0, 0.68254, 0.497816, 0.000567078, 0, 0.714286, 0.479034, 0.000534399, 0, 0.746032, 0.460975, 0.000507013, 0, 0.777778, 0.442935, 0.000477421, 0, 0.809524, 0.425263, 0.000451101, 0, 0.84127, 0.408248, 0.000424964, 0, 0.873016, 0.391339, 0.00039993, 0, 0.904762, 0.37513, 0.000377619, 0, 0.936508, 0.359172, 0.000354418, 0, 0.968254, 0.343876, 0.000334823, 0, 1, 1, 0.000209042, 0, 0, 1, 0.000209045, 0, 0, 1, 0.000209093, 0, 0, 0.999999, 0.000209304, 0, 0, 0.999996, 0.000209871, 0, 0, 0.999991, 0.000211078, 0, 0, 0.999979, 0.000213304, 0, 0, 0.999963, 0.000217061, 0, 0, 0.999933, 0.000223042, 0, 0, 0.999894, 0.000232206, 0, 0, 0.999837, 0.000245901, 0, 0, 0.999756, 0.000266023, 0, 1.02927e-06, 0.999648, 0.000295204, 0, 0.000233468, 0.999499, 0.000336958, 0, 0.00108237, 0.999283, 0.000395563, 0, 0.00268832, 0.998896, 0.000473785, 0, 0.00511138, 0.997006, 0.000520008, 0, 0.00837705, 0.993819, 0.000497261, 0, 0.0124928, 0.991632, 0.000523722, 0, 0.0174561, 0.989875, 0.000587258, 0, 0.0232596, 0.988109, 0.000676329, 0, 0.0298932, 0.985155, 0.000747701, 0, 0.0373453, 0.980479, 0.000768803, 0, 0.0456045, 0.976271, 0.000841054, 0, 0.0546593, 0.971347, 0.000911469, 0, 0.0644994, 0.964528, 0.000953057, 0, 0.0751152, 0.957632, 0.00102221, 0, 0.0864981, 0.948681, 0.00106122, 0, 0.0986407, 0.938716, 0.00111857, 0, 0.111537, 0.926629, 0.00114762, 0, 0.125182, 0.914025, 0.00118995, 0, 0.139571, 0.901026, 0.00121228, 0, 0.154703, 0.890358, 0.00123946, 0, 0.170576, 0.878283, 0.0012527, 0, 0.18719, 0.865459, 0.00125536, 0, 0.204547, 0.851407, 0.00126134, 0, 0.222648, 0.836276, 0.00124759, 0, 0.241498, 0.820436, 0.00124443, 0, 0.261101, 0.803253, 0.00122071, 0, 0.281465, 0.785562, 0.00120107, 0, 0.302595, 0.76718, 0.00117762, 0, 0.324501, 0.748551, 0.00114289, 0, 0.347192, 0.730564, 0.00110872, 0, 0.370679, 0.712253, 0.00107636, 0, 0.394973, 0.692867, 0.00103646, 0, 0.420085, 0.673695, 0.000996793, 0, 0.446027, 0.653912, 0.00095675, 0, 0.47281, 0.634129, 0.000916739, 0, 0.500441, 0.615004, 0.000874401, 0, 0.528921, 0.595587, 0.000833411, 0, 0.558244, 0.575965, 0.000794556, 0, 0.588384, 0.5566, 0.00075196, 0, 0.619281, 0.537428, 0.000716381, 0, 0.650795, 0.518623, 0.000676558, 0, 0.68254, 0.499964, 0.00064074, 0, 0.714286, 0.481356, 0.000605984, 0, 0.746032, 0.463279, 0.000570256, 0, 0.777778, 0.445673, 0.000540138, 0, 0.809524, 0.428032, 0.000507299, 0, 0.84127, 0.411112, 0.000479553, 0, 0.873016, 0.394444, 0.000450737, 0, 0.904762, 0.378247, 0.000424269, 0, 0.936508, 0.362415, 0.000399111, 0, 0.968254, 0.347103, 0.000375274, 0, 1, 1, 0.000300729, 0, 0, 1, 0.000300733, 0, 0, 1, 0.000300797, 0, 0, 0.999998, 0.000301072, 0, 0, 0.999996, 0.000301817, 0, 0, 0.999989, 0.000303398, 0, 0, 0.999977, 0.000306309, 0, 0, 0.999958, 0.000311209, 0, 0, 0.999927, 0.000318975, 0, 0, 0.999884, 0.000330804, 0, 0, 0.99982, 0.00034834, 0, 0, 0.999733, 0.000373854, 0, 3.26995e-05, 0.999613, 0.000410424, 0, 0.000477174, 0.999447, 0.000462047, 0, 0.00161099, 0.999204, 0.000533322, 0, 0.00353153, 0.998725, 0.000624964, 0, 0.00627965, 0.995871, 0.000631786, 0, 0.0098693, 0.993194, 0.000632017, 0, 0.0143011, 0.991541, 0.00068923, 0, 0.019568, 0.989773, 0.000766892, 0, 0.0256593, 0.987647, 0.000863668, 0, 0.0325625, 0.984193, 0.000922089, 0, 0.0402647, 0.980016, 0.000970749, 0, 0.0487532, 0.975859, 0.00106027, 0, 0.058016, 0.970514, 0.00112239, 0, 0.0680419, 0.963625, 0.00117212, 0, 0.0788208, 0.956959, 0.00125211, 0, 0.0903439, 0.947956, 0.00129411, 0, 0.102604, 0.93809, 0.00135879, 0, 0.115594, 0.92659, 0.00139309, 0, 0.129309, 0.913829, 0.00143253, 0, 0.143745, 0.90005, 0.00145809, 0, 0.158901, 0.888129, 0.0014748, 0, 0.174774, 0.87607, 0.00148756, 0, 0.191365, 0.863461, 0.00148714, 0, 0.208674, 0.849594, 0.00148892, 0, 0.226705, 0.834531, 0.00146496, 0, 0.245461, 0.81903, 0.0014579, 0, 0.264947, 0.802122, 0.00143039, 0, 0.28517, 0.78445, 0.00139717, 0, 0.306137, 0.766434, 0.00136312, 0, 0.327857, 0.747816, 0.00132597, 0, 0.350341, 0.729519, 0.00128323, 0, 0.373598, 0.711454, 0.00123803, 0, 0.397642, 0.692699, 0.00119097, 0, 0.422485, 0.673723, 0.00114565, 0, 0.448139, 0.654386, 0.00109552, 0, 0.474619, 0.634673, 0.00104553, 0, 0.501933, 0.615554, 0.00099985, 0, 0.530089, 0.596462, 0.000948207, 0, 0.559087, 0.577385, 0.000902299, 0, 0.588913, 0.558257, 0.000856448, 0, 0.619525, 0.5392, 0.000810395, 0, 0.650826, 0.520543, 0.000768558, 0, 0.68254, 0.502206, 0.0007239, 0, 0.714286, 0.48402, 0.000685794, 0, 0.746032, 0.465779, 0.00064471, 0, 0.777778, 0.448455, 0.000609583, 0, 0.809524, 0.431091, 0.00057227, 0, 0.84127, 0.414147, 0.00054042, 0, 0.873016, 0.39765, 0.000506545, 0, 0.904762, 0.381576, 0.000477635, 0, 0.936508, 0.365881, 0.000448446, 0, 0.968254, 0.350582, 0.000421424, 0, 1, 1, 0.000427144, 0, 0, 1, 0.000427151, 0, 0, 1, 0.000427232, 0, 0, 0.999998, 0.00042759, 0, 0, 0.999995, 0.000428555, 0, 0, 0.999988, 0.000430603, 0, 0, 0.999976, 0.000434368, 0, 0, 0.999952, 0.000440688, 0, 0, 0.999919, 0.000450667, 0, 0, 0.999871, 0.00046578, 0, 0, 0.999801, 0.000488024, 0, 0, 0.999704, 0.000520092, 0, 0.000129791, 0.999572, 0.000565553, 0, 0.000821056, 0.999389, 0.000628906, 0, 0.00225241, 0.999114, 0.000714911, 0, 0.00449109, 0.998488, 0.000819218, 0, 0.00756249, 0.995234, 0.00080415, 0, 0.0114716, 0.993021, 0.000830181, 0, 0.0162131, 0.991407, 0.000902645, 0, 0.021776, 0.989625, 0.000996934, 0, 0.0281471, 0.987064, 0.00109707, 0, 0.0353118, 0.983265, 0.00114353, 0, 0.0432562, 0.979535, 0.0012272, 0, 0.0519665, 0.975224, 0.00132642, 0, 0.0614298, 0.969574, 0.00138092, 0, 0.0716348, 0.963021, 0.00145896, 0, 0.0825709, 0.956046, 0.00152834, 0, 0.094229, 0.947136, 0.00158217, 0, 0.106602, 0.937313, 0.0016347, 0, 0.119682, 0.926073, 0.00168383, 0, 0.133465, 0.913121, 0.00171627, 0, 0.147947, 0.899165, 0.00174229, 0, 0.163125, 0.885891, 0.00176137, 0, 0.178998, 0.873783, 0.00176406, 0, 0.195566, 0.861331, 0.00176156, 0, 0.21283, 0.847569, 0.00175346, 0, 0.230793, 0.832785, 0.00172753, 0, 0.249459, 0.817442, 0.00170204, 0, 0.268832, 0.800613, 0.00166576, 0, 0.28892, 0.783597, 0.00162909, 0, 0.30973, 0.76571, 0.0015826, 0, 0.331271, 0.747021, 0.00153106, 0, 0.353554, 0.728593, 0.00148036, 0, 0.37659, 0.710661, 0.00142808, 0, 0.400391, 0.692426, 0.00136906, 0, 0.424973, 0.673623, 0.00131066, 0, 0.450347, 0.65494, 0.00125569, 0, 0.476531, 0.635448, 0.00119517, 0, 0.503535, 0.616221, 0.00113828, 0, 0.531372, 0.597531, 0.0010816, 0, 0.560047, 0.578795, 0.00102673, 0, 0.589554, 0.559892, 0.000970985, 0, 0.619869, 0.541307, 0.000919773, 0, 0.650923, 0.522608, 0.000868479, 0, 0.68254, 0.504484, 0.00082137, 0, 0.714286, 0.486603, 0.000772916, 0, 0.746032, 0.468802, 0.000730353, 0, 0.777778, 0.451172, 0.000684955, 0, 0.809524, 0.434348, 0.000647565, 0, 0.84127, 0.417445, 0.000605863, 0, 0.873016, 0.401077, 0.000571885, 0, 0.904762, 0.385039, 0.000536034, 0, 0.936508, 0.369483, 0.000504227, 0, 0.968254, 0.354272, 0.000473165, 0, 1, 1, 0.000599525, 0, 0, 1, 0.000599533, 0, 0, 1, 0.000599639, 0, 0, 0.999998, 0.000600097, 0, 0, 0.999994, 0.000601336, 0, 0, 0.999987, 0.000603958, 0, 0, 0.999972, 0.000608775, 0, 0, 0.999949, 0.000616842, 0, 0, 0.999912, 0.000629534, 0, 0, 0.999857, 0.000648658, 0, 0, 0.999781, 0.000676615, 0, 5.38873e-06, 0.999674, 0.000716574, 0, 0.000308602, 0.999528, 0.000772641, 0, 0.00127003, 0.999326, 0.000849806, 0, 0.00300783, 0.999009, 0.000952682, 0, 0.00556637, 0.998112, 0.00106394, 0, 0.00895889, 0.994496, 0.00102228, 0, 0.0131827, 0.992806, 0.00108586, 0, 0.0182277, 0.991211, 0.0011759, 0, 0.0240795, 0.989415, 0.00128955, 0, 0.030723, 0.986499, 0.00139038, 0, 0.0381418, 0.982679, 0.00144539, 0, 0.046321, 0.978839, 0.00153954, 0, 0.0552459, 0.974295, 0.00164417, 0, 0.0649034, 0.968784, 0.00171517, 0, 0.0752814, 0.962324, 0.00180282, 0, 0.0863693, 0.954956, 0.00186387, 0, 0.0981578, 0.94624, 0.00193817, 0, 0.110639, 0.936517, 0.00198156, 0, 0.123806, 0.925186, 0.00203042, 0, 0.137655, 0.91252, 0.0020664, 0, 0.15218, 0.898441, 0.00207822, 0, 0.16738, 0.884394, 0.0020992, 0, 0.183253, 0.871273, 0.00208748, 0, 0.199799, 0.859057, 0.00208686, 0, 0.21702, 0.845243, 0.00205519, 0, 0.234918, 0.830723, 0.00202868, 0, 0.253496, 0.815801, 0.00199501, 0, 0.272761, 0.79914, 0.00194193, 0, 0.292719, 0.782372, 0.00188824, 0, 0.313377, 0.76482, 0.00183695, 0, 0.334745, 0.746586, 0.00177418, 0, 0.356833, 0.7281, 0.00170628, 0, 0.379654, 0.709842, 0.00164063, 0, 0.403221, 0.692019, 0.00157355, 0, 0.427548, 0.67364, 0.00150262, 0, 0.452651, 0.655277, 0.00143473, 0, 0.478545, 0.636438, 0.00136371, 0, 0.505246, 0.617364, 0.00129911, 0, 0.532768, 0.598603, 0.00123014, 0, 0.561122, 0.580195, 0.00116587, 0, 0.590309, 0.561786, 0.00110398, 0, 0.620318, 0.543377, 0.00104148, 0, 0.651102, 0.525093, 0.000983984, 0, 0.682545, 0.506791, 0.00092667, 0, 0.714286, 0.489291, 0.000874326, 0, 0.746032, 0.471811, 0.000821734, 0, 0.777778, 0.454435, 0.000774698, 0, 0.809524, 0.437493, 0.000727302, 0, 0.84127, 0.420977, 0.000684039, 0, 0.873016, 0.404729, 0.00064373, 0, 0.904762, 0.388756, 0.00060285, 0, 0.936508, 0.373344, 0.00056765, 0, 0.968254, 0.358191, 0.000531929, 0, 1, 1, 0.000832169, 0, 0, 1, 0.000832178, 0, 0, 1, 0.00083231, 0, 0, 0.999998, 0.000832893, 0, 0, 0.999995, 0.000834465, 0, 0, 0.999985, 0.000837791, 0, 0, 0.999969, 0.000843893, 0, 0, 0.999944, 0.000854086, 0, 0, 0.999903, 0.000870071, 0, 0, 0.999843, 0.000894042, 0, 0, 0.999759, 0.000928865, 0, 5.31805e-05, 0.999643, 0.000978242, 0, 0.000579365, 0.99948, 0.00104684, 0, 0.00182774, 0.999255, 0.00114012, 0, 0.00387804, 0.998885, 0.00126188, 0, 0.00675709, 0.997405, 0.00135888, 0, 0.010468, 0.99424, 0.00133626, 0, 0.0150018, 0.992458, 0.00140905, 0, 0.0203443, 0.990929, 0.00152305, 0, 0.0264786, 0.989116, 0.00165882, 0, 0.0333875, 0.985624, 0.00174128, 0, 0.0410536, 0.982003, 0.00182108, 0, 0.0494609, 0.978336, 0.00194498, 0, 0.0585941, 0.973184, 0.00202708, 0, 0.0684396, 0.9678, 0.00212166, 0, 0.0789851, 0.961348, 0.00221366, 0, 0.0902199, 0.953841, 0.00228219, 0, 0.102134, 0.94534, 0.00235662, 0, 0.114721, 0.935552, 0.00240572, 0, 0.127972, 0.924064, 0.00244405, 0, 0.141884, 0.911827, 0.00247557, 0, 0.156451, 0.897731, 0.00248374, 0, 0.171672, 0.883409, 0.00249863, 0, 0.187545, 0.868625, 0.00246688, 0, 0.20407, 0.856529, 0.00246523, 0, 0.221249, 0.842999, 0.00242368, 0, 0.239083, 0.828505, 0.00237354, 0, 0.257578, 0.813825, 0.00232588, 0, 0.276738, 0.797813, 0.00226731, 0, 0.296569, 0.781097, 0.00219704, 0, 0.31708, 0.764038, 0.00212394, 0, 0.338281, 0.746067, 0.00204786, 0, 0.360181, 0.727687, 0.00196728, 0, 0.382794, 0.709571, 0.00188779, 0, 0.406133, 0.691503, 0.00180532, 0, 0.430213, 0.673673, 0.00171849, 0, 0.45505, 0.655732, 0.00164147, 0, 0.480662, 0.637399, 0.00155858, 0, 0.507065, 0.618616, 0.00147641, 0, 0.534278, 0.60005, 0.00140125, 0, 0.562313, 0.581713, 0.00132441, 0, 0.59118, 0.563546, 0.00125014, 0, 0.620875, 0.545605, 0.00118249, 0, 0.651373, 0.527559, 0.0011116, 0, 0.682593, 0.509764, 0.00104979, 0, 0.714286, 0.49193, 0.000985977, 0, 0.746032, 0.475011, 0.000928592, 0, 0.777778, 0.457878, 0.000873466, 0, 0.809524, 0.440979, 0.000819585, 0, 0.84127, 0.424613, 0.000772365, 0, 0.873016, 0.408549, 0.000722195, 0, 0.904762, 0.392771, 0.000680014, 0, 0.936508, 0.377317, 0.000636797, 0, 0.968254, 0.362352, 0.000598318, 0, 1, 1, 0.00114313, 0, 0, 1, 0.00114314, 0, 0, 0.999999, 0.00114331, 0, 0, 0.999998, 0.00114404, 0, 0, 0.999994, 0.00114601, 0, 0, 0.999984, 0.00115019, 0, 0, 0.999967, 0.00115784, 0, 0, 0.999937, 0.0011706, 0, 0, 0.999894, 0.00119054, 0, 0, 0.999828, 0.00122031, 0, 0, 0.999735, 0.00126331, 0, 0.000169263, 0.999606, 0.00132382, 0, 0.000949167, 0.999426, 0.0014071, 0, 0.00249668, 0.999173, 0.00151895, 0, 0.00486392, 0.99873, 0.00166102, 0, 0.00806323, 0.996243, 0.0017023, 0, 0.0120895, 0.993779, 0.00172782, 0, 0.0169288, 0.9919, 0.0018108, 0, 0.0225633, 0.990524, 0.00196028, 0, 0.028974, 0.98868, 0.00212014, 0, 0.036142, 0.984663, 0.00217598, 0, 0.044049, 0.981457, 0.00230563, 0, 0.0526781, 0.977608, 0.00243966, 0, 0.0620137, 0.972215, 0.00251336, 0, 0.0720418, 0.966798, 0.0026285, 0, 0.0827499, 0.960241, 0.00271409, 0, 0.0941271, 0.952489, 0.00278381, 0, 0.106164, 0.944127, 0.00285399, 0, 0.118852, 0.934282, 0.00290994, 0, 0.132185, 0.923271, 0.00294558, 0, 0.146157, 0.910803, 0.00296269, 0, 0.160766, 0.896705, 0.00296803, 0, 0.176007, 0.88238, 0.00296637, 0, 0.19188, 0.867116, 0.00293163, 0, 0.208385, 0.853636, 0.00289418, 0, 0.225523, 0.840469, 0.00284663, 0, 0.243296, 0.82639, 0.00278594, 0, 0.261709, 0.811759, 0.00271618, 0, 0.280767, 0.796113, 0.00263187, 0, 0.300476, 0.779518, 0.00254589, 0, 0.320845, 0.763142, 0.00246003, 0, 0.341883, 0.745464, 0.00236529, 0, 0.363601, 0.727491, 0.00226536, 0, 0.386011, 0.709414, 0.00216375, 0, 0.409128, 0.691396, 0.00207127, 0, 0.432967, 0.67368, 0.00197106, 0, 0.457545, 0.656049, 0.00187022, 0, 0.482881, 0.638188, 0.00177605, 0, 0.508992, 0.620177, 0.00168482, 0, 0.535899, 0.601506, 0.00158909, 0, 0.563619, 0.58362, 0.00150583, 0, 0.592165, 0.565496, 0.00141791, 0, 0.621544, 0.54789, 0.00133693, 0, 0.651743, 0.530323, 0.00126038, 0, 0.682709, 0.512795, 0.00118556, 0, 0.714286, 0.495199, 0.00111527, 0, 0.746032, 0.478101, 0.0010489, 0, 0.777778, 0.461511, 0.000984264, 0, 0.809524, 0.444879, 0.00092591, 0, 0.84127, 0.428424, 0.000866582, 0, 0.873016, 0.412495, 0.000814463, 0, 0.904762, 0.396975, 0.000764498, 0, 0.936508, 0.381614, 0.000715967, 0, 0.968254, 0.366732, 0.000672483, 0, 1, 1, 0.00155501, 0, 0, 1, 0.00155503, 0, 0, 1, 0.00155524, 0, 0, 0.999998, 0.00155615, 0, 0, 0.999994, 0.0015586, 0, 0, 0.999983, 0.00156379, 0, 0, 0.999963, 0.0015733, 0, 0, 0.999932, 0.00158911, 0, 0, 0.999882, 0.00161376, 0, 0, 0.99981, 0.00165041, 0, 1.00875e-05, 0.999708, 0.00170304, 0, 0.000367658, 0.999565, 0.00177658, 0, 0.0014234, 0.999368, 0.00187688, 0, 0.00327939, 0.999081, 0.00200989, 0, 0.00596629, 0.99852, 0.00217177, 0, 0.0094852, 0.99549, 0.0021745, 0, 0.013824, 0.993252, 0.00222357, 0, 0.0189642, 0.991727, 0.00235022, 0, 0.0248856, 0.989951, 0.00250561, 0, 0.0315669, 0.988029, 0.00268829, 0, 0.0389882, 0.984029, 0.0027496, 0, 0.0471302, 0.980683, 0.00289793, 0, 0.0559754, 0.976554, 0.00303315, 0, 0.0655081, 0.97139, 0.00313257, 0, 0.0757138, 0.965544, 0.00323656, 0, 0.08658, 0.95912, 0.00333432, 0, 0.0980954, 0.951183, 0.0034039, 0, 0.110251, 0.942974, 0.00347515, 0, 0.123038, 0.932642, 0.00350381, 0, 0.13645, 0.922158, 0.00354519, 0, 0.150482, 0.909404, 0.00353851, 0, 0.165129, 0.896071, 0.0035435, 0, 0.18039, 0.881206, 0.00349936, 0, 0.196263, 0.866077, 0.00347256, 0, 0.212748, 0.85093, 0.003415, 0, 0.229847, 0.837703, 0.00333367, 0, 0.247561, 0.823878, 0.003249, 0, 0.265895, 0.809449, 0.00316347, 0, 0.284854, 0.794379, 0.00306351, 0, 0.304445, 0.778138, 0.0029499, 0, 0.324675, 0.761997, 0.00284099, 0, 0.345555, 0.744938, 0.00272104, 0, 0.367095, 0.727212, 0.00260715, 0, 0.389309, 0.709549, 0.00248855, 0, 0.41221, 0.691704, 0.00236783, 0, 0.435814, 0.673689, 0.00225178, 0, 0.460138, 0.656453, 0.00213765, 0, 0.485203, 0.639128, 0.00202178, 0, 0.511028, 0.621512, 0.00191443, 0, 0.537634, 0.603598, 0.00180977, 0, 0.565041, 0.58559, 0.00170456, 0, 0.593268, 0.567852, 0.00160927, 0, 0.622327, 0.5503, 0.00151395, 0, 0.652217, 0.533033, 0.00142499, 0, 0.682907, 0.515942, 0.00133955, 0, 0.714296, 0.498814, 0.0012602, 0, 0.746032, 0.481595, 0.00118188, 0, 0.777778, 0.465117, 0.00111171, 0, 0.809524, 0.448865, 0.00104091, 0, 0.84127, 0.432711, 0.000976618, 0, 0.873016, 0.416822, 0.00091859, 0, 0.904762, 0.401272, 0.000857704, 0, 0.936508, 0.386226, 0.000807172, 0, 0.968254, 0.371321, 0.00075464, 0, 1, 1, 0.00209596, 0, 0, 1, 0.00209598, 0, 0, 1, 0.00209624, 0, 0, 0.999997, 0.00209736, 0, 0, 0.999991, 0.00210039, 0, 0, 0.999979, 0.00210678, 0, 0, 0.999959, 0.00211847, 0, 0, 0.999925, 0.0021379, 0, 0, 0.99987, 0.00216809, 0, 0, 0.999791, 0.00221281, 0, 6.81487e-05, 0.999677, 0.00227669, 0, 0.000658161, 0.999521, 0.00236533, 0, 0.00200635, 0.999301, 0.00248514, 0, 0.0041779, 0.998977, 0.00264185, 0, 0.00718648, 0.998191, 0.00281695, 0, 0.0110239, 0.994801, 0.00278518, 0, 0.015672, 0.993091, 0.00288774, 0, 0.0211091, 0.991571, 0.00303931, 0, 0.0273123, 0.9897, 0.00321643, 0, 0.034259, 0.987023, 0.00337332, 0, 0.0419282, 0.983289, 0.00346146, 0, 0.0502998, 0.979892, 0.00363704, 0, 0.0593562, 0.975111, 0.00373601, 0, 0.069081, 0.970351, 0.0038842, 0, 0.0794598, 0.964131, 0.00397053, 0, 0.0904798, 0.957747, 0.00408078, 0, 0.10213, 0.949536, 0.00413533, 0, 0.1144, 0.941372, 0.00420305, 0, 0.127284, 0.931049, 0.00422815, 0, 0.140772, 0.920647, 0.00425048, 0, 0.154862, 0.908033, 0.0042281, 0, 0.169548, 0.895028, 0.00422026, 0, 0.184828, 0.879968, 0.00415042, 0, 0.200701, 0.864875, 0.00408821, 0, 0.217167, 0.84918, 0.00400909, 0, 0.234227, 0.834934, 0.00391178, 0, 0.251884, 0.821397, 0.00380066, 0, 0.270141, 0.807135, 0.00367974, 0, 0.289004, 0.792363, 0.00355172, 0, 0.308479, 0.776661, 0.003411, 0, 0.328575, 0.760705, 0.00328123, 0, 0.349301, 0.744408, 0.00314003, 0, 0.370668, 0.726994, 0.0029906, 0, 0.392689, 0.709598, 0.00285034, 0, 0.415379, 0.692112, 0.00271179, 0, 0.438754, 0.674435, 0.00257185, 0, 0.46283, 0.65676, 0.00243425, 0, 0.48763, 0.639982, 0.00230351, 0, 0.513173, 0.622983, 0.0021777, 0, 0.539482, 0.605471, 0.00204991, 0, 0.566579, 0.58796, 0.00193759, 0, 0.594488, 0.570463, 0.00181976, 0, 0.623226, 0.553058, 0.00171497, 0, 0.6528, 0.535894, 0.00161109, 0, 0.683198, 0.519089, 0.00151394, 0, 0.714354, 0.502454, 0.00142122, 0, 0.746032, 0.485681, 0.00133488, 0, 0.777778, 0.468935, 0.00124975, 0, 0.809524, 0.452951, 0.00117309, 0, 0.84127, 0.437139, 0.00110155, 0, 0.873016, 0.421446, 0.00103124, 0, 0.904762, 0.405951, 0.000966387, 0, 0.936508, 0.391003, 0.000908119, 0, 0.968254, 0.376198, 0.000848057, 0, 1, 1, 0.00280076, 0, 0, 1, 0.00280078, 0, 0, 0.999999, 0.00280109, 0, 0, 0.999997, 0.00280246, 0, 0, 0.999992, 0.00280616, 0, 0, 0.999979, 0.00281396, 0, 0, 0.999956, 0.00282822, 0, 0, 0.999916, 0.00285186, 0, 0, 0.999857, 0.0028885, 0, 0, 0.999768, 0.00294259, 0, 0.000196026, 0.999645, 0.00301946, 0, 0.00104842, 0.99947, 0.00312541, 0, 0.00270199, 0.999229, 0.00326733, 0, 0.00519449, 0.998852, 0.00344992, 0, 0.00852602, 0.997558, 0.00361052, 0, 0.0126804, 0.994417, 0.0035898, 0, 0.017635, 0.992824, 0.00372393, 0, 0.023365, 0.991344, 0.00390695, 0, 0.0298456, 0.989337, 0.00410392, 0, 0.0370529, 0.985811, 0.00420987, 0, 0.0449651, 0.982772, 0.00437488, 0, 0.0535615, 0.979001, 0.00455069, 0, 0.0628243, 0.974102, 0.00464462, 0, 0.0727368, 0.969197, 0.00480577, 0, 0.0832844, 0.962759, 0.00487818, 0, 0.0944545, 0.956207, 0.00498176, 0, 0.106236, 0.947909, 0.00503392, 0, 0.118619, 0.939596, 0.00507474, 0, 0.131595, 0.929642, 0.00509798, 0, 0.145159, 0.918807, 0.00508476, 0, 0.159305, 0.906921, 0.00505634, 0, 0.174028, 0.893312, 0.00498845, 0, 0.189327, 0.878933, 0.0049133, 0, 0.2052, 0.863986, 0.0048259, 0, 0.221647, 0.847936, 0.00470848, 0, 0.23867, 0.832253, 0.00456889, 0, 0.25627, 0.818619, 0.00442726, 0, 0.274453, 0.804788, 0.00427677, 0, 0.293222, 0.790241, 0.00411906, 0, 0.312585, 0.775162, 0.00394833, 0, 0.33255, 0.759463, 0.00377366, 0, 0.353126, 0.743598, 0.00361026, 0, 0.374324, 0.72697, 0.00343627, 0, 0.396158, 0.709646, 0.00326422, 0, 0.418641, 0.69277, 0.00309717, 0, 0.44179, 0.675371, 0.0029356, 0, 0.465624, 0.657863, 0.00277712, 0, 0.490163, 0.640772, 0.00261738, 0, 0.515429, 0.624441, 0.0024737, 0, 0.541445, 0.607497, 0.00233125, 0, 0.568236, 0.590438, 0.00218994, 0, 0.595828, 0.573224, 0.0020664, 0, 0.624242, 0.556168, 0.00193526, 0, 0.653496, 0.539232, 0.00182463, 0, 0.683588, 0.522352, 0.00170735, 0, 0.714482, 0.506172, 0.00160555, 0, 0.746032, 0.489842, 0.00150451, 0, 0.777778, 0.473463, 0.00140938, 0, 0.809524, 0.457266, 0.00132568, 0, 0.84127, 0.441609, 0.0012376, 0, 0.873016, 0.426348, 0.00116265, 0, 0.904762, 0.411002, 0.00108935, 0, 0.936508, 0.396045, 0.00101946, 0, 0.968254, 0.381448, 0.000955665, 0, 1, 1, 0.0037121, 0, 0, 1, 0.00371213, 0, 0, 1, 0.00371251, 0, 0, 0.999997, 0.00371417, 0, 0, 0.99999, 0.00371863, 0, 0, 0.999977, 0.00372807, 0, 0, 0.99995, 0.00374529, 0, 0, 0.999908, 0.0037738, 0, 0, 0.999843, 0.00381789, 0, 1.23596e-05, 0.999745, 0.00388273, 0, 0.000407442, 0.999608, 0.00397443, 0, 0.0015447, 0.999415, 0.00409998, 0, 0.00351385, 0.999143, 0.00426662, 0, 0.0063316, 0.9987, 0.00447625, 0, 0.00998679, 0.996363, 0.00455323, 0, 0.0144569, 0.994021, 0.00461052, 0, 0.0197151, 0.992372, 0.00476359, 0, 0.0257344, 0.991007, 0.00499101, 0, 0.0324882, 0.988767, 0.0051972, 0, 0.0399517, 0.984872, 0.00528407, 0, 0.0481022, 0.982004, 0.00548926, 0, 0.0569191, 0.977714, 0.00564385, 0, 0.0663839, 0.973076, 0.0057693, 0, 0.0764801, 0.967565, 0.0058924, 0, 0.0871928, 0.961384, 0.00599629, 0, 0.0985095, 0.954435, 0.00605998, 0, 0.110419, 0.946303, 0.0061133, 0, 0.122912, 0.937662, 0.00612028, 0, 0.13598, 0.927867, 0.00612209, 0, 0.149617, 0.916475, 0.00604813, 0, 0.163817, 0.90541, 0.00603088, 0, 0.178577, 0.891591, 0.00592218, 0, 0.193894, 0.877573, 0.00578854, 0, 0.209767, 0.862511, 0.00566648, 0, 0.226196, 0.846861, 0.00551481, 0, 0.243182, 0.83068, 0.00533754, 0, 0.260728, 0.815725, 0.00515487, 0, 0.278837, 0.802321, 0.0049655, 0, 0.297515, 0.787826, 0.00475421, 0, 0.316768, 0.773454, 0.00456002, 0, 0.336605, 0.758224, 0.00434727, 0, 0.357034, 0.74265, 0.00414444, 0, 0.378067, 0.726729, 0.00393738, 0, 0.399717, 0.710155, 0.00373575, 0, 0.421998, 0.693312, 0.00353736, 0, 0.444928, 0.67653, 0.00334368, 0, 0.468523, 0.659444, 0.00315981, 0, 0.492806, 0.642051, 0.00297809, 0, 0.517798, 0.625758, 0.00280592, 0, 0.543525, 0.609615, 0.00264254, 0, 0.570012, 0.592919, 0.00248459, 0, 0.597288, 0.576298, 0.00233327, 0, 0.625379, 0.559489, 0.00219519, 0, 0.654307, 0.542891, 0.00205441, 0, 0.684084, 0.526255, 0.00193385, 0, 0.714693, 0.509853, 0.00180745, 0, 0.746044, 0.494131, 0.00169817, 0, 0.777778, 0.478114, 0.0015913, 0, 0.809524, 0.462274, 0.00148981, 0, 0.84127, 0.446412, 0.00139537, 0, 0.873016, 0.431274, 0.00130984, 0, 0.904762, 0.41635, 0.00122403, 0, 0.936508, 0.401476, 0.00114809, 0, 0.968254, 0.386993, 0.00107563, 0, 1, 1, 0.00488216, 0, 0, 1, 0.0048822, 0, 0, 1, 0.00488265, 0, 0, 0.999997, 0.00488463, 0, 0, 0.999988, 0.00488999, 0, 0, 0.999974, 0.00490129, 0, 0, 0.999946, 0.00492191, 0, 0, 0.999897, 0.00495598, 0, 0, 0.999825, 0.00500855, 0, 7.44791e-05, 0.999718, 0.00508559, 0, 0.000712744, 0.999565, 0.005194, 0, 0.00215249, 0.999352, 0.00534147, 0, 0.00444576, 0.999046, 0.00553523, 0, 0.00759218, 0.998492, 0.00577016, 0, 0.0115714, 0.995564, 0.00578487, 0, 0.0163557, 0.993339, 0.00586414, 0, 0.021915, 0.991834, 0.00606002, 0, 0.0282201, 0.990496, 0.00633312, 0, 0.0352433, 0.987826, 0.00651941, 0, 0.042959, 0.98383, 0.00660842, 0, 0.0513439, 0.98109, 0.00685523, 0, 0.0603772, 0.976131, 0.00695778, 0, 0.0700402, 0.971922, 0.00714236, 0, 0.0803163, 0.965901, 0.00721437, 0, 0.0911908, 0.959606, 0.00732017, 0, 0.102651, 0.952504, 0.00735788, 0, 0.114686, 0.944365, 0.00738493, 0, 0.127286, 0.935652, 0.00737969, 0, 0.140443, 0.925813, 0.00733612, 0, 0.154151, 0.914397, 0.00723094, 0, 0.168405, 0.903257, 0.00714002, 0, 0.183201, 0.890015, 0.00700149, 0, 0.198536, 0.876014, 0.00682813, 0, 0.214409, 0.861436, 0.00665567, 0, 0.23082, 0.845752, 0.00644526, 0, 0.24777, 0.829169, 0.00621635, 0, 0.265263, 0.813435, 0.00597789, 0, 0.283301, 0.799701, 0.00575694, 0, 0.301889, 0.785726, 0.00549866, 0, 0.321035, 0.77152, 0.0052503, 0, 0.340746, 0.75683, 0.00499619, 0, 0.361032, 0.741951, 0.0047543, 0, 0.381904, 0.726367, 0.0045084, 0, 0.403374, 0.710537, 0.00426784, 0, 0.425457, 0.693965, 0.00403487, 0, 0.448169, 0.677724, 0.0038075, 0, 0.47153, 0.66117, 0.00359431, 0, 0.495561, 0.644274, 0.00338354, 0, 0.520284, 0.627449, 0.00318163, 0, 0.545725, 0.611645, 0.00299672, 0, 0.571911, 0.595614, 0.00281016, 0, 0.598873, 0.579426, 0.00264252, 0, 0.62664, 0.563016, 0.00247509, 0, 0.655239, 0.546728, 0.00232647, 0, 0.684692, 0.530539, 0.00217803, 0, 0.714999, 0.514164, 0.00204216, 0, 0.746106, 0.498344, 0.00191403, 0, 0.777778, 0.482957, 0.00179203, 0, 0.809524, 0.467336, 0.00167695, 0, 0.84127, 0.451994, 0.00157567, 0, 0.873016, 0.436514, 0.00147113, 0, 0.904762, 0.42178, 0.00138034, 0, 0.936508, 0.407271, 0.00129219, 0, 0.968254, 0.392822, 0.0012098, 0, 1, 1, 0.00637427, 0, 0, 1, 0.00637431, 0, 0, 0.999999, 0.00637485, 0, 0, 0.999996, 0.00637721, 0, 0, 0.999987, 0.00638357, 0, 0, 0.999971, 0.006397, 0, 0, 0.999939, 0.00642142, 0, 0, 0.999888, 0.00646177, 0, 0, 0.999807, 0.00652387, 0, 0.000207916, 0.999689, 0.00661454, 0, 0.00112051, 0.99952, 0.00674155, 0, 0.00287719, 0.999283, 0.00691313, 0, 0.00550145, 0.998936, 0.00713598, 0, 0.00897928, 0.998165, 0.00738501, 0, 0.0132829, 0.994847, 0.00734388, 0, 0.01838, 0.993182, 0.00749991, 0, 0.0242381, 0.991665, 0.0077246, 0, 0.030826, 0.989708, 0.00797579, 0, 0.0381152, 0.986663, 0.00813011, 0, 0.0460794, 0.983288, 0.00830365, 0, 0.0546951, 0.980104, 0.00853496, 0, 0.0639411, 0.974855, 0.00861045, 0, 0.0737988, 0.97045, 0.00879133, 0, 0.0842516, 0.964509, 0.00886377, 0, 0.0952848, 0.957594, 0.00890346, 0, 0.106886, 0.950546, 0.00893289, 0, 0.119044, 0.942225, 0.00890074, 0, 0.131749, 0.933365, 0.00886826, 0, 0.144994, 0.923202, 0.0087316, 0, 0.158772, 0.912605, 0.00863082, 0, 0.173078, 0.901099, 0.00847403, 0, 0.187908, 0.888177, 0.00825838, 0, 0.203261, 0.873955, 0.00801834, 0, 0.219134, 0.860091, 0.00779026, 0, 0.235527, 0.84434, 0.00752478, 0, 0.252443, 0.828517, 0.00724074, 0, 0.269883, 0.81239, 0.00693769, 0, 0.287851, 0.79721, 0.00664817, 0, 0.306352, 0.783489, 0.00634763, 0, 0.325393, 0.769514, 0.00604221, 0, 0.344981, 0.755419, 0.00573568, 0, 0.365126, 0.741083, 0.00544359, 0, 0.385839, 0.726059, 0.00515515, 0, 0.407132, 0.710809, 0.00487139, 0, 0.42902, 0.695052, 0.00459846, 0, 0.45152, 0.678886, 0.00433412, 0, 0.474651, 0.663042, 0.00407981, 0, 0.498433, 0.646634, 0.00384264, 0, 0.52289, 0.630117, 0.00360897, 0, 0.548048, 0.613804, 0.00338863, 0, 0.573936, 0.598338, 0.00318486, 0, 0.600584, 0.582687, 0.00298377, 0, 0.628027, 0.566809, 0.00280082, 0, 0.656295, 0.550817, 0.00262255, 0, 0.685417, 0.534937, 0.00245835, 0, 0.715406, 0.519151, 0.00230574, 0, 0.74624, 0.503118, 0.0021549, 0, 0.777778, 0.487723, 0.00202008, 0, 0.809524, 0.472725, 0.00189355, 0, 0.84127, 0.457599, 0.00177108, 0, 0.873016, 0.442558, 0.00165843, 0, 0.904762, 0.427624, 0.00155494, 0, 0.936508, 0.413171, 0.00145273, 0, 0.968254, 0.399122, 0.00136454, 0, 1, 1, 0.00826496, 0, 0, 1, 0.00826499, 0, 0, 1, 0.00826564, 0, 0, 0.999996, 0.00826842, 0, 0, 0.999987, 0.00827589, 0, 0, 0.999967, 0.00829167, 0, 0, 0.999933, 0.00832037, 0, 0, 0.999876, 0.00836768, 0, 1.09338e-05, 0.999786, 0.00844031, 0, 0.000427145, 0.999655, 0.00854603, 0, 0.0016384, 0.999468, 0.00869337, 0, 0.00372392, 0.999203, 0.008891, 0, 0.00668513, 0.998803, 0.00914387, 0, 0.0104968, 0.99748, 0.00935838, 0, 0.015125, 0.994446, 0.00933309, 0, 0.0205338, 0.99292, 0.00953084, 0, 0.0266884, 0.991414, 0.0097893, 0, 0.0335565, 0.989049, 0.0100228, 0, 0.0411086, 0.98582, 0.0101664, 0, 0.0493181, 0.982441, 0.0103582, 0, 0.0581613, 0.978595, 0.0105292, 0, 0.0676169, 0.973495, 0.0106274, 0, 0.0776661, 0.968405, 0.0107261, 0, 0.0882926, 0.962717, 0.0108234, 0, 0.0994817, 0.955478, 0.0108102, 0, 0.111221, 0.948275, 0.0107914, 0, 0.123499, 0.940006, 0.0107161, 0, 0.136308, 0.930831, 0.0106309, 0, 0.149639, 0.920648, 0.0104083, 0, 0.163485, 0.910205, 0.0102312, 0, 0.177843, 0.898445, 0.0100051, 0, 0.192707, 0.885986, 0.00971928, 0, 0.208077, 0.872204, 0.00940747, 0, 0.22395, 0.858436, 0.0091085, 0, 0.240326, 0.843454, 0.00876595, 0, 0.257208, 0.827437, 0.00839794, 0, 0.274596, 0.811488, 0.00803692, 0, 0.292496, 0.796039, 0.00767352, 0, 0.310911, 0.781083, 0.0073097, 0, 0.329849, 0.767642, 0.00694032, 0, 0.349316, 0.753901, 0.00657476, 0, 0.369323, 0.740131, 0.00622699, 0, 0.38988, 0.725845, 0.0058838, 0, 0.410999, 0.710991, 0.00555586, 0, 0.432696, 0.696002, 0.00523089, 0, 0.454987, 0.680461, 0.00492494, 0, 0.47789, 0.664875, 0.00463464, 0, 0.501426, 0.649273, 0.00435422, 0, 0.52562, 0.63302, 0.0040875, 0, 0.550498, 0.61705, 0.00384075, 0, 0.576089, 0.601154, 0.00359557, 0, 0.602427, 0.586008, 0.00337636, 0, 0.629544, 0.570699, 0.00316019, 0, 0.657479, 0.555166, 0.00296033, 0, 0.686264, 0.539645, 0.00277552, 0, 0.715924, 0.524159, 0.00259499, 0, 0.746459, 0.508682, 0.00243257, 0, 0.777789, 0.493163, 0.00227851, 0, 0.809524, 0.478004, 0.00213083, 0, 0.84127, 0.46347, 0.00199502, 0, 0.873016, 0.448778, 0.00186967, 0, 0.904762, 0.434105, 0.00174732, 0, 0.936508, 0.419576, 0.00163861, 0, 0.968254, 0.405541, 0.00153341, 0, 1, 1, 0.0106462, 0, 0, 1, 0.0106462, 0, 0, 0.999999, 0.010647, 0, 0, 0.999995, 0.0106502, 0, 0, 0.999985, 0.0106589, 0, 0, 0.999964, 0.0106773, 0, 0, 0.999925, 0.0107106, 0, 0, 0.999861, 0.0107655, 0, 7.12986e-05, 0.999763, 0.0108497, 0, 0.000743959, 0.999616, 0.0109716, 0, 0.00227361, 0.999408, 0.0111408, 0, 0.0046983, 0.999112, 0.0113659, 0, 0.00800158, 0.998637, 0.0116475, 0, 0.0121493, 0.996223, 0.0117231, 0, 0.0171023, 0.994006, 0.0118064, 0, 0.0228218, 0.992444, 0.0120254, 0, 0.0292711, 0.991028, 0.0123314, 0, 0.036417, 0.98803, 0.0124954, 0, 0.0442295, 0.984816, 0.0126538, 0, 0.0526815, 0.981399, 0.0128537, 0, 0.0617492, 0.977085, 0.0129694, 0, 0.0714114, 0.972154, 0.013091, 0, 0.0816495, 0.966617, 0.0131166, 0, 0.0924472, 0.960628, 0.0131583, 0, 0.10379, 0.953295, 0.0131094, 0, 0.115665, 0.94575, 0.0129966, 0, 0.128062, 0.937654, 0.0128796, 0, 0.140972, 0.927716, 0.0126477, 0, 0.154387, 0.917932, 0.0123889, 0, 0.168301, 0.907719, 0.012131, 0, 0.182709, 0.89584, 0.0118013, 0, 0.197608, 0.883526, 0.0114145, 0, 0.212994, 0.870301, 0.0110075, 0, 0.228867, 0.856272, 0.0106019, 0, 0.245227, 0.842251, 0.0101938, 0, 0.262074, 0.826466, 0.00973254, 0, 0.279412, 0.810859, 0.0092846, 0, 0.297244, 0.795051, 0.00883304, 0, 0.315575, 0.780053, 0.00840272, 0, 0.334412, 0.76575, 0.00796438, 0, 0.35376, 0.752298, 0.00752526, 0, 0.373631, 0.739153, 0.00711486, 0, 0.394034, 0.725514, 0.00670361, 0, 0.414983, 0.711473, 0.00632656, 0, 0.436491, 0.696936, 0.00595206, 0, 0.458575, 0.682126, 0.00559191, 0, 0.481253, 0.667027, 0.00525362, 0, 0.504547, 0.651875, 0.00493805, 0, 0.528481, 0.636463, 0.00462848, 0, 0.553081, 0.620641, 0.00433936, 0, 0.578377, 0.604931, 0.00407, 0, 0.604404, 0.589549, 0.00380864, 0, 0.631197, 0.574712, 0.00357049, 0, 0.658795, 0.559775, 0.00334466, 0, 0.687238, 0.544514, 0.00312505, 0, 0.716559, 0.529555, 0.00293199, 0, 0.746776, 0.514402, 0.00274204, 0, 0.777849, 0.499302, 0.00256647, 0, 0.809524, 0.484114, 0.00239901, 0, 0.84127, 0.469308, 0.00225148, 0, 0.873016, 0.455133, 0.00210178, 0, 0.904762, 0.440939, 0.0019727, 0, 0.936508, 0.426627, 0.00184382, 0, 0.968254, 0.412509, 0.00172548, 0, 1, 1, 0.013628, 0, 0, 1, 0.0136281, 0, 0, 0.999999, 0.0136289, 0, 0, 0.999995, 0.0136327, 0, 0, 0.999983, 0.0136427, 0, 0, 0.99996, 0.0136638, 0, 0, 0.999917, 0.0137022, 0, 0, 0.999846, 0.0137652, 0, 0.000204597, 0.999736, 0.0138615, 0, 0.00116837, 0.999573, 0.0140007, 0, 0.00303325, 0.99934, 0.0141927, 0, 0.00580613, 0.999004, 0.0144457, 0, 0.00945626, 0.998407, 0.0147489, 0, 0.0139421, 0.995464, 0.014731, 0, 0.0192202, 0.993328, 0.0148283, 0, 0.0252495, 0.991799, 0.0150797, 0, 0.0319921, 0.990397, 0.0154316, 0, 0.0394138, 0.986835, 0.0155005, 0, 0.0474843, 0.983938, 0.0157308, 0, 0.0561763, 0.980154, 0.0158753, 0, 0.0654661, 0.975659, 0.0159581, 0, 0.0753326, 0.970171, 0.0159832, 0, 0.0857571, 0.964803, 0.0160084, 0, 0.0967236, 0.958366, 0.0159484, 0, 0.108218, 0.950613, 0.0158001, 0, 0.120227, 0.942874, 0.0155845, 0, 0.132741, 0.935005, 0.0154292, 0, 0.145751, 0.924991, 0.0150742, 0, 0.159249, 0.914814, 0.0146757, 0, 0.17323, 0.904743, 0.0143097, 0, 0.187687, 0.893216, 0.0138695, 0, 0.202619, 0.880769, 0.0133706, 0, 0.218021, 0.868136, 0.0128606, 0, 0.233894, 0.85469, 0.0123403, 0, 0.250238, 0.840593, 0.0118091, 0, 0.267052, 0.825808, 0.011253, 0, 0.284341, 0.81009, 0.0107099, 0, 0.302106, 0.79504, 0.0101636, 0, 0.320354, 0.779757, 0.00964041, 0, 0.33909, 0.764697, 0.00911896, 0, 0.358322, 0.750913, 0.00859533, 0, 0.378059, 0.738175, 0.00811592, 0, 0.398311, 0.725242, 0.00764504, 0, 0.41909, 0.711864, 0.00718885, 0, 0.440412, 0.698009, 0.00675843, 0, 0.462292, 0.683841, 0.00634984, 0, 0.484748, 0.669391, 0.00595502, 0, 0.507802, 0.654731, 0.00558671, 0, 0.531477, 0.639805, 0.00523578, 0, 0.555802, 0.624789, 0.00490834, 0, 0.580805, 0.609325, 0.00459448, 0, 0.606522, 0.593975, 0.00430342, 0, 0.63299, 0.578983, 0.00403019, 0, 0.66025, 0.564442, 0.0037707, 0, 0.688346, 0.549835, 0.0035316, 0, 0.717319, 0.535039, 0.00330255, 0, 0.7472, 0.520403, 0.00308932, 0, 0.777982, 0.505687, 0.00289335, 0, 0.809524, 0.490939, 0.00270818, 0, 0.84127, 0.476233, 0.0025343, 0, 0.873016, 0.461624, 0.00237097, 0, 0.904762, 0.447833, 0.00222065, 0, 0.936508, 0.433992, 0.00207561, 0, 0.968254, 0.420147, 0.00194955, 0, 1, 1, 0.0173415, 0, 0, 1, 0.0173416, 0, 0, 0.999999, 0.0173426, 0, 0, 0.999995, 0.0173468, 0, 0, 0.999983, 0.0173582, 0, 0, 0.999954, 0.0173822, 0, 0, 0.999908, 0.0174258, 0, 6.69501e-06, 0.999828, 0.0174973, 0, 0.000427399, 0.999705, 0.0176063, 0, 0.00171019, 0.999524, 0.0177631, 0, 0.0039248, 0.999263, 0.0179781, 0, 0.00705382, 0.998878, 0.018258, 0, 0.0110552, 0.998012, 0.0185551, 0, 0.0158812, 0.994614, 0.0184264, 0, 0.0214852, 0.993132, 0.0186385, 0, 0.0278239, 0.991563, 0.0189067, 0, 0.0348585, 0.989298, 0.0191577, 0, 0.0425544, 0.986036, 0.0192522, 0, 0.050881, 0.982558, 0.0194063, 0, 0.059811, 0.978531, 0.019486, 0, 0.0693209, 0.974198, 0.0195847, 0, 0.0793895, 0.968148, 0.0194749, 0, 0.0899984, 0.962565, 0.0194277, 0, 0.101132, 0.956041, 0.0192991, 0, 0.112775, 0.947749, 0.0189893, 0, 0.124917, 0.94018, 0.018704, 0, 0.137547, 0.93165, 0.0183458, 0, 0.150655, 0.921798, 0.0178775, 0, 0.164236, 0.911573, 0.0173618, 0, 0.178281, 0.901569, 0.0168482, 0, 0.192788, 0.890341, 0.016265, 0, 0.207752, 0.877835, 0.0156199, 0, 0.223171, 0.865472, 0.0149516, 0, 0.239044, 0.852905, 0.0143274, 0, 0.255371, 0.838906, 0.0136643, 0, 0.272153, 0.824888, 0.0129903, 0, 0.289393, 0.809977, 0.0123218, 0, 0.307093, 0.794697, 0.0116572, 0, 0.325259, 0.780028, 0.0110307, 0, 0.343896, 0.765124, 0.0104236, 0, 0.363012, 0.750411, 0.0098219, 0, 0.382617, 0.737264, 0.00924397, 0, 0.402719, 0.724799, 0.00868719, 0, 0.423332, 0.712253, 0.00816476, 0, 0.444469, 0.699267, 0.00767262, 0, 0.466146, 0.685618, 0.00719746, 0, 0.488383, 0.671736, 0.00673916, 0, 0.511199, 0.657777, 0.00631937, 0, 0.534618, 0.643497, 0.00592411, 0, 0.558668, 0.62889, 0.00553928, 0, 0.58338, 0.614299, 0.0051934, 0, 0.608787, 0.599197, 0.00485985, 0, 0.634929, 0.584175, 0.00454357, 0, 0.661849, 0.569541, 0.00425787, 0, 0.689594, 0.555193, 0.00397905, 0, 0.718211, 0.540947, 0.00372364, 0, 0.747742, 0.526593, 0.00348599, 0, 0.778205, 0.512335, 0.00326103, 0, 0.80953, 0.498017, 0.00305137, 0, 0.84127, 0.483609, 0.00285485, 0, 0.873016, 0.469368, 0.00267472, 0, 0.904762, 0.455037, 0.00249945, 0, 0.936508, 0.441493, 0.00234792, 0, 0.968254, 0.428147, 0.00219936, 0, 1, 1, 0.0219422, 0, 0, 1, 0.0219423, 0, 0, 0.999998, 0.0219434, 0, 0, 0.999993, 0.0219481, 0, 0, 0.999981, 0.021961, 0, 0, 0.999949, 0.0219879, 0, 0, 0.999896, 0.0220367, 0, 5.93194e-05, 0.999808, 0.0221167, 0, 0.00075364, 0.99967, 0.0222383, 0, 0.00237884, 0.999466, 0.0224125, 0, 0.00495612, 0.999174, 0.0226495, 0, 0.00844887, 0.998725, 0.0229525, 0, 0.0128058, 0.996979, 0.0231123, 0, 0.0179742, 0.994317, 0.0230742, 0, 0.0239047, 0.992781, 0.0232895, 0, 0.0305526, 0.991191, 0.0235734, 0, 0.0378786, 0.987787, 0.0236152, 0, 0.0458475, 0.985092, 0.0237994, 0, 0.0544287, 0.981121, 0.0238553, 0, 0.0635952, 0.976924, 0.0238706, 0, 0.0733233, 0.97218, 0.0238704, 0, 0.0835922, 0.965956, 0.0236598, 0, 0.0943839, 0.959998, 0.0234735, 0, 0.105682, 0.953245, 0.0232277, 0, 0.117474, 0.944445, 0.0226973, 0, 0.129747, 0.937087, 0.0223527, 0, 0.142491, 0.928341, 0.0218144, 0, 0.155697, 0.9184, 0.0211516, 0, 0.169358, 0.907959, 0.0204553, 0, 0.183469, 0.89808, 0.0197673, 0, 0.198024, 0.887047, 0.0189915, 0, 0.21302, 0.875221, 0.0182082, 0, 0.228455, 0.86269, 0.0173584, 0, 0.244329, 0.850735, 0.0165718, 0, 0.260639, 0.837545, 0.0157524, 0, 0.277389, 0.823639, 0.0149482, 0, 0.29458, 0.809699, 0.0141431, 0, 0.312216, 0.794797, 0.0133527, 0, 0.3303, 0.780578, 0.0126193, 0, 0.34884, 0.766019, 0.0118914, 0, 0.367842, 0.751447, 0.0111839, 0, 0.387315, 0.737275, 0.010514, 0, 0.40727, 0.724545, 0.00987277, 0, 0.427717, 0.712644, 0.00926569, 0, 0.448671, 0.700432, 0.00869029, 0, 0.470149, 0.687664, 0.00814691, 0, 0.492167, 0.674288, 0.00763012, 0, 0.514746, 0.660966, 0.00714437, 0, 0.537911, 0.647264, 0.00668457, 0, 0.561688, 0.633431, 0.00626581, 0, 0.586108, 0.619133, 0.00585593, 0, 0.611206, 0.604935, 0.00548188, 0, 0.637022, 0.590236, 0.00513288, 0, 0.663599, 0.575473, 0.0047906, 0, 0.690989, 0.561228, 0.00448895, 0, 0.719242, 0.547054, 0.00420233, 0, 0.748411, 0.533175, 0.00392869, 0, 0.778531, 0.519163, 0.00367445, 0, 0.809583, 0.505328, 0.00344097, 0, 0.84127, 0.491446, 0.00322003, 0, 0.873016, 0.477356, 0.00301283, 0, 0.904762, 0.46356, 0.00282592, 0, 0.936508, 0.449623, 0.00264956, 0, 0.968254, 0.436068, 0.00246956, 0, 1, 1, 0.0276135, 0, 0, 1, 0.0276136, 0, 0, 0.999998, 0.0276148, 0, 0, 0.999993, 0.0276201, 0, 0, 0.999976, 0.0276342, 0, 0, 0.999945, 0.027664, 0, 0, 0.999884, 0.0277179, 0, 0.00018679, 0.999784, 0.027806, 0, 0.00119607, 0.99963, 0.0279394, 0, 0.00318407, 0.999401, 0.0281295, 0, 0.00613601, 0.999066, 0.0283858, 0, 0.00999963, 0.998524, 0.0287027, 0, 0.0147164, 0.995702, 0.0286256, 0, 0.0202295, 0.993593, 0.0286733, 0, 0.0264876, 0.992067, 0.0288989, 0, 0.0334452, 0.990548, 0.0292135, 0, 0.0410621, 0.986775, 0.0291296, 0, 0.0493032, 0.984054, 0.0293099, 0, 0.0581381, 0.979481, 0.0291881, 0, 0.0675397, 0.975297, 0.0291598, 0, 0.0774848, 0.96981, 0.028954, 0, 0.0879528, 0.963524, 0.028628, 0, 0.0989258, 0.957398, 0.0283135, 0, 0.110388, 0.950088, 0.0278469, 0, 0.122327, 0.941538, 0.0271798, 0, 0.134729, 0.933332, 0.0265388, 0, 0.147587, 0.924392, 0.0257776, 0, 0.160889, 0.914581, 0.024916, 0, 0.174631, 0.904347, 0.0240242, 0, 0.188806, 0.894324, 0.0231229, 0, 0.203409, 0.883724, 0.022153, 0, 0.218437, 0.872207, 0.0211355, 0, 0.233888, 0.859927, 0.0201048, 0, 0.249761, 0.848373, 0.0191263, 0, 0.266056, 0.836023, 0.0181306, 0, 0.282774, 0.82289, 0.0171718, 0, 0.299917, 0.809324, 0.0162196, 0, 0.317488, 0.795361, 0.0152622, 0, 0.335493, 0.781253, 0.01439, 0, 0.353936, 0.767338, 0.013533, 0, 0.372825, 0.753156, 0.0127244, 0, 0.392168, 0.739122, 0.0119454, 0, 0.411976, 0.725358, 0.0112054, 0, 0.432259, 0.712949, 0.010487, 0, 0.453032, 0.701621, 0.00984032, 0, 0.47431, 0.689703, 0.00921495, 0, 0.496111, 0.677216, 0.00862492, 0, 0.518456, 0.664217, 0.00806882, 0, 0.541367, 0.65137, 0.00755922, 0, 0.564872, 0.638, 0.00705705, 0, 0.589001, 0.62453, 0.00661266, 0, 0.613789, 0.610601, 0.00618432, 0, 0.639277, 0.59676, 0.00578033, 0, 0.66551, 0.582433, 0.00540927, 0, 0.692539, 0.568026, 0.00506104, 0, 0.720422, 0.55414, 0.0047353, 0, 0.749216, 0.540178, 0.00442889, 0, 0.778974, 0.526513, 0.00414363, 0, 0.809711, 0.512954, 0.00388237, 0, 0.84127, 0.499403, 0.00362875, 0, 0.873016, 0.486026, 0.00340827, 0, 0.904762, 0.472345, 0.00318598, 0, 0.936508, 0.458828, 0.00297635, 0, 0.968254, 0.445379, 0.00279447, 0, 1, 1, 0.0345716, 0, 0, 1, 0.0345717, 0, 0, 0.999999, 0.034573, 0, 0, 0.999991, 0.0345787, 0, 0, 0.999974, 0.0345941, 0, 0, 0.999937, 0.0346263, 0, 1.88589e-06, 0.999869, 0.0346847, 0, 0.000409238, 0.999757, 0.0347798, 0, 0.0017674, 0.999582, 0.0349233, 0, 0.00413658, 0.999322, 0.0351265, 0, 0.00747408, 0.998939, 0.0353967, 0, 0.0117157, 0.998219, 0.0357018, 0, 0.0167966, 0.994974, 0.0354726, 0, 0.0226572, 0.993201, 0.0355621, 0, 0.0292445, 0.991573, 0.0357641, 0, 0.0365123, 0.989301, 0.0359252, 0, 0.0444203, 0.985712, 0.0358017, 0, 0.0529334, 0.982411, 0.0358353, 0, 0.0620214, 0.977827, 0.035617, 0, 0.0716574, 0.973278, 0.0354398, 0, 0.0818186, 0.967397, 0.0350483, 0, 0.0924846, 0.960696, 0.0344795, 0, 0.103638, 0.954349, 0.0339861, 0, 0.115263, 0.946066, 0.0331323, 0, 0.127348, 0.938012, 0.032359, 0, 0.13988, 0.929413, 0.0314413, 0, 0.152849, 0.920355, 0.0304103, 0, 0.166248, 0.910586, 0.0292785, 0, 0.18007, 0.900609, 0.0281391, 0, 0.194308, 0.890093, 0.0269103, 0, 0.208958, 0.880013, 0.0257269, 0, 0.224018, 0.869001, 0.0244671, 0, 0.239485, 0.85751, 0.0232252, 0, 0.255359, 0.84582, 0.0220117, 0, 0.271638, 0.834383, 0.0208274, 0, 0.288324, 0.822158, 0.0196628, 0, 0.305419, 0.809056, 0.0185306, 0, 0.322927, 0.795832, 0.0174174, 0, 0.340851, 0.782547, 0.0163758, 0, 0.359199, 0.7689, 0.015391, 0, 0.377975, 0.755526, 0.0144488, 0, 0.397189, 0.741681, 0.0135372, 0, 0.416851, 0.728178, 0.0126957, 0, 0.436971, 0.714642, 0.0118812, 0, 0.457564, 0.702756, 0.0111165, 0, 0.478644, 0.69175, 0.0104145, 0, 0.500229, 0.680159, 0.00974439, 0, 0.522339, 0.668073, 0.00911926, 0, 0.544997, 0.655405, 0.00851393, 0, 0.56823, 0.642921, 0.00797637, 0, 0.592068, 0.629993, 0.00745119, 0, 0.616546, 0.616828, 0.00696972, 0, 0.641705, 0.603305, 0.00652425, 0, 0.66759, 0.589833, 0.00610188, 0, 0.694255, 0.575945, 0.00570834, 0, 0.72176, 0.561745, 0.00533384, 0, 0.750168, 0.548277, 0.00500001, 0, 0.779545, 0.534467, 0.00467582, 0, 0.809933, 0.521032, 0.00438092, 0, 0.841272, 0.507877, 0.00410348, 0, 0.873016, 0.494654, 0.00383618, 0, 0.904762, 0.481592, 0.00358699, 0, 0.936508, 0.468509, 0.00337281, 0, 0.968254, 0.455293, 0.00316196, 0, 1, 1, 0.0430698, 0, 0, 1, 0.0430699, 0, 0, 0.999998, 0.0430713, 0, 0, 0.999991, 0.0430773, 0, 0, 0.99997, 0.0430936, 0, 0, 0.999928, 0.0431277, 0, 4.06396e-05, 0.999852, 0.0431893, 0, 0.000744376, 0.999724, 0.0432895, 0, 0.0024806, 0.999527, 0.0434397, 0, 0.00524779, 0.99923, 0.0436507, 0, 0.00898164, 0.998783, 0.0439255, 0, 0.0136083, 0.997507, 0.0441104, 0, 0.0190582, 0.994418, 0.0438225, 0, 0.0252694, 0.992864, 0.0439396, 0, 0.0321879, 0.991127, 0.0440962, 0, 0.039767, 0.987331, 0.0438408, 0, 0.0479667, 0.984819, 0.0438991, 0, 0.056752, 0.980384, 0.0435906, 0, 0.0660929, 0.975846, 0.0432543, 0, 0.075963, 0.970748, 0.0428293, 0, 0.0863398, 0.964303, 0.042153, 0, 0.0972035, 0.95772, 0.0414111, 0, 0.108537, 0.950747, 0.0405893, 0, 0.120325, 0.942533, 0.0394887, 0, 0.132554, 0.934045, 0.0383544, 0, 0.145215, 0.924942, 0.037057, 0, 0.158296, 0.915811, 0.0356993, 0, 0.17179, 0.90612, 0.0342401, 0, 0.185691, 0.896434, 0.0328078, 0, 0.199993, 0.886021, 0.031288, 0, 0.214691, 0.876081, 0.0297776, 0, 0.229782, 0.865608, 0.0282334, 0, 0.245265, 0.854924, 0.026749, 0, 0.261138, 0.843607, 0.02526, 0, 0.277401, 0.832456, 0.0238214, 0, 0.294056, 0.821342, 0.0224682, 0, 0.311104, 0.809303, 0.0211297, 0, 0.328548, 0.796468, 0.0198387, 0, 0.346394, 0.784046, 0.0186227, 0, 0.364645, 0.771262, 0.0174561, 0, 0.38331, 0.758118, 0.0163806, 0, 0.402396, 0.745075, 0.0153287, 0, 0.421912, 0.731926, 0.0143647, 0, 0.44187, 0.71863, 0.0134363, 0, 0.462283, 0.705414, 0.0125603, 0, 0.483165, 0.693792, 0.0117508, 0, 0.504535, 0.683108, 0.0110016, 0, 0.52641, 0.67183, 0.0102757, 0, 0.548816, 0.66015, 0.00962044, 0, 0.571776, 0.647907, 0.00898031, 0, 0.595323, 0.635734, 0.00840811, 0, 0.619489, 0.623208, 0.00786211, 0, 0.644317, 0.610438, 0.00734953, 0, 0.669852, 0.597345, 0.00687688, 0, 0.696148, 0.584138, 0.00643469, 0, 0.723267, 0.5707, 0.00602236, 0, 0.75128, 0.556966, 0.0056324, 0, 0.780258, 0.543607, 0.00528277, 0, 0.810268, 0.530213, 0.00493999, 0, 0.841311, 0.516912, 0.00462265, 0, 0.873016, 0.503916, 0.0043307, 0, 0.904762, 0.491146, 0.00406858, 0, 0.936508, 0.478439, 0.00381436, 0, 0.968254, 0.465834, 0.00358003, 0, 1, 1, 0.0534039, 0, 0, 1, 0.053404, 0, 0, 0.999998, 0.0534055, 0, 0, 0.999989, 0.0534116, 0, 0, 0.999968, 0.0534283, 0, 0, 0.999918, 0.0534633, 0, 0.000155895, 0.99983, 0.0535262, 0, 0.00120914, 0.999685, 0.0536281, 0, 0.00334944, 0.999461, 0.0537799, 0, 0.00653077, 0.999119, 0.0539902, 0, 0.0106718, 0.998582, 0.0542524, 0, 0.0156907, 0.995919, 0.0540318, 0, 0.0215147, 0.993735, 0.0538914, 0, 0.0280801, 0.992126, 0.0539557, 0, 0.0353323, 0.990266, 0.0540401, 0, 0.0432247, 0.986317, 0.0536064, 0, 0.0517172, 0.983213, 0.0534425, 0, 0.0607754, 0.978303, 0.0528622, 0, 0.0703698, 0.973665, 0.0523363, 0, 0.0804742, 0.968091, 0.0516165, 0, 0.0910667, 0.961026, 0.0505434, 0, 0.102128, 0.954333, 0.049523, 0, 0.113641, 0.946372, 0.0481698, 0, 0.125591, 0.938254, 0.0467674, 0, 0.137965, 0.929516, 0.0452341, 0, 0.150754, 0.920106, 0.0435083, 0, 0.163947, 0.910899, 0.0417399, 0, 0.177537, 0.901532, 0.0399389, 0, 0.191516, 0.891919, 0.0380901, 0, 0.205881, 0.882006, 0.0362341, 0, 0.220626, 0.871965, 0.0343444, 0, 0.235749, 0.862145, 0.0324832, 0, 0.251248, 0.852058, 0.0306681, 0, 0.267121, 0.84161, 0.0289097, 0, 0.283368, 0.830806, 0.0272079, 0, 0.299992, 0.820476, 0.0256089, 0, 0.316992, 0.809514, 0.0240394, 0, 0.334374, 0.797865, 0.0225379, 0, 0.35214, 0.785621, 0.0211235, 0, 0.370296, 0.773765, 0.0197908, 0, 0.388849, 0.761629, 0.0185235, 0, 0.407807, 0.748891, 0.0173358, 0, 0.427178, 0.736437, 0.0162305, 0, 0.446974, 0.723707, 0.0151778, 0, 0.467207, 0.710606, 0.0141791, 0, 0.487892, 0.698019, 0.0132592, 0, 0.509046, 0.686203, 0.0123887, 0, 0.530687, 0.675692, 0.0115976, 0, 0.552839, 0.664826, 0.0108325, 0, 0.575527, 0.65349, 0.0101348, 0, 0.59878, 0.641774, 0.00947756, 0, 0.622634, 0.629794, 0.00886058, 0, 0.647128, 0.617647, 0.00828526, 0, 0.672308, 0.60534, 0.00775312, 0, 0.698231, 0.592718, 0.00726033, 0, 0.724958, 0.579746, 0.00679731, 0, 0.752563, 0.566763, 0.00636111, 0, 0.781127, 0.553515, 0.00595228, 0, 0.810733, 0.540118, 0.00556876, 0, 0.841426, 0.527325, 0.00523051, 0, 0.873016, 0.514265, 0.00490712, 0, 0.904762, 0.501406, 0.00460297, 0, 0.936508, 0.488922, 0.00431247, 0, 0.968254, 0.476541, 0.0040472, 0, 1, 1, 0.0659184, 0, 0, 1, 0.0659185, 0, 0, 0.999998, 0.06592, 0, 0, 0.999988, 0.0659259, 0, 0, 0.999963, 0.0659423, 0, 0, 0.999907, 0.0659764, 0, 0.000374198, 0.999806, 0.0660376, 0, 0.00182071, 0.999639, 0.0661361, 0, 0.0043894, 0.999378, 0.0662814, 0, 0.00800055, 0.998985, 0.0664779, 0, 0.0125594, 0.998285, 0.0666914, 0, 0.0179786, 0.995071, 0.0661989, 0, 0.0241822, 0.993172, 0.0660454, 0, 0.031106, 0.991438, 0.0660105, 0, 0.0386952, 0.988428, 0.0656875, 0, 0.0469032, 0.985218, 0.0652913, 0, 0.0556905, 0.981128, 0.0647107, 0, 0.065023, 0.976015, 0.0638491, 0, 0.0748717, 0.97097, 0.062993, 0, 0.0852112, 0.964582, 0.0617927, 0, 0.0960199, 0.957383, 0.0603626, 0, 0.107279, 0.949969, 0.0588128, 0, 0.118971, 0.941843, 0.0570274, 0, 0.131084, 0.933624, 0.0551885, 0, 0.143604, 0.924543, 0.053122, 0, 0.156521, 0.914919, 0.0508897, 0, 0.169825, 0.905773, 0.0486418, 0, 0.18351, 0.896434, 0.0463364, 0, 0.197569, 0.887195, 0.0440623, 0, 0.211997, 0.877706, 0.0417799, 0, 0.226789, 0.867719, 0.03945, 0, 0.241944, 0.858587, 0.037243, 0, 0.257458, 0.849317, 0.0350956, 0, 0.273331, 0.839585, 0.0329852, 0, 0.289563, 0.829856, 0.0310028, 0, 0.306154, 0.819589, 0.0290953, 0, 0.323108, 0.809714, 0.0272738, 0, 0.340426, 0.79934, 0.0255631, 0, 0.358113, 0.788224, 0.0239175, 0, 0.376175, 0.776619, 0.0223831, 0, 0.394616, 0.76521, 0.0209298, 0, 0.413445, 0.753716, 0.0195786, 0, 0.432671, 0.741564, 0.0183001, 0, 0.452305, 0.729413, 0.0171259, 0, 0.472358, 0.717146, 0.0159933, 0, 0.492845, 0.70436, 0.0149495, 0, 0.513783, 0.69219, 0.0139681, 0, 0.535189, 0.680289, 0.0130577, 0, 0.557087, 0.669611, 0.0122198, 0, 0.5795, 0.659113, 0.0114174, 0, 0.602459, 0.648148, 0.0106729, 0, 0.625997, 0.636905, 0.00998997, 0, 0.650154, 0.625154, 0.00934313, 0, 0.674976, 0.613481, 0.00874839, 0, 0.700518, 0.60154, 0.00818265, 0, 0.726845, 0.58943, 0.00766889, 0, 0.754032, 0.576828, 0.00717153, 0, 0.782167, 0.564194, 0.00672696, 0, 0.811344, 0.551501, 0.00630863, 0, 0.841644, 0.538635, 0.00592177, 0, 0.873016, 0.525724, 0.00554888, 0, 0.904762, 0.513209, 0.00520225, 0, 0.936508, 0.500457, 0.00488231, 0, 0.968254, 0.48799, 0.00457153, 0, 1, 1, 0.0810131, 0, 0, 1, 0.0810133, 0, 0, 0.999997, 0.0810145, 0, 0, 0.999985, 0.08102, 0, 0, 0.999956, 0.0810347, 0, 1.95026e-05, 0.999893, 0.0810656, 0, 0.000719316, 0.999777, 0.0811205, 0, 0.00259774, 0.999583, 0.081208, 0, 0.00561807, 0.999281, 0.0813343, 0, 0.00967472, 0.998813, 0.0814969, 0, 0.0146627, 0.997597, 0.0815217, 0, 0.0204902, 0.994379, 0.0808502, 0, 0.0270802, 0.992744, 0.0806792, 0, 0.0343674, 0.990745, 0.0804589, 0, 0.0422974, 0.986646, 0.0796107, 0, 0.0508242, 0.983611, 0.0790913, 0, 0.0599087, 0.978869, 0.0780746, 0, 0.0695175, 0.973475, 0.0768218, 0, 0.0796223, 0.967845, 0.0754926, 0, 0.0901983, 0.960778, 0.0737063, 0, 0.101224, 0.953333, 0.0718052, 0, 0.112682, 0.945274, 0.0695946, 0, 0.124555, 0.936955, 0.0672492, 0, 0.136831, 0.928319, 0.0647732, 0, 0.149496, 0.919075, 0.0620947, 0, 0.162542, 0.909114, 0.0591816, 0, 0.175958, 0.900137, 0.0563917, 0, 0.189739, 0.891069, 0.0535392, 0, 0.203877, 0.882262, 0.0507642, 0, 0.218368, 0.873232, 0.0479793, 0, 0.233208, 0.864042, 0.045226, 0, 0.248393, 0.855002, 0.0425413, 0, 0.263923, 0.846569, 0.0400126, 0, 0.279796, 0.837714, 0.0375269, 0, 0.296012, 0.828918, 0.0352027, 0, 0.312573, 0.819783, 0.0330011, 0, 0.329479, 0.810129, 0.0308908, 0, 0.346734, 0.800866, 0.0289112, 0, 0.364342, 0.79093, 0.0270255, 0, 0.382307, 0.780593, 0.0252758, 0, 0.400637, 0.769511, 0.0236178, 0, 0.419337, 0.758558, 0.0220652, 0, 0.438418, 0.747632, 0.0206289, 0, 0.457889, 0.736146, 0.0192873, 0, 0.477761, 0.724093, 0.0180333, 0, 0.49805, 0.71234, 0.0168264, 0, 0.51877, 0.700201, 0.015746, 0, 0.53994, 0.687949, 0.0147027, 0, 0.561581, 0.676163, 0.0137512, 0, 0.583718, 0.665001, 0.0128655, 0, 0.60638, 0.65472, 0.0120366, 0, 0.629599, 0.644213, 0.0112604, 0, 0.653415, 0.633382, 0.0105413, 0, 0.677874, 0.62212, 0.00986498, 0, 0.70303, 0.610631, 0.00923308, 0, 0.728948, 0.599078, 0.00864206, 0, 0.755706, 0.587519, 0.00811784, 0, 0.783396, 0.575505, 0.00761237, 0, 0.812121, 0.563148, 0.00713949, 0, 0.841989, 0.550828, 0.00668379, 0, 0.873035, 0.538458, 0.00627715, 0, 0.904762, 0.525905, 0.00588336, 0, 0.936508, 0.513517, 0.00552687, 0, 0.968254, 0.501395, 0.00519681, 0, 1, 1, 0.0991506, 0, 0, 1, 0.0991504, 0, 0, 0.999996, 0.0991515, 0, 0, 0.999984, 0.0991558, 0, 0, 0.999947, 0.0991672, 0, 0.000114389, 0.999874, 0.0991912, 0, 0.00121503, 0.999739, 0.0992331, 0, 0.00356108, 0.999514, 0.0992983, 0, 0.00705578, 0.999159, 0.0993877, 0, 0.011574, 0.998586, 0.0994837, 0, 0.017003, 0.995731, 0.0988425, 0, 0.0232484, 0.993384, 0.098276, 0, 0.0302318, 0.991615, 0.0979269, 0, 0.0378884, 0.989029, 0.0973432, 0, 0.0461641, 0.985373, 0.0963539, 0, 0.0550136, 0.981278, 0.0952306, 0, 0.0643988, 0.975777, 0.0936233, 0, 0.0742868, 0.970526, 0.0920219, 0, 0.0846501, 0.963755, 0.0898912, 0, 0.0954644, 0.956676, 0.0876064, 0, 0.106709, 0.948099, 0.0847751, 0, 0.118367, 0.939718, 0.0818638, 0, 0.130423, 0.931305, 0.078857, 0, 0.142862, 0.922342, 0.0756127, 0, 0.155674, 0.912842, 0.0721473, 0, 0.168849, 0.903304, 0.0686195, 0, 0.182378, 0.89411, 0.0650589, 0, 0.196255, 0.885512, 0.0616022, 0, 0.210473, 0.877193, 0.0582434, 0, 0.225027, 0.86877, 0.0548979, 0, 0.239915, 0.860267, 0.0516095, 0, 0.255132, 0.851915, 0.048468, 0, 0.270678, 0.843912, 0.0454447, 0, 0.286551, 0.83604, 0.0425612, 0, 0.302751, 0.828245, 0.0398752, 0, 0.31928, 0.820159, 0.0373198, 0, 0.336138, 0.81167, 0.034916, 0, 0.35333, 0.802659, 0.0326402, 0, 0.370858, 0.793921, 0.0304901, 0, 0.388728, 0.784713, 0.0284857, 0, 0.406944, 0.774946, 0.0266186, 0, 0.425515, 0.76448, 0.0248593, 0, 0.444449, 0.753793, 0.0232114, 0, 0.463756, 0.743506, 0.0217039, 0, 0.483447, 0.732555, 0.0202841, 0, 0.503535, 0.720965, 0.0189648, 0, 0.524036, 0.709422, 0.0177189, 0, 0.544968, 0.697756, 0.0165626, 0, 0.56635, 0.685565, 0.015483, 0, 0.588208, 0.673987, 0.0144892, 0, 0.610569, 0.66244, 0.0135607, 0, 0.633466, 0.651675, 0.0126956, 0, 0.656936, 0.641598, 0.0118788, 0, 0.681025, 0.63121, 0.0111261, 0, 0.705788, 0.620514, 0.010437, 0, 0.731289, 0.609366, 0.00978747, 0, 0.757606, 0.598137, 0.00917257, 0, 0.784834, 0.586966, 0.00859778, 0, 0.813085, 0.575549, 0.00806803, 0, 0.842485, 0.563797, 0.00757294, 0, 0.87313, 0.551758, 0.00710592, 0, 0.904762, 0.539894, 0.0066841, 0, 0.936508, 0.527901, 0.00627901, 0, 0.968254, 0.515819, 0.00590506, 0, 1, 1, 0.120864, 0, 0, 1, 0.120864, 0, 0, 0.999996, 0.120864, 0, 0, 0.99998, 0.120867, 0, 0, 0.99994, 0.120872, 0, 0.000323781, 0.999852, 0.120884, 0, 0.00188693, 0.999693, 0.120903, 0, 0.00473489, 0.999426, 0.120929, 0, 0.00872704, 0.999002, 0.120955, 0, 0.0137237, 0.998235, 0.120918, 0, 0.0196068, 0.994608, 0.119764, 0, 0.0262803, 0.992997, 0.119265, 0, 0.0336657, 0.990968, 0.11863, 0, 0.0416987, 0.987002, 0.117261, 0, 0.0503261, 0.983524, 0.116009, 0, 0.0595035, 0.97875, 0.114252, 0, 0.0691935, 0.972652, 0.11193, 0, 0.0793645, 0.966613, 0.109555, 0, 0.0899894, 0.959275, 0.106612, 0, 0.101045, 0.951272, 0.103375, 0, 0.112512, 0.942323, 0.0996594, 0, 0.124372, 0.933679, 0.0958841, 0, 0.136611, 0.924822, 0.0919265, 0, 0.149216, 0.915742, 0.0878061, 0, 0.162176, 0.906348, 0.0834894, 0, 0.175482, 0.896883, 0.079085, 0, 0.189125, 0.88774, 0.0746745, 0, 0.203098, 0.87986, 0.0705773, 0, 0.217396, 0.871998, 0.0665005, 0, 0.232015, 0.864325, 0.0625413, 0, 0.24695, 0.856685, 0.0586781, 0, 0.2622, 0.84925, 0.0550063, 0, 0.277761, 0.841719, 0.0514727, 0, 0.293634, 0.834755, 0.0481398, 0, 0.309819, 0.827853, 0.0450172, 0, 0.326315, 0.820888, 0.0420969, 0, 0.343126, 0.813616, 0.0393702, 0, 0.360254, 0.805767, 0.0367771, 0, 0.377701, 0.797338, 0.0343274, 0, 0.395474, 0.789122, 0.0320529, 0, 0.413577, 0.780601, 0.0299485, 0, 0.432018, 0.771424, 0.0279812, 0, 0.450804, 0.761502, 0.0261054, 0, 0.469944, 0.751166, 0.0243942, 0, 0.489451, 0.741276, 0.0228087, 0, 0.509337, 0.730898, 0.0213265, 0, 0.529617, 0.719878, 0.0199307, 0, 0.550307, 0.708379, 0.0186574, 0, 0.571428, 0.697165, 0.0174446, 0, 0.593003, 0.685554, 0.0163144, 0, 0.615059, 0.673631, 0.015276, 0, 0.637628, 0.662385, 0.0143003, 0, 0.660746, 0.651059, 0.0134112, 0, 0.68446, 0.640451, 0.0125794, 0, 0.70882, 0.630536, 0.011793, 0, 0.733893, 0.620316, 0.0110547, 0, 0.759756, 0.609722, 0.0103668, 0, 0.786505, 0.598804, 0.00973009, 0, 0.814259, 0.587871, 0.00912812, 0, 0.843157, 0.577121, 0.00858916, 0, 0.87334, 0.566019, 0.00807333, 0, 0.904762, 0.554664, 0.00759687, 0, 0.936508, 0.543101, 0.00714759, 0, 0.968254, 0.531558, 0.00673418, 0, 1, 1, 0.146767, 0, 0, 1, 0.146767, 0, 0, 0.999997, 0.146767, 0, 0, 0.999977, 0.146765, 0, 3.20658e-06, 0.999929, 0.146762, 0, 0.000682576, 0.999823, 0.146753, 0, 0.00276402, 0.999633, 0.146735, 0, 0.00614771, 0.999314, 0.146699, 0, 0.0106613, 0.998796, 0.14662, 0, 0.0161546, 0.997124, 0.146107, 0, 0.0225063, 0.994062, 0.144857, 0, 0.0296198, 0.992154, 0.144011, 0, 0.037417, 0.989186, 0.142712, 0, 0.0458348, 0.985279, 0.140926, 0, 0.0548211, 0.980826, 0.13885, 0, 0.0643326, 0.975056, 0.136168, 0, 0.074333, 0.969005, 0.133217, 0, 0.0847917, 0.961554, 0.12959, 0, 0.0956828, 0.954206, 0.125886, 0, 0.106984, 0.945046, 0.121335, 0, 0.118675, 0.935678, 0.116492, 0, 0.130741, 0.926748, 0.111635, 0, 0.143166, 0.917764, 0.106625, 0, 0.155939, 0.908358, 0.101325, 0, 0.169049, 0.899219, 0.0960249, 0, 0.182487, 0.890089, 0.0906527, 0, 0.196245, 0.881488, 0.0853905, 0, 0.210317, 0.874031, 0.0804177, 0, 0.224697, 0.866932, 0.0756005, 0, 0.23938, 0.859976, 0.0709019, 0, 0.254364, 0.853375, 0.0664391, 0, 0.269646, 0.846971, 0.0622012, 0, 0.285223, 0.840483, 0.058129, 0, 0.301096, 0.833969, 0.0542762, 0, 0.317265, 0.82806, 0.0507042, 0, 0.333729, 0.822128, 0.047368, 0, 0.350491, 0.815989, 0.044272, 0, 0.367554, 0.809336, 0.0413444, 0, 0.38492, 0.802177, 0.038601, 0, 0.402594, 0.79441, 0.0360227, 0, 0.420582, 0.786573, 0.0336383, 0, 0.438891, 0.778619, 0.0314321, 0, 0.457527, 0.77, 0.029362, 0, 0.476499, 0.760698, 0.0274102, 0, 0.49582, 0.750932, 0.0256146, 0, 0.5155, 0.740993, 0.023974, 0, 0.535555, 0.731159, 0.0224182, 0, 0.556, 0.720836, 0.0209889, 0, 0.576855, 0.709913, 0.0196411, 0, 0.598143, 0.698415, 0.0183824, 0, 0.619888, 0.68745, 0.0172222, 0, 0.642123, 0.676154, 0.0161509, 0, 0.664883, 0.664383, 0.0151397, 0, 0.688211, 0.6533, 0.0141873, 0, 0.71216, 0.642072, 0.0133105, 0, 0.736792, 0.631412, 0.0124932, 0, 0.762186, 0.621622, 0.0117408, 0, 0.788439, 0.611681, 0.0110358, 0, 0.815672, 0.60142, 0.0103775, 0, 0.844034, 0.59083, 0.00975623, 0, 0.873699, 0.580254, 0.00918084, 0, 0.904765, 0.569841, 0.00864721, 0, 0.936508, 0.559224, 0.00815731, 0, 0.968254, 0.548315, 0.00767924, 0, 1, 1, 0.177563, 0, 0, 1, 0.177563, 0, 0, 0.999994, 0.177562, 0, 0, 0.999972, 0.177555, 0, 6.64171e-05, 0.999914, 0.177536, 0, 0.0012276, 0.999787, 0.177496, 0, 0.00388025, 0.999556, 0.17742, 0, 0.00783463, 0.999165, 0.177285, 0, 0.0128953, 0.9985, 0.177037, 0, 0.0189053, 0.995388, 0.175634, 0, 0.025742, 0.993102, 0.174375, 0, 0.033309, 0.990992, 0.173121, 0, 0.0415298, 0.986932, 0.170896, 0, 0.0503425, 0.982786, 0.16847, 0, 0.0596964, 0.977592, 0.165455, 0, 0.0695498, 0.971075, 0.161676, 0, 0.0798676, 0.963967, 0.157458, 0, 0.0906201, 0.956397, 0.152836, 0, 0.101783, 0.947489, 0.147467, 0, 0.113333, 0.937564, 0.14145, 0, 0.125254, 0.928182, 0.135383, 0, 0.137529, 0.919027, 0.129212, 0, 0.150144, 0.909618, 0.12276, 0, 0.163088, 0.900492, 0.116273, 0, 0.176351, 0.891671, 0.1098, 0, 0.189924, 0.883146, 0.103362, 0, 0.203799, 0.875151, 0.0970799, 0, 0.21797, 0.868338, 0.0911732, 0, 0.232433, 0.862033, 0.0854966, 0, 0.247182, 0.856107, 0.0800691, 0, 0.262216, 0.850644, 0.0749618, 0, 0.27753, 0.845261, 0.070079, 0, 0.293124, 0.839885, 0.0654321, 0, 0.308997, 0.834609, 0.0610975, 0, 0.325149, 0.829083, 0.0569741, 0, 0.341581, 0.82404, 0.0531736, 0, 0.358294, 0.818968, 0.049665, 0, 0.37529, 0.813496, 0.0463856, 0, 0.392573, 0.807533, 0.0433217, 0, 0.410148, 0.80099, 0.0404402, 0, 0.428019, 0.793891, 0.0377578, 0, 0.446192, 0.786281, 0.0352616, 0, 0.464676, 0.778773, 0.0329577, 0, 0.483478, 0.770737, 0.030808, 0, 0.502608, 0.762094, 0.0287964, 0, 0.522079, 0.752898, 0.0269254, 0, 0.541905, 0.743306, 0.0251926, 0, 0.5621, 0.733416, 0.023595, 0, 0.582684, 0.723742, 0.0221155, 0, 0.603677, 0.713542, 0.0207435, 0, 0.625106, 0.702755, 0.019434, 0, 0.646998, 0.691484, 0.0182046, 0, 0.66939, 0.680531, 0.0170771, 0, 0.692324, 0.66953, 0.0160339, 0, 0.715849, 0.658126, 0.0150677, 0, 0.740028, 0.646933, 0.0141551, 0, 0.764937, 0.636107, 0.0133179, 0, 0.790673, 0.625271, 0.0125284, 0, 0.817358, 0.615225, 0.0117937, 0, 0.84515, 0.605678, 0.0111181, 0, 0.874244, 0.59583, 0.0104759, 0, 0.904828, 0.585704, 0.00986672, 0, 0.936508, 0.575413, 0.00929712, 0, 0.968254, 0.565373, 0.00876713, 0, 1, 1, 0.214058, 0, 0, 0.999999, 0.214058, 0, 0, 0.999994, 0.214055, 0, 0, 0.999966, 0.214039, 0, 0.000259642, 0.999893, 0.213998, 0, 0.00200075, 0.999737, 0.21391, 0, 0.00527775, 0.999449, 0.213745, 0, 0.00983959, 0.99896, 0.213458, 0, 0.0154755, 0.9979, 0.212855, 0, 0.0220249, 0.994278, 0.210779, 0, 0.0293654, 0.992254, 0.20926, 0, 0.0374021, 0.98881, 0.206908, 0, 0.0460604, 0.984715, 0.204009, 0, 0.0552802, 0.979738, 0.200471, 0, 0.0650127, 0.972884, 0.195813, 0, 0.0752175, 0.965996, 0.190856, 0, 0.0858612, 0.957974, 0.185077, 0, 0.0969155, 0.949155, 0.17868, 0, 0.108356, 0.939288, 0.171513, 0, 0.120163, 0.928996, 0.163838, 0, 0.132319, 0.919563, 0.156246, 0, 0.144808, 0.910004, 0.148359, 0, 0.157618, 0.900791, 0.140417, 0, 0.170737, 0.892135, 0.132569, 0, 0.184155, 0.883803, 0.124741, 0, 0.197866, 0.876034, 0.117091, 0, 0.211861, 0.869219, 0.109835, 0, 0.226134, 0.863062, 0.102859, 0, 0.240682, 0.857795, 0.0962928, 0, 0.255499, 0.853009, 0.0900725, 0, 0.270583, 0.848603, 0.0842101, 0, 0.285931, 0.844335, 0.0786527, 0, 0.301542, 0.840208, 0.0734397, 0, 0.317415, 0.836035, 0.0685334, 0, 0.33355, 0.83172, 0.0639275, 0, 0.349948, 0.827135, 0.0595909, 0, 0.36661, 0.822797, 0.0556204, 0, 0.383539, 0.818387, 0.0519394, 0, 0.400738, 0.813565, 0.0485317, 0, 0.41821, 0.808142, 0.0453138, 0, 0.435961, 0.802212, 0.0423354, 0, 0.453997, 0.79573, 0.0395553, 0, 0.472324, 0.788741, 0.036988, 0, 0.490951, 0.781093, 0.0345688, 0, 0.509887, 0.773597, 0.0323297, 0, 0.529144, 0.765622, 0.0302719, 0, 0.548735, 0.757083, 0.0283477, 0, 0.568674, 0.747992, 0.0265562, 0, 0.588979, 0.738591, 0.0248844, 0, 0.609671, 0.728719, 0.0233342, 0, 0.630773, 0.719146, 0.0219081, 0, 0.652314, 0.709165, 0.0205711, 0, 0.674328, 0.69875, 0.0193248, 0, 0.696854, 0.687884, 0.0181582, 0, 0.719942, 0.676818, 0.0170746, 0, 0.743651, 0.666247, 0.0160718, 0, 0.768057, 0.655284, 0.0151262, 0, 0.793253, 0.64401, 0.0142561, 0, 0.819363, 0.633353, 0.0134327, 0, 0.846547, 0.622674, 0.012653, 0, 0.875017, 0.612265, 0.0119354, 0, 0.905021, 0.602455, 0.0112533, 0, 0.936508, 0.593147, 0.0106234, 0, 0.968254, 0.583592, 0.0100213, 0, 1, 1, 0.25717, 0, 0, 1, 0.25717, 0, 0, 0.999992, 0.257164, 0, 0, 0.999958, 0.257135, 0, 0.000641715, 0.999864, 0.25706, 0, 0.00305314, 0.999666, 0.256897, 0, 0.00700975, 0.999302, 0.256596, 0, 0.0122194, 0.998663, 0.25607, 0, 0.0184622, 0.995607, 0.254123, 0, 0.0255773, 0.993094, 0.252081, 0, 0.0334439, 0.9907, 0.249867, 0, 0.0419696, 0.98594, 0.246118, 0, 0.0510823, 0.981214, 0.242049, 0, 0.0607242, 0.974966, 0.236869, 0, 0.0708486, 0.967589, 0.230724, 0, 0.081417, 0.95915, 0.223635, 0, 0.0923974, 0.950257, 0.21596, 0, 0.103763, 0.940165, 0.207296, 0, 0.115491, 0.929396, 0.197901, 0, 0.127562, 0.919288, 0.188437, 0, 0.13996, 0.909428, 0.178762, 0, 0.15267, 0.900105, 0.169072, 0, 0.165679, 0.891418, 0.159478, 0, 0.178979, 0.883347, 0.15002, 0, 0.192558, 0.875992, 0.140813, 0, 0.20641, 0.869466, 0.13196, 0, 0.220529, 0.863699, 0.123501, 0, 0.234907, 0.858553, 0.115436, 0, 0.249542, 0.854379, 0.107901, 0, 0.264428, 0.850894, 0.10088, 0, 0.279564, 0.847632, 0.0942296, 0, 0.294947, 0.844571, 0.0879861, 0, 0.310575, 0.84163, 0.0821534, 0, 0.326448, 0.838542, 0.0766409, 0, 0.342566, 0.835412, 0.0715322, 0, 0.358929, 0.831899, 0.0666883, 0, 0.37554, 0.828177, 0.0622175, 0, 0.392399, 0.82416, 0.0580452, 0, 0.409511, 0.820393, 0.054267, 0, 0.426878, 0.816068, 0.0507172, 0, 0.444506, 0.811201, 0.0474041, 0, 0.4624, 0.805785, 0.0443174, 0, 0.480566, 0.799878, 0.0414562, 0, 0.499013, 0.793469, 0.0388147, 0, 0.517749, 0.786473, 0.0363453, 0, 0.536785, 0.778874, 0.0340225, 0, 0.556134, 0.771277, 0.0318599, 0, 0.575809, 0.763426, 0.0298859, 0, 0.595827, 0.755044, 0.0280357, 0, 0.616207, 0.746161, 0.0262979, 0, 0.636973, 0.737124, 0.0247295, 0, 0.65815, 0.72761, 0.0232514, 0, 0.679772, 0.717822, 0.0218755, 0, 0.701876, 0.708279, 0.0205942, 0, 0.724509, 0.698333, 0.0193947, 0, 0.74773, 0.68802, 0.0182717, 0, 0.771609, 0.677321, 0.0172044, 0, 0.79624, 0.666504, 0.0162122, 0, 0.821743, 0.656184, 0.0152924, 0, 0.84828, 0.64556, 0.0144326, 0, 0.876069, 0.634636, 0.0136157, 0, 0.905404, 0.624124, 0.0128612, 0, 0.936508, 0.613914, 0.0121435, 0, 0.968254, 0.603589, 0.0114887, 0, 1, 1, 0.307946, 0, 0, 0.999999, 0.307945, 0, 0, 0.999988, 0.307934, 0, 2.04479e-05, 0.999944, 0.307886, 0, 0.00127833, 0.999824, 0.307756, 0, 0.00445047, 0.999565, 0.30748, 0, 0.00914673, 0.999085, 0.306966, 0, 0.0150498, 0.998103, 0.306004, 0, 0.0219367, 0.994249, 0.303028, 0, 0.0296485, 0.991807, 0.300435, 0, 0.038068, 0.987773, 0.296554, 0, 0.0471062, 0.982673, 0.2916, 0, 0.0566942, 0.976623, 0.285641, 0, 0.0667768, 0.968757, 0.27815, 0, 0.0773099, 0.959849, 0.269529, 0, 0.088257, 0.950663, 0.260248, 0, 0.0995879, 0.940129, 0.249704, 0, 0.111277, 0.92895, 0.238291, 0, 0.123304, 0.917996, 0.226501, 0, 0.13565, 0.907813, 0.214669, 0, 0.148299, 0.898305, 0.202835, 0, 0.161237, 0.889626, 0.191158, 0, 0.174455, 0.88175, 0.179695, 0, 0.187941, 0.874715, 0.168548, 0, 0.201687, 0.868746, 0.15792, 0, 0.215687, 0.863703, 0.147807, 0, 0.229933, 0.859315, 0.138149, 0, 0.24442, 0.855538, 0.128993, 0, 0.259145, 0.852428, 0.120414, 0, 0.274103, 0.850168, 0.112498, 0, 0.289293, 0.848132, 0.105054, 0, 0.304711, 0.846291, 0.0981087, 0, 0.320357, 0.844431, 0.0915942, 0, 0.33623, 0.842493, 0.0855056, 0, 0.35233, 0.840368, 0.0798204, 0, 0.368658, 0.83798, 0.0745097, 0, 0.385214, 0.83523, 0.0695424, 0, 0.402002, 0.832091, 0.0649092, 0, 0.419023, 0.828667, 0.0606291, 0, 0.436282, 0.824805, 0.0566523, 0, 0.453782, 0.820988, 0.0530229, 0, 0.471529, 0.816635, 0.0496364, 0, 0.489528, 0.811725, 0.0464658, 0, 0.507788, 0.806316, 0.0435082, 0, 0.526317, 0.800469, 0.0407873, 0, 0.545124, 0.794107, 0.038255, 0, 0.564221, 0.787218, 0.0358825, 0, 0.583621, 0.779872, 0.0336785, 0, 0.603341, 0.772097, 0.0316379, 0, 0.623397, 0.764484, 0.0297379, 0, 0.643812, 0.756428, 0.0279581, 0, 0.664611, 0.748022, 0.0263153, 0, 0.685824, 0.739268, 0.0247799, 0, 0.707488, 0.73024, 0.0233385, 0, 0.729646, 0.720893, 0.0220035, 0, 0.752354, 0.71119, 0.0207555, 0, 0.77568, 0.701791, 0.0195843, 0, 0.799715, 0.692184, 0.0184891, 0, 0.824574, 0.682258, 0.0174541, 0, 0.850417, 0.67206, 0.0164873, 0, 0.877466, 0.661717, 0.0155959, 0, 0.90604, 0.651462, 0.0147519, 0, 0.936528, 0.641467, 0.0139727, 0, 0.968254, 0.631229, 0.0132363, 0, 1, 1, 0.367573, 0, 0, 0.999999, 0.367571, 0, 0, 0.999984, 0.367553, 0, 0.000183382, 0.999925, 0.367473, 0, 0.00225254, 0.999759, 0.367259, 0, 0.00628165, 0.99941, 0.366801, 0, 0.0117858, 0.998739, 0.365946, 0, 0.0184359, 0.995529, 0.363191, 0, 0.0260114, 0.992875, 0.360171, 0, 0.0343581, 0.989135, 0.355981, 0, 0.0433637, 0.984166, 0.350401, 0, 0.0529438, 0.977871, 0.343348, 0, 0.0630334, 0.96951, 0.334341, 0, 0.0735805, 0.959964, 0.323862, 0, 0.0845437, 0.950162, 0.312521, 0, 0.095889, 0.938882, 0.299577, 0, 0.107588, 0.926992, 0.285573, 0, 0.119617, 0.915589, 0.271212, 0, 0.131957, 0.904791, 0.256611, 0, 0.144591, 0.895177, 0.242224, 0, 0.157503, 0.886403, 0.227952, 0, 0.170682, 0.878957, 0.214192, 0, 0.184117, 0.872418, 0.200795, 0, 0.197799, 0.867029, 0.188015, 0, 0.21172, 0.862835, 0.175975, 0, 0.225873, 0.859411, 0.164526, 0, 0.240253, 0.856655, 0.153693, 0, 0.254854, 0.854519, 0.14352, 0, 0.269673, 0.852828, 0.13397, 0, 0.284707, 0.851412, 0.124984, 0, 0.299953, 0.850609, 0.116748, 0, 0.315408, 0.849855, 0.10905, 0, 0.331073, 0.849017, 0.101839, 0, 0.346946, 0.848079, 0.0951359, 0, 0.363028, 0.846911, 0.0888774, 0, 0.379318, 0.845445, 0.0830375, 0, 0.395818, 0.84362, 0.0775844, 0, 0.41253, 0.841411, 0.0725054, 0, 0.429457, 0.838768, 0.0677691, 0, 0.446602, 0.835801, 0.0634016, 0, 0.463968, 0.832341, 0.0593095, 0, 0.481561, 0.828424, 0.0555121, 0, 0.499386, 0.824312, 0.052024, 0, 0.51745, 0.819918, 0.0487865, 0, 0.535761, 0.815072, 0.0457801, 0, 0.554328, 0.809863, 0.0430184, 0, 0.573162, 0.804164, 0.0404245, 0, 0.592275, 0.798034, 0.0380146, 0, 0.611681, 0.791436, 0.0357436, 0, 0.631398, 0.784498, 0.0336475, 0, 0.651445, 0.777125, 0.0316666, 0, 0.671845, 0.769365, 0.0298122, 0, 0.692628, 0.761579, 0.0281001, 0, 0.713827, 0.753746, 0.0265049, 0, 0.735484, 0.745573, 0.0250067, 0, 0.75765, 0.737083, 0.0236026, 0, 0.78039, 0.728545, 0.0223302, 0, 0.803789, 0.719691, 0.0211243, 0, 0.82796, 0.710569, 0.0199983, 0, 0.853056, 0.701216, 0.0189569, 0, 0.879298, 0.692094, 0.0179702, 0, 0.907014, 0.682909, 0.0170418, 0, 0.936691, 0.673509, 0.0161732, 0, 0.968254, 0.663863, 0.0153406, 0, 1, 1, 0.437395, 0, 0, 0.999998, 0.437394, 0, 0, 0.99998, 0.437363, 0, 0.000616704, 0.999891, 0.437232, 0, 0.00367925, 0.999656, 0.436877, 0, 0.00867446, 0.999148, 0.436121, 0, 0.0150679, 0.997959, 0.434564, 0, 0.022531, 0.993464, 0.430134, 0, 0.0308507, 0.990606, 0.426077, 0, 0.0398805, 0.985027, 0.419397, 0, 0.0495148, 0.978491, 0.41118, 0, 0.0596749, 0.969643, 0.40048, 0, 0.0703001, 0.959189, 0.38769, 0, 0.0813427, 0.948223, 0.373575, 0, 0.0927641, 0.935955, 0.357622, 0, 0.104533, 0.923237, 0.34043, 0, 0.116624, 0.911074, 0.322735, 0, 0.129015, 0.899724, 0.30479, 0, 0.141687, 0.890189, 0.287392, 0, 0.154626, 0.881796, 0.270248, 0, 0.167818, 0.874781, 0.253659, 0, 0.181252, 0.869166, 0.237786, 0, 0.194918, 0.864725, 0.222618, 0, 0.208807, 0.861565, 0.208356, 0, 0.222913, 0.859284, 0.194867, 0, 0.237229, 0.857677, 0.18212, 0, 0.25175, 0.856714, 0.17018, 0, 0.266473, 0.856155, 0.158969, 0, 0.281392, 0.8558, 0.148413, 0, 0.296505, 0.855672, 0.138578, 0, 0.311811, 0.855538, 0.129345, 0, 0.327306, 0.855689, 0.120861, 0, 0.342991, 0.855767, 0.112969, 0, 0.358864, 0.855618, 0.105593, 0, 0.374925, 0.85525, 0.0987451, 0, 0.391176, 0.854583, 0.0923727, 0, 0.407616, 0.853534, 0.0864143, 0, 0.424249, 0.852061, 0.0808338, 0, 0.441076, 0.850253, 0.0756771, 0, 0.4581, 0.848004, 0.0708612, 0, 0.475324, 0.845333, 0.0663784, 0, 0.492754, 0.842376, 0.0622631, 0, 0.510394, 0.838956, 0.0584112, 0, 0.528251, 0.835121, 0.0548328, 0, 0.546331, 0.830842, 0.0514838, 0, 0.564644, 0.826212, 0.048355, 0, 0.583198, 0.821522, 0.0454714, 0, 0.602005, 0.816551, 0.0428263, 0, 0.621078, 0.811211, 0.0403612, 0, 0.640434, 0.805479, 0.038039, 0, 0.660089, 0.799409, 0.0358739, 0, 0.680066, 0.79306, 0.0338727, 0, 0.70039, 0.786395, 0.0319985, 0, 0.721094, 0.779416, 0.030241, 0, 0.742215, 0.77214, 0.0285951, 0, 0.7638, 0.764636, 0.0270747, 0, 0.785912, 0.756836, 0.0256354, 0, 0.808628, 0.749315, 0.0243027, 0, 0.832055, 0.741561, 0.0230497, 0, 0.856338, 0.733589, 0.0218801, 0, 0.88169, 0.725479, 0.020784, 0, 0.908441, 0.717255, 0.0197702, 0, 0.937125, 0.708829, 0.0188168, 0, 0.968254, 0.700191, 0.0179113, 0, 1, 1, 0.518937, 0, 0, 0.999998, 0.518933, 0, 0, 0.999967, 0.518883, 0, 0.00147741, 0.999832, 0.51866, 0, 0.00573221, 0.999466, 0.518057, 0, 0.011826, 0.998644, 0.516752, 0, 0.0192116, 0.994458, 0.512347, 0, 0.027573, 0.991223, 0.507675, 0, 0.0367099, 0.985515, 0.500188, 0, 0.046487, 0.978308, 0.490408, 0, 0.0568071, 0.968359, 0.477357, 0, 0.0675984, 0.95682, 0.461752, 0, 0.0788059, 0.943929, 0.443796, 0, 0.090386, 0.930224, 0.423893, 0, 0.102304, 0.916514, 0.402682, 0, 0.114532, 0.903653, 0.380914, 0, 0.127047, 0.892315, 0.359212, 0, 0.139828, 0.882942, 0.338102, 0, 0.152861, 0.875438, 0.31773, 0, 0.16613, 0.869642, 0.298186, 0, 0.179624, 0.865304, 0.279491, 0, 0.193332, 0.862382, 0.261804, 0, 0.207247, 0.860666, 0.245146, 0, 0.22136, 0.859788, 0.229406, 0, 0.235666, 0.859608, 0.214605, 0, 0.250158, 0.859912, 0.200691, 0, 0.264832, 0.86053, 0.187623, 0, 0.279684, 0.861368, 0.17539, 0, 0.294711, 0.862237, 0.163901, 0, 0.309911, 0.863127, 0.153175, 0, 0.32528, 0.863923, 0.143147, 0, 0.340819, 0.864567, 0.133781, 0, 0.356524, 0.865013, 0.125042, 0, 0.372397, 0.86539, 0.116952, 0, 0.388438, 0.865591, 0.109476, 0, 0.404645, 0.865517, 0.102542, 0, 0.421022, 0.865084, 0.0960688, 0, 0.437569, 0.864309, 0.0900499, 0, 0.454287, 0.863151, 0.0844328, 0, 0.471181, 0.861649, 0.0792218, 0, 0.488253, 0.859742, 0.0743482, 0, 0.505507, 0.857446, 0.0697963, 0, 0.522947, 0.854757, 0.0655364, 0, 0.54058, 0.851783, 0.061608, 0, 0.558412, 0.848516, 0.0579701, 0, 0.576449, 0.844897, 0.0545742, 0, 0.594701, 0.840956, 0.0514167, 0, 0.613178, 0.836676, 0.0484598, 0, 0.631892, 0.832075, 0.0456934, 0, 0.650856, 0.827191, 0.0431178, 0, 0.670088, 0.822295, 0.0407718, 0, 0.689606, 0.817294, 0.0386032, 0, 0.709434, 0.812013, 0.0365675, 0, 0.7296, 0.806465, 0.0346547, 0, 0.750138, 0.800691, 0.0328717, 0, 0.771093, 0.794709, 0.031211, 0, 0.792519, 0.788493, 0.0296504, 0, 0.814488, 0.782049, 0.0281782, 0, 0.837097, 0.775403, 0.0267965, 0, 0.860481, 0.76857, 0.0255002, 0, 0.884842, 0.761536, 0.0242759, 0, 0.910494, 0.754303, 0.0231142, 0, 0.937985, 0.74692, 0.0220305, 0, 0.968254, 0.739745, 0.0210192, 0, 1, 1, 0.613914, 0, 0, 0.999996, 0.613907, 0, 9.63597e-05, 0.999942, 0.613814, 0, 0.00301247, 0.999704, 0.613407, 0, 0.00870385, 0.999046, 0.612302, 0, 0.0160714, 0.995516, 0.608266, 0, 0.0245899, 0.991726, 0.602863, 0, 0.0339681, 0.985157, 0.593956, 0, 0.0440254, 0.97642, 0.581748, 0, 0.0546409, 0.964404, 0.565183, 0, 0.0657284, 0.950601, 0.545273, 0, 0.0772246, 0.935158, 0.522129, 0, 0.0890812, 0.919364, 0.496782, 0, 0.10126, 0.904754, 0.470571, 0, 0.113731, 0.89176, 0.444037, 0, 0.126469, 0.881492, 0.418322, 0, 0.139454, 0.873656, 0.393522, 0, 0.15267, 0.868053, 0.369795, 0, 0.166101, 0.864336, 0.347171, 0, 0.179736, 0.862259, 0.325737, 0, 0.193565, 0.861556, 0.305532, 0, 0.207578, 0.861776, 0.286416, 0, 0.221769, 0.862661, 0.268355, 0, 0.23613, 0.864015, 0.251334, 0, 0.250656, 0.865711, 0.235352, 0, 0.265343, 0.867519, 0.220302, 0, 0.280187, 0.869351, 0.206161, 0, 0.295183, 0.871144, 0.192908, 0, 0.31033, 0.872839, 0.180505, 0, 0.325624, 0.874307, 0.168848, 0, 0.341065, 0.875667, 0.158021, 0, 0.35665, 0.876758, 0.147877, 0, 0.37238, 0.87764, 0.138441, 0, 0.388253, 0.878237, 0.129627, 0, 0.404269, 0.878563, 0.121415, 0, 0.42043, 0.878572, 0.113741, 0, 0.436735, 0.87842, 0.106652, 0, 0.453187, 0.878057, 0.100097, 0, 0.469786, 0.877413, 0.0940128, 0, 0.486536, 0.87646, 0.0883462, 0, 0.503439, 0.875233, 0.0830924, 0, 0.520498, 0.8737, 0.0781975, 0, 0.537717, 0.871873, 0.07364, 0, 0.555102, 0.86978, 0.0694103, 0, 0.572657, 0.867405, 0.0654696, 0, 0.59039, 0.864751, 0.0617914, 0, 0.608307, 0.861818, 0.0583491, 0, 0.626419, 0.858645, 0.0551443, 0, 0.644733, 0.855307, 0.0521894, 0, 0.663264, 0.851736, 0.0494334, 0, 0.682025, 0.847927, 0.0468504, 0, 0.701032, 0.843888, 0.0444261, 0, 0.720308, 0.839629, 0.0421497, 0, 0.739875, 0.835158, 0.0400082, 0, 0.759764, 0.830509, 0.0380076, 0, 0.780014, 0.825714, 0.0361488, 0, 0.800673, 0.820729, 0.0343956, 0, 0.821803, 0.815751, 0.0327781, 0, 0.843492, 0.810752, 0.031275, 0, 0.86586, 0.805587, 0.0298542, 0, 0.889087, 0.800317, 0.0285397, 0, 0.913466, 0.79489, 0.0272948, 0, 0.93952, 0.789314, 0.0261139, 0, 0.96835, 0.783593, 0.0249938, 0, 1, 1, 0.724258, 0, 0, 0.999992, 0.724243, 0, 0.000726889, 0.99987, 0.724044, 0, 0.00569574, 0.999336, 0.72317, 0, 0.0131702, 0.996271, 0.719432, 0, 0.0220738, 0.991159, 0.712576, 0, 0.0319405, 0.982465, 0.700927, 0, 0.0425202, 0.97049, 0.684297, 0, 0.0536599, 0.953973, 0.661244, 0, 0.065258, 0.935546, 0.633804, 0, 0.0772427, 0.916596, 0.603071, 0, 0.0895616, 0.899353, 0.57105, 0, 0.102175, 0.885216, 0.539206, 0, 0.11505, 0.875076, 0.508714, 0, 0.128164, 0.868334, 0.479571, 0, 0.141495, 0.864414, 0.451796, 0, 0.155026, 0.862678, 0.425328, 0, 0.168745, 0.862835, 0.400352, 0, 0.182639, 0.864067, 0.376532, 0, 0.196699, 0.866086, 0.35391, 0, 0.210915, 0.868557, 0.332424, 0, 0.225282, 0.871271, 0.312053, 0, 0.239792, 0.874058, 0.292764, 0, 0.25444, 0.8768, 0.27453, 0, 0.269223, 0.87939, 0.257297, 0, 0.284135, 0.8819, 0.24114, 0, 0.299174, 0.884187, 0.225934, 0, 0.314337, 0.886262, 0.211669, 0, 0.329622, 0.888119, 0.198311, 0, 0.345026, 0.889709, 0.185783, 0, 0.360549, 0.891054, 0.174063, 0, 0.376189, 0.892196, 0.163143, 0, 0.391946, 0.893101, 0.152952, 0, 0.407819, 0.893803, 0.143475, 0, 0.423808, 0.894277, 0.134647, 0, 0.439914, 0.894532, 0.126434, 0, 0.456137, 0.894576, 0.1188, 0, 0.472479, 0.894393, 0.111694, 0, 0.48894, 0.893976, 0.105069, 0, 0.505523, 0.893346, 0.0989077, 0, 0.52223, 0.892502, 0.0931724, 0, 0.539064, 0.891441, 0.0878276, 0, 0.556028, 0.890276, 0.082903, 0, 0.573125, 0.888972, 0.0783505, 0, 0.590361, 0.887469, 0.0741083, 0, 0.607741, 0.885785, 0.0701633, 0, 0.62527, 0.883914, 0.0664835, 0, 0.642957, 0.881872, 0.0630567, 0, 0.660809, 0.879651, 0.0598527, 0, 0.678836, 0.877267, 0.0568615, 0, 0.69705, 0.874717, 0.05406, 0, 0.715465, 0.872012, 0.0514378, 0, 0.734098, 0.869157, 0.0489805, 0, 0.752968, 0.866155, 0.0466727, 0, 0.772101, 0.863014, 0.0445056, 0, 0.791529, 0.859748, 0.0424733, 0, 0.81129, 0.856416, 0.0405957, 0, 0.831438, 0.852958, 0.0388273, 0, 0.852044, 0.849382, 0.0371619, 0, 0.87321, 0.845694, 0.0355959, 0, 0.89509, 0.841893, 0.0341155, 0, 0.917932, 0.837981, 0.0327141, 0, 0.942204, 0.833963, 0.0313856, 0, 0.968981, 0.829847, 0.0301275, 0, 1, 1, 0.85214, 0, 0, 0.999969, 0.852095, 0, 0.00279627, 0.999483, 0.851408, 0, 0.0107635, 0.994545, 0.84579, 0, 0.0206454, 0.986188, 0.835231, 0, 0.0315756, 0.969847, 0.814687, 0, 0.0432021, 0.945951, 0.783735, 0, 0.0553396, 0.91917, 0.746074, 0, 0.0678766, 0.895488, 0.706938, 0, 0.0807395, 0.878232, 0.669534, 0, 0.0938767, 0.868252, 0.635168, 0, 0.10725, 0.863873, 0.603069, 0, 0.120832, 0.863369, 0.572514, 0, 0.134598, 0.86545, 0.543169, 0, 0.148533, 0.868803, 0.514578, 0, 0.16262, 0.872794, 0.486762, 0, 0.176849, 0.87702, 0.459811, 0, 0.19121, 0.881054, 0.433654, 0, 0.205694, 0.884974, 0.408574, 0, 0.220294, 0.888587, 0.384525, 0, 0.235005, 0.891877, 0.36156, 0, 0.24982, 0.894793, 0.339661, 0, 0.264737, 0.89743, 0.318913, 0, 0.279751, 0.899796, 0.299302, 0, 0.294859, 0.901943, 0.280843, 0, 0.310058, 0.903858, 0.263481, 0, 0.325346, 0.905574, 0.247197, 0, 0.340721, 0.907069, 0.231915, 0, 0.356181, 0.908379, 0.217614, 0, 0.371725, 0.90952, 0.20425, 0, 0.387353, 0.910483, 0.191758, 0, 0.403063, 0.91128, 0.180092, 0, 0.418854, 0.911936, 0.169222, 0, 0.434727, 0.912454, 0.159098, 0, 0.450682, 0.912835, 0.149668, 0, 0.466718, 0.913078, 0.140884, 0, 0.482837, 0.913192, 0.132709, 0, 0.499038, 0.913175, 0.125095, 0, 0.515324, 0.91304, 0.118012, 0, 0.531695, 0.912781, 0.111417, 0, 0.548153, 0.91241, 0.105281, 0, 0.5647, 0.911924, 0.0995691, 0, 0.581338, 0.911331, 0.0942531, 0, 0.59807, 0.910637, 0.0893076, 0, 0.6149, 0.90984, 0.0846998, 0, 0.63183, 0.908941, 0.0804044, 0, 0.648865, 0.907944, 0.0763984, 0, 0.666011, 0.906857, 0.0726638, 0, 0.683273, 0.90568, 0.0691783, 0, 0.700659, 0.904416, 0.0659222, 0, 0.718176, 0.903067, 0.0628782, 0, 0.735834, 0.901637, 0.0600307, 0, 0.753646, 0.900128, 0.0573647, 0, 0.771625, 0.898544, 0.0548668, 0, 0.78979, 0.89689, 0.052527, 0, 0.808162, 0.895165, 0.0503306, 0, 0.826771, 0.893371, 0.0482668, 0, 0.845654, 0.891572, 0.0463605, 0, 0.864863, 0.889763, 0.0445998, 0, 0.884472, 0.887894, 0.0429451, 0, 0.904592, 0.885967, 0.0413884, 0, 0.925407, 0.883984, 0.0399225, 0, 0.947271, 0.881945, 0.0385405, 0, 0.97105, 0.879854, 0.0372362, 0, 1, 0.999804, 0.995833, 0, 0, 0.938155, 0.933611, 0, 0.0158731, 0.864755, 0.854311, 0, 0.0317461, 0.888594, 0.865264, 0, 0.0476191, 0.905575, 0.863922, 0, 0.0634921, 0.915125, 0.850558, 0, 0.0793651, 0.920665, 0.829254, 0, 0.0952381, 0.924073, 0.802578, 0, 0.111111, 0.926304, 0.772211, 0, 0.126984, 0.927829, 0.739366, 0, 0.142857, 0.928924, 0.705033, 0, 0.15873, 0.92973, 0.670019, 0, 0.174603, 0.930339, 0.634993, 0, 0.190476, 0.930811, 0.600485, 0, 0.206349, 0.931191, 0.566897, 0, 0.222222, 0.93149, 0.534485, 0, 0.238095, 0.931737, 0.503429, 0, 0.253968, 0.931939, 0.473811, 0, 0.269841, 0.932108, 0.445668, 0, 0.285714, 0.93225, 0.418993, 0, 0.301587, 0.932371, 0.393762, 0, 0.31746, 0.932474, 0.369939, 0, 0.333333, 0.932562, 0.347479, 0, 0.349206, 0.932638, 0.326336, 0, 0.365079, 0.932703, 0.306462, 0, 0.380952, 0.93276, 0.287805, 0, 0.396825, 0.932809, 0.270313, 0, 0.412698, 0.932851, 0.253933, 0, 0.428571, 0.932887, 0.23861, 0, 0.444444, 0.932917, 0.224289, 0, 0.460317, 0.932943, 0.210917, 0, 0.47619, 0.932965, 0.19844, 0, 0.492063, 0.932982, 0.186807, 0, 0.507937, 0.932995, 0.175966, 0, 0.52381, 0.933005, 0.165869, 0, 0.539683, 0.933011, 0.156468, 0, 0.555556, 0.933013, 0.147719, 0, 0.571429, 0.933013, 0.139579, 0, 0.587302, 0.93301, 0.132007, 0, 0.603175, 0.933004, 0.124965, 0, 0.619048, 0.932994, 0.118416, 0, 0.634921, 0.932982, 0.112326, 0, 0.650794, 0.932968, 0.106663, 0, 0.666667, 0.93295, 0.101397, 0, 0.68254, 0.932931, 0.0964993, 0, 0.698413, 0.932908, 0.0919438, 0, 0.714286, 0.932883, 0.0877057, 0, 0.730159, 0.932856, 0.0837623, 0, 0.746032, 0.932827, 0.0800921, 0, 0.761905, 0.932796, 0.0766754, 0, 0.777778, 0.932762, 0.0734936, 0, 0.793651, 0.932727, 0.0705296, 0, 0.809524, 0.932689, 0.0677676, 0, 0.825397, 0.93265, 0.0651929, 0, 0.84127, 0.932609, 0.0627917, 0, 0.857143, 0.932565, 0.0605515, 0, 0.873016, 0.932521, 0.0584606, 0, 0.888889, 0.932474, 0.0565082, 0, 0.904762, 0.932427, 0.0546841, 0, 0.920635, 0.932377, 0.0529793, 0, 0.936508, 0.932326, 0.0513851, 0, 0.952381, 0.932274, 0.0498936, 0, 0.968254, 0.93222, 0.0484975, 0, 0.984127, 0.932164, 0.0471899, 0, 1 ]; + const { LTC_FLOAT_1, LTC_FLOAT_2, LTC_HALF_1, LTC_HALF_2 } = RectAreaLightTexturesLib; // data textures - const ltc_float_1 = new Float32Array( LTC_MAT_1 ); - const ltc_float_2 = new Float32Array( LTC_MAT_2 ); - - UniformsLib.LTC_FLOAT_1 = new DataTexture( ltc_float_1, 64, 64, RGBAFormat, FloatType, UVMapping, ClampToEdgeWrapping, ClampToEdgeWrapping, LinearFilter, NearestFilter, 1 ); - UniformsLib.LTC_FLOAT_2 = new DataTexture( ltc_float_2, 64, 64, RGBAFormat, FloatType, UVMapping, ClampToEdgeWrapping, ClampToEdgeWrapping, LinearFilter, NearestFilter, 1 ); - - UniformsLib.LTC_FLOAT_1.needsUpdate = true; - UniformsLib.LTC_FLOAT_2.needsUpdate = true; - - const ltc_half_1 = new Uint16Array( LTC_MAT_1.length ); - - LTC_MAT_1.forEach( function ( x, index ) { - - ltc_half_1[ index ] = DataUtils.toHalfFloat( x ); - - } ); - - const ltc_half_2 = new Uint16Array( LTC_MAT_2.length ); - - LTC_MAT_2.forEach( function ( x, index ) { - - ltc_half_2[ index ] = DataUtils.toHalfFloat( x ); - - } ); - - UniformsLib.LTC_HALF_1 = new DataTexture( ltc_half_1, 64, 64, RGBAFormat, HalfFloatType, UVMapping, ClampToEdgeWrapping, ClampToEdgeWrapping, LinearFilter, NearestFilter, 1 ); - UniformsLib.LTC_HALF_2 = new DataTexture( ltc_half_2, 64, 64, RGBAFormat, HalfFloatType, UVMapping, ClampToEdgeWrapping, ClampToEdgeWrapping, LinearFilter, NearestFilter, 1 ); - - UniformsLib.LTC_HALF_1.needsUpdate = true; - UniformsLib.LTC_HALF_2.needsUpdate = true; + UniformsLib.LTC_FLOAT_1 = LTC_FLOAT_1; + UniformsLib.LTC_FLOAT_2 = LTC_FLOAT_2; + UniformsLib.LTC_HALF_1 = LTC_HALF_1; + UniformsLib.LTC_HALF_2 = LTC_HALF_2; } diff --git a/examples/jsm/lines/Line2.js b/examples/jsm/lines/Line2.js index cdd1ddfd9bd0c5..cb276221a01ab4 100644 --- a/examples/jsm/lines/Line2.js +++ b/examples/jsm/lines/Line2.js @@ -2,12 +2,49 @@ import { LineSegments2 } from '../lines/LineSegments2.js'; import { LineGeometry } from '../lines/LineGeometry.js'; import { LineMaterial } from '../lines/LineMaterial.js'; +/** + * A polyline drawn between vertices. + * + * This adds functionality beyond {@link Line}, like arbitrary line width and changing width to + * be in world units.It extends {@link LineSegments2}, simplifying constructing segments from a + * chain of points. + * + * This module can only be used with {@link WebGLRenderer}. When using {@link WebGPURenderer}, + * import the class from `lines/webgpu/Line2.js`. + * + * ```js + * const geometry = new LineGeometry(); + * geometry.setPositions( positions ); + * geometry.setColors( colors ); + * + * const material = new LineMaterial( { linewidth: 5, vertexColors: true } }; + * + * const line = new Line2( geometry, material ); + * scene.add( line ); + * ``` + * + * @augments LineSegments2 + * @three_import import { Line2 } from 'three/addons/lines/Line2.js'; + */ class Line2 extends LineSegments2 { + /** + * Constructs a new wide line. + * + * @param {LineGeometry} [geometry] - The line geometry. + * @param {LineMaterial} [material] - The line material. + */ constructor( geometry = new LineGeometry(), material = new LineMaterial( { color: Math.random() * 0xffffff } ) ) { super( geometry, material ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isLine2 = true; this.type = 'Line2'; diff --git a/examples/jsm/lines/LineGeometry.js b/examples/jsm/lines/LineGeometry.js index 1314cf6b4d7c0c..3cc55a8465e755 100644 --- a/examples/jsm/lines/LineGeometry.js +++ b/examples/jsm/lines/LineGeometry.js @@ -1,17 +1,52 @@ import { LineSegmentsGeometry } from '../lines/LineSegmentsGeometry.js'; +/** + * A chain of vertices, forming a polyline. + * + * This is used in {@link Line2} to describe the shape. + * + * ```js + * const points = [ + * new THREE.Vector3( - 10, 0, 0 ), + * new THREE.Vector3( 0, 5, 0 ), + * new THREE.Vector3( 10, 0, 0 ), + * ]; + * + * const geometry = new LineGeometry(); + * geometry.setFromPoints( points ); + * ``` + * + * @augments LineSegmentsGeometry + * @three_import import { LineLineGeometry2 } from 'three/addons/lines/LineGeometry.js'; + */ class LineGeometry extends LineSegmentsGeometry { + /** + * Constructs a new line geometry. + */ constructor() { super(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isLineGeometry = true; this.type = 'LineGeometry'; } + /** + * Sets the given line positions for this geometry. + * + * @param {Float32Array|Array} array - The position data to set. + * @return {LineGeometry} A reference to this geometry. + */ setPositions( array ) { // converts [ x1, y1, z1, x2, y2, z2, ... ] to pairs format @@ -37,6 +72,12 @@ class LineGeometry extends LineSegmentsGeometry { } + /** + * Sets the given line colors for this geometry. + * + * @param {Float32Array|Array} array - The position data to set. + * @return {LineGeometry} A reference to this geometry. + */ setColors( array ) { // converts [ r1, g1, b1, r2, g2, b2, ... ] to pairs format @@ -62,6 +103,43 @@ class LineGeometry extends LineSegmentsGeometry { } + /** + * Setups this line segments geometry from the given sequence of points. + * + * @param {Array} points - An array of points in 2D or 3D space. + * @return {LineGeometry} A reference to this geometry. + */ + setFromPoints( points ) { + + // converts a vector3 or vector2 array to pairs format + + const length = points.length - 1; + const positions = new Float32Array( 6 * length ); + + for ( let i = 0; i < length; i ++ ) { + + positions[ 6 * i ] = points[ i ].x; + positions[ 6 * i + 1 ] = points[ i ].y; + positions[ 6 * i + 2 ] = points[ i ].z || 0; + + positions[ 6 * i + 3 ] = points[ i + 1 ].x; + positions[ 6 * i + 4 ] = points[ i + 1 ].y; + positions[ 6 * i + 5 ] = points[ i + 1 ].z || 0; + + } + + super.setPositions( positions ); + + return this; + + } + + /** + * Setups this line segments geometry from the given line. + * + * @param {Line} line - The line that should be used as a data source for this geometry. + * @return {LineGeometry} A reference to this geometry. + */ fromLine( line ) { const geometry = line.geometry; diff --git a/examples/jsm/lines/LineMaterial.js b/examples/jsm/lines/LineMaterial.js index afff9b08b2a562..f14534aa964f2b 100644 --- a/examples/jsm/lines/LineMaterial.js +++ b/examples/jsm/lines/LineMaterial.js @@ -1,25 +1,11 @@ -/** - * parameters = { - * color: , - * linewidth: , - * dashed: , - * dashScale: , - * dashSize: , - * dashOffset: , - * gapSize: , - * resolution: , // to be set by renderer - * } - */ - import { ShaderLib, ShaderMaterial, UniformsLib, UniformsUtils, - Vector2 + Vector2, } from 'three'; - UniformsLib.line = { worldUnits: { value: 1 }, @@ -169,51 +155,34 @@ ShaderLib[ 'line' ] = { #ifdef WORLD_UNITS - // get the offset direction as perpendicular to the view vector vec3 worldDir = normalize( end.xyz - start.xyz ); - vec3 offset; - if ( position.y < 0.5 ) { - - offset = normalize( cross( start.xyz, worldDir ) ); - - } else { - - offset = normalize( cross( end.xyz, worldDir ) ); + vec3 tmpFwd = normalize( mix( start.xyz, end.xyz, 0.5 ) ); + vec3 worldUp = normalize( cross( worldDir, tmpFwd ) ); + vec3 worldFwd = cross( worldDir, worldUp ); + worldPos = position.y < 0.5 ? start: end; - } - - // sign flip - if ( position.x < 0.0 ) offset *= - 1.0; - - float forwardOffset = dot( worldDir, vec3( 0.0, 0.0, 1.0 ) ); + // height offset + float hw = linewidth * 0.5; + worldPos.xyz += position.x < 0.0 ? hw * worldUp : - hw * worldUp; // don't extend the line if we're rendering dashes because we // won't be rendering the endcaps #ifndef USE_DASH - // extend the line bounds to encompass endcaps - start.xyz += - worldDir * linewidth * 0.5; - end.xyz += worldDir * linewidth * 0.5; - - // shift the position of the quad so it hugs the forward edge of the line - offset.xy -= dir * forwardOffset; - offset.z += 0.5; - - #endif + // cap extension + worldPos.xyz += position.y < 0.5 ? - hw * worldDir : hw * worldDir; - // endcaps - if ( position.y > 1.0 || position.y < 0.0 ) { + // add width to the box + worldPos.xyz += worldFwd * hw; - offset.xy += dir * 2.0 * forwardOffset; + // endcaps + if ( position.y > 1.0 || position.y < 0.0 ) { - } + worldPos.xyz -= worldFwd * 2.0 * hw; - // adjust for linewidth - offset *= linewidth * 0.5; + } - // set the world position - worldPos = ( position.y < 0.5 ) ? start : end; - worldPos.xyz += offset; + #endif // project the worldpos vec4 clip = projectionMatrix * worldPos; @@ -342,6 +311,9 @@ ShaderLib[ 'line' ] = { void main() { + float alpha = opacity; + vec4 diffuseColor = vec4( diffuse, alpha ); + #include #ifdef USE_DASH @@ -352,8 +324,6 @@ ShaderLib[ 'line' ] = { #endif - float alpha = opacity; - #ifdef WORLD_UNITS // Find the closest points on the view ray and the line segment @@ -418,15 +388,13 @@ ShaderLib[ 'line' ] = { #endif - vec4 diffuseColor = vec4( diffuse, alpha ); - #include #include gl_FragColor = vec4( diffuseColor.rgb, alpha ); #include - #include + #include #include #include @@ -434,14 +402,34 @@ ShaderLib[ 'line' ] = { ` }; +/** + * A material for drawing wireframe-style geometries. + * + * Unlike {@link LineBasicMaterial}, it supports arbitrary line widths and allows using world units + * instead of screen space units. This material is used with {@link LineSegments2} and {@link Line2}. + * + * This module can only be used with {@link WebGLRenderer}. When using {@link WebGPURenderer}, + * use {@link Line2NodeMaterial}. + * + * @augments ShaderMaterial + * @three_import import { LineMaterial } from 'three/addons/lines/LineMaterial.js'; + */ class LineMaterial extends ShaderMaterial { + /** + * Constructs a new line segments geometry. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ constructor( parameters ) { super( { type: 'LineMaterial', - uniforms: UniformsUtils.clone( ShaderLib[ 'line' ].uniforms ), vertexShader: ShaderLib[ 'line' ].vertexShader, @@ -451,249 +439,256 @@ class LineMaterial extends ShaderMaterial { } ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isLineMaterial = true; - Object.defineProperties( this, { - - color: { - - enumerable: true, - - get: function () { - - return this.uniforms.diffuse.value; - - }, - - set: function ( value ) { - - this.uniforms.diffuse.value = value; - - } - - }, - - worldUnits: { - - enumerable: true, - - get: function () { - - return 'WORLD_UNITS' in this.defines; - - }, - - set: function ( value ) { - - if ( value === true ) { - - this.defines.WORLD_UNITS = ''; - - } else { - - delete this.defines.WORLD_UNITS; - - } - - } - - }, - - linewidth: { - - enumerable: true, - - get: function () { - - return this.uniforms.linewidth.value; - - }, - - set: function ( value ) { - - this.uniforms.linewidth.value = value; - - } - - }, - - dashed: { - - enumerable: true, - - get: function () { + this.setValues( parameters ); - return Boolean( 'USE_DASH' in this.defines ); + } - }, + /** + * The material's color. + * + * @type {Color} + * @default (1,1,1) + */ + get color() { - set( value ) { + return this.uniforms.diffuse.value; - if ( Boolean( value ) !== Boolean( 'USE_DASH' in this.defines ) ) { + } - this.needsUpdate = true; + set color( value ) { - } + this.uniforms.diffuse.value = value; - if ( value === true ) { + } - this.defines.USE_DASH = ''; + /** + * Whether the material's sizes (width, dash gaps) are in world units. + * + * @type {boolean} + * @default false + */ + get worldUnits() { - } else { + return 'WORLD_UNITS' in this.defines; - delete this.defines.USE_DASH; + } - } + set worldUnits( value ) { - } + if ( value === true ) { - }, + this.defines.WORLD_UNITS = ''; - dashScale: { + } else { - enumerable: true, + delete this.defines.WORLD_UNITS; - get: function () { + } - return this.uniforms.dashScale.value; + } - }, + /** + * Controls line thickness in CSS pixel units when `worldUnits` is `false` (default), + * or in world units when `worldUnits` is `true`. + * + * @type {number} + * @default 1 + */ + get linewidth() { - set: function ( value ) { + return this.uniforms.linewidth.value; - this.uniforms.dashScale.value = value; + } - } + set linewidth( value ) { - }, + if ( ! this.uniforms.linewidth ) return; + this.uniforms.linewidth.value = value; - dashSize: { + } - enumerable: true, + /** + * Whether the line is dashed, or solid. + * + * @type {boolean} + * @default false + */ + get dashed() { - get: function () { + return 'USE_DASH' in this.defines; - return this.uniforms.dashSize.value; + } - }, + set dashed( value ) { - set: function ( value ) { + if ( ( value === true ) !== this.dashed ) { - this.uniforms.dashSize.value = value; + this.needsUpdate = true; - } + } - }, + if ( value === true ) { - dashOffset: { + this.defines.USE_DASH = ''; - enumerable: true, + } else { - get: function () { + delete this.defines.USE_DASH; - return this.uniforms.dashOffset.value; + } - }, + } - set: function ( value ) { + /** + * The scale of the dashes and gaps. + * + * @type {number} + * @default 1 + */ + get dashScale() { - this.uniforms.dashOffset.value = value; + return this.uniforms.dashScale.value; - } + } - }, + set dashScale( value ) { - gapSize: { + this.uniforms.dashScale.value = value; - enumerable: true, + } - get: function () { + /** + * The size of the dash. + * + * @type {number} + * @default 1 + */ + get dashSize() { - return this.uniforms.gapSize.value; + return this.uniforms.dashSize.value; - }, + } - set: function ( value ) { + set dashSize( value ) { - this.uniforms.gapSize.value = value; + this.uniforms.dashSize.value = value; - } + } - }, + /** + * Where in the dash cycle the dash starts. + * + * @type {number} + * @default 0 + */ + get dashOffset() { - opacity: { + return this.uniforms.dashOffset.value; - enumerable: true, + } - get: function () { + set dashOffset( value ) { - return this.uniforms.opacity.value; + this.uniforms.dashOffset.value = value; - }, + } - set: function ( value ) { + /** + * The size of the gap. + * + * @type {number} + * @default 0 + */ + get gapSize() { - this.uniforms.opacity.value = value; + return this.uniforms.gapSize.value; - } + } - }, + set gapSize( value ) { - resolution: { + this.uniforms.gapSize.value = value; - enumerable: true, + } - get: function () { + /** + * The opacity. + * + * @type {number} + * @default 1 + */ + get opacity() { - return this.uniforms.resolution.value; + return this.uniforms.opacity.value; - }, + } - set: function ( value ) { + set opacity( value ) { - this.uniforms.resolution.value.copy( value ); + if ( ! this.uniforms ) return; + this.uniforms.opacity.value = value; - } + } - }, + /** + * The size of the viewport, in screen pixels. This must be kept updated to make + * screen-space rendering accurate.The `LineSegments2.onBeforeRender` callback + * performs the update for visible objects. + * + * @type {Vector2} + */ + get resolution() { - alphaToCoverage: { + return this.uniforms.resolution.value; - enumerable: true, + } - get: function () { + set resolution( value ) { - return Boolean( 'USE_ALPHA_TO_COVERAGE' in this.defines ); + this.uniforms.resolution.value.copy( value ); - }, + } - set: function ( value ) { + /** + * Whether to use alphaToCoverage or not. When enabled, this can improve the + * anti-aliasing of line edges when using MSAA. + * + * @type {boolean} + */ + get alphaToCoverage() { - if ( Boolean( value ) !== Boolean( 'USE_ALPHA_TO_COVERAGE' in this.defines ) ) { + return 'USE_ALPHA_TO_COVERAGE' in this.defines; - this.needsUpdate = true; + } - } + set alphaToCoverage( value ) { - if ( value === true ) { + if ( ! this.defines ) return; - this.defines.USE_ALPHA_TO_COVERAGE = ''; - this.extensions.derivatives = true; + if ( ( value === true ) !== this.alphaToCoverage ) { - } else { + this.needsUpdate = true; - delete this.defines.USE_ALPHA_TO_COVERAGE; - this.extensions.derivatives = false; + } - } + if ( value === true ) { - } + this.defines.USE_ALPHA_TO_COVERAGE = ''; - } + } else { - } ); + delete this.defines.USE_ALPHA_TO_COVERAGE; - this.setValues( parameters ); + } } diff --git a/examples/jsm/lines/LineSegments2.js b/examples/jsm/lines/LineSegments2.js index e2a3e2a14b9a24..268599f731fcac 100644 --- a/examples/jsm/lines/LineSegments2.js +++ b/examples/jsm/lines/LineSegments2.js @@ -13,6 +13,8 @@ import { import { LineSegmentsGeometry } from '../lines/LineSegmentsGeometry.js'; import { LineMaterial } from '../lines/LineMaterial.js'; +const _viewport = new Vector4(); + const _start = new Vector3(); const _end = new Vector3(); @@ -222,22 +224,66 @@ function raycastScreenSpace( lineSegments, camera, intersects ) { } +/** + * A series of lines drawn between pairs of vertices. + * + * This adds functionality beyond {@link LineSegments}, like arbitrary line width and changing width + * to be in world units. {@link Line2} extends this object, forming a polyline instead of individual + * segments. + * + * This module can only be used with {@link WebGLRenderer}. When using {@link WebGPURenderer}, + * import the class from `lines/webgpu/LineSegments2.js`. + * + * ```js + * const geometry = new LineSegmentsGeometry(); + * geometry.setPositions( positions ); + * geometry.setColors( colors ); + * + * const material = new LineMaterial( { linewidth: 5, vertexColors: true } }; + * + * const lineSegments = new LineSegments2( geometry, material ); + * scene.add( lineSegments ); + * ``` + * + * @augments Mesh + * @three_import import { LineSegments2 } from 'three/addons/lines/LineSegments2.js'; + */ class LineSegments2 extends Mesh { + /** + * Constructs a new wide line. + * + * @param {LineSegmentsGeometry} [geometry] - The line geometry. + * @param {LineMaterial} [material] - The line material. + */ constructor( geometry = new LineSegmentsGeometry(), material = new LineMaterial( { color: Math.random() * 0xffffff } ) ) { super( geometry, material ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isLineSegments2 = true; this.type = 'LineSegments2'; } - // for backwards-compatibility, but could be a method of LineSegmentsGeometry... - + /** + * Computes an array of distance values which are necessary for rendering dashed lines. + * For each vertex in the geometry, the method calculates the cumulative length from the + * current point to the very beginning of the line. + * + * @return {LineSegments2} A reference to this instance. + */ computeLineDistances() { + // for backwards-compatibility, but could be a method of LineSegmentsGeometry... + const geometry = this.geometry; const instanceStart = geometry.attributes.instanceStart; @@ -263,6 +309,12 @@ class LineSegments2 extends Mesh { } + /** + * Computes intersection points between a casted ray and this instance. + * + * @param {Raycaster} raycaster - The raycaster. + * @param {Array} intersects - The target array that holds the intersection points. + */ raycast( raycaster, intersects ) { const worldUnits = this.material.worldUnits; @@ -356,6 +408,19 @@ class LineSegments2 extends Mesh { } + onBeforeRender( renderer ) { + + const uniforms = this.material.uniforms; + + if ( uniforms && uniforms.resolution ) { + + renderer.getViewport( _viewport ); + this.material.uniforms.resolution.value.set( _viewport.z, _viewport.w ); + + } + + } + } export { LineSegments2 }; diff --git a/examples/jsm/lines/LineSegmentsGeometry.js b/examples/jsm/lines/LineSegmentsGeometry.js index c7cf8774bc9c41..500117bb69daeb 100644 --- a/examples/jsm/lines/LineSegmentsGeometry.js +++ b/examples/jsm/lines/LineSegmentsGeometry.js @@ -12,12 +12,30 @@ import { const _box = new Box3(); const _vector = new Vector3(); +/** + * A series of vertex pairs, forming line segments. + * + * This is used in {@link LineSegments2} to describe the shape. + * + * @augments InstancedBufferGeometry + * @three_import import { LineSegmentsGeometry } from 'three/addons/lines/LineSegmentsGeometry.js'; + */ class LineSegmentsGeometry extends InstancedBufferGeometry { + /** + * Constructs a new line segments geometry. + */ constructor() { super(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isLineSegmentsGeometry = true; this.type = 'LineSegmentsGeometry'; @@ -32,6 +50,12 @@ class LineSegmentsGeometry extends InstancedBufferGeometry { } + /** + * Applies the given 4x4 transformation matrix to the geometry. + * + * @param {Matrix4} matrix - The matrix to apply. + * @return {LineSegmentsGeometry} A reference to this instance. + */ applyMatrix4( matrix ) { const start = this.attributes.instanceStart; @@ -63,6 +87,13 @@ class LineSegmentsGeometry extends InstancedBufferGeometry { } + /** + * Sets the given line positions for this geometry. The length must be a multiple of six since + * each line segment is defined by a start end vertex in the pattern `(xyz xyz)`. + * + * @param {Float32Array|Array} array - The position data to set. + * @return {LineSegmentsGeometry} A reference to this geometry. + */ setPositions( array ) { let lineSegments; @@ -82,6 +113,8 @@ class LineSegmentsGeometry extends InstancedBufferGeometry { this.setAttribute( 'instanceStart', new InterleavedBufferAttribute( instanceBuffer, 3, 0 ) ); // xyz this.setAttribute( 'instanceEnd', new InterleavedBufferAttribute( instanceBuffer, 3, 3 ) ); // xyz + this.instanceCount = this.attributes.instanceStart.count; + // this.computeBoundingBox(); @@ -91,6 +124,13 @@ class LineSegmentsGeometry extends InstancedBufferGeometry { } + /** + * Sets the given line colors for this geometry. The length must be a multiple of six since + * each line segment is defined by a start end color in the pattern `(rgb rgb)`. + * + * @param {Float32Array|Array} array - The position data to set. + * @return {LineSegmentsGeometry} A reference to this geometry. + */ setColors( array ) { let colors; @@ -114,6 +154,12 @@ class LineSegmentsGeometry extends InstancedBufferGeometry { } + /** + * Setups this line segments geometry from the given wireframe geometry. + * + * @param {WireframeGeometry} geometry - The geometry that should be used as a data source for this geometry. + * @return {LineSegmentsGeometry} A reference to this geometry. + */ fromWireframeGeometry( geometry ) { this.setPositions( geometry.attributes.position.array ); @@ -122,6 +168,12 @@ class LineSegmentsGeometry extends InstancedBufferGeometry { } + /** + * Setups this line segments geometry from the given edges geometry. + * + * @param {EdgesGeometry} geometry - The geometry that should be used as a data source for this geometry. + * @return {LineSegmentsGeometry} A reference to this geometry. + */ fromEdgesGeometry( geometry ) { this.setPositions( geometry.attributes.position.array ); @@ -130,6 +182,12 @@ class LineSegmentsGeometry extends InstancedBufferGeometry { } + /** + * Setups this line segments geometry from the given mesh. + * + * @param {Mesh} mesh - The mesh geometry that should be used as a data source for this geometry. + * @return {LineSegmentsGeometry} A reference to this geometry. + */ fromMesh( mesh ) { this.fromWireframeGeometry( new WireframeGeometry( mesh.geometry ) ); @@ -140,6 +198,13 @@ class LineSegmentsGeometry extends InstancedBufferGeometry { } + /** + * Setups this line segments geometry from the given line segments. + * + * @param {LineSegments} lineSegments - The line segments that should be used as a data source for this geometry. + * Assumes the source geometry is not using indices. + * @return {LineSegmentsGeometry} A reference to this geometry. + */ fromLineSegments( lineSegments ) { const geometry = lineSegments.geometry; @@ -228,14 +293,6 @@ class LineSegmentsGeometry extends InstancedBufferGeometry { } - applyMatrix( matrix ) { - - console.warn( 'THREE.LineSegmentsGeometry: applyMatrix() has been renamed to applyMatrix4().' ); - - return this.applyMatrix4( matrix ); - - } - } export { LineSegmentsGeometry }; diff --git a/examples/jsm/lines/Wireframe.js b/examples/jsm/lines/Wireframe.js index cfa65aa6d62dcb..927e3e7a98492b 100644 --- a/examples/jsm/lines/Wireframe.js +++ b/examples/jsm/lines/Wireframe.js @@ -2,30 +2,69 @@ import { InstancedInterleavedBuffer, InterleavedBufferAttribute, Mesh, - Vector3 + Vector3, + Vector4 } from 'three'; import { LineSegmentsGeometry } from '../lines/LineSegmentsGeometry.js'; import { LineMaterial } from '../lines/LineMaterial.js'; const _start = new Vector3(); const _end = new Vector3(); - +const _viewport = new Vector4(); + +/** + * A class for creating wireframes based on wide lines. + * + * This module can only be used with {@link WebGLRenderer}. When using {@link WebGPURenderer}, + * import the class from `lines/webgpu/Wireframe.js`. + * + * ```js + * const geometry = new THREE.IcosahedronGeometry(); + * const wireframeGeometry = new WireframeGeometry2( geo ); + * + * const wireframe = new Wireframe( wireframeGeometry, material ); + * scene.add( wireframe ); + * ``` + * + * @augments Mesh + * @three_import import { Wireframe } from 'three/addons/lines/Wireframe.js'; + */ class Wireframe extends Mesh { + /** + * Constructs a new wireframe. + * + * @param {LineSegmentsGeometry} [geometry] - The line geometry. + * @param {LineMaterial} [material] - The line material. + */ constructor( geometry = new LineSegmentsGeometry(), material = new LineMaterial( { color: Math.random() * 0xffffff } ) ) { super( geometry, material ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isWireframe = true; this.type = 'Wireframe'; } - // for backwards-compatibility, but could be a method of LineSegmentsGeometry... - + /** + * Computes an array of distance values which are necessary for rendering dashed lines. + * For each vertex in the geometry, the method calculates the cumulative length from the + * current point to the very beginning of the line. + * + * @return {Wireframe} A reference to this instance. + */ computeLineDistances() { + // for backwards-compatibility, but could be a method of LineSegmentsGeometry... + const geometry = this.geometry; const instanceStart = geometry.attributes.instanceStart; @@ -51,6 +90,19 @@ class Wireframe extends Mesh { } + onBeforeRender( renderer ) { + + const uniforms = this.material.uniforms; + + if ( uniforms && uniforms.resolution ) { + + renderer.getViewport( _viewport ); + this.material.uniforms.resolution.value.set( _viewport.z, _viewport.w ); + + } + + } + } export { Wireframe }; diff --git a/examples/jsm/lines/WireframeGeometry2.js b/examples/jsm/lines/WireframeGeometry2.js index 07940d32e37815..82865b98205515 100644 --- a/examples/jsm/lines/WireframeGeometry2.js +++ b/examples/jsm/lines/WireframeGeometry2.js @@ -3,12 +3,37 @@ import { } from 'three'; import { LineSegmentsGeometry } from '../lines/LineSegmentsGeometry.js'; +/** + * A special type of line segments geometry intended for wireframe rendering. + * + * This is used in {@link Wireframe} to describe the shape. + * + * ```js + * const geometry = new THREE.IcosahedronGeometry(); + * const wireframeGeometry = new WireframeGeometry2( geo ); + * ``` + * + * @augments LineSegmentsGeometry + * @three_import import { WireframeGeometry2 } from 'three/addons/lines/WireframeGeometry2.js'; + */ class WireframeGeometry2 extends LineSegmentsGeometry { + /** + * Constructs a new wireframe geometry. + * + * @param {BufferGeometry} [geometry] - The geometry to render the wireframe for. + */ constructor( geometry ) { super(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isWireframeGeometry2 = true; this.type = 'WireframeGeometry2'; diff --git a/examples/jsm/lines/webgpu/Line2.js b/examples/jsm/lines/webgpu/Line2.js new file mode 100644 index 00000000000000..ea8eed842dee5e --- /dev/null +++ b/examples/jsm/lines/webgpu/Line2.js @@ -0,0 +1,46 @@ +import { Line2NodeMaterial } from 'three/webgpu'; + +import { LineSegments2 } from './LineSegments2.js'; +import { LineGeometry } from '../LineGeometry.js'; + +/** + * A polyline drawn between vertices. + * + * This adds functionality beyond {@link Line}, like arbitrary line width and changing width to + * be in world units.It extends {@link LineSegments2}, simplifying constructing segments from a + * chain of points. + * + * This module can only be used with {@link WebGPURenderer}. When using {@link WebGLRenderer}, + * import the class from `lines/Line2.js`. + * + * @augments LineSegments2 + * @three_import import { Line2 } from 'three/addons/lines/webgpu/Line2.js'; + */ +class Line2 extends LineSegments2 { + + /** + * Constructs a new wide line. + * + * @param {LineGeometry} [geometry] - The line geometry. + * @param {Line2NodeMaterial} [material] - The line material. + */ + constructor( geometry = new LineGeometry(), material = new Line2NodeMaterial( { color: Math.random() * 0xffffff } ) ) { + + super( geometry, material ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLine2 = true; + + this.type = 'Line2'; + + } + +} + +export { Line2 }; diff --git a/examples/jsm/lines/webgpu/LineSegments2.js b/examples/jsm/lines/webgpu/LineSegments2.js new file mode 100644 index 00000000000000..7252718d2cd082 --- /dev/null +++ b/examples/jsm/lines/webgpu/LineSegments2.js @@ -0,0 +1,411 @@ +import { + Box3, + InstancedInterleavedBuffer, + InterleavedBufferAttribute, + Line3, + MathUtils, + Matrix4, + Mesh, + Sphere, + Vector3, + Vector4, + Line2NodeMaterial, + Vector2 +} from 'three/webgpu'; +import { LineSegmentsGeometry } from '../../lines/LineSegmentsGeometry.js'; + +const _start = new Vector3(); +const _end = new Vector3(); + +const _start4 = new Vector4(); +const _end4 = new Vector4(); + +const _ssOrigin = new Vector4(); +const _ssOrigin3 = new Vector3(); +const _mvMatrix = new Matrix4(); +const _line = new Line3(); +const _closestPoint = new Vector3(); + +const _box = new Box3(); +const _sphere = new Sphere(); +const _clipToWorldVector = new Vector4(); +const _viewport = new Vector4(); + +let _ray, _lineWidth; + +// Returns the margin required to expand by in world space given the distance from the camera, +// line width, resolution, and camera projection +function getWorldSpaceHalfWidth( camera, distance, resolution ) { + + // transform into clip space, adjust the x and y values by the pixel width offset, then + // transform back into world space to get world offset. Note clip space is [-1, 1] so full + // width does not need to be halved. + _clipToWorldVector.set( 0, 0, - distance, 1.0 ).applyMatrix4( camera.projectionMatrix ); + _clipToWorldVector.multiplyScalar( 1.0 / _clipToWorldVector.w ); + _clipToWorldVector.x = _lineWidth / resolution.width; + _clipToWorldVector.y = _lineWidth / resolution.height; + _clipToWorldVector.applyMatrix4( camera.projectionMatrixInverse ); + _clipToWorldVector.multiplyScalar( 1.0 / _clipToWorldVector.w ); + + return Math.abs( Math.max( _clipToWorldVector.x, _clipToWorldVector.y ) ); + +} + +function raycastWorldUnits( lineSegments, intersects ) { + + const matrixWorld = lineSegments.matrixWorld; + const geometry = lineSegments.geometry; + const instanceStart = geometry.attributes.instanceStart; + const instanceEnd = geometry.attributes.instanceEnd; + const segmentCount = Math.min( geometry.instanceCount, instanceStart.count ); + + for ( let i = 0, l = segmentCount; i < l; i ++ ) { + + _line.start.fromBufferAttribute( instanceStart, i ); + _line.end.fromBufferAttribute( instanceEnd, i ); + + _line.applyMatrix4( matrixWorld ); + + const pointOnLine = new Vector3(); + const point = new Vector3(); + + _ray.distanceSqToSegment( _line.start, _line.end, point, pointOnLine ); + const isInside = point.distanceTo( pointOnLine ) < _lineWidth * 0.5; + + if ( isInside ) { + + intersects.push( { + point, + pointOnLine, + distance: _ray.origin.distanceTo( point ), + object: lineSegments, + face: null, + faceIndex: i, + uv: null, + uv1: null, + } ); + + } + + } + +} + +function raycastScreenSpace( lineSegments, camera, intersects ) { + + const projectionMatrix = camera.projectionMatrix; + const matrixWorld = lineSegments.matrixWorld; + + const resolution = lineSegments._resolution; + + const geometry = lineSegments.geometry; + const instanceStart = geometry.attributes.instanceStart; + const instanceEnd = geometry.attributes.instanceEnd; + const segmentCount = Math.min( geometry.instanceCount, instanceStart.count ); + + const near = - camera.near; + + // + + // pick a point 1 unit out along the ray to avoid the ray origin + // sitting at the camera origin which will cause "w" to be 0 when + // applying the projection matrix. + _ray.at( 1, _ssOrigin ); + + // ndc space [ - 1.0, 1.0 ] + _ssOrigin.w = 1; + _ssOrigin.applyMatrix4( camera.matrixWorldInverse ); + _ssOrigin.applyMatrix4( projectionMatrix ); + _ssOrigin.multiplyScalar( 1 / _ssOrigin.w ); + + // screen space + _ssOrigin.x *= resolution.x / 2; + _ssOrigin.y *= resolution.y / 2; + _ssOrigin.z = 0; + + _ssOrigin3.copy( _ssOrigin ); + + _mvMatrix.multiplyMatrices( camera.matrixWorldInverse, matrixWorld ); + + for ( let i = 0, l = segmentCount; i < l; i ++ ) { + + _start4.fromBufferAttribute( instanceStart, i ); + _end4.fromBufferAttribute( instanceEnd, i ); + + _start4.w = 1; + _end4.w = 1; + + // camera space + _start4.applyMatrix4( _mvMatrix ); + _end4.applyMatrix4( _mvMatrix ); + + // skip the segment if it's entirely behind the camera + const isBehindCameraNear = _start4.z > near && _end4.z > near; + if ( isBehindCameraNear ) { + + continue; + + } + + // trim the segment if it extends behind camera near + if ( _start4.z > near ) { + + const deltaDist = _start4.z - _end4.z; + const t = ( _start4.z - near ) / deltaDist; + _start4.lerp( _end4, t ); + + } else if ( _end4.z > near ) { + + const deltaDist = _end4.z - _start4.z; + const t = ( _end4.z - near ) / deltaDist; + _end4.lerp( _start4, t ); + + } + + // clip space + _start4.applyMatrix4( projectionMatrix ); + _end4.applyMatrix4( projectionMatrix ); + + // ndc space [ - 1.0, 1.0 ] + _start4.multiplyScalar( 1 / _start4.w ); + _end4.multiplyScalar( 1 / _end4.w ); + + // screen space + _start4.x *= resolution.x / 2; + _start4.y *= resolution.y / 2; + + _end4.x *= resolution.x / 2; + _end4.y *= resolution.y / 2; + + // create 2d segment + _line.start.copy( _start4 ); + _line.start.z = 0; + + _line.end.copy( _end4 ); + _line.end.z = 0; + + // get closest point on ray to segment + const param = _line.closestPointToPointParameter( _ssOrigin3, true ); + _line.at( param, _closestPoint ); + + // check if the intersection point is within clip space + const zPos = MathUtils.lerp( _start4.z, _end4.z, param ); + const isInClipSpace = zPos >= - 1 && zPos <= 1; + + const isInside = _ssOrigin3.distanceTo( _closestPoint ) < _lineWidth * 0.5; + + if ( isInClipSpace && isInside ) { + + _line.start.fromBufferAttribute( instanceStart, i ); + _line.end.fromBufferAttribute( instanceEnd, i ); + + _line.start.applyMatrix4( matrixWorld ); + _line.end.applyMatrix4( matrixWorld ); + + const pointOnLine = new Vector3(); + const point = new Vector3(); + + _ray.distanceSqToSegment( _line.start, _line.end, point, pointOnLine ); + + intersects.push( { + point: point, + pointOnLine: pointOnLine, + distance: _ray.origin.distanceTo( point ), + object: lineSegments, + face: null, + faceIndex: i, + uv: null, + uv1: null, + } ); + + } + + } + +} + +/** + * A series of lines drawn between pairs of vertices. + * + * This adds functionality beyond {@link LineSegments}, like arbitrary line width and changing width + * to be in world units. {@link Line2} extends this object, forming a polyline instead of individual + * segments. + * + * This module can only be used with {@link WebGPURenderer}. When using {@link WebGLRenderer}, + * import the class from `lines/LineSegments2.js`. + * + * @augments Mesh + * @three_import import { LineSegments2 } from 'three/addons/lines/webgpu/LineSegments2.js'; + */ +class LineSegments2 extends Mesh { + + /** + * Constructs a new wide line. + * + * @param {LineSegmentsGeometry} [geometry] - The line geometry. + * @param {Line2NodeMaterial} [material] - The line material. + */ + constructor( geometry = new LineSegmentsGeometry(), material = new Line2NodeMaterial( { color: Math.random() * 0xffffff } ) ) { + + super( geometry, material ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLineSegments2 = true; + + this.type = 'LineSegments2'; + + this._resolution = new Vector2(); + + } + + /** + * Computes an array of distance values which are necessary for rendering dashed lines. + * For each vertex in the geometry, the method calculates the cumulative length from the + * current point to the very beginning of the line. + * + * @return {LineSegments2} A reference to this instance. + */ + computeLineDistances() { + + // for backwards-compatibility, but could be a method of LineSegmentsGeometry... + + const geometry = this.geometry; + + const instanceStart = geometry.attributes.instanceStart; + const instanceEnd = geometry.attributes.instanceEnd; + const lineDistances = new Float32Array( 2 * instanceStart.count ); + + for ( let i = 0, j = 0, l = instanceStart.count; i < l; i ++, j += 2 ) { + + _start.fromBufferAttribute( instanceStart, i ); + _end.fromBufferAttribute( instanceEnd, i ); + + lineDistances[ j ] = ( j === 0 ) ? 0 : lineDistances[ j - 1 ]; + lineDistances[ j + 1 ] = lineDistances[ j ] + _start.distanceTo( _end ); + + } + + const instanceDistanceBuffer = new InstancedInterleavedBuffer( lineDistances, 2, 1 ); // d0, d1 + + geometry.setAttribute( 'instanceDistanceStart', new InterleavedBufferAttribute( instanceDistanceBuffer, 1, 0 ) ); // d0 + geometry.setAttribute( 'instanceDistanceEnd', new InterleavedBufferAttribute( instanceDistanceBuffer, 1, 1 ) ); // d1 + + return this; + + } + + onBeforeRender( renderer ) { + + renderer.getViewport( _viewport ); + this._resolution.set( _viewport.z, _viewport.w ); + + } + + /** + * Computes intersection points between a casted ray and this instance. + * + * @param {Raycaster} raycaster - The raycaster. + * @param {Array} intersects - The target array that holds the intersection points. + */ + raycast( raycaster, intersects ) { + + const worldUnits = this.material.worldUnits; + const camera = raycaster.camera; + + if ( camera === null && ! worldUnits ) { + + console.error( 'LineSegments2: "Raycaster.camera" needs to be set in order to raycast against LineSegments2 while worldUnits is set to false.' ); + + } + + const threshold = ( raycaster.params.Line2 !== undefined ) ? raycaster.params.Line2.threshold || 0 : 0; + + _ray = raycaster.ray; + + const matrixWorld = this.matrixWorld; + const geometry = this.geometry; + const material = this.material; + + _lineWidth = material.linewidth + threshold; + + // check if we intersect the sphere bounds + if ( geometry.boundingSphere === null ) { + + geometry.computeBoundingSphere(); + + } + + _sphere.copy( geometry.boundingSphere ).applyMatrix4( matrixWorld ); + + // increase the sphere bounds by the worst case line screen space width + let sphereMargin; + if ( worldUnits ) { + + sphereMargin = _lineWidth * 0.5; + + } else { + + const distanceToSphere = Math.max( camera.near, _sphere.distanceToPoint( _ray.origin ) ); + sphereMargin = getWorldSpaceHalfWidth( camera, distanceToSphere, this._resolution ); + + } + + _sphere.radius += sphereMargin; + + if ( _ray.intersectsSphere( _sphere ) === false ) { + + return; + + } + + // check if we intersect the box bounds + if ( geometry.boundingBox === null ) { + + geometry.computeBoundingBox(); + + } + + _box.copy( geometry.boundingBox ).applyMatrix4( matrixWorld ); + + // increase the box bounds by the worst case line width + let boxMargin; + if ( worldUnits ) { + + boxMargin = _lineWidth * 0.5; + + } else { + + const distanceToBox = Math.max( camera.near, _box.distanceToPoint( _ray.origin ) ); + boxMargin = getWorldSpaceHalfWidth( camera, distanceToBox, this._resolution ); + + } + + _box.expandByScalar( boxMargin ); + + if ( _ray.intersectsBox( _box ) === false ) { + + return; + + } + + if ( worldUnits ) { + + raycastWorldUnits( this, intersects ); + + } else { + + raycastScreenSpace( this, camera, intersects ); + + } + + } + +} + +export { LineSegments2 }; diff --git a/examples/jsm/lines/webgpu/Wireframe.js b/examples/jsm/lines/webgpu/Wireframe.js new file mode 100644 index 00000000000000..10bf86a96e5086 --- /dev/null +++ b/examples/jsm/lines/webgpu/Wireframe.js @@ -0,0 +1,86 @@ +import { + InstancedInterleavedBuffer, + InterleavedBufferAttribute, + Line2NodeMaterial, + Mesh, + Vector3 +} from 'three/webgpu'; + +import { LineSegmentsGeometry } from '../../lines/LineSegmentsGeometry.js'; + +const _start = new Vector3(); +const _end = new Vector3(); + +/** + * A class for creating wireframes based on wide lines. + * + * This module can only be used with {@link WebGPURenderer}. When using {@link WebGLRenderer}, + * import the class from `lines/Wireframe.js`. + * + * @augments Mesh + * @three_import import { Wireframe } from 'three/addons/lines/webgpu/Wireframe.js'; + */ +class Wireframe extends Mesh { + + /** + * Constructs a new wireframe. + * + * @param {LineSegmentsGeometry} [geometry] - The line geometry. + * @param {Line2NodeMaterial} [material] - The line material. + */ + constructor( geometry = new LineSegmentsGeometry(), material = new Line2NodeMaterial( { color: Math.random() * 0xffffff } ) ) { + + super( geometry, material ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isWireframe = true; + + this.type = 'Wireframe'; + + } + + /** + * Computes an array of distance values which are necessary for rendering dashed lines. + * For each vertex in the geometry, the method calculates the cumulative length from the + * current point to the very beginning of the line. + * + * @return {Wireframe} A reference to this instance. + */ + computeLineDistances() { + + // for backwards-compatibility, but could be a method of LineSegmentsGeometry... + + const geometry = this.geometry; + + const instanceStart = geometry.attributes.instanceStart; + const instanceEnd = geometry.attributes.instanceEnd; + const lineDistances = new Float32Array( 2 * instanceStart.count ); + + for ( let i = 0, j = 0, l = instanceStart.count; i < l; i ++, j += 2 ) { + + _start.fromBufferAttribute( instanceStart, i ); + _end.fromBufferAttribute( instanceEnd, i ); + + lineDistances[ j ] = ( j === 0 ) ? 0 : lineDistances[ j - 1 ]; + lineDistances[ j + 1 ] = lineDistances[ j ] + _start.distanceTo( _end ); + + } + + const instanceDistanceBuffer = new InstancedInterleavedBuffer( lineDistances, 2, 1 ); // d0, d1 + + geometry.setAttribute( 'instanceDistanceStart', new InterleavedBufferAttribute( instanceDistanceBuffer, 1, 0 ) ); // d0 + geometry.setAttribute( 'instanceDistanceEnd', new InterleavedBufferAttribute( instanceDistanceBuffer, 1, 1 ) ); // d1 + + return this; + + } + +} + +export { Wireframe }; diff --git a/examples/jsm/loaders/3DMLoader.js b/examples/jsm/loaders/3DMLoader.js index 1f81dd27ea1745..08dcee508d297b 100644 --- a/examples/jsm/loaders/3DMLoader.js +++ b/examples/jsm/loaders/3DMLoader.js @@ -1,38 +1,68 @@ import { BufferGeometryLoader, - FileLoader, - Loader, - Object3D, - MeshStandardMaterial, - Mesh, + CanvasTexture, + ClampToEdgeWrapping, Color, - Points, - PointsMaterial, + DirectionalLight, + DoubleSide, + FileLoader, + LinearFilter, Line, LineBasicMaterial, + Loader, Matrix4, - DirectionalLight, + Mesh, + MeshPhysicalMaterial, + MeshStandardMaterial, + Object3D, PointLight, - SpotLight, + Points, + PointsMaterial, RectAreaLight, + RepeatWrapping, + SpotLight, Sprite, SpriteMaterial, - CanvasTexture, - LinearFilter, - ClampToEdgeWrapping, - RepeatWrapping, - TextureLoader, - DoubleSide + TextureLoader } from 'three'; +import { EXRLoader } from '../loaders/EXRLoader.js'; + const _taskCache = new WeakMap(); +/** + * A loader for Rhinoceros 3D files and objects. + * + * Rhinoceros is a 3D modeler used to create, edit, analyze, document, render, + * animate, and translate NURBS curves, surfaces, breps, extrusions, point clouds, + * as well as polygon meshes and SubD objects. `rhino3dm.js` is compiled to WebAssembly + * from the open source geometry library `openNURBS`. The loader currently uses + * `rhino3dm.js 8.4.0`. + * + * ```js + * const loader = new Rhino3dmLoader(); + * loader.setLibraryPath( 'https://cdn.jsdelivr.net/npm/rhino3dm@8.0.1' ); + * + * const object = await loader.loadAsync( 'models/3dm/Rhino_Logo.3dm' ); + * scene.add( object ); + * ``` + * + * @augments Loader + * @three_import import { Rhino3dmLoader } from 'three/addons/loaders/3DMLoader.js'; + */ class Rhino3dmLoader extends Loader { + /** + * Constructs a new Rhino 3DM loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); + // internals + this.libraryPath = ''; this.libraryPending = null; this.libraryBinary = null; @@ -51,6 +81,12 @@ class Rhino3dmLoader extends Loader { } + /** + * Path to a folder containing the JS and WASM libraries. + * + * @param {string} path - The library path to set. + * @return {Rhino3dmLoader} A reference to this loader. + */ setLibraryPath( path ) { this.libraryPath = path; @@ -59,6 +95,14 @@ class Rhino3dmLoader extends Loader { } + /** + * Sets the maximum number of Web Workers to be used during decoding. + * A lower limit may be preferable if workers are also for other + * tasks in the application. + * + * @param {number} workerLimit - The worker limit. + * @return {Rhino3dmLoader} A reference to this loader. + */ setWorkerLimit( workerLimit ) { this.workerLimit = workerLimit; @@ -67,6 +111,15 @@ class Rhino3dmLoader extends Loader { } + /** + * Starts loading from the given URL and passes the loaded 3DM asset + * to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(Object3D)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ load( url, onLoad, onProgress, onError ) { const loader = new FileLoader( this.manager ); @@ -102,12 +155,22 @@ class Rhino3dmLoader extends Loader { } + /** + * Prints debug messages to the browser console. + */ debug() { console.log( 'Task load: ', this.workerPool.map( ( worker ) => worker._taskLoad ) ); } + /** + * Decodes the 3DM asset data with a Web Worker. + * + * @param {ArrayBuffer} buffer - The raw 3DM asset data as an array buffer. + * @param {string} url - The asset URL. + * @return {Promise} A Promise that resolved with the decoded 3D object. + */ decodeObjects( buffer, url ) { let worker; @@ -167,6 +230,14 @@ class Rhino3dmLoader extends Loader { } + /** + * Parses the given 3DM data and passes the loaded 3DM asset + * to the `onLoad()` callback. + * + * @param {ArrayBuffer} data - The raw 3DM asset data as an array buffer. + * @param {function(Object3D)} onLoad - Executed when the loading process has been finished. + * @param {onErrorCallback} onError - Executed when errors occur. + */ parse( data, onLoad, onError ) { this.decodeObjects( data, '' ) @@ -189,6 +260,7 @@ class Rhino3dmLoader extends Loader { mat.color.g = material.color.g; mat.color.b = material.color.b; mat.type = material.type; + mat.vertexColors = material.vertexColors; const json = JSON.stringify( mat ); @@ -202,6 +274,7 @@ class Rhino3dmLoader extends Loader { _mat.color.g = m.color.g; _mat.color.b = m.color.b; _mat.type = m.type; + _mat.vertexColors = m.vertexColors; if ( JSON.stringify( _mat ) === json ) { @@ -217,40 +290,64 @@ class Rhino3dmLoader extends Loader { } - _createMaterial( material ) { + _createMaterial( material, renderEnvironment ) { if ( material === undefined ) { return new MeshStandardMaterial( { color: new Color( 1, 1, 1 ), metalness: 0.8, - name: 'default', + name: Loader.DEFAULT_MATERIAL_NAME, side: DoubleSide } ); } - const _diffuseColor = material.diffuseColor; + //console.log(material) + + const mat = new MeshPhysicalMaterial( { - const diffusecolor = new Color( _diffuseColor.r / 255.0, _diffuseColor.g / 255.0, _diffuseColor.b / 255.0 ); + color: new Color( material.diffuseColor.r / 255.0, material.diffuseColor.g / 255.0, material.diffuseColor.b / 255.0 ), + emissive: new Color( material.emissionColor.r, material.emissionColor.g, material.emissionColor.b ), + flatShading: material.disableLighting, + ior: material.indexOfRefraction, + name: material.name, + reflectivity: material.reflectivity, + opacity: 1.0 - material.transparency, + side: DoubleSide, + specularColor: material.specularColor, + transparent: material.transparency > 0 ? true : false + + } ); - if ( _diffuseColor.r === 0 && _diffuseColor.g === 0 && _diffuseColor.b === 0 ) { + mat.userData.id = material.id; - diffusecolor.r = 1; - diffusecolor.g = 1; - diffusecolor.b = 1; + if ( material.pbrSupported ) { + + const pbr = material.pbr; + + mat.anisotropy = pbr.anisotropic; + mat.anisotropyRotation = pbr.anisotropicRotation; + mat.color = new Color( pbr.baseColor.r, pbr.baseColor.g, pbr.baseColor.b ); + mat.clearcoat = pbr.clearcoat; + mat.clearcoatRoughness = pbr.clearcoatRoughness; + mat.metalness = pbr.metallic; + mat.transmission = 1 - pbr.opacity; + mat.roughness = pbr.roughness; + mat.sheen = pbr.sheen; + mat.specularIntensity = pbr.specular; + mat.thickness = pbr.subsurface; } - // console.log( material ); + if ( material.pbrSupported && material.pbr.opacity === 0 && material.transparency === 1 ) { - const mat = new MeshStandardMaterial( { - color: diffusecolor, - name: material.name, - side: DoubleSide, - transparent: material.transparency > 0 ? true : false, - opacity: 1.0 - material.transparency - } ); + //some compromises + + mat.opacity = 0.2; + mat.transmission = 1.00; + + } const textureLoader = new TextureLoader(); @@ -262,17 +359,31 @@ class Rhino3dmLoader extends Loader { const map = textureLoader.load( texture.image ); + //console.log(texture.type ) + switch ( texture.type ) { + case 'Bump': + + mat.bumpMap = map; + + break; + case 'Diffuse': mat.map = map; break; - case 'Bump': + case 'Emap': - mat.bumpMap = map; + mat.envMap = map; + + break; + + case 'Opacity': + + mat.transmissionMap = map; break; @@ -283,9 +394,97 @@ class Rhino3dmLoader extends Loader { break; - case 'Emap': + case 'PBR_Alpha': - mat.envMap = map; + mat.alphaMap = map; + mat.transparent = true; + + break; + + case 'PBR_AmbientOcclusion': + + mat.aoMap = map; + + break; + + case 'PBR_Anisotropic': + + mat.anisotropyMap = map; + + break; + + case 'PBR_BaseColor': + + mat.map = map; + + break; + + case 'PBR_Clearcoat': + + mat.clearcoatMap = map; + + break; + + case 'PBR_ClearcoatBump': + + mat.clearcoatNormalMap = map; + + break; + + case 'PBR_ClearcoatRoughness': + + mat.clearcoatRoughnessMap = map; + + break; + + case 'PBR_Displacement': + + mat.displacementMap = map; + + break; + + case 'PBR_Emission': + + mat.emissiveMap = map; + + break; + + case 'PBR_Metallic': + + mat.metalnessMap = map; + + break; + + case 'PBR_Roughness': + + mat.roughnessMap = map; + + break; + + case 'PBR_Sheen': + + mat.sheenColorMap = map; + + break; + + case 'PBR_Specular': + + mat.specularColorMap = map; + + break; + + case 'PBR_Subsurface': + + mat.thicknessMap = map; + + break; + + default: + + this.warnings.push( { + message: `THREE.3DMLoader: No conversion exists for 3dm ${texture.type}.`, + type: 'no conversion' + } ); break; @@ -293,20 +492,34 @@ class Rhino3dmLoader extends Loader { map.wrapS = texture.wrapU === 0 ? RepeatWrapping : ClampToEdgeWrapping; map.wrapT = texture.wrapV === 0 ? RepeatWrapping : ClampToEdgeWrapping; - map.repeat.set( texture.repeat[ 0 ], texture.repeat[ 1 ] ); + + if ( texture.repeat ) { + + map.repeat.set( texture.repeat[ 0 ], texture.repeat[ 1 ] ); + + } } } + if ( renderEnvironment ) { + + new EXRLoader().load( renderEnvironment.image, function ( texture ) { + + texture.mapping = THREE.EquirectangularReflectionMapping; + mat.envMap = texture; + + } ); + + } + return mat; } _createGeometry( data ) { - // console.log(data); - const object = new Object3D(); const instanceDefinitionObjects = []; const instanceDefinitions = []; @@ -315,8 +528,10 @@ class Rhino3dmLoader extends Loader { object.userData[ 'layers' ] = data.layers; object.userData[ 'groups' ] = data.groups; object.userData[ 'settings' ] = data.settings; + object.userData.settings[ 'renderSettings' ] = data.renderSettings; object.userData[ 'objectType' ] = 'File3dm'; object.userData[ 'materials' ] = null; + object.name = this.url; let objects = data.objects; @@ -343,22 +558,44 @@ class Rhino3dmLoader extends Loader { default: - let _object; + let matId = null; - if ( attributes.materialIndex >= 0 ) { + switch ( attributes.materialSource.name ) { - const rMaterial = materials[ attributes.materialIndex ]; - let material = this._createMaterial( rMaterial ); - material = this._compareMaterials( material ); - _object = this._createObject( obj, material ); + case 'ObjectMaterialSource_MaterialFromLayer': + //check layer index + if ( attributes.layerIndex >= 0 ) { - } else { + matId = data.layers[ attributes.layerIndex ].renderMaterialIndex; + + } + + break; + + case 'ObjectMaterialSource_MaterialFromObject': + + if ( attributes.materialIndex >= 0 ) { + + matId = attributes.materialIndex; + + } + + break; + + } + + let material = null; + + if ( matId >= 0 ) { + + const rMaterial = materials[ matId ]; + material = this._createMaterial( rMaterial, data.renderEnvironment ); - const material = this._createMaterial(); - _object = this._createObject( obj, material ); } + const _object = this._createObject( obj, material ); + if ( _object === undefined ) { continue; @@ -440,6 +677,7 @@ class Rhino3dmLoader extends Loader { } object.userData[ 'materials' ] = this.materials; + object.name = ''; return object; } @@ -494,19 +732,22 @@ class Rhino3dmLoader extends Loader { geometry = loader.parse( obj.geometry ); - if ( geometry.attributes.hasOwnProperty( 'color' ) ) { - mat.vertexColors = true; + if ( mat === null ) { + + mat = this._createMaterial(); } - if ( mat === null ) { - mat = this._createMaterial(); - mat = this._compareMaterials( mat ); + if ( geometry.attributes.hasOwnProperty( 'color' ) ) { + + mat.vertexColors = true; } + mat = this._compareMaterials( mat ); + const mesh = new Mesh( geometry, mat ); mesh.castShadow = attributes.castsShadows; mesh.receiveShadow = attributes.receivesShadows; @@ -572,6 +813,7 @@ class Rhino3dmLoader extends Loader { const texture = new CanvasTexture( ctx.canvas ); texture.minFilter = LinearFilter; + texture.generateMipmaps = false; texture.wrapS = ClampToEdgeWrapping; texture.wrapT = ClampToEdgeWrapping; @@ -642,7 +884,7 @@ class Rhino3dmLoader extends Loader { break; case 'LightStyle_WorldLinear': - // not conversion exists, warning has already been printed to the console + // no conversion exists, warning has already been printed to the console break; default: @@ -788,6 +1030,10 @@ class Rhino3dmLoader extends Loader { } + /** + * Frees internal resources. This method should be called + * when the loader is no longer required. + */ dispose() { for ( let i = 0; i < this.workerPool.length; ++ i ) { @@ -798,8 +1044,6 @@ class Rhino3dmLoader extends Loader { this.workerPool.length = 0; - return this; - } } @@ -821,7 +1065,6 @@ function Rhino3dmWorker() { case 'init': - // console.log(message) libraryConfig = message.libraryConfig; const wasmBinary = libraryConfig.wasmBinary; let RhinoModule; @@ -902,7 +1145,7 @@ function Rhino3dmWorker() { // Handle instance definitions // console.log( `Instance Definitions Count: ${doc.instanceDefinitions().count()}` ); - for ( let i = 0; i < doc.instanceDefinitions().count(); i ++ ) { + for ( let i = 0; i < doc.instanceDefinitions().count; i ++ ) { const idef = doc.instanceDefinitions().get( i ); const idefAttributes = extractProperties( idef ); @@ -946,94 +1189,36 @@ function Rhino3dmWorker() { rhino.TextureType.PBR_Displacement ]; - for ( let i = 0; i < doc.materials().count(); i ++ ) { + for ( let i = 0; i < doc.materials().count; i ++ ) { const _material = doc.materials().get( i ); - const _pbrMaterial = _material.physicallyBased(); - let material = extractProperties( _material ); + const material = extractProperties( _material ); const textures = []; - for ( let j = 0; j < textureTypes.length; j ++ ) { - - const _texture = _material.getTexture( textureTypes[ j ] ); - if ( _texture ) { - - let textureType = textureTypes[ j ].constructor.name; - textureType = textureType.substring( 12, textureType.length ); - const texture = { type: textureType }; + textures.push( ...extractTextures( _material, textureTypes, doc ) ); - const image = doc.getEmbeddedFileAsBase64( _texture.fileName ); + material.pbrSupported = _material.physicallyBased().supported; - texture.wrapU = _texture.wrapU; - texture.wrapV = _texture.wrapV; - texture.wrapW = _texture.wrapW; - const uvw = _texture.uvwTransform.toFloatArray( true ); - texture.repeat = [ uvw[ 0 ], uvw[ 5 ] ]; + if ( material.pbrSupported ) { - if ( image ) { - - texture.image = 'data:image/png;base64,' + image; - - } else { - - self.postMessage( { type: 'warning', id: taskID, data: { - message: `THREE.3DMLoader: Image for ${textureType} texture not embedded in file.`, - type: 'missing resource' - } - - } ); - - texture.image = null; - - } - - textures.push( texture ); - - _texture.delete(); - - } + textures.push( ...extractTextures( _material, pbrTextureTypes, doc ) ); + material.pbr = extractProperties( _material.physicallyBased() ); } material.textures = textures; - if ( _pbrMaterial.supported ) { - - for ( let j = 0; j < pbrTextureTypes.length; j ++ ) { - - const _texture = _material.getTexture( pbrTextureTypes[ j ] ); - if ( _texture ) { - - const image = doc.getEmbeddedFileAsBase64( _texture.fileName ); - let textureType = pbrTextureTypes[ j ].constructor.name; - textureType = textureType.substring( 12, textureType.length ); - const texture = { type: textureType, image: 'data:image/png;base64,' + image }; - textures.push( texture ); - - _texture.delete(); - - } - - } - - const pbMaterialProperties = extractProperties( _material.physicallyBased() ); - - material = Object.assign( pbMaterialProperties, material ); - - } - materials.push( material ); _material.delete(); - _pbrMaterial.delete(); } // Handle layers - for ( let i = 0; i < doc.layers().count(); i ++ ) { + for ( let i = 0; i < doc.layers().count; i ++ ) { const _layer = doc.layers().get( i ); const layer = extractProperties( _layer ); @@ -1046,7 +1231,7 @@ function Rhino3dmWorker() { // Handle views - for ( let i = 0; i < doc.views().count(); i ++ ) { + for ( let i = 0; i < doc.views().count; i ++ ) { const _view = doc.views().get( i ); const view = extractProperties( _view ); @@ -1059,7 +1244,7 @@ function Rhino3dmWorker() { // Handle named views - for ( let i = 0; i < doc.namedViews().count(); i ++ ) { + for ( let i = 0; i < doc.namedViews().count; i ++ ) { const _namedView = doc.namedViews().get( i ); const namedView = extractProperties( _namedView ); @@ -1072,7 +1257,7 @@ function Rhino3dmWorker() { // Handle groups - for ( let i = 0; i < doc.groups().count(); i ++ ) { + for ( let i = 0; i < doc.groups().count; i ++ ) { const _group = doc.groups().get( i ); const group = extractProperties( _group ); @@ -1098,9 +1283,9 @@ function Rhino3dmWorker() { // Handle strings // console.log( `Document Strings Count: ${doc.strings().count()}` ); // Note: doc.strings().documentUserTextCount() counts any doc.strings defined in a section - //console.log( `Document User Text Count: ${doc.strings().documentUserTextCount()}` ); + // console.log( `Document User Text Count: ${doc.strings().documentUserTextCount()}` ); - const strings_count = doc.strings().count(); + const strings_count = doc.strings().count; for ( let i = 0; i < strings_count; i ++ ) { @@ -1108,9 +1293,146 @@ function Rhino3dmWorker() { } + // Handle Render Environments for Material Environment + + // get the id of the active render environment skylight, which we'll use for environment texture + const reflectionId = doc.settings().renderSettings().renderEnvironments.reflectionId; + + const rc = doc.renderContent(); + + let renderEnvironment = null; + + for ( let i = 0; i < rc.count; i ++ ) { + + const content = rc.get( i ); + + switch ( content.kind ) { + + case 'environment': + + const id = content.id; + + // there could be multiple render environments in a 3dm file + if ( id !== reflectionId ) break; + + const renderTexture = content.findChild( 'texture' ); + const fileName = renderTexture.fileName; + + for ( let j = 0; j < doc.embeddedFiles().count; j ++ ) { + + const _fileName = doc.embeddedFiles().get( j ).fileName; + + if ( fileName === _fileName ) { + + const background = doc.getEmbeddedFileAsBase64( fileName ); + const backgroundImage = 'data:image/png;base64,' + background; + renderEnvironment = { type: 'renderEnvironment', image: backgroundImage, name: fileName }; + + } + + } + + break; + + } + + } + + // Handle Render Settings + + const renderSettings = { + ambientLight: doc.settings().renderSettings().ambientLight, + backgroundColorTop: doc.settings().renderSettings().backgroundColorTop, + backgroundColorBottom: doc.settings().renderSettings().backgroundColorBottom, + useHiddenLights: doc.settings().renderSettings().useHiddenLights, + depthCue: doc.settings().renderSettings().depthCue, + flatShade: doc.settings().renderSettings().flatShade, + renderBackFaces: doc.settings().renderSettings().renderBackFaces, + renderPoints: doc.settings().renderSettings().renderPoints, + renderCurves: doc.settings().renderSettings().renderCurves, + renderIsoParams: doc.settings().renderSettings().renderIsoParams, + renderMeshEdges: doc.settings().renderSettings().renderMeshEdges, + renderAnnotations: doc.settings().renderSettings().renderAnnotations, + useViewportSize: doc.settings().renderSettings().useViewportSize, + scaleBackgroundToFit: doc.settings().renderSettings().scaleBackgroundToFit, + transparentBackground: doc.settings().renderSettings().transparentBackground, + imageDpi: doc.settings().renderSettings().imageDpi, + shadowMapLevel: doc.settings().renderSettings().shadowMapLevel, + namedView: doc.settings().renderSettings().namedView, + snapShot: doc.settings().renderSettings().snapShot, + specificViewport: doc.settings().renderSettings().specificViewport, + groundPlane: extractProperties( doc.settings().renderSettings().groundPlane ), + safeFrame: extractProperties( doc.settings().renderSettings().safeFrame ), + dithering: extractProperties( doc.settings().renderSettings().dithering ), + skylight: extractProperties( doc.settings().renderSettings().skylight ), + linearWorkflow: extractProperties( doc.settings().renderSettings().linearWorkflow ), + renderChannels: extractProperties( doc.settings().renderSettings().renderChannels ), + sun: extractProperties( doc.settings().renderSettings().sun ), + renderEnvironments: extractProperties( doc.settings().renderSettings().renderEnvironments ), + postEffects: extractProperties( doc.settings().renderSettings().postEffects ), + + }; + doc.delete(); - return { objects, materials, layers, views, namedViews, groups, strings, settings }; + return { objects, materials, layers, views, namedViews, groups, strings, settings, renderSettings, renderEnvironment }; + + } + + function extractTextures( m, tTypes, d ) { + + const textures = []; + + for ( let i = 0; i < tTypes.length; i ++ ) { + + const _texture = m.getTexture( tTypes[ i ] ); + if ( _texture ) { + + let textureType = tTypes[ i ].constructor.name; + textureType = textureType.substring( 12, textureType.length ); + const texture = extractTextureData( _texture, textureType, d ); + textures.push( texture ); + _texture.delete(); + + } + + } + + return textures; + + } + + function extractTextureData( t, tType, d ) { + + const texture = { type: tType }; + + const image = d.getEmbeddedFileAsBase64( t.fileName ); + + texture.wrapU = t.wrapU; + texture.wrapV = t.wrapV; + texture.wrapW = t.wrapW; + const uvw = t.uvwTransform.toFloatArray( true ); + + texture.repeat = [ uvw[ 0 ], uvw[ 5 ] ]; + + if ( image ) { + + texture.image = 'data:image/png;base64,' + image; + + } else { + + self.postMessage( { type: 'warning', id: taskID, data: { + message: `THREE.3DMLoader: Image for ${tType} texture not embedded in file.`, + type: 'missing resource' + } + + } ); + + texture.image = null; + + } + + return texture; } @@ -1270,7 +1592,7 @@ function Rhino3dmWorker() { // TODO: precalculate resulting vertices and faces and warn on excessive results _geometry.subdivide( 3 ); - mesh = rhino.Mesh.createFromSubDControlNet( _geometry ); + mesh = rhino.Mesh.createFromSubDControlNet( _geometry, false ); if ( mesh ) { geometry = mesh.toThreejsJSON(); @@ -1323,6 +1645,18 @@ function Rhino3dmWorker() { } + if ( _attributes.decals().count > 0 ) { + + self.postMessage( { type: 'warning', id: taskID, data: { + message: 'THREE.3DMLoader: No conversion exists for the decals associated with this object.', + type: 'no conversion', + guid: _attributes.id + } + + } ); + + } + attributes.drawColor = _attributes.drawColor( doc ); objectType = objectType.constructor.name; @@ -1358,6 +1692,10 @@ function Rhino3dmWorker() { result[ property ] = { name: value.constructor.name, value: value.value }; + } else if ( typeof value === 'object' && value !== null ) { + + result[ property ] = extractProperties( value ); + } else { result[ property ] = value; diff --git a/examples/jsm/loaders/3MFLoader.js b/examples/jsm/loaders/3MFLoader.js index e0e90024299e57..57c5c92df492a3 100644 --- a/examples/jsm/loaders/3MFLoader.js +++ b/examples/jsm/loaders/3MFLoader.js @@ -24,8 +24,7 @@ import * as fflate from '../libs/fflate.module.js'; const COLOR_SPACE_3MF = SRGBColorSpace; /** - * - * 3D Manufacturing Format (3MF) specification: https://3mf.io/specification/ + * A loader for the [3D Manufacturing Format (3MF)]{@link https://3mf.io/specification/} format. * * The following features from the core specification are supported: * @@ -39,18 +38,47 @@ const COLOR_SPACE_3MF = SRGBColorSpace; * - Texture 2D Groups * - Color Groups (Vertex Colors) * - Metallic Display Properties (PBR) + * + * ```js + * const loader = new ThreeMFLoader(); + * + * const object = await loader.loadAsync( './models/3mf/truck.3mf' ); + * object.rotation.set( - Math.PI / 2, 0, 0 ); // z-up conversion + * scene.add( object ); + * ``` + * + * @augments Loader + * @three_import import { ThreeMFLoader } from 'three/addons/loaders/3MFLoader.js'; */ - class ThreeMFLoader extends Loader { + /** + * Constructs a new 3MF loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); + /** + * An array of available extensions. + * + * @type {Array} + */ this.availableExtensions = []; } + /** + * Starts loading from the given URL and passes the loaded 3MF asset + * to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(Group)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ load( url, onLoad, onProgress, onError ) { const scope = this; @@ -85,6 +113,12 @@ class ThreeMFLoader extends Loader { } + /** + * Parses the given 3MF data and returns the resulting group. + * + * @param {ArrayBuffer} data - The raw 3MF asset data as an array buffer. + * @return {Group} A group representing the parsed asset. + */ parse( data ) { const scope = this; @@ -122,6 +156,8 @@ class ThreeMFLoader extends Loader { } + let rootModelFile = null; + for ( file in zip ) { if ( file.match( /\_rels\/.rels$/ ) ) { @@ -132,9 +168,13 @@ class ThreeMFLoader extends Loader { modelRelsName = file; - } else if ( file.match( /^3D\/.*\.model$/ ) ) { + } else if ( file.match( /^3D\/[^\/]*\.model$/ ) ) { + + rootModelFile = file; - modelPartNames.push( file ); + } else if ( file.match( /^3D\/.*\/.*\.model$/ ) ) { + + modelPartNames.push( file ); // sub models } else if ( file.match( /^3D\/Textures?\/.*/ ) ) { @@ -144,6 +184,10 @@ class ThreeMFLoader extends Loader { } + modelPartNames.push( rootModelFile ); // push root model at the end so it is processed after the sub models + + if ( relsName === undefined ) throw new Error( 'THREE.ThreeMFLoader: Cannot find relationship file `rels` in 3MF archive.' ); + // const relsView = zip[ relsName ]; @@ -373,6 +417,73 @@ class ThreeMFLoader extends Loader { } + function parseImplicitIONode( implicitIONode ) { + + const portNodes = implicitIONode.children; + const portArguments = {}; + for ( let i = 0; i < portNodes.length; i ++ ) { + + const args = { type: portNodes[ i ].nodeName.substring( 2 ) }; + for ( let j = 0; j < portNodes[ i ].attributes.length; j ++ ) { + + const attrib = portNodes[ i ].attributes[ j ]; + if ( attrib.specified ) { + + args[ attrib.name ] = attrib.value; + + } + + } + + portArguments[ portNodes[ i ].getAttribute( 'identifier' ) ] = args; + + } + + return portArguments; + + } + + function parseImplicitFunctionNode( implicitFunctionNode ) { + + const implicitFunctionData = { + id: implicitFunctionNode.getAttribute( 'id' ), + displayname: implicitFunctionNode.getAttribute( 'displayname' ) + }; + + const functionNodes = implicitFunctionNode.children; + + const operations = {}; + + for ( let i = 0; i < functionNodes.length; i ++ ) { + + const operatorNode = functionNodes[ i ]; + + if ( operatorNode.nodeName === 'i:in' || operatorNode.nodeName === 'i:out' ) { + + operations[ operatorNode.nodeName === 'i:in' ? 'inputs' : 'outputs' ] = parseImplicitIONode( operatorNode ); + + } else { + + const inputNodes = operatorNode.children; + const portArguments = { 'op': operatorNode.nodeName.substring( 2 ), 'identifier': operatorNode.getAttribute( 'identifier' ) }; + for ( let i = 0; i < inputNodes.length; i ++ ) { + + portArguments[ inputNodes[ i ].nodeName.substring( 2 ) ] = parseImplicitIONode( inputNodes[ i ] ); + + } + + operations[ portArguments[ 'identifier' ] ] = portArguments; + + } + + } + + implicitFunctionData[ 'operations' ] = operations; + + return implicitFunctionData; + + } + function parseMetallicDisplaypropertiesNode( metallicDisplaypropetiesNode ) { const metallicDisplaypropertiesData = { @@ -671,6 +782,25 @@ class ThreeMFLoader extends Loader { // + const implicitFunctionNodes = resourcesNode.querySelectorAll( 'implicitfunction' ); + + if ( implicitFunctionNodes.length > 0 ) { + + resourcesData[ 'implicitfunction' ] = {}; + + } + + + for ( let i = 0; i < implicitFunctionNodes.length; i ++ ) { + + const implicitFunctionNode = implicitFunctionNodes[ i ]; + const implicitFunctionData = parseImplicitFunctionNode( implicitFunctionNode ); + resourcesData[ 'implicitfunction' ][ implicitFunctionData[ 'id' ] ] = implicitFunctionData; + + } + + // + resourcesData[ 'pbmetallicdisplayproperties' ] = {}; const pbmetallicdisplaypropertiesNodes = resourcesNode.querySelectorAll( 'pbmetallicdisplayproperties' ); @@ -844,11 +974,13 @@ class ThreeMFLoader extends Loader { case 'linear': texture.magFilter = LinearFilter; texture.minFilter = LinearFilter; + texture.generateMipmaps = false; break; case 'nearest': texture.magFilter = NearestFilter; texture.minFilter = NearestFilter; + texture.generateMipmaps = false; break; default: @@ -1067,7 +1199,11 @@ class ThreeMFLoader extends Loader { geometry.setIndex( new BufferAttribute( meshData[ 'triangles' ], 1 ) ); geometry.setAttribute( 'position', new BufferAttribute( meshData[ 'vertices' ], 3 ) ); - const material = new MeshPhongMaterial( { color: 0xffffff, flatShading: true } ); + const material = new MeshPhongMaterial( { + name: Loader.DEFAULT_MATERIAL_NAME, + color: 0xffffff, + flatShading: true + } ); const mesh = new Mesh( geometry, material ); @@ -1361,6 +1497,12 @@ class ThreeMFLoader extends Loader { } + if ( modelData.resources.implicitfunction ) { + + console.warn( 'THREE.ThreeMFLoader: Implicit Functions are implemented in data-only.', modelData.resources.implicitfunction ); + + } + } function buildObjects( data3mf ) { @@ -1463,6 +1605,11 @@ class ThreeMFLoader extends Loader { } + /** + * Adds a 3MF extension. + * + * @param {Object} extension - The extension to add. + */ addExtension( extension ) { this.availableExtensions.push( extension ); diff --git a/examples/jsm/loaders/AMFLoader.js b/examples/jsm/loaders/AMFLoader.js index 870bdbb6c8c233..a89328e5f8c3e7 100644 --- a/examples/jsm/loaders/AMFLoader.js +++ b/examples/jsm/loaders/AMFLoader.js @@ -11,29 +11,43 @@ import { import * as fflate from '../libs/fflate.module.js'; /** - * Description: Early release of an AMF Loader following the pattern of the - * example loaders in the three.js project. + * A loader for the AMF format. * - * Usage: - * const loader = new AMFLoader(); - * loader.load('/path/to/project.amf', function(objecttree) { - * scene.add(objecttree); - * }); + * The loader supports materials, color and ZIP compressed files. + * No constellation support (yet). * - * Materials now supported, material colors supported - * Zip support, requires fflate - * No constellation support (yet)! + * ```js + * const loader = new AMFLoader(); * + * const object = await loader.loadAsync( './models/amf/rook.amf' ); + * scene.add( object ); + * ``` + * + * @augments Loader + * @three_import import { AMFLoader } from 'three/addons/loaders/AMFLoader.js'; */ - class AMFLoader extends Loader { + /** + * Constructs a new AMF loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); } + /** + * Starts loading from the given URL and passes the loaded AMF asset + * to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(Group)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ load( url, onLoad, onProgress, onError ) { const scope = this; @@ -69,6 +83,12 @@ class AMFLoader extends Loader { } + /** + * Parses the given AMF data and returns the resulting group. + * + * @param {ArrayBuffer} data - The raw AMF asset data as an array buffer. + * @return {Group} A group representing the parsed asset. + */ parse( data ) { function loadDocument( data ) { @@ -236,7 +256,7 @@ class AMFLoader extends Loader { function loadMeshVolume( node ) { - const volume = { name: '', triangles: [], materialid: null }; + const volume = { name: '', triangles: [], materialId: null }; let currVolumeNode = node.firstElementChild; @@ -432,7 +452,11 @@ class AMFLoader extends Loader { } const sceneObject = new Group(); - const defaultMaterial = new MeshPhongMaterial( { color: 0xaaaaff, flatShading: true } ); + const defaultMaterial = new MeshPhongMaterial( { + name: Loader.DEFAULT_MATERIAL_NAME, + color: 0xaaaaff, + flatShading: true + } ); sceneObject.name = amfName; sceneObject.userData.author = amfAuthor; diff --git a/examples/jsm/loaders/BVHLoader.js b/examples/jsm/loaders/BVHLoader.js index dde85a90df8865..05a3d6bb9e8ac1 100644 --- a/examples/jsm/loaders/BVHLoader.js +++ b/examples/jsm/loaders/BVHLoader.js @@ -11,23 +11,66 @@ import { } from 'three'; /** - * Description: reads BVH files and outputs a single Skeleton and an AnimationClip + * A loader for the BVH format. * - * Currently only supports bvh files containing a single root. + * Imports BVH files and outputs a single {@link Skeleton} and {@link AnimationClip}. + * The loader only supports BVH files containing a single root right now. * + * ```js + * const loader = new BVHLoader(); + * const result = await loader.loadAsync( 'models/bvh/pirouette.bvh' ); + * + * // visualize skeleton + * const skeletonHelper = new THREE.SkeletonHelper( result.skeleton.bones[ 0 ] ); + * scene.add( result.skeleton.bones[ 0 ] ); + * scene.add( skeletonHelper ); + * + * // play animation clip + * mixer = new THREE.AnimationMixer( result.skeleton.bones[ 0 ] ); + * mixer.clipAction( result.clip ).play(); + * ``` + * + * @augments Loader + * @three_import import { BVHLoader } from 'three/addons/loaders/BVHLoader.js'; */ - class BVHLoader extends Loader { + /** + * Constructs a new BVH loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); + /** + * Whether to animate bone positions or not. + * + * @type {boolean} + * @default true + */ this.animateBonePositions = true; + + /** + * Whether to animate bone rotations or not. + * + * @type {boolean} + * @default true + */ this.animateBoneRotations = true; } + /** + * Starts loading from the given URL and passes the loaded BVH asset + * to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function({skeleton:Skeleton,clip:AnimationClip})} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ load( url, onLoad, onProgress, onError ) { const scope = this; @@ -62,15 +105,19 @@ class BVHLoader extends Loader { } + /** + * Parses the given BVH data and returns the resulting data. + * + * @param {string} text - The raw BVH data as a string. + * @return {{skeleton:Skeleton,clip:AnimationClip}} An object representing the parsed asset. + */ parse( text ) { - /* - reads a string array (lines) from a BVH file - and outputs a skeleton structure including motion data + // reads a string array (lines) from a BVH file + // and outputs a skeleton structure including motion data - returns thee root node: - { name: '', channels: [], children: [] } - */ + // returns thee root node: + // { name: '', channels: [], children: [] } function readBvh( lines ) { // read model structure @@ -204,7 +251,7 @@ class BVHLoader extends Loader { } /* - Recursively parses the HIERACHY section of the BVH file + Recursively parses the HIERARCHY section of the BVH file - lines: all lines of the file. lines are consumed as we go along. - firstline: line containing the node type and name e.g. 'JOINT hip' @@ -338,11 +385,11 @@ class BVHLoader extends Loader { } /* - builds a AnimationClip from the keyframe data saved in each bone. + builds an AnimationClip from the keyframe data saved in each bone. bone: bvh root node - returns: a AnimationClip containing position and quaternion tracks + returns: an AnimationClip containing position and quaternion tracks */ function toTHREEAnimation( bones ) { diff --git a/examples/jsm/loaders/ColladaLoader.js b/examples/jsm/loaders/ColladaLoader.js index 0fdbee106864db..d9fc05f8e011b6 100644 --- a/examples/jsm/loaders/ColladaLoader.js +++ b/examples/jsm/loaders/ColladaLoader.js @@ -5,6 +5,7 @@ import { BufferGeometry, ClampToEdgeWrapping, Color, + ColorManagement, DirectionalLight, DoubleSide, FileLoader, @@ -40,14 +41,36 @@ import { } from 'three'; import { TGALoader } from '../loaders/TGALoader.js'; +/** + * A loader for the Collada format. + * + * The Collada format is very complex so this loader only supports a subset of what + * is defined in the [official specification]{@link https://www.khronos.org/files/collada_spec_1_5.pdf}. + * + * Assets with a Z-UP coordinate system are transformed it into Y-UP by a simple rotation. + * The vertex data are not converted. + * + * ```js + * const loader = new ColladaLoader(); + * + * const result = await loader.loadAsync( './models/collada/elf/elf.dae' ); + * scene.add( result.scene ); + * ``` + * + * @augments Loader + * @three_import import { ColladaLoader } from 'three/addons/loaders/ColladaLoader.js'; + */ class ColladaLoader extends Loader { - constructor( manager ) { - - super( manager ); - - } - + /** + * Starts loading from the given URL and passes the loaded Collada asset + * to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function({scene:Group,animations:Array,kinematics:Object})} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ load( url, onLoad, onProgress, onError ) { const scope = this; @@ -84,6 +107,14 @@ class ColladaLoader extends Loader { } + /** + * Parses the given Collada data and returns a result object holding the parsed scene, + * an array of animation clips and kinematics. + * + * @param {string} text - The raw Collada data as a string. + * @param {string} path - The asset path. + * @return {{scene:Group,animations:Array,kinematics:Object}} An object representing the parsed asset. + */ parse( text, path ) { function getElementsByTagName( xml, name ) { @@ -304,7 +335,7 @@ class ColladaLoader extends Loader { if ( hasChildren === false ) { - // since 'id' attributes can be optional, it's necessary to generate a UUID for unqiue assignment + // since 'id' attributes can be optional, it's necessary to generate a UUID for unique assignment library.animations[ xml.getAttribute( 'id' ) || MathUtils.generateUUID() ] = data; @@ -982,7 +1013,7 @@ class ColladaLoader extends Loader { } // we sort the joints in descending order based on the weights. - // this ensures, we only procced the most important joints of the vertex + // this ensures, we only proceed the most important joints of the vertex vertexSkinData.sort( descending ); @@ -1598,7 +1629,7 @@ class ColladaLoader extends Loader { } - // create texture if image is avaiable + // create texture if image is available if ( image !== null ) { @@ -1687,9 +1718,9 @@ class ColladaLoader extends Loader { } - material.color.convertSRGBToLinear(); - if ( material.specular ) material.specular.convertSRGBToLinear(); - if ( material.emissive ) material.emissive.convertSRGBToLinear(); + ColorManagement.colorSpaceToWorking( material.color, SRGBColorSpace ); + if ( material.specular ) ColorManagement.colorSpaceToWorking( material.specular, SRGBColorSpace ); + if ( material.emissive ) ColorManagement.colorSpaceToWorking( material.emissive, SRGBColorSpace ); // @@ -2025,7 +2056,8 @@ class ColladaLoader extends Loader { case 'color': const array = parseFloats( child.textContent ); - data.color = new Color().fromArray( array ).convertSRGBToLinear(); + data.color = new Color().fromArray( array ); + ColorManagement.colorSpaceToWorking( data.color, SRGBColorSpace ); break; case 'falloff_angle': @@ -2396,7 +2428,7 @@ class ColladaLoader extends Loader { break; default: - console.warn( 'THREE.ColladaLoader: Unknow primitive type:', primitive.type ); + console.warn( 'THREE.ColladaLoader: Unknown primitive type:', primitive.type ); } @@ -2554,8 +2586,9 @@ class ColladaLoader extends Loader { tempColor.setRGB( array[ startIndex + 0 ], array[ startIndex + 1 ], - array[ startIndex + 2 ] - ).convertSRGBToLinear(); + array[ startIndex + 2 ], + SRGBColorSpace + ); array[ startIndex + 0 ] = tempColor.r; array[ startIndex + 1 ] = tempColor.g; @@ -3437,7 +3470,7 @@ class ColladaLoader extends Loader { let i, j, data; // a skeleton can have multiple root bones. collada expresses this - // situtation with multiple "skeleton" tags per controller instance + // situation with multiple "skeleton" tags per controller instance for ( i = 0; i < skeletons.length; i ++ ) { @@ -3710,7 +3743,10 @@ class ColladaLoader extends Loader { } - const fallbackMaterial = new MeshBasicMaterial( { color: 0xff00ff } ); + const fallbackMaterial = new MeshBasicMaterial( { + name: Loader.DEFAULT_MATERIAL_NAME, + color: 0xff00ff + } ); function resolveMaterialBinding( keys, instanceMaterials ) { @@ -3971,7 +4007,7 @@ class ColladaLoader extends Loader { } else { result += '\n'; - stack.push.apply( stack, node.childNodes ); + stack.push( ...node.childNodes ); } diff --git a/examples/jsm/loaders/DDSLoader.js b/examples/jsm/loaders/DDSLoader.js index 32ec834d9499a5..89f80b1891f7f7 100644 --- a/examples/jsm/loaders/DDSLoader.js +++ b/examples/jsm/loaders/DDSLoader.js @@ -4,17 +4,44 @@ import { RGBA_S3TC_DXT3_Format, RGBA_S3TC_DXT5_Format, RGB_ETC1_Format, - RGB_S3TC_DXT1_Format + RGB_S3TC_DXT1_Format, + RGB_BPTC_SIGNED_Format, + RGB_BPTC_UNSIGNED_Format } from 'three'; +/** + * A loader for the S3TC texture compression format. + * + * ```js + * const loader = new DDSLoader(); + * + * const map = loader.load( 'textures/compressed/disturb_dxt1_nomip.dds' ); + * map.colorSpace = THREE.SRGBColorSpace; // only for color textures + * ``` + * + * @augments CompressedTextureLoader + * @three_import import { DDSLoader } from 'three/addons/loaders/DDSLoader.js'; + */ class DDSLoader extends CompressedTextureLoader { + /** + * Constructs a new DDS loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); } + /** + * Parses the given S3TC texture data. + * + * @param {ArrayBuffer} buffer - The raw texture data. + * @param {boolean} loadMipmaps - Whether to load mipmaps or not. + * @return {CompressedTextureLoader~TexData} An object representing the parsed texture data. + */ parse( buffer, loadMipmaps ) { const dds = { mipmaps: [], width: 0, height: 0, format: null, mipmapCount: 1 }; @@ -56,6 +83,9 @@ class DDSLoader extends CompressedTextureLoader { // const DDPF_YUV = 0x200; // const DDPF_LUMINANCE = 0x20000; + const DXGI_FORMAT_BC6H_UF16 = 95; + const DXGI_FORMAT_BC6H_SF16 = 96; + function fourCCToInt32( value ) { return value.charCodeAt( 0 ) + @@ -104,12 +134,41 @@ class DDSLoader extends CompressedTextureLoader { } + function loadRGBMip( buffer, dataOffset, width, height ) { + + const dataLength = width * height * 3; + const srcBuffer = new Uint8Array( buffer, dataOffset, dataLength ); + const byteArray = new Uint8Array( width * height * 4 ); + let dst = 0; + let src = 0; + for ( let y = 0; y < height; y ++ ) { + + for ( let x = 0; x < width; x ++ ) { + + const b = srcBuffer[ src ]; src ++; + const g = srcBuffer[ src ]; src ++; + const r = srcBuffer[ src ]; src ++; + byteArray[ dst ] = r; dst ++; //r + byteArray[ dst ] = g; dst ++; //g + byteArray[ dst ] = b; dst ++; //b + byteArray[ dst ] = 255; dst ++; //a + + } + + } + + return byteArray; + + } + const FOURCC_DXT1 = fourCCToInt32( 'DXT1' ); const FOURCC_DXT3 = fourCCToInt32( 'DXT3' ); const FOURCC_DXT5 = fourCCToInt32( 'DXT5' ); const FOURCC_ETC1 = fourCCToInt32( 'ETC1' ); + const FOURCC_DX10 = fourCCToInt32( 'DX10' ); const headerLengthInt = 31; // The header length in 32 bit ints + const extendedHeaderLengthInt = 5; // The extended header length in 32 bit ints // Offsets into the header array @@ -135,6 +194,9 @@ class DDSLoader extends CompressedTextureLoader { // const off_caps3 = 29; // const off_caps4 = 30; + // If fourCC = DX10, the extended header starts after 32 + const off_dxgiFormat = 0; + // Parse header const header = new Int32Array( buffer, 0, headerLengthInt ); @@ -151,6 +213,9 @@ class DDSLoader extends CompressedTextureLoader { const fourCC = header[ off_pfFourCC ]; let isRGBAUncompressed = false; + let isRGBUncompressed = false; + + let dataOffset = header[ off_size ] + 4; switch ( fourCC ) { @@ -178,6 +243,40 @@ class DDSLoader extends CompressedTextureLoader { dds.format = RGB_ETC1_Format; break; + case FOURCC_DX10: + + dataOffset += extendedHeaderLengthInt * 4; + const extendedHeader = new Int32Array( buffer, ( headerLengthInt + 1 ) * 4, extendedHeaderLengthInt ); + const dxgiFormat = extendedHeader[ off_dxgiFormat ]; + switch ( dxgiFormat ) { + + case DXGI_FORMAT_BC6H_SF16: { + + blockBytes = 16; + dds.format = RGB_BPTC_SIGNED_Format; + break; + + } + + case DXGI_FORMAT_BC6H_UF16: { + + blockBytes = 16; + dds.format = RGB_BPTC_UNSIGNED_Format; + break; + + } + + default: { + + console.error( 'THREE.DDSLoader.parse: Unsupported DXGI_FORMAT code ', dxgiFormat ); + return dds; + + } + + } + + break; + default: if ( header[ off_RGBBitCount ] === 32 @@ -190,6 +289,15 @@ class DDSLoader extends CompressedTextureLoader { blockBytes = 64; dds.format = RGBAFormat; + } else if ( header[ off_RGBBitCount ] === 24 + && header[ off_RBitMask ] & 0xff0000 + && header[ off_GBitMask ] & 0xff00 + && header[ off_BBitMask ] & 0xff ) { + + isRGBUncompressed = true; + blockBytes = 64; + dds.format = RGBAFormat; + } else { console.error( 'THREE.DDSLoader.parse: Unsupported FourCC code ', int32ToFourCC( fourCC ) ); @@ -226,8 +334,6 @@ class DDSLoader extends CompressedTextureLoader { dds.width = header[ off_width ]; dds.height = header[ off_height ]; - let dataOffset = header[ off_size ] + 4; - // Extract mipmaps buffers const faces = dds.isCubemap ? 6 : 1; @@ -246,6 +352,11 @@ class DDSLoader extends CompressedTextureLoader { byteArray = loadARGBMip( buffer, dataOffset, width, height ); dataLength = byteArray.length; + } else if ( isRGBUncompressed ) { + + byteArray = loadRGBMip( buffer, dataOffset, width, height ); + dataLength = width * height * 3; + } else { dataLength = Math.max( 4, width ) / 4 * Math.max( 4, height ) / 4 * blockBytes; diff --git a/examples/jsm/loaders/DRACOLoader.js b/examples/jsm/loaders/DRACOLoader.js index 25fcfa251bc212..385167cd8863fc 100644 --- a/examples/jsm/loaders/DRACOLoader.js +++ b/examples/jsm/loaders/DRACOLoader.js @@ -2,6 +2,7 @@ import { BufferAttribute, BufferGeometry, Color, + ColorManagement, FileLoader, Loader, LinearSRGBColorSpace, @@ -10,8 +11,45 @@ import { const _taskCache = new WeakMap(); +/** + * A loader for the Draco format. + * + * [Draco]{@link https://google.github.io/draco/} is an open source library for compressing + * and decompressing 3D meshes and point clouds. Compressed geometry can be significantly smaller, + * at the cost of additional decoding time on the client device. + * + * Standalone Draco files have a `.drc` extension, and contain vertex positions, normals, colors, + * and other attributes. Draco files do not contain materials, textures, animation, or node hierarchies – + * to use these features, embed Draco geometry inside of a glTF file. A normal glTF file can be converted + * to a Draco-compressed glTF file using [glTF-Pipeline]{@link https://github.com/CesiumGS/gltf-pipeline}. + * When using Draco with glTF, an instance of `DRACOLoader` will be used internally by {@link GLTFLoader}. + * + * It is recommended to create one DRACOLoader instance and reuse it to avoid loading and creating + * multiple decoder instances. + * + * `DRACOLoader` will automatically use either the JS or the WASM decoding library, based on + * browser capabilities. + * + * ```js + * const loader = new DRACOLoader(); + * loader.setDecoderPath( '/examples/jsm/libs/draco/' ); + * + * const geometry = await dracoLoader.loadAsync( 'models/draco/bunny.drc' ); + * geometry.computeVertexNormals(); // optional + * + * dracoLoader.dispose(); + * ``` + * + * @augments Loader + * @three_import import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js'; + */ class DRACOLoader extends Loader { + /** + * Constructs a new Draco loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); @@ -41,6 +79,12 @@ class DRACOLoader extends Loader { } + /** + * Provides configuration for the decoder libraries. Configuration cannot be changed after decoding begins. + * + * @param {string} path - The decoder path. + * @return {DRACOLoader} A reference to this loader. + */ setDecoderPath( path ) { this.decoderPath = path; @@ -49,6 +93,12 @@ class DRACOLoader extends Loader { } + /** + * Provides configuration for the decoder libraries. Configuration cannot be changed after decoding begins. + * + * @param {{type:('js'|'wasm')}} config - The decoder config. + * @return {DRACOLoader} A reference to this loader. + */ setDecoderConfig( config ) { this.decoderConfig = config; @@ -57,6 +107,13 @@ class DRACOLoader extends Loader { } + /** + * Sets the maximum number of Web Workers to be used during decoding. + * A lower limit may be preferable if workers are also for other tasks in the application. + * + * @param {number} workerLimit - The worker limit. + * @return {DRACOLoader} A reference to this loader. + */ setWorkerLimit( workerLimit ) { this.workerLimit = workerLimit; @@ -65,6 +122,15 @@ class DRACOLoader extends Loader { } + /** + * Starts loading from the given URL and passes the loaded Draco asset + * to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(BufferGeometry)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ load( url, onLoad, onProgress, onError ) { const loader = new FileLoader( this.manager ); @@ -82,13 +148,22 @@ class DRACOLoader extends Loader { } - parse( buffer, onLoad, onError ) { + /** + * Parses the given Draco data. + * + * @param {ArrayBuffer} buffer - The raw Draco data as an array buffer. + * @param {function(BufferGeometry)} onLoad - Executed when the loading/parsing process has been finished. + * @param {onErrorCallback} onError - Executed when errors occur. + */ + parse( buffer, onLoad, onError = ()=>{} ) { - this.decodeDracoFile( buffer, onLoad, null, null, SRGBColorSpace ).catch( onError ); + this.decodeDracoFile( buffer, onLoad, null, null, SRGBColorSpace, onError ).catch( onError ); } - decodeDracoFile( buffer, callback, attributeIDs, attributeTypes, vertexColorSpace = LinearSRGBColorSpace ) { + // + + decodeDracoFile( buffer, callback, attributeIDs, attributeTypes, vertexColorSpace = LinearSRGBColorSpace, onError = () => {} ) { const taskConfig = { attributeIDs: attributeIDs || this.defaultAttributeIDs, @@ -97,7 +172,7 @@ class DRACOLoader extends Loader { vertexColorSpace: vertexColorSpace, }; - return this.decodeGeometry( buffer, taskConfig ).then( callback ); + return this.decodeGeometry( buffer, taskConfig ).then( callback ).catch( onError ); } @@ -234,7 +309,8 @@ class DRACOLoader extends Loader { for ( let i = 0, il = attribute.count; i < il; i ++ ) { - _color.fromBufferAttribute( attribute, i ).convertSRGBToLinear(); + _color.fromBufferAttribute( attribute, i ); + ColorManagement.colorSpaceToWorking( _color, SRGBColorSpace ); attribute.setXYZ( i, _color.r, _color.g, _color.b ); } diff --git a/examples/jsm/loaders/EXRLoader.js b/examples/jsm/loaders/EXRLoader.js index a4dd2abd1a873c..d8dfe6fbb66694 100644 --- a/examples/jsm/loaders/EXRLoader.js +++ b/examples/jsm/loaders/EXRLoader.js @@ -11,13 +11,8 @@ import { } from 'three'; import * as fflate from '../libs/fflate.module.js'; -/** - * OpenEXR loader currently supports uncompressed, ZIP(S), RLE, PIZ and DWA/B compression. - * Supports reading as UnsignedByte, HalfFloat and Float type data texture. - * - * Referred to the original Industrial Light & Magic OpenEXR implementation and the TinyEXR / Syoyo Fujita - * implementation, so I have preserved their copyright notices. - */ +// Referred to the original Industrial Light & Magic OpenEXR implementation and the TinyEXR / Syoyo Fujita +// implementation, so I have preserved their copyright notices. // /* // Copyright (c) 2014 - 2017, Syoyo Fujita @@ -84,16 +79,48 @@ import * as fflate from '../libs/fflate.module.js'; // // End of OpenEXR license ------------------------------------------------- + +/** + * A loader for the OpenEXR texture format. + * + * `EXRLoader` currently supports uncompressed, ZIP(S), RLE, PIZ and DWA/B compression. + * Supports reading as UnsignedByte, HalfFloat and Float type data texture. + * + * ```js + * const loader = new EXRLoader(); + * const texture = await loader.loadAsync( 'textures/memorial.exr' ); + * ``` + * + * @augments DataTextureLoader + * @three_import import { EXRLoader } from 'three/addons/loaders/EXRLoader.js'; + */ class EXRLoader extends DataTextureLoader { + /** + * Constructs a new EXR loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); + /** + * The texture type. + * + * @type {(HalfFloatType|FloatType)} + * @default HalfFloatType + */ this.type = HalfFloatType; } + /** + * Parses the given EXR texture data. + * + * @param {ArrayBuffer} buffer - The raw texture data. + * @return {DataTextureLoader~TexData} An object representing the parsed texture data. + */ parse( buffer ) { const USHORT_RANGE = ( 1 << 16 ); @@ -1269,18 +1296,18 @@ class EXRLoader extends DataTextureLoader { const inDataView = info.viewer; const inOffset = { value: info.offset.value }; - const outBuffer = new Uint16Array( info.width * info.scanlineBlockSize * ( info.channels * info.type ) ); + const outBuffer = new Uint16Array( info.columns * info.lines * ( info.inputChannels.length * info.type ) ); const bitmap = new Uint8Array( BITMAP_SIZE ); // Setup channel info let outBufferEnd = 0; - const pizChannelData = new Array( info.channels ); - for ( let i = 0; i < info.channels; i ++ ) { + const pizChannelData = new Array( info.inputChannels.length ); + for ( let i = 0, il = info.inputChannels.length; i < il; i ++ ) { pizChannelData[ i ] = {}; pizChannelData[ i ][ 'start' ] = outBufferEnd; pizChannelData[ i ][ 'end' ] = pizChannelData[ i ][ 'start' ]; - pizChannelData[ i ][ 'nx' ] = info.width; + pizChannelData[ i ][ 'nx' ] = info.columns; pizChannelData[ i ][ 'ny' ] = info.lines; pizChannelData[ i ][ 'size' ] = info.type; @@ -1319,7 +1346,7 @@ class EXRLoader extends DataTextureLoader { hufUncompress( info.array, inDataView, inOffset, length, outBuffer, outBufferEnd ); // Wavelet decoding - for ( let i = 0; i < info.channels; ++ i ) { + for ( let i = 0; i < info.inputChannels.length; ++ i ) { const cd = pizChannelData[ i ]; @@ -1347,7 +1374,7 @@ class EXRLoader extends DataTextureLoader { const tmpBuffer = new Uint8Array( outBuffer.buffer.byteLength ); for ( let y = 0; y < info.lines; y ++ ) { - for ( let c = 0; c < info.channels; c ++ ) { + for ( let c = 0; c < info.inputChannels.length; c ++ ) { const cd = pizChannelData[ c ]; @@ -1372,8 +1399,9 @@ class EXRLoader extends DataTextureLoader { const rawBuffer = fflate.unzlibSync( compressed ); - const sz = info.lines * info.channels * info.width; - const tmpBuffer = ( info.type == 1 ) ? new Uint16Array( sz ) : new Uint32Array( sz ); + const byteSize = info.inputChannels.length * info.lines * info.columns * info.totalBytes; + const tmpBuffer = new ArrayBuffer( byteSize ); + const viewer = new DataView( tmpBuffer ); let tmpBufferEnd = 0; let writePtr = 0; @@ -1381,26 +1409,27 @@ class EXRLoader extends DataTextureLoader { for ( let y = 0; y < info.lines; y ++ ) { - for ( let c = 0; c < info.channels; c ++ ) { + for ( let c = 0; c < info.inputChannels.length; c ++ ) { let pixel = 0; - switch ( info.type ) { + const type = info.inputChannels[ c ].pixelType; + switch ( type ) { case 1: ptr[ 0 ] = tmpBufferEnd; - ptr[ 1 ] = ptr[ 0 ] + info.width; - tmpBufferEnd = ptr[ 1 ] + info.width; + ptr[ 1 ] = ptr[ 0 ] + info.columns; + tmpBufferEnd = ptr[ 1 ] + info.columns; - for ( let j = 0; j < info.width; ++ j ) { + for ( let j = 0; j < info.columns; ++ j ) { const diff = ( rawBuffer[ ptr[ 0 ] ++ ] << 8 ) | rawBuffer[ ptr[ 1 ] ++ ]; pixel += diff; - tmpBuffer[ writePtr ] = pixel; - writePtr ++; + viewer.setUint16( writePtr, pixel, true ); + writePtr += 2; } @@ -1409,18 +1438,18 @@ class EXRLoader extends DataTextureLoader { case 2: ptr[ 0 ] = tmpBufferEnd; - ptr[ 1 ] = ptr[ 0 ] + info.width; - ptr[ 2 ] = ptr[ 1 ] + info.width; - tmpBufferEnd = ptr[ 2 ] + info.width; + ptr[ 1 ] = ptr[ 0 ] + info.columns; + ptr[ 2 ] = ptr[ 1 ] + info.columns; + tmpBufferEnd = ptr[ 2 ] + info.columns; - for ( let j = 0; j < info.width; ++ j ) { + for ( let j = 0; j < info.columns; ++ j ) { const diff = ( rawBuffer[ ptr[ 0 ] ++ ] << 24 ) | ( rawBuffer[ ptr[ 1 ] ++ ] << 16 ) | ( rawBuffer[ ptr[ 2 ] ++ ] << 8 ); pixel += diff; - tmpBuffer[ writePtr ] = pixel; - writePtr ++; + viewer.setUint32( writePtr, pixel, true ); + writePtr += 4; } @@ -1432,7 +1461,7 @@ class EXRLoader extends DataTextureLoader { } - return new DataView( tmpBuffer.buffer ); + return viewer; } @@ -1440,7 +1469,7 @@ class EXRLoader extends DataTextureLoader { const inDataView = info.viewer; const inOffset = { value: info.offset.value }; - const outBuffer = new Uint8Array( info.width * info.lines * ( info.channels * info.type * INT16_SIZE ) ); + const outBuffer = new Uint8Array( info.columns * info.lines * ( info.inputChannels.length * info.type * INT16_SIZE ) ); // Read compression header information const dwaHeader = { @@ -1488,9 +1517,9 @@ class EXRLoader extends DataTextureLoader { // Classify channels const channels = EXRHeader.channels; - const channelData = new Array( info.channels ); + const channelData = new Array( info.inputChannels.length ); - for ( let i = 0; i < info.channels; ++ i ) { + for ( let i = 0; i < info.inputChannels.length; ++ i ) { const cd = channelData[ i ] = {}; const channel = channels[ i ]; @@ -1500,7 +1529,7 @@ class EXRLoader extends DataTextureLoader { cd.decoded = false; cd.type = channel.pixelType; cd.pLinear = channel.pLinear; - cd.width = info.width; + cd.width = info.columns; cd.height = info.lines; } @@ -1509,7 +1538,7 @@ class EXRLoader extends DataTextureLoader { idx: new Array( 3 ) }; - for ( let offset = 0; offset < info.channels; ++ offset ) { + for ( let offset = 0; offset < info.inputChannels.length; ++ offset ) { const cd = channelData[ offset ]; @@ -1886,10 +1915,10 @@ class EXRLoader extends DataTextureLoader { function parseBox2i( dataView, offset ) { - const xMin = parseUint32( dataView, offset ); - const yMin = parseUint32( dataView, offset ); - const xMax = parseUint32( dataView, offset ); - const yMax = parseUint32( dataView, offset ); + const xMin = parseInt32( dataView, offset ); + const yMin = parseInt32( dataView, offset ); + const xMax = parseInt32( dataView, offset ); + const yMax = parseInt32( dataView, offset ); return { xMin: xMin, yMin: yMin, xMax: xMax, yMax: yMax }; @@ -1898,7 +1927,9 @@ class EXRLoader extends DataTextureLoader { function parseLineOrder( dataView, offset ) { const lineOrders = [ - 'INCREASING_Y' + 'INCREASING_Y', + 'DECREASING_Y', + 'RANDOM_Y', ]; const lineOrder = parseUint8( dataView, offset ); @@ -1907,6 +1938,45 @@ class EXRLoader extends DataTextureLoader { } + function parseEnvmap( dataView, offset ) { + + const envmaps = [ + 'ENVMAP_LATLONG', + 'ENVMAP_CUBE' + ]; + + const envmap = parseUint8( dataView, offset ); + + return envmaps[ envmap ]; + + } + + function parseTiledesc( dataView, offset ) { + + const levelModes = [ + 'ONE_LEVEL', + 'MIPMAP_LEVELS', + 'RIPMAP_LEVELS', + ]; + + const roundingModes = [ + 'ROUND_DOWN', + 'ROUND_UP', + ]; + + const xSize = parseUint32( dataView, offset ); + const ySize = parseUint32( dataView, offset ); + const modes = parseUint8( dataView, offset ); + + return { + xSize: xSize, + ySize: ySize, + levelMode: levelModes[ modes & 0xf ], + roundingMode: roundingModes[ modes >> 4 ] + }; + + } + function parseV2f( dataView, offset ) { const x = parseFloat32( dataView, offset ); @@ -1948,6 +2018,14 @@ class EXRLoader extends DataTextureLoader { return parseBox2i( dataView, offset ); + } else if ( type === 'envmap' ) { + + return parseEnvmap( dataView, offset ); + + } else if ( type === 'tiledesc' ) { + + return parseTiledesc( dataView, offset ); + } else if ( type === 'lineOrder' ) { return parseLineOrder( dataView, offset ); @@ -1990,13 +2068,170 @@ class EXRLoader extends DataTextureLoader { } + function roundLog2( x, mode ) { + + const log2 = Math.log2( x ); + return mode == 'ROUND_DOWN' ? Math.floor( log2 ) : Math.ceil( log2 ); + + } + + function calculateTileLevels( tiledesc, w, h ) { + + let num = 0; + + switch ( tiledesc.levelMode ) { + + case 'ONE_LEVEL': + num = 1; + break; + + case 'MIPMAP_LEVELS': + num = roundLog2( Math.max( w, h ), tiledesc.roundingMode ) + 1; + break; + + case 'RIPMAP_LEVELS': + throw new Error( 'THREE.EXRLoader: RIPMAP_LEVELS tiles currently unsupported.' ); + + } + + return num; + + } + + function calculateTiles( count, dataSize, size, roundingMode ) { + + const tiles = new Array( count ); + + for ( let i = 0; i < count; i ++ ) { + + const b = ( 1 << i ); + let s = ( dataSize / b ) | 0; + + if ( roundingMode == 'ROUND_UP' && s * b < dataSize ) s += 1; + + const l = Math.max( s, 1 ); + + tiles[ i ] = ( ( l + size - 1 ) / size ) | 0; + + } + + return tiles; + + } + + function parseTiles() { + + const EXRDecoder = this; + const offset = EXRDecoder.offset; + const tmpOffset = { value: 0 }; + + for ( let tile = 0; tile < EXRDecoder.tileCount; tile ++ ) { + + const tileX = parseInt32( EXRDecoder.viewer, offset ); + const tileY = parseInt32( EXRDecoder.viewer, offset ); + offset.value += 8; // skip levels - only parsing top-level + EXRDecoder.size = parseUint32( EXRDecoder.viewer, offset ); + + const startX = tileX * EXRDecoder.blockWidth; + const startY = tileY * EXRDecoder.blockHeight; + EXRDecoder.columns = ( startX + EXRDecoder.blockWidth > EXRDecoder.width ) ? EXRDecoder.width - startX : EXRDecoder.blockWidth; + EXRDecoder.lines = ( startY + EXRDecoder.blockHeight > EXRDecoder.height ) ? EXRDecoder.height - startY : EXRDecoder.blockHeight; + + const bytesBlockLine = EXRDecoder.columns * EXRDecoder.totalBytes; + const isCompressed = EXRDecoder.size < EXRDecoder.lines * bytesBlockLine; + const viewer = isCompressed ? EXRDecoder.uncompress( EXRDecoder ) : uncompressRAW( EXRDecoder ); + + offset.value += EXRDecoder.size; + + for ( let line = 0; line < EXRDecoder.lines; line ++ ) { + + const lineOffset = line * EXRDecoder.columns * EXRDecoder.totalBytes; + + for ( let channelID = 0; channelID < EXRDecoder.inputChannels.length; channelID ++ ) { + + const name = EXRHeader.channels[ channelID ].name; + const lOff = EXRDecoder.channelByteOffsets[ name ] * EXRDecoder.columns; + const cOff = EXRDecoder.decodeChannels[ name ]; + + if ( cOff === undefined ) continue; + + tmpOffset.value = lineOffset + lOff; + const outLineOffset = ( EXRDecoder.height - ( 1 + startY + line ) ) * EXRDecoder.outLineWidth; + + for ( let x = 0; x < EXRDecoder.columns; x ++ ) { + + const outIndex = outLineOffset + ( x + startX ) * EXRDecoder.outputChannels + cOff; + EXRDecoder.byteArray[ outIndex ] = EXRDecoder.getter( viewer, tmpOffset ); + + } + + } + + } + + } + + } + + function parseScanline() { + + const EXRDecoder = this; + const offset = EXRDecoder.offset; + const tmpOffset = { value: 0 }; + + for ( let scanlineBlockIdx = 0; scanlineBlockIdx < EXRDecoder.height / EXRDecoder.blockHeight; scanlineBlockIdx ++ ) { + + const line = parseInt32( EXRDecoder.viewer, offset ) - EXRHeader.dataWindow.yMin; // line_no + EXRDecoder.size = parseUint32( EXRDecoder.viewer, offset ); // data_len + EXRDecoder.lines = ( ( line + EXRDecoder.blockHeight > EXRDecoder.height ) ? ( EXRDecoder.height - line ) : EXRDecoder.blockHeight ); + + const bytesPerLine = EXRDecoder.columns * EXRDecoder.totalBytes; + const isCompressed = EXRDecoder.size < EXRDecoder.lines * bytesPerLine; + const viewer = isCompressed ? EXRDecoder.uncompress( EXRDecoder ) : uncompressRAW( EXRDecoder ); + + offset.value += EXRDecoder.size; + + for ( let line_y = 0; line_y < EXRDecoder.blockHeight; line_y ++ ) { + + const scan_y = scanlineBlockIdx * EXRDecoder.blockHeight; + const true_y = line_y + EXRDecoder.scanOrder( scan_y ); + if ( true_y >= EXRDecoder.height ) continue; + + const lineOffset = line_y * bytesPerLine; + const outLineOffset = ( EXRDecoder.height - 1 - true_y ) * EXRDecoder.outLineWidth; + + for ( let channelID = 0; channelID < EXRDecoder.inputChannels.length; channelID ++ ) { + + const name = EXRHeader.channels[ channelID ].name; + const lOff = EXRDecoder.channelByteOffsets[ name ] * EXRDecoder.columns; + const cOff = EXRDecoder.decodeChannels[ name ]; + + if ( cOff === undefined ) continue; + + tmpOffset.value = lineOffset + lOff; + + for ( let x = 0; x < EXRDecoder.columns; x ++ ) { + + const outIndex = outLineOffset + x * EXRDecoder.outputChannels + cOff; + EXRDecoder.byteArray[ outIndex ] = EXRDecoder.getter( viewer, tmpOffset ); + + } + + } + + } + + } + + } + function parseHeader( dataView, buffer, offset ) { const EXRHeader = {}; if ( dataView.getUint32( 0, true ) != 20000630 ) { // magic - throw new Error( 'THREE.EXRLoader: provided file doesn\'t appear to be in OpenEXR format.' ); + throw new Error( 'THREE.EXRLoader: Provided file doesn\'t appear to be in OpenEXR format.' ); } @@ -2021,7 +2256,7 @@ class EXRLoader extends DataTextureLoader { const attributeName = parseNullTerminatedString( buffer, offset ); - if ( attributeName == 0 ) { + if ( attributeName === '' ) { keepReading = false; @@ -2033,7 +2268,7 @@ class EXRLoader extends DataTextureLoader { if ( attributeValue === undefined ) { - console.warn( `EXRLoader.parse: skipped unknown header attribute type \'${attributeType}\'.` ); + console.warn( `THREE.EXRLoader: Skipped unknown header attribute type \'${attributeType}\'.` ); } else { @@ -2045,10 +2280,10 @@ class EXRLoader extends DataTextureLoader { } - if ( ( spec & ~ 0x04 ) != 0 ) { // unsupported tiled, deep-image, multi-part + if ( ( spec & ~ 0x06 ) != 0 ) { // unsupported deep-image, multi-part - console.error( 'EXRHeader:', EXRHeader ); - throw new Error( 'THREE.EXRLoader: provided file is currently unsupported.' ); + console.error( 'THREE.EXRHeader:', EXRHeader ); + throw new Error( 'THREE.EXRLoader: Provided file is currently unsupported.' ); } @@ -2065,11 +2300,13 @@ class EXRLoader extends DataTextureLoader { offset: offset, width: EXRHeader.dataWindow.xMax - EXRHeader.dataWindow.xMin + 1, height: EXRHeader.dataWindow.yMax - EXRHeader.dataWindow.yMin + 1, - channels: EXRHeader.channels.length, - bytesPerLine: null, + inputChannels: EXRHeader.channels, + channelByteOffsets: {}, + scanOrder: null, + totalBytes: null, + columns: null, lines: null, - inputSize: null, - type: EXRHeader.channels[ 0 ].pixelType, + type: null, uncompress: null, getter: null, format: null, @@ -2079,42 +2316,42 @@ class EXRLoader extends DataTextureLoader { switch ( EXRHeader.compression ) { case 'NO_COMPRESSION': - EXRDecoder.lines = 1; + EXRDecoder.blockHeight = 1; EXRDecoder.uncompress = uncompressRAW; break; case 'RLE_COMPRESSION': - EXRDecoder.lines = 1; + EXRDecoder.blockHeight = 1; EXRDecoder.uncompress = uncompressRLE; break; case 'ZIPS_COMPRESSION': - EXRDecoder.lines = 1; + EXRDecoder.blockHeight = 1; EXRDecoder.uncompress = uncompressZIP; break; case 'ZIP_COMPRESSION': - EXRDecoder.lines = 16; + EXRDecoder.blockHeight = 16; EXRDecoder.uncompress = uncompressZIP; break; case 'PIZ_COMPRESSION': - EXRDecoder.lines = 32; + EXRDecoder.blockHeight = 32; EXRDecoder.uncompress = uncompressPIZ; break; case 'PXR24_COMPRESSION': - EXRDecoder.lines = 16; + EXRDecoder.blockHeight = 16; EXRDecoder.uncompress = uncompressPXR; break; case 'DWAA_COMPRESSION': - EXRDecoder.lines = 32; + EXRDecoder.blockHeight = 32; EXRDecoder.uncompress = uncompressDWA; break; case 'DWAB_COMPRESSION': - EXRDecoder.lines = 256; + EXRDecoder.blockHeight = 256; EXRDecoder.uncompress = uncompressDWA; break; @@ -2123,7 +2360,42 @@ class EXRLoader extends DataTextureLoader { } - EXRDecoder.scanlineBlockSize = EXRDecoder.lines; + const channels = {}; + for ( const channel of EXRHeader.channels ) { + + switch ( channel.name ) { + + case 'Y': + case 'R': + case 'G': + case 'B': + case 'A': + channels[ channel.name ] = true; + EXRDecoder.type = channel.pixelType; + + } + + } + + // RGB images will be converted to RGBA format, preventing software emulation in select devices. + let fillAlpha = false; + + if ( channels.R && channels.G && channels.B ) { + + fillAlpha = ! channels.A; + EXRDecoder.outputChannels = 4; + EXRDecoder.decodeChannels = { R: 0, G: 1, B: 2, A: 3 }; + + } else if ( channels.Y ) { + + EXRDecoder.outputChannels = 1; + EXRDecoder.decodeChannels = { Y: 0 }; + + } else { + + throw new Error( 'EXRLoader.parse: file contains unsupported data channels.' ); + + } if ( EXRDecoder.type == 1 ) { @@ -2132,12 +2404,10 @@ class EXRLoader extends DataTextureLoader { case FloatType: EXRDecoder.getter = parseFloat16; - EXRDecoder.inputSize = INT16_SIZE; break; case HalfFloatType: EXRDecoder.getter = parseUint16; - EXRDecoder.inputSize = INT16_SIZE; break; } @@ -2149,12 +2419,10 @@ class EXRLoader extends DataTextureLoader { case FloatType: EXRDecoder.getter = parseFloat32; - EXRDecoder.inputSize = FLOAT32_SIZE; break; case HalfFloatType: EXRDecoder.getter = decodeFloat32; - EXRDecoder.inputSize = FLOAT32_SIZE; } @@ -2164,15 +2432,7 @@ class EXRLoader extends DataTextureLoader { } - EXRDecoder.blockCount = ( EXRHeader.dataWindow.yMax + 1 ) / EXRDecoder.scanlineBlockSize; - - for ( let i = 0; i < EXRDecoder.blockCount; i ++ ) - parseInt64( dataView, offset ); // scanlineOffset - - // we should be passed the scanline offset table, ready to start reading pixel data. - - // RGB images will be converted to RGBA format, preventing software emulation in select devices. - EXRDecoder.outputChannels = ( ( EXRDecoder.channels == 3 ) ? 4 : EXRDecoder.channels ); + EXRDecoder.columns = EXRDecoder.width; const size = EXRDecoder.width * EXRDecoder.height * EXRDecoder.outputChannels; switch ( outputType ) { @@ -2181,7 +2441,7 @@ class EXRLoader extends DataTextureLoader { EXRDecoder.byteArray = new Float32Array( size ); // Fill initially with 1s for the alpha value if the texture is not RGBA, RGB values will be overwritten - if ( EXRDecoder.channels < EXRDecoder.outputChannels ) + if ( fillAlpha ) EXRDecoder.byteArray.fill( 1, 0, size ); break; @@ -2189,7 +2449,7 @@ class EXRLoader extends DataTextureLoader { case HalfFloatType: EXRDecoder.byteArray = new Uint16Array( size ); - if ( EXRDecoder.channels < EXRDecoder.outputChannels ) + if ( fillAlpha ) EXRDecoder.byteArray.fill( 0x3C00, 0, size ); // Uint16Array holds half float data, 0x3C00 is 1 break; @@ -2200,7 +2460,31 @@ class EXRLoader extends DataTextureLoader { } - EXRDecoder.bytesPerLine = EXRDecoder.width * EXRDecoder.inputSize * EXRDecoder.channels; + let byteOffset = 0; + for ( const channel of EXRHeader.channels ) { + + if ( EXRDecoder.decodeChannels[ channel.name ] !== undefined ) { + + EXRDecoder.channelByteOffsets[ channel.name ] = byteOffset; + + } + + byteOffset += channel.pixelType * 2; + + } + + EXRDecoder.totalBytes = byteOffset; + EXRDecoder.outLineWidth = EXRDecoder.width * EXRDecoder.outputChannels; + + if ( EXRHeader.lineOrder === 'INCREASING_Y' ) { + + EXRDecoder.scanOrder = ( y ) => y; + + } else { + + EXRDecoder.scanOrder = ( y ) => EXRDecoder.height - 1 - y; + + } if ( EXRDecoder.outputChannels == 4 ) { @@ -2214,58 +2498,55 @@ class EXRLoader extends DataTextureLoader { } - return EXRDecoder; - - } - - // start parsing file [START] - - const bufferDataView = new DataView( buffer ); - const uInt8Array = new Uint8Array( buffer ); - const offset = { value: 0 }; + if ( EXRHeader.spec.singleTile ) { - // get header information and validate format. - const EXRHeader = parseHeader( bufferDataView, buffer, offset ); + EXRDecoder.blockHeight = EXRHeader.tiles.ySize; + EXRDecoder.blockWidth = EXRHeader.tiles.xSize; - // get input compression information and prepare decoding. - const EXRDecoder = setupDecoder( EXRHeader, bufferDataView, uInt8Array, offset, this.type ); + const numXLevels = calculateTileLevels( EXRHeader.tiles, EXRDecoder.width, EXRDecoder.height ); + // const numYLevels = calculateTileLevels( EXRHeader.tiles, EXRDecoder.width, EXRDecoder.height ); - const tmpOffset = { value: 0 }; - const channelOffsets = { R: 0, G: 1, B: 2, A: 3, Y: 0 }; + const numXTiles = calculateTiles( numXLevels, EXRDecoder.width, EXRHeader.tiles.xSize, EXRHeader.tiles.roundingMode ); + const numYTiles = calculateTiles( numXLevels, EXRDecoder.height, EXRHeader.tiles.ySize, EXRHeader.tiles.roundingMode ); - for ( let scanlineBlockIdx = 0; scanlineBlockIdx < EXRDecoder.height / EXRDecoder.scanlineBlockSize; scanlineBlockIdx ++ ) { + EXRDecoder.tileCount = numXTiles[ 0 ] * numYTiles[ 0 ]; - const line = parseUint32( bufferDataView, offset ); // line_no - EXRDecoder.size = parseUint32( bufferDataView, offset ); // data_len - EXRDecoder.lines = ( ( line + EXRDecoder.scanlineBlockSize > EXRDecoder.height ) ? ( EXRDecoder.height - line ) : EXRDecoder.scanlineBlockSize ); + for ( let l = 0; l < numXLevels; l ++ ) + for ( let y = 0; y < numYTiles[ l ]; y ++ ) + for ( let x = 0; x < numXTiles[ l ]; x ++ ) + parseInt64( dataView, offset ); // tileOffset - const isCompressed = EXRDecoder.size < EXRDecoder.lines * EXRDecoder.bytesPerLine; - const viewer = isCompressed ? EXRDecoder.uncompress( EXRDecoder ) : uncompressRAW( EXRDecoder ); + EXRDecoder.decode = parseTiles.bind( EXRDecoder ); - offset.value += EXRDecoder.size; + } else { - for ( let line_y = 0; line_y < EXRDecoder.scanlineBlockSize; line_y ++ ) { + EXRDecoder.blockWidth = EXRDecoder.width; + const blockCount = Math.ceil( EXRDecoder.height / EXRDecoder.blockHeight ); - const true_y = line_y + scanlineBlockIdx * EXRDecoder.scanlineBlockSize; - if ( true_y >= EXRDecoder.height ) break; + for ( let i = 0; i < blockCount; i ++ ) + parseInt64( dataView, offset ); // scanlineOffset - for ( let channelID = 0; channelID < EXRDecoder.channels; channelID ++ ) { + EXRDecoder.decode = parseScanline.bind( EXRDecoder ); - const cOff = channelOffsets[ EXRHeader.channels[ channelID ].name ]; + } - for ( let x = 0; x < EXRDecoder.width; x ++ ) { + return EXRDecoder; - tmpOffset.value = ( line_y * ( EXRDecoder.channels * EXRDecoder.width ) + channelID * EXRDecoder.width + x ) * EXRDecoder.inputSize; - const outIndex = ( EXRDecoder.height - 1 - true_y ) * ( EXRDecoder.width * EXRDecoder.outputChannels ) + x * EXRDecoder.outputChannels + cOff; - EXRDecoder.byteArray[ outIndex ] = EXRDecoder.getter( viewer, tmpOffset ); + } - } + // start parsing file [START] + const offset = { value: 0 }; + const bufferDataView = new DataView( buffer ); + const uInt8Array = new Uint8Array( buffer ); - } + // get header information and validate format. + const EXRHeader = parseHeader( bufferDataView, buffer, offset ); - } + // get input compression information and prepare decoding. + const EXRDecoder = setupDecoder( EXRHeader, bufferDataView, uInt8Array, offset, this.type ); - } + // parse input data + EXRDecoder.decode(); return { header: EXRHeader, @@ -2279,6 +2560,12 @@ class EXRLoader extends DataTextureLoader { } + /** + * Sets the texture type. + * + * @param {(HalfFloatType|FloatType)} value - The texture type to set. + * @return {RGBMLoader} A reference to this loader. + */ setDataType( value ) { this.type = value; diff --git a/examples/jsm/loaders/FBXLoader.js b/examples/jsm/loaders/FBXLoader.js index 8277361504b280..49ff2c7c808680 100644 --- a/examples/jsm/loaders/FBXLoader.js +++ b/examples/jsm/loaders/FBXLoader.js @@ -5,6 +5,7 @@ import { BufferGeometry, ClampToEdgeWrapping, Color, + ColorManagement, DirectionalLight, EquirectangularReflectionMapping, Euler, @@ -23,55 +24,79 @@ import { MeshPhongMaterial, NumberKeyframeTrack, Object3D, - OrthographicCamera, PerspectiveCamera, PointLight, PropertyBinding, Quaternion, QuaternionKeyframeTrack, RepeatWrapping, + SRGBColorSpace, + ShapeUtils, Skeleton, SkinnedMesh, SpotLight, Texture, TextureLoader, Uint16BufferAttribute, + Vector2, Vector3, Vector4, - VectorKeyframeTrack, - SRGBColorSpace + VectorKeyframeTrack } from 'three'; + import * as fflate from '../libs/fflate.module.js'; import { NURBSCurve } from '../curves/NURBSCurve.js'; +let fbxTree; +let connections; +let sceneGraph; + /** - * Loader loads FBX file and generates Group representing FBX scene. - * Requires FBX file to be >= 7.0 and in ASCII or >= 6400 in Binary format - * Versions lower than this may load but will probably have errors + * A loader for the FBX format. + * + * Requires FBX file to be >= 7.0 and in ASCII or >= 6400 in Binary format. + * Versions lower than this may load but will probably have errors. * * Needs Support: - * Morph normals / blend shape normals + * - Morph normals / blend shape normals * * FBX format references: - * https://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_index_html (C++ SDK reference) + * - [C++ SDK reference]{@link https://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_index_html} * * Binary format specification: - * https://code.blender.org/2013/08/fbx-binary-file-format-specification/ + * - [FBX binary file format specification]{@link https://code.blender.org/2013/08/fbx-binary-file-format-specification/} + * + * ```js + * const loader = new FBXLoader(); + * const object = await loader.loadAsync( 'models/fbx/stanford-bunny.fbx' ); + * scene.add( object ); + * ``` + * + * @augments Loader + * @three_import import { FBXLoader } from 'three/addons/loaders/FBXLoader.js'; */ - - -let fbxTree; -let connections; -let sceneGraph; - class FBXLoader extends Loader { + /** + * Constructs a new FBX loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); } + /** + * Starts loading from the given URL and passes the loaded FBX asset + * to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(Group)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ load( url, onLoad, onProgress, onError ) { const scope = this; @@ -110,6 +135,13 @@ class FBXLoader extends Loader { } + /** + * Parses the given FBX data and returns the resulting group. + * + * @param {ArrayBuffer} FBXBuffer - The raw FBX data as an array buffer. + * @param {string} path - The URL base path. + * @return {Group} An object representing the parsed asset. + */ parse( FBXBuffer, path ) { if ( isFbxFormatBinary( FBXBuffer ) ) { @@ -316,6 +348,11 @@ class FBXTreeParser { type = 'image/tga'; break; + case 'webp': + + type = 'image/webp'; + break; + default: console.warn( 'FBXLoader: Image type "' + extension + '" is not supported.' ); @@ -405,56 +442,46 @@ class FBXTreeParser { // load a texture specified as a blob or data URI, or via an external URL using TextureLoader loadTexture( textureNode, images ) { - let fileName; - - const currentPath = this.textureLoader.path; - - const children = connections.get( textureNode.id ).children; + const extension = textureNode.FileName.split( '.' ).pop().toLowerCase(); - if ( children !== undefined && children.length > 0 && images[ children[ 0 ].ID ] !== undefined ) { + let loader = this.manager.getHandler( `.${extension}` ); + if ( loader === null ) loader = this.textureLoader; - fileName = images[ children[ 0 ].ID ]; - - if ( fileName.indexOf( 'blob:' ) === 0 || fileName.indexOf( 'data:' ) === 0 ) { + const loaderPath = loader.path; - this.textureLoader.setPath( undefined ); + if ( ! loaderPath ) { - } + loader.setPath( this.textureLoader.path ); } - let texture; - - const extension = textureNode.FileName.slice( - 3 ).toLowerCase(); - - if ( extension === 'tga' ) { + const children = connections.get( textureNode.id ).children; - const loader = this.manager.getHandler( '.tga' ); + let fileName; - if ( loader === null ) { + if ( children !== undefined && children.length > 0 && images[ children[ 0 ].ID ] !== undefined ) { - console.warn( 'FBXLoader: TGA loader not found, creating placeholder texture for', textureNode.RelativeFilename ); - texture = new Texture(); + fileName = images[ children[ 0 ].ID ]; - } else { + if ( fileName.indexOf( 'blob:' ) === 0 || fileName.indexOf( 'data:' ) === 0 ) { - loader.setPath( this.textureLoader.path ); - texture = loader.load( fileName ); + loader.setPath( undefined ); } - } else if ( extension === 'psd' ) { - - console.warn( 'FBXLoader: PSD textures are not supported, creating placeholder texture for', textureNode.RelativeFilename ); - texture = new Texture(); + } - } else { + if ( fileName === undefined ) { - texture = this.textureLoader.load( fileName ); + console.warn( 'FBXLoader: Undefined filename, creating placeholder texture.' ); + return new Texture(); } - this.textureLoader.setPath( currentPath ); + const texture = loader.load( fileName ); + + // revert to initial path + loader.setPath( loaderPath ); return texture; @@ -542,12 +569,12 @@ class FBXTreeParser { if ( materialNode.Diffuse ) { - parameters.color = new Color().fromArray( materialNode.Diffuse.value ).convertSRGBToLinear(); + parameters.color = ColorManagement.colorSpaceToWorking( new Color().fromArray( materialNode.Diffuse.value ), SRGBColorSpace ); } else if ( materialNode.DiffuseColor && ( materialNode.DiffuseColor.type === 'Color' || materialNode.DiffuseColor.type === 'ColorRGB' ) ) { // The blender exporter exports diffuse here instead of in materialNode.Diffuse - parameters.color = new Color().fromArray( materialNode.DiffuseColor.value ).convertSRGBToLinear(); + parameters.color = ColorManagement.colorSpaceToWorking( new Color().fromArray( materialNode.DiffuseColor.value ), SRGBColorSpace ); } @@ -559,12 +586,12 @@ class FBXTreeParser { if ( materialNode.Emissive ) { - parameters.emissive = new Color().fromArray( materialNode.Emissive.value ).convertSRGBToLinear(); + parameters.emissive = ColorManagement.colorSpaceToWorking( new Color().fromArray( materialNode.Emissive.value ), SRGBColorSpace ); } else if ( materialNode.EmissiveColor && ( materialNode.EmissiveColor.type === 'Color' || materialNode.EmissiveColor.type === 'ColorRGB' ) ) { // The blender exporter exports emissive color here instead of in materialNode.Emissive - parameters.emissive = new Color().fromArray( materialNode.EmissiveColor.value ).convertSRGBToLinear(); + parameters.emissive = ColorManagement.colorSpaceToWorking( new Color().fromArray( materialNode.EmissiveColor.value ), SRGBColorSpace ); } @@ -574,9 +601,19 @@ class FBXTreeParser { } - if ( materialNode.Opacity ) { + // the transparency handling is implemented based on Blender/Unity's approach: https://github.com/sobotka/blender-addons/blob/7d80f2f97161fc8e353a657b179b9aa1f8e5280b/io_scene_fbx/import_fbx.py#L1444-L1459 + + parameters.opacity = 1 - ( materialNode.TransparencyFactor ? parseFloat( materialNode.TransparencyFactor.value ) : 0 ); + + if ( parameters.opacity === 1 || parameters.opacity === 0 ) { - parameters.opacity = parseFloat( materialNode.Opacity.value ); + parameters.opacity = ( materialNode.Opacity ? parseFloat( materialNode.Opacity.value ) : null ); + + if ( parameters.opacity === null ) { + + parameters.opacity = 1 - ( materialNode.TransparentColor ? parseFloat( materialNode.TransparentColor.value[ 0 ] ) : 0 ); + + } } @@ -600,12 +637,12 @@ class FBXTreeParser { if ( materialNode.Specular ) { - parameters.specular = new Color().fromArray( materialNode.Specular.value ).convertSRGBToLinear(); + parameters.specular = ColorManagement.colorSpaceToWorking( new Color().fromArray( materialNode.Specular.value ), SRGBColorSpace ); } else if ( materialNode.SpecularColor && materialNode.SpecularColor.type === 'Color' ) { // The blender exporter exports specular color here instead of in materialNode.Specular - parameters.specular = new Color().fromArray( materialNode.SpecularColor.value ).convertSRGBToLinear(); + parameters.specular = ColorManagement.colorSpaceToWorking( new Color().fromArray( materialNode.SpecularColor.value ), SRGBColorSpace ); } @@ -883,7 +920,7 @@ class FBXTreeParser { this.bindSkeleton( deformers.skeletons, geometryMap, modelMap ); - this.createAmbientLight(); + this.addGlobalSceneSettings(); sceneGraph.traverse( function ( node ) { @@ -961,6 +998,7 @@ class FBXTreeParser { } model.name = node.attrName ? PropertyBinding.sanitizeNodeName( node.attrName ) : ''; + model.userData.originalName = node.attrName; model.ID = id; @@ -997,12 +1035,13 @@ class FBXTreeParser { // set name and id here - otherwise in cases where "subBone" is created it will not have a name / id bone.name = name ? PropertyBinding.sanitizeNodeName( name ) : ''; + bone.userData.originalName = name; bone.ID = id; skeleton.bones[ i ] = bone; // In cases where a bone is shared between multiple meshes - // duplicate the bone here and and it as a child of the first bone + // duplicate the bone here and add it as a child of the first bone if ( subBone !== null ) { bone.add( subBone ); @@ -1096,7 +1135,8 @@ class FBXTreeParser { break; case 1: // Orthographic - model = new OrthographicCamera( - width / 2, width / 2, height / 2, - height / 2, nearClippingPlane, farClippingPlane ); + console.warn( 'THREE.FBXLoader: Orthographic cameras not supported yet.' ); + model = new Object3D(); break; default: @@ -1153,7 +1193,7 @@ class FBXTreeParser { if ( lightAttribute.Color !== undefined ) { - color = new Color().fromArray( lightAttribute.Color.value ).convertSRGBToLinear(); + color = ColorManagement.colorSpaceToWorking( new Color().fromArray( lightAttribute.Color.value ), SRGBColorSpace ); } @@ -1270,7 +1310,10 @@ class FBXTreeParser { } else { - material = new MeshPhongMaterial( { color: 0xcccccc } ); + material = new MeshPhongMaterial( { + name: Loader.DEFAULT_MATERIAL_NAME, + color: 0xcccccc + } ); materials.push( material ); } @@ -1285,6 +1328,35 @@ class FBXTreeParser { } + // Sanitization: If geometry has groups, then it must match the provided material array. + // If not, we need to clean up the `group.materialIndex` properties inside the groups and point at a (new) default material. + // This isn't well defined; Unity creates default material, while Blender implicitly uses the previous material in the list. + if ( geometry.groups.length > 0 ) { + + let needsDefaultMaterial = false; + + for ( let i = 0, il = geometry.groups.length; i < il; i ++ ) { + + const group = geometry.groups[ i ]; + + if ( group.materialIndex < 0 || group.materialIndex >= materials.length ) { + + group.materialIndex = materials.length; + needsDefaultMaterial = true; + + } + + } + + if ( needsDefaultMaterial ) { + + const defaultMaterial = new MeshPhongMaterial(); + materials.push( defaultMaterial ); + + } + + } + if ( geometry.FBX_Deformer ) { model = new SkinnedMesh( geometry, material ); @@ -1311,7 +1383,11 @@ class FBXTreeParser { }, null ); // FBX does not list materials for Nurbs lines, so we'll just put our own in here. - const material = new LineBasicMaterial( { color: 0x3300ff, linewidth: 1 } ); + const material = new LineBasicMaterial( { + name: Loader.DEFAULT_MATERIAL_NAME, + color: 0x3300ff, + linewidth: 1 + } ); return new Line( geometry, material ); } @@ -1324,7 +1400,7 @@ class FBXTreeParser { if ( 'InheritType' in modelNode ) transformData.inheritType = parseInt( modelNode.InheritType.value ); if ( 'RotationOrder' in modelNode ) transformData.eulerOrder = getEulerOrder( modelNode.RotationOrder.value ); - else transformData.eulerOrder = 'ZYX'; + else transformData.eulerOrder = getEulerOrder( 0 ); if ( 'Lcl_Translation' in modelNode ) transformData.translation = modelNode.Lcl_Translation.value; @@ -1457,20 +1533,31 @@ class FBXTreeParser { } - // Parse ambient color in FBXTree.GlobalSettings - if it's not set to black (default), create an ambient light - createAmbientLight() { + addGlobalSceneSettings() { + + if ( 'GlobalSettings' in fbxTree ) { + + if ( 'AmbientColor' in fbxTree.GlobalSettings ) { - if ( 'GlobalSettings' in fbxTree && 'AmbientColor' in fbxTree.GlobalSettings ) { + // Parse ambient color - if it's not set to black (default), create an ambient light + + const ambientColor = fbxTree.GlobalSettings.AmbientColor.value; + const r = ambientColor[ 0 ]; + const g = ambientColor[ 1 ]; + const b = ambientColor[ 2 ]; + + if ( r !== 0 || g !== 0 || b !== 0 ) { + + const color = new Color().setRGB( r, g, b, SRGBColorSpace ); + sceneGraph.add( new AmbientLight( color, 1 ) ); + + } - const ambientColor = fbxTree.GlobalSettings.AmbientColor.value; - const r = ambientColor[ 0 ]; - const g = ambientColor[ 1 ]; - const b = ambientColor[ 2 ]; + } - if ( r !== 0 || g !== 0 || b !== 0 ) { + if ( 'UnitScaleFactor' in fbxTree.GlobalSettings ) { - const color = new Color( r, g, b ).convertSRGBToLinear(); - sceneGraph.add( new AmbientLight( color, 1 ) ); + sceneGraph.userData.unitScaleFactor = fbxTree.GlobalSettings.UnitScaleFactor.value; } @@ -1697,7 +1784,7 @@ class GeometryParser { geoInfo.vertexPositions = ( geoNode.Vertices !== undefined ) ? geoNode.Vertices.a : []; geoInfo.vertexIndices = ( geoNode.PolygonVertexIndex !== undefined ) ? geoNode.PolygonVertexIndex.a : []; - if ( geoNode.LayerElementColor ) { + if ( geoNode.LayerElementColor && geoNode.LayerElementColor.Color ) { geoInfo.color = this.parseVertexColors( geoNode.LayerElementColor[ 0 ] ); @@ -1935,8 +2022,6 @@ class GeometryParser { if ( endOfFace ) { - if ( faceLength > 4 ) console.warn( 'THREE.FBXLoader: Polygons with more than four sides are not supported. Make sure to triangulate the geometry during export.' ); - scope.genFace( buffers, geoInfo, facePositionIndexes, materialIndex, faceNormals, faceColors, faceUVs, faceWeights, faceWeightIndices, faceLength ); polygonIndex ++; @@ -1958,70 +2043,163 @@ class GeometryParser { } + // See https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal + getNormalNewell( vertices ) { + + const normal = new Vector3( 0.0, 0.0, 0.0 ); + + for ( let i = 0; i < vertices.length; i ++ ) { + + const current = vertices[ i ]; + const next = vertices[ ( i + 1 ) % vertices.length ]; + + normal.x += ( current.y - next.y ) * ( current.z + next.z ); + normal.y += ( current.z - next.z ) * ( current.x + next.x ); + normal.z += ( current.x - next.x ) * ( current.y + next.y ); + + } + + normal.normalize(); + + return normal; + + } + + getNormalTangentAndBitangent( vertices ) { + + const normalVector = this.getNormalNewell( vertices ); + // Avoid up being equal or almost equal to normalVector + const up = Math.abs( normalVector.z ) > 0.5 ? new Vector3( 0.0, 1.0, 0.0 ) : new Vector3( 0.0, 0.0, 1.0 ); + const tangent = up.cross( normalVector ).normalize(); + const bitangent = normalVector.clone().cross( tangent ).normalize(); + + return { + normal: normalVector, + tangent: tangent, + bitangent: bitangent + }; + + } + + flattenVertex( vertex, normalTangent, normalBitangent ) { + + return new Vector2( + vertex.dot( normalTangent ), + vertex.dot( normalBitangent ) + ); + + } + // Generate data for a single face in a geometry. If the face is a quad then split it into 2 tris genFace( buffers, geoInfo, facePositionIndexes, materialIndex, faceNormals, faceColors, faceUVs, faceWeights, faceWeightIndices, faceLength ) { - for ( let i = 2; i < faceLength; i ++ ) { + let triangles; + + if ( faceLength > 3 ) { + + // Triangulate n-gon using earcut + + const vertices = []; + // in morphing scenario vertexPositions represent morphPositions + // while baseVertexPositions represent the original geometry's positions + const positions = geoInfo.baseVertexPositions || geoInfo.vertexPositions; + for ( let i = 0; i < facePositionIndexes.length; i += 3 ) { + + vertices.push( + new Vector3( + positions[ facePositionIndexes[ i ] ], + positions[ facePositionIndexes[ i + 1 ] ], + positions[ facePositionIndexes[ i + 2 ] ] + ) + ); + + } + + const { tangent, bitangent } = this.getNormalTangentAndBitangent( vertices ); + const triangulationInput = []; + + for ( const vertex of vertices ) { + + triangulationInput.push( this.flattenVertex( vertex, tangent, bitangent ) ); + + } + + // When vertices is an array of [0,0,0] elements (which is the case for vertices not participating in morph) + // the triangulationInput will be an array of [0,0] elements + // resulting in an array of 0 triangles being returned from ShapeUtils.triangulateShape + // leading to not pushing into buffers.vertex the redundant vertices (the vertices that are not morphed). + // That's why, in order to support morphing scenario, "positions" is looking first for baseVertexPositions, + // so that we don't end up with an array of 0 triangles for the faces not participating in morph. + triangles = ShapeUtils.triangulateShape( triangulationInput, [] ); + + } else { + + // Regular triangle, skip earcut triangulation step + triangles = [[ 0, 1, 2 ]]; + + } + + for ( const [ i0, i1, i2 ] of triangles ) { - buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ 0 ] ] ); - buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ 1 ] ] ); - buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ 2 ] ] ); + buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i0 * 3 ] ] ); + buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i0 * 3 + 1 ] ] ); + buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i0 * 3 + 2 ] ] ); - buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ ( i - 1 ) * 3 ] ] ); - buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ ( i - 1 ) * 3 + 1 ] ] ); - buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ ( i - 1 ) * 3 + 2 ] ] ); + buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i1 * 3 ] ] ); + buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i1 * 3 + 1 ] ] ); + buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i1 * 3 + 2 ] ] ); - buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i * 3 ] ] ); - buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i * 3 + 1 ] ] ); - buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i * 3 + 2 ] ] ); + buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i2 * 3 ] ] ); + buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i2 * 3 + 1 ] ] ); + buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i2 * 3 + 2 ] ] ); if ( geoInfo.skeleton ) { - buffers.vertexWeights.push( faceWeights[ 0 ] ); - buffers.vertexWeights.push( faceWeights[ 1 ] ); - buffers.vertexWeights.push( faceWeights[ 2 ] ); - buffers.vertexWeights.push( faceWeights[ 3 ] ); + buffers.vertexWeights.push( faceWeights[ i0 * 4 ] ); + buffers.vertexWeights.push( faceWeights[ i0 * 4 + 1 ] ); + buffers.vertexWeights.push( faceWeights[ i0 * 4 + 2 ] ); + buffers.vertexWeights.push( faceWeights[ i0 * 4 + 3 ] ); - buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 ] ); - buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 + 1 ] ); - buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 + 2 ] ); - buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 + 3 ] ); + buffers.vertexWeights.push( faceWeights[ i1 * 4 ] ); + buffers.vertexWeights.push( faceWeights[ i1 * 4 + 1 ] ); + buffers.vertexWeights.push( faceWeights[ i1 * 4 + 2 ] ); + buffers.vertexWeights.push( faceWeights[ i1 * 4 + 3 ] ); - buffers.vertexWeights.push( faceWeights[ i * 4 ] ); - buffers.vertexWeights.push( faceWeights[ i * 4 + 1 ] ); - buffers.vertexWeights.push( faceWeights[ i * 4 + 2 ] ); - buffers.vertexWeights.push( faceWeights[ i * 4 + 3 ] ); + buffers.vertexWeights.push( faceWeights[ i2 * 4 ] ); + buffers.vertexWeights.push( faceWeights[ i2 * 4 + 1 ] ); + buffers.vertexWeights.push( faceWeights[ i2 * 4 + 2 ] ); + buffers.vertexWeights.push( faceWeights[ i2 * 4 + 3 ] ); - buffers.weightsIndices.push( faceWeightIndices[ 0 ] ); - buffers.weightsIndices.push( faceWeightIndices[ 1 ] ); - buffers.weightsIndices.push( faceWeightIndices[ 2 ] ); - buffers.weightsIndices.push( faceWeightIndices[ 3 ] ); + buffers.weightsIndices.push( faceWeightIndices[ i0 * 4 ] ); + buffers.weightsIndices.push( faceWeightIndices[ i0 * 4 + 1 ] ); + buffers.weightsIndices.push( faceWeightIndices[ i0 * 4 + 2 ] ); + buffers.weightsIndices.push( faceWeightIndices[ i0 * 4 + 3 ] ); - buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 ] ); - buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 + 1 ] ); - buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 + 2 ] ); - buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 + 3 ] ); + buffers.weightsIndices.push( faceWeightIndices[ i1 * 4 ] ); + buffers.weightsIndices.push( faceWeightIndices[ i1 * 4 + 1 ] ); + buffers.weightsIndices.push( faceWeightIndices[ i1 * 4 + 2 ] ); + buffers.weightsIndices.push( faceWeightIndices[ i1 * 4 + 3 ] ); - buffers.weightsIndices.push( faceWeightIndices[ i * 4 ] ); - buffers.weightsIndices.push( faceWeightIndices[ i * 4 + 1 ] ); - buffers.weightsIndices.push( faceWeightIndices[ i * 4 + 2 ] ); - buffers.weightsIndices.push( faceWeightIndices[ i * 4 + 3 ] ); + buffers.weightsIndices.push( faceWeightIndices[ i2 * 4 ] ); + buffers.weightsIndices.push( faceWeightIndices[ i2 * 4 + 1 ] ); + buffers.weightsIndices.push( faceWeightIndices[ i2 * 4 + 2 ] ); + buffers.weightsIndices.push( faceWeightIndices[ i2 * 4 + 3 ] ); } if ( geoInfo.color ) { - buffers.colors.push( faceColors[ 0 ] ); - buffers.colors.push( faceColors[ 1 ] ); - buffers.colors.push( faceColors[ 2 ] ); + buffers.colors.push( faceColors[ i0 * 3 ] ); + buffers.colors.push( faceColors[ i0 * 3 + 1 ] ); + buffers.colors.push( faceColors[ i0 * 3 + 2 ] ); - buffers.colors.push( faceColors[ ( i - 1 ) * 3 ] ); - buffers.colors.push( faceColors[ ( i - 1 ) * 3 + 1 ] ); - buffers.colors.push( faceColors[ ( i - 1 ) * 3 + 2 ] ); + buffers.colors.push( faceColors[ i1 * 3 ] ); + buffers.colors.push( faceColors[ i1 * 3 + 1 ] ); + buffers.colors.push( faceColors[ i1 * 3 + 2 ] ); - buffers.colors.push( faceColors[ i * 3 ] ); - buffers.colors.push( faceColors[ i * 3 + 1 ] ); - buffers.colors.push( faceColors[ i * 3 + 2 ] ); + buffers.colors.push( faceColors[ i2 * 3 ] ); + buffers.colors.push( faceColors[ i2 * 3 + 1 ] ); + buffers.colors.push( faceColors[ i2 * 3 + 2 ] ); } @@ -2035,17 +2213,17 @@ class GeometryParser { if ( geoInfo.normal ) { - buffers.normal.push( faceNormals[ 0 ] ); - buffers.normal.push( faceNormals[ 1 ] ); - buffers.normal.push( faceNormals[ 2 ] ); + buffers.normal.push( faceNormals[ i0 * 3 ] ); + buffers.normal.push( faceNormals[ i0 * 3 + 1 ] ); + buffers.normal.push( faceNormals[ i0 * 3 + 2 ] ); - buffers.normal.push( faceNormals[ ( i - 1 ) * 3 ] ); - buffers.normal.push( faceNormals[ ( i - 1 ) * 3 + 1 ] ); - buffers.normal.push( faceNormals[ ( i - 1 ) * 3 + 2 ] ); + buffers.normal.push( faceNormals[ i1 * 3 ] ); + buffers.normal.push( faceNormals[ i1 * 3 + 1 ] ); + buffers.normal.push( faceNormals[ i1 * 3 + 2 ] ); - buffers.normal.push( faceNormals[ i * 3 ] ); - buffers.normal.push( faceNormals[ i * 3 + 1 ] ); - buffers.normal.push( faceNormals[ i * 3 + 2 ] ); + buffers.normal.push( faceNormals[ i2 * 3 ] ); + buffers.normal.push( faceNormals[ i2 * 3 + 1 ] ); + buffers.normal.push( faceNormals[ i2 * 3 + 2 ] ); } @@ -2055,14 +2233,14 @@ class GeometryParser { if ( buffers.uvs[ j ] === undefined ) buffers.uvs[ j ] = []; - buffers.uvs[ j ].push( faceUVs[ j ][ 0 ] ); - buffers.uvs[ j ].push( faceUVs[ j ][ 1 ] ); + buffers.uvs[ j ].push( faceUVs[ j ][ i0 * 2 ] ); + buffers.uvs[ j ].push( faceUVs[ j ][ i0 * 2 + 1 ] ); - buffers.uvs[ j ].push( faceUVs[ j ][ ( i - 1 ) * 2 ] ); - buffers.uvs[ j ].push( faceUVs[ j ][ ( i - 1 ) * 2 + 1 ] ); + buffers.uvs[ j ].push( faceUVs[ j ][ i1 * 2 ] ); + buffers.uvs[ j ].push( faceUVs[ j ][ i1 * 2 + 1 ] ); - buffers.uvs[ j ].push( faceUVs[ j ][ i * 2 ] ); - buffers.uvs[ j ].push( faceUVs[ j ][ i * 2 + 1 ] ); + buffers.uvs[ j ].push( faceUVs[ j ][ i2 * 2 ] ); + buffers.uvs[ j ].push( faceUVs[ j ][ i2 * 2 + 1 ] ); } ); @@ -2106,17 +2284,18 @@ class GeometryParser { // Normal and position attributes only have data for the vertices that are affected by the morph genMorphGeometry( parentGeo, parentGeoNode, morphGeoNode, preTransform, name ) { - const vertexIndices = ( parentGeoNode.PolygonVertexIndex !== undefined ) ? parentGeoNode.PolygonVertexIndex.a : []; + const basePositions = parentGeoNode.Vertices !== undefined ? parentGeoNode.Vertices.a : []; + const baseIndices = parentGeoNode.PolygonVertexIndex !== undefined ? parentGeoNode.PolygonVertexIndex.a : []; - const morphPositionsSparse = ( morphGeoNode.Vertices !== undefined ) ? morphGeoNode.Vertices.a : []; - const indices = ( morphGeoNode.Indexes !== undefined ) ? morphGeoNode.Indexes.a : []; + const morphPositionsSparse = morphGeoNode.Vertices !== undefined ? morphGeoNode.Vertices.a : []; + const morphIndices = morphGeoNode.Indexes !== undefined ? morphGeoNode.Indexes.a : []; const length = parentGeo.attributes.position.count * 3; const morphPositions = new Float32Array( length ); - for ( let i = 0; i < indices.length; i ++ ) { + for ( let i = 0; i < morphIndices.length; i ++ ) { - const morphIndex = indices[ i ] * 3; + const morphIndex = morphIndices[ i ] * 3; morphPositions[ morphIndex ] = morphPositionsSparse[ i * 3 ]; morphPositions[ morphIndex + 1 ] = morphPositionsSparse[ i * 3 + 1 ]; @@ -2126,9 +2305,9 @@ class GeometryParser { // TODO: add morph normal support const morphGeoInfo = { - vertexIndices: vertexIndices, + vertexIndices: baseIndices, vertexPositions: morphPositions, - + baseVertexPositions: basePositions }; const morphBuffers = this.genBuffers( morphGeoInfo ); @@ -2211,7 +2390,9 @@ class GeometryParser { for ( let i = 0, c = new Color(); i < buffer.length; i += 4 ) { - c.fromArray( buffer, i ).convertSRGBToLinear().toArray( buffer, i ); + c.fromArray( buffer, i ); + ColorManagement.colorSpaceToWorking( c, SRGBColorSpace ); + c.toArray( buffer, i ); } @@ -2584,7 +2765,7 @@ class AnimationParser { } // parse nodes in FBXTree.Objects.AnimationStack. These are the top level node in the animation - // hierarchy. Each Stack node will be used to create a AnimationClip + // hierarchy. Each Stack node will be used to create an AnimationClip parseAnimStacks( layersMap ) { const rawStacks = fbxTree.Objects.AnimationStack; @@ -2639,13 +2820,11 @@ class AnimationParser { const tracks = []; let initialPosition = new Vector3(); - let initialRotation = new Quaternion(); let initialScale = new Vector3(); - if ( rawTracks.transform ) rawTracks.transform.decompose( initialPosition, initialRotation, initialScale ); + if ( rawTracks.transform ) rawTracks.transform.decompose( initialPosition, new Quaternion(), initialScale ); initialPosition = initialPosition.toArray(); - initialRotation = new Euler().setFromQuaternion( initialRotation, rawTracks.eulerOrder ).toArray(); initialScale = initialScale.toArray(); if ( rawTracks.T !== undefined && Object.keys( rawTracks.T.curves ).length > 0 ) { @@ -2657,7 +2836,7 @@ class AnimationParser { if ( rawTracks.R !== undefined && Object.keys( rawTracks.R.curves ).length > 0 ) { - const rotationTrack = this.generateRotationTrack( rawTracks.modelName, rawTracks.R.curves, initialRotation, rawTracks.preRotation, rawTracks.postRotation, rawTracks.eulerOrder ); + const rotationTrack = this.generateRotationTrack( rawTracks.modelName, rawTracks.R.curves, rawTracks.preRotation, rawTracks.postRotation, rawTracks.eulerOrder ); if ( rotationTrack !== undefined ) tracks.push( rotationTrack ); } @@ -2689,36 +2868,27 @@ class AnimationParser { } - generateRotationTrack( modelName, curves, initialValue, preRotation, postRotation, eulerOrder ) { + generateRotationTrack( modelName, curves, preRotation, postRotation, eulerOrder ) { - if ( curves.x !== undefined ) { + let times; + let values; - this.interpolateRotations( curves.x ); - curves.x.values = curves.x.values.map( MathUtils.degToRad ); - - } + if ( curves.x !== undefined && curves.y !== undefined && curves.z !== undefined ) { - if ( curves.y !== undefined ) { + const result = this.interpolateRotations( curves.x, curves.y, curves.z, eulerOrder ); - this.interpolateRotations( curves.y ); - curves.y.values = curves.y.values.map( MathUtils.degToRad ); + times = result[ 0 ]; + values = result[ 1 ]; } - if ( curves.z !== undefined ) { - - this.interpolateRotations( curves.z ); - curves.z.values = curves.z.values.map( MathUtils.degToRad ); - - } - - const times = this.getTimesForAllAxes( curves ); - const values = this.getKeyframeTrackValues( times, curves, initialValue ); + // For Maya models using "Joint Orient", Euler order only applies to rotation, not pre/post-rotations + const defaultEulerOrder = getEulerOrder( 0 ); if ( preRotation !== undefined ) { preRotation = preRotation.map( MathUtils.degToRad ); - preRotation.push( eulerOrder ); + preRotation.push( defaultEulerOrder ); preRotation = new Euler().fromArray( preRotation ); preRotation = new Quaternion().setFromEuler( preRotation ); @@ -2728,7 +2898,7 @@ class AnimationParser { if ( postRotation !== undefined ) { postRotation = postRotation.map( MathUtils.degToRad ); - postRotation.push( eulerOrder ); + postRotation.push( defaultEulerOrder ); postRotation = new Euler().fromArray( postRotation ); postRotation = new Quaternion().setFromEuler( postRotation ).invert(); @@ -2740,15 +2910,32 @@ class AnimationParser { const quaternionValues = []; + if ( ! values || ! times ) return new QuaternionKeyframeTrack( modelName + '.quaternion', [ 0 ], [ 0 ] ); + for ( let i = 0; i < values.length; i += 3 ) { euler.set( values[ i ], values[ i + 1 ], values[ i + 2 ], eulerOrder ); - quaternion.setFromEuler( euler ); if ( preRotation !== undefined ) quaternion.premultiply( preRotation ); if ( postRotation !== undefined ) quaternion.multiply( postRotation ); + // Check unroll + if ( i > 2 ) { + + const prevQuat = new Quaternion().fromArray( + quaternionValues, + ( ( i - 3 ) / 3 ) * 4 + ); + + if ( prevQuat.dot( quaternion ) < 0 ) { + + quaternion.set( - quaternion.x, - quaternion.y, - quaternion.z, - quaternion.w ); + + } + + } + quaternion.toArray( quaternionValues, ( i / 3 ) * 4 ); } @@ -2879,47 +3066,110 @@ class AnimationParser { // Rotations are defined as Euler angles which can have values of any size // These will be converted to quaternions which don't support values greater than // PI, so we'll interpolate large rotations - interpolateRotations( curve ) { + interpolateRotations( curvex, curvey, curvez, eulerOrder ) { + + const times = []; + const values = []; + + // Add first frame + times.push( curvex.times[ 0 ] ); + values.push( MathUtils.degToRad( curvex.values[ 0 ] ) ); + values.push( MathUtils.degToRad( curvey.values[ 0 ] ) ); + values.push( MathUtils.degToRad( curvez.values[ 0 ] ) ); + + for ( let i = 1; i < curvex.values.length; i ++ ) { - for ( let i = 1; i < curve.values.length; i ++ ) { + const initialValue = [ + curvex.values[ i - 1 ], + curvey.values[ i - 1 ], + curvez.values[ i - 1 ], + ]; - const initialValue = curve.values[ i - 1 ]; - const valuesSpan = curve.values[ i ] - initialValue; + if ( isNaN( initialValue[ 0 ] ) || isNaN( initialValue[ 1 ] ) || isNaN( initialValue[ 2 ] ) ) { - const absoluteSpan = Math.abs( valuesSpan ); + continue; - if ( absoluteSpan >= 180 ) { + } + + const initialValueRad = initialValue.map( MathUtils.degToRad ); + + const currentValue = [ + curvex.values[ i ], + curvey.values[ i ], + curvez.values[ i ], + ]; - const numSubIntervals = absoluteSpan / 180; + if ( isNaN( currentValue[ 0 ] ) || isNaN( currentValue[ 1 ] ) || isNaN( currentValue[ 2 ] ) ) { - const step = valuesSpan / numSubIntervals; - let nextValue = initialValue + step; + continue; + + } - const initialTime = curve.times[ i - 1 ]; - const timeSpan = curve.times[ i ] - initialTime; - const interval = timeSpan / numSubIntervals; - let nextTime = initialTime + interval; + const currentValueRad = currentValue.map( MathUtils.degToRad ); - const interpolatedTimes = []; - const interpolatedValues = []; + const valuesSpan = [ + currentValue[ 0 ] - initialValue[ 0 ], + currentValue[ 1 ] - initialValue[ 1 ], + currentValue[ 2 ] - initialValue[ 2 ], + ]; - while ( nextTime < curve.times[ i ] ) { + const absoluteSpan = [ + Math.abs( valuesSpan[ 0 ] ), + Math.abs( valuesSpan[ 1 ] ), + Math.abs( valuesSpan[ 2 ] ), + ]; - interpolatedTimes.push( nextTime ); - nextTime += interval; + if ( absoluteSpan[ 0 ] >= 180 || absoluteSpan[ 1 ] >= 180 || absoluteSpan[ 2 ] >= 180 ) { - interpolatedValues.push( nextValue ); - nextValue += step; + const maxAbsSpan = Math.max( ...absoluteSpan ); + + const numSubIntervals = maxAbsSpan / 180; + + const E1 = new Euler( ...initialValueRad, eulerOrder ); + const E2 = new Euler( ...currentValueRad, eulerOrder ); + + const Q1 = new Quaternion().setFromEuler( E1 ); + const Q2 = new Quaternion().setFromEuler( E2 ); + + // Check unroll + if ( Q1.dot( Q2 ) ) { + + Q2.set( - Q2.x, - Q2.y, - Q2.z, - Q2.w ); } - curve.times = inject( curve.times, i, interpolatedTimes ); - curve.values = inject( curve.values, i, interpolatedValues ); + // Interpolate + const initialTime = curvex.times[ i - 1 ]; + const timeSpan = curvex.times[ i ] - initialTime; + + const Q = new Quaternion(); + const E = new Euler(); + for ( let t = 0; t < 1; t += 1 / numSubIntervals ) { + + Q.copy( Q1.clone().slerp( Q2.clone(), t ) ); + + times.push( initialTime + t * timeSpan ); + E.setFromQuaternion( Q, eulerOrder ); + + values.push( E.x ); + values.push( E.y ); + values.push( E.z ); + + } + + } else { + + times.push( curvex.times[ i ] ); + values.push( MathUtils.degToRad( curvex.values[ i ] ) ); + values.push( MathUtils.degToRad( curvey.values[ i ] ) ); + values.push( MathUtils.degToRad( curvez.values[ i ] ) ); } } + return [ times, values ]; + } } @@ -3960,10 +4210,13 @@ function generateTransform( transformData ) { if ( transformData.translation ) lTranslationM.setPosition( tempVec.fromArray( transformData.translation ) ); + // For Maya models using "Joint Orient", Euler order only applies to rotation, not pre/post-rotations + const defaultEulerOrder = getEulerOrder( 0 ); + if ( transformData.preRotation ) { const array = transformData.preRotation.map( MathUtils.degToRad ); - array.push( transformData.eulerOrder || Euler.DEFAULT_ORDER ); + array.push( defaultEulerOrder ); lPreRotationM.makeRotationFromEuler( tempEuler.fromArray( array ) ); } @@ -3971,7 +4224,7 @@ function generateTransform( transformData ) { if ( transformData.rotation ) { const array = transformData.rotation.map( MathUtils.degToRad ); - array.push( transformData.eulerOrder || Euler.DEFAULT_ORDER ); + array.push( transformData.eulerOrder || defaultEulerOrder ); lRotationM.makeRotationFromEuler( tempEuler.fromArray( array ) ); } @@ -3979,7 +4232,7 @@ function generateTransform( transformData ) { if ( transformData.postRotation ) { const array = transformData.postRotation.map( MathUtils.degToRad ); - array.push( transformData.eulerOrder || Euler.DEFAULT_ORDER ); + array.push( defaultEulerOrder ); lPostRotationM.makeRotationFromEuler( tempEuler.fromArray( array ) ); lPostRotationM.invert(); @@ -4125,11 +4378,5 @@ function slice( a, b, from, to ) { } -// inject array a2 into array a1 at index -function inject( a1, index, a2 ) { - - return a1.slice( 0, index ).concat( a2 ).concat( a1.slice( index ) ); - -} export { FBXLoader }; diff --git a/examples/jsm/loaders/FontLoader.js b/examples/jsm/loaders/FontLoader.js index 93caeb5bbaad29..51e4b74c1173b4 100644 --- a/examples/jsm/loaders/FontLoader.js +++ b/examples/jsm/loaders/FontLoader.js @@ -4,14 +4,41 @@ import { ShapePath } from 'three'; +/** + * A loader for loading fonts. + * + * You can convert fonts online using [facetype.js]{@link https://gero3.github.io/facetype.js/}. + * + * ```js + * const loader = new FontLoader(); + * const font = await loader.loadAsync( 'fonts/helvetiker_regular.typeface.json' ); + * ``` + * + * @augments Loader + * @three_import import { FontLoader } from 'three/addons/loaders/FontLoader.js'; + */ class FontLoader extends Loader { + /** + * Constructs a new font loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); } + /** + * Starts loading from the given URL and passes the loaded font + * to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(Font)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ load( url, onLoad, onProgress, onError ) { const scope = this; @@ -30,6 +57,12 @@ class FontLoader extends Loader { } + /** + * Parses the given font data and returns the resulting font. + * + * @param {Object} json - The raw font data as a JSON object. + * @return {Font} The font. + */ parse( json ) { return new Font( json ); @@ -38,20 +71,46 @@ class FontLoader extends Loader { } -// - +/** + * Class representing a font. + */ class Font { + /** + * Constructs a new font. + * + * @param {Object} data - The font data as JSON. + */ constructor( data ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isFont = true; this.type = 'Font'; + /** + * The font data as JSON. + * + * @type {Object} + */ this.data = data; } + /** + * Generates geometry shapes from the given text and size. The result of this method + * should be used with {@link ShapeGeometry} to generate the actual geometry data. + * + * @param {string} text - The text. + * @param {number} [size=100] - The text size. + * @return {Array} An array of shapes representing the text. + */ generateShapes( text, size = 100 ) { const shapes = []; diff --git a/examples/jsm/loaders/GCodeLoader.js b/examples/jsm/loaders/GCodeLoader.js index c2f12800c4c7f7..7cb83916cc5f6c 100644 --- a/examples/jsm/loaders/GCodeLoader.js +++ b/examples/jsm/loaders/GCodeLoader.js @@ -9,24 +9,49 @@ import { } from 'three'; /** - * GCodeLoader is used to load gcode files usually used for 3D printing or CNC applications. + * A loader for the GCode format. * - * Gcode files are composed by commands used by machines to create objects. + * GCode files are usually used for 3D printing or CNC applications. * - * @class GCodeLoader - * @param {Manager} manager Loading manager. + * ```js + * const loader = new GCodeLoader(); + * const object = await loader.loadAsync( 'models/gcode/benchy.gcode' ); + * scene.add( object ); + * ``` + * + * @augments Loader + * @three_import import { GCodeLoader } from 'three/addons/loaders/GCodeLoader.js'; */ - class GCodeLoader extends Loader { + /** + * Constructs a new GCode loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); + /** + * Whether to split layers or not. + * + * @type {boolean} + * @default false + */ this.splitLayer = false; } + /** + * Starts loading from the given URL and passes the loaded GCode asset + * to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(Group)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ load( url, onLoad, onProgress, onError ) { const scope = this; @@ -61,6 +86,12 @@ class GCodeLoader extends Loader { } + /** + * Parses the given GCode data and returns a group with lines. + * + * @param {string} data - The raw Gcode data as a string. + * @return {Group} The parsed GCode asset. + */ parse( data ) { let state = { x: 0, y: 0, z: 0, e: 0, f: 0, extruding: false, relative: false }; @@ -123,7 +154,7 @@ class GCodeLoader extends Loader { const tokens = lines[ i ].split( ' ' ); const cmd = tokens[ 0 ].toUpperCase(); - //Argumments + //Arguments const args = {}; tokens.splice( 1 ).forEach( function ( token ) { diff --git a/examples/jsm/loaders/GLTFLoader.js b/examples/jsm/loaders/GLTFLoader.js index 0a11e7ec4cf11e..e8593bd4c60d56 100644 --- a/examples/jsm/loaders/GLTFLoader.js +++ b/examples/jsm/loaders/GLTFLoader.js @@ -6,6 +6,7 @@ import { BufferGeometry, ClampToEdgeWrapping, Color, + ColorManagement, DirectionalLight, DoubleSide, FileLoader, @@ -25,6 +26,7 @@ import { LinearFilter, LinearMipmapLinearFilter, LinearMipmapNearestFilter, + LinearSRGBColorSpace, Loader, LoaderUtils, Material, @@ -60,12 +62,68 @@ import { Vector2, Vector3, VectorKeyframeTrack, - SRGBColorSpace + SRGBColorSpace, + InstancedBufferAttribute } from 'three'; import { toTrianglesDrawMode } from '../utils/BufferGeometryUtils.js'; +/** + * A loader for the glTF 2.0 format. + * + * [glTF]{@link https://www.khronos.org/gltf/} (GL Transmission Format) is an [open format specification]{@link https://github.com/KhronosGroup/glTF/tree/main/specification/2.0} + * for efficient delivery and loading of 3D content. Assets may be provided either in JSON (.gltf) or binary (.glb) + * format. External files store textures (.jpg, .png) and additional binary data (.bin). A glTF asset may deliver + * one or more scenes, including meshes, materials, textures, skins, skeletons, morph targets, animations, lights, + * and/or cameras. + * + * `GLTFLoader` uses {@link ImageBitmapLoader} whenever possible. Be advised that image bitmaps are not + * automatically GC-collected when they are no longer referenced, and they require special handling during + * the disposal process. + * + * `GLTFLoader` supports the following glTF 2.0 extensions: + * - KHR_draco_mesh_compression + * - KHR_materials_clearcoat + * - KHR_materials_dispersion + * - KHR_materials_ior + * - KHR_materials_specular + * - KHR_materials_transmission + * - KHR_materials_iridescence + * - KHR_materials_unlit + * - KHR_materials_volume + * - KHR_mesh_quantization + * - KHR_lights_punctual + * - KHR_texture_basisu + * - KHR_texture_transform + * - EXT_texture_webp + * - EXT_meshopt_compression + * - EXT_mesh_gpu_instancing + * + * The following glTF 2.0 extension is supported by an external user plugin: + * - [KHR_materials_variants]{@link https://github.com/takahirox/three-gltf-extensions} + * - [MSFT_texture_dds]{@link https://github.com/takahirox/three-gltf-extensions} + * + * ```js + * const loader = new GLTFLoader(); + * + * // Optional: Provide a DRACOLoader instance to decode compressed mesh data + * const dracoLoader = new DRACOLoader(); + * dracoLoader.setDecoderPath( '/examples/jsm/libs/draco/' ); + * loader.setDRACOLoader( dracoLoader ); + * + * const gltf = await loader.loadAsync( 'models/gltf/duck/duck.gltf' ); + * scene.add( gltf.scene ); + * ``` + * + * @augments Loader + * @three_import import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; + */ class GLTFLoader extends Loader { + /** + * Constructs a new glTF loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); @@ -82,6 +140,12 @@ class GLTFLoader extends Loader { } ); + this.register( function ( parser ) { + + return new GLTFMaterialsDispersionExtension( parser ); + + } ); + this.register( function ( parser ) { return new GLTFTextureBasisUExtension( parser ); @@ -148,6 +212,12 @@ class GLTFLoader extends Loader { } ); + this.register( function ( parser ) { + + return new GLTFMaterialsBumpExtension( parser ); + + } ); + this.register( function ( parser ) { return new GLTFLightsExtension( parser ); @@ -168,6 +238,15 @@ class GLTFLoader extends Loader { } + /** + * Starts loading from the given URL and passes the loaded glTF asset + * to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(GLTFLoader~LoadObject)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ load( url, onLoad, onProgress, onError ) { const scope = this; @@ -180,7 +259,13 @@ class GLTFLoader extends Loader { } else if ( this.path !== '' ) { - resourcePath = this.path; + // If a base path is set, resources will be relative paths from that plus the relative path of the gltf file + // Example path = 'https://my-cnd-server.com/', url = 'assets/models/model.gltf' + // resourcePath = 'https://my-cnd-server.com/assets/models/' + // referenced resource 'model.bin' will be loaded from 'https://my-cnd-server.com/assets/models/model.bin' + // referenced resource '../textures/texture.png' will be loaded from 'https://my-cnd-server.com/assets/textures/texture.png' + const relativeUrl = LoaderUtils.extractUrlBase( url ); + resourcePath = LoaderUtils.resolveURL( relativeUrl, this.path ); } else { @@ -239,6 +324,13 @@ class GLTFLoader extends Loader { } + /** + * Sets the given Draco loader to this loader. Required for decoding assets + * compressed with the `KHR_draco_mesh_compression` extension. + * + * @param {DRACOLoader} dracoLoader - The Draco loader to set. + * @return {GLTFLoader} A reference to this loader. + */ setDRACOLoader( dracoLoader ) { this.dracoLoader = dracoLoader; @@ -246,16 +338,13 @@ class GLTFLoader extends Loader { } - setDDSLoader() { - - throw new Error( - - 'THREE.GLTFLoader: "MSFT_texture_dds" no longer supported. Please update to "KHR_texture_basisu".' - - ); - - } - + /** + * Sets the given KTX2 loader to this loader. Required for loading KTX2 + * compressed textures. + * + * @param {KTX2Loader} ktx2Loader - The KTX2 loader to set. + * @return {GLTFLoader} A reference to this loader. + */ setKTX2Loader( ktx2Loader ) { this.ktx2Loader = ktx2Loader; @@ -263,6 +352,13 @@ class GLTFLoader extends Loader { } + /** + * Sets the given meshopt decoder. Required for decoding assets + * compressed with the `EXT_meshopt_compression` extension. + * + * @param {Object} meshoptDecoder - The meshopt decoder to set. + * @return {GLTFLoader} A reference to this loader. + */ setMeshoptDecoder( meshoptDecoder ) { this.meshoptDecoder = meshoptDecoder; @@ -270,6 +366,14 @@ class GLTFLoader extends Loader { } + /** + * Registers a plugin callback. This API is internally used to implement the various + * glTF extensions but can also used by third-party code to add additional logic + * to the loader. + * + * @param {function(parser:GLTFParser)} callback - The callback function to register. + * @return {GLTFLoader} A reference to this loader. + */ register( callback ) { if ( this.pluginCallbacks.indexOf( callback ) === - 1 ) { @@ -282,6 +386,12 @@ class GLTFLoader extends Loader { } + /** + * Unregisters a plugin callback. + * + * @param {Function} callback - The callback function to unregister. + * @return {GLTFLoader} A reference to this loader. + */ unregister( callback ) { if ( this.pluginCallbacks.indexOf( callback ) !== - 1 ) { @@ -294,6 +404,14 @@ class GLTFLoader extends Loader { } + /** + * Parses the given FBX data and returns the resulting group. + * + * @param {string|ArrayBuffer} data - The raw glTF data. + * @param {string} path - The URL base path. + * @param {function(GLTFLoader~LoadObject)} onLoad - Executed when the loading process has been finished. + * @param {onErrorCallback} onError - Executed when errors occur. + */ parse( data, path, onLoad, onError ) { let json; @@ -359,6 +477,9 @@ class GLTFLoader extends Loader { for ( let i = 0; i < this.pluginCallbacks.length; i ++ ) { const plugin = this.pluginCallbacks[ i ]( parser ); + + if ( ! plugin.name ) console.error( 'THREE.GLTFLoader: Invalid plugin found: missing name' ); + plugins[ plugin.name ] = plugin; // Workaround to avoid determining as unknown extension @@ -414,6 +535,14 @@ class GLTFLoader extends Loader { } + /** + * Async version of {@link GLTFLoader#parse}. + * + * @async + * @param {string|ArrayBuffer} data - The raw glTF data. + * @param {string} path - The URL base path. + * @return {Promise} A Promise that resolves with the loaded glTF when the parsing has been finished. + */ parseAsync( data, path ) { const scope = this; @@ -473,6 +602,7 @@ const EXTENSIONS = { KHR_DRACO_MESH_COMPRESSION: 'KHR_draco_mesh_compression', KHR_LIGHTS_PUNCTUAL: 'KHR_lights_punctual', KHR_MATERIALS_CLEARCOAT: 'KHR_materials_clearcoat', + KHR_MATERIALS_DISPERSION: 'KHR_materials_dispersion', KHR_MATERIALS_IOR: 'KHR_materials_ior', KHR_MATERIALS_SHEEN: 'KHR_materials_sheen', KHR_MATERIALS_SPECULAR: 'KHR_materials_specular', @@ -485,6 +615,7 @@ const EXTENSIONS = { KHR_TEXTURE_TRANSFORM: 'KHR_texture_transform', KHR_MESH_QUANTIZATION: 'KHR_mesh_quantization', KHR_MATERIALS_EMISSIVE_STRENGTH: 'KHR_materials_emissive_strength', + EXT_MATERIALS_BUMP: 'EXT_materials_bump', EXT_TEXTURE_WEBP: 'EXT_texture_webp', EXT_TEXTURE_AVIF: 'EXT_texture_avif', EXT_MESHOPT_COMPRESSION: 'EXT_meshopt_compression', @@ -495,6 +626,8 @@ const EXTENSIONS = { * Punctual Lights Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual + * + * @private */ class GLTFLightsExtension { @@ -545,7 +678,7 @@ class GLTFLightsExtension { const color = new Color( 0xffffff ); - if ( lightDef.color !== undefined ) color.fromArray( lightDef.color ); + if ( lightDef.color !== undefined ) color.setRGB( lightDef.color[ 0 ], lightDef.color[ 1 ], lightDef.color[ 2 ], LinearSRGBColorSpace ); const range = lightDef.range !== undefined ? lightDef.range : 0; @@ -584,8 +717,6 @@ class GLTFLightsExtension { // here, because node-level parsing will only override position if explicitly specified. lightNode.position.set( 0, 0, 0 ); - lightNode.decay = 2; - assignExtrasToUserData( lightNode, lightDef ); if ( lightDef.intensity !== undefined ) lightNode.intensity = lightDef.intensity; @@ -633,6 +764,8 @@ class GLTFLightsExtension { * Unlit Materials Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit + * + * @private */ class GLTFMaterialsUnlitExtension { @@ -663,7 +796,7 @@ class GLTFMaterialsUnlitExtension { const array = metallicRoughness.baseColorFactor; - materialParams.color.fromArray( array ); + materialParams.color.setRGB( array[ 0 ], array[ 1 ], array[ 2 ], LinearSRGBColorSpace ); materialParams.opacity = array[ 3 ]; } @@ -686,6 +819,8 @@ class GLTFMaterialsUnlitExtension { * Materials Emissive Strength Extension * * Specification: https://github.com/KhronosGroup/glTF/blob/5768b3ce0ef32bc39cdf1bef10b948586635ead3/extensions/2.0/Khronos/KHR_materials_emissive_strength/README.md + * + * @private */ class GLTFMaterialsEmissiveStrengthExtension { @@ -725,6 +860,8 @@ class GLTFMaterialsEmissiveStrengthExtension { * Clearcoat Materials Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_clearcoat + * + * @private */ class GLTFMaterialsClearcoatExtension { @@ -805,10 +942,60 @@ class GLTFMaterialsClearcoatExtension { } +/** + * Materials dispersion Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_dispersion + * + * @private + */ +class GLTFMaterialsDispersionExtension { + + constructor( parser ) { + + this.parser = parser; + this.name = EXTENSIONS.KHR_MATERIALS_DISPERSION; + + } + + getMaterialType( materialIndex ) { + + const parser = this.parser; + const materialDef = parser.json.materials[ materialIndex ]; + + if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; + + return MeshPhysicalMaterial; + + } + + extendMaterialParams( materialIndex, materialParams ) { + + const parser = this.parser; + const materialDef = parser.json.materials[ materialIndex ]; + + if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { + + return Promise.resolve(); + + } + + const extension = materialDef.extensions[ this.name ]; + + materialParams.dispersion = extension.dispersion !== undefined ? extension.dispersion : 0; + + return Promise.resolve(); + + } + +} + /** * Iridescence Materials Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_iridescence + * + * @private */ class GLTFMaterialsIridescenceExtension { @@ -897,6 +1084,8 @@ class GLTFMaterialsIridescenceExtension { * Sheen Materials Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_sheen + * + * @private */ class GLTFMaterialsSheenExtension { @@ -939,7 +1128,8 @@ class GLTFMaterialsSheenExtension { if ( extension.sheenColorFactor !== undefined ) { - materialParams.sheenColor.fromArray( extension.sheenColorFactor ); + const colorFactor = extension.sheenColorFactor; + materialParams.sheenColor.setRGB( colorFactor[ 0 ], colorFactor[ 1 ], colorFactor[ 2 ], LinearSRGBColorSpace ); } @@ -972,6 +1162,8 @@ class GLTFMaterialsSheenExtension { * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_transmission * Draft: https://github.com/KhronosGroup/glTF/pull/1698 + * + * @private */ class GLTFMaterialsTransmissionExtension { @@ -1030,6 +1222,8 @@ class GLTFMaterialsTransmissionExtension { * Materials Volume Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_volume + * + * @private */ class GLTFMaterialsVolumeExtension { @@ -1077,7 +1271,7 @@ class GLTFMaterialsVolumeExtension { materialParams.attenuationDistance = extension.attenuationDistance || Infinity; const colorArray = extension.attenuationColor || [ 1, 1, 1 ]; - materialParams.attenuationColor = new Color( colorArray[ 0 ], colorArray[ 1 ], colorArray[ 2 ] ); + materialParams.attenuationColor = new Color().setRGB( colorArray[ 0 ], colorArray[ 1 ], colorArray[ 2 ], LinearSRGBColorSpace ); return Promise.all( pending ); @@ -1089,6 +1283,8 @@ class GLTFMaterialsVolumeExtension { * Materials ior Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_ior + * + * @private */ class GLTFMaterialsIorExtension { @@ -1135,6 +1331,8 @@ class GLTFMaterialsIorExtension { * Materials specular Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_specular + * + * @private */ class GLTFMaterialsSpecularExtension { @@ -1180,7 +1378,7 @@ class GLTFMaterialsSpecularExtension { } const colorArray = extension.specularColorFactor || [ 1, 1, 1 ]; - materialParams.specularColor = new Color( colorArray[ 0 ], colorArray[ 1 ], colorArray[ 2 ] ); + materialParams.specularColor = new Color().setRGB( colorArray[ 0 ], colorArray[ 1 ], colorArray[ 2 ], LinearSRGBColorSpace ); if ( extension.specularColorTexture !== undefined ) { @@ -1194,10 +1392,69 @@ class GLTFMaterialsSpecularExtension { } + +/** + * Materials bump Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/EXT_materials_bump + * + * @private + */ +class GLTFMaterialsBumpExtension { + + constructor( parser ) { + + this.parser = parser; + this.name = EXTENSIONS.EXT_MATERIALS_BUMP; + + } + + getMaterialType( materialIndex ) { + + const parser = this.parser; + const materialDef = parser.json.materials[ materialIndex ]; + + if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; + + return MeshPhysicalMaterial; + + } + + extendMaterialParams( materialIndex, materialParams ) { + + const parser = this.parser; + const materialDef = parser.json.materials[ materialIndex ]; + + if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { + + return Promise.resolve(); + + } + + const pending = []; + + const extension = materialDef.extensions[ this.name ]; + + materialParams.bumpScale = extension.bumpFactor !== undefined ? extension.bumpFactor : 1.0; + + if ( extension.bumpTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'bumpMap', extension.bumpTexture ) ); + + } + + return Promise.all( pending ); + + } + +} + /** * Materials anisotropy Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_anisotropy + * + * @private */ class GLTFMaterialsAnisotropyExtension { @@ -1262,6 +1519,8 @@ class GLTFMaterialsAnisotropyExtension { * BasisU Texture Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_basisu + * + * @private */ class GLTFTextureBasisUExtension { @@ -1313,6 +1572,8 @@ class GLTFTextureBasisUExtension { * WebP Texture Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_texture_webp + * + * @private */ class GLTFTextureWebPExtension { @@ -1320,7 +1581,6 @@ class GLTFTextureWebPExtension { this.parser = parser; this.name = EXTENSIONS.EXT_TEXTURE_WEBP; - this.isSupported = null; } @@ -1349,46 +1609,7 @@ class GLTFTextureWebPExtension { } - return this.detectSupport().then( function ( isSupported ) { - - if ( isSupported ) return parser.loadTextureImage( textureIndex, extension.source, loader ); - - if ( json.extensionsRequired && json.extensionsRequired.indexOf( name ) >= 0 ) { - - throw new Error( 'THREE.GLTFLoader: WebP required by asset but unsupported.' ); - - } - - // Fall back to PNG or JPEG. - return parser.loadTexture( textureIndex ); - - } ); - - } - - detectSupport() { - - if ( ! this.isSupported ) { - - this.isSupported = new Promise( function ( resolve ) { - - const image = new Image(); - - // Lossy test image. Support for lossy images doesn't guarantee support for all - // WebP images, unfortunately. - image.src = 'data:image/webp;base64,UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA'; - - image.onload = image.onerror = function () { - - resolve( image.height === 1 ); - - }; - - } ); - - } - - return this.isSupported; + return parser.loadTextureImage( textureIndex, extension.source, loader ); } @@ -1398,6 +1619,8 @@ class GLTFTextureWebPExtension { * AVIF Texture Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_texture_avif + * + * @private */ class GLTFTextureAVIFExtension { @@ -1405,7 +1628,6 @@ class GLTFTextureAVIFExtension { this.parser = parser; this.name = EXTENSIONS.EXT_TEXTURE_AVIF; - this.isSupported = null; } @@ -1434,44 +1656,7 @@ class GLTFTextureAVIFExtension { } - return this.detectSupport().then( function ( isSupported ) { - - if ( isSupported ) return parser.loadTextureImage( textureIndex, extension.source, loader ); - - if ( json.extensionsRequired && json.extensionsRequired.indexOf( name ) >= 0 ) { - - throw new Error( 'THREE.GLTFLoader: AVIF required by asset but unsupported.' ); - - } - - // Fall back to PNG or JPEG. - return parser.loadTexture( textureIndex ); - - } ); - - } - - detectSupport() { - - if ( ! this.isSupported ) { - - this.isSupported = new Promise( function ( resolve ) { - - const image = new Image(); - - // Lossy test image. - image.src = 'data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAABcAAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAAEAAAABAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQAMAAAAABNjb2xybmNseAACAAIABoAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAAB9tZGF0EgAKCBgABogQEDQgMgkQAAAAB8dSLfI='; - image.onload = image.onerror = function () { - - resolve( image.height === 1 ); - - }; - - } ); - - } - - return this.isSupported; + return parser.loadTextureImage( textureIndex, extension.source, loader ); } @@ -1481,6 +1666,8 @@ class GLTFTextureAVIFExtension { * meshopt BufferView Compression Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_meshopt_compression + * + * @private */ class GLTFMeshoptCompression { @@ -1566,6 +1753,7 @@ class GLTFMeshoptCompression { * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_mesh_gpu_instancing * + * @private */ class GLTFMeshGpuInstancing { @@ -1676,7 +1864,12 @@ class GLTFMeshGpuInstancing { // Add instance attributes to the geometry, excluding TRS. for ( const attributeName in attributes ) { - if ( attributeName !== 'TRANSLATION' && + if ( attributeName === '_COLOR_0' ) { + + const attr = attributes[ attributeName ]; + instancedMesh.instanceColor = new InstancedBufferAttribute( attr.array, attr.itemSize, attr.normalized ); + + } else if ( attributeName !== 'TRANSLATION' && attributeName !== 'ROTATION' && attributeName !== 'SCALE' ) { @@ -1789,6 +1982,8 @@ class GLTFBinaryExtension { * DRACO Mesh Compression Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_draco_mesh_compression + * + * @private */ class GLTFDracoMeshCompressionExtension { @@ -1843,7 +2038,7 @@ class GLTFDracoMeshCompressionExtension { return parser.getDependency( 'bufferView', bufferViewIndex ).then( function ( bufferView ) { - return new Promise( function ( resolve ) { + return new Promise( function ( resolve, reject ) { dracoLoader.decodeDracoFile( bufferView, function ( geometry ) { @@ -1858,7 +2053,7 @@ class GLTFDracoMeshCompressionExtension { resolve( geometry ); - }, threeAttributeMap, attributeTypeMap ); + }, threeAttributeMap, attributeTypeMap, LinearSRGBColorSpace, reject ); } ); @@ -1872,6 +2067,8 @@ class GLTFDracoMeshCompressionExtension { * Texture Transform Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_transform + * + * @private */ class GLTFTextureTransformExtension { @@ -1931,6 +2128,8 @@ class GLTFTextureTransformExtension { * Mesh Quantization Extension * * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization + * + * @private */ class GLTFMeshQuantizationExtension { @@ -2018,7 +2217,7 @@ class GLTFCubicSplineInterpolant extends Interpolant { } -const _q = new Quaternion(); +const _quaternion = new Quaternion(); class GLTFCubicSplineQuaternionInterpolant extends GLTFCubicSplineInterpolant { @@ -2026,7 +2225,7 @@ class GLTFCubicSplineQuaternionInterpolant extends GLTFCubicSplineInterpolant { const result = super.interpolate_( i1, t0, t, t1 ); - _q.fromArray( result ).normalize().toArray( result ); + _quaternion.fromArray( result ).normalize().toArray( result ); return result; @@ -2132,6 +2331,10 @@ const ALPHA_MODES = { /** * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#default-material + * + * @private + * @param {Object} cache + * @return {Material} */ function createDefaultMaterial( cache ) { @@ -2171,7 +2374,9 @@ function addUnknownExtensionsToUserData( knownExtensions, object, objectDef ) { } /** - * @param {Object3D|Material|BufferGeometry} object + * + * @private + * @param {Object3D|Material|BufferGeometry|Object} object * @param {GLTF.definition} gltfDef */ function assignExtrasToUserData( object, gltfDef ) { @@ -2195,6 +2400,7 @@ function assignExtrasToUserData( object, gltfDef ) { /** * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#morph-targets * + * @private * @param {BufferGeometry} geometry * @param {Array} targets * @param {GLTFParser} parser @@ -2282,6 +2488,8 @@ function addMorphTargets( geometry, targets, parser ) { } /** + * + * @private * @param {Mesh} mesh * @param {GLTF.Mesh} meshDef */ @@ -2402,6 +2610,7 @@ function getImageURIMimeType( uri ) { if ( uri.search( /\.jpe?g($|\?)/i ) > 0 || uri.search( /^data\:image\/jpeg/ ) === 0 ) return 'image/jpeg'; if ( uri.search( /\.webp($|\?)/i ) > 0 || uri.search( /^data\:image\/webp/ ) === 0 ) return 'image/webp'; + if ( uri.search( /\.ktx2($|\?)/i ) > 0 || uri.search( /^data\:image\/ktx2/ ) === 0 ) return 'image/ktx2'; return 'image/png'; @@ -2447,18 +2656,24 @@ class GLTFParser { // expensive work of uploading a texture to the GPU off the main thread. let isSafari = false; + let safariVersion = - 1; let isFirefox = false; let firefoxVersion = - 1; if ( typeof navigator !== 'undefined' ) { - isSafari = /^((?!chrome|android).)*safari/i.test( navigator.userAgent ) === true; - isFirefox = navigator.userAgent.indexOf( 'Firefox' ) > - 1; - firefoxVersion = isFirefox ? navigator.userAgent.match( /Firefox\/([0-9]+)\./ )[ 1 ] : - 1; + const userAgent = navigator.userAgent; + + isSafari = /^((?!chrome|android).)*safari/i.test( userAgent ) === true; + const safariMatch = userAgent.match( /Version\/(\d+)/ ); + safariVersion = isSafari && safariMatch ? parseInt( safariMatch[ 1 ], 10 ) : - 1; + + isFirefox = userAgent.indexOf( 'Firefox' ) > - 1; + firefoxVersion = isFirefox ? userAgent.match( /Firefox\/([0-9]+)\./ )[ 1 ] : - 1; } - if ( typeof createImageBitmap === 'undefined' || isSafari || ( isFirefox && firefoxVersion < 98 ) ) { + if ( typeof createImageBitmap === 'undefined' || ( isSafari && safariVersion < 17 ) || ( isFirefox && firefoxVersion < 98 ) ) { this.textureLoader = new TextureLoader( this.options.manager ); @@ -2541,12 +2756,18 @@ class GLTFParser { assignExtrasToUserData( result, json ); - Promise.all( parser._invokeAll( function ( ext ) { + return Promise.all( parser._invokeAll( function ( ext ) { return ext.afterRoot && ext.afterRoot( result ); } ) ).then( function () { + for ( const scene of result.scenes ) { + + scene.updateMatrixWorld(); + + } + onLoad( result ); } ); @@ -2557,6 +2778,8 @@ class GLTFParser { /** * Marks the special nodes/meshes in json for efficient parse. + * + * @private */ _markDefs() { @@ -2617,6 +2840,10 @@ class GLTFParser { * Textures) can be reused directly and are not marked here. * * Example: CesiumMilkTruck sample model reuses "Wheel" meshes. + * + * @private + * @param {Object} cache + * @param {Object3D} index */ _addNodeRef( cache, index ) { @@ -2632,7 +2859,15 @@ class GLTFParser { } - /** Returns a reference to a shared resource, cloning it if necessary. */ + /** + * Returns a reference to a shared resource, cloning it if necessary. + * + * @private + * @param {Object} cache + * @param {number} index + * @param {Object} object + * @return {Object} + */ _getNodeRef( cache, index, object ) { if ( cache.refs[ index ] <= 1 ) return object; @@ -2704,6 +2939,8 @@ class GLTFParser { /** * Requests the specified dependency asynchronously, with caching. + * + * @private * @param {string} type * @param {number} index * @return {Promise} @@ -2812,6 +3049,8 @@ class GLTFParser { /** * Requests all dependencies of the specified type asynchronously, with caching. + * + * @private * @param {string} type * @return {Promise>} */ @@ -2840,6 +3079,8 @@ class GLTFParser { /** * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views + * + * @private * @param {number} bufferIndex * @return {Promise} */ @@ -2877,6 +3118,8 @@ class GLTFParser { /** * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views + * + * @private * @param {number} bufferViewIndex * @return {Promise} */ @@ -2896,6 +3139,8 @@ class GLTFParser { /** * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#accessors + * + * @private * @param {number} accessorIndex * @return {Promise} */ @@ -3008,6 +3253,9 @@ class GLTFParser { } + // Ignore normalized since we copy from sparse + bufferAttribute.normalized = false; + for ( let i = 0, il = sparseIndices.length; i < il; i ++ ) { const index = sparseIndices[ i ]; @@ -3020,6 +3268,8 @@ class GLTFParser { } + bufferAttribute.normalized = normalized; + } return bufferAttribute; @@ -3030,6 +3280,8 @@ class GLTFParser { /** * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#textures + * + * @private * @param {number} textureIndex * @return {Promise} */ @@ -3090,6 +3342,7 @@ class GLTFParser { texture.minFilter = WEBGL_FILTERS[ sampler.minFilter ] || LinearMipmapLinearFilter; texture.wrapS = WEBGL_WRAPPINGS[ sampler.wrapS ] || RepeatWrapping; texture.wrapT = WEBGL_WRAPPINGS[ sampler.wrapT ] || RepeatWrapping; + texture.generateMipmaps = ! texture.isCompressedTexture && texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter; parser.associations.set( texture, { textures: textureIndex } ); @@ -3178,6 +3431,8 @@ class GLTFParser { } + assignExtrasToUserData( texture, sourceDef ); + texture.userData.mimeType = sourceDef.mimeType || getImageURIMimeType( sourceDef.uri ); return texture; @@ -3196,9 +3451,12 @@ class GLTFParser { /** * Asynchronously assigns a texture to the given material parameters. + * + * @private * @param {Object} materialParams * @param {string} mapName * @param {Object} mapDef + * @param {string} [colorSpace] * @return {Promise} */ assignTexture( materialParams, mapName, mapDef, colorSpace ) { @@ -3250,7 +3508,9 @@ class GLTFParser { * but reuse of the same glTF material may require multiple threejs materials * to accommodate different primitive types, defines, etc. New materials will * be created if necessary, and reused from a cache. - * @param {Object3D} mesh Mesh, Line, or Points instance. + * + * @private + * @param {Object3D} mesh Mesh, Line, or Points instance. */ assignFinalMaterial( mesh ) { @@ -3350,6 +3610,8 @@ class GLTFParser { /** * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#materials + * + * @private * @param {number} materialIndex * @return {Promise} */ @@ -3386,7 +3648,7 @@ class GLTFParser { const array = metallicRoughness.baseColorFactor; - materialParams.color.fromArray( array ); + materialParams.color.setRGB( array[ 0 ], array[ 1 ], array[ 2 ], LinearSRGBColorSpace ); materialParams.opacity = array[ 3 ]; } @@ -3478,7 +3740,8 @@ class GLTFParser { if ( materialDef.emissiveFactor !== undefined && materialType !== MeshBasicMaterial ) { - materialParams.emissive = new Color().fromArray( materialDef.emissiveFactor ); + const emissiveFactor = materialDef.emissiveFactor; + materialParams.emissive = new Color().setRGB( emissiveFactor[ 0 ], emissiveFactor[ 1 ], emissiveFactor[ 2 ], LinearSRGBColorSpace ); } @@ -3506,7 +3769,13 @@ class GLTFParser { } - /** When Object3D instances are targeted by animation, they need unique names. */ + /** + * When Object3D instances are targeted by animation, they need unique names. + * + * @private + * @param {string} originalName + * @return {string} + */ createUniqueName( originalName ) { const sanitizedName = PropertyBinding.sanitizeNodeName( originalName || '' ); @@ -3530,6 +3799,7 @@ class GLTFParser { * * Creates BufferGeometries from primitives. * + * @private * @param {Array} primitives * @return {Promise>} */ @@ -3597,8 +3867,10 @@ class GLTFParser { /** * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#meshes + * + * @private * @param {number} meshIndex - * @return {Promise} + * @return {Promise} */ loadMesh( meshIndex ) { @@ -3745,6 +4017,8 @@ class GLTFParser { /** * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#cameras + * + * @private * @param {number} cameraIndex * @return {Promise} */ @@ -3781,6 +4055,8 @@ class GLTFParser { /** * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins + * + * @private * @param {number} skinIndex * @return {Promise} */ @@ -3851,12 +4127,15 @@ class GLTFParser { /** * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#animations + * + * @private * @param {number} animationIndex * @return {Promise} */ loadAnimation( animationIndex ) { const json = this.json; + const parser = this; const animationDef = json.animations[ animationIndex ]; const animationName = animationDef.name ? animationDef.name : 'animation_' + animationIndex; @@ -3914,103 +4193,22 @@ class GLTFParser { if ( node === undefined ) continue; - node.updateMatrix(); - - let TypedKeyframeTrack; + if ( node.updateMatrix ) { - switch ( PATH_PROPERTIES[ target.path ] ) { - - case PATH_PROPERTIES.weights: - - TypedKeyframeTrack = NumberKeyframeTrack; - break; - - case PATH_PROPERTIES.rotation: - - TypedKeyframeTrack = QuaternionKeyframeTrack; - break; - - case PATH_PROPERTIES.position: - case PATH_PROPERTIES.scale: - default: - - TypedKeyframeTrack = VectorKeyframeTrack; - break; + node.updateMatrix(); } - const targetName = node.name ? node.name : node.uuid; + const createdTracks = parser._createAnimationTracks( node, inputAccessor, outputAccessor, sampler, target ); - const interpolation = sampler.interpolation !== undefined ? INTERPOLATION[ sampler.interpolation ] : InterpolateLinear; + if ( createdTracks ) { - const targetNames = []; + for ( let k = 0; k < createdTracks.length; k ++ ) { - if ( PATH_PROPERTIES[ target.path ] === PATH_PROPERTIES.weights ) { - - node.traverse( function ( object ) { - - if ( object.morphTargetInfluences ) { - - targetNames.push( object.name ? object.name : object.uuid ); - - } - - } ); - - } else { - - targetNames.push( targetName ); - - } - - let outputArray = outputAccessor.array; - - if ( outputAccessor.normalized ) { - - const scale = getNormalizedComponentScale( outputArray.constructor ); - const scaled = new Float32Array( outputArray.length ); - - for ( let j = 0, jl = outputArray.length; j < jl; j ++ ) { - - scaled[ j ] = outputArray[ j ] * scale; + tracks.push( createdTracks[ k ] ); } - outputArray = scaled; - - } - - for ( let j = 0, jl = targetNames.length; j < jl; j ++ ) { - - const track = new TypedKeyframeTrack( - targetNames[ j ] + '.' + PATH_PROPERTIES[ target.path ], - inputAccessor.array, - outputArray, - interpolation - ); - - // Override interpolation with custom factory method. - if ( sampler.interpolation === 'CUBICSPLINE' ) { - - track.createInterpolant = function InterpolantFactoryMethodGLTFCubicSpline( result ) { - - // A CUBICSPLINE keyframe in glTF has three output values for each input value, - // representing inTangent, splineVertex, and outTangent. As a result, track.getValueSize() - // must be divided by three to get the interpolant's sampleSize argument. - - const interpolantType = ( this instanceof QuaternionKeyframeTrack ) ? GLTFCubicSplineQuaternionInterpolant : GLTFCubicSplineInterpolant; - - return new interpolantType( this.times, this.values, this.getValueSize() / 3, result ); - - }; - - // Mark as CUBICSPLINE. `track.getInterpolation()` doesn't support custom interpolants. - track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline = true; - - } - - tracks.push( track ); - } } @@ -4058,6 +4256,8 @@ class GLTFParser { /** * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#nodes-and-hierarchy + * + * @private * @param {number} nodeIndex * @return {Promise} */ @@ -4251,6 +4451,11 @@ class GLTFParser { parser.associations.set( node, {} ); + } else if ( nodeDef.mesh !== undefined && parser.meshCache.refs[ nodeDef.mesh ] > 1 ) { + + const mapping = parser.associations.get( node ); + parser.associations.set( node, { ...mapping } ); + } parser.associations.get( node ).nodes = nodeIndex; @@ -4265,6 +4470,8 @@ class GLTFParser { /** * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#scenes + * + * @private * @param {number} sceneIndex * @return {Promise} */ @@ -4341,9 +4548,146 @@ class GLTFParser { } + _createAnimationTracks( node, inputAccessor, outputAccessor, sampler, target ) { + + const tracks = []; + + const targetName = node.name ? node.name : node.uuid; + const targetNames = []; + + if ( PATH_PROPERTIES[ target.path ] === PATH_PROPERTIES.weights ) { + + node.traverse( function ( object ) { + + if ( object.morphTargetInfluences ) { + + targetNames.push( object.name ? object.name : object.uuid ); + + } + + } ); + + } else { + + targetNames.push( targetName ); + + } + + let TypedKeyframeTrack; + + switch ( PATH_PROPERTIES[ target.path ] ) { + + case PATH_PROPERTIES.weights: + + TypedKeyframeTrack = NumberKeyframeTrack; + break; + + case PATH_PROPERTIES.rotation: + + TypedKeyframeTrack = QuaternionKeyframeTrack; + break; + + case PATH_PROPERTIES.translation: + case PATH_PROPERTIES.scale: + + TypedKeyframeTrack = VectorKeyframeTrack; + break; + + default: + + switch ( outputAccessor.itemSize ) { + + case 1: + TypedKeyframeTrack = NumberKeyframeTrack; + break; + case 2: + case 3: + default: + TypedKeyframeTrack = VectorKeyframeTrack; + break; + + } + + break; + + } + + const interpolation = sampler.interpolation !== undefined ? INTERPOLATION[ sampler.interpolation ] : InterpolateLinear; + + + const outputArray = this._getArrayFromAccessor( outputAccessor ); + + for ( let j = 0, jl = targetNames.length; j < jl; j ++ ) { + + const track = new TypedKeyframeTrack( + targetNames[ j ] + '.' + PATH_PROPERTIES[ target.path ], + inputAccessor.array, + outputArray, + interpolation + ); + + // Override interpolation with custom factory method. + if ( sampler.interpolation === 'CUBICSPLINE' ) { + + this._createCubicSplineTrackInterpolant( track ); + + } + + tracks.push( track ); + + } + + return tracks; + + } + + _getArrayFromAccessor( accessor ) { + + let outputArray = accessor.array; + + if ( accessor.normalized ) { + + const scale = getNormalizedComponentScale( outputArray.constructor ); + const scaled = new Float32Array( outputArray.length ); + + for ( let j = 0, jl = outputArray.length; j < jl; j ++ ) { + + scaled[ j ] = outputArray[ j ] * scale; + + } + + outputArray = scaled; + + } + + return outputArray; + + } + + _createCubicSplineTrackInterpolant( track ) { + + track.createInterpolant = function InterpolantFactoryMethodGLTFCubicSpline( result ) { + + // A CUBICSPLINE keyframe in glTF has three output values for each input value, + // representing inTangent, splineVertex, and outTangent. As a result, track.getValueSize() + // must be divided by three to get the interpolant's sampleSize argument. + + const interpolantType = ( this instanceof QuaternionKeyframeTrack ) ? GLTFCubicSplineQuaternionInterpolant : GLTFCubicSplineInterpolant; + + return new interpolantType( this.times, this.values, this.getValueSize() / 3, result ); + + }; + + // Mark as CUBICSPLINE. `track.getInterpolation()` doesn't support custom interpolants. + track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline = true; + + } + } /** + * + * @private * @param {BufferGeometry} geometry * @param {GLTF.Primitive} primitiveDef * @param {GLTFParser} parser @@ -4459,6 +4803,8 @@ function computeBounds( geometry, primitiveDef, parser ) { } /** + * + * @private * @param {BufferGeometry} geometry * @param {GLTF.Primitive} primitiveDef * @param {GLTFParser} parser @@ -4504,6 +4850,12 @@ function addPrimitiveAttributes( geometry, primitiveDef, parser ) { } + if ( ColorManagement.workingColorSpace !== LinearSRGBColorSpace && 'COLOR_0' in attributes ) { + + console.warn( `THREE.GLTFLoader: Converting vertex colors from "srgb-linear" to "${ColorManagement.workingColorSpace}" not supported.` ); + + } + assignExtrasToUserData( geometry, primitiveDef ); computeBounds( geometry, primitiveDef, parser ); @@ -4518,4 +4870,17 @@ function addPrimitiveAttributes( geometry, primitiveDef, parser ) { } +/** + * Loader result of `GLTFLoader`. + * + * @typedef {Object} GLTFLoader~LoadObject + * @property {Array} animations - An array of animation clips. + * @property {Object} asset - Meta data about the loaded asset. + * @property {Array} cameras - An array of cameras. + * @property {GLTFParser} parser - A reference to the internal parser. + * @property {Group} scene - The default scene. + * @property {Array} scenes - glTF assets might define multiple scenes. + * @property {Object} userData - Additional data. + **/ + export { GLTFLoader }; diff --git a/examples/jsm/loaders/HDRCubeTextureLoader.js b/examples/jsm/loaders/HDRCubeTextureLoader.js index 771912500cd1d3..cdf2fb704cdd50 100644 --- a/examples/jsm/loaders/HDRCubeTextureLoader.js +++ b/examples/jsm/loaders/HDRCubeTextureLoader.js @@ -10,17 +10,60 @@ import { } from 'three'; import { RGBELoader } from '../loaders/RGBELoader.js'; +/** + * A loader for loading HDR cube textures. + * + * ```js + * const loader = new HDRCubeTextureLoader(); + * loader.setPath( 'textures/cube/pisaHDR/' ); + * const cubeTexture = await loader.loadAsync( [ 'px.hdr', 'nx.hdr', 'py.hdr', 'ny.hdr', 'pz.hdr', 'nz.hdr' ] ); + * + * scene.background = cubeTexture; + * scene.environment = cubeTexture; + * ``` + * + * @augments Loader + * @three_import import { HDRCubeTextureLoader } from 'three/addons/loaders/HDRCubeTextureLoader.js'; + */ class HDRCubeTextureLoader extends Loader { + /** + * Constructs a new HDR cube texture loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); + /** + * The internal HDR loader that loads the + * individual textures for each cube face. + * + * @type {RGBELoader} + */ this.hdrLoader = new RGBELoader(); + + /** + * The texture type. + * + * @type {(HalfFloatType|FloatType)} + * @default HalfFloatType + */ this.type = HalfFloatType; } + /** + * Starts loading from the given URLs and passes the loaded HDR cube texture + * to the `onLoad()` callback. + * + * @param {Array} urls - The paths/URLs of the files to be loaded. This can also be a data URIs. + * @param {function(CubeTexture)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + * @return {CubeTexture} The HDR cube texture. + */ load( urls, onLoad, onProgress, onError ) { const texture = new CubeTexture(); @@ -101,6 +144,12 @@ class HDRCubeTextureLoader extends Loader { } + /** + * Sets the texture type. + * + * @param {(HalfFloatType|FloatType)} value - The texture type to set. + * @return {RGBELoader} A reference to this loader. + */ setDataType( value ) { this.type = value; diff --git a/examples/jsm/loaders/IESLoader.js b/examples/jsm/loaders/IESLoader.js index f33c6252f595d5..f8714321ddccd9 100644 --- a/examples/jsm/loaders/IESLoader.js +++ b/examples/jsm/loaders/IESLoader.js @@ -11,12 +11,39 @@ import { DataUtils } from 'three'; +/** + * A loader for the IES format. + * + * The loaded texture should be assigned to {@link IESSpotLight#map}. + * + * ```js + * const loader = new IESLoader(); + * const texture = await loader.loadAsync( 'ies/007cfb11e343e2f42e3b476be4ab684e.ies' ); + * + * const spotLight = new THREE.IESSpotLight( 0xff0000, 500 ); + * spotLight.iesMap = texture; + * ``` + * + * @augments Loader + * @three_import import { IESLoader } from 'three/addons/loaders/IESLoader.js'; + */ class IESLoader extends Loader { + /** + * Constructs a new IES loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); + /** + * The texture type. + * + * @type {(HalfFloatType|FloatType)} + * @default HalfFloatType + */ this.type = HalfFloatType; } @@ -112,6 +139,15 @@ class IESLoader extends Loader { } + /** + * Starts loading from the given URL and passes the loaded IES texture + * to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(DataTexture)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ load( url, onLoad, onProgress, onError ) { const loader = new FileLoader( this.manager ); @@ -129,6 +165,12 @@ class IESLoader extends Loader { } + /** + * Parses the given IES data. + * + * @param {string} text - The raw IES data. + * @return {DataTexture} THE IES data as a texture. + */ parse( text ) { const type = this.type; diff --git a/examples/jsm/loaders/KMZLoader.js b/examples/jsm/loaders/KMZLoader.js index 6f28f367fe8ef3..d01e3110f71dc3 100644 --- a/examples/jsm/loaders/KMZLoader.js +++ b/examples/jsm/loaders/KMZLoader.js @@ -7,14 +7,41 @@ import { import { ColladaLoader } from '../loaders/ColladaLoader.js'; import * as fflate from '../libs/fflate.module.js'; +/** + * A loader for the KMZ format. + * + * ```js + * const loader = new KMZLoader(); + * const kmz = await loader.loadAsync( './models/kmz/Box.kmz' ); + * + * scene.add( kmz.scene ); + * ``` + * + * @augments Loader + * @three_import import { KMZLoader } from 'three/addons/loaders/KMZLoader.js'; + */ class KMZLoader extends Loader { + /** + * Constructs a new KMZ loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); } + /** + * Starts loading from the given URL and passes the loaded KMZ asset + * to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function({scene:Group})} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ load( url, onLoad, onProgress, onError ) { const scope = this; @@ -50,6 +77,12 @@ class KMZLoader extends Loader { } + /** + * Parses the given KMZ data and returns an object holding the scene. + * + * @param {ArrayBuffer} data - The raw KMZ data as an array buffer. + * @return {{scene:Group}} The parsed KMZ asset. + */ parse( data ) { function findFile( url ) { diff --git a/examples/jsm/loaders/KTX2Loader.js b/examples/jsm/loaders/KTX2Loader.js index b7409bf18bb818..951efdb52e1cff 100644 --- a/examples/jsm/loaders/KTX2Loader.js +++ b/examples/jsm/loaders/KTX2Loader.js @@ -1,65 +1,79 @@ -/** - * Loader for KTX 2.0 GPU Texture containers. - * - * KTX 2.0 is a container format for various GPU texture formats. The loader - * supports Basis Universal GPU textures, which can be quickly transcoded to - * a wide variety of GPU texture compression formats, as well as some - * uncompressed DataTexture and Data3DTexture formats. - * - * References: - * - KTX: http://github.khronos.org/KTX-Specification/ - * - DFD: https://www.khronos.org/registry/DataFormat/specs/1.3/dataformat.1.3.html#basicdescriptor - */ - import { - CompressedTexture, CompressedArrayTexture, + CompressedCubeTexture, + CompressedTexture, Data3DTexture, DataTexture, FileLoader, FloatType, HalfFloatType, - NoColorSpace, LinearFilter, LinearMipmapLinearFilter, + LinearSRGBColorSpace, Loader, - RedFormat, - RGB_ETC1_Format, - RGB_ETC2_Format, - RGB_PVRTC_4BPPV1_Format, - RGB_S3TC_DXT1_Format, + NoColorSpace, + RGBAFormat, RGBA_ASTC_4x4_Format, + RGBA_ASTC_6x6_Format, RGBA_BPTC_Format, + RGBA_S3TC_DXT3_Format, RGBA_ETC2_EAC_Format, RGBA_PVRTC_4BPPV1_Format, + RGBA_S3TC_DXT1_Format, RGBA_S3TC_DXT5_Format, - RGBAFormat, + RGB_BPTC_UNSIGNED_Format, + RGB_ETC1_Format, + RGB_ETC2_Format, + RGB_PVRTC_4BPPV1_Format, + RGB_S3TC_DXT1_Format, RGFormat, + RedFormat, SRGBColorSpace, - UnsignedByteType, + UnsignedByteType } from 'three'; import { WorkerPool } from '../utils/WorkerPool.js'; import { read, KHR_DF_FLAG_ALPHA_PREMULTIPLIED, + KHR_DF_PRIMARIES_BT709, + KHR_DF_PRIMARIES_DISPLAYP3, + KHR_DF_PRIMARIES_UNSPECIFIED, KHR_DF_TRANSFER_SRGB, KHR_SUPERCOMPRESSION_NONE, KHR_SUPERCOMPRESSION_ZSTD, - VK_FORMAT_UNDEFINED, - VK_FORMAT_R16_SFLOAT, - VK_FORMAT_R16G16_SFLOAT, + VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK_EXT, + VK_FORMAT_ASTC_4x4_SRGB_BLOCK, + VK_FORMAT_ASTC_4x4_UNORM_BLOCK, + VK_FORMAT_ASTC_6x6_SRGB_BLOCK, + VK_FORMAT_ASTC_6x6_UNORM_BLOCK, + VK_FORMAT_BC1_RGBA_SRGB_BLOCK, + VK_FORMAT_BC1_RGBA_UNORM_BLOCK, + VK_FORMAT_BC1_RGB_SRGB_BLOCK, + VK_FORMAT_BC1_RGB_UNORM_BLOCK, + VK_FORMAT_BC3_SRGB_BLOCK, + VK_FORMAT_BC3_UNORM_BLOCK, + VK_FORMAT_BC5_SNORM_BLOCK, + VK_FORMAT_BC5_UNORM_BLOCK, + VK_FORMAT_BC7_SRGB_BLOCK, + VK_FORMAT_BC7_UNORM_BLOCK, + VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK, + VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK, VK_FORMAT_R16G16B16A16_SFLOAT, - VK_FORMAT_R32_SFLOAT, - VK_FORMAT_R32G32_SFLOAT, + VK_FORMAT_R16G16_SFLOAT, + VK_FORMAT_R16_SFLOAT, VK_FORMAT_R32G32B32A32_SFLOAT, - VK_FORMAT_R8_SRGB, - VK_FORMAT_R8_UNORM, - VK_FORMAT_R8G8_SRGB, - VK_FORMAT_R8G8_UNORM, + VK_FORMAT_R32G32_SFLOAT, + VK_FORMAT_R32_SFLOAT, VK_FORMAT_R8G8B8A8_SRGB, VK_FORMAT_R8G8B8A8_UNORM, + VK_FORMAT_R8G8_SRGB, + VK_FORMAT_R8G8_UNORM, + VK_FORMAT_R8_SRGB, + VK_FORMAT_R8_UNORM, + VK_FORMAT_UNDEFINED } from '../libs/ktx-parse.module.js'; import { ZSTDDecoder } from '../libs/zstddec.module.js'; +import { DisplayP3ColorSpace, LinearDisplayP3ColorSpace } from '../math/ColorSpaces.js'; const _taskCache = new WeakMap(); @@ -67,8 +81,40 @@ let _activeLoaders = 0; let _zstd; +/** + * A loader for KTX 2.0 GPU Texture containers. + * + * KTX 2.0 is a container format for various GPU texture formats. The loader supports Basis Universal GPU textures, + * which can be quickly transcoded to a wide variety of GPU texture compression formats. While KTX 2.0 also allows + * other hardware-specific formats, this loader does not yet parse them. + * + * This loader parses the KTX 2.0 container and transcodes to a supported GPU compressed texture format. + * The required WASM transcoder and JS wrapper are available from the `examples/jsm/libs/basis` directory. + * + * This loader relies on Web Assembly which is not supported in older browsers. + * + * References: + * - [KTX specification]{@link http://github.khronos.org/KTX-Specification/} + * - [DFD]{@link https://www.khronos.org/registry/DataFormat/specs/1.3/dataformat.1.3.html#basicdescriptor} + * - [BasisU HDR]{@link https://github.com/BinomialLLC/basis_universal/wiki/UASTC-HDR-Texture-Specification-v1.0} + * + * ```js + * const loader = new KTX2Loader(); + * loader.setTranscoderPath( 'examples/jsm/libs/basis/' ); + * loader.detectSupport( renderer ); + * const texture = loader.loadAsync( 'diffuse.ktx2' ); + * ``` + * + * @augments Loader + * @three_import import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js'; + */ class KTX2Loader extends Loader { + /** + * Constructs a new KTX2 loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); @@ -94,6 +140,14 @@ class KTX2Loader extends Loader { } + /** + * Sets the transcoder path. + * + * The WASM transcoder and JS wrapper are available from the `examples/jsm/libs/basis` directory. + * + * @param {string} path - The transcoder path to set. + * @return {KTX2Loader} A reference to this loader. + */ setTranscoderPath( path ) { this.transcoderPath = path; @@ -102,31 +156,71 @@ class KTX2Loader extends Loader { } - setWorkerLimit( num ) { + /** + * Sets the maximum number of Web Workers to be allocated by this instance. + * + * @param {number} workerLimit - The worker limit. + * @return {KTX2Loader} A reference to this loader. + */ + setWorkerLimit( workerLimit ) { - this.workerPool.setWorkerLimit( num ); + this.workerPool.setWorkerLimit( workerLimit ); return this; } + + /** + * Async version of {@link KTX2Loader#detectSupport}. + * + * @async + * @param {WebGPURenderer|WebGLRenderer} renderer - The renderer. + * @return {Promise} A Promise that resolves when the support has been detected. + */ + async detectSupportAsync( renderer ) { + + this.workerConfig = { + astcSupported: await renderer.hasFeatureAsync( 'texture-compression-astc' ), + astcHDRSupported: false, // https://github.com/gpuweb/gpuweb/issues/3856 + etc1Supported: await renderer.hasFeatureAsync( 'texture-compression-etc1' ), + etc2Supported: await renderer.hasFeatureAsync( 'texture-compression-etc2' ), + dxtSupported: await renderer.hasFeatureAsync( 'texture-compression-bc' ), + bptcSupported: await renderer.hasFeatureAsync( 'texture-compression-bptc' ), + pvrtcSupported: await renderer.hasFeatureAsync( 'texture-compression-pvrtc' ) + }; + + return this; + + } + + /** + * Detects hardware support for available compressed texture formats, to determine + * the output format for the transcoder. Must be called before loading a texture. + * + * @param {WebGPURenderer|WebGLRenderer} renderer - The renderer. + * @return {KTX2Loader} A reference to this loader. + */ detectSupport( renderer ) { if ( renderer.isWebGPURenderer === true ) { this.workerConfig = { astcSupported: renderer.hasFeature( 'texture-compression-astc' ), - etc1Supported: false, + astcHDRSupported: false, // https://github.com/gpuweb/gpuweb/issues/3856 + etc1Supported: renderer.hasFeature( 'texture-compression-etc1' ), etc2Supported: renderer.hasFeature( 'texture-compression-etc2' ), dxtSupported: renderer.hasFeature( 'texture-compression-bc' ), - bptcSupported: false, - pvrtcSupported: false + bptcSupported: renderer.hasFeature( 'texture-compression-bptc' ), + pvrtcSupported: renderer.hasFeature( 'texture-compression-pvrtc' ) }; } else { this.workerConfig = { astcSupported: renderer.extensions.has( 'WEBGL_compressed_texture_astc' ), + astcHDRSupported: renderer.extensions.has( 'WEBGL_compressed_texture_astc' ) + && renderer.extensions.get( 'WEBGL_compressed_texture_astc' ).getSupportedProfiles().includes( 'hdr' ), etc1Supported: renderer.extensions.has( 'WEBGL_compressed_texture_etc1' ), etc2Supported: renderer.extensions.has( 'WEBGL_compressed_texture_etc' ), dxtSupported: renderer.extensions.has( 'WEBGL_compressed_texture_s3tc' ), @@ -135,19 +229,14 @@ class KTX2Loader extends Loader { || renderer.extensions.has( 'WEBKIT_WEBGL_compressed_texture_pvrtc' ) }; - if ( renderer.capabilities.isWebGL2 ) { - - // https://github.com/mrdoob/three.js/pull/22928 - this.workerConfig.etc1Supported = false; - - } - } return this; } + // TODO: Make this method private + init() { if ( ! this.transcoderPending ) { @@ -173,6 +262,7 @@ class KTX2Loader extends Loader { const body = [ '/* constants */', 'let _EngineFormat = ' + JSON.stringify( KTX2Loader.EngineFormat ), + 'let _EngineType = ' + JSON.stringify( KTX2Loader.EngineType ), 'let _TranscoderFormat = ' + JSON.stringify( KTX2Loader.TranscoderFormat ), 'let _BasisFormat = ' + JSON.stringify( KTX2Loader.BasisFormat ), '/* basis_transcoder.js */', @@ -218,6 +308,15 @@ class KTX2Loader extends Loader { } + /** + * Starts loading from the given URL and passes the loaded KTX2 texture + * to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(CompressedTexture)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ load( url, onLoad, onProgress, onError ) { if ( this.workerConfig === null ) { @@ -228,51 +327,70 @@ class KTX2Loader extends Loader { const loader = new FileLoader( this.manager ); - loader.setResponseType( 'arraybuffer' ); + loader.setPath( this.path ); + loader.setCrossOrigin( this.crossOrigin ); loader.setWithCredentials( this.withCredentials ); + loader.setResponseType( 'arraybuffer' ); loader.load( url, ( buffer ) => { - // Check for an existing task using this buffer. A transferred buffer cannot be transferred - // again from this thread. - if ( _taskCache.has( buffer ) ) { + this.parse( buffer, onLoad, onError ); - const cachedTask = _taskCache.get( buffer ); + }, onProgress, onError ); - return cachedTask.promise.then( onLoad ).catch( onError ); + } - } + /** + * Parses the given KTX2 data. + * + * @param {ArrayBuffer} buffer - The raw KTX2 data as an array buffer. + * @param {function(CompressedTexture)} onLoad - Executed when the loading/parsing process has been finished. + * @param {onErrorCallback} onError - Executed when errors occur. + * @returns {Promise} A Promise that resolves when the parsing has been finished. + */ + parse( buffer, onLoad, onError ) { - this._createTexture( buffer ) - .then( ( texture ) => onLoad ? onLoad( texture ) : null ) - .catch( onError ); + if ( this.workerConfig === null ) { - }, onProgress, onError ); + throw new Error( 'THREE.KTX2Loader: Missing initialization with `.detectSupport( renderer )`.' ); + + } + + // Check for an existing task using this buffer. A transferred buffer cannot be transferred + // again from this thread. + if ( _taskCache.has( buffer ) ) { + + const cachedTask = _taskCache.get( buffer ); + + return cachedTask.promise.then( onLoad ).catch( onError ); + + } + + this._createTexture( buffer ) + .then( ( texture ) => onLoad ? onLoad( texture ) : null ) + .catch( onError ); } _createTextureFrom( transcodeResult, container ) { - const { faces, width, height, format, type, error, dfdTransferFn, dfdFlags } = transcodeResult; + const { type: messageType, error, data: { faces, width, height, format, type, dfdFlags } } = transcodeResult; - if ( type === 'error' ) return Promise.reject( error ); + if ( messageType === 'error' ) return Promise.reject( error ); let texture; if ( container.faceCount === 6 ) { - texture = new CompressedTexture(); - texture.image = faces; - texture.format = format; - texture.type = UnsignedByteType; + texture = new CompressedCubeTexture( faces, format, type ); } else { const mipmaps = faces[ 0 ].mipmaps; texture = container.layerCount > 1 - ? new CompressedArrayTexture( mipmaps, width, height, container.layerCount, format, UnsignedByteType ) - : new CompressedTexture( mipmaps, width, height, format, UnsignedByteType ); + ? new CompressedArrayTexture( mipmaps, width, height, container.layerCount, format, type ) + : new CompressedTexture( mipmaps, width, height, format, type ); } @@ -281,8 +399,7 @@ class KTX2Loader extends Loader { texture.generateMipmaps = false; texture.needsUpdate = true; - // TODO: Detect NoColorSpace vs. LinearSRGBColorSpace based on primaries. - texture.colorSpace = dfdTransferFn === KHR_DF_TRANSFER_SRGB ? SRGBColorSpace : NoColorSpace; + texture.colorSpace = parseColorSpace( container ); texture.premultiplyAlpha = !! ( dfdFlags & KHR_DF_FLAG_ALPHA_PREMULTIPLIED ); return texture; @@ -290,41 +407,30 @@ class KTX2Loader extends Loader { } /** + * @private * @param {ArrayBuffer} buffer - * @param {object?} config + * @param {?Object} config * @return {Promise} */ async _createTexture( buffer, config = {} ) { const container = read( new Uint8Array( buffer ) ); - if ( container.vkFormat !== VK_FORMAT_UNDEFINED ) { - - const mipmaps = []; - const pendings = []; - - for ( let levelIndex = 0; levelIndex < container.levels.length; levelIndex ++ ) { - - pendings.push( createDataTexture( container, levelIndex ).then( function ( dataTexture ) { - - mipmaps[ levelIndex ] = dataTexture; + // Basis UASTC HDR is a subset of ASTC, which can be transcoded efficiently + // to BC6H. To detect whether a KTX2 file uses Basis UASTC HDR, or default + // ASTC, inspect the DFD color model. + // + // Source: https://github.com/BinomialLLC/basis_universal/issues/381 + const isBasisHDR = container.vkFormat === VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK_EXT + && container.dataFormatDescriptor[ 0 ].colorModel === 0xA7; - } ) ); + // If the device supports ASTC, Basis UASTC HDR requires no transcoder. + const needsTranscoder = container.vkFormat === VK_FORMAT_UNDEFINED + || isBasisHDR && ! this.workerConfig.astcHDRSupported; - } + if ( ! needsTranscoder ) { - await Promise.all( pendings ); - - const texture = mipmaps[ 0 ]; - texture.mipmaps = mipmaps.map( dt => { - return { - data: dt.source.data, - width: dt.source.data.width, - height: dt.source.data.height, - depth: dt.source.data.depth - }; - } ); - return texture; + return createRawTexture( container ); } @@ -343,6 +449,10 @@ class KTX2Loader extends Loader { } + /** + * Frees internal resources. This method should be called + * when the loader is no longer required. + */ dispose() { this.workerPool.dispose(); @@ -350,8 +460,6 @@ class KTX2Loader extends Loader { _activeLoaders --; - return this; - } } @@ -361,9 +469,11 @@ class KTX2Loader extends Loader { KTX2Loader.BasisFormat = { ETC1S: 0, - UASTC_4x4: 1, + UASTC: 1, + UASTC_HDR: 2, }; +// Source: https://github.com/BinomialLLC/basis_universal/blob/master/webgl/texture_test/index.html KTX2Loader.TranscoderFormat = { ETC1: 0, ETC2: 1, @@ -382,11 +492,15 @@ KTX2Loader.TranscoderFormat = { RGB565: 14, BGR565: 15, RGBA4444: 16, + BC6H: 22, + RGB_HALF: 24, + RGBA_HALF: 25, }; KTX2Loader.EngineFormat = { RGBAFormat: RGBAFormat, RGBA_ASTC_4x4_Format: RGBA_ASTC_4x4_Format, + RGB_BPTC_UNSIGNED_Format: RGB_BPTC_UNSIGNED_Format, RGBA_BPTC_Format: RGBA_BPTC_Format, RGBA_ETC2_EAC_Format: RGBA_ETC2_EAC_Format, RGBA_PVRTC_4BPPV1_Format: RGBA_PVRTC_4BPPV1_Format, @@ -394,9 +508,14 @@ KTX2Loader.EngineFormat = { RGB_ETC1_Format: RGB_ETC1_Format, RGB_ETC2_Format: RGB_ETC2_Format, RGB_PVRTC_4BPPV1_Format: RGB_PVRTC_4BPPV1_Format, - RGB_S3TC_DXT1_Format: RGB_S3TC_DXT1_Format, + RGBA_S3TC_DXT1_Format: RGBA_S3TC_DXT1_Format, }; +KTX2Loader.EngineType = { + UnsignedByteType: UnsignedByteType, + HalfFloatType: HalfFloatType, + FloatType: FloatType, +}; /* WEB WORKER */ @@ -407,6 +526,7 @@ KTX2Loader.BasisWorker = function () { let BasisModule; const EngineFormat = _EngineFormat; // eslint-disable-line no-undef + const EngineType = _EngineType; // eslint-disable-line no-undef const TranscoderFormat = _TranscoderFormat; // eslint-disable-line no-undef const BasisFormat = _BasisFormat; // eslint-disable-line no-undef @@ -426,9 +546,9 @@ KTX2Loader.BasisWorker = function () { try { - const { faces, buffers, width, height, hasAlpha, format, dfdTransferFn, dfdFlags } = transcode( message.buffer ); + const { faces, buffers, width, height, hasAlpha, format, type, dfdFlags } = transcode( message.buffer ); - self.postMessage( { type: 'transcode', id: message.id, faces, width, height, hasAlpha, format, dfdTransferFn, dfdFlags }, buffers ); + self.postMessage( { type: 'transcode', id: message.id, data: { faces, width, height, hasAlpha, format, type, dfdFlags } }, buffers ); } catch ( error ) { @@ -484,17 +604,35 @@ KTX2Loader.BasisWorker = function () { } - const basisFormat = ktx2File.isUASTC() ? BasisFormat.UASTC_4x4 : BasisFormat.ETC1S; + let basisFormat; + + if ( ktx2File.isUASTC() ) { + + basisFormat = BasisFormat.UASTC; + + } else if ( ktx2File.isETC1S() ) { + + basisFormat = BasisFormat.ETC1S; + + } else if ( ktx2File.isHDR() ) { + + basisFormat = BasisFormat.UASTC_HDR; + + } else { + + throw new Error( 'THREE.KTX2Loader: Unknown Basis encoding' ); + + } + const width = ktx2File.getWidth(); const height = ktx2File.getHeight(); const layerCount = ktx2File.getLayers() || 1; const levelCount = ktx2File.getLevels(); const faceCount = ktx2File.getFaces(); const hasAlpha = ktx2File.getHasAlpha(); - const dfdTransferFn = ktx2File.getDFDTransferFunc(); const dfdFlags = ktx2File.getDFDFlags(); - const { transcoderFormat, engineFormat } = getTranscoderFormat( basisFormat, width, height, hasAlpha ); + const { transcoderFormat, engineFormat, engineType } = getTranscoderFormat( basisFormat, width, height, hasAlpha ); if ( ! width || ! height || ! levelCount ) { @@ -526,11 +664,37 @@ KTX2Loader.BasisWorker = function () { for ( let layer = 0; layer < layerCount; layer ++ ) { const levelInfo = ktx2File.getImageLevelInfo( mip, layer, face ); - mipWidth = levelInfo.origWidth < 4 ? levelInfo.origWidth : levelInfo.width; - mipHeight = levelInfo.origHeight < 4 ? levelInfo.origHeight : levelInfo.height; - const dst = new Uint8Array( ktx2File.getImageTranscodedSizeInBytes( mip, layer, 0, transcoderFormat ) ); + + if ( face === 0 && mip === 0 && layer === 0 && ( levelInfo.origWidth % 4 !== 0 || levelInfo.origHeight % 4 !== 0 ) ) { + + console.warn( 'THREE.KTX2Loader: ETC1S and UASTC textures should use multiple-of-four dimensions.' ); + + } + + if ( levelCount > 1 ) { + + mipWidth = levelInfo.origWidth; + mipHeight = levelInfo.origHeight; + + } else { + + // Handles non-multiple-of-four dimensions in textures without mipmaps. Textures with + // mipmaps must use multiple-of-four dimensions, for some texture formats and APIs. + // See mrdoob/three.js#25908. + mipWidth = levelInfo.width; + mipHeight = levelInfo.height; + + } + + let dst = new Uint8Array( ktx2File.getImageTranscodedSizeInBytes( mip, layer, 0, transcoderFormat ) ); const status = ktx2File.transcodeImage( dst, mip, layer, face, transcoderFormat, 0, - 1, - 1 ); + if ( engineType === EngineType.HalfFloatType ) { + + dst = new Uint16Array( dst.buffer, dst.byteOffset, dst.byteLength / Uint16Array.BYTES_PER_ELEMENT ); + + } + if ( ! status ) { cleanup(); @@ -549,122 +713,159 @@ KTX2Loader.BasisWorker = function () { } - faces.push( { mipmaps, width, height, format: engineFormat } ); + faces.push( { mipmaps, width, height, format: engineFormat, type: engineType } ); } cleanup(); - return { faces, buffers, width, height, hasAlpha, format: engineFormat, dfdTransferFn, dfdFlags }; + return { faces, buffers, width, height, hasAlpha, dfdFlags, format: engineFormat, type: engineType }; } // - // Optimal choice of a transcoder target format depends on the Basis format (ETC1S or UASTC), - // device capabilities, and texture dimensions. The list below ranks the formats separately - // for ETC1S and UASTC. + // Optimal choice of a transcoder target format depends on the Basis format (ETC1S, UASTC, or + // UASTC HDR), device capabilities, and texture dimensions. The list below ranks the formats + // separately for each format. Currently, priority is assigned based on: + // + // high quality > low quality > uncompressed // - // In some cases, transcoding UASTC to RGBA32 might be preferred for higher quality (at - // significant memory cost) compared to ETC1/2, BC1/3, and PVRTC. The transcoder currently - // chooses RGBA32 only as a last resort and does not expose that option to the caller. + // Prioritization may be revisited, or exposed for configuration, in the future. + // + // Reference: https://github.com/KhronosGroup/3D-Formats-Guidelines/blob/main/KTXDeveloperGuide.md const FORMAT_OPTIONS = [ { if: 'astcSupported', - basisFormat: [ BasisFormat.UASTC_4x4 ], + basisFormat: [ BasisFormat.UASTC ], transcoderFormat: [ TranscoderFormat.ASTC_4x4, TranscoderFormat.ASTC_4x4 ], engineFormat: [ EngineFormat.RGBA_ASTC_4x4_Format, EngineFormat.RGBA_ASTC_4x4_Format ], + engineType: [ EngineType.UnsignedByteType ], priorityETC1S: Infinity, priorityUASTC: 1, needsPowerOfTwo: false, }, { if: 'bptcSupported', - basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC ], transcoderFormat: [ TranscoderFormat.BC7_M5, TranscoderFormat.BC7_M5 ], engineFormat: [ EngineFormat.RGBA_BPTC_Format, EngineFormat.RGBA_BPTC_Format ], + engineType: [ EngineType.UnsignedByteType ], priorityETC1S: 3, priorityUASTC: 2, needsPowerOfTwo: false, }, { if: 'dxtSupported', - basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC ], transcoderFormat: [ TranscoderFormat.BC1, TranscoderFormat.BC3 ], - engineFormat: [ EngineFormat.RGB_S3TC_DXT1_Format, EngineFormat.RGBA_S3TC_DXT5_Format ], + engineFormat: [ EngineFormat.RGBA_S3TC_DXT1_Format, EngineFormat.RGBA_S3TC_DXT5_Format ], + engineType: [ EngineType.UnsignedByteType ], priorityETC1S: 4, priorityUASTC: 5, needsPowerOfTwo: false, }, { if: 'etc2Supported', - basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC ], transcoderFormat: [ TranscoderFormat.ETC1, TranscoderFormat.ETC2 ], engineFormat: [ EngineFormat.RGB_ETC2_Format, EngineFormat.RGBA_ETC2_EAC_Format ], + engineType: [ EngineType.UnsignedByteType ], priorityETC1S: 1, priorityUASTC: 3, needsPowerOfTwo: false, }, { if: 'etc1Supported', - basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC ], transcoderFormat: [ TranscoderFormat.ETC1 ], engineFormat: [ EngineFormat.RGB_ETC1_Format ], + engineType: [ EngineType.UnsignedByteType ], priorityETC1S: 2, priorityUASTC: 4, needsPowerOfTwo: false, }, { if: 'pvrtcSupported', - basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ], + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC ], transcoderFormat: [ TranscoderFormat.PVRTC1_4_RGB, TranscoderFormat.PVRTC1_4_RGBA ], engineFormat: [ EngineFormat.RGB_PVRTC_4BPPV1_Format, EngineFormat.RGBA_PVRTC_4BPPV1_Format ], + engineType: [ EngineType.UnsignedByteType ], priorityETC1S: 5, priorityUASTC: 6, needsPowerOfTwo: true, }, - ]; + { + if: 'bptcSupported', + basisFormat: [ BasisFormat.UASTC_HDR ], + transcoderFormat: [ TranscoderFormat.BC6H ], + engineFormat: [ EngineFormat.RGB_BPTC_UNSIGNED_Format ], + engineType: [ EngineType.HalfFloatType ], + priorityHDR: 1, + needsPowerOfTwo: false, + }, - const ETC1S_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) { + // Uncompressed fallbacks. - return a.priorityETC1S - b.priorityETC1S; + { + basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC ], + transcoderFormat: [ TranscoderFormat.RGBA32, TranscoderFormat.RGBA32 ], + engineFormat: [ EngineFormat.RGBAFormat, EngineFormat.RGBAFormat ], + engineType: [ EngineType.UnsignedByteType, EngineType.UnsignedByteType ], + priorityETC1S: 100, + priorityUASTC: 100, + needsPowerOfTwo: false, + }, + { + basisFormat: [ BasisFormat.UASTC_HDR ], + transcoderFormat: [ TranscoderFormat.RGBA_HALF ], + engineFormat: [ EngineFormat.RGBAFormat ], + engineType: [ EngineType.HalfFloatType ], + priorityHDR: 100, + needsPowerOfTwo: false, + } + ]; - } ); - const UASTC_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) { + const OPTIONS = { + // TODO: For ETC1S we intentionally sort by _UASTC_ priority, preserving + // a historical accident shown to avoid performance pitfalls for Linux with + // Firefox & AMD GPU (RadeonSI). Further work needed. + // See https://github.com/mrdoob/three.js/pull/29730. + [ BasisFormat.ETC1S ]: FORMAT_OPTIONS + .filter( ( opt ) => opt.basisFormat.includes( BasisFormat.ETC1S ) ) + .sort( ( a, b ) => a.priorityUASTC - b.priorityUASTC ), - return a.priorityUASTC - b.priorityUASTC; + [ BasisFormat.UASTC ]: FORMAT_OPTIONS + .filter( ( opt ) => opt.basisFormat.includes( BasisFormat.UASTC ) ) + .sort( ( a, b ) => a.priorityUASTC - b.priorityUASTC ), - } ); + [ BasisFormat.UASTC_HDR ]: FORMAT_OPTIONS + .filter( ( opt ) => opt.basisFormat.includes( BasisFormat.UASTC_HDR ) ) + .sort( ( a, b ) => a.priorityHDR - b.priorityHDR ), + }; function getTranscoderFormat( basisFormat, width, height, hasAlpha ) { - let transcoderFormat; - let engineFormat; - - const options = basisFormat === BasisFormat.ETC1S ? ETC1S_OPTIONS : UASTC_OPTIONS; + const options = OPTIONS[ basisFormat ]; for ( let i = 0; i < options.length; i ++ ) { const opt = options[ i ]; - if ( ! config[ opt.if ] ) continue; + if ( opt.if && ! config[ opt.if ] ) continue; if ( ! opt.basisFormat.includes( basisFormat ) ) continue; if ( hasAlpha && opt.transcoderFormat.length < 2 ) continue; if ( opt.needsPowerOfTwo && ! ( isPowerOfTwo( width ) && isPowerOfTwo( height ) ) ) continue; - transcoderFormat = opt.transcoderFormat[ hasAlpha ? 1 : 0 ]; - engineFormat = opt.engineFormat[ hasAlpha ? 1 : 0 ]; + const transcoderFormat = opt.transcoderFormat[ hasAlpha ? 1 : 0 ]; + const engineFormat = opt.engineFormat[ hasAlpha ? 1 : 0 ]; + const engineType = opt.engineType[ 0 ]; - return { transcoderFormat, engineFormat }; + return { transcoderFormat, engineFormat, engineType }; } - console.warn( 'THREE.KTX2Loader: No suitable compressed texture format found. Decoding to RGBA32.' ); - - transcoderFormat = TranscoderFormat.RGBA32; - engineFormat = EngineFormat.RGBAFormat; - - return { transcoderFormat, engineFormat }; + throw new Error( 'THREE.KTX2Loader: Failed to identify transcoding target.' ); } @@ -676,7 +877,12 @@ KTX2Loader.BasisWorker = function () { } - /** Concatenates N byte arrays. */ + /** + * Concatenates N byte arrays. + * + * @param {Uint8Array[]} arrays + * @return {Uint8Array} + */ function concat( arrays ) { if ( arrays.length === 1 ) return arrays[ 0 ]; @@ -709,8 +915,10 @@ KTX2Loader.BasisWorker = function () { }; -// -// DataTexture and Data3DTexture parsing. +// Parsing for non-Basis textures. These textures may have supercompression +// like Zstd, but they do not require transcoding. + +const UNCOMPRESSED_FORMATS = new Set( [ RGBAFormat, RGFormat, RedFormat ] ); const FORMAT_MAP = { @@ -729,6 +937,29 @@ const FORMAT_MAP = { [ VK_FORMAT_R8_SRGB ]: RedFormat, [ VK_FORMAT_R8_UNORM ]: RedFormat, + [ VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK ]: RGB_ETC2_Format, + [ VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK ]: RGBA_ETC2_EAC_Format, + + [ VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK_EXT ]: RGBA_ASTC_4x4_Format, + [ VK_FORMAT_ASTC_4x4_SRGB_BLOCK ]: RGBA_ASTC_4x4_Format, + [ VK_FORMAT_ASTC_4x4_UNORM_BLOCK ]: RGBA_ASTC_4x4_Format, + [ VK_FORMAT_ASTC_6x6_SRGB_BLOCK ]: RGBA_ASTC_6x6_Format, + [ VK_FORMAT_ASTC_6x6_UNORM_BLOCK ]: RGBA_ASTC_6x6_Format, + + [ VK_FORMAT_BC1_RGBA_UNORM_BLOCK ]: RGBA_S3TC_DXT1_Format, + [ VK_FORMAT_BC1_RGBA_SRGB_BLOCK ]: RGBA_S3TC_DXT1_Format, + [ VK_FORMAT_BC1_RGB_UNORM_BLOCK ]: RGB_S3TC_DXT1_Format, + [ VK_FORMAT_BC1_RGB_SRGB_BLOCK ]: RGB_S3TC_DXT1_Format, + + [ VK_FORMAT_BC3_SRGB_BLOCK ]: RGBA_S3TC_DXT3_Format, + [ VK_FORMAT_BC3_UNORM_BLOCK ]: RGBA_S3TC_DXT3_Format, + + [ VK_FORMAT_BC5_SNORM_BLOCK ]: RGBA_S3TC_DXT5_Format, + [ VK_FORMAT_BC5_UNORM_BLOCK ]: RGBA_S3TC_DXT5_Format, + + [ VK_FORMAT_BC7_SRGB_BLOCK ]: RGBA_BPTC_Format, + [ VK_FORMAT_BC7_UNORM_BLOCK ]: RGBA_BPTC_Format, + }; const TYPE_MAP = { @@ -748,22 +979,18 @@ const TYPE_MAP = { [ VK_FORMAT_R8_SRGB ]: UnsignedByteType, [ VK_FORMAT_R8_UNORM ]: UnsignedByteType, -}; - -const COLOR_SPACE_MAP = { + [ VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK ]: UnsignedByteType, + [ VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK ]: UnsignedByteType, - [ VK_FORMAT_R8G8B8A8_SRGB ]: SRGBColorSpace, - [ VK_FORMAT_R8G8_SRGB ]: SRGBColorSpace, - [ VK_FORMAT_R8_SRGB ]: SRGBColorSpace, + [ VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK_EXT ]: HalfFloatType, + [ VK_FORMAT_ASTC_6x6_SRGB_BLOCK ]: UnsignedByteType, + [ VK_FORMAT_ASTC_6x6_UNORM_BLOCK ]: UnsignedByteType, }; -async function createDataTexture( container, levelIndex = 0 ) { +async function createRawTexture( container ) { const { vkFormat } = container; - const pixelWidth = Math.max( 1, container.pixelWidth >> levelIndex ); - const pixelHeight = Math.max( 1, container.pixelHeight >> levelIndex ); - const pixelDepth = Math.max( 1, container.pixelDepth >> levelIndex ); if ( FORMAT_MAP[ vkFormat ] === undefined ) { @@ -771,16 +998,11 @@ async function createDataTexture( container, levelIndex = 0 ) { } - const level = container.levels[ levelIndex ]; - - let levelData; - let view; - - if ( container.supercompressionScheme === KHR_SUPERCOMPRESSION_NONE ) { + // - levelData = level.levelData; + let zstd; - } else if ( container.supercompressionScheme === KHR_SUPERCOMPRESSION_ZSTD ) { + if ( container.supercompressionScheme === KHR_SUPERCOMPRESSION_ZSTD ) { if ( ! _zstd ) { @@ -794,49 +1016,102 @@ async function createDataTexture( container, levelIndex = 0 ) { } - levelData = ( await _zstd ).decode( level.levelData, level.uncompressedByteLength ); + zstd = await _zstd; - } else { + } + + // - throw new Error( 'THREE.KTX2Loader: Unsupported supercompressionScheme.' ); + const mipmaps = []; - } - if ( TYPE_MAP[ vkFormat ] === FloatType ) { + for ( let levelIndex = 0; levelIndex < container.levels.length; levelIndex ++ ) { + + const levelWidth = Math.max( 1, container.pixelWidth >> levelIndex ); + const levelHeight = Math.max( 1, container.pixelHeight >> levelIndex ); + const levelDepth = container.pixelDepth ? Math.max( 1, container.pixelDepth >> levelIndex ) : 0; + + const level = container.levels[ levelIndex ]; + + let levelData; + + if ( container.supercompressionScheme === KHR_SUPERCOMPRESSION_NONE ) { + + levelData = level.levelData; + + } else if ( container.supercompressionScheme === KHR_SUPERCOMPRESSION_ZSTD ) { + + levelData = zstd.decode( level.levelData, level.uncompressedByteLength ); + + } else { + + throw new Error( 'THREE.KTX2Loader: Unsupported supercompressionScheme.' ); + + } + + let data; + + if ( TYPE_MAP[ vkFormat ] === FloatType ) { + + data = new Float32Array( + + levelData.buffer, + levelData.byteOffset, + levelData.byteLength / Float32Array.BYTES_PER_ELEMENT + + ); + + } else if ( TYPE_MAP[ vkFormat ] === HalfFloatType ) { + + data = new Uint16Array( + + levelData.buffer, + levelData.byteOffset, + levelData.byteLength / Uint16Array.BYTES_PER_ELEMENT + + ); + + } else { + + data = levelData; - view = new Float32Array( + } - levelData.buffer, - levelData.byteOffset, - levelData.byteLength / Float32Array.BYTES_PER_ELEMENT + mipmaps.push( { - ); + data: data, + width: levelWidth, + height: levelHeight, + depth: levelDepth, - } else if ( TYPE_MAP[ vkFormat ] === HalfFloatType ) { + } ); + + } - view = new Uint16Array( + let texture; - levelData.buffer, - levelData.byteOffset, - levelData.byteLength / Uint16Array.BYTES_PER_ELEMENT + if ( UNCOMPRESSED_FORMATS.has( FORMAT_MAP[ vkFormat ] ) ) { - ); + texture = container.pixelDepth === 0 + ? new DataTexture( mipmaps[ 0 ].data, container.pixelWidth, container.pixelHeight ) + : new Data3DTexture( mipmaps[ 0 ].data, container.pixelWidth, container.pixelHeight, container.pixelDepth ); } else { - view = levelData; + if ( container.pixelDepth > 0 ) throw new Error( 'THREE.KTX2Loader: Unsupported pixelDepth.' ); + + texture = new CompressedTexture( mipmaps, container.pixelWidth, container.pixelHeight ); + + texture.minFilter = mipmaps.length === 1 ? LinearFilter : LinearMipmapLinearFilter; + texture.magFilter = LinearFilter; } - // - const texture = pixelDepth === 0 - ? new DataTexture( view, pixelWidth, pixelHeight ) - : new Data3DTexture( view, pixelWidth, pixelHeight, pixelDepth ); + texture.mipmaps = mipmaps; texture.type = TYPE_MAP[ vkFormat ]; texture.format = FORMAT_MAP[ vkFormat ]; - texture.colorSpace = COLOR_SPACE_MAP[ vkFormat ] || NoColorSpace; - + texture.colorSpace = parseColorSpace( container ); texture.needsUpdate = true; // @@ -845,4 +1120,29 @@ async function createDataTexture( container, levelIndex = 0 ) { } +function parseColorSpace( container ) { + + const dfd = container.dataFormatDescriptor[ 0 ]; + + if ( dfd.colorPrimaries === KHR_DF_PRIMARIES_BT709 ) { + + return dfd.transferFunction === KHR_DF_TRANSFER_SRGB ? SRGBColorSpace : LinearSRGBColorSpace; + + } else if ( dfd.colorPrimaries === KHR_DF_PRIMARIES_DISPLAYP3 ) { + + return dfd.transferFunction === KHR_DF_TRANSFER_SRGB ? DisplayP3ColorSpace : LinearDisplayP3ColorSpace; + + } else if ( dfd.colorPrimaries === KHR_DF_PRIMARIES_UNSPECIFIED ) { + + return NoColorSpace; + + } else { + + console.warn( `THREE.KTX2Loader: Unsupported color primaries, "${ dfd.colorPrimaries }"` ); + return NoColorSpace; + + } + +} + export { KTX2Loader }; diff --git a/examples/jsm/loaders/KTXLoader.js b/examples/jsm/loaders/KTXLoader.js index 4e4e1c11db6224..026f83e6a2a5bc 100644 --- a/examples/jsm/loaders/KTXLoader.js +++ b/examples/jsm/loaders/KTXLoader.js @@ -3,21 +3,42 @@ import { } from 'three'; /** - * for description see https://www.khronos.org/opengles/sdk/tools/KTX/ - * for file layout see https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/ + * A loader for the KTX texture compression format. * - * ported from https://github.com/BabylonJS/Babylon.js/blob/master/src/Misc/khronosTextureContainer.ts + * References: + * - [The KTX File Format and Tools]{@link https://www.khronos.org/opengles/sdk/tools/KTX/} + * - [Babylon.JS khronosTextureContainer.ts]{@link https://github.com/BabylonJS/Babylon.js/blob/master/src/Misc/khronosTextureContainer.ts} + * + * ```js + * const loader = new KTXLoader(); + * + * const map = loader.load( 'textures/compressed/lensflare_ASTC8x8.ktx' ) + * map.colorSpace = THREE.SRGBColorSpace; // only for color textures + * ``` + * + * @augments CompressedTextureLoader + * @three_import import { KTXLoader } from 'three/addons/loaders/KTXLoader.js'; */ - - class KTXLoader extends CompressedTextureLoader { + /** + * Constructs a new KTX loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); } + /** + * Parses the given KTX texture data. + * + * @param {ArrayBuffer} buffer - The raw texture data. + * @param {boolean} loadMipmaps - Whether to load mipmaps or not. + * @return {CompressedTextureLoader~TexData} An object representing the parsed texture data. + */ parse( buffer, loadMipmaps ) { const ktx = new KhronosTextureContainer( buffer, 1 ); @@ -35,7 +56,6 @@ class KTXLoader extends CompressedTextureLoader { } - const HEADER_LEN = 12 + ( 13 * 4 ); // identifier + header elements (not including key value meta-data pairs) // load types const COMPRESSED_2D = 0; // uses a gl.compressedTexImage2D() @@ -46,10 +66,11 @@ const COMPRESSED_2D = 0; // uses a gl.compressedTexImage2D() class KhronosTextureContainer { /** - * @param {ArrayBuffer} arrayBuffer- contents of the KTX container file - * @param {number} facesExpected- should be either 1 or 6, based whether a cube texture or or - * @param {boolean} threeDExpected- provision for indicating that data should be a 3D texture, not implemented - * @param {boolean} textureArrayExpected- provision for indicating that data should be a texture array, not implemented + * @private + * @param {ArrayBuffer} arrayBuffer - contents of the KTX container file + * @param {number} facesExpected - should be either 1 or 6, based whether a cube texture or or + * @param {boolean} threeDExpected - provision for indicating that data should be a 3D texture, not implemented + * @param {boolean} textureArrayExpected - provision for indicating that data should be a texture array, not implemented */ constructor( arrayBuffer, facesExpected /*, threeDExpected, textureArrayExpected */ ) { diff --git a/examples/jsm/loaders/LDrawLoader.js b/examples/jsm/loaders/LDrawLoader.js index f0fb5887f40ca8..0baaecac406383 100644 --- a/examples/jsm/loaders/LDrawLoader.js +++ b/examples/jsm/loaders/LDrawLoader.js @@ -10,10 +10,7 @@ import { Matrix4, Mesh, MeshStandardMaterial, - ShaderMaterial, SRGBColorSpace, - UniformsLib, - UniformsUtils, Vector3, Ray } from 'three'; @@ -45,134 +42,6 @@ const COLOR_SPACE_LDRAW = SRGBColorSpace; const _tempVec0 = new Vector3(); const _tempVec1 = new Vector3(); -class LDrawConditionalLineMaterial extends ShaderMaterial { - - constructor( parameters ) { - - super( { - - uniforms: UniformsUtils.merge( [ - UniformsLib.fog, - { - diffuse: { - value: new Color() - }, - opacity: { - value: 1.0 - } - } - ] ), - - vertexShader: /* glsl */` - attribute vec3 control0; - attribute vec3 control1; - attribute vec3 direction; - varying float discardFlag; - - #include - #include - #include - #include - #include - void main() { - #include - - vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 ); - gl_Position = projectionMatrix * mvPosition; - - // Transform the line segment ends and control points into camera clip space - vec4 c0 = projectionMatrix * modelViewMatrix * vec4( control0, 1.0 ); - vec4 c1 = projectionMatrix * modelViewMatrix * vec4( control1, 1.0 ); - vec4 p0 = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - vec4 p1 = projectionMatrix * modelViewMatrix * vec4( position + direction, 1.0 ); - - c0.xy /= c0.w; - c1.xy /= c1.w; - p0.xy /= p0.w; - p1.xy /= p1.w; - - // Get the direction of the segment and an orthogonal vector - vec2 dir = p1.xy - p0.xy; - vec2 norm = vec2( -dir.y, dir.x ); - - // Get control point directions from the line - vec2 c0dir = c0.xy - p1.xy; - vec2 c1dir = c1.xy - p1.xy; - - // If the vectors to the controls points are pointed in different directions away - // from the line segment then the line should not be drawn. - float d0 = dot( normalize( norm ), normalize( c0dir ) ); - float d1 = dot( normalize( norm ), normalize( c1dir ) ); - discardFlag = float( sign( d0 ) != sign( d1 ) ); - - #include - #include - #include - } - `, - - fragmentShader: /* glsl */` - uniform vec3 diffuse; - uniform float opacity; - varying float discardFlag; - - #include - #include - #include - #include - #include - void main() { - - if ( discardFlag > 0.5 ) discard; - - #include - vec3 outgoingLight = vec3( 0.0 ); - vec4 diffuseColor = vec4( diffuse, opacity ); - #include - #include - outgoingLight = diffuseColor.rgb; // simple shader - gl_FragColor = vec4( outgoingLight, diffuseColor.a ); - #include - #include - #include - #include - } - `, - - } ); - - Object.defineProperties( this, { - - opacity: { - get: function () { - - return this.uniforms.opacity.value; - - }, - - set: function ( value ) { - - this.uniforms.opacity.value = value; - - } - }, - - color: { - get: function () { - - return this.uniforms.diffuse.value; - - } - } - - } ); - - this.setValues( parameters ); - this.isLDrawConditionalLineMaterial = true; - - } - -} class ConditionalLineSegments extends LineSegments { @@ -767,7 +636,7 @@ class LDrawParsedCache { const text = await fileLoader.loadAsync( subobjectURL ); return text; - } catch { + } catch ( _ ) { continue; @@ -1520,19 +1389,19 @@ class LDrawPartsGeometryCache { const group = info.group; if ( info.faces.length > 0 ) { - group.add( createObject( info.faces, 3, false, info.totalFaces ) ); + group.add( createObject( this.loader, info.faces, 3, false, info.totalFaces ) ); } if ( info.lineSegments.length > 0 ) { - group.add( createObject( info.lineSegments, 2 ) ); + group.add( createObject( this.loader, info.lineSegments, 2 ) ); } if ( info.conditionalSegments.length > 0 ) { - group.add( createObject( info.conditionalSegments, 2, true ) ); + group.add( createObject( this.loader, info.conditionalSegments, 2, true ) ); } @@ -1640,7 +1509,7 @@ function sortByMaterial( a, b ) { } -function createObject( elements, elementSize, isConditionalSegments = false, totalElements = null ) { +function createObject( loader, elements, elementSize, isConditionalSegments = false, totalElements = null ) { // Creates a LineSegments (elementSize = 2) or a Mesh (elementSize = 3 ) // With per face / segment material, implemented with mesh groups and materials array @@ -1759,11 +1628,13 @@ function createObject( elements, elementSize, isConditionalSegments = false, tot if ( isConditionalSegments ) { - materials.push( material.userData.edgeMaterial.userData.conditionalEdgeMaterial ); + const edgeMaterial = loader.edgeMaterialCache.get( material ); + + materials.push( loader.conditionalEdgeMaterialCache.get( edgeMaterial ) ); } else { - materials.push( material.userData.edgeMaterial ); + materials.push( loader.edgeMaterialCache.get( material ) ); } @@ -1875,10 +1746,45 @@ function createObject( elements, elementSize, isConditionalSegments = false, tot } -// - +/** + * A loader for the LDraw format. + * + * [LDraw]{@link https://ldraw.org/} (LEGO Draw) is an [open format specification]{@link https://ldraw.org/article/218.html} + * for describing LEGO and other construction set 3D models. + * + * An LDraw asset (a text file usually with extension .ldr, .dat or .txt) can describe just a single construction + * piece, or an entire model. In the case of a model the LDraw file can reference other LDraw files, which are + * loaded from a library path set with `setPartsLibraryPath`. You usually download the LDraw official parts library, + * extract to a folder and point setPartsLibraryPath to it. + * + * Library parts will be loaded by trial and error in subfolders 'parts', 'p' and 'models'. These file accesses + * are not optimal for web environment, so a script tool has been made to pack an LDraw file with all its dependencies + * into a single file, which loads much faster. See section 'Packing LDraw models'. The LDrawLoader example loads + * several packed files. The official parts library is not included due to its large size. + * + * `LDrawLoader` supports the following extensions: + * - !COLOUR: Color and surface finish declarations. + * - BFC: Back Face Culling specification. + * - !CATEGORY: Model/part category declarations. + * - !KEYWORDS: Model/part keywords declarations. + * + * ```js + * const loader = new LDrawLoader(); + * loader.setConditionalLineMaterial( LDrawConditionalLineMaterial ); // the type of line material depends on the used renderer + * const object = await loader.loadAsync( 'models/ldraw/officialLibrary/models/car.ldr_Packed.mpd' ); + * scene.add( object ); + * ``` + * + * @augments Loader + * @three_import import { LDrawLoader } from 'three/addons/loaders/LDrawLoader.js'; + */ class LDrawLoader extends Loader { + /** + * Constructs a new LDraw loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); @@ -1886,6 +1792,8 @@ class LDrawLoader extends Loader { // Array of THREE.Material this.materials = []; this.materialLibrary = {}; + this.edgeMaterialCache = new WeakMap(); + this.conditionalEdgeMaterialCache = new WeakMap(); // This also allows to handle the embedded text files ("0 FILE" lines) this.partsCache = new LDrawPartsGeometryCache( this ); @@ -1893,27 +1801,32 @@ class LDrawLoader extends Loader { // This object is a map from file names to paths. It agilizes the paths search. If it is not set then files will be searched by trial and error. this.fileMap = {}; - // Initializes the materials library with default materials - this.setMaterials( [] ); - // If this flag is set to true the vertex normals will be smoothed. this.smoothNormals = true; // The path to load parts from the LDraw parts library from. this.partsLibraryPath = ''; + // this material type must be injected via setConditionalLineMaterial() + this.ConditionalLineMaterial = null; + // Material assigned to not available colors for meshes and edges - this.missingColorMaterial = new MeshStandardMaterial( { color: 0xFF00FF, roughness: 0.3, metalness: 0 } ); - this.missingColorMaterial.name = 'Missing material'; - this.missingEdgeColorMaterial = new LineBasicMaterial( { color: 0xFF00FF } ); - this.missingEdgeColorMaterial.name = 'Missing material - Edge'; - this.missingConditionalEdgeColorMaterial = new LDrawConditionalLineMaterial( { fog: true, color: 0xFF00FF } ); - this.missingConditionalEdgeColorMaterial.name = 'Missing material - Conditional Edge'; - this.missingColorMaterial.userData.edgeMaterial = this.missingEdgeColorMaterial; - this.missingEdgeColorMaterial.userData.conditionalEdgeMaterial = this.missingConditionalEdgeColorMaterial; + this.missingColorMaterial = new MeshStandardMaterial( { name: Loader.DEFAULT_MATERIAL_NAME, color: 0xFF00FF, roughness: 0.3, metalness: 0 } ); + this.missingEdgeColorMaterial = new LineBasicMaterial( { name: Loader.DEFAULT_MATERIAL_NAME, color: 0xFF00FF } ); + this.missingConditionalEdgeColorMaterial = null; + this.edgeMaterialCache.set( this.missingColorMaterial, this.missingEdgeColorMaterial ); + this.conditionalEdgeMaterialCache.set( this.missingEdgeColorMaterial, this.missingConditionalEdgeColorMaterial ); } + /** + * This method must be called prior to `load()` unless the model to load does not reference + * library parts (usually it will be a model with all its parts packed in a single file). + * + * @param {string} path - Path to library parts files to load referenced parts from. + * This is different from Loader.setPath, which indicates the path to load the main asset from. + * @return {LDrawLoader} A reference to this loader. + */ setPartsLibraryPath( path ) { this.partsLibraryPath = path; @@ -1921,6 +1834,33 @@ class LDrawLoader extends Loader { } + /** + * Sets the conditional line material type which depends on the used renderer. + * Use {@link LDrawConditionalLineMaterial} when using `WebGLRenderer` and + * {@link LDrawConditionalLineNodeMaterial} when using `WebGPURenderer`. + * + * @param {(LDrawConditionalLineMaterial.constructor|LDrawConditionalLineNodeMaterial.constructor)} type - The conditional line material type. + * @return {LDrawLoader} A reference to this loader. + */ + setConditionalLineMaterial( type ) { + + this.ConditionalLineMaterial = type; + this.missingConditionalEdgeColorMaterial = new this.ConditionalLineMaterial( { name: Loader.DEFAULT_MATERIAL_NAME, fog: true, color: 0xFF00FF } ); + return this; + + } + + /** + * This async method preloads materials from a single LDraw file. In the official + * parts library there is a special file which is loaded always the first (LDConfig.ldr) + * and contains all the standard color codes. This method is intended to be used with + * not packed files, for example in an editor where materials are preloaded and parts + * are loaded on demand. + * + * @async + * @param {string} url - Path of the LDraw materials asset. + * @return {Promise} A Promise that resolves when the preload has finished. + */ async preloadMaterials( url ) { const fileLoader = new FileLoader( this.manager ); @@ -1945,10 +1885,19 @@ class LDrawLoader extends Loader { } - this.setMaterials( materials ); + this.addMaterials( materials ); } + /** + * Starts loading from the given URL and passes the loaded LDraw asset + * to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(Group)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ load( url, onLoad, onProgress, onError ) { const fileLoader = new FileLoader( this.manager ); @@ -1957,8 +1906,11 @@ class LDrawLoader extends Loader { fileLoader.setWithCredentials( this.withCredentials ); fileLoader.load( url, text => { + // Initializes the materials library with default materials + this.addDefaultMaterials(); + this.partsCache - .parseModel( text, this.materialLibrary ) + .parseModel( text ) .then( group => { this.applyMaterialsToMesh( group, MAIN_COLOUR_CODE, this.materialLibrary, true ); @@ -1973,10 +1925,17 @@ class LDrawLoader extends Loader { } - parse( text, onLoad ) { + /** + * Parses the given LDraw data and returns the resulting group. + * + * @param {string} text - The raw VRML data as a string. + * @param {function(Group)} onLoad - Executed when the loading/parsing process has been finished. + * @param {onErrorCallback} onError - Executed when errors occur. + */ + parse( text, onLoad, onError ) { this.partsCache - .parseModel( text, this.materialLibrary ) + .parseModel( text ) .then( group => { this.applyMaterialsToMesh( group, MAIN_COLOUR_CODE, this.materialLibrary, true ); @@ -1984,20 +1943,66 @@ class LDrawLoader extends Loader { group.userData.fileName = ''; onLoad( group ); - } ); + } ) + .catch( onError ); } + /** + * Sets the loader's material library. This method clears existing + * material definitions. + * + * @param {Array} materials - The materials to set. + * @return {LDrawLoader} A reference to this loader. + */ setMaterials( materials ) { + this.clearMaterials(); + this.addMaterials( materials ); + + return this; + + } + + /** + * Clears the loader's material library. + * + * @return {LDrawLoader} A reference to this loader. + */ + clearMaterials() { + this.materialLibrary = {}; this.materials = []; + + return this; + + } + + /** + * Adds a list of materials to the loader's material library. + * + * @param {Array} materials - The materials to add. + * @return {LDrawLoader} A reference to this loader. + */ + addMaterials( materials ) { + for ( let i = 0, l = materials.length; i < l; i ++ ) { this.addMaterial( materials[ i ] ); } + return this; + + } + + /** + * Initializes the loader with default materials. + * + * @return {LDrawLoader} A reference to this loader. + */ + addDefaultMaterials() { + // Add default main triangle and line edge materials (used in pieces that can be colored with a main color) this.addMaterial( this.parseColorMetaDirective( new LineParser( 'Main_Colour CODE 16 VALUE #FF8080 EDGE #333333' ) ) ); this.addMaterial( this.parseColorMetaDirective( new LineParser( 'Edge_Colour CODE 24 VALUE #A0A0A0 EDGE #333333' ) ) ); @@ -2006,6 +2011,14 @@ class LDrawLoader extends Loader { } + /** + * Sets a map which maps referenced library filenames to new filenames. + * If a fileMap is not specified (the default), library parts will be accessed by trial and + * error in subfolders 'parts', 'p' and 'models'. + * + * @param {Object} fileMap - The file map to set. + * @return {LDrawLoader} A reference to this loader. + */ setFileMap( fileMap ) { this.fileMap = fileMap; @@ -2014,6 +2027,12 @@ class LDrawLoader extends Loader { } + /** + * Adds a single material to the loader's material library. + * + * @param {Material} material - The material to add. + * @return {LDrawLoader} A reference to this loader. + */ addMaterial( material ) { // Adds a material to the material library which is on top of the parse scopes stack. And also to the materials array @@ -2030,6 +2049,12 @@ class LDrawLoader extends Loader { } + /** + * Returns a material for the given color code. + * + * @param {string} colorCode - The color code. + * @return {?Material} The material. Returns `null` if no material has been found. + */ getMaterial( colorCode ) { if ( colorCode.startsWith( '0x2' ) ) { @@ -2107,7 +2132,7 @@ class LDrawLoader extends Loader { } else if ( finalMaterialPass ) { - // see if we can get the final material from from the "getMaterial" function which will attempt to + // see if we can get the final material from the "getMaterial" function which will attempt to // parse the "direct" colors material = loader.getMaterial( colorCode ); if ( material === null ) { @@ -2129,11 +2154,11 @@ class LDrawLoader extends Loader { if ( c.isLineSegments ) { - material = material.userData.edgeMaterial; + material = loader.edgeMaterialCache.get( material ); if ( c.isConditionalLine ) { - material = material.userData.conditionalEdgeMaterial; + material = loader.conditionalEdgeMaterialCache.get( material ); } @@ -2145,16 +2170,35 @@ class LDrawLoader extends Loader { } + /** + * Returns the Material for the main LDraw color. + * + * For an already loaded LDraw asset, returns the Material associated with the main color code. + * This method can be useful to modify the main material of a model or part that exposes it. + * + * The main color code is the standard way to color an LDraw part. It is '16' for triangles and + * '24' for edges. Usually a complete model will not expose the main color (that is, no part + * uses the code '16' at the top level, because they are assigned other specific colors) An LDraw + * part file on the other hand will expose the code '16' to be colored, and can have additional + * fixed colors. + * + * @return {?Material} The material. Returns `null` if no material has been found. + */ getMainMaterial() { return this.getMaterial( MAIN_COLOUR_CODE ); } + /** + * Returns the material for the edges main LDraw color. + * + * @return {?Material} The material. Returns `null` if no material has been found. + */ getMainEdgeMaterial() { const mat = this.getMaterial( MAIN_EDGE_COLOUR_CODE ); - return mat ? mat.userData.edgeMaterial : null; + return mat ? this.edgeMaterialCache.get( mat ) : null; } @@ -2239,7 +2283,7 @@ class LDrawLoader extends Loader { } // Get the edge material for this triangle material - edgeMaterial = edgeMaterial.userData.edgeMaterial; + edgeMaterial = this.edgeMaterialCache.get( edgeMaterial ); } @@ -2269,7 +2313,7 @@ class LDrawLoader extends Loader { if ( ! parseLuminance( lineParser.getToken() ) ) { - throw new Error( 'LDrawLoader: Invalid luminance value in material definition' + LineParser.getLineNumberString() + '.' ); + throw new Error( 'LDrawLoader: Invalid luminance value in material definition' + lineParser.getLineNumberString() + '.' ); } @@ -2382,8 +2426,14 @@ class LDrawLoader extends Loader { edgeMaterial.userData.code = code; edgeMaterial.name = name + ' - Edge'; + if ( this.ConditionalLineMaterial === null ) { + + throw new Error( 'THREE.LDrawLoader: ConditionalLineMaterial type must be specified via .setConditionalLineMaterial().' ); + + } + // This is the material used for conditional edges - edgeMaterial.userData.conditionalEdgeMaterial = new LDrawConditionalLineMaterial( { + const conditionalEdgeMaterial = new this.ConditionalLineMaterial( { fog: true, transparent: isTransparent, @@ -2392,15 +2442,17 @@ class LDrawLoader extends Loader { opacity: alpha, } ); - edgeMaterial.userData.conditionalEdgeMaterial.userData.code = code; - edgeMaterial.userData.conditionalEdgeMaterial.name = name + ' - Conditional Edge'; + conditionalEdgeMaterial.userData.code = code; + conditionalEdgeMaterial.name = name + ' - Conditional Edge'; + + this.conditionalEdgeMaterialCache.set( edgeMaterial, conditionalEdgeMaterial ); } material.userData.code = code; material.name = name; - material.userData.edgeMaterial = edgeMaterial; + this.edgeMaterialCache.set( material, edgeMaterial ); this.addMaterial( material ); diff --git a/examples/jsm/loaders/LUT3dlLoader.js b/examples/jsm/loaders/LUT3dlLoader.js index bddeb25f5703c7..c9515aa58ad465 100644 --- a/examples/jsm/loaders/LUT3dlLoader.js +++ b/examples/jsm/loaders/LUT3dlLoader.js @@ -1,18 +1,72 @@ -// http://download.autodesk.com/us/systemdocs/help/2011/lustre/index.html?url=./files/WSc4e151a45a3b785a24c3d9a411df9298473-7ffd.htm,topicNumber=d0e9492 -// https://community.foundry.com/discuss/topic/103636/format-spec-for-3dl?mode=Post&postID=895258 import { - Loader, - FileLoader, - DataTexture, + ClampToEdgeWrapping, Data3DTexture, + FileLoader, + LinearFilter, + Loader, RGBAFormat, UnsignedByteType, - ClampToEdgeWrapping, - LinearFilter, } from 'three'; +/** + * A loader for the 3DL LUT format. + * + * References: + * - [3D LUTs]{@link http://download.autodesk.com/us/systemdocs/help/2011/lustre/index.html?url=./files/WSc4e151a45a3b785a24c3d9a411df9298473-7ffd.htm,topicNumber=d0e9492} + * - [Format Spec for .3dl]{@link https://community.foundry.com/discuss/topic/103636/format-spec-for-3dl?mode=Post&postID=895258} + * + * ```js + * const loader = new LUT3dlLoader(); + * const map = loader.loadAsync( 'luts/Presetpro-Cinematic.3dl' ); + * ``` + * + * @augments Loader + * @three_import import { LUT3dlLoader } from 'three/addons/loaders/LUT3dlLoader.js'; + */ export class LUT3dlLoader extends Loader { + /** + * Constructs a new 3DL LUT loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ + constructor( manager ) { + + super( manager ); + + /** + * The texture type. + * + * @type {(UnsignedByteType|FloatType)} + * @default UnsignedByteType + */ + this.type = UnsignedByteType; + + } + + /** + * Sets the texture type. + * + * @param {(UnsignedByteType|FloatType)} type - The texture type to set. + * @return {LUT3dlLoader} A reference to this loader. + */ + setType( type ) { + + this.type = type; + + return this; + + } + + /** + * Starts loading from the given URL and passes the loaded 3DL LUT asset + * to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function({size:number,texture3D:Data3DTexture})} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ load( url, onLoad, onProgress, onError ) { const loader = new FileLoader( this.manager ); @@ -44,86 +98,87 @@ export class LUT3dlLoader extends Loader { } - parse( str ) { + /** + * Parses the given 3DL LUT data and returns the resulting 3D data texture. + * + * @param {string} input - The raw 3DL LUT data as a string. + * @return {{size:number,texture3D:Data3DTexture}} The parsed 3DL LUT. + */ + parse( input ) { + + const regExpGridInfo = /^[\d ]+$/m; + const regExpDataPoints = /^([\d.e+-]+) +([\d.e+-]+) +([\d.e+-]+) *$/gm; + + // The first line describes the positions of values on the LUT grid. + let result = regExpGridInfo.exec( input ); + + if ( result === null ) { - // remove empty lines and comment lints - str = str - .replace( /^#.*?(\n|\r)/gm, '' ) - .replace( /^\s*?(\n|\r)/gm, '' ) - .trim(); + throw new Error( 'LUT3dlLoader: Missing grid information' ); - const lines = str.split( /[\n\r]+/g ); + } - // first line is the positions on the grid that are provided by the LUT - const gridLines = lines[ 0 ].trim().split( /\s+/g ).map( e => parseFloat( e ) ); + const gridLines = result[ 0 ].trim().split( /\s+/g ).map( Number ); const gridStep = gridLines[ 1 ] - gridLines[ 0 ]; const size = gridLines.length; + const sizeSq = size ** 2; - for ( let i = 1, l = gridLines.length; i < l; i ++ ) { + for ( let i = 1, l = gridLines.length; i < l; ++ i ) { if ( gridStep !== ( gridLines[ i ] - gridLines[ i - 1 ] ) ) { - throw new Error( 'LUT3dlLoader: Inconsistent grid size not supported.' ); + throw new Error( 'LUT3dlLoader: Inconsistent grid size' ); } } - const dataArray = new Array( size * size * size * 4 ); + const dataFloat = new Float32Array( size ** 3 * 4 ); + let maxValue = 0.0; let index = 0; - let maxOutputValue = 0.0; - for ( let i = 1, l = lines.length; i < l; i ++ ) { - const line = lines[ i ].trim(); - const split = line.split( /\s/g ); + while ( ( result = regExpDataPoints.exec( input ) ) !== null ) { + + const r = Number( result[ 1 ] ); + const g = Number( result[ 2 ] ); + const b = Number( result[ 3 ] ); - const r = parseFloat( split[ 0 ] ); - const g = parseFloat( split[ 1 ] ); - const b = parseFloat( split[ 2 ] ); - maxOutputValue = Math.max( maxOutputValue, r, g, b ); + maxValue = Math.max( maxValue, r, g, b ); const bLayer = index % size; const gLayer = Math.floor( index / size ) % size; - const rLayer = Math.floor( index / ( size * size ) ) % size; + const rLayer = Math.floor( index / ( sizeSq ) ) % size; - // b grows first, then g, then r - const pixelIndex = bLayer * size * size + gLayer * size + rLayer; - dataArray[ 4 * pixelIndex + 0 ] = r; - dataArray[ 4 * pixelIndex + 1 ] = g; - dataArray[ 4 * pixelIndex + 2 ] = b; - dataArray[ 4 * pixelIndex + 3 ] = 1.0; - index += 1; + // b grows first, then g, then r. + const d4 = ( bLayer * sizeSq + gLayer * size + rLayer ) * 4; + dataFloat[ d4 + 0 ] = r; + dataFloat[ d4 + 1 ] = g; + dataFloat[ d4 + 2 ] = b; + + ++ index; } - // Find the apparent bit depth of the stored RGB values and map the - // values to [ 0, 255 ]. - const bits = Math.ceil( Math.log2( maxOutputValue ) ); - const maxBitValue = Math.pow( 2.0, bits ); - for ( let i = 0, l = dataArray.length; i < l; i += 4 ) { + // Determine the bit depth to scale the values to [0.0, 1.0]. + const bits = Math.ceil( Math.log2( maxValue ) ); + const maxBitValue = Math.pow( 2, bits ); - const r = dataArray[ i + 0 ]; - const g = dataArray[ i + 1 ]; - const b = dataArray[ i + 2 ]; - dataArray[ i + 0 ] = 255 * r / maxBitValue; // r - dataArray[ i + 1 ] = 255 * g / maxBitValue; // g - dataArray[ i + 2 ] = 255 * b / maxBitValue; // b + const data = this.type === UnsignedByteType ? new Uint8Array( dataFloat.length ) : dataFloat; + const scale = this.type === UnsignedByteType ? 255 : 1; - } + for ( let i = 0, l = data.length; i < l; i += 4 ) { - const data = new Uint8Array( dataArray ); - const texture = new DataTexture(); - texture.image.data = data; - texture.image.width = size; - texture.image.height = size * size; - texture.format = RGBAFormat; - texture.type = UnsignedByteType; - texture.magFilter = LinearFilter; - texture.minFilter = LinearFilter; - texture.wrapS = ClampToEdgeWrapping; - texture.wrapT = ClampToEdgeWrapping; - texture.generateMipmaps = false; - texture.needsUpdate = true; + const i1 = i + 1; + const i2 = i + 2; + const i3 = i + 3; + + // Note: data is dataFloat when type is FloatType. + data[ i ] = dataFloat[ i ] / maxBitValue * scale; + data[ i1 ] = dataFloat[ i1 ] / maxBitValue * scale; + data[ i2 ] = dataFloat[ i2 ] / maxBitValue * scale; + data[ i3 ] = scale; + + } const texture3D = new Data3DTexture(); texture3D.image.data = data; @@ -131,7 +186,7 @@ export class LUT3dlLoader extends Loader { texture3D.image.height = size; texture3D.image.depth = size; texture3D.format = RGBAFormat; - texture3D.type = UnsignedByteType; + texture3D.type = this.type; texture3D.magFilter = LinearFilter; texture3D.minFilter = LinearFilter; texture3D.wrapS = ClampToEdgeWrapping; @@ -142,7 +197,6 @@ export class LUT3dlLoader extends Loader { return { size, - texture, texture3D, }; diff --git a/examples/jsm/loaders/LUTCubeLoader.js b/examples/jsm/loaders/LUTCubeLoader.js index 4a3dac767f9dd0..509291bd173ea8 100644 --- a/examples/jsm/loaders/LUTCubeLoader.js +++ b/examples/jsm/loaders/LUTCubeLoader.js @@ -1,18 +1,71 @@ -// https://wwwimages2.adobe.com/content/dam/acom/en/products/speedgrade/cc/pdfs/cube-lut-specification-1.0.pdf - import { - Loader, - FileLoader, - Vector3, - DataTexture, - Data3DTexture, - UnsignedByteType, ClampToEdgeWrapping, + Data3DTexture, + FileLoader, LinearFilter, + Loader, + UnsignedByteType, + Vector3, } from 'three'; +/** + * A loader for the Cube LUT format. + * + * References: + * - [Cube LUT Specification]{@link https://web.archive.org/web/20220220033515/https://wwwimages2.adobe.com/content/dam/acom/en/products/speedgrade/cc/pdfs/cube-lut-specification-1.0.pdf} + * + * ```js + * const loader = new LUTCubeLoader(); + * const map = loader.loadAsync( 'luts/Bourbon 64.CUBE' ); + * ``` + * + * @augments Loader + * @three_import import { LUTCubeLoader } from 'three/addons/loaders/LUTCubeLoader.js'; + */ export class LUTCubeLoader extends Loader { + /** + * Constructs a new Cube LUT loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ + constructor( manager ) { + + super( manager ); + + /** + * The texture type. + * + * @type {(UnsignedByteType|FloatType)} + * @default UnsignedByteType + */ + this.type = UnsignedByteType; + + } + + /** + * Sets the texture type. + * + * @param {(UnsignedByteType|FloatType)} type - The texture type to set. + * @return {LUTCubeLoader} A reference to this loader. + */ + setType( type ) { + + this.type = type; + + return this; + + } + + /** + * Starts loading from the given URL and passes the loaded Cube LUT asset + * to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function({title:string,size:number,domainMin:Vector3,domainMax:Vector3,texture3D:Data3DTexture})} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ load( url, onLoad, onProgress, onError ) { const loader = new FileLoader( this.manager ); @@ -44,93 +97,78 @@ export class LUTCubeLoader extends Loader { } - parse( str ) { + /** + * Parses the given Cube LUT data and returns the resulting 3D data texture. + * + * @param {string} input - The raw Cube LUT data as a string. + * @return {{title:string,size:number,domainMin:Vector3,domainMax:Vector3,texture3D:Data3DTexture}} The parsed Cube LUT. + */ + parse( input ) { + + const regExpTitle = /TITLE +"([^"]*)"/; + const regExpSize = /LUT_3D_SIZE +(\d+)/; + const regExpDomainMin = /DOMAIN_MIN +([\d.]+) +([\d.]+) +([\d.]+)/; + const regExpDomainMax = /DOMAIN_MAX +([\d.]+) +([\d.]+) +([\d.]+)/; + const regExpDataPoints = /^([\d.e+-]+) +([\d.e+-]+) +([\d.e+-]+) *$/gm; - // Remove empty lines and comments - str = str - .replace( /^#.*?(\n|\r)/gm, '' ) - .replace( /^\s*?(\n|\r)/gm, '' ) - .trim(); + let result = regExpTitle.exec( input ); + const title = ( result !== null ) ? result[ 1 ] : null; + + result = regExpSize.exec( input ); + + if ( result === null ) { + + throw new Error( 'LUTCubeLoader: Missing LUT_3D_SIZE information' ); + + } + + const size = Number( result[ 1 ] ); + const length = size ** 3 * 4; + const data = this.type === UnsignedByteType ? new Uint8Array( length ) : new Float32Array( length ); - let title = null; - let size = null; const domainMin = new Vector3( 0, 0, 0 ); const domainMax = new Vector3( 1, 1, 1 ); - const lines = str.split( /[\n\r]+/g ); - let data = null; - - let currIndex = 0; - for ( let i = 0, l = lines.length; i < l; i ++ ) { - - const line = lines[ i ].trim(); - const split = line.split( /\s/g ); - - switch ( split[ 0 ] ) { - - case 'TITLE': - title = line.substring( 7, line.length - 1 ); - break; - case 'LUT_3D_SIZE': - // TODO: A .CUBE LUT file specifies floating point values and could be represented with - // more precision than can be captured with Uint8Array. - const sizeToken = split[ 1 ]; - size = parseFloat( sizeToken ); - data = new Uint8Array( size * size * size * 4 ); - break; - case 'DOMAIN_MIN': - domainMin.x = parseFloat( split[ 1 ] ); - domainMin.y = parseFloat( split[ 2 ] ); - domainMin.z = parseFloat( split[ 3 ] ); - break; - case 'DOMAIN_MAX': - domainMax.x = parseFloat( split[ 1 ] ); - domainMax.y = parseFloat( split[ 2 ] ); - domainMax.z = parseFloat( split[ 3 ] ); - break; - default: - const r = parseFloat( split[ 0 ] ); - const g = parseFloat( split[ 1 ] ); - const b = parseFloat( split[ 2 ] ); - - if ( - r > 1.0 || r < 0.0 || - g > 1.0 || g < 0.0 || - b > 1.0 || b < 0.0 - ) { - - throw new Error( 'LUTCubeLoader : Non normalized values not supported.' ); - - } - - data[ currIndex + 0 ] = r * 255; - data[ currIndex + 1 ] = g * 255; - data[ currIndex + 2 ] = b * 255; - data[ currIndex + 3 ] = 255; - currIndex += 4; + result = regExpDomainMin.exec( input ); - } + if ( result !== null ) { + + domainMin.set( Number( result[ 1 ] ), Number( result[ 2 ] ), Number( result[ 3 ] ) ); } - const texture = new DataTexture(); - texture.image.data = data; - texture.image.width = size; - texture.image.height = size * size; - texture.type = UnsignedByteType; - texture.magFilter = LinearFilter; - texture.minFilter = LinearFilter; - texture.wrapS = ClampToEdgeWrapping; - texture.wrapT = ClampToEdgeWrapping; - texture.generateMipmaps = false; - texture.needsUpdate = true; + result = regExpDomainMax.exec( input ); + + if ( result !== null ) { + + domainMax.set( Number( result[ 1 ] ), Number( result[ 2 ] ), Number( result[ 3 ] ) ); + + } + + if ( domainMin.x > domainMax.x || domainMin.y > domainMax.y || domainMin.z > domainMax.z ) { + + throw new Error( 'LUTCubeLoader: Invalid input domain' ); + + } + + const scale = this.type === UnsignedByteType ? 255 : 1; + let i = 0; + + while ( ( result = regExpDataPoints.exec( input ) ) !== null ) { + + data[ i ++ ] = Number( result[ 1 ] ) * scale; + data[ i ++ ] = Number( result[ 2 ] ) * scale; + data[ i ++ ] = Number( result[ 3 ] ) * scale; + data[ i ++ ] = scale; + + } const texture3D = new Data3DTexture(); texture3D.image.data = data; texture3D.image.width = size; texture3D.image.height = size; texture3D.image.depth = size; - texture3D.type = UnsignedByteType; + texture3D.type = this.type; texture3D.magFilter = LinearFilter; texture3D.minFilter = LinearFilter; texture3D.wrapS = ClampToEdgeWrapping; @@ -144,7 +182,6 @@ export class LUTCubeLoader extends Loader { size, domainMin, domainMax, - texture, texture3D, }; diff --git a/examples/jsm/loaders/LUTImageLoader.js b/examples/jsm/loaders/LUTImageLoader.js new file mode 100644 index 00000000000000..e45b6dbbcea206 --- /dev/null +++ b/examples/jsm/loaders/LUTImageLoader.js @@ -0,0 +1,190 @@ +import { + Loader, + TextureLoader, + Data3DTexture, + RGBAFormat, + UnsignedByteType, + ClampToEdgeWrapping, + LinearFilter, +} from 'three'; + +/** + * A loader for loading LUT images. + * + * ```js + * const loader = new LUTImageLoader(); + * const map = loader.loadAsync( 'luts/NeutralLUT.png' ); + * ``` + * + * @augments Loader + * @three_import import { LUTImageLoader } from 'three/addons/loaders/LUTImageLoader.js'; + */ +export class LUTImageLoader extends Loader { + + /** + * Constructs a new LUT loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ + constructor( manager ) { + + super( manager ); + + /** + * Whether to vertically flip the LUT or not. + * + * Depending on the LUT's origin, the texture has green at the bottom (e.g. for Unreal) + * or green at the top (e.g. for Unity URP Color Lookup). If you're using lut image strips + * from a Unity pipeline, then set this property to `true`. + * + * @type {boolean} + * @default false + */ + this.flip = false; + + } + + /** + * Starts loading from the given URL and passes the loaded LUT + * to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function({size:number,texture3D:Data3DTexture})} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ + load( url, onLoad, onProgress, onError ) { + + const loader = new TextureLoader( this.manager ); + + loader.setCrossOrigin( this.crossOrigin ); + + loader.setPath( this.path ); + loader.load( url, texture => { + + try { + + let imageData; + + if ( texture.image.width < texture.image.height ) { + + imageData = this._getImageData( texture ); + + } else { + + imageData = this._horz2Vert( texture ); + + } + + onLoad( this.parse( imageData.data, Math.min( texture.image.width, texture.image.height ) ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + this.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + /** + * Parses the given LUT data and returns the resulting 3D data texture. + * + * @param {Uint8ClampedArray} dataArray - The raw LUT data. + * @param {number} size - The LUT size. + * @return {{size:number,texture3D:Data3DTexture}} An object representing the parsed LUT. + */ + parse( dataArray, size ) { + + const data = new Uint8Array( dataArray ); + + const texture3D = new Data3DTexture(); + texture3D.image.data = data; + texture3D.image.width = size; + texture3D.image.height = size; + texture3D.image.depth = size; + texture3D.format = RGBAFormat; + texture3D.type = UnsignedByteType; + texture3D.magFilter = LinearFilter; + texture3D.minFilter = LinearFilter; + texture3D.wrapS = ClampToEdgeWrapping; + texture3D.wrapT = ClampToEdgeWrapping; + texture3D.wrapR = ClampToEdgeWrapping; + texture3D.generateMipmaps = false; + texture3D.needsUpdate = true; + + return { + size, + texture3D, + }; + + } + + // internal + + _getImageData( texture ) { + + const width = texture.image.width; + const height = texture.image.height; + + const canvas = document.createElement( 'canvas' ); + canvas.width = width; + canvas.height = height; + + const context = canvas.getContext( '2d' ); + + if ( this.flip === true ) { + + context.scale( 1, - 1 ); + context.translate( 0, - height ); + + } + + context.drawImage( texture.image, 0, 0 ); + + return context.getImageData( 0, 0, width, height ); + + } + + _horz2Vert( texture ) { + + const width = texture.image.height; + const height = texture.image.width; + + const canvas = document.createElement( 'canvas' ); + canvas.width = width; + canvas.height = height; + + const context = canvas.getContext( '2d' ); + + if ( this.flip === true ) { + + context.scale( 1, - 1 ); + context.translate( 0, - height ); + + } + + for ( let i = 0; i < width; i ++ ) { + + const sy = i * width; + const dy = ( this.flip ) ? height - i * width : i * width; + context.drawImage( texture.image, sy, 0, width, width, 0, dy, width, width ); + + } + + return context.getImageData( 0, 0, width, height ); + + } + +} diff --git a/examples/jsm/loaders/LWOLoader.js b/examples/jsm/loaders/LWOLoader.js index 80c3dc3d24a2be..87ed6419794d7b 100644 --- a/examples/jsm/loaders/LWOLoader.js +++ b/examples/jsm/loaders/LWOLoader.js @@ -1,16 +1,3 @@ -/** - * @version 1.1.1 - * - * @desc Load files in LWO3 and LWO2 format on Three.js - * - * LWO3 format specification: - * https://static.lightwave3d.com/sdk/2019/html/filefmts/lwo3.html - * - * LWO2 format specification: - * https://static.lightwave3d.com/sdk/2019/html/filefmts/lwo2.html - * - **/ - import { AddOperation, BackSide, @@ -43,16 +30,48 @@ import { IFFParser } from './lwo/IFFParser.js'; let _lwoTree; +/** + * A loader for the LWO format. + * + * LWO3 and LWO2 formats are supported. + * + * References: + * - [LWO3 format specification]{@link https://static.lightwave3d.com/sdk/2019/html/filefmts/lwo3.html} + * - [LWO2 format specification]{@link https://static.lightwave3d.com/sdk/2019/html/filefmts/lwo2.html} + * + * ```js + * const loader = new LWOLoader(); + * const lwoData = await loader.loadAsync( 'models/lwo/Objects/LWO3/Demo.lwo' ); + * + * const mesh = object.meshes[ 0 ]; + * scene.add( mesh ); + * ``` + * + * @augments Loader + * @three_import import { LWOLoader } from 'three/addons/loaders/LWOLoader.js'; + */ class LWOLoader extends Loader { - constructor( manager, parameters = {} ) { + /** + * Constructs a new LWO loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ + constructor( manager ) { super( manager ); - this.resourcePath = ( parameters.resourcePath !== undefined ) ? parameters.resourcePath : ''; - } + /** + * Starts loading from the given URL and passes the loaded LWO asset + * to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function({meshes:Array,materials:Array})} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ load( url, onLoad, onProgress, onError ) { const scope = this; @@ -96,6 +115,14 @@ class LWOLoader extends Loader { } + /** + * Parses the given LWO data and returns the resulting meshes and materials. + * + * @param {ArrayBuffer} iffBuffer - The raw LWO data as an array buffer. + * @param {string} path - The URL base path. + * @param {string} modelName - The model name. + * @return {{meshes:Array,materials:Array}} An object holding the parse meshes and materials. + */ parse( iffBuffer, path, modelName ) { _lwoTree = new IFFParser().parse( iffBuffer ); @@ -312,7 +339,7 @@ class MaterialParser { const maps = this.parseTextureNodes( connections.maps ); - this.parseAttributeImageMaps( connections.attributes, textures, maps, materialData.maps ); + this.parseAttributeImageMaps( connections.attributes, textures, maps ); const attributes = this.parseAttributes( connections.attributes, maps ); @@ -496,7 +523,7 @@ class MaterialParser { const mapData = attribute.maps[ 0 ]; - const path = this.getTexturePathByIndex( mapData.imageIndex, textures ); + const path = this.getTexturePathByIndex( mapData.imageIndex ); if ( ! path ) return; const texture = this.loadTexture( path ); @@ -783,8 +810,8 @@ class GeometryParser { geometry.computeVertexNormals(); - this.parseUVs( geometry, layer, indices ); - this.parseMorphTargets( geometry, layer, indices ); + this.parseUVs( geometry, layer ); + this.parseMorphTargets( geometry, layer ); // TODO: z may need to be reversed to account for coordinate system change geometry.translate( - layer.pivot[ 0 ], - layer.pivot[ 1 ], - layer.pivot[ 2 ] ); diff --git a/examples/jsm/loaders/LogLuvLoader.js b/examples/jsm/loaders/LogLuvLoader.js deleted file mode 100644 index 297ea0a47ce1b4..00000000000000 --- a/examples/jsm/loaders/LogLuvLoader.js +++ /dev/null @@ -1,606 +0,0 @@ -import { - DataUtils, - DataTextureLoader, - FloatType, - HalfFloatType, - RGBAFormat -} from 'three'; - -class LogLuvLoader extends DataTextureLoader { - - constructor( manager ) { - - super( manager ); - - this.type = HalfFloatType; - - } - - parse( buffer ) { - - const ifds = UTIF.decode( buffer ); - UTIF.decodeImage( buffer, ifds[ 0 ] ); - const rgba = UTIF.toRGBA( ifds[ 0 ], this.type ); - - return { - width: ifds[ 0 ].width, - height: ifds[ 0 ].height, - data: rgba, - format: RGBAFormat, - type: this.type, - flipY: true - }; - - } - - setDataType( value ) { - - this.type = value; - return this; - - } - -} - -// from https://github.com/photopea/UTIF.js (MIT License) - -const UTIF = {}; - -UTIF.decode = function ( buff, prm ) { - - if ( prm == null ) prm = { parseMN: true, debug: false }; // read MakerNote, debug - var data = new Uint8Array( buff ), offset = 0; - - var id = UTIF._binBE.readASCII( data, offset, 2 ); offset += 2; - var bin = id == 'II' ? UTIF._binLE : UTIF._binBE; - bin.readUshort( data, offset ); offset += 2; - - var ifdo = bin.readUint( data, offset ); - var ifds = []; - while ( true ) { - - var cnt = bin.readUshort( data, ifdo ), typ = bin.readUshort( data, ifdo + 4 ); if ( cnt != 0 ) if ( typ < 1 || 13 < typ ) { - - console.log( 'error in TIFF' ); break; - - } - - - UTIF._readIFD( bin, data, ifdo, ifds, 0, prm ); - - ifdo = bin.readUint( data, ifdo + 2 + cnt * 12 ); - if ( ifdo == 0 ) break; - - } - - return ifds; - -}; - -UTIF.decodeImage = function ( buff, img, ifds ) { - - if ( img.data ) return; - var data = new Uint8Array( buff ); - var id = UTIF._binBE.readASCII( data, 0, 2 ); - - if ( img[ 't256' ] == null ) return; // No width => probably not an image - img.isLE = id == 'II'; - img.width = img[ 't256' ][ 0 ]; //delete img["t256"]; - img.height = img[ 't257' ][ 0 ]; //delete img["t257"]; - - var cmpr = img[ 't259' ] ? img[ 't259' ][ 0 ] : 1; //delete img["t259"]; - var fo = img[ 't266' ] ? img[ 't266' ][ 0 ] : 1; //delete img["t266"]; - if ( img[ 't284' ] && img[ 't284' ][ 0 ] == 2 ) console.log( 'PlanarConfiguration 2 should not be used!' ); - if ( cmpr == 7 && img[ 't258' ] && img[ 't258' ].length > 3 ) img[ 't258' ] = img[ 't258' ].slice( 0, 3 ); - - var bipp; // bits per pixel - if ( img[ 't258' ] ) bipp = Math.min( 32, img[ 't258' ][ 0 ] ) * img[ 't258' ].length; - else bipp = ( img[ 't277' ] ? img[ 't277' ][ 0 ] : 1 ); - // Some .NEF files have t258==14, even though they use 16 bits per pixel - if ( cmpr == 1 && img[ 't279' ] != null && img[ 't278' ] && img[ 't262' ][ 0 ] == 32803 ) { - - bipp = Math.round( ( img[ 't279' ][ 0 ] * 8 ) / ( img.width * img[ 't278' ][ 0 ] ) ); - - } - - var bipl = Math.ceil( img.width * bipp / 8 ) * 8; - var soff = img[ 't273' ]; if ( soff == null ) soff = img[ 't324' ]; - var bcnt = img[ 't279' ]; if ( cmpr == 1 && soff.length == 1 ) bcnt = [ img.height * ( bipl >>> 3 ) ]; if ( bcnt == null ) bcnt = img[ 't325' ]; - //bcnt[0] = Math.min(bcnt[0], data.length); // Hasselblad, "RAW_HASSELBLAD_H3D39II.3FR" - var bytes = new Uint8Array( img.height * ( bipl >>> 3 ) ), bilen = 0; - - if ( img[ 't322' ] != null ) { - - var tw = img[ 't322' ][ 0 ], th = img[ 't323' ][ 0 ]; - var tx = Math.floor( ( img.width + tw - 1 ) / tw ); - var ty = Math.floor( ( img.height + th - 1 ) / th ); - var tbuff = new Uint8Array( Math.ceil( tw * th * bipp / 8 ) | 0 ); - for ( var y = 0; y < ty; y ++ ) - for ( var x = 0; x < tx; x ++ ) { - - var i = y * tx + x; for ( var j = 0; j < tbuff.length; j ++ ) tbuff[ j ] = 0; - UTIF.decode._decompress( img, ifds, data, soff[ i ], bcnt[ i ], cmpr, tbuff, 0, fo ); - // Might be required for 7 too. Need to check - if ( cmpr == 6 ) bytes = tbuff; - else UTIF._copyTile( tbuff, Math.ceil( tw * bipp / 8 ) | 0, th, bytes, Math.ceil( img.width * bipp / 8 ) | 0, img.height, Math.ceil( x * tw * bipp / 8 ) | 0, y * th ); - - } - - bilen = bytes.length * 8; - - } else { - - var rps = img[ 't278' ] ? img[ 't278' ][ 0 ] : img.height; rps = Math.min( rps, img.height ); - for ( var i = 0; i < soff.length; i ++ ) { - - UTIF.decode._decompress( img, ifds, data, soff[ i ], bcnt[ i ], cmpr, bytes, Math.ceil( bilen / 8 ) | 0, fo ); - bilen += bipl * rps; - - } - - bilen = Math.min( bilen, bytes.length * 8 ); - - } - - img.data = new Uint8Array( bytes.buffer, 0, Math.ceil( bilen / 8 ) | 0 ); - -}; - -UTIF.decode._decompress = function ( img, ifds, data, off, len, cmpr, tgt, toff ) { - - //console.log("compression", cmpr); - //var time = Date.now(); - if ( cmpr == 34676 ) UTIF.decode._decodeLogLuv32( img, data, off, len, tgt, toff ); - else console.log( 'Unsupported compression', cmpr ); - - //console.log(Date.now()-time); - - var bps = ( img[ 't258' ] ? Math.min( 32, img[ 't258' ][ 0 ] ) : 1 ); - var noc = ( img[ 't277' ] ? img[ 't277' ][ 0 ] : 1 ), bpp = ( bps * noc ) >>> 3, h = ( img[ 't278' ] ? img[ 't278' ][ 0 ] : img.height ), bpl = Math.ceil( bps * noc * img.width / 8 ); - - // convert to Little Endian /* - if ( bps == 16 && ! img.isLE && img[ 't33422' ] == null ) // not DNG - for ( var y = 0; y < h; y ++ ) { - - //console.log("fixing endianity"); - var roff = toff + y * bpl; - for ( var x = 1; x < bpl; x += 2 ) { - - var t = tgt[ roff + x ]; tgt[ roff + x ] = tgt[ roff + x - 1 ]; tgt[ roff + x - 1 ] = t; - - } - - } //*/ - - if ( img[ 't317' ] && img[ 't317' ][ 0 ] == 2 ) { - - for ( var y = 0; y < h; y ++ ) { - - var ntoff = toff + y * bpl; - if ( bps == 16 ) for ( var j = bpp; j < bpl; j += 2 ) { - - var nv = ( ( tgt[ ntoff + j + 1 ] << 8 ) | tgt[ ntoff + j ] ) + ( ( tgt[ ntoff + j - bpp + 1 ] << 8 ) | tgt[ ntoff + j - bpp ] ); - tgt[ ntoff + j ] = nv & 255; tgt[ ntoff + j + 1 ] = ( nv >>> 8 ) & 255; - - } - else if ( noc == 3 ) for ( var j = 3; j < bpl; j += 3 ) { - - tgt[ ntoff + j ] = ( tgt[ ntoff + j ] + tgt[ ntoff + j - 3 ] ) & 255; - tgt[ ntoff + j + 1 ] = ( tgt[ ntoff + j + 1 ] + tgt[ ntoff + j - 2 ] ) & 255; - tgt[ ntoff + j + 2 ] = ( tgt[ ntoff + j + 2 ] + tgt[ ntoff + j - 1 ] ) & 255; - - } - else for ( var j = bpp; j < bpl; j ++ ) tgt[ ntoff + j ] = ( tgt[ ntoff + j ] + tgt[ ntoff + j - bpp ] ) & 255; - - } - - } - -}; - -UTIF.decode._decodeLogLuv32 = function ( img, data, off, len, tgt, toff ) { - - var w = img.width, qw = w * 4; - var io = 0, out = new Uint8Array( qw ); - - while ( io < len ) { - - var oo = 0; - while ( oo < qw ) { - - var c = data[ off + io ]; io ++; - if ( c < 128 ) { - - for ( var j = 0; j < c; j ++ ) out[ oo + j ] = data[ off + io + j ]; oo += c; io += c; - - } else { - - c = c - 126; for ( var j = 0; j < c; j ++ ) out[ oo + j ] = data[ off + io ]; oo += c; io ++; - - } - - } - - for ( var x = 0; x < w; x ++ ) { - - tgt[ toff + 0 ] = out[ x ]; - tgt[ toff + 1 ] = out[ x + w ]; - tgt[ toff + 2 ] = out[ x + w * 2 ]; - tgt[ toff + 4 ] = out[ x + w * 3 ]; - toff += 6; - - } - - } - -}; - -UTIF.tags = {}; -//UTIF.ttypes = { 256:3,257:3,258:3, 259:3, 262:3, 273:4, 274:3, 277:3,278:4,279:4, 282:5, 283:5, 284:3, 286:5,287:5, 296:3, 305:2, 306:2, 338:3, 513:4, 514:4, 34665:4 }; -// start at tag 250 -UTIF._types = function () { - - var main = new Array( 250 ); main.fill( 0 ); - main = main.concat( [ 0, 0, 0, 0, 4, 3, 3, 3, 3, 3, 0, 0, 3, 0, 0, 0, 3, 0, 0, 2, 2, 2, 2, 4, 3, 0, 0, 3, 4, 4, 3, 3, 5, 5, 3, 2, 5, 5, 0, 0, 0, 0, 4, 4, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 3, 5, 5, 3, 0, 3, 3, 4, 4, 4, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ); - var rest = { 33432: 2, 33434: 5, 33437: 5, 34665: 4, 34850: 3, 34853: 4, 34855: 3, 34864: 3, 34866: 4, 36864: 7, 36867: 2, 36868: 2, 37121: 7, 37377: 10, 37378: 5, 37380: 10, 37381: 5, 37383: 3, 37384: 3, 37385: 3, 37386: 5, 37510: 7, 37520: 2, 37521: 2, 37522: 2, 40960: 7, 40961: 3, 40962: 4, 40963: 4, 40965: 4, 41486: 5, 41487: 5, 41488: 3, 41985: 3, 41986: 3, 41987: 3, 41988: 5, 41989: 3, 41990: 3, 41993: 3, 41994: 3, 41995: 7, 41996: 3, 42032: 2, 42033: 2, 42034: 5, 42036: 2, 42037: 2, 59932: 7 }; - return { - basic: { - main: main, - rest: rest - }, - gps: { - main: [ 1, 2, 5, 2, 5, 1, 5, 5, 0, 9 ], - rest: { 18: 2, 29: 2 } - } - }; - -}(); - -UTIF._readIFD = function ( bin, data, offset, ifds, depth, prm ) { - - var cnt = bin.readUshort( data, offset ); offset += 2; - var ifd = {}; - - if ( prm.debug ) console.log( ' '.repeat( depth ), ifds.length - 1, '>>>----------------' ); - for ( var i = 0; i < cnt; i ++ ) { - - var tag = bin.readUshort( data, offset ); offset += 2; - var type = bin.readUshort( data, offset ); offset += 2; - var num = bin.readUint( data, offset ); offset += 4; - var voff = bin.readUint( data, offset ); offset += 4; - - var arr = []; - //ifd["t"+tag+"-"+UTIF.tags[tag]] = arr; - if ( type == 1 || type == 7 ) { - - arr = new Uint8Array( data.buffer, ( num < 5 ? offset - 4 : voff ), num ); - - } - - if ( type == 2 ) { - - var o0 = ( num < 5 ? offset - 4 : voff ), c = data[ o0 ], len = Math.max( 0, Math.min( num - 1, data.length - o0 ) ); - if ( c < 128 || len == 0 ) arr.push( bin.readASCII( data, o0, len ) ); - else arr = new Uint8Array( data.buffer, o0, len ); - - } - - if ( type == 3 ) { - - for ( var j = 0; j < num; j ++ ) arr.push( bin.readUshort( data, ( num < 3 ? offset - 4 : voff ) + 2 * j ) ); - - } - - if ( type == 4 - || type == 13 ) { - - for ( var j = 0; j < num; j ++ ) arr.push( bin.readUint( data, ( num < 2 ? offset - 4 : voff ) + 4 * j ) ); - - } - - if ( type == 5 || type == 10 ) { - - var ri = type == 5 ? bin.readUint : bin.readInt; - for ( var j = 0; j < num; j ++ ) arr.push( [ ri( data, voff + j * 8 ), ri( data, voff + j * 8 + 4 ) ] ); - - } - - if ( type == 8 ) { - - for ( var j = 0; j < num; j ++ ) arr.push( bin.readShort( data, ( num < 3 ? offset - 4 : voff ) + 2 * j ) ); - - } - - if ( type == 9 ) { - - for ( var j = 0; j < num; j ++ ) arr.push( bin.readInt( data, ( num < 2 ? offset - 4 : voff ) + 4 * j ) ); - - } - - if ( type == 11 ) { - - for ( var j = 0; j < num; j ++ ) arr.push( bin.readFloat( data, voff + j * 4 ) ); - - } - - if ( type == 12 ) { - - for ( var j = 0; j < num; j ++ ) arr.push( bin.readDouble( data, voff + j * 8 ) ); - - } - - if ( num != 0 && arr.length == 0 ) { - - console.log( tag, 'unknown TIFF tag type: ', type, 'num:', num ); if ( i == 0 ) return; continue; - - } - - if ( prm.debug ) console.log( ' '.repeat( depth ), tag, type, UTIF.tags[ tag ], arr ); - - ifd[ 't' + tag ] = arr; - - if ( tag == 330 || tag == 34665 || tag == 34853 || ( tag == 50740 && bin.readUshort( data, bin.readUint( arr, 0 ) ) < 300 ) || tag == 61440 ) { - - var oarr = tag == 50740 ? [ bin.readUint( arr, 0 ) ] : arr; - var subfd = []; - for ( var j = 0; j < oarr.length; j ++ ) UTIF._readIFD( bin, data, oarr[ j ], subfd, depth + 1, prm ); - if ( tag == 330 ) ifd.subIFD = subfd; - if ( tag == 34665 ) ifd.exifIFD = subfd[ 0 ]; - if ( tag == 34853 ) ifd.gpsiIFD = subfd[ 0 ]; //console.log("gps", subfd[0]); } - if ( tag == 50740 ) ifd.dngPrvt = subfd[ 0 ]; - if ( tag == 61440 ) ifd.fujiIFD = subfd[ 0 ]; - - } - - if ( tag == 37500 && prm.parseMN ) { - - var mn = arr; - //console.log(bin.readASCII(mn,0,mn.length), mn); - if ( bin.readASCII( mn, 0, 5 ) == 'Nikon' ) ifd.makerNote = UTIF[ 'decode' ]( mn.slice( 10 ).buffer )[ 0 ]; - else if ( bin.readUshort( data, voff ) < 300 && bin.readUshort( data, voff + 4 ) <= 12 ) { - - var subsub = []; UTIF._readIFD( bin, data, voff, subsub, depth + 1, prm ); - ifd.makerNote = subsub[ 0 ]; - - } - - } - - } - - ifds.push( ifd ); - if ( prm.debug ) console.log( ' '.repeat( depth ), '<<<---------------' ); - return offset; - -}; - -UTIF.toRGBA = function ( out, type ) { - - const w = out.width, h = out.height, area = w * h, data = out.data; - - let img; - - switch ( type ) { - - case HalfFloatType: - - img = new Uint16Array( area * 4 ); - break; - - case FloatType: - - img = new Float32Array( area * 4 ); - break; - - default: - console.error( 'THREE.LogLuvLoader: Unsupported texture data type:', type ); - - } - - let intp = out[ 't262' ] ? out[ 't262' ][ 0 ] : 2; - const bps = out[ 't258' ] ? Math.min( 32, out[ 't258' ][ 0 ] ) : 1; - - if ( out[ 't262' ] == null && bps == 1 ) intp = 0; - - if ( intp == 32845 ) { - - for ( let y = 0; y < h; y ++ ) { - - for ( let x = 0; x < w; x ++ ) { - - const si = ( y * w + x ) * 6, qi = ( y * w + x ) * 4; - let L = ( data[ si + 1 ] << 8 ) | data[ si ]; - - L = Math.pow( 2, ( L + 0.5 ) / 256 - 64 ); - const u = ( data[ si + 3 ] + 0.5 ) / 410; - const v = ( data[ si + 5 ] + 0.5 ) / 410; - - // Luv to xyY - const sX = ( 9 * u ) / ( 6 * u - 16 * v + 12 ); - const sY = ( 4 * v ) / ( 6 * u - 16 * v + 12 ); - const bY = L; - - // xyY to XYZ - const X = ( sX * bY ) / sY, Y = bY, Z = ( 1 - sX - sY ) * bY / sY; - - // XYZ to linear RGB - const r = 2.690 * X - 1.276 * Y - 0.414 * Z; - const g = - 1.022 * X + 1.978 * Y + 0.044 * Z; - const b = 0.061 * X - 0.224 * Y + 1.163 * Z; - - if ( type === HalfFloatType ) { - - img[ qi ] = DataUtils.toHalfFloat( Math.min( r, 65504 ) ); - img[ qi + 1 ] = DataUtils.toHalfFloat( Math.min( g, 65504 ) ); - img[ qi + 2 ] = DataUtils.toHalfFloat( Math.min( b, 65504 ) ); - img[ qi + 3 ] = DataUtils.toHalfFloat( 1 ); - - - } else { - - img[ qi ] = r; - img[ qi + 1 ] = g; - img[ qi + 2 ] = b; - img[ qi + 3 ] = 1; - - } - - } - - } - - } else { - - console.log( 'Unsupported Photometric interpretation: ' + intp ); - - } - - return img; - -}; - -UTIF._binBE = -{ - nextZero: function ( data, o ) { - - while ( data[ o ] != 0 ) o ++; return o; - - }, - readUshort: function ( buff, p ) { - - return ( buff[ p ] << 8 ) | buff[ p + 1 ]; - - }, - readShort: function ( buff, p ) { - - var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 1 ]; a[ 1 ] = buff[ p + 0 ]; return UTIF._binBE.i16[ 0 ]; - - }, - readInt: function ( buff, p ) { - - var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 3 ]; a[ 1 ] = buff[ p + 2 ]; a[ 2 ] = buff[ p + 1 ]; a[ 3 ] = buff[ p + 0 ]; return UTIF._binBE.i32[ 0 ]; - - }, - readUint: function ( buff, p ) { - - var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 3 ]; a[ 1 ] = buff[ p + 2 ]; a[ 2 ] = buff[ p + 1 ]; a[ 3 ] = buff[ p + 0 ]; return UTIF._binBE.ui32[ 0 ]; - - }, - readASCII: function ( buff, p, l ) { - - var s = ''; for ( var i = 0; i < l; i ++ ) s += String.fromCharCode( buff[ p + i ] ); return s; - - }, - readFloat: function ( buff, p ) { - - var a = UTIF._binBE.ui8; for ( var i = 0; i < 4; i ++ ) a[ i ] = buff[ p + 3 - i ]; return UTIF._binBE.fl32[ 0 ]; - - }, - readDouble: function ( buff, p ) { - - var a = UTIF._binBE.ui8; for ( var i = 0; i < 8; i ++ ) a[ i ] = buff[ p + 7 - i ]; return UTIF._binBE.fl64[ 0 ]; - - }, - - writeUshort: function ( buff, p, n ) { - - buff[ p ] = ( n >> 8 ) & 255; buff[ p + 1 ] = n & 255; - - }, - writeInt: function ( buff, p, n ) { - - var a = UTIF._binBE.ui8; UTIF._binBE.i32[ 0 ] = n; buff[ p + 3 ] = a[ 0 ]; buff[ p + 2 ] = a[ 1 ]; buff[ p + 1 ] = a[ 2 ]; buff[ p + 0 ] = a[ 3 ]; - - }, - writeUint: function ( buff, p, n ) { - - buff[ p ] = ( n >> 24 ) & 255; buff[ p + 1 ] = ( n >> 16 ) & 255; buff[ p + 2 ] = ( n >> 8 ) & 255; buff[ p + 3 ] = ( n >> 0 ) & 255; - - }, - writeASCII: function ( buff, p, s ) { - - for ( var i = 0; i < s.length; i ++ ) buff[ p + i ] = s.charCodeAt( i ); - - }, - writeDouble: function ( buff, p, n ) { - - UTIF._binBE.fl64[ 0 ] = n; - for ( var i = 0; i < 8; i ++ ) buff[ p + i ] = UTIF._binBE.ui8[ 7 - i ]; - - } -}; -UTIF._binBE.ui8 = new Uint8Array( 8 ); -UTIF._binBE.i16 = new Int16Array( UTIF._binBE.ui8.buffer ); -UTIF._binBE.i32 = new Int32Array( UTIF._binBE.ui8.buffer ); -UTIF._binBE.ui32 = new Uint32Array( UTIF._binBE.ui8.buffer ); -UTIF._binBE.fl32 = new Float32Array( UTIF._binBE.ui8.buffer ); -UTIF._binBE.fl64 = new Float64Array( UTIF._binBE.ui8.buffer ); - -UTIF._binLE = -{ - nextZero: UTIF._binBE.nextZero, - readUshort: function ( buff, p ) { - - return ( buff[ p + 1 ] << 8 ) | buff[ p ]; - - }, - readShort: function ( buff, p ) { - - var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 0 ]; a[ 1 ] = buff[ p + 1 ]; return UTIF._binBE.i16[ 0 ]; - - }, - readInt: function ( buff, p ) { - - var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 0 ]; a[ 1 ] = buff[ p + 1 ]; a[ 2 ] = buff[ p + 2 ]; a[ 3 ] = buff[ p + 3 ]; return UTIF._binBE.i32[ 0 ]; - - }, - readUint: function ( buff, p ) { - - var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 0 ]; a[ 1 ] = buff[ p + 1 ]; a[ 2 ] = buff[ p + 2 ]; a[ 3 ] = buff[ p + 3 ]; return UTIF._binBE.ui32[ 0 ]; - - }, - readASCII: UTIF._binBE.readASCII, - readFloat: function ( buff, p ) { - - var a = UTIF._binBE.ui8; for ( var i = 0; i < 4; i ++ ) a[ i ] = buff[ p + i ]; return UTIF._binBE.fl32[ 0 ]; - - }, - readDouble: function ( buff, p ) { - - var a = UTIF._binBE.ui8; for ( var i = 0; i < 8; i ++ ) a[ i ] = buff[ p + i ]; return UTIF._binBE.fl64[ 0 ]; - - }, - - writeUshort: function ( buff, p, n ) { - - buff[ p ] = ( n ) & 255; buff[ p + 1 ] = ( n >> 8 ) & 255; - - }, - writeInt: function ( buff, p, n ) { - - var a = UTIF._binBE.ui8; UTIF._binBE.i32[ 0 ] = n; buff[ p + 0 ] = a[ 0 ]; buff[ p + 1 ] = a[ 1 ]; buff[ p + 2 ] = a[ 2 ]; buff[ p + 3 ] = a[ 3 ]; - - }, - writeUint: function ( buff, p, n ) { - - buff[ p ] = ( n >>> 0 ) & 255; buff[ p + 1 ] = ( n >>> 8 ) & 255; buff[ p + 2 ] = ( n >>> 16 ) & 255; buff[ p + 3 ] = ( n >>> 24 ) & 255; - - }, - writeASCII: UTIF._binBE.writeASCII -}; -UTIF._copyTile = function ( tb, tw, th, b, w, h, xoff, yoff ) { - - //log("copyTile", tw, th, w, h, xoff, yoff); - var xlim = Math.min( tw, w - xoff ); - var ylim = Math.min( th, h - yoff ); - for ( var y = 0; y < ylim; y ++ ) { - - var tof = ( yoff + y ) * w + xoff; - var sof = y * tw; - for ( var x = 0; x < xlim; x ++ ) b[ tof + x ] = tb[ sof + x ]; - - } - -}; - -export { LogLuvLoader }; diff --git a/examples/jsm/loaders/LottieLoader.js b/examples/jsm/loaders/LottieLoader.js index 4ef0401c223feb..586388cbad866b 100644 --- a/examples/jsm/loaders/LottieLoader.js +++ b/examples/jsm/loaders/LottieLoader.js @@ -8,20 +8,73 @@ import { import lottie from '../libs/lottie_canvas.module.js'; +/** + * A loader for the Lottie texture animation format. + * + * The loader returns an instance of {@link CanvasTexture} to represent + * the animated texture. Two additional properties are added to each texture: + * - `animation`: The return value of `lottie.loadAnimation()` which is an object + * with an API for controlling the animation's playback. + * - `image`: The image container. + * + * ```js + * const loader = new LottieLoader(); + * loader.setQuality( 2 ); + * const texture = await loader.loadAsync( 'textures/lottie/24017-lottie-logo-animation.json' ); + * + * const geometry = new THREE.BoxGeometry(); + * const material = new THREE.MeshBasicMaterial( { map: texture } ); + * const mesh = new THREE.Mesh( geometry, material ); + * scene.add( mesh ); + * ``` + * + * @augments Loader + * @three_import import { LottieLoader } from 'three/addons/loaders/LottieLoader.js'; + */ class LottieLoader extends Loader { + /** + * Constructs a new Lottie loader. + * + * @deprecated The loader has been deprecated and will be removed with r186. Use lottie-web instead and create your animated texture manually. + * @param {LoadingManager} [manager] - The loading manager. + */ + constructor( manager ) { + + super( manager ); + + console.warn( 'THREE.LottieLoader: The loader has been deprecated and will be removed with r186. Use lottie-web instead and create your animated texture manually.' ); + + } + + /** + * Sets the texture quality. + * + * @param {number} value - The texture quality. + */ setQuality( value ) { this._quality = value; } + /** + * Starts loading from the given URL and passes the loaded Lottie asset + * to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(CanvasTexture)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + * @returns {CanvasTexture} The Lottie texture. + */ load( url, onLoad, onProgress, onError ) { const quality = this._quality || 1; const texture = new CanvasTexture(); texture.minFilter = NearestFilter; + texture.generateMipmaps = false; texture.colorSpace = SRGBColorSpace; const loader = new FileLoader( this.manager ); @@ -32,7 +85,7 @@ class LottieLoader extends Loader { const data = JSON.parse( text ); - // lottie uses container.offetWidth and offsetHeight + // lottie uses container.offsetWidth and offsetHeight // to define width/height const container = document.createElement( 'div' ); diff --git a/examples/jsm/loaders/MD2Loader.js b/examples/jsm/loaders/MD2Loader.js index 2d88be402a7df4..d3a8a06afb17fd 100644 --- a/examples/jsm/loaders/MD2Loader.js +++ b/examples/jsm/loaders/MD2Loader.js @@ -91,14 +91,44 @@ const _normalData = [ [ - 0.587785, - 0.425325, - 0.688191 ], [ - 0.688191, - 0.587785, - 0.425325 ] ]; +/** + * A loader for the MD2 format. + * + * The loader represents the animations of the MD2 asset as an array of animation + * clips and stores them in the `animations` property of the geometry. + * + * ```js + * const loader = new MD2Loader(); + * const geometry = await loader.loadAsync( './models/md2/ogro/ogro.md2' ); + * + * const animations = geometry.animations; + * ``` + * + * @augments Loader + * @three_import import { MD2Loader } from 'three/addons/loaders/MD2Loader.js'; + */ class MD2Loader extends Loader { + /** + * Constructs a new MD2 loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); } + /** + * Starts loading from the given URL and passes the loaded MD2 asset + * to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(BufferGeometry)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} [onProgress] - Executed while the loading is in progress. + * @param {onErrorCallback} [onError] - Executed when errors occur. + */ load( url, onLoad, onProgress, onError ) { const scope = this; @@ -134,6 +164,12 @@ class MD2Loader extends Loader { } + /** + * Parses the given MD2 data and returns a geometry. + * + * @param {ArrayBuffer} buffer - The raw MD2 data as an array buffer. + * @return {BufferGeometry} The parsed geometry data. + */ parse( buffer ) { const data = new DataView( buffer ); @@ -388,7 +424,7 @@ class MD2Loader extends Loader { geometry.morphAttributes.normal = morphNormals; geometry.morphTargetsRelative = false; - geometry.animations = AnimationClip.CreateClipsFromMorphTargetSequences( frames, 10 ); + geometry.animations = AnimationClip.CreateClipsFromMorphTargetSequences( frames, 10, false ); return geometry; diff --git a/examples/jsm/loaders/MDDLoader.js b/examples/jsm/loaders/MDDLoader.js index e70f8b0430f222..5ce29b3470894f 100644 --- a/examples/jsm/loaders/MDDLoader.js +++ b/examples/jsm/loaders/MDDLoader.js @@ -1,15 +1,3 @@ -/** - * MDD is a special format that stores a position for every vertex in a model for every frame in an animation. - * Similar to BVH, it can be used to transfer animation data between different 3D applications or engines. - * - * MDD stores its data in binary format (big endian) in the following way: - * - * number of frames (a single uint32) - * number of vertices (a single uint32) - * time values for each frame (sequence of float32) - * vertex data for each frame (sequence of float32) - */ - import { AnimationClip, BufferAttribute, @@ -18,14 +6,64 @@ import { NumberKeyframeTrack } from 'three'; +/** + * A loader for the MDD format. + * + * MDD stores a position for every vertex in a model for every frame in an animation. + * Similar to BVH, it can be used to transfer animation data between different 3D applications or engines. + * + * MDD stores its data in binary format (big endian) in the following way: + * + * - number of frames (a single uint32) + * - number of vertices (a single uint32) + * - time values for each frame (sequence of float32) + * - vertex data for each frame (sequence of float32) + * + * ```js + * const loader = new MDDLoader(); + * const result = await loader.loadAsync( 'models/mdd/cube.mdd' ); + * + * const morphTargets = result.morphTargets; + * const clip = result.clip; + * // clip.optimize(); // optional + * + * const geometry = new THREE.BoxGeometry(); + * geometry.morphAttributes.position = morphTargets; // apply morph targets (vertex data must match) + * + * const material = new THREE.MeshBasicMaterial(); + * + * const mesh = new THREE.Mesh( geometry, material ); + * scene.add( mesh ); + * + * const mixer = new THREE.AnimationMixer( mesh ); + * mixer.clipAction( clip ).play(); + * ``` + * + * @augments Loader + * @three_import import { MDDLoader } from 'three/addons/loaders/MDDLoader.js'; + */ class MDDLoader extends Loader { + /** + * Constructs a new MDD loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); } + /** + * Starts loading from the given URL and passes the loaded MDD asset + * to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function({clip:AnimationClip, morphTargets:Array})} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ load( url, onLoad, onProgress, onError ) { const scope = this; @@ -41,6 +79,13 @@ class MDDLoader extends Loader { } + /** + * Parses the given MDD data and returns an object holding the animation clip and the respective + * morph targets. + * + * @param {ArrayBuffer} data - The raw XYZ data as an array buffer. + * @return {{clip:AnimationClip, morphTargets:Array}} The result object. + */ parse( data ) { const view = new DataView( data ); diff --git a/examples/jsm/loaders/MMDLoader.js b/examples/jsm/loaders/MMDLoader.js deleted file mode 100644 index 5f2a9393565a4f..00000000000000 --- a/examples/jsm/loaders/MMDLoader.js +++ /dev/null @@ -1,2278 +0,0 @@ -import { - AddOperation, - AnimationClip, - Bone, - BufferGeometry, - Color, - ColorManagement, - CustomBlending, - TangentSpaceNormalMap, - DoubleSide, - DstAlphaFactor, - Euler, - FileLoader, - Float32BufferAttribute, - FrontSide, - Interpolant, - Loader, - LoaderUtils, - UniformsUtils, - ShaderMaterial, - MultiplyOperation, - NearestFilter, - NumberKeyframeTrack, - OneMinusSrcAlphaFactor, - Quaternion, - QuaternionKeyframeTrack, - RepeatWrapping, - Skeleton, - SkinnedMesh, - SrcAlphaFactor, - TextureLoader, - Uint16BufferAttribute, - Vector3, - VectorKeyframeTrack, - RGB_S3TC_DXT1_Format, - RGB_PVRTC_4BPPV1_Format, - RGB_PVRTC_2BPPV1_Format, - RGB_ETC1_Format, - RGB_ETC2_Format -} from 'three'; -import { MMDToonShader } from '../shaders/MMDToonShader.js'; -import { TGALoader } from '../loaders/TGALoader.js'; -import { MMDParser } from '../libs/mmdparser.module.js'; - -/** - * Dependencies - * - mmd-parser https://github.com/takahirox/mmd-parser - * - TGALoader - * - OutlineEffect - * - * MMDLoader creates Three.js Objects from MMD resources as - * PMD, PMX, VMD, and VPD files. - * - * PMD/PMX is a model data format, VMD is a motion data format - * VPD is a posing data format used in MMD(Miku Miku Dance). - * - * MMD official site - * - https://sites.google.com/view/evpvp/ - * - * PMD, VMD format (in Japanese) - * - http://blog.goo.ne.jp/torisu_tetosuki/e/209ad341d3ece2b1b4df24abf619d6e4 - * - * PMX format - * - https://gist.github.com/felixjones/f8a06bd48f9da9a4539f - * - * TODO - * - light motion in vmd support. - * - SDEF support. - * - uv/material/bone morphing support. - * - more precise grant skinning support. - * - shadow support. - */ - -/** - * @param {THREE.LoadingManager} manager - */ -class MMDLoader extends Loader { - - constructor( manager ) { - - super( manager ); - - this.loader = new FileLoader( this.manager ); - - this.parser = null; // lazy generation - this.meshBuilder = new MeshBuilder( this.manager ); - this.animationBuilder = new AnimationBuilder(); - - } - - /** - * @param {string} animationPath - * @return {MMDLoader} - */ - setAnimationPath( animationPath ) { - - this.animationPath = animationPath; - return this; - - } - - // Load MMD assets as Three.js Object - - /** - * Loads Model file (.pmd or .pmx) as a SkinnedMesh. - * - * @param {string} url - url to Model(.pmd or .pmx) file - * @param {function} onLoad - * @param {function} onProgress - * @param {function} onError - */ - load( url, onLoad, onProgress, onError ) { - - const builder = this.meshBuilder.setCrossOrigin( this.crossOrigin ); - - // resource path - - let resourcePath; - - if ( this.resourcePath !== '' ) { - - resourcePath = this.resourcePath; - - } else if ( this.path !== '' ) { - - resourcePath = this.path; - - } else { - - resourcePath = LoaderUtils.extractUrlBase( url ); - - } - - const modelExtension = this._extractExtension( url ).toLowerCase(); - - // Should I detect by seeing header? - if ( modelExtension !== 'pmd' && modelExtension !== 'pmx' ) { - - if ( onError ) onError( new Error( 'THREE.MMDLoader: Unknown model file extension .' + modelExtension + '.' ) ); - - return; - - } - - this[ modelExtension === 'pmd' ? 'loadPMD' : 'loadPMX' ]( url, function ( data ) { - - onLoad( builder.build( data, resourcePath, onProgress, onError ) ); - - }, onProgress, onError ); - - } - - /** - * Loads Motion file(s) (.vmd) as a AnimationClip. - * If two or more files are specified, they'll be merged. - * - * @param {string|Array} url - url(s) to animation(.vmd) file(s) - * @param {SkinnedMesh|THREE.Camera} object - tracks will be fitting to this object - * @param {function} onLoad - * @param {function} onProgress - * @param {function} onError - */ - loadAnimation( url, object, onLoad, onProgress, onError ) { - - const builder = this.animationBuilder; - - this.loadVMD( url, function ( vmd ) { - - onLoad( object.isCamera - ? builder.buildCameraAnimation( vmd ) - : builder.build( vmd, object ) ); - - }, onProgress, onError ); - - } - - /** - * Loads mode file and motion file(s) as an object containing - * a SkinnedMesh and a AnimationClip. - * Tracks of AnimationClip are fitting to the model. - * - * @param {string} modelUrl - url to Model(.pmd or .pmx) file - * @param {string|Array{string}} vmdUrl - url(s) to animation(.vmd) file - * @param {function} onLoad - * @param {function} onProgress - * @param {function} onError - */ - loadWithAnimation( modelUrl, vmdUrl, onLoad, onProgress, onError ) { - - const scope = this; - - this.load( modelUrl, function ( mesh ) { - - scope.loadAnimation( vmdUrl, mesh, function ( animation ) { - - onLoad( { - mesh: mesh, - animation: animation - } ); - - }, onProgress, onError ); - - }, onProgress, onError ); - - } - - // Load MMD assets as Object data parsed by MMDParser - - /** - * Loads .pmd file as an Object. - * - * @param {string} url - url to .pmd file - * @param {function} onLoad - * @param {function} onProgress - * @param {function} onError - */ - loadPMD( url, onLoad, onProgress, onError ) { - - const parser = this._getParser(); - - this.loader - .setMimeType( undefined ) - .setPath( this.path ) - .setResponseType( 'arraybuffer' ) - .setRequestHeader( this.requestHeader ) - .setWithCredentials( this.withCredentials ) - .load( url, function ( buffer ) { - - try { - - onLoad( parser.parsePmd( buffer, true ) ); - - } catch ( e ) { - - if ( onError ) onError( e ); - - } - - }, onProgress, onError ); - - } - - /** - * Loads .pmx file as an Object. - * - * @param {string} url - url to .pmx file - * @param {function} onLoad - * @param {function} onProgress - * @param {function} onError - */ - loadPMX( url, onLoad, onProgress, onError ) { - - const parser = this._getParser(); - - this.loader - .setMimeType( undefined ) - .setPath( this.path ) - .setResponseType( 'arraybuffer' ) - .setRequestHeader( this.requestHeader ) - .setWithCredentials( this.withCredentials ) - .load( url, function ( buffer ) { - - try { - - onLoad( parser.parsePmx( buffer, true ) ); - - } catch ( e ) { - - if ( onError ) onError( e ); - - } - - }, onProgress, onError ); - - } - - /** - * Loads .vmd file as an Object. If two or more files are specified - * they'll be merged. - * - * @param {string|Array} url - url(s) to .vmd file(s) - * @param {function} onLoad - * @param {function} onProgress - * @param {function} onError - */ - loadVMD( url, onLoad, onProgress, onError ) { - - const urls = Array.isArray( url ) ? url : [ url ]; - - const vmds = []; - const vmdNum = urls.length; - - const parser = this._getParser(); - - this.loader - .setMimeType( undefined ) - .setPath( this.animationPath ) - .setResponseType( 'arraybuffer' ) - .setRequestHeader( this.requestHeader ) - .setWithCredentials( this.withCredentials ); - - for ( let i = 0, il = urls.length; i < il; i ++ ) { - - this.loader.load( urls[ i ], function ( buffer ) { - - try { - - vmds.push( parser.parseVmd( buffer, true ) ); - - if ( vmds.length === vmdNum ) onLoad( parser.mergeVmds( vmds ) ); - - } catch ( e ) { - - if ( onError ) onError( e ); - - } - - }, onProgress, onError ); - - } - - } - - /** - * Loads .vpd file as an Object. - * - * @param {string} url - url to .vpd file - * @param {boolean} isUnicode - * @param {function} onLoad - * @param {function} onProgress - * @param {function} onError - */ - loadVPD( url, isUnicode, onLoad, onProgress, onError ) { - - const parser = this._getParser(); - - this.loader - .setMimeType( isUnicode ? undefined : 'text/plain; charset=shift_jis' ) - .setPath( this.animationPath ) - .setResponseType( 'text' ) - .setRequestHeader( this.requestHeader ) - .setWithCredentials( this.withCredentials ) - .load( url, function ( text ) { - - try { - - onLoad( parser.parseVpd( text, true ) ); - - } catch ( e ) { - - if ( onError ) onError( e ); - - } - - }, onProgress, onError ); - - } - - // private methods - - _extractExtension( url ) { - - const index = url.lastIndexOf( '.' ); - return index < 0 ? '' : url.slice( index + 1 ); - - } - - _getParser() { - - if ( this.parser === null ) { - - this.parser = new MMDParser.Parser(); - - } - - return this.parser; - - } - -} - -// Utilities - -/* - * base64 encoded defalut toon textures toon00.bmp - toon10.bmp. - * We don't need to request external toon image files. - */ -const DEFAULT_TOON_TEXTURES = [ - 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAL0lEQVRYR+3QQREAAAzCsOFfNJPBJ1XQS9r2hsUAAQIECBAgQIAAAQIECBAgsBZ4MUx/ofm2I/kAAAAASUVORK5CYII=', - 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAN0lEQVRYR+3WQREAMBACsZ5/bWiiMvgEBTt5cW37hjsBBAgQIECAwFwgyfYPCCBAgAABAgTWAh8aBHZBl14e8wAAAABJRU5ErkJggg==', - 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAOUlEQVRYR+3WMREAMAwDsYY/yoDI7MLwIiP40+RJklfcCCBAgAABAgTqArfb/QMCCBAgQIAAgbbAB3z/e0F3js2cAAAAAElFTkSuQmCC', - 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAN0lEQVRYR+3WQREAMBACsZ5/B5ilMvgEBTt5cW37hjsBBAgQIECAwFwgyfYPCCBAgAABAgTWAh81dWyx0gFwKAAAAABJRU5ErkJggg==', - 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAOklEQVRYR+3WoREAMAwDsWb/UQtCy9wxTOQJ/oQ8SXKKGwEECBAgQIBAXeDt7f4BAQQIECBAgEBb4AOz8Hzx7WLY4wAAAABJRU5ErkJggg==', - 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAABPUlEQVRYR+1XwW7CMAy1+f9fZOMysSEOEweEOPRNdm3HbdOyIhAcklPrOs/PLy9RygBALxzcCDQFmgJNgaZAU6Ap0BR4PwX8gsRMVLssMRH5HcpzJEaWL7EVg9F1IHRlyqQohgVr4FGUlUcMJSjcUlDw0zvjeun70cLWmneoyf7NgBTQSniBTQQSuJAZsOnnaczjIMb5hCiuHKxokCrJfVnrctyZL0PkJAJe1HMil4nxeyi3Ypfn1kX51jpPvo/JeCNC4PhVdHdJw2XjBR8brF8PEIhNVn12AgP7uHsTBguBn53MUZCqv7Lp07Pn5k1Ro+uWmUNn7D+M57rtk7aG0Vo73xyF/fbFf0bPJjDXngnGocDTdFhygZjwUQrMNrDcmZlQT50VJ/g/UwNyHpu778+yW+/ksOz/BFo54P4AsUXMfRq7XWsAAAAASUVORK5CYII=', - 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACMElEQVRYR+2Xv4pTQRTGf2dubhLdICiii2KnYKHVolhauKWPoGAnNr6BD6CvIVaihYuI2i1ia0BY0MZGRHQXjZj/mSPnnskfNWiWZUlzJ5k7M2cm833nO5Mziej2DWWJRUoCpQKlAntSQCqgw39/iUWAGmh37jrRnVsKlgpiqmkoGVABA7E57fvY+pJDdgKqF6HzFCSADkDq+F6AHABtQ+UMVE5D7zXod7fFNhTEckTbj5XQgHzNN+5tQvc5NG7C6BNkp6D3EmpXHDR+dQAjFLchW3VS9rlw3JBh+B7ys5Cf9z0GW1C/7P32AyBAOAz1q4jGliIH3YPuBnSfQX4OGreTIgEYQb/pBDtPnEQ4CivXYPAWBk13oHrB54yA9QuSn2H4AcKRpEILDt0BUzj+RLR1V5EqjD66NPRBVpLcQwjHoHYJOhsQv6U4mnzmrIXJCFr4LDwm/xBUoboG9XX4cc9VKdYoSA2yk5NQLJaKDUjTBoveG3Z2TElTxwjNK4M3LEZgUdDdruvcXzKBpStgp2NPiWi3ks9ZXxIoFVi+AvHLdc9TqtjL3/aYjpPlrzOcEnK62Szhimdd7xX232zFDTgtxezOu3WNMRLjiKgjtOhHVMd1loynVHvOgjuIIJMaELEqhJAV/RCSLbWTcfPFakFgFlALTRRvx+ok6Hlp/Q+v3fmx90bMyUzaEAhmM3KvHlXTL5DxnbGf/1M8RNNACLL5MNtPxP/mypJAqcDSFfgFhpYqWUzhTEAAAAAASUVORK5CYII=', - 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAL0lEQVRYR+3QQREAAAzCsOFfNJPBJ1XQS9r2hsUAAQIECBAgQIAAAQIECBAgsBZ4MUx/ofm2I/kAAAAASUVORK5CYII=', - 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAL0lEQVRYR+3QQREAAAzCsOFfNJPBJ1XQS9r2hsUAAQIECBAgQIAAAQIECBAgsBZ4MUx/ofm2I/kAAAAASUVORK5CYII=', - 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAL0lEQVRYR+3QQREAAAzCsOFfNJPBJ1XQS9r2hsUAAQIECBAgQIAAAQIECBAgsBZ4MUx/ofm2I/kAAAAASUVORK5CYII=', - 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAL0lEQVRYR+3QQREAAAzCsOFfNJPBJ1XQS9r2hsUAAQIECBAgQIAAAQIECBAgsBZ4MUx/ofm2I/kAAAAASUVORK5CYII=' -]; - -const NON_ALPHA_CHANNEL_FORMATS = [ - RGB_S3TC_DXT1_Format, - RGB_PVRTC_4BPPV1_Format, - RGB_PVRTC_2BPPV1_Format, - RGB_ETC1_Format, - RGB_ETC2_Format -]; - -// Builders. They build Three.js object from Object data parsed by MMDParser. - -/** - * @param {THREE.LoadingManager} manager - */ -class MeshBuilder { - - constructor( manager ) { - - this.crossOrigin = 'anonymous'; - this.geometryBuilder = new GeometryBuilder(); - this.materialBuilder = new MaterialBuilder( manager ); - - } - - /** - * @param {string} crossOrigin - * @return {MeshBuilder} - */ - setCrossOrigin( crossOrigin ) { - - this.crossOrigin = crossOrigin; - return this; - - } - - /** - * @param {Object} data - parsed PMD/PMX data - * @param {string} resourcePath - * @param {function} onProgress - * @param {function} onError - * @return {SkinnedMesh} - */ - build( data, resourcePath, onProgress, onError ) { - - const geometry = this.geometryBuilder.build( data ); - const material = this.materialBuilder - .setCrossOrigin( this.crossOrigin ) - .setResourcePath( resourcePath ) - .build( data, geometry, onProgress, onError ); - - const mesh = new SkinnedMesh( geometry, material ); - - const skeleton = new Skeleton( initBones( mesh ) ); - mesh.bind( skeleton ); - - // console.log( mesh ); // for console debug - - return mesh; - - } - -} - -// TODO: Try to remove this function - -function initBones( mesh ) { - - const geometry = mesh.geometry; - - const bones = []; - - if ( geometry && geometry.bones !== undefined ) { - - // first, create array of 'Bone' objects from geometry data - - for ( let i = 0, il = geometry.bones.length; i < il; i ++ ) { - - const gbone = geometry.bones[ i ]; - - // create new 'Bone' object - - const bone = new Bone(); - bones.push( bone ); - - // apply values - - bone.name = gbone.name; - bone.position.fromArray( gbone.pos ); - bone.quaternion.fromArray( gbone.rotq ); - if ( gbone.scl !== undefined ) bone.scale.fromArray( gbone.scl ); - - } - - // second, create bone hierarchy - - for ( let i = 0, il = geometry.bones.length; i < il; i ++ ) { - - const gbone = geometry.bones[ i ]; - - if ( ( gbone.parent !== - 1 ) && ( gbone.parent !== null ) && ( bones[ gbone.parent ] !== undefined ) ) { - - // subsequent bones in the hierarchy - - bones[ gbone.parent ].add( bones[ i ] ); - - } else { - - // topmost bone, immediate child of the skinned mesh - - mesh.add( bones[ i ] ); - - } - - } - - } - - // now the bones are part of the scene graph and children of the skinned mesh. - // let's update the corresponding matrices - - mesh.updateMatrixWorld( true ); - - return bones; - -} - -// - -class GeometryBuilder { - - /** - * @param {Object} data - parsed PMD/PMX data - * @return {BufferGeometry} - */ - build( data ) { - - // for geometry - const positions = []; - const uvs = []; - const normals = []; - - const indices = []; - - const groups = []; - - const bones = []; - const skinIndices = []; - const skinWeights = []; - - const morphTargets = []; - const morphPositions = []; - - const iks = []; - const grants = []; - - const rigidBodies = []; - const constraints = []; - - // for work - let offset = 0; - const boneTypeTable = {}; - - // positions, normals, uvs, skinIndices, skinWeights - - for ( let i = 0; i < data.metadata.vertexCount; i ++ ) { - - const v = data.vertices[ i ]; - - for ( let j = 0, jl = v.position.length; j < jl; j ++ ) { - - positions.push( v.position[ j ] ); - - } - - for ( let j = 0, jl = v.normal.length; j < jl; j ++ ) { - - normals.push( v.normal[ j ] ); - - } - - for ( let j = 0, jl = v.uv.length; j < jl; j ++ ) { - - uvs.push( v.uv[ j ] ); - - } - - for ( let j = 0; j < 4; j ++ ) { - - skinIndices.push( v.skinIndices.length - 1 >= j ? v.skinIndices[ j ] : 0.0 ); - - } - - for ( let j = 0; j < 4; j ++ ) { - - skinWeights.push( v.skinWeights.length - 1 >= j ? v.skinWeights[ j ] : 0.0 ); - - } - - } - - // indices - - for ( let i = 0; i < data.metadata.faceCount; i ++ ) { - - const face = data.faces[ i ]; - - for ( let j = 0, jl = face.indices.length; j < jl; j ++ ) { - - indices.push( face.indices[ j ] ); - - } - - } - - // groups - - for ( let i = 0; i < data.metadata.materialCount; i ++ ) { - - const material = data.materials[ i ]; - - groups.push( { - offset: offset * 3, - count: material.faceCount * 3 - } ); - - offset += material.faceCount; - - } - - // bones - - for ( let i = 0; i < data.metadata.rigidBodyCount; i ++ ) { - - const body = data.rigidBodies[ i ]; - let value = boneTypeTable[ body.boneIndex ]; - - // keeps greater number if already value is set without any special reasons - value = value === undefined ? body.type : Math.max( body.type, value ); - - boneTypeTable[ body.boneIndex ] = value; - - } - - for ( let i = 0; i < data.metadata.boneCount; i ++ ) { - - const boneData = data.bones[ i ]; - - const bone = { - index: i, - transformationClass: boneData.transformationClass, - parent: boneData.parentIndex, - name: boneData.name, - pos: boneData.position.slice( 0, 3 ), - rotq: [ 0, 0, 0, 1 ], - scl: [ 1, 1, 1 ], - rigidBodyType: boneTypeTable[ i ] !== undefined ? boneTypeTable[ i ] : - 1 - }; - - if ( bone.parent !== - 1 ) { - - bone.pos[ 0 ] -= data.bones[ bone.parent ].position[ 0 ]; - bone.pos[ 1 ] -= data.bones[ bone.parent ].position[ 1 ]; - bone.pos[ 2 ] -= data.bones[ bone.parent ].position[ 2 ]; - - } - - bones.push( bone ); - - } - - // iks - - // TODO: remove duplicated codes between PMD and PMX - if ( data.metadata.format === 'pmd' ) { - - for ( let i = 0; i < data.metadata.ikCount; i ++ ) { - - const ik = data.iks[ i ]; - - const param = { - target: ik.target, - effector: ik.effector, - iteration: ik.iteration, - maxAngle: ik.maxAngle * 4, - links: [] - }; - - for ( let j = 0, jl = ik.links.length; j < jl; j ++ ) { - - const link = {}; - link.index = ik.links[ j ].index; - link.enabled = true; - - if ( data.bones[ link.index ].name.indexOf( 'ひざ' ) >= 0 ) { - - link.limitation = new Vector3( 1.0, 0.0, 0.0 ); - - } - - param.links.push( link ); - - } - - iks.push( param ); - - } - - } else { - - for ( let i = 0; i < data.metadata.boneCount; i ++ ) { - - const ik = data.bones[ i ].ik; - - if ( ik === undefined ) continue; - - const param = { - target: i, - effector: ik.effector, - iteration: ik.iteration, - maxAngle: ik.maxAngle, - links: [] - }; - - for ( let j = 0, jl = ik.links.length; j < jl; j ++ ) { - - const link = {}; - link.index = ik.links[ j ].index; - link.enabled = true; - - if ( ik.links[ j ].angleLimitation === 1 ) { - - // Revert if rotationMin/Max doesn't work well - // link.limitation = new Vector3( 1.0, 0.0, 0.0 ); - - const rotationMin = ik.links[ j ].lowerLimitationAngle; - const rotationMax = ik.links[ j ].upperLimitationAngle; - - // Convert Left to Right coordinate by myself because - // MMDParser doesn't convert. It's a MMDParser's bug - - const tmp1 = - rotationMax[ 0 ]; - const tmp2 = - rotationMax[ 1 ]; - rotationMax[ 0 ] = - rotationMin[ 0 ]; - rotationMax[ 1 ] = - rotationMin[ 1 ]; - rotationMin[ 0 ] = tmp1; - rotationMin[ 1 ] = tmp2; - - link.rotationMin = new Vector3().fromArray( rotationMin ); - link.rotationMax = new Vector3().fromArray( rotationMax ); - - } - - param.links.push( link ); - - } - - iks.push( param ); - - // Save the reference even from bone data for efficiently - // simulating PMX animation system - bones[ i ].ik = param; - - } - - } - - // grants - - if ( data.metadata.format === 'pmx' ) { - - // bone index -> grant entry map - const grantEntryMap = {}; - - for ( let i = 0; i < data.metadata.boneCount; i ++ ) { - - const boneData = data.bones[ i ]; - const grant = boneData.grant; - - if ( grant === undefined ) continue; - - const param = { - index: i, - parentIndex: grant.parentIndex, - ratio: grant.ratio, - isLocal: grant.isLocal, - affectRotation: grant.affectRotation, - affectPosition: grant.affectPosition, - transformationClass: boneData.transformationClass - }; - - grantEntryMap[ i ] = { parent: null, children: [], param: param, visited: false }; - - } - - const rootEntry = { parent: null, children: [], param: null, visited: false }; - - // Build a tree representing grant hierarchy - - for ( const boneIndex in grantEntryMap ) { - - const grantEntry = grantEntryMap[ boneIndex ]; - const parentGrantEntry = grantEntryMap[ grantEntry.parentIndex ] || rootEntry; - - grantEntry.parent = parentGrantEntry; - parentGrantEntry.children.push( grantEntry ); - - } - - // Sort grant parameters from parents to children because - // grant uses parent's transform that parent's grant is already applied - // so grant should be applied in order from parents to children - - function traverse( entry ) { - - if ( entry.param ) { - - grants.push( entry.param ); - - // Save the reference even from bone data for efficiently - // simulating PMX animation system - bones[ entry.param.index ].grant = entry.param; - - } - - entry.visited = true; - - for ( let i = 0, il = entry.children.length; i < il; i ++ ) { - - const child = entry.children[ i ]; - - // Cut off a loop if exists. (Is a grant loop invalid?) - if ( ! child.visited ) traverse( child ); - - } - - } - - traverse( rootEntry ); - - } - - // morph - - function updateAttributes( attribute, morph, ratio ) { - - for ( let i = 0; i < morph.elementCount; i ++ ) { - - const element = morph.elements[ i ]; - - let index; - - if ( data.metadata.format === 'pmd' ) { - - index = data.morphs[ 0 ].elements[ element.index ].index; - - } else { - - index = element.index; - - } - - attribute.array[ index * 3 + 0 ] += element.position[ 0 ] * ratio; - attribute.array[ index * 3 + 1 ] += element.position[ 1 ] * ratio; - attribute.array[ index * 3 + 2 ] += element.position[ 2 ] * ratio; - - } - - } - - for ( let i = 0; i < data.metadata.morphCount; i ++ ) { - - const morph = data.morphs[ i ]; - const params = { name: morph.name }; - - const attribute = new Float32BufferAttribute( data.metadata.vertexCount * 3, 3 ); - attribute.name = morph.name; - - for ( let j = 0; j < data.metadata.vertexCount * 3; j ++ ) { - - attribute.array[ j ] = positions[ j ]; - - } - - if ( data.metadata.format === 'pmd' ) { - - if ( i !== 0 ) { - - updateAttributes( attribute, morph, 1.0 ); - - } - - } else { - - if ( morph.type === 0 ) { // group - - for ( let j = 0; j < morph.elementCount; j ++ ) { - - const morph2 = data.morphs[ morph.elements[ j ].index ]; - const ratio = morph.elements[ j ].ratio; - - if ( morph2.type === 1 ) { - - updateAttributes( attribute, morph2, ratio ); - - } else { - - // TODO: implement - - } - - } - - } else if ( morph.type === 1 ) { // vertex - - updateAttributes( attribute, morph, 1.0 ); - - } else if ( morph.type === 2 ) { // bone - - // TODO: implement - - } else if ( morph.type === 3 ) { // uv - - // TODO: implement - - } else if ( morph.type === 4 ) { // additional uv1 - - // TODO: implement - - } else if ( morph.type === 5 ) { // additional uv2 - - // TODO: implement - - } else if ( morph.type === 6 ) { // additional uv3 - - // TODO: implement - - } else if ( morph.type === 7 ) { // additional uv4 - - // TODO: implement - - } else if ( morph.type === 8 ) { // material - - // TODO: implement - - } - - } - - morphTargets.push( params ); - morphPositions.push( attribute ); - - } - - // rigid bodies from rigidBodies field. - - for ( let i = 0; i < data.metadata.rigidBodyCount; i ++ ) { - - const rigidBody = data.rigidBodies[ i ]; - const params = {}; - - for ( const key in rigidBody ) { - - params[ key ] = rigidBody[ key ]; - - } - - /* - * RigidBody position parameter in PMX seems global position - * while the one in PMD seems offset from corresponding bone. - * So unify being offset. - */ - if ( data.metadata.format === 'pmx' ) { - - if ( params.boneIndex !== - 1 ) { - - const bone = data.bones[ params.boneIndex ]; - params.position[ 0 ] -= bone.position[ 0 ]; - params.position[ 1 ] -= bone.position[ 1 ]; - params.position[ 2 ] -= bone.position[ 2 ]; - - } - - } - - rigidBodies.push( params ); - - } - - // constraints from constraints field. - - for ( let i = 0; i < data.metadata.constraintCount; i ++ ) { - - const constraint = data.constraints[ i ]; - const params = {}; - - for ( const key in constraint ) { - - params[ key ] = constraint[ key ]; - - } - - const bodyA = rigidBodies[ params.rigidBodyIndex1 ]; - const bodyB = rigidBodies[ params.rigidBodyIndex2 ]; - - // Refer to http://www20.atpages.jp/katwat/wp/?p=4135 - if ( bodyA.type !== 0 && bodyB.type === 2 ) { - - if ( bodyA.boneIndex !== - 1 && bodyB.boneIndex !== - 1 && - data.bones[ bodyB.boneIndex ].parentIndex === bodyA.boneIndex ) { - - bodyB.type = 1; - - } - - } - - constraints.push( params ); - - } - - // build BufferGeometry. - - const geometry = new BufferGeometry(); - - geometry.setAttribute( 'position', new Float32BufferAttribute( positions, 3 ) ); - geometry.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - geometry.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - geometry.setAttribute( 'skinIndex', new Uint16BufferAttribute( skinIndices, 4 ) ); - geometry.setAttribute( 'skinWeight', new Float32BufferAttribute( skinWeights, 4 ) ); - geometry.setIndex( indices ); - - for ( let i = 0, il = groups.length; i < il; i ++ ) { - - geometry.addGroup( groups[ i ].offset, groups[ i ].count, i ); - - } - - geometry.bones = bones; - - geometry.morphTargets = morphTargets; - geometry.morphAttributes.position = morphPositions; - geometry.morphTargetsRelative = false; - - geometry.userData.MMD = { - bones: bones, - iks: iks, - grants: grants, - rigidBodies: rigidBodies, - constraints: constraints, - format: data.metadata.format - }; - - geometry.computeBoundingSphere(); - - return geometry; - - } - -} - -// - -/** - * @param {THREE.LoadingManager} manager - */ -class MaterialBuilder { - - constructor( manager ) { - - this.manager = manager; - - this.textureLoader = new TextureLoader( this.manager ); - this.tgaLoader = null; // lazy generation - - this.crossOrigin = 'anonymous'; - this.resourcePath = undefined; - - } - - /** - * @param {string} crossOrigin - * @return {MaterialBuilder} - */ - setCrossOrigin( crossOrigin ) { - - this.crossOrigin = crossOrigin; - return this; - - } - - /** - * @param {string} resourcePath - * @return {MaterialBuilder} - */ - setResourcePath( resourcePath ) { - - this.resourcePath = resourcePath; - return this; - - } - - /** - * @param {Object} data - parsed PMD/PMX data - * @param {BufferGeometry} geometry - some properties are dependend on geometry - * @param {function} onProgress - * @param {function} onError - * @return {Array} - */ - build( data, geometry /*, onProgress, onError */ ) { - - const materials = []; - - const textures = {}; - - this.textureLoader.setCrossOrigin( this.crossOrigin ); - - // materials - - for ( let i = 0; i < data.metadata.materialCount; i ++ ) { - - const material = data.materials[ i ]; - - const params = { userData: { MMD: {} } }; - - if ( material.name !== undefined ) params.name = material.name; - - /* - * Color - * - * MMD MMDToonMaterial - * ambient - emissive * a - * (a = 1.0 without map texture or 0.2 with map texture) - * - * MMDToonMaterial doesn't have ambient. Set it to emissive instead. - * It'll be too bright if material has map texture so using coef 0.2. - */ - params.diffuse = new Color().fromArray( material.diffuse ); - params.opacity = material.diffuse[ 3 ]; - params.specular = new Color().fromArray( material.specular ); - params.shininess = material.shininess; - params.emissive = new Color().fromArray( material.ambient ); - params.transparent = params.opacity !== 1.0; - - if ( ColorManagement.enabled === true ) { - - params.diffuse.convertSRGBToLinear(); - params.specular.convertSRGBToLinear(); - params.emissive.convertSRGBToLinear(); - - } - - // - - params.fog = true; - - // blend - - params.blending = CustomBlending; - params.blendSrc = SrcAlphaFactor; - params.blendDst = OneMinusSrcAlphaFactor; - params.blendSrcAlpha = SrcAlphaFactor; - params.blendDstAlpha = DstAlphaFactor; - - // side - - if ( data.metadata.format === 'pmx' && ( material.flag & 0x1 ) === 1 ) { - - params.side = DoubleSide; - - } else { - - params.side = params.opacity === 1.0 ? FrontSide : DoubleSide; - - } - - if ( data.metadata.format === 'pmd' ) { - - // map, matcap - - if ( material.fileName ) { - - const fileName = material.fileName; - const fileNames = fileName.split( '*' ); - - // fileNames[ 0 ]: mapFileName - // fileNames[ 1 ]: matcapFileName( optional ) - - params.map = this._loadTexture( fileNames[ 0 ], textures ); - - if ( fileNames.length > 1 ) { - - const extension = fileNames[ 1 ].slice( - 4 ).toLowerCase(); - - params.matcap = this._loadTexture( - fileNames[ 1 ], - textures - ); - - params.matcapCombine = extension === '.sph' - ? MultiplyOperation - : AddOperation; - - } - - } - - // gradientMap - - const toonFileName = ( material.toonIndex === - 1 ) - ? 'toon00.bmp' - : data.toonTextures[ material.toonIndex ].fileName; - - params.gradientMap = this._loadTexture( - toonFileName, - textures, - { - isToonTexture: true, - isDefaultToonTexture: this._isDefaultToonTexture( toonFileName ) - } - ); - - // parameters for OutlineEffect - - params.userData.outlineParameters = { - thickness: material.edgeFlag === 1 ? 0.003 : 0.0, - color: [ 0, 0, 0 ], - alpha: 1.0, - visible: material.edgeFlag === 1 - }; - - } else { - - // map - - if ( material.textureIndex !== - 1 ) { - - params.map = this._loadTexture( data.textures[ material.textureIndex ], textures ); - - // Since PMX spec don't have standard to list map files except color map and env map, - // we need to save file name for further mapping, like matching normal map file names after model loaded. - // ref: https://gist.github.com/felixjones/f8a06bd48f9da9a4539f#texture - params.userData.MMD.mapFileName = data.textures[ material.textureIndex ]; - - } - - // matcap TODO: support m.envFlag === 3 - - if ( material.envTextureIndex !== - 1 && ( material.envFlag === 1 || material.envFlag == 2 ) ) { - - params.matcap = this._loadTexture( - data.textures[ material.envTextureIndex ], - textures - ); - - // Same as color map above, keep file name in userData for further usage. - params.userData.MMD.matcapFileName = data.textures[ material.envTextureIndex ]; - - params.matcapCombine = material.envFlag === 1 - ? MultiplyOperation - : AddOperation; - - } - - // gradientMap - - let toonFileName, isDefaultToon; - - if ( material.toonIndex === - 1 || material.toonFlag !== 0 ) { - - toonFileName = 'toon' + ( '0' + ( material.toonIndex + 1 ) ).slice( - 2 ) + '.bmp'; - isDefaultToon = true; - - } else { - - toonFileName = data.textures[ material.toonIndex ]; - isDefaultToon = false; - - } - - params.gradientMap = this._loadTexture( - toonFileName, - textures, - { - isToonTexture: true, - isDefaultToonTexture: isDefaultToon - } - ); - - // parameters for OutlineEffect - params.userData.outlineParameters = { - thickness: material.edgeSize / 300, // TODO: better calculation? - color: material.edgeColor.slice( 0, 3 ), - alpha: material.edgeColor[ 3 ], - visible: ( material.flag & 0x10 ) !== 0 && material.edgeSize > 0.0 - }; - - } - - if ( params.map !== undefined ) { - - if ( ! params.transparent ) { - - this._checkImageTransparency( params.map, geometry, i ); - - } - - params.emissive.multiplyScalar( 0.2 ); - - } - - materials.push( new MMDToonMaterial( params ) ); - - } - - if ( data.metadata.format === 'pmx' ) { - - // set transparent true if alpha morph is defined. - - function checkAlphaMorph( elements, materials ) { - - for ( let i = 0, il = elements.length; i < il; i ++ ) { - - const element = elements[ i ]; - - if ( element.index === - 1 ) continue; - - const material = materials[ element.index ]; - - if ( material.opacity !== element.diffuse[ 3 ] ) { - - material.transparent = true; - - } - - } - - } - - for ( let i = 0, il = data.morphs.length; i < il; i ++ ) { - - const morph = data.morphs[ i ]; - const elements = morph.elements; - - if ( morph.type === 0 ) { - - for ( let j = 0, jl = elements.length; j < jl; j ++ ) { - - const morph2 = data.morphs[ elements[ j ].index ]; - - if ( morph2.type !== 8 ) continue; - - checkAlphaMorph( morph2.elements, materials ); - - } - - } else if ( morph.type === 8 ) { - - checkAlphaMorph( elements, materials ); - - } - - } - - } - - return materials; - - } - - // private methods - - _getTGALoader() { - - if ( this.tgaLoader === null ) { - - if ( TGALoader === undefined ) { - - throw new Error( 'THREE.MMDLoader: Import TGALoader' ); - - } - - this.tgaLoader = new TGALoader( this.manager ); - - } - - return this.tgaLoader; - - } - - _isDefaultToonTexture( name ) { - - if ( name.length !== 10 ) return false; - - return /toon(10|0[0-9])\.bmp/.test( name ); - - } - - _loadTexture( filePath, textures, params, onProgress, onError ) { - - params = params || {}; - - const scope = this; - - let fullPath; - - if ( params.isDefaultToonTexture === true ) { - - let index; - - try { - - index = parseInt( filePath.match( /toon([0-9]{2})\.bmp$/ )[ 1 ] ); - - } catch ( e ) { - - console.warn( 'THREE.MMDLoader: ' + filePath + ' seems like a ' - + 'not right default texture path. Using toon00.bmp instead.' ); - - index = 0; - - } - - fullPath = DEFAULT_TOON_TEXTURES[ index ]; - - } else { - - fullPath = this.resourcePath + filePath; - - } - - if ( textures[ fullPath ] !== undefined ) return textures[ fullPath ]; - - let loader = this.manager.getHandler( fullPath ); - - if ( loader === null ) { - - loader = ( filePath.slice( - 4 ).toLowerCase() === '.tga' ) - ? this._getTGALoader() - : this.textureLoader; - - } - - const texture = loader.load( fullPath, function ( t ) { - - // MMD toon texture is Axis-Y oriented - // but Three.js gradient map is Axis-X oriented. - // So here replaces the toon texture image with the rotated one. - if ( params.isToonTexture === true ) { - - t.image = scope._getRotatedImage( t.image ); - - t.magFilter = NearestFilter; - t.minFilter = NearestFilter; - - } - - t.flipY = false; - t.wrapS = RepeatWrapping; - t.wrapT = RepeatWrapping; - - for ( let i = 0; i < texture.readyCallbacks.length; i ++ ) { - - texture.readyCallbacks[ i ]( texture ); - - } - - delete texture.readyCallbacks; - - }, onProgress, onError ); - - texture.readyCallbacks = []; - - textures[ fullPath ] = texture; - - return texture; - - } - - _getRotatedImage( image ) { - - const canvas = document.createElement( 'canvas' ); - const context = canvas.getContext( '2d' ); - - const width = image.width; - const height = image.height; - - canvas.width = width; - canvas.height = height; - - context.clearRect( 0, 0, width, height ); - context.translate( width / 2.0, height / 2.0 ); - context.rotate( 0.5 * Math.PI ); // 90.0 * Math.PI / 180.0 - context.translate( - width / 2.0, - height / 2.0 ); - context.drawImage( image, 0, 0 ); - - return context.getImageData( 0, 0, width, height ); - - } - - // Check if the partial image area used by the texture is transparent. - _checkImageTransparency( map, geometry, groupIndex ) { - - map.readyCallbacks.push( function ( texture ) { - - // Is there any efficient ways? - function createImageData( image ) { - - const canvas = document.createElement( 'canvas' ); - canvas.width = image.width; - canvas.height = image.height; - - const context = canvas.getContext( '2d' ); - context.drawImage( image, 0, 0 ); - - return context.getImageData( 0, 0, canvas.width, canvas.height ); - - } - - function detectImageTransparency( image, uvs, indices ) { - - const width = image.width; - const height = image.height; - const data = image.data; - const threshold = 253; - - if ( data.length / ( width * height ) !== 4 ) return false; - - for ( let i = 0; i < indices.length; i += 3 ) { - - const centerUV = { x: 0.0, y: 0.0 }; - - for ( let j = 0; j < 3; j ++ ) { - - const index = indices[ i * 3 + j ]; - const uv = { x: uvs[ index * 2 + 0 ], y: uvs[ index * 2 + 1 ] }; - - if ( getAlphaByUv( image, uv ) < threshold ) return true; - - centerUV.x += uv.x; - centerUV.y += uv.y; - - } - - centerUV.x /= 3; - centerUV.y /= 3; - - if ( getAlphaByUv( image, centerUV ) < threshold ) return true; - - } - - return false; - - } - - /* - * This method expects - * texture.flipY = false - * texture.wrapS = RepeatWrapping - * texture.wrapT = RepeatWrapping - * TODO: more precise - */ - function getAlphaByUv( image, uv ) { - - const width = image.width; - const height = image.height; - - let x = Math.round( uv.x * width ) % width; - let y = Math.round( uv.y * height ) % height; - - if ( x < 0 ) x += width; - if ( y < 0 ) y += height; - - const index = y * width + x; - - return image.data[ index * 4 + 3 ]; - - } - - if ( texture.isCompressedTexture === true ) { - - if ( NON_ALPHA_CHANNEL_FORMATS.includes( texture.format ) ) { - - map.transparent = false; - - } else { - - // any other way to check transparency of CompressedTexture? - map.transparent = true; - - } - - return; - - } - - const imageData = texture.image.data !== undefined - ? texture.image - : createImageData( texture.image ); - - const group = geometry.groups[ groupIndex ]; - - if ( detectImageTransparency( - imageData, - geometry.attributes.uv.array, - geometry.index.array.slice( group.start, group.start + group.count ) ) ) { - - map.transparent = true; - - } - - } ); - - } - -} - -// - -class AnimationBuilder { - - /** - * @param {Object} vmd - parsed VMD data - * @param {SkinnedMesh} mesh - tracks will be fitting to mesh - * @return {AnimationClip} - */ - build( vmd, mesh ) { - - // combine skeletal and morph animations - - const tracks = this.buildSkeletalAnimation( vmd, mesh ).tracks; - const tracks2 = this.buildMorphAnimation( vmd, mesh ).tracks; - - for ( let i = 0, il = tracks2.length; i < il; i ++ ) { - - tracks.push( tracks2[ i ] ); - - } - - return new AnimationClip( '', - 1, tracks ); - - } - - /** - * @param {Object} vmd - parsed VMD data - * @param {SkinnedMesh} mesh - tracks will be fitting to mesh - * @return {AnimationClip} - */ - buildSkeletalAnimation( vmd, mesh ) { - - function pushInterpolation( array, interpolation, index ) { - - array.push( interpolation[ index + 0 ] / 127 ); // x1 - array.push( interpolation[ index + 8 ] / 127 ); // x2 - array.push( interpolation[ index + 4 ] / 127 ); // y1 - array.push( interpolation[ index + 12 ] / 127 ); // y2 - - } - - const tracks = []; - - const motions = {}; - const bones = mesh.skeleton.bones; - const boneNameDictionary = {}; - - for ( let i = 0, il = bones.length; i < il; i ++ ) { - - boneNameDictionary[ bones[ i ].name ] = true; - - } - - for ( let i = 0; i < vmd.metadata.motionCount; i ++ ) { - - const motion = vmd.motions[ i ]; - const boneName = motion.boneName; - - if ( boneNameDictionary[ boneName ] === undefined ) continue; - - motions[ boneName ] = motions[ boneName ] || []; - motions[ boneName ].push( motion ); - - } - - for ( const key in motions ) { - - const array = motions[ key ]; - - array.sort( function ( a, b ) { - - return a.frameNum - b.frameNum; - - } ); - - const times = []; - const positions = []; - const rotations = []; - const pInterpolations = []; - const rInterpolations = []; - - const basePosition = mesh.skeleton.getBoneByName( key ).position.toArray(); - - for ( let i = 0, il = array.length; i < il; i ++ ) { - - const time = array[ i ].frameNum / 30; - const position = array[ i ].position; - const rotation = array[ i ].rotation; - const interpolation = array[ i ].interpolation; - - times.push( time ); - - for ( let j = 0; j < 3; j ++ ) positions.push( basePosition[ j ] + position[ j ] ); - for ( let j = 0; j < 4; j ++ ) rotations.push( rotation[ j ] ); - for ( let j = 0; j < 3; j ++ ) pushInterpolation( pInterpolations, interpolation, j ); - - pushInterpolation( rInterpolations, interpolation, 3 ); - - } - - const targetName = '.bones[' + key + ']'; - - tracks.push( this._createTrack( targetName + '.position', VectorKeyframeTrack, times, positions, pInterpolations ) ); - tracks.push( this._createTrack( targetName + '.quaternion', QuaternionKeyframeTrack, times, rotations, rInterpolations ) ); - - } - - return new AnimationClip( '', - 1, tracks ); - - } - - /** - * @param {Object} vmd - parsed VMD data - * @param {SkinnedMesh} mesh - tracks will be fitting to mesh - * @return {AnimationClip} - */ - buildMorphAnimation( vmd, mesh ) { - - const tracks = []; - - const morphs = {}; - const morphTargetDictionary = mesh.morphTargetDictionary; - - for ( let i = 0; i < vmd.metadata.morphCount; i ++ ) { - - const morph = vmd.morphs[ i ]; - const morphName = morph.morphName; - - if ( morphTargetDictionary[ morphName ] === undefined ) continue; - - morphs[ morphName ] = morphs[ morphName ] || []; - morphs[ morphName ].push( morph ); - - } - - for ( const key in morphs ) { - - const array = morphs[ key ]; - - array.sort( function ( a, b ) { - - return a.frameNum - b.frameNum; - - } ); - - const times = []; - const values = []; - - for ( let i = 0, il = array.length; i < il; i ++ ) { - - times.push( array[ i ].frameNum / 30 ); - values.push( array[ i ].weight ); - - } - - tracks.push( new NumberKeyframeTrack( '.morphTargetInfluences[' + morphTargetDictionary[ key ] + ']', times, values ) ); - - } - - return new AnimationClip( '', - 1, tracks ); - - } - - /** - * @param {Object} vmd - parsed VMD data - * @return {AnimationClip} - */ - buildCameraAnimation( vmd ) { - - function pushVector3( array, vec ) { - - array.push( vec.x ); - array.push( vec.y ); - array.push( vec.z ); - - } - - function pushQuaternion( array, q ) { - - array.push( q.x ); - array.push( q.y ); - array.push( q.z ); - array.push( q.w ); - - } - - function pushInterpolation( array, interpolation, index ) { - - array.push( interpolation[ index * 4 + 0 ] / 127 ); // x1 - array.push( interpolation[ index * 4 + 1 ] / 127 ); // x2 - array.push( interpolation[ index * 4 + 2 ] / 127 ); // y1 - array.push( interpolation[ index * 4 + 3 ] / 127 ); // y2 - - } - - const cameras = vmd.cameras === undefined ? [] : vmd.cameras.slice(); - - cameras.sort( function ( a, b ) { - - return a.frameNum - b.frameNum; - - } ); - - const times = []; - const centers = []; - const quaternions = []; - const positions = []; - const fovs = []; - - const cInterpolations = []; - const qInterpolations = []; - const pInterpolations = []; - const fInterpolations = []; - - const quaternion = new Quaternion(); - const euler = new Euler(); - const position = new Vector3(); - const center = new Vector3(); - - for ( let i = 0, il = cameras.length; i < il; i ++ ) { - - const motion = cameras[ i ]; - - const time = motion.frameNum / 30; - const pos = motion.position; - const rot = motion.rotation; - const distance = motion.distance; - const fov = motion.fov; - const interpolation = motion.interpolation; - - times.push( time ); - - position.set( 0, 0, - distance ); - center.set( pos[ 0 ], pos[ 1 ], pos[ 2 ] ); - - euler.set( - rot[ 0 ], - rot[ 1 ], - rot[ 2 ] ); - quaternion.setFromEuler( euler ); - - position.add( center ); - position.applyQuaternion( quaternion ); - - pushVector3( centers, center ); - pushQuaternion( quaternions, quaternion ); - pushVector3( positions, position ); - - fovs.push( fov ); - - for ( let j = 0; j < 3; j ++ ) { - - pushInterpolation( cInterpolations, interpolation, j ); - - } - - pushInterpolation( qInterpolations, interpolation, 3 ); - - // use the same parameter for x, y, z axis. - for ( let j = 0; j < 3; j ++ ) { - - pushInterpolation( pInterpolations, interpolation, 4 ); - - } - - pushInterpolation( fInterpolations, interpolation, 5 ); - - } - - const tracks = []; - - // I expect an object whose name 'target' exists under THREE.Camera - tracks.push( this._createTrack( 'target.position', VectorKeyframeTrack, times, centers, cInterpolations ) ); - - tracks.push( this._createTrack( '.quaternion', QuaternionKeyframeTrack, times, quaternions, qInterpolations ) ); - tracks.push( this._createTrack( '.position', VectorKeyframeTrack, times, positions, pInterpolations ) ); - tracks.push( this._createTrack( '.fov', NumberKeyframeTrack, times, fovs, fInterpolations ) ); - - return new AnimationClip( '', - 1, tracks ); - - } - - // private method - - _createTrack( node, typedKeyframeTrack, times, values, interpolations ) { - - /* - * optimizes here not to let KeyframeTrackPrototype optimize - * because KeyframeTrackPrototype optimizes times and values but - * doesn't optimize interpolations. - */ - if ( times.length > 2 ) { - - times = times.slice(); - values = values.slice(); - interpolations = interpolations.slice(); - - const stride = values.length / times.length; - const interpolateStride = interpolations.length / times.length; - - let index = 1; - - for ( let aheadIndex = 2, endIndex = times.length; aheadIndex < endIndex; aheadIndex ++ ) { - - for ( let i = 0; i < stride; i ++ ) { - - if ( values[ index * stride + i ] !== values[ ( index - 1 ) * stride + i ] || - values[ index * stride + i ] !== values[ aheadIndex * stride + i ] ) { - - index ++; - break; - - } - - } - - if ( aheadIndex > index ) { - - times[ index ] = times[ aheadIndex ]; - - for ( let i = 0; i < stride; i ++ ) { - - values[ index * stride + i ] = values[ aheadIndex * stride + i ]; - - } - - for ( let i = 0; i < interpolateStride; i ++ ) { - - interpolations[ index * interpolateStride + i ] = interpolations[ aheadIndex * interpolateStride + i ]; - - } - - } - - } - - times.length = index + 1; - values.length = ( index + 1 ) * stride; - interpolations.length = ( index + 1 ) * interpolateStride; - - } - - const track = new typedKeyframeTrack( node, times, values ); - - track.createInterpolant = function InterpolantFactoryMethodCubicBezier( result ) { - - return new CubicBezierInterpolation( this.times, this.values, this.getValueSize(), result, new Float32Array( interpolations ) ); - - }; - - return track; - - } - -} - -// interpolation - -class CubicBezierInterpolation extends Interpolant { - - constructor( parameterPositions, sampleValues, sampleSize, resultBuffer, params ) { - - super( parameterPositions, sampleValues, sampleSize, resultBuffer ); - - this.interpolationParams = params; - - } - - interpolate_( i1, t0, t, t1 ) { - - const result = this.resultBuffer; - const values = this.sampleValues; - const stride = this.valueSize; - const params = this.interpolationParams; - - const offset1 = i1 * stride; - const offset0 = offset1 - stride; - - // No interpolation if next key frame is in one frame in 30fps. - // This is from MMD animation spec. - // '1.5' is for precision loss. times are Float32 in Three.js Animation system. - const weight1 = ( ( t1 - t0 ) < 1 / 30 * 1.5 ) ? 0.0 : ( t - t0 ) / ( t1 - t0 ); - - if ( stride === 4 ) { // Quaternion - - const x1 = params[ i1 * 4 + 0 ]; - const x2 = params[ i1 * 4 + 1 ]; - const y1 = params[ i1 * 4 + 2 ]; - const y2 = params[ i1 * 4 + 3 ]; - - const ratio = this._calculate( x1, x2, y1, y2, weight1 ); - - Quaternion.slerpFlat( result, 0, values, offset0, values, offset1, ratio ); - - } else if ( stride === 3 ) { // Vector3 - - for ( let i = 0; i !== stride; ++ i ) { - - const x1 = params[ i1 * 12 + i * 4 + 0 ]; - const x2 = params[ i1 * 12 + i * 4 + 1 ]; - const y1 = params[ i1 * 12 + i * 4 + 2 ]; - const y2 = params[ i1 * 12 + i * 4 + 3 ]; - - const ratio = this._calculate( x1, x2, y1, y2, weight1 ); - - result[ i ] = values[ offset0 + i ] * ( 1 - ratio ) + values[ offset1 + i ] * ratio; - - } - - } else { // Number - - const x1 = params[ i1 * 4 + 0 ]; - const x2 = params[ i1 * 4 + 1 ]; - const y1 = params[ i1 * 4 + 2 ]; - const y2 = params[ i1 * 4 + 3 ]; - - const ratio = this._calculate( x1, x2, y1, y2, weight1 ); - - result[ 0 ] = values[ offset0 ] * ( 1 - ratio ) + values[ offset1 ] * ratio; - - } - - return result; - - } - - _calculate( x1, x2, y1, y2, x ) { - - /* - * Cubic Bezier curves - * https://en.wikipedia.org/wiki/B%C3%A9zier_curve#Cubic_B.C3.A9zier_curves - * - * B(t) = ( 1 - t ) ^ 3 * P0 - * + 3 * ( 1 - t ) ^ 2 * t * P1 - * + 3 * ( 1 - t ) * t^2 * P2 - * + t ^ 3 * P3 - * ( 0 <= t <= 1 ) - * - * MMD uses Cubic Bezier curves for bone and camera animation interpolation. - * http://d.hatena.ne.jp/edvakf/20111016/1318716097 - * - * x = ( 1 - t ) ^ 3 * x0 - * + 3 * ( 1 - t ) ^ 2 * t * x1 - * + 3 * ( 1 - t ) * t^2 * x2 - * + t ^ 3 * x3 - * y = ( 1 - t ) ^ 3 * y0 - * + 3 * ( 1 - t ) ^ 2 * t * y1 - * + 3 * ( 1 - t ) * t^2 * y2 - * + t ^ 3 * y3 - * ( x0 = 0, y0 = 0 ) - * ( x3 = 1, y3 = 1 ) - * ( 0 <= t, x1, x2, y1, y2 <= 1 ) - * - * Here solves this equation with Bisection method, - * https://en.wikipedia.org/wiki/Bisection_method - * gets t, and then calculate y. - * - * f(t) = 3 * ( 1 - t ) ^ 2 * t * x1 - * + 3 * ( 1 - t ) * t^2 * x2 - * + t ^ 3 - x = 0 - * - * (Another option: Newton's method - * https://en.wikipedia.org/wiki/Newton%27s_method) - */ - - let c = 0.5; - let t = c; - let s = 1.0 - t; - const loop = 15; - const eps = 1e-5; - const math = Math; - - let sst3, stt3, ttt; - - for ( let i = 0; i < loop; i ++ ) { - - sst3 = 3.0 * s * s * t; - stt3 = 3.0 * s * t * t; - ttt = t * t * t; - - const ft = ( sst3 * x1 ) + ( stt3 * x2 ) + ( ttt ) - x; - - if ( math.abs( ft ) < eps ) break; - - c /= 2.0; - - t += ( ft < 0 ) ? c : - c; - s = 1.0 - t; - - } - - return ( sst3 * y1 ) + ( stt3 * y2 ) + ttt; - - } - -} - -class MMDToonMaterial extends ShaderMaterial { - - constructor( parameters ) { - - super(); - - this.isMMDToonMaterial = true; - - this.type = 'MMDToonMaterial'; - - this._matcapCombine = AddOperation; - this.emissiveIntensity = 1.0; - this.normalMapType = TangentSpaceNormalMap; - - this.combine = MultiplyOperation; - - this.wireframeLinecap = 'round'; - this.wireframeLinejoin = 'round'; - - this.flatShading = false; - - this.lights = true; - - this.vertexShader = MMDToonShader.vertexShader; - this.fragmentShader = MMDToonShader.fragmentShader; - - this.defines = Object.assign( {}, MMDToonShader.defines ); - Object.defineProperty( this, 'matcapCombine', { - - get: function () { - - return this._matcapCombine; - - }, - - set: function ( value ) { - - this._matcapCombine = value; - - switch ( value ) { - - case MultiplyOperation: - this.defines.MATCAP_BLENDING_MULTIPLY = true; - delete this.defines.MATCAP_BLENDING_ADD; - break; - - default: - case AddOperation: - this.defines.MATCAP_BLENDING_ADD = true; - delete this.defines.MATCAP_BLENDING_MULTIPLY; - break; - - } - - }, - - } ); - - this.uniforms = UniformsUtils.clone( MMDToonShader.uniforms ); - - // merged from MeshToon/Phong/MatcapMaterial - const exposePropertyNames = [ - 'specular', - 'opacity', - 'diffuse', - - 'map', - 'matcap', - 'gradientMap', - - 'lightMap', - 'lightMapIntensity', - - 'aoMap', - 'aoMapIntensity', - - 'emissive', - 'emissiveMap', - - 'bumpMap', - 'bumpScale', - - 'normalMap', - 'normalScale', - - 'displacemantBias', - 'displacemantMap', - 'displacemantScale', - - 'specularMap', - - 'alphaMap', - - 'reflectivity', - 'refractionRatio', - ]; - for ( const propertyName of exposePropertyNames ) { - - Object.defineProperty( this, propertyName, { - - get: function () { - - return this.uniforms[ propertyName ].value; - - }, - - set: function ( value ) { - - this.uniforms[ propertyName ].value = value; - - }, - - } ); - - } - - // Special path for shininess to handle zero shininess properly - this._shininess = 30; - Object.defineProperty( this, 'shininess', { - - get: function () { - - return this._shininess; - - }, - - set: function ( value ) { - - this._shininess = value; - this.uniforms.shininess.value = Math.max( this._shininess, 1e-4 ); // To prevent pow( 0.0, 0.0 ) - - }, - - } ); - - Object.defineProperty( - this, - 'color', - Object.getOwnPropertyDescriptor( this, 'diffuse' ) - ); - - this.setValues( parameters ); - - } - - copy( source ) { - - super.copy( source ); - - this.matcapCombine = source.matcapCombine; - this.emissiveIntensity = source.emissiveIntensity; - this.normalMapType = source.normalMapType; - - this.combine = source.combine; - - this.wireframeLinecap = source.wireframeLinecap; - this.wireframeLinejoin = source.wireframeLinejoin; - - this.flatShading = source.flatShading; - - return this; - - } - -} - -export { MMDLoader }; diff --git a/examples/jsm/loaders/MTLLoader.js b/examples/jsm/loaders/MTLLoader.js index f277c973536ea4..3ce468d9ba4548 100644 --- a/examples/jsm/loaders/MTLLoader.js +++ b/examples/jsm/loaders/MTLLoader.js @@ -1,5 +1,6 @@ import { Color, + ColorManagement, DefaultLoadingManager, FileLoader, FrontSide, @@ -13,9 +14,23 @@ import { } from 'three'; /** - * Loads a Wavefront .mtl file specifying materials + * A loader for the MTL format. + * + * The Material Template Library format (MTL) or .MTL File Format is a companion file format + * to OBJ that describes surface shading (material) properties of objects within one or more + * OBJ files. + * + * ```js + * const loader = new MTLLoader(); + * const materials = await loader.loadAsync( 'models/obj/male02/male02.mtl' ); + * + * const objLoader = new OBJLoader(); + * objLoader.setMaterials( materials ); + * ``` + * + * @augments Loader + * @three_import import { MTLLoader } from 'three/addons/loaders/MTLLoader.js'; */ - class MTLLoader extends Loader { constructor( manager ) { @@ -25,17 +40,13 @@ class MTLLoader extends Loader { } /** - * Loads and parses a MTL asset from a URL. - * - * @param {String} url - URL to the MTL file. - * @param {Function} [onLoad] - Callback invoked with the loaded object. - * @param {Function} [onProgress] - Callback for download progress. - * @param {Function} [onError] - Callback for download errors. - * - * @see setPath setResourcePath + * Starts loading from the given URL and passes the loaded MTL asset + * to the `onLoad()` callback. * - * @note In order for relative texture references to resolve correctly - * you must call setResourcePath() explicitly prior to load. + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(MaterialCreator)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. */ load( url, onLoad, onProgress, onError ) { @@ -73,6 +84,12 @@ class MTLLoader extends Loader { } + /** + * Sets the material options. + * + * @param {MTLLoader~MaterialOptions} value - The material options. + * @return {MTLLoader} A reference to this loader. + */ setMaterialOptions( value ) { this.materialOptions = value; @@ -81,15 +98,11 @@ class MTLLoader extends Loader { } /** - * Parses a MTL file. - * - * @param {String} text - Content of MTL file - * @return {MaterialCreator} - * - * @see setPath setResourcePath + * Parses the given MTL data and returns the resulting material creator. * - * @note In order for relative texture references to resolve correctly - * you must call setResourcePath() explicitly prior to parse. + * @param {string} text - The raw MTL data as a string. + * @param {string} path - The URL base path. + * @return {MaterialCreator} The material creator. */ parse( text, path ) { @@ -153,18 +166,13 @@ class MTLLoader extends Loader { } /** - * Create a new MTLLoader.MaterialCreator - * @param baseUrl - Url relative to which textures are loaded - * @param options - Set of options on how to construct the materials - * side: Which side to apply the material - * FrontSide (default), THREE.BackSide, THREE.DoubleSide - * wrap: What type of wrapping to apply for textures - * RepeatWrapping (default), THREE.ClampToEdgeWrapping, THREE.MirroredRepeatWrapping - * normalizeRGB: RGBs need to be normalized to 0-1 from 0-255 - * Default: false, assumed to be already normalized - * ignoreZeroRGBs: Ignore values of RGBs (Ka,Kd,Ks) that are all 0's - * Default: false - * @constructor + * Material options of `MTLLoader`. + * + * @typedef {Object} MTLLoader~MaterialOptions + * @property {(FrontSide|BackSide|DoubleSide)} [side=FrontSide] - Which side to apply the material. + * @property {(RepeatWrapping|ClampToEdgeWrapping|MirroredRepeatWrapping)} [wrap=RepeatWrapping] - What type of wrapping to apply for textures. + * @property {boolean} [normalizeRGB=false] - Whether RGB colors should be normalized to `0-1` from `0-255`. + * @property {boolean} [ignoreZeroRGBs=false] - Ignore values of RGBs (Ka,Kd,Ks) that are all 0's. */ class MaterialCreator { @@ -384,21 +392,21 @@ class MaterialCreator { // Diffuse color (color under white light) using RGB values - params.color = new Color().fromArray( value ).convertSRGBToLinear(); + params.color = ColorManagement.colorSpaceToWorking( new Color().fromArray( value ), SRGBColorSpace ); break; case 'ks': // Specular color (color when light is reflected from shiny surface) using RGB values - params.specular = new Color().fromArray( value ).convertSRGBToLinear(); + params.specular = ColorManagement.colorSpaceToWorking( new Color().fromArray( value ), SRGBColorSpace ); break; case 'ke': // Emissive using RGB values - params.emissive = new Color().fromArray( value ).convertSRGBToLinear(); + params.emissive = ColorManagement.colorSpaceToWorking( new Color().fromArray( value ), SRGBColorSpace ); break; @@ -441,6 +449,14 @@ class MaterialCreator { break; + case 'disp': + + // Displacement texture map + + setMapForType( 'displacementMap', value ); + + break; + case 'map_d': // Alpha map @@ -518,6 +534,16 @@ class MaterialCreator { } + pos = items.indexOf( '-mm' ); + + if ( pos >= 0 ) { + + matParams.displacementBias = parseFloat( items[ pos + 1 ] ); + matParams.displacementScale = parseFloat( items[ pos + 2 ] ); + items.splice( pos, 3 ); + + } + pos = items.indexOf( '-s' ); if ( pos >= 0 ) { diff --git a/examples/jsm/loaders/MaterialXLoader.js b/examples/jsm/loaders/MaterialXLoader.js index a7a928c6199c12..80ac849e2ec92a 100644 --- a/examples/jsm/loaders/MaterialXLoader.js +++ b/examples/jsm/loaders/MaterialXLoader.js @@ -1,33 +1,29 @@ -import { - FileLoader, - Loader, - TextureLoader, - RepeatWrapping -} from 'three'; +import { FileLoader, Loader, TextureLoader, RepeatWrapping, MeshBasicNodeMaterial, MeshPhysicalNodeMaterial } from 'three/webgpu'; import { - MeshPhysicalNodeMaterial, float, bool, int, vec2, vec3, vec4, color, texture, - positionLocal, + positionLocal, positionWorld, uv, vertexColor, + normalLocal, normalWorld, tangentLocal, tangentWorld, add, sub, mul, div, mod, abs, sign, floor, ceil, round, pow, sin, cos, tan, asin, acos, atan2, sqrt, exp, clamp, min, max, normalize, length, dot, cross, normalMap, remap, smoothstep, luminance, mx_rgbtohsv, mx_hsvtorgb, - mix, + mix, split, mx_ramplr, mx_ramptb, mx_splitlr, mx_splittb, mx_fractal_noise_float, mx_noise_float, mx_cell_noise_float, mx_worley_noise_float, mx_transform_uv, mx_safepower, mx_contrast, mx_srgb_texture_to_lin_rec709, - saturation -} from 'three/nodes'; + saturation, + timerLocal, frameId +} from 'three/tsl'; const colorSpaceLib = { mx_srgb_texture_to_lin_rec709 }; -class MtlXElement { +class MXElement { - constructor( name, nodeFunc, params = null ) { + constructor( name, nodeFunc, params = [] ) { this.name = name; this.nodeFunc = nodeFunc; @@ -39,41 +35,56 @@ class MtlXElement { // Ref: https://github.com/mrdoob/three.js/issues/24674 -const MtlXElements = [ +const mx_add = ( in1, in2 = float( 0 ) ) => add( in1, in2 ); +const mx_subtract = ( in1, in2 = float( 0 ) ) => sub( in1, in2 ); +const mx_multiply = ( in1, in2 = float( 1 ) ) => mul( in1, in2 ); +const mx_divide = ( in1, in2 = float( 1 ) ) => div( in1, in2 ); +const mx_modulo = ( in1, in2 = float( 1 ) ) => mod( in1, in2 ); +const mx_power = ( in1, in2 = float( 1 ) ) => pow( in1, in2 ); +const mx_atan2 = ( in1 = float( 0 ), in2 = float( 1 ) ) => atan2( in1, in2 ); +const mx_timer = () => timerLocal(); +const mx_frame = () => frameId; +const mx_invert = ( in1, amount = float( 1 ) ) => sub( amount, in1 ); + +const separate = ( in1, channel ) => split( in1, channel.at( - 1 ) ); +const extract = ( in1, index ) => in1.element( index ); + +const MXElements = [ // << Math >> - new MtlXElement( 'add', add, [ 'in1', 'in2' ] ), - new MtlXElement( 'subtract', sub, [ 'in1', 'in2' ] ), - new MtlXElement( 'multiply', mul, [ 'in1', 'in2' ] ), - new MtlXElement( 'divide', div, [ 'in1', 'in2' ] ), - new MtlXElement( 'modulo', mod, [ 'in1', 'in2' ] ), - new MtlXElement( 'absval', abs, [ 'in1', 'in2' ] ), - new MtlXElement( 'sign', sign, [ 'in1', 'in2' ] ), - new MtlXElement( 'floor', floor, [ 'in1', 'in2' ] ), - new MtlXElement( 'ceil', ceil, [ 'in1', 'in2' ] ), - new MtlXElement( 'round', round, [ 'in1', 'in2' ] ), - new MtlXElement( 'power', pow, [ 'in1', 'in2' ] ), - new MtlXElement( 'sin', sin, [ 'in' ] ), - new MtlXElement( 'cos', cos, [ 'in' ] ), - new MtlXElement( 'tan', tan, [ 'in' ] ), - new MtlXElement( 'asin', asin, [ 'in' ] ), - new MtlXElement( 'acos', acos, [ 'in' ] ), - new MtlXElement( 'atan2', atan2, [ 'in1', 'in2' ] ), - new MtlXElement( 'sqrt', sqrt, [ 'in' ] ), + new MXElement( 'add', mx_add, [ 'in1', 'in2' ] ), + new MXElement( 'subtract', mx_subtract, [ 'in1', 'in2' ] ), + new MXElement( 'multiply', mx_multiply, [ 'in1', 'in2' ] ), + new MXElement( 'divide', mx_divide, [ 'in1', 'in2' ] ), + new MXElement( 'modulo', mx_modulo, [ 'in1', 'in2' ] ), + new MXElement( 'absval', abs, [ 'in1', 'in2' ] ), + new MXElement( 'sign', sign, [ 'in1', 'in2' ] ), + new MXElement( 'floor', floor, [ 'in1', 'in2' ] ), + new MXElement( 'ceil', ceil, [ 'in1', 'in2' ] ), + new MXElement( 'round', round, [ 'in1', 'in2' ] ), + new MXElement( 'power', mx_power, [ 'in1', 'in2' ] ), + new MXElement( 'sin', sin, [ 'in' ] ), + new MXElement( 'cos', cos, [ 'in' ] ), + new MXElement( 'tan', tan, [ 'in' ] ), + new MXElement( 'asin', asin, [ 'in' ] ), + new MXElement( 'acos', acos, [ 'in' ] ), + new MXElement( 'atan2', mx_atan2, [ 'in1', 'in2' ] ), + new MXElement( 'sqrt', sqrt, [ 'in' ] ), //new MtlXElement( 'ln', ... ), - new MtlXElement( 'exp', exp, [ 'in' ] ), - new MtlXElement( 'clamp', clamp, [ 'in', 'low', 'high' ] ), - new MtlXElement( 'min', min, [ 'in1', 'in2' ] ), - new MtlXElement( 'max', max, [ 'in1', 'in2' ] ), - new MtlXElement( 'normalize', normalize, [ 'in' ] ), - new MtlXElement( 'magnitude', length, [ 'in1', 'in2' ] ), - new MtlXElement( 'dotproduct', dot, [ 'in1', 'in2' ] ), - new MtlXElement( 'crossproduct', cross, [ 'in' ] ), + new MXElement( 'exp', exp, [ 'in' ] ), + new MXElement( 'clamp', clamp, [ 'in', 'low', 'high' ] ), + new MXElement( 'min', min, [ 'in1', 'in2' ] ), + new MXElement( 'max', max, [ 'in1', 'in2' ] ), + new MXElement( 'normalize', normalize, [ 'in' ] ), + new MXElement( 'magnitude', length, [ 'in1', 'in2' ] ), + new MXElement( 'dotproduct', dot, [ 'in1', 'in2' ] ), + new MXElement( 'crossproduct', cross, [ 'in' ] ), + new MXElement( 'invert', mx_invert, [ 'in', 'amount' ] ), //new MtlXElement( 'transformpoint', ... ), //new MtlXElement( 'transformvector', ... ), //new MtlXElement( 'transformnormal', ... ), //new MtlXElement( 'transformmatrix', ... ), - new MtlXElement( 'normalmap', normalMap, [ 'in', 'scale' ] ), + new MXElement( 'normalmap', normalMap, [ 'in', 'scale' ] ), //new MtlXElement( 'transpose', ... ), //new MtlXElement( 'determinant', ... ), //new MtlXElement( 'invertmatrix', ... ), @@ -83,64 +94,109 @@ const MtlXElements = [ //new MtlXElement( 'dot', ... ), // << Adjustment >> - new MtlXElement( 'remap', remap, [ 'in', 'inlow', 'inhigh', 'outlow', 'outhigh' ] ), - new MtlXElement( 'smoothstep', smoothstep, [ 'in', 'low', 'high' ] ), + new MXElement( 'remap', remap, [ 'in', 'inlow', 'inhigh', 'outlow', 'outhigh' ] ), + new MXElement( 'smoothstep', smoothstep, [ 'in', 'low', 'high' ] ), //new MtlXElement( 'curveadjust', ... ), //new MtlXElement( 'curvelookup', ... ), - new MtlXElement( 'luminance', luminance, [ 'in', 'lumacoeffs' ] ), - new MtlXElement( 'rgbtohsv', mx_rgbtohsv, [ 'in' ] ), - new MtlXElement( 'hsvtorgb', mx_hsvtorgb, [ 'in' ] ), + new MXElement( 'luminance', luminance, [ 'in', 'lumacoeffs' ] ), + new MXElement( 'rgbtohsv', mx_rgbtohsv, [ 'in' ] ), + new MXElement( 'hsvtorgb', mx_hsvtorgb, [ 'in' ] ), // << Mix >> - new MtlXElement( 'mix', mix, [ 'bg', 'fg', 'mix' ] ), + new MXElement( 'mix', mix, [ 'bg', 'fg', 'mix' ] ), // << Channel >> - new MtlXElement( 'combine2', vec2, [ 'in1', 'in2' ] ), - new MtlXElement( 'combine3', vec3, [ 'in1', 'in2', 'in3' ] ), - new MtlXElement( 'combine4', vec4, [ 'in1', 'in2', 'in3', 'in4' ] ), + new MXElement( 'combine2', vec2, [ 'in1', 'in2' ] ), + new MXElement( 'combine3', vec3, [ 'in1', 'in2', 'in3' ] ), + new MXElement( 'combine4', vec4, [ 'in1', 'in2', 'in3', 'in4' ] ), // << Procedural >> - new MtlXElement( 'ramplr', mx_ramplr, [ 'valuel', 'valuer', 'texcoord' ] ), - new MtlXElement( 'ramptb', mx_ramptb, [ 'valuet', 'valueb', 'texcoord' ] ), - new MtlXElement( 'splitlr', mx_splitlr, [ 'valuel', 'valuer', 'texcoord' ] ), - new MtlXElement( 'splittb', mx_splittb, [ 'valuet', 'valueb', 'texcoord' ] ), - new MtlXElement( 'noise2d', mx_noise_float, [ 'texcoord', 'amplitude', 'pivot' ] ), - new MtlXElement( 'noise3d', mx_noise_float, [ 'texcoord', 'amplitude', 'pivot' ] ), - new MtlXElement( 'fractal3d', mx_fractal_noise_float, [ 'position', 'octaves', 'lacunarity', 'diminish', 'amplitude' ] ), - new MtlXElement( 'cellnoise2d', mx_cell_noise_float, [ 'texcoord' ] ), - new MtlXElement( 'cellnoise3d', mx_cell_noise_float, [ 'texcoord' ] ), - new MtlXElement( 'worleynoise2d', mx_worley_noise_float, [ 'texcoord', 'jitter' ] ), - new MtlXElement( 'worleynoise3d', mx_worley_noise_float, [ 'texcoord', 'jitter' ] ), + new MXElement( 'ramplr', mx_ramplr, [ 'valuel', 'valuer', 'texcoord' ] ), + new MXElement( 'ramptb', mx_ramptb, [ 'valuet', 'valueb', 'texcoord' ] ), + new MXElement( 'splitlr', mx_splitlr, [ 'valuel', 'valuer', 'texcoord' ] ), + new MXElement( 'splittb', mx_splittb, [ 'valuet', 'valueb', 'texcoord' ] ), + new MXElement( 'noise2d', mx_noise_float, [ 'texcoord', 'amplitude', 'pivot' ] ), + new MXElement( 'noise3d', mx_noise_float, [ 'texcoord', 'amplitude', 'pivot' ] ), + new MXElement( 'fractal3d', mx_fractal_noise_float, [ 'position', 'octaves', 'lacunarity', 'diminish', 'amplitude' ] ), + new MXElement( 'cellnoise2d', mx_cell_noise_float, [ 'texcoord' ] ), + new MXElement( 'cellnoise3d', mx_cell_noise_float, [ 'texcoord' ] ), + new MXElement( 'worleynoise2d', mx_worley_noise_float, [ 'texcoord', 'jitter' ] ), + new MXElement( 'worleynoise3d', mx_worley_noise_float, [ 'texcoord', 'jitter' ] ), // << Supplemental >> //new MtlXElement( 'tiledimage', ... ), //new MtlXElement( 'triplanarprojection', triplanarTextures, [ 'filex', 'filey', 'filez' ] ), //new MtlXElement( 'ramp4', ... ), //new MtlXElement( 'place2d', mx_place2d, [ 'texcoord', 'pivot', 'scale', 'rotate', 'offset' ] ), - new MtlXElement( 'safepower', mx_safepower, [ 'in1', 'in2' ] ), - new MtlXElement( 'contrast', mx_contrast, [ 'in', 'amount', 'pivot' ] ), + new MXElement( 'safepower', mx_safepower, [ 'in1', 'in2' ] ), + new MXElement( 'contrast', mx_contrast, [ 'in', 'amount', 'pivot' ] ), //new MtlXElement( 'hsvadjust', ... ), - new MtlXElement( 'saturate', saturation, [ 'in', 'amount' ] ), - //new MtlXElement( 'extract', ... ), - //new MtlXElement( 'separate2', ... ), - //new MtlXElement( 'separate3', ... ), - //new MtlXElement( 'separate4', ... ) + new MXElement( 'saturate', saturation, [ 'in', 'amount' ] ), + new MXElement( 'extract', extract, [ 'in', 'index' ] ), + new MXElement( 'separate2', separate, [ 'in' ] ), + new MXElement( 'separate3', separate, [ 'in' ] ), + new MXElement( 'separate4', separate, [ 'in' ] ), + + new MXElement( 'time', mx_timer ), + new MXElement( 'frame', mx_frame ) ]; const MtlXLibrary = {}; -MtlXElements.forEach( element => MtlXLibrary[ element.name ] = element ); - +MXElements.forEach( element => MtlXLibrary[ element.name ] = element ); + +/** + * A loader for the MaterialX format. + * + * The node materials loaded with this loader can only be used with {@link WebGPURenderer}. + * + * ```js + * const loader = new MaterialXLoader().setPath( SAMPLE_PATH ); + * const materials = await loader.loadAsync( 'standard_surface_brass_tiled.mtlx' ); + * ``` + * + * @augments Loader + * @three_import import { MaterialXLoader } from 'three/addons/loaders/MaterialXLoader.js'; + */ class MaterialXLoader extends Loader { + /** + * Constructs a new MaterialX loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); } + /** + * Starts loading from the given URL and passes the loaded MaterialX asset + * to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(Object)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + * @return {MaterialXLoader} A reference to this loader. + */ load( url, onLoad, onProgress, onError ) { + const _onError = function ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + }; + new FileLoader( this.manager ) .setPath( this.path ) .load( url, async ( text ) => { @@ -151,16 +207,22 @@ class MaterialXLoader extends Loader { } catch ( e ) { - onError( e ); + _onError( e ); } - }, onProgress, onError ); + }, onProgress, _onError ); return this; } + /** + * Parses the given MaterialX data and returns the resulting materials. + * + * @param {string} text - The raw MaterialX data as a string. + * @return {Object} A dictionary holding the parse node materials. + */ parse( text ) { return new MaterialX( this.manager, this.path ).parse( text ); @@ -312,7 +374,17 @@ class MaterialXNode { const filePrefix = this.getRecursiveAttribute( 'fileprefix' ) || ''; - const texture = this.materialX.textureLoader.load( filePrefix + this.value ); + let loader = this.materialX.textureLoader; + const uri = filePrefix + this.value; + + if ( uri ) { + + const handler = this.materialX.manager.getHandler( uri ); + if ( handler !== null ) loader = handler; + + } + + const texture = loader.load( uri ); texture.wrapS = texture.wrapT = RepeatWrapping; texture.flipY = false; @@ -336,11 +408,11 @@ class MaterialXNode { } - getNode() { + getNode( out = null ) { let node = this.node; - if ( node !== null ) { + if ( node !== null && out === null ) { return node; @@ -358,7 +430,13 @@ class MaterialXNode { } else if ( this.hasReference ) { - node = this.materialX.getMaterialXNode( this.referencePath ).getNode(); + if ( this.element === 'output' && this.output && out === null ) { + + out = this.output; + + } + + node = this.materialX.getMaterialXNode( this.referencePath ).getNode( out ); } else { @@ -376,7 +454,32 @@ class MaterialXNode { } else if ( element === 'position' ) { - node = positionLocal; + const space = this.getAttribute( 'space' ); + node = space === 'world' ? positionWorld : positionLocal; + + } else if ( element === 'normal' ) { + + const space = this.getAttribute( 'space' ); + node = space === 'world' ? normalWorld : normalLocal; + + } else if ( element === 'tangent' ) { + + const space = this.getAttribute( 'space' ); + node = space === 'world' ? tangentWorld : tangentLocal; + + } else if ( element === 'texcoord' ) { + + const indexNode = this.getChildByName( 'index' ); + const index = indexNode ? parseInt( indexNode.value ) : 0; + + node = uv( index ); + + } else if ( element === 'geomcolor' ) { + + const indexNode = this.getChildByName( 'index' ); + const index = indexNode ? parseInt( indexNode.value ) : 0; + + node = vertexColor( index ); } else if ( element === 'tiledimage' ) { @@ -416,7 +519,15 @@ class MaterialXNode { const nodeElement = MtlXLibrary[ element ]; - node = nodeElement.nodeFunc( ...this.getNodesByNames( ...nodeElement.params ) ); + if ( out !== null ) { + + node = nodeElement.nodeFunc( ...this.getNodesByNames( ...nodeElement.params ), out ); + + } else { + + node = nodeElement.nodeFunc( ...this.getNodesByNames( ...nodeElement.params ) ); + + } } @@ -484,7 +595,7 @@ class MaterialXNode { const child = this.getChildByName( name ); - return child ? child.getNode() : undefined; + return child ? child.getNode( child.output ) : undefined; } @@ -588,11 +699,30 @@ class MaterialXNode { // + let normalNode = null; + + if ( inputs.normal ) normalNode = inputs.normal; + + // + + let emissiveNode = null; + + if ( inputs.emission ) emissiveNode = inputs.emission; + if ( inputs.emissionColor ) { + + emissiveNode = emissiveNode ? mul( emissiveNode, inputs.emissionColor ) : emissiveNode; + + } + + // + material.colorNode = colorNode || color( 0.8, 0.8, 0.8 ); material.roughnessNode = roughnessNode || float( 0.2 ); material.metalnessNode = metalnessNode || float( 0 ); material.clearcoatNode = clearcoatNode || float( 0 ); material.clearcoatRoughnessNode = clearcoatRoughnessNode || float( 0 ); + if ( normalNode ) material.normalNode = normalNode; + if ( emissiveNode ) material.emissiveNode = emissiveNode; } @@ -620,7 +750,28 @@ class MaterialXNode { } - toMaterial() { + toBasicMaterial() { + + const material = new MeshBasicNodeMaterial(); + material.name = this.name; + + for ( const nodeX of this.children.toReversed() ) { + + if ( nodeX.name === 'out' ) { + + material.colorNode = nodeX.getNode(); + + break; + + } + + } + + return material; + + } + + toPhysicalMaterial() { const material = new MeshPhysicalNodeMaterial(); material.name = this.name; @@ -640,14 +791,34 @@ class MaterialXNode { const materials = {}; + let isUnlit = true; + for ( const nodeX of this.children ) { if ( nodeX.element === 'surfacematerial' ) { - const material = nodeX.toMaterial(); + const material = nodeX.toPhysicalMaterial(); materials[ material.name ] = material; + isUnlit = false; + + } + + } + + if ( isUnlit ) { + + for ( const nodeX of this.children ) { + + if ( nodeX.element === 'nodegraph' ) { + + const material = nodeX.toBasicMaterial(); + + materials[ material.name ] = material; + + } + } } diff --git a/examples/jsm/loaders/NRRDLoader.js b/examples/jsm/loaders/NRRDLoader.js index 0bbd43b0c96492..9b56761885ef18 100644 --- a/examples/jsm/loaders/NRRDLoader.js +++ b/examples/jsm/loaders/NRRDLoader.js @@ -7,14 +7,39 @@ import { import * as fflate from '../libs/fflate.module.js'; import { Volume } from '../misc/Volume.js'; +/** + * A loader for the NRRD format. + * + * ```js + * const loader = new NRRDLoader(); + * const volume = await loader.loadAsync( 'models/nrrd/I.nrrd' ); + * ``` + * + * @augments Loader + * @three_import import { NRRDLoader } from 'three/addons/loaders/NRRDLoader.js'; + */ class NRRDLoader extends Loader { + /** + * Constructs a new NRRD loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); } + /** + * Starts loading from the given URL and passes the loaded NRRD asset + * to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(Volume)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ load( url, onLoad, onProgress, onError ) { const scope = this; @@ -51,15 +76,22 @@ class NRRDLoader extends Loader { } /** + * Toggles the segmentation mode. * - * @param {boolean} segmentation is a option for user to choose - */ + * @param {boolean} segmentation - Whether to use segmentation mode or not. + */ setSegmentation( segmentation ) { - this.segmentation = segmentation; + this.segmentation = segmentation; } + /** + * Parses the given NRRD data and returns the resulting volume data. + * + * @param {ArrayBuffer} data - The raw NRRD data as an array buffer. + * @return {Volume} The parsed volume. + */ parse( data ) { // this parser is largely inspired from the XTK NRRD parser : https://github.com/xtk/X @@ -76,12 +108,6 @@ class NRRDLoader extends Loader { function scan( type, chunks ) { - if ( chunks === undefined || chunks === null ) { - - chunks = 1; - - } - let _chunkSize = 1; let _array_type = Uint8Array; @@ -138,13 +164,6 @@ class NRRDLoader extends Loader { } - if ( chunks == 1 ) { - - // if only one chunk was requested, just return one value - return _bytes[ 0 ]; - - } - // return the byte array return _bytes; @@ -245,7 +264,7 @@ class NRRDLoader extends Loader { } - //parse the data when registred as one of this type : 'text', 'ascii', 'txt' + //parse the data when registered as one of this type : 'text', 'ascii', 'txt' function parseDataAsText( data, start, end ) { let number = ''; @@ -320,7 +339,7 @@ class NRRDLoader extends Loader { // we found two line breaks in a row // now we know what the header is - _header = this.parseChars( _bytes, 0, i - 2 ); + _header = this._parseChars( _bytes, 0, i - 2 ); // this is were the data starts _data_start = i + 1; break; @@ -473,10 +492,10 @@ class NRRDLoader extends Loader { volume.inverseMatrix = new Matrix4(); volume.inverseMatrix.copy( volume.matrix ).invert(); - + volume.RASDimensions = [ - Math.floor( volume.xLength * spacingX ), - Math.floor( volume.yLength * spacingY ), + Math.floor( volume.xLength * spacingX ), + Math.floor( volume.yLength * spacingY ), Math.floor( volume.zLength * spacingZ ) ]; @@ -498,7 +517,7 @@ class NRRDLoader extends Loader { } - parseChars( array, start, end ) { + _parseChars( array, start, end ) { // without borders, use the whole array if ( start === undefined ) { diff --git a/examples/jsm/loaders/OBJLoader.js b/examples/jsm/loaders/OBJLoader.js index 7792458abce807..4e38f046a3a362 100644 --- a/examples/jsm/loaders/OBJLoader.js +++ b/examples/jsm/loaders/OBJLoader.js @@ -12,7 +12,8 @@ import { Points, PointsMaterial, Vector3, - Color + Color, + SRGBColorSpace } from 'three'; // o object_name | g group_name @@ -431,18 +432,54 @@ function ParserState() { } -// +/** + * A loader for the OBJ format. + * + * The [OBJ format]{@link https://en.wikipedia.org/wiki/Wavefront_.obj_file} is a simple data-format that + * represents 3D geometry in a human readable format as the position of each vertex, the UV position of + * each texture coordinate vertex, vertex normals, and the faces that make each polygon defined as a list + * of vertices, and texture vertices. + * + * ```js + * const loader = new OBJLoader(); + * const object = await loader.loadAsync( 'models/monster.obj' ); + * scene.add( object ); + * ``` + * + * @augments Loader + * @three_import import { OBJLoader } from 'three/addons/loaders/OBJLoader.js'; + */ class OBJLoader extends Loader { + /** + * Constructs a new OBJ loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); + /** + * A reference to a material creator. + * + * @type {?MaterialCreator} + * @default null + */ this.materials = null; } + /** + * Starts loading from the given URL and passes the loaded OBJ asset + * to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(Group)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ load( url, onLoad, onProgress, onError ) { const scope = this; @@ -477,6 +514,12 @@ class OBJLoader extends Loader { } + /** + * Sets the material creator for this OBJ. This object is loaded via {@link MTLLoader}. + * + * @param {MaterialCreator} materials - An object that creates the materials for this OBJ. + * @return {OBJLoader} A reference to this loader. + */ setMaterials( materials ) { this.materials = materials; @@ -485,6 +528,12 @@ class OBJLoader extends Loader { } + /** + * Parses the given OBJ data and returns the resulting group. + * + * @param {string} text - The raw OBJ data as a string. + * @return {Group} The parsed OBJ. + */ parse( text ) { const state = new ParserState(); @@ -534,8 +583,9 @@ class OBJLoader extends Loader { _color.setRGB( parseFloat( data[ 4 ] ), parseFloat( data[ 5 ] ), - parseFloat( data[ 6 ] ) - ).convertSRGBToLinear(); + parseFloat( data[ 6 ] ), + SRGBColorSpace + ); state.colors.push( _color.r, _color.g, _color.b ); diff --git a/examples/jsm/loaders/PCDLoader.js b/examples/jsm/loaders/PCDLoader.js index 39b282fa5767e0..3d88d11cdcc4ae 100644 --- a/examples/jsm/loaders/PCDLoader.js +++ b/examples/jsm/loaders/PCDLoader.js @@ -1,23 +1,67 @@ import { BufferGeometry, + Color, FileLoader, Float32BufferAttribute, Int32BufferAttribute, Loader, Points, - PointsMaterial + PointsMaterial, + SRGBColorSpace } from 'three'; +/** + * A loader for the Point Cloud Data (PCD) format. + * + * PCDLoader supports ASCII and (compressed) binary files as well as the following PCD fields: + * - x y z + * - rgb + * - normal_x normal_y normal_z + * - intensity + * - label + * + * ```js + * const loader = new PCDLoader(); + * + * const points = await loader.loadAsync( './models/pcd/binary/Zaghetto.pcd' ); + * points.geometry.center(); // optional + * points.geometry.rotateX( Math.PI ); // optional + * scene.add( points ); + * ``` + * + * @augments Loader + * @three_import import { PCDLoader } from 'three/addons/loaders/PCDLoader.js'; + */ class PCDLoader extends Loader { + /** + * Constructs a new PCD loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); + /** + * Whether to use little Endian or not. + * + * @type {boolean} + * @default true + */ this.littleEndian = true; } + /** + * Starts loading from the given URL and passes the loaded PCD asset + * to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(Points)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ load( url, onLoad, onProgress, onError ) { const scope = this; @@ -53,6 +97,77 @@ class PCDLoader extends Loader { } + /** + * Get dataview value by field type and size. + * + * @param {DataView} dataview - The DataView to read from. + * @param {number} offset - The offset to start reading from. + * @param {'F' | 'U' | 'I'} type - Field type. + * @param {number} size - Field size. + * @returns {number} Field value. + */ + _getDataView( dataview, offset, type, size ) { + + switch ( type ) { + + case 'F': { + + if ( size === 8 ) { + + return dataview.getFloat64( offset, this.littleEndian ); + + } + + return dataview.getFloat32( offset, this.littleEndian ); + + } + + case 'I': { + + if ( size === 1 ) { + + return dataview.getInt8( offset ); + + } + + if ( size === 2 ) { + + return dataview.getInt16( offset, this.littleEndian ); + + } + + return dataview.getInt32( offset, this.littleEndian ); + + } + + case 'U': { + + if ( size === 1 ) { + + return dataview.getUint8( offset ); + + } + + if ( size === 2 ) { + + return dataview.getUint16( offset, this.littleEndian ); + + } + + return dataview.getUint32( offset, this.littleEndian ); + + } + + } + + } + + /** + * Parses the given PCD data and returns a point cloud. + * + * @param {ArrayBuffer} data - The raw PCD data as an array buffer. + * @return {Points} The parsed point cloud. + */ parse( data ) { // from https://gitlab.com/taketwo/three-pcd-loader/blob/master/decompress-lzf.js @@ -110,9 +225,40 @@ class PCDLoader extends Loader { } - function parseHeader( data ) { + function parseHeader( binaryData ) { const PCDheader = {}; + + const buffer = new Uint8Array( binaryData ); + + let data = '', line = '', i = 0, end = false; + + const max = buffer.length; + + while ( i < max && end === false ) { + + const char = String.fromCharCode( buffer[ i ++ ] ); + + if ( char === '\n' || char === '\r' ) { + + if ( line.trim().toLowerCase().startsWith( 'data' ) ) { + + end = true; + + } + + line = ''; + + } else { + + line += char; + + } + + data += char; + + } + const result1 = data.search( /[\r\n]DATA\s(\S*)\s/i ); const result2 = /[\r\n]DATA\s(\S*)\s/i.exec( data.slice( result1 - 1 ) ); @@ -126,15 +272,15 @@ class PCDLoader extends Loader { // parse - PCDheader.version = /VERSION (.*)/i.exec( PCDheader.str ); - PCDheader.fields = /FIELDS (.*)/i.exec( PCDheader.str ); - PCDheader.size = /SIZE (.*)/i.exec( PCDheader.str ); - PCDheader.type = /TYPE (.*)/i.exec( PCDheader.str ); - PCDheader.count = /COUNT (.*)/i.exec( PCDheader.str ); - PCDheader.width = /WIDTH (.*)/i.exec( PCDheader.str ); - PCDheader.height = /HEIGHT (.*)/i.exec( PCDheader.str ); - PCDheader.viewpoint = /VIEWPOINT (.*)/i.exec( PCDheader.str ); - PCDheader.points = /POINTS (.*)/i.exec( PCDheader.str ); + PCDheader.version = /^VERSION (.*)/im.exec( PCDheader.str ); + PCDheader.fields = /^FIELDS (.*)/im.exec( PCDheader.str ); + PCDheader.size = /^SIZE (.*)/im.exec( PCDheader.str ); + PCDheader.type = /^TYPE (.*)/im.exec( PCDheader.str ); + PCDheader.count = /^COUNT (.*)/im.exec( PCDheader.str ); + PCDheader.width = /^WIDTH (.*)/im.exec( PCDheader.str ); + PCDheader.height = /^HEIGHT (.*)/im.exec( PCDheader.str ); + PCDheader.viewpoint = /^VIEWPOINT (.*)/im.exec( PCDheader.str ); + PCDheader.points = /^POINTS (.*)/im.exec( PCDheader.str ); // evaluate @@ -218,11 +364,9 @@ class PCDLoader extends Loader { } - const textData = new TextDecoder().decode( data ); - - // parse header (always ascii format) + // parse header - const PCDheader = parseHeader( textData ); + const PCDheader = parseHeader( data ); // parse data @@ -232,11 +376,14 @@ class PCDLoader extends Loader { const intensity = []; const label = []; + const c = new Color(); + // ascii if ( PCDheader.data === 'ascii' ) { const offset = PCDheader.offset; + const textData = new TextDecoder().decode( data ); const pcdData = textData.slice( PCDheader.headerLen ); const lines = pcdData.split( '\n' ); @@ -272,10 +419,13 @@ class PCDLoader extends Loader { } - const r = ( rgb >> 16 ) & 0x0000ff; - const g = ( rgb >> 8 ) & 0x0000ff; - const b = ( rgb >> 0 ) & 0x0000ff; - color.push( r / 255, g / 255, b / 255 ); + const r = ( ( rgb >> 16 ) & 0x0000ff ) / 255; + const g = ( ( rgb >> 8 ) & 0x0000ff ) / 255; + const b = ( ( rgb >> 0 ) & 0x0000ff ) / 255; + + c.setRGB( r, g, b, SRGBColorSpace ); + + color.push( c.r, c.g, c.b ); } @@ -326,18 +476,23 @@ class PCDLoader extends Loader { const xIndex = PCDheader.fields.indexOf( 'x' ); const yIndex = PCDheader.fields.indexOf( 'y' ); const zIndex = PCDheader.fields.indexOf( 'z' ); - position.push( dataview.getFloat32( ( PCDheader.points * offset.x ) + PCDheader.size[ xIndex ] * i, this.littleEndian ) ); - position.push( dataview.getFloat32( ( PCDheader.points * offset.y ) + PCDheader.size[ yIndex ] * i, this.littleEndian ) ); - position.push( dataview.getFloat32( ( PCDheader.points * offset.z ) + PCDheader.size[ zIndex ] * i, this.littleEndian ) ); + position.push( this._getDataView( dataview, ( PCDheader.points * offset.x ) + PCDheader.size[ xIndex ] * i, PCDheader.type[ xIndex ], PCDheader.size[ xIndex ] ) ); + position.push( this._getDataView( dataview, ( PCDheader.points * offset.y ) + PCDheader.size[ yIndex ] * i, PCDheader.type[ yIndex ], PCDheader.size[ yIndex ] ) ); + position.push( this._getDataView( dataview, ( PCDheader.points * offset.z ) + PCDheader.size[ zIndex ] * i, PCDheader.type[ zIndex ], PCDheader.size[ zIndex ] ) ); } if ( offset.rgb !== undefined ) { const rgbIndex = PCDheader.fields.indexOf( 'rgb' ); - color.push( dataview.getUint8( ( PCDheader.points * offset.rgb ) + PCDheader.size[ rgbIndex ] * i + 2 ) / 255.0 ); - color.push( dataview.getUint8( ( PCDheader.points * offset.rgb ) + PCDheader.size[ rgbIndex ] * i + 1 ) / 255.0 ); - color.push( dataview.getUint8( ( PCDheader.points * offset.rgb ) + PCDheader.size[ rgbIndex ] * i + 0 ) / 255.0 ); + + const r = dataview.getUint8( ( PCDheader.points * offset.rgb ) + PCDheader.size[ rgbIndex ] * i + 2 ) / 255.0; + const g = dataview.getUint8( ( PCDheader.points * offset.rgb ) + PCDheader.size[ rgbIndex ] * i + 1 ) / 255.0; + const b = dataview.getUint8( ( PCDheader.points * offset.rgb ) + PCDheader.size[ rgbIndex ] * i + 0 ) / 255.0; + + c.setRGB( r, g, b, SRGBColorSpace ); + + color.push( c.r, c.g, c.b ); } @@ -346,16 +501,16 @@ class PCDLoader extends Loader { const xIndex = PCDheader.fields.indexOf( 'normal_x' ); const yIndex = PCDheader.fields.indexOf( 'normal_y' ); const zIndex = PCDheader.fields.indexOf( 'normal_z' ); - normal.push( dataview.getFloat32( ( PCDheader.points * offset.normal_x ) + PCDheader.size[ xIndex ] * i, this.littleEndian ) ); - normal.push( dataview.getFloat32( ( PCDheader.points * offset.normal_y ) + PCDheader.size[ yIndex ] * i, this.littleEndian ) ); - normal.push( dataview.getFloat32( ( PCDheader.points * offset.normal_z ) + PCDheader.size[ zIndex ] * i, this.littleEndian ) ); + normal.push( this._getDataView( dataview, ( PCDheader.points * offset.normal_x ) + PCDheader.size[ xIndex ] * i, PCDheader.type[ xIndex ], PCDheader.size[ xIndex ] ) ); + normal.push( this._getDataView( dataview, ( PCDheader.points * offset.normal_y ) + PCDheader.size[ yIndex ] * i, PCDheader.type[ yIndex ], PCDheader.size[ yIndex ] ) ); + normal.push( this._getDataView( dataview, ( PCDheader.points * offset.normal_z ) + PCDheader.size[ zIndex ] * i, PCDheader.type[ zIndex ], PCDheader.size[ zIndex ] ) ); } if ( offset.intensity !== undefined ) { const intensityIndex = PCDheader.fields.indexOf( 'intensity' ); - intensity.push( dataview.getFloat32( ( PCDheader.points * offset.intensity ) + PCDheader.size[ intensityIndex ] * i, this.littleEndian ) ); + intensity.push( this._getDataView( dataview, ( PCDheader.points * offset.intensity ) + PCDheader.size[ intensityIndex ] * i, PCDheader.type[ intensityIndex ], PCDheader.size[ intensityIndex ] ) ); } @@ -381,31 +536,42 @@ class PCDLoader extends Loader { if ( offset.x !== undefined ) { - position.push( dataview.getFloat32( row + offset.x, this.littleEndian ) ); - position.push( dataview.getFloat32( row + offset.y, this.littleEndian ) ); - position.push( dataview.getFloat32( row + offset.z, this.littleEndian ) ); + const xIndex = PCDheader.fields.indexOf( 'x' ); + const yIndex = PCDheader.fields.indexOf( 'y' ); + const zIndex = PCDheader.fields.indexOf( 'z' ); + position.push( this._getDataView( dataview, row + offset.x, PCDheader.type[ xIndex ], PCDheader.size[ xIndex ] ) ); + position.push( this._getDataView( dataview, row + offset.y, PCDheader.type[ yIndex ], PCDheader.size[ yIndex ] ) ); + position.push( this._getDataView( dataview, row + offset.z, PCDheader.type[ zIndex ], PCDheader.size[ zIndex ] ) ); } if ( offset.rgb !== undefined ) { - color.push( dataview.getUint8( row + offset.rgb + 2 ) / 255.0 ); - color.push( dataview.getUint8( row + offset.rgb + 1 ) / 255.0 ); - color.push( dataview.getUint8( row + offset.rgb + 0 ) / 255.0 ); + const r = dataview.getUint8( row + offset.rgb + 2 ) / 255.0; + const g = dataview.getUint8( row + offset.rgb + 1 ) / 255.0; + const b = dataview.getUint8( row + offset.rgb + 0 ) / 255.0; + + c.setRGB( r, g, b, SRGBColorSpace ); + + color.push( c.r, c.g, c.b ); } if ( offset.normal_x !== undefined ) { - normal.push( dataview.getFloat32( row + offset.normal_x, this.littleEndian ) ); - normal.push( dataview.getFloat32( row + offset.normal_y, this.littleEndian ) ); - normal.push( dataview.getFloat32( row + offset.normal_z, this.littleEndian ) ); + const xIndex = PCDheader.fields.indexOf( 'normal_x' ); + const yIndex = PCDheader.fields.indexOf( 'normal_y' ); + const zIndex = PCDheader.fields.indexOf( 'normal_z' ); + normal.push( this._getDataView( dataview, row + offset.normal_x, PCDheader.type[ xIndex ], PCDheader.size[ xIndex ] ) ); + normal.push( this._getDataView( dataview, row + offset.normal_y, PCDheader.type[ yIndex ], PCDheader.size[ yIndex ] ) ); + normal.push( this._getDataView( dataview, row + offset.normal_z, PCDheader.type[ zIndex ], PCDheader.size[ zIndex ] ) ); } if ( offset.intensity !== undefined ) { - intensity.push( dataview.getFloat32( row + offset.intensity, this.littleEndian ) ); + const intensityIndex = PCDheader.fields.indexOf( 'intensity' ); + intensity.push( this._getDataView( dataview, row + offset.intensity, PCDheader.type[ intensityIndex ], PCDheader.size[ intensityIndex ] ) ); } diff --git a/examples/jsm/loaders/PDBLoader.js b/examples/jsm/loaders/PDBLoader.js index 87c048c17f3448..267b06f783a716 100644 --- a/examples/jsm/loaders/PDBLoader.js +++ b/examples/jsm/loaders/PDBLoader.js @@ -2,17 +2,51 @@ import { BufferGeometry, FileLoader, Float32BufferAttribute, - Loader + Loader, + Color, + SRGBColorSpace } from 'three'; +/** + * A loader for the PDB format. + * + * The [Protein Data Bank]{@link https://en.wikipedia.org/wiki/Protein_Data_Bank_(file_format)} + * file format is a textual file describing the three-dimensional structures of molecules. + * + * ```js + * const loader = new PDBLoader(); + * const pdb = await loader.loadAsync( 'models/pdb/ethanol.pdb' ); + * + * const geometryAtoms = pdb.geometryAtoms; + * const geometryBonds = pdb.geometryBonds; + * const json = pdb.json; + * ``` + * + * @augments Loader + * @three_import import { PDBLoader } from 'three/addons/loaders/PDBLoader.js'; + */ class PDBLoader extends Loader { + /** + * Constructs a new PDB loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); } + /** + * Starts loading from the given URL and passes the loaded PDB asset + * to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(Object)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ load( url, onLoad, onProgress, onError ) { const scope = this; @@ -47,10 +81,17 @@ class PDBLoader extends Loader { } - // Based on CanvasMol PDB parser - + /** + * Parses the given PDB data and returns an object holding the atoms and + * bond geometries as well as the raw atom data as JSON. + * + * @param {string} text - The raw PDB data as a string. + * @return {Object} The result object. + */ parse( text ) { + // Based on CanvasMol PDB parser + function trim( text ) { return text.replace( /^\s\s*/, '' ).replace( /\s\s*$/, '' ); @@ -114,6 +155,8 @@ class PDBLoader extends Loader { // atoms + const c = new Color(); + for ( let i = 0, l = atoms.length; i < l; i ++ ) { const atom = atoms[ i ]; @@ -128,7 +171,9 @@ class PDBLoader extends Loader { const g = atom[ 3 ][ 1 ] / 255; const b = atom[ 3 ][ 2 ] / 255; - colorsAtoms.push( r, g, b ); + c.setRGB( r, g, b, SRGBColorSpace ); + + colorsAtoms.push( c.r, c.g, c.b ); } diff --git a/examples/jsm/loaders/PLYLoader.js b/examples/jsm/loaders/PLYLoader.js index 3f274ac05e3cd0..dc0fc6d773adb6 100644 --- a/examples/jsm/loaders/PLYLoader.js +++ b/examples/jsm/loaders/PLYLoader.js @@ -3,58 +3,55 @@ import { FileLoader, Float32BufferAttribute, Loader, - Color + Color, + SRGBColorSpace } from 'three'; +const _color = new Color(); + /** - * Description: A THREE loader for PLY ASCII files (known as the Polygon + * A loader for PLY the PLY format (known as the Polygon * File Format or the Stanford Triangle Format). * - * Limitations: ASCII decoding assumes file is UTF-8. - * - * Usage: - * const loader = new PLYLoader(); - * loader.load('./models/ply/ascii/dolphins.ply', function (geometry) { - * - * scene.add( new THREE.Mesh( geometry ) ); - * - * } ); + * Limitations: + * - ASCII decoding assumes file is UTF-8. * - * If the PLY file uses non standard property names, they can be mapped while - * loading. For example, the following maps the properties - * “diffuse_(red|green|blue)” in the file to standard color names. - * - * loader.setPropertyNameMapping( { - * diffuse_red: 'red', - * diffuse_green: 'green', - * diffuse_blue: 'blue' - * } ); - * - * Custom properties outside of the defaults for position, uv, normal - * and color attributes can be added using the setCustomPropertyNameMapping method. - * For example, the following maps the element properties “custom_property_a” - * and “custom_property_b” to an attribute “customAttribute” with an item size of 2. - * Attribute item sizes are set from the number of element properties in the property array. - * - * loader.setCustomPropertyNameMapping( { - * customAttribute: ['custom_property_a', 'custom_property_b'], - * } ); + * ```js + * const loader = new PLYLoader(); + * const geometry = await loader.loadAsync( './models/ply/ascii/dolphins.ply' ); + * scene.add( new THREE.Mesh( geometry ) ); + * ``` * + * @augments Loader + * @three_import import { PLYLoader } from 'three/addons/loaders/PLYLoader.js'; */ - -const _color = new Color(); - class PLYLoader extends Loader { + /** + * Constructs a new PLY loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); + // internals + this.propertyNameMapping = {}; this.customPropertyMapping = {}; } + /** + * Starts loading from the given URL and passes the loaded PLY asset + * to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(BufferGeometry)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ load( url, onLoad, onProgress, onError ) { const scope = this; @@ -90,31 +87,64 @@ class PLYLoader extends Loader { } + /** + * Sets a property name mapping that maps default property names + * to custom ones. For example, the following maps the properties + * “diffuse_(red|green|blue)” in the file to standard color names. + * + * ```js + * loader.setPropertyNameMapping( { + * diffuse_red: 'red', + * diffuse_green: 'green', + * diffuse_blue: 'blue' + * } ); + * ``` + * + * @param {Object} mapping - The mapping dictionary. + */ setPropertyNameMapping( mapping ) { this.propertyNameMapping = mapping; } + /** + * Custom properties outside of the defaults for position, uv, normal + * and color attributes can be added using the setCustomPropertyNameMapping method. + * For example, the following maps the element properties “custom_property_a” + * and “custom_property_b” to an attribute “customAttribute” with an item size of 2. + * Attribute item sizes are set from the number of element properties in the property array. + * + * ```js + * loader.setCustomPropertyNameMapping( { + * customAttribute: ['custom_property_a', 'custom_property_b'], + * } ); + * ``` + * @param {Object} mapping - The mapping dictionary. + */ setCustomPropertyNameMapping( mapping ) { this.customPropertyMapping = mapping; } + /** + * Parses the given PLY data and returns the resulting geometry. + * + * @param {ArrayBuffer} data - The raw PLY data as an array buffer. + * @return {BufferGeometry} The parsed geometry. + */ parse( data ) { - function parseHeader( data ) { + function parseHeader( data, headerLength = 0 ) { const patternHeader = /^ply([\s\S]*)end_header(\r\n|\r|\n)/; let headerText = ''; - let headerLength = 0; const result = patternHeader.exec( data ); if ( result !== null ) { headerText = result[ 1 ]; - headerLength = new Blob( [ result[ 0 ] ] ).size; } @@ -128,19 +158,19 @@ class PLYLoader extends Loader { const lines = headerText.split( /\r\n|\r|\n/ ); let currentElement; - function make_ply_element_property( propertValues, propertyNameMapping ) { + function make_ply_element_property( propertyValues, propertyNameMapping ) { - const property = { type: propertValues[ 0 ] }; + const property = { type: propertyValues[ 0 ] }; if ( property.type === 'list' ) { - property.name = propertValues[ 3 ]; - property.countType = propertValues[ 1 ]; - property.itemType = propertValues[ 2 ]; + property.name = propertyValues[ 3 ]; + property.countType = propertyValues[ 1 ]; + property.itemType = propertyValues[ 2 ]; } else { - property.name = propertValues[ 1 ]; + property.name = propertyValues[ 1 ]; } @@ -408,6 +438,12 @@ class PLYLoader extends Loader { } + if ( buffer.colors.length > 0 ) { + + geometry.setAttribute( 'color', new Float32BufferAttribute( buffer.colors, 3 ) ); + + } + if ( buffer.faceVertexUvs.length > 0 || buffer.faceVertexColors.length > 0 ) { geometry = geometry.toNonIndexed(); @@ -464,8 +500,9 @@ class PLYLoader extends Loader { _color.setRGB( element[ cacheEntry.attrR ] / 255.0, element[ cacheEntry.attrG ] / 255.0, - element[ cacheEntry.attrB ] / 255.0 - ).convertSRGBToLinear(); + element[ cacheEntry.attrB ] / 255.0, + SRGBColorSpace + ); buffer.colors.push( _color.r, _color.g, _color.b ); @@ -512,8 +549,9 @@ class PLYLoader extends Loader { _color.setRGB( element[ cacheEntry.attrR ] / 255.0, element[ cacheEntry.attrG ] / 255.0, - element[ cacheEntry.attrB ] / 255.0 - ).convertSRGBToLinear(); + element[ cacheEntry.attrB ] / 255.0, + SRGBColorSpace + ); buffer.faceVertexColors.push( _color.r, _color.g, _color.b ); buffer.faceVertexColors.push( _color.r, _color.g, _color.b ); buffer.faceVertexColors.push( _color.r, _color.g, _color.b ); @@ -569,7 +607,7 @@ class PLYLoader extends Loader { switch ( type ) { - // corespondences for non-specific length types here match rply: + // correspondences for non-specific length types here match rply: case 'int8': case 'char': return { read: ( at ) => { return dataview.getInt8( at ); @@ -674,6 +712,9 @@ class PLYLoader extends Loader { let line = ''; const lines = []; + const startLine = new TextDecoder().decode( bytes.subarray( 0, 5 ) ); + const hasCRNL = /^ply\r\n/.test( startLine ); + do { const c = String.fromCharCode( bytes[ i ++ ] ); @@ -696,7 +737,10 @@ class PLYLoader extends Loader { } while ( cont && i < bytes.length ); - return lines.join( '\r' ) + '\r'; + // ascii section using \r\n as line endings + if ( hasCRNL === true ) i ++; + + return { headerText: lines.join( '\r' ) + '\r', headerLength: i }; } @@ -708,8 +752,8 @@ class PLYLoader extends Loader { if ( data instanceof ArrayBuffer ) { const bytes = new Uint8Array( data ); - const headerText = extractHeaderText( bytes ); - const header = parseHeader( headerText ); + const { headerText, headerLength } = extractHeaderText( bytes ); + const header = parseHeader( headerText, headerLength ); if ( header.format === 'ascii' ) { diff --git a/examples/jsm/loaders/PRWMLoader.js b/examples/jsm/loaders/PRWMLoader.js deleted file mode 100644 index 1eb226859f0fdb..00000000000000 --- a/examples/jsm/loaders/PRWMLoader.js +++ /dev/null @@ -1,299 +0,0 @@ -import { - BufferAttribute, - BufferGeometry, - FileLoader, - Loader -} from 'three'; - -/** - * See https://github.com/kchapelier/PRWM for more informations about this file format - */ - -let bigEndianPlatform = null; - -/** - * Check if the endianness of the platform is big-endian (most significant bit first) - * @returns {boolean} True if big-endian, false if little-endian - */ -function isBigEndianPlatform() { - - if ( bigEndianPlatform === null ) { - - const buffer = new ArrayBuffer( 2 ), - uint8Array = new Uint8Array( buffer ), - uint16Array = new Uint16Array( buffer ); - - uint8Array[ 0 ] = 0xAA; // set first byte - uint8Array[ 1 ] = 0xBB; // set second byte - bigEndianPlatform = ( uint16Array[ 0 ] === 0xAABB ); - - } - - return bigEndianPlatform; - -} - -// match the values defined in the spec to the TypedArray types -const InvertedEncodingTypes = [ - null, - Float32Array, - null, - Int8Array, - Int16Array, - null, - Int32Array, - Uint8Array, - Uint16Array, - null, - Uint32Array -]; - -// define the method to use on a DataView, corresponding the TypedArray type -const getMethods = { - Uint16Array: 'getUint16', - Uint32Array: 'getUint32', - Int16Array: 'getInt16', - Int32Array: 'getInt32', - Float32Array: 'getFloat32', - Float64Array: 'getFloat64' -}; - - -function copyFromBuffer( sourceArrayBuffer, viewType, position, length, fromBigEndian ) { - - const bytesPerElement = viewType.BYTES_PER_ELEMENT; - let result; - - if ( fromBigEndian === isBigEndianPlatform() || bytesPerElement === 1 ) { - - result = new viewType( sourceArrayBuffer, position, length ); - - } else { - - const readView = new DataView( sourceArrayBuffer, position, length * bytesPerElement ), - getMethod = getMethods[ viewType.name ], - littleEndian = ! fromBigEndian; - - result = new viewType( length ); - - for ( let i = 0; i < length; i ++ ) { - - result[ i ] = readView[ getMethod ]( i * bytesPerElement, littleEndian ); - - } - - } - - return result; - -} - - -function decodePrwm( buffer ) { - - const array = new Uint8Array( buffer ), - version = array[ 0 ]; - - let flags = array[ 1 ]; - - const indexedGeometry = !! ( flags >> 7 & 0x01 ), - indicesType = flags >> 6 & 0x01, - bigEndian = ( flags >> 5 & 0x01 ) === 1, - attributesNumber = flags & 0x1F; - - let valuesNumber = 0, - indicesNumber = 0; - - if ( bigEndian ) { - - valuesNumber = ( array[ 2 ] << 16 ) + ( array[ 3 ] << 8 ) + array[ 4 ]; - indicesNumber = ( array[ 5 ] << 16 ) + ( array[ 6 ] << 8 ) + array[ 7 ]; - - } else { - - valuesNumber = array[ 2 ] + ( array[ 3 ] << 8 ) + ( array[ 4 ] << 16 ); - indicesNumber = array[ 5 ] + ( array[ 6 ] << 8 ) + ( array[ 7 ] << 16 ); - - } - - /** PRELIMINARY CHECKS **/ - - if ( version === 0 ) { - - throw new Error( 'PRWM decoder: Invalid format version: 0' ); - - } else if ( version !== 1 ) { - - throw new Error( 'PRWM decoder: Unsupported format version: ' + version ); - - } - - if ( ! indexedGeometry ) { - - if ( indicesType !== 0 ) { - - throw new Error( 'PRWM decoder: Indices type must be set to 0 for non-indexed geometries' ); - - } else if ( indicesNumber !== 0 ) { - - throw new Error( 'PRWM decoder: Number of indices must be set to 0 for non-indexed geometries' ); - - } - - } - - /** PARSING **/ - - let pos = 8; - - const attributes = {}; - - for ( let i = 0; i < attributesNumber; i ++ ) { - - let attributeName = ''; - - while ( pos < array.length ) { - - const char = array[ pos ]; - pos ++; - - if ( char === 0 ) { - - break; - - } else { - - attributeName += String.fromCharCode( char ); - - } - - } - - flags = array[ pos ]; - - const attributeType = flags >> 7 & 0x01; - const cardinality = ( flags >> 4 & 0x03 ) + 1; - const encodingType = flags & 0x0F; - const arrayType = InvertedEncodingTypes[ encodingType ]; - - pos ++; - - // padding to next multiple of 4 - pos = Math.ceil( pos / 4 ) * 4; - - const values = copyFromBuffer( buffer, arrayType, pos, cardinality * valuesNumber, bigEndian ); - - pos += arrayType.BYTES_PER_ELEMENT * cardinality * valuesNumber; - - attributes[ attributeName ] = { - type: attributeType, - cardinality: cardinality, - values: values - }; - - } - - pos = Math.ceil( pos / 4 ) * 4; - - let indices = null; - - if ( indexedGeometry ) { - - indices = copyFromBuffer( - buffer, - indicesType === 1 ? Uint32Array : Uint16Array, - pos, - indicesNumber, - bigEndian - ); - - } - - return { - version: version, - attributes: attributes, - indices: indices - }; - -} - -// Define the public interface - -class PRWMLoader extends Loader { - - constructor( manager ) { - - super( manager ); - - } - - load( url, onLoad, onProgress, onError ) { - - const scope = this; - - const loader = new FileLoader( scope.manager ); - loader.setPath( scope.path ); - loader.setResponseType( 'arraybuffer' ); - loader.setRequestHeader( scope.requestHeader ); - loader.setWithCredentials( scope.withCredentials ); - - url = url.replace( /\*/g, isBigEndianPlatform() ? 'be' : 'le' ); - - loader.load( url, function ( arrayBuffer ) { - - try { - - onLoad( scope.parse( arrayBuffer ) ); - - } catch ( e ) { - - if ( onError ) { - - onError( e ); - - } else { - - console.error( e ); - - } - - scope.manager.itemError( url ); - - } - - }, onProgress, onError ); - - } - - parse( arrayBuffer ) { - - const data = decodePrwm( arrayBuffer ), - attributesKey = Object.keys( data.attributes ), - bufferGeometry = new BufferGeometry(); - - for ( let i = 0; i < attributesKey.length; i ++ ) { - - const attribute = data.attributes[ attributesKey[ i ] ]; - bufferGeometry.setAttribute( attributesKey[ i ], new BufferAttribute( attribute.values, attribute.cardinality, attribute.normalized ) ); - - } - - if ( data.indices !== null ) { - - bufferGeometry.setIndex( new BufferAttribute( data.indices, 1 ) ); - - } - - return bufferGeometry; - - } - - static isBigEndianPlatform() { - - return isBigEndianPlatform(); - - } - -} - -export { PRWMLoader }; diff --git a/examples/jsm/loaders/PVRLoader.js b/examples/jsm/loaders/PVRLoader.js index f39f8e75912fc3..bdedd56c3289c9 100644 --- a/examples/jsm/loaders/PVRLoader.js +++ b/examples/jsm/loaders/PVRLoader.js @@ -6,20 +6,39 @@ import { RGB_PVRTC_4BPPV1_Format } from 'three'; -/* - * PVR v2 (legacy) parser - * TODO : Add Support for PVR v3 format - * TODO : implement loadMipmaps option +/** + * A loader for the PVRTC texture compression format. + * + * ```js + * const loader = new PVRLoader(); + * + * const map = loader.load( 'textures/compressed/disturb_4bpp_rgb.pvr' ); + * map.colorSpace = THREE.SRGBColorSpace; // only for color textures + * ``` + * + * @augments CompressedTextureLoader + * @three_import import { PVRLoader } from 'three/addons/loaders/PVRLoader.js'; */ - class PVRLoader extends CompressedTextureLoader { + /** + * Constructs a new PVR loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); } + /** + * Parses the given PVRTC texture data. + * + * @param {ArrayBuffer} buffer - The raw texture data. + * @param {boolean} loadMipmaps - Whether to load mipmaps or not. This option is not yet supported by the loader. + * @return {CompressedTextureLoader~TexData} An object representing the parsed texture data. + */ parse( buffer, loadMipmaps ) { const headerLengthInt = 13; @@ -160,7 +179,7 @@ function _parseV2( pvrDatas ) { pvrDatas.numMipmaps = numMipmaps + 1; // guess cubemap type seems tricky in v2 - // it juste a pvr containing 6 surface (no explicit cubemap type) + // it's just a pvr containing 6 surface (no explicit cubemap type) pvrDatas.isCubemap = ( numSurfs === 6 ); return _extract( pvrDatas ); diff --git a/examples/jsm/loaders/RGBELoader.js b/examples/jsm/loaders/RGBELoader.js index 3ca006a61f78a7..691311d862c277 100644 --- a/examples/jsm/loaders/RGBELoader.js +++ b/examples/jsm/loaders/RGBELoader.js @@ -7,28 +7,52 @@ import { LinearSRGBColorSpace } from 'three'; -// https://github.com/mrdoob/three.js/issues/5552 -// http://en.wikipedia.org/wiki/RGBE_image_format - +/** + * A loader for the RGBE HDR texture format. + * + * ```js + * const loader = new RGBELoader(); + * const envMap = await loader.loadAsync( 'textures/equirectangular/blouberg_sunrise_2_1k.hdr' ); + * envMap.mapping = THREE.EquirectangularReflectionMapping; + * + * scene.environment = envMap; + * ``` + * + * @augments DataTextureLoader + * @three_import import { RGBELoader } from 'three/addons/loaders/RGBELoader.js'; + */ class RGBELoader extends DataTextureLoader { + /** + * Constructs a new RGBE loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); + /** + * The texture type. + * + * @type {(HalfFloatType|FloatType)} + * @default HalfFloatType + */ this.type = HalfFloatType; } - // adapted from http://www.graphics.cornell.edu/~bjw/rgbe.html - + /** + * Parses the given RGBE texture data. + * + * @param {ArrayBuffer} buffer - The raw texture data. + * @return {DataTextureLoader~TexData} An object representing the parsed texture data. + */ parse( buffer ) { - const - /* return codes for rgbe routines */ - //RGBE_RETURN_SUCCESS = 0, - RGBE_RETURN_FAILURE = - 1, + // adapted from http://www.graphics.cornell.edu/~bjw/rgbe.html + const /* default error routine. change this to change error handling */ rgbe_read_error = 1, rgbe_write_error = 2, @@ -38,19 +62,14 @@ class RGBELoader extends DataTextureLoader { switch ( rgbe_error_code ) { - case rgbe_read_error: console.error( 'THREE.RGBELoader Read Error: ' + ( msg || '' ) ); - break; - case rgbe_write_error: console.error( 'THREE.RGBELoader Write Error: ' + ( msg || '' ) ); - break; - case rgbe_format_error: console.error( 'THREE.RGBELoader Bad File Format: ' + ( msg || '' ) ); - break; + case rgbe_read_error: throw new Error( 'THREE.RGBELoader: Read Error: ' + ( msg || '' ) ); + case rgbe_write_error: throw new Error( 'THREE.RGBELoader: Write Error: ' + ( msg || '' ) ); + case rgbe_format_error: throw new Error( 'THREE.RGBELoader: Bad File Format: ' + ( msg || '' ) ); default: - case rgbe_memory_error: console.error( 'THREE.RGBELoader: Error: ' + ( msg || '' ) ); + case rgbe_memory_error: throw new Error( 'THREE.RGBELoader: Memory Error: ' + ( msg || '' ) ); } - return RGBE_RETURN_FAILURE; - }, /* offsets to red, green, and blue components in a data (float) pixel */ @@ -138,14 +157,14 @@ class RGBELoader extends DataTextureLoader { if ( buffer.pos >= buffer.byteLength || ! ( line = fgets( buffer ) ) ) { - return rgbe_error( rgbe_read_error, 'no header found' ); + rgbe_error( rgbe_read_error, 'no header found' ); } /* if you want to require the magic token then uncomment the next line */ if ( ! ( match = line.match( magic_token_re ) ) ) { - return rgbe_error( rgbe_format_error, 'bad initial token' ); + rgbe_error( rgbe_format_error, 'bad initial token' ); } @@ -199,13 +218,13 @@ class RGBELoader extends DataTextureLoader { if ( ! ( header.valid & RGBE_VALID_FORMAT ) ) { - return rgbe_error( rgbe_format_error, 'missing format specifier' ); + rgbe_error( rgbe_format_error, 'missing format specifier' ); } if ( ! ( header.valid & RGBE_VALID_DIMENSIONS ) ) { - return rgbe_error( rgbe_format_error, 'missing image size specifier' ); + rgbe_error( rgbe_format_error, 'missing image size specifier' ); } @@ -231,7 +250,7 @@ class RGBELoader extends DataTextureLoader { if ( scanline_width !== ( ( buffer[ 2 ] << 8 ) | buffer[ 3 ] ) ) { - return rgbe_error( rgbe_format_error, 'wrong scanline width' ); + rgbe_error( rgbe_format_error, 'wrong scanline width' ); } @@ -239,7 +258,7 @@ class RGBELoader extends DataTextureLoader { if ( ! data_rgba.length ) { - return rgbe_error( rgbe_memory_error, 'unable to allocate buffer space' ); + rgbe_error( rgbe_memory_error, 'unable to allocate buffer space' ); } @@ -255,7 +274,7 @@ class RGBELoader extends DataTextureLoader { if ( pos + 4 > buffer.byteLength ) { - return rgbe_error( rgbe_read_error ); + rgbe_error( rgbe_read_error ); } @@ -266,7 +285,7 @@ class RGBELoader extends DataTextureLoader { if ( ( 2 != rgbeStart[ 0 ] ) || ( 2 != rgbeStart[ 1 ] ) || ( ( ( rgbeStart[ 2 ] << 8 ) | rgbeStart[ 3 ] ) != scanline_width ) ) { - return rgbe_error( rgbe_format_error, 'bad rgbe scanline format' ); + rgbe_error( rgbe_format_error, 'bad rgbe scanline format' ); } @@ -282,7 +301,7 @@ class RGBELoader extends DataTextureLoader { if ( ( 0 === count ) || ( ptr + count > ptr_end ) ) { - return rgbe_error( rgbe_format_error, 'bad scanline data' ); + rgbe_error( rgbe_format_error, 'bad scanline data' ); } @@ -362,73 +381,70 @@ class RGBELoader extends DataTextureLoader { byteArray.pos = 0; const rgbe_header_info = RGBE_ReadHeader( byteArray ); - if ( RGBE_RETURN_FAILURE !== rgbe_header_info ) { + const w = rgbe_header_info.width, + h = rgbe_header_info.height, + image_rgba_data = RGBE_ReadPixels_RLE( byteArray.subarray( byteArray.pos ), w, h ); - const w = rgbe_header_info.width, - h = rgbe_header_info.height, - image_rgba_data = RGBE_ReadPixels_RLE( byteArray.subarray( byteArray.pos ), w, h ); - if ( RGBE_RETURN_FAILURE !== image_rgba_data ) { + let data, type; + let numElements; - let data, type; - let numElements; + switch ( this.type ) { - switch ( this.type ) { + case FloatType: - case FloatType: + numElements = image_rgba_data.length / 4; + const floatArray = new Float32Array( numElements * 4 ); - numElements = image_rgba_data.length / 4; - const floatArray = new Float32Array( numElements * 4 ); - - for ( let j = 0; j < numElements; j ++ ) { - - RGBEByteToRGBFloat( image_rgba_data, j * 4, floatArray, j * 4 ); - - } + for ( let j = 0; j < numElements; j ++ ) { - data = floatArray; - type = FloatType; - break; + RGBEByteToRGBFloat( image_rgba_data, j * 4, floatArray, j * 4 ); - case HalfFloatType: - - numElements = image_rgba_data.length / 4; - const halfArray = new Uint16Array( numElements * 4 ); - - for ( let j = 0; j < numElements; j ++ ) { + } - RGBEByteToRGBHalf( image_rgba_data, j * 4, halfArray, j * 4 ); + data = floatArray; + type = FloatType; + break; - } + case HalfFloatType: - data = halfArray; - type = HalfFloatType; - break; + numElements = image_rgba_data.length / 4; + const halfArray = new Uint16Array( numElements * 4 ); - default: + for ( let j = 0; j < numElements; j ++ ) { - console.error( 'THREE.RGBELoader: unsupported type: ', this.type ); - break; + RGBEByteToRGBHalf( image_rgba_data, j * 4, halfArray, j * 4 ); } - return { - width: w, height: h, - data: data, - header: rgbe_header_info.string, - gamma: rgbe_header_info.gamma, - exposure: rgbe_header_info.exposure, - type: type - }; + data = halfArray; + type = HalfFloatType; + break; - } + default: + + throw new Error( 'THREE.RGBELoader: Unsupported type: ' + this.type ); + break; } - return null; + return { + width: w, height: h, + data: data, + header: rgbe_header_info.string, + gamma: rgbe_header_info.gamma, + exposure: rgbe_header_info.exposure, + type: type + }; } + /** + * Sets the texture type. + * + * @param {(HalfFloatType|FloatType)} value - The texture type to set. + * @return {RGBELoader} A reference to this loader. + */ setDataType( value ) { this.type = value; diff --git a/examples/jsm/loaders/RGBMLoader.js b/examples/jsm/loaders/RGBMLoader.js index 44420c47869f37..aa361b8baff683 100644 --- a/examples/jsm/loaders/RGBMLoader.js +++ b/examples/jsm/loaders/RGBMLoader.js @@ -7,17 +7,54 @@ import { DataUtils } from 'three'; +/** + * A loader for the RGBM HDR texture format. + * + * ```js + * const loader = new RGBMLoader(); + * loader.setMaxRange( 16 ); + * + * const texture = await loader.loadAsync( 'textures/memorial.png' ); + * ``` + * + * @augments DataTextureLoader + * @three_import import { RGBMLoader } from 'three/addons/loaders/RGBMLoader.js'; + */ class RGBMLoader extends DataTextureLoader { + /** + * Constructs a new RGBM loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); + /** + * The texture type. + * + * @type {(HalfFloatType|FloatType)} + * @default HalfFloatType + */ this.type = HalfFloatType; - this.maxRange = 7; // more information about this property at https://iwasbeingirony.blogspot.com/2010/06/difference-between-rgbm-and-rgbd.html + + /** + * More information about this property at [The difference between RGBM and RGBD]{@link https://iwasbeingirony.blogspot.com/2010/06/difference-between-rgbm-and-rgbd.html} + * + * @type {(7|16)} + * @default 7 + */ + this.maxRange = 7; } + /** + * Sets the texture type. + * + * @param {(HalfFloatType|FloatType)} value - The texture type to set. + * @return {RGBMLoader} A reference to this loader. + */ setDataType( value ) { this.type = value; @@ -25,6 +62,12 @@ class RGBMLoader extends DataTextureLoader { } + /** + * Sets the maximum range. + * + * @param {(7|16)} value - The maximum range to set. + * @return {RGBMLoader} A reference to this loader. + */ setMaxRange( value ) { this.maxRange = value; @@ -32,10 +75,26 @@ class RGBMLoader extends DataTextureLoader { } + /** + * Starts loading from the given URLs and passes the loaded RGBM cube map + * to the `onLoad()` callback. + * + * @param {Array} urls - The paths/URLs of the files to be loaded. This can also be a data URIs. + * @param {function(CubeTexture)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + * @return {CubeTexture} The cube texture. + */ loadCubemap( urls, onLoad, onProgress, onError ) { const texture = new CubeTexture(); + for ( let i = 0; i < 6; i ++ ) { + + texture.images[ i ] = undefined; + + } + let loaded = 0; const scope = this; @@ -75,6 +134,30 @@ class RGBMLoader extends DataTextureLoader { } + /** + * Async version of {@link RGBMLoader#loadCubemap}. + * + * @async + * @param {Array} urls - The paths/URLs of the files to be loaded. This can also be a data URIs. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @return {Promise} A Promise that resolves with the loaded cube map. + */ + loadCubemapAsync( urls, onProgress ) { + + return new Promise( ( resolve, reject ) => { + + this.loadCubemap( urls, resolve, onProgress, reject ); + + } ); + + } + + /** + * Parses the given RGBM texture data. + * + * @param {ArrayBuffer} buffer - The raw texture data. + * @return {DataTextureLoader~TexData} An object representing the parsed texture data. + */ parse( buffer ) { const img = UPNG.decode( buffer ); diff --git a/examples/jsm/loaders/STLLoader.js b/examples/jsm/loaders/STLLoader.js index 1328d85f839393..45f8ddfcab5882 100644 --- a/examples/jsm/loaders/STLLoader.js +++ b/examples/jsm/loaders/STLLoader.js @@ -1,72 +1,78 @@ import { BufferAttribute, BufferGeometry, + Color, FileLoader, Float32BufferAttribute, Loader, - Vector3 + Vector3, + SRGBColorSpace } from 'three'; /** - * Description: A THREE loader for STL ASCII files, as created by Solidworks and other CAD programs. + * A loader for the STL format, as created by Solidworks and other CAD programs. * - * Supports both binary and ASCII encoded files, with automatic detection of type. - * - * The loader returns a non-indexed buffer geometry. + * Supports both binary and ASCII encoded files. The loader returns a non-indexed buffer geometry. * * Limitations: - * Binary decoding supports "Magics" color format (http://en.wikipedia.org/wiki/STL_(file_format)#Color_in_binary_STL). - * There is perhaps some question as to how valid it is to always assume little-endian-ness. - * ASCII decoding assumes file is UTF-8. - * - * Usage: - * const loader = new STLLoader(); - * loader.load( './models/stl/slotted_disk.stl', function ( geometry ) { - * scene.add( new THREE.Mesh( geometry ) ); - * }); + * - Binary decoding supports "Magics" color format (http://en.wikipedia.org/wiki/STL_(file_format)#Color_in_binary_STL). + * - There is perhaps some question as to how valid it is to always assume little-endian-ness. + * - ASCII decoding assumes file is UTF-8. * + * ```js + * const loader = new STLLoader(); + * const geometry = await loader.loadAsync( './models/stl/slotted_disk.stl' ) + * scene.add( new THREE.Mesh( geometry ) ); + * ``` * For binary STLs geometry might contain colors for vertices. To use it: - * // use the same code to load STL as above - * if (geometry.hasColors) { - * material = new THREE.MeshPhongMaterial({ opacity: geometry.alpha, vertexColors: true }); - * } else { .... } - * const mesh = new THREE.Mesh( geometry, material ); - * + * ```js + * // use the same code to load STL as above + * if ( geometry.hasColors ) { + * material = new THREE.MeshPhongMaterial( { opacity: geometry.alpha, vertexColors: true } ); + * } + * const mesh = new THREE.Mesh( geometry, material ); + * ``` * For ASCII STLs containing multiple solids, each solid is assigned to a different group. * Groups can be used to assign a different color by defining an array of materials with the same length of * geometry.groups and passing it to the Mesh constructor: * - * const mesh = new THREE.Mesh( geometry, material ); - * - * For example: - * - * const materials = []; - * const nGeometryGroups = geometry.groups.length; - * - * const colorMap = ...; // Some logic to index colors. + * ```js + * const materials = []; + * const nGeometryGroups = geometry.groups.length; * - * for (let i = 0; i < nGeometryGroups; i++) { + * for ( let i = 0; i < nGeometryGroups; i ++ ) { + * const material = new THREE.MeshPhongMaterial( { color: colorMap[ i ], wireframe: false } ); + * materials.push( material ); + * } * - * const material = new THREE.MeshPhongMaterial({ - * color: colorMap[i], - * wireframe: false - * }); + * const mesh = new THREE.Mesh(geometry, materials); + * ``` * - * } - * - * materials.push(material); - * const mesh = new THREE.Mesh(geometry, materials); + * @augments Loader + * @three_import import { STLLoader } from 'three/addons/loaders/STLLoader.js'; */ - - class STLLoader extends Loader { + /** + * Constructs a new STL loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); } + /** + * Starts loading from the given URL and passes the loaded STL asset + * to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(BufferGeometry)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ load( url, onLoad, onProgress, onError ) { const scope = this; @@ -103,6 +109,12 @@ class STLLoader extends Loader { } + /** + * Parses the given STL data and returns the resulting geometry. + * + * @param {ArrayBuffer} data - The raw STL data as an array buffer. + * @return {BufferGeometry} The parsed geometry. + */ parse( data ) { function isBinary( data ) { @@ -195,6 +207,8 @@ class STLLoader extends Loader { const vertices = new Float32Array( faces * 3 * 3 ); const normals = new Float32Array( faces * 3 * 3 ); + const color = new Color(); + for ( let face = 0; face < faces; face ++ ) { const start = dataOffset + face * faceLength; @@ -239,9 +253,11 @@ class STLLoader extends Loader { if ( hasColors ) { - colors[ componentIdx ] = r; - colors[ componentIdx + 1 ] = g; - colors[ componentIdx + 2 ] = b; + color.setRGB( r, g, b, SRGBColorSpace ); + + colors[ componentIdx ] = color.r; + colors[ componentIdx + 1 ] = color.g; + colors[ componentIdx + 2 ] = color.b; } @@ -269,6 +285,7 @@ class STLLoader extends Loader { const geometry = new BufferGeometry(); const patternSolid = /solid([\s\S]*?)endsolid/g; const patternFace = /facet([\s\S]*?)endfacet/g; + const patternName = /solid\s(.+)/; let faceCounter = 0; const patternFloat = /[\s]+([+-]?(?:\d*)(?:\.\d*)?(?:[eE][+-]?\d+)?)/.source; @@ -277,6 +294,7 @@ class STLLoader extends Loader { const vertices = []; const normals = []; + const groupNames = []; const normal = new Vector3(); @@ -292,6 +310,9 @@ class STLLoader extends Loader { const solid = result[ 0 ]; + const name = ( result = patternName.exec( solid ) ) !== null ? result[ 1 ] : ''; + groupNames.push( name ); + while ( ( result = patternFace.exec( solid ) ) !== null ) { let vertexCountPerFace = 0; @@ -340,6 +361,8 @@ class STLLoader extends Loader { const start = startVertex; const count = endVertex - startVertex; + geometry.userData.groupNames = groupNames; + geometry.addGroup( start, count, groupCount ); groupCount ++; diff --git a/examples/jsm/loaders/SVGLoader.js b/examples/jsm/loaders/SVGLoader.js index 76a081b9ef821e..055ddd70adb2dc 100644 --- a/examples/jsm/loaders/SVGLoader.js +++ b/examples/jsm/loaders/SVGLoader.js @@ -16,20 +16,85 @@ import { const COLOR_SPACE_SVG = SRGBColorSpace; +/** + * A loader for the SVG format. + * + * Scalable Vector Graphics is an XML-based vector image format for two-dimensional graphics + * with support for interactivity and animation. + * + * ```js + * const loader = new SVGLoader(); + * const data = await loader.loadAsync( 'data/svgSample.svg' ); + * + * const paths = data.paths; + * const group = new THREE.Group(); + * + * for ( let i = 0; i < paths.length; i ++ ) { + * + * const path = paths[ i ]; + * const material = new THREE.MeshBasicMaterial( { + * color: path.color, + * side: THREE.DoubleSide, + * depthWrite: false + * } ); + * + * const shapes = SVGLoader.createShapes( path ); + * + * for ( let j = 0; j < shapes.length; j ++ ) { + * + * const shape = shapes[ j ]; + * const geometry = new THREE.ShapeGeometry( shape ); + * const mesh = new THREE.Mesh( geometry, material ); + * group.add( mesh ); + * + * } + * + * } + * + * scene.add( group ); + * ``` + * + * @augments Loader + * @three_import import { SVGLoader } from 'three/addons/loaders/SVGLoader.js'; + */ class SVGLoader extends Loader { + /** + * Constructs a new SVG loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); - // Default dots per inch + /** + * Default dots per inch. + * + * @type {number} + * @default 90 + */ this.defaultDPI = 90; - // Accepted units: 'mm', 'cm', 'in', 'pt', 'pc', 'px' + /** + * Default unit. + * + * @type {('mm'|'cm'|'in'|'pt'|'pc'|'px')} + * @default 'px' + */ this.defaultUnit = 'px'; } + /** + * Starts loading from the given URL and passes the loaded SVG asset + * to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function({paths:Array,xml:string})} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ load( url, onLoad, onProgress, onError ) { const scope = this; @@ -64,6 +129,13 @@ class SVGLoader extends Loader { } + /** + * Parses the given SVG data and returns the resulting data. + * + * @param {string} text - The raw SVG data as a string. + * @return {{paths:Array,xml:string}} An object holding an array of shape paths and the + * SVG XML document. + */ parse( text ) { const scope = this; @@ -775,6 +847,7 @@ class SVGLoader extends Loader { * According to https://www.w3.org/TR/SVG/shapes.html#RectElementRXAttribute * rounded corner should be rendered to elliptical arc, but bezier curve does the job well enough */ + function parseRectNode( node ) { const x = parseFloatWithUnits( node.getAttribute( 'x' ) || 0 ); @@ -1633,7 +1706,7 @@ class SVGLoader extends Loader { ( curve.aEndAngle - curve.aStartAngle ) % ( 2 * Math.PI ) < Number.EPSILON; // Do not touch angles of a full ellipse because after transformation they - // would converge to a sinle value effectively removing the whole curve + // would converge to a single value effectively removing the whole curve if ( ! isFullEllipse ) { @@ -1916,11 +1989,14 @@ class SVGLoader extends Loader { } + /** + * Creates from the given shape path and array of shapes. + * + * @param {ShapePath} shapePath - The shape path. + * @return {Array} An array of shapes. + */ static createShapes( shapePath ) { - // Param shapePath: a shapepath as returned by the parse function of this class - // Returns Shape object - const BIGNUMBER = 999999999; const IntersectionLocationType = { @@ -2124,7 +2200,7 @@ class SVGLoader extends Loader { // check if the center of the bounding box is in the bounding box of the paths. // this is a pruning method to limit the search of intersections in paths that can't envelop of the current path. - // if a path envelops another path. The center of that oter path, has to be inside the bounding box of the enveloping path. + // if a path envelops another path. The center of that other path, has to be inside the bounding box of the enveloping path. if ( path.boundingBox.containsPoint( center ) ) { const intersections = getIntersections( scanline, path.points ); @@ -2360,15 +2436,18 @@ class SVGLoader extends Loader { } + /** + * Returns a stroke style object from the given parameters. + * + * @param {number} [width=1] - The stroke width. + * @param {string} [color='#000'] - The stroke color, as returned by {@link Color#getStyle}. + * @param {'round'|'bevel'|'miter'|'miter-limit'} [lineJoin='miter'] - The line join style. + * @param {'round'|'square'|'butt'} [lineCap='butt'] - The line cap style. + * @param {number} [miterLimit=4] - Maximum join length, in multiples of the `width` parameter (join is truncated if it exceeds that distance). + * @return {Object} The style object. + */ static getStrokeStyle( width, color, lineJoin, lineCap, miterLimit ) { - // Param width: Stroke width - // Param color: As returned by THREE.Color.getStyle() - // Param lineJoin: One of "round", "bevel", "miter" or "miter-limit" - // Param lineCap: One of "round", "square" or "butt" - // Param miterLimit: Maximum join length, in multiples of the "width" parameter (join is truncated if it exceeds that distance) - // Returns style object - width = width !== undefined ? width : 1; color = color !== undefined ? color : '#000'; lineJoin = lineJoin !== undefined ? lineJoin : 'miter'; @@ -2385,16 +2464,18 @@ class SVGLoader extends Loader { } + /** + * Creates a stroke from an array of points. + * + * @param {Array} points - The points in 2D space. Minimum 2 points. The path can be open or closed (last point equals to first point). + * @param {Object} style - Object with SVG properties as returned by `SVGLoader.getStrokeStyle()`, or `SVGLoader.parse()` in the `path.userData.style` object. + * @param {number} [arcDivisions=12] - Arc divisions for round joins and endcaps. + * @param {number} [minDistance=0.001] - Points closer to this distance will be merged. + * @return {?BufferGeometry} The stroke geometry. UV coordinates are generated ('u' along path. 'v' across it, from left to right). + * Returns `null` if not geometry was generated. + */ static pointsToStroke( points, style, arcDivisions, minDistance ) { - // Generates a stroke with some width around the given path. - // The path can be open or closed (last point equals to first point) - // Param points: Array of Vector2D (the path). Minimum 2 points. - // Param style: Object with SVG properties as returned by SVGLoader.getStrokeStyle(), or SVGLoader.parse() in the path.userData.style object - // Params arcDivisions: Arc divisions for round joins and endcaps. (Optional) - // Param minDistance: Points closer to this distance will be merged. (Optional) - // Returns BufferGeometry with stroke triangles (In plane z = 0). UV coordinates are generated ('u' along path. 'v' across it, from left to right) - const vertices = []; const normals = []; const uvs = []; @@ -2414,6 +2495,19 @@ class SVGLoader extends Loader { } + /** + * Creates a stroke from an array of points. + * + * @param {Array} points - The points in 2D space. Minimum 2 points. + * @param {Object} style - Object with SVG properties as returned by `SVGLoader.getStrokeStyle()`, or `SVGLoader.parse()` in the `path.userData.style` object. + * @param {number} [arcDivisions=12] - Arc divisions for round joins and endcaps. + * @param {number} [minDistance=0.001] - Points closer to this distance will be merged. + * @param {Array} vertices - An array holding vertices. + * @param {Array} normals - An array holding normals. + * @param {Array} uvs - An array holding uvs. + * @param {number} [vertexOffset=0] - The vertex offset. + * @return {number} The number of vertices. + */ static pointsToStrokeWithBuffers( points, style, arcDivisions, minDistance, vertices, normals, uvs, vertexOffset ) { // This function can be called to update existing arrays or buffers. @@ -2924,8 +3018,8 @@ class SVGLoader extends Loader { addVertex( currentPointL, u1, 0 ); addVertex( lastPointR, u0, 1 ); - addVertex( currentPointL, u1, 1 ); - addVertex( currentPointR, u1, 0 ); + addVertex( currentPointL, u1, 0 ); + addVertex( currentPointR, u1, 1 ); } @@ -2968,8 +3062,8 @@ class SVGLoader extends Loader { // Bevel join triangle addVertex( currentPointR, u, 1 ); - addVertex( nextPointR, u, 0 ); - addVertex( innerPoint, u, 0.5 ); + addVertex( innerPoint, u, 0 ); + addVertex( nextPointR, u, 1 ); } @@ -3082,7 +3176,8 @@ class SVGLoader extends Loader { } else { tempV2_3.toArray( vertices, 1 * 3 ); - tempV2_3.toArray( vertices, 3 * 3 ); + // using tempV2_4 to update 3rd vertex if the uv.y of 3rd vertex is 1 + uvs[ 3 * 2 + 1 ] === 1 ? tempV2_4.toArray( vertices, 3 * 3 ) : tempV2_3.toArray( vertices, 3 * 3 ); tempV2_4.toArray( vertices, 0 * 3 ); } @@ -3106,8 +3201,8 @@ class SVGLoader extends Loader { } else { - tempV2_3.toArray( vertices, vl - 2 * 3 ); - tempV2_4.toArray( vertices, vl - 1 * 3 ); + tempV2_4.toArray( vertices, vl - 2 * 3 ); + tempV2_3.toArray( vertices, vl - 1 * 3 ); tempV2_4.toArray( vertices, vl - 4 * 3 ); } diff --git a/examples/jsm/loaders/TDSLoader.js b/examples/jsm/loaders/TDSLoader.js index 0eeeaf1be71147..00ab3d24932fd5 100644 --- a/examples/jsm/loaders/TDSLoader.js +++ b/examples/jsm/loaders/TDSLoader.js @@ -15,22 +15,40 @@ import { } from 'three'; /** - * Autodesk 3DS three.js file loader, based on lib3ds. + * A loader for the 3DS format, based on lib3ds. * * Loads geometry with uv and materials basic properties with texture support. * - * @class TDSLoader - * @constructor + * ```js + * const loader = new TDSLoader(); + * loader.setResourcePath( 'models/3ds/portalgun/textures/' ); + * const object = await loader.loadAsync( 'models/3ds/portalgun/portalgun.3ds' ); + * scene.add( object ); + * + * @augments Loader + * @three_import import { TDSLoader } from 'three/addons/loaders/TDSLoader.js'; */ - class TDSLoader extends Loader { + /** + * Constructs a new 3DS loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); + /** + * Whether debug mode should be enabled or not. + * + * @type {boolean} + * @default false + */ this.debug = false; + // internals + this.group = null; this.materials = []; @@ -39,13 +57,13 @@ class TDSLoader extends Loader { } /** - * Load 3ds file from url. + * Starts loading from the given URL and passes the loaded 3DS asset + * to the `onLoad()` callback. * - * @method load - * @param {[type]} url URL for the file. - * @param {Function} onLoad onLoad callback, receives group Object3D as argument. - * @param {Function} onProgress onProgress callback. - * @param {Function} onError onError callback. + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(Group)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. */ load( url, onLoad, onProgress, onError ) { @@ -86,12 +104,11 @@ class TDSLoader extends Loader { } /** - * Parse arraybuffer data and load 3ds file. + * Parses the given 3DS data and returns the resulting data. * - * @method parse - * @param {ArrayBuffer} arraybuffer Arraybuffer data to be loaded. - * @param {String} path Path for external resources. - * @return {Group} Group loaded from 3ds file. + * @param {ArrayBuffer} arraybuffer - The raw 3DS data as an array buffer. + * @param {string} path - The asset path. + * @return {Group} The parsed asset represented as a group. */ parse( arraybuffer, path ) { @@ -114,9 +131,9 @@ class TDSLoader extends Loader { /** * Decode file content to read 3ds data. * - * @method readFile - * @param {ArrayBuffer} arraybuffer Arraybuffer data to be loaded. - * @param {String} path Path for external resources. + * @private + * @param {ArrayBuffer} arraybuffer - Arraybuffer data to be loaded. + * @param {string} path - Path for external resources. */ readFile( arraybuffer, path ) { @@ -157,9 +174,9 @@ class TDSLoader extends Loader { /** * Read mesh data chunk. * - * @method readMeshData - * @param {Chunk} chunk to read mesh from - * @param {String} path Path for external resources. + * @private + * @param {Chunk} chunk - to read mesh from + * @param {string} path - Path for external resources. */ readMeshData( chunk, path ) { @@ -203,8 +220,8 @@ class TDSLoader extends Loader { /** * Read named object chunk. * - * @method readNamedObject - * @param {Chunk} chunk Chunk in use. + * @private + * @param {Chunk} chunk - Chunk in use. */ readNamedObject( chunk ) { @@ -234,9 +251,9 @@ class TDSLoader extends Loader { /** * Read material data chunk and add it to the material list. * - * @method readMaterialEntry - * @param {Chunk} chunk Chunk in use. - * @param {String} path Path for external resources. + * @private + * @param {Chunk} chunk - Chunk in use. + * @param {string} path - Path for external resources. */ readMaterialEntry( chunk, path ) { @@ -336,9 +353,9 @@ class TDSLoader extends Loader { /** * Read mesh data chunk. * - * @method readMesh - * @param {Chunk} chunk Chunk in use. - * @return {Mesh} The parsed mesh. + * @private + * @param {Chunk} chunk - Chunk in use. + * @return {Mesh} - The parsed mesh. */ readMesh( chunk ) { @@ -398,7 +415,7 @@ class TDSLoader extends Loader { } else if ( next.id === MESH_MATRIX ) { - this.debugMessage( ' Tranformation Matrix (TODO)' ); + this.debugMessage( ' Transformation Matrix (TODO)' ); const values = []; for ( let i = 0; i < 12; i ++ ) { @@ -460,9 +477,9 @@ class TDSLoader extends Loader { /** * Read face array data chunk. * - * @method readFaceArray - * @param {Chunk} chunk Chunk in use. - * @param {Mesh} mesh Mesh to be filled with the data read. + * @private + * @param {Chunk} chunk - Chunk in use. + * @param {Mesh} mesh - Mesh to be filled with the data read. */ readFaceArray( chunk, mesh ) { @@ -528,9 +545,9 @@ class TDSLoader extends Loader { /** * Read texture map data chunk. * - * @method readMap - * @param {Chunk} chunk Chunk in use. - * @param {String} path Path for external resources. + * @private + * @param {Chunk} chunk - Chunk in use. + * @param {string} path - Path for external resources. * @return {Texture} Texture read from this data chunk. */ readMap( chunk, path ) { @@ -587,8 +604,8 @@ class TDSLoader extends Loader { /** * Read material group data chunk. * - * @method readMaterialGroup - * @param {Chunk} chunk Chunk in use. + * @private + * @param {Chunk} chunk - Chunk in use. * @return {Object} Object with name and index of the object. */ readMaterialGroup( chunk ) { @@ -613,9 +630,9 @@ class TDSLoader extends Loader { /** * Read a color value. * - * @method readColor - * @param {Chunk} chunk Chunk. - * @return {Color} Color value read.. + * @private + * @param {Chunk} chunk - Chunk. + * @return {Color} Color value read. */ readColor( chunk ) { @@ -655,9 +672,9 @@ class TDSLoader extends Loader { /** * Read percentage value. * - * @method readPercentage - * @param {Chunk} chunk Chunk to read data from. - * @return {Number} Data read from the dataview. + * @private + * @param {Chunk} chunk - Chunk to read data from. + * @return {number} Data read from the dataview. */ readPercentage( chunk ) { @@ -686,8 +703,8 @@ class TDSLoader extends Loader { * * Is controlled by a flag to show or hide debug messages. * - * @method debugMessage - * @param {Object} message Debug message to print to the console. + * @private + * @param {Object} message - Debug message to print to the console. */ debugMessage( message ) { @@ -701,17 +718,20 @@ class TDSLoader extends Loader { } - -/** Read data/sub-chunks from chunk */ +/** + * Read data/sub-chunks from chunk. + * + * @private + */ class Chunk { /** * Create a new chunk * - * @class Chunk - * @param {DataView} data DataView to read from. - * @param {Number} position in data. - * @param {Function} debugMessage logging callback. + * @private + * @param {DataView} data - DataView to read from. + * @param {number} position - In data. + * @param {Function} debugMessage - Logging callback. */ constructor( data, position, debugMessage ) { @@ -741,10 +761,10 @@ class Chunk { } /** - * read a sub cchunk. + * Reads a sub cchunk. * - * @method readChunk - * @return {Chunk | null} next sub chunk + * @private + * @return {Chunk | null} next sub chunk. */ readChunk() { @@ -770,10 +790,10 @@ class Chunk { } /** - * return the ID of this chunk as Hex + * Returns the ID of this chunk as Hex * - * @method idToString - * @return {String} hex-string of id + * @private + * @return {string} hex-string of id */ get hexId() { @@ -790,8 +810,8 @@ class Chunk { /** * Read byte value. * - * @method readByte - * @return {Number} Data read from the dataview. + * @private + * @return {number} Data read from the dataview. */ readByte() { @@ -804,8 +824,8 @@ class Chunk { /** * Read 32 bit float value. * - * @method readFloat - * @return {Number} Data read from the dataview. + * @private + * @return {number} Data read from the dataview. */ readFloat() { @@ -827,8 +847,8 @@ class Chunk { /** * Read 32 bit signed integer value. * - * @method readInt - * @return {Number} Data read from the dataview. + * @private + * @return {number} Data read from the dataview. */ readInt() { @@ -841,8 +861,8 @@ class Chunk { /** * Read 16 bit signed integer value. * - * @method readShort - * @return {Number} Data read from the dataview. + * @private + * @return {number} Data read from the dataview. */ readShort() { @@ -855,8 +875,8 @@ class Chunk { /** * Read 64 bit unsigned integer value. * - * @method readDWord - * @return {Number} Data read from the dataview. + * @private + * @return {number} Data read from the dataview. */ readDWord() { @@ -869,8 +889,8 @@ class Chunk { /** * Read 32 bit unsigned integer value. * - * @method readWord - * @return {Number} Data read from the dataview. + * @private + * @return {number} Data read from the dataview. */ readWord() { @@ -883,8 +903,8 @@ class Chunk { /** * Read NULL terminated ASCII string value from chunk-pos. * - * @method readString - * @return {String} Data read from the dataview. + * @private + * @return {string} Data read from the dataview. */ readString() { diff --git a/examples/jsm/loaders/TGALoader.js b/examples/jsm/loaders/TGALoader.js index fb35c43809ad92..14fd3f0ff0287a 100644 --- a/examples/jsm/loaders/TGALoader.js +++ b/examples/jsm/loaders/TGALoader.js @@ -3,14 +3,37 @@ import { LinearMipmapLinearFilter } from 'three'; +/** + * A loader for the TGA texture format. + * + * ```js + * const loader = new TGALoader(); + * const texture = await loader.loadAsync( 'textures/crate_color8.tga' ); + * texture.colorSpace = THREE.SRGBColorSpace; // only for color textures + * ``` + * + * @augments DataTextureLoader + * @three_import import { TGALoader } from 'three/addons/loaders/TGALoader.js'; + */ class TGALoader extends DataTextureLoader { + /** + * Constructs a new TGA loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); } + /** + * Parses the given TGA texture data. + * + * @param {ArrayBuffer} buffer - The raw texture data. + * @return {DataTextureLoader~TexData} An object representing the parsed texture data. + */ parse( buffer ) { // reference from vthibault, https://github.com/vthibault/roBrowser/blob/master/src/Loaders/Targa.js @@ -25,7 +48,7 @@ class TGALoader extends DataTextureLoader { case TGA_TYPE_RLE_INDEXED: if ( header.colormap_length > 256 || header.colormap_size !== 24 || header.colormap_type !== 1 ) { - console.error( 'THREE.TGALoader: Invalid type colormap data for indexed type.' ); + throw new Error( 'THREE.TGALoader: Invalid type colormap data for indexed type.' ); } @@ -39,7 +62,7 @@ class TGALoader extends DataTextureLoader { case TGA_TYPE_RLE_GREY: if ( header.colormap_type ) { - console.error( 'THREE.TGALoader: Invalid type colormap data for colormap type.' ); + throw new Error( 'THREE.TGALoader: Invalid type colormap data for colormap type.' ); } @@ -48,12 +71,12 @@ class TGALoader extends DataTextureLoader { // What the need of a file without data ? case TGA_TYPE_NO_DATA: - console.error( 'THREE.TGALoader: No data.' ); + throw new Error( 'THREE.TGALoader: No data.' ); // Invalid type ? default: - console.error( 'THREE.TGALoader: Invalid type "%s".', header.image_type ); + throw new Error( 'THREE.TGALoader: Invalid type ' + header.image_type ); } @@ -61,7 +84,7 @@ class TGALoader extends DataTextureLoader { if ( header.width <= 0 || header.height <= 0 ) { - console.error( 'THREE.TGALoader: Invalid image size.' ); + throw new Error( 'THREE.TGALoader: Invalid image size.' ); } @@ -70,7 +93,7 @@ class TGALoader extends DataTextureLoader { if ( header.pixel_size !== 8 && header.pixel_size !== 16 && header.pixel_size !== 24 && header.pixel_size !== 32 ) { - console.error( 'THREE.TGALoader: Invalid pixel size "%s".', header.pixel_size ); + throw new Error( 'THREE.TGALoader: Invalid pixel size ' + header.pixel_size ); } @@ -365,7 +388,7 @@ class TGALoader extends DataTextureLoader { break; default: - console.error( 'THREE.TGALoader: Format not supported.' ); + throw new Error( 'THREE.TGALoader: Format not supported.' ); break; } @@ -391,7 +414,7 @@ class TGALoader extends DataTextureLoader { break; default: - console.error( 'THREE.TGALoader: Format not supported.' ); + throw new Error( 'THREE.TGALoader: Format not supported.' ); break; } @@ -422,7 +445,7 @@ class TGALoader extends DataTextureLoader { TGA_ORIGIN_UL = 0x02, TGA_ORIGIN_UR = 0x03; - if ( buffer.length < 19 ) console.error( 'THREE.TGALoader: Not enough data to contain header.' ); + if ( buffer.length < 19 ) throw new Error( 'THREE.TGALoader: Not enough data to contain header.' ); let offset = 0; @@ -450,7 +473,7 @@ class TGALoader extends DataTextureLoader { if ( header.id_length + offset > buffer.length ) { - console.error( 'THREE.TGALoader: No data.' ); + throw new Error( 'THREE.TGALoader: No data.' ); } diff --git a/examples/jsm/loaders/TIFFLoader.js b/examples/jsm/loaders/TIFFLoader.js index 950361f7400da2..07b941e966496b 100644 --- a/examples/jsm/loaders/TIFFLoader.js +++ b/examples/jsm/loaders/TIFFLoader.js @@ -6,14 +6,37 @@ import { import UTIF from '../libs/utif.module.js'; +/** + * A loader for the TIFF texture format. + * + * ```js + * const loader = new TIFFLoader(); + * const texture = await loader.loadAsync( 'textures/tiff/crate_lzw.tif' ); + * texture.colorSpace = THREE.SRGBColorSpace; + * ``` + * + * @augments DataTextureLoader + * @three_import import { TIFFLoader } from 'three/addons/loaders/TIFFLoader.js'; + */ class TIFFLoader extends DataTextureLoader { + /** + * Constructs a new TIFF loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); } + /** + * Parses the given TIFF texture data. + * + * @param {ArrayBuffer} buffer - The raw texture data. + * @return {DataTextureLoader~TexData} An object representing the parsed texture data. + */ parse( buffer ) { const ifds = UTIF.decode( buffer ); diff --git a/examples/jsm/loaders/TTFLoader.js b/examples/jsm/loaders/TTFLoader.js index d681fa37ec890e..0863e9358e32a6 100644 --- a/examples/jsm/loaders/TTFLoader.js +++ b/examples/jsm/loaders/TTFLoader.js @@ -5,21 +5,50 @@ import { import opentype from '../libs/opentype.module.js'; /** - * Requires opentype.js to be included in the project. + * A loader for the TTF format. + * * Loads TTF files and converts them into typeface JSON that can be used directly * to create THREE.Font objects. + * + * ```js + * const loader = new TTFLoader(); + * const json = await loader.loadAsync( 'fonts/ttf/kenpixel.ttf' ); + * const font = new Font( json ); + * ``` + * + * @augments Loader + * @three_import import { TTFLoader } from 'three/addons/loaders/TTFLoader.js'; */ - class TTFLoader extends Loader { + /** + * Constructs a new TTF loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); + /** + * Whether the TTF commands should be reversed or not. + * + * @type {boolean} + * @default false + */ this.reversed = false; } + /** + * Starts loading from the given URL and passes the loaded TTF asset + * to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(Object)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ load( url, onLoad, onProgress, onError ) { const scope = this; @@ -55,6 +84,12 @@ class TTFLoader extends Loader { } + /** + * Parses the given TTF data and returns a JSON for creating a font. + * + * @param {ArrayBuffer} arraybuffer - The raw TTF data as an array buffer. + * @return {Object} The result JSON. + */ parse( arraybuffer ) { function convert( font, reversed ) { diff --git a/examples/jsm/loaders/TiltLoader.js b/examples/jsm/loaders/TiltLoader.js deleted file mode 100644 index b8e6a8d935eceb..00000000000000 --- a/examples/jsm/loaders/TiltLoader.js +++ /dev/null @@ -1,520 +0,0 @@ -import { - BufferAttribute, - BufferGeometry, - Color, - DoubleSide, - FileLoader, - Group, - Loader, - Mesh, - MeshBasicMaterial, - RawShaderMaterial, - TextureLoader, - Quaternion, - Vector3 -} from 'three'; -import * as fflate from '../libs/fflate.module.js'; - -class TiltLoader extends Loader { - - load( url, onLoad, onProgress, onError ) { - - const scope = this; - - const loader = new FileLoader( this.manager ); - loader.setPath( this.path ); - loader.setResponseType( 'arraybuffer' ); - loader.setWithCredentials( this.withCredentials ); - - loader.load( url, function ( buffer ) { - - try { - - onLoad( scope.parse( buffer ) ); - - } catch ( e ) { - - if ( onError ) { - - onError( e ); - - } else { - - console.error( e ); - - } - - scope.manager.itemError( url ); - - } - - }, onProgress, onError ); - - } - - parse( buffer ) { - - const group = new Group(); - // https://docs.google.com/document/d/11ZsHozYn9FnWG7y3s3WAyKIACfbfwb4PbaS8cZ_xjvo/edit# - - const zip = fflate.unzipSync( new Uint8Array( buffer.slice( 16 ) ) ); - - /* - const thumbnail = zip[ 'thumbnail.png' ].buffer; - const img = document.createElement( 'img' ); - img.src = URL.createObjectURL( new Blob( [ thumbnail ] ) ); - document.body.appendChild( img ); - */ - - const metadata = JSON.parse( fflate.strFromU8( zip[ 'metadata.json' ] ) ); - - /* - const blob = new Blob( [ zip[ 'data.sketch' ].buffer ], { type: 'application/octet-stream' } ); - window.open( URL.createObjectURL( blob ) ); - */ - - const data = new DataView( zip[ 'data.sketch' ].buffer ); - - const num_strokes = data.getInt32( 16, true ); - - const brushes = {}; - - let offset = 20; - - for ( let i = 0; i < num_strokes; i ++ ) { - - const brush_index = data.getInt32( offset, true ); - - const brush_color = [ - data.getFloat32( offset + 4, true ), - data.getFloat32( offset + 8, true ), - data.getFloat32( offset + 12, true ), - data.getFloat32( offset + 16, true ) - ]; - const brush_size = data.getFloat32( offset + 20, true ); - const stroke_mask = data.getUint32( offset + 24, true ); - const controlpoint_mask = data.getUint32( offset + 28, true ); - - let offset_stroke_mask = 0; - let offset_controlpoint_mask = 0; - - for ( let j = 0; j < 4; j ++ ) { - - // TOFIX: I don't understand these masks yet - - const byte = 1 << j; - if ( ( stroke_mask & byte ) > 0 ) offset_stroke_mask += 4; - if ( ( controlpoint_mask & byte ) > 0 ) offset_controlpoint_mask += 4; - - } - - // console.log( { brush_index, brush_color, brush_size, stroke_mask, controlpoint_mask } ); - // console.log( offset_stroke_mask, offset_controlpoint_mask ); - - offset = offset + 28 + offset_stroke_mask + 4; // TOFIX: This is wrong - - const num_control_points = data.getInt32( offset, true ); - - // console.log( { num_control_points } ); - - const positions = new Float32Array( num_control_points * 3 ); - const quaternions = new Float32Array( num_control_points * 4 ); - - offset = offset + 4; - - for ( let j = 0, k = 0; j < positions.length; j += 3, k += 4 ) { - - positions[ j + 0 ] = data.getFloat32( offset + 0, true ); - positions[ j + 1 ] = data.getFloat32( offset + 4, true ); - positions[ j + 2 ] = data.getFloat32( offset + 8, true ); - - quaternions[ k + 0 ] = data.getFloat32( offset + 12, true ); - quaternions[ k + 1 ] = data.getFloat32( offset + 16, true ); - quaternions[ k + 2 ] = data.getFloat32( offset + 20, true ); - quaternions[ k + 3 ] = data.getFloat32( offset + 24, true ); - - offset = offset + 28 + offset_controlpoint_mask; // TOFIX: This is wrong - - } - - if ( brush_index in brushes === false ) { - - brushes[ brush_index ] = []; - - } - - brushes[ brush_index ].push( [ positions, quaternions, brush_size, brush_color ] ); - - } - - for ( const brush_index in brushes ) { - - const geometry = new StrokeGeometry( brushes[ brush_index ] ); - const material = getMaterial( metadata.BrushIndex[ brush_index ] ); - - group.add( new Mesh( geometry, material ) ); - - } - - return group; - - } - -} - -class StrokeGeometry extends BufferGeometry { - - constructor( strokes ) { - - super(); - - const vertices = []; - const colors = []; - const uvs = []; - - const position = new Vector3(); - const prevPosition = new Vector3(); - - const quaternion = new Quaternion(); - const prevQuaternion = new Quaternion(); - - const vector1 = new Vector3(); - const vector2 = new Vector3(); - const vector3 = new Vector3(); - const vector4 = new Vector3(); - - const color = new Color(); - - // size = size / 2; - - for ( const k in strokes ) { - - const stroke = strokes[ k ]; - const positions = stroke[ 0 ]; - const quaternions = stroke[ 1 ]; - const size = stroke[ 2 ]; - const rgba = stroke[ 3 ]; - const alpha = stroke[ 3 ][ 3 ]; - - color.fromArray( rgba ).convertSRGBToLinear(); - - prevPosition.fromArray( positions, 0 ); - prevQuaternion.fromArray( quaternions, 0 ); - - for ( let i = 3, j = 4, l = positions.length; i < l; i += 3, j += 4 ) { - - position.fromArray( positions, i ); - quaternion.fromArray( quaternions, j ); - - vector1.set( - size, 0, 0 ); - vector1.applyQuaternion( quaternion ); - vector1.add( position ); - - vector2.set( size, 0, 0 ); - vector2.applyQuaternion( quaternion ); - vector2.add( position ); - - vector3.set( size, 0, 0 ); - vector3.applyQuaternion( prevQuaternion ); - vector3.add( prevPosition ); - - vector4.set( - size, 0, 0 ); - vector4.applyQuaternion( prevQuaternion ); - vector4.add( prevPosition ); - - vertices.push( vector1.x, vector1.y, - vector1.z ); - vertices.push( vector2.x, vector2.y, - vector2.z ); - vertices.push( vector4.x, vector4.y, - vector4.z ); - - vertices.push( vector2.x, vector2.y, - vector2.z ); - vertices.push( vector3.x, vector3.y, - vector3.z ); - vertices.push( vector4.x, vector4.y, - vector4.z ); - - prevPosition.copy( position ); - prevQuaternion.copy( quaternion ); - - colors.push( ...color, alpha ); - colors.push( ...color, alpha ); - colors.push( ...color, alpha ); - - colors.push( ...color, alpha ); - colors.push( ...color, alpha ); - colors.push( ...color, alpha ); - - const p1 = i / l; - const p2 = ( i - 3 ) / l; - - uvs.push( p1, 0 ); - uvs.push( p1, 1 ); - uvs.push( p2, 0 ); - - uvs.push( p1, 1 ); - uvs.push( p2, 1 ); - uvs.push( p2, 0 ); - - } - - } - - this.setAttribute( 'position', new BufferAttribute( new Float32Array( vertices ), 3 ) ); - this.setAttribute( 'color', new BufferAttribute( new Float32Array( colors ), 4 ) ); - this.setAttribute( 'uv', new BufferAttribute( new Float32Array( uvs ), 2 ) ); - - } - -} - -const BRUSH_LIST_ARRAY = { - '89d104cd-d012-426b-b5b3-bbaee63ac43c': 'Bubbles', - '700f3aa8-9a7c-2384-8b8a-ea028905dd8c': 'CelVinyl', - '0f0ff7b2-a677-45eb-a7d6-0cd7206f4816': 'ChromaticWave', - '1161af82-50cf-47db-9706-0c3576d43c43': 'CoarseBristles', - '79168f10-6961-464a-8be1-57ed364c5600': 'CoarseBristlesSingleSided', - '1caa6d7d-f015-3f54-3a4b-8b5354d39f81': 'Comet', - 'c8313697-2563-47fc-832e-290f4c04b901': 'DiamondHull', - '4391aaaa-df73-4396-9e33-31e4e4930b27': 'Disco', - 'd1d991f2-e7a0-4cf1-b328-f57e915e6260': 'DotMarker', - '6a1cf9f9-032c-45ec-9b1d-a6680bee30f7': 'Dots', - '0d3889f3-3ede-470c-8af4-f44813306126': 'DoubleTaperedFlat', - '0d3889f3-3ede-470c-8af4-de4813306126': 'DoubleTaperedMarker', - 'd0262945-853c-4481-9cbd-88586bed93cb': 'DuctTape', - '3ca16e2f-bdcd-4da2-8631-dcef342f40f1': 'DuctTapeSingleSided', - 'f6e85de3-6dcc-4e7f-87fd-cee8c3d25d51': 'Electricity', - '02ffb866-7fb2-4d15-b761-1012cefb1360': 'Embers', - 'cb92b597-94ca-4255-b017-0e3f42f12f9e': 'Fire', - '2d35bcf0-e4d8-452c-97b1-3311be063130': 'Flat', - '55303bc4-c749-4a72-98d9-d23e68e76e18': 'FlatDeprecated', - '280c0a7a-aad8-416c-a7d2-df63d129ca70': 'FlatSingleSided', - 'cf019139-d41c-4eb0-a1d0-5cf54b0a42f3': 'Highlighter', - '6a1cf9f9-032c-45ec-9b6e-a6680bee32e9': 'HyperGrid', - 'dce872c2-7b49-4684-b59b-c45387949c5c': 'Hypercolor', - 'e8ef32b1-baa8-460a-9c2c-9cf8506794f5': 'HypercolorSingleSided', - '2f212815-f4d3-c1a4-681a-feeaf9c6dc37': 'Icing', - 'f5c336cf-5108-4b40-ade9-c687504385ab': 'Ink', - 'c0012095-3ffd-4040-8ee1-fc180d346eaa': 'InkSingleSided', - '4a76a27a-44d8-4bfe-9a8c-713749a499b0': 'Leaves', - 'ea19de07-d0c0-4484-9198-18489a3c1487': 'LeavesSingleSided', - '2241cd32-8ba2-48a5-9ee7-2caef7e9ed62': 'Light', - '4391aaaa-df81-4396-9e33-31e4e4930b27': 'LightWire', - 'd381e0f5-3def-4a0d-8853-31e9200bcbda': 'Lofted', - '429ed64a-4e97-4466-84d3-145a861ef684': 'Marker', - '79348357-432d-4746-8e29-0e25c112e3aa': 'MatteHull', - 'b2ffef01-eaaa-4ab5-aa64-95a2c4f5dbc6': 'NeonPulse', - 'f72ec0e7-a844-4e38-82e3-140c44772699': 'OilPaint', - 'c515dad7-4393-4681-81ad-162ef052241b': 'OilPaintSingleSided', - 'f1114e2e-eb8d-4fde-915a-6e653b54e9f5': 'Paper', - '759f1ebd-20cd-4720-8d41-234e0da63716': 'PaperSingleSided', - 'e0abbc80-0f80-e854-4970-8924a0863dcc': 'Petal', - 'c33714d1-b2f9-412e-bd50-1884c9d46336': 'Plasma', - 'ad1ad437-76e2-450d-a23a-e17f8310b960': 'Rainbow', - 'faaa4d44-fcfb-4177-96be-753ac0421ba3': 'ShinyHull', - '70d79cca-b159-4f35-990c-f02193947fe8': 'Smoke', - 'd902ed8b-d0d1-476c-a8de-878a79e3a34c': 'Snow', - 'accb32f5-4509-454f-93f8-1df3fd31df1b': 'SoftHighlighter', - 'cf7f0059-7aeb-53a4-2b67-c83d863a9ffa': 'Spikes', - '8dc4a70c-d558-4efd-a5ed-d4e860f40dc3': 'Splatter', - '7a1c8107-50c5-4b70-9a39-421576d6617e': 'SplatterSingleSided', - '0eb4db27-3f82-408d-b5a1-19ebd7d5b711': 'Stars', - '44bb800a-fbc3-4592-8426-94ecb05ddec3': 'Streamers', - '0077f88c-d93a-42f3-b59b-b31c50cdb414': 'Taffy', - 'b468c1fb-f254-41ed-8ec9-57030bc5660c': 'TaperedFlat', - 'c8ccb53d-ae13-45ef-8afb-b730d81394eb': 'TaperedFlatSingleSided', - 'd90c6ad8-af0f-4b54-b422-e0f92abe1b3c': 'TaperedMarker', - '1a26b8c0-8a07-4f8a-9fac-d2ef36e0cad0': 'TaperedMarker_Flat', - '75b32cf0-fdd6-4d89-a64b-e2a00b247b0f': 'ThickPaint', - 'fdf0326a-c0d1-4fed-b101-9db0ff6d071f': 'ThickPaintSingleSided', - '4391385a-df73-4396-9e33-31e4e4930b27': 'Toon', - 'a8fea537-da7c-4d4b-817f-24f074725d6d': 'UnlitHull', - 'd229d335-c334-495a-a801-660ac8a87360': 'VelvetInk', - '10201aa3-ebc2-42d8-84b7-2e63f6eeb8ab': 'Waveform', - 'b67c0e81-ce6d-40a8-aeb0-ef036b081aa3': 'WetPaint', - 'dea67637-cd1a-27e4-c9b1-52f4bbcb84e5': 'WetPaintSingleSided', - '5347acf0-a8e2-47b6-8346-30c70719d763': 'WigglyGraphite', - 'e814fef1-97fd-7194-4a2f-50c2bb918be2': 'WigglyGraphiteSingleSided', - '4391385a-cf83-4396-9e33-31e4e4930b27': 'Wire' -}; - -const common = { - - 'colors': { - - 'BloomColor': ` - vec3 BloomColor(vec3 color, float gain) { - // Guarantee that there's at least a little bit of all 3 channels. - // This makes fully-saturated strokes (which only have 2 non-zero - // color channels) eventually clip to white rather than to a secondary. - float cmin = length(color.rgb) * .05; - color.rgb = max(color.rgb, vec3(cmin, cmin, cmin)); - // If we try to remove this pow() from .a, it brightens up - // pressure-sensitive strokes; looks better as-is. - color = pow(color, vec3(2.2)); - color.rgb *= 2. * exp(gain * 10.); - return color; - } - `, - - 'LinearToSrgb': ` - vec3 LinearToSrgb(vec3 color) { - // Approximation http://chilliant.blogspot.com/2012/08/srgb-approximations-for-hlsl.html - vec3 linearColor = color.rgb; - vec3 S1 = sqrt(linearColor); - vec3 S2 = sqrt(S1); - vec3 S3 = sqrt(S2); - color.rgb = 0.662002687 * S1 + 0.684122060 * S2 - 0.323583601 * S3 - 0.0225411470 * linearColor; - return color; - } - `, - - 'hsv': ` - // uniform sampler2D lookupTex; - vec4 lookup(vec4 textureColor) { - return textureColor; - } - - vec3 lookup(vec3 textureColor) { - return textureColor; - } - - vec3 hsv2rgb( vec3 hsv ) { - vec3 rgb = clamp( abs(mod(hsv.x*6.0+vec3(0.0,4.0,2.0),6.0)-3.0)-1.0, 0.0, 1.0 ); - return hsv.z * mix( vec3(1.0), rgb, hsv.y); - } - - vec3 rgb2hsv( vec3 rgb ) { - vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); - vec4 p = mix(vec4(rgb.bg, K.wz), vec4(rgb.gb, K.xy), step(rgb.b, rgb.g)); - vec4 q = mix(vec4(p.xyw, rgb.r), vec4(rgb.r, p.yzx), step(p.x, rgb.r)); - - float d = q.x - min(q.w, q.y); - float e = 1.0e-10; - - return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); - } - `, - - 'SrgbToLinear': ` - vec3 SrgbToLinear(vec3 color) { - // Approximation http://chilliant.blogspot.com/2012/08/srgb-approximations-for-hlsl.html - vec3 sRGB = color.rgb; - color.rgb = sRGB * (sRGB * (sRGB * 0.305306011 + 0.682171111) + 0.012522878); - return color; - } - ` - - } - -}; - -let shaders = null; - -function getShaders() { - - if ( shaders === null ) { - - const loader = new TextureLoader().setPath( './textures/tiltbrush/' ); - - shaders = { - 'Light': { - uniforms: { - mainTex: { value: loader.load( 'Light.webp' ) }, - alphaTest: { value: 0.067 }, - emission_gain: { value: 0.45 }, - alpha: { value: 1 }, - }, - vertexShader: ` - precision highp float; - precision highp int; - - attribute vec2 uv; - attribute vec4 color; - attribute vec3 position; - - uniform mat4 modelMatrix; - uniform mat4 modelViewMatrix; - uniform mat4 projectionMatrix; - uniform mat4 viewMatrix; - uniform mat3 normalMatrix; - uniform vec3 cameraPosition; - - varying vec2 vUv; - varying vec3 vColor; - - ${ common.colors.LinearToSrgb } - ${ common.colors.hsv } - - void main() { - - vUv = uv; - - vColor = lookup(color.rgb); - - vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 ); - - gl_Position = projectionMatrix * mvPosition; - - } - `, - fragmentShader: ` - precision highp float; - precision highp int; - - uniform float emission_gain; - - uniform sampler2D mainTex; - uniform float alphaTest; - - varying vec2 vUv; - varying vec3 vColor; - - ${ common.colors.BloomColor } - ${ common.colors.SrgbToLinear } - - void main(){ - vec4 col = texture2D(mainTex, vUv); - vec3 color = vColor; - color = BloomColor(color, emission_gain); - color = color * col.rgb; - color = color * col.a; - color = SrgbToLinear(color); - gl_FragColor = vec4(color, 1.0); - } - `, - side: 2, - transparent: true, - depthFunc: 2, - depthWrite: true, - depthTest: false, - blending: 5, - blendDst: 201, - blendDstAlpha: 201, - blendEquation: 100, - blendEquationAlpha: 100, - blendSrc: 201, - blendSrcAlpha: 201, - } - - }; - - } - - return shaders; - -} - -function getMaterial( GUID ) { - - const name = BRUSH_LIST_ARRAY[ GUID ]; - - switch ( name ) { - - case 'Light': - return new RawShaderMaterial( getShaders().Light ); - - default: - return new MeshBasicMaterial( { vertexColors: true, side: DoubleSide } ); - - } - -} - -export { TiltLoader }; diff --git a/examples/jsm/loaders/USDZLoader.js b/examples/jsm/loaders/USDZLoader.js index b393577d61c064..18b2ca4a028d93 100644 --- a/examples/jsm/loaders/USDZLoader.js +++ b/examples/jsm/loaders/USDZLoader.js @@ -4,14 +4,16 @@ import { ClampToEdgeWrapping, FileLoader, Group, + NoColorSpace, Loader, Mesh, - MeshStandardMaterial, + MeshPhysicalMaterial, MirroredRepeatWrapping, RepeatWrapping, SRGBColorSpace, TextureLoader, Object3D, + Vector2 } from 'three'; import * as fflate from '../libs/fflate.module.js'; @@ -23,9 +25,7 @@ class USDAParser { const data = {}; const lines = text.split( '\n' ); - const length = lines.length; - let current = 0; let string = null; let target = data; @@ -33,9 +33,7 @@ class USDAParser { // debugger; - function parseNextLine() { - - const line = lines[ current ]; + for ( const line of lines ) { // console.log( line ); @@ -54,6 +52,18 @@ class USDAParser { target[ lhs ] = group; target = group; + } else if ( rhs.endsWith( '(' ) ) { + + // see #28631 + + const values = rhs.slice( 0, - 1 ); + target[ lhs ] = values; + + const meta = {}; + stack.push( meta ); + + target = meta; + } else { target[ lhs ] = rhs; @@ -72,7 +82,7 @@ class USDAParser { stack.pop(); - if ( stack.length === 0 ) return; + if ( stack.length === 0 ) continue; target = stack[ stack.length - 1 ]; @@ -98,32 +108,50 @@ class USDAParser { } - current ++; - - if ( current < length ) { - - parseNextLine(); - - } - } - parseNextLine(); - return data; } } +/** + * A loader for the USDZ format. + * + * USDZ files that use USDC internally are not yet supported, only USDA. + * + * ```js + * const loader = new USDZLoader(); + * const model = await loader.loadAsync( 'saeukkang.usdz' ); + * scene.add( model ); + * ``` + * + * @augments Loader + * @three_import import { USDZLoader } from 'three/addons/loaders/USDZLoader.js'; + */ class USDZLoader extends Loader { + /** + * Constructs a new USDZ loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); } + /** + * Starts loading from the given URL and passes the loaded USDZ asset + * to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(Group)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ load( url, onLoad, onProgress, onError ) { const scope = this; @@ -159,6 +187,12 @@ class USDZLoader extends Loader { } + /** + * Parses the given USDZ data and returns the resulting group. + * + * @param {ArrayBuffer} buffer - The raw USDZ data as an array buffer. + * @return {Group} The parsed asset as a group. + */ parse( buffer ) { const parser = new USDAParser(); @@ -173,12 +207,18 @@ class USDZLoader extends Loader { if ( filename.endsWith( 'png' ) ) { - const blob = new Blob( [ zip[ filename ] ], { type: { type: 'image/png' } } ); + const blob = new Blob( [ zip[ filename ] ], { type: 'image/png' } ); data[ filename ] = URL.createObjectURL( blob ); } - if ( filename.endsWith( 'usd' ) ) { + if ( filename.endsWith( 'usd' ) || filename.endsWith( 'usda' ) ) { + + if ( isCrateFile( zip[ filename ] ) ) { + + throw Error( 'THREE.USDZLoader: Crate files (.usdc or binary .usd) are not supported.' ); + + } const text = fflate.strFromU8( zip[ filename ] ); data[ filename ] = parser.parse( text ); @@ -191,18 +231,54 @@ class USDZLoader extends Loader { } + function isCrateFile( buffer ) { + + // Check if this a crate file. First 7 bytes of a crate file are "PXR-USDC". + const fileHeader = buffer.slice( 0, 7 ); + const crateHeader = new Uint8Array( [ 0x50, 0x58, 0x52, 0x2D, 0x55, 0x53, 0x44, 0x43 ] ); + + // If this is not a crate file, we assume it is a plain USDA file. + return fileHeader.every( ( value, index ) => value === crateHeader[ index ] ); + + } + function findUSD( zip ) { - for ( const filename in zip ) { + if ( zip.length < 1 ) return undefined; + + const firstFileName = Object.keys( zip )[ 0 ]; + let isCrate = false; + + // As per the USD specification, the first entry in the zip archive is used as the main file ("UsdStage"). + // ASCII files can end in either .usda or .usd. + // See https://openusd.org/release/spec_usdz.html#layout + if ( firstFileName.endsWith( 'usda' ) ) return zip[ firstFileName ]; - if ( filename.endsWith( 'usda' ) ) { + if ( firstFileName.endsWith( 'usdc' ) ) { - return zip[ filename ]; + isCrate = true; + + } else if ( firstFileName.endsWith( 'usd' ) ) { + + // If this is not a crate file, we assume it is a plain USDA file. + if ( ! isCrateFile( zip[ firstFileName ] ) ) { + + return zip[ firstFileName ]; + + } else { + + isCrate = true; } } + if ( isCrate ) { + + throw Error( 'THREE.USDZLoader: Crate files (.usdc or binary .usd) are not supported.' ); + + } + } const zip = fflate.unzipSync( new Uint8Array( buffer ) ); @@ -215,15 +291,6 @@ class USDZLoader extends Loader { const file = findUSD( zip ); - if ( file === undefined ) { - - console.warn( 'THREE.USDZLoader: No usda file found.' ); - - return new Group(); - - } - - // Parse file const text = fflate.strFromU8( file ); @@ -252,9 +319,11 @@ class USDZLoader extends Loader { function findGeometry( data, id ) { + if ( ! data ) return undefined; + if ( id !== undefined ) { - const def = `def "${id}"`; + const def = `def Mesh "${id}"`; if ( def in data ) { @@ -270,30 +339,6 @@ class USDZLoader extends Loader { if ( name.startsWith( 'def Mesh' ) ) { - // Move points to Mesh - - if ( 'point3f[] points' in data ) { - - object[ 'point3f[] points' ] = data[ 'point3f[] points' ]; - - } - - // Move st to Mesh - - if ( 'float2[] primvars:st' in data ) { - - object[ 'float2[] primvars:st' ] = data[ 'float2[] primvars:st' ]; - - } - - // Move st indices to Mesh - - if ( 'int[] primvars:st:indices' in data ) { - - object[ 'int[] primvars:st:indices' ] = data[ 'int[] primvars:st:indices' ]; - - } - return object; } @@ -315,62 +360,150 @@ class USDZLoader extends Loader { if ( ! data ) return undefined; - let geometry = new BufferGeometry(); + const geometry = new BufferGeometry(); + let indices = null; + let counts = null; + let uvs = null; + + let positionsLength = - 1; + + // index if ( 'int[] faceVertexIndices' in data ) { - const indices = JSON.parse( data[ 'int[] faceVertexIndices' ] ); - geometry.setIndex( new BufferAttribute( new Uint16Array( indices ), 1 ) ); + indices = JSON.parse( data[ 'int[] faceVertexIndices' ] ); + + } + + // face count + + if ( 'int[] faceVertexCounts' in data ) { + + counts = JSON.parse( data[ 'int[] faceVertexCounts' ] ); + indices = toTriangleIndices( indices, counts ); } + // position + if ( 'point3f[] points' in data ) { const positions = JSON.parse( data[ 'point3f[] points' ].replace( /[()]*/g, '' ) ); - const attribute = new BufferAttribute( new Float32Array( positions ), 3 ); + positionsLength = positions.length; + let attribute = new BufferAttribute( new Float32Array( positions ), 3 ); + + if ( indices !== null ) attribute = toFlatBufferAttribute( attribute, indices ); + geometry.setAttribute( 'position', attribute ); } + // uv + + if ( 'float2[] primvars:st' in data ) { + + data[ 'texCoord2f[] primvars:st' ] = data[ 'float2[] primvars:st' ]; + + } + + if ( 'texCoord2f[] primvars:st' in data ) { + + uvs = JSON.parse( data[ 'texCoord2f[] primvars:st' ].replace( /[()]*/g, '' ) ); + let attribute = new BufferAttribute( new Float32Array( uvs ), 2 ); + + if ( indices !== null ) attribute = toFlatBufferAttribute( attribute, indices ); + + geometry.setAttribute( 'uv', attribute ); + + } + + if ( 'int[] primvars:st:indices' in data && uvs !== null ) { + + // custom uv index, overwrite uvs with new data + + const attribute = new BufferAttribute( new Float32Array( uvs ), 2 ); + let indices = JSON.parse( data[ 'int[] primvars:st:indices' ] ); + indices = toTriangleIndices( indices, counts ); + geometry.setAttribute( 'uv', toFlatBufferAttribute( attribute, indices ) ); + + } + + // normal + if ( 'normal3f[] normals' in data ) { const normals = JSON.parse( data[ 'normal3f[] normals' ].replace( /[()]*/g, '' ) ); - const attribute = new BufferAttribute( new Float32Array( normals ), 3 ); + let attribute = new BufferAttribute( new Float32Array( normals ), 3 ); + + // normals require a special treatment in USD + + if ( normals.length === positionsLength ) { + + // raw normal and position data have equal length (like produced by USDZExporter) + + if ( indices !== null ) attribute = toFlatBufferAttribute( attribute, indices ); + + } else { + + // unequal length, normals are independent of faceVertexIndices + + let indices = Array.from( Array( normals.length / 3 ).keys() ); // [ 0, 1, 2, 3 ... ] + indices = toTriangleIndices( indices, counts ); + attribute = toFlatBufferAttribute( attribute, indices ); + + } + geometry.setAttribute( 'normal', attribute ); } else { + // compute flat vertex normals + geometry.computeVertexNormals(); } - if ( 'float2[] primvars:st' in data ) { + return geometry; - data[ 'texCoord2f[] primvars:st' ] = data[ 'float2[] primvars:st' ]; + } - } + function toTriangleIndices( rawIndices, counts ) { - if ( 'texCoord2f[] primvars:st' in data ) { + const indices = []; - const uvs = JSON.parse( data[ 'texCoord2f[] primvars:st' ].replace( /[()]*/g, '' ) ); - const attribute = new BufferAttribute( new Float32Array( uvs ), 2 ); + for ( let i = 0; i < counts.length; i ++ ) { - if ( 'int[] primvars:st:indices' in data ) { + const count = counts[ i ]; - geometry = geometry.toNonIndexed(); + const stride = i * count; - const indices = JSON.parse( data[ 'int[] primvars:st:indices' ] ); - geometry.setAttribute( 'uv', toFlatBufferAttribute( attribute, indices ) ); + if ( count === 3 ) { + + const a = rawIndices[ stride + 0 ]; + const b = rawIndices[ stride + 1 ]; + const c = rawIndices[ stride + 2 ]; + + indices.push( a, b, c ); + + } else if ( count === 4 ) { + + const a = rawIndices[ stride + 0 ]; + const b = rawIndices[ stride + 1 ]; + const c = rawIndices[ stride + 2 ]; + const d = rawIndices[ stride + 3 ]; + + indices.push( a, b, c ); + indices.push( a, c, d ); } else { - geometry.setAttribute( 'uv', attribute ); + console.warn( 'THREE.USDZLoader: Face vertex count of %s unsupported.', count ); } } - return geometry; + return indices; } @@ -441,15 +574,41 @@ class USDZLoader extends Loader { } + function setTextureParams( map, data_value ) { + + // rotation, scale and translation + + if ( data_value[ 'float inputs:rotation' ] ) { + + map.rotation = parseFloat( data_value[ 'float inputs:rotation' ] ); + + } + + if ( data_value[ 'float2 inputs:scale' ] ) { + + map.repeat = new Vector2().fromArray( JSON.parse( '[' + data_value[ 'float2 inputs:scale' ].replace( /[()]*/g, '' ) + ']' ) ); + + } + + if ( data_value[ 'float2 inputs:translation' ] ) { + + map.offset = new Vector2().fromArray( JSON.parse( '[' + data_value[ 'float2 inputs:translation' ].replace( /[()]*/g, '' ) + ']' ) ); + + } + + } + function buildMaterial( data ) { - const material = new MeshStandardMaterial(); + const material = new MeshPhysicalMaterial(); if ( data !== undefined ) { - if ( 'def Shader "PreviewSurface"' in data ) { + const surfaceConnection = data[ 'token outputs:surface.connect' ]; + const surfaceName = /(\w+).output/.exec( surfaceConnection )[ 1 ]; + const surface = data[ `def Shader "${surfaceName}"` ]; - const surface = data[ 'def Shader "PreviewSurface"' ]; + if ( surface !== undefined ) { if ( 'color3f inputs:diffuseColor.connect' in surface ) { @@ -459,6 +618,12 @@ class USDZLoader extends Loader { material.map = buildTexture( sampler ); material.map.colorSpace = SRGBColorSpace; + if ( 'def Shader "Transform2d_diffuse"' in data ) { + + setTextureParams( material.map, data[ 'def Shader "Transform2d_diffuse"' ] ); + + } + } else if ( 'color3f inputs:diffuseColor' in surface ) { const color = surface[ 'color3f inputs:diffuseColor' ].replace( /[()]*/g, '' ); @@ -466,43 +631,149 @@ class USDZLoader extends Loader { } + if ( 'color3f inputs:emissiveColor.connect' in surface ) { + + const path = surface[ 'color3f inputs:emissiveColor.connect' ]; + const sampler = findTexture( root, /(\w+).output/.exec( path )[ 1 ] ); + + material.emissiveMap = buildTexture( sampler ); + material.emissiveMap.colorSpace = SRGBColorSpace; + material.emissive.set( 0xffffff ); + + if ( 'def Shader "Transform2d_emissive"' in data ) { + + setTextureParams( material.emissiveMap, data[ 'def Shader "Transform2d_emissive"' ] ); + + } + + } else if ( 'color3f inputs:emissiveColor' in surface ) { + + const color = surface[ 'color3f inputs:emissiveColor' ].replace( /[()]*/g, '' ); + material.emissive.fromArray( JSON.parse( '[' + color + ']' ) ); + + } + if ( 'normal3f inputs:normal.connect' in surface ) { const path = surface[ 'normal3f inputs:normal.connect' ]; const sampler = findTexture( root, /(\w+).output/.exec( path )[ 1 ] ); material.normalMap = buildTexture( sampler ); + material.normalMap.colorSpace = NoColorSpace; + + if ( 'def Shader "Transform2d_normal"' in data ) { + + setTextureParams( material.normalMap, data[ 'def Shader "Transform2d_normal"' ] ); + + } } - if ( 'float inputs:roughness' in surface ) { + if ( 'float inputs:roughness.connect' in surface ) { + + const path = surface[ 'float inputs:roughness.connect' ]; + const sampler = findTexture( root, /(\w+).output/.exec( path )[ 1 ] ); + + material.roughness = 1.0; + material.roughnessMap = buildTexture( sampler ); + material.roughnessMap.colorSpace = NoColorSpace; + + if ( 'def Shader "Transform2d_roughness"' in data ) { + + setTextureParams( material.roughnessMap, data[ 'def Shader "Transform2d_roughness"' ] ); + + } + + } else if ( 'float inputs:roughness' in surface ) { material.roughness = parseFloat( surface[ 'float inputs:roughness' ] ); } - if ( 'float inputs:metallic' in surface ) { + if ( 'float inputs:metallic.connect' in surface ) { + + const path = surface[ 'float inputs:metallic.connect' ]; + const sampler = findTexture( root, /(\w+).output/.exec( path )[ 1 ] ); + + material.metalness = 1.0; + material.metalnessMap = buildTexture( sampler ); + material.metalnessMap.colorSpace = NoColorSpace; + + if ( 'def Shader "Transform2d_metallic"' in data ) { + + setTextureParams( material.metalnessMap, data[ 'def Shader "Transform2d_metallic"' ] ); + + } + + } else if ( 'float inputs:metallic' in surface ) { material.metalness = parseFloat( surface[ 'float inputs:metallic' ] ); } - } + if ( 'float inputs:clearcoat.connect' in surface ) { - if ( 'def Shader "diffuseColor_texture"' in data ) { + const path = surface[ 'float inputs:clearcoat.connect' ]; + const sampler = findTexture( root, /(\w+).output/.exec( path )[ 1 ] ); - const sampler = data[ 'def Shader "diffuseColor_texture"' ]; + material.clearcoat = 1.0; + material.clearcoatMap = buildTexture( sampler ); + material.clearcoatMap.colorSpace = NoColorSpace; - material.map = buildTexture( sampler ); - material.map.colorSpace = SRGBColorSpace; + if ( 'def Shader "Transform2d_clearcoat"' in data ) { - } + setTextureParams( material.clearcoatMap, data[ 'def Shader "Transform2d_clearcoat"' ] ); - if ( 'def Shader "normal_texture"' in data ) { + } - const sampler = data[ 'def Shader "normal_texture"' ]; + } else if ( 'float inputs:clearcoat' in surface ) { + + material.clearcoat = parseFloat( surface[ 'float inputs:clearcoat' ] ); + + } - material.normalMap = buildTexture( sampler ); + if ( 'float inputs:clearcoatRoughness.connect' in surface ) { + + const path = surface[ 'float inputs:clearcoatRoughness.connect' ]; + const sampler = findTexture( root, /(\w+).output/.exec( path )[ 1 ] ); + + material.clearcoatRoughness = 1.0; + material.clearcoatRoughnessMap = buildTexture( sampler ); + material.clearcoatRoughnessMap.colorSpace = NoColorSpace; + + if ( 'def Shader "Transform2d_clearcoatRoughness"' in data ) { + + setTextureParams( material.clearcoatRoughnessMap, data[ 'def Shader "Transform2d_clearcoatRoughness"' ] ); + + } + + } else if ( 'float inputs:clearcoatRoughness' in surface ) { + + material.clearcoatRoughness = parseFloat( surface[ 'float inputs:clearcoatRoughness' ] ); + + } + + if ( 'float inputs:ior' in surface ) { + + material.ior = parseFloat( surface[ 'float inputs:ior' ] ); + + } + + if ( 'float inputs:occlusion.connect' in surface ) { + + const path = surface[ 'float inputs:occlusion.connect' ]; + const sampler = findTexture( root, /(\w+).output/.exec( path )[ 1 ] ); + + material.aoMap = buildTexture( sampler ); + material.aoMap.colorSpace = NoColorSpace; + + if ( 'def Shader "Transform2d_occlusion"' in data ) { + + setTextureParams( material.aoMap, data[ 'def Shader "Transform2d_occlusion"' ] ); + + } + + } } @@ -540,7 +811,7 @@ class USDZLoader extends Loader { if ( 'asset inputs:file' in data ) { - const path = data[ 'asset inputs:file' ].replace( /@*/g, '' ); + const path = data[ 'asset inputs:file' ].replace( /@*/g, '' ).trim(); const loader = new TextureLoader(); diff --git a/examples/jsm/loaders/UltraHDRLoader.js b/examples/jsm/loaders/UltraHDRLoader.js new file mode 100644 index 00000000000000..555ad1c2da9fdb --- /dev/null +++ b/examples/jsm/loaders/UltraHDRLoader.js @@ -0,0 +1,630 @@ +import { + ClampToEdgeWrapping, + DataTexture, + DataUtils, + FileLoader, + HalfFloatType, + LinearFilter, + LinearMipMapLinearFilter, + LinearSRGBColorSpace, + Loader, + RGBAFormat, + UVMapping, +} from 'three'; + +/** + * UltraHDR Image Format - https://developer.android.com/media/platform/hdr-image-format + * + * Short format brief: + * + * [JPEG headers] + * [XMP metadata describing the MPF container and *both* SDR and gainmap images] + * [Optional metadata] [EXIF] [ICC Profile] + * [SDR image] + * [XMP metadata describing only the gainmap image] + * [Gainmap image] + * + * Each section is separated by a 0xFFXX byte followed by a descriptor byte (0xFFE0, 0xFFE1, 0xFFE2.) + * Binary image storages are prefixed with a unique 0xFFD8 16-bit descriptor. + */ + + +// Calculating this SRGB powers is extremely slow for 4K images and can be sufficiently precalculated for a 3-4x speed boost +const SRGB_TO_LINEAR = Array( 1024 ) + .fill( 0 ) + .map( ( _, value ) => + Math.pow( ( value / 255 ) * 0.9478672986 + 0.0521327014, 2.4 ) + ); + +/** + * A loader for the Ultra HDR Image Format. + * + * Existing HDR or EXR textures can be converted to Ultra HDR with this [tool]{@link https://gainmap-creator.monogrid.com/}. + * + * Current feature set: + * - JPEG headers (required) + * - XMP metadata (required) + * - XMP validation (not implemented) + * - EXIF profile (not implemented) + * - ICC profile (not implemented) + * - Binary storage for SDR & HDR images (required) + * - Gainmap metadata (required) + * - Non-JPEG image formats (not implemented) + * - Primary image as an HDR image (not implemented) + * + * ```js + * const loader = new UltraHDRLoader(); + * const texture = await loader.loadAsync( 'textures/equirectangular/ice_planet_close.jpg' ); + * texture.mapping = THREE.EquirectangularReflectionMapping; + * + * scene.background = texture; + * scene.environment = texture; + * ``` + * + * @augments Loader + * @three_import import { UltraHDRLoader } from 'three/addons/loaders/UltraHDRLoader.js'; + */ +class UltraHDRLoader extends Loader { + + /** + * Constructs a new Ultra HDR loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ + constructor( manager ) { + + super( manager ); + + /** + * The texture type. + * + * @type {(HalfFloatType|FloatType)} + * @default HalfFloatType + */ + this.type = HalfFloatType; + + } + + /** + * Sets the texture type. + * + * @param {(HalfFloatType|FloatType)} value - The texture type to set. + * @return {RGBELoader} A reference to this loader. + */ + setDataType( value ) { + + this.type = value; + + return this; + + } + + /** + * Parses the given Ultra HDR texture data. + * + * @param {ArrayBuffer} buffer - The raw texture data. + * @param {Function} onLoad - The `onLoad` callback. + */ + parse( buffer, onLoad ) { + + const xmpMetadata = { + version: null, + baseRenditionIsHDR: null, + gainMapMin: null, + gainMapMax: null, + gamma: null, + offsetSDR: null, + offsetHDR: null, + hdrCapacityMin: null, + hdrCapacityMax: null, + }; + const textDecoder = new TextDecoder(); + + const data = new DataView( buffer ); + + let byteOffset = 0; + const sections = []; + + while ( byteOffset < data.byteLength ) { + + const byte = data.getUint8( byteOffset ); + + if ( byte === 0xff ) { + + const leadingByte = data.getUint8( byteOffset + 1 ); + + if ( + [ + /* Valid section headers */ + 0xd8, // SOI + 0xe0, // APP0 + 0xe1, // APP1 + 0xe2, // APP2 + ].includes( leadingByte ) + ) { + + sections.push( { + sectionType: leadingByte, + section: [ byte, leadingByte ], + sectionOffset: byteOffset + 2, + } ); + + byteOffset += 2; + + } else { + + sections[ sections.length - 1 ].section.push( byte, leadingByte ); + + byteOffset += 2; + + } + + } else { + + sections[ sections.length - 1 ].section.push( byte ); + + byteOffset ++; + + } + + } + + let primaryImage, gainmapImage; + + for ( let i = 0; i < sections.length; i ++ ) { + + const { sectionType, section, sectionOffset } = sections[ i ]; + + if ( sectionType === 0xe0 ) { + /* JPEG Header - no useful information */ + } else if ( sectionType === 0xe1 ) { + + /* XMP Metadata */ + + this._parseXMPMetadata( + textDecoder.decode( new Uint8Array( section ) ), + xmpMetadata + ); + + } else if ( sectionType === 0xe2 ) { + + /* Data Sections - MPF / EXIF / ICC Profile */ + + const sectionData = new DataView( + new Uint8Array( section.slice( 2 ) ).buffer + ); + const sectionHeader = sectionData.getUint32( 2, false ); + + if ( sectionHeader === 0x4d504600 ) { + + /* MPF Section */ + + /* Section contains a list of static bytes and ends with offsets indicating location of SDR and gainmap images */ + /* First bytes after header indicate little / big endian ordering (0x49492A00 - LE / 0x4D4D002A - BE) */ + /* + ... 60 bytes indicating tags, versions, etc. ... + + bytes | bits | description + + 4 32 primary image size + 4 32 primary image offset + 2 16 0x0000 + 2 16 0x0000 + + 4 32 0x00000000 + 4 32 gainmap image size + 4 32 gainmap image offset + 2 16 0x0000 + 2 16 0x0000 + */ + + const mpfLittleEndian = sectionData.getUint32( 6 ) === 0x49492a00; + const mpfBytesOffset = 60; + + /* SDR size includes the metadata length, SDR offset is always 0 */ + + const primaryImageSize = sectionData.getUint32( + mpfBytesOffset, + mpfLittleEndian + ); + const primaryImageOffset = sectionData.getUint32( + mpfBytesOffset + 4, + mpfLittleEndian + ); + + /* Gainmap size is an absolute value starting from its offset, gainmap offset needs 6 bytes padding to take into account 0x00 bytes at the end of XMP */ + const gainmapImageSize = sectionData.getUint32( + mpfBytesOffset + 16, + mpfLittleEndian + ); + const gainmapImageOffset = + sectionData.getUint32( mpfBytesOffset + 20, mpfLittleEndian ) + + sectionOffset + + 6; + + primaryImage = new Uint8Array( + data.buffer, + primaryImageOffset, + primaryImageSize + ); + + gainmapImage = new Uint8Array( + data.buffer, + gainmapImageOffset, + gainmapImageSize + ); + + } + + } + + } + + /* Minimal sufficient validation - https://developer.android.com/media/platform/hdr-image-format#signal_of_the_format */ + if ( ! xmpMetadata.version ) { + + throw new Error( 'THREE.UltraHDRLoader: Not a valid UltraHDR image' ); + + } + + if ( primaryImage && gainmapImage ) { + + this._applyGainmapToSDR( + xmpMetadata, + primaryImage, + gainmapImage, + ( hdrBuffer, width, height ) => { + + onLoad( { + width, + height, + data: hdrBuffer, + format: RGBAFormat, + type: this.type, + } ); + + }, + ( error ) => { + + throw new Error( error ); + + } + ); + + } else { + + throw new Error( 'THREE.UltraHDRLoader: Could not parse UltraHDR images' ); + + } + + } + + /** + * Starts loading from the given URL and passes the loaded Ultra HDR texture + * to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the files to be loaded. This can also be a data URI. + * @param {function(DataTexture, Object)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + * @return {DataTexture} The Ultra HDR texture. + */ + load( url, onLoad, onProgress, onError ) { + + const texture = new DataTexture( + this.type === HalfFloatType ? new Uint16Array() : new Float32Array(), + 0, + 0, + RGBAFormat, + this.type, + UVMapping, + ClampToEdgeWrapping, + ClampToEdgeWrapping, + LinearFilter, + LinearMipMapLinearFilter, + 1, + LinearSRGBColorSpace + ); + texture.generateMipmaps = true; + texture.flipY = true; + + const loader = new FileLoader( this.manager ); + loader.setResponseType( 'arraybuffer' ); + loader.setRequestHeader( this.requestHeader ); + loader.setPath( this.path ); + loader.setWithCredentials( this.withCredentials ); + loader.load( url, ( buffer ) => { + + try { + + this.parse( + buffer, + ( texData ) => { + + texture.image = { + data: texData.data, + width: texData.width, + height: texData.height, + }; + texture.needsUpdate = true; + + if ( onLoad ) onLoad( texture, texData ); + + } + ); + + } catch ( error ) { + + if ( onError ) onError( error ); + + console.error( error ); + + } + + }, onProgress, onError ); + + return texture; + + } + + _parseXMPMetadata( xmpDataString, xmpMetadata ) { + + const domParser = new DOMParser(); + + const xmpXml = domParser.parseFromString( + xmpDataString.substring( + xmpDataString.indexOf( '<' ), + xmpDataString.lastIndexOf( '>' ) + 1 + ), + 'text/xml' + ); + + /* Determine if given XMP metadata is the primary GContainer descriptor or a gainmap descriptor */ + const [ hasHDRContainerDescriptor ] = xmpXml.getElementsByTagName( + 'Container:Directory' + ); + + if ( hasHDRContainerDescriptor ) { + /* There's not much useful information in the container descriptor besides memory-validation */ + } else { + + /* Gainmap descriptor - defaults from https://developer.android.com/media/platform/hdr-image-format#HDR_gain_map_metadata */ + + const [ gainmapNode ] = xmpXml.getElementsByTagName( 'rdf:Description' ); + + xmpMetadata.version = gainmapNode.getAttribute( 'hdrgm:Version' ); + xmpMetadata.baseRenditionIsHDR = + gainmapNode.getAttribute( 'hdrgm:BaseRenditionIsHDR' ) === 'True'; + xmpMetadata.gainMapMin = parseFloat( + gainmapNode.getAttribute( 'hdrgm:GainMapMin' ) || 0.0 + ); + xmpMetadata.gainMapMax = parseFloat( + gainmapNode.getAttribute( 'hdrgm:GainMapMax' ) || 1.0 + ); + xmpMetadata.gamma = parseFloat( + gainmapNode.getAttribute( 'hdrgm:Gamma' ) || 1.0 + ); + xmpMetadata.offsetSDR = parseFloat( + gainmapNode.getAttribute( 'hdrgm:OffsetSDR' ) / ( 1 / 64 ) + ); + xmpMetadata.offsetHDR = parseFloat( + gainmapNode.getAttribute( 'hdrgm:OffsetHDR' ) / ( 1 / 64 ) + ); + xmpMetadata.hdrCapacityMin = parseFloat( + gainmapNode.getAttribute( 'hdrgm:HDRCapacityMin' ) || 0.0 + ); + xmpMetadata.hdrCapacityMax = parseFloat( + gainmapNode.getAttribute( 'hdrgm:HDRCapacityMax' ) || 1.0 + ); + + } + + } + + _srgbToLinear( value ) { + + if ( value / 255 < 0.04045 ) { + + return ( value / 255 ) * 0.0773993808; + + } + + if ( value < 1024 ) { + + return SRGB_TO_LINEAR[ ~ ~ value ]; + + } + + return Math.pow( ( value / 255 ) * 0.9478672986 + 0.0521327014, 2.4 ); + + } + + _applyGainmapToSDR( + xmpMetadata, + sdrBuffer, + gainmapBuffer, + onSuccess, + onError + ) { + + const getImageDataFromBuffer = ( buffer ) => + new Promise( ( resolve, reject ) => { + + const imageLoader = document.createElement( 'img' ); + + imageLoader.onload = () => { + + const image = { + width: imageLoader.naturalWidth, + height: imageLoader.naturalHeight, + source: imageLoader, + }; + + URL.revokeObjectURL( imageLoader.src ); + + resolve( image ); + + }; + + imageLoader.onerror = () => { + + URL.revokeObjectURL( imageLoader.src ); + + reject(); + + }; + + imageLoader.src = URL.createObjectURL( + new Blob( [ buffer ], { type: 'image/jpeg' } ) + ); + + } ); + + Promise.all( [ + getImageDataFromBuffer( sdrBuffer ), + getImageDataFromBuffer( gainmapBuffer ), + ] ) + .then( ( [ sdrImage, gainmapImage ] ) => { + + const sdrImageAspect = sdrImage.width / sdrImage.height; + const gainmapImageAspect = gainmapImage.width / gainmapImage.height; + + if ( sdrImageAspect !== gainmapImageAspect ) { + + onError( + 'THREE.UltraHDRLoader Error: Aspect ratio mismatch between SDR and Gainmap images' + ); + + return; + + } + + const canvas = document.createElement( 'canvas' ); + const ctx = canvas.getContext( '2d', { + willReadFrequently: true, + colorSpace: 'srgb', + } ); + + canvas.width = sdrImage.width; + canvas.height = sdrImage.height; + + /* Use out-of-the-box interpolation of Canvas API to scale gainmap to fit the SDR resolution */ + ctx.drawImage( + gainmapImage.source, + 0, + 0, + gainmapImage.width, + gainmapImage.height, + 0, + 0, + sdrImage.width, + sdrImage.height + ); + const gainmapImageData = ctx.getImageData( + 0, + 0, + sdrImage.width, + sdrImage.height, + { colorSpace: 'srgb' } + ); + + ctx.drawImage( sdrImage.source, 0, 0 ); + const sdrImageData = ctx.getImageData( + 0, + 0, + sdrImage.width, + sdrImage.height, + { colorSpace: 'srgb' } + ); + + /* HDR Recovery formula - https://developer.android.com/media/platform/hdr-image-format#use_the_gain_map_to_create_adapted_HDR_rendition */ + let hdrBuffer; + + if ( this.type === HalfFloatType ) { + + hdrBuffer = new Uint16Array( sdrImageData.data.length ).fill( 23544 ); + + } else { + + hdrBuffer = new Float32Array( sdrImageData.data.length ).fill( 255 ); + + } + + const maxDisplayBoost = Math.sqrt( + Math.pow( + /* 1.8 instead of 2 near-perfectly rectifies approximations introduced by precalculated SRGB_TO_LINEAR values */ + 1.8, + xmpMetadata.hdrCapacityMax + ) + ); + const unclampedWeightFactor = + ( Math.log2( maxDisplayBoost ) - xmpMetadata.hdrCapacityMin ) / + ( xmpMetadata.hdrCapacityMax - xmpMetadata.hdrCapacityMin ); + const weightFactor = Math.min( + Math.max( unclampedWeightFactor, 0.0 ), + 1.0 + ); + const useGammaOne = xmpMetadata.gamma === 1.0; + + for ( + let pixelIndex = 0; + pixelIndex < sdrImageData.data.length; + pixelIndex += 4 + ) { + + const x = ( pixelIndex / 4 ) % sdrImage.width; + const y = Math.floor( pixelIndex / 4 / sdrImage.width ); + + for ( let channelIndex = 0; channelIndex < 3; channelIndex ++ ) { + + const sdrValue = sdrImageData.data[ pixelIndex + channelIndex ]; + + const gainmapIndex = ( y * sdrImage.width + x ) * 4 + channelIndex; + const gainmapValue = gainmapImageData.data[ gainmapIndex ] / 255.0; + + /* Gamma is 1.0 by default */ + const logRecovery = useGammaOne + ? gainmapValue + : Math.pow( gainmapValue, 1.0 / xmpMetadata.gamma ); + + const logBoost = + xmpMetadata.gainMapMin * ( 1.0 - logRecovery ) + + xmpMetadata.gainMapMax * logRecovery; + + const hdrValue = + ( sdrValue + xmpMetadata.offsetSDR ) * + ( logBoost * weightFactor === 0.0 + ? 1.0 + : Math.pow( 2, logBoost * weightFactor ) ) - + xmpMetadata.offsetHDR; + + const linearHDRValue = Math.min( + Math.max( this._srgbToLinear( hdrValue ), 0 ), + 65504 + ); + + hdrBuffer[ pixelIndex + channelIndex ] = + this.type === HalfFloatType + ? DataUtils.toHalfFloat( linearHDRValue ) + : linearHDRValue; + + } + + } + + onSuccess( hdrBuffer, sdrImage.width, sdrImage.height ); + + } ) + .catch( () => { + + throw new Error( + 'THREE.UltraHDRLoader Error: Could not parse UltraHDR images' + ); + + } ); + + } + +} + +export { UltraHDRLoader }; diff --git a/examples/jsm/loaders/VOXLoader.js b/examples/jsm/loaders/VOXLoader.js index e2f181c95da659..e149a967a5642d 100644 --- a/examples/jsm/loaders/VOXLoader.js +++ b/examples/jsm/loaders/VOXLoader.js @@ -13,8 +13,36 @@ import { SRGBColorSpace } from 'three'; +/** + * A loader for the VOX format. + * + * ```js + * const loader = new VOXLoader(); + * const chunks = await loader.loadAsync( 'models/vox/monu10.vox' ); + * + * for ( let i = 0; i < chunks.length; i ++ ) { + * + * const chunk = chunks[ i ]; + * const mesh = new VOXMesh( chunk ); + * mesh.scale.setScalar( 0.0015 ); + * scene.add( mesh ); + * + * } + * ``` + * @augments Loader + * @three_import import { VOXLoader } from 'three/addons/loaders/VOXLoader.js'; + */ class VOXLoader extends Loader { + /** + * Starts loading from the given URL and passes the loaded VOX asset + * to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(Array)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ load( url, onLoad, onProgress, onError ) { const scope = this; @@ -49,6 +77,12 @@ class VOXLoader extends Loader { } + /** + * Parses the given VOX data and returns the resulting chunks. + * + * @param {ArrayBuffer} buffer - The raw VOX data as an array buffer. + * @return {Array} The parsed chunks. + */ parse( buffer ) { const data = new DataView( buffer ); @@ -56,9 +90,16 @@ class VOXLoader extends Loader { const id = data.getUint32( 0, true ); const version = data.getUint32( 4, true ); - if ( id !== 542658390 || version !== 150 ) { + if ( id !== 542658390 ) { - console.error( 'Not a valid VOX file' ); + console.error( 'THREE.VOXLoader: Invalid VOX file.' ); + return; + + } + + if ( version !== 150 ) { + + console.error( 'THREE.VOXLoader: Invalid VOX file. Unsupported version:', version ); return; } @@ -166,8 +207,20 @@ class VOXLoader extends Loader { } +/** + * A VOX mesh. + * + * Instances of this class are created from the loaded chunks of {@link VOXLoader}. + * + * @augments Mesh + */ class VOXMesh extends Mesh { + /** + * Constructs a new VOX mesh. + * + * @param {Object} chunk - A VOX chunk loaded via {@link VOXLoader}. + */ constructor( chunk ) { const data = chunk.data; @@ -272,8 +325,20 @@ class VOXMesh extends Mesh { } +/** + * A VOX 3D texture. + * + * Instances of this class are created from the loaded chunks of {@link VOXLoader}. + * + * @augments Data3DTexture + */ class VOXData3DTexture extends Data3DTexture { + /** + * Constructs a new VOX 3D texture. + * + * @param {Object} chunk - A VOX chunk loaded via {@link VOXLoader}. + */ constructor( chunk ) { const data = chunk.data; diff --git a/examples/jsm/loaders/VRMLLoader.js b/examples/jsm/loaders/VRMLLoader.js index 5ae51ec04ef566..c109a076c45813 100644 --- a/examples/jsm/loaders/VRMLLoader.js +++ b/examples/jsm/loaders/VRMLLoader.js @@ -5,6 +5,7 @@ import { BufferGeometry, ClampToEdgeWrapping, Color, + ColorManagement, ConeGeometry, CylinderGeometry, DataTexture, @@ -28,21 +29,47 @@ import { Scene, ShapeUtils, SphereGeometry, + SRGBColorSpace, TextureLoader, Vector2, Vector3 } from 'three'; import chevrotain from '../libs/chevrotain.module.min.js'; - +/** + * A loader for the VRML format. + * + * ```js + * const loader = new VRMLLoader(); + * const object = await loader.loadAsync( 'models/vrml/house.wrl' ); + * scene.add( object ); + * ``` + * + * @augments Loader + * @three_import import { VRMLLoader } from 'three/addons/loaders/VRMLLoader.js'; + */ class VRMLLoader extends Loader { + /** + * Constructs a new VRML loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); } + /** + * Starts loading from the given URL and passes the loaded VRML asset + * to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(Scene)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ load( url, onLoad, onProgress, onError ) { const scope = this; @@ -79,6 +106,13 @@ class VRMLLoader extends Loader { } + /** + * Parses the given VRML data and returns the resulting scene. + * + * @param {string} data - The raw VRML data as a string. + * @param {string} path - The URL base path. + * @return {Scene} The parsed scene. + */ parse( data, path ) { const nodeMap = {}; @@ -125,7 +159,7 @@ class VRMLLoader extends Loader { // from http://gun.teipir.gr/VRML-amgem/spec/part1/concepts.html#SyntaxBasics const RouteIdentifier = createToken( { name: 'RouteIdentifier', pattern: /[^\x30-\x39\0-\x20\x22\x27\x23\x2b\x2c\x2d\x2e\x5b\x5d\x5c\x7b\x7d][^\0-\x20\x22\x27\x23\x2b\x2c\x2d\x2e\x5b\x5d\x5c\x7b\x7d]*[\.][^\x30-\x39\0-\x20\x22\x27\x23\x2b\x2c\x2d\x2e\x5b\x5d\x5c\x7b\x7d][^\0-\x20\x22\x27\x23\x2b\x2c\x2d\x2e\x5b\x5d\x5c\x7b\x7d]*/ } ); - const Identifier = createToken( { name: 'Identifier', pattern: /[^\x30-\x39\0-\x20\x22\x27\x23\x2b\x2c\x2d\x2e\x5b\x5d\x5c\x7b\x7d][^\0-\x20\x22\x27\x23\x2b\x2c\x2d\x2e\x5b\x5d\x5c\x7b\x7d]*/, longer_alt: RouteIdentifier } ); + const Identifier = createToken( { name: 'Identifier', pattern: /[^\x30-\x39\0-\x20\x22\x27\x23\x2b\x2c\x2d\x2e\x5b\x5d\x5c\x7b\x7d]([^\0-\x20\x22\x27\x23\x2b\x2c\x2e\x5b\x5d\x5c\x7b\x7d])*/, longer_alt: RouteIdentifier } ); // from http://gun.teipir.gr/VRML-amgem/spec/part1/nodesRef.html @@ -248,7 +282,7 @@ class VRMLLoader extends Loader { function createVisitor( BaseVRMLVisitor ) { - // the visitor is created dynmaically based on the given base class + // the visitor is created dynamically based on the given base class class VRMLToASTVisitor extends BaseVRMLVisitor { @@ -799,7 +833,7 @@ class VRMLLoader extends Loader { break; case 'rotation': - const axis = new Vector3( fieldValues[ 0 ], fieldValues[ 1 ], fieldValues[ 2 ] ); + const axis = new Vector3( fieldValues[ 0 ], fieldValues[ 1 ], fieldValues[ 2 ] ).normalize(); const angle = fieldValues[ 3 ]; object.quaternion.setFromAxisAngle( axis, angle ); break; @@ -917,7 +951,7 @@ class VRMLLoader extends Loader { } else { - skyMaterial.color.setRGB( skyColor[ 0 ], skyColor[ 1 ], skyColor[ 2 ] ); + skyMaterial.color.setRGB( skyColor[ 0 ], skyColor[ 1 ], skyColor[ 2 ], SRGBColorSpace ); } @@ -958,7 +992,10 @@ class VRMLLoader extends Loader { // if the appearance field is NULL or unspecified, lighting is off and the unlit object color is (0, 0, 0) - let material = new MeshBasicMaterial( { color: 0x000000 } ); + let material = new MeshBasicMaterial( { + name: Loader.DEFAULT_MATERIAL_NAME, + color: 0x000000 + } ); let geometry; for ( let i = 0, l = fields.length; i < l; i ++ ) { @@ -1005,7 +1042,12 @@ class VRMLLoader extends Loader { if ( type === 'points' ) { // points - const pointsMaterial = new PointsMaterial( { color: 0xffffff } ); + const pointsMaterial = new PointsMaterial( { + name: Loader.DEFAULT_MATERIAL_NAME, + color: 0xffffff, + opacity: material.opacity, + transparent: material.transparent + } ); if ( geometry.attributes.color !== undefined ) { @@ -1027,7 +1069,12 @@ class VRMLLoader extends Loader { } else if ( type === 'line' ) { // lines - const lineMaterial = new LineBasicMaterial( { color: 0xffffff } ); + const lineMaterial = new LineBasicMaterial( { + name: Loader.DEFAULT_MATERIAL_NAME, + color: 0xffffff, + opacity: material.opacity, + transparent: material.transparent + } ); if ( geometry.attributes.color !== undefined ) { @@ -1114,7 +1161,10 @@ class VRMLLoader extends Loader { // if the material field is NULL or unspecified, lighting is off and the unlit object color is (0, 0, 0) - material = new MeshBasicMaterial( { color: 0x000000 } ); + material = new MeshBasicMaterial( { + name: Loader.DEFAULT_MATERIAL_NAME, + color: 0x000000 + } ); } @@ -1222,11 +1272,11 @@ class VRMLLoader extends Loader { break; case 'diffuseColor': - materialData.diffuseColor = new Color( fieldValues[ 0 ], fieldValues[ 1 ], fieldValues[ 2 ] ); + materialData.diffuseColor = new Color().setRGB( fieldValues[ 0 ], fieldValues[ 1 ], fieldValues[ 2 ], SRGBColorSpace ); break; case 'emissiveColor': - materialData.emissiveColor = new Color( fieldValues[ 0 ], fieldValues[ 1 ], fieldValues[ 2 ] ); + materialData.emissiveColor = new Color().setRGB( fieldValues[ 0 ], fieldValues[ 1 ], fieldValues[ 2 ], SRGBColorSpace ); break; case 'shininess': @@ -1234,7 +1284,7 @@ class VRMLLoader extends Loader { break; case 'specularColor': - materialData.emissiveColor = new Color( fieldValues[ 0 ], fieldValues[ 1 ], fieldValues[ 2 ] ); + materialData.specularColor = new Color().setRGB( fieldValues[ 0 ], fieldValues[ 1 ], fieldValues[ 2 ], SRGBColorSpace ); break; case 'transparency': @@ -1370,6 +1420,7 @@ class VRMLLoader extends Loader { } texture = new DataTexture( data, width, height ); + texture.colorSpace = SRGBColorSpace; texture.needsUpdate = true; texture.__type = textureType; // needed for material modifications break; @@ -1442,6 +1493,7 @@ class VRMLLoader extends Loader { texture.wrapS = wrapS; texture.wrapT = wrapT; + texture.colorSpace = SRGBColorSpace; } @@ -1700,6 +1752,8 @@ class VRMLLoader extends Loader { } + convertColorsToLinearSRGB( colorAttribute ); + } if ( normal ) { @@ -1868,7 +1922,7 @@ class VRMLLoader extends Loader { // if the colorIndex field is not empty, then one color is used for each polyline of the IndexedLineSet. - const expandedColorIndex = expandLineIndex( colorIndex ); // compute colors for each line segment (rendering primitve) + const expandedColorIndex = expandLineIndex( colorIndex ); // compute colors for each line segment (rendering primitive) colorAttribute = computeAttributeFromIndexedData( expandedLineIndex, expandedColorIndex, color, 3 ); // compute data on vertex level } else { @@ -1885,8 +1939,8 @@ class VRMLLoader extends Loader { // if the colorIndex field is not empty, then colors are applied to each vertex of the IndexedLineSet - const flattenLineColors = flattenData( color, colorIndex ); // compute colors for each VRML primitve - const expandedLineColors = expandLineData( flattenLineColors, coordIndex ); // compute colors for each line segment (rendering primitve) + const flattenLineColors = flattenData( color, colorIndex ); // compute colors for each VRML primitive + const expandedLineColors = expandLineData( flattenLineColors, coordIndex ); // compute colors for each line segment (rendering primitive) colorAttribute = computeAttributeFromLineData( expandedLineIndex, expandedLineColors ); // compute data on vertex level @@ -1894,13 +1948,15 @@ class VRMLLoader extends Loader { // if the colorIndex field is empty, then the coordIndex field is used to choose colors from the Color node - const expandedLineColors = expandLineData( color, coordIndex ); // compute colors for each line segment (rendering primitve) + const expandedLineColors = expandLineData( color, coordIndex ); // compute colors for each line segment (rendering primitive) colorAttribute = computeAttributeFromLineData( expandedLineIndex, expandedLineColors ); // compute data on vertex level } } + convertColorsToLinearSRGB( colorAttribute ); + } // @@ -1966,7 +2022,15 @@ class VRMLLoader extends Loader { const geometry = new BufferGeometry(); geometry.setAttribute( 'position', new Float32BufferAttribute( coord, 3 ) ); - if ( color ) geometry.setAttribute( 'color', new Float32BufferAttribute( color, 3 ) ); + + if ( color ) { + + const colorAttribute = new Float32BufferAttribute( color, 3 ); + convertColorsToLinearSRGB( colorAttribute ); + + geometry.setAttribute( 'color', colorAttribute ); + + } geometry._type = 'points'; @@ -2380,6 +2444,8 @@ class VRMLLoader extends Loader { } + convertColorsToLinearSRGB( colorAttribute ); + } // normal attribute @@ -2714,7 +2780,7 @@ class VRMLLoader extends Loader { const indices = []; - // since face defintions can have more than three vertices, it's necessary to + // since face definitions can have more than three vertices, it's necessary to // perform a simple triangulation let start = 0; @@ -3067,13 +3133,29 @@ class VRMLLoader extends Loader { } + function convertColorsToLinearSRGB( attribute ) { + + const color = new Color(); + + for ( let i = 0; i < attribute.count; i ++ ) { + + color.fromBufferAttribute( attribute, i ); + + ColorManagement.colorSpaceToWorking( color, SRGBColorSpace ); + + attribute.setXYZ( i, color.r, color.g, color.b ); + + } + + } + /** * Vertically paints the faces interpolating between the * specified colors at the specified angels. This is used for the Background * node, but could be applied to other nodes with multiple faces as well. * * When used with the Background node, default is directionIsDown is true if - * interpolating the skyColor down from the Zenith. When interpolationg up from + * interpolating the skyColor down from the Zenith. When interpolating up from * the Nadir i.e. interpolating the groundColor, the directionIsDown is false. * * The first angle is never specified, it is the Zenith (0 rad). Angles are specified @@ -3166,6 +3248,8 @@ class VRMLLoader extends Loader { color.copy( colorA ).lerp( colorB, t ); + ColorManagement.colorSpaceToWorking( color, SRGBColorSpace ); + colorAttribute.setXYZ( index, color.r, color.g, color.b ); } diff --git a/examples/jsm/loaders/VTKLoader.js b/examples/jsm/loaders/VTKLoader.js index 01522ab167efe3..af74d3f7da3eeb 100644 --- a/examples/jsm/loaders/VTKLoader.js +++ b/examples/jsm/loaders/VTKLoader.js @@ -1,20 +1,55 @@ import { BufferAttribute, BufferGeometry, + Color, FileLoader, Float32BufferAttribute, - Loader + Loader, + SRGBColorSpace } from 'three'; import * as fflate from '../libs/fflate.module.js'; +/** + * A loader for the VTK format. + * + * This loader only supports the `POLYDATA` dataset format so far. Other formats + * (structured points, structured grid, rectilinear grid, unstructured grid, appended) + * are not supported. + * + * ```js + * const loader = new VTKLoader(); + * const geometry = await loader.loadAsync( 'models/vtk/liver.vtk' ); + * geometry.computeVertexNormals(); + * + * const mesh = new THREE.Mesh( geometry, new THREE.MeshLambertMaterial() ); + * scene.add( mesh ); + * ``` + * + * @augments Loader + * @three_import import { VTKLoader } from 'three/addons/loaders/VTKLoader.js'; + */ class VTKLoader extends Loader { + /** + * Constructs a new VTK loader. + * + * @param {LoadingManager} [manager] - The loading manager. + */ constructor( manager ) { super( manager ); } + /** + * Starts loading from the given URL and passes the loaded VRML asset + * to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(BufferGeometry)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ load( url, onLoad, onProgress, onError ) { const scope = this; @@ -50,6 +85,12 @@ class VTKLoader extends Loader { } + /** + * Parses the given VTK data and returns the resulting geometry. + * + * @param {ArrayBuffer} data - The raw VTK data as an array buffer + * @return {BufferGeometry} The parsed geometry. + */ parse( data ) { function parseASCII( data ) { @@ -107,6 +148,8 @@ class VTKLoader extends Loader { let inColorSection = false; let inNormalsSection = false; + const color = new Color(); + const lines = data.split( '\n' ); for ( const i in lines ) { @@ -207,7 +250,10 @@ class VTKLoader extends Loader { const r = parseFloat( result[ 1 ] ); const g = parseFloat( result[ 2 ] ); const b = parseFloat( result[ 3 ] ); - colors.push( r, g, b ); + + color.setRGB( r, g, b, SRGBColorSpace ); + + colors.push( color.r, color.g, color.b ); } @@ -319,9 +365,11 @@ class VTKLoader extends Loader { const g = colors[ 3 * i + 1 ]; const b = colors[ 3 * i + 2 ]; - newColors.push( r, g, b ); - newColors.push( r, g, b ); - newColors.push( r, g, b ); + color.setRGB( r, g, b, SRGBColorSpace ); + + newColors.push( color.r, color.g, color.b ); + newColors.push( color.r, color.g, color.b ); + newColors.push( color.r, color.g, color.b ); } @@ -439,7 +487,6 @@ class VTKLoader extends Loader { } else { - indices[ indicesIndex ++ ] = strip[ j ]; indices[ indicesIndex ++ ] = strip[ j + 1 ]; indices[ indicesIndex ++ ] = strip[ j + 2 ]; @@ -612,7 +659,17 @@ class VTKLoader extends Loader { const tmp = xmlToJson( item ); - if ( tmp !== '' ) obj[ nodeName ] = tmp; + if ( tmp !== '' ) { + + if ( Array.isArray( tmp[ '#text' ] ) ) { + + tmp[ '#text' ] = tmp[ '#text' ][ 0 ]; + + } + + obj[ nodeName ] = tmp; + + } } else { @@ -625,7 +682,17 @@ class VTKLoader extends Loader { const tmp = xmlToJson( item ); - if ( tmp !== '' ) obj[ nodeName ].push( tmp ); + if ( tmp !== '' ) { + + if ( Array.isArray( tmp[ '#text' ] ) ) { + + tmp[ '#text' ] = tmp[ '#text' ][ 0 ]; + + } + + obj[ nodeName ].push( tmp ); + + } } @@ -886,6 +953,60 @@ class VTKLoader extends Loader { let normals = []; let indices = []; + if ( json.AppendedData ) { + + const appendedData = json.AppendedData[ '#text' ].slice( 1 ); + const piece = json.PolyData.Piece; + + const sections = [ 'PointData', 'CellData', 'Points', 'Verts', 'Lines', 'Strips', 'Polys' ]; + let sectionIndex = 0; + + const offsets = sections.map( s => { + + const sect = piece[ s ]; + + if ( sect && sect.DataArray ) { + + const arr = Array.isArray( sect.DataArray ) ? sect.DataArray : [ sect.DataArray ]; + + return arr.map( a => a.attributes.offset ); + + } + + return []; + + } ).flat(); + + for ( const sect of sections ) { + + const section = piece[ sect ]; + + if ( section && section.DataArray ) { + + if ( Array.isArray( section.DataArray ) ) { + + for ( const sectionEle of section.DataArray ) { + + sectionEle[ '#text' ] = appendedData.slice( offsets[ sectionIndex ], offsets[ sectionIndex + 1 ] ); + sectionEle.attributes.format = 'binary'; + sectionIndex ++; + + } + + } else { + + section.DataArray[ '#text' ] = appendedData.slice( offsets[ sectionIndex ], offsets[ sectionIndex + 1 ] ); + section.DataArray.attributes.format = 'binary'; + sectionIndex ++; + + } + + } + + } + + } + if ( json.PolyData ) { const piece = json.PolyData.Piece; diff --git a/examples/jsm/loaders/XYZLoader.js b/examples/jsm/loaders/XYZLoader.js index e5ece6f174fae3..68665e3458c9fe 100644 --- a/examples/jsm/loaders/XYZLoader.js +++ b/examples/jsm/loaders/XYZLoader.js @@ -1,12 +1,44 @@ import { BufferGeometry, + Color, FileLoader, Float32BufferAttribute, - Loader + Loader, + SRGBColorSpace } from 'three'; +/** + * A loader for the XYZ format. + * + * XYZ is a very simple format for storing point clouds. The layouts + * `XYZ` (points) and `XYZRGB` (points + colors) are supported. + * + * ```js + * const loader = new XYZLoader(); + * const geometry = await loader.loadAsync( 'models/xyz/helix_201.xyz' ); + * geometry.center(); + * + * const vertexColors = ( geometry.hasAttribute( 'color' ) === true ); + * const material = new THREE.PointsMaterial( { size: 0.1, vertexColors: vertexColors } ); + * + * const points = new THREE.Points( geometry, material ); + * scene.add( points ); + * ``` + * + * @augments Loader + * @three_import import { XYZLoader } from 'three/addons/loaders/XYZLoader.js'; + */ class XYZLoader extends Loader { + /** + * Starts loading from the given URL and passes the loaded XYZ asset + * to the `onLoad()` callback. + * + * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI. + * @param {function(BufferGeometry)} onLoad - Executed when the loading process has been finished. + * @param {onProgressCallback} onProgress - Executed while the loading is in progress. + * @param {onErrorCallback} onError - Executed when errors occur. + */ load( url, onLoad, onProgress, onError ) { const scope = this; @@ -41,12 +73,19 @@ class XYZLoader extends Loader { } + /** + * Parses the given XYZ data and returns the resulting geometry. + * + * @param {string} text - The raw XYZ data as a string. + * @return {BufferGeometry} The geometry representing the point cloud. + */ parse( text ) { const lines = text.split( '\n' ); const vertices = []; const colors = []; + const color = new Color(); for ( let line of lines ) { @@ -74,9 +113,13 @@ class XYZLoader extends Loader { vertices.push( parseFloat( lineValues[ 1 ] ) ); vertices.push( parseFloat( lineValues[ 2 ] ) ); - colors.push( parseFloat( lineValues[ 3 ] ) / 255 ); - colors.push( parseFloat( lineValues[ 4 ] ) / 255 ); - colors.push( parseFloat( lineValues[ 5 ] ) / 255 ); + const r = parseFloat( lineValues[ 3 ] ) / 255; + const g = parseFloat( lineValues[ 4 ] ) / 255; + const b = parseFloat( lineValues[ 5 ] ) / 255; + + color.setRGB( r, g, b, SRGBColorSpace ); + + colors.push( color.r, color.g, color.b ); } diff --git a/examples/jsm/loaders/lwo/IFFParser.js b/examples/jsm/loaders/lwo/IFFParser.js index f083043a3f6988..3cfc333da2cd38 100644 --- a/examples/jsm/loaders/lwo/IFFParser.js +++ b/examples/jsm/loaders/lwo/IFFParser.js @@ -35,18 +35,16 @@ import { LWO2Parser } from './LWO2Parser.js'; import { LWO3Parser } from './LWO3Parser.js'; -function IFFParser( ) { +class IFFParser { - this.debugger = new Debugger(); - // this.debugger.enable(); // un-comment to log IFF hierarchy. + constructor() { -} - -IFFParser.prototype = { + this.debugger = new Debugger(); + // this.debugger.enable(); // un-comment to log IFF hierarchy. - constructor: IFFParser, + } - parse: function ( buffer ) { + parse( buffer ) { this.reader = new DataViewReader( buffer ); @@ -82,13 +80,13 @@ IFFParser.prototype = { return this.tree; - }, + } parseTopForm() { this.debugger.offset = this.reader.offset; - var topForm = this.reader.getIDTag(); + const topForm = this.reader.getIDTag(); if ( topForm !== 'FORM' ) { @@ -97,12 +95,12 @@ IFFParser.prototype = { } - var length = this.reader.getUint32(); + const length = this.reader.getUint32(); this.debugger.dataOffset = this.reader.offset; this.debugger.length = length; - var type = this.reader.getIDTag(); + const type = this.reader.getIDTag(); if ( type === 'LWO2' ) { @@ -120,7 +118,7 @@ IFFParser.prototype = { return; - }, + } /// @@ -131,7 +129,7 @@ IFFParser.prototype = { // FORM ::= 'FORM'[ID4], length[U4], type[ID4], ( chunk[CHUNK] | form[FORM] ) * } parseForm( length ) { - var type = this.reader.getIDTag(); + const type = this.reader.getIDTag(); switch ( type ) { @@ -306,7 +304,7 @@ IFFParser.prototype = { this.debugger.nodeID = type; this.debugger.log(); - }, + } setupForm( type, length ) { @@ -331,13 +329,13 @@ IFFParser.prototype = { } - }, + } skipForm( length ) { this.reader.skip( length - 4 ); - }, + } parseUnknownForm( type, length ) { @@ -346,15 +344,15 @@ IFFParser.prototype = { printBuffer( this.reader.dv.buffer, this.reader.offset, length - 4 ); this.reader.skip( length - 4 ); - }, + } parseSurfaceForm( length ) { this.reader.skip( 8 ); // unknown Uint32 x2 - var name = this.reader.getString(); + const name = this.reader.getString(); - var surface = { + const surface = { attributes: {}, // LWO2 style non-node attributes will go here connections: {}, name: name, @@ -370,13 +368,13 @@ IFFParser.prototype = { this.currentForm = surface; this.currentFormEnd = this.reader.offset + length; - }, + } parseSurfaceLwo2( length ) { - var name = this.reader.getString(); + const name = this.reader.getString(); - var surface = { + const surface = { attributes: {}, // LWO2 style non-node attributes will go here connections: {}, name: name, @@ -391,7 +389,7 @@ IFFParser.prototype = { this.currentForm = surface; this.currentFormEnd = this.reader.offset + length; - }, + } parseSubNode( length ) { @@ -400,9 +398,9 @@ IFFParser.prototype = { // some subnodes can be renamed, but Input and Surface cannot this.reader.skip( 8 ); // NRNM + length - var name = this.reader.getString(); + const name = this.reader.getString(); - var node = { + const node = { name: name }; this.currentForm = node; @@ -411,7 +409,7 @@ IFFParser.prototype = { this.currentFormEnd = this.reader.offset + length; - }, + } // collect attributes from all nodes at the top level of a surface parseConnections( length ) { @@ -421,18 +419,18 @@ IFFParser.prototype = { this.currentForm = this.currentSurface.connections; - }, + } // surface node attribute data, e.g. specular, roughness etc parseEntryForm( length ) { this.reader.skip( 8 ); // NAME + length - var name = this.reader.getString(); + const name = this.reader.getString(); this.currentForm = this.currentNode.attributes; this.setupForm( name, length ); - }, + } // parse values from material - doesn't match up to other LWO3 data types // sub form of entry form @@ -440,7 +438,7 @@ IFFParser.prototype = { this.reader.skip( 8 ); // unknown + length - var valueType = this.reader.getString(); + const valueType = this.reader.getString(); if ( valueType === 'double' ) { @@ -462,17 +460,17 @@ IFFParser.prototype = { } - }, + } // holds various data about texture node image state - // Data other thanmipMapLevel unknown + // Data other than mipMapLevel unknown parseImageStateForm() { this.reader.skip( 8 ); // unknown this.currentForm.mipMapLevel = this.reader.getFloat32(); - }, + } // LWO2 style image data node OR LWO3 textures defined at top level in editor (not as SURF node) parseImageMap( length ) { @@ -482,13 +480,13 @@ IFFParser.prototype = { if ( ! this.currentForm.maps ) this.currentForm.maps = []; - var map = {}; + const map = {}; this.currentForm.maps.push( map ); this.currentForm = map; this.reader.skip( 10 ); // unknown, could be an issue if it contains a VX - }, + } parseTextureNodeAttribute( type ) { @@ -526,14 +524,14 @@ IFFParser.prototype = { this.reader.skip( 2 ); // unknown - }, + } // ENVL forms are currently ignored parseEnvelope( length ) { this.reader.skip( length - 4 ); // skipping entirely for now - }, + } /// // CHUNK PARSING METHODS @@ -543,7 +541,7 @@ IFFParser.prototype = { // level and they have a different format in each case parseClip( length ) { - var tag = this.reader.getIDTag(); + const tag = this.reader.getIDTag(); // inside surface node if ( tag === 'FORM' ) { @@ -564,26 +562,26 @@ IFFParser.prototype = { this.reader.skip( 8 ); // unknown - var texture = { + const texture = { index: this.reader.getUint32() }; this.tree.textures.push( texture ); this.currentForm = texture; - }, + } parseClipLwo2( length ) { - var texture = { + const texture = { index: this.reader.getUint32(), fileName: '' }; - // seach STIL block + // search STIL block while ( true ) { - var tag = this.reader.getIDTag(); - var n_length = this.reader.getUint16(); + const tag = this.reader.getIDTag(); + const n_length = this.reader.getUint16(); if ( tag === 'STIL' ) { texture.fileName = this.reader.getString(); @@ -602,29 +600,29 @@ IFFParser.prototype = { this.tree.textures.push( texture ); this.currentForm = texture; - }, + } parseImage() { this.reader.skip( 8 ); // unknown this.currentForm.fileName = this.reader.getString(); - }, + } parseXVAL( type, length ) { - var endOffset = this.reader.offset + length - 4; + const endOffset = this.reader.offset + length - 4; this.reader.skip( 8 ); this.currentForm[ type ] = this.reader.getFloat32(); this.reader.setOffset( endOffset ); // set end offset directly to skip optional envelope - }, + } parseXVAL3( type, length ) { - var endOffset = this.reader.offset + length - 4; + const endOffset = this.reader.offset + length - 4; this.reader.skip( 8 ); this.currentForm[ type ] = { @@ -635,7 +633,7 @@ IFFParser.prototype = { this.reader.setOffset( endOffset ); - }, + } // Tags associated with an object // OTAG { type[ID4], tag-string[S0] } @@ -647,28 +645,31 @@ IFFParser.prototype = { tagString: this.reader.getString() }; - }, + } // Signals the start of a new layer. All the data chunks which follow will be included in this layer until another layer chunk is encountered. // LAYR: number[U2], flags[U2], pivot[VEC12], name[S0], parent[U2] parseLayer( length ) { - var layer = { - number: this.reader.getUint16(), - flags: this.reader.getUint16(), // If the least significant bit of flags is set, the layer is hidden. - pivot: this.reader.getFloat32Array( 3 ), // Note: this seems to be superflous, as the geometry is translated when pivot is present + const number = this.reader.getUint16(); + const flags = this.reader.getUint16(); // If the least significant bit of flags is set, the layer is hidden. + const pivot = this.reader.getFloat32Array( 3 ); // Note: this seems to be superfluous, as the geometry is translated when pivot is present + const layer = { + number: number, + flags: flags, // If the least significant bit of flags is set, the layer is hidden. + pivot: [ - pivot[ 0 ], pivot[ 1 ], pivot[ 2 ] ], // Note: this seems to be superfluous, as the geometry is translated when pivot is present name: this.reader.getString(), }; this.tree.layers.push( layer ); this.currentLayer = layer; - var parsedLength = 16 + stringOffset( this.currentLayer.name ); // index ( 2 ) + flags( 2 ) + pivot( 12 ) + stringlength + const parsedLength = 16 + stringOffset( this.currentLayer.name ); // index ( 2 ) + flags( 2 ) + pivot( 12 ) + stringlength // if we have not reached then end of the layer block, there must be a parent defined this.currentLayer.parent = ( parsedLength < length ) ? this.reader.getUint16() : - 1; // omitted or -1 for no parent - }, + } // VEC12 * ( F4 + F4 + F4 ) array of x,y,z vectors // Converting from left to right handed coordinate system: @@ -676,14 +677,14 @@ IFFParser.prototype = { parsePoints( length ) { this.currentPoints = []; - for ( var i = 0; i < length / 4; i += 3 ) { + for ( let i = 0; i < length / 4; i += 3 ) { - // z -> -z to match three.js right handed coords - this.currentPoints.push( this.reader.getFloat32(), this.reader.getFloat32(), - this.reader.getFloat32() ); + // x -> -x to match three.js right handed coords + this.currentPoints.push( - this.reader.getFloat32(), this.reader.getFloat32(), this.reader.getFloat32() ); } - }, + } // parse VMAP or VMAD // Associates a set of floating-point vectors with a set of points. @@ -697,9 +698,9 @@ IFFParser.prototype = { // VMAD { type[ID4], dimension[U2], name[S0], ( vert[VX], poly[VX], value[F4] # dimension ) * } parseVertexMapping( length, discontinuous ) { - var finalOffset = this.reader.offset + length; + const finalOffset = this.reader.offset + length; - var channelName = this.reader.getString(); + const channelName = this.reader.getString(); if ( this.reader.offset === finalOffset ) { @@ -712,12 +713,12 @@ IFFParser.prototype = { // otherwise reset to initial length and parse normal VMAP CHUNK this.reader.setOffset( this.reader.offset - stringOffset( channelName ) ); - var type = this.reader.getIDTag(); + const type = this.reader.getIDTag(); this.reader.getUint16(); // dimension - var name = this.reader.getString(); + const name = this.reader.getString(); - var remainingLength = length - 6 - stringOffset( name ); + const remainingLength = length - 6 - stringOffset( name ); switch ( type ) { @@ -744,13 +745,13 @@ IFFParser.prototype = { } - }, + } parseUVMapping( name, finalOffset, discontinuous ) { - var uvIndices = []; - var polyIndices = []; - var uvs = []; + const uvIndices = []; + const polyIndices = []; + const uvs = []; while ( this.reader.offset < finalOffset ) { @@ -783,12 +784,12 @@ IFFParser.prototype = { } - }, + } parseMorphTargets( name, finalOffset, type ) { - var indices = []; - var points = []; + const indices = []; + const points = []; type = ( type === 'MORF' ) ? 'relative' : 'absolute'; @@ -808,33 +809,33 @@ IFFParser.prototype = { type: type, }; - }, + } // A list of polygons for the current layer. // POLS { type[ID4], ( numvert+flags[U2], vert[VX] # numvert ) * } parsePolygonList( length ) { - var finalOffset = this.reader.offset + length; - var type = this.reader.getIDTag(); + const finalOffset = this.reader.offset + length; + const type = this.reader.getIDTag(); - var indices = []; + const indices = []; // hold a list of polygon sizes, to be split up later - var polygonDimensions = []; + const polygonDimensions = []; while ( this.reader.offset < finalOffset ) { - var numverts = this.reader.getUint16(); + let numverts = this.reader.getUint16(); - //var flags = numverts & 64512; // 6 high order bits are flags - ignoring for now + //const flags = numverts & 64512; // 6 high order bits are flags - ignoring for now numverts = numverts & 1023; // remaining ten low order bits are vertex num polygonDimensions.push( numverts ); - for ( var j = 0; j < numverts; j ++ ) indices.push( this.reader.getVariableLengthIndex() ); + for ( let j = 0; j < numverts; j ++ ) indices.push( this.reader.getVariableLengthIndex() ); } - var geometryData = { + const geometryData = { type: type, vertexIndices: indices, polygonDimensions: polygonDimensions, @@ -847,7 +848,7 @@ IFFParser.prototype = { this.currentLayer.geometry = geometryData; - }, + } // Lists the tag strings that can be associated with polygons by the PTAG chunk. // TAGS { tag-string[S0] * } @@ -855,14 +856,14 @@ IFFParser.prototype = { this.tree.tags = this.reader.getStringArray( length ); - }, + } // Associates tags of a given type with polygons in the most recent POLS chunk. // PTAG { type[ID4], ( poly[VX], tag[U2] ) * } parsePolygonTagMapping( length ) { - var finalOffset = this.reader.offset + length; - var type = this.reader.getIDTag(); + const finalOffset = this.reader.offset + length; + const type = this.reader.getIDTag(); if ( type === 'SURF' ) this.parseMaterialIndices( finalOffset ); else { //PART, SMGP, COLR not supported @@ -870,7 +871,7 @@ IFFParser.prototype = { } - }, + } parseMaterialIndices( finalOffset ) { @@ -879,14 +880,14 @@ IFFParser.prototype = { while ( this.reader.offset < finalOffset ) { - var polygonIndex = this.reader.getVariableLengthIndex(); - var materialIndex = this.reader.getUint16(); + const polygonIndex = this.reader.getVariableLengthIndex(); + const materialIndex = this.reader.getUint16(); this.currentLayer.geometry.materialIndices.push( polygonIndex, materialIndex ); } - }, + } parseUnknownCHUNK( blockID, length ) { @@ -895,32 +896,31 @@ IFFParser.prototype = { // print the chunk plus some bytes padding either side // printBuffer( this.reader.dv.buffer, this.reader.offset - 20, length + 40 ); - var data = this.reader.getString( length ); + const data = this.reader.getString( length ); this.currentForm[ blockID ] = data; } -}; +} -function DataViewReader( buffer ) { - this.dv = new DataView( buffer ); - this.offset = 0; - this._textDecoder = new TextDecoder(); - this._bytes = new Uint8Array( buffer ); +class DataViewReader { -} + constructor( buffer ) { -DataViewReader.prototype = { + this.dv = new DataView( buffer ); + this.offset = 0; + this._textDecoder = new TextDecoder(); + this._bytes = new Uint8Array( buffer ); - constructor: DataViewReader, + } - size: function () { + size() { return this.dv.buffer.byteLength; - }, + } setOffset( offset ) { @@ -934,76 +934,74 @@ DataViewReader.prototype = { } - }, + } - endOfFile: function () { + endOfFile() { if ( this.offset >= this.size() ) return true; return false; - }, + } - skip: function ( length ) { + skip( length ) { this.offset += length; - }, + } - getUint8: function () { + getUint8() { - var value = this.dv.getUint8( this.offset ); + const value = this.dv.getUint8( this.offset ); this.offset += 1; return value; - }, + } - getUint16: function () { + getUint16() { - var value = this.dv.getUint16( this.offset ); + const value = this.dv.getUint16( this.offset ); this.offset += 2; return value; - }, + } - getInt32: function () { + getInt32() { - var value = this.dv.getInt32( this.offset, false ); + const value = this.dv.getInt32( this.offset, false ); this.offset += 4; return value; - }, + } - getUint32: function () { + getUint32() { - var value = this.dv.getUint32( this.offset, false ); + const value = this.dv.getUint32( this.offset, false ); this.offset += 4; return value; - }, - - getUint64: function () { + } - var low, high; + getUint64() { - high = this.getUint32(); - low = this.getUint32(); + const low = this.getUint32(); + const high = this.getUint32(); return high * 0x100000000 + low; - }, + } - getFloat32: function () { + getFloat32() { - var value = this.dv.getFloat32( this.offset, false ); + const value = this.dv.getFloat32( this.offset, false ); this.offset += 4; return value; - }, + } - getFloat32Array: function ( size ) { + getFloat32Array( size ) { - var a = []; + const a = []; - for ( var i = 0; i < size; i ++ ) { + for ( let i = 0; i < size; i ++ ) { a.push( this.getFloat32() ); @@ -1011,21 +1009,21 @@ DataViewReader.prototype = { return a; - }, + } - getFloat64: function () { + getFloat64() { - var value = this.dv.getFloat64( this.offset, this.littleEndian ); + const value = this.dv.getFloat64( this.offset, this.littleEndian ); this.offset += 8; return value; - }, + } - getFloat64Array: function ( size ) { + getFloat64Array( size ) { - var a = []; + const a = []; - for ( var i = 0; i < size; i ++ ) { + for ( let i = 0; i < size; i ++ ) { a.push( this.getFloat64() ); @@ -1033,7 +1031,7 @@ DataViewReader.prototype = { return a; - }, + } // get variable-length index data type // VX ::= index[U2] | (index + 0xFF000000)[U4] @@ -1043,7 +1041,7 @@ DataViewReader.prototype = { // the four-byte form is being used and the first byte should be discarded or masked out. getVariableLengthIndex() { - var firstByte = this.getUint8(); + const firstByte = this.getUint8(); if ( firstByte === 255 ) { @@ -1053,16 +1051,16 @@ DataViewReader.prototype = { return firstByte * 256 + this.getUint8(); - }, + } // An ID tag is a sequence of 4 bytes containing 7-bit ASCII values getIDTag() { return this.getString( 4 ); - }, + } - getString: function ( size ) { + getString( size ) { if ( size === 0 ) return; @@ -1095,44 +1093,51 @@ DataViewReader.prototype = { return result; - }, + } - getStringArray: function ( size ) { + getStringArray( size ) { - var a = this.getString( size ); + let a = this.getString( size ); a = a.split( '\0' ); return a.filter( Boolean ); // return array with any empty strings removed } -}; +} + // ************** DEBUGGER ************** -function Debugger( ) { +class Debugger { - this.active = false; - this.depth = 0; - this.formList = []; + constructor() { -} + this.active = false; + this.depth = 0; + this.formList = []; + this.offset = 0; -Debugger.prototype = { + this.node = 0; // 0 = FORM, 1 = CHUNK, 2 = SUBNODE + this.nodeID = 'FORM'; - constructor: Debugger, + this.dataOffset = 0; + this.length = 0; + this.skipped = false; - enable: function () { + } + + enable() { this.active = true; - }, + } - log: function () { + log() { if ( ! this.active ) return; - var nodeType; + let nodeType; switch ( this.node ) { @@ -1169,13 +1174,13 @@ Debugger.prototype = { this.skipped = false; - }, + } - closeForms: function () { + closeForms() { if ( ! this.active ) return; - for ( var i = this.formList.length - 1; i >= 0; i -- ) { + for ( let i = this.formList.length - 1; i >= 0; i -- ) { if ( this.offset >= this.formList[ i ] ) { @@ -1189,21 +1194,15 @@ Debugger.prototype = { } -}; +} // ************** UTILITY FUNCTIONS ************** -function isEven( num ) { - - return num % 2; - -} - // calculate the length of the string in the buffer // this will be string.length + nullbyte + optional padbyte to make the length even function stringOffset( string ) { - return string.length + 1 + ( isEven( string.length + 1 ) ? 1 : 0 ); + return string.length + 1 + ( ( string.length + 1 ) % 2 ); } diff --git a/examples/jsm/materials/LDrawConditionalLineMaterial.js b/examples/jsm/materials/LDrawConditionalLineMaterial.js new file mode 100644 index 00000000000000..9bb6be87afe1b7 --- /dev/null +++ b/examples/jsm/materials/LDrawConditionalLineMaterial.js @@ -0,0 +1,183 @@ +import { + Color, + ShaderMaterial, + UniformsLib, + UniformsUtils, +} from 'three'; + +/** + * A special line material for meshes loaded via {@link LDrawLoader}. + * + * This module can only be used with {@link WebGLRenderer}. When using {@link WebGPURenderer}, + * import the class from `LDrawConditionalLineNodeMaterial.js`. + * + * @augments ShaderMaterial + * @three_import import { LDrawConditionalLineMaterial } from 'three/addons/materials/LDrawConditionalLineMaterial.js'; + */ +class LDrawConditionalLineMaterial extends ShaderMaterial { + + static get type() { + + return 'LDrawConditionalLineMaterial'; + + } + + /** + * Constructs a new conditional line material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { + + super( { + + uniforms: UniformsUtils.merge( [ + UniformsLib.fog, + { + diffuse: { + value: new Color() + }, + opacity: { + value: 1.0 + } + } + ] ), + + vertexShader: /* glsl */` + attribute vec3 control0; + attribute vec3 control1; + attribute vec3 direction; + varying float discardFlag; + + #include + #include + #include + #include + #include + void main() { + #include + + vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 ); + gl_Position = projectionMatrix * mvPosition; + + // Transform the line segment ends and control points into camera clip space + vec4 c0 = projectionMatrix * modelViewMatrix * vec4( control0, 1.0 ); + vec4 c1 = projectionMatrix * modelViewMatrix * vec4( control1, 1.0 ); + vec4 p0 = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + vec4 p1 = projectionMatrix * modelViewMatrix * vec4( position + direction, 1.0 ); + + c0.xy /= c0.w; + c1.xy /= c1.w; + p0.xy /= p0.w; + p1.xy /= p1.w; + + // Get the direction of the segment and an orthogonal vector + vec2 dir = p1.xy - p0.xy; + vec2 norm = vec2( -dir.y, dir.x ); + + // Get control point directions from the line + vec2 c0dir = c0.xy - p1.xy; + vec2 c1dir = c1.xy - p1.xy; + + // If the vectors to the controls points are pointed in different directions away + // from the line segment then the line should not be drawn. + float d0 = dot( normalize( norm ), normalize( c0dir ) ); + float d1 = dot( normalize( norm ), normalize( c1dir ) ); + discardFlag = float( sign( d0 ) != sign( d1 ) ); + + #include + #include + #include + } + `, + + fragmentShader: /* glsl */` + uniform vec3 diffuse; + uniform float opacity; + varying float discardFlag; + + #include + #include + #include + #include + #include + void main() { + + if ( discardFlag > 0.5 ) discard; + + #include + vec3 outgoingLight = vec3( 0.0 ); + vec4 diffuseColor = vec4( diffuse, opacity ); + #include + #include + outgoingLight = diffuseColor.rgb; // simple shader + gl_FragColor = vec4( outgoingLight, diffuseColor.a ); + #include + #include + #include + #include + } + `, + + } ); + + Object.defineProperties( this, { + + /** + * The material's opacity. + * + * @name LDrawConditionalLineMaterial#opacity + * @type {number} + * @default 1 + */ + opacity: { + get: function () { + + return this.uniforms.opacity.value; + + }, + + set: function ( value ) { + + this.uniforms.opacity.value = value; + + } + }, + + /** + * The material's color. + * + * @name LDrawConditionalLineMaterial#color + * @type {Color} + * @default (1,1,1) + */ + color: { + get: function () { + + return this.uniforms.diffuse.value; + + } + } + + } ); + + this.setValues( parameters ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLDrawConditionalLineMaterial = true; + + } + +} + +export { LDrawConditionalLineMaterial }; diff --git a/examples/jsm/materials/LDrawConditionalLineNodeMaterial.js b/examples/jsm/materials/LDrawConditionalLineNodeMaterial.js new file mode 100644 index 00000000000000..8de20cf35a3eda --- /dev/null +++ b/examples/jsm/materials/LDrawConditionalLineNodeMaterial.js @@ -0,0 +1,154 @@ +import { Color } from 'three'; +import { attribute, cameraProjectionMatrix, dot, float, Fn, modelViewMatrix, modelViewProjection, NodeMaterial, normalize, positionGeometry, sign, uniform, varyingProperty, vec2, vec4 } from 'three/tsl'; + +/** + * A special line material for meshes loaded via {@link LDrawLoader}. + * + * This module can only be used with {@link WebGPURenderer}. When using {@link WebGLRenderer}, + * import the class from `LDrawConditionalLineMaterial.js`. + * + * @augments NodeMaterial + * @three_import import { LDrawConditionalLineMaterial } from 'three/addons/materials/LDrawConditionalLineMaterial.js'; + */ +class LDrawConditionalLineMaterial extends NodeMaterial { + + static get type() { + + return 'LDrawConditionalLineMaterial'; + + } + + /** + * Constructs a new conditional line material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { + + super(); + + const vertexNode = /*@__PURE__*/ Fn( () => { + + const control0 = attribute( 'control0', 'vec3' ); + const control1 = attribute( 'control1', 'vec3' ); + const direction = attribute( 'direction', 'vec3' ); + + const mvp = cameraProjectionMatrix.mul( modelViewMatrix ); + + // Transform the line segment ends and control points into camera clip space + + const c0 = mvp.mul( vec4( control0, 1 ) ).toVar(); + const c1 = mvp.mul( vec4( control1, 1 ) ).toVar(); + const p0 = mvp.mul( vec4( positionGeometry, 1 ) ).toVar(); + const p1 = mvp.mul( vec4( positionGeometry.add( direction ), 1 ) ).toVar(); + + c0.xy.divAssign( c0.w ); + c1.xy.divAssign( c1.w ); + p0.xy.divAssign( p0.w ); + p1.xy.divAssign( p1.w ); + + // Get the direction of the segment and an orthogonal vector + + const dir = p1.xy.sub( p0.xy ).toVar(); + const norm = vec2( dir.y.negate(), dir.x ).toVar(); + + // Get control point directions from the line + const c0dir = c0.xy.sub( p1.xy ).toVar(); + const c1dir = c1.xy.sub( p1.xy ).toVar(); + + // If the vectors to the controls points are pointed in different directions away + // from the line segment then the line should not be drawn. + const d0 = dot( normalize( norm ), normalize( c0dir ) ).toVar(); + const d1 = dot( normalize( norm ), normalize( c1dir ) ).toVar(); + const discardFlag = sign( d0 ).notEqual( sign( d1 ) ).select( float( 1 ), float( 0 ) ); + + varyingProperty( 'float', 'discardFlag' ).assign( discardFlag ); + + return modelViewProjection; + + } )(); + + const fragmentNode = /*@__PURE__*/ Fn( () => { + + const discardFlag = varyingProperty( 'float', 'discardFlag' ); + + discardFlag.greaterThan( float( 0.5 ) ).discard(); + + return vec4( this._diffuseUniform, this._opacityUniform ); + + } )(); + + this.vertexNode = vertexNode; + this.fragmentNode = fragmentNode; + + this._diffuseUniform = uniform( new Color() ); + this._opacityUniform = uniform( 1 ); + + // + + Object.defineProperties( this, { + + /** + * The material's opacity. + * + * @name LDrawConditionalLineMaterial#opacity + * @type {number} + * @default 1 + */ + opacity: { + get: function () { + + return this._opacityUniform.value; + + }, + + set: function ( value ) { + + this._opacityUniform.value = value; + + } + }, + + /** + * The material's color. + * + * @name LDrawConditionalLineMaterial#color + * @type {Color} + * @default (1,1,1) + */ + color: { + get: function () { + + return this._diffuseUniform.value; + + }, + + set: function ( value ) { + + this._diffuseUniform.value.copy( value ); + + } + } + + } ); + + this.setValues( parameters ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLDrawConditionalLineMaterial = true; + + } + +} + +export { LDrawConditionalLineMaterial }; diff --git a/examples/jsm/materials/MeshGouraudMaterial.js b/examples/jsm/materials/MeshGouraudMaterial.js index 8748b39690acb8..d2feba83b4aca2 100644 --- a/examples/jsm/materials/MeshGouraudMaterial.js +++ b/examples/jsm/materials/MeshGouraudMaterial.js @@ -9,6 +9,8 @@ import { UniformsUtils, UniformsLib, ShaderMaterial, Color, MultiplyOperation } const GouraudShader = { + name: 'GouraudShader', + uniforms: UniformsUtils.merge( [ UniformsLib.common, UniformsLib.specularmap, @@ -74,15 +76,11 @@ const GouraudShader = { vec3 diffuse = vec3( 1.0 ); - GeometricContext geometry; - geometry.position = mvPosition.xyz; - geometry.normal = normalize( transformedNormal ); - geometry.viewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( -mvPosition.xyz ); + vec3 geometryPosition = mvPosition.xyz; + vec3 geometryNormal = normalize( transformedNormal ); + vec3 geometryViewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( -mvPosition.xyz ); - GeometricContext backGeometry; - backGeometry.position = geometry.position; - backGeometry.normal = -geometry.normal; - backGeometry.viewDir = geometry.viewDir; + vec3 backGeometryNormal = - geometryNormal; vLightFront = vec3( 0.0 ); vIndirectFront = vec3( 0.0 ); @@ -97,13 +95,21 @@ const GouraudShader = { vIndirectFront += getAmbientLightIrradiance( ambientLightColor ); - vIndirectFront += getLightProbeIrradiance( lightProbe, geometry.normal ); + #if defined( USE_LIGHT_PROBES ) + + vIndirectFront += getLightProbeIrradiance( lightProbe, geometryNormal ); + + #endif #ifdef DOUBLE_SIDED vIndirectBack += getAmbientLightIrradiance( ambientLightColor ); - vIndirectBack += getLightProbeIrradiance( lightProbe, backGeometry.normal ); + #if defined( USE_LIGHT_PROBES ) + + vIndirectBack += getLightProbeIrradiance( lightProbe, backGeometryNormal ); + + #endif #endif @@ -112,9 +118,9 @@ const GouraudShader = { #pragma unroll_loop_start for ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) { - getPointLightInfo( pointLights[ i ], geometry, directLight ); + getPointLightInfo( pointLights[ i ], geometryPosition, directLight ); - dotNL = dot( geometry.normal, directLight.direction ); + dotNL = dot( geometryNormal, directLight.direction ); directLightColor_Diffuse = directLight.color; vLightFront += saturate( dotNL ) * directLightColor_Diffuse; @@ -135,9 +141,9 @@ const GouraudShader = { #pragma unroll_loop_start for ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) { - getSpotLightInfo( spotLights[ i ], geometry, directLight ); + getSpotLightInfo( spotLights[ i ], geometryPosition, directLight ); - dotNL = dot( geometry.normal, directLight.direction ); + dotNL = dot( geometryNormal, directLight.direction ); directLightColor_Diffuse = directLight.color; vLightFront += saturate( dotNL ) * directLightColor_Diffuse; @@ -157,9 +163,9 @@ const GouraudShader = { #pragma unroll_loop_start for ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) { - getDirectionalLightInfo( directionalLights[ i ], geometry, directLight ); + getDirectionalLightInfo( directionalLights[ i ], directLight ); - dotNL = dot( geometry.normal, directLight.direction ); + dotNL = dot( geometryNormal, directLight.direction ); directLightColor_Diffuse = directLight.color; vLightFront += saturate( dotNL ) * directLightColor_Diffuse; @@ -180,11 +186,11 @@ const GouraudShader = { #pragma unroll_loop_start for ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) { - vIndirectFront += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry.normal ); + vIndirectFront += getHemisphereLightIrradiance( hemisphereLights[ i ], geometryNormal ); #ifdef DOUBLE_SIDED - vIndirectBack += getHemisphereLightIrradiance( hemisphereLights[ i ], backGeometry.normal ); + vIndirectBack += getHemisphereLightIrradiance( hemisphereLights[ i ], backGeometryNormal ); #endif @@ -264,7 +270,13 @@ const GouraudShader = { #endif - #include + #ifdef USE_LIGHTMAP + + vec4 lightMapTexel = texture2D( lightMap, vLightMapUv ); + vec3 lightMapIrradiance = lightMapTexel.rgb * lightMapIntensity; + reflectedLight.indirectDiffuse += lightMapIrradiance; + + #endif reflectedLight.indirectDiffuse *= BRDF_Lambert( diffuseColor.rgb ); @@ -288,9 +300,9 @@ const GouraudShader = { #include - #include + #include #include - #include + #include #include #include #include @@ -307,6 +319,8 @@ class MeshGouraudMaterial extends ShaderMaterial { super(); + console.warn( 'THREE.MeshGouraudMaterial: MeshGouraudMaterial has been deprecated and will be removed with r183. Use THREE.MeshLambertMaterial instead.' ); // @deprecated r173 + this.isMeshGouraudMaterial = true; this.type = 'MeshGouraudMaterial'; diff --git a/examples/jsm/materials/MeshPostProcessingMaterial.js b/examples/jsm/materials/MeshPostProcessingMaterial.js new file mode 100644 index 00000000000000..0e63601ee34235 --- /dev/null +++ b/examples/jsm/materials/MeshPostProcessingMaterial.js @@ -0,0 +1,167 @@ +import { MeshPhysicalMaterial } from 'three'; + +/** + * The aim of this mesh material is to use information from a post processing pass in the diffuse color pass. + * This material is based on the MeshPhysicalMaterial. + * + * In the current state, only the information of a screen space AO pass can be used in the material. + * Actually, the output of any screen space AO (SSAO, GTAO) can be used, + * as it is only necessary to provide the AO in one color channel of a texture, + * however the AO pass must be rendered prior to the color pass, + * which makes the post-processing pass somewhat of a pre-processing pass. + * Fot this purpose a new map (`aoPassMap`) is added to the material. + * The value of the map is used the same way as the `aoMap` value. + * + * Motivation to use the outputs AO pass directly in the material: + * The incident light of a fragment is composed of ambient light, direct light and indirect light + * Ambient Occlusion only occludes ambient light and environment light, but not direct light. + * Direct light is only occluded by geometry that casts shadows. + * And of course the emitted light should not be darkened by ambient occlusion either. + * This cannot be achieved if the AO post processing pass is simply blended with the diffuse render pass. + * + * Further extension work might be to use the output of an SSR pass or an HBIL pass from a previous frame. + * This would then create the possibility of SSR and IR depending on material properties such as `roughness`, `metalness` and `reflectivity`. + * + * @augments MeshPhysicalMaterial + * @three_import import { MeshPostProcessingMaterial } from 'three/addons/materials/MeshPostProcessingMaterial.js'; + */ +class MeshPostProcessingMaterial extends MeshPhysicalMaterial { + + /** + * Constructs a new conditional line material. + * + * @param {Object} [parameters] - An object with one or more properties + * defining the material's appearance. Any property of the material + * (including any property from inherited materials) can be passed + * in here. Color values can be passed any type of value accepted + * by {@link Color#set}. + */ + constructor( parameters ) { + + const aoPassMap = parameters.aoPassMap; + const aoPassMapScale = parameters.aoPassMapScale || 1.0; + delete parameters.aoPassMap; + delete parameters.aoPassMapScale; + + super( parameters ); + + this.onBeforeCompile = this._onBeforeCompile; + this.customProgramCacheKey = this._customProgramCacheKey; + this._aoPassMap = aoPassMap; + + /** + * The scale of the AO pass. + * + * @type {number} + * @default 1 + */ + this.aoPassMapScale = aoPassMapScale; + this._shader = null; + + } + + /** + * A texture representing the AO pass. + * + * @type {Texture} + */ + get aoPassMap() { + + return this._aoPassMap; + + } + + set aoPassMap( aoPassMap ) { + + this._aoPassMap = aoPassMap; + this.needsUpdate = true; + this._setUniforms(); + + } + + _customProgramCacheKey() { + + return this._aoPassMap !== undefined && this._aoPassMap !== null ? 'aoPassMap' : ''; + + } + + _onBeforeCompile( shader ) { + + this._shader = shader; + + if ( this._aoPassMap !== undefined && this._aoPassMap !== null ) { + + shader.fragmentShader = shader.fragmentShader.replace( + '#include ', + aomap_pars_fragment_replacement + ); + shader.fragmentShader = shader.fragmentShader.replace( + '#include ', + aomap_fragment_replacement + ); + + } + + this._setUniforms(); + + } + + _setUniforms() { + + if ( this._shader ) { + + this._shader.uniforms.tAoPassMap = { value: this._aoPassMap }; + this._shader.uniforms.aoPassMapScale = { value: this.aoPassMapScale }; + + } + + } + +} + +const aomap_pars_fragment_replacement = /* glsl */` +#ifdef USE_AOMAP + + uniform sampler2D aoMap; + uniform float aoMapIntensity; + +#endif + + uniform sampler2D tAoPassMap; + uniform float aoPassMapScale; +`; + +const aomap_fragment_replacement = /* glsl */` +#ifndef AOPASSMAP_SWIZZLE + #define AOPASSMAP_SWIZZLE r +#endif + float ambientOcclusion = texelFetch( tAoPassMap, ivec2( gl_FragCoord.xy * aoPassMapScale ), 0 ).AOPASSMAP_SWIZZLE; + +#ifdef USE_AOMAP + + // reads channel R, compatible with a combined OcclusionRoughnessMetallic (RGB) texture + ambientOcclusion = min( ambientOcclusion, texture2D( aoMap, vAoMapUv ).r ); + ambientOcclusion *= ( ambientOcclusion - 1.0 ) * aoMapIntensity + 1.0; + +#endif + + reflectedLight.indirectDiffuse *= ambientOcclusion; + + #if defined( USE_CLEARCOAT ) + clearcoatSpecularIndirect *= ambientOcclusion; + #endif + + #if defined( USE_SHEEN ) + sheenSpecularIndirect *= ambientOcclusion; + #endif + + #if defined( USE_ENVMAP ) && defined( STANDARD ) + + float dotNV = saturate( dot( geometryNormal, geometryViewDir ) ); + + reflectedLight.indirectSpecular *= computeSpecularOcclusion( dotNV, ambientOcclusion, material.roughness ); + + #endif +`; + +export { MeshPostProcessingMaterial }; diff --git a/examples/jsm/math/Capsule.js b/examples/jsm/math/Capsule.js index 08f191c136e7e1..902eefeba2e410 100644 --- a/examples/jsm/math/Capsule.js +++ b/examples/jsm/math/Capsule.js @@ -2,80 +2,140 @@ import { Vector3 } from 'three'; -const _v1 = new Vector3(); -const _v2 = new Vector3(); -const _v3 = new Vector3(); - -const EPS = 1e-10; - +/** + * A capsule is essentially a cylinder with hemispherical caps at both ends. + * It can be thought of as a swept sphere, where a sphere is moved along a line segment. + * + * Capsules are often used as bounding volumes (next to AABBs and bounding spheres). + * + * @three_import import { Capsule } from 'three/addons/math/Capsule.js'; + */ class Capsule { + /** + * Constructs a new capsule. + * + * @param {Vector3} [start] - The start vector. + * @param {Vector3} [end] - The end vector. + * @param {number} [radius=1] - The capsule's radius. + */ constructor( start = new Vector3( 0, 0, 0 ), end = new Vector3( 0, 1, 0 ), radius = 1 ) { + /** + * The start vector. + * + * @type {Vector3} + */ this.start = start; + + /** + * The end vector. + * + * @type {Vector3} + */ this.end = end; + + /** + * The capsule's radius. + * + * @type {number} + * @default 1 + */ this.radius = radius; } + /** + * Returns a new capsule with copied values from this instance. + * + * @return {Capsule} A clone of this instance. + */ clone() { - return new Capsule( this.start.clone(), this.end.clone(), this.radius ); + return new this.constructor().copy( this ); } + /** + * Sets the capsule components to the given values. + * Please note that this method only copies the values from the given objects. + * + * @param {Vector3} start - The start vector. + * @param {Vector3} end - The end vector + * @param {number} radius - The capsule's radius. + * @return {Capsule} A reference to this capsule. + */ set( start, end, radius ) { this.start.copy( start ); this.end.copy( end ); this.radius = radius; + return this; + } + /** + * Copies the values of the given capsule to this instance. + * + * @param {Capsule} capsule - The capsule to copy. + * @return {Capsule} A reference to this capsule. + */ copy( capsule ) { this.start.copy( capsule.start ); this.end.copy( capsule.end ); this.radius = capsule.radius; + return this; + } + /** + * Returns the center point of this capsule. + * + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The center point. + */ getCenter( target ) { return target.copy( this.end ).add( this.start ).multiplyScalar( 0.5 ); } + /** + * Adds the given offset to this capsule, effectively moving it in 3D space. + * + * @param {Vector3} v - The offset that should be used to translate the capsule. + * @return {Capsule} A reference to this capsule. + */ translate( v ) { this.start.add( v ); this.end.add( v ); - } - - checkAABBAxis( p1x, p1y, p2x, p2y, minx, maxx, miny, maxy, radius ) { - - return ( - ( minx - p1x < radius || minx - p2x < radius ) && - ( p1x - maxx < radius || p2x - maxx < radius ) && - ( miny - p1y < radius || miny - p2y < radius ) && - ( p1y - maxy < radius || p2y - maxy < radius ) - ); + return this; } + /** + * Returns `true` if the given bounding box intersects with this capsule. + * + * @param {Box3} box - The bounding box to test. + * @return {boolean} Whether the given bounding box intersects with this capsule. + */ intersectsBox( box ) { return ( - this.checkAABBAxis( + checkAABBAxis( this.start.x, this.start.y, this.end.x, this.end.y, box.min.x, box.max.x, box.min.y, box.max.y, this.radius ) && - this.checkAABBAxis( + checkAABBAxis( this.start.x, this.start.z, this.end.x, this.end.z, box.min.x, box.max.x, box.min.z, box.max.z, this.radius ) && - this.checkAABBAxis( + checkAABBAxis( this.start.y, this.start.z, this.end.y, this.end.z, box.min.y, box.max.y, box.min.z, box.max.z, this.radius ) @@ -83,54 +143,16 @@ class Capsule { } - lineLineMinimumPoints( line1, line2 ) { - - const r = _v1.copy( line1.end ).sub( line1.start ); - const s = _v2.copy( line2.end ).sub( line2.start ); - const w = _v3.copy( line2.start ).sub( line1.start ); - - const a = r.dot( s ), - b = r.dot( r ), - c = s.dot( s ), - d = s.dot( w ), - e = r.dot( w ); - - let t1, t2; - const divisor = b * c - a * a; - - if ( Math.abs( divisor ) < EPS ) { - - const d1 = - d / c; - const d2 = ( a - d ) / c; - - if ( Math.abs( d1 - 0.5 ) < Math.abs( d2 - 0.5 ) ) { - - t1 = 0; - t2 = d1; - - } else { - - t1 = 1; - t2 = d2; - - } - - } else { - - t1 = ( d * a + e * c ) / divisor; - t2 = ( t1 * a - d ) / c; - - } - - t2 = Math.max( 0, Math.min( 1, t2 ) ); - t1 = Math.max( 0, Math.min( 1, t1 ) ); - - const point1 = r.multiplyScalar( t1 ).add( line1.start ); - const point2 = s.multiplyScalar( t2 ).add( line2.start ); +} - return [ point1, point2 ]; +function checkAABBAxis( p1x, p1y, p2x, p2y, minx, maxx, miny, maxy, radius ) { - } + return ( + ( minx - p1x < radius || minx - p2x < radius ) && + ( p1x - maxx < radius || p2x - maxx < radius ) && + ( miny - p1y < radius || miny - p2y < radius ) && + ( p1y - maxy < radius || p2y - maxy < radius ) + ); } diff --git a/examples/jsm/math/ColorConverter.js b/examples/jsm/math/ColorConverter.js index f45b3c7208ceb6..c257f4446fa114 100644 --- a/examples/jsm/math/ColorConverter.js +++ b/examples/jsm/math/ColorConverter.js @@ -2,8 +2,23 @@ import { MathUtils } from 'three'; const _hsl = {}; +/** + * A utility class with helper functions for color conversion. + * + * @hideconstructor + * @three_import import { ColorConverter } from 'three/addons/math/ColorConverter.js'; + */ class ColorConverter { + /** + * Sets the given HSV color definition to the given color object. + * + * @param {Color} color - The color to set. + * @param {number} h - The hue. + * @param {number} s - The saturation. + * @param {number} v - The value. + * @return {Color} The update color. + */ static setHSV( color, h, s, v ) { // https://gist.github.com/xpansive/1337890#file-index-js @@ -16,6 +31,13 @@ class ColorConverter { } + /** + * Returns a HSV color representation of the given color object. + * + * @param {Color} color - The color to get HSV values from. + * @param {{h:number,s:number,v:number}} target - The target object that is used to store the method's result. + * @return {{h:number,s:number,v:number}} The HSV color. + */ static getHSV( color, target ) { color.getHSL( _hsl ); diff --git a/examples/jsm/math/ColorSpaces.js b/examples/jsm/math/ColorSpaces.js new file mode 100644 index 00000000000000..9b2cb5b3be0e98 --- /dev/null +++ b/examples/jsm/math/ColorSpaces.js @@ -0,0 +1,129 @@ +import { LinearTransfer, Matrix3, SRGBTransfer } from 'three'; + +/** @module ColorSpaces */ + +// Reference: http://www.russellcottrell.com/photo/matrixCalculator.htm + +const P3_PRIMARIES = [ 0.680, 0.320, 0.265, 0.690, 0.150, 0.060 ]; +const P3_LUMINANCE_COEFFICIENTS = [ 0.2289, 0.6917, 0.0793 ]; +const REC2020_PRIMARIES = [ 0.708, 0.292, 0.170, 0.797, 0.131, 0.046 ]; +const REC2020_LUMINANCE_COEFFICIENTS = [ 0.2627, 0.6780, 0.0593 ]; +const D65 = [ 0.3127, 0.3290 ]; + +/****************************************************************************** + * Display P3 definitions + */ + +const LINEAR_DISPLAY_P3_TO_XYZ = /*@__PURE__*/ new Matrix3().set( + 0.4865709, 0.2656677, 0.1982173, + 0.2289746, 0.6917385, 0.0792869, + 0.0000000, 0.0451134, 1.0439444 +); + +const XYZ_TO_LINEAR_DISPLAY_P3 = /*@__PURE__*/ new Matrix3().set( + 2.4934969, - 0.9313836, - 0.4027108, + - 0.8294890, 1.7626641, 0.0236247, + 0.0358458, - 0.0761724, 0.9568845 +); + +/** + * Display-P3 color space. + * + * @type {string} + * @constant + */ +export const DisplayP3ColorSpace = 'display-p3'; + +/** + * Display-P3-Linear color space. + * + * @type {string} + * @constant + */ +export const LinearDisplayP3ColorSpace = 'display-p3-linear'; + +/** + * Implementation object for the Display-P3 color space. + * + * @type {module:ColorSpaces~ColorSpaceImpl} + * @constant + */ +export const DisplayP3ColorSpaceImpl = { + primaries: P3_PRIMARIES, + whitePoint: D65, + transfer: SRGBTransfer, + toXYZ: LINEAR_DISPLAY_P3_TO_XYZ, + fromXYZ: XYZ_TO_LINEAR_DISPLAY_P3, + luminanceCoefficients: P3_LUMINANCE_COEFFICIENTS, + outputColorSpaceConfig: { drawingBufferColorSpace: DisplayP3ColorSpace } +}; + +/** + * Implementation object for the Display-P3-Linear color space. + * + * @type {module:ColorSpaces~ColorSpaceImpl} + * @constant + */ +export const LinearDisplayP3ColorSpaceImpl = { + primaries: P3_PRIMARIES, + whitePoint: D65, + transfer: LinearTransfer, + toXYZ: LINEAR_DISPLAY_P3_TO_XYZ, + fromXYZ: XYZ_TO_LINEAR_DISPLAY_P3, + luminanceCoefficients: P3_LUMINANCE_COEFFICIENTS, + workingColorSpaceConfig: { unpackColorSpace: DisplayP3ColorSpace }, + outputColorSpaceConfig: { drawingBufferColorSpace: DisplayP3ColorSpace } +}; + +/****************************************************************************** + * Rec. 2020 definitions + */ + +const LINEAR_REC2020_TO_XYZ = /*@__PURE__*/ new Matrix3().set( + 0.6369580, 0.1446169, 0.1688810, + 0.2627002, 0.6779981, 0.0593017, + 0.0000000, 0.0280727, 1.0609851 +); + +const XYZ_TO_LINEAR_REC2020 = /*@__PURE__*/ new Matrix3().set( + 1.7166512, - 0.3556708, - 0.2533663, + - 0.6666844, 1.6164812, 0.0157685, + 0.0176399, - 0.0427706, 0.9421031 +); + +/** + * Rec2020-Linear color space. + * + * @type {string} + * @constant + */ +export const LinearRec2020ColorSpace = 'rec2020-linear'; + +/** + * Implementation object for the Rec2020-Linear color space. + * + * @type {module:ColorSpaces~ColorSpaceImpl} + * @constant + */ +export const LinearRec2020ColorSpaceImpl = { + primaries: REC2020_PRIMARIES, + whitePoint: D65, + transfer: LinearTransfer, + toXYZ: LINEAR_REC2020_TO_XYZ, + fromXYZ: XYZ_TO_LINEAR_REC2020, + luminanceCoefficients: REC2020_LUMINANCE_COEFFICIENTS, +}; + + +/** + * An object holding the color space implementation. + * + * @typedef {Object} module:ColorSpaces~ColorSpaceImpl + * @property {Array} primaries - The primaries. + * @property {Array} whitePoint - The white point. + * @property {Matrix3} toXYZ - A color space conversion matrix, converting to CIE XYZ. + * @property {Matrix3} fromXYZ - A color space conversion matrix, converting from CIE XYZ. + * @property {Array} luminanceCoefficients - The luminance coefficients. + * @property {{unpackColorSpace:string}} [workingColorSpaceConfig] - The working color space config. + * @property {{drawingBufferColorSpace:string}} [outputColorSpaceConfig] - The drawing buffer color space config. + **/ diff --git a/examples/jsm/math/ConvexHull.js b/examples/jsm/math/ConvexHull.js index b1368a4ed8ea1a..a58fdf43f25807 100644 --- a/examples/jsm/math/ConvexHull.js +++ b/examples/jsm/math/ConvexHull.js @@ -5,10 +5,6 @@ import { Vector3 } from 'three'; -/** - * Ported from: https://github.com/maurizzzio/quickhull3d/ by Mauricio Poppe (https://github.com/maurizzzio) - */ - const Visible = 0; const Deleted = 1; @@ -18,8 +14,20 @@ const _plane = new Plane(); const _closestPoint = new Vector3(); const _triangle = new Triangle(); +/** + * Can be used to compute the convex hull in 3D space for a given set of points. It + * is primarily intended for {@link ConvexGeometry}. + * + * This Quickhull 3D implementation is a port of [quickhull3d]{@link https://github.com/maurizzzio/quickhull3d/} + * by Mauricio Poppe. + * + * @three_import import { ConvexHull } from 'three/addons/math/ConvexHull.js'; + */ class ConvexHull { + /** + * Constructs a new convex hull. + */ constructor() { this.tolerance = - 1; @@ -40,10 +48,16 @@ class ConvexHull { this.assigned = new VertexList(); this.unassigned = new VertexList(); - this.vertices = []; // vertices of the hull (internal representation of given geometry data) + this.vertices = []; // vertices of the hull (internal representation of given geometry data) } + /** + * Computes to convex hull for the given array of points. + * + * @param {Array} points - The array of points in 3D space. + * @return {ConvexHull} A reference to this convex hull. + */ setFromPoints( points ) { // The algorithm needs at least four points. @@ -58,7 +72,7 @@ class ConvexHull { } - this.compute(); + this._compute(); } @@ -66,6 +80,13 @@ class ConvexHull { } + /** + * Computes the convex hull of the given 3D object (including its descendants), + * accounting for the world transforms of both the 3D object and its descendants. + * + * @param {Object3D} object - The 3D object to compute the convex hull for. + * @return {ConvexHull} A reference to this convex hull. + */ setFromObject( object ) { const points = []; @@ -102,6 +123,12 @@ class ConvexHull { } + /** + * Returns `true` if the given point lies in the convex hull. + * + * @param {Vector3} point - The point to test. + * @return {boolean} Whether the given point lies in the convex hull or not. + */ containsPoint( point ) { const faces = this.faces; @@ -120,6 +147,13 @@ class ConvexHull { } + /** + * Computes the intersections point of the given ray and this convex hull. + * + * @param {Ray} ray - The ray to test. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3|null} The intersection point. Returns `null` if not intersection was detected. + */ intersectRay( ray, target ) { // based on "Fast Ray-Convex Polyhedron Intersection" by Eric Haines, GRAPHICS GEMS II @@ -196,12 +230,23 @@ class ConvexHull { } + /** + * Returns `true` if the given ray intersects with this convex hull. + * + * @param {Ray} ray - The ray to test. + * @return {boolean} Whether the given ray intersects with this convex hull or not. + */ intersectsRay( ray ) { return this.intersectRay( ray, _v1 ) !== null; } + /** + * Makes the convex hull empty. + * + * @return {ConvexHull} A reference to this convex hull. + */ makeEmpty() { this.faces = []; @@ -211,9 +256,17 @@ class ConvexHull { } - // Adds a vertex to the 'assigned' list of vertices and assigns it to the given face + // private - addVertexToFace( vertex, face ) { + /** + * Adds a vertex to the 'assigned' list of vertices and assigns it to the given face. + * + * @private + * @param {VertexNode} vertex - The vertex to add. + * @param {Face} face - The target face. + * @return {ConvexHull} A reference to this convex hull. + */ + _addVertexToFace( vertex, face ) { vertex.face = face; @@ -233,9 +286,17 @@ class ConvexHull { } - // Removes a vertex from the 'assigned' list of vertices and from the given face - - removeVertexFromFace( vertex, face ) { + /** + * Removes a vertex from the 'assigned' list of vertices and from the given face. + * It also makes sure that the link from 'face' to the first vertex it sees in 'assigned' + * is linked correctly after the removal. + * + * @private + * @param {VertexNode} vertex - The vertex to remove. + * @param {Face} face - The target face. + * @return {ConvexHull} A reference to this convex hull. + */ + _removeVertexFromFace( vertex, face ) { if ( vertex === face.outside ) { @@ -263,9 +324,15 @@ class ConvexHull { } - // Removes all the visible vertices that a given face is able to see which are stored in the 'assigned' vertex list - - removeAllVerticesFromFace( face ) { + /** + * Removes all the visible vertices that a given face is able to see which are stored in + * the 'assigned' vertex list. + * + * @private + * @param {Face} face - The target face. + * @return {VertexNode|undefined} A reference to this convex hull. + */ + _removeAllVerticesFromFace( face ) { if ( face.outside !== null ) { @@ -293,11 +360,21 @@ class ConvexHull { } - // Removes all the visible vertices that 'face' is able to see - - deleteFaceVertices( face, absorbingFace ) { - - const faceVertices = this.removeAllVerticesFromFace( face ); + /** + * Removes all the visible vertices that `face` is able to see. + * + * - If `absorbingFace` doesn't exist, then all the removed vertices will be added to the 'unassigned' vertex list. + * - If `absorbingFace` exists, then this method will assign all the vertices of 'face' that can see 'absorbingFace'. + * - If a vertex cannot see `absorbingFace`, it's added to the 'unassigned' vertex list. + * + * @private + * @param {Face} face - The given face. + * @param {Face} [absorbingFace] - An optional face that tries to absorb the vertices of the first face. + * @return {ConvexHull} A reference to this convex hull. + */ + _deleteFaceVertices( face, absorbingFace ) { + + const faceVertices = this._removeAllVerticesFromFace( face ); if ( faceVertices !== undefined ) { @@ -327,7 +404,7 @@ class ConvexHull { if ( distance > this.tolerance ) { - this.addVertexToFace( vertex, absorbingFace ); + this._addVertexToFace( vertex, absorbingFace ); } else { @@ -349,9 +426,14 @@ class ConvexHull { } - // Reassigns as many vertices as possible from the unassigned list to the new faces - - resolveUnassignedPoints( newFaces ) { + /** + * Reassigns as many vertices as possible from the unassigned list to the new faces. + * + * @private + * @param {Array} newFaces - The new faces. + * @return {ConvexHull} A reference to this convex hull. + */ + _resolveUnassignedPoints( newFaces ) { if ( this.unassigned.isEmpty() === false ) { @@ -359,7 +441,7 @@ class ConvexHull { do { - // buffer 'next' reference, see .deleteFaceVertices() + // buffer 'next' reference, see ._deleteFaceVertices() const nextVertex = vertex.next; @@ -392,7 +474,7 @@ class ConvexHull { if ( maxFace !== null ) { - this.addVertexToFace( vertex, maxFace ); + this._addVertexToFace( vertex, maxFace ); } @@ -406,9 +488,14 @@ class ConvexHull { } - // Computes the extremes of a simplex which will be the initial hull - - computeExtremes() { + /** + * Computes the extremes values (min/max vectors) which will be used to + * compute the initial hull. + * + * @private + * @return {Object} The extremes. + */ + _computeExtremes() { const min = new Vector3(); const max = new Vector3(); @@ -474,13 +561,17 @@ class ConvexHull { } - // Computes the initial simplex assigning to its faces all the points - // that are candidates to form part of the hull - - computeInitialHull() { + /** + * Computes the initial simplex assigning to its faces all the points that are + * candidates to form part of the hull. + * + * @private + * @return {ConvexHull} A reference to this convex hull. + */ + _computeInitialHull() { const vertices = this.vertices; - const extremes = this.computeExtremes(); + const extremes = this._computeExtremes(); const min = extremes.min; const max = extremes.max; @@ -652,7 +743,7 @@ class ConvexHull { if ( maxFace !== null ) { - this.addVertexToFace( vertex, maxFace ); + this._addVertexToFace( vertex, maxFace ); } @@ -664,9 +755,13 @@ class ConvexHull { } - // Removes inactive faces - - reindexFaces() { + /** + * Removes inactive (e.g. deleted) faces from the internal face list. + * + * @private + * @return {ConvexHull} A reference to this convex hull. + */ + _reindexFaces() { const activeFaces = []; @@ -688,9 +783,17 @@ class ConvexHull { } - // Finds the next vertex to create faces with the current hull - - nextVertexToAdd() { + /** + * Finds the next vertex to create faces with the current hull. + * + * - Let the initial face be the first face existing in the 'assigned' vertex list. + * - If a face doesn't exist then return since there're no vertices left. + * - Otherwise for each vertex that face sees find the one furthest away from it. + * + * @private + * @return {?VertexNode} The next vertex to add. + */ + _nextVertexToAdd() { // if the 'assigned' list of vertices is empty, no vertices are left. return with 'undefined' @@ -698,7 +801,7 @@ class ConvexHull { let eyeVertex, maxDistance = 0; - // grap the first available face and start with the first visible vertex of that face + // grab the first available face and start with the first visible vertex of that face const eyeFace = this.assigned.first().face; let vertex = eyeFace.outside; @@ -726,15 +829,23 @@ class ConvexHull { } - // Computes a chain of half edges in CCW order called the 'horizon'. - // For an edge to be part of the horizon it must join a face that can see - // 'eyePoint' and a face that cannot see 'eyePoint'. - - computeHorizon( eyePoint, crossEdge, face, horizon ) { + /** + * Computes a chain of half edges in CCW order called the 'horizon'. For an edge + * to be part of the horizon it must join a face that can see 'eyePoint' and a face + * that cannot see 'eyePoint'. + * + * @private + * @param {Vector3} eyePoint - The 3D-coordinates of a point. + * @param {HalfEdge} crossEdge - The edge used to jump to the current face. + * @param {Face} face - The current face being tested. + * @param {Array} horizon - The edges that form part of the horizon in CCW order. + * @return {ConvexHull} A reference to this convex hull. + */ + _computeHorizon( eyePoint, crossEdge, face, horizon ) { // moves face's vertices to the 'unassigned' vertex list - this.deleteFaceVertices( face ); + this._deleteFaceVertices( face ); face.mark = Deleted; @@ -764,7 +875,7 @@ class ConvexHull { // the opposite face can see the vertex, so proceed with next edge - this.computeHorizon( eyePoint, twinEdge, oppositeFace, horizon ); + this._computeHorizon( eyePoint, twinEdge, oppositeFace, horizon ); } else { @@ -784,9 +895,17 @@ class ConvexHull { } - // Creates a face with the vertices 'eyeVertex.point', 'horizonEdge.tail' and 'horizonEdge.head' in CCW order - - addAdjoiningFace( eyeVertex, horizonEdge ) { + /** + * Creates a face with the vertices 'eyeVertex.point', 'horizonEdge.tail' and 'horizonEdge.head' + * in CCW order. All the half edges are created in CCW order thus the face is always pointing + * outside the hull. + * + * @private + * @param {VertexNode} eyeVertex - The vertex that is added to the hull. + * @param {HalfEdge} horizonEdge - A single edge of the horizon. + * @return {HalfEdge} The half edge whose vertex is the eyeVertex. + */ + _addAdjoiningFace( eyeVertex, horizonEdge ) { // all the half edges are created in ccw order thus the face is always pointing outside the hull @@ -803,10 +922,16 @@ class ConvexHull { } - // Adds 'horizon.length' faces to the hull, each face will be linked with the - // horizon opposite face and the face on the left/right - - addNewFaces( eyeVertex, horizon ) { + /** + * Adds 'horizon.length' faces to the hull, each face will be linked with the horizon + * opposite face and the face on the left/right. + * + * @private + * @param {VertexNode} eyeVertex - The vertex that is added to the hull. + * @param {Array} horizon - The horizon. + * @return {ConvexHull} A reference to this convex hull. + */ + _addNewFaces( eyeVertex, horizon ) { this.newFaces = []; @@ -819,7 +944,7 @@ class ConvexHull { // returns the right side edge - const sideEdge = this.addAdjoiningFace( eyeVertex, horizonEdge ); + const sideEdge = this._addAdjoiningFace( eyeVertex, horizonEdge ); if ( firstSideEdge === null ) { @@ -846,9 +971,21 @@ class ConvexHull { } - // Adds a vertex to the hull - - addVertexToHull( eyeVertex ) { + /** + * Adds a vertex to the hull with the following algorithm: + * + * - Compute the 'horizon' which is a chain of half edges. For an edge to belong to this group + * it must be the edge connecting a face that can see 'eyeVertex' and a face which cannot see 'eyeVertex'. + * - All the faces that can see 'eyeVertex' have its visible vertices removed from the assigned vertex list. + * - A new set of faces is created with each edge of the 'horizon' and 'eyeVertex'. Each face is connected + * with the opposite horizon face and the face on the left/right. + * - The vertices removed from all the visible faces are assigned to the new faces if possible. + * + * @private + * @param {VertexNode} eyeVertex - The vertex to add. + * @return {ConvexHull} A reference to this convex hull. + */ + _addVertexToHull( eyeVertex ) { const horizon = []; @@ -856,21 +993,27 @@ class ConvexHull { // remove 'eyeVertex' from 'eyeVertex.face' so that it can't be added to the 'unassigned' vertex list - this.removeVertexFromFace( eyeVertex, eyeVertex.face ); + this._removeVertexFromFace( eyeVertex, eyeVertex.face ); - this.computeHorizon( eyeVertex.point, null, eyeVertex.face, horizon ); + this._computeHorizon( eyeVertex.point, null, eyeVertex.face, horizon ); - this.addNewFaces( eyeVertex, horizon ); + this._addNewFaces( eyeVertex, horizon ); // reassign 'unassigned' vertices to the new faces - this.resolveUnassignedPoints( this.newFaces ); + this._resolveUnassignedPoints( this.newFaces ); return this; } - cleanup() { + /** + * Cleans up internal properties after computing the convex hull. + * + * @private + * @return {ConvexHull} A reference to this convex hull. + */ + _cleanup() { this.assigned.clear(); this.unassigned.clear(); @@ -880,23 +1023,29 @@ class ConvexHull { } - compute() { + /** + * Starts the execution of the quick hull algorithm. + * + * @private + * @return {ConvexHull} A reference to this convex hull. + */ + _compute() { let vertex; - this.computeInitialHull(); + this._computeInitialHull(); // add all available vertices gradually to the hull - while ( ( vertex = this.nextVertexToAdd() ) !== undefined ) { + while ( ( vertex = this._nextVertexToAdd() ) !== undefined ) { - this.addVertexToHull( vertex ); + this._addVertexToHull( vertex ); } - this.reindexFaces(); + this._reindexFaces(); - this.cleanup(); + this._cleanup(); return this; @@ -904,23 +1053,84 @@ class ConvexHull { } -// - +/** + * Represents a section bounded by a specific amount of half-edges. + * The current implementation assumes that a face always consist of three edges. + * + * @private + */ class Face { + /** + * Constructs a new face. + */ constructor() { + /** + * The normal vector of the face. + * + * @private + * @type {Vector3} + */ this.normal = new Vector3(); + + /** + * The midpoint or centroid of the face. + * + * @private + * @type {Vector3} + */ this.midpoint = new Vector3(); + + /** + * The area of the face. + * + * @private + * @type {number} + * @default 0 + */ this.area = 0; - this.constant = 0; // signed distance from face to the origin + /** + * Signed distance from face to the origin. + * + * @private + * @type {number} + * @default 0 + */ + this.constant = 0; + + /** + * Reference to a vertex in a vertex list this face can see. + * + * @private + * @type {?VertexNode} + * @default null + */ this.outside = null; // reference to a vertex in a vertex list this face can see this.mark = Visible; + + /** + * Reference to the base edge of a face. To retrieve all edges, you can use the + * `next` reference of the current edge. + * + * @private + * @type {?HalfEdge} + * @default null + */ this.edge = null; } + /** + * Creates a face from the given vertex nodes. + * + * @private + * @param {VertexNode} a - The first vertex node. + * @param {VertexNode} b - The second vertex node. + * @param {VertexNode} c - The third vertex node. + * @return {Face} The created face. + */ static create( a, b, c ) { const face = new Face(); @@ -943,6 +1153,13 @@ class Face { } + /** + * Returns an edge by the given index. + * + * @private + * @param {number} i - The edge index. + * @return {HalfEdge} The edge. + */ getEdge( i ) { let edge = this.edge; @@ -965,6 +1182,12 @@ class Face { } + /** + * Computes all properties of the face. + * + * @private + * @return {Face} A reference to this face. + */ compute() { const a = this.edge.tail(); @@ -983,6 +1206,13 @@ class Face { } + /** + * Returns the signed distance from a given point to the plane representation of this face. + * + * @private + * @param {Vector3} point - The point to compute the distance to. + * @return {number} The distance. + */ distanceToPoint( point ) { return this.normal.dot( point ) - this.constant; @@ -991,33 +1221,97 @@ class Face { } -// Entity for a Doubly-Connected Edge List (DCEL). - +/** + * The basis for a half-edge data structure, also known as doubly + * connected edge list (DCEL). + * + * @private + */ class HalfEdge { - + /** + * Constructs a new half edge. + * + * @param {VertexNode} vertex - A reference to its destination vertex. + * @param {Face} face - A reference to its face. + */ constructor( vertex, face ) { + /** + * A reference to its destination vertex. + * + * @private + * @type {VertexNode} + */ this.vertex = vertex; + + /** + * Reference to the previous half-edge of the same face. + * + * @private + * @type {?HalfEdge} + * @default null + */ this.prev = null; + + /** + * Reference to the next half-edge of the same face. + * + * @private + * @type {?HalfEdge} + * @default null + */ this.next = null; + + /** + * Reference to the twin half-edge to reach the opposite face. + * + * @private + * @type {?HalfEdge} + * @default null + */ this.twin = null; + + /** + * A reference to its face. + * + * @private + * @type {Face} + */ this.face = face; } + /** + * Returns the destination vertex. + * + * @private + * @return {VertexNode} The destination vertex. + */ head() { return this.vertex; } + /** + * Returns the origin vertex. + * + * @private + * @return {VertexNode} The destination vertex. + */ tail() { return this.prev ? this.prev.vertex : null; } + /** + * Returns the Euclidean length (straight-line length) of the edge. + * + * @private + * @return {number} The edge's length. + */ length() { const head = this.head(); @@ -1033,6 +1327,12 @@ class HalfEdge { } + /** + * Returns the square of the Euclidean length (straight-line length) of the edge. + * + * @private + * @return {number} The square of the edge's length. + */ lengthSquared() { const head = this.head(); @@ -1048,6 +1348,14 @@ class HalfEdge { } + /** + * Sets the twin edge of this half-edge. It also ensures that the twin reference + * of the given half-edge is correctly set. + * + * @private + * @param {HalfEdge} edge - The twin edge to set. + * @return {HalfEdge} A reference to this edge. + */ setTwin( edge ) { this.twin = edge; @@ -1059,44 +1367,121 @@ class HalfEdge { } -// A vertex as a double linked list node. - +/** + * A vertex as a double linked list node. + * + * @private + */ class VertexNode { + /** + * Constructs a new vertex node. + * + * @param {Vector3} point - A point in 3D space. + */ constructor( point ) { + /** + * A point in 3D space. + * + * @private + * @type {Vector3} + */ this.point = point; + + /** + * Reference to the previous vertex in the double linked list. + * + * @private + * @type {?VertexNode} + * @default null + */ this.prev = null; + + /** + * Reference to the next vertex in the double linked list. + * + * @private + * @type {?VertexNode} + * @default null + */ this.next = null; - this.face = null; // the face that is able to see this vertex + + /** + * Reference to the face that is able to see this vertex. + * + * @private + * @type {?Face} + * @default null + */ + this.face = null; } } -// A double linked list that contains vertex nodes. - +/** + * A doubly linked list of vertices. + * + * @private + */ class VertexList { + /** + * Constructs a new vertex list. + */ constructor() { + /** + * Reference to the first vertex of the linked list. + * + * @private + * @type {?VertexNode} + * @default null + */ this.head = null; + + /** + * Reference to the last vertex of the linked list. + * + * @private + * @type {?VertexNode} + * @default null + */ this.tail = null; } + /** + * Returns the head reference. + * + * @private + * @return {VertexNode} The head reference. + */ first() { return this.head; } + /** + * Returns the tail reference. + * + * @private + * @return {VertexNode} The tail reference. + */ last() { return this.tail; } + /** + * Clears the linked list. + * + * @private + * @return {VertexList} A reference to this vertex list. + */ clear() { this.head = this.tail = null; @@ -1105,8 +1490,14 @@ class VertexList { } - // Inserts a vertex before the target vertex - + /** + * Inserts a vertex before a target vertex. + * + * @private + * @param {VertexNode} target - The target. + * @param {VertexNode} vertex - The vertex to insert. + * @return {VertexList} A reference to this vertex list. + */ insertBefore( target, vertex ) { vertex.prev = target.prev; @@ -1128,8 +1519,14 @@ class VertexList { } - // Inserts a vertex after the target vertex - + /** + * Inserts a vertex after a target vertex. + * + * @private + * @param {VertexNode} target - The target. + * @param {VertexNode} vertex - The vertex to insert. + * @return {VertexList} A reference to this vertex list. + */ insertAfter( target, vertex ) { vertex.prev = target; @@ -1151,8 +1548,13 @@ class VertexList { } - // Appends a vertex to the end of the linked list - + /** + * Appends a vertex to this vertex list. + * + * @private + * @param {VertexNode} vertex - The vertex to append. + * @return {VertexList} A reference to this vertex list. + */ append( vertex ) { if ( this.head === null ) { @@ -1174,8 +1576,13 @@ class VertexList { } - // Appends a chain of vertices where 'vertex' is the head. - + /** + * Appends a chain of vertices where the given vertex is the head. + * + * @private + * @param {VertexNode} vertex - The head vertex of a chain of vertices. + * @return {VertexList} A reference to this vertex list. + */ appendChain( vertex ) { if ( this.head === null ) { @@ -1204,8 +1611,13 @@ class VertexList { } - // Removes a vertex from the linked list - + /** + * Removes a vertex from the linked list. + * + * @private + * @param {VertexNode} vertex - The vertex to remove. + * @return {VertexList} A reference to this vertex list. + */ remove( vertex ) { if ( vertex.prev === null ) { @@ -1232,8 +1644,14 @@ class VertexList { } - // Removes a list of vertices whose 'head' is 'a' and whose 'tail' is b - + /** + * Removes a sublist of vertices from the linked list. + * + * @private + * @param {VertexNode} a - The head of the sublist. + * @param {VertexNode} b - The tail of the sublist. + * @return {VertexList} A reference to this vertex list. + */ removeSubList( a, b ) { if ( a.prev === null ) { @@ -1260,6 +1678,12 @@ class VertexList { } + /** + * Returns `true` if the linked list is empty. + * + * @private + * @return {boolean} Whether the linked list is empty or not. + */ isEmpty() { return this.head === null; diff --git a/examples/jsm/math/ImprovedNoise.js b/examples/jsm/math/ImprovedNoise.js index 5647d1b208d9e4..9115ebcd7cecc0 100644 --- a/examples/jsm/math/ImprovedNoise.js +++ b/examples/jsm/math/ImprovedNoise.js @@ -1,4 +1,6 @@ -// https://cs.nyu.edu/~perlin/noise/ +import { MathUtils } from 'three'; + +const { lerp } = MathUtils; const _p = [ 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, @@ -23,12 +25,6 @@ function fade( t ) { } -function lerp( t, a, b ) { - - return a + t * ( b - a ); - -} - function grad( hash, x, y, z ) { const h = hash & 15; @@ -37,8 +33,24 @@ function grad( hash, x, y, z ) { } +/** + * A utility class providing a 3D noise function. + * + * The code is based on [IMPROVED NOISE]{@link https://cs.nyu.edu/~perlin/noise/} + * by Ken Perlin, 2002. + * + * @three_import import { ImprovedNoise } from 'three/addons/math/ImprovedNoise.js'; + */ class ImprovedNoise { + /** + * Returns a noise value for the given parameters. + * + * @param {number} x - The x coordinate. + * @param {number} y - The y coordinate. + * @param {number} z - The z coordinate. + * @return {number} The noise value. + */ noise( x, y, z ) { const floorX = Math.floor( x ), floorY = Math.floor( y ), floorZ = Math.floor( z ); @@ -55,14 +67,19 @@ class ImprovedNoise { const A = _p[ X ] + Y, AA = _p[ A ] + Z, AB = _p[ A + 1 ] + Z, B = _p[ X + 1 ] + Y, BA = _p[ B ] + Z, BB = _p[ B + 1 ] + Z; - return lerp( w, lerp( v, lerp( u, grad( _p[ AA ], x, y, z ), - grad( _p[ BA ], xMinus1, y, z ) ), - lerp( u, grad( _p[ AB ], x, yMinus1, z ), - grad( _p[ BB ], xMinus1, yMinus1, z ) ) ), - lerp( v, lerp( u, grad( _p[ AA + 1 ], x, y, zMinus1 ), - grad( _p[ BA + 1 ], xMinus1, y, zMinus1 ) ), - lerp( u, grad( _p[ AB + 1 ], x, yMinus1, zMinus1 ), - grad( _p[ BB + 1 ], xMinus1, yMinus1, zMinus1 ) ) ) ); + return lerp( + lerp( + lerp( grad( _p[ AA ], x, y, z ), grad( _p[ BA ], xMinus1, y, z ), u ), + lerp( grad( _p[ AB ], x, yMinus1, z ), grad( _p[ BB ], xMinus1, yMinus1, z ), u ), + v + ), + lerp( + lerp( grad( _p[ AA + 1 ], x, y, zMinus1 ), grad( _p[ BA + 1 ], xMinus1, y, zMinus1 ), u ), + lerp( grad( _p[ AB + 1 ], x, yMinus1, zMinus1 ), grad( _p[ BB + 1 ], xMinus1, yMinus1, zMinus1 ), u ), + v + ), + w + ); } diff --git a/examples/jsm/math/Lut.js b/examples/jsm/math/Lut.js index 29ae876980017d..b0cbb4844f7125 100644 --- a/examples/jsm/math/Lut.js +++ b/examples/jsm/math/Lut.js @@ -1,24 +1,88 @@ import { Color, + LinearSRGBColorSpace, MathUtils } from 'three'; +/** + * Represents a lookup table for colormaps. It is used to determine the color + * values from a range of data values. + * + * ```js + * const lut = new Lut( 'rainbow', 512 ); + * const color = lut.getColor( 0.5 ); + * ``` + * + * @three_import import { Lut } from 'three/addons/math/Lut.js'; + */ class Lut { + /** + * Constructs a new Lut. + * + * @param {('rainbow'|'cooltowarm'|'blackbody'|'grayscale')} [colormap='rainbow'] - Sets a colormap from predefined list of colormaps. + * @param {number} [count=32] - Sets the number of colors used to represent the data array. + */ constructor( colormap, count = 32 ) { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isLut = true; + + /** + * The lookup table for the selected color map + * + * @type {Array} + */ this.lut = []; + + /** + * The currently selected color map. + * + * @type {Array} + */ this.map = []; + + /** + * The number of colors of the current selected color map. + * + * @type {number} + * @default 32 + */ this.n = 0; + + /** + * The minimum value to be represented with the lookup table. + * + * @type {number} + * @default 0 + */ this.minV = 0; + + /** + * The maximum value to be represented with the lookup table. + * + * @type {number} + * @default 1 + */ this.maxV = 1; this.setColorMap( colormap, count ); } + /** + * Sets the given LUT. + * + * @param {Lut} value - The LUT to set. + * @return {Lut} A reference to this LUT. + */ set( value ) { if ( value.isLut === true ) { @@ -31,6 +95,12 @@ class Lut { } + /** + * Sets the minimum value to be represented with this LUT. + * + * @param {number} min - The minimum value to be represented with the lookup table. + * @return {Lut} A reference to this LUT. + */ setMin( min ) { this.minV = min; @@ -39,6 +109,12 @@ class Lut { } + /** + * Sets the maximum value to be represented with this LUT. + * + * @param {number} max - The maximum value to be represented with the lookup table. + * @return {Lut} A reference to this LUT. + */ setMax( max ) { this.maxV = max; @@ -47,6 +123,13 @@ class Lut { } + /** + * Configure the lookup table for the given color map and number of colors. + * + * @param {string} colormap - The name of the color map. + * @param {number} [count=32] - The number of colors. + * @return {Lut} A reference to this LUT. + */ setColorMap( colormap, count = 32 ) { this.map = ColorMapKeywords[ colormap ] || ColorMapKeywords.rainbow; @@ -75,8 +158,8 @@ class Lut { const min = this.map[ j ][ 0 ]; const max = this.map[ j + 1 ][ 0 ]; - minColor.set( this.map[ j ][ 1 ] ); - maxColor.set( this.map[ j + 1 ][ 1 ] ); + minColor.setHex( this.map[ j ][ 1 ], LinearSRGBColorSpace ); + maxColor.setHex( this.map[ j + 1 ][ 1 ], LinearSRGBColorSpace ); const color = new Color().lerpColors( minColor, maxColor, ( alpha - min ) / ( max - min ) ); @@ -96,6 +179,12 @@ class Lut { } + /** + * Copies the given lut. + * + * @param {Lut} lut - The LUT to copy. + * @return {Lut} A reference to this LUT. + */ copy( lut ) { this.lut = lut.lut; @@ -108,6 +197,12 @@ class Lut { } + /** + * Returns an instance of Color for the given data value. + * + * @param {number} alpha - The value to lookup. + * @return {Color} The color from the LUT. + */ getColor( alpha ) { alpha = MathUtils.clamp( alpha, this.minV, this.maxV ); @@ -120,6 +215,14 @@ class Lut { } + /** + * Adds a color map to this Lut instance. + * + * @param {string} name - The name of the color map. + * @param {Array} arrayOfColors - An array of color values. Each value is an array + * holding a threshold and the actual color value as a hexadecimal number. + * @return {Lut} A reference to this LUT. + */ addColorMap( name, arrayOfColors ) { ColorMapKeywords[ name ] = arrayOfColors; @@ -128,6 +231,11 @@ class Lut { } + /** + * Creates a canvas in order to visualize the lookup table as a texture. + * + * @return {HTMLCanvasElement} The created canvas. + */ createCanvas() { const canvas = document.createElement( 'canvas' ); @@ -140,6 +248,12 @@ class Lut { } + /** + * Updates the given canvas with the Lut's data. + * + * @param {HTMLCanvasElement} canvas - The canvas to update. + * @return {HTMLCanvasElement} The updated canvas. + */ updateCanvas( canvas ) { const ctx = canvas.getContext( '2d', { alpha: false } ); @@ -165,8 +279,8 @@ class Lut { const min = this.map[ j - 1 ][ 0 ]; const max = this.map[ j ][ 0 ]; - minColor.set( this.map[ j - 1 ][ 1 ] ); - maxColor.set( this.map[ j ][ 1 ] ); + minColor.setHex( this.map[ j - 1 ][ 1 ], LinearSRGBColorSpace ); + maxColor.setHex( this.map[ j ][ 1 ], LinearSRGBColorSpace ); finalColor.lerpColors( minColor, maxColor, ( i - min ) / ( max - min ) ); diff --git a/examples/jsm/math/MeshSurfaceSampler.js b/examples/jsm/math/MeshSurfaceSampler.js index c9c2c86601d82f..a218f734f24fd7 100644 --- a/examples/jsm/math/MeshSurfaceSampler.js +++ b/examples/jsm/math/MeshSurfaceSampler.js @@ -1,8 +1,13 @@ import { Triangle, + Vector2, Vector3 } from 'three'; +const _face = new Triangle(); +const _color = new Vector3(); +const _uva = new Vector2(), _uvb = new Vector2(), _uvc = new Vector2(); + /** * Utility class for sampling weighted random points on the surface of a mesh. * @@ -10,38 +15,67 @@ import { * random samples may be selected in O(logn) time. Memory usage is O(n). * * References: - * - http://www.joesfer.com/?p=84 - * - https://stackoverflow.com/a/4322940/1314762 + * - {@link http://www.joesfer.com/?p=84} + * - {@link https://stackoverflow.com/a/4322940/1314762} + * + * ```js + * const sampler = new MeshSurfaceSampler( surfaceMesh ) + * .setWeightAttribute( 'color' ) + * .build(); + * + * const mesh = new THREE.InstancedMesh( sampleGeometry, sampleMaterial, 100 ); + * + * const position = new THREE.Vector3(); + * const matrix = new THREE.Matrix4(); + * + * // Sample randomly from the surface, creating an instance of the sample geometry at each sample point. + * + * for ( let i = 0; i < 100; i ++ ) { + * + * sampler.sample( position ); + * matrix.makeTranslation( position.x, position.y, position.z ); + * mesh.setMatrixAt( i, matrix ); + * + * } + * + * scene.add( mesh ); + * ``` + * + * @three_import import { MeshSurfaceSampler } from 'three/addons/math/MeshSurfaceSampler.js'; */ - -const _face = new Triangle(); -const _color = new Vector3(); - class MeshSurfaceSampler { + /** + * Constructs a mesh surface sampler. + * + * @param {Mesh} mesh - Surface mesh from which to sample. + */ constructor( mesh ) { - let geometry = mesh.geometry; - - if ( geometry.index ) { - - console.warn( 'THREE.MeshSurfaceSampler: Converting geometry to non-indexed BufferGeometry.' ); - - geometry = geometry.toNonIndexed(); - - } - - this.geometry = geometry; + this.geometry = mesh.geometry; this.randomFunction = Math.random; + this.indexAttribute = this.geometry.index; this.positionAttribute = this.geometry.getAttribute( 'position' ); + this.normalAttribute = this.geometry.getAttribute( 'normal' ); this.colorAttribute = this.geometry.getAttribute( 'color' ); + this.uvAttribute = this.geometry.getAttribute( 'uv' ); this.weightAttribute = null; this.distribution = null; } + /** + * Specifies a vertex attribute to be used as a weight when sampling from the surface. + * Faces with higher weights are more likely to be sampled, and those with weights of + * zero will not be sampled at all. For vector attributes, only .x is used in sampling. + * + * If no weight attribute is selected, sampling is randomly distributed by area. + * + * @param {string} name - The attribute name. + * @return {MeshSurfaceSampler} A reference to this sampler. + */ setWeightAttribute( name ) { this.weightAttribute = name ? this.geometry.getAttribute( name ) : null; @@ -50,55 +84,81 @@ class MeshSurfaceSampler { } + /** + * Processes the input geometry and prepares to return samples. Any configuration of the + * geometry or sampler must occur before this method is called. Time complexity is O(n) + * for a surface with n faces. + * + * @return {MeshSurfaceSampler} A reference to this sampler. + */ build() { + const indexAttribute = this.indexAttribute; const positionAttribute = this.positionAttribute; const weightAttribute = this.weightAttribute; - const faceWeights = new Float32Array( positionAttribute.count / 3 ); + const totalFaces = indexAttribute ? ( indexAttribute.count / 3 ) : ( positionAttribute.count / 3 ); + const faceWeights = new Float32Array( totalFaces ); // Accumulate weights for each mesh face. - for ( let i = 0; i < positionAttribute.count; i += 3 ) { + for ( let i = 0; i < totalFaces; i ++ ) { let faceWeight = 1; + let i0 = 3 * i; + let i1 = 3 * i + 1; + let i2 = 3 * i + 2; + + if ( indexAttribute ) { + + i0 = indexAttribute.getX( i0 ); + i1 = indexAttribute.getX( i1 ); + i2 = indexAttribute.getX( i2 ); + + } + if ( weightAttribute ) { - faceWeight = weightAttribute.getX( i ) - + weightAttribute.getX( i + 1 ) - + weightAttribute.getX( i + 2 ); + faceWeight = weightAttribute.getX( i0 ) + + weightAttribute.getX( i1 ) + + weightAttribute.getX( i2 ); } - _face.a.fromBufferAttribute( positionAttribute, i ); - _face.b.fromBufferAttribute( positionAttribute, i + 1 ); - _face.c.fromBufferAttribute( positionAttribute, i + 2 ); + _face.a.fromBufferAttribute( positionAttribute, i0 ); + _face.b.fromBufferAttribute( positionAttribute, i1 ); + _face.c.fromBufferAttribute( positionAttribute, i2 ); faceWeight *= _face.getArea(); - faceWeights[ i / 3 ] = faceWeight; + faceWeights[ i ] = faceWeight; } // Store cumulative total face weights in an array, where weight index // corresponds to face index. - this.distribution = new Float32Array( positionAttribute.count / 3 ); - + const distribution = new Float32Array( totalFaces ); let cumulativeTotal = 0; - for ( let i = 0; i < faceWeights.length; i ++ ) { + for ( let i = 0; i < totalFaces; i ++ ) { cumulativeTotal += faceWeights[ i ]; - - this.distribution[ i ] = cumulativeTotal; + distribution[ i ] = cumulativeTotal; } + this.distribution = distribution; return this; } + /** + * Allows to set a custom random number generator. Default is `Math.random()`. + * + * @param {Function} randomFunction - A random number generator. + * @return {MeshSurfaceSampler} A reference to this sampler. + */ setRandomGenerator( randomFunction ) { this.randomFunction = randomFunction; @@ -106,21 +166,34 @@ class MeshSurfaceSampler { } - sample( targetPosition, targetNormal, targetColor ) { - - const faceIndex = this.sampleFaceIndex(); - return this.sampleFace( faceIndex, targetPosition, targetNormal, targetColor ); + /** + * Selects a random point on the surface of the input geometry, returning the + * position and optionally the normal vector, color and UV Coordinate at that point. + * Time complexity is O(log n) for a surface with n faces. + * + * @param {Vector3} targetPosition - The target object holding the sampled position. + * @param {Vector3} targetNormal - The target object holding the sampled normal. + * @param {Color} targetColor - The target object holding the sampled color. + * @param {Vector2} targetUV - The target object holding the sampled uv coordinates. + * @return {MeshSurfaceSampler} A reference to this sampler. + */ + sample( targetPosition, targetNormal, targetColor, targetUV ) { + + const faceIndex = this._sampleFaceIndex(); + return this._sampleFace( faceIndex, targetPosition, targetNormal, targetColor, targetUV ); } - sampleFaceIndex() { + // private + + _sampleFaceIndex() { const cumulativeTotal = this.distribution[ this.distribution.length - 1 ]; - return this.binarySearch( this.randomFunction() * cumulativeTotal ); + return this._binarySearch( this.randomFunction() * cumulativeTotal ); } - binarySearch( x ) { + _binarySearch( x ) { const dist = this.distribution; let start = 0; @@ -154,7 +227,7 @@ class MeshSurfaceSampler { } - sampleFace( faceIndex, targetPosition, targetNormal, targetColor ) { + _sampleFace( faceIndex, targetPosition, targetNormal, targetColor, targetUV ) { let u = this.randomFunction(); let v = this.randomFunction(); @@ -166,9 +239,22 @@ class MeshSurfaceSampler { } - _face.a.fromBufferAttribute( this.positionAttribute, faceIndex * 3 ); - _face.b.fromBufferAttribute( this.positionAttribute, faceIndex * 3 + 1 ); - _face.c.fromBufferAttribute( this.positionAttribute, faceIndex * 3 + 2 ); + // get the vertex attribute indices + const indexAttribute = this.indexAttribute; + let i0 = faceIndex * 3; + let i1 = faceIndex * 3 + 1; + let i2 = faceIndex * 3 + 2; + if ( indexAttribute ) { + + i0 = indexAttribute.getX( i0 ); + i1 = indexAttribute.getX( i1 ); + i2 = indexAttribute.getX( i2 ); + + } + + _face.a.fromBufferAttribute( this.positionAttribute, i0 ); + _face.b.fromBufferAttribute( this.positionAttribute, i1 ); + _face.c.fromBufferAttribute( this.positionAttribute, i2 ); targetPosition .set( 0, 0, 0 ) @@ -178,15 +264,26 @@ class MeshSurfaceSampler { if ( targetNormal !== undefined ) { - _face.getNormal( targetNormal ); + if ( this.normalAttribute !== undefined ) { + + _face.a.fromBufferAttribute( this.normalAttribute, i0 ); + _face.b.fromBufferAttribute( this.normalAttribute, i1 ); + _face.c.fromBufferAttribute( this.normalAttribute, i2 ); + targetNormal.set( 0, 0, 0 ).addScaledVector( _face.a, u ).addScaledVector( _face.b, v ).addScaledVector( _face.c, 1 - ( u + v ) ).normalize(); + + } else { + + _face.getNormal( targetNormal ); + + } } if ( targetColor !== undefined && this.colorAttribute !== undefined ) { - _face.a.fromBufferAttribute( this.colorAttribute, faceIndex * 3 ); - _face.b.fromBufferAttribute( this.colorAttribute, faceIndex * 3 + 1 ); - _face.c.fromBufferAttribute( this.colorAttribute, faceIndex * 3 + 2 ); + _face.a.fromBufferAttribute( this.colorAttribute, i0 ); + _face.b.fromBufferAttribute( this.colorAttribute, i1 ); + _face.c.fromBufferAttribute( this.colorAttribute, i2 ); _color .set( 0, 0, 0 ) @@ -200,6 +297,15 @@ class MeshSurfaceSampler { } + if ( targetUV !== undefined && this.uvAttribute !== undefined ) { + + _uva.fromBufferAttribute( this.uvAttribute, i0 ); + _uvb.fromBufferAttribute( this.uvAttribute, i1 ); + _uvc.fromBufferAttribute( this.uvAttribute, i2 ); + targetUV.set( 0, 0 ).addScaledVector( _uva, u ).addScaledVector( _uvb, v ).addScaledVector( _uvc, 1 - ( u + v ) ); + + } + return this; } diff --git a/examples/jsm/math/OBB.js b/examples/jsm/math/OBB.js index 29ae1286dc39ba..f838fb270d7723 100644 --- a/examples/jsm/math/OBB.js +++ b/examples/jsm/math/OBB.js @@ -37,18 +37,53 @@ const matrix = new Matrix4(); const inverse = new Matrix4(); const localRay = new Ray(); -// OBB - +/** + * Represents an oriented bounding box (OBB) in 3D space. + * + * @three_import import { OBB } from 'three/addons/math/OBB.js'; + */ class OBB { + /** + * Constructs a new OBB. + * + * @param {Vector3} [center] - The center of the OBB. + * @param {Vector3} [halfSize] - Positive halfwidth extents of the OBB along each axis. + * @param {Matrix3} [rotation] - The rotation of the OBB. + */ constructor( center = new Vector3(), halfSize = new Vector3(), rotation = new Matrix3() ) { + /** + * The center of the OBB. + * + * @type {Vector3} + */ this.center = center; + + /** + * Positive halfwidth extents of the OBB along each axis. + * + * @type {Vector3} + */ this.halfSize = halfSize; + + /** + * The rotation of the OBB. + * + * @type {Matrix3} + */ this.rotation = rotation; } + /** + * Sets the OBBs components to the given values. + * + * @param {Vector3} [center] - The center of the OBB. + * @param {Vector3} [halfSize] - Positive halfwidth extents of the OBB along each axis. + * @param {Matrix3} [rotation] - The rotation of the OBB. + * @return {OBB} A reference to this OBB. + */ set( center, halfSize, rotation ) { this.center = center; @@ -59,6 +94,12 @@ class OBB { } + /** + * Copies the values of the given OBB to this instance. + * + * @param {OBB} obb - The OBB to copy. + * @return {OBB} A reference to this OBB. + */ copy( obb ) { this.center.copy( obb.center ); @@ -69,23 +110,40 @@ class OBB { } + /** + * Returns a new OBB with copied values from this instance. + * + * @return {OBB} A clone of this instance. + */ clone() { return new this.constructor().copy( this ); } - getSize( result ) { + /** + * Returns the size of this OBB. + * + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {Vector3} The size. + */ + getSize( target ) { - return result.copy( this.halfSize ).multiplyScalar( 2 ); + return target.copy( this.halfSize ).multiplyScalar( 2 ); } /** - * Reference: Closest Point on OBB to Point in Real-Time Collision Detection - * by Christer Ericson (chapter 5.1.4) - */ - clampPoint( point, result ) { + * Clamps the given point within the bounds of this OBB. + * + * @param {Vector3} point - The point that should be clamped within the bounds of this OBB. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @returns {Vector3} - The clamped point. + */ + clampPoint( point, target ) { + + // Reference: Closest Point on OBB to Point in Real-Time Collision Detection + // by Christer Ericson (chapter 5.1.4) const halfSize = this.halfSize; @@ -94,23 +152,29 @@ class OBB { // start at the center position of the OBB - result.copy( this.center ); + target.copy( this.center ); // project the target onto the OBB axes and walk towards that point const x = MathUtils.clamp( v1.dot( xAxis ), - halfSize.x, halfSize.x ); - result.add( xAxis.multiplyScalar( x ) ); + target.add( xAxis.multiplyScalar( x ) ); const y = MathUtils.clamp( v1.dot( yAxis ), - halfSize.y, halfSize.y ); - result.add( yAxis.multiplyScalar( y ) ); + target.add( yAxis.multiplyScalar( y ) ); const z = MathUtils.clamp( v1.dot( zAxis ), - halfSize.z, halfSize.z ); - result.add( zAxis.multiplyScalar( z ) ); + target.add( zAxis.multiplyScalar( z ) ); - return result; + return target; } + /** + * Returns `true` if the given point lies within this OBB. + * + * @param {Vector3} point - The point to test. + * @returns {boolean} - Whether the given point lies within this OBB or not. + */ containsPoint( point ) { v1.subVectors( point, this.center ); @@ -124,12 +188,24 @@ class OBB { } + /** + * Returns `true` if the given AABB intersects this OBB. + * + * @param {Box3} box3 - The AABB to test. + * @returns {boolean} - Whether the given AABB intersects this OBB or not. + */ intersectsBox3( box3 ) { return this.intersectsOBB( obb.fromBox3( box3 ) ); } + /** + * Returns `true` if the given bounding sphere intersects this OBB. + * + * @param {Sphere} sphere - The bounding sphere to test. + * @returns {boolean} - Whether the given bounding sphere intersects this OBB or not. + */ intersectsSphere( sphere ) { // find the point on the OBB closest to the sphere center @@ -143,12 +219,17 @@ class OBB { } /** - * Reference: OBB-OBB Intersection in Real-Time Collision Detection - * by Christer Ericson (chapter 4.4.1) - * - */ + * Returns `true` if the given OBB intersects this OBB. + * + * @param {OBB} obb - The OBB to test. + * @param {number} [epsilon=Number.EPSILON] - A small value to prevent arithmetic errors. + * @returns {boolean} - Whether the given OBB intersects this OBB or not. + */ intersectsOBB( obb, epsilon = Number.EPSILON ) { + // Reference: OBB-OBB Intersection in Real-Time Collision Detection + // by Christer Ericson (chapter 4.4.1) + // prepare data structures (the code uses the same nomenclature like the reference) a.c = this.center; @@ -283,11 +364,16 @@ class OBB { } /** - * Reference: Testing Box Against Plane in Real-Time Collision Detection - * by Christer Ericson (chapter 5.2.3) - */ + * Returns `true` if the given plane intersects this OBB. + * + * @param {Plane} plane - The plane to test. + * @returns {boolean} Whether the given plane intersects this OBB or not. + */ intersectsPlane( plane ) { + // Reference: Testing Box Against Plane in Real-Time Collision Detection + // by Christer Ericson (chapter 5.2.3) + this.rotation.extractBasis( xAxis, yAxis, zAxis ); // compute the projection interval radius of this OBB onto L(t) = this->center + t * p.normal; @@ -307,10 +393,14 @@ class OBB { } /** - * Performs a ray/OBB intersection test and stores the intersection point - * to the given 3D vector. If no intersection is detected, *null* is returned. - */ - intersectRay( ray, result ) { + * Performs a ray/OBB intersection test and stores the intersection point + * in the given 3D vector. + * + * @param {Ray} ray - The ray to test. + * @param {Vector3} target - The target vector that is used to store the method's result. + * @return {?Vector3} The intersection point. If no intersection is detected, `null` is returned. + */ + intersectRay( ray, target ) { // the idea is to perform the intersection test in the local space // of the OBB. @@ -330,11 +420,11 @@ class OBB { // perform ray <-> AABB intersection test - if ( localRay.intersectBox( aabb, result ) ) { + if ( localRay.intersectBox( aabb, target ) ) { // transform the intersection point back to world space - return result.applyMatrix4( matrix ); + return target.applyMatrix4( matrix ); } else { @@ -345,15 +435,23 @@ class OBB { } /** - * Performs a ray/OBB intersection test. Returns either true or false if - * there is a intersection or not. - */ + * Returns `true` if the given ray intersects this OBB. + * + * @param {Ray} ray - The ray to test. + * @returns {boolean} Whether the given ray intersects this OBB or not. + */ intersectsRay( ray ) { return this.intersectRay( ray, v1 ) !== null; } + /** + * Defines an OBB based on the given AABB. + * + * @param {Box3} box3 - The AABB to setup the OBB from. + * @return {OBB} A reference of this OBB. + */ fromBox3( box3 ) { box3.getCenter( this.center ); @@ -366,6 +464,12 @@ class OBB { } + /** + * Returns `true` if the given OBB is equal to this OBB. + * + * @param {OBB} obb - The OBB to test. + * @returns {boolean} Whether the given OBB is equal to this OBB or not. + */ equals( obb ) { return obb.center.equals( this.center ) && @@ -374,6 +478,14 @@ class OBB { } + /** + * Applies the given transformation matrix to this OBB. This method can be + * used to transform the bounding volume with the world matrix of a 3D object + * in order to keep both entities in sync. + * + * @param {Matrix4} matrix - The matrix to apply. + * @return {OBB} A reference of this OBB. + */ applyMatrix4( matrix ) { const e = matrix.elements; diff --git a/examples/jsm/math/Octree.js b/examples/jsm/math/Octree.js index fda28705cf8488..962fb029399917 100644 --- a/examples/jsm/math/Octree.js +++ b/examples/jsm/math/Octree.js @@ -4,34 +4,165 @@ import { Plane, Sphere, Triangle, - Vector3 + Vector3, + Layers } from 'three'; import { Capsule } from '../math/Capsule.js'; const _v1 = new Vector3(); const _v2 = new Vector3(); +const _point1 = new Vector3(); +const _point2 = new Vector3(); const _plane = new Plane(); const _line1 = new Line3(); const _line2 = new Line3(); const _sphere = new Sphere(); const _capsule = new Capsule(); -class Octree { +const _temp1 = new Vector3(); +const _temp2 = new Vector3(); +const _temp3 = new Vector3(); +const EPS = 1e-10; + +function lineToLineClosestPoints( line1, line2, target1 = null, target2 = null ) { + + const r = _temp1.copy( line1.end ).sub( line1.start ); + const s = _temp2.copy( line2.end ).sub( line2.start ); + const w = _temp3.copy( line2.start ).sub( line1.start ); + + const a = r.dot( s ), + b = r.dot( r ), + c = s.dot( s ), + d = s.dot( w ), + e = r.dot( w ); + + let t1, t2; + const divisor = b * c - a * a; + + if ( Math.abs( divisor ) < EPS ) { + + const d1 = - d / c; + const d2 = ( a - d ) / c; + + if ( Math.abs( d1 - 0.5 ) < Math.abs( d2 - 0.5 ) ) { + + t1 = 0; + t2 = d1; + + } else { + + t1 = 1; + t2 = d2; + + } + + } else { + + t1 = ( d * a + e * c ) / divisor; + t2 = ( t1 * a - d ) / c; + + } + + t2 = Math.max( 0, Math.min( 1, t2 ) ); + t1 = Math.max( 0, Math.min( 1, t1 ) ); + if ( target1 ) { + target1.copy( r ).multiplyScalar( t1 ).add( line1.start ); + + } + + if ( target2 ) { + + target2.copy( s ).multiplyScalar( t2 ).add( line2.start ); + + } + +} + +/** + * An octree is a hierarchical tree data structure used to partition a three-dimensional + * space by recursively subdividing it into eight octants. + * + * This particular implementation can have up to sixteen levels and stores up to eight triangles + * in leaf nodes. + * + * `Octree` can be used in games to compute collision between the game world and colliders from + * the player or other dynamic 3D objects. + * + * + * ```js + * const octree = new Octree().fromGraphNode( scene ); + * const result = octree.capsuleIntersect( playerCollider ); // collision detection + * ``` + * + * @three_import import { Octree } from 'three/addons/math/Octree.js'; + */ +class Octree { + + /** + * Constructs a new Octree. + * + * @param {Box3} [box] - The base box with enclose the entire Octree. + */ constructor( box ) { - this.triangles = []; + /** + * The base box with enclose the entire Octree. + * + * @type {Box3} + */ this.box = box; + + /** + * The bounds of the Octree. Compared to {@link Octree#box}, no + * margin is applied. + * + * @type {Box3} + */ + this.bounds = new Box3(); + + /** + * Can by used for layers configuration for refine testing. + * + * @type {Layers} + */ + this.layers = new Layers(); + + /** + * The number of triangles a leaf can store before it is split. + * + * @type {number} + * @default 8 + */ + this.trianglesPerLeaf = 8; + + /** + * The maximum level of the Octree. It defines the maximum + * hierarchical depth of the data structure. + * + * @type {number} + * @default 16 + */ + this.maxLevel = 16; + + // private + this.subTrees = []; + this.triangles = []; } + /** + * Adds the given triangle to the Octree. The triangle vertices are clamped if they exceed + * the bounds of the Octree. + * + * @param {Triangle} triangle - The triangle to add. + * @return {Octree} A reference to this Octree. + */ addTriangle( triangle ) { - if ( ! this.bounds ) this.bounds = new Box3(); - this.bounds.min.x = Math.min( this.bounds.min.x, triangle.a.x, triangle.b.x, triangle.c.x ); this.bounds.min.y = Math.min( this.bounds.min.y, triangle.a.y, triangle.b.y, triangle.c.y ); this.bounds.min.z = Math.min( this.bounds.min.z, triangle.a.z, triangle.b.z, triangle.c.z ); @@ -45,6 +176,11 @@ class Octree { } + /** + * Prepares {@link Octree#box} for the build. + * + * @return {Octree} A reference to this Octree. + */ calcBox() { this.box = this.bounds.clone(); @@ -58,6 +194,13 @@ class Octree { } + /** + * Splits the Octree. This method is used recursively when + * building the Octree. + * + * @param {number} level - The current level. + * @return {Octree} A reference to this Octree. + */ split( level ) { if ( ! this.box ) return; @@ -105,7 +248,7 @@ class Octree { const len = subTrees[ i ].triangles.length; - if ( len > 8 && level < 16 ) { + if ( len > this.trianglesPerLeaf && level < this.maxLevel ) { subTrees[ i ].split( level + 1 ); @@ -123,6 +266,11 @@ class Octree { } + /** + * Builds the Octree. + * + * @return {Octree} A reference to this Octree. + */ build() { this.calcBox(); @@ -132,6 +280,12 @@ class Octree { } + /** + * Computes the triangles that potentially intersect with the given ray. + * + * @param {Ray} ray - The ray to test. + * @param {Array} triangles - The target array that holds the triangles. + */ getRayTriangles( ray, triangles ) { for ( let i = 0; i < this.subTrees.length; i ++ ) { @@ -155,10 +309,16 @@ class Octree { } - return triangles; - } + /** + * Computes the intersection between the given capsule and triangle. + * + * @param {Capsule} capsule - The capsule to test. + * @param {Triangle} triangle - The triangle to test. + * @return {Object|false} The intersection object. If no intersection + * is detected, the method returns `false`. + */ triangleCapsuleIntersect( capsule, triangle ) { triangle.getPlane( _plane ); @@ -195,11 +355,15 @@ class Octree { const line2 = _line2.set( lines[ i ][ 0 ], lines[ i ][ 1 ] ); - const [ point1, point2 ] = capsule.lineLineMinimumPoints( line1, line2 ); + lineToLineClosestPoints( line1, line2, _point1, _point2 ); - if ( point1.distanceToSquared( point2 ) < r2 ) { + if ( _point1.distanceToSquared( _point2 ) < r2 ) { - return { normal: point1.clone().sub( point2 ).normalize(), point: point2.clone(), depth: capsule.radius - point1.distanceTo( point2 ) }; + return { + normal: _point1.clone().sub( _point2 ).normalize(), + point: _point2.clone(), + depth: capsule.radius - _point1.distanceTo( _point2 ) + }; } @@ -209,6 +373,14 @@ class Octree { } + /** + * Computes the intersection between the given sphere and triangle. + * + * @param {Sphere} sphere - The sphere to test. + * @param {Triangle} triangle - The triangle to test. + * @return {Object|false} The intersection object. If no intersection + * is detected, the method returns `false`. + */ triangleSphereIntersect( sphere, triangle ) { triangle.getPlane( _plane ); @@ -251,6 +423,12 @@ class Octree { } + /** + * Computes the triangles that potentially intersect with the given bounding sphere. + * + * @param {Sphere} sphere - The sphere to test. + * @param {Array} triangles - The target array that holds the triangles. + */ getSphereTriangles( sphere, triangles ) { for ( let i = 0; i < this.subTrees.length; i ++ ) { @@ -277,6 +455,12 @@ class Octree { } + /** + * Computes the triangles that potentially intersect with the given capsule. + * + * @param {Capsule} capsule - The capsule to test. + * @param {Array} triangles - The target array that holds the triangles. + */ getCapsuleTriangles( capsule, triangles ) { for ( let i = 0; i < this.subTrees.length; i ++ ) { @@ -303,6 +487,13 @@ class Octree { } + /** + * Performs a bounding sphere intersection test with this Octree. + * + * @param {Sphere} sphere - The bounding sphere to test. + * @return {Object|boolean} The intersection object. If no intersection + * is detected, the method returns `false`. + */ sphereIntersect( sphere ) { _sphere.copy( sphere ); @@ -337,6 +528,13 @@ class Octree { } + /** + * Performs a capsule intersection test with this Octree. + * + * @param {Capsule} capsule - The capsule to test. + * @return {Object|boolean} The intersection object. If no intersection + * is detected, the method returns `false`. + */ capsuleIntersect( capsule ) { _capsule.copy( capsule ); @@ -371,10 +569,15 @@ class Octree { } + /** + * Performs a ray intersection test with this Octree. + * + * @param {Ray} ray - The ray to test. + * @return {Object|boolean} The nearest intersection object. If no intersection + * is detected, the method returns `false`. + */ rayIntersect( ray ) { - if ( ray.direction.length() === 0 ) return; - const triangles = []; let triangle, position, distance = 1e100; @@ -404,6 +607,12 @@ class Octree { } + /** + * Constructs the Octree from the given 3D object. + * + * @param {Object3D} group - The scene graph node. + * @return {Octree} A reference to this Octree. + */ fromGraphNode( group ) { group.updateWorldMatrix( true, true ); @@ -412,38 +621,42 @@ class Octree { if ( obj.isMesh === true ) { - let geometry, isTemp = false; + if ( this.layers.test( obj.layers ) ) { - if ( obj.geometry.index !== null ) { + let geometry, isTemp = false; - isTemp = true; - geometry = obj.geometry.toNonIndexed(); + if ( obj.geometry.index !== null ) { - } else { + isTemp = true; + geometry = obj.geometry.toNonIndexed(); - geometry = obj.geometry; + } else { - } + geometry = obj.geometry; - const positionAttribute = geometry.getAttribute( 'position' ); + } - for ( let i = 0; i < positionAttribute.count; i += 3 ) { + const positionAttribute = geometry.getAttribute( 'position' ); - const v1 = new Vector3().fromBufferAttribute( positionAttribute, i ); - const v2 = new Vector3().fromBufferAttribute( positionAttribute, i + 1 ); - const v3 = new Vector3().fromBufferAttribute( positionAttribute, i + 2 ); + for ( let i = 0; i < positionAttribute.count; i += 3 ) { - v1.applyMatrix4( obj.matrixWorld ); - v2.applyMatrix4( obj.matrixWorld ); - v3.applyMatrix4( obj.matrixWorld ); + const v1 = new Vector3().fromBufferAttribute( positionAttribute, i ); + const v2 = new Vector3().fromBufferAttribute( positionAttribute, i + 1 ); + const v3 = new Vector3().fromBufferAttribute( positionAttribute, i + 2 ); - this.addTriangle( new Triangle( v1, v2, v3 ) ); + v1.applyMatrix4( obj.matrixWorld ); + v2.applyMatrix4( obj.matrixWorld ); + v3.applyMatrix4( obj.matrixWorld ); - } + this.addTriangle( new Triangle( v1, v2, v3 ) ); + + } - if ( isTemp ) { + if ( isTemp ) { - geometry.dispose(); + geometry.dispose(); + + } } @@ -457,6 +670,23 @@ class Octree { } + /** + * Clears the Octree by making it empty. + * + * @return {Octree} A reference to this Octree. + */ + clear() { + + this.box = null; + this.bounds.makeEmpty(); + + this.subTrees.length = 0; + this.triangles.length = 0; + + return this; + + } + } export { Octree }; diff --git a/examples/jsm/math/SimplexNoise.js b/examples/jsm/math/SimplexNoise.js index 8ba26942ce9051..6712f3410a42a4 100644 --- a/examples/jsm/math/SimplexNoise.js +++ b/examples/jsm/math/SimplexNoise.js @@ -1,17 +1,19 @@ -// Ported from Stefan Gustavson's java implementation -// http://staffwww.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf -// Read Stefan's excellent paper for details on how this code works. -// -// Sean McCullough banksean@gmail.com -// -// Added 4D noise - /** - * You can pass in a random number generator object if you like. - * It is assumed to have a random() method. + * A utility class providing noise functions. + * + * The code is based on [Simplex noise demystified]{@link https://web.archive.org/web/20210210162332/http://staffwww.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf} + * by Stefan Gustavson, 2005. + * + * @three_import import { SimplexNoise } from 'three/addons/math/SimplexNoise.js'; */ class SimplexNoise { + /** + * Constructs a new simplex noise object. + * + * @param {Object} [r=Math] - A math utility class that holds a `random()` method. This makes it + * possible to pass in custom random number generator. + */ constructor( r = Math ) { this.grad3 = [[ 1, 1, 0 ], [ - 1, 1, 0 ], [ 1, - 1, 0 ], [ - 1, - 1, 0 ], @@ -58,24 +60,13 @@ class SimplexNoise { } - dot( g, x, y ) { - - return g[ 0 ] * x + g[ 1 ] * y; - - } - - dot3( g, x, y, z ) { - - return g[ 0 ] * x + g[ 1 ] * y + g[ 2 ] * z; - - } - - dot4( g, x, y, z, w ) { - - return g[ 0 ] * x + g[ 1 ] * y + g[ 2 ] * z + g[ 3 ] * w; - - } - + /** + * A 2D simplex noise method. + * + * @param {number} xin - The x coordinate. + * @param {number} yin - The y coordinate. + * @return {number} The noise value. + */ noise( xin, yin ) { let n0; // Noise contributions from the three corners @@ -129,7 +120,7 @@ class SimplexNoise { else { t0 *= t0; - n0 = t0 * t0 * this.dot( this.grad3[ gi0 ], x0, y0 ); // (x,y) of grad3 used for 2D gradient + n0 = t0 * t0 * this._dot( this.grad3[ gi0 ], x0, y0 ); // (x,y) of grad3 used for 2D gradient } @@ -138,7 +129,7 @@ class SimplexNoise { else { t1 *= t1; - n1 = t1 * t1 * this.dot( this.grad3[ gi1 ], x1, y1 ); + n1 = t1 * t1 * this._dot( this.grad3[ gi1 ], x1, y1 ); } @@ -147,7 +138,7 @@ class SimplexNoise { else { t2 *= t2; - n2 = t2 * t2 * this.dot( this.grad3[ gi2 ], x2, y2 ); + n2 = t2 * t2 * this._dot( this.grad3[ gi2 ], x2, y2 ); } @@ -157,7 +148,14 @@ class SimplexNoise { } - // 3D simplex noise + /** + * A 3D simplex noise method. + * + * @param {number} xin - The x coordinate. + * @param {number} yin - The y coordinate. + * @param {number} zin - The z coordinate. + * @return {number} The noise value. + */ noise3d( xin, yin, zin ) { let n0; // Noise contributions from the four corners @@ -257,7 +255,7 @@ class SimplexNoise { else { t0 *= t0; - n0 = t0 * t0 * this.dot3( this.grad3[ gi0 ], x0, y0, z0 ); + n0 = t0 * t0 * this._dot3( this.grad3[ gi0 ], x0, y0, z0 ); } @@ -266,7 +264,7 @@ class SimplexNoise { else { t1 *= t1; - n1 = t1 * t1 * this.dot3( this.grad3[ gi1 ], x1, y1, z1 ); + n1 = t1 * t1 * this._dot3( this.grad3[ gi1 ], x1, y1, z1 ); } @@ -275,7 +273,7 @@ class SimplexNoise { else { t2 *= t2; - n2 = t2 * t2 * this.dot3( this.grad3[ gi2 ], x2, y2, z2 ); + n2 = t2 * t2 * this._dot3( this.grad3[ gi2 ], x2, y2, z2 ); } @@ -284,7 +282,7 @@ class SimplexNoise { else { t3 *= t3; - n3 = t3 * t3 * this.dot3( this.grad3[ gi3 ], x3, y3, z3 ); + n3 = t3 * t3 * this._dot3( this.grad3[ gi3 ], x3, y3, z3 ); } @@ -294,7 +292,15 @@ class SimplexNoise { } - // 4D simplex noise + /** + * A 4D simplex noise method. + * + * @param {number} x - The x coordinate. + * @param {number} y - The y coordinate. + * @param {number} z - The z coordinate. + * @param {number} w - The w coordinate. + * @return {number} The noise value. + */ noise4d( x, y, z, w ) { // For faster and easier lookups @@ -394,7 +400,7 @@ class SimplexNoise { else { t0 *= t0; - n0 = t0 * t0 * this.dot4( grad4[ gi0 ], x0, y0, z0, w0 ); + n0 = t0 * t0 * this._dot4( grad4[ gi0 ], x0, y0, z0, w0 ); } @@ -403,7 +409,7 @@ class SimplexNoise { else { t1 *= t1; - n1 = t1 * t1 * this.dot4( grad4[ gi1 ], x1, y1, z1, w1 ); + n1 = t1 * t1 * this._dot4( grad4[ gi1 ], x1, y1, z1, w1 ); } @@ -412,7 +418,7 @@ class SimplexNoise { else { t2 *= t2; - n2 = t2 * t2 * this.dot4( grad4[ gi2 ], x2, y2, z2, w2 ); + n2 = t2 * t2 * this._dot4( grad4[ gi2 ], x2, y2, z2, w2 ); } @@ -421,7 +427,7 @@ class SimplexNoise { else { t3 *= t3; - n3 = t3 * t3 * this.dot4( grad4[ gi3 ], x3, y3, z3, w3 ); + n3 = t3 * t3 * this._dot4( grad4[ gi3 ], x3, y3, z3, w3 ); } @@ -430,7 +436,7 @@ class SimplexNoise { else { t4 *= t4; - n4 = t4 * t4 * this.dot4( grad4[ gi4 ], x4, y4, z4, w4 ); + n4 = t4 * t4 * this._dot4( grad4[ gi4 ], x4, y4, z4, w4 ); } @@ -439,6 +445,26 @@ class SimplexNoise { } + // private + + _dot( g, x, y ) { + + return g[ 0 ] * x + g[ 1 ] * y; + + } + + _dot3( g, x, y, z ) { + + return g[ 0 ] * x + g[ 1 ] * y + g[ 2 ] * z; + + } + + _dot4( g, x, y, z, w ) { + + return g[ 0 ] * x + g[ 1 ] * y + g[ 2 ] * z + g[ 3 ] * w; + + } + } export { SimplexNoise }; diff --git a/examples/jsm/misc/ConvexObjectBreaker.js b/examples/jsm/misc/ConvexObjectBreaker.js index 69e2aa3fa7e216..a6c7ffc972d4c1 100644 --- a/examples/jsm/misc/ConvexObjectBreaker.js +++ b/examples/jsm/misc/ConvexObjectBreaker.js @@ -6,39 +6,35 @@ import { } from 'three'; import { ConvexGeometry } from '../geometries/ConvexGeometry.js'; +const _v1 = new Vector3(); + /** - * @fileoverview This class can be used to subdivide a convex Geometry object into pieces. - * - * Usage: + * This class can be used to subdivide a convex Geometry object into pieces. * * Use the function prepareBreakableObject to prepare a Mesh object to be broken. - * - * Then, call the various functions to subdivide the object (subdivideByImpact, cutByPlane) - * + * Then, call the various functions to subdivide the object (subdivideByImpact, cutByPlane). * Sub-objects that are product of subdivision don't need prepareBreakableObject to be called on them. * * Requisites for the object: - * - * - Mesh object must have a buffer geometry and a material - * - * - Vertex normals must be planar (not smoothed) - * - * - The geometry must be convex (this is not checked in the library). You can create convex - * geometries with ConvexGeometry. The BoxGeometry, SphereGeometry and other convex primitives - * can also be used. + * - Mesh object must have a buffer geometry and a material. + * - Vertex normals must be planar (not smoothed). + * - The geometry must be convex (this is not checked in the library). You can create convex + * geometries with {@link ConvexGeometry}. The {@link BoxGeometry}, {@link SphereGeometry} and other + * convex primitives can also be used. * * Note: This lib adds member variables to object's userData member (see prepareBreakableObject function) * Use with caution and read the code when using with other libs. * - * @param {double} minSizeForBreak Min size a debris can have to break. - * @param {double} smallDelta Max distance to consider that a point belongs to a plane. - * -*/ - -const _v1 = new Vector3(); - + * @three_import import { ConvexObjectBreaker } from 'three/addons/misc/ConvexObjectBreaker.js'; + */ class ConvexObjectBreaker { + /** + * Constructs a new convex object breaker. + * + * @param {number} [minSizeForBreak=1.4] - Min size a debris can have to break. + * @param {number} [smallDelta=0.0001] - Max distance to consider that a point belongs to a plane. + */ constructor( minSizeForBreak = 1.4, smallDelta = 0.0001 ) { this.minSizeForBreak = minSizeForBreak; @@ -68,6 +64,15 @@ class ConvexObjectBreaker { } + /** + * Must be called for all 3D objects that should be breakable. + * + * @param {Object3D} object - The 3D object. It must have a convex geometry. + * @param {number} mass - The 3D object's mass in kg. Must be greater than `0`. + * @param {Vector3} velocity - The 3D object's velocity. + * @param {Vector3} angularVelocity - The 3D object's angular velocity. + * @param {boolean} breakable - Whether the 3D object is breakable or not. + */ prepareBreakableObject( object, mass, velocity, angularVelocity, breakable ) { // object is a Object3d (normally a Mesh), must have a buffer geometry, and it must be convex. @@ -82,11 +87,16 @@ class ConvexObjectBreaker { } - /* - * @param {int} maxRadialIterations Iterations for radial cuts. - * @param {int} maxRandomIterations Max random iterations for not-radial cuts + /** + * Subdivides the given 3D object into pieces by an impact (meaning another object hits + * the given 3D object at a certain surface point). * - * Returns the array of pieces + * @param {Object3D} object - The 3D object to subdivide. + * @param {Vector3} pointOfImpact - The point of impact. + * @param {Vector3} normal - The impact normal. + * @param {number} maxRadialIterations - Iterations for radial cuts. + * @param {number} maxRandomIterations - Max random iterations for not-radial cuts. + * @return {Array} The array of pieces. */ subdivideByImpact( object, pointOfImpact, normal, maxRadialIterations, maxRandomIterations ) { @@ -168,6 +178,14 @@ class ConvexObjectBreaker { } + /** + * Subdivides the given 3D object into pieces by a plane. + * + * @param {Object3D} object - The 3D object to subdivide. + * @param {Plane} plane - The plane to cut the 3D object. + * @param {{object1:?Mesh,object2:?Mesh}} output - An object that stores the pieces. + * @return {number} The number of pieces. + */ cutByPlane( object, plane, output ) { // Returns breakable objects in output.object1 and output.object2 members, the resulting 2 pieces of the cut. @@ -449,6 +467,8 @@ class ConvexObjectBreaker { } + // internal helpers + static transformFreeVector( v, m ) { // input: diff --git a/examples/jsm/misc/GPUComputationRenderer.js b/examples/jsm/misc/GPUComputationRenderer.js index a5e407df64cc08..94ec75aca7f8c2 100644 --- a/examples/jsm/misc/GPUComputationRenderer.js +++ b/examples/jsm/misc/GPUComputationRenderer.js @@ -1,24 +1,20 @@ import { - Camera, ClampToEdgeWrapping, DataTexture, FloatType, - LinearSRGBColorSpace, - Mesh, NearestFilter, - NoToneMapping, - PlaneGeometry, RGBAFormat, - Scene, ShaderMaterial, WebGLRenderTarget } from 'three'; +import { FullScreenQuad } from '../postprocessing/Pass.js'; + /** - * GPUComputationRenderer, based on SimulationRenderer by zz85 + * GPUComputationRenderer, based on SimulationRenderer by @zz85. * * The GPUComputationRenderer uses the concept of variables. These variables are RGBA float textures that hold 4 floats - * for each compute element (texel) + * for each compute element (texel). * * Each variable has a fragment shader that defines the computation made to obtain the variable in question. * You can use as many variables you need, and make dependencies so you can use textures of other variables in the shader @@ -33,12 +29,11 @@ import { * a common approach could be to use 'texture' prefixing the variable name; i.e texturePosition, textureVelocity... * * The size of the computation (sizeX * sizeY) is defined as 'resolution' automatically in the shader. For example: + * ``` * #DEFINE resolution vec2( 1024.0, 1024.0 ) - * - * ------------- - * + * ``` * Basic use: - * + * ```js * // Initialization... * * // Create computation renderer @@ -50,8 +45,8 @@ import { * // and fill in here the texture data... * * // Add texture variables - * const velVar = gpuCompute.addVariable( "textureVelocity", fragmentShaderVel, pos0 ); - * const posVar = gpuCompute.addVariable( "texturePosition", fragmentShaderPos, vel0 ); + * const velVar = gpuCompute.addVariable( "textureVelocity", fragmentShaderVel, vel0 ); + * const posVar = gpuCompute.addVariable( "texturePosition", fragmentShaderPos, pos0 ); * * // Add variable dependencies * gpuCompute.setVariableDependencies( velVar, [ velVar, posVar ] ); @@ -66,7 +61,6 @@ import { * console.error( error ); * } * - * * // In each frame... * * // Compute! @@ -77,12 +71,12 @@ import { * * // Do your rendering * renderer.render( myScene, myCamera ); - * - * ------------- + * ``` * * Also, you can use utility functions to create ShaderMaterial and perform computations (rendering between textures) * Note that the shaders can have multiple input textures. * + * ```js * const myFilter1 = gpuCompute.createShaderMaterial( myFilterFragmentShader1, { theTexture: { value: null } } ); * const myFilter2 = gpuCompute.createShaderMaterial( myFilterFragmentShader2, { theTexture: { value: null } } ); * @@ -103,16 +97,19 @@ import { * // And compute each frame, before rendering to screen: * gpuCompute.doRenderTarget( myFilter1, myRenderTarget ); * gpuCompute.doRenderTarget( myFilter2, outputRenderTarget ); + * ``` * - * - * - * @param {int} sizeX Computation problem size is always 2d: sizeX * sizeY elements. - * @param {int} sizeY Computation problem size is always 2d: sizeX * sizeY elements. - * @param {WebGLRenderer} renderer The renderer - */ - + * @three_import import { GPUComputationRenderer } from 'three/addons/misc/GPUComputationRenderer.js'; + */ class GPUComputationRenderer { + /** + * Constructs a new GPU computation renderer. + * + * @param {number} sizeX - Computation problem size is always 2d: sizeX * sizeY elements. + * @param {number} sizeY - Computation problem size is always 2d: sizeX * sizeY elements. + * @param {WebGLRenderer} renderer - The renderer. + */ constructor( sizeX, sizeY, renderer ) { this.variables = []; @@ -121,21 +118,20 @@ class GPUComputationRenderer { let dataType = FloatType; - const scene = new Scene(); - - const camera = new Camera(); - camera.position.z = 1; - const passThruUniforms = { passThruTexture: { value: null } }; const passThruShader = createShaderMaterial( getPassThroughFragmentShader(), passThruUniforms ); - const mesh = new Mesh( new PlaneGeometry( 2, 2 ), passThruShader ); - scene.add( mesh ); - + const quad = new FullScreenQuad( passThruShader ); + /** + * Sets the data type of the internal textures. + * + * @param {(FloatType|HalfFloatType)} type - The type to set. + * @return {GPUComputationRenderer} A reference to this renderer. + */ this.setDataType = function ( type ) { dataType = type; @@ -143,6 +139,14 @@ class GPUComputationRenderer { }; + /** + * Adds a compute variable to the renderer. + * + * @param {string} variableName - The variable name. + * @param {string} computeFragmentShader - The compute (fragment) shader source. + * @param {Texture} initialValueTexture - The initial value texture. + * @return {Object} The compute variable. + */ this.addVariable = function ( variableName, computeFragmentShader, initialValueTexture ) { const material = this.createShaderMaterial( computeFragmentShader ); @@ -165,20 +169,25 @@ class GPUComputationRenderer { }; + /** + * Sets variable dependencies. + * + * @param {Object} variable - The compute variable. + * @param {Array} dependencies - Other compute variables that represents the dependencies. + */ this.setVariableDependencies = function ( variable, dependencies ) { variable.dependencies = dependencies; }; + /** + * Initializes the renderer. + * + * @return {?string} Returns `null` if no errors are detected. Otherwise returns the error message. + */ this.init = function () { - if ( renderer.capabilities.isWebGL2 === false && renderer.extensions.has( 'OES_texture_float' ) === false ) { - - return 'No OES_texture_float support for float textures.'; - - } - if ( renderer.capabilities.maxVertexTextures === 0 ) { return 'No support for vertex shader textures.'; @@ -245,6 +254,9 @@ class GPUComputationRenderer { }; + /** + * Executes the compute. This method is usually called in the animation loop. + */ this.compute = function () { const currentTextureIndex = this.currentTextureIndex; @@ -278,22 +290,37 @@ class GPUComputationRenderer { }; + /** + * Returns the current render target for the given compute variable. + * + * @param {Object} variable - The compute variable. + * @return {WebGLRenderTarget} The current render target. + */ this.getCurrentRenderTarget = function ( variable ) { return variable.renderTargets[ this.currentTextureIndex ]; }; + /** + * Returns the alternate render target for the given compute variable. + * + * @param {Object} variable - The compute variable. + * @return {WebGLRenderTarget} The alternate render target. + */ this.getAlternateRenderTarget = function ( variable ) { return variable.renderTargets[ this.currentTextureIndex === 0 ? 1 : 0 ]; }; + /** + * Frees all internal resources. Call this method if you don't need the + * renderer anymore. + */ this.dispose = function () { - mesh.geometry.dispose(); - mesh.material.dispose(); + quad.dispose(); const variables = this.variables; @@ -322,6 +349,11 @@ class GPUComputationRenderer { } + /** + * Adds a resolution defined for the given material shader. + * + * @param {Object} materialShader - The material shader. + */ this.addResolutionDefine = addResolutionDefine; @@ -332,6 +364,7 @@ class GPUComputationRenderer { uniforms = uniforms || {}; const material = new ShaderMaterial( { + name: 'GPUComputationShader', uniforms: uniforms, vertexShader: getPassThroughVertexShader(), fragmentShader: computeFragmentShader @@ -345,6 +378,17 @@ class GPUComputationRenderer { this.createShaderMaterial = createShaderMaterial; + /** + * Creates a new render target from the given parameters. + * + * @param {number} sizeXTexture - The width of the render target. + * @param {number} sizeYTexture - The height of the render target. + * @param {number} wrapS - The wrapS value. + * @param {number} wrapT - The wrapS value. + * @param {number} minFilter - The minFilter value. + * @param {number} magFilter - The magFilter value. + * @return {WebGLRenderTarget} The new render target. + */ this.createRenderTarget = function ( sizeXTexture, sizeYTexture, wrapS, wrapT, minFilter, magFilter ) { sizeXTexture = sizeXTexture || sizeX; @@ -370,6 +414,11 @@ class GPUComputationRenderer { }; + /** + * Creates a new data texture. + * + * @return {DataTexture} The new data texture. + */ this.createTexture = function () { const data = new Float32Array( sizeX * sizeY * 4 ); @@ -379,12 +428,14 @@ class GPUComputationRenderer { }; + /** + * Renders the given texture into the given render target. + * + * @param {Texture} input - The input. + * @param {WebGLRenderTarget} output - The output. + */ this.renderTexture = function ( input, output ) { - // Takes a texture, and render out in rendertarget - // input = Texture - // output = RenderTarget - passThruUniforms.passThruTexture.value = input; this.doRenderTarget( passThruShader, output ); @@ -393,29 +444,30 @@ class GPUComputationRenderer { }; + + /** + * Renders the given material into the given render target + * with a full-screen pass. + * + * @param {Material} material - The material. + * @param {WebGLRenderTarget} output - The output. + */ this.doRenderTarget = function ( material, output ) { const currentRenderTarget = renderer.getRenderTarget(); const currentXrEnabled = renderer.xr.enabled; const currentShadowAutoUpdate = renderer.shadowMap.autoUpdate; - const currentOutputColorSpace = renderer.outputColorSpace; - const currentToneMapping = renderer.toneMapping; renderer.xr.enabled = false; // Avoid camera modification renderer.shadowMap.autoUpdate = false; // Avoid re-computing shadows - renderer.outputColorSpace = LinearSRGBColorSpace; - renderer.toneMapping = NoToneMapping; - - mesh.material = material; + quad.material = material; renderer.setRenderTarget( output ); - renderer.render( scene, camera ); - mesh.material = passThruShader; + quad.render( renderer ); + quad.material = passThruShader; renderer.xr.enabled = currentXrEnabled; renderer.shadowMap.autoUpdate = currentShadowAutoUpdate; - renderer.outputColorSpace = currentOutputColorSpace; - renderer.toneMapping = currentToneMapping; renderer.setRenderTarget( currentRenderTarget ); diff --git a/examples/jsm/misc/Gyroscope.js b/examples/jsm/misc/Gyroscope.js index 9269c9c819112d..83ca3d32d7febc 100644 --- a/examples/jsm/misc/Gyroscope.js +++ b/examples/jsm/misc/Gyroscope.js @@ -12,8 +12,20 @@ const _translationWorld = new Vector3(); const _quaternionWorld = new Quaternion(); const _scaleWorld = new Vector3(); +/** + * A special type of 3D object that takes a position from the scene graph hierarchy + * but uses its local rotation as world rotation. It works like real-world gyroscope - + * you can move it around using hierarchy while its orientation stays fixed with + * respect to the world. + * + * @augments Object3D + * @three_import import { Gyroscope } from 'three/addons/misc/Gyroscope.js'; + */ class Gyroscope extends Object3D { + /** + * Constructs a new gyroscope. + */ constructor() { super(); diff --git a/examples/jsm/misc/MD2Character.js b/examples/jsm/misc/MD2Character.js index 758fdb47799e04..9f4355ff0e3e62 100644 --- a/examples/jsm/misc/MD2Character.js +++ b/examples/jsm/misc/MD2Character.js @@ -10,33 +10,113 @@ import { } from 'three'; import { MD2Loader } from '../loaders/MD2Loader.js'; +/** + * This class represents a management component for animated MD2 + * character assets. + * + * @three_import import { MD2Character } from 'three/addons/misc/MD2Character.js'; + */ class MD2Character { + /** + * Constructs a new MD2 character. + */ constructor() { + /** + * The mesh scale. + * + * @type {number} + * @default 1 + */ this.scale = 1; + + /** + * The FPS + * + * @type {number} + * @default 6 + */ this.animationFPS = 6; + /** + * The root 3D object + * + * @type {Object3D} + */ this.root = new Object3D(); + /** + * The body mesh. + * + * @type {?Mesh} + * @default null + */ this.meshBody = null; + + /** + * The weapon mesh. + * + * @type {?Mesh} + * @default null + */ this.meshWeapon = null; + /** + * The body skins. + * + * @type {Array} + */ this.skinsBody = []; + + /** + * The weapon skins. + * + * @type {Array} + */ this.skinsWeapon = []; + /** + * The weapon meshes. + * + * @type {Array} + */ this.weapons = []; - this.activeAnimation = null; - + /** + * The name of the active animation clip. + * + * @type {?string} + * @default null + */ + this.activeAnimationClipName = null; + + /** + * The animation mixer. + * + * @type {?AnimationMixer} + * @default null + */ this.mixer = null; + /** + * The `onLoad` callback function. + * + * @type {Function} + */ this.onLoadComplete = function () {}; + // internal + this.loadCounter = 0; } + /** + * Loads the character model for the given config. + * + * @param {Object} config - The config which defines the model and textures paths. + */ loadParts( config ) { const scope = this; @@ -156,6 +236,11 @@ class MD2Character { } + /** + * Sets the animation playback rate. + * + * @param {number} rate - The playback rate to set. + */ setPlaybackRate( rate ) { if ( rate !== 0 ) { @@ -170,6 +255,11 @@ class MD2Character { } + /** + * Sets the wireframe material flag. + * + * @param {boolean} wireframeEnabled - Whether to enable wireframe rendering or not. + */ setWireframe( wireframeEnabled ) { if ( wireframeEnabled ) { @@ -186,6 +276,12 @@ class MD2Character { } + /** + * Sets the skin defined by the given skin index. This will result in a different texture + * for the body mesh. + * + * @param {number} index - The skin index. + */ setSkin( index ) { if ( this.meshBody && this.meshBody.material.wireframe === false ) { @@ -196,6 +292,12 @@ class MD2Character { } + /** + * Sets the weapon defined by the given weapon index. This will result in a different weapon + * hold by the character. + * + * @param {number} index - The weapon index. + */ setWeapon( index ) { for ( let i = 0; i < this.weapons.length; i ++ ) this.weapons[ i ].visible = false; @@ -213,6 +315,11 @@ class MD2Character { } + /** + * Sets the defined animation clip as the active animation. + * + * @param {string} clipName - The name of the animation clip. + */ setAnimation( clipName ) { if ( this.meshBody ) { @@ -240,6 +347,9 @@ class MD2Character { } + /** + * Synchronizes the weapon with the body animation. + */ syncWeaponAnimation() { const clipName = this.activeClipName; @@ -265,6 +375,11 @@ class MD2Character { } + /** + * Updates the animations of the mesh. Must be called inside the animation loop. + * + * @param {number} delta - The delta time in seconds. + */ update( delta ) { if ( this.mixer ) this.mixer.update( delta ); diff --git a/examples/jsm/misc/MD2CharacterComplex.js b/examples/jsm/misc/MD2CharacterComplex.js index cccc5022a289e5..ef6372b4c004b3 100644 --- a/examples/jsm/misc/MD2CharacterComplex.js +++ b/examples/jsm/misc/MD2CharacterComplex.js @@ -10,45 +10,149 @@ import { import { MD2Loader } from '../loaders/MD2Loader.js'; import { MorphBlendMesh } from '../misc/MorphBlendMesh.js'; +/** + * This class represents a management component for animated MD2 + * character assets. It provides a larger API compared to {@link MD2Character}. + * + * @three_import import { MD2CharacterComplex } from 'three/addons/misc/MD2CharacterComplex.js'; + */ class MD2CharacterComplex { + /** + * Constructs a new MD2 character. + */ constructor() { + /** + * The mesh scale. + * + * @type {number} + * @default 1 + */ this.scale = 1; - // animation parameters - + /** + * The FPS + * + * @type {number} + * @default 6 + */ this.animationFPS = 6; - this.transitionFrames = 15; - // movement model parameters + /** + * The transition frames. + * + * @type {number} + * @default 15 + */ + this.transitionFrames = 15; + /** + * The character's maximum speed. + * + * @type {number} + * @default 275 + */ this.maxSpeed = 275; + + /** + * The character's maximum reverse speed. + * + * @type {number} + * @default - 275 + */ this.maxReverseSpeed = - 275; + /** + * The character's front acceleration. + * + * @type {number} + * @default 600 + */ this.frontAcceleration = 600; - this.backAcceleration = 600; - this.frontDecceleration = 600; + /** + * The character's back acceleration. + * + * @type {number} + * @default 600 + */ + this.backAcceleration = 600; + /** + * The character's front deceleration. + * + * @type {number} + * @default 600 + */ + this.frontDeceleration = 600; + + /** + * The character's angular speed. + * + * @type {number} + * @default 2.5 + */ this.angularSpeed = 2.5; - // rig - + /** + * The root 3D object + * + * @type {Object3D} + */ this.root = new Object3D(); + /** + * The body mesh. + * + * @type {?Mesh} + * @default null + */ this.meshBody = null; + + /** + * The weapon mesh. + * + * @type {?Mesh} + * @default null + */ this.meshWeapon = null; + /** + * The movement controls. + * + * @type {Object} + * @default null + */ this.controls = null; - // skins - + /** + * The body skins. + * + * @type {Array} + */ this.skinsBody = []; + + /** + * The weapon skins. + * + * @type {Array} + */ this.skinsWeapon = []; + /** + * The weapon meshes. + * + * @type {Array} + */ this.weapons = []; + /** + * The current skin. + * + * @type {Texture} + * @default undefined + */ this.currentSkin = undefined; // @@ -79,6 +183,11 @@ class MD2CharacterComplex { } + /** + * Toggles shadow casting and receiving on the character's meshes. + * + * @param {boolean} enable - Whether to enable shadows or not. + */ enableShadows( enable ) { for ( let i = 0; i < this.meshes.length; i ++ ) { @@ -90,6 +199,11 @@ class MD2CharacterComplex { } + /** + * Toggles visibility on the character's meshes. + * + * @param {boolean} enable - Whether the character is visible or not. + */ setVisible( enable ) { for ( let i = 0; i < this.meshes.length; i ++ ) { @@ -101,6 +215,11 @@ class MD2CharacterComplex { } + /** + * Shares certain resources from a different character model. + * + * @param {MD2CharacterComplex} original - The original MD2 character. + */ shareParts( original ) { this.animations = original.animations; @@ -143,6 +262,11 @@ class MD2CharacterComplex { } + /** + * Loads the character model for the given config. + * + * @param {Object} config - The config which defines the model and textures paths. + */ loadParts( config ) { const scope = this; @@ -241,6 +365,11 @@ class MD2CharacterComplex { } + /** + * Sets the animation playback rate. + * + * @param {number} rate - The playback rate to set. + */ setPlaybackRate( rate ) { if ( this.meshBody ) this.meshBody.duration = this.meshBody.baseDuration / rate; @@ -248,6 +377,11 @@ class MD2CharacterComplex { } + /** + * Sets the wireframe material flag. + * + * @param {boolean} wireframeEnabled - Whether to enable wireframe rendering or not. + */ setWireframe( wireframeEnabled ) { if ( wireframeEnabled ) { @@ -264,6 +398,12 @@ class MD2CharacterComplex { } + /** + * Sets the skin defined by the given skin index. This will result in a different texture + * for the body mesh. + * + * @param {number} index - The skin index. + */ setSkin( index ) { if ( this.meshBody && this.meshBody.material.wireframe === false ) { @@ -275,6 +415,12 @@ class MD2CharacterComplex { } + /** + * Sets the weapon defined by the given weapon index. This will result in a different weapon + * hold by the character. + * + * @param {number} index - The weapon index. + */ setWeapon( index ) { for ( let i = 0; i < this.weapons.length; i ++ ) this.weapons[ i ].visible = false; @@ -297,6 +443,11 @@ class MD2CharacterComplex { } + /** + * Sets the defined animation clip as the active animation. + * + * @param {string} animationName - The name of the animation clip. + */ setAnimation( animationName ) { if ( animationName === this.activeAnimation || ! animationName ) return; @@ -336,6 +487,11 @@ class MD2CharacterComplex { } + /** + * Updates the animations of the mesh. Must be called inside the animation loop. + * + * @param {number} delta - The delta time in seconds. + */ updateAnimations( delta ) { let mix = 1; @@ -367,6 +523,9 @@ class MD2CharacterComplex { } + /** + * Updates the animation state based on the control inputs. + */ updateBehaviors() { const controls = this.controls; @@ -476,6 +635,11 @@ class MD2CharacterComplex { } + /** + * Transforms the character model based on the control input. + * + * @param {number} delta - The delta time in seconds. + */ updateMovementModel( delta ) { function exponentialEaseOut( k ) { @@ -522,7 +686,7 @@ class MD2CharacterComplex { if ( this.speed > 0 ) { const k = exponentialEaseOut( this.speed / this.maxSpeed ); - this.speed = MathUtils.clamp( this.speed - k * delta * this.frontDecceleration, 0, this.maxSpeed ); + this.speed = MathUtils.clamp( this.speed - k * delta * this.frontDeceleration, 0, this.maxSpeed ); } else { diff --git a/examples/jsm/misc/MorphAnimMesh.js b/examples/jsm/misc/MorphAnimMesh.js index 9dcfde484f58b7..d67fd915a35183 100644 --- a/examples/jsm/misc/MorphAnimMesh.js +++ b/examples/jsm/misc/MorphAnimMesh.js @@ -4,31 +4,70 @@ import { Mesh } from 'three'; +/** + * A special type of an animated mesh with a simple interface + * for animation playback. It allows to playback just one animation + * without any transitions or fading between animation changes. + * + * @augments Mesh + * @three_import import { MorphAnimMesh } from 'three/addons/misc/MorphAnimMesh.js'; + */ class MorphAnimMesh extends Mesh { + /** + * Constructs a new morph anim mesh. + * + * @param {BufferGeometry} [geometry] - The mesh geometry. + * @param {Material|Array} [material] - The mesh material. + */ constructor( geometry, material ) { super( geometry, material ); this.type = 'MorphAnimMesh'; + /** + * The internal animation mixer. + * + * @type {AnimationMixer} + */ this.mixer = new AnimationMixer( this ); + + /** + * The current active animation action. + * + * @type {?AnimationAction} + * @default null + */ this.activeAction = null; } + /** + * Sets the animation playback direction to "forward". + */ setDirectionForward() { this.mixer.timeScale = 1.0; } + /** + * Sets the animation playback direction to "backward". + */ setDirectionBackward() { this.mixer.timeScale = - 1.0; } + /** + * Plays the defined animation clip. The implementation assumes the animation + * clips are stored in {@link Object3D#animations} or the geometry. + * + * @param {string} label - The name of the animation clip. + * @param {number} fps - The FPS of the animation clip. + */ playAnimation( label, fps ) { if ( this.activeAction ) { @@ -54,6 +93,11 @@ class MorphAnimMesh extends Mesh { } + /** + * Updates the animations of the mesh. Must be called inside the animation loop. + * + * @param {number} delta - The delta time in seconds. + */ updateAnimation( delta ) { this.mixer.update( delta ); diff --git a/examples/jsm/misc/MorphBlendMesh.js b/examples/jsm/misc/MorphBlendMesh.js index 6be2693fc1d595..7a28f092548fb0 100644 --- a/examples/jsm/misc/MorphBlendMesh.js +++ b/examples/jsm/misc/MorphBlendMesh.js @@ -3,13 +3,39 @@ import { Mesh } from 'three'; +/** + * A special type of an animated mesh with a more advanced interface + * for animation playback. Unlike {@link MorphAnimMesh}. It allows to + * playback more than one morph animation at the same time but without + * fading options. + * + * @augments Mesh + * @three_import import { MorphBlendMesh } from 'three/addons/misc/MorphBlendMesh.js'; + */ class MorphBlendMesh extends Mesh { + /** + * Constructs a new morph blend mesh. + * + * @param {BufferGeometry} [geometry] - The mesh geometry. + * @param {Material|Array} [material] - The mesh material. + */ constructor( geometry, material ) { super( geometry, material ); + /** + * A dictionary of animations. + * + * @type {Object} + */ this.animationsMap = {}; + + /** + * A list of animations. + * + * @type {Array} + */ this.animationsList = []; // prepare default animation @@ -29,6 +55,14 @@ class MorphBlendMesh extends Mesh { } + /** + * Creates a new animation. + * + * @param {string} name - The animation name. + * @param {number} start - The start time. + * @param {number} end - The end time. + * @param {number} fps - The FPS. + */ createAnimation( name, start, end, fps ) { const animation = { @@ -60,6 +94,12 @@ class MorphBlendMesh extends Mesh { } + /** + * Automatically creates animations based on the values in + * {@link Mesh#morphTargetDictionary}. + * + * @param {number} fps - The FPS of all animations. + */ autoCreateAnimations( fps ) { const pattern = /([a-z]+)_?(\d+)/i; @@ -104,6 +144,12 @@ class MorphBlendMesh extends Mesh { } + /** + * Sets the animation playback direction to "forward" for the + * defined animation. + * + * @param {string} name - The animation name. + */ setAnimationDirectionForward( name ) { const animation = this.animationsMap[ name ]; @@ -117,6 +163,12 @@ class MorphBlendMesh extends Mesh { } + /** + * Sets the animation playback direction to "backward" for the + * defined animation. + * + * @param {string} name - The animation name. + */ setAnimationDirectionBackward( name ) { const animation = this.animationsMap[ name ]; @@ -130,6 +182,12 @@ class MorphBlendMesh extends Mesh { } + /** + * Sets the FPS to the given value for the defined animation. + * + * @param {string} name - The animation name. + * @param {number} fps - The FPS to set. + */ setAnimationFPS( name, fps ) { const animation = this.animationsMap[ name ]; @@ -143,6 +201,12 @@ class MorphBlendMesh extends Mesh { } + /** + * Sets the duration to the given value for the defined animation. + * + * @param {string} name - The animation name. + * @param {number} duration - The duration to set. + */ setAnimationDuration( name, duration ) { const animation = this.animationsMap[ name ]; @@ -156,6 +220,12 @@ class MorphBlendMesh extends Mesh { } + /** + * Sets the weight to the given value for the defined animation. + * + * @param {string} name - The animation name. + * @param {number} weight - The weight to set. + */ setAnimationWeight( name, weight ) { const animation = this.animationsMap[ name ]; @@ -168,6 +238,12 @@ class MorphBlendMesh extends Mesh { } + /** + * Sets the time to the given value for the defined animation. + * + * @param {string} name - The animation name. + * @param {number} time - The time to set. + */ setAnimationTime( name, time ) { const animation = this.animationsMap[ name ]; @@ -180,6 +256,12 @@ class MorphBlendMesh extends Mesh { } + /** + * Returns the time for the defined animation. + * + * @param {string} name - The animation name. + * @return {number} The time. + */ getAnimationTime( name ) { let time = 0; @@ -196,6 +278,12 @@ class MorphBlendMesh extends Mesh { } + /** + * Returns the duration for the defined animation. + * + * @param {string} name - The animation name. + * @return {number} The duration. + */ getAnimationDuration( name ) { let duration = - 1; @@ -212,6 +300,11 @@ class MorphBlendMesh extends Mesh { } + /** + * Plays the defined animation. + * + * @param {string} name - The animation name. + */ playAnimation( name ) { const animation = this.animationsMap[ name ]; @@ -229,6 +322,11 @@ class MorphBlendMesh extends Mesh { } + /** + * Stops the defined animation. + * + * @param {string} name - The animation name. + */ stopAnimation( name ) { const animation = this.animationsMap[ name ]; @@ -241,6 +339,11 @@ class MorphBlendMesh extends Mesh { } + /** + * Updates the animations of the mesh. + * + * @param {number} delta - The delta time in seconds. + */ update( delta ) { for ( let i = 0, il = this.animationsList.length; i < il; i ++ ) { diff --git a/examples/jsm/misc/ProgressiveLightMap.js b/examples/jsm/misc/ProgressiveLightMap.js index fb792132918e47..b5ef7bf2e9c114 100644 --- a/examples/jsm/misc/ProgressiveLightMap.js +++ b/examples/jsm/misc/ProgressiveLightMap.js @@ -1,8 +1,8 @@ -import * as THREE from 'three'; +import { DoubleSide, FloatType, HalfFloatType, Mesh, MeshBasicMaterial, MeshPhongMaterial, PlaneGeometry, Scene, WebGLRenderTarget } from 'three'; import { potpack } from '../libs/potpack.module.js'; /** - * Progressive Light Map Accumulator, by [zalo](https://github.com/zalo/) + * Progressive Light Map Accumulator, by [zalo]{@link https://github.com/zalo/}. * * To use, simply construct a `ProgressiveLightMap` object, * `plmap.addObjectsToLightMap(object)` an array of semi-static @@ -14,37 +14,59 @@ import { potpack } from '../libs/potpack.module.js'; * your objects, so you can start jittering lighting to achieve * the texture-space effect you're looking for. * - * @param {WebGLRenderer} renderer A WebGL Rendering Context - * @param {number} res The side-long dimension of you total lightmap + * This class can only be used with {@link WebGLRenderer}. + * When using {@link WebGPURenderer}, import from `ProgressiveLightMapGPU.js`. + * + * @three_import import { ProgressiveLightMap } from 'three/addons/misc/ProgressiveLightMap.js'; */ class ProgressiveLightMap { + /** + * Constructs a new progressive light map. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {number} [res=1024] - The side-long dimension of the total lightmap. + */ constructor( renderer, res = 1024 ) { + /** + * The renderer. + * + * @type {WebGLRenderer} + */ this.renderer = renderer; + + /** + * The side-long dimension of the total lightmap. + * + * @type {number} + * @default 1024 + */ this.res = res; + + // internals + this.lightMapContainers = []; - this.compiled = false; - this.scene = new THREE.Scene(); - this.scene.background = null; - this.tinyTarget = new THREE.WebGLRenderTarget( 1, 1 ); + this.scene = new Scene(); this.buffer1Active = false; this.firstUpdate = true; - this.warned = false; + this.labelMesh = null; + this.blurringPlane = null; // Create the Progressive LightMap Texture - const format = /(Android|iPad|iPhone|iPod)/g.test( navigator.userAgent ) ? THREE.HalfFloatType : THREE.FloatType; - this.progressiveLightMap1 = new THREE.WebGLRenderTarget( this.res, this.res, { type: format } ); - this.progressiveLightMap2 = new THREE.WebGLRenderTarget( this.res, this.res, { type: format } ); + const format = /(Android|iPad|iPhone|iPod)/g.test( navigator.userAgent ) ? HalfFloatType : FloatType; + this.progressiveLightMap1 = new WebGLRenderTarget( this.res, this.res, { type: format } ); + this.progressiveLightMap2 = new WebGLRenderTarget( this.res, this.res, { type: format } ); this.progressiveLightMap2.texture.channel = 1; // Inject some spicy new logic into a standard phong material - this.uvMat = new THREE.MeshPhongMaterial(); + this.uvMat = new MeshPhongMaterial(); this.uvMat.uniforms = {}; this.uvMat.onBeforeCompile = ( shader ) => { // Vertex Shader: Set Vertex Positions to the Unwrapped UV Positions shader.vertexShader = + 'attribute vec2 uv1;\n' + '#define USE_LIGHTMAP\n' + '#define LIGHTMAP_UV uv1\n' + shader.vertexShader.slice( 0, - 1 ) + @@ -70,15 +92,14 @@ class ProgressiveLightMap { // Set the new Shader to this this.uvMat.userData.shader = shader; - this.compiled = true; - }; } /** * Sets these objects' materials' lightmaps and modifies their uv1's. - * @param {Object3D} objects An array of objects and lights to set up your lightmap. + * + * @param {Array} objects - An array of objects and lights to set up your lightmap. */ addObjectsToLightMap( objects ) { @@ -96,13 +117,13 @@ class ProgressiveLightMap { } - if ( ! object.geometry.hasAttribute( 'uv' ) ) { + if ( object.geometry.hasAttribute( 'uv' ) === false ) { - console.warn( 'All lightmap objects need UVs!' ); continue; + console.warn( 'THREE.ProgressiveLightMap: All lightmap objects need uvs.' ); continue; } - if ( this.blurringPlane == null ) { + if ( this.blurringPlane === null ) { this._initializeBlurPlane( this.res, this.progressiveLightMap1 ); @@ -122,8 +143,6 @@ class ProgressiveLightMap { this.lightMapContainers.push( { basicMat: object.material, object: object } ); - this.compiled = false; - } // Pack the objects' lightmap UVs into the same global space @@ -146,14 +165,15 @@ class ProgressiveLightMap { } /** - * This function renders each mesh one at a time into their respective surface maps - * @param {Camera} camera Standard Rendering Camera - * @param {number} blendWindow When >1, samples will accumulate over time. - * @param {boolean} blurEdges Whether to fix UV Edges via blurring + * This function renders each mesh one at a time into their respective surface maps. + * + * @param {Camera} camera - The camera the scene is rendered with. + * @param {number} [blendWindow=100] - When >1, samples will accumulate over time. + * @param {boolean} [blurEdges=true] - Whether to fix UV Edges via blurring. */ update( camera, blendWindow = 100, blurEdges = true ) { - if ( this.blurringPlane == null ) { + if ( this.blurringPlane === null ) { return; @@ -174,11 +194,10 @@ class ProgressiveLightMap { } - // Render once normally to initialize everything - if ( this.firstUpdate ) { + // Initialize everything + if ( this.firstUpdate === true ) { - this.renderer.setRenderTarget( this.tinyTarget ); // Tiny for Speed - this.renderer.render( this.scene, camera ); + this.renderer.compile( this.scene, camera ); this.firstUpdate = false; } @@ -220,37 +239,33 @@ class ProgressiveLightMap { } - /** DEBUG - * Draw the lightmap in the main scene. Call this after adding the objects to it. - * @param {boolean} visible Whether the debug plane should be visible - * @param {Vector3} position Where the debug plane should be drawn + /** + * Draws the lightmap in the main scene. Call this after adding the objects to it. + * + * @param {boolean} visible - Whether the debug plane should be visible + * @param {Vector3} [position] - Where the debug plane should be drawn */ showDebugLightmap( visible, position = undefined ) { - if ( this.lightMapContainers.length == 0 ) { + if ( this.lightMapContainers.length === 0 ) { - if ( ! this.warned ) { - - console.warn( 'Call this after adding the objects!' ); this.warned = true; - - } + console.warn( 'THREE.ProgressiveLightMap: Call .showDebugLightmap() after adding the objects.' ); return; } - if ( this.labelMesh == null ) { + if ( this.labelMesh === null ) { - this.labelMaterial = new THREE.MeshBasicMaterial( - { map: this.progressiveLightMap1.texture, side: THREE.DoubleSide } ); - this.labelPlane = new THREE.PlaneGeometry( 100, 100 ); - this.labelMesh = new THREE.Mesh( this.labelPlane, this.labelMaterial ); + const labelMaterial = new MeshBasicMaterial( { map: this.progressiveLightMap1.texture, side: DoubleSide } ); + const labelGeometry = new PlaneGeometry( 100, 100 ); + this.labelMesh = new Mesh( labelGeometry, labelMaterial ); this.labelMesh.position.y = 250; this.lightMapContainers[ 0 ].object.parent.add( this.labelMesh ); } - if ( position != undefined ) { + if ( position !== undefined ) { this.labelMesh.position.copy( position ); @@ -261,13 +276,15 @@ class ProgressiveLightMap { } /** - * INTERNAL Creates the Blurring Plane - * @param {number} res The square resolution of this object's lightMap. - * @param {WebGLRenderTexture} lightMap The lightmap to initialize the plane with. + * Creates the Blurring Plane. + * + * @private + * @param {number} res - The square resolution of this object's lightMap. + * @param {WebGLRenderTarget} [lightMap] - The lightmap to initialize the plane with. */ _initializeBlurPlane( res, lightMap = null ) { - const blurMaterial = new THREE.MeshBasicMaterial(); + const blurMaterial = new MeshBasicMaterial(); blurMaterial.uniforms = { previousShadowMap: { value: null }, pixelOffset: { value: 1.0 / res }, polygonOffset: true, polygonOffsetFactor: - 1, polygonOffsetUnits: 3.0 }; @@ -305,11 +322,9 @@ class ProgressiveLightMap { // Set the new Shader to this blurMaterial.userData.shader = shader; - this.compiled = true; - }; - this.blurringPlane = new THREE.Mesh( new THREE.PlaneGeometry( 1, 1 ), blurMaterial ); + this.blurringPlane = new Mesh( new PlaneGeometry( 1, 1 ), blurMaterial ); this.blurringPlane.name = 'Blurring Plane'; this.blurringPlane.frustumCulled = false; this.blurringPlane.renderOrder = 0; @@ -318,6 +333,32 @@ class ProgressiveLightMap { } + /** + * Frees all internal resources. + */ + dispose() { + + this.progressiveLightMap1.dispose(); + this.progressiveLightMap2.dispose(); + + this.uvMat.dispose(); + + if ( this.blurringPlane !== null ) { + + this.blurringPlane.geometry.dispose(); + this.blurringPlane.material.dispose(); + + } + + if ( this.labelMesh !== null ) { + + this.labelMesh.geometry.dispose(); + this.labelMesh.material.dispose(); + + } + + } + } export { ProgressiveLightMap }; diff --git a/examples/jsm/misc/ProgressiveLightMapGPU.js b/examples/jsm/misc/ProgressiveLightMapGPU.js new file mode 100644 index 00000000000000..50b8a456dfb02d --- /dev/null +++ b/examples/jsm/misc/ProgressiveLightMapGPU.js @@ -0,0 +1,316 @@ +import { DoubleSide, FloatType, HalfFloatType, PlaneGeometry, Mesh, RenderTarget, Scene, MeshPhongNodeMaterial, NodeMaterial } from 'three/webgpu'; +import { add, float, mix, output, sub, texture, uniform, uv, vec2, vec4 } from 'three/tsl'; + +import { potpack } from '../libs/potpack.module.js'; + +/** + * Progressive Light Map Accumulator, by [zalo]{@link https://github.com/zalo/}. + * + * To use, simply construct a `ProgressiveLightMap` object, + * `plmap.addObjectsToLightMap(object)` an array of semi-static + * objects and lights to the class once, and then call + * `plmap.update(camera)` every frame to begin accumulating + * lighting samples. + * + * This should begin accumulating lightmaps which apply to + * your objects, so you can start jittering lighting to achieve + * the texture-space effect you're looking for. + * + * This class can only be used with {@link WebGPURenderer}. + * When using {@link WebGLRenderer}, import from `ProgressiveLightMap.js`. + * + * @three_import import { ProgressiveLightMap } from 'three/addons/misc/ProgressiveLightMapGPU.js'; + */ +class ProgressiveLightMap { + + /** + * @param {WebGPURenderer} renderer - The renderer. + * @param {number} [resolution=1024] - The side-long dimension of the total lightmap. + */ + constructor( renderer, resolution = 1024 ) { + + /** + * The renderer. + * + * @type {WebGPURenderer} + */ + this.renderer = renderer; + + /** + * The side-long dimension of the total lightmap. + * + * @type {number} + * @default 1024 + */ + this.resolution = resolution; + + this._lightMapContainers = []; + this._scene = new Scene(); + this._buffer1Active = false; + this._labelMesh = null; + this._blurringPlane = null; + + // Create the Progressive LightMap Texture + + const type = /(Android|iPad|iPhone|iPod)/g.test( navigator.userAgent ) ? HalfFloatType : FloatType; + this._progressiveLightMap1 = new RenderTarget( this.resolution, this.resolution, { type: type } ); + this._progressiveLightMap2 = new RenderTarget( this.resolution, this.resolution, { type: type } ); + this._progressiveLightMap2.texture.channel = 1; + + // uniforms + + this._averagingWindow = uniform( 100 ); + this._previousShadowMap = texture( this._progressiveLightMap1.texture ); + + // materials + + const uvNode = uv( 1 ).flipY(); + + this._uvMat = new MeshPhongNodeMaterial(); + this._uvMat.vertexNode = vec4( sub( uvNode, vec2( 0.5 ) ).mul( 2 ), 1, 1 ); + this._uvMat.outputNode = vec4( mix( this._previousShadowMap.sample( uv( 1 ) ), output, float( 1 ).div( this._averagingWindow ) ) ); + + } + + /** + * Sets these objects' materials' lightmaps and modifies their uv1's. + * + * @param {Array} objects - An array of objects and lights to set up your lightmap. + */ + addObjectsToLightMap( objects ) { + + // Prepare list of UV bounding boxes for packing later... + const uv_boxes = []; + + const padding = 3 / this.resolution; + + for ( let ob = 0; ob < objects.length; ob ++ ) { + + const object = objects[ ob ]; + + // If this object is a light, simply add it to the internal scene + if ( object.isLight ) { + + this._scene.attach( object ); continue; + + } + + if ( object.geometry.hasAttribute( 'uv' ) === false ) { + + console.warn( 'THREE.ProgressiveLightMap: All lightmap objects need uvs.' ); continue; + + } + + if ( this._blurringPlane === null ) { + + this._initializeBlurPlane(); + + } + + // Apply the lightmap to the object + object.material.lightMap = this._progressiveLightMap2.texture; + object.material.dithering = true; + object.castShadow = true; + object.receiveShadow = true; + object.renderOrder = 1000 + ob; + + // Prepare UV boxes for potpack (potpack will update x and y) + // TODO: Size these by object surface area + uv_boxes.push( { w: 1 + ( padding * 2 ), h: 1 + ( padding * 2 ), index: ob, x: 0, y: 0 } ); + + this._lightMapContainers.push( { basicMat: object.material, object: object } ); + + } + + // Pack the objects' lightmap UVs into the same global space + const dimensions = potpack( uv_boxes ); + uv_boxes.forEach( ( box ) => { + + const uv1 = objects[ box.index ].geometry.getAttribute( 'uv' ).clone(); + for ( let i = 0; i < uv1.array.length; i += uv1.itemSize ) { + + uv1.array[ i ] = ( uv1.array[ i ] + box.x + padding ) / dimensions.w; + uv1.array[ i + 1 ] = 1 - ( ( uv1.array[ i + 1 ] + box.y + padding ) / dimensions.h ); + + } + + objects[ box.index ].geometry.setAttribute( 'uv1', uv1 ); + objects[ box.index ].geometry.getAttribute( 'uv1' ).needsUpdate = true; + + } ); + + } + + /** + * Frees all internal resources. + */ + dispose() { + + this._progressiveLightMap1.dispose(); + this._progressiveLightMap2.dispose(); + + this._uvMat.dispose(); + + if ( this._blurringPlane !== null ) { + + this._blurringPlane.geometry.dispose(); + this._blurringPlane.material.dispose(); + + } + + if ( this._labelMesh !== null ) { + + this._labelMesh.geometry.dispose(); + this._labelMesh.material.dispose(); + + } + + } + + /** + * This function renders each mesh one at a time into their respective surface maps. + * + * @param {Camera} camera - The camera the scene is rendered with. + * @param {number} [blendWindow=100] - When >1, samples will accumulate over time. + * @param {boolean} [blurEdges=true] - Whether to fix UV Edges via blurring. + */ + update( camera, blendWindow = 100, blurEdges = true ) { + + if ( this._blurringPlane === null ) { + + return; + + } + + // Store the original Render Target + const currentRenderTarget = this.renderer.getRenderTarget(); + + // The blurring plane applies blur to the seams of the lightmap + this._blurringPlane.visible = blurEdges; + + // Steal the Object3D from the real world to our special dimension + for ( let l = 0; l < this._lightMapContainers.length; l ++ ) { + + this._lightMapContainers[ l ].object.oldScene = this._lightMapContainers[ l ].object.parent; + this._scene.attach( this._lightMapContainers[ l ].object ); + + } + + // Set each object's material to the UV Unwrapped Surface Mapping Version + for ( let l = 0; l < this._lightMapContainers.length; l ++ ) { + + this._averagingWindow.value = blendWindow; + this._lightMapContainers[ l ].object.material = this._uvMat; + this._lightMapContainers[ l ].object.oldFrustumCulled = this._lightMapContainers[ l ].object.frustumCulled; + this._lightMapContainers[ l ].object.frustumCulled = false; + + } + + // Ping-pong two surface buffers for reading/writing + const activeMap = this._buffer1Active ? this._progressiveLightMap1 : this._progressiveLightMap2; + const inactiveMap = this._buffer1Active ? this._progressiveLightMap2 : this._progressiveLightMap1; + + // Render the object's surface maps + this.renderer.setRenderTarget( activeMap ); + this._previousShadowMap.value = inactiveMap.texture; + + this._buffer1Active = ! this._buffer1Active; + this.renderer.render( this._scene, camera ); + + // Restore the object's Real-time Material and add it back to the original world + for ( let l = 0; l < this._lightMapContainers.length; l ++ ) { + + this._lightMapContainers[ l ].object.frustumCulled = this._lightMapContainers[ l ].object.oldFrustumCulled; + this._lightMapContainers[ l ].object.material = this._lightMapContainers[ l ].basicMat; + this._lightMapContainers[ l ].object.oldScene.attach( this._lightMapContainers[ l ].object ); + + } + + // Restore the original Render Target + this.renderer.setRenderTarget( currentRenderTarget ); + + } + + /** + * Draws the lightmap in the main scene. Call this after adding the objects to it. + * + * @param {boolean} visible - Whether the debug plane should be visible + * @param {Vector3} [position] - Where the debug plane should be drawn + */ + showDebugLightmap( visible, position = null ) { + + if ( this._lightMapContainers.length === 0 ) { + + console.warn( 'THREE.ProgressiveLightMap: Call .showDebugLightmap() after adding the objects.' ); + + return; + + } + + if ( this._labelMesh === null ) { + + const labelMaterial = new NodeMaterial(); + labelMaterial.colorNode = texture( this._progressiveLightMap1.texture ).sample( uv().flipY() ); + labelMaterial.side = DoubleSide; + + const labelGeometry = new PlaneGeometry( 100, 100 ); + + this._labelMesh = new Mesh( labelGeometry, labelMaterial ); + this._labelMesh.position.y = 250; + + this._lightMapContainers[ 0 ].object.parent.add( this._labelMesh ); + + } + + if ( position !== null ) { + + this._labelMesh.position.copy( position ); + + } + + this._labelMesh.visible = visible; + + } + + /** + * Creates the Blurring Plane. + * + * @private + */ + _initializeBlurPlane() { + + const blurMaterial = new NodeMaterial(); + blurMaterial.polygonOffset = true; + blurMaterial.polygonOffsetFactor = - 1; + blurMaterial.polygonOffsetUnits = 3; + + blurMaterial.vertexNode = vec4( sub( uv(), vec2( 0.5 ) ).mul( 2 ), 1, 1 ); + + const uvNode = uv().flipY().toVar(); + const pixelOffset = float( 0.5 ).div( float( this.resolution ) ).toVar(); + + const color = add( + this._previousShadowMap.sample( uvNode.add( vec2( pixelOffset, 0 ) ) ), + this._previousShadowMap.sample( uvNode.add( vec2( 0, pixelOffset ) ) ), + this._previousShadowMap.sample( uvNode.add( vec2( 0, pixelOffset.negate() ) ) ), + this._previousShadowMap.sample( uvNode.add( vec2( pixelOffset.negate(), 0 ) ) ), + this._previousShadowMap.sample( uvNode.add( vec2( pixelOffset, pixelOffset ) ) ), + this._previousShadowMap.sample( uvNode.add( vec2( pixelOffset.negate(), pixelOffset ) ) ), + this._previousShadowMap.sample( uvNode.add( vec2( pixelOffset, pixelOffset.negate() ) ) ), + this._previousShadowMap.sample( uvNode.add( vec2( pixelOffset.negate(), pixelOffset.negate() ) ) ), + ).div( 8 ); + + blurMaterial.fragmentNode = color; + + this._blurringPlane = new Mesh( new PlaneGeometry( 1, 1 ), blurMaterial ); + this._blurringPlane.name = 'Blurring Plane'; + this._blurringPlane.frustumCulled = false; + this._blurringPlane.renderOrder = 0; + this._blurringPlane.material.depthWrite = false; + this._scene.add( this._blurringPlane ); + + } + +} + +export { ProgressiveLightMap }; diff --git a/examples/jsm/misc/RollerCoaster.js b/examples/jsm/misc/RollerCoaster.js index 6c3faa5655de59..a1c82cf4d9205f 100644 --- a/examples/jsm/misc/RollerCoaster.js +++ b/examples/jsm/misc/RollerCoaster.js @@ -8,8 +8,20 @@ import { Vector3 } from 'three'; +/** + * A procedural roller coaster geometry. + * + * @augments BufferGeometry + * @three_import import { RollerCoasterGeometry } from 'three/addons/misc/RollerCoaster.js'; + */ class RollerCoasterGeometry extends BufferGeometry { + /** + * Constructs a new geometry. + * + * @param {Curve} curve - The curve to generate the geometry along. + * @param {number} divisions - The number of divisions which defines the detail of the geometry. + */ constructor( curve, divisions ) { super(); @@ -222,8 +234,20 @@ class RollerCoasterGeometry extends BufferGeometry { } +/** + * A procedural roller coaster lifters geometry. + * + * @augments BufferGeometry + * @three_import import { RollerCoasterLiftersGeometry } from 'three/addons/misc/RollerCoaster.js'; + */ class RollerCoasterLiftersGeometry extends BufferGeometry { + /** + * Constructs a new geometry. + * + * @param {Curve} curve - The curve to generate the geometry along. + * @param {number} divisions - The number of divisions which defines the detail of the geometry. + */ constructor( curve, divisions ) { super(); @@ -398,8 +422,20 @@ class RollerCoasterLiftersGeometry extends BufferGeometry { } +/** + * A procedural roller coaster shadow geometry. + * + * @augments BufferGeometry + * @three_import import { RollerCoasterShadowGeometry } from 'three/addons/misc/RollerCoaster.js'; + */ class RollerCoasterShadowGeometry extends BufferGeometry { + /** + * Constructs a new geometry. + * + * @param {Curve} curve - The curve to generate the geometry along. + * @param {number} divisions - The number of divisions which defines the detail of the geometry. + */ constructor( curve, divisions ) { super(); @@ -470,8 +506,17 @@ class RollerCoasterShadowGeometry extends BufferGeometry { } +/** + * A procedural sky geometry. + * + * @augments BufferGeometry + * @three_import import { SkyGeometry } from 'three/addons/misc/RollerCoaster.js'; + */ class SkyGeometry extends BufferGeometry { + /** + * Constructs a new geometry. + */ constructor() { super(); @@ -503,8 +548,20 @@ class SkyGeometry extends BufferGeometry { } +/** + * A procedural trees geometry. + * + * @augments BufferGeometry + * @three_import import { TreesGeometry } from 'three/addons/misc/RollerCoaster.js'; + */ class TreesGeometry extends BufferGeometry { + /** + * Constructs a new geometry. + * + * @param {Mesh} landscape - A mesh representing the landscape. Trees will be positioned + * randomly on the landscape's surface. + */ constructor( landscape ) { super(); diff --git a/examples/jsm/misc/Timer.js b/examples/jsm/misc/Timer.js new file mode 100644 index 00000000000000..75483366ec9fd7 --- /dev/null +++ b/examples/jsm/misc/Timer.js @@ -0,0 +1,222 @@ +/** + * This class is an alternative to {@link Clock} with a different API design and behavior. + * The goal is to avoid the conceptual flaws that became apparent in `Clock` over time. + * + * - `Timer` has an `update()` method that updates its internal state. That makes it possible to + * call `getDelta()` and `getElapsed()` multiple times per simulation step without getting different values. + * - The class can make use of the Page Visibility API to avoid large time delta values when the app + * is inactive (e.g. tab switched or browser hidden). + * + * ```js + * const timer = new Timer(); + * timer.connect( document ); // use Page Visibility API + * ``` + * + * @three_import import { Timer } from 'three/addons/misc/Timer.js'; + */ +class Timer { + + /** + * Constructs a new timer. + */ + constructor() { + + this._previousTime = 0; + this._currentTime = 0; + this._startTime = now(); + + this._delta = 0; + this._elapsed = 0; + + this._timescale = 1; + + this._document = null; + this._pageVisibilityHandler = null; + + } + + /** + * Connect the timer to the given document.Calling this method is not mandatory to + * use the timer but enables the usage of the Page Visibility API to avoid large time + * delta values. + * + * @param {Document} document - The document. + */ + connect( document ) { + + this._document = document; + + // use Page Visibility API to avoid large time delta values + + if ( document.hidden !== undefined ) { + + this._pageVisibilityHandler = handleVisibilityChange.bind( this ); + + document.addEventListener( 'visibilitychange', this._pageVisibilityHandler, false ); + + } + + } + + /** + * Disconnects the timer from the DOM and also disables the usage of the Page Visibility API. + */ + disconnect() { + + if ( this._pageVisibilityHandler !== null ) { + + this._document.removeEventListener( 'visibilitychange', this._pageVisibilityHandler ); + this._pageVisibilityHandler = null; + + } + + this._document = null; + + } + + /** + * Returns the time delta in seconds. + * + * @return {number} The time delta in second. + */ + getDelta() { + + return this._delta / 1000; + + } + + /** + * Returns the elapsed time in seconds. + * + * @return {number} The elapsed time in second. + */ + getElapsed() { + + return this._elapsed / 1000; + + } + + /** + * Returns the timescale. + * + * @return {number} The timescale. + */ + getTimescale() { + + return this._timescale; + + } + + /** + * Sets the given timescale which scale the time delta computation + * in `update()`. + * + * @param {number} timescale - The timescale to set. + * @return {Timer} A reference to this timer. + */ + setTimescale( timescale ) { + + this._timescale = timescale; + + return this; + + } + + /** + * Resets the time computation for the current simulation step. + * + * @return {Timer} A reference to this timer. + */ + reset() { + + this._currentTime = now() - this._startTime; + + return this; + + } + + /** + * Can be used to free all internal resources. Usually called when + * the timer instance isn't required anymore. + */ + dispose() { + + this.disconnect(); + + } + + /** + * Updates the internal state of the timer. This method should be called + * once per simulation step and before you perform queries against the timer + * (e.g. via `getDelta()`). + * + * @param {number} timestamp - The current time in milliseconds. Can be obtained + * from the `requestAnimationFrame` callback argument. If not provided, the current + * time will be determined with `performance.now`. + * @return {Timer} A reference to this timer. + */ + update( timestamp ) { + + if ( this._pageVisibilityHandler !== null && this._document.hidden === true ) { + + this._delta = 0; + + } else { + + this._previousTime = this._currentTime; + this._currentTime = ( timestamp !== undefined ? timestamp : now() ) - this._startTime; + + this._delta = ( this._currentTime - this._previousTime ) * this._timescale; + this._elapsed += this._delta; // _elapsed is the accumulation of all previous deltas + + } + + return this; + + } + +} + +/** + * A special version of a timer with a fixed time delta value. + * Can be useful for testing and debugging purposes. + * + * @augments Timer + */ +class FixedTimer extends Timer { + + /** + * Constructs a new timer. + * + * @param {number} [fps=60] - The fixed FPS of this timer. + */ + constructor( fps = 60 ) { + + super(); + this._delta = ( 1 / fps ) * 1000; + + } + + update() { + + this._elapsed += ( this._delta * this._timescale ); // _elapsed is the accumulation of all previous deltas + + return this; + + } + +} + +function now() { + + return performance.now(); + +} + +function handleVisibilityChange() { + + if ( this._document.hidden === false ) this.reset(); + +} + +export { Timer, FixedTimer }; diff --git a/examples/jsm/misc/TubePainter.js b/examples/jsm/misc/TubePainter.js index 7fc66e3b0e6f6d..e6ad0485d1340f 100644 --- a/examples/jsm/misc/TubePainter.js +++ b/examples/jsm/misc/TubePainter.js @@ -9,6 +9,20 @@ import { Vector3 } from 'three'; +/** + * @classdesc This module can be used to paint tube-like meshes + * along a sequence of points. This module is used in a XR + * painter demo. + * + * ```js + * const painter = new TubePainter(); + * scene.add( painter.mesh ); + * ``` + * + * @name TubePainter + * @class + * @three_import import { TubePainter } from 'three/addons/misc/TubePainter.js'; + */ function TubePainter() { const BUFFER_SIZE = 1000000 * 3; @@ -176,16 +190,13 @@ function TubePainter() { if ( start === end ) return; - positions.updateRange.offset = start * 3; - positions.updateRange.count = ( end - start ) * 3; + positions.addUpdateRange( start * 3, ( end - start ) * 3 ); positions.needsUpdate = true; - normals.updateRange.offset = start * 3; - normals.updateRange.count = ( end - start ) * 3; + normals.addUpdateRange( start * 3, ( end - start ) * 3 ); normals.needsUpdate = true; - colors.updateRange.offset = start * 3; - colors.updateRange.count = ( end - start ) * 3; + colors.addUpdateRange( start * 3, ( end - start ) * 3 ); colors.needsUpdate = true; count = geometry.drawRange.count; @@ -193,10 +204,50 @@ function TubePainter() { } return { + /** + * The "painted" tube mesh. Must be added to the scene. + * + * @name TubePainter#mesh + * @type {Mesh} + */ mesh: mesh, + + /** + * Moves the current painting position to the given value. + * + * @method + * @name TubePainter#moveTo + * @param {Vector3} position The new painting position. + */ moveTo: moveTo, + + /** + * Draw a stroke from the current position to the given one. + * This method extends the tube while drawing with the XR + * controllers. + * + * @method + * @name TubePainter#lineTo + * @param {Vector3} position The destination position. + */ lineTo: lineTo, + + /** + * Sets the size of newly rendered tube segments. + * + * @method + * @name TubePainter#setSize + * @param {number} size The size. + */ setSize: setSize, + + /** + * Updates the internal geometry buffers so the new painted + * segments are rendered. + * + * @method + * @name TubePainter#update + */ update: update }; diff --git a/examples/jsm/misc/Volume.js b/examples/jsm/misc/Volume.js index 3a2fd992968782..3cfcf09558ff35 100644 --- a/examples/jsm/misc/Volume.js +++ b/examples/jsm/misc/Volume.js @@ -6,42 +6,63 @@ import { import { VolumeSlice } from '../misc/VolumeSlice.js'; /** - * This class had been written to handle the output of the NRRD loader. - * It contains a volume of data and informations about it. - * For now it only handles 3 dimensional data. - * See the webgl_loader_nrrd.html example and the loaderNRRD.js file to see how to use this class. - * @class - * @param {number} xLength Width of the volume - * @param {number} yLength Length of the volume - * @param {number} zLength Depth of the volume - * @param {string} type The type of data (uint8, uint16, ...) - * @param {ArrayBuffer} arrayBuffer The buffer with volume data + * This class had been written to handle the output of the {@link NRRDLoader}. + * It contains a volume of data and information about it. For now it only handles 3 dimensional data. + * + * @three_import import { Volume } from 'three/addons/misc/Volume.js'; */ class Volume { + /** + * Constructs a new volume. + * + * @param {number} [xLength] - Width of the volume. + * @param {number} [yLength] - Length of the volume. + * @param {number} [zLength] - Depth of the volume. + * @param {string} [type] - The type of data (uint8, uint16, ...). + * @param {ArrayBuffer} [arrayBuffer] - The buffer with volume data. + */ constructor( xLength, yLength, zLength, type, arrayBuffer ) { if ( xLength !== undefined ) { /** - * @member {number} xLength Width of the volume in the IJK coordinate system + * Width of the volume in the IJK coordinate system. + * + * @type {number} + * @default 1 */ this.xLength = Number( xLength ) || 1; + /** - * @member {number} yLength Height of the volume in the IJK coordinate system + * Height of the volume in the IJK coordinate system. + * + * @type {number} + * @default 1 */ this.yLength = Number( yLength ) || 1; + /** - * @member {number} zLength Depth of the volume in the IJK coordinate system + * Depth of the volume in the IJK coordinate system. + * + * @type {number} + * @default 1 */ this.zLength = Number( zLength ) || 1; + /** - * @member {Array} The order of the Axis dictated by the NRRD header + * The order of the Axis dictated by the NRRD header + * + * @type {Array} */ this.axisOrder = [ 'x', 'y', 'z' ]; + /** - * @member {TypedArray} data Data of the volume + * The data of the volume. + * + * @type {TypedArray} */ + this.data; switch ( type ) { @@ -127,25 +148,34 @@ class Volume { } /** - * @member {Array} spacing Spacing to apply to the volume from IJK to RAS coordinate system + * Spacing to apply to the volume from IJK to RAS coordinate system + * + * @type {Array} */ this.spacing = [ 1, 1, 1 ]; + /** - * @member {Array} offset Offset of the volume in the RAS coordinate system + * Offset of the volume in the RAS coordinate system + * + * @type {Array} */ this.offset = [ 0, 0, 0 ]; + /** - * @member {Martrix3} matrix The IJK to RAS matrix + * The IJK to RAS matrix. + * + * @type {Martrix3} */ this.matrix = new Matrix3(); this.matrix.identity(); + /** - * @member {Martrix3} inverseMatrix The RAS to IJK matrix - */ - /** - * @member {number} lowerThreshold The voxels with values under this threshold won't appear in the slices. - * If changed, geometryNeedsUpdate is automatically set to true on all the slices associated to this volume + * The RAS to IJK matrix. + * + * @type {Martrix3} */ + this.inverseMatrix = new Matrix3(); + let lowerThreshold = - Infinity; Object.defineProperty( this, 'lowerThreshold', { get: function () { @@ -153,6 +183,14 @@ class Volume { return lowerThreshold; }, + /** + * The voxels with values under this threshold won't appear in the slices. + * If changed, geometryNeedsUpdate is automatically set to true on all the slices associated to this volume. + * + * @name Volume#lowerThreshold + * @type {number} + * @param {number} value + */ set: function ( value ) { lowerThreshold = value; @@ -164,10 +202,7 @@ class Volume { } } ); - /** - * @member {number} upperThreshold The voxels with values over this threshold won't appear in the slices. - * If changed, geometryNeedsUpdate is automatically set to true on all the slices associated to this volume - */ + let upperThreshold = Infinity; Object.defineProperty( this, 'upperThreshold', { get: function () { @@ -175,6 +210,14 @@ class Volume { return upperThreshold; }, + /** + * The voxels with values over this threshold won't appear in the slices. + * If changed, geometryNeedsUpdate is automatically set to true on all the slices associated to this volume + * + * @name Volume#upperThreshold + * @type {number} + * @param {number} value + */ set: function ( value ) { upperThreshold = value; @@ -189,32 +232,38 @@ class Volume { /** - * @member {Array} sliceList The list of all the slices associated to this volume + * The list of all the slices associated to this volume + * + * @type {Array} */ this.sliceList = []; - /** - * @member {boolean} segmentation in segmentation mode, it can load 16-bits nrrds correctly + * Whether to use segmentation mode or not. + * It can load 16-bits nrrds correctly. + * + * @type {boolean} + * @default false */ this.segmentation = false; /** - * @member {Array} RASDimensions This array holds the dimensions of the volume in the RAS space + * This array holds the dimensions of the volume in the RAS space + * + * @type {Array} */ - - + this.RASDimensions = []; } /** - * @member {Function} getData Shortcut for data[access(i,j,k)] - * @memberof Volume - * @param {number} i First coordinate - * @param {number} j Second coordinate - * @param {number} k Third coordinate - * @returns {number} value in the data array + * Shortcut for data[access(i,j,k)]. + * + * @param {number} i - First coordinate. + * @param {number} j - Second coordinate. + * @param {number} k - Third coordinate. + * @returns {number} The value in the data array. */ getData( i, j, k ) { @@ -223,12 +272,12 @@ class Volume { } /** - * @member {Function} access compute the index in the data array corresponding to the given coordinates in IJK system - * @memberof Volume - * @param {number} i First coordinate - * @param {number} j Second coordinate - * @param {number} k Third coordinate - * @returns {number} index + * Compute the index in the data array corresponding to the given coordinates in IJK system. + * + * @param {number} i - First coordinate. + * @param {number} j - Second coordinate. + * @param {number} k - Third coordinate. + * @returns {number} The index. */ access( i, j, k ) { @@ -237,10 +286,10 @@ class Volume { } /** - * @member {Function} reverseAccess Retrieve the IJK coordinates of the voxel corresponding of the given index in the data - * @memberof Volume - * @param {number} index index of the voxel - * @returns {Array} [x,y,z] + * Retrieve the IJK coordinates of the voxel corresponding of the given index in the data. + * + * @param {number} index - Index of the voxel. + * @returns {Array} The IJK coordinates as `[x,y,z]`. */ reverseAccess( index ) { @@ -252,14 +301,12 @@ class Volume { } /** - * @member {Function} map Apply a function to all the voxels, be careful, the value will be replaced - * @memberof Volume - * @param {Function} functionToMap A function to apply to every voxel, will be called with the following parameters : - * value of the voxel - * index of the voxel - * the data (TypedArray) - * @param {Object} context You can specify a context in which call the function, default if this Volume - * @returns {Volume} this + * Apply a function to all the voxels, be careful, the value will be replaced. + * + * @param {Function} functionToMap A function to apply to every voxel, will be called with the following parameters: + * value of the voxel, index of the voxel, the data (TypedArray). + * @param {Object} context - You can specify a context in which call the function, default if this Volume. + * @returns {Volume} A reference to this instance. */ map( functionToMap, context ) { @@ -277,11 +324,12 @@ class Volume { } /** - * @member {Function} extractPerpendicularPlane Compute the orientation of the slice and returns all the information relative to the geometry such as sliceAccess, the plane matrix (orientation and position in RAS coordinate) and the dimensions of the plane in both coordinate system. - * @memberof Volume - * @param {string} axis the normal axis to the slice 'x' 'y' or 'z' - * @param {number} index the index of the slice - * @returns {Object} an object containing all the usefull information on the geometry of the slice + * Compute the orientation of the slice and returns all the information relative to the geometry such as sliceAccess, + * the plane matrix (orientation and position in RAS coordinate) and the dimensions of the plane in both coordinate system. + * + * @param {('x'|'y'|'z')} axis - The normal axis to the slice. + * @param {number} RASIndex - The index of the slice. + * @returns {Object} An object containing all the useful information on the geometry of the slice. */ extractPerpendicularPlane( axis, RASIndex ) { @@ -340,20 +388,18 @@ class Volume { } - - let iLength, jLength; - - if( ! this.segmentation ) { + if ( ! this.segmentation ) { firstDirection.applyMatrix4( volume.inverseMatrix ).normalize(); secondDirection.applyMatrix4( volume.inverseMatrix ).normalize(); axisInIJK.applyMatrix4( volume.inverseMatrix ).normalize(); } + firstDirection.arglet = 'i'; secondDirection.arglet = 'j'; - iLength = Math.floor( Math.abs( firstDirection.dot( dimensions ) ) ); - jLength = Math.floor( Math.abs( secondDirection.dot( dimensions ) ) ); + const iLength = Math.floor( Math.abs( firstDirection.dot( dimensions ) ) ); + const jLength = Math.floor( Math.abs( secondDirection.dot( dimensions ) ) ); const planeWidth = Math.abs( iLength * firstSpacing ); const planeHeight = Math.abs( jLength * secondSpacing ); @@ -403,12 +449,12 @@ class Volume { } /** - * @member {Function} extractSlice Returns a slice corresponding to the given axis and index - * The coordinate are given in the Right Anterior Superior coordinate format - * @memberof Volume - * @param {string} axis the normal axis to the slice 'x' 'y' or 'z' - * @param {number} index the index of the slice - * @returns {VolumeSlice} the extracted slice + * Returns a slice corresponding to the given axis and index. + * The coordinate are given in the Right Anterior Superior coordinate format. + * + * @param {('x'|'y'|'z')} axis - The normal axis to the slice. + * @param {number} index - The index of the slice. + * @returns {VolumeSlice} The extracted slice. */ extractSlice( axis, index ) { @@ -419,10 +465,10 @@ class Volume { } /** - * @member {Function} repaintAllSlices Call repaint on all the slices extracted from this volume - * @see VolumeSlice.repaint - * @memberof Volume - * @returns {Volume} this + * Call repaint on all the slices extracted from this volume. + * + * @see {@link VolumeSlice#repaint} + * @returns {Volume} A reference to this volume. */ repaintAllSlices() { @@ -437,9 +483,9 @@ class Volume { } /** - * @member {Function} computeMinMax Compute the minimum and the maximum of the data in the volume - * @memberof Volume - * @returns {Array} [min,max] + * Compute the minimum and the maximum of the data in the volume. + * + * @returns {Array} The min/max data as `[min,max]`. */ computeMinMax() { diff --git a/examples/jsm/misc/VolumeSlice.js b/examples/jsm/misc/VolumeSlice.js index f0e1088395aa08..be0afbc19aa9db 100644 --- a/examples/jsm/misc/VolumeSlice.js +++ b/examples/jsm/misc/VolumeSlice.js @@ -5,36 +5,51 @@ import { Mesh, MeshBasicMaterial, PlaneGeometry, - Texture + Texture, + SRGBColorSpace } from 'three'; /** - * This class has been made to hold a slice of a volume data - * @class - * @param {Volume} volume The associated volume - * @param {number} [index=0] The index of the slice - * @param {string} [axis='z'] For now only 'x', 'y' or 'z' but later it will change to a normal vector - * @see Volume + * This class has been made to hold a slice of a volume data. + * + * @see {@link Volume} + * @three_import import { VolumeSlice } from 'three/addons/misc/VolumeSlice.js'; */ class VolumeSlice { - constructor( volume, index, axis ) { + /** + * Constructs a new volume slice. + * + * @param {Volume} volume - The associated volume. + * @param {number} [index=0] - The index of the slice. + * @param {('x'|'y'|'z')} [axis='z'] - For now only 'x', 'y' or 'z' but later it will change to a normal vector. + */ + constructor( volume, index = 0, axis = 'z' ) { const slice = this; + /** - * @member {Volume} volume The associated volume + * The associated volume. + * + * @type {Volume} */ this.volume = volume; - /** - * @member {Number} index The index of the slice, if changed, will automatically call updateGeometry at the next repaint - */ - index = index || 0; + Object.defineProperty( this, 'index', { get: function () { return index; }, + /** + * The index of the slice, if changed, will automatically call updateGeometry at the next repaint. + * + * @name VolumeSlice#index + * @type {number} + * @default 0 + * @param {number} value + * @return {number} + */ set: function ( value ) { index = value; @@ -43,65 +58,97 @@ class VolumeSlice { } } ); - /** - * @member {String} axis The normal axis - */ - this.axis = axis || 'z'; /** - * @member {HTMLCanvasElement} canvas The final canvas used for the texture + * The normal axis. + * + * @type {('x'|'y'|'z')} */ + this.axis = axis; + /** - * @member {CanvasRenderingContext2D} ctx Context of the canvas + * The final canvas used for the texture. + * + * @type {HTMLCanvasElement} */ this.canvas = document.createElement( 'canvas' ); + /** - * @member {HTMLCanvasElement} canvasBuffer The intermediary canvas used to paint the data + * The rendering context of the canvas. + * + * @type {CanvasRenderingContext2D} */ + this.ctx; + /** - * @member {CanvasRenderingContext2D} ctxBuffer Context of the canvas buffer + * The intermediary canvas used to paint the data. + * + * @type {HTMLCanvasElement} */ this.canvasBuffer = document.createElement( 'canvas' ); + + /** + * The rendering context of the canvas buffer, + * + * @type {CanvasRenderingContext2D} + */ + this.ctxBuffer; + this.updateGeometry(); const canvasMap = new Texture( this.canvas ); canvasMap.minFilter = LinearFilter; + canvasMap.generateMipmaps = false; canvasMap.wrapS = canvasMap.wrapT = ClampToEdgeWrapping; + canvasMap.colorSpace = SRGBColorSpace; const material = new MeshBasicMaterial( { map: canvasMap, side: DoubleSide, transparent: true } ); + /** - * @member {Mesh} mesh The mesh ready to get used in the scene + * The mesh ready to get used in the scene. + * + * @type {Mesh} */ this.mesh = new Mesh( this.geometry, material ); this.mesh.matrixAutoUpdate = false; + /** - * @member {Boolean} geometryNeedsUpdate If set to true, updateGeometry will be triggered at the next repaint + * If set to `true`, `updateGeometry()` will be triggered at the next repaint. + * + * @type {boolean} + * @default true */ this.geometryNeedsUpdate = true; this.repaint(); /** - * @member {Number} iLength Width of slice in the original coordinate system, corresponds to the width of the buffer canvas + * Width of slice in the original coordinate system, corresponds to the width of the buffer canvas. + * + * @type {number} + * @default 0 */ + this.iLength = 0; /** - * @member {Number} jLength Height of slice in the original coordinate system, corresponds to the height of the buffer canvas + * Height of slice in the original coordinate system, corresponds to the height of the buffer canvas. + * + * @type {number} + * @default 0 */ + this.jLength = 0; /** - * @member {Function} sliceAccess Function that allow the slice to access right data - * @see Volume.extractPerpendicularPlane - * @param {Number} i The first coordinate - * @param {Number} j The second coordinate - * @returns {Number} the index corresponding to the voxel in volume.data of the given position in the slice + * Function that allow the slice to access right data. + * + * @type {?Function} + * @see {@link Volume#extractPerpendicularPlane} */ - + this.sliceAccess = null; } /** - * @member {Function} repaint Refresh the texture and the geometry if geometryNeedsUpdate is set to true - * @memberof VolumeSlice + * Refresh the texture and the geometry if geometryNeedsUpdate is set to `true`. */ repaint() { @@ -133,23 +180,26 @@ class VolumeSlice { if ( volume.dataType === 'label' ) { - //this part is currently useless but will be used when colortables will be handled - for ( let j = 0; j < jLength; j ++ ) { + console.error( 'THREE.VolumeSlice.repaint: label are not supported yet' ); - for ( let i = 0; i < iLength; i ++ ) { + // This part is currently useless but will be used when colortables will be handled - let label = volumeData[ sliceAccess( i, j ) ]; - label = label >= this.colorMap.length ? ( label % this.colorMap.length ) + 1 : label; - const color = this.colorMap[ label ]; - data[ 4 * pixelCount ] = ( color >> 24 ) & 0xff; - data[ 4 * pixelCount + 1 ] = ( color >> 16 ) & 0xff; - data[ 4 * pixelCount + 2 ] = ( color >> 8 ) & 0xff; - data[ 4 * pixelCount + 3 ] = color & 0xff; - pixelCount ++; + // for ( let j = 0; j < jLength; j ++ ) { - } + // for ( let i = 0; i < iLength; i ++ ) { - } + // let label = volumeData[ sliceAccess( i, j ) ]; + // label = label >= this.colorMap.length ? ( label % this.colorMap.length ) + 1 : label; + // const color = this.colorMap[ label ]; + // data[ 4 * pixelCount ] = ( color >> 24 ) & 0xff; + // data[ 4 * pixelCount + 1 ] = ( color >> 16 ) & 0xff; + // data[ 4 * pixelCount + 2 ] = ( color >> 8 ) & 0xff; + // data[ 4 * pixelCount + 3 ] = color & 0xff; + // pixelCount ++; + + // } + + // } } else { @@ -186,9 +236,8 @@ class VolumeSlice { } /** - * @member {Function} Refresh the geometry according to axis and index - * @see Volume.extractPerpendicularPlane - * @memberof VolumeSlice + * Refresh the geometry according to axis and index. + * @see {@link Volume#extractPerpendicularPlane} */ updateGeometry() { diff --git a/examples/jsm/modifiers/CurveModifier.js b/examples/jsm/modifiers/CurveModifier.js index fc3f49c2761c87..77a076ee653590 100644 --- a/examples/jsm/modifiers/CurveModifier.js +++ b/examples/jsm/modifiers/CurveModifier.js @@ -5,12 +5,13 @@ const TEXTURE_HEIGHT = 4; import { DataTexture, + DataUtils, RGBAFormat, - FloatType, + HalfFloatType, RepeatWrapping, Mesh, InstancedMesh, - NearestFilter, + LinearFilter, DynamicDrawUsage, Matrix4 } from 'three'; @@ -18,22 +19,25 @@ import { /** * Make a new DataTexture to store the descriptions of the curves. * - * @param { number } numberOfCurves the number of curves needed to be described by this texture. + * @private + * @param {number} numberOfCurves - The number of curves needed to be described by this texture. + * @returns {DataTexture} */ -export function initSplineTexture( numberOfCurves = 1 ) { +function initSplineTexture( numberOfCurves = 1 ) { - const dataArray = new Float32Array( TEXTURE_WIDTH * TEXTURE_HEIGHT * numberOfCurves * CHANNELS ); + const dataArray = new Uint16Array( TEXTURE_WIDTH * TEXTURE_HEIGHT * numberOfCurves * CHANNELS ); const dataTexture = new DataTexture( dataArray, TEXTURE_WIDTH, TEXTURE_HEIGHT * numberOfCurves, RGBAFormat, - FloatType + HalfFloatType ); dataTexture.wrapS = RepeatWrapping; dataTexture.wrapY = RepeatWrapping; - dataTexture.magFilter = NearestFilter; + dataTexture.magFilter = LinearFilter; + dataTexture.minFilter = LinearFilter; dataTexture.needsUpdate = true; return dataTexture; @@ -41,13 +45,14 @@ export function initSplineTexture( numberOfCurves = 1 ) { } /** - * Write the curve description to the data texture + * Write the curve description to the data texture. * - * @param { DataTexture } texture The DataTexture to write to - * @param { Curve } splineCurve The curve to describe - * @param { number } offset Which curve slot to write to + * @private + * @param {DataTexture} texture - The data texture to write to. + * @param {Curve} splineCurve - The curve to describe. + * @param {number} offset - Which curve slot to write to. */ -export function updateSplineTexture( texture, splineCurve, offset = 0 ) { +function updateSplineTexture( texture, splineCurve, offset = 0 ) { const numberOfPoints = Math.floor( TEXTURE_WIDTH * ( TEXTURE_HEIGHT / 4 ) ); splineCurve.arcLengthDivisions = numberOfPoints / 2; @@ -75,25 +80,25 @@ export function updateSplineTexture( texture, splineCurve, offset = 0 ) { } - function setTextureValue( texture, index, x, y, z, o ) { const image = texture.image; const { data } = image; const i = CHANNELS * TEXTURE_WIDTH * o; // Row Offset - data[ index * CHANNELS + i + 0 ] = x; - data[ index * CHANNELS + i + 1 ] = y; - data[ index * CHANNELS + i + 2 ] = z; - data[ index * CHANNELS + i + 3 ] = 1; + data[ index * CHANNELS + i + 0 ] = DataUtils.toHalfFloat( x ); + data[ index * CHANNELS + i + 1 ] = DataUtils.toHalfFloat( y ); + data[ index * CHANNELS + i + 2 ] = DataUtils.toHalfFloat( z ); + data[ index * CHANNELS + i + 3 ] = DataUtils.toHalfFloat( 1 ); } /** - * Create a new set of uniforms for describing the curve modifier + * Create a new set of uniforms for describing the curve modifier. * - * @param { DataTexture } Texture which holds the curve description + * @param {DataTexture} splineTexture - Which holds the curve description. + * @returns {Object} The uniforms object to be used in the shader. */ -export function getUniforms( splineTexture ) { +function getUniforms( splineTexture ) { const uniforms = { spineTexture: { value: splineTexture }, @@ -107,7 +112,7 @@ export function getUniforms( splineTexture ) { } -export function modifyShader( material, uniforms, numberOfCurves = 1 ) { +function modifyShader( material, uniforms, numberOfCurves = 1 ) { if ( material.__ok ) return; material.__ok = true; @@ -135,10 +140,10 @@ export function modifyShader( material, uniforms, numberOfCurves = 1 ) { // chunk import moved in front of modified shader below .replace( '#include ', '' ) - // vec3 transformedNormal declaration overriden below + // vec3 transformedNormal declaration overridden below .replace( '#include ', '' ) - // vec3 transformed declaration overriden below + // vec3 transformed declaration overridden below .replace( '#include ', '' ) // shader override @@ -194,19 +199,26 @@ vec3 transformedNormal = normalMatrix * (basis * objectNormal); } /** - * A helper class for making meshes bend aroudn curves + * A modifier for making meshes bend around curves. + * + * This module can only be used with {@link WebGLRenderer}. When using {@link WebGPURenderer}, + * import the class from `CurveModifierGPU.js`. + * + * @three_import import { Flow } from 'three/addons/modifiers/CurveModifier.js'; */ export class Flow { /** - * @param {Mesh} mesh The mesh to clone and modify to bend around the curve - * @param {number} numberOfCurves The amount of space that should preallocated for additional curves + * Constructs a new Flow instance. + * + * @param {Mesh} mesh - The mesh to clone and modify to bend around the curve. + * @param {number} numberOfCurves - The amount of space that should preallocated for additional curves. */ constructor( mesh, numberOfCurves = 1 ) { const obj3D = mesh.clone(); - const splineTexure = initSplineTexture( numberOfCurves ); - const uniforms = getUniforms( splineTexure ); + const splineTexture = initSplineTexture( numberOfCurves ); + const uniforms = getUniforms( splineTexture ); obj3D.traverse( function ( child ) { if ( @@ -214,8 +226,26 @@ export class Flow { child instanceof InstancedMesh ) { - child.material = child.material.clone(); - modifyShader( child.material, uniforms, numberOfCurves ); + if ( Array.isArray( child.material ) ) { + + const materials = []; + + for ( const material of child.material ) { + + const newMaterial = material.clone(); + modifyShader( newMaterial, uniforms, numberOfCurves ); + materials.push( newMaterial ); + + } + + child.material = materials; + + } else { + + child.material = child.material.clone(); + modifyShader( child.material, uniforms, numberOfCurves ); + + } } @@ -225,22 +255,33 @@ export class Flow { this.curveLengthArray = new Array( numberOfCurves ); this.object3D = obj3D; - this.splineTexure = splineTexure; + this.splineTexture = splineTexture; this.uniforms = uniforms; } + /** + * Updates the curve for the given curve index. + * + * @param {number} index - The curve index. + * @param {Curve} curve - The curve that should be used to bend the mesh. + */ updateCurve( index, curve ) { - if ( index >= this.curveArray.length ) throw Error( 'Index out of range for Flow' ); + if ( index >= this.curveArray.length ) throw Error( 'Flow: Index out of range.' ); const curveLength = curve.getLength(); this.uniforms.spineLength.value = curveLength; this.curveLengthArray[ index ] = curveLength; this.curveArray[ index ] = curve; - updateSplineTexture( this.splineTexure, curve, index ); + updateSplineTexture( this.splineTexture, curve, index ); } + /** + * Moves the mesh along the curve. + * + * @param {number} amount - The offset. + */ moveAlongCurve( amount ) { this.uniforms.pathOffset.value += amount; @@ -248,19 +289,26 @@ export class Flow { } } -const matrix = new Matrix4(); + +const _matrix = new Matrix4(); /** - * A helper class for creating instanced versions of flow, where the instances are placed on the curve. + * An instanced version of {@link Flow} for making meshes bend around curves, where the instances are placed on the curve. + * + * This module can only be used with {@link WebGLRenderer}. + * + * @augments Flow + * @three_import import { InstancedFlow } from 'three/addons/modifiers/CurveModifier.js'; */ export class InstancedFlow extends Flow { /** + * Constructs a new InstancedFlow instance. * - * @param {number} count The number of instanced elements - * @param {number} curveCount The number of curves to preallocate for - * @param {Geometry} geometry The geometry to use for the instanced mesh - * @param {Material} material The material to use for the instanced mesh + * @param {number} count - The number of instanced elements. + * @param {number} curveCount - The number of curves to preallocate for. + * @param {Geometry} geometry - The geometry to use for the instanced mesh. + * @param {Material} material - The material to use for the instanced mesh. */ constructor( count, curveCount, geometry, material ) { @@ -282,25 +330,25 @@ export class InstancedFlow extends Flow { * The extra information about which curve and curve position is stored in the translation components of the matrix for the instanced objects * This writes that information to the matrix and marks it as needing update. * - * @param {number} index of the instanced element to update + * @param {number} index - The index of tge instanced element to update. */ writeChanges( index ) { - matrix.makeTranslation( + _matrix.makeTranslation( this.curveLengthArray[ this.whichCurve[ index ] ], this.whichCurve[ index ], this.offsets[ index ] ); - this.object3D.setMatrixAt( index, matrix ); + this.object3D.setMatrixAt( index, _matrix ); this.object3D.instanceMatrix.needsUpdate = true; } /** - * Move an individual element along the curve by a specific amount + * Move an individual element along the curve by a specific amount. * - * @param {number} index Which element to update - * @param {number} offset Move by how much + * @param {number} index - Which element to update. + * @param {number} offset - The offset. */ moveIndividualAlongCurve( index, offset ) { @@ -310,14 +358,14 @@ export class InstancedFlow extends Flow { } /** - * Select which curve to use for an element + * Select which curve to use for an element. * - * @param {number} index the index of the instanced element to update - * @param {number} curveNo the index of the curve it should use + * @param {number} index - The index of the instanced element to update. + * @param {number} curveNo - The index of the curve it should use. */ setCurve( index, curveNo ) { - if ( isNaN( curveNo ) ) throw Error( 'curve index being set is Not a Number (NaN)' ); + if ( isNaN( curveNo ) ) throw Error( 'InstancedFlow: Curve index being set is Not a Number (NaN).' ); this.whichCurve[ index ] = curveNo; this.writeChanges( index ); diff --git a/examples/jsm/modifiers/CurveModifierGPU.js b/examples/jsm/modifiers/CurveModifierGPU.js new file mode 100644 index 00000000000000..e3ecaaa1158d18 --- /dev/null +++ b/examples/jsm/modifiers/CurveModifierGPU.js @@ -0,0 +1,256 @@ +// Original src: https://github.com/zz85/threejs-path-flow +const CHANNELS = 4; +const TEXTURE_WIDTH = 1024; +const TEXTURE_HEIGHT = 4; + +import { + DataTexture, + DataUtils, + RGBAFormat, + HalfFloatType, + RepeatWrapping, + Mesh, + InstancedMesh, + LinearFilter +} from 'three'; + +import { modelWorldMatrix, normalLocal, vec2, vec3, vec4, mat3, varyingProperty, texture, reference, Fn, select, positionLocal } from 'three/tsl'; + +/** + * Make a new DataTexture to store the descriptions of the curves. + * + * @private + * @param {number} [numberOfCurves=1] - The number of curves needed to be described by this texture. + * @returns {DataTexture} The new data texture. + */ +function initSplineTexture( numberOfCurves = 1 ) { + + const dataArray = new Uint16Array( TEXTURE_WIDTH * TEXTURE_HEIGHT * numberOfCurves * CHANNELS ); + const dataTexture = new DataTexture( + dataArray, + TEXTURE_WIDTH, + TEXTURE_HEIGHT * numberOfCurves, + RGBAFormat, + HalfFloatType + ); + + dataTexture.wrapS = RepeatWrapping; + dataTexture.wrapY = RepeatWrapping; + dataTexture.magFilter = LinearFilter; + dataTexture.minFilter = LinearFilter; + dataTexture.needsUpdate = true; + + return dataTexture; + +} + +/** + * Write the curve description to the data texture. + * + * @private + * @param {DataTexture} texture - The data texture to write to. + * @param {Curve} splineCurve - The curve to describe. + * @param {number} [offset=0] - Which curve slot to write to. + */ +function updateSplineTexture( texture, splineCurve, offset = 0 ) { + + const numberOfPoints = Math.floor( TEXTURE_WIDTH * ( TEXTURE_HEIGHT / 4 ) ); + splineCurve.arcLengthDivisions = numberOfPoints / 2; + splineCurve.updateArcLengths(); + const points = splineCurve.getSpacedPoints( numberOfPoints ); + const frenetFrames = splineCurve.computeFrenetFrames( numberOfPoints, true ); + + for ( let i = 0; i < numberOfPoints; i ++ ) { + + const rowOffset = Math.floor( i / TEXTURE_WIDTH ); + const rowIndex = i % TEXTURE_WIDTH; + + let pt = points[ i ]; + setTextureValue( texture, rowIndex, pt.x, pt.y, pt.z, 0 + rowOffset + ( TEXTURE_HEIGHT * offset ) ); + pt = frenetFrames.tangents[ i ]; + setTextureValue( texture, rowIndex, pt.x, pt.y, pt.z, 1 + rowOffset + ( TEXTURE_HEIGHT * offset ) ); + pt = frenetFrames.normals[ i ]; + setTextureValue( texture, rowIndex, pt.x, pt.y, pt.z, 2 + rowOffset + ( TEXTURE_HEIGHT * offset ) ); + pt = frenetFrames.binormals[ i ]; + setTextureValue( texture, rowIndex, pt.x, pt.y, pt.z, 3 + rowOffset + ( TEXTURE_HEIGHT * offset ) ); + + } + + texture.needsUpdate = true; + +} + + +function setTextureValue( texture, index, x, y, z, o ) { + + const image = texture.image; + const { data } = image; + const i = CHANNELS * TEXTURE_WIDTH * o; // Row Offset + + data[ index * CHANNELS + i + 0 ] = DataUtils.toHalfFloat( x ); + data[ index * CHANNELS + i + 1 ] = DataUtils.toHalfFloat( y ); + data[ index * CHANNELS + i + 2 ] = DataUtils.toHalfFloat( z ); + data[ index * CHANNELS + i + 3 ] = DataUtils.toHalfFloat( 1 ); + +} + +/** + * Create a new set of uniforms for describing the curve modifier. + * + * @private + * @param {DataTexture} splineTexture - Which holds the curve description. + * @returns {Object} The uniforms object. + */ +function getUniforms( splineTexture ) { + + return { + spineTexture: splineTexture, + pathOffset: 0, // time of path curve + pathSegment: 1, // fractional length of path + spineOffset: 161, + spineLength: 400, + flow: 1, // int + }; + +} + +function modifyShader( material, uniforms, numberOfCurves ) { + + const spineTexture = uniforms.spineTexture; + + const pathOffset = reference( 'pathOffset', 'float', uniforms ); + const pathSegment = reference( 'pathSegment', 'float', uniforms ); + const spineOffset = reference( 'spineOffset', 'float', uniforms ); + const spineLength = reference( 'spineLength', 'float', uniforms ); + const flow = reference( 'flow', 'float', uniforms ); + + material.positionNode = Fn( () => { + + const textureStacks = TEXTURE_HEIGHT / 4; + const textureScale = TEXTURE_HEIGHT * numberOfCurves; + + const worldPos = modelWorldMatrix.mul( vec4( positionLocal, 1 ) ).toVar(); + + const bend = flow.greaterThan( 0 ).toVar(); + const xWeight = select( bend, 0, 1 ).toVar(); + + const spinePortion = select( bend, worldPos.x.add( spineOffset ).div( spineLength ), 0 ); + const mt = spinePortion.mul( pathSegment ).add( pathOffset ).mul( textureStacks ).toVar(); + + mt.assign( mt.mod( textureStacks ) ); + + const rowOffset = mt.floor().toVar(); + + const spinePos = texture( spineTexture, vec2( mt, rowOffset.add( 0.5 ).div( textureScale ) ) ).xyz; + + const a = texture( spineTexture, vec2( mt, rowOffset.add( 1.5 ).div( textureScale ) ) ).xyz; + const b = texture( spineTexture, vec2( mt, rowOffset.add( 2.5 ).div( textureScale ) ) ).xyz; + const c = texture( spineTexture, vec2( mt, rowOffset.add( 3.5 ).div( textureScale ) ) ).xyz; + + const basis = mat3( a, b, c ).toVar(); + + varyingProperty( 'vec3', 'curveNormal' ).assign( basis.mul( normalLocal ) ); + + return basis.mul( vec3( worldPos.x.mul( xWeight ), worldPos.y, worldPos.z ) ).add( spinePos ); + + } )(); + + material.normalNode = varyingProperty( 'vec3', 'curveNormal' ); + +} + +/** + * A modifier for making meshes bend around curves. + * + * This module can only be used with {@link WebGPURenderer}. When using {@link WebGLRenderer}, + * import the class from `CurveModifier.js`. + * + * @three_import import { Flow } from 'three/addons/modifiers/CurveModifierGPU.js'; + */ +export class Flow { + + /** + * Constructs a new Flow instance. + * + * @param {Mesh} mesh - The mesh to clone and modify to bend around the curve. + * @param {number} numberOfCurves - The amount of space that should preallocated for additional curves. + */ + constructor( mesh, numberOfCurves = 1 ) { + + const obj3D = mesh.clone(); + const splineTexture = initSplineTexture( numberOfCurves ); + const uniforms = getUniforms( splineTexture ); + + obj3D.traverse( function ( child ) { + + if ( + child instanceof Mesh || + child instanceof InstancedMesh + ) { + + if ( Array.isArray( child.material ) ) { + + const materials = []; + + for ( const material of child.material ) { + + const newMaterial = material.clone(); + modifyShader( newMaterial, uniforms, numberOfCurves ); + materials.push( newMaterial ); + + } + + child.material = materials; + + } else { + + child.material = child.material.clone(); + modifyShader( child.material, uniforms, numberOfCurves ); + + } + + } + + } ); + + this.curveArray = new Array( numberOfCurves ); + this.curveLengthArray = new Array( numberOfCurves ); + + this.object3D = obj3D; + this.splineTexture = splineTexture; + this.uniforms = uniforms; + + } + + /** + * Updates the curve for the given curve index. + * + * @param {number} index - The curve index. + * @param {Curve} curve - The curve that should be used to bend the mesh. + */ + updateCurve( index, curve ) { + + if ( index >= this.curveArray.length ) throw Error( 'Flow: Index out of range.' ); + + const curveLength = curve.getLength(); + + this.uniforms.spineLength = curveLength; + this.curveLengthArray[ index ] = curveLength; + this.curveArray[ index ] = curve; + + updateSplineTexture( this.splineTexture, curve, index ); + + } + + /** + * Moves the mesh along the curve. + * + * @param {number} amount - The offset. + */ + moveAlongCurve( amount ) { + + this.uniforms.pathOffset += amount; + + } + +} diff --git a/examples/jsm/modifiers/EdgeSplitModifier.js b/examples/jsm/modifiers/EdgeSplitModifier.js index 4ae83add37ff5e..04aeae4a464b4e 100644 --- a/examples/jsm/modifiers/EdgeSplitModifier.js +++ b/examples/jsm/modifiers/EdgeSplitModifier.js @@ -9,8 +9,28 @@ const _A = new Vector3(); const _B = new Vector3(); const _C = new Vector3(); +/** + * The modifier can be used to split faces at sharp edges. This allows to compute + * normals without smoothing the edges which can lead to an improved visual result. + * + * ```js + * const modifier = new EdgeSplitModifier(); + * geometry = modifier.modify( geometry, Math.PI * 0.4 ); + * ``` + * + * @three_import import { EdgeSplitModifier } from 'three/addons/modifiers/EdgeSplitModifier.js'; + */ class EdgeSplitModifier { + /** + * Returns a new, modified version of the given geometry by applying an edge-split operation. + * Please note that the resulting geometry is always indexed. + * + * @param {BufferGeometry} geometry - The geometry to modify. + * @param {number} cutOffAngle - The cut off angle in radians. + * @param {boolean} [tryKeepNormals=true] - Whether to try to keep normals or not. + * @return {BufferGeometry} A new, modified geometry. + */ modify( geometry, cutOffAngle, tryKeepNormals = true ) { function computeNormals() { diff --git a/examples/jsm/modifiers/SimplifyModifier.js b/examples/jsm/modifiers/SimplifyModifier.js index e9e65b82ecd260..54ccf64bb84eab 100644 --- a/examples/jsm/modifiers/SimplifyModifier.js +++ b/examples/jsm/modifiers/SimplifyModifier.js @@ -1,32 +1,53 @@ import { BufferGeometry, + Color, Float32BufferAttribute, - Vector3 + Vector2, + Vector3, + Vector4 } from 'three'; import * as BufferGeometryUtils from '../utils/BufferGeometryUtils.js'; -/** - * Simplification Geometry Modifier - * - based on code and technique - * - by Stan Melax in 1998 - * - Progressive Mesh type Polygon Reduction Algorithm - * - http://www.melax.com/polychop/ - */ - const _cb = new Vector3(), _ab = new Vector3(); +/** + * This class can be used to modify a geometry by simplifying it. A typical use + * case for such a modifier is automatic LOD generation. + * + * The implementation is based on [Progressive Mesh type Polygon Reduction Algorithm]{@link https://web.archive.org/web/20230610044040/http://www.melax.com/polychop/} + * by Stan Melax in 1998. + * + * ```js + * const modifier = new SimplifyModifier(); + * geometry = modifier.modify( geometry ); + * ``` + * + * @three_import import { SimplifyModifier } from 'three/addons/modifiers/SimplifyModifier.js'; + */ class SimplifyModifier { + /** + * Returns a new, modified version of the given geometry by applying a simplification. + * Please note that the resulting geometry is always non-indexed. + * + * @param {BufferGeometry} geometry - The geometry to modify. + * @param {number} count - The number of vertices to remove. + * @return {BufferGeometry} A new, modified geometry. + */ modify( geometry, count ) { geometry = geometry.clone(); + + // currently morphAttributes are not supported + delete geometry.morphAttributes.position; + delete geometry.morphAttributes.normal; const attributes = geometry.attributes; - // this modifier can only process indexed and non-indexed geomtries with a position attribute + // this modifier can only process indexed and non-indexed geometries with at least a position attribute for ( const name in attributes ) { - if ( name !== 'position' ) geometry.deleteAttribute( name ); + if ( name !== 'position' && name !== 'uv' && name !== 'normal' && name !== 'tangent' && name !== 'color' ) geometry.deleteAttribute( name ); } @@ -42,12 +63,44 @@ class SimplifyModifier { // add vertices const positionAttribute = geometry.getAttribute( 'position' ); + const uvAttribute = geometry.getAttribute( 'uv' ); + const normalAttribute = geometry.getAttribute( 'normal' ); + const tangentAttribute = geometry.getAttribute( 'tangent' ); + const colorAttribute = geometry.getAttribute( 'color' ); + + let t = null; + let v2 = null; + let nor = null; + let col = null; for ( let i = 0; i < positionAttribute.count; i ++ ) { const v = new Vector3().fromBufferAttribute( positionAttribute, i ); + if ( uvAttribute ) { + + v2 = new Vector2().fromBufferAttribute( uvAttribute, i ); + + } + + if ( normalAttribute ) { + + nor = new Vector3().fromBufferAttribute( normalAttribute, i ); + + } - const vertex = new Vertex( v ); + if ( tangentAttribute ) { + + t = new Vector4().fromBufferAttribute( tangentAttribute, i ); + + } + + if ( colorAttribute ) { + + col = new Color().fromBufferAttribute( colorAttribute, i ); + + } + + const vertex = new Vertex( v, v2, nor, t, col ); vertices.push( vertex ); } @@ -115,6 +168,10 @@ class SimplifyModifier { const simplifiedGeometry = new BufferGeometry(); const position = []; + const uv = []; + const normal = []; + const tangent = []; + const color = []; index = []; @@ -122,10 +179,35 @@ class SimplifyModifier { for ( let i = 0; i < vertices.length; i ++ ) { - const vertex = vertices[ i ].position; - position.push( vertex.x, vertex.y, vertex.z ); + const vertex = vertices[ i ]; + position.push( vertex.position.x, vertex.position.y, vertex.position.z ); + if ( vertex.uv ) { + + uv.push( vertex.uv.x, vertex.uv.y ); + + } + + if ( vertex.normal ) { + + normal.push( vertex.normal.x, vertex.normal.y, vertex.normal.z ); + + } + + if ( vertex.tangent ) { + + tangent.push( vertex.tangent.x, vertex.tangent.y, vertex.tangent.z, vertex.tangent.w ); + + } + + if ( vertex.color ) { + + color.push( vertex.color.r, vertex.color.g, vertex.color.b ); + + } + + // cache final index to GREATLY speed up faces reconstruction - vertices[ i ].id = i; + vertex.id = i; } @@ -138,9 +220,12 @@ class SimplifyModifier { } - // - simplifiedGeometry.setAttribute( 'position', new Float32BufferAttribute( position, 3 ) ); + if ( uv.length > 0 ) simplifiedGeometry.setAttribute( 'uv', new Float32BufferAttribute( uv, 2 ) ); + if ( normal.length > 0 ) simplifiedGeometry.setAttribute( 'normal', new Float32BufferAttribute( normal, 3 ) ); + if ( tangent.length > 0 ) simplifiedGeometry.setAttribute( 'tangent', new Float32BufferAttribute( tangent, 4 ) ); + if ( color.length > 0 ) simplifiedGeometry.setAttribute( 'color', new Float32BufferAttribute( color, 3 ) ); + simplifiedGeometry.setIndex( index ); return simplifiedGeometry; @@ -318,7 +403,7 @@ function removeFace( f, faces ) { } -function collapse( vertices, faces, u, v ) { // u and v are pointers to vertices of an edge +function collapse( vertices, faces, u, v ) { // Collapse the edge uv by moving vertex u onto v @@ -330,6 +415,24 @@ function collapse( vertices, faces, u, v ) { // u and v are pointers to vertices } + if ( v.uv ) { + + u.uv.copy( v.uv ); + + } + + if ( v.normal ) { + + v.normal.add( u.normal ).normalize(); + + } + + if ( v.tangent ) { + + v.tangent.add( u.tangent ).normalize(); + + } + const tmpVertices = []; for ( let i = 0; i < u.neighbors.length; i ++ ) { @@ -480,9 +583,13 @@ class Triangle { class Vertex { - constructor( v ) { + constructor( v, uv, normal, tangent, color ) { this.position = v; + this.uv = uv; + this.normal = normal; + this.tangent = tangent; + this.color = color; this.id = - 1; // external use position in vertices list (for e.g. face generation) @@ -491,7 +598,7 @@ class Vertex { // these will be computed in computeEdgeCostAtVertex() this.collapseCost = 0; // cost of collapsing this vertex, the less the better. aka objdist - this.collapseNeighbor = null; // best candinate for collapsing + this.collapseNeighbor = null; // best candidate for collapsing } diff --git a/examples/jsm/modifiers/TessellateModifier.js b/examples/jsm/modifiers/TessellateModifier.js index 1197ca98e2485e..47f3bf15659575 100644 --- a/examples/jsm/modifiers/TessellateModifier.js +++ b/examples/jsm/modifiers/TessellateModifier.js @@ -7,18 +7,51 @@ import { } from 'three'; /** - * Break faces with edges longer than maxEdgeLength + * This class can be used to modify a geometry by breaking its edges if they + * are longer than maximum length. + * + * ```js + * const modifier = new TessellateModifier( 8, 6 ); + * geometry = modifier.modify( geometry ); + * ``` + * + * @three_import import { TessellateModifier } from 'three/addons/modifiers/TessellateModifier.js'; */ - class TessellateModifier { + /** + * Constructs a new Tessellate modifier. + * + * @param {number} [maxEdgeLength=0.1] - The maximum edge length. + * @param {number} [maxIterations=6] - The number of iterations. + */ constructor( maxEdgeLength = 0.1, maxIterations = 6 ) { + /** + * The maximum edge length. + * + * @type {number} + * @default 0.1 + */ this.maxEdgeLength = maxEdgeLength; + + /** + * The maximum edge length. + * + * @type {number} + * @default 0.1 + */ this.maxIterations = maxIterations; } + /** + * Returns a new, modified version of the given geometry by applying a tesselation. + * Please note that the resulting geometry is always non-indexed. + * + * @param {BufferGeometry} geometry - The geometry to modify. + * @return {BufferGeometry} A new, modified geometry. + */ modify( geometry ) { if ( geometry.index !== null ) { @@ -111,9 +144,9 @@ class TessellateModifier { const c2 = cs[ b ]; const c3 = cs[ c ]; - colors2.push( c1.x, c1.y, c1.z ); - colors2.push( c2.x, c2.y, c2.z ); - colors2.push( c3.x, c3.y, c3.z ); + colors2.push( c1.r, c1.g, c1.b ); + colors2.push( c2.r, c2.g, c2.b ); + colors2.push( c3.r, c3.g, c3.b ); } diff --git a/examples/jsm/nodes/Nodes.js b/examples/jsm/nodes/Nodes.js deleted file mode 100644 index 45b3c690edd544..00000000000000 --- a/examples/jsm/nodes/Nodes.js +++ /dev/null @@ -1,168 +0,0 @@ -// @TODO: We can simplify "export { default as SomeNode, other, exports } from '...'" to just "export * from '...'" if we will use only named exports -// this will also solve issues like "import TempNode from '../core/Node.js'" - -// constants -export * from './core/constants.js'; - -// core -export { default as ArrayUniformNode /* @TODO: arrayUniform */ } from './core/ArrayUniformNode.js'; -export { default as AttributeNode, attribute } from './core/AttributeNode.js'; -export { default as BypassNode, bypass } from './core/BypassNode.js'; -export { default as CacheNode, cache } from './core/CacheNode.js'; -export { default as ConstNode } from './core/ConstNode.js'; -export { default as ContextNode, context } from './core/ContextNode.js'; -export { default as InstanceIndexNode, instanceIndex } from './core/InstanceIndexNode.js'; -export { default as LightingModel, lightingModel } from './core/LightingModel.js'; -export { default as Node, addNodeClass, createNodeFromType } from './core/Node.js'; -export { default as NodeAttribute } from './core/NodeAttribute.js'; -export { default as NodeBuilder } from './core/NodeBuilder.js'; -export { default as NodeCache } from './core/NodeCache.js'; -export { default as NodeCode } from './core/NodeCode.js'; -export { default as NodeFrame } from './core/NodeFrame.js'; -export { default as NodeFunctionInput } from './core/NodeFunctionInput.js'; -export { default as NodeKeywords } from './core/NodeKeywords.js'; -export { default as NodeUniform } from './core/NodeUniform.js'; -export { default as NodeVar } from './core/NodeVar.js'; -export { default as NodeVarying } from './core/NodeVarying.js'; -export { default as PropertyNode, property, diffuseColor, roughness, metalness, specularColor, shininess } from './core/PropertyNode.js'; -export { default as StackNode, stack } from './core/StackNode.js'; -export { default as TempNode } from './core/TempNode.js'; -export { default as UniformNode, uniform } from './core/UniformNode.js'; -export { default as VarNode, label, temp } from './core/VarNode.js'; -export { default as VaryingNode, varying } from './core/VaryingNode.js'; - -import * as NodeUtils from './core/NodeUtils.js'; -export { NodeUtils }; - -// math -export { default as MathNode, EPSILON, INFINITY, radians, degrees, exp, exp2, log, log2, sqrt, inverseSqrt, floor, ceil, normalize, fract, sin, cos, tan, asin, acos, atan, abs, sign, length, negate, oneMinus, dFdx, dFdy, round, reciprocal, atan2, min, max, mod, step, reflect, distance, difference, dot, cross, pow, pow2, pow3, pow4, transformDirection, mix, clamp, saturate, refract, smoothstep, faceForward } from './math/MathNode.js'; -export { default as OperatorNode, add, sub, mul, div, remainder, equal, assign, lessThan, greaterThan, lessThanEqual, greaterThanEqual, and, or, xor, bitAnd, bitOr, bitXor, shiftLeft, shiftRight } from './math/OperatorNode.js'; -export { default as CondNode, cond } from './math/CondNode.js'; - -// utils -export { default as ArrayElementNode } from './utils/ArrayElementNode.js'; -export { default as ConvertNode } from './utils/ConvertNode.js'; -export { default as DiscardNode, discard } from './utils/DiscardNode.js'; -export { default as EquirectUVNode, equirectUV } from './utils/EquirectUVNode.js'; -export { default as JoinNode } from './utils/JoinNode.js'; -export { default as LoopNode, loop } from './utils/LoopNode.js'; -export { default as MatcapUVNode, matcapUV } from './utils/MatcapUVNode.js'; -export { default as MaxMipLevelNode, maxMipLevel } from './utils/MaxMipLevelNode.js'; -export { default as OscNode, oscSine, oscSquare, oscTriangle, oscSawtooth } from './utils/OscNode.js'; -export { default as PackingNode, directionToColor, colorToDirection } from './utils/PackingNode.js'; -export { default as RemapNode, remap, remapClamp } from './utils/RemapNode.js'; -export { default as RotateUVNode, rotateUV } from './utils/RotateUVNode.js'; -export { default as SpecularMIPLevelNode, specularMIPLevel } from './utils/SpecularMIPLevelNode.js'; -export { default as SplitNode } from './utils/SplitNode.js'; -export { default as SpriteSheetUVNode, spritesheetUV } from './utils/SpriteSheetUVNode.js'; -export { default as TimerNode, timerLocal, timerGlobal, timerDelta, frameId } from './utils/TimerNode.js'; -export { default as TriplanarTexturesNode, triplanarTextures, triplanarTexture } from './utils/TriplanarTexturesNode.js'; - -// shadernode -export * from './shadernode/ShaderNode.js'; - -// accessors -export { default as BitangentNode, bitangentGeometry, bitangentLocal, bitangentView, bitangentWorld, transformedBitangentView, transformedBitangentWorld } from './accessors/BitangentNode.js'; -export { default as BufferAttributeNode, bufferAttribute, dynamicBufferAttribute } from './accessors/BufferAttributeNode.js'; -export { default as BufferNode, buffer } from './accessors/BufferNode.js'; -export { default as CameraNode, cameraProjectionMatrix, cameraViewMatrix, cameraNormalMatrix, cameraWorldMatrix, cameraPosition } from './accessors/CameraNode.js'; -export { default as CubeTextureNode, cubeTexture } from './accessors/CubeTextureNode.js'; -export { default as ExtendedMaterialNode, materialNormal } from './accessors/ExtendedMaterialNode.js'; -export { default as InstanceNode, instance } from './accessors/InstanceNode.js'; -export { default as MaterialNode, materialUV, materialAlphaTest, materialColor, materialShininess, materialEmissive, materialOpacity, materialSpecularColor, materialReflectivity, materialRoughness, materialMetalness, materialRotation } from './accessors/MaterialNode.js'; -export { default as MaterialReferenceNode, materialReference } from './accessors/MaterialReferenceNode.js'; -export { default as ModelNode, modelDirection, modelViewMatrix, modelNormalMatrix, modelWorldMatrix, modelPosition, modelViewPosition } from './accessors/ModelNode.js'; -export { default as ModelViewProjectionNode, modelViewProjection } from './accessors/ModelViewProjectionNode.js'; -export { default as NormalNode, normalGeometry, normalLocal, normalView, normalWorld, transformedNormalView, transformedNormalWorld } from './accessors/NormalNode.js'; -export { default as Object3DNode, objectDirection, objectViewMatrix, objectNormalMatrix, objectWorldMatrix, objectPosition, objectViewPosition } from './accessors/Object3DNode.js'; -export { default as PointUVNode, pointUV } from './accessors/PointUVNode.js'; -export { default as PositionNode, positionGeometry, positionLocal, positionWorld, positionWorldDirection, positionView, positionViewDirection } from './accessors/PositionNode.js'; -export { default as ReferenceNode, reference } from './accessors/ReferenceNode.js'; -export { default as ReflectVectorNode, reflectVector } from './accessors/ReflectVectorNode.js'; -export { default as SkinningNode, skinning } from './accessors/SkinningNode.js'; -export { default as StorageBufferNode, storage } from './accessors/StorageBufferNode.js'; -export { default as TangentNode, tangentGeometry, tangentLocal, tangentView, tangentWorld, transformedTangentView, transformedTangentWorld } from './accessors/TangentNode.js'; -export { default as TextureNode, texture, sampler } from './accessors/TextureNode.js'; -export { default as UVNode, uv } from './accessors/UVNode.js'; -export { default as UserDataNode, userData } from './accessors/UserDataNode.js'; - -// display -export { default as BlendModeNode, burn, dodge, overlay, screen } from './display/BlendModeNode.js'; -export { default as ColorAdjustmentNode, saturation, vibrance, hue, lumaCoeffs, luminance } from './display/ColorAdjustmentNode.js'; -export { default as ColorSpaceNode, colorSpace } from './display/ColorSpaceNode.js'; -export { default as FrontFacingNode, frontFacing, faceDirection } from './display/FrontFacingNode.js'; -export { default as NormalMapNode, normalMap, TBNViewMatrix } from './display/NormalMapNode.js'; -export { default as PosterizeNode, posterize } from './display/PosterizeNode.js'; -export { default as ToneMappingNode, toneMapping } from './display/ToneMappingNode.js'; -export { default as ViewportNode, viewportCoordinate, viewportResolution, viewportTopLeft, viewportBottomLeft, viewportTopRight, viewportBottomRight } from './display/ViewportNode.js'; -export { default as ViewportTextureNode, viewportTexture } from './display/ViewportTextureNode.js'; -export { default as ViewportSharedTextureNode, viewportSharedTexture } from './display/ViewportSharedTextureNode.js'; - -// code -export { default as ExpressionNode, expression } from './code/ExpressionNode.js'; -export { default as CodeNode, code, js } from './code/CodeNode.js'; -export { default as FunctionCallNode, call } from './code/FunctionCallNode.js'; -export { default as FunctionNode, func, fn } from './code/FunctionNode.js'; -export { default as ScriptableNode, scriptable, global } from './code/ScriptableNode.js'; -export { default as ScriptableValueNode, scriptableValue } from './code/ScriptableValueNode.js'; - -// fog -export { default as FogNode, fog } from './fog/FogNode.js'; -export { default as FogRangeNode, rangeFog } from './fog/FogRangeNode.js'; -export { default as FogExp2Node, densityFog } from './fog/FogExp2Node.js'; - -// geometry -export { default as RangeNode, range } from './geometry/RangeNode.js'; - -// gpgpu -export { default as ComputeNode, compute } from './gpgpu/ComputeNode.js'; - -// lighting -export { default as LightNode, lightTargetDirection } from './lighting/LightNode.js'; -export { default as PointLightNode } from './lighting/PointLightNode.js'; -export { default as DirectionalLightNode } from './lighting/DirectionalLightNode.js'; -export { default as SpotLightNode } from './lighting/SpotLightNode.js'; -export { default as IESSpotLightNode } from './lighting/IESSpotLightNode.js'; -export { default as AmbientLightNode } from './lighting/AmbientLightNode.js'; -export { default as LightsNode, lights, lightsWithoutWrap, addLightNode } from './lighting/LightsNode.js'; -export { default as LightingNode /* @TODO: lighting (abstract), light */ } from './lighting/LightingNode.js'; -export { default as LightingContextNode, lightingContext } from './lighting/LightingContextNode.js'; -export { default as HemisphereLightNode } from './lighting/HemisphereLightNode.js'; -export { default as EnvironmentNode } from './lighting/EnvironmentNode.js'; -export { default as AONode } from './lighting/AONode.js'; -export { default as AnalyticLightNode } from './lighting/AnalyticLightNode.js'; - -// procedural -export { default as CheckerNode, checker } from './procedural/CheckerNode.js'; - -// loaders -export { default as NodeLoader } from './loaders/NodeLoader.js'; -export { default as NodeObjectLoader } from './loaders/NodeObjectLoader.js'; -export { default as NodeMaterialLoader } from './loaders/NodeMaterialLoader.js'; - -// parsers -export { default as WGSLNodeParser } from './parsers/WGSLNodeParser.js'; -export { default as GLSLNodeParser } from './parsers/GLSLNodeParser.js'; - -// materials -export * from './materials/Materials.js'; - -// materialX -export * from './materialx/MaterialXNodes.js'; - -// functions -export { default as BRDF_BlinnPhong } from './functions/BSDF/BRDF_BlinnPhong.js'; -export { default as BRDF_GGX } from './functions/BSDF/BRDF_GGX.js'; -export { default as BRDF_Lambert } from './functions/BSDF/BRDF_Lambert.js'; -export { default as D_GGX } from './functions/BSDF/D_GGX.js'; -export { default as DFGApprox } from './functions/BSDF/DFGApprox.js'; -export { default as F_Schlick } from './functions/BSDF/F_Schlick.js'; -export { default as V_GGX_SmithCorrelated } from './functions/BSDF/V_GGX_SmithCorrelated.js'; - -export { getDistanceAttenuation } from './lighting/LightUtils.js'; - -export { default as getGeometryRoughness } from './functions/material/getGeometryRoughness.js'; -export { default as getRoughness } from './functions/material/getRoughness.js'; - -export { default as phongLightingModel } from './functions/PhongLightingModel.js'; -export { default as physicalLightingModel } from './functions/PhysicalLightingModel.js'; diff --git a/examples/jsm/nodes/accessors/BitangentNode.js b/examples/jsm/nodes/accessors/BitangentNode.js deleted file mode 100644 index 3951d83ccd932d..00000000000000 --- a/examples/jsm/nodes/accessors/BitangentNode.js +++ /dev/null @@ -1,89 +0,0 @@ -import Node, { addNodeClass } from '../core/Node.js'; -import { varying } from '../core/VaryingNode.js'; -import { normalize } from '../math/MathNode.js'; -import { cameraViewMatrix } from './CameraNode.js'; -import { normalGeometry, normalLocal, normalView, normalWorld, transformedNormalView } from './NormalNode.js'; -import { tangentGeometry, tangentLocal, tangentView, tangentWorld, transformedTangentView } from './TangentNode.js'; -import { nodeImmutable } from '../shadernode/ShaderNode.js'; - -class BitangentNode extends Node { - - constructor( scope = BitangentNode.LOCAL ) { - - super( 'vec3' ); - - this.scope = scope; - - } - - getHash( /*builder*/ ) { - - return `bitangent-${this.scope}`; - - } - - generate( builder ) { - - const scope = this.scope; - - let crossNormalTangent; - - if ( scope === BitangentNode.GEOMETRY ) { - - crossNormalTangent = normalGeometry.cross( tangentGeometry ); - - } else if ( scope === BitangentNode.LOCAL ) { - - crossNormalTangent = normalLocal.cross( tangentLocal ); - - } else if ( scope === BitangentNode.VIEW ) { - - crossNormalTangent = normalView.cross( tangentView ); - - } else if ( scope === BitangentNode.WORLD ) { - - crossNormalTangent = normalWorld.cross( tangentWorld ); - - } - - const vertexNode = crossNormalTangent.mul( tangentGeometry.w ).xyz; - - const outputNode = normalize( varying( vertexNode ) ); - - return outputNode.build( builder, this.getNodeType( builder ) ); - - } - - serialize( data ) { - - super.serialize( data ); - - data.scope = this.scope; - - } - - deserialize( data ) { - - super.deserialize( data ); - - this.scope = data.scope; - - } - -} - -BitangentNode.GEOMETRY = 'geometry'; -BitangentNode.LOCAL = 'local'; -BitangentNode.VIEW = 'view'; -BitangentNode.WORLD = 'world'; - -export default BitangentNode; - -export const bitangentGeometry = nodeImmutable( BitangentNode, BitangentNode.GEOMETRY ); -export const bitangentLocal = nodeImmutable( BitangentNode, BitangentNode.LOCAL ); -export const bitangentView = nodeImmutable( BitangentNode, BitangentNode.VIEW ); -export const bitangentWorld = nodeImmutable( BitangentNode, BitangentNode.WORLD ); -export const transformedBitangentView = normalize( transformedNormalView.cross( transformedTangentView ).mul( tangentGeometry.w ) ); -export const transformedBitangentWorld = normalize( transformedBitangentView.transformDirection( cameraViewMatrix ) ); - -addNodeClass( BitangentNode ); diff --git a/examples/jsm/nodes/accessors/BufferAttributeNode.js b/examples/jsm/nodes/accessors/BufferAttributeNode.js deleted file mode 100644 index c46f40d38ef0d0..00000000000000 --- a/examples/jsm/nodes/accessors/BufferAttributeNode.js +++ /dev/null @@ -1,86 +0,0 @@ -import InputNode from '../core/InputNode.js'; -import { addNodeClass } from '../core/Node.js'; -import { varying } from '../core/VaryingNode.js'; -import { nodeObject } from '../shadernode/ShaderNode.js'; -import { InterleavedBufferAttribute, InterleavedBuffer, StaticDrawUsage, DynamicDrawUsage } from 'three'; - -class BufferAttributeNode extends InputNode { - - constructor( value, bufferType, bufferStride = 0, bufferOffset = 0 ) { - - super( value, bufferType ); - - this.isBufferNode = true; - - this.bufferType = bufferType; - this.bufferStride = bufferStride; - this.bufferOffset = bufferOffset; - - this.usage = StaticDrawUsage; - - } - - construct( builder ) { - - const type = this.getNodeType( builder ); - const array = this.value; - const itemSize = builder.getTypeLength( type ); - const stride = this.bufferStride || itemSize; - const offset = this.bufferOffset; - - const buffer = new InterleavedBuffer( array, stride ); - const bufferAttribute = new InterleavedBufferAttribute( buffer, itemSize, offset ); - - buffer.setUsage( this.usage ); - - this.attribute = bufferAttribute; - this.attribute.isInstancedBufferAttribute = true; // @TODO: Add a possible: InstancedInterleavedBufferAttribute - - } - - generate( builder ) { - - const nodeType = this.getNodeType( builder ); - - const nodeUniform = builder.getBufferAttributeFromNode( this, nodeType ); - const propertyName = builder.getPropertyName( nodeUniform ); - - let output = null; - - if ( builder.shaderStage === 'vertex' ) { - - output = propertyName; - - } else { - - const nodeVarying = varying( this ); - - output = nodeVarying.build( builder, nodeType ); - - } - - return output; - - } - - getInputType( /*builder*/ ) { - - return 'bufferAttribute'; - - } - -} - -export default BufferAttributeNode; - -export const bufferAttribute = ( array, type, stride, offset ) => nodeObject( new BufferAttributeNode( array, type, stride, offset ) ); -export const dynamicBufferAttribute = ( array, type, stride, offset ) => { - - const node = bufferAttribute( array, type, stride, offset ); - node.usage = DynamicDrawUsage; - - return node; - -}; - -addNodeClass( BufferAttributeNode ); diff --git a/examples/jsm/nodes/accessors/BufferNode.js b/examples/jsm/nodes/accessors/BufferNode.js deleted file mode 100644 index 80416cb4e3ff1a..00000000000000 --- a/examples/jsm/nodes/accessors/BufferNode.js +++ /dev/null @@ -1,30 +0,0 @@ -import UniformNode from '../core/UniformNode.js'; -import { addNodeClass } from '../core/Node.js'; -import { nodeObject } from '../shadernode/ShaderNode.js'; - -class BufferNode extends UniformNode { - - constructor( value, bufferType, bufferCount = 0 ) { - - super( value, bufferType ); - - this.isBufferNode = true; - - this.bufferType = bufferType; - this.bufferCount = bufferCount; - - } - - getInputType( /*builder*/ ) { - - return 'buffer'; - - } - -} - -export default BufferNode; - -export const buffer = ( value, type, count ) => nodeObject( new BufferNode( value, type, count ) ); - -addNodeClass( BufferNode ); diff --git a/examples/jsm/nodes/accessors/CameraNode.js b/examples/jsm/nodes/accessors/CameraNode.js deleted file mode 100644 index 7e41af5a01f62b..00000000000000 --- a/examples/jsm/nodes/accessors/CameraNode.js +++ /dev/null @@ -1,77 +0,0 @@ -import Object3DNode from './Object3DNode.js'; -import { addNodeClass } from '../core/Node.js'; -import { nodeImmutable } from '../shadernode/ShaderNode.js'; - -class CameraNode extends Object3DNode { - - constructor( scope = CameraNode.POSITION ) { - - super( scope ); - - } - - getNodeType( builder ) { - - const scope = this.scope; - - if ( scope === CameraNode.PROJECTION_MATRIX ) { - - return 'mat4'; - - } - - return super.getNodeType( builder ); - - } - - update( frame ) { - - const camera = frame.camera; - const uniformNode = this._uniformNode; - const scope = this.scope; - - if ( scope === CameraNode.PROJECTION_MATRIX ) { - - uniformNode.value = camera.projectionMatrix; - - } else if ( scope === CameraNode.VIEW_MATRIX ) { - - uniformNode.value = camera.matrixWorldInverse; - - } else { - - this.object3d = camera; - - super.update( frame ); - - } - - } - - generate( builder ) { - - const scope = this.scope; - - if ( scope === CameraNode.PROJECTION_MATRIX ) { - - this._uniformNode.nodeType = 'mat4'; - - } - - return super.generate( builder ); - - } - -} - -CameraNode.PROJECTION_MATRIX = 'projectionMatrix'; - -export default CameraNode; - -export const cameraProjectionMatrix = nodeImmutable( CameraNode, CameraNode.PROJECTION_MATRIX ); -export const cameraViewMatrix = nodeImmutable( CameraNode, CameraNode.VIEW_MATRIX ); -export const cameraNormalMatrix = nodeImmutable( CameraNode, CameraNode.NORMAL_MATRIX ); -export const cameraWorldMatrix = nodeImmutable( CameraNode, CameraNode.WORLD_MATRIX ); -export const cameraPosition = nodeImmutable( CameraNode, CameraNode.POSITION ); - -addNodeClass( CameraNode ); diff --git a/examples/jsm/nodes/accessors/CubeTextureNode.js b/examples/jsm/nodes/accessors/CubeTextureNode.js deleted file mode 100644 index 3715111b5c44f9..00000000000000 --- a/examples/jsm/nodes/accessors/CubeTextureNode.js +++ /dev/null @@ -1,101 +0,0 @@ -import TextureNode from './TextureNode.js'; -import UniformNode from '../core/UniformNode.js'; -import { reflectVector } from './ReflectVectorNode.js'; -import { addNodeClass } from '../core/Node.js'; -import { addNodeElement, nodeProxy, vec3 } from '../shadernode/ShaderNode.js'; - -class CubeTextureNode extends TextureNode { - - constructor( value, uvNode = null, levelNode = null ) { - - super( value, uvNode, levelNode ); - - this.isCubeTextureNode = true; - - } - - getInputType( /*builder*/ ) { - - return 'cubeTexture'; - - } - - getDefaultUV() { - - return reflectVector; - - } - - generate( builder, output ) { - - const { uvNode, levelNode } = builder.getNodeProperties( this ); - - const texture = this.value; - - if ( ! texture || texture.isCubeTexture !== true ) { - - throw new Error( 'CubeTextureNode: Need a three.js cube texture.' ); - - } - - const textureProperty = UniformNode.prototype.generate.call( this, builder, 'cubeTexture' ); - - if ( output === 'sampler' ) { - - return textureProperty + '_sampler'; - - } else if ( builder.isReference( output ) ) { - - return textureProperty; - - } else { - - const nodeData = builder.getDataFromNode( this ); - - let propertyName = nodeData.propertyName; - - if ( propertyName === undefined ) { - - const cubeUV = vec3( uvNode.x.negate(), uvNode.yz ); - const uvSnippet = cubeUV.build( builder, 'vec3' ); - - const nodeVar = builder.getVarFromNode( this, 'vec4' ); - - propertyName = builder.getPropertyName( nodeVar ); - - let snippet = null; - - if ( levelNode && levelNode.isNode === true ) { - - const levelSnippet = levelNode.build( builder, 'float' ); - - snippet = builder.getTextureLevel( this, textureProperty, uvSnippet, levelSnippet ); - - } else { - - snippet = builder.getTexture( this, textureProperty, uvSnippet ); - - } - - builder.addLineFlowCode( `${propertyName} = ${snippet}` ); - - nodeData.snippet = snippet; - nodeData.propertyName = propertyName; - - } - - return builder.format( propertyName, 'vec4', output ); - - } - - } - -} - -export default CubeTextureNode; - -export const cubeTexture = nodeProxy( CubeTextureNode ); - -addNodeElement( 'cubeTexture', cubeTexture ); - -addNodeClass( CubeTextureNode ); diff --git a/examples/jsm/nodes/accessors/ExtendedMaterialNode.js b/examples/jsm/nodes/accessors/ExtendedMaterialNode.js deleted file mode 100644 index 647535c9352d69..00000000000000 --- a/examples/jsm/nodes/accessors/ExtendedMaterialNode.js +++ /dev/null @@ -1,58 +0,0 @@ -// @TODO: Is this needed? Can it be moved in MaterialNode? - -import MaterialNode from './MaterialNode.js'; -import { materialReference } from './MaterialReferenceNode.js'; -import { normalView } from './NormalNode.js'; -import { normalMap } from '../display/NormalMapNode.js'; -import { addNodeClass } from '../core/Node.js'; -import { nodeImmutable } from '../shadernode/ShaderNode.js'; - -class ExtendedMaterialNode extends MaterialNode { - - constructor( scope ) { - - super( scope ); - - } - - getNodeType( builder ) { - - const scope = this.scope; - let type = null; - - if ( scope === ExtendedMaterialNode.NORMAL ) { - - type = 'vec3'; - - } - - return type || super.getNodeType( builder ); - - } - - construct( builder ) { - - const material = builder.material; - const scope = this.scope; - - let node = null; - - if ( scope === ExtendedMaterialNode.NORMAL ) { - - node = material.normalMap ? normalMap( this.getTexture( 'normalMap' ), materialReference( 'normalScale', 'vec2' ) ) : normalView; - - } - - return node || super.construct( builder ); - - } - -} - -ExtendedMaterialNode.NORMAL = 'normal'; - -export default ExtendedMaterialNode; - -export const materialNormal = nodeImmutable( ExtendedMaterialNode, ExtendedMaterialNode.NORMAL ); - -addNodeClass( ExtendedMaterialNode ); diff --git a/examples/jsm/nodes/accessors/InstanceNode.js b/examples/jsm/nodes/accessors/InstanceNode.js deleted file mode 100644 index b6e0ed96462c0b..00000000000000 --- a/examples/jsm/nodes/accessors/InstanceNode.js +++ /dev/null @@ -1,73 +0,0 @@ -import Node, { addNodeClass } from '../core/Node.js'; -import { bufferAttribute, dynamicBufferAttribute } from './BufferAttributeNode.js'; -import { normalLocal } from './NormalNode.js'; -import { positionLocal } from './PositionNode.js'; -import { nodeProxy, vec3, mat3, mat4 } from '../shadernode/ShaderNode.js'; -import { DynamicDrawUsage } from 'three'; - -class InstanceNode extends Node { - - constructor( instanceMesh ) { - - super( 'void' ); - - this.instanceMesh = instanceMesh; - - this.instanceMatrixNode = null; - - } - - construct( builder ) { - - let instanceMatrixNode = this.instanceMatrixNode; - - if ( instanceMatrixNode === null ) { - - const instanceMesh = this.instanceMesh; - const instaceAttribute = instanceMesh.instanceMatrix; - const array = instaceAttribute.array; - - const bufferFn = instaceAttribute.usage === DynamicDrawUsage ? dynamicBufferAttribute : bufferAttribute; - - const instanceBuffers = [ - // F.Signature -> bufferAttribute( array, type, stride, offset ) - bufferFn( array, 'vec4', 16, 0 ), - bufferFn( array, 'vec4', 16, 4 ), - bufferFn( array, 'vec4', 16, 8 ), - bufferFn( array, 'vec4', 16, 12 ) - ]; - - instanceMatrixNode = mat4( ...instanceBuffers ); - - this.instanceMatrixNode = instanceMatrixNode; - - } - - // POSITION - - const instancePosition = instanceMatrixNode.mul( positionLocal ).xyz; - - // NORMAL - - const m = mat3( instanceMatrixNode[ 0 ].xyz, instanceMatrixNode[ 1 ].xyz, instanceMatrixNode[ 2 ].xyz ); - - const transformedNormal = normalLocal.div( vec3( m[ 0 ].dot( m[ 0 ] ), m[ 1 ].dot( m[ 1 ] ), m[ 2 ].dot( m[ 2 ] ) ) ); - - const instanceNormal = m.mul( transformedNormal ).xyz; - - // ASSIGNS - - builder.stack.assign( positionLocal, instancePosition ); - builder.stack.assign( normalLocal, instanceNormal ); - - return builder.stack; - - } - -} - -export default InstanceNode; - -export const instance = nodeProxy( InstanceNode ); - -addNodeClass( InstanceNode ); diff --git a/examples/jsm/nodes/accessors/MaterialNode.js b/examples/jsm/nodes/accessors/MaterialNode.js deleted file mode 100644 index 141be9b4dee4be..00000000000000 --- a/examples/jsm/nodes/accessors/MaterialNode.js +++ /dev/null @@ -1,269 +0,0 @@ -import Node, { addNodeClass } from '../core/Node.js'; -import { uniform } from '../core/UniformNode.js'; -import { materialReference } from './MaterialReferenceNode.js'; -import { uv } from './UVNode.js'; -import { nodeImmutable, vec3 } from '../shadernode/ShaderNode.js'; - -class MaterialNode extends Node { - - constructor( scope ) { - - super(); - - this.scope = scope; - - } - - getNodeType( builder ) { - - const scope = this.scope; - const material = builder.context.material; - - if ( scope === MaterialNode.COLOR ) { - - return material.map !== null ? 'vec4' : 'vec3'; - - } else if ( scope === MaterialNode.OPACITY || scope === MaterialNode.ROTATION ) { - - return 'float'; - - } else if ( scope === MaterialNode.UV ) { - - return 'vec2'; - - } else if ( scope === MaterialNode.EMISSIVE ) { - - return 'vec3'; - - } else if ( scope === MaterialNode.ROUGHNESS || scope === MaterialNode.METALNESS || scope === MaterialNode.SPECULAR || scope === MaterialNode.SHININESS ) { - - return 'float'; - - } - - } - - getFloat( property ) { - - //@TODO: Check if it can be cached by property name. - - return materialReference( property, 'float' ); - - } - - getColor( property ) { - - //@TODO: Check if it can be cached by property name. - - return materialReference( property, 'color' ); - - } - - getTexture( property ) { - - //@TODO: Check if it can be cached by property name. - - const textureRefNode = materialReference( property, 'texture' ); - textureRefNode.node.uvNode = materialUV; - - return textureRefNode; - - } - - construct( builder ) { - - const material = builder.context.material; - const scope = this.scope; - - let node = null; - - if ( scope === MaterialNode.ALPHA_TEST ) { - - node = this.getFloat( 'alphaTest' ); - - } else if ( scope === MaterialNode.COLOR ) { - - const colorNode = this.getColor( 'color' ); - - if ( material.map && material.map.isTexture === true ) { - - node = colorNode.mul( this.getTexture( 'map' ) ); - - } else { - - node = colorNode; - - } - - } else if ( scope === MaterialNode.OPACITY ) { - - const opacityNode = this.getFloat( 'opacity' ); - - if ( material.alphaMap && material.alphaMap.isTexture === true ) { - - node = opacityNode.mul( this.getTexture( 'alphaMap' ) ); - - } else { - - node = opacityNode; - - } - - } else if ( scope === MaterialNode.SHININESS ) { - - node = this.getFloat( 'shininess' ); - - } else if ( scope === MaterialNode.SPECULAR_COLOR ) { - - node = this.getColor( 'specular' ); - - } else if ( scope === MaterialNode.REFLECTIVITY ) { - - const reflectivityNode = this.getFloat( 'reflectivity' ); - - if ( material.specularMap && material.specularMap.isTexture === true ) { - - node = reflectivityNode.mul( this.getTexture( 'specularMap' ).r ); - - } else { - - node = reflectivityNode; - - } - - } else if ( scope === MaterialNode.ROUGHNESS ) { - - const roughnessNode = this.getFloat( 'roughness' ); - - if ( material.roughnessMap && material.roughnessMap.isTexture === true ) { - - node = roughnessNode.mul( this.getTexture( 'roughnessMap' ).g ); - - } else { - - node = roughnessNode; - - } - - } else if ( scope === MaterialNode.METALNESS ) { - - const metalnessNode = this.getFloat( 'metalness' ); - - if ( material.metalnessMap && material.metalnessMap.isTexture === true ) { - - node = metalnessNode.mul( this.getTexture( 'metalnessMap' ).b ); - - } else { - - node = metalnessNode; - - } - - } else if ( scope === MaterialNode.EMISSIVE ) { - - const emissiveNode = this.getColor( 'emissive' ); - - if ( material.emissiveMap && material.emissiveMap.isTexture === true ) { - - node = emissiveNode.mul( this.getTexture( 'emissiveMap' ) ); - - } else { - - node = emissiveNode; - - } - - } else if ( scope === MaterialNode.ROTATION ) { - - node = this.getFloat( 'rotation' ); - - } else if ( scope === MaterialNode.UV ) { - - // uv repeat and offset setting priorities - - let uvScaleMap = - material.map || - material.specularMap || - material.displacementMap || - material.normalMap || - material.bumpMap || - material.roughnessMap || - material.metalnessMap || - material.alphaMap || - material.emissiveMap || - material.clearcoatMap || - material.clearcoatNormalMap || - material.clearcoatRoughnessMap || - material.iridescenceMap || - material.iridescenceThicknessMap || - material.specularIntensityMap || - material.specularColorMap || - material.transmissionMap || - material.thicknessMap || - material.sheenColorMap || - material.sheenRoughnessMap; - - if ( uvScaleMap ) { - - // backwards compatibility - if ( uvScaleMap.isWebGLRenderTarget ) { - - uvScaleMap = uvScaleMap.texture; - - } - - if ( uvScaleMap.matrixAutoUpdate === true ) { - - uvScaleMap.updateMatrix(); - - } - - node = uniform( uvScaleMap.matrix ).mul( vec3( uv(), 1 ) ); - - } else { - - node = uv(); - - } - - } else { - - const outputType = this.getNodeType( builder ); - - node = materialReference( scope, outputType ); - - } - - return node; - - } - -} - -MaterialNode.ALPHA_TEST = 'alphaTest'; -MaterialNode.COLOR = 'color'; -MaterialNode.OPACITY = 'opacity'; -MaterialNode.SHININESS = 'shininess'; -MaterialNode.SPECULAR_COLOR = 'specularColor'; -MaterialNode.REFLECTIVITY = 'reflectivity'; -MaterialNode.ROUGHNESS = 'roughness'; -MaterialNode.METALNESS = 'metalness'; -MaterialNode.EMISSIVE = 'emissive'; -MaterialNode.ROTATION = 'rotation'; -MaterialNode.UV = 'uv'; - -export default MaterialNode; - -export const materialUV = nodeImmutable( MaterialNode, MaterialNode.UV ); -export const materialAlphaTest = nodeImmutable( MaterialNode, MaterialNode.ALPHA_TEST ); -export const materialColor = nodeImmutable( MaterialNode, MaterialNode.COLOR ); -export const materialShininess = nodeImmutable( MaterialNode, MaterialNode.SHININESS ); -export const materialEmissive = nodeImmutable( MaterialNode, MaterialNode.EMISSIVE ); -export const materialOpacity = nodeImmutable( MaterialNode, MaterialNode.OPACITY ); -export const materialSpecularColor = nodeImmutable( MaterialNode, MaterialNode.SPECULAR_COLOR ); -export const materialReflectivity = nodeImmutable( MaterialNode, MaterialNode.REFLECTIVITY ); -export const materialRoughness = nodeImmutable( MaterialNode, MaterialNode.ROUGHNESS ); -export const materialMetalness = nodeImmutable( MaterialNode, MaterialNode.METALNESS ); -export const materialRotation = nodeImmutable( MaterialNode, MaterialNode.ROTATION ); - -addNodeClass( MaterialNode ); diff --git a/examples/jsm/nodes/accessors/MaterialReferenceNode.js b/examples/jsm/nodes/accessors/MaterialReferenceNode.js deleted file mode 100644 index 0e6cf712db4fb2..00000000000000 --- a/examples/jsm/nodes/accessors/MaterialReferenceNode.js +++ /dev/null @@ -1,39 +0,0 @@ -import ReferenceNode from './ReferenceNode.js'; -import { addNodeClass } from '../core/Node.js'; -import { nodeObject } from '../shadernode/ShaderNode.js'; - -class MaterialReferenceNode extends ReferenceNode { - - constructor( property, inputType, material = null ) { - - super( property, inputType, material ); - - this.material = material; - - } - - construct( builder ) { - - const material = this.material !== null ? this.material : builder.material; - - this.node.value = material[ this.property ]; - - return super.construct( builder ); - - } - - update( frame ) { - - this.object = this.material !== null ? this.material : frame.material; - - super.update( frame ); - - } - -} - -export default MaterialReferenceNode; - -export const materialReference = ( name, type, material ) => nodeObject( new MaterialReferenceNode( name, type, material ) ); - -addNodeClass( MaterialReferenceNode ); diff --git a/examples/jsm/nodes/accessors/ModelNode.js b/examples/jsm/nodes/accessors/ModelNode.js deleted file mode 100644 index d63adbd57b1242..00000000000000 --- a/examples/jsm/nodes/accessors/ModelNode.js +++ /dev/null @@ -1,32 +0,0 @@ -import Object3DNode from './Object3DNode.js'; -import { addNodeClass } from '../core/Node.js'; -import { nodeImmutable } from '../shadernode/ShaderNode.js'; - -class ModelNode extends Object3DNode { - - constructor( scope = ModelNode.VIEW_MATRIX ) { - - super( scope ); - - } - - update( frame ) { - - this.object3d = frame.object; - - super.update( frame ); - - } - -} - -export default ModelNode; - -export const modelDirection = nodeImmutable( ModelNode, ModelNode.DIRECTION ); -export const modelViewMatrix = nodeImmutable( ModelNode, ModelNode.VIEW_MATRIX ); -export const modelNormalMatrix = nodeImmutable( ModelNode, ModelNode.NORMAL_MATRIX ); -export const modelWorldMatrix = nodeImmutable( ModelNode, ModelNode.WORLD_MATRIX ); -export const modelPosition = nodeImmutable( ModelNode, ModelNode.POSITION ); -export const modelViewPosition = nodeImmutable( ModelNode, ModelNode.VIEW_POSITION ); - -addNodeClass( ModelNode ); diff --git a/examples/jsm/nodes/accessors/ModelViewProjectionNode.js b/examples/jsm/nodes/accessors/ModelViewProjectionNode.js deleted file mode 100644 index 448eb414980688..00000000000000 --- a/examples/jsm/nodes/accessors/ModelViewProjectionNode.js +++ /dev/null @@ -1,29 +0,0 @@ -import Node, { addNodeClass } from '../core/Node.js'; -import { cameraProjectionMatrix } from './CameraNode.js'; -import { modelViewMatrix } from './ModelNode.js'; -import { positionLocal } from './PositionNode.js'; -import { nodeProxy } from '../shadernode/ShaderNode.js'; - -class ModelViewProjectionNode extends Node { - - constructor( positionNode = positionLocal ) { - - super( 'vec4' ); - - this.positionNode = positionNode; - - } - - construct() { - - return cameraProjectionMatrix.mul( modelViewMatrix ).mul( this.positionNode ); - - } - -} - -export default ModelViewProjectionNode; - -export const modelViewProjection = nodeProxy( ModelViewProjectionNode ); - -addNodeClass( ModelViewProjectionNode ); diff --git a/examples/jsm/nodes/accessors/NormalNode.js b/examples/jsm/nodes/accessors/NormalNode.js deleted file mode 100644 index 6c2221e7f560ec..00000000000000 --- a/examples/jsm/nodes/accessors/NormalNode.js +++ /dev/null @@ -1,95 +0,0 @@ -import Node, { addNodeClass } from '../core/Node.js'; -import { attribute } from '../core/AttributeNode.js'; -import { label } from '../core/VarNode.js'; -import { varying } from '../core/VaryingNode.js'; -import { normalize } from '../math/MathNode.js'; -import { cameraViewMatrix } from './CameraNode.js'; -import { modelNormalMatrix } from './ModelNode.js'; -import { nodeImmutable } from '../shadernode/ShaderNode.js'; - -class NormalNode extends Node { - - constructor( scope = NormalNode.LOCAL ) { - - super( 'vec3' ); - - this.scope = scope; - - } - - isGlobal() { - - return true; - - } - - getHash( /*builder*/ ) { - - return `normal-${this.scope}`; - - } - - generate( builder ) { - - const scope = this.scope; - - let outputNode = null; - - if ( scope === NormalNode.GEOMETRY ) { - - outputNode = attribute( 'normal', 'vec3' ); - - } else if ( scope === NormalNode.LOCAL ) { - - outputNode = varying( normalGeometry ); - - } else if ( scope === NormalNode.VIEW ) { - - const vertexNode = modelNormalMatrix.mul( normalLocal ); - outputNode = normalize( varying( vertexNode ) ); - - } else if ( scope === NormalNode.WORLD ) { - - // To use inverseTransformDirection only inverse the param order like this: cameraViewMatrix.transformDirection( normalView ) - const vertexNode = normalView.transformDirection( cameraViewMatrix ); - outputNode = normalize( varying( vertexNode ) ); - - } - - return outputNode.build( builder, this.getNodeType( builder ) ); - - } - - serialize( data ) { - - super.serialize( data ); - - data.scope = this.scope; - - } - - deserialize( data ) { - - super.deserialize( data ); - - this.scope = data.scope; - - } - -} - -NormalNode.GEOMETRY = 'geometry'; -NormalNode.LOCAL = 'local'; -NormalNode.VIEW = 'view'; -NormalNode.WORLD = 'world'; - -export default NormalNode; - -export const normalGeometry = nodeImmutable( NormalNode, NormalNode.GEOMETRY ); -export const normalLocal = nodeImmutable( NormalNode, NormalNode.LOCAL ); -export const normalView = nodeImmutable( NormalNode, NormalNode.VIEW ); -export const normalWorld = nodeImmutable( NormalNode, NormalNode.WORLD ); -export const transformedNormalView = label( normalView, 'TransformedNormalView' ); -export const transformedNormalWorld = transformedNormalView.transformDirection( cameraViewMatrix ).normalize(); - -addNodeClass( NormalNode ); diff --git a/examples/jsm/nodes/accessors/Object3DNode.js b/examples/jsm/nodes/accessors/Object3DNode.js deleted file mode 100644 index 516fc32a8d82bb..00000000000000 --- a/examples/jsm/nodes/accessors/Object3DNode.js +++ /dev/null @@ -1,142 +0,0 @@ -import Node, { addNodeClass } from '../core/Node.js'; -import { NodeUpdateType } from '../core/constants.js'; -import { uniform } from '../core/UniformNode.js'; -import { nodeProxy } from '../shadernode/ShaderNode.js'; - -import { Vector3 } from 'three'; - -class Object3DNode extends Node { - - constructor( scope = Object3DNode.VIEW_MATRIX, object3d = null ) { - - super(); - - this.scope = scope; - this.object3d = object3d; - - this.updateType = NodeUpdateType.OBJECT; - - this._uniformNode = uniform( null ); - - } - - getNodeType() { - - const scope = this.scope; - - if ( scope === Object3DNode.WORLD_MATRIX || scope === Object3DNode.VIEW_MATRIX ) { - - return 'mat4'; - - } else if ( scope === Object3DNode.NORMAL_MATRIX ) { - - return 'mat3'; - - } else if ( scope === Object3DNode.POSITION || scope === Object3DNode.VIEW_POSITION || scope === Object3DNode.DIRECTION ) { - - return 'vec3'; - - } - - } - - update( frame ) { - - const object = this.object3d; - const uniformNode = this._uniformNode; - const scope = this.scope; - - if ( scope === Object3DNode.VIEW_MATRIX ) { - - uniformNode.value = object.modelViewMatrix; - - } else if ( scope === Object3DNode.NORMAL_MATRIX ) { - - uniformNode.value = object.normalMatrix; - - } else if ( scope === Object3DNode.WORLD_MATRIX ) { - - uniformNode.value = object.matrixWorld; - - } else if ( scope === Object3DNode.POSITION ) { - - uniformNode.value = uniformNode.value || new Vector3(); - - uniformNode.value.setFromMatrixPosition( object.matrixWorld ); - - } else if ( scope === Object3DNode.DIRECTION ) { - - uniformNode.value = uniformNode.value || new Vector3(); - - object.getWorldDirection( uniformNode.value ); - - } else if ( scope === Object3DNode.VIEW_POSITION ) { - - const camera = frame.camera; - - uniformNode.value = uniformNode.value || new Vector3(); - uniformNode.value.setFromMatrixPosition( object.matrixWorld ); - - uniformNode.value.applyMatrix4( camera.matrixWorldInverse ); - - } - - } - - generate( builder ) { - - const scope = this.scope; - - if ( scope === Object3DNode.WORLD_MATRIX || scope === Object3DNode.VIEW_MATRIX ) { - - this._uniformNode.nodeType = 'mat4'; - - } else if ( scope === Object3DNode.NORMAL_MATRIX ) { - - this._uniformNode.nodeType = 'mat3'; - - } else if ( scope === Object3DNode.POSITION || scope === Object3DNode.VIEW_POSITION || scope === Object3DNode.DIRECTION ) { - - this._uniformNode.nodeType = 'vec3'; - - } - - return this._uniformNode.build( builder ); - - } - - serialize( data ) { - - super.serialize( data ); - - data.scope = this.scope; - - } - - deserialize( data ) { - - super.deserialize( data ); - - this.scope = data.scope; - - } - -} - -Object3DNode.VIEW_MATRIX = 'viewMatrix'; -Object3DNode.NORMAL_MATRIX = 'normalMatrix'; -Object3DNode.WORLD_MATRIX = 'worldMatrix'; -Object3DNode.POSITION = 'position'; -Object3DNode.VIEW_POSITION = 'viewPosition'; -Object3DNode.DIRECTION = 'direction'; - -export default Object3DNode; - -export const objectDirection = nodeProxy( Object3DNode, Object3DNode.DIRECTION ); -export const objectViewMatrix = nodeProxy( Object3DNode, Object3DNode.VIEW_MATRIX ); -export const objectNormalMatrix = nodeProxy( Object3DNode, Object3DNode.NORMAL_MATRIX ); -export const objectWorldMatrix = nodeProxy( Object3DNode, Object3DNode.WORLD_MATRIX ); -export const objectPosition = nodeProxy( Object3DNode, Object3DNode.POSITION ); -export const objectViewPosition = nodeProxy( Object3DNode, Object3DNode.VIEW_POSITION ); - -addNodeClass( Object3DNode ); diff --git a/examples/jsm/nodes/accessors/PointUVNode.js b/examples/jsm/nodes/accessors/PointUVNode.js deleted file mode 100644 index f270b6c4b7aeef..00000000000000 --- a/examples/jsm/nodes/accessors/PointUVNode.js +++ /dev/null @@ -1,26 +0,0 @@ -import Node, { addNodeClass } from '../core/Node.js'; -import { nodeImmutable } from '../shadernode/ShaderNode.js'; - -class PointUVNode extends Node { - - constructor() { - - super( 'vec2' ); - - this.isPointUVNode = true; - - } - - generate( /*builder*/ ) { - - return 'vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y )'; - - } - -} - -export default PointUVNode; - -export const pointUV = nodeImmutable( PointUVNode ); - -addNodeClass( PointUVNode ); diff --git a/examples/jsm/nodes/accessors/PositionNode.js b/examples/jsm/nodes/accessors/PositionNode.js deleted file mode 100644 index 820c6e81552a74..00000000000000 --- a/examples/jsm/nodes/accessors/PositionNode.js +++ /dev/null @@ -1,104 +0,0 @@ -import Node, { addNodeClass } from '../core/Node.js'; -import { attribute } from '../core/AttributeNode.js'; -import { varying } from '../core/VaryingNode.js'; -import { normalize } from '../math/MathNode.js'; -import { modelWorldMatrix, modelViewMatrix } from './ModelNode.js'; -import { nodeImmutable } from '../shadernode/ShaderNode.js'; - -class PositionNode extends Node { - - constructor( scope = PositionNode.LOCAL ) { - - super( 'vec3' ); - - this.scope = scope; - - } - - isGlobal() { - - return true; - - } - - getHash( /*builder*/ ) { - - return `position-${this.scope}`; - - } - - generate( builder ) { - - const scope = this.scope; - - let outputNode = null; - - if ( scope === PositionNode.GEOMETRY ) { - - outputNode = attribute( 'position', 'vec3' ); - - } else if ( scope === PositionNode.LOCAL ) { - - outputNode = varying( positionGeometry ); - - } else if ( scope === PositionNode.WORLD ) { - - const vertexPositionNode = modelWorldMatrix.mul( positionLocal ); - outputNode = varying( vertexPositionNode ); - - } else if ( scope === PositionNode.VIEW ) { - - const vertexPositionNode = modelViewMatrix.mul( positionLocal ); - outputNode = varying( vertexPositionNode ); - - } else if ( scope === PositionNode.VIEW_DIRECTION ) { - - const vertexPositionNode = positionView.negate(); - outputNode = normalize( varying( vertexPositionNode ) ); - - } else if ( scope === PositionNode.WORLD_DIRECTION ) { - - const vertexPositionNode = positionLocal.transformDirection( modelWorldMatrix ); - outputNode = normalize( varying( vertexPositionNode ) ); - - } - - return outputNode.build( builder, this.getNodeType( builder ) ); - - } - - serialize( data ) { - - super.serialize( data ); - - data.scope = this.scope; - - } - - deserialize( data ) { - - super.deserialize( data ); - - this.scope = data.scope; - - } - -} - -PositionNode.GEOMETRY = 'geometry'; -PositionNode.LOCAL = 'local'; -PositionNode.WORLD = 'world'; -PositionNode.WORLD_DIRECTION = 'worldDirection'; -PositionNode.VIEW = 'view'; -PositionNode.VIEW_DIRECTION = 'viewDirection'; - -export default PositionNode; - -export const positionGeometry = nodeImmutable( PositionNode, PositionNode.GEOMETRY ); -export const positionLocal = nodeImmutable( PositionNode, PositionNode.LOCAL ); -export const positionWorld = nodeImmutable( PositionNode, PositionNode.WORLD ); -export const positionWorldDirection = nodeImmutable( PositionNode, PositionNode.WORLD_DIRECTION ); -export const positionView = nodeImmutable( PositionNode, PositionNode.VIEW ); -export const positionViewDirection = nodeImmutable( PositionNode, PositionNode.VIEW_DIRECTION ); - -addNodeClass( PositionNode ); diff --git a/examples/jsm/nodes/accessors/ReferenceNode.js b/examples/jsm/nodes/accessors/ReferenceNode.js deleted file mode 100644 index 6ff591a36a44d6..00000000000000 --- a/examples/jsm/nodes/accessors/ReferenceNode.js +++ /dev/null @@ -1,72 +0,0 @@ -import Node, { addNodeClass } from '../core/Node.js'; -import { NodeUpdateType } from '../core/constants.js'; -import { uniform } from '../core/UniformNode.js'; -import { texture } from './TextureNode.js'; -import { nodeObject } from '../shadernode/ShaderNode.js'; - -class ReferenceNode extends Node { - - constructor( property, uniformType, object = null ) { - - super(); - - this.property = property; - - this.uniformType = uniformType; - - this.object = object; - - this.node = null; - - this.updateType = NodeUpdateType.OBJECT; - - this.setNodeType( uniformType ); - - } - - setNodeType( uniformType ) { - - let node = null; - - if ( uniformType === 'texture' ) { - - node = texture( null ); - - } else { - - node = uniform( uniformType ); - - } - - this.node = node; - - } - - getNodeType( builder ) { - - return this.node.getNodeType( builder ); - - } - - update( frame ) { - - const object = this.object !== null ? this.object : frame.object; - const property = this.property; - - this.node.value = object[ property ]; - - } - - construct( /*builder*/ ) { - - return this.node; - - } - -} - -export default ReferenceNode; - -export const reference = ( name, type, object ) => nodeObject( new ReferenceNode( name, type, object ) ); - -addNodeClass( ReferenceNode ); diff --git a/examples/jsm/nodes/accessors/ReflectVectorNode.js b/examples/jsm/nodes/accessors/ReflectVectorNode.js deleted file mode 100644 index 508d907df72eda..00000000000000 --- a/examples/jsm/nodes/accessors/ReflectVectorNode.js +++ /dev/null @@ -1,35 +0,0 @@ -import Node, { addNodeClass } from '../core/Node.js'; -import { cameraViewMatrix } from './CameraNode.js'; -import { transformedNormalView } from './NormalNode.js'; -import { positionViewDirection } from './PositionNode.js'; -import { nodeImmutable } from '../shadernode/ShaderNode.js'; - -class ReflectVectorNode extends Node { - - constructor() { - - super( 'vec3' ); - - } - - getHash( /*builder*/ ) { - - return 'reflectVector'; - - } - - construct() { - - const reflectView = positionViewDirection.negate().reflect( transformedNormalView ); - - return reflectView.transformDirection( cameraViewMatrix ); - - } - -} - -export default ReflectVectorNode; - -export const reflectVector = nodeImmutable( ReflectVectorNode ); - -addNodeClass( ReflectVectorNode ); diff --git a/examples/jsm/nodes/accessors/SkinningNode.js b/examples/jsm/nodes/accessors/SkinningNode.js deleted file mode 100644 index b53fc53fe3491a..00000000000000 --- a/examples/jsm/nodes/accessors/SkinningNode.js +++ /dev/null @@ -1,112 +0,0 @@ -import Node, { addNodeClass } from '../core/Node.js'; -import { NodeUpdateType } from '../core/constants.js'; -import { ShaderNode, nodeProxy } from '../shadernode/ShaderNode.js'; -import { attribute } from '../core/AttributeNode.js'; -import { uniform } from '../core/UniformNode.js'; -import { add } from '../math/OperatorNode.js'; -import { buffer } from './BufferNode.js'; -import { normalLocal } from './NormalNode.js'; -import { positionLocal } from './PositionNode.js'; -import { tangentLocal } from './TangentNode.js'; - -const Skinning = new ShaderNode( ( inputs, {}, builder ) => { - - const { index, weight, bindMatrix, bindMatrixInverse, boneMatrices } = inputs; - - const boneMatX = boneMatrices.element( index.x ); - const boneMatY = boneMatrices.element( index.y ); - const boneMatZ = boneMatrices.element( index.z ); - const boneMatW = boneMatrices.element( index.w ); - - // POSITION - - const skinVertex = bindMatrix.mul( positionLocal ); - - const skinned = add( - boneMatX.mul( weight.x ).mul( skinVertex ), - boneMatY.mul( weight.y ).mul( skinVertex ), - boneMatZ.mul( weight.z ).mul( skinVertex ), - boneMatW.mul( weight.w ).mul( skinVertex ) - ); - - const skinPosition = bindMatrixInverse.mul( skinned ).xyz; - - // NORMAL - - let skinMatrix = add( - weight.x.mul( boneMatX ), - weight.y.mul( boneMatY ), - weight.z.mul( boneMatZ ), - weight.w.mul( boneMatW ) - ); - - skinMatrix = bindMatrixInverse.mul( skinMatrix ).mul( bindMatrix ); - - const skinNormal = skinMatrix.transformDirection( normalLocal ).xyz; - - // ASSIGNS - - positionLocal.assign( skinPosition ).build( builder ); // @TODO: For some reason this doesn't work as stack.assign( positionLocal, skinPosition )? - normalLocal.assign( skinNormal ).build( builder ); - - if ( builder.hasGeometryAttribute( 'tangent' ) ) { - - tangentLocal.assign( skinNormal ).build( builder ); - - } - -} ); - -class SkinningNode extends Node { - - constructor( skinnedMesh ) { - - super( 'void' ); - - this.skinnedMesh = skinnedMesh; - - this.updateType = NodeUpdateType.OBJECT; - - // - - this.skinIndexNode = attribute( 'skinIndex', 'uvec4' ); - this.skinWeightNode = attribute( 'skinWeight', 'vec4' ); - - this.bindMatrixNode = uniform( skinnedMesh.bindMatrix, 'mat4' ); - this.bindMatrixInverseNode = uniform( skinnedMesh.bindMatrixInverse, 'mat4' ); - this.boneMatricesNode = buffer( skinnedMesh.skeleton.boneMatrices, 'mat4', skinnedMesh.skeleton.bones.length ); - - } - - generate( builder ) { - - /*return new ShaderNode( ( {}, stack, builder ) => Skinning.call( { - index: this.skinIndexNode, - weight: this.skinWeightNode, - bindMatrix: this.bindMatrixNode, - bindMatrixInverse: this.bindMatrixInverseNode, - boneMatrices: this.boneMatricesNode - }, stack, builder ) ).build( builder );*/ - Skinning.call( { - index: this.skinIndexNode, - weight: this.skinWeightNode, - bindMatrix: this.bindMatrixNode, - bindMatrixInverse: this.bindMatrixInverseNode, - boneMatrices: this.boneMatricesNode - }, {}, builder ); - - } - - update() { - - this.skinnedMesh.skeleton.update(); - - } - -} - -export default SkinningNode; - -export const skinning = nodeProxy( SkinningNode ); - -addNodeClass( SkinningNode ); diff --git a/examples/jsm/nodes/accessors/StorageBufferNode.js b/examples/jsm/nodes/accessors/StorageBufferNode.js deleted file mode 100644 index 7b5053493860ab..00000000000000 --- a/examples/jsm/nodes/accessors/StorageBufferNode.js +++ /dev/null @@ -1,27 +0,0 @@ -import BufferNode from './BufferNode.js'; -import { addNodeClass } from '../core/Node.js'; -import { nodeObject } from '../shadernode/ShaderNode.js'; - -class StorageBufferNode extends BufferNode { - - constructor( value, bufferType, bufferCount = 0 ) { - - super( value, bufferType, bufferCount ); - - this.isStorageBufferNode = true; - - } - - getInputType( /*builder*/ ) { - - return 'storageBuffer'; - - } - -} - -export default StorageBufferNode; - -export const storage = ( value, type, count ) => nodeObject( new StorageBufferNode( value, type, count ) ); - -addNodeClass( StorageBufferNode ); diff --git a/examples/jsm/nodes/accessors/TangentNode.js b/examples/jsm/nodes/accessors/TangentNode.js deleted file mode 100644 index 1240a6b2dc46c1..00000000000000 --- a/examples/jsm/nodes/accessors/TangentNode.js +++ /dev/null @@ -1,103 +0,0 @@ -import Node, { addNodeClass } from '../core/Node.js'; -import { attribute } from '../core/AttributeNode.js'; -import { label } from '../core/VarNode.js'; -import { varying } from '../core/VaryingNode.js'; -import { normalize } from '../math/MathNode.js'; -import { cameraViewMatrix } from './CameraNode.js'; -import { modelViewMatrix } from './ModelNode.js'; -import { nodeImmutable } from '../shadernode/ShaderNode.js'; - -class TangentNode extends Node { - - constructor( scope = TangentNode.LOCAL ) { - - super(); - - this.scope = scope; - - } - - getHash( /*builder*/ ) { - - return `tangent-${this.scope}`; - - } - - getNodeType() { - - const scope = this.scope; - - if ( scope === TangentNode.GEOMETRY ) { - - return 'vec4'; - - } - - return 'vec3'; - - } - - - generate( builder ) { - - const scope = this.scope; - - let outputNode = null; - - if ( scope === TangentNode.GEOMETRY ) { - - outputNode = attribute( 'tangent', 'vec4' ); - - } else if ( scope === TangentNode.LOCAL ) { - - outputNode = varying( tangentGeometry.xyz ); - - } else if ( scope === TangentNode.VIEW ) { - - const vertexNode = modelViewMatrix.mul( tangentLocal ).xyz; - outputNode = normalize( varying( vertexNode ) ); - - } else if ( scope === TangentNode.WORLD ) { - - const vertexNode = tangentView.transformDirection( cameraViewMatrix ); - outputNode = normalize( varying( vertexNode ) ); - - } - - return outputNode.build( builder, this.getNodeType( builder ) ); - - } - - serialize( data ) { - - super.serialize( data ); - - data.scope = this.scope; - - } - - deserialize( data ) { - - super.deserialize( data ); - - this.scope = data.scope; - - } - -} - -TangentNode.GEOMETRY = 'geometry'; -TangentNode.LOCAL = 'local'; -TangentNode.VIEW = 'view'; -TangentNode.WORLD = 'world'; - -export default TangentNode; - -export const tangentGeometry = nodeImmutable( TangentNode, TangentNode.GEOMETRY ); -export const tangentLocal = nodeImmutable( TangentNode, TangentNode.LOCAL ); -export const tangentView = nodeImmutable( TangentNode, TangentNode.VIEW ); -export const tangentWorld = nodeImmutable( TangentNode, TangentNode.WORLD ); -export const transformedTangentView = label( tangentView, 'TransformedTangentView' ); -export const transformedTangentWorld = normalize( transformedTangentView.transformDirection( cameraViewMatrix ) ); - -addNodeClass( TangentNode ); diff --git a/examples/jsm/nodes/accessors/TextureNode.js b/examples/jsm/nodes/accessors/TextureNode.js deleted file mode 100644 index fce9e2f6fcfcdb..00000000000000 --- a/examples/jsm/nodes/accessors/TextureNode.js +++ /dev/null @@ -1,168 +0,0 @@ -import UniformNode from '../core/UniformNode.js'; -import { uv } from './UVNode.js'; -import { addNodeClass } from '../core/Node.js'; -import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js'; - -let defaultUV; - -class TextureNode extends UniformNode { - - constructor( value, uvNode = null, levelNode = null ) { - - super( value ); - - this.isTextureNode = true; - - this.uvNode = uvNode; - this.levelNode = levelNode; - - } - - getUniformHash( /*builder*/ ) { - - return this.value.uuid; - - } - - getNodeType( /*builder*/ ) { - - if ( this.value.isDepthTexture === true ) return 'float'; - - return 'vec4'; - - } - - getInputType( /*builder*/ ) { - - return 'texture'; - - } - - getDefaultUV() { - - return defaultUV || ( defaultUV = uv() ); - - } - - construct( builder ) { - - const properties = builder.getNodeProperties( this ); - - // - - let uvNode = this.uvNode; - - if ( uvNode === null && builder.context.getUVNode ) { - - uvNode = builder.context.getUVNode( this ); - - } - - uvNode || ( uvNode = this.getDefaultUV() ); - - // - - let levelNode = this.levelNode; - - if ( levelNode === null && builder.context.getSamplerLevelNode ) { - - levelNode = builder.context.getSamplerLevelNode( this ); - - } - - // - - properties.uvNode = uvNode; - properties.levelNode = levelNode ? builder.context.getMIPLevelAlgorithmNode( this, levelNode ) : null; - - } - - generate( builder, output ) { - - const { uvNode, levelNode } = builder.getNodeProperties( this ); - - const texture = this.value; - - if ( ! texture || texture.isTexture !== true ) { - - throw new Error( 'TextureNode: Need a three.js texture.' ); - - } - - const textureProperty = super.generate( builder, 'property' ); - - if ( output === 'sampler' ) { - - return textureProperty + '_sampler'; - - } else if ( builder.isReference( output ) ) { - - return textureProperty; - - } else { - - const nodeType = this.getNodeType( builder ); - const nodeData = builder.getDataFromNode( this ); - - let propertyName = nodeData.propertyName; - - if ( propertyName === undefined ) { - - const uvSnippet = uvNode.build( builder, 'vec2' ); - const nodeVar = builder.getVarFromNode( this, nodeType ); - - propertyName = builder.getPropertyName( nodeVar ); - - let snippet = null; - - if ( levelNode && levelNode.isNode === true ) { - - const levelSnippet = levelNode.build( builder, 'float' ); - - snippet = builder.getTextureLevel( texture, textureProperty, uvSnippet, levelSnippet ); - - } else { - - snippet = builder.getTexture( texture, textureProperty, uvSnippet ); - - } - - builder.addLineFlowCode( `${propertyName} = ${snippet}` ); - - nodeData.snippet = snippet; - nodeData.propertyName = propertyName; - - } - - return builder.format( propertyName, nodeType, output ); - - } - - } - - serialize( data ) { - - super.serialize( data ); - - data.value = this.value.toJSON( data.meta ).uuid; - - } - - deserialize( data ) { - - super.deserialize( data ); - - this.value = data.meta.textures[ data.value ]; - - } - -} - -export default TextureNode; - -export const texture = nodeProxy( TextureNode ); -export const sampler = ( aTexture ) => ( aTexture.isNode === true ? aTexture : texture( aTexture ) ).convert( 'sampler' ); - -addNodeElement( 'texture', texture ); - -addNodeClass( TextureNode ); diff --git a/examples/jsm/nodes/accessors/UVNode.js b/examples/jsm/nodes/accessors/UVNode.js deleted file mode 100644 index 63c5d943545948..00000000000000 --- a/examples/jsm/nodes/accessors/UVNode.js +++ /dev/null @@ -1,47 +0,0 @@ -import { addNodeClass } from '../core/Node.js'; -import AttributeNode from '../core/AttributeNode.js'; -import { nodeObject } from '../shadernode/ShaderNode.js'; - -class UVNode extends AttributeNode { - - constructor( index = 0 ) { - - super( null, 'vec2' ); - - this.isUVNode = true; - - this.index = index; - - } - - getAttributeName( /*builder*/ ) { - - const index = this.index; - - return 'uv' + ( index > 0 ? index + 1 : '' ); - - } - - serialize( data ) { - - super.serialize( data ); - - data.index = this.index; - - } - - deserialize( data ) { - - super.deserialize( data ); - - this.index = data.index; - - } - -} - -export default UVNode; - -export const uv = ( ...params ) => nodeObject( new UVNode( ...params ) ); - -addNodeClass( UVNode ); diff --git a/examples/jsm/nodes/accessors/UserDataNode.js b/examples/jsm/nodes/accessors/UserDataNode.js deleted file mode 100644 index 5295676389bfab..00000000000000 --- a/examples/jsm/nodes/accessors/UserDataNode.js +++ /dev/null @@ -1,29 +0,0 @@ -import ReferenceNode from './ReferenceNode.js'; -import { addNodeClass } from '../core/Node.js'; -import { nodeObject } from '../shadernode/ShaderNode.js'; - -class UserDataNode extends ReferenceNode { - - constructor( property, inputType, userData = null ) { - - super( property, inputType, userData ); - - this.userData = userData; - - } - - update( frame ) { - - this.object = this.userData !== null ? this.userData : frame.object.userData; - - super.update( frame ); - - } - -} - -export default UserDataNode; - -export const userData = ( name, inputType, userData ) => nodeObject( new UserDataNode( name, inputType, userData ) ); - -addNodeClass( UserDataNode ); diff --git a/examples/jsm/nodes/code/CodeNode.js b/examples/jsm/nodes/code/CodeNode.js deleted file mode 100644 index 7aba2d967ad9d9..00000000000000 --- a/examples/jsm/nodes/code/CodeNode.js +++ /dev/null @@ -1,75 +0,0 @@ -import Node, { addNodeClass } from '../core/Node.js'; -import { nodeProxy } from '../shadernode/ShaderNode.js'; - -class CodeNode extends Node { - - constructor( code = '', includes = [], language = '' ) { - - super( 'code' ); - - this.isCodeNode = true; - - this.code = code; - this.language = language; - - this._includes = includes; - - } - - setIncludes( includes ) { - - this._includes = includes; - - return this; - - } - - getIncludes( /*builder*/ ) { - - return this._includes; - - } - - generate( builder ) { - - const includes = this.getIncludes( builder ); - - for ( const include of includes ) { - - include.build( builder ); - - } - - const nodeCode = builder.getCodeFromNode( this, this.getNodeType( builder ) ); - nodeCode.code = this.code; - - return nodeCode.code; - - } - - serialize( data ) { - - super.serialize( data ); - - data.code = this.code; - data.language = this.language; - - } - - deserialize( data ) { - - super.deserialize( data ); - - this.code = data.code; - this.language = data.language; - - } - -} - -export default CodeNode; - -export const code = nodeProxy( CodeNode ); -export const js = ( src, includes ) => code( src, includes, 'js' ); - -addNodeClass( CodeNode ); diff --git a/examples/jsm/nodes/code/ExpressionNode.js b/examples/jsm/nodes/code/ExpressionNode.js deleted file mode 100644 index 3a10ad33748c96..00000000000000 --- a/examples/jsm/nodes/code/ExpressionNode.js +++ /dev/null @@ -1,37 +0,0 @@ -import Node, { addNodeClass } from '../core/Node.js'; -import { nodeProxy } from '../shadernode/ShaderNode.js'; - -class ExpressionNode extends Node { - - constructor( snippet = '', nodeType = 'void' ) { - - super( nodeType ); - - this.snippet = snippet; - - } - - generate( builder, output ) { - - const type = this.getNodeType( builder ); - const snippet = this.snippet; - - if ( type === 'void' ) { - - builder.addLineFlowCode( snippet ); - - } else { - - return builder.format( `( ${ snippet } )`, type, output ); - - } - - } - -} - -export default ExpressionNode; - -export const expression = nodeProxy( ExpressionNode ); - -addNodeClass( ExpressionNode ); diff --git a/examples/jsm/nodes/code/FunctionCallNode.js b/examples/jsm/nodes/code/FunctionCallNode.js deleted file mode 100644 index b397c929f6e2c6..00000000000000 --- a/examples/jsm/nodes/code/FunctionCallNode.js +++ /dev/null @@ -1,96 +0,0 @@ -import TempNode from '../core/TempNode.js'; -import { addNodeClass } from '../core/Node.js'; -import { addNodeElement, nodeArray, nodeObject, nodeObjects } from '../shadernode/ShaderNode.js'; - -class FunctionCallNode extends TempNode { - - constructor( functionNode = null, parameters = {} ) { - - super(); - - this.functionNode = functionNode; - this.parameters = parameters; - - } - - setParameters( parameters ) { - - this.parameters = parameters; - - return this; - - } - - getParameters() { - - return this.parameters; - - } - - getNodeType( builder ) { - - return this.functionNode.getNodeType( builder ); - - } - - generate( builder ) { - - const params = []; - - const functionNode = this.functionNode; - - const inputs = functionNode.getInputs( builder ); - const parameters = this.parameters; - - if ( Array.isArray( parameters ) ) { - - for ( let i = 0; i < parameters.length; i ++ ) { - - const inputNode = inputs[ i ]; - const node = parameters[ i ]; - - params.push( node.build( builder, inputNode.type ) ); - - } - - } else { - - for ( const inputNode of inputs ) { - - const node = parameters[ inputNode.name ]; - - if ( node !== undefined ) { - - params.push( node.build( builder, inputNode.type ) ); - - } else { - - throw new Error( `FunctionCallNode: Input '${inputNode.name}' not found in FunctionNode.` ); - - } - - } - - } - - const functionName = functionNode.build( builder, 'property' ); - - return `${functionName}( ${params.join( ', ' )} )`; - - } - -} - -export default FunctionCallNode; - -export const call = ( func, ...params ) => { - - params = params.length > 1 || ( params[ 0 ] && params[ 0 ].isNode === true ) ? nodeArray( params ) : nodeObjects( params[ 0 ] ); - - return nodeObject( new FunctionCallNode( nodeObject( func ), params ) ); - -}; - -addNodeElement( 'call', call ); - -addNodeClass( FunctionCallNode ); diff --git a/examples/jsm/nodes/code/FunctionNode.js b/examples/jsm/nodes/code/FunctionNode.js deleted file mode 100644 index f6009848cb1d5f..00000000000000 --- a/examples/jsm/nodes/code/FunctionNode.js +++ /dev/null @@ -1,106 +0,0 @@ -import CodeNode from './CodeNode.js'; -import { addNodeClass } from '../core/Node.js'; -import { nodeObject } from '../shadernode/ShaderNode.js'; - -class FunctionNode extends CodeNode { - - constructor( code = '', includes = [] ) { - - super( code, includes ); - - this.keywords = {}; - - } - - getNodeType( builder ) { - - return this.getNodeFunction( builder ).type; - - } - - getInputs( builder ) { - - return this.getNodeFunction( builder ).inputs; - - } - - getNodeFunction( builder ) { - - const nodeData = builder.getDataFromNode( this ); - - let nodeFunction = nodeData.nodeFunction; - - if ( nodeFunction === undefined ) { - - nodeFunction = builder.parser.parseFunction( this.code ); - - nodeData.nodeFunction = nodeFunction; - - } - - return nodeFunction; - - } - - generate( builder, output ) { - - super.generate( builder ); - - const nodeFunction = this.getNodeFunction( builder ); - - const name = nodeFunction.name; - const type = nodeFunction.type; - - const nodeCode = builder.getCodeFromNode( this, type ); - - if ( name !== '' ) { - - // use a custom property name - - nodeCode.name = name; - - } - - const propertyName = builder.getPropertyName( nodeCode ); - - let code = this.getNodeFunction( builder ).getCode( propertyName ); - - const keywords = this.keywords; - const keywordsProperties = Object.keys( keywords ); - - if ( keywordsProperties.length > 0 ) { - - for ( const property of keywordsProperties ) { - - const propertyRegExp = new RegExp( `\\b${property}\\b`, 'g' ); - const nodeProperty = keywords[ property ].build( builder, 'property' ); - - code = code.replace( propertyRegExp, nodeProperty ); - - } - - } - - nodeCode.code = code; - - if ( output === 'property' ) { - - return propertyName; - - } else { - - return builder.format( `${ propertyName }()`, type, output ); - - } - - } - -} - -export default FunctionNode; - -export const func = ( code, includes ) => nodeObject( new FunctionNode( code, includes ) ); - -export const fn = ( code, includes ) => func( code, includes ).call; - -addNodeClass( FunctionNode ); diff --git a/examples/jsm/nodes/code/ScriptableNode.js b/examples/jsm/nodes/code/ScriptableNode.js deleted file mode 100644 index 8268b2a0c73807..00000000000000 --- a/examples/jsm/nodes/code/ScriptableNode.js +++ /dev/null @@ -1,488 +0,0 @@ -import Node, { addNodeClass } from '../core/Node.js'; -import { scriptableValue } from './ScriptableValueNode.js'; -import { addNodeElement, nodeProxy, float } from '../shadernode/ShaderNode.js'; - -class Resources extends Map { - - get( key, callback = null, ...params ) { - - if ( this.has( key ) ) return super.get( key ); - - if ( callback !== null ) { - - const value = callback( ...params ); - this.set( key, value ); - return value; - - } - - } - -} - -class Parameters { - - constructor( scriptableNode ) { - - this.scriptableNode = scriptableNode; - - } - - get parameters() { - - return this.scriptableNode.parameters; - - } - - get layout() { - - return this.scriptableNode.getLayout(); - - } - - getInputLayout( id ) { - - return this.scriptableNode.getInputLayout( id ); - - } - - get( name ) { - - const param = this.parameters[ name ]; - const value = param ? param.getValue() : null; - - return value; - - } - -} - -export const global = new Resources(); - -class ScriptableNode extends Node { - - constructor( codeNode = null, parameters = {} ) { - - super(); - - this.codeNode = codeNode; - this.parameters = parameters; - - this._local = new Resources(); - this._output = scriptableValue(); - this._outputs = {}; - this._source = this.source; - this._method = null; - this._object = null; - this._value = null; - this._needsOutputUpdate = true; - - this.onRefresh = this.onRefresh.bind( this ); - - this.isScriptableNode = true; - - } - - get source() { - - return this.codeNode ? this.codeNode.code : ''; - - } - - setLocal( name, value ) { - - return this._local.set( name, value ); - - } - - getLocal( name ) { - - return this._local.get( name ); - - } - - onRefresh() { - - this._refresh(); - - } - - getInputLayout( id ) { - - for ( const element of this.getLayout() ) { - - if ( element.inputType && ( element.id === id || element.name === id ) ) { - - return element; - - } - - } - - } - - getOutputLayout( id ) { - - for ( const element of this.getLayout() ) { - - if ( element.outputType && ( element.id === id || element.name === id ) ) { - - return element; - - } - - } - - } - - setOutput( name, value ) { - - const outputs = this._outputs; - - if ( outputs[ name ] === undefined ) { - - outputs[ name ] = scriptableValue( value ); - - } else { - - outputs[ name ].value = value; - - } - - return this; - - } - - getOutput( name ) { - - return this._outputs[ name ]; - - } - - getParameter( name ) { - - return this.parameters[ name ]; - - } - - setParameter( name, value ) { - - const parameters = this.parameters; - - if ( value && value.isScriptableNode ) { - - this.deleteParameter( name ); - - parameters[ name ] = value; - parameters[ name ].getDefaultOutput().events.addEventListener( 'refresh', this.onRefresh ); - - } else if ( value && value.isScriptableValueNode ) { - - this.deleteParameter( name ); - - parameters[ name ] = value; - parameters[ name ].events.addEventListener( 'refresh', this.onRefresh ); - - } else if ( parameters[ name ] === undefined ) { - - parameters[ name ] = scriptableValue( value ); - parameters[ name ].events.addEventListener( 'refresh', this.onRefresh ); - - } else { - - parameters[ name ].value = value; - - } - - return this; - - } - - getValue() { - - return this.getDefaultOutput().getValue(); - - } - - deleteParameter( name ) { - - let valueNode = this.parameters[ name ]; - - if ( valueNode ) { - - if ( valueNode.isScriptableNode ) valueNode = valueNode.getDefaultOutput(); - - valueNode.events.removeEventListener( 'refresh', this.onRefresh ); - - } - - return this; - - } - - clearParameters() { - - for ( const name of Object.keys( this.parameters ) ) { - - this.deleteParameter( name ); - - } - - this.needsUpdate = true; - - return this; - - } - - call( name, ...params ) { - - const object = this.getObject(); - const method = object[ name ]; - - if ( typeof method === 'function' ) { - - return method( ...params ); - - } - - } - - async callAsync( name, ...params ) { - - const object = this.getObject(); - const method = object[ name ]; - - if ( typeof method === 'function' ) { - - return method.constructor.name === 'AsyncFunction' ? await method( ...params ) : method( ...params ); - - } - - } - - getNodeType( builder ) { - - return this.getDefaultOutputNode().getNodeType( builder ); - - } - - refresh( output = null ) { - - if ( output !== null ) { - - this.getOutput( output ).refresh(); - - } else { - - this._refresh(); - - } - - } - - getObject() { - - if ( this.needsUpdate ) this.dispose(); - if ( this._object !== null ) return this._object; - - // - - const refresh = () => this.refresh(); - const setOutput = ( id, value ) => this.setOutput( id, value ); - - const parameters = new Parameters( this ); - - const THREE = global.get( 'THREE' ); - const TSL = global.get( 'TSL' ); - - const method = this.getMethod( this.codeNode ); - const params = [ parameters, this._local, global, refresh, setOutput, THREE, TSL ]; - - this._object = method( ...params ); - - const layout = this._object.layout; - - if ( layout ) { - - if ( layout.cache === false ) { - - this._local.clear(); - - } - - // default output - this._output.outputType = layout.outputType || null; - - if ( Array.isArray( layout.elements ) ) { - - for ( const element of layout.elements ) { - - const id = element.id || element.name; - - if ( element.inputType ) { - - if ( this.getParameter( id ) === undefined ) this.setParameter( id, null ); - - this.getParameter( id ).inputType = element.inputType; - - } - - if ( element.outputType ) { - - if ( this.getOutput( id ) === undefined ) this.setOutput( id, null ); - - this.getOutput( id ).outputType = element.outputType; - - } - - } - - } - - } - - return this._object; - - } - - deserialize( data ) { - - super.deserialize( data ); - - for ( const name in this.parameters ) { - - let valueNode = this.parameters[ name ]; - - if ( valueNode.isScriptableNode ) valueNode = valueNode.getDefaultOutput(); - - valueNode.events.addEventListener( 'refresh', this.onRefresh ); - - } - - } - - getLayout() { - - return this.getObject().layout; - - } - - getDefaultOutputNode() { - - const output = this.getDefaultOutput().value; - - if ( output && output.isNode ) { - - return output; - - } - - return float(); - - } - - getDefaultOutput() { - - return this._exec()._output; - - } - - getMethod() { - - if ( this.needsUpdate ) this.dispose(); - if ( this._method !== null ) return this._method; - - // - - const parametersProps = [ 'parameters', 'local', 'global', 'refresh', 'setOutput', 'THREE', 'TSL' ]; - const interfaceProps = [ 'layout', 'init', 'main', 'dispose' ]; - - const properties = interfaceProps.join( ', ' ); - const declarations = 'var ' + properties + '; var output = {};\n'; - const returns = '\nreturn { ...output, ' + properties + ' };'; - - const code = declarations + this.codeNode.code + returns; - - // - - this._method = new Function( ...parametersProps, code ); - - return this._method; - - } - - dispose() { - - if ( this._method === null ) return; - - if ( this._object && typeof this._object.dispose === 'function' ) { - - this._object.dispose(); - - } - - this._method = null; - this._object = null; - this._source = null; - this._value = null; - this._needsOutputUpdate = true; - this._output.value = null; - this._outputs = {}; - - } - - construct() { - - return this.getDefaultOutputNode(); - - } - - set needsUpdate( value ) { - - if ( value === true ) this.dispose(); - - } - - get needsUpdate() { - - return this.source !== this._source; - - } - - _exec() { - - if ( this.codeNode === null ) return this; - - if ( this._needsOutputUpdate === true ) { - - this._value = this.call( 'main' ); - - this._needsOutputUpdate = false; - - } - - this._output.value = this._value; - - return this; - - } - - _refresh() { - - this.needsUpdate = true; - - this._exec(); - - this._output.refresh(); - - } - -} - -export default ScriptableNode; - -export const scriptable = nodeProxy( ScriptableNode ); - -addNodeElement( 'scriptable', scriptable ); - -addNodeClass( ScriptableNode ); diff --git a/examples/jsm/nodes/code/ScriptableValueNode.js b/examples/jsm/nodes/code/ScriptableValueNode.js deleted file mode 100644 index 8ace150c680fb6..00000000000000 --- a/examples/jsm/nodes/code/ScriptableValueNode.js +++ /dev/null @@ -1,167 +0,0 @@ -import Node, { addNodeClass } from '../core/Node.js'; -import { arrayBufferToBase64, base64ToArrayBuffer } from '../core/NodeUtils.js'; -import { addNodeElement, nodeProxy, float } from '../shadernode/ShaderNode.js'; -import { EventDispatcher } from 'three'; - -class ScriptableValueNode extends Node { - - constructor( value = null ) { - - super(); - - this._value = value; - this._cache = null; - - this.inputType = null; - this.outpuType = null; - - this.events = new EventDispatcher(); - - this.isScriptableValueNode = true; - - } - - get isScriptableOutputNode() { - - return this.outputType !== null; - - } - - set value( val ) { - - if ( this._value === val ) return; - - if ( this._cache && this.inputType === 'URL' && this.value.value instanceof ArrayBuffer ) { - - URL.revokeObjectURL( this._cache ); - - this._cache = null; - - } - - this._value = val; - - this.events.dispatchEvent( { type: 'change' } ); - - this.refresh(); - - } - - get value() { - - return this._value; - - } - - refresh() { - - this.events.dispatchEvent( { type: 'refresh' } ); - - } - - getValue() { - - const value = this.value; - - if ( value && this._cache === null && this.inputType === 'URL' && value.value instanceof ArrayBuffer ) { - - this._cache = URL.createObjectURL( new Blob( [ value.value ] ) ); - - } else if ( value && value.value !== null && value.value !== undefined && ( - ( ( this.inputType === 'URL' || this.inputType === 'String' ) && typeof value.value === 'string' ) || - ( this.inputType === 'Number' && typeof value.value === 'number' ) || - ( this.inputType === 'Vector2' && value.value.isVector2 ) || - ( this.inputType === 'Vector3' && value.value.isVector3 ) || - ( this.inputType === 'Vector4' && value.value.isVector4 ) || - ( this.inputType === 'Color' && value.value.isColor ) || - ( this.inputType === 'Matrix3' && value.value.isMatrix3 ) || - ( this.inputType === 'Matrix4' && value.value.isMatrix4 ) - ) ) { - - return value.value; - - } - - return this._cache || value; - - } - - getNodeType( builder ) { - - return this.value && this.value.isNode ? this.value.getNodeType( builder ) : 'float'; - - } - - construct() { - - return this.value && this.value.isNode ? this.value : float(); - - } - - serialize( data ) { - - super.serialize( data ); - - if ( this.value !== null ) { - - if ( this.inputType === 'ArrayBuffer' ) { - - data.value = arrayBufferToBase64( this.value ); - - } else { - - data.value = this.value ? this.value.toJSON( data.meta ).uuid : null; - - } - - } else { - - data.value = null; - - } - - data.inputType = this.inputType; - data.outputType = this.outputType; - - } - - deserialize( data ) { - - super.deserialize( data ); - - let value = null; - - if ( data.value !== null ) { - - if ( data.inputType === 'ArrayBuffer' ) { - - value = base64ToArrayBuffer( data.value ); - - } else if ( data.inputType === 'Texture' ) { - - value = data.meta.textures[ data.value ]; - - } else { - - value = data.meta.nodes[ data.value ] || null; - - } - - } - - this.value = value; - - this.inputType = data.inputType; - this.outputType = data.outputType; - - } - -} - -export default ScriptableValueNode; - -export const scriptableValue = nodeProxy( ScriptableValueNode ); - -addNodeElement( 'scriptableValue', scriptableValue ); - -addNodeClass( ScriptableValueNode ); diff --git a/examples/jsm/nodes/core/ArrayUniformNode.js b/examples/jsm/nodes/core/ArrayUniformNode.js deleted file mode 100644 index eea3f9c46f37a2..00000000000000 --- a/examples/jsm/nodes/core/ArrayUniformNode.js +++ /dev/null @@ -1,26 +0,0 @@ -import UniformNode from './UniformNode.js'; -import { addNodeClass } from './Node.js'; - -class ArrayUniformNode extends UniformNode { - - constructor( nodes = [] ) { - - super(); - - this.isArrayUniformNode = true; - - this.nodes = nodes; - - } - - getNodeType( builder ) { - - return this.nodes[ 0 ].getNodeType( builder ); - - } - -} - -export default ArrayUniformNode; - -addNodeClass( ArrayUniformNode ); diff --git a/examples/jsm/nodes/core/AttributeNode.js b/examples/jsm/nodes/core/AttributeNode.js deleted file mode 100644 index 1b9c50bc786cbd..00000000000000 --- a/examples/jsm/nodes/core/AttributeNode.js +++ /dev/null @@ -1,102 +0,0 @@ -import Node, { addNodeClass } from './Node.js'; -import { varying } from './VaryingNode.js'; -import { nodeObject } from '../shadernode/ShaderNode.js'; - -class AttributeNode extends Node { - - constructor( attributeName, nodeType = null ) { - - super( nodeType ); - - this._attributeName = attributeName; - - } - - getHash( builder ) { - - return this.getAttributeName( builder ); - - } - - getNodeType( builder ) { - - const attributeName = this.getAttributeName( builder ); - - let nodeType = super.getNodeType( builder ); - - if ( nodeType === null ) { - - if ( builder.hasGeometryAttribute( attributeName ) ) { - - const attribute = builder.geometry.getAttribute( attributeName ); - - nodeType = builder.getTypeFromAttribute( attribute ); - - } else { - - nodeType = 'float'; - - } - - } - - return nodeType; - - } - - setAttributeName( attributeName ) { - - this._attributeName = attributeName; - - return this; - - } - - getAttributeName( /*builder*/ ) { - - return this._attributeName; - - } - - generate( builder ) { - - const attributeName = this.getAttributeName( builder ); - const nodeType = this.getNodeType( builder ); - const geometryAttribute = builder.hasGeometryAttribute( attributeName ); - - if ( geometryAttribute === true ) { - - const attribute = builder.geometry.getAttribute( attributeName ); - const attributeType = builder.getTypeFromAttribute( attribute ); - - const nodeAttribute = builder.getAttribute( attributeName, attributeType ); - - if ( builder.shaderStage === 'vertex' ) { - - return builder.format( nodeAttribute.name, attributeType, nodeType ); - - } else { - - const nodeVarying = varying( this ); - - return nodeVarying.build( builder, nodeType ); - - } - - } else { - - console.warn( `AttributeNode: Attribute "${ attributeName }" not found.` ); - - return builder.getConst( nodeType ); - - } - - } - -} - -export default AttributeNode; - -export const attribute = ( name, nodeType ) => nodeObject( new AttributeNode( name, nodeType ) ); - -addNodeClass( AttributeNode ); diff --git a/examples/jsm/nodes/core/BypassNode.js b/examples/jsm/nodes/core/BypassNode.js deleted file mode 100644 index e3c27cead41933..00000000000000 --- a/examples/jsm/nodes/core/BypassNode.js +++ /dev/null @@ -1,45 +0,0 @@ -import Node, { addNodeClass } from './Node.js'; -import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js'; - -class BypassNode extends Node { - - constructor( returnNode, callNode ) { - - super(); - - this.isBypassNode = true; - - this.outputNode = returnNode; - this.callNode = callNode; - - } - - getNodeType( builder ) { - - return this.outputNode.getNodeType( builder ); - - } - - generate( builder, output ) { - - const snippet = this.callNode.build( builder, 'void' ); - - if ( snippet !== '' ) { - - builder.addLineFlowCode( snippet ); - - } - - return this.outputNode.build( builder, output ); - - } - -} - -export default BypassNode; - -export const bypass = nodeProxy( BypassNode ); - -addNodeElement( 'bypass', bypass ); - -addNodeClass( BypassNode ); diff --git a/examples/jsm/nodes/core/CacheNode.js b/examples/jsm/nodes/core/CacheNode.js deleted file mode 100644 index 7a084fc99d92bf..00000000000000 --- a/examples/jsm/nodes/core/CacheNode.js +++ /dev/null @@ -1,46 +0,0 @@ -import Node, { addNodeClass } from './Node.js'; -import NodeCache from './NodeCache.js'; -import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js'; - -class CacheNode extends Node { - - constructor( node, cache = new NodeCache() ) { - - super(); - - this.isCacheNode = true; - - this.node = node; - this.cache = cache; - - } - - getNodeType( builder ) { - - return this.node.getNodeType( builder ); - - } - - build( builder, ...params ) { - - const previousCache = builder.getCache(); - - builder.setCache( this.cache ); - - const data = this.node.build( builder, ...params ); - - builder.setCache( previousCache ); - - return data; - - } - -} - -export default CacheNode; - -export const cache = nodeProxy( CacheNode ); - -addNodeElement( 'cache', cache ); - -addNodeClass( CacheNode ); diff --git a/examples/jsm/nodes/core/ConstNode.js b/examples/jsm/nodes/core/ConstNode.js deleted file mode 100644 index 65c1d5df7b3b6d..00000000000000 --- a/examples/jsm/nodes/core/ConstNode.js +++ /dev/null @@ -1,32 +0,0 @@ -import InputNode from './InputNode.js'; -import { addNodeClass } from './Node.js'; - -class ConstNode extends InputNode { - - constructor( value, nodeType = null ) { - - super( value, nodeType ); - - this.isConstNode = true; - - } - - generateConst( builder ) { - - return builder.getConst( this.getNodeType( builder ), this.value ); - - } - - generate( builder, output ) { - - const type = this.getNodeType( builder ); - - return builder.format( this.generateConst( builder ), type, output ); - - } - -} - -export default ConstNode; - -addNodeClass( ConstNode ); diff --git a/examples/jsm/nodes/core/ContextNode.js b/examples/jsm/nodes/core/ContextNode.js deleted file mode 100644 index 8b792c57cdb28a..00000000000000 --- a/examples/jsm/nodes/core/ContextNode.js +++ /dev/null @@ -1,59 +0,0 @@ -import Node, { addNodeClass } from './Node.js'; -import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js'; - -class ContextNode extends Node { - - constructor( node, context = {} ) { - - super(); - - this.isContextNode = true; - - this.node = node; - this.context = context; - - } - - getNodeType( builder ) { - - return this.node.getNodeType( builder ); - - } - - construct( builder ) { - - const previousContext = builder.getContext(); - - builder.setContext( { ...builder.context, ...this.context } ); - - const node = this.node.build( builder ); - - builder.setContext( previousContext ); - - return node; - - } - - generate( builder, output ) { - - const previousContext = builder.getContext(); - - builder.setContext( { ...builder.context, ...this.context } ); - - const snippet = this.node.build( builder, output ); - - builder.setContext( previousContext ); - - return snippet; - - } - -} - -export default ContextNode; - -export const context = nodeProxy( ContextNode ); - -addNodeElement( 'context', context ); - -addNodeClass( ContextNode ); diff --git a/examples/jsm/nodes/core/InputNode.js b/examples/jsm/nodes/core/InputNode.js deleted file mode 100644 index 3bcf14fe9fc898..00000000000000 --- a/examples/jsm/nodes/core/InputNode.js +++ /dev/null @@ -1,83 +0,0 @@ -import Node, { addNodeClass } from './Node.js'; -import { getValueType, getValueFromType, arrayBufferToBase64 } from './NodeUtils.js'; - -class InputNode extends Node { - - constructor( value, nodeType = null ) { - - super( nodeType ); - - this.isInputNode = true; - - this.value = value; - this.precision = null; - - } - - getNodeType( /*builder*/ ) { - - if ( this.nodeType === null ) { - - return getValueType( this.value ); - - } - - return this.nodeType; - - } - - getInputType( builder ) { - - return this.getNodeType( builder ); - - } - - setPrecision( precision ) { - - this.precision = precision; - - return this; - - } - - serialize( data ) { - - super.serialize( data ); - - data.value = this.value; - - if ( this.value && this.value.toArray ) data.value = this.value.toArray(); - - data.valueType = getValueType( this.value ); - data.nodeType = this.nodeType; - - if ( data.valueType === 'ArrayBuffer' ) data.value = arrayBufferToBase64( data.value ); - - data.precision = this.precision; - - } - - deserialize( data ) { - - super.deserialize( data ); - - this.nodeType = data.nodeType; - this.value = Array.isArray( data.value ) ? getValueFromType( data.valueType, ...data.value ) : data.value; - - this.precision = data.precision || null; - - if ( this.value && this.value.fromArray ) this.value = this.value.fromArray( data.value ); - - } - - generate( /*builder, output*/ ) { - - console.warn( 'Abstract function.' ); - - } - -} - -export default InputNode; - -addNodeClass( InputNode ); diff --git a/examples/jsm/nodes/core/InstanceIndexNode.js b/examples/jsm/nodes/core/InstanceIndexNode.js deleted file mode 100644 index a9ea6460393fdd..00000000000000 --- a/examples/jsm/nodes/core/InstanceIndexNode.js +++ /dev/null @@ -1,45 +0,0 @@ -import Node, { addNodeClass } from './Node.js'; -import { varying } from '../core/VaryingNode.js'; -import { nodeImmutable } from '../shadernode/ShaderNode.js'; - -class InstanceIndexNode extends Node { - - constructor() { - - super( 'uint' ); - - this.isInstanceIndexNode = true; - - } - - generate( builder ) { - - const nodeType = this.getNodeType( builder ); - - const propertyName = builder.getInstanceIndex(); - - let output = null; - - if ( builder.shaderStage === 'vertex' || builder.shaderStage === 'compute' ) { - - output = propertyName; - - } else { - - const nodeVarying = varying( this ); - - output = nodeVarying.build( builder, nodeType ); - - } - - return output; - - } - -} - -export default InstanceIndexNode; - -export const instanceIndex = nodeImmutable( InstanceIndexNode ); - -addNodeClass( InstanceIndexNode ); diff --git a/examples/jsm/nodes/core/LightingModel.js b/examples/jsm/nodes/core/LightingModel.js deleted file mode 100644 index 296a373e084232..00000000000000 --- a/examples/jsm/nodes/core/LightingModel.js +++ /dev/null @@ -1,16 +0,0 @@ -class LightingModel { - - constructor( direct = null, indirectDiffuse = null, indirectSpecular = null, ambientOcclusion = null ) { - - this.direct = direct; - this.indirectDiffuse = indirectDiffuse; - this.indirectSpecular = indirectSpecular; - this.ambientOcclusion = ambientOcclusion; - - } - -} - -export default LightingModel; - -export const lightingModel = ( ...params ) => new LightingModel( ...params ); diff --git a/examples/jsm/nodes/core/Node.js b/examples/jsm/nodes/core/Node.js deleted file mode 100644 index 1a81c4c868e0cf..00000000000000 --- a/examples/jsm/nodes/core/Node.js +++ /dev/null @@ -1,448 +0,0 @@ -import { NodeUpdateType } from './constants.js'; -import { getNodeChildren, getCacheKey } from './NodeUtils.js'; -import { MathUtils } from 'three'; - -const NodeClasses = new Map(); - -let _nodeId = 0; - -class Node { - - constructor( nodeType = null ) { - - this.isNode = true; - - this.nodeType = nodeType; - - this.updateType = NodeUpdateType.NONE; - this.updateBeforeType = NodeUpdateType.NONE; - - this.uuid = MathUtils.generateUUID(); - - Object.defineProperty( this, 'id', { value: _nodeId ++ } ); - - } - - get type() { - - return this.constructor.name; - - } - - isGlobal( /*builder*/ ) { - - return false; - - } - - * getChildren() { - - const self = this; - - for ( const { property, index, childNode } of getNodeChildren( this ) ) { - - if ( index !== undefined ) { - - yield { childNode, replaceNode( node ) { - - self[ property ][ index ] = node; - - } }; - - } else { - - yield { childNode, replaceNode( node ) { - - self[ property ] = node; - - } }; - - } - - } - - } - - traverse( callback, replaceNode = null ) { - - callback( this, replaceNode ); - - for ( const { childNode, replaceNode } of this.getChildren() ) { - - childNode.traverse( callback, replaceNode ); - - } - - } - - getCacheKey() { - - return getCacheKey( this ); - - } - - getHash( /*builder*/ ) { - - return this.uuid; - - } - - getUpdateType() { - - return this.updateType; - - } - - getUpdateBeforeType() { - - return this.updateBeforeType; - - } - - getNodeType( /*builder*/ ) { - - return this.nodeType; - - } - - getReference( builder ) { - - const hash = this.getHash( builder ); - const nodeFromHash = builder.getNodeFromHash( hash ); - - return nodeFromHash || this; - - } - - construct( builder ) { - - const nodeProperties = builder.getNodeProperties( this ); - - for ( const { childNode } of this.getChildren() ) { - - nodeProperties[ '_node' + childNode.id ] = childNode; - - } - - // return a outputNode if exists - return null; - - } - - analyze( builder ) { - - const nodeData = builder.getDataFromNode( this ); - nodeData.dependenciesCount = nodeData.dependenciesCount === undefined ? 1 : nodeData.dependenciesCount + 1; - - if ( nodeData.dependenciesCount === 1 ) { - - // node flow children - - const nodeProperties = builder.getNodeProperties( this ); - - for ( const childNode of Object.values( nodeProperties ) ) { - - if ( childNode && childNode.isNode === true ) { - - childNode.build( builder ); - - } - - } - - } - - } - - generate( builder, output ) { - - const { outputNode } = builder.getNodeProperties( this ); - - if ( outputNode && outputNode.isNode === true ) { - - return outputNode.build( builder, output ); - - } - - } - - updateBefore( /*frame*/ ) { - - console.warn( 'Abstract function.' ); - - } - - update( /*frame*/ ) { - - console.warn( 'Abstract function.' ); - - } - - build( builder, output = null ) { - - const refNode = this.getReference( builder ); - - if ( this !== refNode ) { - - return refNode.build( builder, output ); - - } - - builder.addNode( this ); - builder.addChain( this ); - - /* Build stages expected results: - - "construct" -> Node - - "analyze" -> null - - "generate" -> String - */ - let result = null; - - const buildStage = builder.getBuildStage(); - - if ( buildStage === 'construct' ) { - - const properties = builder.getNodeProperties( this ); - - if ( properties.initialized !== true || builder.context.tempRead === false ) { - - properties.initialized = true; - properties.outputNode = this.construct( builder ); - - for ( const childNode of Object.values( properties ) ) { - - if ( childNode && childNode.isNode === true ) { - - childNode.build( builder ); - - } - - } - - } - - } else if ( buildStage === 'analyze' ) { - - this.analyze( builder ); - - } else if ( buildStage === 'generate' ) { - - const isGenerateOnce = this.generate.length === 1; - - if ( isGenerateOnce ) { - - const type = this.getNodeType( builder ); - const nodeData = builder.getDataFromNode( this ); - - result = nodeData.snippet; - - if ( result === undefined /*|| builder.context.tempRead === false*/ ) { - - result = this.generate( builder ) || ''; - - nodeData.snippet = result; - - } - - result = builder.format( result, type, output ); - - } else { - - result = this.generate( builder, output ) || ''; - - } - - } - - builder.removeChain( this ); - - return result; - - } - - getSerializeChildren() { - - return getNodeChildren( this ); - - } - - serialize( json ) { - - const nodeChildren = this.getSerializeChildren(); - - const inputNodes = {}; - - for ( const { property, index, childNode } of nodeChildren ) { - - if ( index !== undefined ) { - - if ( inputNodes[ property ] === undefined ) { - - inputNodes[ property ] = Number.isInteger( index ) ? [] : {}; - - } - - inputNodes[ property ][ index ] = childNode.toJSON( json.meta ).uuid; - - } else { - - inputNodes[ property ] = childNode.toJSON( json.meta ).uuid; - - } - - } - - if ( Object.keys( inputNodes ).length > 0 ) { - - json.inputNodes = inputNodes; - - } - - } - - deserialize( json ) { - - if ( json.inputNodes !== undefined ) { - - const nodes = json.meta.nodes; - - for ( const property in json.inputNodes ) { - - if ( Array.isArray( json.inputNodes[ property ] ) ) { - - const inputArray = []; - - for ( const uuid of json.inputNodes[ property ] ) { - - inputArray.push( nodes[ uuid ] ); - - } - - this[ property ] = inputArray; - - } else if ( typeof json.inputNodes[ property ] === 'object' ) { - - const inputObject = {}; - - for ( const subProperty in json.inputNodes[ property ] ) { - - const uuid = json.inputNodes[ property ][ subProperty ]; - - inputObject[ subProperty ] = nodes[ uuid ]; - - } - - this[ property ] = inputObject; - - } else { - - const uuid = json.inputNodes[ property ]; - - this[ property ] = nodes[ uuid ]; - - } - - } - - } - - } - - toJSON( meta ) { - - const { uuid, type } = this; - const isRoot = ( meta === undefined || typeof meta === 'string' ); - - if ( isRoot ) { - - meta = { - textures: {}, - images: {}, - nodes: {} - }; - - } - - // serialize - - let data = meta.nodes[ uuid ]; - - if ( data === undefined ) { - - data = { - uuid, - type, - meta, - metadata: { - version: 4.6, - type: 'Node', - generator: 'Node.toJSON' - } - }; - - if ( isRoot !== true ) meta.nodes[ data.uuid ] = data; - - this.serialize( data ); - - delete data.meta; - - } - - // TODO: Copied from Object3D.toJSON - - function extractFromCache( cache ) { - - const values = []; - - for ( const key in cache ) { - - const data = cache[ key ]; - delete data.metadata; - values.push( data ); - - } - - return values; - - } - - if ( isRoot ) { - - const textures = extractFromCache( meta.textures ); - const images = extractFromCache( meta.images ); - const nodes = extractFromCache( meta.nodes ); - - if ( textures.length > 0 ) data.textures = textures; - if ( images.length > 0 ) data.images = images; - if ( nodes.length > 0 ) data.nodes = nodes; - - } - - return data; - - } - -} - -export default Node; - -export function addNodeClass( nodeClass ) { - - if ( typeof nodeClass !== 'function' || ! nodeClass.name ) throw new Error( `Node class ${ nodeClass.name } is not a class` ); - if ( NodeClasses.has( nodeClass.name ) ) throw new Error( `Redefinition of node class ${ nodeClass.name }` ); - - NodeClasses.set( nodeClass.name, nodeClass ); - -} - -export function createNodeFromType( type ) { - - const Class = NodeClasses.get( type ); - - if ( Class !== undefined ) { - - return new Class(); - - } - -} diff --git a/examples/jsm/nodes/core/NodeAttribute.js b/examples/jsm/nodes/core/NodeAttribute.js deleted file mode 100644 index 35deaa33eb2094..00000000000000 --- a/examples/jsm/nodes/core/NodeAttribute.js +++ /dev/null @@ -1,15 +0,0 @@ -class NodeAttribute { - - constructor( name, type, node = null ) { - - this.isNodeAttribute = true; - - this.name = name; - this.type = type; - this.node = node; - - } - -} - -export default NodeAttribute; diff --git a/examples/jsm/nodes/core/NodeBuilder.js b/examples/jsm/nodes/core/NodeBuilder.js deleted file mode 100644 index 29e2cb6e732d62..00000000000000 --- a/examples/jsm/nodes/core/NodeBuilder.js +++ /dev/null @@ -1,971 +0,0 @@ -import NodeUniform from './NodeUniform.js'; -import NodeAttribute from './NodeAttribute.js'; -import NodeVarying from './NodeVarying.js'; -import NodeVar from './NodeVar.js'; -import NodeCode from './NodeCode.js'; -import NodeKeywords from './NodeKeywords.js'; -import NodeCache from './NodeCache.js'; -import { NodeUpdateType, defaultBuildStages, shaderStages } from './constants.js'; - -import { REVISION, NoColorSpace, LinearEncoding, sRGBEncoding, SRGBColorSpace, Color, Vector2, Vector3, Vector4, Float16BufferAttribute } from 'three'; - -import { stack } from './StackNode.js'; -import { maxMipLevel } from '../utils/MaxMipLevelNode.js'; - -const typeFromLength = new Map( [ - [ 2, 'vec2' ], - [ 3, 'vec3' ], - [ 4, 'vec4' ], - [ 9, 'mat3' ], - [ 16, 'mat4' ] -] ); - -const typeFromArray = new Map( [ - [ Int8Array, 'int' ], - [ Int16Array, 'int' ], - [ Int32Array, 'int' ], - [ Uint8Array, 'uint' ], - [ Uint16Array, 'uint' ], - [ Uint32Array, 'uint' ], - [ Float32Array, 'float' ] -] ); - -const isNonPaddingElementArray = new Set( [ Int32Array, Uint32Array, Float32Array ] ); - -const toFloat = ( value ) => { - - value = Number( value ); - - return value + ( value % 1 ? '' : '.0' ); - -}; - -class NodeBuilder { - - constructor( object, renderer, parser ) { - - this.object = object; - this.material = object && ( object.material || null ); - this.geometry = object && ( object.geometry || null ); - this.renderer = renderer; - this.parser = parser; - - this.nodes = []; - this.updateNodes = []; - this.updateBeforeNodes = []; - this.hashNodes = {}; - - this.lightsNode = null; - this.environmentNode = null; - this.fogNode = null; - this.toneMappingNode = null; - - this.vertexShader = null; - this.fragmentShader = null; - this.computeShader = null; - - this.flowNodes = { vertex: [], fragment: [], compute: [] }; - this.flowCode = { vertex: '', fragment: '', compute: [] }; - this.uniforms = { vertex: [], fragment: [], compute: [], index: 0 }; - this.codes = { vertex: [], fragment: [], compute: [] }; - this.attributes = []; - this.bufferAttributes = []; - this.varyings = []; - this.vars = { vertex: [], fragment: [], compute: [] }; - this.flow = { code: '' }; - this.chaining = []; - this.stack = stack(); - this.tab = '\t'; - - this.context = { - keywords: new NodeKeywords(), - material: this.material, - getMIPLevelAlgorithmNode: ( textureNode, levelNode ) => levelNode.mul( maxMipLevel( textureNode ) ) - }; - - this.cache = new NodeCache(); - this.globalCache = this.cache; - - this.flowsData = new WeakMap(); - - this.shaderStage = null; - this.buildStage = null; - - } - - setHashNode( node, hash ) { - - this.hashNodes[ hash ] = node; - - } - - addNode( node ) { - - if ( this.nodes.indexOf( node ) === - 1 ) { - - const updateType = node.getUpdateType(); - const updateBeforeType = node.getUpdateBeforeType(); - - if ( updateType !== NodeUpdateType.NONE ) { - - this.updateNodes.push( node ); - - } - - if ( updateBeforeType !== NodeUpdateType.NONE ) { - - this.updateBeforeNodes.push( node ); - - } - - this.nodes.push( node ); - - this.setHashNode( node, node.getHash( this ) ); - - } - - } - - get currentNode() { - - return this.chaining[ this.chaining.length - 1 ]; - - } - - addChain( node ) { - - /* - if ( this.chaining.indexOf( node ) !== - 1 ) { - - console.warn( 'Recursive node: ', node ); - - } - */ - - this.chaining.push( node ); - - } - - removeChain( node ) { - - const lastChain = this.chaining.pop(); - - if ( lastChain !== node ) { - - throw new Error( 'NodeBuilder: Invalid node chaining!' ); - - } - - } - - getMethod( method ) { - - return method; - - } - - getNodeFromHash( hash ) { - - return this.hashNodes[ hash ]; - - } - - addFlow( shaderStage, node ) { - - this.flowNodes[ shaderStage ].push( node ); - - return node; - - } - - setContext( context ) { - - this.context = context; - - } - - getContext() { - - return this.context; - - } - - setCache( cache ) { - - this.cache = cache; - - } - - getCache() { - - return this.cache; - - } - - isAvailable( /*name*/ ) { - - return false; - - } - - getInstanceIndex() { - - console.warn( 'Abstract function.' ); - - } - - getFrontFacing() { - - console.warn( 'Abstract function.' ); - - } - - getFragCoord() { - - console.warn( 'Abstract function.' ); - - } - - isFlipY() { - - return false; - - } - - getTexture( /* texture, textureProperty, uvSnippet */ ) { - - console.warn( 'Abstract function.' ); - - } - - getTextureLevel( /* texture, textureProperty, uvSnippet, levelSnippet */ ) { - - console.warn( 'Abstract function.' ); - - } - - // @TODO: rename to .generateConst() - getConst( type, value = null ) { - - if ( value === null ) { - - if ( type === 'float' || type === 'int' || type === 'uint' ) value = 0; - else if ( type === 'bool' ) value = false; - else if ( type === 'color' ) value = new Color(); - else if ( type === 'vec2' ) value = new Vector2(); - else if ( type === 'vec3' ) value = new Vector3(); - else if ( type === 'vec4' ) value = new Vector4(); - - } - - if ( type === 'float' ) return toFloat( value ); - if ( type === 'int' ) return `${ Math.round( value ) }`; - if ( type === 'uint' ) return value >= 0 ? `${ Math.round( value ) }u` : '0u'; - if ( type === 'bool' ) return value ? 'true' : 'false'; - if ( type === 'color' ) return `${ this.getType( 'vec3' ) }( ${ toFloat( value.r ) }, ${ toFloat( value.g ) }, ${ toFloat( value.b ) } )`; - - const typeLength = this.getTypeLength( type ); - - const componentType = this.getComponentType( type ); - - const getConst = value => this.getConst( componentType, value ); - - if ( typeLength === 2 ) { - - return `${ this.getType( type ) }( ${ getConst( value.x ) }, ${ getConst( value.y ) } )`; - - } else if ( typeLength === 3 ) { - - return `${ this.getType( type ) }( ${ getConst( value.x ) }, ${ getConst( value.y ) }, ${ getConst( value.z ) } )`; - - } else if ( typeLength === 4 ) { - - return `${ this.getType( type ) }( ${ getConst( value.x ) }, ${ getConst( value.y ) }, ${ getConst( value.z ) }, ${ getConst( value.w ) } )`; - - } else if ( typeLength > 4 && value && ( value.isMatrix3 || value.isMatrix4 ) ) { - - return `${ this.getType( type ) }( ${ value.elements.map( getConst ).join( ', ' ) } )`; - - } else if ( typeLength > 4 ) { - - return `${ this.getType( type ) }()`; - - } - - throw new Error( `NodeBuilder: Type '${type}' not found in generate constant attempt.` ); - - } - - getType( type ) { - - return type; - - } - - generateMethod( method ) { - - return method; - - } - - hasGeometryAttribute( name ) { - - return this.geometry && this.geometry.getAttribute( name ) !== undefined; - - } - - getAttribute( name, type ) { - - const attributes = this.attributes; - - // find attribute - - for ( const attribute of attributes ) { - - if ( attribute.name === name ) { - - return attribute; - - } - - } - - // create a new if no exist - - const attribute = new NodeAttribute( name, type ); - - attributes.push( attribute ); - - return attribute; - - } - - getPropertyName( node/*, shaderStage*/ ) { - - return node.name; - - } - - isVector( type ) { - - return /vec\d/.test( type ); - - } - - isMatrix( type ) { - - return /mat\d/.test( type ); - - } - - isReference( type ) { - - return type === 'void' || type === 'property' || type === 'sampler' || type === 'texture' || type === 'cubeTexture'; - - } - - /** @deprecated, r152 */ - getTextureEncodingFromMap( map ) { - - console.warn( 'THREE.NodeBuilder: Method .getTextureEncodingFromMap replaced by .getTextureColorSpaceFromMap in r152+.' ); - return this.getTextureColorSpaceFromMap( map ) === SRGBColorSpace ? sRGBEncoding : LinearEncoding; - - } - - getTextureColorSpaceFromMap( map ) { - - let colorSpace; - - if ( map && map.isTexture ) { - - colorSpace = map.colorSpace; - - } else if ( map && map.isWebGLRenderTarget ) { - - colorSpace = map.texture.colorSpace; - - } else { - - colorSpace = NoColorSpace; - - } - - return colorSpace; - - } - - getComponentType( type ) { - - type = this.getVectorType( type ); - - if ( type === 'float' || type === 'bool' || type === 'int' || type === 'uint' ) return type; - - const componentType = /(b|i|u|)(vec|mat)([2-4])/.exec( type ); - - if ( componentType === null ) return null; - - if ( componentType[ 1 ] === 'b' ) return 'bool'; - if ( componentType[ 1 ] === 'i' ) return 'int'; - if ( componentType[ 1 ] === 'u' ) return 'uint'; - - return 'float'; - - } - - getVectorType( type ) { - - if ( type === 'color' ) return 'vec3'; - if ( type === 'texture' ) return 'vec4'; - - return type; - - } - - getTypeFromLength( length, componentType = 'float' ) { - - if ( length === 1 ) return componentType; - - const baseType = typeFromLength.get( length ); - const prefix = componentType === 'float' ? '' : componentType[ 0 ]; - - return prefix + baseType; - - } - - getTypeFromArray( array ) { - - return typeFromArray.get( array.constructor ); - - } - - getTypeFromAttribute( attribute ) { - - let dataAttribute = attribute; - - if ( attribute.isInterleavedBufferAttribute ) dataAttribute = attribute.data; - - const array = dataAttribute.array; - const itemSize = isNonPaddingElementArray.has( array.constructor ) ? attribute.itemSize : dataAttribute.stride || attribute.itemSize; - const normalized = attribute.normalized; - - let arrayType; - - if ( ! ( attribute instanceof Float16BufferAttribute ) && normalized !== true ) { - - arrayType = this.getTypeFromArray( array ); - - } - - return this.getTypeFromLength( itemSize, arrayType ); - - } - - getTypeLength( type ) { - - const vecType = this.getVectorType( type ); - const vecNum = /vec([2-4])/.exec( vecType ); - - if ( vecNum !== null ) return Number( vecNum[ 1 ] ); - if ( vecType === 'float' || vecType === 'bool' || vecType === 'int' || vecType === 'uint' ) return 1; - if ( /mat3/.test( type ) === true ) return 9; - if ( /mat4/.test( type ) === true ) return 16; - - return 0; - - } - - getVectorFromMatrix( type ) { - - return type.replace( 'mat', 'vec' ); - - } - - changeComponentType( type, newComponentType ) { - - return this.getTypeFromLength( this.getTypeLength( type ), newComponentType ); - - } - - getIntegerType( type ) { - - const componentType = this.getComponentType( type ); - - if ( componentType === 'int' || componentType === 'uint' ) return type; - - return this.changeComponentType( type, 'int' ); - - } - - addStack() { - - this.stack = stack( this.stack ); - - return this.stack; - - } - - removeStack() { - - const currentStack = this.stack; - - this.stack = currentStack.parent; - - return currentStack; - - } - - getDataFromNode( node, shaderStage = this.shaderStage ) { - - const cache = node.isGlobal( this ) ? this.globalCache : this.cache; - - let nodeData = cache.getNodeData( node ); - - if ( nodeData === undefined ) { - - nodeData = { vertex: {}, fragment: {}, compute: {} }; - - cache.setNodeData( node, nodeData ); - - } - - return shaderStage !== null ? nodeData[ shaderStage ] : nodeData; - - } - - getNodeProperties( node, shaderStage = this.shaderStage ) { - - const nodeData = this.getDataFromNode( node, shaderStage ); - - return nodeData.properties || ( nodeData.properties = { outputNode: null } ); - - } - - getBufferAttributeFromNode( node, type ) { - - const nodeData = this.getDataFromNode( node ); - - let bufferAttribute = nodeData.bufferAttribute; - - if ( bufferAttribute === undefined ) { - - const index = this.uniforms.index ++; - - bufferAttribute = new NodeAttribute( 'nodeAttribute' + index, type, node ); - - this.bufferAttributes.push( bufferAttribute ); - - nodeData.bufferAttribute = bufferAttribute; - - } - - return bufferAttribute; - - } - - getUniformFromNode( node, type, shaderStage = this.shaderStage ) { - - const nodeData = this.getDataFromNode( node, shaderStage ); - - let nodeUniform = nodeData.uniform; - - if ( nodeUniform === undefined ) { - - const index = this.uniforms.index ++; - - nodeUniform = new NodeUniform( 'nodeUniform' + index, type, node ); - - this.uniforms[ shaderStage ].push( nodeUniform ); - - nodeData.uniform = nodeUniform; - - } - - return nodeUniform; - - } - - getVarFromNode( node, type, shaderStage = this.shaderStage ) { - - const nodeData = this.getDataFromNode( node, shaderStage ); - - let nodeVar = nodeData.variable; - - if ( nodeVar === undefined ) { - - const vars = this.vars[ shaderStage ]; - const index = vars.length; - - nodeVar = new NodeVar( 'nodeVar' + index, type ); - - vars.push( nodeVar ); - - nodeData.variable = nodeVar; - - } - - return nodeVar; - - } - - getVaryingFromNode( node, type ) { - - const nodeData = this.getDataFromNode( node, null ); - - let nodeVarying = nodeData.varying; - - if ( nodeVarying === undefined ) { - - const varyings = this.varyings; - const index = varyings.length; - - nodeVarying = new NodeVarying( 'nodeVarying' + index, type ); - - varyings.push( nodeVarying ); - - nodeData.varying = nodeVarying; - - } - - return nodeVarying; - - } - - getCodeFromNode( node, type, shaderStage = this.shaderStage ) { - - const nodeData = this.getDataFromNode( node ); - - let nodeCode = nodeData.code; - - if ( nodeCode === undefined ) { - - const codes = this.codes[ shaderStage ]; - const index = codes.length; - - nodeCode = new NodeCode( 'nodeCode' + index, type ); - - codes.push( nodeCode ); - - nodeData.code = nodeCode; - - } - - return nodeCode; - - } - - addLineFlowCode( code ) { - - if ( code === '' ) return this; - - code = this.tab + code; - - if ( ! /;\s*$/.test( code ) ) { - - code = code + ';\n'; - - } - - this.flow.code += code; - - return this; - - } - - addFlowCode( code ) { - - this.flow.code += code; - - return this; - - } - - addFlowTab() { - - this.tab += '\t'; - - return this; - - } - - removeFlowTab() { - - this.tab = this.tab.slice( 0, - 1 ); - - return this; - - } - - getFlowData( node/*, shaderStage*/ ) { - - return this.flowsData.get( node ); - - } - - flowNode( node ) { - - const output = node.getNodeType( this ); - - const flowData = this.flowChildNode( node, output ); - - this.flowsData.set( node, flowData ); - - return flowData; - - } - - flowChildNode( node, output = null ) { - - const previousFlow = this.flow; - - const flow = { - code: '', - }; - - this.flow = flow; - - flow.result = node.build( this, output ); - - this.flow = previousFlow; - - return flow; - - } - - flowNodeFromShaderStage( shaderStage, node, output = null, propertyName = null ) { - - const previousShaderStage = this.shaderStage; - - this.setShaderStage( shaderStage ); - - const flowData = this.flowChildNode( node, output ); - - if ( propertyName !== null ) { - - flowData.code += `${ this.tab + propertyName } = ${ flowData.result };\n`; - - } - - this.flowCode[ shaderStage ] = this.flowCode[ shaderStage ] + flowData.code; - - this.setShaderStage( previousShaderStage ); - - return flowData; - - } - - getAttributesArray() { - - return this.attributes.concat( this.bufferAttributes ); - - } - - getAttributes( /*shaderStage*/ ) { - - console.warn( 'Abstract function.' ); - - } - - getVaryings( /*shaderStage*/ ) { - - console.warn( 'Abstract function.' ); - - } - - getVar( type, name ) { - - return `${type} ${name}`; - - } - - getVars( shaderStage ) { - - let snippet = ''; - - const vars = this.vars[ shaderStage ]; - - for ( const variable of vars ) { - - snippet += `${ this.getVar( variable.type, variable.name ) }; `; - - } - - return snippet; - - } - - getUniforms( /*shaderStage*/ ) { - - console.warn( 'Abstract function.' ); - - } - - getCodes( shaderStage ) { - - const codes = this.codes[ shaderStage ]; - - let code = ''; - - for ( const nodeCode of codes ) { - - code += nodeCode.code + '\n'; - - } - - return code; - - } - - getHash() { - - return this.vertexShader + this.fragmentShader + this.computeShader; - - } - - setShaderStage( shaderStage ) { - - this.shaderStage = shaderStage; - - } - - getShaderStage() { - - return this.shaderStage; - - } - - setBuildStage( buildStage ) { - - this.buildStage = buildStage; - - } - - getBuildStage() { - - return this.buildStage; - - } - - buildCode() { - - console.warn( 'Abstract function.' ); - - } - - build() { - - // construct() -> stage 1: create possible new nodes and returns an output reference node - // analyze() -> stage 2: analyze nodes to possible optimization and validation - // generate() -> stage 3: generate shader - - for ( const buildStage of defaultBuildStages ) { - - this.setBuildStage( buildStage ); - - if ( this.context.vertex && this.context.vertex.isNode ) { - - this.flowNodeFromShaderStage( 'vertex', this.context.vertex ); - - } - - for ( const shaderStage of shaderStages ) { - - this.setShaderStage( shaderStage ); - - const flowNodes = this.flowNodes[ shaderStage ]; - - for ( const node of flowNodes ) { - - if ( buildStage === 'generate' ) { - - this.flowNode( node ); - - } else { - - node.build( this ); - - } - - } - - } - - } - - this.setBuildStage( null ); - this.setShaderStage( null ); - - // stage 4: build code for a specific output - - this.buildCode(); - - return this; - - } - - format( snippet, fromType, toType ) { - - fromType = this.getVectorType( fromType ); - toType = this.getVectorType( toType ); - - if ( fromType === toType || toType === null || this.isReference( toType ) ) { - - return snippet; - - } - - const fromTypeLength = this.getTypeLength( fromType ); - const toTypeLength = this.getTypeLength( toType ); - - if ( fromTypeLength > 4 ) { // fromType is matrix-like - - // @TODO: ignore for now - - return snippet; - - } - - if ( toTypeLength > 4 || toTypeLength === 0 ) { // toType is matrix-like or unknown - - // @TODO: ignore for now - - return snippet; - - } - - if ( fromTypeLength === toTypeLength ) { - - return `${ this.getType( toType ) }( ${ snippet } )`; - - } - - if ( fromTypeLength > toTypeLength ) { - - return this.format( `${ snippet }.${ 'xyz'.slice( 0, toTypeLength ) }`, this.getTypeFromLength( toTypeLength, this.getComponentType( fromType ) ), toType ); - - } - - if ( toTypeLength === 4 ) { // toType is vec4-like - - return `${ this.getType( toType ) }( ${ this.format( snippet, fromType, 'vec3' ) }, 1.0 )`; - - } - - if ( fromTypeLength === 2 ) { // fromType is vec2-like and toType is vec3-like - - return `${ this.getType( toType ) }( ${ this.format( snippet, fromType, 'vec2' ) }, 0.0 )`; - - } - - return `${ this.getType( toType ) }( ${ snippet } )`; // fromType is float-like - - } - - getSignature() { - - return `// Three.js r${ REVISION } - NodeMaterial System\n`; - - } - -} - -export default NodeBuilder; diff --git a/examples/jsm/nodes/core/NodeCache.js b/examples/jsm/nodes/core/NodeCache.js deleted file mode 100644 index 6c73981e4d5884..00000000000000 --- a/examples/jsm/nodes/core/NodeCache.js +++ /dev/null @@ -1,26 +0,0 @@ -let id = 0; - -class NodeCache { - - constructor() { - - this.id = id ++; - this.nodesData = new WeakMap(); - - } - - getNodeData( node ) { - - return this.nodesData.get( node ); - - } - - setNodeData( node, data ) { - - this.nodesData.set( node, data ); - - } - -} - -export default NodeCache; diff --git a/examples/jsm/nodes/core/NodeCode.js b/examples/jsm/nodes/core/NodeCode.js deleted file mode 100644 index ac47d0a46fb96f..00000000000000 --- a/examples/jsm/nodes/core/NodeCode.js +++ /dev/null @@ -1,15 +0,0 @@ -class NodeCode { - - constructor( name, type, code = '' ) { - - this.name = name; - this.type = type; - this.code = code; - - Object.defineProperty( this, 'isNodeCode', { value: true } ); - - } - -} - -export default NodeCode; diff --git a/examples/jsm/nodes/core/NodeFrame.js b/examples/jsm/nodes/core/NodeFrame.js deleted file mode 100644 index de0d4f98f6ec90..00000000000000 --- a/examples/jsm/nodes/core/NodeFrame.js +++ /dev/null @@ -1,110 +0,0 @@ -import { NodeUpdateType } from './constants.js'; - -class NodeFrame { - - constructor() { - - this.time = 0; - this.deltaTime = 0; - - this.frameId = 0; - this.renderId = 0; - - this.startTime = null; - - this.frameMap = new WeakMap(); - this.frameBeforeMap = new WeakMap(); - this.renderMap = new WeakMap(); - this.renderBeforeMap = new WeakMap(); - - this.renderer = null; - this.material = null; - this.camera = null; - this.object = null; - this.scene = null; - - } - - updateBeforeNode( node ) { - - const updateType = node.getUpdateBeforeType(); - - if ( updateType === NodeUpdateType.FRAME ) { - - if ( this.frameBeforeMap.get( node ) !== this.frameId ) { - - this.frameBeforeMap.set( node, this.frameId ); - - node.updateBefore( this ); - - } - - } else if ( updateType === NodeUpdateType.RENDER ) { - - if ( this.renderBeforeMap.get( node ) !== this.renderId || this.frameBeforeMap.get( node ) !== this.frameId ) { - - this.renderBeforeMap.set( node, this.renderId ); - this.frameBeforeMap.set( node, this.frameId ); - - node.updateBefore( this ); - - } - - } else if ( updateType === NodeUpdateType.OBJECT ) { - - node.updateBefore( this ); - - } - - } - - updateNode( node ) { - - const updateType = node.getUpdateType(); - - if ( updateType === NodeUpdateType.FRAME ) { - - if ( this.frameMap.get( node ) !== this.frameId ) { - - this.frameMap.set( node, this.frameId ); - - node.update( this ); - - } - - } else if ( updateType === NodeUpdateType.RENDER ) { - - if ( this.renderMap.get( node ) !== this.renderId || this.frameMap.get( node ) !== this.frameId ) { - - this.renderMap.set( node, this.renderId ); - this.frameMap.set( node, this.frameId ); - - node.update( this ); - - } - - } else if ( updateType === NodeUpdateType.OBJECT ) { - - node.update( this ); - - } - - } - - update() { - - this.frameId ++; - - if ( this.lastTime === undefined ) this.lastTime = performance.now(); - - this.deltaTime = ( performance.now() - this.lastTime ) / 1000; - - this.lastTime = performance.now(); - - this.time += this.deltaTime; - - } - -} - -export default NodeFrame; diff --git a/examples/jsm/nodes/core/NodeFunction.js b/examples/jsm/nodes/core/NodeFunction.js deleted file mode 100644 index 646dabe059299d..00000000000000 --- a/examples/jsm/nodes/core/NodeFunction.js +++ /dev/null @@ -1,22 +0,0 @@ -class NodeFunction { - - constructor( type, inputs, name = '', presicion = '' ) { - - this.type = type; - this.inputs = inputs; - this.name = name; - this.presicion = presicion; - - } - - getCode( /*name = this.name*/ ) { - - console.warn( 'Abstract function.' ); - - } - -} - -NodeFunction.isNodeFunction = true; - -export default NodeFunction; diff --git a/examples/jsm/nodes/core/NodeFunctionInput.js b/examples/jsm/nodes/core/NodeFunctionInput.js deleted file mode 100644 index 09ac2c4d57c02d..00000000000000 --- a/examples/jsm/nodes/core/NodeFunctionInput.js +++ /dev/null @@ -1,17 +0,0 @@ -class NodeFunctionInput { - - constructor( type, name, count = null, qualifier = '', isConst = false ) { - - this.type = type; - this.name = name; - this.count = count; - this.qualifier = qualifier; - this.isConst = isConst; - - } - -} - -NodeFunctionInput.isNodeFunctionInput = true; - -export default NodeFunctionInput; diff --git a/examples/jsm/nodes/core/NodeKeywords.js b/examples/jsm/nodes/core/NodeKeywords.js deleted file mode 100644 index 27f6cd856102a6..00000000000000 --- a/examples/jsm/nodes/core/NodeKeywords.js +++ /dev/null @@ -1,80 +0,0 @@ -class NodeKeywords { - - constructor() { - - this.keywords = []; - this.nodes = []; - this.keywordsCallback = {}; - - } - - getNode( name ) { - - let node = this.nodes[ name ]; - - if ( node === undefined && this.keywordsCallback[ name ] !== undefined ) { - - node = this.keywordsCallback[ name ]( name ); - - this.nodes[ name ] = node; - - } - - return node; - - } - - addKeyword( name, callback ) { - - this.keywords.push( name ); - this.keywordsCallback[ name ] = callback; - - return this; - - } - - parse( code ) { - - const keywordNames = this.keywords; - - const regExp = new RegExp( `\\b${keywordNames.join( '\\b|\\b' )}\\b`, 'g' ); - - const codeKeywords = code.match( regExp ); - - const keywordNodes = []; - - if ( codeKeywords !== null ) { - - for ( const keyword of codeKeywords ) { - - const node = this.getNode( keyword ); - - if ( node !== undefined && keywordNodes.indexOf( node ) === - 1 ) { - - keywordNodes.push( node ); - - } - - } - - } - - return keywordNodes; - - } - - include( builder, code ) { - - const keywordNodes = this.parse( code ); - - for ( const keywordNode of keywordNodes ) { - - keywordNode.build( builder ); - - } - - } - -} - -export default NodeKeywords; diff --git a/examples/jsm/nodes/core/NodeParser.js b/examples/jsm/nodes/core/NodeParser.js deleted file mode 100644 index 2c9a39fa009e98..00000000000000 --- a/examples/jsm/nodes/core/NodeParser.js +++ /dev/null @@ -1,11 +0,0 @@ -class NodeParser { - - parseFunction( /*source*/ ) { - - console.warn( 'Abstract function.' ); - - } - -} - -export default NodeParser; diff --git a/examples/jsm/nodes/core/NodeUniform.js b/examples/jsm/nodes/core/NodeUniform.js deleted file mode 100644 index 5dc7dc0948557e..00000000000000 --- a/examples/jsm/nodes/core/NodeUniform.js +++ /dev/null @@ -1,28 +0,0 @@ -class NodeUniform { - - constructor( name, type, node, needsUpdate = undefined ) { - - this.isNodeUniform = true; - - this.name = name; - this.type = type; - this.node = node; - this.needsUpdate = needsUpdate; - - } - - get value() { - - return this.node.value; - - } - - set value( val ) { - - this.node.value = val; - - } - -} - -export default NodeUniform; diff --git a/examples/jsm/nodes/core/NodeUtils.js b/examples/jsm/nodes/core/NodeUtils.js deleted file mode 100644 index deed0cbf9d2579..00000000000000 --- a/examples/jsm/nodes/core/NodeUtils.js +++ /dev/null @@ -1,212 +0,0 @@ -import { Color, Matrix3, Matrix4, Vector2, Vector3, Vector4 } from 'three'; - -export function getCacheKey( object ) { - - let cacheKey = '{'; - - if ( object.isNode === true ) { - - cacheKey += `uuid:"${ object.uuid }"`; - - } - - for ( const { property, index, childNode } of getNodeChildren( object ) ) { - - // @TODO: Think about implement NodeArray and NodeObject. - - let childCacheKey = getCacheKey( childNode ); - if ( ! childCacheKey.includes( ',' ) ) childCacheKey = childCacheKey.slice( childCacheKey.indexOf( '"' ), childCacheKey.indexOf( '}' ) ); - cacheKey += `,${ property }${ index !== undefined ? '/' + index : '' }:${ childCacheKey }`; - - } - - cacheKey += '}'; - - return cacheKey; - -} - -export function* getNodeChildren( node, toJSON = false ) { - - for ( const property in node ) { - - // Ignore private properties. - if ( property.startsWith( '_' ) === true ) continue; - - const object = node[ property ]; - - if ( Array.isArray( object ) === true ) { - - for ( let i = 0; i < object.length; i ++ ) { - - const child = object[ i ]; - - if ( child && ( child.isNode === true || toJSON && typeof child.toJSON === 'function' ) ) { - - yield { property, index: i, childNode: child }; - - } - - } - - } else if ( object && object.isNode === true ) { - - yield { property, childNode: object }; - - } else if ( typeof object === 'object' ) { - - for ( const subProperty in object ) { - - const child = object[ subProperty ]; - - if ( child && ( child.isNode === true || toJSON && typeof child.toJSON === 'function' ) ) { - - yield { property, index: subProperty, childNode: child }; - - } - - } - - } - - } - -} - -export function getValueType( value ) { - - if ( value === undefined || value === null ) return null; - - const typeOf = typeof value; - - if ( value.isNode === true ) { - - return 'node'; - - } else if ( typeOf === 'number' ) { - - return 'float'; - - } else if ( typeOf === 'boolean' ) { - - return 'bool'; - - } else if ( typeOf === 'string' ) { - - return 'string'; - - } else if ( typeOf === 'function' ) { - - return 'shader'; - - } else if ( value.isVector2 === true ) { - - return 'vec2'; - - } else if ( value.isVector3 === true ) { - - return 'vec3'; - - } else if ( value.isVector4 === true ) { - - return 'vec4'; - - } else if ( value.isMatrix3 === true ) { - - return 'mat3'; - - } else if ( value.isMatrix4 === true ) { - - return 'mat4'; - - } else if ( value.isColor === true ) { - - return 'color'; - - } else if ( value instanceof ArrayBuffer ) { - - return 'ArrayBuffer'; - - } - - return null; - -} - -export function getValueFromType( type, ...params ) { - - const last4 = type ? type.slice( - 4 ) : undefined; - - if ( ( last4 === 'vec2' || last4 === 'vec3' || last4 === 'vec4' ) && params.length === 1 ) { // ensure same behaviour as in NodeBuilder.format() - - params = last4 === 'vec2' ? [ params[ 0 ], params[ 0 ] ] : [ params[ 0 ], params[ 0 ], params[ 0 ] ]; - - } - - if ( type === 'color' ) { - - return new Color( ...params ); - - } else if ( last4 === 'vec2' ) { - - return new Vector2( ...params ); - - } else if ( last4 === 'vec3' ) { - - return new Vector3( ...params ); - - } else if ( last4 === 'vec4' ) { - - return new Vector4( ...params ); - - } else if ( last4 === 'mat3' ) { - - return new Matrix3( ...params ); - - } else if ( last4 === 'mat4' ) { - - return new Matrix4( ...params ); - - } else if ( type === 'bool' ) { - - return params[ 0 ] || false; - - } else if ( ( type === 'float' ) || ( type === 'int' ) || ( type === 'uint' ) ) { - - return params[ 0 ] || 0; - - } else if ( type === 'string' ) { - - return params[ 0 ] || ''; - - } else if ( type === 'ArrayBuffer' ) { - - return base64ToArrayBuffer( params[ 0 ] ); - - } - - return null; - -} - -export function arrayBufferToBase64( arrayBuffer ) { - - let chars = ''; - - const array = new Uint8Array( arrayBuffer ); - - for ( let i = 0; i < array.length; i ++ ) { - - chars += String.fromCharCode( array[ i ] ); - - } - - return btoa( chars ); - -} - -export function base64ToArrayBuffer( base64 ) { - - return Uint8Array.from( atob( base64 ), c => c.charCodeAt( 0 ) ).buffer; - -} diff --git a/examples/jsm/nodes/core/NodeVar.js b/examples/jsm/nodes/core/NodeVar.js deleted file mode 100644 index 59bb62584bad06..00000000000000 --- a/examples/jsm/nodes/core/NodeVar.js +++ /dev/null @@ -1,14 +0,0 @@ -class NodeVar { - - constructor( name, type ) { - - this.isNodeVar = true; - - this.name = name; - this.type = type; - - } - -} - -export default NodeVar; diff --git a/examples/jsm/nodes/core/NodeVarying.js b/examples/jsm/nodes/core/NodeVarying.js deleted file mode 100644 index 0a71413bf9969f..00000000000000 --- a/examples/jsm/nodes/core/NodeVarying.js +++ /dev/null @@ -1,17 +0,0 @@ -import NodeVar from './NodeVar.js'; - -class NodeVarying extends NodeVar { - - constructor( name, type ) { - - super( name, type ); - - this.needsInterpolation = false; - - this.isNodeVarying = true; - - } - -} - -export default NodeVarying; diff --git a/examples/jsm/nodes/core/PropertyNode.js b/examples/jsm/nodes/core/PropertyNode.js deleted file mode 100644 index a8298d4681f982..00000000000000 --- a/examples/jsm/nodes/core/PropertyNode.js +++ /dev/null @@ -1,53 +0,0 @@ -import Node, { addNodeClass } from './Node.js'; -import { nodeImmutable, nodeObject } from '../shadernode/ShaderNode.js'; - -class PropertyNode extends Node { - - constructor( nodeType, name = null ) { - - super( nodeType ); - - this.name = name; - - } - - getHash( builder ) { - - return this.name || super.getHash( builder ); - - } - - isGlobal( /*builder*/ ) { - - return true; - - } - - generate( builder ) { - - const nodeVary = builder.getVarFromNode( this, this.getNodeType( builder ) ); - const name = this.name; - - if ( name !== null ) { - - nodeVary.name = name; - - } - - return builder.getPropertyName( nodeVary ); - - } - -} - -export default PropertyNode; - -export const property = ( type, name ) => nodeObject( new PropertyNode( type, name ) ); - -export const diffuseColor = nodeImmutable( PropertyNode, 'vec4', 'DiffuseColor' ); -export const roughness = nodeImmutable( PropertyNode, 'float', 'Roughness' ); -export const metalness = nodeImmutable( PropertyNode, 'float', 'Metalness' ); -export const specularColor = nodeImmutable( PropertyNode, 'color', 'SpecularColor' ); -export const shininess = nodeImmutable( PropertyNode, 'float', 'Shininess' ); - -addNodeClass( PropertyNode ); diff --git a/examples/jsm/nodes/core/StackNode.js b/examples/jsm/nodes/core/StackNode.js deleted file mode 100644 index 3c93c321ef097b..00000000000000 --- a/examples/jsm/nodes/core/StackNode.js +++ /dev/null @@ -1,99 +0,0 @@ -import Node, { addNodeClass } from './Node.js'; -import { assign } from '../math/OperatorNode.js'; -import { bypass } from '../core/BypassNode.js'; -import { expression } from '../code/ExpressionNode.js'; -import { cond } from '../math/CondNode.js'; -import { loop } from '../utils/LoopNode.js'; -import { nodeProxy, shader } from '../shadernode/ShaderNode.js'; - -class StackNode extends Node { - - constructor( parent = null ) { - - super(); - - this.nodes = []; - this.outputNode = null; - - this.parent = parent; - - this._currentCond = null; - - this.isStackNode = true; - - } - - getNodeType( builder ) { - - return this.outputNode ? this.outputNode.getNodeType( builder ) : 'void'; - - } - - add( node ) { - - this.nodes.push( bypass( expression(), node ) ); - - return this; - - } - - if( boolNode, method ) { - - const methodNode = shader( method ); - this._currentCond = cond( boolNode, methodNode ); - - return this.add( this._currentCond ); - - } - - elseif( boolNode, method ) { - - const methodNode = shader( method ); - const ifNode = cond( boolNode, methodNode ); - - this._currentCond.elseNode = ifNode; - this._currentCond = ifNode; - - return this; - - } - - else( method ) { - - this._currentCond.elseNode = shader( method ); - - return this; - - } - - assign( targetNode, sourceValue ) { - - return this.add( assign( targetNode, sourceValue ) ); - - } - - loop( ...params ) { - - return this.add( loop( ...params ) ); - - } - - build( builder, ...params ) { - - for ( const node of this.nodes ) { - - node.build( builder ); - - } - - return this.outputNode ? this.outputNode.build( builder, ...params ) : super.build( builder, ...params ); - - } - -} - -export default StackNode; - -export const stack = nodeProxy( StackNode ); - -addNodeClass( StackNode ); diff --git a/examples/jsm/nodes/core/TempNode.js b/examples/jsm/nodes/core/TempNode.js deleted file mode 100644 index 107f1e65b9dd84..00000000000000 --- a/examples/jsm/nodes/core/TempNode.js +++ /dev/null @@ -1,58 +0,0 @@ -import Node, { addNodeClass } from './Node.js'; - -class TempNode extends Node { - - constructor( type ) { - - super( type ); - - this.isTempNode = true; - - } - - hasDependencies( builder ) { - - return builder.getDataFromNode( this ).dependenciesCount > 1; - - } - - build( builder, output ) { - - const buildStage = builder.getBuildStage(); - - if ( buildStage === 'generate' ) { - - const type = builder.getVectorType( this.getNodeType( builder, output ) ); - const nodeData = builder.getDataFromNode( this ); - - if ( builder.context.tempRead !== false && nodeData.propertyName !== undefined ) { - - return builder.format( nodeData.propertyName, type, output ); - - } else if ( builder.context.tempWrite !== false && type !== 'void' && output !== 'void' && this.hasDependencies( builder ) ) { - - const snippet = super.build( builder, type ); - - const nodeVar = builder.getVarFromNode( this, type ); - const propertyName = builder.getPropertyName( nodeVar ); - - builder.addLineFlowCode( `${propertyName} = ${snippet}` ); - - nodeData.snippet = snippet; - nodeData.propertyName = propertyName; - - return builder.format( nodeData.propertyName, type, output ); - - } - - } - - return super.build( builder, output ); - - } - -} - -export default TempNode; - -addNodeClass( TempNode ); diff --git a/examples/jsm/nodes/core/UniformNode.js b/examples/jsm/nodes/core/UniformNode.js deleted file mode 100644 index f730906f3deb2b..00000000000000 --- a/examples/jsm/nodes/core/UniformNode.js +++ /dev/null @@ -1,61 +0,0 @@ -import InputNode from './InputNode.js'; -import { addNodeClass } from './Node.js'; -import { nodeObject, getConstNodeType } from '../shadernode/ShaderNode.js'; - -class UniformNode extends InputNode { - - constructor( value, nodeType = null ) { - - super( value, nodeType ); - - this.isUniformNode = true; - - } - - getUniformHash( builder ) { - - return this.getHash( builder ); - - } - - generate( builder, output ) { - - const type = this.getNodeType( builder ); - - const hash = this.getUniformHash( builder ); - - let sharedNode = builder.getNodeFromHash( hash ); - - if ( sharedNode === undefined ) { - - builder.setHashNode( this, hash ); - - sharedNode = this; - - } - - const sharedNodeType = sharedNode.getInputType( builder ); - - const nodeUniform = builder.getUniformFromNode( sharedNode, sharedNodeType, builder.shaderStage ); - const propertyName = builder.getPropertyName( nodeUniform ); - - return builder.format( propertyName, type, output ); - - } - -} - -export default UniformNode; - -export const uniform = ( arg1, arg2 ) => { - - const nodeType = getConstNodeType( arg2 || arg1 ); - - // @TODO: get ConstNode from .traverse() in the future - const value = ( arg1 && arg1.isNode === true ) ? ( arg1.node && arg1.node.value ) || arg1.value : arg1; - - return nodeObject( new UniformNode( value, nodeType ) ); - -}; - -addNodeClass( UniformNode ); diff --git a/examples/jsm/nodes/core/VarNode.js b/examples/jsm/nodes/core/VarNode.js deleted file mode 100644 index ecd190c4e3ca08..00000000000000 --- a/examples/jsm/nodes/core/VarNode.js +++ /dev/null @@ -1,89 +0,0 @@ -import Node, { addNodeClass } from './Node.js'; -import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js'; - -class VarNode extends Node { - - constructor( node, name = null ) { - - super(); - - this.node = node; - this.name = name; - - } - - assign( node ) { - - node.traverse( ( childNode, replaceNode ) => { - - if ( replaceNode && childNode.uuid === this.uuid ) { - - replaceNode( this.node ); - - } - - } ); - this.node = node; - return this; - - } - - isGlobal() { - - return true; - - } - - getHash( builder ) { - - return this.name || super.getHash( builder ); - - } - - getNodeType( builder ) { - - return this.node.getNodeType( builder ); - - } - - generate( builder ) { - - const node = this.node; - const name = this.name; - - if ( name === null && node.isTempNode === true ) { - - return node.build( builder ); - - } - - const type = builder.getVectorType( this.getNodeType( builder ) ); - - const snippet = node.build( builder, type ); - const nodeVar = builder.getVarFromNode( this, type ); - - if ( name !== null ) { - - nodeVar.name = name; - - } - - const propertyName = builder.getPropertyName( nodeVar ); - - builder.addLineFlowCode( `${propertyName} = ${snippet}` ); - - return propertyName; - - } - -} - -export default VarNode; - -export const label = nodeProxy( VarNode ); -export const temp = label; - -addNodeElement( 'label', label ); -addNodeElement( 'temp', temp ); - -addNodeClass( VarNode ); diff --git a/examples/jsm/nodes/core/VaryingNode.js b/examples/jsm/nodes/core/VaryingNode.js deleted file mode 100644 index e936c8336620ac..00000000000000 --- a/examples/jsm/nodes/core/VaryingNode.js +++ /dev/null @@ -1,69 +0,0 @@ -import Node, { addNodeClass } from './Node.js'; -import { NodeShaderStage } from './constants.js'; -import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js'; - -class VaryingNode extends Node { - - constructor( node, name = null ) { - - super(); - - this.node = node; - this.name = name; - - } - - isGlobal() { - - return true; - - } - - getHash( builder ) { - - return this.name || super.getHash( builder ); - - } - - getNodeType( builder ) { - - // VaryingNode is auto type - - return this.node.getNodeType( builder ); - - } - - generate( builder ) { - - const { name, node } = this; - const type = this.getNodeType( builder ); - - const nodeVarying = builder.getVaryingFromNode( this, type ); - - // this property can be used to check if the varying can be optimized for a var - nodeVarying.needsInterpolation || ( nodeVarying.needsInterpolation = ( builder.shaderStage === 'fragment' ) ); - - if ( name !== null ) { - - nodeVarying.name = name; - - } - - const propertyName = builder.getPropertyName( nodeVarying, NodeShaderStage.VERTEX ); - - // force node run in vertex stage - builder.flowNodeFromShaderStage( NodeShaderStage.VERTEX, node, type, propertyName ); - - return builder.getPropertyName( nodeVarying ); - - } - -} - -export default VaryingNode; - -export const varying = nodeProxy( VaryingNode ); - -addNodeElement( 'varying', varying ); - -addNodeClass( VaryingNode ); diff --git a/examples/jsm/nodes/core/constants.js b/examples/jsm/nodes/core/constants.js deleted file mode 100644 index a4f7acf41a17ec..00000000000000 --- a/examples/jsm/nodes/core/constants.js +++ /dev/null @@ -1,27 +0,0 @@ -export const NodeShaderStage = { - VERTEX: 'vertex', - FRAGMENT: 'fragment' -}; - -export const NodeUpdateType = { - NONE: 'none', - FRAME: 'frame', - RENDER: 'render', - OBJECT: 'object' -}; - -export const NodeType = { - BOOLEAN: 'bool', - INTEGER: 'int', - FLOAT: 'float', - VECTOR2: 'vec2', - VECTOR3: 'vec3', - VECTOR4: 'vec4', - MATRIX3: 'mat3', - MATRIX4: 'mat4' -}; - -export const defaultShaderStages = [ 'fragment', 'vertex' ]; -export const defaultBuildStages = [ 'construct', 'analyze', 'generate' ]; -export const shaderStages = [ ...defaultShaderStages, 'compute' ]; -export const vectorComponents = [ 'x', 'y', 'z', 'w' ]; diff --git a/examples/jsm/nodes/display/BlendModeNode.js b/examples/jsm/nodes/display/BlendModeNode.js deleted file mode 100644 index 60c4db6b747154..00000000000000 --- a/examples/jsm/nodes/display/BlendModeNode.js +++ /dev/null @@ -1,99 +0,0 @@ -import TempNode from '../core/TempNode.js'; -import { EPSILON } from '../math/MathNode.js'; -import { addNodeClass } from '../core/Node.js'; -import { addNodeElement, ShaderNode, nodeProxy, vec3 } from '../shadernode/ShaderNode.js'; - -export const BurnNode = new ShaderNode( ( { base, blend } ) => { - - const fn = ( c ) => blend[ c ].lessThan( EPSILON ).cond( blend[ c ], base[ c ].oneMinus().div( blend[ c ] ).oneMinus().max( 0 ) ); - - return vec3( fn( 'x' ), fn( 'y' ), fn( 'z' ) ); - -} ); - -export const DodgeNode = new ShaderNode( ( { base, blend } ) => { - - const fn = ( c ) => blend[ c ].equal( 1.0 ).cond( blend[ c ], base[ c ].div( blend[ c ].oneMinus() ).max( 0 ) ); - - return vec3( fn( 'x' ), fn( 'y' ), fn( 'z' ) ); - -} ); - -export const ScreenNode = new ShaderNode( ( { base, blend } ) => { - - const fn = ( c ) => base[ c ].oneMinus().mul( blend[ c ].oneMinus() ).oneMinus(); - - return vec3( fn( 'x' ), fn( 'y' ), fn( 'z' ) ); - -} ); - -export const OverlayNode = new ShaderNode( ( { base, blend } ) => { - - const fn = ( c ) => base[ c ].lessThan( 0.5 ).cond( base[ c ].mul( blend[ c ], 2.0 ), base[ c ].oneMinus().mul( blend[ c ].oneMinus() ).oneMinus() ); - - return vec3( fn( 'x' ), fn( 'y' ), fn( 'z' ) ); - -} ); - -class BlendModeNode extends TempNode { - - constructor( blendMode, baseNode, blendNode ) { - - super(); - - this.blendMode = blendMode; - - this.baseNode = baseNode; - this.blendNode = blendNode; - - } - - construct() { - - const { blendMode, baseNode, blendNode } = this; - const params = { base: baseNode, blend: blendNode }; - - let outputNode = null; - - if ( blendMode === BlendModeNode.BURN ) { - - outputNode = BurnNode.call( params ); - - } else if ( blendMode === BlendModeNode.DODGE ) { - - outputNode = DodgeNode.call( params ); - - } else if ( blendMode === BlendModeNode.SCREEN ) { - - outputNode = ScreenNode.call( params ); - - } else if ( blendMode === BlendModeNode.OVERLAY ) { - - outputNode = OverlayNode.call( params ); - - } - - return outputNode; - - } - -} - -BlendModeNode.BURN = 'burn'; -BlendModeNode.DODGE = 'dodge'; -BlendModeNode.SCREEN = 'screen'; -BlendModeNode.OVERLAY = 'overlay'; - -export default BlendModeNode; - -export const burn = nodeProxy( BlendModeNode, BlendModeNode.BURN ); -export const dodge = nodeProxy( BlendModeNode, BlendModeNode.DODGE ); -export const overlay = nodeProxy( BlendModeNode, BlendModeNode.OVERLAY ); -export const screen = nodeProxy( BlendModeNode, BlendModeNode.SCREEN ); - -addNodeElement( 'burn', burn ); -addNodeElement( 'dodge', dodge ); -addNodeElement( 'overlay', overlay ); -addNodeElement( 'screen', screen ); - -addNodeClass( BlendModeNode ); diff --git a/examples/jsm/nodes/display/ColorAdjustmentNode.js b/examples/jsm/nodes/display/ColorAdjustmentNode.js deleted file mode 100644 index 7b757853f2ad8f..00000000000000 --- a/examples/jsm/nodes/display/ColorAdjustmentNode.js +++ /dev/null @@ -1,100 +0,0 @@ -import TempNode from '../core/TempNode.js'; -import { dot, mix } from '../math/MathNode.js'; -import { add } from '../math/OperatorNode.js'; -import { addNodeClass } from '../core/Node.js'; -import { addNodeElement, ShaderNode, nodeProxy, float, vec3, mat3 } from '../shadernode/ShaderNode.js'; - -const saturationNode = new ShaderNode( ( { color, adjustment } ) => { - - return adjustment.mix( luminance( color ), color ); - -} ); - -const vibranceNode = new ShaderNode( ( { color, adjustment } ) => { - - const average = add( color.r, color.g, color.b ).div( 3.0 ); - - const mx = color.r.max( color.g.max( color.b ) ); - const amt = mx.sub( average ).mul( adjustment ).mul( - 3.0 ); - - return mix( color, mx, amt ); - -} ); - -const hueNode = new ShaderNode( ( { color, adjustment } ) => { - - const RGBtoYIQ = mat3( 0.299, 0.587, 0.114, 0.595716, - 0.274453, - 0.321263, 0.211456, - 0.522591, 0.311135 ); - const YIQtoRGB = mat3( 1.0, 0.9563, 0.6210, 1.0, - 0.2721, - 0.6474, 1.0, - 1.107, 1.7046 ); - - const yiq = RGBtoYIQ.mul( color ); - - const hue = yiq.z.atan2( yiq.y ).add( adjustment ); - const chroma = yiq.yz.length(); - - return YIQtoRGB.mul( vec3( yiq.x, chroma.mul( hue.cos() ), chroma.mul( hue.sin() ) ) ); - -} ); - -class ColorAdjustmentNode extends TempNode { - - constructor( method, colorNode, adjustmentNode = float( 1 ) ) { - - super( 'vec3' ); - - this.method = method; - - this.colorNode = colorNode; - this.adjustmentNode = adjustmentNode; - - } - - construct() { - - const { method, colorNode, adjustmentNode } = this; - - const callParams = { color: colorNode, adjustment: adjustmentNode }; - - let outputNode = null; - - if ( method === ColorAdjustmentNode.SATURATION ) { - - outputNode = saturationNode.call( callParams ); - - } else if ( method === ColorAdjustmentNode.VIBRANCE ) { - - outputNode = vibranceNode.call( callParams ); - - } else if ( method === ColorAdjustmentNode.HUE ) { - - outputNode = hueNode.call( callParams ); - - } else { - - console.error( `${ this.type }: Method "${ this.method }" not supported!` ); - - } - - return outputNode; - - } - -} - -ColorAdjustmentNode.SATURATION = 'saturation'; -ColorAdjustmentNode.VIBRANCE = 'vibrance'; -ColorAdjustmentNode.HUE = 'hue'; - -export default ColorAdjustmentNode; - -export const saturation = nodeProxy( ColorAdjustmentNode, ColorAdjustmentNode.SATURATION ); -export const vibrance = nodeProxy( ColorAdjustmentNode, ColorAdjustmentNode.VIBRANCE ); -export const hue = nodeProxy( ColorAdjustmentNode, ColorAdjustmentNode.HUE ); - -export const lumaCoeffs = vec3( 0.2125, 0.7154, 0.0721 ); -export const luminance = ( color, luma = lumaCoeffs ) => dot( color, luma ); - -addNodeElement( 'saturation', saturation ); -addNodeElement( 'vibrance', vibrance ); -addNodeElement( 'hue', hue ); - -addNodeClass( ColorAdjustmentNode ); diff --git a/examples/jsm/nodes/display/ColorSpaceNode.js b/examples/jsm/nodes/display/ColorSpaceNode.js deleted file mode 100644 index 5bbd3ea97a6044..00000000000000 --- a/examples/jsm/nodes/display/ColorSpaceNode.js +++ /dev/null @@ -1,107 +0,0 @@ -import TempNode from '../core/TempNode.js'; -import { mix } from '../math/MathNode.js'; -import { addNodeClass } from '../core/Node.js'; -import { addNodeElement, ShaderNode, nodeObject, vec4 } from '../shadernode/ShaderNode.js'; - -import { LinearEncoding, LinearSRGBColorSpace, sRGBEncoding, SRGBColorSpace } from 'three'; - -export const LinearToLinear = new ShaderNode( ( inputs ) => { - - return inputs.value; - -} ); - -export const LinearTosRGB = new ShaderNode( ( inputs ) => { - - const { value } = inputs; - const { rgb } = value; - - const a = rgb.pow( 0.41666 ).mul( 1.055 ).sub( 0.055 ); - const b = rgb.mul( 12.92 ); - const factor = rgb.lessThanEqual( 0.0031308 ); - - const rgbResult = mix( a, b, factor ); - - return vec4( rgbResult, value.a ); - -} ); - -const EncodingLib = { - LinearToLinear, - LinearTosRGB -}; - -class ColorSpaceNode extends TempNode { - - constructor( method, node ) { - - super( 'vec4' ); - - this.method = method; - - this.node = node; - - } - - fromColorSpace( colorSpace ) { - - let method = null; - - if ( colorSpace === LinearSRGBColorSpace ) { - - method = 'Linear'; - - } else if ( colorSpace === SRGBColorSpace ) { - - method = 'sRGB'; - - } - - this.method = 'LinearTo' + method; - - return this; - - } - - fromEncoding( encoding ) { // @deprecated, r152 - - console.warn( 'THREE.ColorSpaceNode: Method .fromEncoding renamed to .fromColorSpace.' ); - - let method = null; - - if ( encoding === LinearEncoding ) { - - method = 'Linear'; - - } else if ( encoding === sRGBEncoding ) { - - method = 'sRGB'; - - } - - this.method = 'LinearTo' + method; - - return this; - - } - - construct() { - - const { method, node } = this; - - return EncodingLib[ method ].call( { value: node } ); - - } - -} - -ColorSpaceNode.LINEAR_TO_LINEAR = 'LinearToLinear'; -ColorSpaceNode.LINEAR_TO_SRGB = 'LinearTosRGB'; - -export default ColorSpaceNode; - -export const colorSpace = ( node, colorSpace ) => nodeObject( new ColorSpaceNode( null, nodeObject( node ) ).fromColorSpace( colorSpace ) ); - -addNodeElement( 'colorSpace', colorSpace ); - -addNodeClass( ColorSpaceNode ); diff --git a/examples/jsm/nodes/display/FrontFacingNode.js b/examples/jsm/nodes/display/FrontFacingNode.js deleted file mode 100644 index 762e2094dfe8ff..00000000000000 --- a/examples/jsm/nodes/display/FrontFacingNode.js +++ /dev/null @@ -1,27 +0,0 @@ -import Node, { addNodeClass } from '../core/Node.js'; -import { nodeImmutable, float } from '../shadernode/ShaderNode.js'; - -class FrontFacingNode extends Node { - - constructor() { - - super( 'bool' ); - - this.isFrontFacingNode = true; - - } - - generate( builder ) { - - return builder.getFrontFacing(); - - } - -} - -export default FrontFacingNode; - -export const frontFacing = nodeImmutable( FrontFacingNode ); -export const faceDirection = float( frontFacing ).mul( 2.0 ).sub( 1.0 ); - -addNodeClass( FrontFacingNode ); diff --git a/examples/jsm/nodes/display/NormalMapNode.js b/examples/jsm/nodes/display/NormalMapNode.js deleted file mode 100644 index e4fba88641e816..00000000000000 --- a/examples/jsm/nodes/display/NormalMapNode.js +++ /dev/null @@ -1,106 +0,0 @@ -import TempNode from '../core/TempNode.js'; -import { add } from '../math/OperatorNode.js'; -import { bitangentView } from '../accessors/BitangentNode.js'; -import { modelNormalMatrix } from '../accessors/ModelNode.js'; -import { normalView } from '../accessors/NormalNode.js'; -import { positionView } from '../accessors/PositionNode.js'; -import { tangentView } from '../accessors/TangentNode.js'; -import { uv } from '../accessors/UVNode.js'; -import { faceDirection } from './FrontFacingNode.js'; -import { addNodeClass } from '../core/Node.js'; -import { ShaderNode, nodeProxy, vec3, mat3 } from '../shadernode/ShaderNode.js'; - -import { TangentSpaceNormalMap, ObjectSpaceNormalMap } from 'three'; - -// Normal Mapping Without Precomputed Tangents -// http://www.thetenthplanet.de/archives/1180 - -const perturbNormal2ArbNode = new ShaderNode( ( inputs ) => { - - const { eye_pos, surf_norm, mapN, uv } = inputs; - - const q0 = eye_pos.dFdx(); - const q1 = eye_pos.dFdy(); - const st0 = uv.dFdx(); - const st1 = uv.dFdy(); - - const N = surf_norm; // normalized - - const q1perp = q1.cross( N ); - const q0perp = N.cross( q0 ); - - const T = q1perp.mul( st0.x ).add( q0perp.mul( st1.x ) ); - const B = q1perp.mul( st0.y ).add( q0perp.mul( st1.y ) ); - - const det = T.dot( T ).max( B.dot( B ) ); - const scale = faceDirection.mul( det.inverseSqrt() ); - - return add( T.mul( mapN.x, scale ), B.mul( mapN.y, scale ), N.mul( mapN.z ) ).normalize(); - -} ); - -class NormalMapNode extends TempNode { - - constructor( node, scaleNode = null ) { - - super( 'vec3' ); - - this.node = node; - this.scaleNode = scaleNode; - - this.normalMapType = TangentSpaceNormalMap; - - } - - construct( builder ) { - - const { normalMapType, scaleNode } = this; - - let normalMap = this.node.mul( 2.0 ).sub( 1.0 ); - - if ( scaleNode !== null ) { - - normalMap = vec3( normalMap.xy.mul( scaleNode ), normalMap.z ); - - } - - let outputNode = null; - - if ( normalMapType === ObjectSpaceNormalMap ) { - - outputNode = modelNormalMatrix.mul( normalMap ).normalize(); - - } else if ( normalMapType === TangentSpaceNormalMap ) { - - const tangent = builder.hasGeometryAttribute( 'tangent' ); - - if ( tangent === true ) { - - outputNode = TBNViewMatrix.mul( normalMap ).normalize(); - - } else { - - outputNode = perturbNormal2ArbNode.call( { - eye_pos: positionView, - surf_norm: normalView, - mapN: normalMap, - uv: uv() - } ); - - } - - } - - return outputNode; - - } - -} - -export default NormalMapNode; - -export const normalMap = nodeProxy( NormalMapNode ); - -export const TBNViewMatrix = mat3( tangentView, bitangentView, normalView ); - -addNodeClass( NormalMapNode ); diff --git a/examples/jsm/nodes/display/PosterizeNode.js b/examples/jsm/nodes/display/PosterizeNode.js deleted file mode 100644 index 9b719c7bea2302..00000000000000 --- a/examples/jsm/nodes/display/PosterizeNode.js +++ /dev/null @@ -1,32 +0,0 @@ -import TempNode from '../core/TempNode.js'; -import { addNodeClass } from '../core/Node.js'; -import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js'; - -class PosterizeNode extends TempNode { - - constructor( sourceNode, stepsNode ) { - - super(); - - this.sourceNode = sourceNode; - this.stepsNode = stepsNode; - - } - - construct() { - - const { sourceNode, stepsNode } = this; - - return sourceNode.mul( stepsNode ).floor().div( stepsNode ); - - } - -} - -export default PosterizeNode; - -export const posterize = nodeProxy( PosterizeNode ); - -addNodeElement( 'posterize', posterize ); - -addNodeClass( PosterizeNode ); diff --git a/examples/jsm/nodes/display/ToneMappingNode.js b/examples/jsm/nodes/display/ToneMappingNode.js deleted file mode 100644 index 8b19e250b0c5fb..00000000000000 --- a/examples/jsm/nodes/display/ToneMappingNode.js +++ /dev/null @@ -1,132 +0,0 @@ -import TempNode from '../core/TempNode.js'; -import { addNodeClass } from '../core/Node.js'; -import { ShaderNode, nodeObject, float, mat3 } from '../shadernode/ShaderNode.js'; - -import { NoToneMapping, LinearToneMapping, ReinhardToneMapping, CineonToneMapping, ACESFilmicToneMapping } from 'three'; - -// exposure only -const LinearToneMappingNode = new ShaderNode( ( { color, exposure } ) => { - - return color.mul( exposure ); - -} ); - -// source: https://www.cs.utah.edu/docs/techreports/2002/pdf/UUCS-02-001.pdf -const ReinhardToneMappingNode = new ShaderNode( ( { color, exposure } ) => { - - color = color.mul( exposure ); - - return color.div( color.add( 1.0 ) ).clamp(); - -} ); - -// source: http://filmicworlds.com/blog/filmic-tonemapping-operators/ -const OptimizedCineonToneMappingNode = new ShaderNode( ( { color, exposure } ) => { - - // optimized filmic operator by Jim Hejl and Richard Burgess-Dawson - color = color.mul( exposure ); - color = color.sub( 0.004 ).max( 0.0 ); - - const a = color.mul( color.mul( 6.2 ).add( 0.5 ) ); - const b = color.mul( color.mul( 6.2 ).add( 1.7 ) ).add( 0.06 ); - - return a.div( b ).pow( 2.2 ); - -} ); - -// source: https://github.com/selfshadow/ltc_code/blob/master/webgl/shaders/ltc/ltc_blit.fs -const RRTAndODTFit = new ShaderNode( ( { color } ) => { - - const a = color.mul( color.add( 0.0245786 ) ).sub( 0.000090537 ); - const b = color.mul( color.add( 0.4329510 ).mul( 0.983729 ) ).add( 0.238081 ); - - return a.div( b ); - -} ); - -// source: https://github.com/selfshadow/ltc_code/blob/master/webgl/shaders/ltc/ltc_blit.fs -const ACESFilmicToneMappingNode = new ShaderNode( ( { color, exposure } ) => { - - // sRGB => XYZ => D65_2_D60 => AP1 => RRT_SAT - const ACESInputMat = mat3( - 0.59719, 0.35458, 0.04823, - 0.07600, 0.90834, 0.01566, - 0.02840, 0.13383, 0.83777 - ); - - // ODT_SAT => XYZ => D60_2_D65 => sRGB - const ACESOutputMat = mat3( - 1.60475, - 0.53108, - 0.07367, - - 0.10208, 1.10813, - 0.00605, - - 0.00327, - 0.07276, 1.07602 - ); - - color = color.mul( exposure ).div( 0.6 ); - - color = ACESInputMat.mul( color ); - - // Apply RRT and ODT - color = RRTAndODTFit.call( { color } ); - - color = ACESOutputMat.mul( color ); - - // Clamp to [0, 1] - return color.clamp(); - -} ); - -const toneMappingLib = { - [ LinearToneMapping ]: LinearToneMappingNode, - [ ReinhardToneMapping ]: ReinhardToneMappingNode, - [ CineonToneMapping ]: OptimizedCineonToneMappingNode, - [ ACESFilmicToneMapping ]: ACESFilmicToneMappingNode -}; - -class ToneMappingNode extends TempNode { - - constructor( toneMapping = NoToneMapping, exposureNode = float( 1 ), colorNode = null ) { - - super( 'vec3' ); - - this.toneMapping = toneMapping; - - this.exposureNode = exposureNode; - this.colorNode = colorNode; - - } - - construct( builder ) { - - const colorNode = this.colorNode || builder.context.color; - const toneMapping = this.toneMapping; - - if ( toneMapping === NoToneMapping ) return colorNode; - - const toneMappingParams = { exposure: this.exposureNode, color: colorNode }; - const toneMappingNode = toneMappingLib[ toneMapping ]; - - let outputNode = null; - - if ( toneMappingNode ) { - - outputNode = toneMappingNode.call( toneMappingParams ); - - } else { - - console.error( 'ToneMappingNode: Unsupported Tone Mapping configuration.', toneMapping ); - - outputNode = colorNode; - - } - - return outputNode; - - } - -} - -export default ToneMappingNode; - -export const toneMapping = ( mapping, exposure, color ) => nodeObject( new ToneMappingNode( mapping, nodeObject( exposure ), nodeObject( color ) ) ); - -addNodeClass( ToneMappingNode ); diff --git a/examples/jsm/nodes/display/ViewportNode.js b/examples/jsm/nodes/display/ViewportNode.js deleted file mode 100644 index faf013a5a5844b..00000000000000 --- a/examples/jsm/nodes/display/ViewportNode.js +++ /dev/null @@ -1,115 +0,0 @@ -import Node, { addNodeClass } from '../core/Node.js'; -import { NodeUpdateType } from '../core/constants.js'; -import { uniform } from '../core/UniformNode.js'; -import { nodeImmutable, vec2 } from '../shadernode/ShaderNode.js'; - -import { Vector2 } from 'three'; - -let resolution; - -class ViewportNode extends Node { - - constructor( scope ) { - - super(); - - this.scope = scope; - - this.isViewportNode = true; - - } - - getNodeType() { - - return this.scope === ViewportNode.COORDINATE ? 'vec4' : 'vec2'; - - } - - getUpdateType() { - - let updateType = NodeUpdateType.NONE; - - if ( this.scope === ViewportNode.RESOLUTION ) { - - updateType = NodeUpdateType.FRAME; - - } - - this.updateType = updateType; - - return updateType; - - } - - update( { renderer } ) { - - renderer.getDrawingBufferSize( resolution ); - - } - - construct( builder ) { - - const scope = this.scope; - - if ( scope === ViewportNode.COORDINATE ) return; - - let output = null; - - if ( scope === ViewportNode.RESOLUTION ) { - - output = uniform( resolution || ( resolution = new Vector2() ) ); - - } else { - - const coordinateNode = vec2( new ViewportNode( ViewportNode.COORDINATE ) ); - const resolutionNode = new ViewportNode( ViewportNode.RESOLUTION ); - - output = coordinateNode.div( resolutionNode ); - - let outX = output.x; - let outY = output.y; - - if ( /top/i.test( scope ) && builder.isFlipY() ) outY = outY.oneMinus(); - else if ( /bottom/i.test( scope ) && builder.isFlipY() === false ) outY = outY.oneMinus(); - - if ( /right/i.test( scope ) ) outX = outX.oneMinus(); - - output = vec2( outX, outY ); - - } - - return output; - - } - - generate( builder ) { - - if ( this.scope === ViewportNode.COORDINATE ) { - - return builder.getFragCoord(); - - } - - return super.generate( builder ); - - } - -} - -ViewportNode.COORDINATE = 'coordinate'; -ViewportNode.RESOLUTION = 'resolution'; -ViewportNode.TOP_LEFT = 'topLeft'; -ViewportNode.BOTTOM_LEFT = 'bottomLeft'; -ViewportNode.TOP_RIGHT = 'topRight'; -ViewportNode.BOTTOM_RIGHT = 'bottomRight'; - -export default ViewportNode; - -export const viewportCoordinate = nodeImmutable( ViewportNode, ViewportNode.COORDINATE ); -export const viewportResolution = nodeImmutable( ViewportNode, ViewportNode.RESOLUTION ); -export const viewportTopLeft = nodeImmutable( ViewportNode, ViewportNode.TOP_LEFT ); -export const viewportBottomLeft = nodeImmutable( ViewportNode, ViewportNode.BOTTOM_LEFT ); -export const viewportTopRight = nodeImmutable( ViewportNode, ViewportNode.TOP_RIGHT ); -export const viewportBottomRight = nodeImmutable( ViewportNode, ViewportNode.BOTTOM_RIGHT ); - -addNodeClass( ViewportNode ); diff --git a/examples/jsm/nodes/display/ViewportSharedTextureNode.js b/examples/jsm/nodes/display/ViewportSharedTextureNode.js deleted file mode 100644 index 395a3daeb2a871..00000000000000 --- a/examples/jsm/nodes/display/ViewportSharedTextureNode.js +++ /dev/null @@ -1,30 +0,0 @@ -import ViewportTextureNode from './ViewportTextureNode.js'; -import { addNodeClass } from '../core/Node.js'; -import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js'; -import { viewportTopLeft } from './ViewportNode.js'; - -let rtt = null; - -class ViewportSharedTextureNode extends ViewportTextureNode { - - constructor( uv = viewportTopLeft ) { - - super( uv ); - - } - - constructRTT( builder ) { - - return rtt || ( rtt = builder.getRenderTarget() ); - - } - -} - -export default ViewportSharedTextureNode; - -export const viewportSharedTexture = nodeProxy( ViewportSharedTextureNode ); - -addNodeElement( 'viewportSharedTexture', viewportSharedTexture ); - -addNodeClass( ViewportSharedTextureNode ); diff --git a/examples/jsm/nodes/display/ViewportTextureNode.js b/examples/jsm/nodes/display/ViewportTextureNode.js deleted file mode 100644 index 57a1d6308b287f..00000000000000 --- a/examples/jsm/nodes/display/ViewportTextureNode.js +++ /dev/null @@ -1,61 +0,0 @@ -import TextureNode from '../accessors/TextureNode.js'; -import { NodeUpdateType } from '../core/constants.js'; -import { addNodeClass } from '../core/Node.js'; -import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js'; -import { viewportTopLeft } from './ViewportNode.js'; -import { Vector2 } from 'three'; - -let size = new Vector2(); - -class ViewportTextureNode extends TextureNode { - - constructor( uv = viewportTopLeft, level = null ) { - - super( null, uv, level ); - - this.rtt = null; - - this.isOutputTextureNode = true; - - this.updateBeforeType = NodeUpdateType.FRAME; - - } - - constructRTT( builder ) { - - return builder.getRenderTarget(); - - } - - construct( builder ) { - - if ( this.rtt === null ) this.rtt = this.constructRTT( builder ); - - this.value = this.rtt.texture; - - return super.construct( builder ); - - } - - updateBefore( frame ) { - - const rtt = this.rtt; - - const renderer = frame.renderer; - renderer.getDrawingBufferSize( size ); - - rtt.setSize( size.width, size.height ); - - renderer.copyFramebufferToRenderTarget( rtt ); - - } - -} - -export default ViewportTextureNode; - -export const viewportTexture = nodeProxy( ViewportTextureNode ); - -addNodeElement( 'viewportTexture', viewportTexture ); - -addNodeClass( ViewportTextureNode ); diff --git a/examples/jsm/nodes/fog/FogExp2Node.js b/examples/jsm/nodes/fog/FogExp2Node.js deleted file mode 100644 index 554054ec85cd63..00000000000000 --- a/examples/jsm/nodes/fog/FogExp2Node.js +++ /dev/null @@ -1,35 +0,0 @@ -import FogNode from './FogNode.js'; -import { positionView } from '../accessors/PositionNode.js'; -import { addNodeClass } from '../core/Node.js'; -import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js'; - -class FogExp2Node extends FogNode { - - constructor( colorNode, densityNode ) { - - super( colorNode ); - - this.isFogExp2Node = true; - - this.densityNode = densityNode; - - } - - construct() { - - const depthNode = positionView.z.negate(); - const densityNode = this.densityNode; - - return densityNode.mul( densityNode, depthNode, depthNode ).negate().exp().oneMinus(); - - } - -} - -export default FogExp2Node; - -export const densityFog = nodeProxy( FogExp2Node ); - -addNodeElement( 'densityFog', densityFog ); - -addNodeClass( FogExp2Node ); diff --git a/examples/jsm/nodes/fog/FogNode.js b/examples/jsm/nodes/fog/FogNode.js deleted file mode 100644 index d787115973f8f4..00000000000000 --- a/examples/jsm/nodes/fog/FogNode.js +++ /dev/null @@ -1,37 +0,0 @@ -import Node, { addNodeClass } from '../core/Node.js'; -import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js'; - -class FogNode extends Node { - - constructor( colorNode, factorNode ) { - - super( 'float' ); - - this.isFogNode = true; - - this.colorNode = colorNode; - this.factorNode = factorNode; - - } - - mixAssign( outputNode ) { - - return this.mix( outputNode, this.colorNode ); - - } - - construct() { - - return this.factorNode; - - } - -} - -export default FogNode; - -export const fog = nodeProxy( FogNode ); - -addNodeElement( 'fog', fog ); - -addNodeClass( FogNode ); diff --git a/examples/jsm/nodes/fog/FogRangeNode.js b/examples/jsm/nodes/fog/FogRangeNode.js deleted file mode 100644 index 35d8464d197f21..00000000000000 --- a/examples/jsm/nodes/fog/FogRangeNode.js +++ /dev/null @@ -1,34 +0,0 @@ -import FogNode from './FogNode.js'; -import { smoothstep } from '../math/MathNode.js'; -import { positionView } from '../accessors/PositionNode.js'; -import { addNodeClass } from '../core/Node.js'; -import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js'; - -class FogRangeNode extends FogNode { - - constructor( colorNode, nearNode, farNode ) { - - super( colorNode ); - - this.isFogRangeNode = true; - - this.nearNode = nearNode; - this.farNode = farNode; - - } - - construct() { - - return smoothstep( this.nearNode, this.farNode, positionView.z.negate() ); - - } - -} - -export default FogRangeNode; - -export const rangeFog = nodeProxy( FogRangeNode ); - -addNodeElement( 'rangeFog', rangeFog ); - -addNodeClass( FogRangeNode ); diff --git a/examples/jsm/nodes/functions/BSDF/BRDF_BlinnPhong.js b/examples/jsm/nodes/functions/BSDF/BRDF_BlinnPhong.js deleted file mode 100644 index 5db86b9b7dac1a..00000000000000 --- a/examples/jsm/nodes/functions/BSDF/BRDF_BlinnPhong.js +++ /dev/null @@ -1,30 +0,0 @@ -import F_Schlick from './F_Schlick.js'; -import { shininess, specularColor } from '../../core/PropertyNode.js'; -import { transformedNormalView } from '../../accessors/NormalNode.js'; -import { positionViewDirection } from '../../accessors/PositionNode.js'; -import { ShaderNode, float } from '../../shadernode/ShaderNode.js'; - -const G_BlinnPhong_Implicit = () => float( 0.25 ); - -const D_BlinnPhong = new ShaderNode( ( { dotNH } ) => { - - return shininess.mul( 0.5 / Math.PI ).add( 1.0 ).mul( dotNH.pow( shininess ) ); - -} ); - -const BRDF_BlinnPhong = new ShaderNode( ( { lightDirection } ) => { - - const halfDir = lightDirection.add( positionViewDirection ).normalize(); - - const dotNH = transformedNormalView.dot( halfDir ).clamp(); - const dotVH = positionViewDirection.dot( halfDir ).clamp(); - - const F = F_Schlick.call( { f0: specularColor, f90: 1.0, dotVH } ); - const G = G_BlinnPhong_Implicit(); - const D = D_BlinnPhong.call( { dotNH } ); - - return F.mul( G ).mul( D ); - -} ); - -export default BRDF_BlinnPhong; diff --git a/examples/jsm/nodes/functions/BSDF/BRDF_GGX.js b/examples/jsm/nodes/functions/BSDF/BRDF_GGX.js deleted file mode 100644 index cf2728078018c9..00000000000000 --- a/examples/jsm/nodes/functions/BSDF/BRDF_GGX.js +++ /dev/null @@ -1,30 +0,0 @@ -import F_Schlick from './F_Schlick.js'; -import V_GGX_SmithCorrelated from './V_GGX_SmithCorrelated.js'; -import D_GGX from './D_GGX.js'; -import { transformedNormalView } from '../../accessors/NormalNode.js'; -import { positionViewDirection } from '../../accessors/PositionNode.js'; -import { ShaderNode } from '../../shadernode/ShaderNode.js'; - -// GGX Distribution, Schlick Fresnel, GGX_SmithCorrelated Visibility -const BRDF_GGX = new ShaderNode( ( inputs ) => { - - const { lightDirection, f0, f90, roughness } = inputs; - - const alpha = roughness.pow2(); // UE4's roughness - - const halfDir = lightDirection.add( positionViewDirection ).normalize(); - - const dotNL = transformedNormalView.dot( lightDirection ).clamp(); - const dotNV = transformedNormalView.dot( positionViewDirection ).clamp(); - const dotNH = transformedNormalView.dot( halfDir ).clamp(); - const dotVH = positionViewDirection.dot( halfDir ).clamp(); - - const F = F_Schlick.call( { f0, f90, dotVH } ); - const V = V_GGX_SmithCorrelated.call( { alpha, dotNL, dotNV } ); - const D = D_GGX.call( { alpha, dotNH } ); - - return F.mul( V ).mul( D ); - -} ); // validated - -export default BRDF_GGX; diff --git a/examples/jsm/nodes/functions/BSDF/BRDF_Lambert.js b/examples/jsm/nodes/functions/BSDF/BRDF_Lambert.js deleted file mode 100644 index 3b7a2bb0ce2c01..00000000000000 --- a/examples/jsm/nodes/functions/BSDF/BRDF_Lambert.js +++ /dev/null @@ -1,9 +0,0 @@ -import { ShaderNode } from '../../shadernode/ShaderNode.js'; - -const BRDF_Lambert = new ShaderNode( ( inputs ) => { - - return inputs.diffuseColor.mul( 1 / Math.PI ); // punctual light - -} ); // validated - -export default BRDF_Lambert; diff --git a/examples/jsm/nodes/functions/BSDF/DFGApprox.js b/examples/jsm/nodes/functions/BSDF/DFGApprox.js deleted file mode 100644 index 77343b21d03efb..00000000000000 --- a/examples/jsm/nodes/functions/BSDF/DFGApprox.js +++ /dev/null @@ -1,29 +0,0 @@ -import { transformedNormalView } from '../../accessors/NormalNode.js'; -import { positionViewDirection } from '../../accessors/PositionNode.js'; -import { ShaderNode, vec2, vec4 } from '../../shadernode/ShaderNode.js'; - -// Analytical approximation of the DFG LUT, one half of the -// split-sum approximation used in indirect specular lighting. -// via 'environmentBRDF' from "Physically Based Shading on Mobile" -// https://www.unrealengine.com/blog/physically-based-shading-on-mobile -const DFGApprox = new ShaderNode( ( inputs ) => { - - const { roughness } = inputs; - - const c0 = vec4( - 1, - 0.0275, - 0.572, 0.022 ); - - const c1 = vec4( 1, 0.0425, 1.04, - 0.04 ); - - const r = roughness.mul( c0 ).add( c1 ); - - const dotNV = transformedNormalView.dot( positionViewDirection ).clamp(); - - const a004 = r.x.mul( r.x ).min( dotNV.mul( - 9.28 ).exp2() ).mul( r.x ).add( r.y ); - - const fab = vec2( - 1.04, 1.04 ).mul( a004 ).add( r.zw ); - - return fab; - -} ); - -export default DFGApprox; diff --git a/examples/jsm/nodes/functions/BSDF/D_GGX.js b/examples/jsm/nodes/functions/BSDF/D_GGX.js deleted file mode 100644 index ef4d957e44e467..00000000000000 --- a/examples/jsm/nodes/functions/BSDF/D_GGX.js +++ /dev/null @@ -1,18 +0,0 @@ -import { ShaderNode } from '../../shadernode/ShaderNode.js'; - -// Microfacet Models for Refraction through Rough Surfaces - equation (33) -// http://graphicrants.blogspot.com/2013/08/specular-brdf-reference.html -// alpha is "roughness squared" in Disney’s reparameterization -const D_GGX = new ShaderNode( ( inputs ) => { - - const { alpha, dotNH } = inputs; - - const a2 = alpha.pow2(); - - const denom = dotNH.pow2().mul( a2.oneMinus() ).oneMinus(); // avoid alpha = 0 with dotNH = 1 - - return a2.div( denom.pow2() ).mul( 1 / Math.PI ); - -} ); // validated - -export default D_GGX; diff --git a/examples/jsm/nodes/functions/BSDF/F_Schlick.js b/examples/jsm/nodes/functions/BSDF/F_Schlick.js deleted file mode 100644 index 6dd6b1d9be9e94..00000000000000 --- a/examples/jsm/nodes/functions/BSDF/F_Schlick.js +++ /dev/null @@ -1,18 +0,0 @@ -import { ShaderNode } from '../../shadernode/ShaderNode.js'; - -const F_Schlick = new ShaderNode( ( inputs ) => { - - const { f0, f90, dotVH } = inputs; - - // Original approximation by Christophe Schlick '94 - // float fresnel = pow( 1.0 - dotVH, 5.0 ); - - // Optimized variant (presented by Epic at SIGGRAPH '13) - // https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf - const fresnel = dotVH.mul( - 5.55473 ).sub( 6.98316 ).mul( dotVH ).exp2(); - - return f0.mul( fresnel.oneMinus() ).add( f90.mul( fresnel ) ); - -} ); // validated - -export default F_Schlick; diff --git a/examples/jsm/nodes/functions/BSDF/V_GGX_SmithCorrelated.js b/examples/jsm/nodes/functions/BSDF/V_GGX_SmithCorrelated.js deleted file mode 100644 index 46f92849846ecc..00000000000000 --- a/examples/jsm/nodes/functions/BSDF/V_GGX_SmithCorrelated.js +++ /dev/null @@ -1,20 +0,0 @@ -import { div } from '../../math/OperatorNode.js'; -import { EPSILON } from '../../math/MathNode.js'; -import { ShaderNode } from '../../shadernode/ShaderNode.js'; - -// Moving Frostbite to Physically Based Rendering 3.0 - page 12, listing 2 -// https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf -const V_GGX_SmithCorrelated = new ShaderNode( ( inputs ) => { - - const { alpha, dotNL, dotNV } = inputs; - - const a2 = alpha.pow2(); - - const gv = dotNL.mul( a2.add( a2.oneMinus().mul( dotNV.pow2() ) ).sqrt() ); - const gl = dotNV.mul( a2.add( a2.oneMinus().mul( dotNL.pow2() ) ).sqrt() ); - - return div( 0.5, gv.add( gl ).max( EPSILON ) ); - -} ); // validated - -export default V_GGX_SmithCorrelated; diff --git a/examples/jsm/nodes/functions/PhongLightingModel.js b/examples/jsm/nodes/functions/PhongLightingModel.js deleted file mode 100644 index 39fcb19ed76e27..00000000000000 --- a/examples/jsm/nodes/functions/PhongLightingModel.js +++ /dev/null @@ -1,28 +0,0 @@ -import BRDF_Lambert from './BSDF/BRDF_Lambert.js'; -import BRDF_BlinnPhong from './BSDF/BRDF_BlinnPhong.js'; -import { lightingModel } from '../core/LightingModel.js'; -import { diffuseColor } from '../core/PropertyNode.js'; -import { materialReflectivity } from '../accessors/MaterialNode.js'; -import { transformedNormalView } from '../accessors/NormalNode.js'; -import { ShaderNode } from '../shadernode/ShaderNode.js'; - -const RE_Direct_BlinnPhong = new ShaderNode( ( { lightDirection, lightColor, reflectedLight } ) => { - - const dotNL = transformedNormalView.dot( lightDirection ).clamp(); - const irradiance = dotNL.mul( lightColor ); - - reflectedLight.directDiffuse.addAssign( irradiance.mul( BRDF_Lambert.call( { diffuseColor: diffuseColor.rgb } ) ) ); - - reflectedLight.directSpecular.addAssign( irradiance.mul( BRDF_BlinnPhong.call( { lightDirection } ) ).mul( materialReflectivity ) ); - -} ); - -const RE_IndirectDiffuse_BlinnPhong = new ShaderNode( ( { irradiance, reflectedLight } ) => { - - reflectedLight.indirectDiffuse.addAssign( irradiance.mul( BRDF_Lambert.call( { diffuseColor } ) ) ); - -} ); - -const phongLightingModel = lightingModel( RE_Direct_BlinnPhong, RE_IndirectDiffuse_BlinnPhong ); - -export default phongLightingModel; diff --git a/examples/jsm/nodes/functions/PhysicalLightingModel.js b/examples/jsm/nodes/functions/PhysicalLightingModel.js deleted file mode 100644 index 4c0a4824e1e019..00000000000000 --- a/examples/jsm/nodes/functions/PhysicalLightingModel.js +++ /dev/null @@ -1,91 +0,0 @@ -import BRDF_Lambert from './BSDF/BRDF_Lambert.js'; -import BRDF_GGX from './BSDF/BRDF_GGX.js'; -import DFGApprox from './BSDF/DFGApprox.js'; -import { lightingModel } from '../core/LightingModel.js'; -import { temp } from '../core/VarNode.js'; -import { diffuseColor, specularColor, roughness } from '../core/PropertyNode.js'; -import { transformedNormalView } from '../accessors/NormalNode.js'; -import { positionViewDirection } from '../accessors/PositionNode.js'; -import { ShaderNode, float, vec3 } from '../shadernode/ShaderNode.js'; - -// Fdez-Agüera's "Multiple-Scattering Microfacet Model for Real-Time Image Based Lighting" -// Approximates multiscattering in order to preserve energy. -// http://www.jcgt.org/published/0008/01/03/ -const computeMultiscattering = ( singleScatter, multiScatter, specularF90 = float( 1 ) ) => { - - const fab = DFGApprox.call( { roughness } ); - - const FssEss = specularColor.mul( fab.x ).add( specularF90.mul( fab.y ) ); - - const Ess = fab.x.add( fab.y ); - const Ems = Ess.oneMinus(); - - const Favg = specularColor.add( specularColor.oneMinus().mul( 0.047619 ) ); // 1/21 - const Fms = FssEss.mul( Favg ).div( Ems.mul( Favg ).oneMinus() ); - - singleScatter.addAssign( FssEss ); - multiScatter.addAssign( Fms.mul( Ems ) ); - -}; - -const RE_IndirectSpecular_Physical = new ShaderNode( ( inputs ) => { - - const { radiance, iblIrradiance, reflectedLight } = inputs; - - // Both indirect specular and indirect diffuse light accumulate here - - const singleScattering = temp( vec3() ); - const multiScattering = temp( vec3() ); - const cosineWeightedIrradiance = iblIrradiance.mul( 1 / Math.PI ); - - computeMultiscattering( singleScattering, multiScattering ); - - const diffuse = diffuseColor.mul( singleScattering.add( multiScattering ).oneMinus() ); - - reflectedLight.indirectSpecular.addAssign( radiance.mul( singleScattering ) ); - reflectedLight.indirectSpecular.addAssign( multiScattering.mul( cosineWeightedIrradiance ) ); - - reflectedLight.indirectDiffuse.addAssign( diffuse.mul( cosineWeightedIrradiance ) ); - -} ); - -const RE_IndirectDiffuse_Physical = new ShaderNode( ( inputs ) => { - - const { irradiance, reflectedLight } = inputs; - - reflectedLight.indirectDiffuse.addAssign( irradiance.mul( BRDF_Lambert.call( { diffuseColor } ) ) ); - -} ); - -const RE_Direct_Physical = new ShaderNode( ( inputs ) => { - - const { lightDirection, lightColor, reflectedLight } = inputs; - - const dotNL = transformedNormalView.dot( lightDirection ).clamp(); - const irradiance = dotNL.mul( lightColor ); - - reflectedLight.directDiffuse.addAssign( irradiance.mul( BRDF_Lambert.call( { diffuseColor: diffuseColor.rgb } ) ) ); - - reflectedLight.directSpecular.addAssign( irradiance.mul( BRDF_GGX.call( { lightDirection, f0: specularColor, f90: 1, roughness } ) ) ); - -} ); - -const RE_AmbientOcclusion_Physical = new ShaderNode( ( { ambientOcclusion, reflectedLight } ) => { - - const dotNV = transformedNormalView.dot( positionViewDirection ).clamp(); - - const aoNV = dotNV.add( ambientOcclusion ); - const aoExp = roughness.mul( - 16.0 ).oneMinus().negate().exp2(); - - const aoNode = ambientOcclusion.sub( aoNV.pow( aoExp ).oneMinus() ).clamp(); - - reflectedLight.indirectDiffuse.mulAssign( ambientOcclusion ); - - reflectedLight.indirectSpecular.mulAssign( aoNode ); - - -} ); - -const physicalLightingModel = lightingModel( RE_Direct_Physical, RE_IndirectDiffuse_Physical, RE_IndirectSpecular_Physical, RE_AmbientOcclusion_Physical ); - -export default physicalLightingModel; diff --git a/examples/jsm/nodes/functions/material/getGeometryRoughness.js b/examples/jsm/nodes/functions/material/getGeometryRoughness.js deleted file mode 100644 index d55b2b22d0589a..00000000000000 --- a/examples/jsm/nodes/functions/material/getGeometryRoughness.js +++ /dev/null @@ -1,13 +0,0 @@ -import { normalGeometry } from '../../accessors/NormalNode.js'; -import { ShaderNode } from '../../shadernode/ShaderNode.js'; - -const getGeometryRoughness = new ShaderNode( () => { - - const dxy = normalGeometry.dFdx().abs().max( normalGeometry.dFdy().abs() ); - const geometryRoughness = dxy.x.max( dxy.y ).max( dxy.z ); - - return geometryRoughness; - -} ); - -export default getGeometryRoughness; diff --git a/examples/jsm/nodes/functions/material/getRoughness.js b/examples/jsm/nodes/functions/material/getRoughness.js deleted file mode 100644 index 728d57136127aa..00000000000000 --- a/examples/jsm/nodes/functions/material/getRoughness.js +++ /dev/null @@ -1,18 +0,0 @@ -import getGeometryRoughness from './getGeometryRoughness.js'; -import { ShaderNode } from '../../shadernode/ShaderNode.js'; - -const getRoughness = new ShaderNode( ( inputs ) => { - - const { roughness } = inputs; - - const geometryRoughness = getGeometryRoughness.call(); - - let roughnessFactor = roughness.max( 0.0525 ); // 0.0525 corresponds to the base mip of a 256 cubemap. - roughnessFactor = roughnessFactor.add( geometryRoughness ); - roughnessFactor = roughnessFactor.min( 1.0 ); - - return roughnessFactor; - -} ); - -export default getRoughness; diff --git a/examples/jsm/nodes/geometry/RangeNode.js b/examples/jsm/nodes/geometry/RangeNode.js deleted file mode 100644 index b875fb44482a79..00000000000000 --- a/examples/jsm/nodes/geometry/RangeNode.js +++ /dev/null @@ -1,104 +0,0 @@ -import Node, { addNodeClass } from '../core/Node.js'; -import { getValueType } from '../core/NodeUtils.js'; -import { buffer } from '../accessors/BufferNode.js'; -//import { bufferAttribute } from '../accessors/BufferAttributeNode.js'; -import { instanceIndex } from '../core/InstanceIndexNode.js'; -import { nodeProxy, float } from '../shadernode/ShaderNode.js'; - -import { Vector4, MathUtils } from 'three'; - -let min = null; -let max = null; - -class RangeNode extends Node { - - constructor( minNode = float(), maxNode = float() ) { - - super(); - - this.minNode = minNode; - this.maxNode = maxNode; - - } - - getVectorLength( builder ) { - - const minLength = builder.getTypeLength( getValueType( this.minNode.value ) ); - const maxLength = builder.getTypeLength( getValueType( this.maxNode.value ) ); - - return minLength > maxLength ? minLength : maxLength; - - } - - getNodeType( builder ) { - - return builder.object.isInstancedMesh === true ? builder.getTypeFromLength( this.getVectorLength( builder ) ) : 'float'; - - } - - construct( builder ) { - - const object = builder.object; - - let output = null; - - if ( object.isInstancedMesh === true ) { - - let minValue = this.minNode.value; - let maxValue = this.maxNode.value; - - const minLength = builder.getTypeLength( getValueType( minValue ) ); - const maxLength = builder.getTypeLength( getValueType( maxValue ) ); - - min = min || new Vector4(); - max = max || new Vector4(); - - min.setScalar( 0 ); - max.setScalar( 0 ); - - if ( minLength === 1 ) min.setScalar( minValue ); - else if ( minValue.isColor ) min.set( minValue.r, minValue.g, minValue.b ); - else min.set( minValue.x, minValue.y, minValue.z || 0, minValue.w || 0 ); - - if ( maxLength === 1 ) max.setScalar( maxValue ); - else if ( maxValue.isColor ) max.set( maxValue.r, maxValue.g, maxValue.b ); - else max.set( maxValue.x, maxValue.y, maxValue.z || 0, maxValue.w || 0 ); - - const stride = 4; - - const length = stride * object.count; - const array = new Float32Array( length ); - - for ( let i = 0; i < length; i ++ ) { - - const index = i % stride; - - const minElementValue = min.getComponent( index ); - const maxElementValue = max.getComponent( index ); - - array[ i ] = MathUtils.lerp( minElementValue, maxElementValue, Math.random() ); - - } - - const nodeType = this.getNodeType( builder ); - - output = buffer( array, 'vec4', object.count ).element( instanceIndex ).convert( nodeType ); - //output = bufferAttribute( array, 'vec4', 4, 0 ).convert( nodeType ); - - } else { - - output = float( 0 ); - - } - - return output; - - } - -} - -export default RangeNode; - -export const range = nodeProxy( RangeNode ); - -addNodeClass( RangeNode ); diff --git a/examples/jsm/nodes/gpgpu/ComputeNode.js b/examples/jsm/nodes/gpgpu/ComputeNode.js deleted file mode 100644 index 8f7a911e737ecd..00000000000000 --- a/examples/jsm/nodes/gpgpu/ComputeNode.js +++ /dev/null @@ -1,72 +0,0 @@ -import Node, { addNodeClass } from '../core/Node.js'; -import { NodeUpdateType } from '../core/constants.js'; -import { addNodeElement, nodeObject } from '../shadernode/ShaderNode.js'; - -class ComputeNode extends Node { - - constructor( computeNode, count, workgroupSize = [ 64 ] ) { - - super( 'void' ); - - this.isComputeNode = true; - - this.computeNode = computeNode; - - this.count = count; - this.workgroupSize = workgroupSize; - this.dispatchCount = 0; - - this.updateType = NodeUpdateType.OBJECT; - - this.updateDispatchCount(); - - } - - updateDispatchCount() { - - const { count, workgroupSize } = this; - - let size = workgroupSize[ 0 ]; - - for ( let i = 1; i < workgroupSize.length; i ++ ) - size *= workgroupSize[ i ]; - - this.dispatchCount = Math.ceil( count / size ); - - } - - onInit() { } - - update( { renderer } ) { - - renderer.compute( this ); - - } - - generate( builder ) { - - const { shaderStage } = builder; - - if ( shaderStage === 'compute' ) { - - const snippet = this.computeNode.build( builder, 'void' ); - - if ( snippet !== '' ) { - - builder.addLineFlowCode( snippet ); - - } - - } - - } - -} - -export default ComputeNode; - -export const compute = ( node, count, workgroupSize ) => nodeObject( new ComputeNode( nodeObject( node ), count, workgroupSize ) ); - -addNodeElement( 'compute', compute ); - -addNodeClass( ComputeNode ); diff --git a/examples/jsm/nodes/lighting/AONode.js b/examples/jsm/nodes/lighting/AONode.js deleted file mode 100644 index bc2fb12dd975cc..00000000000000 --- a/examples/jsm/nodes/lighting/AONode.js +++ /dev/null @@ -1,27 +0,0 @@ -import LightingNode from './LightingNode.js'; -import { addNodeClass } from '../core/Node.js'; - -class AONode extends LightingNode { - - constructor( aoNode = null ) { - - super(); - - this.aoNode = aoNode; - - } - - construct( builder ) { - - const aoIntensity = 1; - const aoNode = this.aoNode.sub( 1.0 ).mul( aoIntensity ).add( 1.0 ); - - builder.context.ambientOcclusion.mulAssign( aoNode ); - - } - -} - -export default AONode; - -addNodeClass( AONode ); diff --git a/examples/jsm/nodes/lighting/AmbientLightNode.js b/examples/jsm/nodes/lighting/AmbientLightNode.js deleted file mode 100644 index 08d1e95228797d..00000000000000 --- a/examples/jsm/nodes/lighting/AmbientLightNode.js +++ /dev/null @@ -1,27 +0,0 @@ -import AnalyticLightNode from './AnalyticLightNode.js'; -import { addLightNode } from './LightsNode.js'; -import { addNodeClass } from '../core/Node.js'; - -import { AmbientLight } from 'three'; - -class AmbientLightNode extends AnalyticLightNode { - - constructor( light = null ) { - - super( light ); - - } - - construct( { context } ) { - - context.irradiance.addAssign( this.colorNode ); - - } - -} - -export default AmbientLightNode; - -addLightNode( AmbientLight, AmbientLightNode ); - -addNodeClass( AmbientLightNode ); diff --git a/examples/jsm/nodes/lighting/AnalyticLightNode.js b/examples/jsm/nodes/lighting/AnalyticLightNode.js deleted file mode 100644 index 04d3b287f6e01c..00000000000000 --- a/examples/jsm/nodes/lighting/AnalyticLightNode.js +++ /dev/null @@ -1,143 +0,0 @@ -import LightingNode from './LightingNode.js'; -import { NodeUpdateType } from '../core/constants.js'; -import { uniform } from '../core/UniformNode.js'; -import { addNodeClass } from '../core/Node.js'; -import { vec3 } from '../shadernode/ShaderNode.js'; -import { reference } from '../accessors/ReferenceNode.js'; -import { texture } from '../accessors/TextureNode.js'; -import { positionWorld } from '../accessors/PositionNode.js'; -import { cond } from '../math/CondNode.js'; -import MeshBasicNodeMaterial from '../materials/MeshBasicNodeMaterial.js'; - -import { Color, DepthTexture, NearestFilter } from 'three'; - -let depthMaterial = null; - -class AnalyticLightNode extends LightingNode { - - constructor( light = null ) { - - super(); - - this.updateType = NodeUpdateType.FRAME; - - this.light = light; - - this.rtt = null; - this.shadowNode = null; - - this.color = new Color(); - this.colorNode = uniform( this.color ); - - } - - getHash( /*builder*/ ) { - - return this.light.uuid; - - } - - constructShadow( builder ) { - - let shadowNode = this.shadowNode; - - if ( shadowNode === null ) { - - if ( depthMaterial === null ) depthMaterial = new MeshBasicNodeMaterial(); - - const shadow = this.light.shadow; - const rtt = builder.getRenderTarget( shadow.mapSize.width, shadow.mapSize.height ); - - const depthTexture = new DepthTexture(); - depthTexture.minFilter = NearestFilter; - depthTexture.magFilter = NearestFilter; - - rtt.depthTexture = depthTexture; - - shadow.camera.updateProjectionMatrix(); - - // - - const bias = reference( 'bias', 'float', shadow ); - - //const diffuseFactor = normalView.dot( objectViewPosition( this.light ).sub( positionView ).normalize().negate() ); - //bias = mix( bias, 0, diffuseFactor ); - - let shadowCoord = uniform( shadow.matrix ).mul( positionWorld ); - shadowCoord = shadowCoord.xyz.div( shadowCoord.w ); - - shadowCoord = vec3( - shadowCoord.x, - shadowCoord.y.oneMinus(), - shadowCoord.z - ); - - // @TODO: Optimize using WebGPU compare-sampler - - let depth = texture( depthTexture, shadowCoord.xy ); - depth = depth.mul( .5 ).add( .5 ).add( bias ); - - shadowNode = cond( shadowCoord.z.lessThan( depth ).or( shadowCoord.y.lessThan( .000001 ) /*@TODO: find the cause and remove it soon */ ), 1, 0 ); - //shadowNode = step( shadowCoord.z, depth ); - - // - - this.rtt = rtt; - this.colorNode = this.colorNode.mul( shadowNode ); - - this.shadowNode = shadowNode; - - // - - this.updateBeforeType = NodeUpdateType.RENDER; - - } - - } - - construct( builder ) { - - if ( this.light.castShadow ) this.constructShadow( builder ); - - } - - updateShadow( frame ) { - - const { rtt, light } = this; - const { renderer, scene } = frame; - - scene.overrideMaterial = depthMaterial; - - rtt.setSize( light.shadow.mapSize.width, light.shadow.mapSize.height ); - - light.shadow.updateMatrices( light ); - - renderer.setRenderTarget( rtt ); - renderer.render( scene, light.shadow.camera ); - renderer.setRenderTarget( null ); - - scene.overrideMaterial = null; - - } - - updateBefore( frame ) { - - const { light } = this; - - if ( light.castShadow ) this.updateShadow( frame ); - - } - - update( frame ) { - - const { light } = this; - - this.color.copy( light.color ).multiplyScalar( light.intensity ); - - } - -} - -export default AnalyticLightNode; - -addNodeClass( AnalyticLightNode ); diff --git a/examples/jsm/nodes/lighting/DirectionalLightNode.js b/examples/jsm/nodes/lighting/DirectionalLightNode.js deleted file mode 100644 index f32a5ad69a5211..00000000000000 --- a/examples/jsm/nodes/lighting/DirectionalLightNode.js +++ /dev/null @@ -1,43 +0,0 @@ -import AnalyticLightNode from './AnalyticLightNode.js'; -import { lightTargetDirection } from './LightNode.js'; -import { addLightNode } from './LightsNode.js'; -import { addNodeClass } from '../core/Node.js'; - -import { DirectionalLight } from 'three'; - -class DirectionalLightNode extends AnalyticLightNode { - - constructor( light = null ) { - - super( light ); - - } - - construct( builder ) { - - super.construct( builder ); - - const lightColor = this.colorNode; - const lightDirection = lightTargetDirection( this.light ); - const lightingModelFunctionNode = builder.context.lightingModelNode; - const reflectedLight = builder.context.reflectedLight; - - if ( lightingModelFunctionNode && lightingModelFunctionNode.direct ) { - - lightingModelFunctionNode.direct.call( { - lightDirection, - lightColor, - reflectedLight - }, builder ); - - } - - } - -} - -export default DirectionalLightNode; - -addLightNode( DirectionalLight, DirectionalLightNode ); - -addNodeClass( DirectionalLightNode ); diff --git a/examples/jsm/nodes/lighting/EnvironmentNode.js b/examples/jsm/nodes/lighting/EnvironmentNode.js deleted file mode 100644 index ebc9d65e8f3056..00000000000000 --- a/examples/jsm/nodes/lighting/EnvironmentNode.js +++ /dev/null @@ -1,136 +0,0 @@ -import LightingNode from './LightingNode.js'; -import { cache } from '../core/CacheNode.js'; -import { context } from '../core/ContextNode.js'; -import { roughness } from '../core/PropertyNode.js'; -import { equirectUV } from '../utils/EquirectUVNode.js'; -import { specularMIPLevel } from '../utils/SpecularMIPLevelNode.js'; -import { cameraViewMatrix } from '../accessors/CameraNode.js'; -import { transformedNormalView, transformedNormalWorld } from '../accessors/NormalNode.js'; -import { positionViewDirection } from '../accessors/PositionNode.js'; -import { addNodeClass } from '../core/Node.js'; -import { float, vec2 } from '../shadernode/ShaderNode.js'; - -class EnvironmentNode extends LightingNode { - - constructor( envNode = null ) { - - super(); - - this.envNode = envNode; - - } - - construct( builder ) { - - const envNode = this.envNode; - const properties = builder.getNodeProperties( this ); - - let reflectVec; - let radianceTextureUVNode; - let irradianceTextureUVNode; - - const radianceContext = context( envNode, { - getUVNode: ( textureNode ) => { - - let node = null; - - if ( reflectVec === undefined ) { - - reflectVec = positionViewDirection.negate().reflect( transformedNormalView ); - reflectVec = roughness.mul( roughness ).mix( reflectVec, transformedNormalView ).normalize(); - reflectVec = reflectVec.transformDirection( cameraViewMatrix ); - - } - - if ( textureNode.isCubeTextureNode ) { - - node = reflectVec; - - } else if ( textureNode.isTextureNode ) { - - if ( radianceTextureUVNode === undefined ) { - - // @TODO: Needed PMREM - - radianceTextureUVNode = equirectUV( reflectVec ); - - } - - node = radianceTextureUVNode; - - } - - return node; - - }, - getSamplerLevelNode: () => { - - return roughness; - - }, - getMIPLevelAlgorithmNode: ( textureNode, levelNode ) => { - - return specularMIPLevel( textureNode, levelNode ); - - } - } ); - - const irradianceContext = context( envNode, { - getUVNode: ( textureNode ) => { - - let node = null; - - if ( textureNode.isCubeTextureNode ) { - - node = transformedNormalWorld; - - } else if ( textureNode.isTextureNode ) { - - if ( irradianceTextureUVNode === undefined ) { - - // @TODO: Needed PMREM - - irradianceTextureUVNode = equirectUV( transformedNormalWorld ); - irradianceTextureUVNode = vec2( irradianceTextureUVNode.x, irradianceTextureUVNode.y.oneMinus() ); - - } - - node = irradianceTextureUVNode; - - } - - return node; - - }, - getSamplerLevelNode: () => { - - return float( 1 ); - - }, - getMIPLevelAlgorithmNode: ( textureNode, levelNode ) => { - - return specularMIPLevel( textureNode, levelNode ); - - } - } ); - - // - - const isolateRadianceFlowContext = cache( radianceContext ); - - // - - builder.context.radiance.addAssign( isolateRadianceFlowContext ); - - builder.context.iblIrradiance.addAssign( irradianceContext.mul( Math.PI ) ); - - properties.radianceContext = isolateRadianceFlowContext; - properties.irradianceContext = irradianceContext; - - } - -} - -export default EnvironmentNode; - -addNodeClass( EnvironmentNode ); diff --git a/examples/jsm/nodes/lighting/HemisphereLightNode.js b/examples/jsm/nodes/lighting/HemisphereLightNode.js deleted file mode 100644 index ade9b69d69dde9..00000000000000 --- a/examples/jsm/nodes/lighting/HemisphereLightNode.js +++ /dev/null @@ -1,55 +0,0 @@ -import AnalyticLightNode from './AnalyticLightNode.js'; -import { addLightNode } from './LightsNode.js'; -import { uniform } from '../core/UniformNode.js'; -import { mix } from '../math/MathNode.js'; -import { normalView } from '../accessors/NormalNode.js'; -import { objectPosition } from '../accessors/Object3DNode.js'; -import { addNodeClass } from '../core/Node.js'; - -import { Color, HemisphereLight } from 'three'; - -class HemisphereLightNode extends AnalyticLightNode { - - constructor( light = null ) { - - super( light ); - - this.lightPositionNode = objectPosition; - this.lightDirectionNode = objectPosition.normalize(); - - this.groundColorNode = uniform( new Color() ); - - } - - update( frame ) { - - const { light } = this; - - super.update( frame ); - - this.lightPositionNode.object3d = light; - - this.groundColorNode.value.copy( light.groundColor ).multiplyScalar( light.intensity ); - - } - - generate( builder ) { - - const { colorNode, groundColorNode, lightDirectionNode } = this; - - const dotNL = normalView.dot( lightDirectionNode ); - const hemiDiffuseWeight = dotNL.mul( 0.5 ).add( 0.5 ); - - const irradiance = mix( groundColorNode, colorNode, hemiDiffuseWeight ); - - builder.context.irradiance.addAssign( irradiance ); - - } - -} - -export default HemisphereLightNode; - -addLightNode( HemisphereLight, HemisphereLightNode ); - -addNodeClass( HemisphereLightNode ); diff --git a/examples/jsm/nodes/lighting/IESSpotLightNode.js b/examples/jsm/nodes/lighting/IESSpotLightNode.js deleted file mode 100644 index 5a3db66c12c2f2..00000000000000 --- a/examples/jsm/nodes/lighting/IESSpotLightNode.js +++ /dev/null @@ -1,39 +0,0 @@ -import SpotLightNode from './SpotLightNode.js'; -import { addLightNode } from './LightsNode.js'; -import { texture } from '../accessors/TextureNode.js'; -import { vec2 } from '../shadernode/ShaderNode.js'; -import { addNodeClass } from '../core/Node.js'; - -import IESSpotLight from '../../lights/IESSpotLight.js'; - -class IESSpotLightNode extends SpotLightNode { - - getSpotAttenuation( angleCosine ) { - - const iesMap = this.light.iesMap; - - let spotAttenuation = null; - - if ( iesMap && iesMap.isTexture === true ) { - - const angle = angleCosine.acos().mul( 1.0 / Math.PI ); - - spotAttenuation = texture( iesMap, vec2( angle, 0 ), 0 ).r; - - } else { - - spotAttenuation = super.getSpotAttenuation( angleCosine ); - - } - - return spotAttenuation; - - } - -} - -export default IESSpotLightNode; - -addLightNode( IESSpotLight, IESSpotLightNode ); - -addNodeClass( IESSpotLightNode ); diff --git a/examples/jsm/nodes/lighting/LightNode.js b/examples/jsm/nodes/lighting/LightNode.js deleted file mode 100644 index 019615496318b6..00000000000000 --- a/examples/jsm/nodes/lighting/LightNode.js +++ /dev/null @@ -1,57 +0,0 @@ -import Node, { addNodeClass } from '../core/Node.js'; -import { nodeProxy } from '../shadernode/ShaderNode.js'; -import { objectPosition } from '../accessors/Object3DNode.js'; -import { cameraViewMatrix } from '../accessors/CameraNode.js'; - -class LightNode extends Node { - - constructor( scope = LightNode.TARGET_DIRECTION, light = null ) { - - super(); - - this.scope = scope; - this.light = light; - - } - - construct() { - - const { scope, light } = this; - - let output = null; - - if ( scope === LightNode.TARGET_DIRECTION ) { - - output = cameraViewMatrix.transformDirection( objectPosition( light ).sub( objectPosition( light.target ) ) ); - - } - - return output; - - } - - serialize( data ) { - - super.serialize( data ); - - data.scope = this.scope; - - } - - deserialize( data ) { - - super.deserialize( data ); - - this.scope = data.scope; - - } - -} - -LightNode.TARGET_DIRECTION = 'targetDirection'; - -export default LightNode; - -export const lightTargetDirection = nodeProxy( LightNode, LightNode.TARGET_DIRECTION ); - -addNodeClass( LightNode ); diff --git a/examples/jsm/nodes/lighting/LightUtils.js b/examples/jsm/nodes/lighting/LightUtils.js deleted file mode 100644 index 32f477d8e25029..00000000000000 --- a/examples/jsm/nodes/lighting/LightUtils.js +++ /dev/null @@ -1,17 +0,0 @@ -import { ShaderNode } from '../shadernode/ShaderNode.js'; - -export const getDistanceAttenuation = new ShaderNode( ( inputs ) => { - - const { lightDistance, cutoffDistance, decayExponent } = inputs; - - // based upon Frostbite 3 Moving to Physically-based Rendering - // page 32, equation 26: E[window1] - // https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf - const distanceFalloff = lightDistance.pow( decayExponent ).max( 0.01 ).reciprocal(); - - return cutoffDistance.greaterThan( 0 ).cond( - distanceFalloff.mul( lightDistance.div( cutoffDistance ).pow4().oneMinus().clamp().pow2() ), - distanceFalloff - ); - -} ); // validated diff --git a/examples/jsm/nodes/lighting/LightingContextNode.js b/examples/jsm/nodes/lighting/LightingContextNode.js deleted file mode 100644 index c5a74e6c793442..00000000000000 --- a/examples/jsm/nodes/lighting/LightingContextNode.js +++ /dev/null @@ -1,97 +0,0 @@ -import ContextNode from '../core/ContextNode.js'; -import { temp } from '../core/VarNode.js'; -import { add } from '../math/OperatorNode.js'; -import { mix } from '../math/MathNode.js'; -import { addNodeClass } from '../core/Node.js'; -import { addNodeElement, nodeProxy, float, vec3 } from '../shadernode/ShaderNode.js'; - -class LightingContextNode extends ContextNode { - - constructor( node, lightingModelNode = null, backdropNode = null, backdropAlphaNode = null ) { - - super( node ); - - this.lightingModelNode = lightingModelNode; - this.backdropNode = backdropNode; - this.backdropAlphaNode = backdropAlphaNode; - - } - - getNodeType( /*builder*/ ) { - - return 'vec3'; - - } - - construct( builder ) { - - const { lightingModelNode, backdropNode, backdropAlphaNode } = this; - - const context = this.context = {}; // reset context - const properties = builder.getNodeProperties( this ); - - const directDiffuse = temp( vec3() ), - directSpecular = temp( vec3() ), - indirectDiffuse = temp( vec3() ), - indirectSpecular = temp( vec3() ); - - let totalDiffuse = add( directDiffuse, indirectDiffuse ); - - if ( backdropNode !== null ) { - - totalDiffuse = vec3( backdropAlphaNode !== null ? mix( totalDiffuse, backdropNode, backdropAlphaNode ) : backdropNode ); - - } - - const totalSpecular = add( directSpecular, indirectSpecular ); - const total = add( totalDiffuse, totalSpecular ); - - const reflectedLight = { - directDiffuse, - directSpecular, - indirectDiffuse, - indirectSpecular, - total - }; - - const lighting = { - radiance: temp( vec3() ), - irradiance: temp( vec3() ), - iblIrradiance: temp( vec3() ), - ambientOcclusion: temp( float( 1 ) ) - }; - - Object.assign( properties, reflectedLight, lighting ); - Object.assign( context, lighting ); - - context.reflectedLight = reflectedLight; - context.lightingModelNode = lightingModelNode || context.lightingModelNode; - - if ( lightingModelNode && lightingModelNode.indirectDiffuse ) lightingModelNode.indirectDiffuse.call( context ); - if ( lightingModelNode && lightingModelNode.indirectSpecular ) lightingModelNode.indirectSpecular.call( context ); - if ( lightingModelNode && lightingModelNode.ambientOcclusion ) lightingModelNode.ambientOcclusion.call( context ); - - return super.construct( builder ); - - } - - generate( builder ) { - - const { context } = this; - const type = this.getNodeType( builder ); - - super.generate( builder, type ); - - return context.reflectedLight.total.build( builder, type ); - - } - -} - -export default LightingContextNode; - -export const lightingContext = nodeProxy( LightingContextNode ); - -addNodeElement( 'lightingContext', lightingContext ); - -addNodeClass( LightingContextNode ); diff --git a/examples/jsm/nodes/lighting/LightingNode.js b/examples/jsm/nodes/lighting/LightingNode.js deleted file mode 100644 index 4c5bcfe46cd441..00000000000000 --- a/examples/jsm/nodes/lighting/LightingNode.js +++ /dev/null @@ -1,21 +0,0 @@ -import Node, { addNodeClass } from '../core/Node.js'; - -class LightingNode extends Node { - - constructor() { - - super( 'vec3' ); - - } - - generate( /*builder*/ ) { - - console.warn( 'Abstract function.' ); - - } - -} - -export default LightingNode; - -addNodeClass( LightingNode ); diff --git a/examples/jsm/nodes/lighting/LightsNode.js b/examples/jsm/nodes/lighting/LightsNode.js deleted file mode 100644 index 9104ceca245a03..00000000000000 --- a/examples/jsm/nodes/lighting/LightsNode.js +++ /dev/null @@ -1,128 +0,0 @@ -import Node from '../core/Node.js'; -import AnalyticLightNode from './AnalyticLightNode.js'; -import { nodeObject, nodeProxy } from '../shadernode/ShaderNode.js'; - -const LightNodes = new WeakMap(); - -const sortLights = ( lights ) => { - - return lights.sort( ( a, b ) => a.id - b.id ); - -}; - -class LightsNode extends Node { - - constructor( lightNodes = [] ) { - - super( 'vec3' ); - - this.lightNodes = lightNodes; - - this._hash = null; - - } - - get hasLight() { - - return this.lightNodes.length > 0; - - } - - construct( builder ) { - - const lightNodes = this.lightNodes; - - for ( const lightNode of lightNodes ) { - - lightNode.build( builder ); - - } - - } - - getHash( builder ) { - - if ( this._hash === null ) { - - let hash = ''; - - const lightNodes = this.lightNodes; - - for ( const lightNode of lightNodes ) { - - hash += lightNode.getHash( builder ) + ' '; - - } - - this._hash = hash; - - } - - return this._hash; - - } - - getLightNodeByHash( hash ) { - - const lightNodes = this.lightNodes; - - for ( const lightNode of lightNodes ) { - - if ( lightNode.light.uuid === hash ) { - - return lightNode; - - } - - } - - return null; - - } - - fromLights( lights = [] ) { - - const lightNodes = []; - - lights = sortLights( lights ); - - for ( const light of lights ) { - - let lightNode = this.getLightNodeByHash( light.uuid ); - - if ( lightNode === null ) { - - const lightClass = light.constructor; - const lightNodeClass = LightNodes.has( lightClass ) ? LightNodes.get( lightClass ) : AnalyticLightNode; - - lightNode = nodeObject( new lightNodeClass( light ) ); - - } - - lightNodes.push( lightNode ); - - } - - this.lightNodes = lightNodes; - this._hash = null; - - return this; - - } - -} - -export default LightsNode; - -export const lights = ( lights ) => nodeObject( new LightsNode().fromLights( lights ) ); -export const lightsWithoutWrap = nodeProxy( LightsNode ); - -export function addLightNode( lightClass, lightNodeClass ) { - - if ( LightNodes.has( lightClass ) ) throw new Error( `Redefinition of light node ${ lightNodeClass.name }` ); - if ( typeof lightClass !== 'function' || ! lightClass.name ) throw new Error( `Light ${ lightClass.name } is not a class` ); - if ( typeof lightNodeClass !== 'function' || ! lightNodeClass.name ) throw new Error( `Light node ${ lightNodeClass.name } is not a class` ); - - LightNodes.set( lightClass, lightNodeClass ); - -} diff --git a/examples/jsm/nodes/lighting/PointLightNode.js b/examples/jsm/nodes/lighting/PointLightNode.js deleted file mode 100644 index 493856bc21816c..00000000000000 --- a/examples/jsm/nodes/lighting/PointLightNode.js +++ /dev/null @@ -1,71 +0,0 @@ -import AnalyticLightNode from './AnalyticLightNode.js'; -import { addLightNode } from './LightsNode.js'; -import { getDistanceAttenuation } from './LightUtils.js'; -import { uniform } from '../core/UniformNode.js'; -import { objectViewPosition } from '../accessors/Object3DNode.js'; -import { positionView } from '../accessors/PositionNode.js'; -import { addNodeClass } from '../core/Node.js'; - -import { PointLight } from 'three'; - -class PointLightNode extends AnalyticLightNode { - - constructor( light = null ) { - - super( light ); - - this.cutoffDistanceNode = uniform( 0 ); - this.decayExponentNode = uniform( 0 ); - - } - - update( frame ) { - - const { light } = this; - - super.update( frame ); - - this.cutoffDistanceNode.value = light.distance; - this.decayExponentNode.value = light.decay; - - } - - construct( builder ) { - - const { colorNode, cutoffDistanceNode, decayExponentNode, light } = this; - - const lVector = objectViewPosition( light ).sub( positionView ); // @TODO: Add it into LightNode - - const lightDirection = lVector.normalize(); - const lightDistance = lVector.length(); - - const lightAttenuation = getDistanceAttenuation.call( { - lightDistance, - cutoffDistance: cutoffDistanceNode, - decayExponent: decayExponentNode - } ); - - const lightColor = colorNode.mul( lightAttenuation ); - - const lightingModelFunctionNode = builder.context.lightingModelNode; - const reflectedLight = builder.context.reflectedLight; - - if ( lightingModelFunctionNode && lightingModelFunctionNode.direct ) { - - lightingModelFunctionNode.direct.call( { - lightDirection, - lightColor, - reflectedLight - }, builder ); - - } - - } - -} - -export default PointLightNode; - -addLightNode( PointLight, PointLightNode ); - -addNodeClass( PointLightNode ); diff --git a/examples/jsm/nodes/lighting/SpotLightNode.js b/examples/jsm/nodes/lighting/SpotLightNode.js deleted file mode 100644 index 961e7b59536047..00000000000000 --- a/examples/jsm/nodes/lighting/SpotLightNode.js +++ /dev/null @@ -1,92 +0,0 @@ -import AnalyticLightNode from './AnalyticLightNode.js'; -import { lightTargetDirection } from './LightNode.js'; -import { addLightNode } from './LightsNode.js'; -import { getDistanceAttenuation } from './LightUtils.js'; -import { uniform } from '../core/UniformNode.js'; -import { smoothstep } from '../math/MathNode.js'; -import { objectViewPosition } from '../accessors/Object3DNode.js'; -import { positionView } from '../accessors/PositionNode.js'; -import { addNodeClass } from '../core/Node.js'; - -import { SpotLight } from 'three'; - -class SpotLightNode extends AnalyticLightNode { - - constructor( light = null ) { - - super( light ); - - this.coneCosNode = uniform( 0 ); - this.penumbraCosNode = uniform( 0 ); - - this.cutoffDistanceNode = uniform( 0 ); - this.decayExponentNode = uniform( 0 ); - - } - - update( frame ) { - - super.update( frame ); - - const { light } = this; - - this.coneCosNode.value = Math.cos( light.angle ); - this.penumbraCosNode.value = Math.cos( light.angle * ( 1 - light.penumbra ) ); - - this.cutoffDistanceNode.value = light.distance; - this.decayExponentNode.value = light.decay; - - } - - getSpotAttenuation( angleCosine ) { - - const { coneCosNode, penumbraCosNode } = this; - - return smoothstep( coneCosNode, penumbraCosNode, angleCosine ); - - } - - construct( builder ) { - - super.construct( builder ); - - const { colorNode, cutoffDistanceNode, decayExponentNode, light } = this; - - const lVector = objectViewPosition( light ).sub( positionView ); // @TODO: Add it into LightNode - - const lightDirection = lVector.normalize(); - const angleCos = lightDirection.dot( lightTargetDirection( light ) ); - const spotAttenuation = this.getSpotAttenuation( angleCos ); - - const lightDistance = lVector.length(); - - const lightAttenuation = getDistanceAttenuation.call( { - lightDistance, - cutoffDistance: cutoffDistanceNode, - decayExponent: decayExponentNode - } ); - - const lightColor = colorNode.mul( spotAttenuation ).mul( lightAttenuation ); - - const lightingModelFunctionNode = builder.context.lightingModelNode; - const reflectedLight = builder.context.reflectedLight; - - if ( lightingModelFunctionNode && lightingModelFunctionNode.direct ) { - - lightingModelFunctionNode.direct.call( { - lightDirection, - lightColor, - reflectedLight - }, builder ); - - } - - } - -} - -export default SpotLightNode; - -addLightNode( SpotLight, SpotLightNode ); - -addNodeClass( SpotLightNode ); diff --git a/examples/jsm/nodes/loaders/NodeLoader.js b/examples/jsm/nodes/loaders/NodeLoader.js deleted file mode 100644 index fff46ef8ccbde6..00000000000000 --- a/examples/jsm/nodes/loaders/NodeLoader.js +++ /dev/null @@ -1,108 +0,0 @@ -import { createNodeFromType } from '../core/Node.js'; -import { nodeObject } from '../shadernode/ShaderNode.js'; -import { FileLoader, Loader } from 'three'; - -class NodeLoader extends Loader { - - constructor( manager ) { - - super( manager ); - - this.textures = {}; - - } - - load( url, onLoad, onProgress, onError ) { - - const loader = new FileLoader( this.manager ); - loader.setPath( this.path ); - loader.setRequestHeader( this.requestHeader ); - loader.setWithCredentials( this.withCredentials ); - loader.load( url, ( text ) => { - - try { - - onLoad( this.parse( JSON.parse( text ) ) ); - - } catch ( e ) { - - if ( onError ) { - - onError( e ); - - } else { - - console.error( e ); - - } - - this.manager.itemError( url ); - - } - - }, onProgress, onError ); - - } - - parseNodes( json ) { - - const nodes = {}; - - if ( json !== undefined ) { - - for ( const nodeJSON of json ) { - - const { uuid, type } = nodeJSON; - - nodes[ uuid ] = nodeObject( createNodeFromType( type ) ); // @TODO: Maybe nodeObjectify the node in createNodeFromType? - nodes[ uuid ].uuid = uuid; - - } - - const meta = { nodes, textures: this.textures }; - - for ( const nodeJSON of json ) { - - nodeJSON.meta = meta; - - const node = nodes[ nodeJSON.uuid ]; - node.deserialize( nodeJSON ); - - delete nodeJSON.meta; - - } - - } - - return nodes; - - } - - parse( json ) { - - const node = nodeObject( createNodeFromType( json.type ) ); - node.uuid = json.uuid; - - const nodes = this.parseNodes( json.nodes ); - const meta = { nodes, textures: this.textures }; - - json.meta = meta; - - node.deserialize( json ); - - delete json.meta; - - return node; - - } - - setTextures( value ) { - - this.textures = value; - return this; - - } - -} - -export default NodeLoader; diff --git a/examples/jsm/nodes/loaders/NodeMaterialLoader.js b/examples/jsm/nodes/loaders/NodeMaterialLoader.js deleted file mode 100644 index 2f2c55e9aeaf3f..00000000000000 --- a/examples/jsm/nodes/loaders/NodeMaterialLoader.js +++ /dev/null @@ -1,59 +0,0 @@ -import { MaterialLoader } from 'three'; -import { createNodeMaterialFromType } from '../materials/Materials.js'; - -const superFromTypeFunction = MaterialLoader.createMaterialFromType; - -MaterialLoader.createMaterialFromType = function ( type ) { - - const material = createNodeMaterialFromType( type ); - - if ( material !== undefined ) { - - return material; - - } - - return superFromTypeFunction.call( this, type ); - -}; - -class NodeMaterialLoader extends MaterialLoader { - - constructor( manager ) { - - super( manager ); - - this.nodes = {}; - - } - - parse( json ) { - - const material = super.parse( json ); - - const nodes = this.nodes; - const inputNodes = json.inputNodes; - - for ( const property in inputNodes ) { - - const uuid = inputNodes[ property ]; - - material[ property ] = nodes[ uuid ]; - - } - - return material; - - } - - setNodes( value ) { - - this.nodes = value; - - return this; - - } - -} - -export default NodeMaterialLoader; diff --git a/examples/jsm/nodes/loaders/NodeObjectLoader.js b/examples/jsm/nodes/loaders/NodeObjectLoader.js deleted file mode 100644 index c149b9948cfc4d..00000000000000 --- a/examples/jsm/nodes/loaders/NodeObjectLoader.js +++ /dev/null @@ -1,70 +0,0 @@ -import NodeLoader from './NodeLoader.js'; -import NodeMaterialLoader from './NodeMaterialLoader.js'; -import { ObjectLoader } from 'three'; - -class NodeObjectLoader extends ObjectLoader { - - constructor( manager ) { - - super( manager ); - - this._nodesJSON = null; - - } - - parse( json, onLoad ) { - - this._nodesJSON = json.nodes; - - const data = super.parse( json, onLoad ); - - this._nodesJSON = null; // dispose - - return data; - - } - - parseNodes( json, textures ) { - - if ( json !== undefined ) { - - const loader = new NodeLoader(); - loader.setTextures( textures ); - - return loader.parseNodes( json ); - - } - - return {}; - - } - - parseMaterials( json, textures ) { - - const materials = {}; - - if ( json !== undefined ) { - - const nodes = this.parseNodes( this._nodesJSON, textures ); - - const loader = new NodeMaterialLoader(); - loader.setTextures( textures ); - loader.setNodes( nodes ); - - for ( let i = 0, l = json.length; i < l; i ++ ) { - - const data = json[ i ]; - - materials[ data.uuid ] = loader.parse( data ); - - } - - } - - return materials; - - } - -} - -export default NodeObjectLoader; diff --git a/examples/jsm/nodes/materials/LineBasicNodeMaterial.js b/examples/jsm/nodes/materials/LineBasicNodeMaterial.js deleted file mode 100644 index a946c742799765..00000000000000 --- a/examples/jsm/nodes/materials/LineBasicNodeMaterial.js +++ /dev/null @@ -1,43 +0,0 @@ -import NodeMaterial, { addNodeMaterial } from './NodeMaterial.js'; - -import { LineBasicMaterial } from 'three'; - -const defaultValues = new LineBasicMaterial(); - -class LineBasicNodeMaterial extends NodeMaterial { - - constructor( parameters ) { - - super(); - - this.isLineBasicNodeMaterial = true; - - this.lights = false; - this.normals = false; - - this.setDefaultValues( defaultValues ); - - this.setValues( parameters ); - - } - - copy( source ) { - - this.colorNode = source.colorNode; - this.opacityNode = source.opacityNode; - - this.alphaTestNode = source.alphaTestNode; - - this.lightNode = source.lightNode; - - this.positionNode = source.positionNode; - - return super.copy( source ); - - } - -} - -export default LineBasicNodeMaterial; - -addNodeMaterial( LineBasicNodeMaterial ); diff --git a/examples/jsm/nodes/materials/Materials.js b/examples/jsm/nodes/materials/Materials.js deleted file mode 100644 index 8453c84c182ad3..00000000000000 --- a/examples/jsm/nodes/materials/Materials.js +++ /dev/null @@ -1,11 +0,0 @@ -// @TODO: We can simplify "export { default as SomeNode, other, exports } from '...'" to just "export * from '...'" if we will use only named exports - -export { default as NodeMaterial, addNodeMaterial, createNodeMaterialFromType } from './NodeMaterial.js'; -export { default as LineBasicNodeMaterial } from './LineBasicNodeMaterial.js'; -export { default as MeshNormalNodeMaterial } from './MeshNormalNodeMaterial.js'; -export { default as MeshBasicNodeMaterial } from './MeshBasicNodeMaterial.js'; -export { default as MeshPhongNodeMaterial } from './MeshPhongNodeMaterial.js'; -export { default as MeshStandardNodeMaterial } from './MeshStandardNodeMaterial.js'; -export { default as MeshPhysicalNodeMaterial } from './MeshPhysicalNodeMaterial.js'; -export { default as PointsNodeMaterial } from './PointsNodeMaterial.js'; -export { default as SpriteNodeMaterial } from './SpriteNodeMaterial.js'; diff --git a/examples/jsm/nodes/materials/MeshBasicNodeMaterial.js b/examples/jsm/nodes/materials/MeshBasicNodeMaterial.js deleted file mode 100644 index f4d6866b4c51a4..00000000000000 --- a/examples/jsm/nodes/materials/MeshBasicNodeMaterial.js +++ /dev/null @@ -1,42 +0,0 @@ -import NodeMaterial, { addNodeMaterial } from './NodeMaterial.js'; - -import { MeshBasicMaterial } from 'three'; - -const defaultValues = new MeshBasicMaterial(); - -class MeshBasicNodeMaterial extends NodeMaterial { - - constructor( parameters ) { - - super(); - - this.isMeshBasicNodeMaterial = true; - - this.lights = false; - - this.setDefaultValues( defaultValues ); - - this.setValues( parameters ); - - } - - copy( source ) { - - this.colorNode = source.colorNode; - this.opacityNode = source.opacityNode; - - this.alphaTestNode = source.alphaTestNode; - - this.lightNode = source.lightNode; - - this.positionNode = source.positionNode; - - return super.copy( source ); - - } - -} - -export default MeshBasicNodeMaterial; - -addNodeMaterial( MeshBasicNodeMaterial ); diff --git a/examples/jsm/nodes/materials/MeshNormalNodeMaterial.js b/examples/jsm/nodes/materials/MeshNormalNodeMaterial.js deleted file mode 100644 index b2574ed3f66c4d..00000000000000 --- a/examples/jsm/nodes/materials/MeshNormalNodeMaterial.js +++ /dev/null @@ -1,48 +0,0 @@ -import NodeMaterial, { addNodeMaterial } from './NodeMaterial.js'; -import { diffuseColor } from '../core/PropertyNode.js'; -import { directionToColor } from '../utils/PackingNode.js'; -import { materialOpacity } from '../accessors/MaterialNode.js'; -import { transformedNormalView } from '../accessors/NormalNode.js'; -import { float, vec4 } from '../shadernode/ShaderNode.js'; - -import { MeshNormalMaterial } from 'three'; - -const defaultValues = new MeshNormalMaterial(); - -class MeshNormalNodeMaterial extends NodeMaterial { - - constructor( parameters ) { - - super(); - - this.isMeshNormalNodeMaterial = true; - - this.setDefaultValues( defaultValues ); - - this.setValues( parameters ); - - } - - constructDiffuseColor( { stack } ) { - - const opacityNode = this.opacityNode ? float( this.opacityNode ) : materialOpacity; - - stack.assign( diffuseColor, vec4( directionToColor( transformedNormalView ), opacityNode ) ); - - } - - copy( source ) { - - this.opacityNode = source.opacityNode; - - this.positionNode = source.positionNode; - - return super.copy( source ); - - } - -} - -export default MeshNormalNodeMaterial; - -addNodeMaterial( MeshNormalNodeMaterial ); diff --git a/examples/jsm/nodes/materials/MeshPhongNodeMaterial.js b/examples/jsm/nodes/materials/MeshPhongNodeMaterial.js deleted file mode 100644 index f87436598fcdf1..00000000000000 --- a/examples/jsm/nodes/materials/MeshPhongNodeMaterial.js +++ /dev/null @@ -1,74 +0,0 @@ -import NodeMaterial, { addNodeMaterial } from './NodeMaterial.js'; -import { shininess, specularColor } from '../core/PropertyNode.js'; -import { materialShininess, materialSpecularColor } from '../accessors/MaterialNode.js'; -import phongLightingModel from '../functions/PhongLightingModel.js'; -import { float } from '../shadernode/ShaderNode.js'; - -import { MeshPhongMaterial } from 'three'; - -const defaultValues = new MeshPhongMaterial(); - -class MeshPhongNodeMaterial extends NodeMaterial { - - constructor( parameters ) { - - super(); - - this.isMeshPhongNodeMaterial = true; - - this.lights = true; - - this.shininessNode = null; - this.specularNode = null; - - this.setDefaultValues( defaultValues ); - - this.setValues( parameters ); - - } - - constructLightingModel( /*builder*/ ) { - - return phongLightingModel; - - } - - constructVariants( { stack } ) { - - // SHININESS - - const shininessNode = ( this.shininessNode ? float( this.shininessNode ) : materialShininess ).max( 1e-4 ); // to prevent pow( 0.0, 0.0 ) - - stack.assign( shininess, shininessNode ); - - // SPECULAR COLOR - - const specularNode = this.specularNode || materialSpecularColor; - - stack.assign( specularColor, specularNode ); - - } - - copy( source ) { - - this.colorNode = source.colorNode; - this.opacityNode = source.opacityNode; - - this.alphaTestNode = source.alphaTestNode; - - this.shininessNode = source.shininessNode; - this.specularNode = source.specularNode; - - this.lightNode = source.lightNode; - - this.positionNode = source.positionNode; - - return super.copy( source ); - - } - -} - -export default MeshPhongNodeMaterial; - -addNodeMaterial( MeshPhongNodeMaterial ); diff --git a/examples/jsm/nodes/materials/MeshPhysicalNodeMaterial.js b/examples/jsm/nodes/materials/MeshPhysicalNodeMaterial.js deleted file mode 100644 index 711449ab3ee1e2..00000000000000 --- a/examples/jsm/nodes/materials/MeshPhysicalNodeMaterial.js +++ /dev/null @@ -1,70 +0,0 @@ -import { addNodeMaterial } from './NodeMaterial.js'; -import MeshStandardNodeMaterial from './MeshStandardNodeMaterial.js'; - -import { MeshPhysicalMaterial } from 'three'; - -const defaultValues = new MeshPhysicalMaterial(); - -class MeshPhysicalNodeMaterial extends MeshStandardNodeMaterial { - - constructor( parameters ) { - - super(); - - this.isMeshPhysicalNodeMaterial = true; - - this.clearcoatNode = null; - this.clearcoatRoughnessNode = null; - this.clearcoatNormalNode = null; - - this.sheenNode = null; - this.sheenRoughnessNode = null; - - this.iridescenceNode = null; - this.iridescenceIORNode = null; - this.iridescenceThicknessNode = null; - - this.specularIntensityNode = null; - this.specularColorNode = null; - - this.transmissionNode = null; - this.thicknessNode = null; - this.attenuationDistanceNode = null; - this.attenuationColorNode = null; - - this.setDefaultValues( defaultValues ); - - this.setValues( parameters ); - - } - - copy( source ) { - - this.clearcoatNode = source.clearcoatNode; - this.clearcoatRoughnessNode = source.clearcoatRoughnessNode; - this.clearcoatNormalNode = source.clearcoatNormalNode; - - this.sheenNode = source.sheenNode; - this.sheenRoughnessNode = source.sheenRoughnessNode; - - this.iridescenceNode = source.iridescenceNode; - this.iridescenceIORNode = source.iridescenceIORNode; - this.iridescenceThicknessNode = source.iridescenceThicknessNode; - - this.specularIntensityNode = source.specularIntensityNode; - this.specularColorNode = source.specularColorNode; - - this.transmissionNode = source.transmissionNode; - this.thicknessNode = source.thicknessNode; - this.attenuationDistanceNode = source.attenuationDistanceNode; - this.attenuationColorNode = source.attenuationColorNode; - - return super.copy( source ); - - } - -} - -export default MeshPhysicalNodeMaterial; - -addNodeMaterial( MeshPhysicalNodeMaterial ); diff --git a/examples/jsm/nodes/materials/MeshStandardNodeMaterial.js b/examples/jsm/nodes/materials/MeshStandardNodeMaterial.js deleted file mode 100644 index bd156909cff656..00000000000000 --- a/examples/jsm/nodes/materials/MeshStandardNodeMaterial.js +++ /dev/null @@ -1,93 +0,0 @@ -import NodeMaterial, { addNodeMaterial } from './NodeMaterial.js'; -import { diffuseColor, metalness, roughness, specularColor } from '../core/PropertyNode.js'; -import { mix } from '../math/MathNode.js'; -import { materialRoughness, materialMetalness } from '../accessors/MaterialNode.js'; -import getRoughness from '../functions/material/getRoughness.js'; -import physicalLightingModel from '../functions/PhysicalLightingModel.js'; -import { float, vec3, vec4 } from '../shadernode/ShaderNode.js'; - -import { MeshStandardMaterial } from 'three'; - -const defaultValues = new MeshStandardMaterial(); - -class MeshStandardNodeMaterial extends NodeMaterial { - - constructor( parameters ) { - - super(); - - this.isMeshStandardNodeMaterial = true; - - this.emissiveNode = null; - - this.metalnessNode = null; - this.roughnessNode = null; - - this.setDefaultValues( defaultValues ); - - this.setValues( parameters ); - - } - - constructLightingModel( /*builder*/ ) { - - return physicalLightingModel; - - } - - constructVariants( { stack } ) { - - // METALNESS - - const metalnessNode = this.metalnessNode ? float( this.metalnessNode ) : materialMetalness; - - stack.assign( metalness, metalnessNode ); - - // ROUGHNESS - - let roughnessNode = this.roughnessNode ? float( this.roughnessNode ) : materialRoughness; - roughnessNode = getRoughness.call( { roughness: roughnessNode } ); - - stack.assign( roughness, roughnessNode ); - - // SPECULAR COLOR - - const specularColorNode = mix( vec3( 0.04 ), diffuseColor.rgb, metalnessNode ); - - stack.assign( specularColor, specularColorNode ); - - // DIFFUSE COLOR - - stack.assign( diffuseColor, vec4( diffuseColor.rgb.mul( metalnessNode.oneMinus() ), diffuseColor.a ) ); - - } - - copy( source ) { - - this.colorNode = source.colorNode; - this.opacityNode = source.opacityNode; - - this.alphaTestNode = source.alphaTestNode; - - this.normalNode = source.normalNode; - - this.emissiveNode = source.emissiveNode; - - this.metalnessNode = source.metalnessNode; - this.roughnessNode = source.roughnessNode; - - this.envNode = source.envNode; - - this.lightsNode = source.lightsNode; - - this.positionNode = source.positionNode; - - return super.copy( source ); - - } - -} - -export default MeshStandardNodeMaterial; - -addNodeMaterial( MeshStandardNodeMaterial ); diff --git a/examples/jsm/nodes/materials/NodeMaterial.js b/examples/jsm/nodes/materials/NodeMaterial.js deleted file mode 100644 index 5e1555e9908dc6..00000000000000 --- a/examples/jsm/nodes/materials/NodeMaterial.js +++ /dev/null @@ -1,420 +0,0 @@ -import { Material, ShaderMaterial } from 'three'; -import { getNodeChildren, getCacheKey } from '../core/NodeUtils.js'; -import { attribute } from '../core/AttributeNode.js'; -import { diffuseColor } from '../core/PropertyNode.js'; -import { materialNormal } from '../accessors/ExtendedMaterialNode.js'; -import { materialAlphaTest, materialColor, materialOpacity, materialEmissive } from '../accessors/MaterialNode.js'; -import { modelViewProjection } from '../accessors/ModelViewProjectionNode.js'; -import { transformedNormalView } from '../accessors/NormalNode.js'; -import { instance } from '../accessors/InstanceNode.js'; -import { positionLocal } from '../accessors/PositionNode.js'; -import { skinning } from '../accessors/SkinningNode.js'; -import { texture } from '../accessors/TextureNode.js'; -import { lightsWithoutWrap } from '../lighting/LightsNode.js'; -import { mix } from '../math/MathNode.js'; -import { float, vec3, vec4 } from '../shadernode/ShaderNode.js'; -import AONode from '../lighting/AONode.js'; -import EnvironmentNode from '../lighting/EnvironmentNode.js'; - -const NodeMaterials = new Map(); - -class NodeMaterial extends ShaderMaterial { - - constructor() { - - super(); - - this.isNodeMaterial = true; - - this.type = this.constructor.name; - - this.lights = true; - this.normals = true; - - this.lightsNode = null; - this.envNode = null; - - this.colorNode = null; - this.normalNode = null; - this.opacityNode = null; - this.backdropNode = null; - this.backdropAlphaNode = null; - this.alphaTestNode = null; - - this.positionNode = null; - - } - - customProgramCacheKey() { - - return getCacheKey( this ); - - } - - build( builder ) { - - this.construct( builder ); - - } - - construct( builder ) { - - // < VERTEX STAGE > - - builder.addStack(); - - builder.stack.outputNode = this.constructPosition( builder ); - - builder.addFlow( 'vertex', builder.removeStack() ); - - // < FRAGMENT STAGE > - - builder.addStack(); - - if ( this.normals === true ) this.constructNormal( builder ); - - this.constructDiffuseColor( builder ); - this.constructVariants( builder ); - - const outgoingLightNode = this.constructLighting( builder ); - - builder.stack.outputNode = this.constructOutput( builder, outgoingLightNode, diffuseColor.a ); - - builder.addFlow( 'fragment', builder.removeStack() ); - - } - - constructPosition( builder ) { - - const object = builder.object; - - let vertex = positionLocal; - - if ( this.positionNode !== null ) { - - vertex = vertex.bypass( positionLocal.assign( this.positionNode ) ); - - } - - if ( ( object.instanceMatrix && object.instanceMatrix.isInstancedBufferAttribute === true ) && builder.isAvailable( 'instance' ) === true ) { - - vertex = vertex.bypass( instance( object ) ); - - } - - if ( object.isSkinnedMesh === true ) { - - vertex = vertex.bypass( skinning( object ) ); - - } - - builder.context.vertex = vertex; - - return modelViewProjection(); - - } - - constructDiffuseColor( { stack, geometry } ) { - - let colorNode = this.colorNode ? vec4( this.colorNode ) : materialColor; - - // VERTEX COLORS - - if ( this.vertexColors === true && geometry.hasAttribute( 'color' ) ) { - - colorNode = vec4( colorNode.xyz.mul( attribute( 'color' ) ), colorNode.a ); - - } - - // COLOR - - stack.assign( diffuseColor, colorNode ); - - // OPACITY - - const opacityNode = this.opacityNode ? float( this.opacityNode ) : materialOpacity; - stack.assign( diffuseColor.a, diffuseColor.a.mul( opacityNode ) ); - - // ALPHA TEST - - if ( this.alphaTestNode || this.alphaTest > 0 ) { - - const alphaTestNode = this.alphaTestNode ? float( this.alphaTestNode ) : materialAlphaTest; - - stack.add( diffuseColor.a.lessThanEqual( alphaTestNode ).discard() ); - - } - - } - - constructVariants( /*builder*/ ) { - - // Interface function. - - } - - constructNormal( { stack } ) { - - // NORMAL VIEW - - const normalNode = this.normalNode ? vec3( this.normalNode ) : materialNormal; - - stack.assign( transformedNormalView, normalNode ); - - return normalNode; - - } - - constructLights( builder ) { - - const envNode = this.envNode || builder.environmentNode; - - const materialLightsNode = []; - - if ( envNode ) { - - materialLightsNode.push( new EnvironmentNode( envNode ) ); - - } - - if ( builder.material.aoMap ) { - - materialLightsNode.push( new AONode( texture( builder.material.aoMap ) ) ); - - } - - let lightsNode = this.lightsNode || builder.lightsNode; - - if ( materialLightsNode.length > 0 ) { - - lightsNode = lightsWithoutWrap( [ ...lightsNode.lightNodes, ...materialLightsNode ] ); - - } - - return lightsNode; - - } - - constructLightingModel( /*builder*/ ) { - - // Interface function. - - } - - constructLighting( builder ) { - - const { material } = builder; - const { backdropNode, backdropAlphaNode, emissiveNode } = this; - - // OUTGOING LIGHT - - const lights = this.lights === true || this.lightsNode !== null; - - const lightsNode = lights ? this.constructLights( builder ) : null; - const lightingModelNode = lightsNode ? this.constructLightingModel( builder ) : null; - - let outgoingLightNode = diffuseColor.rgb; - - if ( lightsNode && lightsNode.hasLight !== false ) { - - outgoingLightNode = lightsNode.lightingContext( lightingModelNode, backdropNode, backdropAlphaNode ); - - } else if ( backdropNode !== null ) { - - outgoingLightNode = vec3( backdropAlphaNode !== null ? mix( outgoingLightNode, backdropNode, backdropAlphaNode ) : backdropNode ); - - } - - // EMISSIVE - - if ( ( emissiveNode && emissiveNode.isNode === true ) || ( material.emissive && material.emissive.isColor === true ) ) { - - outgoingLightNode = outgoingLightNode.add( emissiveNode ? vec3( emissiveNode ) : materialEmissive ); - - } - - return outgoingLightNode; - - } - - constructOutput( builder, outgoingLight, opacity ) { - - const renderer = builder.renderer; - - // TONE MAPPING - - const toneMappingNode = builder.toneMappingNode; - - if ( toneMappingNode ) { - - outgoingLight = toneMappingNode.context( { color: outgoingLight } ); - - } - - // @TODO: Optimize outputNode to vec3. - - let outputNode = vec4( outgoingLight, opacity ); - - // ENCODING - - outputNode = outputNode.colorSpace( renderer.outputColorSpace ); - - // FOG - - const fogNode = builder.fogNode; - - if ( fogNode ) outputNode = vec4( fogNode.mixAssign( outputNode.rgb ), outputNode.a ); - - return outputNode; - - } - - setDefaultValues( material ) { - - // This approach is to reuse the native refreshUniforms* - // and turn available the use of features like transmission and environment in core - - for ( const property in material ) { - - const value = material[ property ]; - - if ( this[ property ] === undefined ) { - - this[ property ] = value; - - if ( value && value.clone ) this[ property ] = value.clone(); - - } - - } - - Object.assign( this.defines, material.defines ); - - const descriptors = Object.getOwnPropertyDescriptors( material.constructor.prototype ); - - for ( const key in descriptors ) { - - if ( Object.getOwnPropertyDescriptor( this.constructor.prototype, key ) === undefined && - descriptors[ key ].get !== undefined ) { - - Object.defineProperty( this.constructor.prototype, key, descriptors[ key ] ); - - } - - } - - } - - toJSON( meta ) { - - const isRoot = ( meta === undefined || typeof meta === 'string' ); - - if ( isRoot ) { - - meta = { - textures: {}, - images: {}, - nodes: {} - }; - - } - - const data = Material.prototype.toJSON.call( this, meta ); - const nodeChildren = getNodeChildren( this ); - - data.inputNodes = {}; - - for ( const { property, childNode } of nodeChildren ) { - - data.inputNodes[ property ] = childNode.toJSON( meta ).uuid; - - } - - // TODO: Copied from Object3D.toJSON - - function extractFromCache( cache ) { - - const values = []; - - for ( const key in cache ) { - - const data = cache[ key ]; - delete data.metadata; - values.push( data ); - - } - - return values; - - } - - if ( isRoot ) { - - const textures = extractFromCache( meta.textures ); - const images = extractFromCache( meta.images ); - const nodes = extractFromCache( meta.nodes ); - - if ( textures.length > 0 ) data.textures = textures; - if ( images.length > 0 ) data.images = images; - if ( nodes.length > 0 ) data.nodes = nodes; - - } - - return data; - - } - - static fromMaterial( material ) { - - if ( material.isNodeMaterial === true ) { // is already a node material - - return material; - - } - - const type = material.type.replace( 'Material', 'NodeMaterial' ); - - const nodeMaterial = createNodeMaterialFromType( type ); - - if ( nodeMaterial === undefined ) { - - throw new Error( `NodeMaterial: Material "${ material.type }" is not compatible.` ); - - } - - for ( const key in material ) { - - nodeMaterial[ key ] = material[ key ]; - - } - - return nodeMaterial; - - } - -} - -export default NodeMaterial; - -export function addNodeMaterial( nodeMaterial ) { - - if ( typeof nodeMaterial !== 'function' || ! nodeMaterial.name ) throw new Error( `Node material ${ nodeMaterial.name } is not a class` ); - if ( NodeMaterials.has( nodeMaterial.name ) ) throw new Error( `Redefinition of node material ${ nodeMaterial.name }` ); - - NodeMaterials.set( nodeMaterial.name, nodeMaterial ); - -} - -export function createNodeMaterialFromType( type ) { - - const Material = NodeMaterials.get( type ); - - if ( Material !== undefined ) { - - return new Material(); - - } - -} - -addNodeMaterial( NodeMaterial ); diff --git a/examples/jsm/nodes/materials/PointsNodeMaterial.js b/examples/jsm/nodes/materials/PointsNodeMaterial.js deleted file mode 100644 index 14702ab959c488..00000000000000 --- a/examples/jsm/nodes/materials/PointsNodeMaterial.js +++ /dev/null @@ -1,58 +0,0 @@ -import NodeMaterial, { addNodeMaterial } from './NodeMaterial.js'; - -import { PointsMaterial } from 'three'; - -const defaultValues = new PointsMaterial(); - -class PointsNodeMaterial extends NodeMaterial { - - constructor( parameters ) { - - super(); - - this.isPointsNodeMaterial = true; - - this.lights = false; - this.normals = false; - - this.transparent = true; - - this.colorNode = null; - this.opacityNode = null; - - this.alphaTestNode = null; - - this.lightNode = null; - - this.sizeNode = null; - - this.positionNode = null; - - this.setDefaultValues( defaultValues ); - - this.setValues( parameters ); - - } - - copy( source ) { - - this.colorNode = source.colorNode; - this.opacityNode = source.opacityNode; - - this.alphaTestNode = source.alphaTestNode; - - this.lightNode = source.lightNode; - - this.sizeNode = source.sizeNode; - - this.positionNode = source.positionNode; - - return super.copy( source ); - - } - -} - -export default PointsNodeMaterial; - -addNodeMaterial( PointsNodeMaterial ); diff --git a/examples/jsm/nodes/materials/SpriteNodeMaterial.js b/examples/jsm/nodes/materials/SpriteNodeMaterial.js deleted file mode 100644 index cb0b2932634c78..00000000000000 --- a/examples/jsm/nodes/materials/SpriteNodeMaterial.js +++ /dev/null @@ -1,110 +0,0 @@ -import NodeMaterial, { addNodeMaterial } from './NodeMaterial.js'; -import { uniform } from '../core/UniformNode.js'; -import { cameraProjectionMatrix } from '../accessors/CameraNode.js'; -import { materialRotation } from '../accessors/MaterialNode.js'; -import { modelViewMatrix, modelWorldMatrix } from '../accessors/ModelNode.js'; -import { positionLocal } from '../accessors/PositionNode.js'; -import { vec2, vec3, vec4 } from '../shadernode/ShaderNode.js'; - -import { SpriteMaterial } from 'three'; - -const defaultValues = new SpriteMaterial(); - -class SpriteNodeMaterial extends NodeMaterial { - - constructor( parameters ) { - - super(); - - this.isSpriteNodeMaterial = true; - - this.lights = false; - this.normals = false; - - this.colorNode = null; - this.opacityNode = null; - - this.alphaTestNode = null; - - this.lightNode = null; - - this.positionNode = null; - this.rotationNode = null; - this.scaleNode = null; - - this.setDefaultValues( defaultValues ); - - this.setValues( parameters ); - - } - - constructPosition( { object, context } ) { - - // < VERTEX STAGE > - - const { positionNode, rotationNode, scaleNode } = this; - - const vertex = positionLocal; - - let mvPosition = modelViewMatrix.mul( vec3( positionNode || 0 ) ); - - let scale = vec2( modelWorldMatrix[ 0 ].xyz.length(), modelWorldMatrix[ 1 ].xyz.length() ); - - if ( scaleNode !== null ) { - - scale = scale.mul( scaleNode ); - - } - - let alignedPosition = vertex.xy; - - if ( object.center && object.center.isVector2 === true ) { - - alignedPosition = alignedPosition.sub( uniform( object.center ).sub( 0.5 ) ); - - } - - alignedPosition = alignedPosition.mul( scale ); - - const rotation = rotationNode || materialRotation; - - const cosAngle = rotation.cos(); - const sinAngle = rotation.sin(); - - const rotatedPosition = vec2( // @TODO: Maybe we can create mat2 and write something like rotationMatrix.mul( alignedPosition )? - vec2( cosAngle, sinAngle.negate() ).dot( alignedPosition ), - vec2( sinAngle, cosAngle ).dot( alignedPosition ) - ); - - mvPosition = vec4( mvPosition.xy.add( rotatedPosition ), mvPosition.zw ); - - const modelViewProjection = cameraProjectionMatrix.mul( mvPosition ); - - context.vertex = vertex; - - return modelViewProjection; - - } - - copy( source ) { - - this.colorNode = source.colorNode; - this.opacityNode = source.opacityNode; - - this.alphaTestNode = source.alphaTestNode; - - this.lightNode = source.lightNode; - - this.positionNode = source.positionNode; - this.rotationNode = source.rotationNode; - this.scaleNode = source.scaleNode; - - return super.copy( source ); - - } - -} - -export default SpriteNodeMaterial; - -addNodeMaterial( SpriteNodeMaterial ); diff --git a/examples/jsm/nodes/materialx/lib/mx_hsv.js b/examples/jsm/nodes/materialx/lib/mx_hsv.js deleted file mode 100644 index 11c27f58f33d8c..00000000000000 --- a/examples/jsm/nodes/materialx/lib/mx_hsv.js +++ /dev/null @@ -1,56 +0,0 @@ -import { fn } from '../../code/FunctionNode.js'; - -// Original shader code from: -// https://github.com/AcademySoftwareFoundation/MaterialX/blob/main/libraries/stdlib/genglsl/lib/mx_hsv.glsl - -export const mx_hsvtorgb = fn( `vec3 mx_hsvtorgb(vec3 hsv) -{ - // Reference for this technique: Foley & van Dam - float h = hsv.x; float s = hsv.y; float v = hsv.z; - if (s < 0.0001f) { - return vec3 (v, v, v); - } else { - h = 6.0f * (h - floor(h)); // expand to [0..6) - int hi = int(trunc(h)); - float f = h - float(hi); - float p = v * (1.0f-s); - float q = v * (1.0f-s*f); - float t = v * (1.0f-s*(1.0f-f)); - if (hi == 0) - return vec3 (v, t, p); - else if (hi == 1) - return vec3 (q, v, p); - else if (hi == 2) - return vec3 (p, v, t); - else if (hi == 3) - return vec3 (p, q, v); - else if (hi == 4) - return vec3 (t, p, v); - return vec3 (v, p, q); - } -}` ); - -export const mx_rgbtohsv = fn( `vec3 mx_rgbtohsv(vec3 c) -{ - // See Foley & van Dam - float r = c.x; float g = c.y; float b = c.z; - float mincomp = min (r, min(g, b)); - float maxcomp = max (r, max(g, b)); - float delta = maxcomp - mincomp; // chroma - float h, s, v; - v = maxcomp; - if (maxcomp > 0.0f) - s = delta / maxcomp; - else s = 0.0f; - if (s <= 0.0f) - h = 0.0f; - else { - if (r >= maxcomp) h = (g-b) / delta; - else if (g >= maxcomp) h = 2.0f + (b-r) / delta; - else h = 4.0f + (r-g) / delta; - h *= (1.0f/6.0f); - if (h < 0.0f) - h += 1.0f; - } - return vec3(h, s, v); -}` ); diff --git a/examples/jsm/nodes/materialx/lib/mx_noise.js b/examples/jsm/nodes/materialx/lib/mx_noise.js deleted file mode 100644 index 3442d512881198..00000000000000 --- a/examples/jsm/nodes/materialx/lib/mx_noise.js +++ /dev/null @@ -1,618 +0,0 @@ -import { code } from '../../code/CodeNode.js'; -import { fn } from '../../code/FunctionNode.js'; - -// Original shader code from: -// https://github.com/AcademySoftwareFoundation/MaterialX/blob/main/libraries/stdlib/genglsl/lib/mx_noise.glsl - -export const mx_noise = code( `float mx_select(bool b, float t, float f) -{ - return b ? t : f; -} - -float mx_negate_if(float val, bool b) -{ - return b ? -val : val; -} - -int mx_floor(float x) -{ - return int(floor(x)); -} - -// return mx_floor as well as the fractional remainder -float mx_floorfrac(float x, out int i) -{ - i = mx_floor(x); - return x - float(i); -} - -float mx_bilerp(float v0, float v1, float v2, float v3, float s, float t) -{ - float s1 = 1.0 - s; - return (1.0 - t) * (v0*s1 + v1*s) + t * (v2*s1 + v3*s); -} -vec3 mx_bilerp(vec3 v0, vec3 v1, vec3 v2, vec3 v3, float s, float t) -{ - float s1 = 1.0 - s; - return (1.0 - t) * (v0*s1 + v1*s) + t * (v2*s1 + v3*s); -} -float mx_trilerp(float v0, float v1, float v2, float v3, float v4, float v5, float v6, float v7, float s, float t, float r) -{ - float s1 = 1.0 - s; - float t1 = 1.0 - t; - float r1 = 1.0 - r; - return (r1*(t1*(v0*s1 + v1*s) + t*(v2*s1 + v3*s)) + - r*(t1*(v4*s1 + v5*s) + t*(v6*s1 + v7*s))); -} -vec3 mx_trilerp(vec3 v0, vec3 v1, vec3 v2, vec3 v3, vec3 v4, vec3 v5, vec3 v6, vec3 v7, float s, float t, float r) -{ - float s1 = 1.0 - s; - float t1 = 1.0 - t; - float r1 = 1.0 - r; - return (r1*(t1*(v0*s1 + v1*s) + t*(v2*s1 + v3*s)) + - r*(t1*(v4*s1 + v5*s) + t*(v6*s1 + v7*s))); -} - -// 2 and 3 dimensional gradient functions - perform a dot product against a -// randomly chosen vector. Note that the gradient vector is not normalized, but -// this only affects the overal "scale" of the result, so we simply account for -// the scale by multiplying in the corresponding "perlin" function. -float mx_gradient_float(uint hash, float x, float y) -{ - // 8 possible directions (+-1,+-2) and (+-2,+-1) - uint h = hash & 7u; - float u = mx_select(h<4u, x, y); - float v = 2.0 * mx_select(h<4u, y, x); - // compute the dot product with (x,y). - return mx_negate_if(u, bool(h&1u)) + mx_negate_if(v, bool(h&2u)); -} -float mx_gradient_float(uint hash, float x, float y, float z) -{ - // use vectors pointing to the edges of the cube - uint h = hash & 15u; - float u = mx_select(h<8u, x, y); - float v = mx_select(h<4u, y, mx_select((h==12u)||(h==14u), x, z)); - return mx_negate_if(u, bool(h&1u)) + mx_negate_if(v, bool(h&2u)); -} -vec3 mx_gradient_vec3(uvec3 hash, float x, float y) -{ - return vec3(mx_gradient_float(hash.x, x, y), mx_gradient_float(hash.y, x, y), mx_gradient_float(hash.z, x, y)); -} -vec3 mx_gradient_vec3(uvec3 hash, float x, float y, float z) -{ - return vec3(mx_gradient_float(hash.x, x, y, z), mx_gradient_float(hash.y, x, y, z), mx_gradient_float(hash.z, x, y, z)); -} -// Scaling factors to normalize the result of gradients above. -// These factors were experimentally calculated to be: -// 2D: 0.6616 -// 3D: 0.9820 -float mx_gradient_scale2d(float v) { return 0.6616 * v; } -float mx_gradient_scale3d(float v) { return 0.9820 * v; } -vec3 mx_gradient_scale2d(vec3 v) { return 0.6616 * v; } -vec3 mx_gradient_scale3d(vec3 v) { return 0.9820 * v; } - -/// Bitwise circular rotation left by k bits (for 32 bit unsigned integers) -uint mx_rotl32(uint x, int k) -{ - return (x<>(32-k)); -} - -void mx_bjmix(inout uint a, inout uint b, inout uint c) -{ - a -= c; a ^= mx_rotl32(c, 4); c += b; - b -= a; b ^= mx_rotl32(a, 6); a += c; - c -= b; c ^= mx_rotl32(b, 8); b += a; - a -= c; a ^= mx_rotl32(c,16); c += b; - b -= a; b ^= mx_rotl32(a,19); a += c; - c -= b; c ^= mx_rotl32(b, 4); b += a; -} - -// Mix up and combine the bits of a, b, and c (doesn't change them, but -// returns a hash of those three original values). -uint mx_bjfinal(uint a, uint b, uint c) -{ - c ^= b; c -= mx_rotl32(b,14); - a ^= c; a -= mx_rotl32(c,11); - b ^= a; b -= mx_rotl32(a,25); - c ^= b; c -= mx_rotl32(b,16); - a ^= c; a -= mx_rotl32(c,4); - b ^= a; b -= mx_rotl32(a,14); - c ^= b; c -= mx_rotl32(b,24); - return c; -} - -// Convert a 32 bit integer into a floating point number in [0,1] -float mx_bits_to_01(uint bits) -{ - return float(bits) / float(uint(0xffffffff)); -} - -float mx_fade(float t) -{ - return t * t * t * (t * (t * 6.0 - 15.0) + 10.0); -} - -uint mx_hash_int(int x) -{ - uint len = 1u; - uint seed = uint(0xdeadbeef) + (len << 2u) + 13u; - return mx_bjfinal(seed+uint(x), seed, seed); -} - -uint mx_hash_int(int x, int y) -{ - uint len = 2u; - uint a, b, c; - a = b = c = uint(0xdeadbeef) + (len << 2u) + 13u; - a += uint(x); - b += uint(y); - return mx_bjfinal(a, b, c); -} - -uint mx_hash_int(int x, int y, int z) -{ - uint len = 3u; - uint a, b, c; - a = b = c = uint(0xdeadbeef) + (len << 2u) + 13u; - a += uint(x); - b += uint(y); - c += uint(z); - return mx_bjfinal(a, b, c); -} - -uint mx_hash_int(int x, int y, int z, int xx) -{ - uint len = 4u; - uint a, b, c; - a = b = c = uint(0xdeadbeef) + (len << 2u) + 13u; - a += uint(x); - b += uint(y); - c += uint(z); - mx_bjmix(a, b, c); - a += uint(xx); - return mx_bjfinal(a, b, c); -} - -uint mx_hash_int(int x, int y, int z, int xx, int yy) -{ - uint len = 5u; - uint a, b, c; - a = b = c = uint(0xdeadbeef) + (len << 2u) + 13u; - a += uint(x); - b += uint(y); - c += uint(z); - mx_bjmix(a, b, c); - a += uint(xx); - b += uint(yy); - return mx_bjfinal(a, b, c); -} - -uvec3 mx_hash_vec3(int x, int y) -{ - uint h = mx_hash_int(x, y); - // we only need the low-order bits to be random, so split out - // the 32 bit result into 3 parts for each channel - uvec3 result; - result.x = (h ) & 0xFFu; - result.y = (h >> 8 ) & 0xFFu; - result.z = (h >> 16) & 0xFFu; - return result; -} - -uvec3 mx_hash_vec3(int x, int y, int z) -{ - uint h = mx_hash_int(x, y, z); - // we only need the low-order bits to be random, so split out - // the 32 bit result into 3 parts for each channel - uvec3 result; - result.x = (h ) & 0xFFu; - result.y = (h >> 8 ) & 0xFFu; - result.z = (h >> 16) & 0xFFu; - return result; -} - -float mx_perlin_noise_float(vec2 p) -{ - int X, Y; - float fx = mx_floorfrac(p.x, X); - float fy = mx_floorfrac(p.y, Y); - float u = mx_fade(fx); - float v = mx_fade(fy); - float result = mx_bilerp( - mx_gradient_float(mx_hash_int(X , Y ), fx , fy ), - mx_gradient_float(mx_hash_int(X+1, Y ), fx-1.0, fy ), - mx_gradient_float(mx_hash_int(X , Y+1), fx , fy-1.0), - mx_gradient_float(mx_hash_int(X+1, Y+1), fx-1.0, fy-1.0), - u, v); - return mx_gradient_scale2d(result); -} - -float mx_perlin_noise_float(vec3 p) -{ - int X, Y, Z; - float fx = mx_floorfrac(p.x, X); - float fy = mx_floorfrac(p.y, Y); - float fz = mx_floorfrac(p.z, Z); - float u = mx_fade(fx); - float v = mx_fade(fy); - float w = mx_fade(fz); - float result = mx_trilerp( - mx_gradient_float(mx_hash_int(X , Y , Z ), fx , fy , fz ), - mx_gradient_float(mx_hash_int(X+1, Y , Z ), fx-1.0, fy , fz ), - mx_gradient_float(mx_hash_int(X , Y+1, Z ), fx , fy-1.0, fz ), - mx_gradient_float(mx_hash_int(X+1, Y+1, Z ), fx-1.0, fy-1.0, fz ), - mx_gradient_float(mx_hash_int(X , Y , Z+1), fx , fy , fz-1.0), - mx_gradient_float(mx_hash_int(X+1, Y , Z+1), fx-1.0, fy , fz-1.0), - mx_gradient_float(mx_hash_int(X , Y+1, Z+1), fx , fy-1.0, fz-1.0), - mx_gradient_float(mx_hash_int(X+1, Y+1, Z+1), fx-1.0, fy-1.0, fz-1.0), - u, v, w); - return mx_gradient_scale3d(result); -} - -vec3 mx_perlin_noise_vec3(vec2 p) -{ - int X, Y; - float fx = mx_floorfrac(p.x, X); - float fy = mx_floorfrac(p.y, Y); - float u = mx_fade(fx); - float v = mx_fade(fy); - vec3 result = mx_bilerp( - mx_gradient_vec3(mx_hash_vec3(X , Y ), fx , fy ), - mx_gradient_vec3(mx_hash_vec3(X+1, Y ), fx-1.0, fy ), - mx_gradient_vec3(mx_hash_vec3(X , Y+1), fx , fy-1.0), - mx_gradient_vec3(mx_hash_vec3(X+1, Y+1), fx-1.0, fy-1.0), - u, v); - return mx_gradient_scale2d(result); -} - -vec3 mx_perlin_noise_vec3(vec3 p) -{ - int X, Y, Z; - float fx = mx_floorfrac(p.x, X); - float fy = mx_floorfrac(p.y, Y); - float fz = mx_floorfrac(p.z, Z); - float u = mx_fade(fx); - float v = mx_fade(fy); - float w = mx_fade(fz); - vec3 result = mx_trilerp( - mx_gradient_vec3(mx_hash_vec3(X , Y , Z ), fx , fy , fz ), - mx_gradient_vec3(mx_hash_vec3(X+1, Y , Z ), fx-1.0, fy , fz ), - mx_gradient_vec3(mx_hash_vec3(X , Y+1, Z ), fx , fy-1.0, fz ), - mx_gradient_vec3(mx_hash_vec3(X+1, Y+1, Z ), fx-1.0, fy-1.0, fz ), - mx_gradient_vec3(mx_hash_vec3(X , Y , Z+1), fx , fy , fz-1.0), - mx_gradient_vec3(mx_hash_vec3(X+1, Y , Z+1), fx-1.0, fy , fz-1.0), - mx_gradient_vec3(mx_hash_vec3(X , Y+1, Z+1), fx , fy-1.0, fz-1.0), - mx_gradient_vec3(mx_hash_vec3(X+1, Y+1, Z+1), fx-1.0, fy-1.0, fz-1.0), - u, v, w); - return mx_gradient_scale3d(result); -} - -float mx_cell_noise_float(float p) -{ - int ix = mx_floor(p); - return mx_bits_to_01(mx_hash_int(ix)); -} - -float mx_cell_noise_float(vec2 p) -{ - int ix = mx_floor(p.x); - int iy = mx_floor(p.y); - return mx_bits_to_01(mx_hash_int(ix, iy)); -} - -float mx_cell_noise_float(vec3 p) -{ - int ix = mx_floor(p.x); - int iy = mx_floor(p.y); - int iz = mx_floor(p.z); - return mx_bits_to_01(mx_hash_int(ix, iy, iz)); -} - -float mx_cell_noise_float(vec4 p) -{ - int ix = mx_floor(p.x); - int iy = mx_floor(p.y); - int iz = mx_floor(p.z); - int iw = mx_floor(p.w); - return mx_bits_to_01(mx_hash_int(ix, iy, iz, iw)); -} - -vec3 mx_cell_noise_vec3(float p) -{ - int ix = mx_floor(p); - return vec3( - mx_bits_to_01(mx_hash_int(ix, 0)), - mx_bits_to_01(mx_hash_int(ix, 1)), - mx_bits_to_01(mx_hash_int(ix, 2)) - ); -} - -vec3 mx_cell_noise_vec3(vec2 p) -{ - int ix = mx_floor(p.x); - int iy = mx_floor(p.y); - return vec3( - mx_bits_to_01(mx_hash_int(ix, iy, 0)), - mx_bits_to_01(mx_hash_int(ix, iy, 1)), - mx_bits_to_01(mx_hash_int(ix, iy, 2)) - ); -} - -vec3 mx_cell_noise_vec3(vec3 p) -{ - int ix = mx_floor(p.x); - int iy = mx_floor(p.y); - int iz = mx_floor(p.z); - return vec3( - mx_bits_to_01(mx_hash_int(ix, iy, iz, 0)), - mx_bits_to_01(mx_hash_int(ix, iy, iz, 1)), - mx_bits_to_01(mx_hash_int(ix, iy, iz, 2)) - ); -} - -vec3 mx_cell_noise_vec3(vec4 p) -{ - int ix = mx_floor(p.x); - int iy = mx_floor(p.y); - int iz = mx_floor(p.z); - int iw = mx_floor(p.w); - return vec3( - mx_bits_to_01(mx_hash_int(ix, iy, iz, iw, 0)), - mx_bits_to_01(mx_hash_int(ix, iy, iz, iw, 1)), - mx_bits_to_01(mx_hash_int(ix, iy, iz, iw, 2)) - ); -} - -float mx_fractal_noise_float(vec3 p, int octaves, float lacunarity, float diminish) -{ - float result = 0.0; - float amplitude = 1.0; - for (int i = 0; i < octaves; ++i) - { - result += amplitude * mx_perlin_noise_float(p); - amplitude *= diminish; - p *= lacunarity; - } - return result; -} - -vec3 mx_fractal_noise_vec3(vec3 p, int octaves, float lacunarity, float diminish) -{ - vec3 result = vec3(0.0); - float amplitude = 1.0; - for (int i = 0; i < octaves; ++i) - { - result += amplitude * mx_perlin_noise_vec3(p); - amplitude *= diminish; - p *= lacunarity; - } - return result; -} - -vec2 mx_fractal_noise_vec2(vec3 p, int octaves, float lacunarity, float diminish) -{ - return vec2(mx_fractal_noise_float(p, octaves, lacunarity, diminish), - mx_fractal_noise_float(p+vec3(19, 193, 17), octaves, lacunarity, diminish)); -} - -vec4 mx_fractal_noise_vec4(vec3 p, int octaves, float lacunarity, float diminish) -{ - vec3 c = mx_fractal_noise_vec3(p, octaves, lacunarity, diminish); - float f = mx_fractal_noise_float(p+vec3(19, 193, 17), octaves, lacunarity, diminish); - return vec4(c, f); -} - -float mx_worley_distance(vec2 p, int x, int y, int xoff, int yoff, float jitter, int metric) -{ - vec3 tmp = mx_cell_noise_vec3(vec2(x+xoff, y+yoff)); - vec2 off = vec2(tmp.x, tmp.y); - - off -= 0.5f; - off *= jitter; - off += 0.5f; - - vec2 cellpos = vec2(float(x), float(y)) + off; - vec2 diff = cellpos - p; - if (metric == 2) - return abs(diff.x) + abs(diff.y); // Manhattan distance - if (metric == 3) - return max(abs(diff.x), abs(diff.y)); // Chebyshev distance - // Either Euclidian or Distance^2 - return dot(diff, diff); -} - -float mx_worley_distance(vec3 p, int x, int y, int z, int xoff, int yoff, int zoff, float jitter, int metric) -{ - vec3 off = mx_cell_noise_vec3(vec3(x+xoff, y+yoff, z+zoff)); - - off -= 0.5f; - off *= jitter; - off += 0.5f; - - vec3 cellpos = vec3(float(x), float(y), float(z)) + off; - vec3 diff = cellpos - p; - if (metric == 2) - return abs(diff.x) + abs(diff.y) + abs(diff.z); // Manhattan distance - if (metric == 3) - return max(max(abs(diff.x), abs(diff.y)), abs(diff.z)); // Chebyshev distance - // Either Euclidian or Distance^2 - return dot(diff, diff); -} - -float mx_worley_noise_float(vec2 p, float jitter, int metric) -{ - int X, Y; - vec2 localpos = vec2(mx_floorfrac(p.x, X), mx_floorfrac(p.y, Y)); - float sqdist = 1e6f; // Some big number for jitter > 1 (not all GPUs may be IEEE) - for (int x = -1; x <= 1; ++x) - { - for (int y = -1; y <= 1; ++y) - { - float dist = mx_worley_distance(localpos, x, y, X, Y, jitter, metric); - sqdist = min(sqdist, dist); - } - } - if (metric == 0) - sqdist = sqrt(sqdist); - return sqdist; -} - -vec2 mx_worley_noise_vec2(vec2 p, float jitter, int metric) -{ - int X, Y; - vec2 localpos = vec2(mx_floorfrac(p.x, X), mx_floorfrac(p.y, Y)); - vec2 sqdist = vec2(1e6f, 1e6f); - for (int x = -1; x <= 1; ++x) - { - for (int y = -1; y <= 1; ++y) - { - float dist = mx_worley_distance(localpos, x, y, X, Y, jitter, metric); - if (dist < sqdist.x) - { - sqdist.y = sqdist.x; - sqdist.x = dist; - } - else if (dist < sqdist.y) - { - sqdist.y = dist; - } - } - } - if (metric == 0) - sqdist = sqrt(sqdist); - return sqdist; -} - -vec3 mx_worley_noise_vec3(vec2 p, float jitter, int metric) -{ - int X, Y; - vec2 localpos = vec2(mx_floorfrac(p.x, X), mx_floorfrac(p.y, Y)); - vec3 sqdist = vec3(1e6f, 1e6f, 1e6f); - for (int x = -1; x <= 1; ++x) - { - for (int y = -1; y <= 1; ++y) - { - float dist = mx_worley_distance(localpos, x, y, X, Y, jitter, metric); - if (dist < sqdist.x) - { - sqdist.z = sqdist.y; - sqdist.y = sqdist.x; - sqdist.x = dist; - } - else if (dist < sqdist.y) - { - sqdist.z = sqdist.y; - sqdist.y = dist; - } - else if (dist < sqdist.z) - { - sqdist.z = dist; - } - } - } - if (metric == 0) - sqdist = sqrt(sqdist); - return sqdist; -} - -float mx_worley_noise_float(vec3 p, float jitter, int metric) -{ - int X, Y, Z; - vec3 localpos = vec3(mx_floorfrac(p.x, X), mx_floorfrac(p.y, Y), mx_floorfrac(p.z, Z)); - float sqdist = 1e6f; - for (int x = -1; x <= 1; ++x) - { - for (int y = -1; y <= 1; ++y) - { - for (int z = -1; z <= 1; ++z) - { - float dist = mx_worley_distance(localpos, x, y, z, X, Y, Z, jitter, metric); - sqdist = min(sqdist, dist); - } - } - } - if (metric == 0) - sqdist = sqrt(sqdist); - return sqdist; -} - -vec2 mx_worley_noise_vec2(vec3 p, float jitter, int metric) -{ - int X, Y, Z; - vec3 localpos = vec3(mx_floorfrac(p.x, X), mx_floorfrac(p.y, Y), mx_floorfrac(p.z, Z)); - vec2 sqdist = vec2(1e6f, 1e6f); - for (int x = -1; x <= 1; ++x) - { - for (int y = -1; y <= 1; ++y) - { - for (int z = -1; z <= 1; ++z) - { - float dist = mx_worley_distance(localpos, x, y, z, X, Y, Z, jitter, metric); - if (dist < sqdist.x) - { - sqdist.y = sqdist.x; - sqdist.x = dist; - } - else if (dist < sqdist.y) - { - sqdist.y = dist; - } - } - } - } - if (metric == 0) - sqdist = sqrt(sqdist); - return sqdist; -} - -vec3 mx_worley_noise_vec3(vec3 p, float jitter, int metric) -{ - int X, Y, Z; - vec3 localpos = vec3(mx_floorfrac(p.x, X), mx_floorfrac(p.y, Y), mx_floorfrac(p.z, Z)); - vec3 sqdist = vec3(1e6f, 1e6f, 1e6f); - for (int x = -1; x <= 1; ++x) - { - for (int y = -1; y <= 1; ++y) - { - for (int z = -1; z <= 1; ++z) - { - float dist = mx_worley_distance(localpos, x, y, z, X, Y, Z, jitter, metric); - if (dist < sqdist.x) - { - sqdist.z = sqdist.y; - sqdist.y = sqdist.x; - sqdist.x = dist; - } - else if (dist < sqdist.y) - { - sqdist.z = sqdist.y; - sqdist.y = dist; - } - else if (dist < sqdist.z) - { - sqdist.z = dist; - } - } - } - } - if (metric == 0) - sqdist = sqrt(sqdist); - return sqdist; -}` ); - -const includes = [ mx_noise ]; - -export const mx_perlin_noise_float = fn( 'float mx_perlin_noise_float( any p )', includes ); -export const mx_perlin_noise_vec2 = fn( 'vec2 mx_perlin_noise_vec2( any p )', includes ); -export const mx_perlin_noise_vec3 = fn( 'vec3 mx_perlin_noise_vec3( any p )', includes ); - -export const mx_cell_noise_float = fn( 'float mx_cell_noise_float( vec3 p )', includes ); - -export const mx_worley_noise_float = fn( 'float mx_worley_noise_float( any p, float jitter, int metric )', includes ); -export const mx_worley_noise_vec2 = fn( 'float mx_worley_noise_vec2( any p, float jitter, int metric )', includes ); -export const mx_worley_noise_vec3 = fn( 'float mx_worley_noise_vec3( any p, float jitter, int metric )', includes ); - -export const mx_fractal_noise_float = fn( 'float mx_fractal_noise_float( vec3 p, int octaves, float lacunarity, float diminish )', includes ); -export const mx_fractal_noise_vec2 = fn( 'float mx_fractal_noise_vec2( vec3 p, int octaves, float lacunarity, float diminish )', includes ); -export const mx_fractal_noise_vec3 = fn( 'float mx_fractal_noise_vec3( vec3 p, int octaves, float lacunarity, float diminish )', includes ); -export const mx_fractal_noise_vec4 = fn( 'float mx_fractal_noise_vec4( vec3 p, int octaves, float lacunarity, float diminish )', includes ); diff --git a/examples/jsm/nodes/materialx/lib/mx_transform_color.js b/examples/jsm/nodes/materialx/lib/mx_transform_color.js deleted file mode 100644 index c99fa146e5c2e3..00000000000000 --- a/examples/jsm/nodes/materialx/lib/mx_transform_color.js +++ /dev/null @@ -1,19 +0,0 @@ -import { code } from '../../code/CodeNode.js'; -import { fn } from '../../code/FunctionNode.js'; - -// Original shader code from: -// https://github.com/AcademySoftwareFoundation/MaterialX/blob/main/libraries/stdlib/genglsl/lib/mx_transform_color.glsl - -export const mx_transform_color = code( `#define M_AP1_TO_REC709 mat3(1.705079555511475, -0.1297005265951157, -0.02416634373366833, -0.6242334842681885, 1.138468623161316, -0.1246141716837883, -0.0808461606502533, -0.008768022060394287, 1.148780584335327) - -vec3 mx_srgb_texture_to_lin_rec709(vec3 color) -{ - bvec3 isAbove = greaterThan(color, vec3(0.04045)); - vec3 linSeg = color / 12.92; - vec3 powSeg = pow(max(color + vec3(0.055), vec3(0.0)) / 1.055, vec3(2.4)); - return mix(linSeg, powSeg, isAbove); -}` ); - -const includes = [ mx_transform_color ]; - -export const mx_srgb_texture_to_lin_rec709 = fn( 'vec3 mx_srgb_texture_to_lin_rec709( vec3 color )', includes ); diff --git a/examples/jsm/nodes/math/CondNode.js b/examples/jsm/nodes/math/CondNode.js deleted file mode 100644 index 142d17e76f768d..00000000000000 --- a/examples/jsm/nodes/math/CondNode.js +++ /dev/null @@ -1,86 +0,0 @@ -import Node, { addNodeClass } from '../core/Node.js'; -import { property } from '../core/PropertyNode.js'; -import { context as contextNode } from '../core/ContextNode.js'; -import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js'; - -class CondNode extends Node { - - constructor( condNode, ifNode, elseNode = null ) { - - super(); - - this.condNode = condNode; - - this.ifNode = ifNode; - this.elseNode = elseNode; - - } - - getNodeType( builder ) { - - const ifType = this.ifNode.getNodeType( builder ); - - if ( this.elseNode !== null ) { - - const elseType = this.elseNode.getNodeType( builder ); - - if ( builder.getTypeLength( elseType ) > builder.getTypeLength( ifType ) ) { - - return elseType; - - } - - } - - return ifType; - - } - - generate( builder ) { - - const type = this.getNodeType( builder ); - const context = { tempWrite: false }; - - const { ifNode, elseNode } = this; - - const needsProperty = ifNode.getNodeType( builder ) !== 'void' || ( elseNode && elseNode.getNodeType( builder ) !== 'void' ); - const nodeProperty = needsProperty ? property( type ).build( builder ) : ''; - - const nodeSnippet = contextNode( this.condNode/*, context*/ ).build( builder, 'bool' ); - - builder.addFlowCode( `\n${ builder.tab }if ( ${ nodeSnippet } ) {\n\n` ).addFlowTab(); - - let ifSnippet = contextNode( this.ifNode, context ).build( builder, type ); - - ifSnippet = needsProperty ? nodeProperty + ' = ' + ifSnippet + ';' : ifSnippet; - - builder.removeFlowTab().addFlowCode( builder.tab + '\t' + ifSnippet + '\n\n' + builder.tab + '}' ); - - if ( elseNode !== null ) { - - builder.addFlowCode( ' else {\n\n' ).addFlowTab(); - - let elseSnippet = contextNode( elseNode, context ).build( builder, type ); - elseSnippet = nodeProperty ? nodeProperty + ' = ' + elseSnippet + ';' : elseSnippet; - - builder.removeFlowTab().addFlowCode( builder.tab + '\t' + elseSnippet + '\n\n' + builder.tab + '}\n\n' ); - - } else { - - builder.addFlowCode( '\n\n' ); - - } - - return nodeProperty; - - } - -} - -export default CondNode; - -export const cond = nodeProxy( CondNode ); - -addNodeElement( 'cond', cond ); - -addNodeClass( CondNode ); diff --git a/examples/jsm/nodes/math/MathNode.js b/examples/jsm/nodes/math/MathNode.js deleted file mode 100644 index 32948146ae4e48..00000000000000 --- a/examples/jsm/nodes/math/MathNode.js +++ /dev/null @@ -1,353 +0,0 @@ -import TempNode from '../core/TempNode.js'; -import { sub, mul, div } from './OperatorNode.js'; -import { addNodeClass } from '../core/Node.js'; -import { addNodeElement, nodeObject, nodeProxy, float, vec3, vec4 } from '../shadernode/ShaderNode.js'; - -class MathNode extends TempNode { - - constructor( method, aNode, bNode = null, cNode = null ) { - - super(); - - this.method = method; - - this.aNode = aNode; - this.bNode = bNode; - this.cNode = cNode; - - } - - getInputType( builder ) { - - const aType = this.aNode.getNodeType( builder ); - const bType = this.bNode ? this.bNode.getNodeType( builder ) : null; - const cType = this.cNode ? this.cNode.getNodeType( builder ) : null; - - const aLen = builder.isMatrix( aType ) ? 0 : builder.getTypeLength( aType ); - const bLen = builder.isMatrix( bType ) ? 0 : builder.getTypeLength( bType ); - const cLen = builder.isMatrix( cType ) ? 0 : builder.getTypeLength( cType ); - - if ( aLen > bLen && aLen > cLen ) { - - return aType; - - } else if ( bLen > cLen ) { - - return bType; - - } else if ( cLen > aLen ) { - - return cType; - - } - - return aType; - - } - - getNodeType( builder ) { - - const method = this.method; - - if ( method === MathNode.LENGTH || method === MathNode.DISTANCE || method === MathNode.DOT ) { - - return 'float'; - - } else if ( method === MathNode.CROSS ) { - - return 'vec3'; - - } else { - - return this.getInputType( builder ); - - } - - } - - generate( builder, output ) { - - const method = this.method; - - const type = this.getNodeType( builder ); - const inputType = this.getInputType( builder ); - - const a = this.aNode; - const b = this.bNode; - const c = this.cNode; - - const isWebGL = builder.renderer.isWebGLRenderer === true; - - if ( method === MathNode.TRANSFORM_DIRECTION ) { - - // dir can be either a direction vector or a normal vector - // upper-left 3x3 of matrix is assumed to be orthogonal - - let tA = a; - let tB = b; - - if ( builder.isMatrix( tA.getNodeType( builder ) ) ) { - - tB = vec4( vec3( tB ), 0.0 ); - - } else { - - tA = vec4( vec3( tA ), 0.0 ); - - } - - const mulNode = mul( tA, tB ).xyz; - - return normalize( mulNode ).build( builder, output ); - - } else if ( method === MathNode.NEGATE ) { - - return builder.format( '-' + a.build( builder, inputType ), type, output ); - - } else if ( method === MathNode.ONE_MINUS ) { - - return sub( 1.0, a ).build( builder, output ); - - } else if ( method === MathNode.RECIPROCAL ) { - - return div( 1.0, a ).build( builder, output ); - - } else if ( method === MathNode.DIFFERENCE ) { - - return abs( sub( a, b ) ).build( builder, output ); - - } else { - - const params = []; - - if ( method === MathNode.CROSS ) { - - params.push( - a.build( builder, type ), - b.build( builder, type ) - ); - - } else if ( method === MathNode.STEP ) { - - params.push( - a.build( builder, builder.getTypeLength( a.getNodeType( builder ) ) === 1 ? 'float' : inputType ), - b.build( builder, inputType ) - ); - - } else if ( ( isWebGL && ( method === MathNode.MIN || method === MathNode.MAX ) ) || method === MathNode.MOD ) { - - params.push( - a.build( builder, inputType ), - b.build( builder, builder.getTypeLength( b.getNodeType( builder ) ) === 1 ? 'float' : inputType ) - ); - - } else if ( method === MathNode.REFRACT ) { - - params.push( - a.build( builder, inputType ), - b.build( builder, inputType ), - c.build( builder, 'float' ) - ); - - } else if ( method === MathNode.MIX ) { - - params.push( - a.build( builder, inputType ), - b.build( builder, inputType ), - c.build( builder, builder.getTypeLength( c.getNodeType( builder ) ) === 1 ? 'float' : inputType ) - ); - - } else { - - params.push( a.build( builder, inputType ) ); - if ( b !== null ) params.push( b.build( builder, inputType ) ); - if ( c !== null ) params.push( c.build( builder, inputType ) ); - - } - - return builder.format( `${ builder.getMethod( method ) }( ${params.join( ', ' )} )`, type, output ); - - } - - } - - serialize( data ) { - - super.serialize( data ); - - data.method = this.method; - - } - - deserialize( data ) { - - super.deserialize( data ); - - this.method = data.method; - - } - -} - -// 1 input - -MathNode.RADIANS = 'radians'; -MathNode.DEGREES = 'degrees'; -MathNode.EXP = 'exp'; -MathNode.EXP2 = 'exp2'; -MathNode.LOG = 'log'; -MathNode.LOG2 = 'log2'; -MathNode.SQRT = 'sqrt'; -MathNode.INVERSE_SQRT = 'inversesqrt'; -MathNode.FLOOR = 'floor'; -MathNode.CEIL = 'ceil'; -MathNode.NORMALIZE = 'normalize'; -MathNode.FRACT = 'fract'; -MathNode.SIN = 'sin'; -MathNode.COS = 'cos'; -MathNode.TAN = 'tan'; -MathNode.ASIN = 'asin'; -MathNode.ACOS = 'acos'; -MathNode.ATAN = 'atan'; -MathNode.ABS = 'abs'; -MathNode.SIGN = 'sign'; -MathNode.LENGTH = 'length'; -MathNode.NEGATE = 'negate'; -MathNode.ONE_MINUS = 'oneMinus'; -MathNode.DFDX = 'dFdx'; -MathNode.DFDY = 'dFdy'; -MathNode.ROUND = 'round'; -MathNode.RECIPROCAL = 'reciprocal'; - -// 2 inputs - -MathNode.ATAN2 = 'atan2'; -MathNode.MIN = 'min'; -MathNode.MAX = 'max'; -MathNode.MOD = 'mod'; -MathNode.STEP = 'step'; -MathNode.REFLECT = 'reflect'; -MathNode.DISTANCE = 'distance'; -MathNode.DIFFERENCE = 'difference'; -MathNode.DOT = 'dot'; -MathNode.CROSS = 'cross'; -MathNode.POW = 'pow'; -MathNode.TRANSFORM_DIRECTION = 'transformDirection'; - -// 3 inputs - -MathNode.MIX = 'mix'; -MathNode.CLAMP = 'clamp'; -MathNode.REFRACT = 'refract'; -MathNode.SMOOTHSTEP = 'smoothstep'; -MathNode.FACEFORWARD = 'faceforward'; - -export default MathNode; - -export const EPSILON = float( 1e-6 ); -export const INFINITY = float( 1e6 ); - -export const radians = nodeProxy( MathNode, MathNode.RADIANS ); -export const degrees = nodeProxy( MathNode, MathNode.DEGREES ); -export const exp = nodeProxy( MathNode, MathNode.EXP ); -export const exp2 = nodeProxy( MathNode, MathNode.EXP2 ); -export const log = nodeProxy( MathNode, MathNode.LOG ); -export const log2 = nodeProxy( MathNode, MathNode.LOG2 ); -export const sqrt = nodeProxy( MathNode, MathNode.SQRT ); -export const inverseSqrt = nodeProxy( MathNode, MathNode.INVERSE_SQRT ); -export const floor = nodeProxy( MathNode, MathNode.FLOOR ); -export const ceil = nodeProxy( MathNode, MathNode.CEIL ); -export const normalize = nodeProxy( MathNode, MathNode.NORMALIZE ); -export const fract = nodeProxy( MathNode, MathNode.FRACT ); -export const sin = nodeProxy( MathNode, MathNode.SIN ); -export const cos = nodeProxy( MathNode, MathNode.COS ); -export const tan = nodeProxy( MathNode, MathNode.TAN ); -export const asin = nodeProxy( MathNode, MathNode.ASIN ); -export const acos = nodeProxy( MathNode, MathNode.ACOS ); -export const atan = nodeProxy( MathNode, MathNode.ATAN ); -export const abs = nodeProxy( MathNode, MathNode.ABS ); -export const sign = nodeProxy( MathNode, MathNode.SIGN ); -export const length = nodeProxy( MathNode, MathNode.LENGTH ); -export const negate = nodeProxy( MathNode, MathNode.NEGATE ); -export const oneMinus = nodeProxy( MathNode, MathNode.ONE_MINUS ); -export const dFdx = nodeProxy( MathNode, MathNode.DFDX ); -export const dFdy = nodeProxy( MathNode, MathNode.DFDY ); -export const round = nodeProxy( MathNode, MathNode.ROUND ); -export const reciprocal = nodeProxy( MathNode, MathNode.RECIPROCAL ); - -export const atan2 = nodeProxy( MathNode, MathNode.ATAN2 ); -export const min = nodeProxy( MathNode, MathNode.MIN ); -export const max = nodeProxy( MathNode, MathNode.MAX ); -export const mod = nodeProxy( MathNode, MathNode.MOD ); -export const step = nodeProxy( MathNode, MathNode.STEP ); -export const reflect = nodeProxy( MathNode, MathNode.REFLECT ); -export const distance = nodeProxy( MathNode, MathNode.DISTANCE ); -export const difference = nodeProxy( MathNode, MathNode.DIFFERENCE ); -export const dot = nodeProxy( MathNode, MathNode.DOT ); -export const cross = nodeProxy( MathNode, MathNode.CROSS ); -export const pow = nodeProxy( MathNode, MathNode.POW ); -export const pow2 = nodeProxy( MathNode, MathNode.POW, 2 ); -export const pow3 = nodeProxy( MathNode, MathNode.POW, 3 ); -export const pow4 = nodeProxy( MathNode, MathNode.POW, 4 ); -export const transformDirection = nodeProxy( MathNode, MathNode.TRANSFORM_DIRECTION ); - -export const mix = nodeProxy( MathNode, MathNode.MIX ); -export const clamp = ( value, low = 0, high = 1 ) => nodeObject( new MathNode( MathNode.CLAMP, nodeObject( value ), nodeObject( low ), nodeObject( high ) ) ); -export const saturate = ( value ) => clamp( value ); -export const refract = nodeProxy( MathNode, MathNode.REFRACT ); -export const smoothstep = nodeProxy( MathNode, MathNode.SMOOTHSTEP ); -export const faceForward = nodeProxy( MathNode, MathNode.FACEFORWARD ); - -export const mixElement = ( t, e1, e2 ) => mix( e1, e2, t ); -export const smoothstepElement = ( x, low, high ) => smoothstep( low, high, x ); - -addNodeElement( 'radians', radians ); -addNodeElement( 'degrees', degrees ); -addNodeElement( 'exp', exp ); -addNodeElement( 'exp2', exp2 ); -addNodeElement( 'log', log ); -addNodeElement( 'log2', log2 ); -addNodeElement( 'sqrt', sqrt ); -addNodeElement( 'inverseSqrt', inverseSqrt ); -addNodeElement( 'floor', floor ); -addNodeElement( 'ceil', ceil ); -addNodeElement( 'normalize', normalize ); -addNodeElement( 'fract', fract ); -addNodeElement( 'sin', sin ); -addNodeElement( 'cos', cos ); -addNodeElement( 'tan', tan ); -addNodeElement( 'asin', asin ); -addNodeElement( 'acos', acos ); -addNodeElement( 'atan', atan ); -addNodeElement( 'abs', abs ); -addNodeElement( 'sign', sign ); -addNodeElement( 'length', length ); -addNodeElement( 'negate', negate ); -addNodeElement( 'oneMinus', oneMinus ); -addNodeElement( 'dFdx', dFdx ); -addNodeElement( 'dFdy', dFdy ); -addNodeElement( 'round', round ); -addNodeElement( 'reciprocal', reciprocal ); -addNodeElement( 'atan2', atan2 ); -addNodeElement( 'min', min ); -addNodeElement( 'max', max ); -addNodeElement( 'mod', mod ); -addNodeElement( 'step', step ); -addNodeElement( 'reflect', reflect ); -addNodeElement( 'distance', distance ); -addNodeElement( 'dot', dot ); -addNodeElement( 'cross', cross ); -addNodeElement( 'pow', pow ); -addNodeElement( 'pow2', pow2 ); -addNodeElement( 'pow3', pow3 ); -addNodeElement( 'pow4', pow4 ); -addNodeElement( 'transformDirection', transformDirection ); -addNodeElement( 'mix', mixElement ); -addNodeElement( 'clamp', clamp ); -addNodeElement( 'refract', refract ); -addNodeElement( 'smoothstep', smoothstepElement ); -addNodeElement( 'faceForward', faceForward ); -addNodeElement( 'difference', difference ); -addNodeElement( 'saturate', saturate ); - -addNodeClass( MathNode ); diff --git a/examples/jsm/nodes/math/OperatorNode.js b/examples/jsm/nodes/math/OperatorNode.js deleted file mode 100644 index 6a8ae5f3d47787..00000000000000 --- a/examples/jsm/nodes/math/OperatorNode.js +++ /dev/null @@ -1,269 +0,0 @@ -import TempNode from '../core/TempNode.js'; -import { addNodeClass } from '../core/Node.js'; -import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js'; - -class OperatorNode extends TempNode { - - constructor( op, aNode, bNode, ...params ) { - - super(); - - this.op = op; - - if ( params.length > 0 ) { - - let finalBNode = bNode; - - for ( let i = 0; i < params.length; i ++ ) { - - finalBNode = new OperatorNode( op, finalBNode, params[ i ] ); - - } - - bNode = finalBNode; - - } - - this.aNode = aNode; - this.bNode = bNode; - - } - - hasDependencies( builder ) { - - return this.op !== '=' ? super.hasDependencies( builder ) : false; - - } - - getNodeType( builder, output ) { - - const op = this.op; - - const aNode = this.aNode; - const bNode = this.bNode; - - const typeA = aNode.getNodeType( builder ); - const typeB = bNode.getNodeType( builder ); - - if ( typeA === 'void' || typeB === 'void' ) { - - return 'void'; - - } else if ( op === '=' || op === '%' ) { - - return typeA; - - } else if ( op === '&' || op === '|' || op === '^' || op === '>>' || op === '<<' ) { - - return builder.getIntegerType( typeA ); - - } else if ( op === '==' || op === '&&' || op === '||' || op === '^^' ) { - - return 'bool'; - - } else if ( op === '<' || op === '>' || op === '<=' || op === '>=' ) { - - const typeLength = output ? builder.getTypeLength( output ) : Math.max( builder.getTypeLength( typeA ), builder.getTypeLength( typeB ) ); - - return typeLength > 1 ? `bvec${ typeLength }` : 'bool'; - - } else { - - if ( typeA === 'float' && builder.isMatrix( typeB ) ) { - - return typeB; - - } else if ( builder.isMatrix( typeA ) && builder.isVector( typeB ) ) { - - // matrix x vector - - return builder.getVectorFromMatrix( typeA ); - - } else if ( builder.isVector( typeA ) && builder.isMatrix( typeB ) ) { - - // vector x matrix - - return builder.getVectorFromMatrix( typeB ); - - } else if ( builder.getTypeLength( typeB ) > builder.getTypeLength( typeA ) ) { - - // anytype x anytype: use the greater length vector - - return typeB; - - } - - return typeA; - - } - - } - - generate( builder, output ) { - - const op = this.op; - - const aNode = this.aNode; - const bNode = this.bNode; - - const type = this.getNodeType( builder, output ); - - let typeA = null; - let typeB = null; - - if ( type !== 'void' ) { - - typeA = aNode.getNodeType( builder ); - typeB = bNode.getNodeType( builder ); - - if ( op === '=' ) { - - typeB = typeA; - - } else if ( op === '<' || op === '>' || op === '<=' || op === '>=' ) { - - if ( builder.isVector( typeA ) ) { - - typeB = typeA; - - } else { - - typeA = typeB = 'float'; - - } - - } else if ( op === '>>' || op === '<<' ) { - - typeA = type; - typeB = builder.changeComponentType( typeB, 'uint' ); - - } else if ( builder.isMatrix( typeA ) && builder.isVector( typeB ) ) { - - // matrix x vector - - typeB = builder.getVectorFromMatrix( typeA ); - - } else if ( builder.isVector( typeA ) && builder.isMatrix( typeB ) ) { - - // vector x matrix - - typeA = builder.getVectorFromMatrix( typeB ); - - } else { - - // anytype x anytype - - typeA = typeB = type; - - } - - } else { - - typeA = typeB = type; - - } - - const a = aNode.build( builder, typeA ); - const b = bNode.build( builder, typeB ); - - const outputLength = builder.getTypeLength( output ); - - if ( output !== 'void' ) { - - if ( op === '=' ) { - - builder.addLineFlowCode( `${a} ${this.op} ${b}` ); - - return a; - - } else if ( op === '<' && outputLength > 1 ) { - - return builder.format( `${ builder.getMethod( 'lessThan' ) }( ${a}, ${b} )`, type, output ); - - } else if ( op === '<=' && outputLength > 1 ) { - - return builder.format( `${ builder.getMethod( 'lessThanEqual' ) }( ${a}, ${b} )`, type, output ); - - } else if ( op === '>' && outputLength > 1 ) { - - return builder.format( `${ builder.getMethod( 'greaterThan' ) }( ${a}, ${b} )`, type, output ); - - } else if ( op === '>=' && outputLength > 1 ) { - - return builder.format( `${ builder.getMethod( 'greaterThanEqual' ) }( ${a}, ${b} )`, type, output ); - - } else { - - return builder.format( `( ${a} ${this.op} ${b} )`, type, output ); - - } - - } else if ( typeA !== 'void' ) { - - return builder.format( `${a} ${this.op} ${b}`, type, output ); - - } - - } - - serialize( data ) { - - super.serialize( data ); - - data.op = this.op; - - } - - deserialize( data ) { - - super.deserialize( data ); - - this.op = data.op; - - } - -} - -export default OperatorNode; - -export const add = nodeProxy( OperatorNode, '+' ); -export const sub = nodeProxy( OperatorNode, '-' ); -export const mul = nodeProxy( OperatorNode, '*' ); -export const div = nodeProxy( OperatorNode, '/' ); -export const remainder = nodeProxy( OperatorNode, '%' ); -export const equal = nodeProxy( OperatorNode, '==' ); -export const assign = nodeProxy( OperatorNode, '=' ); -export const lessThan = nodeProxy( OperatorNode, '<' ); -export const greaterThan = nodeProxy( OperatorNode, '>' ); -export const lessThanEqual = nodeProxy( OperatorNode, '<=' ); -export const greaterThanEqual = nodeProxy( OperatorNode, '>=' ); -export const and = nodeProxy( OperatorNode, '&&' ); -export const or = nodeProxy( OperatorNode, '||' ); -export const xor = nodeProxy( OperatorNode, '^^' ); -export const bitAnd = nodeProxy( OperatorNode, '&' ); -export const bitOr = nodeProxy( OperatorNode, '|' ); -export const bitXor = nodeProxy( OperatorNode, '^' ); -export const shiftLeft = nodeProxy( OperatorNode, '<<' ); -export const shiftRight = nodeProxy( OperatorNode, '>>' ); - -addNodeElement( 'add', add ); -addNodeElement( 'sub', sub ); -addNodeElement( 'mul', mul ); -addNodeElement( 'div', div ); -addNodeElement( 'remainder', remainder ); -addNodeElement( 'equal', equal ); -addNodeElement( 'assign', assign ); -addNodeElement( 'lessThan', lessThan ); -addNodeElement( 'greaterThan', greaterThan ); -addNodeElement( 'lessThanEqual', lessThanEqual ); -addNodeElement( 'greaterThanEqual', greaterThanEqual ); -addNodeElement( 'and', and ); -addNodeElement( 'or', or ); -addNodeElement( 'xor', xor ); -addNodeElement( 'bitAnd', bitAnd ); -addNodeElement( 'bitOr', bitOr ); -addNodeElement( 'bitXor', bitXor ); -addNodeElement( 'shiftLeft', shiftLeft ); -addNodeElement( 'shiftRight', shiftRight ); - -addNodeClass( OperatorNode ); diff --git a/examples/jsm/nodes/parsers/GLSLNodeParser.js b/examples/jsm/nodes/parsers/GLSLNodeParser.js deleted file mode 100644 index 3ae20b3b0e13e5..00000000000000 --- a/examples/jsm/nodes/parsers/GLSLNodeParser.js +++ /dev/null @@ -1,14 +0,0 @@ -import NodeParser from '../core/NodeParser.js'; -import GLSLNodeFunction from './GLSLNodeFunction.js'; - -class GLSLNodeParser extends NodeParser { - - parseFunction( source ) { - - return new GLSLNodeFunction( source ); - - } - -} - -export default GLSLNodeParser; diff --git a/examples/jsm/nodes/parsers/WGSLNodeFunction.js b/examples/jsm/nodes/parsers/WGSLNodeFunction.js deleted file mode 100644 index 0119477e37ef70..00000000000000 --- a/examples/jsm/nodes/parsers/WGSLNodeFunction.js +++ /dev/null @@ -1,104 +0,0 @@ -import NodeFunction from '../core/NodeFunction.js'; -import NodeFunctionInput from '../core/NodeFunctionInput.js'; - -const declarationRegexp = /^[fn]*\s*([a-z_0-9]+)?\s*\(([\s\S]*?)\)\s*[\-\>]*\s*([a-z_0-9]+)?/i; -const propertiesRegexp = /[a-z_0-9]+/ig; - -const wgslTypeLib = { - f32: 'float' -}; - -const parse = ( source ) => { - - source = source.trim(); - - const declaration = source.match( declarationRegexp ); - - if ( declaration !== null && declaration.length === 4 ) { - - // tokenizer - - const inputsCode = declaration[ 2 ]; - const propsMatches = []; - - let nameMatch = null; - - while ( ( nameMatch = propertiesRegexp.exec( inputsCode ) ) !== null ) { - - propsMatches.push( nameMatch ); - - } - - // parser - - const inputs = []; - - let i = 0; - - while ( i < propsMatches.length ) { - - // default - - const name = propsMatches[ i ++ ][ 0 ]; - let type = propsMatches[ i ++ ][ 0 ]; - - type = wgslTypeLib[ type ] || type; - - // precision - - if ( i < propsMatches.length && /^[fui]\d{2}$/.test( propsMatches[ i ][ 0 ] ) === true ) - i ++; - - // add input - - inputs.push( new NodeFunctionInput( type, name ) ); - - } - - // - - const blockCode = source.substring( declaration[ 0 ].length ); - - const name = declaration[ 1 ] !== undefined ? declaration[ 1 ] : ''; - const type = declaration[ 3 ] || 'void'; - - return { - type, - inputs, - name, - inputsCode, - blockCode - }; - - } else { - - throw new Error( 'FunctionNode: Function is not a WGSL code.' ); - - } - -}; - -class WGSLNodeFunction extends NodeFunction { - - constructor( source ) { - - const { type, inputs, name, inputsCode, blockCode } = parse( source ); - - super( type, inputs, name ); - - this.inputsCode = inputsCode; - this.blockCode = blockCode; - - } - - getCode( name = this.name ) { - - const type = this.type !== 'void' ? '-> ' + this.type : ''; - - return `fn ${ name } ( ${ this.inputsCode.trim() } ) ${ type }` + this.blockCode; - - } - -} - -export default WGSLNodeFunction; diff --git a/examples/jsm/nodes/parsers/WGSLNodeParser.js b/examples/jsm/nodes/parsers/WGSLNodeParser.js deleted file mode 100644 index 4f62a3bc0bcc31..00000000000000 --- a/examples/jsm/nodes/parsers/WGSLNodeParser.js +++ /dev/null @@ -1,14 +0,0 @@ -import NodeParser from '../core/NodeParser.js'; -import WGSLNodeFunction from './WGSLNodeFunction.js'; - -class WGSLNodeParser extends NodeParser { - - parseFunction( source ) { - - return new WGSLNodeFunction( source ); - - } - -} - -export default WGSLNodeParser; diff --git a/examples/jsm/nodes/procedural/CheckerNode.js b/examples/jsm/nodes/procedural/CheckerNode.js deleted file mode 100644 index b4816a8053c2a1..00000000000000 --- a/examples/jsm/nodes/procedural/CheckerNode.js +++ /dev/null @@ -1,42 +0,0 @@ -import TempNode from '../core/TempNode.js'; -import { uv } from '../accessors/UVNode.js'; -import { addNodeClass } from '../core/Node.js'; -import { addNodeElement, ShaderNode, nodeProxy } from '../shadernode/ShaderNode.js'; - -const checkerShaderNode = new ShaderNode( ( inputs ) => { - - const uv = inputs.uv.mul( 2.0 ); - - const cx = uv.x.floor(); - const cy = uv.y.floor(); - const result = cx.add( cy ).mod( 2.0 ); - - return result.sign(); - -} ); - -class CheckerNode extends TempNode { - - constructor( uvNode = uv() ) { - - super( 'float' ); - - this.uvNode = uvNode; - - } - - generate( builder ) { - - return checkerShaderNode.call( { uv: this.uvNode } ).build( builder ); - - } - -} - -export default CheckerNode; - -export const checker = nodeProxy( CheckerNode ); - -addNodeElement( 'checker', checker ); - -addNodeClass( CheckerNode ); diff --git a/examples/jsm/nodes/shadernode/ShaderNode.js b/examples/jsm/nodes/shadernode/ShaderNode.js deleted file mode 100644 index cbb44b3c92670a..00000000000000 --- a/examples/jsm/nodes/shadernode/ShaderNode.js +++ /dev/null @@ -1,400 +0,0 @@ -import Node, { addNodeClass } from '../core/Node.js'; -import ArrayElementNode from '../utils/ArrayElementNode.js'; -import ConvertNode from '../utils/ConvertNode.js'; -import JoinNode from '../utils/JoinNode.js'; -import SplitNode from '../utils/SplitNode.js'; -import ConstNode from '../core/ConstNode.js'; -import { getValueFromType, getValueType } from '../core/NodeUtils.js'; - -const NodeElements = new Map(); // @TODO: Currently only a few nodes are added, probably also add others - -export function addNodeElement( name, nodeElement ) { - - if ( NodeElements.has( name ) ) throw new Error( `Redefinition of node element ${ name }` ); - if ( typeof nodeElement !== 'function' ) throw new Error( `Node element ${ name } is not a function` ); - - NodeElements.set( name, nodeElement ); - -} - -const shaderNodeHandler = { - - construct( NodeClosure, params ) { - - const inputs = params.shift(); - - return NodeClosure( nodeObjects( inputs ), ...params ); - - }, - - get: function ( node, prop, nodeObj ) { - - if ( typeof prop === 'string' && node[ prop ] === undefined ) { - - if ( NodeElements.has( prop ) ) { - - const nodeElement = NodeElements.get( prop ); - - return ( ...params ) => nodeElement( nodeObj, ...params ); - - } else if ( prop.endsWith( 'Assign' ) && NodeElements.has( prop.slice( 0, prop.length - 'Assign'.length ) ) ) { - - const nodeElement = NodeElements.get( prop.slice( 0, prop.length - 'Assign'.length ) ); - - return ( ...params ) => nodeObj.assign( nodeElement( nodeObj, ...params ) ); - - } else if ( /^[xyzwrgbastpq]{1,4}$/.test( prop ) === true ) { - - // accessing properties ( swizzle ) - - prop = prop - .replace( /r|s/g, 'x' ) - .replace( /g|t/g, 'y' ) - .replace( /b|p/g, 'z' ) - .replace( /a|q/g, 'w' ); - - return nodeObject( new SplitNode( node, prop ) ); - - } else if ( prop === 'width' || prop === 'height' ) { - - // accessing property - - return nodeObject( new SplitNode( node, prop === 'width' ? 'x' : 'y' ) ); - - } else if ( /^\d+$/.test( prop ) === true ) { - - // accessing array - - return nodeObject( new ArrayElementNode( node, new ConstNode( Number( prop ), 'uint' ) ) ); - - } - - } - - return node[ prop ]; - - } - -}; - -const nodeObjectsCacheMap = new WeakMap(); - -const ShaderNodeObject = function ( obj, altType = null ) { - - const type = getValueType( obj ); - - if ( type === 'node' ) { - - let nodeObject = nodeObjectsCacheMap.get( obj ); - - if ( nodeObject === undefined ) { - - nodeObject = new Proxy( obj, shaderNodeHandler ); - nodeObjectsCacheMap.set( obj, nodeObject ); - nodeObjectsCacheMap.set( nodeObject, nodeObject ); - - } - - return nodeObject; - - } else if ( ( altType === null && ( type === 'float' || type === 'boolean' ) ) || ( type && type !== 'shader' && type !== 'string' ) ) { - - return nodeObject( getConstNode( obj, altType ) ); - - } else if ( type === 'shader' ) { - - return shader( obj ); - - } - - return obj; - -}; - -const ShaderNodeObjects = function ( objects, altType = null ) { - - for ( const name in objects ) { - - objects[ name ] = nodeObject( objects[ name ], altType ); - - } - - return objects; - -}; - -const ShaderNodeArray = function ( array, altType = null ) { - - const len = array.length; - - for ( let i = 0; i < len; i ++ ) { - - array[ i ] = nodeObject( array[ i ], altType ); - - } - - return array; - -}; - -const ShaderNodeProxy = function ( NodeClass, scope = null, factor = null, settings = null ) { - - const assignNode = ( node ) => nodeObject( settings !== null ? Object.assign( node, settings ) : node ); - - if ( scope === null ) { - - return ( ...params ) => { - - return assignNode( new NodeClass( ...nodeArray( params ) ) ); - - }; - - } else if ( factor !== null ) { - - factor = nodeObject( factor ); - - return ( ...params ) => { - - return assignNode( new NodeClass( scope, ...nodeArray( params ), factor ) ); - - }; - - } else { - - return ( ...params ) => { - - return assignNode( new NodeClass( scope, ...nodeArray( params ) ) ); - - }; - - } - -}; - -const ShaderNodeImmutable = function ( NodeClass, ...params ) { - - return nodeObject( new NodeClass( ...nodeArray( params ) ) ); - -}; - -class ShaderNodeInternal extends Node { - - constructor( jsFunc ) { - - super(); - - this._jsFunc = jsFunc; - - } - - call( inputs, stack, builder ) { - - inputs = nodeObjects( inputs ); - - return nodeObject( this._jsFunc( inputs, stack, builder ) ); - - } - - getNodeType( builder ) { - - const { outputNode } = builder.getNodeProperties( this ); - - return outputNode ? outputNode.getNodeType( builder ) : super.getNodeType( builder ); - - } - - construct( builder ) { - - builder.addStack(); - - builder.stack.outputNode = nodeObject( this._jsFunc( builder.stack, builder ) ); - - return builder.removeStack(); - - } - -} - -const bools = [ false, true ]; -const uints = [ 0, 1, 2, 3 ]; -const ints = [ - 1, - 2 ]; -const floats = [ 0.5, 1.5, 1 / 3, 1e-6, 1e6, Math.PI, Math.PI * 2, 1 / Math.PI, 2 / Math.PI, 1 / ( Math.PI * 2 ), Math.PI / 2 ]; - -const boolsCacheMap = new Map(); -for ( const bool of bools ) boolsCacheMap.set( bool, new ConstNode( bool ) ); - -const uintsCacheMap = new Map(); -for ( const uint of uints ) uintsCacheMap.set( uint, new ConstNode( uint, 'uint' ) ); - -const intsCacheMap = new Map( [ ...uintsCacheMap ].map( el => new ConstNode( el.value, 'int' ) ) ); -for ( const int of ints ) intsCacheMap.set( int, new ConstNode( int, 'int' ) ); - -const floatsCacheMap = new Map( [ ...intsCacheMap ].map( el => new ConstNode( el.value ) ) ); -for ( const float of floats ) floatsCacheMap.set( float, new ConstNode( float ) ); -for ( const float of floats ) floatsCacheMap.set( - float, new ConstNode( - float ) ); - -const cacheMaps = { bool: boolsCacheMap, uint: uintsCacheMap, ints: intsCacheMap, float: floatsCacheMap }; - -const constNodesCacheMap = new Map( [ ...boolsCacheMap, ...floatsCacheMap ] ); - -const getConstNode = ( value, type ) => { - - if ( constNodesCacheMap.has( value ) ) { - - return constNodesCacheMap.get( value ); - - } else if ( value.isNode === true ) { - - return value; - - } else { - - return new ConstNode( value, type ); - - } - -}; - -const safeGetNodeType = ( node ) => { - - try { - - return node.getNodeType(); - - } catch { - - return undefined; - - } - -}; - -const ConvertType = function ( type, cacheMap = null ) { - - return ( ...params ) => { - - if ( params.length === 0 || ( ! [ 'bool', 'float', 'int', 'uint' ].includes( type ) && params.every( param => typeof param !== 'object' ) ) ) { - - params = [ getValueFromType( type, ...params ) ]; - - } - - if ( params.length === 1 && cacheMap !== null && cacheMap.has( params[ 0 ] ) ) { - - return nodeObject( cacheMap.get( params[ 0 ] ) ); - - } - - if ( params.length === 1 ) { - - const node = getConstNode( params[ 0 ], type ); - if ( safeGetNodeType( node ) === type ) return nodeObject( node ); - return nodeObject( new ConvertNode( node, type ) ); - - } - - const nodes = params.map( param => getConstNode( param ) ); - return nodeObject( new JoinNode( nodes, type ) ); - - }; - -}; - -// exports - -// utils - -export const getConstNodeType = ( value ) => ( value !== undefined && value !== null ) ? ( value.nodeType || value.convertTo || ( typeof value === 'string' ? value : null ) ) : null; - -// shader node base - -export function ShaderNode( jsFunc ) { - - return new Proxy( new ShaderNodeInternal( jsFunc ), shaderNodeHandler ); - -} - -export const nodeObject = ( val, altType = null ) => /* new */ ShaderNodeObject( val, altType ); -export const nodeObjects = ( val, altType = null ) => new ShaderNodeObjects( val, altType ); -export const nodeArray = ( val, altType = null ) => new ShaderNodeArray( val, altType ); -export const nodeProxy = ( ...val ) => new ShaderNodeProxy( ...val ); -export const nodeImmutable = ( ...val ) => new ShaderNodeImmutable( ...val ); - -export const shader = ( ...val ) => new ShaderNode( ...val ); - -addNodeClass( ShaderNode ); - -// types -// @TODO: Maybe export from ConstNode.js? - -export const color = new ConvertType( 'color' ); - -export const float = new ConvertType( 'float', cacheMaps.float ); -export const int = new ConvertType( 'int', cacheMaps.int ); -export const uint = new ConvertType( 'uint', cacheMaps.uint ); -export const bool = new ConvertType( 'bool', cacheMaps.bool ); - -export const vec2 = new ConvertType( 'vec2' ); -export const ivec2 = new ConvertType( 'ivec2' ); -export const uvec2 = new ConvertType( 'uvec2' ); -export const bvec2 = new ConvertType( 'bvec2' ); - -export const vec3 = new ConvertType( 'vec3' ); -export const ivec3 = new ConvertType( 'ivec3' ); -export const uvec3 = new ConvertType( 'uvec3' ); -export const bvec3 = new ConvertType( 'bvec3' ); - -export const vec4 = new ConvertType( 'vec4' ); -export const ivec4 = new ConvertType( 'ivec4' ); -export const uvec4 = new ConvertType( 'uvec4' ); -export const bvec4 = new ConvertType( 'bvec4' ); - -export const mat3 = new ConvertType( 'mat3' ); -export const imat3 = new ConvertType( 'imat3' ); -export const umat3 = new ConvertType( 'umat3' ); -export const bmat3 = new ConvertType( 'bmat3' ); - -export const mat4 = new ConvertType( 'mat4' ); -export const imat4 = new ConvertType( 'imat4' ); -export const umat4 = new ConvertType( 'umat4' ); -export const bmat4 = new ConvertType( 'bmat4' ); - -export const string = ( value = '' ) => nodeObject( new ConstNode( value, 'string' ) ); -export const arrayBuffer = ( value ) => nodeObject( new ConstNode( value, 'ArrayBuffer' ) ); - -addNodeElement( 'color', color ); -addNodeElement( 'float', float ); -addNodeElement( 'int', int ); -addNodeElement( 'uint', uint ); -addNodeElement( 'bool', bool ); -addNodeElement( 'vec2', vec2 ); -addNodeElement( 'ivec2', ivec2 ); -addNodeElement( 'uvec2', uvec2 ); -addNodeElement( 'bvec2', bvec2 ); -addNodeElement( 'vec3', vec3 ); -addNodeElement( 'ivec3', ivec3 ); -addNodeElement( 'uvec3', uvec3 ); -addNodeElement( 'bvec3', bvec3 ); -addNodeElement( 'vec4', vec4 ); -addNodeElement( 'ivec4', ivec4 ); -addNodeElement( 'uvec4', uvec4 ); -addNodeElement( 'bvec4', bvec4 ); -addNodeElement( 'mat3', mat3 ); -addNodeElement( 'imat3', imat3 ); -addNodeElement( 'umat3', umat3 ); -addNodeElement( 'bmat3', bmat3 ); -addNodeElement( 'mat4', mat4 ); -addNodeElement( 'imat4', imat4 ); -addNodeElement( 'umat4', umat4 ); -addNodeElement( 'bmat4', bmat4 ); -addNodeElement( 'string', string ); -addNodeElement( 'arrayBuffer', arrayBuffer ); - -// basic nodes -// HACK - we cannot export them from the corresponding files because of the cyclic dependency -export const element = nodeProxy( ArrayElementNode ); -export const convert = ( node, types ) => nodeObject( new ConvertNode( nodeObject( node ), types ) ); -export const split = ( node, channels ) => nodeObject( new SplitNode( nodeObject( node ), channels ) ); - -addNodeElement( 'element', element ); -addNodeElement( 'convert', convert ); diff --git a/examples/jsm/nodes/utils/ArrayElementNode.js b/examples/jsm/nodes/utils/ArrayElementNode.js deleted file mode 100644 index 2f42b336964683..00000000000000 --- a/examples/jsm/nodes/utils/ArrayElementNode.js +++ /dev/null @@ -1,33 +0,0 @@ -import Node, { addNodeClass } from '../core/Node.js'; - -class ArrayElementNode extends Node { // @TODO: If extending from TempNode it breaks webgpu_compute - - constructor( node, indexNode ) { - - super(); - - this.node = node; - this.indexNode = indexNode; - - } - - getNodeType( builder ) { - - return this.node.getNodeType( builder ); - - } - - generate( builder ) { - - const nodeSnippet = this.node.build( builder ); - const indexSnippet = this.indexNode.build( builder, 'uint' ); - - return `${nodeSnippet}[ ${indexSnippet} ]`; - - } - -} - -export default ArrayElementNode; - -addNodeClass( ArrayElementNode ); diff --git a/examples/jsm/nodes/utils/ConvertNode.js b/examples/jsm/nodes/utils/ConvertNode.js deleted file mode 100644 index 51f2a28893c73c..00000000000000 --- a/examples/jsm/nodes/utils/ConvertNode.js +++ /dev/null @@ -1,65 +0,0 @@ -import Node, { addNodeClass } from '../core/Node.js'; - -class ConvertNode extends Node { - - constructor( node, convertTo ) { - - super(); - - this.node = node; - this.convertTo = convertTo; - - } - - getNodeType( builder ) { - - const requestType = this.node.getNodeType( builder ); - - let convertTo = null; - - for ( const overloadingType of this.convertTo.split( '|' ) ) { - - if ( convertTo === null || builder.getTypeLength( requestType ) === builder.getTypeLength( overloadingType ) ) { - - convertTo = overloadingType; - - } - - } - - return convertTo; - - } - - serialize( data ) { - - super.serialize( data ); - - data.convertTo = this.convertTo; - - } - - deserialize( data ) { - - super.deserialize( data ); - - this.convertTo = data.convertTo; - - } - - generate( builder, output ) { - - const node = this.node; - const type = this.getNodeType( builder ); - - const snippet = node.build( builder, type ); - - return builder.format( snippet, type, output ); - - } - -} - -export default ConvertNode; - -addNodeClass( ConvertNode ); diff --git a/examples/jsm/nodes/utils/DiscardNode.js b/examples/jsm/nodes/utils/DiscardNode.js deleted file mode 100644 index c52fdafcb490a1..00000000000000 --- a/examples/jsm/nodes/utils/DiscardNode.js +++ /dev/null @@ -1,26 +0,0 @@ -import CondNode from '../math/CondNode.js'; -import { expression } from '../code/ExpressionNode.js'; -import { addNodeClass } from '../core/Node.js'; -import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js'; - -let discardExpression; - -class DiscardNode extends CondNode { - - constructor( condNode ) { - - discardExpression = discardExpression || expression( 'discard' ); - - super( condNode, discardExpression ); - - } - -} - -export default DiscardNode; - -export const discard = nodeProxy( DiscardNode ); - -addNodeElement( 'discard', discard ); - -addNodeClass( DiscardNode ); diff --git a/examples/jsm/nodes/utils/EquirectUVNode.js b/examples/jsm/nodes/utils/EquirectUVNode.js deleted file mode 100644 index d53f2f16226e1a..00000000000000 --- a/examples/jsm/nodes/utils/EquirectUVNode.js +++ /dev/null @@ -1,34 +0,0 @@ -import TempNode from '../core/TempNode.js'; -import { negate } from '../math/MathNode.js'; -import { positionWorldDirection } from '../accessors/PositionNode.js'; -import { nodeProxy, vec2 } from '../shadernode/ShaderNode.js'; -import { addNodeClass } from '../core/Node.js'; - -class EquirectUVNode extends TempNode { - - constructor( dirNode = positionWorldDirection ) { - - super( 'vec2' ); - - this.dirNode = dirNode; - - } - - construct() { - - const dir = negate( this.dirNode ); - - const u = dir.z.atan2( dir.x ).mul( 1 / ( Math.PI * 2 ) ).add( 0.5 ); - const v = dir.y.clamp( - 1.0, 1.0 ).asin().mul( 1 / Math.PI ).add( 0.5 ); - - return vec2( u, v ); - - } - -} - -export default EquirectUVNode; - -export const equirectUV = nodeProxy( EquirectUVNode ); - -addNodeClass( EquirectUVNode ); diff --git a/examples/jsm/nodes/utils/JoinNode.js b/examples/jsm/nodes/utils/JoinNode.js deleted file mode 100644 index 3fe906cb650838..00000000000000 --- a/examples/jsm/nodes/utils/JoinNode.js +++ /dev/null @@ -1,51 +0,0 @@ -import { addNodeClass } from '../core/Node.js'; -import TempNode from '../core/TempNode.js'; - -class JoinNode extends TempNode { - - constructor( nodes = [], nodeType = null ) { - - super( nodeType ); - - this.nodes = nodes; - - } - - getNodeType( builder ) { - - if ( this.nodeType !== null ) { - - return builder.getVectorType( this.nodeType ); - - } - - return builder.getTypeFromLength( this.nodes.reduce( ( count, cur ) => count + builder.getTypeLength( cur.getNodeType( builder ) ), 0 ) ); - - } - - generate( builder, output ) { - - const type = this.getNodeType( builder ); - const nodes = this.nodes; - - const snippetValues = []; - - for ( const input of nodes ) { - - const inputSnippet = input.build( builder ); - - snippetValues.push( inputSnippet ); - - } - - const snippet = `${ builder.getType( type ) }( ${ snippetValues.join( ', ' ) } )`; - - return builder.format( snippet, type, output ); - - } - -} - -export default JoinNode; - -addNodeClass( JoinNode ); diff --git a/examples/jsm/nodes/utils/LoopNode.js b/examples/jsm/nodes/utils/LoopNode.js deleted file mode 100644 index 1ae27fc2ba563e..00000000000000 --- a/examples/jsm/nodes/utils/LoopNode.js +++ /dev/null @@ -1,186 +0,0 @@ -import Node, { addNodeClass } from '../core/Node.js'; -import { expression } from '../code/ExpressionNode.js'; -import { bypass } from '../core/BypassNode.js'; -import { context as contextNode } from '../core/ContextNode.js'; -import { addNodeElement, nodeObject, nodeArray } from '../shadernode/ShaderNode.js'; - -class LoopNode extends Node { - - constructor( params = [] ) { - - super(); - - this.params = params; - - } - - getVarName( index ) { - - return String.fromCharCode( 'i'.charCodeAt() + index ); - - } - - getProperties( builder ) { - - const properties = builder.getNodeProperties( this ); - - if ( properties.stackNode !== undefined ) return properties; - - // - - const inputs = {}; - - for ( let i = 0, l = this.params.length - 1; i < l; i ++ ) { - - const prop = this.getVarName( i ); - - inputs[ prop ] = expression( prop, 'int' ); - - } - - properties.returnsNode = this.params[ this.params.length - 1 ].call( inputs, builder.addStack(), builder ); - properties.stackNode = builder.removeStack(); - - return properties; - - } - - getNodeType( builder ) { - - const { returnsNode } = this.getProperties( builder ); - - return returnsNode ? returnsNode.getNodeType( builder ) : 'void'; - - } - - construct( builder ) { - - // construct properties - - this.getProperties( builder ); - - } - - generate( builder ) { - - const properties = this.getProperties( builder ); - - const context = { tempWrite: false }; - - const params = this.params; - const stackNode = properties.stackNode; - - const returnsSnippet = properties.returnsNode ? properties.returnsNode.build( builder ) : ''; - - for ( let i = 0, l = params.length - 1; i < l; i ++ ) { - - const param = params[ i ]; - const property = this.getVarName( i ); - - let start = null, end = null, direction = null; - - if ( param.isNode ) { - - start = '0'; - end = param.generate( builder, 'int' ); - direction = 'forward'; - - } else { - - start = param.start; - end = param.end; - direction = param.direction; - - if ( typeof start === 'number' ) start = start.toString(); - else if ( start && start.isNode ) start = start.generate( builder, 'int' ); - - if ( typeof end === 'number' ) end = end.toString(); - else if ( end && end.isNode ) end = end.generate( builder, 'int' ); - - if ( start !== undefined && end === undefined ) { - - start = start + ' - 1'; - end = '0'; - direction = 'backwards'; - - } else if ( end !== undefined && start === undefined ) { - - start = '0'; - direction = 'forward'; - - } - - if ( direction === undefined ) { - - if ( Number( start ) > Number( end ) ) { - - direction = 'backwards'; - - } else { - - direction = 'forward'; - - } - - } - - } - - const internalParam = { start, end, direction }; - - // - - const startSnippet = internalParam.start; - const endSnippet = internalParam.end; - - let declarationSnippet = ''; - let conditionalSnippet = ''; - let updateSnippet = ''; - - declarationSnippet += builder.getVar( 'int', property ) + ' = ' + startSnippet; - - if ( internalParam.direction === 'backwards' ) { - - conditionalSnippet += property + ' >= ' + endSnippet; - updateSnippet += property + ' --'; - - } else { - - // forward - - conditionalSnippet += property + ' < ' + endSnippet; - updateSnippet += property + ' ++'; - - } - - const forSnippet = `for ( ${ declarationSnippet }; ${ conditionalSnippet }; ${ updateSnippet } )`; - - builder.addFlowCode( ( i === 0 ? '\n' : '' ) + builder.tab + forSnippet + ' {\n\n' ).addFlowTab(); - - } - - const stackSnippet = contextNode( stackNode, context ).build( builder, 'void' ); - - builder.removeFlowTab().addFlowCode( '\n' + builder.tab + stackSnippet ); - - for ( let i = 0, l = this.params.length - 1; i < l; i ++ ) { - - builder.addFlowCode( ( i === 0 ? '' : builder.tab ) + '}\n\n' ).removeFlowTab(); - - } - - builder.addFlowTab(); - - return returnsSnippet; - - } - -} - -export default LoopNode; - -export const loop = ( ...params ) => nodeObject( new LoopNode( nodeArray( params, 'int' ) ) ); - -addNodeElement( 'loop', ( returns, ...params ) => bypass( returns, loop( ...params ) ) ); - -addNodeClass( LoopNode ); diff --git a/examples/jsm/nodes/utils/MatcapUVNode.js b/examples/jsm/nodes/utils/MatcapUVNode.js deleted file mode 100644 index a2c9f39689fc64..00000000000000 --- a/examples/jsm/nodes/utils/MatcapUVNode.js +++ /dev/null @@ -1,30 +0,0 @@ -import TempNode from '../core/TempNode.js'; -import { transformedNormalView } from '../accessors/NormalNode.js'; -import { positionViewDirection } from '../accessors/PositionNode.js'; -import { nodeImmutable, vec2, vec3 } from '../shadernode/ShaderNode.js'; -import { addNodeClass } from '../core/Node.js'; - -class MatcapUVNode extends TempNode { - - constructor() { - - super( 'vec2' ); - - } - - construct() { - - const x = vec3( positionViewDirection.z, 0, positionViewDirection.x.negate() ).normalize(); - const y = positionViewDirection.cross( x ); - - return vec2( x.dot( transformedNormalView ), y.dot( transformedNormalView ) ).mul( 0.495 ).add( 0.5 ); - - } - -} - -export default MatcapUVNode; - -export const matcapUV = nodeImmutable( MatcapUVNode ); - -addNodeClass( MatcapUVNode ); diff --git a/examples/jsm/nodes/utils/MaxMipLevelNode.js b/examples/jsm/nodes/utils/MaxMipLevelNode.js deleted file mode 100644 index f3d34d5a3fefdb..00000000000000 --- a/examples/jsm/nodes/utils/MaxMipLevelNode.js +++ /dev/null @@ -1,46 +0,0 @@ -import UniformNode from '../core/UniformNode.js'; -import { NodeUpdateType } from '../core/constants.js'; -import { nodeProxy } from '../shadernode/ShaderNode.js'; -import { addNodeClass } from '../core/Node.js'; - -class MaxMipLevelNode extends UniformNode { - - constructor( textureNode ) { - - super( 0 ); - - this.textureNode = textureNode; - - this.updateType = NodeUpdateType.FRAME; - - } - - get texture() { - - return this.textureNode.value; - - } - - update() { - - const texture = this.texture; - const images = texture.images; - const image = ( images && images.length > 0 ) ? ( ( images[ 0 ] && images[ 0 ].image ) || images[ 0 ] ) : texture.image; - - if ( image && image.width !== undefined ) { - - const { width, height } = image; - - this.value = Math.log2( Math.max( width, height ) ); - - } - - } - -} - -export default MaxMipLevelNode; - -export const maxMipLevel = nodeProxy( MaxMipLevelNode ); - -addNodeClass( MaxMipLevelNode ); diff --git a/examples/jsm/nodes/utils/OscNode.js b/examples/jsm/nodes/utils/OscNode.js deleted file mode 100644 index 6d8952b1bc1881..00000000000000 --- a/examples/jsm/nodes/utils/OscNode.js +++ /dev/null @@ -1,81 +0,0 @@ -import Node, { addNodeClass } from '../core/Node.js'; -import { timerLocal } from './TimerNode.js'; -import { nodeObject, nodeProxy } from '../shadernode/ShaderNode.js'; - -class OscNode extends Node { - - constructor( method = OscNode.SINE, timeNode = timerLocal() ) { - - super(); - - this.method = method; - this.timeNode = timeNode; - - } - - getNodeType( builder ) { - - return this.timeNode.getNodeType( builder ); - - } - - construct() { - - const method = this.method; - const timeNode = nodeObject( this.timeNode ); - - let outputNode = null; - - if ( method === OscNode.SINE ) { - - outputNode = timeNode.add( 0.75 ).mul( Math.PI * 2 ).sin().mul( 0.5 ).add( 0.5 ); - - } else if ( method === OscNode.SQUARE ) { - - outputNode = timeNode.fract().round(); - - } else if ( method === OscNode.TRIANGLE ) { - - outputNode = timeNode.add( 0.5 ).fract().mul( 2 ).sub( 1 ).abs(); - - } else if ( method === OscNode.SAWTOOTH ) { - - outputNode = timeNode.fract(); - - } - - return outputNode; - - } - - serialize( data ) { - - super.serialize( data ); - - data.method = this.method; - - } - - deserialize( data ) { - - super.deserialize( data ); - - this.method = data.method; - - } - -} - -OscNode.SINE = 'sine'; -OscNode.SQUARE = 'square'; -OscNode.TRIANGLE = 'triangle'; -OscNode.SAWTOOTH = 'sawtooth'; - -export default OscNode; - -export const oscSine = nodeProxy( OscNode, OscNode.SINE ); -export const oscSquare = nodeProxy( OscNode, OscNode.SQUARE ); -export const oscTriangle = nodeProxy( OscNode, OscNode.TRIANGLE ); -export const oscSawtooth = nodeProxy( OscNode, OscNode.SAWTOOTH ); - -addNodeClass( OscNode ); diff --git a/examples/jsm/nodes/utils/PackingNode.js b/examples/jsm/nodes/utils/PackingNode.js deleted file mode 100644 index 784a1b2647babd..00000000000000 --- a/examples/jsm/nodes/utils/PackingNode.js +++ /dev/null @@ -1,55 +0,0 @@ -import TempNode from '../core/TempNode.js'; -import { addNodeClass } from '../core/Node.js'; -import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js'; - -class PackingNode extends TempNode { - - constructor( scope, node ) { - - super(); - - this.scope = scope; - this.node = node; - - } - - getNodeType( builder ) { - - return this.node.getNodeType( builder ); - - } - - construct() { - - const { scope, node } = this; - - let result = null; - - if ( scope === PackingNode.DIRECTION_TO_COLOR ) { - - result = node.mul( 0.5 ).add( 0.5 ); - - } else if ( scope === PackingNode.COLOR_TO_DIRECTION ) { - - result = node.mul( 2.0 ).sub( 1 ); - - } - - return result; - - } - -} - -PackingNode.DIRECTION_TO_COLOR = 'directionToColor'; -PackingNode.COLOR_TO_DIRECTION = 'colorToDirection'; - -export default PackingNode; - -export const directionToColor = nodeProxy( PackingNode, PackingNode.DIRECTION_TO_COLOR ); -export const colorToDirection = nodeProxy( PackingNode, PackingNode.COLOR_TO_DIRECTION ); - -addNodeElement( 'directionToColor', directionToColor ); -addNodeElement( 'colorToDirection', colorToDirection ); - -addNodeClass( PackingNode ); diff --git a/examples/jsm/nodes/utils/RemapNode.js b/examples/jsm/nodes/utils/RemapNode.js deleted file mode 100644 index 0cfadad94e40dd..00000000000000 --- a/examples/jsm/nodes/utils/RemapNode.js +++ /dev/null @@ -1,42 +0,0 @@ -import Node, { addNodeClass } from '../core/Node.js'; -import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js'; - -class RemapNode extends Node { - - constructor( node, inLowNode, inHighNode, outLowNode, outHighNode ) { - - super(); - - this.node = node; - this.inLowNode = inLowNode; - this.inHighNode = inHighNode; - this.outLowNode = outLowNode; - this.outHighNode = outHighNode; - - this.doClamp = true; - - } - - construct() { - - const { node, inLowNode, inHighNode, outLowNode, outHighNode, doClamp } = this; - - let t = node.sub( inLowNode ).div( inHighNode.sub( inLowNode ) ); - - if ( doClamp === true ) t = t.clamp(); - - return t.mul( outHighNode.sub( outLowNode ) ).add( outLowNode ); - - } - -} - -export default RemapNode; - -export const remap = nodeProxy( RemapNode, null, null, { doClamp: false } ); -export const remapClamp = nodeProxy( RemapNode ); - -addNodeElement( 'remap', remap ); -addNodeElement( 'remapClamp', remapClamp ); - -addNodeClass( RemapNode ); diff --git a/examples/jsm/nodes/utils/RotateUVNode.js b/examples/jsm/nodes/utils/RotateUVNode.js deleted file mode 100644 index 0aad815bc3842b..00000000000000 --- a/examples/jsm/nodes/utils/RotateUVNode.js +++ /dev/null @@ -1,43 +0,0 @@ -import TempNode from '../core/TempNode.js'; -import { addNodeClass } from '../core/Node.js'; -import { addNodeElement, nodeProxy, vec2 } from '../shadernode/ShaderNode.js'; - -class RotateUVNode extends TempNode { - - constructor( uvNode, rotationNode, centerNode = vec2( 0.5 ) ) { - - super( 'vec2' ); - - this.uvNode = uvNode; - this.rotationNode = rotationNode; - this.centerNode = centerNode; - - } - - construct() { - - const { uvNode, rotationNode, centerNode } = this; - - const cosAngle = rotationNode.cos(); - const sinAngle = rotationNode.sin(); - - const vector = uvNode.sub( centerNode ); - - const rotatedVector = vec2( // @TODO: Maybe we can create mat2 and write something like rotationMatrix.mul( vector )? - vec2( cosAngle, sinAngle ).dot( vector ), - vec2( sinAngle.negate(), cosAngle ).dot( vector ) - ); - - return rotatedVector.add( centerNode ); - - } - -} - -export default RotateUVNode; - -export const rotateUV = nodeProxy( RotateUVNode ); - -addNodeElement( 'rotateUV', rotateUV ); - -addNodeClass( RotateUVNode ); diff --git a/examples/jsm/nodes/utils/SpecularMIPLevelNode.js b/examples/jsm/nodes/utils/SpecularMIPLevelNode.js deleted file mode 100644 index b2bebbe4c73123..00000000000000 --- a/examples/jsm/nodes/utils/SpecularMIPLevelNode.js +++ /dev/null @@ -1,37 +0,0 @@ -import Node, { addNodeClass } from '../core/Node.js'; -import { maxMipLevel } from './MaxMipLevelNode.js'; -import { nodeProxy } from '../shadernode/ShaderNode.js'; - -class SpecularMIPLevelNode extends Node { - - constructor( textureNode, roughnessNode = null ) { - - super( 'float' ); - - this.textureNode = textureNode; - this.roughnessNode = roughnessNode; - - } - - construct() { - - const { textureNode, roughnessNode } = this; - - // taken from here: http://casual-effects.blogspot.ca/2011/08/plausible-environment-lighting-in-two.html - - const maxMIPLevelScalar = maxMipLevel( textureNode ); - - const sigma = roughnessNode.mul( roughnessNode ).mul( Math.PI ).div( roughnessNode.add( 1.0 ) ); - const desiredMIPLevel = maxMIPLevelScalar.add( sigma.log2() ); - - return desiredMIPLevel.clamp( 0.0, maxMIPLevelScalar ); - - } - -} - -export default SpecularMIPLevelNode; - -export const specularMIPLevel = nodeProxy( SpecularMIPLevelNode ); - -addNodeClass( SpecularMIPLevelNode ); diff --git a/examples/jsm/nodes/utils/SplitNode.js b/examples/jsm/nodes/utils/SplitNode.js deleted file mode 100644 index d6ab3046c27671..00000000000000 --- a/examples/jsm/nodes/utils/SplitNode.js +++ /dev/null @@ -1,104 +0,0 @@ -import Node, { addNodeClass } from '../core/Node.js'; -import { vectorComponents } from '../core/constants.js'; - -const stringVectorComponents = vectorComponents.join( '' ); - -class SplitNode extends Node { - - constructor( node, components = 'x' ) { - - super(); - - this.node = node; - this.components = components; - - } - - getVectorLength() { - - let vectorLength = this.components.length; - - for ( const c of this.components ) { - - vectorLength = Math.max( vectorComponents.indexOf( c ) + 1, vectorLength ); - - } - - return vectorLength; - - } - - getNodeType( builder ) { - - return builder.getTypeFromLength( this.components.length ); - - } - - generate( builder, output ) { - - const node = this.node; - const nodeTypeLength = builder.getTypeLength( node.getNodeType( builder ) ); - - let snippet = null; - - if ( nodeTypeLength > 1 ) { - - let type = null; - - const componentsLength = this.getVectorLength(); - - if ( componentsLength >= nodeTypeLength ) { - - // needed expand the input node - - type = builder.getTypeFromLength( this.getVectorLength() ); - - } - - const nodeSnippet = node.build( builder, type ); - - if ( this.components.length === nodeTypeLength && this.components === stringVectorComponents.slice( 0, this.components.length ) ) { - - // unecessary swizzle - - snippet = builder.format( nodeSnippet, type, output ); - - } else { - - snippet = builder.format( `${nodeSnippet}.${this.components}`, this.getNodeType( builder ), output ); - - } - - } else { - - // ignore .components if .node returns float/integer - - snippet = node.build( builder, output ); - - } - - return snippet; - - } - - serialize( data ) { - - super.serialize( data ); - - data.components = this.components; - - } - - deserialize( data ) { - - super.deserialize( data ); - - this.components = data.components; - - } - -} - -export default SplitNode; - -addNodeClass( SplitNode ); diff --git a/examples/jsm/nodes/utils/SpriteSheetUVNode.js b/examples/jsm/nodes/utils/SpriteSheetUVNode.js deleted file mode 100644 index 2ada59f907eb08..00000000000000 --- a/examples/jsm/nodes/utils/SpriteSheetUVNode.js +++ /dev/null @@ -1,41 +0,0 @@ -import Node, { addNodeClass } from '../core/Node.js'; -import { uv } from '../accessors/UVNode.js'; -import { nodeProxy, float, vec2 } from '../shadernode/ShaderNode.js'; - -class SpriteSheetUVNode extends Node { - - constructor( countNode, uvNode = uv(), frameNode = float( 0 ) ) { - - super( 'vec2' ); - - this.countNode = countNode; - this.uvNode = uvNode; - this.frameNode = frameNode; - - } - - construct() { - - const { frameNode, uvNode, countNode } = this; - - const { width, height } = countNode; - - const frameNum = frameNode.mod( width.mul( height ) ).floor(); - - const column = frameNum.mod( width ); - const row = height.sub( frameNum.add( 1 ).div( width ).ceil() ); - - const scale = countNode.reciprocal(); - const uvFrameOffset = vec2( column, row ); - - return uvNode.add( uvFrameOffset ).mul( scale ); - - } - -} - -export default SpriteSheetUVNode; - -export const spritesheetUV = nodeProxy( SpriteSheetUVNode ); - -addNodeClass( SpriteSheetUVNode ); diff --git a/examples/jsm/nodes/utils/TimerNode.js b/examples/jsm/nodes/utils/TimerNode.js deleted file mode 100644 index 7870f389f4afa6..00000000000000 --- a/examples/jsm/nodes/utils/TimerNode.js +++ /dev/null @@ -1,94 +0,0 @@ -import UniformNode from '../core/UniformNode.js'; -import { NodeUpdateType } from '../core/constants.js'; -import { nodeObject, nodeImmutable } from '../shadernode/ShaderNode.js'; -import { addNodeClass } from '../core/Node.js'; - -class TimerNode extends UniformNode { - - constructor( scope = TimerNode.LOCAL, scale = 1, value = 0 ) { - - super( value ); - - this.scope = scope; - this.scale = scale; - - this.updateType = NodeUpdateType.FRAME; - - } - /* - @TODO: - getNodeType( builder ) { - - const scope = this.scope; - - if ( scope === TimerNode.FRAME ) { - - return 'uint'; - - } - - return 'float'; - - } -*/ - update( frame ) { - - const scope = this.scope; - const scale = this.scale; - - if ( scope === TimerNode.LOCAL ) { - - this.value += frame.deltaTime * scale; - - } else if ( scope === TimerNode.DELTA ) { - - this.value = frame.deltaTime * scale; - - } else if ( scope === TimerNode.FRAME ) { - - this.value = frame.frameId; - - } else { - - // global - - this.value = frame.time * scale; - - } - - } - - serialize( data ) { - - super.serialize( data ); - - data.scope = this.scope; - data.scale = this.scale; - - } - - deserialize( data ) { - - super.deserialize( data ); - - this.scope = data.scope; - this.scale = data.scale; - - } - -} - -TimerNode.LOCAL = 'local'; -TimerNode.GLOBAL = 'global'; -TimerNode.DELTA = 'delta'; -TimerNode.FRAME = 'frame'; - -export default TimerNode; - -// @TODO: add support to use node in timeScale -export const timerLocal = ( timeScale, value = 0 ) => nodeObject( new TimerNode( TimerNode.LOCAL, timeScale, value ) ); -export const timerGlobal = ( timeScale, value = 0 ) => nodeObject( new TimerNode( TimerNode.GLOBAL, timeScale, value ) ); -export const timerDelta = ( timeScale, value = 0 ) => nodeObject( new TimerNode( TimerNode.DELTA, timeScale, value ) ); -export const frameId = nodeImmutable( TimerNode, TimerNode.FRAME ); - -addNodeClass( TimerNode ); diff --git a/examples/jsm/nodes/utils/TriplanarTexturesNode.js b/examples/jsm/nodes/utils/TriplanarTexturesNode.js deleted file mode 100644 index 22c6b69e5d7fba..00000000000000 --- a/examples/jsm/nodes/utils/TriplanarTexturesNode.js +++ /dev/null @@ -1,62 +0,0 @@ -import Node, { addNodeClass } from '../core/Node.js'; -import { add } from '../math/OperatorNode.js'; -import { normalWorld } from '../accessors/NormalNode.js'; -import { positionWorld } from '../accessors/PositionNode.js'; -import { texture } from '../accessors/TextureNode.js'; -import { addNodeElement, nodeProxy, float, vec3 } from '../shadernode/ShaderNode.js'; - -class TriplanarTexturesNode extends Node { - - constructor( textureXNode, textureYNode = null, textureZNode = null, scaleNode = float( 1 ), positionNode = positionWorld, normalNode = normalWorld ) { - - super( 'vec4' ); - - this.textureXNode = textureXNode; - this.textureYNode = textureYNode; - this.textureZNode = textureZNode; - - this.scaleNode = scaleNode; - - this.positionNode = positionNode; - this.normalNode = normalNode; - - } - - construct() { - - const { textureXNode, textureYNode, textureZNode, scaleNode, positionNode, normalNode } = this; - - // Ref: https://github.com/keijiro/StandardTriplanar - - // Blending factor of triplanar mapping - let bf = normalNode.abs().normalize(); - bf = bf.div( bf.dot( vec3( 1.0 ) ) ); - - // Triplanar mapping - const tx = positionNode.yz.mul( scaleNode ); - const ty = positionNode.zx.mul( scaleNode ); - const tz = positionNode.xy.mul( scaleNode ); - - // Base color - const textureX = textureXNode.value; - const textureY = textureYNode !== null ? textureYNode.value : textureX; - const textureZ = textureZNode !== null ? textureZNode.value : textureX; - - const cx = texture( textureX, tx ).mul( bf.x ); - const cy = texture( textureY, ty ).mul( bf.y ); - const cz = texture( textureZ, tz ).mul( bf.z ); - - return add( cx, cy, cz ); - - } - -} - -export default TriplanarTexturesNode; - -export const triplanarTextures = nodeProxy( TriplanarTexturesNode ); -export const triplanarTexture = ( texture, ...params ) => triplanarTextures( texture, texture, texture, ...params ); - -addNodeElement( 'triplanarTexture', triplanarTexture ); - -addNodeClass( TriplanarTexturesNode ); diff --git a/examples/jsm/objects/GroundProjectedSkybox.js b/examples/jsm/objects/GroundProjectedSkybox.js deleted file mode 100644 index 66a6f7de92a107..00000000000000 --- a/examples/jsm/objects/GroundProjectedSkybox.js +++ /dev/null @@ -1,172 +0,0 @@ -import { Mesh, IcosahedronGeometry, ShaderMaterial, DoubleSide } from 'three'; - -/** - * Ground projected env map adapted from @react-three/drei. - * https://github.com/pmndrs/drei/blob/master/src/core/Environment.tsx - */ -class GroundProjectedSkybox extends Mesh { - - constructor( texture, options = {} ) { - - const isCubeMap = texture.isCubeTexture; - - const defines = [ - isCubeMap ? '#define ENVMAP_TYPE_CUBE' : '' - ]; - - const vertexShader = /* glsl */ ` - varying vec3 vWorldPosition; - - void main() { - - vec4 worldPosition = ( modelMatrix * vec4( position, 1.0 ) ); - vWorldPosition = worldPosition.xyz; - - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - - } - `; - const fragmentShader = defines.join( '\n' ) + /* glsl */ ` - - varying vec3 vWorldPosition; - - uniform float radius; - uniform float height; - uniform float angle; - - #ifdef ENVMAP_TYPE_CUBE - - uniform samplerCube map; - - #else - - uniform sampler2D map; - - #endif - - // From: https://www.shadertoy.com/view/4tsBD7 - float diskIntersectWithBackFaceCulling( vec3 ro, vec3 rd, vec3 c, vec3 n, float r ) - { - - float d = dot ( rd, n ); - - if( d > 0.0 ) { return 1e6; } - - vec3 o = ro - c; - float t = - dot( n, o ) / d; - vec3 q = o + rd * t; - - return ( dot( q, q ) < r * r ) ? t : 1e6; - - } - - // From: https://www.iquilezles.org/www/articles/intersectors/intersectors.htm - float sphereIntersect( vec3 ro, vec3 rd, vec3 ce, float ra ) { - - vec3 oc = ro - ce; - float b = dot( oc, rd ); - float c = dot( oc, oc ) - ra * ra; - float h = b * b - c; - - if( h < 0.0 ) { return -1.0; } - - h = sqrt( h ); - - return - b + h; - - } - - vec3 project() { - - vec3 p = normalize( vWorldPosition ); - vec3 camPos = cameraPosition; - camPos.y -= height; - - float intersection = sphereIntersect( camPos, p, vec3( 0.0 ), radius ); - if( intersection > 0.0 ) { - - vec3 h = vec3( 0.0, - height, 0.0 ); - float intersection2 = diskIntersectWithBackFaceCulling( camPos, p, h, vec3( 0.0, 1.0, 0.0 ), radius ); - p = ( camPos + min( intersection, intersection2 ) * p ) / radius; - - } else { - - p = vec3( 0.0, 1.0, 0.0 ); - - } - - return p; - - } - - #include - - void main() { - - vec3 projectedWorldPosition = project(); - - #ifdef ENVMAP_TYPE_CUBE - - vec3 outcolor = textureCube( map, projectedWorldPosition ).rgb; - - #else - - vec3 direction = normalize( projectedWorldPosition ); - vec2 uv = equirectUv( direction ); - vec3 outcolor = texture2D( map, uv ).rgb; - - #endif - - gl_FragColor = vec4( outcolor, 1.0 ); - - #include - #include - - } - `; - - const uniforms = { - map: { value: texture }, - height: { value: options.height || 15 }, - radius: { value: options.radius || 100 }, - }; - - const geometry = new IcosahedronGeometry( 1, 16 ); - const material = new ShaderMaterial( { - uniforms, - fragmentShader, - vertexShader, - side: DoubleSide, - } ); - - super( geometry, material ); - - } - - set radius( radius ) { - - this.material.uniforms.radius.value = radius; - - } - - get radius() { - - return this.material.uniforms.radius.value; - - } - - set height( height ) { - - this.material.uniforms.height.value = height; - - } - - get height() { - - return this.material.uniforms.height.value; - - } - -} - -export { GroundProjectedSkybox }; diff --git a/examples/jsm/objects/GroundedSkybox.js b/examples/jsm/objects/GroundedSkybox.js new file mode 100644 index 00000000000000..d794f1ef328585 --- /dev/null +++ b/examples/jsm/objects/GroundedSkybox.js @@ -0,0 +1,69 @@ +import { Mesh, MeshBasicMaterial, SphereGeometry, Vector3 } from 'three'; + +/** + * A ground-projected skybox. + * + * By default the object is centered at the camera, so it is often helpful to set + * `skybox.position.y = height` to put the ground at the origin. + * + * ```js + * const height = 15, radius = 100; + * + * const skybox = new GroundedSkybox( envMap, height, radius ); + * skybox.position.y = height; + * scene.add( skybox ); + * ``` + * + * @augments Mesh + * @three_import import { GroundedSkybox } from 'three/addons/objects/GroundedSkybox.js'; + */ +class GroundedSkybox extends Mesh { + + /** + * Constructs a new ground-projected skybox. + * + * @param {Texture} map - The environment map to use. + * @param {number} height - The height is how far the camera that took the photo was above the ground. + * A larger value will magnify the downward part of the image. + * @param {number} radius - The radius of the skybox. Must be large enough to ensure the scene's camera stays inside. + * @param {number} [resolution=128] - The geometry resolution of the skybox. + */ + constructor( map, height, radius, resolution = 128 ) { + + if ( height <= 0 || radius <= 0 || resolution <= 0 ) { + + throw new Error( 'GroundedSkybox height, radius, and resolution must be positive.' ); + + } + + const geometry = new SphereGeometry( radius, 2 * resolution, resolution ); + geometry.scale( 1, 1, - 1 ); + + const pos = geometry.getAttribute( 'position' ); + const tmp = new Vector3(); + + for ( let i = 0; i < pos.count; ++ i ) { + + tmp.fromBufferAttribute( pos, i ); + if ( tmp.y < 0 ) { + + // Smooth out the transition from flat floor to sphere: + const y1 = - height * 3 / 2; + const f = + tmp.y < y1 ? - height / tmp.y : ( 1 - tmp.y * tmp.y / ( 3 * y1 * y1 ) ); + tmp.multiplyScalar( f ); + tmp.toArray( pos.array, 3 * i ); + + } + + } + + pos.needsUpdate = true; + + super( geometry, new MeshBasicMaterial( { map, depthWrite: false } ) ); + + } + +} + +export { GroundedSkybox }; diff --git a/examples/jsm/objects/Lensflare.js b/examples/jsm/objects/Lensflare.js index d349139998eb58..b3a7d8f4e0759d 100644 --- a/examples/jsm/objects/Lensflare.js +++ b/examples/jsm/objects/Lensflare.js @@ -9,21 +9,66 @@ import { Mesh, MeshBasicMaterial, RawShaderMaterial, + UnsignedByteType, Vector2, Vector3, Vector4 } from 'three'; +/** + * Creates a simulated lens flare that tracks a light. + * + * Note that this class can only be used with {@link WebGLRenderer}. + * When using {@link WebGPURenderer}, use {@link LensflareMesh}. + * + * ```js + * const light = new THREE.PointLight( 0xffffff, 1.5, 2000 ); + * + * const lensflare = new Lensflare(); + * lensflare.addElement( new LensflareElement( textureFlare0, 512, 0 ) ); + * lensflare.addElement( new LensflareElement( textureFlare1, 512, 0 ) ); + * lensflare.addElement( new LensflareElement( textureFlare2, 60, 0.6 ) ); + * + * light.add( lensflare ); + * ``` + * + * @augments Mesh + * @three_import import { Lensflare } from 'three/addons/objects/Lensflare.js'; + */ class Lensflare extends Mesh { + /** + * Constructs a new lensflare. + */ constructor() { super( Lensflare.Geometry, new MeshBasicMaterial( { opacity: 0, transparent: true } ) ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isLensflare = true; this.type = 'Lensflare'; + + /** + * Overwritten to disable view-frustum culling by default. + * + * @type {boolean} + * @default false + */ this.frustumCulled = false; + + /** + * Overwritten to make sure lensflares a rendered last. + * + * @type {number} + * @default Infinity + */ this.renderOrder = Infinity; // @@ -36,6 +81,8 @@ class Lensflare extends Mesh { const tempMap = new FramebufferTexture( 16, 16 ); const occlusionMap = new FramebufferTexture( 16, 16 ); + let currentType = UnsignedByteType; + // material const geometry = Lensflare.Geometry; @@ -129,6 +176,7 @@ class Lensflare extends Mesh { const shader = LensflareElement.Shader; const material2 = new RawShaderMaterial( { + name: shader.name, uniforms: { 'map': { value: null }, 'occlusionMap': { value: occlusionMap }, @@ -145,6 +193,11 @@ class Lensflare extends Mesh { const mesh2 = new Mesh( geometry, material2 ); + /** + * Adds the given lensflare element to this instance. + * + * @param {LensflareElement} element - The element to add. + */ this.addElement = function ( element ) { elements.push( element ); @@ -162,6 +215,20 @@ class Lensflare extends Mesh { renderer.getCurrentViewport( viewport ); + const renderTarget = renderer.getRenderTarget(); + const type = ( renderTarget !== null ) ? renderTarget.texture.type : UnsignedByteType; + + if ( currentType !== type ) { + + tempMap.dispose(); + occlusionMap.dispose(); + + tempMap.type = occlusionMap.type = type; + + currentType = type; + + } + const invAspect = viewport.w / viewport.z; const halfViewportWidth = viewport.z / 2.0; const halfViewportHeight = viewport.w / 2.0; @@ -192,7 +259,7 @@ class Lensflare extends Mesh { // save current RGB to temp texture - renderer.copyFramebufferToTexture( screenPositionPixels, tempMap ); + renderer.copyFramebufferToTexture( tempMap, screenPositionPixels ); // render pink quad @@ -204,7 +271,7 @@ class Lensflare extends Mesh { // copy result to occlusionMap - renderer.copyFramebufferToTexture( screenPositionPixels, occlusionMap ); + renderer.copyFramebufferToTexture( occlusionMap, screenPositionPixels ); // restore graphics @@ -245,6 +312,10 @@ class Lensflare extends Mesh { }; + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ this.dispose = function () { material1a.dispose(); @@ -266,15 +337,54 @@ class Lensflare extends Mesh { } -// - +/** + * Represents a single flare that can be added to a {@link Lensflare} container. + * + * @three_import import { LensflareElement } from 'three/addons/objects/Lensflare.js'; + */ class LensflareElement { + /** + * Constructs a new lensflare element. + * + * @param {Texture} texture - The flare's texture. + * @param {number} [size=1] - The size in pixels. + * @param {number} [distance=0] - The normalized distance (`[0,1]`) from the light source. + * A value of `0` means the flare is located at light source. + * @param {Color} [color] - The flare's color + */ constructor( texture, size = 1, distance = 0, color = new Color( 0xffffff ) ) { + /** + * The flare's texture. + * + * @type {Texture} + */ this.texture = texture; + + /** + * The size in pixels. + * + * @type {number} + * @default 1 + */ this.size = size; + + /** + * The normalized distance (`[0,1]`) from the light source. + * A value of `0` means the flare is located at light source. + * + * @type {number} + * @default 0 + */ this.distance = distance; + + /** + * The flare's color + * + * @type {Color} + * @default (1,1,1) + */ this.color = color; } @@ -283,6 +393,8 @@ class LensflareElement { LensflareElement.Shader = { + name: 'LensflareElementShader', + uniforms: { 'map': { value: null }, diff --git a/examples/jsm/objects/LensflareMesh.js b/examples/jsm/objects/LensflareMesh.js new file mode 100644 index 00000000000000..6aa7539d030ff2 --- /dev/null +++ b/examples/jsm/objects/LensflareMesh.js @@ -0,0 +1,376 @@ +import { + AdditiveBlending, + Box2, + BufferGeometry, + Color, + FramebufferTexture, + InterleavedBuffer, + InterleavedBufferAttribute, + Mesh, + MeshBasicNodeMaterial, + NodeMaterial, + UnsignedByteType, + Vector2, + Vector3, + Vector4, + Node +} from 'three/webgpu'; + +import { texture, textureLoad, uv, ivec2, vec2, vec4, positionGeometry, reference, varyingProperty, materialReference, Fn } from 'three/tsl'; + +/** + * Creates a simulated lens flare that tracks a light. + * + * Note that this class can only be used with {@link WebGPURenderer}. + * When using {@link WebGLRenderer}, use {@link Lensflare}. + * + * ```js + * const light = new THREE.PointLight( 0xffffff, 1.5, 2000 ); + * + * const lensflare = new LensflareMesh(); + * lensflare.addElement( new LensflareElement( textureFlare0, 512, 0 ) ); + * lensflare.addElement( new LensflareElement( textureFlare1, 512, 0 ) ); + * lensflare.addElement( new LensflareElement( textureFlare2, 60, 0.6 ) ); + * + * light.add( lensflare ); + * ``` + * + * @augments Mesh + * @three_import import { LensflareMesh } from 'three/addons/objects/LensflareMesh.js'; + */ +class LensflareMesh extends Mesh { + + /** + * Constructs a new lensflare mesh. + */ + constructor() { + + super( LensflareMesh.Geometry, new MeshBasicNodeMaterial( { opacity: 0, transparent: true } ) ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isLensflareMesh = true; + + this.type = 'LensflareMesh'; + + /** + * Overwritten to disable view-frustum culling by default. + * + * @type {boolean} + * @default false + */ + this.frustumCulled = false; + + /** + * Overwritten to make sure lensflares a rendered last. + * + * @type {number} + * @default Infinity + */ + this.renderOrder = Infinity; + + // + + const positionView = new Vector3(); + + // textures + + const tempMap = new FramebufferTexture( 16, 16 ); + const occlusionMap = new FramebufferTexture( 16, 16 ); + + let currentType = UnsignedByteType; + + const geometry = LensflareMesh.Geometry; + + // values for shared material uniforms + + const sharedValues = { + scale: new Vector2(), + positionScreen: new Vector3() + }; + + // materials + + const scale = reference( 'scale', 'vec2', sharedValues ); + const screenPosition = reference( 'positionScreen', 'vec3', sharedValues ); + + const vertexNode = vec4( positionGeometry.xy.mul( scale ).add( screenPosition.xy ), screenPosition.z, 1.0 ); + + const material1a = new NodeMaterial(); + + material1a.depthTest = true; + material1a.depthWrite = false; + material1a.transparent = false; + material1a.fog = false; + material1a.type = 'Lensflare-1a'; + + material1a.vertexNode = vertexNode; + material1a.fragmentNode = vec4( 1.0, 0.0, 1.0, 1.0 ); + + const material1b = new NodeMaterial(); + + material1b.depthTest = false; + material1b.depthWrite = false; + material1b.transparent = false; + material1b.fog = false; + material1b.type = 'Lensflare-1b'; + + material1b.vertexNode = vertexNode; + material1b.fragmentNode = texture( tempMap, vec2( uv().flipY() ) ); + + // the following object is used for occlusionMap generation + + const mesh1 = new Mesh( geometry, material1a ); + + // + + const elements = []; + const elementMeshes = []; + + const material2 = new NodeMaterial(); + + material2.transparent = true; + material2.blending = AdditiveBlending; + material2.depthWrite = false; + material2.depthTest = false; + material2.fog = false; + material2.type = 'Lensflare-2'; + + material2.screenPosition = new Vector3(); + material2.scale = new Vector2(); + material2.occlusionMap = occlusionMap; + + material2.vertexNode = Fn( ( { material } ) => { + + const scale = materialReference( 'scale', 'vec2' ); + const screenPosition = materialReference( 'screenPosition', 'vec3' ); + + const occlusionMap = material.occlusionMap; + + const pos = positionGeometry.xy.toVar(); + + const visibility = textureLoad( occlusionMap, ivec2( 2, 2 ) ).toVar(); + visibility.addAssign( textureLoad( occlusionMap, ivec2( 8, 2 ) ) ); + visibility.addAssign( textureLoad( occlusionMap, ivec2( 14, 2 ) ) ); + visibility.addAssign( textureLoad( occlusionMap, ivec2( 14, 8 ) ) ); + visibility.addAssign( textureLoad( occlusionMap, ivec2( 14, 14 ) ) ); + visibility.addAssign( textureLoad( occlusionMap, ivec2( 8, 14 ) ) ); + visibility.addAssign( textureLoad( occlusionMap, ivec2( 2, 14 ) ) ); + visibility.addAssign( textureLoad( occlusionMap, ivec2( 2, 8 ) ) ); + visibility.addAssign( textureLoad( occlusionMap, ivec2( 8, 8 ) ) ); + + const vVisibility = varyingProperty( 'float', 'vVisibility' ); + + vVisibility.assign( visibility.r.div( 9.0 ) ); + vVisibility.mulAssign( visibility.g.div( 9.0 ).oneMinus() ); + vVisibility.mulAssign( visibility.b.div( 9.0 ) ); + + return vec4( ( pos.mul( scale ).add( screenPosition.xy ).xy ), screenPosition.z, 1.0 ); + + } )(); + + material2.fragmentNode = Fn( () => { + + const color = reference( 'color', 'color' ); + const map = reference( 'map', 'texture' ); + + const vVisibility = varyingProperty( 'float', 'vVisibility' ); + + const output = map.toVar(); + + output.a.mulAssign( vVisibility ); + output.rgb.mulAssign( color ); + + return output; + + } )(); + + /** + * Adds the given lensflare element to this instance. + * + * @param {LensflareElement} element - The element to add. + */ + this.addElement = function ( element ) { + + elements.push( element ); + + }; + + // + + const positionScreen = sharedValues.positionScreen; + const screenPositionPixels = new Vector4( 0, 0, 16, 16 ); + const validArea = new Box2(); + const viewport = new Vector4(); + + // dummy node for renderer.renderObject() + const lightsNode = new Node(); + + this.onBeforeRender = ( renderer, scene, camera ) => { + + renderer.getViewport( viewport ); + + viewport.multiplyScalar( window.devicePixelRatio ); + + const renderTarget = renderer.getRenderTarget(); + const type = ( renderTarget !== null ) ? renderTarget.texture.type : UnsignedByteType; + + if ( currentType !== type ) { + + tempMap.dispose(); + occlusionMap.dispose(); + + tempMap.type = occlusionMap.type = type; + + currentType = type; + + } + + const invAspect = viewport.w / viewport.z; + const halfViewportWidth = viewport.z / 2.0; + const halfViewportHeight = viewport.w / 2.0; + + const size = 16 / viewport.w; + + sharedValues.scale.set( size * invAspect, size ); + + validArea.min.set( viewport.x, viewport.y ); + validArea.max.set( viewport.x + ( viewport.z - 16 ), viewport.y + ( viewport.w - 16 ) ); + + // calculate position in screen space + + positionView.setFromMatrixPosition( this.matrixWorld ); + positionView.applyMatrix4( camera.matrixWorldInverse ); + + if ( positionView.z > 0 ) return; // lensflare is behind the camera + + positionScreen.copy( positionView ).applyMatrix4( camera.projectionMatrix ); + + // horizontal and vertical coordinate of the lower left corner of the pixels to copy + + screenPositionPixels.x = viewport.x + ( positionScreen.x * halfViewportWidth ) + halfViewportWidth - 8; + screenPositionPixels.y = viewport.y - ( positionScreen.y * halfViewportHeight ) + halfViewportHeight - 8; + + // screen cull + + if ( validArea.containsPoint( screenPositionPixels ) ) { + + // save current RGB to temp texture + + renderer.copyFramebufferToTexture( tempMap, screenPositionPixels ); + + // render pink quad + + renderer.renderObject( mesh1, scene, camera, geometry, material1a, null, lightsNode ); + + // copy result to occlusionMap + + renderer.copyFramebufferToTexture( occlusionMap, screenPositionPixels ); + + // restore graphics + + renderer.renderObject( mesh1, scene, camera, geometry, material1b, null, lightsNode ); + + // render elements + + const vecX = - positionScreen.x * 2; + const vecY = - positionScreen.y * 2; + + for ( let i = 0, l = elements.length; i < l; i ++ ) { + + const element = elements[ i ]; + + let mesh2 = elementMeshes[ i ]; + + if ( mesh2 === undefined ) { + + mesh2 = elementMeshes[ i ] = new Mesh( geometry, material2 ); + + mesh2.color = element.color.convertSRGBToLinear(); + mesh2.map = element.texture; + + } + + material2.screenPosition.x = positionScreen.x + vecX * element.distance; + material2.screenPosition.y = positionScreen.y - vecY * element.distance; + material2.screenPosition.z = positionScreen.z; + + const size = element.size / viewport.w; + + material2.scale.set( size * invAspect, size ); + + renderer.renderObject( mesh2, scene, camera, geometry, material2, null, lightsNode ); + + } + + } + + }; + + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + this.dispose = function () { + + material1a.dispose(); + material1b.dispose(); + material2.dispose(); + + tempMap.dispose(); + occlusionMap.dispose(); + + for ( let i = 0, l = elements.length; i < l; i ++ ) { + + elements[ i ].texture.dispose(); + + } + + }; + + } + +} + +// + +class LensflareElement { + + constructor( texture, size = 1, distance = 0, color = new Color( 0xffffff ) ) { + + this.texture = texture; + this.size = size; + this.distance = distance; + this.color = color; + + } + +} + +LensflareMesh.Geometry = ( function () { + + const geometry = new BufferGeometry(); + + const float32Array = new Float32Array( [ + - 1, - 1, 0, 0, 0, + 1, - 1, 0, 1, 0, + 1, 1, 0, 1, 1, + - 1, 1, 0, 0, 1 + ] ); + + const interleavedBuffer = new InterleavedBuffer( float32Array, 5 ); + + geometry.setIndex( [ 0, 1, 2, 0, 2, 3 ] ); + geometry.setAttribute( 'position', new InterleavedBufferAttribute( interleavedBuffer, 3, 0, false ) ); + geometry.setAttribute( 'uv', new InterleavedBufferAttribute( interleavedBuffer, 2, 3, false ) ); + + return geometry; + +} )(); + +export { LensflareMesh, LensflareElement }; diff --git a/examples/jsm/objects/LightningStorm.js b/examples/jsm/objects/LightningStorm.js deleted file mode 100644 index 8423e7f3dc2239..00000000000000 --- a/examples/jsm/objects/LightningStorm.js +++ /dev/null @@ -1,245 +0,0 @@ -import { - MathUtils, - Mesh, - MeshBasicMaterial, - Object3D -} from 'three'; -import { LightningStrike } from '../geometries/LightningStrike.js'; - -/** - * @fileoverview Lightning strike object generator - * - * - * Usage - * - * const myStorm = new LightningStorm( paramsObject ); - * myStorm.position.set( ... ); - * scene.add( myStorm ); - * ... - * myStorm.update( currentTime ); - * - * The "currentTime" can only go forwards or be stopped. - * - * - * LightningStorm parameters: - * - * @param {double} size Size of the storm. If no 'onRayPosition' parameter is defined, it means the side of the rectangle the storm covers. - * - * @param {double} minHeight Minimum height a ray can start at. If no 'onRayPosition' parameter is defined, it means the height above plane y = 0. - * - * @param {double} maxHeight Maximum height a ray can start at. If no 'onRayPosition' parameter is defined, it means the height above plane y = 0. - * - * @param {double} maxSlope The maximum inclination slope of a ray. If no 'onRayPosition' parameter is defined, it means the slope relative to plane y = 0. - * - * @param {integer} maxLightnings Greater than 0. The maximum number of simultaneous rays. - * - * @param {double} lightningMinPeriod minimum time between two consecutive rays. - * - * @param {double} lightningMaxPeriod maximum time between two consecutive rays. - * - * @param {double} lightningMinDuration The minimum time a ray can last. - * - * @param {double} lightningMaxDuration The maximum time a ray can last. - * - * @param {Object} lightningParameters The parameters for created rays. See LightningStrike (geometry) - * - * @param {Material} lightningMaterial The THREE.Material used for the created rays. - * - * @param {function} onRayPosition Optional callback with two Vector3 parameters (source, dest). You can set here the start and end points for each created ray, using the standard size, minHeight, etc parameters and other values in your algorithm. - * - * @param {function} onLightningDown This optional callback is called with one parameter (lightningStrike) when a ray ends propagating, so it has hit the ground. - * - * -*/ - -class LightningStorm extends Object3D { - - constructor( stormParams = {} ) { - - super(); - - this.isLightningStorm = true; - - // Parameters - - this.stormParams = stormParams; - - stormParams.size = stormParams.size !== undefined ? stormParams.size : 1000.0; - stormParams.minHeight = stormParams.minHeight !== undefined ? stormParams.minHeight : 80.0; - stormParams.maxHeight = stormParams.maxHeight !== undefined ? stormParams.maxHeight : 100.0; - stormParams.maxSlope = stormParams.maxSlope !== undefined ? stormParams.maxSlope : 1.1; - - stormParams.maxLightnings = stormParams.maxLightnings !== undefined ? stormParams.maxLightnings : 3; - - stormParams.lightningMinPeriod = stormParams.lightningMinPeriod !== undefined ? stormParams.lightningMinPeriod : 3.0; - stormParams.lightningMaxPeriod = stormParams.lightningMaxPeriod !== undefined ? stormParams.lightningMaxPeriod : 7.0; - - stormParams.lightningMinDuration = stormParams.lightningMinDuration !== undefined ? stormParams.lightningMinDuration : 1.0; - stormParams.lightningMaxDuration = stormParams.lightningMaxDuration !== undefined ? stormParams.lightningMaxDuration : 2.5; - - this.lightningParameters = LightningStrike.copyParameters( stormParams.lightningParameters, stormParams.lightningParameters ); - - this.lightningParameters.isEternal = false; - - this.lightningMaterial = stormParams.lightningMaterial !== undefined ? stormParams.lightningMaterial : new MeshBasicMaterial( { color: 0xB0FFFF } ); - - if ( stormParams.onRayPosition !== undefined ) { - - this.onRayPosition = stormParams.onRayPosition; - - } else { - - this.onRayPosition = function ( source, dest ) { - - dest.set( ( Math.random() - 0.5 ) * stormParams.size, 0, ( Math.random() - 0.5 ) * stormParams.size ); - - const height = MathUtils.lerp( stormParams.minHeight, stormParams.maxHeight, Math.random() ); - - source.set( stormParams.maxSlope * ( 2 * Math.random() - 1 ), 1, stormParams.maxSlope * ( 2 * Math.random() - 1 ) ).multiplyScalar( height ).add( dest ); - - }; - - } - - this.onLightningDown = stormParams.onLightningDown; - - // Internal state - - this.inited = false; - this.nextLightningTime = 0; - this.lightningsMeshes = []; - this.deadLightningsMeshes = []; - - for ( let i = 0; i < this.stormParams.maxLightnings; i ++ ) { - - const lightning = new LightningStrike( LightningStrike.copyParameters( {}, this.lightningParameters ) ); - const mesh = new Mesh( lightning, this.lightningMaterial ); - this.deadLightningsMeshes.push( mesh ); - - } - - } - - update( time ) { - - if ( ! this.inited ) { - - this.nextLightningTime = this.getNextLightningTime( time ) * Math.random(); - this.inited = true; - - } - - if ( time >= this.nextLightningTime ) { - - // Lightning creation - - const lightningMesh = this.deadLightningsMeshes.pop(); - - if ( lightningMesh ) { - - const lightningParams1 = LightningStrike.copyParameters( lightningMesh.geometry.rayParameters, this.lightningParameters ); - - lightningParams1.birthTime = time; - lightningParams1.deathTime = time + MathUtils.lerp( this.stormParams.lightningMinDuration, this.stormParams.lightningMaxDuration, Math.random() ); - - this.onRayPosition( lightningParams1.sourceOffset, lightningParams1.destOffset ); - - lightningParams1.noiseSeed = Math.random(); - - this.add( lightningMesh ); - - this.lightningsMeshes.push( lightningMesh ); - - } - - // Schedule next lightning - this.nextLightningTime = this.getNextLightningTime( time ); - - } - - let i = 0, il = this.lightningsMeshes.length; - - while ( i < il ) { - - const mesh = this.lightningsMeshes[ i ]; - - const lightning = mesh.geometry; - - const prevState = lightning.state; - - lightning.update( time ); - - if ( prevState === LightningStrike.RAY_PROPAGATING && lightning.state > prevState ) { - - if ( this.onLightningDown ) { - - this.onLightningDown( lightning ); - - } - - } - - if ( lightning.state === LightningStrike.RAY_EXTINGUISHED ) { - - // Lightning is to be destroyed - - this.lightningsMeshes.splice( this.lightningsMeshes.indexOf( mesh ), 1 ); - - this.deadLightningsMeshes.push( mesh ); - - this.remove( mesh ); - - il --; - - } else { - - i ++; - - } - - } - - } - - getNextLightningTime( currentTime ) { - - return currentTime + MathUtils.lerp( this.stormParams.lightningMinPeriod, this.stormParams.lightningMaxPeriod, Math.random() ) / ( this.stormParams.maxLightnings + 1 ); - - } - - copy( source, recursive ) { - - super.copy( source, recursive ); - - this.stormParams.size = source.stormParams.size; - this.stormParams.minHeight = source.stormParams.minHeight; - this.stormParams.maxHeight = source.stormParams.maxHeight; - this.stormParams.maxSlope = source.stormParams.maxSlope; - - this.stormParams.maxLightnings = source.stormParams.maxLightnings; - - this.stormParams.lightningMinPeriod = source.stormParams.lightningMinPeriod; - this.stormParams.lightningMaxPeriod = source.stormParams.lightningMaxPeriod; - - this.stormParams.lightningMinDuration = source.stormParams.lightningMinDuration; - this.stormParams.lightningMaxDuration = source.stormParams.lightningMaxDuration; - - this.lightningParameters = LightningStrike.copyParameters( {}, source.lightningParameters ); - - this.lightningMaterial = source.stormParams.lightningMaterial; - - this.onLightningDown = source.onLightningDown; - - return this; - - } - - clone() { - - return new this.constructor( this.stormParams ).copy( this ); - - } - -} - -export { LightningStorm }; diff --git a/examples/jsm/objects/MarchingCubes.js b/examples/jsm/objects/MarchingCubes.js index 642d08648e82fa..29a405be3eae30 100644 --- a/examples/jsm/objects/MarchingCubes.js +++ b/examples/jsm/objects/MarchingCubes.js @@ -9,17 +9,36 @@ import { } from 'three'; /** - * Port of http://webglsamples.org/blob/blob.html + * A marching cubes implementation. + * + * Port of: {@link http://webglsamples.org/blob/blob.html} + * + * @three_import import { MarchingCubes } from 'three/addons/objects/MarchingCubes.js'; */ - class MarchingCubes extends Mesh { + /** + * Constructs a new marching cubes instance. + * + * @param {number} resolution - The effect's resolution. + * @param {Material} material - The cube's material. + * @param {boolean} [enableUvs=false] - Whether texture coordinates should be animated or not. + * @param {boolean} [enableColors=false] - Whether colors should be animated or not. + * @param {number} [maxPolyCount=10000] - The maximum size of the geometry buffers. + */ constructor( resolution, material, enableUvs = false, enableColors = false, maxPolyCount = 10000 ) { const geometry = new BufferGeometry(); super( geometry, material ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isMarchingCubes = true; const scope = this; @@ -30,7 +49,20 @@ class MarchingCubes extends Mesh { const nlist = new Float32Array( 12 * 3 ); const clist = new Float32Array( 12 * 3 ); + /** + * Whether texture coordinates should be animated or not. + * + * @type {boolean} + * @default false + */ this.enableUvs = enableUvs; + + /** + * Whether colors should be animated or not. + * + * @type {boolean} + * @default false + */ this.enableColors = enableColors; // functions have to be object properties @@ -495,9 +527,17 @@ class MarchingCubes extends Mesh { // Metaballs ///////////////////////////////////// - // Adds a reciprocal ball (nice and blobby) that, to be fast, fades to zero after - // a fixed distance, determined by strength and subtract. - + /** + * Adds a reciprocal ball (nice and blobby) that, to be fast, fades to zero after + * a fixed distance, determined by strength and subtract. + * + * @param {number} ballx - The x-coordinate of the ball. + * @param {number} bally - The y-coordinate of the ball. + * @param {number} ballz - The z-coordinate of the ball. + * @param {number} strength - The strength factor. + * @param {number} subtract - The subtract factor. + * @param {Color} colors - The color. + */ this.addBall = function ( ballx, bally, ballz, strength, subtract, colors ) { const sign = Math.sign( strength ); @@ -598,6 +638,12 @@ class MarchingCubes extends Mesh { }; + /** + * Adds a plane along the x-axis. + * + * @param {number} strength - The strength factor. + * @param {number} subtract - The subtract factor. + */ this.addPlaneX = function ( strength, subtract ) { // cache attribute lookups @@ -643,6 +689,12 @@ class MarchingCubes extends Mesh { }; + /** + * Adds a plane along the y-axis. + * + * @param {number} strength - The strength factor. + * @param {number} subtract - The subtract factor. + */ this.addPlaneY = function ( strength, subtract ) { // cache attribute lookups @@ -687,6 +739,12 @@ class MarchingCubes extends Mesh { }; + /** + * Adds a plane along the z-axis. + * + * @param {number} strength - The strength factor. + * @param {number} subtract - The subtract factor. + */ this.addPlaneZ = function ( strength, subtract ) { // cache attribute lookups @@ -735,6 +793,14 @@ class MarchingCubes extends Mesh { // Updates ///////////////////////////////////// + /** + * Sets the cell value for the given coordinates. + * + * @param {number} x - The x value. + * @param {number} y - The y value. + * @param {number} z - The z value. + * @param {number} value - The value to set. + */ this.setCell = function ( x, y, z, value ) { const index = this.size2 * z + this.size * y + x; @@ -742,6 +808,14 @@ class MarchingCubes extends Mesh { }; + /** + * Returns the cell value for the given coordinates. + * + * @param {number} x - The x value. + * @param {number} y - The y value. + * @param {number} z - The z value. + * @return {number} The value. + */ this.getCell = function ( x, y, z ) { const index = this.size2 * z + this.size * y + x; @@ -749,6 +823,11 @@ class MarchingCubes extends Mesh { }; + /** + * Applies a blur with the given intensity. + * + * @param {number} [intensity=1] - The intensity of the blur. + */ this.blur = function ( intensity = 1 ) { const field = this.field; @@ -802,6 +881,9 @@ class MarchingCubes extends Mesh { }; + /** + * Resets the effect. + */ this.reset = function () { // wipe the normal cache @@ -818,6 +900,9 @@ class MarchingCubes extends Mesh { }; + /** + * Updates the effect. + */ this.update = function () { this.count = 0; diff --git a/examples/jsm/objects/Reflector.js b/examples/jsm/objects/Reflector.js index f7195bc92d6f1a..19b96b1f7dc424 100644 --- a/examples/jsm/objects/Reflector.js +++ b/examples/jsm/objects/Reflector.js @@ -9,20 +9,69 @@ import { Vector3, Vector4, WebGLRenderTarget, - HalfFloatType, - NoToneMapping, - LinearSRGBColorSpace + HalfFloatType } from 'three'; +/** + * Can be used to create a flat, reflective surface like a mirror. + * + * Note that this class can only be used with {@link WebGLRenderer}. + * When using {@link WebGPURenderer}, use {@link ReflectorNode}. + * + * ```js + * const geometry = new THREE.PlaneGeometry( 100, 100 ); + * + * const reflector = new Reflector( geometry, { + * clipBias: 0.003, + * textureWidth: window.innerWidth * window.devicePixelRatio, + * textureHeight: window.innerHeight * window.devicePixelRatio, + * color: 0xc1cbcb + * } ); + * + * scene.add( reflector ); + * ``` + * + * @augments Mesh + * @three_import import { Reflector } from 'three/addons/objects/Reflector.js'; + */ class Reflector extends Mesh { + /** + * Constructs a new reflector. + * + * @param {BufferGeometry} geometry - The reflector's geometry. + * @param {Reflector~Options} [options] - The configuration options. + */ constructor( geometry, options = {} ) { super( geometry ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isReflector = true; this.type = 'Reflector'; + + /** + * Whether to force an update, no matter if the reflector + * is in view or not. + * + * @type {boolean} + * @default false + */ + this.forceUpdate = false; + + /** + * The reflector's virtual camera. This is used to render + * the scene from the mirror's point of view. + * + * @type {PerspectiveCamera} + */ this.camera = new PerspectiveCamera(); const scope = this; @@ -54,6 +103,7 @@ class Reflector extends Mesh { const renderTarget = new WebGLRenderTarget( textureWidth, textureHeight, { samples: multisample, type: HalfFloatType } ); const material = new ShaderMaterial( { + name: ( shader.name !== undefined ) ? shader.name : 'unspecified', uniforms: UniformsUtils.clone( shader.uniforms ), fragmentShader: shader.fragmentShader, vertexShader: shader.vertexShader @@ -77,9 +127,10 @@ class Reflector extends Mesh { view.subVectors( reflectorWorldPosition, cameraWorldPosition ); - // Avoid rendering when reflector is facing away + // Avoid rendering when reflector is facing away unless forcing an update + const isFacingAway = view.dot( normal ) > 0; - if ( view.dot( normal ) > 0 ) return; + if ( isFacingAway === true && this.forceUpdate === false ) return; view.reflect( normal ).negate(); view.add( reflectorWorldPosition ); @@ -146,13 +197,9 @@ class Reflector extends Mesh { const currentXrEnabled = renderer.xr.enabled; const currentShadowAutoUpdate = renderer.shadowMap.autoUpdate; - const currentOutputColorSpace = renderer.outputColorSpace; - const currentToneMapping = renderer.toneMapping; renderer.xr.enabled = false; // Avoid camera modification renderer.shadowMap.autoUpdate = false; // Avoid re-computing shadows - renderer.outputColorSpace = LinearSRGBColorSpace; - renderer.toneMapping = NoToneMapping; renderer.setRenderTarget( renderTarget ); @@ -163,8 +210,6 @@ class Reflector extends Mesh { renderer.xr.enabled = currentXrEnabled; renderer.shadowMap.autoUpdate = currentShadowAutoUpdate; - renderer.outputColorSpace = currentOutputColorSpace; - renderer.toneMapping = currentToneMapping; renderer.setRenderTarget( currentRenderTarget ); @@ -179,15 +224,25 @@ class Reflector extends Mesh { } scope.visible = true; + this.forceUpdate = false; }; + /** + * Returns the reflector's internal render target. + * + * @return {WebGLRenderTarget} The internal render target + */ this.getRenderTarget = function () { return renderTarget; }; + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ this.dispose = function () { renderTarget.dispose(); @@ -201,6 +256,8 @@ class Reflector extends Mesh { Reflector.ReflectorShader = { + name: 'ReflectorShader', + uniforms: { 'color': { @@ -261,9 +318,21 @@ Reflector.ReflectorShader = { gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 ); #include - #include + #include }` }; +/** + * Constructor options of `Reflector`. + * + * @typedef {Object} Reflector~Options + * @property {number|Color|string} [color=0x7F7F7F] - The reflector's color. + * @property {number} [textureWidth=512] - The texture width. A higher value results in more clear reflections but is also more expensive. + * @property {number} [textureHeight=512] - The texture height. A higher value results in more clear reflections but is also more expensive. + * @property {number} [clipBias=0] - The clip bias. + * @property {Object} [shader] - Can be used to pass in a custom shader that defines how the reflective view is projected onto the reflector's geometry. + * @property {number} [multisample=4] - How many samples to use for MSAA. `0` disables MSAA. + **/ + export { Reflector }; diff --git a/examples/jsm/objects/ReflectorForSSRPass.js b/examples/jsm/objects/ReflectorForSSRPass.js index fd5fc0b0fcce6f..1813c3c507135a 100644 --- a/examples/jsm/objects/ReflectorForSSRPass.js +++ b/examples/jsm/objects/ReflectorForSSRPass.js @@ -15,8 +15,20 @@ import { HalfFloatType } from 'three'; +/** + * A special version of {@link Reflector} for usage with {@link SSRPass}. + * + * @augments Mesh + * @three_import import { ReflectorForSSRPass } from 'three/addons/objects/ReflectorForSSRPass.js'; + */ class ReflectorForSSRPass extends Mesh { + /** + * Constructs a new reflector. + * + * @param {BufferGeometry} geometry - The reflector's geometry. + * @param {ReflectorForSSRPass~Options} [options] - The configuration options. + */ constructor( geometry, options = {} ) { super( geometry ); @@ -111,6 +123,7 @@ class ReflectorForSSRPass extends Mesh { const renderTarget = new WebGLRenderTarget( textureWidth, textureHeight, parameters ); const material = new ShaderMaterial( { + name: ( shader.name !== undefined ) ? shader.name : 'unspecified', transparent: useDepthTexture, defines: Object.assign( {}, ReflectorForSSRPass.ReflectorShader.defines, { useDepthTexture @@ -239,18 +252,36 @@ class ReflectorForSSRPass extends Mesh { }; + /** + * Returns the reflector's internal render target. + * + * @return {WebGLRenderTarget} The internal render target + */ this.getRenderTarget = function () { return renderTarget; }; + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + this.dispose = function () { + + renderTarget.dispose(); + scope.material.dispose(); + + }; + } } ReflectorForSSRPass.ReflectorShader = { + name: 'ReflectorShader', + defines: { DISTANCE_ATTENUATION: true, FRESNEL: true, @@ -346,4 +377,16 @@ ReflectorForSSRPass.ReflectorShader = { `, }; +/** + * Constructor options of `ReflectorForSSRPass`. + * + * @typedef {Object} ReflectorForSSRPass~Options + * @property {number|Color|string} [color=0x7F7F7F] - The reflector's color. + * @property {number} [textureWidth=512] - The texture width. A higher value results in more clear reflections but is also more expensive. + * @property {number} [textureHeight=512] - The texture height. A higher value results in more clear reflections but is also more expensive. + * @property {number} [clipBias=0] - The clip bias. + * @property {Object} [shader] - Can be used to pass in a custom shader that defines how the reflective view is projected onto the reflector's geometry. + * @property {boolean} [useDepthTexture=true] - Whether to store depth values in a texture or not. + **/ + export { ReflectorForSSRPass }; diff --git a/examples/jsm/objects/Refractor.js b/examples/jsm/objects/Refractor.js index 0461e5e1f10d38..e0743119662e22 100644 --- a/examples/jsm/objects/Refractor.js +++ b/examples/jsm/objects/Refractor.js @@ -10,20 +10,59 @@ import { Vector3, Vector4, WebGLRenderTarget, - LinearSRGBColorSpace, - NoToneMapping, HalfFloatType } from 'three'; +/** + * Can be used to create a flat, refractive surface like for special + * windows or water effects. + * + * Note that this class can only be used with {@link WebGLRenderer}. + * When using {@link WebGPURenderer}, use {@link viewportSharedTexture}. + * + * ```js + * const geometry = new THREE.PlaneGeometry( 100, 100 ); + * + * const refractor = new Refractor( refractorGeometry, { + * color: 0xcbcbcb, + * textureWidth: 1024, + * textureHeight: 1024 + * } ); + * + * scene.add( refractor ); + * ``` + * + * @augments Mesh + * @three_import import { Refractor } from 'three/addons/objects/Refractor.js'; + */ class Refractor extends Mesh { + /** + * Constructs a new refractor. + * + * @param {BufferGeometry} geometry - The refractor's geometry. + * @param {Refractor~Options} [options] - The configuration options. + */ constructor( geometry, options = {} ) { super( geometry ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isRefractor = true; this.type = 'Refractor'; + + /** + * The reflector's virtual camera. + * + * @type {PerspectiveCamera} + */ this.camera = new PerspectiveCamera(); const scope = this; @@ -53,6 +92,7 @@ class Refractor extends Mesh { // material this.material = new ShaderMaterial( { + name: ( shader.name !== undefined ) ? shader.name : 'unspecified', uniforms: UniformsUtils.clone( shader.uniforms ), vertexShader: shader.vertexShader, fragmentShader: shader.fragmentShader, @@ -194,13 +234,9 @@ class Refractor extends Mesh { const currentRenderTarget = renderer.getRenderTarget(); const currentXrEnabled = renderer.xr.enabled; const currentShadowAutoUpdate = renderer.shadowMap.autoUpdate; - const currentOutputColorSpace = renderer.outputColorSpace; - const currentToneMapping = renderer.toneMapping; renderer.xr.enabled = false; // avoid camera modification renderer.shadowMap.autoUpdate = false; // avoid re-computing shadows - renderer.outputColorSpace = LinearSRGBColorSpace; - renderer.toneMapping = NoToneMapping; renderer.setRenderTarget( renderTarget ); if ( renderer.autoClear === false ) renderer.clear(); @@ -208,8 +244,6 @@ class Refractor extends Mesh { renderer.xr.enabled = currentXrEnabled; renderer.shadowMap.autoUpdate = currentShadowAutoUpdate; - renderer.outputColorSpace = currentOutputColorSpace; - renderer.toneMapping = currentToneMapping; renderer.setRenderTarget( currentRenderTarget ); // restore viewport @@ -250,12 +284,21 @@ class Refractor extends Mesh { }; + /** + * Returns the reflector's internal render target. + * + * @return {WebGLRenderTarget} The internal render target + */ this.getRenderTarget = function () { return renderTarget; }; + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ this.dispose = function () { renderTarget.dispose(); @@ -269,6 +312,8 @@ class Refractor extends Mesh { Refractor.RefractorShader = { + name: 'RefractorShader', + uniforms: { 'color': { @@ -323,10 +368,22 @@ Refractor.RefractorShader = { gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 ); #include - #include + #include }` }; +/** + * Constructor options of `Refractor`. + * + * @typedef {Object} Refractor~Options + * @property {number|Color|string} [color=0x7F7F7F] - The refractor's color. + * @property {number} [textureWidth=512] - The texture width. A higher value results in more clear refractions but is also more expensive. + * @property {number} [textureHeight=512] - The texture height. A higher value results in more clear refractions but is also more expensive. + * @property {number} [clipBias=0] - The clip bias. + * @property {Object} [shader] - Can be used to pass in a custom shader that defines how the refractive view is projected onto the reflector's geometry. + * @property {number} [multisample=4] - How many samples to use for MSAA. `0` disables MSAA. + **/ + export { Refractor }; diff --git a/examples/jsm/objects/ShadowMesh.js b/examples/jsm/objects/ShadowMesh.js index d43236a4954dde..fab5dca0b84372 100644 --- a/examples/jsm/objects/ShadowMesh.js +++ b/examples/jsm/objects/ShadowMesh.js @@ -6,14 +6,32 @@ import { IncrementStencilOp } from 'three'; -/** - * A shadow Mesh that follows a shadow-casting Mesh in the scene, but is confined to a single plane. - */ - const _shadowMatrix = new Matrix4(); +/** + * A Shadow Mesh that follows a shadow-casting mesh in the scene, + * but is confined to a single plane. This technique can be used as + * a very performant alternative to classic shadow mapping. However, + * it has serious limitations like: + * + * - Shadows can only be casted on flat planes. + * - No soft shadows support. + * + * ```js + * const cubeShadow = new ShadowMesh( cube ); + * scene.add( cubeShadow ); + * ``` + * + * @augments Mesh + * @three_import import { ShadowMesh } from 'three/addons/objects/ShadowMesh.js'; + */ class ShadowMesh extends Mesh { + /** + * Constructs a new shadow mesh. + * + * @param {Mesh} mesh - The shadow-casting reference mesh. + */ constructor( mesh ) { const shadowMaterial = new MeshBasicMaterial( { @@ -31,15 +49,47 @@ class ShadowMesh extends Mesh { super( mesh.geometry, shadowMaterial ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isShadowMesh = true; + /** + * Represent the world matrix of the reference mesh. + * + * @type {Matrix4} + */ this.meshMatrix = mesh.matrixWorld; + /** + * Overwritten to disable view-frustum culling by default. + * + * @type {boolean} + * @default false + */ this.frustumCulled = false; + + /** + * Overwritten to disable automatic matrix update. The local + * matrix is computed manually in {@link ShadowMesh#update}. + * + * @type {boolean} + * @default false + */ this.matrixAutoUpdate = false; } + /** + * Updates the shadow mesh so it follows its shadow-casting reference mesh. + * + * @param {Plane} plane - The plane onto the shadow mesh is projected. + * @param {Vector4} lightPosition4D - The light position. + */ update( plane, lightPosition4D ) { // based on https://www.opengl.org/archives/resources/features/StencilTalk/tsld021.htm diff --git a/examples/jsm/objects/Sky.js b/examples/jsm/objects/Sky.js index a6f9b1fffbd824..3b490e8ef81467 100644 --- a/examples/jsm/objects/Sky.js +++ b/examples/jsm/objects/Sky.js @@ -8,36 +8,54 @@ import { } from 'three'; /** - * Based on "A Practical Analytic Model for Daylight" - * aka The Preetham Model, the de facto standard analytic skydome model - * https://www.researchgate.net/publication/220720443_A_Practical_Analytic_Model_for_Daylight + * Represents a skydome for scene backgrounds. Based on [A Practical Analytic Model for Daylight]{@link https://www.researchgate.net/publication/220720443_A_Practical_Analytic_Model_for_Daylight} + * aka The Preetham Model, the de facto standard for analytical skydomes. * - * First implemented by Simon Wallner - * http://simonwallner.at/project/atmospheric-scattering/ + * Note that this class can only be used with {@link WebGLRenderer}. + * When using {@link WebGPURenderer}, use {@link SkyMesh}. * - * Improved by Martin Upitis - * http://blenderartists.org/forum/showthread.php?245954-preethams-sky-impementation-HDR + * More references: * - * Three.js integration by zz85 http://twitter.com/blurspline -*/ - + * - {@link http://simonwallner.at/project/atmospheric-scattering/} + * - {@link http://blenderartists.org/forum/showthread.php?245954-preethams-sky-impementation-HDR} + * + * + * ```js + * const sky = new Sky(); + * sky.scale.setScalar( 10000 ); + * scene.add( sky ); + * ``` + * + * @augments Mesh + * @three_import import { Sky } from 'three/addons/objects/Sky.js'; + */ class Sky extends Mesh { + /** + * Constructs a new skydome. + */ constructor() { const shader = Sky.SkyShader; const material = new ShaderMaterial( { - name: 'SkyShader', - fragmentShader: shader.fragmentShader, - vertexShader: shader.vertexShader, + name: shader.name, uniforms: UniformsUtils.clone( shader.uniforms ), + vertexShader: shader.vertexShader, + fragmentShader: shader.fragmentShader, side: BackSide, depthWrite: false } ); super( new BoxGeometry( 1, 1, 1 ), material ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isSky = true; } @@ -46,6 +64,8 @@ class Sky extends Mesh { Sky.SkyShader = { + name: 'SkyShader', + uniforms: { 'turbidity': { value: 2 }, 'rayleigh': { value: 1 }, @@ -75,7 +95,7 @@ Sky.SkyShader = { // wavelength of used primaries, according to preetham const vec3 lambda = vec3( 680E-9, 550E-9, 450E-9 ); - // this pre-calcuation replaces older TotalRayleigh(vec3 lambda) function: + // this pre-calculation replaces older TotalRayleigh(vec3 lambda) function: // (8.0 * pow(pi, 3.0) * pow(pow(n, 2.0) - 1.0, 2.0) * (6.0 + 3.0 * pn)) / (3.0 * N * pow(lambda, vec3(4.0)) * (6.0 - 7.0 * pn)) const vec3 totalRayleigh = vec3( 5.804542996261093E-6, 1.3562911419845635E-5, 3.0265902468824876E-5 ); @@ -118,7 +138,7 @@ Sky.SkyShader = { float rayleighCoefficient = rayleigh - ( 1.0 * ( 1.0 - vSunfade ) ); - // extinction (absorbtion + out scattering) + // extinction (absorption + out scattering) // rayleigh coefficients vBetaR = totalRayleigh * rayleighCoefficient; @@ -138,8 +158,6 @@ Sky.SkyShader = { uniform float mieDirectionalG; uniform vec3 up; - const vec3 cameraPos = vec3( 0.0, 0.0, 0.0 ); - // constants for atmospheric scattering const float pi = 3.141592653589793238462643383279502884197169; @@ -169,7 +187,7 @@ Sky.SkyShader = { void main() { - vec3 direction = normalize( vWorldPosition - cameraPos ); + vec3 direction = normalize( vWorldPosition - cameraPosition ); // optical length // cutoff angle at 90 to avoid singularity in next formula. @@ -210,7 +228,7 @@ Sky.SkyShader = { gl_FragColor = vec4( retColor, 1.0 ); #include - #include + #include }` diff --git a/examples/jsm/objects/SkyMesh.js b/examples/jsm/objects/SkyMesh.js new file mode 100644 index 00000000000000..2c64832e0c7677 --- /dev/null +++ b/examples/jsm/objects/SkyMesh.js @@ -0,0 +1,243 @@ +import { + BackSide, + BoxGeometry, + Mesh, + Vector3, + NodeMaterial +} from 'three/webgpu'; + +import { Fn, float, vec3, acos, add, mul, clamp, cos, dot, exp, max, mix, modelViewProjection, normalize, positionWorld, pow, smoothstep, sub, varyingProperty, vec4, uniform, cameraPosition } from 'three/tsl'; + +/** + * Represents a skydome for scene backgrounds. Based on [A Practical Analytic Model for Daylight]{@link https://www.researchgate.net/publication/220720443_A_Practical_Analytic_Model_for_Daylight} + * aka The Preetham Model, the de facto standard for analytical skydomes. + * + * Note that this class can only be used with {@link WebGLRenderer}. + * When using {@link WebGPURenderer}, use {@link SkyMesh}. + * + * More references: + * + * - {@link http://simonwallner.at/project/atmospheric-scattering/} + * - {@link http://blenderartists.org/forum/showthread.php?245954-preethams-sky-impementation-HDR} + * + * ```js + * const sky = new SkyMesh(); + * sky.scale.setScalar( 10000 ); + * scene.add( sky ); + * ``` + * + * @augments Mesh + * @three_import import { SkyMesh } from 'three/addons/objects/SkyMesh.js'; + */ +class SkyMesh extends Mesh { + + /** + * Constructs a new skydome. + */ + constructor() { + + const material = new NodeMaterial(); + + super( new BoxGeometry( 1, 1, 1 ), material ); + + /** + * The turbidity uniform. + * + * @type {UniformNode} + */ + this.turbidity = uniform( 2 ); + + /** + * The rayleigh uniform. + * + * @type {UniformNode} + */ + this.rayleigh = uniform( 1 ); + + /** + * The mieCoefficient uniform. + * + * @type {UniformNode} + */ + this.mieCoefficient = uniform( 0.005 ); + + /** + * The mieDirectionalG uniform. + * + * @type {UniformNode} + */ + this.mieDirectionalG = uniform( 0.8 ); + + /** + * The sun position uniform. + * + * @type {UniformNode} + */ + this.sunPosition = uniform( new Vector3() ); + + /** + * The up position. + * + * @type {UniformNode} + */ + this.upUniform = uniform( new Vector3( 0, 1, 0 ) ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSky = true; + + // Varyings + + const vSunDirection = varyingProperty( 'vec3' ); + const vSunE = varyingProperty( 'float' ); + const vSunfade = varyingProperty( 'float' ); + const vBetaR = varyingProperty( 'vec3' ); + const vBetaM = varyingProperty( 'vec3' ); + + const vertexNode = /*@__PURE__*/ Fn( () => { + + // constants for atmospheric scattering + const e = float( 2.71828182845904523536028747135266249775724709369995957 ); + // const pi = float( 3.141592653589793238462643383279502884197169 ); + + // wavelength of used primaries, according to preetham + // const lambda = vec3( 680E-9, 550E-9, 450E-9 ); + // this pre-calculation replaces older TotalRayleigh(vec3 lambda) function: + // (8.0 * pow(pi, 3.0) * pow(pow(n, 2.0) - 1.0, 2.0) * (6.0 + 3.0 * pn)) / (3.0 * N * pow(lambda, vec3(4.0)) * (6.0 - 7.0 * pn)) + const totalRayleigh = vec3( 5.804542996261093E-6, 1.3562911419845635E-5, 3.0265902468824876E-5 ); + + // mie stuff + // K coefficient for the primaries + // const v = float( 4.0 ); + // const K = vec3( 0.686, 0.678, 0.666 ); + // MieConst = pi * pow( ( 2.0 * pi ) / lambda, vec3( v - 2.0 ) ) * K + const MieConst = vec3( 1.8399918514433978E14, 2.7798023919660528E14, 4.0790479543861094E14 ); + + // earth shadow hack + // cutoffAngle = pi / 1.95; + const cutoffAngle = float( 1.6110731556870734 ); + const steepness = float( 1.5 ); + const EE = float( 1000.0 ); + + // varying sun position + + const sunDirection = normalize( this.sunPosition ); + vSunDirection.assign( sunDirection ); + + // varying sun intensity + + const angle = dot( sunDirection, this.upUniform ); + const zenithAngleCos = clamp( angle, - 1, 1 ); + const sunIntensity = EE.mul( max( 0.0, float( 1.0 ).sub( pow( e, cutoffAngle.sub( acos( zenithAngleCos ) ).div( steepness ).negate() ) ) ) ); + vSunE.assign( sunIntensity ); + + // varying sun fade + + const sunfade = float( 1.0 ).sub( clamp( float( 1.0 ).sub( exp( this.sunPosition.y.div( 450000.0 ) ) ), 0, 1 ) ); + vSunfade.assign( sunfade ); + + // varying vBetaR + + const rayleighCoefficient = this.rayleigh.sub( float( 1.0 ).mul( float( 1.0 ).sub( sunfade ) ) ); + + // extinction (absorption + out scattering) + // rayleigh coefficients + vBetaR.assign( totalRayleigh.mul( rayleighCoefficient ) ); + + // varying vBetaM + + const c = float( 0.2 ).mul( this.turbidity ).mul( 10E-18 ); + const totalMie = float( 0.434 ).mul( c ).mul( MieConst ); + + vBetaM.assign( totalMie.mul( this.mieCoefficient ) ); + + // position + + const position = modelViewProjection; + position.z.assign( position.w ); // set z to camera.far + + return position; + + } )(); + + const fragmentNode = /*@__PURE__*/ Fn( () => { + + // constants for atmospheric scattering + const pi = float( 3.141592653589793238462643383279502884197169 ); + + // optical length at zenith for molecules + const rayleighZenithLength = float( 8.4E3 ); + const mieZenithLength = float( 1.25E3 ); + // 66 arc seconds -> degrees, and the cosine of that + const sunAngularDiameterCos = float( 0.999956676946448443553574619906976478926848692873900859324 ); + + // 3.0 / ( 16.0 * pi ) + const THREE_OVER_SIXTEENPI = float( 0.05968310365946075 ); + // 1.0 / ( 4.0 * pi ) + const ONE_OVER_FOURPI = float( 0.07957747154594767 ); + + // + + const direction = normalize( positionWorld.sub( cameraPosition ) ); + + // optical length + // cutoff angle at 90 to avoid singularity in next formula. + const zenithAngle = acos( max( 0.0, dot( this.upUniform, direction ) ) ); + const inverse = float( 1.0 ).div( cos( zenithAngle ).add( float( 0.15 ).mul( pow( float( 93.885 ).sub( zenithAngle.mul( 180.0 ).div( pi ) ), - 1.253 ) ) ) ); + const sR = rayleighZenithLength.mul( inverse ); + const sM = mieZenithLength.mul( inverse ); + + // combined extinction factor + const Fex = exp( mul( vBetaR, sR ).add( mul( vBetaM, sM ) ).negate() ); + + // in scattering + const cosTheta = dot( direction, vSunDirection ); + + // betaRTheta + + const c = cosTheta.mul( 0.5 ).add( 0.5 ); + const rPhase = THREE_OVER_SIXTEENPI.mul( float( 1.0 ).add( pow( c, 2.0 ) ) ); + const betaRTheta = vBetaR.mul( rPhase ); + + // betaMTheta + + const g2 = pow( this.mieDirectionalG, 2.0 ); + const inv = float( 1.0 ).div( pow( float( 1.0 ).sub( float( 2.0 ).mul( this.mieDirectionalG ).mul( cosTheta ) ).add( g2 ), 1.5 ) ); + const mPhase = ONE_OVER_FOURPI.mul( float( 1.0 ).sub( g2 ) ).mul( inv ); + const betaMTheta = vBetaM.mul( mPhase ); + + const Lin = pow( vSunE.mul( add( betaRTheta, betaMTheta ).div( add( vBetaR, vBetaM ) ) ).mul( sub( 1.0, Fex ) ), vec3( 1.5 ) ); + Lin.mulAssign( mix( vec3( 1.0 ), pow( vSunE.mul( add( betaRTheta, betaMTheta ).div( add( vBetaR, vBetaM ) ) ).mul( Fex ), vec3( 1.0 / 2.0 ) ), clamp( pow( sub( 1.0, dot( this.upUniform, vSunDirection ) ), 5.0 ), 0.0, 1.0 ) ) ); + + // nightsky + + const L0 = vec3( 0.1 ).mul( Fex ); + + // composition + solar disc + const sundisk = smoothstep( sunAngularDiameterCos, sunAngularDiameterCos.add( 0.00002 ), cosTheta ); + L0.addAssign( vSunE.mul( 19000.0 ).mul( Fex ).mul( sundisk ) ); + + const texColor = add( Lin, L0 ).mul( 0.04 ).add( vec3( 0.0, 0.0003, 0.00075 ) ); + + const retColor = pow( texColor, vec3( float( 1.0 ).div( float( 1.2 ).add( vSunfade.mul( 1.2 ) ) ) ) ); + + return vec4( retColor, 1.0 ); + + } )(); + + material.side = BackSide; + material.depthWrite = false; + + material.vertexNode = vertexNode; + material.fragmentNode = fragmentNode; + + } + +} + +export { SkyMesh }; diff --git a/examples/jsm/objects/Water.js b/examples/jsm/objects/Water.js index 4067c60208ca1f..e9d3fc1ebad89e 100644 --- a/examples/jsm/objects/Water.js +++ b/examples/jsm/objects/Water.js @@ -14,18 +14,39 @@ import { } from 'three'; /** - * Work based on : - * https://github.com/Slayvin: Flat mirror for three.js - * https://home.adelphi.edu/~stemkoski/ : An implementation of water shader based on the flat mirror - * http://29a.ch/ && http://29a.ch/slides/2012/webglwater/ : Water shader explanations in WebGL + * A basic flat, reflective water effect. + * + * Note that this class can only be used with {@link WebGLRenderer}. + * When using {@link WebGPURenderer}, use {@link WaterMesh}. + * + * References: + * + * - [Flat mirror for three.js]{@link https://github.com/Slayvin} + * - [An implementation of water shader based on the flat mirror]{@link https://home.adelphi.edu/~stemkoski/} + * - [Water shader explanations in WebGL]{@link http://29a.ch/slides/2012/webglwater/ } + * + * @augments Mesh + * @three_import import { Water } from 'three/addons/objects/Water.js'; */ - class Water extends Mesh { + /** + * Constructs a new water instance. + * + * @param {BufferGeometry} geometry - The water's geometry. + * @param {Water~Options} [options] - The configuration options. + */ constructor( geometry, options = {} ) { super( geometry ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isWater = true; const scope = this; @@ -67,6 +88,8 @@ class Water extends Mesh { const mirrorShader = { + name: 'MirrorShader', + uniforms: UniformsUtils.merge( [ UniformsLib[ 'fog' ], UniformsLib[ 'lights' ], @@ -181,15 +204,17 @@ class Water extends Mesh { gl_FragColor = vec4( outgoingLight, alpha ); #include - #include + #include + #include }` }; const material = new ShaderMaterial( { - fragmentShader: mirrorShader.fragmentShader, - vertexShader: mirrorShader.vertexShader, + name: mirrorShader.name, uniforms: UniformsUtils.clone( mirrorShader.uniforms ), + vertexShader: mirrorShader.vertexShader, + fragmentShader: mirrorShader.fragmentShader, lights: true, side: side, fog: fog @@ -326,4 +351,23 @@ class Water extends Mesh { } +/** + * Constructor options of `Water`. + * + * @typedef {Object} Water~Options + * @property {number} [textureWidth=512] - The texture width. A higher value results in more clear reflections but is also more expensive. + * @property {number} [textureHeight=512] - The texture height. A higher value results in more clear reflections but is also more expensive. + * @property {number} [clipBias=0] - The clip bias. + * @property {number} [alpha=1] - The alpha value. + * @property {number} [time=0] - The time value. + * @property {?Texture} [waterNormals=null] - The water's normal map. + * @property {Vector3} [sunDirection=(0.70707,0.70707,0.0)] - The sun direction. + * @property {number|Color|string} [sunColor=0xffffff] - The sun color. + * @property {number|Color|string} [waterColor=0x7F7F7F] - The water color. + * @property {Vector3} [eye] - The eye vector. + * @property {number} [distortionScale=20] - The distortion scale. + * @property {(FrontSide|BackSide|DoubleSide)} [side=FrontSide] - The water material's `side` property. + * @property {boolean} [fog=false] - Whether the water should be affected by fog or not. + **/ + export { Water }; diff --git a/examples/jsm/objects/Water2.js b/examples/jsm/objects/Water2.js index 01c0fabec076be..3ba6894b5f05e4 100644 --- a/examples/jsm/objects/Water2.js +++ b/examples/jsm/objects/Water2.js @@ -14,19 +14,41 @@ import { import { Reflector } from '../objects/Reflector.js'; import { Refractor } from '../objects/Refractor.js'; +/** @module Water2 */ + /** + * An advanced water effect that supports reflections, refractions and flow maps. + * + * Note that this class can only be used with {@link WebGLRenderer}. + * When using {@link WebGPURenderer}, use {@link module:Water2Mesh}. + * * References: - * https://alex.vlachos.com/graphics/Vlachos-SIGGRAPH10-WaterFlow.pdf - * http://graphicsrunner.blogspot.de/2010/08/water-using-flow-maps.html * + * - {@link https://alex.vlachos.com/graphics/Vlachos-SIGGRAPH10-WaterFlow.pdf} + * - {@link http://graphicsrunner.blogspot.de/2010/08/water-using-flow-maps.html} + * + * @augments Mesh + * @three_import import { Water } from 'three/addons/objects/Water2.js'; */ - class Water extends Mesh { + /** + * Constructs a new water instance. + * + * @param {BufferGeometry} geometry - The water's geometry. + * @param {module:Water2~Options} [options] - The configuration options. + */ constructor( geometry, options = {} ) { super( geometry ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isWater = true; this.type = 'Water'; @@ -34,14 +56,14 @@ class Water extends Mesh { const scope = this; const color = ( options.color !== undefined ) ? new Color( options.color ) : new Color( 0xFFFFFF ); - const textureWidth = options.textureWidth || 512; - const textureHeight = options.textureHeight || 512; - const clipBias = options.clipBias || 0; - const flowDirection = options.flowDirection || new Vector2( 1, 0 ); - const flowSpeed = options.flowSpeed || 0.03; - const reflectivity = options.reflectivity || 0.02; - const scale = options.scale || 1; - const shader = options.shader || Water.WaterShader; + const textureWidth = options.textureWidth !== undefined ? options.textureWidth : 512; + const textureHeight = options.textureHeight !== undefined ? options.textureHeight : 512; + const clipBias = options.clipBias !== undefined ? options.clipBias : 0; + const flowDirection = options.flowDirection !== undefined ? options.flowDirection : new Vector2( 1, 0 ); + const flowSpeed = options.flowSpeed !== undefined ? options.flowSpeed : 0.03; + const reflectivity = options.reflectivity !== undefined ? options.reflectivity : 0.02; + const scale = options.scale !== undefined ? options.scale : 1; + const shader = options.shader !== undefined ? options.shader : Water.WaterShader; const textureLoader = new TextureLoader(); @@ -88,6 +110,7 @@ class Water extends Mesh { // material this.material = new ShaderMaterial( { + name: shader.name, uniforms: UniformsUtils.merge( [ UniformsLib[ 'fog' ], shader.uniforms @@ -131,7 +154,7 @@ class Water extends Mesh { this.material.uniforms[ 'reflectivity' ].value = reflectivity; this.material.uniforms[ 'textureMatrix' ].value = textureMatrix; - // inital values + // initial values this.material.uniforms[ 'config' ].value.x = 0; // flowMapOffset0 this.material.uniforms[ 'config' ].value.y = halfCycle; // flowMapOffset1 @@ -205,6 +228,8 @@ class Water extends Mesh { Water.WaterShader = { + name: 'WaterShader', + uniforms: { 'color': { @@ -348,11 +373,29 @@ Water.WaterShader = { gl_FragColor = vec4( color, 1.0 ) * mix( refractColor, reflectColor, reflectance ); #include - #include + #include #include }` }; +/** + * Constructor options of `Water`. + * + * @typedef {Object} module:Water2~Options + * @property {number|Color|string} [color=0xFFFFFF] - The water color. + * @property {number} [textureWidth=512] - The texture width. A higher value results in better quality but is also more expensive. + * @property {number} [textureHeight=512] - The texture height. A higher value results in better quality but is also more expensive. + * @property {number} [clipBias=0] - The clip bias. + * @property {Vector2} [flowDirection=(1,0)] - The water's flow direction. + * @property {number} [flowSpeed=0.03] - The water's flow speed. + * @property {number} [reflectivity=0.02] - The water's reflectivity. + * @property {number} [scale=1] - The water's scale. + * @property {Object} [shader] - A custom water shader. + * @property {?Texture} [flowMap=null] - The flow map. If no flow map is assigned, the water flow is defined by `flowDirection`. + * @property {?Texture} [normalMap0] - The first water normal map. + * @property {?Texture} [normalMap1] - The second water normal map. + **/ + export { Water }; diff --git a/examples/jsm/objects/Water2Mesh.js b/examples/jsm/objects/Water2Mesh.js new file mode 100644 index 00000000000000..f3c168cc228364 --- /dev/null +++ b/examples/jsm/objects/Water2Mesh.js @@ -0,0 +1,199 @@ +import { + Color, + Mesh, + Vector2, + Vector3, + NodeMaterial, + NodeUpdateType, + TempNode +} from 'three/webgpu'; + +import { Fn, vec2, viewportSafeUV, viewportSharedTexture, reflector, pow, float, abs, texture, uniform, vec4, cameraPosition, positionWorld, uv, mix, vec3, normalize, max, dot, screenUV } from 'three/tsl'; + +/** @module Water2Mesh */ + +/** + * An advanced water effect that supports reflections, refractions and flow maps. + * + * Note that this class can only be used with {@link WebGPURenderer}. + * When using {@link WebGLRenderer}, use {@link module:Water2}. + * + * References: + * + * - {@link https://alex.vlachos.com/graphics/Vlachos-SIGGRAPH10-WaterFlow.pdf} + * - {@link http://graphicsrunner.blogspot.de/2010/08/water-using-flow-maps.html} + * + * @augments Mesh + * @three_import import { WaterMesh } from 'three/addons/objects/Water2Mesh.js'; + */ +class WaterMesh extends Mesh { + + /** + * Constructs a new water mesh. + * + * @param {BufferGeometry} geometry - The water's geometry. + * @param {module:Water2~Options} [options] - The configuration options. + */ + constructor( geometry, options = {} ) { + + const material = new NodeMaterial(); + material.transparent = true; + + super( geometry, material ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isWater = true; + + material.colorNode = new WaterNode( options, this ); + + } + +} + +class WaterNode extends TempNode { + + constructor( options, waterBody ) { + + super( 'vec4' ); + + this.waterBody = waterBody; + + this.normalMap0 = texture( options.normalMap0 ); + this.normalMap1 = texture( options.normalMap1 ); + this.flowMap = texture( options.flowMap !== undefined ? options.flowMap : null ); + + this.color = uniform( options.color !== undefined ? new Color( options.color ) : new Color( 0xffffff ) ); + this.flowDirection = uniform( options.flowDirection !== undefined ? options.flowDirection : new Vector2( 1, 0 ) ); + this.flowSpeed = uniform( options.flowSpeed !== undefined ? options.flowSpeed : 0.03 ); + this.reflectivity = uniform( options.reflectivity !== undefined ? options.reflectivity : 0.02 ); + this.scale = uniform( options.scale !== undefined ? options.scale : 1 ); + this.flowConfig = uniform( new Vector3() ); + + this.updateBeforeType = NodeUpdateType.RENDER; + + this._cycle = 0.15; // a cycle of a flow map phase + this._halfCycle = this._cycle * 0.5; + + this._USE_FLOW = options.flowMap !== undefined; + + } + + updateFlow( delta ) { + + this.flowConfig.value.x += this.flowSpeed.value * delta; // flowMapOffset0 + this.flowConfig.value.y = this.flowConfig.value.x + this._halfCycle; // flowMapOffset1 + + // Important: The distance between offsets should be always the value of "halfCycle". + // Moreover, both offsets should be in the range of [ 0, cycle ]. + // This approach ensures a smooth water flow and avoids "reset" effects. + + if ( this.flowConfig.value.x >= this._cycle ) { + + this.flowConfig.value.x = 0; + this.flowConfig.value.y = this._halfCycle; + + } else if ( this.flowConfig.value.y >= this._cycle ) { + + this.flowConfig.value.y = this.flowConfig.value.y - this._cycle; + + } + + this.flowConfig.value.z = this._halfCycle; + + } + + updateBefore( frame ) { + + this.updateFlow( frame.deltaTime ); + + } + + setup() { + + const outputNode = Fn( () => { + + const flowMapOffset0 = this.flowConfig.x; + const flowMapOffset1 = this.flowConfig.y; + const halfCycle = this.flowConfig.z; + + const toEye = normalize( cameraPosition.sub( positionWorld ) ); + + let flow; + + if ( this._USE_FLOW === true ) { + + flow = this.flowMap.rg.mul( 2 ).sub( 1 ); + + } else { + + flow = vec2( this.flowDirection.x, this.flowDirection.y ); + + } + + flow.x.mulAssign( - 1 ); + + // sample normal maps (distort uvs with flowdata) + + const uvs = uv(); + + const normalUv0 = uvs.mul( this.scale ).add( flow.mul( flowMapOffset0 ) ); + const normalUv1 = uvs.mul( this.scale ).add( flow.mul( flowMapOffset1 ) ); + + const normalColor0 = this.normalMap0.sample( normalUv0 ); + const normalColor1 = this.normalMap1.sample( normalUv1 ); + + // linear interpolate to get the final normal color + const flowLerp = abs( halfCycle.sub( flowMapOffset0 ) ).div( halfCycle ); + const normalColor = mix( normalColor0, normalColor1, flowLerp ); + + // calculate normal vector + const normal = normalize( vec3( normalColor.r.mul( 2 ).sub( 1 ), normalColor.b, normalColor.g.mul( 2 ).sub( 1 ) ) ); + + // calculate the fresnel term to blend reflection and refraction maps + const theta = max( dot( toEye, normal ), 0 ); + const reflectance = pow( float( 1.0 ).sub( theta ), 5.0 ).mul( float( 1.0 ).sub( this.reflectivity ) ).add( this.reflectivity ); + + // reflector, refractor + + const offset = normal.xz.mul( 0.05 ).toVar(); + + const reflectionSampler = reflector(); + this.waterBody.add( reflectionSampler.target ); + reflectionSampler.uvNode = reflectionSampler.uvNode.add( offset ); + + const refractorUV = screenUV.add( offset ); + const refractionSampler = viewportSharedTexture( viewportSafeUV( refractorUV ) ); + + // calculate final uv coords + + return vec4( this.color, 1.0 ).mul( mix( refractionSampler, reflectionSampler, reflectance ) ); + + } )(); + + return outputNode; + + } + +} + +/** + * Constructor options of `WaterMesh`. + * + * @typedef {Object} module:Water2Mesh~Options + * @property {number|Color|string} [color=0xFFFFFF] - The water color. + * @property {Vector2} [flowDirection=(1,0)] - The water's flow direction. + * @property {number} [flowSpeed=0.03] - The water's flow speed. + * @property {number} [reflectivity=0.02] - The water's reflectivity. + * @property {number} [scale=1] - The water's scale. + * @property {?Texture} [flowMap=null] - The flow map. If no flow map is assigned, the water flow is defined by `flowDirection`. + * @property {Texture} normalMap0 - The first water normal map. + * @property {Texture} normalMap1 - The second water normal map. + **/ + +export { WaterMesh }; diff --git a/examples/jsm/objects/WaterMesh.js b/examples/jsm/objects/WaterMesh.js new file mode 100644 index 00000000000000..43b15299c9b005 --- /dev/null +++ b/examples/jsm/objects/WaterMesh.js @@ -0,0 +1,196 @@ +import { + Color, + Mesh, + Vector3, + MeshLambertNodeMaterial +} from 'three/webgpu'; + +import { Fn, add, cameraPosition, div, normalize, positionWorld, sub, time, texture, vec2, vec3, max, dot, reflect, pow, length, float, uniform, reflector, mul, mix, diffuseColor } from 'three/tsl'; + +/** + * A basic flat, reflective water effect. + * + * Note that this class can only be used with {@link WebGPURenderer}. + * When using {@link WebGLRenderer}, use {@link Water}. + * + * References: + * + * - [Flat mirror for three.js]{@link https://github.com/Slayvin} + * - [An implementation of water shader based on the flat mirror]{@link https://home.adelphi.edu/~stemkoski/} + * - [Water shader explanations in WebGL]{@link http://29a.ch/slides/2012/webglwater/ } + * + * @augments Mesh + * @three_import import { WaterMesh } from 'three/addons/objects/WaterMesh.js'; + */ +class WaterMesh extends Mesh { + + /** + * Constructs a new water mesh. + * + * @param {BufferGeometry} geometry - The water mesh's geometry. + * @param {WaterMesh~Options} [options] - The configuration options. + */ + constructor( geometry, options ) { + + const material = new MeshLambertNodeMaterial(); + + super( geometry, material ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isWaterMesh = true; + + /** + * The effect's resolution scale. + * + * @type {number} + * @default 0.5 + */ + this.resolution = options.resolution !== undefined ? options.resolution : 0.5; + + // Uniforms + + /** + * The water's normal map. + * + * @type {TextureNode} + */ + this.waterNormals = texture( options.waterNormals ); + + /** + * The alpha value. + * + * @type {UniformNode} + * @default 1 + */ + this.alpha = uniform( options.alpha !== undefined ? options.alpha : 1.0 ); + + /** + * The size value. + * + * @type {UniformNode} + * @default 1 + */ + this.size = uniform( options.size !== undefined ? options.size : 1.0 ); + + /** + * The sun color. + * + * @type {UniformNode} + * @default 0xffffff + */ + this.sunColor = uniform( new Color( options.sunColor !== undefined ? options.sunColor : 0xffffff ) ); + + /** + * The sun direction. + * + * @type {UniformNode} + * @default (0.70707,0.70707,0.0) + */ + this.sunDirection = uniform( options.sunDirection !== undefined ? options.sunDirection : new Vector3( 0.70707, 0.70707, 0.0 ) ); + + /** + * The water color. + * + * @type {UniformNode} + * @default 0x7f7f7f + */ + this.waterColor = uniform( new Color( options.waterColor !== undefined ? options.waterColor : 0x7f7f7f ) ); + + /** + * The distortion scale. + * + * @type {UniformNode} + * @default 20 + */ + this.distortionScale = uniform( options.distortionScale !== undefined ? options.distortionScale : 20.0 ); + + // TSL + + const getNoise = Fn( ( [ uv ] ) => { + + const offset = time; + + const uv0 = add( div( uv, 103 ), vec2( div( offset, 17 ), div( offset, 29 ) ) ).toVar(); + const uv1 = div( uv, 107 ).sub( vec2( div( offset, - 19 ), div( offset, 31 ) ) ).toVar(); + const uv2 = add( div( uv, vec2( 8907.0, 9803.0 ) ), vec2( div( offset, 101 ), div( offset, 97 ) ) ).toVar(); + const uv3 = sub( div( uv, vec2( 1091.0, 1027.0 ) ), vec2( div( offset, 109 ), div( offset, - 113 ) ) ).toVar(); + + const sample0 = this.waterNormals.sample( uv0 ); + const sample1 = this.waterNormals.sample( uv1 ); + const sample2 = this.waterNormals.sample( uv2 ); + const sample3 = this.waterNormals.sample( uv3 ); + + const noise = sample0.add( sample1 ).add( sample2 ).add( sample3 ); + + return noise.mul( 0.5 ).sub( 1 ); + + } ); + + const noise = getNoise( positionWorld.xz.mul( this.size ) ); + const surfaceNormal = normalize( noise.xzy.mul( 1.5, 1.0, 1.5 ) ); + + const worldToEye = cameraPosition.sub( positionWorld ); + const eyeDirection = normalize( worldToEye ); + + const reflection = normalize( reflect( this.sunDirection.negate(), surfaceNormal ) ); + const direction = max( 0.0, dot( eyeDirection, reflection ) ); + const specularLight = pow( direction, 100 ).mul( this.sunColor ).mul( 2.0 ); + const diffuseLight = max( dot( this.sunDirection, surfaceNormal ), 0.0 ).mul( this.sunColor ).mul( 0.5 ); + + const distance = length( worldToEye ); + + const distortion = surfaceNormal.xz.mul( float( 0.001 ).add( float( 1.0 ).div( distance ) ) ).mul( this.distortionScale ); + + // Material + + material.transparent = true; + + material.opacityNode = this.alpha; + + material.receivedShadowPositionNode = positionWorld.add( distortion ); + + material.setupOutgoingLight = () => diffuseColor.rgb; // backwards compatibility + + material.colorNode = Fn( () => { + + const mirrorSampler = reflector(); + mirrorSampler.uvNode = mirrorSampler.uvNode.add( distortion ); + mirrorSampler.resolution = this.resolution; + + this.add( mirrorSampler.target ); + + const theta = max( dot( eyeDirection, surfaceNormal ), 0.0 ); + const rf0 = float( 0.3 ); + const reflectance = mul( pow( float( 1.0 ).sub( theta ), 5.0 ), float( 1.0 ).sub( rf0 ) ).add( rf0 ); + const scatter = max( 0.0, dot( surfaceNormal, eyeDirection ) ).mul( this.waterColor ); + const albedo = mix( this.sunColor.mul( diffuseLight ).mul( 0.3 ).add( scatter ), mirrorSampler.rgb.mul( specularLight ).add( mirrorSampler.rgb.mul( 0.9 ) ).add( vec3( 0.1 ) ), reflectance ); + + return albedo; + + } )(); + + } + +} + +/** + * Constructor options of `WaterMesh`. + * + * @typedef {Object} WaterMesh~Options + * @property {number} [resolution=0.5] - The resolution scale. + * @property {?Texture} [waterNormals=null] - The water's normal map. + * @property {number} [alpha=1] - The alpha value. + * @property {number} [size=1] - The size value. + * @property {number|Color|string} [sunColor=0xffffff] - The sun color. + * @property {Vector3} [sunDirection=(0.70707,0.70707,0.0)] - The sun direction. + * @property {number|Color|string} [waterColor=0x7F7F7F] - The water color. + * @property {number} [distortionScale=20] - The distortion scale. + **/ + +export { WaterMesh }; diff --git a/examples/jsm/physics/AmmoPhysics.js b/examples/jsm/physics/AmmoPhysics.js index 6c3cecf0ad67f5..d1258497fd944f 100644 --- a/examples/jsm/physics/AmmoPhysics.js +++ b/examples/jsm/physics/AmmoPhysics.js @@ -1,3 +1,19 @@ +/** + * @classdesc Can be used to include Ammo.js as a Physics engine into + * `three.js` apps. Make sure to include `ammo.wasm.js` first: + * ``` + * + * ``` + * It is then possible to initialize the API via: + * ```js + * const physics = await AmmoPhysics(); + * ``` + * + * @name AmmoPhysics + * @class + * @hideconstructor + * @three_import import { AmmoPhysics } from 'three/addons/physics/AmmoPhysics.js'; + */ async function AmmoPhysics() { if ( 'Ammo' in window === false ) { @@ -57,6 +73,26 @@ async function AmmoPhysics() { const meshes = []; const meshMap = new WeakMap(); + function addScene( scene ) { + + scene.traverse( function ( child ) { + + if ( child.isMesh ) { + + const physics = child.userData.physics; + + if ( physics ) { + + addMesh( child, physics.mass ); + + } + + } + + } ); + + } + function addMesh( mesh, mass = 0 ) { const shape = getShape( mesh.geometry ); @@ -245,7 +281,40 @@ async function AmmoPhysics() { setInterval( step, 1000 / frameRate ); return { + /** + * Adds the given scene to this physics simulation. Only meshes with a + * `physics` object in their {@link Object3D#userData} field will be honored. + * The object can be used to store the mass of the mesh. E.g.: + * ```js + * box.userData.physics = { mass: 1 }; + * ``` + * + * @method + * @name AmmoPhysics#addScene + * @param {Object3D} scene The scene or any type of 3D object to add. + */ + addScene: addScene, + + /** + * Adds the given mesh to this physics simulation. + * + * @method + * @name AmmoPhysics#addMesh + * @param {Mesh} mesh The mesh to add. + * @param {number} [mass=0] The mass in kg of the mesh. + */ addMesh: addMesh, + + /** + * Set the position of the given mesh which is part of the physics simulation. Calling this + * method will reset the current simulated velocity of the mesh. + * + * @method + * @name AmmoPhysics#setMeshPosition + * @param {Mesh} mesh The mesh to update the position for. + * @param {Vector3} position - The new position. + * @param {number} [index=0] - If the mesh is instanced, the index represents the instanced ID. + */ setMeshPosition: setMeshPosition // addCompoundMesh }; diff --git a/examples/jsm/physics/JoltPhysics.js b/examples/jsm/physics/JoltPhysics.js new file mode 100644 index 00000000000000..0a3582eb84095d --- /dev/null +++ b/examples/jsm/physics/JoltPhysics.js @@ -0,0 +1,330 @@ +import { Clock, Vector3, Quaternion, Matrix4 } from 'three'; + +const JOLT_PATH = 'https://cdn.jsdelivr.net/npm/jolt-physics@0.23.0/dist/jolt-physics.wasm-compat.js'; + +const frameRate = 60; + +let Jolt = null; + +function getShape( geometry ) { + + const parameters = geometry.parameters; + + // TODO change type to is* + + if ( geometry.type === 'BoxGeometry' ) { + + const sx = parameters.width !== undefined ? parameters.width / 2 : 0.5; + const sy = parameters.height !== undefined ? parameters.height / 2 : 0.5; + const sz = parameters.depth !== undefined ? parameters.depth / 2 : 0.5; + + return new Jolt.BoxShape( new Jolt.Vec3( sx, sy, sz ), 0.05 * Math.min( sx, sy, sz ), null ); + + } else if ( geometry.type === 'SphereGeometry' || geometry.type === 'IcosahedronGeometry' ) { + + const radius = parameters.radius !== undefined ? parameters.radius : 1; + + return new Jolt.SphereShape( radius, null ); + + } + + return null; + +} + +// Object layers +const LAYER_NON_MOVING = 0; +const LAYER_MOVING = 1; +const NUM_OBJECT_LAYERS = 2; + +function setupCollisionFiltering( settings ) { + + const objectFilter = new Jolt.ObjectLayerPairFilterTable( NUM_OBJECT_LAYERS ); + objectFilter.EnableCollision( LAYER_NON_MOVING, LAYER_MOVING ); + objectFilter.EnableCollision( LAYER_MOVING, LAYER_MOVING ); + + const BP_LAYER_NON_MOVING = new Jolt.BroadPhaseLayer( 0 ); + const BP_LAYER_MOVING = new Jolt.BroadPhaseLayer( 1 ); + const NUM_BROAD_PHASE_LAYERS = 2; + + const bpInterface = new Jolt.BroadPhaseLayerInterfaceTable( NUM_OBJECT_LAYERS, NUM_BROAD_PHASE_LAYERS ); + bpInterface.MapObjectToBroadPhaseLayer( LAYER_NON_MOVING, BP_LAYER_NON_MOVING ); + bpInterface.MapObjectToBroadPhaseLayer( LAYER_MOVING, BP_LAYER_MOVING ); + + settings.mObjectLayerPairFilter = objectFilter; + settings.mBroadPhaseLayerInterface = bpInterface; + settings.mObjectVsBroadPhaseLayerFilter = new Jolt.ObjectVsBroadPhaseLayerFilterTable( settings.mBroadPhaseLayerInterface, NUM_BROAD_PHASE_LAYERS, settings.mObjectLayerPairFilter, NUM_OBJECT_LAYERS ); + +} + +/** + * @classdesc Can be used to include Jolt as a Physics engine into + * `three.js` apps. The API can be initialized via: + * ```js + * const physics = await JoltPhysics(); + * ``` + * The component automatically imports Jolt from a CDN so make sure + * to use the component with an active Internet connection. + * + * @name JoltPhysics + * @class + * @hideconstructor + * @three_import import { JoltPhysics } from 'three/addons/physics/JoltPhysics.js'; + */ +async function JoltPhysics() { + + if ( Jolt === null ) { + + const { default: initJolt } = await import( `${JOLT_PATH}` ); + Jolt = await initJolt(); + + } + + const settings = new Jolt.JoltSettings(); + setupCollisionFiltering( settings ); + + const jolt = new Jolt.JoltInterface( settings ); + Jolt.destroy( settings ); + + const physicsSystem = jolt.GetPhysicsSystem(); + const bodyInterface = physicsSystem.GetBodyInterface(); + + const meshes = []; + const meshMap = new WeakMap(); + + const _position = new Vector3(); + const _quaternion = new Quaternion(); + const _scale = new Vector3( 1, 1, 1 ); + + const _matrix = new Matrix4(); + + function addScene( scene ) { + + scene.traverse( function ( child ) { + + if ( child.isMesh ) { + + const physics = child.userData.physics; + + if ( physics ) { + + addMesh( child, physics.mass, physics.restitution ); + + } + + } + + } ); + + } + + function addMesh( mesh, mass = 0, restitution = 0 ) { + + const shape = getShape( mesh.geometry ); + + if ( shape === null ) return; + + const body = mesh.isInstancedMesh + ? createInstancedBody( mesh, mass, restitution, shape ) + : createBody( mesh.position, mesh.quaternion, mass, restitution, shape ); + + if ( mass > 0 ) { + + meshes.push( mesh ); + meshMap.set( mesh, body ); + + } + + } + + function createInstancedBody( mesh, mass, restitution, shape ) { + + const array = mesh.instanceMatrix.array; + + const bodies = []; + + for ( let i = 0; i < mesh.count; i ++ ) { + + const position = _position.fromArray( array, i * 16 + 12 ); + const quaternion = _quaternion.setFromRotationMatrix( _matrix.fromArray( array, i * 16 ) ); // TODO Copilot did this + bodies.push( createBody( position, quaternion, mass, restitution, shape ) ); + + } + + return bodies; + + } + + function createBody( position, rotation, mass, restitution, shape ) { + + const pos = new Jolt.Vec3( position.x, position.y, position.z ); + const rot = new Jolt.Quat( rotation.x, rotation.y, rotation.z, rotation.w ); + + const motion = mass > 0 ? Jolt.EMotionType_Dynamic : Jolt.EMotionType_Static; + const layer = mass > 0 ? LAYER_MOVING : LAYER_NON_MOVING; + + const creationSettings = new Jolt.BodyCreationSettings( shape, pos, rot, motion, layer ); + creationSettings.mRestitution = restitution; + + const body = bodyInterface.CreateBody( creationSettings ); + + bodyInterface.AddBody( body.GetID(), Jolt.EActivation_Activate ); + + Jolt.destroy( creationSettings ); + + return body; + + } + + function setMeshPosition( mesh, position, index = 0 ) { + + if ( mesh.isInstancedMesh ) { + + const bodies = meshMap.get( mesh ); + + const body = bodies[ index ]; + + bodyInterface.RemoveBody( body.GetID() ); + bodyInterface.DestroyBody( body.GetID() ); + + const physics = mesh.userData.physics; + + const shape = body.GetShape(); + const body2 = createBody( position, { x: 0, y: 0, z: 0, w: 1 }, physics.mass, physics.restitution, shape ); + + bodies[ index ] = body2; + + } else { + + // TODO: Implement this + + } + + } + + function setMeshVelocity( mesh, velocity, index = 0 ) { + + /* + let body = meshMap.get( mesh ); + + if ( mesh.isInstancedMesh ) { + + body = body[ index ]; + + } + + body.setLinvel( velocity ); + */ + + } + + // + + const clock = new Clock(); + + function step() { + + let deltaTime = clock.getDelta(); + + // Don't go below 30 Hz to prevent spiral of death + deltaTime = Math.min( deltaTime, 1.0 / 30.0 ); + + // When running below 55 Hz, do 2 steps instead of 1 + const numSteps = deltaTime > 1.0 / 55.0 ? 2 : 1; + + // Step the physics world + jolt.Step( deltaTime, numSteps ); + + // + + for ( let i = 0, l = meshes.length; i < l; i ++ ) { + + const mesh = meshes[ i ]; + + if ( mesh.isInstancedMesh ) { + + const array = mesh.instanceMatrix.array; + const bodies = meshMap.get( mesh ); + + for ( let j = 0; j < bodies.length; j ++ ) { + + const body = bodies[ j ]; + + const position = body.GetPosition(); + const quaternion = body.GetRotation(); + + _position.set( position.GetX(), position.GetY(), position.GetZ() ); + _quaternion.set( quaternion.GetX(), quaternion.GetY(), quaternion.GetZ(), quaternion.GetW() ); + + _matrix.compose( _position, _quaternion, _scale ).toArray( array, j * 16 ); + + } + + mesh.instanceMatrix.needsUpdate = true; + mesh.computeBoundingSphere(); + + } else { + + const body = meshMap.get( mesh ); + + const position = body.GetPosition(); + const rotation = body.GetRotation(); + + mesh.position.set( position.GetX(), position.GetY(), position.GetZ() ); + mesh.quaternion.set( rotation.GetX(), rotation.GetY(), rotation.GetZ(), rotation.GetW() ); + + } + + } + + } + + // animate + + setInterval( step, 1000 / frameRate ); + + return { + /** + * Adds the given scene to this physics simulation. Only meshes with a + * `physics` object in their {@link Object3D#userData} field will be honored. + * The object can be used to store the mass and restitution of the mesh. E.g.: + * ```js + * box.userData.physics = { mass: 1, restitution: 0 }; + * ``` + * + * @method + * @name JoltPhysics#addScene + * @param {Object3D} scene The scene or any type of 3D object to add. + */ + addScene: addScene, + + /** + * Adds the given mesh to this physics simulation. + * + * @method + * @name JoltPhysics#addMesh + * @param {Mesh} mesh The mesh to add. + * @param {number} [mass=0] The mass in kg of the mesh. + * @param {number} [restitution=0] The restitution/friction of the mesh. + */ + addMesh: addMesh, + + /** + * Set the position of the given mesh which is part of the physics simulation. Calling this + * method will reset the current simulated velocity of the mesh. + * + * @method + * @name JoltPhysics#setMeshPosition + * @param {Mesh} mesh The mesh to update the position for. + * @param {Vector3} position - The new position. + * @param {number} [index=0] - If the mesh is instanced, the index represents the instanced ID. + */ + setMeshPosition: setMeshPosition, + + // NOOP + setMeshVelocity: setMeshVelocity + }; + +} + +export { JoltPhysics }; diff --git a/examples/jsm/physics/RapierPhysics.js b/examples/jsm/physics/RapierPhysics.js index 8f55433b606f09..83a5fc183b3e48 100644 --- a/examples/jsm/physics/RapierPhysics.js +++ b/examples/jsm/physics/RapierPhysics.js @@ -1,6 +1,6 @@ import { Clock, Vector3, Quaternion, Matrix4 } from 'three'; -const RAPIER_PATH = 'https://cdn.skypack.dev/@dimforge/rapier3d-compat@0.11.2'; +const RAPIER_PATH = 'https://cdn.skypack.dev/@dimforge/rapier3d-compat@0.17.3'; const frameRate = 60; @@ -9,7 +9,7 @@ const ZERO = new Vector3(); let RAPIER = null; -function getCollider( geometry ) { +function getShape( geometry ) { const parameters = geometry.parameters; @@ -28,22 +28,70 @@ function getCollider( geometry ) { const radius = parameters.radius !== undefined ? parameters.radius : 1; return RAPIER.ColliderDesc.ball( radius ); + } else if ( geometry.type === 'CylinderGeometry' ) { + + const radius = parameters.radiusBottom !== undefined ? parameters.radiusBottom : 0.5; + const length = parameters.height !== undefined ? parameters.height : 0.5; + + return RAPIER.ColliderDesc.cylinder( length / 2, radius ); + + } else if ( geometry.type === 'CapsuleGeometry' ) { + + const radius = parameters.radius !== undefined ? parameters.radius : 0.5; + const length = parameters.height !== undefined ? parameters.height : 0.5; + + return RAPIER.ColliderDesc.capsule( length / 2, radius ); + + } else if ( geometry.type === 'BufferGeometry' ) { + + const vertices = []; + const vertex = new Vector3(); + const position = geometry.getAttribute( 'position' ); + + for ( let i = 0; i < position.count; i ++ ) { + + vertex.fromBufferAttribute( position, i ); + vertices.push( vertex.x, vertex.y, vertex.z ); + + } + + // if the buffer is non-indexed, generate an index buffer + const indices = geometry.getIndex() === null + ? Uint32Array.from( Array( parseInt( vertices.length / 3 ) ).keys() ) + : geometry.getIndex().array; + + return RAPIER.ColliderDesc.trimesh( vertices, indices ); + } return null; } +/** + * @classdesc Can be used to include Rapier as a Physics engine into + * `three.js` apps. The API can be initialized via: + * ```js + * const physics = await RapierPhysics(); + * ``` + * The component automatically imports Rapier from a CDN so make sure + * to use the component with an active Internet connection. + * + * @name RapierPhysics + * @class + * @hideconstructor + * @three_import import { RapierPhysics } from 'three/addons/physics/RapierPhysics.js'; + */ async function RapierPhysics() { if ( RAPIER === null ) { - RAPIER = await import( RAPIER_PATH ); + RAPIER = await import( `${RAPIER_PATH}` ); await RAPIER.init(); } - // Docs: https://rapier.rs/docs/api/javascript/JavaScript3D/ + // Docs: https://rapier.rs/docs/api/javascript/JavaScript3D/ const gravity = new Vector3( 0.0, - 9.81, 0.0 ); const world = new RAPIER.World( gravity ); @@ -55,23 +103,69 @@ async function RapierPhysics() { const _quaternion = new Quaternion(); const _matrix = new Matrix4(); + function addScene( scene ) { + + scene.traverse( function ( child ) { + + if ( child.isMesh ) { + + const physics = child.userData.physics; + + if ( physics ) { + + addMesh( child, physics.mass, physics.restitution ); + + } + + } + + } ); + + } + function addMesh( mesh, mass = 0, restitution = 0 ) { - const shape = getCollider( mesh.geometry ); + const shape = getShape( mesh.geometry ); if ( shape === null ) return; shape.setMass( mass ); shape.setRestitution( restitution ); - const body = mesh.isInstancedMesh - ? createInstancedBody( mesh, mass, shape ) - : createBody( mesh.position, mesh.quaternion, mass, shape ); + const { body, collider } = mesh.isInstancedMesh + ? createInstancedBody( mesh, mass, shape ) + : createBody( mesh.position, mesh.quaternion, mass, shape ); + + if ( ! mesh.userData.physics ) mesh.userData.physics = {}; + + mesh.userData.physics.body = body; + mesh.userData.physics.collider = collider; if ( mass > 0 ) { meshes.push( mesh ); - meshMap.set( mesh, body ); + meshMap.set( mesh, { body, collider } ); + + } + + } + + function removeMesh( mesh ) { + + const index = meshes.indexOf( mesh ); + + if ( index !== - 1 ) { + + meshes.splice( index, 1 ); + meshMap.delete( mesh ); + + if ( ! mesh.userData.physics ) return; + + const body = mesh.userData.physics.body; + const collider = mesh.userData.physics.collider; + + if ( body ) removeBody( body ); + if ( collider ) removeCollider( collider ); } @@ -82,15 +176,18 @@ async function RapierPhysics() { const array = mesh.instanceMatrix.array; const bodies = []; + const colliders = []; for ( let i = 0; i < mesh.count; i ++ ) { const position = _vector.fromArray( array, i * 16 + 12 ); - bodies.push( createBody( position, null, mass, shape ) ); + const { body, collider } = createBody( position, null, mass, shape ); + bodies.push( body ); + colliders.push( collider ); } - return bodies; + return { body: bodies, collider: colliders }; } @@ -101,15 +198,51 @@ async function RapierPhysics() { if ( quaternion !== null ) desc.setRotation( quaternion ); const body = world.createRigidBody( desc ); - world.createCollider( shape, body ); + const collider = world.createCollider( shape, body ); - return body; + return { body, collider }; + + } + + function removeBody( body ) { + + if ( Array.isArray( body ) ) { + + for ( let i = 0; i < body.length; i ++ ) { + + world.removeRigidBody( body[ i ] ); + + } + + } else { + + world.removeRigidBody( body ); + + } + + } + + function removeCollider( collider ) { + + if ( Array.isArray( collider ) ) { + + for ( let i = 0; i < collider.length; i ++ ) { + + world.removeCollider( collider[ i ] ); + + } + + } else { + + world.removeCollider( collider ); + + } } function setMeshPosition( mesh, position, index = 0 ) { - let body = meshMap.get( mesh ); + let { body } = meshMap.get( mesh ); if ( mesh.isInstancedMesh ) { @@ -125,7 +258,7 @@ async function RapierPhysics() { function setMeshVelocity( mesh, velocity, index = 0 ) { - let body = meshMap.get( mesh ); + let { body } = meshMap.get( mesh ); if ( mesh.isInstancedMesh ) { @@ -137,6 +270,24 @@ async function RapierPhysics() { } + function addHeightfield( mesh, width, depth, heights, scale ) { + + const shape = RAPIER.ColliderDesc.heightfield( width, depth, heights, scale ); + + const bodyDesc = RAPIER.RigidBodyDesc.fixed(); + bodyDesc.setTranslation( mesh.position.x, mesh.position.y, mesh.position.z ); + bodyDesc.setRotation( mesh.quaternion ); + + const body = world.createRigidBody( bodyDesc ); + world.createCollider( shape, body ); + + if ( ! mesh.userData.physics ) mesh.userData.physics = {}; + mesh.userData.physics.body = body; + + return body; + + } + // const clock = new Clock(); @@ -155,7 +306,7 @@ async function RapierPhysics() { if ( mesh.isInstancedMesh ) { const array = mesh.instanceMatrix.array; - const bodies = meshMap.get( mesh ); + const { body: bodies } = meshMap.get( mesh ); for ( let j = 0; j < bodies.length; j ++ ) { @@ -173,7 +324,7 @@ async function RapierPhysics() { } else { - const body = meshMap.get( mesh ); + const { body } = meshMap.get( mesh ); mesh.position.copy( body.translation() ); mesh.quaternion.copy( body.rotation() ); @@ -189,9 +340,82 @@ async function RapierPhysics() { setInterval( step, 1000 / frameRate ); return { + RAPIER, + world, + /** + * Adds the given scene to this physics simulation. Only meshes with a + * `physics` object in their {@link Object3D#userData} field will be honored. + * The object can be used to store the mass and restitution of the mesh. E.g.: + * ```js + * box.userData.physics = { mass: 1, restitution: 0 }; + * ``` + * + * @method + * @name RapierPhysics#addScene + * @param {Object3D} scene The scene or any type of 3D object to add. + */ + addScene: addScene, + + /** + * Adds the given mesh to this physics simulation. + * + * @method + * @name RapierPhysics#addMesh + * @param {Mesh} mesh The mesh to add. + * @param {number} [mass=0] The mass in kg of the mesh. + * @param {number} [restitution=0] The restitution/friction of the mesh. + */ addMesh: addMesh, + + /** + * Removes the given mesh from this physics simulation. + * + * @method + * @name RapierPhysics#removeMesh + * @param {Mesh} mesh The mesh to remove. + */ + removeMesh: removeMesh, + + /** + * Set the position of the given mesh which is part of the physics simulation. Calling this + * method will reset the current simulated velocity of the mesh. + * + * @method + * @name RapierPhysics#setMeshPosition + * @param {Mesh} mesh The mesh to update the position for. + * @param {Vector3} position - The new position. + * @param {number} [index=0] - If the mesh is instanced, the index represents the instanced ID. + */ setMeshPosition: setMeshPosition, - setMeshVelocity: setMeshVelocity + + /** + * Set the velocity of the given mesh which is part of the physics simulation. + * + * @method + * @name RapierPhysics#setMeshVelocity + * @param {Mesh} mesh The mesh to update the velocity for. + * @param {Vector3} velocity - The new velocity. + * @param {number} [index=0] - If the mesh is instanced, the index represents the instanced ID. + */ + setMeshVelocity: setMeshVelocity, + + /** + * Adds a heightfield terrain to the physics simulation. + * + * @method + * @name RapierPhysics#addHeightfield + * @param {Mesh} mesh - The Three.js mesh representing the terrain. + * @param {number} width - The number of vertices along the width (x-axis) of the heightfield. + * @param {number} depth - The number of vertices along the depth (z-axis) of the heightfield. + * @param {Float32Array} heights - Array of height values for each vertex in the heightfield. + * @param {Object} scale - Scale factors for the heightfield dimensions. + * @param {number} scale.x - Scale factor for width. + * @param {number} scale.y - Scale factor for height. + * @param {number} scale.z - Scale factor for depth. + * @returns {RigidBody} The created Rapier rigid body for the heightfield. + */ + addHeightfield: addHeightfield + }; } diff --git a/examples/jsm/postprocessing/AdaptiveToneMappingPass.js b/examples/jsm/postprocessing/AdaptiveToneMappingPass.js deleted file mode 100644 index 503536aef9e7e0..00000000000000 --- a/examples/jsm/postprocessing/AdaptiveToneMappingPass.js +++ /dev/null @@ -1,361 +0,0 @@ -import { - LinearMipmapLinearFilter, - MeshBasicMaterial, - NoBlending, - ShaderMaterial, - UniformsUtils, - WebGLRenderTarget -} from 'three'; -import { Pass, FullScreenQuad } from './Pass.js'; -import { CopyShader } from '../shaders/CopyShader.js'; -import { LuminosityShader } from '../shaders/LuminosityShader.js'; -import { ToneMapShader } from '../shaders/ToneMapShader.js'; - -/** - * Generate a texture that represents the luminosity of the current scene, adapted over time - * to simulate the optic nerve responding to the amount of light it is receiving. - * Based on a GDC2007 presentation by Wolfgang Engel titled "Post-Processing Pipeline" - * - * Full-screen tone-mapping shader based on http://www.graphics.cornell.edu/~jaf/publications/sig02_paper.pdf - */ - -class AdaptiveToneMappingPass extends Pass { - - constructor( adaptive, resolution ) { - - super(); - - this.resolution = ( resolution !== undefined ) ? resolution : 256; - this.needsInit = true; - this.adaptive = adaptive !== undefined ? !! adaptive : true; - - this.luminanceRT = null; - this.previousLuminanceRT = null; - this.currentLuminanceRT = null; - - const copyShader = CopyShader; - - this.copyUniforms = UniformsUtils.clone( copyShader.uniforms ); - - this.materialCopy = new ShaderMaterial( { - - uniforms: this.copyUniforms, - vertexShader: copyShader.vertexShader, - fragmentShader: copyShader.fragmentShader, - blending: NoBlending, - depthTest: false - - } ); - - this.materialLuminance = new ShaderMaterial( { - - uniforms: UniformsUtils.clone( LuminosityShader.uniforms ), - vertexShader: LuminosityShader.vertexShader, - fragmentShader: LuminosityShader.fragmentShader, - blending: NoBlending - } ); - - this.adaptLuminanceShader = { - defines: { - 'MIP_LEVEL_1X1': ( Math.log( this.resolution ) / Math.log( 2.0 ) ).toFixed( 1 ) - }, - uniforms: { - 'lastLum': { value: null }, - 'currentLum': { value: null }, - 'minLuminance': { value: 0.01 }, - 'delta': { value: 0.016 }, - 'tau': { value: 1.0 } - }, - vertexShader: - `varying vec2 vUv; - - void main() { - - vUv = uv; - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - - }`, - - fragmentShader: - `varying vec2 vUv; - - uniform sampler2D lastLum; - uniform sampler2D currentLum; - uniform float minLuminance; - uniform float delta; - uniform float tau; - - void main() { - - vec4 lastLum = texture2D( lastLum, vUv, MIP_LEVEL_1X1 ); - vec4 currentLum = texture2D( currentLum, vUv, MIP_LEVEL_1X1 ); - - float fLastLum = max( minLuminance, lastLum.r ); - float fCurrentLum = max( minLuminance, currentLum.r ); - - //The adaption seems to work better in extreme lighting differences - //if the input luminance is squared. - fCurrentLum *= fCurrentLum; - - // Adapt the luminance using Pattanaik's technique - float fAdaptedLum = fLastLum + (fCurrentLum - fLastLum) * (1.0 - exp(-delta * tau)); - // "fAdaptedLum = sqrt(fAdaptedLum); - gl_FragColor.r = fAdaptedLum; - }` - - }; - - this.materialAdaptiveLum = new ShaderMaterial( { - - uniforms: UniformsUtils.clone( this.adaptLuminanceShader.uniforms ), - vertexShader: this.adaptLuminanceShader.vertexShader, - fragmentShader: this.adaptLuminanceShader.fragmentShader, - defines: Object.assign( {}, this.adaptLuminanceShader.defines ), - blending: NoBlending - } ); - - this.materialToneMap = new ShaderMaterial( { - - uniforms: UniformsUtils.clone( ToneMapShader.uniforms ), - vertexShader: ToneMapShader.vertexShader, - fragmentShader: ToneMapShader.fragmentShader, - blending: NoBlending - } ); - - this.fsQuad = new FullScreenQuad( null ); - - } - - render( renderer, writeBuffer, readBuffer, deltaTime/*, maskActive*/ ) { - - if ( this.needsInit ) { - - this.reset( renderer ); - - this.luminanceRT.texture.type = readBuffer.texture.type; - this.previousLuminanceRT.texture.type = readBuffer.texture.type; - this.currentLuminanceRT.texture.type = readBuffer.texture.type; - this.needsInit = false; - - } - - if ( this.adaptive ) { - - //Render the luminance of the current scene into a render target with mipmapping enabled - this.fsQuad.material = this.materialLuminance; - this.materialLuminance.uniforms.tDiffuse.value = readBuffer.texture; - renderer.setRenderTarget( this.currentLuminanceRT ); - this.fsQuad.render( renderer ); - - //Use the new luminance values, the previous luminance and the frame delta to - //adapt the luminance over time. - this.fsQuad.material = this.materialAdaptiveLum; - this.materialAdaptiveLum.uniforms.delta.value = deltaTime; - this.materialAdaptiveLum.uniforms.lastLum.value = this.previousLuminanceRT.texture; - this.materialAdaptiveLum.uniforms.currentLum.value = this.currentLuminanceRT.texture; - renderer.setRenderTarget( this.luminanceRT ); - this.fsQuad.render( renderer ); - - //Copy the new adapted luminance value so that it can be used by the next frame. - this.fsQuad.material = this.materialCopy; - this.copyUniforms.tDiffuse.value = this.luminanceRT.texture; - renderer.setRenderTarget( this.previousLuminanceRT ); - this.fsQuad.render( renderer ); - - } - - this.fsQuad.material = this.materialToneMap; - this.materialToneMap.uniforms.tDiffuse.value = readBuffer.texture; - - if ( this.renderToScreen ) { - - renderer.setRenderTarget( null ); - this.fsQuad.render( renderer ); - - } else { - - renderer.setRenderTarget( writeBuffer ); - - if ( this.clear ) renderer.clear(); - - this.fsQuad.render( renderer ); - - } - - } - - reset() { - - // render targets - if ( this.luminanceRT ) { - - this.luminanceRT.dispose(); - - } - - if ( this.currentLuminanceRT ) { - - this.currentLuminanceRT.dispose(); - - } - - if ( this.previousLuminanceRT ) { - - this.previousLuminanceRT.dispose(); - - } - - - this.luminanceRT = new WebGLRenderTarget( this.resolution, this.resolution ); - this.luminanceRT.texture.name = 'AdaptiveToneMappingPass.l'; - this.luminanceRT.texture.generateMipmaps = false; - - this.previousLuminanceRT = new WebGLRenderTarget( this.resolution, this.resolution ); - this.previousLuminanceRT.texture.name = 'AdaptiveToneMappingPass.pl'; - this.previousLuminanceRT.texture.generateMipmaps = false; - - // We only need mipmapping for the current luminosity because we want a down-sampled version to sample in our adaptive shader - - const pars = { minFilter: LinearMipmapLinearFilter, generateMipmaps: true }; - - this.currentLuminanceRT = new WebGLRenderTarget( this.resolution, this.resolution, pars ); - this.currentLuminanceRT.texture.name = 'AdaptiveToneMappingPass.cl'; - - if ( this.adaptive ) { - - this.materialToneMap.defines[ 'ADAPTED_LUMINANCE' ] = ''; - this.materialToneMap.uniforms.luminanceMap.value = this.luminanceRT.texture; - - } - - //Put something in the adaptive luminance texture so that the scene can render initially - this.fsQuad.material = new MeshBasicMaterial( { color: 0x777777 } ); - this.materialLuminance.needsUpdate = true; - this.materialAdaptiveLum.needsUpdate = true; - this.materialToneMap.needsUpdate = true; - // renderer.render( this.scene, this.camera, this.luminanceRT ); - // renderer.render( this.scene, this.camera, this.previousLuminanceRT ); - // renderer.render( this.scene, this.camera, this.currentLuminanceRT ); - - } - - setAdaptive( adaptive ) { - - if ( adaptive ) { - - this.adaptive = true; - this.materialToneMap.defines[ 'ADAPTED_LUMINANCE' ] = ''; - this.materialToneMap.uniforms.luminanceMap.value = this.luminanceRT.texture; - - } else { - - this.adaptive = false; - delete this.materialToneMap.defines[ 'ADAPTED_LUMINANCE' ]; - this.materialToneMap.uniforms.luminanceMap.value = null; - - } - - this.materialToneMap.needsUpdate = true; - - } - - setAdaptionRate( rate ) { - - if ( rate ) { - - this.materialAdaptiveLum.uniforms.tau.value = Math.abs( rate ); - - } - - } - - setMinLuminance( minLum ) { - - if ( minLum ) { - - this.materialToneMap.uniforms.minLuminance.value = minLum; - this.materialAdaptiveLum.uniforms.minLuminance.value = minLum; - - } - - } - - setMaxLuminance( maxLum ) { - - if ( maxLum ) { - - this.materialToneMap.uniforms.maxLuminance.value = maxLum; - - } - - } - - setAverageLuminance( avgLum ) { - - if ( avgLum ) { - - this.materialToneMap.uniforms.averageLuminance.value = avgLum; - - } - - } - - setMiddleGrey( middleGrey ) { - - if ( middleGrey ) { - - this.materialToneMap.uniforms.middleGrey.value = middleGrey; - - } - - } - - dispose() { - - if ( this.luminanceRT ) { - - this.luminanceRT.dispose(); - - } - - if ( this.previousLuminanceRT ) { - - this.previousLuminanceRT.dispose(); - - } - - if ( this.currentLuminanceRT ) { - - this.currentLuminanceRT.dispose(); - - } - - if ( this.materialLuminance ) { - - this.materialLuminance.dispose(); - - } - - if ( this.materialAdaptiveLum ) { - - this.materialAdaptiveLum.dispose(); - - } - - if ( this.materialCopy ) { - - this.materialCopy.dispose(); - - } - - if ( this.materialToneMap ) { - - this.materialToneMap.dispose(); - - } - - } - -} - -export { AdaptiveToneMappingPass }; diff --git a/examples/jsm/postprocessing/AfterimagePass.js b/examples/jsm/postprocessing/AfterimagePass.js index a64405c801376f..b770622c62dc19 100644 --- a/examples/jsm/postprocessing/AfterimagePass.js +++ b/examples/jsm/postprocessing/AfterimagePass.js @@ -1,62 +1,136 @@ import { - MeshBasicMaterial, + HalfFloatType, NearestFilter, + NoBlending, ShaderMaterial, UniformsUtils, WebGLRenderTarget } from 'three'; import { Pass, FullScreenQuad } from './Pass.js'; +import { CopyShader } from '../shaders/CopyShader.js'; import { AfterimageShader } from '../shaders/AfterimageShader.js'; +/** + * Pass for a basic after image effect. + * + * ```js + * const afterimagePass = new AfterimagePass( 0.9 ); + * composer.addPass( afterimagePass ); + * ``` + * + * @augments Pass + * @three_import import { AfterimagePass } from 'three/addons/postprocessing/AfterimagePass.js'; + */ class AfterimagePass extends Pass { + /** + * Constructs a new after image pass. + * + * @param {number} [damp=0.96] - The damping intensity. A higher value means a stronger after image effect. + */ constructor( damp = 0.96 ) { super(); - this.shader = AfterimageShader; + /** + * The pass uniforms. Use this object if you want to update the + * `damp` value at runtime. + * ```js + * pass.uniforms.damp.value = 0.9; + * ``` + * + * @type {Object} + */ + this.uniforms = UniformsUtils.clone( AfterimageShader.uniforms ); + + this.damp = damp; + + /** + * The composition material. + * + * @type {ShaderMaterial} + */ + this.compFsMaterial = new ShaderMaterial( { - this.uniforms = UniformsUtils.clone( this.shader.uniforms ); + uniforms: this.uniforms, + vertexShader: AfterimageShader.vertexShader, + fragmentShader: AfterimageShader.fragmentShader - this.uniforms[ 'damp' ].value = damp; + } ); - this.textureComp = new WebGLRenderTarget( window.innerWidth, window.innerHeight, { + /** + * The copy material. + * + * @type {ShaderMaterial} + */ + this.copyFsMaterial = new ShaderMaterial( { + uniforms: UniformsUtils.clone( CopyShader.uniforms ), + vertexShader: CopyShader.vertexShader, + fragmentShader: CopyShader.fragmentShader, + blending: NoBlending, + depthTest: false, + depthWrite: false + } ); + + // internals + + this._textureComp = new WebGLRenderTarget( window.innerWidth, window.innerHeight, { magFilter: NearestFilter, + type: HalfFloatType } ); - this.textureOld = new WebGLRenderTarget( window.innerWidth, window.innerHeight, { + this._textureOld = new WebGLRenderTarget( window.innerWidth, window.innerHeight, { magFilter: NearestFilter, + type: HalfFloatType } ); - this.compFsMaterial = new ShaderMaterial( { + this._compFsQuad = new FullScreenQuad( this.compFsMaterial ); + this._copyFsQuad = new FullScreenQuad( this.copyFsMaterial ); - uniforms: this.uniforms, - vertexShader: this.shader.vertexShader, - fragmentShader: this.shader.fragmentShader + } - } ); + /** + * The damping intensity, from 0.0 to 1.0. A higher value means a stronger after image effect. + * + * @type {number} + */ + get damp() { + + return this.uniforms[ 'damp' ].value; + + } - this.compFsQuad = new FullScreenQuad( this.compFsMaterial ); + set damp( value ) { - this.copyFsMaterial = new MeshBasicMaterial(); - this.copyFsQuad = new FullScreenQuad( this.copyFsMaterial ); + this.uniforms[ 'damp' ].value = value; } + /** + * Performs the after image pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer/*, deltaTime, maskActive*/ ) { - this.uniforms[ 'tOld' ].value = this.textureOld.texture; + this.uniforms[ 'tOld' ].value = this._textureOld.texture; this.uniforms[ 'tNew' ].value = readBuffer.texture; - renderer.setRenderTarget( this.textureComp ); - this.compFsQuad.render( renderer ); + renderer.setRenderTarget( this._textureComp ); + this._compFsQuad.render( renderer ); - this.copyFsQuad.material.map = this.textureComp.texture; + this._copyFsQuad.material.uniforms.tDiffuse.value = this._textureComp.texture; if ( this.renderToScreen ) { renderer.setRenderTarget( null ); - this.copyFsQuad.render( renderer ); + this._copyFsQuad.render( renderer ); } else { @@ -64,35 +138,45 @@ class AfterimagePass extends Pass { if ( this.clear ) renderer.clear(); - this.copyFsQuad.render( renderer ); + this._copyFsQuad.render( renderer ); } // Swap buffers. - const temp = this.textureOld; - this.textureOld = this.textureComp; - this.textureComp = temp; + const temp = this._textureOld; + this._textureOld = this._textureComp; + this._textureComp = temp; // Now textureOld contains the latest image, ready for the next frame. } + /** + * Sets the size of the pass. + * + * @param {number} width - The width to set. + * @param {number} height - The height to set. + */ setSize( width, height ) { - this.textureComp.setSize( width, height ); - this.textureOld.setSize( width, height ); + this._textureComp.setSize( width, height ); + this._textureOld.setSize( width, height ); } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ dispose() { - this.textureComp.dispose(); - this.textureOld.dispose(); + this._textureComp.dispose(); + this._textureOld.dispose(); this.compFsMaterial.dispose(); this.copyFsMaterial.dispose(); - this.compFsQuad.dispose(); - this.copyFsQuad.dispose(); + this._compFsQuad.dispose(); + this._copyFsQuad.dispose(); } diff --git a/examples/jsm/postprocessing/BloomPass.js b/examples/jsm/postprocessing/BloomPass.js index 949f4c157e107d..5394a43ea04f23 100644 --- a/examples/jsm/postprocessing/BloomPass.js +++ b/examples/jsm/postprocessing/BloomPass.js @@ -1,5 +1,6 @@ import { AdditiveBlending, + HalfFloatType, ShaderMaterial, UniformsUtils, Vector2, @@ -8,27 +9,51 @@ import { import { Pass, FullScreenQuad } from './Pass.js'; import { ConvolutionShader } from '../shaders/ConvolutionShader.js'; +/** + * A pass for a basic Bloom effect. + * + * {@link UnrealBloomPass} produces a more advanced Bloom but is also + * more expensive. + * + * ```js + * const effectBloom = new BloomPass( 0.75 ); + * composer.addPass( effectBloom ); + * ``` + * + * @augments Pass + * @three_import import { BloomPass } from 'three/addons/postprocessing/BloomPass.js'; + */ class BloomPass extends Pass { + /** + * Constructs a new Bloom pass. + * + * @param {number} [strength=1] - The Bloom strength. + * @param {number} [kernelSize=25] - The kernel size. + * @param {number} [sigma=4] - The sigma. + */ constructor( strength = 1, kernelSize = 25, sigma = 4 ) { super(); - // render targets - - this.renderTargetX = new WebGLRenderTarget(); // will be resized later - this.renderTargetX.texture.name = 'BloomPass.x'; - this.renderTargetY = new WebGLRenderTarget(); // will be resized later - this.renderTargetY.texture.name = 'BloomPass.y'; - // combine material + /** + * The combine pass uniforms. + * + * @type {Object} + */ this.combineUniforms = UniformsUtils.clone( CombineShader.uniforms ); - this.combineUniforms[ 'strength' ].value = strength; + /** + * The combine pass material. + * + * @type {ShaderMaterial} + */ this.materialCombine = new ShaderMaterial( { + name: CombineShader.name, uniforms: this.combineUniforms, vertexShader: CombineShader.vertexShader, fragmentShader: CombineShader.fragmentShader, @@ -41,13 +66,24 @@ class BloomPass extends Pass { const convolutionShader = ConvolutionShader; + /** + * The convolution pass uniforms. + * + * @type {Object} + */ this.convolutionUniforms = UniformsUtils.clone( convolutionShader.uniforms ); this.convolutionUniforms[ 'uImageIncrement' ].value = BloomPass.blurX; - this.convolutionUniforms[ 'cKernel' ].value = ConvolutionShader.buildKernel( sigma ); + this.convolutionUniforms[ 'cKernel' ].value = buildKernel( sigma ); + /** + * The convolution pass material. + * + * @type {ShaderMaterial} + */ this.materialConvolution = new ShaderMaterial( { + name: convolutionShader.name, uniforms: this.convolutionUniforms, vertexShader: convolutionShader.vertexShader, fragmentShader: convolutionShader.fragmentShader, @@ -58,67 +94,101 @@ class BloomPass extends Pass { } ); + /** + * Overwritten to disable the swap. + * + * @type {boolean} + * @default false + */ this.needsSwap = false; - this.fsQuad = new FullScreenQuad( null ); + // internals + + this._renderTargetX = new WebGLRenderTarget( 1, 1, { type: HalfFloatType } ); // will be resized later + this._renderTargetX.texture.name = 'BloomPass.x'; + this._renderTargetY = new WebGLRenderTarget( 1, 1, { type: HalfFloatType } ); // will be resized later + this._renderTargetY.texture.name = 'BloomPass.y'; + + this._fsQuad = new FullScreenQuad( null ); } + /** + * Performs the Bloom pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer, deltaTime, maskActive ) { if ( maskActive ) renderer.state.buffers.stencil.setTest( false ); - // Render quad with blured scene into texture (convolution pass 1) + // Render quad with blurred scene into texture (convolution pass 1) - this.fsQuad.material = this.materialConvolution; + this._fsQuad.material = this.materialConvolution; this.convolutionUniforms[ 'tDiffuse' ].value = readBuffer.texture; this.convolutionUniforms[ 'uImageIncrement' ].value = BloomPass.blurX; - renderer.setRenderTarget( this.renderTargetX ); + renderer.setRenderTarget( this._renderTargetX ); renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); - // Render quad with blured scene into texture (convolution pass 2) + // Render quad with blurred scene into texture (convolution pass 2) - this.convolutionUniforms[ 'tDiffuse' ].value = this.renderTargetX.texture; + this.convolutionUniforms[ 'tDiffuse' ].value = this._renderTargetX.texture; this.convolutionUniforms[ 'uImageIncrement' ].value = BloomPass.blurY; - renderer.setRenderTarget( this.renderTargetY ); + renderer.setRenderTarget( this._renderTargetY ); renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); // Render original scene with superimposed blur to texture - this.fsQuad.material = this.materialCombine; + this._fsQuad.material = this.materialCombine; - this.combineUniforms[ 'tDiffuse' ].value = this.renderTargetY.texture; + this.combineUniforms[ 'tDiffuse' ].value = this._renderTargetY.texture; if ( maskActive ) renderer.state.buffers.stencil.setTest( true ); renderer.setRenderTarget( readBuffer ); if ( this.clear ) renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } + /** + * Sets the size of the pass. + * + * @param {number} width - The width to set. + * @param {number} height - The height to set. + */ setSize( width, height ) { - this.renderTargetX.setSize( width, height ); - this.renderTargetY.setSize( width, height ); + this._renderTargetX.setSize( width, height ); + this._renderTargetY.setSize( width, height ); } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ dispose() { - this.renderTargetX.dispose(); - this.renderTargetY.dispose(); + this._renderTargetX.dispose(); + this._renderTargetY.dispose(); this.materialCombine.dispose(); this.materialConvolution.dispose(); - this.fsQuad.dispose(); + this._fsQuad.dispose(); } @@ -126,6 +196,8 @@ class BloomPass extends Pass { const CombineShader = { + name: 'CombineShader', + uniforms: { 'tDiffuse': { value: null }, @@ -164,4 +236,39 @@ const CombineShader = { BloomPass.blurX = new Vector2( 0.001953125, 0.0 ); BloomPass.blurY = new Vector2( 0.0, 0.001953125 ); + +function gauss( x, sigma ) { + + return Math.exp( - ( x * x ) / ( 2.0 * sigma * sigma ) ); + +} + +function buildKernel( sigma ) { + + // We loop off the sqrt(2 * pi) * sigma term, since we're going to normalize anyway. + + const kMaxKernelSize = 25; + let kernelSize = 2 * Math.ceil( sigma * 3.0 ) + 1; + + if ( kernelSize > kMaxKernelSize ) kernelSize = kMaxKernelSize; + + const halfWidth = ( kernelSize - 1 ) * 0.5; + + const values = new Array( kernelSize ); + let sum = 0.0; + for ( let i = 0; i < kernelSize; ++ i ) { + + values[ i ] = gauss( i - halfWidth, sigma ); + sum += values[ i ]; + + } + + // normalize the kernel + + for ( let i = 0; i < kernelSize; ++ i ) values[ i ] /= sum; + + return values; + +} + export { BloomPass }; diff --git a/examples/jsm/postprocessing/BokehPass.js b/examples/jsm/postprocessing/BokehPass.js index 0e5cd4ef19c2e6..3bc06db46f5305 100644 --- a/examples/jsm/postprocessing/BokehPass.js +++ b/examples/jsm/postprocessing/BokehPass.js @@ -1,5 +1,6 @@ import { Color, + HalfFloatType, MeshDepthMaterial, NearestFilter, NoBlending, @@ -12,73 +13,130 @@ import { Pass, FullScreenQuad } from './Pass.js'; import { BokehShader } from '../shaders/BokehShader.js'; /** - * Depth-of-field post-process with bokeh shader + * Pass for creating depth of field (DOF) effect. + * + * ```js + * const bokehPass = new BokehPass( scene, camera, { + * focus: 500 + * aperture: 5, + * maxblur: 0.01 + * } ); + * composer.addPass( bokehPass ); + * ``` + * + * @augments Pass + * @three_import import { BokehPass } from 'three/addons/postprocessing/BokehPass.js'; */ - class BokehPass extends Pass { + /** + * Constructs a new Bokeh pass. + * + * @param {Scene} scene - The scene to render the DOF for. + * @param {Camera} camera - The camera. + * @param {BokehPass~Options} params - The pass options. + */ constructor( scene, camera, params ) { super(); + /** + * The scene to render the DOF for. + * + * @type {Scene} + */ this.scene = scene; + + /** + * The camera. + * + * @type {Camera} + */ this.camera = camera; const focus = ( params.focus !== undefined ) ? params.focus : 1.0; - const aspect = ( params.aspect !== undefined ) ? params.aspect : camera.aspect; const aperture = ( params.aperture !== undefined ) ? params.aperture : 0.025; const maxblur = ( params.maxblur !== undefined ) ? params.maxblur : 1.0; // render targets - this.renderTargetDepth = new WebGLRenderTarget( 1, 1, { // will be resized later + this._renderTargetDepth = new WebGLRenderTarget( 1, 1, { // will be resized later minFilter: NearestFilter, - magFilter: NearestFilter + magFilter: NearestFilter, + type: HalfFloatType } ); - this.renderTargetDepth.texture.name = 'BokehPass.depth'; + this._renderTargetDepth.texture.name = 'BokehPass.depth'; // depth material - this.materialDepth = new MeshDepthMaterial(); - this.materialDepth.depthPacking = RGBADepthPacking; - this.materialDepth.blending = NoBlending; + this._materialDepth = new MeshDepthMaterial(); + this._materialDepth.depthPacking = RGBADepthPacking; + this._materialDepth.blending = NoBlending; // bokeh material - const bokehShader = BokehShader; - const bokehUniforms = UniformsUtils.clone( bokehShader.uniforms ); + const bokehUniforms = UniformsUtils.clone( BokehShader.uniforms ); - bokehUniforms[ 'tDepth' ].value = this.renderTargetDepth.texture; + bokehUniforms[ 'tDepth' ].value = this._renderTargetDepth.texture; bokehUniforms[ 'focus' ].value = focus; - bokehUniforms[ 'aspect' ].value = aspect; + bokehUniforms[ 'aspect' ].value = camera.aspect; bokehUniforms[ 'aperture' ].value = aperture; bokehUniforms[ 'maxblur' ].value = maxblur; bokehUniforms[ 'nearClip' ].value = camera.near; bokehUniforms[ 'farClip' ].value = camera.far; + /** + * The pass bokeh material. + * + * @type {ShaderMaterial} + */ this.materialBokeh = new ShaderMaterial( { - defines: Object.assign( {}, bokehShader.defines ), + defines: Object.assign( {}, BokehShader.defines ), uniforms: bokehUniforms, - vertexShader: bokehShader.vertexShader, - fragmentShader: bokehShader.fragmentShader + vertexShader: BokehShader.vertexShader, + fragmentShader: BokehShader.fragmentShader } ); + /** + * The pass uniforms. Use this object if you want to update the + * `focus`, `aperture` or `maxblur` values at runtime. + * + * ```js + * pass.uniforms.focus.value = focus; + * pass.uniforms.aperture.value = aperture; + * pass.uniforms.maxblur.value = maxblur; + * ``` + * + * @type {Object} + */ this.uniforms = bokehUniforms; - this.needsSwap = false; - this.fsQuad = new FullScreenQuad( this.materialBokeh ); + // internals + + this._fsQuad = new FullScreenQuad( this.materialBokeh ); this._oldClearColor = new Color(); } + /** + * Performs the Bokeh pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer/*, deltaTime, maskActive*/ ) { // Render depth into texture - this.scene.overrideMaterial = this.materialDepth; + this.scene.overrideMaterial = this._materialDepth; renderer.getClearColor( this._oldClearColor ); const oldClearAlpha = renderer.getClearAlpha(); @@ -87,7 +145,7 @@ class BokehPass extends Pass { renderer.setClearColor( 0xffffff ); renderer.setClearAlpha( 1.0 ); - renderer.setRenderTarget( this.renderTargetDepth ); + renderer.setRenderTarget( this._renderTargetDepth ); renderer.clear(); renderer.render( this.scene, this.camera ); @@ -100,13 +158,13 @@ class BokehPass extends Pass { if ( this.renderToScreen ) { renderer.setRenderTarget( null ); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } else { renderer.setRenderTarget( writeBuffer ); renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } @@ -117,23 +175,44 @@ class BokehPass extends Pass { } + /** + * Sets the size of the pass. + * + * @param {number} width - The width to set. + * @param {number} height - The height to set. + */ setSize( width, height ) { - this.renderTargetDepth.setSize( width, height ); + this.materialBokeh.uniforms[ 'aspect' ].value = width / height; + + this._renderTargetDepth.setSize( width, height ); } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ dispose() { - this.renderTargetDepth.dispose(); + this._renderTargetDepth.dispose(); - this.materialDepth.dispose(); + this._materialDepth.dispose(); this.materialBokeh.dispose(); - this.fsQuad.dispose(); + this._fsQuad.dispose(); } } +/** + * Constructor options of `BokehPass`. + * + * @typedef {Object} BokehPass~Options + * @property {number} [focus=1] - Defines the effect's focus which is the distance along the camera's look direction in world units. + * @property {number} [aperture=0.025] - Defines the effect's aperture. + * @property {number} [maxblur=1] - Defines the effect's maximum blur. + **/ + export { BokehPass }; diff --git a/examples/jsm/postprocessing/ClearPass.js b/examples/jsm/postprocessing/ClearPass.js index eac2ec756a034f..9b1cc0e1cce8ec 100644 --- a/examples/jsm/postprocessing/ClearPass.js +++ b/examples/jsm/postprocessing/ClearPass.js @@ -3,20 +3,71 @@ import { } from 'three'; import { Pass } from './Pass.js'; +/** + * This class can be used to force a clear operation for the current read or + * default framebuffer (when rendering to screen). + * + * ```js + * const clearPass = new ClearPass(); + * composer.addPass( clearPass ); + * ``` + * + * @augments Pass + * @three_import import { ClearPass } from 'three/addons/postprocessing/ClearPass.js'; + */ class ClearPass extends Pass { - constructor( clearColor, clearAlpha ) { + /** + * Constructs a new clear pass. + * + * @param {(number|Color|string)} [clearColor=0x000000] - The clear color. + * @param {number} [clearAlpha=0] - The clear alpha. + */ + constructor( clearColor = 0x000000, clearAlpha = 0 ) { super(); + /** + * Overwritten to disable the swap. + * + * @type {boolean} + * @default false + */ this.needsSwap = false; - this.clearColor = ( clearColor !== undefined ) ? clearColor : 0x000000; - this.clearAlpha = ( clearAlpha !== undefined ) ? clearAlpha : 0; + /** + * The clear color. + * + * @type {(number|Color|string)} + * @default 0x000000 + */ + this.clearColor = clearColor; + + /** + * The clear alpha. + * + * @type {number} + * @default 0 + */ + this.clearAlpha = clearAlpha; + + // internals + this._oldClearColor = new Color(); } + /** + * Performs the clear operation. This affects the current read or the default framebuffer. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) { let oldClearAlpha; diff --git a/examples/jsm/postprocessing/CubeTexturePass.js b/examples/jsm/postprocessing/CubeTexturePass.js index ff9b1f6e481cbc..8089e2b42d5255 100644 --- a/examples/jsm/postprocessing/CubeTexturePass.js +++ b/examples/jsm/postprocessing/CubeTexturePass.js @@ -10,30 +10,79 @@ import { } from 'three'; import { Pass } from './Pass.js'; +/** + * This pass can be used to render a cube texture over the entire screen. + * + * ```js + * const cubeMap = new THREE.CubeTextureLoader().load( urls ); + * + * const cubeTexturePass = new CubeTexturePass( camera, cubemap ); + * composer.addPass( cubeTexturePass ); + * ``` + * + * @augments Pass + * @three_import import { CubeTexturePass } from 'three/addons/postprocessing/CubeTexturePass.js'; + */ class CubeTexturePass extends Pass { + /** + * Constructs a new cube texture pass. + * + * @param {PerspectiveCamera} camera - The camera. + * @param {CubeTexture} tCube - The cube texture to render. + * @param {number} [opacity=1] - The opacity. + */ constructor( camera, tCube, opacity = 1 ) { super(); + /** + * The camera. + * + * @type {PerspectiveCamera} + */ this.camera = camera; + /** + * The cube texture to render. + * + * @type {CubeTexture} + */ + this.tCube = tCube; + + /** + * The opacity. + * + * @type {number} + * @default 1 + */ + this.opacity = opacity; + + /** + * Overwritten to disable the swap. + * + * @type {boolean} + * @default false + */ this.needsSwap = false; - this.cubeShader = ShaderLib[ 'cube' ]; - this.cubeMesh = new Mesh( + // internals + + const cubeShader = ShaderLib[ 'cube' ]; + + this._cubeMesh = new Mesh( new BoxGeometry( 10, 10, 10 ), new ShaderMaterial( { - uniforms: UniformsUtils.clone( this.cubeShader.uniforms ), - vertexShader: this.cubeShader.vertexShader, - fragmentShader: this.cubeShader.fragmentShader, + uniforms: UniformsUtils.clone( cubeShader.uniforms ), + vertexShader: cubeShader.vertexShader, + fragmentShader: cubeShader.fragmentShader, depthTest: false, depthWrite: false, side: BackSide } ) ); - Object.defineProperty( this.cubeMesh.material, 'envMap', { + Object.defineProperty( this._cubeMesh.material, 'envMap', { get: function () { @@ -43,40 +92,52 @@ class CubeTexturePass extends Pass { } ); - this.tCube = tCube; - this.opacity = opacity; - - this.cubeScene = new Scene(); - this.cubeCamera = new PerspectiveCamera(); - this.cubeScene.add( this.cubeMesh ); + this._cubeScene = new Scene(); + this._cubeCamera = new PerspectiveCamera(); + this._cubeScene.add( this._cubeMesh ); } + /** + * Performs the cube texture pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer/*, deltaTime, maskActive*/ ) { const oldAutoClear = renderer.autoClear; renderer.autoClear = false; - this.cubeCamera.projectionMatrix.copy( this.camera.projectionMatrix ); - this.cubeCamera.quaternion.setFromRotationMatrix( this.camera.matrixWorld ); + this._cubeCamera.projectionMatrix.copy( this.camera.projectionMatrix ); + this._cubeCamera.quaternion.setFromRotationMatrix( this.camera.matrixWorld ); - this.cubeMesh.material.uniforms.tCube.value = this.tCube; - this.cubeMesh.material.uniforms.tFlip.value = ( this.tCube.isCubeTexture && this.tCube.isRenderTargetTexture === false ) ? - 1 : 1; - this.cubeMesh.material.uniforms.opacity.value = this.opacity; - this.cubeMesh.material.transparent = ( this.opacity < 1.0 ); + this._cubeMesh.material.uniforms.tCube.value = this.tCube; + this._cubeMesh.material.uniforms.tFlip.value = ( this.tCube.isCubeTexture && this.tCube.isRenderTargetTexture === false ) ? - 1 : 1; + this._cubeMesh.material.uniforms.opacity.value = this.opacity; + this._cubeMesh.material.transparent = ( this.opacity < 1.0 ); renderer.setRenderTarget( this.renderToScreen ? null : readBuffer ); if ( this.clear ) renderer.clear(); - renderer.render( this.cubeScene, this.cubeCamera ); + renderer.render( this._cubeScene, this._cubeCamera ); renderer.autoClear = oldAutoClear; } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ dispose() { - this.cubeMesh.geometry.dispose(); - this.cubeMesh.material.dispose(); + this._cubeMesh.geometry.dispose(); + this._cubeMesh.material.dispose(); } diff --git a/examples/jsm/postprocessing/DotScreenPass.js b/examples/jsm/postprocessing/DotScreenPass.js index b8365892a54664..8efc56be1b791d 100644 --- a/examples/jsm/postprocessing/DotScreenPass.js +++ b/examples/jsm/postprocessing/DotScreenPass.js @@ -5,32 +5,78 @@ import { import { Pass, FullScreenQuad } from './Pass.js'; import { DotScreenShader } from '../shaders/DotScreenShader.js'; +/** + * Pass for creating a dot-screen effect. + * + * ```js + * const pass = new DotScreenPass( new THREE.Vector2( 0, 0 ), 0.5, 0.8 ); + * composer.addPass( pass ); + * ``` + * + * @augments Pass + * @three_import import { DotScreenPass } from 'three/addons/postprocessing/DotScreenPass.js'; + */ class DotScreenPass extends Pass { + /** + * Constructs a new dot screen pass. + * + * @param {Vector2} center - The center point. + * @param {number} angle - The rotation of the effect in radians. + * @param {number} scale - The scale of the effect. A higher value means smaller dots. + */ constructor( center, angle, scale ) { super(); - const shader = DotScreenShader; - - this.uniforms = UniformsUtils.clone( shader.uniforms ); + /** + * The pass uniforms. Use this object if you want to update the + * `center`, `angle` or `scale` values at runtime. + * ```js + * pass.uniforms.center.value.copy( center ); + * pass.uniforms.angle.value = 0; + * pass.uniforms.scale.value = 0.5; + * ``` + * + * @type {Object} + */ + this.uniforms = UniformsUtils.clone( DotScreenShader.uniforms ); if ( center !== undefined ) this.uniforms[ 'center' ].value.copy( center ); if ( angle !== undefined ) this.uniforms[ 'angle' ].value = angle; if ( scale !== undefined ) this.uniforms[ 'scale' ].value = scale; + /** + * The pass material. + * + * @type {ShaderMaterial} + */ this.material = new ShaderMaterial( { + name: DotScreenShader.name, uniforms: this.uniforms, - vertexShader: shader.vertexShader, - fragmentShader: shader.fragmentShader + vertexShader: DotScreenShader.vertexShader, + fragmentShader: DotScreenShader.fragmentShader } ); - this.fsQuad = new FullScreenQuad( this.material ); + // internals + + this._fsQuad = new FullScreenQuad( this.material ); } + /** + * Performs the dot screen pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) { this.uniforms[ 'tDiffuse' ].value = readBuffer.texture; @@ -39,23 +85,27 @@ class DotScreenPass extends Pass { if ( this.renderToScreen ) { renderer.setRenderTarget( null ); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } else { renderer.setRenderTarget( writeBuffer ); if ( this.clear ) renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ dispose() { this.material.dispose(); - this.fsQuad.dispose(); + this._fsQuad.dispose(); } diff --git a/examples/jsm/postprocessing/EffectComposer.js b/examples/jsm/postprocessing/EffectComposer.js index 2e7eecee53f31f..cc807df391d6d0 100644 --- a/examples/jsm/postprocessing/EffectComposer.js +++ b/examples/jsm/postprocessing/EffectComposer.js @@ -1,17 +1,61 @@ import { Clock, + HalfFloatType, + NoBlending, Vector2, WebGLRenderTarget } from 'three'; import { CopyShader } from '../shaders/CopyShader.js'; import { ShaderPass } from './ShaderPass.js'; -import { MaskPass } from './MaskPass.js'; -import { ClearMaskPass } from './MaskPass.js'; - +import { ClearMaskPass, MaskPass } from './MaskPass.js'; + +/** + * Used to implement post-processing effects in three.js. + * The class manages a chain of post-processing passes to produce the final visual result. + * Post-processing passes are executed in order of their addition/insertion. + * The last pass is automatically rendered to screen. + * + * This module can only be used with {@link WebGLRenderer}. + * + * ```js + * const composer = new EffectComposer( renderer ); + * + * // adding some passes + * const renderPass = new RenderPass( scene, camera ); + * composer.addPass( renderPass ); + * + * const glitchPass = new GlitchPass(); + * composer.addPass( glitchPass ); + * + * const outputPass = new OutputPass() + * composer.addPass( outputPass ); + * + * function animate() { + * + * composer.render(); // instead of renderer.render() + * + * } + * ``` + * + * @three_import import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'; + */ class EffectComposer { + /** + * Constructs a new effect composer. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} [renderTarget] - This render target and a clone will + * be used as the internal read and write buffers. If not given, the composer creates + * the buffers automatically. + */ constructor( renderer, renderTarget ) { + /** + * The renderer. + * + * @type {WebGLRenderer} + */ this.renderer = renderer; this._pixelRatio = renderer.getPixelRatio(); @@ -22,7 +66,7 @@ class EffectComposer { this._width = size.width; this._height = size.height; - renderTarget = new WebGLRenderTarget( this._width * this._pixelRatio, this._height * this._pixelRatio ); + renderTarget = new WebGLRenderTarget( this._width * this._pixelRatio, this._height * this._pixelRatio, { type: HalfFloatType } ); renderTarget.texture.name = 'EffectComposer.rt1'; } else { @@ -36,19 +80,59 @@ class EffectComposer { this.renderTarget2 = renderTarget.clone(); this.renderTarget2.texture.name = 'EffectComposer.rt2'; + /** + * A reference to the internal write buffer. Passes usually write + * their result into this buffer. + * + * @type {WebGLRenderTarget} + */ this.writeBuffer = this.renderTarget1; + + /** + * A reference to the internal read buffer. Passes usually read + * the previous render result from this buffer. + * + * @type {WebGLRenderTarget} + */ this.readBuffer = this.renderTarget2; + /** + * Whether the final pass is rendered to the screen (default framebuffer) or not. + * + * @type {boolean} + * @default true + */ this.renderToScreen = true; + /** + * An array representing the (ordered) chain of post-processing passes. + * + * @type {Array} + */ this.passes = []; + /** + * A copy pass used for internal swap operations. + * + * @private + * @type {ShaderPass} + */ this.copyPass = new ShaderPass( CopyShader ); - + this.copyPass.material.blending = NoBlending; + + /** + * The internal clock for managing time data. + * + * @private + * @type {Clock} + */ this.clock = new Clock(); } + /** + * Swaps the internal read/write buffers. + */ swapBuffers() { const tmp = this.readBuffer; @@ -57,6 +141,11 @@ class EffectComposer { } + /** + * Adds the given pass to the pass chain. + * + * @param {Pass} pass - The pass to add. + */ addPass( pass ) { this.passes.push( pass ); @@ -64,6 +153,12 @@ class EffectComposer { } + /** + * Inserts the given pass at a given index. + * + * @param {Pass} pass - The pass to insert. + * @param {number} index - The index into the pass chain. + */ insertPass( pass, index ) { this.passes.splice( index, 0, pass ); @@ -71,6 +166,11 @@ class EffectComposer { } + /** + * Removes the given pass from the pass chain. + * + * @param {Pass} pass - The pass to remove. + */ removePass( pass ) { const index = this.passes.indexOf( pass ); @@ -83,6 +183,12 @@ class EffectComposer { } + /** + * Returns `true` if the pass for the given index is the last enabled pass in the pass chain. + * + * @param {number} passIndex - The pass index. + * @return {boolean} Whether the pass for the given index is the last pass in the pass chain. + */ isLastEnabledPass( passIndex ) { for ( let i = passIndex + 1; i < this.passes.length; i ++ ) { @@ -99,6 +205,12 @@ class EffectComposer { } + /** + * Executes all enabled post-processing passes in order to produce the final frame. + * + * @param {number} deltaTime - The delta time in seconds. If not given, the composer computes + * its own time delta value. + */ render( deltaTime ) { // deltaTime value is in seconds @@ -163,6 +275,12 @@ class EffectComposer { } + /** + * Resets the internal state of the EffectComposer. + * + * @param {WebGLRenderTarget} [renderTarget] - This render target has the same purpose like + * the one from the constructor. If set, it is used to setup the read and write buffers. + */ reset( renderTarget ) { if ( renderTarget === undefined ) { @@ -187,6 +305,13 @@ class EffectComposer { } + /** + * Resizes the internal read and write buffers as well as all passes. Similar to {@link WebGLRenderer#setSize}, + * this method honors the current pixel ration. + * + * @param {number} width - The width in logical pixels. + * @param {number} height - The height in logical pixels. + */ setSize( width, height ) { this._width = width; @@ -206,6 +331,12 @@ class EffectComposer { } + /** + * Sets device pixel ratio. This is usually used for HiDPI device to prevent blurring output. + * Setting the pixel ratio will automatically resize the composer. + * + * @param {number} pixelRatio - The pixel ratio to set. + */ setPixelRatio( pixelRatio ) { this._pixelRatio = pixelRatio; @@ -214,6 +345,10 @@ class EffectComposer { } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the composer is no longer used in your app. + */ dispose() { this.renderTarget1.dispose(); diff --git a/examples/jsm/postprocessing/FXAAPass.js b/examples/jsm/postprocessing/FXAAPass.js new file mode 100644 index 00000000000000..50c5287610ca5d --- /dev/null +++ b/examples/jsm/postprocessing/FXAAPass.js @@ -0,0 +1,40 @@ +import { FXAAShader } from '../shaders/FXAAShader.js'; +import { ShaderPass } from './ShaderPass.js'; + +/** + * A pass for applying FXAA. + * + * ```js + * const fxaaPass = new FXAAPass(); + * composer.addPass( fxaaPass ); + * ``` + * + * @augments ShaderPass + * @three_import import { FXAAPass } from 'three/addons/postprocessing/FXAAPass.js'; + */ +class FXAAPass extends ShaderPass { + + /** + * Constructs a new FXAA pass. + */ + constructor() { + + super( FXAAShader ); + + } + + /** + * Sets the size of the pass. + * + * @param {number} width - The width to set. + * @param {number} height - The height to set. + */ + setSize( width, height ) { + + this.material.uniforms[ 'resolution' ].value.set( 1 / width, 1 / height ); + + } + +} + +export { FXAAPass }; diff --git a/examples/jsm/postprocessing/FilmPass.js b/examples/jsm/postprocessing/FilmPass.js index 0c999bdd547d97..00017af6c3cd2c 100644 --- a/examples/jsm/postprocessing/FilmPass.js +++ b/examples/jsm/postprocessing/FilmPass.js @@ -5,33 +5,77 @@ import { import { Pass, FullScreenQuad } from './Pass.js'; import { FilmShader } from '../shaders/FilmShader.js'; +/** + * This pass can be used to create a film grain effect. + * + * ```js + * const filmPass = new FilmPass(); + * composer.addPass( filmPass ); + * ``` + * + * @augments Pass + * @three_import import { FilmPass } from 'three/addons/postprocessing/FilmPass.js'; + */ class FilmPass extends Pass { - constructor( noiseIntensity, scanlinesIntensity, scanlinesCount, grayscale ) { + /** + * Constructs a new film pass. + * + * @param {number} [intensity=0.5] - The grain intensity in the range `[0,1]` (0 = no effect, 1 = full effect). + * @param {boolean} [grayscale=false] - Whether to apply a grayscale effect or not. + */ + constructor( intensity = 0.5, grayscale = false ) { super(); const shader = FilmShader; + /** + * The pass uniforms. Use this object if you want to update the + * `intensity` or `grayscale` values at runtime. + * ```js + * pass.uniforms.intensity.value = 1; + * pass.uniforms.grayscale.value = true; + * ``` + * + * @type {Object} + */ this.uniforms = UniformsUtils.clone( shader.uniforms ); + /** + * The pass material. + * + * @type {ShaderMaterial} + */ this.material = new ShaderMaterial( { + name: shader.name, uniforms: this.uniforms, vertexShader: shader.vertexShader, fragmentShader: shader.fragmentShader } ); - if ( grayscale !== undefined ) this.uniforms.grayscale.value = grayscale; - if ( noiseIntensity !== undefined ) this.uniforms.nIntensity.value = noiseIntensity; - if ( scanlinesIntensity !== undefined ) this.uniforms.sIntensity.value = scanlinesIntensity; - if ( scanlinesCount !== undefined ) this.uniforms.sCount.value = scanlinesCount; + this.uniforms.intensity.value = intensity; + this.uniforms.grayscale.value = grayscale; - this.fsQuad = new FullScreenQuad( this.material ); + // internals + + this._fsQuad = new FullScreenQuad( this.material ); } + /** + * Performs the film pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer, deltaTime /*, maskActive */ ) { this.uniforms[ 'tDiffuse' ].value = readBuffer.texture; @@ -40,23 +84,27 @@ class FilmPass extends Pass { if ( this.renderToScreen ) { renderer.setRenderTarget( null ); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } else { renderer.setRenderTarget( writeBuffer ); if ( this.clear ) renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ dispose() { this.material.dispose(); - this.fsQuad.dispose(); + this._fsQuad.dispose(); } diff --git a/examples/jsm/postprocessing/GTAOPass.js b/examples/jsm/postprocessing/GTAOPass.js new file mode 100644 index 00000000000000..1864a60026ba3a --- /dev/null +++ b/examples/jsm/postprocessing/GTAOPass.js @@ -0,0 +1,726 @@ +import { + AddEquation, + Color, + CustomBlending, + DataTexture, + DepthTexture, + DepthStencilFormat, + DstAlphaFactor, + DstColorFactor, + HalfFloatType, + MeshNormalMaterial, + NearestFilter, + NoBlending, + RepeatWrapping, + RGBAFormat, + ShaderMaterial, + UniformsUtils, + UnsignedByteType, + UnsignedInt248Type, + WebGLRenderTarget, + ZeroFactor +} from 'three'; +import { Pass, FullScreenQuad } from './Pass.js'; +import { generateMagicSquareNoise, GTAOShader, GTAODepthShader, GTAOBlendShader } from '../shaders/GTAOShader.js'; +import { generatePdSamplePointInitializer, PoissonDenoiseShader } from '../shaders/PoissonDenoiseShader.js'; +import { CopyShader } from '../shaders/CopyShader.js'; +import { SimplexNoise } from '../math/SimplexNoise.js'; + +/** + * A pass for an GTAO effect. + * + * `GTAOPass` provides better quality than {@link SSAOPass} but is also more expensive. + * + * ```js + * const gtaoPass = new GTAOPass( scene, camera, width, height ); + * gtaoPass.output = GTAOPass.OUTPUT.Denoise; + * composer.addPass( gtaoPass ); + * ``` + * + * @augments Pass + * @three_import import { GTAOPass } from 'three/addons/postprocessing/GTAOPass.js'; + */ +class GTAOPass extends Pass { + + /** + * Constructs a new GTAO pass. + * + * @param {Scene} scene - The scene to compute the AO for. + * @param {Camera} camera - The camera. + * @param {number} [width=512] - The width of the effect. + * @param {number} [height=512] - The height of the effect. + * @param {Object} [parameters] - The pass parameters. + * @param {Object} [aoParameters] - The AO parameters. + * @param {Object} [pdParameters] - The denoise parameters. + */ + constructor( scene, camera, width = 512, height = 512, parameters, aoParameters, pdParameters ) { + + super(); + + /** + * The width of the effect. + * + * @type {number} + * @default 512 + */ + this.width = width; + + /** + * The height of the effect. + * + * @type {number} + * @default 512 + */ + this.height = height; + + /** + * Overwritten to perform a clear operation by default. + * + * @type {boolean} + * @default true + */ + this.clear = true; + + /** + * The camera. + * + * @type {Camera} + */ + this.camera = camera; + + /** + * The scene to render the AO for. + * + * @type {Scene} + */ + this.scene = scene; + + /** + * The output configuration. + * + * @type {number} + * @default 0 + */ + this.output = 0; + this._renderGBuffer = true; + this._visibilityCache = new Map(); + + /** + * The AO blend intensity. + * + * @type {number} + * @default 1 + */ + this.blendIntensity = 1.; + + /** + * The number of Poisson Denoise rings. + * + * @type {number} + * @default 2 + */ + this.pdRings = 2.; + + /** + * The Poisson Denoise radius exponent. + * + * @type {number} + * @default 2 + */ + this.pdRadiusExponent = 2.; + + /** + * The Poisson Denoise sample count. + * + * @type {number} + * @default 16 + */ + this.pdSamples = 16; + + this.gtaoNoiseTexture = generateMagicSquareNoise(); + this.pdNoiseTexture = this._generateNoise(); + + this.gtaoRenderTarget = new WebGLRenderTarget( this.width, this.height, { type: HalfFloatType } ); + this.pdRenderTarget = this.gtaoRenderTarget.clone(); + + this.gtaoMaterial = new ShaderMaterial( { + defines: Object.assign( {}, GTAOShader.defines ), + uniforms: UniformsUtils.clone( GTAOShader.uniforms ), + vertexShader: GTAOShader.vertexShader, + fragmentShader: GTAOShader.fragmentShader, + blending: NoBlending, + depthTest: false, + depthWrite: false, + } ); + this.gtaoMaterial.defines.PERSPECTIVE_CAMERA = this.camera.isPerspectiveCamera ? 1 : 0; + this.gtaoMaterial.uniforms.tNoise.value = this.gtaoNoiseTexture; + this.gtaoMaterial.uniforms.resolution.value.set( this.width, this.height ); + this.gtaoMaterial.uniforms.cameraNear.value = this.camera.near; + this.gtaoMaterial.uniforms.cameraFar.value = this.camera.far; + + this.normalMaterial = new MeshNormalMaterial(); + this.normalMaterial.blending = NoBlending; + + this.pdMaterial = new ShaderMaterial( { + defines: Object.assign( {}, PoissonDenoiseShader.defines ), + uniforms: UniformsUtils.clone( PoissonDenoiseShader.uniforms ), + vertexShader: PoissonDenoiseShader.vertexShader, + fragmentShader: PoissonDenoiseShader.fragmentShader, + depthTest: false, + depthWrite: false, + } ); + this.pdMaterial.uniforms.tDiffuse.value = this.gtaoRenderTarget.texture; + this.pdMaterial.uniforms.tNoise.value = this.pdNoiseTexture; + this.pdMaterial.uniforms.resolution.value.set( this.width, this.height ); + this.pdMaterial.uniforms.lumaPhi.value = 10; + this.pdMaterial.uniforms.depthPhi.value = 2; + this.pdMaterial.uniforms.normalPhi.value = 3; + this.pdMaterial.uniforms.radius.value = 8; + + this.depthRenderMaterial = new ShaderMaterial( { + defines: Object.assign( {}, GTAODepthShader.defines ), + uniforms: UniformsUtils.clone( GTAODepthShader.uniforms ), + vertexShader: GTAODepthShader.vertexShader, + fragmentShader: GTAODepthShader.fragmentShader, + blending: NoBlending + } ); + this.depthRenderMaterial.uniforms.cameraNear.value = this.camera.near; + this.depthRenderMaterial.uniforms.cameraFar.value = this.camera.far; + + this.copyMaterial = new ShaderMaterial( { + uniforms: UniformsUtils.clone( CopyShader.uniforms ), + vertexShader: CopyShader.vertexShader, + fragmentShader: CopyShader.fragmentShader, + transparent: true, + depthTest: false, + depthWrite: false, + blendSrc: DstColorFactor, + blendDst: ZeroFactor, + blendEquation: AddEquation, + blendSrcAlpha: DstAlphaFactor, + blendDstAlpha: ZeroFactor, + blendEquationAlpha: AddEquation + } ); + + this.blendMaterial = new ShaderMaterial( { + uniforms: UniformsUtils.clone( GTAOBlendShader.uniforms ), + vertexShader: GTAOBlendShader.vertexShader, + fragmentShader: GTAOBlendShader.fragmentShader, + transparent: true, + depthTest: false, + depthWrite: false, + blending: CustomBlending, + blendSrc: DstColorFactor, + blendDst: ZeroFactor, + blendEquation: AddEquation, + blendSrcAlpha: DstAlphaFactor, + blendDstAlpha: ZeroFactor, + blendEquationAlpha: AddEquation + } ); + + this._fsQuad = new FullScreenQuad( null ); + + this._originalClearColor = new Color(); + + this.setGBuffer( parameters ? parameters.depthTexture : undefined, parameters ? parameters.normalTexture : undefined ); + + if ( aoParameters !== undefined ) { + + this.updateGtaoMaterial( aoParameters ); + + } + + if ( pdParameters !== undefined ) { + + this.updatePdMaterial( pdParameters ); + + } + + } + + /** + * Sets the size of the pass. + * + * @param {number} width - The width to set. + * @param {number} height - The height to set. + */ + setSize( width, height ) { + + this.width = width; + this.height = height; + + this.gtaoRenderTarget.setSize( width, height ); + this.normalRenderTarget.setSize( width, height ); + this.pdRenderTarget.setSize( width, height ); + + this.gtaoMaterial.uniforms.resolution.value.set( width, height ); + this.gtaoMaterial.uniforms.cameraProjectionMatrix.value.copy( this.camera.projectionMatrix ); + this.gtaoMaterial.uniforms.cameraProjectionMatrixInverse.value.copy( this.camera.projectionMatrixInverse ); + + this.pdMaterial.uniforms.resolution.value.set( width, height ); + this.pdMaterial.uniforms.cameraProjectionMatrixInverse.value.copy( this.camera.projectionMatrixInverse ); + + } + + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ + dispose() { + + this.gtaoNoiseTexture.dispose(); + this.pdNoiseTexture.dispose(); + this.normalRenderTarget.dispose(); + this.gtaoRenderTarget.dispose(); + this.pdRenderTarget.dispose(); + this.normalMaterial.dispose(); + this.pdMaterial.dispose(); + this.copyMaterial.dispose(); + this.depthRenderMaterial.dispose(); + this._fsQuad.dispose(); + + } + + /** + * A texture holding the computed AO. + * + * @type {Texture} + * @readonly + */ + get gtaoMap() { + + return this.pdRenderTarget.texture; + + } + + /** + * Configures the GBuffer of this pass. If no arguments are passed, + * the pass creates an internal render target for holding depth + * and normal data. + * + * @param {DepthTexture} [depthTexture] - The depth texture. + * @param {DepthTexture} [normalTexture] - The normal texture. + */ + setGBuffer( depthTexture, normalTexture ) { + + if ( depthTexture !== undefined ) { + + this.depthTexture = depthTexture; + this.normalTexture = normalTexture; + this._renderGBuffer = false; + + } else { + + this.depthTexture = new DepthTexture(); + this.depthTexture.format = DepthStencilFormat; + this.depthTexture.type = UnsignedInt248Type; + this.normalRenderTarget = new WebGLRenderTarget( this.width, this.height, { + minFilter: NearestFilter, + magFilter: NearestFilter, + type: HalfFloatType, + depthTexture: this.depthTexture + } ); + this.normalTexture = this.normalRenderTarget.texture; + this._renderGBuffer = true; + + } + + const normalVectorType = ( this.normalTexture ) ? 1 : 0; + const depthValueSource = ( this.depthTexture === this.normalTexture ) ? 'w' : 'x'; + + this.gtaoMaterial.defines.NORMAL_VECTOR_TYPE = normalVectorType; + this.gtaoMaterial.defines.DEPTH_SWIZZLING = depthValueSource; + this.gtaoMaterial.uniforms.tNormal.value = this.normalTexture; + this.gtaoMaterial.uniforms.tDepth.value = this.depthTexture; + + this.pdMaterial.defines.NORMAL_VECTOR_TYPE = normalVectorType; + this.pdMaterial.defines.DEPTH_SWIZZLING = depthValueSource; + this.pdMaterial.uniforms.tNormal.value = this.normalTexture; + this.pdMaterial.uniforms.tDepth.value = this.depthTexture; + + this.depthRenderMaterial.uniforms.tDepth.value = this.normalRenderTarget.depthTexture; + + } + + /** + * Configures the clip box of the GTAO shader with the given AABB. + * + * @param {?Box3} box - The AABB enclosing the scene that should receive AO. When passing + * `null`, to clip box is used. + */ + setSceneClipBox( box ) { + + if ( box ) { + + this.gtaoMaterial.needsUpdate = this.gtaoMaterial.defines.SCENE_CLIP_BOX !== 1; + this.gtaoMaterial.defines.SCENE_CLIP_BOX = 1; + this.gtaoMaterial.uniforms.sceneBoxMin.value.copy( box.min ); + this.gtaoMaterial.uniforms.sceneBoxMax.value.copy( box.max ); + + } else { + + this.gtaoMaterial.needsUpdate = this.gtaoMaterial.defines.SCENE_CLIP_BOX === 0; + this.gtaoMaterial.defines.SCENE_CLIP_BOX = 0; + + } + + } + + /** + * Updates the GTAO material from the given parameter object. + * + * @param {Object} parameters - The GTAO material parameters. + */ + updateGtaoMaterial( parameters ) { + + if ( parameters.radius !== undefined ) { + + this.gtaoMaterial.uniforms.radius.value = parameters.radius; + + } + + if ( parameters.distanceExponent !== undefined ) { + + this.gtaoMaterial.uniforms.distanceExponent.value = parameters.distanceExponent; + + } + + if ( parameters.thickness !== undefined ) { + + this.gtaoMaterial.uniforms.thickness.value = parameters.thickness; + + } + + if ( parameters.distanceFallOff !== undefined ) { + + this.gtaoMaterial.uniforms.distanceFallOff.value = parameters.distanceFallOff; + this.gtaoMaterial.needsUpdate = true; + + } + + if ( parameters.scale !== undefined ) { + + this.gtaoMaterial.uniforms.scale.value = parameters.scale; + + } + + if ( parameters.samples !== undefined && parameters.samples !== this.gtaoMaterial.defines.SAMPLES ) { + + this.gtaoMaterial.defines.SAMPLES = parameters.samples; + this.gtaoMaterial.needsUpdate = true; + + } + + if ( parameters.screenSpaceRadius !== undefined && ( parameters.screenSpaceRadius ? 1 : 0 ) !== this.gtaoMaterial.defines.SCREEN_SPACE_RADIUS ) { + + this.gtaoMaterial.defines.SCREEN_SPACE_RADIUS = parameters.screenSpaceRadius ? 1 : 0; + this.gtaoMaterial.needsUpdate = true; + + } + + } + + /** + * Updates the Denoise material from the given parameter object. + * + * @param {Object} parameters - The denoise parameters. + */ + updatePdMaterial( parameters ) { + + let updateShader = false; + + if ( parameters.lumaPhi !== undefined ) { + + this.pdMaterial.uniforms.lumaPhi.value = parameters.lumaPhi; + + } + + if ( parameters.depthPhi !== undefined ) { + + this.pdMaterial.uniforms.depthPhi.value = parameters.depthPhi; + + } + + if ( parameters.normalPhi !== undefined ) { + + this.pdMaterial.uniforms.normalPhi.value = parameters.normalPhi; + + } + + if ( parameters.radius !== undefined && parameters.radius !== this.radius ) { + + this.pdMaterial.uniforms.radius.value = parameters.radius; + + } + + if ( parameters.radiusExponent !== undefined && parameters.radiusExponent !== this.pdRadiusExponent ) { + + this.pdRadiusExponent = parameters.radiusExponent; + updateShader = true; + + } + + if ( parameters.rings !== undefined && parameters.rings !== this.pdRings ) { + + this.pdRings = parameters.rings; + updateShader = true; + + } + + if ( parameters.samples !== undefined && parameters.samples !== this.pdSamples ) { + + this.pdSamples = parameters.samples; + updateShader = true; + + } + + if ( updateShader ) { + + this.pdMaterial.defines.SAMPLES = this.pdSamples; + this.pdMaterial.defines.SAMPLE_VECTORS = generatePdSamplePointInitializer( this.pdSamples, this.pdRings, this.pdRadiusExponent ); + this.pdMaterial.needsUpdate = true; + + } + + } + + /** + * Performs the GTAO pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ + render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) { + + // render normals and depth (honor only meshes, points and lines do not contribute to AO) + + if ( this._renderGBuffer ) { + + this._overrideVisibility(); + this._renderOverride( renderer, this.normalMaterial, this.normalRenderTarget, 0x7777ff, 1.0 ); + this._restoreVisibility(); + + } + + // render AO + + this.gtaoMaterial.uniforms.cameraNear.value = this.camera.near; + this.gtaoMaterial.uniforms.cameraFar.value = this.camera.far; + this.gtaoMaterial.uniforms.cameraProjectionMatrix.value.copy( this.camera.projectionMatrix ); + this.gtaoMaterial.uniforms.cameraProjectionMatrixInverse.value.copy( this.camera.projectionMatrixInverse ); + this.gtaoMaterial.uniforms.cameraWorldMatrix.value.copy( this.camera.matrixWorld ); + this._renderPass( renderer, this.gtaoMaterial, this.gtaoRenderTarget, 0xffffff, 1.0 ); + + // render poisson denoise + + this.pdMaterial.uniforms.cameraProjectionMatrixInverse.value.copy( this.camera.projectionMatrixInverse ); + this._renderPass( renderer, this.pdMaterial, this.pdRenderTarget, 0xffffff, 1.0 ); + + // output result to screen + + switch ( this.output ) { + + case GTAOPass.OUTPUT.Off: + break; + + case GTAOPass.OUTPUT.Diffuse: + + this.copyMaterial.uniforms.tDiffuse.value = readBuffer.texture; + this.copyMaterial.blending = NoBlending; + this._renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + + break; + + case GTAOPass.OUTPUT.AO: + + this.copyMaterial.uniforms.tDiffuse.value = this.gtaoRenderTarget.texture; + this.copyMaterial.blending = NoBlending; + this._renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + + break; + + case GTAOPass.OUTPUT.Denoise: + + this.copyMaterial.uniforms.tDiffuse.value = this.pdRenderTarget.texture; + this.copyMaterial.blending = NoBlending; + this._renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + + break; + + case GTAOPass.OUTPUT.Depth: + + this.depthRenderMaterial.uniforms.cameraNear.value = this.camera.near; + this.depthRenderMaterial.uniforms.cameraFar.value = this.camera.far; + this._renderPass( renderer, this.depthRenderMaterial, this.renderToScreen ? null : writeBuffer ); + + break; + + case GTAOPass.OUTPUT.Normal: + + this.copyMaterial.uniforms.tDiffuse.value = this.normalRenderTarget.texture; + this.copyMaterial.blending = NoBlending; + this._renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + + break; + + case GTAOPass.OUTPUT.Default: + + this.copyMaterial.uniforms.tDiffuse.value = readBuffer.texture; + this.copyMaterial.blending = NoBlending; + this._renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + + this.blendMaterial.uniforms.intensity.value = this.blendIntensity; + this.blendMaterial.uniforms.tDiffuse.value = this.pdRenderTarget.texture; + this._renderPass( renderer, this.blendMaterial, this.renderToScreen ? null : writeBuffer ); + + break; + + default: + console.warn( 'THREE.GTAOPass: Unknown output type.' ); + + } + + } + + // internals + + _renderPass( renderer, passMaterial, renderTarget, clearColor, clearAlpha ) { + + // 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.material = passMaterial; + this._fsQuad.render( renderer ); + + // restore original state + renderer.autoClear = originalAutoClear; + renderer.setClearColor( this._originalClearColor ); + renderer.setClearAlpha( originalClearAlpha ); + + } + + _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(); + + } + + this.scene.overrideMaterial = overrideMaterial; + renderer.render( this.scene, this.camera ); + this.scene.overrideMaterial = null; + + renderer.autoClear = originalAutoClear; + renderer.setClearColor( this._originalClearColor ); + renderer.setClearAlpha( originalClearAlpha ); + + } + + _overrideVisibility() { + + const scene = this.scene; + const cache = this._visibilityCache; + + scene.traverse( function ( object ) { + + cache.set( object, object.visible ); + + if ( object.isPoints || object.isLine ) object.visible = false; + + } ); + + } + + _restoreVisibility() { + + const scene = this.scene; + const cache = this._visibilityCache; + + scene.traverse( function ( object ) { + + const visible = cache.get( object ); + object.visible = visible; + + } ); + + cache.clear(); + + } + + _generateNoise( size = 64 ) { + + const simplex = new SimplexNoise(); + + const arraySize = size * size * 4; + const data = new Uint8Array( arraySize ); + + for ( let i = 0; i < size; i ++ ) { + + for ( let j = 0; j < size; j ++ ) { + + const x = i; + const y = j; + + data[ ( i * size + j ) * 4 ] = ( simplex.noise( x, y ) * 0.5 + 0.5 ) * 255; + data[ ( i * size + j ) * 4 + 1 ] = ( simplex.noise( x + size, y ) * 0.5 + 0.5 ) * 255; + data[ ( i * size + j ) * 4 + 2 ] = ( simplex.noise( x, y + size ) * 0.5 + 0.5 ) * 255; + data[ ( i * size + j ) * 4 + 3 ] = ( simplex.noise( x + size, y + size ) * 0.5 + 0.5 ) * 255; + + } + + } + + const noiseTexture = new DataTexture( data, size, size, RGBAFormat, UnsignedByteType ); + noiseTexture.wrapS = RepeatWrapping; + noiseTexture.wrapT = RepeatWrapping; + noiseTexture.needsUpdate = true; + + return noiseTexture; + + } + +} + +GTAOPass.OUTPUT = { + 'Off': - 1, + 'Default': 0, + 'Diffuse': 1, + 'Depth': 2, + 'Normal': 3, + 'AO': 4, + 'Denoise': 5, +}; + +export { GTAOPass }; diff --git a/examples/jsm/postprocessing/GlitchPass.js b/examples/jsm/postprocessing/GlitchPass.js index 5b044da97851b1..31552125328e96 100644 --- a/examples/jsm/postprocessing/GlitchPass.js +++ b/examples/jsm/postprocessing/GlitchPass.js @@ -3,50 +3,93 @@ import { FloatType, MathUtils, RedFormat, - LuminanceFormat, ShaderMaterial, UniformsUtils } from 'three'; import { Pass, FullScreenQuad } from './Pass.js'; import { DigitalGlitch } from '../shaders/DigitalGlitch.js'; +/** + * Pass for creating a glitch effect. + * + * ```js + * const glitchPass = new GlitchPass(); + * composer.addPass( glitchPass ); + * ``` + * + * @augments Pass + * @three_import import { GlitchPass } from 'three/addons/postprocessing/GlitchPass.js'; + */ class GlitchPass extends Pass { + /** + * Constructs a new glitch pass. + * + * @param {number} [dt_size=64] - The size of the displacement texture + * for digital glitch squares. + */ constructor( dt_size = 64 ) { super(); - const shader = DigitalGlitch; + /** + * The pass uniforms. + * + * @type {Object} + */ + this.uniforms = UniformsUtils.clone( DigitalGlitch.uniforms ); + + /** + * The pass material. + * + * @type {ShaderMaterial} + */ + this.material = new ShaderMaterial( { + uniforms: this.uniforms, + vertexShader: DigitalGlitch.vertexShader, + fragmentShader: DigitalGlitch.fragmentShader + } ); - this.uniforms = UniformsUtils.clone( shader.uniforms ); + /** + * Whether to noticeably increase the effect intensity or not. + * + * @type {boolean} + * @default false + */ + this.goWild = false; - this.heightMap = this.generateHeightmap( dt_size ); + // internals + this._heightMap = this._generateHeightmap( dt_size ); this.uniforms[ 'tDisp' ].value = this.heightMap; - this.material = new ShaderMaterial( { - uniforms: this.uniforms, - vertexShader: shader.vertexShader, - fragmentShader: shader.fragmentShader - } ); + this._fsQuad = new FullScreenQuad( this.material ); - this.fsQuad = new FullScreenQuad( this.material ); + this._curF = 0; + this._randX = 0; - this.goWild = false; - this.curF = 0; - this.generateTrigger(); + this._generateTrigger(); } + /** + * Performs the glitch pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) { - if ( renderer.capabilities.isWebGL2 === false ) this.uniforms[ 'tDisp' ].value.format = LuminanceFormat; - this.uniforms[ 'tDiffuse' ].value = readBuffer.texture; - this.uniforms[ 'seed' ].value = Math.random();//default seeding + this.uniforms[ 'seed' ].value = Math.random(); // default seeding this.uniforms[ 'byp' ].value = 0; - if ( this.curF % this.randX == 0 || this.goWild == true ) { + if ( this._curF % this._randX == 0 || this.goWild == true ) { this.uniforms[ 'amount' ].value = Math.random() / 30; this.uniforms[ 'angle' ].value = MathUtils.randFloat( - Math.PI, Math.PI ); @@ -54,10 +97,10 @@ class GlitchPass extends Pass { this.uniforms[ 'seed_y' ].value = MathUtils.randFloat( - 1, 1 ); this.uniforms[ 'distortion_x' ].value = MathUtils.randFloat( 0, 1 ); this.uniforms[ 'distortion_y' ].value = MathUtils.randFloat( 0, 1 ); - this.curF = 0; - this.generateTrigger(); + this._curF = 0; + this._generateTrigger(); - } else if ( this.curF % this.randX < this.randX / 5 ) { + } else if ( this._curF % this._randX < this._randX / 5 ) { this.uniforms[ 'amount' ].value = Math.random() / 90; this.uniforms[ 'angle' ].value = MathUtils.randFloat( - Math.PI, Math.PI ); @@ -72,30 +115,46 @@ class GlitchPass extends Pass { } - this.curF ++; + this._curF ++; if ( this.renderToScreen ) { renderer.setRenderTarget( null ); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } else { renderer.setRenderTarget( writeBuffer ); if ( this.clear ) renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } } - generateTrigger() { + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ + dispose() { + + this.material.dispose(); + + this.heightMap.dispose(); + + this._fsQuad.dispose(); + + } + + // internals - this.randX = MathUtils.randInt( 120, 240 ); + _generateTrigger() { + + this._randX = MathUtils.randInt( 120, 240 ); } - generateHeightmap( dt_size ) { + _generateHeightmap( dt_size ) { const data_arr = new Float32Array( dt_size * dt_size ); const length = dt_size * dt_size; @@ -113,16 +172,6 @@ class GlitchPass extends Pass { } - dispose() { - - this.material.dispose(); - - this.heightMap.dispose(); - - this.fsQuad.dispose(); - - } - } export { GlitchPass }; diff --git a/examples/jsm/postprocessing/HalftonePass.js b/examples/jsm/postprocessing/HalftonePass.js index 0727f468463b1d..5445cdcbd922e6 100644 --- a/examples/jsm/postprocessing/HalftonePass.js +++ b/examples/jsm/postprocessing/HalftonePass.js @@ -6,25 +6,57 @@ import { Pass, FullScreenQuad } from './Pass.js'; import { HalftoneShader } from '../shaders/HalftoneShader.js'; /** - * RGB Halftone pass for three.js effects composer. Requires HalftoneShader. + * Pass for creating a RGB halftone effect. + * + * ```js + * const params = { + * shape: 1, + * radius: 4, + * rotateR: Math.PI / 12, + * rotateB: Math.PI / 12 * 2, + * rotateG: Math.PI / 12 * 3, + * scatter: 0, + * blending: 1, + * blendingMode: 1, + * greyscale: false, + * disable: false + * }; + * const halftonePass = new HalftonePass( params ); + * composer.addPass( halftonePass ); + * ``` + * + * @augments Pass + * @three_import import { HalftonePass } from 'three/addons/postprocessing/HalftonePass.js'; */ - class HalftonePass extends Pass { - constructor( width, height, params ) { + /** + * Constructs a new halftone pass. + * + * @param {Object} params - The halftone shader parameter. + */ + constructor( params ) { super(); + /** + * The pass uniforms. + * + * @type {Object} + */ this.uniforms = UniformsUtils.clone( HalftoneShader.uniforms ); + + /** + * The pass material. + * + * @type {ShaderMaterial} + */ this.material = new ShaderMaterial( { uniforms: this.uniforms, fragmentShader: HalftoneShader.fragmentShader, vertexShader: HalftoneShader.vertexShader } ); - // set params - this.uniforms.width.value = width; - this.uniforms.height.value = height; for ( const key in params ) { @@ -36,10 +68,23 @@ class HalftonePass extends Pass { } - this.fsQuad = new FullScreenQuad( this.material ); + // internals + + this._fsQuad = new FullScreenQuad( this.material ); } + /** + * Performs the halftone pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer/*, deltaTime, maskActive*/ ) { this.material.uniforms[ 'tDiffuse' ].value = readBuffer.texture; @@ -47,18 +92,24 @@ class HalftonePass extends Pass { if ( this.renderToScreen ) { renderer.setRenderTarget( null ); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } else { renderer.setRenderTarget( writeBuffer ); if ( this.clear ) renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } } + /** + * Sets the size of the pass. + * + * @param {number} width - The width to set. + * @param {number} height - The height to set. + */ setSize( width, height ) { this.uniforms.width.value = width; @@ -66,11 +117,15 @@ class HalftonePass extends Pass { } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ dispose() { this.material.dispose(); - this.fsQuad.dispose(); + this._fsQuad.dispose(); } diff --git a/examples/jsm/postprocessing/LUTPass.js b/examples/jsm/postprocessing/LUTPass.js index ceb356d008cb04..4ddffb94126f6c 100644 --- a/examples/jsm/postprocessing/LUTPass.js +++ b/examples/jsm/postprocessing/LUTPass.js @@ -2,12 +2,9 @@ import { ShaderPass } from './ShaderPass.js'; const LUTShader = { - defines: { - USE_3DTEXTURE: 1, - }, + name: 'LUTShader', uniforms: { - lut3d: { value: null }, lut: { value: null }, lutSize: { value: 0 }, @@ -29,49 +26,10 @@ const LUTShader = { `, - fragmentShader: /* glsl */` uniform float lutSize; - #if USE_3DTEXTURE - precision highp sampler3D; - uniform sampler3D lut3d; - #else - uniform sampler2D lut; - - vec3 lutLookup( sampler2D tex, float size, vec3 rgb ) { - - float sliceHeight = 1.0 / size; - float yPixelHeight = 1.0 / ( size * size ); - - // Get the slices on either side of the sample - float slice = rgb.b * size; - float interp = fract( slice ); - float slice0 = slice - interp; - float centeredInterp = interp - 0.5; - - float slice1 = slice0 + sign( centeredInterp ); - - // Pull y sample in by half a pixel in each direction to avoid color - // bleeding from adjacent slices. - float greenOffset = clamp( rgb.g * sliceHeight, yPixelHeight * 0.5, sliceHeight - yPixelHeight * 0.5 ); - - vec2 uv0 = vec2( - rgb.r, - slice0 * sliceHeight + greenOffset - ); - vec2 uv1 = vec2( - rgb.r, - slice1 * sliceHeight + greenOffset - ); - - vec3 sample0 = texture2D( tex, uv0 ).rgb; - vec3 sample1 = texture2D( tex, uv1 ).rgb; - - return mix( sample0, sample1, abs( centeredInterp ) ); - - } - #endif + uniform sampler3D lut; varying vec2 vUv; uniform float intensity; @@ -87,15 +45,8 @@ const LUTShader = { float halfPixelWidth = 0.5 / lutSize; vec3 uvw = vec3( halfPixelWidth ) + val.rgb * ( 1.0 - pixelWidth ); - #if USE_3DTEXTURE - - lutVal = vec4( texture( lut3d, uvw ).rgb, val.a ); - - #else - lutVal = vec4( lutLookup( lut, lutSize, uvw ), val.a ); - - #endif + lutVal = vec4( texture( lut, uvw ).rgb, val.a ); gl_FragColor = vec4( mix( val, lutVal, intensity ) ); @@ -105,36 +56,58 @@ const LUTShader = { }; +/** + * Pass for color grading via lookup tables. + * + * ```js + * const lutPass = new LUTPass( { lut: lut.texture3D } ); + * composer.addPass( lutPass ); + * ``` + * + * @augments ShaderPass + * @three_import import { LUTPass } from 'three/addons/postprocessing/LUTPass.js'; + */ class LUTPass extends ShaderPass { - set lut( v ) { - - const material = this.material; - if ( v !== this.lut ) { + /** + * Constructs a LUT pass. + * + * @param {{lut:Data3DTexture,intensity:number}} [options={}] - The pass options. + */ + constructor( options = {} ) { - material.uniforms.lut3d.value = null; - material.uniforms.lut.value = null; + super( LUTShader ); - if ( v ) { + /** + * The LUT as a 3D texture. + * + * @type {?Data3DTexture} + * @default null + */ + this.lut = options.lut || null; - const is3dTextureDefine = v.isData3DTexture ? 1 : 0; - if ( is3dTextureDefine !== material.defines.USE_3DTEXTURE ) { + /** + * The intensity. + * + * @type {?number} + * @default 1 + */ + this.intensity = 'intensity' in options ? options.intensity : 1; - material.defines.USE_3DTEXTURE = is3dTextureDefine; - material.needsUpdate = true; + } - } + set lut( v ) { - material.uniforms.lutSize.value = v.image.width; - if ( v.isData3DTexture ) { + const material = this.material; - material.uniforms.lut3d.value = v; + if ( v !== this.lut ) { - } else { + material.uniforms.lut.value = null; - material.uniforms.lut.value = v; + if ( v ) { - } + material.uniforms.lutSize.value = v.image.width; + material.uniforms.lut.value = v; } @@ -144,7 +117,7 @@ class LUTPass extends ShaderPass { get lut() { - return this.material.uniforms.lut.value || this.material.uniforms.lut3d.value; + return this.material.uniforms.lut.value; } @@ -160,14 +133,6 @@ class LUTPass extends ShaderPass { } - constructor( options = {} ) { - - super( LUTShader ); - this.lut = options.lut || null; - this.intensity = 'intensity' in options ? options.intensity : 1; - - } - } export { LUTPass }; diff --git a/examples/jsm/postprocessing/MaskPass.js b/examples/jsm/postprocessing/MaskPass.js index c51eb00f0872e3..3a2afff8cf261f 100644 --- a/examples/jsm/postprocessing/MaskPass.js +++ b/examples/jsm/postprocessing/MaskPass.js @@ -1,21 +1,82 @@ import { Pass } from './Pass.js'; +/** + * This pass can be used to define a mask during post processing. + * Meaning only areas of subsequent post processing are affected + * which lie in the masking area of this pass. Internally, the masking + * is implemented with the stencil buffer. + * + * ```js + * const maskPass = new MaskPass( scene, camera ); + * composer.addPass( maskPass ); + * ``` + * + * @augments Pass + * @three_import import { MaskPass } from 'three/addons/postprocessing/MaskPass.js'; + */ class MaskPass extends Pass { + /** + * Constructs a new mask pass. + * + * @param {Scene} scene - The 3D objects in this scene will define the mask. + * @param {Camera} camera - The camera. + */ constructor( scene, camera ) { super(); + /** + * The scene that defines the mask. + * + * @type {Scene} + */ this.scene = scene; + + /** + * The camera. + * + * @type {Camera} + */ this.camera = camera; + /** + * Overwritten to perform a clear operation by default. + * + * @type {boolean} + * @default true + */ this.clear = true; + + /** + * Overwritten to disable the swap. + * + * @type {boolean} + * @default false + */ this.needsSwap = false; + /** + * Whether to inverse the mask or not. + * + * @type {boolean} + * @default false + */ this.inverse = false; } + /** + * Performs a mask pass with the configured scene and camera. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) { const context = renderer.getContext(); @@ -63,11 +124,14 @@ class MaskPass extends Pass { if ( this.clear ) renderer.clear(); renderer.render( this.scene, this.camera ); - // unlock color and depth buffer for subsequent rendering + // unlock color and depth buffer and make them writable for subsequent rendering/clearing state.buffers.color.setLocked( false ); state.buffers.depth.setLocked( false ); + state.buffers.color.setMask( true ); + state.buffers.depth.setMask( true ); + // only render where stencil is set to 1 state.buffers.stencil.setLocked( false ); @@ -79,16 +143,46 @@ class MaskPass extends Pass { } +/** + * This pass can be used to clear a mask previously defined with {@link MaskPass}. + * + * ```js + * const clearPass = new ClearMaskPass(); + * composer.addPass( clearPass ); + * ``` + * + * @augments Pass + */ class ClearMaskPass extends Pass { + /** + * Constructs a new clear mask pass. + */ constructor() { super(); + /** + * Overwritten to disable the swap. + * + * @type {boolean} + * @default false + */ this.needsSwap = false; } + /** + * Performs the clear of the currently defined mask. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer /*, writeBuffer, readBuffer, deltaTime, maskActive */ ) { renderer.state.buffers.stencil.setLocked( false ); diff --git a/examples/jsm/postprocessing/OutlinePass.js b/examples/jsm/postprocessing/OutlinePass.js index c2755955ab4ea5..a34340d00630f9 100644 --- a/examples/jsm/postprocessing/OutlinePass.js +++ b/examples/jsm/postprocessing/OutlinePass.js @@ -2,6 +2,7 @@ import { AdditiveBlending, Color, DoubleSide, + HalfFloatType, Matrix4, MeshDepthMaterial, NoBlending, @@ -15,27 +16,138 @@ import { import { Pass, FullScreenQuad } from './Pass.js'; import { CopyShader } from '../shaders/CopyShader.js'; +/** + * A pass for rendering outlines around selected objects. + * + * ```js + * const resolution = new THREE.Vector2( window.innerWidth, window.innerHeight ); + * const outlinePass = new OutlinePass( resolution, scene, camera ); + * composer.addPass( outlinePass ); + * ``` + * + * @augments Pass + * @three_import import { OutlinePass } from 'three/addons/postprocessing/OutlinePass.js'; + */ class OutlinePass extends Pass { + /** + * Constructs a new outline pass. + * + * @param {Vector2} [resolution] - The effect's resolution. + * @param {Scene} scene - The scene to render. + * @param {Camera} camera - The camera. + * @param {Array} [selectedObjects] - The selected 3D objects that should receive an outline. + * + */ constructor( resolution, scene, camera, selectedObjects ) { super(); + /** + * The scene to render. + * + * @type {Object} + */ this.renderScene = scene; + + /** + * The camera. + * + * @type {Object} + */ this.renderCamera = camera; + + /** + * The selected 3D objects that should receive an outline. + * + * @type {Array} + */ this.selectedObjects = selectedObjects !== undefined ? selectedObjects : []; + + /** + * The visible edge color. + * + * @type {Color} + * @default (1,1,1) + */ this.visibleEdgeColor = new Color( 1, 1, 1 ); + + /** + * The hidden edge color. + * + * @type {Color} + * @default (0.1,0.04,0.02) + */ this.hiddenEdgeColor = new Color( 0.1, 0.04, 0.02 ); + + /** + * Can be used for an animated glow/pulse effect. + * + * @type {number} + * @default 0 + */ this.edgeGlow = 0.0; + + /** + * Whether to use a pattern texture for to highlight selected + * 3D objects or not. + * + * @type {boolean} + * @default false + */ this.usePatternTexture = false; + + /** + * Can be used to highlight selected 3D objects. Requires to set + * {@link OutlinePass#usePatternTexture} to `true`. + * + * @type {?Texture} + * @default null + */ + this.patternTexture = null; + + /** + * The edge thickness. + * + * @type {number} + * @default 1 + */ this.edgeThickness = 1.0; + + /** + * The edge strength. + * + * @type {number} + * @default 3 + */ this.edgeStrength = 3.0; + + /** + * The downsample ratio. The effect can be rendered in a much + * lower resolution than the beauty pass. + * + * @type {number} + * @default 2 + */ this.downSampleRatio = 2; + + /** + * The pulse period. + * + * @type {number} + * @default 0 + */ this.pulsePeriod = 0; this._visibilityCache = new Map(); - - + this._selectionCache = new Set(); + + /** + * The effect's resolution. + * + * @type {Vector2} + * @default (256,256) + */ this.resolution = ( resolution !== undefined ) ? new Vector2( resolution.x, resolution.y ) : new Vector2( 256, 256 ); const resx = Math.round( this.resolution.x / this.downSampleRatio ); @@ -50,52 +162,51 @@ class OutlinePass extends Pass { this.depthMaterial.depthPacking = RGBADepthPacking; this.depthMaterial.blending = NoBlending; - this.prepareMaskMaterial = this.getPrepareMaskMaterial(); + this.prepareMaskMaterial = this._getPrepareMaskMaterial(); this.prepareMaskMaterial.side = DoubleSide; this.prepareMaskMaterial.fragmentShader = replaceDepthToViewZ( this.prepareMaskMaterial.fragmentShader, this.renderCamera ); - this.renderTargetDepthBuffer = new WebGLRenderTarget( this.resolution.x, this.resolution.y ); + this.renderTargetDepthBuffer = new WebGLRenderTarget( this.resolution.x, this.resolution.y, { type: HalfFloatType } ); this.renderTargetDepthBuffer.texture.name = 'OutlinePass.depth'; this.renderTargetDepthBuffer.texture.generateMipmaps = false; - this.renderTargetMaskDownSampleBuffer = new WebGLRenderTarget( resx, resy ); + this.renderTargetMaskDownSampleBuffer = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } ); this.renderTargetMaskDownSampleBuffer.texture.name = 'OutlinePass.depthDownSample'; this.renderTargetMaskDownSampleBuffer.texture.generateMipmaps = false; - this.renderTargetBlurBuffer1 = new WebGLRenderTarget( resx, resy ); + this.renderTargetBlurBuffer1 = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } ); this.renderTargetBlurBuffer1.texture.name = 'OutlinePass.blur1'; this.renderTargetBlurBuffer1.texture.generateMipmaps = false; - this.renderTargetBlurBuffer2 = new WebGLRenderTarget( Math.round( resx / 2 ), Math.round( resy / 2 ) ); + this.renderTargetBlurBuffer2 = new WebGLRenderTarget( Math.round( resx / 2 ), Math.round( resy / 2 ), { type: HalfFloatType } ); this.renderTargetBlurBuffer2.texture.name = 'OutlinePass.blur2'; this.renderTargetBlurBuffer2.texture.generateMipmaps = false; - this.edgeDetectionMaterial = this.getEdgeDetectionMaterial(); - this.renderTargetEdgeBuffer1 = new WebGLRenderTarget( resx, resy ); + this.edgeDetectionMaterial = this._getEdgeDetectionMaterial(); + this.renderTargetEdgeBuffer1 = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } ); this.renderTargetEdgeBuffer1.texture.name = 'OutlinePass.edge1'; this.renderTargetEdgeBuffer1.texture.generateMipmaps = false; - this.renderTargetEdgeBuffer2 = new WebGLRenderTarget( Math.round( resx / 2 ), Math.round( resy / 2 ) ); + this.renderTargetEdgeBuffer2 = new WebGLRenderTarget( Math.round( resx / 2 ), Math.round( resy / 2 ), { type: HalfFloatType } ); this.renderTargetEdgeBuffer2.texture.name = 'OutlinePass.edge2'; this.renderTargetEdgeBuffer2.texture.generateMipmaps = false; const MAX_EDGE_THICKNESS = 4; const MAX_EDGE_GLOW = 4; - this.separableBlurMaterial1 = this.getSeperableBlurMaterial( MAX_EDGE_THICKNESS ); + this.separableBlurMaterial1 = this._getSeparableBlurMaterial( MAX_EDGE_THICKNESS ); this.separableBlurMaterial1.uniforms[ 'texSize' ].value.set( resx, resy ); this.separableBlurMaterial1.uniforms[ 'kernelRadius' ].value = 1; - this.separableBlurMaterial2 = this.getSeperableBlurMaterial( MAX_EDGE_GLOW ); + this.separableBlurMaterial2 = this._getSeparableBlurMaterial( MAX_EDGE_GLOW ); this.separableBlurMaterial2.uniforms[ 'texSize' ].value.set( Math.round( resx / 2 ), Math.round( resy / 2 ) ); this.separableBlurMaterial2.uniforms[ 'kernelRadius' ].value = MAX_EDGE_GLOW; // Overlay material - this.overlayMaterial = this.getOverlayMaterial(); + this.overlayMaterial = this._getOverlayMaterial(); // copy material const copyShader = CopyShader; this.copyUniforms = UniformsUtils.clone( copyShader.uniforms ); - this.copyUniforms[ 'opacity' ].value = 1.0; this.materialCopy = new ShaderMaterial( { uniforms: this.copyUniforms, @@ -103,8 +214,7 @@ class OutlinePass extends Pass { fragmentShader: copyShader.fragmentShader, blending: NoBlending, depthTest: false, - depthWrite: false, - transparent: true + depthWrite: false } ); this.enabled = true; @@ -113,7 +223,7 @@ class OutlinePass extends Pass { this._oldClearColor = new Color(); this.oldClearAlpha = 1; - this.fsQuad = new FullScreenQuad( null ); + this._fsQuad = new FullScreenQuad( null ); this.tempPulseColor1 = new Color(); this.tempPulseColor2 = new Color(); @@ -129,6 +239,10 @@ class OutlinePass extends Pass { } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ dispose() { this.renderTargetMaskBuffer.dispose(); @@ -147,10 +261,16 @@ class OutlinePass extends Pass { this.overlayMaterial.dispose(); this.materialCopy.dispose(); - this.fsQuad.dispose(); + this._fsQuad.dispose(); } + /** + * Sets the size of the pass. + * + * @param {number} width - The width to set. + * @param {number} height - The height to set. + */ setSize( width, height ) { this.renderTargetMaskBuffer.setSize( width, height ); @@ -173,126 +293,17 @@ class OutlinePass extends Pass { } - changeVisibilityOfSelectedObjects( bVisible ) { - - const cache = this._visibilityCache; - - function gatherSelectedMeshesCallBack( object ) { - - if ( object.isMesh ) { - - if ( bVisible === true ) { - - object.visible = cache.get( object ); - - } else { - - cache.set( object, object.visible ); - object.visible = bVisible; - - } - - } - - } - - for ( let i = 0; i < this.selectedObjects.length; i ++ ) { - - const selectedObject = this.selectedObjects[ i ]; - selectedObject.traverse( gatherSelectedMeshesCallBack ); - - } - - } - - changeVisibilityOfNonSelectedObjects( bVisible ) { - - const cache = this._visibilityCache; - const selectedMeshes = []; - - function gatherSelectedMeshesCallBack( object ) { - - if ( object.isMesh ) selectedMeshes.push( object ); - - } - - for ( let i = 0; i < this.selectedObjects.length; i ++ ) { - - const selectedObject = this.selectedObjects[ i ]; - selectedObject.traverse( gatherSelectedMeshesCallBack ); - - } - - function VisibilityChangeCallBack( object ) { - - if ( object.isMesh || object.isSprite ) { - - // only meshes and sprites are supported by OutlinePass - - let bFound = false; - - for ( let i = 0; i < selectedMeshes.length; i ++ ) { - - const selectedObjectId = selectedMeshes[ i ].id; - - if ( selectedObjectId === object.id ) { - - bFound = true; - break; - - } - - } - - if ( bFound === false ) { - - const visibility = object.visible; - - if ( bVisible === false || cache.get( object ) === true ) { - - object.visible = bVisible; - - } - - cache.set( object, visibility ); - - } - - } else if ( object.isPoints || object.isLine ) { - - // the visibilty of points and lines is always set to false in order to - // not affect the outline computation - - if ( bVisible === true ) { - - object.visible = cache.get( object ); // restore - - } else { - - cache.set( object, object.visible ); - object.visible = bVisible; - - } - - } - - } - - this.renderScene.traverse( VisibilityChangeCallBack ); - - } - - updateTextureMatrix() { - - this.textureMatrix.set( 0.5, 0.0, 0.0, 0.5, - 0.0, 0.5, 0.0, 0.5, - 0.0, 0.0, 0.5, 0.5, - 0.0, 0.0, 0.0, 1.0 ); - this.textureMatrix.multiply( this.renderCamera.projectionMatrix ); - this.textureMatrix.multiply( this.renderCamera.matrixWorldInverse ); - - } - + /** + * Performs the Outline pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer, deltaTime, maskActive ) { if ( this.selectedObjects.length > 0 ) { @@ -307,10 +318,13 @@ class OutlinePass extends Pass { renderer.setClearColor( 0xffffff, 1 ); + this._updateSelectionCache(); + // Make selected objects invisible - this.changeVisibilityOfSelectedObjects( false ); + this._changeVisibilityOfSelectedObjects( false ); const currentBackground = this.renderScene.background; + const currentOverrideMaterial = this.renderScene.overrideMaterial; this.renderScene.background = null; // 1. Draw Non Selected objects in the depth buffer @@ -320,14 +334,14 @@ class OutlinePass extends Pass { renderer.render( this.renderScene, this.renderCamera ); // Make selected objects visible - this.changeVisibilityOfSelectedObjects( true ); + this._changeVisibilityOfSelectedObjects( true ); this._visibilityCache.clear(); // Update Texture Matrix for Depth compare - this.updateTextureMatrix(); + this._updateTextureMatrix(); // Make non selected objects invisible, and draw only the selected objects, by comparing the depth buffer of non selected objects - this.changeVisibilityOfNonSelectedObjects( false ); + this._changeVisibilityOfNonSelectedObjects( false ); this.renderScene.overrideMaterial = this.prepareMaskMaterial; this.prepareMaskMaterial.uniforms[ 'cameraNearFar' ].value.set( this.renderCamera.near, this.renderCamera.far ); this.prepareMaskMaterial.uniforms[ 'depthTexture' ].value = this.renderTargetDepthBuffer.texture; @@ -335,18 +349,19 @@ class OutlinePass extends Pass { renderer.setRenderTarget( this.renderTargetMaskBuffer ); renderer.clear(); renderer.render( this.renderScene, this.renderCamera ); - this.renderScene.overrideMaterial = null; - this.changeVisibilityOfNonSelectedObjects( true ); + this._changeVisibilityOfNonSelectedObjects( true ); this._visibilityCache.clear(); + this._selectionCache.clear(); this.renderScene.background = currentBackground; + this.renderScene.overrideMaterial = currentOverrideMaterial; // 2. Downsample to Half resolution - this.fsQuad.material = this.materialCopy; + this._fsQuad.material = this.materialCopy; this.copyUniforms[ 'tDiffuse' ].value = this.renderTargetMaskBuffer.texture; renderer.setRenderTarget( this.renderTargetMaskDownSampleBuffer ); renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); this.tempPulseColor1.copy( this.visibleEdgeColor ); this.tempPulseColor2.copy( this.hiddenEdgeColor ); @@ -360,44 +375,44 @@ class OutlinePass extends Pass { } // 3. Apply Edge Detection Pass - this.fsQuad.material = this.edgeDetectionMaterial; + this._fsQuad.material = this.edgeDetectionMaterial; this.edgeDetectionMaterial.uniforms[ 'maskTexture' ].value = this.renderTargetMaskDownSampleBuffer.texture; this.edgeDetectionMaterial.uniforms[ 'texSize' ].value.set( this.renderTargetMaskDownSampleBuffer.width, this.renderTargetMaskDownSampleBuffer.height ); this.edgeDetectionMaterial.uniforms[ 'visibleEdgeColor' ].value = this.tempPulseColor1; this.edgeDetectionMaterial.uniforms[ 'hiddenEdgeColor' ].value = this.tempPulseColor2; renderer.setRenderTarget( this.renderTargetEdgeBuffer1 ); renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); // 4. Apply Blur on Half res - this.fsQuad.material = this.separableBlurMaterial1; + this._fsQuad.material = this.separableBlurMaterial1; this.separableBlurMaterial1.uniforms[ 'colorTexture' ].value = this.renderTargetEdgeBuffer1.texture; this.separableBlurMaterial1.uniforms[ 'direction' ].value = OutlinePass.BlurDirectionX; this.separableBlurMaterial1.uniforms[ 'kernelRadius' ].value = this.edgeThickness; renderer.setRenderTarget( this.renderTargetBlurBuffer1 ); renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); this.separableBlurMaterial1.uniforms[ 'colorTexture' ].value = this.renderTargetBlurBuffer1.texture; this.separableBlurMaterial1.uniforms[ 'direction' ].value = OutlinePass.BlurDirectionY; renderer.setRenderTarget( this.renderTargetEdgeBuffer1 ); renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); // Apply Blur on quarter res - this.fsQuad.material = this.separableBlurMaterial2; + this._fsQuad.material = this.separableBlurMaterial2; this.separableBlurMaterial2.uniforms[ 'colorTexture' ].value = this.renderTargetEdgeBuffer1.texture; this.separableBlurMaterial2.uniforms[ 'direction' ].value = OutlinePass.BlurDirectionX; renderer.setRenderTarget( this.renderTargetBlurBuffer2 ); renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); this.separableBlurMaterial2.uniforms[ 'colorTexture' ].value = this.renderTargetBlurBuffer2.texture; this.separableBlurMaterial2.uniforms[ 'direction' ].value = OutlinePass.BlurDirectionY; renderer.setRenderTarget( this.renderTargetEdgeBuffer2 ); renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); // Blend it additively over the input texture - this.fsQuad.material = this.overlayMaterial; + this._fsQuad.material = this.overlayMaterial; this.overlayMaterial.uniforms[ 'maskTexture' ].value = this.renderTargetMaskBuffer.texture; this.overlayMaterial.uniforms[ 'edgeTexture1' ].value = this.renderTargetEdgeBuffer1.texture; this.overlayMaterial.uniforms[ 'edgeTexture2' ].value = this.renderTargetEdgeBuffer2.texture; @@ -410,7 +425,7 @@ class OutlinePass extends Pass { if ( maskActive ) renderer.state.buffers.stencil.setTest( true ); renderer.setRenderTarget( readBuffer ); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); renderer.setClearColor( this._oldClearColor, this.oldClearAlpha ); renderer.autoClear = oldAutoClear; @@ -419,16 +434,120 @@ class OutlinePass extends Pass { if ( this.renderToScreen ) { - this.fsQuad.material = this.materialCopy; + this._fsQuad.material = this.materialCopy; this.copyUniforms[ 'tDiffuse' ].value = readBuffer.texture; renderer.setRenderTarget( null ); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); + + } + + } + + // internals + + _updateSelectionCache() { + + const cache = this._selectionCache; + + function gatherSelectedMeshesCallBack( object ) { + + if ( object.isMesh ) cache.add( object ); + + } + + cache.clear(); + + for ( let i = 0; i < this.selectedObjects.length; i ++ ) { + + const selectedObject = this.selectedObjects[ i ]; + selectedObject.traverse( gatherSelectedMeshesCallBack ); } } - getPrepareMaskMaterial() { + _changeVisibilityOfSelectedObjects( bVisible ) { + + const cache = this._visibilityCache; + + for ( const mesh of this._selectionCache ) { + + if ( bVisible === true ) { + + mesh.visible = cache.get( mesh ); + + } else { + + cache.set( mesh, mesh.visible ); + mesh.visible = bVisible; + + } + + } + + } + + _changeVisibilityOfNonSelectedObjects( bVisible ) { + + const visibilityCache = this._visibilityCache; + const selectionCache = this._selectionCache; + + function VisibilityChangeCallBack( object ) { + + if ( object.isMesh || object.isSprite ) { + + // only meshes and sprites are supported by OutlinePass + + if ( ! selectionCache.has( object ) ) { + + const visibility = object.visible; + + if ( bVisible === false || visibilityCache.get( object ) === true ) { + + object.visible = bVisible; + + } + + visibilityCache.set( object, visibility ); + + } + + } else if ( object.isPoints || object.isLine ) { + + // the visibility of points and lines is always set to false in order to + // not affect the outline computation + + if ( bVisible === true ) { + + object.visible = visibilityCache.get( object ); // restore + + } else { + + visibilityCache.set( object, object.visible ); + object.visible = bVisible; + + } + + } + + } + + this.renderScene.traverse( VisibilityChangeCallBack ); + + } + + _updateTextureMatrix() { + + this.textureMatrix.set( 0.5, 0.0, 0.0, 0.5, + 0.0, 0.5, 0.0, 0.5, + 0.0, 0.0, 0.5, 0.5, + 0.0, 0.0, 0.0, 1.0 ); + this.textureMatrix.multiply( this.renderCamera.projectionMatrix ); + this.textureMatrix.multiply( this.renderCamera.matrixWorldInverse ); + + } + + _getPrepareMaskMaterial() { return new ShaderMaterial( { @@ -439,7 +558,8 @@ class OutlinePass extends Pass { }, vertexShader: - `#include + `#include + #include #include varying vec4 projTexCoord; @@ -448,6 +568,7 @@ class OutlinePass extends Pass { void main() { + #include #include #include #include @@ -463,7 +584,7 @@ class OutlinePass extends Pass { worldPosition = instanceMatrix * worldPosition; #endif - + worldPosition = modelMatrix * worldPosition; projTexCoord = textureMatrix * worldPosition; @@ -490,7 +611,7 @@ class OutlinePass extends Pass { } - getEdgeDetectionMaterial() { + _getEdgeDetectionMaterial() { return new ShaderMaterial( { @@ -537,7 +658,7 @@ class OutlinePass extends Pass { } - getSeperableBlurMaterial( maxRadius ) { + _getSeparableBlurMaterial( maxRadius ) { return new ShaderMaterial( { @@ -594,7 +715,7 @@ class OutlinePass extends Pass { } - getOverlayMaterial() { + _getOverlayMaterial() { return new ShaderMaterial( { diff --git a/examples/jsm/postprocessing/OutputPass.js b/examples/jsm/postprocessing/OutputPass.js new file mode 100644 index 00000000000000..1148de644671f9 --- /dev/null +++ b/examples/jsm/postprocessing/OutputPass.js @@ -0,0 +1,139 @@ +import { + ColorManagement, + RawShaderMaterial, + UniformsUtils, + LinearToneMapping, + ReinhardToneMapping, + CineonToneMapping, + AgXToneMapping, + ACESFilmicToneMapping, + NeutralToneMapping, + CustomToneMapping, + SRGBTransfer +} from 'three'; +import { Pass, FullScreenQuad } from './Pass.js'; +import { OutputShader } from '../shaders/OutputShader.js'; + +/** + * This pass is responsible for including tone mapping and color space conversion + * into your pass chain. In most cases, this pass should be included at the end + * of each pass chain. If a pass requires sRGB input (e.g. like FXAA), the pass + * must follow `OutputPass` in the pass chain. + * + * The tone mapping and color space settings are extracted from the renderer. + * + * ```js + * const outputPass = new OutputPass(); + * composer.addPass( outputPass ); + * ``` + * + * @augments Pass + * @three_import import { OutputPass } from 'three/addons/postprocessing/OutputPass.js'; + */ +class OutputPass extends Pass { + + /** + * Constructs a new output pass. + */ + constructor() { + + super(); + + /** + * The pass uniforms. + * + * @type {Object} + */ + this.uniforms = UniformsUtils.clone( OutputShader.uniforms ); + + /** + * The pass material. + * + * @type {RawShaderMaterial} + */ + this.material = new RawShaderMaterial( { + name: OutputShader.name, + uniforms: this.uniforms, + vertexShader: OutputShader.vertexShader, + fragmentShader: OutputShader.fragmentShader + } ); + + // internals + + this._fsQuad = new FullScreenQuad( this.material ); + + this._outputColorSpace = null; + this._toneMapping = null; + + } + + /** + * Performs the output pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ + render( renderer, writeBuffer, readBuffer/*, deltaTime, maskActive */ ) { + + this.uniforms[ 'tDiffuse' ].value = readBuffer.texture; + this.uniforms[ 'toneMappingExposure' ].value = renderer.toneMappingExposure; + + // rebuild defines if required + + if ( this._outputColorSpace !== renderer.outputColorSpace || this._toneMapping !== renderer.toneMapping ) { + + this._outputColorSpace = renderer.outputColorSpace; + this._toneMapping = renderer.toneMapping; + + this.material.defines = {}; + + if ( ColorManagement.getTransfer( this._outputColorSpace ) === SRGBTransfer ) this.material.defines.SRGB_TRANSFER = ''; + + if ( this._toneMapping === LinearToneMapping ) this.material.defines.LINEAR_TONE_MAPPING = ''; + else if ( this._toneMapping === ReinhardToneMapping ) this.material.defines.REINHARD_TONE_MAPPING = ''; + else if ( this._toneMapping === CineonToneMapping ) this.material.defines.CINEON_TONE_MAPPING = ''; + else if ( this._toneMapping === ACESFilmicToneMapping ) this.material.defines.ACES_FILMIC_TONE_MAPPING = ''; + else if ( this._toneMapping === AgXToneMapping ) this.material.defines.AGX_TONE_MAPPING = ''; + else if ( this._toneMapping === NeutralToneMapping ) this.material.defines.NEUTRAL_TONE_MAPPING = ''; + else if ( this._toneMapping === CustomToneMapping ) this.material.defines.CUSTOM_TONE_MAPPING = ''; + + this.material.needsUpdate = true; + + } + + // + + if ( this.renderToScreen === true ) { + + renderer.setRenderTarget( null ); + this._fsQuad.render( renderer ); + + } else { + + renderer.setRenderTarget( writeBuffer ); + if ( this.clear ) renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil ); + this._fsQuad.render( renderer ); + + } + + } + + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ + dispose() { + + this.material.dispose(); + this._fsQuad.dispose(); + + } + +} + +export { OutputPass }; diff --git a/examples/jsm/postprocessing/Pass.js b/examples/jsm/postprocessing/Pass.js index d00ad47064ef87..50acb4784ee0d6 100644 --- a/examples/jsm/postprocessing/Pass.js +++ b/examples/jsm/postprocessing/Pass.js @@ -5,34 +5,98 @@ import { Mesh } from 'three'; +/** + * Abstract base class for all post processing passes. + * + * This module is only relevant for post processing with {@link WebGLRenderer}. + * + * @abstract + * @three_import import { Pass } from 'three/addons/postprocessing/Pass.js'; + */ class Pass { + /** + * Constructs a new pass. + */ constructor() { + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isPass = true; - // if set to true, the pass is processed by the composer + /** + * If set to `true`, the pass is processed by the composer. + * + * @type {boolean} + * @default true + */ this.enabled = true; - // if set to true, the pass indicates to swap read and write buffer after rendering + /** + * If set to `true`, the pass indicates to swap read and write buffer after rendering. + * + * @type {boolean} + * @default true + */ this.needsSwap = true; - // if set to true, the pass clears its buffer before rendering + /** + * If set to `true`, the pass clears its buffer before rendering + * + * @type {boolean} + * @default false + */ this.clear = false; - // if set to true, the result of the pass is rendered to screen. This is set automatically by EffectComposer. + /** + * If set to `true`, the result of the pass is rendered to screen. The last pass in the composers + * pass chain gets automatically rendered to screen, no matter how this property is configured. + * + * @type {boolean} + * @default false + */ this.renderToScreen = false; } + /** + * Sets the size of the pass. + * + * @abstract + * @param {number} width - The width to set. + * @param {number} height - The height to set. + */ setSize( /* width, height */ ) {} + /** + * This method holds the render logic of a pass. It must be implemented in all derived classes. + * + * @abstract + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( /* renderer, writeBuffer, readBuffer, deltaTime, maskActive */ ) { console.error( 'THREE.Pass: .render() must be implemented in derived pass.' ); } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + * + * @abstract + */ dispose() {} } @@ -43,30 +107,73 @@ const _camera = new OrthographicCamera( - 1, 1, 1, - 1, 0, 1 ); // https://github.com/mrdoob/three.js/pull/21358 -const _geometry = new BufferGeometry(); -_geometry.setAttribute( 'position', new Float32BufferAttribute( [ - 1, 3, 0, - 1, - 1, 0, 3, - 1, 0 ], 3 ) ); -_geometry.setAttribute( 'uv', new Float32BufferAttribute( [ 0, 2, 0, 0, 2, 0 ], 2 ) ); +class FullscreenTriangleGeometry extends BufferGeometry { + constructor() { + + super(); + + this.setAttribute( 'position', new Float32BufferAttribute( [ - 1, 3, 0, - 1, - 1, 0, 3, - 1, 0 ], 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( [ 0, 2, 0, 0, 2, 0 ], 2 ) ); + + } + +} + +const _geometry = new FullscreenTriangleGeometry(); + + +/** + * This module is a helper for passes which need to render a full + * screen effect which is quite common in context of post processing. + * + * The intended usage is to reuse a single full screen quad for rendering + * subsequent passes by just reassigning the `material` reference. + * + * This module can only be used with {@link WebGLRenderer}. + * + * @augments Mesh + * @three_import import { FullScreenQuad } from 'three/addons/postprocessing/Pass.js'; + */ class FullScreenQuad { + /** + * Constructs a new full screen quad. + * + * @param {?Material} material - The material to render te full screen quad with. + */ constructor( material ) { this._mesh = new Mesh( _geometry, material ); } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the instance is no longer used in your app. + */ dispose() { this._mesh.geometry.dispose(); } + /** + * Renders the full screen quad. + * + * @param {WebGLRenderer} renderer - The renderer. + */ render( renderer ) { renderer.render( this._mesh, _camera ); } + /** + * The quad's material. + * + * @type {?Material} + */ get material() { return this._mesh.material; diff --git a/examples/jsm/postprocessing/RenderPass.js b/examples/jsm/postprocessing/RenderPass.js index 333b2fe7e549b3..c85f0e559a7a78 100644 --- a/examples/jsm/postprocessing/RenderPass.js +++ b/examples/jsm/postprocessing/RenderPass.js @@ -3,27 +3,111 @@ import { } from 'three'; import { Pass } from './Pass.js'; +/** + * This class represents a render pass. It takes a camera and a scene and produces + * a beauty pass for subsequent post processing effects. + * + * ```js + * const renderPass = new RenderPass( scene, camera ); + * composer.addPass( renderPass ); + * ``` + * + * @augments Pass + * @three_import import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'; + */ class RenderPass extends Pass { - constructor( scene, camera, overrideMaterial, clearColor, clearAlpha ) { + /** + * Constructs a new render pass. + * + * @param {Scene} scene - The scene to render. + * @param {Camera} camera - The camera. + * @param {?Material} [overrideMaterial=null] - The override material. If set, this material is used + * for all objects in the scene. + * @param {?(number|Color|string)} [clearColor=null] - The clear color of the render pass. + * @param {?number} [clearAlpha=null] - The clear alpha of the render pass. + */ + constructor( scene, camera, overrideMaterial = null, clearColor = null, clearAlpha = null ) { super(); + /** + * The scene to render. + * + * @type {Scene} + */ this.scene = scene; + + /** + * The camera. + * + * @type {Camera} + */ this.camera = camera; + /** + * The override material. If set, this material is used + * for all objects in the scene. + * + * @type {?Material} + * @default null + */ this.overrideMaterial = overrideMaterial; + /** + * The clear color of the render pass. + * + * @type {?(number|Color|string)} + * @default null + */ this.clearColor = clearColor; - this.clearAlpha = ( clearAlpha !== undefined ) ? clearAlpha : 0; + /** + * The clear alpha of the render pass. + * + * @type {?number} + * @default null + */ + this.clearAlpha = clearAlpha; + + /** + * Overwritten to perform a clear operation by default. + * + * @type {boolean} + * @default true + */ this.clear = true; + + /** + * If set to `true`, only the depth can be cleared when `clear` is to `false`. + * + * @type {boolean} + * @default false + */ this.clearDepth = false; + + /** + * Overwritten to disable the swap. + * + * @type {boolean} + * @default false + */ this.needsSwap = false; this._oldClearColor = new Color(); } + /** + * Performs a beauty pass with the configured scene and camera. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) { const oldAutoClear = renderer.autoClear; @@ -31,7 +115,7 @@ class RenderPass extends Pass { let oldClearAlpha, oldOverrideMaterial; - if ( this.overrideMaterial !== undefined ) { + if ( this.overrideMaterial !== null ) { oldOverrideMaterial = this.scene.overrideMaterial; @@ -39,16 +123,21 @@ class RenderPass extends Pass { } - if ( this.clearColor ) { + if ( this.clearColor !== null ) { renderer.getClearColor( this._oldClearColor ); - oldClearAlpha = renderer.getClearAlpha(); + renderer.setClearColor( this.clearColor, renderer.getClearAlpha() ); - renderer.setClearColor( this.clearColor, this.clearAlpha ); + } + + if ( this.clearAlpha !== null ) { + + oldClearAlpha = renderer.getClearAlpha(); + renderer.setClearAlpha( this.clearAlpha ); } - if ( this.clearDepth ) { + if ( this.clearDepth == true ) { renderer.clearDepth(); @@ -56,17 +145,30 @@ class RenderPass extends Pass { 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 ); + if ( this.clear === true ) { + + // TODO: Avoid using autoClear properties, see https://github.com/mrdoob/three.js/pull/15571#issuecomment-465669600 + renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil ); + + } + renderer.render( this.scene, this.camera ); - if ( this.clearColor ) { + // restore + + if ( this.clearColor !== null ) { + + renderer.setClearColor( this._oldClearColor ); + + } + + if ( this.clearAlpha !== null ) { - renderer.setClearColor( this._oldClearColor, oldClearAlpha ); + renderer.setClearAlpha( oldClearAlpha ); } - if ( this.overrideMaterial !== undefined ) { + if ( this.overrideMaterial !== null ) { this.scene.overrideMaterial = oldOverrideMaterial; diff --git a/examples/jsm/postprocessing/RenderPixelatedPass.js b/examples/jsm/postprocessing/RenderPixelatedPass.js index 87096539fc0649..4eebcfa3f5ab83 100644 --- a/examples/jsm/postprocessing/RenderPixelatedPass.js +++ b/examples/jsm/postprocessing/RenderPixelatedPass.js @@ -5,91 +5,176 @@ import { Vector2, Vector4, DepthTexture, - NearestFilter + NearestFilter, + HalfFloatType } from 'three'; import { Pass, FullScreenQuad } from './Pass.js'; +/** + * A special type of render pass that produces a pixelated beauty pass. + * + * ```js + * const renderPixelatedPass = new RenderPixelatedPass( 6, scene, camera ); + * composer.addPass( renderPixelatedPass ); + * ``` + * + * @augments Pass + * @three_import import { RenderPixelatedPass } from 'three/addons/postprocessing/RenderPixelatedPass.js'; + */ class RenderPixelatedPass extends Pass { + /** + * Constructs a new render pixelated pass. + * + * @param {number} pixelSize - The effect's pixel size. + * @param {Scene} scene - The scene to render. + * @param {Camera} camera - The camera. + * @param {{normalEdgeStrength:number,depthEdgeStrength:number}} options - The pass options. + */ constructor( pixelSize, scene, camera, options = {} ) { super(); + /** + * The effect's pixel size. + * + * @type {number} + */ this.pixelSize = pixelSize; - this.resolution = new Vector2(); - this.renderResolution = new Vector2(); - this.pixelatedMaterial = this.createPixelatedMaterial(); - this.normalMaterial = new MeshNormalMaterial(); - - this.fsQuad = new FullScreenQuad( this.pixelatedMaterial ); + /** + * The scene to render. + * + * @type {Scene} + */ this.scene = scene; + + /** + * The camera. + * + * @type {Camera} + */ this.camera = camera; + /** + * The normal edge strength. + * + * @type {number} + * @default 0.3 + */ this.normalEdgeStrength = options.normalEdgeStrength || 0.3; + + /** + * The normal edge strength. + * + * @type {number} + * @default 0.4 + */ this.depthEdgeStrength = options.depthEdgeStrength || 0.4; - this.beautyRenderTarget = new WebGLRenderTarget(); - this.beautyRenderTarget.texture.minFilter = NearestFilter; - this.beautyRenderTarget.texture.magFilter = NearestFilter; - this.beautyRenderTarget.depthTexture = new DepthTexture(); + /** + * The pixelated material. + * + * @type {ShaderMaterial} + */ + this.pixelatedMaterial = this._createPixelatedMaterial(); + + // internals - this.normalRenderTarget = new WebGLRenderTarget(); - this.normalRenderTarget.texture.minFilter = NearestFilter; - this.normalRenderTarget.texture.magFilter = NearestFilter; + this._resolution = new Vector2(); + this._renderResolution = new Vector2(); + this._normalMaterial = new MeshNormalMaterial(); + this._beautyRenderTarget = new WebGLRenderTarget(); + this._beautyRenderTarget.texture.minFilter = NearestFilter; + this._beautyRenderTarget.texture.magFilter = NearestFilter; + this._beautyRenderTarget.texture.type = HalfFloatType; + this._beautyRenderTarget.depthTexture = new DepthTexture(); + + this._normalRenderTarget = new WebGLRenderTarget(); + this._normalRenderTarget.texture.minFilter = NearestFilter; + this._normalRenderTarget.texture.magFilter = NearestFilter; + this._normalRenderTarget.texture.type = HalfFloatType; + + this._fsQuad = new FullScreenQuad( this.pixelatedMaterial ); } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ dispose() { - this.beautyRenderTarget.dispose(); - this.normalRenderTarget.dispose(); + this._beautyRenderTarget.dispose(); + this._normalRenderTarget.dispose(); this.pixelatedMaterial.dispose(); - this.normalMaterial.dispose(); + this._normalMaterial.dispose(); - this.fsQuad.dispose(); + this._fsQuad.dispose(); } + /** + * Sets the size of the pass. + * + * @param {number} width - The width to set. + * @param {number} height - The height to set. + */ setSize( width, height ) { - this.resolution.set( width, height ); - this.renderResolution.set( ( width / this.pixelSize ) | 0, ( height / this.pixelSize ) | 0 ); - const { x, y } = this.renderResolution; - this.beautyRenderTarget.setSize( x, y ); - this.normalRenderTarget.setSize( x, y ); - this.fsQuad.material.uniforms.resolution.value.set( x, y, 1 / x, 1 / y ); + this._resolution.set( width, height ); + this._renderResolution.set( ( width / this.pixelSize ) | 0, ( height / this.pixelSize ) | 0 ); + const { x, y } = this._renderResolution; + this._beautyRenderTarget.setSize( x, y ); + this._normalRenderTarget.setSize( x, y ); + this._fsQuad.material.uniforms.resolution.value.set( x, y, 1 / x, 1 / y ); } + /** + * Sets the effect's pixel size. + * + * @param {number} pixelSize - The pixel size to set. + */ setPixelSize( pixelSize ) { this.pixelSize = pixelSize; - this.setSize( this.resolution.x, this.resolution.y ); + this.setSize( this._resolution.x, this._resolution.y ); } - render( renderer, writeBuffer ) { - - const uniforms = this.fsQuad.material.uniforms; + /** + * Performs the pixelation pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ + render( renderer, writeBuffer/*, readBuffer , deltaTime, maskActive */ ) { + + const uniforms = this._fsQuad.material.uniforms; uniforms.normalEdgeStrength.value = this.normalEdgeStrength; uniforms.depthEdgeStrength.value = this.depthEdgeStrength; - renderer.setRenderTarget( this.beautyRenderTarget ); + renderer.setRenderTarget( this._beautyRenderTarget ); renderer.render( this.scene, this.camera ); const overrideMaterial_old = this.scene.overrideMaterial; - renderer.setRenderTarget( this.normalRenderTarget ); - this.scene.overrideMaterial = this.normalMaterial; + renderer.setRenderTarget( this._normalRenderTarget ); + this.scene.overrideMaterial = this._normalMaterial; renderer.render( this.scene, this.camera ); this.scene.overrideMaterial = overrideMaterial_old; - uniforms.tDiffuse.value = this.beautyRenderTarget.texture; - uniforms.tDepth.value = this.beautyRenderTarget.depthTexture; - uniforms.tNormal.value = this.normalRenderTarget.texture; + uniforms.tDiffuse.value = this._beautyRenderTarget.texture; + uniforms.tDepth.value = this._beautyRenderTarget.depthTexture; + uniforms.tNormal.value = this._normalRenderTarget.texture; if ( this.renderToScreen ) { @@ -103,25 +188,20 @@ class RenderPixelatedPass extends Pass { } - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } - createPixelatedMaterial() { + // internals + + _createPixelatedMaterial() { return new ShaderMaterial( { uniforms: { tDiffuse: { value: null }, tDepth: { value: null }, tNormal: { value: null }, - resolution: { - value: new Vector4( - this.renderResolution.x, - this.renderResolution.y, - 1 / this.renderResolution.x, - 1 / this.renderResolution.y, - ) - }, + resolution: { value: new Vector4() }, normalEdgeStrength: { value: 0 }, depthEdgeStrength: { value: 0 } }, diff --git a/examples/jsm/postprocessing/RenderTransitionPass.js b/examples/jsm/postprocessing/RenderTransitionPass.js new file mode 100644 index 00000000000000..4e1535bb9e4026 --- /dev/null +++ b/examples/jsm/postprocessing/RenderTransitionPass.js @@ -0,0 +1,267 @@ +import { + HalfFloatType, + ShaderMaterial, + WebGLRenderTarget +} from 'three'; +import { FullScreenQuad, Pass } from './Pass.js'; + +/** + * A special type of render pass for implementing transition effects. + * When active, the pass will transition from scene A to scene B. + * + * ```js + * const renderTransitionPass = new RenderTransitionPass( fxSceneA.scene, fxSceneA.camera, fxSceneB.scene, fxSceneB.camera ); + * renderTransitionPass.setTexture( textures[ 0 ] ); + * composer.addPass( renderTransitionPass ); + * ``` + * + * @augments Pass + * @three_import import { RenderTransitionPass } from 'three/addons/postprocessing/RenderTransitionPass.js'; + */ +class RenderTransitionPass extends Pass { + + /** + * Constructs a render transition pass. + * + * @param {Scene} sceneA - The first scene. + * @param {Camera} cameraA - The camera of the first scene. + * @param {Scene} sceneB - The second scene. + * @param {Camera} cameraB - The camera of the second scene. + */ + constructor( sceneA, cameraA, sceneB, cameraB ) { + + super(); + + /** + * The first scene. + * + * @type {Scene} + */ + this.sceneA = sceneA; + + + /** + * The camera of the first scene. + * + * @type {Camera} + */ + this.cameraA = cameraA; + + /** + * The second scene. + * + * @type {Scene} + */ + this.sceneB = sceneB; + + /** + * The camera of the second scene. + * + * @type {Camera} + */ + this.cameraB = cameraB; + + /** + * The pass material. + * + * @type {ShaderMaterial} + */ + this.material = this._createMaterial(); + + // internals + + this._renderTargetA = new WebGLRenderTarget(); + this._renderTargetA.texture.type = HalfFloatType; + this._renderTargetB = new WebGLRenderTarget(); + this._renderTargetB.texture.type = HalfFloatType; + + this._fsQuad = new FullScreenQuad( this.material ); + + } + + /** + * Sets the transition factor. Must be in the range `[0,1]`. + * This value determines to what degree both scenes are mixed. + * + * @param {boolean|number} value - The transition factor. + */ + setTransition( value ) { + + this.material.uniforms.mixRatio.value = value; + + } + + /** + * Toggles the usage of a texture for the effect. + * + * @param {boolean} value - Whether to use a texture for the transition effect or not. + */ + useTexture( value ) { + + this.material.uniforms.useTexture.value = value ? 1 : 0; + + } + + /** + * Sets the effect texture. + * + * @param {Texture} value - The effect texture. + */ + setTexture( value ) { + + this.material.uniforms.tMixTexture.value = value; + + } + + /** + * Sets the texture threshold. This value defined how strong the texture effects + * the transition. Must be in the range `[0,1]` (0 means full effect, 1 means no effect). + * + * @param {boolean|number} value - The threshold value. + */ + setTextureThreshold( value ) { + + this.material.uniforms.threshold.value = value; + + } + + /** + * Sets the size of the pass. + * + * @param {number} width - The width to set. + * @param {number} height - The height to set. + */ + setSize( width, height ) { + + this._renderTargetA.setSize( width, height ); + this._renderTargetB.setSize( width, height ); + + } + + /** + * Performs the transition pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ + render( renderer, writeBuffer/*, readBuffer , deltaTime, maskActive */ ) { + + renderer.setRenderTarget( this._renderTargetA ); + renderer.render( this.sceneA, this.cameraA ); + renderer.setRenderTarget( this._renderTargetB ); + renderer.render( this.sceneB, this.cameraB ); + + const uniforms = this._fsQuad.material.uniforms; + uniforms.tDiffuse1.value = this._renderTargetA.texture; + uniforms.tDiffuse2.value = this._renderTargetB.texture; + + if ( this.renderToScreen ) { + + renderer.setRenderTarget( null ); + renderer.clear(); + + } else { + + renderer.setRenderTarget( writeBuffer ); + if ( this.clear ) renderer.clear(); + + } + + this._fsQuad.render( renderer ); + + } + + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ + dispose() { + + this.material.dispose(); + + this._renderTargetA.dispose(); + this._renderTargetB.dispose(); + this._fsQuad.dispose(); + + } + + // internals + + _createMaterial() { + + return new ShaderMaterial( { + uniforms: { + tDiffuse1: { + value: null + }, + tDiffuse2: { + value: null + }, + mixRatio: { + value: 0.0 + }, + threshold: { + value: 0.1 + }, + useTexture: { + value: 1 + }, + tMixTexture: { + value: null + } + }, + vertexShader: /* glsl */` + varying vec2 vUv; + + void main() { + + vUv = vec2( uv.x, uv.y ); + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + + } + `, + fragmentShader: /* glsl */` + uniform float mixRatio; + + uniform sampler2D tDiffuse1; + uniform sampler2D tDiffuse2; + uniform sampler2D tMixTexture; + + uniform int useTexture; + uniform float threshold; + + varying vec2 vUv; + + void main() { + + vec4 texel1 = texture2D( tDiffuse1, vUv ); + vec4 texel2 = texture2D( tDiffuse2, vUv ); + + if (useTexture == 1) { + + vec4 transitionTexel = texture2D( tMixTexture, vUv ); + float r = mixRatio * ( 1.0 + threshold * 2.0 ) - threshold; + float mixf = clamp( ( transitionTexel.r - r ) * ( 1.0 / threshold ), 0.0, 1.0 ); + + gl_FragColor = mix( texel1, texel2, mixf ); + + } else { + + gl_FragColor = mix( texel2, texel1, mixRatio ); + + } + + } + ` + } ); + + } + +} + +export { RenderTransitionPass }; diff --git a/examples/jsm/postprocessing/SAOPass.js b/examples/jsm/postprocessing/SAOPass.js index c5378ce7e353c6..5956de5d60a904 100644 --- a/examples/jsm/postprocessing/SAOPass.js +++ b/examples/jsm/postprocessing/SAOPass.js @@ -5,48 +5,88 @@ import { DepthTexture, DstAlphaFactor, DstColorFactor, - MeshDepthMaterial, + HalfFloatType, MeshNormalMaterial, NearestFilter, NoBlending, - RGBADepthPacking, ShaderMaterial, UniformsUtils, - UnsignedShortType, + DepthStencilFormat, + UnsignedInt248Type, Vector2, WebGLRenderTarget, ZeroFactor } from 'three'; import { Pass, FullScreenQuad } from './Pass.js'; import { SAOShader } from '../shaders/SAOShader.js'; -import { DepthLimitedBlurShader } from '../shaders/DepthLimitedBlurShader.js'; -import { BlurShaderUtils } from '../shaders/DepthLimitedBlurShader.js'; +import { BlurShaderUtils, DepthLimitedBlurShader } from '../shaders/DepthLimitedBlurShader.js'; import { CopyShader } from '../shaders/CopyShader.js'; -import { UnpackDepthRGBAShader } from '../shaders/UnpackDepthRGBAShader.js'; /** - * SAO implementation inspired from bhouston previous SAO work + * A SAO implementation inspired from @bhouston previous SAO work. + * + * `SAOPass` provides better quality than {@link SSAOPass} but is also more expensive. + * + * ```js + * const saoPass = new SAOPass( scene, camera ); + * composer.addPass( saoPass ); + * ``` + * + * @augments Pass + * @three_import import { SAOPass } from 'three/addons/postprocessing/SAOPass.js'; */ - class SAOPass extends Pass { - constructor( scene, camera, useDepthTexture = false, useNormals = false, resolution = new Vector2( 256, 256 ) ) { + /** + * Constructs a new SAO pass. + * + * @param {Scene} scene - The scene to compute the AO for. + * @param {Camera} camera - The camera. + * @param {Vector2} [resolution] - The effect's resolution. + */ + constructor( scene, camera, resolution = new Vector2( 256, 256 ) ) { super(); + /** + * The scene to render the AO for. + * + * @type {Scene} + */ this.scene = scene; + + /** + * The camera. + * + * @type {Camera} + */ this.camera = camera; + /** + * Overwritten to perform a clear operation by default. + * + * @type {boolean} + * @default true + */ this.clear = true; - this.needsSwap = false; - this.supportsDepthTextureExtension = useDepthTexture; - this.supportsNormalTexture = useNormals; + /** + * Overwritten to disable the swap. + * + * @type {boolean} + * @default false + */ + this.needsSwap = false; - this.originalClearColor = new Color(); + this._originalClearColor = new Color(); this._oldClearColor = new Color(); - this.oldClearAlpha = 1; + this._oldClearAlpha = 1; + /** + * The SAO parameter. + * + * @type {Object} + */ this.params = { output: 0, saoBias: 0.5, @@ -60,33 +100,27 @@ class SAOPass extends Pass { saoBlurDepthCutoff: 0.01 }; + /** + * The effect's resolution. + * + * @type {Vector2} + * @default (256,256) + */ this.resolution = new Vector2( resolution.x, resolution.y ); - this.saoRenderTarget = new WebGLRenderTarget( this.resolution.x, this.resolution.y ); + this.saoRenderTarget = new WebGLRenderTarget( this.resolution.x, this.resolution.y, { type: HalfFloatType } ); this.blurIntermediateRenderTarget = this.saoRenderTarget.clone(); - this.beautyRenderTarget = this.saoRenderTarget.clone(); + + const depthTexture = new DepthTexture(); + depthTexture.format = DepthStencilFormat; + depthTexture.type = UnsignedInt248Type; this.normalRenderTarget = new WebGLRenderTarget( this.resolution.x, this.resolution.y, { minFilter: NearestFilter, - magFilter: NearestFilter + magFilter: NearestFilter, + type: HalfFloatType, + depthTexture: depthTexture } ); - this.depthRenderTarget = this.normalRenderTarget.clone(); - - let depthTexture; - - if ( this.supportsDepthTextureExtension ) { - - depthTexture = new DepthTexture(); - depthTexture.type = UnsignedShortType; - - this.beautyRenderTarget.depthTexture = depthTexture; - this.beautyRenderTarget.depthBuffer = true; - - } - - this.depthMaterial = new MeshDepthMaterial(); - this.depthMaterial.depthPacking = RGBADepthPacking; - this.depthMaterial.blending = NoBlending; this.normalMaterial = new MeshNormalMaterial(); this.normalMaterial.blending = NoBlending; @@ -97,11 +131,8 @@ class SAOPass extends Pass { vertexShader: SAOShader.vertexShader, uniforms: UniformsUtils.clone( SAOShader.uniforms ) } ); - this.saoMaterial.extensions.derivatives = true; - this.saoMaterial.defines[ 'DEPTH_PACKING' ] = this.supportsDepthTextureExtension ? 0 : 1; - this.saoMaterial.defines[ 'NORMAL_TEXTURE' ] = this.supportsNormalTexture ? 1 : 0; this.saoMaterial.defines[ 'PERSPECTIVE_CAMERA' ] = this.camera.isPerspectiveCamera ? 1 : 0; - this.saoMaterial.uniforms[ 'tDepth' ].value = ( this.supportsDepthTextureExtension ) ? depthTexture : this.depthRenderTarget.texture; + this.saoMaterial.uniforms[ 'tDepth' ].value = depthTexture; 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 ); @@ -114,10 +145,10 @@ class SAOPass extends Pass { vertexShader: DepthLimitedBlurShader.vertexShader, fragmentShader: DepthLimitedBlurShader.fragmentShader } ); - this.vBlurMaterial.defines[ 'DEPTH_PACKING' ] = this.supportsDepthTextureExtension ? 0 : 1; + this.vBlurMaterial.defines[ 'DEPTH_PACKING' ] = 0; this.vBlurMaterial.defines[ 'PERSPECTIVE_CAMERA' ] = this.camera.isPerspectiveCamera ? 1 : 0; this.vBlurMaterial.uniforms[ 'tDiffuse' ].value = this.saoRenderTarget.texture; - this.vBlurMaterial.uniforms[ 'tDepth' ].value = ( this.supportsDepthTextureExtension ) ? depthTexture : this.depthRenderTarget.texture; + this.vBlurMaterial.uniforms[ 'tDepth' ].value = depthTexture; this.vBlurMaterial.uniforms[ 'size' ].value.set( this.resolution.x, this.resolution.y ); this.vBlurMaterial.blending = NoBlending; @@ -127,10 +158,10 @@ class SAOPass extends Pass { vertexShader: DepthLimitedBlurShader.vertexShader, fragmentShader: DepthLimitedBlurShader.fragmentShader } ); - this.hBlurMaterial.defines[ 'DEPTH_PACKING' ] = this.supportsDepthTextureExtension ? 0 : 1; + this.hBlurMaterial.defines[ 'DEPTH_PACKING' ] = 0; this.hBlurMaterial.defines[ 'PERSPECTIVE_CAMERA' ] = this.camera.isPerspectiveCamera ? 1 : 0; this.hBlurMaterial.uniforms[ 'tDiffuse' ].value = this.blurIntermediateRenderTarget.texture; - this.hBlurMaterial.uniforms[ 'tDepth' ].value = ( this.supportsDepthTextureExtension ) ? depthTexture : this.depthRenderTarget.texture; + this.hBlurMaterial.uniforms[ 'tDepth' ].value = depthTexture; this.hBlurMaterial.uniforms[ 'size' ].value.set( this.resolution.x, this.resolution.y ); this.hBlurMaterial.blending = NoBlending; @@ -151,17 +182,21 @@ class SAOPass extends Pass { this.materialCopy.blendDstAlpha = ZeroFactor; this.materialCopy.blendEquationAlpha = AddEquation; - this.depthCopy = new ShaderMaterial( { - uniforms: UniformsUtils.clone( UnpackDepthRGBAShader.uniforms ), - vertexShader: UnpackDepthRGBAShader.vertexShader, - fragmentShader: UnpackDepthRGBAShader.fragmentShader, - blending: NoBlending - } ); - this.fsQuad = new FullScreenQuad( null ); } + /** + * Performs the SAO pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer/*, deltaTime, maskActive*/ ) { // Rendering readBuffer first when rendering to screen @@ -170,24 +205,15 @@ class SAOPass extends Pass { this.materialCopy.blending = NoBlending; this.materialCopy.uniforms[ 'tDiffuse' ].value = readBuffer.texture; this.materialCopy.needsUpdate = true; - this.renderPass( renderer, this.materialCopy, null ); - - } - - if ( this.params.output === 1 ) { - - return; + this._renderPass( renderer, this.materialCopy, null ); } renderer.getClearColor( this._oldClearColor ); - this.oldClearAlpha = renderer.getClearAlpha(); + this._oldClearAlpha = renderer.getClearAlpha(); const oldAutoClear = renderer.autoClear; 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; @@ -216,56 +242,24 @@ class SAOPass extends Pass { } - // Rendering scene to depth texture - renderer.setClearColor( 0x000000 ); - renderer.setRenderTarget( this.beautyRenderTarget ); - renderer.clear(); - renderer.render( this.scene, this.camera ); - - // 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.supportsNormalTexture ) { - - // Clear rule : default normal is facing the camera - this.renderOverride( renderer, this.normalMaterial, this.normalRenderTarget, 0x7777ff, 1.0 ); - - } + // render normal and depth + this._renderOverride( renderer, this.normalMaterial, this.normalRenderTarget, 0x7777ff, 1.0 ); // Rendering SAO texture - this.renderPass( renderer, this.saoMaterial, this.saoRenderTarget, 0xffffff, 1.0 ); + 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 ); + this._renderPass( renderer, this.vBlurMaterial, this.blurIntermediateRenderTarget, 0xffffff, 1.0 ); + this._renderPass( renderer, this.hBlurMaterial, this.saoRenderTarget, 0xffffff, 1.0 ); } - let outputMaterial = this.materialCopy; - // Setting up SAO rendering - if ( this.params.output === 3 ) { - - if ( this.supportsDepthTextureExtension ) { - - this.materialCopy.uniforms[ 'tDiffuse' ].value = this.beautyRenderTarget.depthTexture; - this.materialCopy.needsUpdate = true; - - } else { - - this.depthCopy.uniforms[ 'tDiffuse' ].value = this.depthRenderTarget.texture; - this.depthCopy.needsUpdate = true; - outputMaterial = this.depthCopy; + const outputMaterial = this.materialCopy; - } - - } else if ( this.params.output === 4 ) { + // Setting up SAO rendering + if ( this.params.output === SAOPass.OUTPUT.Normal ) { this.materialCopy.uniforms[ 'tDiffuse' ].value = this.normalRenderTarget.texture; this.materialCopy.needsUpdate = true; @@ -277,8 +271,8 @@ class SAOPass extends Pass { } - // Blending depends on output, only want a CustomBlending when showing SAO - if ( this.params.output === 0 ) { + // Blending depends on output + if ( this.params.output === SAOPass.OUTPUT.Default ) { outputMaterial.blending = CustomBlending; @@ -289,17 +283,64 @@ class SAOPass extends Pass { } // Rendering SAOPass result on top of previous pass - this.renderPass( renderer, outputMaterial, this.renderToScreen ? null : readBuffer ); + this._renderPass( renderer, outputMaterial, this.renderToScreen ? null : readBuffer ); - renderer.setClearColor( this._oldClearColor, this.oldClearAlpha ); + renderer.setClearColor( this._oldClearColor, this._oldClearAlpha ); renderer.autoClear = oldAutoClear; } - renderPass( renderer, passMaterial, renderTarget, clearColor, clearAlpha ) { + /** + * Sets the size of the pass. + * + * @param {number} width - The width to set. + * @param {number} height - The height to set. + */ + setSize( width, height ) { + + this.saoRenderTarget.setSize( width, height ); + this.blurIntermediateRenderTarget.setSize( width, height ); + this.normalRenderTarget.setSize( width, height ); + + this.saoMaterial.uniforms[ 'size' ].value.set( width, height ); + this.saoMaterial.uniforms[ 'cameraInverseProjectionMatrix' ].value.copy( this.camera.projectionMatrixInverse ); + this.saoMaterial.uniforms[ 'cameraProjectionMatrix' ].value = this.camera.projectionMatrix; + this.saoMaterial.needsUpdate = true; + + this.vBlurMaterial.uniforms[ 'size' ].value.set( width, height ); + this.vBlurMaterial.needsUpdate = true; + + this.hBlurMaterial.uniforms[ 'size' ].value.set( width, height ); + this.hBlurMaterial.needsUpdate = true; + + } + + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ + dispose() { + + this.saoRenderTarget.dispose(); + this.blurIntermediateRenderTarget.dispose(); + this.normalRenderTarget.dispose(); + + this.normalMaterial.dispose(); + this.saoMaterial.dispose(); + this.vBlurMaterial.dispose(); + this.hBlurMaterial.dispose(); + this.materialCopy.dispose(); + + this.fsQuad.dispose(); + + } + + // internal + + _renderPass( renderer, passMaterial, renderTarget, clearColor, clearAlpha ) { // save original state - renderer.getClearColor( this.originalClearColor ); + renderer.getClearColor( this._originalClearColor ); const originalClearAlpha = renderer.getClearAlpha(); const originalAutoClear = renderer.autoClear; @@ -320,14 +361,14 @@ class SAOPass extends Pass { // restore original state renderer.autoClear = originalAutoClear; - renderer.setClearColor( this.originalClearColor ); + renderer.setClearColor( this._originalClearColor ); renderer.setClearAlpha( originalClearAlpha ); } - renderOverride( renderer, overrideMaterial, renderTarget, clearColor, clearAlpha ) { + _renderOverride( renderer, overrideMaterial, renderTarget, clearColor, clearAlpha ) { - renderer.getClearColor( this.originalClearColor ); + renderer.getClearColor( this._originalClearColor ); const originalClearAlpha = renderer.getClearAlpha(); const originalAutoClear = renderer.autoClear; @@ -350,60 +391,17 @@ class SAOPass extends Pass { // restore original state renderer.autoClear = originalAutoClear; - renderer.setClearColor( this.originalClearColor ); + renderer.setClearColor( this._originalClearColor ); renderer.setClearAlpha( originalClearAlpha ); } - setSize( width, height ) { - - this.beautyRenderTarget.setSize( width, height ); - this.saoRenderTarget.setSize( width, height ); - this.blurIntermediateRenderTarget.setSize( width, height ); - this.normalRenderTarget.setSize( width, height ); - this.depthRenderTarget.setSize( width, height ); - - this.saoMaterial.uniforms[ 'size' ].value.set( width, height ); - this.saoMaterial.uniforms[ 'cameraInverseProjectionMatrix' ].value.copy( this.camera.projectionMatrixInverse ); - this.saoMaterial.uniforms[ 'cameraProjectionMatrix' ].value = this.camera.projectionMatrix; - this.saoMaterial.needsUpdate = true; - - this.vBlurMaterial.uniforms[ 'size' ].value.set( width, height ); - this.vBlurMaterial.needsUpdate = true; - - this.hBlurMaterial.uniforms[ 'size' ].value.set( width, height ); - this.hBlurMaterial.needsUpdate = true; - - } - - dispose() { - - this.saoRenderTarget.dispose(); - this.blurIntermediateRenderTarget.dispose(); - this.beautyRenderTarget.dispose(); - this.normalRenderTarget.dispose(); - this.depthRenderTarget.dispose(); - - this.depthMaterial.dispose(); - this.normalMaterial.dispose(); - this.saoMaterial.dispose(); - this.vBlurMaterial.dispose(); - this.hBlurMaterial.dispose(); - this.materialCopy.dispose(); - this.depthCopy.dispose(); - - this.fsQuad.dispose(); - - } - } SAOPass.OUTPUT = { - 'Beauty': 1, 'Default': 0, - 'SAO': 2, - 'Depth': 3, - 'Normal': 4 + 'SAO': 1, + 'Normal': 2 }; export { SAOPass }; diff --git a/examples/jsm/postprocessing/SMAAPass.js b/examples/jsm/postprocessing/SMAAPass.js index 12e7fb7ba0743b..e330f9048562e0 100644 --- a/examples/jsm/postprocessing/SMAAPass.js +++ b/examples/jsm/postprocessing/SMAAPass.js @@ -1,4 +1,5 @@ import { + HalfFloatType, LinearFilter, NearestFilter, ShaderMaterial, @@ -7,189 +8,220 @@ import { WebGLRenderTarget } from 'three'; import { Pass, FullScreenQuad } from './Pass.js'; -import { SMAAEdgesShader } from '../shaders/SMAAShader.js'; -import { SMAAWeightsShader } from '../shaders/SMAAShader.js'; -import { SMAABlendShader } from '../shaders/SMAAShader.js'; - +import { SMAABlendShader, SMAAEdgesShader, SMAAWeightsShader } from '../shaders/SMAAShader.js'; + +/** + * A pass for applying SMAA. Unlike {@link FXAAPass}, `SMAAPass` operates in + * `linear-srgb` so this pass must be executed before {@link OutputPass}. + * + * ```js + * const smaaPass = new SMAAPass(); + * composer.addPass( smaaPass ); + * ``` + * + * @augments Pass + * @three_import import { SMAAPass } from 'three/addons/postprocessing/SMAAPass.js'; + */ class SMAAPass extends Pass { - constructor( width, height ) { + /** + * Constructs a new SMAA pass. + */ + constructor( ) { super(); // render targets - this.edgesRT = new WebGLRenderTarget( width, height, { - depthBuffer: false + this._edgesRT = new WebGLRenderTarget( 1, 1, { + depthBuffer: false, + type: HalfFloatType } ); - this.edgesRT.texture.name = 'SMAAPass.edges'; + this._edgesRT.texture.name = 'SMAAPass.edges'; - this.weightsRT = new WebGLRenderTarget( width, height, { - depthBuffer: false + this._weightsRT = new WebGLRenderTarget( 1, 1, { + depthBuffer: false, + type: HalfFloatType } ); - this.weightsRT.texture.name = 'SMAAPass.weights'; + this._weightsRT.texture.name = 'SMAAPass.weights'; // textures const scope = this; const areaTextureImage = new Image(); - areaTextureImage.src = this.getAreaTexture(); + areaTextureImage.src = this._getAreaTexture(); areaTextureImage.onload = function () { // assigning data to HTMLImageElement.src is asynchronous (see #15162) - scope.areaTexture.needsUpdate = true; + scope._areaTexture.needsUpdate = true; }; - this.areaTexture = new Texture(); - this.areaTexture.name = 'SMAAPass.area'; - this.areaTexture.image = areaTextureImage; - this.areaTexture.minFilter = LinearFilter; - this.areaTexture.generateMipmaps = false; - this.areaTexture.flipY = false; + this._areaTexture = new Texture(); + this._areaTexture.name = 'SMAAPass.area'; + this._areaTexture.image = areaTextureImage; + this._areaTexture.minFilter = LinearFilter; + this._areaTexture.generateMipmaps = false; + this._areaTexture.flipY = false; const searchTextureImage = new Image(); - searchTextureImage.src = this.getSearchTexture(); + searchTextureImage.src = this._getSearchTexture(); searchTextureImage.onload = function () { // assigning data to HTMLImageElement.src is asynchronous (see #15162) - scope.searchTexture.needsUpdate = true; + scope._searchTexture.needsUpdate = true; }; - this.searchTexture = new Texture(); - this.searchTexture.name = 'SMAAPass.search'; - this.searchTexture.image = searchTextureImage; - this.searchTexture.magFilter = NearestFilter; - this.searchTexture.minFilter = NearestFilter; - this.searchTexture.generateMipmaps = false; - this.searchTexture.flipY = false; + this._searchTexture = new Texture(); + this._searchTexture.name = 'SMAAPass.search'; + this._searchTexture.image = searchTextureImage; + this._searchTexture.magFilter = NearestFilter; + this._searchTexture.minFilter = NearestFilter; + this._searchTexture.generateMipmaps = false; + this._searchTexture.flipY = false; // materials - pass 1 - this.uniformsEdges = UniformsUtils.clone( SMAAEdgesShader.uniforms ); - - this.uniformsEdges[ 'resolution' ].value.set( 1 / width, 1 / height ); + this._uniformsEdges = UniformsUtils.clone( SMAAEdgesShader.uniforms ); - this.materialEdges = new ShaderMaterial( { + this._materialEdges = new ShaderMaterial( { defines: Object.assign( {}, SMAAEdgesShader.defines ), - uniforms: this.uniformsEdges, + uniforms: this._uniformsEdges, vertexShader: SMAAEdgesShader.vertexShader, fragmentShader: SMAAEdgesShader.fragmentShader } ); // materials - pass 2 - this.uniformsWeights = UniformsUtils.clone( SMAAWeightsShader.uniforms ); + this._uniformsWeights = UniformsUtils.clone( SMAAWeightsShader.uniforms ); - this.uniformsWeights[ 'resolution' ].value.set( 1 / width, 1 / height ); - this.uniformsWeights[ 'tDiffuse' ].value = this.edgesRT.texture; - this.uniformsWeights[ 'tArea' ].value = this.areaTexture; - this.uniformsWeights[ 'tSearch' ].value = this.searchTexture; + this._uniformsWeights[ 'tDiffuse' ].value = this._edgesRT.texture; + this._uniformsWeights[ 'tArea' ].value = this._areaTexture; + this._uniformsWeights[ 'tSearch' ].value = this._searchTexture; - this.materialWeights = new ShaderMaterial( { + this._materialWeights = new ShaderMaterial( { defines: Object.assign( {}, SMAAWeightsShader.defines ), - uniforms: this.uniformsWeights, + uniforms: this._uniformsWeights, vertexShader: SMAAWeightsShader.vertexShader, fragmentShader: SMAAWeightsShader.fragmentShader } ); // materials - pass 3 - this.uniformsBlend = UniformsUtils.clone( SMAABlendShader.uniforms ); + this._uniformsBlend = UniformsUtils.clone( SMAABlendShader.uniforms ); + this._uniformsBlend[ 'tDiffuse' ].value = this._weightsRT.texture; - this.uniformsBlend[ 'resolution' ].value.set( 1 / width, 1 / height ); - this.uniformsBlend[ 'tDiffuse' ].value = this.weightsRT.texture; - - this.materialBlend = new ShaderMaterial( { - uniforms: this.uniformsBlend, + this._materialBlend = new ShaderMaterial( { + uniforms: this._uniformsBlend, vertexShader: SMAABlendShader.vertexShader, fragmentShader: SMAABlendShader.fragmentShader } ); - this.needsSwap = false; - - this.fsQuad = new FullScreenQuad( null ); + this._fsQuad = new FullScreenQuad( null ); } + /** + * Performs the SMAA pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer/*, deltaTime, maskActive*/ ) { // pass 1 - this.uniformsEdges[ 'tDiffuse' ].value = readBuffer.texture; + this._uniformsEdges[ 'tDiffuse' ].value = readBuffer.texture; - this.fsQuad.material = this.materialEdges; + this._fsQuad.material = this._materialEdges; - renderer.setRenderTarget( this.edgesRT ); + renderer.setRenderTarget( this._edgesRT ); if ( this.clear ) renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); // pass 2 - this.fsQuad.material = this.materialWeights; + this._fsQuad.material = this._materialWeights; - renderer.setRenderTarget( this.weightsRT ); + renderer.setRenderTarget( this._weightsRT ); if ( this.clear ) renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); // pass 3 - this.uniformsBlend[ 'tColor' ].value = readBuffer.texture; + this._uniformsBlend[ 'tColor' ].value = readBuffer.texture; - this.fsQuad.material = this.materialBlend; + this._fsQuad.material = this._materialBlend; if ( this.renderToScreen ) { renderer.setRenderTarget( null ); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } else { renderer.setRenderTarget( writeBuffer ); if ( this.clear ) renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } } + /** + * Sets the size of the pass. + * + * @param {number} width - The width to set. + * @param {number} height - The height to set. + */ setSize( width, height ) { - this.edgesRT.setSize( width, height ); - this.weightsRT.setSize( width, height ); + this._edgesRT.setSize( width, height ); + this._weightsRT.setSize( width, height ); - this.materialEdges.uniforms[ 'resolution' ].value.set( 1 / width, 1 / height ); - this.materialWeights.uniforms[ 'resolution' ].value.set( 1 / width, 1 / height ); - this.materialBlend.uniforms[ 'resolution' ].value.set( 1 / width, 1 / height ); + this._materialEdges.uniforms[ 'resolution' ].value.set( 1 / width, 1 / height ); + this._materialWeights.uniforms[ 'resolution' ].value.set( 1 / width, 1 / height ); + this._materialBlend.uniforms[ 'resolution' ].value.set( 1 / width, 1 / height ); } - getAreaTexture() { + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ + dispose() { - return 'data:image/png;base64,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'; + this._edgesRT.dispose(); + this._weightsRT.dispose(); - } + this._areaTexture.dispose(); + this._searchTexture.dispose(); - getSearchTexture() { + this._materialEdges.dispose(); + this._materialWeights.dispose(); + this._materialBlend.dispose(); - return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEIAAAAhCAAAAABIXyLAAAAAOElEQVRIx2NgGAWjYBSMglEwEICREYRgFBZBqDCSLA2MGPUIVQETE9iNUAqLR5gIeoQKRgwXjwAAGn4AtaFeYLEAAAAASUVORK5CYII='; + this._fsQuad.dispose(); } - dispose() { + // internals + + _getAreaTexture() { - this.edgesRT.dispose(); - this.weightsRT.dispose(); + return 'data:image/png;base64,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'; - this.areaTexture.dispose(); - this.searchTexture.dispose(); + } - this.materialEdges.dispose(); - this.materialWeights.dispose(); - this.materialBlend.dispose(); + _getSearchTexture() { - this.fsQuad.dispose(); + return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEIAAAAhCAAAAABIXyLAAAAAOElEQVRIx2NgGAWjYBSMglEwEICREYRgFBZBqDCSLA2MGPUIVQETE9iNUAqLR5gIeoQKRgwXjwAAGn4AtaFeYLEAAAAASUVORK5CYII='; } diff --git a/examples/jsm/postprocessing/SSAARenderPass.js b/examples/jsm/postprocessing/SSAARenderPass.js index e0bccf3a3b5010..797c466f1f49c7 100644 --- a/examples/jsm/postprocessing/SSAARenderPass.js +++ b/examples/jsm/postprocessing/SSAARenderPass.js @@ -1,9 +1,7 @@ import { - CustomBlending, - OneFactor, - AddEquation, - SrcAlphaFactor, + AdditiveBlending, Color, + HalfFloatType, ShaderMaterial, UniformsUtils, WebGLRenderTarget @@ -12,83 +10,162 @@ import { Pass, FullScreenQuad } from './Pass.js'; import { CopyShader } from '../shaders/CopyShader.js'; /** -* -* Supersample Anti-Aliasing Render Pass -* -* This manual approach to SSAA re-renders the scene ones for each sample with camera jitter and accumulates the results. -* -* References: https://en.wikipedia.org/wiki/Supersampling -* -*/ - + * Supersample Anti-Aliasing Render Pass. + * + * This manual approach to SSAA re-renders the scene ones for each sample with camera jitter and accumulates the results. + * + * ```js + * const ssaaRenderPass = new SSAARenderPass( scene, camera ); + * ssaaRenderPass.sampleLevel = 3; + * composer.addPass( ssaaRenderPass ); + * ``` + * + * @augments Pass + * @three_import import { SSAARenderPass } from 'three/addons/postprocessing/SSAARenderPass.js'; + */ class SSAARenderPass extends Pass { - constructor( scene, camera, clearColor, clearAlpha ) { + /** + * Constructs a new SSAA render pass. + * + * @param {Scene} scene - The scene to render. + * @param {Camera} camera - The camera. + * @param {?(number|Color|string)} [clearColor=0x000000] - The clear color of the render pass. + * @param {?number} [clearAlpha=0] - The clear alpha of the render pass. + */ + constructor( scene, camera, clearColor = 0x000000, clearAlpha = 0 ) { super(); + /** + * The scene to render. + * + * @type {Scene} + */ this.scene = scene; + + /** + * The camera. + * + * @type {Camera} + */ this.camera = camera; - this.sampleLevel = 4; // specified as n, where the number of samples is 2^n, so sampleLevel = 4, is 2^4 samples, 16. + /** + * The sample level. Specified as n, where the number of + * samples is 2^n, so sampleLevel = 4, is 2^4 samples, 16. + * + * @type {number} + * @default 4 + */ + this.sampleLevel = 4; + + /** + * Whether the pass should be unbiased or not. This property has the most + * visible effect when rendering to a RGBA8 buffer because it mitigates + * rounding errors. By default RGBA16F is used. + * + * @type {boolean} + * @default true + */ this.unbiased = true; - // as we need to clear the buffer in this pass, clearColor must be set to something, defaults to black. - this.clearColor = ( clearColor !== undefined ) ? clearColor : 0x000000; - this.clearAlpha = ( clearAlpha !== undefined ) ? clearAlpha : 0; + /** + * Whether to use a stencil buffer or not. This property can't + * be changed after the first render. + * + * @type {boolean} + * @default false + */ + this.stencilBuffer = false; + + /** + * The clear color of the render pass. + * + * @type {?(number|Color|string)} + * @default 0x000000 + */ + this.clearColor = clearColor; + + /** + * The clear alpha of the render pass. + * + * @type {?number} + * @default 0 + */ + this.clearAlpha = clearAlpha; + + // internals + + this._sampleRenderTarget = null; + this._oldClearColor = new Color(); - const copyShader = CopyShader; - this.copyUniforms = UniformsUtils.clone( copyShader.uniforms ); + this._copyUniforms = UniformsUtils.clone( CopyShader.uniforms ); - this.copyMaterial = new ShaderMaterial( { - uniforms: this.copyUniforms, - vertexShader: copyShader.vertexShader, - fragmentShader: copyShader.fragmentShader, + this._copyMaterial = new ShaderMaterial( { + uniforms: this._copyUniforms, + vertexShader: CopyShader.vertexShader, + fragmentShader: CopyShader.fragmentShader, transparent: true, depthTest: false, depthWrite: false, - - // do not use AdditiveBlending because it mixes the alpha channel instead of adding - blending: CustomBlending, - blendEquation: AddEquation, - blendDst: OneFactor, - blendDstAlpha: OneFactor, - blendSrc: SrcAlphaFactor, - blendSrcAlpha: OneFactor + premultipliedAlpha: true, + blending: AdditiveBlending } ); - this.fsQuad = new FullScreenQuad( this.copyMaterial ); + this._fsQuad = new FullScreenQuad( this._copyMaterial ); } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ dispose() { - if ( this.sampleRenderTarget ) { + if ( this._sampleRenderTarget ) { - this.sampleRenderTarget.dispose(); - this.sampleRenderTarget = null; + this._sampleRenderTarget.dispose(); + this._sampleRenderTarget = null; } - this.copyMaterial.dispose(); + this._copyMaterial.dispose(); - this.fsQuad.dispose(); + this._fsQuad.dispose(); } + /** + * Sets the size of the pass. + * + * @param {number} width - The width to set. + * @param {number} height - The height to set. + */ setSize( width, height ) { - if ( this.sampleRenderTarget ) this.sampleRenderTarget.setSize( width, height ); + if ( this._sampleRenderTarget ) this._sampleRenderTarget.setSize( width, height ); } - render( renderer, writeBuffer, readBuffer ) { + /** + * Performs the SSAA render pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ + render( renderer, writeBuffer, readBuffer/*, deltaTime, maskActive */ ) { - if ( ! this.sampleRenderTarget ) { + if ( ! this._sampleRenderTarget ) { - this.sampleRenderTarget = new WebGLRenderTarget( readBuffer.width, readBuffer.height ); - this.sampleRenderTarget.texture.name = 'SSAARenderPass.sample'; + this._sampleRenderTarget = new WebGLRenderTarget( readBuffer.width, readBuffer.height, { type: HalfFloatType, stencilBuffer: this.stencilBuffer } ); + this._sampleRenderTarget.texture.name = 'SSAARenderPass.sample'; } @@ -102,7 +179,7 @@ class SSAARenderPass extends Pass { const baseSampleWeight = 1.0 / jitterOffsets.length; const roundingRange = 1 / 32; - this.copyUniforms[ 'tDiffuse' ].value = this.sampleRenderTarget.texture; + this._copyUniforms[ 'tDiffuse' ].value = this._sampleRenderTarget.texture; const viewOffset = { @@ -151,9 +228,9 @@ class SSAARenderPass extends Pass { } - this.copyUniforms[ 'opacity' ].value = sampleWeight; + this._copyUniforms[ 'opacity' ].value = sampleWeight; renderer.setClearColor( this.clearColor, this.clearAlpha ); - renderer.setRenderTarget( this.sampleRenderTarget ); + renderer.setRenderTarget( this._sampleRenderTarget ); renderer.clear(); renderer.render( this.scene, this.camera ); @@ -166,7 +243,7 @@ class SSAARenderPass extends Pass { } - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } diff --git a/examples/jsm/postprocessing/SSAOPass.js b/examples/jsm/postprocessing/SSAOPass.js index 8a6841a716fc01..8fbbc05c9f5986 100644 --- a/examples/jsm/postprocessing/SSAOPass.js +++ b/examples/jsm/postprocessing/SSAOPass.js @@ -7,12 +7,12 @@ import { DstAlphaFactor, DstColorFactor, FloatType, + HalfFloatType, MathUtils, MeshNormalMaterial, NearestFilter, NoBlending, RedFormat, - LuminanceFormat, DepthStencilFormat, UnsignedInt248Type, RepeatWrapping, @@ -24,60 +24,146 @@ import { } from 'three'; import { Pass, FullScreenQuad } from './Pass.js'; import { SimplexNoise } from '../math/SimplexNoise.js'; -import { SSAOShader } from '../shaders/SSAOShader.js'; -import { SSAOBlurShader } from '../shaders/SSAOShader.js'; -import { SSAODepthShader } from '../shaders/SSAOShader.js'; +import { SSAOBlurShader, SSAODepthShader, SSAOShader } from '../shaders/SSAOShader.js'; import { CopyShader } from '../shaders/CopyShader.js'; +/** + * A pass for a basic SSAO effect. + * + * {@link SAOPass} and {@link GTAPass} produce a more advanced AO but are also + * more expensive. + * + * ```js + * const ssaoPass = new SSAOPass( scene, camera, width, height ); + * composer.addPass( ssaoPass ); + * ``` + * + * @augments Pass + * @three_import import { SSAOPass } from 'three/addons/postprocessing/SSAOPass.js'; + */ class SSAOPass extends Pass { - constructor( scene, camera, width, height ) { + /** + * Constructs a new SSAO pass. + * + * @param {Scene} scene - The scene to compute the AO for. + * @param {Camera} camera - The camera. + * @param {number} [width=512] - The width of the effect. + * @param {number} [height=512] - The height of the effect. + * @param {number} [kernelSize=32] - The kernel size. + */ + constructor( scene, camera, width = 512, height = 512, kernelSize = 32 ) { super(); - this.width = ( width !== undefined ) ? width : 512; - this.height = ( height !== undefined ) ? height : 512; + /** + * The width of the effect. + * + * @type {number} + * @default 512 + */ + this.width = width; + + /** + * The height of the effect. + * + * @type {number} + * @default 512 + */ + this.height = height; + /** + * Overwritten to perform a clear operation by default. + * + * @type {boolean} + * @default true + */ this.clear = true; + /** + * Overwritten to disable the swap. + * + * @type {boolean} + * @default false + */ + this.needsSwap = false; + + /** + * The camera. + * + * @type {Camera} + */ this.camera = camera; + + /** + * The scene to render the AO for. + * + * @type {Scene} + */ this.scene = scene; + /** + * The kernel radius controls how wide the + * AO spreads. + * + * @type {number} + * @default 8 + */ this.kernelRadius = 8; - this.kernelSize = 32; this.kernel = []; this.noiseTexture = null; + + /** + * The output configuration. + * + * @type {number} + * @default 0 + */ this.output = 0; + /** + * Defines the minimum distance that should be + * affected by the AO. + * + * @type {number} + * @default 0.005 + */ this.minDistance = 0.005; + + /** + * Defines the maximum distance that should be + * affected by the AO. + * + * @type {number} + * @default 0.1 + */ this.maxDistance = 0.1; this._visibilityCache = new Map(); // - this.generateSampleKernel(); - this.generateRandomKernelRotations(); + this._generateSampleKernel( kernelSize ); + this._generateRandomKernelRotations(); - // beauty render target + // depth texture const depthTexture = new DepthTexture(); depthTexture.format = DepthStencilFormat; depthTexture.type = UnsignedInt248Type; - this.beautyRenderTarget = new WebGLRenderTarget( this.width, this.height ); - // normal render target with depth buffer this.normalRenderTarget = new WebGLRenderTarget( this.width, this.height, { minFilter: NearestFilter, magFilter: NearestFilter, + type: HalfFloatType, depthTexture: depthTexture } ); // ssao render target - this.ssaoRenderTarget = new WebGLRenderTarget( this.width, this.height ); + this.ssaoRenderTarget = new WebGLRenderTarget( this.width, this.height, { type: HalfFloatType } ); this.blurRenderTarget = this.ssaoRenderTarget.clone(); @@ -91,7 +177,8 @@ class SSAOPass extends Pass { blending: NoBlending } ); - this.ssaoMaterial.uniforms[ 'tDiffuse' ].value = this.beautyRenderTarget.texture; + this.ssaoMaterial.defines[ 'KERNEL_SIZE' ] = kernelSize; + this.ssaoMaterial.uniforms[ 'tNormal' ].value = this.normalRenderTarget.texture; this.ssaoMaterial.uniforms[ 'tDepth' ].value = this.normalRenderTarget.depthTexture; this.ssaoMaterial.uniforms[ 'tNoise' ].value = this.noiseTexture; @@ -148,17 +235,22 @@ class SSAOPass extends Pass { blendEquationAlpha: AddEquation } ); - this.fsQuad = new FullScreenQuad( null ); + // internals + + this._fsQuad = new FullScreenQuad( null ); - this.originalClearColor = new Color(); + this._originalClearColor = new Color(); } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ dispose() { // dispose render targets - this.beautyRenderTarget.dispose(); this.normalRenderTarget.dispose(); this.ssaoRenderTarget.dispose(); this.blurRenderTarget.dispose(); @@ -170,38 +262,41 @@ class SSAOPass extends Pass { this.copyMaterial.dispose(); this.depthRenderMaterial.dispose(); - // dipsose full screen quad + // dispose full screen quad - this.fsQuad.dispose(); + this._fsQuad.dispose(); } - render( renderer, writeBuffer /*, readBuffer, deltaTime, maskActive */ ) { - - if ( renderer.capabilities.isWebGL2 === false ) this.noiseTexture.format = LuminanceFormat; - - // render beauty - - renderer.setRenderTarget( this.beautyRenderTarget ); - renderer.clear(); - renderer.render( this.scene, this.camera ); + /** + * Performs the SSAO pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ + render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) { // render normals and depth (honor only meshes, points and lines do not contribute to SSAO) - this.overrideVisibility(); - this.renderOverride( renderer, this.normalMaterial, this.normalRenderTarget, 0x7777ff, 1.0 ); - this.restoreVisibility(); + this._overrideVisibility(); + this._renderOverride( renderer, this.normalMaterial, this.normalRenderTarget, 0x7777ff, 1.0 ); + this._restoreVisibility(); // render SSAO this.ssaoMaterial.uniforms[ 'kernelRadius' ].value = this.kernelRadius; this.ssaoMaterial.uniforms[ 'minDistance' ].value = this.minDistance; this.ssaoMaterial.uniforms[ 'maxDistance' ].value = this.maxDistance; - this.renderPass( renderer, this.ssaoMaterial, this.ssaoRenderTarget ); + this._renderPass( renderer, this.ssaoMaterial, this.ssaoRenderTarget ); // render blur - this.renderPass( renderer, this.blurMaterial, this.blurRenderTarget ); + this._renderPass( renderer, this.blurMaterial, this.blurRenderTarget ); // output result to screen @@ -211,7 +306,7 @@ class SSAOPass extends Pass { this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.ssaoRenderTarget.texture; this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + this._renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : readBuffer ); break; @@ -219,21 +314,13 @@ class SSAOPass extends Pass { this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.blurRenderTarget.texture; this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); - - break; - - case SSAOPass.OUTPUT.Beauty: - - this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.beautyRenderTarget.texture; - this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + this._renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : readBuffer ); break; case SSAOPass.OUTPUT.Depth: - this.renderPass( renderer, this.depthRenderMaterial, this.renderToScreen ? null : writeBuffer ); + this._renderPass( renderer, this.depthRenderMaterial, this.renderToScreen ? null : readBuffer ); break; @@ -241,19 +328,15 @@ class SSAOPass extends Pass { this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.normalRenderTarget.texture; this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + this._renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : readBuffer ); break; case SSAOPass.OUTPUT.Default: - this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.beautyRenderTarget.texture; - this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); - this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.blurRenderTarget.texture; this.copyMaterial.blending = CustomBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + this._renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : readBuffer ); break; @@ -264,10 +347,35 @@ class SSAOPass extends Pass { } - renderPass( renderer, passMaterial, renderTarget, clearColor, clearAlpha ) { + /** + * Sets the size of the pass. + * + * @param {number} width - The width to set. + * @param {number} height - The height to set. + */ + setSize( width, height ) { + + this.width = width; + this.height = height; + + this.ssaoRenderTarget.setSize( width, height ); + this.normalRenderTarget.setSize( width, height ); + this.blurRenderTarget.setSize( width, height ); + + this.ssaoMaterial.uniforms[ 'resolution' ].value.set( width, height ); + this.ssaoMaterial.uniforms[ 'cameraProjectionMatrix' ].value.copy( this.camera.projectionMatrix ); + this.ssaoMaterial.uniforms[ 'cameraInverseProjectionMatrix' ].value.copy( this.camera.projectionMatrixInverse ); + + this.blurMaterial.uniforms[ 'resolution' ].value.set( width, height ); + + } + + // internals + + _renderPass( renderer, passMaterial, renderTarget, clearColor, clearAlpha ) { // save original state - renderer.getClearColor( this.originalClearColor ); + renderer.getClearColor( this._originalClearColor ); const originalClearAlpha = renderer.getClearAlpha(); const originalAutoClear = renderer.autoClear; @@ -283,19 +391,19 @@ class SSAOPass extends Pass { } - this.fsQuad.material = passMaterial; - this.fsQuad.render( renderer ); + this._fsQuad.material = passMaterial; + this._fsQuad.render( renderer ); // restore original state renderer.autoClear = originalAutoClear; - renderer.setClearColor( this.originalClearColor ); + renderer.setClearColor( this._originalClearColor ); renderer.setClearAlpha( originalClearAlpha ); } - renderOverride( renderer, overrideMaterial, renderTarget, clearColor, clearAlpha ) { + _renderOverride( renderer, overrideMaterial, renderTarget, clearColor, clearAlpha ) { - renderer.getClearColor( this.originalClearColor ); + renderer.getClearColor( this._originalClearColor ); const originalClearAlpha = renderer.getClearAlpha(); const originalAutoClear = renderer.autoClear; @@ -320,32 +428,13 @@ class SSAOPass extends Pass { // restore original state renderer.autoClear = originalAutoClear; - renderer.setClearColor( this.originalClearColor ); + renderer.setClearColor( this._originalClearColor ); renderer.setClearAlpha( originalClearAlpha ); } - setSize( width, height ) { - - this.width = width; - this.height = height; - - this.beautyRenderTarget.setSize( width, height ); - this.ssaoRenderTarget.setSize( width, height ); - this.normalRenderTarget.setSize( width, height ); - this.blurRenderTarget.setSize( width, height ); - - this.ssaoMaterial.uniforms[ 'resolution' ].value.set( width, height ); - this.ssaoMaterial.uniforms[ 'cameraProjectionMatrix' ].value.copy( this.camera.projectionMatrix ); - this.ssaoMaterial.uniforms[ 'cameraInverseProjectionMatrix' ].value.copy( this.camera.projectionMatrixInverse ); - - this.blurMaterial.uniforms[ 'resolution' ].value.set( width, height ); - - } - - generateSampleKernel() { + _generateSampleKernel( kernelSize ) { - const kernelSize = this.kernelSize; const kernel = this.kernel; for ( let i = 0; i < kernelSize; i ++ ) { @@ -367,7 +456,7 @@ class SSAOPass extends Pass { } - generateRandomKernelRotations() { + _generateRandomKernelRotations() { const width = 4, height = 4; @@ -393,7 +482,7 @@ class SSAOPass extends Pass { } - overrideVisibility() { + _overrideVisibility() { const scene = this.scene; const cache = this._visibilityCache; @@ -408,7 +497,7 @@ class SSAOPass extends Pass { } - restoreVisibility() { + _restoreVisibility() { const scene = this.scene; const cache = this._visibilityCache; @@ -430,9 +519,8 @@ SSAOPass.OUTPUT = { 'Default': 0, 'SSAO': 1, 'Blur': 2, - 'Beauty': 3, - 'Depth': 4, - 'Normal': 5 + 'Depth': 3, + 'Normal': 4 }; export { SSAOPass }; diff --git a/examples/jsm/postprocessing/SSRPass.js b/examples/jsm/postprocessing/SSRPass.js index 4ac8387e5d566c..dfd4fe4154e58e 100644 --- a/examples/jsm/postprocessing/SSRPass.js +++ b/examples/jsm/postprocessing/SSRPass.js @@ -16,37 +16,142 @@ import { HalfFloatType, } from 'three'; import { Pass, FullScreenQuad } from './Pass.js'; -import { SSRShader } from '../shaders/SSRShader.js'; -import { SSRBlurShader } from '../shaders/SSRShader.js'; -import { SSRDepthShader } from '../shaders/SSRShader.js'; +import { SSRBlurShader, SSRDepthShader, SSRShader } from '../shaders/SSRShader.js'; import { CopyShader } from '../shaders/CopyShader.js'; +/** + * A pass for a basic SSR effect. + * + * ```js + * const ssrPass = new SSRPass( { + * renderer, + * scene, + * camera, + * width: innerWidth, + * height: innerHeight + * } ); + * composer.addPass( ssrPass ); + * ``` + * + * @augments Pass + * @three_import import { SSRPass } from 'three/addons/postprocessing/SSRPass.js'; + */ class SSRPass extends Pass { - constructor( { renderer, scene, camera, width, height, selects, bouncing = false, groundReflector } ) { + /** + * Constructs a new SSR pass. + * + * @param {SSRPass~Options} options - The pass options. + */ + constructor( { renderer, scene, camera, width = 512, height = 512, selects = null, bouncing = false, groundReflector = null } ) { super(); - this.width = ( width !== undefined ) ? width : 512; - this.height = ( height !== undefined ) ? height : 512; + /** + * The width of the effect. + * + * @type {number} + * @default 512 + */ + this.width = width; + + /** + * The height of the effect. + * + * @type {number} + * @default 512 + */ + this.height = height; + /** + * Overwritten to perform a clear operation by default. + * + * @type {boolean} + * @default true + */ this.clear = true; + /** + * The renderer. + * + * @type {WebGLRenderer} + */ this.renderer = renderer; + + /** + * The scene to render. + * + * @type {Scene} + */ this.scene = scene; + + /** + * The camera. + * + * @type {Camera} + */ this.camera = camera; + + /** + * The ground reflector. + * + * @type {?ReflectorForSSRPass} + * @default 0 + */ this.groundReflector = groundReflector; + /** + * The opacity. + * + * @type {number} + * @default 0.5 + */ this.opacity = SSRShader.uniforms.opacity.value; + + /** + * The output configuration. + * + * @type {number} + * @default 0 + */ this.output = 0; + /** + * Controls how far a fragment can reflect. + * + * @type {number} + * @default 180 + */ this.maxDistance = SSRShader.uniforms.maxDistance.value; + + /** + * Controls the cutoff between what counts as a + * possible reflection hit and what does not. + * + * @type {number} + * @default .018 + */ this.thickness = SSRShader.uniforms.thickness.value; this.tempColor = new Color(); this._selects = selects; + + /** + * Whether the pass is selective or not. + * + * @type {boolean} + * @default false + */ this.selective = Array.isArray( this._selects ); + + /** + * Which 3D objects should be affected by SSR. If not set, the entire scene is affected. + * + * @name SSRPass#selects + * @type {?Array} + * @default null + */ Object.defineProperty( this, 'selects', { get() { @@ -75,6 +180,14 @@ class SSRPass extends Pass { } ); this._bouncing = bouncing; + + /** + * Whether bouncing is enabled or not. + * + * @name SSRPass#bouncing + * @type {boolean} + * @default false + */ Object.defineProperty( this, 'bouncing', { get() { @@ -98,9 +211,23 @@ class SSRPass extends Pass { } } ); + /** + * Whether to blur reflections or not. + * + * @type {boolean} + * @default true + */ this.blur = true; this._distanceAttenuation = SSRShader.defines.DISTANCE_ATTENUATION; + + /** + * Whether to use distance attenuation or not. + * + * @name SSRPass#distanceAttenuation + * @type {boolean} + * @default true + */ Object.defineProperty( this, 'distanceAttenuation', { get() { @@ -119,6 +246,14 @@ class SSRPass extends Pass { this._fresnel = SSRShader.defines.FRESNEL; + + /** + * Whether to use fresnel or not. + * + * @name SSRPass#fresnel + * @type {boolean} + * @default true + */ Object.defineProperty( this, 'fresnel', { get() { @@ -136,6 +271,14 @@ class SSRPass extends Pass { } ); this._infiniteThick = SSRShader.defines.INFINITE_THICK; + + /** + * Whether to use infinite thickness or not. + * + * @name SSRPass#infiniteThick + * @type {boolean} + * @default false + */ Object.defineProperty( this, 'infiniteThick', { get() { @@ -162,6 +305,7 @@ class SSRPass extends Pass { this.beautyRenderTarget = new WebGLRenderTarget( this.width, this.height, { minFilter: NearestFilter, magFilter: NearestFilter, + type: HalfFloatType, depthTexture: depthTexture, depthBuffer: true } ); @@ -184,7 +328,8 @@ class SSRPass extends Pass { this.metalnessRenderTarget = new WebGLRenderTarget( this.width, this.height, { minFilter: NearestFilter, - magFilter: NearestFilter + magFilter: NearestFilter, + type: HalfFloatType, } ); @@ -312,6 +457,10 @@ class SSRPass extends Pass { } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ dispose() { // dispose render targets @@ -335,12 +484,23 @@ class SSRPass extends Pass { this.copyMaterial.dispose(); this.depthRenderMaterial.dispose(); - // dipsose full screen quad + // dispose full screen quad this.fsQuad.dispose(); } + /** + * Performs the SSR pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer /*, readBuffer, deltaTime, maskActive */ ) { // render beauty and depth @@ -360,13 +520,13 @@ class SSRPass extends Pass { // render normals - this.renderOverride( renderer, this.normalMaterial, this.normalRenderTarget, 0, 0 ); + this._renderOverride( renderer, this.normalMaterial, this.normalRenderTarget, 0, 0 ); // render metalnesses if ( this.selective ) { - this.renderMetalness( renderer, this.metalnessOnMaterial, this.metalnessRenderTarget, 0, 0 ); + this._renderMetalness( renderer, this.metalnessOnMaterial, this.metalnessRenderTarget, 0, 0 ); } @@ -375,16 +535,16 @@ class SSRPass extends Pass { this.ssrMaterial.uniforms[ 'opacity' ].value = this.opacity; this.ssrMaterial.uniforms[ 'maxDistance' ].value = this.maxDistance; this.ssrMaterial.uniforms[ 'thickness' ].value = this.thickness; - this.renderPass( renderer, this.ssrMaterial, this.ssrRenderTarget ); + this._renderPass( renderer, this.ssrMaterial, this.ssrRenderTarget ); // render blur if ( this.blur ) { - this.renderPass( renderer, this.blurMaterial, this.blurRenderTarget ); - this.renderPass( renderer, this.blurMaterial2, this.blurRenderTarget2 ); - // this.renderPass(renderer, this.blurMaterial3, this.blurRenderTarget3); + this._renderPass( renderer, this.blurMaterial, this.blurRenderTarget ); + this._renderPass( renderer, this.blurMaterial2, this.blurRenderTarget2 ); + // this._renderPass(renderer, this.blurMaterial3, this.blurRenderTarget3); } @@ -398,31 +558,31 @@ class SSRPass extends Pass { this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.beautyRenderTarget.texture; this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.prevRenderTarget ); + this._renderPass( renderer, this.copyMaterial, this.prevRenderTarget ); if ( this.blur ) this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.blurRenderTarget2.texture; else this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.ssrRenderTarget.texture; this.copyMaterial.blending = NormalBlending; - this.renderPass( renderer, this.copyMaterial, this.prevRenderTarget ); + this._renderPass( renderer, this.copyMaterial, this.prevRenderTarget ); this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.prevRenderTarget.texture; this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + this._renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); } else { this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.beautyRenderTarget.texture; this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + this._renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); if ( this.blur ) this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.blurRenderTarget2.texture; else this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.ssrRenderTarget.texture; this.copyMaterial.blending = NormalBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + this._renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); } @@ -434,7 +594,7 @@ class SSRPass extends Pass { else this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.ssrRenderTarget.texture; this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + this._renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); if ( this.bouncing ) { @@ -443,11 +603,11 @@ class SSRPass extends Pass { else this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.beautyRenderTarget.texture; this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.prevRenderTarget ); + this._renderPass( renderer, this.copyMaterial, this.prevRenderTarget ); this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.ssrRenderTarget.texture; this.copyMaterial.blending = NormalBlending; - this.renderPass( renderer, this.copyMaterial, this.prevRenderTarget ); + this._renderPass( renderer, this.copyMaterial, this.prevRenderTarget ); } @@ -457,13 +617,13 @@ class SSRPass extends Pass { this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.beautyRenderTarget.texture; this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + this._renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); break; case SSRPass.OUTPUT.Depth: - this.renderPass( renderer, this.depthRenderMaterial, this.renderToScreen ? null : writeBuffer ); + this._renderPass( renderer, this.depthRenderMaterial, this.renderToScreen ? null : writeBuffer ); break; @@ -471,7 +631,7 @@ class SSRPass extends Pass { this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.normalRenderTarget.texture; this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + this._renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); break; @@ -479,7 +639,7 @@ class SSRPass extends Pass { this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.metalnessRenderTarget.texture; this.copyMaterial.blending = NoBlending; - this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); + this._renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer ); break; @@ -490,7 +650,40 @@ class SSRPass extends Pass { } - renderPass( renderer, passMaterial, renderTarget, clearColor, clearAlpha ) { + /** + * Sets the size of the pass. + * + * @param {number} width - The width to set. + * @param {number} height - The height to set. + */ + setSize( width, height ) { + + this.width = width; + this.height = height; + + this.ssrMaterial.defines.MAX_STEP = Math.sqrt( width * width + height * height ); + this.ssrMaterial.needsUpdate = true; + this.beautyRenderTarget.setSize( width, height ); + this.prevRenderTarget.setSize( width, height ); + this.ssrRenderTarget.setSize( width, height ); + this.normalRenderTarget.setSize( width, height ); + this.metalnessRenderTarget.setSize( width, height ); + this.blurRenderTarget.setSize( width, height ); + this.blurRenderTarget2.setSize( width, height ); + // this.blurRenderTarget3.setSize(width, height); + + this.ssrMaterial.uniforms[ 'resolution' ].value.set( width, height ); + this.ssrMaterial.uniforms[ 'cameraProjectionMatrix' ].value.copy( this.camera.projectionMatrix ); + this.ssrMaterial.uniforms[ 'cameraInverseProjectionMatrix' ].value.copy( this.camera.projectionMatrixInverse ); + + this.blurMaterial.uniforms[ 'resolution' ].value.set( width, height ); + this.blurMaterial2.uniforms[ 'resolution' ].value.set( width, height ); + + } + + // internals + + _renderPass( renderer, passMaterial, renderTarget, clearColor, clearAlpha ) { // save original state this.originalClearColor.copy( renderer.getClearColor( this.tempColor ) ); @@ -519,7 +712,7 @@ class SSRPass extends Pass { } - renderOverride( renderer, overrideMaterial, renderTarget, clearColor, clearAlpha ) { + _renderOverride( renderer, overrideMaterial, renderTarget, clearColor, clearAlpha ) { this.originalClearColor.copy( renderer.getClearColor( this.tempColor ) ); const originalClearAlpha = renderer.getClearAlpha( this.tempColor ); @@ -551,14 +744,18 @@ class SSRPass extends Pass { } - renderMetalness( renderer, overrideMaterial, renderTarget, clearColor, clearAlpha ) { + _renderMetalness( renderer, overrideMaterial, renderTarget, clearColor, clearAlpha ) { this.originalClearColor.copy( renderer.getClearColor( this.tempColor ) ); const originalClearAlpha = renderer.getClearAlpha( this.tempColor ); const originalAutoClear = renderer.autoClear; + const originalBackground = this.scene.background; + const originalFog = this.scene.fog; renderer.setRenderTarget( renderTarget ); renderer.autoClear = false; + this.scene.background = null; + this.scene.fog = null; clearColor = overrideMaterial.clearColor || clearColor; clearAlpha = overrideMaterial.clearAlpha || clearAlpha; @@ -597,36 +794,27 @@ class SSRPass extends Pass { renderer.autoClear = originalAutoClear; renderer.setClearColor( this.originalClearColor ); renderer.setClearAlpha( originalClearAlpha ); - - } - - setSize( width, height ) { - - this.width = width; - this.height = height; - - this.ssrMaterial.defines.MAX_STEP = Math.sqrt( width * width + height * height ); - this.ssrMaterial.needsUpdate = true; - this.beautyRenderTarget.setSize( width, height ); - this.prevRenderTarget.setSize( width, height ); - this.ssrRenderTarget.setSize( width, height ); - this.normalRenderTarget.setSize( width, height ); - this.metalnessRenderTarget.setSize( width, height ); - this.blurRenderTarget.setSize( width, height ); - this.blurRenderTarget2.setSize( width, height ); - // this.blurRenderTarget3.setSize(width, height); - - this.ssrMaterial.uniforms[ 'resolution' ].value.set( width, height ); - this.ssrMaterial.uniforms[ 'cameraProjectionMatrix' ].value.copy( this.camera.projectionMatrix ); - this.ssrMaterial.uniforms[ 'cameraInverseProjectionMatrix' ].value.copy( this.camera.projectionMatrixInverse ); - - this.blurMaterial.uniforms[ 'resolution' ].value.set( width, height ); - this.blurMaterial2.uniforms[ 'resolution' ].value.set( width, height ); + this.scene.background = originalBackground; + this.scene.fog = originalFog; } } +/** + * Constructor options of `SSRPass`. + * + * @typedef {Object} SSRPass~Options + * @property {WebGLRenderer} renderer - The renderer. + * @property {Scene} scene - The scene to render. + * @property {Camera} camera - The camera. + * @property {number} [width=512] - The width of the effect. + * @property {number} [height=512] - The width of the effect. + * @property {?Array} [selects=null] - Which 3D objects should be affected by SSR. If not set, the entire scene is affected. + * @property {boolean} [bouncing=false] - Whether bouncing is enabled or not. + * @property {?ReflectorForSSRPass} [groundReflector=null] - A ground reflector. + **/ + SSRPass.OUTPUT = { 'Default': 0, 'SSR': 1, diff --git a/examples/jsm/postprocessing/SavePass.js b/examples/jsm/postprocessing/SavePass.js index 9a8697a839c5ca..f92292e9df34bb 100644 --- a/examples/jsm/postprocessing/SavePass.js +++ b/examples/jsm/postprocessing/SavePass.js @@ -1,4 +1,6 @@ import { + HalfFloatType, + NoBlending, ShaderMaterial, UniformsUtils, WebGLRenderTarget @@ -6,68 +8,122 @@ import { import { Pass, FullScreenQuad } from './Pass.js'; import { CopyShader } from '../shaders/CopyShader.js'; +/** + * A pass that saves the contents of the current read buffer in a render target. + * + * ```js + * const savePass = new SavePass( customRenderTarget ); + * composer.addPass( savePass ); + * ``` + * + * @augments Pass + * @three_import import { SavePass } from 'three/addons/postprocessing/SavePass.js'; + */ class SavePass extends Pass { + /** + * Constructs a new save pass. + * + * @param {WebGLRenderTarget} [renderTarget] - The render target for saving the read buffer. + * If not provided, the pass automatically creates a render target. + */ constructor( renderTarget ) { super(); - const shader = CopyShader; - - this.textureID = 'tDiffuse'; - - this.uniforms = UniformsUtils.clone( shader.uniforms ); - + /** + * The pass uniforms. + * + * @type {Object} + */ + this.uniforms = UniformsUtils.clone( CopyShader.uniforms ); + + /** + * The pass material. + * + * @type {ShaderMaterial} + */ this.material = new ShaderMaterial( { uniforms: this.uniforms, - vertexShader: shader.vertexShader, - fragmentShader: shader.fragmentShader + vertexShader: CopyShader.vertexShader, + fragmentShader: CopyShader.fragmentShader, + blending: NoBlending } ); + /** + * The render target which is used to save the read buffer. + * + * @type {WebGLRenderTarget} + */ this.renderTarget = renderTarget; if ( this.renderTarget === undefined ) { - this.renderTarget = new WebGLRenderTarget(); // will be resized later + this.renderTarget = new WebGLRenderTarget( 1, 1, { type: HalfFloatType } ); // will be resized later this.renderTarget.texture.name = 'SavePass.rt'; } + /** + * Overwritten to disable the swap. + * + * @type {boolean} + * @default false + */ this.needsSwap = false; - this.fsQuad = new FullScreenQuad( this.material ); + // internals + + this._fsQuad = new FullScreenQuad( this.material ); } + /** + * Performs the save pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer/*, deltaTime, maskActive */ ) { - if ( this.uniforms[ this.textureID ] ) { - - this.uniforms[ this.textureID ].value = readBuffer.texture; - - } + this.uniforms[ 'tDiffuse' ].value = readBuffer.texture; renderer.setRenderTarget( this.renderTarget ); if ( this.clear ) renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } + /** + * Sets the size of the pass. + * + * @param {number} width - The width to set. + * @param {number} height - The height to set. + */ setSize( width, height ) { this.renderTarget.setSize( width, height ); } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ dispose() { this.renderTarget.dispose(); this.material.dispose(); - this.fsQuad.dispose(); + this._fsQuad.dispose(); } diff --git a/examples/jsm/postprocessing/ShaderPass.js b/examples/jsm/postprocessing/ShaderPass.js index 0fa61782675bbe..ce72fcf13ec3da 100644 --- a/examples/jsm/postprocessing/ShaderPass.js +++ b/examples/jsm/postprocessing/ShaderPass.js @@ -4,13 +4,54 @@ import { } from 'three'; import { Pass, FullScreenQuad } from './Pass.js'; +/** + * This pass can be used to create a post processing effect + * with a raw GLSL shader object. Useful for implementing custom + * effects. + * + * ```js + * const fxaaPass = new ShaderPass( FXAAShader ); + * composer.addPass( fxaaPass ); + * ``` + * + * @augments Pass + * @three_import import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js'; + */ class ShaderPass extends Pass { - constructor( shader, textureID ) { + /** + * Constructs a new shader pass. + * + * @param {Object|ShaderMaterial} [shader] - A shader object holding vertex and fragment shader as well as + * defines and uniforms. It's also valid to pass a custom shader material. + * @param {string} [textureID='tDiffuse'] - The name of the texture uniform that should sample + * the read buffer. + */ + constructor( shader, textureID = 'tDiffuse' ) { super(); - this.textureID = ( textureID !== undefined ) ? textureID : 'tDiffuse'; + /** + * The name of the texture uniform that should sample the read buffer. + * + * @type {string} + * @default 'tDiffuse' + */ + this.textureID = textureID; + + /** + * The pass uniforms. + * + * @type {?Object} + */ + this.uniforms = null; + + /** + * The pass material. + * + * @type {?ShaderMaterial} + */ + this.material = null; if ( shader instanceof ShaderMaterial ) { @@ -24,6 +65,7 @@ class ShaderPass extends Pass { this.material = new ShaderMaterial( { + name: ( shader.name !== undefined ) ? shader.name : 'unspecified', defines: Object.assign( {}, shader.defines ), uniforms: this.uniforms, vertexShader: shader.vertexShader, @@ -33,10 +75,23 @@ class ShaderPass extends Pass { } - this.fsQuad = new FullScreenQuad( this.material ); + // internals + + this._fsQuad = new FullScreenQuad( this.material ); } + /** + * Performs the shader pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) { if ( this.uniforms[ this.textureID ] ) { @@ -45,29 +100,33 @@ class ShaderPass extends Pass { } - this.fsQuad.material = this.material; + this._fsQuad.material = this.material; if ( this.renderToScreen ) { renderer.setRenderTarget( null ); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } else { renderer.setRenderTarget( writeBuffer ); // 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 ); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ dispose() { this.material.dispose(); - this.fsQuad.dispose(); + this._fsQuad.dispose(); } diff --git a/examples/jsm/postprocessing/TAARenderPass.js b/examples/jsm/postprocessing/TAARenderPass.js index 6a7180ae840de0..eeec1281340732 100644 --- a/examples/jsm/postprocessing/TAARenderPass.js +++ b/examples/jsm/postprocessing/TAARenderPass.js @@ -1,32 +1,85 @@ import { + HalfFloatType, WebGLRenderTarget } from 'three'; import { SSAARenderPass } from './SSAARenderPass.js'; /** * - * Temporal Anti-Aliasing Render Pass + * Temporal Anti-Aliasing Render Pass. * - * When there is no motion in the scene, the TAA render pass accumulates jittered camera samples across frames to create a high quality anti-aliased result. + * When there is no motion in the scene, the TAA render pass accumulates jittered camera + * samples across frames to create a high quality anti-aliased result. * - * References: + * Note: This effect uses no reprojection so it is no TRAA implementation. * - * TODO: Add support for motion vector pas so that accumulation of samples across frames can occur on dynamics scenes. + * ```js + * const taaRenderPass = new TAARenderPass( scene, camera ); + * taaRenderPass.unbiased = false; + * composer.addPass( taaRenderPass ); + * ``` * + * @augments SSAARenderPass + * @three_import import { TAARenderPass } from 'three/addons/postprocessing/TAARenderPass.js'; */ - class TAARenderPass extends SSAARenderPass { + /** + * Constructs a new TAA render pass. + * + * @param {Scene} scene - The scene to render. + * @param {Camera} camera - The camera. + * @param {?(number|Color|string)} [clearColor=0x000000] - The clear color of the render pass. + * @param {?number} [clearAlpha=0] - The clear alpha of the render pass. + */ constructor( scene, camera, clearColor, clearAlpha ) { super( scene, camera, clearColor, clearAlpha ); + /** + * Overwritten and set to 0 by default. + * + * @type {number} + * @default 0 + */ this.sampleLevel = 0; + + /** + * Whether to accumulate frames or not. This enables + * the TAA. + * + * @type {boolean} + * @default false + */ this.accumulate = false; + /** + * The accumulation index. + * + * @type {number} + * @default -1 + */ + this.accumulateIndex = - 1; + + // internals + + this._sampleRenderTarget = null; + this._holdRenderTarget = null; + } - render( renderer, writeBuffer, readBuffer, deltaTime ) { + /** + * Performs the TAA render pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ + render( renderer, writeBuffer, readBuffer, deltaTime/*, maskActive*/ ) { if ( this.accumulate === false ) { @@ -39,23 +92,23 @@ class TAARenderPass extends SSAARenderPass { const jitterOffsets = _JitterVectors[ 5 ]; - if ( this.sampleRenderTarget === undefined ) { + if ( this._sampleRenderTarget === null ) { - this.sampleRenderTarget = new WebGLRenderTarget( readBuffer.width, readBuffer.height, this.params ); - this.sampleRenderTarget.texture.name = 'TAARenderPass.sample'; + this._sampleRenderTarget = new WebGLRenderTarget( readBuffer.width, readBuffer.height, { type: HalfFloatType } ); + this._sampleRenderTarget.texture.name = 'TAARenderPass.sample'; } - if ( this.holdRenderTarget === undefined ) { + if ( this._holdRenderTarget === null ) { - this.holdRenderTarget = new WebGLRenderTarget( readBuffer.width, readBuffer.height, this.params ); - this.holdRenderTarget.texture.name = 'TAARenderPass.hold'; + this._holdRenderTarget = new WebGLRenderTarget( readBuffer.width, readBuffer.height, { type: HalfFloatType } ); + this._holdRenderTarget.texture.name = 'TAARenderPass.hold'; } if ( this.accumulateIndex === - 1 ) { - super.render( renderer, this.holdRenderTarget, readBuffer, deltaTime ); + super.render( renderer, this._holdRenderTarget, readBuffer, deltaTime ); this.accumulateIndex = 0; @@ -64,12 +117,15 @@ class TAARenderPass extends SSAARenderPass { const autoClear = renderer.autoClear; renderer.autoClear = false; + renderer.getClearColor( this._oldClearColor ); + const oldClearAlpha = renderer.getClearAlpha(); + const sampleWeight = 1.0 / ( jitterOffsets.length ); if ( this.accumulateIndex >= 0 && this.accumulateIndex < jitterOffsets.length ) { - this.copyUniforms[ 'opacity' ].value = sampleWeight; - this.copyUniforms[ 'tDiffuse' ].value = writeBuffer.texture; + this._copyUniforms[ 'opacity' ].value = sampleWeight; + this._copyUniforms[ 'tDiffuse' ].value = writeBuffer.texture; // render the scene multiple times, each slightly jitter offset from the last and accumulate the results. const numSamplesPerFrame = Math.pow( 2, this.sampleLevel ); @@ -87,12 +143,19 @@ class TAARenderPass extends SSAARenderPass { } renderer.setRenderTarget( writeBuffer ); + renderer.setClearColor( this.clearColor, this.clearAlpha ); renderer.clear(); renderer.render( this.scene, this.camera ); - renderer.setRenderTarget( this.sampleRenderTarget ); - if ( this.accumulateIndex === 0 ) renderer.clear(); - this.fsQuad.render( renderer ); + renderer.setRenderTarget( this._sampleRenderTarget ); + if ( this.accumulateIndex === 0 ) { + + renderer.setClearColor( 0x000000, 0.0 ); + renderer.clear(); + + } + + this._fsQuad.render( renderer ); this.accumulateIndex ++; @@ -104,38 +167,42 @@ class TAARenderPass extends SSAARenderPass { } + renderer.setClearColor( this.clearColor, this.clearAlpha ); const accumulationWeight = this.accumulateIndex * sampleWeight; if ( accumulationWeight > 0 ) { - this.copyUniforms[ 'opacity' ].value = 1.0; - this.copyUniforms[ 'tDiffuse' ].value = this.sampleRenderTarget.texture; + this._copyUniforms[ 'opacity' ].value = 1.0; + this._copyUniforms[ 'tDiffuse' ].value = this._sampleRenderTarget.texture; renderer.setRenderTarget( writeBuffer ); renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } if ( accumulationWeight < 1.0 ) { - this.copyUniforms[ 'opacity' ].value = 1.0 - accumulationWeight; - this.copyUniforms[ 'tDiffuse' ].value = this.holdRenderTarget.texture; + this._copyUniforms[ 'opacity' ].value = 1.0 - accumulationWeight; + this._copyUniforms[ 'tDiffuse' ].value = this._holdRenderTarget.texture; renderer.setRenderTarget( writeBuffer ); - if ( accumulationWeight === 0 ) renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } renderer.autoClear = autoClear; + renderer.setClearColor( this._oldClearColor, oldClearAlpha ); } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ dispose() { super.dispose(); - if ( this.sampleRenderTarget !== undefined ) this.sampleRenderTarget.dispose(); - if ( this.holdRenderTarget !== undefined ) this.holdRenderTarget.dispose(); + if ( this._holdRenderTarget ) this._holdRenderTarget.dispose(); } diff --git a/examples/jsm/postprocessing/TexturePass.js b/examples/jsm/postprocessing/TexturePass.js index e7d6ac1faa170d..649a235416d3a1 100644 --- a/examples/jsm/postprocessing/TexturePass.js +++ b/examples/jsm/postprocessing/TexturePass.js @@ -5,41 +5,103 @@ import { import { Pass, FullScreenQuad } from './Pass.js'; import { CopyShader } from '../shaders/CopyShader.js'; +/** + * This pass can be used to render a texture over the entire screen. + * + * ```js + * const texture = new THREE.TextureLoader().load( 'textures/2294472375_24a3b8ef46_o.jpg' ); + * texture.colorSpace = THREE.SRGBColorSpace; + * + * const texturePass = new TexturePass( texture ); + * composer.addPass( texturePass ); + * ``` + * + * @augments Pass + * @three_import import { TexturePass } from 'three/addons/postprocessing/TexturePass.js'; + */ class TexturePass extends Pass { - constructor( map, opacity ) { + /** + * Constructs a new texture pass. + * + * @param {Texture} map - The texture to render. + * @param {number} [opacity=1] - The opacity. + */ + constructor( map, opacity = 1 ) { super(); const shader = CopyShader; + /** + * The texture to render. + * + * @type {Texture} + */ this.map = map; - this.opacity = ( opacity !== undefined ) ? opacity : 1.0; + /** + * The opacity. + * + * @type {number} + * @default 1 + */ + this.opacity = opacity; + + /** + * Overwritten to disable the swap. + * + * @type {boolean} + * @default false + */ + this.needsSwap = false; + + /** + * The pass uniforms. + * + * @type {Object} + */ this.uniforms = UniformsUtils.clone( shader.uniforms ); + /** + * The pass material. + * + * @type {ShaderMaterial} + */ this.material = new ShaderMaterial( { uniforms: this.uniforms, vertexShader: shader.vertexShader, fragmentShader: shader.fragmentShader, depthTest: false, - depthWrite: false + depthWrite: false, + premultipliedAlpha: true } ); - this.needsSwap = false; + // internals - this.fsQuad = new FullScreenQuad( null ); + this._fsQuad = new FullScreenQuad( null ); } + /** + * Performs the texture pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) { const oldAutoClear = renderer.autoClear; renderer.autoClear = false; - this.fsQuad.material = this.material; + this._fsQuad.material = this.material; this.uniforms[ 'opacity' ].value = this.opacity; this.uniforms[ 'tDiffuse' ].value = this.map; @@ -47,17 +109,20 @@ class TexturePass extends Pass { renderer.setRenderTarget( this.renderToScreen ? null : readBuffer ); if ( this.clear ) renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); renderer.autoClear = oldAutoClear; } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ dispose() { this.material.dispose(); - - this.fsQuad.dispose(); + this._fsQuad.dispose(); } diff --git a/examples/jsm/postprocessing/UnrealBloomPass.js b/examples/jsm/postprocessing/UnrealBloomPass.js index 555491c23a4d35..5ad6fc0ce4ac2a 100644 --- a/examples/jsm/postprocessing/UnrealBloomPass.js +++ b/examples/jsm/postprocessing/UnrealBloomPass.js @@ -1,6 +1,7 @@ import { AdditiveBlending, Color, + HalfFloatType, MeshBasicMaterial, ShaderMaterial, UniformsUtils, @@ -13,28 +14,87 @@ import { CopyShader } from '../shaders/CopyShader.js'; import { LuminosityHighPassShader } from '../shaders/LuminosityHighPassShader.js'; /** - * UnrealBloomPass is inspired by the bloom pass of Unreal Engine. It creates a + * This pass is inspired by the bloom pass of Unreal Engine. It creates a * mip map chain of bloom textures and blurs them with different radii. Because * of the weighted combination of mips, and because larger blurs are done on * higher mips, this effect provides good quality and performance. * + * When using this pass, tone mapping must be enabled in the renderer settings. + * * Reference: - * - https://docs.unrealengine.com/latest/INT/Engine/Rendering/PostProcessEffects/Bloom/ + * - [Bloom in Unreal Engine]{@link https://docs.unrealengine.com/latest/INT/Engine/Rendering/PostProcessEffects/Bloom/} + * + * ```js + * const resolution = new THREE.Vector2( window.innerWidth, window.innerHeight ); + * const bloomPass = new UnrealBloomPass( resolution, 1.5, 0.4, 0.85 ); + * composer.addPass( bloomPass ); + * ``` + * + * @augments Pass + * @three_import import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js'; */ class UnrealBloomPass extends Pass { - constructor( resolution, strength, radius, threshold ) { + /** + * Constructs a new Unreal Bloom pass. + * + * @param {Vector2} [resolution] - The effect's resolution. + * @param {number} [strength=1] - The Bloom strength. + * @param {number} radius - The Bloom radius. + * @param {number} threshold - The luminance threshold limits which bright areas contribute to the Bloom effect. + */ + constructor( resolution, strength = 1, radius, threshold ) { super(); - this.strength = ( strength !== undefined ) ? strength : 1; + /** + * The Bloom strength. + * + * @type {number} + * @default 1 + */ + this.strength = strength; + + /** + * The Bloom radius. + * + * @type {number} + */ this.radius = radius; + + /** + * The luminance threshold limits which bright areas contribute to the Bloom effect. + * + * @type {number} + */ this.threshold = threshold; + + /** + * The effect's resolution. + * + * @type {Vector2} + * @default (256,256) + */ this.resolution = ( resolution !== undefined ) ? new Vector2( resolution.x, resolution.y ) : new Vector2( 256, 256 ); - // create color only once here, reuse it later inside the render function + /** + * The effect's clear color + * + * @type {Color} + * @default (0,0,0) + */ this.clearColor = new Color( 0, 0, 0 ); + /** + * Overwritten to disable the swap. + * + * @type {boolean} + * @default false + */ + this.needsSwap = false; + + // internals + // render targets this.renderTargetsHorizontal = []; this.renderTargetsVertical = []; @@ -42,20 +102,20 @@ class UnrealBloomPass extends Pass { let resx = Math.round( this.resolution.x / 2 ); let resy = Math.round( this.resolution.y / 2 ); - this.renderTargetBright = new WebGLRenderTarget( resx, resy ); + this.renderTargetBright = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } ); this.renderTargetBright.texture.name = 'UnrealBloomPass.bright'; this.renderTargetBright.texture.generateMipmaps = false; for ( let i = 0; i < this.nMips; i ++ ) { - const renderTargetHorizonal = new WebGLRenderTarget( resx, resy ); + const renderTargetHorizontal = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } ); - renderTargetHorizonal.texture.name = 'UnrealBloomPass.h' + i; - renderTargetHorizonal.texture.generateMipmaps = false; + renderTargetHorizontal.texture.name = 'UnrealBloomPass.h' + i; + renderTargetHorizontal.texture.generateMipmaps = false; - this.renderTargetsHorizontal.push( renderTargetHorizonal ); + this.renderTargetsHorizontal.push( renderTargetHorizontal ); - const renderTargetVertical = new WebGLRenderTarget( resx, resy ); + const renderTargetVertical = new WebGLRenderTarget( resx, resy, { type: HalfFloatType } ); renderTargetVertical.texture.name = 'UnrealBloomPass.v' + i; renderTargetVertical.texture.generateMipmaps = false; @@ -79,11 +139,11 @@ class UnrealBloomPass extends Pass { this.materialHighPassFilter = new ShaderMaterial( { uniforms: this.highPassUniforms, vertexShader: highPassShader.vertexShader, - fragmentShader: highPassShader.fragmentShader, - defines: {} + fragmentShader: highPassShader.fragmentShader } ); - // Gaussian Blur Materials + // gaussian blur materials + this.separableBlurMaterials = []; const kernelSizeArray = [ 3, 5, 7, 9, 11 ]; resx = Math.round( this.resolution.x / 2 ); @@ -91,9 +151,9 @@ class UnrealBloomPass extends Pass { for ( let i = 0; i < this.nMips; i ++ ) { - this.separableBlurMaterials.push( this.getSeperableBlurMaterial( kernelSizeArray[ i ] ) ); + this.separableBlurMaterials.push( this._getSeparableBlurMaterial( kernelSizeArray[ i ] ) ); - this.separableBlurMaterials[ i ].uniforms[ 'texSize' ].value = new Vector2( resx, resy ); + this.separableBlurMaterials[ i ].uniforms[ 'invSize' ].value = new Vector2( 1 / resx, 1 / resy ); resx = Math.round( resx / 2 ); @@ -101,8 +161,9 @@ class UnrealBloomPass extends Pass { } - // Composite material - this.compositeMaterial = this.getCompositeMaterial( this.nMips ); + // composite material + + this.compositeMaterial = this._getCompositeMaterial( this.nMips ); this.compositeMaterial.uniforms[ 'blurTexture1' ].value = this.renderTargetsVertical[ 0 ].texture; this.compositeMaterial.uniforms[ 'blurTexture2' ].value = this.renderTargetsVertical[ 1 ].texture; this.compositeMaterial.uniforms[ 'blurTexture3' ].value = this.renderTargetsVertical[ 2 ].texture; @@ -110,42 +171,39 @@ class UnrealBloomPass extends Pass { this.compositeMaterial.uniforms[ 'blurTexture5' ].value = this.renderTargetsVertical[ 4 ].texture; this.compositeMaterial.uniforms[ 'bloomStrength' ].value = strength; this.compositeMaterial.uniforms[ 'bloomRadius' ].value = 0.1; - this.compositeMaterial.needsUpdate = true; const bloomFactors = [ 1.0, 0.8, 0.6, 0.4, 0.2 ]; this.compositeMaterial.uniforms[ 'bloomFactors' ].value = bloomFactors; this.bloomTintColors = [ new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ) ]; this.compositeMaterial.uniforms[ 'bloomTintColors' ].value = this.bloomTintColors; - // copy material + // blend material - const copyShader = CopyShader; + this.copyUniforms = UniformsUtils.clone( CopyShader.uniforms ); - this.copyUniforms = UniformsUtils.clone( copyShader.uniforms ); - this.copyUniforms[ 'opacity' ].value = 1.0; - - this.materialCopy = new ShaderMaterial( { + this.blendMaterial = new ShaderMaterial( { uniforms: this.copyUniforms, - vertexShader: copyShader.vertexShader, - fragmentShader: copyShader.fragmentShader, + vertexShader: CopyShader.vertexShader, + fragmentShader: CopyShader.fragmentShader, blending: AdditiveBlending, depthTest: false, depthWrite: false, transparent: true } ); - this.enabled = true; - this.needsSwap = false; - this._oldClearColor = new Color(); - this.oldClearAlpha = 1; + this._oldClearAlpha = 1; - this.basic = new MeshBasicMaterial(); + this._basic = new MeshBasicMaterial(); - this.fsQuad = new FullScreenQuad( null ); + this._fsQuad = new FullScreenQuad( null ); } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever the pass is no longer used in your app. + */ dispose() { for ( let i = 0; i < this.renderTargetsHorizontal.length; i ++ ) { @@ -171,15 +229,21 @@ class UnrealBloomPass extends Pass { } this.compositeMaterial.dispose(); - this.materialCopy.dispose(); - this.basic.dispose(); + this.blendMaterial.dispose(); + this._basic.dispose(); // - this.fsQuad.dispose(); + this._fsQuad.dispose(); } + /** + * Sets the size of the pass. + * + * @param {number} width - The width to set. + * @param {number} height - The height to set. + */ setSize( width, height ) { let resx = Math.round( width / 2 ); @@ -192,7 +256,7 @@ class UnrealBloomPass extends Pass { this.renderTargetsHorizontal[ i ].setSize( resx, resy ); this.renderTargetsVertical[ i ].setSize( resx, resy ); - this.separableBlurMaterials[ i ].uniforms[ 'texSize' ].value = new Vector2( resx, resy ); + this.separableBlurMaterials[ i ].uniforms[ 'invSize' ].value = new Vector2( 1 / resx, 1 / resy ); resx = Math.round( resx / 2 ); resy = Math.round( resy / 2 ); @@ -201,10 +265,21 @@ class UnrealBloomPass extends Pass { } + /** + * Performs the Bloom pass. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {WebGLRenderTarget} writeBuffer - The write buffer. This buffer is intended as the rendering + * destination for the pass. + * @param {WebGLRenderTarget} readBuffer - The read buffer. The pass can access the result from the + * previous pass from this buffer. + * @param {number} deltaTime - The delta time in seconds. + * @param {boolean} maskActive - Whether masking is active or not. + */ render( renderer, writeBuffer, readBuffer, deltaTime, maskActive ) { renderer.getClearColor( this._oldClearColor ); - this.oldClearAlpha = renderer.getClearAlpha(); + this._oldClearAlpha = renderer.getClearAlpha(); const oldAutoClear = renderer.autoClear; renderer.autoClear = false; @@ -216,12 +291,12 @@ class UnrealBloomPass extends Pass { if ( this.renderToScreen ) { - this.fsQuad.material = this.basic; - this.basic.map = readBuffer.texture; + this._fsQuad.material = this._basic; + this._basic.map = readBuffer.texture; renderer.setRenderTarget( null ); renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } @@ -229,11 +304,11 @@ class UnrealBloomPass extends Pass { this.highPassUniforms[ 'tDiffuse' ].value = readBuffer.texture; this.highPassUniforms[ 'luminosityThreshold' ].value = this.threshold; - this.fsQuad.material = this.materialHighPassFilter; + this._fsQuad.material = this.materialHighPassFilter; renderer.setRenderTarget( this.renderTargetBright ); renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); // 2. Blur All the mips progressively @@ -241,19 +316,19 @@ class UnrealBloomPass extends Pass { for ( let i = 0; i < this.nMips; i ++ ) { - this.fsQuad.material = this.separableBlurMaterials[ i ]; + this._fsQuad.material = this.separableBlurMaterials[ i ]; this.separableBlurMaterials[ i ].uniforms[ 'colorTexture' ].value = inputRenderTarget.texture; this.separableBlurMaterials[ i ].uniforms[ 'direction' ].value = UnrealBloomPass.BlurDirectionX; renderer.setRenderTarget( this.renderTargetsHorizontal[ i ] ); renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); this.separableBlurMaterials[ i ].uniforms[ 'colorTexture' ].value = this.renderTargetsHorizontal[ i ].texture; this.separableBlurMaterials[ i ].uniforms[ 'direction' ].value = UnrealBloomPass.BlurDirectionY; renderer.setRenderTarget( this.renderTargetsVertical[ i ] ); renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); inputRenderTarget = this.renderTargetsVertical[ i ]; @@ -261,18 +336,18 @@ class UnrealBloomPass extends Pass { // Composite All the mips - this.fsQuad.material = this.compositeMaterial; + this._fsQuad.material = this.compositeMaterial; this.compositeMaterial.uniforms[ 'bloomStrength' ].value = this.strength; this.compositeMaterial.uniforms[ 'bloomRadius' ].value = this.radius; this.compositeMaterial.uniforms[ 'bloomTintColors' ].value = this.bloomTintColors; renderer.setRenderTarget( this.renderTargetsHorizontal[ 0 ] ); renderer.clear(); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); // Blend it additively over the input texture - this.fsQuad.material = this.materialCopy; + this._fsQuad.material = this.blendMaterial; this.copyUniforms[ 'tDiffuse' ].value = this.renderTargetsHorizontal[ 0 ].texture; if ( maskActive ) renderer.state.buffers.stencil.setTest( true ); @@ -280,35 +355,45 @@ class UnrealBloomPass extends Pass { if ( this.renderToScreen ) { renderer.setRenderTarget( null ); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } else { renderer.setRenderTarget( readBuffer ); - this.fsQuad.render( renderer ); + this._fsQuad.render( renderer ); } // Restore renderer settings - renderer.setClearColor( this._oldClearColor, this.oldClearAlpha ); + renderer.setClearColor( this._oldClearColor, this._oldClearAlpha ); renderer.autoClear = oldAutoClear; } - getSeperableBlurMaterial( kernelRadius ) { + // internals + + _getSeparableBlurMaterial( kernelRadius ) { + + const coefficients = []; + + for ( let i = 0; i < kernelRadius; i ++ ) { + + coefficients.push( 0.39894 * Math.exp( - 0.5 * i * i / ( kernelRadius * kernelRadius ) ) / kernelRadius ); + + } return new ShaderMaterial( { defines: { - 'KERNEL_RADIUS': kernelRadius, - 'SIGMA': kernelRadius + 'KERNEL_RADIUS': kernelRadius }, uniforms: { 'colorTexture': { value: null }, - 'texSize': { value: new Vector2( 0.5, 0.5 ) }, - 'direction': { value: new Vector2( 0.5, 0.5 ) } + 'invSize': { value: new Vector2( 0.5, 0.5 ) }, // inverse texture size + 'direction': { value: new Vector2( 0.5, 0.5 ) }, + 'gaussianCoefficients': { value: coefficients } // precomputed Gaussian coefficients }, vertexShader: @@ -322,23 +407,19 @@ class UnrealBloomPass extends Pass { `#include varying vec2 vUv; uniform sampler2D colorTexture; - uniform vec2 texSize; + uniform vec2 invSize; uniform vec2 direction; + uniform float gaussianCoefficients[KERNEL_RADIUS]; - float gaussianPdf(in float x, in float sigma) { - return 0.39894 * exp( -0.5 * x * x/( sigma * sigma))/sigma; - } void main() { - vec2 invSize = 1.0 / texSize; - float fSigma = float(SIGMA); - float weightSum = gaussianPdf(0.0, fSigma); - vec3 diffuseSum = texture2D( colorTexture, vUv).rgb * weightSum; + float weightSum = gaussianCoefficients[0]; + vec3 diffuseSum = texture2D( colorTexture, vUv ).rgb * weightSum; for( int i = 1; i < KERNEL_RADIUS; i ++ ) { float x = float(i); - float w = gaussianPdf(x, fSigma); + float w = gaussianCoefficients[i]; vec2 uvOffset = direction * invSize * x; - vec3 sample1 = texture2D( colorTexture, vUv + uvOffset).rgb; - vec3 sample2 = texture2D( colorTexture, vUv - uvOffset).rgb; + vec3 sample1 = texture2D( colorTexture, vUv + uvOffset ).rgb; + vec3 sample2 = texture2D( colorTexture, vUv - uvOffset ).rgb; diffuseSum += (sample1 + sample2) * w; weightSum += 2.0 * w; } @@ -348,7 +429,7 @@ class UnrealBloomPass extends Pass { } - getCompositeMaterial( nMips ) { + _getCompositeMaterial( nMips ) { return new ShaderMaterial( { diff --git a/examples/jsm/renderers/CSS2DRenderer.js b/examples/jsm/renderers/CSS2DRenderer.js index 6d67931b97ff44..1def549b33f3db 100644 --- a/examples/jsm/renderers/CSS2DRenderer.js +++ b/examples/jsm/renderers/CSS2DRenderer.js @@ -5,14 +5,39 @@ import { Vector3 } from 'three'; +/** + * The only type of 3D object that is supported by {@link CSS2DRenderer}. + * + * @augments Object3D + * @three_import import { CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js'; + */ class CSS2DObject extends Object3D { + /** + * Constructs a new CSS2D object. + * + * @param {DOMElement} [element] - The DOM element. + */ constructor( element = document.createElement( 'div' ) ) { super(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isCSS2DObject = true; + /** + * The DOM element which defines the appearance of this 3D object. + * + * @type {DOMElement} + * @readonly + * @default true + */ this.element = element; this.element.style.position = 'absolute'; @@ -20,15 +45,25 @@ class CSS2DObject extends Object3D { this.element.setAttribute( 'draggable', false ); - this.center = new Vector2( 0.5, 0.5 ); // ( 0, 0 ) is the lower left; ( 1, 1 ) is the top right + /** + * The 3D objects center point. + * `( 0, 0 )` is the lower left, `( 1, 1 )` is the top right. + * + * @type {Vector2} + * @default (0.5,0.5) + */ + this.center = new Vector2( 0.5, 0.5 ); this.addEventListener( 'removed', function () { this.traverse( function ( object ) { - if ( object.element instanceof Element && object.element.parentNode !== null ) { + if ( + object.element instanceof object.element.ownerDocument.defaultView.Element && + object.element.parentNode !== null + ) { - object.element.parentNode.removeChild( object.element ); + object.element.remove(); } @@ -60,8 +95,25 @@ const _viewProjectionMatrix = new Matrix4(); const _a = new Vector3(); const _b = new Vector3(); +/** + * This renderer is a simplified version of {@link CSS3DRenderer}. The only transformation that is + * supported is translation. + * + * The renderer is very useful if you want to combine HTML based labels with 3D objects. Here too, + * the respective DOM elements are wrapped into an instance of {@link CSS2DObject} and added to the + * scene graph. All other types of renderable 3D objects (like meshes or point clouds) are ignored. + * + * `CSS2DRenderer` only supports 100% browser and display zoom. + * + * @three_import import { CSS2DRenderer } from 'three/addons/renderers/CSS2DRenderer.js'; + */ class CSS2DRenderer { + /** + * Constructs a new CSS2D renderer. + * + * @param {CSS2DRenderer~Parameters} [parameters] - The parameters. + */ constructor( parameters = {} ) { const _this = this; @@ -77,8 +129,18 @@ class CSS2DRenderer { domElement.style.overflow = 'hidden'; + /** + * The DOM where the renderer appends its child-elements. + * + * @type {DOMElement} + */ this.domElement = domElement; + /** + * Returns an object containing the width and height of the renderer. + * + * @return {{width:number,height:number}} The size of the renderer. + */ this.getSize = function () { return { @@ -88,6 +150,12 @@ class CSS2DRenderer { }; + /** + * Renders the given scene using the given camera. + * + * @param {Object3D} scene - A scene or any other type of 3D object. + * @param {Camera} camera - The camera. + */ this.render = function ( scene, camera ) { if ( scene.matrixWorldAutoUpdate === true ) scene.updateMatrixWorld(); @@ -101,6 +169,12 @@ class CSS2DRenderer { }; + /** + * Resizes the renderer to the given width and height. + * + * @param {number} width - The width of the renderer. + * @param {number} height - The height of the renderer. + */ this.setSize = function ( width, height ) { _width = width; @@ -114,22 +188,42 @@ class CSS2DRenderer { }; + function hideObject( object ) { + + if ( object.isCSS2DObject ) object.element.style.display = 'none'; + + for ( let i = 0, l = object.children.length; i < l; i ++ ) { + + hideObject( object.children[ i ] ); + + } + + } + function renderObject( object, scene, camera ) { + if ( object.visible === false ) { + + hideObject( object ); + + return; + + } + if ( object.isCSS2DObject ) { _vector.setFromMatrixPosition( object.matrixWorld ); _vector.applyMatrix4( _viewProjectionMatrix ); - const visible = ( object.visible === true ) && ( _vector.z >= - 1 && _vector.z <= 1 ) && ( object.layers.test( camera.layers ) === true ); - object.element.style.display = ( visible === true ) ? '' : 'none'; + const visible = ( _vector.z >= - 1 && _vector.z <= 1 ) && ( object.layers.test( camera.layers ) === true ); + + const element = object.element; + element.style.display = visible === true ? '' : 'none'; if ( visible === true ) { object.onBeforeRender( _this, scene, camera ); - const element = object.element; - element.style.transform = 'translate(' + ( - 100 * object.center.x ) + '%,' + ( - 100 * object.center.y ) + '%)' + 'translate(' + ( _vector.x * _widthHalf + _widthHalf ) + 'px,' + ( - _vector.y * _heightHalf + _heightHalf ) + 'px)'; if ( element.parentNode !== domElement ) { @@ -171,7 +265,7 @@ class CSS2DRenderer { const result = []; - scene.traverse( function ( object ) { + scene.traverseVisible( function ( object ) { if ( object.isCSS2DObject ) result.push( object ); @@ -212,4 +306,12 @@ class CSS2DRenderer { } +/** + * Constructor parameters of `CSS2DRenderer`. + * + * @typedef {Object} CSS2DRenderer~Parameters + * @property {DOMElement} [element] - A DOM element where the renderer appends its child-elements. + * If not passed in here, a new div element will be created. + **/ + export { CSS2DObject, CSS2DRenderer }; diff --git a/examples/jsm/renderers/CSS3DRenderer.js b/examples/jsm/renderers/CSS3DRenderer.js index 0dffeeaef4eed4..94aacafc9d7792 100644 --- a/examples/jsm/renderers/CSS3DRenderer.js +++ b/examples/jsm/renderers/CSS3DRenderer.js @@ -5,22 +5,45 @@ import { Vector3 } from 'three'; -/** - * Based on http://www.emagix.net/academic/mscs-project/item/camera-sync-with-css3-and-webgl-threejs - */ +// Based on http://www.emagix.net/academic/mscs-project/item/camera-sync-with-css3-and-webgl-threejs const _position = new Vector3(); const _quaternion = new Quaternion(); const _scale = new Vector3(); +/** + * The base 3D object that is supported by {@link CSS3DRenderer}. + * + * @augments Object3D + * @three_import import { CSS3DObject } from 'three/addons/renderers/CSS3DRenderer.js'; + */ class CSS3DObject extends Object3D { + /** + * Constructs a new CSS3D object. + * + * @param {DOMElement} [element] - The DOM element. + */ constructor( element = document.createElement( 'div' ) ) { super(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isCSS3DObject = true; + /** + * The DOM element which defines the appearance of this 3D object. + * + * @type {DOMElement} + * @readonly + * @default true + */ this.element = element; this.element.style.position = 'absolute'; this.element.style.pointerEvents = 'auto'; @@ -32,9 +55,12 @@ class CSS3DObject extends Object3D { this.traverse( function ( object ) { - if ( object.element instanceof Element && object.element.parentNode !== null ) { + if ( + object.element instanceof object.element.ownerDocument.defaultView.Element && + object.element.parentNode !== null + ) { - object.element.parentNode.removeChild( object.element ); + object.element.remove(); } @@ -56,14 +82,39 @@ class CSS3DObject extends Object3D { } +/** + * A specialized version of {@link CSS3DObject} that represents + * DOM elements as sprites. + * + * @augments CSS3DObject + * @three_import import { CSS3DSprite } from 'three/addons/renderers/CSS3DRenderer.js'; + */ class CSS3DSprite extends CSS3DObject { + /** + * Constructs a new CSS3D sprite object. + * + * @param {DOMElement} [element] - The DOM element. + */ constructor( element ) { super( element ); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isCSS3DSprite = true; + /** + * The sprite's rotation in radians. + * + * @type {number} + * @default 0 + */ this.rotation2D = 0; } @@ -85,8 +136,30 @@ class CSS3DSprite extends CSS3DObject { const _matrix = new Matrix4(); const _matrix2 = new Matrix4(); +/** + * This renderer can be used to apply hierarchical 3D transformations to DOM elements + * via the CSS3 [transform]{@link https://www.w3schools.com/cssref/css3_pr_transform.asp} property. + * `CSS3DRenderer` is particularly interesting if you want to apply 3D effects to a website without + * canvas based rendering. It can also be used in order to combine DOM elements with WebGLcontent. + * + * There are, however, some important limitations: + * + * - It's not possible to use the material system of *three.js*. + * - It's also not possible to use geometries. + * - The renderer only supports 100% browser and display zoom. + * + * So `CSS3DRenderer` is just focused on ordinary DOM elements. These elements are wrapped into special + * 3D objects ({@link CSS3DObject} or {@link CSS3DSprite}) and then added to the scene graph. + * + * @three_import import { CSS3DRenderer } from 'three/addons/renderers/CSS3DRenderer.js'; + */ class CSS3DRenderer { + /** + * Constructs a new CSS3D renderer. + * + * @param {CSS3DRenderer~Parameters} [parameters] - The parameters. + */ constructor( parameters = {} ) { const _this = this; @@ -95,7 +168,7 @@ class CSS3DRenderer { let _widthHalf, _heightHalf; const cache = { - camera: { fov: 0, style: '' }, + camera: { style: '' }, objects: new WeakMap() }; @@ -103,6 +176,11 @@ class CSS3DRenderer { domElement.style.overflow = 'hidden'; + /** + * The DOM where the renderer appends its child-elements. + * + * @type {DOMElement} + */ this.domElement = domElement; const viewElement = document.createElement( 'div' ); @@ -116,6 +194,11 @@ class CSS3DRenderer { viewElement.appendChild( cameraElement ); + /** + * Returns an object containing the width and height of the renderer. + * + * @return {{width:number,height:number}} The size of the renderer. + */ this.getSize = function () { return { @@ -125,17 +208,16 @@ class CSS3DRenderer { }; + /** + * Renders the given scene using the given camera. + * + * @param {Object3D} scene - A scene or any other type of 3D object. + * @param {Camera} camera - The camera. + */ this.render = function ( scene, camera ) { const fov = camera.projectionMatrix.elements[ 5 ] * _heightHalf; - if ( cache.camera.fov !== fov ) { - - viewElement.style.perspective = camera.isPerspectiveCamera ? fov + 'px' : ''; - cache.camera.fov = fov; - - } - if ( camera.view && camera.view.enabled ) { // view offset @@ -166,8 +248,9 @@ class CSS3DRenderer { const cameraCSSMatrix = camera.isOrthographicCamera ? `scale( ${ scaleByViewOffset } )` + 'scale(' + fov + ')' + 'translate(' + epsilon( tx ) + 'px,' + epsilon( ty ) + 'px)' + getCameraCSSMatrix( camera.matrixWorldInverse ) : `scale( ${ scaleByViewOffset } )` + 'translateZ(' + fov + 'px)' + getCameraCSSMatrix( camera.matrixWorldInverse ); + const perspective = camera.isPerspectiveCamera ? 'perspective(' + fov + 'px) ' : ''; - const style = cameraCSSMatrix + + const style = perspective + cameraCSSMatrix + 'translate(' + _widthHalf + 'px,' + _heightHalf + 'px)'; if ( cache.camera.style !== style ) { @@ -182,6 +265,12 @@ class CSS3DRenderer { }; + /** + * Resizes the renderer to the given width and height. + * + * @param {number} width - The width of the renderer. + * @param {number} height - The height of the renderer. + */ this.setSize = function ( width, height ) { _width = width; @@ -257,12 +346,34 @@ class CSS3DRenderer { } + function hideObject( object ) { + + if ( object.isCSS3DObject ) object.element.style.display = 'none'; + + for ( let i = 0, l = object.children.length; i < l; i ++ ) { + + hideObject( object.children[ i ] ); + + } + + } + function renderObject( object, scene, camera, cameraCSSMatrix ) { + if ( object.visible === false ) { + + hideObject( object ); + + return; + + } + if ( object.isCSS3DObject ) { - const visible = ( object.visible === true ) && ( object.layers.test( camera.layers ) === true ); - object.element.style.display = ( visible === true ) ? '' : 'none'; + const visible = ( object.layers.test( camera.layers ) === true ); + + const element = object.element; + element.style.display = visible === true ? '' : 'none'; if ( visible === true ) { @@ -296,7 +407,6 @@ class CSS3DRenderer { } - const element = object.element; const cachedObject = cache.objects.get( object ); if ( cachedObject === undefined || cachedObject.style !== style ) { @@ -332,4 +442,12 @@ class CSS3DRenderer { } +/** + * Constructor parameters of `CSS3DRenderer`. + * + * @typedef {Object} CSS3DRenderer~Parameters + * @property {DOMElement} [element] - A DOM element where the renderer appends its child-elements. + * If not passed in here, a new div element will be created. + **/ + export { CSS3DObject, CSS3DSprite, CSS3DRenderer }; diff --git a/examples/jsm/renderers/Projector.js b/examples/jsm/renderers/Projector.js index f80df799d42e66..2d3882984412fc 100644 --- a/examples/jsm/renderers/Projector.js +++ b/examples/jsm/renderers/Projector.js @@ -120,10 +120,18 @@ class RenderableSprite { } -// - +/** + * This class can project a given scene in 3D space into a 2D representation + * used for rendering with a 2D API. `Projector` is currently used by {@link SVGRenderer} + * and was previously used by the legacy `CanvasRenderer`. + * + * @three_import import { Projector } from 'three/addons/renderers/Projector.js'; + */ class Projector { + /** + * Constructs a new projector. + */ constructor() { let _object, _objectCount, _objectPoolLength = 0, @@ -403,6 +411,16 @@ class Projector { } + /** + * Projects the given scene in 3D space into a 2D representation. The result + * is an object with renderable items. + * + * @param {Object3D} scene - A scene or any other type of 3D object. + * @param {Camera} camera - The camera. + * @param {boolean} sortObjects - Whether to sort objects or not. + * @param {boolean} sortElements - Whether to sort elements (faces, lines and sprites) or not. + * @return {{objects:Array,lights:Array,elements:Array}} The projected scene as renderable objects. + */ this.projectScene = function ( scene, camera, sortObjects, sortElements ) { _faceCount = 0; diff --git a/examples/jsm/renderers/SVGRenderer.js b/examples/jsm/renderers/SVGRenderer.js index 529196364b1180..87f0c08acd67b8 100644 --- a/examples/jsm/renderers/SVGRenderer.js +++ b/examples/jsm/renderers/SVGRenderer.js @@ -5,29 +5,79 @@ import { Matrix3, Matrix4, Object3D, + SRGBColorSpace, Vector3 } from 'three'; -import { Projector } from '../renderers/Projector.js'; -import { RenderableFace } from '../renderers/Projector.js'; -import { RenderableLine } from '../renderers/Projector.js'; -import { RenderableSprite } from '../renderers/Projector.js'; - +import { + Projector, + RenderableFace, + RenderableLine, + RenderableSprite +} from '../renderers/Projector.js'; + +/** + * Can be used to wrap SVG elements into a 3D object. + * + * @augments Object3D + * @three_import import { SVGObject } from 'three/addons/renderers/SVGRenderer.js'; + */ class SVGObject extends Object3D { + /** + * Constructs a new SVG object. + * + * @param {SVGElement} node - The SVG element. + */ constructor( node ) { super(); + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ this.isSVGObject = true; + /** + * This SVG element. + * + * @type {SVGElement} + */ this.node = node; } } +/** + * This renderer an be used to render geometric data using SVG. The produced vector + * graphics are particular useful in the following use cases: + * + * - Animated logos or icons. + * - Interactive 2D/3D diagrams or graphs. + * - Interactive maps. + * - Complex or animated user interfaces. + * + * `SVGRenderer` has various advantages. It produces crystal-clear and sharp output which + * is independent of the actual viewport resolution.SVG elements can be styled via CSS. + * And they have good accessibility since it's possible to add metadata like title or description + * (useful for search engines or screen readers). + * + * There are, however, some important limitations: + * - No advanced shading. + * - No texture support. + * - No shadow support. + * + * @three_import import { SVGRenderer } from 'three/addons/renderers/SVGRenderer.js'; + */ class SVGRenderer { + /** + * Constructs a new SVG renderer. + */ constructor() { let _renderData, _elements, _lights, @@ -67,14 +117,60 @@ class SVGRenderer { _projector = new Projector(), _svg = document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' ); + /** + * The DOM where the renderer appends its child-elements. + * + * @type {DOMElement} + */ this.domElement = _svg; + /** + * Whether to automatically perform a clear before a render call or not. + * + * @type {boolean} + * @default true + */ this.autoClear = true; + + /** + * Whether to sort 3D objects or not. + * + * @type {boolean} + * @default true + */ this.sortObjects = true; + + /** + * Whether to sort elements or not. + * + * @type {boolean} + * @default true + */ this.sortElements = true; + /** + * Number of fractional pixels to enlarge polygons in order to + * prevent anti-aliasing gaps. Range is `[0,1]`. + * + * @type {number} + * @default 0.5 + */ this.overdraw = 0.5; + /** + * The output color space. + * + * @type {(SRGBColorSpace|LinearSRGBColorSpace)} + * @default SRGBColorSpace + */ + this.outputColorSpace = SRGBColorSpace; + + /** + * Provides information about the number of + * rendered vertices and faces. + * + * @type {Object} + */ this.info = { render: { @@ -86,6 +182,12 @@ class SVGRenderer { }; + /** + * Sets the render quality. Setting to `high` means This value indicates that the browser + * tries to improve the SVG quality over rendering speed and geometric precision. + * + * @param {('low'|'high')} quality - The quality. + */ this.setQuality = function ( quality ) { switch ( quality ) { @@ -97,6 +199,11 @@ class SVGRenderer { }; + /** + * Sets the clear color. + * + * @param {(number|Color|string)} color - The clear color to set. + */ this.setClearColor = function ( color ) { _clearColor.set( color ); @@ -105,6 +212,12 @@ class SVGRenderer { this.setPixelRatio = function () {}; + /** + * Resizes the renderer to the given width and height. + * + * @param {number} width - The width of the renderer. + * @param {number} height - The height of the renderer. + */ this.setSize = function ( width, height ) { _svgWidth = width; _svgHeight = height; @@ -119,6 +232,11 @@ class SVGRenderer { }; + /** + * Returns an object containing the width and height of the renderer. + * + * @return {{width:number,height:number}} The size of the renderer. + */ this.getSize = function () { return { @@ -128,6 +246,11 @@ class SVGRenderer { }; + /** + * Sets the precision of the data used to create a paths. + * + * @param {number} precision - The precision to set. + */ this.setPrecision = function ( precision ) { _precision = precision; @@ -152,13 +275,22 @@ class SVGRenderer { } + /** + * Performs a manual clear with the defined clear color. + */ this.clear = function () { removeChildNodes(); - _svg.style.backgroundColor = _clearColor.getStyle(); + _svg.style.backgroundColor = _clearColor.getStyle( _this.outputColorSpace ); }; + /** + * Renders the given scene using the given camera. + * + * @param {Object3D} scene - A scene or any other type of 3D object. + * @param {Camera} camera - The camera. + */ this.render = function ( scene, camera ) { if ( camera instanceof Camera === false ) { @@ -173,7 +305,7 @@ class SVGRenderer { if ( background && background.isColor ) { removeChildNodes(); - _svg.style.backgroundColor = background.getStyle(); + _svg.style.backgroundColor = background.getStyle( _this.outputColorSpace ); } else if ( this.autoClear === true ) { @@ -389,7 +521,7 @@ class SVGRenderer { if ( material.isSpriteMaterial || material.isPointsMaterial ) { - style = 'fill:' + material.color.getStyle() + ';fill-opacity:' + material.opacity; + style = 'fill:' + material.color.getStyle( _this.outputColorSpace ) + ';fill-opacity:' + material.opacity; } @@ -403,7 +535,7 @@ class SVGRenderer { if ( material.isLineBasicMaterial ) { - let style = 'fill:none;stroke:' + material.color.getStyle() + ';stroke-opacity:' + material.opacity + ';stroke-width:' + material.linewidth + ';stroke-linecap:' + material.linecap; + let style = 'fill:none;stroke:' + material.color.getStyle( _this.outputColorSpace ) + ';stroke-opacity:' + material.opacity + ';stroke-width:' + material.linewidth + ';stroke-linecap:' + material.linecap; if ( material.isLineDashedMaterial ) { @@ -463,11 +595,11 @@ class SVGRenderer { if ( material.wireframe ) { - style = 'fill:none;stroke:' + _color.getStyle() + ';stroke-opacity:' + material.opacity + ';stroke-width:' + material.wireframeLinewidth + ';stroke-linecap:' + material.wireframeLinecap + ';stroke-linejoin:' + material.wireframeLinejoin; + style = 'fill:none;stroke:' + _color.getStyle( _this.outputColorSpace ) + ';stroke-opacity:' + material.opacity + ';stroke-width:' + material.wireframeLinewidth + ';stroke-linecap:' + material.wireframeLinecap + ';stroke-linejoin:' + material.wireframeLinejoin; } else { - style = 'fill:' + _color.getStyle() + ';fill-opacity:' + material.opacity; + style = 'fill:' + _color.getStyle( _this.outputColorSpace ) + ';fill-opacity:' + material.opacity; } diff --git a/examples/jsm/renderers/webgl/nodes/SlotNode.js b/examples/jsm/renderers/webgl/nodes/SlotNode.js deleted file mode 100644 index 497385cd6b034a..00000000000000 --- a/examples/jsm/renderers/webgl/nodes/SlotNode.js +++ /dev/null @@ -1,26 +0,0 @@ -import { Node } from 'three/nodes'; - -class SlotNode extends Node { - - constructor( params ) { - - super( params.nodeType ); - - this.node = null; - this.source = null; - this.target = null; - this.inclusionType = 'replace'; - - Object.assign( this, params ); - - } - - generate( builder ) { - - return this.node.build( builder, this.getNodeType( builder ) ); - - } - -} - -export default SlotNode; diff --git a/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js b/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js deleted file mode 100644 index c964db64284c80..00000000000000 --- a/examples/jsm/renderers/webgl/nodes/WebGLNodeBuilder.js +++ /dev/null @@ -1,760 +0,0 @@ -import { defaultShaderStages, NodeFrame, MathNode, GLSLNodeParser, NodeBuilder } from 'three/nodes'; -import SlotNode from './SlotNode.js'; -import { PerspectiveCamera, ShaderChunk, ShaderLib, UniformsUtils, UniformsLib } from 'three'; - -const nodeFrame = new NodeFrame(); -nodeFrame.camera = new PerspectiveCamera(); - -const nodeShaderLib = { - LineBasicNodeMaterial: ShaderLib.basic, - MeshBasicNodeMaterial: ShaderLib.basic, - PointsNodeMaterial: ShaderLib.points, - MeshStandardNodeMaterial: ShaderLib.standard, - MeshPhysicalNodeMaterial: ShaderLib.physical -}; - -const glslMethods = { - [ MathNode.ATAN2 ]: 'atan' -}; - -const precisionLib = { - low: 'lowp', - medium: 'mediump', - high: 'highp' -}; - -function getIncludeSnippet( name ) { - - return `#include <${name}>`; - -} - -function getShaderStageProperty( shaderStage ) { - - return `${shaderStage}Shader`; - -} - -class WebGLNodeBuilder extends NodeBuilder { - - constructor( object, renderer, shader ) { - - super( object, renderer, new GLSLNodeParser() ); - - this.shader = shader; - this.slots = { vertex: [], fragment: [] }; - - this._parseShaderLib(); - this._parseInclude( 'fragment', 'lights_physical_fragment', 'clearcoat_normal_fragment_begin', 'transmission_fragment' ); - this._parseObject(); - - this._sortSlotsToFlow(); - - } - - getMethod( method ) { - - return glslMethods[ method ] || method; - - } - - addSlot( shaderStage, slotNode ) { - - this.slots[ shaderStage ].push( slotNode ); - - } - - _parseShaderLib() { - - const material = this.material; - - let type = material.type; - - // see https://github.com/mrdoob/three.js/issues/23707 - - if ( material.isMeshPhysicalNodeMaterial ) type = 'MeshPhysicalNodeMaterial'; - else if ( material.isMeshStandardNodeMaterial ) type = 'MeshStandardNodeMaterial'; - else if ( material.isMeshBasicNodeMaterial ) type = 'MeshBasicNodeMaterial'; - else if ( material.isPointsNodeMaterial ) type = 'PointsNodeMaterial'; - else if ( material.isLineBasicNodeMaterial ) type = 'LineBasicNodeMaterial'; - - // shader lib - - if ( nodeShaderLib[ type ] !== undefined ) { - - const shaderLib = nodeShaderLib[ type ]; - const shader = this.shader; - - shader.vertexShader = shaderLib.vertexShader; - shader.fragmentShader = shaderLib.fragmentShader; - shader.uniforms = UniformsUtils.merge( [ shaderLib.uniforms, UniformsLib.lights ] ); - - } - - } - - _parseObject() { - - const { material, renderer } = this; - - if ( renderer.toneMappingNode && renderer.toneMappingNode.isNode === true ) { - - this.addSlot( 'fragment', new SlotNode( { - node: material.colorNode, - nodeType: 'vec4', - source: getIncludeSnippet( 'tonemapping_fragment' ), - target: '' - } ) ); - - } - - // parse inputs - - if ( material.colorNode && material.colorNode.isNode ) { - - this.addSlot( 'fragment', new SlotNode( { - node: material.colorNode, - nodeType: 'vec4', - source: 'vec4 diffuseColor = vec4( diffuse, opacity );', - target: 'vec4 diffuseColor = %RESULT%;' - } ) ); - - } - - if ( material.opacityNode && material.opacityNode.isNode ) { - - this.addSlot( 'fragment', new SlotNode( { - node: material.opacityNode, - nodeType: 'float', - source: getIncludeSnippet( 'alphatest_fragment' ), - target: 'diffuseColor.a = %RESULT%;', - inclusionType: 'append' - } ) ); - - } else { - - this.addCode( 'fragment', getIncludeSnippet( 'alphatest_fragment' ), 'diffuseColor.a = opacity;', this.shader ); - - } - - if ( material.normalNode && material.normalNode.isNode ) { - - this.addSlot( 'fragment', new SlotNode( { - node: material.normalNode, - nodeType: 'vec3', - source: getIncludeSnippet( 'normal_fragment_begin' ), - target: 'normal = %RESULT%;', - inclusionType: 'append' - } ) ); - - } - - if ( material.emissiveNode && material.emissiveNode.isNode ) { - - this.addSlot( 'fragment', new SlotNode( { - node: material.emissiveNode, - nodeType: 'vec3', - source: getIncludeSnippet( 'emissivemap_fragment' ), - target: 'totalEmissiveRadiance = %RESULT%;', - inclusionType: 'append' - } ) ); - - } - - if ( material.isMeshStandardNodeMaterial ) { - - if ( material.metalnessNode && material.metalnessNode.isNode ) { - - this.addSlot( 'fragment', new SlotNode( { - node: material.metalnessNode, - nodeType: 'float', - source: getIncludeSnippet( 'metalnessmap_fragment' ), - target: 'metalnessFactor = %RESULT%;', - inclusionType: 'append' - } ) ); - - } - - if ( material.roughnessNode && material.roughnessNode.isNode ) { - - this.addSlot( 'fragment', new SlotNode( { - node: material.roughnessNode, - nodeType: 'float', - source: getIncludeSnippet( 'roughnessmap_fragment' ), - target: 'roughnessFactor = %RESULT%;', - inclusionType: 'append' - } ) ); - - } - - if ( material.isMeshPhysicalNodeMaterial ) { - - if ( material.clearcoatNode && material.clearcoatNode.isNode ) { - - this.addSlot( 'fragment', new SlotNode( { - node: material.clearcoatNode, - nodeType: 'float', - source: 'material.clearcoat = clearcoat;', - target: 'material.clearcoat = %RESULT%;' - } ) ); - - if ( material.clearcoatRoughnessNode && material.clearcoatRoughnessNode.isNode ) { - - this.addSlot( 'fragment', new SlotNode( { - node: material.clearcoatRoughnessNode, - nodeType: 'float', - source: 'material.clearcoatRoughness = clearcoatRoughness;', - target: 'material.clearcoatRoughness = %RESULT%;' - } ) ); - - } - - if ( material.clearcoatNormalNode && material.clearcoatNormalNode.isNode ) { - - this.addSlot( 'fragment', new SlotNode( { - node: material.clearcoatNormalNode, - nodeType: 'vec3', - source: 'vec3 clearcoatNormal = geometryNormal;', - target: 'vec3 clearcoatNormal = %RESULT%;' - } ) ); - - } - - material.defines.USE_CLEARCOAT = ''; - - } else { - - delete material.defines.USE_CLEARCOAT; - - } - - if ( material.sheenNode && material.sheenNode.isNode ) { - - this.addSlot( 'fragment', new SlotNode( { - node: material.sheenNode, - nodeType: 'vec3', - source: 'material.sheenColor = sheenColor;', - target: 'material.sheenColor = %RESULT%;' - } ) ); - - if ( material.sheenRoughnessNode && material.sheenRoughnessNode.isNode ) { - - this.addSlot( 'fragment', new SlotNode( { - node: material.sheenRoughnessNode, - nodeType: 'float', - source: 'material.sheenRoughness = clamp( sheenRoughness, 0.07, 1.0 );', - target: 'material.sheenRoughness = clamp( %RESULT%, 0.07, 1.0 );' - } ) ); - - } - - material.defines.USE_SHEEN = ''; - - } else { - - delete material.defines.USE_SHEEN; - - } - - if ( material.iridescenceNode && material.iridescenceNode.isNode ) { - - this.addSlot( 'fragment', new SlotNode( { - node: material.iridescenceNode, - nodeType: 'float', - source: 'material.iridescence = iridescence;', - target: 'material.iridescence = %RESULT%;' - } ) ); - - if ( material.iridescenceIORNode && material.iridescenceIORNode.isNode ) { - - this.addSlot( 'fragment', new SlotNode( { - node: material.iridescenceIORNode, - nodeType: 'float', - source: 'material.iridescenceIOR = iridescenceIOR;', - target: 'material.iridescenceIOR = %RESULT%;' - } ) ); - - } - - if ( material.iridescenceThicknessNode && material.iridescenceThicknessNode.isNode ) { - - this.addSlot( 'fragment', new SlotNode( { - node: material.iridescenceThicknessNode, - nodeType: 'float', - source: 'material.iridescenceThickness = iridescenceThicknessMaximum;', - target: 'material.iridescenceThickness = %RESULT%;' - } ) ); - - } - - material.defines.USE_IRIDESCENCE = ''; - - } else { - - delete material.defines.USE_IRIDESCENCE; - - } - - if ( material.iorNode && material.iorNode.isNode ) { - - this.addSlot( 'fragment', new SlotNode( { - node: material.iorNode, - nodeType: 'float', - source: 'material.ior = ior;', - target: 'material.ior = %RESULT%;' - } ) ); - - } - - if ( material.specularColorNode && material.specularColorNode.isNode ) { - - this.addSlot( 'fragment', new SlotNode( { - node: material.specularColorNode, - nodeType: 'vec3', - source: 'vec3 specularColorFactor = specularColor;', - target: 'vec3 specularColorFactor = %RESULT%;' - } ) ); - - } - - if ( material.specularIntensityNode && material.specularIntensityNode.isNode ) { - - this.addSlot( 'fragment', new SlotNode( { - node: material.specularIntensityNode, - nodeType: 'float', - source: 'float specularIntensityFactor = specularIntensity;', - target: 'float specularIntensityFactor = %RESULT%;' - } ) ); - - } - - if ( material.transmissionNode && material.transmissionNode.isNode ) { - - this.addSlot( 'fragment', new SlotNode( { - node: material.transmissionNode, - nodeType: 'float', - source: 'material.transmission = transmission;', - target: 'material.transmission = %RESULT%;' - } ) ); - - if ( material.thicknessNode && material.thicknessNode.isNode ) { - - this.addSlot( 'fragment', new SlotNode( { - node: material.thicknessNode, - nodeType: 'float', - source: 'material.thickness = thickness;', - target: 'material.thickness = %RESULT%;' - } ) ); - - } - - if ( material.thicknessNode && material.thicknessNode.isNode ) { - - this.addSlot( 'fragment', new SlotNode( { - node: material.thicknessNode, - nodeType: 'float', - source: 'material.thickness = thickness;', - target: 'material.thickness = %RESULT%;' - } ) ); - - } - - if ( material.attenuationDistanceNode && material.attenuationDistanceNode.isNode ) { - - this.addSlot( 'fragment', new SlotNode( { - node: material.attenuationDistanceNode, - nodeType: 'float', - source: 'material.attenuationDistance = attenuationDistance;', - target: 'material.attenuationDistance = %RESULT%;' - } ) ); - - } - - if ( material.attenuationColorNode && material.attenuationColorNode.isNode ) { - - this.addSlot( 'fragment', new SlotNode( { - node: material.attenuationColorNode, - nodeType: 'vec3', - source: 'material.attenuationColor = attenuationColor;', - target: 'material.attenuationColor = %RESULT%;' - } ) ); - - } - - material.transmission = 1; - material.defines.USE_TRANSMISSION = ''; - - } else { - - material.transmission = 0; - delete material.defines.USE_TRANSMISSION; - - } - - } - - } - - // - - if ( material.positionNode && material.positionNode.isNode ) { - - this.addSlot( 'vertex', new SlotNode( { - node: material.positionNode, - nodeType: 'vec3', - source: getIncludeSnippet( 'begin_vertex' ), - target: 'transformed = %RESULT%;', - inclusionType: 'append' - } ) ); - - } - - if ( material.sizeNode && material.sizeNode.isNode ) { - - this.addSlot( 'vertex', new SlotNode( { - node: material.sizeNode, - nodeType: 'float', - source: 'gl_PointSize = size;', - target: 'gl_PointSize = %RESULT%;' - } ) ); - - } - - } - - getTexture( texture, textureProperty, uvSnippet ) { - - if ( texture.isTextureCube ) { - - return `textureCube( ${textureProperty}, ${uvSnippet} )`; - - } else { - - return `texture2D( ${textureProperty}, ${uvSnippet} )`; - - } - - } - - getTextureBias( texture, textureProperty, uvSnippet, biasSnippet ) { - - if ( this.material.extensions !== undefined ) this.material.extensions.shaderTextureLOD = true; - - return `textureLod( ${textureProperty}, ${uvSnippet}, ${biasSnippet} )`; - - } - - getUniforms( shaderStage ) { - - const uniforms = this.uniforms[ shaderStage ]; - - let output = ''; - - for ( const uniform of uniforms ) { - - let snippet = null; - - if ( uniform.type === 'texture' ) { - - snippet = `sampler2D ${uniform.name}; `; - - } else if ( uniform.type === 'cubeTexture' ) { - - snippet = `samplerCube ${uniform.name}; `; - - } else { - - const vectorType = this.getVectorType( uniform.type ); - - snippet = `${vectorType} ${uniform.name}; `; - - } - - const precision = uniform.node.precision; - - if ( precision !== null ) { - - snippet = 'uniform ' + precisionLib[ precision ] + ' ' + snippet; - - } else { - - snippet = 'uniform ' + snippet; - - } - - output += snippet; - - } - - return output; - - } - - getAttributes( shaderStage ) { - - let snippet = ''; - - if ( shaderStage === 'vertex' ) { - - const attributes = this.attributes; - - for ( const attribute of attributes ) { - - // ignore common attributes to prevent redefinitions - if ( attribute.name === 'uv' || attribute.name === 'position' || attribute.name === 'normal' ) - continue; - - snippet += `attribute ${attribute.type} ${attribute.name}; `; - - } - - } - - return snippet; - - } - - getVaryings( shaderStage ) { - - let snippet = ''; - - const varyings = this.varyings; - - if ( shaderStage === 'vertex' ) { - - for ( const varying of varyings ) { - - snippet += `${varying.needsInterpolation ? 'varying' : '/*varying*/'} ${varying.type} ${varying.name}; `; - - } - - } else if ( shaderStage === 'fragment' ) { - - for ( const varying of varyings ) { - - if ( varying.needsInterpolation ) { - - snippet += `varying ${varying.type} ${varying.name}; `; - - } - - } - - } - - return snippet; - - } - - addCode( shaderStage, source, code, scope = this ) { - - const shaderProperty = getShaderStageProperty( shaderStage ); - - let snippet = scope[ shaderProperty ]; - - const index = snippet.indexOf( source ); - - if ( index !== - 1 ) { - - const start = snippet.substring( 0, index + source.length ); - const end = snippet.substring( index + source.length ); - - snippet = `${start}\n${code}\n${end}`; - - } - - scope[ shaderProperty ] = snippet; - - } - - replaceCode( shaderStage, source, target, scope = this ) { - - const shaderProperty = getShaderStageProperty( shaderStage ); - - scope[ shaderProperty ] = scope[ shaderProperty ].replaceAll( source, target ); - - } - - getFrontFacing() { - - return 'gl_FrontFacing'; - - } - - getFragCoord() { - - return 'gl_FragCoord'; - - } - - isFlipY() { - - return true; - - } - - buildCode() { - - const shaderData = {}; - - for ( const shaderStage of defaultShaderStages ) { - - const uniforms = this.getUniforms( shaderStage ); - const attributes = this.getAttributes( shaderStage ); - const varyings = this.getVaryings( shaderStage ); - const vars = this.getVars( shaderStage ); - const codes = this.getCodes( shaderStage ); - - shaderData[ shaderStage ] = `${this.getSignature()} -// - -// uniforms -${uniforms} - -// attributes -${attributes} - -// varyings -${varyings} - -// vars -${vars} - -// codes -${codes} - -// - -${this.shader[ getShaderStageProperty( shaderStage ) ]} -`; - - } - - this.vertexShader = shaderData.vertex; - this.fragmentShader = shaderData.fragment; - - } - - build() { - - super.build(); - - this._addSnippets(); - this._addUniforms(); - - this._updateUniforms(); - - this.shader.vertexShader = this.vertexShader; - this.shader.fragmentShader = this.fragmentShader; - - return this; - - } - - _parseInclude( shaderStage, ...includes ) { - - for ( const name of includes ) { - - const includeSnippet = getIncludeSnippet( name ); - const code = ShaderChunk[ name ]; - - const shaderProperty = getShaderStageProperty( shaderStage ); - - this.shader[ shaderProperty ] = this.shader[ shaderProperty ].replaceAll( includeSnippet, code ); - - } - - } - - _sortSlotsToFlow() { - - for ( const shaderStage of defaultShaderStages ) { - - const sourceCode = this.shader[ getShaderStageProperty( shaderStage ) ]; - - const slots = this.slots[ shaderStage ].sort( ( slotA, slotB ) => { - - return sourceCode.indexOf( slotA.source ) > sourceCode.indexOf( slotB.source ) ? 1 : - 1; - - } ); - - for ( const slotNode of slots ) { - - this.addFlow( shaderStage, slotNode ); - - } - - } - - } - - _addSnippets() { - - for ( const shaderStage of defaultShaderStages ) { - - for ( const slotNode of this.slots[ shaderStage ] ) { - - const flowData = this.getFlowData( slotNode/*, shaderStage*/ ); - - const inclusionType = slotNode.inclusionType; - const source = slotNode.source; - const target = flowData.code + '\n\t' + slotNode.target.replace( '%RESULT%', flowData.result ); - - if ( inclusionType === 'append' ) { - - this.addCode( shaderStage, source, target ); - - } else if ( inclusionType === 'replace' ) { - - this.replaceCode( shaderStage, source, target ); - - } else { - - console.warn( `Inclusion type "${ inclusionType }" not compatible.` ); - - } - - } - - this.addCode( - shaderStage, - 'main() {', - '\n\t' + this.flowCode[ shaderStage ] - ); - - } - - } - - _addUniforms() { - - for ( const shaderStage of defaultShaderStages ) { - - // uniforms - - for ( const uniform of this.uniforms[ shaderStage ] ) { - - this.shader.uniforms[ uniform.name ] = uniform; - - } - - } - - } - - _updateUniforms() { - - nodeFrame.object = this.object; - nodeFrame.renderer = this.renderer; - - for ( const node of this.updateNodes ) { - - nodeFrame.updateNode( node ); - - } - - } - -} - -export { WebGLNodeBuilder }; diff --git a/examples/jsm/renderers/webgl/nodes/WebGLNodes.js b/examples/jsm/renderers/webgl/nodes/WebGLNodes.js deleted file mode 100644 index 2825b7335103ab..00000000000000 --- a/examples/jsm/renderers/webgl/nodes/WebGLNodes.js +++ /dev/null @@ -1,49 +0,0 @@ -import { WebGLNodeBuilder } from './WebGLNodeBuilder.js'; -import { NodeFrame } from 'three/nodes'; - -import { Material } from 'three'; - -const builders = new WeakMap(); -export const nodeFrame = new NodeFrame(); - -Material.prototype.onBuild = function ( object, parameters, renderer ) { - - if ( object.material.isNodeMaterial === true ) { - - builders.set( this, new WebGLNodeBuilder( object, renderer, parameters ).build() ); - - } - -}; - -Material.prototype.onBeforeRender = function ( renderer, scene, camera, geometry, object ) { - - const nodeBuilder = builders.get( this ); - - if ( nodeBuilder !== undefined ) { - - nodeFrame.material = this; - nodeFrame.camera = camera; - nodeFrame.object = object; - nodeFrame.renderer = renderer; - - const updateNodes = nodeBuilder.updateNodes; - - if ( updateNodes.length > 0 ) { - - // force refresh material uniforms - renderer.state.useProgram( null ); - - //this.uniformsNeedUpdate = true; - - for ( const node of updateNodes ) { - - nodeFrame.updateNode( node ); - - } - - } - - } - -}; diff --git a/examples/jsm/renderers/webgpu/WebGPUAnimation.js b/examples/jsm/renderers/webgpu/WebGPUAnimation.js deleted file mode 100644 index 8603d47304cd1c..00000000000000 --- a/examples/jsm/renderers/webgpu/WebGPUAnimation.js +++ /dev/null @@ -1,58 +0,0 @@ -class WebGPUAnimation { - - constructor() { - - this.nodes = null; - - this.animationLoop = null; - this.requestId = null; - - this.isAnimating = false; - - this.context = self; - - } - - start() { - - if ( this.isAnimating === true || this.animationLoop === null || this.nodes === null ) return; - - this.isAnimating = true; - - const update = ( time, frame ) => { - - this.requestId = self.requestAnimationFrame( update ); - - this.nodes.nodeFrame.update(); - - this.animationLoop( time, frame ); - - }; - - this.requestId = self.requestAnimationFrame( update ); - - } - - stop() { - - self.cancelAnimationFrame( this.requestId ); - - this.isAnimating = false; - - } - - setAnimationLoop( callback ) { - - this.animationLoop = callback; - - } - - setNodes( nodes ) { - - this.nodes = nodes; - - } - -} - -export default WebGPUAnimation; diff --git a/examples/jsm/renderers/webgpu/WebGPUAttributes.js b/examples/jsm/renderers/webgpu/WebGPUAttributes.js deleted file mode 100644 index 881d1f809f528b..00000000000000 --- a/examples/jsm/renderers/webgpu/WebGPUAttributes.js +++ /dev/null @@ -1,199 +0,0 @@ -import { DynamicDrawUsage } from "three"; - -class WebGPUAttributes { - - constructor( device ) { - - this.buffers = new WeakMap(); - this.device = device; - - } - - get( attribute ) { - - const bufferAttribute = this._getBufferAttribute( attribute ); - - return this.buffers.get( bufferAttribute ); - - } - - remove( attribute ) { - - const bufferAttribute = this._getBufferAttribute( attribute ); - - const data = this.buffers.get( bufferAttribute ); - - if ( data ) { - - this._destroyBuffers( data ); - - this.buffers.delete( bufferAttribute ); - - } - - } - - update( attribute, isIndex = false, gpuUsage = null ) { - - const bufferAttribute = this._getBufferAttribute( attribute ); - - let data = this.buffers.get( bufferAttribute ); - - if ( data === undefined ) { - - if ( gpuUsage === null ) { - - gpuUsage = ( isIndex === true ) ? GPUBufferUsage.INDEX : GPUBufferUsage.VERTEX; - - } - - data = this._createBuffer( bufferAttribute, gpuUsage ); - - this.buffers.set( bufferAttribute, data ); - - } else if ( gpuUsage && gpuUsage !== data.usage ) { - - this._destroyBuffers( data ); - - data = this._createBuffer( bufferAttribute, gpuUsage ); - - this.buffers.set( bufferAttribute, data ); - - } else if ( data.version < bufferAttribute.version || bufferAttribute.usage === DynamicDrawUsage ) { - - this._writeBuffer( data.buffer, bufferAttribute ); - - data.version = bufferAttribute.version; - - } - - } - - async getArrayBuffer( attribute ) { - - const data = this.get( attribute ); - const device = this.device; - - const gpuBuffer = data.buffer; - const size = gpuBuffer.size; - - let gpuReadBuffer = data.readBuffer; - let needsUnmap = true; - - if ( gpuReadBuffer === null ) { - - gpuReadBuffer = device.createBuffer( { - label: attribute.name, - size, - usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ - } ); - - needsUnmap = false; - - data.readBuffer = gpuReadBuffer; - - } - - const cmdEncoder = device.createCommandEncoder( {} ); - - cmdEncoder.copyBufferToBuffer( - gpuBuffer, - 0, - gpuReadBuffer, - 0, - size - ); - - if ( needsUnmap ) gpuReadBuffer.unmap(); - - const gpuCommands = cmdEncoder.finish(); - device.queue.submit( [ gpuCommands ] ); - - await gpuReadBuffer.mapAsync( GPUMapMode.READ ); - - const arrayBuffer = gpuReadBuffer.getMappedRange(); - - return arrayBuffer; - - } - - _getBufferAttribute( attribute ) { - - if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; - - return attribute; - - } - - _createBuffer( bufferAttribute, usage ) { - - const array = bufferAttribute.array; - const size = array.byteLength + ( ( 4 - ( array.byteLength % 4 ) ) % 4 ); // ensure 4 byte alignment, see #20441 - - const buffer = this.device.createBuffer( { - label: bufferAttribute.name, - size, - usage: usage | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST, - mappedAtCreation: true - } ); - - new array.constructor( buffer.getMappedRange() ).set( array ); - - buffer.unmap(); - - bufferAttribute.onUploadCallback(); - - return { - version: bufferAttribute.version, - buffer, - readBuffer: null, - usage - }; - - } - - _writeBuffer( buffer, attribute ) { - - const device = this.device; - - const array = attribute.array; - const updateRange = attribute.updateRange; - - if ( updateRange.count === - 1 ) { - - // Not using update ranges - - device.queue.writeBuffer( - buffer, - 0, - array, - 0 - ); - - } else { - - device.queue.writeBuffer( - buffer, - 0, - array, - updateRange.offset * array.BYTES_PER_ELEMENT, - updateRange.count * array.BYTES_PER_ELEMENT - ); - - updateRange.count = - 1; // reset range - - } - - } - - _destroyBuffers( { buffer, readBuffer } ) { - - buffer.destroy(); - - if ( readBuffer !== null ) readBuffer.destroy(); - - } - -} - -export default WebGPUAttributes; diff --git a/examples/jsm/renderers/webgpu/WebGPUBackground.js b/examples/jsm/renderers/webgpu/WebGPUBackground.js deleted file mode 100644 index d1e2fa66c9d7f8..00000000000000 --- a/examples/jsm/renderers/webgpu/WebGPUBackground.js +++ /dev/null @@ -1,182 +0,0 @@ -import { GPULoadOp, GPUStoreOp } from './constants.js'; -import { Color, Mesh, BoxGeometry, BackSide } from 'three'; -import { context, positionWorldDirection, MeshBasicNodeMaterial } from '../../nodes/Nodes.js'; - -let _clearAlpha; -const _clearColor = new Color(); - -class WebGPUBackground { - - constructor( renderer, properties ) { - - this.renderer = renderer; - this.properties = properties; - - this.boxMesh = null; - this.boxMeshNode = null; - - } - - update( scene, renderList, renderState ) { - - const renderer = this.renderer; - const background = ( scene.isScene === true ) ? scene.backgroundNode || this.properties.get( scene ).backgroundNode || scene.background : null; - - let forceClear = false; - - if ( background === null ) { - - // no background settings, use clear color configuration from the renderer - - _clearColor.copy( renderer._clearColor ); - _clearAlpha = renderer._clearAlpha; - - } else if ( background.isColor === true ) { - - // background is an opaque color - - _clearColor.copy( background ); - _clearAlpha = 1; - forceClear = true; - - } else if ( background.isNode === true ) { - - const sceneProperties = this.properties.get( scene ); - const backgroundNode = background; - - _clearColor.copy( renderer._clearColor ); - _clearAlpha = renderer._clearAlpha; - - let boxMesh = this.boxMesh; - - if ( boxMesh === null ) { - - this.boxMeshNode = context( backgroundNode, { - // @TODO: Add Texture2D support using node context - getUVNode: () => positionWorldDirection - } ); - - const nodeMaterial = new MeshBasicNodeMaterial(); - nodeMaterial.colorNode = this.boxMeshNode; - nodeMaterial.side = BackSide; - nodeMaterial.depthTest = false; - nodeMaterial.depthWrite = false; - nodeMaterial.fog = false; - - this.boxMesh = boxMesh = new Mesh( new BoxGeometry( 1, 1, 1 ), nodeMaterial ); - boxMesh.frustumCulled = false; - - boxMesh.onBeforeRender = function ( renderer, scene, camera ) { - - const scale = camera.far; - - this.matrixWorld.makeScale( scale, scale, scale ).copyPosition( camera.matrixWorld ); - - }; - - } - - const backgroundCacheKey = backgroundNode.getCacheKey(); - - if ( sceneProperties.backgroundCacheKey !== backgroundCacheKey ) { - - this.boxMeshNode.node = backgroundNode; - - boxMesh.material.needsUpdate = true; - - sceneProperties.backgroundCacheKey = backgroundCacheKey; - - } - - renderList.unshift( boxMesh, boxMesh.geometry, boxMesh.material, 0, 0, null ); - - } else { - - console.error( 'THREE.WebGPURenderer: Unsupported background configuration.', background ); - - } - - // configure render pass descriptor - - const colorAttachment = renderState.descriptorGPU.colorAttachments[ 0 ]; - const depthStencilAttachment = renderState.descriptorGPU.depthStencilAttachment; - - if ( renderer.autoClear === true || forceClear === true ) { - - if ( renderer.autoClearColor === true ) { - - _clearColor.multiplyScalar( _clearAlpha ); - - colorAttachment.clearValue = { r: _clearColor.r, g: _clearColor.g, b: _clearColor.b, a: _clearAlpha }; - colorAttachment.loadOp = GPULoadOp.Clear; - colorAttachment.storeOp = GPUStoreOp.Store; - - } else { - - colorAttachment.loadOp = GPULoadOp.Load; - colorAttachment.storeOp = GPUStoreOp.Store; - - } - - if ( renderState.depth ) { - - if ( renderer.autoClearDepth === true ) { - - depthStencilAttachment.depthClearValue = renderer._clearDepth; - depthStencilAttachment.depthLoadOp = GPULoadOp.Clear; - depthStencilAttachment.depthStoreOp = GPUStoreOp.Store; - - } else { - - depthStencilAttachment.depthLoadOp = GPULoadOp.Load; - depthStencilAttachment.depthStoreOp = GPUStoreOp.Store; - - } - - } - - if ( renderState.stencil ) { - - if ( renderer.autoClearStencil === true ) { - - depthStencilAttachment.stencilClearValue = renderer._clearStencil; - depthStencilAttachment.stencilLoadOp = GPULoadOp.Clear; - depthStencilAttachment.stencilStoreOp = GPUStoreOp.Store; - - } else { - - depthStencilAttachment.stencilLoadOp = GPULoadOp.Load; - depthStencilAttachment.stencilStoreOp = GPUStoreOp.Store; - - } - - } - - } else { - - colorAttachment.loadOp = GPULoadOp.Load; - colorAttachment.storeOp = GPUStoreOp.Store; - - if ( renderState.depth ) { - - depthStencilAttachment.depthLoadOp = GPULoadOp.Load; - depthStencilAttachment.depthStoreOp = GPUStoreOp.Store; - - } - - if ( renderState.stencil ) { - - depthStencilAttachment.stencilLoadOp = GPULoadOp.Load; - depthStencilAttachment.stencilStoreOp = GPUStoreOp.Store; - - } - - } - - this.forceClear = false; - - } - -} - -export default WebGPUBackground; diff --git a/examples/jsm/renderers/webgpu/WebGPUBinding.js b/examples/jsm/renderers/webgpu/WebGPUBinding.js deleted file mode 100644 index 9dfb302b38159a..00000000000000 --- a/examples/jsm/renderers/webgpu/WebGPUBinding.js +++ /dev/null @@ -1,22 +0,0 @@ -class WebGPUBinding { - - constructor( name = '' ) { - - this.name = name; - this.visibility = null; - - this.type = null; // read-only - - this.isShared = false; - - } - - setVisibility( visibility ) { - - this.visibility = visibility; - - } - -} - -export default WebGPUBinding; diff --git a/examples/jsm/renderers/webgpu/WebGPUBindings.js b/examples/jsm/renderers/webgpu/WebGPUBindings.js deleted file mode 100644 index 07d75b54ef4d15..00000000000000 --- a/examples/jsm/renderers/webgpu/WebGPUBindings.js +++ /dev/null @@ -1,270 +0,0 @@ -class WebGPUBindings { - - constructor( device, info, properties, textures, renderPipelines, computePipelines, attributes, nodes ) { - - this.device = device; - this.info = info; - this.properties = properties; - this.textures = textures; - this.renderPipelines = renderPipelines; - this.computePipelines = computePipelines; - this.attributes = attributes; - this.nodes = nodes; - - this.uniformsData = new WeakMap(); - - this.updateMap = new WeakMap(); - - } - - get( renderObject ) { - - let data = this.uniformsData.get( renderObject ); - - if ( data === undefined ) { - - // each object defines an array of bindings (ubos, textures, samplers etc.) - - const nodeBuilder = this.nodes.get( renderObject ); - const bindings = nodeBuilder.getBindings(); - - // setup (static) binding layout and (dynamic) binding group - - const pipeline = this.renderPipelines.get( renderObject ).pipeline; - - const bindLayout = pipeline.getBindGroupLayout( 0 ); - const bindGroup = this._createBindGroup( bindings, bindLayout ); - - data = { - layout: bindLayout, - group: bindGroup, - bindings: bindings - }; - - this.uniformsData.set( renderObject, data ); - - } - - return data; - - } - - getForCompute( computeNode ) { - - let data = this.uniformsData.get( computeNode ); - - if ( data === undefined ) { - - // each object defines an array of bindings (ubos, textures, samplers etc.) - - const nodeBuilder = this.nodes.getForCompute( computeNode ); - const bindings = nodeBuilder.getBindings(); - - // setup (static) binding layout and (dynamic) binding group - - const pipeline = this.computePipelines.get( computeNode ); - - const bindLayout = pipeline.getBindGroupLayout( 0 ); - const bindGroup = this._createBindGroup( bindings, bindLayout ); - - data = { - layout: bindLayout, - group: bindGroup, - bindings: bindings - }; - - this.uniformsData.set( computeNode, data ); - - } - - return data; - - } - - remove( object ) { - - this.uniformsData.delete( object ); - - } - - update( object ) { - - const textures = this.textures; - - const data = this.get( object ); - const bindings = data.bindings; - - const updateMap = this.updateMap; - const frame = this.info.render.frame; - - let needsBindGroupRefresh = false; - - // iterate over all bindings and check if buffer updates or a new binding group is required - - for ( const binding of bindings ) { - - const isShared = binding.isShared; - const isUpdated = updateMap.get( binding ) === frame; - - if ( isShared && isUpdated ) continue; - - if ( binding.isUniformBuffer ) { - - const buffer = binding.getBuffer(); - const needsBufferWrite = binding.update(); - - if ( needsBufferWrite === true ) { - - const bufferGPU = binding.bufferGPU; - - this.device.queue.writeBuffer( bufferGPU, 0, buffer, 0 ); - - } - - } else if ( binding.isStorageBuffer ) { - - const attribute = binding.attribute; - - this.attributes.update( attribute, false, binding.usage ); - - } else if ( binding.isSampler ) { - - const texture = binding.getTexture(); - - textures.updateSampler( texture ); - - const samplerGPU = textures.getSampler( texture ); - - if ( binding.samplerGPU !== samplerGPU ) { - - binding.samplerGPU = samplerGPU; - needsBindGroupRefresh = true; - - } - - } else if ( binding.isSampledTexture ) { - - const texture = binding.getTexture(); - - const needsTextureRefresh = textures.updateTexture( texture ); - - const textureGPU = textures.getTextureGPU( texture ); - - if ( textureGPU !== undefined && binding.textureGPU !== textureGPU || needsTextureRefresh === true ) { - - binding.textureGPU = textureGPU; - needsBindGroupRefresh = true; - - } - - } - - updateMap.set( binding, frame ); - - } - - if ( needsBindGroupRefresh === true ) { - - data.group = this._createBindGroup( bindings, data.layout ); - - } - - } - - dispose() { - - this.uniformsData = new WeakMap(); - this.updateMap = new WeakMap(); - - } - - _createBindGroup( bindings, layout ) { - - let bindingPoint = 0; - const entries = []; - - for ( const binding of bindings ) { - - if ( binding.isUniformBuffer ) { - - if ( binding.bufferGPU === null ) { - - const byteLength = binding.getByteLength(); - - binding.bufferGPU = this.device.createBuffer( { - label: 'bindingBuffer', - size: byteLength, - usage: binding.usage - } ); - - } - - entries.push( { binding: bindingPoint, resource: { buffer: binding.bufferGPU } } ); - - } else if ( binding.isStorageBuffer ) { - - if ( binding.bufferGPU === null ) { - - const attribute = binding.attribute; - - this.attributes.update( attribute, false, binding.usage ); - binding.bufferGPU = this.attributes.get( attribute ).buffer; - - } - - entries.push( { binding: bindingPoint, resource: { buffer: binding.bufferGPU } } ); - - } else if ( binding.isSampler ) { - - if ( binding.samplerGPU === null ) { - - binding.samplerGPU = this.textures.getDefaultSampler(); - - } - - entries.push( { binding: bindingPoint, resource: binding.samplerGPU } ); - - } else if ( binding.isSampledTexture ) { - - if ( binding.textureGPU === null ) { - - if ( binding.isSampledCubeTexture ) { - - binding.textureGPU = this.textures.getDefaultCubeTexture(); - - } else if ( binding.texture.isVideoTexture ) { - - binding.textureGPU = this.textures.getDefaultVideoTexture(); - - } else if ( binding.texture.isDepthTexture ) { - - binding.textureGPU = this.textures.getDefaultDepthTexture(); - - } else { - - binding.textureGPU = this.textures.getDefaultTexture(); - - } - - } - - const resource = binding.textureGPU instanceof GPUTexture ? binding.textureGPU.createView( { aspect: binding.aspect, dimension: binding.dimension } ) : binding.textureGPU; - - entries.push( { binding: bindingPoint, resource } ); - - } - - bindingPoint ++; - - } - - return this.device.createBindGroup( { - layout, - entries - } ); - - } - -} - -export default WebGPUBindings; diff --git a/examples/jsm/renderers/webgpu/WebGPUBuffer.js b/examples/jsm/renderers/webgpu/WebGPUBuffer.js deleted file mode 100644 index f3eb9bc0df6877..00000000000000 --- a/examples/jsm/renderers/webgpu/WebGPUBuffer.js +++ /dev/null @@ -1,43 +0,0 @@ -import WebGPUBinding from './WebGPUBinding.js'; -import { getFloatLength } from './WebGPUBufferUtils.js'; - -class WebGPUBuffer extends WebGPUBinding { - - constructor( name, type, buffer = null ) { - - super( name ); - - this.isBuffer = true; - - this.bytesPerElement = Float32Array.BYTES_PER_ELEMENT; - this.type = type; - this.visibility = GPUShaderStage.VERTEX; - - this.usage = GPUBufferUsage.COPY_DST; - - this.buffer = buffer; - this.bufferGPU = null; // set by the renderer - - } - - getByteLength() { - - return getFloatLength( this.buffer.byteLength ); - - } - - getBuffer() { - - return this.buffer; - - } - - update() { - - return true; - - } - -} - -export default WebGPUBuffer; diff --git a/examples/jsm/renderers/webgpu/WebGPUBufferUtils.js b/examples/jsm/renderers/webgpu/WebGPUBufferUtils.js deleted file mode 100644 index b1b1bfc2e09fbc..00000000000000 --- a/examples/jsm/renderers/webgpu/WebGPUBufferUtils.js +++ /dev/null @@ -1,33 +0,0 @@ -import { GPUChunkSize } from './constants.js'; - -function getFloatLength( floatLength ) { - - // ensure chunk size alignment (STD140 layout) - - return floatLength + ( ( GPUChunkSize - ( floatLength % GPUChunkSize ) ) % GPUChunkSize ); - -} - -function getVectorLength( count, vectorLength = 4 ) { - - const strideLength = getStrideLength( vectorLength ); - - const floatLength = strideLength * count; - - return getFloatLength( floatLength ); - -} - -function getStrideLength( vectorLength ) { - - const strideLength = 4; - - return vectorLength + ( ( strideLength - ( vectorLength % strideLength ) ) % strideLength ); - -} - -export { - getFloatLength, - getVectorLength, - getStrideLength -}; diff --git a/examples/jsm/renderers/webgpu/WebGPUComputePipelines.js b/examples/jsm/renderers/webgpu/WebGPUComputePipelines.js deleted file mode 100644 index cad813b0a34d15..00000000000000 --- a/examples/jsm/renderers/webgpu/WebGPUComputePipelines.js +++ /dev/null @@ -1,78 +0,0 @@ -import WebGPUProgrammableStage from './WebGPUProgrammableStage.js'; - -class WebGPUComputePipelines { - - constructor( device, nodes ) { - - this.device = device; - this.nodes = nodes; - - this.pipelines = new WeakMap(); - this.stages = { - compute: new WeakMap() - }; - - } - - has( computeNode ) { - - return this.pipelines.get( computeNode ) !== undefined; - - } - - get( computeNode ) { - - let pipeline = this.pipelines.get( computeNode ); - - // @TODO: Reuse compute pipeline if possible, introduce WebGPUComputePipeline - - if ( pipeline === undefined ) { - - const device = this.device; - - // get shader - - const nodeBuilder = this.nodes.getForCompute( computeNode ); - const computeShader = nodeBuilder.computeShader; - - const shader = { - computeShader - }; - - // programmable stage - - let stageCompute = this.stages.compute.get( shader ); - - if ( stageCompute === undefined ) { - - stageCompute = new WebGPUProgrammableStage( device, computeShader, 'compute' ); - - this.stages.compute.set( shader, stageCompute ); - - } - - pipeline = device.createComputePipeline( { - compute: stageCompute.stage, - layout: 'auto' - } ); - - this.pipelines.set( computeNode, pipeline ); - - } - - return pipeline; - - } - - dispose() { - - this.pipelines = new WeakMap(); - this.stages = { - compute: new WeakMap() - }; - - } - -} - -export default WebGPUComputePipelines; diff --git a/examples/jsm/renderers/webgpu/WebGPUGeometries.js b/examples/jsm/renderers/webgpu/WebGPUGeometries.js deleted file mode 100644 index 92193aad90c83d..00000000000000 --- a/examples/jsm/renderers/webgpu/WebGPUGeometries.js +++ /dev/null @@ -1,213 +0,0 @@ -import { Uint32BufferAttribute, Uint16BufferAttribute } from 'three'; - -function arrayNeedsUint32( array ) { - - // assumes larger values usually on last - - for ( let i = array.length - 1; i >= 0; -- i ) { - - if ( array[ i ] >= 65535 ) return true; // account for PRIMITIVE_RESTART_FIXED_INDEX, #24565 - - } - - return false; - -} - -function getWireframeVersion( geometry ) { - - return ( geometry.index !== null ) ? geometry.index.version : geometry.attributes.position.version; - -} - -function getWireframeIndex( geometry ) { - - const indices = []; - - const geometryIndex = geometry.index; - const geometryPosition = geometry.attributes.position; - - if ( geometryIndex !== null ) { - - const array = geometryIndex.array; - - for ( let i = 0, l = array.length; i < l; i += 3 ) { - - const a = array[ i + 0 ]; - const b = array[ i + 1 ]; - const c = array[ i + 2 ]; - - indices.push( a, b, b, c, c, a ); - - } - - } else { - - const array = geometryPosition.array; - - for ( let i = 0, l = ( array.length / 3 ) - 1; i < l; i += 3 ) { - - const a = i + 0; - const b = i + 1; - const c = i + 2; - - indices.push( a, b, b, c, c, a ); - - } - - } - - const attribute = new ( arrayNeedsUint32( indices ) ? Uint32BufferAttribute : Uint16BufferAttribute )( indices, 1 ); - attribute.version = getWireframeVersion( geometry ); - - return attribute; - -} - -class WebGPUGeometries { - - constructor( attributes, properties, info ) { - - this.attributes = attributes; - this.properties = properties; - this.info = info; - - this.wireframes = new WeakMap(); - this.geometryFrame = new WeakMap(); - - } - - has( renderObject ) { - - const geometry = renderObject.geometry; - - return this.properties.has( geometry ) && this.properties.get( geometry ).initialized === true; - - } - - update( renderObject ) { - - if ( this.has( renderObject ) === false ) this.initGeometry( renderObject ); - - this.updateFrameAttributes( renderObject ); - - } - - initGeometry( renderObject ) { - - const geometry = renderObject.geometry; - const geometryProperties = this.properties.get( geometry ); - - geometryProperties.initialized = true; - - const dispose = () => { - - this.info.memory.geometries --; - - const index = geometry.index; - const geometryAttributes = renderObject.getAttributes(); - - if ( index !== null ) { - - this.attributes.remove( index ); - - } - - for ( const geometryAttribute of geometryAttributes ) { - - this.attributes.remove( geometryAttribute ); - - } - - const wireframeAttribute = this.wireframes.get( geometry ); - - if ( wireframeAttribute !== undefined ) { - - this.attributes.remove( wireframeAttribute ); - - } - - geometry.removeEventListener( 'dispose', dispose ); - - }; - - this.info.memory.geometries ++; - - geometry.addEventListener( 'dispose', dispose ); - - } - - updateFrameAttributes( renderObject ) { - - const frame = this.info.render.frame; - const geometry = renderObject.geometry; - - if ( this.geometryFrame.get( geometry ) !== frame ) { - - this.updateAttributes( renderObject ); - - this.geometryFrame.set( geometry, frame ); - - } - - } - - updateAttributes( renderObject ) { - - const attributes = renderObject.getAttributes(); - - for ( const attribute of attributes ) { - - this.attributes.update( attribute ); - - } - - const index = this.getIndex( renderObject ); - - if ( index !== null ) { - - this.attributes.update( index, true ); - - } - - } - - getIndex( renderObject ) { - - const { geometry, material } = renderObject; - - let index = geometry.index; - - if ( material.wireframe === true ) { - - const wireframes = this.wireframes; - - let wireframeAttribute = wireframes.get( geometry ); - - if ( wireframeAttribute === undefined ) { - - wireframeAttribute = getWireframeIndex( geometry ); - - wireframes.set( geometry, wireframeAttribute ); - - } else if ( wireframeAttribute.version !== getWireframeVersion( geometry ) ) { - - this.attributes.remove( wireframeAttribute ); - - wireframeAttribute = getWireframeIndex( geometry ); - - wireframes.set( geometry, wireframeAttribute ); - - } - - index = wireframeAttribute; - - } - - return index; - - } - -} - -export default WebGPUGeometries; diff --git a/examples/jsm/renderers/webgpu/WebGPUInfo.js b/examples/jsm/renderers/webgpu/WebGPUInfo.js deleted file mode 100644 index a62e6d626009ef..00000000000000 --- a/examples/jsm/renderers/webgpu/WebGPUInfo.js +++ /dev/null @@ -1,73 +0,0 @@ -class WebGPUInfo { - - constructor() { - - this.autoReset = true; - - this.render = { - frame: 0, - drawCalls: 0, - triangles: 0, - points: 0, - lines: 0 - }; - - this.memory = { - geometries: 0, - textures: 0 - }; - - } - - update( object, count, instanceCount ) { - - this.render.drawCalls ++; - - if ( object.isMesh || object.isSprite ) { - - this.render.triangles += instanceCount * ( count / 3 ); - - } else if ( object.isPoints ) { - - this.render.points += instanceCount * count; - - } else if ( object.isLineSegments ) { - - this.render.lines += instanceCount * ( count / 2 ); - - } else if ( object.isLine ) { - - this.render.lines += instanceCount * ( count - 1 ); - - } else { - - console.error( 'THREE.WebGPUInfo: Unknown object type.' ); - - } - - } - - reset() { - - this.render.drawCalls = 0; - this.render.triangles = 0; - this.render.points = 0; - this.render.lines = 0; - - } - - dispose() { - - this.reset(); - - this.render.frame = 0; - - this.memory.geometries = 0; - this.memory.textures = 0; - - } - -} - - -export default WebGPUInfo; diff --git a/examples/jsm/renderers/webgpu/WebGPUProgrammableStage.js b/examples/jsm/renderers/webgpu/WebGPUProgrammableStage.js deleted file mode 100644 index 8f281d54aa0203..00000000000000 --- a/examples/jsm/renderers/webgpu/WebGPUProgrammableStage.js +++ /dev/null @@ -1,22 +0,0 @@ -let _id = 0; - -class WebGPUProgrammableStage { - - constructor( device, code, type ) { - - this.id = _id ++; - - this.code = code; - this.type = type; - this.usedTimes = 0; - - this.stage = { - module: device.createShaderModule( { code, label: type } ), - entryPoint: 'main' - }; - - } - -} - -export default WebGPUProgrammableStage; diff --git a/examples/jsm/renderers/webgpu/WebGPUProperties.js b/examples/jsm/renderers/webgpu/WebGPUProperties.js deleted file mode 100644 index f70a2e87ab54d8..00000000000000 --- a/examples/jsm/renderers/webgpu/WebGPUProperties.js +++ /dev/null @@ -1,44 +0,0 @@ -class WebGPUProperties { - - constructor() { - - this.properties = new WeakMap(); - - } - - get( object ) { - - let map = this.properties.get( object ); - - if ( map === undefined ) { - - map = {}; - this.properties.set( object, map ); - - } - - return map; - - } - - remove( object ) { - - this.properties.delete( object ); - - } - - has( object ) { - - return this.properties.has( object ); - - } - - dispose() { - - this.properties = new WeakMap(); - - } - -} - -export default WebGPUProperties; diff --git a/examples/jsm/renderers/webgpu/WebGPURenderLists.js b/examples/jsm/renderers/webgpu/WebGPURenderLists.js deleted file mode 100644 index 79d693f0e8260f..00000000000000 --- a/examples/jsm/renderers/webgpu/WebGPURenderLists.js +++ /dev/null @@ -1,215 +0,0 @@ -import WebGPUWeakMap from './WebGPUWeakMap.js'; -import { lights } from '../../nodes/Nodes.js'; - -function painterSortStable( a, b ) { - - if ( a.groupOrder !== b.groupOrder ) { - - return a.groupOrder - b.groupOrder; - - } else if ( a.renderOrder !== b.renderOrder ) { - - return a.renderOrder - b.renderOrder; - - } else if ( a.material.id !== b.material.id ) { - - return a.material.id - b.material.id; - - } else if ( a.z !== b.z ) { - - return a.z - b.z; - - } else { - - return a.id - b.id; - - } - -} - -function reversePainterSortStable( a, b ) { - - if ( a.groupOrder !== b.groupOrder ) { - - return a.groupOrder - b.groupOrder; - - } else if ( a.renderOrder !== b.renderOrder ) { - - return a.renderOrder - b.renderOrder; - - } else if ( a.z !== b.z ) { - - return b.z - a.z; - - } else { - - return a.id - b.id; - - } - -} - -class WebGPURenderList { - - constructor() { - - this.renderItems = []; - this.renderItemsIndex = 0; - - this.opaque = []; - this.transparent = []; - - this.lightsNode = lights( [] ); - this.lightsArray = []; - - } - - init() { - - this.renderItemsIndex = 0; - - this.opaque.length = 0; - this.transparent.length = 0; - this.lightsArray.length = 0; - - return this; - - } - - getNextRenderItem( object, geometry, material, groupOrder, z, group ) { - - let renderItem = this.renderItems[ this.renderItemsIndex ]; - - if ( renderItem === undefined ) { - - renderItem = { - id: object.id, - object: object, - geometry: geometry, - material: material, - groupOrder: groupOrder, - renderOrder: object.renderOrder, - z: z, - group: group - }; - - this.renderItems[ this.renderItemsIndex ] = renderItem; - - } else { - - renderItem.id = object.id; - renderItem.object = object; - renderItem.geometry = geometry; - renderItem.material = material; - renderItem.groupOrder = groupOrder; - renderItem.renderOrder = object.renderOrder; - renderItem.z = z; - renderItem.group = group; - - } - - this.renderItemsIndex ++; - - return renderItem; - - } - - push( object, geometry, material, groupOrder, z, group ) { - - const renderItem = this.getNextRenderItem( object, geometry, material, groupOrder, z, group ); - - ( material.transparent === true ? this.transparent : this.opaque ).push( renderItem ); - - } - - unshift( object, geometry, material, groupOrder, z, group ) { - - const renderItem = this.getNextRenderItem( object, geometry, material, groupOrder, z, group ); - - ( material.transparent === true ? this.transparent : this.opaque ).unshift( renderItem ); - - } - - pushLight( light ) { - - this.lightsArray.push( light ); - - } - - getLightsNode() { - - return this.lightsNode.fromLights( this.lightsArray ); - - } - - sort( customOpaqueSort, customTransparentSort ) { - - if ( this.opaque.length > 1 ) this.opaque.sort( customOpaqueSort || painterSortStable ); - if ( this.transparent.length > 1 ) this.transparent.sort( customTransparentSort || reversePainterSortStable ); - - } - - finish() { - - // update lights - - this.lightsNode.fromLights( this.lightsArray ); - - // Clear references from inactive renderItems in the list - - for ( let i = this.renderItemsIndex, il = this.renderItems.length; i < il; i ++ ) { - - const renderItem = this.renderItems[ i ]; - - if ( renderItem.id === null ) break; - - renderItem.id = null; - renderItem.object = null; - renderItem.geometry = null; - renderItem.material = null; - renderItem.program = null; - renderItem.group = null; - - } - - } - -} - -class WebGPURenderLists { - - constructor() { - - this.lists = new WeakMap(); - this.lists = new WebGPUWeakMap(); - - } - - get( scene, camera ) { - - const lists = this.lists; - const keys = [ scene, camera ]; - - let list = lists.get( keys ); - - if ( list === undefined ) { - - list = new WebGPURenderList(); - lists.set( keys, list ); - - - } - - return list; - - } - - dispose() { - - this.lists = new WeakMap(); - - } - -} - -export default WebGPURenderLists; diff --git a/examples/jsm/renderers/webgpu/WebGPURenderObject.js b/examples/jsm/renderers/webgpu/WebGPURenderObject.js deleted file mode 100644 index f9ccc86c6763a4..00000000000000 --- a/examples/jsm/renderers/webgpu/WebGPURenderObject.js +++ /dev/null @@ -1,64 +0,0 @@ -export default class WebGPURenderObject { - - constructor( renderer, nodes, object, material, scene, camera, lightsNode ) { - - this.renderer = renderer; - this.nodes = nodes; - - this.object = object; - this.material = material; - this.scene = scene; - this.camera = camera; - this.lightsNode = lightsNode; - - this.geometry = object.geometry; - - this.attributes = null; - - this._materialVersion = - 1; - this._materialCacheKey = ''; - - } - - getAttributes() { - - if ( this.attributes !== null ) return this.attributes; - - const nodeAttributes = this.nodes.get( this ).getAttributesArray(); - const geometry = this.geometry; - - const attributes = []; - - for ( const nodeAttribute of nodeAttributes ) { - - attributes.push( nodeAttribute.node && nodeAttribute.node.attribute ? nodeAttribute.node.attribute : geometry.getAttribute( nodeAttribute.name ) ); - - } - - this.attributes = attributes; - - return attributes; - - } - - getCacheKey() { - - const { material, scene, lightsNode } = this; - - if ( material.version !== this._materialVersion ) { - - this._materialVersion = material.version; - this._materialCacheKey = material.customProgramCacheKey(); - - } - - const cacheKey = []; - - cacheKey.push( 'material:' + this._materialCacheKey ); - cacheKey.push( 'nodes:' + this.nodes.getCacheKey( scene, lightsNode ) ); - - return '{' + cacheKey.join( ',' ) + '}'; - - } - -} diff --git a/examples/jsm/renderers/webgpu/WebGPURenderObjects.js b/examples/jsm/renderers/webgpu/WebGPURenderObjects.js deleted file mode 100644 index c7a59e91f034d7..00000000000000 --- a/examples/jsm/renderers/webgpu/WebGPURenderObjects.js +++ /dev/null @@ -1,50 +0,0 @@ -import WebGPUWeakMap from './WebGPUWeakMap.js'; -import WebGPURenderObject from './WebGPURenderObject.js'; - -class WebGPURenderObjects { - - constructor( renderer, nodes, geometries, info ) { - - this.renderer = renderer; - this.nodes = nodes; - this.geometries = geometries; - this.info = info; - - this.cache = new WebGPUWeakMap(); - - } - - get( object, material, scene, camera, lightsNode ) { - - const chainKey = [ object, material, scene, camera, lightsNode ]; - - let renderObject = this.cache.get( chainKey ); - - if ( renderObject === undefined ) { - - renderObject = new WebGPURenderObject( this.renderer, this.nodes, object, material, scene, camera, lightsNode ); - - this.cache.set( chainKey, renderObject ); - - } - - return renderObject; - - } - - remove( object, material, scene, camera, lightsNode ) { - - this.cache.delete( [ object, material, scene, camera, lightsNode ] ); - - } - - dispose() { - - this.cache = new WebGPUWeakMap(); - this.updateMap = new WeakMap(); - - } - -} - -export default WebGPURenderObjects; diff --git a/examples/jsm/renderers/webgpu/WebGPURenderPipeline.js b/examples/jsm/renderers/webgpu/WebGPURenderPipeline.js deleted file mode 100644 index bd796252a5edc8..00000000000000 --- a/examples/jsm/renderers/webgpu/WebGPURenderPipeline.js +++ /dev/null @@ -1,669 +0,0 @@ -import { GPUIndexFormat, GPUCompareFunction, GPUFrontFace, GPUCullMode, GPUBlendFactor, GPUBlendOperation, BlendColorFactor, OneMinusBlendColorFactor, GPUColorWriteFlags, GPUStencilOperation, GPUInputStepMode } from './constants.js'; -import { - Float16BufferAttribute, - FrontSide, BackSide, DoubleSide, - NeverDepth, AlwaysDepth, LessDepth, LessEqualDepth, EqualDepth, GreaterEqualDepth, GreaterDepth, NotEqualDepth, - NeverStencilFunc, AlwaysStencilFunc, LessStencilFunc, LessEqualStencilFunc, EqualStencilFunc, GreaterEqualStencilFunc, GreaterStencilFunc, NotEqualStencilFunc, - KeepStencilOp, ZeroStencilOp, ReplaceStencilOp, InvertStencilOp, IncrementStencilOp, DecrementStencilOp, IncrementWrapStencilOp, DecrementWrapStencilOp, - NoBlending, NormalBlending, AdditiveBlending, SubtractiveBlending, MultiplyBlending, CustomBlending, - AddEquation, SubtractEquation, ReverseSubtractEquation, MinEquation, MaxEquation, - ZeroFactor, OneFactor, SrcColorFactor, OneMinusSrcColorFactor, SrcAlphaFactor, OneMinusSrcAlphaFactor, DstAlphaFactor, OneMinusDstAlphaFactor, DstColorFactor, OneMinusDstColorFactor, SrcAlphaSaturateFactor -} from 'three'; - -const typedArraysToVertexFormatPrefix = new Map( [ - [ Int8Array, [ 'sint8', 'snorm8' ]], - [ Uint8Array, [ 'uint8', 'unorm8' ]], - [ Int16Array, [ 'sint16', 'snorm16' ]], - [ Uint16Array, [ 'uint16', 'unorm16' ]], - [ Int32Array, [ 'sint32', 'snorm32' ]], - [ Uint32Array, [ 'uint32', 'unorm32' ]], - [ Float32Array, [ 'float32', ]], -] ); - -const typedAttributeToVertexFormatPrefix = new Map( [ - [ Float16BufferAttribute, [ 'float16', ]], -] ); - -const typeArraysToVertexFormatPrefixForItemSize1 = new Map( [ - [ Int32Array, 'sint32' ], - [ Uint32Array, 'uint32' ], - [ Float32Array, 'float32' ] -] ); - -class WebGPURenderPipeline { - - constructor( device, utils ) { - - this.cacheKey = null; - this.shaderAttributes = null; - this.stageVertex = null; - this.stageFragment = null; - this.usedTimes = 0; - - this._device = device; - this._utils = utils; - - } - - init( renderObject, cacheKey, stageVertex, stageFragment ) { - - const { object, material, geometry } = renderObject; - - // determine shader attributes - - const shaderAttributes = this._getShaderAttributes( renderObject ); - - // vertex buffers - - const vertexBuffers = []; - - for ( const attribute of shaderAttributes ) { - - const geometryAttribute = attribute.geometryAttribute; - const stepMode = ( geometryAttribute !== undefined && geometryAttribute.isInstancedBufferAttribute ) ? GPUInputStepMode.Instance : GPUInputStepMode.Vertex; - - vertexBuffers.push( { - arrayStride: attribute.arrayStride, - attributes: [ { shaderLocation: attribute.slot, offset: attribute.offset, format: attribute.format } ], - stepMode: stepMode - } ); - - } - - this.cacheKey = cacheKey; - this.shaderAttributes = shaderAttributes; - this.stageVertex = stageVertex; - this.stageFragment = stageFragment; - - // blending - - let alphaBlend = {}; - let colorBlend = {}; - - if ( material.transparent === true && material.blending !== NoBlending ) { - - alphaBlend = this._getAlphaBlend( material ); - colorBlend = this._getColorBlend( material ); - - } - - // stencil - - let stencilFront = {}; - - if ( material.stencilWrite === true ) { - - stencilFront = { - compare: this._getStencilCompare( material ), - failOp: this._getStencilOperation( material.stencilFail ), - depthFailOp: this._getStencilOperation( material.stencilZFail ), - passOp: this._getStencilOperation( material.stencilZPass ) - }; - - } - - // - - const primitiveState = this._getPrimitiveState( object, geometry, material ); - const colorWriteMask = this._getColorWriteMask( material ); - const depthCompare = this._getDepthCompare( material ); - const colorFormat = this._utils.getCurrentColorFormat(); - const depthStencilFormat = this._utils.getCurrentDepthStencilFormat(); - const sampleCount = this._utils.getSampleCount(); - - this.pipeline = this._device.createRenderPipeline( { - vertex: Object.assign( {}, stageVertex.stage, { buffers: vertexBuffers } ), - fragment: Object.assign( {}, stageFragment.stage, { targets: [ { - format: colorFormat, - blend: { - alpha: alphaBlend, - color: colorBlend - }, - writeMask: colorWriteMask - } ] } ), - primitive: primitiveState, - depthStencil: { - format: depthStencilFormat, - depthWriteEnabled: material.depthWrite, - depthCompare: depthCompare, - stencilFront: stencilFront, - stencilBack: {}, // three.js does not provide an API to configure the back function (gl.stencilFuncSeparate() was never used) - stencilReadMask: material.stencilFuncMask, - stencilWriteMask: material.stencilWriteMask - }, - multisample: { - count: sampleCount - }, - layout: 'auto' - } ); - - } - - _getAlphaBlend( material ) { - - const blending = material.blending; - const premultipliedAlpha = material.premultipliedAlpha; - - let alphaBlend = undefined; - - switch ( blending ) { - - case NormalBlending: - - if ( premultipliedAlpha === false ) { - - alphaBlend = { - srcFactor: GPUBlendFactor.One, - dstFactor: GPUBlendFactor.OneMinusSrcAlpha, - operation: GPUBlendOperation.Add - }; - - } - - break; - - case AdditiveBlending: - - alphaBlend = { - srcFactor: GPUBlendFactor.Zero, - dstFactor: GPUBlendFactor.One, - operation: GPUBlendOperation.Add - }; - - break; - - case SubtractiveBlending: - - if ( premultipliedAlpha === true ) { - - alphaBlend = { - srcFactor: GPUBlendFactor.OneMinusSrcColor, - dstFactor: GPUBlendFactor.OneMinusSrcAlpha, - operation: GPUBlendOperation.Add - }; - - } - - break; - - case MultiplyBlending: - - if ( premultipliedAlpha === true ) { - - alphaBlend = { - srcFactor: GPUBlendFactor.Zero, - dstFactor: GPUBlendFactor.SrcAlpha, - operation: GPUBlendOperation.Add - }; - - } - - break; - - case CustomBlending: - - const blendSrcAlpha = material.blendSrcAlpha; - const blendDstAlpha = material.blendDstAlpha; - const blendEquationAlpha = material.blendEquationAlpha; - - if ( blendSrcAlpha !== null && blendDstAlpha !== null && blendEquationAlpha !== null ) { - - alphaBlend = { - srcFactor: this._getBlendFactor( blendSrcAlpha ), - dstFactor: this._getBlendFactor( blendDstAlpha ), - operation: this._getBlendOperation( blendEquationAlpha ) - }; - - } - - break; - - default: - console.error( 'THREE.WebGPURenderer: Blending not supported.', blending ); - - } - - return alphaBlend; - - } - - _getBlendFactor( blend ) { - - let blendFactor; - - switch ( blend ) { - - case ZeroFactor: - blendFactor = GPUBlendFactor.Zero; - break; - - case OneFactor: - blendFactor = GPUBlendFactor.One; - break; - - case SrcColorFactor: - blendFactor = GPUBlendFactor.SrcColor; - break; - - case OneMinusSrcColorFactor: - blendFactor = GPUBlendFactor.OneMinusSrcColor; - break; - - case SrcAlphaFactor: - blendFactor = GPUBlendFactor.SrcAlpha; - break; - - case OneMinusSrcAlphaFactor: - blendFactor = GPUBlendFactor.OneMinusSrcAlpha; - break; - - case DstColorFactor: - blendFactor = GPUBlendFactor.DstColor; - break; - - case OneMinusDstColorFactor: - blendFactor = GPUBlendFactor.OneMinusDstColor; - break; - - case DstAlphaFactor: - blendFactor = GPUBlendFactor.DstAlpha; - break; - - case OneMinusDstAlphaFactor: - blendFactor = GPUBlendFactor.OneMinusDstAlpha; - break; - - case SrcAlphaSaturateFactor: - blendFactor = GPUBlendFactor.SrcAlphaSaturated; - break; - - case BlendColorFactor: - blendFactor = GPUBlendFactor.BlendColor; - break; - - case OneMinusBlendColorFactor: - blendFactor = GPUBlendFactor.OneMinusBlendColor; - break; - - - default: - console.error( 'THREE.WebGPURenderer: Blend factor not supported.', blend ); - - } - - return blendFactor; - - } - - _getBlendOperation( blendEquation ) { - - let blendOperation; - - switch ( blendEquation ) { - - case AddEquation: - blendOperation = GPUBlendOperation.Add; - break; - - case SubtractEquation: - blendOperation = GPUBlendOperation.Subtract; - break; - - case ReverseSubtractEquation: - blendOperation = GPUBlendOperation.ReverseSubtract; - break; - - case MinEquation: - blendOperation = GPUBlendOperation.Min; - break; - - case MaxEquation: - blendOperation = GPUBlendOperation.Max; - break; - - default: - console.error( 'THREE.WebGPURenderer: Blend equation not supported.', blendEquation ); - - } - - return blendOperation; - - } - - _getColorBlend( material ) { - - const blending = material.blending; - const premultipliedAlpha = material.premultipliedAlpha; - - const colorBlend = { - srcFactor: null, - dstFactor: null, - operation: null - }; - - switch ( blending ) { - - case NormalBlending: - colorBlend.srcFactor = ( premultipliedAlpha === true ) ? GPUBlendFactor.One : GPUBlendFactor.SrcAlpha; - colorBlend.dstFactor = GPUBlendFactor.OneMinusSrcAlpha; - colorBlend.operation = GPUBlendOperation.Add; - break; - - case AdditiveBlending: - colorBlend.srcFactor = ( premultipliedAlpha === true ) ? GPUBlendFactor.One : GPUBlendFactor.SrcAlpha; - colorBlend.dstFactor = GPUBlendFactor.One; - colorBlend.operation = GPUBlendOperation.Add; - break; - - case SubtractiveBlending: - colorBlend.srcFactor = GPUBlendFactor.Zero; - colorBlend.dstFactor = ( premultipliedAlpha === true ) ? GPUBlendFactor.Zero : GPUBlendFactor.OneMinusSrcColor; - colorBlend.operation = GPUBlendOperation.Add; - break; - - case MultiplyBlending: - colorBlend.srcFactor = GPUBlendFactor.Zero; - colorBlend.dstFactor = GPUBlendFactor.SrcColor; - colorBlend.operation = GPUBlendOperation.Add; - break; - - case CustomBlending: - colorBlend.srcFactor = this._getBlendFactor( material.blendSrc ); - colorBlend.dstFactor = this._getBlendFactor( material.blendDst ); - colorBlend.operation = this._getBlendOperation( material.blendEquation ); - break; - - default: - console.error( 'THREE.WebGPURenderer: Blending not supported.', blending ); - - } - - return colorBlend; - - } - - _getColorWriteMask( material ) { - - return ( material.colorWrite === true ) ? GPUColorWriteFlags.All : GPUColorWriteFlags.None; - - } - - _getDepthCompare( material ) { - - let depthCompare; - - if ( material.depthTest === false ) { - - depthCompare = GPUCompareFunction.Always; - - } else { - - const depthFunc = material.depthFunc; - - switch ( depthFunc ) { - - case NeverDepth: - depthCompare = GPUCompareFunction.Never; - break; - - case AlwaysDepth: - depthCompare = GPUCompareFunction.Always; - break; - - case LessDepth: - depthCompare = GPUCompareFunction.Less; - break; - - case LessEqualDepth: - depthCompare = GPUCompareFunction.LessEqual; - break; - - case EqualDepth: - depthCompare = GPUCompareFunction.Equal; - break; - - case GreaterEqualDepth: - depthCompare = GPUCompareFunction.GreaterEqual; - break; - - case GreaterDepth: - depthCompare = GPUCompareFunction.Greater; - break; - - case NotEqualDepth: - depthCompare = GPUCompareFunction.NotEqual; - break; - - default: - console.error( 'THREE.WebGPURenderer: Invalid depth function.', depthFunc ); - - } - - } - - return depthCompare; - - } - - _getPrimitiveState( object, geometry, material ) { - - const descriptor = {}; - - descriptor.topology = this._utils.getPrimitiveTopology( object, material ); - - if ( object.isLine === true && object.isLineSegments !== true ) { - - const count = ( geometry.index ) ? geometry.index.count : geometry.attributes.position.count; - descriptor.stripIndexFormat = ( count > 65535 ) ? GPUIndexFormat.Uint32 : GPUIndexFormat.Uint16; // define data type for primitive restart value - - } - - switch ( material.side ) { - - case FrontSide: - descriptor.frontFace = GPUFrontFace.CW; - descriptor.cullMode = GPUCullMode.Front; - break; - - case BackSide: - descriptor.frontFace = GPUFrontFace.CW; - descriptor.cullMode = GPUCullMode.Back; - break; - - case DoubleSide: - descriptor.frontFace = GPUFrontFace.CW; - descriptor.cullMode = GPUCullMode.None; - break; - - default: - console.error( 'THREE.WebGPURenderer: Unknown Material.side value.', material.side ); - break; - - } - - return descriptor; - - } - - _getStencilCompare( material ) { - - let stencilCompare; - - const stencilFunc = material.stencilFunc; - - switch ( stencilFunc ) { - - case NeverStencilFunc: - stencilCompare = GPUCompareFunction.Never; - break; - - case AlwaysStencilFunc: - stencilCompare = GPUCompareFunction.Always; - break; - - case LessStencilFunc: - stencilCompare = GPUCompareFunction.Less; - break; - - case LessEqualStencilFunc: - stencilCompare = GPUCompareFunction.LessEqual; - break; - - case EqualStencilFunc: - stencilCompare = GPUCompareFunction.Equal; - break; - - case GreaterEqualStencilFunc: - stencilCompare = GPUCompareFunction.GreaterEqual; - break; - - case GreaterStencilFunc: - stencilCompare = GPUCompareFunction.Greater; - break; - - case NotEqualStencilFunc: - stencilCompare = GPUCompareFunction.NotEqual; - break; - - default: - console.error( 'THREE.WebGPURenderer: Invalid stencil function.', stencilFunc ); - - } - - return stencilCompare; - - } - - _getStencilOperation( op ) { - - let stencilOperation; - - switch ( op ) { - - case KeepStencilOp: - stencilOperation = GPUStencilOperation.Keep; - break; - - case ZeroStencilOp: - stencilOperation = GPUStencilOperation.Zero; - break; - - case ReplaceStencilOp: - stencilOperation = GPUStencilOperation.Replace; - break; - - case InvertStencilOp: - stencilOperation = GPUStencilOperation.Invert; - break; - - case IncrementStencilOp: - stencilOperation = GPUStencilOperation.IncrementClamp; - break; - - case DecrementStencilOp: - stencilOperation = GPUStencilOperation.DecrementClamp; - break; - - case IncrementWrapStencilOp: - stencilOperation = GPUStencilOperation.IncrementWrap; - break; - - case DecrementWrapStencilOp: - stencilOperation = GPUStencilOperation.DecrementWrap; - break; - - default: - console.error( 'THREE.WebGPURenderer: Invalid stencil operation.', stencilOperation ); - - } - - return stencilOperation; - - } - - _getVertexFormat( geometryAttribute ) { - - const { itemSize, normalized } = geometryAttribute; - const ArrayType = geometryAttribute.array.constructor; - const AttributeType = geometryAttribute.constructor; - - let format; - - if ( itemSize == 1 ) { - - format = typeArraysToVertexFormatPrefixForItemSize1.get( ArrayType ); - - } else { - - const prefixOptions = typedAttributeToVertexFormatPrefix.get( AttributeType ) || typedArraysToVertexFormatPrefix.get( ArrayType ); - const prefix = prefixOptions[ normalized ? 1 : 0 ]; - - if ( prefix ) { - - const bytesPerUnit = ArrayType.BYTES_PER_ELEMENT * itemSize; - const paddedBytesPerUnit = Math.floor( ( bytesPerUnit + 3 ) / 4 ) * 4; - const paddedItemSize = paddedBytesPerUnit / ArrayType.BYTES_PER_ELEMENT; - - if ( paddedItemSize % 1 ) { - - throw new Error( 'THREE.WebGPURenderer: Bad vertex format item size.' ); - - } - - format = `${prefix}x${paddedItemSize}`; - - } - - } - - if ( ! format ) { - - console.error( 'THREE.WebGPURenderer: Vertex format not supported yet.' ); - - } - - return format; - - } - - _getShaderAttributes( renderObject ) { - - const attributes = renderObject.getAttributes(); - const shaderAttributes = []; - - for ( let slot = 0; slot < attributes.length; slot ++ ) { - - const geometryAttribute = attributes[ slot ]; - const bytesPerElement = geometryAttribute.array.BYTES_PER_ELEMENT; - - const format = this._getVertexFormat( geometryAttribute ); - - let arrayStride = geometryAttribute.itemSize * bytesPerElement; - let offset = 0; - - if ( geometryAttribute.isInterleavedBufferAttribute === true ) { - - // @TODO: It can be optimized for "vertexBuffers" on RenderPipeline - - arrayStride = geometryAttribute.data.stride * bytesPerElement; - offset = geometryAttribute.offset * bytesPerElement; - - } - - shaderAttributes.push( { - geometryAttribute, - arrayStride, - offset, - format, - slot - } ); - - } - - return shaderAttributes; - - } - -} - -export default WebGPURenderPipeline; diff --git a/examples/jsm/renderers/webgpu/WebGPURenderPipelines.js b/examples/jsm/renderers/webgpu/WebGPURenderPipelines.js deleted file mode 100644 index f9bee8df6b380f..00000000000000 --- a/examples/jsm/renderers/webgpu/WebGPURenderPipelines.js +++ /dev/null @@ -1,274 +0,0 @@ -import WebGPURenderPipeline from './WebGPURenderPipeline.js'; -import WebGPUProgrammableStage from './WebGPUProgrammableStage.js'; - -class WebGPURenderPipelines { - - constructor( device, nodes, utils ) { - - this.device = device; - this.nodes = nodes; - this.utils = utils; - - this.bindings = null; - - this.pipelines = []; - this.cache = new WeakMap(); - - this.stages = { - vertex: new Map(), - fragment: new Map() - }; - - } - - get( renderObject ) { - - const device = this.device; - const cache = this._getCache( renderObject ); - - let currentPipeline = cache.currentPipeline; - - if ( this._needsUpdate( renderObject ) ) { - - // release previous cache - - this._releasePipeline( renderObject ); - - // get shader - - const nodeBuilder = this.nodes.get( renderObject ); - - // programmable stages - - let stageVertex = this.stages.vertex.get( nodeBuilder.vertexShader ); - - if ( stageVertex === undefined ) { - - stageVertex = new WebGPUProgrammableStage( device, nodeBuilder.vertexShader, 'vertex' ); - this.stages.vertex.set( nodeBuilder.vertexShader, stageVertex ); - - } - - let stageFragment = this.stages.fragment.get( nodeBuilder.fragmentShader ); - - if ( stageFragment === undefined ) { - - stageFragment = new WebGPUProgrammableStage( device, nodeBuilder.fragmentShader, 'fragment' ); - this.stages.fragment.set( nodeBuilder.fragmentShader, stageFragment ); - - } - - // determine render pipeline - - currentPipeline = this._acquirePipeline( renderObject, stageVertex, stageFragment ); - cache.currentPipeline = currentPipeline; - - // keep track of all used times - - currentPipeline.usedTimes ++; - stageVertex.usedTimes ++; - stageFragment.usedTimes ++; - - } - - return currentPipeline; - - } - - remove( renderObject ) { - - this._releasePipeline( renderObject ); - - } - - dispose() { - - this.pipelines = []; - this.cache = new WeakMap(); - this.shaderModules = { - vertex: new Map(), - fragment: new Map() - }; - - } - - _acquirePipeline( renderObject, stageVertex, stageFragment ) { - - let pipeline; - const pipelines = this.pipelines; - - // check for existing pipeline - - const cacheKey = this._computeCacheKey( renderObject, stageVertex, stageFragment ); - - for ( let i = 0, il = pipelines.length; i < il; i ++ ) { - - const preexistingPipeline = pipelines[ i ]; - - if ( preexistingPipeline.cacheKey === cacheKey ) { - - pipeline = preexistingPipeline; - break; - - } - - } - - if ( pipeline === undefined ) { - - pipeline = new WebGPURenderPipeline( this.device, this.utils ); - pipeline.init( renderObject, cacheKey, stageVertex, stageFragment ); - - pipelines.push( pipeline ); - - } - - return pipeline; - - } - - _computeCacheKey( renderObject, stageVertex, stageFragment ) { - - const { object, material } = renderObject; - const utils = this.utils; - - const parameters = [ - stageVertex.id, stageFragment.id, - material.transparent, material.blending, material.premultipliedAlpha, - material.blendSrc, material.blendDst, material.blendEquation, - material.blendSrcAlpha, material.blendDstAlpha, material.blendEquationAlpha, - material.colorWrite, - material.depthWrite, material.depthTest, material.depthFunc, - material.stencilWrite, material.stencilFunc, - material.stencilFail, material.stencilZFail, material.stencilZPass, - material.stencilFuncMask, material.stencilWriteMask, - material.side, - utils.getSampleCount(), - utils.getCurrentColorSpace(), utils.getCurrentColorFormat(), utils.getCurrentDepthStencilFormat(), - utils.getPrimitiveTopology( object, material ) - ]; - - return parameters.join(); - - } - - _getCache( renderObject ) { - - let cache = this.cache.get( renderObject ); - - if ( cache === undefined ) { - - cache = {}; - this.cache.set( renderObject, cache ); - - } - - return cache; - - } - - _releasePipeline( renderObject ) { - - const cache = this._getCache( renderObject ); - - const pipeline = cache.currentPipeline; - delete cache.currentPipeline; - - this.bindings.remove( renderObject ); - - if ( pipeline && -- pipeline.usedTimes === 0 ) { - - const pipelines = this.pipelines; - - const i = pipelines.indexOf( pipeline ); - pipelines[ i ] = pipelines[ pipelines.length - 1 ]; - pipelines.pop(); - - this._releaseStage( pipeline.stageVertex ); - this._releaseStage( pipeline.stageFragment ); - - } - - } - - _releaseStage( stage ) { - - if ( -- stage.usedTimes === 0 ) { - - const code = stage.code; - const type = stage.type; - - this.stages[ type ].delete( code ); - - } - - } - - _needsUpdate( renderObject ) { - - const cache = this._getCache( renderObject ); - const material = renderObject.material; - - let needsUpdate = false; - - // check pipeline state - - if ( cache.currentPipeline === undefined ) needsUpdate = true; - - // check material state - - if ( cache.material !== material || cache.materialVersion !== material.version || - cache.transparent !== material.transparent || cache.blending !== material.blending || cache.premultipliedAlpha !== material.premultipliedAlpha || - cache.blendSrc !== material.blendSrc || cache.blendDst !== material.blendDst || cache.blendEquation !== material.blendEquation || - cache.blendSrcAlpha !== material.blendSrcAlpha || cache.blendDstAlpha !== material.blendDstAlpha || cache.blendEquationAlpha !== material.blendEquationAlpha || - cache.colorWrite !== material.colorWrite || - cache.depthWrite !== material.depthWrite || cache.depthTest !== material.depthTest || cache.depthFunc !== material.depthFunc || - cache.stencilWrite !== material.stencilWrite || cache.stencilFunc !== material.stencilFunc || - cache.stencilFail !== material.stencilFail || cache.stencilZFail !== material.stencilZFail || cache.stencilZPass !== material.stencilZPass || - cache.stencilFuncMask !== material.stencilFuncMask || cache.stencilWriteMask !== material.stencilWriteMask || - cache.side !== material.side - ) { - - cache.material = material; cache.materialVersion = material.version; - cache.transparent = material.transparent; cache.blending = material.blending; cache.premultipliedAlpha = material.premultipliedAlpha; - cache.blendSrc = material.blendSrc; cache.blendDst = material.blendDst; cache.blendEquation = material.blendEquation; - cache.blendSrcAlpha = material.blendSrcAlpha; cache.blendDstAlpha = material.blendDstAlpha; cache.blendEquationAlpha = material.blendEquationAlpha; - cache.colorWrite = material.colorWrite; - cache.depthWrite = material.depthWrite; cache.depthTest = material.depthTest; cache.depthFunc = material.depthFunc; - cache.stencilWrite = material.stencilWrite; cache.stencilFunc = material.stencilFunc; - cache.stencilFail = material.stencilFail; cache.stencilZFail = material.stencilZFail; cache.stencilZPass = material.stencilZPass; - cache.stencilFuncMask = material.stencilFuncMask; cache.stencilWriteMask = material.stencilWriteMask; - cache.side = material.side; - - needsUpdate = true; - - } - - // check renderer state - - const utils = this.utils; - - const sampleCount = utils.getSampleCount(); - const colorSpace = utils.getCurrentColorSpace(); - const colorFormat = utils.getCurrentColorFormat(); - const depthStencilFormat = utils.getCurrentDepthStencilFormat(); - - if ( cache.sampleCount !== sampleCount || cache.colorSpace !== colorSpace || - cache.colorFormat !== colorFormat || cache.depthStencilFormat !== depthStencilFormat ) { - - cache.sampleCount = sampleCount; - cache.colorSpace = colorSpace; - cache.colorFormat = colorFormat; - cache.depthStencilFormat = depthStencilFormat; - - needsUpdate = true; - - } - - return needsUpdate; - - } - -} - -export default WebGPURenderPipelines; diff --git a/examples/jsm/renderers/webgpu/WebGPURenderStates.js b/examples/jsm/renderers/webgpu/WebGPURenderStates.js deleted file mode 100644 index 21c5d926a66b69..00000000000000 --- a/examples/jsm/renderers/webgpu/WebGPURenderStates.js +++ /dev/null @@ -1,54 +0,0 @@ -import WebGPUWeakMap from './WebGPUWeakMap.js'; - -class WebGPURenderState { - - constructor() { - - this.depth = true; - this.stencil = true; - - // defined by renderer(backend) - - this.descriptorGPU = null; - this.encoderGPU = null; - this.currentPassGPU = null; - - } - -} - -class WebGPURenderStates { - - constructor() { - - this.renderStates = new WebGPUWeakMap(); - - } - - get( scene, camera ) { - - const chainKey = [ scene, camera ]; - - let renderState = this.renderStates.get( chainKey ); - - if ( renderState === undefined ) { - - renderState = new WebGPURenderState(); - - this.renderStates.set( chainKey, renderState ); - - } - - return renderState; - - } - - dispose() { - - this.renderStates = new WebGPUWeakMap(); - - } - -} - -export default WebGPURenderStates; diff --git a/examples/jsm/renderers/webgpu/WebGPURenderTarget.js b/examples/jsm/renderers/webgpu/WebGPURenderTarget.js deleted file mode 100644 index 4726614088457c..00000000000000 --- a/examples/jsm/renderers/webgpu/WebGPURenderTarget.js +++ /dev/null @@ -1,15 +0,0 @@ -import { WebGLRenderTarget } from 'three'; - -// @TODO: Consider rename WebGLRenderTarget to just RenderTarget - -class WebGPURenderTarget extends WebGLRenderTarget { - - constructor( width, height, options = {} ) { - - super( width, height, options ); - - } - -} - -export default WebGPURenderTarget; diff --git a/examples/jsm/renderers/webgpu/WebGPURenderer.js b/examples/jsm/renderers/webgpu/WebGPURenderer.js deleted file mode 100644 index 2ec9369a5d5d7e..00000000000000 --- a/examples/jsm/renderers/webgpu/WebGPURenderer.js +++ /dev/null @@ -1,1236 +0,0 @@ -import { GPUIndexFormat, GPUTextureFormat, GPUFeatureName, GPULoadOp } from './constants.js'; -import WebGPUAnimation from './WebGPUAnimation.js'; -import WebGPURenderObjects from './WebGPURenderObjects.js'; -import WebGPUAttributes from './WebGPUAttributes.js'; -import WebGPUGeometries from './WebGPUGeometries.js'; -import WebGPUInfo from './WebGPUInfo.js'; -import WebGPUProperties from './WebGPUProperties.js'; -import WebGPURenderPipelines from './WebGPURenderPipelines.js'; -import WebGPUComputePipelines from './WebGPUComputePipelines.js'; -import WebGPUBindings from './WebGPUBindings.js'; -import WebGPURenderLists from './WebGPURenderLists.js'; -import WebGPURenderStates from './WebGPURenderStates.js'; -import WebGPUTextures from './WebGPUTextures.js'; -import WebGPUBackground from './WebGPUBackground.js'; -import WebGPUNodes from './nodes/WebGPUNodes.js'; -import WebGPUUtils from './WebGPUUtils.js'; -import { Frustum, Matrix4, Vector3, Color, SRGBColorSpace, NoToneMapping, DepthFormat } from 'three'; - -let staticAdapter = null; - -if ( navigator.gpu !== undefined ) { - - staticAdapter = await navigator.gpu.requestAdapter(); - -} - -console.info( 'THREE.WebGPURenderer: Modified Matrix4.makePerspective() and Matrix4.makeOrtographic() to work with WebGPU, see https://github.com/mrdoob/three.js/issues/20276.' ); - -Matrix4.prototype.makePerspective = function ( left, right, top, bottom, near, far ) { - - const te = this.elements; - const x = 2 * near / ( right - left ); - const y = 2 * near / ( top - bottom ); - - const a = ( right + left ) / ( right - left ); - const b = ( top + bottom ) / ( top - bottom ); - const c = - far / ( far - near ); - const d = - far * near / ( far - near ); - - te[ 0 ] = x; te[ 4 ] = 0; te[ 8 ] = a; te[ 12 ] = 0; - te[ 1 ] = 0; te[ 5 ] = y; te[ 9 ] = b; te[ 13 ] = 0; - te[ 2 ] = 0; te[ 6 ] = 0; te[ 10 ] = c; te[ 14 ] = d; - te[ 3 ] = 0; te[ 7 ] = 0; te[ 11 ] = - 1; te[ 15 ] = 0; - - return this; - -}; - -Matrix4.prototype.makeOrthographic = function ( left, right, top, bottom, near, far ) { - - const te = this.elements; - const w = 1.0 / ( right - left ); - const h = 1.0 / ( top - bottom ); - const p = 1.0 / ( far - near ); - - const x = ( right + left ) * w; - const y = ( top + bottom ) * h; - const z = near * p; - - te[ 0 ] = 2 * w; te[ 4 ] = 0; te[ 8 ] = 0; te[ 12 ] = - x; - te[ 1 ] = 0; te[ 5 ] = 2 * h; te[ 9 ] = 0; te[ 13 ] = - y; - te[ 2 ] = 0; te[ 6 ] = 0; te[ 10 ] = - 1 * p; te[ 14 ] = - z; - te[ 3 ] = 0; te[ 7 ] = 0; te[ 11 ] = 0; te[ 15 ] = 1; - - return this; - -}; - -Frustum.prototype.setFromProjectionMatrix = function ( m ) { - - const planes = this.planes; - const me = m.elements; - const me0 = me[ 0 ], me1 = me[ 1 ], me2 = me[ 2 ], me3 = me[ 3 ]; - const me4 = me[ 4 ], me5 = me[ 5 ], me6 = me[ 6 ], me7 = me[ 7 ]; - const me8 = me[ 8 ], me9 = me[ 9 ], me10 = me[ 10 ], me11 = me[ 11 ]; - const me12 = me[ 12 ], me13 = me[ 13 ], me14 = me[ 14 ], me15 = me[ 15 ]; - - planes[ 0 ].setComponents( me3 - me0, me7 - me4, me11 - me8, me15 - me12 ).normalize(); - planes[ 1 ].setComponents( me3 + me0, me7 + me4, me11 + me8, me15 + me12 ).normalize(); - planes[ 2 ].setComponents( me3 + me1, me7 + me5, me11 + me9, me15 + me13 ).normalize(); - planes[ 3 ].setComponents( me3 - me1, me7 - me5, me11 - me9, me15 - me13 ).normalize(); - planes[ 4 ].setComponents( me3 - me2, me7 - me6, me11 - me10, me15 - me14 ).normalize(); - planes[ 5 ].setComponents( me2, me6, me10, me14 ).normalize(); - - return this; - -}; - -const _frustum = new Frustum(); -const _projScreenMatrix = new Matrix4(); -const _vector3 = new Vector3(); - -class WebGPURenderer { - - constructor( parameters = {} ) { - - this.isWebGPURenderer = true; - - // public - - this.domElement = ( parameters.canvas !== undefined ) ? parameters.canvas : this._createCanvasElement(); - - this.autoClear = true; - this.autoClearColor = true; - this.autoClearDepth = true; - this.autoClearStencil = true; - - this.outputColorSpace = SRGBColorSpace; - - this.toneMapping = NoToneMapping; - this.toneMappingExposure = 1.0; - - this.sortObjects = true; - - // internals - - this._parameters = Object.assign( {}, parameters ); - - this._pixelRatio = 1; - this._width = this.domElement.width; - this._height = this.domElement.height; - - this._viewport = null; - this._scissor = null; - - this._adapter = null; - this._device = null; - this._context = null; - this._colorBuffer = null; - this._depthBuffer = null; - - this._info = null; - this._properties = null; - this._attributes = null; - this._geometries = null; - this._nodes = null; - this._bindings = null; - this._objects = null; - this._renderPipelines = null; - this._computePipelines = null; - this._renderLists = null; - this._renderStates = null; - this._textures = null; - this._background = null; - - this._animation = new WebGPUAnimation(); - - this._currentRenderState = null; - this._lastRenderState = null; - - this._opaqueSort = null; - this._transparentSort = null; - - this._clearAlpha = 1; - this._clearColor = new Color( 0x000000 ); - this._clearDepth = 1; - this._clearStencil = 0; - - this._renderTarget = null; - - this._initialized = false; - this._initPromise = null; - - // some parameters require default values other than "undefined" - - this._parameters.antialias = ( parameters.antialias === true ); - - if ( this._parameters.antialias === true ) { - - this._parameters.sampleCount = ( parameters.sampleCount === undefined ) ? 4 : parameters.sampleCount; - - } else { - - this._parameters.sampleCount = 1; - - } - - this._parameters.requiredLimits = ( parameters.requiredLimits === undefined ) ? {} : parameters.requiredLimits; - - // backwards compatibility - - this.shadow = { - shadowMap: {} - }; - - } - - async init() { - - if ( this._initialized === true ) { - - throw new Error( 'WebGPURenderer: Device has already been initialized.' ); - - } - - if ( this._initPromise !== null ) { - - return this._initPromise; - - } - - this._initPromise = new Promise( async ( resolve, reject ) => { - - const parameters = this._parameters; - - const adapterOptions = { - powerPreference: parameters.powerPreference - }; - - const adapter = await navigator.gpu.requestAdapter( adapterOptions ); - - if ( adapter === null ) { - - reject( new Error( 'WebGPURenderer: Unable to create WebGPU adapter.' ) ); - return; - - } - - // feature support - - const features = Object.values( GPUFeatureName ); - - const supportedFeatures = []; - - for ( const name of features ) { - - if ( adapter.features.has( name ) ) { - - supportedFeatures.push( name ); - - } - - } - - const deviceDescriptor = { - requiredFeatures: supportedFeatures, - requiredLimits: parameters.requiredLimits - }; - - const device = await adapter.requestDevice( deviceDescriptor ); - - const context = ( parameters.context !== undefined ) ? parameters.context : this.domElement.getContext( 'webgpu' ); - - this._adapter = adapter; - this._device = device; - this._context = context; - - this._configureContext(); - - this._info = new WebGPUInfo(); - this._properties = new WebGPUProperties(); - this._attributes = new WebGPUAttributes( device ); - this._geometries = new WebGPUGeometries( this._attributes, this._properties, this._info ); - this._textures = new WebGPUTextures( device, this._properties, this._info ); - this._utils = new WebGPUUtils( this ); - this._nodes = new WebGPUNodes( this, this._properties ); - this._objects = new WebGPURenderObjects( this, this._nodes, this._geometries, this._info ); - this._computePipelines = new WebGPUComputePipelines( device, this._nodes ); - this._renderPipelines = new WebGPURenderPipelines( device, this._nodes, this._utils ); - this._bindings = this._renderPipelines.bindings = new WebGPUBindings( device, this._info, this._properties, this._textures, this._renderPipelines, this._computePipelines, this._attributes, this._nodes ); - this._renderLists = new WebGPURenderLists(); - this._renderStates = new WebGPURenderStates(); - this._background = new WebGPUBackground( this, this._properties ); - - // - - this._setupColorBuffer(); - this._setupDepthBuffer(); - - this._animation.setNodes( this._nodes ); - this._animation.start(); - - this._initialized = true; - - resolve(); - - } ); - - return this._initPromise; - - } - - async render( scene, camera ) { - - if ( this._initialized === false ) await this.init(); - - // preserve render tree - - const nodeFrame = this._nodes.nodeFrame; - - const previousRenderId = nodeFrame.renderId; - const previousRenderState = this._currentRenderState; - - // - - const renderState = this._renderStates.get( scene, camera ); - const renderTarget = this._renderTarget; - - this._currentRenderState = renderState; - - nodeFrame.renderId ++; - - // - - if ( this._animation.isAnimating === false ) nodeFrame.update(); - - if ( scene.matrixWorldAutoUpdate === true ) scene.updateMatrixWorld(); - - if ( camera.parent === null && camera.matrixWorldAutoUpdate === true ) camera.updateMatrixWorld(); - - if ( this._info.autoReset === true ) this._info.reset(); - - this._info.render.frame ++; - - _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); - _frustum.setFromProjectionMatrix( _projScreenMatrix ); - - const renderList = this._renderLists.get( scene, camera ); - renderList.init(); - - this._projectObject( scene, camera, 0, renderList ); - - renderList.finish(); - - if ( this.sortObjects === true ) { - - renderList.sort( this._opaqueSort, this._transparentSort ); - - } - - // prepare render pass descriptor - - renderState.descriptorGPU = { - colorAttachments: [ { - view: null - } ], - depthStencilAttachment: { - view: null - } - }; - - const colorAttachment = renderState.descriptorGPU.colorAttachments[ 0 ]; - const depthStencilAttachment = renderState.descriptorGPU.depthStencilAttachment; - - if ( renderTarget !== null ) { - - this._textures.initRenderTarget( renderTarget ); - - // @TODO: Support RenderTarget with antialiasing. - - const renderTargetProperties = this._properties.get( renderTarget ); - - colorAttachment.view = renderTargetProperties.colorTextureGPU.createView(); - depthStencilAttachment.view = renderTargetProperties.depthTextureGPU.createView(); - - renderState.stencil = renderTarget.depthTexture ? renderTarget.depthTexture.format !== DepthFormat : true; - - } else { - - if ( this._parameters.antialias === true ) { - - colorAttachment.view = this._colorBuffer.createView(); - colorAttachment.resolveTarget = this._context.getCurrentTexture().createView(); - - } else { - - colorAttachment.view = this._context.getCurrentTexture().createView(); - colorAttachment.resolveTarget = undefined; - - } - - depthStencilAttachment.view = this._depthBuffer.createView(); - - } - - // - - this._nodes.updateEnvironment( scene ); - this._nodes.updateFog( scene ); - this._nodes.updateBackground( scene ); - this._nodes.updateToneMapping(); - - // - - this._background.update( scene, renderList, renderState ); - - // start render pass - - const device = this._device; - - renderState.encoderGPU = device.createCommandEncoder( {} ); - renderState.currentPassGPU = renderState.encoderGPU.beginRenderPass( renderState.descriptorGPU ); - - // global rasterization settings for all renderable objects - - const vp = this._viewport; - - if ( vp !== null ) { - - const width = Math.floor( vp.width * this._pixelRatio ); - const height = Math.floor( vp.height * this._pixelRatio ); - - renderState.currentPassGPU.setViewport( vp.x, vp.y, width, height, vp.minDepth, vp.maxDepth ); - - } - - const sc = this._scissor; - - if ( sc !== null ) { - - const width = Math.floor( sc.width * this._pixelRatio ); - const height = Math.floor( sc.height * this._pixelRatio ); - - renderState.currentPassGPU.setScissorRect( sc.x, sc.y, width, height ); - - } - - // process render lists - - const opaqueObjects = renderList.opaque; - const transparentObjects = renderList.transparent; - const lightsNode = renderList.lightsNode; - - if ( opaqueObjects.length > 0 ) this._renderObjects( opaqueObjects, camera, scene, lightsNode, renderState ); - if ( transparentObjects.length > 0 ) this._renderObjects( transparentObjects, camera, scene, lightsNode, renderState ); - - // finish render pass - - renderState.currentPassGPU.end(); - - device.queue.submit( [ renderState.encoderGPU.finish() ] ); - - // restore render tree - - nodeFrame.renderId = previousRenderId; - this._currentRenderState = previousRenderState; - - this._lastRenderState = renderState; - - } - - setAnimationLoop( callback ) { - - if ( this._initialized === false ) this.init(); - - const animation = this._animation; - - animation.setAnimationLoop( callback ); - - ( callback === null ) ? animation.stop() : animation.start(); - - } - - async getArrayBuffer( attribute ) { - - return await this._attributes.getArrayBuffer( attribute ); - - } - - getContext() { - - return this._context; - - } - - getPixelRatio() { - - return this._pixelRatio; - - } - - getDrawingBufferSize( target ) { - - return target.set( this._width * this._pixelRatio, this._height * this._pixelRatio ).floor(); - - } - - getSize( target ) { - - return target.set( this._width, this._height ); - - } - - setPixelRatio( value = 1 ) { - - this._pixelRatio = value; - - this.setSize( this._width, this._height, false ); - - } - - setDrawingBufferSize( width, height, pixelRatio ) { - - this._width = width; - this._height = height; - - this._pixelRatio = pixelRatio; - - this.domElement.width = Math.floor( width * pixelRatio ); - this.domElement.height = Math.floor( height * pixelRatio ); - - this._configureContext(); - this._setupColorBuffer(); - this._setupDepthBuffer(); - - } - - setSize( width, height, updateStyle = true ) { - - this._width = width; - this._height = height; - - this.domElement.width = Math.floor( width * this._pixelRatio ); - this.domElement.height = Math.floor( height * this._pixelRatio ); - - if ( updateStyle === true ) { - - this.domElement.style.width = width + 'px'; - this.domElement.style.height = height + 'px'; - - } - - this._configureContext(); - this._setupColorBuffer(); - this._setupDepthBuffer(); - - } - - setOpaqueSort( method ) { - - this._opaqueSort = method; - - } - - setTransparentSort( method ) { - - this._transparentSort = method; - - } - - getScissor( target ) { - - const scissor = this._scissor; - - target.x = scissor.x; - target.y = scissor.y; - target.width = scissor.width; - target.height = scissor.height; - - return target; - - } - - setScissor( x, y, width, height ) { - - if ( x === null ) { - - this._scissor = null; - - } else { - - this._scissor = { - x: x, - y: y, - width: width, - height: height - }; - - } - - } - - copyFramebufferToRenderTarget( renderTarget ) { - - const renderState = this._currentRenderState; - const { encoderGPU, descriptorGPU } = renderState; - - const texture = renderTarget.texture; - texture.internalFormat = GPUTextureFormat.BGRA8Unorm; - - this._textures.initRenderTarget( renderTarget ); - - const sourceGPU = this._context.getCurrentTexture(); - const destinationGPU = this._textures.getTextureGPU( texture ); - - renderState.currentPassGPU.end(); - - encoderGPU.copyTextureToTexture( - { - texture: sourceGPU - }, - { - texture: destinationGPU - }, - [ - texture.image.width, - texture.image.height - ] - ); - - descriptorGPU.colorAttachments[ 0 ].loadOp = GPULoadOp.Load; - if ( renderState.depth ) descriptorGPU.depthStencilAttachment.depthLoadOp = GPULoadOp.Load; - if ( renderState.stencil ) descriptorGPU.depthStencilAttachment.stencilLoadOp = GPULoadOp.Load; - - renderState.currentPassGPU = encoderGPU.beginRenderPass( descriptorGPU ); - - } - - getViewport( target ) { - - const viewport = this._viewport; - - target.x = viewport.x; - target.y = viewport.y; - target.width = viewport.width; - target.height = viewport.height; - target.minDepth = viewport.minDepth; - target.maxDepth = viewport.maxDepth; - - return target; - - } - - setViewport( x, y, width, height, minDepth = 0, maxDepth = 1 ) { - - if ( x === null ) { - - this._viewport = null; - - } else { - - this._viewport = { - x: x, - y: y, - width: width, - height: height, - minDepth: minDepth, - maxDepth: maxDepth - }; - - } - - } - - getClearColor( target ) { - - return target.copy( this._clearColor ); - - } - - setClearColor( color, alpha = 1 ) { - - this._clearColor.set( color ); - this._clearAlpha = alpha; - - } - - getClearAlpha() { - - return this._clearAlpha; - - } - - setClearAlpha( alpha ) { - - this._clearAlpha = alpha; - - } - - getClearDepth() { - - return this._clearDepth; - - } - - setClearDepth( depth ) { - - this._clearDepth = depth; - - } - - getClearStencil() { - - return this._clearStencil; - - } - - setClearStencil( stencil ) { - - this._clearStencil = stencil; - - } - - clear( color = true, depth = true, stencil = true ) { - - const renderState = this._currentRenderState || this._lastRenderState; - if ( renderState === null ) return; - - depth = depth && renderState.depth; - stencil = stencil && renderState.stencil; - - const descriptorGPU = renderState.descriptorGPU; - const colorAttachment = descriptorGPU.colorAttachments[ 0 ]; - - // @TODO: Include render target in clear operation. - if ( this._parameters.antialias === true ) { - - colorAttachment.view = this._colorBuffer.createView(); - colorAttachment.resolveTarget = this._context.getCurrentTexture().createView(); - - } else { - - colorAttachment.view = this._context.getCurrentTexture().createView(); - colorAttachment.resolveTarget = undefined; - - } - - descriptorGPU.depthStencilAttachment.view = this._depthBuffer.createView(); - - if ( color ) { - - colorAttachment.loadOp = GPULoadOp.Clear; - colorAttachment.clearValue = { r: this._clearColor.r, g: this._clearColor.g, b: this._clearColor.b, a: this._clearAlpha }; - - } - - if ( depth ) { - - descriptorGPU.depthStencilAttachment.depthLoadOp = GPULoadOp.Clear; - descriptorGPU.depthStencilAttachment.depthClearValue = this._clearDepth; - - } - - if ( stencil ) { - - descriptorGPU.depthStencilAttachment.stencilLoadOp = GPULoadOp.Clear; - descriptorGPU.depthStencilAttachment.stencilClearValue = this._clearStencil; - - } - - renderState.encoderGPU = this._device.createCommandEncoder( {} ); - renderState.currentPassGPU = renderState.encoderGPU.beginRenderPass( renderState.descriptorGPU ); - - renderState.currentPassGPU.end(); - - this._device.queue.submit( [ renderState.encoderGPU.finish() ] ); - - } - - clearColor() { - - this.clear( true, false, false ); - - } - - clearDepth() { - - this.clear( false, true, false ); - - } - - clearStencil() { - - this.clear( false, false, true ); - - } - - dispose() { - - this._objects.dispose(); - this._properties.dispose(); - this._renderPipelines.dispose(); - this._computePipelines.dispose(); - this._nodes.dispose(); - this._bindings.dispose(); - this._info.dispose(); - this._renderLists.dispose(); - this._renderStates.dispose(); - this._textures.dispose(); - - this.setRenderTarget( null ); - this.setAnimationLoop( null ); - - } - - setRenderTarget( renderTarget ) { - - this._renderTarget = renderTarget; - - } - - async compute( ...computeNodes ) { - - if ( this._initialized === false ) await this.init(); - - const device = this._device; - const computePipelines = this._computePipelines; - - const cmdEncoder = device.createCommandEncoder( {} ); - const passEncoder = cmdEncoder.beginComputePass(); - - for ( const computeNode of computeNodes ) { - - // onInit - - if ( computePipelines.has( computeNode ) === false ) { - - computeNode.onInit( { renderer: this } ); - - } - - // pipeline - - const pipeline = computePipelines.get( computeNode ); - passEncoder.setPipeline( pipeline ); - - // node - - //this._nodes.update( computeNode ); - - // bind group - - const bindGroup = this._bindings.getForCompute( computeNode ).group; - this._bindings.update( computeNode ); - passEncoder.setBindGroup( 0, bindGroup ); - - passEncoder.dispatchWorkgroups( computeNode.dispatchCount ); - - } - - passEncoder.end(); - device.queue.submit( [ cmdEncoder.finish() ] ); - - } - - getRenderTarget() { - - return this._renderTarget; - - } - - hasFeature( name ) { - - const adapter = this._adapter || staticAdapter; - - // - - const features = Object.values( GPUFeatureName ); - - if ( features.includes( name ) === false ) { - - throw new Error( 'THREE.WebGPURenderer: Unknown WebGPU GPU feature: ' + name ); - - } - - // - - return adapter.features.has( name ); - - } - - _projectObject( object, camera, groupOrder, renderList ) { - - if ( object.visible === false ) return; - - const visible = object.layers.test( camera.layers ); - - if ( visible ) { - - if ( object.isGroup ) { - - groupOrder = object.renderOrder; - - } else if ( object.isLOD ) { - - if ( object.autoUpdate === true ) object.update( camera ); - - } else if ( object.isLight ) { - - renderList.pushLight( object ); - - } else if ( object.isSprite ) { - - if ( ! object.frustumCulled || _frustum.intersectsSprite( object ) ) { - - if ( this.sortObjects === true ) { - - _vector3.setFromMatrixPosition( object.matrixWorld ).applyMatrix4( _projScreenMatrix ); - - } - - const geometry = object.geometry; - const material = object.material; - - if ( material.visible ) { - - renderList.push( object, geometry, material, groupOrder, _vector3.z, null ); - - } - - } - - } else if ( object.isLineLoop ) { - - console.error( 'THREE.WebGPURenderer: Objects of type THREE.LineLoop are not supported. Please use THREE.Line or THREE.LineSegments.' ); - - } else if ( object.isMesh || object.isLine || object.isPoints ) { - - if ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) { - - const geometry = object.geometry; - const material = object.material; - - if ( this.sortObjects === true ) { - - if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); - - _vector3 - .copy( geometry.boundingSphere.center ) - .applyMatrix4( object.matrixWorld ) - .applyMatrix4( _projScreenMatrix ); - - } - - if ( Array.isArray( material ) ) { - - const groups = geometry.groups; - - for ( let i = 0, l = groups.length; i < l; i ++ ) { - - const group = groups[ i ]; - const groupMaterial = material[ group.materialIndex ]; - - if ( groupMaterial && groupMaterial.visible ) { - - renderList.push( object, geometry, groupMaterial, groupOrder, _vector3.z, group ); - - } - - } - - } else if ( material.visible ) { - - renderList.push( object, geometry, material, groupOrder, _vector3.z, null ); - - } - - } - - } - - } - - const children = object.children; - - for ( let i = 0, l = children.length; i < l; i ++ ) { - - this._projectObject( children[ i ], camera, groupOrder, renderList ); - - } - - } - - _renderObjects( renderList, camera, scene, lightsNode ) { - - // process renderable objects - - for ( let i = 0, il = renderList.length; i < il; i ++ ) { - - const renderItem = renderList[ i ]; - - // @TODO: Add support for multiple materials per object. This will require to extract - // the material from the renderItem object and pass it with its group data to _renderObject(). - - const { object, geometry, material, group } = renderItem; - - if ( camera.isArrayCamera ) { - - const cameras = camera.cameras; - - for ( let j = 0, jl = cameras.length; j < jl; j ++ ) { - - const camera2 = cameras[ j ]; - - if ( object.layers.test( camera2.layers ) ) { - - const vp = camera2.viewport; - const minDepth = ( vp.minDepth === undefined ) ? 0 : vp.minDepth; - const maxDepth = ( vp.maxDepth === undefined ) ? 1 : vp.maxDepth; - - this._currentRenderState.currentPassGPU.setViewport( vp.x, vp.y, vp.width, vp.height, minDepth, maxDepth ); - - this._renderObject( object, scene, camera2, geometry, material, group, lightsNode ); - - } - - } - - } else { - - this._renderObject( object, scene, camera, geometry, material, group, lightsNode ); - - } - - } - - } - - _renderObject( object, scene, camera, geometry, material, group, lightsNode ) { - - material = scene.overrideMaterial !== null ? scene.overrideMaterial : material; - - // - - object.onBeforeRender( this, scene, camera, geometry, material, group ); - - // - - const renderObject = this._getRenderObject( object, material, scene, camera, lightsNode ); - - // - - this._nodes.updateBefore( renderObject ); - - // - - const passEncoder = this._currentRenderState.currentPassGPU; - const info = this._info; - - // - - object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld ); - object.normalMatrix.getNormalMatrix( object.modelViewMatrix ); - - // updates - - this._nodes.update( renderObject ); - this._geometries.update( renderObject ); - this._bindings.update( renderObject ); - - // pipeline - - const renderPipeline = this._renderPipelines.get( renderObject ); - passEncoder.setPipeline( renderPipeline.pipeline ); - - // bind group - - const bindGroup = this._bindings.get( renderObject ).group; - passEncoder.setBindGroup( 0, bindGroup ); - - // index - - const index = this._geometries.getIndex( renderObject ); - - const hasIndex = ( index !== null ); - - if ( hasIndex === true ) { - - this._setupIndexBuffer( renderObject ); - - } - - // vertex buffers - - this._setupVertexBuffers( renderObject ); - - // draw - - const drawRange = geometry.drawRange; - const firstVertex = drawRange.start; - - const instanceCount = geometry.isInstancedBufferGeometry ? geometry.instanceCount : ( object.isInstancedMesh ? object.count : 1 ); - - if ( hasIndex === true ) { - - const indexCount = ( drawRange.count !== Infinity ) ? drawRange.count : index.count; - - passEncoder.drawIndexed( indexCount, instanceCount, firstVertex, 0, 0 ); - - info.update( object, indexCount, instanceCount ); - - } else { - - const positionAttribute = geometry.attributes.position; - const vertexCount = ( drawRange.count !== Infinity ) ? drawRange.count : positionAttribute.count; - - passEncoder.draw( vertexCount, instanceCount, firstVertex, 0 ); - - info.update( object, vertexCount, instanceCount ); - - } - - } - - _getRenderObject( object, material, scene, camera, lightsNode ) { - - const renderObject = this._objects.get( object, material, scene, camera, lightsNode ); - const renderObjectProperties = this._properties.get( renderObject ); - - if ( renderObjectProperties.initialized !== true ) { - - renderObjectProperties.initialized = true; - - const dispose = () => { - - this._renderPipelines.remove( renderObject ); - this._nodes.remove( renderObject ); - this._properties.remove( renderObject ); - - this._objects.remove( object, material, scene, camera, lightsNode ); - - renderObject.material.removeEventListener( 'dispose', dispose ); - - }; - - renderObject.material.addEventListener( 'dispose', dispose ); - - } - - const cacheKey = renderObject.getCacheKey(); - - if ( renderObjectProperties.cacheKey !== cacheKey ) { - - renderObjectProperties.cacheKey = cacheKey; - - this._renderPipelines.remove( renderObject ); - this._nodes.remove( renderObject ); - - } - - return renderObject; - - } - - _setupIndexBuffer( renderObject ) { - - const index = this._geometries.getIndex( renderObject ); - const passEncoder = this._currentRenderState.currentPassGPU; - - const buffer = this._attributes.get( index ).buffer; - const indexFormat = ( index.array instanceof Uint16Array ) ? GPUIndexFormat.Uint16 : GPUIndexFormat.Uint32; - - passEncoder.setIndexBuffer( buffer, indexFormat ); - - } - - _setupVertexBuffers( renderObject ) { - - const passEncoder = this._currentRenderState.currentPassGPU; - const attributes = renderObject.getAttributes(); - - for ( let i = 0, l = attributes.length; i < l; i ++ ) { - - const buffer = this._attributes.get( attributes[ i ] ).buffer; - passEncoder.setVertexBuffer( i, buffer ); - - } - - } - - _setupColorBuffer() { - - const device = this._device; - - if ( device ) { - - if ( this._colorBuffer ) this._colorBuffer.destroy(); - - this._colorBuffer = this._device.createTexture( { - label: 'colorBuffer', - size: { - width: Math.floor( this._width * this._pixelRatio ), - height: Math.floor( this._height * this._pixelRatio ), - depthOrArrayLayers: 1 - }, - sampleCount: this._parameters.sampleCount, - format: GPUTextureFormat.BGRA8Unorm, - usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC - } ); - - } - - } - - _setupDepthBuffer() { - - const device = this._device; - - if ( device ) { - - if ( this._depthBuffer ) this._depthBuffer.destroy(); - - this._depthBuffer = this._device.createTexture( { - label: 'depthBuffer', - size: { - width: Math.floor( this._width * this._pixelRatio ), - height: Math.floor( this._height * this._pixelRatio ), - depthOrArrayLayers: 1 - }, - sampleCount: this._parameters.sampleCount, - format: GPUTextureFormat.Depth24PlusStencil8, - usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC - } ); - - } - - } - - _configureContext() { - - const device = this._device; - - if ( device ) { - - this._context.configure( { - device: device, - format: GPUTextureFormat.BGRA8Unorm, - usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC, - alphaMode: 'premultiplied' - } ); - - } - - } - - _createCanvasElement() { - - const canvas = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' ); - canvas.style.display = 'block'; - return canvas; - - } - -} - -export default WebGPURenderer; diff --git a/examples/jsm/renderers/webgpu/WebGPUSampledTexture.js b/examples/jsm/renderers/webgpu/WebGPUSampledTexture.js deleted file mode 100644 index 8355e0e5f34fe5..00000000000000 --- a/examples/jsm/renderers/webgpu/WebGPUSampledTexture.js +++ /dev/null @@ -1,75 +0,0 @@ -import WebGPUBinding from './WebGPUBinding.js'; -import { GPUBindingType, GPUTextureViewDimension, GPUTextureAspect } from './constants.js'; - -class WebGPUSampledTexture extends WebGPUBinding { - - constructor( name, texture ) { - - super( name ); - - this.isSampledTexture = true; - - this.texture = texture; - - this.dimension = GPUTextureViewDimension.TwoD; - - this.type = GPUBindingType.SampledTexture; - this.visibility = GPUShaderStage.FRAGMENT; - - this.aspect = texture.isDepthTexture ? GPUTextureAspect.DepthOnly : GPUTextureAspect.All; - - this.textureGPU = null; // set by the renderer - - } - - getTexture() { - - return this.texture; - - } - -} - -class WebGPUSampledArrayTexture extends WebGPUSampledTexture { - - constructor( name, texture ) { - - super( name, texture ); - - this.isSampledArrayTexture = true; - - this.dimension = GPUTextureViewDimension.TwoDArray; - - } - -} - -class WebGPUSampled3DTexture extends WebGPUSampledTexture { - - constructor( name, texture ) { - - super( name, texture ); - - this.isSampled3DTexture = true; - - this.dimension = GPUTextureViewDimension.ThreeD; - - } - -} - -class WebGPUSampledCubeTexture extends WebGPUSampledTexture { - - constructor( name, texture ) { - - super( name, texture ); - - this.isSampledCubeTexture = true; - - this.dimension = GPUTextureViewDimension.Cube; - - } - -} - -export { WebGPUSampledTexture, WebGPUSampledArrayTexture, WebGPUSampled3DTexture, WebGPUSampledCubeTexture }; diff --git a/examples/jsm/renderers/webgpu/WebGPUSampler.js b/examples/jsm/renderers/webgpu/WebGPUSampler.js deleted file mode 100644 index eaf1065cc5a8ee..00000000000000 --- a/examples/jsm/renderers/webgpu/WebGPUSampler.js +++ /dev/null @@ -1,29 +0,0 @@ -import WebGPUBinding from './WebGPUBinding.js'; -import { GPUBindingType } from './constants.js'; - -class WebGPUSampler extends WebGPUBinding { - - constructor( name, texture ) { - - super( name ); - - this.isSampler = true; - - this.texture = texture; - - this.type = GPUBindingType.Sampler; - this.visibility = GPUShaderStage.FRAGMENT; - - this.samplerGPU = null; // set by the renderer - - } - - getTexture() { - - return this.texture; - - } - -} - -export default WebGPUSampler; diff --git a/examples/jsm/renderers/webgpu/WebGPUStorageBuffer.js b/examples/jsm/renderers/webgpu/WebGPUStorageBuffer.js deleted file mode 100644 index 54d6fd8c423e6d..00000000000000 --- a/examples/jsm/renderers/webgpu/WebGPUStorageBuffer.js +++ /dev/null @@ -1,20 +0,0 @@ -import WebGPUBuffer from './WebGPUBuffer.js'; -import { GPUBindingType } from './constants.js'; - -class WebGPUStorageBuffer extends WebGPUBuffer { - - constructor( name, attribute ) { - - super( name, GPUBindingType.StorageBuffer, attribute.array ); - - this.isStorageBuffer = true; - - this.usage |= GPUBufferUsage.VERTEX | GPUBufferUsage.STORAGE; - - this.attribute = attribute; - - } - -} - -export default WebGPUStorageBuffer; diff --git a/examples/jsm/renderers/webgpu/WebGPUTextureRenderer.js b/examples/jsm/renderers/webgpu/WebGPUTextureRenderer.js deleted file mode 100644 index da82960b6784a1..00000000000000 --- a/examples/jsm/renderers/webgpu/WebGPUTextureRenderer.js +++ /dev/null @@ -1,38 +0,0 @@ -import WebGPURenderTarget from './WebGPURenderTarget.js'; - -class WebGPUTextureRenderer { - - constructor( renderer, options = {} ) { - - this.renderer = renderer; - - this.renderTarget = new WebGPURenderTarget( 1, 1, options ); - - } - - getTexture() { - - return this.renderTarget.texture; - - } - - setSize( width, height ) { - - this.renderTarget.setSize( width, height ); - - } - - render( scene, camera ) { - - const renderer = this.renderer; - const renderTarget = this.renderTarget; - - renderer.setRenderTarget( renderTarget ); - renderer.render( scene, camera ); - renderer.setRenderTarget( null ); - - } - -} - -export default WebGPUTextureRenderer; diff --git a/examples/jsm/renderers/webgpu/WebGPUTextureUtils.js b/examples/jsm/renderers/webgpu/WebGPUTextureUtils.js deleted file mode 100644 index 535df382201890..00000000000000 --- a/examples/jsm/renderers/webgpu/WebGPUTextureUtils.js +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright 2020 Brandon Jones -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: - -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. - -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -import { GPUTextureViewDimension, GPUIndexFormat, GPUFilterMode, GPUPrimitiveTopology, GPULoadOp, GPUStoreOp } from './constants.js'; - -// ported from https://github.com/toji/web-texture-tool/blob/master/src/webgpu-mipmap-generator.js - -class WebGPUTextureUtils { - - constructor( device ) { - - this.device = device; - - const mipmapVertexSource = ` -struct VarysStruct { - @builtin( position ) Position: vec4, - @location( 0 ) vTex : vec2 -}; - -@vertex -fn main( @builtin( vertex_index ) vertexIndex : u32 ) -> VarysStruct { - - var Varys : VarysStruct; - - var pos = array< vec2, 4 >( - vec2( -1.0, 1.0 ), - vec2( 1.0, 1.0 ), - vec2( -1.0, -1.0 ), - vec2( 1.0, -1.0 ) - ); - - var tex = array< vec2, 4 >( - vec2( 0.0, 0.0 ), - vec2( 1.0, 0.0 ), - vec2( 0.0, 1.0 ), - vec2( 1.0, 1.0 ) - ); - - Varys.vTex = tex[ vertexIndex ]; - Varys.Position = vec4( pos[ vertexIndex ], 0.0, 1.0 ); - - return Varys; - -} -`; - - const mipmapFragmentSource = ` -@group( 0 ) @binding( 0 ) -var imgSampler : sampler; - -@group( 0 ) @binding( 1 ) -var img : texture_2d; - -@fragment -fn main( @location( 0 ) vTex : vec2 ) -> @location( 0 ) vec4 { - - return textureSample( img, imgSampler, vTex ); - -} -`; - - this.sampler = device.createSampler( { minFilter: GPUFilterMode.Linear } ); - - // We'll need a new pipeline for every texture format used. - this.pipelines = {}; - - this.mipmapVertexShaderModule = device.createShaderModule( { - label: 'mipmapVertex', - code: mipmapVertexSource - } ); - - this.mipmapFragmentShaderModule = device.createShaderModule( { - label: 'mipmapFragment', - code: mipmapFragmentSource - } ); - - } - - getMipmapPipeline( format ) { - - let pipeline = this.pipelines[ format ]; - - if ( pipeline === undefined ) { - - pipeline = this.device.createRenderPipeline( { - vertex: { - module: this.mipmapVertexShaderModule, - entryPoint: 'main' - }, - fragment: { - module: this.mipmapFragmentShaderModule, - entryPoint: 'main', - targets: [ { format } ] - }, - primitive: { - topology: GPUPrimitiveTopology.TriangleStrip, - stripIndexFormat: GPUIndexFormat.Uint32 - }, - layout: 'auto' - } ); - - this.pipelines[ format ] = pipeline; - - } - - return pipeline; - - } - - generateMipmaps( textureGPU, textureGPUDescriptor, baseArrayLayer = 0 ) { - - const pipeline = this.getMipmapPipeline( textureGPUDescriptor.format ); - - const commandEncoder = this.device.createCommandEncoder( {} ); - const bindGroupLayout = pipeline.getBindGroupLayout( 0 ); // @TODO: Consider making this static. - - let srcView = textureGPU.createView( { - baseMipLevel: 0, - mipLevelCount: 1, - dimension: GPUTextureViewDimension.TwoD, - baseArrayLayer - } ); - - for ( let i = 1; i < textureGPUDescriptor.mipLevelCount; i ++ ) { - - const dstView = textureGPU.createView( { - baseMipLevel: i, - mipLevelCount: 1, - dimension: GPUTextureViewDimension.TwoD, - baseArrayLayer - } ); - - const passEncoder = commandEncoder.beginRenderPass( { - colorAttachments: [ { - view: dstView, - loadOp: GPULoadOp.Clear, - storeOp: GPUStoreOp.Store, - clearValue: [ 0, 0, 0, 0 ] - } ] - } ); - - const bindGroup = this.device.createBindGroup( { - layout: bindGroupLayout, - entries: [ { - binding: 0, - resource: this.sampler - }, { - binding: 1, - resource: srcView - } ] - } ); - - passEncoder.setPipeline( pipeline ); - passEncoder.setBindGroup( 0, bindGroup ); - passEncoder.draw( 4, 1, 0, 0 ); - passEncoder.end(); - - srcView = dstView; - - } - - this.device.queue.submit( [ commandEncoder.finish() ] ); - - } - -} - -export default WebGPUTextureUtils; diff --git a/examples/jsm/renderers/webgpu/WebGPUTextures.js b/examples/jsm/renderers/webgpu/WebGPUTextures.js deleted file mode 100644 index ba6e7196b1f408..00000000000000 --- a/examples/jsm/renderers/webgpu/WebGPUTextures.js +++ /dev/null @@ -1,1050 +0,0 @@ -import { GPUTextureFormat, GPUAddressMode, GPUFilterMode, GPUTextureDimension, GPUFeatureName } from './constants.js'; -import { VideoTexture, CubeTexture, Texture, NearestFilter, NearestMipmapNearestFilter, NearestMipmapLinearFilter, LinearFilter, RepeatWrapping, MirroredRepeatWrapping, RGB_ETC2_Format, RGBA_ETC2_EAC_Format, - RGBAFormat, RedFormat, RGFormat, RGBA_S3TC_DXT1_Format, RGBA_S3TC_DXT3_Format, RGBA_S3TC_DXT5_Format, UnsignedByteType, FloatType, HalfFloatType, SRGBColorSpace, DepthFormat, DepthStencilFormat, DepthTexture, - RGBA_ASTC_4x4_Format, RGBA_ASTC_5x4_Format, RGBA_ASTC_5x5_Format, RGBA_ASTC_6x5_Format, RGBA_ASTC_6x6_Format, RGBA_ASTC_8x5_Format, RGBA_ASTC_8x6_Format, RGBA_ASTC_8x8_Format, RGBA_ASTC_10x5_Format, - RGBA_ASTC_10x6_Format, RGBA_ASTC_10x8_Format, RGBA_ASTC_10x10_Format, RGBA_ASTC_12x10_Format, RGBA_ASTC_12x12_Format, UnsignedIntType, UnsignedShortType, UnsignedInt248Type -} from 'three'; -import WebGPUTextureUtils from './WebGPUTextureUtils.js'; - -class WebGPUTextures { - - constructor( device, properties, info ) { - - this.device = device; - this.properties = properties; - this.info = info; - - this.defaultTexture = null; - this.depthDefaultTexture = null; - this.defaultVideoTexture = null; - this.defaultCubeTexture = null; - this.defaultSampler = null; - - this.samplerCache = new Map(); - this.utils = null; - - } - - getDefaultSampler() { - - if ( this.defaultSampler === null ) { - - this.defaultSampler = this.device.createSampler( {} ); - - } - - return this.defaultSampler; - - } - - getDefaultDepthTexture() { - - if ( this.depthDefaultTexture === null ) { - - const depthTexture = new DepthTexture(); - depthTexture.image.width = 1; - depthTexture.image.height = 1; - - this._uploadTexture( depthTexture ); - - this.depthDefaultTexture = this.getTextureGPU( depthTexture ); - - } - - return this.depthDefaultTexture; - - } - - getDefaultTexture() { - - if ( this.defaultTexture === null ) { - - const texture = new Texture(); - texture.minFilter = NearestFilter; - texture.magFilter = NearestFilter; - - this._uploadTexture( texture ); - - this.defaultTexture = this.getTextureGPU( texture ); - - } - - return this.defaultTexture; - - } - - getDefaultVideoTexture() { - - if ( this.defaultVideoTexture === null ) { - - const video = document.getElementById( 'video' ); - - const texture = new VideoTexture( video ); - texture.minFilter = NearestFilter; - texture.magFilter = NearestFilter; - - this._uploadVideoTexture( texture ); - - this.defaultVideoTexture = this.getTextureGPU( texture ); - - } - - return this.defaultVideoTexture; - - } - - getDefaultCubeTexture() { - - if ( this.defaultCubeTexture === null ) { - - const texture = new CubeTexture(); - texture.minFilter = NearestFilter; - texture.magFilter = NearestFilter; - - this._uploadTexture( texture ); - - this.defaultCubeTexture = this.getTextureGPU( texture ); - - } - - return this.defaultCubeTexture; - - } - - getTextureGPU( texture ) { - - const textureProperties = this.properties.get( texture ); - - return textureProperties.textureGPU; - - } - - getSampler( texture ) { - - const textureProperties = this.properties.get( texture ); - - return textureProperties.samplerGPU; - - } - - updateTexture( texture ) { - - let needsUpdate = false; - - const textureProperties = this.properties.get( texture ); - - if ( texture.version > 0 && textureProperties.version !== texture.version ) { - - const image = texture.image; - - if ( image === undefined ) { - - console.warn( 'THREE.WebGPURenderer: Texture marked for update but image is undefined.' ); - - } else if ( image.complete === false ) { - - console.warn( 'THREE.WebGPURenderer: Texture marked for update but image is incomplete.' ); - - } else { - - // texture init - - if ( textureProperties.initialized === undefined ) { - - textureProperties.initialized = true; - - const disposeCallback = onTextureDispose.bind( this ); - textureProperties.disposeCallback = disposeCallback; - - texture.addEventListener( 'dispose', disposeCallback ); - - this.info.memory.textures ++; - - } - - // - - if ( texture.isVideoTexture ) { - - needsUpdate = this._uploadVideoTexture( texture ); - - } else { - - needsUpdate = this._uploadTexture( texture ); - - } - - } - - } - - // if the texture is used for RTT, it's necessary to init it once so the binding - // group's resource definition points to the respective GPUTexture - - if ( textureProperties.initializedRTT === false ) { - - textureProperties.initializedRTT = true; - needsUpdate = true; - - } - - return needsUpdate; - - } - - updateSampler( texture ) { - - const array = []; - - array.push( texture.wrapS ); - array.push( texture.wrapT ); - array.push( texture.wrapR ); - array.push( texture.magFilter ); - array.push( texture.minFilter ); - array.push( texture.anisotropy ); - - const key = array.join(); - let samplerGPU = this.samplerCache.get( key ); - - if ( samplerGPU === undefined ) { - - samplerGPU = this.device.createSampler( { - addressModeU: this._convertAddressMode( texture.wrapS ), - addressModeV: this._convertAddressMode( texture.wrapT ), - addressModeW: this._convertAddressMode( texture.wrapR ), - magFilter: this._convertFilterMode( texture.magFilter ), - minFilter: this._convertFilterMode( texture.minFilter ), - mipmapFilter: this._convertFilterMode( texture.minFilter ), - maxAnisotropy: texture.anisotropy - } ); - - this.samplerCache.set( key, samplerGPU ); - - } - - const textureProperties = this.properties.get( texture ); - textureProperties.samplerGPU = samplerGPU; - - } - - initRenderTarget( renderTarget ) { - - const properties = this.properties; - const renderTargetProperties = properties.get( renderTarget ); - - if ( renderTargetProperties.initialized === undefined ) { - - const device = this.device; - - const width = renderTarget.width; - const height = renderTarget.height; - - const texture = renderTarget.texture; - - const colorTextureFormat = texture.internalFormat || this._getFormat( texture ); - const label = texture.name ? '_' + texture.name : ''; - const needsMipmaps = this._needsMipmaps( texture ); - const mipLevelCount = this._getMipLevelCount( texture, width, height, needsMipmaps ); - - const colorTextureGPU = device.createTexture( { - label: 'renderTarget' + label, - size: { - width: width, - height: height, - depthOrArrayLayers: 1 - }, - mipLevelCount: mipLevelCount, - format: colorTextureFormat, - usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST - } ); - - this.info.memory.textures ++; - - renderTargetProperties.colorTextureGPU = colorTextureGPU; - renderTargetProperties.colorTextureFormat = colorTextureFormat; - - // When the ".texture" or ".depthTexture" property of a render target is used as a map, - // the renderer has to find the respective GPUTexture objects to setup the bind groups. - // Since it's not possible to see just from a texture object whether it belongs to a render - // target or not, we need the initializedRTT flag. - - const textureProperties = properties.get( texture ); - textureProperties.textureGPU = colorTextureGPU; - textureProperties.initializedRTT = false; - - if ( renderTarget.depthBuffer === true ) { - - const depthTextureFormat = renderTarget.depthTexture !== null ? this._getFormat( renderTarget.depthTexture ) : GPUTextureFormat.Depth24PlusStencil8; - - const depthTextureGPU = device.createTexture( { - label: 'renderTarget' + label + '_depthBuffer', - size: { - width: width, - height: height, - depthOrArrayLayers: 1 - }, - format: depthTextureFormat, - usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST - } ); - - this.info.memory.textures ++; - - renderTargetProperties.depthTextureGPU = depthTextureGPU; - renderTargetProperties.depthTextureFormat = depthTextureFormat; - - if ( renderTarget.depthTexture !== null ) { - - const depthTextureProperties = properties.get( renderTarget.depthTexture ); - depthTextureProperties.textureGPU = depthTextureGPU; - depthTextureProperties.initializedRTT = false; - - } - - } - - // - - const disposeCallback = onRenderTargetDispose.bind( this ); - renderTargetProperties.disposeCallback = disposeCallback; - - renderTarget.addEventListener( 'dispose', disposeCallback ); - - // - - renderTargetProperties.initialized = true; - - } - - } - - dispose() { - - this.samplerCache.clear(); - - } - - _convertAddressMode( value ) { - - let addressMode = GPUAddressMode.ClampToEdge; - - if ( value === RepeatWrapping ) { - - addressMode = GPUAddressMode.Repeat; - - } else if ( value === MirroredRepeatWrapping ) { - - addressMode = GPUAddressMode.MirrorRepeat; - - } - - return addressMode; - - } - - _convertFilterMode( value ) { - - let filterMode = GPUFilterMode.Linear; - - if ( value === NearestFilter || value === NearestMipmapNearestFilter || value === NearestMipmapLinearFilter ) { - - filterMode = GPUFilterMode.Nearest; - - } - - return filterMode; - - } - - _uploadVideoTexture( texture ) { - - const device = this.device; - - const textureProperties = this.properties.get( texture ); - - const textureGPU = device.importExternalTexture( { - source: texture.source.data - } ); - - textureProperties.textureGPU = textureGPU; - //textureProperties.version = texture.version; // @TODO: Force update for now, study a better solution soon using native VideoTexture.update() to fix warns - - return true; - - } - - _uploadTexture( texture ) { - - let needsUpdate = false; - - const device = this.device; - const image = texture.image; - - const textureProperties = this.properties.get( texture ); - - const { width, height, depth } = this._getSize( texture ); - const needsMipmaps = this._needsMipmaps( texture ); - const dimension = this._getDimension( texture ); - const mipLevelCount = this._getMipLevelCount( texture, width, height, needsMipmaps ); - const format = texture.internalFormat || this._getFormat( texture ); - - let usage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST; - - if ( needsMipmaps ) { - - // current mipmap generation requires RENDER_ATTACHMENT - - usage |= GPUTextureUsage.RENDER_ATTACHMENT; - - } - - const textureGPUDescriptor = { - label: texture.name, - size: { - width: width, - height: height, - depthOrArrayLayers: depth, - }, - mipLevelCount: mipLevelCount, - sampleCount: 1, - dimension: dimension, - format: format, - usage: usage - }; - - // texture creation - - let textureGPU = textureProperties.textureGPU; - - if ( textureGPU === undefined ) { - - textureGPU = device.createTexture( textureGPUDescriptor ); - - needsUpdate = true; - - } - - // transfer texture data - - if ( texture.isDataTexture || texture.isDataArrayTexture || texture.isData3DTexture ) { - - this._copyBufferToTexture( image, textureGPU, textureGPUDescriptor, needsMipmaps ); - - } else if ( texture.isCompressedTexture ) { - - this._copyCompressedBufferToTexture( texture.mipmaps, textureGPU, textureGPUDescriptor ); - - } else if ( texture.isCubeTexture ) { - - if ( image.length === 6 ) { - - this._copyCubeMapToTexture( image, texture, textureGPU, textureGPUDescriptor, needsMipmaps ); - - } - - } else if ( texture.isRenderTargetTexture ) { - - if ( needsMipmaps === true ) this._generateMipmaps( textureGPU, textureGPUDescriptor ); - - } else if ( texture.isDepthTexture !== true && image !== null ) { - - this._copyImageToTexture( image, texture, textureGPU, textureGPUDescriptor, needsMipmaps ); - - } - - // - - textureProperties.textureGPU = textureGPU; - textureProperties.version = texture.version; - - return needsUpdate; - - } - - _copyBufferToTexture( image, textureGPU, textureGPUDescriptor, needsMipmaps, originDepth = 0 ) { - - // @TODO: Consider to use GPUCommandEncoder.copyBufferToTexture() - // @TODO: Consider to support valid buffer layouts with other formats like RGB - - const data = image.data; - - const bytesPerTexel = this._getBytesPerTexel( textureGPUDescriptor.format ); - const bytesPerRow = image.width * bytesPerTexel; - - this.device.queue.writeTexture( - { - texture: textureGPU, - mipLevel: 0, - origin: { x: 0, y: 0, z: originDepth } - }, - data, - { - offset: 0, - bytesPerRow - }, - { - width: image.width, - height: image.height, - depthOrArrayLayers: ( image.depth !== undefined ) ? image.depth : 1 - } ); - - if ( needsMipmaps === true ) this._generateMipmaps( textureGPU, textureGPUDescriptor, originDepth ); - - } - - _copyCubeMapToTexture( images, texture, textureGPU, textureGPUDescriptor, needsMipmaps ) { - - for ( let i = 0; i < 6; i ++ ) { - - const image = images[ i ]; - - if ( image.isDataTexture ) { - - this._copyBufferToTexture( image.image, textureGPU, textureGPUDescriptor, needsMipmaps, i ); - - } else { - - this._copyImageToTexture( image, texture, textureGPU, textureGPUDescriptor, needsMipmaps, i ); - - } - - } - - } - - _copyExternalImageToTexture( image, textureGPU, textureGPUDescriptor, needsMipmaps, originDepth = 0 ) { - - this.device.queue.copyExternalImageToTexture( - { - source: image - }, { - texture: textureGPU, - mipLevel: 0, - origin: { x: 0, y: 0, z: originDepth } - }, { - width: image.width, - height: image.height, - depthOrArrayLayers: 1 - } - ); - - if ( needsMipmaps ) this._generateMipmaps( textureGPU, textureGPUDescriptor, originDepth ); - - } - - _copyCompressedBufferToTexture( mipmaps, textureGPU, textureGPUDescriptor ) { - - // @TODO: Consider to use GPUCommandEncoder.copyBufferToTexture() - - const blockData = this._getBlockData( textureGPUDescriptor.format ); - - for ( let i = 0; i < mipmaps.length; i ++ ) { - - const mipmap = mipmaps[ i ]; - - const width = mipmap.width; - const height = mipmap.height; - - const bytesPerRow = Math.ceil( width / blockData.width ) * blockData.byteLength; - - this.device.queue.writeTexture( - { - texture: textureGPU, - mipLevel: i - }, - mipmap.data, - { - offset: 0, - bytesPerRow - }, - { - width: Math.ceil( width / blockData.width ) * blockData.width, - height: Math.ceil( height / blockData.width ) * blockData.width, - depthOrArrayLayers: 1 - } - ); - - } - - } - - _generateMipmaps( textureGPU, textureGPUDescriptor, baseArrayLayer ) { - - if ( this.utils === null ) { - - this.utils = new WebGPUTextureUtils( this.device ); // only create this helper if necessary - - } - - this.utils.generateMipmaps( textureGPU, textureGPUDescriptor, baseArrayLayer ); - - } - - _getBlockData( format ) { - - // this method is only relevant for compressed texture formats - - if ( format === GPUTextureFormat.BC1RGBAUnorm || format === GPUTextureFormat.BC1RGBAUnormSRGB ) return { byteLength: 8, width: 4, height: 4 }; // DXT1 - if ( format === GPUTextureFormat.BC2RGBAUnorm || format === GPUTextureFormat.BC2RGBAUnormSRGB ) return { byteLength: 16, width: 4, height: 4 }; // DXT3 - if ( format === GPUTextureFormat.BC3RGBAUnorm || format === GPUTextureFormat.BC3RGBAUnormSRGB ) return { byteLength: 16, width: 4, height: 4 }; // DXT5 - if ( format === GPUTextureFormat.BC4RUnorm || format === GPUTextureFormat.BC4RSNorm ) return { byteLength: 8, width: 4, height: 4 }; // RGTC1 - if ( format === GPUTextureFormat.BC5RGUnorm || format === GPUTextureFormat.BC5RGSnorm ) return { byteLength: 16, width: 4, height: 4 }; // RGTC2 - if ( format === GPUTextureFormat.BC6HRGBUFloat || format === GPUTextureFormat.BC6HRGBFloat ) return { byteLength: 16, width: 4, height: 4 }; // BPTC (float) - if ( format === GPUTextureFormat.BC7RGBAUnorm || format === GPUTextureFormat.BC7RGBAUnormSRGB ) return { byteLength: 16, width: 4, height: 4 }; // BPTC (unorm) - - if ( format === GPUTextureFormat.ETC2RGB8Unorm || format === GPUTextureFormat.ETC2RGB8UnormSRGB ) return { byteLength: 8, width: 4, height: 4 }; - if ( format === GPUTextureFormat.ETC2RGB8A1Unorm || format === GPUTextureFormat.ETC2RGB8A1UnormSRGB ) return { byteLength: 8, width: 4, height: 4 }; - if ( format === GPUTextureFormat.ETC2RGBA8Unorm || format === GPUTextureFormat.ETC2RGBA8UnormSRGB ) return { byteLength: 16, width: 4, height: 4 }; - if ( format === GPUTextureFormat.EACR11Unorm ) return { byteLength: 8, width: 4, height: 4 }; - if ( format === GPUTextureFormat.EACR11Snorm ) return { byteLength: 8, width: 4, height: 4 }; - if ( format === GPUTextureFormat.EACRG11Unorm ) return { byteLength: 16, width: 4, height: 4 }; - if ( format === GPUTextureFormat.EACRG11Snorm ) return { byteLength: 16, width: 4, height: 4 }; - - if ( format === GPUTextureFormat.ASTC4x4Unorm || format === GPUTextureFormat.ASTC4x4UnormSRGB ) return { byteLength: 16, width: 4, height: 4 }; - if ( format === GPUTextureFormat.ASTC5x4Unorm || format === GPUTextureFormat.ASTC5x4UnormSRGB ) return { byteLength: 16, width: 5, height: 4 }; - if ( format === GPUTextureFormat.ASTC5x5Unorm || format === GPUTextureFormat.ASTC5x5UnormSRGB ) return { byteLength: 16, width: 5, height: 5 }; - if ( format === GPUTextureFormat.ASTC6x5Unorm || format === GPUTextureFormat.ASTC6x5UnormSRGB ) return { byteLength: 16, width: 6, height: 5 }; - if ( format === GPUTextureFormat.ASTC6x6Unorm || format === GPUTextureFormat.ASTC6x6UnormSRGB ) return { byteLength: 16, width: 6, height: 6 }; - if ( format === GPUTextureFormat.ASTC8x5Unorm || format === GPUTextureFormat.ASTC8x5UnormSRGB ) return { byteLength: 16, width: 8, height: 5 }; - if ( format === GPUTextureFormat.ASTC8x6Unorm || format === GPUTextureFormat.ASTC8x6UnormSRGB ) return { byteLength: 16, width: 8, height: 6 }; - if ( format === GPUTextureFormat.ASTC8x8Unorm || format === GPUTextureFormat.ASTC8x8UnormSRGB ) return { byteLength: 16, width: 8, height: 8 }; - if ( format === GPUTextureFormat.ASTC10x5Unorm || format === GPUTextureFormat.ASTC10x5UnormSRGB ) return { byteLength: 16, width: 10, height: 5 }; - if ( format === GPUTextureFormat.ASTC10x6Unorm || format === GPUTextureFormat.ASTC10x6UnormSRGB ) return { byteLength: 16, width: 10, height: 6 }; - if ( format === GPUTextureFormat.ASTC10x8Unorm || format === GPUTextureFormat.ASTC10x8UnormSRGB ) return { byteLength: 16, width: 10, height: 8 }; - if ( format === GPUTextureFormat.ASTC10x10Unorm || format === GPUTextureFormat.ASTC10x10UnormSRGB ) return { byteLength: 16, width: 10, height: 10 }; - if ( format === GPUTextureFormat.ASTC12x10Unorm || format === GPUTextureFormat.ASTC12x10UnormSRGB ) return { byteLength: 16, width: 12, height: 10 }; - if ( format === GPUTextureFormat.ASTC12x12Unorm || format === GPUTextureFormat.ASTC12x12UnormSRGB ) return { byteLength: 16, width: 12, height: 12 }; - - } - - _getBytesPerTexel( format ) { - - if ( format === GPUTextureFormat.R8Unorm ) return 1; - if ( format === GPUTextureFormat.R16Float ) return 2; - if ( format === GPUTextureFormat.RG8Unorm ) return 2; - if ( format === GPUTextureFormat.RG16Float ) return 4; - if ( format === GPUTextureFormat.R32Float ) return 4; - if ( format === GPUTextureFormat.RGBA8Unorm || format === GPUTextureFormat.RGBA8UnormSRGB ) return 4; - if ( format === GPUTextureFormat.RG32Float ) return 8; - if ( format === GPUTextureFormat.RGBA16Float ) return 8; - if ( format === GPUTextureFormat.RGBA32Float ) return 16; - - } - - _getDimension( texture ) { - - let dimension; - - if ( texture.isData3DTexture ) { - - dimension = GPUTextureDimension.ThreeD; - - } else { - - dimension = GPUTextureDimension.TwoD; - - } - - return dimension; - - } - - _getFormat( texture ) { - - const format = texture.format; - const type = texture.type; - const colorSpace = texture.colorSpace; - - let formatGPU; - - if ( texture.isCompressedTexture === true ) { - - switch ( format ) { - - case RGBA_S3TC_DXT1_Format: - formatGPU = ( colorSpace === SRGBColorSpace ) ? GPUTextureFormat.BC1RGBAUnormSRGB : GPUTextureFormat.BC1RGBAUnorm; - break; - - case RGBA_S3TC_DXT3_Format: - formatGPU = ( colorSpace === SRGBColorSpace ) ? GPUTextureFormat.BC2RGBAUnormSRGB : GPUTextureFormat.BC2RGBAUnorm; - break; - - case RGBA_S3TC_DXT5_Format: - formatGPU = ( colorSpace === SRGBColorSpace ) ? GPUTextureFormat.BC3RGBAUnormSRGB : GPUTextureFormat.BC3RGBAUnorm; - break; - - case RGB_ETC2_Format: - formatGPU = ( colorSpace === SRGBColorSpace ) ? GPUTextureFormat.ETC2RGB8UnormSRGB : GPUTextureFormat.ETC2RGB8Unorm; - break; - - case RGBA_ETC2_EAC_Format: - formatGPU = ( colorSpace === SRGBColorSpace ) ? GPUTextureFormat.ETC2RGBA8UnormSRGB : GPUTextureFormat.ETC2RGBA8Unorm; - break; - - case RGBA_ASTC_4x4_Format: - formatGPU = ( colorSpace === SRGBColorSpace ) ? GPUTextureFormat.ASTC4x4UnormSRGB : GPUTextureFormat.ASTC4x4Unorm; - break; - - case RGBA_ASTC_5x4_Format: - formatGPU = ( colorSpace === SRGBColorSpace ) ? GPUTextureFormat.ASTC5x4UnormSRGB : GPUTextureFormat.ASTC5x4Unorm; - break; - - case RGBA_ASTC_5x5_Format: - formatGPU = ( colorSpace === SRGBColorSpace ) ? GPUTextureFormat.ASTC5x5UnormSRGB : GPUTextureFormat.ASTC5x5Unorm; - break; - - case RGBA_ASTC_6x5_Format: - formatGPU = ( colorSpace === SRGBColorSpace ) ? GPUTextureFormat.ASTC6x5UnormSRGB : GPUTextureFormat.ASTC6x5Unorm; - break; - - case RGBA_ASTC_6x6_Format: - formatGPU = ( colorSpace === SRGBColorSpace ) ? GPUTextureFormat.ASTC6x6UnormSRGB : GPUTextureFormat.ASTC6x6Unorm; - break; - - case RGBA_ASTC_8x5_Format: - formatGPU = ( colorSpace === SRGBColorSpace ) ? GPUTextureFormat.ASTC8x5UnormSRGB : GPUTextureFormat.ASTC8x5Unorm; - break; - - case RGBA_ASTC_8x6_Format: - formatGPU = ( colorSpace === SRGBColorSpace ) ? GPUTextureFormat.ASTC8x6UnormSRGB : GPUTextureFormat.ASTC8x6Unorm; - break; - - case RGBA_ASTC_8x8_Format: - formatGPU = ( colorSpace === SRGBColorSpace ) ? GPUTextureFormat.ASTC8x8UnormSRGB : GPUTextureFormat.ASTC8x8Unorm; - break; - - case RGBA_ASTC_10x5_Format: - formatGPU = ( colorSpace === SRGBColorSpace ) ? GPUTextureFormat.ASTC10x5UnormSRGB : GPUTextureFormat.ASTC10x5Unorm; - break; - - case RGBA_ASTC_10x6_Format: - formatGPU = ( colorSpace === SRGBColorSpace ) ? GPUTextureFormat.ASTC10x6UnormSRGB : GPUTextureFormat.ASTC10x6Unorm; - break; - - case RGBA_ASTC_10x8_Format: - formatGPU = ( colorSpace === SRGBColorSpace ) ? GPUTextureFormat.ASTC10x8UnormSRGB : GPUTextureFormat.ASTC10x8Unorm; - break; - - case RGBA_ASTC_10x10_Format: - formatGPU = ( colorSpace === SRGBColorSpace ) ? GPUTextureFormat.ASTC10x10UnormSRGB : GPUTextureFormat.ASTC10x10Unorm; - break; - - case RGBA_ASTC_12x10_Format: - formatGPU = ( colorSpace === SRGBColorSpace ) ? GPUTextureFormat.ASTC12x10UnormSRGB : GPUTextureFormat.ASTC12x10Unorm; - break; - - case RGBA_ASTC_12x12_Format: - formatGPU = ( colorSpace === SRGBColorSpace ) ? GPUTextureFormat.ASTC12x12UnormSRGB : GPUTextureFormat.ASTC12x12Unorm; - break; - - default: - console.error( 'WebGPURenderer: Unsupported texture format.', format ); - - } - - } else { - - switch ( format ) { - - case RGBAFormat: - - switch ( type ) { - - case UnsignedByteType: - formatGPU = ( colorSpace === SRGBColorSpace ) ? GPUTextureFormat.RGBA8UnormSRGB : GPUTextureFormat.RGBA8Unorm; - break; - - case HalfFloatType: - formatGPU = GPUTextureFormat.RGBA16Float; - break; - - case FloatType: - formatGPU = GPUTextureFormat.RGBA32Float; - break; - - default: - console.error( 'WebGPURenderer: Unsupported texture type with RGBAFormat.', type ); - - } - - break; - - case RedFormat: - - switch ( type ) { - - case UnsignedByteType: - formatGPU = GPUTextureFormat.R8Unorm; - break; - - case HalfFloatType: - formatGPU = GPUTextureFormat.R16Float; - break; - - case FloatType: - formatGPU = GPUTextureFormat.R32Float; - break; - - default: - console.error( 'WebGPURenderer: Unsupported texture type with RedFormat.', type ); - - } - - break; - - case RGFormat: - - switch ( type ) { - - case UnsignedByteType: - formatGPU = GPUTextureFormat.RG8Unorm; - break; - - case HalfFloatType: - formatGPU = GPUTextureFormat.RG16Float; - break; - - case FloatType: - formatGPU = GPUTextureFormat.RG32Float; - break; - - default: - console.error( 'WebGPURenderer: Unsupported texture type with RGFormat.', type ); - - } - - break; - - case DepthFormat: - - switch ( type ) { - - case UnsignedShortType: - formatGPU = GPUTextureFormat.Depth16Unorm; - break; - - case UnsignedIntType: - formatGPU = GPUTextureFormat.Depth24Plus; - break; - - case FloatType: - formatGPU = GPUTextureFormat.Depth32Float; - break; - - default: - console.error( 'WebGPURenderer: Unsupported texture type with DepthFormat.', type ); - - } - - break; - - case DepthStencilFormat: - - switch ( type ) { - - case UnsignedInt248Type: - formatGPU = GPUTextureFormat.Depth24PlusStencil8; - break; - - case FloatType: - - if ( this.device.features.has( GPUFeatureName.Depth32FloatStencil8 ) === false ) { - - console.error( 'WebGPURenderer: Depth textures with DepthStencilFormat + FloatType can only be used with the "depth32float-stencil8" GPU feature.' ); - - } - - formatGPU = GPUTextureFormat.Depth32FloatStencil8; - - break; - - default: - console.error( 'WebGPURenderer: Unsupported texture type with DepthStencilFormat.', type ); - - } - - break; - - default: - console.error( 'WebGPURenderer: Unsupported texture format.', format ); - - } - - } - - return formatGPU; - - } - - _isHTMLImage( image ) { - - return ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ); - - } - - _copyImageToTexture( image, texture, textureGPU, textureGPUDescriptor, needsMipmaps, originDepth ) { - - if ( this._isHTMLImage( image ) ) { - - this._getImageBitmapFromHTML( image, texture ).then( imageBitmap => { - - this._copyExternalImageToTexture( imageBitmap, textureGPU, textureGPUDescriptor, needsMipmaps, originDepth ); - - } ); - - } else { - - // assume ImageBitmap - - this._copyExternalImageToTexture( image, textureGPU, textureGPUDescriptor, needsMipmaps, originDepth ); - - } - - } - - _getImageBitmapFromHTML( image, texture ) { - - const width = image.width; - const height = image.height; - - const options = {}; - - options.imageOrientation = ( texture.flipY === true ) ? 'flipY' : 'none'; - options.premultiplyAlpha = ( texture.premultiplyAlpha === true ) ? 'premultiply' : 'default'; - - return createImageBitmap( image, 0, 0, width, height, options ); - - } - - _getImageBitmap( image, texture ) { - - const width = image.width; - const height = image.height; - - if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || - ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) ) { - - const options = {}; - - options.imageOrientation = ( texture.flipY === true ) ? 'flipY' : 'none'; - options.premultiplyAlpha = ( texture.premultiplyAlpha === true ) ? 'premultiply' : 'default'; - - return createImageBitmap( image, 0, 0, width, height, options ); - - } else { - - // assume ImageBitmap - - return Promise.resolve( image ); - - } - - } - - _getMipLevelCount( texture, width, height, needsMipmaps ) { - - let mipLevelCount; - - if ( texture.isCompressedTexture ) { - - mipLevelCount = texture.mipmaps.length; - - } else if ( needsMipmaps ) { - - mipLevelCount = Math.floor( Math.log2( Math.max( width, height ) ) ) + 1; - - } else { - - mipLevelCount = 1; // a texture without mipmaps has a base mip (mipLevel 0) - - } - - return mipLevelCount; - - } - - _getSize( texture ) { - - const image = texture.image; - - let width, height, depth; - - if ( texture.isCubeTexture ) { - - const faceImage = image.length > 0 ? image[ 0 ].image || image[ 0 ] : null; - - width = faceImage ? faceImage.width : 1; - height = faceImage ? faceImage.height : 1; - depth = 6; // one image for each side of the cube map - - } else if ( image !== null ) { - - width = image.width; - height = image.height; - depth = ( image.depth !== undefined ) ? image.depth : 1; - - } else { - - width = height = depth = 1; - - } - - return { width, height, depth }; - - } - - _needsMipmaps( texture ) { - - return ( texture.isCompressedTexture !== true ) && ( texture.generateMipmaps === true ) && ( texture.minFilter !== NearestFilter ) && ( texture.minFilter !== LinearFilter ); - - } - -} - -function onRenderTargetDispose( event ) { - - const renderTarget = event.target; - const properties = this.properties; - - const renderTargetProperties = properties.get( renderTarget ); - - renderTarget.removeEventListener( 'dispose', renderTargetProperties.disposeCallback ); - - renderTargetProperties.colorTextureGPU.destroy(); - properties.remove( renderTarget.texture ); - - this.info.memory.textures --; - - if ( renderTarget.depthBuffer === true ) { - - renderTargetProperties.depthTextureGPU.destroy(); - - this.info.memory.textures --; - - if ( renderTarget.depthTexture !== null ) { - - properties.remove( renderTarget.depthTexture ); - - } - - } - - properties.remove( renderTarget ); - -} - -function onTextureDispose( event ) { - - const texture = event.target; - - const textureProperties = this.properties.get( texture ); - textureProperties.textureGPU.destroy(); - - texture.removeEventListener( 'dispose', textureProperties.disposeCallback ); - - this.properties.remove( texture ); - - this.info.memory.textures --; - -} - -export default WebGPUTextures; diff --git a/examples/jsm/renderers/webgpu/WebGPUUniform.js b/examples/jsm/renderers/webgpu/WebGPUUniform.js deleted file mode 100644 index 6ca6afb71cbeeb..00000000000000 --- a/examples/jsm/renderers/webgpu/WebGPUUniform.js +++ /dev/null @@ -1,136 +0,0 @@ -import { Color, Matrix3, Matrix4, Vector2, Vector3, Vector4 } from 'three'; - -class WebGPUUniform { - - constructor( name, value = null ) { - - this.name = name; - this.value = value; - - this.boundary = 0; // used to build the uniform buffer according to the STD140 layout - this.itemSize = 0; - - this.offset = 0; // this property is set by WebGPUUniformsGroup and marks the start position in the uniform buffer - - } - - setValue( value ) { - - this.value = value; - - } - - getValue() { - - return this.value; - - } - -} - -class FloatUniform extends WebGPUUniform { - - constructor( name, value = 0 ) { - - super( name, value ); - - this.isFloatUniform = true; - - this.boundary = 4; - this.itemSize = 1; - - } - -} - -class Vector2Uniform extends WebGPUUniform { - - constructor( name, value = new Vector2() ) { - - super( name, value ); - - this.isVector2Uniform = true; - - this.boundary = 8; - this.itemSize = 2; - - } - -} - -class Vector3Uniform extends WebGPUUniform { - - constructor( name, value = new Vector3() ) { - - super( name, value ); - - this.isVector3Uniform = true; - - this.boundary = 16; - this.itemSize = 3; - - } - -} - -class Vector4Uniform extends WebGPUUniform { - - constructor( name, value = new Vector4() ) { - - super( name, value ); - - this.isVector4Uniform = true; - - this.boundary = 16; - this.itemSize = 4; - - } - -} - -class ColorUniform extends WebGPUUniform { - - constructor( name, value = new Color() ) { - - super( name, value ); - - this.isColorUniform = true; - - this.boundary = 16; - this.itemSize = 3; - - } - -} - -class Matrix3Uniform extends WebGPUUniform { - - constructor( name, value = new Matrix3() ) { - - super( name, value ); - - this.isMatrix3Uniform = true; - - this.boundary = 48; - this.itemSize = 12; - - } - -} - -class Matrix4Uniform extends WebGPUUniform { - - constructor( name, value = new Matrix4() ) { - - super( name, value ); - - this.isMatrix4Uniform = true; - - this.boundary = 64; - this.itemSize = 16; - - } - -} - -export { FloatUniform, Vector2Uniform, Vector3Uniform, Vector4Uniform, ColorUniform, Matrix3Uniform, Matrix4Uniform }; diff --git a/examples/jsm/renderers/webgpu/WebGPUUniformBuffer.js b/examples/jsm/renderers/webgpu/WebGPUUniformBuffer.js deleted file mode 100644 index 8ce93aa6938426..00000000000000 --- a/examples/jsm/renderers/webgpu/WebGPUUniformBuffer.js +++ /dev/null @@ -1,18 +0,0 @@ -import WebGPUBuffer from './WebGPUBuffer.js'; -import { GPUBindingType } from './constants.js'; - -class WebGPUUniformBuffer extends WebGPUBuffer { - - constructor( name, buffer = null ) { - - super( name, GPUBindingType.UniformBuffer, buffer ); - - this.isUniformBuffer = true; - - this.usage |= GPUBufferUsage.UNIFORM; - - } - -} - -export default WebGPUUniformBuffer; diff --git a/examples/jsm/renderers/webgpu/WebGPUUniformsGroup.js b/examples/jsm/renderers/webgpu/WebGPUUniformsGroup.js deleted file mode 100644 index af376d87d1f0da..00000000000000 --- a/examples/jsm/renderers/webgpu/WebGPUUniformsGroup.js +++ /dev/null @@ -1,299 +0,0 @@ -import WebGPUUniformBuffer from './WebGPUUniformBuffer.js'; -import { GPUChunkSize } from './constants.js'; - -class WebGPUUniformsGroup extends WebGPUUniformBuffer { - - constructor( name ) { - - super( name ); - - this.isUniformsGroup = true; - - // the order of uniforms in this array must match the order of uniforms in the shader - - this.uniforms = []; - - } - - addUniform( uniform ) { - - this.uniforms.push( uniform ); - - return this; - - } - - removeUniform( uniform ) { - - const index = this.uniforms.indexOf( uniform ); - - if ( index !== - 1 ) { - - this.uniforms.splice( index, 1 ); - - } - - return this; - - } - - getBuffer() { - - let buffer = this.buffer; - - if ( buffer === null ) { - - const byteLength = this.getByteLength(); - - buffer = new Float32Array( new ArrayBuffer( byteLength ) ); - - this.buffer = buffer; - - } - - return buffer; - - } - - getByteLength() { - - let offset = 0; // global buffer offset in bytes - - for ( let i = 0, l = this.uniforms.length; i < l; i ++ ) { - - const uniform = this.uniforms[ i ]; - - // offset within a single chunk in bytes - - const chunkOffset = offset % GPUChunkSize; - const remainingSizeInChunk = GPUChunkSize - chunkOffset; - - // conformance tests - - if ( chunkOffset !== 0 && ( remainingSizeInChunk - uniform.boundary ) < 0 ) { - - // check for chunk overflow - - offset += ( GPUChunkSize - chunkOffset ); - - } else if ( chunkOffset % uniform.boundary !== 0 ) { - - // check for correct alignment - - offset += ( chunkOffset % uniform.boundary ); - - } - - uniform.offset = ( offset / this.bytesPerElement ); - - offset += ( uniform.itemSize * this.bytesPerElement ); - - } - - return Math.ceil( offset / GPUChunkSize ) * GPUChunkSize; - - } - - update() { - - let updated = false; - - for ( const uniform of this.uniforms ) { - - if ( this.updateByType( uniform ) === true ) { - - updated = true; - - } - - } - - return updated; - - } - - updateByType( uniform ) { - - if ( uniform.isFloatUniform ) return this.updateNumber( uniform ); - if ( uniform.isVector2Uniform ) return this.updateVector2( uniform ); - if ( uniform.isVector3Uniform ) return this.updateVector3( uniform ); - if ( uniform.isVector4Uniform ) return this.updateVector4( uniform ); - if ( uniform.isColorUniform ) return this.updateColor( uniform ); - if ( uniform.isMatrix3Uniform ) return this.updateMatrix3( uniform ); - if ( uniform.isMatrix4Uniform ) return this.updateMatrix4( uniform ); - - console.error( 'THREE.WebGPUUniformsGroup: Unsupported uniform type.', uniform ); - - } - - updateNumber( uniform ) { - - let updated = false; - - const a = this.buffer; - const v = uniform.getValue(); - const offset = uniform.offset; - - if ( a[ offset ] !== v ) { - - a[ offset ] = v; - updated = true; - - } - - return updated; - - } - - updateVector2( uniform ) { - - let updated = false; - - const a = this.buffer; - const v = uniform.getValue(); - const offset = uniform.offset; - - if ( a[ offset + 0 ] !== v.x || a[ offset + 1 ] !== v.y ) { - - a[ offset + 0 ] = v.x; - a[ offset + 1 ] = v.y; - - updated = true; - - } - - return updated; - - } - - updateVector3( uniform ) { - - let updated = false; - - const a = this.buffer; - const v = uniform.getValue(); - const offset = uniform.offset; - - if ( a[ offset + 0 ] !== v.x || a[ offset + 1 ] !== v.y || a[ offset + 2 ] !== v.z ) { - - a[ offset + 0 ] = v.x; - a[ offset + 1 ] = v.y; - a[ offset + 2 ] = v.z; - - updated = true; - - } - - return updated; - - } - - updateVector4( uniform ) { - - let updated = false; - - const a = this.buffer; - const v = uniform.getValue(); - const offset = uniform.offset; - - if ( a[ offset + 0 ] !== v.x || a[ offset + 1 ] !== v.y || a[ offset + 2 ] !== v.z || a[ offset + 4 ] !== v.w ) { - - a[ offset + 0 ] = v.x; - a[ offset + 1 ] = v.y; - a[ offset + 2 ] = v.z; - a[ offset + 3 ] = v.w; - - updated = true; - - } - - return updated; - - } - - updateColor( uniform ) { - - let updated = false; - - const a = this.buffer; - const c = uniform.getValue(); - const offset = uniform.offset; - - if ( a[ offset + 0 ] !== c.r || a[ offset + 1 ] !== c.g || a[ offset + 2 ] !== c.b ) { - - a[ offset + 0 ] = c.r; - a[ offset + 1 ] = c.g; - a[ offset + 2 ] = c.b; - - updated = true; - - } - - return updated; - - } - - updateMatrix3( uniform ) { - - let updated = false; - - const a = this.buffer; - const e = uniform.getValue().elements; - const offset = uniform.offset; - - if ( a[ offset + 0 ] !== e[ 0 ] || a[ offset + 1 ] !== e[ 1 ] || a[ offset + 2 ] !== e[ 2 ] || - a[ offset + 4 ] !== e[ 3 ] || a[ offset + 5 ] !== e[ 4 ] || a[ offset + 6 ] !== e[ 5 ] || - a[ offset + 8 ] !== e[ 6 ] || a[ offset + 9 ] !== e[ 7 ] || a[ offset + 10 ] !== e[ 8 ] ) { - - a[ offset + 0 ] = e[ 0 ]; - a[ offset + 1 ] = e[ 1 ]; - a[ offset + 2 ] = e[ 2 ]; - a[ offset + 4 ] = e[ 3 ]; - a[ offset + 5 ] = e[ 4 ]; - a[ offset + 6 ] = e[ 5 ]; - a[ offset + 8 ] = e[ 6 ]; - a[ offset + 9 ] = e[ 7 ]; - a[ offset + 10 ] = e[ 8 ]; - - updated = true; - - } - - return updated; - - } - - updateMatrix4( uniform ) { - - let updated = false; - - const a = this.buffer; - const e = uniform.getValue().elements; - const offset = uniform.offset; - - if ( arraysEqual( a, e, offset ) === false ) { - - a.set( e, offset ); - updated = true; - - } - - return updated; - - } - -} - -function arraysEqual( a, b, offset ) { - - for ( let i = 0, l = b.length; i < l; i ++ ) { - - if ( a[ offset + i ] !== b[ i ] ) return false; - - } - - return true; - -} - -export default WebGPUUniformsGroup; diff --git a/examples/jsm/renderers/webgpu/WebGPUUtils.js b/examples/jsm/renderers/webgpu/WebGPUUtils.js deleted file mode 100644 index 800bd3eae25707..00000000000000 --- a/examples/jsm/renderers/webgpu/WebGPUUtils.js +++ /dev/null @@ -1,88 +0,0 @@ -import { GPUPrimitiveTopology, GPUTextureFormat } from './constants.js'; - -class WebGPUUtils { - - constructor( renderer ) { - - this.renderer = renderer; - - } - - getCurrentColorSpace() { - - const renderer = this.renderer; - - const renderTarget = renderer.getRenderTarget(); - - if ( renderTarget !== null ) { - - return renderTarget.texture.colorSpace; - - } - - return renderer.outputColorSpace; - - } - - getCurrentColorFormat() { - - let format; - - const renderer = this.renderer; - const renderTarget = renderer.getRenderTarget(); - - if ( renderTarget !== null ) { - - const renderTargetProperties = renderer._properties.get( renderTarget ); - format = renderTargetProperties.colorTextureFormat; - - } else { - - format = GPUTextureFormat.BGRA8Unorm; // default context format - - } - - return format; - - } - - getCurrentDepthStencilFormat() { - - let format; - - const renderer = this.renderer; - const renderTarget = renderer.getRenderTarget(); - - if ( renderTarget !== null ) { - - const renderTargetProperties = renderer._properties.get( renderTarget ); - format = renderTargetProperties.depthTextureFormat; - - } else { - - format = GPUTextureFormat.Depth24PlusStencil8; - - } - - return format; - - } - - getPrimitiveTopology( object, material ) { - - if ( object.isPoints ) return GPUPrimitiveTopology.PointList; - else if ( object.isLineSegments || ( object.isMesh && material.wireframe === true ) ) return GPUPrimitiveTopology.LineList; - else if ( object.isLine ) return GPUPrimitiveTopology.LineStrip; - else if ( object.isMesh ) return GPUPrimitiveTopology.TriangleList; - - } - - getSampleCount() { - - return this.renderer._parameters.sampleCount; - - } - -} - -export default WebGPUUtils; diff --git a/examples/jsm/renderers/webgpu/WebGPUWeakMap.js b/examples/jsm/renderers/webgpu/WebGPUWeakMap.js deleted file mode 100644 index 62a77748eddd2a..00000000000000 --- a/examples/jsm/renderers/webgpu/WebGPUWeakMap.js +++ /dev/null @@ -1,83 +0,0 @@ -export default class WebGPUWeakMap extends WeakMap { - - constructor() { - - super(); - - } - - get( keys ) { - - if ( Array.isArray( keys ) ) { - - let map = this; - - for ( let i = 0; i < keys.length - 1; i ++ ) { - - map = map.get( keys[ i ] ); - - if ( map === undefined ) return undefined; - - } - - return map.get( keys[ keys.length - 1 ] ); - - } else { - - return super.get( keys ); - - } - - } - - set( keys, value ) { - - if ( Array.isArray( keys ) ) { - - let map = this; - - for ( let i = 0; i < keys.length - 1; i ++ ) { - - const key = keys[ i ]; - - if ( map.has( key ) === false ) map.set( key, new WeakMap() ); - - map = map.get( key ); - - } - - return map.set( keys[ keys.length - 1 ], value ); - - } else { - - return super.set( keys, value ); - - } - - } - - delete( keys ) { - - if ( Array.isArray( keys ) ) { - - let map = this; - - for ( let i = 0; i < keys.length - 1; i ++ ) { - - map = map.get( keys[ i ] ); - - if ( map === undefined ) return false; - - } - - return map.delete( keys[ keys.length - 1 ] ); - - } else { - - return super.delete( keys ); - - } - - } - -} diff --git a/examples/jsm/renderers/webgpu/constants.js b/examples/jsm/renderers/webgpu/constants.js deleted file mode 100644 index 96b929a3a2137b..00000000000000 --- a/examples/jsm/renderers/webgpu/constants.js +++ /dev/null @@ -1,323 +0,0 @@ -export const GPUPrimitiveTopology = { - PointList: 'point-list', - LineList: 'line-list', - LineStrip: 'line-strip', - TriangleList: 'triangle-list', - TriangleStrip: 'triangle-strip', -}; - -export const GPUCompareFunction = { - Never: 'never', - Less: 'less', - Equal: 'equal', - LessEqual: 'less-equal', - Greater: 'greater', - NotEqual: 'not-equal', - GreaterEqual: 'greater-equal', - Always: 'always' -}; - -export const GPUStoreOp = { - Store: 'store', - Discard: 'discard' -}; - -export const GPULoadOp = { - Load: 'load', - Clear: 'clear' -}; - -export const GPUFrontFace = { - CCW: 'ccw', - CW: 'cw' -}; - -export const GPUCullMode = { - None: 'none', - Front: 'front', - Back: 'back' -}; - -export const GPUIndexFormat = { - Uint16: 'uint16', - Uint32: 'uint32' -}; - -export const GPUVertexFormat = { - Uint8x2: 'uint8x2', - Uint8x4: 'uint8x4', - Sint8x2: 'sint8x2', - Sint8x4: 'sint8x4', - Unorm8x2: 'unorm8x2', - Unorm8x4: 'unorm8x4', - Snorm8x2: 'snorm8x2', - Snorm8x4: 'snorm8x4', - Uint16x2: 'uint16x2', - Uint16x4: 'uint16x4', - Sint16x2: 'sint16x2', - Sint16x4: 'sint16x4', - Unorm16x2: 'unorm16x2', - Unorm16x4: 'unorm16x4', - Snorm16x2: 'snorm16x2', - Snorm16x4: 'snorm16x4', - Float16x2: 'float16x2', - Float16x4: 'float16x4', - Float32: 'float32', - Float32x2: 'float32x2', - Float32x3: 'float32x3', - Float32x4: 'float32x4', - Uint32: 'uint32', - Uint32x2: 'uint32x2', - Uint32x3: 'uint32x3', - Uint32x4: 'uint32x4', - Sint32: 'sint32', - Sint32x2: 'sint32x2', - Sint32x3: 'sint32x3', - Sint32x4: 'sint32x4' -}; - -export const GPUTextureFormat = { - - // 8-bit formats - - R8Unorm: 'r8unorm', - R8Snorm: 'r8snorm', - R8Uint: 'r8uint', - R8Sint: 'r8sint', - - // 16-bit formats - - R16Uint: 'r16uint', - R16Sint: 'r16sint', - R16Float: 'r16float', - RG8Unorm: 'rg8unorm', - RG8Snorm: 'rg8snorm', - RG8Uint: 'rg8uint', - RG8Sint: 'rg8sint', - - // 32-bit formats - - R32Uint: 'r32uint', - R32Sint: 'r32sint', - R32Float: 'r32float', - RG16Uint: 'rg16uint', - RG16Sint: 'rg16sint', - RG16Float: 'rg16float', - RGBA8Unorm: 'rgba8unorm', - RGBA8UnormSRGB: 'rgba8unorm-srgb', - RGBA8Snorm: 'rgba8snorm', - RGBA8Uint: 'rgba8uint', - RGBA8Sint: 'rgba8sint', - BGRA8Unorm: 'bgra8unorm', - BGRA8UnormSRGB: 'bgra8unorm-srgb', - // Packed 32-bit formats - RGB9E5UFloat: 'rgb9e5ufloat', - RGB10A2Unorm: 'rgb10a2unorm', - RG11B10uFloat: 'rgb10a2unorm', - - // 64-bit formats - - RG32Uint: 'rg32uint', - RG32Sint: 'rg32sint', - RG32Float: 'rg32float', - RGBA16Uint: 'rgba16uint', - RGBA16Sint: 'rgba16sint', - RGBA16Float: 'rgba16float', - - // 128-bit formats - - RGBA32Uint: 'rgba32uint', - RGBA32Sint: 'rgba32sint', - RGBA32Float: 'rgba32float', - - // Depth and stencil formats - - Stencil8: 'stencil8', - Depth16Unorm: 'depth16unorm', - Depth24Plus: 'depth24plus', - Depth24PlusStencil8: 'depth24plus-stencil8', - Depth32Float: 'depth32float', - - // 'depth32float-stencil8' extension - - Depth32FloatStencil8: 'depth32float-stencil8', - - // BC compressed formats usable if 'texture-compression-bc' is both - // supported by the device/user agent and enabled in requestDevice. - - BC1RGBAUnorm: 'bc1-rgba-unorm', - BC1RGBAUnormSRGB: 'bc1-rgba-unorm-srgb', - BC2RGBAUnorm: 'bc2-rgba-unorm', - BC2RGBAUnormSRGB: 'bc2-rgba-unorm-srgb', - BC3RGBAUnorm: 'bc3-rgba-unorm', - BC3RGBAUnormSRGB: 'bc3-rgba-unorm-srgb', - BC4RUnorm: 'bc4-r-unorm', - BC4RSnorm: 'bc4-r-snorm', - BC5RGUnorm: 'bc5-rg-unorm', - BC5RGSnorm: 'bc5-rg-snorm', - BC6HRGBUFloat: 'bc6h-rgb-ufloat', - BC6HRGBFloat: 'bc6h-rgb-float', - BC7RGBAUnorm: 'bc7-rgba-unorm', - BC7RGBAUnormSRGB: 'bc7-rgba-srgb', - - // ETC2 compressed formats usable if 'texture-compression-etc2' is both - // supported by the device/user agent and enabled in requestDevice. - - ETC2RGB8Unorm: 'etc2-rgb8unorm', - ETC2RGB8UnormSRGB: 'etc2-rgb8unorm-srgb', - ETC2RGB8A1Unorm: 'etc2-rgb8a1unorm', - ETC2RGB8A1UnormSRGB: 'etc2-rgb8a1unorm-srgb', - ETC2RGBA8Unorm: 'etc2-rgba8unorm', - ETC2RGBA8UnormSRGB: 'etc2-rgba8unorm-srgb', - EACR11Unorm: 'eac-r11unorm', - EACR11Snorm: 'eac-r11snorm', - EACRG11Unorm: 'eac-rg11unorm', - EACRG11Snorm: 'eac-rg11snorm', - - // ASTC compressed formats usable if 'texture-compression-astc' is both - // supported by the device/user agent and enabled in requestDevice. - - ASTC4x4Unorm: 'astc-4x4-unorm', - ASTC4x4UnormSRGB: 'astc-4x4-unorm-srgb', - ASTC5x4Unorm: 'astc-5x4-unorm', - ASTC5x4UnormSRGB: 'astc-5x4-unorm-srgb', - ASTC5x5Unorm: 'astc-5x5-unorm', - ASTC5x5UnormSRGB: 'astc-5x5-unorm-srgb', - ASTC6x5Unorm: 'astc-6x5-unorm', - ASTC6x5UnormSRGB: 'astc-6x5-unorm-srgb', - ASTC6x6Unorm: 'astc-6x6-unorm', - ASTC6x6UnormSRGB: 'astc-6x6-unorm-srgb', - ASTC8x5Unorm: 'astc-8x5-unorm', - ASTC8x5UnormSRGB: 'astc-8x5-unorm-srgb', - ASTC8x6Unorm: 'astc-8x6-unorm', - ASTC8x6UnormSRGB: 'astc-8x6-unorm-srgb', - ASTC8x8Unorm: 'astc-8x8-unorm', - ASTC8x8UnormSRGB: 'astc-8x8-unorm-srgb', - ASTC10x5Unorm: 'astc-10x5-unorm', - ASTC10x5UnormSRGB: 'astc-10x5-unorm-srgb', - ASTC10x6Unorm: 'astc-10x6-unorm', - ASTC10x6UnormSRGB: 'astc-10x6-unorm-srgb', - ASTC10x8Unorm: 'astc-10x8-unorm', - ASTC10x8UnormSRGB: 'astc-10x8-unorm-srgb', - ASTC10x10Unorm: 'astc-10x10-unorm', - ASTC10x10UnormSRGB: 'astc-10x10-unorm-srgb', - ASTC12x10Unorm: 'astc-12x10-unorm', - ASTC12x10UnormSRGB: 'astc-12x10-unorm-srgb', - ASTC12x12Unorm: 'astc-12x12-unorm', - ASTC12x12UnormSRGB: 'astc-12x12-unorm-srgb', - -}; - -export const GPUAddressMode = { - ClampToEdge: 'clamp-to-edge', - Repeat: 'repeat', - MirrorRepeat: 'mirror-repeat' -}; - -export const GPUFilterMode = { - Linear: 'linear', - Nearest: 'nearest' -}; - -export const GPUBlendFactor = { - Zero: 'zero', - One: 'one', - SrcColor: 'src-color', - OneMinusSrcColor: 'one-minus-src-color', - SrcAlpha: 'src-alpha', - OneMinusSrcAlpha: 'one-minus-src-alpha', - DstColor: 'dst-color', - OneMinusDstColor: 'one-minus-dst-color', - DstAlpha: 'dst-alpha', - OneMinusDstAlpha: 'one-minus-dst-alpha', - SrcAlphaSaturated: 'src-alpha-saturated', - BlendColor: 'blend-color', - OneMinusBlendColor: 'one-minus-blend-color' -}; - -export const GPUBlendOperation = { - Add: 'add', - Subtract: 'subtract', - ReverseSubtract: 'reverse-subtract', - Min: 'min', - Max: 'max' -}; - -export const GPUColorWriteFlags = { - None: 0, - Red: 0x1, - Green: 0x2, - Blue: 0x4, - Alpha: 0x8, - All: 0xF -}; - -export const GPUStencilOperation = { - Keep: 'keep', - Zero: 'zero', - Replace: 'replace', - Invert: 'invert', - IncrementClamp: 'increment-clamp', - DecrementClamp: 'decrement-clamp', - IncrementWrap: 'increment-wrap', - DecrementWrap: 'decrement-wrap' -}; - -export const GPUBindingType = { - UniformBuffer: 'uniform-buffer', - StorageBuffer: 'storage-buffer', - ReadonlyStorageBuffer: 'readonly-storage-buffer', - Sampler: 'sampler', - ComparisonSampler: 'comparison-sampler', - SampledTexture: 'sampled-texture', - MultisampledTexture: 'multisampled-texture', - ReadonlyStorageTexture: 'readonly-storage-texture', - WriteonlyStorageTexture: 'writeonly-storage-texture' -}; - -export const GPUTextureDimension = { - OneD: '1d', - TwoD: '2d', - ThreeD: '3d' -}; - -export const GPUTextureViewDimension = { - OneD: '1d', - TwoD: '2d', - TwoDArray: '2d-array', - Cube: 'cube', - CubeArray: 'cube-array', - ThreeD: '3d' -}; - -export const GPUTextureAspect = { - All: 'all', - StencilOnly: 'stencil-only', - DepthOnly: 'depth-only' -}; - -export const GPUInputStepMode = { - Vertex: 'vertex', - Instance: 'instance' -}; - -export const GPUFeatureName = { - DepthClipControl: 'depth-clip-control', - Depth32FloatStencil8: 'depth32float-stencil8', - TextureCompressionBC: 'texture-compression-bc', - TextureCompressionETC2: 'texture-compression-etc2', - TextureCompressionASTC: 'texture-compression-astc', - TimestampQuery: 'timestamp-query', - IndirectFirstInstance: 'indirect-first-instance', - ShaderF16: 'shader-f16', - RG11B10UFloat: 'rg11b10ufloat-renderable', - BGRA8UNormStorage: 'bgra8unorm-storage', - Float32Filterable: 'float32-filterable' -}; - -export const GPUChunkSize = 16; // size of a chunk in bytes (STD140 layout) - -// @TODO: Move to src/constants.js - -export const BlendColorFactor = 211; -export const OneMinusBlendColorFactor = 212; diff --git a/examples/jsm/renderers/webgpu/nodes/WebGPUNodeBuilder.js b/examples/jsm/renderers/webgpu/nodes/WebGPUNodeBuilder.js deleted file mode 100644 index 33293c613bddcd..00000000000000 --- a/examples/jsm/renderers/webgpu/nodes/WebGPUNodeBuilder.js +++ /dev/null @@ -1,858 +0,0 @@ -import WebGPUUniformsGroup from '../WebGPUUniformsGroup.js'; -import { - FloatNodeUniform, Vector2NodeUniform, Vector3NodeUniform, Vector4NodeUniform, - ColorNodeUniform, Matrix3NodeUniform, Matrix4NodeUniform -} from './WebGPUNodeUniform.js'; -import WebGPUNodeSampler from './WebGPUNodeSampler.js'; -import { WebGPUNodeSampledTexture, WebGPUNodeSampledCubeTexture } from './WebGPUNodeSampledTexture.js'; - -import WebGPUUniformBuffer from '../WebGPUUniformBuffer.js'; -import WebGPUStorageBuffer from '../WebGPUStorageBuffer.js'; -import { getVectorLength, getStrideLength } from '../WebGPUBufferUtils.js'; - -import WebGPURenderTarget from '../WebGPURenderTarget.js'; - -import { NodeBuilder, WGSLNodeParser, CodeNode, NodeMaterial } from '../../../nodes/Nodes.js'; - -const gpuShaderStageLib = { - 'vertex': GPUShaderStage.VERTEX, - 'fragment': GPUShaderStage.FRAGMENT, - 'compute': GPUShaderStage.COMPUTE -}; - -const supports = { - instance: true -}; - -const wgslTypeLib = { - float: 'f32', - int: 'i32', - uint: 'u32', - bool: 'bool', - color: 'vec3', - - vec2: 'vec2', - ivec2: 'vec2', - uvec2: 'vec2', - bvec2: 'vec2', - - vec3: 'vec3', - ivec3: 'vec3', - uvec3: 'vec3', - bvec3: 'vec3', - - vec4: 'vec4', - ivec4: 'vec4', - uvec4: 'vec4', - bvec4: 'vec4', - - mat3: 'mat3x3', - imat3: 'mat3x3', - umat3: 'mat3x3', - bmat3: 'mat3x3', - - mat4: 'mat4x4', - imat4: 'mat4x4', - umat4: 'mat4x4', - bmat4: 'mat4x4' -}; - -const wgslMethods = { - dFdx: 'dpdx', - dFdy: 'dpdy', - mod: 'threejs_mod', - lessThanEqual: 'threejs_lessThanEqual', - inversesqrt: 'inverseSqrt' -}; - -const wgslPolyfill = { - lessThanEqual: new CodeNode( ` -fn threejs_lessThanEqual( a : vec3, b : vec3 ) -> vec3 { - - return vec3( a.x <= b.x, a.y <= b.y, a.z <= b.z ); - -} -` ), - mod: new CodeNode( ` -fn threejs_mod( x : f32, y : f32 ) -> f32 { - - return x - y * floor( x / y ); - -} -` ), - repeatWrapping: new CodeNode( ` -fn threejs_repeatWrapping( uv : vec2, dimension : vec2 ) -> vec2 { - - let uvScaled = vec2( uv * vec2( dimension ) ); - - return ( ( uvScaled % dimension ) + dimension ) % dimension; - -} -` ) -}; - -class WebGPUNodeBuilder extends NodeBuilder { - - constructor( object, renderer ) { - - super( object, renderer, new WGSLNodeParser() ); - - this.bindings = { vertex: [], fragment: [], compute: [] }; - this.bindingsOffset = { vertex: 0, fragment: 0, compute: 0 }; - - this.uniformsGroup = {}; - - this.builtins = { - vertex: new Map(), - fragment: new Map(), - compute: new Map(), - attribute: new Map() - }; - - } - - build() { - - const { object, material } = this; - - if ( material !== null ) { - - NodeMaterial.fromMaterial( material ).build( this ); - - } else { - - this.addFlow( 'compute', object ); - - } - - return super.build(); - - } - - getSampler( textureProperty, uvSnippet, shaderStage = this.shaderStage ) { - - if ( shaderStage === 'fragment' ) { - - return `textureSample( ${textureProperty}, ${textureProperty}_sampler, ${uvSnippet} )`; - - } else { - - this._include( 'repeatWrapping' ); - - const dimension = `textureDimensions( ${textureProperty}, 0 )`; - - return `textureLoad( ${textureProperty}, threejs_repeatWrapping( ${uvSnippet}, ${dimension} ), 0 )`; - - } - - } - - getVideoSampler( textureProperty, uvSnippet, shaderStage = this.shaderStage ) { - - if ( shaderStage === 'fragment' ) { - - return `textureSampleBaseClampToEdge( ${textureProperty}, ${textureProperty}_sampler, vec2( ${uvSnippet}.x, 1.0 - ${uvSnippet}.y ) )`; - - } else { - - console.error( `WebGPURenderer: THREE.VideoTexture does not support ${ shaderStage } shader.` ); - - } - - } - - getSamplerLevel( textureProperty, uvSnippet, biasSnippet, shaderStage = this.shaderStage ) { - - if ( shaderStage === 'fragment' ) { - - return `textureSampleLevel( ${textureProperty}, ${textureProperty}_sampler, ${uvSnippet}, ${biasSnippet} )`; - - } else { - - this._include( 'repeatWrapping' ); - - const dimension = `textureDimensions( ${textureProperty}, 0 )`; - - return `textureLoad( ${textureProperty}, threejs_repeatWrapping( ${uvSnippet}, ${dimension} ), i32( ${biasSnippet} ) )`; - - } - - } - - getTexture( texture, textureProperty, uvSnippet, shaderStage = this.shaderStage ) { - - let snippet = null; - - if ( texture.isVideoTexture === true ) { - - snippet = this.getVideoSampler( textureProperty, uvSnippet, shaderStage ); - - } else { - - snippet = this.getSampler( textureProperty, uvSnippet, shaderStage ); - - } - - return snippet; - - } - - getTextureLevel( texture, textureProperty, uvSnippet, biasSnippet, shaderStage = this.shaderStage ) { - - let snippet = null; - - if ( texture.isVideoTexture === true ) { - - snippet = this.getVideoSampler( textureProperty, uvSnippet, shaderStage ); - - } else { - - snippet = this.getSamplerLevel( textureProperty, uvSnippet, biasSnippet, shaderStage ); - - } - - return snippet; - - } - - getPropertyName( node, shaderStage = this.shaderStage ) { - - if ( node.isNodeVarying === true && node.needsInterpolation === true ) { - - if ( shaderStage === 'vertex' ) { - - return `NodeVaryings.${ node.name }`; - - } - - } else if ( node.isNodeUniform === true ) { - - const name = node.name; - const type = node.type; - - if ( type === 'texture' || type === 'cubeTexture' ) { - - return name; - - } else if ( type === 'buffer' || type === 'storageBuffer' ) { - - return `NodeBuffer_${node.node.id}.${name}`; - - } else { - - return `NodeUniforms.${name}`; - - } - - } - - return super.getPropertyName( node ); - - } - - getBindings() { - - const bindings = this.bindings; - - return this.material !== null ? [ ...bindings.vertex, ...bindings.fragment ] : bindings.compute; - - } - - getUniformFromNode( node, type, shaderStage ) { - - const uniformNode = super.getUniformFromNode( node, type, shaderStage ); - const nodeData = this.getDataFromNode( node, shaderStage ); - - if ( nodeData.uniformGPU === undefined ) { - - let uniformGPU; - - const bindings = this.bindings[ shaderStage ]; - - if ( type === 'texture' || type === 'cubeTexture' ) { - - const sampler = new WebGPUNodeSampler( `${uniformNode.name}_sampler`, uniformNode.node ); - - let texture = null; - - if ( type === 'texture' ) { - - texture = new WebGPUNodeSampledTexture( uniformNode.name, uniformNode.node ); - - } else if ( type === 'cubeTexture' ) { - - texture = new WebGPUNodeSampledCubeTexture( uniformNode.name, uniformNode.node ); - - } - - // add first textures in sequence and group for last - const lastBinding = bindings[ bindings.length - 1 ]; - const index = lastBinding && lastBinding.isUniformsGroup ? bindings.length - 1 : bindings.length; - - if ( shaderStage === 'fragment' ) { - - bindings.splice( index, 0, sampler, texture ); - - uniformGPU = [ sampler, texture ]; - - } else { - - bindings.splice( index, 0, texture ); - - uniformGPU = [ texture ]; - - } - - } else if ( type === 'buffer' || type === 'storageBuffer' ) { - - const bufferClass = type === 'storageBuffer' ? WebGPUStorageBuffer : WebGPUUniformBuffer; - const buffer = new bufferClass( 'NodeBuffer_' + node.id, node.value ); - buffer.setVisibility( gpuShaderStageLib[ shaderStage ] ); - - // add first textures in sequence and group for last - const lastBinding = bindings[ bindings.length - 1 ]; - const index = lastBinding && lastBinding.isUniformsGroup ? bindings.length - 1 : bindings.length; - - bindings.splice( index, 0, buffer ); - - uniformGPU = buffer; - - } else { - - let uniformsGroup = this.uniformsGroup[ shaderStage ]; - - if ( uniformsGroup === undefined ) { - - uniformsGroup = new WebGPUUniformsGroup( 'nodeUniforms' ); - uniformsGroup.setVisibility( gpuShaderStageLib[ shaderStage ] ); - - this.uniformsGroup[ shaderStage ] = uniformsGroup; - - bindings.push( uniformsGroup ); - - } - - if ( node.isArrayUniformNode === true ) { - - uniformGPU = []; - - for ( const uniformNode of node.nodes ) { - - const uniformNodeGPU = this._getNodeUniform( uniformNode, type ); - - // fit bounds to buffer - uniformNodeGPU.boundary = getVectorLength( uniformNodeGPU.itemSize ); - uniformNodeGPU.itemSize = getStrideLength( uniformNodeGPU.itemSize ); - - uniformsGroup.addUniform( uniformNodeGPU ); - - uniformGPU.push( uniformNodeGPU ); - - } - - } else { - - uniformGPU = this._getNodeUniform( uniformNode, type ); - - uniformsGroup.addUniform( uniformGPU ); - - } - - } - - nodeData.uniformGPU = uniformGPU; - - if ( shaderStage === 'vertex' ) { - - this.bindingsOffset[ 'fragment' ] = bindings.length; - - } - - } - - return uniformNode; - - } - - isReference( type ) { - - return super.isReference( type ) || type === 'texture_2d' || type === 'texture_cube'; - - } - - getBuiltin( name, property, type, shaderStage = this.shaderStage ) { - - const map = this.builtins[ shaderStage ]; - - if ( map.has( name ) === false ) { - - map.set( name, { - name, - property, - type - } ); - - } - - return property; - - } - - getInstanceIndex() { - - if ( this.shaderStage === 'vertex' ) { - - return this.getBuiltin( 'instance_index', 'instanceIndex', 'u32', 'attribute' ); - - } - - return 'instanceIndex'; - - } - - getFrontFacing() { - - return this.getBuiltin( 'front_facing', 'isFront', 'bool' ); - - } - - getFragCoord() { - - return this.getBuiltin( 'position', 'fragCoord', 'vec4', 'fragment' ); - - } - - isFlipY() { - - return false; - - } - - getAttributes( shaderStage ) { - - const snippets = []; - - if ( shaderStage === 'compute' ) { - - this.getBuiltin( 'global_invocation_id', 'id', 'vec3', 'attribute' ); - - } - - if ( shaderStage === 'vertex' || shaderStage === 'compute' ) { - - for ( const { name, property, type } of this.builtins.attribute.values() ) { - - snippets.push( `@builtin( ${name} ) ${property} : ${type}` ); - - } - - const attributes = this.getAttributesArray(); - - for ( let index = 0, length = attributes.length; index < length; index ++ ) { - - const attribute = attributes[ index ]; - const name = attribute.name; - const type = this.getType( attribute.type ); - - snippets.push( `@location( ${index} ) ${ name } : ${ type }` ); - - } - - } - - return snippets.join( ',\n\t' ); - - } - - getVar( type, name ) { - - return `var ${ name } : ${ this.getType( type ) }`; - - } - - getVars( shaderStage ) { - - const snippets = []; - const vars = this.vars[ shaderStage ]; - - for ( const variable of vars ) { - - snippets.push( `\t${ this.getVar( variable.type, variable.name ) };` ); - - } - - return `\n${ snippets.join( '\n' ) }\n`; - - } - - getVaryings( shaderStage ) { - - const snippets = []; - - if ( shaderStage === 'vertex' ) { - - this.getBuiltin( 'position', 'Vertex', 'vec4', 'vertex' ); - - } - - if ( shaderStage === 'vertex' || shaderStage === 'fragment' ) { - - const varyings = this.varyings; - const vars = this.vars[ shaderStage ]; - - for ( let index = 0; index < varyings.length; index ++ ) { - - const varying = varyings[ index ]; - - if ( varying.needsInterpolation ) { - - let attributesSnippet = `@location( ${index} )`; - - if ( varying.type === 'int' || varying.type === 'uint' ) { - - attributesSnippet += ' @interpolate( flat )'; - - } - - snippets.push( `${ attributesSnippet } ${ varying.name } : ${ this.getType( varying.type ) }` ); - - } else if ( vars.includes( varying ) === false ) { - - vars.push( varying ); - - } - - } - - } - - for ( const { name, property, type } of this.builtins[ shaderStage ].values() ) { - - snippets.push( `@builtin( ${name} ) ${property} : ${type}` ); - - } - - const code = snippets.join( ',\n\t' ); - - return shaderStage === 'vertex' ? this._getWGSLStruct( 'NodeVaryingsStruct', '\t' + code ) : code; - - } - - getUniforms( shaderStage ) { - - const uniforms = this.uniforms[ shaderStage ]; - - const bindingSnippets = []; - const bufferSnippets = []; - const groupSnippets = []; - - let index = this.bindingsOffset[ shaderStage ]; - - for ( const uniform of uniforms ) { - - if ( uniform.type === 'texture' || uniform.type === 'cubeTexture' ) { - - if ( shaderStage === 'fragment' ) { - - bindingSnippets.push( `@group( 0 ) @binding( ${index ++} ) var ${uniform.name}_sampler : sampler;` ); - - } - - const texture = uniform.node.value; - - let textureType; - - if ( texture.isCubeTexture === true ) { - - textureType = 'texture_cube'; - - } else if ( texture.isDepthTexture === true ) { - - textureType = 'texture_depth_2d'; - - } else if ( texture.isVideoTexture === true ) { - - textureType = 'texture_external'; - - } else { - - textureType = 'texture_2d'; - - } - - bindingSnippets.push( `@group( 0 ) @binding( ${index ++} ) var ${uniform.name} : ${textureType};` ); - - } else if ( uniform.type === 'buffer' || uniform.type === 'storageBuffer' ) { - - const bufferNode = uniform.node; - const bufferType = this.getType( bufferNode.bufferType ); - const bufferCount = bufferNode.bufferCount; - - const bufferCountSnippet = bufferCount > 0 ? ', ' + bufferCount : ''; - const bufferSnippet = `\t${uniform.name} : array< ${bufferType}${bufferCountSnippet} >\n`; - const bufferAccessMode = bufferNode.isStorageBufferNode ? 'storage,read_write' : 'uniform'; - - bufferSnippets.push( this._getWGSLStructBinding( 'NodeBuffer_' + bufferNode.id, bufferSnippet, bufferAccessMode, index ++ ) ); - - } else { - - const vectorType = this.getType( this.getVectorType( uniform.type ) ); - - if ( Array.isArray( uniform.value ) === true ) { - - const length = uniform.value.length; - - groupSnippets.push( `uniform ${vectorType}[ ${length} ] ${uniform.name}` ); - - } else { - - groupSnippets.push( `\t${uniform.name} : ${ vectorType}` ); - - } - - } - - } - - let code = bindingSnippets.join( '\n' ); - code += bufferSnippets.join( '\n' ); - - if ( groupSnippets.length > 0 ) { - - code += this._getWGSLStructBinding( 'NodeUniforms', groupSnippets.join( ',\n' ), 'uniform', index ++ ); - - } - - return code; - - } - - buildCode() { - - const shadersData = this.material !== null ? { fragment: {}, vertex: {} } : { compute: {} }; - - for ( const shaderStage in shadersData ) { - - let flow = '// code\n\n'; - flow += this.flowCode[ shaderStage ]; - - const flowNodes = this.flowNodes[ shaderStage ]; - const mainNode = flowNodes[ flowNodes.length - 1 ]; - - for ( const node of flowNodes ) { - - const flowSlotData = this.getFlowData( node/*, shaderStage*/ ); - const slotName = node.name; - - if ( slotName ) { - - if ( flow.length > 0 ) flow += '\n'; - - flow += `\t// flow -> ${ slotName }\n\t`; - - } - - flow += `${ flowSlotData.code }\n\t`; - - if ( node === mainNode && shaderStage !== 'compute' ) { - - flow += '// result\n\t'; - - if ( shaderStage === 'vertex' ) { - - flow += 'NodeVaryings.Vertex = '; - - } else if ( shaderStage === 'fragment' ) { - - flow += 'return '; - - } - - flow += `${ flowSlotData.result };`; - - } - - } - - const stageData = shadersData[ shaderStage ]; - - stageData.uniforms = this.getUniforms( shaderStage ); - stageData.attributes = this.getAttributes( shaderStage ); - stageData.varyings = this.getVaryings( shaderStage ); - stageData.vars = this.getVars( shaderStage ); - stageData.codes = this.getCodes( shaderStage ); - stageData.flow = flow; - - } - - if ( this.material !== null ) { - - this.vertexShader = this._getWGSLVertexCode( shadersData.vertex ); - this.fragmentShader = this._getWGSLFragmentCode( shadersData.fragment ); - - } else { - - this.computeShader = this._getWGSLComputeCode( shadersData.compute, ( this.object.workgroupSize || [ 64 ] ).join( ', ' ) ); - - } - - } - - getRenderTarget( width, height, options ) { - - return new WebGPURenderTarget( width, height, options ); - - } - - getMethod( method ) { - - if ( wgslPolyfill[ method ] !== undefined ) { - - this._include( method ); - - } - - return wgslMethods[ method ] || method; - - } - - getType( type ) { - - return wgslTypeLib[ type ] || type; - - } - - isAvailable( name ) { - - return supports[ name ] === true; - - } - - _include( name ) { - - wgslPolyfill[ name ].build( this ); - - } - - _getNodeUniform( uniformNode, type ) { - - if ( type === 'float' ) return new FloatNodeUniform( uniformNode ); - if ( type === 'vec2' ) return new Vector2NodeUniform( uniformNode ); - if ( type === 'vec3' ) return new Vector3NodeUniform( uniformNode ); - if ( type === 'vec4' ) return new Vector4NodeUniform( uniformNode ); - if ( type === 'color' ) return new ColorNodeUniform( uniformNode ); - if ( type === 'mat3' ) return new Matrix3NodeUniform( uniformNode ); - if ( type === 'mat4' ) return new Matrix4NodeUniform( uniformNode ); - - throw new Error( `Uniform "${type}" not declared.` ); - - } - - _getWGSLVertexCode( shaderData ) { - - return `${ this.getSignature() } - -// uniforms -${shaderData.uniforms} - -// varyings -${shaderData.varyings} - -// codes -${shaderData.codes} - -@vertex -fn main( ${shaderData.attributes} ) -> NodeVaryingsStruct { - - // system - var NodeVaryings: NodeVaryingsStruct; - - // vars - ${shaderData.vars} - - // flow - ${shaderData.flow} - - return NodeVaryings; - -} -`; - - } - - _getWGSLFragmentCode( shaderData ) { - - return `${ this.getSignature() } - -// uniforms -${shaderData.uniforms} - -// codes -${shaderData.codes} - -@fragment -fn main( ${shaderData.varyings} ) -> @location( 0 ) vec4 { - - // vars - ${shaderData.vars} - - // flow - ${shaderData.flow} - -} -`; - - } - - _getWGSLComputeCode( shaderData, workgroupSize ) { - - return `${ this.getSignature() } -// system -var instanceIndex : u32; - -// uniforms -${shaderData.uniforms} - -// codes -${shaderData.codes} - -@compute @workgroup_size( ${workgroupSize} ) -fn main( ${shaderData.attributes} ) { - - // system - instanceIndex = id.x; - - // vars - ${shaderData.vars} - - // flow - ${shaderData.flow} - -} -`; - - } - - _getWGSLStruct( name, vars ) { - - return ` -struct ${name} { -${vars} -};`; - - } - - _getWGSLStructBinding( name, vars, access, binding = 0, group = 0 ) { - - const structName = name + 'Struct'; - const structSnippet = this._getWGSLStruct( structName, vars ); - - return `${structSnippet} -@binding( ${binding} ) @group( ${group} ) -var<${access}> ${name} : ${structName};`; - - } - -} - -export default WebGPUNodeBuilder; diff --git a/examples/jsm/renderers/webgpu/nodes/WebGPUNodeSampledTexture.js b/examples/jsm/renderers/webgpu/nodes/WebGPUNodeSampledTexture.js deleted file mode 100644 index 12ce201de3852b..00000000000000 --- a/examples/jsm/renderers/webgpu/nodes/WebGPUNodeSampledTexture.js +++ /dev/null @@ -1,39 +0,0 @@ -import { WebGPUSampledTexture, WebGPUSampledCubeTexture } from '../WebGPUSampledTexture.js'; - -class WebGPUNodeSampledTexture extends WebGPUSampledTexture { - - constructor( name, textureNode ) { - - super( name, textureNode.value ); - - this.textureNode = textureNode; - - } - - getTexture() { - - return this.textureNode.value; - - } - -} - -class WebGPUNodeSampledCubeTexture extends WebGPUSampledCubeTexture { - - constructor( name, textureNode ) { - - super( name, textureNode.value ); - - this.textureNode = textureNode; - - } - - getTexture() { - - return this.textureNode.value; - - } - -} - -export { WebGPUNodeSampledTexture, WebGPUNodeSampledCubeTexture }; diff --git a/examples/jsm/renderers/webgpu/nodes/WebGPUNodeSampler.js b/examples/jsm/renderers/webgpu/nodes/WebGPUNodeSampler.js deleted file mode 100644 index c0a645b6ae4963..00000000000000 --- a/examples/jsm/renderers/webgpu/nodes/WebGPUNodeSampler.js +++ /dev/null @@ -1,21 +0,0 @@ -import WebGPUSampler from '../WebGPUSampler.js'; - -class WebGPUNodeSampler extends WebGPUSampler { - - constructor( name, textureNode ) { - - super( name, textureNode.value ); - - this.textureNode = textureNode; - - } - - getTexture() { - - return this.textureNode.value; - - } - -} - -export default WebGPUNodeSampler; diff --git a/examples/jsm/renderers/webgpu/nodes/WebGPUNodeUniform.js b/examples/jsm/renderers/webgpu/nodes/WebGPUNodeUniform.js deleted file mode 100644 index 06239366a0db36..00000000000000 --- a/examples/jsm/renderers/webgpu/nodes/WebGPUNodeUniform.js +++ /dev/null @@ -1,135 +0,0 @@ -import { - FloatUniform, Vector2Uniform, Vector3Uniform, Vector4Uniform, - ColorUniform, Matrix3Uniform, Matrix4Uniform -} from '../WebGPUUniform.js'; - -class FloatNodeUniform extends FloatUniform { - - constructor( nodeUniform ) { - - super( nodeUniform.name, nodeUniform.value ); - - this.nodeUniform = nodeUniform; - - } - - getValue() { - - return this.nodeUniform.value; - - } - -} - -class Vector2NodeUniform extends Vector2Uniform { - - constructor( nodeUniform ) { - - super( nodeUniform.name, nodeUniform.value ); - - this.nodeUniform = nodeUniform; - - } - - getValue() { - - return this.nodeUniform.value; - - } - -} - -class Vector3NodeUniform extends Vector3Uniform { - - constructor( nodeUniform ) { - - super( nodeUniform.name, nodeUniform.value ); - - this.nodeUniform = nodeUniform; - - } - - getValue() { - - return this.nodeUniform.value; - - } - -} - -class Vector4NodeUniform extends Vector4Uniform { - - constructor( nodeUniform ) { - - super( nodeUniform.name, nodeUniform.value ); - - this.nodeUniform = nodeUniform; - - } - - getValue() { - - return this.nodeUniform.value; - - } - -} - -class ColorNodeUniform extends ColorUniform { - - constructor( nodeUniform ) { - - super( nodeUniform.name, nodeUniform.value ); - - this.nodeUniform = nodeUniform; - - } - - getValue() { - - return this.nodeUniform.value; - - } - -} - -class Matrix3NodeUniform extends Matrix3Uniform { - - constructor( nodeUniform ) { - - super( nodeUniform.name, nodeUniform.value ); - - this.nodeUniform = nodeUniform; - - } - - getValue() { - - return this.nodeUniform.value; - - } - -} - -class Matrix4NodeUniform extends Matrix4Uniform { - - constructor( nodeUniform ) { - - super( nodeUniform.name, nodeUniform.value ); - - this.nodeUniform = nodeUniform; - - } - - getValue() { - - return this.nodeUniform.value; - - } - -} - -export { - FloatNodeUniform, Vector2NodeUniform, Vector3NodeUniform, Vector4NodeUniform, - ColorNodeUniform, Matrix3NodeUniform, Matrix4NodeUniform -}; diff --git a/examples/jsm/renderers/webgpu/nodes/WebGPUNodes.js b/examples/jsm/renderers/webgpu/nodes/WebGPUNodes.js deleted file mode 100644 index c932b6fae86276..00000000000000 --- a/examples/jsm/renderers/webgpu/nodes/WebGPUNodes.js +++ /dev/null @@ -1,302 +0,0 @@ -import WebGPUNodeBuilder from './WebGPUNodeBuilder.js'; -import { NoToneMapping, EquirectangularReflectionMapping, EquirectangularRefractionMapping } from 'three'; -import { NodeFrame, cubeTexture, texture, rangeFog, densityFog, reference, toneMapping, positionWorld, modelWorldMatrix, transformDirection, equirectUV, viewportBottomLeft } from '../../../nodes/Nodes.js'; - -class WebGPUNodes { - - constructor( renderer, properties ) { - - this.renderer = renderer; - this.properties = properties; - - this.nodeFrame = new NodeFrame(); - - } - - get( renderObject ) { - - const renderObjectProperties = this.properties.get( renderObject ); - - let nodeBuilder = renderObjectProperties.nodeBuilder; - - if ( nodeBuilder === undefined ) { - - nodeBuilder = new WebGPUNodeBuilder( renderObject.object, this.renderer ); - nodeBuilder.material = renderObject.material; - nodeBuilder.lightsNode = renderObject.lightsNode; - nodeBuilder.environmentNode = this.getEnvironmentNode( renderObject.scene ); - nodeBuilder.fogNode = this.getFogNode( renderObject.scene ); - nodeBuilder.toneMappingNode = this.getToneMappingNode(); - nodeBuilder.build(); - - renderObjectProperties.nodeBuilder = nodeBuilder; - - } - - return nodeBuilder; - - } - - getForCompute( computeNode ) { - - const computeProperties = this.properties.get( computeNode ); - - let nodeBuilder = computeProperties.nodeBuilder; - - if ( nodeBuilder === undefined ) { - - nodeBuilder = new WebGPUNodeBuilder( computeNode, this.renderer ); - nodeBuilder.build(); - - computeProperties.nodeBuilder = nodeBuilder; - - } - - return nodeBuilder; - - } - - remove( renderObject ) { - - const objectProperties = this.properties.get( renderObject ); - - delete objectProperties.nodeBuilder; - - } - - getEnvironmentNode( scene ) { - - return scene.environmentNode || this.properties.get( scene ).environmentNode || null; - - } - - getFogNode( scene ) { - - return scene.fogNode || this.properties.get( scene ).fogNode || null; - - } - - getToneMappingNode() { - - return this.renderer.toneMappingNode || this.properties.get( this.renderer ).toneMappingNode || null; - - } - - getCacheKey( scene, lightsNode ) { - - const environmentNode = this.getEnvironmentNode( scene ); - const fogNode = this.getFogNode( scene ); - const toneMappingNode = this.getToneMappingNode(); - - const cacheKey = []; - - if ( lightsNode ) cacheKey.push( 'lightsNode:' + lightsNode.getCacheKey() ); - if ( environmentNode ) cacheKey.push( 'environmentNode:' + environmentNode.getCacheKey() ); - if ( fogNode ) cacheKey.push( 'fogNode:' + fogNode.getCacheKey() ); - if ( toneMappingNode ) cacheKey.push( 'toneMappingNode:' + toneMappingNode.getCacheKey() ); - - return '{' + cacheKey.join( ',' ) + '}'; - - } - - updateToneMapping() { - - const renderer = this.renderer; - const rendererProperties = this.properties.get( renderer ); - const rendererToneMapping = renderer.toneMapping; - - if ( rendererToneMapping !== NoToneMapping ) { - - if ( rendererProperties.toneMapping !== rendererToneMapping ) { - - rendererProperties.toneMappingNode = toneMapping( rendererToneMapping, reference( 'toneMappingExposure', 'float', renderer ) ); - rendererProperties.toneMapping = rendererToneMapping; - - } - - } else { - - delete rendererProperties.toneMappingNode; - delete rendererProperties.toneMapping; - - } - - } - - updateBackground( scene ) { - - const sceneProperties = this.properties.get( scene ); - const background = scene.background; - - if ( background ) { - - if ( sceneProperties.background !== background ) { - - let backgroundNode = null; - - if ( background.isCubeTexture === true ) { - - backgroundNode = cubeTexture( background, transformDirection( positionWorld, modelWorldMatrix ) ); - - } else if ( background.isTexture === true ) { - - let nodeUV = null; - - if ( background.mapping === EquirectangularReflectionMapping || background.mapping === EquirectangularRefractionMapping ) { - - nodeUV = equirectUV(); - - } else { - - nodeUV = viewportBottomLeft; - - } - - backgroundNode = texture( background, nodeUV ); - - } else if ( background.isColor !== true ) { - - console.error( 'WebGPUNodes: Unsupported background configuration.', background ); - - } - - sceneProperties.backgroundNode = backgroundNode; - sceneProperties.background = background; - - } - - } else if ( sceneProperties.backgroundNode ) { - - delete sceneProperties.backgroundNode; - delete sceneProperties.background; - - } - - } - - updateFog( scene ) { - - const sceneProperties = this.properties.get( scene ); - const fog = scene.fog; - - if ( fog ) { - - if ( sceneProperties.fog !== fog ) { - - let fogNode = null; - - if ( fog.isFogExp2 ) { - - fogNode = densityFog( reference( 'color', 'color', fog ), reference( 'density', 'float', fog ) ); - - } else if ( fog.isFog ) { - - fogNode = rangeFog( reference( 'color', 'color', fog ), reference( 'near', 'float', fog ), reference( 'far', 'float', fog ) ); - - } else { - - console.error( 'WebGPUNodes: Unsupported fog configuration.', fog ); - - } - - sceneProperties.fogNode = fogNode; - sceneProperties.fog = fog; - - } - - } else { - - delete sceneProperties.fogNode; - delete sceneProperties.fog; - - } - - } - - updateEnvironment( scene ) { - - const sceneProperties = this.properties.get( scene ); - const environment = scene.environment; - - if ( environment ) { - - if ( sceneProperties.environment !== environment ) { - - let environmentNode = null; - - if ( environment.isCubeTexture === true ) { - - environmentNode = cubeTexture( environment ); - - } else if ( environment.isTexture === true ) { - - environmentNode = texture( environment ); - - } else { - - console.error( 'WebGPUNodes: Unsupported environment configuration.', environment ); - - } - - sceneProperties.environmentNode = environmentNode; - sceneProperties.environment = environment; - - } - - } else if ( sceneProperties.environmentNode ) { - - delete sceneProperties.environmentNode; - delete sceneProperties.environment; - - } - - } - - getNodeFrame( renderObject ) { - - const nodeFrame = this.nodeFrame; - nodeFrame.scene = renderObject.scene; - nodeFrame.object = renderObject.object; - nodeFrame.camera = renderObject.camera; - nodeFrame.renderer = renderObject.renderer; - nodeFrame.material = renderObject.material; - - return nodeFrame; - - } - - updateBefore( renderObject ) { - - const nodeFrame = this.getNodeFrame( renderObject ); - const nodeBuilder = this.get( renderObject ); - - for ( const node of nodeBuilder.updateBeforeNodes ) { - - nodeFrame.updateBeforeNode( node ); - - } - - } - - update( renderObject ) { - - const nodeFrame = this.getNodeFrame( renderObject ); - const nodeBuilder = this.get( renderObject ); - - for ( const node of nodeBuilder.updateNodes ) { - - nodeFrame.updateNode( node ); - - } - - } - - dispose() { - - this.nodeFrame = new NodeFrame(); - - } - -} - -export default WebGPUNodes; diff --git a/examples/jsm/shaders/ACESFilmicToneMappingShader.js b/examples/jsm/shaders/ACESFilmicToneMappingShader.js index 1c8756c8c90175..8c3544bc7a2707 100644 --- a/examples/jsm/shaders/ACESFilmicToneMappingShader.js +++ b/examples/jsm/shaders/ACESFilmicToneMappingShader.js @@ -1,13 +1,22 @@ /** - * ACES Filmic Tone Mapping Shader by Stephen Hill - * source: https://github.com/selfshadow/ltc_code/blob/master/webgl/shaders/ltc/ltc_blit.fs - * - * this implementation of ACES is modified to accommodate a brighter viewing environment. - * the scale factor of 1/0.6 is subjective. see discussion in #19621. + * @module ACESFilmicToneMappingShader + * @three_import import { ACESFilmicToneMappingShader } from 'three/addons/shaders/ACESFilmicToneMappingShader.js'; */ +/** + * ACES Filmic Tone Mapping Shader by Stephen Hill. + * Reference: [ltc_blit.fs]{@link https://github.com/selfshadow/ltc_code/blob/master/webgl/shaders/ltc/ltc_blit.fs} + * + * This implementation of ACES is modified to accommodate a brighter viewing environment. + * The scale factor of 1/0.6 is subjective. See discussion in #19621. + * + * @constant + * @type {ShaderMaterial~Shader} + */ const ACESFilmicToneMappingShader = { + name: 'ACESFilmicToneMappingShader', + uniforms: { 'tDiffuse': { value: null }, diff --git a/examples/jsm/shaders/AfterimageShader.js b/examples/jsm/shaders/AfterimageShader.js index a8a78d390d1325..e2fda0c6d5640c 100644 --- a/examples/jsm/shaders/AfterimageShader.js +++ b/examples/jsm/shaders/AfterimageShader.js @@ -1,11 +1,18 @@ /** - * Afterimage shader - * I created this effect inspired by a demo on codepen: - * https://codepen.io/brunoimbrizi/pen/MoRJaN?page=1& + * @module AfterimageShader + * @three_import import { AfterimageShader } from 'three/addons/shaders/AfterimageShader.js'; */ +/** + * Inspired by [Three.js FBO motion trails]{@link https://codepen.io/brunoimbrizi/pen/MoRJaN?page=1&}. + * + * @constant + * @type {ShaderMaterial~Shader} + */ const AfterimageShader = { + name: 'AfterimageShader', + uniforms: { 'damp': { value: 0.96 }, diff --git a/examples/jsm/shaders/BasicShader.js b/examples/jsm/shaders/BasicShader.js index b88bfad1a198a9..2a5da5603608fe 100644 --- a/examples/jsm/shaders/BasicShader.js +++ b/examples/jsm/shaders/BasicShader.js @@ -1,9 +1,18 @@ /** - * Simple test shader + * @module BasicShader + * @three_import import { BasicShader } from 'three/addons/shaders/BasicShader.js'; */ +/** + * Simple shader for testing. + * + * @constant + * @type {ShaderMaterial~Shader} + */ const BasicShader = { + name: 'BasicShader', + uniforms: {}, vertexShader: /* glsl */` diff --git a/examples/jsm/shaders/BleachBypassShader.js b/examples/jsm/shaders/BleachBypassShader.js index cc5284ceeec103..b170f3e3094eda 100644 --- a/examples/jsm/shaders/BleachBypassShader.js +++ b/examples/jsm/shaders/BleachBypassShader.js @@ -1,11 +1,20 @@ + /** - * Bleach bypass shader [http://en.wikipedia.org/wiki/Bleach_bypass] - * - based on Nvidia example - * http://developer.download.nvidia.com/shaderlibrary/webpages/shader_library.html#post_bleach_bypass + * @module BleachBypassShader + * @three_import import { BleachBypassShader } from 'three/addons/shaders/BleachBypassShader.js'; */ +/** + * Bleach bypass shader [http://en.wikipedia.org/wiki/Bleach_bypass] based on + * [Nvidia Shader library]{@link http://developer.download.nvidia.com/shaderlibrary/webpages/shader_library.html#post_bleach_bypass}. + * + * @constant + * @type {ShaderMaterial~Shader} + */ const BleachBypassShader = { + name: 'BleachBypassShader', + uniforms: { 'tDiffuse': { value: null }, @@ -36,8 +45,7 @@ const BleachBypassShader = { vec4 base = texture2D( tDiffuse, vUv ); - vec3 lumCoeff = vec3( 0.25, 0.65, 0.1 ); - float lum = dot( lumCoeff, base.rgb ); + float lum = luminance( base.rgb ); vec3 blend = vec3( lum ); float L = min( 1.0, max( 0.0, 10.0 * ( lum - 0.45 ) ) ); diff --git a/examples/jsm/shaders/BlendShader.js b/examples/jsm/shaders/BlendShader.js index 5b27cf6afc3f17..8fc453120d2848 100644 --- a/examples/jsm/shaders/BlendShader.js +++ b/examples/jsm/shaders/BlendShader.js @@ -1,9 +1,18 @@ /** - * Blend two textures + * @module BlendShader + * @three_import import { BlendShader } from 'three/addons/shaders/BlendShader.js'; */ +/** + * Blends two textures. + * + * @constant + * @type {ShaderMaterial~Shader} + */ const BlendShader = { + name: 'BlendShader', + uniforms: { 'tDiffuse1': { value: null }, @@ -38,8 +47,7 @@ const BlendShader = { vec4 texel1 = texture2D( tDiffuse1, vUv ); vec4 texel2 = texture2D( tDiffuse2, vUv ); - gl_FragColor = mix( texel1, texel2, mixRatio ); - gl_FragColor.a *= opacity; + gl_FragColor = opacity * mix( texel1, texel2, mixRatio ); }` diff --git a/examples/jsm/shaders/BokehShader.js b/examples/jsm/shaders/BokehShader.js index 32a20875275e1e..8bead5eb08817f 100644 --- a/examples/jsm/shaders/BokehShader.js +++ b/examples/jsm/shaders/BokehShader.js @@ -1,11 +1,19 @@ /** - * Depth-of-field shader with bokeh - * ported from GLSL shader by Martins Upitis - * http://artmartinsh.blogspot.com/2010/02/glsl-lens-blur-filter-with-bokeh.html + * @module BokehShader + * @three_import import { BokehShader } from 'three/addons/shaders/BokehShader.js'; */ +/** + * Depth-of-field shader with bokeh ported from + * [GLSL shader by Martins Upitis]{@link http://artmartinsh.blogspot.com/2010/02/glsl-lens-blur-filter-with-bokeh.html}. + * + * @constant + * @type {ShaderMaterial~Shader} + */ const BokehShader = { + name: 'BokehShader', + defines: { 'DEPTH_PACKING': 1, 'PERSPECTIVE_CAMERA': 1, diff --git a/examples/jsm/shaders/BokehShader2.js b/examples/jsm/shaders/BokehShader2.js index 4db405e19d9470..04784d8a343d77 100644 --- a/examples/jsm/shaders/BokehShader2.js +++ b/examples/jsm/shaders/BokehShader2.js @@ -3,14 +3,23 @@ import { } from 'three'; /** - * Depth-of-field shader with bokeh - * ported from GLSL shader by Martins Upitis - * http://blenderartists.org/forum/showthread.php?237488-GLSL-depth-of-field-with-bokeh-v2-4-(update) + * @module BokehShader2 + * @three_import import { BokehShader, BokehDepthShader } from 'three/addons/shaders/BokehShader2.js'; + */ + +/** + * Depth-of-field shader with bokeh ported from + * [GLSL shader by Martins Upitis]{@link http://blenderartists.org/forum/showthread.php?237488-GLSL-depth-of-field-with-bokeh-v2-4-(update)}. * * Requires #define RINGS and SAMPLES integers + * + * @constant + * @type {ShaderMaterial~Shader} */ const BokehShader = { + name: 'BokehShader', + uniforms: { 'textureWidth': { value: 1.0 }, @@ -102,7 +111,7 @@ const BokehShader = { float vignout = 1.3; // vignetting outer border float vignin = 0.0; // vignetting inner border - float vignfade = 22.0; // f-stops till vignete fades + float vignfade = 22.0; // f-stops till vignette fades uniform bool shaderFocus; // disable if you use external focalDepth value @@ -345,12 +354,17 @@ const BokehShader = { gl_FragColor.rgb = col; gl_FragColor.a = 1.0; + + #include + #include }` }; const BokehDepthShader = { + name: 'BokehDepthShader', + uniforms: { 'mNear': { value: 1.0 }, diff --git a/examples/jsm/shaders/BrightnessContrastShader.js b/examples/jsm/shaders/BrightnessContrastShader.js index 41edbdd21cf52a..073f07e1bb56f9 100644 --- a/examples/jsm/shaders/BrightnessContrastShader.js +++ b/examples/jsm/shaders/BrightnessContrastShader.js @@ -1,12 +1,20 @@ /** - * Brightness and contrast adjustment - * https://github.com/evanw/glfx.js - * brightness: -1 to 1 (-1 is solid black, 0 is no change, and 1 is solid white) - * contrast: -1 to 1 (-1 is solid gray, 0 is no change, and 1 is maximum contrast) + * @module BrightnessContrastShader + * @three_import import { BrightnessContrastShader } from 'three/addons/shaders/BrightnessContrastShader.js'; */ +/** + * Brightness and contrast adjustment {@link https://github.com/evanw/glfx.js}. + * Brightness: -1 to 1 (-1 is solid black, 0 is no change, and 1 is solid white) + * Contrast: -1 to 1 (-1 is solid gray, 0 is no change, and 1 is maximum contrast) + * + * @constant + * @type {ShaderMaterial~Shader} + */ const BrightnessContrastShader = { + name: 'BrightnessContrastShader', + uniforms: { 'tDiffuse': { value: null }, diff --git a/examples/jsm/shaders/ColorCorrectionShader.js b/examples/jsm/shaders/ColorCorrectionShader.js index df93a63ff9ec47..0ae42d99de7fd0 100644 --- a/examples/jsm/shaders/ColorCorrectionShader.js +++ b/examples/jsm/shaders/ColorCorrectionShader.js @@ -3,11 +3,20 @@ import { } from 'three'; /** - * Color correction + * @module ColorCorrectionShader + * @three_import import { ColorCorrectionShader } from 'three/addons/shaders/ColorCorrectionShader.js'; */ +/** + * Color correction shader. + * + * @constant + * @type {ShaderMaterial~Shader} + */ const ColorCorrectionShader = { + name: 'ColorCorrectionShader', + uniforms: { 'tDiffuse': { value: null }, diff --git a/examples/jsm/shaders/ColorifyShader.js b/examples/jsm/shaders/ColorifyShader.js index 5f430290dd4884..029671fff857e6 100644 --- a/examples/jsm/shaders/ColorifyShader.js +++ b/examples/jsm/shaders/ColorifyShader.js @@ -3,11 +3,20 @@ import { } from 'three'; /** - * Colorify shader + * @module ColorifyShader + * @three_import import { ColorifyShader } from 'three/addons/shaders/ColorifyShader.js'; */ +/** + * Colorify shader. + * + * @constant + * @type {ShaderMaterial~Shader} + */ const ColorifyShader = { + name: 'ColorifyShader', + uniforms: { 'tDiffuse': { value: null }, @@ -37,8 +46,7 @@ const ColorifyShader = { vec4 texel = texture2D( tDiffuse, vUv ); - vec3 luma = vec3( 0.299, 0.587, 0.114 ); - float v = dot( texel.xyz, luma ); + float v = luminance( texel.xyz ); gl_FragColor = vec4( v * color, texel.w ); diff --git a/examples/jsm/shaders/ConvolutionShader.js b/examples/jsm/shaders/ConvolutionShader.js index 9e0ff77b6e5763..5db1cdf2bef318 100644 --- a/examples/jsm/shaders/ConvolutionShader.js +++ b/examples/jsm/shaders/ConvolutionShader.js @@ -3,12 +3,20 @@ import { } from 'three'; /** - * Convolution shader - * ported from o3d sample to WebGL / GLSL + * @module ConvolutionShader + * @three_import import { ConvolutionShader } from 'three/addons/shaders/ConvolutionShader.js'; */ +/** + * Convolution shader ported from o3d sample to WebGL / GLSL. + * + * @constant + * @type {ShaderMaterial~Shader} + */ const ConvolutionShader = { + name: 'ConvolutionShader', + defines: { 'KERNEL_SIZE_FLOAT': '25.0', @@ -60,42 +68,7 @@ const ConvolutionShader = { gl_FragColor = sum; - }`, - - buildKernel: function ( sigma ) { - - // We lop off the sqrt(2 * pi) * sigma term, since we're going to normalize anyway. - - const kMaxKernelSize = 25; - let kernelSize = 2 * Math.ceil( sigma * 3.0 ) + 1; - - if ( kernelSize > kMaxKernelSize ) kernelSize = kMaxKernelSize; - - const halfWidth = ( kernelSize - 1 ) * 0.5; - - const values = new Array( kernelSize ); - let sum = 0.0; - for ( let i = 0; i < kernelSize; ++ i ) { - - values[ i ] = gauss( i - halfWidth, sigma ); - sum += values[ i ]; - - } - - // normalize the kernel - - for ( let i = 0; i < kernelSize; ++ i ) values[ i ] /= sum; - - return values; - - } - + }` }; -function gauss( x, sigma ) { - - return Math.exp( - ( x * x ) / ( 2.0 * sigma * sigma ) ); - -} - export { ConvolutionShader }; diff --git a/examples/jsm/shaders/CopyShader.js b/examples/jsm/shaders/CopyShader.js index fc75c56a903014..6deb24849e1dd0 100644 --- a/examples/jsm/shaders/CopyShader.js +++ b/examples/jsm/shaders/CopyShader.js @@ -1,9 +1,18 @@ /** - * Full-screen textured quad shader + * @module CopyShader + * @three_import import { CopyShader } from 'three/addons/shaders/CopyShader.js'; */ +/** + * Full-screen copy shader pass. + * + * @constant + * @type {ShaderMaterial~Shader} + */ const CopyShader = { + name: 'CopyShader', + uniforms: { 'tDiffuse': { value: null }, @@ -32,8 +41,8 @@ const CopyShader = { void main() { - gl_FragColor = texture2D( tDiffuse, vUv ); - gl_FragColor.a *= opacity; + vec4 texel = texture2D( tDiffuse, vUv ); + gl_FragColor = opacity * texel; }` diff --git a/examples/jsm/shaders/DOFMipMapShader.js b/examples/jsm/shaders/DOFMipMapShader.js index e8add02132e240..4cddc6142c7067 100644 --- a/examples/jsm/shaders/DOFMipMapShader.js +++ b/examples/jsm/shaders/DOFMipMapShader.js @@ -1,11 +1,20 @@ /** - * Depth-of-field shader using mipmaps - * - from Matt Handley @applmak - * - requires power-of-2 sized render target with enabled mipmaps + * @module DOFMipMapShader + * @three_import import { DOFMipMapShader } from 'three/addons/shaders/DOFMipMapShader.js'; */ +/** + * Depth-of-field shader using mipmaps from Matt Handley @applmak. + * + * Requires power-of-2 sized render target with enabled mipmaps. + * + * @constant + * @type {ShaderMaterial~Shader} + */ const DOFMipMapShader = { + name: 'DOFMipMapShader', + uniforms: { 'tColor': { value: null }, diff --git a/examples/jsm/shaders/DepthLimitedBlurShader.js b/examples/jsm/shaders/DepthLimitedBlurShader.js index d8a933483c6f8b..ef5e8525124e85 100644 --- a/examples/jsm/shaders/DepthLimitedBlurShader.js +++ b/examples/jsm/shaders/DepthLimitedBlurShader.js @@ -3,15 +3,28 @@ import { } from 'three'; /** - * TODO + * @module DepthLimitedBlurShader + * @three_import import { DepthLimitedBlurShader, BlurShaderUtils } from 'three/addons/shaders/DepthLimitedBlurShader.js'; */ +/** + * TODO + * + * Used by {@link SAOPass}. + * + * @constant + * @type {ShaderMaterial~Shader} + */ const DepthLimitedBlurShader = { + + name: 'DepthLimitedBlurShader', + defines: { 'KERNEL_RADIUS': 4, 'DEPTH_PACKING': 1, 'PERSPECTIVE_CAMERA': 1 }, + uniforms: { 'tDiffuse': { value: null }, 'size': { value: new Vector2( 512, 512 ) }, @@ -22,6 +35,7 @@ const DepthLimitedBlurShader = { 'cameraFar': { value: 1000 }, 'depthCutoff': { value: 10 }, }, + vertexShader: /* glsl */` #include diff --git a/examples/jsm/shaders/DigitalGlitch.js b/examples/jsm/shaders/DigitalGlitch.js index 8e2663f589f75e..23e873bcede6b0 100644 --- a/examples/jsm/shaders/DigitalGlitch.js +++ b/examples/jsm/shaders/DigitalGlitch.js @@ -1,15 +1,18 @@ /** - * RGB Shift Shader - * Shifts red and blue channels from center in opposite directions - * Ported from http://kriss.cx/tom/2009/05/rgb-shift/ - * by Tom Butterworth / http://kriss.cx/tom/ - * - * amount: shift distance (1 is width of input) - * angle: shift angle in radians + * @module DigitalGlitch + * @three_import import { DigitalGlitch } from 'three/addons/shaders/DigitalGlitch.js'; */ +/** + * Digital glitch shader. + * + * @constant + * @type {ShaderMaterial~Shader} + */ const DigitalGlitch = { + name: 'DigitalGlitch', + uniforms: { 'tDiffuse': { value: null }, //diffuse texture diff --git a/examples/jsm/shaders/DotScreenShader.js b/examples/jsm/shaders/DotScreenShader.js index db5eca4f72440a..dbea3516f8316f 100644 --- a/examples/jsm/shaders/DotScreenShader.js +++ b/examples/jsm/shaders/DotScreenShader.js @@ -3,13 +3,20 @@ import { } from 'three'; /** - * Dot screen shader - * based on glfx.js sepia shader - * https://github.com/evanw/glfx.js + * @module DotScreenShader + * @three_import import { DotScreenShader } from 'three/addons/shaders/DotScreenShader.js'; */ +/** + * Dot screen shader based on [glfx.js sepia shader]{@link https://github.com/evanw/glfx.js}. + * + * @constant + * @type {ShaderMaterial~Shader} + */ const DotScreenShader = { + name: 'DotScreenShader', + uniforms: { 'tDiffuse': { value: null }, diff --git a/examples/jsm/shaders/ExposureShader.js b/examples/jsm/shaders/ExposureShader.js new file mode 100644 index 00000000000000..d61d492f396875 --- /dev/null +++ b/examples/jsm/shaders/ExposureShader.js @@ -0,0 +1,51 @@ +/** + * @module ExposureShader + * @three_import import { ExposureShader } from 'three/addons/shaders/ExposureShader.js'; + */ + +/** + * TODO + * + * @constant + * @type {ShaderMaterial~Shader} + */ +const ExposureShader = { + + name: 'ExposureShader', + + uniforms: { + + 'tDiffuse': { value: null }, + 'exposure': { value: 1.0 } + + }, + + vertexShader: /* glsl */` + + varying vec2 vUv; + + void main() { + + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + + }`, + + fragmentShader: /* glsl */` + + uniform float exposure; + + uniform sampler2D tDiffuse; + + varying vec2 vUv; + + void main() { + + gl_FragColor = texture2D( tDiffuse, vUv ); + gl_FragColor.rgb *= exposure; + + }` + +}; + +export { ExposureShader }; diff --git a/examples/jsm/shaders/FXAAShader.js b/examples/jsm/shaders/FXAAShader.js index 299a792e7340e7..7181751daf5275 100644 --- a/examples/jsm/shaders/FXAAShader.js +++ b/examples/jsm/shaders/FXAAShader.js @@ -3,15 +3,24 @@ import { } from 'three'; /** - * NVIDIA FXAA by Timothy Lottes - * https://developer.download.nvidia.com/assets/gamedev/files/sdk/11/FXAA_WhitePaper.pdf - * - WebGL port by @supereggbert - * http://www.glge.org/demos/fxaa/ - * Further improved by Daniel Sturk + * @module FXAAShader + * @three_import import { FXAAShader } from 'three/addons/shaders/FXAAShader.js'; */ +/** + * FXAA algorithm from NVIDIA, C# implementation by Jasper Flick, GLSL port by Dave Hoskins. + * + * References: + * - {@link http://developer.download.nvidia.com/assets/gamedev/files/sdk/11/FXAA_WhitePaper.pdf}. + * - {@link https://catlikecoding.com/unity/tutorials/advanced-rendering/fxaa/}. + * + * @constant + * @type {ShaderMaterial~Shader} + */ const FXAAShader = { + name: 'FXAAShader', + uniforms: { 'tDiffuse': { value: null }, @@ -30,256 +39,259 @@ const FXAAShader = { }`, - fragmentShader: ` - precision highp float; - - uniform sampler2D tDiffuse; - - uniform vec2 resolution; - - varying vec2 vUv; - - // FXAA 3.11 implementation by NVIDIA, ported to WebGL by Agost Biro (biro@archilogic.com) - - //---------------------------------------------------------------------------------- - // File: es3-kepler\FXAA\assets\shaders/FXAA_DefaultES.frag - // SDK Version: v3.00 - // Email: gameworks@nvidia.com - // Site: http://developer.nvidia.com/ - // - // Copyright (c) 2014-2015, NVIDIA CORPORATION. All rights reserved. - // - // Redistribution and use in source and binary forms, with or without - // modification, are permitted provided that the following conditions - // are met: - // * Redistributions of source code must retain the above copyright - // notice, this list of conditions and the following disclaimer. - // * Redistributions in binary form must reproduce the above copyright - // notice, this list of conditions and the following disclaimer in the - // documentation and/or other materials provided with the distribution. - // * Neither the name of NVIDIA CORPORATION nor the names of its - // contributors may be used to endorse or promote products derived - // from this software without specific prior written permission. - // - // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY - // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY - // OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - // - //---------------------------------------------------------------------------------- - - #ifndef FXAA_DISCARD - // - // Only valid for PC OpenGL currently. - // Probably will not work when FXAA_GREEN_AS_LUMA = 1. - // - // 1 = Use discard on pixels which don't need AA. - // For APIs which enable concurrent TEX+ROP from same surface. - // 0 = Return unchanged color on pixels which don't need AA. - // - #define FXAA_DISCARD 0 - #endif - - /*--------------------------------------------------------------------------*/ - #define FxaaTexTop(t, p) texture2D(t, p, -100.0) - #define FxaaTexOff(t, p, o, r) texture2D(t, p + (o * r), -100.0) - /*--------------------------------------------------------------------------*/ - - #define NUM_SAMPLES 5 - - // assumes colors have premultipliedAlpha, so that the calculated color contrast is scaled by alpha - float contrast( vec4 a, vec4 b ) { - vec4 diff = abs( a - b ); - return max( max( max( diff.r, diff.g ), diff.b ), diff.a ); - } - - /*============================================================================ - - FXAA3 QUALITY - PC - - ============================================================================*/ - - /*--------------------------------------------------------------------------*/ - vec4 FxaaPixelShader( - vec2 posM, - sampler2D tex, - vec2 fxaaQualityRcpFrame, - float fxaaQualityEdgeThreshold, - float fxaaQualityinvEdgeThreshold - ) { - vec4 rgbaM = FxaaTexTop(tex, posM); - vec4 rgbaS = FxaaTexOff(tex, posM, vec2( 0.0, 1.0), fxaaQualityRcpFrame.xy); - vec4 rgbaE = FxaaTexOff(tex, posM, vec2( 1.0, 0.0), fxaaQualityRcpFrame.xy); - vec4 rgbaN = FxaaTexOff(tex, posM, vec2( 0.0,-1.0), fxaaQualityRcpFrame.xy); - vec4 rgbaW = FxaaTexOff(tex, posM, vec2(-1.0, 0.0), fxaaQualityRcpFrame.xy); - // . S . - // W M E - // . N . - - bool earlyExit = max( max( max( - contrast( rgbaM, rgbaN ), - contrast( rgbaM, rgbaS ) ), - contrast( rgbaM, rgbaE ) ), - contrast( rgbaM, rgbaW ) ) - < fxaaQualityEdgeThreshold; - // . 0 . - // 0 0 0 - // . 0 . - - #if (FXAA_DISCARD == 1) - if(earlyExit) FxaaDiscard; - #else - if(earlyExit) return rgbaM; - #endif - - float contrastN = contrast( rgbaM, rgbaN ); - float contrastS = contrast( rgbaM, rgbaS ); - float contrastE = contrast( rgbaM, rgbaE ); - float contrastW = contrast( rgbaM, rgbaW ); - - float relativeVContrast = ( contrastN + contrastS ) - ( contrastE + contrastW ); - relativeVContrast *= fxaaQualityinvEdgeThreshold; - - bool horzSpan = relativeVContrast > 0.; - // . 1 . - // 0 0 0 - // . 1 . - - // 45 deg edge detection and corners of objects, aka V/H contrast is too similar - if( abs( relativeVContrast ) < .3 ) { - // locate the edge - vec2 dirToEdge; - dirToEdge.x = contrastE > contrastW ? 1. : -1.; - dirToEdge.y = contrastS > contrastN ? 1. : -1.; - // . 2 . . 1 . - // 1 0 2 ~= 0 0 1 - // . 1 . . 0 . - - // tap 2 pixels and see which ones are "outside" the edge, to - // determine if the edge is vertical or horizontal - - vec4 rgbaAlongH = FxaaTexOff(tex, posM, vec2( dirToEdge.x, -dirToEdge.y ), fxaaQualityRcpFrame.xy); - float matchAlongH = contrast( rgbaM, rgbaAlongH ); - // . 1 . - // 0 0 1 - // . 0 H - - vec4 rgbaAlongV = FxaaTexOff(tex, posM, vec2( -dirToEdge.x, dirToEdge.y ), fxaaQualityRcpFrame.xy); - float matchAlongV = contrast( rgbaM, rgbaAlongV ); - // V 1 . - // 0 0 1 - // . 0 . - - relativeVContrast = matchAlongV - matchAlongH; - relativeVContrast *= fxaaQualityinvEdgeThreshold; - - if( abs( relativeVContrast ) < .3 ) { // 45 deg edge - // 1 1 . - // 0 0 1 - // . 0 1 - - // do a simple blur - return mix( - rgbaM, - (rgbaN + rgbaS + rgbaE + rgbaW) * .25, - .4 - ); - } - - horzSpan = relativeVContrast > 0.; + fragmentShader: /* glsl */` + + uniform sampler2D tDiffuse; + uniform vec2 resolution; + varying vec2 vUv; + + #define EDGE_STEP_COUNT 6 + #define EDGE_GUESS 8.0 + #define EDGE_STEPS 1.0, 1.5, 2.0, 2.0, 2.0, 4.0 + const float edgeSteps[EDGE_STEP_COUNT] = float[EDGE_STEP_COUNT]( EDGE_STEPS ); + + float _ContrastThreshold = 0.0312; + float _RelativeThreshold = 0.063; + float _SubpixelBlending = 1.0; + + vec4 Sample( sampler2D tex2D, vec2 uv ) { + + return texture( tex2D, uv ); + + } + + float SampleLuminance( sampler2D tex2D, vec2 uv ) { + + return dot( Sample( tex2D, uv ).rgb, vec3( 0.3, 0.59, 0.11 ) ); + + } + + float SampleLuminance( sampler2D tex2D, vec2 texSize, vec2 uv, float uOffset, float vOffset ) { + + uv += texSize * vec2(uOffset, vOffset); + return SampleLuminance(tex2D, uv); + + } + + struct LuminanceData { + + float m, n, e, s, w; + float ne, nw, se, sw; + float highest, lowest, contrast; + + }; + + LuminanceData SampleLuminanceNeighborhood( sampler2D tex2D, vec2 texSize, vec2 uv ) { + + LuminanceData l; + l.m = SampleLuminance( tex2D, uv ); + l.n = SampleLuminance( tex2D, texSize, uv, 0.0, 1.0 ); + l.e = SampleLuminance( tex2D, texSize, uv, 1.0, 0.0 ); + l.s = SampleLuminance( tex2D, texSize, uv, 0.0, -1.0 ); + l.w = SampleLuminance( tex2D, texSize, uv, -1.0, 0.0 ); + + l.ne = SampleLuminance( tex2D, texSize, uv, 1.0, 1.0 ); + l.nw = SampleLuminance( tex2D, texSize, uv, -1.0, 1.0 ); + l.se = SampleLuminance( tex2D, texSize, uv, 1.0, -1.0 ); + l.sw = SampleLuminance( tex2D, texSize, uv, -1.0, -1.0 ); + + l.highest = max( max( max( max( l.n, l.e ), l.s ), l.w ), l.m ); + l.lowest = min( min( min( min( l.n, l.e ), l.s ), l.w ), l.m ); + l.contrast = l.highest - l.lowest; + return l; + + } + + bool ShouldSkipPixel( LuminanceData l ) { + + float threshold = max( _ContrastThreshold, _RelativeThreshold * l.highest ); + return l.contrast < threshold; + + } + + float DeterminePixelBlendFactor( LuminanceData l ) { + + float f = 2.0 * ( l.n + l.e + l.s + l.w ); + f += l.ne + l.nw + l.se + l.sw; + f *= 1.0 / 12.0; + f = abs( f - l.m ); + f = clamp( f / l.contrast, 0.0, 1.0 ); + + float blendFactor = smoothstep( 0.0, 1.0, f ); + return blendFactor * blendFactor * _SubpixelBlending; + + } + + struct EdgeData { + + bool isHorizontal; + float pixelStep; + float oppositeLuminance, gradient; + + }; + + EdgeData DetermineEdge( vec2 texSize, LuminanceData l ) { + + EdgeData e; + float horizontal = + abs( l.n + l.s - 2.0 * l.m ) * 2.0 + + abs( l.ne + l.se - 2.0 * l.e ) + + abs( l.nw + l.sw - 2.0 * l.w ); + float vertical = + abs( l.e + l.w - 2.0 * l.m ) * 2.0 + + abs( l.ne + l.nw - 2.0 * l.n ) + + abs( l.se + l.sw - 2.0 * l.s ); + e.isHorizontal = horizontal >= vertical; + + float pLuminance = e.isHorizontal ? l.n : l.e; + float nLuminance = e.isHorizontal ? l.s : l.w; + float pGradient = abs( pLuminance - l.m ); + float nGradient = abs( nLuminance - l.m ); + + e.pixelStep = e.isHorizontal ? texSize.y : texSize.x; + + if (pGradient < nGradient) { + + e.pixelStep = -e.pixelStep; + e.oppositeLuminance = nLuminance; + e.gradient = nGradient; + + } else { + + e.oppositeLuminance = pLuminance; + e.gradient = pGradient; + + } + + return e; + + } + + float DetermineEdgeBlendFactor( sampler2D tex2D, vec2 texSize, LuminanceData l, EdgeData e, vec2 uv ) { + + vec2 uvEdge = uv; + vec2 edgeStep; + if (e.isHorizontal) { + + uvEdge.y += e.pixelStep * 0.5; + edgeStep = vec2( texSize.x, 0.0 ); + + } else { + + uvEdge.x += e.pixelStep * 0.5; + edgeStep = vec2( 0.0, texSize.y ); + + } + + float edgeLuminance = ( l.m + e.oppositeLuminance ) * 0.5; + float gradientThreshold = e.gradient * 0.25; + + vec2 puv = uvEdge + edgeStep * edgeSteps[0]; + float pLuminanceDelta = SampleLuminance( tex2D, puv ) - edgeLuminance; + bool pAtEnd = abs( pLuminanceDelta ) >= gradientThreshold; + + for ( int i = 1; i < EDGE_STEP_COUNT && !pAtEnd; i++ ) { + + puv += edgeStep * edgeSteps[i]; + pLuminanceDelta = SampleLuminance( tex2D, puv ) - edgeLuminance; + pAtEnd = abs( pLuminanceDelta ) >= gradientThreshold; + + } + + if ( !pAtEnd ) { + + puv += edgeStep * EDGE_GUESS; + + } + + vec2 nuv = uvEdge - edgeStep * edgeSteps[0]; + float nLuminanceDelta = SampleLuminance( tex2D, nuv ) - edgeLuminance; + bool nAtEnd = abs( nLuminanceDelta ) >= gradientThreshold; + + for ( int i = 1; i < EDGE_STEP_COUNT && !nAtEnd; i++ ) { + + nuv -= edgeStep * edgeSteps[i]; + nLuminanceDelta = SampleLuminance( tex2D, nuv ) - edgeLuminance; + nAtEnd = abs( nLuminanceDelta ) >= gradientThreshold; + + } + + if ( !nAtEnd ) { + + nuv -= edgeStep * EDGE_GUESS; + + } + + float pDistance, nDistance; + if ( e.isHorizontal ) { + + pDistance = puv.x - uv.x; + nDistance = uv.x - nuv.x; + + } else { + + pDistance = puv.y - uv.y; + nDistance = uv.y - nuv.y; + + } + + float shortestDistance; + bool deltaSign; + if ( pDistance <= nDistance ) { + + shortestDistance = pDistance; + deltaSign = pLuminanceDelta >= 0.0; + + } else { + + shortestDistance = nDistance; + deltaSign = nLuminanceDelta >= 0.0; + + } + + if ( deltaSign == ( l.m - edgeLuminance >= 0.0 ) ) { + + return 0.0; + } - if(!horzSpan) rgbaN = rgbaW; - if(!horzSpan) rgbaS = rgbaE; - // . 0 . 1 - // 1 0 1 -> 0 - // . 0 . 1 - - bool pairN = contrast( rgbaM, rgbaN ) > contrast( rgbaM, rgbaS ); - if(!pairN) rgbaN = rgbaS; - - vec2 offNP; - offNP.x = (!horzSpan) ? 0.0 : fxaaQualityRcpFrame.x; - offNP.y = ( horzSpan) ? 0.0 : fxaaQualityRcpFrame.y; - - bool doneN = false; - bool doneP = false; - - float nDist = 0.; - float pDist = 0.; - - vec2 posN = posM; - vec2 posP = posM; - - int iterationsUsed = 0; - int iterationsUsedN = 0; - int iterationsUsedP = 0; - for( int i = 0; i < NUM_SAMPLES; i++ ) { - iterationsUsed = i; - - float increment = float(i + 1); - - if(!doneN) { - nDist += increment; - posN = posM + offNP * nDist; - vec4 rgbaEndN = FxaaTexTop(tex, posN.xy); - doneN = contrast( rgbaEndN, rgbaM ) > contrast( rgbaEndN, rgbaN ); - iterationsUsedN = i; - } - - if(!doneP) { - pDist += increment; - posP = posM - offNP * pDist; - vec4 rgbaEndP = FxaaTexTop(tex, posP.xy); - doneP = contrast( rgbaEndP, rgbaM ) > contrast( rgbaEndP, rgbaN ); - iterationsUsedP = i; - } - - if(doneN || doneP) break; + return 0.5 - shortestDistance / ( pDistance + nDistance ); + + } + + vec4 ApplyFXAA( sampler2D tex2D, vec2 texSize, vec2 uv ) { + + LuminanceData luminance = SampleLuminanceNeighborhood( tex2D, texSize, uv ); + if ( ShouldSkipPixel( luminance ) ) { + + return Sample( tex2D, uv ); + } + float pixelBlend = DeterminePixelBlendFactor( luminance ); + EdgeData edge = DetermineEdge( texSize, luminance ); + float edgeBlend = DetermineEdgeBlendFactor( tex2D, texSize, luminance, edge, uv ); + float finalBlend = max( pixelBlend, edgeBlend ); + + if (edge.isHorizontal) { - if ( !doneP && !doneN ) return rgbaM; // failed to find end of edge + uv.y += edge.pixelStep * finalBlend; - float dist = min( - doneN ? float( iterationsUsedN ) / float( NUM_SAMPLES - 1 ) : 1., - doneP ? float( iterationsUsedP ) / float( NUM_SAMPLES - 1 ) : 1. - ); + } else { - // hacky way of reduces blurriness of mostly diagonal edges - // but reduces AA quality - dist = pow(dist, .5); + uv.x += edge.pixelStep * finalBlend; - dist = 1. - dist; + } + + return Sample( tex2D, uv ); - return mix( - rgbaM, - rgbaN, - dist * .5 - ); - } + } - void main() { - const float edgeDetectionQuality = .2; - const float invEdgeDetectionQuality = 1. / edgeDetectionQuality; + void main() { - gl_FragColor = FxaaPixelShader( - vUv, - tDiffuse, - resolution, - edgeDetectionQuality, // [0,1] contrast needed, otherwise early discard - invEdgeDetectionQuality - ); + gl_FragColor = ApplyFXAA( tDiffuse, resolution.xy, vUv ); - } - ` + }` }; diff --git a/examples/jsm/shaders/FilmShader.js b/examples/jsm/shaders/FilmShader.js index c2d89f8abd1a71..2f25c5945fb34e 100644 --- a/examples/jsm/shaders/FilmShader.js +++ b/examples/jsm/shaders/FilmShader.js @@ -1,33 +1,26 @@ /** - * Film grain & scanlines shader - * - * - ported from HLSL to WebGL / GLSL - * https://web.archive.org/web/20210226214859/http://www.truevision3d.com/forums/showcase/staticnoise_colorblackwhite_scanline_shaders-t18698.0.html - * - * Screen Space Static Postprocessor - * - * Produces an analogue noise overlay similar to a film grain / TV static - * - * Original implementation and noise algorithm - * Pat 'Hawthorne' Shearon + * @module FilmShader + * @three_import import { FilmShader } from 'three/addons/shaders/FilmShader.js'; + */ + +/** + * TODO * - * Optimized scanlines + noise version with intensity scaling - * Georg 'Leviathan' Steinrohder + * Used by {@link FilmPass}. * - * This version is provided under a Creative Commons Attribution 3.0 License - * http://creativecommons.org/licenses/by/3.0/ + * @constant + * @type {ShaderMaterial~Shader} */ - const FilmShader = { + name: 'FilmShader', + uniforms: { 'tDiffuse': { value: null }, 'time': { value: 0.0 }, - 'nIntensity': { value: 0.5 }, - 'sIntensity': { value: 0.05 }, - 'sCount': { value: 4096 }, - 'grayscale': { value: 1 } + 'intensity': { value: 0.5 }, + 'grayscale': { value: false } }, @@ -46,19 +39,9 @@ const FilmShader = { #include - // control parameter - uniform float time; - + uniform float intensity; uniform bool grayscale; - - // noise effect intensity value (0 = no effect, 1 = full effect) - uniform float nIntensity; - - // scanlines effect intensity value (0 = no effect, 1 = full effect) - uniform float sIntensity; - - // scanlines effect count value (0 = no effect, 4096 = full effect) - uniform float sCount; + uniform float time; uniform sampler2D tDiffuse; @@ -66,32 +49,21 @@ const FilmShader = { void main() { - // sample the source - vec4 cTextureScreen = texture2D( tDiffuse, vUv ); - - // make some noise - float dx = rand( vUv + time ); - - // add noise - vec3 cResult = cTextureScreen.rgb + cTextureScreen.rgb * clamp( 0.1 + dx, 0.0, 1.0 ); + vec4 base = texture2D( tDiffuse, vUv ); - // get us a sine and cosine - vec2 sc = vec2( sin( vUv.y * sCount ), cos( vUv.y * sCount ) ); + float noise = rand( fract( vUv + time ) ); - // add scanlines - cResult += cTextureScreen.rgb * vec3( sc.x, sc.y, sc.x ) * sIntensity; + vec3 color = base.rgb + base.rgb * clamp( 0.1 + noise, 0.0, 1.0 ); - // interpolate between source and result by intensity - cResult = cTextureScreen.rgb + clamp( nIntensity, 0.0,1.0 ) * ( cResult - cTextureScreen.rgb ); + color = mix( base.rgb, color, intensity ); - // convert to grayscale if desired - if( grayscale ) { + if ( grayscale ) { - cResult = vec3( cResult.r * 0.3 + cResult.g * 0.59 + cResult.b * 0.11 ); + color = vec3( luminance( color ) ); // assuming linear-srgb } - gl_FragColor = vec4( cResult, cTextureScreen.a ); + gl_FragColor = vec4( color, base.a ); }`, diff --git a/examples/jsm/shaders/FocusShader.js b/examples/jsm/shaders/FocusShader.js index e15259c69f03e7..6bde0cc5882194 100644 --- a/examples/jsm/shaders/FocusShader.js +++ b/examples/jsm/shaders/FocusShader.js @@ -1,11 +1,18 @@ /** - * Focus shader - * based on PaintEffect postprocess from ro.me - * http://code.google.com/p/3-dreams-of-black/source/browse/deploy/js/effects/PaintEffect.js + * @module FocusShader + * @three_import import { FocusShader } from 'three/addons/shaders/FocusShader.js'; */ +/** + * Focus shader based on [PaintEffect postprocess from ro.me]{@link http://code.google.com/p/3-dreams-of-black/source/browse/deploy/js/effects/PaintEffect.js}. + * + * @constant + * @type {ShaderMaterial~Shader} + */ const FocusShader = { + name: 'FocusShader', + uniforms: { 'tDiffuse': { value: null }, diff --git a/examples/jsm/shaders/FreiChenShader.js b/examples/jsm/shaders/FreiChenShader.js index 2a85f2f938c224..9912356cf61234 100644 --- a/examples/jsm/shaders/FreiChenShader.js +++ b/examples/jsm/shaders/FreiChenShader.js @@ -3,14 +3,23 @@ import { } from 'three'; /** - * Edge Detection Shader using Frei-Chen filter - * Based on http://rastergrid.com/blog/2011/01/frei-chen-edge-detector + * @module FreiChenShader + * @three_import import { FreiChenShader } from 'three/addons/shaders/FreiChenShader.js'; + */ + +/** + * Edge Detection Shader using Frei-Chen filter. + * Based on {@link http://rastergrid.com/blog/2011/01/frei-chen-edge-detector}. * * aspect: vec2 of (1/width, 1/height) + * + * @constant + * @type {ShaderMaterial~Shader} */ - const FreiChenShader = { + name: 'FreiChenShader', + uniforms: { 'tDiffuse': { value: null }, diff --git a/examples/jsm/shaders/GTAOShader.js b/examples/jsm/shaders/GTAOShader.js new file mode 100644 index 00000000000000..83ff79fcb96bf2 --- /dev/null +++ b/examples/jsm/shaders/GTAOShader.js @@ -0,0 +1,421 @@ +import { + DataTexture, + Matrix4, + RepeatWrapping, + Vector2, + Vector3, +} from 'three'; + +/** + * @module GTAOShader + * @three_import import { GTAOShader } from 'three/addons/shaders/GTAOShader.js'; + */ + +/** + * GTAO shader. Use by {@link GTAOPass}. + * + * References: + * - [Practical Realtime Strategies for Accurate Indirect Occlusion]{@link https://iryoku.com/downloads/Practical-Realtime-Strategies-for-Accurate-Indirect-Occlusion.pdf}. + * - [Horizon-Based Indirect Lighting (HBIL)]{@link https://github.com/Patapom/GodComplex/blob/master/Tests/TestHBIL/2018%20Mayaux%20-%20Horizon-Based%20Indirect%20Lighting%20(HBIL).pdf} + * + * @constant + * @type {ShaderMaterial~Shader} + */ +const GTAOShader = { + + name: 'GTAOShader', + + defines: { + PERSPECTIVE_CAMERA: 1, + SAMPLES: 16, + NORMAL_VECTOR_TYPE: 1, + DEPTH_SWIZZLING: 'x', + SCREEN_SPACE_RADIUS: 0, + SCREEN_SPACE_RADIUS_SCALE: 100.0, + SCENE_CLIP_BOX: 0, + }, + + uniforms: { + tNormal: { value: null }, + tDepth: { value: null }, + tNoise: { value: null }, + resolution: { value: new Vector2() }, + cameraNear: { value: null }, + cameraFar: { value: null }, + cameraProjectionMatrix: { value: new Matrix4() }, + cameraProjectionMatrixInverse: { value: new Matrix4() }, + cameraWorldMatrix: { value: new Matrix4() }, + radius: { value: 0.25 }, + distanceExponent: { value: 1. }, + thickness: { value: 1. }, + distanceFallOff: { value: 1. }, + scale: { value: 1. }, + sceneBoxMin: { value: new Vector3( - 1, - 1, - 1 ) }, + sceneBoxMax: { value: new Vector3( 1, 1, 1 ) }, + }, + + vertexShader: /* glsl */` + + varying vec2 vUv; + + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + }`, + + fragmentShader: /* glsl */` + varying vec2 vUv; + uniform highp sampler2D tNormal; + uniform highp sampler2D tDepth; + uniform sampler2D tNoise; + uniform vec2 resolution; + uniform float cameraNear; + uniform float cameraFar; + uniform mat4 cameraProjectionMatrix; + uniform mat4 cameraProjectionMatrixInverse; + uniform mat4 cameraWorldMatrix; + uniform float radius; + uniform float distanceExponent; + uniform float thickness; + uniform float distanceFallOff; + uniform float scale; + #if SCENE_CLIP_BOX == 1 + uniform vec3 sceneBoxMin; + uniform vec3 sceneBoxMax; + #endif + + #include + #include + + #ifndef FRAGMENT_OUTPUT + #define FRAGMENT_OUTPUT vec4(vec3(ao), 1.) + #endif + + vec3 getViewPosition(const in vec2 screenPosition, const in float depth) { + vec4 clipSpacePosition = vec4(vec3(screenPosition, depth) * 2.0 - 1.0, 1.0); + vec4 viewSpacePosition = cameraProjectionMatrixInverse * clipSpacePosition; + return viewSpacePosition.xyz / viewSpacePosition.w; + } + + float getDepth(const vec2 uv) { + return textureLod(tDepth, uv.xy, 0.0).DEPTH_SWIZZLING; + } + + float fetchDepth(const ivec2 uv) { + return texelFetch(tDepth, uv.xy, 0).DEPTH_SWIZZLING; + } + + float getViewZ(const in float depth) { + #if PERSPECTIVE_CAMERA == 1 + return perspectiveDepthToViewZ(depth, cameraNear, cameraFar); + #else + return orthographicDepthToViewZ(depth, cameraNear, cameraFar); + #endif + } + + vec3 computeNormalFromDepth(const vec2 uv) { + vec2 size = vec2(textureSize(tDepth, 0)); + ivec2 p = ivec2(uv * size); + float c0 = fetchDepth(p); + float l2 = fetchDepth(p - ivec2(2, 0)); + float l1 = fetchDepth(p - ivec2(1, 0)); + float r1 = fetchDepth(p + ivec2(1, 0)); + float r2 = fetchDepth(p + ivec2(2, 0)); + float b2 = fetchDepth(p - ivec2(0, 2)); + float b1 = fetchDepth(p - ivec2(0, 1)); + float t1 = fetchDepth(p + ivec2(0, 1)); + float t2 = fetchDepth(p + ivec2(0, 2)); + float dl = abs((2.0 * l1 - l2) - c0); + float dr = abs((2.0 * r1 - r2) - c0); + float db = abs((2.0 * b1 - b2) - c0); + float dt = abs((2.0 * t1 - t2) - c0); + vec3 ce = getViewPosition(uv, c0).xyz; + vec3 dpdx = (dl < dr) ? ce - getViewPosition((uv - vec2(1.0 / size.x, 0.0)), l1).xyz : -ce + getViewPosition((uv + vec2(1.0 / size.x, 0.0)), r1).xyz; + vec3 dpdy = (db < dt) ? ce - getViewPosition((uv - vec2(0.0, 1.0 / size.y)), b1).xyz : -ce + getViewPosition((uv + vec2(0.0, 1.0 / size.y)), t1).xyz; + return normalize(cross(dpdx, dpdy)); + } + + vec3 getViewNormal(const vec2 uv) { + #if NORMAL_VECTOR_TYPE == 2 + return normalize(textureLod(tNormal, uv, 0.).rgb); + #elif NORMAL_VECTOR_TYPE == 1 + return unpackRGBToNormal(textureLod(tNormal, uv, 0.).rgb); + #else + return computeNormalFromDepth(uv); + #endif + } + + vec3 getSceneUvAndDepth(vec3 sampleViewPos) { + vec4 sampleClipPos = cameraProjectionMatrix * vec4(sampleViewPos, 1.); + vec2 sampleUv = sampleClipPos.xy / sampleClipPos.w * 0.5 + 0.5; + float sampleSceneDepth = getDepth(sampleUv); + return vec3(sampleUv, sampleSceneDepth); + } + + void main() { + float depth = getDepth(vUv.xy); + if (depth >= 1.0) { + discard; + return; + } + vec3 viewPos = getViewPosition(vUv, depth); + vec3 viewNormal = getViewNormal(vUv); + + float radiusToUse = radius; + float distanceFalloffToUse = thickness; + #if SCREEN_SPACE_RADIUS == 1 + float radiusScale = getViewPosition(vec2(0.5 + float(SCREEN_SPACE_RADIUS_SCALE) / resolution.x, 0.0), depth).x; + radiusToUse *= radiusScale; + distanceFalloffToUse *= radiusScale; + #endif + + #if SCENE_CLIP_BOX == 1 + vec3 worldPos = (cameraWorldMatrix * vec4(viewPos, 1.0)).xyz; + float boxDistance = length(max(vec3(0.0), max(sceneBoxMin - worldPos, worldPos - sceneBoxMax))); + if (boxDistance > radiusToUse) { + discard; + return; + } + #endif + + vec2 noiseResolution = vec2(textureSize(tNoise, 0)); + vec2 noiseUv = vUv * resolution / noiseResolution; + vec4 noiseTexel = textureLod(tNoise, noiseUv, 0.0); + vec3 randomVec = noiseTexel.xyz * 2.0 - 1.0; + vec3 tangent = normalize(vec3(randomVec.xy, 0.)); + vec3 bitangent = vec3(-tangent.y, tangent.x, 0.); + mat3 kernelMatrix = mat3(tangent, bitangent, vec3(0., 0., 1.)); + + const int DIRECTIONS = SAMPLES < 30 ? 3 : 5; + const int STEPS = (SAMPLES + DIRECTIONS - 1) / DIRECTIONS; + float ao = 0.0; + for (int i = 0; i < DIRECTIONS; ++i) { + + float angle = float(i) / float(DIRECTIONS) * PI; + vec4 sampleDir = vec4(cos(angle), sin(angle), 0., 0.5 + 0.5 * noiseTexel.w); + sampleDir.xyz = normalize(kernelMatrix * sampleDir.xyz); + + vec3 viewDir = normalize(-viewPos.xyz); + vec3 sliceBitangent = normalize(cross(sampleDir.xyz, viewDir)); + vec3 sliceTangent = cross(sliceBitangent, viewDir); + vec3 normalInSlice = normalize(viewNormal - sliceBitangent * dot(viewNormal, sliceBitangent)); + + vec3 tangentToNormalInSlice = cross(normalInSlice, sliceBitangent); + vec2 cosHorizons = vec2(dot(viewDir, tangentToNormalInSlice), dot(viewDir, -tangentToNormalInSlice)); + + for (int j = 0; j < STEPS; ++j) { + vec3 sampleViewOffset = sampleDir.xyz * radiusToUse * sampleDir.w * pow(float(j + 1) / float(STEPS), distanceExponent); + + vec3 sampleSceneUvDepth = getSceneUvAndDepth(viewPos + sampleViewOffset); + vec3 sampleSceneViewPos = getViewPosition(sampleSceneUvDepth.xy, sampleSceneUvDepth.z); + vec3 viewDelta = sampleSceneViewPos - viewPos; + if (abs(viewDelta.z) < thickness) { + float sampleCosHorizon = dot(viewDir, normalize(viewDelta)); + cosHorizons.x += max(0., (sampleCosHorizon - cosHorizons.x) * mix(1., 2. / float(j + 2), distanceFallOff)); + } + + sampleSceneUvDepth = getSceneUvAndDepth(viewPos - sampleViewOffset); + sampleSceneViewPos = getViewPosition(sampleSceneUvDepth.xy, sampleSceneUvDepth.z); + viewDelta = sampleSceneViewPos - viewPos; + if (abs(viewDelta.z) < thickness) { + float sampleCosHorizon = dot(viewDir, normalize(viewDelta)); + cosHorizons.y += max(0., (sampleCosHorizon - cosHorizons.y) * mix(1., 2. / float(j + 2), distanceFallOff)); + } + } + + vec2 sinHorizons = sqrt(1. - cosHorizons * cosHorizons); + float nx = dot(normalInSlice, sliceTangent); + float ny = dot(normalInSlice, viewDir); + float nxb = 1. / 2. * (acos(cosHorizons.y) - acos(cosHorizons.x) + sinHorizons.x * cosHorizons.x - sinHorizons.y * cosHorizons.y); + float nyb = 1. / 2. * (2. - cosHorizons.x * cosHorizons.x - cosHorizons.y * cosHorizons.y); + float occlusion = nx * nxb + ny * nyb; + ao += occlusion; + } + + ao = clamp(ao / float(DIRECTIONS), 0., 1.); + #if SCENE_CLIP_BOX == 1 + ao = mix(ao, 1., smoothstep(0., radiusToUse, boxDistance)); + #endif + ao = pow(ao, scale); + + gl_FragColor = FRAGMENT_OUTPUT; + }` + +}; + +/** + * GTAO depth shader. Use by {@link GTAOPass}. + * + * @constant + * @type {Object} + */ +const GTAODepthShader = { + + name: 'GTAODepthShader', + + defines: { + PERSPECTIVE_CAMERA: 1 + }, + + uniforms: { + tDepth: { value: null }, + cameraNear: { value: null }, + cameraFar: { value: null }, + }, + + vertexShader: /* glsl */` + varying vec2 vUv; + + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + }`, + + fragmentShader: /* glsl */` + uniform sampler2D tDepth; + uniform float cameraNear; + uniform float cameraFar; + varying vec2 vUv; + + #include + + float getLinearDepth( const in vec2 screenPosition ) { + #if PERSPECTIVE_CAMERA == 1 + float fragCoordZ = texture2D( tDepth, screenPosition ).x; + float viewZ = perspectiveDepthToViewZ( fragCoordZ, cameraNear, cameraFar ); + return viewZToOrthographicDepth( viewZ, cameraNear, cameraFar ); + #else + return texture2D( tDepth, screenPosition ).x; + #endif + } + + void main() { + float depth = getLinearDepth( vUv ); + gl_FragColor = vec4( vec3( 1.0 - depth ), 1.0 ); + + }` + +}; + +/** + * GTAO blend shader. Use by {@link GTAOPass}. + * + * @constant + * @type {Object} + */ +const GTAOBlendShader = { + + name: 'GTAOBlendShader', + + uniforms: { + tDiffuse: { value: null }, + intensity: { value: 1.0 } + }, + + vertexShader: /* glsl */` + varying vec2 vUv; + + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + }`, + + fragmentShader: /* glsl */` + uniform float intensity; + uniform sampler2D tDiffuse; + varying vec2 vUv; + + void main() { + vec4 texel = texture2D( tDiffuse, vUv ); + gl_FragColor = vec4(mix(vec3(1.), texel.rgb, intensity), texel.a); + }` + +}; + + +function generateMagicSquareNoise( size = 5 ) { + + const noiseSize = Math.floor( size ) % 2 === 0 ? Math.floor( size ) + 1 : Math.floor( size ); + const magicSquare = generateMagicSquare( noiseSize ); + const noiseSquareSize = magicSquare.length; + const data = new Uint8Array( noiseSquareSize * 4 ); + + for ( let inx = 0; inx < noiseSquareSize; ++ inx ) { + + const iAng = magicSquare[ inx ]; + const angle = ( 2 * Math.PI * iAng ) / noiseSquareSize; + const randomVec = new Vector3( + Math.cos( angle ), + Math.sin( angle ), + 0 + ).normalize(); + data[ inx * 4 ] = ( randomVec.x * 0.5 + 0.5 ) * 255; + data[ inx * 4 + 1 ] = ( randomVec.y * 0.5 + 0.5 ) * 255; + data[ inx * 4 + 2 ] = 127; + data[ inx * 4 + 3 ] = 255; + + } + + const noiseTexture = new DataTexture( data, noiseSize, noiseSize ); + noiseTexture.wrapS = RepeatWrapping; + noiseTexture.wrapT = RepeatWrapping; + noiseTexture.needsUpdate = true; + + return noiseTexture; + +} + +function generateMagicSquare( size ) { + + const noiseSize = Math.floor( size ) % 2 === 0 ? Math.floor( size ) + 1 : Math.floor( size ); + const noiseSquareSize = noiseSize * noiseSize; + const magicSquare = Array( noiseSquareSize ).fill( 0 ); + let i = Math.floor( noiseSize / 2 ); + let j = noiseSize - 1; + + for ( let num = 1; num <= noiseSquareSize; ) { + + if ( i === - 1 && j === noiseSize ) { + + j = noiseSize - 2; + i = 0; + + } else { + + if ( j === noiseSize ) { + + j = 0; + + } + + if ( i < 0 ) { + + i = noiseSize - 1; + + } + + } + + if ( magicSquare[ i * noiseSize + j ] !== 0 ) { + + j -= 2; + i ++; + continue; + + } else { + + magicSquare[ i * noiseSize + j ] = num ++; + + } + + j ++; + i --; + + } + + return magicSquare; + +} + + +export { generateMagicSquareNoise, GTAOShader, GTAODepthShader, GTAOBlendShader }; diff --git a/examples/jsm/shaders/GammaCorrectionShader.js b/examples/jsm/shaders/GammaCorrectionShader.js index 7108187c2067e2..3f5c8a617a8190 100644 --- a/examples/jsm/shaders/GammaCorrectionShader.js +++ b/examples/jsm/shaders/GammaCorrectionShader.js @@ -1,10 +1,21 @@ /** - * Gamma Correction Shader - * http://en.wikipedia.org/wiki/gamma_correction + * @module GammaCorrectionShader + * @three_import import { GammaCorrectionShader } from 'three/addons/shaders/GammaCorrectionShader.js'; */ +/** + * Gamma Correction Shader + * + * References: + * - {@link http://en.wikipedia.org/wiki/gamma_correction}. + * + * @constant + * @type {ShaderMaterial~Shader} + */ const GammaCorrectionShader = { + name: 'GammaCorrectionShader', + uniforms: { 'tDiffuse': { value: null } @@ -32,7 +43,7 @@ const GammaCorrectionShader = { vec4 tex = texture2D( tDiffuse, vUv ); - gl_FragColor = LinearTosRGB( tex ); + gl_FragColor = sRGBTransferOETF( tex ); }` diff --git a/examples/jsm/shaders/GodRaysShader.js b/examples/jsm/shaders/GodRaysShader.js index 65afbb135e7104..ba0dd89d880d8c 100644 --- a/examples/jsm/shaders/GodRaysShader.js +++ b/examples/jsm/shaders/GodRaysShader.js @@ -3,6 +3,11 @@ import { Vector3 } from 'three'; +/** + * @module GodRaysShader + * @three_import import * as GodRaysShader from 'three/addons/shaders/GodRaysShader.js'; + */ + /** * God-rays (crepuscular rays) * @@ -12,17 +17,20 @@ import { * sample count to produce a blur filter with large support. * * My implementation performs 3 passes, similar to the implementation from Sousa. I found - * just 6 samples per pass produced acceptible results. The blur is applied three times, + * just 6 samples per pass produced acceptable results. The blur is applied three times, * with decreasing filter support. The result is equivalent to a single pass with * 6*6*6 = 216 samples. * * References: + * - [Sousa2008, Crysis Next Gen Effects, GDC2008]{@link http://www.crytek.com/sites/default/files/GDC08_SousaT_CrysisEffects.ppt}. * - * Sousa2008 - Crysis Next Gen Effects, GDC2008, http://www.crytek.com/sites/default/files/GDC08_SousaT_CrysisEffects.ppt + * @constant + * @type {ShaderMaterial~Shader} */ - const GodRaysDepthMaskShader = { + name: 'GodRaysDepthMaskShader', + uniforms: { tInput: { @@ -70,10 +78,14 @@ const GodRaysDepthMaskShader = { * * The results of the previous pass are re-blurred, each time with a * decreased distance between samples. + * + * @constant + * @type {ShaderMaterial~Shader} */ - const GodRaysGenerateShader = { + name: 'GodRaysGenerateShader', + uniforms: { tInput: { @@ -132,12 +144,12 @@ const GodRaysGenerateShader = { // - see http://code.google.com/p/chromium/issues/detail?id=153105 /* - // Unrolling didnt do much on my hardware (ATI Mobility Radeon 3450), + // Unrolling didn't do much on my hardware (ATI Mobility Radeon 3450), // so i've just left the loop "for ( float i = 0.0; i < TAPS_PER_PASS; i += 1.0 ) {", - // Accumulate samples, making sure we dont walk past the light source. + // Accumulate samples, making sure we don't walk past the light source. // The check for uv.y < 1 would not be necessary with "border" UV wrap // mode, with a black border color. I don't think this is currently @@ -190,10 +202,14 @@ const GodRaysGenerateShader = { /** * Additively applies god rays from texture tGodRays to a background (tColors). * fGodRayIntensity attenuates the god rays. + * + * @constant + * @type {ShaderMaterial~Shader} */ - const GodRaysCombineShader = { + name: 'GodRaysCombineShader', + uniforms: { tColors: { @@ -247,10 +263,14 @@ const GodRaysCombineShader = { /** * A dodgy sun/sky shader. Makes a bright spot at the sun location. Would be * cheaper/faster/simpler to implement this as a simple sun sprite. + * + * @constant + * @type {Object} */ - const GodRaysFakeSunShader = { + name: 'GodRaysFakeSunShader', + uniforms: { vSunPositionScreenSpace: { diff --git a/examples/jsm/shaders/HalftoneShader.js b/examples/jsm/shaders/HalftoneShader.js index b586070349fc84..78b3f57ff84071 100644 --- a/examples/jsm/shaders/HalftoneShader.js +++ b/examples/jsm/shaders/HalftoneShader.js @@ -1,12 +1,23 @@ /** - * RGB Halftone shader for three.js. - * NOTE: - * Shape (1 = Dot, 2 = Ellipse, 3 = Line, 4 = Square) - * Blending Mode (1 = Linear, 2 = Multiply, 3 = Add, 4 = Lighter, 5 = Darker) + * @module HalftoneShader + * @three_import import { HalftoneShader } from 'three/addons/shaders/HalftoneShader.js'; */ +/** + * RGB Halftone shader. + * + * Used by {@link HalftonePass}. + * + * Shape (1 = Dot, 2 = Ellipse, 3 = Line, 4 = Square) + * Blending Mode (1 = Linear, 2 = Multiply, 3 = Add, 4 = Lighter, 5 = Darker) + * + * @constant + * @type {ShaderMaterial~Shader} + */ const HalftoneShader = { + name: 'HalftoneShader', + uniforms: { 'tDiffuse': { value: null }, 'shape': { value: 1 }, diff --git a/examples/jsm/shaders/HorizontalBlurShader.js b/examples/jsm/shaders/HorizontalBlurShader.js index 78446ee70f7466..46c7892a4d2929 100644 --- a/examples/jsm/shaders/HorizontalBlurShader.js +++ b/examples/jsm/shaders/HorizontalBlurShader.js @@ -1,14 +1,25 @@ /** - * Two pass Gaussian blur filter (horizontal and vertical blur shaders) - * - see http://www.cake23.de/traveling-wavefronts-lit-up.html + * @module HorizontalBlurShader + * @three_import import { HorizontalBlurShader } from 'three/addons/shaders/HorizontalBlurShader.js'; + */ + +/** + * Two pass Gaussian blur filter (horizontal and vertical blur shaders). + * + * References: + * - {@link http://www.cake23.de/traveling-wavefronts-lit-up.html}. * * - 9 samples per pass * - standard deviation 2.7 * - "h" and "v" parameters should be set to "1 / width" and "1 / height" + * + * @constant + * @type {ShaderMaterial~Shader} */ - const HorizontalBlurShader = { + name: 'HorizontalBlurShader', + uniforms: { 'tDiffuse': { value: null }, diff --git a/examples/jsm/shaders/HorizontalTiltShiftShader.js b/examples/jsm/shaders/HorizontalTiltShiftShader.js index 56efbb5923100b..2d4a90261064c5 100644 --- a/examples/jsm/shaders/HorizontalTiltShiftShader.js +++ b/examples/jsm/shaders/HorizontalTiltShiftShader.js @@ -1,14 +1,23 @@ /** - * Simple fake tilt-shift effect, modulating two pass Gaussian blur (see above) by vertical position + * @module HorizontalTiltShiftShader + * @three_import import { HorizontalTiltShiftShader } from 'three/addons/shaders/HorizontalTiltShiftShader.js'; + */ + +/** + * Simple fake tilt-shift effect, modulating two pass Gaussian blur (see above) by vertical position. * * - 9 samples per pass * - standard deviation 2.7 * - "h" and "v" parameters should be set to "1 / width" and "1 / height" * - "r" parameter control where "focused" horizontal line lies + * + * @constant + * @type {ShaderMaterial~Shader} */ - const HorizontalTiltShiftShader = { + name: 'HorizontalTiltShiftShader', + uniforms: { 'tDiffuse': { value: null }, diff --git a/examples/jsm/shaders/HueSaturationShader.js b/examples/jsm/shaders/HueSaturationShader.js index 880dcae07325eb..08bf3cd6b4929a 100644 --- a/examples/jsm/shaders/HueSaturationShader.js +++ b/examples/jsm/shaders/HueSaturationShader.js @@ -1,12 +1,21 @@ /** - * Hue and saturation adjustment - * https://github.com/evanw/glfx.js + * @module HueSaturationShader + * @three_import import { HueSaturationShader } from 'three/addons/shaders/HueSaturationShader.js'; + */ + +/** + * Hue and saturation adjustment, {@link https://github.com/evanw/glfx.js}. + * * hue: -1 to 1 (-1 is 180 degrees in the negative direction, 0 is no change, etc. * saturation: -1 to 1 (-1 is solid gray, 0 is no change, and 1 is maximum contrast) + * + * @constant + * @type {ShaderMaterial~Shader} */ - const HueSaturationShader = { + name: 'HueSaturationShader', + uniforms: { 'tDiffuse': { value: null }, diff --git a/examples/jsm/shaders/KaleidoShader.js b/examples/jsm/shaders/KaleidoShader.js index 9f1fb0aa2f18bf..46da8d8c13462d 100644 --- a/examples/jsm/shaders/KaleidoShader.js +++ b/examples/jsm/shaders/KaleidoShader.js @@ -1,15 +1,24 @@ /** - * Kaleidoscope Shader + * @module KaleidoShader + * @three_import import { KaleidoShader } from 'three/addons/shaders/KaleidoShader.js'; + */ + +/** + * Kaleidoscope Shader. * Radial reflection around center point - * Ported from: http://pixelshaders.com/editor/ - * by Toby Schachman / http://tobyschachman.com/ + * Ported from: {@link http://pixelshaders.com/editor/} + * by [Toby Schachman]{@link http://tobyschachman.com/} * * sides: number of reflections * angle: initial angle in radians + * + * @constant + * @type {ShaderMaterial~Shader} */ - const KaleidoShader = { + name: 'KaleidoShader', + uniforms: { 'tDiffuse': { value: null }, diff --git a/examples/jsm/shaders/LuminosityHighPassShader.js b/examples/jsm/shaders/LuminosityHighPassShader.js index 38937db7ad6b39..16ff4e69f7cf74 100644 --- a/examples/jsm/shaders/LuminosityHighPassShader.js +++ b/examples/jsm/shaders/LuminosityHighPassShader.js @@ -3,13 +3,19 @@ import { } from 'three'; /** - * Luminosity - * http://en.wikipedia.org/wiki/Luminosity + * @module LuminosityHighPassShader + * @three_import import { LuminosityHighPassShader } from 'three/addons/shaders/LuminosityHighPassShader.js'; */ +/** + * Luminosity high pass shader. + * + * @constant + * @type {ShaderMaterial~Shader} + */ const LuminosityHighPassShader = { - shaderID: 'luminosityHighPass', + name: 'LuminosityHighPassShader', uniforms: { @@ -47,9 +53,7 @@ const LuminosityHighPassShader = { vec4 texel = texture2D( tDiffuse, vUv ); - vec3 luma = vec3( 0.299, 0.587, 0.114 ); - - float v = dot( texel.xyz, luma ); + float v = luminance( texel.xyz ); vec4 outputColor = vec4( defaultColor.rgb, defaultOpacity ); diff --git a/examples/jsm/shaders/LuminosityShader.js b/examples/jsm/shaders/LuminosityShader.js index 678d9cfd374b80..6e2ef6107fb322 100644 --- a/examples/jsm/shaders/LuminosityShader.js +++ b/examples/jsm/shaders/LuminosityShader.js @@ -1,10 +1,18 @@ /** - * Luminosity - * http://en.wikipedia.org/wiki/Luminosity + * @module LuminosityShader + * @three_import import { LuminosityShader } from 'three/addons/shaders/LuminosityShader.js'; */ +/** + * Luminosity shader. + * + * @constant + * @type {ShaderMaterial~Shader} + */ const LuminosityShader = { + name: 'LuminosityShader', + uniforms: { 'tDiffuse': { value: null } diff --git a/examples/jsm/shaders/MMDToonShader.js b/examples/jsm/shaders/MMDToonShader.js deleted file mode 100644 index 9a41cbf3477a04..00000000000000 --- a/examples/jsm/shaders/MMDToonShader.js +++ /dev/null @@ -1,132 +0,0 @@ -/** - * MMD Toon Shader - * - * This shader is extended from MeshPhongMaterial, and merged algorithms with - * MeshToonMaterial and MeshMetcapMaterial. - * Ideas came from https://github.com/mrdoob/three.js/issues/19609 - * - * Combining steps: - * * Declare matcap uniform. - * * Add gradientmap_pars_fragment. - * * Use gradient irradiances instead of dotNL irradiance from MeshPhongMaterial. - * (Replace lights_phong_pars_fragment with lights_mmd_toon_pars_fragment) - * * Add mmd_toon_matcap_fragment. - */ - -import { UniformsUtils, ShaderLib } from 'three'; - -const lights_mmd_toon_pars_fragment = /* glsl */` -varying vec3 vViewPosition; - -struct BlinnPhongMaterial { - - vec3 diffuseColor; - vec3 specularColor; - float specularShininess; - float specularStrength; - -}; - -void RE_Direct_BlinnPhong( const in IncidentLight directLight, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) { - - vec3 irradiance = getGradientIrradiance( geometry.normal, directLight.direction ) * directLight.color; - - reflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor ); - - reflectedLight.directSpecular += irradiance * BRDF_BlinnPhong( directLight.direction, geometry.viewDir, geometry.normal, material.specularColor, material.specularShininess ) * material.specularStrength; - -} - -void RE_IndirectDiffuse_BlinnPhong( const in vec3 irradiance, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) { - - reflectedLight.indirectDiffuse += irradiance * BRDF_Lambert( material.diffuseColor ); - -} - -#define RE_Direct RE_Direct_BlinnPhong -#define RE_IndirectDiffuse RE_IndirectDiffuse_BlinnPhong -`; - -const mmd_toon_matcap_fragment = /* glsl */` -#ifdef USE_MATCAP - - vec3 viewDir = normalize( vViewPosition ); - vec3 x = normalize( vec3( viewDir.z, 0.0, - viewDir.x ) ); - vec3 y = cross( viewDir, x ); - vec2 uv = vec2( dot( x, normal ), dot( y, normal ) ) * 0.495 + 0.5; // 0.495 to remove artifacts caused by undersized matcap disks - vec4 matcapColor = texture2D( matcap, uv ); - - #ifdef MATCAP_BLENDING_MULTIPLY - - outgoingLight *= matcapColor.rgb; - - #elif defined( MATCAP_BLENDING_ADD ) - - outgoingLight += matcapColor.rgb; - - #endif - -#endif -`; - -const MMDToonShader = { - - defines: { - TOON: true, - MATCAP: true, - MATCAP_BLENDING_ADD: true, - }, - - uniforms: UniformsUtils.merge( [ - ShaderLib.toon.uniforms, - ShaderLib.phong.uniforms, - ShaderLib.matcap.uniforms, - ] ), - - vertexShader: - ShaderLib.phong.vertexShader - .replace( - '#include ', - '' - ) - .replace( - '#include ', - '' - ), - - fragmentShader: - ShaderLib.phong.fragmentShader - .replace( - '#include ', - ` - #ifdef USE_MATCAP - uniform sampler2D matcap; - #endif - - #include - ` - ) - .replace( - '#include ', - ` - #include - ` - ) - .replace( - '#include ', - '' - ) - .replace( - '#include ', - lights_mmd_toon_pars_fragment - ) - .replace( - '#include ', - ` - ${mmd_toon_matcap_fragment} - ` - ) - -}; - -export { MMDToonShader }; diff --git a/examples/jsm/shaders/MirrorShader.js b/examples/jsm/shaders/MirrorShader.js index 6544cec03f652a..7850e0f206297f 100644 --- a/examples/jsm/shaders/MirrorShader.js +++ b/examples/jsm/shaders/MirrorShader.js @@ -1,12 +1,20 @@ /** - * Mirror Shader - * Copies half the input to the other half - * - * side: side of input to mirror (0 = left, 1 = right, 2 = top, 3 = bottom) + * @module MirrorShader + * @three_import import { MirrorShader } from 'three/addons/shaders/MirrorShader.js'; */ +/** + * Copies half the input to the other half. + * + * side: side of input to mirror (0 = left, 1 = right, 2 = top, 3 = bottom). + * + * @constant + * @type {ShaderMaterial~Shader} + */ const MirrorShader = { + name: 'MirrorShader', + uniforms: { 'tDiffuse': { value: null }, diff --git a/examples/jsm/shaders/NormalMapShader.js b/examples/jsm/shaders/NormalMapShader.js index ecc88da6eefdc6..b600c551b388d0 100644 --- a/examples/jsm/shaders/NormalMapShader.js +++ b/examples/jsm/shaders/NormalMapShader.js @@ -3,12 +3,19 @@ import { } from 'three'; /** - * Normal map shader - * - compute normals from heightmap + * @module NormalMapShader + * @three_import import { NormalMapShader } from 'three/addons/shaders/NormalMapShader.js'; */ +/** + * Normal map shader, compute normals from heightmap. + * @constant + * @type {ShaderMaterial~Shader} + */ const NormalMapShader = { + name: 'NormalMapShader', + uniforms: { 'heightMap': { value: null }, diff --git a/examples/jsm/shaders/OutputShader.js b/examples/jsm/shaders/OutputShader.js new file mode 100644 index 00000000000000..5f257829fe655f --- /dev/null +++ b/examples/jsm/shaders/OutputShader.js @@ -0,0 +1,103 @@ +/** + * @module OutputShader + * @three_import import { OutputShader } from 'three/addons/shaders/OutputShader.js'; + */ + +/** + * Performs tone mapping and color space conversion for + * FX workflows. + * + * Used by {@link OutputPass}. + * + * @constant + * @type {ShaderMaterial~Shader} + */ +const OutputShader = { + + name: 'OutputShader', + + uniforms: { + + 'tDiffuse': { value: null }, + 'toneMappingExposure': { value: 1 } + + }, + + vertexShader: /* glsl */` + precision highp float; + + uniform mat4 modelViewMatrix; + uniform mat4 projectionMatrix; + + attribute vec3 position; + attribute vec2 uv; + + varying vec2 vUv; + + void main() { + + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + + }`, + + fragmentShader: /* glsl */` + + precision highp float; + + uniform sampler2D tDiffuse; + + #include + #include + + varying vec2 vUv; + + void main() { + + gl_FragColor = texture2D( tDiffuse, vUv ); + + // tone mapping + + #ifdef LINEAR_TONE_MAPPING + + gl_FragColor.rgb = LinearToneMapping( gl_FragColor.rgb ); + + #elif defined( REINHARD_TONE_MAPPING ) + + gl_FragColor.rgb = ReinhardToneMapping( gl_FragColor.rgb ); + + #elif defined( CINEON_TONE_MAPPING ) + + gl_FragColor.rgb = CineonToneMapping( gl_FragColor.rgb ); + + #elif defined( ACES_FILMIC_TONE_MAPPING ) + + gl_FragColor.rgb = ACESFilmicToneMapping( gl_FragColor.rgb ); + + #elif defined( AGX_TONE_MAPPING ) + + gl_FragColor.rgb = AgXToneMapping( gl_FragColor.rgb ); + + #elif defined( NEUTRAL_TONE_MAPPING ) + + gl_FragColor.rgb = NeutralToneMapping( gl_FragColor.rgb ); + + #elif defined( CUSTOM_TONE_MAPPING ) + + gl_FragColor.rgb = CustomToneMapping( gl_FragColor.rgb ); + + #endif + + // color space + + #ifdef SRGB_TRANSFER + + gl_FragColor = sRGBTransferOETF( gl_FragColor ); + + #endif + + }` + +}; + +export { OutputShader }; diff --git a/examples/jsm/shaders/PoissonDenoiseShader.js b/examples/jsm/shaders/PoissonDenoiseShader.js new file mode 100644 index 00000000000000..cd2e788226de0b --- /dev/null +++ b/examples/jsm/shaders/PoissonDenoiseShader.js @@ -0,0 +1,235 @@ +import { + Matrix4, + Vector2, + Vector3, +} from 'three'; + +/** + * @module PoissonDenoiseShader + * @three_import import { PoissonDenoiseShader } from 'three/addons/shaders/PoissonDenoiseShader.js'; + */ + +/** + * Poisson Denoise Shader. + * + * References: + * - [Self-Supervised Poisson-Gaussian Denoising]{@link https://openaccess.thecvf.com/content/WACV2021/papers/Khademi_Self-Supervised_Poisson-Gaussian_Denoising_WACV_2021_paper.pdf}. + * - [Poisson2Sparse: Self-Supervised Poisson Denoising From a Single Image]{@link https://arxiv.org/pdf/2206.01856.pdf} + * + * @constant + * @type {ShaderMaterial~Shader} + */ +const PoissonDenoiseShader = { + + name: 'PoissonDenoiseShader', + + defines: { + 'SAMPLES': 16, + 'SAMPLE_VECTORS': generatePdSamplePointInitializer( 16, 2, 1 ), + 'NORMAL_VECTOR_TYPE': 1, + 'DEPTH_VALUE_SOURCE': 0, + }, + + uniforms: { + 'tDiffuse': { value: null }, + 'tNormal': { value: null }, + 'tDepth': { value: null }, + 'tNoise': { value: null }, + 'resolution': { value: new Vector2() }, + 'cameraProjectionMatrixInverse': { value: new Matrix4() }, + 'lumaPhi': { value: 5. }, + 'depthPhi': { value: 5. }, + 'normalPhi': { value: 5. }, + 'radius': { value: 4. }, + 'index': { value: 0 } + }, + + vertexShader: /* glsl */` + + varying vec2 vUv; + + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + }`, + + fragmentShader: /* glsl */` + + varying vec2 vUv; + + uniform sampler2D tDiffuse; + uniform sampler2D tNormal; + uniform sampler2D tDepth; + uniform sampler2D tNoise; + uniform vec2 resolution; + uniform mat4 cameraProjectionMatrixInverse; + uniform float lumaPhi; + uniform float depthPhi; + uniform float normalPhi; + uniform float radius; + uniform int index; + + #include + #include + + #ifndef SAMPLE_LUMINANCE + #define SAMPLE_LUMINANCE dot(vec3(0.2125, 0.7154, 0.0721), a) + #endif + + #ifndef FRAGMENT_OUTPUT + #define FRAGMENT_OUTPUT vec4(denoised, 1.) + #endif + + float getLuminance(const in vec3 a) { + return SAMPLE_LUMINANCE; + } + + const vec3 poissonDisk[SAMPLES] = SAMPLE_VECTORS; + + vec3 getViewPosition(const in vec2 screenPosition, const in float depth) { + vec4 clipSpacePosition = vec4(vec3(screenPosition, depth) * 2.0 - 1.0, 1.0); + vec4 viewSpacePosition = cameraProjectionMatrixInverse * clipSpacePosition; + return viewSpacePosition.xyz / viewSpacePosition.w; + } + + float getDepth(const vec2 uv) { + #if DEPTH_VALUE_SOURCE == 1 + return textureLod(tDepth, uv.xy, 0.0).a; + #else + return textureLod(tDepth, uv.xy, 0.0).r; + #endif + } + + float fetchDepth(const ivec2 uv) { + #if DEPTH_VALUE_SOURCE == 1 + return texelFetch(tDepth, uv.xy, 0).a; + #else + return texelFetch(tDepth, uv.xy, 0).r; + #endif + } + + vec3 computeNormalFromDepth(const vec2 uv) { + vec2 size = vec2(textureSize(tDepth, 0)); + ivec2 p = ivec2(uv * size); + float c0 = fetchDepth(p); + float l2 = fetchDepth(p - ivec2(2, 0)); + float l1 = fetchDepth(p - ivec2(1, 0)); + float r1 = fetchDepth(p + ivec2(1, 0)); + float r2 = fetchDepth(p + ivec2(2, 0)); + float b2 = fetchDepth(p - ivec2(0, 2)); + float b1 = fetchDepth(p - ivec2(0, 1)); + float t1 = fetchDepth(p + ivec2(0, 1)); + float t2 = fetchDepth(p + ivec2(0, 2)); + float dl = abs((2.0 * l1 - l2) - c0); + float dr = abs((2.0 * r1 - r2) - c0); + float db = abs((2.0 * b1 - b2) - c0); + float dt = abs((2.0 * t1 - t2) - c0); + vec3 ce = getViewPosition(uv, c0).xyz; + vec3 dpdx = (dl < dr) ? ce - getViewPosition((uv - vec2(1.0 / size.x, 0.0)), l1).xyz + : -ce + getViewPosition((uv + vec2(1.0 / size.x, 0.0)), r1).xyz; + vec3 dpdy = (db < dt) ? ce - getViewPosition((uv - vec2(0.0, 1.0 / size.y)), b1).xyz + : -ce + getViewPosition((uv + vec2(0.0, 1.0 / size.y)), t1).xyz; + return normalize(cross(dpdx, dpdy)); + } + + vec3 getViewNormal(const vec2 uv) { + #if NORMAL_VECTOR_TYPE == 2 + return normalize(textureLod(tNormal, uv, 0.).rgb); + #elif NORMAL_VECTOR_TYPE == 1 + return unpackRGBToNormal(textureLod(tNormal, uv, 0.).rgb); + #else + return computeNormalFromDepth(uv); + #endif + } + + void denoiseSample(in vec3 center, in vec3 viewNormal, in vec3 viewPos, in vec2 sampleUv, inout vec3 denoised, inout float totalWeight) { + vec4 sampleTexel = textureLod(tDiffuse, sampleUv, 0.0); + float sampleDepth = getDepth(sampleUv); + vec3 sampleNormal = getViewNormal(sampleUv); + vec3 neighborColor = sampleTexel.rgb; + vec3 viewPosSample = getViewPosition(sampleUv, sampleDepth); + + float normalDiff = dot(viewNormal, sampleNormal); + float normalSimilarity = pow(max(normalDiff, 0.), normalPhi); + float lumaDiff = abs(getLuminance(neighborColor) - getLuminance(center)); + float lumaSimilarity = max(1.0 - lumaDiff / lumaPhi, 0.0); + float depthDiff = abs(dot(viewPos - viewPosSample, viewNormal)); + float depthSimilarity = max(1. - depthDiff / depthPhi, 0.); + float w = lumaSimilarity * depthSimilarity * normalSimilarity; + + denoised += w * neighborColor; + totalWeight += w; + } + + void main() { + float depth = getDepth(vUv.xy); + vec3 viewNormal = getViewNormal(vUv); + if (depth == 1. || dot(viewNormal, viewNormal) == 0.) { + discard; + return; + } + vec4 texel = textureLod(tDiffuse, vUv, 0.0); + vec3 center = texel.rgb; + vec3 viewPos = getViewPosition(vUv, depth); + + vec2 noiseResolution = vec2(textureSize(tNoise, 0)); + vec2 noiseUv = vUv * resolution / noiseResolution; + vec4 noiseTexel = textureLod(tNoise, noiseUv, 0.0); + vec2 noiseVec = vec2(sin(noiseTexel[index % 4] * 2. * PI), cos(noiseTexel[index % 4] * 2. * PI)); + mat2 rotationMatrix = mat2(noiseVec.x, -noiseVec.y, noiseVec.x, noiseVec.y); + + float totalWeight = 1.0; + vec3 denoised = texel.rgb; + for (int i = 0; i < SAMPLES; i++) { + vec3 sampleDir = poissonDisk[i]; + vec2 offset = rotationMatrix * (sampleDir.xy * (1. + sampleDir.z * (radius - 1.)) / resolution); + vec2 sampleUv = vUv + offset; + denoiseSample(center, viewNormal, viewPos, sampleUv, denoised, totalWeight); + } + + if (totalWeight > 0.) { + denoised /= totalWeight; + } + gl_FragColor = FRAGMENT_OUTPUT; + }` + +}; + +function generatePdSamplePointInitializer( samples, rings, radiusExponent ) { + + const poissonDisk = generateDenoiseSamples( + samples, + rings, + radiusExponent, + ); + + let glslCode = 'vec3[SAMPLES]('; + + for ( let i = 0; i < samples; i ++ ) { + + const sample = poissonDisk[ i ]; + glslCode += `vec3(${sample.x}, ${sample.y}, ${sample.z})${( i < samples - 1 ) ? ',' : ')'}`; + + } + + return glslCode; + +} + +function generateDenoiseSamples( numSamples, numRings, radiusExponent ) { + + const samples = []; + + for ( let i = 0; i < numSamples; i ++ ) { + + const angle = 2 * Math.PI * numRings * i / numSamples; + const radius = Math.pow( i / ( numSamples - 1 ), radiusExponent ); + samples.push( new Vector3( Math.cos( angle ), Math.sin( angle ), radius ) ); + + } + + return samples; + +} + +export { generatePdSamplePointInitializer, PoissonDenoiseShader }; diff --git a/examples/jsm/shaders/RGBShiftShader.js b/examples/jsm/shaders/RGBShiftShader.js index 2b92f77d8dd29d..acbe4413cb1cbb 100644 --- a/examples/jsm/shaders/RGBShiftShader.js +++ b/examples/jsm/shaders/RGBShiftShader.js @@ -1,3 +1,8 @@ +/** + * @module RGBShiftShader + * @three_import import { RGBShiftShader } from 'three/addons/shaders/RGBShiftShader.js'; + */ + /** * RGB Shift Shader * Shifts red and blue channels from center in opposite directions @@ -6,10 +11,14 @@ * * amount: shift distance (1 is width of input) * angle: shift angle in radians + * + * @constant + * @type {ShaderMaterial~Shader} */ - const RGBShiftShader = { + name: 'RGBShiftShader', + uniforms: { 'tDiffuse': { value: null }, diff --git a/examples/jsm/shaders/SAOShader.js b/examples/jsm/shaders/SAOShader.js index 632ba540a611d5..dbf9c6ff371f8e 100644 --- a/examples/jsm/shaders/SAOShader.js +++ b/examples/jsm/shaders/SAOShader.js @@ -4,18 +4,29 @@ import { } from 'three'; /** - * TODO + * @module SAOShader + * @three_import import { SAOShader } from 'three/addons/shaders/SAOShader.js'; */ +/** + * SAO shader. + * + * Used by {@link SAOPass}. + * + * @constant + * @type {ShaderMaterial~Shader} + */ const SAOShader = { + + name: 'SAOShader', + defines: { 'NUM_SAMPLES': 7, 'NUM_RINGS': 4, - 'NORMAL_TEXTURE': 0, 'DIFFUSE_TEXTURE': 0, - 'DEPTH_PACKING': 1, 'PERSPECTIVE_CAMERA': 1 }, + uniforms: { 'tDepth': { value: null }, @@ -36,6 +47,7 @@ const SAOShader = { 'kernelRadius': { value: 100.0 }, 'randomSeed': { value: 0.0 } }, + vertexShader: /* glsl */` varying vec2 vUv; @@ -46,7 +58,6 @@ const SAOShader = { }`, fragmentShader: /* glsl */` - #include varying vec2 vUv; @@ -55,11 +66,8 @@ const SAOShader = { uniform sampler2D tDiffuse; #endif - uniform sampler2D tDepth; - - #if NORMAL_TEXTURE == 1 - uniform sampler2D tNormal; - #endif + uniform highp sampler2D tDepth; + uniform highp sampler2D tNormal; uniform float cameraNear; uniform float cameraFar; @@ -87,11 +95,7 @@ const SAOShader = { } float getDepth( const in vec2 screenPosition ) { - #if DEPTH_PACKING == 1 - return unpackRGBAToDepth( texture2D( tDepth, screenPosition ) ); - #else return texture2D( tDepth, screenPosition ).x; - #endif } float getViewZ( const in float depth ) { @@ -111,11 +115,7 @@ const SAOShader = { } vec3 getViewNormal( const in vec3 viewPosition, const in vec2 screenPosition ) { - #if NORMAL_TEXTURE == 1 return unpackRGBToNormal( texture2D( tNormal, screenPosition ).xyz ); - #else - return normalize( cross( dFdx( viewPosition ), dFdy( viewPosition ) ) ); - #endif } float scaleDividedByCameraFar; diff --git a/examples/jsm/shaders/SMAAShader.js b/examples/jsm/shaders/SMAAShader.js index d1bfb47e213df5..ce1b47e7596562 100644 --- a/examples/jsm/shaders/SMAAShader.js +++ b/examples/jsm/shaders/SMAAShader.js @@ -5,11 +5,24 @@ import { /** * WebGL port of Subpixel Morphological Antialiasing (SMAA) v2.8 * Preset: SMAA 1x Medium (with color edge detection) - * https://github.com/iryoku/smaa/releases/tag/v2.8 + * + * References: + * - {@link https://github.com/iryoku/smaa/releases/tag/v2.8} + * + * @module SMAAShader + * @three_import import { SMAAShader } from 'three/addons/shaders/SMAAShader.js'; */ +/** + * SMAA Edges shader. + * + * @constant + * @type {ShaderMaterial~Shader} + */ const SMAAEdgesShader = { + name: 'SMAAEdgesShader', + defines: { 'SMAA_THRESHOLD': '0.1' @@ -113,8 +126,16 @@ const SMAAEdgesShader = { }; +/** + * SMAA Weights shader. + * + * @constant + * @type {ShaderMaterial~Shader} + */ const SMAAWeightsShader = { + name: 'SMAAWeightsShader', + defines: { 'SMAA_MAX_SEARCH_STEPS': '8', @@ -367,8 +388,16 @@ const SMAAWeightsShader = { }; +/** + * SMAA Blend shader. + * + * @constant + * @type {ShaderMaterial~Shader} + */ const SMAABlendShader = { + name: 'SMAABlendShader', + uniforms: { 'tDiffuse': { value: null }, diff --git a/examples/jsm/shaders/SSAOShader.js b/examples/jsm/shaders/SSAOShader.js index b256c153b37f35..ab1dc6f25eca7e 100644 --- a/examples/jsm/shaders/SSAOShader.js +++ b/examples/jsm/shaders/SSAOShader.js @@ -4,14 +4,25 @@ import { } from 'three'; /** - * References: - * http://john-chapman-graphics.blogspot.com/2013/01/ssao-tutorial.html - * https://learnopengl.com/Advanced-Lighting/SSAO - * https://github.com/McNopper/OpenGL/blob/master/Example28/shader/ssao.frag.glsl + * @module SSAOShader + * @three_import import { SSAOShader } from 'three/addons/shaders/SSAOShader.js'; */ +/** + * SSAO shader. + * + * References: + * - {@link http://john-chapman-graphics.blogspot.com/2013/01/ssao-tutorial.html} + * - {@link https://learnopengl.com/Advanced-Lighting/SSAO} + * - {@link https://github.com/McNopper/OpenGL/blob/master/Example28/shader/ssao.frag.glsl} + * + * @constant + * @type {ShaderMaterial~Shader} + */ const SSAOShader = { + name: 'SSAOShader', + defines: { 'PERSPECTIVE_CAMERA': 1, 'KERNEL_SIZE': 32 @@ -19,7 +30,6 @@ const SSAOShader = { uniforms: { - 'tDiffuse': { value: null }, 'tNormal': { value: null }, 'tDepth': { value: null }, 'tNoise': { value: null }, @@ -48,10 +58,8 @@ const SSAOShader = { }`, fragmentShader: /* glsl */` - - uniform sampler2D tDiffuse; - uniform sampler2D tNormal; - uniform sampler2D tDepth; + uniform highp sampler2D tNormal; + uniform highp sampler2D tDepth; uniform sampler2D tNoise; uniform vec3 kernel[ KERNEL_SIZE ]; @@ -128,54 +136,71 @@ const SSAOShader = { void main() { float depth = getDepth( vUv ); - float viewZ = getViewZ( depth ); - vec3 viewPosition = getViewPosition( vUv, depth, viewZ ); - vec3 viewNormal = getViewNormal( vUv ); + if ( depth == 1.0 ) { + + gl_FragColor = vec4( 1.0 ); // don't influence background + + } else { + + float viewZ = getViewZ( depth ); - vec2 noiseScale = vec2( resolution.x / 4.0, resolution.y / 4.0 ); - vec3 random = vec3( texture2D( tNoise, vUv * noiseScale ).r ); + vec3 viewPosition = getViewPosition( vUv, depth, viewZ ); + vec3 viewNormal = getViewNormal( vUv ); - // compute matrix used to reorient a kernel vector + vec2 noiseScale = vec2( resolution.x / 4.0, resolution.y / 4.0 ); + vec3 random = vec3( texture2D( tNoise, vUv * noiseScale ).r ); - vec3 tangent = normalize( random - viewNormal * dot( random, viewNormal ) ); - vec3 bitangent = cross( viewNormal, tangent ); - mat3 kernelMatrix = mat3( tangent, bitangent, viewNormal ); + // compute matrix used to reorient a kernel vector - float occlusion = 0.0; + vec3 tangent = normalize( random - viewNormal * dot( random, viewNormal ) ); + vec3 bitangent = cross( viewNormal, tangent ); + mat3 kernelMatrix = mat3( tangent, bitangent, viewNormal ); - for ( int i = 0; i < KERNEL_SIZE; i ++ ) { + float occlusion = 0.0; - vec3 sampleVector = kernelMatrix * kernel[ i ]; // reorient sample vector in view space - vec3 samplePoint = viewPosition + ( sampleVector * kernelRadius ); // calculate sample point + for ( int i = 0; i < KERNEL_SIZE; i ++ ) { - vec4 samplePointNDC = cameraProjectionMatrix * vec4( samplePoint, 1.0 ); // project point and calculate NDC - samplePointNDC /= samplePointNDC.w; + vec3 sampleVector = kernelMatrix * kernel[ i ]; // reorient sample vector in view space + vec3 samplePoint = viewPosition + ( sampleVector * kernelRadius ); // calculate sample point - vec2 samplePointUv = samplePointNDC.xy * 0.5 + 0.5; // compute uv coordinates + vec4 samplePointNDC = cameraProjectionMatrix * vec4( samplePoint, 1.0 ); // project point and calculate NDC + samplePointNDC /= samplePointNDC.w; - float realDepth = getLinearDepth( samplePointUv ); // get linear depth from depth texture - float sampleDepth = viewZToOrthographicDepth( samplePoint.z, cameraNear, cameraFar ); // compute linear depth of the sample view Z value - float delta = sampleDepth - realDepth; + vec2 samplePointUv = samplePointNDC.xy * 0.5 + 0.5; // compute uv coordinates - if ( delta > minDistance && delta < maxDistance ) { // if fragment is before sample point, increase occlusion + float realDepth = getLinearDepth( samplePointUv ); // get linear depth from depth texture + float sampleDepth = viewZToOrthographicDepth( samplePoint.z, cameraNear, cameraFar ); // compute linear depth of the sample view Z value + float delta = sampleDepth - realDepth; - occlusion += 1.0; + if ( delta > minDistance && delta < maxDistance ) { // if fragment is before sample point, increase occlusion + + occlusion += 1.0; + + } } - } + occlusion = clamp( occlusion / float( KERNEL_SIZE ), 0.0, 1.0 ); - occlusion = clamp( occlusion / float( KERNEL_SIZE ), 0.0, 1.0 ); + gl_FragColor = vec4( vec3( 1.0 - occlusion ), 1.0 ); - gl_FragColor = vec4( vec3( 1.0 - occlusion ), 1.0 ); + } }` }; +/** + * SSAO depth shader. + * + * @constant + * @type {ShaderMaterial~Shader} + */ const SSAODepthShader = { + name: 'SSAODepthShader', + defines: { 'PERSPECTIVE_CAMERA': 1 }, @@ -235,8 +260,16 @@ const SSAODepthShader = { }; +/** + * SSAO blur shader. + * + * @constant + * @type {Object} + */ const SSAOBlurShader = { + name: 'SSAOBlurShader', + uniforms: { 'tDiffuse': { value: null }, diff --git a/examples/jsm/shaders/SSRShader.js b/examples/jsm/shaders/SSRShader.js index 54455f6743d0d8..a05834f68d280b 100644 --- a/examples/jsm/shaders/SSRShader.js +++ b/examples/jsm/shaders/SSRShader.js @@ -2,13 +2,28 @@ import { Matrix4, Vector2 } from 'three'; + + /** + * A collection of shaders used for SSR. + * * References: - * https://lettier.github.io/3d-game-shaders-for-beginners/screen-space-reflection.html + * - [3D Game Shaders For Beginners, Screen Space Reflection (SSR)]{@link https://lettier.github.io/3d-game-shaders-for-beginners/screen-space-reflection.html}. + * + * @module SSRShader + * @three_import import * as SSRShader from 'three/addons/shaders/SSRShader.js'; */ +/** + * SSR shader. + * + * @constant + * @type {ShaderMaterial~Shader} + */ const SSRShader = { + name: 'SSRShader', + defines: { MAX_STEP: 0, PERSPECTIVE_CAMERA: true, @@ -231,8 +246,16 @@ const SSRShader = { }; +/** + * SSR Depth shader. + * + * @constant + * @type {ShaderMaterial~Shader} + */ const SSRDepthShader = { + name: 'SSRDepthShader', + defines: { 'PERSPECTIVE_CAMERA': 1 }, @@ -298,8 +321,16 @@ const SSRDepthShader = { }; +/** + * SSR Blur shader. + * + * @constant + * @type {ShaderMaterial~Shader} + */ const SSRBlurShader = { + name: 'SSRBlurShader', + uniforms: { 'tDiffuse': { value: null }, diff --git a/examples/jsm/shaders/SepiaShader.js b/examples/jsm/shaders/SepiaShader.js index 01a6daa98eac8a..5e37d4b383f91e 100644 --- a/examples/jsm/shaders/SepiaShader.js +++ b/examples/jsm/shaders/SepiaShader.js @@ -1,11 +1,18 @@ /** - * Sepia tone shader - * based on glfx.js sepia shader - * https://github.com/evanw/glfx.js + * @module SepiaShader + * @three_import import { SepiaShader } from 'three/addons/shaders/SepiaShader.js'; */ +/** + * Sepia tone shader based on [glfx.js sepia shader]{@link https://github.com/evanw/glfx.js}. + * + * @constant + * @type {ShaderMaterial~Shader} + */ const SepiaShader = { + name: 'SepiaShader', + uniforms: { 'tDiffuse': { value: null }, diff --git a/examples/jsm/shaders/SobelOperatorShader.js b/examples/jsm/shaders/SobelOperatorShader.js index cebcbd2691d96a..43d190f93a7295 100644 --- a/examples/jsm/shaders/SobelOperatorShader.js +++ b/examples/jsm/shaders/SobelOperatorShader.js @@ -3,14 +3,22 @@ import { } from 'three'; /** - * Sobel Edge Detection (see https://youtu.be/uihBwtPIBxM) + * @module SobelOperatorShader + * @three_import import { SobelOperatorShader } from 'three/addons/shaders/SobelOperatorShader.js'; + */ + +/** + * Sobel Edge Detection (see {@link https://youtu.be/uihBwtPIBxM}). * * As mentioned in the video the Sobel operator expects a grayscale image as input. * + * @constant + * @type {ShaderMaterial~Shader} */ - const SobelOperatorShader = { + name: 'SobelOperatorShader', + uniforms: { 'tDiffuse': { value: null }, @@ -77,7 +85,7 @@ const SobelOperatorShader = { Gy[0][1] * tx0y1 + Gy[1][1] * tx1y1 + Gy[2][1] * tx2y1 + Gy[0][2] * tx0y2 + Gy[1][2] * tx1y2 + Gy[2][2] * tx2y2; - // magnitute of the total gradient + // magnitude of the total gradient float G = sqrt( ( valueGx * valueGx ) + ( valueGy * valueGy ) ); diff --git a/examples/jsm/shaders/SubsurfaceScatteringShader.js b/examples/jsm/shaders/SubsurfaceScatteringShader.js index b522d1fb0630b1..faab31834f181d 100644 --- a/examples/jsm/shaders/SubsurfaceScatteringShader.js +++ b/examples/jsm/shaders/SubsurfaceScatteringShader.js @@ -5,14 +5,6 @@ import { UniformsUtils } from 'three'; -/** - * ------------------------------------------------------------------------------------------ - * Subsurface Scattering shader - * Based on GDC 2011 – Approximating Translucency for a Fast, Cheap and Convincing Subsurface Scattering Look - * https://colinbarrebrisebois.com/2011/03/07/gdc-2011-approximating-translucency-for-a-fast-cheap-and-convincing-subsurface-scattering-look/ - *------------------------------------------------------------------------------------------ - */ - function replaceAll( string, find, replace ) { return string.split( find ).join( replace ); @@ -22,8 +14,23 @@ function replaceAll( string, find, replace ) { const meshphong_frag_head = ShaderChunk[ 'meshphong_frag' ].slice( 0, ShaderChunk[ 'meshphong_frag' ].indexOf( 'void main() {' ) ); const meshphong_frag_body = ShaderChunk[ 'meshphong_frag' ].slice( ShaderChunk[ 'meshphong_frag' ].indexOf( 'void main() {' ) ); +/** + * @module SubsurfaceScatteringShader + * @three_import import { SubsurfaceScatteringShader } from 'three/addons/shaders/SubsurfaceScatteringShader.js'; + */ + +/** + * Subsurface Scattering shader. + * + * Based on GDC 2011 – [Approximating Translucency for a Fast, Cheap and Convincing Subsurface Scattering Look]{@link https://colinbarrebrisebois.com/2011/03/07/gdc-2011-approximating-translucency-for-a-fast-cheap-and-convincing-subsurface-scattering-look/} + * + * @constant + * @type {ShaderMaterial~Shader} + */ const SubsurfaceScatteringShader = { + name: 'SubsurfaceScatteringShader', + uniforms: UniformsUtils.merge( [ ShaderLib[ 'phong' ].uniforms, { @@ -57,10 +64,10 @@ const SubsurfaceScatteringShader = { 'uniform float thicknessAttenuation;', 'uniform vec3 thicknessColor;', - 'void RE_Direct_Scattering(const in IncidentLight directLight, const in vec2 uv, const in GeometricContext geometry, inout ReflectedLight reflectedLight) {', + 'void RE_Direct_Scattering(const in IncidentLight directLight, const in vec2 uv, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, inout ReflectedLight reflectedLight) {', ' vec3 thickness = thicknessColor * texture2D(thicknessMap, uv).r;', - ' vec3 scatteringHalf = normalize(directLight.direction + (geometry.normal * thicknessDistortion));', - ' float scatteringDot = pow(saturate(dot(geometry.viewDir, -scatteringHalf)), thicknessPower) * thicknessScale;', + ' vec3 scatteringHalf = normalize(directLight.direction + (geometryNormal * thicknessDistortion));', + ' float scatteringDot = pow(saturate(dot(geometryViewDir, -scatteringHalf)), thicknessPower) * thicknessScale;', ' vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * thickness;', ' reflectedLight.directDiffuse += scatteringIllu * thicknessAttenuation * directLight.color;', '}', @@ -69,12 +76,12 @@ const SubsurfaceScatteringShader = { replaceAll( ShaderChunk[ 'lights_fragment_begin' ], - 'RE_Direct( directLight, geometry, material, reflectedLight );', + 'RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );', [ - 'RE_Direct( directLight, geometry, material, reflectedLight );', + 'RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );', '#if defined( SUBSURFACE ) && defined( USE_UV )', - ' RE_Direct_Scattering(directLight, vUv, geometry, reflectedLight);', + ' RE_Direct_Scattering(directLight, vUv, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, reflectedLight);', '#endif', ].join( '\n' ) ), diff --git a/examples/jsm/shaders/TechnicolorShader.js b/examples/jsm/shaders/TechnicolorShader.js index ef0f2ff654e147..8752a056f59ac1 100644 --- a/examples/jsm/shaders/TechnicolorShader.js +++ b/examples/jsm/shaders/TechnicolorShader.js @@ -1,12 +1,20 @@ /** - * Technicolor Shader - * Simulates the look of the two-strip technicolor process popular in early 20th century films. - * More historical info here: http://www.widescreenmuseum.com/oldcolor/technicolor1.htm - * Demo here: http://charliehoey.com/technicolor_shader/shader_test.html + * @module TriangleBlurShader + * @three_import import { TriangleBlurShader } from 'three/addons/shaders/TriangleBlurShader.js'; */ +/** + * Simulates the look of the two-strip technicolor process popular in early 20th century films. + * More historical info here: {@link http://www.widescreenmuseum.com/oldcolor/technicolor1.htm} + * Demo here: {@link http://charliehoey.com/technicolor_shader/shader_test.html} + * + * @constant + * @type {ShaderMaterial~Shader} + */ const TechnicolorShader = { + name: 'TechnicolorShader', + uniforms: { 'tDiffuse': { value: null } diff --git a/examples/jsm/shaders/ToneMapShader.js b/examples/jsm/shaders/ToneMapShader.js deleted file mode 100644 index bc4c0aeb5dea93..00000000000000 --- a/examples/jsm/shaders/ToneMapShader.js +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Full-screen tone-mapping shader based on http://www.cis.rit.edu/people/faculty/ferwerda/publications/sig02_paper.pdf - */ - -const ToneMapShader = { - - uniforms: { - - 'tDiffuse': { value: null }, - 'averageLuminance': { value: 1.0 }, - 'luminanceMap': { value: null }, - 'maxLuminance': { value: 16.0 }, - 'minLuminance': { value: 0.01 }, - 'middleGrey': { value: 0.6 } - }, - - vertexShader: /* glsl */` - - varying vec2 vUv; - - void main() { - - vUv = uv; - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - - }`, - - fragmentShader: /* glsl */` - - #include - - uniform sampler2D tDiffuse; - - varying vec2 vUv; - - uniform float middleGrey; - uniform float minLuminance; - uniform float maxLuminance; - #ifdef ADAPTED_LUMINANCE - uniform sampler2D luminanceMap; - #else - uniform float averageLuminance; - #endif - - vec3 ToneMap( vec3 vColor ) { - #ifdef ADAPTED_LUMINANCE - // Get the calculated average luminance - float fLumAvg = texture2D(luminanceMap, vec2(0.5, 0.5)).r; - #else - float fLumAvg = averageLuminance; - #endif - - // Calculate the luminance of the current pixel - float fLumPixel = luminance( vColor ); - - // Apply the modified operator (Eq. 4) - float fLumScaled = (fLumPixel * middleGrey) / max( minLuminance, fLumAvg ); - - float fLumCompressed = (fLumScaled * (1.0 + (fLumScaled / (maxLuminance * maxLuminance)))) / (1.0 + fLumScaled); - return fLumCompressed * vColor; - } - - void main() { - - vec4 texel = texture2D( tDiffuse, vUv ); - - gl_FragColor = vec4( ToneMap( texel.xyz ), texel.w ); - - }` - -}; - -export { ToneMapShader }; diff --git a/examples/jsm/shaders/ToonShader.js b/examples/jsm/shaders/ToonShader.js index e3c5f6dbcb657d..59daae98f26168 100644 --- a/examples/jsm/shaders/ToonShader.js +++ b/examples/jsm/shaders/ToonShader.js @@ -4,16 +4,22 @@ import { } from 'three'; /** - * Currently contains: + * Collection of toon shaders. * - * toon1 - * toon2 - * hatching - * dotted + * @module TriangleBlurShader + * @three_import import * as ToonShader from 'three/addons/shaders/ToonShader.js'; */ +/** + * Toon1 shader. + * + * @constant + * @type {ShaderMaterial~Shader} + */ const ToonShader1 = { + name: 'ToonShader1', + uniforms: { 'uDirLightPos': { value: new Vector3() }, @@ -78,12 +84,19 @@ const ToonShader1 = { gl_FragColor = vec4( 1.0 - 2.0 * ( 1.0 - intensity ) * ( 1.0 - uBaseColor ), 1.0 ); - } + } + + #include }` }; - +/** + * Toon2 shader. + * + * @constant + * @type {Object} + */ const ToonShader2 = { uniforms: { @@ -146,10 +159,18 @@ const ToonShader2 = { } + #include + }` }; +/** + * Toon Hatching shader. + * + * @constant + * @type {Object} + */ const ToonShaderHatching = { uniforms: { @@ -240,10 +261,18 @@ const ToonShaderHatching = { } + #include + }` }; +/** + * Toon Dotted shader. + * + * @constant + * @type {Object} + */ const ToonShaderDotted = { uniforms: { @@ -286,12 +315,12 @@ const ToonShaderDotted = { void main() { - float directionalLightWeighting = max( dot( normalize(vNormal), uDirLightPos ), 0.0); - vec3 lightWeighting = uAmbientLightColor + uDirLightColor * directionalLightWeighting; + float directionalLightWeighting = max( dot( normalize(vNormal), uDirLightPos ), 0.0); + vec3 lightWeighting = uAmbientLightColor + uDirLightColor * directionalLightWeighting; - gl_FragColor = vec4( uBaseColor, 1.0 ); + gl_FragColor = vec4( uBaseColor, 1.0 ); - if ( length(lightWeighting) < 1.00 ) { + if ( length(lightWeighting) < 1.00 ) { if ( ( mod(gl_FragCoord.x, 4.001) + mod(gl_FragCoord.y, 4.0) ) > 6.00 ) { @@ -311,6 +340,8 @@ const ToonShaderDotted = { } + #include + }` }; diff --git a/examples/jsm/shaders/TriangleBlurShader.js b/examples/jsm/shaders/TriangleBlurShader.js index 50147751f791a6..7d75459ec9b8c7 100644 --- a/examples/jsm/shaders/TriangleBlurShader.js +++ b/examples/jsm/shaders/TriangleBlurShader.js @@ -3,17 +3,24 @@ import { } from 'three'; /** - * Triangle blur shader - * based on glfx.js triangle blur shader - * https://github.com/evanw/glfx.js + * @module TriangleBlurShader + * @three_import import { TriangleBlurShader } from 'three/addons/shaders/TriangleBlurShader.js'; + */ + +/** + * Triangle blur shader based on [glfx.js triangle blur shader]{@link https://github.com/evanw/glfx.js}. * * A basic blur filter, which convolves the image with a * pyramid filter. The pyramid filter is separable and is applied as two * perpendicular triangle filters. + * + * @constant + * @type {ShaderMaterial~Shader} */ - const TriangleBlurShader = { + name: 'TriangleBlurShader', + uniforms: { 'texture': { value: null }, diff --git a/examples/jsm/shaders/UnpackDepthRGBAShader.js b/examples/jsm/shaders/UnpackDepthRGBAShader.js index eb72e6db0ff22c..8ee55029078cca 100644 --- a/examples/jsm/shaders/UnpackDepthRGBAShader.js +++ b/examples/jsm/shaders/UnpackDepthRGBAShader.js @@ -1,10 +1,18 @@ /** - * Unpack RGBA depth shader - * - show RGBA encoded depth as monochrome color + * @module UnpackDepthRGBAShader + * @three_import import { UnpackDepthRGBAShader } from 'three/addons/shaders/UnpackDepthRGBAShader.js'; */ +/** + * Unpack RGBA depth shader that shows RGBA encoded depth as monochrome color. + * + * @constant + * @type {ShaderMaterial~Shader} + */ const UnpackDepthRGBAShader = { + name: 'UnpackDepthRGBAShader', + uniforms: { 'tDiffuse': { value: null }, diff --git a/examples/jsm/shaders/VelocityShader.js b/examples/jsm/shaders/VelocityShader.js index a053d927f6ff53..9c5ef710fa3864 100644 --- a/examples/jsm/shaders/VelocityShader.js +++ b/examples/jsm/shaders/VelocityShader.js @@ -5,11 +5,20 @@ import { } from 'three'; /** - * Mesh Velocity Shader @bhouston + * @module VelocityShader + * @three_import import { VelocityShader } from 'three/addons/shaders/VelocityShader.js'; */ +/** + * Mesh velocity shader by @bhouston. + * + * @constant + * @type {ShaderMaterial~Shader} + */ const VelocityShader = { + name: 'VelocityShader', + uniforms: UniformsUtils.merge( [ UniformsLib.common, UniformsLib.displacementmap, diff --git a/examples/jsm/shaders/VerticalBlurShader.js b/examples/jsm/shaders/VerticalBlurShader.js index 7bf51840523cb7..e4c953a8c4a6c9 100644 --- a/examples/jsm/shaders/VerticalBlurShader.js +++ b/examples/jsm/shaders/VerticalBlurShader.js @@ -1,14 +1,23 @@ +/** + * @module VerticalBlurShader + * @three_import import { VerticalBlurShader } from 'three/addons/shaders/VerticalBlurShader.js'; + */ + /** * Two pass Gaussian blur filter (horizontal and vertical blur shaders) - * - see http://www.cake23.de/traveling-wavefronts-lit-up.html + * - see {@link http://www.cake23.de/traveling-wavefronts-lit-up.html} * * - 9 samples per pass * - standard deviation 2.7 * - "h" and "v" parameters should be set to "1 / width" and "1 / height" + * + * @constant + * @type {ShaderMaterial~Shader} */ - const VerticalBlurShader = { + name: 'VerticalBlurShader', + uniforms: { 'tDiffuse': { value: null }, diff --git a/examples/jsm/shaders/VerticalTiltShiftShader.js b/examples/jsm/shaders/VerticalTiltShiftShader.js index 768c8aeda3426d..f4442b4ef84b05 100644 --- a/examples/jsm/shaders/VerticalTiltShiftShader.js +++ b/examples/jsm/shaders/VerticalTiltShiftShader.js @@ -1,3 +1,8 @@ +/** + * @module VerticalTiltShiftShader + * @three_import import { VerticalTiltShiftShader } from 'three/addons/shaders/VerticalTiltShiftShader.js'; + */ + /** * Simple fake tilt-shift effect, modulating two pass Gaussian blur (see above) by vertical position * @@ -5,10 +10,14 @@ * - standard deviation 2.7 * - "h" and "v" parameters should be set to "1 / width" and "1 / height" * - "r" parameter control where "focused" horizontal line lies + * + * @constant + * @type {ShaderMaterial~Shader} */ - const VerticalTiltShiftShader = { + name: 'VerticalTiltShiftShader', + uniforms: { 'tDiffuse': { value: null }, diff --git a/examples/jsm/shaders/VignetteShader.js b/examples/jsm/shaders/VignetteShader.js index 839dc42afd026c..4d3856730dfa6a 100644 --- a/examples/jsm/shaders/VignetteShader.js +++ b/examples/jsm/shaders/VignetteShader.js @@ -1,11 +1,18 @@ /** - * Vignette shader - * based on PaintEffect postprocess from ro.me - * http://code.google.com/p/3-dreams-of-black/source/browse/deploy/js/effects/PaintEffect.js + * @module VignetteShader + * @three_import import { VignetteShader } from 'three/addons/shaders/VignetteShader.js'; */ +/** + * Based on [PaintEffect postprocess from ro.me]{@link http://code.google.com/p/3-dreams-of-black/source/browse/deploy/js/effects/PaintEffect.js}. + * + * @constant + * @type {ShaderMaterial~Shader} + */ const VignetteShader = { + name: 'VignetteShader', + uniforms: { 'tDiffuse': { value: null }, diff --git a/examples/jsm/shaders/VolumeShader.js b/examples/jsm/shaders/VolumeShader.js index 1ef52c56a651ae..069f2d7ffeea1f 100644 --- a/examples/jsm/shaders/VolumeShader.js +++ b/examples/jsm/shaders/VolumeShader.js @@ -3,14 +3,23 @@ import { Vector3 } from 'three'; +/** + * @module VolumeShader + * @three_import import { VolumeRenderShader1 } from 'three/addons/shaders/VolumeShader.js'; + */ + /** * Shaders to render 3D volumes using raycasting. * The applied techniques are based on similar implementations in the Visvis and Vispy projects. * This is not the only approach, therefore it's marked 1. + * + * @constant + * @type {ShaderMaterial~Shader} */ - const VolumeRenderShader1 = { + name: 'VolumeRenderShader1', + uniforms: { 'u_size': { value: new Vector3( 1, 1, 1 ) }, 'u_renderstyle': { value: 0 }, @@ -257,7 +266,7 @@ const VolumeRenderShader1 = { // note: could allow multiple lights for (int i=0; i<1; i++) { - // Get light direction (make sure to prevent zero devision) + // Get light direction (make sure to prevent zero division) vec3 L = normalize(view_ray); //lightDirs[i]; float lightEnabled = float( length(L) > 0.0 ); L = normalize(L + (1.0 - lightEnabled)); diff --git a/examples/jsm/shaders/WaterRefractionShader.js b/examples/jsm/shaders/WaterRefractionShader.js index 93f0da2e704cb5..680c4ad5c7b6e9 100644 --- a/examples/jsm/shaders/WaterRefractionShader.js +++ b/examples/jsm/shaders/WaterRefractionShader.js @@ -1,5 +1,18 @@ +/** + * @module WaterRefractionShader + * @three_import import { WaterRefractionShader } from 'three/addons/shaders/WaterRefractionShader.js'; + */ + +/** + * Basic water refraction shader. + * + * @constant + * @type {ShaderMaterial~Shader} + */ const WaterRefractionShader = { + name: 'WaterRefractionShader', + uniforms: { 'color': { @@ -76,13 +89,16 @@ const WaterRefractionShader = { // new uv coords - vec4 uv = vec4( vUvRefraction ); - uv.xy += distortion; + vec4 uv = vec4( vUvRefraction ); + uv.xy += distortion; vec4 base = texture2DProj( tDiffuse, uv ); gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 ); + #include + #include + }` }; diff --git a/examples/jsm/textures/FlakesTexture.js b/examples/jsm/textures/FlakesTexture.js index 3873dff534b62b..c0f4986d069ec9 100644 --- a/examples/jsm/textures/FlakesTexture.js +++ b/examples/jsm/textures/FlakesTexture.js @@ -1,5 +1,19 @@ +/** + * Utility class for generating a flakes texture image. This image might be used + * as a normal map to produce a car paint like effect. + * + * @three_import import { FlakesTexture } from 'three/addons/textures/FlakesTexture.js'; + */ class FlakesTexture { + /** + * Generates a new flakes texture image. The result is a canvas + * that can be used as an input for {@link CanvasTexture}. + * + * @param {number} [width=512] - The width of the image. + * @param {number} [height=512] - The height of the image. + * @return {HTMLCanvasElement} The generated image. + */ constructor( width = 512, height = 512 ) { const canvas = document.createElement( 'canvas' ); diff --git a/examples/jsm/transpiler/AST.js b/examples/jsm/transpiler/AST.js new file mode 100644 index 00000000000000..7473a23e04b7e2 --- /dev/null +++ b/examples/jsm/transpiler/AST.js @@ -0,0 +1,631 @@ +import { toFloatType } from './TranspilerUtils.js'; + +export class ASTNode { + + constructor() { + + this.isASTNode = true; + + this.linker = { + reference: null, + accesses: [], + assignments: [] + }; + + this.parent = null; + + } + + get isNumericExpression() { + + return false; + + } + + get hasAssignment() { + + if ( this.isAssignment === true ) { + + return true; + + } + + if ( this.parent === null ) { + + return false; + + } + + return this.parent.hasAssignment; + + } + + getType() { + + return this.type || null; + + } + + getParent( parents = [] ) { + + if ( this.parent === null ) { + + return parents; + + } + + parents.push( this.parent ); + + return this.parent.getParent( parents ); + + } + + initialize() { + + for ( const key in this ) { + + if ( this[ key ] && this[ key ].isASTNode ) { + + this[ key ].parent = this; + + } else if ( Array.isArray( this[ key ] ) ) { + + const array = this[ key ]; + + for ( const item of array ) { + + if ( item && item.isASTNode ) { + + item.parent = this; + + } + + } + + } + + } + + } + +} + +export class Comment extends ASTNode { + + constructor( comment ) { + + super(); + + this.comment = comment; + + this.isComment = true; + + this.initialize(); + + } + +} + + +export class Program extends ASTNode { + + constructor( body = [] ) { + + super(); + + this.body = body; + + this.isProgram = true; + + this.initialize(); + + } + +} + +export class VariableDeclaration extends ASTNode { + + constructor( type, name, value = null, next = null, immutable = false ) { + + super(); + + this.type = type; + this.name = name; + this.value = value; + this.next = next; + + this.immutable = immutable; + + this.isVariableDeclaration = true; + + this.initialize(); + + } + + get isAssignment() { + + return this.value !== null; + + } + +} + +export class Uniform extends ASTNode { + + constructor( type, name ) { + + super(); + + this.type = type; + this.name = name; + + this.isUniform = true; + + this.initialize(); + + } + +} + +export class Varying extends ASTNode { + + constructor( type, name ) { + + super(); + + this.type = type; + this.name = name; + + this.isVarying = true; + + this.initialize(); + + } + +} + +export class FunctionParameter extends ASTNode { + + constructor( type, name, qualifier = null, immutable = true ) { + + super(); + + this.type = type; + this.name = name; + this.qualifier = qualifier; + this.immutable = immutable; + + this.isFunctionParameter = true; + + this.initialize(); + + } + +} + +export class FunctionDeclaration extends ASTNode { + + constructor( type, name, params = [], body = [] ) { + + super(); + + this.type = type; + this.name = name; + this.params = params; + this.body = body; + + this.isFunctionDeclaration = true; + + this.initialize(); + + } + +} + +export class Expression extends ASTNode { + + constructor( expression ) { + + super(); + + this.expression = expression; + + this.isExpression = true; + + this.initialize(); + + } + +} + +export class Ternary extends ASTNode { + + constructor( cond, left, right ) { + + super(); + + this.cond = cond; + this.left = left; + this.right = right; + + this.isTernary = true; + + this.initialize(); + + } + +} + +export class Operator extends ASTNode { + + constructor( type, left, right ) { + + super(); + + this.type = type; + this.left = left; + this.right = right; + + this.isOperator = true; + + this.initialize(); + + } + + get isAssignment() { + + return /^(=|\+=|-=|\*=|\/=|%=|<<=|>>=|>>>=|&=|\^=|\|=)$/.test( this.type ); + + } + + get isNumericExpression() { + + if ( this.left.isNumericExpression && this.right.isNumericExpression ) { + + return true; + + } + + return false; + + } + + getType() { + + const leftType = this.left.getType(); + const rightType = this.right.getType(); + + if ( leftType === rightType ) { + + return leftType; + + } else if ( toFloatType( leftType ) === toFloatType( rightType ) ) { + + return toFloatType( leftType ); + + } + + return null; + + } + +} + + +export class Unary extends ASTNode { + + constructor( type, expression, after = false ) { + + super(); + + this.type = type; + this.expression = expression; + this.after = after; + + this.isUnary = true; + + this.initialize(); + + } + + get isAssignment() { + + return /^(\+\+|--)$/.test( this.type ); + + } + + get isNumericExpression() { + + if ( this.expression.isNumber ) { + + return true; + + } + + return false; + + } + +} + +export class Number extends ASTNode { + + constructor( value, type = 'float' ) { + + super(); + + this.type = type; + this.value = value; + + this.isNumber = true; + + this.initialize(); + + } + + get isNumericExpression() { + + return true; + + } + +} + +export class String extends ASTNode { + + constructor( value ) { + + super(); + + this.value = value; + + this.isString = true; + + this.initialize(); + + } + +} + + +export class Conditional extends ASTNode { + + constructor( cond = null, body = [] ) { + + super(); + + this.cond = cond; + this.body = body; + this.elseConditional = null; + + this.isConditional = true; + + this.initialize(); + + } + +} + +export class FunctionCall extends ASTNode { + + constructor( name, params = [] ) { + + super(); + + this.name = name; + this.params = params; + + this.isFunctionCall = true; + + this.initialize(); + + } + +} + +export class Return extends ASTNode { + + constructor( value ) { + + super(); + + this.value = value; + + this.isReturn = true; + + this.initialize(); + + } + +} + +export class Discard extends ASTNode { + + constructor() { + + super(); + + this.isDiscard = true; + + this.initialize(); + + } + +} + +export class Continue extends ASTNode { + + constructor() { + + super(); + + this.isContinue = true; + + this.initialize(); + + } + +} + +export class Break extends ASTNode { + + constructor() { + + super(); + + this.isBreak = true; + + this.initialize(); + + } + +} + +export class Accessor extends ASTNode { + + constructor( property ) { + + super(); + + this.property = property; + + this.isAccessor = true; + + this.initialize(); + + } + + getType() { + + if ( this.linker.reference ) { + + return this.linker.reference.getType(); + + } + + return super.getType(); + + } + +} + +export class StaticElement extends ASTNode { + + constructor( value ) { + + super(); + + this.value = value; + + this.isStaticElement = true; + + this.initialize(); + + } + +} + +export class DynamicElement extends ASTNode { + + constructor( value ) { + + super(); + + this.value = value; + + this.isDynamicElement = true; + + this.initialize(); + + } + +} + +export class AccessorElements extends ASTNode { + + constructor( object, elements = [] ) { + + super(); + + this.object = object; + this.elements = elements; + + this.isAccessorElements = true; + + this.initialize(); + + } + +} + +export class For extends ASTNode { + + constructor( initialization, condition, afterthought, body = [] ) { + + super(); + + this.initialization = initialization; + this.condition = condition; + this.afterthought = afterthought; + this.body = body; + + this.isFor = true; + + this.initialize(); + + } + +} + +export class While extends ASTNode { + + constructor( condition, body = [] ) { + + super(); + + this.condition = condition; + this.body = body; + + this.isWhile = true; + + this.initialize(); + + } + +} + + +export class Switch extends ASTNode { + + constructor( discriminant, cases ) { + + super(); + + this.discriminant = discriminant; + this.cases = cases; + + this.isSwitch = true; + + this.initialize(); + + } + +} + +export class SwitchCase extends ASTNode { + + constructor( body, conditions = null ) { + + super(); + + this.body = body; + this.conditions = conditions; + + this.isDefault = conditions === null ? true : false; + this.isSwitchCase = true; + + this.initialize(); + + } + +} diff --git a/examples/jsm/transpiler/GLSLDecoder.js b/examples/jsm/transpiler/GLSLDecoder.js new file mode 100644 index 00000000000000..93121fa912e802 --- /dev/null +++ b/examples/jsm/transpiler/GLSLDecoder.js @@ -0,0 +1,1168 @@ +import { Program, FunctionDeclaration, Switch, For, AccessorElements, Ternary, Varying, DynamicElement, StaticElement, FunctionParameter, Unary, Conditional, VariableDeclaration, Operator, Number, String, FunctionCall, Return, Accessor, Uniform, Discard, SwitchCase, Continue, Break, While, Comment } from './AST.js'; + +import { isType } from './TranspilerUtils.js'; + +const unaryOperators = [ + '+', '-', '~', '!', '++', '--' +]; + +const arithmeticOperators = [ + '*', '/', '%', '+', '-', '<<', '>>' +]; + +const precedenceOperators = [ + '*', '/', '%', + '-', '+', + '<<', '>>', + '<', '>', '<=', '>=', + '==', '!=', + '&', + '^', + '|', + '&&', + '^^', + '||', + '?', + '=', + '+=', '-=', '*=', '/=', '%=', '^=', '&=', '|=', '<<=', '>>=', + ',' +].reverse(); + +const associativityRightToLeft = [ + '=', + '+=', '-=', '*=', '/=', '%=', '^=', '&=', '|=', '<<=', '>>=', + ',', + '?', + ':' +]; + +const glslToTSL = { + inversesqrt: 'inverseSqrt' +}; + +const samplers = [ 'sampler1D', 'sampler2D', 'sampler2DArray', 'sampler2DShadow', 'sampler2DArrayShadow', 'isampler2D', 'isampler2DArray', 'usampler2D', 'usampler2DArray' ]; +const samplersCube = [ 'samplerCube', 'samplerCubeShadow', 'usamplerCube', 'isamplerCube' ]; +const samplers3D = [ 'sampler3D', 'isampler3D', 'usampler3D' ]; + +const spaceRegExp = /^((\t| )\n*)+/; +const lineRegExp = /^\n+/; +const commentRegExp = /^\/\*[\s\S]*?\*\//; +const inlineCommentRegExp = /^\/\/.*?(?=\n|$)/; + +const numberRegExp = /^((0x\w+)|(\.?\d+\.?\d*((e-?\d+)|\w)?))/; +const stringDoubleRegExp = /^(\"((?:[^"\\]|\\.)*)\")/; +const stringSingleRegExp = /^(\'((?:[^'\\]|\\.)*)\')/; +const literalRegExp = /^[A-Za-z](\w|\.)*/; +const operatorsRegExp = new RegExp( '^(\\' + [ + '<<=', '>>=', '++', '--', '<<', '>>', '+=', '-=', '*=', '/=', '%=', '&=', '^^', '^=', '|=', + '<=', '>=', '==', '!=', '&&', '||', + '(', ')', '[', ']', '{', '}', + '.', ',', ';', '!', '=', '~', '*', '/', '%', '+', '-', '<', '>', '&', '^', '|', '?', ':', '#' +].join( '$' ).split( '' ).join( '\\' ).replace( /\\\$/g, '|' ) + ')' ); + +function getFunctionName( str ) { + + return glslToTSL[ str ] || str; + +} + +function getGroupDelta( str ) { + + if ( str === '(' || str === '[' || str === '{' ) return 1; + if ( str === ')' || str === ']' || str === '}' ) return - 1; + + return 0; + +} + +class Token { + + constructor( tokenizer, type, str, pos ) { + + this.tokenizer = tokenizer; + + this.type = type; + + this.str = str; + this.pos = pos; + + this.isTag = false; + + this.tags = null; + + } + + get endPos() { + + return this.pos + this.str.length; + + } + + get isNumber() { + + return this.type === Token.NUMBER; + + } + + get isString() { + + return this.type === Token.STRING; + + } + + get isLiteral() { + + return this.type === Token.LITERAL; + + } + + get isOperator() { + + return this.type === Token.OPERATOR; + + } + +} + +Token.LINE = 'line'; +Token.COMMENT = 'comment'; +Token.NUMBER = 'number'; +Token.STRING = 'string'; +Token.LITERAL = 'literal'; +Token.OPERATOR = 'operator'; + +const TokenParserList = [ + { type: Token.LINE, regexp: lineRegExp, isTag: true }, + { type: Token.COMMENT, regexp: commentRegExp, isTag: true }, + { type: Token.COMMENT, regexp: inlineCommentRegExp, isTag: true }, + { type: Token.NUMBER, regexp: numberRegExp }, + { type: Token.STRING, regexp: stringDoubleRegExp, group: 2 }, + { type: Token.STRING, regexp: stringSingleRegExp, group: 2 }, + { type: Token.LITERAL, regexp: literalRegExp }, + { type: Token.OPERATOR, regexp: operatorsRegExp } +]; + +class Tokenizer { + + constructor( source ) { + + this.source = source; + this.position = 0; + + this.tokens = []; + + } + + tokenize() { + + let token = this.readToken(); + + while ( token ) { + + this.tokens.push( token ); + + token = this.readToken(); + + } + + return this; + + } + + skip( ...params ) { + + let remainingCode = this.source.substr( this.position ); + let i = params.length; + + while ( i -- ) { + + const skip = params[ i ].exec( remainingCode ); + const skipLength = skip ? skip[ 0 ].length : 0; + + if ( skipLength > 0 ) { + + this.position += skipLength; + + remainingCode = this.source.substr( this.position ); + + // re-skip, new remainingCode is generated + // maybe exist previous regexp non detected + i = params.length; + + } + + } + + return remainingCode; + + } + + nextToken() { + + const remainingCode = this.skip( spaceRegExp ); + + for ( var i = 0; i < TokenParserList.length; i ++ ) { + + const parser = TokenParserList[ i ]; + const result = parser.regexp.exec( remainingCode ); + + if ( result ) { + + const token = new Token( this, parser.type, result[ parser.group || 0 ], this.position ); + token.isTag = parser.isTag; + + this.position += result[ 0 ].length; + + return token; + + } + + } + + } + + readToken() { + + let token = this.nextToken(); + + if ( token && token.isTag ) { + + const tags = []; + + while ( token.isTag ) { + + tags.push( token ); + + token = this.nextToken(); + + if ( ! token ) return; + + } + + token.tags = tags; + + } + + return token; + + } + +} + +class GLSLDecoder { + + constructor() { + + this.index = 0; + this.tokenizer = null; + this.keywords = []; + + this.addPolyfill( 'gl_FragCoord', 'vec3 gl_FragCoord = vec3( screenCoordinate.x, screenCoordinate.y.oneMinus(), screenCoordinate.z );' ); + + } + + addPolyfill( name, polyfill ) { + + this.keywords.push( { name, polyfill } ); + + return this; + + } + + get tokens() { + + return this.tokenizer.tokens; + + } + + readToken() { + + return this.tokens[ this.index ++ ]; + + } + + getToken( offset = 0 ) { + + return this.tokens[ this.index + offset ]; + + } + + getTokensUntil( str, tokens, offset = 0 ) { + + const output = []; + + let groupIndex = 0; + + for ( let i = offset; i < tokens.length; i ++ ) { + + const token = tokens[ i ]; + + groupIndex += getGroupDelta( token.str ); + + output.push( token ); + + if ( groupIndex === 0 && token.str === str ) { + + break; + + } + + } + + return output; + + } + + readTokensUntil( str ) { + + const tokens = this.getTokensUntil( str, this.tokens, this.index ); + + this.index += tokens.length; + + return tokens; + + } + + parseExpressionFromTokens( tokens ) { + + if ( tokens.length === 0 ) return null; + + const firstToken = tokens[ 0 ]; + const lastToken = tokens[ tokens.length - 1 ]; + + // precedence operators + + let groupIndex = 0; + + for ( const operator of precedenceOperators ) { + + const parseToken = ( i, inverse = false ) => { + + const token = tokens[ i ]; + + groupIndex += getGroupDelta( token.str ); + + if ( ! token.isOperator || i === 0 || i === tokens.length - 1 ) return; + + // important for negate operator after arithmetic operator: a * -1, a * -( b ) + if ( inverse && arithmeticOperators.includes( tokens[ i - 1 ].str ) ) { + + return; + + } + + if ( groupIndex === 0 && token.str === operator ) { + + if ( operator === '?' ) { + + const conditionTokens = tokens.slice( 0, i ); + const leftTokens = this.getTokensUntil( ':', tokens, i + 1 ).slice( 0, - 1 ); + const rightTokens = tokens.slice( i + leftTokens.length + 2 ); + + const condition = this.parseExpressionFromTokens( conditionTokens ); + const left = this.parseExpressionFromTokens( leftTokens ); + const right = this.parseExpressionFromTokens( rightTokens ); + + return new Ternary( condition, left, right ); + + } else { + + const left = this.parseExpressionFromTokens( tokens.slice( 0, i ) ); + const right = this.parseExpressionFromTokens( tokens.slice( i + 1, tokens.length ) ); + + return new Operator( operator, left, right ); + + } + + } + + if ( inverse ) { + + if ( groupIndex > 0 ) { + + return this.parseExpressionFromTokens( tokens.slice( i ) ); + + } + + } else { + + if ( groupIndex < 0 ) { + + return this.parseExpressionFromTokens( tokens.slice( 0, i ) ); + + } + + } + + }; + + if ( associativityRightToLeft.includes( operator ) ) { + + for ( let i = 0; i < tokens.length; i ++ ) { + + const result = parseToken( i ); + + if ( result ) return result; + + } + + } else { + + for ( let i = tokens.length - 1; i >= 0; i -- ) { + + const result = parseToken( i, true ); + + if ( result ) return result; + + } + + } + + } + + // unary operators (before) + + if ( firstToken.isOperator ) { + + for ( const operator of unaryOperators ) { + + if ( firstToken.str === operator ) { + + const right = this.parseExpressionFromTokens( tokens.slice( 1 ) ); + + return new Unary( operator, right ); + + } + + } + + } + + // unary operators (after) + + if ( lastToken.isOperator ) { + + for ( const operator of unaryOperators ) { + + if ( lastToken.str === operator ) { + + const left = this.parseExpressionFromTokens( tokens.slice( 0, tokens.length - 1 ) ); + + return new Unary( operator, left, true ); + + } + + } + + } + + // groups + + if ( firstToken.str === '(' ) { + + const leftTokens = this.getTokensUntil( ')', tokens ); + + const left = this.parseExpressionFromTokens( leftTokens.slice( 1, leftTokens.length - 1 ) ); + + const operator = tokens[ leftTokens.length ]; + + if ( operator ) { + + const rightTokens = tokens.slice( leftTokens.length + 1 ); + const right = this.parseExpressionFromTokens( rightTokens ); + + return new Operator( operator.str, left, right ); + + } + + return left; + + } + + // primitives and accessors + + if ( firstToken.isNumber ) { + + let type; + + const isHex = /^(0x)/.test( firstToken.str ); + + if ( isHex ) type = 'int'; + else if ( /u$|U$/.test( firstToken.str ) ) type = 'uint'; + else if ( /f|e|\./.test( firstToken.str ) ) type = 'float'; + else type = 'int'; + + let str = firstToken.str.replace( /u|U|i$/, '' ); + + if ( isHex === false ) { + + str = str.replace( /f$/, '' ); + + } + + return new Number( str, type ); + + } else if ( firstToken.isString ) { + + return new String( firstToken.str ); + + } else if ( firstToken.isLiteral ) { + + if ( firstToken.str === 'return' ) { + + return new Return( this.parseExpressionFromTokens( tokens.slice( 1 ) ) ); + + } else if ( firstToken.str === 'discard' ) { + + return new Discard(); + + } else if ( firstToken.str === 'continue' ) { + + return new Continue(); + + } else if ( firstToken.str === 'break' ) { + + return new Break(); + + } + + const secondToken = tokens[ 1 ]; + + if ( secondToken ) { + + if ( secondToken.str === '(' ) { + + // function call + + const internalTokens = this.getTokensUntil( ')', tokens, 1 ).slice( 1, - 1 ); + + const paramsTokens = this.parseFunctionParametersFromTokens( internalTokens ); + + const functionCall = new FunctionCall( getFunctionName( firstToken.str ), paramsTokens ); + + const accessTokens = tokens.slice( 3 + internalTokens.length ); + + if ( accessTokens.length > 0 ) { + + const elements = this.parseAccessorElementsFromTokens( accessTokens ); + + return new AccessorElements( functionCall, elements ); + + } + + return functionCall; + + } else if ( secondToken.str === '[' ) { + + // array accessor + + const elements = this.parseAccessorElementsFromTokens( tokens.slice( 1 ) ); + + return new AccessorElements( new Accessor( firstToken.str ), elements ); + + } + + } + + return new Accessor( firstToken.str ); + + } + + } + + parseAccessorElementsFromTokens( tokens ) { + + const elements = []; + + let currentTokens = tokens; + + while ( currentTokens.length > 0 ) { + + const token = currentTokens[ 0 ]; + + if ( token.str === '[' ) { + + const accessorTokens = this.getTokensUntil( ']', currentTokens ); + + const element = this.parseExpressionFromTokens( accessorTokens.slice( 1, accessorTokens.length - 1 ) ); + + currentTokens = currentTokens.slice( accessorTokens.length ); + + elements.push( new DynamicElement( element ) ); + + } else if ( token.str === '.' ) { + + const accessorTokens = currentTokens.slice( 1, 2 ); + + const element = this.parseExpressionFromTokens( accessorTokens ); + + currentTokens = currentTokens.slice( 2 ); + + elements.push( new StaticElement( element ) ); + + } else { + + console.error( 'Unknown accessor expression', token ); + + break; + + } + + } + + return elements; + + } + + parseFunctionParametersFromTokens( tokens ) { + + if ( tokens.length === 0 ) return []; + + const expression = this.parseExpressionFromTokens( tokens ); + const params = []; + + let current = expression; + + while ( current.type === ',' ) { + + params.push( current.left ); + + current = current.right; + + } + + params.push( current ); + + return params; + + } + + parseExpression() { + + const tokens = this.readTokensUntil( ';' ); + + const exp = this.parseExpressionFromTokens( tokens.slice( 0, tokens.length - 1 ) ); + + return exp; + + } + + parseFunctionParams( tokens ) { + + const params = []; + + for ( let i = 0; i < tokens.length; i ++ ) { + + const immutable = tokens[ i ].str === 'const'; + if ( immutable ) i ++; + + let qualifier = tokens[ i ].str; + + if ( /^(in|out|inout)$/.test( qualifier ) ) { + + i ++; + + } else { + + qualifier = null; + + } + + const type = tokens[ i ++ ].str; + const name = tokens[ i ++ ].str; + + params.push( new FunctionParameter( type, name, qualifier, immutable ) ); + + if ( tokens[ i ] && tokens[ i ].str !== ',' ) throw new Error( 'Expected ","' ); + + } + + return params; + + } + + parseFunction() { + + const type = this.readToken().str; + const name = this.readToken().str; + + const paramsTokens = this.readTokensUntil( ')' ); + + const params = this.parseFunctionParams( paramsTokens.slice( 1, paramsTokens.length - 1 ) ); + const body = this.parseBlock(); + + const func = new FunctionDeclaration( type, name, params, body ); + + return func; + + } + + parseVariablesFromToken( tokens, type ) { + + let index = 0; + const immutable = tokens[ 0 ].str === 'const'; + + if ( immutable ) index ++; + + type = type || tokens[ index ++ ].str; + const name = tokens[ index ++ ].str; + + const token = tokens[ index ]; + + let init = null; + let next = null; + + if ( token ) { + + const initTokens = this.getTokensUntil( ',', tokens, index ); + + if ( initTokens[ 0 ].str === '=' ) { + + const expressionTokens = initTokens.slice( 1 ); + if ( expressionTokens[ expressionTokens.length - 1 ].str === ',' ) expressionTokens.pop(); + + init = this.parseExpressionFromTokens( expressionTokens ); + + } + + const nextTokens = tokens.slice( initTokens.length + ( index - 1 ) ); + + if ( nextTokens[ 0 ] && nextTokens[ 0 ].str === ',' ) { + + next = this.parseVariablesFromToken( nextTokens.slice( 1 ), type ); + + } + + } + + const variable = new VariableDeclaration( type, name, init, next, immutable ); + + return variable; + + } + + parseVariables() { + + const tokens = this.readTokensUntil( ';' ); + + return this.parseVariablesFromToken( tokens.slice( 0, tokens.length - 1 ) ); + + } + + parseUniform() { + + const tokens = this.readTokensUntil( ';' ); + + let type = tokens[ 1 ].str; + const name = tokens[ 2 ].str; + + // GLSL to TSL types + + if ( samplers.includes( type ) ) type = 'texture'; + else if ( samplersCube.includes( type ) ) type = 'cubeTexture'; + else if ( samplers3D.includes( type ) ) type = 'texture3D'; + + return new Uniform( type, name ); + + } + + parseVarying() { + + const tokens = this.readTokensUntil( ';' ); + + const type = tokens[ 1 ].str; + const name = tokens[ 2 ].str; + + return new Varying( type, name ); + + } + + parseReturn() { + + this.readToken(); // skip 'return' + + const expression = this.parseExpression(); + + return new Return( expression ); + + } + + parseWhile() { + + this.readToken(); // skip 'while' + + const conditionTokens = this.readTokensUntil( ')' ).slice( 1, - 1 ); + const condition = this.parseExpressionFromTokens( conditionTokens ); + + let body; + + if ( this.getToken().str === '{' ) { + + body = this.parseBlock(); + + } else { + + body = [ this.parseExpression() ]; + + } + + const statement = new While( condition, body ); + + return statement; + + } + + parseFor() { + + this.readToken(); // skip 'for' + + const forTokens = this.readTokensUntil( ')' ).slice( 1, - 1 ); + + const initializationTokens = this.getTokensUntil( ';', forTokens, 0 ).slice( 0, - 1 ); + const conditionTokens = this.getTokensUntil( ';', forTokens, initializationTokens.length + 1 ).slice( 0, - 1 ); + const afterthoughtTokens = forTokens.slice( initializationTokens.length + conditionTokens.length + 2 ); + + let initialization; + + if ( initializationTokens[ 0 ] && isType( initializationTokens[ 0 ].str ) ) { + + initialization = this.parseVariablesFromToken( initializationTokens ); + + } else { + + initialization = this.parseExpressionFromTokens( initializationTokens ); + + } + + const condition = this.parseExpressionFromTokens( conditionTokens ); + const afterthought = this.parseExpressionFromTokens( afterthoughtTokens ); + + let body; + + if ( this.getToken().str === '{' ) { + + body = this.parseBlock(); + + } else { + + body = [ this.parseExpression() ]; + + } + + const statement = new For( initialization, condition, afterthought, body ); + + return statement; + + } + + parseSwitch() { + + this.readToken(); // Skip 'switch' + + const switchDeterminantTokens = this.readTokensUntil( ')' ); + + // Parse expresison between parentheses. Index 1: char after '('. Index -1: char before ')' + const discriminant = this.parseExpressionFromTokens( switchDeterminantTokens.slice( 1, - 1 ) ); + + // Validate curly braces + if ( this.getToken().str !== '{' ) { + + throw new Error( 'Expected \'{\' after switch(...) ' ); + + } + + this.readToken(); // Skip '{' + + const cases = this.parseSwitchCases(); + + const switchStatement = new Switch( discriminant, cases ); + + return switchStatement; + + } + + parseSwitchCases() { + + const cases = []; + + let token = this.getToken(); + let conditions = null; + + const isCase = ( token ) => token.str === 'case' || token.str === 'default'; + + while ( isCase( token ) ) { + + this.readToken(); // Skip 'case' or 'default' + + if ( token.str === 'case' ) { + + const caseTokens = this.readTokensUntil( ':' ); + const caseStatement = this.parseExpressionFromTokens( caseTokens.slice( 0, - 1 ) ); + + conditions = conditions || []; + conditions.push( caseStatement ); + + } else { + + this.readTokensUntil( ':' ); // Skip 'default:' + + conditions = null; + + } + + token = this.getToken(); + + if ( isCase( token ) ) { + + // If the next token is another case/default, continue parsing + continue; + + } + + cases.push( new SwitchCase( this.parseBlock(), conditions ) ); + + token = this.getToken(); + + conditions = null; + + } + + return cases; + + } + + parseIf() { + + const parseIfExpression = () => { + + this.readToken(); // skip 'if' + + const condTokens = this.readTokensUntil( ')' ); + + return this.parseExpressionFromTokens( condTokens.slice( 1, condTokens.length - 1 ) ); + + }; + + const parseIfBlock = () => { + + let body; + + if ( this.getToken().str === '{' ) { + + body = this.parseBlock(); + + } else { + + body = [ this.parseExpression() ]; + + } + + return body; + + }; + + // + + // Parse the first if statement + const conditional = new Conditional( parseIfExpression(), parseIfBlock() ); + + // + + let current = conditional; + + while ( this.getToken() && this.getToken().str === 'else' ) { + + this.readToken(); // skip 'else' + + // Assign the current if/else statement as the previous within the chain of conditionals + const previous = current; + + let expression = null; + + // If an 'else if' statement, parse the conditional within the if + if ( this.getToken().str === 'if' ) { + + // Current conditional now equal to next conditional in the chain + expression = parseIfExpression(); + + } + + current = new Conditional( expression, parseIfBlock() ); + current.parent = previous; + + // n - 1 conditional's else statement assigned to new if/else statement + previous.elseConditional = current; + + } + + return conditional; + + } + + parseBlock() { + + const body = []; + + const firstToken = this.getToken(); + + if ( firstToken.str === '{' ) { + + this.readToken(); // skip '{' + + } + + let groupIndex = 0; + + while ( this.index < this.tokens.length ) { + + const token = this.getToken(); + + let statement = null; + + groupIndex += getGroupDelta( token.str ); + + if ( groupIndex === 0 && ( token.str === 'case' || token.str === 'default' ) ) { + + return body; // switch case or default statement, return body + + } else if ( groupIndex < 0 ) { + + this.readToken(); // skip '}' + + return body; + + } + + // + + if ( token.tags ) { + + let lastStatement = null; + + for ( const tag of token.tags ) { + + if ( tag.type === Token.COMMENT ) { + + const str = tag.str.replace( /\t/g, '' ); + + if ( ! lastStatement || lastStatement.isComment !== true ) { + + lastStatement = new Comment( str ); + body.push( lastStatement ); + + } else { + + lastStatement.comment += '\n' + str; + + } + + } + + } + + } + + if ( token.isLiteral || token.isOperator ) { + + if ( token.str === 'const' ) { + + statement = this.parseVariables(); + + } else if ( token.str === 'uniform' ) { + + statement = this.parseUniform(); + + } else if ( token.str === 'varying' ) { + + statement = this.parseVarying(); + + } else if ( isType( token.str ) ) { + + if ( this.getToken( 2 ).str === '(' ) { + + statement = this.parseFunction(); + + } else { + + statement = this.parseVariables(); + + } + + } else if ( token.str === 'return' ) { + + statement = this.parseReturn(); + + } else if ( token.str === 'if' ) { + + statement = this.parseIf(); + + } else if ( token.str === 'for' ) { + + statement = this.parseFor(); + + } else if ( token.str === 'while' ) { + + statement = this.parseWhile(); + + } else if ( token.str === 'switch' ) { + + statement = this.parseSwitch(); + + } else { + + statement = this.parseExpression(); + + } + + } + + if ( statement ) { + + body.push( statement ); + + } else { + + this.index ++; + + } + + } + + return body; + + } + + parse( source ) { + + let polyfill = ''; + + for ( const keyword of this.keywords ) { + + if ( new RegExp( `(^|\\b)${ keyword.name }($|\\b)`, 'gm' ).test( source ) ) { + + polyfill += keyword.polyfill + '\n'; + + } + + } + + if ( polyfill ) { + + polyfill = '// Polyfills\n\n' + polyfill + '\n'; + + } + + this.index = 0; + this.tokenizer = new Tokenizer( polyfill + source ).tokenize(); + + const body = this.parseBlock(); + const program = new Program( body ); + + return program; + + + } + +} + +export default GLSLDecoder; diff --git a/examples/jsm/transpiler/Linker.js b/examples/jsm/transpiler/Linker.js new file mode 100644 index 00000000000000..5cb4c980c7d65c --- /dev/null +++ b/examples/jsm/transpiler/Linker.js @@ -0,0 +1,327 @@ +class Block { + + constructor( node, parent = null ) { + + this.node = node; + this.parent = parent; + + this.properties = {}; + + } + + setProperty( name, value ) { + + this.properties[ name ] = value; + + } + + getProperty( name ) { + + let value = this.properties[ name ]; + + if ( value === undefined && this.parent !== null ) { + + value = this.parent.getProperty( name ); + + } + + return value; + + } + +} + +class Linker { + + constructor() { + + this.block = null; + + } + + addBlock( node ) { + + this.block = new Block( node, this.block ); + + } + + removeBlock( node ) { + + if ( this.block === null || this.block.node !== node ) { + + throw new Error( 'No block to remove or block mismatch.' ); + + } + + this.block = this.block.parent; + + } + + processVariables( node ) { + + this.block.setProperty( node.name, node ); + + if ( node.value ) { + + this.processExpression( node.value ); + + } + + } + + processUniform( node ) { + + this.block.setProperty( node.name, node ); + + } + + processVarying( node ) { + + this.block.setProperty( node.name, node ); + + } + + evalProperty( node ) { + + let property = ''; + + if ( node.isAccessor ) { + + property += node.property; + + } + + return property; + + } + + processExpression( node ) { + + if ( node.isAccessor ) { + + const property = this.block.getProperty( this.evalProperty( node ) ); + + if ( property ) { + + node.linker.reference = property; + + property.linker.accesses.push( node ); + + } + + } else if ( node.isNumber || node.isString ) { + + // Process primitive values + + } else if ( node.isOperator ) { + + this.processExpression( node.left ); + this.processExpression( node.right ); + + if ( node.isAssignment ) { + + const property = this.block.getProperty( this.evalProperty( node.left ) ); + + if ( property ) { + + property.linker.assignments.push( node ); + + } + + } + + } else if ( node.isFunctionCall ) { + + for ( const param of node.params ) { + + this.processExpression( param ); + + } + + } else if ( node.isReturn ) { + + if ( node.value ) this.processExpression( node.value ); + + } else if ( node.isDiscard || node.isBreak || node.isContinue ) { + + // Process control flow + + } else if ( node.isAccessorElements ) { + + this.processExpression( node.object ); + + for ( const element of node.elements ) { + + this.processExpression( element.value ); + + } + + } else if ( node.isDynamicElement || node.isStaticElement ) { + + this.processExpression( node.value ); + + } else if ( node.isFor || node.isWhile ) { + + this.processForWhile( node ); + + } else if ( node.isSwitch ) { + + this.processSwitch( node ); + + } else if ( node.isVariableDeclaration ) { + + this.processVariables( node ); + + } else if ( node.isUniform ) { + + this.processUniform( node ); + + } else if ( node.isVarying ) { + + this.processVarying( node ); + + } else if ( node.isTernary ) { + + this.processExpression( node.cond ); + this.processExpression( node.left ); + this.processExpression( node.right ); + + } else if ( node.isConditional ) { + + this.processConditional( node ); + + } else if ( node.isUnary ) { + + this.processExpression( node.expression ); + + if ( node.isAssignment ) { + + if ( node.parent.hasAssignment !== true ) { + + // optimize increment/decrement operator + // to avoid creating a new variable + + node.after = false; + + } + + const property = this.block.getProperty( this.evalProperty( node.expression ) ); + + if ( property ) { + + property.linker.assignments.push( node ); + + } + + } + + } + + } + + processBody( body ) { + + for ( const statement of body ) { + + this.processExpression( statement ); + + } + + } + + processConditional( node ) { + + this.processExpression( node.cond ); + this.processBody( node.body ); + + let current = node; + + while ( current.elseConditional ) { + + if ( current.elseConditional.cond ) { + + this.processExpression( current.elseConditional.cond ); + + } + + this.processBody( current.elseConditional.body ); + + current = current.elseConditional; + + } + + } + + processForWhile( node ) { + + if ( node.initialization ) this.processExpression( node.initialization ); + if ( node.condition ) this.processExpression( node.condition ); + if ( node.afterthought ) this.processExpression( node.afterthought ); + + this.processBody( node.body ); + + } + + processSwitch( switchNode ) { + + this.processExpression( switchNode.discriminant ); + + for ( const switchCase of switchNode.cases ) { + + if ( switchCase.isDefault !== true ) { + + for ( const condition of switchCase.conditions ) { + + this.processExpression( condition ); + + } + + } + + this.processBody( switchCase.body ); + + } + + } + + processFunction( node ) { + + this.addBlock( node ); + + for ( const param of node.params ) { + + this.block.setProperty( param.name, param ); + + } + + this.processBody( node.body ); + + this.removeBlock( node ); + + } + + process( ast ) { + + this.addBlock( ast ); + + for ( const statement of ast.body ) { + + if ( statement.isFunctionDeclaration ) { + + this.processFunction( statement ); + + } else { + + this.processExpression( statement ); + + } + + } + + this.removeBlock( ast ); + + } + +} + +export default Linker; diff --git a/examples/jsm/transpiler/ShaderToyDecoder.js b/examples/jsm/transpiler/ShaderToyDecoder.js new file mode 100644 index 00000000000000..f4fe871432e18c --- /dev/null +++ b/examples/jsm/transpiler/ShaderToyDecoder.js @@ -0,0 +1,49 @@ +import { Return, VariableDeclaration, Accessor } from './AST.js'; +import GLSLDecoder from './GLSLDecoder.js'; + +class ShaderToyDecoder extends GLSLDecoder { + + constructor() { + + super(); + + this.addPolyfill( 'iTime', 'float iTime = time;' ); + this.addPolyfill( 'iResolution', 'vec2 iResolution = screenSize;' ); + this.addPolyfill( 'fragCoord', 'vec3 fragCoord = vec3( screenCoordinate.x, screenSize.y - screenCoordinate.y, screenCoordinate.z );' ); + + } + + parseFunction() { + + const node = super.parseFunction(); + + if ( node.name === 'mainImage' ) { + + node.params = []; // remove default parameters + node.type = 'vec4'; + node.layout = false; // for now + + const fragColor = new Accessor( 'fragColor' ); + + for ( const subNode of node.body ) { + + if ( subNode.isReturn ) { + + subNode.value = fragColor; + + } + + } + + node.body.unshift( new VariableDeclaration( 'vec4', 'fragColor' ) ); + node.body.push( new Return( fragColor ) ); + + } + + return node; + + } + +} + +export default ShaderToyDecoder; diff --git a/examples/jsm/transpiler/TSLEncoder.js b/examples/jsm/transpiler/TSLEncoder.js new file mode 100644 index 00000000000000..c55ab1b932d6e7 --- /dev/null +++ b/examples/jsm/transpiler/TSLEncoder.js @@ -0,0 +1,941 @@ +import { REVISION } from 'three/webgpu'; +import * as TSL from 'three/tsl'; + +import { VariableDeclaration, Accessor } from './AST.js'; +import { isExpression, isPrimitive } from './TranspilerUtils.js'; + +const opLib = { + '=': 'assign', + '+': 'add', + '-': 'sub', + '*': 'mul', + '/': 'div', + '%': 'remainder', + '<': 'lessThan', + '>': 'greaterThan', + '<=': 'lessThanEqual', + '>=': 'greaterThanEqual', + '==': 'equal', + '!=': 'notEqual', + '&&': 'and', + '||': 'or', + '^^': 'xor', + '&': 'bitAnd', + '|': 'bitOr', + '^': 'bitXor', + '<<': 'shiftLeft', + '>>': 'shiftRight', + '+=': 'addAssign', + '-=': 'subAssign', + '*=': 'mulAssign', + '/=': 'divAssign', + '%=': 'remainderAssign', + '^=': 'bitXorAssign', + '&=': 'bitAndAssign', + '|=': 'bitOrAssign', + '<<=': 'shiftLeftAssign', + '>>=': 'shiftRightAssign' +}; + +const unaryLib = { + '+': '', // positive + '-': 'negate', + '~': 'bitNot', + '!': 'not', + '++': 'increment', // incrementBefore + '--': 'decrement' // decrementBefore +}; + +const textureLookupFunctions = [ 'texture', 'texture2D', 'texture3D', 'textureCube', 'textureLod', 'texelFetch', 'textureGrad' ]; + +class TSLEncoder { + + constructor() { + + this.tab = ''; + this.imports = new Set(); + this.global = new Set(); + this.overloadings = new Map(); + this.iife = false; + this.reference = false; + + this.block = null; + + } + + addImport( name ) { + + // import only if it's a node + + name = name.split( '.' )[ 0 ]; + + if ( TSL[ name ] !== undefined && this.global.has( name ) === false ) { + + this.imports.add( name ); + + } + + } + + emitUniform( node ) { + + let code = `const ${ node.name } = `; + this.global.add( node.name ); + + if ( this.reference === true ) { + + this.addImport( 'reference' ); + + //code += `reference( '${ node.name }', '${ node.type }', uniforms )`; + + // legacy + code += `reference( 'value', '${ node.type }', uniforms[ '${ node.name }' ] )`; + + } else { + + if ( node.type === 'texture' ) { + + this.addImport( 'texture' ); + + code += 'texture( /* */ )'; + + } else if ( node.type === 'cubeTexture' ) { + + this.addImport( 'cubeTexture' ); + + code += 'cubeTexture( /* */ )'; + + } else if ( node.type === 'texture3D' ) { + + this.addImport( 'texture3D' ); + + code += 'texture3D( /* */ )'; + + } else { + + // default uniform + + this.addImport( 'uniform' ); + + code += `uniform( '${ node.type }' )`; + + } + + } + + return code; + + } + + emitExpression( node, output = null ) { + + let code; + + if ( node.isAccessor ) { + + if ( node.linker.reference === null ) { + + this.addImport( node.property ); + + } + + code = node.property; + + } else if ( node.isNumber ) { + + code = node.value; + + } else if ( node.isString ) { + + code = '\'' + node.value + '\''; + + } else if ( node.isOperator ) { + + const opFn = opLib[ node.type ] || node.type; + + const left = this.emitExpression( node.left, output ); + const right = this.emitExpression( node.right, output ); + + if ( node.isNumericExpression ) { + + return left + ' ' + node.type + ' ' + right; + + } + + if ( isPrimitive( left ) ) { + + code = opFn + '( ' + left + ', ' + right + ' )'; + + this.addImport( opFn ); + + } else if ( opFn === '.' ) { + + code = left + opFn + right; + + } else { + + code = left + '.' + opFn + '( ' + right + ' )'; + + } + + } else if ( node.isFunctionCall ) { + + const params = []; + + for ( const parameter of node.params ) { + + params.push( this.emitExpression( parameter ) ); + + } + + // handle texture lookup function calls in separate branch + + if ( textureLookupFunctions.includes( node.name ) ) { + + code = `${ params[ 0 ] }.sample( ${ params[ 1 ] } )`; + + if ( node.name === 'texture' || node.name === 'texture2D' || node.name === 'texture3D' || node.name === 'textureCube' ) { + + if ( params.length === 3 ) { + + code += `.bias( ${ params[ 2 ] } )`; + + } + + } else if ( node.name === 'textureLod' ) { + + code += `.level( ${ params[ 2 ] } )`; + + } else if ( node.name === 'textureGrad' ) { + + code += `.grad( ${ params[ 2 ] }, ${ params[ 3 ] } )`; + + } else if ( node.name === 'texelFetch' ) { + + code += '.setSampler( false )'; + + } + + } else { + + this.addImport( node.name ); + + const paramsStr = params.length > 0 ? ' ' + params.join( ', ' ) + ' ' : ''; + + code = `${ node.name }(${ paramsStr })`; + + } + + } else if ( node.isReturn ) { + + code = 'return'; + + if ( node.value ) { + + code += ' ' + this.emitExpression( node.value ); + + } + + } else if ( node.isDiscard ) { + + this.addImport( 'Discard' ); + + code = 'Discard()'; + + } else if ( node.isBreak ) { + + this.addImport( 'Break' ); + + code = 'Break()'; + + } else if ( node.isContinue ) { + + this.addImport( 'Continue' ); + + code = 'Continue()'; + + } else if ( node.isAccessorElements ) { + + code = this.emitExpression( node.object ); + + for ( const element of node.elements ) { + + if ( element.isStaticElement ) { + + code += '.' + this.emitExpression( element.value ); + + } else if ( element.isDynamicElement ) { + + const value = this.emitExpression( element.value ); + + if ( isPrimitive( value ) ) { + + code += `[ ${ value } ]`; + + } else { + + code += `.element( ${ value } )`; + + } + + } + + } + + } else if ( node.isDynamicElement ) { + + code = this.emitExpression( node.value ); + + } else if ( node.isStaticElement ) { + + code = this.emitExpression( node.value ); + + } else if ( node.isFor ) { + + code = this.emitFor( node ); + + } else if ( node.isWhile ) { + + code = this.emitWhile( node ); + + } else if ( node.isSwitch ) { + + code = this.emitSwitch( node ); + + } else if ( node.isVariableDeclaration ) { + + code = this.emitVariables( node ); + + } else if ( node.isUniform ) { + + code = this.emitUniform( node ); + + } else if ( node.isVarying ) { + + code = this.emitVarying( node ); + + } else if ( node.isTernary ) { + + code = this.emitTernary( node ); + + } else if ( node.isConditional ) { + + code = this.emitConditional( node ); + + } else if ( node.isUnary && node.expression.isNumber && node.type === '-' ) { + + code = '- ' + node.expression.value; + + if ( node.expression.type !== 'float' ) { + + code = node.expression.type + '( ' + code + ' )'; + + this.addImport( node.expression.type ); + + } + + } else if ( node.isUnary ) { + + let type = unaryLib[ node.type ]; + + if ( node.hasAssignment ) { + + if ( node.after === false ) { + + type += 'Before'; + + } + + } + + const exp = this.emitExpression( node.expression ); + + if ( isPrimitive( exp ) ) { + + this.addImport( type ); + + code = type + '( ' + exp + ' )'; + + } else { + + code = exp + '.' + type + '()'; + + } + + } else { + + console.warn( 'Unknown node type', node ); + + } + + if ( ! code ) code = '/* unknown statement */'; + + return code; + + } + + emitBody( body ) { + + let code = ''; + + this.tab += '\t'; + + for ( const statement of body ) { + + code += this.emitExtraLine( statement, body ); + + if ( statement.isComment ) { + + code += this.emitComment( statement, body ); + + continue; + + } + + if ( this.block && this.block.isSwitchCase ) { + + if ( statement.isBreak ) continue; // skip break statements in switch cases + + } + + code += this.tab + this.emitExpression( statement ); + + if ( code.slice( - 1 ) !== '}' ) code += ';'; + + code += '\n'; + + } + + code = code.slice( 0, - 1 ); // remove the last extra line + + this.tab = this.tab.slice( 0, - 1 ); + + return code; + + + } + + emitTernary( node ) { + + const condStr = this.emitExpression( node.cond ); + const leftStr = this.emitExpression( node.left ); + const rightStr = this.emitExpression( node.right ); + + this.addImport( 'select' ); + + return `select( ${ condStr }, ${ leftStr }, ${ rightStr } )`; + + } + + emitConditional( node ) { + + const condStr = this.emitExpression( node.cond ); + const bodyStr = this.emitBody( node.body ); + + let ifStr = `If( ${ condStr }, () => { + +${ bodyStr } + +${ this.tab }} )`; + + let current = node; + + while ( current.elseConditional ) { + + const elseBodyStr = this.emitBody( current.elseConditional.body ); + + if ( current.elseConditional.cond ) { + + const elseCondStr = this.emitExpression( current.elseConditional.cond ); + + ifStr += `.ElseIf( ${ elseCondStr }, () => { + +${ elseBodyStr } + +${ this.tab }} )`; + + } else { + + ifStr += `.Else( () => { + +${ elseBodyStr } + +${ this.tab }} )`; + + } + + current = current.elseConditional; + + + } + + this.imports.add( 'If' ); + + return ifStr; + + } + + emitLoop( node ) { + + const start = this.emitExpression( node.initialization.value ); + const end = this.emitExpression( node.condition.right ); + + const name = node.initialization.name; + const type = node.initialization.type; + const condition = node.condition.type; + + const nameParam = name !== 'i' ? `, name: '${ name }'` : ''; + const typeParam = type !== 'int' ? `, type: '${ type }'` : ''; + const conditionParam = condition !== '<' ? `, condition: '${ condition }'` : ''; + + let updateParam = ''; + + if ( node.afterthought.isUnary ) { + + if ( node.afterthought.type !== '++' ) { + + updateParam = `, update: '${ node.afterthought.type }'`; + + } + + } else if ( node.afterthought.isOperator ) { + + if ( node.afterthought.right.isAccessor || node.afterthought.right.isNumber ) { + + updateParam = `, update: ${ this.emitExpression( node.afterthought.right ) }`; + + } else { + + updateParam = `, update: ( { i } ) => ${ this.emitExpression( node.afterthought ) }`; + + } + + } + + let loopStr = `Loop( { start: ${ start }, end: ${ end + nameParam + typeParam + conditionParam + updateParam } }, ( { ${ name } } ) => {\n\n`; + + loopStr += this.emitBody( node.body ) + '\n\n'; + + loopStr += this.tab + '} )'; + + this.imports.add( 'Loop' ); + + return loopStr; + + } + + + emitSwitch( switchNode ) { + + const discriminantString = this.emitExpression( switchNode.discriminant ); + + this.tab += '\t'; + + let switchString = `Switch( ${ discriminantString } )\n${ this.tab }`; + + const previousBlock = this.block; + + for ( const switchCase of switchNode.cases ) { + + this.block = switchCase; + + let caseBodyString; + + if ( ! switchCase.isDefault ) { + + const caseConditions = [ ]; + + for ( const condition of switchCase.conditions ) { + + caseConditions.push( this.emitExpression( condition ) ); + + } + + caseBodyString = this.emitBody( switchCase.body ); + + switchString += `.Case( ${ caseConditions.join( ', ' ) }, `; + + } else { + + caseBodyString = this.emitBody( switchCase.body ); + + switchString += '.Default( '; + + } + + switchString += `() => { + +${ caseBodyString } + +${ this.tab }} )`; + + } + + this.block = previousBlock; + + this.tab = this.tab.slice( 0, - 1 ); + + this.imports.add( 'Switch' ); + + return switchString; + + } + + emitFor( node ) { + + const { initialization, condition, afterthought } = node; + + if ( ( initialization && initialization.isVariableDeclaration && initialization.next === null ) && + ( condition && condition.left.isAccessor && condition.left.property === initialization.name ) && + ( afterthought && ( + ( afterthought.isUnary && ( initialization.name === afterthought.expression.property ) ) || + ( afterthought.isOperator && ( initialization.name === afterthought.left.property ) ) + ) ) + ) { + + return this.emitLoop( node ); + + } + + return this.emitForWhile( node ); + + } + + emitForWhile( node ) { + + const initialization = this.emitExpression( node.initialization ); + const condition = this.emitExpression( node.condition ); + const afterthought = this.emitExpression( node.afterthought ); + + this.tab += '\t'; + + let forStr = '{\n\n' + this.tab + initialization + ';\n\n'; + forStr += `${ this.tab }Loop( ${ condition }, () => {\n\n`; + + forStr += this.emitBody( node.body ) + '\n\n'; + + forStr += this.tab + '\t' + afterthought + ';\n\n'; + + forStr += this.tab + '} )\n\n'; + + this.tab = this.tab.slice( 0, - 1 ); + + forStr += this.tab + '}'; + + this.imports.add( 'Loop' ); + + return forStr; + + } + + emitWhile( node ) { + + const condition = this.emitExpression( node.condition ); + + let whileStr = `Loop( ${ condition }, () => {\n\n`; + + whileStr += this.emitBody( node.body ) + '\n\n'; + + whileStr += this.tab + '} )'; + + this.imports.add( 'Loop' ); + + return whileStr; + + } + + emitVariables( node, isRoot = true ) { + + const { name, type, value, next } = node; + + let varStr = isRoot ? 'const ' : ''; + varStr += name; + + if ( value ) { + + let valueStr = this.emitExpression( value ); + + if ( value.isNumericExpression ) { + + // convert JS primitive to node + + valueStr = `${ type }( ${ valueStr } )`; + + this.addImport( type ); + + } + + if ( node.linker.assignments.length > 0 ) { + + varStr += ' = ' + valueStr + '.toVar()'; + + } else { + + varStr += ' = ' + valueStr; + + } + + } else { + + varStr += ` = property( '${ type }' )`; + + this.addImport( 'property' ); + + } + + if ( next ) { + + varStr += ', ' + this.emitVariables( next, false ); + + } + + return varStr; + + } + + emitVarying( node ) { + + const { name, type } = node; + + this.addImport( 'varying' ); + this.addImport( type ); + + return `const ${ name } = varying( ${ type }(), '${ name }' )`; + + } + + emitOverloadingFunction( nodes ) { + + const { name } = nodes[ 0 ]; + + this.addImport( 'overloadingFn' ); + + const prefix = this.iife === false ? 'export ' : ''; + + return `${ prefix }const ${ name } = /*@__PURE__*/ overloadingFn( [ ${ nodes.map( node => node.name + '_' + nodes.indexOf( node ) ).join( ', ' ) } ] );\n`; + + } + + emitFunction( node ) { + + const { name, type } = node; + + const params = []; + const inputs = []; + const mutableParams = []; + + let hasPointer = false; + + for ( const param of node.params ) { + + let name = param.name; + + if ( param.linker.assignments.length > 0 ) { + + name = name + '_immutable'; + + mutableParams.push( param ); + + } + + if ( param.qualifier ) { + + if ( param.qualifier === 'inout' || param.qualifier === 'out' ) { + + hasPointer = true; + + } + + } + + inputs.push( param.name + ': \'' + param.type + '\'' ); + params.push( name ); + + } + + for ( const param of mutableParams ) { + + const mutableParam = new VariableDeclaration( param.type, param.name, new Accessor( param.name + '_immutable' ), null, true ); + mutableParam.parent = param.parent; // link to the original node + mutableParam.linker.assignments.push( mutableParam ); + + node.body.unshift( mutableParam ); + + } + + const paramsStr = params.length > 0 ? ' [ ' + params.join( ', ' ) + ' ] ' : ''; + const bodyStr = this.emitBody( node.body ); + + let fnName = name; + let overloadingNodes = null; + + if ( this.overloadings.has( name ) ) { + + const overloadings = this.overloadings.get( name ); + + if ( overloadings.length > 1 ) { + + const index = overloadings.indexOf( node ); + + fnName += '_' + index; + + if ( index === overloadings.length - 1 ) { + + overloadingNodes = overloadings; + + } + + } + + } + + const prefix = this.iife === false ? 'export ' : ''; + + let funcStr = `${ prefix }const ${ fnName } = /*@__PURE__*/ Fn( (${ paramsStr }) => { + +${ bodyStr } + +${ this.tab }}`; + + if ( node.layout !== false && hasPointer === false ) { + + funcStr += ', { ' + inputs.join( ', ' ) + ', return: \'' + type + '\' }'; + + } + + funcStr += ' );\n'; + + this.imports.add( 'Fn' ); + + this.global.add( node.name ); + + if ( overloadingNodes !== null ) { + + funcStr += '\n' + this.emitOverloadingFunction( overloadingNodes ); + + } + + return funcStr; + + } + + emitComment( statement, body ) { + + const index = body.indexOf( statement ); + const previous = body[ index - 1 ]; + const next = body[ index + 1 ]; + + let output = ''; + + if ( previous && isExpression( previous ) ) { + + output += '\n'; + + } + + output += this.tab + statement.comment.replace( /\n/g, '\n' + this.tab ) + '\n'; + + if ( next && isExpression( next ) ) { + + output += '\n'; + + } + + return output; + + } + + emitExtraLine( statement, body ) { + + const index = body.indexOf( statement ); + const previous = body[ index - 1 ]; + + if ( previous === undefined ) return ''; + + if ( statement.isReturn ) return '\n'; + + const lastExp = isExpression( previous ); + const currExp = isExpression( statement ); + + if ( lastExp !== currExp || ( ! lastExp && ! currExp ) ) return '\n'; + + return ''; + + } + + emit( ast ) { + + let code = '\n'; + + if ( this.iife ) this.tab += '\t'; + + const overloadings = this.overloadings; + + for ( const statement of ast.body ) { + + if ( statement.isFunctionDeclaration ) { + + if ( overloadings.has( statement.name ) === false ) { + + overloadings.set( statement.name, [] ); + + } + + overloadings.get( statement.name ).push( statement ); + + } + + } + + for ( const statement of ast.body ) { + + code += this.emitExtraLine( statement, ast.body ); + + if ( statement.isComment ) { + + code += this.emitComment( statement, ast.body ); + + continue; + + } + + if ( statement.isFunctionDeclaration ) { + + code += this.tab + this.emitFunction( statement ); + + } else { + + code += this.tab + this.emitExpression( statement ) + ';\n'; + + } + + } + + const imports = [ ...this.imports ]; + const exports = [ ...this.global ]; + + let header = '// Three.js Transpiler r' + REVISION + '\n\n'; + let footer = ''; + + if ( this.iife ) { + + header += '( function ( TSL, uniforms ) {\n\n'; + + header += imports.length > 0 ? '\tconst { ' + imports.join( ', ' ) + ' } = TSL;\n' : ''; + footer += exports.length > 0 ? '\treturn { ' + exports.join( ', ' ) + ' };\n' : ''; + + footer += '\n} );'; + + } else { + + header += imports.length > 0 ? 'import { ' + imports.join( ', ' ) + ' } from \'three/tsl\';\n' : ''; + + } + + return header + code + footer; + + } + +} + +export default TSLEncoder; diff --git a/examples/jsm/transpiler/Transpiler.js b/examples/jsm/transpiler/Transpiler.js new file mode 100644 index 00000000000000..a8e67d0789f89d --- /dev/null +++ b/examples/jsm/transpiler/Transpiler.js @@ -0,0 +1,67 @@ +import Linker from './Linker.js'; + +/** + * A class that transpiles shader code from one language into another. + * + * `Transpiler` can only be used to convert GLSL into TSL right now. It is intended + * to support developers when they want to migrate their custom materials from the + * current to the new node-based material system. + * + * @three_import import Transpiler from 'three/addons/transpiler/Transpiler.js'; + */ +class Transpiler { + + /** + * Constructs a new transpiler. + * + * @param {GLSLDecoder} decoder - The GLSL decoder. + * @param {TSLEncoder} encoder - The TSL encoder. + */ + constructor( decoder, encoder ) { + + /** + * The GLSL decoder. This component parse GLSL and produces + * a language-independent AST for further processing. + * + * @type {GLSLDecoder} + */ + this.decoder = decoder; + + /** + * The TSL encoder. It takes the AST and emits TSL code. + * + * @type {TSLEncoder} + */ + this.encoder = encoder; + + /** + * The linker. It processes the AST and resolves + * variable and function references, ensuring that all + * dependencies are properly linked. + * + * @type {Linker} + */ + this.linker = new Linker(); + + } + + /** + * Parses the given GLSL source and returns TSL syntax. + * + * @param {string} source - The GLSL source. + * @return {string} The TSL code. + */ + parse( source ) { + + const ast = this.decoder.parse( source ); + + // Process the AST to resolve variable and function references and optimizations. + this.linker.process( ast ); + + return this.encoder.emit( ast ); + + } + +} + +export default Transpiler; diff --git a/examples/jsm/transpiler/TranspilerUtils.js b/examples/jsm/transpiler/TranspilerUtils.js new file mode 100644 index 00000000000000..d9e74a32504183 --- /dev/null +++ b/examples/jsm/transpiler/TranspilerUtils.js @@ -0,0 +1,29 @@ +export function isExpression( st ) { + + return st.isFunctionDeclaration !== true && st.isFor !== true && st.isWhile !== true && st.isConditional !== true && st.isSwitch !== true; + +} + +export function isPrimitive( value ) { + + return /^(true|false|-?(\d|\.\d))/.test( value ); + +} + +export function isType( str ) { + + return /void|bool|float|u?int|mat[234]|mat[234]x[234]|(u|i|b)?vec[234]/.test( str ); + +} + +export function toFloatType( type ) { + + if ( /^(i?int)$/.test( type ) ) return 'float'; + + const vecMatch = /^(i|u)?vec([234])$/.exec( type ); + + if ( vecMatch ) return 'vec' + vecMatch[ 2 ]; + + return type; + +} diff --git a/examples/jsm/transpiler/WGSLEncoder.js b/examples/jsm/transpiler/WGSLEncoder.js new file mode 100644 index 00000000000000..2c6e0fff51e672 --- /dev/null +++ b/examples/jsm/transpiler/WGSLEncoder.js @@ -0,0 +1,788 @@ +import { REVISION } from 'three/webgpu'; + +import { VariableDeclaration, Accessor } from './AST.js'; +import { isExpression } from './TranspilerUtils.js'; + +// Note: This is a simplified list. A complete implementation would need more mappings. +const typeMap = { + 'float': 'f32', + 'int': 'i32', + 'uint': 'u32', + 'bool': 'bool', + 'vec2': 'vec2f', + 'ivec2': 'vec2i', + 'uvec2': 'vec2u', + 'bvec2': 'vec2b', + 'vec3': 'vec3f', + 'ivec3': 'vec3i', + 'uvec3': 'vec3u', + 'bvec3': 'vec3b', + 'vec4': 'vec4f', + 'ivec4': 'vec4i', + 'uvec4': 'vec4u', + 'bvec4': 'vec4b', + 'mat3': 'mat3x3', + 'mat4': 'mat4x4', + 'texture': 'texture_2d', + 'textureCube': 'texture_cube', + 'texture3D': 'texture_3d', +}; + +// GLSL to WGSL built-in function mapping +const wgslLib = { + 'abs': 'abs', + 'acos': 'acos', + 'asin': 'asin', + 'atan': 'atan', + 'atan2': 'atan2', + 'ceil': 'ceil', + 'clamp': 'clamp', + 'cos': 'cos', + 'cross': 'cross', + 'degrees': 'degrees', + 'distance': 'distance', + 'dot': 'dot', + 'exp': 'exp', + 'exp2': 'exp2', + 'faceforward': 'faceForward', + 'floor': 'floor', + 'fract': 'fract', + 'inverse': 'inverse', + 'inversesqrt': 'inverseSqrt', + 'length': 'length', + 'log': 'log', + 'log2': 'log2', + 'max': 'max', + 'min': 'min', + 'mix': 'mix', + 'normalize': 'normalize', + 'pow': 'pow', + 'radians': 'radians', + 'reflect': 'reflect', + 'refract': 'refract', + 'round': 'round', + 'sign': 'sign', + 'sin': 'sin', + 'smoothstep': 'smoothstep', + 'sqrt': 'sqrt', + 'step': 'step', + 'tan': 'tan', + 'transpose': 'transpose', + 'trunc': 'trunc', + 'dFdx': 'dpdx', + 'dFdy': 'dpdy', + 'fwidth': 'fwidth', + // Texture functions are handled separately + 'texture': 'textureSample', + 'texture2D': 'textureSample', + 'texture3D': 'textureSample', + 'textureCube': 'textureSample', + 'textureLod': 'textureSampleLevel', + 'texelFetch': 'textureLoad', + 'textureGrad': 'textureSampleGrad', +}; + +class WGSLEncoder { + + constructor() { + + this.tab = ''; + this.functions = new Map(); + this.uniforms = []; + this.varyings = []; + this.structs = new Map(); + this.polyfills = new Map(); + + // Assume a single group for simplicity + this.groupIndex = 0; + + } + + getWgslType( type ) { + + return typeMap[ type ] || type; + + } + + emitExpression( node ) { + + if ( ! node ) return ''; + + let code; + + if ( node.isAccessor ) { + + // Check if this accessor is part of a uniform struct + const uniform = this.uniforms.find( u => u.name === node.property ); + + if ( uniform && ! uniform.type.includes( 'texture' ) ) { + + return `uniforms.${node.property}`; + + } + + code = node.property; + + } else if ( node.isNumber ) { + + code = node.value; + + // WGSL requires floating point numbers to have a decimal + if ( node.type === 'float' && ! code.includes( '.' ) ) { + + code += '.0'; + + } + + } else if ( node.isOperator ) { + + const left = this.emitExpression( node.left ); + const right = this.emitExpression( node.right ); + + code = `${ left } ${ node.type } ${ right }`; + + if ( node.parent.isAssignment !== true && node.parent.isOperator ) { + + code = `( ${ code } )`; + + } + + } else if ( node.isFunctionCall ) { + + const fnName = wgslLib[ node.name ] || node.name; + + if ( fnName === 'mod' ) { + + const snippets = node.params.map( p => this.emitExpression( p ) ); + const types = node.params.map( p => p.getType() ); + + const modFnName = 'mod_' + types.join( '_' ); + + if ( this.polyfills.has( modFnName ) === false ) { + + this.polyfills.set( modFnName, `fn ${ modFnName }( x: ${ this.getWgslType( types[ 0 ] ) }, y: ${ this.getWgslType( types[ 1 ] ) } ) -> ${ this.getWgslType( types[ 0 ] ) } { + + return x - y * floor( x / y ); + +}` ); + + } + + code = `${ modFnName }( ${ snippets.join( ', ' ) } )`; + + } else if ( fnName.startsWith( 'texture' ) ) { + + // Handle texture functions separately due to sampler handling + + code = this.emitTextureAccess( node ); + + } else { + + const params = node.params.map( p => this.emitExpression( p ) ); + + if ( typeMap[ fnName ] ) { + + // Handle type constructors like vec3(...) + + code = this.getWgslType( fnName ); + + } else { + + code = fnName; + + } + + if ( params.length > 0 ) { + + code += '( ' + params.join( ', ' ) + ' )'; + + } else { + + code += '()'; + + } + + } + + } else if ( node.isReturn ) { + + code = 'return'; + + if ( node.value ) { + + code += ' ' + this.emitExpression( node.value ); + + } + + } else if ( node.isDiscard ) { + + code = 'discard'; + + } else if ( node.isBreak ) { + + if ( node.parent.isSwitchCase !== true ) { + + code = 'break'; + + } + + } else if ( node.isContinue ) { + + code = 'continue'; + + } else if ( node.isAccessorElements ) { + + code = this.emitExpression( node.object ); + + for ( const element of node.elements ) { + + const value = this.emitExpression( element.value ); + + if ( element.isStaticElement ) { + + code += '.' + value; + + } else if ( element.isDynamicElement ) { + + code += `[${value}]`; + + } + + } + + } else if ( node.isFor ) { + + code = this.emitFor( node ); + + } else if ( node.isWhile ) { + + code = this.emitWhile( node ); + + } else if ( node.isSwitch ) { + + code = this.emitSwitch( node ); + + } else if ( node.isVariableDeclaration ) { + + code = this.emitVariables( node ); + + } else if ( node.isUniform ) { + + this.uniforms.push( node ); + return ''; // Defer emission to the header + + } else if ( node.isVarying ) { + + this.varyings.push( node ); + return ''; // Defer emission to the header + + } else if ( node.isTernary ) { + + const cond = this.emitExpression( node.cond ); + const left = this.emitExpression( node.left ); + const right = this.emitExpression( node.right ); + + // WGSL's equivalent to the ternary operator is select(false_val, true_val, condition) + code = `select( ${ right }, ${ left }, ${ cond } )`; + + } else if ( node.isConditional ) { + + code = this.emitConditional( node ); + + } else if ( node.isUnary ) { + + const expr = this.emitExpression( node.expression ); + + if ( node.type === '++' || node.type === '--' ) { + + const op = node.type === '++' ? '+' : '-'; + + code = `${ expr } = ${ expr } ${ op } 1`; + + } else { + + code = `${ node.type }${ expr }`; + + } + + } else { + + console.warn( 'Unknown node type in WGSL Encoder:', node ); + + code = `/* unknown node: ${ node.constructor.name } */`; + + } + + return code; + + } + + emitTextureAccess( node ) { + + const wgslFn = wgslLib[ node.name ]; + const textureName = this.emitExpression( node.params[ 0 ] ); + const uv = this.emitExpression( node.params[ 1 ] ); + + // WGSL requires explicit samplers. We assume a naming convention. + const samplerName = `${textureName}_sampler`; + + let code; + + switch ( node.name ) { + + case 'texture': + case 'texture2D': + case 'texture3D': + case 'textureCube': + // format: textureSample(texture, sampler, coords, [offset]) + code = `${wgslFn}(${textureName}, ${samplerName}, ${uv}`; + // Handle optional bias parameter (note: WGSL uses textureSampleBias) + if ( node.params.length === 3 ) { + + const bias = this.emitExpression( node.params[ 2 ] ); + code = `textureSampleBias(${textureName}, ${samplerName}, ${uv}, ${bias})`; + + } else { + + code += ')'; + + } + + break; + + case 'textureLod': + // format: textureSampleLevel(texture, sampler, coords, level) + const lod = this.emitExpression( node.params[ 2 ] ); + code = `${wgslFn}(${textureName}, ${samplerName}, ${uv}, ${lod})`; + break; + + case 'textureGrad': + // format: textureSampleGrad(texture, sampler, coords, ddx, ddy) + const ddx = this.emitExpression( node.params[ 2 ] ); + const ddy = this.emitExpression( node.params[ 3 ] ); + code = `${wgslFn}(${textureName}, ${samplerName}, ${uv}, ${ddx}, ${ddy})`; + break; + + case 'texelFetch': + // format: textureLoad(texture, coords, [level]) + const coords = this.emitExpression( node.params[ 1 ] ); // should be ivec + const lodFetch = node.params.length > 2 ? this.emitExpression( node.params[ 2 ] ) : '0'; + code = `${wgslFn}(${textureName}, ${coords}, ${lodFetch})`; + break; + + default: + code = `/* unsupported texture op: ${node.name} */`; + + } + + return code; + + } + + emitBody( body ) { + + let code = ''; + this.tab += '\t'; + + for ( const statement of body ) { + + code += this.emitExtraLine( statement, body ); + + if ( statement.isComment ) { + + code += this.emitComment( statement, body ); + continue; + + } + + const statementCode = this.emitExpression( statement ); + + if ( statementCode ) { + + code += this.tab + statementCode; + + if ( ! statementCode.endsWith( '}' ) && ! statementCode.endsWith( '{' ) ) { + + code += ';'; + + } + + code += '\n'; + + } + + } + + this.tab = this.tab.slice( 0, - 1 ); + return code.slice( 0, - 1 ); // remove the last extra line + + } + + emitConditional( node ) { + + const condStr = this.emitExpression( node.cond ); + const bodyStr = this.emitBody( node.body ); + + let ifStr = `if ( ${ condStr } ) {\n\n${ bodyStr }\n\n${ this.tab }}`; + + let current = node; + + while ( current.elseConditional ) { + + current = current.elseConditional; + const elseBodyStr = this.emitBody( current.body ); + + if ( current.cond ) { // This is an 'else if' + + const elseCondStr = this.emitExpression( current.cond ); + + ifStr += ` else if ( ${ elseCondStr } ) {\n\n${ elseBodyStr }\n\n${ this.tab }}`; + + } else { // This is an 'else' + + ifStr += ` else {\n\n${ elseBodyStr }\n\n${ this.tab }}`; + + } + + } + + return ifStr; + + } + + emitFor( node ) { + + const init = this.emitExpression( node.initialization ); + const cond = this.emitExpression( node.condition ); + const after = this.emitExpression( node.afterthought ); + const body = this.emitBody( node.body ); + + return `for ( ${ init }; ${ cond }; ${ after } ) {\n\n${ body }\n\n${ this.tab }}`; + + } + + emitWhile( node ) { + + const cond = this.emitExpression( node.condition ); + const body = this.emitBody( node.body ); + + return `while ( ${ cond } ) {\n\n${ body }\n\n${ this.tab }}`; + + } + + emitSwitch( node ) { + + const discriminant = this.emitExpression( node.discriminant ); + + let switchStr = `switch ( ${ discriminant } ) {\n\n`; + + this.tab += '\t'; + + for ( const switchCase of node.cases ) { + + const body = this.emitBody( switchCase.body ); + + if ( switchCase.isDefault ) { + + switchStr += `${ this.tab }default: {\n\n${ body }\n\n${ this.tab }}\n\n`; + + } else { + + const cases = switchCase.conditions.map( c => this.emitExpression( c ) ).join( ', ' ); + + switchStr += `${ this.tab }case ${ cases }: {\n\n${ body }\n\n${ this.tab }}\n\n`; + + } + + } + + this.tab = this.tab.slice( 0, - 1 ); + + switchStr += `${this.tab}}`; + + return switchStr; + + } + + emitVariables( node ) { + + const declarations = []; + + let current = node; + + while ( current ) { + + const type = this.getWgslType( current.type ); + + let valueStr = ''; + + if ( current.value ) { + + valueStr = ` = ${this.emitExpression( current.value )}`; + + } + + // The AST linker tracks if a variable is ever reassigned. + // If so, use 'var'; otherwise, use 'let'. + + let keyword; + + if ( current.linker ) { + + if ( current.linker.assignments.length > 0 ) { + + keyword = 'var'; // Reassigned variable + + } else { + + if ( current.value && current.value.isNumericExpression ) { + + keyword = 'const'; // Immutable numeric expression + + } else { + + keyword = 'let'; // Immutable variable + + } + + } + + } + + declarations.push( `${ keyword } ${ current.name }: ${ type }${ valueStr }` ); + + current = current.next; + + } + + // In WGSL, multiple declarations in one line are not supported, so join with semicolons. + return declarations.join( ';\n' + this.tab ); + + } + + emitFunction( node ) { + + const name = node.name; + const returnType = this.getWgslType( node.type ); + + const params = []; + // We will prepend to a copy of the body, not the original AST node. + const body = [ ...node.body ]; + + for ( const param of node.params ) { + + const paramName = param.name; + let paramType = this.getWgslType( param.type ); + + // Handle 'inout' and 'out' qualifiers using pointers. They are already mutable. + if ( param.qualifier === 'inout' || param.qualifier === 'out' ) { + + paramType = `ptr`; + params.push( `${paramName}: ${paramType}` ); + continue; + + } + + // If the parameter is reassigned within the function, we need to + // create a local, mutable variable that shadows the parameter's name. + if ( param.linker && param.linker.assignments.length > 0 ) { + + // 1. Rename the incoming parameter to avoid name collision. + const immutableParamName = `${paramName}_in`; + params.push( `${immutableParamName}: ${paramType}` ); + + // 2. Create a new Accessor node for the renamed immutable parameter. + const immutableAccessor = new Accessor( immutableParamName ); + immutableAccessor.isAccessor = true; + immutableAccessor.property = immutableParamName; + + // 3. Create a new VariableDeclaration node for the mutable local variable. + // This new variable will have the original parameter's name. + const mutableVar = new VariableDeclaration( param.type, param.name, immutableAccessor ); + + // 4. Mark this new variable as mutable so `emitVariables` uses `var`. + mutableVar.linker = { assignments: [ true ] }; + + // 5. Prepend this new declaration to the function's body. + body.unshift( mutableVar ); + + } else { + + // This parameter is not reassigned, so treat it as a normal immutable parameter. + params.push( `${paramName}: ${paramType}` ); + + } + + } + + const paramsStr = params.length > 0 ? ' ' + params.join( ', ' ) + ' ' : ''; + const returnStr = ( returnType && returnType !== 'void' ) ? ` -> ${returnType}` : ''; + + // Emit the function body, which now includes our injected variable declarations. + const bodyStr = this.emitBody( body ); + + return `fn ${name}(${paramsStr})${returnStr} {\n\n${bodyStr}\n\n${this.tab}}`; + + } + + emitComment( statement, body ) { + + const index = body.indexOf( statement ); + const previous = body[ index - 1 ]; + const next = body[ index + 1 ]; + + let output = ''; + + if ( previous && isExpression( previous ) ) { + + output += '\n'; + + } + + output += this.tab + statement.comment.replace( /\n/g, '\n' + this.tab ) + '\n'; + + if ( next && isExpression( next ) ) { + + output += '\n'; + + } + + return output; + + } + + emitExtraLine( statement, body ) { + + const index = body.indexOf( statement ); + const previous = body[ index - 1 ]; + + if ( previous === undefined ) return ''; + + if ( statement.isReturn ) return '\n'; + + const lastExp = isExpression( previous ); + const currExp = isExpression( statement ); + + if ( lastExp !== currExp || ( ! lastExp && ! currExp ) ) return '\n'; + + return ''; + + } + + emit( ast ) { + + const header = '// Three.js Transpiler r' + REVISION + '\n\n'; + + let globals = ''; + let functions = ''; + let dependencies = ''; + + // 1. Pre-process to find all global declarations + for ( const statement of ast.body ) { + + if ( statement.isFunctionDeclaration ) { + + this.functions.set( statement.name, statement ); + + } else if ( statement.isUniform ) { + + this.uniforms.push( statement ); + + } else if ( statement.isVarying ) { + + this.varyings.push( statement ); + + } + + } + + // 2. Build resource bindings (uniforms, textures, samplers) + if ( this.uniforms.length > 0 ) { + + let bindingIndex = 0; + const uniformStructMembers = []; + const textureGlobals = []; + + for ( const uniform of this.uniforms ) { + + // Textures are declared as separate global variables, not in the UBO + if ( uniform.type.includes( 'texture' ) ) { + + textureGlobals.push( `@group(${this.groupIndex}) @binding(${bindingIndex ++}) var ${uniform.name}: ${this.getWgslType( uniform.type )};` ); + textureGlobals.push( `@group(${this.groupIndex}) @binding(${bindingIndex ++}) var ${uniform.name}_sampler: sampler;` ); + + } else { + + uniformStructMembers.push( `\t${uniform.name}: ${this.getWgslType( uniform.type )},` ); + + } + + } + + // Create a UBO struct if there are any non-texture uniforms + if ( uniformStructMembers.length > 0 ) { + + globals += 'struct Uniforms {\n'; + globals += uniformStructMembers.join( '\n' ); + globals += '\n};\n'; + globals += `@group(${this.groupIndex}) @binding(${bindingIndex ++}) var uniforms: Uniforms;\n\n`; + + } + + // Add the texture and sampler globals + globals += textureGlobals.join( '\n' ) + '\n\n'; + + } + + // 3. Build varying structs for stage I/O + // This is a simplification; a full implementation would need to know the shader stage. + if ( this.varyings.length > 0 ) { + + globals += 'struct Varyings {\n'; + let location = 0; + for ( const varying of this.varyings ) { + + globals += `\t@location(${location ++}) ${varying.name}: ${this.getWgslType( varying.type )},\n`; + + } + + globals += '};\n\n'; + + } + + // 4. Emit all functions and other global statements + for ( const statement of ast.body ) { + + functions += this.emitExtraLine( statement, ast.body ); + + if ( statement.isFunctionDeclaration ) { + + functions += this.emitFunction( statement ) + '\n'; + + } else if ( statement.isComment ) { + + functions += this.emitComment( statement, ast.body ); + + } else if ( ! statement.isUniform && ! statement.isVarying ) { + + // Handle other top-level statements like 'const' + functions += this.emitExpression( statement ) + ';\n'; + + } + + } + + // 4. Build dependencies + for ( const value of this.polyfills.values() ) { + + dependencies = `${ value }\n\n`; + + } + + return header + dependencies + globals + functions.trimEnd() + '\n'; + + } + +} + +export default WGSLEncoder; diff --git a/examples/jsm/tsl/display/AfterImageNode.js b/examples/jsm/tsl/display/AfterImageNode.js new file mode 100644 index 00000000000000..2d4c9e91ce5eb6 --- /dev/null +++ b/examples/jsm/tsl/display/AfterImageNode.js @@ -0,0 +1,242 @@ +import { RenderTarget, Vector2, QuadMesh, NodeMaterial, RendererUtils, TempNode, NodeUpdateType } from 'three/webgpu'; +import { nodeObject, Fn, float, uv, texture, passTexture, uniform, sign, max, convertToTexture } from 'three/tsl'; + +const _size = /*@__PURE__*/ new Vector2(); +const _quadMeshComp = /*@__PURE__*/ new QuadMesh(); + +let _rendererState; + +/** + * Post processing node for creating an after image effect. + * + * @augments TempNode + * @three_import import { afterImage } from 'three/addons/tsl/display/AfterImageNode.js'; + */ +class AfterImageNode extends TempNode { + + static get type() { + + return 'AfterImageNode'; + + } + + /** + * Constructs a new after image node. + * + * @param {TextureNode} textureNode - The texture node that represents the input of the effect. + * @param {number} [damp=0.96] - The damping intensity. A higher value means a stronger after image effect. + */ + constructor( textureNode, damp = 0.96 ) { + + super( 'vec4' ); + + /** + * The texture node that represents the input of the effect. + * + * @type {TextureNode} + */ + this.textureNode = textureNode; + + /** + * The texture represents the pervious frame. + * + * @type {TextureNode} + */ + this.textureNodeOld = texture( null ); + + /** + * How quickly the after-image fades. A higher value means the after-image + * persists longer, while a lower value means it fades faster. Should be in + * the range `[0, 1]`. + * + * @type {UniformNode} + */ + this.damp = uniform( damp ); + + /** + * The render target used for compositing the effect. + * + * @private + * @type {RenderTarget} + */ + this._compRT = new RenderTarget( 1, 1, { depthBuffer: false } ); + this._compRT.texture.name = 'AfterImageNode.comp'; + + /** + * The render target that represents the previous frame. + * + * @private + * @type {RenderTarget} + */ + this._oldRT = new RenderTarget( 1, 1, { depthBuffer: false } ); + this._oldRT.texture.name = 'AfterImageNode.old'; + + /** + * The result of the effect is represented as a separate texture node. + * + * @private + * @type {PassTextureNode} + */ + this._textureNode = passTexture( this, this._compRT.texture ); + + /** + * The `updateBeforeType` is set to `NodeUpdateType.FRAME` since the node renders + * its effect once per frame in `updateBefore()`. + * + * @type {string} + * @default 'frame' + */ + this.updateBeforeType = NodeUpdateType.FRAME; + + } + + /** + * Returns the result of the effect as a texture node. + * + * @return {PassTextureNode} A texture node that represents the result of the effect. + */ + getTextureNode() { + + return this._textureNode; + + } + + /** + * Sets the size of the effect. + * + * @param {number} width - The width of the effect. + * @param {number} height - The height of the effect. + */ + setSize( width, height ) { + + this._compRT.setSize( width, height ); + this._oldRT.setSize( width, height ); + + } + + /** + * This method is used to render the effect once per frame. + * + * @param {NodeFrame} frame - The current node frame. + */ + updateBefore( frame ) { + + const { renderer } = frame; + + _rendererState = RendererUtils.resetRendererState( renderer, _rendererState ); + + // + + const textureNode = this.textureNode; + const map = textureNode.value; + + const textureType = map.type; + + this._compRT.texture.type = textureType; + this._oldRT.texture.type = textureType; + + renderer.getDrawingBufferSize( _size ); + + this.setSize( _size.x, _size.y ); + + const currentTexture = textureNode.value; + + this.textureNodeOld.value = this._oldRT.texture; + + // comp + _quadMeshComp.material = this._materialComposed; + + renderer.setRenderTarget( this._compRT ); + _quadMeshComp.render( renderer ); + + // Swap the textures + + const temp = this._oldRT; + this._oldRT = this._compRT; + this._compRT = temp; + + // + + textureNode.value = currentTexture; + + RendererUtils.restoreRendererState( renderer, _rendererState ); + + } + + /** + * This method is used to setup the effect's TSL code. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {PassTextureNode} + */ + setup( builder ) { + + const textureNode = this.textureNode; + const textureNodeOld = this.textureNodeOld; + + // + + textureNodeOld.uvNode = textureNode.uvNode || uv(); + + const afterImg = Fn( () => { + + const texelOld = textureNodeOld.sample().toVar(); + const texelNew = textureNode.sample().toVar(); + + const threshold = float( 0.1 ).toConst(); + + // m acts as a mask. It's 1 if the previous pixel was "bright enough" (above the threshold) and 0 if it wasn't. + const m = max( sign( texelOld.sub( threshold ) ), 0.0 ); + + // This is where the after-image fades: + // + // - If m is 0, texelOld is multiplied by 0, effectively clearing the after-image for that pixel. + // - If m is 1, texelOld is multiplied by "damp". Since "damp" is between 0 and 1, this reduces the color value of + // texelOld, making it darker and causing it to fade. + texelOld.mulAssign( this.damp.mul( m ) ); + + return max( texelNew, texelOld ); + + } ); + + // + + const materialComposed = this._materialComposed || ( this._materialComposed = new NodeMaterial() ); + materialComposed.name = 'AfterImage'; + materialComposed.fragmentNode = afterImg(); + // + + const properties = builder.getNodeProperties( this ); + properties.textureNode = textureNode; + + // + + return this._textureNode; + + } + + /** + * Frees internal resources. This method should be called + * when the effect is no longer required. + */ + dispose() { + + this._compRT.dispose(); + this._oldRT.dispose(); + + } + +} + +/** + * TSL function for creating an after image node for post processing. + * + * @tsl + * @function + * @param {Node} node - The node that represents the input of the effect. + * @param {number} [damp=0.96] - The damping intensity. A higher value means a stronger after image effect. + * @returns {AfterImageNode} + */ +export const afterImage = ( node, damp ) => nodeObject( new AfterImageNode( convertToTexture( node ), damp ) ); + +export default AfterImageNode; diff --git a/examples/jsm/tsl/display/AnaglyphPassNode.js b/examples/jsm/tsl/display/AnaglyphPassNode.js new file mode 100644 index 00000000000000..d50adcbbc424c6 --- /dev/null +++ b/examples/jsm/tsl/display/AnaglyphPassNode.js @@ -0,0 +1,107 @@ +import { Matrix3, NodeMaterial } from 'three/webgpu'; +import { clamp, nodeObject, Fn, vec4, uv, uniform, max } from 'three/tsl'; +import StereoCompositePassNode from './StereoCompositePassNode.js'; + +/** + * A render pass node that creates an anaglyph effect. + * + * @augments StereoCompositePassNode + * @three_import import { anaglyphPass } from 'three/addons/tsl/display/AnaglyphPassNode.js'; + */ +class AnaglyphPassNode extends StereoCompositePassNode { + + static get type() { + + return 'AnaglyphPassNode'; + + } + + /** + * Constructs a new anaglyph pass node. + * + * @param {Scene} scene - The scene to render. + * @param {Camera} camera - The camera to render the scene with. + */ + constructor( scene, camera ) { + + super( scene, camera ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isAnaglyphPassNode = true; + + // Dubois matrices from https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.7.6968&rep=rep1&type=pdf#page=4 + + /** + * Color matrix node for the left eye. + * + * @type {UniformNode} + */ + this._colorMatrixLeft = uniform( new Matrix3().fromArray( [ + 0.456100, - 0.0400822, - 0.0152161, + 0.500484, - 0.0378246, - 0.0205971, + 0.176381, - 0.0157589, - 0.00546856 + ] ) ); + + /** + * Color matrix node for the right eye. + * + * @type {UniformNode} + */ + this._colorMatrixRight = uniform( new Matrix3().fromArray( [ + - 0.0434706, 0.378476, - 0.0721527, + - 0.0879388, 0.73364, - 0.112961, + - 0.00155529, - 0.0184503, 1.2264 + ] ) ); + + } + + /** + * This method is used to setup the effect's TSL code. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {PassTextureNode} + */ + setup( builder ) { + + const uvNode = uv(); + + const anaglyph = Fn( () => { + + const colorL = this._mapLeft.sample( uvNode ); + const colorR = this._mapRight.sample( uvNode ); + + const color = clamp( this._colorMatrixLeft.mul( colorL.rgb ).add( this._colorMatrixRight.mul( colorR.rgb ) ) ); + + return vec4( color.rgb, max( colorL.a, colorR.a ) ); + + } ); + + const material = this._material || ( this._material = new NodeMaterial() ); + material.fragmentNode = anaglyph().context( builder.getSharedContext() ); + material.name = 'Anaglyph'; + material.needsUpdate = true; + + return super.setup( builder ); + + } + +} + +export default AnaglyphPassNode; + +/** + * TSL function for creating an anaglyph pass node. + * + * @tsl + * @function + * @param {Scene} scene - The scene to render. + * @param {Camera} camera - The camera to render the scene with. + * @returns {AnaglyphPassNode} + */ +export const anaglyphPass = ( scene, camera ) => nodeObject( new AnaglyphPassNode( scene, camera ) ); diff --git a/examples/jsm/tsl/display/AnamorphicNode.js b/examples/jsm/tsl/display/AnamorphicNode.js new file mode 100644 index 00000000000000..f2d80c4d16793e --- /dev/null +++ b/examples/jsm/tsl/display/AnamorphicNode.js @@ -0,0 +1,258 @@ +import { RenderTarget, Vector2, TempNode, QuadMesh, NodeMaterial, RendererUtils } from 'three/webgpu'; +import { nodeObject, Fn, float, NodeUpdateType, uv, passTexture, uniform, convertToTexture, vec2, vec3, Loop, mix, luminance } from 'three/tsl'; + +const _quadMesh = /*@__PURE__*/ new QuadMesh(); + +let _rendererState; + +/** + * Post processing node for adding an anamorphic flare effect. + * + * @augments TempNode + * @three_import import { anamorphic } from 'three/addons/tsl/display/AnamorphicNode.js'; + */ +class AnamorphicNode extends TempNode { + + static get type() { + + return 'AnamorphicNode'; + + } + + /** + * Constructs a new anamorphic node. + * + * @param {TextureNode} textureNode - The texture node that represents the input of the effect. + * @param {Node} thresholdNode - The threshold is one option to control the intensity and size of the effect. + * @param {Node} scaleNode - Defines the vertical scale of the flares. + * @param {number} samples - More samples result in larger flares and a more expensive runtime behavior. + */ + constructor( textureNode, thresholdNode, scaleNode, samples ) { + + super( 'vec4' ); + + /** + * The texture node that represents the input of the effect. + * + * @type {TextureNode} + */ + this.textureNode = textureNode; + + /** + * The threshold is one option to control the intensity and size of the effect. + * + * @type {Node} + */ + this.thresholdNode = thresholdNode; + + /** + * Defines the vertical scale of the flares. + * + * @type {Node} + */ + this.scaleNode = scaleNode; + + /** + * The color of the flares. + * + * @type {Node} + */ + this.colorNode = vec3( 0.1, 0.0, 1.0 ); + + /** + * More samples result in larger flares and a more expensive runtime behavior. + * + * @type {Node} + */ + this.samples = samples; + + /** + * The resolution scale. + * + * @type {Vector2} + */ + this.resolution = new Vector2( 1, 1 ); + + /** + * The internal render target of the effect. + * + * @private + * @type {RenderTarget} + */ + this._renderTarget = new RenderTarget( 1, 1, { depthBuffer: false } ); + this._renderTarget.texture.name = 'anamorphic'; + + /** + * A uniform node holding the inverse resolution value. + * + * @private + * @type {UniformNode} + */ + this._invSize = uniform( new Vector2() ); + + /** + * The result of the effect is represented as a separate texture node. + * + * @private + * @type {PassTextureNode} + */ + this._textureNode = passTexture( this, this._renderTarget.texture ); + + /** + * The `updateBeforeType` is set to `NodeUpdateType.FRAME` since the node renders + * its effect once per frame in `updateBefore()`. + * + * @type {string} + * @default 'frame' + */ + this.updateBeforeType = NodeUpdateType.FRAME; + + } + + /** + * Returns the result of the effect as a texture node. + * + * @return {PassTextureNode} A texture node that represents the result of the effect. + */ + getTextureNode() { + + return this._textureNode; + + } + + /** + * Sets the size of the effect. + * + * @param {number} width - The width of the effect. + * @param {number} height - The height of the effect. + */ + setSize( width, height ) { + + this._invSize.value.set( 1 / width, 1 / height ); + + width = Math.max( Math.round( width * this.resolution.x ), 1 ); + height = Math.max( Math.round( height * this.resolution.y ), 1 ); + + this._renderTarget.setSize( width, height ); + + } + + /** + * This method is used to render the effect once per frame. + * + * @param {NodeFrame} frame - The current node frame. + */ + updateBefore( frame ) { + + const { renderer } = frame; + + _rendererState = RendererUtils.resetRendererState( renderer, _rendererState ); + + // + + const textureNode = this.textureNode; + const map = textureNode.value; + + this._renderTarget.texture.type = map.type; + + const currentTexture = textureNode.value; + + _quadMesh.material = this._material; + + this.setSize( map.image.width, map.image.height ); + + // render + + renderer.setRenderTarget( this._renderTarget ); + + _quadMesh.render( renderer ); + + // restore + + textureNode.value = currentTexture; + + RendererUtils.restoreRendererState( renderer, _rendererState ); + + } + + /** + * This method is used to setup the effect's TSL code. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {PassTextureNode} + */ + setup( builder ) { + + const textureNode = this.textureNode; + const uvNode = textureNode.uvNode || uv(); + + const sampleTexture = ( uv ) => textureNode.sample( uv ); + + const threshold = ( color, threshold ) => mix( vec3( 0.0 ), color, luminance( color ).sub( threshold ).max( 0 ) ); + + const anamorph = Fn( () => { + + const samples = this.samples; + const halfSamples = Math.floor( samples / 2 ); + + const total = vec3( 0 ).toVar(); + + Loop( { start: - halfSamples, end: halfSamples }, ( { i } ) => { + + const softness = float( i ).abs().div( halfSamples ).oneMinus(); + + const uv = vec2( uvNode.x.add( this._invSize.x.mul( i ).mul( this.scaleNode ) ), uvNode.y ); + const color = sampleTexture( uv ); + const pass = threshold( color, this.thresholdNode ).mul( softness ); + + total.addAssign( pass ); + + } ); + + return total.mul( this.colorNode ); + + } ); + + // + + const material = this._material || ( this._material = new NodeMaterial() ); + material.name = 'Anamorphic'; + material.fragmentNode = anamorph(); + + // + + const properties = builder.getNodeProperties( this ); + properties.textureNode = textureNode; + + // + + return this._textureNode; + + } + + /** + * Frees internal resources. This method should be called + * when the effect is no longer required. + */ + dispose() { + + this._renderTarget.dispose(); + + } + +} + +/** + * TSL function for creating an anamorphic flare effect. + * + * @tsl + * @function + * @param {TextureNode} node - The node that represents the input of the effect. + * @param {Node | number} [threshold=0.9] - The threshold is one option to control the intensity and size of the effect. + * @param {Node | number} [scale=3] - Defines the vertical scale of the flares. + * @param {number} [samples=32] - More samples result in larger flares and a more expensive runtime behavior. + * @returns {AnamorphicNode} + */ +export const anamorphic = ( node, threshold = .9, scale = 3, samples = 32 ) => nodeObject( new AnamorphicNode( convertToTexture( node ), nodeObject( threshold ), nodeObject( scale ), samples ) ); + +export default AnamorphicNode; diff --git a/examples/jsm/tsl/display/BleachBypass.js b/examples/jsm/tsl/display/BleachBypass.js new file mode 100644 index 00000000000000..0a3c5b2defa153 --- /dev/null +++ b/examples/jsm/tsl/display/BleachBypass.js @@ -0,0 +1,33 @@ +import { float, Fn, vec3, vec4, min, max, mix, luminance } from 'three/tsl'; + +/** + * Applies a bleach bypass effect to the given color node. + * + * @tsl + * @function + * @param {Node} color - The color node to apply the sepia for. + * @param {Node} [opacity=1] - Influences how strong the effect is blended with the original color. + * @return {Node} The updated color node. + */ +export const bleach = /*@__PURE__*/ Fn( ( [ color, opacity = 1 ] ) => { + + const base = color; + const lum = luminance( base.rgb ); + const blend = vec3( lum ); + + const L = min( 1.0, max( 0.0, float( 10.0 ).mul( lum.sub( 0.45 ) ) ) ); + + const result1 = blend.mul( base.rgb ).mul( 2.0 ); + const result2 = float( 2.0 ).mul( blend.oneMinus() ).mul( base.rgb.oneMinus() ).oneMinus(); + + const newColor = mix( result1, result2, L ); + + const A2 = base.a.mul( opacity ); + + const mixRGB = A2.mul( newColor.rgb ); + + mixRGB.addAssign( base.rgb.mul( A2.oneMinus() ) ); + + return vec4( mixRGB, base.a ); + +} ); diff --git a/examples/jsm/tsl/display/BloomNode.js b/examples/jsm/tsl/display/BloomNode.js new file mode 100644 index 00000000000000..924c50cc98ec30 --- /dev/null +++ b/examples/jsm/tsl/display/BloomNode.js @@ -0,0 +1,519 @@ +import { HalfFloatType, RenderTarget, Vector2, Vector3, TempNode, QuadMesh, NodeMaterial, RendererUtils, NodeUpdateType } from 'three/webgpu'; +import { nodeObject, Fn, float, uv, passTexture, uniform, Loop, texture, luminance, smoothstep, mix, vec4, uniformArray, add, int } from 'three/tsl'; + +const _quadMesh = /*@__PURE__*/ new QuadMesh(); +const _size = /*@__PURE__*/ new Vector2(); + +const _BlurDirectionX = /*@__PURE__*/ new Vector2( 1.0, 0.0 ); +const _BlurDirectionY = /*@__PURE__*/ new Vector2( 0.0, 1.0 ); + +let _rendererState; + +/** + * Post processing node for creating a bloom effect. + * ```js + * const postProcessing = new THREE.PostProcessing( renderer ); + * + * const scenePass = pass( scene, camera ); + * const scenePassColor = scenePass.getTextureNode( 'output' ); + * + * const bloomPass = bloom( scenePassColor ); + * + * postProcessing.outputNode = scenePassColor.add( bloomPass ); + * ``` + * By default, the node affects the entire image. For a selective bloom, + * use the `emissive` material property to control which objects should + * contribute to bloom or not. This can be achieved via MRT. + * ```js + * const postProcessing = new THREE.PostProcessing( renderer ); + * + * const scenePass = pass( scene, camera ); + * scenePass.setMRT( mrt( { + * output, + * emissive + * } ) ); + * + * const scenePassColor = scenePass.getTextureNode( 'output' ); + * const emissivePass = scenePass.getTextureNode( 'emissive' ); + * + * const bloomPass = bloom( emissivePass ); + * postProcessing.outputNode = scenePassColor.add( bloomPass ); + * ``` + * @augments TempNode + * @three_import import { bloom } from 'three/addons/tsl/display/BloomNode.js'; + */ +class BloomNode extends TempNode { + + static get type() { + + return 'BloomNode'; + + } + + /** + * Constructs a new bloom node. + * + * @param {Node} inputNode - The node that represents the input of the effect. + * @param {number} [strength=1] - The strength of the bloom. + * @param {number} [radius=0] - The radius of the bloom. + * @param {number} [threshold=0] - The luminance threshold limits which bright areas contribute to the bloom effect. + */ + constructor( inputNode, strength = 1, radius = 0, threshold = 0 ) { + + super( 'vec4' ); + + /** + * The node that represents the input of the effect. + * + * @type {Node} + */ + this.inputNode = inputNode; + + /** + * The strength of the bloom. + * + * @type {UniformNode} + */ + this.strength = uniform( strength ); + + /** + * The radius of the bloom. + * + * @type {UniformNode} + */ + this.radius = uniform( radius ); + + /** + * The luminance threshold limits which bright areas contribute to the bloom effect. + * + * @type {UniformNode} + */ + this.threshold = uniform( threshold ); + + /** + * Can be used to tweak the extracted luminance from the scene. + * + * @type {UniformNode} + */ + this.smoothWidth = uniform( 0.01 ); + + /** + * An array that holds the render targets for the horizontal blur passes. + * + * @private + * @type {Array} + */ + this._renderTargetsHorizontal = []; + + /** + * An array that holds the render targets for the vertical blur passes. + * + * @private + * @type {Array} + */ + this._renderTargetsVertical = []; + + /** + * The number if blur mips. + * + * @private + * @type {number} + */ + this._nMips = 5; + + /** + * The render target for the luminance pass. + * + * @private + * @type {RenderTarget} + */ + this._renderTargetBright = new RenderTarget( 1, 1, { depthBuffer: false, type: HalfFloatType } ); + this._renderTargetBright.texture.name = 'UnrealBloomPass.bright'; + this._renderTargetBright.texture.generateMipmaps = false; + + // + + for ( let i = 0; i < this._nMips; i ++ ) { + + const renderTargetHorizontal = new RenderTarget( 1, 1, { depthBuffer: false, type: HalfFloatType } ); + + renderTargetHorizontal.texture.name = 'UnrealBloomPass.h' + i; + renderTargetHorizontal.texture.generateMipmaps = false; + + this._renderTargetsHorizontal.push( renderTargetHorizontal ); + + const renderTargetVertical = new RenderTarget( 1, 1, { depthBuffer: false, type: HalfFloatType } ); + + renderTargetVertical.texture.name = 'UnrealBloomPass.v' + i; + renderTargetVertical.texture.generateMipmaps = false; + + this._renderTargetsVertical.push( renderTargetVertical ); + + } + + /** + * The material for the composite pass. + * + * @private + * @type {?NodeMaterial} + */ + this._compositeMaterial = null; + + /** + * The material for the luminance pass. + * + * @private + * @type {?NodeMaterial} + */ + this._highPassFilterMaterial = null; + + /** + * The materials for the blur pass. + * + * @private + * @type {Array} + */ + this._separableBlurMaterials = []; + + /** + * The result of the luminance pass as a texture node for further processing. + * + * @private + * @type {TextureNode} + */ + this._textureNodeBright = texture( this._renderTargetBright.texture ); + + /** + * The result of the first blur pass as a texture node for further processing. + * + * @private + * @type {TextureNode} + */ + this._textureNodeBlur0 = texture( this._renderTargetsVertical[ 0 ].texture ); + + /** + * The result of the second blur pass as a texture node for further processing. + * + * @private + * @type {TextureNode} + */ + this._textureNodeBlur1 = texture( this._renderTargetsVertical[ 1 ].texture ); + + /** + * The result of the third blur pass as a texture node for further processing. + * + * @private + * @type {TextureNode} + */ + this._textureNodeBlur2 = texture( this._renderTargetsVertical[ 2 ].texture ); + + /** + * The result of the fourth blur pass as a texture node for further processing. + * + * @private + * @type {TextureNode} + */ + this._textureNodeBlur3 = texture( this._renderTargetsVertical[ 3 ].texture ); + + /** + * The result of the fifth blur pass as a texture node for further processing. + * + * @private + * @type {TextureNode} + */ + this._textureNodeBlur4 = texture( this._renderTargetsVertical[ 4 ].texture ); + + /** + * The result of the effect is represented as a separate texture node. + * + * @private + * @type {PassTextureNode} + */ + this._textureOutput = passTexture( this, this._renderTargetsHorizontal[ 0 ].texture ); + + /** + * The `updateBeforeType` is set to `NodeUpdateType.FRAME` since the node renders + * its effect once per frame in `updateBefore()`. + * + * @type {string} + * @default 'frame' + */ + this.updateBeforeType = NodeUpdateType.FRAME; + + } + + /** + * Returns the result of the effect as a texture node. + * + * @return {PassTextureNode} A texture node that represents the result of the effect. + */ + getTextureNode() { + + return this._textureOutput; + + } + + /** + * Sets the size of the effect. + * + * @param {number} width - The width of the effect. + * @param {number} height - The height of the effect. + */ + setSize( width, height ) { + + let resx = Math.round( width / 2 ); + let resy = Math.round( height / 2 ); + + this._renderTargetBright.setSize( resx, resy ); + + for ( let i = 0; i < this._nMips; i ++ ) { + + this._renderTargetsHorizontal[ i ].setSize( resx, resy ); + this._renderTargetsVertical[ i ].setSize( resx, resy ); + + this._separableBlurMaterials[ i ].invSize.value.set( 1 / resx, 1 / resy ); + + resx = Math.round( resx / 2 ); + resy = Math.round( resy / 2 ); + + } + + } + + /** + * This method is used to render the effect once per frame. + * + * @param {NodeFrame} frame - The current node frame. + */ + updateBefore( frame ) { + + const { renderer } = frame; + + _rendererState = RendererUtils.resetRendererState( renderer, _rendererState ); + + // + + const size = renderer.getDrawingBufferSize( _size ); + this.setSize( size.width, size.height ); + + // 1. Extract bright areas + + renderer.setRenderTarget( this._renderTargetBright ); + _quadMesh.material = this._highPassFilterMaterial; + _quadMesh.render( renderer ); + + // 2. Blur all the mips progressively + + let inputRenderTarget = this._renderTargetBright; + + for ( let i = 0; i < this._nMips; i ++ ) { + + _quadMesh.material = this._separableBlurMaterials[ i ]; + + this._separableBlurMaterials[ i ].colorTexture.value = inputRenderTarget.texture; + this._separableBlurMaterials[ i ].direction.value = _BlurDirectionX; + renderer.setRenderTarget( this._renderTargetsHorizontal[ i ] ); + _quadMesh.render( renderer ); + + this._separableBlurMaterials[ i ].colorTexture.value = this._renderTargetsHorizontal[ i ].texture; + this._separableBlurMaterials[ i ].direction.value = _BlurDirectionY; + renderer.setRenderTarget( this._renderTargetsVertical[ i ] ); + _quadMesh.render( renderer ); + + inputRenderTarget = this._renderTargetsVertical[ i ]; + + } + + // 3. Composite all the mips + + renderer.setRenderTarget( this._renderTargetsHorizontal[ 0 ] ); + _quadMesh.material = this._compositeMaterial; + _quadMesh.render( renderer ); + + // restore + + RendererUtils.restoreRendererState( renderer, _rendererState ); + + } + + /** + * This method is used to setup the effect's TSL code. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {PassTextureNode} + */ + setup( builder ) { + + // luminosity high pass material + + const luminosityHighPass = Fn( () => { + + const texel = this.inputNode; + const v = luminance( texel.rgb ); + + const alpha = smoothstep( this.threshold, this.threshold.add( this.smoothWidth ), v ); + + return mix( vec4( 0 ), texel, alpha ); + + } ); + + this._highPassFilterMaterial = this._highPassFilterMaterial || new NodeMaterial(); + this._highPassFilterMaterial.fragmentNode = luminosityHighPass().context( builder.getSharedContext() ); + this._highPassFilterMaterial.name = 'Bloom_highPass'; + this._highPassFilterMaterial.needsUpdate = true; + + // gaussian blur materials + + const kernelSizeArray = [ 3, 5, 7, 9, 11 ]; + + for ( let i = 0; i < this._nMips; i ++ ) { + + this._separableBlurMaterials.push( this._getSeparableBlurMaterial( builder, kernelSizeArray[ i ] ) ); + + } + + // composite material + + const bloomFactors = uniformArray( [ 1.0, 0.8, 0.6, 0.4, 0.2 ] ); + const bloomTintColors = uniformArray( [ new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ), new Vector3( 1, 1, 1 ) ] ); + + const lerpBloomFactor = Fn( ( [ factor, radius ] ) => { + + const mirrorFactor = float( 1.2 ).sub( factor ); + return mix( factor, mirrorFactor, radius ); + + } ).setLayout( { + name: 'lerpBloomFactor', + type: 'float', + inputs: [ + { name: 'factor', type: 'float' }, + { name: 'radius', type: 'float' }, + ] + } ); + + + const compositePass = Fn( () => { + + const color0 = lerpBloomFactor( bloomFactors.element( 0 ), this.radius ).mul( vec4( bloomTintColors.element( 0 ), 1.0 ) ).mul( this._textureNodeBlur0 ); + const color1 = lerpBloomFactor( bloomFactors.element( 1 ), this.radius ).mul( vec4( bloomTintColors.element( 1 ), 1.0 ) ).mul( this._textureNodeBlur1 ); + const color2 = lerpBloomFactor( bloomFactors.element( 2 ), this.radius ).mul( vec4( bloomTintColors.element( 2 ), 1.0 ) ).mul( this._textureNodeBlur2 ); + const color3 = lerpBloomFactor( bloomFactors.element( 3 ), this.radius ).mul( vec4( bloomTintColors.element( 3 ), 1.0 ) ).mul( this._textureNodeBlur3 ); + const color4 = lerpBloomFactor( bloomFactors.element( 4 ), this.radius ).mul( vec4( bloomTintColors.element( 4 ), 1.0 ) ).mul( this._textureNodeBlur4 ); + + const sum = color0.add( color1 ).add( color2 ).add( color3 ).add( color4 ); + + return sum.mul( this.strength ); + + } ); + + this._compositeMaterial = this._compositeMaterial || new NodeMaterial(); + this._compositeMaterial.fragmentNode = compositePass().context( builder.getSharedContext() ); + this._compositeMaterial.name = 'Bloom_comp'; + this._compositeMaterial.needsUpdate = true; + + // + + return this._textureOutput; + + } + + /** + * Frees internal resources. This method should be called + * when the effect is no longer required. + */ + dispose() { + + for ( let i = 0; i < this._renderTargetsHorizontal.length; i ++ ) { + + this._renderTargetsHorizontal[ i ].dispose(); + + } + + for ( let i = 0; i < this._renderTargetsVertical.length; i ++ ) { + + this._renderTargetsVertical[ i ].dispose(); + + } + + this._renderTargetBright.dispose(); + + } + + /** + * Create a separable blur material for the given kernel radius. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {number} kernelRadius - The kernel radius. + * @return {NodeMaterial} + */ + _getSeparableBlurMaterial( builder, kernelRadius ) { + + const coefficients = []; + + for ( let i = 0; i < kernelRadius; i ++ ) { + + coefficients.push( 0.39894 * Math.exp( - 0.5 * i * i / ( kernelRadius * kernelRadius ) ) / kernelRadius ); + + } + + // + + const colorTexture = texture( null ); + const gaussianCoefficients = uniformArray( coefficients ); + const invSize = uniform( new Vector2() ); + const direction = uniform( new Vector2( 0.5, 0.5 ) ); + + const uvNode = uv(); + const sampleTexel = ( uv ) => colorTexture.sample( uv ); + + const separableBlurPass = Fn( () => { + + const weightSum = gaussianCoefficients.element( 0 ).toVar(); + const diffuseSum = sampleTexel( uvNode ).rgb.mul( weightSum ).toVar(); + + Loop( { start: int( 1 ), end: int( kernelRadius ), type: 'int', condition: '<' }, ( { i } ) => { + + const x = float( i ); + const w = gaussianCoefficients.element( i ); + const uvOffset = direction.mul( invSize ).mul( x ); + const sample1 = sampleTexel( uvNode.add( uvOffset ) ).rgb; + const sample2 = sampleTexel( uvNode.sub( uvOffset ) ).rgb; + diffuseSum.addAssign( add( sample1, sample2 ).mul( w ) ); + weightSum.addAssign( float( 2.0 ).mul( w ) ); + + } ); + + return vec4( diffuseSum.div( weightSum ), 1.0 ); + + } ); + + const separableBlurMaterial = new NodeMaterial(); + separableBlurMaterial.fragmentNode = separableBlurPass().context( builder.getSharedContext() ); + separableBlurMaterial.name = 'Bloom_separable'; + separableBlurMaterial.needsUpdate = true; + + // uniforms + separableBlurMaterial.colorTexture = colorTexture; + separableBlurMaterial.direction = direction; + separableBlurMaterial.invSize = invSize; + + return separableBlurMaterial; + + } + +} + +/** + * TSL function for creating a bloom effect. + * + * @tsl + * @function + * @param {Node} node - The node that represents the input of the effect. + * @param {number} [strength=1] - The strength of the bloom. + * @param {number} [radius=0] - The radius of the bloom. + * @param {number} [threshold=0] - The luminance threshold limits which bright areas contribute to the bloom effect. + * @returns {BloomNode} + */ +export const bloom = ( node, strength, radius, threshold ) => nodeObject( new BloomNode( nodeObject( node ), strength, radius, threshold ) ); + +export default BloomNode; diff --git a/examples/jsm/tsl/display/ChromaticAberrationNode.js b/examples/jsm/tsl/display/ChromaticAberrationNode.js new file mode 100644 index 00000000000000..625f58caeadc52 --- /dev/null +++ b/examples/jsm/tsl/display/ChromaticAberrationNode.js @@ -0,0 +1,206 @@ +import { Vector2, TempNode } from 'three/webgpu'; +import { + nodeObject, + Fn, + uniform, + convertToTexture, + float, + vec4, + uv, + NodeUpdateType, +} from 'three/tsl'; + +/** + * Post processing node for applying chromatic aberration effect. + * This effect simulates the color fringing that occurs in real camera lenses + * by separating and offsetting the red, green, and blue channels. + * + * @augments TempNode + * @three_import import { chromaticAberration } from 'three/addons/tsl/display/ChromaticAberrationNode.js'; + */ +class ChromaticAberrationNode extends TempNode { + + static get type() { + + return 'ChromaticAberrationNode'; + + } + + /** + * Constructs a new chromatic aberration node. + * + * @param {TextureNode} textureNode - The texture node that represents the input of the effect. + * @param {Node} strengthNode - The strength of the chromatic aberration effect as a node. + * @param {Node} centerNode - The center point of the effect as a node. + * @param {Node} scaleNode - The scale factor for stepped scaling from center as a node. + */ + constructor( textureNode, strengthNode, centerNode, scaleNode ) { + + super( 'vec4' ); + + /** + * The texture node that represents the input of the effect. + * + * @type {texture} + */ + this.textureNode = textureNode; + + /** + * The `updateBeforeType` is set to `NodeUpdateType.FRAME` since the node updates + * its internal uniforms once per frame in `updateBefore()`. + * + * @type {string} + * @default 'frame' + */ + this.updateBeforeType = NodeUpdateType.FRAME; + + /** + * A node holding the strength of the effect. + * + * @type {Node} + */ + this.strengthNode = strengthNode; + + /** + * A node holding the center point of the effect. + * + * @type {Node} + */ + this.centerNode = centerNode; + + /** + * A node holding the scale factor for stepped scaling. + * + * @type {Node} + */ + this.scaleNode = scaleNode; + + /** + * A uniform node holding the inverse resolution value. + * + * @private + * @type {UniformNode} + */ + this._invSize = uniform( new Vector2() ); + + } + + /** + * This method is used to update the effect's uniforms once per frame. + * + * @param {NodeFrame} frame - The current node frame. + */ + updateBefore( /* frame */ ) { + + const map = this.textureNode.value; + this._invSize.value.set( 1 / map.image.width, 1 / map.image.height ); + + } + + /** + * This method is used to setup the effect's TSL code. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {ShaderCallNodeInternal} + */ + setup( /* builder */ ) { + + const textureNode = this.textureNode; + const uvNode = textureNode.uvNode || uv(); + + const ApplyChromaticAberration = Fn( ( [ uv, strength, center, scale ] ) => { + + // Calculate distance from center + const offset = uv.sub( center ); + const distance = offset.length(); + + // Create stepped scaling zones based on distance + // Each channel gets different scaling steps + const redScale = float( 1.0 ).add( scale.mul( 0.02 ).mul( strength ) ); // Red channel scaled outward + const greenScale = float( 1.0 ); // Green stays at original scale + const blueScale = float( 1.0 ).sub( scale.mul( 0.02 ).mul( strength ) ); // Blue channel scaled inward + + // Create radial distortion based on distance from center + const aberrationStrength = strength.mul( distance ); + + // Calculate scaled UV coordinates for each channel + const redUV = center.add( offset.mul( redScale ) ); + const greenUV = center.add( offset.mul( greenScale ) ); + const blueUV = center.add( offset.mul( blueScale ) ); + + // Apply additional chromatic offset based on aberration strength + const rOffset = offset.mul( aberrationStrength ).mul( float( 0.01 ) ); + const gOffset = offset.mul( aberrationStrength ).mul( float( 0.0 ) ); + const bOffset = offset.mul( aberrationStrength ).mul( float( - 0.01 ) ); + + // Final UV coordinates combining scale and chromatic aberration + const finalRedUV = redUV.add( rOffset ); + const finalGreenUV = greenUV.add( gOffset ); + const finalBlueUV = blueUV.add( bOffset ); + + // Sample texture for each channel + const r = textureNode.sample( finalRedUV ).r; + const g = textureNode.sample( finalGreenUV ).g; + const b = textureNode.sample( finalBlueUV ).b; + + // Get original alpha + const a = textureNode.sample( uv ).a; + + return vec4( r, g, b, a ); + + } ).setLayout( { + name: 'ChromaticAberrationShader', + type: 'vec4', + inputs: [ + { name: 'uv', type: 'vec2' }, + { name: 'strength', type: 'float' }, + { name: 'center', type: 'vec2' }, + { name: 'scale', type: 'float' }, + { name: 'invSize', type: 'vec2' } + ] + } ); + + const chromaticAberrationFn = Fn( () => { + + return ApplyChromaticAberration( + uvNode, + this.strengthNode, + this.centerNode, + this.scaleNode, + this._invSize + ); + + } ); + + const outputNode = chromaticAberrationFn(); + + return outputNode; + + } + +} + +export default ChromaticAberrationNode; + +/** + * TSL function for creating a chromatic aberration node for post processing. + * + * @tsl + * @function + * @param {Node} node - The node that represents the input of the effect. + * @param {Node|number} [strength=1.0] - The strength of the chromatic aberration effect as a node or value. + * @param {Node|Vector2} [center=null] - The center point of the effect as a node or value. If null, uses screen center (0.5, 0.5). + * @param {Node|number} [scale=1.1] - The scale factor for stepped scaling from center as a node or value. + * @returns {ChromaticAberrationNode} + */ +export const chromaticAberration = ( node, strength = 1.0, center = null, scale = 1.1 ) => { + + return nodeObject( + new ChromaticAberrationNode( + convertToTexture( node ), + nodeObject( strength ), + nodeObject( center ), + nodeObject( scale ) + ) + ); +}; diff --git a/examples/jsm/tsl/display/DenoiseNode.js b/examples/jsm/tsl/display/DenoiseNode.js new file mode 100644 index 00000000000000..1b4958960a7cfc --- /dev/null +++ b/examples/jsm/tsl/display/DenoiseNode.js @@ -0,0 +1,332 @@ +import { DataTexture, RepeatWrapping, Vector2, Vector3, TempNode } from 'three/webgpu'; +import { texture, getNormalFromDepth, getViewPosition, convertToTexture, nodeObject, Fn, float, NodeUpdateType, uv, uniform, Loop, luminance, vec2, vec3, vec4, uniformArray, int, dot, max, pow, abs, If, textureSize, sin, cos, mat2, PI, property } from 'three/tsl'; +import { SimplexNoise } from '../../math/SimplexNoise.js'; + +/** + * Post processing node for denoising data like raw screen-space ambient occlusion output. + * Denoise can noticeably improve the quality of ambient occlusion but also add quite some + * overhead to the post processing setup. It's best to make its usage optional (e.g. via + * graphic settings). + * + * Reference: {@link https://openaccess.thecvf.com/content/WACV2021/papers/Khademi_Self-Supervised_Poisson-Gaussian_Denoising_WACV_2021_paper.pdf}. + * + * @augments TempNode + * @three_import import { denoise } from 'three/addons/tsl/display/DenoiseNode.js'; + */ +class DenoiseNode extends TempNode { + + static get type() { + + return 'DenoiseNode'; + + } + + /** + * Constructs a new denoise node. + * + * @param {TextureNode} textureNode - The texture node that represents the input of the effect (e.g. AO). + * @param {Node} depthNode - A node that represents the scene's depth. + * @param {?Node} normalNode - A node that represents the scene's normals. + * @param {Camera} camera - The camera the scene is rendered with. + */ + constructor( textureNode, depthNode, normalNode, camera ) { + + super( 'vec4' ); + + /** + * The texture node that represents the input of the effect (e.g. AO). + * + * @type {TextureNode} + */ + this.textureNode = textureNode; + + /** + * A node that represents the scene's depth. + * + * @type {Node} + */ + this.depthNode = depthNode; + + /** + * A node that represents the scene's normals. If no normals are passed to the + * constructor (because MRT is not available), normals can be automatically + * reconstructed from depth values in the shader. + * + * @type {?Node} + */ + this.normalNode = normalNode; + + /** + * The node represents the internal noise texture. + * + * @type {TextureNode} + */ + this.noiseNode = texture( generateDefaultNoise() ); + + /** + * The luma Phi value. + * + * @type {UniformNode} + */ + this.lumaPhi = uniform( 5 ); + + /** + * The depth Phi value. + * + * @type {UniformNode} + */ + this.depthPhi = uniform( 5 ); + + /** + * The normal Phi value. + * + * @type {UniformNode} + */ + this.normalPhi = uniform( 5 ); + + /** + * The radius. + * + * @type {UniformNode} + */ + this.radius = uniform( 5 ); + + /** + * The index. + * + * @type {UniformNode} + */ + this.index = uniform( 0 ); + + /** + * The `updateBeforeType` is set to `NodeUpdateType.FRAME` since the node updates + * its internal uniforms once per frame in `updateBefore()`. + * + * @type {string} + * @default 'frame' + */ + this.updateBeforeType = NodeUpdateType.FRAME; + + /** + * The resolution of the effect. + * + * @private + * @type {UniformNode} + */ + this._resolution = uniform( new Vector2() ); + + /** + * An array of sample vectors. + * + * @private + * @type {UniformArrayNode} + */ + this._sampleVectors = uniformArray( generateDenoiseSamples( 16, 2, 1 ) ); + + /** + * Represents the inverse projection matrix of the scene's camera. + * + * @private + * @type {UniformNode} + */ + this._cameraProjectionMatrixInverse = uniform( camera.projectionMatrixInverse ); + + } + + /** + * This method is used to update internal uniforms once per frame. + * + * @param {NodeFrame} frame - The current node frame. + */ + updateBefore() { + + const map = this.textureNode.value; + + this._resolution.value.set( map.image.width, map.image.height ); + + } + + /** + * This method is used to setup the effect's TSL code. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {ShaderCallNodeInternal} + */ + setup( /* builder */ ) { + + const uvNode = uv(); + + const sampleTexture = ( uv ) => this.textureNode.sample( uv ); + const sampleDepth = ( uv ) => this.depthNode.sample( uv ).x; + const sampleNormal = ( uv ) => ( this.normalNode !== null ) ? this.normalNode.sample( uv ).rgb.normalize() : getNormalFromDepth( uv, this.depthNode.value, this._cameraProjectionMatrixInverse ); + const sampleNoise = ( uv ) => this.noiseNode.sample( uv ); + + const denoiseSample = Fn( ( [ center, viewNormal, viewPosition, sampleUv ] ) => { + + const texel = sampleTexture( sampleUv ).toVar(); + const depth = sampleDepth( sampleUv ).toVar(); + const normal = sampleNormal( sampleUv ).toVar(); + const neighborColor = texel.rgb; + const viewPos = getViewPosition( sampleUv, depth, this._cameraProjectionMatrixInverse ).toVar(); + + const normalDiff = dot( viewNormal, normal ).toVar(); + const normalSimilarity = pow( max( normalDiff, 0 ), this.normalPhi ).toVar(); + const lumaDiff = abs( luminance( neighborColor ).sub( luminance( center ) ) ).toVar(); + const lumaSimilarity = max( float( 1.0 ).sub( lumaDiff.div( this.lumaPhi ) ), 0 ).toVar(); + const depthDiff = abs( dot( viewPosition.sub( viewPos ), viewNormal ) ).toVar(); + const depthSimilarity = max( float( 1.0 ).sub( depthDiff.div( this.depthPhi ) ), 0 ); + const w = lumaSimilarity.mul( depthSimilarity ).mul( normalSimilarity ); + + return vec4( neighborColor.mul( w ), w ); + + } ); + + const denoise = Fn( ( [ uvNode ] ) => { + + const depth = sampleDepth( uvNode ).toVar(); + const viewNormal = sampleNormal( uvNode ).toVar(); + + const texel = sampleTexture( uvNode ).toVar(); + const result = property( 'vec4' ); + + If( depth.greaterThanEqual( 1.0 ).or( dot( viewNormal, viewNormal ).equal( 0.0 ) ), () => { + + result.assign( texel ); + + } ).Else( () => { + + const center = vec3( texel.rgb ); + + const viewPosition = getViewPosition( uvNode, depth, this._cameraProjectionMatrixInverse ).toConst(); + + const noiseResolution = textureSize( this.noiseNode, 0 ); + let noiseUv = vec2( uvNode.x, uvNode.y.oneMinus() ); + noiseUv = noiseUv.mul( this._resolution.div( noiseResolution ) ); + const noiseTexel = sampleNoise( noiseUv ).toVar(); + + const x = sin( noiseTexel.element( this.index.mod( 4 ).mul( 2 ).mul( PI ) ) ); + const y = cos( noiseTexel.element( this.index.mod( 4 ).mul( 2 ).mul( PI ) ) ); + + const noiseVec = vec2( x, y ); + const rotationMatrix = mat2( noiseVec.x, noiseVec.y.negate(), noiseVec.x, noiseVec.y ); + + const totalWeight = float( 1.0 ).toVar(); + const denoised = vec3( texel.rgb ).toVar(); + + Loop( { start: int( 0 ), end: int( 16 ), type: 'int', condition: '<' }, ( { i } ) => { + + const sampleDir = this._sampleVectors.element( i ); + const offset = rotationMatrix.mul( sampleDir.xy.mul( float( 1.0 ).add( sampleDir.z.mul( this.radius.sub( 1 ) ) ) ) ).div( this._resolution ); + const sampleUv = uvNode.add( offset ); + + const sampleResult = denoiseSample( center, viewNormal, viewPosition, sampleUv ); + + denoised.addAssign( sampleResult.xyz ); + totalWeight.addAssign( sampleResult.w ); + + } ); + + If( totalWeight.greaterThan( float( 0 ) ), () => { + + denoised.divAssign( totalWeight ); + + } ); + + result.assign( vec4( denoised, texel.a ) ); + + } ); + + return result; + + }/*, { uv: 'vec2', return: 'vec4' }*/ ); + + const output = Fn( () => { + + return denoise( uvNode ); + + } ); + + const outputNode = output(); + + return outputNode; + + } + +} + +export default DenoiseNode; + +/** + * Generates denoise samples based on the given parameters. + * + * @param {number} numSamples - The number of samples. + * @param {number} numRings - The number of rings. + * @param {number} radiusExponent - The radius exponent. + * @return {Array} The denoise samples. + */ +function generateDenoiseSamples( numSamples, numRings, radiusExponent ) { + + const samples = []; + + for ( let i = 0; i < numSamples; i ++ ) { + + const angle = 2 * Math.PI * numRings * i / numSamples; + const radius = Math.pow( i / ( numSamples - 1 ), radiusExponent ); + samples.push( new Vector3( Math.cos( angle ), Math.sin( angle ), radius ) ); + + } + + return samples; + +} + +/** + * Generates a default noise texture for the given size. + * + * @param {number} [size=64] - The texture size. + * @return {DataTexture} The generated noise texture. + */ +function generateDefaultNoise( size = 64 ) { + + const simplex = new SimplexNoise(); + + const arraySize = size * size * 4; + const data = new Uint8Array( arraySize ); + + for ( let i = 0; i < size; i ++ ) { + + for ( let j = 0; j < size; j ++ ) { + + const x = i; + const y = j; + + data[ ( i * size + j ) * 4 ] = ( simplex.noise( x, y ) * 0.5 + 0.5 ) * 255; + data[ ( i * size + j ) * 4 + 1 ] = ( simplex.noise( x + size, y ) * 0.5 + 0.5 ) * 255; + data[ ( i * size + j ) * 4 + 2 ] = ( simplex.noise( x, y + size ) * 0.5 + 0.5 ) * 255; + data[ ( i * size + j ) * 4 + 3 ] = ( simplex.noise( x + size, y + size ) * 0.5 + 0.5 ) * 255; + + } + + } + + const noiseTexture = new DataTexture( data, size, size ); + noiseTexture.wrapS = RepeatWrapping; + noiseTexture.wrapT = RepeatWrapping; + noiseTexture.needsUpdate = true; + + return noiseTexture; + +} + +/** + * TSL function for creating a denoise effect. + * + * @tsl + * @function + * @param {Node} node - The node that represents the input of the effect (e.g. AO). + * @param {Node} depthNode - A node that represents the scene's depth. + * @param {?Node} normalNode - A node that represents the scene's normals. + * @param {Camera} camera - The camera the scene is rendered with. + * @returns {DenoiseNode} + */ +export const denoise = ( node, depthNode, normalNode, camera ) => nodeObject( new DenoiseNode( convertToTexture( node ), nodeObject( depthNode ), nodeObject( normalNode ), camera ) ); diff --git a/examples/jsm/tsl/display/DepthOfFieldNode.js b/examples/jsm/tsl/display/DepthOfFieldNode.js new file mode 100644 index 00000000000000..0d9f9c5b7996bc --- /dev/null +++ b/examples/jsm/tsl/display/DepthOfFieldNode.js @@ -0,0 +1,198 @@ +import { TempNode, NodeUpdateType } from 'three/webgpu'; +import { convertToTexture, nodeObject, Fn, uv, uniform, vec2, vec4, clamp } from 'three/tsl'; + +/** + * Post processing node for creating depth of field (DOF) effect. + * + * @augments TempNode + * @three_import import { dof } from 'three/addons/tsl/display/DepthOfFieldNode.js'; + */ +class DepthOfFieldNode extends TempNode { + + static get type() { + + return 'DepthOfFieldNode'; + + } + + /** + * Constructs a new DOF node. + * + * @param {TextureNode} textureNode - The texture node that represents the input of the effect. + * @param {Node} viewZNode - Represents the viewZ depth values of the scene. + * @param {Node} focusNode - Defines the effect's focus which is the distance along the camera's look direction in world units. + * @param {Node} apertureNode - Defines the effect's aperture. + * @param {Node} maxblurNode - Defines the effect's maximum blur. + */ + constructor( textureNode, viewZNode, focusNode, apertureNode, maxblurNode ) { + + super( 'vec4' ); + + /** + * The texture node that represents the input of the effect. + * + * @type {TextureNode} + */ + this.textureNode = textureNode; + + /** + * Represents the viewZ depth values of the scene. + * + * @type {Node} + */ + this.viewZNode = viewZNode; + + /** + * Defines the effect's focus which is the distance along the camera's look direction in world units. + * + * @type {Node} + */ + this.focusNode = focusNode; + + /** + * Defines the effect's aperture. + * + * @type {Node} + */ + this.apertureNode = apertureNode; + + /** + * Defines the effect's maximum blur. + * + * @type {Node} + */ + this.maxblurNode = maxblurNode; + + /** + * Represents the input's aspect ratio. + * + * @private + * @type {UniformNode} + */ + this._aspect = uniform( 0 ); + + /** + * The `updateBeforeType` is set to `NodeUpdateType.FRAME` since the node updates + * its internal uniforms once per frame in `updateBefore()`. + * + * @type {string} + * @default 'frame' + */ + this.updateBeforeType = NodeUpdateType.FRAME; + + } + + /** + * This method is used to update the effect's uniforms once per frame. + * + * @param {NodeFrame} frame - The current node frame. + */ + updateBefore() { + + const map = this.textureNode.value; + + this._aspect.value = map.image.width / map.image.height; + + } + + /** + * This method is used to setup the effect's TSL code. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {ShaderCallNodeInternal} + */ + setup() { + + const textureNode = this.textureNode; + const uvNode = textureNode.uvNode || uv(); + + const sampleTexture = ( uv ) => textureNode.sample( uv ); + + const dof = Fn( () => { + + const aspectcorrect = vec2( 1.0, this._aspect ); + + const factor = this.focusNode.add( this.viewZNode ); + + const dofblur = vec2( clamp( factor.mul( this.apertureNode ), this.maxblurNode.negate(), this.maxblurNode ) ); + + const dofblur9 = dofblur.mul( 0.9 ); + const dofblur7 = dofblur.mul( 0.7 ); + const dofblur4 = dofblur.mul( 0.4 ); + + let col = vec4( 0.0 ); + + col = col.add( sampleTexture( uvNode ) ); + + col = col.add( sampleTexture( uvNode.add( vec2( 0.0, 0.4 ).mul( aspectcorrect ).mul( dofblur ) ) ) ); + col = col.add( sampleTexture( uvNode.add( vec2( 0.15, 0.37 ).mul( aspectcorrect ).mul( dofblur ) ) ) ); + col = col.add( sampleTexture( uvNode.add( vec2( 0.29, 0.29 ).mul( aspectcorrect ).mul( dofblur ) ) ) ); + col = col.add( sampleTexture( uvNode.add( vec2( - 0.37, 0.15 ).mul( aspectcorrect ).mul( dofblur ) ) ) ); + col = col.add( sampleTexture( uvNode.add( vec2( 0.40, 0.0 ).mul( aspectcorrect ).mul( dofblur ) ) ) ); + col = col.add( sampleTexture( uvNode.add( vec2( 0.37, - 0.15 ).mul( aspectcorrect ).mul( dofblur ) ) ) ); + col = col.add( sampleTexture( uvNode.add( vec2( 0.29, - 0.29 ).mul( aspectcorrect ).mul( dofblur ) ) ) ); + col = col.add( sampleTexture( uvNode.add( vec2( - 0.15, - 0.37 ).mul( aspectcorrect ).mul( dofblur ) ) ) ); + col = col.add( sampleTexture( uvNode.add( vec2( 0.0, - 0.4 ).mul( aspectcorrect ).mul( dofblur ) ) ) ); + col = col.add( sampleTexture( uvNode.add( vec2( - 0.15, 0.37 ).mul( aspectcorrect ).mul( dofblur ) ) ) ); + col = col.add( sampleTexture( uvNode.add( vec2( - 0.29, 0.29 ).mul( aspectcorrect ).mul( dofblur ) ) ) ); + col = col.add( sampleTexture( uvNode.add( vec2( 0.37, 0.15 ).mul( aspectcorrect ).mul( dofblur ) ) ) ); + col = col.add( sampleTexture( uvNode.add( vec2( - 0.4, 0.0 ).mul( aspectcorrect ).mul( dofblur ) ) ) ); + col = col.add( sampleTexture( uvNode.add( vec2( - 0.37, - 0.15 ).mul( aspectcorrect ).mul( dofblur ) ) ) ); + col = col.add( sampleTexture( uvNode.add( vec2( - 0.29, - 0.29 ).mul( aspectcorrect ).mul( dofblur ) ) ) ); + col = col.add( sampleTexture( uvNode.add( vec2( 0.15, - 0.37 ).mul( aspectcorrect ).mul( dofblur ) ) ) ); + col = col.add( sampleTexture( uvNode.add( vec2( 0.15, 0.37 ).mul( aspectcorrect ).mul( dofblur9 ) ) ) ); + col = col.add( sampleTexture( uvNode.add( vec2( - 0.37, 0.15 ).mul( aspectcorrect ).mul( dofblur9 ) ) ) ); + col = col.add( sampleTexture( uvNode.add( vec2( 0.37, - 0.15 ).mul( aspectcorrect ).mul( dofblur9 ) ) ) ); + col = col.add( sampleTexture( uvNode.add( vec2( - 0.15, - 0.37 ).mul( aspectcorrect ).mul( dofblur9 ) ) ) ); + col = col.add( sampleTexture( uvNode.add( vec2( - 0.15, 0.37 ).mul( aspectcorrect ).mul( dofblur9 ) ) ) ); + col = col.add( sampleTexture( uvNode.add( vec2( 0.37, 0.15 ).mul( aspectcorrect ).mul( dofblur9 ) ) ) ); + col = col.add( sampleTexture( uvNode.add( vec2( - 0.37, - 0.15 ).mul( aspectcorrect ).mul( dofblur9 ) ) ) ); + col = col.add( sampleTexture( uvNode.add( vec2( 0.15, - 0.37 ).mul( aspectcorrect ).mul( dofblur9 ) ) ) ); + col = col.add( sampleTexture( uvNode.add( vec2( 0.29, 0.29 ).mul( aspectcorrect ).mul( dofblur7 ) ) ) ); + col = col.add( sampleTexture( uvNode.add( vec2( 0.40, 0.0 ).mul( aspectcorrect ).mul( dofblur7 ) ) ) ); + col = col.add( sampleTexture( uvNode.add( vec2( 0.29, - 0.29 ).mul( aspectcorrect ).mul( dofblur7 ) ) ) ); + col = col.add( sampleTexture( uvNode.add( vec2( 0.0, - 0.4 ).mul( aspectcorrect ).mul( dofblur7 ) ) ) ); + col = col.add( sampleTexture( uvNode.add( vec2( - 0.29, 0.29 ).mul( aspectcorrect ).mul( dofblur7 ) ) ) ); + col = col.add( sampleTexture( uvNode.add( vec2( - 0.4, 0.0 ).mul( aspectcorrect ).mul( dofblur7 ) ) ) ); + col = col.add( sampleTexture( uvNode.add( vec2( - 0.29, - 0.29 ).mul( aspectcorrect ).mul( dofblur7 ) ) ) ); + col = col.add( sampleTexture( uvNode.add( vec2( 0.0, 0.4 ).mul( aspectcorrect ).mul( dofblur7 ) ) ) ); + col = col.add( sampleTexture( uvNode.add( vec2( 0.29, 0.29 ).mul( aspectcorrect ).mul( dofblur4 ) ) ) ); + col = col.add( sampleTexture( uvNode.add( vec2( 0.4, 0.0 ).mul( aspectcorrect ).mul( dofblur4 ) ) ) ); + col = col.add( sampleTexture( uvNode.add( vec2( 0.29, - 0.29 ).mul( aspectcorrect ).mul( dofblur4 ) ) ) ); + col = col.add( sampleTexture( uvNode.add( vec2( 0.0, - 0.4 ).mul( aspectcorrect ).mul( dofblur4 ) ) ) ); + col = col.add( sampleTexture( uvNode.add( vec2( - 0.29, 0.29 ).mul( aspectcorrect ).mul( dofblur4 ) ) ) ); + col = col.add( sampleTexture( uvNode.add( vec2( - 0.4, 0.0 ).mul( aspectcorrect ).mul( dofblur4 ) ) ) ); + col = col.add( sampleTexture( uvNode.add( vec2( - 0.29, - 0.29 ).mul( aspectcorrect ).mul( dofblur4 ) ) ) ); + col = col.add( sampleTexture( uvNode.add( vec2( 0.0, 0.4 ).mul( aspectcorrect ).mul( dofblur4 ) ) ) ); + + col = col.div( 41 ); + col.a = 1; + + return vec4( col ); + + + } ); + + const outputNode = dof(); + + return outputNode; + + } + +} + +export default DepthOfFieldNode; + +/** + * TSL function for creating a depth-of-field effect (DOF) for post processing. + * + * @tsl + * @function + * @param {Node} node - The node that represents the input of the effect. + * @param {Node} viewZNode - Represents the viewZ depth values of the scene. + * @param {Node | number} focus - Defines the effect's focus which is the distance along the camera's look direction in world units. + * @param {Node | number} aperture - Defines the effect's aperture. + * @param {Node | number} maxblur - Defines the effect's maximum blur. + * @returns {DepthOfFieldNode} + */ +export const dof = ( node, viewZNode, focus = 1, aperture = 0.025, maxblur = 1 ) => nodeObject( new DepthOfFieldNode( convertToTexture( node ), nodeObject( viewZNode ), nodeObject( focus ), nodeObject( aperture ), nodeObject( maxblur ) ) ); diff --git a/examples/jsm/tsl/display/DotScreenNode.js b/examples/jsm/tsl/display/DotScreenNode.js new file mode 100644 index 00000000000000..35939efe3e0edc --- /dev/null +++ b/examples/jsm/tsl/display/DotScreenNode.js @@ -0,0 +1,104 @@ +import { TempNode } from 'three/webgpu'; +import { nodeObject, Fn, uv, uniform, vec2, vec3, sin, cos, add, vec4, screenSize } from 'three/tsl'; + +/** + * Post processing node for creating dot-screen effect. + * + * @augments TempNode + * @three_import import { dotScreen } from 'three/addons/tsl/display/DotScreenNode.js'; + */ +class DotScreenNode extends TempNode { + + static get type() { + + return 'DotScreenNode'; + + } + + /** + * Constructs a new dot screen node. + * + * @param {Node} inputNode - The node that represents the input of the effect. + * @param {number} [angle=1.57] - The rotation of the effect in radians. + * @param {number} [scale=1] - The scale of the effect. A higher value means smaller dots. + */ + constructor( inputNode, angle = 1.57, scale = 1 ) { + + super( 'vec4' ); + + /** + * The node that represents the input of the effect. + * + * @type {Node} + */ + this.inputNode = inputNode; + + /** + * A uniform node that represents the rotation of the effect in radians. + * + * @type {UniformNode} + */ + this.angle = uniform( angle ); + + /** + * A uniform node that represents the scale of the effect. A higher value means smaller dots. + * + * @type {UniformNode} + */ + this.scale = uniform( scale ); + + } + + /** + * This method is used to setup the effect's TSL code. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {ShaderCallNodeInternal} + */ + setup() { + + const inputNode = this.inputNode; + + const pattern = Fn( () => { + + const s = sin( this.angle ); + const c = cos( this.angle ); + + const tex = uv().mul( screenSize ); + const point = vec2( c.mul( tex.x ).sub( s.mul( tex.y ) ), s.mul( tex.x ).add( c.mul( tex.y ) ) ).mul( this.scale ); + + return sin( point.x ).mul( sin( point.y ) ).mul( 4 ); + + } ); + + const dotScreen = Fn( () => { + + const color = inputNode; + + const average = add( color.r, color.g, color.b ).div( 3 ); + + return vec4( vec3( average.mul( 10 ).sub( 5 ).add( pattern() ) ), color.a ); + + } ); + + const outputNode = dotScreen(); + + return outputNode; + + } + +} + +export default DotScreenNode; + +/** + * TSL function for creating a dot-screen node for post processing. + * + * @tsl + * @function + * @param {Node} node - The node that represents the input of the effect. + * @param {number} [angle=1.57] - The rotation of the effect in radians. + * @param {number} [scale=1] - The scale of the effect. A higher value means smaller dots. + * @returns {DotScreenNode} + */ +export const dotScreen = ( node, angle, scale ) => nodeObject( new DotScreenNode( nodeObject( node ), angle, scale ) ); diff --git a/examples/jsm/tsl/display/FXAANode.js b/examples/jsm/tsl/display/FXAANode.js new file mode 100644 index 00000000000000..f90b6fcff2f6be --- /dev/null +++ b/examples/jsm/tsl/display/FXAANode.js @@ -0,0 +1,365 @@ +import { Vector2, TempNode } from 'three/webgpu'; +import { nodeObject, Fn, uniformArray, select, float, NodeUpdateType, uv, dot, clamp, uniform, convertToTexture, smoothstep, bool, vec2, vec3, If, Loop, max, min, Break, abs } from 'three/tsl'; + +/** + * Post processing node for applying FXAA. This node requires sRGB input + * so tone mapping and color space conversion must happen before the anti-aliasing. + * + * @augments TempNode + * @three_import import { fxaa } from 'three/addons/tsl/display/FXAANode.js'; + */ +class FXAANode extends TempNode { + + static get type() { + + return 'FXAANode'; + + } + + /** + * Constructs a new FXAA node. + * + * @param {TextureNode} textureNode - The texture node that represents the input of the effect. + */ + constructor( textureNode ) { + + super( 'vec4' ); + + /** + * The texture node that represents the input of the effect. + * + * @type {TextureNode} + */ + this.textureNode = textureNode; + + /** + * The `updateBeforeType` is set to `NodeUpdateType.FRAME` since the node updates + * its internal uniforms once per frame in `updateBefore()`. + * + * @type {string} + * @default 'frame' + */ + this.updateBeforeType = NodeUpdateType.FRAME; + + /** + * A uniform node holding the inverse resolution value. + * + * @private + * @type {UniformNode} + */ + this._invSize = uniform( new Vector2() ); + + } + + /** + * This method is used to update the effect's uniforms once per frame. + * + * @param {NodeFrame} frame - The current node frame. + */ + updateBefore( /* frame */ ) { + + const map = this.textureNode.value; + + this._invSize.value.set( 1 / map.image.width, 1 / map.image.height ); + + } + + /** + * This method is used to setup the effect's TSL code. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {ShaderCallNodeInternal} + */ + setup( /* builder */ ) { + + const textureNode = this.textureNode.bias( - 100 ); + const uvNode = textureNode.uvNode || uv(); + + const EDGE_STEP_COUNT = float( 6 ); + const EDGE_GUESS = float( 8.0 ); + const EDGE_STEPS = uniformArray( [ 1.0, 1.5, 2.0, 2.0, 2.0, 4.0 ] ); + + const _ContrastThreshold = float( 0.0312 ); + const _RelativeThreshold = float( 0.063 ); + const _SubpixelBlending = float( 1.0 ); + + const Sample = Fn( ( [ uv ] ) => { + + return textureNode.sample( uv ); + + } ); + + const SampleLuminance = Fn( ( [ uv ] ) => { + + return dot( Sample( uv ).rgb, vec3( 0.3, 0.59, 0.11 ) ); + + } ); + + const SampleLuminanceOffset = Fn( ( [ texSize, uv, uOffset, vOffset ] ) => { + + const shiftedUv = uv.add( texSize.mul( vec2( uOffset, vOffset ) ) ); + return SampleLuminance( shiftedUv ); + + } ); + + const ShouldSkipPixel = ( l ) => { + + const threshold = max( _ContrastThreshold, _RelativeThreshold.mul( l.highest ) ); + return l.contrast.lessThan( threshold ); + + }; + + const SampleLuminanceNeighborhood = ( texSize, uv ) => { + + const m = SampleLuminance( uv ); + + const n = SampleLuminanceOffset( texSize, uv, 0.0, - 1.0 ); + const e = SampleLuminanceOffset( texSize, uv, 1.0, 0.0 ); + const s = SampleLuminanceOffset( texSize, uv, 0.0, 1.0 ); + const w = SampleLuminanceOffset( texSize, uv, - 1.0, 0.0 ); + + const ne = SampleLuminanceOffset( texSize, uv, 1.0, - 1.0 ); + const nw = SampleLuminanceOffset( texSize, uv, - 1.0, - 1.0 ); + const se = SampleLuminanceOffset( texSize, uv, 1.0, 1.0 ); + const sw = SampleLuminanceOffset( texSize, uv, - 1.0, 1.0 ); + + const highest = max( s, e, n, w, m ); + const lowest = min( s, e, n, w, m ); + const contrast = highest.sub( lowest ); + + return { m, n, e, s, w, ne, nw, se, sw, highest, lowest, contrast }; + + }; + + const DeterminePixelBlendFactor = ( l ) => { + + let f = float( 2.0 ).mul( l.s.add( l.e ).add( l.n ).add( l.w ) ); + f = f.add( l.se.add( l.sw ).add( l.ne ).add( l.nw ) ); + f = f.mul( 1.0 / 12.0 ); + f = abs( f.sub( l.m ) ); + f = clamp( f.div( max( l.contrast, 0 ) ), 0.0, 1.0 ); + + const blendFactor = smoothstep( 0.0, 1.0, f ); + return blendFactor.mul( blendFactor ).mul( _SubpixelBlending ); + + }; + + const DetermineEdge = ( texSize, l ) => { + + const horizontal = + abs( l.s.add( l.n ).sub( l.m.mul( 2.0 ) ) ).mul( 2.0 ).add( + abs( l.se.add( l.ne ).sub( l.e.mul( 2.0 ) ) ).add( + abs( l.sw.add( l.nw ).sub( l.w.mul( 2.0 ) ) ) + ) + ); + + const vertical = + abs( l.e.add( l.w ).sub( l.m.mul( 2.0 ) ) ).mul( 2.0 ).add( + abs( l.se.add( l.sw ).sub( l.s.mul( 2.0 ) ) ).add( + abs( l.ne.add( l.nw ).sub( l.n.mul( 2.0 ) ) ) + ) + ); + + const isHorizontal = horizontal.greaterThanEqual( vertical ); + + const pLuminance = select( isHorizontal, l.s, l.e ); + const nLuminance = select( isHorizontal, l.n, l.w ); + const pGradient = abs( pLuminance.sub( l.m ) ); + const nGradient = abs( nLuminance.sub( l.m ) ); + + const pixelStep = select( isHorizontal, texSize.y, texSize.x ).toVar(); + const oppositeLuminance = float().toVar(); + const gradient = float().toVar(); + + If( pGradient.lessThan( nGradient ), () => { + + pixelStep.assign( pixelStep.negate() ); + oppositeLuminance.assign( nLuminance ); + gradient.assign( nGradient ); + + } ).Else( () => { + + oppositeLuminance.assign( pLuminance ); + gradient.assign( pGradient ); + + } ); + + return { isHorizontal, pixelStep, oppositeLuminance, gradient }; + + }; + + const DetermineEdgeBlendFactor = ( texSize, l, e, uv ) => { + + const uvEdge = uv.toVar(); + const edgeStep = vec2().toVar(); + If( e.isHorizontal, () => { + + uvEdge.y.addAssign( e.pixelStep.mul( 0.5 ) ); + edgeStep.assign( vec2( texSize.x, 0.0 ) ); + + } ).Else( () => { + + uvEdge.x.addAssign( e.pixelStep.mul( 0.5 ) ); + edgeStep.assign( vec2( 0.0, texSize.y ) ); + + } ); + + const edgeLuminance = l.m.add( e.oppositeLuminance ).mul( 0.5 ); + const gradientThreshold = e.gradient.mul( 0.25 ); + + const puv = uvEdge.add( edgeStep.mul( EDGE_STEPS.element( 0 ) ) ).toVar(); + const pLuminanceDelta = SampleLuminance( puv ).sub( edgeLuminance ).toVar(); + const pAtEnd = abs( pLuminanceDelta ).greaterThanEqual( gradientThreshold ).toVar(); + + Loop( { start: 1, end: EDGE_STEP_COUNT }, ( { i } ) => { + + If( pAtEnd, () => { + + Break(); + + } ); + + puv.addAssign( edgeStep.mul( EDGE_STEPS.element( i ) ) ); + pLuminanceDelta.assign( SampleLuminance( puv ).sub( edgeLuminance ) ); + pAtEnd.assign( abs( pLuminanceDelta ).greaterThanEqual( gradientThreshold ) ); + + } ); + + If( pAtEnd.not(), () => { + + puv.addAssign( edgeStep.mul( EDGE_GUESS ) ); + + } ); + + const nuv = uvEdge.sub( edgeStep.mul( EDGE_STEPS.element( 0 ) ) ).toVar(); + const nLuminanceDelta = SampleLuminance( nuv ).sub( edgeLuminance ).toVar(); + const nAtEnd = abs( nLuminanceDelta ).greaterThanEqual( gradientThreshold ).toVar(); + + Loop( { start: 1, end: EDGE_STEP_COUNT }, ( { i } ) => { + + If( nAtEnd, () => { + + Break(); + + } ); + + nuv.subAssign( edgeStep.mul( EDGE_STEPS.element( i ) ) ); + nLuminanceDelta.assign( SampleLuminance( nuv ).sub( edgeLuminance ) ); + nAtEnd.assign( abs( nLuminanceDelta ).greaterThanEqual( gradientThreshold ) ); + + } ); + + If( nAtEnd.not(), () => { + + nuv.subAssign( edgeStep.mul( EDGE_GUESS ) ); + + } ); + + const pDistance = float().toVar(); + const nDistance = float().toVar(); + + If( e.isHorizontal, () => { + + pDistance.assign( puv.x.sub( uv.x ) ); + nDistance.assign( uv.x.sub( nuv.x ) ); + + } ).Else( () => { + + pDistance.assign( puv.y.sub( uv.y ) ); + nDistance.assign( uv.y.sub( nuv.y ) ); + + } ); + + const shortestDistance = float().toVar(); + const deltaSign = bool().toVar(); + + If( pDistance.lessThanEqual( nDistance ), () => { + + shortestDistance.assign( pDistance ); + deltaSign.assign( pLuminanceDelta.greaterThanEqual( 0.0 ) ); + + } ).Else( () => { + + shortestDistance.assign( nDistance ); + deltaSign.assign( nLuminanceDelta.greaterThanEqual( 0.0 ) ); + + } ); + + const blendFactor = float().toVar(); + + If( deltaSign.equal( l.m.sub( edgeLuminance ).greaterThanEqual( 0.0 ) ), () => { + + blendFactor.assign( 0.0 ); + + } ).Else( () => { + + blendFactor.assign( float( 0.5 ).sub( shortestDistance.div( pDistance.add( nDistance ) ) ) ); + + } ); + + return blendFactor; + + }; + + const ApplyFXAA = Fn( ( [ uv, texSize ] ) => { + + const luminance = SampleLuminanceNeighborhood( texSize, uv ); + If( ShouldSkipPixel( luminance ), () => { + + return Sample( uv ); + + } ); + + const pixelBlend = DeterminePixelBlendFactor( luminance ); + const edge = DetermineEdge( texSize, luminance ); + const edgeBlend = DetermineEdgeBlendFactor( texSize, luminance, edge, uv ); + + const finalBlend = max( pixelBlend, edgeBlend ); + const finalUv = uv.toVar(); + + If( edge.isHorizontal, () => { + + finalUv.y.addAssign( edge.pixelStep.mul( finalBlend ) ); + + } ).Else( () => { + + finalUv.x.addAssign( edge.pixelStep.mul( finalBlend ) ); + + } ); + + return Sample( finalUv ); + + } ).setLayout( { + name: 'FxaaPixelShader', + type: 'vec4', + inputs: [ + { name: 'uv', type: 'vec2' }, + { name: 'texSize', type: 'vec2' }, + ] + } ); + + const fxaa = Fn( () => { + + return ApplyFXAA( uvNode, this._invSize ); + + } ); + + const outputNode = fxaa(); + + return outputNode; + + } + +} + +export default FXAANode; + +/** + * TSL function for creating a FXAA node for anti-aliasing via post processing. + * + * @tsl + * @function + * @param {Node} node - The node that represents the input of the effect. + * @returns {FXAANode} + */ +export const fxaa = ( node ) => nodeObject( new FXAANode( convertToTexture( node ) ) ); diff --git a/examples/jsm/tsl/display/FilmNode.js b/examples/jsm/tsl/display/FilmNode.js new file mode 100644 index 00000000000000..7c955c4de32950 --- /dev/null +++ b/examples/jsm/tsl/display/FilmNode.js @@ -0,0 +1,101 @@ +import { TempNode } from 'three/webgpu'; +import { rand, Fn, fract, time, uv, clamp, mix, vec4, nodeProxy } from 'three/tsl'; + +/** + * Post processing node for creating a film grain effect. + * + * @augments TempNode + * @three_import import { film } from 'three/addons/tsl/display/FilmNode.js'; + */ +class FilmNode extends TempNode { + + static get type() { + + return 'FilmNode'; + + } + + /** + * Constructs a new film node. + * + * @param {Node} inputNode - The node that represents the input of the effect. + * @param {?Node} [intensityNode=null] - A node that represents the effect's intensity. + * @param {?Node} [uvNode=null] - A node that allows to pass custom (e.g. animated) uv data. + */ + constructor( inputNode, intensityNode = null, uvNode = null ) { + + super( 'vec4' ); + + /** + * The node that represents the input of the effect. + * + * @type {Node} + */ + this.inputNode = inputNode; + + /** + * A node that represents the effect's intensity. + * + * @type {?Node} + * @default null + */ + this.intensityNode = intensityNode; + + /** + * A node that allows to pass custom (e.g. animated) uv data. + * + * @type {?Node} + * @default null + */ + this.uvNode = uvNode; + + } + + /** + * This method is used to setup the effect's TSL code. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {ShaderCallNodeInternal} + */ + setup( /* builder */ ) { + + const uvNode = this.uvNode || uv(); + + const film = Fn( () => { + + const base = this.inputNode.rgb; + const noise = rand( fract( uvNode.add( time ) ) ); + + let color = base.add( base.mul( clamp( noise.add( 0.1 ), 0, 1 ) ) ); + + if ( this.intensityNode !== null ) { + + color = mix( base, color, this.intensityNode ); + + } + + return vec4( color, this.inputNode.a ); + + } ); + + const outputNode = film(); + + return outputNode; + + } + +} + +export default FilmNode; + +/** + * TSL function for creating a film node for post processing. + * + * @tsl + * @function + * @param {Node} inputNode - The node that represents the input of the effect. + * @param {?Node} [intensityNode=null] - A node that represents the effect's intensity. + * @param {?Node} [uvNode=null] - A node that allows to pass custom (e.g. animated) uv data. + * @returns {FilmNode} + */ +export const film = /*@__PURE__*/ nodeProxy( FilmNode ); diff --git a/examples/jsm/tsl/display/GTAONode.js b/examples/jsm/tsl/display/GTAONode.js new file mode 100644 index 00000000000000..a9079041c19a20 --- /dev/null +++ b/examples/jsm/tsl/display/GTAONode.js @@ -0,0 +1,522 @@ +import { DataTexture, RenderTarget, RepeatWrapping, Vector2, Vector3, TempNode, QuadMesh, NodeMaterial, RendererUtils } from 'three/webgpu'; +import { reference, logarithmicDepthToViewZ, viewZToPerspectiveDepth, getNormalFromDepth, getScreenPosition, getViewPosition, nodeObject, Fn, float, NodeUpdateType, uv, uniform, Loop, vec2, vec3, vec4, int, dot, max, pow, abs, If, textureSize, sin, cos, PI, texture, passTexture, mat3, add, normalize, mul, cross, div, mix, sqrt, sub, acos, clamp } from 'three/tsl'; + +const _quadMesh = /*@__PURE__*/ new QuadMesh(); +const _size = /*@__PURE__*/ new Vector2(); + +let _rendererState; + +/** + * Post processing node for applying Ground Truth Ambient Occlusion (GTAO) to a scene. + * ```js + * const postProcessing = new THREE.PostProcessing( renderer ); + * + * const scenePass = pass( scene, camera ); + * scenePass.setMRT( mrt( { + * output: output, + * normal: normalView + * } ) ); + * + * const scenePassColor = scenePass.getTextureNode( 'output' ); + * const scenePassNormal = scenePass.getTextureNode( 'normal' ); + * const scenePassDepth = scenePass.getTextureNode( 'depth' ); + * + * const aoPass = ao( scenePassDepth, scenePassNormal, camera ); + * + * postProcessing.outputNod = aoPass.getTextureNode().mul( scenePassColor ); + * ``` + * + * Reference: {@link https://www.activision.com/cdn/research/Practical_Real_Time_Strategies_for_Accurate_Indirect_Occlusion_NEW%20VERSION_COLOR.pdf}. + * + * @augments TempNode + * @three_import import { ao } from 'three/addons/tsl/display/GTAONode.js'; + */ +class GTAONode extends TempNode { + + static get type() { + + return 'GTAONode'; + + } + + /** + * Constructs a new GTAO node. + * + * @param {Node} depthNode - A node that represents the scene's depth. + * @param {?Node} normalNode - A node that represents the scene's normals. + * @param {Camera} camera - The camera the scene is rendered with. + */ + constructor( depthNode, normalNode, camera ) { + + super( 'vec4' ); + + /** + * A node that represents the scene's depth. + * + * @type {Node} + */ + this.depthNode = depthNode; + + /** + * A node that represents the scene's normals. If no normals are passed to the + * constructor (because MRT is not available), normals can be automatically + * reconstructed from depth values in the shader. + * + * @type {?Node} + */ + this.normalNode = normalNode; + + /** + * The resolution scale. By default the effect is rendered in full resolution + * for best quality but a value of `0.5` should be sufficient for most scenes. + * + * @type {number} + * @default 1 + */ + this.resolutionScale = 1; + + /** + * The `updateBeforeType` is set to `NodeUpdateType.FRAME` since the node renders + * its effect once per frame in `updateBefore()`. + * + * @type {string} + * @default 'frame' + */ + this.updateBeforeType = NodeUpdateType.FRAME; + + /** + * The render target the ambient occlusion is rendered into. + * + * @private + * @type {RenderTarget} + */ + this._aoRenderTarget = new RenderTarget( 1, 1, { depthBuffer: false } ); + this._aoRenderTarget.texture.name = 'GTAONode.AO'; + + // uniforms + + /** + * The radius of the ambient occlusion. + * + * @type {UniformNode} + */ + this.radius = uniform( 0.25 ); + + /** + * The resolution of the effect. Can be scaled via + * `resolutionScale`. + * + * @type {UniformNode} + */ + this.resolution = uniform( new Vector2() ); + + /** + * The thickness of the ambient occlusion. + * + * @type {UniformNode} + */ + this.thickness = uniform( 1 ); + + /** + * Another option to tweak the occlusion. The recommended range is + * `[1,2]` for attenuating the AO. + * + * @type {UniformNode} + */ + this.distanceExponent = uniform( 1 ); + + /** + * The distance fall off value of the ambient occlusion. + * A lower value leads to a larger AO effect. The value + * should lie in the range `[0,1]`. + * + * @type {UniformNode} + */ + this.distanceFallOff = uniform( 1 ); + + /** + * The scale of the ambient occlusion. + * + * @type {UniformNode} + */ + this.scale = uniform( 1 ); + + /** + * How many samples are used to compute the AO. + * A higher value results in better quality but also + * in a more expensive runtime behavior. + * + * @type {UniformNode} + */ + this.samples = uniform( 16 ); + + /** + * The node represents the internal noise texture used by the AO. + * + * @private + * @type {TextureNode} + */ + this._noiseNode = texture( generateMagicSquareNoise() ); + + /** + * Represents the projection matrix of the scene's camera. + * + * @private + * @type {UniformNode} + */ + this._cameraProjectionMatrix = uniform( camera.projectionMatrix ); + + /** + * Represents the inverse projection matrix of the scene's camera. + * + * @private + * @type {UniformNode} + */ + this._cameraProjectionMatrixInverse = uniform( camera.projectionMatrixInverse ); + + /** + * Represents the near value of the scene's camera. + * + * @private + * @type {ReferenceNode} + */ + this._cameraNear = reference( 'near', 'float', camera ); + + /** + * Represents the far value of the scene's camera. + * + * @private + * @type {ReferenceNode} + */ + this._cameraFar = reference( 'far', 'float', camera ); + + /** + * The material that is used to render the effect. + * + * @private + * @type {NodeMaterial} + */ + this._material = new NodeMaterial(); + this._material.name = 'GTAO'; + + /** + * The result of the effect is represented as a separate texture node. + * + * @private + * @type {PassTextureNode} + */ + this._textureNode = passTexture( this, this._aoRenderTarget.texture ); + + } + + /** + * Returns the result of the effect as a texture node. + * + * @return {PassTextureNode} A texture node that represents the result of the effect. + */ + getTextureNode() { + + return this._textureNode; + + } + + /** + * Sets the size of the effect. + * + * @param {number} width - The width of the effect. + * @param {number} height - The height of the effect. + */ + setSize( width, height ) { + + width = Math.round( this.resolutionScale * width ); + height = Math.round( this.resolutionScale * height ); + + this.resolution.value.set( width, height ); + this._aoRenderTarget.setSize( width, height ); + + } + + /** + * This method is used to render the effect once per frame. + * + * @param {NodeFrame} frame - The current node frame. + */ + updateBefore( frame ) { + + const { renderer } = frame; + + _rendererState = RendererUtils.resetRendererState( renderer, _rendererState ); + + // + + const size = renderer.getDrawingBufferSize( _size ); + this.setSize( size.width, size.height ); + + _quadMesh.material = this._material; + + // clear + + renderer.setClearColor( 0xffffff, 1 ); + + // ao + + renderer.setRenderTarget( this._aoRenderTarget ); + _quadMesh.render( renderer ); + + // restore + + RendererUtils.restoreRendererState( renderer, _rendererState ); + + } + + /** + * This method is used to setup the effect's TSL code. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {PassTextureNode} + */ + setup( builder ) { + + const uvNode = uv(); + + const sampleDepth = ( uv ) => { + + const depth = this.depthNode.sample( uv ).r; + + if ( builder.renderer.logarithmicDepthBuffer === true ) { + + const viewZ = logarithmicDepthToViewZ( depth, this._cameraNear, this._cameraFar ); + + return viewZToPerspectiveDepth( viewZ, this._cameraNear, this._cameraFar ); + + } + + return depth; + + }; + + const sampleNoise = ( uv ) => this._noiseNode.sample( uv ); + const sampleNormal = ( uv ) => ( this.normalNode !== null ) ? this.normalNode.sample( uv ).rgb.normalize() : getNormalFromDepth( uv, this.depthNode.value, this._cameraProjectionMatrixInverse ); + + const ao = Fn( () => { + + const depth = sampleDepth( uvNode ).toVar(); + + depth.greaterThanEqual( 1.0 ).discard(); + + const viewPosition = getViewPosition( uvNode, depth, this._cameraProjectionMatrixInverse ).toVar(); + const viewNormal = sampleNormal( uvNode ).toVar(); + + const radiusToUse = this.radius; + + const noiseResolution = textureSize( this._noiseNode, 0 ); + let noiseUv = vec2( uvNode.x, uvNode.y.oneMinus() ); + noiseUv = noiseUv.mul( this.resolution.div( noiseResolution ) ); + const noiseTexel = sampleNoise( noiseUv ); + const randomVec = noiseTexel.xyz.mul( 2.0 ).sub( 1.0 ); + const tangent = vec3( randomVec.xy, 0.0 ).normalize(); + const bitangent = vec3( tangent.y.mul( - 1.0 ), tangent.x, 0.0 ); + const kernelMatrix = mat3( tangent, bitangent, vec3( 0.0, 0.0, 1.0 ) ); + + const DIRECTIONS = this.samples.lessThan( 30 ).select( 3, 5 ).toVar(); + const STEPS = add( this.samples, DIRECTIONS.sub( 1 ) ).div( DIRECTIONS ).toVar(); + + const ao = float( 0 ).toVar(); + + Loop( { start: int( 0 ), end: DIRECTIONS, type: 'int', condition: '<' }, ( { i } ) => { + + const angle = float( i ).div( float( DIRECTIONS ) ).mul( PI ).toVar(); + const sampleDir = vec4( cos( angle ), sin( angle ), 0., add( 0.5, mul( 0.5, noiseTexel.w ) ) ); + sampleDir.xyz = normalize( kernelMatrix.mul( sampleDir.xyz ) ); + + const viewDir = normalize( viewPosition.xyz.negate() ).toVar(); + const sliceBitangent = normalize( cross( sampleDir.xyz, viewDir ) ).toVar(); + const sliceTangent = cross( sliceBitangent, viewDir ); + const normalInSlice = normalize( viewNormal.sub( sliceBitangent.mul( dot( viewNormal, sliceBitangent ) ) ) ); + + const tangentToNormalInSlice = cross( normalInSlice, sliceBitangent ).toVar(); + const cosHorizons = vec2( dot( viewDir, tangentToNormalInSlice ), dot( viewDir, tangentToNormalInSlice.negate() ) ).toVar(); + + Loop( { end: STEPS, type: 'int', name: 'j', condition: '<' }, ( { j } ) => { + + const sampleViewOffset = sampleDir.xyz.mul( radiusToUse ).mul( sampleDir.w ).mul( pow( div( float( j ).add( 1.0 ), float( STEPS ) ), this.distanceExponent ) ); + + // x + + const sampleScreenPositionX = getScreenPosition( viewPosition.add( sampleViewOffset ), this._cameraProjectionMatrix ).toVar(); + const sampleDepthX = sampleDepth( sampleScreenPositionX ).toVar(); + const sampleSceneViewPositionX = getViewPosition( sampleScreenPositionX, sampleDepthX, this._cameraProjectionMatrixInverse ).toVar(); + const viewDeltaX = sampleSceneViewPositionX.sub( viewPosition ).toVar(); + + If( abs( viewDeltaX.z ).lessThan( this.thickness ), () => { + + const sampleCosHorizon = dot( viewDir, normalize( viewDeltaX ) ); + cosHorizons.x.addAssign( max( 0, mul( sampleCosHorizon.sub( cosHorizons.x ), mix( 1.0, float( 2.0 ).div( float( j ).add( 2 ) ), this.distanceFallOff ) ) ) ); + + } ); + + // y + + const sampleScreenPositionY = getScreenPosition( viewPosition.sub( sampleViewOffset ), this._cameraProjectionMatrix ).toVar(); + const sampleDepthY = sampleDepth( sampleScreenPositionY ).toVar(); + const sampleSceneViewPositionY = getViewPosition( sampleScreenPositionY, sampleDepthY, this._cameraProjectionMatrixInverse ).toVar(); + const viewDeltaY = sampleSceneViewPositionY.sub( viewPosition ).toVar(); + + If( abs( viewDeltaY.z ).lessThan( this.thickness ), () => { + + const sampleCosHorizon = dot( viewDir, normalize( viewDeltaY ) ); + cosHorizons.y.addAssign( max( 0, mul( sampleCosHorizon.sub( cosHorizons.y ), mix( 1.0, float( 2.0 ).div( float( j ).add( 2 ) ), this.distanceFallOff ) ) ) ); + + } ); + + } ); + + const sinHorizons = sqrt( sub( 1.0, cosHorizons.mul( cosHorizons ) ) ).toVar(); + const nx = dot( normalInSlice, sliceTangent ); + const ny = dot( normalInSlice, viewDir ); + const nxb = mul( 0.5, acos( cosHorizons.y ).sub( acos( cosHorizons.x ) ).add( sinHorizons.x.mul( cosHorizons.x ).sub( sinHorizons.y.mul( cosHorizons.y ) ) ) ); + const nyb = mul( 0.5, sub( 2.0, cosHorizons.x.mul( cosHorizons.x ) ).sub( cosHorizons.y.mul( cosHorizons.y ) ) ); + const occlusion = nx.mul( nxb ).add( ny.mul( nyb ) ); + ao.addAssign( occlusion ); + + } ); + + ao.assign( clamp( ao.div( DIRECTIONS ), 0, 1 ) ); + ao.assign( pow( ao, this.scale ) ); + + return vec4( vec3( ao ), 1.0 ); + + } ); + + this._material.fragmentNode = ao().context( builder.getSharedContext() ); + this._material.needsUpdate = true; + + // + + return this._textureNode; + + } + + /** + * Frees internal resources. This method should be called + * when the effect is no longer required. + */ + dispose() { + + this._aoRenderTarget.dispose(); + + this._material.dispose(); + + } + +} + +export default GTAONode; + +/** + * Generates the AO's noise texture for the given size. + * + * @param {number} [size=5] - The noise size. + * @return {DataTexture} The generated noise texture. + */ +function generateMagicSquareNoise( size = 5 ) { + + const noiseSize = Math.floor( size ) % 2 === 0 ? Math.floor( size ) + 1 : Math.floor( size ); + const magicSquare = generateMagicSquare( noiseSize ); + const noiseSquareSize = magicSquare.length; + const data = new Uint8Array( noiseSquareSize * 4 ); + + for ( let inx = 0; inx < noiseSquareSize; ++ inx ) { + + const iAng = magicSquare[ inx ]; + const angle = ( 2 * Math.PI * iAng ) / noiseSquareSize; + const randomVec = new Vector3( + Math.cos( angle ), + Math.sin( angle ), + 0 + ).normalize(); + data[ inx * 4 ] = ( randomVec.x * 0.5 + 0.5 ) * 255; + data[ inx * 4 + 1 ] = ( randomVec.y * 0.5 + 0.5 ) * 255; + data[ inx * 4 + 2 ] = 127; + data[ inx * 4 + 3 ] = 255; + + } + + const noiseTexture = new DataTexture( data, noiseSize, noiseSize ); + noiseTexture.wrapS = RepeatWrapping; + noiseTexture.wrapT = RepeatWrapping; + noiseTexture.needsUpdate = true; + + return noiseTexture; + +} + +/** + * Computes an array of magic square values required to generate the noise texture. + * + * @param {number} size - The noise size. + * @return {Array} The magic square values. + */ +function generateMagicSquare( size ) { + + const noiseSize = Math.floor( size ) % 2 === 0 ? Math.floor( size ) + 1 : Math.floor( size ); + const noiseSquareSize = noiseSize * noiseSize; + const magicSquare = Array( noiseSquareSize ).fill( 0 ); + let i = Math.floor( noiseSize / 2 ); + let j = noiseSize - 1; + + for ( let num = 1; num <= noiseSquareSize; ) { + + if ( i === - 1 && j === noiseSize ) { + + j = noiseSize - 2; + i = 0; + + } else { + + if ( j === noiseSize ) { + + j = 0; + + } + + if ( i < 0 ) { + + i = noiseSize - 1; + + } + + } + + if ( magicSquare[ i * noiseSize + j ] !== 0 ) { + + j -= 2; + i ++; + continue; + + } else { + + magicSquare[ i * noiseSize + j ] = num ++; + + } + + j ++; + i --; + + } + + return magicSquare; + +} + +/** + * TSL function for creating a Ground Truth Ambient Occlusion (GTAO) effect. + * + * @tsl + * @function + * @param {Node} depthNode - A node that represents the scene's depth. + * @param {?Node} normalNode - A node that represents the scene's normals. + * @param {Camera} camera - The camera the scene is rendered with. + * @returns {GTAONode} + */ +export const ao = ( depthNode, normalNode, camera ) => nodeObject( new GTAONode( nodeObject( depthNode ), nodeObject( normalNode ), camera ) ); diff --git a/examples/jsm/tsl/display/GaussianBlurNode.js b/examples/jsm/tsl/display/GaussianBlurNode.js new file mode 100644 index 00000000000000..38e1306e1c1bd3 --- /dev/null +++ b/examples/jsm/tsl/display/GaussianBlurNode.js @@ -0,0 +1,368 @@ +import { RenderTarget, Vector2, NodeMaterial, RendererUtils, QuadMesh, TempNode, NodeUpdateType } from 'three/webgpu'; +import { nodeObject, Fn, float, uv, uniform, convertToTexture, vec2, vec4, passTexture, mul, premultiplyAlpha, unpremultiplyAlpha } from 'three/tsl'; + +const _quadMesh = /*@__PURE__*/ new QuadMesh(); + +let _rendererState; + +/** + * Post processing node for creating a gaussian blur effect. + * + * @augments TempNode + * @three_import import { gaussianBlur, premultipliedGaussianBlur } from 'three/addons/tsl/display/GaussianBlurNode.js'; + */ +class GaussianBlurNode extends TempNode { + + static get type() { + + return 'GaussianBlurNode'; + + } + + /** + * Constructs a new gaussian blur node. + * + * @param {TextureNode} textureNode - The texture node that represents the input of the effect. + * @param {Node} directionNode - Defines the direction and radius of the blur. + * @param {number} sigma - Controls the kernel of the blur filter. Higher values mean a wider blur radius. + */ + constructor( textureNode, directionNode = null, sigma = 2 ) { + + super( 'vec4' ); + + /** + * The texture node that represents the input of the effect. + * + * @type {TextureNode} + */ + this.textureNode = textureNode; + + /** + * Defines the direction and radius of the blur. + * + * @type {Node} + */ + this.directionNode = directionNode; + + /** + * Controls the kernel of the blur filter. Higher values mean a wider blur radius. + * + * @type {number} + */ + this.sigma = sigma; + + /** + * A uniform node holding the inverse resolution value. + * + * @private + * @type {UniformNode} + */ + this._invSize = uniform( new Vector2() ); + + /** + * Gaussian blur is applied in two passes (horizontal, vertical). + * This node controls the direction of each pass. + * + * @private + * @type {UniformNode} + */ + this._passDirection = uniform( new Vector2() ); + + /** + * The render target used for the horizontal pass. + * + * @private + * @type {RenderTarget} + */ + this._horizontalRT = new RenderTarget( 1, 1, { depthBuffer: false } ); + this._horizontalRT.texture.name = 'GaussianBlurNode.horizontal'; + + /** + * The render target used for the vertical pass. + * + * @private + * @type {RenderTarget} + */ + this._verticalRT = new RenderTarget( 1, 1, { depthBuffer: false } ); + this._verticalRT.texture.name = 'GaussianBlurNode.vertical'; + + /** + * The result of the effect is represented as a separate texture node. + * + * @private + * @type {PassTextureNode} + */ + this._textureNode = passTexture( this, this._verticalRT.texture ); + this._textureNode.uvNode = textureNode.uvNode; + + /** + * The `updateBeforeType` is set to `NodeUpdateType.FRAME` since the node renders + * its effect once per frame in `updateBefore()`. + * + * @type {string} + * @default 'frame' + */ + this.updateBeforeType = NodeUpdateType.FRAME; + + /** + * Controls the resolution of the effect. + * + * @type {Vector2} + * @default (1,1) + */ + this.resolution = new Vector2( 1, 1 ); + + /** + * Whether the effect should use premultiplied alpha or not. Set this to `true` + * if you are going to blur texture input with transparency. + * + * @type {boolean} + * @default false + */ + this.premultipliedAlpha = false; + + } + + /** + * Sets the given premultiplied alpha value. + * + * @param {boolean} value - Whether the effect should use premultiplied alpha or not. + * @return {GaussianBlurNode} height - A reference to this node. + */ + setPremultipliedAlpha( value ) { + + this.premultipliedAlpha = value; + + return this; + + } + + /** + * Returns the premultiplied alpha value. + * + * @return {boolean} Whether the effect should use premultiplied alpha or not. + */ + getPremultipliedAlpha() { + + return this.premultipliedAlpha; + + } + + /** + * Sets the size of the effect. + * + * @param {number} width - The width of the effect. + * @param {number} height - The height of the effect. + */ + setSize( width, height ) { + + width = Math.max( Math.round( width * this.resolution.x ), 1 ); + height = Math.max( Math.round( height * this.resolution.y ), 1 ); + + this._invSize.value.set( 1 / width, 1 / height ); + this._horizontalRT.setSize( width, height ); + this._verticalRT.setSize( width, height ); + + } + + /** + * This method is used to render the effect once per frame. + * + * @param {NodeFrame} frame - The current node frame. + */ + updateBefore( frame ) { + + const { renderer } = frame; + + _rendererState = RendererUtils.resetRendererState( renderer, _rendererState ); + + // + + const textureNode = this.textureNode; + const map = textureNode.value; + + const currentTexture = textureNode.value; + + _quadMesh.material = this._material; + + this.setSize( map.image.width, map.image.height ); + + const textureType = map.type; + + this._horizontalRT.texture.type = textureType; + this._verticalRT.texture.type = textureType; + + // horizontal + + renderer.setRenderTarget( this._horizontalRT ); + + this._passDirection.value.set( 1, 0 ); + + _quadMesh.render( renderer ); + + // vertical + + textureNode.value = this._horizontalRT.texture; + renderer.setRenderTarget( this._verticalRT ); + + this._passDirection.value.set( 0, 1 ); + + _quadMesh.render( renderer ); + + // restore + + textureNode.value = currentTexture; + + RendererUtils.restoreRendererState( renderer, _rendererState ); + + } + + /** + * Returns the result of the effect as a texture node. + * + * @return {PassTextureNode} A texture node that represents the result of the effect. + */ + getTextureNode() { + + return this._textureNode; + + } + + /** + * This method is used to setup the effect's TSL code. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {PassTextureNode} + */ + setup( builder ) { + + const textureNode = this.textureNode; + + // + + const uvNode = uv(); + const directionNode = vec2( this.directionNode || 1 ); + + let sampleTexture, output; + + if ( this.premultipliedAlpha ) { + + // https://lisyarus.github.io/blog/posts/blur-coefficients-generator.html + + sampleTexture = ( uv ) => premultiplyAlpha( textureNode.sample( uv ) ); + output = ( color ) => unpremultiplyAlpha( color ); + + } else { + + sampleTexture = ( uv ) => textureNode.sample( uv ); + output = ( color ) => color; + + } + + const blur = Fn( () => { + + const kernelSize = 3 + ( 2 * this.sigma ); + const gaussianCoefficients = this._getCoefficients( kernelSize ); + + const invSize = this._invSize; + const direction = directionNode.mul( this._passDirection ); + + const weightSum = float( gaussianCoefficients[ 0 ] ).toVar(); + const diffuseSum = vec4( sampleTexture( uvNode ).mul( weightSum ) ).toVar(); + + for ( let i = 1; i < kernelSize; i ++ ) { + + const x = float( i ); + const w = float( gaussianCoefficients[ i ] ); + + const uvOffset = vec2( direction.mul( invSize.mul( x ) ) ).toVar(); + + const sample1 = sampleTexture( uvNode.add( uvOffset ) ); + const sample2 = sampleTexture( uvNode.sub( uvOffset ) ); + + diffuseSum.addAssign( sample1.add( sample2 ).mul( w ) ); + weightSum.addAssign( mul( 2.0, w ) ); + + } + + return output( diffuseSum.div( weightSum ) ); + + } ); + + // + + const material = this._material || ( this._material = new NodeMaterial() ); + material.fragmentNode = blur().context( builder.getSharedContext() ); + material.name = 'Gaussian_blur'; + material.needsUpdate = true; + + // + + const properties = builder.getNodeProperties( this ); + properties.textureNode = textureNode; + + // + + return this._textureNode; + + } + + /** + * Frees internal resources. This method should be called + * when the effect is no longer required. + */ + dispose() { + + this._horizontalRT.dispose(); + this._verticalRT.dispose(); + + } + + /** + * Computes gaussian coefficients depending on the given kernel radius. + * + * @private + * @param {number} kernelRadius - The kernel radius. + * @return {Array} + */ + _getCoefficients( kernelRadius ) { + + const coefficients = []; + + for ( let i = 0; i < kernelRadius; i ++ ) { + + coefficients.push( 0.39894 * Math.exp( - 0.5 * i * i / ( kernelRadius * kernelRadius ) ) / kernelRadius ); + + } + + return coefficients; + + } + +} + +export default GaussianBlurNode; + +/** + * TSL function for creating a gaussian blur node for post processing. + * + * @tsl + * @function + * @param {Node} node - The node that represents the input of the effect. + * @param {Node} directionNode - Defines the direction and radius of the blur. + * @param {number} sigma - Controls the kernel of the blur filter. Higher values mean a wider blur radius. + * @returns {GaussianBlurNode} + */ +export const gaussianBlur = ( node, directionNode, sigma ) => nodeObject( new GaussianBlurNode( convertToTexture( node ), directionNode, sigma ) ); + +/** + * TSL function for creating a gaussian blur node for post processing with enabled premultiplied alpha. + * + * @tsl + * @function + * @param {Node} node - The node that represents the input of the effect. + * @param {Node} directionNode - Defines the direction and radius of the blur. + * @param {number} sigma - Controls the kernel of the blur filter. Higher values mean a wider blur radius. + * @returns {GaussianBlurNode} + */ +export const premultipliedGaussianBlur = ( node, directionNode, sigma ) => nodeObject( new GaussianBlurNode( convertToTexture( node ), directionNode, sigma ).setPremultipliedAlpha( true ) ); diff --git a/examples/jsm/tsl/display/LensflareNode.js b/examples/jsm/tsl/display/LensflareNode.js new file mode 100644 index 00000000000000..9654ace92348bd --- /dev/null +++ b/examples/jsm/tsl/display/LensflareNode.js @@ -0,0 +1,279 @@ +import { RenderTarget, Vector2, TempNode, NodeUpdateType, QuadMesh, RendererUtils, NodeMaterial } from 'three/webgpu'; +import { convertToTexture, nodeObject, Fn, passTexture, uv, vec2, vec3, vec4, max, float, sub, int, Loop, fract, pow, distance } from 'three/tsl'; + +const _quadMesh = /*@__PURE__*/ new QuadMesh(); +const _size = /*@__PURE__*/ new Vector2(); +let _rendererState; + +/** + * Post processing node for adding a bloom-based lens flare effect. This effect + * requires that you extract the bloom of the scene via a bloom pass first. + * + * References: + * - {@link https://john-chapman-graphics.blogspot.com/2013/02/pseudo-lens-flare.html}. + * - {@link https://john-chapman.github.io/2017/11/05/pseudo-lens-flare.html}. + * + * @augments TempNode + * @three_import import { lensflare } from 'three/addons/tsl/display/LensflareNode.js'; + */ +class LensflareNode extends TempNode { + + static get type() { + + return 'LensflareNode'; + + } + + /** + * Constructs a new lens flare node. + * + * @param {TextureNode} textureNode - The texture node that represents the scene's bloom. + * @param {Object} params - The parameter object for configuring the effect. + * @param {Node | Color} [params.ghostTint=vec3(1, 1, 1)] - Defines the tint of the flare/ghosts. + * @param {Node | number} [params.threshold=float(0.5)] - Controls the size and strength of the effect. A higher threshold results in smaller flares. + * @param {Node | number} [params.ghostSamples=float(4)] - Represents the number of flares/ghosts per bright spot which pivot around the center. + * @param {Node | number} [params.ghostSpacing=float(0.25)] - Defines the spacing of the flares/ghosts. + * @param {Node | number} [params.ghostAttenuationFactor=float(25)] - Defines the attenuation factor of flares/ghosts. + * @param {number} [params.downSampleRatio=4] - Defines how downsampling since the effect is usually not rendered at full resolution. + */ + constructor( textureNode, params = {} ) { + + super( 'vec4' ); + + /** + * The texture node that represents the scene's bloom. + * + * @type {TextureNode} + */ + this.textureNode = textureNode; + + const { + ghostTint = vec3( 1, 1, 1 ), + threshold = float( 0.5 ), + ghostSamples = float( 4 ), + ghostSpacing = float( 0.25 ), + ghostAttenuationFactor = float( 25 ), + downSampleRatio = 4 + } = params; + + /** + * Defines the tint of the flare/ghosts. + * + * @type {Node} + */ + this.ghostTintNode = nodeObject( ghostTint ); + + /** + * Controls the size and strength of the effect. A higher threshold results in smaller flares. + * + * @type {Node} + */ + this.thresholdNode = nodeObject( threshold ); + + /** + * Represents the number of flares/ghosts per bright spot which pivot around the center. + * + * @type {Node} + */ + this.ghostSamplesNode = nodeObject( ghostSamples ); + + /** + * Defines the spacing of the flares/ghosts. + * + * @type {Node} + */ + this.ghostSpacingNode = nodeObject( ghostSpacing ); + + /** + * Defines the attenuation factor of flares/ghosts. + * + * @type {Node} + */ + this.ghostAttenuationFactorNode = nodeObject( ghostAttenuationFactor ); + + /** + * Defines how downsampling since the effect is usually not rendered at full resolution. + * + * @type {number} + */ + this.downSampleRatio = downSampleRatio; + + /** + * The `updateBeforeType` is set to `NodeUpdateType.FRAME` since the node renders + * its effect once per frame in `updateBefore()`. + * + * @type {string} + * @default 'frame' + */ + this.updateBeforeType = NodeUpdateType.FRAME; + + /** + * The internal render target of the effect. + * + * @private + * @type {RenderTarget} + */ + this._renderTarget = new RenderTarget( 1, 1, { depthBuffer: false } ); + this._renderTarget.texture.name = 'LensflareNode'; + + /** + * The node material that holds the effect's TSL code. + * + * @private + * @type {NodeMaterial} + */ + this._material = new NodeMaterial(); + this._material.name = 'LensflareNode'; + + /** + * The result of the effect is represented as a separate texture node. + * + * @private + * @type {PassTextureNode} + */ + this._textureNode = passTexture( this, this._renderTarget.texture ); + + } + + /** + * Returns the result of the effect as a texture node. + * + * @return {PassTextureNode} A texture node that represents the result of the effect. + */ + getTextureNode() { + + return this._textureNode; + + } + + /** + * Sets the size of the effect. + * + * @param {number} width - The width of the effect. + * @param {number} height - The height of the effect. + */ + setSize( width, height ) { + + const resx = Math.round( width / this.downSampleRatio ); + const resy = Math.round( height / this.downSampleRatio ); + + this._renderTarget.setSize( resx, resy ); + + } + + /** + * This method is used to render the effect once per frame. + * + * @param {NodeFrame} frame - The current node frame. + */ + updateBefore( frame ) { + + const { renderer } = frame; + + const size = renderer.getDrawingBufferSize( _size ); + this.setSize( size.width, size.height ); + + _rendererState = RendererUtils.resetRendererState( renderer, _rendererState ); + + _quadMesh.material = this._material; + + // clear + + renderer.setMRT( null ); + + // lensflare + + renderer.setRenderTarget( this._renderTarget ); + _quadMesh.render( renderer ); + + // restore + + RendererUtils.restoreRendererState( renderer, _rendererState ); + + } + + /** + * This method is used to setup the effect's TSL code. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {PassTextureNode} + */ + setup( builder ) { + + const lensflare = Fn( () => { + + // flip uvs so lens flare pivot around the image center + + const texCoord = uv().oneMinus().toVar(); + + // ghosts are positioned along this vector + + const ghostVec = sub( vec2( 0.5 ), texCoord ).mul( this.ghostSpacingNode ).toVar(); + + // sample ghosts + + const result = vec4().toVar(); + + Loop( { start: int( 0 ), end: int( this.ghostSamplesNode ), type: 'int', condition: '<' }, ( { i } ) => { + + // use fract() to ensure that the texture coordinates wrap around + + const sampleUv = fract( texCoord.add( ghostVec.mul( float( i ) ) ) ).toVar(); + + // reduce contributions from samples at the screen edge + + const d = distance( sampleUv, vec2( 0.5 ) ); + const weight = pow( d.oneMinus(), this.ghostAttenuationFactorNode ); + + // accumulate + + let sample = this.textureNode.sample( sampleUv ).rgb; + + sample = max( sample.sub( this.thresholdNode ), vec3( 0 ) ).mul( this.ghostTintNode ); + + result.addAssign( sample.mul( weight ) ); + + } ); + + return result; + + } ); + + this._material.fragmentNode = lensflare().context( builder.getSharedContext() ); + this._material.needsUpdate = true; + + return this._textureNode; + + } + + /** + * Frees internal resources. This method should be called + * when the effect is no longer required. + */ + dispose() { + + this._renderTarget.dispose(); + this._material.dispose(); + + } + +} + +export default LensflareNode; + +/** + * TSL function for creating a bloom-based lens flare effect. + * + * @tsl + * @function + * @param {TextureNode} node - The node that represents the scene's bloom. + * @param {Object} params - The parameter object for configuring the effect. + * @param {Node | Color} [params.ghostTint=vec3(1, 1, 1)] - Defines the tint of the flare/ghosts. + * @param {Node | number} [params.threshold=float(0.5)] - Controls the size and strength of the effect. A higher threshold results in smaller flares. + * @param {Node | number} [params.ghostSamples=float(4)] - Represents the number of flares/ghosts per bright spot which pivot around the center. + * @param {Node | number} [params.ghostSpacing=float(0.25)] - Defines the spacing of the flares/ghosts. + * @param {Node | number} [params.ghostAttenuationFactor=float(25)] - Defines the attenuation factor of flares/ghosts. + * @param {number} [params.downSampleRatio=4] - Defines how downsampling since the effect is usually not rendered at full resolution. + * @returns {LensflareNode} + */ +export const lensflare = ( node, params ) => nodeObject( new LensflareNode( convertToTexture( node ), params ) ); diff --git a/examples/jsm/tsl/display/Lut3DNode.js b/examples/jsm/tsl/display/Lut3DNode.js new file mode 100644 index 00000000000000..3b588f637fb343 --- /dev/null +++ b/examples/jsm/tsl/display/Lut3DNode.js @@ -0,0 +1,109 @@ +import { TempNode } from 'three/webgpu'; +import { nodeObject, Fn, float, uniform, vec3, vec4, mix } from 'three/tsl'; + +/** + * A post processing node for color grading via lookup tables. + * + * @augments TempNode + * @three_import import { lut3D } from 'three/addons/tsl/display/Lut3DNode.js'; + */ +class Lut3DNode extends TempNode { + + static get type() { + + return 'Lut3DNode'; + + } + + /** + * Constructs a new LUT node. + * + * @param {Node} inputNode - The node that represents the input of the effect. + * @param {TextureNode} lutNode - A texture node that represents the lookup table. + * @param {number} size - The size of the lookup table. + * @param {Node} intensityNode - Controls the intensity of the effect. + */ + constructor( inputNode, lutNode, size, intensityNode ) { + + super( 'vec4' ); + + /** + * The node that represents the input of the effect. + * + * @type {Node} + */ + this.inputNode = inputNode; + + /** + * A texture node that represents the lookup table. + * + * @type {TextureNode} + */ + this.lutNode = lutNode; + + /** + * The size of the lookup table. + * + * @type {UniformNode} + */ + this.size = uniform( size ); + + /** + * Controls the intensity of the effect. + * + * @type {Node} + */ + this.intensityNode = intensityNode; + + } + + /** + * This method is used to setup the effect's TSL code. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {ShaderCallNodeInternal} + */ + setup() { + + const { inputNode, lutNode } = this; + + const sampleLut = ( uv ) => lutNode.sample( uv ); + + const lut3D = Fn( () => { + + const base = inputNode; + + // pull the sample in by half a pixel so the sample begins at the center of the edge pixels. + + const pixelWidth = float( 1.0 ).div( this.size ); + const halfPixelWidth = float( 0.5 ).div( this.size ); + const uvw = vec3( halfPixelWidth ).add( base.rgb.mul( float( 1.0 ).sub( pixelWidth ) ) ); + + const lutValue = vec4( sampleLut( uvw ).rgb, base.a ); + + return vec4( mix( base, lutValue, this.intensityNode ) ); + + } ); + + const outputNode = lut3D(); + + return outputNode; + + } + +} + +export default Lut3DNode; + +/** + * TSL function for creating a LUT node for color grading via post processing. + * + * @tsl + * @function + * @param {Node} node - The node that represents the input of the effect. + * @param {TextureNode} lut - A texture node that represents the lookup table. + * @param {number} size - The size of the lookup table. + * @param {Node | number} intensity - Controls the intensity of the effect. + * @returns {Lut3DNode} + */ +export const lut3D = ( node, lut, size, intensity ) => nodeObject( new Lut3DNode( nodeObject( node ), nodeObject( lut ), size, nodeObject( intensity ) ) ); diff --git a/examples/jsm/tsl/display/MotionBlur.js b/examples/jsm/tsl/display/MotionBlur.js new file mode 100644 index 00000000000000..91862dbc50a000 --- /dev/null +++ b/examples/jsm/tsl/display/MotionBlur.js @@ -0,0 +1,33 @@ +import { Fn, float, uv, Loop, int } from 'three/tsl'; + +/** + * Applies a motion blur effect to the given input node. + * + * @tsl + * @function + * @param {Node} inputNode - The input node to apply the motion blur for. + * @param {Node} velocity - The motion vectors of the beauty pass. + * @param {Node} [numSamples=int(16)] - How many samples the effect should use. A higher value results in better quality but is also more expensive. + * @return {Node} The input node with the motion blur effect applied. + */ +export const motionBlur = /*@__PURE__*/ Fn( ( [ inputNode, velocity, numSamples = int( 16 ) ] ) => { + + const sampleColor = ( uv ) => inputNode.sample( uv ); + + const uvs = uv(); + + const colorResult = sampleColor( uvs ).toVar(); + const fSamples = float( numSamples ); + + Loop( { start: int( 1 ), end: numSamples, type: 'int', condition: '<=' }, ( { i } ) => { + + const offset = velocity.mul( float( i ).div( fSamples.sub( 1 ) ).sub( 0.5 ) ); + colorResult.addAssign( sampleColor( uvs.add( offset ) ) ); + + } ); + + colorResult.divAssign( fSamples ); + + return colorResult; + +} ); diff --git a/examples/jsm/tsl/display/OutlineNode.js b/examples/jsm/tsl/display/OutlineNode.js new file mode 100644 index 00000000000000..8daee555cf2dd8 --- /dev/null +++ b/examples/jsm/tsl/display/OutlineNode.js @@ -0,0 +1,751 @@ +import { DepthTexture, FloatType, RenderTarget, Vector2, TempNode, QuadMesh, NodeMaterial, RendererUtils, NodeUpdateType } from 'three/webgpu'; +import { Loop, int, exp, min, float, mul, uv, vec2, vec3, Fn, textureSize, orthographicDepthToViewZ, screenUV, nodeObject, uniform, vec4, passTexture, texture, perspectiveDepthToViewZ, positionView, reference } from 'three/tsl'; + +const _quadMesh = /*@__PURE__*/ new QuadMesh(); +const _size = /*@__PURE__*/ new Vector2(); +const _BLUR_DIRECTION_X = /*@__PURE__*/ new Vector2( 1.0, 0.0 ); +const _BLUR_DIRECTION_Y = /*@__PURE__*/ new Vector2( 0.0, 1.0 ); + +let _rendererState; + +/** + * Post processing node for rendering outlines around selected objects. The node + * gives you great flexibility in composing the final outline look depending on + * your requirements. + * ```js + * const postProcessing = new THREE.PostProcessing( renderer ); + * + * const scenePass = pass( scene, camera ); + * + * // outline parameter + * + * const edgeStrength = uniform( 3.0 ); + * const edgeGlow = uniform( 0.0 ); + * const edgeThickness = uniform( 1.0 ); + * const visibleEdgeColor = uniform( new THREE.Color( 0xffffff ) ); + * const hiddenEdgeColor = uniform( new THREE.Color( 0x4e3636 ) ); + * + * outlinePass = outline( scene, camera, { + * selectedObjects, + * edgeGlow, + * edgeThickness + * } ); + * + * // compose custom outline + * + * const { visibleEdge, hiddenEdge } = outlinePass; + * const outlineColor = visibleEdge.mul( visibleEdgeColor ).add( hiddenEdge.mul( hiddenEdgeColor ) ).mul( edgeStrength ); + * + * postProcessing.outputNode = outlineColor.add( scenePass ); + * ``` + * + * @augments TempNode + * @three_import import { outline } from 'three/addons/tsl/display/OutlineNode.js'; + */ +class OutlineNode extends TempNode { + + static get type() { + + return 'OutlineNode'; + + } + + /** + * Constructs a new outline node. + * + * @param {Scene} scene - A reference to the scene. + * @param {Camera} camera - The camera the scene is rendered with. + * @param {Object} params - The configuration parameters. + * @param {Array} params.selectedObjects - An array of selected objects. + * @param {Node} [params.edgeThickness=float(1)] - The thickness of the edges. + * @param {Node} [params.edgeGlow=float(0)] - Can be used for an animated glow/pulse effects. + * @param {number} [params.downSampleRatio=2] - The downsample ratio. + */ + constructor( scene, camera, params = {} ) { + + super( 'vec4' ); + + const { + selectedObjects = [], + edgeThickness = float( 1 ), + edgeGlow = float( 0 ), + downSampleRatio = 2 + } = params; + + /** + * A reference to the scene. + * + * @type {Scene} + */ + this.scene = scene; + + /** + * The camera the scene is rendered with. + * + * @type {Camera} + */ + this.camera = camera; + + /** + * An array of selected objects. + * + * @type {Array} + */ + this.selectedObjects = selectedObjects; + + /** + * The thickness of the edges. + * + * @type {Node} + */ + this.edgeThicknessNode = nodeObject( edgeThickness ); + + /** + * Can be used for an animated glow/pulse effect. + * + * @type {Node} + */ + this.edgeGlowNode = nodeObject( edgeGlow ); + + /** + * The downsample ratio. + * + * @type {number} + * @default 2 + */ + this.downSampleRatio = downSampleRatio; + + /** + * The `updateBeforeType` is set to `NodeUpdateType.FRAME` since the node renders + * its effect once per frame in `updateBefore()`. + * + * @type {string} + * @default 'frame' + */ + this.updateBeforeType = NodeUpdateType.FRAME; + + // render targets + + /** + * The render target for the depth pre-pass. + * + * @private + * @type {RenderTarget} + */ + this._renderTargetDepthBuffer = new RenderTarget(); + this._renderTargetDepthBuffer.depthTexture = new DepthTexture(); + this._renderTargetDepthBuffer.depthTexture.type = FloatType; + + /** + * The render target for the mask pass. + * + * @private + * @type {RenderTarget} + */ + this._renderTargetMaskBuffer = new RenderTarget(); + + /** + * The render target for the mask downsample. + * + * @private + * @type {RenderTarget} + */ + this._renderTargetMaskDownSampleBuffer = new RenderTarget( 1, 1, { depthBuffer: false } ); + + /** + * The first render target for the edge detection. + * + * @private + * @type {RenderTarget} + */ + this._renderTargetEdgeBuffer1 = new RenderTarget( 1, 1, { depthBuffer: false } ); + + /** + * The second render target for the edge detection. + * + * @private + * @type {RenderTarget} + */ + this._renderTargetEdgeBuffer2 = new RenderTarget( 1, 1, { depthBuffer: false } ); + + /** + * The first render target for the blur pass. + * + * @private + * @type {RenderTarget} + */ + this._renderTargetBlurBuffer1 = new RenderTarget( 1, 1, { depthBuffer: false } ); + + /** + * The second render target for the blur pass. + * + * @private + * @type {RenderTarget} + */ + this._renderTargetBlurBuffer2 = new RenderTarget( 1, 1, { depthBuffer: false } ); + + /** + * The render target for the final composite. + * + * @private + * @type {RenderTarget} + */ + this._renderTargetComposite = new RenderTarget( 1, 1, { depthBuffer: false } ); + + // uniforms + + /** + * Represents the near value of the scene's camera. + * + * @private + * @type {ReferenceNode} + */ + this._cameraNear = reference( 'near', 'float', camera ); + + /** + * Represents the far value of the scene's camera. + * + * @private + * @type {ReferenceNode} + */ + this._cameraFar = reference( 'far', 'float', camera ); + + /** + * Uniform that represents the blur direction of the pass. + * + * @private + * @type {UniformNode} + */ + this._blurDirection = uniform( new Vector2() ); + + /** + * Texture node that holds the data from the depth pre-pass. + * + * @private + * @type {TextureNode} + */ + this._depthTextureUniform = texture( this._renderTargetDepthBuffer.depthTexture ); + + /** + * Texture node that holds the data from the mask pass. + * + * @private + * @type {TextureNode} + */ + this._maskTextureUniform = texture( this._renderTargetMaskBuffer.texture ); + + /** + * Texture node that holds the data from the mask downsample pass. + * + * @private + * @type {TextureNode} + */ + this._maskTextureDownsSampleUniform = texture( this._renderTargetMaskDownSampleBuffer.texture ); + + /** + * Texture node that holds the data from the first edge detection pass. + * + * @private + * @type {TextureNode} + */ + this._edge1TextureUniform = texture( this._renderTargetEdgeBuffer1.texture ); + + /** + * Texture node that holds the data from the second edge detection pass. + * + * @private + * @type {TextureNode} + */ + this._edge2TextureUniform = texture( this._renderTargetEdgeBuffer2.texture ); + + /** + * Texture node that holds the current blurred color data. + * + * @private + * @type {TextureNode} + */ + this._blurColorTextureUniform = texture( this._renderTargetEdgeBuffer1.texture ); + + // constants + + /** + * Visible edge color. + * + * @private + * @type {Node} + */ + this._visibleEdgeColor = vec3( 1, 0, 0 ); + + /** + * Hidden edge color. + * + * @private + * @type {Node} + */ + this._hiddenEdgeColor = vec3( 0, 1, 0 ); + + // materials + + /** + * The material for the depth pre-pass. + * + * @private + * @type {NodeMaterial} + */ + this._depthMaterial = new NodeMaterial(); + this._depthMaterial.fragmentNode = vec4( 0, 0, 0, 1 ); + this._depthMaterial.name = 'OutlineNode.depth'; + + /** + * The material for preparing the mask. + * + * @private + * @type {NodeMaterial} + */ + this._prepareMaskMaterial = new NodeMaterial(); + this._prepareMaskMaterial.name = 'OutlineNode.prepareMask'; + + /** + * The copy material + * + * @private + * @type {NodeMaterial} + */ + this._materialCopy = new NodeMaterial(); + this._materialCopy.name = 'OutlineNode.copy'; + + /** + * The edge detection material. + * + * @private + * @type {NodeMaterial} + */ + this._edgeDetectionMaterial = new NodeMaterial(); + this._edgeDetectionMaterial.name = 'OutlineNode.edgeDetection'; + + /** + * The material that is used to render in the blur pass. + * + * @private + * @type {NodeMaterial} + */ + this._separableBlurMaterial = new NodeMaterial(); + this._separableBlurMaterial.name = 'OutlineNode.separableBlur'; + + /** + * The material that is used to render in the blur pass. + * + * @private + * @type {NodeMaterial} + */ + this._separableBlurMaterial2 = new NodeMaterial(); + this._separableBlurMaterial2.name = 'OutlineNode.separableBlur2'; + + /** + * The final composite material. + * + * @private + * @type {NodeMaterial} + */ + this._compositeMaterial = new NodeMaterial(); + this._compositeMaterial.name = 'OutlineNode.composite'; + + /** + * A set to cache selected objects in the scene. + * + * @private + * @type {Set} + */ + this._selectionCache = new Set(); + + /** + * The result of the effect is represented as a separate texture node. + * + * @private + * @type {PassTextureNode} + */ + this._textureNode = passTexture( this, this._renderTargetComposite.texture ); + + } + + /** + * A mask value that represents the visible edge. + * + * @return {Node} The visible edge. + */ + get visibleEdge() { + + return this.r; + + } + + /** + * A mask value that represents the hidden edge. + * + * @return {Node} The hidden edge. + */ + get hiddenEdge() { + + return this.g; + + } + + /** + * Returns the result of the effect as a texture node. + * + * @return {PassTextureNode} A texture node that represents the result of the effect. + */ + getTextureNode() { + + return this._textureNode; + + } + + /** + * Sets the size of the effect. + * + * @param {number} width - The width of the effect. + * @param {number} height - The height of the effect. + */ + setSize( width, height ) { + + this._renderTargetDepthBuffer.setSize( width, height ); + this._renderTargetMaskBuffer.setSize( width, height ); + this._renderTargetComposite.setSize( width, height ); + + // downsample 1 + + let resx = Math.round( width / this.downSampleRatio ); + let resy = Math.round( height / this.downSampleRatio ); + + this._renderTargetMaskDownSampleBuffer.setSize( resx, resy ); + this._renderTargetEdgeBuffer1.setSize( resx, resy ); + this._renderTargetBlurBuffer1.setSize( resx, resy ); + + // downsample 2 + + resx = Math.round( resx / 2 ); + resy = Math.round( resy / 2 ); + + this._renderTargetEdgeBuffer2.setSize( resx, resy ); + this._renderTargetBlurBuffer2.setSize( resx, resy ); + + } + + /** + * This method is used to render the effect once per frame. + * + * @param {NodeFrame} frame - The current node frame. + */ + updateBefore( frame ) { + + const { renderer } = frame; + const { camera, scene } = this; + + _rendererState = RendererUtils.resetRendererAndSceneState( renderer, scene, _rendererState ); + + // + + const size = renderer.getDrawingBufferSize( _size ); + this.setSize( size.width, size.height ); + + // + + renderer.setClearColor( 0xffffff, 1 ); + + this._updateSelectionCache(); + + // 1. Draw non-selected objects in the depth buffer + + scene.overrideMaterial = this._depthMaterial; + + renderer.setRenderTarget( this._renderTargetDepthBuffer ); + renderer.setRenderObjectFunction( ( object, ...params ) => { + + if ( this._selectionCache.has( object ) === false ) { + + renderer.renderObject( object, ...params ); + + } + + } ); + + renderer.render( scene, camera ); + + // 2. Draw only the selected objects by comparing the depth buffer of non-selected objects + + scene.overrideMaterial = this._prepareMaskMaterial; + + renderer.setRenderTarget( this._renderTargetMaskBuffer ); + renderer.setRenderObjectFunction( ( object, ...params ) => { + + if ( this._selectionCache.has( object ) === true ) { + + renderer.renderObject( object, ...params ); + + } + + } ); + + renderer.render( scene, camera ); + + // + + renderer.setRenderObjectFunction( _rendererState.renderObjectFunction ); + + this._selectionCache.clear(); + + // 3. Downsample to (at least) half resolution + + _quadMesh.material = this._materialCopy; + renderer.setRenderTarget( this._renderTargetMaskDownSampleBuffer ); + _quadMesh.render( renderer ); + + // 4. Perform edge detection (half resolution) + + _quadMesh.material = this._edgeDetectionMaterial; + renderer.setRenderTarget( this._renderTargetEdgeBuffer1 ); + _quadMesh.render( renderer ); + + // 5. Apply blur (half resolution) + + this._blurColorTextureUniform.value = this._renderTargetEdgeBuffer1.texture; + this._blurDirection.value.copy( _BLUR_DIRECTION_X ); + + _quadMesh.material = this._separableBlurMaterial; + renderer.setRenderTarget( this._renderTargetBlurBuffer1 ); + _quadMesh.render( renderer ); + + this._blurColorTextureUniform.value = this._renderTargetBlurBuffer1.texture; + this._blurDirection.value.copy( _BLUR_DIRECTION_Y ); + + renderer.setRenderTarget( this._renderTargetEdgeBuffer1 ); + _quadMesh.render( renderer ); + + // 6. Apply blur (quarter resolution) + + this._blurColorTextureUniform.value = this._renderTargetEdgeBuffer1.texture; + this._blurDirection.value.copy( _BLUR_DIRECTION_X ); + + _quadMesh.material = this._separableBlurMaterial2; + renderer.setRenderTarget( this._renderTargetBlurBuffer2 ); + _quadMesh.render( renderer ); + + this._blurColorTextureUniform.value = this._renderTargetBlurBuffer2.texture; + this._blurDirection.value.copy( _BLUR_DIRECTION_Y ); + + renderer.setRenderTarget( this._renderTargetEdgeBuffer2 ); + _quadMesh.render( renderer ); + + // 7. Composite + + _quadMesh.material = this._compositeMaterial; + renderer.setRenderTarget( this._renderTargetComposite ); + _quadMesh.render( renderer ); + + // restore + + RendererUtils.restoreRendererAndSceneState( renderer, scene, _rendererState ); + + } + + /** + * This method is used to setup the effect's TSL code. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {PassTextureNode} + */ + setup() { + + // prepare mask material + + const prepareMask = () => { + + const depth = this._depthTextureUniform.sample( screenUV ); + + let viewZNode; + + if ( this.camera.isPerspectiveCamera ) { + + viewZNode = perspectiveDepthToViewZ( depth, this._cameraNear, this._cameraFar ); + + } else { + + viewZNode = orthographicDepthToViewZ( depth, this._cameraNear, this._cameraFar ); + + } + + const depthTest = positionView.z.lessThanEqual( viewZNode ).select( 1, 0 ); + return vec4( 0.0, depthTest, 1.0, 1.0 ); + + }; + + this._prepareMaskMaterial.fragmentNode = prepareMask(); + this._prepareMaskMaterial.needsUpdate = true; + + // copy material + + this._materialCopy.fragmentNode = this._maskTextureUniform; + this._materialCopy.needsUpdate = true; + + // edge detection material + + const edgeDetection = Fn( () => { + + const resolution = textureSize( this._maskTextureDownsSampleUniform ); + const invSize = vec2( 1 ).div( resolution ).toVar(); + const uvOffset = vec4( 1.0, 0.0, 0.0, 1.0 ).mul( vec4( invSize, invSize ) ); + + const uvNode = uv(); + const c1 = this._maskTextureDownsSampleUniform.sample( uvNode.add( uvOffset.xy ) ).toVar(); + const c2 = this._maskTextureDownsSampleUniform.sample( uvNode.sub( uvOffset.xy ) ).toVar(); + const c3 = this._maskTextureDownsSampleUniform.sample( uvNode.add( uvOffset.yw ) ).toVar(); + const c4 = this._maskTextureDownsSampleUniform.sample( uvNode.sub( uvOffset.yw ) ).toVar(); + + const diff1 = mul( c1.r.sub( c2.r ), 0.5 ); + const diff2 = mul( c3.r.sub( c4.r ), 0.5 ); + const d = vec2( diff1, diff2 ).length(); + const a1 = min( c1.g, c2.g ); + const a2 = min( c3.g, c4.g ); + const visibilityFactor = min( a1, a2 ); + const edgeColor = visibilityFactor.oneMinus().greaterThan( 0.001 ).select( this._visibleEdgeColor, this._hiddenEdgeColor ); + return vec4( edgeColor, 1 ).mul( d ); + + } ); + + this._edgeDetectionMaterial.fragmentNode = edgeDetection(); + this._edgeDetectionMaterial.needsUpdate = true; + + // separable blur material + + const MAX_RADIUS = 4; + + const gaussianPdf = Fn( ( [ x, sigma ] ) => { + + return float( 0.39894 ).mul( exp( float( - 0.5 ).mul( x ).mul( x ).div( sigma.mul( sigma ) ) ).div( sigma ) ); + + } ); + + const separableBlur = Fn( ( [ kernelRadius ] ) => { + + const resolution = textureSize( this._maskTextureDownsSampleUniform ); + const invSize = vec2( 1 ).div( resolution ).toVar(); + const uvNode = uv(); + + const sigma = kernelRadius.div( 2 ).toVar(); + const weightSum = gaussianPdf( 0, sigma ).toVar(); + const diffuseSum = this._blurColorTextureUniform.sample( uvNode ).mul( weightSum ).toVar(); + const delta = this._blurDirection.mul( invSize ).mul( kernelRadius ).div( MAX_RADIUS ).toVar(); + + const uvOffset = delta.toVar(); + + Loop( { start: int( 1 ), end: int( MAX_RADIUS ), type: 'int', condition: '<=' }, ( { i } ) => { + + const x = kernelRadius.mul( float( i ) ).div( MAX_RADIUS ); + const w = gaussianPdf( x, sigma ); + const sample1 = this._blurColorTextureUniform.sample( uvNode.add( uvOffset ) ); + const sample2 = this._blurColorTextureUniform.sample( uvNode.sub( uvOffset ) ); + + diffuseSum.addAssign( sample1.add( sample2 ).mul( w ) ); + weightSum.addAssign( w.mul( 2 ) ); + uvOffset.addAssign( delta ); + + } ); + + return diffuseSum.div( weightSum ); + + } ); + + this._separableBlurMaterial.fragmentNode = separableBlur( this.edgeThicknessNode ); + this._separableBlurMaterial.needsUpdate = true; + + this._separableBlurMaterial2.fragmentNode = separableBlur( MAX_RADIUS ); + this._separableBlurMaterial2.needsUpdate = true; + + // composite material + + const composite = Fn( () => { + + const edgeValue1 = this._edge1TextureUniform; + const edgeValue2 = this._edge2TextureUniform; + const maskColor = this._maskTextureUniform; + + const edgeValue = edgeValue1.add( edgeValue2.mul( this.edgeGlowNode ) ); + + return maskColor.r.mul( edgeValue ); + + } ); + + this._compositeMaterial.fragmentNode = composite(); + this._compositeMaterial.needsUpdate = true; + + return this._textureNode; + + } + + /** + * Frees internal resources. This method should be called + * when the effect is no longer required. + */ + dispose() { + + this.selectedObjects.length = 0; + + this._renderTargetDepthBuffer.dispose(); + this._renderTargetMaskBuffer.dispose(); + this._renderTargetMaskDownSampleBuffer.dispose(); + this._renderTargetEdgeBuffer1.dispose(); + this._renderTargetEdgeBuffer2.dispose(); + this._renderTargetBlurBuffer1.dispose(); + this._renderTargetBlurBuffer2.dispose(); + this._renderTargetComposite.dispose(); + + this._depthMaterial.dispose(); + this._prepareMaskMaterial.dispose(); + this._materialCopy.dispose(); + this._edgeDetectionMaterial.dispose(); + this._separableBlurMaterial.dispose(); + this._separableBlurMaterial2.dispose(); + this._compositeMaterial.dispose(); + + } + + /** + * Updates the selection cache based on the selected objects. + * + * @private + */ + _updateSelectionCache() { + + for ( let i = 0; i < this.selectedObjects.length; i ++ ) { + + const selectedObject = this.selectedObjects[ i ]; + selectedObject.traverse( ( object ) => { + + if ( object.isMesh ) this._selectionCache.add( object ); + + } ); + + } + + } + +} + +export default OutlineNode; + +/** + * TSL function for creating an outline effect around selected objects. + * + * @tsl + * @function + * @param {Scene} scene - A reference to the scene. + * @param {Camera} camera - The camera the scene is rendered with. + * @param {Object} params - The configuration parameters. + * @param {Array} params.selectedObjects - An array of selected objects. + * @param {Node} [params.edgeThickness=float(1)] - The thickness of the edges. + * @param {Node} [params.edgeGlow=float(0)] - Can be used for animated glow/pulse effects. + * @param {number} [params.downSampleRatio=2] - The downsample ratio. + * @returns {OutlineNode} + */ +export const outline = ( scene, camera, params ) => nodeObject( new OutlineNode( scene, camera, params ) ); diff --git a/examples/jsm/tsl/display/ParallaxBarrierPassNode.js b/examples/jsm/tsl/display/ParallaxBarrierPassNode.js new file mode 100644 index 00000000000000..8403e2b19bf149 --- /dev/null +++ b/examples/jsm/tsl/display/ParallaxBarrierPassNode.js @@ -0,0 +1,89 @@ +import { NodeMaterial } from 'three/webgpu'; +import { nodeObject, Fn, vec4, uv, If, mod, screenCoordinate } from 'three/tsl'; +import StereoCompositePassNode from './StereoCompositePassNode.js'; + +/** + * A render pass node that creates a parallax barrier effect. + * + * @augments StereoCompositePassNode + * @three_import import { parallaxBarrierPass } from 'three/addons/tsl/display/ParallaxBarrierPassNode.js'; + */ +class ParallaxBarrierPassNode extends StereoCompositePassNode { + + static get type() { + + return 'ParallaxBarrierPassNode'; + + } + + /** + * Constructs a new parallax barrier pass node. + * + * @param {Scene} scene - The scene to render. + * @param {Camera} camera - The camera to render the scene with. + */ + constructor( scene, camera ) { + + super( scene, camera ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isParallaxBarrierPassNode = true; + + } + + /** + * This method is used to setup the effect's TSL code. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {PassTextureNode} + */ + setup( builder ) { + + const uvNode = uv(); + + const parallaxBarrier = Fn( () => { + + const color = vec4().toVar(); + + If( mod( screenCoordinate.y, 2 ).greaterThan( 1 ), () => { + + color.assign( this._mapLeft.sample( uvNode ) ); + + } ).Else( () => { + + color.assign( this._mapRight.sample( uvNode ) ); + + } ); + + return color; + + } ); + + const material = this._material || ( this._material = new NodeMaterial() ); + material.fragmentNode = parallaxBarrier().context( builder.getSharedContext() ); + material.needsUpdate = true; + + return super.setup( builder ); + + } + +} + +export default ParallaxBarrierPassNode; + +/** + * TSL function for creating an parallax barrier pass node. + * + * @tsl + * @function + * @param {Scene} scene - The scene to render. + * @param {Camera} camera - The camera to render the scene with. + * @returns {ParallaxBarrierPassNode} + */ +export const parallaxBarrierPass = ( scene, camera ) => nodeObject( new ParallaxBarrierPassNode( scene, camera ) ); diff --git a/examples/jsm/tsl/display/PixelationPassNode.js b/examples/jsm/tsl/display/PixelationPassNode.js new file mode 100644 index 00000000000000..da8e8b60fe8c21 --- /dev/null +++ b/examples/jsm/tsl/display/PixelationPassNode.js @@ -0,0 +1,334 @@ +import { NearestFilter, Vector4, TempNode, NodeUpdateType, PassNode } from 'three/webgpu'; +import { nodeObject, Fn, float, uv, uniform, convertToTexture, vec2, vec3, clamp, floor, dot, smoothstep, If, sign, step, mrt, output, normalView, property } from 'three/tsl'; + +/** + * A inner node definition that implements the actual pixelation TSL code. + * + * @inner + * @augments TempNode + */ +class PixelationNode extends TempNode { + + static get type() { + + return 'PixelationNode'; + + } + + /** + * Constructs a new pixelation node. + * + * @param {TextureNode} textureNode - The texture node that represents the beauty pass. + * @param {TextureNode} depthNode - The texture that represents the beauty's depth. + * @param {TextureNode} normalNode - The texture that represents the beauty's normals. + * @param {Node} pixelSize - The pixel size. + * @param {Node} normalEdgeStrength - The normal edge strength. + * @param {Node} depthEdgeStrength - The depth edge strength. + */ + constructor( textureNode, depthNode, normalNode, pixelSize, normalEdgeStrength, depthEdgeStrength ) { + + super( 'vec4' ); + + /** + * The texture node that represents the beauty pass. + * + * @type {TextureNode} + */ + this.textureNode = textureNode; + + /** + * The texture that represents the beauty's depth. + * + * @type {TextureNode} + */ + this.depthNode = depthNode; + + /** + * The texture that represents the beauty's normals. + * + * @type {TextureNode} + */ + this.normalNode = normalNode; + + /** + * The pixel size. + * + * @type {Node} + */ + this.pixelSize = pixelSize; + + /** + * The pixel size. + * + * @type {Node} + */ + this.normalEdgeStrength = normalEdgeStrength; + + /** + * The depth edge strength. + * + * @type {Node} + */ + this.depthEdgeStrength = depthEdgeStrength; + + /** + * Uniform node that represents the resolution. + * + * @type {Node} + */ + this._resolution = uniform( new Vector4() ); + + /** + * The `updateBeforeType` is set to `NodeUpdateType.FRAME` since the node updates + * its internal uniforms once per frame in `updateBefore()`. + * + * @type {string} + * @default 'frame' + */ + this.updateBeforeType = NodeUpdateType.FRAME; + + } + + /** + * This method is used to update uniforms once per frame. + * + * @param {NodeFrame} frame - The current node frame. + */ + updateBefore() { + + const map = this.textureNode.value; + + const width = map.image.width; + const height = map.image.height; + + this._resolution.value.set( width, height, 1 / width, 1 / height ); + + } + + /** + * This method is used to setup the effect's TSL code. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {ShaderCallNodeInternal} + */ + setup() { + + const { textureNode, depthNode, normalNode } = this; + + const uvNodeTexture = textureNode.uvNode || uv(); + const uvNodeDepth = depthNode.uvNode || uv(); + const uvNodeNormal = normalNode.uvNode || uv(); + + const sampleTexture = () => textureNode.sample( uvNodeTexture ); + + const sampleDepth = ( x, y ) => depthNode.sample( uvNodeDepth.add( vec2( x, y ).mul( this._resolution.zw ) ) ).r; + + const sampleNormal = ( x, y ) => normalNode.sample( uvNodeNormal.add( vec2( x, y ).mul( this._resolution.zw ) ) ).rgb.normalize(); + + const depthEdgeIndicator = ( depth ) => { + + const diff = property( 'float', 'diff' ); + diff.addAssign( clamp( sampleDepth( 1, 0 ).sub( depth ) ) ); + diff.addAssign( clamp( sampleDepth( - 1, 0 ).sub( depth ) ) ); + diff.addAssign( clamp( sampleDepth( 0, 1 ).sub( depth ) ) ); + diff.addAssign( clamp( sampleDepth( 0, - 1 ).sub( depth ) ) ); + + return floor( smoothstep( 0.01, 0.02, diff ).mul( 2 ) ).div( 2 ); + + }; + + const neighborNormalEdgeIndicator = ( x, y, depth, normal ) => { + + const depthDiff = sampleDepth( x, y ).sub( depth ); + const neighborNormal = sampleNormal( x, y ); + + // Edge pixels should yield to faces who's normals are closer to the bias normal. + + const normalEdgeBias = vec3( 1, 1, 1 ); // This should probably be a parameter. + const normalDiff = dot( normal.sub( neighborNormal ), normalEdgeBias ); + const normalIndicator = clamp( smoothstep( - 0.01, 0.01, normalDiff ), 0.0, 1.0 ); + + // Only the shallower pixel should detect the normal edge. + + const depthIndicator = clamp( sign( depthDiff.mul( .25 ).add( .0025 ) ), 0.0, 1.0 ); + + return float( 1.0 ).sub( dot( normal, neighborNormal ) ).mul( depthIndicator ).mul( normalIndicator ); + + }; + + const normalEdgeIndicator = ( depth, normal ) => { + + const indicator = property( 'float', 'indicator' ); + + indicator.addAssign( neighborNormalEdgeIndicator( 0, - 1, depth, normal ) ); + indicator.addAssign( neighborNormalEdgeIndicator( 0, 1, depth, normal ) ); + indicator.addAssign( neighborNormalEdgeIndicator( - 1, 0, depth, normal ) ); + indicator.addAssign( neighborNormalEdgeIndicator( 1, 0, depth, normal ) ); + + return step( 0.1, indicator ); + + }; + + const pixelation = Fn( () => { + + const texel = sampleTexture(); + + const depth = property( 'float', 'depth' ); + const normal = property( 'vec3', 'normal' ); + + If( this.depthEdgeStrength.greaterThan( 0.0 ).or( this.normalEdgeStrength.greaterThan( 0.0 ) ), () => { + + depth.assign( sampleDepth( 0, 0 ) ); + normal.assign( sampleNormal( 0, 0 ) ); + + } ); + + const dei = property( 'float', 'dei' ); + + If( this.depthEdgeStrength.greaterThan( 0.0 ), () => { + + dei.assign( depthEdgeIndicator( depth ) ); + + } ); + + const nei = property( 'float', 'nei' ); + + If( this.normalEdgeStrength.greaterThan( 0.0 ), () => { + + nei.assign( normalEdgeIndicator( depth, normal ) ); + + } ); + + const strength = dei.greaterThan( 0 ).select( float( 1.0 ).sub( dei.mul( this.depthEdgeStrength ) ), nei.mul( this.normalEdgeStrength ).add( 1 ) ); + + return texel.mul( strength ); + + } ); + + const outputNode = pixelation(); + + return outputNode; + + } + +} + +const pixelation = ( node, depthNode, normalNode, pixelSize = 6, normalEdgeStrength = 0.3, depthEdgeStrength = 0.4 ) => nodeObject( new PixelationNode( convertToTexture( node ), convertToTexture( depthNode ), convertToTexture( normalNode ), nodeObject( pixelSize ), nodeObject( normalEdgeStrength ), nodeObject( depthEdgeStrength ) ) ); + +/** + * A special render pass node that renders the scene with a pixelation effect. + * + * @augments PassNode + * @three_import import { pixelationPass } from 'three/addons/tsl/display/PixelationPassNode.js'; + */ +class PixelationPassNode extends PassNode { + + static get type() { + + return 'PixelationPassNode'; + + } + + /** + * Constructs a new pixelation pass node. + * + * @param {Scene} scene - The scene to render. + * @param {Camera} camera - The camera to render the scene with. + * @param {Node | number} [pixelSize=6] - The pixel size. + * @param {Node | number} [normalEdgeStrength=0.3] - The normal edge strength. + * @param {Node | number} [depthEdgeStrength=0.4] - The depth edge strength. + */ + constructor( scene, camera, pixelSize = 6, normalEdgeStrength = 0.3, depthEdgeStrength = 0.4 ) { + + super( PassNode.COLOR, scene, camera, { minFilter: NearestFilter, magFilter: NearestFilter } ); + + /** + * The pixel size. + * + * @type {number} + * @default 6 + */ + this.pixelSize = pixelSize; + + /** + * The normal edge strength. + * + * @type {number} + * @default 0.3 + */ + this.normalEdgeStrength = normalEdgeStrength; + + /** + * The depth edge strength. + * + * @type {number} + * @default 0.4 + */ + this.depthEdgeStrength = depthEdgeStrength; + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isPixelationPassNode = true; + + this._mrt = mrt( { + output: output, + normal: normalView + } ); + + } + + /** + * Sets the size of the pass. + * + * @param {number} width - The width of the pass. + * @param {number} height - The height of the pass. + */ + setSize( width, height ) { + + const pixelSize = this.pixelSize.value ? this.pixelSize.value : this.pixelSize; + + const adjustedWidth = Math.floor( width / pixelSize ); + const adjustedHeight = Math.floor( height / pixelSize ); + + super.setSize( adjustedWidth, adjustedHeight ); + + } + + /** + * This method is used to setup the effect's TSL code. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {PixelationNode} + */ + setup() { + + const color = super.getTextureNode( 'output' ); + const depth = super.getTextureNode( 'depth' ); + const normal = super.getTextureNode( 'normal' ); + + return pixelation( color, depth, normal, this.pixelSize, this.normalEdgeStrength, this.depthEdgeStrength ); + + } + +} + +/** + * TSL function for creating a pixelation render pass node for post processing. + * + * @tsl + * @function + * @param {Scene} scene - The scene to render. + * @param {Camera} camera - The camera to render the scene with. + * @param {Node | number} [pixelSize=6] - The pixel size. + * @param {Node | number} [normalEdgeStrength=0.3] - The normal edge strength. + * @param {Node | number} [depthEdgeStrength=0.4] - The depth edge strength. + * @returns {PixelationPassNode} + */ +export const pixelationPass = ( scene, camera, pixelSize, normalEdgeStrength, depthEdgeStrength ) => nodeObject( new PixelationPassNode( scene, camera, pixelSize, normalEdgeStrength, depthEdgeStrength ) ); + +export default PixelationPassNode; diff --git a/examples/jsm/tsl/display/RGBShiftNode.js b/examples/jsm/tsl/display/RGBShiftNode.js new file mode 100644 index 00000000000000..c615737c1ab41a --- /dev/null +++ b/examples/jsm/tsl/display/RGBShiftNode.js @@ -0,0 +1,96 @@ +import { TempNode } from 'three/webgpu'; +import { nodeObject, Fn, uv, uniform, vec2, sin, cos, vec4, convertToTexture } from 'three/tsl'; + +/** + * Post processing node for shifting/splitting RGB color channels. The effect + * separates color channels and offsets them from each other. + * + * @augments TempNode + * @three_import import { rgbShift } from 'three/addons/tsl/display/RGBShiftNode.js'; + */ +class RGBShiftNode extends TempNode { + + static get type() { + + return 'RGBShiftNode'; + + } + + /** + * Constructs a new RGB shift node. + * + * @param {TextureNode} textureNode - The texture node that represents the input of the effect. + * @param {number} [amount=0.005] - The amount of the RGB shift. + * @param {number} [angle=0] - Defines the orientation in which colors are shifted. + */ + constructor( textureNode, amount = 0.005, angle = 0 ) { + + super( 'vec4' ); + + /** + * The texture node that represents the input of the effect. + * + * @type {TextureNode} + */ + this.textureNode = textureNode; + + /** + * The amount of the RGB shift. + * + * @type {UniformNode} + */ + this.amount = uniform( amount ); + + /** + * Defines in which direction colors are shifted. + * + * @type {UniformNode} + */ + this.angle = uniform( angle ); + + } + + /** + * This method is used to setup the effect's TSL code. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {ShaderCallNodeInternal} + */ + setup( /* builder */ ) { + + const { textureNode } = this; + + const uvNode = textureNode.uvNode || uv(); + + const sampleTexture = ( uv ) => textureNode.sample( uv ); + + const rgbShift = Fn( () => { + + const offset = vec2( cos( this.angle ), sin( this.angle ) ).mul( this.amount ); + const cr = sampleTexture( uvNode.add( offset ) ); + const cga = sampleTexture( uvNode ); + const cb = sampleTexture( uvNode.sub( offset ) ); + + return vec4( cr.r, cga.g, cb.b, cga.a ); + + } ); + + return rgbShift(); + + } + +} + +export default RGBShiftNode; + +/** + * TSL function for creating a RGB shift or split effect for post processing. + * + * @tsl + * @function + * @param {Node} node - The node that represents the input of the effect. + * @param {number} [amount=0.005] - The amount of the RGB shift. + * @param {number} [angle=0] - Defines in which direction colors are shifted. + * @returns {RGBShiftNode} + */ +export const rgbShift = ( node, amount, angle ) => nodeObject( new RGBShiftNode( convertToTexture( node ), amount, angle ) ); diff --git a/examples/jsm/tsl/display/SMAANode.js b/examples/jsm/tsl/display/SMAANode.js new file mode 100644 index 00000000000000..bf907e1f0f7a17 --- /dev/null +++ b/examples/jsm/tsl/display/SMAANode.js @@ -0,0 +1,768 @@ +import { HalfFloatType, LinearFilter, NearestFilter, RenderTarget, Texture, Vector2, QuadMesh, NodeMaterial, TempNode, RendererUtils } from 'three/webgpu'; +import { abs, nodeObject, Fn, NodeUpdateType, uv, uniform, convertToTexture, varyingProperty, vec2, vec4, modelViewProjection, passTexture, max, step, dot, float, texture, If, Loop, int, Break, sqrt, sign, mix } from 'three/tsl'; + +const _quadMesh = /*@__PURE__*/ new QuadMesh(); +const _size = /*@__PURE__*/ new Vector2(); + +let _rendererState; + +/** + * Post processing node for applying SMAA. Unlike FXAA, this node + * should be applied before converting colors to sRGB. SMAA should produce + * better results than FXAA but is also more expensive to execute. + * + * Used Preset: SMAA 1x Medium (with color edge detection) + * Reference: {@link https://github.com/iryoku/smaa/releases/tag/v2.8}. + * + * @augments TempNode + * @three_import import { smaa } from 'three/addons/tsl/display/SMAANode.js'; + */ +class SMAANode extends TempNode { + + static get type() { + + return 'SMAANode'; + + } + + /** + * Constructs a new SMAA node. + * + * @param {TextureNode} textureNode - The texture node that represents the input of the effect. + */ + constructor( textureNode ) { + + super( 'vec4' ); + + /** + * The texture node that represents the input of the effect. + * + * @type {TextureNode} + */ + this.textureNode = textureNode; + + /** + * The `updateBeforeType` is set to `NodeUpdateType.FRAME` since the node renders + * its effect once per frame in `updateBefore()`. + * + * @type {string} + * @default 'frame' + */ + this.updateBeforeType = NodeUpdateType.FRAME; + + /** + * The render target used for the edges pass. + * + * @private + * @type {RenderTarget} + */ + this._renderTargetEdges = new RenderTarget( 1, 1, { depthBuffer: false, type: HalfFloatType } ); + this._renderTargetEdges.texture.name = 'SMAANode.edges'; + + /** + * The render target used for the weights pass. + * + * @private + * @type {RenderTarget} + */ + this._renderTargetWeights = new RenderTarget( 1, 1, { depthBuffer: false, type: HalfFloatType } ); + this._renderTargetWeights.texture.name = 'SMAANode.weights'; + + /** + * The render target used for the blend pass. + * + * @private + * @type {RenderTarget} + */ + this._renderTargetBlend = new RenderTarget( 1, 1, { depthBuffer: false, type: HalfFloatType } ); + this._renderTargetBlend.texture.name = 'SMAANode.blend'; + + // textures + + const scope = this; + + const areaTextureImage = new Image(); + areaTextureImage.src = this._getAreaTexture(); + areaTextureImage.onload = function () { + + // assigning data to HTMLImageElement.src is asynchronous (see #15162) + scope._areaTexture.needsUpdate = true; + + }; + + /** + * Represents the "area" texture used by the SMAA implementation. + * + * @private + * @type {RenderTarget} + */ + this._areaTexture = new Texture(); + this._areaTexture.name = 'SMAANode.area'; + this._areaTexture.image = areaTextureImage; + this._areaTexture.minFilter = LinearFilter; + this._areaTexture.generateMipmaps = false; + this._areaTexture.flipY = false; + + const searchTextureImage = new Image(); + searchTextureImage.src = this._getSearchTexture(); + searchTextureImage.onload = function () { + + // assigning data to HTMLImageElement.src is asynchronous (see #15162) + scope._searchTexture.needsUpdate = true; + + }; + + /** + * Represents the "search" texture used by the SMAA implementation. + * + * @private + * @type {RenderTarget} + */ + this._searchTexture = new Texture(); + this._searchTexture.name = 'SMAANode.search'; + this._searchTexture.image = searchTextureImage; + this._searchTexture.magFilter = NearestFilter; + this._searchTexture.minFilter = NearestFilter; + this._searchTexture.generateMipmaps = false; + this._searchTexture.flipY = false; + + /** + * A uniform node holding the inverse resolution value. + * + * @private + * @type {UniformNode} + */ + this._invSize = uniform( new Vector2() ); + + /** + * A uniform texture node holding the area texture. + * + * @private + * @type {TextureNode} + */ + this._areaTextureUniform = texture( this._areaTexture ); + + /** + * A uniform texture node holding the search texture. + * + * @private + * @type {TextureNode} + */ + this._searchTextureUniform = texture( this._searchTexture ); + + /** + * A uniform texture node representing the edges pass. + * + * @private + * @type {TextureNode} + */ + this._edgesTextureUniform = texture( this._renderTargetEdges.texture ); + + /** + * A uniform texture node representing the weights pass. + * + * @private + * @type {TextureNode} + */ + this._weightsTextureUniform = texture( this._renderTargetWeights.texture ); + + /** + * The node material that holds the TSL for rendering the edges pass. + * + * @private + * @type {NodeMaterial} + */ + this._materialEdges = new NodeMaterial(); + this._materialEdges.name = 'SMAANode.edges'; + + /** + * The node material that holds the TSL for rendering the weights pass. + * + * @private + * @type {NodeMaterial} + */ + this._materialWeights = new NodeMaterial(); + this._materialWeights.name = 'SMAANode.weights'; + + /** + * The node material that holds the TSL for rendering the blend pass. + * + * @private + * @type {NodeMaterial} + */ + this._materialBlend = new NodeMaterial(); + this._materialBlend.name = 'SMAANode.blend'; + + /** + * The result of the effect is represented as a separate texture node. + * + * @private + * @type {PassTextureNode} + */ + this._textureNode = passTexture( this, this._renderTargetBlend.texture ); + + } + + /** + * Returns the result of the effect as a texture node. + * + * @return {PassTextureNode} A texture node that represents the result of the effect. + */ + getTextureNode() { + + return this._textureNode; + + } + + /** + * Sets the size of the effect. + * + * @param {number} width - The width of the effect. + * @param {number} height - The height of the effect. + */ + setSize( width, height ) { + + this._invSize.value.set( 1 / width, 1 / height ); + + this._renderTargetEdges.setSize( width, height ); + this._renderTargetWeights.setSize( width, height ); + this._renderTargetBlend.setSize( width, height ); + + } + + /** + * This method is used to render the effect once per frame. + * + * @param {NodeFrame} frame - The current node frame. + */ + updateBefore( frame ) { + + const { renderer } = frame; + + _rendererState = RendererUtils.resetRendererState( renderer, _rendererState ); + + // + + const size = renderer.getDrawingBufferSize( _size ); + this.setSize( size.width, size.height ); + + // edges + + renderer.setRenderTarget( this._renderTargetEdges ); + + _quadMesh.material = this._materialEdges; + _quadMesh.render( renderer ); + + // weights + + renderer.setRenderTarget( this._renderTargetWeights ); + + _quadMesh.material = this._materialWeights; + _quadMesh.render( renderer ); + + // blend + + renderer.setRenderTarget( this._renderTargetBlend ); + + _quadMesh.material = this._materialBlend; + _quadMesh.render( renderer ); + + // restore + + RendererUtils.restoreRendererState( renderer, _rendererState ); + + } + + /** + * This method is used to setup the effect's TSL code. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {PassTextureNode} + */ + setup( builder ) { + + const SMAA_THRESHOLD = 0.1; + const SMAA_MAX_SEARCH_STEPS = 8; + const SMAA_AREATEX_MAX_DISTANCE = 16; + const SMAA_AREATEX_PIXEL_SIZE = vec2( 1 / 160, 1 / 560 ); + const SMAA_AREATEX_SUBTEX_SIZE = ( 1 / 7 ); + + const textureNode = this.textureNode; + const uvNode = textureNode.uvNode || uv(); + + // edges + + const SMAAEdgeDetectionVS = Fn( () => { + + const vOffset0 = vec4( uvNode.xy, uvNode.xy ).add( vec4( this._invSize.xy, this._invSize.xy ).mul( vec4( - 1.0, 0.0, 0.0, - 1.0 ) ) ); + const vOffset1 = vec4( uvNode.xy, uvNode.xy ).add( vec4( this._invSize.xy, this._invSize.xy ).mul( vec4( 1.0, 0.0, 0.0, 1.0 ) ) ); + const vOffset2 = vec4( uvNode.xy, uvNode.xy ).add( vec4( this._invSize.xy, this._invSize.xy ).mul( vec4( - 2.0, 0.0, 0.0, - 2.0 ) ) ); + + varyingProperty( 'vec4', 'vOffset0' ).assign( vOffset0 ); + varyingProperty( 'vec4', 'vOffset1' ).assign( vOffset1 ); + varyingProperty( 'vec4', 'vOffset2' ).assign( vOffset2 ); + + return modelViewProjection; + + } ); + + const SMAAEdgeDetectionFS = Fn( () => { + + const vOffset0 = varyingProperty( 'vec4', 'vOffset0' ); + const vOffset1 = varyingProperty( 'vec4', 'vOffset1' ); + const vOffset2 = varyingProperty( 'vec4', 'vOffset2' ); + + const threshold = vec2( SMAA_THRESHOLD, SMAA_THRESHOLD ); + + // Calculate color deltas: + const delta = vec4().toVar(); + const C = this.textureNode.sample( uvNode ).rgb.toVar(); + + // Calculate left and top deltas: + const Cleft = this.textureNode.sample( vOffset0.xy ).rgb.toVar(); + let t = abs( C.sub( Cleft ) ); + delta.x = max( t.r, t.g, t.b ); + + const Ctop = this.textureNode.sample( vOffset0.zw ).rgb.toVar(); + t = abs( C.sub( Ctop ) ); + delta.y = max( t.r, t.g, t.b ); + + // We do the usual threshold: + const edges = step( threshold, delta.xy ).toVar(); + + // Then discard if there is no edge: + dot( edges, vec2( 1.0, 1.0 ) ).equal( 0 ).discard(); + + // Calculate right and bottom deltas: + const Cright = this.textureNode.sample( vOffset1.xy ).rgb.toVar(); + t = abs( C.sub( Cright ) ); + delta.z = max( t.r, t.g, t.b ); + + const Cbottom = this.textureNode.sample( vOffset1.zw ).rgb.toVar(); + t = abs( C.sub( Cbottom ) ); + delta.w = max( t.r, t.g, t.b ); + + // Calculate the maximum delta in the direct neighborhood: + let maxDelta = max( delta.x, delta.y, delta.z, delta.w ).toVar(); + + // Calculate left-left and top-top deltas: + const Cleftleft = this.textureNode.sample( vOffset2.xy ).rgb.toVar(); + t = abs( C.sub( Cleftleft ) ); + delta.z = max( t.r, t.g, t.b ); + + const Ctoptop = this.textureNode.sample( vOffset2.zw ).rgb.toVar(); + t = abs( C.sub( Ctoptop ) ); + delta.w = max( t.r, t.g, t.b ); + + // Calculate the final maximum delta: + maxDelta = max( maxDelta, delta.z, delta.w ); + + // Local contrast adaptation in action: + edges.xy.mulAssign( vec2( step( float( 0.5 ).mul( maxDelta ), delta.xy ) ) ); + + return vec4( edges, 0, 0 ); + + } ); + + // weights + + const SMAASearchLength = Fn( ( [ searchTex, e, bias, scale ] ) => { + + // Not required if searchTex accesses are set to point: + // float2 SEARCH_TEX_PIXEL_SIZE = 1.0 / float2(66.0, 33.0); + // e = float2(bias, 0.0) + 0.5 * SEARCH_TEX_PIXEL_SIZE + e * float2(scale, 1.0) * float2(64.0, 32.0) * SEARCH_TEX_PIXEL_SIZE; + const coord = vec2( e ).toVar(); + coord.r = bias.add( coord.r.mul( scale ) ); + return float( 255 ).mul( searchTex.sample( coord ) ).r; + + } ); + + const SMAAArea = Fn( ( [ areaTex, dist, e1, e2, offset ] ) => { + + // Rounding prevents precision errors of bilinear filtering: + let texcoord = float( SMAA_AREATEX_MAX_DISTANCE ).mul( float( 4 ).mul( vec2( e1, e2 ) ).round() ).add( dist ); + + // We do a scale and bias for mapping to texel space: + texcoord = SMAA_AREATEX_PIXEL_SIZE.mul( texcoord ).add( float( 0.5 ).mul( SMAA_AREATEX_PIXEL_SIZE ) ); + + // Move to proper place, according to the subpixel offset: + texcoord.y.addAssign( float( SMAA_AREATEX_SUBTEX_SIZE ).mul( offset ) ); + + return areaTex.sample( texcoord ).rg; + + } ); + + const SMAASearchXLeft = Fn( ( [ edgesTex, searchTex, texcoord, end ] ) => { + + /** + * @PSEUDO_GATHER4 + * This texcoord has been offset by (-0.25, -0.125) in the vertex shader to + * sample between edge, thus fetching four edges in a row. + * Sampling with different offsets in each direction allows to disambiguate + * which edges are active from the four fetched ones. + */ + + const e = vec2( 0.0, 1.0 ).toVar(); + const coord = vec2( texcoord ).toVar(); + + Loop( { start: int( 0 ), end: int( SMAA_MAX_SEARCH_STEPS ), type: 'int', condition: '<' }, () => { // port note: Changed while to for + + e.assign( edgesTex.sample( coord ).rg ); + coord.subAssign( vec2( 2, 0 ).mul( this._invSize ) ); + + If( coord.x.lessThanEqual( end ).or( e.g.lessThanEqual( float( 0.8281 ) ).or( e.r.notEqual( float( 0 ) ) ) ), () => { + + Break(); + + } ); + + } ); + + // We correct the previous (-0.25, -0.125) offset we applied: + coord.x.addAssign( float( 0.25 ).mul( this._invSize.x ) ); + + // The searches are bias by 1, so adjust the coords accordingly: + coord.x.addAssign( this._invSize.x ); + + // Disambiguate the length added by the last step: + coord.x.addAssign( float( 2 ).mul( this._invSize.x ) ); + coord.x.subAssign( this._invSize.x.mul( SMAASearchLength( searchTex, e, 0, 0.5 ) ) ); + + return coord.x; + + } ); + + const SMAASearchXRight = Fn( ( [ edgesTex, searchTex, texcoord, end ] ) => { + + const e = vec2( 0.0, 1.0 ).toVar(); + const coord = vec2( texcoord ).toVar(); + + Loop( { start: int( 0 ), end: int( SMAA_MAX_SEARCH_STEPS ), type: 'int', condition: '<' }, () => { // port note: Changed while to for + + e.assign( edgesTex.sample( coord ).rg ); + coord.addAssign( vec2( 2, 0 ).mul( this._invSize ) ); + + If( coord.x.greaterThanEqual( end ).or( e.g.lessThanEqual( float( 0.8281 ) ).or( e.r.notEqual( float( 0 ) ) ) ), () => { + + Break(); + + } ); + + } ); + + coord.x.subAssign( float( 0.25 ).mul( this._invSize.x ) ); + coord.x.subAssign( this._invSize.x ); + coord.x.subAssign( float( 2 ).mul( this._invSize.x ) ); + coord.x.addAssign( this._invSize.x.mul( SMAASearchLength( searchTex, e, 0.5, 0.5 ) ) ); + + return coord.x; + + } ); + + const SMAASearchYUp = Fn( ( [ edgesTex, searchTex, texcoord, end ] ) => { + + const e = vec2( 1.0, 0.0 ).toVar(); + const coord = vec2( texcoord ).toVar(); + + Loop( { start: int( 0 ), end: int( SMAA_MAX_SEARCH_STEPS ), type: 'int', condition: '<' }, () => { // port note: Changed while to for + + e.assign( edgesTex.sample( coord ).rg ); + coord.addAssign( vec2( 0, - 2 ).mul( this._invSize ) ); + + If( coord.y.lessThanEqual( end ).or( e.r.lessThanEqual( float( 0.8281 ) ).or( e.g.notEqual( float( 0 ) ) ) ), () => { + + Break(); + + } ); + + } ); + + coord.y.addAssign( float( 0.25 ).mul( this._invSize.y ) ); + coord.y.addAssign( this._invSize.y ); + coord.y.addAssign( float( 2 ).mul( this._invSize.y ) ); + coord.y.subAssign( this._invSize.y.mul( SMAASearchLength( searchTex, e.gr, 0, 0.5 ) ) ); + + return coord.y; + + } ); + + const SMAASearchYDown = Fn( ( [ edgesTex, searchTex, texcoord, end ] ) => { + + const e = vec2( 1.0, 0.0 ).toVar(); + const coord = vec2( texcoord ).toVar(); + + Loop( { start: int( 0 ), end: int( SMAA_MAX_SEARCH_STEPS ), type: 'int', condition: '<' }, () => { // port note: Changed while to for + + e.assign( edgesTex.sample( coord ).rg ); + coord.subAssign( vec2( 0, - 2 ).mul( this._invSize ) ); + + If( coord.y.greaterThanEqual( end ).or( e.r.lessThanEqual( float( 0.8281 ) ).or( e.g.notEqual( float( 0 ) ) ) ), () => { + + Break(); + + } ); + + } ); + + coord.y.subAssign( float( 0.25 ).mul( this._invSize.y ) ); + coord.y.subAssign( this._invSize.y ); + coord.y.subAssign( float( 2 ).mul( this._invSize.y ) ); + coord.y.addAssign( this._invSize.y.mul( SMAASearchLength( searchTex, e.gr, 0.5, 0.5 ) ) ); + + return coord.y; + + } ); + + const SMAAWeightsVS = Fn( () => { + + const vPixcoord = uvNode.xy.div( this._invSize ); + + // We will use these offsets for the searches later on (see @PSEUDO_GATHER4): + const vOffset0 = vec4( uvNode.xy, uvNode.xy ).add( vec4( this._invSize.xy, this._invSize.xy ).mul( vec4( - 0.25, - 0.125, 1.25, - 0.125 ) ) ).toVar(); + const vOffset1 = vec4( uvNode.xy, uvNode.xy ).add( vec4( this._invSize.xy, this._invSize.xy ).mul( vec4( - 0.125, - 0.25, - 0.125, 1.25 ) ) ).toVar(); + + // And these for the searches, they indicate the ends of the loops: + const vOffset2 = vec4( vOffset0.xz, vOffset1.yw ).add( vec4( - 2.0, 2.0, - 2.0, 2.0 ).mul( vec4( this._invSize.xx, this._invSize.yy ) ).mul( float( SMAA_MAX_SEARCH_STEPS ) ) ).toVar(); + + varyingProperty( 'vec2', 'vPixcoord' ).assign( vPixcoord ); + varyingProperty( 'vec4', 'vOffset0' ).assign( vOffset0 ); + varyingProperty( 'vec4', 'vOffset1' ).assign( vOffset1 ); + varyingProperty( 'vec4', 'vOffset2' ).assign( vOffset2 ); + + return modelViewProjection; + + } ); + + const SMAAWeightsFS = Fn( () => { + + const vPixcoord = varyingProperty( 'vec2', 'vPixcoord' ); + const vOffset0 = varyingProperty( 'vec4', 'vOffset0' ); + const vOffset1 = varyingProperty( 'vec4', 'vOffset1' ); + const vOffset2 = varyingProperty( 'vec4', 'vOffset2' ); + + const weights = vec4( 0.0, 0.0, 0.0, 0.0 ).toVar(); + const subsampleIndices = vec4( 0.0, 0.0, 0.0, 0.0 ).toVar(); + + const e = this._edgesTextureUniform.sample( uvNode ).rg.toVar(); + + If( e.g.greaterThan( float( 0 ) ), () => { // Edge at north + + let d = vec2().toVar(); + + // Find the distance to the left: + + const coordsLeft = vec2().toVar(); + coordsLeft.x = SMAASearchXLeft( this._edgesTextureUniform, this._searchTextureUniform, vOffset0.xy, vOffset2.x ); + coordsLeft.y = vOffset1.y; // offset[1].y = texcoord.y - 0.25 * resolution.y (@CROSSING_OFFSET) + d.x = coordsLeft.x; + + // Now fetch the left crossing edges, two at a time using bilinear + // filtering. Sampling at -0.25 (see @CROSSING_OFFSET) enables to + // discern what value each edge has: + const e1 = this._edgesTextureUniform.sample( coordsLeft ).r.toVar(); + + // Find the distance to the right: + const coordsRight = vec2().toVar(); + coordsRight.x = SMAASearchXRight( this._edgesTextureUniform, this._searchTextureUniform, vOffset0.zw, vOffset2.y ); + coordsRight.y = vOffset1.y; + d.y = coordsRight.x; + + // We want the distances to be in pixel units (doing this here allow to + // better interleave arithmetic and memory accesses): + d = d.div( this._invSize.x ).sub( vPixcoord.x ); + + // SMAAArea below needs a sqrt, as the areas texture is compressed quadratically: + const sqrt_d = sqrt( abs( d ) ); + + // Fetch the right crossing edges: + const e2 = this._edgesTextureUniform.sample( coordsRight.add( vec2( 1, 0 ).mul( this._invSize ) ) ).r.toVar(); + weights.r = e2; + + // Get the area for this direction: + weights.rg = SMAAArea( this._areaTextureUniform, sqrt_d, e1, e2, float( subsampleIndices.y ) ); + + } ); + + If( e.r.greaterThan( float( 0 ) ), () => { // Edge at west + + let d = vec2().toVar(); + + // Find the distance to the top: + + const coordsUp = vec2().toVar(); + coordsUp.y = SMAASearchYUp( this._edgesTextureUniform, this._searchTextureUniform, vOffset1.xy, vOffset2.z ); + coordsUp.x = vOffset0.x; // offset[1].x = texcoord.x - 0.25 * resolution.x; + d.x = coordsUp.y; + + // Fetch the top crossing edges: + const e1 = this._edgesTextureUniform.sample( coordsUp ).g.toVar(); + + // Find the distance to the bottom: + const coordsDown = vec2().toVar(); + coordsDown.y = SMAASearchYDown( this._edgesTextureUniform, this._searchTextureUniform, vOffset1.zw, vOffset2.w ); + coordsDown.x = vOffset0.x; + d.y = coordsDown.y; + + // We want the distances to be in pixel units: + d = d.div( this._invSize.y ).sub( vPixcoord.y ); + + // SMAAArea below needs a sqrt, as the areas texture is compressed quadratically: + const sqrt_d = sqrt( abs( d ) ); + + // Fetch the bottom crossing edges: + const e2 = this._edgesTextureUniform.sample( coordsDown.add( vec2( 0, 1 ).mul( this._invSize ) ) ).g.toVar(); + + // Get the area for this direction: + weights.ba = SMAAArea( this._areaTextureUniform, sqrt_d, e1, e2, float( subsampleIndices.x ) ); + + } ); + + return weights; + + } ); + + // blend + + const SMAABlendVS = Fn( () => { + + //const vOffset0 = vec4( uvNode.xy, uvNode.xy ).add( vec4( this._invSize.xy, this._invSize.xy ).mul( vec4( - 1.0, 0.0, 0.0, - 1.0 ) ) ); + const vOffset1 = vec4( uvNode.xy, uvNode.xy ).add( vec4( this._invSize.xy, this._invSize.xy ).mul( vec4( 1.0, 0.0, 0.0, 1.0 ) ) ); + + //varyingProperty( 'vec4', 'vOffset0' ).assign( vOffset0 ); + varyingProperty( 'vec4', 'vOffset1' ).assign( vOffset1 ); + + return modelViewProjection; + + } ); + + const SMAABlendFS = Fn( () => { + + //const vOffset0 = varyingProperty( 'vec4', 'vOffset0' ); + const vOffset1 = varyingProperty( 'vec4', 'vOffset1' ); + const result = vec4().toVar(); + + // Fetch the blending weights for current pixel: + + const a = vec4().toVar(); + a.xz = this._weightsTextureUniform.sample( uvNode ).xz; + a.y = this._weightsTextureUniform.sample( vOffset1.zw ).g; + a.w = this._weightsTextureUniform.sample( vOffset1.xy ).a; + + // Is there any blending weight with a value greater than 0.0? + + If( dot( a, vec4( 1.0 ) ).lessThan( 1e-5 ), () => { // Edge at north + + result.assign( this.textureNode.sample( uvNode ) ); + + } ).Else( () => { + + // Up to 4 lines can be crossing a pixel (one through each edge). We + // favor blending by choosing the line with the maximum weight for each + // direction: + + const offset = vec2().toVar(); + + offset.x = a.a.greaterThan( a.b ).select( a.a, a.b.negate() ); // left vs. right + offset.y = a.g.greaterThan( a.r ).select( a.g, a.r.negate() ); // top vs. bottom + + // Then we go in the direction that has the maximum weight: + + If( abs( offset.x ).greaterThan( abs( offset.y ) ), () => { // horizontal vs. vertical + + offset.y.assign( 0 ); + + } ).Else( () => { + + offset.x.assign( 0 ); + + } ); + + // Fetch the opposite color and lerp by hand: + + const C = this.textureNode.sample( uvNode ).toVar(); + const texcoord = vec2( uvNode ).toVar(); + texcoord.addAssign( sign( offset ).mul( this._invSize ) ); + const Cop = this.textureNode.sample( texcoord ).toVar(); + const s = abs( offset.x ).greaterThan( abs( offset.y ) ).select( abs( offset.x ), abs( offset.y ) ).toVar(); + + const mixed = mix( C, Cop, s ); + result.assign( mixed ); + + } ); + + return result; + + } ); + + this._materialEdges.vertexNode = SMAAEdgeDetectionVS().context( builder.getSharedContext() ); + this._materialEdges.fragmentNode = SMAAEdgeDetectionFS().context( builder.getSharedContext() ); + this._materialEdges.needsUpdate = true; + + this._materialWeights.vertexNode = SMAAWeightsVS().context( builder.getSharedContext() ); + this._materialWeights.fragmentNode = SMAAWeightsFS().context( builder.getSharedContext() ); + this._materialWeights.needsUpdate = true; + + this._materialBlend.vertexNode = SMAABlendVS().context( builder.getSharedContext() ); + this._materialBlend.fragmentNode = SMAABlendFS().context( builder.getSharedContext() ); + this._materialBlend.needsUpdate = true; + + return this._textureNode; + + } + + /** + * Frees internal resources. This method should be called + * when the effect is no longer required. + */ + dispose() { + + this._renderTargetEdges.dispose(); + this._renderTargetWeights.dispose(); + this._renderTargetBlend.dispose(); + + this._areaTexture.dispose(); + this._searchTexture.dispose(); + + this._materialEdges.dispose(); + this._materialWeights.dispose(); + this._materialBlend.dispose(); + + } + + /** + * Returns the area texture as a Base64 string. + * + * @private + * @return {string} The area texture. + */ + _getAreaTexture() { + + return 'data:image/png;base64,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'; + + } + + /** + * Returns the search texture as a Base64 string.. + * + * @private + * @return {string} The search texture. + */ + _getSearchTexture() { + + return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEIAAAAhCAAAAABIXyLAAAAAOElEQVRIx2NgGAWjYBSMglEwEICREYRgFBZBqDCSLA2MGPUIVQETE9iNUAqLR5gIeoQKRgwXjwAAGn4AtaFeYLEAAAAASUVORK5CYII='; + + } + +} + +export default SMAANode; + +/** + * TSL function for creating a SMAA node for anti-aliasing via post processing. + * + * @tsl + * @function + * @param {Node} node - The node that represents the input of the effect. + * @returns {SMAANode} + */ +export const smaa = ( node ) => nodeObject( new SMAANode( convertToTexture( node ) ) ); diff --git a/examples/jsm/tsl/display/SSAAPassNode.js b/examples/jsm/tsl/display/SSAAPassNode.js new file mode 100644 index 00000000000000..88d2426b473b20 --- /dev/null +++ b/examples/jsm/tsl/display/SSAAPassNode.js @@ -0,0 +1,358 @@ +import { AdditiveBlending, Color, Vector2, RendererUtils, PassNode, QuadMesh, NodeMaterial } from 'three/webgpu'; +import { nodeObject, uniform, mrt, texture, getTextureIndex, unpremultiplyAlpha } from 'three/tsl'; + +const _size = /*@__PURE__*/ new Vector2(); + +let _rendererState; + +/** + * A special render pass node that renders the scene with SSAA (Supersampling Anti-Aliasing). + * This manual SSAA approach re-renders the scene ones for each sample with camera jitter and accumulates the results. + * + * This node produces a high-quality anti-aliased output but is also extremely expensive because of + * its brute-force approach of re-rendering the entire scene multiple times. + * + * Reference: {@link https://en.wikipedia.org/wiki/Supersampling} + * + * @augments PassNode + * @three_import import { ssaaPass } from 'three/addons/tsl/display/SSAAPassNode.js'; + */ +class SSAAPassNode extends PassNode { + + static get type() { + + return 'SSAAPassNode'; + + } + + /** + * Constructs a new SSAA pass node. + * + * @param {Scene} scene - The scene to render. + * @param {Camera} camera - The camera to render the scene with. + */ + constructor( scene, camera ) { + + super( PassNode.COLOR, scene, camera ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isSSAAPassNode = true; + + /** + * The sample level specified as n, where the number of samples is 2^n, + * so sampleLevel = 4, is 2^4 samples, 16. + * + * @type {number} + * @default 4 + */ + this.sampleLevel = 4; + + /** + * Whether rounding errors should be mitigated or not. + * + * @type {boolean} + * @default true + */ + this.unbiased = true; + + /** + * The clear color of the pass. + * + * @type {Color} + * @default 0x000000 + */ + this.clearColor = new Color( 0x000000 ); + + /** + * The clear alpha of the pass. + * + * @type {number} + * @default 0 + */ + this.clearAlpha = 0; + + /** + * A uniform node representing the sample weight. + * + * @type {UniformNode} + * @default 1 + */ + this.sampleWeight = uniform( 1 ); + + /** + * Reference to the internal render target that holds the current sample. + * + * @private + * @type {?RenderTarget} + * @default null + */ + this._sampleRenderTarget = null; + + /** + * Reference to the internal quad mesh. + * + * @private + * @type {QuadMesh} + */ + this._quadMesh = new QuadMesh(); + + } + + /** + * This method is used to render the SSAA effect once per frame. + * + * @param {NodeFrame} frame - The current node frame. + */ + updateBefore( frame ) { + + const { renderer } = frame; + const { scene, camera } = this; + + _rendererState = RendererUtils.resetRendererState( renderer, _rendererState ); + + // + + this._pixelRatio = renderer.getPixelRatio(); + + const size = renderer.getSize( _size ); + + this.setSize( size.width, size.height ); + this._sampleRenderTarget.setSize( this.renderTarget.width, this.renderTarget.height ); + + // + + this._cameraNear.value = camera.near; + this._cameraFar.value = camera.far; + + renderer.setMRT( this.getMRT() ); + renderer.autoClear = false; + + const jitterOffsets = _JitterVectors[ Math.max( 0, Math.min( this.sampleLevel, 5 ) ) ]; + + const baseSampleWeight = 1.0 / jitterOffsets.length; + const roundingRange = 1 / 32; + + const viewOffset = { + + fullWidth: this.renderTarget.width, + fullHeight: this.renderTarget.height, + offsetX: 0, + offsetY: 0, + width: this.renderTarget.width, + height: this.renderTarget.height + + }; + + const originalViewOffset = Object.assign( {}, camera.view ); + + if ( originalViewOffset.enabled ) Object.assign( viewOffset, originalViewOffset ); + + // render the scene multiple times, each slightly jitter offset from the last and accumulate the results. + + for ( let i = 0; i < jitterOffsets.length; i ++ ) { + + const jitterOffset = jitterOffsets[ i ]; + + if ( camera.setViewOffset ) { + + camera.setViewOffset( + + viewOffset.fullWidth, viewOffset.fullHeight, + + viewOffset.offsetX + jitterOffset[ 0 ] * 0.0625, viewOffset.offsetY + jitterOffset[ 1 ] * 0.0625, // 0.0625 = 1 / 16 + + viewOffset.width, viewOffset.height + + ); + + } + + this.sampleWeight.value = baseSampleWeight; + + if ( this.unbiased ) { + + // the theory is that equal weights for each sample lead to an accumulation of rounding errors. + // The following equation varies the sampleWeight per sample so that it is uniformly distributed + // across a range of values whose rounding errors cancel each other out. + + const uniformCenteredDistribution = ( - 0.5 + ( i + 0.5 ) / jitterOffsets.length ); + this.sampleWeight.value += roundingRange * uniformCenteredDistribution; + + } + + renderer.setClearColor( this.clearColor, this.clearAlpha ); + renderer.setRenderTarget( this._sampleRenderTarget ); + renderer.clear(); + renderer.render( scene, camera ); + + // accumulation + + renderer.setRenderTarget( this.renderTarget ); + + if ( i === 0 ) { + + renderer.setClearColor( 0x000000, 0.0 ); + renderer.clear(); + + } + + this._quadMesh.render( renderer ); + + } + + renderer.copyTextureToTexture( this._sampleRenderTarget.depthTexture, this.renderTarget.depthTexture ); + + // restore + + if ( camera.setViewOffset && originalViewOffset.enabled ) { + + camera.setViewOffset( + + originalViewOffset.fullWidth, originalViewOffset.fullHeight, + + originalViewOffset.offsetX, originalViewOffset.offsetY, + + originalViewOffset.width, originalViewOffset.height + + ); + + } else if ( camera.clearViewOffset ) { + + camera.clearViewOffset(); + + } + + // + + RendererUtils.restoreRendererState( renderer, _rendererState ); + + } + + /** + * This method is used to setup the effect's MRT configuration and quad mesh. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {PassTextureNode} + */ + setup( builder ) { + + if ( this._sampleRenderTarget === null ) { + + this._sampleRenderTarget = this.renderTarget.clone(); + + } + + let sampleTexture; + + const passMRT = this.getMRT(); + + if ( passMRT !== null ) { + + const outputs = {}; + + for ( const name in passMRT.outputNodes ) { + + const index = getTextureIndex( this._sampleRenderTarget.textures, name ); + + if ( index >= 0 ) { + + outputs[ name ] = texture( this._sampleRenderTarget.textures[ index ] ).mul( this.sampleWeight ); + + } + + } + + sampleTexture = mrt( outputs ); + + } else { + + sampleTexture = texture( this._sampleRenderTarget.texture ).mul( this.sampleWeight ); + + } + + this._quadMesh.material = new NodeMaterial(); + this._quadMesh.material.fragmentNode = unpremultiplyAlpha( sampleTexture ); + this._quadMesh.material.transparent = true; + this._quadMesh.material.depthTest = false; + this._quadMesh.material.depthWrite = false; + this._quadMesh.material.premultipliedAlpha = true; + this._quadMesh.material.blending = AdditiveBlending; + this._quadMesh.material.name = 'SSAA'; + + return super.setup( builder ); + + } + + /** + * Frees internal resources. This method should be called + * when the pass is no longer required. + */ + dispose() { + + super.dispose(); + + if ( this._sampleRenderTarget !== null ) { + + this._sampleRenderTarget.dispose(); + + } + + } + +} + +export default SSAAPassNode; + +// These jitter vectors are specified in integers because it is easier. +// I am assuming a [-8,8) integer grid, but it needs to be mapped onto [-0.5,0.5) +// before being used, thus these integers need to be scaled by 1/16. +// +// Sample patterns reference: https://msdn.microsoft.com/en-us/library/windows/desktop/ff476218%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396 +const _JitterVectors = [ + [ + [ 0, 0 ] + ], + [ + [ 4, 4 ], [ - 4, - 4 ] + ], + [ + [ - 2, - 6 ], [ 6, - 2 ], [ - 6, 2 ], [ 2, 6 ] + ], + [ + [ 1, - 3 ], [ - 1, 3 ], [ 5, 1 ], [ - 3, - 5 ], + [ - 5, 5 ], [ - 7, - 1 ], [ 3, 7 ], [ 7, - 7 ] + ], + [ + [ 1, 1 ], [ - 1, - 3 ], [ - 3, 2 ], [ 4, - 1 ], + [ - 5, - 2 ], [ 2, 5 ], [ 5, 3 ], [ 3, - 5 ], + [ - 2, 6 ], [ 0, - 7 ], [ - 4, - 6 ], [ - 6, 4 ], + [ - 8, 0 ], [ 7, - 4 ], [ 6, 7 ], [ - 7, - 8 ] + ], + [ + [ - 4, - 7 ], [ - 7, - 5 ], [ - 3, - 5 ], [ - 5, - 4 ], + [ - 1, - 4 ], [ - 2, - 2 ], [ - 6, - 1 ], [ - 4, 0 ], + [ - 7, 1 ], [ - 1, 2 ], [ - 6, 3 ], [ - 3, 3 ], + [ - 7, 6 ], [ - 3, 6 ], [ - 5, 7 ], [ - 1, 7 ], + [ 5, - 7 ], [ 1, - 6 ], [ 6, - 5 ], [ 4, - 4 ], + [ 2, - 3 ], [ 7, - 2 ], [ 1, - 1 ], [ 4, - 1 ], + [ 2, 1 ], [ 6, 2 ], [ 0, 4 ], [ 4, 4 ], + [ 2, 5 ], [ 7, 5 ], [ 5, 6 ], [ 3, 7 ] + ] +]; + +/** + * TSL function for creating a SSAA pass node for Supersampling Anti-Aliasing. + * + * @tsl + * @function + * @param {Scene} scene - The scene to render. + * @param {Camera} camera - The camera to render the scene with. + * @returns {SSAAPassNode} + */ +export const ssaaPass = ( scene, camera ) => nodeObject( new SSAAPassNode( scene, camera ) ); diff --git a/examples/jsm/tsl/display/SSRNode.js b/examples/jsm/tsl/display/SSRNode.js new file mode 100644 index 00000000000000..feb24047a5d211 --- /dev/null +++ b/examples/jsm/tsl/display/SSRNode.js @@ -0,0 +1,539 @@ +import { NearestFilter, RenderTarget, Vector2, RendererUtils, QuadMesh, TempNode, NodeMaterial, NodeUpdateType } from 'three/webgpu'; +import { reference, viewZToPerspectiveDepth, logarithmicDepthToViewZ, getScreenPosition, getViewPosition, sqrt, mul, div, cross, float, Continue, Break, Loop, int, max, abs, sub, If, dot, reflect, normalize, screenCoordinate, nodeObject, Fn, passTexture, uv, uniform, perspectiveDepthToViewZ, orthographicDepthToViewZ, vec2, vec3, vec4 } from 'three/tsl'; + +const _quadMesh = /*@__PURE__*/ new QuadMesh(); +const _size = /*@__PURE__*/ new Vector2(); +let _rendererState; + +/** + * Post processing node for computing screen space reflections (SSR). + * + * Reference: {@link https://lettier.github.io/3d-game-shaders-for-beginners/screen-space-reflection.html} + * + * @augments TempNode + * @three_import import { ssr } from 'three/addons/tsl/display/SSRNode.js'; + */ +class SSRNode extends TempNode { + + static get type() { + + return 'SSRNode'; + + } + + /** + * Constructs a new SSR node. + * + * @param {Node} colorNode - The node that represents the beauty pass. + * @param {Node} depthNode - A node that represents the beauty pass's depth. + * @param {Node} normalNode - A node that represents the beauty pass's normals. + * @param {Node} metalnessNode - A node that represents the beauty pass's metalness. + * @param {Camera} camera - The camera the scene is rendered with. + */ + constructor( colorNode, depthNode, normalNode, metalnessNode, camera ) { + + super( 'vec4' ); + + /** + * The node that represents the beauty pass. + * + * @type {Node} + */ + this.colorNode = colorNode; + + /** + * A node that represents the beauty pass's depth. + * + * @type {Node} + */ + this.depthNode = depthNode; + + /** + * A node that represents the beauty pass's normals. + * + * @type {Node} + */ + this.normalNode = normalNode; + + /** + * A node that represents the beauty pass's metalness. + * + * @type {Node} + */ + this.metalnessNode = metalnessNode; + + /** + * The camera the scene is rendered with. + * + * @type {Camera} + */ + this.camera = camera; + + /** + * The resolution scale. By default SSR reflections + * are computed in half resolutions. Setting the value + * to `1` improves quality but also results in more + * computational overhead. + * + * @type {number} + * @default 0.5 + */ + this.resolutionScale = 0.5; + + /** + * The `updateBeforeType` is set to `NodeUpdateType.FRAME` since the node renders + * its effect once per frame in `updateBefore()`. + * + * @type {string} + * @default 'frame' + */ + this.updateBeforeType = NodeUpdateType.FRAME; + + /** + * The render target the SSR is rendered into. + * + * @private + * @type {RenderTarget} + */ + this._ssrRenderTarget = new RenderTarget( 1, 1, { depthBuffer: false, minFilter: NearestFilter, magFilter: NearestFilter } ); + this._ssrRenderTarget.texture.name = 'SSRNode.SSR'; + + /** + * Controls how far a fragment can reflect. + * + * + * @type {UniformNode} + */ + this.maxDistance = uniform( 1 ); + + /** + * Controls the cutoff between what counts as a possible reflection hit and what does not. + * + * @type {UniformNode} + */ + this.thickness = uniform( 0.1 ); + + /** + * Controls the transparency of the reflected colors. + * + * @type {UniformNode} + */ + this.opacity = uniform( 1 ); + + /** + * Represents the projection matrix of the scene's camera. + * + * @private + * @type {UniformNode} + */ + this._cameraProjectionMatrix = uniform( camera.projectionMatrix ); + + /** + * Represents the inverse projection matrix of the scene's camera. + * + * @private + * @type {UniformNode} + */ + this._cameraProjectionMatrixInverse = uniform( camera.projectionMatrixInverse ); + + /** + * Represents the near value of the scene's camera. + * + * @private + * @type {ReferenceNode} + */ + this._cameraNear = reference( 'near', 'float', camera ); + + /** + * Represents the far value of the scene's camera. + * + * @private + * @type {ReferenceNode} + */ + this._cameraFar = reference( 'far', 'float', camera ); + + /** + * Whether the scene's camera is perspective or orthographic. + * + * @private + * @type {UniformNode} + */ + this._isPerspectiveCamera = uniform( camera.isPerspectiveCamera ? 1 : 0 ); + + /** + * The resolution of the pass. + * + * @private + * @type {UniformNode} + */ + this._resolution = uniform( new Vector2() ); + + /** + * This value is derived from the resolution and restricts + * the maximum raymarching steps in the fragment shader. + * + * @private + * @type {UniformNode} + */ + this._maxStep = uniform( 0 ); + + /** + * The material that is used to render the effect. + * + * @private + * @type {NodeMaterial} + */ + this._material = new NodeMaterial(); + this._material.name = 'SSRNode.SSR'; + + /** + * The result of the effect is represented as a separate texture node. + * + * @private + * @type {PassTextureNode} + */ + this._textureNode = passTexture( this, this._ssrRenderTarget.texture ); + + } + + /** + * Returns the result of the effect as a texture node. + * + * @return {PassTextureNode} A texture node that represents the result of the effect. + */ + getTextureNode() { + + return this._textureNode; + + } + + /** + * Sets the size of the effect. + * + * @param {number} width - The width of the effect. + * @param {number} height - The height of the effect. + */ + setSize( width, height ) { + + width = Math.round( this.resolutionScale * width ); + height = Math.round( this.resolutionScale * height ); + + this._resolution.value.set( width, height ); + this._maxStep.value = Math.round( Math.sqrt( width * width + height * height ) ); + + this._ssrRenderTarget.setSize( width, height ); + + } + + /** + * This method is used to render the effect once per frame. + * + * @param {NodeFrame} frame - The current node frame. + */ + updateBefore( frame ) { + + const { renderer } = frame; + + _rendererState = RendererUtils.resetRendererState( renderer, _rendererState ); + + const size = renderer.getDrawingBufferSize( _size ); + + _quadMesh.material = this._material; + + this.setSize( size.width, size.height ); + + // clear + + renderer.setMRT( null ); + renderer.setClearColor( 0x000000, 0 ); + + // ssr + + renderer.setRenderTarget( this._ssrRenderTarget ); + _quadMesh.render( renderer ); + + // restore + + RendererUtils.restoreRendererState( renderer, _rendererState ); + + } + + /** + * This method is used to setup the effect's TSL code. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {PassTextureNode} + */ + setup( builder ) { + + const uvNode = uv(); + + const pointToLineDistance = Fn( ( [ point, linePointA, linePointB ] )=> { + + // https://mathworld.wolfram.com/Point-LineDistance3-Dimensional.html + + return cross( point.sub( linePointA ), point.sub( linePointB ) ).length().div( linePointB.sub( linePointA ).length() ); + + } ); + + const pointPlaneDistance = Fn( ( [ point, planePoint, planeNormal ] )=> { + + // https://mathworld.wolfram.com/Point-PlaneDistance.html + // https://en.wikipedia.org/wiki/Plane_(geometry) + // http://paulbourke.net/geometry/pointlineplane/ + + const d = mul( planeNormal.x, planePoint.x ).add( mul( planeNormal.y, planePoint.y ) ).add( mul( planeNormal.z, planePoint.z ) ).negate().toVar(); + + const denominator = sqrt( mul( planeNormal.x, planeNormal.x, ).add( mul( planeNormal.y, planeNormal.y ) ).add( mul( planeNormal.z, planeNormal.z ) ) ).toVar(); + const distance = div( mul( planeNormal.x, point.x ).add( mul( planeNormal.y, point.y ) ).add( mul( planeNormal.z, point.z ) ).add( d ), denominator ); + return distance; + + } ); + + const getViewZ = Fn( ( [ depth ] ) => { + + let viewZNode; + + if ( this.camera.isPerspectiveCamera ) { + + viewZNode = perspectiveDepthToViewZ( depth, this._cameraNear, this._cameraFar ); + + } else { + + viewZNode = orthographicDepthToViewZ( depth, this._cameraNear, this._cameraFar ); + + } + + return viewZNode; + + } ); + + const sampleDepth = ( uv ) => { + + const depth = this.depthNode.sample( uv ).r; + + if ( builder.renderer.logarithmicDepthBuffer === true ) { + + const viewZ = logarithmicDepthToViewZ( depth, this._cameraNear, this._cameraFar ); + + return viewZToPerspectiveDepth( viewZ, this._cameraNear, this._cameraFar ); + + } + + return depth; + + }; + + const ssr = Fn( () => { + + const metalness = this.metalnessNode.sample( uvNode ).r; + + // fragments with no metalness do not reflect their environment + metalness.equal( 0.0 ).discard(); + + // compute some standard FX entities + const depth = sampleDepth( uvNode ).toVar(); + const viewPosition = getViewPosition( uvNode, depth, this._cameraProjectionMatrixInverse ).toVar(); + const viewNormal = this.normalNode.rgb.normalize().toVar(); + + // compute the direction from the position in view space to the camera + const viewIncidentDir = ( ( this.camera.isPerspectiveCamera ) ? normalize( viewPosition ) : vec3( 0, 0, - 1 ) ).toVar(); + + // compute the direction in which the light is reflected on the surface + const viewReflectDir = reflect( viewIncidentDir, viewNormal ).toVar(); + + // adapt maximum distance to the local geometry (see https://www.mathsisfun.com/algebra/vectors-dot-product.html) + const maxReflectRayLen = this.maxDistance.div( dot( viewIncidentDir.negate(), viewNormal ) ).toVar(); + + // compute the maximum point of the reflection ray in view space + const d1viewPosition = viewPosition.add( viewReflectDir.mul( maxReflectRayLen ) ).toVar(); + + // check if d1viewPosition lies behind the camera near plane + If( this._isPerspectiveCamera.equal( float( 1 ) ).and( d1viewPosition.z.greaterThan( this._cameraNear.negate() ) ), () => { + + // if so, ensure d1viewPosition is clamped on the near plane. + // this prevents artifacts during the ray marching process + const t = sub( this._cameraNear.negate(), viewPosition.z ).div( viewReflectDir.z ); + d1viewPosition.assign( viewPosition.add( viewReflectDir.mul( t ) ) ); + + } ); + + // d0 and d1 are the start and maximum points of the reflection ray in screen space + const d0 = screenCoordinate.xy.toVar(); + const d1 = getScreenPosition( d1viewPosition, this._cameraProjectionMatrix ).mul( this._resolution ).toVar(); + + // below variables are used to control the raymarching process + + // total length of the ray + const totalLen = d1.sub( d0 ).length().toVar(); + + // offset in x and y direction + const xLen = d1.x.sub( d0.x ).toVar(); + const yLen = d1.y.sub( d0.y ).toVar(); + + // determine the larger delta + // The larger difference will help to determine how much to travel in the X and Y direction each iteration and + // how many iterations are needed to travel the entire ray + const totalStep = max( abs( xLen ), abs( yLen ) ).toVar(); + + // step sizes in the x and y directions + const xSpan = xLen.div( totalStep ).toVar(); + const ySpan = yLen.div( totalStep ).toVar(); + + const output = vec4( 0 ).toVar(); + + // the actual ray marching loop + // starting from d0, the code gradually travels along the ray and looks for an intersection with the geometry. + // it does not exceed d1 (the maximum ray extend) + Loop( { start: int( 0 ), end: int( this._maxStep ), type: 'int', condition: '<' }, ( { i } ) => { + + // TODO: Remove this when Chrome is fixed, see https://issues.chromium.org/issues/372714384#comment14 + If( metalness.equal( 0 ), () => { + + Break(); + + } ); + + // stop if the maximum number of steps is reached for this specific ray + If( float( i ).greaterThanEqual( totalStep ), () => { + + Break(); + + } ); + + // advance on the ray by computing a new position in screen space + const xy = vec2( d0.x.add( xSpan.mul( float( i ) ) ), d0.y.add( ySpan.mul( float( i ) ) ) ).toVar(); + + // stop processing if the new position lies outside of the screen + If( xy.x.lessThan( 0 ).or( xy.x.greaterThan( this._resolution.x ) ).or( xy.y.lessThan( 0 ) ).or( xy.y.greaterThan( this._resolution.y ) ), () => { + + Break(); + + } ); + + // compute new uv, depth, viewZ and viewPosition for the new location on the ray + const uvNode = xy.div( this._resolution ); + const d = sampleDepth( uvNode ).toVar(); + const vZ = getViewZ( d ).toVar(); + const vP = getViewPosition( uvNode, d, this._cameraProjectionMatrixInverse ).toVar(); + + const viewReflectRayZ = float( 0 ).toVar(); + + // normalized distance between the current position xy and the starting point d0 + const s = xy.sub( d0 ).length().div( totalLen ); + + // depending on the camera type, we now compute the z-coordinate of the reflected ray at the current step in view space + If( this._isPerspectiveCamera.equal( float( 1 ) ), () => { + + const recipVPZ = float( 1 ).div( viewPosition.z ).toVar(); + viewReflectRayZ.assign( float( 1 ).div( recipVPZ.add( s.mul( float( 1 ).div( d1viewPosition.z ).sub( recipVPZ ) ) ) ) ); + + } ).Else( () => { + + viewReflectRayZ.assign( viewPosition.z.add( s.mul( d1viewPosition.z.sub( viewPosition.z ) ) ) ); + + } ); + + // if viewReflectRayZ is less or equal than the real z-coordinate at this place, it potentially intersects the geometry + If( viewReflectRayZ.lessThanEqual( vZ ), () => { + + // compute the distance of the new location to the ray in view space + // to clarify vP is the fragment's view position which is not an exact point on the ray + const away = pointToLineDistance( vP, viewPosition, d1viewPosition ).toVar(); + + // compute the minimum thickness between the current fragment and its neighbor in the x-direction. + const xyNeighbor = vec2( xy.x.add( 1 ), xy.y ).toVar(); // move one pixel + const uvNeighbor = xyNeighbor.div( this._resolution ); + const vPNeighbor = getViewPosition( uvNeighbor, d, this._cameraProjectionMatrixInverse ).toVar(); + const minThickness = vPNeighbor.x.sub( vP.x ).toVar(); + minThickness.mulAssign( 3 ); // expand a bit to avoid errors + + const tk = max( minThickness, this.thickness ).toVar(); + + If( away.lessThanEqual( tk ), () => { // hit + + const vN = this.normalNode.sample( uvNode ).rgb.normalize().toVar(); + + If( dot( viewReflectDir, vN ).greaterThanEqual( 0 ), () => { + + // the reflected ray is pointing towards the same side as the fragment's normal (current ray position), + // which means it wouldn't reflect off the surface. The loop continues to the next step for the next ray sample. + Continue(); + + } ); + + // this distance represents the depth of the intersection point between the reflected ray and the scene. + const distance = pointPlaneDistance( vP, viewPosition, viewNormal ).toVar(); + + If( distance.greaterThan( this.maxDistance ), () => { + + // Distance exceeding limit: The reflection is potentially too far away and + // might not contribute significantly to the final color + Break(); + + } ); + + const op = this.opacity.mul( metalness ).toVar(); + + // distance attenuation (the reflection should fade out the farther it is away from the surface) + const ratio = float( 1 ).sub( distance.div( this.maxDistance ) ).toVar(); + const attenuation = ratio.mul( ratio ); + op.mulAssign( attenuation ); + + // fresnel (reflect more light on surfaces that are viewed at grazing angles) + const fresnelCoe = div( dot( viewIncidentDir, viewReflectDir ).add( 1 ), 2 ); + op.mulAssign( fresnelCoe ); + + // output + const reflectColor = this.colorNode.sample( uvNode ); + output.assign( vec4( reflectColor.rgb, op ) ); + Break(); + + } ); + + } ); + + } ); + + return output; + + } ); + + this._material.fragmentNode = ssr().context( builder.getSharedContext() ); + this._material.needsUpdate = true; + + // + + return this._textureNode; + + } + + /** + * Frees internal resources. This method should be called + * when the effect is no longer required. + */ + dispose() { + + this._ssrRenderTarget.dispose(); + + this._material.dispose(); + + } + +} + +export default SSRNode; + +/** + * TSL function for creating screen space reflections (SSR). + * + * @tsl + * @function + * @param {Node} colorNode - The node that represents the beauty pass. + * @param {Node} depthNode - A node that represents the beauty pass's depth. + * @param {Node} normalNode - A node that represents the beauty pass's normals. + * @param {Node} metalnessNode - A node that represents the beauty pass's metalness. + * @param {Camera} camera - The camera the scene is rendered with. + * @returns {SSRNode} + */ +export const ssr = ( colorNode, depthNode, normalNode, metalnessNode, camera ) => nodeObject( new SSRNode( nodeObject( colorNode ), nodeObject( depthNode ), nodeObject( normalNode ), nodeObject( metalnessNode ), camera ) ); diff --git a/examples/jsm/tsl/display/Sepia.js b/examples/jsm/tsl/display/Sepia.js new file mode 100644 index 00000000000000..d703032b2e868e --- /dev/null +++ b/examples/jsm/tsl/display/Sepia.js @@ -0,0 +1,24 @@ +import { dot, Fn, vec3, vec4 } from 'three/tsl'; + +/** + * Applies a sepia effect to the given color node. + * + * @tsl + * @function + * @param {Node} color - The color node to apply the sepia for. + * @return {Node} The updated color node. + */ +export const sepia = /*@__PURE__*/ Fn( ( [ color ] ) => { + + const c = vec3( color ); + + // https://github.com/evanw/glfx.js/blob/master/src/filters/adjust/sepia.js + + return vec4( + dot( c, vec3( 0.393, 0.769, 0.189 ) ), + dot( c, vec3( 0.349, 0.686, 0.168 ) ), + dot( c, vec3( 0.272, 0.534, 0.131 ) ), + color.a + ); + +} ); diff --git a/examples/jsm/tsl/display/SobelOperatorNode.js b/examples/jsm/tsl/display/SobelOperatorNode.js new file mode 100644 index 00000000000000..14762dd60d0223 --- /dev/null +++ b/examples/jsm/tsl/display/SobelOperatorNode.js @@ -0,0 +1,168 @@ +import { Vector2, TempNode, NodeUpdateType } from 'three/webgpu'; +import { nodeObject, Fn, uv, uniform, convertToTexture, vec2, vec3, vec4, mat3, luminance, add } from 'three/tsl'; + +/** + * Post processing node for detecting edges with a sobel filter. + * A sobel filter should be applied after tone mapping and output color + * space conversion. + * + * @augments TempNode + * @three_import import { sobel } from 'three/addons/tsl/display/SobelOperatorNode.js'; + */ +class SobelOperatorNode extends TempNode { + + static get type() { + + return 'SobelOperatorNode'; + + } + + /** + * Constructs a new sobel operator node. + * + * @param {TextureNode} textureNode - The texture node that represents the input of the effect. + */ + constructor( textureNode ) { + + super( 'vec4' ); + + /** + * The texture node that represents the input of the effect. + * + * @type {TextureNode} + */ + this.textureNode = textureNode; + + /** + * The `updateBeforeType` is set to `NodeUpdateType.FRAME` since the node updates + * its internal uniforms once per frame in `updateBefore()`. + * + * @type {string} + * @default 'frame' + */ + this.updateBeforeType = NodeUpdateType.FRAME; + + /** + * A uniform node holding the inverse resolution value. + * + * @private + * @type {UniformNode} + */ + this._invSize = uniform( new Vector2() ); + + } + + /** + * This method is used to update the effect's uniforms once per frame. + * + * @param {NodeFrame} frame - The current node frame. + */ + updateBefore( /* frame */ ) { + + const map = this.textureNode.value; + + this._invSize.value.set( 1 / map.image.width, 1 / map.image.height ); + + } + + /** + * This method is used to setup the effect's TSL code. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {ShaderCallNodeInternal} + */ + setup( /* builder */ ) { + + const { textureNode } = this; + + const uvNode = textureNode.uvNode || uv(); + + const sampleTexture = ( uv ) => textureNode.sample( uv ); + + const sobel = Fn( () => { + + // Sobel Edge Detection (see https://youtu.be/uihBwtPIBxM) + + const texel = this._invSize; + + // kernel definition (in glsl matrices are filled in column-major order) + + const Gx = mat3( - 1, - 2, - 1, 0, 0, 0, 1, 2, 1 ); // x direction kernel + const Gy = mat3( - 1, 0, 1, - 2, 0, 2, - 1, 0, 1 ); // y direction kernel + + // fetch the 3x3 neighbourhood of a fragment + + // first column + + const tx0y0 = luminance( sampleTexture( uvNode.add( texel.mul( vec2( - 1, - 1 ) ) ) ).xyz ); + const tx0y1 = luminance( sampleTexture( uvNode.add( texel.mul( vec2( - 1, 0 ) ) ) ).xyz ); + const tx0y2 = luminance( sampleTexture( uvNode.add( texel.mul( vec2( - 1, 1 ) ) ) ).xyz ); + + // second column + + const tx1y0 = luminance( sampleTexture( uvNode.add( texel.mul( vec2( 0, - 1 ) ) ) ).xyz ); + const tx1y1 = luminance( sampleTexture( uvNode.add( texel.mul( vec2( 0, 0 ) ) ) ).xyz ); + const tx1y2 = luminance( sampleTexture( uvNode.add( texel.mul( vec2( 0, 1 ) ) ) ).xyz ); + + // third column + + const tx2y0 = luminance( sampleTexture( uvNode.add( texel.mul( vec2( 1, - 1 ) ) ) ).xyz ); + const tx2y1 = luminance( sampleTexture( uvNode.add( texel.mul( vec2( 1, 0 ) ) ) ).xyz ); + const tx2y2 = luminance( sampleTexture( uvNode.add( texel.mul( vec2( 1, 1 ) ) ) ).xyz ); + + // gradient value in x direction + + const valueGx = add( + Gx[ 0 ][ 0 ].mul( tx0y0 ), + Gx[ 1 ][ 0 ].mul( tx1y0 ), + Gx[ 2 ][ 0 ].mul( tx2y0 ), + Gx[ 0 ][ 1 ].mul( tx0y1 ), + Gx[ 1 ][ 1 ].mul( tx1y1 ), + Gx[ 2 ][ 1 ].mul( tx2y1 ), + Gx[ 0 ][ 2 ].mul( tx0y2 ), + Gx[ 1 ][ 2 ].mul( tx1y2 ), + Gx[ 2 ][ 2 ].mul( tx2y2 ) + ); + + + // gradient value in y direction + + const valueGy = add( + Gy[ 0 ][ 0 ].mul( tx0y0 ), + Gy[ 1 ][ 0 ].mul( tx1y0 ), + Gy[ 2 ][ 0 ].mul( tx2y0 ), + Gy[ 0 ][ 1 ].mul( tx0y1 ), + Gy[ 1 ][ 1 ].mul( tx1y1 ), + Gy[ 2 ][ 1 ].mul( tx2y1 ), + Gy[ 0 ][ 2 ].mul( tx0y2 ), + Gy[ 1 ][ 2 ].mul( tx1y2 ), + Gy[ 2 ][ 2 ].mul( tx2y2 ) + ); + + // magnitude of the total gradient + + const G = valueGx.mul( valueGx ).add( valueGy.mul( valueGy ) ).sqrt(); + + return vec4( vec3( G ), 1 ); + + } ); + + const outputNode = sobel(); + + return outputNode; + + } + +} + +export default SobelOperatorNode; + +/** + * TSL function for creating a sobel operator node which performs edge detection with a sobel filter. + * + * @tsl + * @function + * @param {Node} node - The node that represents the input of the effect. + * @returns {SobelOperatorNode} + */ +export const sobel = ( node ) => nodeObject( new SobelOperatorNode( convertToTexture( node ) ) ); diff --git a/examples/jsm/tsl/display/StereoCompositePassNode.js b/examples/jsm/tsl/display/StereoCompositePassNode.js new file mode 100644 index 00000000000000..5c72b6cc276573 --- /dev/null +++ b/examples/jsm/tsl/display/StereoCompositePassNode.js @@ -0,0 +1,185 @@ +import { RenderTarget, StereoCamera, HalfFloatType, LinearFilter, NearestFilter, Vector2, PassNode, QuadMesh, RendererUtils } from 'three/webgpu'; +import { texture } from 'three/tsl'; + +const _size = /*@__PURE__*/ new Vector2(); +const _quadMesh = /*@__PURE__*/ new QuadMesh(); + +let _rendererState; + +/** + * A special (abstract) render pass node that renders the scene + * as a stereoscopic image. Unlike {@link StereoPassNode}, this + * node merges the image for the left and right eye + * into a single one. That is required for effects like + * anaglyph or parallax barrier. + * + * @abstract + * @augments PassNode + * @three_import import { StereoCompositePassNode } from 'three/addons/tsl/display/StereoCompositePassNode.js'; + */ +class StereoCompositePassNode extends PassNode { + + static get type() { + + return 'StereoCompositePassNode'; + + } + + /** + * Constructs a new stereo composite pass node. + * + * @param {Scene} scene - The scene to render. + * @param {Camera} camera - The camera to render the scene with. + */ + constructor( scene, camera ) { + + super( PassNode.COLOR, scene, camera ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isStereoCompositePassNode = true; + + /** + * The internal stereo camera that is used to render the scene. + * + * @type {StereoCamera} + */ + this.stereo = new StereoCamera(); + const _params = { minFilter: LinearFilter, magFilter: NearestFilter, type: HalfFloatType }; + + /** + * The render target for rendering the left eye's view. + * + * @type {RenderTarget} + */ + this._renderTargetL = new RenderTarget( 1, 1, _params ); + + /** + * The render target for rendering the right eye's view. + * + * @type {RenderTarget} + */ + this._renderTargetR = new RenderTarget( 1, 1, _params ); + + /** + * A texture node representing the left's eye view. + * + * @type {TextureNode} + */ + this._mapLeft = texture( this._renderTargetL.texture ); + + /** + * A texture node representing the right's eye view. + * + * @type {TextureNode} + */ + this._mapRight = texture( this._renderTargetR.texture ); + + /** + * The node material that implements the composite. All + * derived effect passes must provide an instance for rendering. + * + * @type {NodeMaterial} + */ + this._material = null; + + } + + /** + * Updates the internal stereo camera. + * + * @param {number} coordinateSystem - The current coordinate system. + */ + updateStereoCamera( coordinateSystem ) { + + this.stereo.cameraL.coordinateSystem = coordinateSystem; + this.stereo.cameraR.coordinateSystem = coordinateSystem; + this.stereo.update( this.camera ); + + } + + /** + * Sets the size of the pass. + * + * @param {number} width - The width of the pass. + * @param {number} height - The height of the pass. + */ + setSize( width, height ) { + + super.setSize( width, height ); + + this._renderTargetL.setSize( this.renderTarget.width, this.renderTarget.height ); + this._renderTargetR.setSize( this.renderTarget.width, this.renderTarget.height ); + + } + + /** + * This method is used to render the effect once per frame. + * + * @param {NodeFrame} frame - The current node frame. + */ + updateBefore( frame ) { + + const { renderer } = frame; + const { scene, stereo, renderTarget } = this; + + _rendererState = RendererUtils.resetRendererState( renderer, _rendererState ); + + // + + this._pixelRatio = renderer.getPixelRatio(); + + this.updateStereoCamera( renderer.coordinateSystem ); + + const size = renderer.getSize( _size ); + this.setSize( size.width, size.height ); + + // left + + renderer.setRenderTarget( this._renderTargetL ); + renderer.render( scene, stereo.cameraL ); + + // right + + renderer.setRenderTarget( this._renderTargetR ); + renderer.render( scene, stereo.cameraR ); + + // composite + + renderer.setRenderTarget( renderTarget ); + _quadMesh.material = this._material; + _quadMesh.render( renderer ); + + // restore + + RendererUtils.restoreRendererState( renderer, _rendererState ); + + } + + /** + * Frees internal resources. This method should be called + * when the pass is no longer required. + */ + dispose() { + + super.dispose(); + + this._renderTargetL.dispose(); + this._renderTargetR.dispose(); + + if ( this._material !== null ) { + + this._material.dispose(); + + } + + } + +} + +export default StereoCompositePassNode; diff --git a/examples/jsm/tsl/display/StereoPassNode.js b/examples/jsm/tsl/display/StereoPassNode.js new file mode 100644 index 00000000000000..7dd442963bc7df --- /dev/null +++ b/examples/jsm/tsl/display/StereoPassNode.js @@ -0,0 +1,120 @@ +import { StereoCamera, Vector2, PassNode, RendererUtils } from 'three/webgpu'; +import { nodeObject } from 'three/tsl'; + +const _size = /*@__PURE__*/ new Vector2(); + +let _rendererState; + +/** + * A special render pass node that renders the scene as a stereoscopic image. + * + * @augments PassNode + * @three_import import { stereoPass } from 'three/addons/tsl/display/StereoPassNode.js'; + */ +class StereoPassNode extends PassNode { + + static get type() { + + return 'StereoPassNode'; + + } + + /** + * Constructs a new stereo pass node. + * + * @param {Scene} scene - The scene to render. + * @param {Camera} camera - The camera to render the scene with. + */ + constructor( scene, camera ) { + + super( PassNode.COLOR, scene, camera ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isStereoPassNode = true; + + /** + * The internal stereo camera that is used to render the scene. + * + * @type {StereoCamera} + */ + this.stereo = new StereoCamera(); + this.stereo.aspect = 0.5; + + } + + /** + * This method is used to render the stereo effect once per frame. + * + * @param {NodeFrame} frame - The current node frame. + */ + updateBefore( frame ) { + + const { renderer } = frame; + const { scene, camera, stereo, renderTarget } = this; + + _rendererState = RendererUtils.resetRendererState( renderer, _rendererState ); + + // + + this._pixelRatio = renderer.getPixelRatio(); + + stereo.cameraL.coordinateSystem = renderer.coordinateSystem; + stereo.cameraR.coordinateSystem = renderer.coordinateSystem; + stereo.update( camera ); + + const size = renderer.getSize( _size ); + this.setSize( size.width, size.height ); + + renderer.autoClear = false; + + this._cameraNear.value = camera.near; + this._cameraFar.value = camera.far; + + for ( const name in this._previousTextures ) { + + this.toggleTexture( name ); + + } + + renderer.setRenderTarget( renderTarget ); + renderer.setMRT( this._mrt ); + renderer.clear(); + + renderTarget.scissorTest = true; + + renderTarget.scissor.set( 0, 0, renderTarget.width / 2, renderTarget.height ); + renderTarget.viewport.set( 0, 0, renderTarget.width / 2, renderTarget.height ); + renderer.render( scene, stereo.cameraL ); + + renderTarget.scissor.set( renderTarget.width / 2, 0, renderTarget.width / 2, renderTarget.height ); + renderTarget.viewport.set( renderTarget.width / 2, 0, renderTarget.width / 2, renderTarget.height ); + renderer.render( scene, stereo.cameraR ); + + renderTarget.scissorTest = false; + + // restore + + RendererUtils.restoreRendererState( renderer, _rendererState ); + + } + +} + +export default StereoPassNode; + +/** + * TSL function for creating a stereo pass node for stereoscopic rendering. + * + * @tsl + * @function + * @param {Scene} scene - The scene to render. + * @param {Camera} camera - The camera to render the scene with. + * @returns {StereoPassNode} + */ +export const stereoPass = ( scene, camera ) => nodeObject( new StereoPassNode( scene, camera ) ); diff --git a/examples/jsm/tsl/display/TRAAPassNode.js b/examples/jsm/tsl/display/TRAAPassNode.js new file mode 100644 index 00000000000000..07c3ad9f44e788 --- /dev/null +++ b/examples/jsm/tsl/display/TRAAPassNode.js @@ -0,0 +1,452 @@ +import { Color, Vector2, NearestFilter, Matrix4, RendererUtils, PassNode, QuadMesh, NodeMaterial } from 'three/webgpu'; +import { add, float, If, Loop, int, Fn, min, max, clamp, nodeObject, texture, uniform, uv, vec2, vec4, luminance } from 'three/tsl'; + +const _quadMesh = /*@__PURE__*/ new QuadMesh(); +const _size = /*@__PURE__*/ new Vector2(); + +let _rendererState; + + +/** + * A special render pass node that renders the scene with TRAA (Temporal Reprojection Anti-Aliasing). + * + * Note: The current implementation does not yet support MRT setups. + * + * References: + * - {@link https://alextardif.com/TAA.html} + * - {@link https://www.elopezr.com/temporal-aa-and-the-quest-for-the-holy-trail/} + * + * @augments PassNode + * @three_import import { traaPass } from 'three/addons/tsl/display/TRAAPassNode.js'; + */ +class TRAAPassNode extends PassNode { + + static get type() { + + return 'TRAAPassNode'; + + } + + /** + * Constructs a new TRAA pass node. + * + * @param {Scene} scene - The scene to render. + * @param {Camera} camera - The camera to render the scene with. + */ + constructor( scene, camera ) { + + super( PassNode.COLOR, scene, camera ); + + /** + * This flag can be used for type testing. + * + * @type {boolean} + * @readonly + * @default true + */ + this.isTRAAPassNode = true; + + /** + * The clear color of the pass. + * + * @type {Color} + * @default 0x000000 + */ + this.clearColor = new Color( 0x000000 ); + + /** + * The clear alpha of the pass. + * + * @type {number} + * @default 0 + */ + this.clearAlpha = 0; + + /** + * The jitter index selects the current camera offset value. + * + * @private + * @type {number} + * @default 0 + */ + this._jitterIndex = 0; + + /** + * Used to save the original/unjittered projection matrix. + * + * @private + * @type {Matrix4} + */ + this._originalProjectionMatrix = new Matrix4(); + + /** + * A uniform node holding the inverse resolution value. + * + * @private + * @type {UniformNode} + */ + this._invSize = uniform( new Vector2() ); + + /** + * The render target that holds the current sample. + * + * @private + * @type {?RenderTarget} + * @default null + */ + this._sampleRenderTarget = null; + + /** + * The render target that represents the history of frame data. + * + * @private + * @type {?RenderTarget} + * @default null + */ + this._historyRenderTarget = null; + + /** + * Material used for the resolve step. + * + * @private + * @type {NodeMaterial} + */ + this._resolveMaterial = new NodeMaterial(); + this._resolveMaterial.name = 'TRAA.Resolve'; + + } + + /** + * Sets the size of the effect. + * + * @param {number} width - The width of the effect. + * @param {number} height - The height of the effect. + * @return {boolean} Whether the TRAA needs a restart or not. That is required after a resize since buffer data with different sizes can't be resolved. + */ + setSize( width, height ) { + + super.setSize( width, height ); + + let needsRestart = false; + + if ( this.renderTarget.width !== this._sampleRenderTarget.width || this.renderTarget.height !== this._sampleRenderTarget.height ) { + + this._sampleRenderTarget.setSize( this.renderTarget.width, this.renderTarget.height ); + this._historyRenderTarget.setSize( this.renderTarget.width, this.renderTarget.height ); + + this._invSize.value.set( 1 / this.renderTarget.width, 1 / this.renderTarget.height ); + + needsRestart = true; + + } + + return needsRestart; + + } + + /** + * This method is used to render the effect once per frame. + * + * @param {NodeFrame} frame - The current node frame. + */ + updateBefore( frame ) { + + const { renderer } = frame; + const { scene, camera } = this; + + _rendererState = RendererUtils.resetRendererState( renderer, _rendererState ); + + // + + this._pixelRatio = renderer.getPixelRatio(); + const size = renderer.getSize( _size ); + + const needsRestart = this.setSize( size.width, size.height ); + + // save original/unjittered projection matrix for velocity pass + + camera.updateProjectionMatrix(); + this._originalProjectionMatrix.copy( camera.projectionMatrix ); + + // camera configuration + + this._cameraNear.value = camera.near; + this._cameraFar.value = camera.far; + + // configure jitter as view offset + + const viewOffset = { + + fullWidth: this.renderTarget.width, + fullHeight: this.renderTarget.height, + offsetX: 0, + offsetY: 0, + width: this.renderTarget.width, + height: this.renderTarget.height + + }; + + const originalViewOffset = Object.assign( {}, camera.view ); + + if ( originalViewOffset.enabled ) Object.assign( viewOffset, originalViewOffset ); + + const jitterOffset = _JitterVectors[ this._jitterIndex ]; + + camera.setViewOffset( + + viewOffset.fullWidth, viewOffset.fullHeight, + + viewOffset.offsetX + jitterOffset[ 0 ] * 0.0625, viewOffset.offsetY + jitterOffset[ 1 ] * 0.0625, // 0.0625 = 1 / 16 + + viewOffset.width, viewOffset.height + + ); + + // configure velocity + + const mrt = this.getMRT(); + const velocityOutput = mrt.get( 'velocity' ); + + if ( velocityOutput !== undefined ) { + + velocityOutput.setProjectionMatrix( this._originalProjectionMatrix ); + + } else { + + throw new Error( 'THREE:TRAAPassNode: Missing velocity output in MRT configuration.' ); + + } + + // render sample + + renderer.setMRT( mrt ); + + renderer.setClearColor( this.clearColor, this.clearAlpha ); + renderer.setRenderTarget( this._sampleRenderTarget ); + renderer.render( scene, camera ); + + renderer.setRenderTarget( null ); + renderer.setMRT( null ); + + // every time when the dimensions change we need fresh history data. Copy the sample + // into the history and final render target (no AA happens at that point). + + if ( needsRestart === true ) { + + // bind and clear render target to make sure they are initialized after the resize which triggers a dispose() + + renderer.setRenderTarget( this._historyRenderTarget ); + renderer.clear(); + + renderer.setRenderTarget( this.renderTarget ); + renderer.clear(); + + renderer.setRenderTarget( null ); + + renderer.copyTextureToTexture( this._sampleRenderTarget.texture, this._historyRenderTarget.texture ); + renderer.copyTextureToTexture( this._sampleRenderTarget.texture, this.renderTarget.texture ); + + } else { + + // resolve + + renderer.setRenderTarget( this.renderTarget ); + _quadMesh.material = this._resolveMaterial; + _quadMesh.render( renderer ); + renderer.setRenderTarget( null ); + + // update history + + renderer.copyTextureToTexture( this.renderTarget.texture, this._historyRenderTarget.texture ); + + } + + // copy depth + + renderer.copyTextureToTexture( this._sampleRenderTarget.depthTexture, this.renderTarget.depthTexture ); + + // update jitter index + + this._jitterIndex ++; + this._jitterIndex = this._jitterIndex % ( _JitterVectors.length - 1 ); + + // restore + + if ( originalViewOffset.enabled ) { + + camera.setViewOffset( + + originalViewOffset.fullWidth, originalViewOffset.fullHeight, + + originalViewOffset.offsetX, originalViewOffset.offsetY, + + originalViewOffset.width, originalViewOffset.height + + ); + + } else { + + camera.clearViewOffset(); + + } + + velocityOutput.setProjectionMatrix( null ); + + RendererUtils.restoreRendererState( renderer, _rendererState ); + + } + + /** + * This method is used to setup the effect's render targets and TSL code. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {PassTextureNode} + */ + setup( builder ) { + + if ( this._sampleRenderTarget === null ) { + + this._sampleRenderTarget = this.renderTarget.clone(); + this._historyRenderTarget = this.renderTarget.clone(); + + this._sampleRenderTarget.texture.minFiler = NearestFilter; + this._sampleRenderTarget.texture.magFilter = NearestFilter; + + const velocityTarget = this._sampleRenderTarget.texture.clone(); + velocityTarget.isRenderTargetTexture = true; + velocityTarget.name = 'velocity'; + + this._sampleRenderTarget.textures.push( velocityTarget ); // for MRT + + } + + // textures + + const historyTexture = texture( this._historyRenderTarget.texture ); + const sampleTexture = texture( this._sampleRenderTarget.textures[ 0 ] ); + const velocityTexture = texture( this._sampleRenderTarget.textures[ 1 ] ); + const depthTexture = texture( this._sampleRenderTarget.depthTexture ); + + const resolve = Fn( () => { + + const uvNode = uv(); + + const minColor = vec4( 10000 ).toVar(); + const maxColor = vec4( - 10000 ).toVar(); + const closestDepth = float( 1 ).toVar(); + const closestDepthPixelPosition = vec2( 0 ).toVar(); + + // sample a 3x3 neighborhood to create a box in color space + // clamping the history color with the resulting min/max colors mitigates ghosting + + Loop( { start: int( - 1 ), end: int( 1 ), type: 'int', condition: '<=', name: 'x' }, ( { x } ) => { + + Loop( { start: int( - 1 ), end: int( 1 ), type: 'int', condition: '<=', name: 'y' }, ( { y } ) => { + + const uvNeighbor = uvNode.add( vec2( float( x ), float( y ) ).mul( this._invSize ) ).toVar(); + const colorNeighbor = max( vec4( 0 ), sampleTexture.sample( uvNeighbor ) ).toVar(); // use max() to avoid propagate garbage values + + minColor.assign( min( minColor, colorNeighbor ) ); + maxColor.assign( max( maxColor, colorNeighbor ) ); + + const currentDepth = depthTexture.sample( uvNeighbor ).r.toVar(); + + // find the sample position of the closest depth in the neighborhood (used for velocity) + + If( currentDepth.lessThan( closestDepth ), () => { + + closestDepth.assign( currentDepth ); + closestDepthPixelPosition.assign( uvNeighbor ); + + } ); + + } ); + + } ); + + // sampling/reprojection + + const offset = velocityTexture.sample( closestDepthPixelPosition ).xy.mul( vec2( 0.5, - 0.5 ) ); // NDC to uv offset + + const currentColor = sampleTexture.sample( uvNode ); + const historyColor = historyTexture.sample( uvNode.sub( offset ) ); + + // clamping + + const clampedHistoryColor = clamp( historyColor, minColor, maxColor ); + + // flicker reduction based on luminance weighing + + const currentWeight = float( 0.05 ).toVar(); + const historyWeight = currentWeight.oneMinus().toVar(); + + const compressedCurrent = currentColor.mul( float( 1 ).div( ( max( currentColor.r, currentColor.g, currentColor.b ).add( 1.0 ) ) ) ); + const compressedHistory = clampedHistoryColor.mul( float( 1 ).div( ( max( clampedHistoryColor.r, clampedHistoryColor.g, clampedHistoryColor.b ).add( 1.0 ) ) ) ); + + const luminanceCurrent = luminance( compressedCurrent.rgb ); + const luminanceHistory = luminance( compressedHistory.rgb ); + + currentWeight.mulAssign( float( 1.0 ).div( luminanceCurrent.add( 1 ) ) ); + historyWeight.mulAssign( float( 1.0 ).div( luminanceHistory.add( 1 ) ) ); + + return add( currentColor.mul( currentWeight ), clampedHistoryColor.mul( historyWeight ) ).div( max( currentWeight.add( historyWeight ), 0.00001 ) ); + + } ); + + // materials + + this._resolveMaterial.fragmentNode = resolve(); + + return super.setup( builder ); + + } + + /** + * Frees internal resources. This method should be called + * when the effect is no longer required. + */ + dispose() { + + super.dispose(); + + if ( this._sampleRenderTarget !== null ) { + + this._sampleRenderTarget.dispose(); + this._historyRenderTarget.dispose(); + + } + + this._resolveMaterial.dispose(); + + } + +} + +export default TRAAPassNode; + +// These jitter vectors are specified in integers because it is easier. +// I am assuming a [-8,8) integer grid, but it needs to be mapped onto [-0.5,0.5) +// before being used, thus these integers need to be scaled by 1/16. +// +// Sample patterns reference: https://msdn.microsoft.com/en-us/library/windows/desktop/ff476218%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396 +const _JitterVectors = [ + [ - 4, - 7 ], [ - 7, - 5 ], [ - 3, - 5 ], [ - 5, - 4 ], + [ - 1, - 4 ], [ - 2, - 2 ], [ - 6, - 1 ], [ - 4, 0 ], + [ - 7, 1 ], [ - 1, 2 ], [ - 6, 3 ], [ - 3, 3 ], + [ - 7, 6 ], [ - 3, 6 ], [ - 5, 7 ], [ - 1, 7 ], + [ 5, - 7 ], [ 1, - 6 ], [ 6, - 5 ], [ 4, - 4 ], + [ 2, - 3 ], [ 7, - 2 ], [ 1, - 1 ], [ 4, - 1 ], + [ 2, 1 ], [ 6, 2 ], [ 0, 4 ], [ 4, 4 ], + [ 2, 5 ], [ 7, 5 ], [ 5, 6 ], [ 3, 7 ] +]; + +/** + * TSL function for creating a TRAA pass node for Temporal Reprojection Anti-Aliasing. + * + * @tsl + * @function + * @param {Scene} scene - The scene to render. + * @param {Camera} camera - The camera to render the scene with. + * @returns {TRAAPassNode} + */ +export const traaPass = ( scene, camera ) => nodeObject( new TRAAPassNode( scene, camera ) ); diff --git a/examples/jsm/tsl/display/TransitionNode.js b/examples/jsm/tsl/display/TransitionNode.js new file mode 100644 index 00000000000000..d2d745d34a95b4 --- /dev/null +++ b/examples/jsm/tsl/display/TransitionNode.js @@ -0,0 +1,141 @@ +import { TempNode } from 'three/webgpu'; +import { nodeObject, Fn, float, uv, convertToTexture, vec4, If, int, clamp, sub, mix } from 'three/tsl'; + +/** + * Post processing node for creating a transition effect between scenes. + * + * @augments TempNode + * @three_import import { transition } from 'three/addons/tsl/display/TransitionNode.js'; + */ +class TransitionNode extends TempNode { + + static get type() { + + return 'TransitionNode'; + + } + + /** + * Constructs a new transition node. + * + * @param {TextureNode} textureNodeA - A texture node that represents the beauty pass of the first scene. + * @param {TextureNode} textureNodeB - A texture node that represents the beauty pass of the second scene. + * @param {TextureNode} mixTextureNode - A texture node that defines how the transition effect should look like. + * @param {Node} mixRatioNode - The interpolation factor that controls the mix. + * @param {Node} thresholdNode - Can be used to tweak the linear interpolation. + * @param {Node} useTextureNode - Whether `mixTextureNode` should influence the transition or not. + */ + constructor( textureNodeA, textureNodeB, mixTextureNode, mixRatioNode, thresholdNode, useTextureNode ) { + + super( 'vec4' ); + + /** + * A texture node that represents the beauty pass of the first scene. + * + * @type {TextureNode} + */ + this.textureNodeA = textureNodeA; + + /** + * A texture node that represents the beauty pass of the second scene. + * + * @type {TextureNode} + */ + this.textureNodeB = textureNodeB; + + /** + * A texture that defines how the transition effect should look like. + * + * @type {TextureNode} + */ + this.mixTextureNode = mixTextureNode; + + /** + * The interpolation factor that controls the mix. + * + * @type {Node} + */ + this.mixRatioNode = mixRatioNode; + + /** + * Can be used to tweak the linear interpolation. + * + * @type {Node} + */ + this.thresholdNode = thresholdNode; + + /** + * Whether `mixTextureNode` should influence the transition or not. + * + * @type {Node} + */ + this.useTextureNode = useTextureNode; + + } + + /** + * This method is used to setup the effect's TSL code. + * + * @param {NodeBuilder} builder - The current node builder. + * @return {ShaderCallNodeInternal} + */ + setup() { + + const { textureNodeA, textureNodeB, mixTextureNode, mixRatioNode, thresholdNode, useTextureNode } = this; + + const sampleTexture = ( textureNode ) => { + + const uvNodeTexture = textureNode.uvNode || uv(); + return textureNode.sample( uvNodeTexture ); + + }; + + const transition = Fn( () => { + + const texelOne = sampleTexture( textureNodeA ); + const texelTwo = sampleTexture( textureNodeB ); + + const color = vec4().toVar(); + + If( useTextureNode.equal( int( 1 ) ), () => { + + const transitionTexel = sampleTexture( mixTextureNode ); + const r = mixRatioNode.mul( thresholdNode.mul( 2.0 ).add( 1.0 ) ).sub( thresholdNode ); + const mixf = clamp( sub( transitionTexel.r, r ).mul( float( 1.0 ).div( thresholdNode ) ), 0.0, 1.0 ); + + color.assign( mix( texelOne, texelTwo, mixf ) ); + + } ).Else( () => { + + color.assign( mix( texelTwo, texelOne, mixRatioNode ) ); + + } ); + + return color; + + } ); + + const outputNode = transition(); + + return outputNode; + + } + +} + +export default TransitionNode; + +/** + * TSL function for creating a transition node for post processing. + * + * @tsl + * @function + * @param {Node} nodeA - A texture node that represents the beauty pass of the first scene. + * @param {Node} nodeB - A texture node that represents the beauty pass of the second scene. + * @param {Node} mixTextureNode - A texture that defines how the transition effect should look like. + * @param {Node | number} mixRatio - The interpolation factor that controls the mix. + * @param {Node | number} threshold - Can be used to tweak the linear interpolation. + * @param {Node | number} useTexture - Whether `mixTextureNode` should influence the transition or not. + * @returns {TransitionNode} + */ +export const transition = ( nodeA, nodeB, mixTextureNode, mixRatio, threshold, useTexture ) => nodeObject( new TransitionNode( convertToTexture( nodeA ), convertToTexture( nodeB ), convertToTexture( mixTextureNode ), nodeObject( mixRatio ), nodeObject( threshold ), nodeObject( useTexture ) ) ); diff --git a/examples/jsm/tsl/display/hashBlur.js b/examples/jsm/tsl/display/hashBlur.js new file mode 100644 index 00000000000000..95889c9877d0b1 --- /dev/null +++ b/examples/jsm/tsl/display/hashBlur.js @@ -0,0 +1,56 @@ +import { float, Fn, vec2, uv, sin, rand, degrees, cos, Loop, vec4, premultiplyAlpha, unpremultiplyAlpha } from 'three/tsl'; + +/** + * Applies a hash blur effect to the given texture node. + * + * Reference: {@link https://www.shadertoy.com/view/4lXXWn}. + * + * @function + * @param {Node} textureNode - The texture node that should be blurred. + * @param {Node} [bluramount=float(0.1)] - This node determines the amount of blur. + * @param {Object} [options={}] - Additional options for the hash blur effect. + * @param {Node} [options.repeats=float(45)] - The number of iterations for the blur effect. + * @param {Node} [options.mask=null] - A mask node to control the alpha blending of the blur. + * @param {boolean} [options.premultipliedAlpha=false] - Whether to use premultiplied alpha for the blur effect. + * @return {Node} The blurred texture node. + */ +export const hashBlur = /*#__PURE__*/ Fn( ( [ textureNode, bluramount = float( 0.1 ), options = {} ] ) => { + + const { + repeats = float( 45 ), + mask = null, + premultipliedAlpha = false + } = options; + + const draw = ( uv ) => { + + let sample = textureNode.sample( uv ); + + if ( mask !== null ) { + + const alpha = mask.sample( uv ).x; + + sample = vec4( sample.rgb, sample.a.mul( alpha ) ); + + } + + return premultipliedAlpha ? premultiplyAlpha( sample ) : sample; + + }; + + const targetUV = textureNode.uvNode || uv(); + const blurred_image = vec4( 0. ).toVar(); + + Loop( { start: 0., end: repeats, type: 'float' }, ( { i } ) => { + + const q = vec2( vec2( cos( degrees( i.div( repeats ).mul( 360. ) ) ), sin( degrees( i.div( repeats ).mul( 360. ) ) ) ).mul( rand( vec2( i, targetUV.x.add( targetUV.y ) ) ).add( bluramount ) ) ); + const uv2 = vec2( targetUV.add( q.mul( bluramount ) ) ); + blurred_image.addAssign( draw( uv2 ) ); + + } ); + + blurred_image.divAssign( repeats ); + + return premultipliedAlpha ? unpremultiplyAlpha( blurred_image ) : blurred_image; + +} ); diff --git a/examples/jsm/tsl/lighting/TiledLightsNode.js b/examples/jsm/tsl/lighting/TiledLightsNode.js new file mode 100644 index 00000000000000..dedd5de9797c04 --- /dev/null +++ b/examples/jsm/tsl/lighting/TiledLightsNode.js @@ -0,0 +1,422 @@ +import { DataTexture, FloatType, RGBAFormat, Vector2, Vector3, LightsNode, NodeUpdateType } from 'three/webgpu'; + +import { + attributeArray, nodeProxy, int, float, vec2, ivec2, ivec4, uniform, Break, Loop, positionView, + Fn, If, Return, textureLoad, instanceIndex, screenCoordinate, directPointLight +} from 'three/tsl'; + +export const circleIntersectsAABB = /*@__PURE__*/ Fn( ( [ circleCenter, radius, minBounds, maxBounds ] ) => { + + // Find the closest point on the AABB to the circle's center using method chaining + const closestX = minBounds.x.max( circleCenter.x.min( maxBounds.x ) ); + const closestY = minBounds.y.max( circleCenter.y.min( maxBounds.y ) ); + + // Compute the distance between the circle's center and the closest point + const distX = circleCenter.x.sub( closestX ); + const distY = circleCenter.y.sub( closestY ); + + // Calculate the squared distance + const distSquared = distX.mul( distX ).add( distY.mul( distY ) ); + + return distSquared.lessThanEqual( radius.mul( radius ) ); + +} ).setLayout( { + name: 'circleIntersectsAABB', + type: 'bool', + inputs: [ + { name: 'circleCenter', type: 'vec2' }, + { name: 'radius', type: 'float' }, + { name: 'minBounds', type: 'vec2' }, + { name: 'maxBounds', type: 'vec2' } + ] +} ); + +const _vector3 = /*@__PURE__*/ new Vector3(); +const _size = /*@__PURE__*/ new Vector2(); + +/** + * A custom version of `LightsNode` implementing tiled lighting. This node is used in + * {@link TiledLighting} to overwrite the renderer's default lighting with + * a custom implementation. + * + * @augments LightsNode + * @three_import import { tiledLights } from 'three/addons/tsl/lighting/TiledLightsNode.js'; + */ +class TiledLightsNode extends LightsNode { + + static get type() { + + return 'TiledLightsNode'; + + } + + /** + * Constructs a new tiled lights node. + * + * @param {number} [maxLights=1024] - The maximum number of lights. + * @param {number} [tileSize=32] - The tile size. + */ + constructor( maxLights = 1024, tileSize = 32 ) { + + super(); + + this.materialLights = []; + this.tiledLights = []; + + /** + * The maximum number of lights. + * + * @type {number} + * @default 1024 + */ + this.maxLights = maxLights; + + /** + * The tile size. + * + * @type {number} + * @default 32 + */ + this.tileSize = tileSize; + + this._bufferSize = null; + this._lightIndexes = null; + this._screenTileIndex = null; + this._compute = null; + this._lightsTexture = null; + + this._lightsCount = uniform( 0, 'int' ); + this._tileLightCount = 8; + this._screenSize = uniform( new Vector2() ); + this._cameraProjectionMatrix = uniform( 'mat4' ); + this._cameraViewMatrix = uniform( 'mat4' ); + + this.updateBeforeType = NodeUpdateType.RENDER; + + } + + customCacheKey() { + + return this._compute.getCacheKey() + super.customCacheKey(); + + } + + updateLightsTexture() { + + const { _lightsTexture: lightsTexture, tiledLights } = this; + + const data = lightsTexture.image.data; + const lineSize = lightsTexture.image.width * 4; + + this._lightsCount.value = tiledLights.length; + + for ( let i = 0; i < tiledLights.length; i ++ ) { + + const light = tiledLights[ i ]; + + // world position + + _vector3.setFromMatrixPosition( light.matrixWorld ); + + // store data + + const offset = i * 4; + + data[ offset + 0 ] = _vector3.x; + data[ offset + 1 ] = _vector3.y; + data[ offset + 2 ] = _vector3.z; + data[ offset + 3 ] = light.distance; + + data[ lineSize + offset + 0 ] = light.color.r * light.intensity; + data[ lineSize + offset + 1 ] = light.color.g * light.intensity; + data[ lineSize + offset + 2 ] = light.color.b * light.intensity; + data[ lineSize + offset + 3 ] = light.decay; + + } + + lightsTexture.needsUpdate = true; + + } + + updateBefore( frame ) { + + const { renderer, camera } = frame; + + this.updateProgram( renderer ); + + this.updateLightsTexture( camera ); + + this._cameraProjectionMatrix.value = camera.projectionMatrix; + this._cameraViewMatrix.value = camera.matrixWorldInverse; + + renderer.getDrawingBufferSize( _size ); + this._screenSize.value.copy( _size ); + + renderer.compute( this._compute ); + + } + + setLights( lights ) { + + const { tiledLights, materialLights } = this; + + let materialindex = 0; + let tiledIndex = 0; + + for ( const light of lights ) { + + if ( light.isPointLight === true ) { + + tiledLights[ tiledIndex ++ ] = light; + + } else { + + materialLights[ materialindex ++ ] = light; + + } + + } + + materialLights.length = materialindex; + tiledLights.length = tiledIndex; + + return super.setLights( materialLights ); + + } + + getBlock( block = 0 ) { + + return this._lightIndexes.element( this._screenTileIndex.mul( int( 2 ).add( int( block ) ) ) ); + + } + + getTile( element ) { + + element = int( element ); + + const stride = int( 4 ); + const tileOffset = element.div( stride ); + const tileIndex = this._screenTileIndex.mul( int( 2 ) ).add( tileOffset ); + + return this._lightIndexes.element( tileIndex ).element( element.mod( stride ) ); + + } + + getLightData( index ) { + + index = int( index ); + + const dataA = textureLoad( this._lightsTexture, ivec2( index, 0 ) ); + const dataB = textureLoad( this._lightsTexture, ivec2( index, 1 ) ); + + const position = dataA.xyz; + const viewPosition = this._cameraViewMatrix.mul( position ); + const distance = dataA.w; + const color = dataB.rgb; + const decay = dataB.w; + + return { + position, + viewPosition, + distance, + color, + decay + }; + + } + + setupLights( builder, lightNodes ) { + + this.updateProgram( builder.renderer ); + + // + + const lightingModel = builder.context.reflectedLight; + + // force declaration order, before of the loop + lightingModel.directDiffuse.toStack(); + lightingModel.directSpecular.toStack(); + + super.setupLights( builder, lightNodes ); + + Fn( () => { + + Loop( this._tileLightCount, ( { i } ) => { + + const lightIndex = this.getTile( i ); + + If( lightIndex.equal( int( 0 ) ), () => { + + Break(); + + } ); + + const { color, decay, viewPosition, distance } = this.getLightData( lightIndex.sub( 1 ) ); + + builder.lightsNode.setupDirectLight( builder, this, directPointLight( { + color, + lightVector: viewPosition.sub( positionView ), + cutoffDistance: distance, + decayExponent: decay + } ) ); + + } ); + + }, 'void' )(); + + } + + getBufferFitSize( value ) { + + const multiple = this.tileSize; + + return Math.ceil( value / multiple ) * multiple; + + } + + setSize( width, height ) { + + width = this.getBufferFitSize( width ); + height = this.getBufferFitSize( height ); + + if ( ! this._bufferSize || this._bufferSize.width !== width || this._bufferSize.height !== height ) { + + this.create( width, height ); + + } + + return this; + + } + + updateProgram( renderer ) { + + renderer.getDrawingBufferSize( _size ); + + const width = this.getBufferFitSize( _size.width ); + const height = this.getBufferFitSize( _size.height ); + + if ( this._bufferSize === null ) { + + this.create( width, height ); + + } else if ( this._bufferSize.width !== width || this._bufferSize.height !== height ) { + + this.create( width, height ); + + } + + } + + create( width, height ) { + + const { tileSize, maxLights } = this; + + const bufferSize = new Vector2( width, height ); + const lineSize = Math.floor( bufferSize.width / tileSize ); + const count = Math.floor( ( bufferSize.width * bufferSize.height ) / tileSize ); + + // buffers + + const lightsData = new Float32Array( maxLights * 4 * 2 ); // 2048 lights, 4 elements(rgba), 2 components, 1 component per line (position, distance, color, decay) + const lightsTexture = new DataTexture( lightsData, lightsData.length / 8, 2, RGBAFormat, FloatType ); + + const lightIndexesArray = new Int32Array( count * 4 * 2 ); + const lightIndexes = attributeArray( lightIndexesArray, 'ivec4' ).label( 'lightIndexes' ); + + // compute + + const getBlock = ( index ) => { + + const tileIndex = instanceIndex.mul( int( 2 ) ).add( int( index ) ); + + return lightIndexes.element( tileIndex ); + + }; + + const getTile = ( elementIndex ) => { + + elementIndex = int( elementIndex ); + + const stride = int( 4 ); + const tileOffset = elementIndex.div( stride ); + const tileIndex = instanceIndex.mul( int( 2 ) ).add( tileOffset ); + + return lightIndexes.element( tileIndex ).element( elementIndex.mod( stride ) ); + + }; + + const compute = Fn( () => { + + const { _cameraProjectionMatrix: cameraProjectionMatrix, _bufferSize: bufferSize, _screenSize: screenSize } = this; + + const tiledBufferSize = bufferSize.clone().divideScalar( tileSize ).floor(); + + const tileScreen = vec2( + instanceIndex.mod( tiledBufferSize.width ), + instanceIndex.div( tiledBufferSize.width ) + ).mul( tileSize ).div( screenSize ); + + const blockSize = float( tileSize ).div( screenSize ); + const minBounds = tileScreen; + const maxBounds = minBounds.add( blockSize ); + + const index = int( 0 ).toVar(); + + getBlock( 0 ).assign( ivec4( 0 ) ); + getBlock( 1 ).assign( ivec4( 0 ) ); + + Loop( this.maxLights, ( { i } ) => { + + If( index.greaterThanEqual( this._tileLightCount ).or( int( i ).greaterThanEqual( int( this._lightsCount ) ) ), () => { + + Return(); + + } ); + + const { viewPosition, distance } = this.getLightData( i ); + + const projectedPosition = cameraProjectionMatrix.mul( viewPosition ); + const ndc = projectedPosition.div( projectedPosition.w ); + const screenPosition = ndc.xy.mul( 0.5 ).add( 0.5 ).flipY(); + + const distanceFromCamera = viewPosition.z; + const pointRadius = distance.div( distanceFromCamera ); + + If( circleIntersectsAABB( screenPosition, pointRadius, minBounds, maxBounds ), () => { + + getTile( index ).assign( i.add( int( 1 ) ) ); + index.addAssign( int( 1 ) ); + + } ); + + } ); + + } )().compute( count ); + + // screen coordinate lighting indexes + + const screenTile = screenCoordinate.div( tileSize ).floor().toVar(); + const screenTileIndex = screenTile.x.add( screenTile.y.mul( lineSize ) ); + + // assigns + + this._bufferSize = bufferSize; + this._lightIndexes = lightIndexes; + this._screenTileIndex = screenTileIndex; + this._compute = compute; + this._lightsTexture = lightsTexture; + + } + + get hasLights() { + + return super.hasLights || this.tiledLights.length > 0; + + } + +} + +export default TiledLightsNode; + +export const tiledLights = /*@__PURE__*/ nodeProxy( TiledLightsNode ); diff --git a/examples/jsm/tsl/math/Bayer.js b/examples/jsm/tsl/math/Bayer.js new file mode 100644 index 00000000000000..6ab6734942550e --- /dev/null +++ b/examples/jsm/tsl/math/Bayer.js @@ -0,0 +1,34 @@ +import { TextureLoader } from 'three'; +import { Fn, int, ivec2, textureLoad } from 'three/tsl'; + +/** + * @module Bayer + * @three_import import { bayer16 } from 'three/addons/tsl/math/Bayer.js'; + */ + +let bayer16Texture = null; + +/** + * This TSL function can be used to sample a Bayer16 texture which is a 16x16 texture with a Bayer Matrix pattern. + * It can be used for dithering effects but also as an alternative to blue-noise. When used with Ray Marching + * specifically in {@link VolumeNodeMaterial#offsetNode}, it reduces banding problem, thus being able to use + * fewer steps without affecting the visuals as much. + * + * @tsl + * @function + * @param {Node} uv - The uv to sample the bayer16 texture. + * @return {Node} The sampled bayer value. + */ +export const bayer16 = Fn( ( [ uv ] ) => { + + if ( bayer16Texture === null ) { + + const bayer16Base64 = 'data:image/png;base64,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'; + + bayer16Texture = new TextureLoader().load( bayer16Base64 ); + + } + + return textureLoad( bayer16Texture, ivec2( uv ).mod( int( 16 ) ) ); + +} ); diff --git a/examples/jsm/tsl/shadows/TileShadowNode.js b/examples/jsm/tsl/shadows/TileShadowNode.js new file mode 100644 index 00000000000000..4a1179a1266b93 --- /dev/null +++ b/examples/jsm/tsl/shadows/TileShadowNode.js @@ -0,0 +1,456 @@ +import { + Vector3, + Object3D, + ShadowBaseNode, + Plane, + Line3, + DepthTexture, + LessCompare, + Vector2, + RedFormat, + ArrayCamera, + VSMShadowMap, + RendererUtils, + Quaternion +} from 'three/webgpu'; + +import { min, Fn, shadow, NodeUpdateType, getShadowMaterial, getShadowRenderObjectFunction } from 'three/tsl'; + +const { resetRendererAndSceneState, restoreRendererAndSceneState } = RendererUtils; +let _rendererState; + +const _cameraLayers = []; +const _vec3Temp1 = /*@__PURE__*/ new Vector3(); +const _vec3Temp2 = /*@__PURE__*/ new Vector3(); +const _vec3Temp3 = /*@__PURE__*/ new Vector3(); +const _quatTemp1 = /*@__PURE__*/ new Quaternion(); + +class LwLight extends Object3D { + + constructor() { + + super(); + this.target = new Object3D(); + + } + +} + +/** + * A class that extends `ShadowBaseNode` to implement tiled shadow mapping. + * This allows splitting a shadow map into multiple tiles, each with its own light and camera, + * to improve shadow quality and performance for large scenes. + * + * **Note:** This class does not support `VSMShadowMap` at the moment. + * + * @class + * @augments ShadowBaseNode + * @three_import import { TileShadowNode } from 'three/addons/tsl/shadows/TileShadowNode.js'; + */ +class TileShadowNode extends ShadowBaseNode { + + /** + * Creates an instance of `TileShadowNode`. + * + * @param {Light} light - The original light source used for shadow mapping. + * @param {Object} [options={}] - Configuration options for the tiled shadow node. + * @param {number} [options.tilesX=2] - The number of tiles along the X-axis. + * @param {number} [options.tilesY=2] - The number of tiles along the Y-axis. + * @param {Object} [options.resolution] - The resolution of the shadow map. + * @param {boolean} [options.debug=false] - Whether to enable debug mode. + */ + constructor( light, options = {} ) { + + super( light ); + + // Default configuration with sensible defaults + this.config = { + tilesX: options.tilesX || 2, + tilesY: options.tilesY || 2, + resolution: options.resolution || light.shadow.mapSize, + debug: options.debug !== undefined ? options.debug : false + }; + + this.debug = this.config.debug; + + this.originalLight = light; + this.lightPlane = new Plane( new Vector3( 0, 1, 0 ), 0 ); + this.line = new Line3(); + + this.initialLightDirection = new Vector3(); + this.updateLightDirection(); + + this._cameraFrameId = new WeakMap(); + + this.shadowSize = { + top: light.shadow.camera.top, + bottom: light.shadow.camera.bottom, + left: light.shadow.camera.left, + right: light.shadow.camera.right, + }; + + this.lights = []; + this._shadowNodes = []; + + this.tiles = this.generateTiles( this.config.tilesX, this.config.tilesY ); + + } + + /** + * Generates the tiles for the shadow map based on the specified number of tiles along the X and Y axes. + * + * @param {number} tilesX - The number of tiles along the X-axis. + * @param {number} tilesY - The number of tiles along the Y-axis. + * @returns {Array} An array of tile objects, each containing the tile's bounds and index. + */ + generateTiles( tilesX, tilesY ) { + + const tiles = []; + const tileWidth = 1 / tilesX; + const tileHeight = 1 / tilesY; + + for ( let y = 0; y < tilesY; y ++ ) { + + for ( let x = 0; x < tilesX; x ++ ) { + + tiles.push( { + x: [ x * tileWidth, ( x + 1 ) * tileWidth ], + y: [ ( tilesY - 1 - y ) * tileHeight, ( tilesY - y ) * tileHeight ], // Start from top row + index: y * tilesX + x + } ); + + } + + } + + return tiles; + + } + + /** + * Updates the initial light direction based on the light's target position. + */ + updateLightDirection() { + + this.initialLightDirection.subVectors( + this.originalLight.target.getWorldPosition( new Vector3() ), + this.originalLight.getWorldPosition( new Vector3() ) + ).normalize(); + + } + + /** + * Initializes the tiled shadow node by creating lights, cameras, and shadow maps for each tile. + * + * @param {Builder} builder - The builder used to create render targets and other resources. + */ + init( builder ) { + + const light = this.originalLight; + const parent = light.parent; + + const width = this.shadowSize.right - this.shadowSize.left; + const height = this.shadowSize.top - this.shadowSize.bottom; + + const tileCount = this.tiles.length; + const shadowWidth = this.config.resolution.width; + const shadowHeight = this.config.resolution.height; + + // Clear existing lights/nodes if re-initializing + this.disposeLightsAndNodes(); + + const depthTexture = new DepthTexture( shadowWidth, shadowHeight, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, tileCount ); + depthTexture.compareFunction = LessCompare; + depthTexture.name = 'ShadowDepthArrayTexture'; + const shadowMap = builder.createRenderTarget( shadowWidth, shadowHeight, { format: RedFormat, depth: tileCount } ); + shadowMap.depthTexture = depthTexture; + shadowMap.texture.name = 'ShadowTexture'; + this.shadowMap = shadowMap; + const cameras = []; + + + // Create lights, one for each tile + for ( let i = 0; i < tileCount; i ++ ) { + + const lwLight = new LwLight(); + lwLight.castShadow = true; + const lShadow = light.shadow.clone(); + lShadow.filterNode = light.shadow.filterNode; + const tile = this.tiles[ i ]; + lShadow.camera.left = this.shadowSize.left + width * tile.x[ 0 ]; + lShadow.camera.right = this.shadowSize.left + width * tile.x[ 1 ]; + lShadow.camera.top = this.shadowSize.bottom + height * tile.y[ 1 ]; + lShadow.camera.bottom = this.shadowSize.bottom + height * tile.y[ 0 ]; + lShadow.bias = light.shadow.bias; + lShadow.camera.near = light.shadow.camera.near; + lShadow.camera.far = light.shadow.camera.far; + lShadow.camera.userData.tileIndex = i; + lwLight.shadow = lShadow; + + if ( parent ) { + + parent.add( lwLight ); + parent.add( lwLight.target ); + + } else { + + console.warn( 'TileShadowNode: Original light has no parent during init. Tile lights not added to scene graph directly.' ); + + } + + this.syncLightTransformation( lwLight, light ); + + this.lights.push( lwLight ); + lShadow.camera.updateMatrixWorld(); + + cameras.push( lShadow.camera ); + const shadowNode = shadow( lwLight, lShadow ); + shadowNode.depthLayer = i; + shadowNode.updateBeforeType = NodeUpdateType.NONE; + + shadowNode.setupRenderTarget = () => { + + return { shadowMap, depthTexture }; + + }; + + this._shadowNodes.push( shadowNode ); + + } + + const cameraArray = new ArrayCamera( cameras ); + this.cameraArray = cameraArray; + + } + + /** + * Updates the light transformations and shadow cameras for each tile. + */ + update() { + + const light = this.originalLight; + + const shadowCam = light.shadow.camera; + const lsMin = new Vector2( shadowCam.left, shadowCam.bottom ); + const lsMax = new Vector2( shadowCam.right, shadowCam.top ); + const fullWidth = lsMax.x - lsMin.x; + const fullHeight = lsMax.y - lsMin.y; + + for ( let i = 0; i < this.lights.length; i ++ ) { + + const lwLight = this.lights[ i ]; + const tile = this.tiles[ i ]; + this.syncLightTransformation( lwLight, light ); + const lShadow = lwLight.shadow; + const tileLeft = lsMin.x + tile.x[ 0 ] * fullWidth; + const tileRight = lsMin.x + tile.x[ 1 ] * fullWidth; + const tileBottom = lsMin.y + tile.y[ 0 ] * fullHeight; + const tileTop = lsMin.y + tile.y[ 1 ] * fullHeight; + lShadow.camera.left = tileLeft; + lShadow.camera.right = tileRight; + lShadow.camera.bottom = tileBottom; + lShadow.camera.top = tileTop; + lShadow.camera.near = light.shadow.camera.near; + lShadow.camera.far = light.shadow.camera.far; + lShadow.camera.updateProjectionMatrix(); + lShadow.camera.updateWorldMatrix( true, false ); + lShadow.camera.updateMatrixWorld( true ); + this._shadowNodes[ i ].shadow.needsUpdate = true; + + } + + } + + /** + * Updates the shadow map rendering. + * @param {NodeFrame} frame - A reference to the current node frame. + */ + updateShadow( frame ) { + + const { shadowMap, light } = this; + const { renderer, scene, camera } = frame; + const shadowType = renderer.shadowMap.type; + const depthVersion = shadowMap.depthTexture.version; + this._depthVersionCached = depthVersion; + const currentRenderObjectFunction = renderer.getRenderObjectFunction(); + const currentMRT = renderer.getMRT(); + const useVelocity = currentMRT ? currentMRT.has( 'velocity' ) : false; + + _rendererState = resetRendererAndSceneState( renderer, scene, _rendererState ); + scene.overrideMaterial = getShadowMaterial( light ); + renderer.setRenderTarget( this.shadowMap ); + + + for ( let index = 0; index < this.lights.length; index ++ ) { + + const light = this.lights[ index ]; + const shadow = light.shadow; + + const _shadowCameraLayer = shadow.camera.layers.mask; + _cameraLayers.push( _shadowCameraLayer ); + + if ( ( shadow.camera.layers.mask & 0xFFFFFFFE ) === 0 ) { + + shadow.camera.layers.mask = camera.layers.mask; + + } + + shadow.updateMatrices( light ); + + renderer.setRenderObjectFunction( getShadowRenderObjectFunction( renderer, shadow, shadowType, useVelocity ) ); + this.shadowMap.setSize( shadow.mapSize.width, shadow.mapSize.height, shadowMap.depth ); + + } + + renderer.render( scene, this.cameraArray ); + renderer.setRenderObjectFunction( currentRenderObjectFunction ); + + if ( light.isPointLight !== true && shadowType === VSMShadowMap ) { + + console.warn( 'THREE.TileShadowNode: VSM shadow map is not supported yet.' ); + // this.vsmPass( renderer ); + + } + + restoreRendererAndSceneState( renderer, scene, _rendererState ); + + for ( let index = 0; index < this.lights.length; index ++ ) { + + const light = this.lights[ index ]; + const shadow = light.shadow; + + shadow.camera.layers.mask = _cameraLayers[ index ]; + + } + + _cameraLayers.length = 0; + + } + + /** + * The implementation performs the update of the shadow map if necessary. + * + * @param {NodeFrame} frame - A reference to the current node frame. + */ + updateBefore( frame ) { + + const shadow = this.originalLight.shadow; + + let needsUpdate = shadow.needsUpdate || shadow.autoUpdate; + + if ( needsUpdate ) { + + if ( this._cameraFrameId[ frame.camera ] === frame.frameId ) { + + needsUpdate = false; + + } + + this._cameraFrameId[ frame.camera ] = frame.frameId; + + } + + if ( needsUpdate ) { + + this.update(); + this.updateShadow( frame ); + + if ( this.shadowMap.depthTexture.version === this._depthVersionCached ) { + + shadow.needsUpdate = false; + + } + + } + + } + + /** + * Synchronizes the transformation of a tile light with the source light. + * + * @param {LwLight} lwLight - The tile light to synchronize. + * @param {Light} sourceLight - The source light to copy transformations from. + */ + syncLightTransformation( lwLight, sourceLight ) { + + const sourceWorldPos = sourceLight.getWorldPosition( _vec3Temp1 ); + const targetWorldPos = sourceLight.target.getWorldPosition( _vec3Temp2 ); + const forward = _vec3Temp3.subVectors( targetWorldPos, sourceWorldPos ); + const targetDistance = forward.length(); + forward.normalize(); + lwLight.position.copy( sourceWorldPos ); + lwLight.target.position.copy( sourceWorldPos ).add( forward.multiplyScalar( targetDistance ) ); + lwLight.quaternion.copy( sourceLight.getWorldQuaternion( _quatTemp1 ) ); + lwLight.scale.copy( sourceLight.scale ); + lwLight.updateMatrix(); + lwLight.updateMatrixWorld( true ); + lwLight.target.updateMatrix(); + lwLight.target.updateMatrixWorld( true ); + + } + + /** + * Sets up the shadow node for rendering. + * + * @param {Builder} builder - The builder used to set up the shadow node. + * @returns {Node} A node representing the shadow value. + */ + setup( builder ) { + + if ( this.lights.length === 0 ) { + + this.init( builder ); + + } + + return Fn( ( builder ) => { + + this.setupShadowPosition( builder ); + return min( ...this._shadowNodes ).toVar( 'shadowValue' ); + + } )(); + + } + + /** + * Helper method to remove lights and associated nodes/targets. + * Used internally during dispose and potential re-initialization. + */ + disposeLightsAndNodes() { + + for ( const light of this.lights ) { + + const parent = light.parent; + if ( parent ) { + + parent.remove( light.target ); + parent.remove( light ); + + } + + } + + this.lights = []; + this._shadowNodes = []; + + if ( this.shadowMap ) { + + this.shadowMap.dispose(); // Disposes render target and textures + this.shadowMap = null; + + } + + } + + + dispose() { + + // Dispose lights, nodes, and shadow map + this.disposeLightsAndNodes(); + super.dispose(); + + } + +} + +export { TileShadowNode }; diff --git a/examples/jsm/tsl/shadows/TileShadowNodeHelper.js b/examples/jsm/tsl/shadows/TileShadowNodeHelper.js new file mode 100644 index 00000000000000..aaba411088fdf1 --- /dev/null +++ b/examples/jsm/tsl/shadows/TileShadowNodeHelper.js @@ -0,0 +1,212 @@ +import { Group, NodeMaterial, Mesh, PlaneGeometry, DoubleSide, CameraHelper } from 'three/webgpu'; +import { Fn, vec4, vec3, texture, uv, positionLocal, vec2, float, screenSize } from 'three/tsl'; + +/** + * Helper class to manage and display debug visuals for TileShadowNode. + * + * @augments Group + * @three_import import { TileShadowNodeHelper } from 'three/addons/tsl/shadows/TileShadowNodeHelper.js'; + */ +class TileShadowNodeHelper extends Group { + + /** + * @param {TileShadowNode} tileShadowNode The TileShadowNode instance to debug. + */ + constructor( tileShadowNode ) { + + super(); + + if ( ! tileShadowNode ) { + + throw new Error( 'TileShadowNode instance is required for TileShadowNodeHelper.' ); + + } + + this.tileShadowNode = tileShadowNode; + this.config = tileShadowNode.config; + this.tiles = tileShadowNode.tiles; + this._debugMeshes = []; + this._shadowCamHelpers = []; + + this.initialized = false; + + } + + /** + * Initializes the debug displays (planes and camera helpers). + * Should be called after TileShadowNode has initialized its lights and shadow nodes. + */ + init() { + + if ( this.tileShadowNode._shadowNodes.length !== this.tiles.length ) { + + console.error( 'Cannot initialize TileShadowNodeHelper: Shadow nodes not ready or mismatch count.' ); + return; + + } + + const tilesX = this.config.tilesX; + const tilesY = this.config.tilesY; + + // Clear previous helpers if any (e.g., during a re-init) + this.dispose(); + + // Create a display for each shadow map tile + for ( let i = 0; i < this.tiles.length; i ++ ) { + + // Create display plane + const display = new Mesh( new PlaneGeometry( 1, 1 ), new NodeMaterial() ); + display.renderOrder = 9999999; // Ensure they appear on top + display.material.transparent = true; + display.frustumCulled = false; + display.side = DoubleSide; + display.material.depthTest = false; // Disable depth testing + display.material.depthWrite = false; // Disable depth writing + + const col = i % tilesX; + const row = Math.floor( i / tilesX ); + + // Vertex shader logic for positioning the debug quad + display.material.vertexNode = Fn( () => { + + const aspectRatio = screenSize.x.div( screenSize.y ); + const maxTiles = Math.max( tilesX, tilesY ); + const displaySize = float( 0.8 / maxTiles ); // Size adapts to number of tiles + const margin = float( 0.01 ); + const cornerOffset = float( 0.05 ); + + // Position tiles left-to-right, top-to-bottom + const xBase = float( - 1.0 ).add( cornerOffset ).add( + displaySize.div( 2 ).div( aspectRatio ) + ).add( float( col ).mul( displaySize.div( aspectRatio ).add( margin ) ) ); + + const yBase = float( 1.0 ).sub( cornerOffset ).sub( + displaySize.div( 2 ) + ).sub( float( row ).mul( displaySize.add( margin ) ) ); + + const scaledPos = vec2( + positionLocal.x.mul( displaySize.div( aspectRatio ) ), + positionLocal.y.mul( displaySize ) + ); + + const finalPos = vec2( + scaledPos.x.add( xBase ), + scaledPos.y.add( yBase ) + ); + + return vec4( finalPos.x, finalPos.y, 0.0, 1.0 ); + + } )(); + + display.material.outputNode = Fn( () => { + + // Ensure shadowMap and depthTexture are available + if ( ! this.tileShadowNode.shadowMap || ! this.tileShadowNode.shadowMap.depthTexture ) { + + return vec4( 1, 0, 1, 1 ); // Magenta error color + + } + + const sampledDepth = texture( this.tileShadowNode.shadowMap.depthTexture ) + .sample( uv().flipY() ) + .depth( float( i ) ) // Sample correct layer + .compare( 0.9 ); // Example comparison value + + // Simple tint based on index for visual distinction + const r = float( 0.5 + ( i % 3 ) * 0.16 ); + const g = float( 0.5 + ( i % 2 ) * 0.25 ); + const b = float( 0.7 + ( i % 4 ) * 0.075 ); + + return vec4( + vec3( r, g, b ) + .mul( sampledDepth ) + .saturate() + .rgb, + 1.0 + ); + + } )(); + + this.add( display ); + this._debugMeshes.push( display ); + + if ( this.tileShadowNode._shadowNodes[ i ] && this.tileShadowNode._shadowNodes[ i ].shadow ) { + + const camHelper = new CameraHelper( this.tileShadowNode._shadowNodes[ i ].shadow.camera ); + camHelper.fog = false; + this.add( camHelper ); + this._shadowCamHelpers.push( camHelper ); + + } else { + + console.warn( `TileShadowNodeHelper: Could not create CameraHelper for tile index ${i}. Shadow node or camera missing.` ); + this._shadowCamHelpers.push( null ); + + } + + } + + this.initialized = true; + + } + + /** + * Updates the debug visuals (specifically camera helpers). + * Should be called within TileShadowNode's update method. + */ + update() { + + if ( this.initialized === false ) { + + this.init(); + + } + + for ( const helper of this._shadowCamHelpers ) { + + if ( helper ) { + + helper.update(); // Update CameraHelper matrices + helper.updateMatrixWorld( true ); // Ensure world matrix is current + + } + + } + + } + + /** + * Removes all debug objects (planes and helpers) from the scene. + */ + dispose() { + + if ( this.scene ) { + + for ( const mesh of this._debugMeshes ) { + + mesh.geometry.dispose(); + mesh.material.dispose(); + this.scene.remove( mesh ); + + } + + for ( const helper of this._shadowCamHelpers ) { + + if ( helper ) { + + this.scene.remove( helper ); + + } + + } + + } + + this._debugMeshes = []; + this._shadowCamHelpers = []; + + } + +} + +export { TileShadowNodeHelper }; diff --git a/examples/jsm/tsl/utils/Raymarching.js b/examples/jsm/tsl/utils/Raymarching.js new file mode 100644 index 00000000000000..a1a60d3365c424 --- /dev/null +++ b/examples/jsm/tsl/utils/Raymarching.js @@ -0,0 +1,70 @@ +import { varying, vec4, modelWorldMatrixInverse, cameraPosition, positionGeometry, float, Fn, Loop, max, min, vec2, vec3 } from 'three/tsl'; + +/** + * @module Raymarching + * @three_import import { RaymarchingBox } from 'three/addons/tsl/utils/Raymarching.js'; + */ + +const hitBox = /*@__PURE__*/ Fn( ( { orig, dir } ) => { + + const box_min = vec3( - 0.5 ); + const box_max = vec3( 0.5 ); + + const inv_dir = dir.reciprocal(); + + const tmin_tmp = box_min.sub( orig ).mul( inv_dir ); + const tmax_tmp = box_max.sub( orig ).mul( inv_dir ); + + const tmin = min( tmin_tmp, tmax_tmp ); + const tmax = max( tmin_tmp, tmax_tmp ); + + const t0 = max( tmin.x, max( tmin.y, tmin.z ) ); + const t1 = min( tmax.x, min( tmax.y, tmax.z ) ); + + return vec2( t0, t1 ); + +} ); + +/** + * TSL function for performing raymarching in a box-area using the specified number of steps + * and a callback function. + * + * ```js + * RaymarchingBox( count, ( { positionRay } ) => { + * + * } ); + * ``` + * + * @tsl + * @function + * @param {number|Node} steps - The number of steps for raymarching. + * @param {Function|FunctionNode} callback - The callback function to execute at each step. + */ +export const RaymarchingBox = ( steps, callback ) => { + + const vOrigin = varying( vec3( modelWorldMatrixInverse.mul( vec4( cameraPosition, 1.0 ) ) ) ); + const vDirection = varying( positionGeometry.sub( vOrigin ) ); + + const rayDir = vDirection.normalize(); + const bounds = vec2( hitBox( { orig: vOrigin, dir: rayDir } ) ).toVar(); + + bounds.x.greaterThan( bounds.y ).discard(); + + bounds.assign( vec2( max( bounds.x, 0.0 ), bounds.y ) ); + + const inc = vec3( rayDir.abs().reciprocal() ).toVar(); + const delta = float( min( inc.x, min( inc.y, inc.z ) ) ).toVar(); + + delta.divAssign( float( steps ) ); + + const positionRay = vec3( vOrigin.add( bounds.x.mul( rayDir ) ) ).toVar(); + + Loop( { type: 'float', start: bounds.x, end: bounds.y, update: delta }, () => { + + callback( { positionRay } ); + + positionRay.addAssign( rayDir.mul( delta ) ); + + } ); + +}; diff --git a/examples/jsm/utils/BufferGeometryUtils.js b/examples/jsm/utils/BufferGeometryUtils.js index ff86be0b7a8de9..611478bf4177c2 100644 --- a/examples/jsm/utils/BufferGeometryUtils.js +++ b/examples/jsm/utils/BufferGeometryUtils.js @@ -11,6 +11,30 @@ import { Vector3, } from 'three'; +/** + * @module BufferGeometryUtils + * @three_import import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js'; + */ + +/** + * Computes vertex tangents using the MikkTSpace algorithm. MikkTSpace generates the same tangents consistently, + * and is used in most modelling tools and normal map bakers. Use MikkTSpace for materials with normal maps, + * because inconsistent tangents may lead to subtle visual issues in the normal map, particularly around mirrored + * UV seams. + * + * In comparison to this method, {@link BufferGeometry#computeTangents} (a custom algorithm) generates tangents that + * probably will not match the tangents in other software. The custom algorithm is sufficient for general use with a + * custom material, and may be faster than MikkTSpace. + * + * Returns the original BufferGeometry. Indexed geometries will be de-indexed. Requires position, normal, and uv attributes. + * + * @param {BufferGeometry} geometry - The geometry to compute tangents for. + * @param {Object} MikkTSpace - Instance of `examples/jsm/libs/mikktspace.module.js`, or `mikktspace` npm package. + * Await `MikkTSpace.ready` before use. + * @param {boolean} [negateSign=true] - Whether to negate the sign component (.w) of each tangent. + * Required for normal map conventions in some formats, including glTF. + * @return {BufferGeometry} The updated geometry. + */ function computeMikkTSpaceTangents( geometry, MikkTSpace, negateSign = true ) { if ( ! MikkTSpace || ! MikkTSpace.isReady ) { @@ -100,9 +124,11 @@ function computeMikkTSpaceTangents( geometry, MikkTSpace, negateSign = true ) { } /** - * @param {Array} geometries - * @param {Boolean} useGroups - * @return {BufferGeometry} + * Merges a set of geometries into a single instance. All geometries must have compatible attributes. + * + * @param {Array} geometries - The geometries to merge. + * @param {boolean} [useGroups=false] - Whether to use groups or not. + * @return {?BufferGeometry} The merged geometry. Returns `null` if the merge does not succeed. */ function mergeGeometries( geometries, useGroups = false ) { @@ -296,8 +322,11 @@ function mergeGeometries( geometries, useGroups = false ) { } /** - * @param {Array} attributes - * @return {BufferAttribute} + * Merges a set of attributes into a single instance. All attributes must have compatible properties and types. + * Instances of {@link InterleavedBufferAttribute} are not supported. + * + * @param {Array} attributes - The attributes to merge. + * @return {?BufferAttribute} The merged attribute. Returns `null` if the merge does not succeed. */ function mergeAttributes( attributes ) { @@ -311,13 +340,6 @@ function mergeAttributes( attributes ) { const attribute = attributes[ i ]; - if ( attribute.isInterleavedBufferAttribute ) { - - console.error( 'THREE.BufferGeometryUtils: .mergeAttributes() failed. InterleavedBufferAttributes are not supported.' ); - return null; - - } - if ( TypedArray === undefined ) TypedArray = attribute.array.constructor; if ( TypedArray !== attribute.array.constructor ) { @@ -350,22 +372,41 @@ function mergeAttributes( attributes ) { } - arrayLength += attribute.array.length; + arrayLength += attribute.count * itemSize; } const array = new TypedArray( arrayLength ); + const result = new BufferAttribute( array, itemSize, normalized ); let offset = 0; for ( let i = 0; i < attributes.length; ++ i ) { - array.set( attributes[ i ].array, offset ); + const attribute = attributes[ i ]; + if ( attribute.isInterleavedBufferAttribute ) { + + const tupleOffset = offset / itemSize; + for ( let j = 0, l = attribute.count; j < l; j ++ ) { + + for ( let c = 0; c < itemSize; c ++ ) { + + const value = attribute.getComponent( j, c ); + result.setComponent( j + tupleOffset, c, value ); + + } + + } + + } else { + + array.set( attribute.array, offset ); + + } - offset += attributes[ i ].array.length; + offset += attribute.count * itemSize; } - const result = new BufferAttribute( array, itemSize, normalized ); if ( gpuType !== undefined ) { result.gpuType = gpuType; @@ -377,10 +418,12 @@ function mergeAttributes( attributes ) { } /** - * @param {BufferAttribute} - * @return {BufferAttribute} + * Performs a deep clone of the given buffer attribute. + * + * @param {BufferAttribute} attribute - The attribute to clone. + * @return {BufferAttribute} The cloned attribute. */ -export function deepCloneAttribute( attribute ) { +function deepCloneAttribute( attribute ) { if ( attribute.isInstancedInterleavedBufferAttribute || attribute.isInterleavedBufferAttribute ) { @@ -399,8 +442,11 @@ export function deepCloneAttribute( attribute ) { } /** - * @param {Array} attributes - * @return {Array} + * Interleaves a set of attributes and returns a new array of corresponding attributes that share a + * single {@link InterleavedBuffer} instance. All attributes must have compatible types. + * + * @param {Array} attributes - The attributes to interleave. + * @return {Array} An array of interleaved attributes. If interleave does not succeed, the method returns `null`. */ function interleaveAttributes( attributes ) { @@ -463,8 +509,13 @@ function interleaveAttributes( attributes ) { } -// returns a new, non-interleaved version of the provided attribute -export function deinterleaveAttribute( attribute ) { +/** + * Returns a new, non-interleaved version of the given attribute. + * + * @param {InterleavedBufferAttribute} attribute - The interleaved attribute. + * @return {BufferAttribute} The non-interleaved attribute. + */ +function deinterleaveAttribute( attribute ) { const cons = attribute.data.array.constructor; const count = attribute.count; @@ -511,8 +562,12 @@ export function deinterleaveAttribute( attribute ) { } -// deinterleaves all attributes on the geometry -export function deinterleaveGeometry( geometry ) { +/** + * Deinterleaves all attributes on the given geometry. + * + * @param {BufferGeometry} geometry - The geometry to deinterleave. + */ +function deinterleaveGeometry( geometry ) { const attributes = geometry.attributes; const morphTargets = geometry.morphTargets; @@ -555,8 +610,10 @@ export function deinterleaveGeometry( geometry ) { } /** - * @param {Array} geometry - * @return {number} + * Returns the amount of bytes used by all attributes to represent the geometry. + * + * @param {BufferGeometry} geometry - The geometry. + * @return {number} The estimate bytes used. */ function estimateBytesUsed( geometry ) { @@ -578,9 +635,11 @@ function estimateBytesUsed( geometry ) { } /** - * @param {BufferGeometry} geometry - * @param {number} tolerance - * @return {BufferGeometry} + * Returns a new geometry with vertices for which all similar vertex attributes (within tolerance) are merged. + * + * @param {BufferGeometry} geometry - The geometry to merge vertices for. + * @param {number} [tolerance=1e-4] - The tolerance value. + * @return {BufferGeometry} - The new geometry with merged vertices. */ function mergeVertices( geometry, tolerance = 1e-4 ) { @@ -611,28 +670,32 @@ function mergeVertices( geometry, tolerance = 1e-4 ) { const name = attributeNames[ i ]; const attr = geometry.attributes[ name ]; - tmpAttributes[ name ] = new BufferAttribute( + tmpAttributes[ name ] = new attr.constructor( new attr.array.constructor( attr.count * attr.itemSize ), attr.itemSize, attr.normalized ); - const morphAttr = geometry.morphAttributes[ name ]; - if ( morphAttr ) { + const morphAttributes = geometry.morphAttributes[ name ]; + if ( morphAttributes ) { - tmpMorphAttributes[ name ] = new BufferAttribute( - new morphAttr.array.constructor( morphAttr.count * morphAttr.itemSize ), - morphAttr.itemSize, - morphAttr.normalized - ); + if ( ! tmpMorphAttributes[ name ] ) tmpMorphAttributes[ name ] = []; + morphAttributes.forEach( ( morphAttr, i ) => { + + const array = new morphAttr.array.constructor( morphAttr.count * morphAttr.itemSize ); + tmpMorphAttributes[ name ][ i ] = new morphAttr.constructor( array, morphAttr.itemSize, morphAttr.normalized ); + + } ); } } // convert the error tolerance to an amount of decimal places to truncate to - const decimalShift = Math.log10( 1 / tolerance ); - const shiftMultiplier = Math.pow( 10, decimalShift ); + const halfTolerance = tolerance * 0.5; + const exponent = Math.log10( 1 / tolerance ); + const hashMultiplier = Math.pow( 10, exponent ); + const hashAdditive = halfTolerance * hashMultiplier; for ( let i = 0; i < vertexCount; i ++ ) { const index = indices ? indices.getX( i ) : i; @@ -648,7 +711,7 @@ function mergeVertices( geometry, tolerance = 1e-4 ) { for ( let k = 0; k < itemSize; k ++ ) { // double tilde truncates the decimal value - hash += `${ ~ ~ ( attribute[ getters[ k ] ]( index ) * shiftMultiplier ) },`; + hash += `${ ~ ~ ( attribute[ getters[ k ] ]( index ) * hashMultiplier + hashAdditive ) },`; } @@ -667,22 +730,22 @@ function mergeVertices( geometry, tolerance = 1e-4 ) { const name = attributeNames[ j ]; const attribute = geometry.getAttribute( name ); - const morphAttr = geometry.morphAttributes[ name ]; + const morphAttributes = geometry.morphAttributes[ name ]; const itemSize = attribute.itemSize; - const newarray = tmpAttributes[ name ]; + const newArray = tmpAttributes[ name ]; const newMorphArrays = tmpMorphAttributes[ name ]; for ( let k = 0; k < itemSize; k ++ ) { const getterFunc = getters[ k ]; const setterFunc = setters[ k ]; - newarray[ setterFunc ]( nextIndex, attribute[ getterFunc ]( index ) ); + newArray[ setterFunc ]( nextIndex, attribute[ getterFunc ]( index ) ); - if ( morphAttr ) { + if ( morphAttributes ) { - for ( let m = 0, ml = morphAttr.length; m < ml; m ++ ) { + for ( let m = 0, ml = morphAttributes.length; m < ml; m ++ ) { - newMorphArrays[ m ][ setterFunc ]( nextIndex, morphAttr[ m ][ getterFunc ]( index ) ); + newMorphArrays[ m ][ setterFunc ]( nextIndex, morphAttributes[ m ][ getterFunc ]( index ) ); } @@ -706,7 +769,7 @@ function mergeVertices( geometry, tolerance = 1e-4 ) { const tmpAttribute = tmpAttributes[ name ]; - result.setAttribute( name, new BufferAttribute( + result.setAttribute( name, new tmpAttribute.constructor( tmpAttribute.array.slice( 0, nextIndex * tmpAttribute.itemSize ), tmpAttribute.itemSize, tmpAttribute.normalized, @@ -718,7 +781,7 @@ function mergeVertices( geometry, tolerance = 1e-4 ) { const tmpMorphAttribute = tmpMorphAttributes[ name ][ j ]; - result.morphAttributes[ name ][ j ] = new BufferAttribute( + result.morphAttributes[ name ][ j ] = new tmpMorphAttribute.constructor( tmpMorphAttribute.array.slice( 0, nextIndex * tmpMorphAttribute.itemSize ), tmpMorphAttribute.itemSize, tmpMorphAttribute.normalized, @@ -737,9 +800,12 @@ function mergeVertices( geometry, tolerance = 1e-4 ) { } /** - * @param {BufferGeometry} geometry - * @param {number} drawMode - * @return {BufferGeometry} + * Returns a new indexed geometry based on `TrianglesDrawMode` draw mode. + * This mode corresponds to the `gl.TRIANGLES` primitive in WebGL. + * + * @param {BufferGeometry} geometry - The geometry to convert. + * @param {number} drawMode - The current draw mode. + * @return {BufferGeometry} The new geometry using `TrianglesDrawMode`. */ function toTrianglesDrawMode( geometry, drawMode ) { @@ -848,9 +914,13 @@ function toTrianglesDrawMode( geometry, drawMode ) { /** * Calculates the morphed attributes of a morphed/skinned BufferGeometry. - * Helpful for Raytracing or Decals. - * @param {Mesh | Line | Points} object An instance of Mesh, Line or Points. - * @return {Object} An Object with original position/normal attributes and morphed ones. + * + * Helpful for Raytracing or Decals (i.e. a `DecalGeometry` applied to a morphed Object with a `BufferGeometry` + * will use the original `BufferGeometry`, not the morphed/skinned one, generating an incorrect result. + * Using this function to create a shadow `Object3`D the `DecalGeometry` can be correctly generated). + * + * @param {Mesh|Line|Points} object - The 3D object to compute morph attributes for. + * @return {Object} An object with original position/normal attributes and morphed ones. */ function computeMorphedAttributes( object ) { @@ -1126,6 +1196,12 @@ function computeMorphedAttributes( object ) { } +/** + * Merges the {@link BufferGeometry#groups} for the given geometry. + * + * @param {BufferGeometry} geometry - The geometry to modify. + * @return {BufferGeometry} - The updated geometry + */ function mergeGroups( geometry ) { if ( geometry.groups.length === 0 ) { @@ -1228,15 +1304,21 @@ function mergeGroups( geometry ) { } - -// Creates a new, non-indexed geometry with smooth normals everywhere except faces that meet at -// an angle greater than the crease angle. +/** + * Modifies the supplied geometry if it is non-indexed, otherwise creates a new, + * non-indexed geometry. Returns the geometry with smooth normals everywhere except + * faces that meet at an angle greater than the crease angle. + * + * @param {BufferGeometry} geometry - The geometry to modify. + * @param {number} [creaseAngle=Math.PI/3] - The crease angle in radians. + * @return {BufferGeometry} - The updated geometry + */ function toCreasedNormals( geometry, creaseAngle = Math.PI / 3 /* 60 degrees */ ) { const creaseDot = Math.cos( creaseAngle ); const hashMultiplier = ( 1 + 1e-10 ) * 1e2; - // reusable vertors + // reusable vectors const verts = [ new Vector3(), new Vector3(), new Vector3() ]; const tempVec1 = new Vector3(); const tempVec2 = new Vector3(); @@ -1253,7 +1335,9 @@ function toCreasedNormals( geometry, creaseAngle = Math.PI / 3 /* 60 degrees */ } - const resultGeometry = geometry.toNonIndexed(); + // BufferGeometry.toNonIndexed() warns if the geometry is non-indexed + // and returns the original geometry + const resultGeometry = geometry.index ? geometry.toNonIndexed() : geometry; const posAttr = resultGeometry.attributes.position; const vertexMap = {}; @@ -1334,26 +1418,13 @@ function toCreasedNormals( geometry, creaseAngle = Math.PI / 3 /* 60 degrees */ } -function mergeBufferGeometries( geometries, useGroups = false ) { - - console.warn( 'THREE.BufferGeometryUtils: mergeBufferGeometries() has been renamed to mergeGeometries().' ); // @deprecated, r151 - return mergeGeometries( geometries, useGroups ); - -} - -function mergeBufferAttributes( attributes ) { - - console.warn( 'THREE.BufferGeometryUtils: mergeBufferAttributes() has been renamed to mergeAttributes().' ); // @deprecated, r151 - return mergeAttributes( attributes ); - -} - export { computeMikkTSpaceTangents, mergeGeometries, - mergeBufferGeometries, mergeAttributes, - mergeBufferAttributes, + deepCloneAttribute, + deinterleaveAttribute, + deinterleaveGeometry, interleaveAttributes, estimateBytesUsed, mergeVertices, diff --git a/examples/jsm/utils/CameraUtils.js b/examples/jsm/utils/CameraUtils.js index f7fa1b15858036..c50551356aff3b 100644 --- a/examples/jsm/utils/CameraUtils.js +++ b/examples/jsm/utils/CameraUtils.js @@ -4,6 +4,11 @@ import { Vector3 } from 'three'; +/** + * @module CameraUtils + * @three_import import * as CameraUtils from 'three/addons/utils/CameraUtils.js'; + */ + const _va = /*@__PURE__*/ new Vector3(), // from pe to pa _vb = /*@__PURE__*/ new Vector3(), // from pe to pb _vc = /*@__PURE__*/ new Vector3(), // from pe to pc @@ -14,14 +19,18 @@ const _va = /*@__PURE__*/ new Vector3(), // from pe to pa _quat = /*@__PURE__*/ new Quaternion(); // temporary quaternion -/** Set a PerspectiveCamera's projectionMatrix and quaternion +/** + * Set projection matrix and the orientation of a perspective camera * to exactly frame the corners of an arbitrary rectangle. * NOTE: This function ignores the standard parameters; - * do not call updateProjectionMatrix() after this! - * @param {Vector3} bottomLeftCorner - * @param {Vector3} bottomRightCorner - * @param {Vector3} topLeftCorner - * @param {boolean} estimateViewFrustum */ + * do not call `updateProjectionMatrix()` after this. + * + * @param {PerspectiveCamera} camera - The camera. + * @param {Vector3} bottomLeftCorner - The bottom-left corner point. + * @param {Vector3} bottomRightCorner - The bottom-right corner point. + * @param {Vector3} topLeftCorner - The top-left corner point. + * @param {boolean} [estimateViewFrustum=false] - If set to `true`, the function tries to estimate the camera's FOV. + */ function frameCorners( camera, bottomLeftCorner, bottomRightCorner, topLeftCorner, estimateViewFrustum = false ) { const pa = bottomLeftCorner, pb = bottomRightCorner, pc = topLeftCorner; diff --git a/examples/jsm/utils/GPUStatsPanel.js b/examples/jsm/utils/GPUStatsPanel.js deleted file mode 100644 index cf6aac15425ef4..00000000000000 --- a/examples/jsm/utils/GPUStatsPanel.js +++ /dev/null @@ -1,128 +0,0 @@ -import Stats from '../libs/stats.module.js'; - -// https://www.khronos.org/registry/webgl/extensions/EXT_disjoint_timer_query/ -// https://www.khronos.org/registry/webgl/extensions/EXT_disjoint_timer_query_webgl2/ -export class GPUStatsPanel extends Stats.Panel { - - constructor( context, name = 'GPU MS' ) { - - super( name, '#f90', '#210' ); - - let isWebGL2 = true; - let extension = context.getExtension( 'EXT_disjoint_timer_query_webgl2' ); - if ( extension === null ) { - - isWebGL2 = false; - extension = context.getExtension( 'EXT_disjoint_timer_query' ); - - if ( extension === null ) { - - console.warn( 'GPUStatsPanel: disjoint_time_query extension not available.' ); - - } - - } - - this.context = context; - this.extension = extension; - this.maxTime = 30; - this.activeQueries = 0; - - this.startQuery = function () { - - const gl = this.context; - const ext = this.extension; - - if ( ext === null ) { - - return; - - } - - // create the query object - let query; - if ( isWebGL2 ) { - - query = gl.createQuery(); - gl.beginQuery( ext.TIME_ELAPSED_EXT, query ); - - } else { - - query = ext.createQueryEXT(); - ext.beginQueryEXT( ext.TIME_ELAPSED_EXT, query ); - - } - - this.activeQueries ++; - - const checkQuery = () => { - - // check if the query is available and valid - let available, disjoint, ns; - if ( isWebGL2 ) { - - available = gl.getQueryParameter( query, gl.QUERY_RESULT_AVAILABLE ); - disjoint = gl.getParameter( ext.GPU_DISJOINT_EXT ); - ns = gl.getQueryParameter( query, gl.QUERY_RESULT ); - - } else { - - available = ext.getQueryObjectEXT( query, ext.QUERY_RESULT_AVAILABLE_EXT ); - disjoint = gl.getParameter( ext.GPU_DISJOINT_EXT ); - ns = ext.getQueryObjectEXT( query, ext.QUERY_RESULT_EXT ); - - } - - const ms = ns * 1e-6; - if ( available ) { - - // update the display if it is valid - if ( ! disjoint ) { - - this.update( ms, this.maxTime ); - - } - - this.activeQueries --; - - - } else if ( gl.isContextLost() === false ) { - - // otherwise try again the next frame - requestAnimationFrame( checkQuery ); - - } - - }; - - requestAnimationFrame( checkQuery ); - - }; - - this.endQuery = function () { - - // finish the query measurement - const ext = this.extension; - const gl = this.context; - - if ( ext === null ) { - - return; - - } - - if ( isWebGL2 ) { - - gl.endQuery( ext.TIME_ELAPSED_EXT ); - - } else { - - ext.endQueryEXT( ext.TIME_ELAPSED_EXT ); - - } - - }; - - } - -} diff --git a/examples/jsm/utils/GeometryCompressionUtils.js b/examples/jsm/utils/GeometryCompressionUtils.js index 0be91d568c15bf..9d14bd0ac66b4d 100644 --- a/examples/jsm/utils/GeometryCompressionUtils.js +++ b/examples/jsm/utils/GeometryCompressionUtils.js @@ -1,41 +1,30 @@ -/** - * Octahedron and Quantization encodings based on work by: - * - * @link https://github.com/tsherif/mesh-quantization-example - * - */ - import { BufferAttribute, Matrix3, Matrix4, Vector3 } from 'three'; -import { PackedPhongMaterial } from './PackedPhongMaterial.js'; +/** + * @module GeometryCompressionUtils + * @three_import import * as GeometryCompressionUtils from 'three/addons/utils/GeometryCompressionUtils.js'; + */ +// Octahedron and Quantization encodings based on work by: https://github.com/tsherif/mesh-quantization-example /** - * Make the input mesh.geometry's normal attribute encoded and compressed by 3 different methods. - * Also will change the mesh.material to `PackedPhongMaterial` which let the vertex shader program decode the normal data. - * - * @param {THREE.Mesh} mesh - * @param {String} encodeMethod "DEFAULT" || "OCT1Byte" || "OCT2Byte" || "ANGLES" + * Compressed the given geometry's `normal` attribute by the selected encode method. * + * @param {BufferGeometry} geometry - The geometry whose normals should be compressed. + * @param {('DEFAULT'|'OCT1Byte'|'OCT2Byte'|'ANGLES')} encodeMethod - The compression method. */ -function compressNormals( mesh, encodeMethod ) { +function compressNormals( geometry, encodeMethod ) { - if ( ! mesh.geometry ) { - - console.error( 'Mesh must contain geometry. ' ); - - } - - const normal = mesh.geometry.attributes.normal; + const normal = geometry.attributes.normal; if ( ! normal ) { - console.error( 'Geometry must contain normal attribute. ' ); + console.error( 'THREE.GeometryCompressionUtils.compressNormals(): Geometry must contain normal attribute.' ); } @@ -43,7 +32,7 @@ function compressNormals( mesh, encodeMethod ) { if ( normal.itemSize != 3 ) { - console.error( 'normal.itemSize is not 3, which cannot be encoded. ' ); + console.error( 'THREE.GeometryCompressionUtils.compressNormals(): normal.itemSize is not 3, which cannot be encoded.' ); } @@ -66,16 +55,15 @@ function compressNormals( mesh, encodeMethod ) { } - mesh.geometry.setAttribute( 'normal', new BufferAttribute( result, 3, true ) ); - mesh.geometry.attributes.normal.bytes = result.length * 1; + geometry.setAttribute( 'normal', new BufferAttribute( result, 3, true ) ); + geometry.attributes.normal.bytes = result.length * 1; } else if ( encodeMethod == 'OCT1Byte' ) { - /** - * It is not recommended to use 1-byte octahedron normals encoding unless you want to extremely reduce the memory usage - * As it makes vertex data not aligned to a 4 byte boundary which may harm some WebGL implementations and sometimes the normal distortion is visible - * Please refer to @zeux 's comments in https://github.com/mrdoob/three.js/pull/18208 - */ + + // It is not recommended to use 1-byte octahedron normals encoding unless you want to extremely reduce the memory usage + // As it makes vertex data not aligned to a 4 byte boundary which may harm some WebGL implementations and sometimes the normal distortion is visible + // Please refer to @zeux 's comments in https://github.com/mrdoob/three.js/pull/18208 result = new Int8Array( count * 2 ); @@ -88,8 +76,8 @@ function compressNormals( mesh, encodeMethod ) { } - mesh.geometry.setAttribute( 'normal', new BufferAttribute( result, 2, true ) ); - mesh.geometry.attributes.normal.bytes = result.length * 1; + geometry.setAttribute( 'normal', new BufferAttribute( result, 2, true ) ); + geometry.attributes.normal.bytes = result.length * 1; } else if ( encodeMethod == 'OCT2Byte' ) { @@ -104,8 +92,8 @@ function compressNormals( mesh, encodeMethod ) { } - mesh.geometry.setAttribute( 'normal', new BufferAttribute( result, 2, true ) ); - mesh.geometry.attributes.normal.bytes = result.length * 2; + geometry.setAttribute( 'normal', new BufferAttribute( result, 2, true ) ); + geometry.attributes.normal.bytes = result.length * 2; } else if ( encodeMethod == 'ANGLES' ) { @@ -120,8 +108,8 @@ function compressNormals( mesh, encodeMethod ) { } - mesh.geometry.setAttribute( 'normal', new BufferAttribute( result, 2, true ) ); - mesh.geometry.attributes.normal.bytes = result.length * 2; + geometry.setAttribute( 'normal', new BufferAttribute( result, 2, true ) ); + geometry.attributes.normal.bytes = result.length * 2; } else { @@ -129,64 +117,24 @@ function compressNormals( mesh, encodeMethod ) { } - mesh.geometry.attributes.normal.needsUpdate = true; - mesh.geometry.attributes.normal.isPacked = true; - mesh.geometry.attributes.normal.packingMethod = encodeMethod; - - // modify material - if ( ! ( mesh.material instanceof PackedPhongMaterial ) ) { - - mesh.material = new PackedPhongMaterial().copy( mesh.material ); - - } - - if ( encodeMethod == 'ANGLES' ) { - - mesh.material.defines.USE_PACKED_NORMAL = 0; - - } - - if ( encodeMethod == 'OCT1Byte' ) { - - mesh.material.defines.USE_PACKED_NORMAL = 1; - - } - - if ( encodeMethod == 'OCT2Byte' ) { - - mesh.material.defines.USE_PACKED_NORMAL = 1; - - } - - if ( encodeMethod == 'DEFAULT' ) { - - mesh.material.defines.USE_PACKED_NORMAL = 2; - - } + geometry.attributes.normal.needsUpdate = true; + geometry.attributes.normal.isPacked = true; + geometry.attributes.normal.packingMethod = encodeMethod; } - /** - * Make the input mesh.geometry's position attribute encoded and compressed. - * Also will change the mesh.material to `PackedPhongMaterial` which let the vertex shader program decode the position data. - * - * @param {THREE.Mesh} mesh - * - */ -function compressPositions( mesh ) { - - if ( ! mesh.geometry ) { - - console.error( 'Mesh must contain geometry. ' ); - - } + * Compressed the given geometry's `position` attribute. + * + * @param {BufferGeometry} geometry - The geometry whose position values should be compressed. + */ +function compressPositions( geometry ) { - const position = mesh.geometry.attributes.position; + const position = geometry.attributes.position; if ( ! position ) { - console.error( 'Geometry must contain position attribute. ' ); + console.error( 'THREE.GeometryCompressionUtils.compressPositions(): Geometry must contain position attribute.' ); } @@ -194,7 +142,7 @@ function compressPositions( mesh ) { if ( position.itemSize != 3 ) { - console.error( 'position.itemSize is not 3, which cannot be packed. ' ); + console.error( 'THREE.GeometryCompressionUtils.compressPositions(): position.itemSize is not 3, which cannot be packed.' ); } @@ -204,51 +152,30 @@ function compressPositions( mesh ) { const result = quantizedEncode( array, encodingBytes ); const quantized = result.quantized; - const decodeMat = result.decodeMat; // IMPORTANT: calculate original geometry bounding info first, before updating packed positions - if ( mesh.geometry.boundingBox == null ) mesh.geometry.computeBoundingBox(); - if ( mesh.geometry.boundingSphere == null ) mesh.geometry.computeBoundingSphere(); - - mesh.geometry.setAttribute( 'position', new BufferAttribute( quantized, 3 ) ); - mesh.geometry.attributes.position.isPacked = true; - mesh.geometry.attributes.position.needsUpdate = true; - mesh.geometry.attributes.position.bytes = quantized.length * encodingBytes; - - // modify material - if ( ! ( mesh.material instanceof PackedPhongMaterial ) ) { - - mesh.material = new PackedPhongMaterial().copy( mesh.material ); + if ( geometry.boundingBox == null ) geometry.computeBoundingBox(); + if ( geometry.boundingSphere == null ) geometry.computeBoundingSphere(); - } - - mesh.material.defines.USE_PACKED_POSITION = 0; - - mesh.material.uniforms.quantizeMatPos.value = decodeMat; - mesh.material.uniforms.quantizeMatPos.needsUpdate = true; + geometry.setAttribute( 'position', new BufferAttribute( quantized, 3 ) ); + geometry.attributes.position.isPacked = true; + geometry.attributes.position.needsUpdate = true; + geometry.attributes.position.bytes = quantized.length * encodingBytes; } /** - * Make the input mesh.geometry's uv attribute encoded and compressed. - * Also will change the mesh.material to `PackedPhongMaterial` which let the vertex shader program decode the uv data. - * - * @param {THREE.Mesh} mesh + * Compressed the given geometry's `uv` attribute. * + * @param {BufferGeometry} geometry - The geometry whose texture coordinates should be compressed. */ -function compressUvs( mesh ) { +function compressUvs( geometry ) { - if ( ! mesh.geometry ) { - - console.error( 'Mesh must contain geometry property. ' ); - - } - - const uvs = mesh.geometry.attributes.uv; + const uvs = geometry.attributes.uv; if ( ! uvs ) { - console.error( 'Geometry must contain uv attribute. ' ); + console.error( 'THREE.GeometryCompressionUtils.compressUvs(): Geometry must contain uv attribute.' ); } @@ -281,39 +208,20 @@ function compressUvs( mesh ) { } - mesh.geometry.setAttribute( 'uv', new BufferAttribute( result, 2, true ) ); - mesh.geometry.attributes.uv.isPacked = true; - mesh.geometry.attributes.uv.needsUpdate = true; - mesh.geometry.attributes.uv.bytes = result.length * 2; - - if ( ! ( mesh.material instanceof PackedPhongMaterial ) ) { - - mesh.material = new PackedPhongMaterial().copy( mesh.material ); - - } - - mesh.material.defines.USE_PACKED_UV = 0; + geometry.setAttribute( 'uv', new BufferAttribute( result, 2, true ) ); + geometry.attributes.uv.isPacked = true; + geometry.attributes.uv.needsUpdate = true; + geometry.attributes.uv.bytes = result.length * 2; } else { // use quantized encoding method result = quantizedEncodeUV( array, 2 ); - mesh.geometry.setAttribute( 'uv', new BufferAttribute( result.quantized, 2 ) ); - mesh.geometry.attributes.uv.isPacked = true; - mesh.geometry.attributes.uv.needsUpdate = true; - mesh.geometry.attributes.uv.bytes = result.quantized.length * 2; - - if ( ! ( mesh.material instanceof PackedPhongMaterial ) ) { - - mesh.material = new PackedPhongMaterial().copy( mesh.material ); - - } - - mesh.material.defines.USE_PACKED_UV = 1; - - mesh.material.uniforms.quantizeMatUV.value = result.decodeMat; - mesh.material.uniforms.quantizeMatUV.needsUpdate = true; + geometry.setAttribute( 'uv', new BufferAttribute( result.quantized, 2 ) ); + geometry.attributes.uv.isPacked = true; + geometry.attributes.uv.needsUpdate = true; + geometry.attributes.uv.bytes = result.quantized.length * 2; } diff --git a/examples/jsm/utils/GeometryUtils.js b/examples/jsm/utils/GeometryUtils.js index 4f3452ef6d24d4..7e831f64df1c9b 100644 --- a/examples/jsm/utils/GeometryUtils.js +++ b/examples/jsm/utils/GeometryUtils.js @@ -1,19 +1,23 @@ import { Vector3 } from 'three'; +/** + * @module GeometryUtils + * @three_import import * as GeometryUtils from 'three/addons/utils/GeometryUtils.js'; + */ /** - * Generates 2D-Coordinates in a very fast way. + * Generates 2D-Coordinates along a Hilbert curve. * - * Based on work by: - * @link http://www.openprocessing.org/sketch/15493 + * Based on work by: {@link http://www.openprocessing.org/sketch/15493} * - * @param center Center of Hilbert curve. - * @param size Total width of Hilbert curve. - * @param iterations Number of subdivisions. - * @param v0 Corner index -X, -Z. - * @param v1 Corner index -X, +Z. - * @param v2 Corner index +X, +Z. - * @param v3 Corner index +X, -Z. + * @param {Vector3} [center] - Center of Hilbert curve. + * @param {number} [size=10] - Total width of Hilbert curve. + * @param {number} [iterations=10] - Number of subdivisions. + * @param {number} [v0=0] - Corner index -X, -Z. + * @param {number} [v1=1] - Corner index -X, +Z. + * @param {number} [v2=2] - Corner index +X, +Z. + * @param {number} [v3=3] - Corner index +X, -Z. + * @returns {Array} The Hilbert curve points. */ function hilbert2D( center = new Vector3( 0, 0, 0 ), size = 10, iterations = 1, v0 = 0, v1 = 1, v2 = 2, v3 = 3 ) { @@ -51,22 +55,22 @@ function hilbert2D( center = new Vector3( 0, 0, 0 ), size = 10, iterations = 1, } /** - * Generates 3D-Coordinates in a very fast way. + * Generates 3D-Coordinates along a Hilbert curve. * - * Based on work by: - * @link https://openprocessing.org/user/5654 + * Based on work by: {@link https://openprocessing.org/user/5654} * - * @param center Center of Hilbert curve. - * @param size Total width of Hilbert curve. - * @param iterations Number of subdivisions. - * @param v0 Corner index -X, +Y, -Z. - * @param v1 Corner index -X, +Y, +Z. - * @param v2 Corner index -X, -Y, +Z. - * @param v3 Corner index -X, -Y, -Z. - * @param v4 Corner index +X, -Y, -Z. - * @param v5 Corner index +X, -Y, +Z. - * @param v6 Corner index +X, +Y, +Z. - * @param v7 Corner index +X, +Y, -Z. + * @param {Vector3} [center] - Center of Hilbert curve. + * @param {number} [size=10] - Total width of Hilbert curve. + * @param {number} [iterations=1] - Number of subdivisions. + * @param {number} [v0=0] - Corner index -X, +Y, -Z. + * @param {number} [v1=1] - Corner index -X, +Y, +Z. + * @param {number} [v2=2] - Corner index -X, -Y, +Z. + * @param {number} [v3=3] - Corner index -X, -Y, -Z. + * @param {number} [v4=4] - Corner index +X, -Y, -Z. + * @param {number} [v5=5] - Corner index +X, -Y, +Z. + * @param {number} [v6=6] - Corner index +X, +Y, +Z. + * @param {number} [v7=7] - Corner index +X, +Y, -Z. + * @returns {Array} - The Hilbert curve points. */ function hilbert3D( center = new Vector3( 0, 0, 0 ), size = 10, iterations = 1, v0 = 0, v1 = 1, v2 = 2, v3 = 3, v4 = 4, v5 = 5, v6 = 6, v7 = 7 ) { @@ -117,11 +121,12 @@ function hilbert3D( center = new Vector3( 0, 0, 0 ), size = 10, iterations = 1, } /** - * Generates a Gosper curve (lying in the XY plane) + * Generates a Gosper curve (lying in the XY plane). * - * https://gist.github.com/nitaku/6521802 + * Reference: {@link https://gist.github.com/nitaku/6521802} * - * @param size The size of a single gosper island. + * @param {number} [size=1] - The size of a single gosper island. + * @return {Array} The gosper island points. */ function gosper( size = 1 ) { diff --git a/examples/jsm/utils/LDrawUtils.js b/examples/jsm/utils/LDrawUtils.js index 64bb11c226ff4a..96726fff040120 100644 --- a/examples/jsm/utils/LDrawUtils.js +++ b/examples/jsm/utils/LDrawUtils.js @@ -9,14 +9,23 @@ import { import { mergeGeometries } from './BufferGeometryUtils.js'; +/** + * Utility class for LDraw models. + * + * @three_import import { LDrawUtils } from 'three/addons/utils/LDrawUtils.js'; + */ class LDrawUtils { + /** + * Merges geometries in the given object by materials and returns a new group object. + * Use on not indexed geometries. The object buffers reference the old object ones. + * Special treatment is done to the conditional lines generated by LDrawLoader. + * + * @param {Object3D} object - The object to merge. + * @returns {Group} The merged object. + */ static mergeObject( object ) { - // Merges geometries in object by materials and returns new object. Use on not indexed geometries. - // The object buffers reference the old object ones. - // Special treatment is done to the conditional lines generated by LDrawLoader. - function extractGroup( geometry, group, elementSize, isConditionalLine ) { // Extracts a group from a geometry as a new geometry (with attribute buffers referencing original buffers) diff --git a/examples/jsm/utils/PackedPhongMaterial.js b/examples/jsm/utils/PackedPhongMaterial.js deleted file mode 100644 index f71aab8e2d8c6b..00000000000000 --- a/examples/jsm/utils/PackedPhongMaterial.js +++ /dev/null @@ -1,178 +0,0 @@ - -/** - * `PackedPhongMaterial` inherited from THREE.MeshPhongMaterial - * - * @param {Object} parameters - */ -import { - MeshPhongMaterial, - ShaderChunk, - ShaderLib, - UniformsUtils, -} from 'three'; - -class PackedPhongMaterial extends MeshPhongMaterial { - - constructor( parameters ) { - - super(); - - this.defines = {}; - this.type = 'PackedPhongMaterial'; - this.uniforms = UniformsUtils.merge( [ - - ShaderLib.phong.uniforms, - - { - quantizeMatPos: { value: null }, - quantizeMatUV: { value: null } - } - - ] ); - - this.vertexShader = [ - '#define PHONG', - - 'varying vec3 vViewPosition;', - - ShaderChunk.common, - ShaderChunk.uv_pars_vertex, - ShaderChunk.displacementmap_pars_vertex, - ShaderChunk.envmap_pars_vertex, - ShaderChunk.color_pars_vertex, - ShaderChunk.fog_pars_vertex, - ShaderChunk.normal_pars_vertex, - ShaderChunk.morphtarget_pars_vertex, - ShaderChunk.skinning_pars_vertex, - ShaderChunk.shadowmap_pars_vertex, - ShaderChunk.logdepthbuf_pars_vertex, - ShaderChunk.clipping_planes_pars_vertex, - - `#ifdef USE_PACKED_NORMAL - #if USE_PACKED_NORMAL == 0 - vec3 decodeNormal(vec3 packedNormal) - { - float x = packedNormal.x * 2.0 - 1.0; - float y = packedNormal.y * 2.0 - 1.0; - vec2 scth = vec2(sin(x * PI), cos(x * PI)); - vec2 scphi = vec2(sqrt(1.0 - y * y), y); - return normalize( vec3(scth.y * scphi.x, scth.x * scphi.x, scphi.y) ); - } - #endif - - #if USE_PACKED_NORMAL == 1 - vec3 decodeNormal(vec3 packedNormal) - { - vec3 v = vec3(packedNormal.xy, 1.0 - abs(packedNormal.x) - abs(packedNormal.y)); - if (v.z < 0.0) - { - v.xy = (1.0 - abs(v.yx)) * vec2((v.x >= 0.0) ? +1.0 : -1.0, (v.y >= 0.0) ? +1.0 : -1.0); - } - return normalize(v); - } - #endif - - #if USE_PACKED_NORMAL == 2 - vec3 decodeNormal(vec3 packedNormal) - { - vec3 v = (packedNormal * 2.0) - 1.0; - return normalize(v); - } - #endif - #endif`, - - `#ifdef USE_PACKED_POSITION - #if USE_PACKED_POSITION == 0 - uniform mat4 quantizeMatPos; - #endif - #endif`, - - `#ifdef USE_PACKED_UV - #if USE_PACKED_UV == 1 - uniform mat3 quantizeMatUV; - #endif - #endif`, - - `#ifdef USE_PACKED_UV - #if USE_PACKED_UV == 0 - vec2 decodeUV(vec2 packedUV) - { - vec2 uv = (packedUV * 2.0) - 1.0; - return uv; - } - #endif - - #if USE_PACKED_UV == 1 - vec2 decodeUV(vec2 packedUV) - { - vec2 uv = ( vec3(packedUV, 1.0) * quantizeMatUV ).xy; - return uv; - } - #endif - #endif`, - - 'void main() {', - - ShaderChunk.uv_vertex, - - `#ifdef USE_MAP - #ifdef USE_PACKED_UV - vMapUv = decodeUV(vMapUv); - #endif - #endif`, - - ShaderChunk.color_vertex, - ShaderChunk.morphcolor_vertex, - - ShaderChunk.beginnormal_vertex, - - `#ifdef USE_PACKED_NORMAL - objectNormal = decodeNormal(objectNormal); - #endif - - #ifdef USE_TANGENT - vec3 objectTangent = vec3( tangent.xyz ); - #endif - `, - - ShaderChunk.morphnormal_vertex, - ShaderChunk.skinbase_vertex, - ShaderChunk.skinnormal_vertex, - ShaderChunk.defaultnormal_vertex, - ShaderChunk.normal_vertex, - - ShaderChunk.begin_vertex, - - `#ifdef USE_PACKED_POSITION - #if USE_PACKED_POSITION == 0 - transformed = ( vec4(transformed, 1.0) * quantizeMatPos ).xyz; - #endif - #endif`, - - ShaderChunk.morphtarget_vertex, - ShaderChunk.skinning_vertex, - ShaderChunk.displacementmap_vertex, - ShaderChunk.project_vertex, - ShaderChunk.logdepthbuf_vertex, - ShaderChunk.clipping_planes_vertex, - - 'vViewPosition = - mvPosition.xyz;', - - ShaderChunk.worldpos_vertex, - ShaderChunk.envmap_vertex, - ShaderChunk.shadowmap_vertex, - ShaderChunk.fog_vertex, - - '}', - ].join( '\n' ); - - // Use the original MeshPhongMaterial's fragmentShader. - this.fragmentShader = ShaderLib.phong.fragmentShader; - - this.setValues( parameters ); - - } - -} - -export { PackedPhongMaterial }; diff --git a/examples/jsm/utils/SceneOptimizer.js b/examples/jsm/utils/SceneOptimizer.js new file mode 100644 index 00000000000000..748adb2d800d61 --- /dev/null +++ b/examples/jsm/utils/SceneOptimizer.js @@ -0,0 +1,458 @@ +import * as THREE from 'three'; + +/** + * This class can be used to optimized scenes by converting + * individual meshes into {@link BatchedMesh}. This component + * is an experimental attempt to implement auto-batching in three.js. + * + * @three_import import { SceneOptimizer } from 'three/addons/utils/SceneOptimizer.js'; + */ +class SceneOptimizer { + + /** + * Constructs a new scene optimizer. + * + * @param {Scene} scene - The scene to optimize. + * @param {SceneOptimizer~Options} options - The configuration options. + */ + constructor( scene, options = {} ) { + + this.scene = scene; + this.debug = options.debug || false; + + } + + _bufferToHash( buffer ) { + + let hash = 0; + if ( buffer.byteLength !== 0 ) { + + let uintArray; + if ( buffer.buffer ) { + + uintArray = new Uint8Array( + buffer.buffer, + buffer.byteOffset, + buffer.byteLength + ); + + } else { + + uintArray = new Uint8Array( buffer ); + + } + + for ( let i = 0; i < buffer.byteLength; i ++ ) { + + const byte = uintArray[ i ]; + hash = ( hash << 5 ) - hash + byte; + hash |= 0; + + } + + } + + return hash; + + } + + _getMaterialPropertiesHash( material ) { + + const mapProps = [ + 'map', + 'alphaMap', + 'aoMap', + 'bumpMap', + 'displacementMap', + 'emissiveMap', + 'envMap', + 'lightMap', + 'metalnessMap', + 'normalMap', + 'roughnessMap', + ]; + + const mapHash = mapProps + .map( ( prop ) => { + + const map = material[ prop ]; + if ( ! map ) return 0; + return `${map.uuid}_${map.offset.x}_${map.offset.y}_${map.repeat.x}_${map.repeat.y}_${map.rotation}`; + + } ) + .join( '|' ); + + const physicalProps = [ + 'transparent', + 'opacity', + 'alphaTest', + 'alphaToCoverage', + 'side', + 'vertexColors', + 'visible', + 'blending', + 'wireframe', + 'flatShading', + 'premultipliedAlpha', + 'dithering', + 'toneMapped', + 'depthTest', + 'depthWrite', + 'metalness', + 'roughness', + 'clearcoat', + 'clearcoatRoughness', + 'sheen', + 'sheenRoughness', + 'transmission', + 'thickness', + 'attenuationDistance', + 'ior', + 'iridescence', + 'iridescenceIOR', + 'iridescenceThicknessRange', + 'reflectivity', + ] + .map( ( prop ) => { + + if ( typeof material[ prop ] === 'undefined' ) return 0; + if ( material[ prop ] === null ) return 0; + return material[ prop ].toString(); + + } ) + .join( '|' ); + + const emissiveHash = material.emissive ? material.emissive.getHexString() : 0; + const attenuationHash = material.attenuationColor + ? material.attenuationColor.getHexString() + : 0; + const sheenColorHash = material.sheenColor + ? material.sheenColor.getHexString() + : 0; + + return [ + material.type, + physicalProps, + mapHash, + emissiveHash, + attenuationHash, + sheenColorHash, + ].join( '_' ); + + } + + _getAttributesSignature( geometry ) { + + return Object.keys( geometry.attributes ) + .sort() + .map( ( name ) => { + + const attribute = geometry.attributes[ name ]; + return `${name}_${attribute.itemSize}_${attribute.normalized}`; + + } ) + .join( '|' ); + + } + + _getGeometryHash( geometry ) { + + const indexHash = geometry.index + ? this._bufferToHash( geometry.index.array ) + : 'noIndex'; + const positionHash = this._bufferToHash( geometry.attributes.position.array ); + const attributesSignature = this._getAttributesSignature( geometry ); + return `${indexHash}_${positionHash}_${attributesSignature}`; + + } + + _getBatchKey( materialProps, attributesSignature ) { + + return `${materialProps}_${attributesSignature}`; + + } + + _analyzeModel() { + + const batchGroups = new Map(); + const singleGroups = new Map(); + const uniqueGeometries = new Set(); + + this.scene.updateMatrixWorld( true ); + this.scene.traverse( ( node ) => { + + if ( ! node.isMesh ) return; + + const materialProps = this._getMaterialPropertiesHash( node.material ); + const attributesSignature = this._getAttributesSignature( node.geometry ); + const batchKey = this._getBatchKey( materialProps, attributesSignature ); + const geometryHash = this._getGeometryHash( node.geometry ); + uniqueGeometries.add( geometryHash ); + + if ( ! batchGroups.has( batchKey ) ) { + + batchGroups.set( batchKey, { + meshes: [], + geometryStats: new Map(), + totalInstances: 0, + materialProps: node.material.clone(), + } ); + + } + + const group = batchGroups.get( batchKey ); + group.meshes.push( node ); + group.totalInstances ++; + + if ( ! group.geometryStats.has( geometryHash ) ) { + + group.geometryStats.set( geometryHash, { + count: 0, + vertices: node.geometry.attributes.position.count, + indices: node.geometry.index ? node.geometry.index.count : 0, + geometry: node.geometry, + } ); + + } + + group.geometryStats.get( geometryHash ).count ++; + + } ); + + // Move single instance groups to singleGroups + for ( const [ batchKey, group ] of batchGroups ) { + + if ( group.totalInstances === 1 ) { + + singleGroups.set( batchKey, group ); + batchGroups.delete( batchKey ); + + } + + } + + return { batchGroups, singleGroups, uniqueGeometries: uniqueGeometries.size }; + + } + + _createBatchedMeshes( batchGroups ) { + + const meshesToRemove = new Set(); + + for ( const [ , group ] of batchGroups ) { + + const maxGeometries = group.totalInstances; + const maxVertices = Array.from( group.geometryStats.values() ).reduce( + ( sum, stats ) => sum + stats.vertices, + 0 + ); + const maxIndices = Array.from( group.geometryStats.values() ).reduce( + ( sum, stats ) => sum + stats.indices, + 0 + ); + + const batchedMaterial = new group.materialProps.constructor( group.materialProps ); + + if ( batchedMaterial.color !== undefined ) { + + // Reset color to white, color will be set per instance + batchedMaterial.color.set( 1, 1, 1 ); + + } + + const batchedMesh = new THREE.BatchedMesh( + maxGeometries, + maxVertices, + maxIndices, + batchedMaterial + ); + + const referenceMesh = group.meshes[ 0 ]; + batchedMesh.name = `${referenceMesh.name}_batch`; + + const geometryIds = new Map(); + const inverseParentMatrix = new THREE.Matrix4(); + + if ( referenceMesh.parent ) { + + referenceMesh.parent.updateWorldMatrix( true, false ); + inverseParentMatrix.copy( referenceMesh.parent.matrixWorld ).invert(); + + } + + for ( const mesh of group.meshes ) { + + const geometryHash = this._getGeometryHash( mesh.geometry ); + + if ( ! geometryIds.has( geometryHash ) ) { + + geometryIds.set( geometryHash, batchedMesh.addGeometry( mesh.geometry ) ); + + } + + const geometryId = geometryIds.get( geometryHash ); + const instanceId = batchedMesh.addInstance( geometryId ); + + const localMatrix = new THREE.Matrix4(); + mesh.updateWorldMatrix( true, false ); + localMatrix.copy( mesh.matrixWorld ); + if ( referenceMesh.parent ) { + + localMatrix.premultiply( inverseParentMatrix ); + + } + + batchedMesh.setMatrixAt( instanceId, localMatrix ); + batchedMesh.setColorAt( instanceId, mesh.material.color ); + + meshesToRemove.add( mesh ); + + } + + if ( referenceMesh.parent ) { + + referenceMesh.parent.add( batchedMesh ); + + } + + } + + return meshesToRemove; + + } + + /** + * Removes empty nodes from all descendants of the given 3D object. + * + * @param {Object3D} object - The 3D object to process. + */ + removeEmptyNodes( object ) { + + const children = [ ...object.children ]; + + for ( const child of children ) { + + this.removeEmptyNodes( child ); + + if ( ( child instanceof THREE.Group || child.constructor === THREE.Object3D ) + && child.children.length === 0 ) { + + object.remove( child ); + + } + + } + + } + + /** + * Removes the given array of meshes from the scene. + * + * @param {Set} meshesToRemove - The meshes to remove. + */ + disposeMeshes( meshesToRemove ) { + + meshesToRemove.forEach( ( mesh ) => { + + if ( mesh.parent ) { + + mesh.parent.remove( mesh ); + + } + + if ( mesh.geometry ) mesh.geometry.dispose(); + if ( mesh.material ) { + + if ( Array.isArray( mesh.material ) ) { + + mesh.material.forEach( ( m ) => m.dispose() ); + + } else { + + mesh.material.dispose(); + + } + + } + + } ); + + } + + _logDebugInfo( stats ) { + + console.group( 'Scene Optimization Results' ); + console.log( `Original meshes: ${stats.originalMeshes}` ); + console.log( `Batched into: ${stats.batchedMeshes} BatchedMesh` ); + console.log( `Single meshes: ${stats.singleMeshes} Mesh` ); + console.log( `Total draw calls: ${stats.drawCalls}` ); + console.log( `Reduction Ratio: ${stats.reductionRatio}% fewer draw calls` ); + console.groupEnd(); + + } + + /** + * Performs the auto-baching by identifying groups of meshes in the scene + * that can be represented as a single {@link BatchedMesh}. The method modifies + * the scene by adding instances of `BatchedMesh` and removing the now redundant + * individual meshes. + * + * @return {Scene} The optimized scene. + */ + toBatchedMesh() { + + const { batchGroups, singleGroups, uniqueGeometries } = this._analyzeModel(); + const meshesToRemove = this._createBatchedMeshes( batchGroups ); + + this.disposeMeshes( meshesToRemove ); + this.removeEmptyNodes( this.scene ); + + if ( this.debug ) { + + const totalOriginalMeshes = meshesToRemove.size + singleGroups.size; + const totalFinalMeshes = batchGroups.size + singleGroups.size; + + const stats = { + originalMeshes: totalOriginalMeshes, + batchedMeshes: batchGroups.size, + singleMeshes: singleGroups.size, + drawCalls: totalFinalMeshes, + uniqueGeometries: uniqueGeometries, + reductionRatio: ( ( 1 - totalFinalMeshes / totalOriginalMeshes ) * 100 ).toFixed( 1 ), + }; + + this._logDebugInfo( stats ); + + } + + return this.scene; + + } + + /** + * Performs the auto-instancing by identifying groups of meshes in the scene + * that can be represented as a single {@link InstancedMesh}. The method modifies + * the scene by adding instances of `InstancedMesh` and removing the now redundant + * individual meshes. + * + * This method is not yet implemented. + * + * @abstract + * @return {Scene} The optimized scene. + */ + toInstancingMesh() { + + throw new Error( 'InstancedMesh optimization not implemented yet' ); + + } + +} + +/** + * Constructor options of `SceneOptimizer`. + * + * @typedef {Object} SceneOptimizer~Options + * @property {boolean} [debug=false] - Whether to enable debug mode or not. + **/ + +export { SceneOptimizer }; diff --git a/examples/jsm/utils/SceneUtils.js b/examples/jsm/utils/SceneUtils.js index 45c4c210e81661..987669621dc423 100644 --- a/examples/jsm/utils/SceneUtils.js +++ b/examples/jsm/utils/SceneUtils.js @@ -10,9 +10,22 @@ import { import { mergeGroups, deepCloneAttribute } from './BufferGeometryUtils.js'; +/** + * @module SceneUtils + * @three_import import * as SceneUtils from 'three/addons/utils/SceneUtils.js'; + */ + const _color = /*@__PURE__*/new Color(); const _matrix = /*@__PURE__*/new Matrix4(); +/** + * This function creates a mesh for each instance of the given instanced mesh and + * adds it to a group. Each mesh will honor the current 3D transformation of its + * corresponding instance. + * + * @param {InstancedMesh} instancedMesh - The instanced mesh. + * @return {Group} A group of meshes. + */ function createMeshesFromInstancedMesh( instancedMesh ) { const group = new Group(); @@ -39,6 +52,13 @@ function createMeshesFromInstancedMesh( instancedMesh ) { } +/** + * This function creates a mesh for each geometry-group of the given multi-material mesh and + * adds it to a group. + * + * @param {Mesh} mesh - The multi-material mesh. + * @return {Group} A group of meshes. + */ function createMeshesFromMultiMaterialMesh( mesh ) { if ( Array.isArray( mesh.material ) === false ) { @@ -110,6 +130,16 @@ function createMeshesFromMultiMaterialMesh( mesh ) { } +/** + * This function represents an alternative way to create 3D objects with multiple materials. + * Normally, {@link BufferGeometry#groups} are used which might introduce issues e.g. when + * exporting the object to a 3D format. This function accepts a geometry and an array of + * materials and creates for each material a mesh that is added to a group. + * + * @param {BufferGeometry} geometry - The geometry. + * @param {Array} materials - An array of materials. + * @return {Group} A group representing a multi-material object. + */ function createMultiMaterialObject( geometry, materials ) { const group = new Group(); @@ -124,6 +154,18 @@ function createMultiMaterialObject( geometry, materials ) { } + +/** + * Executes a reducer function for each vertex of the given 3D object. + * `reduceVertices()` returns a single value: the function's accumulated result. + * + * @param {Object3D} object - The 3D object that should be processed. It must have a + * geometry with a `position` attribute. + * @param {function(number,Vector3):number} func - The reducer function. First argument + * is the current value, second argument the current vertex. + * @param {any} initialValue - The initial value. + * @return {any} The result. + */ function reduceVertices( object, func, initialValue ) { let value = initialValue; @@ -174,8 +216,10 @@ function reduceVertices( object, func, initialValue ) { } /** - * @param {InstancedMesh} - * @param {function(int, int):int} + * Sorts the instances of the given instanced mesh. + * + * @param {InstancedMesh} mesh - The instanced mesh to sort. + * @param {function(number, number):number} compareFn - A custom compare function for the sort. */ function sortInstancedMesh( mesh, compareFn ) { @@ -245,10 +289,75 @@ function sortInstancedMesh( mesh, compareFn ) { } +/** + * Generator based alternative to {@link Object3D#traverse}. + * + * @param {Object3D} object - Object to traverse. + * @yields {Object3D} Objects that passed the filter condition. + */ +function* traverseGenerator( object ) { + + yield object; + + const children = object.children; + + for ( let i = 0, l = children.length; i < l; i ++ ) { + + yield* traverseGenerator( children[ i ] ); + + } + +} + +/** + * Generator based alternative to {@link Object3D#traverseVisible}. + * + * @param {Object3D} object Object to traverse. + * @yields {Object3D} Objects that passed the filter condition. + */ +function* traverseVisibleGenerator( object ) { + + if ( object.visible === false ) return; + + yield object; + + const children = object.children; + + for ( let i = 0, l = children.length; i < l; i ++ ) { + + yield* traverseVisibleGenerator( children[ i ] ); + + } + +} + +/** + * Generator based alternative to {@link Object3D#traverseAncestors}. + * + * @param {Object3D} object Object to traverse. + * @yields {Object3D} Objects that passed the filter condition. + */ +function* traverseAncestorsGenerator( object ) { + + const parent = object.parent; + + if ( parent !== null ) { + + yield parent; + + yield* traverseAncestorsGenerator( parent ); + + } + +} + export { createMeshesFromInstancedMesh, createMeshesFromMultiMaterialMesh, createMultiMaterialObject, reduceVertices, - sortInstancedMesh + sortInstancedMesh, + traverseGenerator, + traverseVisibleGenerator, + traverseAncestorsGenerator }; diff --git a/examples/jsm/utils/ShadowMapViewer.js b/examples/jsm/utils/ShadowMapViewer.js index 53b928249dc079..5e43cae4c86539 100644 --- a/examples/jsm/utils/ShadowMapViewer.js +++ b/examples/jsm/utils/ShadowMapViewer.js @@ -1,13 +1,12 @@ import { DoubleSide, - LinearFilter, + CanvasTexture, Mesh, MeshBasicMaterial, OrthographicCamera, PlaneGeometry, Scene, ShaderMaterial, - Texture, UniformsUtils } from 'three'; import { UnpackDepthRGBAShader } from '../shaders/UnpackDepthRGBAShader.js'; @@ -17,30 +16,27 @@ import { UnpackDepthRGBAShader } from '../shaders/UnpackDepthRGBAShader.js'; * It works for shadow casting lights: DirectionalLight and SpotLight. * It renders out the shadow map and displays it on a HUD. * - * Example usage: - * 1) Import ShadowMapViewer into your app. + * This module can only be used with {@link WebGLRenderer}. When using {@link WebGPURenderer}, + * import the class from `ShadowMapViewerGPU.js`. * - * 2) Create a shadow casting light and name it optionally: - * let light = new DirectionalLight( 0xffffff, 1 ); - * light.castShadow = true; - * light.name = 'Sun'; + * ```js + * const lightShadowMapViewer = new ShadowMapViewer( light ); + * lightShadowMapViewer.position.x = 10; + * lightShadowMapViewer.position.y = SCREEN_HEIGHT - ( SHADOW_MAP_HEIGHT / 4 ) - 10; + * lightShadowMapViewer.size.width = SHADOW_MAP_WIDTH / 4; + * lightShadowMapViewer.size.height = SHADOW_MAP_HEIGHT / 4; + * lightShadowMapViewer.update(); + * ``` * - * 3) Create a shadow map viewer for that light and set its size and position optionally: - * let shadowMapViewer = new ShadowMapViewer( light ); - * shadowMapViewer.size.set( 128, 128 ); //width, height default: 256, 256 - * shadowMapViewer.position.set( 10, 10 ); //x, y in pixel default: 0, 0 (top left corner) - * - * 4) Render the shadow map viewer in your render loop: - * shadowMapViewer.render( renderer ); - * - * 5) Optionally: Update the shadow map viewer on window resize: - * shadowMapViewer.updateForWindowResize(); - * - * 6) If you set the position or size members directly, you need to call shadowMapViewer.update(); + * @three_import import { ShadowMapViewer } from 'three/addons/utils/ShadowMapViewer.js'; */ - class ShadowMapViewer { + /** + * Constructs a new shadow map viewer. + * + * @param {Light} light - The shadow casting light. + */ constructor( light ) { //- Internals @@ -93,13 +89,9 @@ class ShadowMapViewer { context.fillStyle = 'rgba( 255, 0, 0, 1 )'; context.fillText( light.name, 0, 20 ); - const labelTexture = new Texture( labelCanvas ); - labelTexture.magFilter = LinearFilter; - labelTexture.minFilter = LinearFilter; - labelTexture.needsUpdate = true; + const labelTexture = new CanvasTexture( labelCanvas ); - const labelMaterial = new MeshBasicMaterial( { map: labelTexture, side: DoubleSide } ); - labelMaterial.transparent = true; + const labelMaterial = new MeshBasicMaterial( { map: labelTexture, side: DoubleSide, transparent: true } ); const labelPlane = new PlaneGeometry( labelCanvas.width, labelCanvas.height ); labelMesh = new Mesh( labelPlane, labelMaterial ); @@ -115,11 +107,21 @@ class ShadowMapViewer { } - //- API - // Set to false to disable displaying this shadow map + /** + * Whether to display the shadow map viewer or not. + * + * @type {boolean} + * @default true + */ this.enabled = true; - // Set the size of the displayed shadow map on the HUD + /** + * The size of the viewer. When changing this property, make sure + * to call {@link ShadowMapViewer#update}. + * + * @type {{width:number,height:number}} + * @default true + */ this.size = { width: frame.width, height: frame.height, @@ -136,7 +138,13 @@ class ShadowMapViewer { } }; - // Set the position of the displayed shadow map on the HUD + /** + * The position of the viewer. When changing this property, make sure + * to call {@link ShadowMapViewer#update}. + * + * @type {{x:number,y:number, set:function(number,number)}} + * @default true + */ this.position = { x: frame.x, y: frame.y, @@ -155,6 +163,11 @@ class ShadowMapViewer { } }; + /** + * Renders the viewer. This method must be called in the app's animation loop. + * + * @param {WebGLRenderer} renderer - The renderer. + */ this.render = function ( renderer ) { if ( this.enabled ) { @@ -176,6 +189,10 @@ class ShadowMapViewer { }; + /** + * Resizes the viewer. This method should be called whenever the app's + * window is resized. + */ this.updateForWindowResize = function () { if ( this.enabled ) { @@ -192,6 +209,9 @@ class ShadowMapViewer { }; + /** + * Updates the viewer. + */ this.update = function () { this.position.set( this.position.x, this.position.y ); diff --git a/examples/jsm/utils/ShadowMapViewerGPU.js b/examples/jsm/utils/ShadowMapViewerGPU.js new file mode 100644 index 00000000000000..73aac851647a65 --- /dev/null +++ b/examples/jsm/utils/ShadowMapViewerGPU.js @@ -0,0 +1,226 @@ +import { + DoubleSide, + CanvasTexture, + Mesh, + MeshBasicMaterial, + NodeMaterial, + OrthographicCamera, + PlaneGeometry, + Scene, + Texture +} from 'three'; +import { texture } from 'three/tsl'; + +/** + * This is a helper for visualising a given light's shadow map. + * It works for shadow casting lights: DirectionalLight and SpotLight. + * It renders out the shadow map and displays it on a HUD. + * + * This module can only be used with {@link WebGPURenderer}. When using {@link WebGLRenderer}, + * import the class from `ShadowMapViewer.js`. + * + * ```js + * const lightShadowMapViewer = new ShadowMapViewer( light ); + * lightShadowMapViewer.position.x = 10; + * lightShadowMapViewer.position.y = SCREEN_HEIGHT - ( SHADOW_MAP_HEIGHT / 4 ) - 10; + * lightShadowMapViewer.size.width = SHADOW_MAP_WIDTH / 4; + * lightShadowMapViewer.size.height = SHADOW_MAP_HEIGHT / 4; + * lightShadowMapViewer.update(); + * ``` + * + * @three_import import { ShadowMapViewer } from 'three/addons/utils/ShadowMapViewerGPU.js'; + */ +class ShadowMapViewer { + + /** + * Constructs a new shadow map viewer. + * + * @param {Light} light - The shadow casting light. + */ + constructor( light ) { + + //- Internals + const scope = this; + const doRenderLabel = ( light.name !== undefined && light.name !== '' ); + let currentAutoClear; + + //Holds the initial position and dimension of the HUD + const frame = { + x: 10, + y: 10, + width: 256, + height: 256 + }; + + const camera = new OrthographicCamera( window.innerWidth / - 2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / - 2, 1, 10 ); + camera.position.set( 0, 0, 2 ); + const scene = new Scene(); + + //HUD for shadow map + + const material = new NodeMaterial(); + + const shadowMapUniform = texture( new Texture() ); + material.fragmentNode = shadowMapUniform; + + const plane = new PlaneGeometry( frame.width, frame.height ); + const mesh = new Mesh( plane, material ); + + scene.add( mesh ); + + //Label for light's name + let labelCanvas, labelMesh; + + if ( doRenderLabel ) { + + labelCanvas = document.createElement( 'canvas' ); + + const context = labelCanvas.getContext( '2d' ); + context.font = 'Bold 20px Arial'; + + const labelWidth = context.measureText( light.name ).width; + labelCanvas.width = labelWidth; + labelCanvas.height = 25; //25 to account for g, p, etc. + + context.font = 'Bold 20px Arial'; + context.fillStyle = 'rgba( 255, 0, 0, 1 )'; + context.fillText( light.name, 0, 20 ); + + const labelTexture = new CanvasTexture( labelCanvas ); + + const labelMaterial = new MeshBasicMaterial( { map: labelTexture, side: DoubleSide, transparent: true } ); + + const labelPlane = new PlaneGeometry( labelCanvas.width, labelCanvas.height ); + labelMesh = new Mesh( labelPlane, labelMaterial ); + + scene.add( labelMesh ); + + } + + function resetPosition() { + + scope.position.set( scope.position.x, scope.position.y ); + + } + + /** + * Whether to display the shadow map viewer or not. + * + * @type {boolean} + * @default true + */ + this.enabled = true; + + /** + * The size of the viewer. When changing this property, make sure + * to call {@link ShadowMapViewer#update}. + * + * @type {{width:number,height:number}} + * @default true + */ + this.size = { + width: frame.width, + height: frame.height, + set: function ( width, height ) { + + this.width = width; + this.height = height; + + mesh.scale.set( this.width / frame.width, this.height / frame.height, 1 ); + + //Reset the position as it is off when we scale stuff + resetPosition(); + + } + }; + + /** + * The position of the viewer. When changing this property, make sure + * to call {@link ShadowMapViewer#update}. + * + * @type {{width:number,height:number}} + * @default true + */ + this.position = { + x: frame.x, + y: frame.y, + set: function ( x, y ) { + + this.x = x; + this.y = y; + + const width = scope.size.width; + const height = scope.size.height; + + mesh.position.set( - window.innerWidth / 2 + width / 2 + this.x, window.innerHeight / 2 - height / 2 - this.y, 0 ); + + if ( doRenderLabel ) labelMesh.position.set( mesh.position.x, mesh.position.y - scope.size.height / 2 + labelCanvas.height / 2, 0 ); + + } + }; + + /** + * Renders the viewer. This method must be called in the app's animation loop. + * + * @param {WebGPURenderer} renderer - The renderer. + */ + this.render = function ( renderer ) { + + if ( this.enabled ) { + + //Because a light's .shadowMap is only initialised after the first render pass + //we have to make sure the correct map is sent into the shader, otherwise we + //always end up with the scene's first added shadow casting light's shadowMap + //in the shader + //See: https://github.com/mrdoob/three.js/issues/5932 + shadowMapUniform.value = light.shadow.map.texture; + + currentAutoClear = renderer.autoClear; + renderer.autoClear = false; // To allow render overlay + renderer.clearDepth(); + renderer.render( scene, camera ); + renderer.autoClear = currentAutoClear; + + } + + }; + + /** + * Resizes the viewer. This method should be called whenever the app's + * window is resized. + */ + this.updateForWindowResize = function () { + + if ( this.enabled ) { + + camera.left = window.innerWidth / - 2; + camera.right = window.innerWidth / 2; + camera.top = window.innerHeight / 2; + camera.bottom = window.innerHeight / - 2; + camera.updateProjectionMatrix(); + + this.update(); + + } + + }; + + /** + * Updates the viewer. + */ + this.update = function () { + + this.position.set( this.position.x, this.position.y ); + this.size.set( this.size.width, this.size.height ); + + }; + + //Force an update to set position/size + this.update(); + + } + +} + + +export { ShadowMapViewer }; diff --git a/examples/jsm/utils/SkeletonUtils.js b/examples/jsm/utils/SkeletonUtils.js index f14195ca5ec048..e208c742286294 100644 --- a/examples/jsm/utils/SkeletonUtils.js +++ b/examples/jsm/utils/SkeletonUtils.js @@ -9,28 +9,50 @@ import { VectorKeyframeTrack } from 'three'; +/** + * @module SkeletonUtils + * @three_import import * as SkeletonUtils from 'three/addons/utils/SkeletonUtils.js'; + */ +function getBoneName( bone, options ) { + + if ( options.getBoneName !== undefined ) { + + return options.getBoneName( bone ); + + } + + return options.names[ bone.name ]; + +} + +/** + * Retargets the skeleton from the given source 3D object to the + * target 3D object. + * + * @param {Object3D} target - The target 3D object. + * @param {Object3D} source - The source 3D object. + * @param {module:SkeletonUtils~RetargetOptions} options - The options. + */ function retarget( target, source, options = {} ) { - const pos = new Vector3(), - quat = new Quaternion(), + const quat = new Quaternion(), scale = new Vector3(), - bindBoneMatrix = new Matrix4(), relativeMatrix = new Matrix4(), globalMatrix = new Matrix4(); - options.preserveMatrix = options.preserveMatrix !== undefined ? options.preserveMatrix : true; - options.preservePosition = options.preservePosition !== undefined ? options.preservePosition : true; - options.preserveHipPosition = options.preserveHipPosition !== undefined ? options.preserveHipPosition : false; + options.preserveBoneMatrix = options.preserveBoneMatrix !== undefined ? options.preserveBoneMatrix : true; + options.preserveBonePositions = options.preserveBonePositions !== undefined ? options.preserveBonePositions : true; options.useTargetMatrix = options.useTargetMatrix !== undefined ? options.useTargetMatrix : false; options.hip = options.hip !== undefined ? options.hip : 'hip'; + options.hipInfluence = options.hipInfluence !== undefined ? options.hipInfluence : new Vector3( 1, 1, 1 ); + options.scale = options.scale !== undefined ? options.scale : 1; options.names = options.names || {}; const sourceBones = source.isObject3D ? source.skeleton.bones : getBones( source ), bones = target.isObject3D ? target.skeleton.bones : getBones( target ); - let bindBones, - bone, name, boneTo, + let bone, name, boneTo, bonesPosition; // reset bones @@ -42,11 +64,11 @@ function retarget( target, source, options = {} ) { } else { options.useTargetMatrix = true; - options.preserveMatrix = false; + options.preserveBoneMatrix = false; } - if ( options.preservePosition ) { + if ( options.preserveBonePositions ) { bonesPosition = []; @@ -58,7 +80,7 @@ function retarget( target, source, options = {} ) { } - if ( options.preserveMatrix ) { + if ( options.preserveBoneMatrix ) { // reset matrix @@ -76,35 +98,10 @@ function retarget( target, source, options = {} ) { } - if ( options.offsets ) { - - bindBones = []; - - for ( let i = 0; i < bones.length; ++ i ) { - - bone = bones[ i ]; - name = options.names[ bone.name ] || bone.name; - - if ( options.offsets[ name ] ) { - - bone.matrix.multiply( options.offsets[ name ] ); - - bone.matrix.decompose( bone.position, bone.quaternion, bone.scale ); - - bone.updateMatrixWorld(); - - } - - bindBones.push( bone.matrixWorld.clone() ); - - } - - } - for ( let i = 0; i < bones.length; ++ i ) { bone = bones[ i ]; - name = options.names[ bone.name ] || bone.name; + name = getBoneName( bone, options ); boneTo = getBoneByName( name, sourceBones ); @@ -136,10 +133,15 @@ function retarget( target, source, options = {} ) { if ( target.isObject3D ) { - const boneIndex = bones.indexOf( bone ), - wBindMatrix = bindBones ? bindBones[ boneIndex ] : bindBoneMatrix.copy( target.skeleton.boneInverses[ boneIndex ] ).invert(); + if ( options.localOffsets ) { + + if ( options.localOffsets[ bone.name ] ) { + + globalMatrix.multiply( options.localOffsets[ bone.name ] ); + + } - globalMatrix.multiply( wBindMatrix ); + } } @@ -147,20 +149,30 @@ function retarget( target, source, options = {} ) { } - if ( bone.parent && bone.parent.isBone ) { + if ( name === options.hip ) { - bone.matrix.copy( bone.parent.matrixWorld ).invert(); - bone.matrix.multiply( globalMatrix ); + globalMatrix.elements[ 12 ] *= options.scale * options.hipInfluence.x; + globalMatrix.elements[ 13 ] *= options.scale * options.hipInfluence.y; + globalMatrix.elements[ 14 ] *= options.scale * options.hipInfluence.z; - } else { + if ( options.hipPosition !== undefined ) { - bone.matrix.copy( globalMatrix ); + globalMatrix.elements[ 12 ] += options.hipPosition.x * options.scale; + globalMatrix.elements[ 13 ] += options.hipPosition.y * options.scale; + globalMatrix.elements[ 14 ] += options.hipPosition.z * options.scale; + + } } - if ( options.preserveHipPosition && name === options.hip ) { + if ( bone.parent ) { + + bone.matrix.copy( bone.parent.matrixWorld ).invert(); + bone.matrix.multiply( globalMatrix ); + + } else { - bone.matrix.setPosition( pos.set( 0, bone.position.y, 0 ) ); + bone.matrix.copy( globalMatrix ); } @@ -170,12 +182,12 @@ function retarget( target, source, options = {} ) { } - if ( options.preservePosition ) { + if ( options.preserveBonePositions ) { for ( let i = 0; i < bones.length; ++ i ) { bone = bones[ i ]; - name = options.names[ bone.name ] || bone.name; + name = getBoneName( bone, options ) || bone.name; if ( name !== options.hip ) { @@ -187,7 +199,7 @@ function retarget( target, source, options = {} ) { } - if ( options.preserveMatrix ) { + if ( options.preserveBoneMatrix ) { // restore matrix @@ -197,10 +209,22 @@ function retarget( target, source, options = {} ) { } +/** + * Retargets the animation clip of the source object to the + * target 3D object. + * + * @param {Object3D} target - The target 3D object. + * @param {Object3D} source - The source 3D object. + * @param {AnimationClip} clip - The animation clip. + * @param {module:SkeletonUtils~RetargetOptions} options - The options. + * @return {AnimationClip} The retargeted animation clip. + */ function retargetClip( target, source, clip, options = {} ) { options.useFirstFramePosition = options.useFirstFramePosition !== undefined ? options.useFirstFramePosition : false; - options.fps = options.fps !== undefined ? options.fps : 30; + + // Calculate the fps from the source clip based on the track with the most frames, unless fps is already provided. + options.fps = options.fps !== undefined ? options.fps : ( Math.max( ...clip.tracks.map( track => track.times.length ) ) / clip.duration ); options.names = options.names || []; if ( ! source.isObject3D ) { @@ -210,35 +234,53 @@ function retargetClip( target, source, clip, options = {} ) { } const numFrames = Math.round( clip.duration * ( options.fps / 1000 ) * 1000 ), - delta = 1 / options.fps, + delta = clip.duration / ( numFrames - 1 ), convertedTracks = [], mixer = new AnimationMixer( source ), bones = getBones( target.skeleton ), boneDatas = []; + let positionOffset, bone, boneTo, boneData, name; mixer.clipAction( clip ).play(); - mixer.update( 0 ); + + // trim + + let start = 0, end = numFrames; + + if ( options.trim !== undefined ) { + + start = Math.round( options.trim[ 0 ] * options.fps ); + end = Math.min( Math.round( options.trim[ 1 ] * options.fps ), numFrames ) - start; + + mixer.update( options.trim[ 0 ] ); + + } else { + + mixer.update( 0 ); + + } source.updateMatrixWorld(); - for ( let i = 0; i < numFrames; ++ i ) { + // - const time = i * delta; + for ( let frame = 0; frame < end; ++ frame ) { + + const time = frame * delta; retarget( target, source, options ); for ( let j = 0; j < bones.length; ++ j ) { - name = options.names[ bones[ j ].name ] || bones[ j ].name; - + bone = bones[ j ]; + name = getBoneName( bone, options ) || bone.name; boneTo = getBoneByName( name, source.skeleton ); if ( boneTo ) { - bone = bones[ j ]; boneData = boneDatas[ j ] = boneDatas[ j ] || { bone: bone }; if ( options.hip === name ) { @@ -246,15 +288,15 @@ function retargetClip( target, source, clip, options = {} ) { if ( ! boneData.pos ) { boneData.pos = { - times: new Float32Array( numFrames ), - values: new Float32Array( numFrames * 3 ) + times: new Float32Array( end ), + values: new Float32Array( end * 3 ) }; } if ( options.useFirstFramePosition ) { - if ( i === 0 ) { + if ( frame === 0 ) { positionOffset = bone.position.clone(); @@ -264,30 +306,40 @@ function retargetClip( target, source, clip, options = {} ) { } - boneData.pos.times[ i ] = time; + boneData.pos.times[ frame ] = time; - bone.position.toArray( boneData.pos.values, i * 3 ); + bone.position.toArray( boneData.pos.values, frame * 3 ); } if ( ! boneData.quat ) { boneData.quat = { - times: new Float32Array( numFrames ), - values: new Float32Array( numFrames * 4 ) + times: new Float32Array( end ), + values: new Float32Array( end * 4 ) }; } - boneData.quat.times[ i ] = time; + boneData.quat.times[ frame ] = time; - bone.quaternion.toArray( boneData.quat.values, i * 4 ); + bone.quaternion.toArray( boneData.quat.values, frame * 4 ); } } - mixer.update( delta ); + if ( frame === end - 2 ) { + + // last mixer update before final loop iteration + // make sure we do not go over or equal to clip duration + mixer.update( delta - 0.0000001 ); + + } else { + + mixer.update( delta ); + + } source.updateMatrixWorld(); @@ -325,6 +377,14 @@ function retargetClip( target, source, clip, options = {} ) { } +/** + * Clones the given 3D object and its descendants, ensuring that any `SkinnedMesh` instances are + * correctly associated with their bones. Bones are also cloned, and must be descendants of the + * object passed to this method. Other data, like geometries and materials, are reused by reference. + * + * @param {Object3D} source - The 3D object to clone. + * @return {Object3D} The cloned 3D object. + */ function clone( source ) { const sourceLookup = new Map(); @@ -406,6 +466,23 @@ function parallelTraverse( a, b, callback ) { } +/** + * Retarget options of `SkeletonUtils`. + * + * @typedef {Object} module:SkeletonUtils~RetargetOptions + * @property {boolean} [useFirstFramePosition=false] - Whether to use the position of the first frame or not. + * @property {number} [fps] - The FPS of the clip. + * @property {Object} [names] - A dictionary for mapping target to source bone names. + * @property {function(string):string} [getBoneName] - A function for mapping bone names. Alternative to `names`. + * @property {Array} [trim] - Whether to trim the clip or not. If set the array should hold two values for the start and end. + * @property {boolean} [preserveBoneMatrix=true] - Whether to preserve bone matrices or not. + * @property {boolean} [preserveBonePositions=true] - Whether to preserve bone positions or not. + * @property {boolean} [useTargetMatrix=false] - Whether to use the target matrix or not. + * @property {string} [hip='hip'] - The name of the source's hip bone. + * @property {Vector3} [hipInfluence=(1,1,1)] - The hip influence. + * @property {number} [scale=1] - The scale. + **/ + export { retarget, retargetClip, diff --git a/examples/jsm/utils/SortUtils.js b/examples/jsm/utils/SortUtils.js new file mode 100644 index 00000000000000..84df1828ecc472 --- /dev/null +++ b/examples/jsm/utils/SortUtils.js @@ -0,0 +1,175 @@ + +/** + * @module SortUtils + * @three_import import * as SortUtils from 'three/addons/utils/SortUtils.js'; + */ + +const POWER = 3; +const BIT_MAX = 32; +const BIN_BITS = 1 << POWER; +const BIN_SIZE = 1 << BIN_BITS; +const BIN_MAX = BIN_SIZE - 1; +const ITERATIONS = BIT_MAX / BIN_BITS; + +const bins = new Array( ITERATIONS ); +const bins_buffer = new ArrayBuffer( ( ITERATIONS + 1 ) * BIN_SIZE * 4 ); + +let c = 0; +for ( let i = 0; i < ( ITERATIONS + 1 ); i ++ ) { + + bins[ i ] = new Uint32Array( bins_buffer, c, BIN_SIZE ); + c += BIN_SIZE * 4; + +} + +const defaultGet = ( el ) => el; + +/** + * Hybrid radix sort from. + * + * - {@link https://gist.github.com/sciecode/93ed864dd77c5c8803c6a86698d68dab} + * - {@link https://github.com/mrdoob/three.js/pull/27202#issuecomment-1817640271} + * + * Expects unsigned 32b integer values. + * + * @function + * @param {Array} arr - The array to sort. + * @param {Object} opt - The options + */ +export const radixSort = ( arr, opt ) => { + + const len = arr.length; + + const options = opt || {}; + const aux = options.aux || new arr.constructor( len ); + const get = options.get || defaultGet; + + const data = [ arr, aux ]; + + let compare, accumulate, recurse; + + if ( options.reversed ) { + + compare = ( a, b ) => a < b; + accumulate = ( bin ) => { + + for ( let j = BIN_SIZE - 2; j >= 0; j -- ) + bin[ j ] += bin[ j + 1 ]; + + }; + + recurse = ( cache, depth, start ) => { + + let prev = 0; + for ( let j = BIN_MAX; j >= 0; j -- ) { + + const cur = cache[ j ], diff = cur - prev; + if ( diff != 0 ) { + + if ( diff > 32 ) + radixSortBlock( depth + 1, start + prev, diff ); + else + insertionSortBlock( depth + 1, start + prev, diff ); + prev = cur; + + } + + } + + }; + + } else { + + compare = ( a, b ) => a > b; + accumulate = ( bin ) => { + + for ( let j = 1; j < BIN_SIZE; j ++ ) + bin[ j ] += bin[ j - 1 ]; + + }; + + recurse = ( cache, depth, start ) => { + + let prev = 0; + for ( let j = 0; j < BIN_SIZE; j ++ ) { + + const cur = cache[ j ], diff = cur - prev; + if ( diff != 0 ) { + + if ( diff > 32 ) + radixSortBlock( depth + 1, start + prev, diff ); + else + insertionSortBlock( depth + 1, start + prev, diff ); + prev = cur; + + } + + } + + }; + + } + + const insertionSortBlock = ( depth, start, len ) => { + + const a = data[ depth & 1 ]; + const b = data[ ( depth + 1 ) & 1 ]; + + for ( let j = start + 1; j < start + len; j ++ ) { + + const p = a[ j ], t = get( p ) >>> 0; + let i = j; + while ( i > start ) { + + if ( compare( get( a[ i - 1 ] ) >>> 0, t ) ) + a[ i ] = a[ -- i ]; + else + break; + + } + + a[ i ] = p; + + } + + if ( ( depth & 1 ) == 1 ) { + + for ( let i = start; i < start + len; i ++ ) + b[ i ] = a[ i ]; + + } + + }; + + const radixSortBlock = ( depth, start, len ) => { + + const a = data[ depth & 1 ]; + const b = data[ ( depth + 1 ) & 1 ]; + + const shift = ( 3 - depth ) << POWER; + const end = start + len; + + const cache = bins[ depth ]; + const bin = bins[ depth + 1 ]; + + bin.fill( 0 ); + + for ( let j = start; j < end; j ++ ) + bin[ ( get( a[ j ] ) >>> shift ) & BIN_MAX ] ++; + + accumulate( bin ); + + cache.set( bin ); + + for ( let j = end - 1; j >= start; j -- ) + b[ start + -- bin[ ( get( a[ j ] ) >>> shift ) & BIN_MAX ] ] = a[ j ]; + + if ( depth == ITERATIONS - 1 ) return; + + recurse( cache, depth, start ); + + }; + + radixSortBlock( 0, 0, len ); + +}; diff --git a/examples/jsm/utils/UVsDebug.js b/examples/jsm/utils/UVsDebug.js index f5cbc35bbb63c0..ee733f800b0cc1 100644 --- a/examples/jsm/utils/UVsDebug.js +++ b/examples/jsm/utils/UVsDebug.js @@ -3,13 +3,21 @@ import { } from 'three'; /** - * tool for "unwrapping" and debugging three.js geometries UV mapping + * @module UVsDebug + * @three_import import { UVsDebug } from 'three/addons/utils/UVsDebug.js'; + */ + +/** + * Function for "unwrapping" and debugging three.js geometries UV mapping. * - * Sample usage: - * document.body.appendChild( UVsDebug( new THREE.SphereGeometry( 10, 10, 10, 10 ) ); + * ```js + * document.body.appendChild( UVsDebug( new THREE.SphereGeometry() ) ); + * ``` * + * @param {BufferGeometry} geometry - The geometry whose uv coordinates should be inspected. + * @param {number} [size=1024] - The size of the debug canvas. + * @return {HTMLCanvasElement} A canvas element with visualized uv coordinates. */ - function UVsDebug( geometry, size = 1024 ) { // handles wrapping of uv.x > 1 only diff --git a/examples/jsm/utils/WebGLTextureUtils.js b/examples/jsm/utils/WebGLTextureUtils.js new file mode 100644 index 00000000000000..952ddd97b89bf0 --- /dev/null +++ b/examples/jsm/utils/WebGLTextureUtils.js @@ -0,0 +1,115 @@ +import { + PlaneGeometry, + ShaderMaterial, + Uniform, + Mesh, + PerspectiveCamera, + Scene, + WebGLRenderer, + CanvasTexture, + SRGBColorSpace +} from 'three'; + +/** + * @module WebGLTextureUtils + * @three_import import * as WebGLTextureUtils from 'three/addons/utils/WebGLTextureUtils.js'; + */ + +let _renderer; +let fullscreenQuadGeometry; +let fullscreenQuadMaterial; +let fullscreenQuad; + +/** + * Returns an uncompressed version of the given compressed texture. + * + * This module can only be used with {@link WebGLRenderer}. When using {@link WebGPURenderer}, + * import the function from {@link WebGPUTextureUtils}. + * + * @param {CompressedTexture} texture - The compressed texture. + * @param {number} [maxTextureSize=Infinity] - The maximum size of the uncompressed texture. + * @param {?WebGLRenderer} [renderer=null] - A reference to a renderer. + * @return {CanvasTexture} The uncompressed texture. + */ +export function decompress( texture, maxTextureSize = Infinity, renderer = null ) { + + if ( ! fullscreenQuadGeometry ) fullscreenQuadGeometry = new PlaneGeometry( 2, 2, 1, 1 ); + if ( ! fullscreenQuadMaterial ) fullscreenQuadMaterial = new ShaderMaterial( { + uniforms: { blitTexture: new Uniform( texture ) }, + vertexShader: ` + varying vec2 vUv; + void main(){ + vUv = uv; + gl_Position = vec4(position.xy * 1.0,0.,.999999); + }`, + fragmentShader: ` + uniform sampler2D blitTexture; + varying vec2 vUv; + + void main(){ + gl_FragColor = vec4(vUv.xy, 0, 1); + + #ifdef IS_SRGB + gl_FragColor = sRGBTransferOETF( texture2D( blitTexture, vUv) ); + #else + gl_FragColor = texture2D( blitTexture, vUv); + #endif + }` + } ); + + fullscreenQuadMaterial.uniforms.blitTexture.value = texture; + fullscreenQuadMaterial.defines.IS_SRGB = texture.colorSpace == SRGBColorSpace; + fullscreenQuadMaterial.needsUpdate = true; + + if ( ! fullscreenQuad ) { + + fullscreenQuad = new Mesh( fullscreenQuadGeometry, fullscreenQuadMaterial ); + fullscreenQuad.frustumCulled = false; + + } + + const _camera = new PerspectiveCamera(); + const _scene = new Scene(); + _scene.add( fullscreenQuad ); + + if ( renderer === null ) { + + renderer = _renderer = new WebGLRenderer( { antialias: false } ); + + } + + const width = Math.min( texture.image.width, maxTextureSize ); + const height = Math.min( texture.image.height, maxTextureSize ); + + renderer.setSize( width, height ); + renderer.clear(); + renderer.render( _scene, _camera ); + + const canvas = document.createElement( 'canvas' ); + const context = canvas.getContext( '2d' ); + + canvas.width = width; + canvas.height = height; + + context.drawImage( renderer.domElement, 0, 0, width, height ); + + const readableTexture = new CanvasTexture( canvas ); + + readableTexture.minFilter = texture.minFilter; + readableTexture.magFilter = texture.magFilter; + readableTexture.wrapS = texture.wrapS; + readableTexture.wrapT = texture.wrapT; + readableTexture.colorSpace = texture.colorSpace; + readableTexture.name = texture.name; + + if ( _renderer ) { + + _renderer.forceContextLoss(); + _renderer.dispose(); + _renderer = null; + + } + + return readableTexture; + +} diff --git a/examples/jsm/utils/WebGPUTextureUtils.js b/examples/jsm/utils/WebGPUTextureUtils.js new file mode 100644 index 00000000000000..1286fe6c478fa1 --- /dev/null +++ b/examples/jsm/utils/WebGPUTextureUtils.js @@ -0,0 +1,81 @@ +import { + QuadMesh, + NodeMaterial, + WebGPURenderer, + CanvasTexture +} from 'three'; +import { texture, uv } from 'three/tsl'; + +/** + * @module WebGPUTextureUtils + * @three_import import * as WebGPUTextureUtils from 'three/addons/utils/WebGPUTextureUtils.js'; + */ + +let _renderer; +const _quadMesh = /*@__PURE__*/ new QuadMesh(); + +/** + * Returns an uncompressed version of the given compressed texture. + * + * This module can only be used with {@link WebGPURenderer}. When using {@link WebGLRenderer}, + * import the function from {@link WebGLTextureUtils}. + * + * @async + * @param {CompressedTexture} blitTexture - The compressed texture. + * @param {number} [maxTextureSize=Infinity] - The maximum size of the uncompressed texture. + * @param {?WebGPURenderer} [renderer=null] - A reference to a renderer. + * @return {Promise} A Promise that resolved with the uncompressed texture. + */ +export async function decompress( blitTexture, maxTextureSize = Infinity, renderer = null ) { + + if ( renderer === null ) { + + renderer = _renderer = new WebGPURenderer(); + await renderer.init(); + + } + + const material = new NodeMaterial(); + + material.fragmentNode = texture( blitTexture, uv().flipY() ); + + const width = Math.min( blitTexture.image.width, maxTextureSize ); + const height = Math.min( blitTexture.image.height, maxTextureSize ); + + const currentOutputColorSpace = renderer.outputColorSpace; + + renderer.setSize( width, height ); + renderer.outputColorSpace = blitTexture.colorSpace; + + _quadMesh.material = material; + _quadMesh.render( renderer ); + + renderer.outputColorSpace = currentOutputColorSpace; + + const canvas = document.createElement( 'canvas' ); + const context = canvas.getContext( '2d' ); + + canvas.width = width; + canvas.height = height; + + context.drawImage( renderer.domElement, 0, 0, width, height ); + + const readableTexture = new CanvasTexture( canvas ); + + readableTexture.minFilter = blitTexture.minFilter; + readableTexture.magFilter = blitTexture.magFilter; + readableTexture.wrapS = blitTexture.wrapS; + readableTexture.wrapT = blitTexture.wrapT; + readableTexture.colorSpace = blitTexture.colorSpace; + readableTexture.name = blitTexture.name; + + if ( _renderer !== null ) { + + _renderer.dispose(); + _renderer = null; + + } + + return readableTexture; + +} diff --git a/examples/jsm/utils/WorkerPool.js b/examples/jsm/utils/WorkerPool.js index c8d191609387e6..9d76566b42f1e3 100644 --- a/examples/jsm/utils/WorkerPool.js +++ b/examples/jsm/utils/WorkerPool.js @@ -1,17 +1,60 @@ /** - * @author Deepkolos / https://github.com/deepkolos + * A simple pool for managing Web Workers. + * + * @three_import import { WorkerPool } from 'three/addons/utils/WorkerPool.js'; */ - export class WorkerPool { + /** + * Constructs a new Worker pool. + * + * @param {number} [pool=4] - The size of the pool. + */ constructor( pool = 4 ) { + /** + * The size of the pool. + * + * @type {number} + * @default 4 + */ this.pool = pool; + + /** + * A message queue. + * + * @type {Array} + */ this.queue = []; + + /** + * An array of Workers. + * + * @type {Array} + */ this.workers = []; + + /** + * An array with resolve functions for messages. + * + * @type {Array} + */ this.workersResolve = []; + + /** + * The current worker status. + * + * @type {number} + */ this.workerStatus = 0; + /** + * A factory function for creating workers. + * + * @type {?Function} + */ + this.workerCreator = null; + } _initWorker( workerId ) { @@ -54,18 +97,36 @@ export class WorkerPool { } + /** + * Sets a function that is responsible for creating Workers. + * + * @param {Function} workerCreator - The worker creator function. + */ setWorkerCreator( workerCreator ) { this.workerCreator = workerCreator; } + /** + * Sets the Worker limit + * + * @param {number} pool - The size of the pool. + */ setWorkerLimit( pool ) { this.pool = pool; } + /** + * Post a message to an idle Worker. If no Worker is available, + * the message is pushed into a message queue for later processing. + * + * @param {Object} msg - The message. + * @param {Array} transfer - An array with array buffers for data transfer. + * @return {Promise} A Promise that resolves when the message has been processed. + */ postMessage( msg, transfer ) { return new Promise( ( resolve ) => { @@ -89,6 +150,10 @@ export class WorkerPool { } + /** + * Terminates all Workers of this pool. Call this method whenever this + * Worker pool is no longer used in your app. + */ dispose() { this.workers.forEach( ( worker ) => worker.terminate() ); diff --git a/examples/jsm/webxr/ARButton.js b/examples/jsm/webxr/ARButton.js index cc2b0173109074..ef2642914d0b3a 100644 --- a/examples/jsm/webxr/ARButton.js +++ b/examples/jsm/webxr/ARButton.js @@ -1,5 +1,24 @@ +/** + * A utility class for creating a button that allows to initiate + * immersive AR sessions based on WebXR. The button can be created + * with a factory method and then appended ot the website's DOM. + * + * ```js + * document.body.appendChild( ARButton.createButton( renderer ) ); + * ``` + * + * @hideconstructor + * @three_import import { ARButton } from 'three/addons/webxr/ARButton.js'; + */ class ARButton { + /** + * Constructs a new AR button. + * + * @param {WebGLRenderer|WebGPURenderer} renderer - The renderer. + * @param {XRSessionInit} [sessionInit] - The a configuration object for the AR session. + * @return {HTMLElement} The button or an error message if `immersive-ar` isn't supported. + */ static createButton( renderer, sessionInit = {} ) { const button = document.createElement( 'button' ); @@ -104,10 +123,34 @@ class ARButton { currentSession.end(); + if ( navigator.xr.offerSession !== undefined ) { + + navigator.xr.offerSession( 'immersive-ar', sessionInit ) + .then( onSessionStarted ) + .catch( ( err ) => { + + console.warn( err ); + + } ); + + } + } }; + if ( navigator.xr.offerSession !== undefined ) { + + navigator.xr.offerSession( 'immersive-ar', sessionInit ) + .then( onSessionStarted ) + .catch( ( err ) => { + + console.warn( err ); + + } ); + + } + } function disableButton() { diff --git a/examples/jsm/webxr/OculusHandModel.js b/examples/jsm/webxr/OculusHandModel.js index 19589211e8ec89..c8cb4806c82712 100644 --- a/examples/jsm/webxr/OculusHandModel.js +++ b/examples/jsm/webxr/OculusHandModel.js @@ -4,17 +4,78 @@ import { XRHandMeshModel } from './XRHandMeshModel.js'; const TOUCH_RADIUS = 0.01; const POINTING_JOINT = 'index-finger-tip'; +/** + * Represents an Oculus hand model. + * + * @augments Object3D + * @three_import import { OculusHandModel } from 'three/addons/webxr/OculusHandModel.js'; + */ class OculusHandModel extends Object3D { - constructor( controller, loader = null ) { + /** + * Constructs a new Oculus hand model. + * + * @param {Group} controller - The hand controller. + * @param {?Loader} [loader=null] - A loader that is used to load hand models. + * @param {?Function} [onLoad=null] - A callback that is executed when a hand model has been loaded. + */ + constructor( controller, loader = null, onLoad = null ) { super(); + /** + * The hand controller. + * + * @type {Group} + */ this.controller = controller; + + /** + * The motion controller. + * + * @type {?MotionController} + * @default null + */ this.motionController = null; + + /** + * The model's environment map. + * + * @type {?Texture} + * @default null + */ this.envMap = null; + + /** + * A loader that is used to load hand models. + * + * @type {?Loader} + * @default null + */ this.loader = loader; + /** + * A callback that is executed when a hand model has been loaded. + * + * @type {?Function} + * @default null + */ + this.onLoad = onLoad; + + /** + * The path to the model repository. + * + * @type {?string} + * @default null + */ + this.path = null; + + /** + * The model mesh. + * + * @type {Mesh} + * @default null + */ this.mesh = null; controller.addEventListener( 'connected', ( event ) => { @@ -25,7 +86,7 @@ class OculusHandModel extends Object3D { this.xrInputSource = xrInputSource; - this.motionController = new XRHandMeshModel( this, controller, this.path, xrInputSource.handedness, this.loader ); + this.motionController = new XRHandMeshModel( this, controller, this.path, xrInputSource.handedness, this.loader, this.onLoad ); } @@ -40,6 +101,12 @@ class OculusHandModel extends Object3D { } + /** + * Overwritten with a custom implementation. Makes sure the motion controller updates the mesh. + * + * @param {boolean} [force=false] - When set to `true`, a recomputation of world matrices is forced even + * when {@link Object3D#matrixWorldAutoUpdate} is set to `false`. + */ updateMatrixWorld( force ) { super.updateMatrixWorld( force ); @@ -52,6 +119,11 @@ class OculusHandModel extends Object3D { } + /** + * Returns the pointer position which is the position of the index finger tip. + * + * @return {Vector3|null} The pointer position. Returns `null` if not index finger tip joint was found. + */ getPointerPosition() { const indexFingerTip = this.controller.joints[ POINTING_JOINT ]; @@ -67,6 +139,13 @@ class OculusHandModel extends Object3D { } + /** + * Returns `true` if the current pointer position (the index finger tip) intersections + * with the given box object. + * + * @param {Mesh} boxObject - The box object. + * @return {boolean} Whether an intersection was found or not. + */ intersectBoxObject( boxObject ) { const pointerPosition = this.getPointerPosition(); @@ -84,6 +163,12 @@ class OculusHandModel extends Object3D { } + /** + * Executed actions depending on the interaction state with + * the given button. + * + * @param {Object} button - The button. + */ checkButton( button ) { if ( this.intersectBoxObject( button ) ) { diff --git a/examples/jsm/webxr/OculusHandPointerModel.js b/examples/jsm/webxr/OculusHandPointerModel.js index 9732f75bab5fc4..6df9c10d77fa16 100644 --- a/examples/jsm/webxr/OculusHandPointerModel.js +++ b/examples/jsm/webxr/OculusHandPointerModel.js @@ -1,4 +1,4 @@ -import * as THREE from 'three'; +import { BufferGeometry, Float32BufferAttribute, Matrix4, Mesh, MeshBasicMaterial, Object3D, Raycaster, SphereGeometry, Vector3 } from 'three'; const PINCH_MAX = 0.05; const PINCH_THRESHOLD = 0.02; @@ -13,54 +13,136 @@ const POINTER_LENGTH = 0.035; const POINTER_SEGMENTS = 16; const POINTER_RINGS = 12; const POINTER_HEMISPHERE_ANGLE = 110; -const YAXIS = new THREE.Vector3( 0, 1, 0 ); -const ZAXIS = new THREE.Vector3( 0, 0, 1 ); +const YAXIS = /* @__PURE__ */ new Vector3( 0, 1, 0 ); +const ZAXIS = /* @__PURE__ */ new Vector3( 0, 0, 1 ); const CURSOR_RADIUS = 0.02; const CURSOR_MAX_DISTANCE = 1.5; -class OculusHandPointerModel extends THREE.Object3D { - +/** + * Represents an Oculus hand pointer model. + * + * @augments Object3D + * @three_import import { OculusHandPointerModel } from 'three/addons/webxr/OculusHandPointerModel.js'; + */ +class OculusHandPointerModel extends Object3D { + + /** + * Constructs a new Oculus hand model. + * + * @param {Group} hand - The hand controller. + * @param {Group} controller - The WebXR controller in target ray space. + */ constructor( hand, controller ) { super(); + /** + * The hand controller. + * + * @type {Group} + */ this.hand = hand; + + /** + * The WebXR controller in target ray space. + * + * @type {Group} + */ this.controller = controller; + + // Unused this.motionController = null; this.envMap = null; - this.mesh = null; + /** + * The pointer geometry. + * + * @type {?BufferGeometry} + * @default null + */ this.pointerGeometry = null; + + /** + * The pointer mesh. + * + * @type {?Mesh} + * @default null + */ this.pointerMesh = null; + + /** + * The pointer object that holds the pointer mesh. + * + * @type {?Object3D} + * @default null + */ this.pointerObject = null; + /** + * Whether the model is pinched or not. + * + * @type {?boolean} + * @default false + */ this.pinched = false; + + /** + * Whether the model is attached or not. + * + * @type {boolean} + * @default false + */ this.attached = false; + /** + * The cursor object. + * + * @type {?Mesh} + * @default null + */ this.cursorObject = null; + /** + * The internal raycaster used for detecting + * intersections. + * + * @type {?Raycaster} + * @default null + */ this.raycaster = null; - hand.addEventListener( 'connected', ( event ) => { + this._onConnected = this._onConnected.bind( this ); + this._onDisconnected = this._onDisconnected.bind( this ); + this.hand.addEventListener( 'connected', this._onConnected ); + this.hand.addEventListener( 'disconnected', this._onDisconnected ); + + } + + _onConnected( event ) { - const xrInputSource = event.data; + const xrInputSource = event.data; + if ( xrInputSource.hand ) { - if ( xrInputSource.hand ) { + this.visible = true; + this.xrInputSource = xrInputSource; - this.visible = true; - this.xrInputSource = xrInputSource; + this.createPointer(); - if ( this.pointerObject === null ) { + } - this.createPointer(); + } - } + _onDisconnected() { - } + this.visible = false; + this.xrInputSource = null; + + if ( this.pointerGeometry ) this.pointerGeometry.dispose(); + if ( this.pointerMesh && this.pointerMesh.material ) this.pointerMesh.material.dispose(); - } ); + this.clear(); } @@ -83,7 +165,7 @@ class OculusHandPointerModel extends THREE.Object3D { const vertices = this.pointerGeometry.attributes.position.array; // first ring for front face - const frontFaceBase = new THREE.Vector3( + const frontFaceBase = new Vector3( POINTER_FRONT_RADIUS, 0, - 1 * ( POINTER_LENGTH - rearRadius ) @@ -91,7 +173,7 @@ class OculusHandPointerModel extends THREE.Object3D { this._drawVerticesRing( vertices, frontFaceBase, 0 ); // rings for rear hemisphere - const rearBase = new THREE.Vector3( + const rearBase = new Vector3( Math.sin( ( Math.PI * POINTER_HEMISPHERE_ANGLE ) / 180 ) * rearRadius, Math.cos( ( Math.PI * POINTER_HEMISPHERE_ANGLE ) / 180 ) * rearRadius, 0 @@ -109,7 +191,7 @@ class OculusHandPointerModel extends THREE.Object3D { // front and rear face center vertices const frontCenterIndex = POINTER_SEGMENTS * ( 1 + POINTER_RINGS ); const rearCenterIndex = POINTER_SEGMENTS * ( 1 + POINTER_RINGS ) + 1; - const frontCenter = new THREE.Vector3( + const frontCenter = new Vector3( 0, 0, - 1 * ( POINTER_LENGTH - rearRadius ) @@ -117,19 +199,22 @@ class OculusHandPointerModel extends THREE.Object3D { vertices[ frontCenterIndex * 3 ] = frontCenter.x; vertices[ frontCenterIndex * 3 + 1 ] = frontCenter.y; vertices[ frontCenterIndex * 3 + 2 ] = frontCenter.z; - const rearCenter = new THREE.Vector3( 0, 0, rearRadius ); + const rearCenter = new Vector3( 0, 0, rearRadius ); vertices[ rearCenterIndex * 3 ] = rearCenter.x; vertices[ rearCenterIndex * 3 + 1 ] = rearCenter.y; vertices[ rearCenterIndex * 3 + 2 ] = rearCenter.z; this.pointerGeometry.setAttribute( 'position', - new THREE.Float32BufferAttribute( vertices, 3 ) + new Float32BufferAttribute( vertices, 3 ) ); // verticesNeedUpdate = true; } + /** + * Creates a pointer mesh and adds it to this model. + */ createPointer() { let i, j; @@ -138,11 +223,11 @@ class OculusHandPointerModel extends THREE.Object3D { ).fill( 0 ); // const vertices = []; const indices = []; - this.pointerGeometry = new THREE.BufferGeometry(); + this.pointerGeometry = new BufferGeometry(); this.pointerGeometry.setAttribute( 'position', - new THREE.Float32BufferAttribute( vertices, 3 ) + new Float32BufferAttribute( vertices, 3 ) ); this._updatePointerVertices( POINTER_REAR_RADIUS ); @@ -200,27 +285,27 @@ class OculusHandPointerModel extends THREE.Object3D { POINTER_SEGMENTS * POINTER_RINGS ); - const material = new THREE.MeshBasicMaterial(); + const material = new MeshBasicMaterial(); material.transparent = true; material.opacity = POINTER_OPACITY_MIN; this.pointerGeometry.setIndex( indices ); - this.pointerMesh = new THREE.Mesh( this.pointerGeometry, material ); + this.pointerMesh = new Mesh( this.pointerGeometry, material ); this.pointerMesh.position.set( 0, 0, - 1 * POINTER_REAR_RADIUS ); - this.pointerObject = new THREE.Object3D(); + this.pointerObject = new Object3D(); this.pointerObject.add( this.pointerMesh ); - this.raycaster = new THREE.Raycaster(); + this.raycaster = new Raycaster(); // create cursor - const cursorGeometry = new THREE.SphereGeometry( CURSOR_RADIUS, 10, 10 ); - const cursorMaterial = new THREE.MeshBasicMaterial(); + const cursorGeometry = new SphereGeometry( CURSOR_RADIUS, 10, 10 ); + const cursorMaterial = new MeshBasicMaterial(); cursorMaterial.transparent = true; cursorMaterial.opacity = POINTER_OPACITY_MIN; - this.cursorObject = new THREE.Mesh( cursorGeometry, cursorMaterial ); + this.cursorObject = new Mesh( cursorGeometry, cursorMaterial ); this.pointerObject.add( this.cursorObject ); this.add( this.pointerObject ); @@ -232,7 +317,7 @@ class OculusHandPointerModel extends THREE.Object3D { if ( this.raycaster ) { const pointerMatrix = this.pointerObject.matrixWorld; - const tempMatrix = new THREE.Matrix4(); + const tempMatrix = new Matrix4(); tempMatrix.identity().extractRotation( pointerMatrix ); this.raycaster.ray.origin.setFromMatrixPosition( pointerMatrix ); this.raycaster.ray.direction.set( 0, 0, - 1 ).applyMatrix4( tempMatrix ); @@ -304,6 +389,12 @@ class OculusHandPointerModel extends THREE.Object3D { } + /** + * Overwritten with a custom implementation. Makes sure the internal pointer and raycaster are updated. + * + * @param {boolean} [force=false] - When set to `true`, a recomputation of world matrices is forced even + * when {@link Object3D#matrixWorldAutoUpdate} is set to `false`. + */ updateMatrixWorld( force ) { super.updateMatrixWorld( force ); @@ -316,24 +407,47 @@ class OculusHandPointerModel extends THREE.Object3D { } + /** + * Returns `true` is the model is pinched. + * + * @return {boolean} Whether the model is pinched or not. + */ isPinched() { return this.pinched; } + /** + * Sets the attached state. + * + * @param {boolean} attached - Whether the model is attached or not. + */ setAttached( attached ) { this.attached = attached; } + /** + * Returns `true` is the model is attached. + * + * @return {boolean} Whether the model is attached or not. + */ isAttached() { return this.attached; } + /** + * Performs an intersection test with the model's raycaster and the given object. + * + * @param {Object3D} object - The 3D object to check for intersection with the ray. + * @param {boolean} [recursive=true] - If set to `true`, it also checks all descendants. + * Otherwise it only checks intersection with the object. + * @return {Array} An array holding the intersection points. + */ intersectObject( object, recursive = true ) { if ( this.raycaster ) { @@ -344,6 +458,14 @@ class OculusHandPointerModel extends THREE.Object3D { } + /** + * Performs an intersection test with the model's raycaster and the given objects. + * + * @param {Array} objects - The 3D objects to check for intersection with the ray. + * @param {boolean} [recursive=true] - If set to `true`, it also checks all descendants. + * Otherwise it only checks intersection with the object. + * @return {Array} An array holding the intersection points. + */ intersectObjects( objects, recursive = true ) { if ( this.raycaster ) { @@ -354,12 +476,20 @@ class OculusHandPointerModel extends THREE.Object3D { } + /** + * Checks for intersections between the model's raycaster and the given objects. The method + * updates the cursor object to the intersection point. + * + * @param {Array} objects - The 3D objects to check for intersection with the ray. + * @param {boolean} [recursive=false] - If set to `true`, it also checks all descendants. + * Otherwise it only checks intersection with the object. + */ checkIntersections( objects, recursive = false ) { if ( this.raycaster && ! this.attached ) { const intersections = this.raycaster.intersectObjects( objects, recursive ); - const direction = new THREE.Vector3( 0, 0, - 1 ); + const direction = new Vector3( 0, 0, - 1 ); if ( intersections.length > 0 ) { const intersection = intersections[ 0 ]; @@ -376,9 +506,14 @@ class OculusHandPointerModel extends THREE.Object3D { } + /** + * Sets the cursor to the given distance. + * + * @param {number} distance - The distance to set the cursor to. + */ setCursor( distance ) { - const direction = new THREE.Vector3( 0, 0, - 1 ); + const direction = new Vector3( 0, 0, - 1 ); if ( this.raycaster && ! this.attached ) { this.cursorObject.position.copy( direction.multiplyScalar( distance ) ); @@ -387,6 +522,18 @@ class OculusHandPointerModel extends THREE.Object3D { } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ + dispose() { + + this._onDisconnected(); + this.hand.removeEventListener( 'connected', this._onConnected ); + this.hand.removeEventListener( 'disconnected', this._onDisconnected ); + + } + } export { OculusHandPointerModel }; diff --git a/examples/jsm/webxr/Text2D.js b/examples/jsm/webxr/Text2D.js index d50c06c9766ec1..771c8556a535ed 100644 --- a/examples/jsm/webxr/Text2D.js +++ b/examples/jsm/webxr/Text2D.js @@ -1,5 +1,19 @@ -import * as THREE from 'three'; +import { DoubleSide, Mesh, MeshBasicMaterial, PlaneGeometry, Texture } from 'three'; +/** + * @module Text2D + * @three_import import * as Text2D from 'three/addons/webxr/Text2D.js'; + */ + +/** + * A helper function for creating a simple plane mesh + * that can be used as a text label. The mesh's material + * holds a canvas texture that displays the given message. + * + * @param {string} message - The message to display. + * @param {number} height - The labels height. + * @return {Mesh} The plane mesh representing a text label. + */ function createText( message, height ) { const canvas = document.createElement( 'canvas' ); @@ -17,20 +31,20 @@ function createText( message, height ) { context.fillStyle = '#ffffff'; context.fillText( message, textWidth / 2, textHeight / 2 ); - const texture = new THREE.Texture( canvas ); + const texture = new Texture( canvas ); texture.needsUpdate = true; - const material = new THREE.MeshBasicMaterial( { + const material = new MeshBasicMaterial( { color: 0xffffff, - side: THREE.DoubleSide, + side: DoubleSide, map: texture, transparent: true, } ); - const geometry = new THREE.PlaneGeometry( + const geometry = new PlaneGeometry( ( height * textWidth ) / textHeight, height ); - const plane = new THREE.Mesh( geometry, material ); + const plane = new Mesh( geometry, material ); return plane; } diff --git a/examples/jsm/webxr/VRButton.js b/examples/jsm/webxr/VRButton.js index 6856a21b17aa45..e3f2a658f28aa3 100644 --- a/examples/jsm/webxr/VRButton.js +++ b/examples/jsm/webxr/VRButton.js @@ -1,6 +1,25 @@ +/** + * A utility class for creating a button that allows to initiate + * immersive VR sessions based on WebXR. The button can be created + * with a factory method and then appended ot the website's DOM. + * + * ```js + * document.body.appendChild( VRButton.createButton( renderer ) ); + * ``` + * + * @hideconstructor + * @three_import import { VRButton } from 'three/addons/webxr/VRButton.js'; + */ class VRButton { - static createButton( renderer ) { + /** + * Constructs a new VR button. + * + * @param {WebGLRenderer|WebGPURenderer} renderer - The renderer. + * @param {XRSessionInit} [sessionInit] - The a configuration object for the AR session. + * @return {HTMLElement} The button or an error message if `immersive-ar` isn't supported. + */ + static createButton( renderer, sessionInit = {} ) { const button = document.createElement( 'button' ); @@ -39,6 +58,23 @@ class VRButton { button.textContent = 'ENTER VR'; + // WebXR's requestReferenceSpace only works if the corresponding feature + // was requested at session creation time. For simplicity, just ask for + // the interesting ones as optional features, but be aware that the + // requestReferenceSpace call will fail if it turns out to be unavailable. + // ('local' is always available for immersive sessions and doesn't need to + // be requested separately.) + + const sessionOptions = { + ...sessionInit, + optionalFeatures: [ + 'local-floor', + 'bounded-floor', + 'layers', + ...( sessionInit.optionalFeatures || [] ) + ], + }; + button.onmouseenter = function () { button.style.opacity = '1.0'; @@ -55,24 +91,40 @@ class VRButton { if ( currentSession === null ) { - // WebXR's requestReferenceSpace only works if the corresponding feature - // was requested at session creation time. For simplicity, just ask for - // the interesting ones as optional features, but be aware that the - // requestReferenceSpace call will fail if it turns out to be unavailable. - // ('local' is always available for immersive sessions and doesn't need to - // be requested separately.) - - const sessionInit = { optionalFeatures: [ 'local-floor', 'bounded-floor', 'hand-tracking', 'layers' ] }; - navigator.xr.requestSession( 'immersive-vr', sessionInit ).then( onSessionStarted ); + navigator.xr.requestSession( 'immersive-vr', sessionOptions ).then( onSessionStarted ); } else { currentSession.end(); + if ( navigator.xr.offerSession !== undefined ) { + + navigator.xr.offerSession( 'immersive-vr', sessionOptions ) + .then( onSessionStarted ) + .catch( ( err ) => { + + console.warn( err ); + + } ); + + } + } }; + if ( navigator.xr.offerSession !== undefined ) { + + navigator.xr.offerSession( 'immersive-vr', sessionOptions ) + .then( onSessionStarted ) + .catch( ( err ) => { + + console.warn( err ); + + } ); + + } + } function disableButton() { @@ -174,9 +226,14 @@ class VRButton { } + /** + * Registers a `sessiongranted` event listener. When a session is granted, the {@link VRButton#xrSessionIsGranted} + * flag will evaluate to `true`. This method is automatically called by the module itself so there + * should be no need to use it on app level. + */ static registerSessionGrantedListener() { - if ( 'xr' in navigator ) { + if ( typeof navigator !== 'undefined' && 'xr' in navigator ) { // WebXRViewer (based on Firefox) has a bug where addEventListener // throws a silent exception and aborts execution entirely. @@ -194,6 +251,13 @@ class VRButton { } +/** + * Whether a XR session has been granted or not. + * + * @static + * @type {boolean} + * @default false + */ VRButton.xrSessionIsGranted = false; VRButton.registerSessionGrantedListener(); diff --git a/examples/jsm/webxr/XRButton.js b/examples/jsm/webxr/XRButton.js index f33cbbea25111b..fab56eef0152de 100644 --- a/examples/jsm/webxr/XRButton.js +++ b/examples/jsm/webxr/XRButton.js @@ -1,6 +1,29 @@ +/** + * A utility class for creating a button that allows to initiate + * immersive XR sessions based on WebXR. The button can be created + * with a factory method and then appended ot the website's DOM. + * + * ```js + * document.body.appendChild( XRButton.createButton( renderer ) ); + * ``` + * + * Compared to {@link ARButton} and {@link VRButton}, this class will + * try to offer an immersive AR session first. If the device does not + * support this type of session, it uses an immersive VR session. + * + * @hideconstructor + * @three_import import { XRButton } from 'three/addons/webxr/XRButton.js'; + */ class XRButton { - static createButton( renderer ) { + /** + * Constructs a new XR button. + * + * @param {WebGLRenderer|WebGPURenderer} renderer - The renderer. + * @param {XRSessionInit} [sessionInit] - The a configuration object for the AR session. + * @return {HTMLElement} The button or an error message if WebXR isn't supported. + */ + static createButton( renderer, sessionInit = {} ) { const button = document.createElement( 'button' ); @@ -40,6 +63,16 @@ class XRButton { button.textContent = 'START XR'; + const sessionOptions = { + ...sessionInit, + optionalFeatures: [ + 'local-floor', + 'bounded-floor', + 'layers', + ...( sessionInit.optionalFeatures || [] ) + ], + }; + button.onmouseenter = function () { button.style.opacity = '1.0'; @@ -56,26 +89,41 @@ class XRButton { if ( currentSession === null ) { - const sessionInit = { - optionalFeatures: [ - 'local-floor', - 'bounded-floor', - 'hand-tracking', - 'layers' - ] - }; - - navigator.xr.requestSession( mode, sessionInit ) + navigator.xr.requestSession( mode, sessionOptions ) .then( onSessionStarted ); } else { currentSession.end(); + if ( navigator.xr.offerSession !== undefined ) { + + navigator.xr.offerSession( mode, sessionOptions ) + .then( onSessionStarted ) + .catch( ( err ) => { + + console.warn( err ); + + } ); + + } + } }; + if ( navigator.xr.offerSession !== undefined ) { + + navigator.xr.offerSession( mode, sessionOptions ) + .then( onSessionStarted ) + .catch( ( err ) => { + + console.warn( err ); + + } ); + + } + } function disableButton() { diff --git a/examples/jsm/webxr/XRControllerModelFactory.js b/examples/jsm/webxr/XRControllerModelFactory.js index ba638190928706..4667423fa58d69 100644 --- a/examples/jsm/webxr/XRControllerModelFactory.js +++ b/examples/jsm/webxr/XRControllerModelFactory.js @@ -16,17 +16,44 @@ import { const DEFAULT_PROFILES_PATH = 'https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@1.0/dist/profiles'; const DEFAULT_PROFILE = 'generic-trigger'; +/** + * Represents a XR controller model. + * + * @augments Object3D + */ class XRControllerModel extends Object3D { + /** + * Constructs a new XR controller model. + */ constructor() { super(); + /** + * The motion controller. + * + * @type {?MotionController} + * @default null + */ this.motionController = null; + + /** + * The controller's environment map. + * + * @type {?Texture} + * @default null + */ this.envMap = null; } + /** + * Sets an environment map that is applied to the controller model. + * + * @param {?Texture} envMap - The environment map to apply. + * @return {XRControllerModel} A reference to this instance. + */ setEnvironmentMap( envMap ) { if ( this.envMap == envMap ) { @@ -52,8 +79,11 @@ class XRControllerModel extends Object3D { } /** - * Polls data from the XRInputSource and updates the model's components to match - * the real world data + * Overwritten with a custom implementation. Polls data from the XRInputSource and updates the + * model's components to match the real world data. + * + * @param {boolean} [force=false] - When set to `true`, a recomputation of world matrices is forced even + * when {@link Object3D#matrixWorldAutoUpdate} is set to `false`. */ updateMatrixWorld( force ) { @@ -107,8 +137,12 @@ class XRControllerModel extends Object3D { /** * Walks the model's tree to find the nodes needed to animate the components and - * saves them to the motionContoller components for use in the frame loop. When + * saves them to the motionController components for use in the frame loop. When * touchpads are found, attaches a touch dot to them. + * + * @private + * @param {MotionController} motionController + * @param {Object3D} scene */ function findNodes( motionController, scene ) { @@ -204,14 +238,60 @@ function addAssetSceneToControllerModel( controllerModel, scene ) { } +/** + * Allows to create controller models for WebXR controllers that can be added as a visual + * representation to your scene. `XRControllerModelFactory` will automatically fetch controller + * models that match what the user is holding as closely as possible. The models should be + * attached to the object returned from getControllerGrip in order to match the orientation of + * the held device. + * + * This module depends on the [motion-controllers]{@link https://github.com/immersive-web/webxr-input-profiles/blob/main/packages/motion-controllers/README.md} + * third-part library. + * + * ```js + * const controllerModelFactory = new XRControllerModelFactory(); + * + * const controllerGrip = renderer.xr.getControllerGrip( 0 ); + * controllerGrip.add( controllerModelFactory.createControllerModel( controllerGrip ) ); + * scene.add( controllerGrip ); + * ``` + * + * @three_import import { XRControllerModelFactory } from 'three/addons/webxr/XRControllerModelFactory.js'; + */ class XRControllerModelFactory { - constructor( gltfLoader = null ) { - + /** + * Constructs a new XR controller model factory. + * + * @param {?GLTFLoader} [gltfLoader=null] - A glTF loader that is used to load controller models. + * @param {?Function} [onLoad=null] - A callback that is executed when a controller model has been loaded. + */ + constructor( gltfLoader = null, onLoad = null ) { + + /** + * A glTF loader that is used to load controller models. + * + * @type {?GLTFLoader} + * @default null + */ this.gltfLoader = gltfLoader; + + /** + * The path to the model repository. + * + * @type {string} + */ this.path = DEFAULT_PROFILES_PATH; this._assetCache = {}; + /** + * A callback that is executed when a controller model has been loaded. + * + * @type {?Function} + * @default null + */ + this.onLoad = onLoad; + // If a GLTFLoader wasn't supplied to the constructor create a new one. if ( ! this.gltfLoader ) { @@ -221,6 +301,26 @@ class XRControllerModelFactory { } + /** + * Sets the path to the model repository. + * + * @param {string} path - The path to set. + * @return {XRControllerModelFactory} A reference to this instance. + */ + setPath( path ) { + + this.path = path; + + return this; + + } + + /** + * Creates a controller model for the given WebXR controller. + * + * @param {Group} controller - The controller. + * @return {XRControllerModel} The XR controller model. + */ createControllerModel( controller ) { const controllerModel = new XRControllerModel(); @@ -230,7 +330,7 @@ class XRControllerModelFactory { const xrInputSource = event.data; - if ( xrInputSource.targetRayMode !== 'tracked-pointer' || ! xrInputSource.gamepad ) return; + if ( xrInputSource.targetRayMode !== 'tracked-pointer' || ! xrInputSource.gamepad || xrInputSource.hand ) return; fetchProfile( xrInputSource, this.path, DEFAULT_PROFILE ).then( ( { profile, assetPath } ) => { @@ -247,6 +347,8 @@ class XRControllerModelFactory { addAssetSceneToControllerModel( controllerModel, scene ); + if ( this.onLoad ) this.onLoad( scene ); + } else { if ( ! this.gltfLoader ) { @@ -264,6 +366,8 @@ class XRControllerModelFactory { addAssetSceneToControllerModel( controllerModel, scene ); + if ( this.onLoad ) this.onLoad( scene ); + }, null, () => { diff --git a/examples/jsm/webxr/XREstimatedLight.js b/examples/jsm/webxr/XREstimatedLight.js index ddd2c087dc872a..ccf90117b99f7f 100644 --- a/examples/jsm/webxr/XREstimatedLight.js +++ b/examples/jsm/webxr/XREstimatedLight.js @@ -79,7 +79,7 @@ class SessionLightProbe { onXRFrame( time, xrFrame ) { - // If either this obejct or the XREstimatedLight has been destroyed, stop + // If either this object or the XREstimatedLight has been destroyed, stop // running the frame loop. if ( ! this.xrLight ) { @@ -133,22 +133,50 @@ class SessionLightProbe { } +/** + * This class can be used to represent the environmental light of + * a XR session. It relies on the WebXR Lighting Estimation API. + * + * @augments Group + * @three_import import { XREstimatedLight } from 'three/addons/webxr/XREstimatedLight.js'; + */ export class XREstimatedLight extends Group { + /** + * Constructs a new light. + * + * @param {WebGLRenderer} renderer - The renderer. + * @param {boolean} [environmentEstimation=true] - Whether to use environment estimation or not. + */ constructor( renderer, environmentEstimation = true ) { super(); + /** + * The light probe that represents the estimated light. + * + * @type {LightProbe} + */ this.lightProbe = new LightProbe(); this.lightProbe.intensity = 0; this.add( this.lightProbe ); + /** + * Represents the primary light from the XR environment. + * + * @type {DirectionalLight} + */ this.directionalLight = new DirectionalLight(); this.directionalLight.intensity = 0; this.add( this.directionalLight ); - // Will be set to a cube map in the SessionLightProbe is environment estimation is - // available and requested. + /** + * Will be set to a cube map in the SessionLightProbe if environment estimation is + * available and requested. + * + * @type {?Texture} + * @default null + */ this.environment = null; let sessionLightProbe = null; @@ -198,7 +226,10 @@ export class XREstimatedLight extends Group { } ); - // Done inline to provide access to sessionLightProbe. + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + */ this.dispose = () => { if ( sessionLightProbe ) { diff --git a/examples/jsm/webxr/XRHandMeshModel.js b/examples/jsm/webxr/XRHandMeshModel.js index cad8487b211a74..9af2a29b538457 100644 --- a/examples/jsm/webxr/XRHandMeshModel.js +++ b/examples/jsm/webxr/XRHandMeshModel.js @@ -2,13 +2,47 @@ import { GLTFLoader } from '../loaders/GLTFLoader.js'; const DEFAULT_HAND_PROFILE_PATH = 'https://cdn.jsdelivr.net/npm/@webxr-input-profiles/assets@1.0/dist/profiles/generic-hand/'; +/** + * Represents one of the hand model types {@link XRHandModelFactory} might produce + * depending on the selected profile. `XRHandMeshModel` represents a hand with a + * custom asset. + * + * @three_import import { XRHandMeshModel } from 'three/addons/webxr/XRHandMeshModel.js'; + */ class XRHandMeshModel { - constructor( handModel, controller, path, handedness, loader = null ) { - + /** + * Constructs a new XR hand mesh model. + * + * @param {XRHandModel} handModel - The hand model. + * @param {Group} controller - The WebXR controller. + * @param {?string} path - The model path. + * @param {XRHandedness} handedness - The handedness of the XR input source. + * @param {?Loader} [loader=null] - The loader. If not provided, an instance of `GLTFLoader` will be used to load models. + * @param {?Function} [onLoad=null] - A callback that is executed when a controller model has been loaded. + */ + constructor( handModel, controller, path, handedness, loader = null, onLoad = null ) { + + /** + * The WebXR controller. + * + * @type {Group} + */ this.controller = controller; + + /** + * The hand model. + * + * @type {XRHandModel} + */ this.handModel = handModel; + /** + * An array of bones representing the bones + * of the hand skeleton. + * + * @type {Array} + */ this.bones = []; if ( loader === null ) { @@ -74,10 +108,15 @@ class XRHandMeshModel { } ); + if ( onLoad ) onLoad( object ); + } ); } + /** + * Updates the mesh based on the tracked XR joints data. + */ updateMesh() { // XR Joints diff --git a/examples/jsm/webxr/XRHandModelFactory.js b/examples/jsm/webxr/XRHandModelFactory.js index 37a16d42f9740a..27c0c0a8b76b63 100644 --- a/examples/jsm/webxr/XRHandModelFactory.js +++ b/examples/jsm/webxr/XRHandModelFactory.js @@ -10,20 +10,61 @@ import { XRHandMeshModel } from './XRHandMeshModel.js'; +/** + * Represents a XR hand model. + * + * @augments Object3D + */ class XRHandModel extends Object3D { + /** + * Constructs a new XR hand model. + * + * @param {Group} controller - The hand controller. + */ constructor( controller ) { super(); + /** + * The hand controller. + * + * @type {Group} + */ this.controller = controller; + + /** + * The motion controller. + * + * @type {?MotionController} + * @default null + */ this.motionController = null; + + /** + * The controller's environment map. + * + * @type {?Texture} + * @default null + */ this.envMap = null; + /** + * The model mesh. + * + * @type {Mesh} + * @default null + */ this.mesh = null; } + /** + * Overwritten with a custom implementation. Makes sure the motion controller updates the mesh. + * + * @param {boolean} [force=false] - When set to `true`, a recomputation of world matrices is forced even + * when {@link Object3D#matrixWorldAutoUpdate} is set to `false`. + */ updateMatrixWorld( force ) { super.updateMatrixWorld( force ); @@ -38,14 +79,62 @@ class XRHandModel extends Object3D { } +/** + * Similar to {@link XRControllerModelFactory}, this class allows to create hand models + * for WebXR controllers that can be added as a visual representation to your scene. + * + * ```js + * const handModelFactory = new XRHandModelFactory(); + * + * const hand = renderer.xr.getHand( 0 ); + * hand.add( handModelFactory.createHandModel( hand ) ); + * scene.add( hand ); + * ``` + * + * @three_import import { XRHandModelFactory } from 'three/addons/webxr/XRHandModelFactory.js'; + */ class XRHandModelFactory { - constructor() { - + /** + * Constructs a new XR hand model factory. + * + * @param {?GLTFLoader} [gltfLoader=null] - A glTF loader that is used to load hand models. + * @param {?Function} [onLoad=null] - A callback that is executed when a hand model has been loaded. + */ + constructor( gltfLoader = null, onLoad = null ) { + + /** + * A glTF loader that is used to load hand models. + * + * @type {?GLTFLoader} + * @default null + */ + this.gltfLoader = gltfLoader; + + /** + * The path to the model repository. + * + * @type {?string} + * @default null + */ this.path = null; + /** + * A callback that is executed when a hand model has been loaded. + * + * @type {?Function} + * @default null + */ + this.onLoad = onLoad; + } + /** + * Sets the path to the hand model repository. + * + * @param {string} path - The path to set. + * @return {XRHandModelFactory} A reference to this instance. + */ setPath( path ) { this.path = path; @@ -54,6 +143,13 @@ class XRHandModelFactory { } + /** + * Creates a controller model for the given WebXR hand controller. + * + * @param {Group} controller - The hand controller. + * @param {('spheres'|'boxes'|'mesh')} [profile] - The model profile that defines the model type. + * @return {XRHandModel} The XR hand model. + */ createHandModel( controller, profile ) { const handModel = new XRHandModel( controller ); @@ -77,7 +173,7 @@ class XRHandModelFactory { } else if ( profile === 'mesh' ) { - handModel.motionController = new XRHandMeshModel( handModel, controller, this.path, xrInputSource.handedness ); + handModel.motionController = new XRHandMeshModel( handModel, controller, this.path, xrInputSource.handedness, this.gltfLoader, this.onLoad ); } diff --git a/examples/jsm/webxr/XRHandPrimitiveModel.js b/examples/jsm/webxr/XRHandPrimitiveModel.js index db6d3f331c1170..b4800fba9b3409 100644 --- a/examples/jsm/webxr/XRHandPrimitiveModel.js +++ b/examples/jsm/webxr/XRHandPrimitiveModel.js @@ -11,12 +11,46 @@ import { const _matrix = new Matrix4(); const _vector = new Vector3(); +/** + * Represents one of the hand model types {@link XRHandModelFactory} might produce + * depending on the selected profile. `XRHandPrimitiveModel` represents a hand + * with sphere or box primitives according to the selected `primitive` option. + * + * @three_import import { XRHandPrimitiveModel } from 'three/addons/webxr/XRHandPrimitiveModel.js'; + */ class XRHandPrimitiveModel { + /** + * Constructs a new XR hand primitive model. + * + * @param {XRHandModel} handModel - The hand model. + * @param {Group} controller - The WebXR controller. + * @param {string} path - The model path. + * @param {XRHandedness} handedness - The handedness of the XR input source. + * @param {XRHandPrimitiveModel~Options} options - The model options. + */ constructor( handModel, controller, path, handedness, options ) { + /** + * The WebXR controller. + * + * @type {Group} + */ this.controller = controller; + + /** + * The hand model. + * + * @type {XRHandModel} + */ this.handModel = handModel; + + /** + * The model's environment map. + * + * @type {?Texture} + * @default null + */ this.envMap = null; let geometry; @@ -34,6 +68,7 @@ class XRHandPrimitiveModel { const material = new MeshStandardMaterial(); this.handMesh = new InstancedMesh( geometry, material, 30 ); + this.handMesh.frustumCulled = false; this.handMesh.instanceMatrix.setUsage( DynamicDrawUsage ); // will be updated every frame this.handMesh.castShadow = true; this.handMesh.receiveShadow = true; @@ -69,6 +104,9 @@ class XRHandPrimitiveModel { } + /** + * Updates the mesh based on the tracked XR joints data. + */ updateMesh() { const defaultRadius = 0.008; @@ -99,4 +137,11 @@ class XRHandPrimitiveModel { } +/** + * Constructor options of `XRHandPrimitiveModel`. + * + * @typedef {Object} XRHandPrimitiveModel~Options + * @property {('box'|'sphere')} [primitive] - The primitive type. + **/ + export { XRHandPrimitiveModel }; diff --git a/examples/jsm/webxr/XRPlanes.js b/examples/jsm/webxr/XRPlanes.js new file mode 100644 index 00000000000000..37833e44a8fb72 --- /dev/null +++ b/examples/jsm/webxr/XRPlanes.js @@ -0,0 +1,118 @@ +import { + BoxGeometry, + Matrix4, + Mesh, + MeshBasicMaterial, + Object3D +} from 'three'; + +/** + * A utility class for the WebXR Plane Detection Module. If planes + * are detected by WebXR, this class will automatically add them + * as thin box meshes to the scene when below code snippet is used. + * + * ```js + * const planes = new XRPlanes( renderer ); + * scene.add( planes ); + * ``` + * + * @augments Object3D + * @three_import import { XRPlanes } from 'three/addons/webxr/XRPlanes.js'; + */ +class XRPlanes extends Object3D { + + /** + * Constructs a new XR plane container. + * + * @param {WebGLRenderer|WebGPURenderer} renderer - The renderer. + */ + constructor( renderer ) { + + super(); + + const matrix = new Matrix4(); + + const currentPlanes = new Map(); + + const xr = renderer.xr; + + xr.addEventListener( 'planesdetected', event => { + + const frame = event.data; + const planes = frame.detectedPlanes; + + const referenceSpace = xr.getReferenceSpace(); + + let planeschanged = false; + + for ( const [ plane, mesh ] of currentPlanes ) { + + if ( planes.has( plane ) === false ) { + + mesh.geometry.dispose(); + mesh.material.dispose(); + this.remove( mesh ); + + currentPlanes.delete( plane ); + + planeschanged = true; + + } + + } + + for ( const plane of planes ) { + + if ( currentPlanes.has( plane ) === false ) { + + const pose = frame.getPose( plane.planeSpace, referenceSpace ); + matrix.fromArray( pose.transform.matrix ); + + const polygon = plane.polygon; + + let minX = Number.MAX_SAFE_INTEGER; + let maxX = Number.MIN_SAFE_INTEGER; + let minZ = Number.MAX_SAFE_INTEGER; + let maxZ = Number.MIN_SAFE_INTEGER; + + for ( const point of polygon ) { + + minX = Math.min( minX, point.x ); + maxX = Math.max( maxX, point.x ); + minZ = Math.min( minZ, point.z ); + maxZ = Math.max( maxZ, point.z ); + + } + + const width = maxX - minX; + const height = maxZ - minZ; + + const geometry = new BoxGeometry( width, 0.01, height ); + const material = new MeshBasicMaterial( { color: 0xffffff * Math.random() } ); + + const mesh = new Mesh( geometry, material ); + mesh.position.setFromMatrixPosition( matrix ); + mesh.quaternion.setFromRotationMatrix( matrix ); + this.add( mesh ); + + currentPlanes.set( plane, mesh ); + + planeschanged = true; + + } + + } + + if ( planeschanged ) { + + this.dispatchEvent( { type: 'planeschanged' } ); + + } + + } ); + + } + +} + +export { XRPlanes }; diff --git a/examples/luts/B&WLUT.png b/examples/luts/B&WLUT.png new file mode 100644 index 00000000000000..8501696068a326 Binary files /dev/null and b/examples/luts/B&WLUT.png differ diff --git a/examples/luts/NeutralLUT.png b/examples/luts/NeutralLUT.png new file mode 100644 index 00000000000000..8a03f0d03e8e0a Binary files /dev/null and b/examples/luts/NeutralLUT.png differ diff --git a/examples/luts/NightLUT.png b/examples/luts/NightLUT.png new file mode 100644 index 00000000000000..faf5d432721473 Binary files /dev/null and b/examples/luts/NightLUT.png differ diff --git a/examples/misc_animation_groups.html b/examples/misc_animation_groups.html index dfa8f015bac32c..d2539e9b1b8248 100644 --- a/examples/misc_animation_groups.html +++ b/examples/misc_animation_groups.html @@ -12,10 +12,6 @@ three.js webgl - animation - groups - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/misc_exporter_draco.html b/examples/misc_exporter_draco.html index 102955d222af89..ed1015292a85e0 100644 --- a/examples/misc_exporter_draco.html +++ b/examples/misc_exporter_draco.html @@ -13,10 +13,6 @@ - - - - + + + + + diff --git a/examples/misc_exporter_gltf.html b/examples/misc_exporter_gltf.html index 34ae244880bff6..146e0b92e8efde 100644 --- a/examples/misc_exporter_gltf.html +++ b/examples/misc_exporter_gltf.html @@ -11,10 +11,6 @@ three.js webgl - exporter - gltf - - - - diff --git a/examples/misc_exporter_ktx2.html b/examples/misc_exporter_ktx2.html new file mode 100644 index 00000000000000..ad4f390bf0d2a5 --- /dev/null +++ b/examples/misc_exporter_ktx2.html @@ -0,0 +1,203 @@ + + + + three.js webgl - exporter - ktx2 + + + + + +
        + three.js webgl - exporter - ktx2 +
        + + + + + + + diff --git a/examples/misc_exporter_obj.html b/examples/misc_exporter_obj.html index 9a7e2134a8789d..653fd18fbb0e11 100644 --- a/examples/misc_exporter_obj.html +++ b/examples/misc_exporter_obj.html @@ -11,10 +11,6 @@ three.js webgl - exporter - obj - - - - - - - - - - - - - - diff --git a/examples/misc_raycaster_helper.html b/examples/misc_raycaster_helper.html new file mode 100644 index 00000000000000..987fff7b90d1a6 --- /dev/null +++ b/examples/misc_raycaster_helper.html @@ -0,0 +1,113 @@ + + + + three.js misc - raycaster - helper + + + + + +
        + three.js - raycaster - helper
        + See external RaycasterHelper for more information. +
        + + + + + + + \ No newline at end of file diff --git a/examples/misc_uv_tests.html b/examples/misc_uv_tests.html index 9616890434f182..802364d39eeb2f 100644 --- a/examples/misc_uv_tests.html +++ b/examples/misc_uv_tests.html @@ -23,11 +23,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + diff --git a/examples/physics_rapier_basic.html b/examples/physics_rapier_basic.html new file mode 100644 index 00000000000000..4c67068f842edc --- /dev/null +++ b/examples/physics_rapier_basic.html @@ -0,0 +1,189 @@ + + + + three.js physics - rapier3d basic + + + + + + + +
        + three.js physics - rapier basic +
        + + + + + + diff --git a/examples/physics_rapier_character_controller.html b/examples/physics_rapier_character_controller.html new file mode 100644 index 00000000000000..1b489069c2d8f6 --- /dev/null +++ b/examples/physics_rapier_character_controller.html @@ -0,0 +1,243 @@ + + + + three.js physics - rapier3d character controller + + + + + + + +
        + three.js physics - rapier character controller +

        WASD or Arrow keys to move

        +
        + + + + + + diff --git a/examples/physics_rapier_instancing.html b/examples/physics_rapier_instancing.html index 8358be56ae73f6..4a8cf2e5f09e42 100644 --- a/examples/physics_rapier_instancing.html +++ b/examples/physics_rapier_instancing.html @@ -9,13 +9,9 @@
        - three.js physics - rapier3d instancing + three.js physics - rapier instancing
        - - - - + + + + diff --git a/examples/physics_rapier_terrain.html b/examples/physics_rapier_terrain.html new file mode 100644 index 00000000000000..23c273518fefc9 --- /dev/null +++ b/examples/physics_rapier_terrain.html @@ -0,0 +1,359 @@ + + + + Rapier.js terrain heightfield demo + + + + + + +
        +
        Rapier.js physics terrain heightfield demo
        + + + + + + diff --git a/examples/physics_rapier_vehicle_controller.html b/examples/physics_rapier_vehicle_controller.html new file mode 100644 index 00000000000000..a7d8a533c1e6cd --- /dev/null +++ b/examples/physics_rapier_vehicle_controller.html @@ -0,0 +1,377 @@ + + + + three.js physics - rapier3d vehicle controller + + + + + + + +
        + three.js physics - rapier vehicle controller +

        WASD or Arrow keys to move

        +

        Space to brake

        +

        R to reset

        +
        + + + + + + diff --git a/examples/screenshots/css2d_label.jpg b/examples/screenshots/css2d_label.jpg index 666c0f08a45dc3..82eb238b6fa3ea 100644 Binary files a/examples/screenshots/css2d_label.jpg and b/examples/screenshots/css2d_label.jpg differ diff --git a/examples/screenshots/css3d_molecules.jpg b/examples/screenshots/css3d_molecules.jpg index 893af3a34a8fee..794c30c8ac4f4c 100644 Binary files a/examples/screenshots/css3d_molecules.jpg and b/examples/screenshots/css3d_molecules.jpg differ diff --git a/examples/screenshots/games_fps.jpg b/examples/screenshots/games_fps.jpg index 91dfd581fc9e65..c3420f303e7ce0 100644 Binary files a/examples/screenshots/games_fps.jpg and b/examples/screenshots/games_fps.jpg differ diff --git a/examples/screenshots/misc_boxselection.jpg b/examples/screenshots/misc_boxselection.jpg index bafb64312fc320..6f6d22d3bdbf72 100644 Binary files a/examples/screenshots/misc_boxselection.jpg and b/examples/screenshots/misc_boxselection.jpg differ diff --git a/examples/screenshots/misc_controls_arcball.jpg b/examples/screenshots/misc_controls_arcball.jpg index 1b1a03a7ec442d..b0525a647ab319 100644 Binary files a/examples/screenshots/misc_controls_arcball.jpg and b/examples/screenshots/misc_controls_arcball.jpg differ diff --git a/examples/screenshots/misc_controls_drag.jpg b/examples/screenshots/misc_controls_drag.jpg index dedf5df034700b..3011398411a60e 100644 Binary files a/examples/screenshots/misc_controls_drag.jpg and b/examples/screenshots/misc_controls_drag.jpg differ diff --git a/examples/screenshots/misc_controls_fly.jpg b/examples/screenshots/misc_controls_fly.jpg index b6a10e70ae11ef..e1b7d2807b0258 100644 Binary files a/examples/screenshots/misc_controls_fly.jpg and b/examples/screenshots/misc_controls_fly.jpg differ diff --git a/examples/screenshots/misc_controls_map.jpg b/examples/screenshots/misc_controls_map.jpg index bd5403a8ae8d9c..fa2084f8a4c4ea 100644 Binary files a/examples/screenshots/misc_controls_map.jpg and b/examples/screenshots/misc_controls_map.jpg differ diff --git a/examples/screenshots/misc_controls_orbit.jpg b/examples/screenshots/misc_controls_orbit.jpg index 8fe2e8b2641408..542b9608d96c4f 100644 Binary files a/examples/screenshots/misc_controls_orbit.jpg and b/examples/screenshots/misc_controls_orbit.jpg differ diff --git a/examples/screenshots/misc_controls_pointerlock.jpg b/examples/screenshots/misc_controls_pointerlock.jpg index c2ea0329a1a08e..2cd58a3dad26a9 100644 Binary files a/examples/screenshots/misc_controls_pointerlock.jpg and b/examples/screenshots/misc_controls_pointerlock.jpg differ diff --git a/examples/screenshots/misc_controls_trackball.jpg b/examples/screenshots/misc_controls_trackball.jpg index 0bf4e0263e4656..ca6274bd542d01 100644 Binary files a/examples/screenshots/misc_controls_trackball.jpg and b/examples/screenshots/misc_controls_trackball.jpg differ diff --git a/examples/screenshots/misc_controls_transform.jpg b/examples/screenshots/misc_controls_transform.jpg index fc8f843d0045b0..eae5c68f276c25 100644 Binary files a/examples/screenshots/misc_controls_transform.jpg and b/examples/screenshots/misc_controls_transform.jpg differ diff --git a/examples/screenshots/misc_exporter_collada.jpg b/examples/screenshots/misc_exporter_collada.jpg deleted file mode 100644 index 5d4b7b63762157..00000000000000 Binary files a/examples/screenshots/misc_exporter_collada.jpg and /dev/null differ diff --git a/examples/screenshots/misc_exporter_draco.jpg b/examples/screenshots/misc_exporter_draco.jpg index b47629bfd8d2cf..3f95379388c5b4 100644 Binary files a/examples/screenshots/misc_exporter_draco.jpg and b/examples/screenshots/misc_exporter_draco.jpg differ diff --git a/examples/screenshots/misc_exporter_exr.jpg b/examples/screenshots/misc_exporter_exr.jpg new file mode 100644 index 00000000000000..d310ed1f6546b0 Binary files /dev/null and b/examples/screenshots/misc_exporter_exr.jpg differ diff --git a/examples/screenshots/misc_exporter_gltf.jpg b/examples/screenshots/misc_exporter_gltf.jpg index 86e845a0c62abd..c45bf0323930da 100644 Binary files a/examples/screenshots/misc_exporter_gltf.jpg and b/examples/screenshots/misc_exporter_gltf.jpg differ diff --git a/examples/screenshots/misc_exporter_ktx2.jpg b/examples/screenshots/misc_exporter_ktx2.jpg new file mode 100644 index 00000000000000..7dd8ed96695e2f Binary files /dev/null and b/examples/screenshots/misc_exporter_ktx2.jpg differ diff --git a/examples/screenshots/misc_exporter_obj.jpg b/examples/screenshots/misc_exporter_obj.jpg index fa5b5d155c6ffa..85cbeac2910abb 100644 Binary files a/examples/screenshots/misc_exporter_obj.jpg and b/examples/screenshots/misc_exporter_obj.jpg differ diff --git a/examples/screenshots/misc_exporter_ply.jpg b/examples/screenshots/misc_exporter_ply.jpg index 97d36c1bafa192..4aeb79e5f76ac7 100644 Binary files a/examples/screenshots/misc_exporter_ply.jpg and b/examples/screenshots/misc_exporter_ply.jpg differ diff --git a/examples/screenshots/misc_exporter_stl.jpg b/examples/screenshots/misc_exporter_stl.jpg index d1e88cc10cf1b1..b88f5778d37611 100644 Binary files a/examples/screenshots/misc_exporter_stl.jpg and b/examples/screenshots/misc_exporter_stl.jpg differ diff --git a/examples/screenshots/misc_exporter_usdz.jpg b/examples/screenshots/misc_exporter_usdz.jpg index 051023b4724cb9..b0a2f3570fc1ac 100644 Binary files a/examples/screenshots/misc_exporter_usdz.jpg and b/examples/screenshots/misc_exporter_usdz.jpg differ diff --git a/examples/screenshots/misc_lookat.jpg b/examples/screenshots/misc_lookat.jpg deleted file mode 100644 index 32816799174def..00000000000000 Binary files a/examples/screenshots/misc_lookat.jpg and /dev/null differ diff --git a/examples/screenshots/misc_raycaster_helper.jpg b/examples/screenshots/misc_raycaster_helper.jpg new file mode 100644 index 00000000000000..8e2287e142ebc1 Binary files /dev/null and b/examples/screenshots/misc_raycaster_helper.jpg differ diff --git a/examples/screenshots/physics_ammo_break.jpg b/examples/screenshots/physics_ammo_break.jpg index dcc56b47adb52c..659d1cc3a05dd1 100644 Binary files a/examples/screenshots/physics_ammo_break.jpg and b/examples/screenshots/physics_ammo_break.jpg differ diff --git a/examples/screenshots/physics_ammo_cloth.jpg b/examples/screenshots/physics_ammo_cloth.jpg index e13dbfb56957d6..489a829985d7f7 100644 Binary files a/examples/screenshots/physics_ammo_cloth.jpg and b/examples/screenshots/physics_ammo_cloth.jpg differ diff --git a/examples/screenshots/physics_ammo_instancing.jpg b/examples/screenshots/physics_ammo_instancing.jpg index 6e368828aba9f8..f3f59456f3ad71 100644 Binary files a/examples/screenshots/physics_ammo_instancing.jpg and b/examples/screenshots/physics_ammo_instancing.jpg differ diff --git a/examples/screenshots/physics_ammo_rope.jpg b/examples/screenshots/physics_ammo_rope.jpg index 8d90337d76ea1b..131bf4f07f9978 100644 Binary files a/examples/screenshots/physics_ammo_rope.jpg and b/examples/screenshots/physics_ammo_rope.jpg differ diff --git a/examples/screenshots/physics_ammo_terrain.jpg b/examples/screenshots/physics_ammo_terrain.jpg index a7c6855d494329..fabc28947dc869 100644 Binary files a/examples/screenshots/physics_ammo_terrain.jpg and b/examples/screenshots/physics_ammo_terrain.jpg differ diff --git a/examples/screenshots/physics_ammo_volume.jpg b/examples/screenshots/physics_ammo_volume.jpg index f86ce537f19e27..da58f8ca99d406 100644 Binary files a/examples/screenshots/physics_ammo_volume.jpg and b/examples/screenshots/physics_ammo_volume.jpg differ diff --git a/examples/screenshots/physics_jolt_instancing.jpg b/examples/screenshots/physics_jolt_instancing.jpg new file mode 100644 index 00000000000000..46191c9c3eb50a Binary files /dev/null and b/examples/screenshots/physics_jolt_instancing.jpg differ diff --git a/examples/screenshots/physics_rapier_basic.jpg b/examples/screenshots/physics_rapier_basic.jpg new file mode 100644 index 00000000000000..3f8dbc03664276 Binary files /dev/null and b/examples/screenshots/physics_rapier_basic.jpg differ diff --git a/examples/screenshots/physics_rapier_character_controller.jpg b/examples/screenshots/physics_rapier_character_controller.jpg new file mode 100644 index 00000000000000..4e5edbc50d5625 Binary files /dev/null and b/examples/screenshots/physics_rapier_character_controller.jpg differ diff --git a/examples/screenshots/physics_rapier_instancing.jpg b/examples/screenshots/physics_rapier_instancing.jpg index 9e069e6decfc9a..65bfd6837166cc 100644 Binary files a/examples/screenshots/physics_rapier_instancing.jpg and b/examples/screenshots/physics_rapier_instancing.jpg differ diff --git a/examples/screenshots/physics_rapier_joints.jpg b/examples/screenshots/physics_rapier_joints.jpg new file mode 100644 index 00000000000000..f042d935bb5de5 Binary files /dev/null and b/examples/screenshots/physics_rapier_joints.jpg differ diff --git a/examples/screenshots/physics_rapier_terrain.jpg b/examples/screenshots/physics_rapier_terrain.jpg new file mode 100644 index 00000000000000..fabc28947dc869 Binary files /dev/null and b/examples/screenshots/physics_rapier_terrain.jpg differ diff --git a/examples/screenshots/physics_rapier_vehicle_controller.jpg b/examples/screenshots/physics_rapier_vehicle_controller.jpg new file mode 100644 index 00000000000000..992369183cdd10 Binary files /dev/null and b/examples/screenshots/physics_rapier_vehicle_controller.jpg differ diff --git a/examples/screenshots/webaudio_orientation.jpg b/examples/screenshots/webaudio_orientation.jpg index 5c2f7ea53b382b..9e71e1e23629ca 100644 Binary files a/examples/screenshots/webaudio_orientation.jpg and b/examples/screenshots/webaudio_orientation.jpg differ diff --git a/examples/screenshots/webaudio_sandbox.jpg b/examples/screenshots/webaudio_sandbox.jpg index 6a7685d6200fa1..ecd2cf803f6152 100644 Binary files a/examples/screenshots/webaudio_sandbox.jpg and b/examples/screenshots/webaudio_sandbox.jpg differ diff --git a/examples/screenshots/webaudio_timing.jpg b/examples/screenshots/webaudio_timing.jpg index 07c3e422f7032b..ebcfc100851c57 100644 Binary files a/examples/screenshots/webaudio_timing.jpg and b/examples/screenshots/webaudio_timing.jpg differ diff --git a/examples/screenshots/webgl2_multiple_rendertargets.jpg b/examples/screenshots/webgl2_multiple_rendertargets.jpg deleted file mode 100644 index e48ce6b7d0a05b..00000000000000 Binary files a/examples/screenshots/webgl2_multiple_rendertargets.jpg and /dev/null differ diff --git a/examples/screenshots/webgl2_multisampled_renderbuffers.jpg b/examples/screenshots/webgl2_multisampled_renderbuffers.jpg deleted file mode 100644 index 9d954517f3afe2..00000000000000 Binary files a/examples/screenshots/webgl2_multisampled_renderbuffers.jpg and /dev/null differ diff --git a/examples/screenshots/webgl2_rendertarget_texture2darray.jpg b/examples/screenshots/webgl2_rendertarget_texture2darray.jpg deleted file mode 100644 index 199763aacc08fc..00000000000000 Binary files a/examples/screenshots/webgl2_rendertarget_texture2darray.jpg and /dev/null differ diff --git a/examples/screenshots/webgl2_ubo.jpg b/examples/screenshots/webgl2_ubo.jpg deleted file mode 100644 index e20c784c68d7f5..00000000000000 Binary files a/examples/screenshots/webgl2_ubo.jpg and /dev/null differ diff --git a/examples/screenshots/webgl2_volume_instancing.jpg b/examples/screenshots/webgl2_volume_instancing.jpg deleted file mode 100644 index 7e01d7da8ecaf6..00000000000000 Binary files a/examples/screenshots/webgl2_volume_instancing.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_animation_keyframes.jpg b/examples/screenshots/webgl_animation_keyframes.jpg index 8eff9abeb5b023..5926436f0d3e98 100644 Binary files a/examples/screenshots/webgl_animation_keyframes.jpg and b/examples/screenshots/webgl_animation_keyframes.jpg differ diff --git a/examples/screenshots/webgl_animation_multiple.jpg b/examples/screenshots/webgl_animation_multiple.jpg index 9b7817ef7e5c7a..4991730262da1b 100644 Binary files a/examples/screenshots/webgl_animation_multiple.jpg and b/examples/screenshots/webgl_animation_multiple.jpg differ diff --git a/examples/screenshots/webgl_animation_skinning_additive_blending.jpg b/examples/screenshots/webgl_animation_skinning_additive_blending.jpg index 18837c942432ef..bf61a83ff8ee7b 100644 Binary files a/examples/screenshots/webgl_animation_skinning_additive_blending.jpg and b/examples/screenshots/webgl_animation_skinning_additive_blending.jpg differ diff --git a/examples/screenshots/webgl_animation_skinning_blending.jpg b/examples/screenshots/webgl_animation_skinning_blending.jpg index c4110ee405feb8..271358537b7fa6 100644 Binary files a/examples/screenshots/webgl_animation_skinning_blending.jpg and b/examples/screenshots/webgl_animation_skinning_blending.jpg differ diff --git a/examples/screenshots/webgl_animation_skinning_ik.jpg b/examples/screenshots/webgl_animation_skinning_ik.jpg index 32a72a34c120ad..ce9896204816c4 100644 Binary files a/examples/screenshots/webgl_animation_skinning_ik.jpg and b/examples/screenshots/webgl_animation_skinning_ik.jpg differ diff --git a/examples/screenshots/webgl_animation_skinning_morph.jpg b/examples/screenshots/webgl_animation_skinning_morph.jpg index f77cafb633883f..9c51699a75efff 100644 Binary files a/examples/screenshots/webgl_animation_skinning_morph.jpg and b/examples/screenshots/webgl_animation_skinning_morph.jpg differ diff --git a/examples/screenshots/webgl_animation_walk.jpg b/examples/screenshots/webgl_animation_walk.jpg new file mode 100644 index 00000000000000..f490610235d4c1 Binary files /dev/null and b/examples/screenshots/webgl_animation_walk.jpg differ diff --git a/examples/screenshots/webgl_batch_lod_bvh.jpg b/examples/screenshots/webgl_batch_lod_bvh.jpg new file mode 100644 index 00000000000000..34f3a662a61a16 Binary files /dev/null and b/examples/screenshots/webgl_batch_lod_bvh.jpg differ diff --git a/examples/screenshots/webgl_buffergeometry.jpg b/examples/screenshots/webgl_buffergeometry.jpg index c4fa982933f232..1c065d774bef22 100644 Binary files a/examples/screenshots/webgl_buffergeometry.jpg and b/examples/screenshots/webgl_buffergeometry.jpg differ diff --git a/examples/screenshots/webgl2_buffergeometry_attributes_integer.jpg b/examples/screenshots/webgl_buffergeometry_attributes_integer.jpg similarity index 100% rename from examples/screenshots/webgl2_buffergeometry_attributes_integer.jpg rename to examples/screenshots/webgl_buffergeometry_attributes_integer.jpg diff --git a/examples/screenshots/webgl2_buffergeometry_attributes_none.jpg b/examples/screenshots/webgl_buffergeometry_attributes_none.jpg similarity index 100% rename from examples/screenshots/webgl2_buffergeometry_attributes_none.jpg rename to examples/screenshots/webgl_buffergeometry_attributes_none.jpg diff --git a/examples/screenshots/webgl_buffergeometry_compression.jpg b/examples/screenshots/webgl_buffergeometry_compression.jpg deleted file mode 100644 index a122d6c2a01e90..00000000000000 Binary files a/examples/screenshots/webgl_buffergeometry_compression.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_buffergeometry_drawrange.jpg b/examples/screenshots/webgl_buffergeometry_drawrange.jpg index 035b2323edd705..5501c17cad5a68 100644 Binary files a/examples/screenshots/webgl_buffergeometry_drawrange.jpg and b/examples/screenshots/webgl_buffergeometry_drawrange.jpg differ diff --git a/examples/screenshots/webgl_buffergeometry_glbufferattribute.jpg b/examples/screenshots/webgl_buffergeometry_glbufferattribute.jpg index 36bfe1c71d9d01..2da4abad64e45c 100644 Binary files a/examples/screenshots/webgl_buffergeometry_glbufferattribute.jpg and b/examples/screenshots/webgl_buffergeometry_glbufferattribute.jpg differ diff --git a/examples/screenshots/webgl_buffergeometry_indexed.jpg b/examples/screenshots/webgl_buffergeometry_indexed.jpg index f61cd2b0ee0e96..969754f8280d52 100644 Binary files a/examples/screenshots/webgl_buffergeometry_indexed.jpg and b/examples/screenshots/webgl_buffergeometry_indexed.jpg differ diff --git a/examples/screenshots/webgl_buffergeometry_lines.jpg b/examples/screenshots/webgl_buffergeometry_lines.jpg index 7c1dc0479cb443..d0a4264dc8f2ab 100644 Binary files a/examples/screenshots/webgl_buffergeometry_lines.jpg and b/examples/screenshots/webgl_buffergeometry_lines.jpg differ diff --git a/examples/screenshots/webgl_buffergeometry_uint.jpg b/examples/screenshots/webgl_buffergeometry_uint.jpg index b6fef7bf55db8b..e5f185c0a4a572 100644 Binary files a/examples/screenshots/webgl_buffergeometry_uint.jpg and b/examples/screenshots/webgl_buffergeometry_uint.jpg differ diff --git a/examples/screenshots/webgl_camera.jpg b/examples/screenshots/webgl_camera.jpg index 2a027a813abe11..997f861bba1d1b 100644 Binary files a/examples/screenshots/webgl_camera.jpg and b/examples/screenshots/webgl_camera.jpg differ diff --git a/examples/screenshots/webgl_camera_array.jpg b/examples/screenshots/webgl_camera_array.jpg index 7aead83bb33764..65348f03585a7c 100644 Binary files a/examples/screenshots/webgl_camera_array.jpg and b/examples/screenshots/webgl_camera_array.jpg differ diff --git a/examples/screenshots/webgl_camera_cinematic.jpg b/examples/screenshots/webgl_camera_cinematic.jpg deleted file mode 100644 index cbde1e61e95571..00000000000000 Binary files a/examples/screenshots/webgl_camera_cinematic.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_camera_logarithmicdepthbuffer.jpg b/examples/screenshots/webgl_camera_logarithmicdepthbuffer.jpg index 7305889829a892..de12ead1e15e15 100644 Binary files a/examples/screenshots/webgl_camera_logarithmicdepthbuffer.jpg and b/examples/screenshots/webgl_camera_logarithmicdepthbuffer.jpg differ diff --git a/examples/screenshots/webgl_clipculldistance.jpg b/examples/screenshots/webgl_clipculldistance.jpg new file mode 100644 index 00000000000000..530de694627e82 Binary files /dev/null and b/examples/screenshots/webgl_clipculldistance.jpg differ diff --git a/examples/screenshots/webgl_clipping.jpg b/examples/screenshots/webgl_clipping.jpg index 3847fe90756f97..93517fb0135b9e 100644 Binary files a/examples/screenshots/webgl_clipping.jpg and b/examples/screenshots/webgl_clipping.jpg differ diff --git a/examples/screenshots/webgl_clipping_advanced.jpg b/examples/screenshots/webgl_clipping_advanced.jpg index 7e936e62dfaa05..c2aace5cbddb07 100644 Binary files a/examples/screenshots/webgl_clipping_advanced.jpg and b/examples/screenshots/webgl_clipping_advanced.jpg differ diff --git a/examples/screenshots/webgl_clipping_intersection.jpg b/examples/screenshots/webgl_clipping_intersection.jpg index d3f1394275f640..f4bff68720b93a 100644 Binary files a/examples/screenshots/webgl_clipping_intersection.jpg and b/examples/screenshots/webgl_clipping_intersection.jpg differ diff --git a/examples/screenshots/webgl_clipping_stencil.jpg b/examples/screenshots/webgl_clipping_stencil.jpg index e5ffe04f278a69..c8025e130e1bff 100644 Binary files a/examples/screenshots/webgl_clipping_stencil.jpg and b/examples/screenshots/webgl_clipping_stencil.jpg differ diff --git a/examples/screenshots/webgl_custom_attributes_lines.jpg b/examples/screenshots/webgl_custom_attributes_lines.jpg index 00f8067a3b47ac..6ec0ba7c9ded18 100644 Binary files a/examples/screenshots/webgl_custom_attributes_lines.jpg and b/examples/screenshots/webgl_custom_attributes_lines.jpg differ diff --git a/examples/screenshots/webgl_decals.jpg b/examples/screenshots/webgl_decals.jpg index 97130e48c4dfa4..cce4cce8edffd2 100644 Binary files a/examples/screenshots/webgl_decals.jpg and b/examples/screenshots/webgl_decals.jpg differ diff --git a/examples/screenshots/webgl_effects_anaglyph.jpg b/examples/screenshots/webgl_effects_anaglyph.jpg index e464fe3c81582b..2e19cc493b9329 100644 Binary files a/examples/screenshots/webgl_effects_anaglyph.jpg and b/examples/screenshots/webgl_effects_anaglyph.jpg differ diff --git a/examples/screenshots/webgl_effects_ascii.jpg b/examples/screenshots/webgl_effects_ascii.jpg index 4d3d85d87fd975..b76d90a5cdf440 100644 Binary files a/examples/screenshots/webgl_effects_ascii.jpg and b/examples/screenshots/webgl_effects_ascii.jpg differ diff --git a/examples/screenshots/webgl_effects_parallaxbarrier.jpg b/examples/screenshots/webgl_effects_parallaxbarrier.jpg index 1a4f15d9025220..e54d4fb552dc83 100644 Binary files a/examples/screenshots/webgl_effects_parallaxbarrier.jpg and b/examples/screenshots/webgl_effects_parallaxbarrier.jpg differ diff --git a/examples/screenshots/webgl_effects_peppersghost.jpg b/examples/screenshots/webgl_effects_peppersghost.jpg deleted file mode 100644 index eb2aff287116dc..00000000000000 Binary files a/examples/screenshots/webgl_effects_peppersghost.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_furnace_test.jpg b/examples/screenshots/webgl_furnace_test.jpg index b34fa1edbb5f72..f97207e3bfeb7f 100644 Binary files a/examples/screenshots/webgl_furnace_test.jpg and b/examples/screenshots/webgl_furnace_test.jpg differ diff --git a/examples/screenshots/webgl_geometries.jpg b/examples/screenshots/webgl_geometries.jpg index bcd7bd6ab5f8b0..434a87edb08432 100644 Binary files a/examples/screenshots/webgl_geometries.jpg and b/examples/screenshots/webgl_geometries.jpg differ diff --git a/examples/screenshots/webgl_geometries_parametric.jpg b/examples/screenshots/webgl_geometries_parametric.jpg deleted file mode 100644 index ded35c85ba8dfc..00000000000000 Binary files a/examples/screenshots/webgl_geometries_parametric.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_geometry_colors.jpg b/examples/screenshots/webgl_geometry_colors.jpg index c4170bd6974ef9..7ba717cde7833c 100644 Binary files a/examples/screenshots/webgl_geometry_colors.jpg and b/examples/screenshots/webgl_geometry_colors.jpg differ diff --git a/examples/screenshots/webgl_geometry_colors_lookuptable.jpg b/examples/screenshots/webgl_geometry_colors_lookuptable.jpg index 6d201144f390b7..99742e1461553b 100644 Binary files a/examples/screenshots/webgl_geometry_colors_lookuptable.jpg and b/examples/screenshots/webgl_geometry_colors_lookuptable.jpg differ diff --git a/examples/screenshots/webgl_geometry_convex.jpg b/examples/screenshots/webgl_geometry_convex.jpg index f41a6ec4d50701..d531e40524bd01 100644 Binary files a/examples/screenshots/webgl_geometry_convex.jpg and b/examples/screenshots/webgl_geometry_convex.jpg differ diff --git a/examples/screenshots/webgl_geometry_csg.jpg b/examples/screenshots/webgl_geometry_csg.jpg index d776bbc9c76d62..94b050759a71d9 100644 Binary files a/examples/screenshots/webgl_geometry_csg.jpg and b/examples/screenshots/webgl_geometry_csg.jpg differ diff --git a/examples/screenshots/webgl_geometry_cube.jpg b/examples/screenshots/webgl_geometry_cube.jpg index 6a7e435bece86d..9e5aac16b2cfef 100644 Binary files a/examples/screenshots/webgl_geometry_cube.jpg and b/examples/screenshots/webgl_geometry_cube.jpg differ diff --git a/examples/screenshots/webgl_geometry_dynamic.jpg b/examples/screenshots/webgl_geometry_dynamic.jpg deleted file mode 100644 index b4b29381bac0d8..00000000000000 Binary files a/examples/screenshots/webgl_geometry_dynamic.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_geometry_extrude_shapes.jpg b/examples/screenshots/webgl_geometry_extrude_shapes.jpg index ee9667b72aca54..cecd95568403ec 100644 Binary files a/examples/screenshots/webgl_geometry_extrude_shapes.jpg and b/examples/screenshots/webgl_geometry_extrude_shapes.jpg differ diff --git a/examples/screenshots/webgl_geometry_extrude_shapes2.jpg b/examples/screenshots/webgl_geometry_extrude_shapes2.jpg deleted file mode 100644 index 795d094b37e45e..00000000000000 Binary files a/examples/screenshots/webgl_geometry_extrude_shapes2.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_geometry_extrude_splines.jpg b/examples/screenshots/webgl_geometry_extrude_splines.jpg index ef3f9714331f55..553ffa1e642d10 100644 Binary files a/examples/screenshots/webgl_geometry_extrude_splines.jpg and b/examples/screenshots/webgl_geometry_extrude_splines.jpg differ diff --git a/examples/screenshots/webgl_geometry_minecraft.jpg b/examples/screenshots/webgl_geometry_minecraft.jpg index c4bb99daa2a630..6682e5052df0a7 100644 Binary files a/examples/screenshots/webgl_geometry_minecraft.jpg and b/examples/screenshots/webgl_geometry_minecraft.jpg differ diff --git a/examples/screenshots/webgl_geometry_nurbs.jpg b/examples/screenshots/webgl_geometry_nurbs.jpg index 3adfcaf5fc329b..aa0d815e888095 100644 Binary files a/examples/screenshots/webgl_geometry_nurbs.jpg and b/examples/screenshots/webgl_geometry_nurbs.jpg differ diff --git a/examples/screenshots/webgl_geometry_shapes.jpg b/examples/screenshots/webgl_geometry_shapes.jpg index d5957124c9ca98..b87c84885aaad1 100644 Binary files a/examples/screenshots/webgl_geometry_shapes.jpg and b/examples/screenshots/webgl_geometry_shapes.jpg differ diff --git a/examples/screenshots/webgl_geometry_spline_editor.jpg b/examples/screenshots/webgl_geometry_spline_editor.jpg index 8bb127a6943633..2b105ae54ae075 100644 Binary files a/examples/screenshots/webgl_geometry_spline_editor.jpg and b/examples/screenshots/webgl_geometry_spline_editor.jpg differ diff --git a/examples/screenshots/webgl_geometry_teapot.jpg b/examples/screenshots/webgl_geometry_teapot.jpg index c28cfaef19a6a6..ec05d359f447f3 100644 Binary files a/examples/screenshots/webgl_geometry_teapot.jpg and b/examples/screenshots/webgl_geometry_teapot.jpg differ diff --git a/examples/screenshots/webgl_geometry_text.jpg b/examples/screenshots/webgl_geometry_text.jpg index a8ac08c2b41bcb..b9e0f4154e1ac0 100644 Binary files a/examples/screenshots/webgl_geometry_text.jpg and b/examples/screenshots/webgl_geometry_text.jpg differ diff --git a/examples/screenshots/webgl_geometry_text_shapes.jpg b/examples/screenshots/webgl_geometry_text_shapes.jpg index 9aabc44692515f..9f95fc20b69ebf 100644 Binary files a/examples/screenshots/webgl_geometry_text_shapes.jpg and b/examples/screenshots/webgl_geometry_text_shapes.jpg differ diff --git a/examples/screenshots/webgl_geometry_text_stroke.jpg b/examples/screenshots/webgl_geometry_text_stroke.jpg index 3c67cfe23f9d8f..88c7d17a8cd8c2 100644 Binary files a/examples/screenshots/webgl_geometry_text_stroke.jpg and b/examples/screenshots/webgl_geometry_text_stroke.jpg differ diff --git a/examples/screenshots/webgl_gpgpu_birds.jpg b/examples/screenshots/webgl_gpgpu_birds.jpg index e6763ff0613c49..da7337c40317ee 100644 Binary files a/examples/screenshots/webgl_gpgpu_birds.jpg and b/examples/screenshots/webgl_gpgpu_birds.jpg differ diff --git a/examples/screenshots/webgl_gpgpu_birds_gltf.jpg b/examples/screenshots/webgl_gpgpu_birds_gltf.jpg index 06d9290693d62d..a5a87fbb38707f 100644 Binary files a/examples/screenshots/webgl_gpgpu_birds_gltf.jpg and b/examples/screenshots/webgl_gpgpu_birds_gltf.jpg differ diff --git a/examples/screenshots/webgl_gpgpu_protoplanet.jpg b/examples/screenshots/webgl_gpgpu_protoplanet.jpg index ae0cb7e5e0ced5..38da4616bacc58 100644 Binary files a/examples/screenshots/webgl_gpgpu_protoplanet.jpg and b/examples/screenshots/webgl_gpgpu_protoplanet.jpg differ diff --git a/examples/screenshots/webgl_gpgpu_water.jpg b/examples/screenshots/webgl_gpgpu_water.jpg index c5d572d841122d..2f5bcdae02cb18 100644 Binary files a/examples/screenshots/webgl_gpgpu_water.jpg and b/examples/screenshots/webgl_gpgpu_water.jpg differ diff --git a/examples/screenshots/webgl_helpers.jpg b/examples/screenshots/webgl_helpers.jpg index c8ae3a6205e27d..bf82bca86d75ef 100644 Binary files a/examples/screenshots/webgl_helpers.jpg and b/examples/screenshots/webgl_helpers.jpg differ diff --git a/examples/screenshots/webgl_instancing_dynamic.jpg b/examples/screenshots/webgl_instancing_dynamic.jpg index fa22199c35b27a..f241a79d2a5830 100644 Binary files a/examples/screenshots/webgl_instancing_dynamic.jpg and b/examples/screenshots/webgl_instancing_dynamic.jpg differ diff --git a/examples/screenshots/webgl_instancing_morph.jpg b/examples/screenshots/webgl_instancing_morph.jpg new file mode 100644 index 00000000000000..0ef49f23cdde1d Binary files /dev/null and b/examples/screenshots/webgl_instancing_morph.jpg differ diff --git a/examples/screenshots/webgl_instancing_performance.jpg b/examples/screenshots/webgl_instancing_performance.jpg index aa8c187ab39299..407fe10e628f97 100644 Binary files a/examples/screenshots/webgl_instancing_performance.jpg and b/examples/screenshots/webgl_instancing_performance.jpg differ diff --git a/examples/screenshots/webgl_instancing_raycast.jpg b/examples/screenshots/webgl_instancing_raycast.jpg index 807d49d51f285e..f9b99a030f338e 100644 Binary files a/examples/screenshots/webgl_instancing_raycast.jpg and b/examples/screenshots/webgl_instancing_raycast.jpg differ diff --git a/examples/screenshots/webgl_instancing_scatter.jpg b/examples/screenshots/webgl_instancing_scatter.jpg index 6b635e76c4a30c..1572e8cb525c2a 100644 Binary files a/examples/screenshots/webgl_instancing_scatter.jpg and b/examples/screenshots/webgl_instancing_scatter.jpg differ diff --git a/examples/screenshots/webgl_interactive_buffergeometry.jpg b/examples/screenshots/webgl_interactive_buffergeometry.jpg index c1789cd2167f97..f154ddd4a792a4 100644 Binary files a/examples/screenshots/webgl_interactive_buffergeometry.jpg and b/examples/screenshots/webgl_interactive_buffergeometry.jpg differ diff --git a/examples/screenshots/webgl_interactive_cubes.jpg b/examples/screenshots/webgl_interactive_cubes.jpg index 491a1a85b7a952..652fe7fc7c0c7b 100644 Binary files a/examples/screenshots/webgl_interactive_cubes.jpg and b/examples/screenshots/webgl_interactive_cubes.jpg differ diff --git a/examples/screenshots/webgl_interactive_cubes_gpu.jpg b/examples/screenshots/webgl_interactive_cubes_gpu.jpg index 957d1a234640bb..da3d39b0d6ba2e 100644 Binary files a/examples/screenshots/webgl_interactive_cubes_gpu.jpg and b/examples/screenshots/webgl_interactive_cubes_gpu.jpg differ diff --git a/examples/screenshots/webgl_interactive_cubes_ortho.jpg b/examples/screenshots/webgl_interactive_cubes_ortho.jpg index eda5b48b12146f..e1b2b58cfe95de 100644 Binary files a/examples/screenshots/webgl_interactive_cubes_ortho.jpg and b/examples/screenshots/webgl_interactive_cubes_ortho.jpg differ diff --git a/examples/screenshots/webgl_interactive_lines.jpg b/examples/screenshots/webgl_interactive_lines.jpg index fe8052c022f454..d55e8442bb69f1 100644 Binary files a/examples/screenshots/webgl_interactive_lines.jpg and b/examples/screenshots/webgl_interactive_lines.jpg differ diff --git a/examples/screenshots/webgl_interactive_points.jpg b/examples/screenshots/webgl_interactive_points.jpg index 26dcb30e58f116..8eb9bc90975c50 100644 Binary files a/examples/screenshots/webgl_interactive_points.jpg and b/examples/screenshots/webgl_interactive_points.jpg differ diff --git a/examples/screenshots/webgl_interactive_raycasting_points.jpg b/examples/screenshots/webgl_interactive_raycasting_points.jpg index 7eb9be59aa1004..3b639240f91e17 100644 Binary files a/examples/screenshots/webgl_interactive_raycasting_points.jpg and b/examples/screenshots/webgl_interactive_raycasting_points.jpg differ diff --git a/examples/screenshots/webgl_layers.jpg b/examples/screenshots/webgl_layers.jpg deleted file mode 100644 index f459aa553a8d91..00000000000000 Binary files a/examples/screenshots/webgl_layers.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_lensflares.jpg b/examples/screenshots/webgl_lensflares.jpg index ee3ffb2fd7142d..ab14300b09b971 100644 Binary files a/examples/screenshots/webgl_lensflares.jpg and b/examples/screenshots/webgl_lensflares.jpg differ diff --git a/examples/screenshots/webgl_lightningstrike.jpg b/examples/screenshots/webgl_lightningstrike.jpg deleted file mode 100644 index f35e3b6a339493..00000000000000 Binary files a/examples/screenshots/webgl_lightningstrike.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_lightprobe.jpg b/examples/screenshots/webgl_lightprobe.jpg index b74517ab08c8ca..0e7f57cb484eb0 100644 Binary files a/examples/screenshots/webgl_lightprobe.jpg and b/examples/screenshots/webgl_lightprobe.jpg differ diff --git a/examples/screenshots/webgl_lights_hemisphere.jpg b/examples/screenshots/webgl_lights_hemisphere.jpg index 0dc8c6c1f70f1c..8501f6f8cf1285 100644 Binary files a/examples/screenshots/webgl_lights_hemisphere.jpg and b/examples/screenshots/webgl_lights_hemisphere.jpg differ diff --git a/examples/screenshots/webgl_lights_pointlights.jpg b/examples/screenshots/webgl_lights_pointlights.jpg deleted file mode 100644 index 3fba766d83fd1a..00000000000000 Binary files a/examples/screenshots/webgl_lights_pointlights.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_lights_spotlight.jpg b/examples/screenshots/webgl_lights_spotlight.jpg index 742aae542c9b34..2d4bd282290add 100644 Binary files a/examples/screenshots/webgl_lights_spotlight.jpg and b/examples/screenshots/webgl_lights_spotlight.jpg differ diff --git a/examples/screenshots/webgl_lights_spotlights.jpg b/examples/screenshots/webgl_lights_spotlights.jpg index d4bffa0c556ea4..afc2e147ef65b9 100644 Binary files a/examples/screenshots/webgl_lights_spotlights.jpg and b/examples/screenshots/webgl_lights_spotlights.jpg differ diff --git a/examples/screenshots/webgl_lines_colors.jpg b/examples/screenshots/webgl_lines_colors.jpg index b599a23594daae..570016d503e59e 100644 Binary files a/examples/screenshots/webgl_lines_colors.jpg and b/examples/screenshots/webgl_lines_colors.jpg differ diff --git a/examples/screenshots/webgl_lines_fat_raycasting.jpg b/examples/screenshots/webgl_lines_fat_raycasting.jpg index a0b91e853c8e84..b5d9ea7a7dc1bf 100644 Binary files a/examples/screenshots/webgl_lines_fat_raycasting.jpg and b/examples/screenshots/webgl_lines_fat_raycasting.jpg differ diff --git a/examples/screenshots/webgl_lines_sphere.jpg b/examples/screenshots/webgl_lines_sphere.jpg deleted file mode 100644 index fd34c7ae79b8cb..00000000000000 Binary files a/examples/screenshots/webgl_lines_sphere.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_loader_3dm.jpg b/examples/screenshots/webgl_loader_3dm.jpg index b6e9c5199ef889..a8e78425c0b20b 100644 Binary files a/examples/screenshots/webgl_loader_3dm.jpg and b/examples/screenshots/webgl_loader_3dm.jpg differ diff --git a/examples/screenshots/webgl_loader_3ds.jpg b/examples/screenshots/webgl_loader_3ds.jpg index 38d76d2fdabe27..3992573bcd49aa 100644 Binary files a/examples/screenshots/webgl_loader_3ds.jpg and b/examples/screenshots/webgl_loader_3ds.jpg differ diff --git a/examples/screenshots/webgl_loader_3dtiles.jpg b/examples/screenshots/webgl_loader_3dtiles.jpg new file mode 100644 index 00000000000000..98d83e0a0d0dd6 Binary files /dev/null and b/examples/screenshots/webgl_loader_3dtiles.jpg differ diff --git a/examples/screenshots/webgl_loader_3mf.jpg b/examples/screenshots/webgl_loader_3mf.jpg index a2c614fc2170b1..9d761fc6e456c7 100644 Binary files a/examples/screenshots/webgl_loader_3mf.jpg and b/examples/screenshots/webgl_loader_3mf.jpg differ diff --git a/examples/screenshots/webgl_loader_3mf_materials.jpg b/examples/screenshots/webgl_loader_3mf_materials.jpg index 929fb09e60e929..7d70224327791f 100644 Binary files a/examples/screenshots/webgl_loader_3mf_materials.jpg and b/examples/screenshots/webgl_loader_3mf_materials.jpg differ diff --git a/examples/screenshots/webgl_loader_amf.jpg b/examples/screenshots/webgl_loader_amf.jpg index e6293b62ecb48c..067bdceb44a968 100644 Binary files a/examples/screenshots/webgl_loader_amf.jpg and b/examples/screenshots/webgl_loader_amf.jpg differ diff --git a/examples/screenshots/webgl_loader_bvh.jpg b/examples/screenshots/webgl_loader_bvh.jpg index 761b52cfb9b158..73b7adde1d46f9 100644 Binary files a/examples/screenshots/webgl_loader_bvh.jpg and b/examples/screenshots/webgl_loader_bvh.jpg differ diff --git a/examples/screenshots/webgl_loader_collada.jpg b/examples/screenshots/webgl_loader_collada.jpg index e5cc49b5c5d446..7927ad025c07dd 100644 Binary files a/examples/screenshots/webgl_loader_collada.jpg and b/examples/screenshots/webgl_loader_collada.jpg differ diff --git a/examples/screenshots/webgl_loader_collada_kinematics.jpg b/examples/screenshots/webgl_loader_collada_kinematics.jpg index 8e3eed2f06c298..bc13602bb59a58 100644 Binary files a/examples/screenshots/webgl_loader_collada_kinematics.jpg and b/examples/screenshots/webgl_loader_collada_kinematics.jpg differ diff --git a/examples/screenshots/webgl_loader_collada_skinning.jpg b/examples/screenshots/webgl_loader_collada_skinning.jpg index 9372cf958cc4fc..f18150a22a72f1 100644 Binary files a/examples/screenshots/webgl_loader_collada_skinning.jpg and b/examples/screenshots/webgl_loader_collada_skinning.jpg differ diff --git a/examples/screenshots/webgl_loader_draco.jpg b/examples/screenshots/webgl_loader_draco.jpg index 6c1b366e4b61c1..efe2e7a8bf0177 100644 Binary files a/examples/screenshots/webgl_loader_draco.jpg and b/examples/screenshots/webgl_loader_draco.jpg differ diff --git a/examples/screenshots/webgl_loader_fbx.jpg b/examples/screenshots/webgl_loader_fbx.jpg index 9ee93d1c9e6100..f52ed677ee63b5 100644 Binary files a/examples/screenshots/webgl_loader_fbx.jpg and b/examples/screenshots/webgl_loader_fbx.jpg differ diff --git a/examples/screenshots/webgl_loader_gcode.jpg b/examples/screenshots/webgl_loader_gcode.jpg index 24898a611ec67a..ec10d1974e3e2b 100644 Binary files a/examples/screenshots/webgl_loader_gcode.jpg and b/examples/screenshots/webgl_loader_gcode.jpg differ diff --git a/examples/screenshots/webgl_loader_gltf.jpg b/examples/screenshots/webgl_loader_gltf.jpg index 3eedd841e2d8f1..a9d947e712f7eb 100644 Binary files a/examples/screenshots/webgl_loader_gltf.jpg and b/examples/screenshots/webgl_loader_gltf.jpg differ diff --git a/examples/screenshots/webgl_loader_gltf_anisotropy.jpg b/examples/screenshots/webgl_loader_gltf_anisotropy.jpg index 6e123fc9367fd8..1979a13e7ecd27 100644 Binary files a/examples/screenshots/webgl_loader_gltf_anisotropy.jpg and b/examples/screenshots/webgl_loader_gltf_anisotropy.jpg differ diff --git a/examples/screenshots/webgl_loader_gltf_compressed.jpg b/examples/screenshots/webgl_loader_gltf_compressed.jpg index 1bdb88518ca10c..a6ef9e61d33a63 100644 Binary files a/examples/screenshots/webgl_loader_gltf_compressed.jpg and b/examples/screenshots/webgl_loader_gltf_compressed.jpg differ diff --git a/examples/screenshots/webgl_loader_gltf_dispersion.jpg b/examples/screenshots/webgl_loader_gltf_dispersion.jpg new file mode 100644 index 00000000000000..17e9be67c49b1d Binary files /dev/null and b/examples/screenshots/webgl_loader_gltf_dispersion.jpg differ diff --git a/examples/screenshots/webgl_loader_gltf_instancing.jpg b/examples/screenshots/webgl_loader_gltf_instancing.jpg index 73b5b1e2885617..592e4cd1cde492 100644 Binary files a/examples/screenshots/webgl_loader_gltf_instancing.jpg and b/examples/screenshots/webgl_loader_gltf_instancing.jpg differ diff --git a/examples/screenshots/webgl_loader_gltf_iridescence.jpg b/examples/screenshots/webgl_loader_gltf_iridescence.jpg index 6c67327ccdcf2c..2718ca22dc421e 100644 Binary files a/examples/screenshots/webgl_loader_gltf_iridescence.jpg and b/examples/screenshots/webgl_loader_gltf_iridescence.jpg differ diff --git a/examples/screenshots/webgl_loader_gltf_lights.jpg b/examples/screenshots/webgl_loader_gltf_lights.jpg deleted file mode 100644 index cc8dcb990a5444..00000000000000 Binary files a/examples/screenshots/webgl_loader_gltf_lights.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_loader_gltf_sheen.jpg b/examples/screenshots/webgl_loader_gltf_sheen.jpg index b29fd99ccbd90d..5e1107494af4da 100644 Binary files a/examples/screenshots/webgl_loader_gltf_sheen.jpg and b/examples/screenshots/webgl_loader_gltf_sheen.jpg differ diff --git a/examples/screenshots/webgl_loader_gltf_transmission.jpg b/examples/screenshots/webgl_loader_gltf_transmission.jpg index fb2ab0e20f15e3..f6abcd04862ff1 100644 Binary files a/examples/screenshots/webgl_loader_gltf_transmission.jpg and b/examples/screenshots/webgl_loader_gltf_transmission.jpg differ diff --git a/examples/screenshots/webgl_loader_gltf_variants.jpg b/examples/screenshots/webgl_loader_gltf_variants.jpg index ab686fac268fbd..74b594e911c57d 100644 Binary files a/examples/screenshots/webgl_loader_gltf_variants.jpg and b/examples/screenshots/webgl_loader_gltf_variants.jpg differ diff --git a/examples/screenshots/webgl_loader_ifc.jpg b/examples/screenshots/webgl_loader_ifc.jpg index 7c0a4b70eb30d0..a85a94c63c5f53 100644 Binary files a/examples/screenshots/webgl_loader_ifc.jpg and b/examples/screenshots/webgl_loader_ifc.jpg differ diff --git a/examples/screenshots/webgl_loader_kmz.jpg b/examples/screenshots/webgl_loader_kmz.jpg index 03100fec44381a..c7981c79c67457 100644 Binary files a/examples/screenshots/webgl_loader_kmz.jpg and b/examples/screenshots/webgl_loader_kmz.jpg differ diff --git a/examples/screenshots/webgl_loader_ldraw.jpg b/examples/screenshots/webgl_loader_ldraw.jpg index 1ff32b99837bb3..d8a857a9dc661f 100644 Binary files a/examples/screenshots/webgl_loader_ldraw.jpg and b/examples/screenshots/webgl_loader_ldraw.jpg differ diff --git a/examples/screenshots/webgl_loader_lwo.jpg b/examples/screenshots/webgl_loader_lwo.jpg index 5f8de00fc4cb5e..1e80238a20df40 100644 Binary files a/examples/screenshots/webgl_loader_lwo.jpg and b/examples/screenshots/webgl_loader_lwo.jpg differ diff --git a/examples/screenshots/webgl_loader_md2.jpg b/examples/screenshots/webgl_loader_md2.jpg index 13d96b9010287f..10d5c10ff50a52 100644 Binary files a/examples/screenshots/webgl_loader_md2.jpg and b/examples/screenshots/webgl_loader_md2.jpg differ diff --git a/examples/screenshots/webgl_loader_md2_control.jpg b/examples/screenshots/webgl_loader_md2_control.jpg index 421edc0efde0bd..44fa5f48728c15 100644 Binary files a/examples/screenshots/webgl_loader_md2_control.jpg and b/examples/screenshots/webgl_loader_md2_control.jpg differ diff --git a/examples/screenshots/webgl_loader_mmd.jpg b/examples/screenshots/webgl_loader_mmd.jpg deleted file mode 100644 index 3f4ec79ce6ac8a..00000000000000 Binary files a/examples/screenshots/webgl_loader_mmd.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_loader_mmd_audio.jpg b/examples/screenshots/webgl_loader_mmd_audio.jpg deleted file mode 100644 index a7b6332d5dc77e..00000000000000 Binary files a/examples/screenshots/webgl_loader_mmd_audio.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_loader_mmd_pose.jpg b/examples/screenshots/webgl_loader_mmd_pose.jpg deleted file mode 100644 index 61e56047475ab7..00000000000000 Binary files a/examples/screenshots/webgl_loader_mmd_pose.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_loader_nrrd.jpg b/examples/screenshots/webgl_loader_nrrd.jpg index 120f5e1731513e..324dda8819e10d 100644 Binary files a/examples/screenshots/webgl_loader_nrrd.jpg and b/examples/screenshots/webgl_loader_nrrd.jpg differ diff --git a/examples/screenshots/webgl_loader_obj.jpg b/examples/screenshots/webgl_loader_obj.jpg index bea761d1bf1322..cde947b59a3303 100644 Binary files a/examples/screenshots/webgl_loader_obj.jpg and b/examples/screenshots/webgl_loader_obj.jpg differ diff --git a/examples/screenshots/webgl_loader_obj_mtl.jpg b/examples/screenshots/webgl_loader_obj_mtl.jpg deleted file mode 100644 index f7ee995951f709..00000000000000 Binary files a/examples/screenshots/webgl_loader_obj_mtl.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_loader_pdb.jpg b/examples/screenshots/webgl_loader_pdb.jpg index e228facb67c030..8c14e833e60ebe 100644 Binary files a/examples/screenshots/webgl_loader_pdb.jpg and b/examples/screenshots/webgl_loader_pdb.jpg differ diff --git a/examples/screenshots/webgl_loader_ply.jpg b/examples/screenshots/webgl_loader_ply.jpg index dd4c1b5b9abd0d..bb8ad7b4580868 100644 Binary files a/examples/screenshots/webgl_loader_ply.jpg and b/examples/screenshots/webgl_loader_ply.jpg differ diff --git a/examples/screenshots/webgl_loader_prwm.jpg b/examples/screenshots/webgl_loader_prwm.jpg deleted file mode 100644 index f519916bf781ce..00000000000000 Binary files a/examples/screenshots/webgl_loader_prwm.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_loader_stl.jpg b/examples/screenshots/webgl_loader_stl.jpg index 9d415462f7e1f7..ce7e327422739b 100644 Binary files a/examples/screenshots/webgl_loader_stl.jpg and b/examples/screenshots/webgl_loader_stl.jpg differ diff --git a/examples/screenshots/webgl_loader_texture_dds.jpg b/examples/screenshots/webgl_loader_texture_dds.jpg index 46a2903f6688cc..dbe9f99c7a60fc 100644 Binary files a/examples/screenshots/webgl_loader_texture_dds.jpg and b/examples/screenshots/webgl_loader_texture_dds.jpg differ diff --git a/examples/screenshots/webgl_loader_texture_ktx.jpg b/examples/screenshots/webgl_loader_texture_ktx.jpg index 1cda63f347d3c7..695f9f9bdad547 100644 Binary files a/examples/screenshots/webgl_loader_texture_ktx.jpg and b/examples/screenshots/webgl_loader_texture_ktx.jpg differ diff --git a/examples/screenshots/webgl_loader_texture_ktx2.jpg b/examples/screenshots/webgl_loader_texture_ktx2.jpg index 3568a8a84fa216..a66fc772ca8488 100644 Binary files a/examples/screenshots/webgl_loader_texture_ktx2.jpg and b/examples/screenshots/webgl_loader_texture_ktx2.jpg differ diff --git a/examples/screenshots/webgl_loader_texture_logluv.jpg b/examples/screenshots/webgl_loader_texture_logluv.jpg deleted file mode 100644 index 0b2c3cd2329db1..00000000000000 Binary files a/examples/screenshots/webgl_loader_texture_logluv.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_loader_texture_lottie.jpg b/examples/screenshots/webgl_loader_texture_lottie.jpg index 1b64e7cd3f6c28..5cdb0eaaffddb4 100644 Binary files a/examples/screenshots/webgl_loader_texture_lottie.jpg and b/examples/screenshots/webgl_loader_texture_lottie.jpg differ diff --git a/examples/screenshots/webgl_loader_texture_tga.jpg b/examples/screenshots/webgl_loader_texture_tga.jpg index 999de4e578389d..c4c6072c323da8 100644 Binary files a/examples/screenshots/webgl_loader_texture_tga.jpg and b/examples/screenshots/webgl_loader_texture_tga.jpg differ diff --git a/examples/screenshots/webgl_loader_texture_ultrahdr.jpg b/examples/screenshots/webgl_loader_texture_ultrahdr.jpg new file mode 100644 index 00000000000000..6cf07cd5ccc909 Binary files /dev/null and b/examples/screenshots/webgl_loader_texture_ultrahdr.jpg differ diff --git a/examples/screenshots/webgl_loader_tilt.jpg b/examples/screenshots/webgl_loader_tilt.jpg deleted file mode 100644 index f0a5c2be70a076..00000000000000 Binary files a/examples/screenshots/webgl_loader_tilt.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_loader_ttf.jpg b/examples/screenshots/webgl_loader_ttf.jpg index 73896ca3869d0e..b22008229a8865 100644 Binary files a/examples/screenshots/webgl_loader_ttf.jpg and b/examples/screenshots/webgl_loader_ttf.jpg differ diff --git a/examples/screenshots/webgl_loader_usdz.jpg b/examples/screenshots/webgl_loader_usdz.jpg index 963b164843c9ea..6fb1061f11afe6 100644 Binary files a/examples/screenshots/webgl_loader_usdz.jpg and b/examples/screenshots/webgl_loader_usdz.jpg differ diff --git a/examples/screenshots/webgl_loader_vox.jpg b/examples/screenshots/webgl_loader_vox.jpg index f0a50990214fbd..1fc113bf296895 100644 Binary files a/examples/screenshots/webgl_loader_vox.jpg and b/examples/screenshots/webgl_loader_vox.jpg differ diff --git a/examples/screenshots/webgl_loader_vrml.jpg b/examples/screenshots/webgl_loader_vrml.jpg index 4ad6380eef5979..f2721b29d4b266 100644 Binary files a/examples/screenshots/webgl_loader_vrml.jpg and b/examples/screenshots/webgl_loader_vrml.jpg differ diff --git a/examples/screenshots/webgl_loader_vtk.jpg b/examples/screenshots/webgl_loader_vtk.jpg index 4af0946c724220..37d08ab89bb2eb 100644 Binary files a/examples/screenshots/webgl_loader_vtk.jpg and b/examples/screenshots/webgl_loader_vtk.jpg differ diff --git a/examples/screenshots/webgl_loader_xyz.jpg b/examples/screenshots/webgl_loader_xyz.jpg index 1d15442d3f4593..898d2348d56519 100644 Binary files a/examples/screenshots/webgl_loader_xyz.jpg and b/examples/screenshots/webgl_loader_xyz.jpg differ diff --git a/examples/screenshots/webgl_lod.jpg b/examples/screenshots/webgl_lod.jpg index 2f641e9c3001bc..2de4d5b1a0e086 100644 Binary files a/examples/screenshots/webgl_lod.jpg and b/examples/screenshots/webgl_lod.jpg differ diff --git a/examples/screenshots/webgl_marchingcubes.jpg b/examples/screenshots/webgl_marchingcubes.jpg index 897d3ab899dbcc..7268ecdcb8d14c 100644 Binary files a/examples/screenshots/webgl_marchingcubes.jpg and b/examples/screenshots/webgl_marchingcubes.jpg differ diff --git a/examples/screenshots/webgl_materials.jpg b/examples/screenshots/webgl_materials.jpg deleted file mode 100644 index ca8e9ccd675c2f..00000000000000 Binary files a/examples/screenshots/webgl_materials.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_materials_alphahash.jpg b/examples/screenshots/webgl_materials_alphahash.jpg new file mode 100644 index 00000000000000..20ec390d075290 Binary files /dev/null and b/examples/screenshots/webgl_materials_alphahash.jpg differ diff --git a/examples/screenshots/webgl_materials_bumpmap.jpg b/examples/screenshots/webgl_materials_bumpmap.jpg index 959a53d9784997..bb2cf510547965 100644 Binary files a/examples/screenshots/webgl_materials_bumpmap.jpg and b/examples/screenshots/webgl_materials_bumpmap.jpg differ diff --git a/examples/screenshots/webgl_materials_car.jpg b/examples/screenshots/webgl_materials_car.jpg index 0c86ddab29353d..4620e42a9117ac 100644 Binary files a/examples/screenshots/webgl_materials_car.jpg and b/examples/screenshots/webgl_materials_car.jpg differ diff --git a/examples/screenshots/webgl_materials_channels.jpg b/examples/screenshots/webgl_materials_channels.jpg index 1c29c972f58664..0b8995fc7acf63 100644 Binary files a/examples/screenshots/webgl_materials_channels.jpg and b/examples/screenshots/webgl_materials_channels.jpg differ diff --git a/examples/screenshots/webgl_materials_cubemap.jpg b/examples/screenshots/webgl_materials_cubemap.jpg index 07c50a4f828026..d5696adb03f9da 100644 Binary files a/examples/screenshots/webgl_materials_cubemap.jpg and b/examples/screenshots/webgl_materials_cubemap.jpg differ diff --git a/examples/screenshots/webgl_materials_cubemap_dynamic.jpg b/examples/screenshots/webgl_materials_cubemap_dynamic.jpg index db18a255dda5b7..c5fe4775add79a 100644 Binary files a/examples/screenshots/webgl_materials_cubemap_dynamic.jpg and b/examples/screenshots/webgl_materials_cubemap_dynamic.jpg differ diff --git a/examples/screenshots/webgl_materials_cubemap_mipmaps.jpg b/examples/screenshots/webgl_materials_cubemap_mipmaps.jpg index 3ba63b454c28ad..2ecf017efed77e 100644 Binary files a/examples/screenshots/webgl_materials_cubemap_mipmaps.jpg and b/examples/screenshots/webgl_materials_cubemap_mipmaps.jpg differ diff --git a/examples/screenshots/webgl_materials_cubemap_refraction.jpg b/examples/screenshots/webgl_materials_cubemap_refraction.jpg index 91fa4cce07a678..48195e04372207 100644 Binary files a/examples/screenshots/webgl_materials_cubemap_refraction.jpg and b/examples/screenshots/webgl_materials_cubemap_refraction.jpg differ diff --git a/examples/screenshots/webgl_materials_cubemap_render_to_mipmaps.jpg b/examples/screenshots/webgl_materials_cubemap_render_to_mipmaps.jpg new file mode 100644 index 00000000000000..1ee1a7fca11ee0 Binary files /dev/null and b/examples/screenshots/webgl_materials_cubemap_render_to_mipmaps.jpg differ diff --git a/examples/screenshots/webgl_materials_curvature.jpg b/examples/screenshots/webgl_materials_curvature.jpg deleted file mode 100644 index 3ff9fdf6a6042c..00000000000000 Binary files a/examples/screenshots/webgl_materials_curvature.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_materials_displacementmap.jpg b/examples/screenshots/webgl_materials_displacementmap.jpg index 1632eb0a7d83fc..8bfdbf1f04b5af 100644 Binary files a/examples/screenshots/webgl_materials_displacementmap.jpg and b/examples/screenshots/webgl_materials_displacementmap.jpg differ diff --git a/examples/screenshots/webgl_materials_envmaps.jpg b/examples/screenshots/webgl_materials_envmaps.jpg index 59671ee04bf55d..a92f26f14b9430 100644 Binary files a/examples/screenshots/webgl_materials_envmaps.jpg and b/examples/screenshots/webgl_materials_envmaps.jpg differ diff --git a/examples/screenshots/webgl_materials_envmaps_exr.jpg b/examples/screenshots/webgl_materials_envmaps_exr.jpg index 615e1bfdb5d7f2..1228370a6e46c5 100644 Binary files a/examples/screenshots/webgl_materials_envmaps_exr.jpg and b/examples/screenshots/webgl_materials_envmaps_exr.jpg differ diff --git a/examples/screenshots/webgl_materials_envmaps_groundprojected.jpg b/examples/screenshots/webgl_materials_envmaps_groundprojected.jpg index 63622f0d3d692c..6cbd53b9394db0 100644 Binary files a/examples/screenshots/webgl_materials_envmaps_groundprojected.jpg and b/examples/screenshots/webgl_materials_envmaps_groundprojected.jpg differ diff --git a/examples/screenshots/webgl_materials_lightmap.jpg b/examples/screenshots/webgl_materials_lightmap.jpg deleted file mode 100644 index f6704ae4362a87..00000000000000 Binary files a/examples/screenshots/webgl_materials_lightmap.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_materials_normalmap.jpg b/examples/screenshots/webgl_materials_normalmap.jpg index 82a0ea58eac79c..d02b3ce11ebdf0 100644 Binary files a/examples/screenshots/webgl_materials_normalmap.jpg and b/examples/screenshots/webgl_materials_normalmap.jpg differ diff --git a/examples/screenshots/webgl_materials_normalmap_object_space.jpg b/examples/screenshots/webgl_materials_normalmap_object_space.jpg index 735bdb11285f0f..a86cb08c238689 100644 Binary files a/examples/screenshots/webgl_materials_normalmap_object_space.jpg and b/examples/screenshots/webgl_materials_normalmap_object_space.jpg differ diff --git a/examples/screenshots/webgl_materials_physical_clearcoat.jpg b/examples/screenshots/webgl_materials_physical_clearcoat.jpg index e675716491066a..40e0549e9257ae 100644 Binary files a/examples/screenshots/webgl_materials_physical_clearcoat.jpg and b/examples/screenshots/webgl_materials_physical_clearcoat.jpg differ diff --git a/examples/screenshots/webgl_materials_physical_reflectivity.jpg b/examples/screenshots/webgl_materials_physical_reflectivity.jpg deleted file mode 100644 index a8a1782b2e468f..00000000000000 Binary files a/examples/screenshots/webgl_materials_physical_reflectivity.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_materials_physical_transmission.jpg b/examples/screenshots/webgl_materials_physical_transmission.jpg index c2e495a899d4ac..865498a5a8f7b4 100644 Binary files a/examples/screenshots/webgl_materials_physical_transmission.jpg and b/examples/screenshots/webgl_materials_physical_transmission.jpg differ diff --git a/examples/screenshots/webgl_materials_physical_transmission_alpha.jpg b/examples/screenshots/webgl_materials_physical_transmission_alpha.jpg index eb57fab713d69d..656a6e4539c24e 100644 Binary files a/examples/screenshots/webgl_materials_physical_transmission_alpha.jpg and b/examples/screenshots/webgl_materials_physical_transmission_alpha.jpg differ diff --git a/examples/screenshots/webgl_materials_standard.jpg b/examples/screenshots/webgl_materials_standard.jpg deleted file mode 100644 index 722d98f305ad25..00000000000000 Binary files a/examples/screenshots/webgl_materials_standard.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_materials_subsurface_scattering.jpg b/examples/screenshots/webgl_materials_subsurface_scattering.jpg index f2b899d83d4a00..ade06b8c71bca2 100644 Binary files a/examples/screenshots/webgl_materials_subsurface_scattering.jpg and b/examples/screenshots/webgl_materials_subsurface_scattering.jpg differ diff --git a/examples/screenshots/webgl_materials_texture_anisotropy.jpg b/examples/screenshots/webgl_materials_texture_anisotropy.jpg index abf6929d2ecf83..48b7fa7dedf369 100644 Binary files a/examples/screenshots/webgl_materials_texture_anisotropy.jpg and b/examples/screenshots/webgl_materials_texture_anisotropy.jpg differ diff --git a/examples/screenshots/webgl_materials_texture_filters.jpg b/examples/screenshots/webgl_materials_texture_filters.jpg index 4f89adcd320330..d67bd0f5fadb5d 100644 Binary files a/examples/screenshots/webgl_materials_texture_filters.jpg and b/examples/screenshots/webgl_materials_texture_filters.jpg differ diff --git a/examples/screenshots/webgl_materials_texture_manualmipmap.jpg b/examples/screenshots/webgl_materials_texture_manualmipmap.jpg index 334478c64ddab4..7bde6041823d3f 100644 Binary files a/examples/screenshots/webgl_materials_texture_manualmipmap.jpg and b/examples/screenshots/webgl_materials_texture_manualmipmap.jpg differ diff --git a/examples/screenshots/webgl_materials_texture_partialupdate.jpg b/examples/screenshots/webgl_materials_texture_partialupdate.jpg index e510acb875a163..f168ed43316d6d 100644 Binary files a/examples/screenshots/webgl_materials_texture_partialupdate.jpg and b/examples/screenshots/webgl_materials_texture_partialupdate.jpg differ diff --git a/examples/screenshots/webgl_materials_toon.jpg b/examples/screenshots/webgl_materials_toon.jpg new file mode 100644 index 00000000000000..c6bdd1fe12cae3 Binary files /dev/null and b/examples/screenshots/webgl_materials_toon.jpg differ diff --git a/examples/screenshots/webgl_materials_variations_basic.jpg b/examples/screenshots/webgl_materials_variations_basic.jpg deleted file mode 100644 index ae6a4bcb801ad4..00000000000000 Binary files a/examples/screenshots/webgl_materials_variations_basic.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_materials_variations_lambert.jpg b/examples/screenshots/webgl_materials_variations_lambert.jpg deleted file mode 100644 index e6b5bf353069aa..00000000000000 Binary files a/examples/screenshots/webgl_materials_variations_lambert.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_materials_variations_phong.jpg b/examples/screenshots/webgl_materials_variations_phong.jpg deleted file mode 100644 index 202c1a194d06b6..00000000000000 Binary files a/examples/screenshots/webgl_materials_variations_phong.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_materials_variations_physical.jpg b/examples/screenshots/webgl_materials_variations_physical.jpg deleted file mode 100644 index d2e82dd3181e6d..00000000000000 Binary files a/examples/screenshots/webgl_materials_variations_physical.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_materials_variations_standard.jpg b/examples/screenshots/webgl_materials_variations_standard.jpg deleted file mode 100644 index 5d0b3cbecf8879..00000000000000 Binary files a/examples/screenshots/webgl_materials_variations_standard.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_materials_variations_toon.jpg b/examples/screenshots/webgl_materials_variations_toon.jpg deleted file mode 100644 index 3e8da687aad568..00000000000000 Binary files a/examples/screenshots/webgl_materials_variations_toon.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_materials_video.jpg b/examples/screenshots/webgl_materials_video.jpg index a1a307aa6e18de..001fe203e0b94e 100644 Binary files a/examples/screenshots/webgl_materials_video.jpg and b/examples/screenshots/webgl_materials_video.jpg differ diff --git a/examples/screenshots/webgl_math_obb.jpg b/examples/screenshots/webgl_math_obb.jpg index 282ad99369173c..bc82eaba17ace0 100644 Binary files a/examples/screenshots/webgl_math_obb.jpg and b/examples/screenshots/webgl_math_obb.jpg differ diff --git a/examples/screenshots/webgl_mesh_batch.jpg b/examples/screenshots/webgl_mesh_batch.jpg new file mode 100644 index 00000000000000..0c471db865c2eb Binary files /dev/null and b/examples/screenshots/webgl_mesh_batch.jpg differ diff --git a/examples/screenshots/webgl_mirror.jpg b/examples/screenshots/webgl_mirror.jpg index 4d7099e4707561..6f78a59341e347 100644 Binary files a/examples/screenshots/webgl_mirror.jpg and b/examples/screenshots/webgl_mirror.jpg differ diff --git a/examples/screenshots/webgl_modifier_curve.jpg b/examples/screenshots/webgl_modifier_curve.jpg index a5b6ed301e2fa0..36bbf10ba24e04 100644 Binary files a/examples/screenshots/webgl_modifier_curve.jpg and b/examples/screenshots/webgl_modifier_curve.jpg differ diff --git a/examples/screenshots/webgl_modifier_curve_instanced.jpg b/examples/screenshots/webgl_modifier_curve_instanced.jpg index af0baca8dd44ba..369a8b803482a7 100644 Binary files a/examples/screenshots/webgl_modifier_curve_instanced.jpg and b/examples/screenshots/webgl_modifier_curve_instanced.jpg differ diff --git a/examples/screenshots/webgl_modifier_edgesplit.jpg b/examples/screenshots/webgl_modifier_edgesplit.jpg index b30671ed231f07..378e06d48628b8 100644 Binary files a/examples/screenshots/webgl_modifier_edgesplit.jpg and b/examples/screenshots/webgl_modifier_edgesplit.jpg differ diff --git a/examples/screenshots/webgl_modifier_simplifier.jpg b/examples/screenshots/webgl_modifier_simplifier.jpg index e36787ea47d83f..241daf62e22f77 100644 Binary files a/examples/screenshots/webgl_modifier_simplifier.jpg and b/examples/screenshots/webgl_modifier_simplifier.jpg differ diff --git a/examples/screenshots/webgl_modifier_subdivision.jpg b/examples/screenshots/webgl_modifier_subdivision.jpg index 24618822326bcf..2465d3e925f95a 100644 Binary files a/examples/screenshots/webgl_modifier_subdivision.jpg and b/examples/screenshots/webgl_modifier_subdivision.jpg differ diff --git a/examples/screenshots/webgl_modifier_tessellation.jpg b/examples/screenshots/webgl_modifier_tessellation.jpg index c100f8d3b08184..70f0ced64bc2f5 100644 Binary files a/examples/screenshots/webgl_modifier_tessellation.jpg and b/examples/screenshots/webgl_modifier_tessellation.jpg differ diff --git a/examples/screenshots/webgl_morphtargets_face.jpg b/examples/screenshots/webgl_morphtargets_face.jpg index a1d77c1af54712..44edc24ad692ce 100644 Binary files a/examples/screenshots/webgl_morphtargets_face.jpg and b/examples/screenshots/webgl_morphtargets_face.jpg differ diff --git a/examples/screenshots/webgl_morphtargets_webcam.jpg b/examples/screenshots/webgl_morphtargets_webcam.jpg new file mode 100644 index 00000000000000..a4361f78df4e09 Binary files /dev/null and b/examples/screenshots/webgl_morphtargets_webcam.jpg differ diff --git a/examples/screenshots/webgl_multiple_canvases_circle.jpg b/examples/screenshots/webgl_multiple_canvases_circle.jpg deleted file mode 100644 index ff9aa51b2eaa15..00000000000000 Binary files a/examples/screenshots/webgl_multiple_canvases_circle.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_multiple_canvases_complex.jpg b/examples/screenshots/webgl_multiple_canvases_complex.jpg deleted file mode 100644 index 0918981c9826d6..00000000000000 Binary files a/examples/screenshots/webgl_multiple_canvases_complex.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_multiple_canvases_grid.jpg b/examples/screenshots/webgl_multiple_canvases_grid.jpg deleted file mode 100644 index 035f5cd5f12f27..00000000000000 Binary files a/examples/screenshots/webgl_multiple_canvases_grid.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_multiple_elements.jpg b/examples/screenshots/webgl_multiple_elements.jpg index b193738df30bc2..ccf241374ab29c 100644 Binary files a/examples/screenshots/webgl_multiple_elements.jpg and b/examples/screenshots/webgl_multiple_elements.jpg differ diff --git a/examples/screenshots/webgl_multiple_renderers.jpg b/examples/screenshots/webgl_multiple_renderers.jpg deleted file mode 100644 index 86a82f1885de28..00000000000000 Binary files a/examples/screenshots/webgl_multiple_renderers.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_multiple_rendertargets.jpg b/examples/screenshots/webgl_multiple_rendertargets.jpg new file mode 100644 index 00000000000000..0f1799ead74ba1 Binary files /dev/null and b/examples/screenshots/webgl_multiple_rendertargets.jpg differ diff --git a/examples/screenshots/webgl_multiple_scenes_comparison.jpg b/examples/screenshots/webgl_multiple_scenes_comparison.jpg index 71ad4b92adf0bd..aa590d1ac34016 100644 Binary files a/examples/screenshots/webgl_multiple_scenes_comparison.jpg and b/examples/screenshots/webgl_multiple_scenes_comparison.jpg differ diff --git a/examples/screenshots/webgl_multiple_views.jpg b/examples/screenshots/webgl_multiple_views.jpg index ccced615bfdeda..33a195c75c9cdd 100644 Binary files a/examples/screenshots/webgl_multiple_views.jpg and b/examples/screenshots/webgl_multiple_views.jpg differ diff --git a/examples/screenshots/webgl_multisampled_renderbuffers.jpg b/examples/screenshots/webgl_multisampled_renderbuffers.jpg new file mode 100644 index 00000000000000..e99ca5a40ec5ab Binary files /dev/null and b/examples/screenshots/webgl_multisampled_renderbuffers.jpg differ diff --git a/examples/screenshots/webgl_nodes_loader_gltf_iridescence.jpg b/examples/screenshots/webgl_nodes_loader_gltf_iridescence.jpg deleted file mode 100644 index 3ebb83eb07b3fa..00000000000000 Binary files a/examples/screenshots/webgl_nodes_loader_gltf_iridescence.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_nodes_loader_gltf_sheen.jpg b/examples/screenshots/webgl_nodes_loader_gltf_sheen.jpg deleted file mode 100644 index b20319b2c2a975..00000000000000 Binary files a/examples/screenshots/webgl_nodes_loader_gltf_sheen.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_nodes_loader_gltf_transmission.jpg b/examples/screenshots/webgl_nodes_loader_gltf_transmission.jpg deleted file mode 100644 index fb2ab0e20f15e3..00000000000000 Binary files a/examples/screenshots/webgl_nodes_loader_gltf_transmission.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_nodes_loader_materialx.jpg b/examples/screenshots/webgl_nodes_loader_materialx.jpg deleted file mode 100644 index 721c4e5b7eb242..00000000000000 Binary files a/examples/screenshots/webgl_nodes_loader_materialx.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_nodes_materials_instance_uniform.jpg b/examples/screenshots/webgl_nodes_materials_instance_uniform.jpg deleted file mode 100644 index 58bcb2ff179e7d..00000000000000 Binary files a/examples/screenshots/webgl_nodes_materials_instance_uniform.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_nodes_materials_physical_clearcoat.jpg b/examples/screenshots/webgl_nodes_materials_physical_clearcoat.jpg deleted file mode 100644 index 9827e23d95ce1c..00000000000000 Binary files a/examples/screenshots/webgl_nodes_materials_physical_clearcoat.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_nodes_materials_standard.jpg b/examples/screenshots/webgl_nodes_materials_standard.jpg deleted file mode 100644 index e579cd1cfeca11..00000000000000 Binary files a/examples/screenshots/webgl_nodes_materials_standard.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_nodes_materialx_noise.jpg b/examples/screenshots/webgl_nodes_materialx_noise.jpg deleted file mode 100644 index 9fb74dd9e4a8e0..00000000000000 Binary files a/examples/screenshots/webgl_nodes_materialx_noise.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_nodes_points.jpg b/examples/screenshots/webgl_nodes_points.jpg deleted file mode 100644 index 04ff84338a6a29..00000000000000 Binary files a/examples/screenshots/webgl_nodes_points.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_performance.jpg b/examples/screenshots/webgl_performance.jpg index f489858f47924a..c6abc91412cf0e 100644 Binary files a/examples/screenshots/webgl_performance.jpg and b/examples/screenshots/webgl_performance.jpg differ diff --git a/examples/screenshots/webgl_performance_shader.jpg b/examples/screenshots/webgl_performance_shader.jpg deleted file mode 100644 index ee61c80dc1bc4e..00000000000000 Binary files a/examples/screenshots/webgl_performance_shader.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_performance_static.jpg b/examples/screenshots/webgl_performance_static.jpg deleted file mode 100644 index 81c9aaefcb131b..00000000000000 Binary files a/examples/screenshots/webgl_performance_static.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_points_dynamic.jpg b/examples/screenshots/webgl_points_dynamic.jpg index 0a32a094536325..9d2587b541641f 100644 Binary files a/examples/screenshots/webgl_points_dynamic.jpg and b/examples/screenshots/webgl_points_dynamic.jpg differ diff --git a/examples/screenshots/webgl_points_waves.jpg b/examples/screenshots/webgl_points_waves.jpg index 1817b98272fd25..318837a38a8165 100644 Binary files a/examples/screenshots/webgl_points_waves.jpg and b/examples/screenshots/webgl_points_waves.jpg differ diff --git a/examples/screenshots/webgl_portal.jpg b/examples/screenshots/webgl_portal.jpg index ffc6bb1107055a..f39d865f553354 100644 Binary files a/examples/screenshots/webgl_portal.jpg and b/examples/screenshots/webgl_portal.jpg differ diff --git a/examples/screenshots/webgl_postprocessing.jpg b/examples/screenshots/webgl_postprocessing.jpg index ca61c2374eb82e..6817acc56a1c4b 100644 Binary files a/examples/screenshots/webgl_postprocessing.jpg and b/examples/screenshots/webgl_postprocessing.jpg differ diff --git a/examples/screenshots/webgl_postprocessing_3dlut.jpg b/examples/screenshots/webgl_postprocessing_3dlut.jpg index 03d0601e020295..01e2166bc21c5d 100644 Binary files a/examples/screenshots/webgl_postprocessing_3dlut.jpg and b/examples/screenshots/webgl_postprocessing_3dlut.jpg differ diff --git a/examples/screenshots/webgl_postprocessing_advanced.jpg b/examples/screenshots/webgl_postprocessing_advanced.jpg index 39e08022b2fb34..f1de8899397e93 100644 Binary files a/examples/screenshots/webgl_postprocessing_advanced.jpg and b/examples/screenshots/webgl_postprocessing_advanced.jpg differ diff --git a/examples/screenshots/webgl_postprocessing_afterimage.jpg b/examples/screenshots/webgl_postprocessing_afterimage.jpg index fce555639e346c..1b6c96b9b876c3 100644 Binary files a/examples/screenshots/webgl_postprocessing_afterimage.jpg and b/examples/screenshots/webgl_postprocessing_afterimage.jpg differ diff --git a/examples/screenshots/webgl_postprocessing_backgrounds.jpg b/examples/screenshots/webgl_postprocessing_backgrounds.jpg index ba20a7399e01c9..e2155df821ef49 100644 Binary files a/examples/screenshots/webgl_postprocessing_backgrounds.jpg and b/examples/screenshots/webgl_postprocessing_backgrounds.jpg differ diff --git a/examples/screenshots/webgl_postprocessing_crossfade.jpg b/examples/screenshots/webgl_postprocessing_crossfade.jpg deleted file mode 100644 index cf4f02a0a68646..00000000000000 Binary files a/examples/screenshots/webgl_postprocessing_crossfade.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_postprocessing_dof.jpg b/examples/screenshots/webgl_postprocessing_dof.jpg index 5c54bfdf4e87c1..e6ba34da6f3797 100644 Binary files a/examples/screenshots/webgl_postprocessing_dof.jpg and b/examples/screenshots/webgl_postprocessing_dof.jpg differ diff --git a/examples/screenshots/webgl_postprocessing_dof2.jpg b/examples/screenshots/webgl_postprocessing_dof2.jpg index e728b193546ba1..10312be65d30cc 100644 Binary files a/examples/screenshots/webgl_postprocessing_dof2.jpg and b/examples/screenshots/webgl_postprocessing_dof2.jpg differ diff --git a/examples/screenshots/webgl_postprocessing_fxaa.jpg b/examples/screenshots/webgl_postprocessing_fxaa.jpg index e891f845e1ff0e..ff0e2ad0472f1f 100644 Binary files a/examples/screenshots/webgl_postprocessing_fxaa.jpg and b/examples/screenshots/webgl_postprocessing_fxaa.jpg differ diff --git a/examples/screenshots/webgl_postprocessing_glitch.jpg b/examples/screenshots/webgl_postprocessing_glitch.jpg index 22a3e770262dcd..dc6fb98db84c6c 100644 Binary files a/examples/screenshots/webgl_postprocessing_glitch.jpg and b/examples/screenshots/webgl_postprocessing_glitch.jpg differ diff --git a/examples/screenshots/webgl_postprocessing_godrays.jpg b/examples/screenshots/webgl_postprocessing_godrays.jpg index 38d62cc64282da..b8f6b65b1c558e 100644 Binary files a/examples/screenshots/webgl_postprocessing_godrays.jpg and b/examples/screenshots/webgl_postprocessing_godrays.jpg differ diff --git a/examples/screenshots/webgl_postprocessing_gtao.jpg b/examples/screenshots/webgl_postprocessing_gtao.jpg new file mode 100644 index 00000000000000..de9cce610c1169 Binary files /dev/null and b/examples/screenshots/webgl_postprocessing_gtao.jpg differ diff --git a/examples/screenshots/webgl_postprocessing_masking.jpg b/examples/screenshots/webgl_postprocessing_masking.jpg index ea87b4b6150a7a..5c038e567e6ea2 100644 Binary files a/examples/screenshots/webgl_postprocessing_masking.jpg and b/examples/screenshots/webgl_postprocessing_masking.jpg differ diff --git a/examples/screenshots/webgl_postprocessing_material_ao.jpg b/examples/screenshots/webgl_postprocessing_material_ao.jpg new file mode 100644 index 00000000000000..210f06ddbe9577 Binary files /dev/null and b/examples/screenshots/webgl_postprocessing_material_ao.jpg differ diff --git a/examples/screenshots/webgl_postprocessing_outline.jpg b/examples/screenshots/webgl_postprocessing_outline.jpg index 3ecfb8daf4145f..0d6930f6e7323b 100644 Binary files a/examples/screenshots/webgl_postprocessing_outline.jpg and b/examples/screenshots/webgl_postprocessing_outline.jpg differ diff --git a/examples/screenshots/webgl_postprocessing_pixel.jpg b/examples/screenshots/webgl_postprocessing_pixel.jpg index 2d49a89a31b505..bea6d2c1d85409 100644 Binary files a/examples/screenshots/webgl_postprocessing_pixel.jpg and b/examples/screenshots/webgl_postprocessing_pixel.jpg differ diff --git a/examples/screenshots/webgl_postprocessing_rgb_halftone.jpg b/examples/screenshots/webgl_postprocessing_rgb_halftone.jpg index 9e172528226183..90757599042d7b 100644 Binary files a/examples/screenshots/webgl_postprocessing_rgb_halftone.jpg and b/examples/screenshots/webgl_postprocessing_rgb_halftone.jpg differ diff --git a/examples/screenshots/webgl_postprocessing_sao.jpg b/examples/screenshots/webgl_postprocessing_sao.jpg index a98c51ff58fbb3..15fcbb394ff18a 100644 Binary files a/examples/screenshots/webgl_postprocessing_sao.jpg and b/examples/screenshots/webgl_postprocessing_sao.jpg differ diff --git a/examples/screenshots/webgl_postprocessing_smaa.jpg b/examples/screenshots/webgl_postprocessing_smaa.jpg index 8c4353dc43c2be..c5b9739282411f 100644 Binary files a/examples/screenshots/webgl_postprocessing_smaa.jpg and b/examples/screenshots/webgl_postprocessing_smaa.jpg differ diff --git a/examples/screenshots/webgl_postprocessing_sobel.jpg b/examples/screenshots/webgl_postprocessing_sobel.jpg index 225e6878743ffc..d466186fa4051e 100644 Binary files a/examples/screenshots/webgl_postprocessing_sobel.jpg and b/examples/screenshots/webgl_postprocessing_sobel.jpg differ diff --git a/examples/screenshots/webgl_postprocessing_ssaa.jpg b/examples/screenshots/webgl_postprocessing_ssaa.jpg index dbe7466c407800..f7d571546416d5 100644 Binary files a/examples/screenshots/webgl_postprocessing_ssaa.jpg and b/examples/screenshots/webgl_postprocessing_ssaa.jpg differ diff --git a/examples/screenshots/webgl_postprocessing_ssao.jpg b/examples/screenshots/webgl_postprocessing_ssao.jpg index 4d1cb62346ab2b..8d0b7f89455ef2 100644 Binary files a/examples/screenshots/webgl_postprocessing_ssao.jpg and b/examples/screenshots/webgl_postprocessing_ssao.jpg differ diff --git a/examples/screenshots/webgl_postprocessing_ssr.jpg b/examples/screenshots/webgl_postprocessing_ssr.jpg index db2d70d73e3ce4..9ae69ab8ae8a9d 100644 Binary files a/examples/screenshots/webgl_postprocessing_ssr.jpg and b/examples/screenshots/webgl_postprocessing_ssr.jpg differ diff --git a/examples/screenshots/webgl_postprocessing_taa.jpg b/examples/screenshots/webgl_postprocessing_taa.jpg index 091aaaf75f7972..f4365d99d3181e 100644 Binary files a/examples/screenshots/webgl_postprocessing_taa.jpg and b/examples/screenshots/webgl_postprocessing_taa.jpg differ diff --git a/examples/screenshots/webgl_postprocessing_transition.jpg b/examples/screenshots/webgl_postprocessing_transition.jpg new file mode 100644 index 00000000000000..e135b301781f1e Binary files /dev/null and b/examples/screenshots/webgl_postprocessing_transition.jpg differ diff --git a/examples/screenshots/webgl_postprocessing_unreal_bloom.jpg b/examples/screenshots/webgl_postprocessing_unreal_bloom.jpg index 02794574cf2b90..babb5ab9ebad31 100644 Binary files a/examples/screenshots/webgl_postprocessing_unreal_bloom.jpg and b/examples/screenshots/webgl_postprocessing_unreal_bloom.jpg differ diff --git a/examples/screenshots/webgl_postprocessing_unreal_bloom_selective.jpg b/examples/screenshots/webgl_postprocessing_unreal_bloom_selective.jpg index c5263be66979fc..c1224c2945777b 100644 Binary files a/examples/screenshots/webgl_postprocessing_unreal_bloom_selective.jpg and b/examples/screenshots/webgl_postprocessing_unreal_bloom_selective.jpg differ diff --git a/examples/screenshots/webgl_random_uv.jpg b/examples/screenshots/webgl_random_uv.jpg new file mode 100644 index 00000000000000..16026cc5a206aa Binary files /dev/null and b/examples/screenshots/webgl_random_uv.jpg differ diff --git a/examples/screenshots/webgl_raycaster_bvh.jpg b/examples/screenshots/webgl_raycaster_bvh.jpg index e9089286dd699d..59ae5b93a95246 100644 Binary files a/examples/screenshots/webgl_raycaster_bvh.jpg and b/examples/screenshots/webgl_raycaster_bvh.jpg differ diff --git a/examples/screenshots/webgl_raymarching_reflect.jpg b/examples/screenshots/webgl_raymarching_reflect.jpg deleted file mode 100644 index 6b3f7c3fc42b9e..00000000000000 Binary files a/examples/screenshots/webgl_raymarching_reflect.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_read_float_buffer.jpg b/examples/screenshots/webgl_read_float_buffer.jpg index d75ee47edfd088..f2231baa6a1ca4 100644 Binary files a/examples/screenshots/webgl_read_float_buffer.jpg and b/examples/screenshots/webgl_read_float_buffer.jpg differ diff --git a/examples/screenshots/webgl_refraction.jpg b/examples/screenshots/webgl_refraction.jpg index faf75a1c35a027..d48b51fac14d0a 100644 Binary files a/examples/screenshots/webgl_refraction.jpg and b/examples/screenshots/webgl_refraction.jpg differ diff --git a/examples/screenshots/webgl2_materials_texture2darray.jpg b/examples/screenshots/webgl_rendertarget_texture2darray.jpg similarity index 100% rename from examples/screenshots/webgl2_materials_texture2darray.jpg rename to examples/screenshots/webgl_rendertarget_texture2darray.jpg diff --git a/examples/screenshots/webgl_reverse_depth_buffer.jpg b/examples/screenshots/webgl_reverse_depth_buffer.jpg new file mode 100644 index 00000000000000..310440b2e687b0 Binary files /dev/null and b/examples/screenshots/webgl_reverse_depth_buffer.jpg differ diff --git a/examples/screenshots/webgl_rtt.jpg b/examples/screenshots/webgl_rtt.jpg index e65e01878fd171..dd799f5169db32 100644 Binary files a/examples/screenshots/webgl_rtt.jpg and b/examples/screenshots/webgl_rtt.jpg differ diff --git a/examples/screenshots/webgl_shader2.jpg b/examples/screenshots/webgl_shader2.jpg deleted file mode 100644 index 9199418172115c..00000000000000 Binary files a/examples/screenshots/webgl_shader2.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_shader_lava.jpg b/examples/screenshots/webgl_shader_lava.jpg index a4d2615ed0c8f6..7e3826ebbbc70d 100644 Binary files a/examples/screenshots/webgl_shader_lava.jpg and b/examples/screenshots/webgl_shader_lava.jpg differ diff --git a/examples/screenshots/webgl_shaders_ocean.jpg b/examples/screenshots/webgl_shaders_ocean.jpg index 4147f0bbbd3e0b..564893755cb552 100644 Binary files a/examples/screenshots/webgl_shaders_ocean.jpg and b/examples/screenshots/webgl_shaders_ocean.jpg differ diff --git a/examples/screenshots/webgl_shaders_tonemapping.jpg b/examples/screenshots/webgl_shaders_tonemapping.jpg deleted file mode 100644 index 93aac1e469cf7e..00000000000000 Binary files a/examples/screenshots/webgl_shaders_tonemapping.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_shadow_contact.jpg b/examples/screenshots/webgl_shadow_contact.jpg index b24640377965f0..574b1e064c40bb 100644 Binary files a/examples/screenshots/webgl_shadow_contact.jpg and b/examples/screenshots/webgl_shadow_contact.jpg differ diff --git a/examples/screenshots/webgl_shadowmap.jpg b/examples/screenshots/webgl_shadowmap.jpg index c3153f9844364b..76c871806e66c0 100644 Binary files a/examples/screenshots/webgl_shadowmap.jpg and b/examples/screenshots/webgl_shadowmap.jpg differ diff --git a/examples/screenshots/webgl_shadowmap_csm.jpg b/examples/screenshots/webgl_shadowmap_csm.jpg index efd6b650c269ec..37b19477b2bef3 100644 Binary files a/examples/screenshots/webgl_shadowmap_csm.jpg and b/examples/screenshots/webgl_shadowmap_csm.jpg differ diff --git a/examples/screenshots/webgl_shadowmap_pcss.jpg b/examples/screenshots/webgl_shadowmap_pcss.jpg index 1abc8f03f58b9d..e483d677dc230e 100644 Binary files a/examples/screenshots/webgl_shadowmap_pcss.jpg and b/examples/screenshots/webgl_shadowmap_pcss.jpg differ diff --git a/examples/screenshots/webgl_shadowmap_performance.jpg b/examples/screenshots/webgl_shadowmap_performance.jpg index 83b810759edd1d..00a963f58be4a0 100644 Binary files a/examples/screenshots/webgl_shadowmap_performance.jpg and b/examples/screenshots/webgl_shadowmap_performance.jpg differ diff --git a/examples/screenshots/webgl_shadowmap_pointlight.jpg b/examples/screenshots/webgl_shadowmap_pointlight.jpg index fc9bb22124130e..5ec397def5d6c5 100644 Binary files a/examples/screenshots/webgl_shadowmap_pointlight.jpg and b/examples/screenshots/webgl_shadowmap_pointlight.jpg differ diff --git a/examples/screenshots/webgl_shadowmap_progressive.jpg b/examples/screenshots/webgl_shadowmap_progressive.jpg index b1a35e17db3f58..7ee3428132d3dd 100644 Binary files a/examples/screenshots/webgl_shadowmap_progressive.jpg and b/examples/screenshots/webgl_shadowmap_progressive.jpg differ diff --git a/examples/screenshots/webgl_shadowmap_viewer.jpg b/examples/screenshots/webgl_shadowmap_viewer.jpg index 51df2627017458..45ad2df6f935e6 100644 Binary files a/examples/screenshots/webgl_shadowmap_viewer.jpg and b/examples/screenshots/webgl_shadowmap_viewer.jpg differ diff --git a/examples/screenshots/webgl_shadowmap_vsm.jpg b/examples/screenshots/webgl_shadowmap_vsm.jpg index 15300d96fde6f3..fcfe1a77eb6ba2 100644 Binary files a/examples/screenshots/webgl_shadowmap_vsm.jpg and b/examples/screenshots/webgl_shadowmap_vsm.jpg differ diff --git a/examples/screenshots/webgl_shadowmesh.jpg b/examples/screenshots/webgl_shadowmesh.jpg index 97d4c78168d2c6..96eb793cdfa386 100644 Binary files a/examples/screenshots/webgl_shadowmesh.jpg and b/examples/screenshots/webgl_shadowmesh.jpg differ diff --git a/examples/screenshots/webgl_simple_gi.jpg b/examples/screenshots/webgl_simple_gi.jpg index fd56f14a73d1ce..0a23d9aefc5d80 100644 Binary files a/examples/screenshots/webgl_simple_gi.jpg and b/examples/screenshots/webgl_simple_gi.jpg differ diff --git a/examples/screenshots/webgl_test_memory.jpg b/examples/screenshots/webgl_test_memory.jpg index 9ee2982740182c..ba6b62d7e98423 100644 Binary files a/examples/screenshots/webgl_test_memory.jpg and b/examples/screenshots/webgl_test_memory.jpg differ diff --git a/examples/screenshots/webgl_test_wide_gamut.jpg b/examples/screenshots/webgl_test_wide_gamut.jpg new file mode 100644 index 00000000000000..3df604ac67e558 Binary files /dev/null and b/examples/screenshots/webgl_test_wide_gamut.jpg differ diff --git a/examples/screenshots/webgl_texture2darray.jpg b/examples/screenshots/webgl_texture2darray.jpg new file mode 100644 index 00000000000000..2a78572742b7be Binary files /dev/null and b/examples/screenshots/webgl_texture2darray.jpg differ diff --git a/examples/screenshots/webgl2_texture2darray_compressed.jpg b/examples/screenshots/webgl_texture2darray_compressed.jpg similarity index 100% rename from examples/screenshots/webgl2_texture2darray_compressed.jpg rename to examples/screenshots/webgl_texture2darray_compressed.jpg diff --git a/examples/screenshots/webgl_texture2darray_layerupdate.jpg b/examples/screenshots/webgl_texture2darray_layerupdate.jpg new file mode 100644 index 00000000000000..8f1cc6d7e50e72 Binary files /dev/null and b/examples/screenshots/webgl_texture2darray_layerupdate.jpg differ diff --git a/examples/screenshots/webgl2_materials_texture3d.jpg b/examples/screenshots/webgl_texture3d.jpg similarity index 100% rename from examples/screenshots/webgl2_materials_texture3d.jpg rename to examples/screenshots/webgl_texture3d.jpg diff --git a/examples/screenshots/webgl2_materials_texture3d_partialupdate.jpg b/examples/screenshots/webgl_texture3d_partialupdate.jpg similarity index 100% rename from examples/screenshots/webgl2_materials_texture3d_partialupdate.jpg rename to examples/screenshots/webgl_texture3d_partialupdate.jpg diff --git a/examples/screenshots/webgl_tiled_forward.jpg b/examples/screenshots/webgl_tiled_forward.jpg deleted file mode 100644 index 6efa92e42f6c1f..00000000000000 Binary files a/examples/screenshots/webgl_tiled_forward.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_tonemapping.jpg b/examples/screenshots/webgl_tonemapping.jpg index e27b33b8caea9e..6ffb8c250d633e 100644 Binary files a/examples/screenshots/webgl_tonemapping.jpg and b/examples/screenshots/webgl_tonemapping.jpg differ diff --git a/examples/screenshots/webgl_trails.jpg b/examples/screenshots/webgl_trails.jpg deleted file mode 100644 index 1f1e0db421256c..00000000000000 Binary files a/examples/screenshots/webgl_trails.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_ubo.jpg b/examples/screenshots/webgl_ubo.jpg new file mode 100644 index 00000000000000..6bb4cfb748ece5 Binary files /dev/null and b/examples/screenshots/webgl_ubo.jpg differ diff --git a/examples/screenshots/webgl_ubo_arrays.jpg b/examples/screenshots/webgl_ubo_arrays.jpg new file mode 100644 index 00000000000000..5519f8be2a90dc Binary files /dev/null and b/examples/screenshots/webgl_ubo_arrays.jpg differ diff --git a/examples/screenshots/webgl_video_panorama_equirectangular.jpg b/examples/screenshots/webgl_video_panorama_equirectangular.jpg index 38cc7996c39d81..43a0299676dd5d 100644 Binary files a/examples/screenshots/webgl_video_panorama_equirectangular.jpg and b/examples/screenshots/webgl_video_panorama_equirectangular.jpg differ diff --git a/examples/screenshots/webgl2_volume_cloud.jpg b/examples/screenshots/webgl_volume_cloud.jpg similarity index 100% rename from examples/screenshots/webgl2_volume_cloud.jpg rename to examples/screenshots/webgl_volume_cloud.jpg diff --git a/examples/screenshots/webgl_volume_instancing.jpg b/examples/screenshots/webgl_volume_instancing.jpg new file mode 100644 index 00000000000000..9501aa5d5849d2 Binary files /dev/null and b/examples/screenshots/webgl_volume_instancing.jpg differ diff --git a/examples/screenshots/webgl2_volume_perlin.jpg b/examples/screenshots/webgl_volume_perlin.jpg similarity index 100% rename from examples/screenshots/webgl2_volume_perlin.jpg rename to examples/screenshots/webgl_volume_perlin.jpg diff --git a/examples/screenshots/webgl_watch.jpg b/examples/screenshots/webgl_watch.jpg new file mode 100644 index 00000000000000..a7c368fcf6a8c9 Binary files /dev/null and b/examples/screenshots/webgl_watch.jpg differ diff --git a/examples/screenshots/webgl_water.jpg b/examples/screenshots/webgl_water.jpg deleted file mode 100644 index a0a095d429fe55..00000000000000 Binary files a/examples/screenshots/webgl_water.jpg and /dev/null differ diff --git a/examples/screenshots/webgl_water_flowmap.jpg b/examples/screenshots/webgl_water_flowmap.jpg deleted file mode 100644 index 4a7e63ad5ade1a..00000000000000 Binary files a/examples/screenshots/webgl_water_flowmap.jpg and /dev/null differ diff --git a/examples/screenshots/webgpu_animation_retargeting.jpg b/examples/screenshots/webgpu_animation_retargeting.jpg new file mode 100644 index 00000000000000..35fc6e84971680 Binary files /dev/null and b/examples/screenshots/webgpu_animation_retargeting.jpg differ diff --git a/examples/screenshots/webgpu_animation_retargeting_readyplayer.jpg b/examples/screenshots/webgpu_animation_retargeting_readyplayer.jpg new file mode 100644 index 00000000000000..2341bbcfd42135 Binary files /dev/null and b/examples/screenshots/webgpu_animation_retargeting_readyplayer.jpg differ diff --git a/examples/screenshots/webgpu_backdrop.jpg b/examples/screenshots/webgpu_backdrop.jpg index 001fe203e0b94e..4fc02957b204ab 100644 Binary files a/examples/screenshots/webgpu_backdrop.jpg and b/examples/screenshots/webgpu_backdrop.jpg differ diff --git a/examples/screenshots/webgpu_backdrop_area.jpg b/examples/screenshots/webgpu_backdrop_area.jpg new file mode 100644 index 00000000000000..74825f4de301ac Binary files /dev/null and b/examples/screenshots/webgpu_backdrop_area.jpg differ diff --git a/examples/screenshots/webgpu_backdrop_water.jpg b/examples/screenshots/webgpu_backdrop_water.jpg new file mode 100644 index 00000000000000..ebc76f3a816d7f Binary files /dev/null and b/examples/screenshots/webgpu_backdrop_water.jpg differ diff --git a/examples/screenshots/webgpu_camera.jpg b/examples/screenshots/webgpu_camera.jpg new file mode 100644 index 00000000000000..1d38a820ad686d Binary files /dev/null and b/examples/screenshots/webgpu_camera.jpg differ diff --git a/examples/screenshots/webgpu_camera_array.jpg b/examples/screenshots/webgpu_camera_array.jpg new file mode 100644 index 00000000000000..7a0a7108f0e571 Binary files /dev/null and b/examples/screenshots/webgpu_camera_array.jpg differ diff --git a/examples/screenshots/webgpu_camera_logarithmicdepthbuffer.jpg b/examples/screenshots/webgpu_camera_logarithmicdepthbuffer.jpg new file mode 100644 index 00000000000000..a11ed8dbae6059 Binary files /dev/null and b/examples/screenshots/webgpu_camera_logarithmicdepthbuffer.jpg differ diff --git a/examples/screenshots/webgpu_caustics.jpg b/examples/screenshots/webgpu_caustics.jpg new file mode 100644 index 00000000000000..a94c1213719daf Binary files /dev/null and b/examples/screenshots/webgpu_caustics.jpg differ diff --git a/examples/screenshots/webgpu_centroid_sampling.jpg b/examples/screenshots/webgpu_centroid_sampling.jpg new file mode 100644 index 00000000000000..f616798fd633df Binary files /dev/null and b/examples/screenshots/webgpu_centroid_sampling.jpg differ diff --git a/examples/screenshots/webgpu_clearcoat.jpg b/examples/screenshots/webgpu_clearcoat.jpg new file mode 100644 index 00000000000000..f16008c4595639 Binary files /dev/null and b/examples/screenshots/webgpu_clearcoat.jpg differ diff --git a/examples/screenshots/webgpu_clipping.jpg b/examples/screenshots/webgpu_clipping.jpg new file mode 100644 index 00000000000000..bae91f99369ed6 Binary files /dev/null and b/examples/screenshots/webgpu_clipping.jpg differ diff --git a/examples/screenshots/webgpu_compute.jpg b/examples/screenshots/webgpu_compute.jpg deleted file mode 100644 index 001fe203e0b94e..00000000000000 Binary files a/examples/screenshots/webgpu_compute.jpg and /dev/null differ diff --git a/examples/screenshots/webgpu_audio_processing.jpg b/examples/screenshots/webgpu_compute_audio.jpg similarity index 100% rename from examples/screenshots/webgpu_audio_processing.jpg rename to examples/screenshots/webgpu_compute_audio.jpg diff --git a/examples/screenshots/webgpu_compute_birds.jpg b/examples/screenshots/webgpu_compute_birds.jpg new file mode 100644 index 00000000000000..f4277add6a400c Binary files /dev/null and b/examples/screenshots/webgpu_compute_birds.jpg differ diff --git a/examples/screenshots/webgpu_compute_cloth.jpg b/examples/screenshots/webgpu_compute_cloth.jpg new file mode 100644 index 00000000000000..dcdc6eeeb90f69 Binary files /dev/null and b/examples/screenshots/webgpu_compute_cloth.jpg differ diff --git a/examples/screenshots/webgpu_compute_geometry.jpg b/examples/screenshots/webgpu_compute_geometry.jpg new file mode 100644 index 00000000000000..1445f3e869ca70 Binary files /dev/null and b/examples/screenshots/webgpu_compute_geometry.jpg differ diff --git a/examples/screenshots/webgpu_compute_particles.jpg b/examples/screenshots/webgpu_compute_particles.jpg new file mode 100644 index 00000000000000..d2f8e2ad9fe221 Binary files /dev/null and b/examples/screenshots/webgpu_compute_particles.jpg differ diff --git a/examples/screenshots/webgpu_compute_particles_fluid.jpg b/examples/screenshots/webgpu_compute_particles_fluid.jpg new file mode 100644 index 00000000000000..6dec5e5064fa9a Binary files /dev/null and b/examples/screenshots/webgpu_compute_particles_fluid.jpg differ diff --git a/examples/screenshots/webgpu_compute_particles_rain.jpg b/examples/screenshots/webgpu_compute_particles_rain.jpg new file mode 100644 index 00000000000000..64432ef49aa461 Binary files /dev/null and b/examples/screenshots/webgpu_compute_particles_rain.jpg differ diff --git a/examples/screenshots/webgpu_compute_particles_snow.jpg b/examples/screenshots/webgpu_compute_particles_snow.jpg new file mode 100644 index 00000000000000..c3c309625ec2e0 Binary files /dev/null and b/examples/screenshots/webgpu_compute_particles_snow.jpg differ diff --git a/examples/screenshots/webgpu_compute_points.jpg b/examples/screenshots/webgpu_compute_points.jpg new file mode 100644 index 00000000000000..b145f01116bd93 Binary files /dev/null and b/examples/screenshots/webgpu_compute_points.jpg differ diff --git a/examples/screenshots/webgpu_compute_sort_bitonic.jpg b/examples/screenshots/webgpu_compute_sort_bitonic.jpg new file mode 100644 index 00000000000000..a0a4d0c1c21099 Binary files /dev/null and b/examples/screenshots/webgpu_compute_sort_bitonic.jpg differ diff --git a/examples/screenshots/webgpu_compute_texture.jpg b/examples/screenshots/webgpu_compute_texture.jpg new file mode 100644 index 00000000000000..83942f79a4fb43 Binary files /dev/null and b/examples/screenshots/webgpu_compute_texture.jpg differ diff --git a/examples/screenshots/webgpu_compute_texture_pingpong.jpg b/examples/screenshots/webgpu_compute_texture_pingpong.jpg new file mode 100644 index 00000000000000..7aa982376111b6 Binary files /dev/null and b/examples/screenshots/webgpu_compute_texture_pingpong.jpg differ diff --git a/examples/screenshots/webgpu_compute_water.jpg b/examples/screenshots/webgpu_compute_water.jpg new file mode 100644 index 00000000000000..20356c0228cf6a Binary files /dev/null and b/examples/screenshots/webgpu_compute_water.jpg differ diff --git a/examples/screenshots/webgpu_cubemap_adjustments.jpg b/examples/screenshots/webgpu_cubemap_adjustments.jpg index 001fe203e0b94e..2eee2ce988ab50 100644 Binary files a/examples/screenshots/webgpu_cubemap_adjustments.jpg and b/examples/screenshots/webgpu_cubemap_adjustments.jpg differ diff --git a/examples/screenshots/webgpu_cubemap_dynamic.jpg b/examples/screenshots/webgpu_cubemap_dynamic.jpg new file mode 100644 index 00000000000000..87699ddbbf94ef Binary files /dev/null and b/examples/screenshots/webgpu_cubemap_dynamic.jpg differ diff --git a/examples/screenshots/webgpu_cubemap_mix.jpg b/examples/screenshots/webgpu_cubemap_mix.jpg index 001fe203e0b94e..4aebc12faf7f19 100644 Binary files a/examples/screenshots/webgpu_cubemap_mix.jpg and b/examples/screenshots/webgpu_cubemap_mix.jpg differ diff --git a/examples/screenshots/webgpu_custom_fog.jpg b/examples/screenshots/webgpu_custom_fog.jpg new file mode 100644 index 00000000000000..48ada44aff90d9 Binary files /dev/null and b/examples/screenshots/webgpu_custom_fog.jpg differ diff --git a/examples/screenshots/webgpu_custom_fog_background.jpg b/examples/screenshots/webgpu_custom_fog_background.jpg new file mode 100644 index 00000000000000..112656882cc20f Binary files /dev/null and b/examples/screenshots/webgpu_custom_fog_background.jpg differ diff --git a/examples/screenshots/webgpu_depth_texture.jpg b/examples/screenshots/webgpu_depth_texture.jpg index 001fe203e0b94e..27ddbc861bfba4 100644 Binary files a/examples/screenshots/webgpu_depth_texture.jpg and b/examples/screenshots/webgpu_depth_texture.jpg differ diff --git a/examples/screenshots/webgpu_display_stereo.jpg b/examples/screenshots/webgpu_display_stereo.jpg new file mode 100644 index 00000000000000..519f7a46e4f2b0 Binary files /dev/null and b/examples/screenshots/webgpu_display_stereo.jpg differ diff --git a/examples/screenshots/webgpu_equirectangular.jpg b/examples/screenshots/webgpu_equirectangular.jpg index 001fe203e0b94e..7f4d9222bd568c 100644 Binary files a/examples/screenshots/webgpu_equirectangular.jpg and b/examples/screenshots/webgpu_equirectangular.jpg differ diff --git a/examples/screenshots/webgpu_instance_mesh.jpg b/examples/screenshots/webgpu_instance_mesh.jpg index 001fe203e0b94e..0493900b56b675 100644 Binary files a/examples/screenshots/webgpu_instance_mesh.jpg and b/examples/screenshots/webgpu_instance_mesh.jpg differ diff --git a/examples/screenshots/webgpu_instance_path.jpg b/examples/screenshots/webgpu_instance_path.jpg new file mode 100644 index 00000000000000..a4adaf38832dff Binary files /dev/null and b/examples/screenshots/webgpu_instance_path.jpg differ diff --git a/examples/screenshots/webgpu_instance_points.jpg b/examples/screenshots/webgpu_instance_points.jpg new file mode 100644 index 00000000000000..40eca59386dc3a Binary files /dev/null and b/examples/screenshots/webgpu_instance_points.jpg differ diff --git a/examples/screenshots/webgpu_instance_sprites.jpg b/examples/screenshots/webgpu_instance_sprites.jpg new file mode 100644 index 00000000000000..a452698dd728e6 Binary files /dev/null and b/examples/screenshots/webgpu_instance_sprites.jpg differ diff --git a/examples/screenshots/webgpu_instance_uniform.jpg b/examples/screenshots/webgpu_instance_uniform.jpg index 001fe203e0b94e..8b81aafb8cbc43 100644 Binary files a/examples/screenshots/webgpu_instance_uniform.jpg and b/examples/screenshots/webgpu_instance_uniform.jpg differ diff --git a/examples/screenshots/webgpu_instancing_morph.jpg b/examples/screenshots/webgpu_instancing_morph.jpg new file mode 100644 index 00000000000000..1cab133b4271c4 Binary files /dev/null and b/examples/screenshots/webgpu_instancing_morph.jpg differ diff --git a/examples/screenshots/webgpu_layers.jpg b/examples/screenshots/webgpu_layers.jpg new file mode 100644 index 00000000000000..ff272e505c4993 Binary files /dev/null and b/examples/screenshots/webgpu_layers.jpg differ diff --git a/examples/screenshots/webgpu_lensflares.jpg b/examples/screenshots/webgpu_lensflares.jpg new file mode 100644 index 00000000000000..c38d5e14a399ae Binary files /dev/null and b/examples/screenshots/webgpu_lensflares.jpg differ diff --git a/examples/screenshots/webgpu_lightprobe.jpg b/examples/screenshots/webgpu_lightprobe.jpg new file mode 100644 index 00000000000000..0f6b4c95bb5b6f Binary files /dev/null and b/examples/screenshots/webgpu_lightprobe.jpg differ diff --git a/examples/screenshots/webgpu_lightprobe_cubecamera.jpg b/examples/screenshots/webgpu_lightprobe_cubecamera.jpg new file mode 100644 index 00000000000000..5d75a244773958 Binary files /dev/null and b/examples/screenshots/webgpu_lightprobe_cubecamera.jpg differ diff --git a/examples/screenshots/webgpu_lights_custom.jpg b/examples/screenshots/webgpu_lights_custom.jpg index 001fe203e0b94e..ee54fef1add122 100644 Binary files a/examples/screenshots/webgpu_lights_custom.jpg and b/examples/screenshots/webgpu_lights_custom.jpg differ diff --git a/examples/screenshots/webgpu_lights_ies_spotlight.jpg b/examples/screenshots/webgpu_lights_ies_spotlight.jpg index 001fe203e0b94e..492dd1dc4c21d0 100644 Binary files a/examples/screenshots/webgpu_lights_ies_spotlight.jpg and b/examples/screenshots/webgpu_lights_ies_spotlight.jpg differ diff --git a/examples/screenshots/webgpu_lights_phong.jpg b/examples/screenshots/webgpu_lights_phong.jpg index 001fe203e0b94e..53f6bdff66c387 100644 Binary files a/examples/screenshots/webgpu_lights_phong.jpg and b/examples/screenshots/webgpu_lights_phong.jpg differ diff --git a/examples/screenshots/webgpu_lights_physical.jpg b/examples/screenshots/webgpu_lights_physical.jpg new file mode 100644 index 00000000000000..b57683e1e8f67c Binary files /dev/null and b/examples/screenshots/webgpu_lights_physical.jpg differ diff --git a/examples/screenshots/webgpu_lights_pointlights.jpg b/examples/screenshots/webgpu_lights_pointlights.jpg new file mode 100644 index 00000000000000..07e1fb5e23be4f Binary files /dev/null and b/examples/screenshots/webgpu_lights_pointlights.jpg differ diff --git a/examples/screenshots/webgpu_lights_projector.jpg b/examples/screenshots/webgpu_lights_projector.jpg new file mode 100644 index 00000000000000..68afd9f22eec46 Binary files /dev/null and b/examples/screenshots/webgpu_lights_projector.jpg differ diff --git a/examples/screenshots/webgpu_lights_rectarealight.jpg b/examples/screenshots/webgpu_lights_rectarealight.jpg new file mode 100644 index 00000000000000..0e5e4854b049ea Binary files /dev/null and b/examples/screenshots/webgpu_lights_rectarealight.jpg differ diff --git a/examples/screenshots/webgpu_lights_selective.jpg b/examples/screenshots/webgpu_lights_selective.jpg index 001fe203e0b94e..d5b13adc6576e7 100644 Binary files a/examples/screenshots/webgpu_lights_selective.jpg and b/examples/screenshots/webgpu_lights_selective.jpg differ diff --git a/examples/screenshots/webgpu_lights_spotlight.jpg b/examples/screenshots/webgpu_lights_spotlight.jpg new file mode 100644 index 00000000000000..6af6c8d65ee615 Binary files /dev/null and b/examples/screenshots/webgpu_lights_spotlight.jpg differ diff --git a/examples/screenshots/webgpu_lights_tiled.jpg b/examples/screenshots/webgpu_lights_tiled.jpg new file mode 100644 index 00000000000000..44fbbdc24612b8 Binary files /dev/null and b/examples/screenshots/webgpu_lights_tiled.jpg differ diff --git a/examples/screenshots/webgpu_lines_fat.jpg b/examples/screenshots/webgpu_lines_fat.jpg new file mode 100644 index 00000000000000..c71027ecb8fbb4 Binary files /dev/null and b/examples/screenshots/webgpu_lines_fat.jpg differ diff --git a/examples/screenshots/webgpu_lines_fat_raycasting.jpg b/examples/screenshots/webgpu_lines_fat_raycasting.jpg new file mode 100644 index 00000000000000..fc0c3ff23a3ae3 Binary files /dev/null and b/examples/screenshots/webgpu_lines_fat_raycasting.jpg differ diff --git a/examples/screenshots/webgpu_lines_fat_wireframe.jpg b/examples/screenshots/webgpu_lines_fat_wireframe.jpg new file mode 100644 index 00000000000000..e08c9ab64a96bc Binary files /dev/null and b/examples/screenshots/webgpu_lines_fat_wireframe.jpg differ diff --git a/examples/screenshots/webgpu_loader_gltf.jpg b/examples/screenshots/webgpu_loader_gltf.jpg index 001fe203e0b94e..cc500be3e326ab 100644 Binary files a/examples/screenshots/webgpu_loader_gltf.jpg and b/examples/screenshots/webgpu_loader_gltf.jpg differ diff --git a/examples/screenshots/webgpu_loader_gltf_anisotropy.jpg b/examples/screenshots/webgpu_loader_gltf_anisotropy.jpg new file mode 100644 index 00000000000000..51b0bea6ae731a Binary files /dev/null and b/examples/screenshots/webgpu_loader_gltf_anisotropy.jpg differ diff --git a/examples/screenshots/webgpu_loader_gltf_compressed.jpg b/examples/screenshots/webgpu_loader_gltf_compressed.jpg index 001fe203e0b94e..fed0e3507f0e43 100644 Binary files a/examples/screenshots/webgpu_loader_gltf_compressed.jpg and b/examples/screenshots/webgpu_loader_gltf_compressed.jpg differ diff --git a/examples/screenshots/webgpu_loader_gltf_dispersion.jpg b/examples/screenshots/webgpu_loader_gltf_dispersion.jpg new file mode 100644 index 00000000000000..beb231e46f5a47 Binary files /dev/null and b/examples/screenshots/webgpu_loader_gltf_dispersion.jpg differ diff --git a/examples/screenshots/webgpu_loader_gltf_iridescence.jpg b/examples/screenshots/webgpu_loader_gltf_iridescence.jpg new file mode 100644 index 00000000000000..b428169290518c Binary files /dev/null and b/examples/screenshots/webgpu_loader_gltf_iridescence.jpg differ diff --git a/examples/screenshots/webgpu_loader_gltf_sheen.jpg b/examples/screenshots/webgpu_loader_gltf_sheen.jpg new file mode 100644 index 00000000000000..19dec65317c284 Binary files /dev/null and b/examples/screenshots/webgpu_loader_gltf_sheen.jpg differ diff --git a/examples/screenshots/webgpu_loader_gltf_transmission.jpg b/examples/screenshots/webgpu_loader_gltf_transmission.jpg new file mode 100644 index 00000000000000..d471479d3d2820 Binary files /dev/null and b/examples/screenshots/webgpu_loader_gltf_transmission.jpg differ diff --git a/examples/screenshots/webgpu_loader_materialx.jpg b/examples/screenshots/webgpu_loader_materialx.jpg new file mode 100644 index 00000000000000..4d47eb15222a27 Binary files /dev/null and b/examples/screenshots/webgpu_loader_materialx.jpg differ diff --git a/examples/screenshots/webgpu_materials.jpg b/examples/screenshots/webgpu_materials.jpg index 001fe203e0b94e..46b2cfd42d5ca9 100644 Binary files a/examples/screenshots/webgpu_materials.jpg and b/examples/screenshots/webgpu_materials.jpg differ diff --git a/examples/screenshots/webgpu_materials_alphahash.jpg b/examples/screenshots/webgpu_materials_alphahash.jpg new file mode 100644 index 00000000000000..b352584958005b Binary files /dev/null and b/examples/screenshots/webgpu_materials_alphahash.jpg differ diff --git a/examples/screenshots/webgpu_materials_arrays.jpg b/examples/screenshots/webgpu_materials_arrays.jpg new file mode 100644 index 00000000000000..db7a27edfe60a1 Binary files /dev/null and b/examples/screenshots/webgpu_materials_arrays.jpg differ diff --git a/examples/screenshots/webgpu_materials_basic.jpg b/examples/screenshots/webgpu_materials_basic.jpg new file mode 100644 index 00000000000000..381aceef9a3bb5 Binary files /dev/null and b/examples/screenshots/webgpu_materials_basic.jpg differ diff --git a/examples/screenshots/webgpu_materials_displacementmap.jpg b/examples/screenshots/webgpu_materials_displacementmap.jpg new file mode 100644 index 00000000000000..0aca26a24f642c Binary files /dev/null and b/examples/screenshots/webgpu_materials_displacementmap.jpg differ diff --git a/examples/screenshots/webgpu_materials_envmaps.jpg b/examples/screenshots/webgpu_materials_envmaps.jpg new file mode 100644 index 00000000000000..5ac9992d916821 Binary files /dev/null and b/examples/screenshots/webgpu_materials_envmaps.jpg differ diff --git a/examples/screenshots/webgpu_materials_envmaps_bpcem.jpg b/examples/screenshots/webgpu_materials_envmaps_bpcem.jpg new file mode 100644 index 00000000000000..e8c829440140c8 Binary files /dev/null and b/examples/screenshots/webgpu_materials_envmaps_bpcem.jpg differ diff --git a/examples/screenshots/webgpu_materials_lightmap.jpg b/examples/screenshots/webgpu_materials_lightmap.jpg new file mode 100644 index 00000000000000..c28ad4819d70d8 Binary files /dev/null and b/examples/screenshots/webgpu_materials_lightmap.jpg differ diff --git a/examples/screenshots/webgpu_materials_matcap.jpg b/examples/screenshots/webgpu_materials_matcap.jpg new file mode 100644 index 00000000000000..088a1f0f091d5d Binary files /dev/null and b/examples/screenshots/webgpu_materials_matcap.jpg differ diff --git a/examples/screenshots/webgpu_materials_sss.jpg b/examples/screenshots/webgpu_materials_sss.jpg new file mode 100644 index 00000000000000..74a96b8da25bcb Binary files /dev/null and b/examples/screenshots/webgpu_materials_sss.jpg differ diff --git a/examples/screenshots/webgpu_materials_toon.jpg b/examples/screenshots/webgpu_materials_toon.jpg new file mode 100644 index 00000000000000..3171f13cef09bc Binary files /dev/null and b/examples/screenshots/webgpu_materials_toon.jpg differ diff --git a/examples/screenshots/webgpu_materials_transmission.jpg b/examples/screenshots/webgpu_materials_transmission.jpg new file mode 100644 index 00000000000000..3f461b7fe3cf88 Binary files /dev/null and b/examples/screenshots/webgpu_materials_transmission.jpg differ diff --git a/examples/screenshots/webgpu_materials_video.jpg b/examples/screenshots/webgpu_materials_video.jpg index 001fe203e0b94e..6debad1f10cd16 100644 Binary files a/examples/screenshots/webgpu_materials_video.jpg and b/examples/screenshots/webgpu_materials_video.jpg differ diff --git a/examples/screenshots/webgpu_materialx_noise.jpg b/examples/screenshots/webgpu_materialx_noise.jpg new file mode 100644 index 00000000000000..cd78c924f1b933 Binary files /dev/null and b/examples/screenshots/webgpu_materialx_noise.jpg differ diff --git a/examples/screenshots/webgpu_mesh_batch.jpg b/examples/screenshots/webgpu_mesh_batch.jpg new file mode 100644 index 00000000000000..f818285fa08813 Binary files /dev/null and b/examples/screenshots/webgpu_mesh_batch.jpg differ diff --git a/examples/screenshots/webgpu_mirror.jpg b/examples/screenshots/webgpu_mirror.jpg new file mode 100644 index 00000000000000..57325d0be92519 Binary files /dev/null and b/examples/screenshots/webgpu_mirror.jpg differ diff --git a/examples/screenshots/webgpu_modifier_curve.jpg b/examples/screenshots/webgpu_modifier_curve.jpg new file mode 100644 index 00000000000000..08490ac0598e33 Binary files /dev/null and b/examples/screenshots/webgpu_modifier_curve.jpg differ diff --git a/examples/screenshots/webgpu_morphtargets.jpg b/examples/screenshots/webgpu_morphtargets.jpg new file mode 100644 index 00000000000000..3f57aacce49a9d Binary files /dev/null and b/examples/screenshots/webgpu_morphtargets.jpg differ diff --git a/examples/screenshots/webgpu_morphtargets_face.jpg b/examples/screenshots/webgpu_morphtargets_face.jpg new file mode 100644 index 00000000000000..44edc24ad692ce Binary files /dev/null and b/examples/screenshots/webgpu_morphtargets_face.jpg differ diff --git a/examples/screenshots/webgpu_mrt.jpg b/examples/screenshots/webgpu_mrt.jpg new file mode 100644 index 00000000000000..2808d5d77f4d6b Binary files /dev/null and b/examples/screenshots/webgpu_mrt.jpg differ diff --git a/examples/screenshots/webgpu_mrt_mask.jpg b/examples/screenshots/webgpu_mrt_mask.jpg new file mode 100644 index 00000000000000..fe39f7e96ae72a Binary files /dev/null and b/examples/screenshots/webgpu_mrt_mask.jpg differ diff --git a/examples/screenshots/webgpu_multiple_rendertargets.jpg b/examples/screenshots/webgpu_multiple_rendertargets.jpg new file mode 100644 index 00000000000000..416c3f145f0aa8 Binary files /dev/null and b/examples/screenshots/webgpu_multiple_rendertargets.jpg differ diff --git a/examples/screenshots/webgpu_multiple_rendertargets_readback.jpg b/examples/screenshots/webgpu_multiple_rendertargets_readback.jpg new file mode 100644 index 00000000000000..b77ac78a9f22ae Binary files /dev/null and b/examples/screenshots/webgpu_multiple_rendertargets_readback.jpg differ diff --git a/examples/screenshots/webgpu_multisampled_renderbuffers.jpg b/examples/screenshots/webgpu_multisampled_renderbuffers.jpg new file mode 100644 index 00000000000000..c0b4ae00ba56b2 Binary files /dev/null and b/examples/screenshots/webgpu_multisampled_renderbuffers.jpg differ diff --git a/examples/screenshots/webgpu_occlusion.jpg b/examples/screenshots/webgpu_occlusion.jpg new file mode 100644 index 00000000000000..91d26d852ef78f Binary files /dev/null and b/examples/screenshots/webgpu_occlusion.jpg differ diff --git a/examples/screenshots/webgpu_ocean.jpg b/examples/screenshots/webgpu_ocean.jpg new file mode 100644 index 00000000000000..7b06285a836cf5 Binary files /dev/null and b/examples/screenshots/webgpu_ocean.jpg differ diff --git a/examples/screenshots/webgpu_parallax_uv.jpg b/examples/screenshots/webgpu_parallax_uv.jpg new file mode 100644 index 00000000000000..521f0038bb3784 Binary files /dev/null and b/examples/screenshots/webgpu_parallax_uv.jpg differ diff --git a/examples/screenshots/webgpu_particles.jpg b/examples/screenshots/webgpu_particles.jpg index 001fe203e0b94e..e2b3a8a8f84d59 100644 Binary files a/examples/screenshots/webgpu_particles.jpg and b/examples/screenshots/webgpu_particles.jpg differ diff --git a/examples/screenshots/webgpu_performance.jpg b/examples/screenshots/webgpu_performance.jpg new file mode 100644 index 00000000000000..64b4fee8619cff Binary files /dev/null and b/examples/screenshots/webgpu_performance.jpg differ diff --git a/examples/screenshots/webgpu_performance_renderbundle.jpg b/examples/screenshots/webgpu_performance_renderbundle.jpg new file mode 100644 index 00000000000000..99e385ec600a37 Binary files /dev/null and b/examples/screenshots/webgpu_performance_renderbundle.jpg differ diff --git a/examples/screenshots/webgpu_pmrem_cubemap.jpg b/examples/screenshots/webgpu_pmrem_cubemap.jpg new file mode 100644 index 00000000000000..d27abade4c575f Binary files /dev/null and b/examples/screenshots/webgpu_pmrem_cubemap.jpg differ diff --git a/examples/screenshots/webgpu_pmrem_equirectangular.jpg b/examples/screenshots/webgpu_pmrem_equirectangular.jpg new file mode 100644 index 00000000000000..ceebb00a68dce7 Binary files /dev/null and b/examples/screenshots/webgpu_pmrem_equirectangular.jpg differ diff --git a/examples/screenshots/webgpu_pmrem_scene.jpg b/examples/screenshots/webgpu_pmrem_scene.jpg new file mode 100644 index 00000000000000..7701b8aafd7160 Binary files /dev/null and b/examples/screenshots/webgpu_pmrem_scene.jpg differ diff --git a/examples/screenshots/webgpu_portal.jpg b/examples/screenshots/webgpu_portal.jpg new file mode 100644 index 00000000000000..7c2e55acde37bd Binary files /dev/null and b/examples/screenshots/webgpu_portal.jpg differ diff --git a/examples/screenshots/webgpu_postprocessing.jpg b/examples/screenshots/webgpu_postprocessing.jpg new file mode 100644 index 00000000000000..305c5bb4bd366d Binary files /dev/null and b/examples/screenshots/webgpu_postprocessing.jpg differ diff --git a/examples/screenshots/webgpu_postprocessing_3dlut.jpg b/examples/screenshots/webgpu_postprocessing_3dlut.jpg new file mode 100644 index 00000000000000..be3840adbd1f7d Binary files /dev/null and b/examples/screenshots/webgpu_postprocessing_3dlut.jpg differ diff --git a/examples/screenshots/webgpu_postprocessing_afterimage.jpg b/examples/screenshots/webgpu_postprocessing_afterimage.jpg new file mode 100644 index 00000000000000..37737081ff54cd Binary files /dev/null and b/examples/screenshots/webgpu_postprocessing_afterimage.jpg differ diff --git a/examples/screenshots/webgpu_postprocessing_anamorphic.jpg b/examples/screenshots/webgpu_postprocessing_anamorphic.jpg new file mode 100644 index 00000000000000..557896f199ce4a Binary files /dev/null and b/examples/screenshots/webgpu_postprocessing_anamorphic.jpg differ diff --git a/examples/screenshots/webgpu_postprocessing_ao.jpg b/examples/screenshots/webgpu_postprocessing_ao.jpg new file mode 100644 index 00000000000000..c2ba93155e9550 Binary files /dev/null and b/examples/screenshots/webgpu_postprocessing_ao.jpg differ diff --git a/examples/screenshots/webgpu_postprocessing_bloom.jpg b/examples/screenshots/webgpu_postprocessing_bloom.jpg new file mode 100644 index 00000000000000..db80eb702cdbf2 Binary files /dev/null and b/examples/screenshots/webgpu_postprocessing_bloom.jpg differ diff --git a/examples/screenshots/webgpu_postprocessing_bloom_emissive.jpg b/examples/screenshots/webgpu_postprocessing_bloom_emissive.jpg new file mode 100644 index 00000000000000..fd4158adac9518 Binary files /dev/null and b/examples/screenshots/webgpu_postprocessing_bloom_emissive.jpg differ diff --git a/examples/screenshots/webgpu_postprocessing_bloom_selective.jpg b/examples/screenshots/webgpu_postprocessing_bloom_selective.jpg new file mode 100644 index 00000000000000..904cc6050cd79f Binary files /dev/null and b/examples/screenshots/webgpu_postprocessing_bloom_selective.jpg differ diff --git a/examples/screenshots/webgpu_postprocessing_ca.jpg b/examples/screenshots/webgpu_postprocessing_ca.jpg new file mode 100644 index 00000000000000..b3186b4a5fe6ab Binary files /dev/null and b/examples/screenshots/webgpu_postprocessing_ca.jpg differ diff --git a/examples/screenshots/webgpu_postprocessing_difference.jpg b/examples/screenshots/webgpu_postprocessing_difference.jpg new file mode 100644 index 00000000000000..3ac2f8057c8136 Binary files /dev/null and b/examples/screenshots/webgpu_postprocessing_difference.jpg differ diff --git a/examples/screenshots/webgpu_postprocessing_dof.jpg b/examples/screenshots/webgpu_postprocessing_dof.jpg new file mode 100644 index 00000000000000..f26163941a27a6 Binary files /dev/null and b/examples/screenshots/webgpu_postprocessing_dof.jpg differ diff --git a/examples/screenshots/webgpu_postprocessing_fxaa.jpg b/examples/screenshots/webgpu_postprocessing_fxaa.jpg new file mode 100644 index 00000000000000..7223a2ac004889 Binary files /dev/null and b/examples/screenshots/webgpu_postprocessing_fxaa.jpg differ diff --git a/examples/screenshots/webgpu_postprocessing_lensflare.jpg b/examples/screenshots/webgpu_postprocessing_lensflare.jpg new file mode 100644 index 00000000000000..162a19ade1a2fe Binary files /dev/null and b/examples/screenshots/webgpu_postprocessing_lensflare.jpg differ diff --git a/examples/screenshots/webgpu_postprocessing_masking.jpg b/examples/screenshots/webgpu_postprocessing_masking.jpg new file mode 100644 index 00000000000000..b8b8c1c4dc32dd Binary files /dev/null and b/examples/screenshots/webgpu_postprocessing_masking.jpg differ diff --git a/examples/screenshots/webgpu_postprocessing_motion_blur.jpg b/examples/screenshots/webgpu_postprocessing_motion_blur.jpg new file mode 100644 index 00000000000000..56ca1afabde396 Binary files /dev/null and b/examples/screenshots/webgpu_postprocessing_motion_blur.jpg differ diff --git a/examples/screenshots/webgpu_postprocessing_outline.jpg b/examples/screenshots/webgpu_postprocessing_outline.jpg new file mode 100644 index 00000000000000..e4664ecfc12ccc Binary files /dev/null and b/examples/screenshots/webgpu_postprocessing_outline.jpg differ diff --git a/examples/screenshots/webgpu_postprocessing_pixel.jpg b/examples/screenshots/webgpu_postprocessing_pixel.jpg new file mode 100644 index 00000000000000..b5672007401aca Binary files /dev/null and b/examples/screenshots/webgpu_postprocessing_pixel.jpg differ diff --git a/examples/screenshots/webgpu_postprocessing_smaa.jpg b/examples/screenshots/webgpu_postprocessing_smaa.jpg new file mode 100644 index 00000000000000..243d90cc5ddc66 Binary files /dev/null and b/examples/screenshots/webgpu_postprocessing_smaa.jpg differ diff --git a/examples/screenshots/webgpu_postprocessing_sobel.jpg b/examples/screenshots/webgpu_postprocessing_sobel.jpg new file mode 100644 index 00000000000000..e2884b83c7a702 Binary files /dev/null and b/examples/screenshots/webgpu_postprocessing_sobel.jpg differ diff --git a/examples/screenshots/webgpu_postprocessing_ssaa.jpg b/examples/screenshots/webgpu_postprocessing_ssaa.jpg new file mode 100644 index 00000000000000..157ce6197305a5 Binary files /dev/null and b/examples/screenshots/webgpu_postprocessing_ssaa.jpg differ diff --git a/examples/screenshots/webgpu_postprocessing_ssr.jpg b/examples/screenshots/webgpu_postprocessing_ssr.jpg new file mode 100644 index 00000000000000..89212457efb475 Binary files /dev/null and b/examples/screenshots/webgpu_postprocessing_ssr.jpg differ diff --git a/examples/screenshots/webgpu_postprocessing_traa.jpg b/examples/screenshots/webgpu_postprocessing_traa.jpg new file mode 100644 index 00000000000000..bdf0e6a599e054 Binary files /dev/null and b/examples/screenshots/webgpu_postprocessing_traa.jpg differ diff --git a/examples/screenshots/webgpu_postprocessing_transition.jpg b/examples/screenshots/webgpu_postprocessing_transition.jpg new file mode 100644 index 00000000000000..1e0cccd8471033 Binary files /dev/null and b/examples/screenshots/webgpu_postprocessing_transition.jpg differ diff --git a/examples/screenshots/webgpu_procedural_texture.jpg b/examples/screenshots/webgpu_procedural_texture.jpg new file mode 100644 index 00000000000000..69bd3d57f1c09a Binary files /dev/null and b/examples/screenshots/webgpu_procedural_texture.jpg differ diff --git a/examples/screenshots/webgpu_reflection.jpg b/examples/screenshots/webgpu_reflection.jpg new file mode 100644 index 00000000000000..173ca740d4d65a Binary files /dev/null and b/examples/screenshots/webgpu_reflection.jpg differ diff --git a/examples/screenshots/webgpu_reflection_blurred.jpg b/examples/screenshots/webgpu_reflection_blurred.jpg new file mode 100644 index 00000000000000..6b2d61d05dd4d9 Binary files /dev/null and b/examples/screenshots/webgpu_reflection_blurred.jpg differ diff --git a/examples/screenshots/webgpu_reflection_roughness.jpg b/examples/screenshots/webgpu_reflection_roughness.jpg new file mode 100644 index 00000000000000..6c2aaf18a9126c Binary files /dev/null and b/examples/screenshots/webgpu_reflection_roughness.jpg differ diff --git a/examples/screenshots/webgpu_refraction.jpg b/examples/screenshots/webgpu_refraction.jpg new file mode 100644 index 00000000000000..4736e611c2c54c Binary files /dev/null and b/examples/screenshots/webgpu_refraction.jpg differ diff --git a/examples/screenshots/webgpu_rendertarget_2d-array_3d.jpg b/examples/screenshots/webgpu_rendertarget_2d-array_3d.jpg new file mode 100644 index 00000000000000..da9ddafedad849 Binary files /dev/null and b/examples/screenshots/webgpu_rendertarget_2d-array_3d.jpg differ diff --git a/examples/screenshots/webgpu_rtt.jpg b/examples/screenshots/webgpu_rtt.jpg index 001fe203e0b94e..af309ad882b452 100644 Binary files a/examples/screenshots/webgpu_rtt.jpg and b/examples/screenshots/webgpu_rtt.jpg differ diff --git a/examples/screenshots/webgpu_sandbox.jpg b/examples/screenshots/webgpu_sandbox.jpg index 001fe203e0b94e..5b795e29c778ce 100644 Binary files a/examples/screenshots/webgpu_sandbox.jpg and b/examples/screenshots/webgpu_sandbox.jpg differ diff --git a/examples/screenshots/webgpu_shadertoy.jpg b/examples/screenshots/webgpu_shadertoy.jpg new file mode 100644 index 00000000000000..722a04e92e9d66 Binary files /dev/null and b/examples/screenshots/webgpu_shadertoy.jpg differ diff --git a/examples/screenshots/webgpu_shadowmap.jpg b/examples/screenshots/webgpu_shadowmap.jpg index 001fe203e0b94e..f714932bee1164 100644 Binary files a/examples/screenshots/webgpu_shadowmap.jpg and b/examples/screenshots/webgpu_shadowmap.jpg differ diff --git a/examples/screenshots/webgpu_shadowmap_array.jpg b/examples/screenshots/webgpu_shadowmap_array.jpg new file mode 100644 index 00000000000000..7daca011a27f55 Binary files /dev/null and b/examples/screenshots/webgpu_shadowmap_array.jpg differ diff --git a/examples/screenshots/webgpu_shadowmap_csm.jpg b/examples/screenshots/webgpu_shadowmap_csm.jpg new file mode 100644 index 00000000000000..1dc458c8b4959a Binary files /dev/null and b/examples/screenshots/webgpu_shadowmap_csm.jpg differ diff --git a/examples/screenshots/webgpu_shadowmap_opacity.jpg b/examples/screenshots/webgpu_shadowmap_opacity.jpg new file mode 100644 index 00000000000000..a22c9cf8f02c78 Binary files /dev/null and b/examples/screenshots/webgpu_shadowmap_opacity.jpg differ diff --git a/examples/screenshots/webgpu_shadowmap_progressive.jpg b/examples/screenshots/webgpu_shadowmap_progressive.jpg new file mode 100644 index 00000000000000..7ee3428132d3dd Binary files /dev/null and b/examples/screenshots/webgpu_shadowmap_progressive.jpg differ diff --git a/examples/screenshots/webgpu_shadowmap_vsm.jpg b/examples/screenshots/webgpu_shadowmap_vsm.jpg new file mode 100644 index 00000000000000..f0ee504b79781f Binary files /dev/null and b/examples/screenshots/webgpu_shadowmap_vsm.jpg differ diff --git a/examples/screenshots/webgpu_skinning.jpg b/examples/screenshots/webgpu_skinning.jpg index 001fe203e0b94e..4557d75e8a83ed 100644 Binary files a/examples/screenshots/webgpu_skinning.jpg and b/examples/screenshots/webgpu_skinning.jpg differ diff --git a/examples/screenshots/webgpu_skinning_instancing.jpg b/examples/screenshots/webgpu_skinning_instancing.jpg index 001fe203e0b94e..3b535c109a0222 100644 Binary files a/examples/screenshots/webgpu_skinning_instancing.jpg and b/examples/screenshots/webgpu_skinning_instancing.jpg differ diff --git a/examples/screenshots/webgpu_skinning_points.jpg b/examples/screenshots/webgpu_skinning_points.jpg index 001fe203e0b94e..7c6f4378a10400 100644 Binary files a/examples/screenshots/webgpu_skinning_points.jpg and b/examples/screenshots/webgpu_skinning_points.jpg differ diff --git a/examples/screenshots/webgpu_sky.jpg b/examples/screenshots/webgpu_sky.jpg new file mode 100644 index 00000000000000..61b045e484ffbf Binary files /dev/null and b/examples/screenshots/webgpu_sky.jpg differ diff --git a/examples/screenshots/webgpu_sprites.jpg b/examples/screenshots/webgpu_sprites.jpg index 001fe203e0b94e..324931b17e8086 100644 Binary files a/examples/screenshots/webgpu_sprites.jpg and b/examples/screenshots/webgpu_sprites.jpg differ diff --git a/examples/screenshots/webgpu_storage_buffer.jpg b/examples/screenshots/webgpu_storage_buffer.jpg new file mode 100644 index 00000000000000..725d5c7323efe6 Binary files /dev/null and b/examples/screenshots/webgpu_storage_buffer.jpg differ diff --git a/examples/screenshots/webgpu_struct_drawindirect.jpg b/examples/screenshots/webgpu_struct_drawindirect.jpg new file mode 100644 index 00000000000000..58ceae7f0eb0a5 Binary files /dev/null and b/examples/screenshots/webgpu_struct_drawindirect.jpg differ diff --git a/examples/screenshots/webgpu_texturegrad.jpg b/examples/screenshots/webgpu_texturegrad.jpg new file mode 100644 index 00000000000000..bb15abb82c2fd7 Binary files /dev/null and b/examples/screenshots/webgpu_texturegrad.jpg differ diff --git a/examples/screenshots/webgpu_textures_2d-array.jpg b/examples/screenshots/webgpu_textures_2d-array.jpg new file mode 100644 index 00000000000000..5bd8e2f8fed8f5 Binary files /dev/null and b/examples/screenshots/webgpu_textures_2d-array.jpg differ diff --git a/examples/screenshots/webgpu_textures_2d-array_compressed.jpg b/examples/screenshots/webgpu_textures_2d-array_compressed.jpg new file mode 100644 index 00000000000000..c92bba94c8d2d4 Binary files /dev/null and b/examples/screenshots/webgpu_textures_2d-array_compressed.jpg differ diff --git a/examples/screenshots/webgpu_textures_anisotropy.jpg b/examples/screenshots/webgpu_textures_anisotropy.jpg new file mode 100644 index 00000000000000..36adfce2a0083e Binary files /dev/null and b/examples/screenshots/webgpu_textures_anisotropy.jpg differ diff --git a/examples/screenshots/webgpu_textures_partialupdate.jpg b/examples/screenshots/webgpu_textures_partialupdate.jpg new file mode 100644 index 00000000000000..bc74311af102b4 Binary files /dev/null and b/examples/screenshots/webgpu_textures_partialupdate.jpg differ diff --git a/examples/screenshots/webgpu_tonemapping.jpg b/examples/screenshots/webgpu_tonemapping.jpg new file mode 100644 index 00000000000000..d46bcbb6f6ed2b Binary files /dev/null and b/examples/screenshots/webgpu_tonemapping.jpg differ diff --git a/examples/screenshots/webgpu_tsl_angular_slicing.jpg b/examples/screenshots/webgpu_tsl_angular_slicing.jpg new file mode 100644 index 00000000000000..a1706e9de4a160 Binary files /dev/null and b/examples/screenshots/webgpu_tsl_angular_slicing.jpg differ diff --git a/examples/screenshots/webgpu_tsl_compute_attractors_particles.jpg b/examples/screenshots/webgpu_tsl_compute_attractors_particles.jpg new file mode 100644 index 00000000000000..67c9ce3c0fa929 Binary files /dev/null and b/examples/screenshots/webgpu_tsl_compute_attractors_particles.jpg differ diff --git a/examples/screenshots/webgpu_tsl_earth.jpg b/examples/screenshots/webgpu_tsl_earth.jpg new file mode 100644 index 00000000000000..cc0be772c6bbfe Binary files /dev/null and b/examples/screenshots/webgpu_tsl_earth.jpg differ diff --git a/examples/screenshots/webgpu_tsl_editor.jpg b/examples/screenshots/webgpu_tsl_editor.jpg new file mode 100644 index 00000000000000..bf39078fbd9008 Binary files /dev/null and b/examples/screenshots/webgpu_tsl_editor.jpg differ diff --git a/examples/screenshots/webgpu_tsl_galaxy.jpg b/examples/screenshots/webgpu_tsl_galaxy.jpg new file mode 100644 index 00000000000000..5e759a1fdbdb58 Binary files /dev/null and b/examples/screenshots/webgpu_tsl_galaxy.jpg differ diff --git a/examples/screenshots/webgpu_tsl_halftone.jpg b/examples/screenshots/webgpu_tsl_halftone.jpg new file mode 100644 index 00000000000000..15f452fa8ce367 Binary files /dev/null and b/examples/screenshots/webgpu_tsl_halftone.jpg differ diff --git a/examples/screenshots/webgpu_tsl_interoperability.jpg b/examples/screenshots/webgpu_tsl_interoperability.jpg new file mode 100644 index 00000000000000..7beccd938d84b9 Binary files /dev/null and b/examples/screenshots/webgpu_tsl_interoperability.jpg differ diff --git a/examples/screenshots/webgpu_tsl_procedural_terrain.jpg b/examples/screenshots/webgpu_tsl_procedural_terrain.jpg new file mode 100644 index 00000000000000..0dec428e6b8165 Binary files /dev/null and b/examples/screenshots/webgpu_tsl_procedural_terrain.jpg differ diff --git a/examples/screenshots/webgpu_tsl_raging_sea.jpg b/examples/screenshots/webgpu_tsl_raging_sea.jpg new file mode 100644 index 00000000000000..4a1a4ee31319f2 Binary files /dev/null and b/examples/screenshots/webgpu_tsl_raging_sea.jpg differ diff --git a/examples/screenshots/webgpu_tsl_transpiler.jpg b/examples/screenshots/webgpu_tsl_transpiler.jpg new file mode 100644 index 00000000000000..80991d4400deee Binary files /dev/null and b/examples/screenshots/webgpu_tsl_transpiler.jpg differ diff --git a/examples/screenshots/webgpu_tsl_vfx_flames.jpg b/examples/screenshots/webgpu_tsl_vfx_flames.jpg new file mode 100644 index 00000000000000..e779f41d2eb202 Binary files /dev/null and b/examples/screenshots/webgpu_tsl_vfx_flames.jpg differ diff --git a/examples/screenshots/webgpu_tsl_vfx_linkedparticles.jpg b/examples/screenshots/webgpu_tsl_vfx_linkedparticles.jpg new file mode 100644 index 00000000000000..2060dda5823963 Binary files /dev/null and b/examples/screenshots/webgpu_tsl_vfx_linkedparticles.jpg differ diff --git a/examples/screenshots/webgpu_tsl_vfx_tornado.jpg b/examples/screenshots/webgpu_tsl_vfx_tornado.jpg new file mode 100644 index 00000000000000..3e331470efb9f2 Binary files /dev/null and b/examples/screenshots/webgpu_tsl_vfx_tornado.jpg differ diff --git a/examples/screenshots/webgpu_video_frame.jpg b/examples/screenshots/webgpu_video_frame.jpg new file mode 100644 index 00000000000000..0f418a4bdb1013 Binary files /dev/null and b/examples/screenshots/webgpu_video_frame.jpg differ diff --git a/examples/screenshots/webgpu_video_panorama.jpg b/examples/screenshots/webgpu_video_panorama.jpg new file mode 100644 index 00000000000000..ffb6b3e8cd42d9 Binary files /dev/null and b/examples/screenshots/webgpu_video_panorama.jpg differ diff --git a/examples/screenshots/webgpu_volume_caustics.jpg b/examples/screenshots/webgpu_volume_caustics.jpg new file mode 100644 index 00000000000000..c179017aa17c62 Binary files /dev/null and b/examples/screenshots/webgpu_volume_caustics.jpg differ diff --git a/examples/screenshots/webgpu_volume_cloud.jpg b/examples/screenshots/webgpu_volume_cloud.jpg new file mode 100644 index 00000000000000..b1abafb33263e6 Binary files /dev/null and b/examples/screenshots/webgpu_volume_cloud.jpg differ diff --git a/examples/screenshots/webgpu_volume_lighting.jpg b/examples/screenshots/webgpu_volume_lighting.jpg new file mode 100644 index 00000000000000..fe79b00ad19627 Binary files /dev/null and b/examples/screenshots/webgpu_volume_lighting.jpg differ diff --git a/examples/screenshots/webgpu_volume_lighting_rectarea.jpg b/examples/screenshots/webgpu_volume_lighting_rectarea.jpg new file mode 100644 index 00000000000000..be3a8dbb24ffa6 Binary files /dev/null and b/examples/screenshots/webgpu_volume_lighting_rectarea.jpg differ diff --git a/examples/screenshots/webgpu_volume_perlin.jpg b/examples/screenshots/webgpu_volume_perlin.jpg new file mode 100644 index 00000000000000..e7f5380a7f5ae4 Binary files /dev/null and b/examples/screenshots/webgpu_volume_perlin.jpg differ diff --git a/examples/screenshots/webgpu_water.jpg b/examples/screenshots/webgpu_water.jpg new file mode 100644 index 00000000000000..d106243d9f59a4 Binary files /dev/null and b/examples/screenshots/webgpu_water.jpg differ diff --git a/examples/screenshots/webgpu_xr_cubes.jpg b/examples/screenshots/webgpu_xr_cubes.jpg new file mode 100644 index 00000000000000..214028953b5d6d Binary files /dev/null and b/examples/screenshots/webgpu_xr_cubes.jpg differ diff --git a/examples/screenshots/webgpu_xr_native_layers.jpg b/examples/screenshots/webgpu_xr_native_layers.jpg new file mode 100644 index 00000000000000..9f569a08a11c4c Binary files /dev/null and b/examples/screenshots/webgpu_xr_native_layers.jpg differ diff --git a/examples/screenshots/webgpu_xr_rollercoaster.jpg b/examples/screenshots/webgpu_xr_rollercoaster.jpg new file mode 100644 index 00000000000000..a55ea215990224 Binary files /dev/null and b/examples/screenshots/webgpu_xr_rollercoaster.jpg differ diff --git a/examples/screenshots/webxr_vr_handinput.jpg b/examples/screenshots/webxr_vr_handinput.jpg index 1ad1e1ed30b8cb..7b4e1da7824f03 100644 Binary files a/examples/screenshots/webxr_vr_handinput.jpg and b/examples/screenshots/webxr_vr_handinput.jpg differ diff --git a/examples/screenshots/webxr_vr_handinput_cubes.jpg b/examples/screenshots/webxr_vr_handinput_cubes.jpg index 1ad1e1ed30b8cb..7b4e1da7824f03 100644 Binary files a/examples/screenshots/webxr_vr_handinput_cubes.jpg and b/examples/screenshots/webxr_vr_handinput_cubes.jpg differ diff --git a/examples/screenshots/webxr_vr_handinput_pointerclick.jpg b/examples/screenshots/webxr_vr_handinput_pointerclick.jpg index 7c2df8185e6459..f614177093d02d 100644 Binary files a/examples/screenshots/webxr_vr_handinput_pointerclick.jpg and b/examples/screenshots/webxr_vr_handinput_pointerclick.jpg differ diff --git a/examples/screenshots/webxr_vr_handinput_pointerdrag.jpg b/examples/screenshots/webxr_vr_handinput_pointerdrag.jpg index 704f95deb620fc..969071abe9d761 100644 Binary files a/examples/screenshots/webxr_vr_handinput_pointerdrag.jpg and b/examples/screenshots/webxr_vr_handinput_pointerdrag.jpg differ diff --git a/examples/screenshots/webxr_vr_handinput_pressbutton.jpg b/examples/screenshots/webxr_vr_handinput_pressbutton.jpg index 2f7b86f033be22..4fafb711bddad4 100644 Binary files a/examples/screenshots/webxr_vr_handinput_pressbutton.jpg and b/examples/screenshots/webxr_vr_handinput_pressbutton.jpg differ diff --git a/examples/screenshots/webxr_vr_handinput_profiles.jpg b/examples/screenshots/webxr_vr_handinput_profiles.jpg index 1ad1e1ed30b8cb..7b4e1da7824f03 100644 Binary files a/examples/screenshots/webxr_vr_handinput_profiles.jpg and b/examples/screenshots/webxr_vr_handinput_profiles.jpg differ diff --git a/examples/screenshots/webxr_vr_layers.jpg b/examples/screenshots/webxr_vr_layers.jpg index 26462eb2f22db1..549a83ea3f6779 100755 Binary files a/examples/screenshots/webxr_vr_layers.jpg and b/examples/screenshots/webxr_vr_layers.jpg differ diff --git a/examples/screenshots/webxr_vr_panorama_depth.jpg b/examples/screenshots/webxr_vr_panorama_depth.jpg index 36fd3ae4a399b9..de869c6002caf0 100644 Binary files a/examples/screenshots/webxr_vr_panorama_depth.jpg and b/examples/screenshots/webxr_vr_panorama_depth.jpg differ diff --git a/examples/screenshots/webxr_vr_rollercoaster.jpg b/examples/screenshots/webxr_vr_rollercoaster.jpg index 9dbae1793c565b..a55ea215990224 100644 Binary files a/examples/screenshots/webxr_vr_rollercoaster.jpg and b/examples/screenshots/webxr_vr_rollercoaster.jpg differ diff --git a/examples/screenshots/webxr_vr_sandbox.jpg b/examples/screenshots/webxr_vr_sandbox.jpg index d846cf0eb55b9b..2fae3eb2340a2f 100644 Binary files a/examples/screenshots/webxr_vr_sandbox.jpg and b/examples/screenshots/webxr_vr_sandbox.jpg differ diff --git a/examples/screenshots/webxr_vr_teleport.jpg b/examples/screenshots/webxr_vr_teleport.jpg index b22fa5ae747797..86f55d661dffd5 100644 Binary files a/examples/screenshots/webxr_vr_teleport.jpg and b/examples/screenshots/webxr_vr_teleport.jpg differ diff --git a/examples/screenshots/webxr_xr_ballshooter.jpg b/examples/screenshots/webxr_xr_ballshooter.jpg index 8f1aaa11e47ed4..beaa96579054ab 100644 Binary files a/examples/screenshots/webxr_xr_ballshooter.jpg and b/examples/screenshots/webxr_xr_ballshooter.jpg differ diff --git a/examples/screenshots/webxr_xr_controls_transform.jpg b/examples/screenshots/webxr_xr_controls_transform.jpg new file mode 100644 index 00000000000000..742b8a8515e863 Binary files /dev/null and b/examples/screenshots/webxr_xr_controls_transform.jpg differ diff --git a/examples/screenshots/webxr_xr_cubes.jpg b/examples/screenshots/webxr_xr_cubes.jpg index dd9ec70b3f3d5d..214028953b5d6d 100644 Binary files a/examples/screenshots/webxr_xr_cubes.jpg and b/examples/screenshots/webxr_xr_cubes.jpg differ diff --git a/examples/screenshots/webxr_xr_dragging.jpg b/examples/screenshots/webxr_xr_dragging.jpg index 59492e3a5ff3e0..f1771c66083f9e 100644 Binary files a/examples/screenshots/webxr_xr_dragging.jpg and b/examples/screenshots/webxr_xr_dragging.jpg differ diff --git a/examples/screenshots/webxr_xr_dragging_custom_depth.jpg b/examples/screenshots/webxr_xr_dragging_custom_depth.jpg new file mode 100644 index 00000000000000..5eec82416892e9 Binary files /dev/null and b/examples/screenshots/webxr_xr_dragging_custom_depth.jpg differ diff --git a/examples/screenshots/webxr_xr_haptics.jpg b/examples/screenshots/webxr_xr_haptics.jpg index 3467c65c70ede8..eda909320f35fe 100644 Binary files a/examples/screenshots/webxr_xr_haptics.jpg and b/examples/screenshots/webxr_xr_haptics.jpg differ diff --git a/examples/screenshots/webxr_xr_paint.jpg b/examples/screenshots/webxr_xr_paint.jpg index 1f8a294496e5e6..0797589bf269ef 100644 Binary files a/examples/screenshots/webxr_xr_paint.jpg and b/examples/screenshots/webxr_xr_paint.jpg differ diff --git a/examples/screenshots/webxr_xr_sculpt.jpg b/examples/screenshots/webxr_xr_sculpt.jpg index 1f8a294496e5e6..0797589bf269ef 100644 Binary files a/examples/screenshots/webxr_xr_sculpt.jpg and b/examples/screenshots/webxr_xr_sculpt.jpg differ diff --git a/examples/svg_lines.html b/examples/svg_lines.html index e134977d9d09fe..f9d1013abb6dba 100644 --- a/examples/svg_lines.html +++ b/examples/svg_lines.html @@ -17,10 +17,6 @@ three.js svg - lines - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/webgl2_buffergeometry_attributes_none.html b/examples/webgl2_buffergeometry_attributes_none.html deleted file mode 100644 index 83c8a9f01015e0..00000000000000 --- a/examples/webgl2_buffergeometry_attributes_none.html +++ /dev/null @@ -1,172 +0,0 @@ - - - - three.js WebGL 2 - buffergeometry - attributes - none - - - - - - -
        three.js WebGL 2 - buffergeometry - attributes - none
        - - - - - - - - - - - - - - - diff --git a/examples/webgl2_materials_texture2darray.html b/examples/webgl2_materials_texture2darray.html deleted file mode 100644 index 33460b99575496..00000000000000 --- a/examples/webgl2_materials_texture2darray.html +++ /dev/null @@ -1,193 +0,0 @@ - - - - three.js webgl - 2D texture array - - - - - - - - -
        - three.js - 2D Texture array
        - Scanned head data by - Divine Augustine
        - licensed under - CPOL -
        - - - - - - - - - - diff --git a/examples/webgl2_materials_texture3d.html b/examples/webgl2_materials_texture3d.html deleted file mode 100644 index 0243677301ef0f..00000000000000 --- a/examples/webgl2_materials_texture3d.html +++ /dev/null @@ -1,185 +0,0 @@ - - - - three.js webgl - volume rendering example - - - - - - -
        - three.js - Float volume render test (mip / isosurface) -
        -
        - - - - - - - - - - - diff --git a/examples/webgl2_materials_texture3d_partialupdate.html b/examples/webgl2_materials_texture3d_partialupdate.html deleted file mode 100644 index ed76a5a8c0cf40..00000000000000 --- a/examples/webgl2_materials_texture3d_partialupdate.html +++ /dev/null @@ -1,379 +0,0 @@ - - - - three.js webgl2 - volume - cloud - - - - - - -
        - three.js webgl2 - volume - cloud -
        - - - - - - - - - - - diff --git a/examples/webgl2_multiple_rendertargets.html b/examples/webgl2_multiple_rendertargets.html deleted file mode 100644 index d7dbbedafc0196..00000000000000 --- a/examples/webgl2_multiple_rendertargets.html +++ /dev/null @@ -1,274 +0,0 @@ - - - - three.js webgl - Multiple Render Targets - - - - - - - - - - - - - - -
        - threejs webgl - Multiple RenderTargets -
        - - - - - - - - - - - diff --git a/examples/webgl2_multisampled_renderbuffers.html b/examples/webgl2_multisampled_renderbuffers.html deleted file mode 100644 index 8f4a70c87276ff..00000000000000 --- a/examples/webgl2_multisampled_renderbuffers.html +++ /dev/null @@ -1,211 +0,0 @@ - - - - three.js WebGL 2 - Multisampled Renderbuffers - - - - - - - - -
        - three.js - Multisampled Renderbuffers
        - Left: WebGLRenderTarget, Right: WebGLMultisampleRenderTarget. -
        -
        -
        - - - - - - - - - - diff --git a/examples/webgl2_rendertarget_texture2darray.html b/examples/webgl2_rendertarget_texture2darray.html deleted file mode 100644 index e930018cea86b7..00000000000000 --- a/examples/webgl2_rendertarget_texture2darray.html +++ /dev/null @@ -1,292 +0,0 @@ - - - - three.js webgl - 2D texture array framebuffer attachment - - - - - - - - - - - - - - -
        - - three.js - - - 2D Texture array framebuffer color attachment -
        - -

        - This example shows how to render to an array of 2D texture.
        - WebGL2 allows to render to specific "layers" in 3D texture and array of textures. -

        - - Scanned head data by - Divine Augustine
        - licensed under - CPOL -
        - - - - - - - - - - diff --git a/examples/webgl2_texture2darray_compressed.html b/examples/webgl2_texture2darray_compressed.html deleted file mode 100644 index 126664517ef4fe..00000000000000 --- a/examples/webgl2_texture2darray_compressed.html +++ /dev/null @@ -1,181 +0,0 @@ - - - - three.js webgl - 2D compressed texture array - - - - - - - - -
        - three.js - 2D Compressed Texture Array
        - Loop from the movie Spirited away - by the Studio Ghibli
        -
        - - - - - - - - - - diff --git a/examples/webgl2_ubo.html b/examples/webgl2_ubo.html deleted file mode 100644 index d9eadaea7b057a..00000000000000 --- a/examples/webgl2_ubo.html +++ /dev/null @@ -1,350 +0,0 @@ - - - - three.js WebGL 2 - Uniform Buffer Objects - - - - - - - -
        - three.js - Uniform Buffer Objects -
        -
        - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/webgl2_volume_cloud.html b/examples/webgl2_volume_cloud.html deleted file mode 100644 index 5b870b11989228..00000000000000 --- a/examples/webgl2_volume_cloud.html +++ /dev/null @@ -1,329 +0,0 @@ - - - - three.js webgl2 - volume - cloud - - - - - - -
        - three.js webgl2 - volume - cloud -
        - - - - - - - - - - - diff --git a/examples/webgl2_volume_instancing.html b/examples/webgl2_volume_instancing.html deleted file mode 100644 index d0c5fc3498373f..00000000000000 --- a/examples/webgl2_volume_instancing.html +++ /dev/null @@ -1,247 +0,0 @@ - - - - three.js webgl2 - volume - instancing - - - - - - -
        - three.js webgl2 - volume - instancing -
        - - - - - - - - - - - diff --git a/examples/webgl2_volume_perlin.html b/examples/webgl2_volume_perlin.html deleted file mode 100644 index aec0663b91938d..00000000000000 --- a/examples/webgl2_volume_perlin.html +++ /dev/null @@ -1,264 +0,0 @@ - - - - three.js webgl2 - volume - - - - - - -
        - three.js webgl2 - volume -
        - - - - - - - - - - - diff --git a/examples/webgl_animation_keyframes.html b/examples/webgl_animation_keyframes.html index 0267e190108655..eadb31245b4707 100644 --- a/examples/webgl_animation_keyframes.html +++ b/examples/webgl_animation_keyframes.html @@ -27,10 +27,6 @@ Glen Fox, CC Attribution. - - - - - - diff --git a/examples/webgl_animation_skinning_blending.html b/examples/webgl_animation_skinning_blending.html index 50f1276809502d..d3a97eaba9896b 100644 --- a/examples/webgl_animation_skinning_blending.html +++ b/examples/webgl_animation_skinning_blending.html @@ -19,10 +19,6 @@ Note: crossfades are possible with blend weights being set to (1,0,0), (0,1,0) or (0,0,1) - - - - diff --git a/examples/webgl_animation_skinning_ik.html b/examples/webgl_animation_skinning_ik.html index 62375a3a0c46f7..9fab7c04180af1 100644 --- a/examples/webgl_animation_skinning_ik.html +++ b/examples/webgl_animation_skinning_ik.html @@ -19,10 +19,6 @@ Character model by Aki, furnitures from poly.pizza, scene by abernier. CC0. - - - - - + + + + + diff --git a/examples/webgl_batch_lod_bvh.html b/examples/webgl_batch_lod_bvh.html new file mode 100644 index 00000000000000..bbe79b22fdf5b4 --- /dev/null +++ b/examples/webgl_batch_lod_bvh.html @@ -0,0 +1,280 @@ + + + + three.js raycaster - batch - lod - bvh + + + + + + + +
        + three.js batch lod bvh - @three.ez/batched-mesh-extensions
        + BatchedMesh with 10 geometries and 500k instances.
        + Each geometry has 5 LODs (4 generated with meshoptimizer).
        + Frustum culling and raycasting are accelerated by using BVHs (TLAS & BLAS).
        +
        + + + + + + + diff --git a/examples/webgl_buffergeometry.html b/examples/webgl_buffergeometry.html index 104f60d0bb1e0b..97be1d70de1733 100644 --- a/examples/webgl_buffergeometry.html +++ b/examples/webgl_buffergeometry.html @@ -11,10 +11,6 @@
        three.js webgl - buffergeometry
        - - - - diff --git a/examples/webgl_buffergeometry_attributes_integer.html b/examples/webgl_buffergeometry_attributes_integer.html new file mode 100644 index 00000000000000..3fae0cc80ec625 --- /dev/null +++ b/examples/webgl_buffergeometry_attributes_integer.html @@ -0,0 +1,175 @@ + + + + three.js WebGL 2 - buffergeometry - integer attributes + + + + + + +
        +
        three.js WebGL 2 - buffergeometry - integer attributes
        + + + + + + + diff --git a/examples/webgl_buffergeometry_attributes_none.html b/examples/webgl_buffergeometry_attributes_none.html new file mode 100644 index 00000000000000..a90c923e6c06a4 --- /dev/null +++ b/examples/webgl_buffergeometry_attributes_none.html @@ -0,0 +1,158 @@ + + + + three.js WebGL 2 - buffergeometry - attributes - none + + + + + + +
        three.js WebGL 2 - buffergeometry - attributes - none
        + + + + + + + + + + + diff --git a/examples/webgl_buffergeometry_compression.html b/examples/webgl_buffergeometry_compression.html deleted file mode 100644 index 5ac544a89af037..00000000000000 --- a/examples/webgl_buffergeometry_compression.html +++ /dev/null @@ -1,281 +0,0 @@ - - - - three.js webgl - buffergeometry - compression - - - - - -
        - three.js - BufferGeometry Compression
        - Octahedron and Quantization encoding methods from Tarek Sherif -
        - - - - - - - - - - - diff --git a/examples/webgl_buffergeometry_custom_attributes_particles.html b/examples/webgl_buffergeometry_custom_attributes_particles.html index 6ca54a3575da44..81f48f15700f38 100644 --- a/examples/webgl_buffergeometry_custom_attributes_particles.html +++ b/examples/webgl_buffergeometry_custom_attributes_particles.html @@ -46,10 +46,6 @@ - - - - diff --git a/examples/webgl_buffergeometry_drawrange.html b/examples/webgl_buffergeometry_drawrange.html index cc6e2a74e80e8d..3a6f2e8061c36d 100644 --- a/examples/webgl_buffergeometry_drawrange.html +++ b/examples/webgl_buffergeometry_drawrange.html @@ -14,10 +14,6 @@ by fernandojsg - - - - - diff --git a/examples/webgl_buffergeometry_indexed.html b/examples/webgl_buffergeometry_indexed.html index 74c869b11466a2..47e1f8fdbfaa39 100644 --- a/examples/webgl_buffergeometry_indexed.html +++ b/examples/webgl_buffergeometry_indexed.html @@ -11,10 +11,6 @@
        three.js webgl - buffergeometry - indexed
        - - - - diff --git a/examples/webgl_buffergeometry_instancing.html b/examples/webgl_buffergeometry_instancing.html index 90153dc8dfa631..c26eb54b19b493 100644 --- a/examples/webgl_buffergeometry_instancing.html +++ b/examples/webgl_buffergeometry_instancing.html @@ -66,10 +66,6 @@ - - - - diff --git a/examples/webgl_buffergeometry_instancing_billboards.html b/examples/webgl_buffergeometry_instancing_billboards.html index 49be62bf851aa6..155c0fd7826adb 100644 --- a/examples/webgl_buffergeometry_instancing_billboards.html +++ b/examples/webgl_buffergeometry_instancing_billboards.html @@ -47,7 +47,7 @@ varying vec2 vUv; varying float vScale; - // HSL to RGB Convertion helpers + // HSL to RGB Conversion helpers vec3 HUEtoRGB(float H){ H = mod(H,1.0); float R = abs(H * 6.0 - 3.0) - 1.0; @@ -70,10 +70,6 @@ } - - - - diff --git a/examples/webgl_buffergeometry_instancing_interleaved.html b/examples/webgl_buffergeometry_instancing_interleaved.html index 8c79bf6012092d..ef7d7875c02701 100644 --- a/examples/webgl_buffergeometry_instancing_interleaved.html +++ b/examples/webgl_buffergeometry_instancing_interleaved.html @@ -14,10 +14,6 @@ - - - - diff --git a/examples/webgl_buffergeometry_lines.html b/examples/webgl_buffergeometry_lines.html index aaf597790b33e7..d8d9443d79c65b 100644 --- a/examples/webgl_buffergeometry_lines.html +++ b/examples/webgl_buffergeometry_lines.html @@ -11,10 +11,6 @@
        three.js webgl - buffergeometry - lines
        - - - - - diff --git a/examples/webgl_buffergeometry_points.html b/examples/webgl_buffergeometry_points.html index e0cd3a4d2b017a..04fb1568493059 100644 --- a/examples/webgl_buffergeometry_points.html +++ b/examples/webgl_buffergeometry_points.html @@ -11,10 +11,6 @@
        three.js webgl - buffergeometry - particles
        - - - - diff --git a/examples/webgl_buffergeometry_points_interleaved.html b/examples/webgl_buffergeometry_points_interleaved.html index c3a55d4511040d..b03a332ae4d5be 100644 --- a/examples/webgl_buffergeometry_points_interleaved.html +++ b/examples/webgl_buffergeometry_points_interleaved.html @@ -11,10 +11,6 @@
        three.js webgl - buffergeometry - particles
        - - - - diff --git a/examples/webgl_buffergeometry_rawshader.html b/examples/webgl_buffergeometry_rawshader.html index b54c6a9c501958..89fc8ddaa8c0e6 100644 --- a/examples/webgl_buffergeometry_rawshader.html +++ b/examples/webgl_buffergeometry_rawshader.html @@ -57,10 +57,6 @@ - - - - diff --git a/examples/webgl_buffergeometry_selective_draw.html b/examples/webgl_buffergeometry_selective_draw.html index 3773ff0d85d40f..c848297ec580bf 100644 --- a/examples/webgl_buffergeometry_selective_draw.html +++ b/examples/webgl_buffergeometry_selective_draw.html @@ -46,10 +46,6 @@ - - - - diff --git a/examples/webgl_buffergeometry_uint.html b/examples/webgl_buffergeometry_uint.html index a4e68b815de4dc..83153194846bbd 100644 --- a/examples/webgl_buffergeometry_uint.html +++ b/examples/webgl_buffergeometry_uint.html @@ -11,10 +11,6 @@
        three.js webgl - buffergeometry - uint
        - - - - diff --git a/examples/webgl_camera.html b/examples/webgl_camera.html index fc593997e4927e..e3dc3d321fcf68 100644 --- a/examples/webgl_camera.html +++ b/examples/webgl_camera.html @@ -16,10 +16,6 @@ O orthographic P perspective - - - - diff --git a/examples/webgl_camera_array.html b/examples/webgl_camera_array.html index 60406799fc03b8..3b56c57787345b 100644 --- a/examples/webgl_camera_array.html +++ b/examples/webgl_camera_array.html @@ -7,11 +7,6 @@ - - - - - diff --git a/examples/webgl_camera_cinematic.html b/examples/webgl_camera_cinematic.html deleted file mode 100644 index 6ef46b6aa1c7b3..00000000000000 --- a/examples/webgl_camera_cinematic.html +++ /dev/null @@ -1,250 +0,0 @@ - - - - three.js webgl - camera cinematic - - - - - - - -
        - three.js webgl - interactive cubes -
        - - - - - - - - - - - diff --git a/examples/webgl_camera_logarithmicdepthbuffer.html b/examples/webgl_camera_logarithmicdepthbuffer.html index 85bf209aed7705..0f1c33a8724735 100644 --- a/examples/webgl_camera_logarithmicdepthbuffer.html +++ b/examples/webgl_camera_logarithmicdepthbuffer.html @@ -62,10 +62,6 @@ mousewheel to dolly out - - - - + + + + + + + + diff --git a/examples/webgl_clipping.html b/examples/webgl_clipping.html index 36ec9ec1f48713..a141d21126c9c8 100644 --- a/examples/webgl_clipping.html +++ b/examples/webgl_clipping.html @@ -7,11 +7,6 @@ - - - - - - diff --git a/examples/webgl_clipping_intersection.html b/examples/webgl_clipping_intersection.html index 9f1779745595e1..768b06ceba51d8 100644 --- a/examples/webgl_clipping_intersection.html +++ b/examples/webgl_clipping_intersection.html @@ -7,11 +7,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/webgl_effects_stereo.html b/examples/webgl_effects_stereo.html index fe7a5bf83bbb7d..ff526edfb6c69f 100644 --- a/examples/webgl_effects_stereo.html +++ b/examples/webgl_effects_stereo.html @@ -12,10 +12,6 @@ three.js - effects - stereo. skybox by Jochum Skoglund - - - - - - - - - - - - - - diff --git a/examples/webgl_geometry_colors.html b/examples/webgl_geometry_colors.html index a296ad4e4d6469..4c7880c47e3e05 100644 --- a/examples/webgl_geometry_colors.html +++ b/examples/webgl_geometry_colors.html @@ -20,10 +20,6 @@
        three.js webgl - vertex colors
        - - - - - - - @@ -62,7 +58,6 @@ }; init(); - animate(); function init() { @@ -74,10 +69,10 @@ scene.background = new THREE.Color( 0xfce4ec ); // lights - const ambient = new THREE.HemisphereLight( 0xffffff, 0xbfd4d2, 0.9 ); + const ambient = new THREE.HemisphereLight( 0xffffff, 0xbfd4d2, 3 ); scene.add( ambient ); - const directionalLight = new THREE.DirectionalLight( 0xffffff, 0.1 ); + const directionalLight = new THREE.DirectionalLight( 0xffffff, 0.3 ); directionalLight.position.set( 1, 4, 3 ).multiplyScalar( 3 ); directionalLight.castShadow = true; directionalLight.shadow.mapSize.setScalar( 2048 ); @@ -89,6 +84,7 @@ renderer = new THREE.WebGLRenderer( { antialias: true } ); renderer.setPixelRatio( window.devicePixelRatio ); renderer.setSize( window.innerWidth, window.innerHeight ); + renderer.setAnimationLoop( animate ); renderer.shadowMap.enabled = true; renderer.shadowMap.type = THREE.PCFSoftShadowMap; document.body.appendChild( renderer.domElement ); @@ -200,8 +196,6 @@ function animate() { - requestAnimationFrame( animate ); - // update the transforms const t = window.performance.now() + 9000; baseBrush.rotation.x = t * 0.0001; diff --git a/examples/webgl_geometry_cube.html b/examples/webgl_geometry_cube.html index 7f3b9108282fd4..8c90b42b18c56a 100644 --- a/examples/webgl_geometry_cube.html +++ b/examples/webgl_geometry_cube.html @@ -7,11 +7,6 @@ - - - - - - - - - - - - diff --git a/examples/webgl_geometry_extrude_shapes.html b/examples/webgl_geometry_extrude_shapes.html index 9fc25503520a2b..0f95cc9f06302d 100644 --- a/examples/webgl_geometry_extrude_shapes.html +++ b/examples/webgl_geometry_extrude_shapes.html @@ -16,11 +16,6 @@ - - - - - - - - - - - - diff --git a/examples/webgl_geometry_extrude_splines.html b/examples/webgl_geometry_extrude_splines.html index 121cb75d8f0c70..a852ff8274002d 100644 --- a/examples/webgl_geometry_extrude_splines.html +++ b/examples/webgl_geometry_extrude_splines.html @@ -22,10 +22,6 @@ three.js - spline extrusion examples - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/webgl_helpers.html b/examples/webgl_helpers.html index 51cf390c1c421f..fb248f252869be 100644 --- a/examples/webgl_helpers.html +++ b/examples/webgl_helpers.html @@ -11,10 +11,6 @@ three.js - helpers - - - - +
        + three.js webgl - instancing - dynamic
        + Based on Cubescape + by oosmoxiecode +
        + + + + + diff --git a/examples/webgl_instancing_performance.html b/examples/webgl_instancing_performance.html index bffa9564213e3b..1a2e3e1759f0ad 100644 --- a/examples/webgl_instancing_performance.html +++ b/examples/webgl_instancing_performance.html @@ -26,10 +26,6 @@
        - - - - - diff --git a/examples/webgl_instancing_scatter.html b/examples/webgl_instancing_scatter.html index 37881b74b4d416..bd9f9aa6f53fd3 100644 --- a/examples/webgl_instancing_scatter.html +++ b/examples/webgl_instancing_scatter.html @@ -7,11 +7,6 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/webgl_lensflares.html b/examples/webgl_lensflares.html index 107c8946675c78..7ebb95ba10305b 100644 --- a/examples/webgl_lensflares.html +++ b/examples/webgl_lensflares.html @@ -14,10 +14,6 @@ fly with WASD/RF/QE + mouse - - - - - - - - - - - diff --git a/examples/webgl_lightprobe.html b/examples/webgl_lightprobe.html index 75ef5e6646fdb9..639aef31c4f13b 100644 --- a/examples/webgl_lightprobe.html +++ b/examples/webgl_lightprobe.html @@ -12,10 +12,6 @@ three.js webgl - light probe - - - - - - - - - - - - - - - - diff --git a/examples/webgl_lights_rectarealight.html b/examples/webgl_lights_rectarealight.html index 91080dda6ca542..c66d889a141ae6 100644 --- a/examples/webgl_lights_rectarealight.html +++ b/examples/webgl_lights_rectarealight.html @@ -13,10 +13,6 @@ by abelnation - - - - - - diff --git a/examples/webgl_lines_colors.html b/examples/webgl_lines_colors.html index 8145c1207a0fbb..2e4ffba9faf26c 100644 --- a/examples/webgl_lines_colors.html +++ b/examples/webgl_lines_colors.html @@ -13,10 +13,6 @@ Hilbert curve thanks to Thomas Diewald - - - - - @@ -30,8 +27,7 @@ import * as THREE from 'three'; - import Stats from 'three/addons/libs/stats.module.js'; - import { GPUStatsPanel } from 'three/addons/utils/GPUStatsPanel.js'; + import Stats from 'stats-gl'; import { GUI } from 'three/addons/libs/lil-gui.module.min.js'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; @@ -43,7 +39,7 @@ let line, renderer, scene, camera, camera2, controls; let line1; let matLine, matLineBasic, matLineDashed; - let stats, gpuPanel; + let stats; let gui; // viewport @@ -51,14 +47,14 @@ let insetHeight; init(); - animate(); function init() { renderer = new THREE.WebGLRenderer( { antialias: true } ); renderer.setPixelRatio( window.devicePixelRatio ); - renderer.setClearColor( 0x000000, 0.0 ); renderer.setSize( window.innerWidth, window.innerHeight ); + renderer.setClearColor( 0x000000, 0.0 ); + renderer.setAnimationLoop( animate ); document.body.appendChild( renderer.domElement ); scene = new THREE.Scene(); @@ -70,6 +66,7 @@ camera2.position.copy( camera.position ); controls = new OrbitControls( camera, renderer.domElement ); + controls.enableDamping = true; controls.minDistance = 10; controls.maxDistance = 500; @@ -111,7 +108,6 @@ linewidth: 5, // in world units with size attenuation, pixels otherwise vertexColors: true, - //resolution: // to be set by renderer, eventually dashed: false, alphaToCoverage: true, @@ -142,13 +138,10 @@ window.addEventListener( 'resize', onWindowResize ); onWindowResize(); - stats = new Stats(); + stats = new Stats( { horizontal: false, trackGPU: true } ); + stats.init( renderer ); document.body.appendChild( stats.dom ); - gpuPanel = new GPUStatsPanel( renderer.getContext() ); - stats.addPanel( gpuPanel ); - stats.showPanel( 0 ); - initGui(); } @@ -170,22 +163,15 @@ function animate() { - requestAnimationFrame( animate ); - - stats.update(); - // main scene renderer.setClearColor( 0x000000, 0 ); renderer.setViewport( 0, 0, window.innerWidth, window.innerHeight ); - // renderer will set this eventually - matLine.resolution.set( window.innerWidth, window.innerHeight ); // resolution of the viewport + controls.update(); - gpuPanel.startQuery(); renderer.render( scene, camera ); - gpuPanel.endQuery(); // inset scene @@ -202,13 +188,12 @@ camera2.position.copy( camera.position ); camera2.quaternion.copy( camera.quaternion ); - // renderer will set this eventually - matLine.resolution.set( insetWidth, insetHeight ); // resolution of the inset viewport - renderer.render( scene, camera2 ); renderer.setScissorTest( false ); + stats.update(); + } // diff --git a/examples/webgl_lines_fat_raycasting.html b/examples/webgl_lines_fat_raycasting.html index 0db208fd66882c..5ac47d01192d48 100644 --- a/examples/webgl_lines_fat_raycasting.html +++ b/examples/webgl_lines_fat_raycasting.html @@ -13,15 +13,12 @@
        three.js - fat lines raycasting
        - - - - @@ -30,8 +27,7 @@ import * as THREE from 'three'; - import Stats from 'three/addons/libs/stats.module.js'; - import { GPUStatsPanel } from 'three/addons/utils/GPUStatsPanel.js'; + import Stats from 'stats-gl'; import { GUI } from 'three/addons/libs/lil-gui.module.min.js'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; @@ -44,7 +40,7 @@ let line, thresholdLine, segments, thresholdSegments; let renderer, scene, camera, controls; let sphereInter, sphereOnLine; - let stats, gpuPanel; + let stats; let gui; let clock; @@ -64,7 +60,6 @@ worldUnits: true, vertexColors: true, - //resolution: // to be set by renderer, eventually alphaToCoverage: true, } ); @@ -79,7 +74,6 @@ opacity: 0.2, depthTest: false, visible: false, - //resolution: // to be set by renderer, eventually } ); @@ -97,7 +91,6 @@ }; init(); - animate(); function init() { @@ -105,8 +98,9 @@ renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true } ); renderer.setPixelRatio( window.devicePixelRatio ); - renderer.setClearColor( 0x000000, 0.0 ); renderer.setSize( window.innerWidth, window.innerHeight ); + renderer.setClearColor( 0x000000, 0.0 ); + renderer.setAnimationLoop( animate ); document.body.appendChild( renderer.domElement ); scene = new THREE.Scene(); @@ -200,12 +194,10 @@ window.addEventListener( 'resize', onWindowResize ); onWindowResize(); - stats = new Stats(); + stats = new Stats( { horizontal: false, trackGPU: true } ); + stats.init( renderer ); document.body.appendChild( stats.dom ); - - gpuPanel = new GPUStatsPanel( renderer.getContext() ); - stats.addPanel( gpuPanel ); - stats.showPanel( 0 ); + initGui(); } @@ -217,10 +209,6 @@ renderer.setSize( window.innerWidth, window.innerHeight ); - // renderer will set this eventually - matLine.resolution.set( window.innerWidth, window.innerHeight ); - matThresholdLine.resolution.set( window.innerWidth, window.innerHeight ); - } function onPointerMove( event ) { @@ -232,10 +220,6 @@ function animate() { - requestAnimationFrame( animate ); - - stats.update(); - const delta = clock.getDelta(); const obj = line.visible ? line : segments; @@ -282,9 +266,9 @@ } - gpuPanel.startQuery(); renderer.render( scene, camera ); - gpuPanel.endQuery(); + + stats.update(); } diff --git a/examples/webgl_lines_fat_wireframe.html b/examples/webgl_lines_fat_wireframe.html index 18e8d50285a830..4607dd9fd868ee 100644 --- a/examples/webgl_lines_fat_wireframe.html +++ b/examples/webgl_lines_fat_wireframe.html @@ -13,10 +13,6 @@
        three.js - fat lines
        - - - - - - - - - - diff --git a/examples/webgl_loader_3dm.html b/examples/webgl_loader_3dm.html index df66b9e9195ea9..1e9ec885ce5e21 100644 --- a/examples/webgl_loader_3dm.html +++ b/examples/webgl_loader_3dm.html @@ -32,10 +32,6 @@
        - - - - - diff --git a/examples/webgl_loader_3dtiles.html b/examples/webgl_loader_3dtiles.html new file mode 100644 index 00000000000000..8ac0f429c6245a --- /dev/null +++ b/examples/webgl_loader_3dtiles.html @@ -0,0 +1,142 @@ + + + + three.js loader - 3d tiles + + + + + + + +
        + three.js 3d tiles - 3d-tiles-renderer
        + See main project repository for more information and examples on loading 3d tiles +
        + and other tiled data formats. Google Photorealistic Tiles token courtesy of Cesium Ion. +
        + +
        + + +
        + + + + + + + diff --git a/examples/webgl_loader_3mf.html b/examples/webgl_loader_3mf.html index 1369d907018675..c3ffbd0435624a 100644 --- a/examples/webgl_loader_3mf.html +++ b/examples/webgl_loader_3mf.html @@ -19,10 +19,6 @@
        Files from 3mf-samples
        - - - - - - - - - - - - - - - - - - + + + + + diff --git a/examples/webgl_loader_gltf_instancing.html b/examples/webgl_loader_gltf_instancing.html index 35b4ae986abb7f..cc1998821d2136 100644 --- a/examples/webgl_loader_gltf_instancing.html +++ b/examples/webgl_loader_gltf_instancing.html @@ -12,13 +12,9 @@ three.js - GLTFLoader + EXT_mesh_gpu_instancing
        Battle Damaged Sci-fi Helmet by theblueturtle_
        - Royal Esplanade by HDRI Haven + Royal Esplanade from HDRI Haven - - - - - - - - - - - - diff --git a/examples/webgl_loader_gltf_sheen.html b/examples/webgl_loader_gltf_sheen.html index 71e40d45ccef05..277efc08a2213d 100644 --- a/examples/webgl_loader_gltf_sheen.html +++ b/examples/webgl_loader_gltf_sheen.html @@ -18,10 +18,6 @@ Sheen Chair from glTF-Sample-Models - - - - - - - @@ -42,8 +38,6 @@ import { IFCLoader } from 'web-ifc-three'; import { IFCSPACE } from 'web-ifc'; - THREE.ColorManagement.enabled = false; // TODO: Confirm correct color management. - let scene, camera, renderer; async function init() { @@ -65,20 +59,20 @@ scene.add( cube ); //Lights - const directionalLight1 = new THREE.DirectionalLight( 0xffeeff, 0.8 ); + const directionalLight1 = new THREE.DirectionalLight( 0xffeeff, 2.5 ); directionalLight1.position.set( 1, 1, 1 ); scene.add( directionalLight1 ); - const directionalLight2 = new THREE.DirectionalLight( 0xffffff, 0.8 ); + const directionalLight2 = new THREE.DirectionalLight( 0xffffff, 2.5 ); directionalLight2.position.set( - 1, 0.5, - 1 ); scene.add( directionalLight2 ); - const ambientLight = new THREE.AmbientLight( 0xffffee, 0.25 ); + const ambientLight = new THREE.AmbientLight( 0xffffee, 0.75 ); scene.add( ambientLight ); //Setup IFC Loader const ifcLoader = new IFCLoader(); - await ifcLoader.ifcManager.setWasmPath( 'https://unpkg.com/web-ifc@0.0.36/', true ); + await ifcLoader.ifcManager.setWasmPath( 'https://cdn.jsdelivr.net/npm/web-ifc@0.0.36/', true ); await ifcLoader.ifcManager.parser.setupOptionalCategories( { [ IFCSPACE ]: false, @@ -99,7 +93,6 @@ renderer = new THREE.WebGLRenderer( { antialias: true } ); renderer.setSize( window.innerWidth, window.innerHeight ); renderer.setPixelRatio( window.devicePixelRatio ); - renderer.outputColorSpace = THREE.LinearSRGBColorSpace; document.body.appendChild( renderer.domElement ); //Controls diff --git a/examples/webgl_loader_imagebitmap.html b/examples/webgl_loader_imagebitmap.html index 39b11646fb2a83..9cd7493a6025b9 100644 --- a/examples/webgl_loader_imagebitmap.html +++ b/examples/webgl_loader_imagebitmap.html @@ -11,10 +11,6 @@ three.js - Texture loader using ImageBitmap - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/webgl_loader_mmd_audio.html b/examples/webgl_loader_mmd_audio.html deleted file mode 100644 index 39dc1e3cae903e..00000000000000 --- a/examples/webgl_loader_mmd_audio.html +++ /dev/null @@ -1,204 +0,0 @@ - - - - three.js webgl - loaders - MMD loader - - - - - - - -
        - -
        -
        - three.js - MMDLoader test
        - MMD Assets license
        - Copyright - Model Data - Dance Data - Audio Data
        - Camera is customized from this Data -
        - - - - - - - - - - - - - diff --git a/examples/webgl_loader_mmd_pose.html b/examples/webgl_loader_mmd_pose.html deleted file mode 100644 index dac41ff4078c26..00000000000000 --- a/examples/webgl_loader_mmd_pose.html +++ /dev/null @@ -1,309 +0,0 @@ - - - - three.js webgl - loaders - MMD loader - - - - - - - -
        - three.js - MMDLoader test
        - MMD Assets license
        - Copyright - Model Data - Pose Data -
        - - - - - - - - - - - - - diff --git a/examples/webgl_loader_nrrd.html b/examples/webgl_loader_nrrd.html index 8ee23d956ecb83..ab5e83ad288429 100644 --- a/examples/webgl_loader_nrrd.html +++ b/examples/webgl_loader_nrrd.html @@ -14,10 +14,6 @@
        - - - - - - - - - - - - diff --git a/examples/webgl_loader_pcd.html b/examples/webgl_loader_pcd.html index e889664f1ee549..6e590e98cb6401 100644 --- a/examples/webgl_loader_pcd.html +++ b/examples/webgl_loader_pcd.html @@ -12,10 +12,6 @@ PCD File format - - - - - - - - - - - - - diff --git a/examples/webgl_loader_stl.html b/examples/webgl_loader_stl.html index 94d2d8c68f3606..1af4b52224250b 100644 --- a/examples/webgl_loader_stl.html +++ b/examples/webgl_loader_stl.html @@ -13,10 +13,6 @@ PR2 head from www.ros.org - - - - - diff --git a/examples/webgl_loader_texture_dds.html b/examples/webgl_loader_texture_dds.html index 0d7b6a9bd115d3..f76766c8ab4b3a 100644 --- a/examples/webgl_loader_texture_dds.html +++ b/examples/webgl_loader_texture_dds.html @@ -13,10 +13,6 @@ leaf texture by lauris71, explosion texture by bart - - - - - - - +
        +
        three.js - KTX2 texture loader - webgl
        +
        diff --git a/examples/webgl_loader_texture_logluv.html b/examples/webgl_loader_texture_logluv.html deleted file mode 100644 index 2c566d1d157b24..00000000000000 --- a/examples/webgl_loader_texture_logluv.html +++ /dev/null @@ -1,117 +0,0 @@ - - - - three.js webgl - materials - LogLuv texture loader - - - - - - -
        - three.js - webgl LogLuv texture loader example -
        - - - - - - - - - - diff --git a/examples/webgl_loader_texture_lottie.html b/examples/webgl_loader_texture_lottie.html index 946b27b7fb9c31..017d3ff8b5ff26 100644 --- a/examples/webgl_loader_texture_lottie.html +++ b/examples/webgl_loader_texture_lottie.html @@ -13,10 +13,6 @@ - - - - - - - - + + + + diff --git a/examples/webgl_loader_tilt.html b/examples/webgl_loader_tilt.html deleted file mode 100644 index d2d08c8dc278e0..00000000000000 --- a/examples/webgl_loader_tilt.html +++ /dev/null @@ -1,95 +0,0 @@ - - - - three.js webgl - tilt loader - - - - - -
        - three.js - tilt loader
        - TILTSPHERE by Rosie Summers -
        - - - - - - - - - - diff --git a/examples/webgl_loader_ttf.html b/examples/webgl_loader_ttf.html index 464d5c0731c99f..79bed77ccdd72a 100644 --- a/examples/webgl_loader_ttf.html +++ b/examples/webgl_loader_ttf.html @@ -12,10 +12,6 @@
        type to enter new text, drag to spin the text - - - - - - - - - - - - - - - - - - diff --git a/examples/webgl_materials_alphahash.html b/examples/webgl_materials_alphahash.html new file mode 100644 index 00000000000000..dd79375a437d14 --- /dev/null +++ b/examples/webgl_materials_alphahash.html @@ -0,0 +1,221 @@ + + + + three.js webgl - materials - alpha hash + + + + + + + + + + diff --git a/examples/webgl_materials_blending.html b/examples/webgl_materials_blending.html index c64b51f5bda708..ca5bf8c956aef3 100644 --- a/examples/webgl_materials_blending.html +++ b/examples/webgl_materials_blending.html @@ -7,11 +7,6 @@ - - - - - - - - - - - - - + + + + + diff --git a/examples/webgl_materials_curvature.html b/examples/webgl_materials_curvature.html deleted file mode 100644 index 897a52e2eb32ac..00000000000000 --- a/examples/webgl_materials_curvature.html +++ /dev/null @@ -1,376 +0,0 @@ - - - - three.js webgl - shader - curvature [ninja] - - - - - - -
        -
        - three.js - curvature estimation of a geometry
        - by CoderCat -
        - - - - - - - - - - - - - - - diff --git a/examples/webgl_materials_displacementmap.html b/examples/webgl_materials_displacementmap.html index fa6b38adf68e00..29f140bdbe5bdc 100644 --- a/examples/webgl_materials_displacementmap.html +++ b/examples/webgl_materials_displacementmap.html @@ -14,10 +14,6 @@ ninja head from AMD GPU MeshMapper - - - - - - + \ No newline at end of file diff --git a/examples/webgl_materials_envmaps_exr.html b/examples/webgl_materials_envmaps_exr.html index 1ccdc3179f865e..065456bc6e2067 100644 --- a/examples/webgl_materials_envmaps_exr.html +++ b/examples/webgl_materials_envmaps_exr.html @@ -11,10 +11,6 @@
        threejs - Example of an IBL (Image based lighting) pipeline based around
        equirectangular EXR lightprobe data. Created by Richard Monette
        - - - - - diff --git a/examples/webgl_materials_envmaps_hdr.html b/examples/webgl_materials_envmaps_hdr.html index 854ce499359fff..4b1440121b6080 100644 --- a/examples/webgl_materials_envmaps_hdr.html +++ b/examples/webgl_materials_envmaps_hdr.html @@ -14,10 +14,6 @@ Created by Prashant Sharma and Ben Houston. - - - - - - - - - - - - diff --git a/examples/webgl_materials_matcap.html b/examples/webgl_materials_matcap.html index 4b96370db82c36..31083527a2271b 100644 --- a/examples/webgl_materials_matcap.html +++ b/examples/webgl_materials_matcap.html @@ -13,10 +13,6 @@ Drag-and-drop JPG, PNG, WebP, AVIF, or EXR MatCap image files
        - - - - - - - - - - - - - - - diff --git a/examples/webgl_materials_physical_transmission.html b/examples/webgl_materials_physical_transmission.html index 2b6c49599f772a..e3e210bd2fd924 100644 --- a/examples/webgl_materials_physical_transmission.html +++ b/examples/webgl_materials_physical_transmission.html @@ -11,10 +11,6 @@
        threejs - Transmission
        - - - - - + \ No newline at end of file diff --git a/examples/webgl_materials_physical_transmission_alpha.html b/examples/webgl_materials_physical_transmission_alpha.html index 2f71ff5be16cd2..b5e192e550ac91 100644 --- a/examples/webgl_materials_physical_transmission_alpha.html +++ b/examples/webgl_materials_physical_transmission_alpha.html @@ -50,10 +50,6 @@
        threejs - transmission - alpha
        - - - - - - - - - - - diff --git a/examples/webgl_materials_subsurface_scattering.html b/examples/webgl_materials_subsurface_scattering.html index 471d62af49d9db..38291a6d1a8227 100644 --- a/examples/webgl_materials_subsurface_scattering.html +++ b/examples/webgl_materials_subsurface_scattering.html @@ -14,10 +14,6 @@ [Thanks for the art support from Shaochun Lin] - - - - - - - - - - + + + + + diff --git a/examples/webgl_materials_variations_basic.html b/examples/webgl_materials_variations_basic.html deleted file mode 100644 index a6a531c66e2bf8..00000000000000 --- a/examples/webgl_materials_variations_basic.html +++ /dev/null @@ -1,217 +0,0 @@ - - - - three.js webgl - materials - - - - - - -
        -
        three.js - Basic Material Variantions by Ben Houston.
        - - - - - - - - - - - diff --git a/examples/webgl_materials_variations_lambert.html b/examples/webgl_materials_variations_lambert.html deleted file mode 100644 index a37b80c2f3739f..00000000000000 --- a/examples/webgl_materials_variations_lambert.html +++ /dev/null @@ -1,217 +0,0 @@ - - - - three.js webgl - materials - - - - - - -
        -
        three.js - Lambert Material Variantions by Ben Houston.
        - - - - - - - - - - - diff --git a/examples/webgl_materials_variations_phong.html b/examples/webgl_materials_variations_phong.html deleted file mode 100644 index 7fa8ea6024f0b9..00000000000000 --- a/examples/webgl_materials_variations_phong.html +++ /dev/null @@ -1,225 +0,0 @@ - - - - three.js webgl - materials - - - - - - -
        -
        three.js - Phong Material Variantions by Ben Houston.
        - - - - - - - - - - - diff --git a/examples/webgl_materials_variations_physical.html b/examples/webgl_materials_variations_physical.html deleted file mode 100644 index a23be64d2ec100..00000000000000 --- a/examples/webgl_materials_variations_physical.html +++ /dev/null @@ -1,228 +0,0 @@ - - - - three.js webgl - materials - - - - - - -
        -
        three.js - Physical Material Variations by Ben Houston.

        - Note: Every second sphere has an IBL environment map on it.
        - - - - - - - - - - - diff --git a/examples/webgl_materials_variations_standard.html b/examples/webgl_materials_variations_standard.html deleted file mode 100644 index 93d0ff3711e52c..00000000000000 --- a/examples/webgl_materials_variations_standard.html +++ /dev/null @@ -1,231 +0,0 @@ - - - - three.js webgl - materials - - - - - - -
        -
        three.js - Standard Material Variations by Ben Houston.

        - Note: Every second sphere has an IBL environment map on it.
        - - - - - - - - - - - diff --git a/examples/webgl_materials_variations_toon.html b/examples/webgl_materials_variations_toon.html deleted file mode 100644 index cd233de7c77d1c..00000000000000 --- a/examples/webgl_materials_variations_toon.html +++ /dev/null @@ -1,211 +0,0 @@ - - - - three.js webgl - materials - - - - - - -
        -
        three.js - Toon Material Variantions with OutlineEffect
        - - - - - - - - - - - diff --git a/examples/webgl_materials_video.html b/examples/webgl_materials_video.html index 79cee1392da1b4..abfd620aceb0f5 100644 --- a/examples/webgl_materials_video.html +++ b/examples/webgl_materials_video.html @@ -23,10 +23,6 @@ - - - - - - - - - - - + + + + + diff --git a/examples/webgl_mirror.html b/examples/webgl_mirror.html index 9de8016a950d48..33f56107243131 100644 --- a/examples/webgl_mirror.html +++ b/examples/webgl_mirror.html @@ -20,10 +20,6 @@
        three.js - mirror
        - - - - - - - - - @@ -74,10 +70,10 @@ controls.target.set( 0, 0, 0 ); controls.update(); - scene.add( new THREE.HemisphereLight( 0xffffff, 0x737373, 1 ) ); + scene.add( new THREE.HemisphereLight( 0xffffff, 0x737373, 3 ) ); - const frontLight = new THREE.DirectionalLight( 0xffffff, 0.5 ); - const backLight = new THREE.DirectionalLight( 0xffffff, 0.5 ); + const frontLight = new THREE.DirectionalLight( 0xffffff, 1.5 ); + const backLight = new THREE.DirectionalLight( 0xffffff, 1.5 ); frontLight.position.set( 0, 1, 1 ); backLight.position.set( 0, 1, - 1 ); scene.add( frontLight, backLight ); @@ -328,4 +324,4 @@ - \ No newline at end of file + diff --git a/examples/webgl_modifier_tessellation.html b/examples/webgl_modifier_tessellation.html index 721a2004c89cb9..e41210aac5bae4 100644 --- a/examples/webgl_modifier_tessellation.html +++ b/examples/webgl_modifier_tessellation.html @@ -53,10 +53,6 @@ - - - - - - diff --git a/examples/webgl_morphtargets_horse.html b/examples/webgl_morphtargets_horse.html index 19a135211345d7..111148728d885a 100644 --- a/examples/webgl_morphtargets_horse.html +++ b/examples/webgl_morphtargets_horse.html @@ -22,10 +22,6 @@ model by mirada from rome - - - - - + + + + diff --git a/examples/webgl_multiple_canvases_circle.html b/examples/webgl_multiple_canvases_circle.html deleted file mode 100644 index 861d4ae14f0f9f..00000000000000 --- a/examples/webgl_multiple_canvases_circle.html +++ /dev/null @@ -1,324 +0,0 @@ - - - - three.js webgl - multiple canvases - circle - - - - - - - -
        -
        -
        - - - - - -
        -
        -
        - -
        three.js webgl - multiple canvases - circle
        -
        -
        - Google's Liquid Galaxy simulator.
        - Here 5 monitors are simulated using 3d css. WebGL is then rendered onto each one. -
        -
        - - - - - - - - - - diff --git a/examples/webgl_multiple_canvases_complex.html b/examples/webgl_multiple_canvases_complex.html deleted file mode 100644 index d5ff8ca983fe60..00000000000000 --- a/examples/webgl_multiple_canvases_complex.html +++ /dev/null @@ -1,258 +0,0 @@ - - - - three.js webgl - multiple canvases - complex - - - - - - - -
        - - - -
        -
        three.js webgl - multiple canvases - complex
        - - - - - - - - - - - diff --git a/examples/webgl_multiple_canvases_grid.html b/examples/webgl_multiple_canvases_grid.html deleted file mode 100644 index a3f11e8504b7cb..00000000000000 --- a/examples/webgl_multiple_canvases_grid.html +++ /dev/null @@ -1,279 +0,0 @@ - - - - three.js webgl - multiple canvases - grid - - - - - - -
        -
        -
        -
        - - -
        -
        - - -
        -
        -
        -
        -
        three.js webgl - multiple canvases - grid
        - - - - - - - - - - - diff --git a/examples/webgl_multiple_elements.html b/examples/webgl_multiple_elements.html index 9f9ebd1148a9b8..d32197a551fb23 100644 --- a/examples/webgl_multiple_elements.html +++ b/examples/webgl_multiple_elements.html @@ -63,10 +63,6 @@
        three.js - multiple elements - webgl
        - - - - - - - - - - - - diff --git a/examples/webgl_multiple_rendertargets.html b/examples/webgl_multiple_rendertargets.html new file mode 100644 index 00000000000000..57ec7bf4a8b5db --- /dev/null +++ b/examples/webgl_multiple_rendertargets.html @@ -0,0 +1,265 @@ + + + + three.js webgl - Multiple Render Targets + + + + + + + + + + + + + + +
        + threejs webgl - Multiple RenderTargets +
        + + + + + + + diff --git a/examples/webgl_multiple_scenes_comparison.html b/examples/webgl_multiple_scenes_comparison.html index b64354c43f9e95..f66fa77a41fcba 100644 --- a/examples/webgl_multiple_scenes_comparison.html +++ b/examples/webgl_multiple_scenes_comparison.html @@ -38,10 +38,6 @@
        - - - - - + + + + diff --git a/examples/webgl_nodes_loader_gltf_iridescence.html b/examples/webgl_nodes_loader_gltf_iridescence.html deleted file mode 100644 index cbd5e4a9965dd0..00000000000000 --- a/examples/webgl_nodes_loader_gltf_iridescence.html +++ /dev/null @@ -1,136 +0,0 @@ - - - - three.js webgl - GLTFloader + Iridescence + Nodes - - - - - - -
        - three.js - GLTFLoader + KHR_materials_iridescence + Nodes
        - Iridescence Lamp from glTF-Sample-Models
        - Venice Sunset by HDRI Haven -
        - - - - - - - - - - - diff --git a/examples/webgl_nodes_loader_gltf_sheen.html b/examples/webgl_nodes_loader_gltf_sheen.html deleted file mode 100644 index 5136336e5987f1..00000000000000 --- a/examples/webgl_nodes_loader_gltf_sheen.html +++ /dev/null @@ -1,139 +0,0 @@ - - - - three.js webgl - GLTFloader + Sheen + Nodes - - - - - - - -
        - three.js - GLTFLoader + KHR_materials_sheen + Nodes
        - Sheen Chair from glTF-Sample-Models -
        - - - - - - - - - - - diff --git a/examples/webgl_nodes_loader_gltf_transmission.html b/examples/webgl_nodes_loader_gltf_transmission.html deleted file mode 100644 index 18f539244e567c..00000000000000 --- a/examples/webgl_nodes_loader_gltf_transmission.html +++ /dev/null @@ -1,160 +0,0 @@ - - - - three.js webgl - GLTFloader + transmission + nodes - - - - - - -
        - three.js - GLTFLoader + KHR_materials_transmission + Nodes
        - Iridescent Dish With Olives by Eric Chadwick
        - Royal Esplanade by HDRI Haven -
        - - - - - - - - - - - diff --git a/examples/webgl_nodes_loader_materialx.html b/examples/webgl_nodes_loader_materialx.html deleted file mode 100644 index ac079377b09be5..00000000000000 --- a/examples/webgl_nodes_loader_materialx.html +++ /dev/null @@ -1,194 +0,0 @@ - - - - three.js webgl - MaterialX loader - - - - - - - -
        - three.js - MaterialXLoader
        -
        - - - - - - - - - - - diff --git a/examples/webgl_nodes_materials_instance_uniform.html b/examples/webgl_nodes_materials_instance_uniform.html deleted file mode 100644 index b621f86efd16d5..00000000000000 --- a/examples/webgl_nodes_materials_instance_uniform.html +++ /dev/null @@ -1,231 +0,0 @@ - - - - three.js webgl - material instance uniform - - - - - -
        - three.js - webgl material instance uniform -
        - - - - - - - - - - - diff --git a/examples/webgl_nodes_materials_physical_clearcoat.html b/examples/webgl_nodes_materials_physical_clearcoat.html deleted file mode 100644 index 33b04a600bf53b..00000000000000 --- a/examples/webgl_nodes_materials_physical_clearcoat.html +++ /dev/null @@ -1,254 +0,0 @@ - - - - three.js webgl - materials - clearcoat nodes - - - - - -
        - three.js webgl - materials - clearcoat nodes -
        - - - - - - - - - - diff --git a/examples/webgl_nodes_materials_standard.html b/examples/webgl_nodes_materials_standard.html deleted file mode 100644 index 4b4f3aa172943d..00000000000000 --- a/examples/webgl_nodes_materials_standard.html +++ /dev/null @@ -1,269 +0,0 @@ - - - - three.js webgl - materials - standard (nodes) - - - - - - -
        - three.js - webgl physically based material
        - Cerberus(FFVII Gun) model by Andrew Maximov. -
        - - - - - - - - - - - diff --git a/examples/webgl_nodes_materialx_noise.html b/examples/webgl_nodes_materialx_noise.html deleted file mode 100644 index 4d0e076b99a235..00000000000000 --- a/examples/webgl_nodes_materialx_noise.html +++ /dev/null @@ -1,207 +0,0 @@ - - - - three.js webgl - materials - materialx nodes - - - - - -
        - three.js webgl - MaterialX - Noise -
        - - - - - - - - - - diff --git a/examples/webgl_nodes_points.html b/examples/webgl_nodes_points.html deleted file mode 100644 index 8b5a571b40d9d1..00000000000000 --- a/examples/webgl_nodes_points.html +++ /dev/null @@ -1,209 +0,0 @@ - - - - three.js webgl - node particles - - - - - - -
        - three.js - webgl node particles example -
        - - - - - - - - - - diff --git a/examples/webgl_panorama_cube.html b/examples/webgl_panorama_cube.html index a40637eca3492d..8faf84e2a33a2b 100644 --- a/examples/webgl_panorama_cube.html +++ b/examples/webgl_panorama_cube.html @@ -12,10 +12,6 @@ three.js webgl - cube panorama demo - - - - - + +
        + three.js - Performance
        + Dungeon - Low Poly Game Level Challenge by + Warkarma
        + Royal Esplanade from HDRI Haven +
        diff --git a/examples/webgl_performance_shader.html b/examples/webgl_performance_shader.html deleted file mode 100644 index 9245de57716cc6..00000000000000 --- a/examples/webgl_performance_shader.html +++ /dev/null @@ -1,250 +0,0 @@ - - - - three.js webgl - materials - shaders [lava] - - - - - - -
        -
        three.js - shader material demo. featuring lava shader by TheGameMaker
        - - - - - - - - - - - - - - - diff --git a/examples/webgl_performance_static.html b/examples/webgl_performance_static.html deleted file mode 100644 index e6d6e5cd6dd338..00000000000000 --- a/examples/webgl_performance_static.html +++ /dev/null @@ -1,147 +0,0 @@ - - - - three.js webgl - performance [static] - - - - - - - - - - - - - - - - - diff --git a/examples/webgl_pmrem_test.html b/examples/webgl_pmrem_test.html index 8de1dd384416ff..7d1d26f9c06f73 100644 --- a/examples/webgl_pmrem_test.html +++ b/examples/webgl_pmrem_test.html @@ -19,10 +19,6 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/webgl_postprocessing_dof.html b/examples/webgl_postprocessing_dof.html index 4fe2374d950706..c752bdb3ef2d50 100644 --- a/examples/webgl_postprocessing_dof.html +++ b/examples/webgl_postprocessing_dof.html @@ -13,10 +13,6 @@ shader by Martins Upitis - - - - - - - - + + + + diff --git a/examples/webgl_postprocessing_masking.html b/examples/webgl_postprocessing_masking.html index 5acf18b7000908..b66b8a0fc52b71 100644 --- a/examples/webgl_postprocessing_masking.html +++ b/examples/webgl_postprocessing_masking.html @@ -10,10 +10,6 @@
        - - - - + + + + diff --git a/examples/webgl_postprocessing_outline.html b/examples/webgl_postprocessing_outline.html index d9de5dc419634b..3a5ca81419c6e3 100644 --- a/examples/webgl_postprocessing_outline.html +++ b/examples/webgl_postprocessing_outline.html @@ -11,10 +11,6 @@ three.js - Outline Pass by Prashant Sharma and Ben Houston

        - - - - - - - - - - - - - - + + + + diff --git a/examples/webgl_postprocessing_unreal_bloom.html b/examples/webgl_postprocessing_unreal_bloom.html index 7c64d8fec36630..b5327a18fad1fe 100644 --- a/examples/webgl_postprocessing_unreal_bloom.html +++ b/examples/webgl_postprocessing_unreal_bloom.html @@ -24,10 +24,6 @@ Mike Murdock, CC Attribution. - - - - - - - - + + + + + diff --git a/examples/webgl_raycaster_bvh.html b/examples/webgl_raycaster_bvh.html index 212cbafac583dc..e76003ae24894a 100644 --- a/examples/webgl_raycaster_bvh.html +++ b/examples/webgl_raycaster_bvh.html @@ -24,16 +24,12 @@ See main project repository for more information and examples on high performance spatial queries. - - - - @@ -41,7 +37,7 @@ - - - - - - - - - - - - - - - diff --git a/examples/webgl_read_float_buffer.html b/examples/webgl_read_float_buffer.html index 511db5b0764585..ecf9035eb4a35a 100644 --- a/examples/webgl_read_float_buffer.html +++ b/examples/webgl_read_float_buffer.html @@ -10,7 +10,7 @@
        - three.js read float pixels webgl example
        + three.js read linear-srgb float pixel data
        @@ -22,6 +22,7 @@ void main() { gl_FragColor = texture2D( tDiffuse, vUv ); + #include } @@ -58,10 +59,6 @@ - - - - - - @@ -64,14 +60,12 @@ import { RGBELoader } from 'three/addons/loaders/RGBELoader.js'; import { LDrawLoader } from 'three/addons/loaders/LDrawLoader.js'; import { LDrawUtils } from 'three/addons/utils/LDrawUtils.js'; - import { FullScreenQuad } from 'three/addons/postprocessing/Pass.js'; - - import { PhysicalPathTracingMaterial, PathTracingRenderer, MaterialReducer, BlurredEnvMapGenerator, PathTracingSceneGenerator, GradientEquirectTexture } from 'three-gpu-pathtracer'; + import { LDrawConditionalLineMaterial } from 'three/addons/materials/LDrawConditionalLineMaterial.js'; + import { WebGLPathTracer, BlurredEnvMapGenerator, GradientEquirectTexture } from 'three-gpu-pathtracer'; let progressBarDiv, samplesEl; let camera, scene, renderer, controls, gui; - let pathTracer, sceneInfo, fsQuad, floor; - let delaySamples = 0; + let pathTracer, floor, gradientMap; const params = { enable: true, @@ -93,7 +87,6 @@ }; init(); - render(); function init() { @@ -101,41 +94,32 @@ camera.position.set( 150, 200, 250 ); // initialize the renderer - renderer = new THREE.WebGLRenderer( { antialias: true, preserveDrawingBuffer: true, premultipliedAlpha: false } ); + renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true, preserveDrawingBuffer: true, premultipliedAlpha: false } ); renderer.setPixelRatio( window.devicePixelRatio ); renderer.setSize( window.innerWidth, window.innerHeight ); renderer.toneMapping = THREE.ACESFilmicToneMapping; - renderer.setClearColor( 0xdddddd ); document.body.appendChild( renderer.domElement ); - // initialize the pathtracer - pathTracer = new PathTracingRenderer( renderer ); - pathTracer.alpha = true; - pathTracer.tiles.set( params.tiles, params.tiles ); - pathTracer.material = new PhysicalPathTracingMaterial(); - pathTracer.material.setDefine( 'FEATURE_MIS', 1 ); - - const gradientMap = new GradientEquirectTexture(); + gradientMap = new GradientEquirectTexture(); gradientMap.topColor.set( 0xeeeeee ); gradientMap.bottomColor.set( 0xeaeaea ); gradientMap.update(); - pathTracer.material.backgroundMap = gradientMap; - pathTracer.camera = camera; - - fsQuad = new FullScreenQuad( new THREE.MeshBasicMaterial( { - map: pathTracer.target.texture, - blending: THREE.CustomBlending - } ) ); + // initialize the pathtracer + pathTracer = new WebGLPathTracer( renderer ); + pathTracer.filterGlossyFactor = 1; + pathTracer.minSamples = 3; + pathTracer.renderScale = params.resolutionScale; + pathTracer.tiles.set( params.tiles, params.tiles ); // scene scene = new THREE.Scene(); + scene.background = gradientMap; controls = new OrbitControls( camera, renderer.domElement ); controls.addEventListener( 'change', () => { - delaySamples = 5; - pathTracer.reset(); + pathTracer.updateCamera(); } ); @@ -164,7 +148,7 @@ progressBarDiv.innerText = 'Loading...'; let model = null; - let envMap = null; + let environment = null; updateProgressBar( 0 ); showProgressBar(); @@ -172,16 +156,27 @@ // only smooth when not rendering with flat colors to improve processing time const ldrawPromise = new LDrawLoader() + .setConditionalLineMaterial( LDrawConditionalLineMaterial ) .setPath( 'models/ldraw/officialLibrary/' ) .loadAsync( 'models/7140-1-X-wingFighter.mpd_Packed.mpd', onProgress ) .then( function ( legoGroup ) { + // Convert from LDraw coordinates: rotate 180 degrees around OX legoGroup = LDrawUtils.mergeObject( legoGroup ); legoGroup.rotation.x = Math.PI; + legoGroup.updateMatrixWorld(); + model = legoGroup; - // adjust the materials to use transmission, be a bit shinier legoGroup.traverse( c => { + // hide the line segments + if ( c.isLineSegments ) { + + c.visible = false; + + } + + // adjust the materials to use transmission, be a bit shinier if ( c.material ) { c.material.roughness *= 0.25; @@ -193,6 +188,7 @@ newMaterial.opacity = 1.0; newMaterial.transmission = 1.0; + newMaterial.thickness = 1.0; newMaterial.ior = 1.4; newMaterial.roughness = oldMaterial.roughness; newMaterial.metalness = 0.0; @@ -210,12 +206,6 @@ } ); - model = new THREE.Group(); - model.add( legoGroup ); - - // Convert from LDraw coordinates: rotate 180 degrees around OX - model.updateMatrixWorld(); - } ) .catch( onError ); @@ -228,16 +218,19 @@ const envMapGenerator = new BlurredEnvMapGenerator( renderer ); const blurredEnvMap = envMapGenerator.generate( tex, 0 ); - scene.environment = blurredEnvMap; - envMap = blurredEnvMap; + environment = blurredEnvMap; - } ); + } ) + .catch( onError ); await Promise.all( [ envMapPromise, ldrawPromise ] ); hideProgressBar(); document.body.classList.add( 'checkerboard' ); + // set environment map + scene.environment = environment; + // Adjust camera const bbox = new THREE.Box3().setFromObject( model ); const size = bbox.getSize( new THREE.Vector3() ); @@ -247,13 +240,16 @@ controls.position0.set( 2.3, 1, 2 ).multiplyScalar( radius ).add( controls.target0 ); controls.reset(); + // add the model + scene.add( model ); + // add floor floor = new THREE.Mesh( new THREE.PlaneGeometry(), new THREE.MeshStandardMaterial( { side: THREE.DoubleSide, - roughness: 0.01, - metalness: 1, + roughness: params.roughness, + metalness: params.metalness, map: generateRadialFloorTexture( 1024 ), transparent: true, } ), @@ -261,50 +257,15 @@ floor.scale.setScalar( 2500 ); floor.rotation.x = - Math.PI / 2; floor.position.y = bbox.min.y; - model.add( floor ); - model.updateMatrixWorld(); - - // de-duplicate and reduce the number of materials used in place - const reducer = new MaterialReducer(); - reducer.process( model ); + scene.add( floor ); // reset the progress bar to display bvh generation progressBarDiv.innerText = 'Generating BVH...'; updateProgressBar( 0 ); - const generator = new PathTracingSceneGenerator(); - const result = generator.generate( model ); + pathTracer.setScene( scene, camera ); - // add the model to the scene - sceneInfo = result; - sceneInfo.scene.traverse( c => { - - if ( c.isLineSegments ) { - - c.visible = false; - - } - - } ); - scene.add( sceneInfo.scene ); - - // update the material - const { bvh, textures, materials } = result; - const geometry = bvh.geometry; - const material = pathTracer.material; - - material.bvh.updateFrom( bvh ); - material.attributesArray.updateFrom( - geometry.attributes.normal, - geometry.attributes.tangent, - geometry.attributes.uv, - geometry.attributes.color, - ); - material.materialIndexAttribute.updateFrom( geometry.attributes.materialIndex ); - material.textures.setTextures( renderer, 2048, 2048, textures ); - material.materials.updateFrom( materials, textures ); - pathTracer.material.envMapInfo.updateFrom( envMap ); - pathTracer.reset(); + renderer.setAnimationLoop( animate ); } @@ -312,19 +273,17 @@ const w = window.innerWidth; const h = window.innerHeight; - const scale = params.resolutionScale; const dpr = window.devicePixelRatio; - pathTracer.setSize( w * scale * dpr, h * scale * dpr ); - pathTracer.reset(); - renderer.setSize( w, h ); - renderer.setPixelRatio( window.devicePixelRatio * scale ); + renderer.setPixelRatio( dpr ); const aspect = w / h; camera.aspect = aspect; camera.updateProjectionMatrix(); + pathTracer.updateCamera(); + } function createGUI() { @@ -341,25 +300,31 @@ gui.add( params, 'toneMapping' ); gui.add( params, 'transparentBackground' ).onChange( v => { - pathTracer.material.backgroundAlpha = v ? 0 : 1; - renderer.setClearAlpha( v ? 0 : 1 ); + scene.background = v ? null : gradientMap; + pathTracer.updateEnvironment(); + + } ); + gui.add( params, 'resolutionScale', 0.1, 1.0, 0.1 ).onChange( v => { + + pathTracer.renderScale = v; pathTracer.reset(); } ); - gui.add( params, 'resolutionScale', 0.1, 1.0, 0.1 ).onChange( onWindowResize ); - gui.add( params, 'tiles', 1, 3, 1 ).onChange( v => { + gui.add( params, 'tiles', 1, 6, 1 ).onChange( v => { pathTracer.tiles.set( v, v ); } ); - gui.add( params, 'roughness', 0, 1 ).name( 'floor roughness' ).onChange( () => { + gui.add( params, 'roughness', 0, 1 ).name( 'floor roughness' ).onChange( v => { - pathTracer.reset(); + floor.material.roughness = v; + pathTracer.updateMaterials(); } ); - gui.add( params, 'metalness', 0, 1 ).name( 'floor metalness' ).onChange( () => { + gui.add( params, 'metalness', 0, 1 ).name( 'floor metalness' ).onChange( v => { - pathTracer.reset(); + floor.material.metalness = v; + pathTracer.updateMaterials(); } ); gui.add( params, 'download' ).name( 'download image' ); @@ -373,58 +338,20 @@ renderFolder.$children.appendChild( samplesEl ); renderFolder.open(); - } // - function render() { - - requestAnimationFrame( render ); - - if ( ! sceneInfo ) { - - return; - - } + function animate() { renderer.toneMapping = params.toneMapping ? THREE.ACESFilmicToneMapping : THREE.NoToneMapping; - if ( pathTracer.samples < 1.0 || ! params.enable ) { - - renderer.render( scene, camera ); + const samples = Math.floor( pathTracer.samples ); + samplesEl.innerText = `samples: ${ samples }`; - } - - if ( params.enable && delaySamples === 0 ) { - - const samples = Math.floor( pathTracer.samples ); - samplesEl.innerText = `samples: ${ samples }`; - - floor.material.roughness = params.roughness; - floor.material.metalness = params.metalness; - - pathTracer.material.materials.updateFrom( sceneInfo.materials, sceneInfo.textures ); - pathTracer.material.filterGlossyFactor = 1; - pathTracer.material.physicalCamera.updateFrom( camera ); - - camera.updateMatrixWorld(); - - if ( ! params.pause || pathTracer.samples < 1 ) { - - pathTracer.update(); - - } - - renderer.autoClear = false; - fsQuad.render( renderer ); - renderer.autoClear = true; - - } else if ( delaySamples > 0 ) { - - delaySamples --; - - } + pathTracer.enablePathTracing = params.enable; + pathTracer.pausePathTracing = params.pause; + pathTracer.renderSample(); samplesEl.innerText = `samples: ${ Math.floor( pathTracer.samples ) }`; @@ -436,7 +363,7 @@ updateProgressBar( xhr.loaded / xhr.total ); - console.log( Math.round( xhr.loaded / xhr.total * 100, 2 ) + '% downloaded' ); + console.log( Math.round( xhr.loaded / xhr.total * 100 ) + '% downloaded' ); } diff --git a/examples/webgl_rendertarget_texture2darray.html b/examples/webgl_rendertarget_texture2darray.html new file mode 100644 index 00000000000000..ed11a9cd384e5b --- /dev/null +++ b/examples/webgl_rendertarget_texture2darray.html @@ -0,0 +1,280 @@ + + + + three.js webgl - 2D texture array framebuffer attachment + + + + + + + + + + + + + + +
        + + three.js + + - 2D Texture array framebuffer color attachment +
        + +

        + This example shows how to render to an array of 2D texture.
        + WebGL2 allows to render to specific "layers" in 3D texture and array of textures. +

        + + Scanned head data by + Divine Augustine
        + licensed under + CPOL +
        + + + + + + diff --git a/examples/webgl_reverse_depth_buffer.html b/examples/webgl_reverse_depth_buffer.html new file mode 100644 index 00000000000000..a25e27d7b2c4ba --- /dev/null +++ b/examples/webgl_reverse_depth_buffer.html @@ -0,0 +1,296 @@ + + + + three.js webgl - reverse depth buffer + + + + + + +
        +

        normal z-buffer

        +

        logarithmic z-buffer

        +

        reverse z-buffer

        +
        + +
        + three.js - reverse depth buffer
        +
        + +
        + Note: For best results, a floating-point depth buffer should be used with post-processing. See Visualizing Depth Precision. +
        + + + + + + + diff --git a/examples/webgl_rtt.html b/examples/webgl_rtt.html index 56ea4b7e5177fc..3dc7693082d4ee 100644 --- a/examples/webgl_rtt.html +++ b/examples/webgl_rtt.html @@ -19,6 +19,7 @@ void main() { gl_FragColor = texture2D( tDiffuse, vUv ); + #include } @@ -55,10 +56,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/webgl_shader_lava.html b/examples/webgl_shader_lava.html index 22f75492f85511..8da0926a0d4f7b 100644 --- a/examples/webgl_shader_lava.html +++ b/examples/webgl_shader_lava.html @@ -74,10 +74,6 @@ - - - - - - - - - - - - - diff --git a/examples/webgl_shadow_contact.html b/examples/webgl_shadow_contact.html index fd9e55feaad0a6..27007a44471939 100644 --- a/examples/webgl_shadow_contact.html +++ b/examples/webgl_shadow_contact.html @@ -19,9 +19,6 @@
        three.js webgl - contact shadows
        - - - - - - - - - diff --git a/examples/webgl_shadowmap_performance.html b/examples/webgl_shadowmap_performance.html index 70b199f1754644..44ddd4f48768bd 100644 --- a/examples/webgl_shadowmap_performance.html +++ b/examples/webgl_shadowmap_performance.html @@ -14,10 +14,6 @@ move camera with WASD / RF + mouse - - - - - - diff --git a/examples/webgl_shadowmap_viewer.html b/examples/webgl_shadowmap_viewer.html index 04423fb19bfd38..16ee18e937bdd3 100644 --- a/examples/webgl_shadowmap_viewer.html +++ b/examples/webgl_shadowmap_viewer.html @@ -11,10 +11,6 @@ three.js - ShadowMapViewer example by arya-s - - - - - - - - - - - - - + + + + diff --git a/examples/webgl_texture2darray.html b/examples/webgl_texture2darray.html new file mode 100644 index 00000000000000..97be775df97ebd --- /dev/null +++ b/examples/webgl_texture2darray.html @@ -0,0 +1,179 @@ + + + + three.js webgl - 2D texture array + + + + + + + + +
        + three.js - 2D Texture array
        + Scanned head data by + Divine Augustine
        + licensed under + CPOL +
        + + + + + + diff --git a/examples/webgl_texture2darray_compressed.html b/examples/webgl_texture2darray_compressed.html new file mode 100644 index 00000000000000..1f0b4a7fabdb3b --- /dev/null +++ b/examples/webgl_texture2darray_compressed.html @@ -0,0 +1,167 @@ + + + + three.js webgl - 2D compressed texture array + + + + + + + + +
        + three.js - 2D Compressed Texture Array
        + Loop from the movie Spirited away + by the Studio Ghibli
        +
        + + + + + + diff --git a/examples/webgl_texture2darray_layerupdate.html b/examples/webgl_texture2darray_layerupdate.html new file mode 100644 index 00000000000000..3c9f89841fea18 --- /dev/null +++ b/examples/webgl_texture2darray_layerupdate.html @@ -0,0 +1,202 @@ + + + + three.js webgl - texture array layer update + + + + + + + + +
        + three.js - 2D Compressed Texture Array Layer Updates
        + Loop from the movie Spirited away + by the Studio Ghibli
        +
        + + + + + + diff --git a/examples/webgl_texture3d.html b/examples/webgl_texture3d.html new file mode 100644 index 00000000000000..983d55632c511c --- /dev/null +++ b/examples/webgl_texture3d.html @@ -0,0 +1,174 @@ + + + + three.js webgl - volume rendering example + + + + + + +
        + three.js - Float volume render test (mip / isosurface) +
        +
        + + + + + + + diff --git a/examples/webgl_texture3d_partialupdate.html b/examples/webgl_texture3d_partialupdate.html new file mode 100644 index 00000000000000..09e84283630edf --- /dev/null +++ b/examples/webgl_texture3d_partialupdate.html @@ -0,0 +1,366 @@ + + + + three.js webgl2 - volume - cloud + + + + + + +
        + three.js webgl2 - volume - cloud +
        + + + + + + + diff --git a/examples/webgl_tiled_forward.html b/examples/webgl_tiled_forward.html deleted file mode 100644 index c7d4b4ab9df237..00000000000000 --- a/examples/webgl_tiled_forward.html +++ /dev/null @@ -1,396 +0,0 @@ - - - - three.js webgl - tiled forward lighting - - - - - -
        - threejs - Tiled forward lighting
        - Created by wizgrav. -
        - - - - - - - - - - diff --git a/examples/webgl_tonemapping.html b/examples/webgl_tonemapping.html index fdfaea92bbaa3b..a19ba77ce27c8e 100644 --- a/examples/webgl_tonemapping.html +++ b/examples/webgl_tonemapping.html @@ -10,15 +10,11 @@
        three.js - Tone Mapping
        - Battle Damaged Sci-fi Helmet by - theblueturtle_
        - Venice Sunset by HDRI Haven + Venice Mask by + DailyArt is licensed under CC Attribution-NonCommercial
        + Venice Sunset from HDRI Haven
        - - - - - - - - - - - diff --git a/examples/webgl_ubo.html b/examples/webgl_ubo.html new file mode 100644 index 00000000000000..0ae58dd241541b --- /dev/null +++ b/examples/webgl_ubo.html @@ -0,0 +1,346 @@ + + + + three.js WebGL 2 - Uniform Buffer Objects + + + + + + + +
        + three.js - Uniform Buffer Objects +
        +
        + + + + + + + + + + + + + + + + + diff --git a/examples/webgl_ubo_arrays.html b/examples/webgl_ubo_arrays.html new file mode 100644 index 00000000000000..6fe8d627cb3294 --- /dev/null +++ b/examples/webgl_ubo_arrays.html @@ -0,0 +1,303 @@ + + + + three.js WebGL 2 - Uniform Buffer Objects Arrays + + + + + + + +
        + three.js - Uniform Buffer Objects Arrays +
        +
        + + + + + + + + + + + diff --git a/examples/webgl_video_kinect.html b/examples/webgl_video_kinect.html index 4cf433ae8e5f9e..b77bb3b9309179 100644 --- a/examples/webgl_video_kinect.html +++ b/examples/webgl_video_kinect.html @@ -68,10 +68,6 @@ - - - - - + + + + + diff --git a/examples/webgl_volume_instancing.html b/examples/webgl_volume_instancing.html new file mode 100644 index 00000000000000..c3e0d91f1f7098 --- /dev/null +++ b/examples/webgl_volume_instancing.html @@ -0,0 +1,266 @@ + + + + three.js webgl2 - volume - instancing + + + + + + +
        + three.js webgl2 - volume - instancing +
        + + + + + + + diff --git a/examples/webgl_volume_perlin.html b/examples/webgl_volume_perlin.html new file mode 100644 index 00000000000000..131b9bd5a86e58 --- /dev/null +++ b/examples/webgl_volume_perlin.html @@ -0,0 +1,251 @@ + + + + three.js webgl2 - volume + + + + + + +
        + three.js webgl2 - volume +
        + + + + + + + diff --git a/examples/webgl_watch.html b/examples/webgl_watch.html new file mode 100644 index 00000000000000..a9604451103a4f --- /dev/null +++ b/examples/webgl_watch.html @@ -0,0 +1,348 @@ + + + + three.js webgl - watch + + + + + + +
        +
        + three.js - Rolex + aomap - 610 ko +
        + + + + + + + diff --git a/examples/webgl_water.html b/examples/webgl_water.html deleted file mode 100644 index 1518e6f7151a41..00000000000000 --- a/examples/webgl_water.html +++ /dev/null @@ -1,216 +0,0 @@ - - - - three.js - water - - - - - - -
        -
        - three.js - water -
        - - - - - - - - - - - diff --git a/examples/webgl_water_flowmap.html b/examples/webgl_water_flowmap.html deleted file mode 100644 index c57222e74d5128..00000000000000 --- a/examples/webgl_water_flowmap.html +++ /dev/null @@ -1,153 +0,0 @@ - - - - three.js - water flow map - - - - - - -
        -
        - three.js - water flow map -
        - - - - - - - - - - - diff --git a/examples/webgl_worker_offscreencanvas.html b/examples/webgl_worker_offscreencanvas.html index b5b96b84247669..f4027c2dab4c09 100644 --- a/examples/webgl_worker_offscreencanvas.html +++ b/examples/webgl_worker_offscreencanvas.html @@ -53,7 +53,7 @@
        three.js - offscreen canvas (about)
        -

        Your browser does not support OffscreenCanvas. Check the browser support here

        +

        Your browser does not support OffscreenCanvas with a WebGL Context. Check the browser support here

        @@ -64,10 +64,6 @@
        - - - - + + + + diff --git a/examples/webgpu_animation_retargeting_readyplayer.html b/examples/webgpu_animation_retargeting_readyplayer.html new file mode 100644 index 00000000000000..98fa6b5443bedc --- /dev/null +++ b/examples/webgpu_animation_retargeting_readyplayer.html @@ -0,0 +1,211 @@ + + + three.js webgpu - animation retargeting + + + + + + +
        + three.js webgpu - animation retargeting
        + mixamo to readyplayer.me +
        + + + + + + diff --git a/examples/webgpu_audio_processing.html b/examples/webgpu_audio_processing.html deleted file mode 100644 index 088826a3d480a3..00000000000000 --- a/examples/webgpu_audio_processing.html +++ /dev/null @@ -1,244 +0,0 @@ - - - three.js - WebGPU - Audio Processing - - - - - - -
        - -
        - -
        - three.js WebGPU - Audio Processing -
        Click on screen to process the audio using WebGPU. -
        - - - - - - - - diff --git a/examples/webgpu_backdrop.html b/examples/webgpu_backdrop.html index 54d35bb1eb842b..a01287c26d7df9 100644 --- a/examples/webgpu_backdrop.html +++ b/examples/webgpu_backdrop.html @@ -12,14 +12,13 @@ three.js WebGPU - Backdrop - - @@ -27,13 +26,10 @@ + + + + diff --git a/examples/webgpu_backdrop_water.html b/examples/webgpu_backdrop_water.html new file mode 100644 index 00000000000000..e18b980b99a113 --- /dev/null +++ b/examples/webgpu_backdrop_water.html @@ -0,0 +1,282 @@ + + + + three.js - WebGPU - Backdrop Water + + + + + + +
        + three.js WebGPU - Backdrop Water +
        + + + + + + diff --git a/examples/webgpu_camera.html b/examples/webgpu_camera.html new file mode 100644 index 00000000000000..da5af7329f7e8b --- /dev/null +++ b/examples/webgpu_camera.html @@ -0,0 +1,268 @@ + + + + three.js webgpu - cameras + + + + + + +
        three.js - cameras
        + O orthographic P perspective +
        + + + + + + + diff --git a/examples/webgpu_camera_array.html b/examples/webgpu_camera_array.html new file mode 100644 index 00000000000000..c184f31e539033 --- /dev/null +++ b/examples/webgpu_camera_array.html @@ -0,0 +1,154 @@ + + + + three.js webgpu - arraycamera + + + + + + + + + + + + diff --git a/examples/webgpu_camera_logarithmicdepthbuffer.html b/examples/webgpu_camera_logarithmicdepthbuffer.html new file mode 100644 index 00000000000000..0bc40b4bd46fcf --- /dev/null +++ b/examples/webgpu_camera_logarithmicdepthbuffer.html @@ -0,0 +1,343 @@ + + + + three.js webgpu - cameras - logarithmic depth buffer + + + + + + + +
        +

        normal z-buffer

        +

        logarithmic z-buffer

        +
        +
        + +
        + three.js - cameras - logarithmic depth buffer
        + mousewheel to dolly out +
        + + + + + + diff --git a/examples/webgpu_caustics.html b/examples/webgpu_caustics.html new file mode 100644 index 00000000000000..4529702e1c076a --- /dev/null +++ b/examples/webgpu_caustics.html @@ -0,0 +1,247 @@ + + + + three.js webgpu - caustics + + + + + + +
        + three.js webgpu - realtime caustics +
        + + + + + + diff --git a/examples/webgpu_centroid_sampling.html b/examples/webgpu_centroid_sampling.html new file mode 100644 index 00000000000000..f562353ee36bde --- /dev/null +++ b/examples/webgpu_centroid_sampling.html @@ -0,0 +1,290 @@ + + + three.js webgpu - centroid sampling + + + + + + +
        +
        +
        antialising disabled
        +
        +
        +
        antialising enabled
        +
        +
        + + + + + + diff --git a/examples/webgpu_clearcoat.html b/examples/webgpu_clearcoat.html new file mode 100644 index 00000000000000..80939996ddcd41 --- /dev/null +++ b/examples/webgpu_clearcoat.html @@ -0,0 +1,248 @@ + + + + three.js webgpu - materials - clearcoat + + + + + +
        + three.js webgpu - clearcoat +
        + + + + + + \ No newline at end of file diff --git a/examples/webgpu_clipping.html b/examples/webgpu_clipping.html new file mode 100644 index 00000000000000..bde07ea006a9f8 --- /dev/null +++ b/examples/webgpu_clipping.html @@ -0,0 +1,277 @@ + + + + three.js webgpu - clipping planes + + + + + +
        + three.js webgpu - clipping +
        + + + + + + diff --git a/examples/webgpu_compute.html b/examples/webgpu_compute.html deleted file mode 100644 index 90cd3b3260f8de..00000000000000 --- a/examples/webgpu_compute.html +++ /dev/null @@ -1,192 +0,0 @@ - - - three.js - WebGPU - Compute - - - - - - -
        - three.js WebGPU - Compute -
        - - - - - - - - diff --git a/examples/webgpu_compute_audio.html b/examples/webgpu_compute_audio.html new file mode 100644 index 00000000000000..0668404d2b3e5d --- /dev/null +++ b/examples/webgpu_compute_audio.html @@ -0,0 +1,230 @@ + + + three.js - WebGPU - Audio Processing + + + + + + +
        + +
        + +
        + three.js WebGPU - Audio Processing +
        Click on screen to process the audio using WebGPU. +
        + + + + + + diff --git a/examples/webgpu_compute_birds.html b/examples/webgpu_compute_birds.html new file mode 100644 index 00000000000000..9129cc6dec9290 --- /dev/null +++ b/examples/webgpu_compute_birds.html @@ -0,0 +1,520 @@ + + + + three.js webgpu - compute - flocking + + + + + + + +
        + three.js - webgpu compute birds
        + Move mouse to disturb birds. +
        + + + + + + \ No newline at end of file diff --git a/examples/webgpu_compute_cloth.html b/examples/webgpu_compute_cloth.html new file mode 100644 index 00000000000000..2543ab108f6ab7 --- /dev/null +++ b/examples/webgpu_compute_cloth.html @@ -0,0 +1,564 @@ + + + + three.js webgpu - compute cloth + + + + + +
        + three.js webgpu - compute cloth
        + Simple cloth simulation with a verlet system running in compute shaders +
        + + + + + + \ No newline at end of file diff --git a/examples/webgpu_compute_geometry.html b/examples/webgpu_compute_geometry.html new file mode 100644 index 00000000000000..d8b7b4c8ad1c04 --- /dev/null +++ b/examples/webgpu_compute_geometry.html @@ -0,0 +1,237 @@ + + + + three.js webgpu - compute geometry + + + + + + +
        + three.js webgpu - compute geometry +
        + + + + + + diff --git a/examples/webgpu_compute_particles.html b/examples/webgpu_compute_particles.html new file mode 100644 index 00000000000000..d9579f61999e7c --- /dev/null +++ b/examples/webgpu_compute_particles.html @@ -0,0 +1,307 @@ + + + three.js - WebGPU - Compute Particles + + + + + + +
        + three.js WebGPU - Compute - 500k Particles +
        +
        + + + + + + diff --git a/examples/webgpu_compute_particles_fluid.html b/examples/webgpu_compute_particles_fluid.html new file mode 100644 index 00000000000000..0e42532e26ac46 --- /dev/null +++ b/examples/webgpu_compute_particles_fluid.html @@ -0,0 +1,569 @@ + + + + three.js webgpu - compute fluid particles + + + + + + +
        + three.js webgpu - fluid particles
        + MLS-MPM particle simulation running in compute shaders +
        + + + + + + diff --git a/examples/webgpu_compute_particles_rain.html b/examples/webgpu_compute_particles_rain.html new file mode 100644 index 00000000000000..d1905b46ca83df --- /dev/null +++ b/examples/webgpu_compute_particles_rain.html @@ -0,0 +1,367 @@ + + + three.js - WebGPU - Compute Particles Rain + + + + + + +
        + three.js WebGPU - GPU Compute Rain +
        + + + + + + diff --git a/examples/webgpu_compute_particles_snow.html b/examples/webgpu_compute_particles_snow.html new file mode 100644 index 00000000000000..3a87f1b15e1751 --- /dev/null +++ b/examples/webgpu_compute_particles_snow.html @@ -0,0 +1,369 @@ + + + three.js - WebGPU - Compute Particles Snow + + + + + + +
        + three.js WebGPU - Compute Snow - 100K Particles +
        + + + + + + diff --git a/examples/webgpu_compute_points.html b/examples/webgpu_compute_points.html new file mode 100644 index 00000000000000..02e1ff5f08a4ea --- /dev/null +++ b/examples/webgpu_compute_points.html @@ -0,0 +1,194 @@ + + + three.js - WebGPU - Compute + + + + + + +
        + three.js WebGPU - Compute - 300000 Points +
        + + + + + + diff --git a/examples/webgpu_compute_sort_bitonic.html b/examples/webgpu_compute_sort_bitonic.html new file mode 100644 index 00000000000000..10201cf7279b26 --- /dev/null +++ b/examples/webgpu_compute_sort_bitonic.html @@ -0,0 +1,587 @@ + + + three.js webgpu - storage pbo external element + + + + + + +
        + three.js +
        This example demonstrates a bitonic sort running step by step in a compute shader. +
        The left canvas swaps values within workgroup local arrays. The right swaps values within storage buffers. +
        Reference implementation by Tim Gfrerer +
        +
        +
        +
        + + + + + + \ No newline at end of file diff --git a/examples/webgpu_compute_texture.html b/examples/webgpu_compute_texture.html new file mode 100644 index 00000000000000..5b8be54996c912 --- /dev/null +++ b/examples/webgpu_compute_texture.html @@ -0,0 +1,134 @@ + + + three.js - WebGPU - Compute Texture + + + + + + +
        + three.js WebGPU - Compute Texture +
        + + + + + + diff --git a/examples/webgpu_compute_texture_pingpong.html b/examples/webgpu_compute_texture_pingpong.html new file mode 100644 index 00000000000000..a56305d5fb0cbb --- /dev/null +++ b/examples/webgpu_compute_texture_pingpong.html @@ -0,0 +1,223 @@ + + + three.js - WebGPU - Compute Ping/Pong Texture + + + + + + +
        + three.js WebGPU - Compute Ping/Pong Texture +
        + + + + + + diff --git a/examples/webgpu_compute_water.html b/examples/webgpu_compute_water.html new file mode 100644 index 00000000000000..f14e5b799ff4f2 --- /dev/null +++ b/examples/webgpu_compute_water.html @@ -0,0 +1,594 @@ + + + + three.js webgpu - compute water + + + + + + +
        + three.js - webgpu compute water
        + Click and move mouse to disturb water. +
        + + + + + + diff --git a/examples/webgpu_cubemap_adjustments.html b/examples/webgpu_cubemap_adjustments.html index 8d4c694a1f6a28..7b7d0b579a12c7 100644 --- a/examples/webgpu_cubemap_adjustments.html +++ b/examples/webgpu_cubemap_adjustments.html @@ -14,16 +14,13 @@ theblueturtle_
        - - - - @@ -31,10 +28,7 @@ + + + + + \ No newline at end of file diff --git a/examples/webgpu_cubemap_mix.html b/examples/webgpu_cubemap_mix.html index d356d6e4cd35e1..0fff79122a9927 100644 --- a/examples/webgpu_cubemap_mix.html +++ b/examples/webgpu_cubemap_mix.html @@ -14,16 +14,13 @@ theblueturtle_
        - - - - @@ -31,10 +28,7 @@ + + + + diff --git a/examples/webgpu_custom_fog_background.html b/examples/webgpu_custom_fog_background.html new file mode 100644 index 00000000000000..f666a5006b4145 --- /dev/null +++ b/examples/webgpu_custom_fog_background.html @@ -0,0 +1,147 @@ + + + + three.js webgpu - custom fog background + + + + + + + +
        + three.js webgpu - custom fog background
        + Battle Damaged Sci-fi Helmet by + theblueturtle_
        + Royal Esplanade by HDRI Haven +
        + + + + + + + diff --git a/examples/webgpu_depth_texture.html b/examples/webgpu_depth_texture.html index 8caf557f1f7297..bd57e1322a9834 100644 --- a/examples/webgpu_depth_texture.html +++ b/examples/webgpu_depth_texture.html @@ -1,6 +1,6 @@ - three.js - WebGPU - Depth Texture + three.js webgpu - depth texture @@ -8,17 +8,16 @@
        - three.js WebGPU - Depth Texture + three.js webgpu - depth texture
        - - @@ -26,17 +25,13 @@ + + + + + diff --git a/examples/webgpu_equirectangular.html b/examples/webgpu_equirectangular.html index 76d6f61641f4e5..4ebe4a03e1ef9c 100644 --- a/examples/webgpu_equirectangular.html +++ b/examples/webgpu_equirectangular.html @@ -12,16 +12,13 @@ three.js webgpu - equirectangular panorama demo. photo by Jón Ragnarsson. - - - - @@ -29,10 +26,9 @@ diff --git a/examples/webgpu_instance_mesh.html b/examples/webgpu_instance_mesh.html index a55c39b2123c86..183ae138a1e677 100644 --- a/examples/webgpu_instance_mesh.html +++ b/examples/webgpu_instance_mesh.html @@ -1,7 +1,7 @@ - three.js - WebGPU - Instance Mesh + three.js webgpu - instance mesh @@ -9,17 +9,16 @@
        - three.js - WebGPU - Instance Mesh + three.js webgpu - instance mesh
        - - @@ -27,13 +26,11 @@ + + + + + diff --git a/examples/webgpu_instance_points.html b/examples/webgpu_instance_points.html new file mode 100644 index 00000000000000..667459dfe3d5cc --- /dev/null +++ b/examples/webgpu_instance_points.html @@ -0,0 +1,248 @@ + + + + three.js webgpu - points instanced + + + + + + + +
        + +
        three.js webgpu - instanced points
        + + + + + + + + diff --git a/examples/webgpu_instance_sprites.html b/examples/webgpu_instance_sprites.html new file mode 100644 index 00000000000000..2fb6b1216d6f87 --- /dev/null +++ b/examples/webgpu_instance_sprites.html @@ -0,0 +1,171 @@ + + + + three.js webgpu - instance sprites + + + + + + +
        + three.js webgpu - instance sprites +
        + + + + + + diff --git a/examples/webgpu_instance_uniform.html b/examples/webgpu_instance_uniform.html index 5d312d554bd286..9e5623fa435fb1 100644 --- a/examples/webgpu_instance_uniform.html +++ b/examples/webgpu_instance_uniform.html @@ -1,7 +1,7 @@ - three.js - WebGPU - Instance Uniform + three.js webgpu - instance uniform @@ -9,17 +9,16 @@
        - three.js - WebGPU - Instance Uniform + three.js webgpu - instance uniform
        - - @@ -27,10 +26,7 @@ + + + + + diff --git a/examples/webgpu_layers.html b/examples/webgpu_layers.html new file mode 100644 index 00000000000000..e041136499370f --- /dev/null +++ b/examples/webgpu_layers.html @@ -0,0 +1,209 @@ + + + + three.js webgpu - layers + + + + + + +
        + three.js - webgpu layers +
        + + + + + + + diff --git a/examples/webgpu_lensflares.html b/examples/webgpu_lensflares.html new file mode 100644 index 00000000000000..668e279911660b --- /dev/null +++ b/examples/webgpu_lensflares.html @@ -0,0 +1,187 @@ + + + + three.js webgpu - lensflares + + + + + + +
        + three.js - lensflares
        + textures from ro.me
        + fly with WASD/RF/QE + mouse +
        + + + + + + + diff --git a/examples/webgpu_lightprobe.html b/examples/webgpu_lightprobe.html new file mode 100644 index 00000000000000..098d17546eae74 --- /dev/null +++ b/examples/webgpu_lightprobe.html @@ -0,0 +1,183 @@ + + + + three.js webgpu - light probe + + + + + + +
        + three.js webgpu - light probe +
        + + + + + + + diff --git a/examples/webgpu_lightprobe_cubecamera.html b/examples/webgpu_lightprobe_cubecamera.html new file mode 100644 index 00000000000000..2de0ef0c80849b --- /dev/null +++ b/examples/webgpu_lightprobe_cubecamera.html @@ -0,0 +1,126 @@ + + + + three.js webgpu - light probe from cubeCamera + + + + + + +
        + three.js webgpu - light probe from cubeCamera +
        + + + + + + + diff --git a/examples/webgpu_lights_custom.html b/examples/webgpu_lights_custom.html index 5670bdd897c2ed..afbe5b5dfe5fc1 100644 --- a/examples/webgpu_lights_custom.html +++ b/examples/webgpu_lights_custom.html @@ -1,6 +1,6 @@ - three.js - WebGPU - Custom Lighting Model + three.js webgpu - custom lighting model @@ -8,17 +8,16 @@
        - three.js WebGPU - Custom Lighting Model + three.js webgpu - custom lighting model
        - - @@ -26,48 +25,46 @@ - @@ -30,34 +27,17 @@ import * as THREE from 'three'; - import WebGPU from 'three/addons/capabilities/WebGPU.js'; - import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js'; - - import IESSpotLight from 'three/addons/lights/IESSpotLight.js'; - import { OrbitControls } from './jsm/controls/OrbitControls.js'; - import { IESLoader } from './jsm/loaders/IESLoader.js'; + import { IESLoader } from 'three/addons/loaders/IESLoader.js'; + + import { GUI } from 'three/addons/libs/lil-gui.module.min.js'; let renderer, scene, camera; let lights; async function init() { - if ( WebGPU.isAvailable() === false ) { - - document.body.appendChild( WebGPU.getErrorMessage() ); - - throw new Error( 'No WebGPU support' ); - - } - - // - - scene = new THREE.Scene(); - - // - const iesLoader = new IESLoader().setPath( './ies/' ); //iesLoader.type = THREE.UnsignedByteType; // LDR @@ -70,43 +50,63 @@ // - const spotLight = new IESSpotLight( 0xff0000, 500 ); - spotLight.position.set( 6.5, 1.5, 6.5 ); + scene = new THREE.Scene(); + + // + + const spotLight = new THREE.IESSpotLight( 0xff0000, 500 ); + spotLight.position.set( 6.5, 3, 6.5 ); spotLight.angle = Math.PI / 8; spotLight.penumbra = 0.7; spotLight.distance = 20; + spotLight.castShadow = true; spotLight.iesMap = iesTexture1; + spotLight.userData.helper = new THREE.SpotLightHelper( spotLight ); scene.add( spotLight ); + scene.add( spotLight.target ); + scene.add( spotLight.userData.helper ); // - const spotLight2 = new IESSpotLight( 0x00ff00, 500 ); - spotLight2.position.set( 6.5, 1.5, - 6.5 ); + const spotLight2 = new THREE.IESSpotLight( 0x00ff00, 500 ); + spotLight2.position.set( - 6.5, 3, 6.5 ); spotLight2.angle = Math.PI / 8; spotLight2.penumbra = 0.7; spotLight2.distance = 20; + spotLight2.castShadow = true; spotLight2.iesMap = iesTexture2; + spotLight2.userData.helper = new THREE.SpotLightHelper( spotLight2 ); scene.add( spotLight2 ); + scene.add( spotLight2.target ); + scene.add( spotLight2.userData.helper ); // - const spotLight3 = new IESSpotLight( 0x0000ff, 500 ); - spotLight3.position.set( - 6.5, 1.5, - 6.5 ); + const spotLight3 = new THREE.IESSpotLight( 0x0000ff, 500 ); + spotLight3.position.set( - 6.5, 3, - 6.5 ); spotLight3.angle = Math.PI / 8; spotLight3.penumbra = 0.7; spotLight3.distance = 20; + spotLight3.castShadow = true; spotLight3.iesMap = iesTexture3; + spotLight3.userData.helper = new THREE.SpotLightHelper( spotLight3 ); scene.add( spotLight3 ); + scene.add( spotLight3.target ); + scene.add( spotLight3.userData.helper ); // - const spotLight4 = new IESSpotLight( 0xffffff, 500 ); - spotLight4.position.set( - 6.5, 1.5, 6.5 ); + const spotLight4 = new THREE.IESSpotLight( 0xffffff, 500 ); + spotLight4.position.set( 6.5, 3, - 6.5 ); spotLight4.angle = Math.PI / 8; spotLight4.penumbra = 0.7; spotLight4.distance = 20; + spotLight4.castShadow = true; spotLight4.iesMap = iesTexture4; + spotLight4.userData.helper = new THREE.SpotLightHelper( spotLight4 ); scene.add( spotLight4 ); + scene.add( spotLight4.target ); + scene.add( spotLight4.userData.helper ); // @@ -114,33 +114,64 @@ // - const material = new THREE.MeshPhongMaterial( { color: 0x808080/*, dithering: true*/ } ); + const material = new THREE.MeshPhongMaterial( { color: 0x999999/*, dithering: true*/ } ); const geometry = new THREE.PlaneGeometry( 200, 200 ); const mesh = new THREE.Mesh( geometry, material ); mesh.rotation.x = - Math.PI * 0.5; + mesh.receiveShadow = true; scene.add( mesh ); + const geometry2 = new THREE.BoxGeometry( 2, 2, 2 ); + //const geometry2 = new THREE.IcosahedronGeometry( 1, 5 ); + + const mesh2 = new THREE.Mesh( geometry2, material ); + mesh2.position.y = 1; + mesh2.castShadow = true; + scene.add( mesh2 ); + // - renderer = new WebGPURenderer(); + renderer = new THREE.WebGPURenderer( { antialias: true } ); renderer.setPixelRatio( window.devicePixelRatio ); renderer.setSize( window.innerWidth, window.innerHeight ); renderer.setAnimationLoop( render ); + renderer.shadowMap.enabled = true; document.body.appendChild( renderer.domElement ); camera = new THREE.PerspectiveCamera( 35, window.innerWidth / window.innerHeight, .1, 100 ); camera.position.set( 16, 4, 1 ); const controls = new OrbitControls( camera, renderer.domElement ); - controls.addEventListener( 'change', render ); controls.minDistance = 2; controls.maxDistance = 50; controls.enablePan = false; // + function setHelperVisible( value ) { + + for ( let i = 0; i < lights.length; i ++ ) { + + lights[ i ].userData.helper.visible = value; + + } + + } + + setHelperVisible( false ); + + // + + const gui = new GUI(); + + gui.add( { helper: false }, 'helper' ).onChange( ( v ) => setHelperVisible( v ) ); + + gui.open(); + + // + window.addEventListener( 'resize', onWindowResize ); } @@ -156,11 +187,18 @@ function render( time ) { - time = ( time / 1000 ) * 2.0; + time = time / 1000; for ( let i = 0; i < lights.length; i ++ ) { - lights[ i ].position.y = Math.sin( time + i ) + 0.97; + const t = ( Math.sin( ( time + i ) * ( Math.PI / 2 ) ) + 1 ) / 2; + + const x = THREE.MathUtils.lerp( lights[ i ].position.x, 0, t ); + const z = THREE.MathUtils.lerp( lights[ i ].position.z, 0, t ); + + lights[ i ].target.position.x = x; + lights[ i ].target.position.z = z; + if ( lights[ i ].userData.helper ) lights[ i ].userData.helper.update(); } diff --git a/examples/webgpu_lights_phong.html b/examples/webgpu_lights_phong.html index e51dd378a5fc85..928ea8d1e2a086 100644 --- a/examples/webgpu_lights_phong.html +++ b/examples/webgpu_lights_phong.html @@ -1,7 +1,7 @@ - three.js - WebGPU - Lights Phong + three.js webgpu - phong lighting model @@ -9,18 +9,17 @@
        - three.js - WebGPU - Phong Model Lighting
        + three.js webgpu - phong lighting model
        Left: Red lights - Center: All lights - Right: blue light
        - - @@ -28,16 +27,13 @@ + + + + diff --git a/examples/webgpu_lights_pointlights.html b/examples/webgpu_lights_pointlights.html new file mode 100644 index 00000000000000..73064744d9394f --- /dev/null +++ b/examples/webgpu_lights_pointlights.html @@ -0,0 +1,219 @@ + + + + three.js - lights - point lights + + + + + + +
        + three.js - point lights
        + Walt Disney head by David OReilly
        + Displacement effect by oosmoxiecode +
        + + + + + + diff --git a/examples/webgpu_lights_projector.html b/examples/webgpu_lights_projector.html new file mode 100644 index 00000000000000..fc8d87cb30096b --- /dev/null +++ b/examples/webgpu_lights_projector.html @@ -0,0 +1,297 @@ + + + + three.js webgpu - projector light + + + + + + +
        + three.js webgpu - projector light
        +
        + + + + + + + + + + diff --git a/examples/webgpu_lights_rectarealight.html b/examples/webgpu_lights_rectarealight.html new file mode 100644 index 00000000000000..ebab2b8c938b46 --- /dev/null +++ b/examples/webgpu_lights_rectarealight.html @@ -0,0 +1,117 @@ + + + + three.js webGPU - rect area light + + + + + + +
        + three.js webgpu - rect area light
        + by abelnation +
        + + + + + + diff --git a/examples/webgpu_lights_selective.html b/examples/webgpu_lights_selective.html index 85b0c5284f5396..e31d95c8c8fe83 100644 --- a/examples/webgpu_lights_selective.html +++ b/examples/webgpu_lights_selective.html @@ -1,7 +1,7 @@ - three.js - WebGPU - Selective Lights + three.js webgpu - selective lights @@ -9,18 +9,17 @@
        - three.js - WebGPU - Selective Lights
        + three.js webgpu - selective lights
        Left: Red lights - Center: All lights - Right: blue light
        - - @@ -28,7 +27,7 @@ + + + + + + diff --git a/examples/webgpu_lights_tiled.html b/examples/webgpu_lights_tiled.html new file mode 100644 index 00000000000000..f9c4159c5e1ee5 --- /dev/null +++ b/examples/webgpu_lights_tiled.html @@ -0,0 +1,234 @@ + + + + three.js webgpu - tiled lighting + + + + + + +
        + three.js webgpu - Compute-based Tiled Lighting
        +
        + + + + + + diff --git a/examples/webgpu_lines_fat.html b/examples/webgpu_lines_fat.html new file mode 100644 index 00000000000000..b58b308eea5b82 --- /dev/null +++ b/examples/webgpu_lines_fat.html @@ -0,0 +1,322 @@ + + + + three.js webgpu - fat lines + + + + + + + +
        + +
        three.js - fat lines
        + + + + + + + + diff --git a/examples/webgpu_lines_fat_raycasting.html b/examples/webgpu_lines_fat_raycasting.html new file mode 100644 index 00000000000000..ebbcbac1e27e71 --- /dev/null +++ b/examples/webgpu_lines_fat_raycasting.html @@ -0,0 +1,365 @@ + + + + three.js webgpu - lines - fat + + + + + + + +
        + +
        three.js - fat lines raycasting
        + + + + + + + + diff --git a/examples/webgpu_lines_fat_wireframe.html b/examples/webgpu_lines_fat_wireframe.html new file mode 100644 index 00000000000000..1d330df5283452 --- /dev/null +++ b/examples/webgpu_lines_fat_wireframe.html @@ -0,0 +1,269 @@ + + + + three.js webgpu - lines - fat - wireframe + + + + + + + +
        + +
        three.js - fat lines
        + + + + + + + + diff --git a/examples/webgpu_loader_gltf.html b/examples/webgpu_loader_gltf.html index b6d2b5b12674f3..b80766aa4815b6 100644 --- a/examples/webgpu_loader_gltf.html +++ b/examples/webgpu_loader_gltf.html @@ -1,7 +1,7 @@ - three.js webgpu - GLTFloader + three.js webgpu - gltf loader @@ -9,20 +9,18 @@
        - three.js - GLTFLoader
        + three.js webgpu - gltf loader
        Battle Damaged Sci-fi Helmet by theblueturtle_
        Royal Esplanade by HDRI Haven
        - - - - + + + + + diff --git a/examples/webgpu_loader_gltf_compressed.html b/examples/webgpu_loader_gltf_compressed.html index 333b144074873b..0cef58c00620ee 100644 --- a/examples/webgpu_loader_gltf_compressed.html +++ b/examples/webgpu_loader_gltf_compressed.html @@ -1,7 +1,7 @@ - three.js - WebGPU - GLTFloader + compressed + three.js webgpu - glTF + compressed @@ -9,15 +9,15 @@
        - three.js WebGPU - GLTFLoader + compression extensions + three.js webgpu - glTF + compression extensions
        - - + + + + + diff --git a/examples/webgpu_loader_gltf_iridescence.html b/examples/webgpu_loader_gltf_iridescence.html new file mode 100644 index 00000000000000..d7bc7ac4ab9ea6 --- /dev/null +++ b/examples/webgpu_loader_gltf_iridescence.html @@ -0,0 +1,113 @@ + + + + three.js webgpu - glTF + iridescence + + + + + + +
        + three.js - glTF + KHR_materials_iridescence
        + Iridescence Lamp from glTF-Sample-Models
        + Venice Sunset from HDRI Haven +
        + + + + + + + diff --git a/examples/webgpu_loader_gltf_sheen.html b/examples/webgpu_loader_gltf_sheen.html new file mode 100644 index 00000000000000..8b3919037ebe37 --- /dev/null +++ b/examples/webgpu_loader_gltf_sheen.html @@ -0,0 +1,135 @@ + + + + three.js webgpu - sheen + + + + + + + +
        + three.js - glTF + KHR_materials_sheen
        + Sheen Chair from glTF-Sample-Models +
        + + + + + + + diff --git a/examples/webgpu_loader_gltf_transmission.html b/examples/webgpu_loader_gltf_transmission.html new file mode 100644 index 00000000000000..f0563afd724793 --- /dev/null +++ b/examples/webgpu_loader_gltf_transmission.html @@ -0,0 +1,126 @@ + + + + three.js webgpu - glTF + transmission + + + + + + +
        + three.js webgpu - glTF + KHR_materials_transmission
        + Iridescent Dish With Olives by Eric Chadwick
        + Royal Esplanade from HDRI Haven +
        + + + + + + + diff --git a/examples/webgpu_loader_materialx.html b/examples/webgpu_loader_materialx.html new file mode 100644 index 00000000000000..3bb722da7706f4 --- /dev/null +++ b/examples/webgpu_loader_materialx.html @@ -0,0 +1,189 @@ + + + + three.js webgpu - materialx loader + + + + + + + +
        + three.js webgpu - materialx loader
        +
        + + + + + + + diff --git a/examples/webgpu_materials.html b/examples/webgpu_materials.html index 942d393f39c542..e83c34c590ce3e 100644 --- a/examples/webgpu_materials.html +++ b/examples/webgpu_materials.html @@ -1,7 +1,7 @@ - three.js - WebGPU - Materials + three.js webgpu - materials @@ -9,17 +9,16 @@
        - three.js WebGPU - Materials + three.js webgpu - materials
        - - @@ -27,14 +26,12 @@ + + + + diff --git a/examples/webgpu_materials_arrays.html b/examples/webgpu_materials_arrays.html new file mode 100644 index 00000000000000..6afdc3e8f49f8f --- /dev/null +++ b/examples/webgpu_materials_arrays.html @@ -0,0 +1,175 @@ + + + + three.js webgpu - materials arrays and geometry groups + + + + + + +
        + three.js - WebGPU Materials Arrays and Geometry Groups
        +
        + + + + + + + diff --git a/examples/webgpu_materials_basic.html b/examples/webgpu_materials_basic.html new file mode 100644 index 00000000000000..8f22634b0b8477 --- /dev/null +++ b/examples/webgpu_materials_basic.html @@ -0,0 +1,170 @@ + + + + three.js webgpu - material - basic + + + + + + + + + + + diff --git a/examples/webgpu_materials_displacementmap.html b/examples/webgpu_materials_displacementmap.html new file mode 100644 index 00000000000000..ceab4512294471 --- /dev/null +++ b/examples/webgpu_materials_displacementmap.html @@ -0,0 +1,263 @@ + + + + three.js webgpu - displacement map + + + + + + + +
        + three.js webgpu - ( normal + ao + displacement + environment ) maps
        + ninja head from AMD GPU MeshMapper +
        + + + + + + + + diff --git a/examples/webgpu_materials_envmaps.html b/examples/webgpu_materials_envmaps.html new file mode 100644 index 00000000000000..46d41deddc667b --- /dev/null +++ b/examples/webgpu_materials_envmaps.html @@ -0,0 +1,189 @@ + + + + three.js webgpu - materials - environment maps + + + + + + +
        + three.js - webgpu environment mapping example
        + Equirectangular Map by Jón Ragnarsson. +
        + + + + + + + + \ No newline at end of file diff --git a/examples/webgpu_materials_envmaps_bpcem.html b/examples/webgpu_materials_envmaps_bpcem.html new file mode 100644 index 00000000000000..f959175aea9efb --- /dev/null +++ b/examples/webgpu_materials_envmaps_bpcem.html @@ -0,0 +1,227 @@ + + + + three.js webgpu - materials - bpcem + + + + + + +
        + three.js - webgpu - box projected cube environment mapping (BPCEM)
        +
        + + + + + + + + \ No newline at end of file diff --git a/examples/webgpu_materials_lightmap.html b/examples/webgpu_materials_lightmap.html new file mode 100644 index 00000000000000..6dde32df846839 --- /dev/null +++ b/examples/webgpu_materials_lightmap.html @@ -0,0 +1,142 @@ + + + + three.js webgpu - lightmap + + + + + + + + + + + + + diff --git a/examples/webgpu_materials_matcap.html b/examples/webgpu_materials_matcap.html new file mode 100644 index 00000000000000..0772d109a1283b --- /dev/null +++ b/examples/webgpu_materials_matcap.html @@ -0,0 +1,271 @@ + + + + three.js webgpu - matcap material + + + + + + +
        + three.js webgpu - matcap material
        + Drag-and-drop JPG, PNG, WebP, AVIF, or EXR MatCap image files
        +
        + + + + + + + diff --git a/examples/webgpu_materials_sss.html b/examples/webgpu_materials_sss.html new file mode 100644 index 00000000000000..32b352c1e75b20 --- /dev/null +++ b/examples/webgpu_materials_sss.html @@ -0,0 +1,213 @@ + + + + three.js webgpu - sss + + + + + + +
        +
        three.js webgpu - sss +
        Fast subsurface scattering
        + [Thanks for the art support from Shaochun Lin] +
        + + + + + + + diff --git a/examples/webgpu_materials_toon.html b/examples/webgpu_materials_toon.html new file mode 100644 index 00000000000000..ce44c443da756f --- /dev/null +++ b/examples/webgpu_materials_toon.html @@ -0,0 +1,205 @@ + + + + three.js webgpu - toon material + + + + + + +
        +
        three.js webgpu - toon material
        + + + + + + + diff --git a/examples/webgpu_materials_transmission.html b/examples/webgpu_materials_transmission.html new file mode 100644 index 00000000000000..eef8e0719043b5 --- /dev/null +++ b/examples/webgpu_materials_transmission.html @@ -0,0 +1,243 @@ + + + + threejs webgpu - transmission + + + + + + +
        +
        threejs webgpu - transmission
        + + + + + + \ No newline at end of file diff --git a/examples/webgpu_materials_video.html b/examples/webgpu_materials_video.html index 650cf352b0769d..154d5ab6c3bef4 100644 --- a/examples/webgpu_materials_video.html +++ b/examples/webgpu_materials_video.html @@ -1,7 +1,7 @@ - three.js webgpu - materials - video + three.js webgpu - video material @@ -14,7 +14,7 @@
        - three.js - webgpu video demo
        + three.js webgpu - video material
        playing sintel trailer
        @@ -23,16 +23,13 @@ - - - - @@ -41,9 +38,6 @@ import * as THREE from 'three'; - import WebGPU from 'three/addons/capabilities/WebGPU.js'; - import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js'; - let container; let camera, scene, renderer; @@ -73,14 +67,6 @@ function init() { - if ( WebGPU.isAvailable() === false ) { - - document.body.appendChild( WebGPU.getErrorMessage() ); - - throw new Error( 'No WebGPU support' ); - - } - const overlay = document.getElementById( 'overlay' ); overlay.remove(); @@ -96,7 +82,7 @@ light.position.set( 0.5, 1, 1 ).normalize(); scene.add( light ); - renderer = new WebGPURenderer(); + renderer = new THREE.WebGPURenderer( { antialias: true } ); renderer.setPixelRatio( window.devicePixelRatio ); renderer.setSize( window.innerWidth, window.innerHeight ); renderer.setAnimationLoop( render ); @@ -266,7 +252,6 @@ } - diff --git a/examples/webgpu_materialx_noise.html b/examples/webgpu_materialx_noise.html new file mode 100644 index 00000000000000..f3f2c8ec204a29 --- /dev/null +++ b/examples/webgpu_materialx_noise.html @@ -0,0 +1,197 @@ + + + + three.js webgpu - materialx noise + + + + + +
        + three.js webgpu - materialx noise +
        + + + + + + diff --git a/examples/webgpu_mesh_batch.html b/examples/webgpu_mesh_batch.html new file mode 100644 index 00000000000000..30939971432220 --- /dev/null +++ b/examples/webgpu_mesh_batch.html @@ -0,0 +1,371 @@ + + + + three.js webgpu - mesh - batch + + + + + + + +
        + three.js webgpu - mesh - batch +
        + + + + + + + diff --git a/examples/webgpu_mirror.html b/examples/webgpu_mirror.html new file mode 100644 index 00000000000000..42126e335456fb --- /dev/null +++ b/examples/webgpu_mirror.html @@ -0,0 +1,222 @@ + + + + three.js webgpu - mirror + + + + + + +
        + three.js webgpu - mirror +
        + + + + + + diff --git a/examples/webgpu_modifier_curve.html b/examples/webgpu_modifier_curve.html new file mode 100644 index 00000000000000..325a02c48b1eda --- /dev/null +++ b/examples/webgpu_modifier_curve.html @@ -0,0 +1,221 @@ + + + + three.js webgpu - curve modifier + + + + + +
        + three.js webgpu - curve modifier +
        + + + + + + diff --git a/examples/webgpu_morphtargets.html b/examples/webgpu_morphtargets.html new file mode 100644 index 00000000000000..ab0c31daf5f9d5 --- /dev/null +++ b/examples/webgpu_morphtargets.html @@ -0,0 +1,169 @@ + + + + three.js webgpu - morph targets + + + + + + +
        +
        + three.js webgpu - morph targets
        + by Discover three.js +
        + + + + + + + diff --git a/examples/webgpu_morphtargets_face.html b/examples/webgpu_morphtargets_face.html new file mode 100644 index 00000000000000..9640686a43455e --- /dev/null +++ b/examples/webgpu_morphtargets_face.html @@ -0,0 +1,157 @@ + + + + three.js webgpu - morph face targets + + + + + + + +
        + three.js webgpu - morph face targets
        + model by Face Cap +
        + + + + + + diff --git a/examples/webgpu_mrt.html b/examples/webgpu_mrt.html new file mode 100644 index 00000000000000..c5d0539a98920d --- /dev/null +++ b/examples/webgpu_mrt.html @@ -0,0 +1,153 @@ + + + + three.js webgpu - mrt + + + + + + +
        + three.js webgpu - mrt
        + Final / Beauty / Normal / Emissive / Diffuse +
        + + + + + + + diff --git a/examples/webgpu_mrt_mask.html b/examples/webgpu_mrt_mask.html new file mode 100644 index 00000000000000..817b14f93a538f --- /dev/null +++ b/examples/webgpu_mrt_mask.html @@ -0,0 +1,172 @@ + + + + three.js webgpu - mrt mask + + + + + + +
        + three.js webgpu - mrt mask +
        The mask is applied followed by a gaussian blur only on some selected materials. +
        + + + + + + diff --git a/examples/webgpu_multiple_rendertargets.html b/examples/webgpu_multiple_rendertargets.html new file mode 100644 index 00000000000000..9df4e78fbfdbe9 --- /dev/null +++ b/examples/webgpu_multiple_rendertargets.html @@ -0,0 +1,129 @@ + + + + three.js webgpu - multiple render targets + + + + + + +
        + threejs webgpu - multiple rendertargets +
        + + + + + + + diff --git a/examples/webgpu_multiple_rendertargets_readback.html b/examples/webgpu_multiple_rendertargets_readback.html new file mode 100644 index 00000000000000..82a9afd7208e9f --- /dev/null +++ b/examples/webgpu_multiple_rendertargets_readback.html @@ -0,0 +1,200 @@ + + + three.js webgpu - mrt readback + + + + + + + +
        + three.js webgpu - mrt readback +
        + + + + + + + diff --git a/examples/webgpu_multisampled_renderbuffers.html b/examples/webgpu_multisampled_renderbuffers.html new file mode 100644 index 00000000000000..aea799f9fdddca --- /dev/null +++ b/examples/webgpu_multisampled_renderbuffers.html @@ -0,0 +1,175 @@ + + + three.js webgpu - multisampled renderbuffers + + + + + + +
        + three.js webgpu - multisampled renderbuffers +
        + + + + + + diff --git a/examples/webgpu_occlusion.html b/examples/webgpu_occlusion.html new file mode 100644 index 00000000000000..4d42643323990e --- /dev/null +++ b/examples/webgpu_occlusion.html @@ -0,0 +1,140 @@ + + + + three.js webgpu - occlusion + + + + + + +
        + three.js webgpu - occlusion
        + The plane is green when the sphere is completely occluded. +
        + + + + + + diff --git a/examples/webgpu_ocean.html b/examples/webgpu_ocean.html new file mode 100644 index 00000000000000..1276a8abfc44fb --- /dev/null +++ b/examples/webgpu_ocean.html @@ -0,0 +1,207 @@ + + + + three.js webgpu - ocean + + + + + + +
        +
        + three.js - webgpu ocean +
        + + + + + + diff --git a/examples/webgpu_parallax_uv.html b/examples/webgpu_parallax_uv.html new file mode 100644 index 00000000000000..38d1215bb71f2d --- /dev/null +++ b/examples/webgpu_parallax_uv.html @@ -0,0 +1,152 @@ + + + + three.js webgpu - parallax uv + + + + + + +
        + three.js webgpu - parallax uv
        + Textures by ambientCG
        + +
        + + + + + + diff --git a/examples/webgpu_particles.html b/examples/webgpu_particles.html index d1fbb41adfc21c..c9252878768d12 100644 --- a/examples/webgpu_particles.html +++ b/examples/webgpu_particles.html @@ -1,6 +1,6 @@ - three.js - WebGPU - Particles + three.js webgpu - Particles @@ -8,17 +8,16 @@
        - three.js WebGPU - Particles + three.js webgpu - particles
        - - @@ -26,12 +25,10 @@ + + + + + diff --git a/examples/webgpu_performance_renderbundle.html b/examples/webgpu_performance_renderbundle.html new file mode 100644 index 00000000000000..c683c37f19499c --- /dev/null +++ b/examples/webgpu_performance_renderbundle.html @@ -0,0 +1,325 @@ + + + + three.js webgpu - renderbundle + + + + + + + +
        + + three.js webgpu - renderbundle +
        + (WebGL uses 10 times fewer meshes to prevent performance issues.) + +
        + +
        + Draw Calls: 0 +
        + + + + + + + diff --git a/examples/webgpu_pmrem_cubemap.html b/examples/webgpu_pmrem_cubemap.html new file mode 100644 index 00000000000000..85cf742bfd1747 --- /dev/null +++ b/examples/webgpu_pmrem_cubemap.html @@ -0,0 +1,112 @@ + + + + three.js webgpu - pmrem cubemap + + + + + + + + + + + + diff --git a/examples/webgpu_pmrem_equirectangular.html b/examples/webgpu_pmrem_equirectangular.html new file mode 100644 index 00000000000000..cdb62d88fbc1ef --- /dev/null +++ b/examples/webgpu_pmrem_equirectangular.html @@ -0,0 +1,114 @@ + + + + three.js webgpu - pmrem equirectangular + + + + + + + + + + + + diff --git a/examples/webgpu_pmrem_scene.html b/examples/webgpu_pmrem_scene.html new file mode 100644 index 00000000000000..f0d15f01388abf --- /dev/null +++ b/examples/webgpu_pmrem_scene.html @@ -0,0 +1,136 @@ + + + + three.js webgpu - pmrem scene + + + + + + + + + + + + diff --git a/examples/webgpu_portal.html b/examples/webgpu_portal.html new file mode 100644 index 00000000000000..350d2954fbf2c8 --- /dev/null +++ b/examples/webgpu_portal.html @@ -0,0 +1,187 @@ + + + + three.js webgpu - portal + + + + + + +
        + three.js webgpu - portal +
        + + + + + + diff --git a/examples/webgpu_postprocessing.html b/examples/webgpu_postprocessing.html new file mode 100644 index 00000000000000..8e72bc1e581a31 --- /dev/null +++ b/examples/webgpu_postprocessing.html @@ -0,0 +1,114 @@ + + + + three.js webgpu - postprocessing + + + + + + + + + + + diff --git a/examples/webgpu_postprocessing_3dlut.html b/examples/webgpu_postprocessing_3dlut.html new file mode 100644 index 00000000000000..1d06fab4bc48ef --- /dev/null +++ b/examples/webgpu_postprocessing_3dlut.html @@ -0,0 +1,266 @@ + + + + three.js webgpu - 3d luts + + + + + + +
        + three.js webgpu - post processing - 3D LUTs
        + Based on Three.js Journey lesson
        + Perlin noise texture from Perlin Noise Maker, + LUTs from RocketStock, FreePresets.com +
        + + + + + + diff --git a/examples/webgpu_postprocessing_afterimage.html b/examples/webgpu_postprocessing_afterimage.html new file mode 100644 index 00000000000000..8d97f7d3509efd --- /dev/null +++ b/examples/webgpu_postprocessing_afterimage.html @@ -0,0 +1,193 @@ + + + + three.js webgpu - postprocessing afterimage + + + + + +
        + three.js - postprocessing - after image
        + Based on Particle Spiral + by oosmoxiecode +
        + + + + + + diff --git a/examples/webgpu_postprocessing_anamorphic.html b/examples/webgpu_postprocessing_anamorphic.html new file mode 100644 index 00000000000000..e6441620727145 --- /dev/null +++ b/examples/webgpu_postprocessing_anamorphic.html @@ -0,0 +1,135 @@ + + + + three.js webgpu - postprocessing anamorphic + + + + + + +
        + three.js webgpu - postprocessing anamorphic
        + Battle Damaged Sci-fi Helmet by + theblueturtle_
        +
        + + + + + + + diff --git a/examples/webgpu_postprocessing_ao.html b/examples/webgpu_postprocessing_ao.html new file mode 100644 index 00000000000000..5f3c835b3eadd6 --- /dev/null +++ b/examples/webgpu_postprocessing_ao.html @@ -0,0 +1,233 @@ + + + + three.js webgpu - ambient occlusion + + + + + + +
        + three.js webgpu - postprocessing - ambient occlusion
        + Minimalistic Modern Bedroom by + dylanheyes is licensed under Creative Commons Attribution.
        +
        + + + + + + \ No newline at end of file diff --git a/examples/webgpu_postprocessing_bloom.html b/examples/webgpu_postprocessing_bloom.html new file mode 100644 index 00000000000000..34f48332491a3e --- /dev/null +++ b/examples/webgpu_postprocessing_bloom.html @@ -0,0 +1,186 @@ + + + + three.js webgpu - postprocessing - bloom + + + + + + + +
        + +
        + three.js - Bloom pass by Prashant Sharma and Ben Houston +
        + Model: Primary Ion Drive by + Mike Murdock, CC Attribution. +
        + + + + + + + + diff --git a/examples/webgpu_postprocessing_bloom_emissive.html b/examples/webgpu_postprocessing_bloom_emissive.html new file mode 100644 index 00000000000000..76c11e66c26df6 --- /dev/null +++ b/examples/webgpu_postprocessing_bloom_emissive.html @@ -0,0 +1,143 @@ + + + + three.js webgpu - bloom emissive + + + + + + +
        + three.js webgpu - bloom emissive +
        + + + + + + + diff --git a/examples/webgpu_postprocessing_bloom_selective.html b/examples/webgpu_postprocessing_bloom_selective.html new file mode 100644 index 00000000000000..a6597ba4f7208d --- /dev/null +++ b/examples/webgpu_postprocessing_bloom_selective.html @@ -0,0 +1,164 @@ + + + + three.js webgpu - bloom selective + + + + + + +
        + three.js webgpu - bloom selective +
        + + + + + + + + diff --git a/examples/webgpu_postprocessing_ca.html b/examples/webgpu_postprocessing_ca.html new file mode 100644 index 00000000000000..6e66fce2ca8ccb --- /dev/null +++ b/examples/webgpu_postprocessing_ca.html @@ -0,0 +1,334 @@ + + + + three.js webgpu - chromatic aberration + + + + + + + +
        + three.js webgpu - chromatic aberration +
        + + + + + + + diff --git a/examples/webgpu_postprocessing_difference.html b/examples/webgpu_postprocessing_difference.html new file mode 100644 index 00000000000000..d9f3e4f5639274 --- /dev/null +++ b/examples/webgpu_postprocessing_difference.html @@ -0,0 +1,132 @@ + + + + three.js webgpu - frame difference + + + + + + +
        + three.js +
        saturated color of objects according to the difference from one frame to another +
        + + + + + + + \ No newline at end of file diff --git a/examples/webgpu_postprocessing_dof.html b/examples/webgpu_postprocessing_dof.html new file mode 100644 index 00000000000000..3cf520604c866b --- /dev/null +++ b/examples/webgpu_postprocessing_dof.html @@ -0,0 +1,192 @@ + + + + three.js webgpu - postprocessing dof + + + + + + + + + + diff --git a/examples/webgpu_postprocessing_fxaa.html b/examples/webgpu_postprocessing_fxaa.html new file mode 100644 index 00000000000000..4089f33d58196d --- /dev/null +++ b/examples/webgpu_postprocessing_fxaa.html @@ -0,0 +1,168 @@ + + + + three.js webgpu - FXAA + + + + + + + + + + + + + diff --git a/examples/webgpu_postprocessing_lensflare.html b/examples/webgpu_postprocessing_lensflare.html new file mode 100644 index 00000000000000..82da7a66ba5e27 --- /dev/null +++ b/examples/webgpu_postprocessing_lensflare.html @@ -0,0 +1,184 @@ + + + + three.js webgpu - postprocessing lensflares + + + + + + +
        + three.js webgpu - postprocessing lensflares
        + Space Ship Hallway by + yeeyeeman is licensed under Creative Commons Attribution.
        + Ice Planet Close from Space Spheremaps. +
        + + + + + + + diff --git a/examples/webgpu_postprocessing_masking.html b/examples/webgpu_postprocessing_masking.html new file mode 100644 index 00000000000000..c7928dbb6c7b9a --- /dev/null +++ b/examples/webgpu_postprocessing_masking.html @@ -0,0 +1,121 @@ + + + + three.js webgpu - masking + + + + + + +
        + + + + + + diff --git a/examples/webgpu_postprocessing_motion_blur.html b/examples/webgpu_postprocessing_motion_blur.html new file mode 100644 index 00000000000000..fb0976bb6f74d7 --- /dev/null +++ b/examples/webgpu_postprocessing_motion_blur.html @@ -0,0 +1,247 @@ + + + + three.js webgpu - motion blur + + + + + + +
        + three.js webgpu - motion blur +
        + + + + + + diff --git a/examples/webgpu_postprocessing_outline.html b/examples/webgpu_postprocessing_outline.html new file mode 100644 index 00000000000000..d2d49130d2395b --- /dev/null +++ b/examples/webgpu_postprocessing_outline.html @@ -0,0 +1,296 @@ + + + + three.js webgpu - post processing - Outline Pass + + + + + +
        + three.js - Outline Pass by Prashant Sharma and Ben Houston

        +
        + + + + + + diff --git a/examples/webgpu_postprocessing_pixel.html b/examples/webgpu_postprocessing_pixel.html new file mode 100644 index 00000000000000..08a960fc7e2f0b --- /dev/null +++ b/examples/webgpu_postprocessing_pixel.html @@ -0,0 +1,281 @@ + + + + three.js webgpu - postprocessing pixel + + + + + + +
        + three.js - Node based pixelation pass with optional single pixel outlines by + Kody King

        +
        + +
        + + + + + + + + diff --git a/examples/webgpu_postprocessing_smaa.html b/examples/webgpu_postprocessing_smaa.html new file mode 100644 index 00000000000000..47aab4904e8def --- /dev/null +++ b/examples/webgpu_postprocessing_smaa.html @@ -0,0 +1,153 @@ + + + + three.js webgpu - postprocessing smaa + + + + + +
        + three.js - post-processing SMAA +
        + + + + + + + diff --git a/examples/webgpu_postprocessing_sobel.html b/examples/webgpu_postprocessing_sobel.html new file mode 100644 index 00000000000000..5fe3f75e3ced6d --- /dev/null +++ b/examples/webgpu_postprocessing_sobel.html @@ -0,0 +1,138 @@ + + + + three.js webgpu - postprocessing sobel + + + + + +
        + three.js - Edge Detection with Sobel +
        + + + + + + \ No newline at end of file diff --git a/examples/webgpu_postprocessing_ssaa.html b/examples/webgpu_postprocessing_ssaa.html new file mode 100644 index 00000000000000..f2a761eef33f50 --- /dev/null +++ b/examples/webgpu_postprocessing_ssaa.html @@ -0,0 +1,217 @@ + + + + three.js webgpu - postprocessing manual ssaa + + + + + +
        + three.js - Unbiased Manual Supersampling Anti-Aliasing (SSAA) pass by Ben Houston +
        + + + + + + diff --git a/examples/webgpu_postprocessing_ssr.html b/examples/webgpu_postprocessing_ssr.html new file mode 100644 index 00000000000000..9ef04730b6642d --- /dev/null +++ b/examples/webgpu_postprocessing_ssr.html @@ -0,0 +1,221 @@ + + + + + + + three.js webgpu - postprocessing - Screen Space Reflections (SSR) + + + + + + + +
        + three.js webgpu - postprocessing - screen space reflections
        + Steampunk Camera by + dylanheyes is licensed under Creative Commons Attribution.
        +
        + + + + + + + diff --git a/examples/webgpu_postprocessing_traa.html b/examples/webgpu_postprocessing_traa.html new file mode 100644 index 00000000000000..f1f315a7cfcb22 --- /dev/null +++ b/examples/webgpu_postprocessing_traa.html @@ -0,0 +1,128 @@ + + + + three.js webgpu - postprocessing traa + + + + + +
        + three.js - Temporal Reprojection Anti-Aliasing (TRAA) +
        + + + + + + diff --git a/examples/webgpu_postprocessing_transition.html b/examples/webgpu_postprocessing_transition.html new file mode 100644 index 00000000000000..125f688959591f --- /dev/null +++ b/examples/webgpu_postprocessing_transition.html @@ -0,0 +1,267 @@ + + + + three.js webgpu - scenes transition + + + + + + +
        + three.js webgpu scene transitions.
        + Original implementation by fernandojsg - github +
        + + + + + + diff --git a/examples/webgpu_procedural_texture.html b/examples/webgpu_procedural_texture.html new file mode 100644 index 00000000000000..f010f1d757a33b --- /dev/null +++ b/examples/webgpu_procedural_texture.html @@ -0,0 +1,111 @@ + + + three.js webgpu - procedural texture + + + + + + +
        + three.js webgpu - procedural texture +
        + + + + + + diff --git a/examples/webgpu_reflection.html b/examples/webgpu_reflection.html new file mode 100644 index 00000000000000..a3ae2597eea915 --- /dev/null +++ b/examples/webgpu_reflection.html @@ -0,0 +1,197 @@ + + + + three.js webgpu - reflection + + + + + + +
        + three.js webgpu - reflection +
        + + + + + + diff --git a/examples/webgpu_reflection_blurred.html b/examples/webgpu_reflection_blurred.html new file mode 100644 index 00000000000000..6432e9bf9964aa --- /dev/null +++ b/examples/webgpu_reflection_blurred.html @@ -0,0 +1,236 @@ + + + + three.js webgpu - blurred reflection + + + + + + +
        + three.js webgpu - blurred reflection +
        + + + + + + diff --git a/examples/webgpu_reflection_roughness.html b/examples/webgpu_reflection_roughness.html new file mode 100644 index 00000000000000..02f928e4ed67b8 --- /dev/null +++ b/examples/webgpu_reflection_roughness.html @@ -0,0 +1,167 @@ + + + + three.js webgpu - roughness reflection + + + + + + +
        + three.js webgpu - roughness reflection +
        + + + + + + + diff --git a/examples/webgpu_refraction.html b/examples/webgpu_refraction.html new file mode 100644 index 00000000000000..44f83b769d1a8e --- /dev/null +++ b/examples/webgpu_refraction.html @@ -0,0 +1,175 @@ + + + + three.js webgpu - refraction + + + + + + +
        + three.js webgpu - refraction +
        + + + + + + diff --git a/examples/webgpu_rendertarget_2d-array_3d.html b/examples/webgpu_rendertarget_2d-array_3d.html new file mode 100644 index 00000000000000..0201b97f743153 --- /dev/null +++ b/examples/webgpu_rendertarget_2d-array_3d.html @@ -0,0 +1,340 @@ + + + + three.js webgpu - RenderTargetArray and RenderTarget3D + + + + + + + +
        + three.js - WebGPU - RenderTargetArray and RenderTarget3D
        +
        +
        DataArrayTexture
        +
        Data3DTexture
        +
        RenderTarget3D
        +
        RenderTargetArray
        + + + + + + \ No newline at end of file diff --git a/examples/webgpu_rtt.html b/examples/webgpu_rtt.html index 1ed44a0c402fc6..2267574dc833c2 100644 --- a/examples/webgpu_rtt.html +++ b/examples/webgpu_rtt.html @@ -1,6 +1,6 @@ - three.js - WebGPU - RTT + three.js webgpu - rtt @@ -8,17 +8,16 @@
        - three.js WebGPU - RTT + three.js webgpu - rtt
        - - @@ -26,16 +25,12 @@ - @@ -26,13 +25,10 @@ + + + + + + + + diff --git a/examples/webgpu_shadowmap.html b/examples/webgpu_shadowmap.html index fe5590d68d7060..bacfad01f79c61 100644 --- a/examples/webgpu_shadowmap.html +++ b/examples/webgpu_shadowmap.html @@ -8,19 +8,16 @@
        - three.js - webgpu shadow map + three.js webgpu - webgpu shadow map
        - - - - @@ -28,9 +25,7 @@ + + + + \ No newline at end of file diff --git a/examples/webgpu_shadowmap_csm.html b/examples/webgpu_shadowmap_csm.html new file mode 100644 index 00000000000000..66c02e14ac8d14 --- /dev/null +++ b/examples/webgpu_shadowmap_csm.html @@ -0,0 +1,321 @@ + + + + three.js webgpu - cascaded shadow maps + + + + + + +
        +
        + three.js webgpu - cascaded shadow maps
        + by StrandedKitty (original repository) +
        + + + + + + + diff --git a/examples/webgpu_shadowmap_opacity.html b/examples/webgpu_shadowmap_opacity.html new file mode 100644 index 00000000000000..5b98303bf88e35 --- /dev/null +++ b/examples/webgpu_shadowmap_opacity.html @@ -0,0 +1,154 @@ + + + + three.js webgpu - shadowmap + opacity + + + + + + +
        + three.js webgpu - shadowmap + opacity +
        + + + + + + + diff --git a/examples/webgpu_shadowmap_progressive.html b/examples/webgpu_shadowmap_progressive.html new file mode 100644 index 00000000000000..de36dee6ba4ed9 --- /dev/null +++ b/examples/webgpu_shadowmap_progressive.html @@ -0,0 +1,249 @@ + + + + three.js webgpu - progressive lightmap accumulation + + + + + +
        +
        + three.js - Progressive Lightmaps by zalo
        + [Inspired by evanw's Lightmap Generation] +
        + + + + + + diff --git a/examples/webgpu_shadowmap_vsm.html b/examples/webgpu_shadowmap_vsm.html new file mode 100644 index 00000000000000..1415bf0086de85 --- /dev/null +++ b/examples/webgpu_shadowmap_vsm.html @@ -0,0 +1,241 @@ + + + + three.js webgpu - VSM Shadows example + + + + + +
        + three.js - VSM Shadows example by Paul Brunt +
        + + + + + + diff --git a/examples/webgpu_skinning.html b/examples/webgpu_skinning.html index a02ca8e45020b1..bf8510ce130b14 100644 --- a/examples/webgpu_skinning.html +++ b/examples/webgpu_skinning.html @@ -1,7 +1,7 @@ - three.js - WebGPU - Skinning + three.js webgpu - skinning @@ -9,17 +9,16 @@
        - three.js WebGPU - Skinning + three.js webgpu - skinning
        - - @@ -27,13 +26,10 @@ - @@ -27,14 +26,13 @@ - @@ -27,35 +27,26 @@ + + + + + + diff --git a/examples/webgpu_sprites.html b/examples/webgpu_sprites.html index 8ea2279e449a84..3478f5b538a92c 100644 --- a/examples/webgpu_sprites.html +++ b/examples/webgpu_sprites.html @@ -1,6 +1,6 @@ - three.js - WebGPU - Sprites + three.js webgpu - sprites @@ -8,17 +8,16 @@
        - three.js WebGPU - Sprites + three.js webgpu - sprites
        - - @@ -26,10 +25,7 @@ + + + + diff --git a/examples/webgpu_struct_drawindirect.html b/examples/webgpu_struct_drawindirect.html new file mode 100644 index 00000000000000..23f86502943ae3 --- /dev/null +++ b/examples/webgpu_struct_drawindirect.html @@ -0,0 +1,268 @@ + + + + three.js webgpu - struct drawIndirect + + + + + + +
        + three.js webgpu - struct drawIndirect
        +
        + + + + + + + \ No newline at end of file diff --git a/examples/webgpu_texturegrad.html b/examples/webgpu_texturegrad.html new file mode 100644 index 00000000000000..d7399d5e56ccef --- /dev/null +++ b/examples/webgpu_texturegrad.html @@ -0,0 +1,151 @@ + + + three.js webgpu - texture gradient + + + + + + +
        + three.js +
        This example demonstrate texture gradient +
        Left canvas is using WebGPU Backend, right canvas is WebGL Backend. +
        The bottom half of the texture benefits from the gradient to achieve better blur quality. +
        + + + + + + diff --git a/examples/webgpu_textures_2d-array.html b/examples/webgpu_textures_2d-array.html new file mode 100644 index 00000000000000..c939d853bdc6ac --- /dev/null +++ b/examples/webgpu_textures_2d-array.html @@ -0,0 +1,122 @@ + + + + three.js webgpu - 2d texture array + + + + + +
        + three.js - 2D Texture array
        + Scanned head data by + Divine Augustine
        + licensed under + CPOL +
        + + + + + + diff --git a/examples/webgpu_textures_2d-array_compressed.html b/examples/webgpu_textures_2d-array_compressed.html new file mode 100644 index 00000000000000..0d208d9c83d273 --- /dev/null +++ b/examples/webgpu_textures_2d-array_compressed.html @@ -0,0 +1,130 @@ + + + + three.js webgl - 2D compressed texture array + + + + + + +
        + three.js - WebGPU - 2D Compressed Texture Array
        + Loop from the movie Spirited away + by the Studio Ghibli
        +
        + + + + + + diff --git a/examples/webgpu_textures_anisotropy.html b/examples/webgpu_textures_anisotropy.html new file mode 100644 index 00000000000000..46755c5d9b8166 --- /dev/null +++ b/examples/webgpu_textures_anisotropy.html @@ -0,0 +1,239 @@ + + + + three.js webgpu - anisotropic texture filtering + + + + + + + +
        + three.js - anisotropic texture filtering example +
        + +
        + anisotropy:
        +
        + +
        + anisotropy:
        +
        + + + + + + + diff --git a/examples/webgpu_textures_partialupdate.html b/examples/webgpu_textures_partialupdate.html new file mode 100644 index 00000000000000..af480cc5ab119f --- /dev/null +++ b/examples/webgpu_textures_partialupdate.html @@ -0,0 +1,150 @@ + + + + three.js webgl - webgpu partial texture update + + + + + + +
        + three.js - partial webgpu texture update
        + replace parts of an existing texture with all data of another texture +
        + + + + + + + diff --git a/examples/webgpu_tonemapping.html b/examples/webgpu_tonemapping.html new file mode 100644 index 00000000000000..ab13e4a106a60b --- /dev/null +++ b/examples/webgpu_tonemapping.html @@ -0,0 +1,207 @@ + + + + three.js webgpu - tone mapping + + + + + + +
        + three.js webgpu - tone mapping
        + Venice Mask by + DailyArt is licensed under CC Attribution-NonCommercial
        + Venice Sunset from HDRI Haven +
        + + + + + + + diff --git a/examples/webgpu_tsl_angular_slicing.html b/examples/webgpu_tsl_angular_slicing.html new file mode 100644 index 00000000000000..5cfb4c9e3a4cee --- /dev/null +++ b/examples/webgpu_tsl_angular_slicing.html @@ -0,0 +1,234 @@ + + + + three.js webgpu - angular slicing + + + + + + +
        + three.js webgpu - angular slicing +
        + Based on Three.js Journey lesson +
        + + + + + + diff --git a/examples/webgpu_tsl_compute_attractors_particles.html b/examples/webgpu_tsl_compute_attractors_particles.html new file mode 100644 index 00000000000000..c83ac92ee88d17 --- /dev/null +++ b/examples/webgpu_tsl_compute_attractors_particles.html @@ -0,0 +1,356 @@ + + + + three.js webgpu - attractors particles + + + + + + +
        + three.js WebGPU - Compute Attractors Particles +
        + + + + + + diff --git a/examples/webgpu_tsl_earth.html b/examples/webgpu_tsl_earth.html new file mode 100644 index 00000000000000..7fc8834b5ea0c5 --- /dev/null +++ b/examples/webgpu_tsl_earth.html @@ -0,0 +1,204 @@ + + + + three.js webgpu - earth + + + + + + +
        + three.js webgpu - earth +
        + Based on Three.js Journey lesson +
        + Earth textures from Solar System Scope (resized and merged) +
        + + + + + + diff --git a/examples/webgpu_tsl_editor.html b/examples/webgpu_tsl_editor.html new file mode 100644 index 00000000000000..3c4c137f2937e7 --- /dev/null +++ b/examples/webgpu_tsl_editor.html @@ -0,0 +1,252 @@ + + + three.js webgpu - tsl editor + + + + + + + + +
        +
        +
        + + + + + + + diff --git a/examples/webgpu_tsl_galaxy.html b/examples/webgpu_tsl_galaxy.html new file mode 100644 index 00000000000000..3a148e3516449e --- /dev/null +++ b/examples/webgpu_tsl_galaxy.html @@ -0,0 +1,142 @@ + + + + three.js webgpu - galaxy + + + + + + +
        + three.js webgpu - galaxy +
        + Based on Three.js Journey lessons +
        + + + + + + diff --git a/examples/webgpu_tsl_halftone.html b/examples/webgpu_tsl_halftone.html new file mode 100644 index 00000000000000..3278a690239942 --- /dev/null +++ b/examples/webgpu_tsl_halftone.html @@ -0,0 +1,257 @@ + + + + three.js webgpu - halftone + + + + + + +
        + three.js WebGPU - Halftone +
        + + + + + + diff --git a/examples/webgpu_tsl_interoperability.html b/examples/webgpu_tsl_interoperability.html new file mode 100644 index 00000000000000..722cae5a8232fb --- /dev/null +++ b/examples/webgpu_tsl_interoperability.html @@ -0,0 +1,298 @@ + + + three.js - wgsl/tsl node interoperability + + + + + + +
        + three.js webgpu - wgsl/tsl node interoperability +
        CRT Shader adapted from Xor. +
        +
        + +
        + + + + + + diff --git a/examples/webgpu_tsl_procedural_terrain.html b/examples/webgpu_tsl_procedural_terrain.html new file mode 100644 index 00000000000000..d27aa5e9287bb7 --- /dev/null +++ b/examples/webgpu_tsl_procedural_terrain.html @@ -0,0 +1,360 @@ + + + + three.js webgpu - procedural terrain + + + + + + +
        + three.js webgpu - procedural terrain +
        + Based on Three.js Journey lessons +
        + + + + + + diff --git a/examples/webgpu_tsl_raging_sea.html b/examples/webgpu_tsl_raging_sea.html new file mode 100644 index 00000000000000..5054f9094bb089 --- /dev/null +++ b/examples/webgpu_tsl_raging_sea.html @@ -0,0 +1,201 @@ + + + + three.js webgpu - raging sea + + + + + + +
        + three.js webgpu - raging sea +
        + Based on Three.js Journey lesson +
        + + + + + + diff --git a/examples/webgpu_tsl_transpiler.html b/examples/webgpu_tsl_transpiler.html new file mode 100644 index 00000000000000..f0ba67498e47a8 --- /dev/null +++ b/examples/webgpu_tsl_transpiler.html @@ -0,0 +1,216 @@ + + + three.js - webgpu - tsl transpiler + + + + + + + + +
        +
        + + + + + + + diff --git a/examples/webgpu_tsl_vfx_flames.html b/examples/webgpu_tsl_vfx_flames.html new file mode 100644 index 00000000000000..99e36c678ba0dc --- /dev/null +++ b/examples/webgpu_tsl_vfx_flames.html @@ -0,0 +1,229 @@ + + + + three.js webgpu - vfx flames + + + + + + +
        + three.js webgpu - vfx flames +
        + Inspired by @cmzw_ +
        + + + + + + diff --git a/examples/webgpu_tsl_vfx_linkedparticles.html b/examples/webgpu_tsl_vfx_linkedparticles.html new file mode 100644 index 00000000000000..4cad79978ce67e --- /dev/null +++ b/examples/webgpu_tsl_vfx_linkedparticles.html @@ -0,0 +1,461 @@ + + + + three.js webgpu - VFX Linked particles + + + + + + +
        + three.js webgpu - VFX Linked particles +
        + Based on this experiment by Christophe Choffel +
        + + + + + + diff --git a/examples/webgpu_tsl_vfx_tornado.html b/examples/webgpu_tsl_vfx_tornado.html new file mode 100644 index 00000000000000..934e12660e3fd1 --- /dev/null +++ b/examples/webgpu_tsl_vfx_tornado.html @@ -0,0 +1,337 @@ + + + + three.js webgpu - VFX Tornado + + + + + + +
        + three.js webgpu - VFX Tornado +
        + Based on Three.js Journey lesson +
        + + + + + + diff --git a/examples/webgpu_video_frame.html b/examples/webgpu_video_frame.html new file mode 100644 index 00000000000000..aa99d7c3630528 --- /dev/null +++ b/examples/webgpu_video_frame.html @@ -0,0 +1,136 @@ + + + + three.js webgpu - video frames + + + + + +
        + three.js - video - frames
        + Decodes all frames from a MP4 file and renders them onto a plane as fast as possible.
        + mp4box.js used for MP4 parsing. +
        + + + + + + diff --git a/examples/webgpu_video_panorama.html b/examples/webgpu_video_panorama.html new file mode 100644 index 00000000000000..2d9fd7ce40ea03 --- /dev/null +++ b/examples/webgpu_video_panorama.html @@ -0,0 +1,155 @@ + + + + three.js webgpu - video panorama + + + + + + +
        + three.js - video panorama +
        + +
        + + + + + + + + diff --git a/examples/webgpu_volume_caustics.html b/examples/webgpu_volume_caustics.html new file mode 100644 index 00000000000000..403a0982930b44 --- /dev/null +++ b/examples/webgpu_volume_caustics.html @@ -0,0 +1,333 @@ + + + + three.js webgpu - volumetric caustics + + + + + + +
        + three.js webgpu - volumetric caustics +
        + + + + + + diff --git a/examples/webgpu_volume_cloud.html b/examples/webgpu_volume_cloud.html new file mode 100644 index 00000000000000..c7210816214d89 --- /dev/null +++ b/examples/webgpu_volume_cloud.html @@ -0,0 +1,210 @@ + + + + three.js webgpu - volumetric cloud + + + + + + +
        + three.js webgpu - volumetric cloud +
        + + + + + + + diff --git a/examples/webgpu_volume_lighting.html b/examples/webgpu_volume_lighting.html new file mode 100644 index 00000000000000..d844e12a287b2b --- /dev/null +++ b/examples/webgpu_volume_lighting.html @@ -0,0 +1,302 @@ + + + + three.js webgpu - volumetric lighting + + + + + + + +
        + three.js webgpu - volumetric lighting +
        Improve the quality/performance adjusting the parameters in the Controls +
        + + + + + + + diff --git a/examples/webgpu_volume_lighting_rectarea.html b/examples/webgpu_volume_lighting_rectarea.html new file mode 100644 index 00000000000000..720868636f1a91 --- /dev/null +++ b/examples/webgpu_volume_lighting_rectarea.html @@ -0,0 +1,315 @@ + + + + three.js webgpu - volumetric lighting rect area + + + + + + + +
        + three.js webgpu - volumetric lighting rect area +
        Improve the quality/performance adjusting the parameters in the Controls +
        + + + + + + + diff --git a/examples/webgpu_volume_perlin.html b/examples/webgpu_volume_perlin.html new file mode 100644 index 00000000000000..0571495c5f00a5 --- /dev/null +++ b/examples/webgpu_volume_perlin.html @@ -0,0 +1,163 @@ + + + + three.js webgpu - volumetric perlin + + + + + + +
        + three.js webgpu - volumetric perlin +
        + + + + + + + diff --git a/examples/webgpu_water.html b/examples/webgpu_water.html new file mode 100644 index 00000000000000..39a6415ed229e7 --- /dev/null +++ b/examples/webgpu_water.html @@ -0,0 +1,253 @@ + + + + three.js - water + + + + + + +
        +
        + three.js - water
        + The Night Pool by + syntheticplants is licensed under CC BY 4.0
        +
        + + + + + + + diff --git a/examples/webgpu_xr_cubes.html b/examples/webgpu_xr_cubes.html new file mode 100644 index 00000000000000..4b8e525e98af52 --- /dev/null +++ b/examples/webgpu_xr_cubes.html @@ -0,0 +1,279 @@ + + + + three.js xr - cubes + + + + + + +
        + three.js xr - interactive cubes +
        + + + + + + diff --git a/examples/webgpu_xr_native_layers.html b/examples/webgpu_xr_native_layers.html new file mode 100644 index 00000000000000..0358495d173849 --- /dev/null +++ b/examples/webgpu_xr_native_layers.html @@ -0,0 +1,713 @@ + + + + three.js vr - xr layers + + + + + + +
        + three.js vr - xr layers +
        + + + + + + \ No newline at end of file diff --git a/examples/webgpu_xr_rollercoaster.html b/examples/webgpu_xr_rollercoaster.html new file mode 100644 index 00000000000000..b6309062edef58 --- /dev/null +++ b/examples/webgpu_xr_rollercoaster.html @@ -0,0 +1,251 @@ + + + + three.js vr - roller coaster + + + + + + + + + + + diff --git a/examples/webxr_ar_cones.html b/examples/webxr_ar_cones.html index b063f31dbacb7f..2cd0696ac6192a 100644 --- a/examples/webxr_ar_cones.html +++ b/examples/webxr_ar_cones.html @@ -9,13 +9,9 @@
        - three.js ar - cones
        (Chrome Android 81+) + three.js ar - cones
        - - - - - - - - - - - - - - - - - diff --git a/examples/webxr_vr_sandbox.html b/examples/webxr_vr_sandbox.html index f3fbf9fba6bebf..eee364555f1a91 100644 --- a/examples/webxr_vr_sandbox.html +++ b/examples/webxr_vr_sandbox.html @@ -7,10 +7,6 @@ - - - - - - - + + + + diff --git a/examples/webxr_xr_cubes.html b/examples/webxr_xr_cubes.html index b08b0d78f3fee3..27b3c615990e34 100644 --- a/examples/webxr_xr_cubes.html +++ b/examples/webxr_xr_cubes.html @@ -12,10 +12,6 @@ three.js xr - interactive cubes - - - - - + + + + diff --git a/examples/webxr_xr_haptics.html b/examples/webxr_xr_haptics.html index b4c99ff7e86930..20448fae75d39f 100644 --- a/examples/webxr_xr_haptics.html +++ b/examples/webxr_xr_haptics.html @@ -12,10 +12,6 @@ three.js xr - haptics - - - - - - - diff --git a/manual/en/animation-system.html b/manual/en/animation-system.html new file mode 100644 index 00000000000000..231e6e7396ef44 --- /dev/null +++ b/manual/en/animation-system.html @@ -0,0 +1,173 @@ + + + Animation System + + + + + + + + + + + + + +
        +
        +

        Animation System

        +
        +
        +
        + +

        Overview

        + +

        + Within the three.js animation system you can animate various properties of your models: + the bones of a skinned and rigged model, morph targets, different material properties + (colors, opacity, booleans), visibility and transforms. The animated properties can be faded in, + faded out, crossfaded and warped. The weight and time scales of different simultaneous + animations on the same object as well as on different objects can be changed + independently. Various animations on the same and on different objects can be + synchronized.

        + + To achieve all this in one homogeneous system, the three.js animation system + [link:https://github.com/mrdoob/three.js/issues/6881 has completely changed in 2015] + (beware of outdated information!), and it has now an architecture similar to + Unity/Unreal Engine 4. This page gives a short overview of the main components of the + system and how they work together. + +

        + +

        Animation Clips

        + +

        + + If you have successfully imported an animated 3D object (it doesn't matter if it has + bones or morph targets or both) — for example exporting it from Blender with the + [link:https://github.com/KhronosGroup/glTF-Blender-IO glTF Blender exporter] and + loading it into a three.js scene using `GLTFLoader` — one of the response fields + should be an array named "animations", containing the animation clips + for this model (see a list of possible loaders below).

        + + Each `AnimationClip` usually holds the data for a certain activity of the object. If the + mesh is a character, for example, there may be one AnimationClip for a walkcycle, a second + for a jump, a third for sidestepping and so on. + +

        + +

        Keyframe Tracks

        + +

        + + Inside of such an `AnimationClip` the data for each animated property are stored in a + separate `KeyframeTrack`. Assuming a character object has a skeleton, + one keyframe track could store the data for the position changes of the lower arm bone + over time, a different track the data for the rotation changes of the same bone, a third + the track position, rotation or scaling of another bone, and so on. It should be clear, + that an AnimationClip can be composed of lots of such tracks.

        + + Assuming the model has morph targets (for example one morph + target showing a friendly face and another showing an angry face), each track holds the + information as to how the influence of a certain morph target changes during the performance + of the clip. + +

        + +

        Animation Mixer

        + +

        + + The stored data forms only the basis for the animations - actual playback is controlled by + the `AnimationMixer`. You can imagine this not only as a player for animations, but + as a simulation of a hardware like a real mixer console, which can control several animations + simultaneously, blending and merging them. + +

        + +

        Animation Actions

        + +

        + + The `AnimationMixer` itself has only very few (general) properties and methods, because it + can be controlled by the animation actions. By configuring an + `AnimationAction` you can determine when a certain `AnimationClip` shall be played, paused + or stopped on one of the mixers, if and how often the clip has to be repeated, whether it + shall be performed with a fade or a time scaling, and some additional things, such crossfading + or synchronizing. + +

        + +

        Animation Object Groups

        + +

        + + If you want a group of objects to receive a shared animation state, you can use an + `AnimationObjectGroup`. + +

        + +

        Supported Formats and Loaders

        + +

        + Note that not all model formats include animation (OBJ notably does not), and that only some + three.js loaders support `AnimationClip` sequences. Several that do + support this animation type: +

        + +
          +
        • THREE.ObjectLoader
        • +
        • THREE.BVHLoader
        • +
        • THREE.ColladaLoader
        • +
        • THREE.FBXLoader
        • +
        • THREE.GLTFLoader
        • +
        + +

        + Note that 3ds max and Maya currently can't export multiple animations (meaning animations which are not + on the same timeline) directly to a single file. +

        + +

        Example

        + +
        +let mesh;
        +
        +// Create an AnimationMixer, and get the list of AnimationClip instances
        +const mixer = new THREE.AnimationMixer( mesh );
        +const clips = mesh.animations;
        +
        +// Update the mixer on each frame
        +function update () {
        +  mixer.update( deltaSeconds );
        +}
        +
        +// Play a specific animation
        +const clip = THREE.AnimationClip.findByName( clips, 'dance' );
        +const action = mixer.clipAction( clip );
        +action.play();
        +
        +// Play all animations
        +clips.forEach( function ( clip ) {
        +  mixer.clipAction( clip ).play();
        +} );
        +
        + +
        +
        +
        + + + + + + + + diff --git a/manual/en/backgrounds.html b/manual/en/backgrounds.html index 5dbbcc34f49386..e42780f9b12ee2 100644 --- a/manual/en/backgrounds.html +++ b/manual/en/backgrounds.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/en/billboards.html b/manual/en/billboards.html index 6232d173a2f489..88a1ac6ce967b5 100644 --- a/manual/en/billboards.html +++ b/manual/en/billboards.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/en/cameras.html b/manual/en/cameras.html index ede24751a60c7e..9603715c9deedf 100644 --- a/manual/en/cameras.html +++ b/manual/en/cameras.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/en/canvas-textures.html b/manual/en/canvas-textures.html index a328eaf10f856b..92090d68765567 100644 --- a/manual/en/canvas-textures.html +++ b/manual/en/canvas-textures.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/en/cleanup.html b/manual/en/cleanup.html index 6cf0edd52f8ee4..7ac0c5668768f8 100644 --- a/manual/en/cleanup.html +++ b/manual/en/cleanup.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/en/color-management.html b/manual/en/color-management.html new file mode 100644 index 00000000000000..158a7fec308c11 --- /dev/null +++ b/manual/en/color-management.html @@ -0,0 +1,357 @@ + + + Color Management + + + + + + + + + + + + + + +
        +
        +

        Color Management

        +
        +
        +
        + +

        What is a color space?

        + +

        + Every color space is a collection of several design decisions, chosen together to support a + large range of colors while satisfying technical constraints related to precision and display + technologies. When creating a 3D asset, or assembling 3D assets together into a scene, it is + important to know what these properties are, and how the properties of one color space relate + to other color spaces in the scene. +

        + +
        + +
        + sRGB colors and white point (D65) displayed in the reference CIE 1931 chromaticity + diagram. Colored region represents a 2D projection of the sRGB gamut, which is a 3D + volume. Source: Wikipedia +
        +
        + +
          +
        • + Color primaries: Primary colors (e.g. red, green, blue) are not absolutes; they are + selected from the visible spectrum based on constraints of limited precision and + capabilities of available display devices. Colors are expressed as a ratio of the primary colors. +
        • +
        • + White point: Most color spaces are engineered such that an equally weighted sum of + primaries R = G = B will appear to be without color, or "achromatic". The appearance + of achromatic values (like white or grey) depend on human perception, which in turn depends + heavily on the context of the observer. A color space specifies its "white point" to balance + these needs. The white point defined by the sRGB color space is + [link:https://en.wikipedia.org/wiki/Illuminant_D65 D65]. +
        • +
        • + Transfer functions: After choosing the color gamut and a color model, we still need to + define mappings ("transfer functions") of numerical values to/from the color space. Does r = 0.5 + represent 50% less physical illumination than r = 1.0? Or 50% less bright, as perceived + by an average human eye? These are different things, and that difference can be represented as + a mathematical function. Transfer functions may be linear or nonlinear, depending + on the objectives of the color space. sRGB defines nonlinear transfer functions. Those + functions are sometimes approximated as gamma functions, but the term "gamma" is + ambiguous and should be avoided in this context. +
        • +
        + + These three parameters — color primaries, white point, and transfer functions — define a color + space, with each chosen for particular goals. Having defined the parameters, a few additional terms + are helpful: + +
          +
        • + Color model: Syntax for numerically identifying colors within chosen the color gamut — + a coordinate system for colors. In three.js we're mainly concerned with the RGB color + model, having three coordinates r, g, b ∈ [0,1] ("closed domain") or + r, g, b ∈ [0,∞] ("open domain") each representing a fraction of a primary + color. Other color models (HSL, Lab, LCH) are commonly used for artistic control. +
        • +
        • + Color gamut: Once color primaries and a white point have been chosen, these represent + a volume within the visible spectrum (a "gamut"). Colors not within this volume ("out of gamut") + cannot be expressed by closed domain [0,1] RGB values. In the open domain [0,∞], the gamut is + technically infinite. +
        • +
        + +

        + Consider two very common color spaces: `SRGBColorSpace` ("sRGB") and + `LinearSRGBColorSpace` ("Linear-sRGB"). Both use the same primaries and white point, + and therefore have the same color gamut. Both use the RGB color model. They differ only in + the transfer functions — Linear-sRGB is linear with respect to physical light intensity. + sRGB uses the nonlinear sRGB transfer functions, and more closely resembles the way that + the human eye perceives light and the responsiveness of common display devices. +

        + +

        + That difference is important. Lighting calculations and other rendering operations must + generally occur in a linear color space. However, a linear colors are less efficient to + store in an image or framebuffer, and do not look correct when viewed by a human observer. + As a result, input textures and the final rendered image will generally use the nonlinear + sRGB color space. +

        + +
        +

        + ℹ️ NOTICE: While some modern displays support wider gamuts like Display-P3, + the web platform's graphics APIs largely rely on sRGB. Applications using three.js + today will typically use only the sRGB and Linear-sRGB color spaces. +

        +
        + +

        Roles of color spaces

        + +

        + Linear workflows — required for modern rendering methods — generally involve more than + one color space, each assigned to a particular role. Linear and nonlinear color spaces are + appropriate for different roles, explained below. +

        + +

        Input color space

        + +

        + Colors supplied to three.js — from color pickers, textures, 3D models, and other sources — + each have an associated color space. Those not already in the Linear-sRGB working color + space must be converted, and textures be given the correct texture.colorSpace assignment. + Certain conversions (for hexadecimal and CSS colors in sRGB) can be made automatically if + the THREE.ColorManagement API is enabled before initializing colors: +

        + + +THREE.ColorManagement.enabled = true; + + +

        + THREE.ColorManagement is enabled by default. +

        + +
          +
        • + Materials, lights, and shaders: Colors in materials, lights, and shaders store + RGB components in the Linear-sRGB working color space. +
        • +
        • + Vertex colors: `BufferAttribute` store RGB components in the + Linear-sRGB working color space. +
        • +
        • + Color textures: PNG or JPEG `Texture` containing color information + (like .map or .emissiveMap) use the closed domain sRGB color space, and must be annotated with + texture.colorSpace = SRGBColorSpace. Formats like OpenEXR (sometimes used for .envMap or + .lightMap) use the Linear-sRGB color space indicated with texture.colorSpace = LinearSRGBColorSpace, + and may contain values in the open domain [0,∞]. +
        • +
        • + Non-color textures: Textures that do not store color information (like .normalMap + or .roughnessMap) do not have an associated color space, and generally use the (default) texture + annotation of texture.colorSpace = NoColorSpace. In rare cases, non-color data + may be represented with other nonlinear encodings for technical reasons. +
        • +
        + +
        +

        + ⚠️ WARNING: Many formats for 3D models do not correctly or consistently + define color space information. While three.js attempts to handle most cases, problems + are common with older file formats. For best results, use glTF 2.0 (`GLTFLoader`) + and test 3D models in online viewers early to confirm the asset itself is correct. +

        +
        + +

        Working color space

        + +

        + Rendering, interpolation, and many other operations must be performed in an open domain + linear working color space, in which RGB components are proportional to physical + illumination. In three.js, the working color space is Linear-sRGB. +

        + +

        Output color space

        + +

        + Output to a display device, image, or video may involve conversion from the open domain + Linear-sRGB working color space to another color space. The conversion is defined by + (`WebGLRenderer.outputColorSpace`). When using post-processing, this requires OutputPass. +

        + +
          +
        • + Display: Colors written to a WebGL canvas for display should be in the sRGB + color space. +
        • +
        • + Image: Colors written to an image should use the color space appropriate for + the format and usage. Fully-rendered images written to PNG or JPEG textures generally + use the sRGB color space. Images containing emission, light maps, or other data not + confined to the [0,1] range will generally use the open domain Linear-sRGB color space, + and a compatible image format like OpenEXR. +
        • +
        + +
        +

        + ⚠️ WARNING: Render targets may use either sRGB or Linear-sRGB. sRGB makes + better use of limited precision. In the closed domain, 8 bits often suffice for sRGB + whereas ≥12 bits (half float) may be required for Linear-sRGB. If later pipeline + stages require Linear-sRGB input, the additional conversions may have a small + performance cost. +

        +
        + +

        + Custom materials based on `ShaderMaterial` and `RawShaderMaterial` have to implement their own output color space conversion. + For instances of `ShaderMaterial`, adding the `colorspace_fragment` shader chunk to the fragment shader's `main()` function should be sufficient. +

        + +

        Working with THREE.Color instances

        + +

        + Methods reading or modifying `Color` instances assume data is already in the + three.js working color space, Linear-sRGB. RGB and HSL components are direct + representations of data stored by the Color instance, and are never converted + implicitly. Color data may be explicitly converted with .convertLinearToSRGB() + or .convertSRGBToLinear(). +

        + +
        +// RGB components (no change).
        +color.r = color.g = color.b = 0.5;
        +console.log( color.r ); // → 0.5
        +
        +// Manual conversion.
        +color.r = 0.5;
        +color.convertSRGBToLinear();
        +console.log( color.r ); // → 0.214041140
        +
        + +

        + With ColorManagement.enabled = true set (recommended), certain conversions + are made automatically. Because hexadecimal and CSS colors are generally sRGB, `Color` + methods will automatically convert these inputs from sRGB to Linear-sRGB in setters, or + convert from Linear-sRGB to sRGB when returning hexadecimal or CSS output from getters. +

        + +
        +// Hexadecimal conversion.
        +color.setHex( 0x808080 );
        +console.log( color.r ); // → 0.214041140
        +console.log( color.getHex() ); // → 0x808080
        +
        +// CSS conversion.
        +color.setStyle( 'rgb( 0.5, 0.5, 0.5 )' );
        +console.log( color.r ); // → 0.214041140
        +
        +// Override conversion with 'colorSpace' argument.
        +color.setHex( 0x808080, LinearSRGBColorSpace );
        +console.log( color.r ); // → 0.5
        +console.log( color.getHex( LinearSRGBColorSpace ) ); // → 0x808080
        +console.log( color.getHex( SRGBColorSpace ) ); // → 0xBCBCBC
        +
        + +

        Common mistakes

        + +

        + When an individual color or texture is misconfigured, it will appear darker or lighter than + expected. When the renderer's output color space is misconfigured, the entire scene may appear + darker (e.g. missing conversion to sRGB) or lighter (e.g. a double conversion to sRGB with + post-processing). In each case the problem may not be uniform, and simply increasing/decreasing + lighting does not solve it. +

        + +

        + A more subtle issue appears when both the input color spaces and the output color + spaces are incorrect — the overall brightness levels may be fine, but colors may change + unexpectedly under different lighting, or shading may appear more blown-out and less soft + than intended. These two wrongs do not make a right, and it's important that the working + color space be linear ("scene referred") and the output color space be nonlinear + ("display referred"). +

        + +

        Further reading

        + + + +
        +
        +
        + + + + + + + + diff --git a/manual/en/creating-a-scene.html b/manual/en/creating-a-scene.html new file mode 100644 index 00000000000000..7d9cd480465957 --- /dev/null +++ b/manual/en/creating-a-scene.html @@ -0,0 +1,179 @@ + + + Creating a scene + + + + + + + + + + + + + +
        +
        +

        Creating a scene

        +
        +
        +
        + +

        The goal of this section is to give a brief introduction to three.js. We will start by setting up a scene, with a spinning cube. A working example is provided at the bottom of the page in case you get stuck and need help.

        + +

        Before we start

        + +

        + If you haven't yet, go through the `Installation` guide. We'll assume you've already set up the same project structure (including index.html and main.js), have installed three.js, and are either running a build tool, or using a local server with a CDN and import maps. +

        + +

        Creating the scene

        + +

        To actually be able to display anything with three.js, we need three things: scene, camera and renderer, so that we can render the scene with camera.

        + +

        main.js —

        + +
        +import * as THREE from 'three';
        +
        +const scene = new THREE.Scene();
        +const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
        +
        +const renderer = new THREE.WebGLRenderer();
        +renderer.setSize( window.innerWidth, window.innerHeight );
        +document.body.appendChild( renderer.domElement );
        +
        + +

        Let's take a moment to explain what's going on here. We have now set up the scene, our camera and the renderer.

        + +

        There are a few different cameras in three.js. For now, let's use a `PerspectiveCamera`.

        + +

        The first attribute is the `field of view`. FOV is the extent of the scene that is seen on the display at any given moment. The value is in degrees.

        + +

        The second one is the `aspect ratio`. You almost always want to use the width of the element divided by the height, or you'll get the same result as when you play old movies on a widescreen TV - the image looks squished.

        + +

        The next two attributes are the `near` and `far` clipping plane. What that means, is that objects further away from the camera than the value of `far` or closer than `near` won't be rendered. You don't have to worry about this now, but you may want to use other values in your apps to get better performance.

        + +

        Next up is the renderer. In addition to creating the renderer instance, we also need to set the size at which we want it to render our app. It's a good idea to use the width and height of the area we want to fill with our app - in this case, the width and height of the browser window. For performance intensive apps, you can also give `setSize` smaller values, like `window.innerWidth/2` and `window.innerHeight/2`, which will make the app render at quarter size.

        + +

        If you wish to keep the size of your app but render it at a lower resolution, you can do so by calling `setSize` with false as `updateStyle` (the third argument). For example, `setSize(window.innerWidth/2, window.innerHeight/2, false)` will render your app at half resolution, given that your <canvas> has 100% width and height.

        + +

        Last but not least, we add the `renderer` element to our HTML document. This is a <canvas> element the renderer uses to display the scene to us.

        + +

        "That's all good, but where's that cube you promised?" Let's add it now.

        + +
        +const geometry = new THREE.BoxGeometry( 1, 1, 1 );
        +const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
        +const cube = new THREE.Mesh( geometry, material );
        +scene.add( cube );
        +
        +camera.position.z = 5;
        +
        + +

        To create a cube, we need a `BoxGeometry`. This is an object that contains all the points (`vertices`) and fill (`faces`) of the cube. We'll explore this more in the future.

        + +

        In addition to the geometry, we need a material to color it. Three.js comes with several materials, but we'll stick to the `MeshBasicMaterial` for now. All materials take an object of properties which will be applied to them. To keep things very simple, we only supply a color attribute of `0x00ff00`, which is green. This works the same way that colors work in CSS or Photoshop (`hex colors`).

        + +

        The third thing we need is a `Mesh`. A mesh is an object that takes a geometry, and applies a material to it, which we then can insert to our scene, and move freely around.

        + +

        By default, when we call `scene.add()`, the thing we add will be added to the coordinates `(0,0,0)`. This would cause both the camera and the cube to be inside each other. To avoid this, we simply move the camera out a bit.

        + +

        Rendering the scene

        + +

        If you copied the code from above into the main.js file we created earlier, you wouldn't be able to see anything. This is because we're not actually rendering anything yet. For that, we need what's called a render or animation loop.

        + +
        +function animate() {
        +  renderer.render( scene, camera );
        +}
        +renderer.setAnimationLoop( animate );
        +
        + +

        This will create a loop that causes the renderer to draw the scene every time the screen is refreshed (on a typical screen this means 60 times per second). If you're new to writing games in the browser, you might say "why don't we just create a setInterval ?" The thing is - we could, but `requestAnimationFrame` which is internally used in `WebGLRenderer` has a number of advantages. Perhaps the most important one is that it pauses when the user navigates to another browser tab, hence not wasting their precious processing power and battery life.

        + +

        Animating the cube

        + +

        If you insert all the code above into the file you created before we began, you should see a green box. Let's make it all a little more interesting by rotating it.

        + +

        Add the following code right above the `renderer.render` call in your `animate` function:

        + +
        +cube.rotation.x += 0.01;
        +cube.rotation.y += 0.01;
        +
        + +

        This will be run every frame (normally 60 times per second), and give the cube a nice rotation animation. Basically, anything you want to move or change while the app is running has to go through the animation loop. You can of course call other functions from there, so that you don't end up with an `animate` function that's hundreds of lines.

        + +

        The result

        +

        Congratulations! You have now completed your first three.js application. It's simple, but you have to start somewhere.

        + +

        The full code is available below and as an editable [link:https://jsfiddle.net/tswh48fL/ live example]. Play around with it to get a better understanding of how it works.

        + +

        index.html —

        + +
        +<!DOCTYPE html>
        +<html lang="en">
        +  <head>
        +    <meta charset="utf-8">
        +    <title>My first three.js app</title>
        +    <style>
        +      body { margin: 0; }
        +    </style>
        +  </head>
        +  <body>
        +    <script type="module" src="/main.js"></script>
        +  </body>
        +</html>
        +
        + +

        main.js —

        + +
        +import * as THREE from 'three';
        +
        +const scene = new THREE.Scene();
        +const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
        +
        +const renderer = new THREE.WebGLRenderer();
        +renderer.setSize( window.innerWidth, window.innerHeight );
        +renderer.setAnimationLoop( animate );
        +document.body.appendChild( renderer.domElement );
        +
        +const geometry = new THREE.BoxGeometry( 1, 1, 1 );
        +const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
        +const cube = new THREE.Mesh( geometry, material );
        +scene.add( cube );
        +
        +camera.position.z = 5;
        +
        +function animate() {
        +
        +  cube.rotation.x += 0.01;
        +  cube.rotation.y += 0.01;
        +
        +  renderer.render( scene, camera );
        +
        +}
        +
        + +
        +
        +
        + + + + + + + + diff --git a/manual/en/creating-text.html b/manual/en/creating-text.html new file mode 100644 index 00000000000000..55746dbb444601 --- /dev/null +++ b/manual/en/creating-text.html @@ -0,0 +1,170 @@ + + + Creating Text + + + + + + + + + + + + + +
        +
        +

        Creating Text

        +
        +
        +
        + +
        +

        + There are often times when you might need to use text in your three.js application - here are + a couple of ways that you can do so. +

        +
        + +

        1. DOM + CSS

        +
        +

        + Using HTML is generally the easiest and fastest manner to add text. This is the method + used for descriptive overlays in most three.js examples. +

        +

        You can add content to a

        +
        +<div id="info">Description</div>
        +
        +

        + and use CSS markup to position absolutely at a position above all others with a + z-index especially if you are running three.js full screen. +

        + +
        +#info {
        +  position: absolute;
        +  top: 10px;
        +  width: 100%;
        +  text-align: center;
        +  z-index: 100;
        +  display:block;
        +}
        +
        + +
        + + +

        2. Use `CSS2DRenderer` or `CSS3DRenderer`

        +
        +

        + Use these renderers to draw high-quality text contained in DOM elements to your three.js scene. + This is similar to 1. except that with these renderers elements can be integrated more tightly and dynamically into the scene. +

        +
        + + +

        3. Draw text to canvas and use as a `Texture`

        +
        +

        Use this method if you wish to draw text easily on a plane in your three.js scene.

        +
        + + +

        4. Create a model in your favourite 3D application and export to three.js

        +
        +

        Use this method if you prefer working with your 3d applications and importing the models to three.js.

        +
        + + +

        5. Procedural Text Geometry

        +
        +

        + If you prefer to work purely in THREE.js or to create procedural and dynamic 3D + text geometries, you can create a mesh whose geometry is an instance of THREE.TextGeometry: +

        +

        + new THREE.TextGeometry( text, parameters ); +

        +

        + In order for this to work, however, your TextGeometry will need an instance of THREE.Font + to be set on its "font" parameter. + + See the `TextGeometry` page for more info on how this can be done, descriptions of each + accepted parameter, and a list of the JSON fonts that come with the THREE.js distribution itself. +

        + +

        Examples

        + +

        + [example:webgl_geometry_text WebGL / geometry / text]
        + [example:webgl_shadowmap WebGL / shadowmap] +

        + +

        + If Typeface is down, or you want to use a font that is not there, there's a tutorial + with a python script for blender that allows you to export text to Three.js's JSON format: + [link:http://www.jaanga.com/2012/03/blender-to-threejs-create-3d-text-with.html] +

        + +
        + + +

        6. Bitmap Fonts

        +
        +

        + BMFonts (bitmap fonts) allow batching glyphs into a single BufferGeometry. BMFont rendering + supports word-wrapping, letter spacing, kerning, signed distance fields with standard + derivatives, multi-channel signed distance fields, multi-texture fonts, and more. + See [link:https://github.com/felixmariotto/three-mesh-ui three-mesh-ui] or [link:https://github.com/Jam3/three-bmfont-text three-bmfont-text]. +

        +

        + Stock fonts are available in projects like + [link:https://github.com/etiennepinchon/aframe-fonts A-Frame Fonts], or you can create your own + from any .TTF font, optimizing to include only characters required for a project. +

        +

        + Some helpful tools: +

        +
          +
        • [link:http://msdf-bmfont.donmccurdy.com/ msdf-bmfont-web] (web-based)
        • +
        • [link:https://github.com/soimy/msdf-bmfont-xml msdf-bmfont-xml] (commandline)
        • +
        • [link:https://github.com/libgdx/libgdx/wiki/Hiero hiero] (desktop app)
        • +
        +
        + + +

        7. Troika Text

        +
        +

        + The [link:https://www.npmjs.com/package/troika-three-text troika-three-text] package renders + quality antialiased text using a similar technique as BMFonts, but works directly with any .TTF + or .WOFF font file so you don't have to pregenerate a glyph texture offline. It also adds + capabilities including: +

        +
          +
        • Effects like strokes, drop shadows, and curvature
        • +
        • The ability to apply any three.js Material, even a custom ShaderMaterial
        • +
        • Support for font ligatures, scripts with joined letters, and right-to-left/bidirectional layout
        • +
        • Optimization for large amounts of dynamic text, performing most work off the main thread in a web worker
        • +
        +
        + +
        +
        +
        + + + + + + + + diff --git a/manual/en/custom-buffergeometry.html b/manual/en/custom-buffergeometry.html index b2db696c9f59c2..6d7350a71e05a5 100644 --- a/manual/en/custom-buffergeometry.html +++ b/manual/en/custom-buffergeometry.html @@ -11,10 +11,6 @@ - - - - @@ -445,7 +440,7 @@

        Custom BufferGeometry

        - + diff --git a/manual/en/debugging-glsl.html b/manual/en/debugging-glsl.html index 7500b0262437bb..4744fe4df26460 100644 --- a/manual/en/debugging-glsl.html +++ b/manual/en/debugging-glsl.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/en/debugging-javascript.html b/manual/en/debugging-javascript.html index 5ff7c5d081c75a..26c5f4fcd91315 100644 --- a/manual/en/debugging-javascript.html +++ b/manual/en/debugging-javascript.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/en/drawing-lines.html b/manual/en/drawing-lines.html new file mode 100644 index 00000000000000..8adf159d93eb22 --- /dev/null +++ b/manual/en/drawing-lines.html @@ -0,0 +1,91 @@ + + + Drawing Lines + + + + + + + + + + + + + +
        +
        +

        Drawing Lines

        +
        +
        +
        + +

        + Let's say you want to draw a line or a circle, not a wireframe `Mesh`. + First we need to set up the renderer, scene and camera (see the Creating a scene page). +

        + +

        Here is the code that we will use:

        +
        +const renderer = new THREE.WebGLRenderer();
        +renderer.setSize( window.innerWidth, window.innerHeight );
        +document.body.appendChild( renderer.domElement );
        +
        +const camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 500 );
        +camera.position.set( 0, 0, 100 );
        +camera.lookAt( 0, 0, 0 );
        +
        +const scene = new THREE.Scene();
        +
        +

        Next thing we will do is define a material. For lines we have to use `LineBasicMaterial` or `LineDashedMaterial`.

        +
        +//create a blue LineBasicMaterial
        +const material = new THREE.LineBasicMaterial( { color: 0x0000ff } );
        +
        + +

        + After material we will need a geometry with some vertices: +

        + +
        +const points = [];
        +points.push( new THREE.Vector3( - 10, 0, 0 ) );
        +points.push( new THREE.Vector3( 0, 10, 0 ) );
        +points.push( new THREE.Vector3( 10, 0, 0 ) );
        +
        +const geometry = new THREE.BufferGeometry().setFromPoints( points );
        +
        + +

        Note that lines are drawn between each consecutive pair of vertices, but not between the first and last (the line is not closed.)

        + +

        Now that we have points for two lines and a material, we can put them together to form a line.

        +
        +const line = new THREE.Line( geometry, material );
        +
        +

        All that's left is to add it to the scene and call `renderer.render()`.

        + +
        +scene.add( line );
        +renderer.render( scene, camera );
        +
        + +

        You should now be seeing an arrow pointing upwards, made from two blue lines.

        + +
        +
        +
        + + + + + + + + diff --git a/manual/en/faq.html b/manual/en/faq.html new file mode 100644 index 00000000000000..16adfc074d7939 --- /dev/null +++ b/manual/en/faq.html @@ -0,0 +1,95 @@ + + + FAQ + + + + + + + + + + + + + +
        +
        +

        FAQ

        +
        +
        +
        + +

        Which 3D model format is best supported?

        +
        +

        + The recommended format for importing and exporting assets is glTF (GL Transmission Format). Because glTF is focused on runtime asset delivery, it is compact to transmit and fast to load. +

        +

        + three.js provides loaders for many other popular formats like FBX, Collada or OBJ as well. Nevertheless, you should always try to establish a glTF based workflow in your projects first. +

        +
        + +

        Why are there meta viewport tags in examples?

        +
        +
        <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
        + +

        These tags control viewport size and scale for mobile browsers (where page content may be rendered at different size than visible viewport).

        + +

        [link:https://developer.apple.com/library/content/documentation/AppleApplications/Reference/SafariWebContent/UsingtheViewport/UsingtheViewport.html Safari: Using the Viewport]

        + +

        [link:https://developer.mozilla.org/en-US/docs/Web/HTML/Viewport_meta_tag MDN: Using the viewport meta tag]

        +
        + +

        How can scene scale be preserved on resize?

        +

        + We want all objects, regardless of their distance from the camera, to appear the same size, even as the window is resized. + + The key equation to solving this is this formula for the visible height at a given distance: + +

        visible_height = 2 * Math.tan( ( Math.PI / 180 ) * camera.fov / 2 ) * distance_from_camera;
        + If we increase the window height by a certain percentage, then what we want is the visible height at all distances + to increase by the same percentage. + + This can not be done by changing the camera position. Instead you have to change the camera field-of-view. + [link:http://jsfiddle.net/Q4Jpu/ Example]. +

        + +

        Why is part of my object invisible?

        +

        + This could be because of face culling. Faces have an orientation that decides which side is which. And the culling removes the backside in normal circumstances. + To see if this is your problem, change the material side to THREE.DoubleSide. +

        material.side = THREE.DoubleSide
        +

        + +

        Why does three.js sometimes return strange results for invalid inputs?

        +

        + For performance reasons, three.js doesn't validate inputs in most cases. It's your app's responsibility to make sure that all inputs are valid. +

        + +

        Can I use three.js in Node.js?

        +

        + Because three.js is built for the web, it depends on browser and DOM APIs that don't always exist in Node.js. Some of these issues can be avoided by using shims like + [link:https://github.com/stackgl/headless-gl headless-gl] and [link:https://github.com/rstacruz/jsdom-global jsdom-global], or by replacing components like `TextureLoader` + with custom alternatives. Other DOM APIs may be deeply intertwined with the code that uses them, and will be harder to work around. We welcome simple and maintainable pull + requests to improve Node.js support, but recommend opening an issue to discuss your improvements first. +

        + +
        +
        +
        + + + + + + + + diff --git a/manual/en/fog.html b/manual/en/fog.html index 80f852954ca5cf..7a9f18b8a729fc 100644 --- a/manual/en/fog.html +++ b/manual/en/fog.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/en/fundamentals.html b/manual/en/fundamentals.html index 749d1530c9021d..f006992ccdb36e 100644 --- a/manual/en/fundamentals.html +++ b/manual/en/fundamentals.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/en/game.html b/manual/en/game.html index 2a88f968d74b38..50785afd630667 100644 --- a/manual/en/game.html +++ b/manual/en/game.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/en/how-to-create-vr-content.html b/manual/en/how-to-create-vr-content.html new file mode 100644 index 00000000000000..4f39e591ec8b7b --- /dev/null +++ b/manual/en/how-to-create-vr-content.html @@ -0,0 +1,105 @@ + + + How to create VR content + + + + + + + + + + + + + +
        +
        +

        How to create VR content

        +
        +
        +
        + +

        + This guide provides a brief overview of the basic components of a web-based VR application + made with three.js. +

        + +

        Workflow

        + +

        + First, you have to include [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/webxr/VRButton.js VRButton.js] + into your project. +

        + +
        +import { VRButton } from 'three/addons/webxr/VRButton.js';
        +
        + +

        + *VRButton.createButton()* does two important things: It creates a button which indicates + VR compatibility. Besides, it initiates a VR session if the user activates the button. The only thing you have + to do is to add the following line of code to your app. +

        + +
        +document.body.appendChild( VRButton.createButton( renderer ) );
        +
        + +

        + Next, you have to tell your instance of `WebGLRenderer` to enable XR rendering. +

        + +
        +renderer.xr.enabled = true;
        +
        + +

        + Finally, you have to adjust your animation loop since we can't use our well known + *window.requestAnimationFrame()* function. For VR projects we use `renderer.setAnimationLoop()`. + The minimal code looks like this: +

        + +
        +renderer.setAnimationLoop( function () {
        +
        +  renderer.render( scene, camera );
        +
        +} );
        +
        + +

        Next Steps

        + +

        + Have a look at one of the official WebVR examples to see this workflow in action.

        + + [example:webxr_xr_ballshooter WebXR / XR / ballshooter]
        + [example:webxr_xr_cubes WebXR / XR / cubes]
        + [example:webxr_xr_dragging WebXR / XR / dragging]
        + [example:webxr_xr_paint WebXR / XR / paint]
        + [example:webxr_xr_sculpt WebXR / XR / sculpt]
        + [example:webxr_vr_panorama_depth WebXR / VR / panorama_depth]
        + [example:webxr_vr_panorama WebXR / VR / panorama]
        + [example:webxr_vr_rollercoaster WebXR / VR / rollercoaster]
        + [example:webxr_vr_sandbox WebXR / VR / sandbox]
        + [example:webxr_vr_video WebXR / VR / video] +

        + +
        +
        +
        + + + + + + + + diff --git a/manual/en/how-to-dispose-of-objects.html b/manual/en/how-to-dispose-of-objects.html new file mode 100644 index 00000000000000..6095c875db6544 --- /dev/null +++ b/manual/en/how-to-dispose-of-objects.html @@ -0,0 +1,169 @@ + + + How to dispose of Objects + + + + + + + + + + + + + +
        +
        +

        How to dispose of Objects

        +
        +
        +
        + +

        + One important aspect in order to improve performance and avoid memory leaks in your application is the disposal of unused library entities. + Whenever you create an instance of a *three.js* type, you allocate a certain amount of memory. However, *three.js* creates for specific objects + like geometries or materials WebGL related entities like buffers or shader programs which are necessary for rendering. It's important to + highlight that these objects are not released automatically. Instead, the application has to use a special API in order to free such resources. + This guide provides a brief overview about how this API is used and what objects are relevant in this context. +

        + +

        Geometries

        + +

        + A geometry usually represents vertex information defined as a collection of attributes. *three.js* internally creates an object of type [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLBuffer WebGLBuffer] + for each attribute. These entities are only deleted if you call `BufferGeometry.dispose()`. If a geometry becomes obsolete in your application, + execute the method to free all related resources. +

        + +

        Materials

        + +

        + A material defines how objects are rendered. *three.js* uses the information of a material definition in order to construct a shader program for rendering. + Shader programs can only be deleted if the respective material is disposed. For performance reasons, *three.js* tries to reuse existing + shader programs if possible. So a shader program is only deleted if all related materials are disposed. You can indicate the disposal of a material by + executing `Material.dispose()`. +

        + +

        Textures

        + +

        + The disposal of a material has no effect on textures. They are handled separately since a single texture can be used by multiple materials at the same time. + Whenever you create an instance of `Texture`, three.js internally creates an instance of [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLTexture WebGLTexture]. + Similar to buffers, this object can only be deleted by calling `Texture.dispose()`. +

        + +

        + If you use an `ImageBitmap` as the texture's data source, you have to call [link:https://developer.mozilla.org/en-US/docs/Web/API/ImageBitmap/close ImageBitmap.close]() at the application level to dispose of all CPU-side resources. + An automated call of `ImageBitmap.close()` in `Texture.dispose()` is not possible, since the image bitmap becomes unusable, and the engine has no way of knowing if the image bitmap is used elsewhere. +

        + +

        Render Targets

        + +

        + Objects of type `WebGLRenderTarget` not only allocate an instance of [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLTexture WebGLTexture] but also + [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLFramebuffer WebGLFramebuffer]s and [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderbuffer WebGLRenderbuffer]s + for realizing custom rendering destinations. These objects are only deallocated by executing `WebGLRenderTarget.dispose()`. +

        + +

        Skinned Mesh

        + +

        + Skinned meshes represent their bone hierarchy as skeletons. If you don't need a skinned mesh anymore, consider to call `Skeleton.dispose()` on the skeleton to free internal resources. + Keep in mind that skeletons can be shared across multiple skinned meshes, so only call `dispose()` if the skeleton is not used by other active skinned meshes. +

        + +

        Miscellaneous

        + +

        + There are other classes from the examples directory like controls or post processing passes which provide `dispose()` methods in order to remove internal event listeners + or render targets. In general, it's recommended to check the API or documentation of a class and watch for `dispose()`. If present, you should use it when cleaning things up. +

        + +

        FAQ

        + +

        Why can't *three.js* dispose objects automatically?

        + +

        + This question was asked many times by the community so it's important to clarify this matter. Fact is that *three.js* does not know the lifetime or scope + of user-created entities like geometries or materials. This is the responsibility of the application. For example even if a material is currently not used for rendering, + it might be necessary for the next frame. So if the application decides that a certain object can be deleted, it has to notify the engine via calling the respective + `dispose()` method. +

        + +

        Does removing a mesh from the scene also dispose its geometry and material?

        + +

        + No, you have to explicitly dispose the geometry and material via *dispose()*. Keep in mind that geometries and materials can be shared among 3D objects like meshes. +

        + +

        Does *three.js* provide information about the amount of cached objects?

        + +

        + Yes. It's possible to evaluate `renderer.info`, a special property of the renderer with a series of statistical information about the graphics board memory + and the rendering process. Among other things, it tells you how many textures, geometries and shader programs are internally stored. If you notice performance problems + in your application, it's a good idea to debug this property in order to easily identify a memory leak. +

        + +

        What happens when you call `dispose()` on a texture but the image is not loaded yet?

        + +

        + Internal resources for a texture are only allocated if the image has fully loaded. If you dispose a texture before the image was loaded, + nothing happens. No resources were allocated so there is also no need for clean up. +

        + +

        What happens when I call `dispose()` and then use the respective object at a later point?

        + +

        + That depends. For geometries, materials, textures, render targets and post processing passes the deleted internal resources can be created again by the engine. + So no runtime error will occur but you might notice a negative performance impact for the current frame, especially when shader programs have to be compiled. + + Controls and renderers are an exception. Instances of these classes can not be used after `dispose()` has been called. You have to create new instances in this case. +

        + +

        How should I manage *three.js* objects in my app? When do I know how to dispose things?

        + +

        + In general, there is no definite recommendation for this. It highly depends on the specific use case when calling `dispose()` is appropriate. It's important to highlight that + it's not always necessary to dispose objects all the time. A good example for this is a game which consists of multiple levels. A good place for object disposal is when + switching the level. The app could traverse through the old scene and dispose all obsolete materials, geometries and textures. As mentioned in the previous section, it does not + produce a runtime error if you dispose an object that is actually still in use. The worst thing that can happen is performance drop for a single frame. +

        + +

        Why `renderer.info.memory` is still reporting geometries and textures after traversing the scene and disposing all reachable textures and geometries?

        + +

        + In certain cases, there are some textures and geometries used internally by Three.js + that are not reachable when traversing the scene graph in order to be disposed. + It is expected that `renderer.info.memory` will still report them even after a full scene cleanup. + However, they do not leak, but they are reused on consecutive scene cleanup/repopulating cycles. + + These cases could be related to using `material.envMap`, `scene.background`, `scene.environment`, + or other contexts that would require the engine to create textures or geometries for internal use. +

        + +

        Examples that demonstrate the usage of dispose()

        + +

        + [example:webgl_test_memory WebGL / test / memory]
        + [example:webgl_test_memory2 WebGL / test / memory2]
        +

        + +
        +
        +
        + + + + + + + + diff --git a/manual/en/how-to-update-things.html b/manual/en/how-to-update-things.html new file mode 100644 index 00000000000000..e508f6ffaaa4a4 --- /dev/null +++ b/manual/en/how-to-update-things.html @@ -0,0 +1,276 @@ + + + How to update Things + + + + + + + + + + + + + +
        +
        +

        How to update Things

        +
        +
        +
        + +
        +

        All objects by default automatically update their matrices if they have been added to the scene with

        +
        +const object = new THREE.Object3D();
        +scene.add( object );
        +
        + or if they are the child of another object that has been added to the scene: +
        +const object1 = new THREE.Object3D();
        +const object2 = new THREE.Object3D();
        +
        +object1.add( object2 );
        +scene.add( object1 ); //object1 and object2 will automatically update their matrices
        +
        +
        + +

        However, if you know the object will be static, you can disable this and update the transform matrix manually just when needed.

        + +
        +object.matrixAutoUpdate = false;
        +object.updateMatrix();
        +
        + +

        BufferGeometry

        +
        +

        + BufferGeometries store information (such as vertex positions, face indices, normals, colors, + UVs, and any custom attributes) in attribute buffers - that is, + [link:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays typed arrays]. + This makes them generally faster than standard Geometries, at the cost of being somewhat harder to + work with. +

        +

        + With regards to updating BufferGeometries, the most important thing to understand is that + you cannot resize buffers (this is very costly, basically the equivalent to creating a new geometry). + You can however update the content of buffers. +

        +

        + This means that if you know an attribute of your BufferGeometry will grow, say the number of vertices, + you must pre-allocate a buffer large enough to hold any new vertices that may be created. Of + course, this also means that there will be a maximum size for your BufferGeometry - there is + no way to create a BufferGeometry that can efficiently be extended indefinitely. +

        +

        + We'll use the example of a line that gets extended at render time. We'll allocate space + in the buffer for 500 vertices but draw only two at first, using `BufferGeometry.drawRange`. +

        +
        +const MAX_POINTS = 500;
        +
        +// geometry
        +const geometry = new THREE.BufferGeometry();
        +
        +// attributes
        +const positions = new Float32Array( MAX_POINTS * 3 ); // 3 floats (x, y and z) per point
        +geometry.setAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );
        +
        +// draw range
        +const drawCount = 2; // draw the first 2 points, only
        +geometry.setDrawRange( 0, drawCount );
        +
        +// material
        +const material = new THREE.LineBasicMaterial( { color: 0xff0000 } );
        +
        +// line
        +const line = new THREE.Line( geometry, material );
        +scene.add( line );
        +
        +

        + Next we'll randomly add points to the line using a pattern like: +

        +
        +const positionAttribute = line.geometry.getAttribute( 'position' );
        +
        +let x = 0, y = 0, z = 0;
        +
        +for ( let i = 0; i < positionAttribute.count; i ++ ) {
        +
        +    positionAttribute.setXYZ( i, x, y, z );
        +
        +    x += ( Math.random() - 0.5 ) * 30;
        +    y += ( Math.random() - 0.5 ) * 30;
        +    z += ( Math.random() - 0.5 ) * 30;
        +
        +}
        +
        +

        + If you want to change the number of points rendered after the first render, do this: +

        +
        +line.geometry.setDrawRange( 0, newValue );
        +
        +

        + If you want to change the position data values after the first render, you need to + set the needsUpdate flag like so: +

        +
        +positionAttribute.needsUpdate = true; // required after the first render
        +
        + +

        + If you change the position data values after the initial render, you may need to recompute + bounding volumes so other features of the engine like view frustum culling or helpers properly work. +

        +
        +line.geometry.computeBoundingBox();
        +line.geometry.computeBoundingSphere();
        +
        + +

        + [link:https://jsfiddle.net/t4m85pLr/1/ Here is a fiddle] showing an animated line which you can adapt to your use case. +

        + +

        Examples

        + +

        + [example:webgl_custom_attributes WebGL / custom / attributes]
        + [example:webgl_buffergeometry_custom_attributes_particles WebGL / buffergeometry / custom / attributes / particles] +

        + +
        + +

        Materials

        +
        +

        All uniforms values can be changed freely (e.g. colors, textures, opacity, etc), values are sent to the shader every frame.

        + +

        Also GLstate related parameters can change any time (depthTest, blending, polygonOffset, etc).

        + +

        The following properties can't be easily changed at runtime (once the material is rendered at least once):

        +
          +
        • numbers and types of uniforms
        • +
        • presence or not of +
            +
          • texture
          • +
          • fog
          • +
          • vertex colors
          • +
          • morphing
          • +
          • shadow map
          • +
          • alpha test
          • +
          • transparent
          • +
          +
        • +
        + +

        Changes in these require building of new shader program. You'll need to set

        + material.needsUpdate = true + +

        Bear in mind this might be quite slow and induce jerkiness in framerate (especially on Windows, as shader compilation is slower in DirectX than OpenGL).

        + +

        For smoother experience you can emulate changes in these features to some degree by having "dummy" values like zero intensity lights, white textures, or zero density fog.

        + +

        You can freely change the material used for geometry chunks, however you cannot change how an object is divided into chunks (according to face materials).

        + +

        If you need to have different configurations of materials during runtime:

        +

        If the number of materials / chunks is small, you could pre-divide the object beforehand (e.g. hair / face / body / upper clothes / trousers for a human, front / sides / top / glass / tire / interior for a car).

        + +

        If the number is large (e.g. each face could be potentially different), consider a different solution, such as using attributes / textures to drive different per-face look.

        + +

        Examples

        +

        + [example:webgl_materials_car WebGL / materials / car]
        + [example:webgl_postprocessing_dof WebGL / webgl_postprocessing / dof] +

        +
        + + +

        Textures

        +
        +

        Image, canvas, video and data textures need to have the following flag set if they are changed:

        + + texture.needsUpdate = true; + +

        Render targets update automatically.

        + +

        Examples

        +

        + [example:webgl_materials_video WebGL / materials / video]
        + [example:webgl_rtt WebGL / rtt] +

        + +
        + +

        Cameras

        +
        +

        A camera's position and target is updated automatically. If you need to change

        +
          +
        • + fov +
        • +
        • + aspect +
        • +
        • + near +
        • +
        • + far +
        • +
        +

        + then you'll need to recompute the projection matrix: +

        +
        +camera.aspect = window.innerWidth / window.innerHeight;
        +camera.updateProjectionMatrix();
        +
        +
        + +

        InstancedMesh

        +
        +

        + `InstancedMesh` is a class for conveniently access instanced rendering in `three.js`. Certain library features like view frustum culling or + ray casting rely on up-to-date bounding volumes (bounding sphere and bounding box). Because of the way how `InstancedMesh` works, the class + has its own `boundingBox` and `boundingSphere` properties that supersede the bounding volumes on geometry level. +

        +

        + Similar to geometries you have to recompute the bounding box and sphere whenever you change the underlying data. In context of `InstancedMesh`, that + happens when you transform instances via `setMatrixAt()`. You can use the same pattern like with geometries. +

        +
        +instancedMesh.computeBoundingBox();
        +instancedMesh.computeBoundingSphere();
        +
        + +
        + +

        SkinnedMesh

        +
        +

        + `SkinnedMesh` follows the same principles like `InstancedMesh` in context of bounding volumes. Meaning the class has its own version of + `boundingBox` and `boundingSphere` to correctly enclose animated meshes. + When calling `computeBoundingBox()` and `computeBoundingSphere()`, the class computes the respective bounding volumes based on the current + bone transformation (or in other words the current animation state). +

        +
        + +
        +
        +
        + + + + + + + + diff --git a/manual/en/how-to-use-post-processing.html b/manual/en/how-to-use-post-processing.html new file mode 100644 index 00000000000000..6c57904e6639e0 --- /dev/null +++ b/manual/en/how-to-use-post-processing.html @@ -0,0 +1,142 @@ + + + How to use Post Processing + + + + + + + + + + + + + +
        +
        +

        How to use Post Processing

        +
        +
        +
        + +

        + Many three.js applications render their 3D objects directly to the screen. Sometimes, however, you want to apply one or more graphical + effects like Depth-Of-Field, Bloom, Film Grain or various types of Anti-aliasing. Post-processing is a widely used approach + to implement such effects. First, the scene is rendered to a render target which represents a buffer in the video card's memory. + In the next step one or more post-processing passes apply filters and effects to the image buffer before it is eventually rendered to + the screen. +

        +

        + three.js provides a complete post-processing solution via `EffectComposer` to implement such a workflow. +

        + +

        Workflow

        + +

        + The first step in the process is to import all necessary files from the examples directory. The guide assumes you are using the official + [link:https://www.npmjs.com/package/three npm package] of three.js. For our basic demo in this guide we need the following files. +

        + +
        +import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
        +import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
        +import { GlitchPass } from 'three/addons/postprocessing/GlitchPass.js';
        +import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
        +
        + +

        + After all files are successfully imported, we can create our composer by passing in an instance of `WebGLRenderer`. +

        + +
        +const composer = new EffectComposer( renderer );
        +
        + +

        + When using a composer, it's necessary to change the application's animation loop. Instead of calling the render method of + `WebGLRenderer`, we now use the respective counterpart of `EffectComposer`. +

        + +
        +function animate() {
        +
        +  requestAnimationFrame( animate );
        +
        +  composer.render();
        +
        +}
        +
        + +

        + Our composer is now ready so it's possible to configure the chain of post-processing passes. These passes are responsible for creating + the final visual output of the application. They are processed in order of their addition/insertion. In our example, the instance of `RenderPass` + is executed first, then the instance of `GlitchPass` and finally `OutputPass`. The last enabled pass in the chain is automatically rendered to the screen. + The setup of the passes looks like so: +

        + +
        +const renderPass = new RenderPass( scene, camera );
        +composer.addPass( renderPass );
        +
        +const glitchPass = new GlitchPass();
        +composer.addPass( glitchPass );
        +
        +const outputPass = new OutputPass();
        +composer.addPass( outputPass );
        +
        + +

        + `RenderPass` is normally placed at the beginning of the chain in order to provide the rendered scene as an input for the next post-processing step. In our case, + `GlitchPass` is going to use these image data to apply a wild glitch effect. `OutputPass` is usually the last pass in the chain which performs sRGB color space conversion and tone mapping. + Check out this [link:https://threejs.org/examples/webgl_postprocessing_glitch live example] to see it in action. +

        + +

        Built-in Passes

        + +

        + You can use a wide range of pre-defined post-processing passes provided by the engine. They are located in the + [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm/postprocessing postprocessing] directory. +

        + +

        Custom Passes

        + +

        + Sometimes you want to write a custom post-processing shader and include it into the chain of post-processing passes. For this scenario, + you can utilize `ShaderPass`. After importing the file and your custom shader, you can use the following code to setup the pass. +

        + +
        +import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
        +import { LuminosityShader } from 'three/addons/shaders/LuminosityShader.js';
        +
        +// later in your init routine
        +
        +const luminosityPass = new ShaderPass( LuminosityShader );
        +composer.addPass( luminosityPass );
        +
        + +

        + The repository provides a file called [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/shaders/CopyShader.js CopyShader] which is a + good starting code for your own custom shader. `CopyShader` just copies the image contents of the `EffectComposer`'s read buffer + to its write buffer without applying any effects. +

        + +
        +
        +
        + + + + + + + + diff --git a/manual/en/indexed-textures.html b/manual/en/indexed-textures.html index 9d1ba8bd250ed4..1f0430cace0fc0 100644 --- a/manual/en/indexed-textures.html +++ b/manual/en/indexed-textures.html @@ -11,10 +11,6 @@ - - - - + + +
        +
        +

        Installation

        +
        +
        +
        + +

        Project structure

        + +

        + Every three.js project needs at least one HTML file to define the webpage, and a JavaScript file to run your three.js code. The structure and naming choices below aren't required, but will be used throughout this guide for consistency. +

        + +
          +
        • + index.html +
          +<!DOCTYPE html>
          +<html lang="en">
          +  <head>
          +    <meta charset="utf-8">
          +    <title>My first three.js app</title>
          +    <style>
          +      body { margin: 0; }
          +    </style>
          +  </head>
          +  <body>
          +    <script type="module" src="/main.js"></script>
          +  </body>
          +</html>
          +    
          +
        • +
        • + main.js +
          +import * as THREE from 'three';
          +
          +...
          +
          +
        • +
        • + public/ +
            +
          • + The public/ folder is sometimes also called a "static" folder, because the files it contains are pushed to the website unchanged. Usually textures, audio, and 3D models will go here. +
          • +
          +
        • +
        + +

        + Now that we've set up the basic project structure, we need a way to run the project locally and access it through a web browser. Installation and local development can be accomplished with npm and a build tool, or by importing three.js from a CDN. Both options are explained in the sections below. +

        + +

        Option 1: Install with NPM and a build tool

        + +

        Development

        + +

        + Installing from the [link:https://www.npmjs.com/ npm package registry] and using a [link:https://eloquentjavascript.net/10_modules.html#h_zWTXAU93DC build tool] is the recommended approach for most users — the more dependencies your project needs, the more likely you are to run into problems that the static hosting cannot easily resolve. With a build tool, importing local JavaScript files and npm packages should work out of the box, without import maps. +

        + + +
          +
        1. + Install [link:https://nodejs.org/ Node.js]. We'll need it to load manage dependencies and to run our build tool. +
        2. +
        3. +

          + Install three.js and a build tool, [link:https://vitejs.dev/ Vite], using a [link:https://www.joshwcomeau.com/javascript/terminal-for-js-devs/ terminal] in your project folder. Vite will be used during development, but it isn't part of the final webpage. If you prefer to use another build tool, that's fine — we support modern build tools that can import [link:https://eloquentjavascript.net/10_modules.html#h_zWTXAU93DC ES Modules]. +

          +
          +# three.js
          +npm install --save three
          +
          +# vite
          +npm install --save-dev vite
          +
          + +
        4. +
        5. + From your terminal, run: +
          npx vite 
          + +
        6. +
        7. + If everything went well, you'll see a URL like http://localhost:5173 appear in your terminal, and can open that URL to see your web application. +
        8. +
        + +

        + The page will be blank — you're ready to create a scene. +

        + +

        + If you want to learn more about these tools before you continue, see: +

        + +
          +
        • + [link:https://threejs-journey.com/lessons/local-server three.js journey: Local Server] +
        • +
        • + [link:https://vitejs.dev/guide/cli.html Vite: Command Line Interface] +
        • +
        • + [link:https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Package_management MDN: Package management basics] +
        • +
        + +

        Production

        + +

        + Later, when you're ready to deploy your web application, you'll just need to tell Vite to run a production build — npx vite build. Everything used by the application will be compiled, optimized, and copied into the dist/ folder. The contents of that folder are ready to be hosted on your website. +

        + +

        Option 2: Import from a CDN

        + +

        Development

        + +

        Installing without build tools will require some changes to the project structure given above.

        + +
          +
        1. +

          + We imported code from 'three' (an npm package) in main.js, and web browsers don't know what that means. In index.html we'll need to add an [link:https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap import map] defining where to get the package. Put the code below inside the <head></head> tag, after the styles. +

          +
          +<script type="importmap">
          +{
          +  "imports": {
          +    "three": "https://cdn.jsdelivr.net/npm/three@<version>/build/three.module.js",
          +    "three/addons/": "https://cdn.jsdelivr.net/npm/three@<version>/examples/jsm/"
          +  }
          +}
          +</script>
          +
          +

          + Don't forget to replace <version> with an actual version of three.js, like "v0.149.0". The most recent version can be found on the [link:https://www.npmjs.com/package/three?activeTab=versions npm version list]. +

          +
        2. +
        3. +

          + We'll also need to run a local server to host these files at URL where the web browser can access them. While it's technically possible to double-click an HTML file and open it in your browser, important features that we'll later implement, do not work when the page is opened this way, for security reasons. +

          +

          + Install [link:https://nodejs.org/ Node.js], then run [link:https://www.npmjs.com/package/serve serve] to start a local server in the project's directory: +

          +
          npx serve .
          +
        4. +
        5. + If everything went well, you'll see a URL like http://localhost:3000 appear in your terminal, and can open that URL to see your web application. +
        6. +
        + +

        + The page will be blank — you're ready to [link:#manual/introduction/Creating-a-scene create a scene]. +

        + +

        + Many other local static servers are available — some use different languages instead of Node.js, and others are desktop applications. They all work basically the same way, and we've provided a few alternatives below. +

        + +
        + More local servers + +

        Command Line

        + +

        Command line local servers run from a terminal window. The associated programming language may need to be installed first.

        + +
          +
        • npx http-server (Node.js)
        • +
        • npx five-server (Node.js)
        • +
        • python -m SimpleHTTPServer (Python 2.x)
        • +
        • python -m http.server (Python 3.x)
        • +
        • php -S localhost:8000 (PHP 5.4+)
        • +
        + + +

        GUI

        + +

        GUI local servers run as an application window on your computer, and may have a user interface.

        + +
          +
        • [link:https://greggman.github.io/servez Servez]
        • +
        + +

        Code Editor Plugins

        + +

        Some code editors have plugins that spawn a simple server on demand.

        + +
          +
        • [link:https://marketplace.visualstudio.com/items?itemName=yandeu.five-server Five Server] for Visual Studio Code
        • +
        • [link:https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer Live Server] for Visual Studio Code
        • +
        • [link:https://atom.io/packages/atom-live-server Live Server] for Atom
        • +
        + + +
        + +

        Production

        + +

        + When you're ready to deploy your web application, push the source files to your web hosting provider — no need to build or compile anything. The downside of that tradeoff is that you'll need to be careful to keep the import map updated with any dependencies (and dependencies of dependencies!) that your application requires. If the CDN hosting your dependencies goes down temporarily, your website will stop working too. +

        + +

        + IMPORTANT: Import all dependencies from the same version of three.js, and from the same CDN. Mixing files from different sources may cause duplicate code to be included, or even break the application in unexpected ways. +

        + +

        Addons

        + +

        + Out of the box, three.js includes the fundamentals of a 3D engine. Other three.js components — such as controls, loaders, and post-processing effects — are part of the [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm addons/] directory. Addons do not need to be installed separately, but do need to be imported separately. +

        + +

        + The example below shows how to import three.js with the `OrbitControls` and `GLTFLoader` addons. Where necessary, this will also be mentioned in each addon's documentation or examples. +

        + +
        +import * as THREE from 'three';
        +import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
        +import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
        +
        +const controls = new OrbitControls( camera, renderer.domElement );
        +const loader = new GLTFLoader();
        +
        + +

        + Some excellent third-party projects are available for three.js, too. These need to be installed separately — see Libraries and Plugins. +

        + +

        Next Steps

        + +

        + You're now ready to create a scene. +

        + +
        +
        +
        + + + + + + + + diff --git a/manual/en/libraries-and-plugins.html b/manual/en/libraries-and-plugins.html new file mode 100644 index 00000000000000..6e7eba45650cfa --- /dev/null +++ b/manual/en/libraries-and-plugins.html @@ -0,0 +1,146 @@ + + + Libraries and Plugins + + + + + + + + + + + + + +
        +
        +

        Libraries and Plugins

        +
        +
        +
        + +

        + Listed here are externally developed compatible libraries and plugins for three.js. This + list and the associated packages are maintained by the community and not guaranteed + to be up to date. If you'd like to update this list make a PR! +

        + +

        Physics

        + +
          +
        • [link:https://github.com/lo-th/Oimo.js/ Oimo.js]
        • +
        • [link:https://enable3d.io/ enable3d]
        • +
        • [link:https://github.com/kripken/ammo.js/ ammo.js]
        • +
        • [link:https://github.com/pmndrs/cannon-es cannon-es]
        • +
        • [link:https://rapier.rs/ rapier]
        • +
        • [link:https://github.com/jrouwe/JoltPhysics.js Jolt]
        • + +
        + +

        Postprocessing

        + +

        + In addition to the [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm/postprocessing official three.js postprocessing effects], + support for some additional effects and frameworks are available through external libraries. +

        + +
          +
        • [link:https://github.com/vanruesc/postprocessing postprocessing]
        • +
        + +

        Intersection and Raycast Performance

        + +
          +
        • [link:https://github.com/gkjohnson/three-mesh-bvh three-mesh-bvh]
        • +
        + +

        Path Tracing

        + +
          +
        • [link:https://github.com/gkjohnson/three-gpu-pathtracer three-gpu-pathtracer]
        • +
        + +

        File Formats

        + +

        + In addition to the [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm/loaders official three.js loaders], + support for some additional formats is available through external libraries. +

        + +
          +
        • [link:https://github.com/gkjohnson/urdf-loaders/tree/master/javascript urdf-loader]
        • +
        • [link:https://github.com/NASA-AMMOS/3DTilesRendererJS 3d-tiles-renderer-js]
        • +
        • [link:https://github.com/kaisalmen/WWOBJLoader WebWorker OBJLoader]
        • +
        • [link:https://github.com/IFCjs/web-ifc-three IFC.js]
        • +
        + +

        Geometry

        + +
          +
        • [link:https://github.com/spite/THREE.MeshLine THREE.MeshLine]
        • +
        + +

        3D Text and Layout

        + +
          +
        • [link:https://github.com/protectwise/troika/tree/master/packages/troika-three-text troika-three-text]
        • +
        • [link:https://github.com/felixmariotto/three-mesh-ui three-mesh-ui]
        • +
        + +

        Particle Systems

        + +
          +
        • [link:https://github.com/Alchemist0823/three.quarks three.quarks]
        • +
        • [link:https://github.com/creativelifeform/three-nebula three-nebula]
        • +
        + +

        Inverse Kinematics

        + +
          +
        • [link:https://github.com/jsantell/THREE.IK THREE.IK]
        • +
        • [link:https://github.com/lo-th/fullik fullik]
        • +
        • [link:https://github.com/gkjohnson/closed-chain-ik-js closed-chain-ik]
        • +
        + +

        Game AI

        + +
          +
        • [link:https://mugen87.github.io/yuka/ yuka]
        • +
        • [link:https://github.com/donmccurdy/three-pathfinding three-pathfinding]
        • +
        • [link:https://github.com/isaac-mason/recast-navigation-js recast-navigation-js]
        • +
        + +

        Wrappers and Frameworks

        + +
          +
        • [link:https://aframe.io/ A-Frame]
        • +
        • [link:https://lume.io/ Lume] - HTML elements for 3D graphics built on Three.
        • +
        • [link:https://github.com/pmndrs/react-three-fiber react-three-fiber] - React components for 3D graphics built on Three.
        • +
        • [link:https://threepipe.org/ threepipe] - A versatile 3D viewer framework using three.js for rendering.
        • +
        • [link:https://github.com/ecsyjs/ecsy-three ECSY]
        • +
        • [link:https://threlte.xyz/ Threlte] - Svelte components for 3D graphics built on Three.
        • +
        • [link:https://needle.tools/ Needle Engine]
        • +
        • [link:https://tresjs.org/ tresjs] - Vue components for 3D graphics built on Three.
        • +
        • [link:https://giro3d.org Giro3D] - Versatile framework built on Three for visualizing and interacting with Geospatial 2D, 2.5D and 3D data.
        • +
        • [link:https://zap.works/mattercraft/ Mattercraft] - Browser-based visual editor for AR, WebXR and 3D web content, built on three.js with real-time preview and physics engine.
        • +
        + +
        +
        +
        + + + + + + + + diff --git a/manual/en/lights.html b/manual/en/lights.html index 2e6b2f059a22c3..6931ce1ca45d09 100644 --- a/manual/en/lights.html +++ b/manual/en/lights.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/en/load-gltf.html b/manual/en/load-gltf.html index 32a306466664f6..471f5d6d303e44 100644 --- a/manual/en/load-gltf.html +++ b/manual/en/load-gltf.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/en/load-obj.html b/manual/en/load-obj.html index daac37e922aaab..f2ae1202806b35 100644 --- a/manual/en/load-obj.html +++ b/manual/en/load-obj.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/en/loading-3d-models.html b/manual/en/loading-3d-models.html new file mode 100644 index 00000000000000..cdcfd919644567 --- /dev/null +++ b/manual/en/loading-3d-models.html @@ -0,0 +1,187 @@ + + + Loading 3D Models + + + + + + + + + + + + + +
        +
        +

        Loading 3D Models

        +
        +
        +
        + +

        + 3D models are available in hundreds of file formats, each with different + purposes, assorted features, and varying complexity. Although + + three.js provides many loaders, choosing the right format and + workflow will save time and frustration later on. Some formats are + difficult to work with, inefficient for realtime experiences, or simply not + fully supported at this time. +

        + +

        + This guide provides a workflow recommended for most users, and suggestions + for what to try if things don't go as expected. +

        + +

        Before we start

        + +

        + If you're new to running a local server, begin with + Installation + first. Many common errors viewing 3D models can be avoided by hosting files + correctly. +

        + +

        Recommended workflow

        + +

        + Where possible, we recommend using glTF (GL Transmission Format). Both + .GLB and .GLTF versions of the format are + well supported. Because glTF is focused on runtime asset delivery, it is + compact to transmit and fast to load. Features include meshes, materials, + textures, skins, skeletons, morph targets, animations, lights, and + cameras. +

        + +

        + Public-domain glTF files are available on sites like + + Sketchfab, or various tools include glTF export: +

        + + + +

        + If your preferred tools do not support glTF, consider requesting glTF + export from the authors, or posting on + the glTF roadmap thread. +

        + +

        + When glTF is not an option, popular formats such as FBX, OBJ, or COLLADA + are also available and regularly maintained. +

        + +

        Loading

        + +

        + Only a few loaders (e.g. `ObjectLoader`) are included by default with + three.js — others should be added to your app individually. +

        + +
        +import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
        +
        + +

        + Once you've imported a loader, you're ready to add a model to your scene. Syntax varies among + different loaders — when using another format, check the examples and documentation for that + loader. For glTF, usage with global scripts would be: +

        + +
        +const loader = new GLTFLoader();
        +
        +loader.load( 'path/to/model.glb', function ( gltf ) {
        +
        +  scene.add( gltf.scene );
        +
        +}, undefined, function ( error ) {
        +
        +  console.error( error );
        +
        +} );
        +
        + +

        Troubleshooting

        + +

        + You've spent hours modeling an artisanal masterpiece, you load it into + the webpage, and — oh no! 😭 It's distorted, miscolored, or missing entirely. + Start with these troubleshooting steps: +

        + +
          +
        1. + Check the JavaScript console for errors, and make sure you've used an + `onError` callback when calling `.load()` to log the result. +
        2. +
        3. + View the model in another application. For glTF, drag-and-drop viewers + are available for + three.js and + babylon.js. If the model + appears correctly in one or more applications, + file a bug against three.js. + If the model cannot be shown in any application, we strongly encourage + filing a bug with the application used to create the model. +
        4. +
        5. + Try scaling the model up or down by a factor of 1000. Many models are + scaled differently, and large models may not appear if the camera is + inside the model. +
        6. +
        7. + Try to add and position a light source. The model may be hidden in the dark. +
        8. +
        9. + Look for failed texture requests in the network tab, like + `"C:\\Path\To\Model\texture.jpg"`. Use paths relative to your + model instead, such as `images/texture.jpg` — this may require + editing the model file in a text editor. +
        10. +
        + +

        Asking for help

        + +

        + If you've gone through the troubleshooting process above and your model + still isn't working, the right approach to asking for help will get you to + a solution faster. Post a question on the + three.js forum and, whenever possible, + include your model (or a simpler model with the same problem) in any formats + you have available. Include enough information for someone else to reproduce + the issue quickly — ideally, a live demo. +

        + +
        +
        +
        + + + + + + + + diff --git a/manual/en/material-table.html b/manual/en/material-table.html index 12e12e25a216d2..628157f1c3f53f 100644 --- a/manual/en/material-table.html +++ b/manual/en/material-table.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/en/materials.html b/manual/en/materials.html index dfa2381af59585..b3e6ace3eae542 100644 --- a/manual/en/materials.html +++ b/manual/en/materials.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/en/matrix-transformations.html b/manual/en/matrix-transformations.html new file mode 100644 index 00000000000000..a65c3e8c28016e --- /dev/null +++ b/manual/en/matrix-transformations.html @@ -0,0 +1,97 @@ + + + Matrix Transformations + + + + + + + + + + + + + +
        +
        +

        Matrix Transformations

        +
        +
        +
        + +

        + Three.js uses `matrices` to encode 3D transformations---translations (position), rotations, and scaling. Every instance of `Object3D` has a `matrix` which stores that object's position, rotation, and scale. This page describes how to update an object's transformation. +

        + +

        Convenience properties and `matrixAutoUpdate`

        + +

        + There are two ways to update an object's transformation: +

        +
          +
        1. + Modify the object's `position`, `quaternion`, and `scale` properties, and let three.js recompute + the object's matrix from these properties: +
          +object.position.copy( start_position );
          +object.quaternion.copy( quaternion );
          +
          + By default, the `matrixAutoUpdate` property is set true, and the matrix will be automatically recalculated. + If the object is static, or you wish to manually control when recalculation occurs, better performance can be obtained by setting the property false: +
          +object.matrixAutoUpdate = false;
          +
          + And after changing any properties, manually update the matrix: +
          +object.updateMatrix();
          +
          +
        2. +
        3. + Modify the object's matrix directly. The `Matrix4` class has various methods for modifying the matrix: +
          +object.matrix.setRotationFromQuaternion( quaternion );
          +object.matrix.setPosition( start_position );
          +object.matrixAutoUpdate = false;
          +
          + Note that `matrixAutoUpdate` must be set to `false` in this case, and you should make sure not to call `updateMatrix`. Calling `updateMatrix` will clobber the manual changes made to the matrix, recalculating the matrix from `position`, `scale`, and so on. +
        4. +
        + +

        Object and world matrices

        +

        + An object's matrix stores the object's transformation relative to the object's parent; to get the object's transformation in world coordinates, you must access the object's world matrix. +

        +

        + When either the parent or the child object's transformation changes, you can request that the child object's world matrix be updated by calling `object.updateMatrixWorld()`. +

        +

        + An object can be transformed via `applyMatrix4()`. Note: Under-the-hood, this method relies on `Matrix4.decompose()`, and not all matrices are decomposable in this way. For example, if an object has a non-uniformly scaled parent, then the object's world matrix may not be decomposable, and this method may not be appropriate. +

        + +

        Rotation and Quaternion

        +

        + Three.js provides two ways of representing 3D rotations: Euler angles and Quaternions, as well as methods for converting between the two. Euler angles are subject to a problem called "gimbal lock," where certain configurations can lose a degree of freedom (preventing the object from being rotated about one axis). For this reason, object rotations are always stored in the object's quaternion. +

        +

        + Previous versions of the library included a `useQuaternion` property which, when set to false, would cause the object's matrix to be calculated from an Euler angle. This practice is deprecated---instead, you should use the `object.setRotationFromEuler()` method, which will update the quaternion. +

        + +
        +
        +
        + + + + + + + + diff --git a/manual/en/multiple-scenes.html b/manual/en/multiple-scenes.html index c25f5870c0a47d..050f4bfac51c9f 100644 --- a/manual/en/multiple-scenes.html +++ b/manual/en/multiple-scenes.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/en/offscreencanvas.html b/manual/en/offscreencanvas.html index 6be18f3b16f5d3..260f82089c6788 100644 --- a/manual/en/offscreencanvas.html +++ b/manual/en/offscreencanvas.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/en/optimize-lots-of-objects-animated.html b/manual/en/optimize-lots-of-objects-animated.html index 682dd4c18a4543..04183bd08cc9e1 100644 --- a/manual/en/optimize-lots-of-objects-animated.html +++ b/manual/en/optimize-lots-of-objects-animated.html @@ -11,10 +11,6 @@ - - - - - \ No newline at end of file + diff --git a/manual/en/optimize-lots-of-objects.html b/manual/en/optimize-lots-of-objects.html index e1e6c56b62eda0..fc5207e946fb5c 100644 --- a/manual/en/optimize-lots-of-objects.html +++ b/manual/en/optimize-lots-of-objects.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/en/picking.html b/manual/en/picking.html index 1514087f0c8ea5..e05b84bfc52a0a 100644 --- a/manual/en/picking.html +++ b/manual/en/picking.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/en/post-processing-3dlut.html b/manual/en/post-processing-3dlut.html deleted file mode 100644 index b9134381044f29..00000000000000 --- a/manual/en/post-processing-3dlut.html +++ /dev/null @@ -1,448 +0,0 @@ - - - Post Processing 3DLUT - - - - - - - - - - - - - - - - - -
        -
        -

        Post Processing 3DLUT

        -
        -
        -
        -

        In the last article we went over post processing. -One of the common ways to post process is called a LUT or 3DLUT. LUT stands for LookUp Table. A 3DLUT is therefore a 3 dimensional look up table.

        -

        How it works is we make a cube of colors. Then we index the cube using the colors of our source image. For each pixel in the original image we look up a position in the cube based on the red, green, and blue colors of the original pixel. The value we pull out of the 3DLUT is the new color.

        -

        In Javascript we might do it like this. Imagine the colors are specified in integers from 0 to 255 and we have a large 3 dimensional array 256x256x256 in size. Then for to translate a color through the look up table

        -
        const newColor = lut[origColor.red][origColor.green][origColor.bue]
        -

        Of course a 256x256x256 array would be rather large but as we pointed out in the article on textures textures are referenced from values of 0.0 to 1.0 regardless of the dimensions of the texture.

        -

        Let's imagine an 8x8x8 cube.

        -
        - -

        First we might fill in the corners with 0,0,0 corner being pure black, the opposite 1,1,1 corner pure white. 1,0,0 being pure red. 0,1,0 being pure green and 0,0,1 being blue.

        -
        - -

        We'd add in the colors down each axis.

        -
        - -

        And the colors on edges that use 2 or more channels.

        -
        - -

        And finally fill in all the colors in between. This is an "identity" 3DLUT. It produces the exact same output as input. If you look up a color you'll get the same color out.

        -
        - -

        If we change the cube to shades of amber though then as we look up colors, we look up the same locations in the 3D lookup table but they produce different output.

        -
        - -

        Using this techinque by supplying a different lookup table we can apply all kinds of effects. Basically any effect that can be computed based only on a single color input. Those effects include adjusting hue, contrast, saturation, color cast, tint, brightness, exposure, levels, curves, posterize, shadows, highlghts, and many others. Even better they can all be combined into a single look up table.

        -

        To use it we need a scene to apply it to. Let's throw together a quick scene. We'll start with a glTF file and display it like we covered in the article on loading a glTF. The model we're loading is this model by The Ice Wolves. It uses no lights so I removed the lights.

        -

        We'll also add a background image like we covered in backgrounds and skyboxs.

        -

        - -

        -

        Now that we have a scene we need a 3DLUT. The simplest 3DLUT is a 2x2x2 identity LUT where identity means nothing happens. It's like multiplying by 1 or doing nothing, even though we're looking up colors in the LUT each color in maps to the same color out.

        -
        - -

        WebGL1 doesn't support 3D textures so we'll use 4x2 2D texture and treat it as a 3D texture inside a custom shader where each slice of the cube is spread out horizontally across the texture.

        -

        Here's the code to make 4x2 2D texture with the colors required for an identity LUT.

        -
        const makeIdentityLutTexture = function() {
        -  const identityLUT = new Uint8Array([
        -      0,   0,   0, 255,  // black
        -    255,   0,   0, 255,  // red
        -      0,   0, 255, 255,  // blue
        -    255,   0, 255, 255,  // magenta
        -      0, 255,   0, 255,  // green
        -    255, 255,   0, 255,  // yellow
        -      0, 255, 255, 255,  // cyan
        -    255, 255, 255, 255,  // white
        -  ]);
        -
        -  return function(filter) {
        -    const texture = new THREE.DataTexture(identityLUT, 4, 2, THREE.RGBAFormat);
        -    texture.minFilter = filter;
        -    texture.magFilter = filter;
        -    texture.needsUpdate = true;
        -    texture.flipY = false;
        -    return texture;
        -  };
        -}();
        -
        -

        Let's make 2 of them, one filtered and one not

        -
        const lutTextures = [
        -  { name: 'identity', size: 2, texture: makeIdentityLutTexture(THREE.LinearFilter) },
        -  { name: 'identity not filtered', size: 2, texture: makeIdentityLutTexture(THREE.NearestFilter) },
        -];
        -
        -

        Taking the example using a custom shader from the article on post processing lets use these 2 custom shaders instead.

        -
        const lutShader = {
        -  uniforms: {
        -    tDiffuse: { value: null },
        -    lutMap:  { value: null },
        -    lutMapSize: { value: 1, },
        -  },
        -  vertexShader: `
        -    varying vec2 vUv;
        -    void main() {
        -      vUv = uv;
        -      gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
        -    }
        -  `,
        -  fragmentShader: `
        -    #include <common>
        -
        -    #define FILTER_LUT true
        -
        -    uniform sampler2D tDiffuse;
        -    uniform sampler2D lutMap;
        -    uniform float lutMapSize;
        -
        -    varying vec2 vUv;
        -
        -    vec4 sampleAs3DTexture(sampler2D tex, vec3 texCoord, float size) {
        -      float sliceSize = 1.0 / size;                  // space of 1 slice
        -      float slicePixelSize = sliceSize / size;       // space of 1 pixel
        -      float width = size - 1.0;
        -      float sliceInnerSize = slicePixelSize * width; // space of size pixels
        -      float zSlice0 = floor( texCoord.z * width);
        -      float zSlice1 = min( zSlice0 + 1.0, width);
        -      float xOffset = slicePixelSize * 0.5 + texCoord.x * sliceInnerSize;
        -      float yRange = (texCoord.y * width + 0.5) / size;
        -      float s0 = xOffset + (zSlice0 * sliceSize);
        -
        -      #ifdef FILTER_LUT
        -
        -        float s1 = xOffset + (zSlice1 * sliceSize);
        -        vec4 slice0Color = texture2D(tex, vec2(s0, yRange));
        -        vec4 slice1Color = texture2D(tex, vec2(s1, yRange));
        -        float zOffset = mod(texCoord.z * width, 1.0);
        -        return mix(slice0Color, slice1Color, zOffset);
        -
        -      #else
        -
        -        return texture2D(tex, vec2( s0, yRange));
        -
        -      #endif
        -    }
        -
        -    void main() {
        -      vec4 originalColor = texture2D(tDiffuse, vUv);
        -      gl_FragColor = sampleAs3DTexture(lutMap, originalColor.xyz, lutMapSize);
        -    }
        -  `,
        -};
        -
        -const lutNearestShader = {
        -  uniforms: {...lutShader.uniforms},
        -  vertexShader: lutShader.vertexShader,
        -  fragmentShader: lutShader.fragmentShader.replace('#define FILTER_LUT', '//'),
        -};
        -
        -

        You can see in the fragment shader there is this line

        -
        #define FILTER_LUT true
        -
        -

        To generate the second shader we comment out that line.

        -

        Then we use them to make 2 custom effects

        -
        const effectLUT = new THREE.ShaderPass(lutShader);
        -const effectLUTNearest = new THREE.ShaderPass(lutNearestShader);
        -
        -

        Translating our existing code that draws the background as a separate scene we a RenderPass for both the scene drawing the glTF and the scene drawing the background.

        -
        const renderModel = new THREE.RenderPass(scene, camera);
        -renderModel.clear = false;  // so we don't clear out the background
        -const renderBG = new THREE.RenderPass(sceneBG, cameraBG);
        -
        -

        and we can setup our EffectComposer to use all the passes

        -
        const composer = new THREE.EffectComposer(renderer);
        -
        -composer.addPass(renderBG);
        -composer.addPass(renderModel);
        -composer.addPass(effectLUT);
        -composer.addPass(effectLUTNearest);
        -composer.addPass(gammaPass);
        -
        -

        Let's make some GUI code to select one lut or the other

        -
        const lutNameIndexMap = {};
        -lutTextures.forEach((info, ndx) => {
        -  lutNameIndexMap[info.name] = ndx;
        -});
        -
        -const lutSettings = {
        -  lut: lutNameIndexMap.identity,
        -};
        -const gui = new GUI({ width: 300 });
        -gui.add(lutSettings, 'lut', lutNameIndexMap);
        -
        -

        The last thing to do is turn on one effect or the other, depending on whether or not we want filtering, set the effect to use the selected texture, and render via the EffectComposer

        -
        const lutInfo = lutTextures[lutSettings.lut];
        -
        -const effect = lutInfo.filter ? effectLUT : effectLUTNearest;
        -effectLUT.enabled = lutInfo.filter;
        -effectLUTNearest.enabled = !lutInfo.filter;
        -
        -const lutTexture = lutInfo.texture;
        -effect.uniforms.lutMap.value = lutTexture;
        -effect.uniforms.lutMapSize.value = lutInfo.size;
        -
        -composer.render(delta);
        -
        -

        Given it's the identity 3DLUT nothing changes

        -

        - -

        -

        but we select the unfiltered LUT we get something much more interesting

        -
        - -

        Why does this happen? Because with filtering on, the GPU linearly interpolates between the colors. With filtering off it does no interpolation so looking up colors in the 3DLUT only gives one of the exact colors in the 3DLUT.

        -

        So how do we go about making more interesting 3DLUTs?

        -

        First decide on the resolution of the table you want and generate the slices of the lookup cube using a simple script.

        -
        const ctx = document.querySelector('canvas').getContext('2d');
        -
        -function drawColorCubeImage(ctx, size) {
        -  const canvas = ctx.canvas;
        -  canvas.width = size * size;
        -  canvas.height = size;
        -
        -  for (let zz = 0; zz < size; ++zz) {
        -    for (let yy = 0; yy < size; ++yy) {
        -      for (let xx = 0; xx < size; ++xx) {
        -        const r = Math.floor(xx / (size - 1) * 255);
        -        const g = Math.floor(yy / (size - 1) * 255);
        -        const b = Math.floor(zz / (size - 1) * 255);
        -        ctx.fillStyle = `rgb(${r},${g},${b})`;
        -        ctx.fillRect(zz * size + xx, yy, 1, 1);
        -      }
        -    }
        -  }
        -  document.querySelector('#width').textContent = canvas.width;
        -  document.querySelector('#height').textContent = canvas.height;
        -}
        -
        -drawColorCubeImage(ctx, 8);
        -
        -

        and we need a canvas

        -
        <canvas></canvas>
        -
        -

        then we can generate a identity 3d lookup table for any size.

        -

        - -

        -

        The larger the resolution the more fine adjustments we can make but being a cube of data the size required grows quickly. A size 8 cube only requires 2k but a size 64 cube requires 1meg. So use the smallest that reproduces the effect you want.

        -

        Let's set the size to 16 and then click save the file which gives us this file.

        -
        - -

        We also need to capture an image of the thing we want to apply the LUT to, in this case the scene we created above before applying any effects. Note that normally we could right click on the scene above and pick "Save As..." but the OrbitControls might be preventing right clicking depending on your OS. In my case I used my OSes screen capture feature to get a screenshot.

        -
        - -

        We then go it into an image editor, in my case Photoshop, load up the sample image, and paste the 3DLUT in the top left corner

        -
        -

        note: I first tried dragging and dropping the lut file on top of the image -in Photoshop but that didn't work. Photoshop made the image twice as large. -I'm guessing it was trying to match DPI or something. Loading the lut file -separately and then copying and pasting it into the screen capture worked.

        -
        -
        - -

        We then use any of the color based full image adjustments to adjust the image. For Photoshop most of the adjustments we can use are available under the Image->Adjustments menu.

        -
        - -

        After we've adjusted the image to our liking you can see the 3DLUT slices we placed in the top left corner have the same adjustments applied.

        -

        Okay but how do we use it?

        -

        First I saved it as a png 3dlut-red-only-s16.png. To save memory we could have cropped it to just the 16x256 top left corner of the LUT table but just for fun we'll crop it after loading. The good thing about using this method is we can get some idea of the effective of the LUT just by looking at the .png file. The bad thing is of course wasted bandwidth.

        -

        Here's some code to load it. The code starts with an identity lut so the texture is usable immediately. It then loads the image, copies out only the 3DLUT part into a canvas, gets the data from the canvas, set it on the texture and sets needsUpdate to true to tell THREE.js to get the new data.

        -
        const makeLUTTexture = function() {
        -  const imgLoader = new THREE.ImageLoader();
        -  const ctx = document.createElement('canvas').getContext('2d');
        -
        -  return function(info) {
        -    const lutSize = info.size;
        -    const width = lutSize * lutSize;
        -    const height = lutSize;
        -    const texture = new THREE.DataTexture(new Uint8Array(width * height), width, height);
        -    texture.minFilter = texture.magFilter = info.filter ? THREE.LinearFilter : THREE.NearestFilter;
        -    texture.flipY = false;
        -
        -    if (info.url) {
        -
        -      imgLoader.load(info.url, function(image) {
        -        ctx.canvas.width = width;
        -        ctx.canvas.height = height;
        -        ctx.drawImage(image, 0, 0);
        -        const imageData = ctx.getImageData(0, 0, width, height);
        -
        -        texture.image.data = new Uint8Array(imageData.data.buffer);
        -        texture.image.width = width;
        -        texture.image.height = height;
        -        texture.needsUpdate = true;
        -      });
        -    }
        -
        -    return texture;
        -  };
        -}();
        -
        -

        Let's use it to load the lut png we just created.

        -
        const lutTextures = [
        -  { name: 'identity',           size: 2, filter: true , },
        -  { name: 'identity no filter', size: 2, filter: false, },
        -+  { name: 'custom',          url: 'resources/images/lut/3dlut-red-only-s16.png' },
        -];
        -
        -+lutTextures.forEach((info) => {
        -+  // if not size set get it from the filename
        -+  if (!info.size) {
        -+    // assumes filename ends in '-s<num>[n]'
        -+    // where <num> is the size of the 3DLUT cube
        -+    // and [n] means 'no filtering' or 'nearest'
        -+    //
        -+    // examples:
        -+    //    'foo-s16.png' = size:16, filter: true
        -+    //    'bar-s8n.png' = size:8, filter: false
        -+    const m = /-s(\d+)(n*)\.[^.]+$/.exec(info.url);
        -+    if (m) {
        -+      info.size = parseInt(m[1]);
        -+      info.filter = info.filter === undefined ? m[2] !== 'n' : info.filter;
        -+    }
        -+  }
        -+
        -+  info.texture = makeLUTTexture(info);
        -+});
        -
        -

        Above you can see we encoded the size of the LUT into the end of the filename. This makes it easier to pass around LUTs as pngs.

        -

        While we're at it lets add a bunch more existing lut png files.

        -
        const lutTextures = [
        -  { name: 'identity',           size: 2, filter: true , },
        -  { name: 'identity no filter', size: 2, filter: false, },
        -  { name: 'custom',          url: 'resources/images/lut/3dlut-red-only-s16.png' },
        -+  { name: 'monochrome',      url: 'resources/images/lut/monochrome-s8.png' },
        -+  { name: 'sepia',           url: 'resources/images/lut/sepia-s8.png' },
        -+  { name: 'saturated',       url: 'resources/images/lut/saturated-s8.png', },
        -+  { name: 'posterize',       url: 'resources/images/lut/posterize-s8n.png', },
        -+  { name: 'posterize-3-rgb', url: 'resources/images/lut/posterize-3-rgb-s8n.png', },
        -+  { name: 'posterize-3-lab', url: 'resources/images/lut/posterize-3-lab-s8n.png', },
        -+  { name: 'posterize-4-lab', url: 'resources/images/lut/posterize-4-lab-s8n.png', },
        -+  { name: 'posterize-more',  url: 'resources/images/lut/posterize-more-s8n.png', },
        -+  { name: 'inverse',         url: 'resources/images/lut/inverse-s8.png', },
        -+  { name: 'color negative',  url: 'resources/images/lut/color-negative-s8.png', },
        -+  { name: 'high contrast',   url: 'resources/images/lut/high-contrast-bw-s8.png', },
        -+  { name: 'funky contrast',  url: 'resources/images/lut/funky-contrast-s8.png', },
        -+  { name: 'nightvision',     url: 'resources/images/lut/nightvision-s8.png', },
        -+  { name: 'thermal',         url: 'resources/images/lut/thermal-s8.png', },
        -+  { name: 'b/w',             url: 'resources/images/lut/black-white-s8n.png', },
        -+  { name: 'hue +60',         url: 'resources/images/lut/hue-plus-60-s8.png', },
        -+  { name: 'hue +180',        url: 'resources/images/lut/hue-plus-180-s8.png', },
        -+  { name: 'hue -60',         url: 'resources/images/lut/hue-minus-60-s8.png', },
        -+  { name: 'red to cyan',     url: 'resources/images/lut/red-to-cyan-s8.png' },
        -+  { name: 'blues',           url: 'resources/images/lut/blues-s8.png' },
        -+  { name: 'infrared',        url: 'resources/images/lut/infrared-s8.png' },
        -+  { name: 'radioactive',     url: 'resources/images/lut/radioactive-s8.png' },
        -+  { name: 'goolgey',         url: 'resources/images/lut/googley-s8.png' },
        -+  { name: 'bgy',             url: 'resources/images/lut/bgy-s8.png' },
        -];
        -
        -

        And here's a bunch of luts to choose from.

        -

        - -

        -

        One last thing, just for fun, it turns out there's a standard LUT format defined by Adobe. If you search on the net you can find lots of these LUT files.

        -

        I wrote a quick loader. Unfortunately there's 4 variations of the format but I could only find examples of 1 variation so I couldn't easily test that all variations work.

        -

        I also wrote a quick drag and drop library. Let's use both to make it so you can drag and drop an Adobe LUT file to see it take affect.

        -

        First we need the 2 libraries

        -
        import * as lutParser from './resources/lut-reader.js';
        -import * as dragAndDrop from './resources/drag-and-drop.js';
        -
        -

        Then we can use them like this

        -
        dragAndDrop.setup({msg: 'Drop LUT File here'});
        -dragAndDrop.onDropFile(readLUTFile);
        -
        -function ext(s) {
        -  const period = s.lastIndexOf('.');
        -  return s.slice(period + 1);
        -}
        -
        -function readLUTFile(file) {
        -  const reader = new FileReader();
        -  reader.onload = (e) => {
        -    const type = ext(file.name);
        -    const lut = lutParser.lutTo2D3Drgba8(lutParser.parse(e.target.result, type));
        -    const {size, data, name} = lut;
        -    const texture = new THREE.DataTexture(data, size * size, size);
        -    texture.minFilter = THREE.LinearFilter;
        -    texture.needsUpdate = true;
        -    texture.flipY = false;
        -    const lutTexture = {
        -      name: (name && name.toLowerCase().trim() !== 'untitled')
        -          ? name
        -          : file.name,
        -      size: size,
        -      filter: true,
        -      texture,
        -    };
        -    lutTextures.push(lutTexture);
        -    lutSettings.lut = lutTextures.length - 1;
        -    updateGUI();
        -  };
        -
        -  reader.readAsText(file);
        -}
        -
        -

        so you should be able to download an Adobe LUT and then drag and drop it on the example below.

        -

        - -

        -

        Note that Adobe LUTs are not designed for online usage. They are large files. You can convert them to smaller files and save as our PNG format by dragging and dropping on the sample below, choosing a size and clicking "Save...".

        -

        The sample below is just a modification of the code above. We only draw the background picture, no glTF file. That picture is an identity lut image created from the script above. We then use the effect to apply whatever LUT file is loaded so the result is the image we'd need to reproduce the LUT file as a PNG.

        -

        - -

        -

        One thing completely skipped is how the shader itself works. Hopefully we can cover a little more GLSL in the future. For now, if you're curious, you can follow the links in the post processing article as well as maybe take a look at this video.

        - - - -
        -
        -
        - - - - - - - - diff --git a/manual/en/post-processing.html b/manual/en/post-processing.html index 03bfa8e24eab6b..11009d14d3f0e0 100644 --- a/manual/en/post-processing.html +++ b/manual/en/post-processing.html @@ -11,10 +11,6 @@ - - - - - \ No newline at end of file + diff --git a/manual/en/prerequisites.html b/manual/en/prerequisites.html index defc42b9323d8f..4e1354f44e572f 100644 --- a/manual/en/prerequisites.html +++ b/manual/en/prerequisites.html @@ -11,10 +11,6 @@ - - - - - \ No newline at end of file + diff --git a/manual/en/primitives.html b/manual/en/primitives.html index 5dd64759aa4f0d..7f887aa90e0474 100644 --- a/manual/en/primitives.html +++ b/manual/en/primitives.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/en/rendering-on-demand.html b/manual/en/rendering-on-demand.html index 9dce3061cb9a8d..62249b5576e0a1 100644 --- a/manual/en/rendering-on-demand.html +++ b/manual/en/rendering-on-demand.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/en/rendertargets.html b/manual/en/rendertargets.html index 6c686b4183b40c..43191adc170123 100644 --- a/manual/en/rendertargets.html +++ b/manual/en/rendertargets.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/en/responsive.html b/manual/en/responsive.html index 4995f7e7a66585..6dc443a763ece7 100644 --- a/manual/en/responsive.html +++ b/manual/en/responsive.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/en/scenegraph.html b/manual/en/scenegraph.html index 5b5b50b619e4d7..72ab47a76a2d85 100644 --- a/manual/en/scenegraph.html +++ b/manual/en/scenegraph.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/en/setup.html b/manual/en/setup.html index 84adf6dc1e9a38..56509a403663ba 100644 --- a/manual/en/setup.html +++ b/manual/en/setup.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/en/shadertoy.html b/manual/en/shadertoy.html index af33edf7d8acfc..41fb27c8a6a8e8 100644 --- a/manual/en/shadertoy.html +++ b/manual/en/shadertoy.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/en/shadows.html b/manual/en/shadows.html index 166dfbdce5afba..f23f5cddf93592 100644 --- a/manual/en/shadows.html +++ b/manual/en/shadows.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/en/textures.html b/manual/en/textures.html index d5e9febd6cb712..d7d6e09d3c3637 100644 --- a/manual/en/textures.html +++ b/manual/en/textures.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/en/tips.html b/manual/en/tips.html index 265187639aea63..3c7151c3c6859d 100644 --- a/manual/en/tips.html +++ b/manual/en/tips.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/en/transparency.html b/manual/en/transparency.html index 4013dd0f98977e..b3a4378e24012e 100644 --- a/manual/en/transparency.html +++ b/manual/en/transparency.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/en/uniform-types.html b/manual/en/uniform-types.html new file mode 100644 index 00000000000000..658d36f0e1e4fb --- /dev/null +++ b/manual/en/uniform-types.html @@ -0,0 +1,254 @@ + + + Uniform Types + + + + + + + + + + + + + +
        +
        +

        Uniform Types

        +
        +
        +
        + +

        + Each uniform must have a `value` property. The type of the value must + correspond to the type of the uniform variable in the GLSL code as + specified for the primitive GLSL types in the table below. Uniform + structures and arrays are also supported. GLSL arrays of primitive type + must either be specified as an array of the corresponding THREE objects or + as a flat array containing the data of all the objects. In other words; + GLSL primitives in arrays must not be represented by arrays. This rule + does not apply transitively. An array of `vec2` arrays, each with a length + of five vectors, must be an array of arrays, of either five `Vector2` + objects or ten `number`s. +

        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        GLSL typeJavaScript type
        intNumber
        uintNumber
        floatNumber
        boolBoolean
        boolNumber
        vec2Vector2
        vec2Float32Array (*)
        vec2Array (*)
        vec3Vector3
        vec3Color
        vec3Float32Array (*)
        vec3Array (*)
        vec4Vector4
        vec4Quaternion
        vec4Float32Array (*)
        vec4Array (*)
        mat2Float32Array (*)
        mat2Array (*)
        mat3Matrix3
        mat3Float32Array (*)
        mat3Array (*)
        mat4Matrix4
        mat4Float32Array (*)
        mat4Array (*)
        ivec2, bvec2Float32Array (*)
        ivec2, bvec2Array (*)
        ivec3, bvec3Int32Array (*)
        ivec3, bvec3Array (*)
        ivec4, bvec4Int32Array (*)
        ivec4, bvec4Array (*)
        sampler2DTexture
        samplerCubeCubeTexture
        + +

        + (*) Same for an (innermost) array (dimension) of the same GLSL type, + containing the components of all vectors or matrices in the array. +

        + +

        Structured Uniforms

        + +

        + Sometimes you want to organize uniforms as `structs` in your shader code. + The following style must be used so `three.js` is able to process + structured uniform data. +

        +
        +uniforms = {
        +  data: { 
        +    value: {
        +      position: new Vector3(), 
        +      direction: new Vector3( 0, 0, 1 ) 
        +    } 
        +  } 
        +};
        +
        + This definition can be mapped on the following GLSL code: +
        +struct Data { 
        +  vec3 position;
        +  vec3 direction;
        +};
        +uniform Data data;
        +
        + +

        Structured Uniforms with Arrays

        + +

        + It's also possible to manage `structs` in arrays. The syntax for this use + case looks like so: +

        +
        +const entry1 = {
        +  position: new Vector3(),
        +  direction: new Vector3( 0, 0, 1 )
        +};
        +const entry2 = {
        +  position: new Vector3( 1, 1, 1 ),
        +  direction: new Vector3( 0, 1, 0 )
        +};
        +
        +uniforms = {
        +  data: {
        +    value: [ entry1, entry2 ]
        +  }
        +};
        +
        + This definition can be mapped on the following GLSL code: +
        +struct Data { 
        +  vec3 position; 
        +  vec3 direction; 
        +};
        +uniform Data data[ 2 ];
        +
        + +
        +
        +
        + + + + + + + + diff --git a/manual/en/useful-links.html b/manual/en/useful-links.html new file mode 100644 index 00000000000000..81c2ac42df31fa --- /dev/null +++ b/manual/en/useful-links.html @@ -0,0 +1,204 @@ + + + Useful Links + + + + + + + + + + + + + +
        +
        +

        Useful Links

        +
        +
        +
        + +

        + The following is a collection of links that you might find useful when learning three.js.
        + If you find something that you'd like to add here, or think that one of the links below is no longer + relevant or working, feel free to click the 'edit' button in the bottom right and make some changes!

        + + Note also that as three.js is under rapid development, a lot of these links will contain information that is + out of date - if something isn't working as you'd expect or as one of these links says it should, + check the browser console for warnings or errors. Also check the relevant docs pages. +

        + +

        Help forums

        +

        + Three.js officially uses the [link:https://discourse.threejs.org/ forum] and [link:http://stackoverflow.com/tags/three.js/info Stack Overflow] for help requests. + If you need assistance with something, that's the place to go. Do NOT open an issue on Github for help requests. +

        + +

        Tutorials and courses

        + +

        Getting started with three.js

        +
          +
        • + [link:https://threejs.org/manual/#en/fundamentals Three.js Fundamentals starting lesson] +
        • +
        • + [link:https://codepen.io/rachsmith/post/beginning-with-3d-webgl-pt-1-the-scene Beginning with 3D WebGL] by [link:https://codepen.io/rachsmith/ Rachel Smith]. +
        • +
        • + [link:https://www.august.com.au/blog/animating-scenes-with-webgl-three-js/ Animating scenes with WebGL and three.js] +
        • +
        + +

        More extensive / advanced articles and courses

        +
          +
        • + [link:https://threejs-journey.com/ Three Journey] Course by [link:https://bruno-simon.com/ Bruno Simon] - Teaches beginners how to use Three.js step by step +
        • +
        • + [link:https://discoverthreejs.com/ Discover three.js] +
        • +
        • + [link:http://blog.cjgammon.com/ Collection of tutorials] by [link:http://www.cjgammon.com/ CJ Gammon]. +
        • +
        • + [link:https://medium.com/soffritti.pierfrancesco/glossy-spheres-in-three-js-bfd2785d4857 Glossy spheres in three.js]. +
        • +
        • + [link:https://www.udacity.com/course/interactive-3d-graphics--cs291 Interactive 3D Graphics] - a free course on Udacity that teaches the fundamentals of 3D Graphics, + and uses three.js as its coding tool. +
        • +
        • + [Link:https://aerotwist.com/tutorials/ Aerotwist] tutorials by [link:https://github.com/paullewis/ Paul Lewis]. +
        • +
        • + [link:https://discourse.threejs.org/t/three-js-bookshelf/2468 Three.js Bookshelf] - Looking for more resources about three.js or computer graphics in general? + Check out the selection of literature recommended by the community. +
        • +
        + +

        News and Updates

        +
          +
        • + [link:https://twitter.com/hashtag/threejs Three.js on Twitter] +
        • +
        • + [link:http://www.reddit.com/r/threejs/ Three.js on reddit] +
        • +
        • + [link:http://www.reddit.com/r/webgl/ WebGL on reddit] +
        • +
        + +

        Examples

        +
          +
        • + [link:https://github.com/edwinwebb/three-seed/ three-seed] - three.js starter project with ES6 and Webpack +
        • +
        • + [link:http://stemkoski.github.io/Three.js/index.html Professor Stemkoskis Examples] - a collection of beginner friendly + examples built using three.js r60. +
        • +
        • + [link:https://threejs.org/examples/ Official three.js examples] - these examples are + maintained as part of the three.js repository, and always use the latest version of three.js. +
        • +
        • + [link:https://raw.githack.com/mrdoob/three.js/dev/examples/ Official three.js dev branch examples] - + Same as the above, except these use the dev branch of three.js, and are used to check that + everything is working as three.js being is developed. +
        • +
        + +

        Tools

        +
          +
        • + [link:https://github.com/tbensky/physgl physgl.org] - JavaScript front-end with wrappers to three.js, to bring WebGL + graphics to students learning physics and math. +
        • +
        • + [link:https://whsjs.readme.io/ Whitestorm.js] – Modular three.js framework with AmmoNext physics plugin. +
        • +
        • + [link:http://zz85.github.io/zz85-bookmarklets/threelabs.html Three.js Inspector] +
        • +
        • + [link:http://idflood.github.io/ThreeNodes.js/ ThreeNodes.js]. +
        • +
        • + [link:https://marketplace.visualstudio.com/items?itemName=slevesque.shader vscode shader] - Syntax highlighter for shader language. +
          + [link:https://marketplace.visualstudio.com/items?itemName=bierner.comment-tagged-templates vscode comment-tagged-templates] - Syntax highlighting for tagged template strings using comments to shader language, like: glsl.js. +
        • +
        • + [link:https://github.com/MozillaReality/WebXR-emulator-extension WebXR-emulator-extension] +
        • +
        + +

        WebGL References

        +
          +
        • + [link:https://www.khronos.org/files/webgl/webgl-reference-card-1_0.pdf webgl-reference-card.pdf] - Reference of all WebGL and GLSL keywords, terminology, syntax and definitions. +
        • +
        + +

        Old Links

        +

        + These links are kept for historical purposes - you may still find them useful, but be warned that + they may have information relating to very old versions of three.js. +

        + +
          +
        • + [link:https://www.youtube.com/watch?v=Dir4KO9RdhM AlterQualia at WebGL Camp 3] +
        • +
        • + [link:http://yomotsu.github.io/threejs-examples/ Yomotsus Examples] - a collection of examples using three.js r45. +
        • +
        • + [link:http://fhtr.org/BasicsOfThreeJS/#1 Introduction to Three.js] by [link:http://github.com/kig/ Ilmari Heikkinen] (slideshow). +
        • +
        • + [link:http://www.slideshare.net/yomotsu/webgl-and-threejs WebGL and Three.js] by [link:http://github.com/yomotsu Akihiro Oyamada] (slideshow). +
        • +
        • + [link:https://www.youtube.com/watch?v=VdQnOaolrPA Trigger Rally] by [link:https://github.com/jareiko jareiko] (video). +
        • +
        • + [link:http://blackjk3.github.io/threefab/ ThreeFab] - scene editor, maintained up until around three.js r50. +
        • +
        • + [link:http://bkcore.com/blog/3d/webgl-three-js-workflow-tips.html Max to Three.js workflow tips and tricks] by [link:https://github.com/BKcore BKcore] +
        • +
        • + [link:http://12devsofxmas.co.uk/2012/01/webgl-and-three-js/ A whirlwind look at Three.js] + by [link:http://github.com/nrocy Paul King] +
        • +
        • + [link:http://bkcore.com/blog/3d/webgl-three-js-animated-selective-glow.html Animated selective glow in Three.js] + by [link:https://github.com/BKcore BKcore] +
        • +
        • + [link:http://www.natural-science.or.jp/article/20120220155529.php Building A Physics Simulation Environment] - three.js tutorial in Japanese +
        • +
        + +
        +
        +
        + + + + + + + + diff --git a/manual/en/voxel-geometry.html b/manual/en/voxel-geometry.html index e69ac281c43b90..898e5edfcba203 100644 --- a/manual/en/voxel-geometry.html +++ b/manual/en/voxel-geometry.html @@ -11,10 +11,6 @@ - - - - + + +
        +
        +

        WebGL Compatibility Check

        +
        +
        +
        + +

        + Even though this is becoming less and less of a problem, some devices or browsers may still not support WebGL 2. + The following method allows you to check if it is supported and display a message to the user if it is not. + Import the WebGL support detection module, and run the following before attempting to render anything. +

        + +
        +import WebGL from 'three/addons/capabilities/WebGL.js';
        +
        +if ( WebGL.isWebGL2Available() ) {
        +
        +  // Initiate function or other initializations here
        +  animate();
        +
        +} else {
        +
        +  const warning = WebGL.getWebGL2ErrorMessage();
        +  document.getElementById( 'container' ).appendChild( warning );
        +
        +}
        +
        + +
        +
        +
        + + + + + + + + diff --git a/manual/en/webxr-basics.html b/manual/en/webxr-basics.html index 5c488f03bd969b..53c6f44a51ad8e 100644 --- a/manual/en/webxr-basics.html +++ b/manual/en/webxr-basics.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/en/webxr-look-to-select.html b/manual/en/webxr-look-to-select.html index b9b24fc6307ee5..94cc3cfb3766ed 100644 --- a/manual/en/webxr-look-to-select.html +++ b/manual/en/webxr-look-to-select.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/en/webxr-point-to-select.html b/manual/en/webxr-point-to-select.html index 7a11c927015bc3..cb949075818670 100644 --- a/manual/en/webxr-point-to-select.html +++ b/manual/en/webxr-point-to-select.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/examples/3dlut-base-cube-maker.html b/manual/examples/3dlut-base-cube-maker.html index f3aecd6438c771..5137a7b8c88948 100644 --- a/manual/examples/3dlut-base-cube-maker.html +++ b/manual/examples/3dlut-base-cube-maker.html @@ -26,63 +26,88 @@

        Color Cube Image Maker

        diff --git a/manual/examples/align-html-elements-to-3d-globe-too-many-labels.html b/manual/examples/align-html-elements-to-3d-globe-too-many-labels.html index 3851074e776da6..78fdd8770cb65d 100644 --- a/manual/examples/align-html-elements-to-3d-globe-too-many-labels.html +++ b/manual/examples/align-html-elements-to-3d-globe-too-many-labels.html @@ -58,10 +58,6 @@
        - - - - - diff --git a/manual/examples/align-html-elements-to-3d-globe.html b/manual/examples/align-html-elements-to-3d-globe.html index 172f3767288fcc..5f12753ad958e0 100644 --- a/manual/examples/align-html-elements-to-3d-globe.html +++ b/manual/examples/align-html-elements-to-3d-globe.html @@ -58,10 +58,6 @@
        - - - - - diff --git a/manual/examples/align-html-to-3d-w-hiding.html b/manual/examples/align-html-to-3d-w-hiding.html index 3e9c60227a4dd0..043a61d7ca060e 100644 --- a/manual/examples/align-html-to-3d-w-hiding.html +++ b/manual/examples/align-html-to-3d-w-hiding.html @@ -55,10 +55,6 @@
        - - - - - diff --git a/manual/examples/align-html-to-3d-w-sorting.html b/manual/examples/align-html-to-3d-w-sorting.html index 03fe202a03c2d1..447d809b00b7ab 100644 --- a/manual/examples/align-html-to-3d-w-sorting.html +++ b/manual/examples/align-html-to-3d-w-sorting.html @@ -57,10 +57,6 @@
        - - - - - diff --git a/manual/examples/align-html-to-3d.html b/manual/examples/align-html-to-3d.html index 0d395745c2b4e7..d685e9269e7bb6 100644 --- a/manual/examples/align-html-to-3d.html +++ b/manual/examples/align-html-to-3d.html @@ -55,10 +55,6 @@
        - - - - - diff --git a/manual/examples/background-css.html b/manual/examples/background-css.html index fa9f7e465d3b2d..828a26850cfe3c 100644 --- a/manual/examples/background-css.html +++ b/manual/examples/background-css.html @@ -22,10 +22,6 @@ - - - - - diff --git a/manual/examples/background-cubemap.html b/manual/examples/background-cubemap.html index 71096cc2d532d9..e56cec0a95f2ae 100644 --- a/manual/examples/background-cubemap.html +++ b/manual/examples/background-cubemap.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/background-equirectangularmap.html b/manual/examples/background-equirectangularmap.html index 28c9bd14d696ea..152d719317041a 100644 --- a/manual/examples/background-equirectangularmap.html +++ b/manual/examples/background-equirectangularmap.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/background-scene-background-fixed-aspect.html b/manual/examples/background-scene-background-fixed-aspect.html index f1e2509975ede0..a4f5d77dde1a43 100644 --- a/manual/examples/background-scene-background-fixed-aspect.html +++ b/manual/examples/background-scene-background-fixed-aspect.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/background-scene-background.html b/manual/examples/background-scene-background.html index 430d89f9b0102d..49718d7f228f73 100644 --- a/manual/examples/background-scene-background.html +++ b/manual/examples/background-scene-background.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/background-separate-scene-bad-aspect.html b/manual/examples/background-separate-scene-bad-aspect.html index 01f897547191a9..ae3874d5933da6 100644 --- a/manual/examples/background-separate-scene-bad-aspect.html +++ b/manual/examples/background-separate-scene-bad-aspect.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/background-separate-scene.html b/manual/examples/background-separate-scene.html index d4b7cec4808f10..eee65088eca0cd 100644 --- a/manual/examples/background-separate-scene.html +++ b/manual/examples/background-separate-scene.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/background-v01.html b/manual/examples/background-v01.html index 22b7ff7f5dfbad..2dfea3b969554f 100644 --- a/manual/examples/background-v01.html +++ b/manual/examples/background-v01.html @@ -21,10 +21,6 @@ - - - - - diff --git a/manual/examples/background.html b/manual/examples/background.html index b80b39dd005f3b..093f141ba91140 100644 --- a/manual/examples/background.html +++ b/manual/examples/background.html @@ -21,10 +21,6 @@ - - - - - diff --git a/manual/examples/billboard-labels-w-sprites-adjust-height.html b/manual/examples/billboard-labels-w-sprites-adjust-height.html index 2cd2614eca1160..4f9678051e87a3 100644 --- a/manual/examples/billboard-labels-w-sprites-adjust-height.html +++ b/manual/examples/billboard-labels-w-sprites-adjust-height.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/billboard-labels-w-sprites.html b/manual/examples/billboard-labels-w-sprites.html index 416cc11543be2d..bb9cb2be746e76 100644 --- a/manual/examples/billboard-labels-w-sprites.html +++ b/manual/examples/billboard-labels-w-sprites.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/billboard-trees-no-billboards.html b/manual/examples/billboard-trees-no-billboards.html index 084bbeed2db7c1..187a706ff19b86 100644 --- a/manual/examples/billboard-trees-no-billboards.html +++ b/manual/examples/billboard-trees-no-billboards.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/billboard-trees-static-billboards.html b/manual/examples/billboard-trees-static-billboards.html index 2c7f9739d9aacc..f772202eb0af04 100644 --- a/manual/examples/billboard-trees-static-billboards.html +++ b/manual/examples/billboard-trees-static-billboards.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/cameras-logarithmic-depth-buffer.html b/manual/examples/cameras-logarithmic-depth-buffer.html index 7825c52b65e177..2cc45966e9060c 100644 --- a/manual/examples/cameras-logarithmic-depth-buffer.html +++ b/manual/examples/cameras-logarithmic-depth-buffer.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/cameras-orthographic-2-scenes.html b/manual/examples/cameras-orthographic-2-scenes.html index c354b273edf1fd..4500ac855724a0 100644 --- a/manual/examples/cameras-orthographic-2-scenes.html +++ b/manual/examples/cameras-orthographic-2-scenes.html @@ -36,10 +36,6 @@
        - - - - - diff --git a/manual/examples/cameras-orthographic-canvas-top-left-origin.html b/manual/examples/cameras-orthographic-canvas-top-left-origin.html index a6f1829062e8af..f612e6c57361a1 100644 --- a/manual/examples/cameras-orthographic-canvas-top-left-origin.html +++ b/manual/examples/cameras-orthographic-canvas-top-left-origin.html @@ -32,10 +32,6 @@ - - - - - diff --git a/manual/examples/cameras-perspective-2-scenes.html b/manual/examples/cameras-perspective-2-scenes.html index a7936afdae9052..66520f0f289499 100644 --- a/manual/examples/cameras-perspective-2-scenes.html +++ b/manual/examples/cameras-perspective-2-scenes.html @@ -36,10 +36,6 @@
        - - - - - diff --git a/manual/examples/cameras-perspective.html b/manual/examples/cameras-perspective.html index 60550b36eb035e..6bb99e2b940c70 100644 --- a/manual/examples/cameras-perspective.html +++ b/manual/examples/cameras-perspective.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/cameras-z-fighting.html b/manual/examples/cameras-z-fighting.html index 863e0fa4713583..b7d19cf19e1294 100644 --- a/manual/examples/cameras-z-fighting.html +++ b/manual/examples/cameras-z-fighting.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/canvas-random-dots.html b/manual/examples/canvas-random-dots.html index 8215dacd8ab2c4..910134cf09fa39 100644 --- a/manual/examples/canvas-random-dots.html +++ b/manual/examples/canvas-random-dots.html @@ -11,37 +11,49 @@ - - diff --git a/manual/examples/canvas-textured-cube.html b/manual/examples/canvas-textured-cube.html index 635fc9c249aa7e..371323fdd4259f 100644 --- a/manual/examples/canvas-textured-cube.html +++ b/manual/examples/canvas-textured-cube.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/canvas-textured-labels-one-canvas.html b/manual/examples/canvas-textured-labels-one-canvas.html index 6f5e417b23ec95..ecd4575b8e2e7d 100644 --- a/manual/examples/canvas-textured-labels-one-canvas.html +++ b/manual/examples/canvas-textured-labels-one-canvas.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/canvas-textured-labels-scale-to-fit.html b/manual/examples/canvas-textured-labels-scale-to-fit.html index d0e78ac16d18af..8f432e3abcd0ed 100644 --- a/manual/examples/canvas-textured-labels-scale-to-fit.html +++ b/manual/examples/canvas-textured-labels-scale-to-fit.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/canvas-textured-labels.html b/manual/examples/canvas-textured-labels.html index 2588719b75ceb4..329e9de07b85fd 100644 --- a/manual/examples/canvas-textured-labels.html +++ b/manual/examples/canvas-textured-labels.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/cleanup-loaded-files.html b/manual/examples/cleanup-loaded-files.html index 9444a078af7594..8c38a6f8b4da8e 100644 --- a/manual/examples/cleanup-loaded-files.html +++ b/manual/examples/cleanup-loaded-files.html @@ -25,10 +25,6 @@ - - - - - - - diff --git a/manual/examples/custom-buffergeometry-cube-typedarrays.html b/manual/examples/custom-buffergeometry-cube-typedarrays.html index 7c13854b79aa38..a52b7b3d42754e 100644 --- a/manual/examples/custom-buffergeometry-cube-typedarrays.html +++ b/manual/examples/custom-buffergeometry-cube-typedarrays.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/custom-buffergeometry-cube.html b/manual/examples/custom-buffergeometry-cube.html index 7c28deac39d93f..76b56cc822734e 100644 --- a/manual/examples/custom-buffergeometry-cube.html +++ b/manual/examples/custom-buffergeometry-cube.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/custom-buffergeometry-dynamic.html b/manual/examples/custom-buffergeometry-dynamic.html index 7c8e161de60937..b5f5c651c76c8c 100644 --- a/manual/examples/custom-buffergeometry-dynamic.html +++ b/manual/examples/custom-buffergeometry-dynamic.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/custom-geometry-cube-face-colors.html b/manual/examples/custom-geometry-cube-face-colors.html deleted file mode 100644 index 2ffc7a97dcbb55..00000000000000 --- a/manual/examples/custom-geometry-cube-face-colors.html +++ /dev/null @@ -1,163 +0,0 @@ - - - - - - - Three.js - Custom Geometry - Cube - Face Colors - - - - - - - - - - - - - - diff --git a/manual/examples/custom-geometry-cube-face-normals.html b/manual/examples/custom-geometry-cube-face-normals.html deleted file mode 100644 index d56d6b82ba5f4d..00000000000000 --- a/manual/examples/custom-geometry-cube-face-normals.html +++ /dev/null @@ -1,158 +0,0 @@ - - - - - - - Three.js - Custom Geometry - Cube - face normals - - - - - - - - - - - - - - diff --git a/manual/examples/custom-geometry-cube-texcoords.html b/manual/examples/custom-geometry-cube-texcoords.html deleted file mode 100644 index 7340127487c95d..00000000000000 --- a/manual/examples/custom-geometry-cube-texcoords.html +++ /dev/null @@ -1,182 +0,0 @@ - - - - - - - Three.js - Custom Geometry - Cube - Texture Coordinates - - - - - - - - - - - - - - diff --git a/manual/examples/custom-geometry-cube-vertex-colors.html b/manual/examples/custom-geometry-cube-vertex-colors.html deleted file mode 100644 index 447d6123bfdc10..00000000000000 --- a/manual/examples/custom-geometry-cube-vertex-colors.html +++ /dev/null @@ -1,166 +0,0 @@ - - - - - - - Three.js - Custom Geometry - Cube - Vertex Colors - - - - - - - - - - - - - - diff --git a/manual/examples/custom-geometry-cube-vertex-normals.html b/manual/examples/custom-geometry-cube-vertex-normals.html deleted file mode 100644 index 422fe5a9233bc5..00000000000000 --- a/manual/examples/custom-geometry-cube-vertex-normals.html +++ /dev/null @@ -1,158 +0,0 @@ - - - - - - - Three.js - Custom Geometry - Cube - Vertex Normals - - - - - - - - - - - - - - diff --git a/manual/examples/custom-geometry-cube.html b/manual/examples/custom-geometry-cube.html deleted file mode 100644 index 7ea446946a2861..00000000000000 --- a/manual/examples/custom-geometry-cube.html +++ /dev/null @@ -1,156 +0,0 @@ - - - - - - - Three.js - Custom Geometry - Cube - - - - - - - - - - - - - - diff --git a/manual/examples/custom-geometry-heightmap.html b/manual/examples/custom-geometry-heightmap.html deleted file mode 100644 index 69a80d4fb3cdf6..00000000000000 --- a/manual/examples/custom-geometry-heightmap.html +++ /dev/null @@ -1,195 +0,0 @@ - - - - - - - Three.js - Custom Geometry - Heightmap - - - - - - - - - - - - - - diff --git a/manual/examples/debug-js-clearing-logger.html b/manual/examples/debug-js-clearing-logger.html index ba5affee7765d4..c801ef150f7753 100644 --- a/manual/examples/debug-js-clearing-logger.html +++ b/manual/examples/debug-js-clearing-logger.html @@ -33,10 +33,6 @@
        
             
           
        -
        -
        -
        -
         
         
        -
        diff --git a/manual/examples/debug-js-html-elements.html b/manual/examples/debug-js-html-elements.html
        index 57189edec93968..28a1a9e325cc14 100644
        --- a/manual/examples/debug-js-html-elements.html
        +++ b/manual/examples/debug-js-html-elements.html
        @@ -34,10 +34,6 @@
               
        z:
        - - - - - diff --git a/manual/examples/debug-js-params.html b/manual/examples/debug-js-params.html index 3ea80b6b8969b6..0e3e5deb189beb 100644 --- a/manual/examples/debug-js-params.html +++ b/manual/examples/debug-js-params.html @@ -44,10 +44,6 @@
        click to launch
        - - - - - diff --git a/manual/examples/debugging-mcve.html b/manual/examples/debugging-mcve.html index 3830be11e50bcb..0dc624e2ecf2a8 100644 --- a/manual/examples/debugging-mcve.html +++ b/manual/examples/debugging-mcve.html @@ -1,10 +1,6 @@ - - - - - - diff --git a/manual/examples/fog.html b/manual/examples/fog.html index f5f42c603b0743..19094ae59754af 100644 --- a/manual/examples/fog.html +++ b/manual/examples/fog.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/fundamentals-3-cubes.html b/manual/examples/fundamentals-3-cubes.html index 72a4206462828e..52b59c3aff8f3f 100644 --- a/manual/examples/fundamentals-3-cubes.html +++ b/manual/examples/fundamentals-3-cubes.html @@ -9,10 +9,6 @@ - - - - - diff --git a/manual/examples/fundamentals-with-animation.html b/manual/examples/fundamentals-with-animation.html index 34d0257363c691..d60fe9965bae6d 100644 --- a/manual/examples/fundamentals-with-animation.html +++ b/manual/examples/fundamentals-with-animation.html @@ -9,10 +9,6 @@ - - - - - diff --git a/manual/examples/fundamentals-with-light.html b/manual/examples/fundamentals-with-light.html index 1e2cc11f04d842..9400c592dbb3dd 100644 --- a/manual/examples/fundamentals-with-light.html +++ b/manual/examples/fundamentals-with-light.html @@ -9,10 +9,6 @@ - - - - - diff --git a/manual/examples/fundamentals.html b/manual/examples/fundamentals.html index 97824659ba3f2b..cff423e79bb009 100644 --- a/manual/examples/fundamentals.html +++ b/manual/examples/fundamentals.html @@ -9,10 +9,6 @@ - - - - - diff --git a/manual/examples/game-check-animations.html b/manual/examples/game-check-animations.html index 66b1ac8df1e869..9754fc746b4119 100644 --- a/manual/examples/game-check-animations.html +++ b/manual/examples/game-check-animations.html @@ -73,10 +73,6 @@ - - - - - - - - - diff --git a/manual/examples/indexed-textures-picking-and-highlighting.html b/manual/examples/indexed-textures-picking-and-highlighting.html index d053205dfc2bc2..3d99f82145d372 100644 --- a/manual/examples/indexed-textures-picking-and-highlighting.html +++ b/manual/examples/indexed-textures-picking-and-highlighting.html @@ -58,10 +58,6 @@
        - - - - - - - diff --git a/manual/examples/indexed-textures-random-colors.html b/manual/examples/indexed-textures-random-colors.html index 9997422701fdb7..2b0dfe55c475d3 100644 --- a/manual/examples/indexed-textures-random-colors.html +++ b/manual/examples/indexed-textures-random-colors.html @@ -58,10 +58,6 @@
        - - - - - - diff --git a/manual/examples/lights-directional-w-helper.html b/manual/examples/lights-directional-w-helper.html index 158ca7b2901654..d5d3a73a942b75 100644 --- a/manual/examples/lights-directional-w-helper.html +++ b/manual/examples/lights-directional-w-helper.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/lights-directional.html b/manual/examples/lights-directional.html index b71f04c1a3be80..1e1cea64ccd0c3 100644 --- a/manual/examples/lights-directional.html +++ b/manual/examples/lights-directional.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/lights-hemisphere.html b/manual/examples/lights-hemisphere.html index 299087b27d0689..3edaf7a1db29dc 100644 --- a/manual/examples/lights-hemisphere.html +++ b/manual/examples/lights-hemisphere.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/lights-point-physically-correct.html b/manual/examples/lights-point-physically-correct.html deleted file mode 100644 index 305f5eab79c9d5..00000000000000 --- a/manual/examples/lights-point-physically-correct.html +++ /dev/null @@ -1,171 +0,0 @@ - - - - - - - Three.js - Lights - Physically Correct Lights - - - - - - - - - - - - - - diff --git a/manual/examples/lights-point.html b/manual/examples/lights-point.html index 67ea54c28a645c..9bd7f6b5d03eff 100644 --- a/manual/examples/lights-point.html +++ b/manual/examples/lights-point.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/lights-rectarea.html b/manual/examples/lights-rectarea.html index 19d8c74af8e017..a44e2183236680 100644 --- a/manual/examples/lights-rectarea.html +++ b/manual/examples/lights-rectarea.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/lights-spot-w-helper.html b/manual/examples/lights-spot-w-helper.html index 5f02edf422aa26..52a9dda9e9840c 100644 --- a/manual/examples/lights-spot-w-helper.html +++ b/manual/examples/lights-spot-w-helper.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/load-gltf-animated-cars.html b/manual/examples/load-gltf-animated-cars.html index d7b0b12c4d856c..cb7b8f67a6cbde 100644 --- a/manual/examples/load-gltf-animated-cars.html +++ b/manual/examples/load-gltf-animated-cars.html @@ -20,10 +20,6 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/manual/examples/lots-of-objects-merged-vertexcolors.html b/manual/examples/lots-of-objects-merged-vertexcolors.html index cd3614cfd51c9c..ce77c956c93794 100644 --- a/manual/examples/lots-of-objects-merged-vertexcolors.html +++ b/manual/examples/lots-of-objects-merged-vertexcolors.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/lots-of-objects-merged.html b/manual/examples/lots-of-objects-merged.html index 5746130d28585d..28e375ce685c7f 100644 --- a/manual/examples/lots-of-objects-merged.html +++ b/manual/examples/lots-of-objects-merged.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/lots-of-objects-morphtargets.html b/manual/examples/lots-of-objects-morphtargets.html index 0fd552ac8c2f1f..e3f472b32928ad 100644 --- a/manual/examples/lots-of-objects-morphtargets.html +++ b/manual/examples/lots-of-objects-morphtargets.html @@ -41,10 +41,6 @@
        - - - - - diff --git a/manual/examples/lots-of-objects-multiple-data-sets.html b/manual/examples/lots-of-objects-multiple-data-sets.html index a2a07a677f158c..6e4da29d97a862 100644 --- a/manual/examples/lots-of-objects-multiple-data-sets.html +++ b/manual/examples/lots-of-objects-multiple-data-sets.html @@ -41,10 +41,6 @@
        - - - - - diff --git a/manual/examples/lots-of-objects-slow.html b/manual/examples/lots-of-objects-slow.html index ac62b3b2fc2eb6..0340cf1a82fad7 100644 --- a/manual/examples/lots-of-objects-slow.html +++ b/manual/examples/lots-of-objects-slow.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/multiple-scenes-controls.html b/manual/examples/multiple-scenes-controls.html index cc5c4b596f848f..16342061e6bc64 100644 --- a/manual/examples/multiple-scenes-controls.html +++ b/manual/examples/multiple-scenes-controls.html @@ -48,10 +48,6 @@ and finding a undiscovered tomb full of mummies and treasure.

        - - - - - diff --git a/manual/examples/multiple-scenes-copy-canvas.html b/manual/examples/multiple-scenes-copy-canvas.html index f1e872e8e9810c..986b608fd3322a 100644 --- a/manual/examples/multiple-scenes-copy-canvas.html +++ b/manual/examples/multiple-scenes-copy-canvas.html @@ -43,10 +43,6 @@ and finding a undiscovered tomb full of mummies and treasure.

        - - - - - diff --git a/manual/examples/multiple-scenes-generic.html b/manual/examples/multiple-scenes-generic.html index a1c0e6a0187bb0..157905fc400147 100644 --- a/manual/examples/multiple-scenes-generic.html +++ b/manual/examples/multiple-scenes-generic.html @@ -48,10 +48,6 @@ and finding a undiscovered tomb full of mummies and treasure.

        - - - - - diff --git a/manual/examples/multiple-scenes-selector.html b/manual/examples/multiple-scenes-selector.html index 6754e2d65bf9b5..6fd6475eb8afef 100644 --- a/manual/examples/multiple-scenes-selector.html +++ b/manual/examples/multiple-scenes-selector.html @@ -48,10 +48,6 @@ and finding a undiscovered tomb full of mummies and treasure.

        - - - - - diff --git a/manual/examples/multiple-scenes-v1.html b/manual/examples/multiple-scenes-v1.html index 08dacc71900168..71084792e5307d 100644 --- a/manual/examples/multiple-scenes-v1.html +++ b/manual/examples/multiple-scenes-v1.html @@ -48,10 +48,6 @@ and finding a undiscovered tomb full of mummies and treasure.

        - - - - - diff --git a/manual/examples/multiple-scenes-v2.html b/manual/examples/multiple-scenes-v2.html index 79e305d5dd0c1b..54243dabc895ee 100644 --- a/manual/examples/multiple-scenes-v2.html +++ b/manual/examples/multiple-scenes-v2.html @@ -49,10 +49,6 @@ and finding a undiscovered tomb full of mummies and treasure.

        - - - - - diff --git a/manual/examples/multiple-scenes-v3.html b/manual/examples/multiple-scenes-v3.html index 551fdb21a80dd8..43e5124678eef6 100644 --- a/manual/examples/multiple-scenes-v3.html +++ b/manual/examples/multiple-scenes-v3.html @@ -49,10 +49,6 @@ and finding a undiscovered tomb full of mummies and treasure.

        - - - - - diff --git a/manual/examples/offscreencanvas-w-fallback.html b/manual/examples/offscreencanvas-w-fallback.html index 8e0a9f06bda093..27ef17b7b50d6d 100644 --- a/manual/examples/offscreencanvas-w-fallback.html +++ b/manual/examples/offscreencanvas-w-fallback.html @@ -21,47 +21,62 @@ diff --git a/manual/examples/offscreencanvas.html b/manual/examples/offscreencanvas.html index 1eb2c93bfd33ed..b3f474780abc5d 100644 --- a/manual/examples/offscreencanvas.html +++ b/manual/examples/offscreencanvas.html @@ -33,28 +33,36 @@ diff --git a/manual/examples/picking-gpu.html b/manual/examples/picking-gpu.html index ed4b1af2646646..96703e78541019 100644 --- a/manual/examples/picking-gpu.html +++ b/manual/examples/picking-gpu.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/picking-raycaster-complex-geo.html b/manual/examples/picking-raycaster-complex-geo.html index 1f00e6f79f9dd8..341d369869b6e6 100644 --- a/manual/examples/picking-raycaster-complex-geo.html +++ b/manual/examples/picking-raycaster-complex-geo.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/picking-raycaster-transparency.html b/manual/examples/picking-raycaster-transparency.html index 8f7cb5bddb8cf7..011237b856df86 100644 --- a/manual/examples/picking-raycaster-transparency.html +++ b/manual/examples/picking-raycaster-transparency.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/picking-raycaster.html b/manual/examples/picking-raycaster.html index 972fb0039c87d5..03919270474827 100644 --- a/manual/examples/picking-raycaster.html +++ b/manual/examples/picking-raycaster.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/postprocessing-3dlut-identity.html b/manual/examples/postprocessing-3dlut-identity.html deleted file mode 100644 index 0291067ee6f350..00000000000000 --- a/manual/examples/postprocessing-3dlut-identity.html +++ /dev/null @@ -1,316 +0,0 @@ - - - - Three.js - postprocessing - 3DLUT - - - - - - - - - - - - - - - diff --git a/manual/examples/postprocessing-3dlut-prep.html b/manual/examples/postprocessing-3dlut-prep.html deleted file mode 100644 index 2d39b0ddd64202..00000000000000 --- a/manual/examples/postprocessing-3dlut-prep.html +++ /dev/null @@ -1,172 +0,0 @@ - - - - - - - Three.js - Post Processing 3DLUT (not) - prep - - - - - - - - - - - - - diff --git a/manual/examples/postprocessing-3dlut-w-loader.html b/manual/examples/postprocessing-3dlut-w-loader.html deleted file mode 100644 index d01c589a90bf6b..00000000000000 --- a/manual/examples/postprocessing-3dlut-w-loader.html +++ /dev/null @@ -1,415 +0,0 @@ - - - - Three.js - postprocessing - 3DLUT w/loader - - - - - - - - - - - diff --git a/manual/examples/postprocessing-3dlut.html b/manual/examples/postprocessing-3dlut.html deleted file mode 100644 index e5da9ebba4f0e3..00000000000000 --- a/manual/examples/postprocessing-3dlut.html +++ /dev/null @@ -1,382 +0,0 @@ - - - - Three.js - postprocessing - 3DLUT - - - - - - - - - - - - - - - diff --git a/manual/examples/postprocessing-adobe-lut-to-png-converter.html b/manual/examples/postprocessing-adobe-lut-to-png-converter.html deleted file mode 100644 index c0f5705423a7b6..00000000000000 --- a/manual/examples/postprocessing-adobe-lut-to-png-converter.html +++ /dev/null @@ -1,253 +0,0 @@ - - - - Three.js - postprocessing - 3DLUT w/loader - - - - - -

        Adobe LUT to PNG converter

        -

        Drag and drop a LUT file here

        -
        size:
        -

        -

        -
        - - - - - diff --git a/manual/examples/postprocessing-custom.html b/manual/examples/postprocessing-custom.html index f1653c1e8873e4..9126e6f681a865 100644 --- a/manual/examples/postprocessing-custom.html +++ b/manual/examples/postprocessing-custom.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/postprocessing-gui.html b/manual/examples/postprocessing-gui.html index 33ab24185c85f8..6845b5ff987d29 100644 --- a/manual/examples/postprocessing-gui.html +++ b/manual/examples/postprocessing-gui.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/postprocessing.html b/manual/examples/postprocessing.html index b72713bd550eee..62f38e40fa4c79 100644 --- a/manual/examples/postprocessing.html +++ b/manual/examples/postprocessing.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/primitives-text.html b/manual/examples/primitives-text.html index 64172a010034db..107943505b66c8 100644 --- a/manual/examples/primitives-text.html +++ b/manual/examples/primitives-text.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/primitives.html b/manual/examples/primitives.html index e33ea17698c25c..b83e9e1c8193cd 100644 --- a/manual/examples/primitives.html +++ b/manual/examples/primitives.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/render-on-demand-w-damping.html b/manual/examples/render-on-demand-w-damping.html index 61e910c70adfc1..5e9f2751105726 100644 --- a/manual/examples/render-on-demand-w-damping.html +++ b/manual/examples/render-on-demand-w-damping.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/render-on-demand-w-gui.html b/manual/examples/render-on-demand-w-gui.html index 36cf634d86c6e8..9a24e6f74e35b3 100644 --- a/manual/examples/render-on-demand-w-gui.html +++ b/manual/examples/render-on-demand-w-gui.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/render-on-demand.html b/manual/examples/render-on-demand.html index 63a95b8162ba6f..0b843ae08fad6b 100644 --- a/manual/examples/render-on-demand.html +++ b/manual/examples/render-on-demand.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/render-target.html b/manual/examples/render-target.html index 03cfda0279134b..ed2f33ae58d62f 100644 --- a/manual/examples/render-target.html +++ b/manual/examples/render-target.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/resources/editor-settings.js b/manual/examples/resources/editor-settings.js index ea7c6d003f96e4..d3e1ce5ec1df3f 100644 --- a/manual/examples/resources/editor-settings.js +++ b/manual/examples/resources/editor-settings.js @@ -70,7 +70,7 @@ const linkRE = /(href=)(")(.*?)(")()/g; const imageSrcRE = /((?:image|img)\.src = )(")(.*?)(")()/g; const loaderLoadRE = /(loader\.load[a-z]*\s*\(\s*)('|")(.*?)('|")/ig; - const loaderArrayLoadRE = /(loader\.load[a-z]*\(\[)([\s\S]*?)(\])/ig; + const loaderArrayLoadRE = /(loader\.load[a-z]*\(\s*\[)([\s\S]*?)(\])/ig; const loadFileRE = /(loadFile\s*\(\s*)('|")(.*?)('|")/ig; const threejsUrlRE = /(.*?)('|")([^"']*?)('|")([^'"]*?)(\/\*\s+threejs.org:\s+url\s+\*\/)/ig; const arrayLineRE = /^(\s*["|'])([\s\S]*?)(["|']*$)/; @@ -169,8 +169,8 @@ const moduleRE = /(import.*?)('|")(.*?)('|")/g; - // convert https://threejs.org/build/three.module.js -> https://unpkg.com/three@ - // convert https://threejs.org/examples/jsm/.?? -> https://unpkg.com/three@/examples/jsm/.?? + // convert https://threejs.org/build/three.module.js -> https://cdn.jsdelivr.net/npm/three@ + // convert https://threejs.org/examples/jsm/.?? -> https://cdn.jsdelivr.net/npm/three@/examples/jsm/.?? if ( ! version ) { @@ -194,12 +194,12 @@ if ( href.includes( '/build/three.module.js' ) ) { - return `https://unpkg.com/three@${version}`; + return `https://cdn.jsdelivr.net/npm/three@${version}`; } else if ( href.includes( '/examples/jsm/' ) ) { const url = new URL( href ); - return `https://unpkg.com/three@${version}${url.pathname}${url.search}${url.hash}`; + return `https://cdn.jsdelivr.net/npm/three@${version}${url.pathname}${url.search}${url.hash}`; } diff --git a/manual/examples/resources/editor.html b/manual/examples/resources/editor.html index c4e5b6e53896d0..cfda6d29b7bf75 100644 --- a/manual/examples/resources/editor.html +++ b/manual/examples/resources/editor.html @@ -242,7 +242,7 @@
        @@ -277,7 +277,7 @@
        - + diff --git a/manual/examples/resources/editor.js b/manual/examples/resources/editor.js index 23fc40181e104c..2603a90adef5f0 100644 --- a/manual/examples/resources/editor.js +++ b/manual/examples/resources/editor.js @@ -1713,7 +1713,7 @@ async function openInStackBlitz() { const url = getSourceBlobFromEditor(); // g.iframe.src = url; // work around firefox bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1828286 - g.iframe.contentWindow.location.replace(url); + g.iframe.contentWindow.location.replace( url ); } @@ -1961,7 +1961,7 @@ async function openInStackBlitz() { } else { applySubstitutions(); - require.config( { paths: { 'vs': 'https://unpkg.com/monaco-editor@0.34.1/min/vs' } } ); + require.config( { paths: { 'vs': 'https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs' } } ); require( [ 'vs/editor/editor.main' ], main ); } diff --git a/manual/examples/resources/webgl-debug-helper.js b/manual/examples/resources/webgl-debug-helper.js index 9aa0d7764096bd..eabcad4c3b5e7d 100644 --- a/manual/examples/resources/webgl-debug-helper.js +++ b/manual/examples/resources/webgl-debug-helper.js @@ -625,7 +625,7 @@ } - // Make a an object that has a copy of every property of the WebGL context + // Make an object that has a copy of every property of the WebGL context // but wraps all functions. for ( const propertyName in ctx ) { diff --git a/manual/examples/responsive-editor.html b/manual/examples/responsive-editor.html index 7285afdd002b43..f59dc1b3e55754 100644 --- a/manual/examples/responsive-editor.html +++ b/manual/examples/responsive-editor.html @@ -63,19 +63,23 @@ // This code is only related to handling the split. // Our three.js code has not changed -Split(['#view', '#controls'], { // eslint-disable-line new-cap - sizes: [75, 25], - minSize: 100, - elementStyle: (dimension, size, gutterSize) => { - return { - 'flex-basis': `calc(${size}% - ${gutterSize}px)`, - }; - }, - gutterStyle: (dimension, gutterSize) => { - return { - 'flex-basis': `${gutterSize}px`, - }; - }, -}); +Split( [ '#view', '#controls' ], { // eslint-disable-line new-cap + sizes: [ 75, 25 ], + minSize: 100, + elementStyle: ( dimension, size, gutterSize ) => { + + return { + 'flex-basis': `calc(${size}% - ${gutterSize}px)`, + }; + + }, + gutterStyle: ( dimension, gutterSize ) => { + + return { + 'flex-basis': `${gutterSize}px`, + }; + + }, +} ); diff --git a/manual/examples/responsive-hd-dpi.html b/manual/examples/responsive-hd-dpi.html index 998c004b97b1aa..5b3b27d26e282a 100644 --- a/manual/examples/responsive-hd-dpi.html +++ b/manual/examples/responsive-hd-dpi.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/responsive-no-resize.html b/manual/examples/responsive-no-resize.html index 9a53477da516e4..218cde48892daf 100644 --- a/manual/examples/responsive-no-resize.html +++ b/manual/examples/responsive-no-resize.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/responsive-update-camera.html b/manual/examples/responsive-update-camera.html index bd8c25711e1214..fd419a7bdaa127 100644 --- a/manual/examples/responsive-update-camera.html +++ b/manual/examples/responsive-update-camera.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/responsive.html b/manual/examples/responsive.html index b111d216eb8de8..6d713b649515aa 100644 --- a/manual/examples/responsive.html +++ b/manual/examples/responsive.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/scenegraph-car.html b/manual/examples/scenegraph-car.html index fdf741f861b448..ccc7cf461ec6f9 100644 --- a/manual/examples/scenegraph-car.html +++ b/manual/examples/scenegraph-car.html @@ -21,10 +21,6 @@ - - - - - - diff --git a/manual/examples/scenegraph-sun-earth-moon-axes-grids.html b/manual/examples/scenegraph-sun-earth-moon-axes-grids.html index 11faf65670dead..bdcfb5efe630f4 100644 --- a/manual/examples/scenegraph-sun-earth-moon-axes-grids.html +++ b/manual/examples/scenegraph-sun-earth-moon-axes-grids.html @@ -46,10 +46,6 @@
        - - - - - - diff --git a/manual/examples/scenegraph-sun-earth-moon-axes.html b/manual/examples/scenegraph-sun-earth-moon-axes.html index 7a080aac9615b2..128110c2883cec 100644 --- a/manual/examples/scenegraph-sun-earth-moon-axes.html +++ b/manual/examples/scenegraph-sun-earth-moon-axes.html @@ -20,10 +20,6 @@ - - - - - - diff --git a/manual/examples/scenegraph-sun-earth-moon.html b/manual/examples/scenegraph-sun-earth-moon.html index 8dd4fb37d90372..af22c3d9a83dc7 100644 --- a/manual/examples/scenegraph-sun-earth-moon.html +++ b/manual/examples/scenegraph-sun-earth-moon.html @@ -20,10 +20,6 @@ - - - - - - diff --git a/manual/examples/scenegraph-sun-earth-orbit-fixed.html b/manual/examples/scenegraph-sun-earth-orbit-fixed.html index 6f921556b8b6df..3c2652aee444c2 100644 --- a/manual/examples/scenegraph-sun-earth-orbit-fixed.html +++ b/manual/examples/scenegraph-sun-earth-orbit-fixed.html @@ -20,10 +20,6 @@ - - - - - - diff --git a/manual/examples/scenegraph-sun-earth-orbit.html b/manual/examples/scenegraph-sun-earth-orbit.html index 6ae12b621dc6cb..49329f860910b5 100644 --- a/manual/examples/scenegraph-sun-earth-orbit.html +++ b/manual/examples/scenegraph-sun-earth-orbit.html @@ -20,10 +20,6 @@ - - - - - - diff --git a/manual/examples/scenegraph-sun-earth.html b/manual/examples/scenegraph-sun-earth.html index 61dc47fa2159b8..cfed2ab24caa9c 100644 --- a/manual/examples/scenegraph-sun-earth.html +++ b/manual/examples/scenegraph-sun-earth.html @@ -20,10 +20,6 @@ - - - - - - diff --git a/manual/examples/scenegraph-sun.html b/manual/examples/scenegraph-sun.html index 2499ae3a3fade3..5ded2e2c7a74d3 100644 --- a/manual/examples/scenegraph-sun.html +++ b/manual/examples/scenegraph-sun.html @@ -20,10 +20,6 @@ - - - - - - diff --git a/manual/examples/scenegraph-tank.html b/manual/examples/scenegraph-tank.html index 63ba1670d4ee74..fff2cfe110f588 100644 --- a/manual/examples/scenegraph-tank.html +++ b/manual/examples/scenegraph-tank.html @@ -15,11 +15,11 @@ height: 100%; display: block; } - #info { - position: absolute; - left: 1em; - top: 1em; - background: rgba(0,0,0,.8); + #info { + position: absolute; + left: 1em; + top: 1em; + background: rgba(0,0,0,.8); padding: .5em; color: white; font-family: monospace; @@ -30,10 +30,6 @@
        - - - - - - diff --git a/manual/examples/shadertoy-as-texture.html b/manual/examples/shadertoy-as-texture.html index 8fcf3c92d1687f..4c1d8cc4839679 100644 --- a/manual/examples/shadertoy-as-texture.html +++ b/manual/examples/shadertoy-as-texture.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/shadertoy-basic-x40.html b/manual/examples/shadertoy-basic-x40.html index be091f0b1961a6..7170149ba627ca 100644 --- a/manual/examples/shadertoy-basic-x40.html +++ b/manual/examples/shadertoy-basic-x40.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/shadertoy-basic.html b/manual/examples/shadertoy-basic.html index 4b2058fec145d8..27972345fdca5c 100644 --- a/manual/examples/shadertoy-basic.html +++ b/manual/examples/shadertoy-basic.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/shadertoy-bleepy-blocks.html b/manual/examples/shadertoy-bleepy-blocks.html index c9ce93f7ae0056..0f8d31621e709e 100644 --- a/manual/examples/shadertoy-bleepy-blocks.html +++ b/manual/examples/shadertoy-bleepy-blocks.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/shadertoy-prep.html b/manual/examples/shadertoy-prep.html index b11bb47e2fd281..357a533e8d80d3 100644 --- a/manual/examples/shadertoy-prep.html +++ b/manual/examples/shadertoy-prep.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/shadows-directional-light-shadow-acne.html b/manual/examples/shadows-directional-light-shadow-acne.html index 25758ac7304eb1..ae0b2240ddbd8a 100644 --- a/manual/examples/shadows-directional-light-shadow-acne.html +++ b/manual/examples/shadows-directional-light-shadow-acne.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/shadows-directional-light-with-camera-gui.html b/manual/examples/shadows-directional-light-with-camera-gui.html index cdccc505aa1776..0ff237497d6692 100644 --- a/manual/examples/shadows-directional-light-with-camera-gui.html +++ b/manual/examples/shadows-directional-light-with-camera-gui.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/shadows-directional-light-with-camera-helper.html b/manual/examples/shadows-directional-light-with-camera-helper.html index 224c35f5d640ea..42fd78e70f6ace 100644 --- a/manual/examples/shadows-directional-light-with-camera-helper.html +++ b/manual/examples/shadows-directional-light-with-camera-helper.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/shadows-directional-light.html b/manual/examples/shadows-directional-light.html index b0ba2d5689e7ff..255d92d386cbc0 100644 --- a/manual/examples/shadows-directional-light.html +++ b/manual/examples/shadows-directional-light.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/shadows-fake.html b/manual/examples/shadows-fake.html index e65aaf3fea7d0d..a7eabfdf30a4e0 100644 --- a/manual/examples/shadows-fake.html +++ b/manual/examples/shadows-fake.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/shadows-point-light.html b/manual/examples/shadows-point-light.html index 1ca74407d4d776..6db7c4b5aa8fad 100644 --- a/manual/examples/shadows-point-light.html +++ b/manual/examples/shadows-point-light.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/shadows-spot-light-with-camera-gui.html b/manual/examples/shadows-spot-light-with-camera-gui.html index 16a9cdacec6e44..f1b1646052d62c 100644 --- a/manual/examples/shadows-spot-light-with-camera-gui.html +++ b/manual/examples/shadows-spot-light-with-camera-gui.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/shadows-spot-light-with-shadow-radius.html b/manual/examples/shadows-spot-light-with-shadow-radius.html index 53fdff03f7d79a..ea71f79a7a1cb2 100644 --- a/manual/examples/shadows-spot-light-with-shadow-radius.html +++ b/manual/examples/shadows-spot-light-with-shadow-radius.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/textured-cube-6-textures.html b/manual/examples/textured-cube-6-textures.html index bd4c1c829eb480..399f928e84851d 100644 --- a/manual/examples/textured-cube-6-textures.html +++ b/manual/examples/textured-cube-6-textures.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/textured-cube-adjust.html b/manual/examples/textured-cube-adjust.html index eef8081e5ef8c0..a0e6a034bedab4 100644 --- a/manual/examples/textured-cube-adjust.html +++ b/manual/examples/textured-cube-adjust.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/textured-cube-wait-for-all-textures.html b/manual/examples/textured-cube-wait-for-all-textures.html index 17ec2984a13f4b..2dcc7d8500f89e 100644 --- a/manual/examples/textured-cube-wait-for-all-textures.html +++ b/manual/examples/textured-cube-wait-for-all-textures.html @@ -45,10 +45,6 @@
        - - - - - diff --git a/manual/examples/textured-cube-wait-for-texture.html b/manual/examples/textured-cube-wait-for-texture.html index a32aa80b84e1f1..d1a45f2f5abc68 100644 --- a/manual/examples/textured-cube-wait-for-texture.html +++ b/manual/examples/textured-cube-wait-for-texture.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/textured-cube.html b/manual/examples/textured-cube.html index 5f526735119142..1f919f2beb1f59 100644 --- a/manual/examples/textured-cube.html +++ b/manual/examples/textured-cube.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/threejs-responsive.js b/manual/examples/threejs-responsive.js index 96f472b968dde1..2cb2a448883bc6 100644 --- a/manual/examples/threejs-responsive.js +++ b/manual/examples/threejs-responsive.js @@ -17,7 +17,7 @@ function main() { { const color = 0xFFFFFF; - const intensity = 1; + const intensity = 3; const light = new THREE.DirectionalLight( color, intensity ); light.position.set( - 1, 2, 4 ); scene.add( light ); diff --git a/manual/examples/tips-preservedrawingbuffer.html b/manual/examples/tips-preservedrawingbuffer.html index 1e5e717bc160d5..062dc50ff4f1b3 100644 --- a/manual/examples/tips-preservedrawingbuffer.html +++ b/manual/examples/tips-preservedrawingbuffer.html @@ -20,10 +20,6 @@ - - - - - - diff --git a/manual/examples/tips-screenshot-good.html b/manual/examples/tips-screenshot-good.html index d41db299ddd6f2..a0ff949eaa1f49 100644 --- a/manual/examples/tips-screenshot-good.html +++ b/manual/examples/tips-screenshot-good.html @@ -37,10 +37,6 @@ - - - - - diff --git a/manual/examples/tips-tabindex.html b/manual/examples/tips-tabindex.html index 306f2b3220d062..71cf6232bc5beb 100644 --- a/manual/examples/tips-tabindex.html +++ b/manual/examples/tips-tabindex.html @@ -38,29 +38,40 @@ diff --git a/manual/examples/tips-transparent-canvas.html b/manual/examples/tips-transparent-canvas.html index 6607b41e76c90f..3ddb8a3bfb3bbc 100644 --- a/manual/examples/tips-transparent-canvas.html +++ b/manual/examples/tips-transparent-canvas.html @@ -47,10 +47,6 @@

        Cubes-R-Us!

        - - - - - diff --git a/manual/examples/transparency-doubleside-hack.html b/manual/examples/transparency-doubleside-hack.html index 636d494f285f6b..ac9af1716751be 100644 --- a/manual/examples/transparency-doubleside-hack.html +++ b/manual/examples/transparency-doubleside-hack.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/transparency-doubleside.html b/manual/examples/transparency-doubleside.html index 55d3a22fa06c47..4b5892453d650b 100644 --- a/manual/examples/transparency-doubleside.html +++ b/manual/examples/transparency-doubleside.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/transparency-intersecting-planes-alphatest.html b/manual/examples/transparency-intersecting-planes-alphatest.html index 987056d05469af..8a6bae2db0adf8 100644 --- a/manual/examples/transparency-intersecting-planes-alphatest.html +++ b/manual/examples/transparency-intersecting-planes-alphatest.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/transparency-intersecting-planes-fixed.html b/manual/examples/transparency-intersecting-planes-fixed.html index fa0dfbdf4be047..13cab68fe62ecf 100644 --- a/manual/examples/transparency-intersecting-planes-fixed.html +++ b/manual/examples/transparency-intersecting-planes-fixed.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/transparency-intersecting-planes.html b/manual/examples/transparency-intersecting-planes.html index 8abf5d22eb922d..03f413a2571ee9 100644 --- a/manual/examples/transparency-intersecting-planes.html +++ b/manual/examples/transparency-intersecting-planes.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/transparency.html b/manual/examples/transparency.html index 581b9c3dc78a44..0184976328d0eb 100644 --- a/manual/examples/transparency.html +++ b/manual/examples/transparency.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/voxel-geometry-culled-faces-ui.html b/manual/examples/voxel-geometry-culled-faces-ui.html index 328efc6abd843f..90fd65cbcce07a 100644 --- a/manual/examples/voxel-geometry-culled-faces-ui.html +++ b/manual/examples/voxel-geometry-culled-faces-ui.html @@ -71,10 +71,6 @@ - - - - - - diff --git a/manual/examples/voxel-geometry-culled-faces.html b/manual/examples/voxel-geometry-culled-faces.html index ccd914a3accd15..439d246f9fe582 100644 --- a/manual/examples/voxel-geometry-culled-faces.html +++ b/manual/examples/voxel-geometry-culled-faces.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/voxel-geometry-merged.html b/manual/examples/voxel-geometry-merged.html index e447e4450ecefd..b54ab67bdbcac3 100644 --- a/manual/examples/voxel-geometry-merged.html +++ b/manual/examples/voxel-geometry-merged.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/voxel-geometry-separate-cubes.html b/manual/examples/voxel-geometry-separate-cubes.html index 8c3a4d31939bea..7f14255d1a079e 100644 --- a/manual/examples/voxel-geometry-separate-cubes.html +++ b/manual/examples/voxel-geometry-separate-cubes.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/webxr-basic-vr-optional.html b/manual/examples/webxr-basic-vr-optional.html index d1d064d455b043..5388e464cf8f59 100644 --- a/manual/examples/webxr-basic-vr-optional.html +++ b/manual/examples/webxr-basic-vr-optional.html @@ -29,10 +29,6 @@ Use Non-VR Mode - - - - - diff --git a/manual/examples/webxr-basic-w-background.html b/manual/examples/webxr-basic-w-background.html index 767b9f47d9a624..33fa152798af9d 100644 --- a/manual/examples/webxr-basic-w-background.html +++ b/manual/examples/webxr-basic-w-background.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/webxr-basic.html b/manual/examples/webxr-basic.html index 87232ef52f3484..e35249270ba001 100644 --- a/manual/examples/webxr-basic.html +++ b/manual/examples/webxr-basic.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/webxr-look-to-select-selector.html b/manual/examples/webxr-look-to-select-selector.html index a0c3f52e08c7b9..5794964207838e 100644 --- a/manual/examples/webxr-look-to-select-selector.html +++ b/manual/examples/webxr-look-to-select-selector.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/webxr-look-to-select-w-cursor.html b/manual/examples/webxr-look-to-select-w-cursor.html index 0441d386b77bc4..18f39f220758b3 100644 --- a/manual/examples/webxr-look-to-select-w-cursor.html +++ b/manual/examples/webxr-look-to-select-w-cursor.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/webxr-look-to-select.html b/manual/examples/webxr-look-to-select.html index b53db99f4d9cc2..f98f0d68066f6c 100644 --- a/manual/examples/webxr-look-to-select.html +++ b/manual/examples/webxr-look-to-select.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/webxr-point-to-select-w-move.html b/manual/examples/webxr-point-to-select-w-move.html index 72eedb9102e7ce..e46f9c0404c211 100644 --- a/manual/examples/webxr-point-to-select-w-move.html +++ b/manual/examples/webxr-point-to-select-w-move.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/examples/webxr-point-to-select.html b/manual/examples/webxr-point-to-select.html index 07471de38e9e45..d8a2170f403d18 100644 --- a/manual/examples/webxr-point-to-select.html +++ b/manual/examples/webxr-point-to-select.html @@ -20,10 +20,6 @@ - - - - - diff --git a/manual/fr/align-html-elements-to-3d.html b/manual/fr/align-html-elements-to-3d.html index aa380e5d911dd9..5a3931dbef36f5 100644 --- a/manual/fr/align-html-elements-to-3d.html +++ b/manual/fr/align-html-elements-to-3d.html @@ -1,20 +1,16 @@ - Aligning HTML Elements to 3D + Aligner les éléments HTML en 3D - + - - - - - + diff --git a/manual/fr/animation-system.html b/manual/fr/animation-system.html new file mode 100644 index 00000000000000..af31ce1b9eaee2 --- /dev/null +++ b/manual/fr/animation-system.html @@ -0,0 +1,173 @@ + + + Système d'Animation + + + + + + + + + + + + + +
        +
        +

        Système d'Animation

        +
        +
        +
        + +

        Vue d'ensemble

        + +

        + Au sein du système d'animation de three.js, vous pouvez animer diverses propriétés de vos modèles : + les os d'un modèle skinné et riggé, les morph targets, différentes propriétés de matériaux + (couleurs, opacité, booléens), la visibilité et les transformations. Les propriétés animées peuvent être introduites en fondu, + dissoutes en fondu, fondues enchaînées et déformées. L'influence (weight) et l'échelle temporelle (time scales) de différentes animations simultanées + sur le même objet ainsi que sur différents objets peuvent être modifiées + indépendamment. Diverses animations sur le même objet et sur différents objets peuvent être + synchronisées.

        + + Pour atteindre tout cela dans un seul système homogène, le système d'animation de three.js + [link:https://github.com/mrdoob/three.js/issues/6881 a complètement changé en 2015] + (méfiez-vous des informations obsolètes !), et il a maintenant une architecture similaire à + Unity/Unreal Engine 4. Cette page donne un bref aperçu des principaux composants du + système et de leur fonctionnement ensemble. + +

        + +

        Clips d'Animation

        + +

        + + Si vous avez importé avec succès un objet 3D animé (peu importe qu'il ait des + os, des morph targets, ou les deux) — par exemple en l'exportant depuis Blender avec l' + [link:https://github.com/KhronosGroup/glTF-Blender-IO exportateur glTF pour Blender] et + en le chargeant dans une scène three.js à l'aide de `GLTFLoader` — l'un des champs de réponse + devrait être un tableau nommé "animations", contenant les clips d'animation + pour ce modèle (voir une liste des chargeurs possibles ci-dessous).

        + + Chaque `AnimationClip` contient généralement les données pour une certaine activité de l'objet. Si le + mesh est un personnage, par exemple, il peut y avoir un AnimationClip pour un cycle de marche, un second + pour un saut, un troisième pour un pas de côté, et ainsi de suite. + +

        + +

        Pistes d'Images Clés

        + +

        + + À l'intérieur d'un tel `AnimationClip`, les données pour chaque propriété animée sont stockées dans une + `KeyframeTrack` séparée. En supposant qu'un objet personnage a un squelette, + une piste d'images clés pourrait stocker les données des changements de position de l'os de l'avant-bras + au fil du temps, une piste différente les données des changements de rotation du même os, une troisième + la position, la rotation ou l'échelle d'un autre os, et ainsi de suite. Il devrait être clair + qu'un AnimationClip peut être composé de nombreuses pistes de ce type.

        + + En supposant que le modèle a des morph targets (par exemple un morph + target montrant un visage amical et un autre montrant un visage en colère), chaque piste contient les + informations sur la manière dont l'influence d'un certain morph target change pendant la performance + du clip. + +

        + +

        Mixeur d'Animation

        + +

        + + Les données stockées ne forment que la base des animations - la lecture réelle est contrôlée par le + `AnimationMixer`. Vous pouvez l'imaginer non seulement comme un lecteur d'animations, mais + comme une simulation d'un matériel comme une véritable console de mixage, qui peut contrôler plusieurs animations + simultanément, en les mélangeant et en les fusionnant. + +

        + +

        Actions d'Animation

        + +

        + + Le `AnimationMixer` lui-même n'a que très peu de propriétés et de méthodes (générales), car il + peut être contrôlé par les actions d'animation. En configurant une + `AnimationAction`, vous pouvez déterminer quand un certain `AnimationClip` doit être lu, mis en pause + ou arrêté sur l'un des mixeurs, si et combien de fois le clip doit être répété, s'il + doit être exécuté avec un fondu ou une échelle temporelle, et quelques éléments supplémentaires, tels que le fondu enchaîné + ou la synchronisation. + +

        + +

        Groupes d'Objets d'Animation

        + +

        + + Si vous souhaitez qu'un groupe d'objets reçoive un état d'animation partagé, vous pouvez utiliser un + `AnimationObjectGroup`. + +

        + +

        Formats et Chargeurs Pris en Charge

        + +

        + Notez que tous les formats de modèle n'incluent pas l'animation (OBJ notamment ne le fait pas), et que seuls certains + chargeurs three.js supportent les séquences `AnimationClip`. Plusieurs qui supportent + ce type d'animation : +

        + +
          +
        • THREE.ObjectLoader
        • +
        • THREE.BVHLoader
        • +
        • THREE.ColladaLoader
        • +
        • THREE.FBXLoader
        • +
        • THREE.GLTFLoader
        • +
        + +

        + Notez que 3ds max et Maya ne peuvent actuellement pas exporter plusieurs animations (c'est-à-dire des animations qui ne sont pas + sur la même ligne de temps) directement dans un seul fichier. +

        + +

        Exemple

        + +
        +let mesh;
        +
        +// Créer un AnimationMixer, et obtenir la liste des instances de AnimationClip
        +const mixer = new THREE.AnimationMixer( mesh );
        +const clips = mesh.animations;
        +
        +// Mettre à jour le mixeur à chaque image
        +function update () {
        +  mixer.update( deltaSeconds );
        +}
        +
        +// Jouer une animation spécifique
        +const clip = THREE.AnimationClip.findByName( clips, 'dance' );
        +const action = mixer.clipAction( clip );
        +action.play();
        +
        +// Jouer toutes les animations
        +clips.forEach( function ( clip ) {
        +  mixer.clipAction( clip ).play();
        +} );
        +
        + +
        +
        +
        + + + + + + + + \ No newline at end of file diff --git a/manual/fr/backgrounds.html b/manual/fr/backgrounds.html index ce3c65be3c858b..e8616c326c9e3d 100644 --- a/manual/fr/backgrounds.html +++ b/manual/fr/backgrounds.html @@ -1,20 +1,16 @@ - Backgrounds and Skyboxes + Arrière-plans et Skyboxes - + - - - - diff --git a/manual/fr/billboards.html b/manual/fr/billboards.html index 93c4538a4d463b..783bcef0b0533b 100644 --- a/manual/fr/billboards.html +++ b/manual/fr/billboards.html @@ -1,20 +1,16 @@ - Billboards + Panneaux - + - - - - diff --git a/manual/fr/cameras.html b/manual/fr/cameras.html index 5ab115205f9c6f..96862a03cf8dc6 100644 --- a/manual/fr/cameras.html +++ b/manual/fr/cameras.html @@ -1,20 +1,16 @@ - Caméras dans + Caméras - + - - - - - + diff --git a/manual/fr/canvas-textures.html b/manual/fr/canvas-textures.html index a4893792b883f1..f39829bcc97b9a 100644 --- a/manual/fr/canvas-textures.html +++ b/manual/fr/canvas-textures.html @@ -1,20 +1,16 @@ - Canvas Textures + Textures sur Canvas - + - - - - diff --git a/manual/fr/cleanup.html b/manual/fr/cleanup.html index 33755a49c2bdfb..fb5f53a021082b 100644 --- a/manual/fr/cleanup.html +++ b/manual/fr/cleanup.html @@ -1,20 +1,16 @@ - Cleanup + Nettoyage - + - - - - diff --git a/manual/fr/color-management.html b/manual/fr/color-management.html new file mode 100644 index 00000000000000..1c45647e936a02 --- /dev/null +++ b/manual/fr/color-management.html @@ -0,0 +1,354 @@ + + + Gestion des couleurs + + + + + + + + + + + + + + +
        +
        +

        Gestion des couleurs

        +
        +
        +
        + +

        Qu'est-ce qu'un espace couleur ?

        + +

        + Chaque espace couleur est une collection de plusieurs décisions de conception, choisies ensemble pour prendre en charge une + vaste gamme de couleurs tout en satisfaisant aux contraintes techniques liées à la précision et aux technologies + d'affichage. Lors de la création d'un actif 3D, ou de l'assemblage d'actifs 3D dans une scène, il est + important de connaître ces propriétés et la façon dont les propriétés d'un espace couleur sont liées + aux autres espaces couleur de la scène. +

        + +
        + +
        + Couleurs sRGB et point blanc (D65) affichés dans le diagramme de chromaticité de référence CIE 1931. + La région colorée représente une projection 2D du gamut sRGB, qui est un volume 3D. + Source : Wikipedia +
        +
        + +
          +
        • + Primaires de couleur : Les couleurs primaires (par exemple rouge, vert, bleu) ne sont pas absolues ; elles sont + sélectionnées à partir du spectre visible en fonction des contraintes de précision limitée et des capacités des + périphériques d'affichage disponibles. Les couleurs sont exprimées comme un ratio des couleurs primaires. +
        • +
        • + Point blanc : La plupart des espaces couleur sont conçus de telle sorte qu'une somme pondérée égale des + primaires R = G = B apparaisse sans couleur, ou "achromatique". L'apparence + des valeurs achromatiques (comme le blanc ou le gris) dépend de la perception humaine, qui à son tour dépend + fortement du contexte de l'observateur. Un espace couleur spécifie son "point blanc" pour équilibrer + ces besoins. Le point blanc défini par l'espace couleur sRGB est + [link:https://en.wikipedia.org/wiki/Illuminant_D65 D65]. +
        • +
        • + Fonctions de transfert : Après avoir choisi le gamut et un modèle de couleur, nous devons encore + définir des correspondances ("fonctions de transfert") des valeurs numériques vers/depuis l'espace couleur. Est-ce que r = 0.5 + représente 50 % moins d'éclairage physique que r = 1.0 ? Ou 50 % moins lumineux, tel que perçu + par un œil humain moyen ? Ce sont des choses différentes, et cette différence peut être représentée par + une fonction mathématique. Les fonctions de transfert peuvent être linéaires ou non linéaires, en fonction + des objectifs de l'espace couleur. sRGB définit des fonctions de transfert non linéaires. Ces + fonctions sont parfois approximées par des fonctions gamma, mais le terme "gamma" est + ambigu et doit être évité dans ce contexte. +
        • +
        + + Ces trois paramètres — primaires de couleur, point blanc et fonctions de transfert — définissent un espace + couleur, chacun choisi pour des objectifs particuliers. Ayant défini les paramètres, quelques termes supplémentaires + sont utiles : + +
          +
        • + Modèle de couleur : Syntaxe pour identifier numériquement les couleurs dans le gamut choisi — + un système de coordonnées pour les couleurs. Dans three.js, nous sommes principalement concernés par le modèle de couleur + RGB, ayant trois coordonnées r, g, b ∈ [0,1] ("domaine fermé") ou + r, g, b ∈ [0,∞] ("domaine ouvert") représentant chacune une fraction d'une couleur + primaire. D'autres modèles de couleur (HSL, Lab, LCH) sont couramment utilisés pour le contrôle artistique. +
        • +
        • + Gamut de couleur : Une fois que les primaires de couleur et un point blanc ont été choisis, ceux-ci représentent + un volume dans le spectre visible (un "gamut"). Les couleurs ne se trouvant pas dans ce volume ("hors gamut") + ne peuvent pas être exprimées par des valeurs RGB [0,1] en domaine fermé. Dans le domaine ouvert [0,∞], le gamut est + techniquement infini. +
        • +
        + +

        + Considérez deux espaces couleur très courants : `SRGBColorSpace` ("sRGB") et + `LinearSRGBColorSpace` ("Linear-sRGB"). Les deux utilisent les mêmes primaires et le même point blanc, + et ont donc le même gamut de couleur. Les deux utilisent le modèle de couleur RGB. Ils ne diffèrent que par + les fonctions de transfert — Linear-sRGB est linéaire par rapport à l'intensité lumineuse physique. + sRGB utilise les fonctions de transfert sRGB non linéaires, et ressemble plus étroitement à la façon dont + l'œil humain perçoit la lumière et à la réactivité des périphériques d'affichage courants. +

        + +

        + Cette différence est importante. Les calculs d'éclairage et autres opérations de rendu doivent + généralement avoir lieu dans un espace couleur linéaire. Cependant, les couleurs linéaires sont moins efficaces pour + être stockées dans une image ou un framebuffer, et ne paraissent pas correctes lorsqu'elles sont visualisées par un observateur humain. + En conséquence, les textures d'entrée et l'image finale rendue utiliseront généralement l'espace couleur sRGB non linéaire. +

        + +
        +

        + ℹ️ AVIS : Bien que certains écrans modernes prennent en charge des gamuts plus larges comme Display-P3, + les API graphiques de la plateforme web reposent largement sur sRGB. Les applications utilisant three.js + aujourd'hui utiliseront généralement uniquement les espaces couleur sRGB et Linear-sRGB. +

        +
        + +

        Rôles des espaces couleur

        + +

        + Les flux de travail linéaires — requis pour les méthodes de rendu modernes — impliquent généralement plus d'un + espace couleur, chacun assigné à un rôle particulier. Les espaces couleur linéaires et non linéaires sont + appropriés pour différents rôles, expliqués ci-dessous. +

        + +

        Espace couleur d'entrée

        + +

        + Les couleurs fournies à three.js — à partir de sélecteurs de couleur, de textures, de modèles 3D et d'autres sources — + ont chacune un espace couleur associé. Celles qui ne sont pas déjà dans l'espace couleur de travail Linear-sRGB + doivent être converties, et les textures doivent recevoir l'affectation correcte texture.colorSpace. + Certaines conversions (pour les couleurs hexadécimales et CSS en sRGB) peuvent être effectuées automatiquement si + l'API THREE.ColorManagement est activée avant l'initialisation des couleurs : +

        + + +THREE.ColorManagement.enabled = true; + + +

        + THREE.ColorManagement est activé par défaut. +

        + +
          +
        • + Matériaux, lumières et shaders : Les couleurs dans les matériaux, les lumières et les shaders stockent + les composants RGB dans l'espace couleur de travail Linear-sRGB. +
        • +
        • + Couleurs de sommet : `BufferAttribute` stockent les composants RGB dans l'espace couleur de travail + Linear-sRGB. +
        • +
        • + Textures de couleur : Les `Texture` PNG ou JPEG contenant des informations de couleur + (comme .map ou .emissiveMap) utilisent l'espace couleur sRGB en domaine fermé, et doivent être annotées avec + texture.colorSpace = SRGBColorSpace. Des formats comme OpenEXR (parfois utilisés pour .envMap ou + .lightMap) utilisent l'espace couleur Linear-sRGB indiqué par texture.colorSpace = LinearSRGBColorSpace, + et peuvent contenir des valeurs dans le domaine ouvert [0,∞]. +
        • +
        • + Textures non couleur : Les textures qui ne stockent pas d'informations de couleur (comme .normalMap + ou .roughnessMap) n'ont pas d'espace couleur associé, et utilisent généralement l'annotation de texture (par défaut) de + texture.colorSpace = NoColorSpace. Dans de rares cas, les données non couleur + peuvent être représentées avec d'autres encodages non linéaires pour des raisons techniques. +
        • +
        + +
        +

        + ⚠️ AVERTISSEMENT : De nombreux formats de modèles 3D ne définissent pas correctement ou de manière cohérente + les informations d'espace couleur. Bien que three.js tente de gérer la plupart des cas, les problèmes + sont fréquents avec les anciens formats de fichiers. Pour de meilleurs résultats, utilisez glTF 2.0 (`GLTFLoader`) + et testez les modèles 3D dans des visualiseurs en ligne tôt pour confirmer que l'actif lui-même est correct. +

        +
        + +

        Espace couleur de travail

        + +

        + Le rendu, l'interpolation et de nombreuses autres opérations doivent être effectuées dans un espace couleur de travail + linéaire en domaine ouvert, dans lequel les composants RGB sont proportionnels à l'illumination physique. + Dans three.js, l'espace couleur de travail est Linear-sRGB. +

        + +

        Espace couleur de sortie

        + +

        + La sortie vers un périphérique d'affichage, une image ou une vidéo peut impliquer une conversion de l'espace couleur de travail + Linear-sRGB en domaine ouvert vers un autre espace couleur. La conversion est définie par + (`WebGLRenderer.outputColorSpace`). Lors de l'utilisation du post-traitement, cela nécessite OutputPass. +

        + +
          +
        • + Affichage : Les couleurs écrites sur un canevas WebGL pour l'affichage doivent être dans l'espace + couleur sRGB. +
        • +
        • + Image : Les couleurs écrites dans une image doivent utiliser l'espace couleur approprié pour + le format et l'utilisation. Les images entièrement rendues écrites dans des textures PNG ou JPEG utilisent généralement + l'espace couleur sRGB. Les images contenant de l'émission, des lightmaps ou d'autres données non confinées à la plage [0,1] + utiliseront généralement l'espace couleur Linear-sRGB en domaine ouvert, et un format d'image compatible comme OpenEXR. +
        • +
        + +
        +

        + ⚠️ AVERTISSEMENT : Les cibles de rendu peuvent utiliser soit sRGB soit Linear-sRGB. sRGB utilise + mieux la précision limitée. Dans le domaine fermé, 8 bits suffisent souvent pour sRGB tandis que ≥12 bits + (half float) peuvent être nécessaires pour Linear-sRGB. Si les étapes ultérieures du pipeline nécessitent + une entrée Linear-sRGB, les conversions supplémentaires peuvent entraîner un léger coût de performance. +

        +
        + +

        + Les matériaux personnalisés basés sur `ShaderMaterial` et `RawShaderMaterial` doivent implémenter leur propre conversion d'espace couleur de sortie. + Pour les instances de `ShaderMaterial`, ajouter le chunk de shader `colorspace_fragment` à la fonction `main()` du shader de fragment devrait être suffisant. +

        + +

        Utilisation des instances THREE.Color

        + +

        + Les méthodes lisant ou modifiant des instances `Color` supposent que les données sont déjà dans + l'espace couleur de travail de three.js, Linear-sRGB. Les composants RGB et HSL sont des + représentations directes des données stockées par l'instance Color, et ne sont jamais convertis + implicitement. Les données de couleur peuvent être explicitement converties avec .convertLinearToSRGB() + ou .convertSRGBToLinear(). +

        + +
        +// RGB components (no change).
        +color.r = color.g = color.b = 0.5;
        +console.log( color.r ); // → 0.5
        +
        +// Manual conversion.
        +color.r = 0.5;
        +color.convertSRGBToLinear();
        +console.log( color.r ); // → 0.214041140
        +
        + +

        + Avec ColorManagement.enabled = true (recommandé), certaines conversions + sont effectuées automatiquement. Comme les couleurs hexadécimales et CSS sont généralement sRGB, les méthodes `Color` + convertiront automatiquement ces entrées de sRGB vers Linear-sRGB dans les setters, ou convertiront de + Linear-sRGB vers sRGB lors du retour d'une sortie hexadécimale ou CSS à partir des getters. +

        + +
        +// Hexadecimal conversion.
        +color.setHex( 0x808080 );
        +console.log( color.r ); // → 0.214041140
        +console.log( color.getHex() ); // → 0x808080
        +
        +// CSS conversion.
        +color.setStyle( 'rgb( 0.5, 0.5, 0.5 )' );
        +console.log( color.r ); // → 0.214041140
        +
        +// Override conversion with 'colorSpace' argument.
        +color.setHex( 0x808080, LinearSRGBColorSpace );
        +console.log( color.r ); // → 0.5
        +console.log( color.getHex( LinearSRGBColorSpace ) ); // → 0x808080
        +console.log( color.getHex( SRGBColorSpace ) ); // → 0xBCBCBC
        +
        + +

        Erreurs courantes

        + +

        + Lorsqu'une couleur ou une texture individuelle est mal configurée, elle apparaîtra plus foncée ou plus claire que + prévu. Lorsque l'espace couleur de sortie du renderer est mal configuré, toute la scène peut apparaître + plus foncée (par exemple, conversion manquante vers sRGB) ou plus claire (par exemple, une double conversion vers sRGB avec + post-traitement). Dans chaque cas, le problème peut ne pas être uniforme, et simplement augmenter/diminuer + l'éclairage ne le résout pas. +

        + +

        + Un problème plus subtil apparaît lorsque à la fois les espaces couleur d'entrée et les espaces couleur de sortie + sont incorrects — les niveaux de luminosité globaux peuvent être corrects, mais les couleurs peuvent changer + de manière inattendue sous différents éclairages, ou l'ombrage peut sembler plus brûlé et moins doux + que prévu. Ces deux erreurs ne font pas une seule bonne chose, et il est important que l'espace + couleur de travail soit linéaire ("référé à la scène") et l'espace couleur de sortie soit non linéaire + ("référé à l'affichage"). +

        + +

        Pour aller plus loin

        + + + +
        +
        +
        + + + + + + + + \ No newline at end of file diff --git a/manual/fr/creating-a-scene.html b/manual/fr/creating-a-scene.html new file mode 100644 index 00000000000000..c5b71c40a9d085 --- /dev/null +++ b/manual/fr/creating-a-scene.html @@ -0,0 +1,179 @@ + + + Créer une scène + + + + + + + + + + + + + +
        +
        +

        Créer une scène

        +
        +
        +
        + +

        Le but de cette section est de donner une brève introduction à three.js. Nous commencerons par configurer une scène, avec un cube en rotation. Un exemple fonctionnel est fourni en bas de la page au cas où vous seriez bloqué et auriez besoin d'aide.

        + +

        Avant de commencer

        + +

        + Si vous ne l'avez pas encore fait, parcourez le guide `Installation`. Nous supposerons que vous avez déjà configuré la même structure de projet (incluant index.html et main.js), avez installé three.js, et utilisez soit un outil de build, soit un serveur local avec un CDN et des import maps. +

        + +

        Créer la scène

        + +

        Pour pouvoir afficher quoi que ce soit avec three.js, nous avons besoin de trois éléments : une scène, une caméra et un moteur de rendu (renderer), afin que nous puissions afficher la scène avec la caméra.

        + +

        main.js —

        + +
        +import * as THREE from 'three';
        +
        +const scene = new THREE.Scene();
        +const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
        +
        +const renderer = new THREE.WebGLRenderer();
        +renderer.setSize( window.innerWidth, window.innerHeight );
        +document.body.appendChild( renderer.domElement );
        +
        + +

        Prenons un instant pour expliquer ce qui se passe ici. Nous avons maintenant configuré la scène, notre caméra et le moteur de rendu.

        + +

        Il existe plusieurs caméras différentes dans three.js. Pour l'instant, utilisons une `PerspectiveCamera`.

        + +

        Le premier attribut est le `champ de vision`. Le FOV est l'étendue de la scène visible sur l'écran à un moment donné. La valeur est en degrés.

        + +

        Le deuxième est le `rapport d'aspect`. Vous voudrez presque toujours utiliser la largeur de l'élément divisée par la hauteur, sinon vous obtiendrez le même résultat que lorsque vous regardez de vieux films sur une télévision grand écran - l'image semble écrasée.

        + +

        Les deux attributs suivants sont les plans de découpe `near` (proche) et `far` (éloigné). Cela signifie que les objets plus éloignés de la caméra que la valeur de `far` ou plus proches que `near` ne seront pas rendus. Vous n'avez pas à vous en soucier maintenant, mais vous pourriez vouloir utiliser d'autres valeurs dans vos applications pour obtenir de meilleures performances.

        + +

        Vient ensuite le moteur de rendu (renderer). En plus de créer l'instance du moteur de rendu, nous devons également définir la taille à laquelle nous voulons qu'il affiche notre application. C'est une bonne idée d'utiliser la largeur et la hauteur de la zone que nous voulons remplir avec notre application - dans ce cas, la largeur et la hauteur de la fenêtre du navigateur. Pour les applications gourmandes en performances, vous pouvez également donner des valeurs plus petites à `setSize`, comme `window.innerWidth/2` et `window.innerHeight/2`, ce qui fera afficher l'application à un quart de la taille.

        + +

        Si vous souhaitez conserver la taille de votre application mais l'afficher à une résolution inférieure, vous pouvez le faire en appelant `setSize` avec `false` comme argument `updateStyle` (le troisième argument). Par exemple, `setSize(window.innerWidth/2, window.innerHeight/2, false)` affichera votre application à moitié résolution, étant donné que votre <canvas> a une largeur et une hauteur de 100%.

        + +

        Enfin, nous ajoutons l'élément `renderer` à notre document HTML. C'est un élément <canvas> que le moteur de rendu utilise pour nous afficher la scène.

        + +

        "Tout ça c'est bien beau, mais où est ce cube que vous avez promis ?" Ajoutons-le maintenant.

        + +
        +const geometry = new THREE.BoxGeometry( 1, 1, 1 );
        +const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
        +const cube = new THREE.Mesh( geometry, material );
        +scene.add( cube );
        +
        +camera.position.z = 5;
        +
        + +

        Pour créer un cube, nous avons besoin d'une `BoxGeometry`. C'est un objet qui contient tous les points (`vertices`) et le remplissage (`faces`) du cube. Nous explorerons cela plus en détail à l'avenir.

        + +

        En plus de la géométrie, nous avons besoin d'un matériau pour le colorer. Three.js est livré avec plusieurs matériaux, mais nous nous en tiendrons au `MeshBasicMaterial` pour l'instant. Tous les matériaux prennent un objet de propriétés qui leur seront appliquées. Pour simplifier les choses au maximum, nous fournissons seulement un attribut de couleur `0x00ff00`, qui est le vert. Cela fonctionne de la même manière que les couleurs dans CSS ou Photoshop (`couleurs hexadécimales`).

        + +

        La troisième chose dont nous avons besoin est un `Mesh`. Un mesh est un objet qui prend une géométrie et lui applique un matériau, que nous pouvons ensuite insérer dans notre scène et déplacer librement.

        + +

        Par défaut, lorsque nous appelons `scene.add()`, l'élément que nous ajoutons sera ajouté aux coordonnées `(0,0,0)`. Cela entraînerait la caméra et le cube à être l'un dans l'autre. Pour éviter cela, nous déplaçons simplement un peu la caméra.

        + +

        Afficher la scène

        + +

        Si vous copiez le code ci-dessus dans le fichier main.js que nous avons créé précédemment, vous ne pourrez rien voir. C'est parce que nous n'affichons encore rien. Pour cela, nous avons besoin de ce qu'on appelle une boucle de rendu ou d'animation.

        + +
        +function animate() {
        +  renderer.render( scene, camera );
        +}
        +renderer.setAnimationLoop( animate );
        +
        + +

        Cela créera une boucle qui fait que le moteur de rendu dessine la scène à chaque rafraîchissement de l'écran (sur un écran typique, cela signifie 60 fois par seconde). Si vous débutez dans l'écriture de jeux dans le navigateur, vous pourriez dire "pourquoi ne pas simplement créer un setInterval ?" Le fait est que - nous pourrions, mais `requestAnimationFrame` qui est utilisé en interne dans `WebGLRenderer` présente un certain nombre d'avantages. Le plus important est peut-être qu'il se met en pause lorsque l'utilisateur navigue vers un autre onglet du navigateur, évitant ainsi de gaspiller sa précieuse puissance de traitement et l'autonomie de sa batterie.

        + +

        Animer le cube

        + +

        Si vous insérez tout le code ci-dessus dans le fichier que vous avez créé avant de commencer, vous devriez voir une boîte verte. Rendons le tout un peu plus intéressant en le faisant pivoter.

        + +

        Ajoutez le code suivant juste au-dessus de l'appel `renderer.render` dans votre fonction `animate` :

        + +
        +cube.rotation.x += 0.01;
        +cube.rotation.y += 0.01;
        +
        + +

        Cela sera exécuté à chaque image (normalement 60 fois par seconde) et donnera au cube une belle animation de rotation. En gros, tout ce que vous voulez déplacer ou modifier pendant que l'application est en cours d'exécution doit passer par la boucle d'animation. Vous pouvez bien sûr appeler d'autres fonctions à partir de là, afin de ne pas vous retrouver avec une fonction `animate` de plusieurs centaines de lignes.

        + +

        Le résultat

        +

        Félicitations ! Vous avez maintenant terminé votre première application three.js. C'est simple, mais il faut bien commencer quelque part.

        + +

        Le code complet est disponible ci-dessous et sous forme d'un [link:https://jsfiddle.net/tswh48fL/ exemple live] modifiable. Jouez avec pour mieux comprendre comment cela fonctionne.

        + +

        index.html —

        + +
        +<!DOCTYPE html>
        +<html lang="en">
        +  <head>
        +    <meta charset="utf-8">
        +    <title>Ma première application three.js</title>
        +    <style>
        +      body { margin: 0; }
        +    </style>
        +  </head>
        +  <body>
        +    <script type="module" src="/main.js"></script>
        +  </body>
        +</html>
        +
        + +

        main.js —

        + +
        +import * as THREE from 'three';
        +
        +const scene = new THREE.Scene();
        +const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
        +
        +const renderer = new THREE.WebGLRenderer();
        +renderer.setSize( window.innerWidth, window.innerHeight );
        +renderer.setAnimationLoop( animate );
        +document.body.appendChild( renderer.domElement );
        +
        +const geometry = new THREE.BoxGeometry( 1, 1, 1 );
        +const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
        +const cube = new THREE.Mesh( geometry, material );
        +scene.add( cube );
        +
        +camera.position.z = 5;
        +
        +function animate() {
        +
        +  cube.rotation.x += 0.01;
        +  cube.rotation.y += 0.01;
        +
        +  renderer.render( scene, camera );
        +
        +}
        +
        + +
        +
        +
        + + + + + + + + \ No newline at end of file diff --git a/manual/fr/creating-text.html b/manual/fr/creating-text.html new file mode 100644 index 00000000000000..ede41a986e21c2 --- /dev/null +++ b/manual/fr/creating-text.html @@ -0,0 +1,170 @@ + + + Création de texte + + + + + + + + + + + + + +
        +
        +

        Création de texte

        +
        +
        +
        + +
        +

        + Il y a souvent des moments où vous pourriez avoir besoin d'utiliser du texte dans votre application three.js - voici + quelques façons de le faire. +

        +
        + +

        1. DOM + CSS

        +
        +

        + L'utilisation de HTML est généralement la manière la plus simple et la plus rapide d'ajouter du texte. C'est la méthode + utilisée pour les superpositions descriptives dans la plupart des exemples three.js. +

        +

        Vous pouvez ajouter du contenu à un

        +
        +<div id="info">Description</div>
        +
        +

        + et utiliser du balisage CSS pour le positionner absolument à une position au-dessus de tous les autres avec un + z-index, surtout si vous exécutez three.js en plein écran. +

        + +
        +#info {
        +  position: absolute;
        +  top: 10px;
        +  width: 100%;
        +  text-align: center;
        +  z-index: 100;
        +  display:block;
        +}
        +
        + +
        + + +

        2. Utiliser `CSS2DRenderer` ou `CSS3DRenderer`

        +
        +

        + Utilisez ces moteurs de rendu pour dessiner du texte de haute qualité contenu dans des éléments DOM dans votre scène three.js. + C'est similaire à 1., sauf qu'avec ces moteurs de rendu, les éléments peuvent être intégrés de manière plus étroite et dynamique dans la scène. +

        +
        + + +

        3. Dessiner du texte sur un canvas et l'utiliser comme `Texture`

        +
        +

        Utilisez cette méthode si vous souhaitez dessiner facilement du texte sur un plan dans votre scène three.js.

        +
        + + +

        4. Créer un modèle dans votre application 3D préférée et exporter vers three.js

        +
        +

        Utilisez cette méthode si vous préférez travailler avec vos applications 3D et importer les modèles dans three.js.

        +
        + + +

        5. Géométrie de Texte Procédurale

        +
        +

        + Si vous préférez travailler purement en THREE.js ou créer des géométries de texte 3D procédurales et dynamiques, + vous pouvez créer un maillage dont la géométrie est une instance de THREE.TextGeometry : +

        +

        + new THREE.TextGeometry( text, parameters ); +

        +

        + Pour que cela fonctionne, cependant, votre TextGeometry aura besoin d'une instance de THREE.Font + à définir sur son paramètre « font ». + + Consultez la page `TextGeometry` pour plus d'informations sur la manière dont cela peut être fait, des descriptions de chaque + paramètre accepté, et une liste des polices JSON qui sont incluses dans la distribution THREE.js elle-même. +

        + +

        Exemples

        + +

        + [example:webgl_geometry_text WebGL / géométrie / texte]
        + [example:webgl_shadowmap WebGL / shadowmap] +

        + +

        + Si Typeface est indisponible, ou si vous souhaitez utiliser une police qui ne s'y trouve pas, il existe un tutoriel + avec un script python pour blender qui vous permet d'exporter du texte au format JSON de Three.js : + [link:http://www.jaanga.com/2012/03/blender-to-threejs-create-3d-text-with.html] +

        + +
        + + +

        6. Polices bitmap

        +
        +

        + Les BMFonts (polices bitmap) permettent de regrouper les glyphes dans une seule BufferGeometry. Le rendu BMFont + prend en charge le retour à la ligne, l'espacement des lettres, le crénage, les champs de distance signés avec dérivées standard, + les champs de distance signés multicanaux, les polices multi-textures, et plus encore. + Voir [link:https://github.com/felixmariotto/three-mesh-ui three-mesh-ui] ou [link:https://github.com/Jam3/three-bmfont-text three-bmfont-text]. +

        +

        + Des polices standard sont disponibles dans des projets comme + [link:https://github.com/etiennepinchon/aframe-fonts A-Frame Fonts], ou vous pouvez créer les vôtres + à partir de n'importe quelle police .TTF, en optimisant pour n'inclure que les caractères requis pour un projet. +

        +

        + Quelques outils utiles : +

        +
          +
        • [link:http://msdf-bmfont.donmccurdy.com/ msdf-bmfont-web] (basé sur le web)
        • +
        • [link:https://github.com/soimy/msdf-bmfont-xml msdf-bmfont-xml] (ligne de commande)
        • +
        • [link:https://github.com/libgdx/libgdx/wiki/Hiero hiero] (application de bureau)
        • +
        +
        + + +

        7. Troika Text

        +
        +

        + Le paquet [link:https://www.npmjs.com/package/troika-three-text troika-three-text] rend + du texte antialiasé de qualité en utilisant une technique similaire à celle des BMFonts, mais fonctionne directement + avec n'importe quel fichier de police .TTF ou .WOFF, vous n'avez donc pas à prégénérer une texture de glyphe hors ligne. Il ajoute + également des capacités, notamment : +

        +
          +
        • Effets comme les contours, les ombres portées et la courbure
        • +
        • La possibilité d'appliquer n'importe quel Material three.js, même un ShaderMaterial personnalisé
        • +
        • Prise en charge des ligatures de police, des scripts avec lettres jointes et de la mise en page de droite à gauche/bidirectionnelle
        • +
        • Optimisation pour de grandes quantités de texte dynamique, effectuant la majeure partie du travail hors du thread principal dans un web worker
        • +
        +
        + +
        +
        +
        + + + + + + + + \ No newline at end of file diff --git a/manual/fr/custom-buffergeometry.html b/manual/fr/custom-buffergeometry.html index 2cc116ebd1efa3..30438ec64dd9e8 100644 --- a/manual/fr/custom-buffergeometry.html +++ b/manual/fr/custom-buffergeometry.html @@ -1,20 +1,16 @@ - Custom BufferGeometry + BufferGeometry Personnalisée - + - - - - @@ -445,7 +437,7 @@

        Custom BufferGeometry

        - + diff --git a/manual/fr/debugging-glsl.html b/manual/fr/debugging-glsl.html index 078177c4433c9b..59320839eec095 100644 --- a/manual/fr/debugging-glsl.html +++ b/manual/fr/debugging-glsl.html @@ -1,20 +1,16 @@ - Debugging - GLSL + Débogage - GLSL - + - - - - diff --git a/manual/fr/debugging-javascript.html b/manual/fr/debugging-javascript.html index b564e39405c652..48f5aa6913f506 100644 --- a/manual/fr/debugging-javascript.html +++ b/manual/fr/debugging-javascript.html @@ -1,20 +1,16 @@ - Debugging JavaScript + Débogage JavaScript - + - - - - diff --git a/manual/fr/drawing-lines.html b/manual/fr/drawing-lines.html new file mode 100644 index 00000000000000..076e61978da2eb --- /dev/null +++ b/manual/fr/drawing-lines.html @@ -0,0 +1,91 @@ + + + Dessiner des lignes + + + + + + + + + + + + + +
        +
        +

        Dessiner des lignes

        +
        +
        +
        + +

        + Disons que vous voulez dessiner une ligne ou un cercle, pas un `Mesh` en fil de fer. + Nous devons d'abord configurer le renderer, la scène et la caméra (voir la page Créer une scène). +

        + +

        Voici le code que nous allons utiliser :

        +
        +const renderer = new THREE.WebGLRenderer();
        +renderer.setSize( window.innerWidth, window.innerHeight );
        +document.body.appendChild( renderer.domElement );
        +
        +const camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 500 );
        +camera.position.set( 0, 0, 100 );
        +camera.lookAt( 0, 0, 0 );
        +
        +const scene = new THREE.Scene();
        +
        +

        Next thing we will do is define a material. For lines we have to use `LineBasicMaterial` or `LineDashedMaterial`.

        +
        +//créer un LineBasicMaterial bleu
        +const material = new THREE.LineBasicMaterial( { color: 0x0000ff } );
        +
        + +

        + Après le material, nous aurons besoin d'une géométrie avec quelques sommets : +

        + +
        +const points = [];
        +points.push( new THREE.Vector3( - 10, 0, 0 ) );
        +points.push( new THREE.Vector3( 0, 10, 0 ) );
        +points.push( new THREE.Vector3( 10, 0, 0 ) );
        +
        +const geometry = new THREE.BufferGeometry().setFromPoints( points );
        +
        + +

        Notez que les lignes sont tracées entre chaque paire consécutive de sommets, mais pas entre le premier et le dernier (la ligne n'est pas fermée).

        + +

        Maintenant que nous avons des points pour deux lignes et un material, nous pouvons les assembler pour former une ligne.

        +
        +const line = new THREE.Line( geometry, material );
        +
        +

        Tout ce qui reste est de l'ajouter à la scène et d'appeler `renderer.render()`.

        + +
        +scene.add( line );
        +renderer.render( scene, camera );
        +
        + +

        Vous devriez maintenant voir une flèche pointant vers le haut, faite de deux lignes bleues.

        + +
        +
        +
        + + + + + + + + \ No newline at end of file diff --git a/manual/fr/faq.html b/manual/fr/faq.html new file mode 100644 index 00000000000000..7ea4d7bd2a954d --- /dev/null +++ b/manual/fr/faq.html @@ -0,0 +1,93 @@ + + + Foire aux questions + + + + + + + + + + + + + +
        +
        +

        Foire aux questions

        +
        +
        +
        + +

        Quel format de modèle 3D est le mieux supporté ?

        +
        +

        + Le format recommandé pour l'importation et l'exportation d'assets est glTF (GL Transmission Format). Comme glTF est axé sur la livraison d'assets au moment de l'exécution, il est compact à transmettre et rapide à charger. +

        +

        + three.js fournit également des chargeurs pour de nombreux autres formats populaires comme FBX, Collada ou OBJ. Néanmoins, vous devriez toujours essayer d'établir d'abord un workflow basé sur glTF dans vos projets. +

        +
        + +

        Pourquoi y a-t-il des balises meta viewport dans les exemples ?

        +
        +
        <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
        + +

        Ces balises contrôlent la taille et l'échelle de la fenêtre d'affichage (viewport) pour les navigateurs mobiles (où le contenu de la page peut être rendu à une taille différente de la fenêtre d'affichage visible).

        + +

        [link:https://developer.apple.com/library/content/documentation/AppleApplications/Reference/SafariWebContent/UsingtheViewport/UsingtheViewport.html Safari : Utiliser la fenêtre d'affichage]

        + +

        [link:https://developer.mozilla.org/en-US/docs/Web/HTML/Viewport_meta_tag MDN : Utiliser la balise meta viewport]

        +
        + +

        Comment l'échelle de la scène peut-elle être préservée lors du redimensionnement ?

        +

        + Nous voulons que tous les objets, quelle que soit leur distance par rapport à la caméra, apparaissent de la même taille, même lorsque la fenêtre est redimensionnée. + + L'équation clé pour résoudre cela est cette formule pour la hauteur visible à une distance donnée : + +

        visible_height = 2 * Math.tan( ( Math.PI / 180 ) * camera.fov / 2 ) * distance_from_camera;
        + Si nous augmentons la hauteur de la fenêtre d'un certain pourcentage, alors ce que nous voulons, c'est que la hauteur visible à toutes les distances augmente du même pourcentage. + + Cela ne peut pas être fait en changeant la position de la caméra. Au lieu de cela, vous devez changer le champ de vision de la caméra. + [link:http://jsfiddle.net/Q4Jpu/ Exemple]. +

        + +

        Pourquoi une partie de mon objet est-elle invisible ?

        +

        + Cela pourrait être dû au culling des faces (face culling). Les faces ont une orientation qui décide quel côté est lequel. Et le culling supprime le côté arrière dans des circonstances normales. + Pour voir si c'est votre problème, changez le côté du matériau en THREE.DoubleSide. +

        material.side = THREE.DoubleSide
        +

        + +

        Pourquoi three.js renvoie-t-il parfois des résultats étranges pour des entrées invalides ?

        +

        + Pour des raisons de performance, three.js ne valide pas les entrées dans la plupart des cas. Il est de la responsabilité de votre application de s'assurer que toutes les entrées sont valides. +

        + +

        Puis-je utiliser three.js dans Node.js ?

        +

        + Parce que three.js est conçu pour le web, il dépend d'APIs de navigateur et du DOM qui n'existent pas toujours dans Node.js. Certains de ces problèmes peuvent être évités en utilisant des shims comme + [link:https://github.com/stackgl/headless-gl headless-gl] et [link:https://github.com/rstacruz/jsdom-global jsdom-global], ou en remplaçant des composants comme `TextureLoader` + par des alternatives personnalisées. D'autres APIs du DOM peuvent être profondément liées au code qui les utilise, et seront plus difficiles à contourner. Nous accueillons favorablement les pull requests simples et maintenables pour améliorer le support de Node.js, mais recommandons d'ouvrir d'abord un issue pour discuter de vos améliorations. +

        + +
        +
        +
        + + + + + + + + \ No newline at end of file diff --git a/manual/fr/fog.html b/manual/fr/fog.html index 7a34da7f546585..d37face92f2bf9 100644 --- a/manual/fr/fog.html +++ b/manual/fr/fog.html @@ -1,6 +1,6 @@ - Le brouillard + Brouillard @@ -11,10 +11,6 @@ - - - - - - + \ No newline at end of file diff --git a/manual/fr/game.html b/manual/fr/game.html index 43aa069cb55c1b..bc87447df8ee68 100644 --- a/manual/fr/game.html +++ b/manual/fr/game.html @@ -1,20 +1,16 @@ - Making a Game + Créer un jeu - + - - - - diff --git a/manual/fr/how-to-create-vr-content.html b/manual/fr/how-to-create-vr-content.html new file mode 100644 index 00000000000000..01c7403929cc47 --- /dev/null +++ b/manual/fr/how-to-create-vr-content.html @@ -0,0 +1,100 @@ + + + Comment créer du contenu VR + + + + + + + + + + + + + +
        +
        +

        Comment créer du contenu VR

        +
        +
        +
        + +

        + Ce guide fournit un bref aperçu des composants de base d'une application VR basée sur le web réalisée avec three.js. +

        + +

        Flux de travail

        + +

        + Tout d'abord, vous devez inclure [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/webxr/VRButton.js VRButton.js] + dans votre projet. +

        + +
        +import { VRButton } from 'three/addons/webxr/VRButton.js';
        +
        + +

        + *VRButton.createButton()* fait deux choses importantes : il crée un bouton qui indique la compatibilité VR. De plus, il initialise une session VR si l'utilisateur active le bouton. La seule chose que vous avez à faire est d'ajouter la ligne de code suivante à votre application. +

        + +
        +document.body.appendChild( VRButton.createButton( renderer ) );
        +
        + +

        + Ensuite, vous devez indiquer à votre instance de `WebGLRenderer` d'activer le rendu XR. +

        + +
        +renderer.xr.enabled = true;
        +
        + +

        + Enfin, vous devez ajuster votre boucle d'animation car nous ne pouvons pas utiliser notre fonction bien connue *window.requestAnimationFrame()*. Pour les projets VR, nous utilisons `renderer.setAnimationLoop()`. Le code minimal ressemble à ceci : +

        + +
        +renderer.setAnimationLoop( function () {
        +
        +  renderer.render( scene, camera );
        +
        +} );
        +
        + +

        Étapes suivantes

        + +

        + Jetez un œil à l'un des exemples officiels de WebVR pour voir ce flux de travail en action.

        + + [example:webxr_xr_ballshooter WebXR / XR / tireur de balles]
        + [example:webxr_xr_cubes WebXR / XR / cubes]
        + [example:webxr_xr_dragging WebXR / XR / glisser-déposer]
        + [example:webxr_xr_paint WebXR / XR / peinture]
        + [example:webxr_xr_sculpt WebXR / XR / sculpture]
        + [example:webxr_vr_panorama_depth WebXR / VR / panorama-profondeur]
        + [example:webxr_vr_panorama WebXR / VR / panorama]
        + [example:webxr_vr_rollercoaster WebXR / VR / montagnes russes]
        + [example:webxr_vr_sandbox WebXR / VR / bac à sable]
        + [example:webxr_vr_video WebXR / VR / vidéo] +

        + +
        +
        +
        + + + + + + + + \ No newline at end of file diff --git a/manual/fr/how-to-dispose-of-objects.html b/manual/fr/how-to-dispose-of-objects.html new file mode 100644 index 00000000000000..0ec48728ecd91c --- /dev/null +++ b/manual/fr/how-to-dispose-of-objects.html @@ -0,0 +1,169 @@ + + + Comment se débarrasser des objets + + + + + + + + + + + + + +
        +
        +

        Comment se débarrasser des objets

        +
        +
        +
        + +

        + Un aspect important pour améliorer les performances et éviter les fuites de mémoire dans votre application est la libération des entités de la librairie inutilisées. + Chaque fois que vous créez une instance d'un type *three.js*, vous allouez une certaine quantité de mémoire. Cependant, *three.js* crée pour des objets spécifiques + comme les géométries ou les matériaux des entités liées à WebGL comme des tampons (buffers) ou des programmes de shaders qui sont nécessaires au rendu. Il est important de + souligner que ces objets ne sont pas libérés automatiquement. Au lieu de cela, l'application doit utiliser une API spéciale afin de libérer de telles ressources. + Ce guide donne un bref aperçu de la manière dont cette API est utilisée et des objets pertinents dans ce contexte. +

        + +

        Géométries

        + +

        + Une géométrie représente généralement les informations de vertex définies comme une collection d'attributs. *three.js* crée en interne un objet de type [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLBuffer WebGLBuffer] + pour chaque attribut. Ces entités ne sont supprimées que si vous appelez `BufferGeometry.dispose()`. Si une géométrie devient obsolète dans votre application, + exécutez la méthode pour libérer toutes les ressources associées. +

        + +

        Matériaux

        + +

        + Un matériau définit la manière dont les objets sont rendus. *three.js* utilise les informations d'une définition de matériau afin de construire un programme de shader pour le rendu. + Les programmes de shader ne peuvent être supprimés que si le matériau respectif est libéré. Pour des raisons de performance, *three.js* essaie de réutiliser les + programmes de shader existants si possible. Ainsi, un programme de shader n'est supprimé que si tous les matériaux associés sont libérés. Vous pouvez indiquer la libération d'un matériau en + exécutant `Material.dispose()`. +

        + +

        Textures

        + +

        + La libération d'un matériau n'a aucun effet sur les textures. Elles sont gérées séparément car une seule texture peut être utilisée par plusieurs matériaux en même temps. + Chaque fois que vous créez une instance de `Texture`, three.js crée en interne une instance de [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLTexture WebGLTexture]. + Comme pour les tampons (buffers), cet objet ne peut être supprimé qu'en appelant `Texture.dispose()`. +

        + +

        + Si vous utilisez un `ImageBitmap` comme source de données de la texture, vous devez appeler [link:https://developer.mozilla.org/en-US/docs/Web/API/ImageBitmap/close ImageBitmap.close]() au niveau de l'application pour libérer toutes les ressources côté CPU. + Un appel automatique de `ImageBitmap.close()` dans `Texture.dispose()` n'est pas possible, car l'image bitmap devient inutilisable, et le moteur n'a aucun moyen de savoir si l'image bitmap est utilisée ailleurs. +

        + +

        Cibles de rendu

        + +

        + Les objets de type `WebGLRenderTarget` allouent non seulement une instance de [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLTexture WebGLTexture] mais aussi + des [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLFramebuffer WebGLFramebuffer] et des [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderbuffer WebGLRenderbuffer] + pour réaliser des destinations de rendu personnalisées. Ces objets ne sont désalloués qu'en exécutant `WebGLRenderTarget.dispose()`. +

        + +

        Mesh skinné

        + +

        + Les meshes skinnés représentent leur hiérarchie d'os comme des squelettes. Si vous n'avez plus besoin d'un mesh skinné, envisagez d'appeler `Skeleton.dispose()` sur le squelette pour libérer les ressources internes. + Gardez à l'esprit que les squelettes peuvent être partagés entre plusieurs meshes skinnés, n'appelez donc `dispose()` que si le squelette n'est pas utilisé par d'autres meshes skinnés actifs. +

        + +

        Divers

        + +

        + Il existe d'autres classes dans le répertoire d'exemples, comme les contrôles ou les passes de post-traitement, qui fournissent des méthodes `dispose()` afin de supprimer les écouteurs d'événements internes + ou les cibles de rendu. En général, il est recommandé de vérifier l'API ou la documentation d'une classe et de rechercher `dispose()`. Si présent, vous devriez l'utiliser lors du nettoyage. +

        + +

        FAQ

        + +

        Pourquoi *three.js* ne peut-il pas libérer les objets automatiquement ?

        + +

        + Cette question a été posée de nombreuses fois par la communauté, il est donc important de clarifier ce point. Le fait est que *three.js* ne connaît pas la durée de vie ou la portée + des entités créées par l'utilisateur, comme les géométries ou les matériaux. C'est la responsabilité de l'application. Par exemple, même si un matériau n'est actuellement pas utilisé pour le rendu, + il pourrait être nécessaire pour la prochaine image. Donc, si l'application décide qu'un certain objet peut être supprimé, elle doit en informer le moteur en appelant la méthode + `dispose()` respective. +

        + +

        La suppression d'un mesh de la scène libère-t-elle également sa géométrie et son matériau ?

        + +

        + Non, vous devez explicitement libérer la géométrie et le matériau via *dispose()*. Gardez à l'esprit que les géométries et les matériaux peuvent être partagés entre des objets 3D comme les meshes. +

        + +

        *three.js* fournit-il des informations sur la quantité d'objets mis en cache ?

        + +

        + Oui. Il est possible d'évaluer `renderer.info`, une propriété spéciale du renderer avec une série d'informations statistiques sur la mémoire de la carte graphique + et le processus de rendu. Entre autres choses, elle vous indique combien de textures, de géométries et de programmes de shader sont stockés en interne. Si vous remarquez des problèmes de performance + dans votre application, c'est une bonne idée de déboguer cette propriété afin d'identifier facilement une fuite de mémoire. +

        + +

        Que se passe-t-il lorsque vous appelez `dispose()` sur une texture mais que l'image n'est pas encore chargée ?

        + +

        + Les ressources internes d'une texture ne sont allouées que si l'image est entièrement chargée. Si vous libérez une texture avant que l'image ne soit chargée, + rien ne se passe. Aucune ressource n'a été allouée, il n'y a donc pas besoin de nettoyage. +

        + +

        Que se passe-t-il si j'appelle `dispose()` puis utilise l'objet respectif ultérieurement ?

        + +

        + Cela dépend. Pour les géométries, les matériaux, les textures, les cibles de rendu et les passes de post-traitement, les ressources internes supprimées peuvent être recréées par le moteur. + Aucune erreur d'exécution ne se produira donc, mais vous pourriez remarquer un impact négatif sur les performances pour l'image actuelle, surtout lorsque les programmes de shader doivent être compilés. + + Les contrôles et les renderers sont une exception. Les instances de ces classes ne peuvent pas être utilisées après que `dispose()` a été appelée. Vous devez créer de nouvelles instances dans ce cas. +

        + +

        Comment gérer les objets *three.js* dans mon application ? Quand savoir comment libérer les choses ?

        + +

        + En général, il n'y a pas de recommandation définitive pour cela. Cela dépend fortement du cas d'utilisation spécifique pour savoir quand appeler `dispose()` est approprié. Il est important de souligner que + il n'est pas toujours nécessaire de libérer les objets en permanence. Un bon exemple est un jeu composé de plusieurs niveaux. Un bon moment pour la libération des objets est lors du changement de niveau. + L'application pourrait parcourir l'ancienne scène et libérer tous les matériaux, géométries et textures obsolètes. Comme mentionné dans la section précédente, cela ne produit pas + d'erreur d'exécution si vous libérez un objet qui est en fait toujours utilisé. Le pire qui puisse arriver est une chute de performance pour une seule image. +

        + +

        Pourquoi `renderer.info.memory` rapporte toujours des géométries et des textures après avoir parcouru la scène et libéré toutes les textures et géométries accessibles ?

        + +

        + Dans certains cas, certaines textures et géométries utilisées en interne par Three.js + ne sont pas accessibles lors de la traversée du graphe de scène afin d'être libérées. + Il est prévu que `renderer.info.memory` les signale toujours même après un nettoyage complet de la scène. + Cependant, elles ne fuient pas, mais sont réutilisées lors des cycles consécutifs de nettoyage/repopulation de la scène. + + Ces cas peuvent être liés à l'utilisation de `material.envMap`, `scene.background`, `scene.environment`, + ou d'autres contextes qui nécessitent que le moteur crée des textures ou des géométries pour un usage interne. +

        + +

        Exemples illustrant l'utilisation de dispose()

        + +

        + [example:webgl_test_memory WebGL / test / mémoire]
        + [example:webgl_test_memory2 WebGL / test / mémoire2]
        +

        + +
        +
        +
        + + + + + + + + \ No newline at end of file diff --git a/manual/fr/how-to-update-things.html b/manual/fr/how-to-update-things.html new file mode 100644 index 00000000000000..8be9e69ff74eb0 --- /dev/null +++ b/manual/fr/how-to-update-things.html @@ -0,0 +1,275 @@ + + + Comment mettre à jour les éléments + + + + + + + + + + + + + +
        +
        +

        Comment mettre à jour les éléments

        +
        +
        +
        + +
        +

        Par défaut, tous les objets mettent automatiquement à jour leurs matrices s'ils ont été ajoutés à la scène avec

        +
        +const object = new THREE.Object3D();
        +scene.add( object );
        +
        + ou s'ils sont l'enfant d'un autre objet qui a été ajouté à la scène : +
        +const object1 = new THREE.Object3D();
        +const object2 = new THREE.Object3D();
        +
        +object1.add( object2 );
        +scene.add( object1 ); //object1 et object2 mettront automatiquement à jour leurs matrices
        +
        +
        + +

        Cependant, si vous savez que l'objet sera statique, vous pouvez désactiver cela et mettre à jour la matrice de transformation manuellement uniquement lorsque nécessaire.

        + +
        +object.matrixAutoUpdate = false;
        +object.updateMatrix();
        +
        + +

        BufferGeometry

        +
        +

        + Les BufferGeometries stockent des informations (telles que les positions des sommets, les indices des faces, les normales, les couleurs, + les UV et tout attribut personnalisé) dans des tampons d'attributs - c'est-à-dire des + [link:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays tableaux typés]. + Cela les rend généralement plus rapides que les Geometries standard, au prix d'être un peu plus difficiles à + utiliser. +

        +

        + En ce qui concerne la mise à jour des BufferGeometries, la chose la plus importante à comprendre est que + vous ne pouvez pas redimensionner les tampons (c'est très coûteux, c'est fondamentalement l'équivalent de la création d'une nouvelle géométrie). + Vous pouvez cependant mettre à jour le contenu des tampons. +

        +

        + Cela signifie que si vous savez qu'un attribut de votre BufferGeometry va croître, par exemple le nombre de sommets, + vous devez pré-allouer un tampon suffisamment grand pour contenir tous les nouveaux sommets qui pourraient être créés. Bien sûr, + cela signifie également qu'il y aura une taille maximale pour votre BufferGeometry - il n'y a + aucun moyen de créer une BufferGeometry qui puisse être étendue efficacement indéfiniment. +

        +

        + Nous utiliserons l'exemple d'une ligne qui s'étend au moment du rendu. Nous allouerons de l'espace + dans le tampon pour 500 sommets, mais n'en dessinerons que deux au début, en utilisant `BufferGeometry.drawRange`. +

        +
        +const MAX_POINTS = 500;
        +
        +// geometry
        +const geometry = new THREE.BufferGeometry();
        +
        +// attributes
        +const positions = new Float32Array( MAX_POINTS * 3 ); // 3 floats (x, y et z) par point
        +geometry.setAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );
        +
        +// draw range
        +const drawCount = 2; // dessine seulement les 2 premiers points, seulement
        +geometry.setDrawRange( 0, drawCount );
        +
        +// material
        +const material = new THREE.LineBasicMaterial( { color: 0xff0000 } );
        +
        +// line
        +const line = new THREE.Line( geometry, material );
        +scene.add( line );
        +
        +

        + Ensuite, nous ajouterons aléatoirement des points à la ligne en utilisant un modèle comme : +

        +
        +const positionAttribute = line.geometry.getAttribute( 'position' );
        +
        +let x = 0, y = 0, z = 0;
        +
        +for ( let i = 0; i < positionAttribute.count; i ++ ) {
        +
        +    positionAttribute.setXYZ( i, x, y, z );
        +
        +    x += ( Math.random() - 0.5 ) * 30;
        +    y += ( Math.random() - 0.5 ) * 30;
        +    z += ( Math.random() - 0.5 ) * 30;
        +
        +}
        +
        +

        + Si vous souhaitez modifier le nombre de points rendus après le premier rendu, faites ceci : +

        +
        +line.geometry.setDrawRange( 0, newValue );
        +
        +

        + Si vous souhaitez modifier les valeurs des données de position après le premier rendu, vous devez + définir le drapeau needsUpdate comme suit : +

        +
        +positionAttribute.needsUpdate = true; // requis après le premier rendu
        +
        + +

        + Si vous modifiez les valeurs des données de position après le rendu initial, vous pourriez avoir besoin de recalculer + les volumes englobants afin que d'autres fonctionnalités du moteur comme le culling par frustum de vue ou les assistants fonctionnent correctement. +

        +
        +line.geometry.computeBoundingBox();
        +line.geometry.computeBoundingSphere();
        +
        + +

        + [link:https://jsfiddle.net/t4m85pLr/1/ Voici un fiddle] montrant une ligne animée que vous pouvez adapter à votre cas d'utilisation. +

        + +

        Exemples

        + +

        + [example:webgl_custom_attributes WebGL / personnalisé / attributs]
        + [example:webgl_buffergeometry_custom_attributes_particles WebGL / buffergeometry / personnalisé / attributs / particules] +

        + +
        + +

        Matériaux

        +
        +

        Toutes les valeurs des uniforms peuvent être modifiées librement (par exemple couleurs, textures, opacité, etc.), les valeurs sont envoyées au shader à chaque image.

        + +

        De plus, les paramètres liés à l'état GL peuvent changer à tout moment (depthTest, blending, polygonOffset, etc.).

        + +

        Les propriétés suivantes ne peuvent pas être facilement modifiées à l'exécution (une fois que le matériau a été rendu au moins une fois) :

        +
          +
        • nombre et types des uniforms
        • +
        • présence ou non de +
            +
          • texture
          • +
          • brouillard
          • +
          • couleurs de sommet
          • +
          • morphing
          • +
          • shadow map
          • +
          • test alpha
          • +
          • transparent
          • +
          +
        • +
        + +

        Les modifications de ces éléments nécessitent la construction d'un nouveau programme de shader. Vous devrez définir

        + material.needsUpdate = true + +

        Gardez à l'esprit que cela peut être assez lent et provoquer des à-coups dans la cadence d'images (surtout sous Windows, car la compilation des shaders est plus lente en DirectX qu'en OpenGL).

        + +

        Pour une expérience plus fluide, vous pouvez émuler dans une certaine mesure les modifications de ces fonctionnalités en utilisant des valeurs "factices" comme des lumières d'intensité nulle, des textures blanches ou un brouillard de densité nulle.

        + +

        Vous pouvez modifier librement le matériau utilisé pour les morceaux de géométrie, cependant, vous ne pouvez pas modifier la façon dont un objet est divisé en morceaux (selon les matériaux des faces).

        + +

        Si vous avez besoin d'avoir différentes configurations de matériaux pendant l'exécution :

        +

        Si le nombre de matériaux / morceaux est faible, vous pouvez pré-diviser l'objet à l'avance (par exemple cheveux / visage / corps / vêtements du haut / pantalon pour un humain, avant / côtés / haut / verre / pneu / intérieur pour une voiture).

        + +

        Si le nombre est élevé (par exemple, chaque face pourrait être potentiellement différente), envisagez une solution différente, telle que l'utilisation d'attributs / textures pour piloter un aspect différent par face.

        + +

        Exemples

        +

        + [example:webgl_materials_car WebGL / matériaux / voiture]
        + [example:webgl_postprocessing_dof WebGL / webgl_postprocessing / dof] +

        +
        + + +

        Textures

        +
        +

        Les textures d'image, de canevas, de vidéo et de données doivent avoir le drapeau suivant défini si elles sont modifiées :

        + + texture.needsUpdate = true; + +

        Les cibles de rendu se mettent à jour automatiquement.

        + +

        Exemples

        +

        + [example:webgl_materials_video WebGL / matériaux / vidéo]
        + [example:webgl_rtt WebGL / rtt] +

        + +
        + +

        Caméras

        +
        +

        La position et la cible d'une caméra sont mises à jour automatiquement. Si vous avez besoin de changer

        +
          +
        • + fov +
        • +
        • + aspect +
        • +
        • + near +
        • +
        • + far +
        • +
        +

        + alors vous devrez recalculer la matrice de projection : +

        +
        +camera.aspect = window.innerWidth / window.innerHeight;
        +camera.updateProjectionMatrix();
        +
        +
        + +

        InstancedMesh

        +
        +

        + InstancedMesh est une classe permettant d'accéder facilement au rendu instancié dans three.js. Certaines fonctionnalités de la bibliothèque comme le culling par frustum de vue ou + le ray casting dépendent de volumes englobants à jour (sphère englobante et boîte englobante). En raison de la façon dont InstancedMesh fonctionne, la classe + possède ses propres propriétés boundingBox et boundingSphere qui remplacent les volumes englobants au niveau de la géométrie. +

        +

        + Similaire aux géométries, vous devez recalculer la boîte englobante et la sphère chaque fois que vous modifiez les données sous-jacentes. Dans le contexte de InstancedMesh, cela + se produit lorsque vous transformez des instances via setMatrixAt(). Vous pouvez utiliser le même modèle qu'avec les géométries. +

        +
        +instancedMesh.computeBoundingBox();
        +instancedMesh.computeBoundingSphere();
        +
        + +
        + +

        SkinnedMesh

        +
        +

        + SkinnedMesh suit les mêmes principes que InstancedMesh en ce qui concerne les volumes englobants. Cela signifie que la classe a sa propre version de + boundingBox et boundingSphere pour enfermer correctement les maillages animés. + Lors de l'appel de computeBoundingBox() et computeBoundingSphere(), la classe calcule les volumes englobants respectifs en fonction de la transformation actuelle des os (ou en d'autres termes, de l'état d'animation actuel). +

        +
        + +
        +
        +
        + + + + + + + + \ No newline at end of file diff --git a/manual/fr/how-to-use-post-processing.html b/manual/fr/how-to-use-post-processing.html new file mode 100644 index 00000000000000..60881ee8aae0f3 --- /dev/null +++ b/manual/fr/how-to-use-post-processing.html @@ -0,0 +1,142 @@ + + + Comment utiliser le post-traitement + + + + + + + + + + + + + +
        +
        +

        Comment utiliser le post-traitement

        +
        +
        +
        + +

        + De nombreuses applications three.js rendent leurs objets 3D directement à l'écran. Parfois, cependant, vous souhaitez appliquer un ou plusieurs effets graphiques + tels que la profondeur de champ, le Bloom, le grain de film ou divers types d'anti-aliasing. Le post-traitement est une approche largement utilisée + pour implémenter de tels effets. D'abord, la scène est rendue sur une cible de rendu qui représente un tampon dans la mémoire de la carte graphique. + À l'étape suivante, une ou plusieurs passes de post-traitement appliquent des filtres et des effets au tampon d'image avant qu'il ne soit finalement rendu à + l'écran. +

        +

        + three.js fournit une solution complète de post-traitement via `EffectComposer` pour implémenter un tel flux de travail. +

        + +

        Flux de travail

        + +

        + La première étape du processus consiste à importer tous les fichiers nécessaires depuis le répertoire d'exemples. Ce guide suppose que vous utilisez le + [link:https://www.npmjs.com/package/three package npm] officiel de three.js. Pour notre démo de base dans ce guide, nous avons besoin des fichiers suivants. +

        + +
        +import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
        +import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
        +import { GlitchPass } from 'three/addons/postprocessing/GlitchPass.js';
        +import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
        +
        + +

        + Après l'importation réussie de tous les fichiers, nous pouvons créer notre compositeur en lui passant une instance de `WebGLRenderer`. +

        + +
        +const composer = new EffectComposer( renderer );
        +
        + +

        + Lorsque vous utilisez un compositeur, il est nécessaire de modifier la boucle d'animation de l'application. Au lieu d'appeler la méthode de rendu de + `WebGLRenderer`, nous utilisons maintenant la contrepartie respective de `EffectComposer`. +

        + +
        +function animate() {
        +
        +  requestAnimationFrame( animate );
        +
        +  composer.render();
        +
        +}
        +
        + +

        + Notre compositeur est maintenant prêt, il est donc possible de configurer la chaîne de passes de post-traitement. Ces passes sont responsables de la création + du rendu visuel final de l'application. Elles sont traitées dans l'ordre de leur ajout/insertion. Dans notre exemple, l'instance de `RenderPass` + est exécutée en premier, puis l'instance de `GlitchPass` et enfin `OutputPass`. La dernière passe activée dans la chaîne est automatiquement rendue à l'écran. + La configuration des passes ressemble à ceci : +

        + +
        +const renderPass = new RenderPass( scene, camera );
        +composer.addPass( renderPass );
        +
        +const glitchPass = new GlitchPass();
        +composer.addPass( glitchPass );
        +
        +const outputPass = new OutputPass();
        +composer.addPass( outputPass );
        +
        + +

        + `RenderPass` est normalement placée au début de la chaîne afin de fournir la scène rendue comme entrée pour l'étape de post-traitement suivante. Dans notre cas, + `GlitchPass` utilisera ces données d'image pour appliquer un effet de glitch sauvage. `OutputPass` est généralement la dernière passe de la chaîne qui effectue la conversion de l'espace colorimétrique sRGB et le mappage tonal. + Découvrez cet [link:https://threejs.org/examples/webgl_postprocessing_glitch exemple live] pour le voir en action. +

        + +

        Passes intégrées

        + +

        + Vous pouvez utiliser une large gamme de passes de post-traitement prédéfinies fournies par le moteur. Elles se trouvent dans le + répertoire [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm/postprocessing postprocessing]. +

        + +

        Passes personnalisées

        + +

        + Parfois, vous souhaitez écrire un shader de post-traitement personnalisé et l'inclure dans la chaîne de passes de post-traitement. Pour ce scénario, + vous pouvez utiliser `ShaderPass`. Après avoir importé le fichier et votre shader personnalisé, vous pouvez utiliser le code suivant pour configurer la passe. +

        + +
        +import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
        +import { LuminosityShader } from 'three/addons/shaders/LuminosityShader.js';
        +
        +// plus tard dans votre routine d'initialisation
        +
        +const luminosityPass = new ShaderPass( LuminosityShader );
        +composer.addPass( luminosityPass );
        +
        + +

        + Le dépôt fournit un fichier appelé [link:https://github.com/mrdoob/three.js/blob/master/examples/jsm/shaders/CopyShader.js CopyShader] qui constitue un + bon point de départ pour votre propre shader personnalisé. `CopyShader` copie simplement le contenu de l'image du tampon de lecture (`read buffer`) de l'`EffectComposer` + vers son tampon d'écriture (`write buffer`) sans appliquer aucun effet. +

        + +
        +
        +
        + + + + + + + + \ No newline at end of file diff --git a/manual/fr/indexed-textures.html b/manual/fr/indexed-textures.html index 0923ddd0b1d57f..3a9a3bc0739a31 100644 --- a/manual/fr/indexed-textures.html +++ b/manual/fr/indexed-textures.html @@ -1,20 +1,16 @@ - Indexed Textures for Picking and Color + Textures Indexées pour la Sélection et la Couleur - + - - - - diff --git a/manual/fr/installation.html b/manual/fr/installation.html new file mode 100644 index 00000000000000..08e3c366f1a54b --- /dev/null +++ b/manual/fr/installation.html @@ -0,0 +1,306 @@ + + + Installation + + + + + + + + + + + + + +
        +
        +

        Installation

        +
        +
        +
        + +

        Structure du projet

        + +

        + Tout projet three.js nécessite au moins un fichier HTML pour définir la page web, et un fichier JavaScript pour exécuter votre code three.js. La structure et les choix de noms ci-dessous ne sont pas obligatoires, mais seront utilisés tout au long de ce guide par souci de cohérence. +

        + +
          +
        • + index.html +
          +<!DOCTYPE html>
          +<html lang="en">
          +  <head>
          +    <meta charset="utf-8">
          +    <title>Ma première application three.js</title>
          +    <style>
          +      body { margin: 0; }
          +    </style>
          +  </head>
          +  <body>
          +    <script type="module" src="/main.js"></script>
          +  </body>
          +</html>
          +    
          +
        • +
        • + main.js +
          +import * as THREE from 'three';
          +
          +...
          +
          +
        • +
        • + public/ +
            +
          • + Le dossier public/ est parfois aussi appelé dossier "static", car les fichiers qu'il contient sont poussés vers le site web sans modification. Généralement, les textures, l'audio et les modèles 3D s'y trouvent. +
          • +
          +
        • +
        + +

        + Maintenant que nous avons mis en place la structure de base du projet, nous avons besoin d'un moyen pour exécuter le projet localement et y accéder via un navigateur web. L'installation et le développement local peuvent être accomplis avec npm et un outil de build, ou en important three.js depuis un CDN. Les deux options sont expliquées dans les sections ci-dessous. +

        + +

        Option 1 : Installation avec NPM et un outil de build

        + +

        Développement

        + +

        + L'installation depuis le [link:https://www.npmjs.com/ registre de packages npm] et l'utilisation d'un [link:https://eloquentjavascript.net/10_modules.html#h_zWTXAU93DC outil de build] est l'approche recommandée pour la plupart des utilisateurs — plus votre projet a de dépendances, plus vous êtes susceptible de rencontrer des problèmes que l'hébergement statique ne peut pas facilement résoudre. Avec un outil de build, l'importation de fichiers JavaScript locaux et de packages npm devrait fonctionner directement, sans cartes d'importation. +

        + + +
          +
        1. + Installez [link:https://nodejs.org/ Node.js]. Nous en aurons besoin pour gérer les dépendances et exécuter notre outil de build. +
        2. +
        3. +

          + Installez three.js et un outil de build, [link:https://vitejs.dev/ Vite], en utilisant un [link:https://www.joshwcomeau.com/javascript/terminal-for-js-devs/ terminal] dans le dossier de votre projet. Vite sera utilisé pendant le développement, mais ne fait pas partie de la page web finale. Si vous préférez utiliser un autre outil de build, c'est bien — nous supportons les outils de build modernes qui peuvent importer les [link:https://eloquentjavascript.net/10_modules.html#h_zWTXAU93DC Modules ES]. +

          +
          +# three.js
          +npm install --save three
          +
          +# vite
          +npm install --save-dev vite
          +
          + +
        4. +
        5. + Depuis votre terminal, exécutez : +
          npx vite 
          + +
        6. +
        7. + Si tout s'est bien passé, vous verrez une URL comme http://localhost:5173 apparaître dans votre terminal, et vous pourrez ouvrir cette URL pour voir votre application web. +
        8. +
        + +

        + La page sera vide — vous êtes prêt à créer une scène. +

        + +

        + Si vous voulez en savoir plus sur ces outils avant de continuer, consultez : +

        + +
          +
        • + [link:https://threejs-journey.com/lessons/local-server three.js journey : Serveur local] +
        • +
        • + [link:https://vitejs.dev/guide/cli.html Vite : Interface en ligne de commande] +
        • +
        • + [link:https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Package_management MDN : Principes de base de la gestion des packages] +
        • +
        + +

        Production

        + +

        + Plus tard, lorsque vous serez prêt à déployer votre application web, il vous suffira d'indiquer à Vite d'exécuter une build de production — npx vite build. Tout ce qui est utilisé par l'application sera compilé, optimisé et copié dans le dossier dist/. Le contenu de ce dossier est prêt à être hébergé sur votre site web. +

        + +

        Option 2 : Importation depuis un CDN

        + +

        Développement

        + +

        L'installation sans outils de build nécessitera quelques modifications de la structure du projet donnée ci-dessus.

        + +
          +
        1. +

          + Nous avons importé du code depuis 'three' (un package npm) dans main.js, et les navigateurs web ne savent pas ce que cela signifie. Dans index.html, nous devrons ajouter une [link:https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap carte d'importation] définissant où obtenir le package. Placez le code ci-dessous à l'intérieur de la balise <head></head>, après les styles. +

          +
          +<script type="importmap">
          +{
          +  "imports": {
          +    "three": "https://cdn.jsdelivr.net/npm/three@<version>/build/three.module.js",
          +    "three/addons/": "https://cdn.jsdelivr.net/npm/three@<version>/examples/jsm/"
          +  }
          +}
          +</script>
          +
          +

          + N'oubliez pas de remplacer <version> par une version réelle de three.js, comme "v0.149.0". La version la plus récente peut être trouvée sur la [link:https://www.npmjs.com/package/three?activeTab=versions liste des versions npm]. +

          +
        2. +
        3. +

          + Nous aurons également besoin d'exécuter un serveur local pour héberger ces fichiers à une URL accessible par le navigateur web. Bien qu'il soit techniquement possible de double-cliquer sur un fichier HTML et de l'ouvrir dans votre navigateur, des fonctionnalités importantes que nous implémenterons plus tard ne fonctionnent pas lorsque la page est ouverte de cette manière, pour des raisons de sécurité. +

          +

          + Installez [link:https://nodejs.org/ Node.js], puis exécutez [link:https://www.npmjs.com/package/serve serve] pour démarrer un serveur local dans le répertoire du projet : +

          +
          npx serve .
          +
        4. +
        5. + Si tout s'est bien passé, vous verrez une URL comme http://localhost:3000 apparaître dans votre terminal, et vous pourrez ouvrir cette URL pour voir votre application web. +
        6. +
        + +

        + La page sera vide — vous êtes prêt à [link:#manual/introduction/Creating-a-scene créer une scène]. +

        + +

        + De nombreux autres serveurs statiques locaux sont disponibles — certains utilisent des langages différents au lieu de Node.js, et d'autres sont des applications de bureau. Ils fonctionnent tous fondamentalement de la même manière, et nous avons fourni quelques alternatives ci-dessous. +

        + +
        + Plus de serveurs locaux + +

        Ligne de commande

        + +

        Les serveurs locaux en ligne de commande s'exécutent depuis une fenêtre de terminal. Le langage de programmation associé peut devoir être installé au préalable.

        + +
          +
        • npx http-server (Node.js)
        • +
        • npx five-server (Node.js)
        • +
        • python -m SimpleHTTPServer (Python 2.x)
        • +
        • python -m http.server (Python 3.x)
        • +
        • php -S localhost:8000 (PHP 5.4+)
        • +
        + + +

        GUI

        + +

        Les serveurs locaux GUI s'exécutent sous forme de fenêtre d'application sur votre ordinateur, et peuvent avoir une interface utilisateur.

        + +
          +
        • [link:https://greggman.github.io/servez Servez]
        • +
        + +

        Plugins d'éditeur de code

        + +

        Certains éditeurs de code disposent de plugins qui lancent un simple serveur à la demande.

        + +
          +
        • [link:https://marketplace.visualstudio.com/items?itemName=yandeu.five-server Five Server] pour Visual Studio Code
        • +
        • [link:https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer Live Server] pour Visual Studio Code
        • +
        • [link:https://atom.io/packages/atom-live-server Live Server] pour Atom
        • +
        + + +
        + +

        Production

        + +

        + Lorsque vous êtes prêt à déployer votre application web, poussez les fichiers source chez votre hébergeur web — pas besoin de build ou de compiler quoi que ce soit. L'inconvénient de ce compromis est que vous devrez veiller à maintenir la carte d'importation à jour avec toutes les dépendances (et les dépendances des dépendances !) dont votre application a besoin. Si le CDN hébergeant vos dépendances tombe temporairement, votre site web cessera également de fonctionner. +

        + +

        + IMPORTANT : Importez toutes les dépendances depuis la même version de three.js et depuis le même CDN. Mélanger des fichiers de différentes sources peut entraîner l'inclusion de code dupliqué, ou même casser l'application de manière inattendue. +

        + +

        Addons

        + +

        + Par défaut, three.js inclut les fondamentaux d'un moteur 3D. Les autres composants de three.js — tels que les contrôles, les chargeurs et les effets de post-traitement — font partie du répertoire [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm addons/]. Les Addons n'ont pas besoin d'être installés séparément, mais doivent être importés séparément. +

        + +

        + L'exemple ci-dessous montre comment importer three.js avec les addons `OrbitControls` et `GLTFLoader`. Si nécessaire, cela sera également mentionné dans la documentation ou les exemples de chaque addon. +

        + +
        +import * as THREE from 'three';
        +import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
        +import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
        +
        +const controls = new OrbitControls( camera, renderer.domElement );
        +const loader = new GLTFLoader();
        +
        + +

        + D'excellents projets tiers sont également disponibles pour three.js. Ceux-ci doivent être installés séparément — voir Bibliothèques et Plugins. +

        + +

        Étapes suivantes

        + +

        + Vous êtes maintenant prêt à créer une scène. +

        + +
        +
        +
        + + + + + + + + \ No newline at end of file diff --git a/manual/fr/libraries-and-plugins.html b/manual/fr/libraries-and-plugins.html new file mode 100644 index 00000000000000..0935775a11093d --- /dev/null +++ b/manual/fr/libraries-and-plugins.html @@ -0,0 +1,146 @@ + + + Bibliothèques et Plugins + + + + + + + + + + + + + +
        +
        +

        Bibliothèques et Plugins

        +
        +
        +
        + +

        + Voici une liste de bibliothèques et plugins compatibles développés en externe pour three.js. Cette + liste et les paquets associés sont maintenus par la communauté et ne sont pas garantis + d'être à jour. Si vous souhaitez mettre à jour cette liste, faites une Pull Request ! +

        + +

        Physique

        + +
          +
        • [link:https://github.com/lo-th/Oimo.js/ Oimo.js]
        • +
        • [link:https://enable3d.io/ enable3d]
        • +
        • [link:https://github.com/kripken/ammo.js/ ammo.js]
        • +
        • [link:https://github.com/pmndrs/cannon-es cannon-es]
        • +
        • [link:https://rapier.rs/ rapier]
        • +
        • [link:https://github.com/jrouwe/JoltPhysics.js Jolt]
        • + +
        + +

        Post-traitement

        + +

        + En plus des [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm/postprocessing effets de post-traitement officiels de three.js], + la prise en charge d'effets et de frameworks supplémentaires est disponible via des bibliothèques externes. +

        + +
          +
        • [link:https://github.com/vanruesc/postprocessing postprocessing]
        • +
        + +

        Performance d'Intersection et de Raycast

        + +
          +
        • [link:https://github.com/gkjohnson/three-mesh-bvh three-mesh-bvh]
        • +
        + +

        Tracé de chemin

        + +
          +
        • [link:https://github.com/gkjohnson/three-gpu-pathtracer three-gpu-pathtracer]
        • +
        + +

        Formats de fichier

        + +

        + En plus des [link:https://github.com/mrdoob/three.js/tree/dev/examples/jsm/loaders chargeurs officiels de three.js], + la prise en charge de formats supplémentaires est disponible via des bibliothèques externes. +

        + +
          +
        • [link:https://github.com/gkjohnson/urdf-loaders/tree/master/javascript urdf-loader]
        • +
        • [link:https://github.com/NASA-AMMOS/3DTilesRendererJS 3d-tiles-renderer-js]
        • +
        • [link:https://github.com/kaisalmen/WWOBJLoader Chargeur OBJ WebWorker]
        • +
        • [link:https://github.com/IFCjs/web-ifc-three IFC.js]
        • +
        + +

        Géométrie

        + +
          +
        • [link:https://github.com/spite/THREE.MeshLine THREE.MeshLine]
        • +
        + +

        Texte et Mise en page 3D

        + +
          +
        • [link:https://github.com/protectwise/troika/tree/master/packages/troika-three-text troika-three-text]
        • +
        • [link:https://github.com/felixmariotto/three-mesh-ui three-mesh-ui]
        • +
        + +

        Systèmes de particules

        + +
          +
        • [link:https://github.com/Alchemist0823/three.quarks three.quarks]
        • +
        • [link:https://github.com/creativelifeform/three-nebula three-nebula]
        • +
        + +

        Cinématique inverse

        + +
          +
        • [link:https://github.com/jsantell/THREE.IK THREE.IK]
        • +
        • [link:https://github.com/lo-th/fullik fullik]
        • +
        • [link:https://github.com/gkjohnson/closed-chain-ik-js closed-chain-ik]
        • +
        + +

        IA de jeu

        + +
          +
        • [link:https://mugen87.github.io/yuka/ yuka]
        • +
        • [link:https://github.com/donmccurdy/three-pathfinding three-pathfinding]
        • +
        • [link:https://github.com/isaac-mason/recast-navigation-js recast-navigation-js]
        • +
        + +

        Wrappers et Frameworks

        + +
          +
        • [link:https://aframe.io/ A-Frame]
        • +
        • [link:https://lume.io/ Lume] - Éléments HTML pour graphismes 3D basés sur Three.
        • +
        • [link:https://github.com/pmndrs/react-three-fiber react-three-fiber] - Composants React pour graphismes 3D basés sur Three.
        • +
        • [link:https://threepipe.org/ threepipe] - Un framework de visualisation 3D polyvalent utilisant three.js pour le rendu.
        • +
        • [link:https://ecsyjs/ecsy-three ECSY]
        • +
        • [link:https://threlte.xyz/ Threlte] - Composants Svelte pour graphismes 3D basés sur Three.
        • +
        • [link:https://needle.tools/ Needle Engine]
        • +
        • [link:https://tresjs.org/ tresjs] - Composants Vue pour graphismes 3D basés sur Three.
        • +
        • [link:https://giro3d.org Giro3D] - Framework polyvalent basé sur Three pour visualiser et interagir avec des données géospatiales 2D, 2.5D et 3D.
        • +
        • [link:https://zap.works/mattercraft/ Mattercraft] - Éditeur visuel basé sur navigateur pour le contenu web AR, WebXR et 3D, construit sur three.js avec aperçu en temps réel et moteur physique.
        • +
        + +
        +
        +
        + + + + + + + + \ No newline at end of file diff --git a/manual/fr/lights.html b/manual/fr/lights.html index 0ecfcf108133b7..d0d3c37ecfa56f 100644 --- a/manual/fr/lights.html +++ b/manual/fr/lights.html @@ -1,20 +1,16 @@ - Lumières en + Lumières - + - - - - - + diff --git a/manual/fr/load-gltf.html b/manual/fr/load-gltf.html index 07c2061a27041a..fc6661e871e17e 100644 --- a/manual/fr/load-gltf.html +++ b/manual/fr/load-gltf.html @@ -1,20 +1,16 @@ - Loading a .GLTF File + Chargement d'un fichier .GLTF - + - - - - diff --git a/manual/fr/load-obj.html b/manual/fr/load-obj.html index d29b8d74e9d407..f068e25dd2aaa7 100644 --- a/manual/fr/load-obj.html +++ b/manual/fr/load-obj.html @@ -1,20 +1,16 @@ - Loading a .OBJ File + Charger un fichier .OBJ - + - - - - diff --git a/manual/fr/loading-3d-models.html b/manual/fr/loading-3d-models.html new file mode 100644 index 00000000000000..a62e64f272ac94 --- /dev/null +++ b/manual/fr/loading-3d-models.html @@ -0,0 +1,160 @@ + + + Chargement de modèles 3D + + + + + + + + + + + + + +
        +
        +

        Chargement de modèles 3D

        +
        +
        +
        + +

        + Les modèles 3D sont disponibles dans des centaines de formats de fichiers, chacun ayant des objectifs différents, des fonctionnalités variées et une complexité variable. Bien que + + three.js propose de nombreux chargeurs, choisir le bon format et le bon flux de travail vous fera gagner du temps et vous évitera des frustrations plus tard. Certains formats sont difficiles à manipuler, inefficaces pour les expériences en temps réel, ou simplement pas entièrement pris en charge à l'heure actuelle. +

        + +

        + Ce guide propose un flux de travail recommandé pour la plupart des utilisateurs, ainsi que des suggestions sur ce qu'il faut essayer si les choses ne se passent pas comme prévu. +

        + +

        Avant de commencer

        + +

        + Si vous débutez avec l'exécution d'un serveur local, commencez par + Installation + d'abord. De nombreuses erreurs courantes lors de la visualisation de modèles 3D peuvent être évitées en hébergeant correctement les fichiers. +

        + +

        Flux de travail recommandé

        + +

        + Dans la mesure du possible, nous recommandons l'utilisation de glTF (GL Transmission Format). Les versions + .GLB et .GLTF du format sont bien prises en charge. Étant donné que glTF est axé sur la diffusion d'assets en temps réel, il est compact à transmettre et rapide à charger. Les fonctionnalités incluent les maillages, les matériaux, les textures, les peaux, les squelettes, les cibles de déformation (morph targets), les animations, les lumières et les caméras. +

        + +

        + Des fichiers glTF du domaine public sont disponibles sur des sites comme + + Sketchfab, ou divers outils incluent l'exportation glTF : +

        + + + +

        + Si vos outils préférés ne prennent pas en charge glTF, envisagez de demander l'exportation glTF aux auteurs, ou de poster sur + le fil de discussion de la feuille de route glTF. +

        + +

        + Lorsque glTF n'est pas une option, des formats populaires tels que FBX, OBJ ou COLLADA sont également disponibles et régulièrement mis à jour. +

        + +

        Chargement

        + +

        + Seuls quelques chargeurs (par exemple `ObjectLoader`) sont inclus par défaut avec three.js — les autres doivent être ajoutés individuellement à votre application. +

        + +
        +import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
        +
        + +

        + Une fois qu'un chargeur a été importé, vous êtes prêt à ajouter un modèle à votre scène. La syntaxe varie selon les différents chargeurs — lorsque vous utilisez un autre format, consultez les exemples et la documentation de ce chargeur. Pour glTF, l'utilisation avec des scripts globaux serait : +

        + +
        +const loader = new GLTFLoader();
        +
        +loader.load( 'path/to/model.glb', function ( gltf ) {
        +
        +  scene.add( gltf.scene );
        +
        +}, undefined, function ( error ) {
        +
        +  console.error( error );
        +
        +} );
        +
        + +

        Dépannage

        + +

        + Vous avez passé des heures à modéliser un chef-d'œuvre artisanal, vous le chargez dans la page web, et — oh non ! 😭 Il est déformé, mal coloré, ou manque entièrement. Commencez par ces étapes de dépannage : +

        + +
          +
        1. + Vérifiez la console JavaScript pour les erreurs, et assurez-vous d'avoir utilisé une fonction de rappel `onError` lors de l'appel à `.load()` pour journaliser le résultat. +
        2. +
        3. + Visualisez le modèle dans une autre application. Pour glTF, des visionneuses par glisser-déposer sont disponibles pour + three.js et + babylon.js. Si le modèle + apparaît correctement dans une ou plusieurs applications, + signalez un bug à three.js. + Si le modèle ne peut être affiché dans aucune application, nous vous encourageons fortement à signaler un bug à l'application utilisée pour créer le modèle. +
        4. +
        5. + Essayez de mettre à l'échelle le modèle vers le haut ou vers le bas par un facteur de 1000. De nombreux modèles sont mis à l'échelle différemment, et les grands modèles peuvent ne pas apparaître si la caméra est à l'intérieur du modèle. +
        6. +
        7. + Essayez d'ajouter et de positionner une source de lumière. Le modèle peut être caché dans l'obscurité. +
        8. +
        9. + Recherchez les demandes de texture échouées dans l'onglet réseau, comme + `"C:\\Path\To\Model\texture.jpg"`. Utilisez plutôt des chemins relatifs à votre + modèle, tels que `images/texture.jpg` — cela peut nécessiter d'éditer le fichier modèle dans un éditeur de texte. +
        10. +
        + +

        Demander de l'aide

        + +

        + Si vous avez suivi le processus de dépannage ci-dessus et que votre modèle ne fonctionne toujours pas, la bonne approche pour demander de l'aide vous permettra d'obtenir une solution plus rapidement. Posez une question sur le + forum three.js et, dans la mesure du possible, + incluez votre modèle (ou un modèle plus simple présentant le même problème) dans tous les formats dont vous disposez. Incluez suffisamment d'informations pour que quelqu'un d'autre puisse reproduire le problème rapidement — idéalement, une démo en direct. +

        + +
        +
        +
        + + + + + + + + \ No newline at end of file diff --git a/manual/fr/material-table.html b/manual/fr/material-table.html index afb3722edab305..9287f8ca14a743 100644 --- a/manual/fr/material-table.html +++ b/manual/fr/material-table.html @@ -1,20 +1,16 @@ - Material Feature Table + Tableau des fonctionnalités des matériaux - + - - - - + + + - + diff --git a/manual/fr/materials.html b/manual/fr/materials.html index acdf74a46db693..770216a9b6258a 100644 --- a/manual/fr/materials.html +++ b/manual/fr/materials.html @@ -1,6 +1,6 @@ - Les matériaux + Matériaux @@ -11,10 +11,6 @@ - - - - + + +
        +
        +

        Transformations matricielles

        +
        +
        +
        + +

        + Three.js utilise des `matrices` pour encoder les transformations 3D : translations (position), rotations et mises à l'échelle. Chaque instance d'`Object3D` possède une `matrix` qui stocke la position, la rotation et l'échelle de cet objet. Cette page décrit comment mettre à jour la transformation d'un objet. +

        + +

        Propriétés de commodité et `matrixAutoUpdate`

        + +

        + Il existe deux manières de mettre à jour la transformation d'un objet : +

        +
          +
        1. + Modifiez les propriétés `position`, `quaternion` et `scale` de l'objet, et laissez three.js recalculer la matrice de l'objet à partir de ces propriétés : +
          +object.position.copy( start_position );
          +object.quaternion.copy( quaternion );
          +
          + Par défaut, la propriété `matrixAutoUpdate` est définie sur true, et la matrice sera automatiquement recalculée. + Si l'objet est statique, ou si vous souhaitez contrôler manuellement le moment où le recalcul se produit, de meilleures performances peuvent être obtenues en définissant la propriété sur false : +
          +object.matrixAutoUpdate = false;
          +
          + Et après avoir modifié une propriété, mettez à jour la matrice manuellement : +
          +object.updateMatrix();
          +
          +
        2. +
        3. + Modifiez la matrice de l'objet directement. La classe `Matrix4` dispose de différentes méthodes pour modifier la matrice : +
          +object.matrix.setRotationFromQuaternion( quaternion );
          +object.matrix.setPosition( start_position );
          +object.matrixAutoUpdate = false;
          +
          + Notez que `matrixAutoUpdate` doit être défini sur `false` dans ce cas, et vous devez vous assurer de ne pas appeler `updateMatrix`. Appeler `updateMatrix` écrasera les modifications manuelles apportées à la matrice, en recalculant la matrice à partir de `position`, `scale`, etc. +
        4. +
        + +

        Matrices de l'objet et du monde

        +

        + La matrice d'un objet stocke la transformation de l'objet par rapport à son parent ; pour obtenir la transformation de l'objet en coordonnées du monde, vous devez accéder à la matrice du monde de l'objet. +

        +

        + Lorsque la transformation du parent ou de l'enfant change, vous pouvez demander la mise à jour de la matrice du monde de l'objet enfant en appelant `object.updateMatrixWorld()`. +

        +

        + Un objet peut être transformé via `applyMatrix4()`. Note : En coulisses, cette méthode repose sur `Matrix4.decompose()`, et toutes les matrices ne sont pas décomposables de cette manière. Par exemple, si un objet a un parent avec une mise à l'échelle non uniforme, la matrice du monde de l'objet peut ne pas être décomposable, et cette méthode pourrait ne pas être appropriée. +

        + +

        Rotation et Quaternion

        +

        + Three.js propose deux manières de représenter les rotations 3D : les angles d'Euler et les Quaternions, ainsi que des méthodes pour convertir entre les deux. Les angles d'Euler sont sujets à un problème appelé « verrouillage de cardan » (gimbal lock), où certaines configurations peuvent perdre un degré de liberté (empêchant l'objet de tourner autour d'un axe). Pour cette raison, les rotations d'objets sont toujours stockées dans le quaternion de l'objet. +

        +

        + Les versions précédentes de la librairie incluaient une propriété `useQuaternion` qui, lorsqu'elle était définie sur false, entraînait le calcul de la matrice de l'objet à partir d'un angle d'Euler. Cette pratique est obsolète --- à la place, vous devriez utiliser la méthode `object.setRotationFromEuler()`, qui mettra à jour le quaternion. +

        + +
        +
        +
        + + + + + + + + \ No newline at end of file diff --git a/manual/fr/multiple-scenes.html b/manual/fr/multiple-scenes.html index b968ce87137baf..52f0284fde5a46 100644 --- a/manual/fr/multiple-scenes.html +++ b/manual/fr/multiple-scenes.html @@ -1,20 +1,16 @@ - Multiple Canvases Multiple Scenes + Plusieurs Canvases, Plusieurs Scènes - + - - - - diff --git a/manual/fr/offscreencanvas.html b/manual/fr/offscreencanvas.html index 19d437aa8cf353..51b0eae9d902d3 100644 --- a/manual/fr/offscreencanvas.html +++ b/manual/fr/offscreencanvas.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/fr/optimize-lots-of-objects-animated.html b/manual/fr/optimize-lots-of-objects-animated.html index 05f282c0ccaf2c..1742cb77df7b30 100644 --- a/manual/fr/optimize-lots-of-objects-animated.html +++ b/manual/fr/optimize-lots-of-objects-animated.html @@ -1,20 +1,16 @@ - Optimize Lots of Objects Animated + Optimiser de nombreux objets animés - + - - - - diff --git a/manual/fr/optimize-lots-of-objects.html b/manual/fr/optimize-lots-of-objects.html index 1ecdb7f73cdcfd..99a40b4e78bfcd 100644 --- a/manual/fr/optimize-lots-of-objects.html +++ b/manual/fr/optimize-lots-of-objects.html @@ -1,6 +1,6 @@ - Optimize Lots of Objects + Optimiser Beaucoup d'Objets @@ -11,10 +11,6 @@ - - - - - + diff --git a/manual/fr/picking.html b/manual/fr/picking.html index 3cada737115e8b..eef73d366dc3fc 100644 --- a/manual/fr/picking.html +++ b/manual/fr/picking.html @@ -1,20 +1,16 @@ - Picking + Sélection - + - - - - diff --git a/manual/fr/post-processing-3dlut.html b/manual/fr/post-processing-3dlut.html deleted file mode 100644 index 13fc760d136889..00000000000000 --- a/manual/fr/post-processing-3dlut.html +++ /dev/null @@ -1,46 +0,0 @@ - - - Post Processing 3DLUT - - - - - - - - - - - - - - - - - -
        -
        -

        Post Processing 3DLUT

        -
        -
        -
        -

        Désolé, cet article n'a pas encore été traduit. Les traductions sont le bienvenue! 😄

        -

        Voici l'article anglais originel pour le moment.

        - -
        -
        -
        - - - - - - - - \ No newline at end of file diff --git a/manual/fr/post-processing.html b/manual/fr/post-processing.html index cdcb570ee57ec0..c422e49e085578 100644 --- a/manual/fr/post-processing.html +++ b/manual/fr/post-processing.html @@ -1,20 +1,16 @@ - Post Processing + Post-traitement - + - - - - diff --git a/manual/fr/prerequisites.html b/manual/fr/prerequisites.html index 882d880215ef87..6a8911cdcfaf8e 100644 --- a/manual/fr/prerequisites.html +++ b/manual/fr/prerequisites.html @@ -1,20 +1,16 @@ - Pré-requis pour + Conditions préalables - + - - - - diff --git a/manual/fr/primitives.html b/manual/fr/primitives.html index bc34e0547f41d1..1e52461727b0f1 100644 --- a/manual/fr/primitives.html +++ b/manual/fr/primitives.html @@ -1,20 +1,16 @@ - Primitives de + Primitives - + - - - - + - + diff --git a/manual/fr/rendering-on-demand.html b/manual/fr/rendering-on-demand.html index d06afe29925851..72bfd38777e967 100644 --- a/manual/fr/rendering-on-demand.html +++ b/manual/fr/rendering-on-demand.html @@ -1,6 +1,6 @@ - Rendering on Demand + Rendu à la demande @@ -11,10 +11,6 @@ - - - - diff --git a/manual/fr/rendertargets.html b/manual/fr/rendertargets.html index e4ad8a9fc3aefd..dc8fde818ee9c4 100644 --- a/manual/fr/rendertargets.html +++ b/manual/fr/rendertargets.html @@ -1,20 +1,16 @@ - Render Targets + Cibles de rendu - + - - - - diff --git a/manual/fr/responsive.html b/manual/fr/responsive.html index e77402e6dff6fe..1d76fee24e8432 100644 --- a/manual/fr/responsive.html +++ b/manual/fr/responsive.html @@ -1,20 +1,16 @@ - Design réactif et + Conception Réactive - + - - - - diff --git a/manual/fr/scenegraph.html b/manual/fr/scenegraph.html index 69ec859d16e971..3f169c54201ebb 100644 --- a/manual/fr/scenegraph.html +++ b/manual/fr/scenegraph.html @@ -11,10 +11,6 @@ - - - - - diff --git a/manual/fr/shadertoy.html b/manual/fr/shadertoy.html index f54bcce891695b..f7a6d74eaa053a 100644 --- a/manual/fr/shadertoy.html +++ b/manual/fr/shadertoy.html @@ -1,20 +1,16 @@ - Three.js and Shadertoy + Three.js et Shadertoy - + - - - - diff --git a/manual/fr/shadows.html b/manual/fr/shadows.html index 80dd101194946b..a1d75a9e98d8d2 100644 --- a/manual/fr/shadows.html +++ b/manual/fr/shadows.html @@ -1,20 +1,16 @@ - Les ombres dans + Ombres - + - - - - - + \ No newline at end of file diff --git a/manual/fr/textures.html b/manual/fr/textures.html index dde1982e82c554..a8b2c1da837e78 100644 --- a/manual/fr/textures.html +++ b/manual/fr/textures.html @@ -1,20 +1,16 @@ - Les textures dans + Textures - + - - - - - + diff --git a/manual/fr/tips.html b/manual/fr/tips.html index da85c83cef5a0c..5f025d7f6cca1c 100644 --- a/manual/fr/tips.html +++ b/manual/fr/tips.html @@ -1,6 +1,6 @@ - Tips + Conseils @@ -11,10 +11,6 @@ - - - - diff --git a/manual/fr/transparency.html b/manual/fr/transparency.html index ee53c6f8c73e20..ff4420a9f94ae0 100644 --- a/manual/fr/transparency.html +++ b/manual/fr/transparency.html @@ -1,20 +1,16 @@ - Transparency + Transparence - + - - - - diff --git a/manual/fr/uniform-types.html b/manual/fr/uniform-types.html new file mode 100644 index 00000000000000..1f511b7abb58c9 --- /dev/null +++ b/manual/fr/uniform-types.html @@ -0,0 +1,254 @@ + + + Types d'uniformes + + + + + + + + + + + + + +
        +
        +

        Types d'uniformes

        +
        +
        +
        + +

        + Chaque uniforme doit avoir une propriété `value`. Le type de la valeur doit + correspondre au type de la variable uniforme dans le code GLSL tel que + spécifié pour les types GLSL primitifs dans le tableau ci-dessous. Les structures et + tableaux d'uniformes sont également pris en charge. Les tableaux GLSL de type primitif + doivent être spécifiés soit comme un tableau des objets THREE correspondants, soit + comme un tableau plat contenant les données de tous les objets. En d'autres termes, + les primitives GLSL dans les tableaux ne doivent pas être représentées par des tableaux. Cette règle + ne s'applique pas de manière transitive. Un tableau de tableaux `vec2`, chacun d'une longueur + de cinq vecteurs, doit être un tableau de tableaux, soit de cinq objets `Vector2`, + soit de dix `number`s. +

        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        GLSL typeJavaScript type
        intNumber
        uintNumber
        floatNumber
        boolBoolean
        boolNumber
        vec2Vector2
        vec2Float32Array (*)
        vec2Array (*)
        vec3Vector3
        vec3Color
        vec3Float32Array (*)
        vec3Array (*)
        vec4Vector4
        vec4Quaternion
        vec4Float32Array (*)
        vec4Array (*)
        mat2Float32Array (*)
        mat2Array (*)
        mat3Matrix3
        mat3Float32Array (*)
        mat3Array (*)
        mat4Matrix4
        mat4Float32Array (*)
        mat4Array (*)
        ivec2, bvec2Float32Array (*)
        ivec2, bvec2Array (*)
        ivec3, bvec3Int32Array (*)
        ivec3, bvec3Array (*)
        ivec4, bvec4Int32Array (*)
        ivec4, bvec4Array (*)
        sampler2DTexture
        samplerCubeCubeTexture
        + +

        + (*) De même pour un tableau (le plus interne) (dimension) du même type GLSL, + contenant les composants de tous les vecteurs ou matrices du tableau. +

        + +

        Uniforms structurés

        + +

        + Parfois, vous voulez organiser les uniformes en tant que `structs` dans votre code de shader. + Le style suivant doit être utilisé pour que `three.js` puisse traiter + les données d'uniformes structurées. +

        +
        +uniforms = {
        +  data: { 
        +    value: {
        +      position: new Vector3(), 
        +      direction: new Vector3( 0, 0, 1 ) 
        +    } 
        +  } 
        +};
        +
        + Cette définition peut être mappée sur le code GLSL suivant : +
        +struct Data { 
        +  vec3 position;
        +  vec3 direction;
        +};
        +uniform Data data;
        +
        + +

        Uniforms structurés avec tableaux

        + +

        + Il est également possible de gérer des `structs` dans des tableaux. La syntaxe pour ce cas d'utilisation + est la suivante : +

        +
        +const entry1 = {
        +  position: new Vector3(),
        +  direction: new Vector3( 0, 0, 1 )
        +};
        +const entry2 = {
        +  position: new Vector3( 1, 1, 1 ),
        +  direction: new Vector3( 0, 1, 0 )
        +};
        +
        +uniforms = {
        +  data: {
        +    value: [ entry1, entry2 ]
        +  }
        +};
        +
        + Cette définition peut être mappée sur le code GLSL suivant : +
        +struct Data { 
        +  vec3 position; 
        +  vec3 direction; 
        +};
        +uniform Data data[ 2 ];
        +
        + +
        +
        +
        + + + + + + + + \ No newline at end of file diff --git a/manual/fr/useful-links.html b/manual/fr/useful-links.html new file mode 100644 index 00000000000000..61ea23177180f2 --- /dev/null +++ b/manual/fr/useful-links.html @@ -0,0 +1,193 @@ + + + Liens utiles + + + + + + + + + + + + + +
        +
        +

        Liens utiles

        +
        +
        +
        + +

        + Voici une collection de liens qui pourraient vous être utiles pour apprendre three.js.
        + Si vous trouvez quelque chose que vous aimeriez ajouter ici, ou si vous pensez que l'un des liens ci-dessous n'est plus pertinent ou ne fonctionne plus, n'hésitez pas à cliquer sur le bouton 'edit' en bas à droite et à apporter des modifications !

        + + Notez également qu'étant donné que three.js est en développement rapide, de nombreux liens contiendront des informations obsolètes. Si quelque chose ne fonctionne pas comme prévu ou comme l'indique l'un de ces liens, vérifiez la console du navigateur pour les avertissements ou les erreurs. Consultez également les pages de documentation pertinentes. +

        + +

        Forums d'aide

        +

        + Three.js utilise officiellement le [link:https://discourse.threejs.org/ forum] et [link:http://stackoverflow.com/tags/three.js/info Stack Overflow] pour les demandes d'aide. + Si vous avez besoin d'aide pour quelque chose, c'est l'endroit où aller. NE PAS ouvrir de problème sur Github pour les demandes d'aide. +

        + +

        Tutoriels et cours

        + +

        Pour commencer avec three.js

        +
          +
        • + [link:https://threejs.org/manual/#en/fundamentals Leçon d'introduction aux Fondamentaux de Three.js] +
        • +
        • + [link:https://codepen.io/rachsmith/post/beginning-with-3d-webgl-pt-1-the-scene Pour commencer avec la 3D WebGL] par [link:https://codepen.io/rachsmith/ Rachel Smith]. +
        • +
        • + [link:https://www.august.com.au/blog/animating-scenes-with-webgl-three-js/ Animer des scènes avec WebGL et three.js] +
        • +
        + +

        Articles et cours plus approfondis / avancés

        +
          +
        • + [link:https://threejs-journey.com/ Three Journey] Cours par [link:https://bruno-simon.com/ Bruno Simon] - Apprend aux débutants à utiliser Three.js étape par étape +
        • +
        • + [link:https://discoverthreejs.com/ Découvrir three.js] +
        • +
        • + [link:http://blog.cjgammon.com/ Collection de tutoriels] par [link:http://www.cjgammon.com/ CJ Gammon]. +
        • +
        • + [link:https://medium.com/soffritti.pierfrancesco/glossy-spheres-in-three-js-bfd2785d4857 Sphères brillantes dans three.js]. +
        • +
        • + [link:https://www.udacity.com/course/interactive-3d-graphics--cs291 Graphismes 3D Interactifs] - un cours gratuit sur Udacity qui enseigne les fondamentaux des graphismes 3D et utilise three.js comme outil de codage. +
        • +
        • + [Link:https://aerotwist.com/tutorials/ Aerotwist] tutoriels par [link:https://github.com/paullewis/ Paul Lewis]. +
        • +
        • + [link:https://discourse.threejs.org/t/three-js-bookshelf/2468 Étagère à livres Three.js] - Vous cherchez plus de ressources sur three.js ou les graphismes par ordinateur en général ? Consultez la sélection de littérature recommandée par la communauté. +
        • +
        + +

        Nouvelles et mises à jour

        +
          +
        • + [link:https://twitter.com/hashtag/threejs Three.js sur Twitter] +
        • +
        • + [link:http://www.reddit.com/r/threejs/ Three.js sur reddit] +
        • +
        • + [link:http://www.reddit.com/r/webgl/ WebGL sur reddit] +
        • +
        + +

        Exemples

        +
          +
        • + [link:https://github.com/edwinwebb/three-seed/ three-seed] - projet de démarrage three.js avec ES6 et Webpack +
        • +
        • + [link:http://stemkoski.github.io/Three.js/index.html Exemples du Professeur Stemkoski] - une collection d'exemples adaptés aux débutants construits à l'aide de three.js r60. +
        • +
        • + [link:https://threejs.org/examples/ Exemples officiels de three.js] - ces exemples sont maintenus dans le cadre du dépôt three.js et utilisent toujours la dernière version de three.js. +
        • +
        • + [link:https://raw.githack.com/mrdoob/three.js/dev/examples/ Exemples officiels de la branche de développement three.js] - Identiques aux exemples ci-dessus, sauf qu'ils utilisent la branche de développement de three.js et sont utilisés pour vérifier que tout fonctionne correctement pendant le développement de three.js. +
        • +
        + +

        Outils

        +
          +
        • + [link:https://github.com/tbensky/physgl physgl.org] - Interface front-end JavaScript avec des wrappers pour three.js, pour apporter les graphismes WebGL aux étudiants apprenant la physique et les mathématiques. +
        • +
        • + [link:https://whsjs.readme.io/ Whitestorm.js] – Framework three.js modulaire avec plugin physique AmmoNext. +
        • +
        • + [link:http://zz85.github.io/zz85-bookmarklets/threelabs.html Inspecteur Three.js] +
        • +
        • + [link:http://idflood.github.io/ThreeNodes.js/ ThreeNodes.js]. +
        • +
        • + [link:https://marketplace.visualstudio.com/items?itemName=slevesque.shader vscode shader] - Colorateur syntaxique pour le langage de shader. +
          + [link:https://marketplace.visualstudio.com/items?itemName=bierner.comment-tagged-templates vscode comment-tagged-templates] - Coloration syntaxique pour les chaînes de gabarit marquées utilisant des commentaires pour le langage de shader, comme : glsl.js. +
        • +
        • + [link:https://github.com/MozillaReality/WebXR-emulator-extension WebXR-emulator-extension] +
        • +
        + +

        Références WebGL

        +
          +
        • + [link:https://www.khronos.org/files/webgl/webgl-reference-card-1_0.pdf webgl-reference-card.pdf] - Référence de tous les mots-clés, terminologie, syntaxe et définitions de WebGL et GLSL. +
        • +
        + +

        Anciens liens

        +

        + Ces liens sont conservés à des fins historiques - vous pouvez toujours les trouver utiles, mais sachez qu'ils peuvent contenir des informations relatives à de très anciennes versions de three.js. +

        + +
          +
        • + [link:https://www.youtube.com/watch?v=Dir4KO9RdhM AlterQualia at WebGL Camp 3] +
        • +
        • + [link:http://yomotsu.github.io/threejs-examples/ Yomotsus Examples] - une collection d'exemples utilisant three.js r45. +
        • +
        • + [link:http://fhtr.org/BasicsOfThreeJS/#1 Introduction à Three.js] par [link:http://github.com/kig/ Ilmari Heikkinen] (diaporama). +
        • +
        • + [link:http://www.slideshare.net/yomotsu/webgl-and-threejs WebGL and Three.js] par [link:http://github.com/yomotsu Akihiro Oyamada] (diaporama). +
        • +
        • + [link:https://www.youtube.com/watch?v=VdQnOaolrPA Trigger Rally] par [link:https://github.com/jareiko jareiko] (vidéo). +
        • +
        • + [link:http://blackjk3.github.io/threefab/ ThreeFab] - éditeur de scène, maintenu jusqu'à environ three.js r50. +
        • +
        • + [link:http://bkcore.com/blog/3d/webgl-three-js-workflow-tips.html Max to Three.js workflow tips and tricks] par [link:https://github.com/BKcore BKcore] +
        • +
        • + [link:http://12devsofxmas.co.uk/2012/01/webgl-and-three-js/ Un aperçu rapide de Three.js] + par [link:http://github.com/nrocy Paul King] +
        • +
        • + [link:http://bkcore.com/blog/3d/webgl-three-js-animated-selective-glow.html Lueur sélective animée dans Three.js] + par [link:https://github.com/BKcore BKcore] +
        • +
        • + [link:http://www.natural-science.or.jp/article/20120220155529.php Building A Physics Simulation Environment] - tutoriel three.js en japonais +
        • +
        + +
        +
        +
        + + + + + + + + \ No newline at end of file diff --git a/manual/fr/voxel-geometry.html b/manual/fr/voxel-geometry.html index 7e437680c02b1e..15dee6fdea8e02 100644 --- a/manual/fr/voxel-geometry.html +++ b/manual/fr/voxel-geometry.html @@ -1,20 +1,16 @@ - Voxel(Minecraft Like) Geometry + Géométrie Voxel (type Minecraft) - + - - - - + - + diff --git a/manual/fr/webgl-compatibility-check.html b/manual/fr/webgl-compatibility-check.html new file mode 100644 index 00000000000000..fcf1ea33abe92d --- /dev/null +++ b/manual/fr/webgl-compatibility-check.html @@ -0,0 +1,62 @@ + + + Vérification de la compatibilité WebGL + + + + + + + + + + + + + +
        +
        +

        Vérification de la compatibilité WebGL

        +
        +
        +
        + +

        + Bien que cela devienne de moins en moins un problème, certains appareils ou navigateurs peuvent encore ne pas prendre en charge WebGL 2. + La méthode suivante vous permet de vérifier s'il est pris en charge et d'afficher un message à l'utilisateur si ce n'est pas le cas. + Importez le module de détection de la prise en charge WebGL et exécutez le code suivant avant de tenter de rendre quoi que ce soit. +

        + +
        +import WebGL from 'three/addons/capabilities/WebGL.js';
        +
        +if ( WebGL.isWebGL2Available() ) {
        +
        +  // Initialisez la fonction ou d'autres initialisations ici
        +  animate();
        +
        +} else {
        +
        +  const warning = WebGL.getWebGL2ErrorMessage();
        +  document.getElementById( 'container' ).appendChild( warning );
        +
        +}
        +
        + +
        +
        +
        + + + + + + + + \ No newline at end of file diff --git a/manual/fr/webxr-basics.html b/manual/fr/webxr-basics.html index 3e0e3f545259ef..ab8d3d186253f1 100644 --- a/manual/fr/webxr-basics.html +++ b/manual/fr/webxr-basics.html @@ -1,6 +1,6 @@ - VR + RV @@ -11,10 +11,6 @@ - - - - diff --git a/manual/fr/webxr-look-to-select.html b/manual/fr/webxr-look-to-select.html index 78ba6ead0ee4b0..bf60125decee3f 100644 --- a/manual/fr/webxr-look-to-select.html +++ b/manual/fr/webxr-look-to-select.html @@ -1,6 +1,6 @@ - VR - Look to Select + VR - Sélection par le regard @@ -11,10 +11,6 @@ - - - - diff --git a/manual/fr/webxr-point-to-select.html b/manual/fr/webxr-point-to-select.html index 5ae1b3792252ac..87dd35db187cb9 100644 --- a/manual/fr/webxr-point-to-select.html +++ b/manual/fr/webxr-point-to-select.html @@ -1,20 +1,16 @@ - VR - 3DOF Point to Select + VR - Sélection par Pointage 3DOF - + - - - - diff --git a/manual/index.html b/manual/index.html index 67bbd8f566d476..41df10c246faf8 100644 --- a/manual/index.html +++ b/manual/index.html @@ -20,6 +20,7 @@

        three.js

        + docs manual
        @@ -231,6 +232,14 @@

        three.js

        // Create categories + if ( category === '---' ) { + + const separator = document.createElement( 'hr' ); + navigation.appendChild( separator ); + continue; + + } + const pages = categories[ category ]; const categoryContainer = document.createElement( 'div' ); diff --git a/manual/ja/align-html-elements-to-3d.html b/manual/ja/align-html-elements-to-3d.html index d533cb518d9718..c8996396bd26ff 100644 --- a/manual/ja/align-html-elements-to-3d.html +++ b/manual/ja/align-html-elements-to-3d.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ja/backgrounds.html b/manual/ja/backgrounds.html index b46bba35bc7cb5..0e6edccd22aca3 100644 --- a/manual/ja/backgrounds.html +++ b/manual/ja/backgrounds.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ja/billboards.html b/manual/ja/billboards.html index f589ea5ac4ef74..b41ed3d48d3740 100644 --- a/manual/ja/billboards.html +++ b/manual/ja/billboards.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ja/cameras.html b/manual/ja/cameras.html index 29f6ba13f8aa10..7ac3174586b1e6 100644 --- a/manual/ja/cameras.html +++ b/manual/ja/cameras.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ja/canvas-textures.html b/manual/ja/canvas-textures.html index 21c9a861d50771..4aa9ebe26129c3 100644 --- a/manual/ja/canvas-textures.html +++ b/manual/ja/canvas-textures.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ja/cleanup.html b/manual/ja/cleanup.html index 7c63332c7099f6..a33a034b832147 100644 --- a/manual/ja/cleanup.html +++ b/manual/ja/cleanup.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ja/custom-buffergeometry.html b/manual/ja/custom-buffergeometry.html index 130380f258d0a6..f2e79cc218f657 100644 --- a/manual/ja/custom-buffergeometry.html +++ b/manual/ja/custom-buffergeometry.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ja/debugging-glsl.html b/manual/ja/debugging-glsl.html index 00f11ec98002ca..c06028299876c4 100644 --- a/manual/ja/debugging-glsl.html +++ b/manual/ja/debugging-glsl.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ja/debugging-javascript.html b/manual/ja/debugging-javascript.html index 4000ec7a8ac4c7..23394930d3f80b 100644 --- a/manual/ja/debugging-javascript.html +++ b/manual/ja/debugging-javascript.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ja/fog.html b/manual/ja/fog.html index c6fead071cbad6..2d9180f59d37d6 100644 --- a/manual/ja/fog.html +++ b/manual/ja/fog.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ja/fundamentals.html b/manual/ja/fundamentals.html index 09cfa24a00a0bb..3dc8eeea5e3dce 100644 --- a/manual/ja/fundamentals.html +++ b/manual/ja/fundamentals.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ja/game.html b/manual/ja/game.html index ad0fa550f2f3e1..26ed2f5d00b2da 100644 --- a/manual/ja/game.html +++ b/manual/ja/game.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ja/indexed-textures.html b/manual/ja/indexed-textures.html index 2927238e2c2f47..b60f50974fb769 100644 --- a/manual/ja/indexed-textures.html +++ b/manual/ja/indexed-textures.html @@ -11,10 +11,6 @@ - - - - - diff --git a/manual/ja/load-gltf.html b/manual/ja/load-gltf.html index 0ad843a9f71998..7dd15962a0d1ac 100644 --- a/manual/ja/load-gltf.html +++ b/manual/ja/load-gltf.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ja/load-obj.html b/manual/ja/load-obj.html index 421b628c1d96d6..7a14e6455a244d 100644 --- a/manual/ja/load-obj.html +++ b/manual/ja/load-obj.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ja/material-table.html b/manual/ja/material-table.html index 56cf3a8bd1f06f..bcc345b7d71868 100644 --- a/manual/ja/material-table.html +++ b/manual/ja/material-table.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ja/materials.html b/manual/ja/materials.html index c0d224984c2269..1068b80fc7bdf5 100644 --- a/manual/ja/materials.html +++ b/manual/ja/materials.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ja/multiple-scenes.html b/manual/ja/multiple-scenes.html index 0c4c159605606a..3ad88efc53720b 100644 --- a/manual/ja/multiple-scenes.html +++ b/manual/ja/multiple-scenes.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ja/offscreencanvas.html b/manual/ja/offscreencanvas.html index 6f2dd7966025b6..cd1d363e2276d2 100644 --- a/manual/ja/offscreencanvas.html +++ b/manual/ja/offscreencanvas.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ja/optimize-lots-of-objects-animated.html b/manual/ja/optimize-lots-of-objects-animated.html index 016cc07bf84d61..d8f7a490d29a7e 100644 --- a/manual/ja/optimize-lots-of-objects-animated.html +++ b/manual/ja/optimize-lots-of-objects-animated.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ja/optimize-lots-of-objects.html b/manual/ja/optimize-lots-of-objects.html index d49f36b6c7158a..6f64fb6046286e 100644 --- a/manual/ja/optimize-lots-of-objects.html +++ b/manual/ja/optimize-lots-of-objects.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ja/picking.html b/manual/ja/picking.html index 61ad6275f5dde9..e329a1a038262b 100644 --- a/manual/ja/picking.html +++ b/manual/ja/picking.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ja/post-processing-3dlut.html b/manual/ja/post-processing-3dlut.html deleted file mode 100644 index 029d0789b94ab7..00000000000000 --- a/manual/ja/post-processing-3dlut.html +++ /dev/null @@ -1,492 +0,0 @@ - - - の3DLUTポストプロセス - - - - - - - - - - - - - - - - - -
        -
        -

        の3DLUTポストプロセス

        -
        -
        -
        -

        前回の記事ではポストプロセスの説明をしました。 -ポストプロセスの一般的な方法の1つにLUT(ラット)や3DLUT(3次元ラット)と呼ばれるものがあります。 -LUTはルックアップテーブル(参照対応表)の略です。したがって、3DLUTは3次元のルックアップテーブルです。

        -

        3DLUTがどのように機能するかというとカラーのキューブを作ります。 -元となる画像のカラーを使い、キューブにインデックスを作成します。 -元画像の各ピクセルに対して、赤、緑、青のカラーに基づいてキューブの位置を調べます。 -キューブの位置が3DLUTから引き出した新しいカラーとなります。

        -

        Javascriptでは次のようにします。 -カラーは0〜255までの整数で指定されており、サイズが256 x 256 x 256の大きな3次元配列があると想像して下さい。 -ルックアップテーブルを通してカラーを変換します。

        -
        const newColor = lut[origColor.red][origColor.green][origColor.bue]
        -

        もちろん、256 x 256 x 256の配列はかなり大きいですが、テクスチャの記事で指摘したようにテクスチャの寸法に関係なく0.0~1.0の値を参照します。

        -

        8 × 8 × 8のキューブを想像してみましょう。

        -
        - -

        最初に0, 0, 0の位置の角は黒にし、反対の1, 1, 1の角は白にします。 -1, 0, 0はです。 -0, 1, 0はで0, 0, 1はにします。

        -
        - -

        各軸線にカラーを追加していきます。

        -
        - -

        2チャンネル以上を使用するエッジのカラーです。

        -
        - -

        最後に中間にあるカラーも全て埋めます。 -これは"同一性"の3DLUTです。入力と全く同じ出力を生成します。 -もし色を入力して調べれば、入力と同じカラーが出力されます。

        -
        - -

        キューブをシェーダーで琥珀色に変更し3Dルックアップテーブルの同じ場所を調べると、異なる出力が得られます。

        -
        - -

        別のルックアップテーブルを提供してこの技術を使用すると、全種類の効果を適用できます。 -基本的には単一のカラー入力のみを計算できる効果です。 -これらの効果には色相、コントラスト、彩度、カラーキャスト、色合い、明るさ、露出、レベル、カーブ、ポスタライズ、シャドウ、ハイライト、その他多くの調整が含まれます。 -これが優れている点は全て1つのルックアップテーブルにまとめられてます。

        -

        これを使用するには適用するシーンが必要です。 -ちょっとしたシーンにこれを適用してみましょう。 -まずはglTFを読み込む記事で取り上げたようにglTFファイルを表示する所から始めてみます。 -載せているモデルは氷の狼このモデルです。 -ライトは使わないので削除しました。

        -

        背景とスカイボックスで説明したような背景画像も追加します。

        -

        - -

        -

        シーンがあるので3DLUTが必要です。 -最も単純な3DLUTは2 x 2 x 2の同一性LUTです。同一性とは何も起こらない事を意味します。 -1を掛けるようなもので、LUTでカラーを調べているにも関わらず、入力カラーと同じ出力カラーがマップされてます。

        -
        - -

        WebGL1は3Dテクスチャは非サポートのため、4 x 2の2Dテクスチャを使用しカスタムシェーダーの中で3Dテクスチャとして扱います。 -カスタムシェーダーではキューブの各切片がテクスチャ全体に水平に広がっています。

        -

        以下はidentityLUTに必要なカラーで4 x 2の2Dテクスチャを作るコードです。

        -
        const makeIdentityLutTexture = function() {
        -  const identityLUT = new Uint8Array([
        -      0,   0,   0, 255,  // black
        -    255,   0,   0, 255,  // red
        -      0,   0, 255, 255,  // blue
        -    255,   0, 255, 255,  // magenta
        -      0, 255,   0, 255,  // green
        -    255, 255,   0, 255,  // yellow
        -      0, 255, 255, 255,  // cyan
        -    255, 255, 255, 255,  // white
        -  ]);
        -
        -  return function(filter) {
        -    const texture = new THREE.DataTexture(identityLUT, 4, 2, THREE.RGBAFormat);
        -    texture.minFilter = filter;
        -    texture.magFilter = filter;
        -    texture.needsUpdate = true;
        -    texture.flipY = false;
        -    return texture;
        -  };
        -}();
        -
        -

        フィルターをかけたテクステャ、かけていないテクステャの2つを作ります。

        -
        const lutTextures = [
        -  { name: 'identity', size: 2, texture: makeIdentityLutTexture(THREE.LinearFilter) },
        -  { name: 'identity not filtered', size: 2, texture: makeIdentityLutTexture(THREE.NearestFilter) },
        -];
        -
        -

        ポストプロセスの記事のカスタムシェーダーを使った例を参考に、2つのカスタムシェーダーを使ってみましょう。

        -
        const lutShader = {
        -  uniforms: {
        -    tDiffuse: { value: null },
        -    lutMap:  { value: null },
        -    lutMapSize: { value: 1, },
        -  },
        -  vertexShader: `
        -    varying vec2 vUv;
        -    void main() {
        -      vUv = uv;
        -      gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
        -    }
        -  `,
        -  fragmentShader: `
        -    #include <common>
        -
        -    #define FILTER_LUT true
        -
        -    uniform sampler2D tDiffuse;
        -    uniform sampler2D lutMap;
        -    uniform float lutMapSize;
        -
        -    varying vec2 vUv;
        -
        -    vec4 sampleAs3DTexture(sampler2D tex, vec3 texCoord, float size) {
        -      float sliceSize = 1.0 / size;                  // space of 1 slice
        -      float slicePixelSize = sliceSize / size;       // space of 1 pixel
        -      float width = size - 1.0;
        -      float sliceInnerSize = slicePixelSize * width; // space of size pixels
        -      float zSlice0 = floor( texCoord.z * width);
        -      float zSlice1 = min( zSlice0 + 1.0, width);
        -      float xOffset = slicePixelSize * 0.5 + texCoord.x * sliceInnerSize;
        -      float yRange = (texCoord.y * width + 0.5) / size;
        -      float s0 = xOffset + (zSlice0 * sliceSize);
        -
        -      #ifdef FILTER_LUT
        -
        -        float s1 = xOffset + (zSlice1 * sliceSize);
        -        vec4 slice0Color = texture2D(tex, vec2(s0, yRange));
        -        vec4 slice1Color = texture2D(tex, vec2(s1, yRange));
        -        float zOffset = mod(texCoord.z * width, 1.0);
        -        return mix(slice0Color, slice1Color, zOffset);
        -
        -      #else
        -
        -        return texture2D(tex, vec2( s0, yRange));
        -
        -      #endif
        -    }
        -
        -    void main() {
        -      vec4 originalColor = texture2D(tDiffuse, vUv);
        -      gl_FragColor = sampleAs3DTexture(lutMap, originalColor.xyz, lutMapSize);
        -    }
        -  `,
        -};
        -
        -const lutNearestShader = {
        -  uniforms: {...lutShader.uniforms},
        -  vertexShader: lutShader.vertexShader,
        -  fragmentShader: lutShader.fragmentShader.replace('#define FILTER_LUT', '//'),
        -};
        -
        -

        フラグメントシェーダーの中に次のような行があるのが分かります。

        -
        #define FILTER_LUT true
        -
        -

        2番目のシェーダーを生成するためにその行をコメントアウトします。

        -

        これらを使用して2つのカスタムエフェクトを作成します。

        -
        const effectLUT = new THREE.ShaderPass(lutShader);
        -const effectLUTNearest = new THREE.ShaderPass(lutNearestShader);
        -
        -

        背景を別のシーンに描画する既存コードを変更し、glTFと背景を描画するシーンの両方に RenderPass を適用します。

        -
        const renderModel = new THREE.RenderPass(scene, camera);
        -renderModel.clear = false;  // so we don't clear out the background
        -const renderBG = new THREE.RenderPass(sceneBG, cameraBG);
        -
        -

        全てのパスを使用するように EffectComposer を設定できます。

        -
        const composer = new THREE.EffectComposer(renderer);
        -
        -composer.addPass(renderBG);
        -composer.addPass(renderModel);
        -composer.addPass(effectLUT);
        -composer.addPass(effectLUTNearest);
        -composer.addPass(gammaPass);
        -
        -

        LUTを選択するためのGUIコードを作ってみましょう。

        -
        const lutNameIndexMap = {};
        -lutTextures.forEach((info, ndx) => {
        -  lutNameIndexMap[info.name] = ndx;
        -});
        -
        -const lutSettings = {
        -  lut: lutNameIndexMap.identity,
        -};
        -const gui = new GUI({ width: 300 });
        -gui.add(lutSettings, 'lut', lutNameIndexMap);
        -
        -

        最後にfilterするかに応じてeffectをオンにし、選択したテクスチャを使用するようにeffectを設定して、EffectComposer を通してレンダリングします。

        -
        const lutInfo = lutTextures[lutSettings.lut];
        -
        -const effect = lutInfo.filter ? effectLUT : effectLUTNearest;
        -effectLUT.enabled = lutInfo.filter;
        -effectLUTNearest.enabled = !lutInfo.filter;
        -
        -const lutTexture = lutInfo.texture;
        -effect.uniforms.lutMap.value = lutTexture;
        -effect.uniforms.lutMapSize.value = lutInfo.size;
        -
        -composer.render(delta);
        -
        -

        同一性の3DLUTである事を考えると何も変わりません。

        -

        - -

        -

        しかし、GUIでidentity not filteredを選択すると興味深い結果になります。

        -
        - -

        なぜこのようなことが起こるのでしょうか? -filterをオンにするとGPUはカラーの中間を線形補間します。 -filterをオフにすると補間は行わなわれず、3DLUT内のカラーを探しても3DLUT内の正確なカラーの1つしか得られません。

        -

        もっと面白い3DLUTを作るにはどうすれば良いでしょうか?

        -

        まず必要なテーブルの解像度を決定し、簡単なスクリプトを使用しルックアップキューブの切片を生成します。

        -
        const ctx = document.querySelector('canvas').getContext('2d');
        -
        -function drawColorCubeImage(ctx, size) {
        -  const canvas = ctx.canvas;
        -  canvas.width = size * size;
        -  canvas.height = size;
        -
        -  for (let zz = 0; zz < size; ++zz) {
        -    for (let yy = 0; yy < size; ++yy) {
        -      for (let xx = 0; xx < size; ++xx) {
        -        const r = Math.floor(xx / (size - 1) * 255);
        -        const g = Math.floor(yy / (size - 1) * 255);
        -        const b = Math.floor(zz / (size - 1) * 255);
        -        ctx.fillStyle = `rgb(${r},${g},${b})`;
        -        ctx.fillRect(zz * size + xx, yy, 1, 1);
        -      }
        -    }
        -  }
        -  document.querySelector('#width').textContent = canvas.width;
        -  document.querySelector('#height').textContent = canvas.height;
        -}
        -
        -drawColorCubeImage(ctx, 8);
        -
        -

        キャンバスが必要です。

        -
        <canvas></canvas>
        -
        -

        これで任意のサイズで同一性の3Dルックアップテーブルを生成できます。

        -

        - -

        -

        解像度が大きいほど微調整が可能ですが、キューブのデータであるため必要なサイズはすぐに大きくなります。 -サイズ8のキューブでは2KBしか必要ありませんが、サイズ64のキューブでは1MB必要です。 -したがって、望む効果を再現する最小のものを使用して下さい。

        -

        サイズを16に設定しSaveをクリックすると以下のようなファイルができます。

        -
        - -

        また、LUTを適用したい部分の画像キャプチャをする必要があります。 -通常は上記のシーンを右クリックして "名前を付けて保存... "を選択できますが、OrbitControls がOSによっては右クリック防止してるかもしれない事に注意して下さい。 -私の場合は、スクリーンショットを取得するためにOSのスクリーンキャプチャ機能を使用しました。

        -
        - -

        次に画像エディタ(私の場合はPhotoshop)で上記の画像を読み込み、左上に3DLUTの画像を貼り付けます。

        -
        -

        備考: 最初にPhotoshop上でLUTファイルをドラッグ&ドロップしてみましたが、上手くいきませんでした。 -Photoshopで2倍の大きさにしてみました。 -DPIか何かに合わせようとしているのかもしれません。 -LUTファイルを個別に読み込み、コピーして画面キャプチャに貼り付けると上手くいきました。

        -
        -
        - -

        カラーベースのフルイメージ調整を使い画像調整します。 -Photoshopの場合、使用できる調整のほとんどは画像 → 調整メニューにあります。

        -
        - -

        好みに合わせて画像を調整して、左上に配置した3DLUTスライスにも同じ調整が適用されているのが分かります。

        -

        分かりましたがどうやって使うのでしょうか?

        -

        最初にpngを3dlut-red-only-s16.pngで保存しました。 -メモリを節約するために左上にLUTテーブルを16 x 256でトリミングしましたが、もっと楽しむためにロード後にトリミングしておきます。 -これの良い点はpngファイルを見ると、LUTの効果をある程度把握できます。 -悪い点はもちろん帯域の無駄遣いです。

        -

        以下はそれをロードするためのコードです。 -このコードはテクスチャをすぐに使用できるように、同一性のLUTから始まります。 -次に画像をロードし3D LUT部分だけをキャンバスにコピーします。 -キャンバスからデータを取得してテクスチャに設定し、needsUpdate をtrueに設定して新しいデータを取得させます。

        -
        const makeLUTTexture = function() {
        -  const imgLoader = new THREE.ImageLoader();
        -  const ctx = document.createElement('canvas').getContext('2d');
        -
        -  return function(info) {
        -    const lutSize = info.size;
        -    const width = lutSize * lutSize;
        -    const height = lutSize;
        -    const texture = new THREE.DataTexture(new Uint8Array(width * height), width, height);
        -    texture.minFilter = texture.magFilter = info.filter ? THREE.LinearFilter : THREE.NearestFilter;
        -    texture.flipY = false;
        -
        -    if (info.url) {
        -
        -      imgLoader.load(info.url, function(image) {
        -        ctx.canvas.width = width;
        -        ctx.canvas.height = height;
        -        ctx.drawImage(image, 0, 0);
        -        const imageData = ctx.getImageData(0, 0, width, height);
        -
        -        texture.image.data = new Uint8Array(imageData.data.buffer);
        -        texture.image.width = width;
        -        texture.image.height = height;
        -        texture.needsUpdate = true;
        -      });
        -    }
        -
        -    return texture;
        -  };
        -}();
        -
        -

        先ほど作成したLUTのpngを読み込むのに使ってみましょう。

        -
        const lutTextures = [
        -  { name: 'identity',           size: 2, filter: true , },
        -  { name: 'identity no filter', size: 2, filter: false, },
        -+  { name: 'custom',          url: 'resources/images/lut/3dlut-red-only-s16.png' },
        -];
        -
        -+lutTextures.forEach((info) => {
        -+  // if not size set get it from the filename
        -+  if (!info.size) {
        -+    // assumes filename ends in '-s<num>[n]'
        -+    // where <num> is the size of the 3DLUT cube
        -+    // and [n] means 'no filtering' or 'nearest'
        -+    //
        -+    // examples:
        -+    //    'foo-s16.png' = size:16, filter: true
        -+    //    'bar-s8n.png' = size:8, filter: false
        -+    const m = /-s(\d+)(n*)\.[^.]+$/.exec(info.url);
        -+    if (m) {
        -+      info.size = parseInt(m[1]);
        -+      info.filter = info.filter === undefined ? m[2] !== 'n' : info.filter;
        -+    }
        -+  }
        -+
        -+  info.texture = makeLUTTexture(info);
        -+});
        -
        -

        上記ではLUTのサイズをファイル名の最後にエンコードしてます。 -これでLUTをpngとして渡すのが簡単になります。

        -

        既存のLUTのpngファイルをたくさん追加しておきましょう。

        -
        const lutTextures = [
        -  { name: 'identity',           size: 2, filter: true , },
        -  { name: 'identity no filter', size: 2, filter: false, },
        -  { name: 'custom',          url: 'resources/images/lut/3dlut-red-only-s16.png' },
        -+  { name: 'monochrome',      url: 'resources/images/lut/monochrome-s8.png' },
        -+  { name: 'sepia',           url: 'resources/images/lut/sepia-s8.png' },
        -+  { name: 'saturated',       url: 'resources/images/lut/saturated-s8.png', },
        -+  { name: 'posterize',       url: 'resources/images/lut/posterize-s8n.png', },
        -+  { name: 'posterize-3-rgb', url: 'resources/images/lut/posterize-3-rgb-s8n.png', },
        -+  { name: 'posterize-3-lab', url: 'resources/images/lut/posterize-3-lab-s8n.png', },
        -+  { name: 'posterize-4-lab', url: 'resources/images/lut/posterize-4-lab-s8n.png', },
        -+  { name: 'posterize-more',  url: 'resources/images/lut/posterize-more-s8n.png', },
        -+  { name: 'inverse',         url: 'resources/images/lut/inverse-s8.png', },
        -+  { name: 'color negative',  url: 'resources/images/lut/color-negative-s8.png', },
        -+  { name: 'high contrast',   url: 'resources/images/lut/high-contrast-bw-s8.png', },
        -+  { name: 'funky contrast',  url: 'resources/images/lut/funky-contrast-s8.png', },
        -+  { name: 'nightvision',     url: 'resources/images/lut/nightvision-s8.png', },
        -+  { name: 'thermal',         url: 'resources/images/lut/thermal-s8.png', },
        -+  { name: 'b/w',             url: 'resources/images/lut/black-white-s8n.png', },
        -+  { name: 'hue +60',         url: 'resources/images/lut/hue-plus-60-s8.png', },
        -+  { name: 'hue +180',        url: 'resources/images/lut/hue-plus-180-s8.png', },
        -+  { name: 'hue -60',         url: 'resources/images/lut/hue-minus-60-s8.png', },
        -+  { name: 'red to cyan',     url: 'resources/images/lut/red-to-cyan-s8.png' },
        -+  { name: 'blues',           url: 'resources/images/lut/blues-s8.png' },
        -+  { name: 'infrared',        url: 'resources/images/lut/infrared-s8.png' },
        -+  { name: 'radioactive',     url: 'resources/images/lut/radioactive-s8.png' },
        -+  { name: 'goolgey',         url: 'resources/images/lut/googley-s8.png' },
        -+  { name: 'bgy',             url: 'resources/images/lut/bgy-s8.png' },
        -];
        -
        -

        そして、ここにはたくさんのLUTがあります。

        -

        - -

        -

        最後にもう1つ、ただのお遊びですがAdobeが定義した標準LUTフォーマットがあります。 -ネットで検索するとたくさんのLUTファイルが見つかります。

        -

        クイックローダーを書いてみました。 -フォーマットの種類は4つありますが、残念ながら私は1種類の例しか見つけられなかったので、全ての種類が動作するかを簡単にテストできませんでした。

        -

        ドラッグ&ドロップライブラリも書いてみます。 -両方を使いAdobe LUTファイルをドラッグ&ドロップして効果を確認できるようにしてみましょう。

        -

        まず2つのライブラリが必要です。

        -
        import * as lutParser from './resources/lut-reader.js';
        -import * as dragAndDrop from './resources/drag-and-drop.js';
        -
        -

        そして次のように利用できます。

        -
        dragAndDrop.setup({msg: 'Drop LUT File here'});
        -dragAndDrop.onDropFile(readLUTFile);
        -
        -function ext(s) {
        -  const period = s.lastIndexOf('.');
        -  return s.slice(period + 1);
        -}
        -
        -function readLUTFile(file) {
        -  const reader = new FileReader();
        -  reader.onload = (e) => {
        -    const type = ext(file.name);
        -    const lut = lutParser.lutTo2D3Drgba8(lutParser.parse(e.target.result, type));
        -    const {size, data, name} = lut;
        -    const texture = new THREE.DataTexture(data, size * size, size);
        -    texture.minFilter = THREE.LinearFilter;
        -    texture.needsUpdate = true;
        -    texture.flipY = false;
        -    const lutTexture = {
        -      name: (name && name.toLowerCase().trim() !== 'untitled')
        -          ? name
        -          : file.name,
        -      size: size,
        -      filter: true,
        -      texture,
        -    };
        -    lutTextures.push(lutTexture);
        -    lutSettings.lut = lutTextures.length - 1;
        -    updateGUI();
        -  };
        -
        -  reader.readAsText(file);
        -}
        -
        -

        Adobe LUTをダウンロードし、下の例にドラッグ&ドロップできます。

        -

        - -

        -

        Adobe LUTはWeb上のオンライン利用を想定して設計されていません。 -これらは大きなファイルです。 -下のサンプルの上にドラッグ&ドロップしてサイズを選択し、"Save... "をクリックし小さなファイルに変換し、PNG形式で保存できます。

        -

        以下のサンプルは上記のコードを変更したものです。 -背景の絵を描くだけでglTFファイルはありません。 -同一性のLUT画像です。

        -

        この画像は上記スクリプトから作成された同一性のLUT画像です。 -次に読み込まれたLUTファイルを適用するための効果を使用しているので、結果はLUTファイルをPNGとして再現するために必要な画像になります。

        -

        - -

        -

        1つ解説を完全に飛ばしてるのは、シェーダー自体がどのように動作するかです。 -将来的にはもう少しGLSLをカバーできると良いと思います。 -今の所は興味があればポストプロセスの記事のリンクを見たりこの動画を見て下さい

        - - -
        -
        -
        - - - - - - - - diff --git a/manual/ja/post-processing.html b/manual/ja/post-processing.html index 353113e0bf8f0d..5c236810a3c649 100644 --- a/manual/ja/post-processing.html +++ b/manual/ja/post-processing.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ja/prerequisites.html b/manual/ja/prerequisites.html index ef54394acde609..dda13665f6bca6 100644 --- a/manual/ja/prerequisites.html +++ b/manual/ja/prerequisites.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ja/primitives.html b/manual/ja/primitives.html index 16a0fe6ea00e50..09419e715a70c0 100644 --- a/manual/ja/primitives.html +++ b/manual/ja/primitives.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ja/rendering-on-demand.html b/manual/ja/rendering-on-demand.html index b4ac4261c1ee72..9bf2b820b3c88f 100644 --- a/manual/ja/rendering-on-demand.html +++ b/manual/ja/rendering-on-demand.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ja/rendertargets.html b/manual/ja/rendertargets.html index 7166232b726f99..105cf4c38d2456 100644 --- a/manual/ja/rendertargets.html +++ b/manual/ja/rendertargets.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ja/responsive.html b/manual/ja/responsive.html index 931fa0e9a90124..933ebbd4088ea2 100644 --- a/manual/ja/responsive.html +++ b/manual/ja/responsive.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ja/scenegraph.html b/manual/ja/scenegraph.html index 0b83162a32fa97..e5ca1faedc0a56 100644 --- a/manual/ja/scenegraph.html +++ b/manual/ja/scenegraph.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ja/setup.html b/manual/ja/setup.html index 12502335ff924a..032f29b59862eb 100644 --- a/manual/ja/setup.html +++ b/manual/ja/setup.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ja/shadertoy.html b/manual/ja/shadertoy.html index db34cba1ecb918..52d1e755477ac3 100644 --- a/manual/ja/shadertoy.html +++ b/manual/ja/shadertoy.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ja/shadows.html b/manual/ja/shadows.html index 8d8197717058a0..f07959802763bb 100644 --- a/manual/ja/shadows.html +++ b/manual/ja/shadows.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ja/textures.html b/manual/ja/textures.html index f905815ba23c57..6f14fcca321c46 100644 --- a/manual/ja/textures.html +++ b/manual/ja/textures.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ja/tips.html b/manual/ja/tips.html index 0c1b7b0fd72d8c..208c571fa70139 100644 --- a/manual/ja/tips.html +++ b/manual/ja/tips.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ja/transparency.html b/manual/ja/transparency.html index 533dca3096e853..d7135211732c25 100644 --- a/manual/ja/transparency.html +++ b/manual/ja/transparency.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ja/voxel-geometry.html b/manual/ja/voxel-geometry.html index 9209617e04cc89..904c25aa1206af 100644 --- a/manual/ja/voxel-geometry.html +++ b/manual/ja/voxel-geometry.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ja/webxr-basics.html b/manual/ja/webxr-basics.html index 5522bdb589df30..f7af921ade6b6d 100644 --- a/manual/ja/webxr-basics.html +++ b/manual/ja/webxr-basics.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ja/webxr-look-to-select.html b/manual/ja/webxr-look-to-select.html index c4c08e7b73b095..9c011fe92bc2e3 100644 --- a/manual/ja/webxr-look-to-select.html +++ b/manual/ja/webxr-look-to-select.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ja/webxr-point-to-select.html b/manual/ja/webxr-point-to-select.html index 34264e4e4b102d..85b643373559f9 100644 --- a/manual/ja/webxr-point-to-select.html +++ b/manual/ja/webxr-point-to-select.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ko/align-html-elements-to-3d.html b/manual/ko/align-html-elements-to-3d.html index 0a0a862d9570c1..1cfce2a891de06 100644 --- a/manual/ko/align-html-elements-to-3d.html +++ b/manual/ko/align-html-elements-to-3d.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ko/backgrounds.html b/manual/ko/backgrounds.html index 336455912f389a..557334b3593fc5 100644 --- a/manual/ko/backgrounds.html +++ b/manual/ko/backgrounds.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ko/billboards.html b/manual/ko/billboards.html index e0e3ff2d85c780..5450dbac1666e3 100644 --- a/manual/ko/billboards.html +++ b/manual/ko/billboards.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ko/cameras.html b/manual/ko/cameras.html index b6cdbdc944fe4a..8118485e47812b 100644 --- a/manual/ko/cameras.html +++ b/manual/ko/cameras.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ko/canvas-textures.html b/manual/ko/canvas-textures.html index ba337252d26533..bc8b1eb044a2d3 100644 --- a/manual/ko/canvas-textures.html +++ b/manual/ko/canvas-textures.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ko/cleanup.html b/manual/ko/cleanup.html index ea8dad7022eb0d..50bc61c717dd4b 100644 --- a/manual/ko/cleanup.html +++ b/manual/ko/cleanup.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ko/custom-buffergeometry.html b/manual/ko/custom-buffergeometry.html index 7ce69255d3fdf1..843a18640b1092 100644 --- a/manual/ko/custom-buffergeometry.html +++ b/manual/ko/custom-buffergeometry.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ko/debugging-glsl.html b/manual/ko/debugging-glsl.html index bbf7b8de9805a9..aedf886464ea5c 100644 --- a/manual/ko/debugging-glsl.html +++ b/manual/ko/debugging-glsl.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ko/debugging-javascript.html b/manual/ko/debugging-javascript.html index c86e0cf08b18a7..51d267dda6a848 100644 --- a/manual/ko/debugging-javascript.html +++ b/manual/ko/debugging-javascript.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ko/fog.html b/manual/ko/fog.html index 3d7a97b2ff4078..30847d540ce113 100644 --- a/manual/ko/fog.html +++ b/manual/ko/fog.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ko/fundamentals.html b/manual/ko/fundamentals.html index bd47ddd157d5df..20e551e487caaf 100644 --- a/manual/ko/fundamentals.html +++ b/manual/ko/fundamentals.html @@ -11,10 +11,6 @@ - - - - - diff --git a/manual/ko/indexed-textures.html b/manual/ko/indexed-textures.html index 3e6ad811495351..cccc23666ad5ee 100644 --- a/manual/ko/indexed-textures.html +++ b/manual/ko/indexed-textures.html @@ -11,10 +11,6 @@ - - - - - diff --git a/manual/ko/load-gltf.html b/manual/ko/load-gltf.html index 038606bd76c23e..6516509639b0b1 100644 --- a/manual/ko/load-gltf.html +++ b/manual/ko/load-gltf.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ko/load-obj.html b/manual/ko/load-obj.html index 1b740769ba6179..030af4757784d2 100644 --- a/manual/ko/load-obj.html +++ b/manual/ko/load-obj.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ko/material-table.html b/manual/ko/material-table.html index ff3ccd2706eec9..bf8f1b2a4440a0 100644 --- a/manual/ko/material-table.html +++ b/manual/ko/material-table.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ko/materials.html b/manual/ko/materials.html index 3ea3aa67476fb8..1fe12cf2ad00ee 100644 --- a/manual/ko/materials.html +++ b/manual/ko/materials.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ko/multiple-scenes.html b/manual/ko/multiple-scenes.html index 123d1ff89e8af0..3e8a3b4e2b2d7d 100644 --- a/manual/ko/multiple-scenes.html +++ b/manual/ko/multiple-scenes.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ko/offscreencanvas.html b/manual/ko/offscreencanvas.html index 8dc53c2129d0e4..3b3a64462d0c3e 100644 --- a/manual/ko/offscreencanvas.html +++ b/manual/ko/offscreencanvas.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ko/optimize-lots-of-objects-animated.html b/manual/ko/optimize-lots-of-objects-animated.html index 57c6c15e427a07..6cd493ebf0403d 100644 --- a/manual/ko/optimize-lots-of-objects-animated.html +++ b/manual/ko/optimize-lots-of-objects-animated.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ko/optimize-lots-of-objects.html b/manual/ko/optimize-lots-of-objects.html index fce8ab06644eb1..51f69bf1b61e3e 100644 --- a/manual/ko/optimize-lots-of-objects.html +++ b/manual/ko/optimize-lots-of-objects.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ko/picking.html b/manual/ko/picking.html index aaf120f6bb5e97..9560fbc6b0647f 100644 --- a/manual/ko/picking.html +++ b/manual/ko/picking.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ko/post-processing-3dlut.html b/manual/ko/post-processing-3dlut.html deleted file mode 100644 index 810e1e4bcce865..00000000000000 --- a/manual/ko/post-processing-3dlut.html +++ /dev/null @@ -1,450 +0,0 @@ - - - 3DLUT로 후처리하기 - - - - - - - - - - - - - - - - - - -
        -
        -

        3DLUT로 후처리하기

        -
        -
        -
        -

        이전 글에서는 후처리(Post processing)에 관해 알아보았습니다. 보통 후처리는 LUT 또는 3DLUT라고 부르기도 합니다. LUT는 룩업 테이블(Look-Up Table, 순람표)의 줄임말이고, 3DLUT는 3차원 룩업 테이블의 줄임말입니다.

        -

        3DLUT는 2D 이미지를 특정한 색상 정육면체를 매핑한다고 생각하면 쉽습니다. 먼저 원본 이미지의 색상을 정육면체의 인덱스 값과 매칭시킵니다. 원본 이미지의 픽셀 하나당 해당 픽셀 색상의 빨강(red), 초록(green), 파랑(blue) 값을 이용해 정육면체의 특정 지점을 가리키는(look-up) 3D 벡터 인덱스를 만드는 것이죠. 이 인덱스를 통해 3DLUT에서 뽑아낸 값을 새로운 색으로 사용하는 겁니다.

        -

        자바스크립트의 경우 아래처럼 구현할 수 있습니다. RGB 각 색상값을 0부터 255의 정수로 표현한 3차원 256x256x256 배열로 룩업 테이블을 구현하고, 이 룩업 테이블에서 RGB 색상값을 이용해 새로운 색상값을 선택하는 거죠.

        -
        const newColor = lut[origColor.red][origColor.green][origColor.blue]
        -
        -

        물론 256x256x256 배열은 큰 배열입니다. 텍스처에 관한 글에서 배웠듯 텍스처는 크기에 상관 없이 0.0에서 1.0로 값을 지정합니다.

        -

        8x8x8 정육면체를 예로 들어보죠.

        -
        - -

        먼저 0,0,0 부분을 검정색으로 채웁니다. 맞은편의 1,1,1 부분은 하얀색, 1,0,0 부분은 빨강, 0,1,0은 초록, 0,0,1은 파랑으로 채웁니다.

        -
        - -

        그리고 각 축을 따라 색을 채워넣습니다.

        -
        - -

        빈 모서리를 2개 이상의 색상 채널을 사용하는 색으로 채웁니다(초록 + 빨강, 파랑 + 빨강 등).

        -
        - -

        마지막으로 빈 공간을 채웁니다. 이 형태가 3DLUT 기본 구조입니다. 지금은 효과를 주기 전과 후의 차이가 없습니다. 색상값을 인덱스로 사용해 새로운 색상값을 선택하면, 정확히 같은 색상값이 나오기 때문이죠.

        -
        - -

        이 정육면체를 호박색 쉐이드로 바꾸면 같은 인덱스를 참조하지만 전혀 다른 결과가 나옵니다.

        -
        - -

        이 기법을 사용하면 룩업 테이블을 교체하는 것으로 많은 효과를 구현할 수 있습니다. 색상 계산 기반의 효과는 대부분 하나의 색상값만을 사용합니다. 색상, 대비, 채도, 컬러 캐스트(color cast), 틴트(tint), 밝기, 노출도, 레벨, 커브, 포스터화, 그림자, 강조, 등 거의 모든 효과를 색상값 계산을 기반으로 구현하죠. 또 이 모든 효과를 하나의 룩업 테이블로 합칠 수도 있습니다.

        -

        룩업 테이블을 사용하려면 먼저 적용할 장면이 필요하니 간단한 장면을 하나 만들어보겠습니다. glTF 불러오기에서 배웠듯 glTF 파일을 불러와 사용하겠습니다. 예제에 사용할 모델은 The Ice Wolves작품입니다.

        -

        배경과 하늘 상자에서 배웠던 대로 배경도 추가하겠습니다.

        -

        - -

        -

        이제 장면을 구현했으니 3DLUT를 만들어야 합니다. 가장 간단한 3DLUT는 2x2x2 identity LUT로, 여기서 identity(동일한)은 아무런 변화도 없음을 의미합니다. 1을 곱하거나 아무것도 안 하는 경우와 같죠. LUT 안의 색상값을 사용한다고 해도 입력된 값과 정확히 같은 값을 반환할 테니까요.

        -
        - -

        WebGL1은 3D 텍스쳐를 지원하지 않습니다. 따라서 3D 텍스처를 썰어 펼쳐 놓은 형태의 4x2짜리 2D 텍스처를 대신 사용하겠습니다.

        -

        아래는 4x2 2D 텍스처로 identity LUT를 구현한 것입니다.

        -
        const makeIdentityLutTexture = function() {
        -  const identityLUT = new Uint8Array([
        -      0,   0,   0, 255,  // black
        -    255,   0,   0, 255,  // red
        -      0,   0, 255, 255,  // blue
        -    255,   0, 255, 255,  // magenta
        -      0, 255,   0, 255,  // green
        -    255, 255,   0, 255,  // yellow
        -      0, 255, 255, 255,  // cyan
        -    255, 255, 255, 255,  // white
        -  ]);
        -
        -  return function(filter) {
        -    const texture = new THREE.DataTexture(identityLUT, 4, 2, THREE.RGBAFormat);
        -    texture.minFilter = filter;
        -    texture.magFilter = filter;
        -    texture.needsUpdate = true;
        -    texture.flipY = false;
        -    return texture;
        -  };
        -}();
        -
        -

        필터가 들어간 것, 안 들어간 것 총 2개를 만들겠습니다.

        -
        const lutTextures = [
        -  { name: 'identity', size: 2, texture: makeIdentityLutTexture(THREE.LinearFilter) },
        -  { name: 'identity not filtered', size: 2, texture: makeIdentityLutTexture(THREE.NearestFilter) },
        -];
        -
        -

        후처리에 관한 글에서 작성했던 코드를 가져와 이 쉐이더들을 대신 쓰도록 합니다.

        -
        const lutShader = {
        -  uniforms: {
        -    tDiffuse: { value: null },
        -    lutMap:  { value: null },
        -    lutMapSize: { value: 1, },
        -  },
        -  vertexShader: `
        -    varying vec2 vUv;
        -    void main() {
        -      vUv = uv;
        -      gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
        -    }
        -  `,
        -  fragmentShader: `
        -    #include <common>
        -
        -    #define FILTER_LUT true
        -
        -    uniform sampler2D tDiffuse;
        -    uniform sampler2D lutMap;
        -    uniform float lutMapSize;
        -
        -    varying vec2 vUv;
        -
        -    vec4 sampleAs3DTexture(sampler2D tex, vec3 texCoord, float size) {
        -      float sliceSize = 1.0 / size;                  // space of 1 slice
        -      float slicePixelSize = sliceSize / size;       // space of 1 pixel
        -      float width = size - 1.0;
        -      float sliceInnerSize = slicePixelSize * width; // space of size pixels
        -      float zSlice0 = floor( texCoord.z * width);
        -      float zSlice1 = min( zSlice0 + 1.0, width);
        -      float xOffset = slicePixelSize * 0.5 + texCoord.x * sliceInnerSize;
        -      float yRange = (texCoord.y * width + 0.5) / size;
        -      float s0 = xOffset + (zSlice0 * sliceSize);
        -
        -      #ifdef FILTER_LUT
        -
        -        float s1 = xOffset + (zSlice1 * sliceSize);
        -        vec4 slice0Color = texture2D(tex, vec2(s0, yRange));
        -        vec4 slice1Color = texture2D(tex, vec2(s1, yRange));
        -        float zOffset = mod(texCoord.z * width, 1.0);
        -        return mix(slice0Color, slice1Color, zOffset);
        -
        -      #else
        -
        -        return texture2D(tex, vec2( s0, yRange));
        -
        -      #endif
        -    }
        -
        -    void main() {
        -      vec4 originalColor = texture2D(tDiffuse, vUv);
        -      gl_FragColor = sampleAs3DTexture(lutMap, originalColor.xyz, lutMapSize);
        -    }
        -  `,
        -};
        -
        -const lutNearestShader = {
        -  uniforms: {...lutShader.uniforms},
        -  vertexShader: lutShader.vertexShader,
        -  fragmentShader: lutShader.fragmentShader.replace('#define FILTER_LUT', '//'),
        -};
        -
        -

        fragment 쉐이더의 다음 코드는

        -
        #define FILTER_LUT true
        -
        -

        주석 처리했던 두 번째 쉐이더를 생성하기 위한 것입니다.

        -

        그리고 각 쉐이더로 Pass를 만듭니다.

        -
        const effectLUT = new THREE.ShaderPass(lutShader);
        -const effectLUTNearest = new THREE.ShaderPass(lutNearestShader);
        -
        -

        기존에 배경과 glTF를 별도 장면으로 분리했으므로 각 장면의 RenderPass를 따로 생성합니다.

        -
        const renderModel = new THREE.RenderPass(scene, camera);
        -renderModel.clear = false;  // 배경을 지우지 않도록 합니다
        -const renderBG = new THREE.RenderPass(sceneBG, cameraBG);
        -
        -

        다음으로 사용할 pass*를 EffectComposer에 추가합니다.

        -

        ※ 편의상 Pass 인스턴스를 pass로 번역합니다.

        -
        const composer = new THREE.EffectComposer(renderer);
        -
        -composer.addPass(renderBG);
        -composer.addPass(renderModel);
        -composer.addPass(effectLUT);
        -composer.addPass(effectLUTNearest);
        -composer.addPass(gammaPass);
        -
        -

        GUI를 만들어 LUT를 바꿀 수 있도록 합니다.

        -
        const lutNameIndexMap = {};
        -lutTextures.forEach((info, ndx) => {
        -  lutNameIndexMap[info.name] = ndx;
        -});
        -
        -const lutSettings = {
        -  lut: lutNameIndexMap.identity,
        -};
        -const gui = new GUI({ width: 300 });
        -gui.add(lutSettings, 'lut', lutNameIndexMap);
        -
        -

        마지막으로 필터링 여부에 따라 효과가 바뀌도록 설정합니다. LUT가 선택한 텍스처를 사용하도록 하고, EffectComposer로 렌더링 합니다.

        -
        const lutInfo = lutTextures[lutSettings.lut];
        -
        -const effect = lutInfo.filter ? effectLUT : effectLUTNearest;
        -effectLUT.enabled = lutInfo.filter;
        -effectLUTNearest.enabled = !lutInfo.filter;
        -
        -const lutTexture = lutInfo.texture;
        -effect.uniforms.lutMap.value = lutTexture;
        -effect.uniforms.lutMapSize.value = lutInfo.size;
        -
        -composer.render(delta);
        -
        -

        identity 3DLUT를 선택했을 때는 아무런 변화가 없습니다.

        -

        - -

        -

        하지만 필터가 identity not filtered LUT를 선택하면 재미있는 결과가 나옵니다.

        -
        - -

        왜 이런 결과가 나온 걸까요? 필터링을 사용할 경우(linear), GPU는 선형적으로 색상값을 채워넣습니다. 필터링을 사용하지 않을 경우(nearest), 알아서 색상값을 채워넣지 않기에 3DLUT에서(근처의) 색상값이 있는 곳을 찾아 사용하는 것이죠.

        -

        어느정도 이해했다면 더 다양한 3DLUT를 만들어봅시다.

        -

        먼저 룩업 테이블의 해상도를 정하고 간단한 코드를 만들어 룩업 테이블 정육면체의 각 면을 만들겠습니다.

        -
        const ctx = document.querySelector('canvas').getContext('2d');
        -
        -function drawColorCubeImage(ctx, size) {
        -  const canvas = ctx.canvas;
        -  canvas.width = size * size;
        -  canvas.height = size;
        -
        -  for (let zz = 0; zz < size; ++zz) {
        -    for (let yy = 0; yy < size; ++yy) {
        -      for (let xx = 0; xx < size; ++xx) {
        -        const r = Math.floor(xx / (size - 1) * 255);
        -        const g = Math.floor(yy / (size - 1) * 255);
        -        const b = Math.floor(zz / (size - 1) * 255);
        -        ctx.fillStyle = `rgb(${ r },${ g },${ b })`;
        -        ctx.fillRect(zz * size + xx, yy, 1, 1);
        -      }
        -    }
        -  }
        -  document.querySelector('#width').textContent = canvas.width;
        -  document.querySelector('#height').textContent = canvas.height;
        -}
        -
        -drawColorCubeImage(ctx, 8);
        -
        -

        캔버스 요소도 만듭니다.

        -
        <canvas></canvas>
        -
        -

        이제 어떤 identity 3D 룩업 테이블이든 만들 수 있습니다.

        -

        - -

        -

        해상도가 높을수록 더 세밀한 효과를 줄 수 있지만 정육면체형 데이터의 크기는 기하급수적으로 늘어납니다. 크기 8x8 정육면체는 2kb 정도지만 64x64 정육면체는 약 1mb나 되죠. 그러니 충분히 효과를 구현할 수 있는 만큼만 사용하는 게 좋습니다.

        -

        사이즈를 16으로 설정하고 Save... 버튼을 클릭하면 아래와 같은 파일이 나옵니다.

        -
        - -

        그리고 LUT를 적용할 화면을 캡쳐해야 합니다. 이 경우에는 이전에 만든 장면에 아무런 효과를 주지 않은 화면이겠죠. 대게 위 예제를 오른쪽 클릭해 "다른 이름으로 저장..."을 클릭하면 되지만, OS에 따라 마우스 우클릭이 동작하지 않을 수 있습니다. 제 경우 OS에 내장된 스크린샷 기능을 이용해 화면을 캡쳐했습니다*.

        -

        ※ Windows 10 RS5(레드스톤 5) 이상이라면 Windows + Shift + S를 눌러 화면을 캡쳐할 수 있습니다. 역주.

        -
        - -

        캡쳐본을 이미지 에디터에서 불러옵니다. 저는 포토샵을 사용해 샘플 이미지를 불러오고, 한쪽 귀퉁이에 3DLUT를 붙여 넣었습니다.

        -
        -

        참고: 제 경우 포토샵에서 캡쳐본 위에 lut 파일을 불러오려고 했을 때 이미지가 두 배 더 커졌습니다. 아마 DPI를 맞추거나 하는 이유 때문에 그런 거겠죠. lut 파일을 별도 탭에 불러와 캡쳐본 위에 복사 붙여 넣기 하니 정상적으로 불러와지더군요.

        -
        -
        - -

        그리고 이미지에 부여하고 싶은 색상 효과를 부여합니다. 포토샵의 경우 대부분의 효과는 이미지(Image)->조정(Adjustments) 메뉴에 있습니다.

        -
        - -

        색상을 조정하면 3DLUT 이미지에도 같은 효과가 적용될 겁니다.

        -

        자 그럼 이제 이걸 어떻게 쓸 수 있을까요?

        -

        먼저 저는 3DLUT 이미지를 3dlut-red-only-s16.png라는 이름으로 저장했습니다. 메모리를 아끼려면 이미지를 LUT 부분만 잘라 16x256로 맞추는 것이 좋지만, 그냥 재미삼아 이미지를 불러온 이후 자르겠습니다*. 이 방법의 장점은 귀찮게 이미지를 자르는 과정 없이 효과를 적용해보고 싶은 대로 바로바로 적용할 수 있다는 것이죠. 물론 대역폭을 낭비한다는 게 단점입니다.

        -

        ※ 포토샵 CC 이후 버젼을 사용한다면 레이어를 오른쪽 클릭해 PNG로 빠르게 내보내기 메뉴로 해당 그룹 또는 레이어만 .png 파일로 내보낼 수 있습니다. 이미지를 귀찮게 자르는 과정 없이 .png 파일을 바로 생성할 수 있죠. 역주.

        -

        아래는 이미지를 불러오는 코드입니다. 실제 코드에서는 텍스처를 불러왔을 때 바로 사용할 수 있도록 identity lut를 먼저 만들었습니다. 그 다음 이미지를 불러와 3DLUT 부분만 캔버스에 복사하고, 캔버스에서 가져온 데이터를 텍스처에 지정합니다. 또한 텍스처가 바뀌었을 때 바로 적용하도록 needsUpdate 속성도 true로 설정합니다.

        -
        const makeLUTTexture = function() {
        -  const imgLoader = new THREE.ImageLoader();
        -  const ctx = document.createElement('canvas').getContext('2d');
        -
        -  return function(info) {
        -    const lutSize = info.size;
        -    const width = lutSize * lutSize;
        -    const height = lutSize;
        -    const texture = new THREE.DataTexture(new Uint8Array(width * height), width, height);
        -    texture.minFilter = texture.magFilter = info.filter ? THREE.LinearFilter : THREE.NearestFilter;
        -    texture.flipY = false;
        -
        -    if (info.url) {
        -
        -      imgLoader.load(info.url, function(image) {
        -        ctx.canvas.width = width;
        -        ctx.canvas.height = height;
        -        ctx.drawImage(image, 0, 0);
        -        const imageData = ctx.getImageData(0, 0, width, height);
        -
        -        texture.image.data = new Uint8Array(imageData.data.buffer);
        -        texture.image.width = width;
        -        texture.image.height = height;
        -        texture.needsUpdate = true;
        -      });
        -    }
        -
        -    return texture;
        -  };
        -}();
        -
        -

        기존 코드가 LUT png 파일을 사용하도록 수정합니다.

        -
        const lutTextures = [
        -  { name: 'identity',           size: 2, filter: true , },
        -  { name: 'identity no filter', size: 2, filter: false, },
        -+  { name: 'custom',          url: 'resources/images/lut/3dlut-red-only-s16.png' },
        -];
        -
        -+lutTextures.forEach((info) => {
        -+  // 사이즈값이 없다면 사이즈 정보를 파일 이름에서 가져옵니다.
        -+  if (!info.size) {
        -+    /**
        -+     * 파일 이름이 '-s<숫자>[n]' 이렇게 끝난다고 가정합니다.
        -+     * <숫자>는 3DLUT 정육면체의 크기입니다.
        -+     * [n]은 '필터링 없음' 또는 'nearest'를 의미합니다.
        -+     *
        -+     * 예시:
        -+     *    'foo-s16.png' = 크기:16, 필터: true
        -+     *    'bar-s8n.png' = 크기:8, 필터: false
        -+     **/
        -+    const m = /-s(\d+)(n*)\.[^.]+$/.exec(info.url);
        -+    if (m) {
        -+      info.size = parseInt(m[1]);
        -+      info.filter = info.filter === undefined ? m[2] !== 'n' : info.filter;
        -+    }
        -+  }
        -+
        -+  info.texture = makeLUTTexture(info);
        -+});
        -
        -

        위 코드가 LUT의 사이즈를 파일 이름에 인코딩한 예입니다. 이러면 png로 LUT를 바꾸기가 훨씬 쉽죠.

        -

        그냥은 좀 심심하니 lut png 파일을 더 많이 만들어봅시다.

        -
        const lutTextures = [
        -  { name: 'identity',           size: 2, filter: true , },
        -  { name: 'identity no filter', size: 2, filter: false, },
        -  { name: 'custom',          url: 'resources/images/lut/3dlut-red-only-s16.png' },
        -+  { name: 'monochrome',      url: 'resources/images/lut/monochrome-s8.png' },
        -+  { name: 'sepia',           url: 'resources/images/lut/sepia-s8.png' },
        -+  { name: 'saturated',       url: 'resources/images/lut/saturated-s8.png', },
        -+  { name: 'posterize',       url: 'resources/images/lut/posterize-s8n.png', },
        -+  { name: 'posterize-3-rgb', url: 'resources/images/lut/posterize-3-rgb-s8n.png', },
        -+  { name: 'posterize-3-lab', url: 'resources/images/lut/posterize-3-lab-s8n.png', },
        -+  { name: 'posterize-4-lab', url: 'resources/images/lut/posterize-4-lab-s8n.png', },
        -+  { name: 'posterize-more',  url: 'resources/images/lut/posterize-more-s8n.png', },
        -+  { name: 'inverse',         url: 'resources/images/lut/inverse-s8.png', },
        -+  { name: 'color negative',  url: 'resources/images/lut/color-negative-s8.png', },
        -+  { name: 'high contrast',   url: 'resources/images/lut/high-contrast-bw-s8.png', },
        -+  { name: 'funky contrast',  url: 'resources/images/lut/funky-contrast-s8.png', },
        -+  { name: 'nightvision',     url: 'resources/images/lut/nightvision-s8.png', },
        -+  { name: 'thermal',         url: 'resources/images/lut/thermal-s8.png', },
        -+  { name: 'b/w',             url: 'resources/images/lut/black-white-s8n.png', },
        -+  { name: 'hue +60',         url: 'resources/images/lut/hue-plus-60-s8.png', },
        -+  { name: 'hue +180',        url: 'resources/images/lut/hue-plus-180-s8.png', },
        -+  { name: 'hue -60',         url: 'resources/images/lut/hue-minus-60-s8.png', },
        -+  { name: 'red to cyan',     url: 'resources/images/lut/red-to-cyan-s8.png' },
        -+  { name: 'blues',           url: 'resources/images/lut/blues-s8.png' },
        -+  { name: 'infrared',        url: 'resources/images/lut/infrared-s8.png' },
        -+  { name: 'radioactive',     url: 'resources/images/lut/radioactive-s8.png' },
        -+  { name: 'goolgey',         url: 'resources/images/lut/googley-s8.png' },
        -+  { name: 'bgy',             url: 'resources/images/lut/bgy-s8.png' },
        -];
        -
        -

        아래 예제에서 여러 lut를 시험해볼 수 있습니다.

        -

        - -

        -

        추가로 한 가지 덧붙이겠습니다. 인터넷을 뒤져보니 Adobe에서 만든 표준 LUT 형식이 있더군요. 인터넷에서 검색해보면 이런 LUT 형식의 파일을 쉽게 찾을 수 있을 겁니다.

        -

        이를 기반으로 간단하게 로더를 작성했습니다. 총 4가지 형식이 있다고는 하나, 제가 찾은 형식은 하나뿐이라 모든 형식에서 테스트하진 못했습니다.

        -

        여기에 간단한 드래그-앤-드롭 라이브러리도 만들었습니다. 이 두 라이브러리를 이용해 여러분이 직접 LUT 파일을 적용할 수 있도록 말이죠.

        -

        먼저 앞서 만든 두 라이브러리를 불러온 뒤

        -
        import * as lutParser from './resources/lut-reader.js';
        -import * as dragAndDrop from './resources/drag-and-drop.js';
        -
        -

        아래처럼 사용합니다.

        -
        dragAndDrop.setup({ msg: 'Drop LUT File here' });
        -dragAndDrop.onDropFile(readLUTFile);
        -
        -function ext(s) {
        -  const period = s.lastIndexOf('.');
        -  return s.slice(period + 1);
        -}
        -
        -function readLUTFile(file) {
        -  const reader = new FileReader();
        -  reader.onload = (e) => {
        -    const type = ext(file.name);
        -    const lut = lutParser.lutTo2D3Drgba8(lutParser.parse(e.target.result, type));
        -    const {size, data, name} = lut;
        -    const texture = new THREE.DataTexture(data, size * size, size);
        -    texture.minFilter = THREE.LinearFilter;
        -    texture.needsUpdate = true;
        -    texture.flipY = false;
        -    const lutTexture = {
        -      name: (name && name.toLowerCase().trim() !== 'untitled')
        -          ? name
        -          : file.name,
        -      size: size,
        -      filter: true,
        -      texture,
        -    };
        -    lutTextures.push(lutTexture);
        -    lutSettings.lut = lutTextures.length - 1;
        -    updateGUI();
        -  };
        -
        -  reader.readAsText(file);
        -}
        -
        -

        이제 Adobe LUT 파일을 다운해 아래 예제에 드래그-앤-드롭으로 불러올 수 있을 겁니다.

        -

        - -

        -

        다만 Adobe LUT는 온라인 환경에 최적화되지 않았습니다. 파일 용량이 꽤 큰 편이죠. 아래 예제를 사용하면 용량을 좀 더 줄일 수 있습니다. 드래그-앤-드롭으로 파일을 불러오고 크기를 선택한 뒤 "Save..." 버튼을 클릭하면 되죠.

        -

        아래 예제는 단순히 위에서 썼던 예제를 조금 수정한 것입니다. glFT 파일 없이 배경만 렌더링한 것이죠. 배경 이미지는 아까 본 스크립트로 만든 identity lut 이미지입니다. 여기에 LUT 파일을 불러와 해당 LUT 파일을 PNG로 만드는 데 사용하는 것이죠.

        -

        - -

        -

        이 글에서는 쉐이더가 어떻게 작동하는지에 대해서는 아예 설명하지 않았습니다. 나중에 GLSL에 대해 더 다룰 기회가 있었으면 좋겠네요. 쉐이더의 작동 방식을 알고 싶다면 후처리에 관한 글에 있는 링크 또는 이 유튜브 영상을 참고하기 바랍니다.

        - - -
        -
        -
        - - - - - - - - diff --git a/manual/ko/post-processing.html b/manual/ko/post-processing.html index 8b695ff892eb4a..e2911dcb909785 100644 --- a/manual/ko/post-processing.html +++ b/manual/ko/post-processing.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ko/prerequisites.html b/manual/ko/prerequisites.html index 859f4a4f4bd7dd..1d742eb2a15ccb 100644 --- a/manual/ko/prerequisites.html +++ b/manual/ko/prerequisites.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ko/primitives.html b/manual/ko/primitives.html index a5ff4fb913c47f..99d17c500eebfe 100644 --- a/manual/ko/primitives.html +++ b/manual/ko/primitives.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ko/rendering-on-demand.html b/manual/ko/rendering-on-demand.html index 698e48e7a807ec..babe438e5d80a7 100644 --- a/manual/ko/rendering-on-demand.html +++ b/manual/ko/rendering-on-demand.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ko/rendertargets.html b/manual/ko/rendertargets.html index f0dea11c952bab..c7dd3fafa1caff 100644 --- a/manual/ko/rendertargets.html +++ b/manual/ko/rendertargets.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ko/responsive.html b/manual/ko/responsive.html index 1f7cc605693dbe..df09040d8ee952 100644 --- a/manual/ko/responsive.html +++ b/manual/ko/responsive.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ko/scenegraph.html b/manual/ko/scenegraph.html index 5507c4d4563ff2..6551684e7be40d 100644 --- a/manual/ko/scenegraph.html +++ b/manual/ko/scenegraph.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ko/setup.html b/manual/ko/setup.html index 1e3cadfc88a8fa..1efbfdeff9a42b 100644 --- a/manual/ko/setup.html +++ b/manual/ko/setup.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ko/shadertoy.html b/manual/ko/shadertoy.html index 527b59ada4934d..c86cbdbda86bd3 100644 --- a/manual/ko/shadertoy.html +++ b/manual/ko/shadertoy.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ko/shadows.html b/manual/ko/shadows.html index a65fc58ee35ebc..2c6cbb7a7ca5c0 100644 --- a/manual/ko/shadows.html +++ b/manual/ko/shadows.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ko/textures.html b/manual/ko/textures.html index 9fccd23dacffe0..cb1b6defec4b28 100644 --- a/manual/ko/textures.html +++ b/manual/ko/textures.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ko/tips.html b/manual/ko/tips.html index 56c3771a222b0f..d2f094e099a5c2 100644 --- a/manual/ko/tips.html +++ b/manual/ko/tips.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ko/transparency.html b/manual/ko/transparency.html index 38c06483ef5f1a..9ac41864bbf64b 100644 --- a/manual/ko/transparency.html +++ b/manual/ko/transparency.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ko/voxel-geometry.html b/manual/ko/voxel-geometry.html index 81b5325692e206..76d41b4898ac97 100644 --- a/manual/ko/voxel-geometry.html +++ b/manual/ko/voxel-geometry.html @@ -11,10 +11,6 @@ - - - - - diff --git a/manual/ko/webxr-look-to-select.html b/manual/ko/webxr-look-to-select.html index cdd4301637e7f8..b2fa54b65fb006 100644 --- a/manual/ko/webxr-look-to-select.html +++ b/manual/ko/webxr-look-to-select.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ko/webxr-point-to-select.html b/manual/ko/webxr-point-to-select.html index 4cf974b6ca3c5c..b9ffe070cc67fe 100644 --- a/manual/ko/webxr-point-to-select.html +++ b/manual/ko/webxr-point-to-select.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/list.json b/manual/list.json index eeb6171a5570ad..33a460c3a47013 100644 --- a/manual/list.json +++ b/manual/list.json @@ -1,5 +1,27 @@ { "en": { + "Getting Started": { + "Installation": "en/installation", + "Creating a Scene": "en/creating-a-scene", + "Creating Text": "en/creating-text", + "Drawing Lines": "en/drawing-lines", + "FAQ": "en/faq", + "Libraries and Plugins": "en/libraries-and-plugins", + "Loading 3D Models": "en/loading-3d-models", + "Uniform Types": "en/uniform-types", + "Useful Links": "en/useful-links", + "WebGL Compatibility Check": "en/webgl-compatibility-check" + }, + "Next Steps": { + "Animation System": "en/animation-system", + "Color Management": "en/color-management", + "How to create VR content": "en/how-to-create-vr-content", + "How to dispose of Objects": "en/how-to-dispose-of-objects", + "How to update Things": "en/how-to-update-things", + "How to use Post Processing": "en/how-to-use-post-processing", + "Matrix Transformations": "en/matrix-transformations" + }, + "---": {}, "Basics": { "Fundamentals": "en/fundamentals", "Responsive Design": "en/responsive", @@ -41,7 +63,6 @@ "Multiple Canvases, Multiple Scenes": "en/multiple-scenes", "Picking Objects with the mouse": "en/picking", "Post Processing": "en/post-processing", - "Applying a LUT File for effects": "en/post-processing-3dlut", "Using Shadertoy shaders": "en/shadertoy", "Aligning HTML Elements to 3D": "en/align-html-elements-to-3d", "Using Indexed Textures for Picking and Color": "en/indexed-textures", @@ -102,7 +123,6 @@ "Multiple Canvases, Multiple Scenes": "fr/multiple-scenes", "Picking Objects with the mouse": "fr/picking", "Post Processing": "fr/post-processing", - "Applying a LUT File for effects": "fr/post-processing-3dlut", "Using Shadertoy shaders": "fr/shadertoy", "Aligning HTML Elements to 3D": "fr/align-html-elements-to-3d", "Using Indexed Textures for Picking and Color": "fr/indexed-textures", @@ -163,7 +183,6 @@ "複数キャンバスと複数シーン": "ja/multiple-scenes", "マウスでオブジェクトをピッキング": "ja/picking", "ポストプロセス": "ja/post-processing", - "エフェクトにLUTファイルを適用する": "ja/post-processing-3dlut", "Shadertoyのシェーダーを使う": "ja/shadertoy", "HTML要素を3Dに揃える": "ja/align-html-elements-to-3d", "圧縮テクスチャのピッキングとカラー": "ja/indexed-textures", @@ -224,7 +243,6 @@ "다중 캔버스, 다중 장면 만들기": "ko/multiple-scenes", "물체를 마우스로 피킹하기": "ko/picking", "후처리": "ko/post-processing", - "LUT 파일로 후처리 효과 적용하기": "ko/post-processing-3dlut", "쉐이더토이 쉐이더 활용하기": "ko/shadertoy", "HTML 요소를 3D로 정렬하기": "ko/align-html-elements-to-3d", "피킹과 색상에 인덱스 텍스처 사용하기": "ko/indexed-textures", @@ -285,7 +303,6 @@ "Несколько холстов, несколько сцен": "ru/multiple-scenes", "Picking Objects with the mouse": "ru/picking", "Post Processing": "ru/post-processing", - "Applying a LUT File for effects": "ru/post-processing-3dlut", "Using Shadertoy shaders": "ru/shadertoy", "Aligning HTML Elements to 3D": "ru/align-html-elements-to-3d", "Using Indexed Textures for Picking and Color": "ru/indexed-textures", @@ -336,7 +353,7 @@ "优化": { "大量对象的优化": "zh/optimize-lots-of-objects", "优化对象的同时保持动画效果": "zh/optimize-lots-of-objects-animated", - "Using OffscreenCanvas in a Web Worker": "zh/offscreencanvas" + "在Web Worker中使用离屏渲染": "zh/offscreencanvas" }, "解决方案": { "加载 .OBJ 文件": "zh/load-obj", @@ -346,20 +363,19 @@ "多个画布, 多个场景": "zh/multiple-scenes", "鼠标选取对象": "zh/picking", "后期处理": "zh/post-processing", - "Applying a LUT File for effects": "zh/post-processing-3dlut", - "Using Shadertoy shaders": "zh/shadertoy", + "使用Shadertoy中的着色器": "zh/shadertoy", "对齐HTML元素到3D对象": "zh/align-html-elements-to-3d", "使用纹理索引来拾取和着色": "zh/indexed-textures", "使用Canvas生成动态纹理": "zh/canvas-textures", "广告牌(Billboards)": "zh/billboards", "释放资源": "zh/cleanup", - "Making Voxel Geometry (Minecraft)": "zh/voxel-geometry", - "Start making a Game": "zh/game" + "体素几何体 (Minecraft)": "zh/voxel-geometry", + "来试试做一个游戏吧": "zh/game" }, "WebXR": { - "VR - Basics": "zh/webxr-basics", - "VR - Look To Select": "zh/webxr-look-to-select", - "VR - Point To Select": "zh/webxr-point-to-select" + "VR - 基础": "zh/webxr-basics", + "VR - 用目光进行选择": "zh/webxr-look-to-select", + "VR - 用点进行选择": "zh/webxr-point-to-select" }, "参考": { "材质特性表": "zh/material-table" diff --git a/manual/resources/lesson.css b/manual/resources/lesson.css index 1ece3c5308d27e..ae3f10897b04b0 100644 --- a/manual/resources/lesson.css +++ b/manual/resources/lesson.css @@ -96,8 +96,30 @@ li > p { } table { - margin-top: 1em; - margin-bottom: 1em; + margin-top: 1em; + margin-bottom: 1em; + width: 100%; + border-collapse: collapse; +} + +.desc { + padding-left: 0px; +} + +table th, +table td { + text-align: left; + vertical-align: top; + padding: 8px 6px; + border-bottom: var(--border-style); +} + +table th { + text-decoration: none; +} +table th:first-child, +table td:first-child { + padding-left: 0; } .warning { diff --git a/manual/resources/lesson.js b/manual/resources/lesson.js index 012a9a96e524f2..31ac826c35d036 100644 --- a/manual/resources/lesson.js +++ b/manual/resources/lesson.js @@ -48,6 +48,23 @@ } + const parts = window.location.href.split( '/' ); + const filename = parts[ parts.length - 1 ]; + + if ( filename !== 'primitives.html' ) { + + let text = document.body.innerHTML; + + text = text.replace( /\[link:([\w\:\/\.\-\_\(\)\?\#\=\!\~]+)\]/gi, '$1' ); // [link:url] + text = text.replace( /\[link:([\w:/.\-_()?#=!~]+) ([\w\p{L}:/.\-_'\s]+)\]/giu, '$2' ); // [link:url title] + text = text.replace( /\[example:([\w\_]+)\]/gi, '[example:$1 $1]' ); // [example:name] to [example:name title] + text = text.replace( /\[example:([\w\_]+) ([\w\:\/\.\-\_ \s]+)\]/gi, '$2' ); // [example:name title] + text = text.replace( /\`(.*?)\`/gs, '$1' ); // `code` + + document.body.innerHTML = text; + + } + if ( window.prettyPrint ) { window.prettyPrint(); diff --git a/manual/resources/srgb_gamut.png b/manual/resources/srgb_gamut.png new file mode 100644 index 00000000000000..cc24f07932fade Binary files /dev/null and b/manual/resources/srgb_gamut.png differ diff --git a/manual/resources/threejs-material-table.js b/manual/resources/threejs-material-table.js index cfe5552b74ea8d..c6ba1d49c6f47f 100644 --- a/manual/resources/threejs-material-table.js +++ b/manual/resources/threejs-material-table.js @@ -9,6 +9,8 @@ const materials = [ 'color', 'combine', 'envMap', + 'envMapRotation', + 'fog', 'lightMap', 'lightMapIntensity', 'map', @@ -16,6 +18,9 @@ const materials = [ 'refractionRatio', 'specularMap', 'wireframe', + 'wireframeLinecap', + 'wireframeLinejoin', + 'wireframeLinewidth' ], }, { @@ -29,13 +34,16 @@ const materials = [ 'bumpScale', 'color', 'combine', + 'displacementBias', 'displacementMap', 'displacementScale', - 'displacementBias', 'emissive', - 'emissiveMap', 'emissiveIntensity', + 'emissiveMap', 'envMap', + 'envMapRotation', + 'flatShading', + 'fog', 'lightMap', 'lightMapIntensity', 'map', @@ -46,6 +54,9 @@ const materials = [ 'refractionRatio', 'specularMap', 'wireframe', + 'wireframeLinecap', + 'wireframeLinejoin', + 'wireframeLinewidth' ], }, { @@ -59,13 +70,16 @@ const materials = [ 'bumpScale', 'color', 'combine', + 'displacementBias', 'displacementMap', 'displacementScale', - 'displacementBias', 'emissive', - 'emissiveMap', 'emissiveIntensity', + 'emissiveMap', 'envMap', + 'envMapRotation', + 'flatShading', + 'fog', 'lightMap', 'lightMapIntensity', 'map', @@ -78,6 +92,9 @@ const materials = [ 'specular', 'specularMap', 'wireframe', + 'wireframeLinecap', + 'wireframeLinejoin', + 'wireframeLinewidth' ], }, { @@ -90,14 +107,17 @@ const materials = [ 'bumpMap', 'bumpScale', 'color', + 'displacementBias', 'displacementMap', 'displacementScale', - 'displacementBias', 'emissive', - 'emissiveMap', 'emissiveIntensity', + 'emissiveMap', 'envMap', 'envMapIntensity', + 'envMapRotation', + 'flatShading', + 'fog', 'lightMap', 'lightMapIntensity', 'map', @@ -106,10 +126,12 @@ const materials = [ 'normalMap', 'normalMapType', 'normalScale', - 'refractionRatio', 'roughness', 'roughnessMap', 'wireframe', + 'wireframeLinecap', + 'wireframeLinejoin', + 'wireframeLinewidth' ], }, { @@ -119,38 +141,46 @@ const materials = [ 'alphaMap', 'aoMap', 'aoMapIntensity', + 'anisotropy', + 'anisotropyRotation', + 'anisotropyMap', + 'attenuationColor', + 'attenuationDistance', 'bumpMap', 'bumpScale', 'clearcoat', 'clearcoatMap', + 'clearcoatNormalMap', + 'clearcoatNormalScale', 'clearcoatRoughness', 'clearcoatRoughnessMap', - 'clearcoatNormalScale', - 'clearcoatNormalMap', 'color', + 'displacementBias', 'displacementMap', 'displacementScale', - 'displacementBias', 'emissive', - 'emissiveMap', 'emissiveIntensity', + 'emissiveMap', 'envMap', 'envMapIntensity', + 'envMapRotation', + 'flatShading', + 'fog', + 'ior', 'iridescence', - 'iridescenceMap', 'iridescenceIOR', - 'iridescenceThicknessRange', + 'iridescenceMap', 'iridescenceThicknessMap', + 'iridescenceThicknessRange', 'lightMap', 'lightMapIntensity', - 'ior', 'map', 'metalness', 'metalnessMap', 'normalMap', 'normalMapType', 'normalScale', - 'refractionRatio', + 'reflectivity', 'roughness', 'roughnessMap', 'sheen', @@ -158,31 +188,28 @@ const materials = [ 'sheenColorMap', 'sheenRoughness', 'sheenRoughnessMap', + 'specularColor', + 'specularColorMap', + 'specularIntensity', + 'specularIntensityMap', 'thickness', 'thicknessMap', 'transmission', 'transmissionMap', - 'attenuationDistance', - 'attenuationColor', - 'anisotropy', - 'anisotropyRotation', - 'anisotropyMap', - 'specularIntensity', - 'specularIntensityMap', - 'specularColor', - 'specularColorMap', 'wireframe', - 'reflectivity', + 'wireframeLinecap', + 'wireframeLinejoin', + 'wireframeLinewidth' ], }, ]; -const allProperties = {}; +const allProperties = new Set(); materials.forEach( ( material ) => { material.properties.forEach( ( property ) => { - allProperties[ property ] = true; + allProperties.add( property ); } ); @@ -222,7 +249,7 @@ const thead = addElem( 'thead', table ); } -Object.keys( allProperties ).sort().forEach( ( property ) => { +Array.from( allProperties ).sort().forEach( ( property ) => { const tr = addElem( 'tr', table ); addElem( 'td', tr, property ); diff --git a/manual/resources/threejs-primitives.js b/manual/resources/threejs-primitives.js index 29c67f5c5f6854..6f9bbcf6836c80 100644 --- a/manual/resources/threejs-primitives.js +++ b/manual/resources/threejs-primitives.js @@ -538,7 +538,7 @@ return geometry; ui: { text: { type: 'text', maxLength: 30, }, size: { type: 'range', min: 1, max: 10, precision: 1, }, - height: { type: 'range', min: 1, max: 10, precision: 1, }, + depth: { type: 'range', min: 1, max: 10, precision: 1, }, curveSegments: { type: 'range', min: 1, max: 20, }, // font', fonts ).onChange( generateGeometry ); // weight', weights ).onChange( generateGeometry ); @@ -548,7 +548,7 @@ return geometry; bevelSegments: { type: 'range', min: 0, max: 8, }, }, addConstCode: false, - create( text = 'three.js', size = 3, height = 0.2, curveSegments = 12, bevelEnabled = true, bevelThickness = 0.15, bevelSize = 0.3, bevelSegments = 5 ) { + create( text = 'three.js', size = 3, depth = 0.2, curveSegments = 12, bevelEnabled = true, bevelThickness = 0.15, bevelSize = 0.3, bevelSegments = 5 ) { return new Promise( ( resolve ) => { @@ -557,7 +557,7 @@ return geometry; resolve( new TextGeometry( text, { font: font, size, - height, + depth, curveSegments, bevelEnabled, bevelThickness, @@ -578,7 +578,7 @@ loader.load('../resources/threejs/fonts/helvetiker_regular.typeface.json', (font const geometry = new THREE.TextGeometry(text, { font: font, size: 3, // ui: size - height: 0.2, // ui: height + depth: 0.2, // ui: depth curveSegments: 12, // ui: curveSegments bevelEnabled: true, // ui: bevelEnabled bevelThickness: 0.15, // ui: bevelThickness @@ -830,7 +830,7 @@ const geometry = new THREE.WireframeGeometry( const a = document.createElement( 'a' ); a.setAttribute( 'target', '_blank' ); - a.href = href || `https://threejs.org/docs/#api/geometries/${name}`; + a.href = href || `https://threejs.org/docs/#api/en/geometries/${name}`; const code = document.createElement( 'code' ); code.textContent = name; a.appendChild( code ); @@ -842,7 +842,7 @@ const geometry = new THREE.WireframeGeometry( function addDeepLink( parent, name, href ) { const a = document.createElement( 'a' ); - a.href = href || `https://threejs.org/docs/#api/geometries/${name}`; + a.href = href || `https://threejs.org/docs/#api/en/geometries/${name}`; a.textContent = name; a.className = 'deep-link'; parent.appendChild( a ); diff --git a/manual/resources/threejs-textures.js b/manual/resources/threejs-textures.js index 3321a3559b70fb..0340b075201844 100644 --- a/manual/resources/threejs-textures.js +++ b/manual/resources/threejs-textures.js @@ -196,7 +196,7 @@ import { threejsLessonUtils } from './threejs-lesson-utils.js'; // but no checks for mipmaps I'm guessing. It seems like // they shouldn't be checking for same image, the should be // checking for same WebGLTexture. Given there is more than - // WebGL to support maybe they need to abtract WebGLTexture to + // WebGL to support maybe they need to abstract WebGLTexture to // PlatformTexture or something? const meshInfos = [ diff --git a/manual/resources/tools/geo-picking/shapefile.js b/manual/resources/tools/geo-picking/shapefile.js index 206755057fd2eb..a8acf84a43c823 100644 --- a/manual/resources/tools/geo-picking/shapefile.js +++ b/manual/resources/tools/geo-picking/shapefile.js @@ -97,7 +97,7 @@ var slice_slice = function(length) { return (function read() { return that._source.read().then(function(result) { - // When done, it’s possible the request wasn’t fully fullfilled! + // When done, it’s possible the request wasn’t fully fulfilled! // If so, the pre-allocated array is too big and needs slicing. if (result.done) { that._array = empty; diff --git a/manual/ru/align-html-elements-to-3d.html b/manual/ru/align-html-elements-to-3d.html index 50fb9336fd6a6c..c9a04614f8a5d2 100644 --- a/manual/ru/align-html-elements-to-3d.html +++ b/manual/ru/align-html-elements-to-3d.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ru/backgrounds.html b/manual/ru/backgrounds.html index c9d04b8f6c24bb..4a15730714bb8d 100644 --- a/manual/ru/backgrounds.html +++ b/manual/ru/backgrounds.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ru/billboards.html b/manual/ru/billboards.html index 3e027ae24400ff..2ddb73a49a68b3 100644 --- a/manual/ru/billboards.html +++ b/manual/ru/billboards.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ru/cameras.html b/manual/ru/cameras.html index 1c986e12cefb1d..b0fe8e6ac18e03 100644 --- a/manual/ru/cameras.html +++ b/manual/ru/cameras.html @@ -11,10 +11,6 @@ - - - - @@ -549,7 +545,7 @@

        - Камера

        - + diff --git a/manual/ru/canvas-textures.html b/manual/ru/canvas-textures.html index f4e51e0b7703aa..b4ec263ca24c6d 100644 --- a/manual/ru/canvas-textures.html +++ b/manual/ru/canvas-textures.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ru/cleanup.html b/manual/ru/cleanup.html index 17331f0cb1d63d..9bd753f85e5780 100644 --- a/manual/ru/cleanup.html +++ b/manual/ru/cleanup.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ru/custom-buffergeometry.html b/manual/ru/custom-buffergeometry.html index e82267f6e18e7b..863df54850638b 100644 --- a/manual/ru/custom-buffergeometry.html +++ b/manual/ru/custom-buffergeometry.html @@ -11,10 +11,6 @@ - - - - @@ -430,7 +419,7 @@

        Пользовательская BufferGeometry

        - + diff --git a/manual/ru/debugging-glsl.html b/manual/ru/debugging-glsl.html index 01b61fb17efc6a..4ad443e02ecdda 100644 --- a/manual/ru/debugging-glsl.html +++ b/manual/ru/debugging-glsl.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ru/debugging-javascript.html b/manual/ru/debugging-javascript.html index 8a481776a02b16..1a5b12999f0962 100644 --- a/manual/ru/debugging-javascript.html +++ b/manual/ru/debugging-javascript.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ru/fog.html b/manual/ru/fog.html index b928dba21ab985..4d4d0ddb643488 100644 --- a/manual/ru/fog.html +++ b/manual/ru/fog.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ru/fundamentals.html b/manual/ru/fundamentals.html index 5ad1a0c375d3dc..268625f119ac7e 100644 --- a/manual/ru/fundamentals.html +++ b/manual/ru/fundamentals.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ru/game.html b/manual/ru/game.html index f82f0c88074b86..55bf05dbb1be7b 100644 --- a/manual/ru/game.html +++ b/manual/ru/game.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ru/indexed-textures.html b/manual/ru/indexed-textures.html index c621e161ad8461..dbc08d677311d2 100644 --- a/manual/ru/indexed-textures.html +++ b/manual/ru/indexed-textures.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ru/lights.html b/manual/ru/lights.html index 8ecd0b86e0d040..a7776a5e97f596 100644 --- a/manual/ru/lights.html +++ b/manual/ru/lights.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ru/load-gltf.html b/manual/ru/load-gltf.html index 10de32178535a1..228bd6f6e9b186 100644 --- a/manual/ru/load-gltf.html +++ b/manual/ru/load-gltf.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ru/load-obj.html b/manual/ru/load-obj.html index 3cefd7dd33e576..261a4b568cd620 100644 --- a/manual/ru/load-obj.html +++ b/manual/ru/load-obj.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ru/material-table.html b/manual/ru/material-table.html index 486e805634f9c2..424cb3bbe8b206 100644 --- a/manual/ru/material-table.html +++ b/manual/ru/material-table.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ru/materials.html b/manual/ru/materials.html index d6b55650d7bd1c..78f736501065d0 100644 --- a/manual/ru/materials.html +++ b/manual/ru/materials.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ru/multiple-scenes.html b/manual/ru/multiple-scenes.html index b3251e5b0a68fd..e8fadd309687ef 100644 --- a/manual/ru/multiple-scenes.html +++ b/manual/ru/multiple-scenes.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ru/offscreencanvas.html b/manual/ru/offscreencanvas.html index f2eabd9e266c74..3e8edf4b2756e8 100644 --- a/manual/ru/offscreencanvas.html +++ b/manual/ru/offscreencanvas.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ru/optimize-lots-of-objects-animated.html b/manual/ru/optimize-lots-of-objects-animated.html index 02a3ca871c455b..b36f2884ee1ecd 100644 --- a/manual/ru/optimize-lots-of-objects-animated.html +++ b/manual/ru/optimize-lots-of-objects-animated.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ru/optimize-lots-of-objects.html b/manual/ru/optimize-lots-of-objects.html index 20fc573673d55b..2bebd7cb1b571e 100644 --- a/manual/ru/optimize-lots-of-objects.html +++ b/manual/ru/optimize-lots-of-objects.html @@ -11,10 +11,6 @@ - - - - @@ -482,7 +478,7 @@

        Оптимизация большого количества объекто - + diff --git a/manual/ru/picking.html b/manual/ru/picking.html index 6a43586982a27c..b3aef01f3d389a 100644 --- a/manual/ru/picking.html +++ b/manual/ru/picking.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ru/post-processing-3dlut.html b/manual/ru/post-processing-3dlut.html deleted file mode 100644 index 396d7b86a53a0d..00000000000000 --- a/manual/ru/post-processing-3dlut.html +++ /dev/null @@ -1,46 +0,0 @@ - - - Post Processing 3DLUT - - - - - - - - - - - - - - - - - -
        -
        -

        Post Processing 3DLUT

        -
        -
        - -
        -
        - - - - - - - - \ No newline at end of file diff --git a/manual/ru/post-processing.html b/manual/ru/post-processing.html index 20dfd5e4604922..02f20b716c9fce 100644 --- a/manual/ru/post-processing.html +++ b/manual/ru/post-processing.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ru/prerequisites.html b/manual/ru/prerequisites.html index cca5846df0a1c2..4ed4d4ed1afe5a 100644 --- a/manual/ru/prerequisites.html +++ b/manual/ru/prerequisites.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ru/primitives.html b/manual/ru/primitives.html index 9906c329973c0c..9303956cc112fc 100644 --- a/manual/ru/primitives.html +++ b/manual/ru/primitives.html @@ -11,10 +11,6 @@ - - - -

        @@ -288,7 +284,7 @@

        Примитивы

        - + diff --git a/manual/ru/rendering-on-demand.html b/manual/ru/rendering-on-demand.html index 5c7beaaaa1c686..d343bba5f9a8cd 100644 --- a/manual/ru/rendering-on-demand.html +++ b/manual/ru/rendering-on-demand.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ru/rendertargets.html b/manual/ru/rendertargets.html index 59f44e12621d6c..a4fae0e2f4dba3 100644 --- a/manual/ru/rendertargets.html +++ b/manual/ru/rendertargets.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ru/responsive.html b/manual/ru/responsive.html index 31a7671365a8f5..b84622d37b2c45 100644 --- a/manual/ru/responsive.html +++ b/manual/ru/responsive.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ru/scenegraph.html b/manual/ru/scenegraph.html index 66e3d65a4683d8..4941572c1db71c 100644 --- a/manual/ru/scenegraph.html +++ b/manual/ru/scenegraph.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ru/setup.html b/manual/ru/setup.html index 6c978d2799df29..45ce0cad82f924 100644 --- a/manual/ru/setup.html +++ b/manual/ru/setup.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ru/shadertoy.html b/manual/ru/shadertoy.html index 01ea74dd59f22f..61b0106fcdef36 100644 --- a/manual/ru/shadertoy.html +++ b/manual/ru/shadertoy.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ru/shadows.html b/manual/ru/shadows.html index ed946418602eae..0a86e380906e73 100644 --- a/manual/ru/shadows.html +++ b/manual/ru/shadows.html @@ -11,10 +11,6 @@ - - - - - - diff --git a/manual/ru/transparency.html b/manual/ru/transparency.html index 5699d7606c6c73..2ec392af679469 100644 --- a/manual/ru/transparency.html +++ b/manual/ru/transparency.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ru/voxel-geometry.html b/manual/ru/voxel-geometry.html index d1d7686816ecaf..ed3eba0cbd19aa 100644 --- a/manual/ru/voxel-geometry.html +++ b/manual/ru/voxel-geometry.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ru/webxr-basics.html b/manual/ru/webxr-basics.html index 0af72969caa486..782e13c805d8ed 100644 --- a/manual/ru/webxr-basics.html +++ b/manual/ru/webxr-basics.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ru/webxr-look-to-select.html b/manual/ru/webxr-look-to-select.html index 1815a1c28d3063..23aa5278a516a5 100644 --- a/manual/ru/webxr-look-to-select.html +++ b/manual/ru/webxr-look-to-select.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/ru/webxr-point-to-select.html b/manual/ru/webxr-point-to-select.html index b9920314c3fbdf..0c6f9d5af92d1b 100644 --- a/manual/ru/webxr-point-to-select.html +++ b/manual/ru/webxr-point-to-select.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/zh/align-html-elements-to-3d.html b/manual/zh/align-html-elements-to-3d.html index 3e172cb815b1b0..14b9912930bf9b 100644 --- a/manual/zh/align-html-elements-to-3d.html +++ b/manual/zh/align-html-elements-to-3d.html @@ -14,9 +14,6 @@ - - - - - \ No newline at end of file + diff --git a/manual/zh/canvas-textures.html b/manual/zh/canvas-textures.html index 1d35fff73117a8..08149d6eb60f3e 100644 --- a/manual/zh/canvas-textures.html +++ b/manual/zh/canvas-textures.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/zh/cleanup.html b/manual/zh/cleanup.html index c42a28ad7b0b53..cf878673370e12 100644 --- a/manual/zh/cleanup.html +++ b/manual/zh/cleanup.html @@ -11,10 +11,6 @@ - - - - - @@ -386,7 +381,7 @@

        自定义缓冲几何体

        - + diff --git a/manual/zh/debugging-glsl.html b/manual/zh/debugging-glsl.html index 45d04dede74d58..3e5161dfc61ed5 100644 --- a/manual/zh/debugging-glsl.html +++ b/manual/zh/debugging-glsl.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/zh/debugging-javascript.html b/manual/zh/debugging-javascript.html index 90e5e26e6f6199..b36a430e498b51 100644 --- a/manual/zh/debugging-javascript.html +++ b/manual/zh/debugging-javascript.html @@ -14,10 +14,6 @@ - - - - - diff --git a/manual/zh/fundamentals.html b/manual/zh/fundamentals.html index 1cd6cd393cd4b0..82d8221b9928ae 100644 --- a/manual/zh/fundamentals.html +++ b/manual/zh/fundamentals.html @@ -11,10 +11,6 @@ - - - - - \ No newline at end of file + diff --git a/manual/zh/game.html b/manual/zh/game.html index bb9dea7d01075c..1a6bd3c0641de1 100644 --- a/manual/zh/game.html +++ b/manual/zh/game.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/zh/indexed-textures.html b/manual/zh/indexed-textures.html index 1e74ca62ead002..78108fa3a1651a 100644 --- a/manual/zh/indexed-textures.html +++ b/manual/zh/indexed-textures.html @@ -14,9 +14,6 @@ - - - - diff --git a/manual/zh/load-gltf.html b/manual/zh/load-gltf.html index 01f331fe83fb4a..ea0dd47d4b74bd 100644 --- a/manual/zh/load-gltf.html +++ b/manual/zh/load-gltf.html @@ -14,10 +14,6 @@ - - - - - diff --git a/manual/zh/material-table.html b/manual/zh/material-table.html index f0180c41cf9f78..aecc2f70eb921c 100644 --- a/manual/zh/material-table.html +++ b/manual/zh/material-table.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/zh/materials.html b/manual/zh/materials.html index caa4af1af640b8..163143acb1a7d5 100644 --- a/manual/zh/materials.html +++ b/manual/zh/materials.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/zh/multiple-scenes.html b/manual/zh/multiple-scenes.html index 2dacd2bea151e3..2fcca250306255 100644 --- a/manual/zh/multiple-scenes.html +++ b/manual/zh/multiple-scenes.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/zh/offscreencanvas.html b/manual/zh/offscreencanvas.html index 6cc686ea8ff583..7c2315d5c75c79 100644 --- a/manual/zh/offscreencanvas.html +++ b/manual/zh/offscreencanvas.html @@ -14,9 +14,6 @@ - - - - - \ No newline at end of file + diff --git a/manual/zh/optimize-lots-of-objects.html b/manual/zh/optimize-lots-of-objects.html index c5eed4280885bd..5130419aa86523 100644 --- a/manual/zh/optimize-lots-of-objects.html +++ b/manual/zh/optimize-lots-of-objects.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/zh/picking.html b/manual/zh/picking.html index 028e2a6a8d49aa..0e618074dec069 100644 --- a/manual/zh/picking.html +++ b/manual/zh/picking.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/zh/post-processing-3dlut.html b/manual/zh/post-processing-3dlut.html deleted file mode 100644 index 4b50f40f6acc0b..00000000000000 --- a/manual/zh/post-processing-3dlut.html +++ /dev/null @@ -1,47 +0,0 @@ - - - Post Processing 3DLUT - - - - - - - - - - - - - - - - - - -
        -
        -

        Post Processing 3DLUT

        -
        -
        -
        -

        抱歉,还没有中文翻译哦。 欢迎加入翻译! 😄

        -

        英文原文链接.

        - -
        -
        -
        - - - - - - - - \ No newline at end of file diff --git a/manual/zh/post-processing.html b/manual/zh/post-processing.html index 29c456ad9ec3b5..479724ea2841a8 100644 --- a/manual/zh/post-processing.html +++ b/manual/zh/post-processing.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/zh/prerequisites.html b/manual/zh/prerequisites.html index 35ee6b947974c5..191840381c79cc 100644 --- a/manual/zh/prerequisites.html +++ b/manual/zh/prerequisites.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/zh/primitives.html b/manual/zh/primitives.html index cf67e168f05008..c5032a2d9e1c15 100644 --- a/manual/zh/primitives.html +++ b/manual/zh/primitives.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/zh/rendering-on-demand.html b/manual/zh/rendering-on-demand.html index 76824c768f3377..22f060e7e9e45e 100644 --- a/manual/zh/rendering-on-demand.html +++ b/manual/zh/rendering-on-demand.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/zh/rendertargets.html b/manual/zh/rendertargets.html index a8ee25bf402c15..f10f30ffa5acee 100644 --- a/manual/zh/rendertargets.html +++ b/manual/zh/rendertargets.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/zh/responsive.html b/manual/zh/responsive.html index 15efe18494c069..5abfe7f88594f1 100644 --- a/manual/zh/responsive.html +++ b/manual/zh/responsive.html @@ -14,9 +14,6 @@ - - - - diff --git a/manual/zh/setup.html b/manual/zh/setup.html index 35cc0fba48ee48..07b449046812e1 100644 --- a/manual/zh/setup.html +++ b/manual/zh/setup.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/zh/shadertoy.html b/manual/zh/shadertoy.html index 691fd38a8893e8..026b6d3c6761c1 100644 --- a/manual/zh/shadertoy.html +++ b/manual/zh/shadertoy.html @@ -1,47 +1,418 @@ - - - Three.js and Shadertoy - - - - - - - - - - - - - - - - - - -
        -
        -

        Three.js and Shadertoy

        -
        -
        -
        -

        抱歉,还没有中文翻译哦。 欢迎加入翻译! 😄

        -

        英文原文链接.

        + + + + +
        +
        +

        Three.js 与 Shadertoy

        +
        +
        +
        +

        Shadertoy 是一个有着众多惊艳的shader实践的著名网站。 经常有人问如何在 Three.js 里面使用那些shader。

        +

        重要的是要知道,被称作ShaderTOY 事出有因。 通常与其把 ShaderToy 里的shader当做最佳实践,不如称它们是有趣的挑战,比如:dwitter (代码少于140 个字符) 或js13kGames + (用不多于13k代码制作游戏)。

        +

        使用Shadertoy 的难题是, 给特定位置的像素着色写函数从而绘制有趣的图像。这是一种有趣的挑战,很多的结果非常惊艳。但请注意,这并非最佳实践。

        +

        点击 这个惊艳的shader绘制了整个城市

        +
        +

        在我的GPU 上全屏运行,它的运行速度为每秒大约5帧。与《城市:天际线》这样的游戏形成鲜明对比。

        +
        +

        这个游戏在同一台机器上每秒运行 30-60 帧,因为它使用更多 传统技术,建筑物由三角形绘制而成,并带有纹理,等等...

        +

        言归正传,让我们回到如何在three.js使用 Shadertoy的shader 。

        +

        当你在 shadertoy.com上点击“新建”,这是个初始的shader,至少 2019 年 1 月是这样的。

        +
        // By iq: https://www.shadertoy.com/user/iq
        +// license: Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
        +void mainImage( out vec4 fragColor, in vec2 fragCoord )
        +{
        +    // Normalized pixel coordinates (from 0 to 1)
        +    vec2 uv = fragCoord/iResolution.xy;
        +
        +    // Time varying pixel color
        +    vec3 col = 0.5 + 0.5*cos(iTime+uv.xyx+vec3(0,2,4));
        +
        +    // Output to screen
        +    fragColor = vec4(col,1.0);
        +}
        +
        +

        关于shader你首要知道的重点是,他们是用一种叫做GLSL (Graphics Library Shading Language)的语言写成的,这是一种专为3D 数学设计的强类型语言。在上面我们看到vec4, vec2,vec3 这三种特定类型。 一个 vec2 有2个value, 一个 vec3 + 有3个value,一个vec4 有4个 values。他们的使用方法非常灵活。最常见的用法是使用 x, y, z, 以及w 例如:

        +
        vec4 v1 = vec4(1.0, 2.0, 3.0, 4.0);
        +float v2 = v1.x + v1.y;  // adds 1.0 + 2.0
        +
        +

        与JavaScript不同,GLSL更像是C / C++,其中变量必须定义类型,所以不能写成这样var v = 1.2; + 而是通过 float v = 1.2;v 声明为浮点数。

        +

        详解 GLSL超出本文范畴。 概览GLSL可以点击本文 + ,进阶可以查看 本系列

        +

        注意,在2019 年 1 月, + shadertoy.com 仅关注 fragment + shaders. Fragment shader的职责在于,给定一个像素的位置,输出该像素颜色。 +

        +

        上面的代码我们看到 shader 有一个out 修饰的叫fragColor的参数。out 代表 output。这个参数向函数传递参数。我们需要将其设置为某种颜色。

        +

        它也有一个 叫 fragCoordin (代表 input)参数。 这代表了将要绘制的像素坐标。基于坐标我们可以生成特定颜色。 如果canvas有 400x300 像素,那么函数将会被调用 400x300 + 次或者说是 120,000 次。 每次 fragCoord 都是一个不同的像素坐标。

        +

        还有 2 个正在使用但未在代码中定义的变量, 一是 + iResolution。 该参数设置 canvas分辨率 。若该参数设置为 + 400x300 则 iResolution 是 400,300 。随着像素值 + 在400,300变化 uv 将在texture的纵横两个方向从 0.0 to 1.0 变化。 使用 + 规范化 值能简化工作,而且 shadertoy上大部分的 + shaders也以类似方式开始。 +

        +

        shader中另一个未定义的参数是 iTime。 该参数代表页面加载后的秒数。

        +

        上面这俩全局变量在shader术语中被称为 uniform 变量。 之所以被称为 uniform + 在于这些变量在shader的一次调用中保持uniform(统一),直到下一次shader调用。需要注意的是,这些参数都是在shadertoy定义的特定变量, 而非GLSL官方 + 变量。这俩变量是发明shadertoy的人定义的。

        +

        这篇 Shadertoy 文档中有更多定义。 现在让我们一起来写点代码来操作上面俩shader参数。

        +

        首先我们定义一个填充canvas的plane。 + 参考这篇关于背景的文章。 + 我们以这篇文章开始,不过要先删掉cube。代码很简单,如下:

        +
        function main() {
        +  const canvas = document.querySelector('#c');
        +  const renderer = new THREE.WebGLRenderer({antialias: true, canvas});
        +  renderer.autoClearColor = false;
        +
        +  const camera = new THREE.OrthographicCamera(
        +    -1, // left
        +     1, // right
        +     1, // top
        +    -1, // bottom
        +    -1, // near,
        +     1, // far
        +  );
        +  const scene = new THREE.Scene();
        +  const plane = new THREE.PlaneGeometry(2, 2);
        +  const material = new THREE.MeshBasicMaterial({
        +      color: 'red',
        +  });
        +  scene.add(new THREE.Mesh(plane, material));
        +
        +  function resizeRendererToDisplaySize(renderer) {
        +    const canvas = renderer.domElement;
        +    const width = canvas.clientWidth;
        +    const height = canvas.clientHeight;
        +    const needResize = canvas.width !== width || canvas.height !== height;
        +    if (needResize) {
        +      renderer.setSize(width, height, false);
        +    }
        +    return needResize;
        +  }
        +
        +  function render() {
        +    resizeRendererToDisplaySize(renderer);
        +
        +    renderer.render(scene, camera);
        +
        +    requestAnimationFrame(render);
        +  }
        +
        +  requestAnimationFrame(render);
        +}
        +
        +main();
        +
        +

        正如关于背景的文章所解释,这些参数将定义 + OrthographicCamera 以及一个大小是2个单位且被canvas填充的plane。 + 当前我们得到一个红色的canvas,因为我们使用的是红色 + MeshBasicMaterial材质。 +

        +

        + + +

        +

        现在我们添加shadertoy shader。

        +
        const fragmentShader = `
        +#include <common>
        +
        +uniform vec3 iResolution;
        +uniform float iTime;
        +
        +// By iq: https://www.shadertoy.com/user/iq
        +// license: Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
        +void mainImage( out vec4 fragColor, in vec2 fragCoord )
        +{
        +    // Normalized pixel coordinates (from 0 to 1)
        +    vec2 uv = fragCoord/iResolution.xy;
        +
        +    // Time varying pixel color
        +    vec3 col = 0.5 + 0.5*cos(iTime+uv.xyx+vec3(0,2,4));
        +
        +    // Output to screen
        +    fragColor = vec4(col,1.0);
        +}
         
        +void main() {
        +  mainImage(gl_FragColor, gl_FragCoord.xy);
        +}
        +`;
        +
        +

        上面我们定义了刚刚提到的2个uniform变量,接下来我们关注从shadertoy里的shader GLSL代码。我们调用 + mainImage ,同时传递 + gl_FragColorgl_FragCoord.xygl_FragColor + 是一个WebGL官方 + 全局变量,代表当前像素的颜色。gl_FragCoord 是另一个WebGL官方 + 全局变量,代表当前着色像素的坐标。 +

        +

        然后设置three.js uniforms,以便控制shader参数。

        +
        const uniforms = {
        +  iTime: { value: 0 },
        +  iResolution:  { value: new THREE.Vector3() },
        +};
        +
        +

        在THREE.js的每个uniform都有 value 参数。该参数必须与shader中的uniform类型匹配。

        +

        然后我们把fragmentshader和uniforms都传递给 + ShaderMaterial。 +

        +
        -const material = new THREE.MeshBasicMaterial({
        +-    color: 'red',
        +-});
        ++const material = new THREE.ShaderMaterial({
        ++  fragmentShader,
        ++  uniforms,
        ++});
        +
        +

        在渲染前,需要先设置uniforms的值。 +

        +
        -function render() {
        ++function render(time) {
        ++  time *= 0.001;  // convert to seconds
        +
        +  resizeRendererToDisplaySize(renderer);
        +
        ++  const canvas = renderer.domElement;
        ++  uniforms.iResolution.value.set(canvas.width, canvas.height, 1);
        ++  uniforms.iTime.value = time;
        +
        +  renderer.render(scene, camera);
        +
        +  requestAnimationFrame(render);
        +}
        +
        +
        +

        注意: + 不清楚为何iResolution是个vec3,而且 + shadertoy.com上的文档也没有说明第三个参数是啥,在上面没有用到第三个参数所以暂时设置为1。¯\_(ツ)_/¯ +

        +
        +

        + + +

        +

        上面定义的新shader效果与我们在 Shadertoy上看到的匹配, + 至少 2019 年 1 月是这样的 😉。这个shader做了些啥?

        +
          +
        • uv 从0变到1。
        • +
        • cos(uv.xyx)得到3个cos值,以vec3形式输出,一个是uv.x的cos值, 一个是uv.y的cos值,最后是uv.x的cos值。
        • +
        • 参数中加上时间 cos(iTime+uv.xyx)形成动画。
        • +
        • 另外vec3(0,2,4)参数与cos(iTime+uv.xyx+vec3(0,2,4)) 求和使cos波偏移。
        • +
        • cos 输出值范围从-1到1,所以经过0.5 * 0.5 + cos(...)从-1 <-> 1 变为 0.0 <-> 1.0
        • +
        • 计算结果作为RGB颜色赋予当前像素。
        • +
        +

        为了更容易看出cos波形我们稍微调整一下代码。当前uv + 仅能从0到1,因cos波形在2π处重复,我们通过将uv乘上40,实现cos波形从0到40的变化,这将会使cos波形重复大约6.3次。

        +
        -vec3 col = 0.5 + 0.5*cos(iTime+uv.xyx+vec3(0,2,4));
        ++vec3 col = 0.5 + 0.5*cos(iTime+uv.xyx*40.0+vec3(0,2,4));
        +
        +

        如下我数了下大约是重复了6.3次,通过 +vec3(0,2,4)偏移了4因此我们能看到红蓝相间,否则我们将看到红蓝颜色混合为紫色。

        +

        + + +

        +

        了解到输入如此简单,当看到如 + a city canal, + a forest, + a snail, + a + mushroom这些结果,让人更觉得充满挑战。幸运的是这也清晰的说明为何相对于传统的三角形构成的场景,这通常这不是正确的方式。因为每个像素颜色都需要经过许多数学计算,通常会导致运行缓慢。 +

        +

        有些shadertoy的shaders使用纹理贴图作为输入,比如这个

        +
        // By Daedelus: https://www.shadertoy.com/user/Daedelus
        +// license: Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
        +#define TIMESCALE 0.25
        +#define TILES 8
        +#define COLOR 0.7, 1.6, 2.8
        +
        +void mainImage( out vec4 fragColor, in vec2 fragCoord )
        +{
        +    vec2 uv = fragCoord.xy / iResolution.xy;
        +    uv.x *= iResolution.x / iResolution.y;
        +
        +    vec4 noise = texture2D(iChannel0, floor(uv * float(TILES)) / float(TILES));
        +    float p = 1.0 - mod(noise.r + noise.g + noise.b + iTime * float(TIMESCALE), 1.0);
        +    p = min(max(p * 3.0 - 1.8, 0.1), 2.0);
        +
        +    vec2 r = mod(uv * float(TILES), 1.0);
        +    r = vec2(pow(r.x - 0.5, 2.0), pow(r.y - 0.5, 2.0));
        +    p *= 1.0 - pow(min(1.0, 12.0 * dot(r, r)), 2.0);
        +
        +    fragColor = vec4(COLOR, 1.0) * p;
        +}
        +
        +

        给shader传递纹理与给常规材质传递纹理一样,只不过需要通过uniforms来设置纹理。

        +

        首先需要给shader添加一个纹理的uniform。在GLSL中对应为 + sampler2D 。 +

        +
        const fragmentShader = `
        +#include <common>
        +
        +uniform vec3 iResolution;
        +uniform float iTime;
        ++uniform sampler2D iChannel0;
        +
        +...
        +
        +

        然后我们可以像这里一样载入纹理,并且设置uniform的值。

        +
        +const loader = new THREE.TextureLoader();
        ++const texture = loader.load('resources/images/bayer.png');
        ++texture.minFilter = THREE.NearestFilter;
        ++texture.magFilter = THREE.NearestFilter;
        ++texture.wrapS = THREE.RepeatWrapping;
        ++texture.wrapT = THREE.RepeatWrapping;
        +const uniforms = {
        +  iTime: { value: 0 },
        +  iResolution:  { value: new THREE.Vector3() },
        ++  iChannel0: { value: texture },
        +};
        +
        +

        + + +

        +

        到目前为止,我们一直用Shadertoy.com上的方式使用 Shadertoy + shaders,即在canvas上绘制shader。但我们无需受限于此。请留意,通常人们在Shadertoy上写的函数仅输入一个fragCoord 和一个iResolution参数。fragCoord 不一定来自像素坐标,像纹理坐标也可以,然后就可以像常规的纹理一样使用。通常把这种通过函数生成纹理的技术叫做procedural texture

        +

        让我们改一改上面的shader,最简单的莫过于使用three.js提供的纹理坐标,乘上iResolution再传到fragCoords

        +

        我们需要加一个varying变量。varing变量通过对顶点进行插值(也叫varied)实现从vertex shader传值到fragment shader。在fragment + shader中使用之前需要先声明该变量。这个变量名中的 uv代表纹理坐标,前面的v代表varying

        +
        ...
        +
        ++varying vec2 vUv;
        +
        +void main() {
        +-  mainImage(gl_FragColor, gl_FragCoord.xy);
        ++  mainImage(gl_FragColor, vUv * iResolution.xy);
        +}
        +
        +

        然后我们需要实现vertex shader,下面是最简化的three.js的vertex shader。three.js中定义了uvprojectionMatrixmodelViewMatrix,和 position这几个参数,且可以传值给shader。

        +
        const vertexShader = `
        +  varying vec2 vUv;
        +  void main() {
        +    vUv = uv;
        +    gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
        +  }
        +`;
        +
        +

        把vertexshader传给ShaderMaterial

        +
        const material = new THREE.ShaderMaterial({
        +  vertexShader,
        +  fragmentShader,
        +  uniforms,
        +});
        +
        +

        因为iResolution保持不变,因此可以在初始化时设定它的值。

        +
        const uniforms = {
        +  iTime: { value: 0 },
        +-  iResolution:  { value: new THREE.Vector3() },
        ++  iResolution:  { value: new THREE.Vector3(1, 1, 1) },
        +  iChannel0: { value: texture },
        +};
        +
        +

        在渲染时无需设置它的值。

        +
        -const canvas = renderer.domElement;
        +-uniforms.iResolution.value.set(canvas.width, canvas.height, 1);
        +uniforms.iTime.value = time;
        +
        +

        另外我从关于响应能力的文章复制了一段3个旋转cube代码。效果如下:

        +

        + + +

        +

        希望这篇文字能说清在three.js使用shadertoy shader的入门方法。再次重申,大部分的shadertoy + shaders与其说是性能方面的最佳实践,不如称它们是有趣的挑战(通过函数实现所有绘制)。尽管如此,他们还是有着令人印象深刻的惊艳和美,了解shader工作原理可以学到很多东西。

        - +
        + - \ No newline at end of file + + + diff --git a/manual/zh/shadows.html b/manual/zh/shadows.html index bb54d033505f1e..9ba2ed8fd438b7 100644 --- a/manual/zh/shadows.html +++ b/manual/zh/shadows.html @@ -11,10 +11,6 @@ - - - - - \ No newline at end of file + diff --git a/manual/zh/textures.html b/manual/zh/textures.html index 7c30acfb23f798..a7f75164c7fa1f 100644 --- a/manual/zh/textures.html +++ b/manual/zh/textures.html @@ -11,10 +11,6 @@ - - - - - \ No newline at end of file + diff --git a/manual/zh/tips.html b/manual/zh/tips.html index 3f0f184871ffcf..54763b2fd50638 100644 --- a/manual/zh/tips.html +++ b/manual/zh/tips.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/zh/transparency.html b/manual/zh/transparency.html index 37333f421ef49e..7c037a6606cb0c 100644 --- a/manual/zh/transparency.html +++ b/manual/zh/transparency.html @@ -11,10 +11,6 @@ - - - - - diff --git a/manual/zh/webxr-basics.html b/manual/zh/webxr-basics.html index ad7d4931a585ff..1c17d3f9cadf85 100644 --- a/manual/zh/webxr-basics.html +++ b/manual/zh/webxr-basics.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/zh/webxr-look-to-select.html b/manual/zh/webxr-look-to-select.html index 70a1a11201b67e..62ebb044727eb2 100644 --- a/manual/zh/webxr-look-to-select.html +++ b/manual/zh/webxr-look-to-select.html @@ -11,10 +11,6 @@ - - - - diff --git a/manual/zh/webxr-point-to-select.html b/manual/zh/webxr-point-to-select.html index b54145fed36446..f5f1a5ade3a423 100644 --- a/manual/zh/webxr-point-to-select.html +++ b/manual/zh/webxr-point-to-select.html @@ -11,10 +11,6 @@ - - - - diff --git a/package-lock.json b/package-lock.json index d19479bbacb491..7543abf870c97f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,80 +1,151 @@ { "name": "three", - "version": "0.153.0", - "lockfileVersion": 2, + "version": "0.177.0", + "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "three", - "version": "0.153.0", + "version": "0.177.0", "license": "MIT", "devDependencies": { - "@rollup/plugin-node-resolve": "^15.0.1", + "@rollup/plugin-node-resolve": "^16.0.0", "@rollup/plugin-terser": "^0.4.0", "chalk": "^5.2.0", - "concurrently": "^8.0.1", + "concurrently": "^9.0.0", + "dpdm": "^3.14.0", "eslint": "^8.37.0", "eslint-config-mdcs": "^5.0.0", - "eslint-plugin-compat": "^4.1.2", - "eslint-plugin-html": "^7.1.0", + "eslint-plugin-compat": "^6.0.0", + "eslint-plugin-html": "^8.0.0", "eslint-plugin-import": "^2.27.5", "failonlyreporter": "^1.0.0", - "jimp": "^0.22.7", + "jimp": "^1.6.0", + "jsdoc": "^4.0.4", "magic-string": "^0.30.0", - "pixelmatch": "^5.3.0", - "puppeteer-core": "^19.8.1", + "pixelmatch": "^7.0.0", + "puppeteer": "^22.0.0", "qunit": "^2.19.4", - "rollup": "^3.20.2", + "rollup": "^4.6.0", "rollup-plugin-filesize": "^10.0.0", - "rollup-plugin-visualizer": "^5.9.0", - "servez": "^1.14.2" + "rollup-plugin-visualizer": "^6.0.0", + "servez": "^2.2.4" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.7.tgz", + "integrity": "sha512-qnzXzDXdr/po3bOTbTIQZ7+TxNKxpkN5IifVLXS+r7qwynkZfPyjZfE7hCXbo7IoO9TNcSyibgONsf2HauUd3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" } }, "node_modules/@babel/runtime": { - "version": "7.20.13", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz", - "integrity": "sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==", + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz", + "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.7.tgz", + "integrity": "sha512-8OLQgDScAOHXnAz2cV+RfzzNMipuLVBz2biuAJFMV9bfkNf393je3VM8CLkjQodW5+iWsSJdSgSWT6rsZoXHPw==", "dev": true, + "license": "MIT", "dependencies": { - "regenerator-runtime": "^0.13.11" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.2.0.tgz", - "integrity": "sha512-gB8T4H4DEfX2IV9zGDJPOBgP1e/DbfCPDTtEqUMckpvzS1OYtva8JdFYBqMwYk7xAQ429WGF/UPqn8uQ//h2vQ==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", "dev": true, + "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.3" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, + "funding": { + "url": "https://opencollective.com/eslint" + }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "node_modules/@eslint-community/regexpp": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.4.0.tgz", - "integrity": "sha512-A9983Q0LnDGdLPjxyXQ00sbV+K+O+ko2Dr+CZigbHWtX9pNfxlaBkMR8X1CztI73zuEyEBXTVjx7CE+/VSwDiQ==", + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", "dev": true, + "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/eslintrc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz", - "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, + "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.5.2", + "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -90,10 +161,11 @@ } }, "node_modules/@eslint/js": { - "version": "8.40.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.40.0.tgz", - "integrity": "sha512-ElyB54bJIhXQYVKjDSvCkPO1iU1tSAeVQJbllWJq1XQSmmA4dgFk8CbiBGpiOPxleE48vDogxCtmMYku4HSVLA==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true, + "license": "MIT", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } @@ -102,16 +174,19 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", - "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", "minimatch": "^3.0.5" }, "engines": { @@ -123,6 +198,7 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=12.22" }, @@ -132,536 +208,599 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" }, - "node_modules/@jimp/bmp": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.22.8.tgz", - "integrity": "sha512-JEMKgM1AEvvWfn9ZCHn62nK+QCE3Pb/ZhPdL3NF0ZgKNww6pqOmo6KqXzqY18JLB7c0epuTp4GPDPDhOh/ou1g==", + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, + "license": "ISC", "dependencies": { - "@jimp/utils": "^0.22.8", - "bmp-js": "^0.1.0" + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" + "engines": { + "node": ">=12" } }, - "node_modules/@jimp/core": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.22.8.tgz", - "integrity": "sha512-vkN28aFikzQieA6bGxN+qe20pseCAemCyUI0YmRkJIArlb6OujtAwWAKyokv2lylV56bq8EQGIz+Y30OXUnRqg==", + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, - "dependencies": { - "@jimp/utils": "^0.22.8", - "any-base": "^1.1.0", - "buffer": "^5.2.0", - "exif-parser": "^0.1.12", - "file-type": "^16.5.4", - "isomorphic-fetch": "^3.0.0", - "mkdirp": "^2.1.3", - "pixelmatch": "^4.0.2", - "tinycolor2": "^1.6.0" + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/@jimp/core/node_modules/mkdirp": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.6.tgz", - "integrity": "sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==", + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, - "bin": { - "mkdirp": "dist/cjs/src/bin.js" + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" }, "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/@jimp/core/node_modules/pixelmatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-4.0.2.tgz", - "integrity": "sha512-J8B6xqiO37sU/gkcMglv6h5Jbd9xNER7aHzpfRdNmV4IbQBzBpe4l9XmbG+xPF/znacgu2jfEw+wHffaq/YkXA==", + "node_modules/@jimp/core": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/core/-/core-1.6.0.tgz", + "integrity": "sha512-EQQlKU3s9QfdJqiSrZWNTxBs3rKXgO2W+GxNXDtwchF3a4IqxDheFX1ti+Env9hdJXDiYLp2jTRjlxhPthsk8w==", "dev": true, + "license": "MIT", "dependencies": { - "pngjs": "^3.0.0" + "@jimp/file-ops": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "await-to-js": "^3.0.0", + "exif-parser": "^0.1.12", + "file-type": "^16.0.0", + "mime": "3" }, - "bin": { - "pixelmatch": "bin/pixelmatch" + "engines": { + "node": ">=18" } }, - "node_modules/@jimp/core/node_modules/pngjs": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", - "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==", + "node_modules/@jimp/diff": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/diff/-/diff-1.6.0.tgz", + "integrity": "sha512-+yUAQ5gvRC5D1WHYxjBHZI7JBRusGGSLf8AmPRPCenTzh4PA+wZ1xv2+cYqQwTfQHU5tXYOhA0xDytfHUf1Zyw==", "dev": true, + "license": "MIT", + "dependencies": { + "@jimp/plugin-resize": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "pixelmatch": "^5.3.0" + }, "engines": { - "node": ">=4.0.0" + "node": ">=18" } }, - "node_modules/@jimp/custom": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.22.8.tgz", - "integrity": "sha512-u6lP9x/HNeGHB0Oojv4c2mhuDvn7G0ikzYbK4IKLsH4HzHxt62faMjBzQMcFhKJhR6UiiKE/jiHrhGvBT/fMkw==", + "node_modules/@jimp/diff/node_modules/pixelmatch": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.3.0.tgz", + "integrity": "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==", "dev": true, + "license": "ISC", "dependencies": { - "@jimp/core": "^0.22.8" + "pngjs": "^6.0.0" + }, + "bin": { + "pixelmatch": "bin/pixelmatch" } }, - "node_modules/@jimp/gif": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/gif/-/gif-0.22.8.tgz", - "integrity": "sha512-I0l6koS67IPU40RPxCJTD1NvePEd8vUIHTejx1ly0jrjGnumbqdarAlBUkDrKfPPc+Fnqp84hBbSN1w5hNPT6w==", + "node_modules/@jimp/diff/node_modules/pngjs": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", + "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", "dev": true, - "dependencies": { - "@jimp/utils": "^0.22.8", - "gifwrap": "^0.9.2", - "omggif": "^1.0.9" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" + "license": "MIT", + "engines": { + "node": ">=12.13.0" } }, - "node_modules/@jimp/jpeg": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/jpeg/-/jpeg-0.22.8.tgz", - "integrity": "sha512-hLXrQ7/0QiUhAVAF10dfGCSq3hvyqjKltlpu/87b3wqMDKe9KdvhX1AJHiUUrAbJv1fAcnOmQGTyXGuySa1D6A==", + "node_modules/@jimp/file-ops": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/file-ops/-/file-ops-1.6.0.tgz", + "integrity": "sha512-Dx/bVDmgnRe1AlniRpCKrGRm5YvGmUwbDzt+MAkgmLGf+jvBT75hmMEZ003n9HQI/aPnm/YKnXjg/hOpzNCpHQ==", "dev": true, - "dependencies": { - "@jimp/utils": "^0.22.8", - "jpeg-js": "^0.4.4" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" + "license": "MIT", + "engines": { + "node": ">=18" } }, - "node_modules/@jimp/plugin-blit": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.22.8.tgz", - "integrity": "sha512-rQ19txVCKIwo74HtgFodFt4//0ATPCJK+f24riqzb+nx+1JaOo1xRvpJqg4moirHwKR2fhwdDxmY7KX20kCeYA==", + "node_modules/@jimp/js-bmp": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/js-bmp/-/js-bmp-1.6.0.tgz", + "integrity": "sha512-FU6Q5PC/e3yzLyBDXupR3SnL3htU7S3KEs4e6rjDP6gNEOXRFsWs6YD3hXuXd50jd8ummy+q2WSwuGkr8wi+Gw==", "dev": true, + "license": "MIT", "dependencies": { - "@jimp/utils": "^0.22.8" + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "bmp-ts": "^1.0.9" }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" + "engines": { + "node": ">=18" } }, - "node_modules/@jimp/plugin-blur": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-0.22.8.tgz", - "integrity": "sha512-GWbNK3YW6k2EKiGJdpAFEr0jezPBtiVxj2wG/lCPuWJz7KmzSSN99hQjIy73xQxoBCRdALfJlkhe3leFNRueSQ==", + "node_modules/@jimp/js-gif": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/js-gif/-/js-gif-1.6.0.tgz", + "integrity": "sha512-N9CZPHOrJTsAUoWkWZstLPpwT5AwJ0wge+47+ix3++SdSL/H2QzyMqxbcDYNFe4MoI5MIhATfb0/dl/wmX221g==", "dev": true, + "license": "MIT", "dependencies": { - "@jimp/utils": "^0.22.8" + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "gifwrap": "^0.10.1", + "omggif": "^1.0.10" }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" + "engines": { + "node": ">=18" } }, - "node_modules/@jimp/plugin-circle": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-0.22.8.tgz", - "integrity": "sha512-qPCw8XFW8opT89ciFDuvs+eB3EB1mZIJWVajD2qAlprHiE7YGr34TkM7N5MNr3qZ1pJgkYdW6+HbBrJwBaonqw==", + "node_modules/@jimp/js-jpeg": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/js-jpeg/-/js-jpeg-1.6.0.tgz", + "integrity": "sha512-6vgFDqeusblf5Pok6B2DUiMXplH8RhIKAryj1yn+007SIAQ0khM1Uptxmpku/0MfbClx2r7pnJv9gWpAEJdMVA==", "dev": true, + "license": "MIT", "dependencies": { - "@jimp/utils": "^0.22.8" + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "jpeg-js": "^0.4.4" }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" + "engines": { + "node": ">=18" } }, - "node_modules/@jimp/plugin-color": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.22.8.tgz", - "integrity": "sha512-ogkbg6rpDVH/mMLgAQKg17z3oZE0VN7ZWxNoH12fUHchqKz1I57zpa65fxZe2I8T5Xz97HR3x+7V7oI8qQGdSA==", + "node_modules/@jimp/js-png": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/js-png/-/js-png-1.6.0.tgz", + "integrity": "sha512-AbQHScy3hDDgMRNfG0tPjL88AV6qKAILGReIa3ATpW5QFjBKpisvUaOqhzJ7Reic1oawx3Riyv152gaPfqsBVg==", "dev": true, + "license": "MIT", "dependencies": { - "@jimp/utils": "^0.22.8", - "tinycolor2": "^1.6.0" + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "pngjs": "^7.0.0" }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" + "engines": { + "node": ">=18" } }, - "node_modules/@jimp/plugin-contain": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-0.22.8.tgz", - "integrity": "sha512-oiaPLdJt9Dk+XEEhM/OU3lFemM51mA9NgMCAdburSCjDzKacJYBGFSHjTOhXzcxOie/ZDpOYN/UzFGKy8Dgl9A==", + "node_modules/@jimp/js-tiff": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/js-tiff/-/js-tiff-1.6.0.tgz", + "integrity": "sha512-zhReR8/7KO+adijj3h0ZQUOiun3mXUv79zYEAKvE0O+rP7EhgtKvWJOZfRzdZSNv0Pu1rKtgM72qgtwe2tFvyw==", "dev": true, + "license": "MIT", "dependencies": { - "@jimp/utils": "^0.22.8" + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "utif2": "^4.1.0" }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5", - "@jimp/plugin-blit": ">=0.3.5", - "@jimp/plugin-resize": ">=0.3.5", - "@jimp/plugin-scale": ">=0.3.5" + "engines": { + "node": ">=18" } }, - "node_modules/@jimp/plugin-cover": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-0.22.8.tgz", - "integrity": "sha512-mO68w1m/LhfuHU8LKHY05a4/hhWnY4t+T+8JCw9t+5yfzA4+LofBZZKtFtWgwf/QGe1y3X2rtUU/avAzDUKyyA==", + "node_modules/@jimp/plugin-blit": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-1.6.0.tgz", + "integrity": "sha512-M+uRWl1csi7qilnSK8uxK4RJMSuVeBiO1AY0+7APnfUbQNZm6hCe0CCFv1Iyw1D/Dhb8ph8fQgm5mwM0eSxgVA==", "dev": true, + "license": "MIT", "dependencies": { - "@jimp/utils": "^0.22.8" + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5", - "@jimp/plugin-crop": ">=0.3.5", - "@jimp/plugin-resize": ">=0.3.5", - "@jimp/plugin-scale": ">=0.3.5" + "engines": { + "node": ">=18" } }, - "node_modules/@jimp/plugin-crop": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.22.8.tgz", - "integrity": "sha512-ns4oH0h0gezYsbuH8RThcMLY5uTLk/vnqOVjWCehMHEzxi0DHMWCmpcb6bC//vJ+XFNhtVGn1ALN7+ROmPrj+A==", + "node_modules/@jimp/plugin-blur": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-1.6.0.tgz", + "integrity": "sha512-zrM7iic1OTwUCb0g/rN5y+UnmdEsT3IfuCXCJJNs8SZzP0MkZ1eTvuwK9ZidCuMo4+J3xkzCidRwYXB5CyGZTw==", "dev": true, + "license": "MIT", "dependencies": { - "@jimp/utils": "^0.22.8" + "@jimp/core": "1.6.0", + "@jimp/utils": "1.6.0" }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" + "engines": { + "node": ">=18" } }, - "node_modules/@jimp/plugin-displace": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-0.22.8.tgz", - "integrity": "sha512-Cj8nHYgsdFynOIx3dbbiVwRuZn3xO+RVfwkTRy0JBye+K2AU8SQJS+hSFNMQFTZt5djivh6kh0TzvR/6LkOd1w==", + "node_modules/@jimp/plugin-circle": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-1.6.0.tgz", + "integrity": "sha512-xt1Gp+LtdMKAXfDp3HNaG30SPZW6AQ7dtAtTnoRKorRi+5yCJjKqXRgkewS5bvj8DEh87Ko1ydJfzqS3P2tdWw==", "dev": true, + "license": "MIT", "dependencies": { - "@jimp/utils": "^0.22.8" + "@jimp/types": "1.6.0", + "zod": "^3.23.8" }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" + "engines": { + "node": ">=18" } }, - "node_modules/@jimp/plugin-dither": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-0.22.8.tgz", - "integrity": "sha512-oE0Us/6bEgrgEg56plU3jSBzvB9iGhweKUHmxYMWnQbFCHP4mNCtPAs8+Fmq6c+m98ZgBgRcrJTnC7lphHkGyw==", + "node_modules/@jimp/plugin-color": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-1.6.0.tgz", + "integrity": "sha512-J5q8IVCpkBsxIXM+45XOXTrsyfblyMZg3a9eAo0P7VPH4+CrvyNQwaYatbAIamSIN1YzxmO3DkIZXzRjFSz1SA==", "dev": true, + "license": "MIT", "dependencies": { - "@jimp/utils": "^0.22.8" + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "tinycolor2": "^1.6.0", + "zod": "^3.23.8" }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" + "engines": { + "node": ">=18" } }, - "node_modules/@jimp/plugin-fisheye": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-0.22.8.tgz", - "integrity": "sha512-bWvYY/nfMcKclWEaRyAir+YsT6C5St823HUQAsewZowTrJmme+w4U2a6InsryTHUL01BBcV5BLH0aDHuV3StvA==", + "node_modules/@jimp/plugin-contain": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-1.6.0.tgz", + "integrity": "sha512-oN/n+Vdq/Qg9bB4yOBOxtY9IPAtEfES8J1n9Ddx+XhGBYT1/QTU/JYkGaAkIGoPnyYvmLEDqMz2SGihqlpqfzQ==", "dev": true, + "license": "MIT", "dependencies": { - "@jimp/utils": "^0.22.8" + "@jimp/core": "1.6.0", + "@jimp/plugin-blit": "1.6.0", + "@jimp/plugin-resize": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" + "engines": { + "node": ">=18" } }, - "node_modules/@jimp/plugin-flip": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-0.22.8.tgz", - "integrity": "sha512-0NFTNzjsdmOQkaIkNjZqO3/yU4SQb9nnWQXsLS1fFo+9QrIL5v8vVkXpk/rhiND6PyTj2mMTNjOa76GuZcC+iQ==", + "node_modules/@jimp/plugin-cover": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-1.6.0.tgz", + "integrity": "sha512-Iow0h6yqSC269YUJ8HC3Q/MpCi2V55sMlbkkTTx4zPvd8mWZlC0ykrNDeAy9IJegrQ7v5E99rJwmQu25lygKLA==", "dev": true, + "license": "MIT", "dependencies": { - "@jimp/utils": "^0.22.8" + "@jimp/core": "1.6.0", + "@jimp/plugin-crop": "1.6.0", + "@jimp/plugin-resize": "1.6.0", + "@jimp/types": "1.6.0", + "zod": "^3.23.8" }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5", - "@jimp/plugin-rotate": ">=0.3.5" + "engines": { + "node": ">=18" } }, - "node_modules/@jimp/plugin-gaussian": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugin-gaussian/-/plugin-gaussian-0.22.8.tgz", - "integrity": "sha512-E/f14aLzCS50QAM7K+InI9V61KVy/Zx52vy7Jjfo1h7qKhQHss3PYaydaH0N6qlXRNeXgh+4/32P9JfieLMcdw==", + "node_modules/@jimp/plugin-crop": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-1.6.0.tgz", + "integrity": "sha512-KqZkEhvs+21USdySCUDI+GFa393eDIzbi1smBqkUPTE+pRwSWMAf01D5OC3ZWB+xZsNla93BDS9iCkLHA8wang==", "dev": true, + "license": "MIT", "dependencies": { - "@jimp/utils": "^0.22.8" + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" + "engines": { + "node": ">=18" } }, - "node_modules/@jimp/plugin-invert": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugin-invert/-/plugin-invert-0.22.8.tgz", - "integrity": "sha512-UauP39FF2cwbA5VU+Tz9VlNa9rtULPSHZb0Huwcjqjm9/G/xVN69VJ8+RKiFC4zM1/kYAUp/6IRwPa6qdKJpSw==", + "node_modules/@jimp/plugin-displace": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-1.6.0.tgz", + "integrity": "sha512-4Y10X9qwr5F+Bo5ME356XSACEF55485j5nGdiyJ9hYzjQP9nGgxNJaZ4SAOqpd+k5sFaIeD7SQ0Occ26uIng5Q==", "dev": true, + "license": "MIT", "dependencies": { - "@jimp/utils": "^0.22.8" + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" + "engines": { + "node": ">=18" } }, - "node_modules/@jimp/plugin-mask": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-0.22.8.tgz", - "integrity": "sha512-bhg5+3i8x1CmYj6cjvPBQZLwZEI3iK3gJWF25ZHF+12d3cqDuJngtr8oRQOQLlAgvKmrj9FXIiEPDczUI9cnWQ==", + "node_modules/@jimp/plugin-dither": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-1.6.0.tgz", + "integrity": "sha512-600d1RxY0pKwgyU0tgMahLNKsqEcxGdbgXadCiVCoGd6V6glyCvkNrnnwC0n5aJ56Htkj88PToSdF88tNVZEEQ==", "dev": true, + "license": "MIT", "dependencies": { - "@jimp/utils": "^0.22.8" + "@jimp/types": "1.6.0" }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" + "engines": { + "node": ">=18" } }, - "node_modules/@jimp/plugin-normalize": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugin-normalize/-/plugin-normalize-0.22.8.tgz", - "integrity": "sha512-Yg5nreAR1JYuSObu3ExlgaLxVeW6VvjVL5qFwiPFxSNlG8JIwL1Ir3K3ChSnnvymyZvJMHb6YKTYNfXKw5Da6g==", + "node_modules/@jimp/plugin-fisheye": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-1.6.0.tgz", + "integrity": "sha512-E5QHKWSCBFtpgZarlmN3Q6+rTQxjirFqo44ohoTjzYVrDI6B6beXNnPIThJgPr0Y9GwfzgyarKvQuQuqCnnfbA==", "dev": true, + "license": "MIT", "dependencies": { - "@jimp/utils": "^0.22.8" + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" + "engines": { + "node": ">=18" } }, - "node_modules/@jimp/plugin-print": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-0.22.8.tgz", - "integrity": "sha512-86O5ejCDi543IYl0TykSmNWErzAjEYhiAxNQb2F7rFRT38WJYNVsvJ6QhxhDQHKxSmF5iwmqbk0jYk5Wp2Z1kw==", + "node_modules/@jimp/plugin-flip": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-1.6.0.tgz", + "integrity": "sha512-/+rJVDuBIVOgwoyVkBjUFHtP+wmW0r+r5OQ2GpatQofToPVbJw1DdYWXlwviSx7hvixTWLKVgRWQ5Dw862emDg==", "dev": true, + "license": "MIT", "dependencies": { - "@jimp/utils": "^0.22.8", - "load-bmfont": "^1.4.1" + "@jimp/types": "1.6.0", + "zod": "^3.23.8" }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5", - "@jimp/plugin-blit": ">=0.3.5" + "engines": { + "node": ">=18" } }, - "node_modules/@jimp/plugin-resize": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.22.8.tgz", - "integrity": "sha512-kg8ArQRPqv/iU3DWNXCa8kcVIhoq64Ze0aGCAeFLKlAq/59f5pzAci6m6vV4L/uOVdYmUa9/kYwIFY6RWKpfzQ==", + "node_modules/@jimp/plugin-hash": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-hash/-/plugin-hash-1.6.0.tgz", + "integrity": "sha512-wWzl0kTpDJgYVbZdajTf+4NBSKvmI3bRI8q6EH9CVeIHps9VWVsUvEyb7rpbcwVLWYuzDtP2R0lTT6WeBNQH9Q==", "dev": true, + "license": "MIT", "dependencies": { - "@jimp/utils": "^0.22.8" + "@jimp/core": "1.6.0", + "@jimp/js-bmp": "1.6.0", + "@jimp/js-jpeg": "1.6.0", + "@jimp/js-png": "1.6.0", + "@jimp/js-tiff": "1.6.0", + "@jimp/plugin-color": "1.6.0", + "@jimp/plugin-resize": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "any-base": "^1.1.0" }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" + "engines": { + "node": ">=18" } }, - "node_modules/@jimp/plugin-rotate": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.22.8.tgz", - "integrity": "sha512-9a+VPZWMN/Cks76wf8LjM5RVA3ntP9+NAdsS1SZhhXel7U3Re/dWMouIEbo3QTt6K+igRo4txUCdZiw4ZucvkQ==", + "node_modules/@jimp/plugin-mask": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-1.6.0.tgz", + "integrity": "sha512-Cwy7ExSJMZszvkad8NV8o/Z92X2kFUFM8mcDAhNVxU0Q6tA0op2UKRJY51eoK8r6eds/qak3FQkXakvNabdLnA==", "dev": true, + "license": "MIT", "dependencies": { - "@jimp/utils": "^0.22.8" + "@jimp/types": "1.6.0", + "zod": "^3.23.8" }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5", - "@jimp/plugin-blit": ">=0.3.5", - "@jimp/plugin-crop": ">=0.3.5", - "@jimp/plugin-resize": ">=0.3.5" + "engines": { + "node": ">=18" } }, - "node_modules/@jimp/plugin-scale": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.22.8.tgz", - "integrity": "sha512-dQS4pG6DX6endu8zUpvBBOEtGC+ljDDDNw0scSXY71TxyQdNo5Ro0apfsppjmuAr8rNotRkfyxbITKkXQDRUDQ==", + "node_modules/@jimp/plugin-print": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-1.6.0.tgz", + "integrity": "sha512-zarTIJi8fjoGMSI/M3Xh5yY9T65p03XJmPsuNet19K/Q7mwRU6EV2pfj+28++2PV2NJ+htDF5uecAlnGyxFN2A==", "dev": true, + "license": "MIT", "dependencies": { - "@jimp/utils": "^0.22.8" + "@jimp/core": "1.6.0", + "@jimp/js-jpeg": "1.6.0", + "@jimp/js-png": "1.6.0", + "@jimp/plugin-blit": "1.6.0", + "@jimp/types": "1.6.0", + "parse-bmfont-ascii": "^1.0.6", + "parse-bmfont-binary": "^1.0.6", + "parse-bmfont-xml": "^1.1.6", + "simple-xml-to-json": "^1.2.2", + "zod": "^3.23.8" }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5", - "@jimp/plugin-resize": ">=0.3.5" + "engines": { + "node": ">=18" } }, - "node_modules/@jimp/plugin-shadow": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugin-shadow/-/plugin-shadow-0.22.8.tgz", - "integrity": "sha512-HyAhr7OblTQh+BoKHQg4qbS9MweNlH77yfpBqUEyDtfyjI5r06+5chf1ZdLRIPEWv/BdCfdI/g81Wv69muCMwA==", + "node_modules/@jimp/plugin-quantize": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-quantize/-/plugin-quantize-1.6.0.tgz", + "integrity": "sha512-EmzZ/s9StYQwbpG6rUGBCisc3f64JIhSH+ncTJd+iFGtGo0YvSeMdAd+zqgiHpfZoOL54dNavZNjF4otK+mvlg==", "dev": true, + "license": "MIT", "dependencies": { - "@jimp/utils": "^0.22.8" + "image-q": "^4.0.0", + "zod": "^3.23.8" }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5", - "@jimp/plugin-blur": ">=0.3.5", - "@jimp/plugin-resize": ">=0.3.5" + "engines": { + "node": ">=18" } }, - "node_modules/@jimp/plugin-threshold": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-0.22.8.tgz", - "integrity": "sha512-ZmkfH0PtjvF1UcKsjw0H7V6r+LC0yKzEfg76Jhs2nIqIgsxsSOVfHwS7z0/1IWnyXxSw36m+NjCAotNHRILGmA==", + "node_modules/@jimp/plugin-resize": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-1.6.0.tgz", + "integrity": "sha512-uSUD1mqXN9i1SGSz5ov3keRZ7S9L32/mAQG08wUwZiEi5FpbV0K8A8l1zkazAIZi9IJzLlTauRNU41Mi8IF9fA==", "dev": true, + "license": "MIT", "dependencies": { - "@jimp/utils": "^0.22.8" + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "zod": "^3.23.8" }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5", - "@jimp/plugin-color": ">=0.8.0", - "@jimp/plugin-resize": ">=0.8.0" - } - }, - "node_modules/@jimp/plugins": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugins/-/plugins-0.22.8.tgz", - "integrity": "sha512-ieI2+kCpmIfjwVlT7B67ULCzxMizfj7LspJh9HnIZCDXQB9GBOZ9KImLYc75Krae0dP/3FR7FglLiSI7fkOHbw==", - "dev": true, - "dependencies": { - "@jimp/plugin-blit": "^0.22.8", - "@jimp/plugin-blur": "^0.22.8", - "@jimp/plugin-circle": "^0.22.8", - "@jimp/plugin-color": "^0.22.8", - "@jimp/plugin-contain": "^0.22.8", - "@jimp/plugin-cover": "^0.22.8", - "@jimp/plugin-crop": "^0.22.8", - "@jimp/plugin-displace": "^0.22.8", - "@jimp/plugin-dither": "^0.22.8", - "@jimp/plugin-fisheye": "^0.22.8", - "@jimp/plugin-flip": "^0.22.8", - "@jimp/plugin-gaussian": "^0.22.8", - "@jimp/plugin-invert": "^0.22.8", - "@jimp/plugin-mask": "^0.22.8", - "@jimp/plugin-normalize": "^0.22.8", - "@jimp/plugin-print": "^0.22.8", - "@jimp/plugin-resize": "^0.22.8", - "@jimp/plugin-rotate": "^0.22.8", - "@jimp/plugin-scale": "^0.22.8", - "@jimp/plugin-shadow": "^0.22.8", - "@jimp/plugin-threshold": "^0.22.8", - "timm": "^1.6.1" - }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" + "engines": { + "node": ">=18" } }, - "node_modules/@jimp/png": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/png/-/png-0.22.8.tgz", - "integrity": "sha512-XOj11kcCr8zKg24QSwlRfH9k4hbV6rkMGUVxMS3puRzzB0FBSQy42NBYEfYf2XlY2QJSAByPl4AYerOtKb805w==", + "node_modules/@jimp/plugin-rotate": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-1.6.0.tgz", + "integrity": "sha512-JagdjBLnUZGSG4xjCLkIpQOZZ3Mjbg8aGCCi4G69qR+OjNpOeGI7N2EQlfK/WE8BEHOW5vdjSyglNqcYbQBWRw==", "dev": true, + "license": "MIT", "dependencies": { - "@jimp/utils": "^0.22.8", - "pngjs": "^6.0.0" + "@jimp/core": "1.6.0", + "@jimp/plugin-crop": "1.6.0", + "@jimp/plugin-resize": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" + "engines": { + "node": ">=18" } }, - "node_modules/@jimp/tiff": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/tiff/-/tiff-0.22.8.tgz", - "integrity": "sha512-K0hYUVW5MLgwq3jiHVHa6LvP05J1rXOlRCC+5dMTUnAXVwi45+MKsqA/8lzzwhHYJ65CNhZwy6D3+ZNzM9SIBQ==", + "node_modules/@jimp/plugin-threshold": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-1.6.0.tgz", + "integrity": "sha512-M59m5dzLoHOVWdM41O8z9SyySzcDn43xHseOH0HavjsfQsT56GGCC4QzU1banJidbUrePhzoEdS42uFE8Fei8w==", "dev": true, + "license": "MIT", "dependencies": { - "utif2": "^4.0.1" + "@jimp/core": "1.6.0", + "@jimp/plugin-color": "1.6.0", + "@jimp/plugin-hash": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" + "engines": { + "node": ">=18" } }, "node_modules/@jimp/types": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/types/-/types-0.22.8.tgz", - "integrity": "sha512-9+xc+mzuYwu0i+6dsnhXiUgfcS+Ktqn5q2jczoKyyBT0cOKgsk+57EIeFLgpTfVGRKRR0y/UIdHByeCzGguF3A==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/types/-/types-1.6.0.tgz", + "integrity": "sha512-7UfRsiKo5GZTAATxm2qQ7jqmUXP0DxTArztllTcYdyw6Xi5oT4RaoXynVtCD4UyLK5gJgkZJcwonoijrhYFKfg==", "dev": true, + "license": "MIT", "dependencies": { - "@jimp/bmp": "^0.22.8", - "@jimp/gif": "^0.22.8", - "@jimp/jpeg": "^0.22.8", - "@jimp/png": "^0.22.8", - "@jimp/tiff": "^0.22.8", - "timm": "^1.6.1" + "zod": "^3.23.8" }, - "peerDependencies": { - "@jimp/custom": ">=0.3.5" + "engines": { + "node": ">=18" } }, "node_modules/@jimp/utils": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.8.tgz", - "integrity": "sha512-AaqjfqDeLzSFzrbGRKHMXg/ntiWKvoG9tpVgWzgOx5/gPWj/IyGfztojLTTvY8HqZCr25z8z91u2lAQD2v46Jw==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-1.6.0.tgz", + "integrity": "sha512-gqFTGEosKbOkYF/WFj26jMHOI5OH2jeP1MmC/zbK6BF6VJBf8rIC5898dPfSzZEbSA0wbbV5slbntWVc5PKLFA==", "dev": true, + "license": "MIT", "dependencies": { - "regenerator-runtime": "^0.13.3" + "@jimp/types": "1.6.0", + "tinycolor2": "^1.6.0" + }, + "engines": { + "node": ">=18" } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.10.tgz", + "integrity": "sha512-HM2F4B9N4cA0RH2KQiIZOHAZqtP4xGS4IZ+SFe1SIbO4dyjf9MTY2Bo3vHYnm0hglWfXqBrzUBSa+cJfl3Xvrg==", "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.8.tgz", + "integrity": "sha512-3EDAPd0B8X1gsQQgGHU8vyxSp2MB414z3roN67fY7nI0GV3GDthHfaWcbCfrC95tpAzA5xUvAuoO9Dxx/ywwRQ==", "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.2.tgz", + "integrity": "sha512-gKYheCylLIedI+CSZoDtGkFV9YEBxRRVcfCH7OfAqh4TyUyRjEE6WVE/aXDXX0p8BIe/QgLcaAoI0220KRRFgg==", + "dev": true, + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.17", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", - "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "version": "0.3.27", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.27.tgz", + "integrity": "sha512-VO95AxtSFMelbg3ouljAYnfvTEwSWVt/2YLf+U5Ejd8iT5mXE2Sa/1LGyvySMne2CGsepGLI7KpF3EzE3Aq9Mg==", "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jsdoc/salty": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.9.tgz", + "integrity": "sha512-yYxMVH7Dqw6nO0d5NIV8OQWnitU8k6vXH8NtgqAfIa/IUqRMxRv/NUJJ08VEKbAakwxlgBl5PJdrU0dMPStsnw==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=v12.0.0" } }, "node_modules/@mdn/browser-compat-data": { - "version": "5.2.49", - "resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-5.2.49.tgz", - "integrity": "sha512-tXJUP9EFcfeTcn3hpn616qtcbaLMrhqfgsljRnIv/qYckL8ywLodk7Cj3oJlZed3zWLZLnE9LHHsfpO8w4yJuw==", - "dev": true + "version": "5.7.6", + "resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-5.7.6.tgz", + "integrity": "sha512-7xdrMX0Wk7grrTZQwAoy1GkvPMFoizStUoL+VmtUkAxegbCCec+3FKwOM6yc/uGU5+BEczQHXAlWiqvM8JeENg==", + "dev": true, + "license": "CC0-1.0" }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -675,6 +814,7 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, + "license": "MIT", "engines": { "node": ">= 8" } @@ -684,6 +824,7 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -693,10 +834,11 @@ } }, "node_modules/@npmcli/fs": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.0.tgz", - "integrity": "sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", + "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", "dev": true, + "license": "ISC", "dependencies": { "semver": "^7.3.5" }, @@ -705,14 +847,14 @@ } }, "node_modules/@npmcli/git": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-4.0.3.tgz", - "integrity": "sha512-8cXNkDIbnXPVbhXMmQ7/bklCAjtmPaXfI9aEM4iH+xSuEHINLMHhlfESvVwdqmHJRJkR48vNJTSUvoF6GRPSFA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-4.1.0.tgz", + "integrity": "sha512-9hwoB3gStVfa0N31ymBmrX+GuDGdVA/QWShZVqE0HK2Af+7QGGrCTbZia/SW0ImUTjTne7SP91qxDmtXvDHRPQ==", "dev": true, + "license": "ISC", "dependencies": { "@npmcli/promise-spawn": "^6.0.0", "lru-cache": "^7.4.4", - "mkdirp": "^1.0.4", "npm-pick-manifest": "^8.0.0", "proc-log": "^3.0.0", "promise-inflight": "^1.0.1", @@ -729,15 +871,17 @@ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", "dev": true, + "license": "ISC", "engines": { "node": ">=12" } }, "node_modules/@npmcli/git/node_modules/which": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-3.0.0.tgz", - "integrity": "sha512-nla//68K9NU6yRiwDY/Q8aU6siKlSs64aEC7+IV56QoAuyQT2ovsJcgGYGyqMOmI/CGN1BOR6mM5EN0FBO+zyQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", + "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", "dev": true, + "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -749,16 +893,17 @@ } }, "node_modules/@npmcli/installed-package-contents": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.0.2.tgz", - "integrity": "sha512-xACzLPhnfD51GKvTOOuNX2/V4G4mz9/1I2MfDoye9kBM3RYe5g2YbscsaGoTlaWqkxeiapBWyseULVKpSVHtKQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.1.0.tgz", + "integrity": "sha512-c8UuGLeZpm69BryRykLuKRyKFZYJsZSCT4aVY5ds4omyZqJ172ApzgfKJ5eV/r3HgLdUYgFVe54KSFVjKoe27w==", "dev": true, + "license": "ISC", "dependencies": { "npm-bundled": "^3.0.0", "npm-normalize-package-bin": "^3.0.0" }, "bin": { - "installed-package-contents": "lib/index.js" + "installed-package-contents": "bin/index.js" }, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" @@ -770,6 +915,7 @@ "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", "deprecated": "This functionality has been moved to @npmcli/fs", "dev": true, + "license": "MIT", "dependencies": { "mkdirp": "^1.0.4", "rimraf": "^3.0.2" @@ -783,6 +929,7 @@ "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz", "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==", "dev": true, + "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -792,6 +939,7 @@ "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-6.0.2.tgz", "integrity": "sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==", "dev": true, + "license": "ISC", "dependencies": { "which": "^3.0.0" }, @@ -800,10 +948,11 @@ } }, "node_modules/@npmcli/promise-spawn/node_modules/which": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-3.0.0.tgz", - "integrity": "sha512-nla//68K9NU6yRiwDY/Q8aU6siKlSs64aEC7+IV56QoAuyQT2ovsJcgGYGyqMOmI/CGN1BOR6mM5EN0FBO+zyQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", + "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", "dev": true, + "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -815,10 +964,11 @@ } }, "node_modules/@npmcli/run-script": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-6.0.0.tgz", - "integrity": "sha512-ql+AbRur1TeOdl1FY+RAwGW9fcr4ZwiVKabdvm93mujGREVuVLbdkXRJDrkTXSdCjaxYydr1wlA2v67jxWG5BQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-6.0.2.tgz", + "integrity": "sha512-NCcr1uQo1k5U+SYlnIrbAh3cxy+OQT1VtqiAbxdymSlptbzBb62AjH2xXgjNCoP073hoa1CfCAcwoZ8k96C4nA==", "dev": true, + "license": "ISC", "dependencies": { "@npmcli/node-gyp": "^3.0.0", "@npmcli/promise-spawn": "^6.0.0", @@ -831,10 +981,11 @@ } }, "node_modules/@npmcli/run-script/node_modules/which": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-3.0.0.tgz", - "integrity": "sha512-nla//68K9NU6yRiwDY/Q8aU6siKlSs64aEC7+IV56QoAuyQT2ovsJcgGYGyqMOmI/CGN1BOR6mM5EN0FBO+zyQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", + "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", "dev": true, + "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -845,46 +996,50 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@puppeteer/browsers": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-0.5.0.tgz", - "integrity": "sha512-Uw6oB7VvmPRLE4iKsjuOh8zgDabhNX67dzo8U/BB0f9527qx+4eeUs+korU98OhG5C4ubg7ufBgVi63XYwS6TQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.0.tgz", + "integrity": "sha512-ioXoq9gPxkss4MYhD+SFaU9p1IHFUX0ILAWFPyjGaBdjLsYAlZw6j1iLA0N/m12uVHLFDfSYNF7EQccjinIMDA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "https-proxy-agent": "5.0.1", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" + "debug": "^4.3.5", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" }, "bin": { "browsers": "lib/cjs/main-cli.js" }, "engines": { - "node": ">=14.1.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=18" } }, "node_modules/@rollup/plugin-node-resolve": { - "version": "15.0.2", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.0.2.tgz", - "integrity": "sha512-Y35fRGUjC3FaurG722uhUuG8YHOJRJQbI6/CkbRkdPotSpDj9NtIN85z1zrcyDcCQIW4qp5mgG72U+gJ0TAFEg==", + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.1.tgz", + "integrity": "sha512-tk5YCxJWIG81umIvNkSod2qK5KyQW19qcBF/B78n1bjtOON6gzKoVeSzAE8yHCZEDmqkHKkxplExA8KzdJLJpA==", "dev": true, + "license": "MIT", "dependencies": { "@rollup/pluginutils": "^5.0.1", "@types/resolve": "1.20.2", "deepmerge": "^4.2.2", - "is-builtin-module": "^3.2.1", "is-module": "^1.0.0", "resolve": "^1.22.1" }, @@ -892,7 +1047,7 @@ "node": ">=14.0.0" }, "peerDependencies": { - "rollup": "^2.78.0||^3.0.0" + "rollup": "^2.78.0||^3.0.0||^4.0.0" }, "peerDependenciesMeta": { "rollup": { @@ -901,20 +1056,21 @@ } }, "node_modules/@rollup/plugin-terser": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.1.tgz", - "integrity": "sha512-aKS32sw5a7hy+fEXVy+5T95aDIwjpGHCTv833HXVtyKMDoVS7pBr5K3L9hEQoNqbJFjfANPrNpIXlTQ7is00eA==", + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz", + "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==", "dev": true, + "license": "MIT", "dependencies": { - "serialize-javascript": "^6.0.0", - "smob": "^0.0.6", - "terser": "^5.15.1" + "serialize-javascript": "^6.0.1", + "smob": "^1.0.0", + "terser": "^5.17.4" }, "engines": { "node": ">=14.0.0" }, "peerDependencies": { - "rollup": "^2.x || ^3.x" + "rollup": "^2.0.0||^3.0.0||^4.0.0" }, "peerDependenciesMeta": { "rollup": { @@ -923,20 +1079,21 @@ } }, "node_modules/@rollup/pluginutils": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.2.tgz", - "integrity": "sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.2.0.tgz", + "integrity": "sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==", "dev": true, + "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", - "picomatch": "^2.3.1" + "picomatch": "^4.0.2" }, "engines": { "node": ">=14.0.0" }, "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0" + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "peerDependenciesMeta": { "rollup": { @@ -944,957 +1101,1137 @@ } } }, - "node_modules/@tokenizer/token": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", - "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", - "dev": true - }, - "node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.44.1.tgz", + "integrity": "sha512-JAcBr1+fgqx20m7Fwe1DxPUl/hPkee6jA6Pl7n1v2EFiktAHenTaXl5aIFjUIEsfn9w3HE4gK1lEgNGMzBDs1w==", + "cpu": [ + "arm" + ], "dev": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true + "license": "MIT", + "optional": true, + "os": [ + "android" + ] }, - "node_modules/@tufjs/models": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-1.0.0.tgz", - "integrity": "sha512-RRMu4uMxWnZlxaIBxahSb2IssFZiu188sndesZflWOe1cA/qUqtemSIoBWbuVKPvvdktapImWNnKpBcc+VrCQw==", + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.44.1.tgz", + "integrity": "sha512-RurZetXqTu4p+G0ChbnkwBuAtwAbIwJkycw1n6GvlGlBuS4u5qlr5opix8cBAYFJgaY05TWtM+LaoFggUmbZEQ==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "minimatch": "^6.1.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "android" + ] }, - "node_modules/@tufjs/models/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.44.1.tgz", + "integrity": "sha512-fM/xPesi7g2M7chk37LOnmnSTHLG/v2ggWqKj3CCA1rMA4mm5KVBT1fNoswbo1JhPuNNZrVwpTvlCVggv8A2zg==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@tufjs/models/node_modules/minimatch": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-6.2.0.tgz", - "integrity": "sha512-sauLxniAmvnhhRjFwPNnJKaPFYyddAgbYdeUpHULtCT/GhzdCx/MDNy+Y40lBxTQUrMzDE8e0S43Z5uqfO0REg==", + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.44.1.tgz", + "integrity": "sha512-gDnWk57urJrkrHQ2WVx9TSVTH7lSlU7E3AFqiko+bgjlh78aJ88/3nycMax52VIVjIm3ObXnDL2H00e/xzoipw==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@types/estree": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", - "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==", - "dev": true + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.44.1.tgz", + "integrity": "sha512-wnFQmJ/zPThM5zEGcnDcCJeYJgtSLjh1d//WuHzhf6zT3Md1BvvhJnWoy+HECKu2bMxaIcfWiu3bJgx6z4g2XA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] }, - "node_modules/@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.44.1.tgz", + "integrity": "sha512-uBmIxoJ4493YATvU2c0upGz87f99e3wop7TJgOA/bXMFd2SvKCI7xkxY/5k50bv7J6dw1SXT4MQBQSLn8Bb/Uw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] }, - "node_modules/@types/node": { - "version": "16.9.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz", - "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==", - "dev": true + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.44.1.tgz", + "integrity": "sha512-n0edDmSHlXFhrlmTK7XBuwKlG5MbS7yleS1cQ9nn4kIeW+dJH+ExqNgQ0RrFRew8Y+0V/x6C5IjsHrJmiHtkxQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@types/resolve": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", - "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", - "dev": true + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.44.1.tgz", + "integrity": "sha512-8WVUPy3FtAsKSpyk21kV52HCxB+me6YkbkFHATzC2Yd3yuqHwy2lbFL4alJOLXKljoRw08Zk8/xEj89cLQ/4Nw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@types/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.44.1.tgz", + "integrity": "sha512-yuktAOaeOgorWDeFJggjuCkMGeITfqvPgkIXhDqsfKX8J3jGyxdDZgBV/2kj/2DyPaLiX6bPdjJDTu9RB8lUPQ==", + "cpu": [ + "arm64" + ], "dev": true, + "license": "MIT", "optional": true, - "dependencies": { - "@types/node": "*" - } + "os": [ + "linux" + ] }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.44.1.tgz", + "integrity": "sha512-W+GBM4ifET1Plw8pdVaecwUgxmiH23CfAUj32u8knq0JPFyK4weRy6H7ooxYFD19YxBulL0Ktsflg5XS7+7u9g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.44.1.tgz", + "integrity": "sha512-1zqnUEMWp9WrGVuVak6jWTl4fEtrVKfZY7CvcBmUUpxAJ7WcSowPSAWIKa/0o5mBL/Ij50SIf9tuirGx63Ovew==", + "cpu": [ + "loong64" + ], "dev": true, - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.44.1.tgz", + "integrity": "sha512-Rl3JKaRu0LHIx7ExBAAnf0JcOQetQffaw34T8vLlg9b1IhzcBgaIdnvEbbsZq9uZp3uAH+JkHd20Nwn0h9zPjA==", + "cpu": [ + "ppc64" + ], "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.44.1.tgz", + "integrity": "sha512-j5akelU3snyL6K3N/iX7otLBIl347fGwmd95U5gS/7z6T4ftK288jKq3A5lcFKcx7wwzb5rgNvAg3ZbV4BqUSw==", + "cpu": [ + "riscv64" + ], "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.44.1.tgz", + "integrity": "sha512-ppn5llVGgrZw7yxbIm8TTvtj1EoPgYUAbfw0uDjIOzzoqlZlZrLJ/KuiE7uf5EpTpCTrNt1EdtzF0naMm0wGYg==", + "cpu": [ + "riscv64" + ], "dev": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/agentkeepalive": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.3.0.tgz", - "integrity": "sha512-7Epl1Blf4Sy37j4v9f9FjICCh4+KAQOyXgHEwlyBiAQLbhKdq/i2QQU3amQalS/wPhdPzDXPL5DMR5bkn+YeWg==", + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.44.1.tgz", + "integrity": "sha512-Hu6hEdix0oxtUma99jSP7xbvjkUM/ycke/AQQ4EC5g7jNRLLIwjcNwaUy95ZKBJJwg1ZowsclNnjYqzN4zwkAw==", + "cpu": [ + "s390x" + ], "dev": true, - "dependencies": { - "debug": "^4.1.0", - "depd": "^2.0.0", - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.44.1.tgz", + "integrity": "sha512-EtnsrmZGomz9WxK1bR5079zee3+7a+AdFlghyd6VbAjgRJDbTANJ9dcPIPAi76uG05micpEL+gPGmAKYTschQw==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.44.1.tgz", + "integrity": "sha512-iAS4p+J1az6Usn0f8xhgL4PaU878KEtutP4hqw52I4IO6AGoyOkHCxcc4bqufv1tQLdDWFx8lR9YlwxKuv3/3g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.44.1.tgz", + "integrity": "sha512-NtSJVKcXwcqozOl+FwI41OH3OApDyLk3kqTJgx8+gp6On9ZEt5mYhIsKNPGuaZr3p9T6NWPKGU/03Vw4CNU9qg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.44.1.tgz", + "integrity": "sha512-JYA3qvCOLXSsnTR3oiyGws1Dm0YTuxAAeaYGVlGpUsHqloPcFjPg+X0Fj2qODGLNwQOAcCiQmHub/V007kiH5A==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.44.1.tgz", + "integrity": "sha512-J8o22LuF0kTe7m+8PvW9wk3/bRq5+mRo5Dqo6+vXb7otCm3TPhYOJqOaQtGU9YMWQSL3krMnoOxMr0+9E6F3Ug==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sigstore/bundle": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-1.1.0.tgz", + "integrity": "sha512-PFutXEy0SmQxYI4texPw3dd2KewuNqv7OuK1ZFtY2fM754yhvG2KdgwIhRnoEE2uHdtdGNQ8s0lb94dW9sELog==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.2.0" }, "engines": { - "node": ">=8" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "node_modules/@sigstore/protobuf-specs": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.2.1.tgz", + "integrity": "sha512-XTWVxnWJu+c1oCshMLwnKvz8ZQJJDVOlciMfgpJBQbThVjKTCG8dwyhgLngBD2KN0ap9F/gOV8rFDEx8uh7R2A==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/sign": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-1.0.0.tgz", + "integrity": "sha512-INxFVNQteLtcfGmcoldzV6Je0sbbfh9I16DM4yJPw3j5+TFP8X6uIiA18mvpEa9yyeycAKgPmOA3X9hVdVTPUA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "@sigstore/bundle": "^1.1.0", + "@sigstore/protobuf-specs": "^0.2.0", + "make-fetch-happen": "^11.0.1" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "node_modules/@sigstore/sign/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "dev": true, + "license": "MIT", "dependencies": { - "string-width": "^4.1.0" + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" } }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "node_modules/@sigstore/sign/node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, "engines": { - "node": ">=6" + "node": ">= 6" } }, - "node_modules/ansi-regex": { + "node_modules/@sigstore/sign/node_modules/https-proxy-agent": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@sigstore/sign/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", "dev": true, + "license": "ISC", "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@sigstore/sign/node_modules/make-fetch-happen": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz", + "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==", "dev": true, + "license": "ISC", "dependencies": { - "color-convert": "^2.0.1" + "agentkeepalive": "^4.2.1", + "cacache": "^17.0.0", + "http-cache-semantics": "^4.1.1", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^5.0.0", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^10.0.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/any-base": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz", - "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==", - "dev": true - }, - "node_modules/aproba": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", - "dev": true + "node_modules/@sigstore/sign/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=8" + } }, - "node_modules/are-we-there-yet": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", - "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "node_modules/@sigstore/sign/node_modules/minipass-fetch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz", + "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==", "dev": true, + "license": "MIT", "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" } }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "dev": true + "node_modules/@sigstore/sign/node_modules/minipass-fetch/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } }, - "node_modules/array-includes": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", - "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "node_modules/@sigstore/sign/node_modules/socks-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", + "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "get-intrinsic": "^1.1.3", - "is-string": "^1.0.7" + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 10" } }, - "node_modules/array.prototype.flat": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", - "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "node_modules/@sigstore/tuf": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-1.0.3.tgz", + "integrity": "sha512-2bRovzs0nJZFlCN3rXirE4gwxCn97JNjMmwpecqlbgV9WcxX7WRuIrgzx/X7Ib7MYRbyUTpBYE0s2x6AmZXnlg==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0" + "@sigstore/protobuf-specs": "^0.2.0", + "tuf-js": "^1.1.7" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/array.prototype.flatmap": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", - "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tufjs/canonical-json": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-1.0.0.tgz", + "integrity": "sha512-QTnf++uxunWvG2z3UFNzAoQPHxnSXOwtaI3iJ+AohhV+5vONuArPjJE7aPXPVXfXJsqrVbZBu9b81AJoSd09IQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@tufjs/models": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-1.0.4.tgz", + "integrity": "sha512-qaGV9ltJP0EO25YfFUPhxRVK0evXFIAGicsVXuRim4Ed9cjPxYhNnNJ49SFmbeLgtxpslIkX317IgpfcHPVj/A==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0" + "@tufjs/canonical-json": "1.0.0", + "minimatch": "^9.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/ast-metadata-inferer": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/ast-metadata-inferer/-/ast-metadata-inferer-0.8.0.tgz", - "integrity": "sha512-jOMKcHht9LxYIEQu+RVd22vtgrPaVCtDRQ/16IGmurdzxvYbDd5ynxjnyrzLnieG96eTcAyaoj/wN/4/1FyyeA==", + "node_modules/@tufjs/models/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, + "license": "MIT", "dependencies": { - "@mdn/browser-compat-data": "^5.2.34" + "balanced-match": "^1.0.0" } }, - "node_modules/available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "node_modules/@tufjs/models/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, "engines": { - "node": ">= 0.4" + "node": ">=16 || 14 >=14.17" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] + "license": "MIT" }, - "node_modules/basic-auth": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", - "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", "dev": true, + "license": "MIT", "dependencies": { - "safe-buffer": "5.1.2" - }, - "engines": { - "node": ">= 0.8" + "@types/linkify-it": "^5", + "@types/mdurl": "^2" } }, - "node_modules/batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", - "dev": true + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true, + "license": "MIT" }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "node_modules/@types/node": { + "version": "16.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz", + "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==", "dev": true, + "license": "MIT" + }, + "node_modules/@types/node-forge": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", + "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", + "dev": true, + "license": "MIT", "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" + "@types/node": "*" } }, - "node_modules/bmp-js": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz", - "integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==", - "dev": true + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true, + "license": "MIT" }, - "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", "dev": true, + "license": "MIT", + "optional": true, "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "@types/node": "*" } }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", "dev": true, - "dependencies": { - "ms": "2.0.0" - } + "license": "ISC" }, - "node_modules/body-parser/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true, + "license": "ISC" + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "event-target-shim": "^5.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=6.5" } }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/boxen": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", - "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-align": "^3.0.0", - "camelcase": "^6.2.0", - "chalk": "^4.1.0", - "cli-boxes": "^2.2.1", - "string-width": "^4.2.2", - "type-fest": "^0.20.2", - "widest-line": "^3.1.0", - "wrap-ansi": "^7.0.0" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.6" } }, - "node_modules/boxen/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">= 0.6" } }, - "node_modules/boxen/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, - "dependencies": { - "has-flag": "^4.0.0" + "license": "MIT", + "bin": { + "acorn": "bin/acorn" }, "engines": { - "node": ">=8" + "node": ">=0.4.0" } }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/brotli-size": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/brotli-size/-/brotli-size-4.0.0.tgz", - "integrity": "sha512-uA9fOtlTRC0iqKfzff1W34DXUA3GyVqbUaeo3Rw3d4gd1eavKVCETXrn3NzO74W+UVkG3UHu8WxUi+XvKI/huA==", + "node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", "dev": true, + "license": "MIT", "dependencies": { - "duplexer": "0.1.1" + "humanize-ms": "^1.2.1" }, "engines": { - "node": ">= 10.16.0" + "node": ">= 8.0.0" } }, - "node_modules/browserslist": { - "version": "4.21.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", - "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - } - ], + "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001449", - "electron-to-chromium": "^1.4.284", - "node-releases": "^2.0.8", - "update-browserslist-db": "^1.0.10" - }, - "bin": { - "browserslist": "cli.js" + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" }, "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + "node": ">=8" } }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], + "license": "MIT", "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", "dev": true, - "engines": { - "node": "*" + "license": "ISC", + "dependencies": { + "string-width": "^4.1.0" } }, - "node_modules/buffer-equal": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", - "integrity": "sha512-RgSV6InVQ9ODPdLWJ5UAqBqJBOg370Nz6ZQtRzpt6nUjc8v0St97uJ4PYC6NztqIScrAXafKM3mZPMygSe1ggA==", + "node_modules/ansi-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, "engines": { - "node": ">=0.4.0" + "node": ">=8" } }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "node_modules/builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/builtins": { + "node_modules/ansi-regex": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", - "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, - "dependencies": { - "semver": "^7.0.0" + "license": "MIT", + "engines": { + "node": ">=8" } }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/cacache": { - "version": "17.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-17.0.4.tgz", - "integrity": "sha512-Z/nL3gU+zTUjz5pCA5vVjYM8pmaw2kxM7JEiE0fv3w77Wj+sFbi70CrBruUWH0uNcEdvLDixFpgA2JM4F4DBjA==", + "node_modules/any-base": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz", + "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "deprecated": "This package is no longer supported.", "dev": true, + "license": "ISC", "dependencies": { - "@npmcli/fs": "^3.1.0", - "fs-minipass": "^3.0.0", - "glob": "^8.0.1", - "lru-cache": "^7.7.1", - "minipass": "^4.0.0", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "ssri": "^10.0.0", - "tar": "^6.1.11", - "unique-filename": "^3.0.0" + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/cacache/node_modules/brace-expansion": { + "node_modules/argparse": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } + "license": "Python-2.0" }, - "node_modules/cacache/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", "dev": true, + "license": "MIT", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" }, "engines": { - "node": ">=12" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/cacache/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "dev": true, - "engines": { - "node": ">=12" - } + "license": "MIT" }, - "node_modules/cacache/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", "dev": true, + "license": "MIT", "dependencies": { - "brace-expansion": "^2.0.1" + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" }, "engines": { - "node": ">=10" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", "dev": true, + "license": "MIT", "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, "engines": { - "node": ">=6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001476", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001476.tgz", - "integrity": "sha512-JmpktFppVSvyUN4gsLS0bShY2L9ZUslHLE72vgemBkS43JD2fOvKTKs+GtRwuxrtRGnwJFW0ye7kWRRlLJS9vQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ] - }, - "node_modules/chalk": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", - "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==", + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "node_modules/ast-metadata-inferer": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/ast-metadata-inferer/-/ast-metadata-inferer-0.8.1.tgz", + "integrity": "sha512-ht3Dm6Zr7SXv6t1Ra6gFo0+kLDglHGrEbYihTkcycrbHw7WCcuhBzPlJYHEsIpycaUwzsJHje+vUcxXUX4ztTA==", "dev": true, - "engines": { - "node": ">=10" + "license": "MIT", + "dependencies": { + "@mdn/browser-compat-data": "^5.6.19" } }, - "node_modules/chromium-bidi": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.7.tgz", - "integrity": "sha512-6+mJuFXwTMU6I3vYLs6IL8A1DyQTPjCfIL971X0aMPVGRbGnNfl6i6Cl0NMbxi2bRYLGESt9T2ZIMRM5PAEcIQ==", + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", "dev": true, + "license": "MIT", "dependencies": { - "mitt": "3.0.0" + "tslib": "^2.0.1" }, - "peerDependencies": { - "devtools-protocol": "*" + "engines": { + "node": ">=4" } }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", "dev": true, + "license": "MIT", "engines": { - "node": ">=6" + "node": ">= 0.4" } }, - "node_modules/cli-boxes": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", - "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, "engines": { - "node": ">=6" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "node_modules/await-to-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/await-to-js/-/await-to-js-3.0.0.tgz", + "integrity": "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g==", "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=6.0.0" } }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/b4a": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", + "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/bare-events": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.4.tgz", + "integrity": "sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==", + "dev": true, + "license": "Apache-2.0", + "optional": true + }, + "node_modules/bare-fs": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.1.5.tgz", + "integrity": "sha512-1zccWBMypln0jEE05LzZt+V/8y8AQsQQqxtklqaIyg5nu6OAYFhZxPXinJTSG+kU5qyNmeLgcn9AW7eHiCHVLA==", "dev": true, + "license": "Apache-2.0", + "optional": true, "dependencies": { - "color-name": "~1.1.4" + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4" }, "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "dev": true, - "bin": { - "color-support": "bin.js" - } - }, - "node_modules/colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true, - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/concurrently": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.0.1.tgz", - "integrity": "sha512-Sh8bGQMEL0TAmAm2meAXMjcASHZa7V0xXQVDBLknCPa9TPtkY9yYs+0cnGGgfdkW0SV1Mlg+hVGfXcoI8d3MJA==", - "dev": true, - "dependencies": { - "chalk": "^4.1.2", - "date-fns": "^2.29.3", - "lodash": "^4.17.21", - "rxjs": "^7.8.0", - "shell-quote": "^1.8.0", - "spawn-command": "0.0.2-1", - "supports-color": "^8.1.1", - "tree-kill": "^1.2.2", - "yargs": "^17.7.1" - }, - "bin": { - "conc": "dist/bin/concurrently.js", - "concurrently": "dist/bin/concurrently.js" + "bare": ">=1.16.0" }, - "engines": { - "node": "^14.13.0 || >=16.0.0" + "peerDependencies": { + "bare-buffer": "*" }, - "funding": { - "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } } }, - "node_modules/concurrently/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/bare-os": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.1.tgz", + "integrity": "sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g==", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "license": "Apache-2.0", + "optional": true, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "bare": ">=1.14.0" } }, - "node_modules/concurrently/node_modules/chalk/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/bare-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", + "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", "dev": true, + "license": "Apache-2.0", + "optional": true, "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "bare-os": "^3.0.1" } }, - "node_modules/console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", - "dev": true - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "node_modules/bare-stream": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.6.5.tgz", + "integrity": "sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA==", "dev": true, + "license": "Apache-2.0", + "optional": true, "dependencies": { - "safe-buffer": "5.2.1" + "streamx": "^2.21.0" }, - "engines": { - "node": ">= 0.6" + "peerDependencies": { + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } } }, - "node_modules/content-disposition/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "dev": true, "funding": [ { @@ -1909,1492 +2246,1435 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.1.2" + }, "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, - "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/basic-ftp": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=10.0.0" } }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "dev": true + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true, + "license": "MIT" }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "dev": true, + "license": "MIT", "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" } }, - "node_modules/cross-fetch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", - "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "dev": true, - "dependencies": { - "node-fetch": "2.6.7" - } + "license": "MIT" }, - "node_modules/cross-fetch/node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "node_modules/bmp-ts": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/bmp-ts/-/bmp-ts-1.0.9.tgz", + "integrity": "sha512-cTEHk2jLrPyi+12M3dhpEbnnPOsaZuq7C45ylbbQIiWgDFZq4UVYPEY5mlqjvsj/6gJv9qX5sa+ebDzLXT28Vw==", "dev": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } + "license": "MIT" }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dev": true, + "license": "MIT", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" }, "engines": { - "node": ">= 8" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/date-fns": { - "version": "2.29.3", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz", - "integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==", + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, - "engines": { - "node": ">=0.11" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/date-fns" + "license": "MIT", + "dependencies": { + "ms": "2.0.0" } }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, + "license": "MIT", "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" + "safer-buffer": ">= 2.1.2 < 3" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/deepmerge": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.0.tgz", - "integrity": "sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og==", - "dev": true, "engines": { "node": ">=0.10.0" } }, - "node_modules/define-lazy-prop": { + "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true, - "engines": { - "node": ">=8" - } + "license": "MIT" }, - "node_modules/define-properties": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", - "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "node_modules/boxen": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", + "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", "dev": true, + "license": "MIT", "dependencies": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" + "ansi-align": "^3.0.0", + "camelcase": "^6.2.0", + "chalk": "^4.1.0", + "cli-boxes": "^2.2.1", + "string-width": "^4.2.2", + "type-fest": "^0.20.2", + "widest-line": "^3.1.0", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "dev": true - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "dev": true, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/devtools-protocol": { - "version": "0.0.1107588", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1107588.tgz", - "integrity": "sha512-yIR+pG9x65Xko7bErCUSQaDLrO/P1p3JUzEk7JCU4DowPcGHkTGUGQapcfcLc4qj0UaALwZ+cr0riFgiqpixcg==", - "dev": true - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "node_modules/boxen/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "license": "MIT", "dependencies": { - "esutils": "^2.0.2" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "dev": true, - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" + "node": ">=8" }, "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/dom-walk": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", - "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==", - "dev": true - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ] - }, - "node_modules/domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "node_modules/boxen/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { - "domelementtype": "^2.3.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz", - "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==", - "dev": true, - "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.1" + "node": ">=10" }, "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/duplexer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", - "integrity": "sha512-sxNZ+ljy+RA1maXoUReeqBBpBC6RLKmg5ewzV+x+mSETmWNoKdZN6vcQjpFROemza23hGFskJtFNoUWUaQ+R4Q==", - "dev": true - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "dev": true - }, - "node_modules/electron-to-chromium": { - "version": "1.4.308", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.308.tgz", - "integrity": "sha512-qyTx2aDFjEni4UnRWEME9ubd2Xc9c0zerTUl/ZinvD4QPsF0S7kJTV/Es/lPCTkNX6smyYar+z/n8Cl6pFr8yQ==", - "dev": true - }, - "node_modules/emoji-regex": { + "node_modules/boxen/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", "dev": true, - "engines": { - "node": ">= 0.8" - } + "license": "MIT" }, - "node_modules/encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "node_modules/boxen/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, - "optional": true, + "license": "MIT", "dependencies": { - "iconv-lite": "^0.6.2" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" } }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "node_modules/boxen/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "license": "MIT", "dependencies": { - "once": "^1.4.0" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/entities": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", - "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", + "node_modules/boxen/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, "engines": { - "node": ">=0.12" + "node": ">=10" }, "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, - "engines": { - "node": ">=6" + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/err-code": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "dev": true - }, - "node_modules/es-abstract": { - "version": "1.21.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.1.tgz", - "integrity": "sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg==", - "dev": true, - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-set-tostringtag": "^2.0.1", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.1.3", - "get-symbol-description": "^1.0.0", - "globalthis": "^1.0.3", - "gopd": "^1.0.1", - "has": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.4", - "is-array-buffer": "^3.0.1", - "is-callable": "^1.2.7", - "is-negative-zero": "^2.0.2", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.10", - "is-weakref": "^1.0.2", - "object-inspect": "^1.12.2", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", - "safe-regex-test": "^1.0.0", - "string.prototype.trimend": "^1.0.6", - "string.prototype.trimstart": "^1.0.6", - "typed-array-length": "^1.0.4", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.9" + "node_modules/brotli-size": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/brotli-size/-/brotli-size-4.0.0.tgz", + "integrity": "sha512-uA9fOtlTRC0iqKfzff1W34DXUA3GyVqbUaeo3Rw3d4gd1eavKVCETXrn3NzO74W+UVkG3UHu8WxUi+XvKI/huA==", + "dev": true, + "license": "MIT", + "dependencies": { + "duplexer": "0.1.1" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 10.16.0" } }, - "node_modules/es-set-tostringtag": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", - "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "node_modules/browserslist": { + "version": "4.25.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", + "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", "dependencies": { - "get-intrinsic": "^1.1.3", - "has": "^1.0.3", - "has-tostringtag": "^1.0.0" + "caniuse-lite": "^1.0.30001726", + "electron-to-chromium": "^1.5.173", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" }, "engines": { - "node": ">= 0.4" + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/es-shim-unscopables": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", - "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", "dependencies": { - "has": "^1.0.3" + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" } }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", "dev": true, - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, + "license": "MIT", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "*" } }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true, - "engines": { - "node": ">=6" - } + "license": "MIT" }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "dev": true, + "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.8" } }, - "node_modules/eslint": { - "version": "8.40.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.40.0.tgz", - "integrity": "sha512-bvR+TsP9EHL3TqNtj9sCNJVAFK3fBN8Q7g5waghxyRsPLIMwL73XSKnZFK0hk/O2ANC+iAoq6PWMQ+IfBAJIiQ==", + "node_modules/cacache": { + "version": "17.1.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-17.1.4.tgz", + "integrity": "sha512-/aJwG2l3ZMJ1xNAnqbMpA40of9dj/pIH3QfiuQSqjfPJF747VR0J/bHn+/KdNnHKc6XQcWt/AfRSBft82W1d2A==", "dev": true, + "license": "ISC", "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.3", - "@eslint/js": "8.40.0", - "@humanwhocodes/config-array": "^0.11.8", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.0", - "eslint-visitor-keys": "^3.4.1", - "espree": "^9.5.2", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" + "@npmcli/fs": "^3.1.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^7.7.1", + "minipass": "^7.0.3", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^4.0.0", + "ssri": "^10.0.0", + "tar": "^6.1.11", + "unique-filename": "^3.0.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/eslint-config-mdcs": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-mdcs/-/eslint-config-mdcs-5.0.0.tgz", - "integrity": "sha512-d4lzeT/sQ3TkI69hd+N/dtQ15g3GrbIboTfCAw6FaDQTLjWK2O3+dNfOOfkAC5TlwyU9BxztR1TE+x8iSzyuPw==", - "dev": true + "node_modules/cacache/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", - "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", "dev": true, + "license": "MIT", "dependencies": { - "debug": "^3.2.7", - "is-core-module": "^2.11.0", - "resolve": "^1.22.1" + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-import-resolver-node/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "dev": true, + "license": "MIT", "dependencies": { - "ms": "^2.1.1" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" } }, - "node_modules/eslint-module-utils": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", - "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "dev": true, + "license": "MIT", "dependencies": { - "debug": "^3.2.7" + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { - "node": ">=4" + "node": ">= 0.4" }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, - "dependencies": { - "ms": "^2.1.1" + "license": "MIT", + "engines": { + "node": ">=6" } }, - "node_modules/eslint-plugin-compat": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-compat/-/eslint-plugin-compat-4.1.4.tgz", - "integrity": "sha512-RxySWBmzfIROLFKgeJBJue2BU/6vM2KJWXWAUq+oW4QtrsZXRxbjgxmO1OfF3sHcRuuIenTS/wgo3GyUWZF24w==", + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, - "dependencies": { - "@mdn/browser-compat-data": "^5.2.47", - "@tsconfig/node14": "^1.0.3", - "ast-metadata-inferer": "^0.8.0", - "browserslist": "^4.21.5", - "caniuse-lite": "^1.0.30001473", - "find-up": "^5.0.0", - "lodash.memoize": "4.1.2", - "semver": "7.3.8" - }, + "license": "MIT", "engines": { - "node": ">=14.x" + "node": ">=10" }, - "peerDependencies": { - "eslint": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint-plugin-html": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-html/-/eslint-plugin-html-7.1.0.tgz", - "integrity": "sha512-fNLRraV/e6j8e3XYOC9xgND4j+U7b1Rq+OygMlLcMg+wI/IpVbF+ubQa3R78EjKB9njT6TQOlcK5rFKBVVtdfg==", + "node_modules/caniuse-lite": { + "version": "1.0.30001726", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz", + "integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==", "dev": true, - "dependencies": { - "htmlparser2": "^8.0.1" - } + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" }, - "node_modules/eslint-plugin-import": { - "version": "2.27.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", - "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", + "node_modules/catharsis": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", + "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", "dev": true, + "license": "MIT", "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flat": "^1.3.1", - "array.prototype.flatmap": "^1.3.1", - "debug": "^3.2.7", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.7", - "eslint-module-utils": "^2.7.4", - "has": "^1.0.3", - "is-core-module": "^2.11.0", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.values": "^1.1.6", - "resolve": "^1.22.1", - "semver": "^6.3.0", - "tsconfig-paths": "^3.14.1" + "lodash": "^4.17.15" }, "engines": { - "node": ">=4" + "node": ">= 10" + } + }, + "node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", "dev": true, - "dependencies": { - "ms": "^2.1.1" + "license": "ISC", + "engines": { + "node": ">=10" } }, - "node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "node_modules/chromium-bidi": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.6.3.tgz", + "integrity": "sha512-qXlsCmpCZJAnoTYI83Iu6EdYQpMYdVkCfq08KDh2pmlVqK5t5IA9mGs4/LwCwp4fqisSOMXZxP3HIh8w8aRn0A==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "esutils": "^2.0.2" + "mitt": "3.0.1", + "urlpattern-polyfill": "10.0.0", + "zod": "3.23.8" }, - "engines": { - "node": ">=0.10.0" + "peerDependencies": { + "devtools-protocol": "*" } }, - "node_modules/eslint-plugin-import/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "node_modules/chromium-bidi/node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", "dev": true, - "bin": { - "semver": "bin/semver.js" + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/eslint-scope": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", - "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, + "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=6" } }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", - "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", + "node_modules/cli-boxes": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", + "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", "dev": true, + "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=6" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "restore-cursor": "^3.1.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=8" } }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, + "license": "ISC", "dependencies": { - "has-flag": "^4.0.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/espree": { - "version": "9.5.2", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", - "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "license": "MIT", "dependencies": { - "acorn": "^8.8.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "color-convert": "^2.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=8" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "dependencies": { - "estraverse": "^5.1.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=0.10" + "node": ">=8" } }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, + "license": "MIT", "dependencies": { - "estraverse": "^5.2.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=4.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", "dev": true, + "license": "MIT", "engines": { - "node": ">=4.0" + "node": ">=0.8" } }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, "engines": { - "node": ">=0.10.0" + "node": ">=7.0.0" } }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, - "engines": { - "node": ">= 0.6" - } + "license": "MIT" }, - "node_modules/exif-parser": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz", - "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==", - "dev": true + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true, + "license": "ISC", + "bin": { + "color-support": "bin.js" + } }, - "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", "dev": true, - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.1", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.5.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, + "license": "MIT", "engines": { - "node": ">= 0.10.0" + "node": ">=0.1.90" } }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", "dev": true, - "dependencies": { - "ms": "2.0.0" + "license": "MIT", + "engines": { + "node": ">= 10" } }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/express/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] + "license": "MIT" }, - "node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "node_modules/concurrently": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.0.tgz", + "integrity": "sha512-IsB/fiXTupmagMW4MNp2lx2cdSN2FfZq78vF90LBB+zZHArbIQZjQtzXCiXnvTxCZSvXanTqFLWBjw2UkLx1SQ==", "dev": true, + "license": "MIT", "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" + "chalk": "^4.1.2", + "lodash": "^4.17.21", + "rxjs": "^7.8.1", + "shell-quote": "^1.8.1", + "supports-color": "^8.1.1", + "tree-kill": "^1.2.2", + "yargs": "^17.7.2" }, "bin": { - "extract-zip": "cli.js" + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" }, "engines": { - "node": ">= 10.17.0" + "node": ">=18" }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" - } - }, - "node_modules/failonlyreporter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/failonlyreporter/-/failonlyreporter-1.0.0.tgz", - "integrity": "sha512-daW559J4F/nWk0AiUPuxpCNCRXNa74yQdZNAVBIJt192VbsfKMNZocCqvRLjFIIp9BeBGu4gUhFJImmb4kSWOQ==", - "dev": true, - "dependencies": { - "chalk": "^2.4.2" + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" } }, - "node_modules/failonlyreporter/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "node_modules/concurrently/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "license": "MIT", "dependencies": { - "color-convert": "^1.9.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/failonlyreporter/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "node_modules/concurrently/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/failonlyreporter/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "node_modules/concurrently/node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "license": "MIT", "dependencies": { - "color-name": "1.1.3" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/failonlyreporter/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "dev": true, + "license": "ISC" }, - "node_modules/failonlyreporter/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, "engines": { - "node": ">=0.8.0" + "node": ">= 0.6" } }, - "node_modules/failonlyreporter/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "dev": true, + "license": "MIT", "engines": { - "node": ">=4" + "node": ">= 0.6" } }, - "node_modules/failonlyreporter/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, + "license": "MIT", "engines": { - "node": ">=4" + "node": ">= 0.6" } }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } + "license": "MIT" }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", "dev": true, + "license": "MIT", "dependencies": { - "pend": "~1.2.0" + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" } }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, + "license": "MIT", "dependencies": { - "flat-cache": "^3.0.4" + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/file-type": { - "version": "16.5.4", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", - "integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==", + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, + "license": "MIT", "dependencies": { - "readable-web-to-node-stream": "^3.0.0", - "strtok3": "^6.2.4", - "token-types": "^4.1.1" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/file-type?sponsor=1" + "node": ">= 8" } }, - "node_modules/filesize": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-6.4.0.tgz", - "integrity": "sha512-mjFIpOHC4jbfcTfoh4rkWpI31mF7viw9ikj/JyLoKzqlwG/YsefKfvYlYhdYdg/9mtK2z1AzgN/0LvVQ3zdlSQ==", + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.4.0" + "node": ">= 14" } }, - "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", "dev": true, + "license": "MIT", "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", "dev": true, + "license": "MIT", "dependencies": { - "ms": "2.0.0" + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" } }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", "dev": true, + "license": "MIT", "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "dev": true, + "license": "MIT", "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" + "ms": "^2.1.3" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" }, - "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", "dev": true, + "license": "MIT", "dependencies": { - "is-callable": "^1.1.3" + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, "engines": { - "node": ">= 0.6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=8" } }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true - }, - "node_modules/fs-minipass": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.1.tgz", - "integrity": "sha512-MhaJDcFRTuLidHrIttu0RDGyyXs/IYHVmlcxfLAEFIWjc1vdLAkdwT7Ace2u7DbitWC0toKMl5eJZRYNVreIMw==", + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "dev": true, + "license": "MIT", "dependencies": { - "minipass": "^4.0.0" + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "node_modules/function.prototype.name": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", - "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 14" } }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "license": "MIT" }, - "node_modules/gauge": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", - "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "dev": true, - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", - "has-unicode": "^2.0.1", - "signal-exit": "^3.0.7", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" - }, + "license": "MIT", "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">= 0.8" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", "dev": true, + "license": "MIT", "engines": { - "node": "6.* || 8.* || >= 10.*" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/get-intrinsic": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", - "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "node_modules/devtools-protocol": { + "version": "0.0.1312386", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz", + "integrity": "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==", "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" + "esutils": "^2.0.2" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=6.0.0" } }, - "node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", "dev": true, + "license": "MIT", "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" } }, - "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "node_modules/dom-serializer/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - }, + "license": "BSD-2-Clause", "engines": { - "node": ">= 0.4" + "node": ">=0.12" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/gifwrap": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.9.4.tgz", - "integrity": "sha512-MDMwbhASQuVeD4JKd1fKgNgCRL3fGqMM4WaqpNhWO0JiMOAjbQdumbs4BbBZEy9/M00EHEjKN3HieVhCUlwjeQ==", + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", "dev": true, - "dependencies": { - "image-q": "^4.0.0", - "omggif": "^1.0.10" - } + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "domelementtype": "^2.3.0" }, "engines": { - "node": "*" + "node": ">= 4" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/fb55/domhandler?sponsor=1" } }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "is-glob": "^4.0.3" + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" }, - "engines": { - "node": ">=10.13.0" + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" } }, - "node_modules/global": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", - "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "node_modules/dpdm": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/dpdm/-/dpdm-3.14.0.tgz", + "integrity": "sha512-YJzsFSyEtj88q5eTELg3UWU7TVZkG1dpbF4JDQ3t1b07xuzXmdoGeSz9TKOke1mUuOpWlk4q+pBh+aHzD6GBTg==", "dev": true, + "license": "MIT", "dependencies": { - "min-document": "^2.19.0", - "process": "^0.11.10" + "chalk": "^4.1.2", + "fs-extra": "^11.1.1", + "glob": "^10.3.4", + "ora": "^5.4.1", + "tslib": "^2.6.2", + "typescript": "^5.2.2", + "yargs": "^17.7.2" + }, + "bin": { + "dpdm": "lib/bin/dpdm.js" } }, - "node_modules/globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "node_modules/dpdm/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "license": "MIT", "dependencies": { - "type-fest": "^0.20.2" + "color-convert": "^2.0.1" }, "engines": { "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/globalthis": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", - "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "node_modules/dpdm/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { - "define-properties": "^1.1.3" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/globalyzer": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz", - "integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==", - "dev": true - }, - "node_modules/globrex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", - "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", - "dev": true - }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "node_modules/dpdm/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "license": "MIT", "dependencies": { - "get-intrinsic": "^1.1.3" + "has-flag": "^4.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=8" } }, - "node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - }, - "node_modules/grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", - "dev": true - }, - "node_modules/gzip-size": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", - "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "dev": true, + "license": "MIT", "dependencies": { - "duplexer": "^0.1.2" + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.4" } }, - "node_modules/gzip-size/node_modules/duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "node_modules/duplexer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "integrity": "sha512-sxNZ+ljy+RA1maXoUReeqBBpBC6RLKmg5ewzV+x+mSETmWNoKdZN6vcQjpFROemza23hGFskJtFNoUWUaQ+R4Q==", "dev": true }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } + "license": "MIT" }, - "node_modules/has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "license": "MIT" }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/electron-to-chromium": { + "version": "1.5.177", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.177.tgz", + "integrity": "sha512-7EH2G59nLsEMj97fpDuvVcYi6lwTcM1xuWw3PssD8xzboAW7zj7iB3COEEEATUfjLHrs5uKBLQT03V/8URx06g==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.8" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", "dev": true, + "license": "MIT", + "optional": true, "dependencies": { - "get-intrinsic": "^1.1.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "iconv-lite": "^0.6.2" } }, - "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", "dev": true, + "license": "BSD-2-Clause", "engines": { - "node": ">= 0.4" + "node": ">=0.12" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=6" } }, - "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", + "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", "dev": true, + "license": "MIT", "dependencies": { - "has-symbols": "^1.0.2" + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" }, "engines": { "node": ">= 0.4" @@ -3403,1548 +3683,1781 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", - "dev": true - }, - "node_modules/hosted-git-info": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz", - "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==", + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "dev": true, - "dependencies": { - "lru-cache": "^7.5.1" - }, + "license": "MIT", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">= 0.4" } }, - "node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "dev": true, + "license": "MIT", "engines": { - "node": ">=12" - } - }, - "node_modules/htmlparser2": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.1.tgz", - "integrity": "sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==", - "dev": true, - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "entities": "^4.3.0" + "node": ">= 0.4" } }, - "node_modules/http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", - "dev": true - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "dev": true, + "license": "MIT", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "es-errors": "^1.3.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.4" } }, - "node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "dev": true, + "license": "MIT", "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { - "node": ">= 6" + "node": ">= 0.4" } }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", "dev": true, + "license": "MIT", "dependencies": { - "agent-base": "6", - "debug": "4" + "hasown": "^2.0.2" }, "engines": { - "node": ">= 6" - } - }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "dev": true, - "dependencies": { - "ms": "^2.0.0" + "node": ">= 0.4" } }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", "dev": true, - "optional": true, + "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true, - "engines": { - "node": ">= 4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/ignore-walk": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.1.tgz", - "integrity": "sha512-/c8MxUAqpRccq+LyDOecwF+9KqajueJHh8fz7g3YqjMZt+NSfJzx05zrKiXwa2sKwFCzaiZ5qUVfRj0pmxixEA==", + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, - "dependencies": { - "minimatch": "^6.1.6" - }, + "license": "MIT", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=6" } }, - "node_modules/ignore-walk/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } + "license": "MIT" }, - "node_modules/ignore-walk/node_modules/minimatch": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-6.2.0.tgz", - "integrity": "sha512-sauLxniAmvnhhRjFwPNnJKaPFYyddAgbYdeUpHULtCT/GhzdCx/MDNy+Y40lBxTQUrMzDE8e0S43Z5uqfO0REg==", + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, + "license": "MIT", "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/image-q": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/image-q/-/image-q-4.0.0.tgz", - "integrity": "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==", - "dev": true, - "dependencies": { - "@types/node": "16.9.1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" }, "engines": { - "node": ">=6" + "node": ">=6.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "optionalDependencies": { + "source-map": "~0.6.1" } }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, "engines": { - "node": ">=8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "dev": true + "node_modules/eslint-config-mdcs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-mdcs/-/eslint-config-mdcs-5.0.0.tgz", + "integrity": "sha512-d4lzeT/sQ3TkI69hd+N/dtQ15g3GrbIboTfCAw6FaDQTLjWK2O3+dNfOOfkAC5TlwyU9BxztR1TE+x8iSzyuPw==", + "dev": true, + "license": "MIT" }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", "dev": true, + "license": "MIT", "dependencies": { - "once": "^1.3.0", - "wrappy": "1" + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } }, - "node_modules/internal-slot": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.4.tgz", - "integrity": "sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==", + "node_modules/eslint-module-utils": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", + "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", "dev": true, + "license": "MIT", "dependencies": { - "get-intrinsic": "^1.1.3", - "has": "^1.0.3", - "side-channel": "^1.0.4" + "debug": "^3.2.7" }, "engines": { - "node": ">= 0.4" + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } } }, - "node_modules/ip": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", - "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", - "dev": true + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "node_modules/eslint-plugin-compat": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-compat/-/eslint-plugin-compat-6.0.2.tgz", + "integrity": "sha512-1ME+YfJjmOz1blH0nPZpHgjMGK4kjgEeoYqGCqoBPQ/mGu/dJzdoP0f1C8H2jcWZjzhZjAMccbM/VdXhPORIfA==", "dev": true, + "license": "MIT", + "dependencies": { + "@mdn/browser-compat-data": "^5.5.35", + "ast-metadata-inferer": "^0.8.1", + "browserslist": "^4.24.2", + "caniuse-lite": "^1.0.30001687", + "find-up": "^5.0.0", + "globals": "^15.7.0", + "lodash.memoize": "^4.1.2", + "semver": "^7.6.2" + }, "engines": { - "node": ">= 0.10" + "node": ">=18.x" + }, + "peerDependencies": { + "eslint": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" } }, - "node_modules/is-array-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.1.tgz", - "integrity": "sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==", + "node_modules/eslint-plugin-compat/node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "is-typed-array": "^1.1.10" + "license": "MIT", + "engines": { + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "node_modules/eslint-plugin-html": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-html/-/eslint-plugin-html-8.1.3.tgz", + "integrity": "sha512-cnCdO7yb/jrvgSJJAfRkGDOwLu1AOvNdw8WCD6nh/2C4RnxuI4tz6QjMEAmmSiHSeugq/fXcIO8yBpIBQrMZCg==", "dev": true, + "license": "ISC", "dependencies": { - "has-bigints": "^1.0.1" + "htmlparser2": "^10.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=16.0.0" } }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "node_modules/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" }, "engines": { - "node": ">= 0.4" + "node": ">=4" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, - "node_modules/is-builtin-module": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", - "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "builtin-modules": "^3.3.0" + "esutils": "^2.0.2" }, "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.10.0" } }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, "engines": { - "node": ">= 0.4" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://opencollective.com/eslint" } }, - "node_modules/is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, - "dependencies": { - "has": "^1.0.3" + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://opencollective.com/eslint" } }, - "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">= 0.4" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "bin": { - "is-docker": "cli.js" + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, "engines": { - "node": ">=8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/is-function": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", - "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==", - "dev": true - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" }, "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, - "node_modules/is-lambda": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", - "dev": true - }, - "node_modules/is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", - "dev": true - }, - "node_modules/is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, - "engines": { - "node": ">= 0.4" + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=0.10" } }, - "node_modules/is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "has-tostringtag": "^1.0.0" + "estraverse": "^5.2.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=4.0" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, + "license": "BSD-2-Clause", "engines": { - "node": ">=8" + "node": ">=4.0" } }, - "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=0.10.0" } }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "dev": true, - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "license": "MIT", + "engines": { + "node": ">= 0.6" } }, - "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, + "license": "MIT", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=6" } }, - "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/exif-parser": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz", + "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==", + "dev": true + }, + "node_modules/exponential-backoff": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.2.tgz", + "integrity": "sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "dev": true, + "license": "MIT", "dependencies": { - "has-symbols": "^1.0.2" + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" }, "engines": { - "node": ">= 0.4" + "node": ">= 0.10.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/is-typed-array": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", - "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, + "license": "MIT", "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" }, "engines": { - "node": ">= 0.4" + "node": ">= 10.17.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "optionalDependencies": { + "@types/yauzl": "^2.9.1" } }, - "node_modules/is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "node_modules/failonlyreporter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/failonlyreporter/-/failonlyreporter-1.0.0.tgz", + "integrity": "sha512-daW559J4F/nWk0AiUPuxpCNCRXNa74yQdZNAVBIJt192VbsfKMNZocCqvRLjFIIp9BeBGu4gUhFJImmb4kSWOQ==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "chalk": "^2.4.2" } }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "node_modules/failonlyreporter/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, + "license": "MIT", "dependencies": { - "is-docker": "^2.0.0" + "color-convert": "^1.9.0" }, "engines": { - "node": ">=8" + "node": ">=4" } }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/isomorphic-fetch": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", - "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", + "node_modules/failonlyreporter/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, + "license": "MIT", "dependencies": { - "node-fetch": "^2.6.1", - "whatwg-fetch": "^3.4.1" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" } }, - "node_modules/jimp": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.22.8.tgz", - "integrity": "sha512-pBbrooJMX7795sDcxx1XpwNZC8B/ITyDV+JK2/1qNbQl/1UWqWeh5Dq7qQpMZl5jLdcFDv5IVTM+OhpafSqSFA==", + "node_modules/failonlyreporter/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, + "license": "MIT", "dependencies": { - "@jimp/custom": "^0.22.8", - "@jimp/plugins": "^0.22.8", - "@jimp/types": "^0.22.8", - "regenerator-runtime": "^0.13.3" + "color-name": "1.1.3" } }, - "node_modules/jpeg-js": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz", - "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==", - "dev": true - }, - "node_modules/js-sdsl": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", - "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", + "node_modules/failonlyreporter/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" - } + "license": "MIT" }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "node_modules/failonlyreporter/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "license": "MIT", + "engines": { + "node": ">=0.8.0" } }, - "node_modules/json-parse-even-better-errors": { + "node_modules/failonlyreporter/node_modules/has-flag": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.0.tgz", - "integrity": "sha512-iZbGHafX/59r39gPwVPRBGw0QQKnA7tte5pSMrhWOW7swGsVvVTjmfyAV9pNqk8YGT7tRCdxRu8uzcgZwoDooA==", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, + "license": "MIT", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=4" } }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "node_modules/failonlyreporter/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, + "license": "MIT", "dependencies": { - "minimist": "^1.2.0" + "has-flag": "^3.0.0" }, - "bin": { - "json5": "lib/cli.js" + "engines": { + "node": ">=4" } }, - "node_modules/jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true, - "engines": [ - "node >= 0.2.0" - ] + "license": "MIT" }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" + "reusify": "^1.0.4" } }, - "node_modules/load-bmfont": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.1.tgz", - "integrity": "sha512-8UyQoYmdRDy81Brz6aLAUhfZLwr5zV0L3taTQ4hju7m6biuwiWiJXjPhBJxbUQJA8PrkvJ/7Enqmwk2sM14soA==", + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", "dev": true, + "license": "MIT", "dependencies": { - "buffer-equal": "0.0.1", - "mime": "^1.3.4", - "parse-bmfont-ascii": "^1.0.3", - "parse-bmfont-binary": "^1.0.5", - "parse-bmfont-xml": "^1.1.4", - "phin": "^2.9.1", - "xhr": "^2.0.1", - "xtend": "^4.0.0" + "pend": "~1.2.0" } }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, + "license": "MIT", "dependencies": { - "p-locate": "^5.0.0" + "flat-cache": "^3.0.4" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "node_modules/file-type": { + "version": "16.5.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", + "integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==", "dev": true, + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" + "readable-web-to-node-stream": "^3.0.0", + "strtok3": "^6.2.4", + "token-types": "^4.1.1" }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" } }, - "node_modules/magic-string": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz", - "integrity": "sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==", + "node_modules/filesize": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-6.4.0.tgz", + "integrity": "sha512-mjFIpOHC4jbfcTfoh4rkWpI31mF7viw9ikj/JyLoKzqlwG/YsefKfvYlYhdYdg/9mtK2z1AzgN/0LvVQ3zdlSQ==", "dev": true, - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.13" - }, + "license": "BSD-3-Clause", "engines": { - "node": ">=12" + "node": ">= 0.4.0" } }, - "node_modules/make-fetch-happen": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", - "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dev": true, + "license": "MIT", "dependencies": { - "agentkeepalive": "^4.2.1", - "cacache": "^16.1.0", - "http-cache-semantics": "^4.1.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^3.1.6", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^2.0.3", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", - "ssri": "^9.0.0" + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">= 0.8" } }, - "node_modules/make-fetch-happen/node_modules/@npmcli/fs": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", - "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, + "license": "MIT", "dependencies": { - "@gar/promisify": "^1.1.3", - "semver": "^7.3.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "ms": "2.0.0" } }, - "node_modules/make-fetch-happen/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, + "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/make-fetch-happen/node_modules/cacache": { - "version": "16.1.3", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", - "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, + "license": "MIT", "dependencies": { - "@npmcli/fs": "^2.1.0", - "@npmcli/move-file": "^2.0.0", - "chownr": "^2.0.0", - "fs-minipass": "^2.1.0", - "glob": "^8.0.1", - "infer-owner": "^1.0.4", - "lru-cache": "^7.7.1", - "minipass": "^3.1.6", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "mkdirp": "^1.0.4", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^9.0.0", - "tar": "^6.1.11", - "unique-filename": "^2.0.0" + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/make-fetch-happen/node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", "dev": true, + "license": "MIT", "dependencies": { - "minipass": "^3.0.0" + "is-callable": "^1.2.7" }, "engines": { - "node": ">= 8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/make-fetch-happen/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, + "license": "ISC", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" }, "engines": { - "node": ">=12" + "node": ">=14" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/make-fetch-happen/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "dev": true, + "license": "MIT", "engines": { - "node": ">=12" - } - }, - "node_modules/make-fetch-happen/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" + "node": ">= 0.6" } }, - "node_modules/make-fetch-happen/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.6" } }, - "node_modules/make-fetch-happen/node_modules/ssri": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", - "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", + "node_modules/fs-extra": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", + "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", "dev": true, + "license": "MIT", "dependencies": { - "minipass": "^3.1.1" + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=14.14" } }, - "node_modules/make-fetch-happen/node_modules/unique-filename": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", - "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", + "node_modules/fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", "dev": true, + "license": "ISC", "dependencies": { - "unique-slug": "^3.0.0" + "minipass": "^7.0.3" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/make-fetch-happen/node_modules/unique-slug": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", - "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } + "license": "ISC" }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">= 0.6" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", - "dev": true - }, - "node_modules/methods": { + "node_modules/function-bind": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true, - "engines": { - "node": ">= 0.6" + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", "dev": true, - "bin": { - "mime": "cli.js" + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" }, "engines": { - "node": ">=4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "dev": true, - "engines": { - "node": ">= 0.6" + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "node_modules/gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "deprecated": "This package is no longer supported.", "dev": true, + "license": "ISC", "dependencies": { - "mime-db": "1.52.0" + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" }, "engines": { - "node": ">= 0.6" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/min-document": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", - "integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==", + "node_modules/gauge/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, - "dependencies": { - "dom-walk": "^0.1.0" - } + "license": "MIT" }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/gauge/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/gauge/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": "*" + "node": ">=8" } }, - "node_modules/minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/minipass": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.4.tgz", - "integrity": "sha512-lwycX3cBMTvcejsHITUgYj6Gy6A7Nh4Q6h9NP4sTHY1ccJlC7yKzDmiShEHsJ16Jf1nKGDEaiHxiltsJEvk0nQ==", + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/minipass-collect": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", - "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "dev": true, + "license": "MIT", "dependencies": { - "minipass": "^3.0.0" + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": ">= 8" + "node": ">= 0.4" } }, - "node_modules/minipass-collect/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "dev": true, + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" + "pump": "^3.0.0" }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/minipass-fetch": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", - "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", "dev": true, + "license": "MIT", "dependencies": { - "minipass": "^3.1.6", - "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">= 0.4" }, - "optionalDependencies": { - "encoding": "^0.1.13" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/minipass-fetch/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "node_modules/get-uri": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.4.tgz", + "integrity": "sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==", "dev": true, + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" }, "engines": { - "node": ">=8" + "node": ">= 14" } }, - "node_modules/minipass-flush": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "node_modules/gifwrap": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.10.1.tgz", + "integrity": "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw==", "dev": true, + "license": "MIT", "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" + "image-q": "^4.0.0", + "omggif": "^1.0.10" } }, - "node_modules/minipass-flush/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, + "license": "ISC", "dependencies": { - "yallist": "^4.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, - "engines": { - "node": ">=8" + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/minipass-json-stream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz", - "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==", + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, + "license": "ISC", "dependencies": { - "jsonparse": "^1.3.1", - "minipass": "^3.0.0" + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" } }, - "node_modules/minipass-json-stream/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" + "balanced-match": "^1.0.0" } }, - "node_modules/minipass-pipeline": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, + "license": "ISC", "dependencies": { - "minipass": "^3.0.0" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/minipass-pipeline/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" + "type-fest": "^0.20.2" }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/minipass-sized": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", - "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", "dev": true, + "license": "MIT", "dependencies": { - "minipass": "^3.0.0" + "define-properties": "^1.2.1", + "gopd": "^1.0.1" }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/minipass-sized/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "node_modules/globalyzer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz", + "integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==", "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } + "license": "MIT" }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", "dev": true, - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } + "license": "MIT" }, - "node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mitt": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz", - "integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==", - "dev": true + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" + "license": "MIT" + }, + "node_modules/gzip-size": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "duplexer": "^0.1.2" }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "dev": true - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "node_modules/gzip-size/node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", "dev": true, - "engines": { - "node": ">= 0.6" - } + "license": "MIT" }, - "node_modules/node-fetch": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", - "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", "dev": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, + "license": "MIT", "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" + "node": ">= 0.4" }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">= 6.13.0" + "node": ">=8" } }, - "node_modules/node-gyp": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.3.1.tgz", - "integrity": "sha512-4Q16ZCqq3g8awk6UplT7AuxQ35XN4R/yf/+wSAwcBUAjg7l58RTactWaP8fIDTi0FzI7YcVLujwExakZlfWkXg==", + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dev": true, + "license": "MIT", "dependencies": { - "env-paths": "^2.2.0", - "glob": "^7.1.4", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^10.0.3", - "nopt": "^6.0.0", - "npmlog": "^6.0.0", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.2", - "which": "^2.0.2" + "es-define-property": "^1.0.0" }, - "bin": { - "node-gyp": "bin/node-gyp.js" + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" }, "engines": { - "node": "^12.13 || ^14.13 || >=16" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/node-releases": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", - "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", - "dev": true - }, - "node_modules/node-watch": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/node-watch/-/node-watch-0.7.3.tgz", - "integrity": "sha512-3l4E8uMPY1HdMMryPRUAl+oIHtXtyiTlIiESNSVSNxcPfzAFzeTbXFQkZfAwBbo0B1qMSG8nUABx+Gd+YrbKrQ==", + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">=6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/nopt": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", - "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, + "license": "MIT", "dependencies": { - "abbrev": "^1.0.0" - }, - "bin": { - "nopt": "bin/nopt.js" + "has-symbols": "^1.0.3" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/normalize-package-data": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-5.0.0.tgz", - "integrity": "sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==", + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, + "license": "MIT", "dependencies": { - "hosted-git-info": "^6.0.0", - "is-core-module": "^2.8.1", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" + "function-bind": "^1.1.2" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">= 0.4" } }, - "node_modules/npm-bundled": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.0.tgz", - "integrity": "sha512-Vq0eyEQy+elFpzsKjMss9kxqb9tG3YHg4dsyWuUENuzvSUWe1TCnW/vV9FkhvBk/brEDoDiVd+M1Btosa6ImdQ==", + "node_modules/hosted-git-info": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.3.tgz", + "integrity": "sha512-HVJyzUrLIL1c0QmviVh5E8VGyUS7xCFPS6yydaVd1UegW+ibV/CohqTH9MkOLDp5o+rb82DMo77PTuc9F/8GKw==", "dev": true, + "license": "ISC", "dependencies": { - "npm-normalize-package-bin": "^3.0.0" + "lru-cache": "^7.5.1" }, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/npm-install-checks": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.0.0.tgz", - "integrity": "sha512-SBU9oFglRVZnfElwAtF14NivyulDqF1VKqqwNsFW9HDcbHMAPHpRSsVFgKuwFGq/hVvWZExz62Th0kvxn/XE7Q==", + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", "dev": true, - "dependencies": { - "semver": "^7.1.1" - }, + "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=12" } }, - "node_modules/npm-normalize-package-bin": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.0.tgz", - "integrity": "sha512-g+DPQSkusnk7HYXr75NtzkIP4+N81i3RPsGFidF3DzHd9MT9wWngmqoeg/fnHFz5MNdtG4w03s+QnhewSLTT2Q==", + "node_modules/htmlparser2": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz", + "integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==", "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.1", + "entities": "^6.0.0" } }, - "node_modules/npm-package-arg": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-10.1.0.tgz", - "integrity": "sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA==", + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "dev": true, + "license": "MIT", "dependencies": { - "hosted-git-info": "^6.0.0", - "proc-log": "^3.0.0", - "semver": "^7.3.5", - "validate-npm-package-name": "^5.0.0" + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">= 0.8" } }, - "node_modules/npm-packlist": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-7.0.4.tgz", - "integrity": "sha512-d6RGEuRrNS5/N84iglPivjaJPxhDbZmlbTwTDX2IbcRHG5bZCdtysYMhwiPvcF4GisXHGn7xsxv+GQ7T/02M5Q==", + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, + "license": "MIT", "dependencies": { - "ignore-walk": "^6.0.0" + "agent-base": "^7.1.0", + "debug": "^4.3.4" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">= 14" } }, - "node_modules/npm-pick-manifest": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-8.0.1.tgz", - "integrity": "sha512-mRtvlBjTsJvfCCdmPtiu2bdlx8d/KXtF7yNXNWe7G0Z36qWA9Ny5zXsI2PfBZEv7SXgoxTmNaTzGSbbzDZChoA==", + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, + "license": "MIT", "dependencies": { - "npm-install-checks": "^6.0.0", - "npm-normalize-package-bin": "^3.0.0", - "npm-package-arg": "^10.0.0", - "semver": "^7.3.5" + "agent-base": "^7.1.2", + "debug": "4" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">= 14" } }, - "node_modules/npm-registry-fetch": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-14.0.3.tgz", - "integrity": "sha512-YaeRbVNpnWvsGOjX2wk5s85XJ7l1qQBGAp724h8e2CZFFhMSuw9enom7K1mWVUtvXO1uUSFIAPofQK0pPN0ZcA==", + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, + "license": "MIT", + "optional": true, "dependencies": { - "make-fetch-happen": "^11.0.0", - "minipass": "^4.0.0", - "minipass-fetch": "^3.0.0", - "minipass-json-stream": "^1.0.1", - "minizlib": "^2.1.2", - "npm-package-arg": "^10.0.0", - "proc-log": "^3.0.0" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=0.10.0" } }, - "node_modules/npm-registry-fetch/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, + "license": "MIT", "engines": { - "node": ">=12" + "node": ">= 4" } }, - "node_modules/npm-registry-fetch/node_modules/make-fetch-happen": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.0.3.tgz", - "integrity": "sha512-oPLh5m10lRNNZDjJ2kP8UpboUx2uFXVaVweVe/lWut4iHWcQEmfqSVJt2ihZsFI8HbpwyyocaXbCAWf0g1ukIA==", + "node_modules/ignore-walk": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.5.tgz", + "integrity": "sha512-VuuG0wCnjhnylG1ABXT3dAuIpTNDs/G8jlpmwXY03fXoXy/8ZK8/T+hMzt8L4WnrLCJgdybqgPagnF/f97cg3A==", "dev": true, + "license": "ISC", "dependencies": { - "agentkeepalive": "^4.2.1", - "cacache": "^17.0.0", - "http-cache-semantics": "^4.1.1", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^4.0.0", - "minipass-fetch": "^3.0.0", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", - "ssri": "^10.0.0" + "minimatch": "^9.0.0" }, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/npm-registry-fetch/node_modules/minipass-fetch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.1.tgz", - "integrity": "sha512-t9/wowtf7DYkwz8cfMSt0rMwiyNIBXf5CKZ3S5ZMqRqMYT0oLTp0x1WorMI9WTwvaPg21r1JbFxJMum8JrLGfw==", + "node_modules/ignore-walk/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, + "license": "MIT", "dependencies": { - "minipass": "^4.0.0", - "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" + "balanced-match": "^1.0.0" + } + }, + "node_modules/ignore-walk/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=16 || 14 >=14.17" }, - "optionalDependencies": { - "encoding": "^0.1.13" + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/npmlog": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", - "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "node_modules/image-q": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/image-q/-/image-q-4.0.0.tgz", + "integrity": "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==", "dev": true, + "license": "MIT", "dependencies": { - "are-we-there-yet": "^3.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^4.0.3", - "set-blocking": "^2.0.0" + "@types/node": "16.9.1" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=0.8.19" } }, - "node_modules/object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "license": "MIT", + "engines": { + "node": ">=8" } }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "node_modules/infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true, + "license": "ISC" + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, "engines": { "node": ">= 0.4" } }, - "node_modules/object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" }, "engines": { "node": ">= 0.4" @@ -4953,15 +5466,25 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object.values": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", - "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -4970,1716 +5493,1754 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/omggif": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz", - "integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==", - "dev": true - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", "dev": true, + "license": "MIT", "dependencies": { - "ee-first": "1.1.1" + "has-bigints": "^1.0.2" }, "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/open": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", - "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", "dev": true, + "license": "MIT", "dependencies": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { - "node": ">=12" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "dev": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - }, + "license": "MIT", "engines": { - "node": ">= 0.8.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, + "license": "MIT", "dependencies": { - "yocto-queue": "^0.1.0" + "hasown": "^2.0.2" }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", "dev": true, + "license": "MIT", "dependencies": { - "p-limit": "^3.0.2" + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", "dev": true, + "license": "MIT", "dependencies": { - "aggregate-error": "^3.0.0" + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/pacote": { - "version": "15.1.1", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-15.1.1.tgz", - "integrity": "sha512-eeqEe77QrA6auZxNHIp+1TzHQ0HBKf5V6c8zcaYZ134EJe1lCi+fjXATkNiEEfbG+e50nu02GLvUtmZcGOYabQ==", + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", "dev": true, - "dependencies": { - "@npmcli/git": "^4.0.0", - "@npmcli/installed-package-contents": "^2.0.1", - "@npmcli/promise-spawn": "^6.0.1", - "@npmcli/run-script": "^6.0.0", - "cacache": "^17.0.0", - "fs-minipass": "^3.0.0", - "minipass": "^4.0.0", - "npm-package-arg": "^10.0.0", - "npm-packlist": "^7.0.0", - "npm-pick-manifest": "^8.0.0", - "npm-registry-fetch": "^14.0.0", - "proc-log": "^3.0.0", - "promise-retry": "^2.0.1", - "read-package-json": "^6.0.0", - "read-package-json-fast": "^3.0.0", - "sigstore": "^1.0.0", - "ssri": "^10.0.0", - "tar": "^6.1.11" - }, + "license": "MIT", "bin": { - "pacote": "lib/bin.js" + "is-docker": "cli.js" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, + "license": "MIT", "engines": { - "node": ">=6" + "node": ">=0.10.0" } }, - "node_modules/parse-bmfont-ascii": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz", - "integrity": "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==", - "dev": true - }, - "node_modules/parse-bmfont-binary": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz", - "integrity": "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==", - "dev": true - }, - "node_modules/parse-bmfont-xml": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.4.tgz", - "integrity": "sha512-bjnliEOmGv3y1aMEfREMBJ9tfL3WR0i0CKPj61DnSLaoxWR3nLrsQrEbCId/8rF4NyRF0cCqisSVXyQYWM+mCQ==", + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", "dev": true, + "license": "MIT", "dependencies": { - "xml-parse-from-string": "^1.0.0", - "xml2js": "^0.4.5" + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/parse-headers": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.5.tgz", - "integrity": "sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==", - "dev": true - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">=8" } }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, "engines": { "node": ">=0.10.0" } }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", - "dev": true + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "dev": true, + "license": "MIT" }, - "node_modules/peek-readable": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", - "integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==", + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.4" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true - }, - "node_modules/phin": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/phin/-/phin-2.9.3.tgz", - "integrity": "sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA==", - "dev": true - }, - "node_modules/picocolors": { + "node_modules/is-module": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true, + "license": "MIT" }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8.6" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/pixelmatch": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.3.0.tgz", - "integrity": "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==", + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", "dev": true, + "license": "MIT", "dependencies": { - "pngjs": "^6.0.0" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, - "bin": { - "pixelmatch": "bin/pixelmatch" + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/pngjs": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", - "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">=12.13.0" + "node": ">=8" } }, - "node_modules/prelude-ls": { + "node_modules/is-regex": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, "engines": { - "node": ">= 0.8.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/proc-log": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", - "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==", + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", "dev": true, + "license": "MIT", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, "engines": { - "node": ">= 0.6.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, "engines": { - "node": ">=0.4.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", - "dev": true - }, - "node_modules/promise-retry": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", - "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", "dev": true, + "license": "MIT", "dependencies": { - "err-code": "^2.0.2", - "retry": "^0.12.0" + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" }, "engines": { - "node": ">=10" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", "dev": true, + "license": "MIT", "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" + "which-typed-array": "^1.1.16" }, "engines": { - "node": ">= 0.10" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true - }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", "dev": true, + "license": "MIT", "engines": { - "node": ">=6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/puppeteer-core": { - "version": "19.11.1", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-19.11.1.tgz", - "integrity": "sha512-qcuC2Uf0Fwdj9wNtaTZ2OvYRraXpAK+puwwVW8ofOhOgLPZyz1c68tsorfIZyCUOpyBisjr+xByu7BMbEYMepA==", + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", "dev": true, + "license": "MIT", "dependencies": { - "@puppeteer/browsers": "0.5.0", - "chromium-bidi": "0.4.7", - "cross-fetch": "3.1.5", - "debug": "4.3.4", - "devtools-protocol": "0.0.1107588", - "extract-zip": "2.0.1", - "https-proxy-agent": "5.0.1", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "ws": "8.13.0" + "call-bound": "^1.0.3" }, "engines": { - "node": ">=14.14.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" + "node": ">= 0.4" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", "dev": true, + "license": "MIT", "dependencies": { - "side-channel": "^1.0.4" + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" }, "engines": { - "node": ">=0.6" + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/qunit": { - "version": "2.19.4", - "resolved": "https://registry.npmjs.org/qunit/-/qunit-2.19.4.tgz", - "integrity": "sha512-aqUzzUeCqlleWYKlpgfdHHw9C6KxkB9H3wNfiBg5yHqQMzy0xw/pbCRHYFkjl8MsP/t8qkTQE+JTYL71azgiew==", + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "dev": true, + "license": "MIT", "dependencies": { - "commander": "7.2.0", - "node-watch": "0.7.3", - "tiny-glob": "0.2.9" - }, - "bin": { - "qunit": "bin/qunit.js" + "is-docker": "^2.0.0" }, "engines": { - "node": ">=10" + "node": ">=8" } }, - "node_modules/qunit/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", "dev": true, - "engines": { - "node": ">= 10" - } + "license": "MIT" }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } + "license": "ISC" }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, - "engines": { - "node": ">= 0.6" + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "node_modules/jimp": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/jimp/-/jimp-1.6.0.tgz", + "integrity": "sha512-YcwCHw1kiqEeI5xRpDlPPBGL2EOpBKLwO4yIBJcXWHPj5PnA5urGq0jbyhM5KoNpypQ6VboSoxc9D8HyfvngSg==", "dev": true, + "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" + "@jimp/core": "1.6.0", + "@jimp/diff": "1.6.0", + "@jimp/js-bmp": "1.6.0", + "@jimp/js-gif": "1.6.0", + "@jimp/js-jpeg": "1.6.0", + "@jimp/js-png": "1.6.0", + "@jimp/js-tiff": "1.6.0", + "@jimp/plugin-blit": "1.6.0", + "@jimp/plugin-blur": "1.6.0", + "@jimp/plugin-circle": "1.6.0", + "@jimp/plugin-color": "1.6.0", + "@jimp/plugin-contain": "1.6.0", + "@jimp/plugin-cover": "1.6.0", + "@jimp/plugin-crop": "1.6.0", + "@jimp/plugin-displace": "1.6.0", + "@jimp/plugin-dither": "1.6.0", + "@jimp/plugin-fisheye": "1.6.0", + "@jimp/plugin-flip": "1.6.0", + "@jimp/plugin-hash": "1.6.0", + "@jimp/plugin-mask": "1.6.0", + "@jimp/plugin-print": "1.6.0", + "@jimp/plugin-quantize": "1.6.0", + "@jimp/plugin-resize": "1.6.0", + "@jimp/plugin-rotate": "1.6.0", + "@jimp/plugin-threshold": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0" + }, + "engines": { + "node": ">=18" } }, - "node_modules/raw-body/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "node_modules/jpeg-js": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz", + "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "argparse": "^2.0.1" }, - "engines": { - "node": ">=0.10.0" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/read-package-json": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-6.0.0.tgz", - "integrity": "sha512-b/9jxWJ8EwogJPpv99ma+QwtqB7FSl3+V6UXS7Aaay8/5VwMY50oIFooY1UKXMWpfNCM6T/PoGqa5GD1g9xf9w==", + "node_modules/js2xmlparser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "glob": "^8.0.1", - "json-parse-even-better-errors": "^3.0.0", - "normalize-package-data": "^5.0.0", - "npm-normalize-package-bin": "^3.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "xmlcreate": "^2.0.4" } }, - "node_modules/read-package-json-fast": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz", - "integrity": "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==", + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", "dev": true, - "dependencies": { - "json-parse-even-better-errors": "^3.0.0", - "npm-normalize-package-bin": "^3.0.0" + "license": "MIT" + }, + "node_modules/jsdoc": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.4.tgz", + "integrity": "sha512-zeFezwyXeG4syyYHbvh1A967IAqq/67yXtXvuL5wnqCkFZe8I0vKfm+EO+YEvLguo6w9CDUbrAXVtJSHh2E8rw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@babel/parser": "^7.20.15", + "@jsdoc/salty": "^0.2.1", + "@types/markdown-it": "^14.1.1", + "bluebird": "^3.7.2", + "catharsis": "^0.9.0", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.2", + "klaw": "^3.0.0", + "markdown-it": "^14.1.0", + "markdown-it-anchor": "^8.6.7", + "marked": "^4.0.10", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "underscore": "~1.13.2" + }, + "bin": { + "jsdoc": "jsdoc.js" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=12.0.0" } }, - "node_modules/read-package-json/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "node_modules/jsdoc/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" + "license": "MIT", + "engines": { + "node": ">=8" } }, - "node_modules/read-package-json/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, + "license": "MIT", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" + "minimist": "^1.2.0" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "bin": { + "json5": "lib/cli.js" } }, - "node_modules/read-package-json/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, + "license": "MIT", "dependencies": { - "brace-expansion": "^2.0.1" + "universalify": "^2.0.0" }, - "engines": { - "node": ">=10" + "optionalDependencies": { + "graceful-fs": "^4.1.6" } }, - "node_modules/readable-stream": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.1.tgz", - "integrity": "sha512-+rQmrWMYGA90yenhTYsLWAsLsqVC8osOw6PKE1HDYiO0gdPeKe/xDHNzIAIn4C91YQ6oenEhfYqqc1883qHbjQ==", + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, + "engines": [ + "node >= 0.2.0" + ], + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, + "license": "MIT", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" + "json-buffer": "3.0.1" } }, - "node_modules/readable-web-to-node-stream": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz", - "integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==", + "node_modules/klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", "dev": true, + "license": "MIT", "dependencies": { - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" + "graceful-fs": "^4.1.9" } }, - "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", - "dev": true - }, - "node_modules/regexp.prototype.flags": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", - "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "functions-have-names": "^1.2.2" + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 0.8.0" } }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true, - "engines": { - "node": ">=0.10.0" + "license": "MIT" + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" } }, - "node_modules/resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, + "license": "MIT", "dependencies": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" + "p-locate": "^5.0.0" }, - "bin": { - "resolve": "bin/resolve" + "engines": { + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true, - "engines": { - "node": ">=4" - } + "license": "MIT" }, - "node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", "dev": true, - "engines": { - "node": ">= 4" - } + "license": "MIT" }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } + "license": "MIT" }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "dev": true, + "license": "MIT", "dependencies": { - "glob": "^7.1.3" + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" }, - "bin": { - "rimraf": "bin.js" + "engines": { + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/rollup": { - "version": "3.21.7", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.21.7.tgz", - "integrity": "sha512-KXPaEuR8FfUoK2uHwNjxTmJ18ApyvD6zJpYv9FOJSqLStmt6xOY84l1IjK2dSolQmoXknrhEFRaPRgOPdqCT5w==", + "node_modules/log-symbols/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "bin": { - "rollup": "dist/bin/rollup" + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" }, "engines": { - "node": ">=14.18.0", - "npm": ">=8.0.0" + "node": ">=8" }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/rollup-plugin-filesize": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-filesize/-/rollup-plugin-filesize-10.0.0.tgz", - "integrity": "sha512-JAYYhzCcmGjmCzo3LEHSDE3RAPHKIeBdpqRhiyZSv5o/3wFhktUOzYAWg/uUKyEu5dEaVaql6UOmaqHx1qKrZA==", + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.13.8", - "boxen": "^5.0.0", - "brotli-size": "4.0.0", - "colors": "1.4.0", - "filesize": "^6.1.0", - "gzip-size": "^6.0.0", - "pacote": "^15.1.1", - "terser": "^5.6.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/rollup-plugin-visualizer": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.9.0.tgz", - "integrity": "sha512-bbDOv47+Bw4C/cgs0czZqfm8L82xOZssk4ayZjG40y9zbXclNk7YikrZTDao6p7+HDiGxrN0b65SgZiVm9k1Cg==", + "node_modules/log-symbols/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "license": "MIT", "dependencies": { - "open": "^8.4.0", - "picomatch": "^2.3.1", - "source-map": "^0.7.4", - "yargs": "^17.5.1" - }, - "bin": { - "rollup-plugin-visualizer": "dist/bin/cli.js" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=14" - }, - "peerDependencies": { - "rollup": "2.x || 3.x" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } + "node": ">=8" } }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } + "license": "ISC" }, - "node_modules/rxjs": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", - "integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==", + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", "dev": true, + "license": "MIT", "dependencies": { - "tslib": "^2.1.0" + "@jridgewell/sourcemap-codec": "^1.5.0" } }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/safe-regex-test": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", - "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "node_modules/make-fetch-happen": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", + "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", "dev": true, + "license": "ISC", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "is-regex": "^1.1.4" + "agentkeepalive": "^4.2.1", + "cacache": "^16.1.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^2.0.3", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^9.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/safer-buffer": { + "node_modules/make-fetch-happen/node_modules/@npmcli/fs": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "node_modules/sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true - }, - "node_modules/secure-compare": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", - "integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==", - "dev": true - }, - "node_modules/selfsigned": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.1.1.tgz", - "integrity": "sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", + "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", "dev": true, + "license": "ISC", "dependencies": { - "node-forge": "^1" + "@gar/promisify": "^1.1.3", + "semver": "^7.3.5" }, "engines": { - "node": ">=10" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "node_modules/make-fetch-happen/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "dev": true, + "license": "MIT", "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" + "debug": "4" }, "engines": { - "node": ">=10" + "node": ">= 6.0.0" } }, - "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "node_modules/make-fetch-happen/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/cacache": { + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", + "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", "dev": true, + "license": "ISC", "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" + "@npmcli/fs": "^2.1.0", + "@npmcli/move-file": "^2.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "glob": "^8.0.1", + "infer-owner": "^1.0.4", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11", + "unique-filename": "^2.0.0" }, "engines": { - "node": ">= 0.8.0" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/make-fetch-happen/node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", "dev": true, + "license": "ISC", "dependencies": { - "ms": "2.0.0" + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" } }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/serialize-javascript": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", - "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "node_modules/make-fetch-happen/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, + "license": "ISC", "dependencies": { - "randombytes": "^2.1.0" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "node_modules/make-fetch-happen/node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", "dev": true, + "license": "MIT", "dependencies": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 6" } }, - "node_modules/serve-index/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/make-fetch-happen/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "dev": true, + "license": "MIT", "dependencies": { - "ms": "2.0.0" + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" } }, - "node_modules/serve-index/node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "node_modules/make-fetch-happen/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", "dev": true, + "license": "ISC", "engines": { - "node": ">= 0.6" + "node": ">=12" } }, - "node_modules/serve-index/node_modules/http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "node_modules/make-fetch-happen/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, + "license": "ISC", "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">= 0.6" + "node": ">=10" } }, - "node_modules/serve-index/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "dev": true + "node_modules/make-fetch-happen/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } }, - "node_modules/serve-index/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/serve-index/node_modules/setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true - }, - "node_modules/serve-index/node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "node_modules/make-fetch-happen/node_modules/socks-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", + "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, "engines": { - "node": ">= 0.6" + "node": ">= 10" } }, - "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "node_modules/make-fetch-happen/node_modules/ssri": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", + "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", "dev": true, + "license": "ISC", "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" + "minipass": "^3.1.1" }, "engines": { - "node": ">= 0.8.0" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/server-destroy": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/server-destroy/-/server-destroy-1.0.1.tgz", - "integrity": "sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==", - "dev": true - }, - "node_modules/servez": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/servez/-/servez-1.14.2.tgz", - "integrity": "sha512-bB2h2O/aVAzWjxjovGlDHP4Hv65+YgNwjwq5uIb6EAwOfnBCgfu/yOy7t76blJ/g7LSdbBNh55Mvr1IH5WPn6Q==", + "node_modules/make-fetch-happen/node_modules/unique-filename": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", + "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", "dev": true, + "license": "ISC", "dependencies": { - "ansi-colors": "^4.1.1", - "color-support": "^1.1.3", - "optionator": "^0.8.2", - "servez-lib": "^2.7.0" + "unique-slug": "^3.0.0" }, - "bin": { - "servez": "bin/servez" + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/servez-lib": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/servez-lib/-/servez-lib-2.7.0.tgz", - "integrity": "sha512-mWF7AVrFMQ8C2Jzh8LQGeY8c97dh5mXGdUu8OzcFf1yQBfTdhcTaeHwVylzR/oMibrnze9fjkd+9MJHDJiN6sw==", + "node_modules/make-fetch-happen/node_modules/unique-slug": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", + "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", "dev": true, + "license": "ISC", "dependencies": { - "basic-auth": "^2.0.1", - "cors": "^2.8.5", - "debug": "^4.3.4", - "express": "^4.18.2", - "secure-compare": "^3.0.1", - "selfsigned": "^2.1.1", - "serve-index": "^1.9.1", - "server-destroy": "^1.0.1" + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/servez/node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", "dev": true, + "license": "MIT", "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" }, - "engines": { - "node": ">= 0.8.0" + "bin": { + "markdown-it": "bin/markdown-it.mjs" } }, - "node_modules/servez/node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "node_modules/markdown-it-anchor": { + "version": "8.6.7", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz", + "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==", "dev": true, - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" + "license": "Unlicense", + "peerDependencies": { + "@types/markdown-it": "*", + "markdown-it": "*" } }, - "node_modules/servez/node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "node_modules/markdown-it/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true, + "license": "BSD-2-Clause", "engines": { - "node": ">= 0.8.0" + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/servez/node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2" + "license": "MIT", + "bin": { + "marked": "bin/marked.js" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 12" } }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.4" } }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.6" } }, - "node_modules/shell-quote": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.0.tgz", - "integrity": "sha512-QHsz8GgQIGKlRi24yFc6a6lN69Idnx634w49ay6+jA5yFh7a1UY+4Rp6HPx/L/1zcEDPEij8cIsiqR6bQsE5VQ==", + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", "dev": true, + "license": "MIT", "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "license": "MIT", + "engines": { + "node": ">= 0.6" } }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "node_modules/sigstore": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-1.0.0.tgz", - "integrity": "sha512-e+qfbn/zf1+rCza/BhIA//Awmf0v1pa5HQS8Xk8iXrn9bgytytVLqYD0P7NSqZ6IELTgq+tcDvLPkQjNHyWLNg==", + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", "dev": true, - "dependencies": { - "make-fetch-happen": "^11.0.1", - "tuf-js": "^1.0.0" - }, + "license": "MIT", "bin": { - "sigstore": "bin/sigstore.js" + "mime": "cli.js" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=10.0.0" } }, - "node_modules/sigstore/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, + "license": "MIT", "engines": { - "node": ">=12" + "node": ">= 0.6" } }, - "node_modules/sigstore/node_modules/make-fetch-happen": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.0.3.tgz", - "integrity": "sha512-oPLh5m10lRNNZDjJ2kP8UpboUx2uFXVaVweVe/lWut4iHWcQEmfqSVJt2ihZsFI8HbpwyyocaXbCAWf0g1ukIA==", + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, + "license": "MIT", "dependencies": { - "agentkeepalive": "^4.2.1", - "cacache": "^17.0.0", - "http-cache-semantics": "^4.1.1", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^4.0.0", - "minipass-fetch": "^3.0.0", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", - "ssri": "^10.0.0" + "mime-db": "1.52.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">= 0.6" } }, - "node_modules/sigstore/node_modules/minipass-fetch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.1.tgz", - "integrity": "sha512-t9/wowtf7DYkwz8cfMSt0rMwiyNIBXf5CKZ3S5ZMqRqMYT0oLTp0x1WorMI9WTwvaPg21r1JbFxJMum8JrLGfw==", + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true, - "dependencies": { - "minipass": "^4.0.0", - "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" - }, + "license": "MIT", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - }, - "optionalDependencies": { - "encoding": "^0.1.13" + "node": ">=6" } }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" + "node": "*" } }, - "node_modules/smob": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/smob/-/smob-0.0.6.tgz", - "integrity": "sha512-V21+XeNni+tTyiST1MHsa84AQhT1aFZipzPpOFAVB8DkHzwJyjjAmt9bgwnuZiZWnIbMo2duE29wybxv/7HWUw==", - "dev": true + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/socks": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", - "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, - "dependencies": { - "ip": "^2.0.0", - "smart-buffer": "^4.2.0" - }, + "license": "ISC", "engines": { - "node": ">= 10.13.0", - "npm": ">= 3.0.0" + "node": ">=16 || 14 >=14.17" } }, - "node_modules/socks-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", - "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", + "node_modules/minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", "dev": true, + "license": "ISC", "dependencies": { - "agent-base": "^6.0.2", - "debug": "^4.3.3", - "socks": "^2.6.2" + "minipass": "^3.0.0" }, "engines": { - "node": ">= 10" + "node": ">= 8" } }, - "node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "node_modules/minipass-collect/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, + "license": "ISC", "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, + "yallist": "^4.0.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/spawn-command": { - "version": "0.0.2-1", - "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz", - "integrity": "sha512-n98l9E2RMSJ9ON1AKisHzz7V42VDiBQGY6PB1BwRglz99wpVsSuGzQ+jOi6lFXBGVTCrRpltvjm+/XA+tpeJrg==", - "dev": true - }, - "node_modules/spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "node_modules/minipass-fetch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", + "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", "dev": true, + "license": "MIT", "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" + "minipass": "^3.1.6", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" } }, - "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "node_modules/minipass-fetch/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, + "license": "ISC", "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/spdx-license-ids": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz", - "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==", - "dev": true - }, - "node_modules/ssri": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.1.tgz", - "integrity": "sha512-WVy6di9DlPOeBWEjMScpNipeSX2jIZBGEn5Uuo8Q7aIuFEuDX0pw8RxcOjlD1TWP4obi24ki7m/13+nFpcbXrw==", + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", "dev": true, + "license": "ISC", "dependencies": { - "minipass": "^4.0.0" + "minipass": "^3.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">= 8" } }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, "engines": { - "node": ">= 0.8" + "node": ">=8" } }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "node_modules/minipass-json-stream": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.2.tgz", + "integrity": "sha512-myxeeTm57lYs8pH2nxPzmEEg8DGIgW+9mv6D4JZD2pa81I/OBjeU7PtICXV6c9eRGTA5JMDsuIPUZRCyBMYNhg==", "dev": true, + "license": "MIT", "dependencies": { - "safe-buffer": "~5.1.0" + "jsonparse": "^1.3.1", + "minipass": "^3.0.0" } }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/minipass-json-stream/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, + "license": "ISC", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "yallist": "^4.0.0" }, "engines": { "node": ">=8" } }, - "node_modules/string.prototype.trimend": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", - "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", "dev": true, + "license": "ISC", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "minipass": "^3.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=8" } }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", - "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, + "license": "ISC", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "yallist": "^4.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=8" } }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", "dev": true, + "license": "ISC", "dependencies": { - "ansi-regex": "^5.0.1" + "minipass": "^3.0.0" }, "engines": { "node": ">=8" } }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strtok3": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", - "integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==", + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", "dev": true, + "license": "MIT", "dependencies": { - "@tokenizer/token": "^0.3.0", - "peek-readable": "^4.1.0" + "minipass": "^3.0.0", + "yallist": "^4.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" + "node": ">= 8" } }, - "node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, + "license": "ISC", "dependencies": { - "has-flag": "^4.0.0" + "yallist": "^4.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "node": ">=8" } }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "license": "MIT" }, - "node_modules/tar": { - "version": "6.1.13", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.13.tgz", - "integrity": "sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==", + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true, - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^4.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" }, "engines": { "node": ">=10" } }, - "node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } + "license": "MIT" }, - "node_modules/tar-fs/node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" }, - "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", "dev": true, - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, + "license": "MIT", "engines": { - "node": ">=6" + "node": ">= 0.6" } }, - "node_modules/tar/node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", "dev": true, - "dependencies": { - "minipass": "^3.0.0" + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "dev": true, + "license": "(BSD-3-Clause OR GPL-2.0)", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-gyp": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.4.1.tgz", + "integrity": "sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^10.0.3", + "nopt": "^6.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" }, "engines": { - "node": ">= 8" + "node": "^12.13 || ^14.13 || >=16" } }, - "node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "node_modules/node-gyp/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, + "license": "ISC", "dependencies": { - "yallist": "^4.0.0" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { - "node": ">=8" + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/terser": { - "version": "5.16.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.1.tgz", - "integrity": "sha512-xvQfyfA1ayT0qdK47zskQgRZeWLoOQ8JQ6mIgRGVNwZKdQMU+5FkCBjmv4QjcrTzyZquRw2FVtlJSRUmMKQslw==", + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-watch": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/node-watch/-/node-watch-0.7.3.tgz", + "integrity": "sha512-3l4E8uMPY1HdMMryPRUAl+oIHtXtyiTlIiESNSVSNxcPfzAFzeTbXFQkZfAwBbo0B1qMSG8nUABx+Gd+YrbKrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/nopt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", + "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", "dev": true, + "license": "ISC", "dependencies": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" + "abbrev": "^1.0.0" }, "bin": { - "terser": "bin/terser" + "nopt": "bin/nopt.js" }, "engines": { - "node": ">=10" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true - }, - "node_modules/timm": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/timm/-/timm-1.7.1.tgz", - "integrity": "sha512-IjZc9KIotudix8bMaBW6QvMuq64BrJWFs1+4V0lXwWGQZwH+LnX87doAYhem4caOEusRP9/g6jVDQmZ8XOk1nw==", - "dev": true + "node_modules/normalize-package-data": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-5.0.0.tgz", + "integrity": "sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^6.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } }, - "node_modules/tiny-glob": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz", - "integrity": "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==", + "node_modules/npm-bundled": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.1.tgz", + "integrity": "sha512-+AvaheE/ww1JEwRHOrn4WHNzOxGtVp+adrg2AeZS/7KuxGUYFuBta98wYpfHBbJp6Tg6j1NKSEVHNcfZzJHQwQ==", "dev": true, + "license": "ISC", "dependencies": { - "globalyzer": "0.1.0", - "globrex": "^0.1.2" + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/tinycolor2": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", - "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", - "dev": true + "node_modules/npm-install-checks": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", + "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "node_modules/npm-normalize-package-bin": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", "dev": true, + "license": "ISC", "engines": { - "node": ">=0.6" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/token-types": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", - "integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==", + "node_modules/npm-package-arg": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-10.1.0.tgz", + "integrity": "sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA==", "dev": true, + "license": "ISC", "dependencies": { - "@tokenizer/token": "^0.3.0", - "ieee754": "^1.2.1" + "hosted-git-info": "^6.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" }, "engines": { - "node": ">=10" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-packlist": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-7.0.4.tgz", + "integrity": "sha512-d6RGEuRrNS5/N84iglPivjaJPxhDbZmlbTwTDX2IbcRHG5bZCdtysYMhwiPvcF4GisXHGn7xsxv+GQ7T/02M5Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "ignore-walk": "^6.0.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Borewit" + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true + "node_modules/npm-pick-manifest": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-8.0.2.tgz", + "integrity": "sha512-1dKY+86/AIiq1tkKVD3l0WI+Gd3vkknVGAggsFeBkTvbhMQ1OND/LKkYv4JtXPKUJ8bOTCyLiqEg2P6QNdK+Gg==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-install-checks": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "npm-package-arg": "^10.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } }, - "node_modules/tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "node_modules/npm-registry-fetch": { + "version": "14.0.5", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-14.0.5.tgz", + "integrity": "sha512-kIDMIo4aBm6xg7jOttupWZamsZRkAqMqwqqbVXnUqstY5+tapvv6bkH/qMR76jdgV+YljEUCyWx3hRYMrJiAgA==", "dev": true, - "bin": { - "tree-kill": "cli.js" + "license": "ISC", + "dependencies": { + "make-fetch-happen": "^11.0.0", + "minipass": "^5.0.0", + "minipass-fetch": "^3.0.0", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.1.2", + "npm-package-arg": "^10.0.0", + "proc-log": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/tsconfig-paths": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", - "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "node_modules/npm-registry-fetch/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "dev": true, + "license": "MIT", "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.1", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" } }, - "node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "dev": true + "node_modules/npm-registry-fetch/node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } }, - "node_modules/tuf-js": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-1.1.1.tgz", - "integrity": "sha512-WTp382/PR96k0dI4GD5RdiRhgOU0rAC7+lnoih/5pZg3cyb3aNMqDozleEEWwyfT3+FOg7Qz9JU3n6A44tLSHw==", + "node_modules/npm-registry-fetch/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "dev": true, + "license": "MIT", "dependencies": { - "@tufjs/models": "1.0.0", - "make-fetch-happen": "^11.0.1" + "agent-base": "6", + "debug": "4" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">= 6" } }, - "node_modules/tuf-js/node_modules/lru-cache": { + "node_modules/npm-registry-fetch/node_modules/lru-cache": { "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", "dev": true, + "license": "ISC", "engines": { "node": ">=12" } }, - "node_modules/tuf-js/node_modules/make-fetch-happen": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.0.3.tgz", - "integrity": "sha512-oPLh5m10lRNNZDjJ2kP8UpboUx2uFXVaVweVe/lWut4iHWcQEmfqSVJt2ihZsFI8HbpwyyocaXbCAWf0g1ukIA==", + "node_modules/npm-registry-fetch/node_modules/make-fetch-happen": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz", + "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==", "dev": true, + "license": "ISC", "dependencies": { "agentkeepalive": "^4.2.1", "cacache": "^17.0.0", @@ -6688,7 +7249,7 @@ "https-proxy-agent": "^5.0.0", "is-lambda": "^1.0.1", "lru-cache": "^7.7.1", - "minipass": "^4.0.0", + "minipass": "^5.0.0", "minipass-fetch": "^3.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", @@ -6701,13 +7262,24 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/tuf-js/node_modules/minipass-fetch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.1.tgz", - "integrity": "sha512-t9/wowtf7DYkwz8cfMSt0rMwiyNIBXf5CKZ3S5ZMqRqMYT0oLTp0x1WorMI9WTwvaPg21r1JbFxJMum8JrLGfw==", + "node_modules/npm-registry-fetch/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm-registry-fetch/node_modules/minipass-fetch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz", + "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==", "dev": true, + "license": "MIT", "dependencies": { - "minipass": "^4.0.0", + "minipass": "^7.0.3", "minipass-sized": "^1.0.3", "minizlib": "^2.1.2" }, @@ -6718,5848 +7290,3555 @@ "encoding": "^0.1.13" } }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "node_modules/npm-registry-fetch/node_modules/minipass-fetch/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, + "license": "ISC", "engines": { - "node": ">= 0.8.0" + "node": ">=16 || 14 >=14.17" } }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "node_modules/npm-registry-fetch/node_modules/socks-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", + "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", "dev": true, - "engines": { - "node": ">=10" + "license": "MIT", + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">= 10" } }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "node_modules/npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "deprecated": "This package is no longer supported.", "dev": true, + "license": "ISC", "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" }, "engines": { - "node": ">= 0.6" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/typed-array-length": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", - "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "is-typed-array": "^1.1.9" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "license": "MIT", + "engines": { + "node": ">=0.10.0" } }, - "node_modules/unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" + "license": "MIT", + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/unbzip2-stream": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", - "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true, - "dependencies": { - "buffer": "^5.2.1", - "through": "^2.3.8" + "license": "MIT", + "engines": { + "node": ">= 0.4" } }, - "node_modules/unique-filename": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", - "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", "dev": true, + "license": "MIT", "dependencies": { - "unique-slug": "^4.0.0" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/unique-slug": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", - "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", "dev": true, + "license": "MIT", "dependencies": { - "imurmurhash": "^0.1.4" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, "engines": { - "node": ">= 0.8" + "node": ">= 0.4" } }, - "node_modules/update-browserslist-db": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", - "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - } - ], + "license": "MIT", "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" }, - "bin": { - "browserslist-lint": "cli.js" + "engines": { + "node": ">= 0.4" }, - "peerDependencies": { - "browserslist": ">= 4.21.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "node_modules/omggif": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz", + "integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==", "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } + "license": "MIT" }, - "node_modules/utif2": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/utif2/-/utif2-4.1.0.tgz", - "integrity": "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w==", + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dev": true, + "license": "MIT", "dependencies": { - "pako": "^1.0.11" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "dev": true, + "ee-first": "1.1.1" + }, "engines": { - "node": ">= 0.4.0" + "node": ">= 0.8" } }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, + "license": "ISC", "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" + "wrappy": "1" } }, - "node_modules/validate-npm-package-name": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.0.tgz", - "integrity": "sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ==", + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, + "license": "MIT", "dependencies": { - "builtins": "^5.0.0" + "mimic-fn": "^2.1.0" }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "dev": true, - "engines": { - "node": ">= 0.8" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true - }, - "node_modules/whatwg-fetch": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", - "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==", - "dev": true - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", "dev": true, + "license": "MIT", "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, + "license": "MIT", "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" }, "engines": { - "node": ">= 8" + "node": ">= 0.8.0" } }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", "dev": true, + "license": "MIT", "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/which-typed-array": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", - "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "node_modules/ora/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "license": "MIT", "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" + "color-convert": "^2.0.1" }, "engines": { - "node": ">= 0.4" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/wide-align": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "node_modules/ora/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/widest-line": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", - "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "node_modules/ora/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "license": "MIT", "dependencies": { - "string-width": "^4.0.0" + "has-flag": "^4.0.0" }, "engines": { "node": ">=8" } }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "yocto-queue": "^0.1.0" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "node_modules/ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, - "engines": { - "node": ">=10.0.0" + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" + "engines": { + "node": ">=10" }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/xhr": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", - "integrity": "sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==", + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", "dev": true, + "license": "MIT", "dependencies": { - "global": "~4.4.0", - "is-function": "^1.0.1", - "parse-headers": "^2.0.0", - "xtend": "^4.0.0" + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/xml-parse-from-string": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz", - "integrity": "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==", - "dev": true - }, - "node_modules/xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "node_modules/pac-proxy-agent": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", + "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", "dev": true, + "license": "MIT", "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" }, "engines": { - "node": ">=4.0.0" + "node": ">= 14" } }, - "node_modules/xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", "dev": true, + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, "engines": { - "node": ">=4.0" + "node": ">= 14" } }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/pacote": { + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-15.2.0.tgz", + "integrity": "sha512-rJVZeIwHTUta23sIZgEIM62WYwbmGbThdbnkt81ravBplQv+HjyroqnLRNH2+sLJHcGZmLRmhPwACqhfTcOmnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^4.0.0", + "@npmcli/installed-package-contents": "^2.0.1", + "@npmcli/promise-spawn": "^6.0.1", + "@npmcli/run-script": "^6.0.0", + "cacache": "^17.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^5.0.0", + "npm-package-arg": "^10.0.0", + "npm-packlist": "^7.0.0", + "npm-pick-manifest": "^8.0.0", + "npm-registry-fetch": "^14.0.0", + "proc-log": "^3.0.0", + "promise-retry": "^2.0.1", + "read-package-json": "^6.0.0", + "read-package-json-fast": "^3.0.0", + "sigstore": "^1.3.0", + "ssri": "^10.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "lib/bin.js" + }, "engines": { - "node": ">=0.4" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "node_modules/pacote/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", "dev": true, + "license": "ISC", "engines": { - "node": ">=10" + "node": ">=8" } }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true, + "license": "(MIT AND Zlib)" }, - "node_modules/yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, + "license": "MIT", "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "callsites": "^3.0.0" }, "engines": { - "node": ">=12" + "node": ">=6" } }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "node_modules/parse-bmfont-ascii": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz", + "integrity": "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==", "dev": true, - "engines": { - "node": ">=12" - } + "license": "MIT" }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "node_modules/parse-bmfont-binary": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz", + "integrity": "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse-bmfont-xml": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.6.tgz", + "integrity": "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA==", "dev": true, + "license": "MIT", "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" + "xml-parse-from-string": "^1.0.0", + "xml2js": "^0.5.0" } }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, "engines": { - "node": ">=10" + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } - } - }, - "dependencies": { - "@babel/runtime": { - "version": "7.20.13", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz", - "integrity": "sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==", + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "dev": true, - "requires": { - "regenerator-runtime": "^0.13.11" + "license": "MIT", + "engines": { + "node": ">= 0.8" } }, - "@eslint-community/eslint-utils": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.2.0.tgz", - "integrity": "sha512-gB8T4H4DEfX2IV9zGDJPOBgP1e/DbfCPDTtEqUMckpvzS1OYtva8JdFYBqMwYk7xAQ429WGF/UPqn8uQ//h2vQ==", + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, - "requires": { - "eslint-visitor-keys": "^3.3.0" + "license": "MIT", + "engines": { + "node": ">=8" } }, - "@eslint-community/regexpp": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.4.0.tgz", - "integrity": "sha512-A9983Q0LnDGdLPjxyXQ00sbV+K+O+ko2Dr+CZigbHWtX9pNfxlaBkMR8X1CztI73zuEyEBXTVjx7CE+/VSwDiQ==", - "dev": true + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } }, - "@eslint/eslintrc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz", - "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==", + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.5.2", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" + "license": "MIT", + "engines": { + "node": ">=8" } }, - "@eslint/js": { - "version": "8.40.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.40.0.tgz", - "integrity": "sha512-ElyB54bJIhXQYVKjDSvCkPO1iU1tSAeVQJbllWJq1XQSmmA4dgFk8CbiBGpiOPxleE48vDogxCtmMYku4HSVLA==", - "dev": true - }, - "@gar/promisify": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", - "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", - "dev": true - }, - "@humanwhocodes/config-array": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", - "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" - } - }, - "@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true - }, - "@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true + "license": "MIT" }, - "@jimp/bmp": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.22.8.tgz", - "integrity": "sha512-JEMKgM1AEvvWfn9ZCHn62nK+QCE3Pb/ZhPdL3NF0ZgKNww6pqOmo6KqXzqY18JLB7c0epuTp4GPDPDhOh/ou1g==", + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, - "requires": { - "@jimp/utils": "^0.22.8", - "bmp-js": "^0.1.0" + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "@jimp/core": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.22.8.tgz", - "integrity": "sha512-vkN28aFikzQieA6bGxN+qe20pseCAemCyUI0YmRkJIArlb6OujtAwWAKyokv2lylV56bq8EQGIz+Y30OXUnRqg==", + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "dev": true, - "requires": { - "@jimp/utils": "^0.22.8", - "any-base": "^1.1.0", - "buffer": "^5.2.0", - "exif-parser": "^0.1.12", - "file-type": "^16.5.4", - "isomorphic-fetch": "^3.0.0", - "mkdirp": "^2.1.3", - "pixelmatch": "^4.0.2", - "tinycolor2": "^1.6.0" - }, - "dependencies": { - "mkdirp": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.6.tgz", - "integrity": "sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==", - "dev": true - }, - "pixelmatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-4.0.2.tgz", - "integrity": "sha512-J8B6xqiO37sU/gkcMglv6h5Jbd9xNER7aHzpfRdNmV4IbQBzBpe4l9XmbG+xPF/znacgu2jfEw+wHffaq/YkXA==", - "dev": true, - "requires": { - "pngjs": "^3.0.0" - } - }, - "pngjs": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", - "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==", - "dev": true - } - } + "license": "MIT" }, - "@jimp/custom": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.22.8.tgz", - "integrity": "sha512-u6lP9x/HNeGHB0Oojv4c2mhuDvn7G0ikzYbK4IKLsH4HzHxt62faMjBzQMcFhKJhR6UiiKE/jiHrhGvBT/fMkw==", + "node_modules/peek-readable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", + "integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==", "dev": true, - "requires": { - "@jimp/core": "^0.22.8" + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" } }, - "@jimp/gif": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/gif/-/gif-0.22.8.tgz", - "integrity": "sha512-I0l6koS67IPU40RPxCJTD1NvePEd8vUIHTejx1ly0jrjGnumbqdarAlBUkDrKfPPc+Fnqp84hBbSN1w5hNPT6w==", + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", "dev": true, - "requires": { - "@jimp/utils": "^0.22.8", - "gifwrap": "^0.9.2", - "omggif": "^1.0.9" - } + "license": "MIT" }, - "@jimp/jpeg": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/jpeg/-/jpeg-0.22.8.tgz", - "integrity": "sha512-hLXrQ7/0QiUhAVAF10dfGCSq3hvyqjKltlpu/87b3wqMDKe9KdvhX1AJHiUUrAbJv1fAcnOmQGTyXGuySa1D6A==", + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, - "requires": { - "@jimp/utils": "^0.22.8", - "jpeg-js": "^0.4.4" - } + "license": "ISC" }, - "@jimp/plugin-blit": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.22.8.tgz", - "integrity": "sha512-rQ19txVCKIwo74HtgFodFt4//0ATPCJK+f24riqzb+nx+1JaOo1xRvpJqg4moirHwKR2fhwdDxmY7KX20kCeYA==", + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "dev": true, - "requires": { - "@jimp/utils": "^0.22.8" + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "@jimp/plugin-blur": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-0.22.8.tgz", - "integrity": "sha512-GWbNK3YW6k2EKiGJdpAFEr0jezPBtiVxj2wG/lCPuWJz7KmzSSN99hQjIy73xQxoBCRdALfJlkhe3leFNRueSQ==", + "node_modules/pixelmatch": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-7.1.0.tgz", + "integrity": "sha512-1wrVzJ2STrpmONHKBy228LM1b84msXDUoAzVEl0R8Mz4Ce6EPr+IVtxm8+yvrqLYMHswREkjYFaMxnyGnaY3Ng==", "dev": true, - "requires": { - "@jimp/utils": "^0.22.8" + "license": "ISC", + "dependencies": { + "pngjs": "^7.0.0" + }, + "bin": { + "pixelmatch": "bin/pixelmatch" } }, - "@jimp/plugin-circle": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-0.22.8.tgz", - "integrity": "sha512-qPCw8XFW8opT89ciFDuvs+eB3EB1mZIJWVajD2qAlprHiE7YGr34TkM7N5MNr3qZ1pJgkYdW6+HbBrJwBaonqw==", + "node_modules/pngjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", "dev": true, - "requires": { - "@jimp/utils": "^0.22.8" + "license": "MIT", + "engines": { + "node": ">=14.19.0" } }, - "@jimp/plugin-color": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.22.8.tgz", - "integrity": "sha512-ogkbg6rpDVH/mMLgAQKg17z3oZE0VN7ZWxNoH12fUHchqKz1I57zpa65fxZe2I8T5Xz97HR3x+7V7oI8qQGdSA==", + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", "dev": true, - "requires": { - "@jimp/utils": "^0.22.8", - "tinycolor2": "^1.6.0" + "license": "MIT", + "engines": { + "node": ">= 0.4" } }, - "@jimp/plugin-contain": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-0.22.8.tgz", - "integrity": "sha512-oiaPLdJt9Dk+XEEhM/OU3lFemM51mA9NgMCAdburSCjDzKacJYBGFSHjTOhXzcxOie/ZDpOYN/UzFGKy8Dgl9A==", + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, - "requires": { - "@jimp/utils": "^0.22.8" + "license": "MIT", + "engines": { + "node": ">= 0.8.0" } }, - "@jimp/plugin-cover": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-0.22.8.tgz", - "integrity": "sha512-mO68w1m/LhfuHU8LKHY05a4/hhWnY4t+T+8JCw9t+5yfzA4+LofBZZKtFtWgwf/QGe1y3X2rtUU/avAzDUKyyA==", + "node_modules/proc-log": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", + "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==", "dev": true, - "requires": { - "@jimp/utils": "^0.22.8" + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "@jimp/plugin-crop": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.22.8.tgz", - "integrity": "sha512-ns4oH0h0gezYsbuH8RThcMLY5uTLk/vnqOVjWCehMHEzxi0DHMWCmpcb6bC//vJ+XFNhtVGn1ALN7+ROmPrj+A==", + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", "dev": true, - "requires": { - "@jimp/utils": "^0.22.8" + "license": "MIT", + "engines": { + "node": ">= 0.6.0" } }, - "@jimp/plugin-displace": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-0.22.8.tgz", - "integrity": "sha512-Cj8nHYgsdFynOIx3dbbiVwRuZn3xO+RVfwkTRy0JBye+K2AU8SQJS+hSFNMQFTZt5djivh6kh0TzvR/6LkOd1w==", + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true, - "requires": { - "@jimp/utils": "^0.22.8" + "license": "MIT", + "engines": { + "node": ">=0.4.0" } }, - "@jimp/plugin-dither": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-0.22.8.tgz", - "integrity": "sha512-oE0Us/6bEgrgEg56plU3jSBzvB9iGhweKUHmxYMWnQbFCHP4mNCtPAs8+Fmq6c+m98ZgBgRcrJTnC7lphHkGyw==", + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", "dev": true, - "requires": { - "@jimp/utils": "^0.22.8" - } + "license": "ISC" }, - "@jimp/plugin-fisheye": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-0.22.8.tgz", - "integrity": "sha512-bWvYY/nfMcKclWEaRyAir+YsT6C5St823HUQAsewZowTrJmme+w4U2a6InsryTHUL01BBcV5BLH0aDHuV3StvA==", + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", "dev": true, - "requires": { - "@jimp/utils": "^0.22.8" + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" } }, - "@jimp/plugin-flip": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-0.22.8.tgz", - "integrity": "sha512-0NFTNzjsdmOQkaIkNjZqO3/yU4SQb9nnWQXsLS1fFo+9QrIL5v8vVkXpk/rhiND6PyTj2mMTNjOa76GuZcC+iQ==", + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "dev": true, - "requires": { - "@jimp/utils": "^0.22.8" + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" } }, - "@jimp/plugin-gaussian": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugin-gaussian/-/plugin-gaussian-0.22.8.tgz", - "integrity": "sha512-E/f14aLzCS50QAM7K+InI9V61KVy/Zx52vy7Jjfo1h7qKhQHss3PYaydaH0N6qlXRNeXgh+4/32P9JfieLMcdw==", + "node_modules/proxy-agent": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", "dev": true, - "requires": { - "@jimp/utils": "^0.22.8" + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" } }, - "@jimp/plugin-invert": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugin-invert/-/plugin-invert-0.22.8.tgz", - "integrity": "sha512-UauP39FF2cwbA5VU+Tz9VlNa9rtULPSHZb0Huwcjqjm9/G/xVN69VJ8+RKiFC4zM1/kYAUp/6IRwPa6qdKJpSw==", + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", "dev": true, - "requires": { - "@jimp/utils": "^0.22.8" + "license": "ISC", + "engines": { + "node": ">=12" } }, - "@jimp/plugin-mask": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-0.22.8.tgz", - "integrity": "sha512-bhg5+3i8x1CmYj6cjvPBQZLwZEI3iK3gJWF25ZHF+12d3cqDuJngtr8oRQOQLlAgvKmrj9FXIiEPDczUI9cnWQ==", + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "dev": true, - "requires": { - "@jimp/utils": "^0.22.8" - } + "license": "MIT" }, - "@jimp/plugin-normalize": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugin-normalize/-/plugin-normalize-0.22.8.tgz", - "integrity": "sha512-Yg5nreAR1JYuSObu3ExlgaLxVeW6VvjVL5qFwiPFxSNlG8JIwL1Ir3K3ChSnnvymyZvJMHb6YKTYNfXKw5Da6g==", + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", "dev": true, - "requires": { - "@jimp/utils": "^0.22.8" + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" } }, - "@jimp/plugin-print": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-0.22.8.tgz", - "integrity": "sha512-86O5ejCDi543IYl0TykSmNWErzAjEYhiAxNQb2F7rFRT38WJYNVsvJ6QhxhDQHKxSmF5iwmqbk0jYk5Wp2Z1kw==", + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, - "requires": { - "@jimp/utils": "^0.22.8", - "load-bmfont": "^1.4.1" + "license": "MIT", + "engines": { + "node": ">=6" } }, - "@jimp/plugin-resize": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.22.8.tgz", - "integrity": "sha512-kg8ArQRPqv/iU3DWNXCa8kcVIhoq64Ze0aGCAeFLKlAq/59f5pzAci6m6vV4L/uOVdYmUa9/kYwIFY6RWKpfzQ==", + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", "dev": true, - "requires": { - "@jimp/utils": "^0.22.8" + "license": "MIT", + "engines": { + "node": ">=6" } }, - "@jimp/plugin-rotate": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.22.8.tgz", - "integrity": "sha512-9a+VPZWMN/Cks76wf8LjM5RVA3ntP9+NAdsS1SZhhXel7U3Re/dWMouIEbo3QTt6K+igRo4txUCdZiw4ZucvkQ==", + "node_modules/puppeteer": { + "version": "22.15.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-22.15.0.tgz", + "integrity": "sha512-XjCY1SiSEi1T7iSYuxS82ft85kwDJUS7wj1Z0eGVXKdtr5g4xnVcbjwxhq5xBnpK/E7x1VZZoJDxpjAOasHT4Q==", "dev": true, - "requires": { - "@jimp/utils": "^0.22.8" + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.3.0", + "cosmiconfig": "^9.0.0", + "devtools-protocol": "0.0.1312386", + "puppeteer-core": "22.15.0" + }, + "bin": { + "puppeteer": "lib/esm/puppeteer/node/cli.js" + }, + "engines": { + "node": ">=18" } }, - "@jimp/plugin-scale": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.22.8.tgz", - "integrity": "sha512-dQS4pG6DX6endu8zUpvBBOEtGC+ljDDDNw0scSXY71TxyQdNo5Ro0apfsppjmuAr8rNotRkfyxbITKkXQDRUDQ==", + "node_modules/puppeteer-core": { + "version": "22.15.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.15.0.tgz", + "integrity": "sha512-cHArnywCiAAVXa3t4GGL2vttNxh7GqXtIYGym99egkNJ3oG//wL9LkvO4WE8W1TJe95t1F1ocu9X4xWaGsOKOA==", "dev": true, - "requires": { - "@jimp/utils": "^0.22.8" + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.3.0", + "chromium-bidi": "0.6.3", + "debug": "^4.3.6", + "devtools-protocol": "0.0.1312386", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=18" } }, - "@jimp/plugin-shadow": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugin-shadow/-/plugin-shadow-0.22.8.tgz", - "integrity": "sha512-HyAhr7OblTQh+BoKHQg4qbS9MweNlH77yfpBqUEyDtfyjI5r06+5chf1ZdLRIPEWv/BdCfdI/g81Wv69muCMwA==", + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dev": true, - "requires": { - "@jimp/utils": "^0.22.8" + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "@jimp/plugin-threshold": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-0.22.8.tgz", - "integrity": "sha512-ZmkfH0PtjvF1UcKsjw0H7V6r+LC0yKzEfg76Jhs2nIqIgsxsSOVfHwS7z0/1IWnyXxSw36m+NjCAotNHRILGmA==", + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true, - "requires": { - "@jimp/utils": "^0.22.8" - } + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" }, - "@jimp/plugins": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/plugins/-/plugins-0.22.8.tgz", - "integrity": "sha512-ieI2+kCpmIfjwVlT7B67ULCzxMizfj7LspJh9HnIZCDXQB9GBOZ9KImLYc75Krae0dP/3FR7FglLiSI7fkOHbw==", + "node_modules/qunit": { + "version": "2.24.1", + "resolved": "https://registry.npmjs.org/qunit/-/qunit-2.24.1.tgz", + "integrity": "sha512-Eu0k/5JDjx0QnqxsE1WavnDNDgL1zgMZKsMw/AoAxnsl9p4RgyLODyo2N7abZY7CEAnvl5YUqFZdkImzbgXzSg==", "dev": true, - "requires": { - "@jimp/plugin-blit": "^0.22.8", - "@jimp/plugin-blur": "^0.22.8", - "@jimp/plugin-circle": "^0.22.8", - "@jimp/plugin-color": "^0.22.8", - "@jimp/plugin-contain": "^0.22.8", - "@jimp/plugin-cover": "^0.22.8", - "@jimp/plugin-crop": "^0.22.8", - "@jimp/plugin-displace": "^0.22.8", - "@jimp/plugin-dither": "^0.22.8", - "@jimp/plugin-fisheye": "^0.22.8", - "@jimp/plugin-flip": "^0.22.8", - "@jimp/plugin-gaussian": "^0.22.8", - "@jimp/plugin-invert": "^0.22.8", - "@jimp/plugin-mask": "^0.22.8", - "@jimp/plugin-normalize": "^0.22.8", - "@jimp/plugin-print": "^0.22.8", - "@jimp/plugin-resize": "^0.22.8", - "@jimp/plugin-rotate": "^0.22.8", - "@jimp/plugin-scale": "^0.22.8", - "@jimp/plugin-shadow": "^0.22.8", - "@jimp/plugin-threshold": "^0.22.8", - "timm": "^1.6.1" + "license": "MIT", + "dependencies": { + "commander": "7.2.0", + "node-watch": "0.7.3", + "tiny-glob": "0.2.9" + }, + "bin": { + "qunit": "bin/qunit.js" + }, + "engines": { + "node": ">=10" } }, - "@jimp/png": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/png/-/png-0.22.8.tgz", - "integrity": "sha512-XOj11kcCr8zKg24QSwlRfH9k4hbV6rkMGUVxMS3puRzzB0FBSQy42NBYEfYf2XlY2QJSAByPl4AYerOtKb805w==", + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, - "requires": { - "@jimp/utils": "^0.22.8", - "pngjs": "^6.0.0" + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" } }, - "@jimp/tiff": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/tiff/-/tiff-0.22.8.tgz", - "integrity": "sha512-K0hYUVW5MLgwq3jiHVHa6LvP05J1rXOlRCC+5dMTUnAXVwi45+MKsqA/8lzzwhHYJ65CNhZwy6D3+ZNzM9SIBQ==", + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", "dev": true, - "requires": { - "utif2": "^4.0.1" + "license": "MIT", + "engines": { + "node": ">= 0.6" } }, - "@jimp/types": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/types/-/types-0.22.8.tgz", - "integrity": "sha512-9+xc+mzuYwu0i+6dsnhXiUgfcS+Ktqn5q2jczoKyyBT0cOKgsk+57EIeFLgpTfVGRKRR0y/UIdHByeCzGguF3A==", + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dev": true, - "requires": { - "@jimp/bmp": "^0.22.8", - "@jimp/gif": "^0.22.8", - "@jimp/jpeg": "^0.22.8", - "@jimp/png": "^0.22.8", - "@jimp/tiff": "^0.22.8", - "timm": "^1.6.1" + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" } }, - "@jimp/utils": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.8.tgz", - "integrity": "sha512-AaqjfqDeLzSFzrbGRKHMXg/ntiWKvoG9tpVgWzgOx5/gPWj/IyGfztojLTTvY8HqZCr25z8z91u2lAQD2v46Jw==", + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, - "requires": { - "regenerator-runtime": "^0.13.3" + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" } }, - "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "node_modules/read-package-json": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-6.0.4.tgz", + "integrity": "sha512-AEtWXYfopBj2z5N5PbkAOeNHRPUg5q+Nen7QLxV8M2zJq1ym6/lCz3fYNTCXe19puu2d06jfHhrP7v/S2PtMMw==", + "deprecated": "This package is no longer supported. Please use @npmcli/package-json instead.", "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "license": "ISC", + "dependencies": { + "glob": "^10.2.2", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^5.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true - }, - "@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true - }, - "@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "node_modules/read-package-json-fast": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz", + "integrity": "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==", "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true - }, - "@jridgewell/trace-mapping": { - "version": "0.3.17", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", - "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "node_modules/read-package-json-fast/node_modules/json-parse-even-better-errors": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", + "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", "dev": true, - "requires": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "license": "MIT", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "@mdn/browser-compat-data": { - "version": "5.2.49", - "resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-5.2.49.tgz", - "integrity": "sha512-tXJUP9EFcfeTcn3hpn616qtcbaLMrhqfgsljRnIv/qYckL8ywLodk7Cj3oJlZed3zWLZLnE9LHHsfpO8w4yJuw==", - "dev": true - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "node_modules/read-package-json/node_modules/json-parse-even-better-errors": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", + "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "license": "MIT", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" } }, - "@npmcli/fs": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.0.tgz", - "integrity": "sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w==", + "node_modules/readable-web-to-node-stream": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.4.tgz", + "integrity": "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==", "dev": true, - "requires": { - "semver": "^7.3.5" + "license": "MIT", + "dependencies": { + "readable-stream": "^4.7.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" } }, - "@npmcli/git": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-4.0.3.tgz", - "integrity": "sha512-8cXNkDIbnXPVbhXMmQ7/bklCAjtmPaXfI9aEM4iH+xSuEHINLMHhlfESvVwdqmHJRJkR48vNJTSUvoF6GRPSFA==", + "node_modules/readable-web-to-node-stream/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "dev": true, - "requires": { - "@npmcli/promise-spawn": "^6.0.0", - "lru-cache": "^7.4.4", - "mkdirp": "^1.0.4", - "npm-pick-manifest": "^8.0.0", - "proc-log": "^3.0.0", - "promise-inflight": "^1.0.1", - "promise-retry": "^2.0.1", - "semver": "^7.3.5", - "which": "^3.0.0" - }, - "dependencies": { - "lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" }, - "which": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-3.0.0.tgz", - "integrity": "sha512-nla//68K9NU6yRiwDY/Q8aU6siKlSs64aEC7+IV56QoAuyQT2ovsJcgGYGyqMOmI/CGN1BOR6mM5EN0FBO+zyQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } + { + "type": "consulting", + "url": "https://feross.org/support" } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" } }, - "@npmcli/installed-package-contents": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.0.2.tgz", - "integrity": "sha512-xACzLPhnfD51GKvTOOuNX2/V4G4mz9/1I2MfDoye9kBM3RYe5g2YbscsaGoTlaWqkxeiapBWyseULVKpSVHtKQ==", + "node_modules/readable-web-to-node-stream/node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", "dev": true, - "requires": { - "npm-bundled": "^3.0.0", - "npm-normalize-package-bin": "^3.0.0" + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "@npmcli/move-file": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", - "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", "dev": true, - "requires": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "@npmcli/node-gyp": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz", - "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==", - "dev": true - }, - "@npmcli/promise-spawn": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-6.0.2.tgz", - "integrity": "sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==", + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", "dev": true, - "requires": { - "which": "^3.0.0" - }, + "license": "MIT", "dependencies": { - "which": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-3.0.0.tgz", - "integrity": "sha512-nla//68K9NU6yRiwDY/Q8aU6siKlSs64aEC7+IV56QoAuyQT2ovsJcgGYGyqMOmI/CGN1BOR6mM5EN0FBO+zyQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "@npmcli/run-script": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-6.0.0.tgz", - "integrity": "sha512-ql+AbRur1TeOdl1FY+RAwGW9fcr4ZwiVKabdvm93mujGREVuVLbdkXRJDrkTXSdCjaxYydr1wlA2v67jxWG5BQ==", + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, - "requires": { - "@npmcli/node-gyp": "^3.0.0", - "@npmcli/promise-spawn": "^6.0.0", - "node-gyp": "^9.0.0", - "read-package-json-fast": "^3.0.0", - "which": "^3.0.0" - }, - "dependencies": { - "which": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-3.0.0.tgz", - "integrity": "sha512-nla//68K9NU6yRiwDY/Q8aU6siKlSs64aEC7+IV56QoAuyQT2ovsJcgGYGyqMOmI/CGN1BOR6mM5EN0FBO+zyQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } + "license": "MIT", + "engines": { + "node": ">=0.10.0" } }, - "@puppeteer/browsers": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-0.5.0.tgz", - "integrity": "sha512-Uw6oB7VvmPRLE4iKsjuOh8zgDabhNX67dzo8U/BB0f9527qx+4eeUs+korU98OhG5C4ubg7ufBgVi63XYwS6TQ==", - "dev": true, - "requires": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "https-proxy-agent": "5.0.1", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - } - }, - "@rollup/plugin-node-resolve": { - "version": "15.0.2", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.0.2.tgz", - "integrity": "sha512-Y35fRGUjC3FaurG722uhUuG8YHOJRJQbI6/CkbRkdPotSpDj9NtIN85z1zrcyDcCQIW4qp5mgG72U+gJ0TAFEg==", - "dev": true, - "requires": { - "@rollup/pluginutils": "^5.0.1", - "@types/resolve": "1.20.2", - "deepmerge": "^4.2.2", - "is-builtin-module": "^3.2.1", - "is-module": "^1.0.0", - "resolve": "^1.22.1" + "node_modules/requizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", + "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.21" } }, - "@rollup/plugin-terser": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.1.tgz", - "integrity": "sha512-aKS32sw5a7hy+fEXVy+5T95aDIwjpGHCTv833HXVtyKMDoVS7pBr5K3L9hEQoNqbJFjfANPrNpIXlTQ7is00eA==", + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", "dev": true, - "requires": { - "serialize-javascript": "^6.0.0", - "smob": "^0.0.6", - "terser": "^5.15.1" + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "@rollup/pluginutils": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.2.tgz", - "integrity": "sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==", + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, - "requires": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^2.3.1" + "license": "MIT", + "engines": { + "node": ">=4" } }, - "@tokenizer/token": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", - "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", - "dev": true - }, - "@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "dev": true - }, - "@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true - }, - "@tufjs/models": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-1.0.0.tgz", - "integrity": "sha512-RRMu4uMxWnZlxaIBxahSb2IssFZiu188sndesZflWOe1cA/qUqtemSIoBWbuVKPvvdktapImWNnKpBcc+VrCQw==", + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", "dev": true, - "requires": { - "minimatch": "^6.1.0" - }, + "license": "MIT", "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "minimatch": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-6.2.0.tgz", - "integrity": "sha512-sauLxniAmvnhhRjFwPNnJKaPFYyddAgbYdeUpHULtCT/GhzdCx/MDNy+Y40lBxTQUrMzDE8e0S43Z5uqfO0REg==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - } + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" } }, - "@types/estree": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", - "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==", - "dev": true - }, - "@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true - }, - "@types/node": { - "version": "16.9.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz", - "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==", - "dev": true - }, - "@types/resolve": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", - "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", - "dev": true + "node_modules/restore-cursor/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" }, - "@types/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", "dev": true, - "optional": true, - "requires": { - "@types/node": "*" + "license": "MIT", + "engines": { + "node": ">= 4" } }, - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true - }, - "accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "dev": true, - "requires": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" } }, - "acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", - "dev": true - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, - "requires": {} + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, - "requires": { - "debug": "4" + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "agentkeepalive": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.3.0.tgz", - "integrity": "sha512-7Epl1Blf4Sy37j4v9f9FjICCh4+KAQOyXgHEwlyBiAQLbhKdq/i2QQU3amQalS/wPhdPzDXPL5DMR5bkn+YeWg==", + "node_modules/rollup": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.44.1.tgz", + "integrity": "sha512-x8H8aPvD+xbl0Do8oez5f5o8eMS3trfCghc4HhLAnCkj7Vl0d1JWGs0UF/D886zLW2rOj2QymV/JcSSsw+XDNg==", "dev": true, - "requires": { - "debug": "^4.1.0", - "depd": "^2.0.0", - "humanize-ms": "^1.2.1" + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.44.1", + "@rollup/rollup-android-arm64": "4.44.1", + "@rollup/rollup-darwin-arm64": "4.44.1", + "@rollup/rollup-darwin-x64": "4.44.1", + "@rollup/rollup-freebsd-arm64": "4.44.1", + "@rollup/rollup-freebsd-x64": "4.44.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.44.1", + "@rollup/rollup-linux-arm-musleabihf": "4.44.1", + "@rollup/rollup-linux-arm64-gnu": "4.44.1", + "@rollup/rollup-linux-arm64-musl": "4.44.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.44.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.44.1", + "@rollup/rollup-linux-riscv64-gnu": "4.44.1", + "@rollup/rollup-linux-riscv64-musl": "4.44.1", + "@rollup/rollup-linux-s390x-gnu": "4.44.1", + "@rollup/rollup-linux-x64-gnu": "4.44.1", + "@rollup/rollup-linux-x64-musl": "4.44.1", + "@rollup/rollup-win32-arm64-msvc": "4.44.1", + "@rollup/rollup-win32-ia32-msvc": "4.44.1", + "@rollup/rollup-win32-x64-msvc": "4.44.1", + "fsevents": "~2.3.2" } }, - "aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "node_modules/rollup-plugin-filesize": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-filesize/-/rollup-plugin-filesize-10.0.0.tgz", + "integrity": "sha512-JAYYhzCcmGjmCzo3LEHSDE3RAPHKIeBdpqRhiyZSv5o/3wFhktUOzYAWg/uUKyEu5dEaVaql6UOmaqHx1qKrZA==", "dev": true, - "requires": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.8", + "boxen": "^5.0.0", + "brotli-size": "4.0.0", + "colors": "1.4.0", + "filesize": "^6.1.0", + "gzip-size": "^6.0.0", + "pacote": "^15.1.1", + "terser": "^5.6.0" + }, + "engines": { + "node": ">=16.0.0" } }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "node_modules/rollup-plugin-visualizer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-6.0.3.tgz", + "integrity": "sha512-ZU41GwrkDcCpVoffviuM9Clwjy5fcUxlz0oMoTXTYsK+tcIFzbdacnrr2n8TXcHxbGKKXtOdjxM2HUS4HjkwIw==", "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "license": "MIT", + "dependencies": { + "open": "^8.0.0", + "picomatch": "^4.0.2", + "source-map": "^0.7.4", + "yargs": "^17.5.1" + }, + "bin": { + "rollup-plugin-visualizer": "dist/bin/cli.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "rolldown": "1.x || ^1.0.0-beta", + "rollup": "2.x || 3.x || 4.x" + }, + "peerDependenciesMeta": { + "rolldown": { + "optional": true + }, + "rollup": { + "optional": true + } } }, - "ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "node_modules/rollup-plugin-visualizer/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", "dev": true, - "requires": { - "string-width": "^4.1.0" + "license": "BSD-3-Clause", + "engines": { + "node": ">= 8" } }, - "ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "dev": true, - "requires": { - "color-convert": "^2.0.1" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" } }, - "any-base": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz", - "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==", - "dev": true - }, - "aproba": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", - "dev": true - }, - "are-we-there-yet": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", - "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "dev": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" } }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "dev": true - }, - "array-includes": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", - "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "get-intrinsic": "^1.1.3", - "is-string": "^1.0.7" + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "array.prototype.flat": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", - "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0" - } + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" }, - "array.prototype.flatmap": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", - "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0" + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "ast-metadata-inferer": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/ast-metadata-inferer/-/ast-metadata-inferer-0.8.0.tgz", - "integrity": "sha512-jOMKcHht9LxYIEQu+RVd22vtgrPaVCtDRQ/16IGmurdzxvYbDd5ynxjnyrzLnieG96eTcAyaoj/wN/4/1FyyeA==", + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", "dev": true, - "requires": { - "@mdn/browser-compat-data": "^5.2.34" + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "available-typed-arrays": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", - "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "dev": true + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "dev": true, + "license": "ISC" }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true + "node_modules/secure-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", + "integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==", + "dev": true, + "license": "MIT" }, - "basic-auth": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", - "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "node_modules/selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", "dev": true, - "requires": { - "safe-buffer": "5.1.2" + "license": "MIT", + "dependencies": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + }, + "engines": { + "node": ">=10" } }, - "batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", - "dev": true - }, - "bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, - "requires": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, - "bmp-js": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz", - "integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==", - "dev": true - }, - "body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dev": true, - "requires": { - "bytes": "3.1.2", - "content-type": "~1.0.4", + "license": "MIT", + "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - } - } - }, - "boxen": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", - "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", - "dev": true, - "requires": { - "ansi-align": "^3.0.0", - "camelcase": "^6.2.0", - "chalk": "^4.1.0", - "cli-boxes": "^2.2.1", - "string-width": "^4.2.2", - "type-fest": "^0.20.2", - "widest-line": "^3.1.0", - "wrap-ansi": "^7.0.0" - }, - "dependencies": { - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "brotli-size": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/brotli-size/-/brotli-size-4.0.0.tgz", - "integrity": "sha512-uA9fOtlTRC0iqKfzff1W34DXUA3GyVqbUaeo3Rw3d4gd1eavKVCETXrn3NzO74W+UVkG3UHu8WxUi+XvKI/huA==", - "dev": true, - "requires": { - "duplexer": "0.1.1" - } - }, - "browserslist": { - "version": "4.21.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", - "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001449", - "electron-to-chromium": "^1.4.284", - "node-releases": "^2.0.8", - "update-browserslist-db": "^1.0.10" - } - }, - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true - }, - "buffer-equal": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", - "integrity": "sha512-RgSV6InVQ9ODPdLWJ5UAqBqJBOg370Nz6ZQtRzpt6nUjc8v0St97uJ4PYC6NztqIScrAXafKM3mZPMygSe1ggA==", - "dev": true - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", - "dev": true - }, - "builtins": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", - "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", - "dev": true, - "requires": { - "semver": "^7.0.0" - } - }, - "bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true - }, - "cacache": { - "version": "17.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-17.0.4.tgz", - "integrity": "sha512-Z/nL3gU+zTUjz5pCA5vVjYM8pmaw2kxM7JEiE0fv3w77Wj+sFbi70CrBruUWH0uNcEdvLDixFpgA2JM4F4DBjA==", - "dev": true, - "requires": { - "@npmcli/fs": "^3.1.0", - "fs-minipass": "^3.0.0", - "glob": "^8.0.1", - "lru-cache": "^7.7.1", - "minipass": "^4.0.0", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "ssri": "^10.0.0", - "tar": "^6.1.11", - "unique-filename": "^3.0.0" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - } - }, - "lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true - }, - "minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - } - } - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30001476", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001476.tgz", - "integrity": "sha512-JmpktFppVSvyUN4gsLS0bShY2L9ZUslHLE72vgemBkS43JD2fOvKTKs+GtRwuxrtRGnwJFW0ye7kWRRlLJS9vQ==", - "dev": true - }, - "chalk": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", - "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==", - "dev": true - }, - "chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true - }, - "chromium-bidi": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.7.tgz", - "integrity": "sha512-6+mJuFXwTMU6I3vYLs6IL8A1DyQTPjCfIL971X0aMPVGRbGnNfl6i6Cl0NMbxi2bRYLGESt9T2ZIMRM5PAEcIQ==", - "dev": true, - "requires": { - "mitt": "3.0.0" - } - }, - "clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true - }, - "cli-boxes": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", - "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", - "dev": true - }, - "cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "dev": true - }, - "colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true - }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "concurrently": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.0.1.tgz", - "integrity": "sha512-Sh8bGQMEL0TAmAm2meAXMjcASHZa7V0xXQVDBLknCPa9TPtkY9yYs+0cnGGgfdkW0SV1Mlg+hVGfXcoI8d3MJA==", - "dev": true, - "requires": { - "chalk": "^4.1.2", - "date-fns": "^2.29.3", - "lodash": "^4.17.21", - "rxjs": "^7.8.0", - "shell-quote": "^1.8.0", - "spawn-command": "0.0.2-1", - "supports-color": "^8.1.1", - "tree-kill": "^1.2.2", - "yargs": "^17.7.1" - }, - "dependencies": { - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - } - } - }, - "console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", - "dev": true - }, - "content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dev": true, - "requires": { - "safe-buffer": "5.2.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - } - } - }, - "content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "dev": true - }, - "cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", - "dev": true - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "dev": true - }, - "cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "dev": true, - "requires": { - "object-assign": "^4", - "vary": "^1" - } - }, - "cross-fetch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", - "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", - "dev": true, - "requires": { - "node-fetch": "2.6.7" - }, - "dependencies": { - "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dev": true, - "requires": { - "whatwg-url": "^5.0.0" - } - } - } - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "date-fns": { - "version": "2.29.3", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz", - "integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==", - "dev": true - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "deepmerge": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.0.tgz", - "integrity": "sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og==", - "dev": true - }, - "define-lazy-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", - "dev": true - }, - "define-properties": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", - "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", - "dev": true, - "requires": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - } - }, - "delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "dev": true - }, - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true - }, - "destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "dev": true - }, - "devtools-protocol": { - "version": "0.0.1107588", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1107588.tgz", - "integrity": "sha512-yIR+pG9x65Xko7bErCUSQaDLrO/P1p3JUzEk7JCU4DowPcGHkTGUGQapcfcLc4qj0UaALwZ+cr0riFgiqpixcg==", - "dev": true - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "dev": true, - "requires": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - } - }, - "dom-walk": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", - "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==", - "dev": true - }, - "domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "dev": true - }, - "domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "dev": true, - "requires": { - "domelementtype": "^2.3.0" - } - }, - "domutils": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz", - "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==", - "dev": true, - "requires": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.1" - } - }, - "duplexer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", - "integrity": "sha512-sxNZ+ljy+RA1maXoUReeqBBpBC6RLKmg5ewzV+x+mSETmWNoKdZN6vcQjpFROemza23hGFskJtFNoUWUaQ+R4Q==", - "dev": true - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "dev": true - }, - "electron-to-chromium": { - "version": "1.4.308", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.308.tgz", - "integrity": "sha512-qyTx2aDFjEni4UnRWEME9ubd2Xc9c0zerTUl/ZinvD4QPsF0S7kJTV/Es/lPCTkNX6smyYar+z/n8Cl6pFr8yQ==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "dev": true - }, - "encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "dev": true, - "optional": true, - "requires": { - "iconv-lite": "^0.6.2" - } - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, - "entities": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", - "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", - "dev": true - }, - "env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true - }, - "err-code": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "dev": true - }, - "es-abstract": { - "version": "1.21.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.1.tgz", - "integrity": "sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg==", - "dev": true, - "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-set-tostringtag": "^2.0.1", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.1.3", - "get-symbol-description": "^1.0.0", - "globalthis": "^1.0.3", - "gopd": "^1.0.1", - "has": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.4", - "is-array-buffer": "^3.0.1", - "is-callable": "^1.2.7", - "is-negative-zero": "^2.0.2", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.10", - "is-weakref": "^1.0.2", - "object-inspect": "^1.12.2", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", - "safe-regex-test": "^1.0.0", - "string.prototype.trimend": "^1.0.6", - "string.prototype.trimstart": "^1.0.6", - "typed-array-length": "^1.0.4", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.9" - } - }, - "es-set-tostringtag": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", - "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.3", - "has": "^1.0.3", - "has-tostringtag": "^1.0.0" - } - }, - "es-shim-unscopables": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", - "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "eslint": { - "version": "8.40.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.40.0.tgz", - "integrity": "sha512-bvR+TsP9EHL3TqNtj9sCNJVAFK3fBN8Q7g5waghxyRsPLIMwL73XSKnZFK0hk/O2ANC+iAoq6PWMQ+IfBAJIiQ==", - "dev": true, - "requires": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.3", - "@eslint/js": "8.40.0", - "@humanwhocodes/config-array": "^0.11.8", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.0", - "eslint-visitor-keys": "^3.4.1", - "espree": "^9.5.2", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0" - }, - "dependencies": { - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "eslint-config-mdcs": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-mdcs/-/eslint-config-mdcs-5.0.0.tgz", - "integrity": "sha512-d4lzeT/sQ3TkI69hd+N/dtQ15g3GrbIboTfCAw6FaDQTLjWK2O3+dNfOOfkAC5TlwyU9BxztR1TE+x8iSzyuPw==", - "dev": true - }, - "eslint-import-resolver-node": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", - "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", - "dev": true, - "requires": { - "debug": "^3.2.7", - "is-core-module": "^2.11.0", - "resolve": "^1.22.1" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "eslint-module-utils": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", - "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", - "dev": true, - "requires": { - "debug": "^3.2.7" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "eslint-plugin-compat": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-compat/-/eslint-plugin-compat-4.1.4.tgz", - "integrity": "sha512-RxySWBmzfIROLFKgeJBJue2BU/6vM2KJWXWAUq+oW4QtrsZXRxbjgxmO1OfF3sHcRuuIenTS/wgo3GyUWZF24w==", - "dev": true, - "requires": { - "@mdn/browser-compat-data": "^5.2.47", - "@tsconfig/node14": "^1.0.3", - "ast-metadata-inferer": "^0.8.0", - "browserslist": "^4.21.5", - "caniuse-lite": "^1.0.30001473", - "find-up": "^5.0.0", - "lodash.memoize": "4.1.2", - "semver": "7.3.8" - } - }, - "eslint-plugin-html": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-html/-/eslint-plugin-html-7.1.0.tgz", - "integrity": "sha512-fNLRraV/e6j8e3XYOC9xgND4j+U7b1Rq+OygMlLcMg+wI/IpVbF+ubQa3R78EjKB9njT6TQOlcK5rFKBVVtdfg==", - "dev": true, - "requires": { - "htmlparser2": "^8.0.1" - } - }, - "eslint-plugin-import": { - "version": "2.27.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", - "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", - "dev": true, - "requires": { - "array-includes": "^3.1.6", - "array.prototype.flat": "^1.3.1", - "array.prototype.flatmap": "^1.3.1", - "debug": "^3.2.7", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.7", - "eslint-module-utils": "^2.7.4", - "has": "^1.0.3", - "is-core-module": "^2.11.0", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.values": "^1.1.6", - "resolve": "^1.22.1", - "semver": "^6.3.0", - "tsconfig-paths": "^3.14.1" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "eslint-scope": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", - "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - } - }, - "eslint-visitor-keys": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", - "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", - "dev": true - }, - "espree": { - "version": "9.5.2", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", - "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", - "dev": true, - "requires": { - "acorn": "^8.8.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - } - }, - "esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - } - }, - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - }, - "estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "dev": true - }, - "exif-parser": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz", - "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==", - "dev": true - }, - "express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", - "dev": true, - "requires": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.1", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.5.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - } - } - }, - "extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "requires": { - "@types/yauzl": "^2.9.1", - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - } - }, - "failonlyreporter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/failonlyreporter/-/failonlyreporter-1.0.0.tgz", - "integrity": "sha512-daW559J4F/nWk0AiUPuxpCNCRXNa74yQdZNAVBIJt192VbsfKMNZocCqvRLjFIIp9BeBGu4gUhFJImmb4kSWOQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "dev": true, - "requires": { - "pend": "~1.2.0" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "file-type": { - "version": "16.5.4", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", - "integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==", - "dev": true, - "requires": { - "readable-web-to-node-stream": "^3.0.0", - "strtok3": "^6.2.4", - "token-types": "^4.1.1" - } - }, - "filesize": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-6.4.0.tgz", - "integrity": "sha512-mjFIpOHC4jbfcTfoh4rkWpI31mF7viw9ikj/JyLoKzqlwG/YsefKfvYlYhdYdg/9mtK2z1AzgN/0LvVQ3zdlSQ==", - "dev": true - }, - "finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "dev": true, - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - } - } - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - } - }, - "flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true - }, - "for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, - "requires": { - "is-callable": "^1.1.3" - } - }, - "forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "dev": true - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "dev": true - }, - "fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true - }, - "fs-minipass": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.1.tgz", - "integrity": "sha512-MhaJDcFRTuLidHrIttu0RDGyyXs/IYHVmlcxfLAEFIWjc1vdLAkdwT7Ace2u7DbitWC0toKMl5eJZRYNVreIMw==", - "dev": true, - "requires": { - "minipass": "^4.0.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "function.prototype.name": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", - "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" - } - }, - "functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true - }, - "gauge": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", - "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", - "dev": true, - "requires": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", - "has-unicode": "^2.0.1", - "signal-exit": "^3.0.7", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" - } - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-intrinsic": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", - "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - } - }, - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - } - }, - "gifwrap": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.9.4.tgz", - "integrity": "sha512-MDMwbhASQuVeD4JKd1fKgNgCRL3fGqMM4WaqpNhWO0JiMOAjbQdumbs4BbBZEy9/M00EHEjKN3HieVhCUlwjeQ==", - "dev": true, - "requires": { - "image-q": "^4.0.0", - "omggif": "^1.0.10" - } - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "requires": { - "is-glob": "^4.0.3" - } - }, - "global": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", - "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", - "dev": true, - "requires": { - "min-document": "^2.19.0", - "process": "^0.11.10" - } - }, - "globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "globalthis": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", - "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", - "dev": true, - "requires": { - "define-properties": "^1.1.3" - } - }, - "globalyzer": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz", - "integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==", - "dev": true - }, - "globrex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", - "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", - "dev": true - }, - "gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.3" - } - }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - }, - "grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", - "dev": true - }, - "gzip-size": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", - "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", - "dev": true, - "requires": { - "duplexer": "^0.1.2" - }, - "dependencies": { - "duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", - "dev": true - } - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.1" - } - }, - "has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "dev": true - }, - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true - }, - "has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, - "requires": { - "has-symbols": "^1.0.2" - } - }, - "has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", - "dev": true - }, - "hosted-git-info": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz", - "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==", - "dev": true, - "requires": { - "lru-cache": "^7.5.1" - }, - "dependencies": { - "lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true - } - } - }, - "htmlparser2": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.1.tgz", - "integrity": "sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==", - "dev": true, - "requires": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "entities": "^4.3.0" - } - }, - "http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", - "dev": true - }, - "http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dev": true, - "requires": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - } - }, - "http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, - "requires": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - } - }, - "https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "requires": { - "agent-base": "6", - "debug": "4" - } - }, - "humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "dev": true, - "requires": { - "ms": "^2.0.0" - } - }, - "iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "optional": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - } - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true - }, - "ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", - "dev": true - }, - "ignore-walk": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.1.tgz", - "integrity": "sha512-/c8MxUAqpRccq+LyDOecwF+9KqajueJHh8fz7g3YqjMZt+NSfJzx05zrKiXwa2sKwFCzaiZ5qUVfRj0pmxixEA==", - "dev": true, - "requires": { - "minimatch": "^6.1.6" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "minimatch": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-6.2.0.tgz", - "integrity": "sha512-sauLxniAmvnhhRjFwPNnJKaPFYyddAgbYdeUpHULtCT/GhzdCx/MDNy+Y40lBxTQUrMzDE8e0S43Z5uqfO0REg==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - } - } - }, - "image-q": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/image-q/-/image-q-4.0.0.tgz", - "integrity": "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==", - "dev": true, - "requires": { - "@types/node": "16.9.1" - } - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true - }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - }, - "infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "internal-slot": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.4.tgz", - "integrity": "sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.3", - "has": "^1.0.3", - "side-channel": "^1.0.4" - } - }, - "ip": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", - "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", - "dev": true - }, - "ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true - }, - "is-array-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.1.tgz", - "integrity": "sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "is-typed-array": "^1.1.10" - } - }, - "is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "requires": { - "has-bigints": "^1.0.1" - } - }, - "is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-builtin-module": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", - "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", - "dev": true, - "requires": { - "builtin-modules": "^3.3.0" - } - }, - "is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true - }, - "is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "is-function": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", - "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==", - "dev": true - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-lambda": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", - "dev": true - }, - "is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", - "dev": true - }, - "is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", - "dev": true - }, - "is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true - }, - "is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2" - } - }, - "is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.2" - } - }, - "is-typed-array": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", - "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", - "dev": true, - "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" - } - }, - "is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2" - } - }, - "is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "requires": { - "is-docker": "^2.0.0" - } - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "isomorphic-fetch": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", - "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", - "dev": true, - "requires": { - "node-fetch": "^2.6.1", - "whatwg-fetch": "^3.4.1" - } - }, - "jimp": { - "version": "0.22.8", - "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.22.8.tgz", - "integrity": "sha512-pBbrooJMX7795sDcxx1XpwNZC8B/ITyDV+JK2/1qNbQl/1UWqWeh5Dq7qQpMZl5jLdcFDv5IVTM+OhpafSqSFA==", - "dev": true, - "requires": { - "@jimp/custom": "^0.22.8", - "@jimp/plugins": "^0.22.8", - "@jimp/types": "^0.22.8", - "regenerator-runtime": "^0.13.3" - } - }, - "jpeg-js": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz", - "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==", - "dev": true - }, - "js-sdsl": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz", - "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==", - "dev": true - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "json-parse-even-better-errors": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.0.tgz", - "integrity": "sha512-iZbGHafX/59r39gPwVPRBGw0QQKnA7tte5pSMrhWOW7swGsVvVTjmfyAV9pNqk8YGT7tRCdxRu8uzcgZwoDooA==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", - "dev": true - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "load-bmfont": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.1.tgz", - "integrity": "sha512-8UyQoYmdRDy81Brz6aLAUhfZLwr5zV0L3taTQ4hju7m6biuwiWiJXjPhBJxbUQJA8PrkvJ/7Enqmwk2sM14soA==", - "dev": true, - "requires": { - "buffer-equal": "0.0.1", - "mime": "^1.3.4", - "parse-bmfont-ascii": "^1.0.3", - "parse-bmfont-binary": "^1.0.5", - "parse-bmfont-xml": "^1.1.4", - "phin": "^2.9.1", - "xhr": "^2.0.1", - "xtend": "^4.0.0" - } - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "magic-string": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz", - "integrity": "sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==", - "dev": true, - "requires": { - "@jridgewell/sourcemap-codec": "^1.4.13" - } - }, - "make-fetch-happen": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", - "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", - "dev": true, - "requires": { - "agentkeepalive": "^4.2.1", - "cacache": "^16.1.0", - "http-cache-semantics": "^4.1.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^3.1.6", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^2.0.3", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", - "ssri": "^9.0.0" - }, - "dependencies": { - "@npmcli/fs": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", - "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", - "dev": true, - "requires": { - "@gar/promisify": "^1.1.3", - "semver": "^7.3.5" - } - }, - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "cacache": { - "version": "16.1.3", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", - "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", - "dev": true, - "requires": { - "@npmcli/fs": "^2.1.0", - "@npmcli/move-file": "^2.0.0", - "chownr": "^2.0.0", - "fs-minipass": "^2.1.0", - "glob": "^8.0.1", - "infer-owner": "^1.0.4", - "lru-cache": "^7.7.1", - "minipass": "^3.1.6", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "mkdirp": "^1.0.4", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^9.0.0", - "tar": "^6.1.11", - "unique-filename": "^2.0.0" - } - }, - "fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, - "requires": { - "minipass": "^3.0.0" - } - }, - "glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - } - }, - "lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true - }, - "minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - }, - "minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "ssri": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", - "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", - "dev": true, - "requires": { - "minipass": "^3.1.1" - } - }, - "unique-filename": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", - "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", - "dev": true, - "requires": { - "unique-slug": "^3.0.0" - } - }, - "unique-slug": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", - "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4" - } - } - } - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "dev": true - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", - "dev": true - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "dev": true - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true - }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "requires": { - "mime-db": "1.52.0" - } - }, - "min-document": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", - "integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==", - "dev": true, - "requires": { - "dom-walk": "^0.1.0" - } - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", - "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", - "dev": true - }, - "minipass": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.4.tgz", - "integrity": "sha512-lwycX3cBMTvcejsHITUgYj6Gy6A7Nh4Q6h9NP4sTHY1ccJlC7yKzDmiShEHsJ16Jf1nKGDEaiHxiltsJEvk0nQ==", - "dev": true - }, - "minipass-collect": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", - "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", - "dev": true, - "requires": { - "minipass": "^3.0.0" - }, - "dependencies": { - "minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - } - } - }, - "minipass-fetch": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", - "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", - "dev": true, - "requires": { - "encoding": "^0.1.13", - "minipass": "^3.1.6", - "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" - }, - "dependencies": { - "minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - } - } - }, - "minipass-flush": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", - "dev": true, - "requires": { - "minipass": "^3.0.0" - }, - "dependencies": { - "minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - } - } - }, - "minipass-json-stream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz", - "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==", - "dev": true, - "requires": { - "jsonparse": "^1.3.1", - "minipass": "^3.0.0" - }, - "dependencies": { - "minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - } - } - }, - "minipass-pipeline": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "dev": true, - "requires": { - "minipass": "^3.0.0" - }, - "dependencies": { - "minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - } - } - }, - "minipass-sized": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", - "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", - "dev": true, - "requires": { - "minipass": "^3.0.0" - }, - "dependencies": { - "minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - } - } - }, - "minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dev": true, - "requires": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "dependencies": { - "minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - } - } - }, - "mitt": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz", - "integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==", - "dev": true - }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - }, - "mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "dev": true - }, - "node-fetch": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", - "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", - "dev": true, - "requires": { - "whatwg-url": "^5.0.0" - } - }, - "node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", - "dev": true - }, - "node-gyp": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.3.1.tgz", - "integrity": "sha512-4Q16ZCqq3g8awk6UplT7AuxQ35XN4R/yf/+wSAwcBUAjg7l58RTactWaP8fIDTi0FzI7YcVLujwExakZlfWkXg==", - "dev": true, - "requires": { - "env-paths": "^2.2.0", - "glob": "^7.1.4", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^10.0.3", - "nopt": "^6.0.0", - "npmlog": "^6.0.0", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.2", - "which": "^2.0.2" - } - }, - "node-releases": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", - "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", - "dev": true - }, - "node-watch": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/node-watch/-/node-watch-0.7.3.tgz", - "integrity": "sha512-3l4E8uMPY1HdMMryPRUAl+oIHtXtyiTlIiESNSVSNxcPfzAFzeTbXFQkZfAwBbo0B1qMSG8nUABx+Gd+YrbKrQ==", - "dev": true - }, - "nopt": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", - "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", - "dev": true, - "requires": { - "abbrev": "^1.0.0" - } - }, - "normalize-package-data": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-5.0.0.tgz", - "integrity": "sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==", - "dev": true, - "requires": { - "hosted-git-info": "^6.0.0", - "is-core-module": "^2.8.1", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" - } - }, - "npm-bundled": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.0.tgz", - "integrity": "sha512-Vq0eyEQy+elFpzsKjMss9kxqb9tG3YHg4dsyWuUENuzvSUWe1TCnW/vV9FkhvBk/brEDoDiVd+M1Btosa6ImdQ==", - "dev": true, - "requires": { - "npm-normalize-package-bin": "^3.0.0" - } - }, - "npm-install-checks": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.0.0.tgz", - "integrity": "sha512-SBU9oFglRVZnfElwAtF14NivyulDqF1VKqqwNsFW9HDcbHMAPHpRSsVFgKuwFGq/hVvWZExz62Th0kvxn/XE7Q==", - "dev": true, - "requires": { - "semver": "^7.1.1" - } - }, - "npm-normalize-package-bin": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.0.tgz", - "integrity": "sha512-g+DPQSkusnk7HYXr75NtzkIP4+N81i3RPsGFidF3DzHd9MT9wWngmqoeg/fnHFz5MNdtG4w03s+QnhewSLTT2Q==", - "dev": true - }, - "npm-package-arg": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-10.1.0.tgz", - "integrity": "sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA==", - "dev": true, - "requires": { - "hosted-git-info": "^6.0.0", - "proc-log": "^3.0.0", - "semver": "^7.3.5", - "validate-npm-package-name": "^5.0.0" - } - }, - "npm-packlist": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-7.0.4.tgz", - "integrity": "sha512-d6RGEuRrNS5/N84iglPivjaJPxhDbZmlbTwTDX2IbcRHG5bZCdtysYMhwiPvcF4GisXHGn7xsxv+GQ7T/02M5Q==", - "dev": true, - "requires": { - "ignore-walk": "^6.0.0" - } - }, - "npm-pick-manifest": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-8.0.1.tgz", - "integrity": "sha512-mRtvlBjTsJvfCCdmPtiu2bdlx8d/KXtF7yNXNWe7G0Z36qWA9Ny5zXsI2PfBZEv7SXgoxTmNaTzGSbbzDZChoA==", - "dev": true, - "requires": { - "npm-install-checks": "^6.0.0", - "npm-normalize-package-bin": "^3.0.0", - "npm-package-arg": "^10.0.0", - "semver": "^7.3.5" - } - }, - "npm-registry-fetch": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-14.0.3.tgz", - "integrity": "sha512-YaeRbVNpnWvsGOjX2wk5s85XJ7l1qQBGAp724h8e2CZFFhMSuw9enom7K1mWVUtvXO1uUSFIAPofQK0pPN0ZcA==", - "dev": true, - "requires": { - "make-fetch-happen": "^11.0.0", - "minipass": "^4.0.0", - "minipass-fetch": "^3.0.0", - "minipass-json-stream": "^1.0.1", - "minizlib": "^2.1.2", - "npm-package-arg": "^10.0.0", - "proc-log": "^3.0.0" - }, - "dependencies": { - "lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true - }, - "make-fetch-happen": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.0.3.tgz", - "integrity": "sha512-oPLh5m10lRNNZDjJ2kP8UpboUx2uFXVaVweVe/lWut4iHWcQEmfqSVJt2ihZsFI8HbpwyyocaXbCAWf0g1ukIA==", - "dev": true, - "requires": { - "agentkeepalive": "^4.2.1", - "cacache": "^17.0.0", - "http-cache-semantics": "^4.1.1", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^4.0.0", - "minipass-fetch": "^3.0.0", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", - "ssri": "^10.0.0" - } - }, - "minipass-fetch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.1.tgz", - "integrity": "sha512-t9/wowtf7DYkwz8cfMSt0rMwiyNIBXf5CKZ3S5ZMqRqMYT0oLTp0x1WorMI9WTwvaPg21r1JbFxJMum8JrLGfw==", - "dev": true, - "requires": { - "encoding": "^0.1.13", - "minipass": "^4.0.0", - "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" - } - } - } - }, - "npmlog": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", - "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", - "dev": true, - "requires": { - "are-we-there-yet": "^3.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^4.0.3", - "set-blocking": "^2.0.0" - } - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true - }, - "object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", - "dev": true - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - } - }, - "object.values": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", - "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - } - }, - "omggif": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz", - "integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==", - "dev": true - }, - "on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, - "requires": { - "ee-first": "1.1.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "open": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", - "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", - "dev": true, - "requires": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - } - }, - "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - } - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - }, - "p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "requires": { - "aggregate-error": "^3.0.0" - } - }, - "pacote": { - "version": "15.1.1", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-15.1.1.tgz", - "integrity": "sha512-eeqEe77QrA6auZxNHIp+1TzHQ0HBKf5V6c8zcaYZ134EJe1lCi+fjXATkNiEEfbG+e50nu02GLvUtmZcGOYabQ==", - "dev": true, - "requires": { - "@npmcli/git": "^4.0.0", - "@npmcli/installed-package-contents": "^2.0.1", - "@npmcli/promise-spawn": "^6.0.1", - "@npmcli/run-script": "^6.0.0", - "cacache": "^17.0.0", - "fs-minipass": "^3.0.0", - "minipass": "^4.0.0", - "npm-package-arg": "^10.0.0", - "npm-packlist": "^7.0.0", - "npm-pick-manifest": "^8.0.0", - "npm-registry-fetch": "^14.0.0", - "proc-log": "^3.0.0", - "promise-retry": "^2.0.1", - "read-package-json": "^6.0.0", - "read-package-json-fast": "^3.0.0", - "sigstore": "^1.0.0", - "ssri": "^10.0.0", - "tar": "^6.1.11" - } - }, - "pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parse-bmfont-ascii": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz", - "integrity": "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==", - "dev": true - }, - "parse-bmfont-binary": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz", - "integrity": "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==", - "dev": true - }, - "parse-bmfont-xml": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.4.tgz", - "integrity": "sha512-bjnliEOmGv3y1aMEfREMBJ9tfL3WR0i0CKPj61DnSLaoxWR3nLrsQrEbCId/8rF4NyRF0cCqisSVXyQYWM+mCQ==", - "dev": true, - "requires": { - "xml-parse-from-string": "^1.0.0", - "xml2js": "^0.4.5" - } - }, - "parse-headers": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.5.tgz", - "integrity": "sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==", - "dev": true - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", - "dev": true - }, - "peek-readable": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", - "integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==", - "dev": true - }, - "pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true - }, - "phin": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/phin/-/phin-2.9.3.tgz", - "integrity": "sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA==", - "dev": true - }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } }, - "pixelmatch": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.3.0.tgz", - "integrity": "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==", + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, - "requires": { - "pngjs": "^6.0.0" + "license": "MIT", + "dependencies": { + "ms": "2.0.0" } }, - "pngjs": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", - "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", - "dev": true - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "proc-log": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", - "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==", - "dev": true - }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "dev": true - }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, - "promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", - "dev": true + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" }, - "promise-retry": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", - "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", "dev": true, - "requires": { - "err-code": "^2.0.2", - "retry": "^0.12.0" + "license": "MIT", + "engines": { + "node": ">= 0.8" } }, - "proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "node_modules/send/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "dev": true, - "requires": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" } }, - "proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" } }, - "punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "dev": true + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } }, - "puppeteer-core": { - "version": "19.11.1", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-19.11.1.tgz", - "integrity": "sha512-qcuC2Uf0Fwdj9wNtaTZ2OvYRraXpAK+puwwVW8ofOhOgLPZyz1c68tsorfIZyCUOpyBisjr+xByu7BMbEYMepA==", + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", "dev": true, - "requires": { - "@puppeteer/browsers": "0.5.0", - "chromium-bidi": "0.4.7", - "cross-fetch": "3.1.5", - "debug": "4.3.4", - "devtools-protocol": "0.0.1107588", - "extract-zip": "2.0.1", - "https-proxy-agent": "5.0.1", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "ws": "8.13.0" + "license": "MIT", + "engines": { + "node": ">= 0.6" } }, - "qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", "dev": true, - "requires": { - "side-channel": "^1.0.4" + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" } }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true + "node_modules/serve-index/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true, + "license": "ISC" }, - "qunit": { - "version": "2.19.4", - "resolved": "https://registry.npmjs.org/qunit/-/qunit-2.19.4.tgz", - "integrity": "sha512-aqUzzUeCqlleWYKlpgfdHHw9C6KxkB9H3wNfiBg5yHqQMzy0xw/pbCRHYFkjl8MsP/t8qkTQE+JTYL71azgiew==", + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true, - "requires": { - "commander": "7.2.0", - "node-watch": "0.7.3", - "tiny-glob": "0.2.9" - }, - "dependencies": { - "commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "dev": true - } + "license": "MIT" + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" } }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dev": true, - "requires": { - "safe-buffer": "^5.1.0" + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" } }, - "range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true + "node_modules/server-destroy": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/server-destroy/-/server-destroy-1.0.1.tgz", + "integrity": "sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==", + "dev": true, + "license": "ISC" }, - "raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "node_modules/servez": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/servez/-/servez-2.3.0.tgz", + "integrity": "sha512-47Et/S+wuVwO2qsNMT/MhVNLvlBJD6sr+qQxTdvNoWOEzDRPfdQWSy6qjWUHWd70n39HoVLAvfPhWAFlgVMbvQ==", "dev": true, - "requires": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, + "license": "MIT", "dependencies": { - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - } + "ansi-colors": "^4.1.3", + "color-support": "^1.1.3", + "commander": "^12.1.0", + "servez-lib": "^2.10.0" + }, + "bin": { + "servez": "bin/servez" } }, - "read-package-json": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-6.0.0.tgz", - "integrity": "sha512-b/9jxWJ8EwogJPpv99ma+QwtqB7FSl3+V6UXS7Aaay8/5VwMY50oIFooY1UKXMWpfNCM6T/PoGqa5GD1g9xf9w==", + "node_modules/servez-lib": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/servez-lib/-/servez-lib-2.10.0.tgz", + "integrity": "sha512-QLh3bXMBljYRajszb0f5gp5W3aJg/8LSfHY5UYY+fzWt4+egDr21ZnKUrzEPYV8XNajiAyinOlaGbzL6NHUJFQ==", "dev": true, - "requires": { - "glob": "^8.0.1", - "json-parse-even-better-errors": "^3.0.0", - "normalize-package-data": "^5.0.0", - "npm-normalize-package-bin": "^3.0.0" - }, + "license": "MIT", "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - } - }, - "minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - } + "basic-auth": "^2.0.1", + "cors": "^2.8.5", + "debug": "^4.4.0", + "express": "^4.21.2", + "secure-compare": "^3.0.1", + "selfsigned": "^2.4.1", + "serve-index": "^1.9.1", + "server-destroy": "^1.0.1" } }, - "read-package-json-fast": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz", - "integrity": "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==", + "node_modules/servez/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", "dev": true, - "requires": { - "json-parse-even-better-errors": "^3.0.0", - "npm-normalize-package-bin": "^3.0.0" + "license": "MIT", + "engines": { + "node": ">=18" } }, - "readable-stream": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.1.tgz", - "integrity": "sha512-+rQmrWMYGA90yenhTYsLWAsLsqVC8osOw6PKE1HDYiO0gdPeKe/xDHNzIAIn4C91YQ6oenEhfYqqc1883qHbjQ==", + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "license": "ISC" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" } }, - "readable-web-to-node-stream": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz", - "integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==", + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", "dev": true, - "requires": { - "readable-stream": "^3.6.0" + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" } }, - "regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", - "dev": true - }, - "regexp.prototype.flags": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", - "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "functions-have-names": "^1.2.2" + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" } }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true - }, - "resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true, - "requires": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - }, - "retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "dev": true - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true + "license": "ISC" }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, - "requires": { - "glob": "^7.1.3" + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" } }, - "rollup": { - "version": "3.21.7", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.21.7.tgz", - "integrity": "sha512-KXPaEuR8FfUoK2uHwNjxTmJ18ApyvD6zJpYv9FOJSqLStmt6xOY84l1IjK2dSolQmoXknrhEFRaPRgOPdqCT5w==", + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, - "requires": { - "fsevents": "~2.3.2" + "license": "MIT", + "engines": { + "node": ">=8" } }, - "rollup-plugin-filesize": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-filesize/-/rollup-plugin-filesize-10.0.0.tgz", - "integrity": "sha512-JAYYhzCcmGjmCzo3LEHSDE3RAPHKIeBdpqRhiyZSv5o/3wFhktUOzYAWg/uUKyEu5dEaVaql6UOmaqHx1qKrZA==", + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", "dev": true, - "requires": { - "@babel/runtime": "^7.13.8", - "boxen": "^5.0.0", - "brotli-size": "4.0.0", - "colors": "1.4.0", - "filesize": "^6.1.0", - "gzip-size": "^6.0.0", - "pacote": "^15.1.1", - "terser": "^5.6.0" + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "rollup-plugin-visualizer": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.9.0.tgz", - "integrity": "sha512-bbDOv47+Bw4C/cgs0czZqfm8L82xOZssk4ayZjG40y9zbXclNk7YikrZTDao6p7+HDiGxrN0b65SgZiVm9k1Cg==", + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "dev": true, - "requires": { - "open": "^8.4.0", - "picomatch": "^2.3.1", - "source-map": "^0.7.4", - "yargs": "^17.5.1" + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", "dev": true, - "requires": { - "queue-microtask": "^1.2.2" + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "rxjs": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", - "integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==", + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "dev": true, - "requires": { - "tslib": "^2.1.0" + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "safe-regex-test": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", - "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "is-regex": "^1.1.4" + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true - }, - "secure-compare": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", - "integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==", - "dev": true - }, - "selfsigned": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.1.1.tgz", - "integrity": "sha512-GSL3aowiF7wa/WtSFwnUrludWFoNhftq8bUkH9pkzjpN2XSPOAYEgg6e0sS9s0rZwgJzJiQRPU18A6clnoW5wQ==", + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, - "requires": { - "node-forge": "^1" + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "node_modules/sigstore": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-1.9.0.tgz", + "integrity": "sha512-0Zjz0oe37d08VeOtBIuB6cRriqXse2e8w+7yIy2XSXjshRKxbc2KkhXjL229jXSxEm7UbcjS76wcJDGQddVI9A==", "dev": true, - "requires": { - "lru-cache": "^6.0.0" + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^1.1.0", + "@sigstore/protobuf-specs": "^0.2.0", + "@sigstore/sign": "^1.0.0", + "@sigstore/tuf": "^1.0.3", + "make-fetch-happen": "^11.0.1" + }, + "bin": { + "sigstore": "bin/sigstore.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "node_modules/sigstore/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "dev": true, - "requires": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, + "license": "MIT", "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - } - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - } + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" } }, - "serialize-javascript": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", - "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "node_modules/sigstore/node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", "dev": true, - "requires": { - "randombytes": "^2.1.0" + "license": "MIT", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" } }, - "serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "node_modules/sigstore/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "dev": true, - "requires": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" - }, + "license": "MIT", "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", - "dev": true - }, - "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", - "dev": true, - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "dev": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true - }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "dev": true - } + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" } }, - "serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "node_modules/sigstore/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", "dev": true, - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" + "license": "ISC", + "engines": { + "node": ">=12" } }, - "server-destroy": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/server-destroy/-/server-destroy-1.0.1.tgz", - "integrity": "sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==", - "dev": true - }, - "servez": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/servez/-/servez-1.14.2.tgz", - "integrity": "sha512-bB2h2O/aVAzWjxjovGlDHP4Hv65+YgNwjwq5uIb6EAwOfnBCgfu/yOy7t76blJ/g7LSdbBNh55Mvr1IH5WPn6Q==", + "node_modules/sigstore/node_modules/make-fetch-happen": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz", + "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==", "dev": true, - "requires": { - "ansi-colors": "^4.1.1", - "color-support": "^1.1.3", - "optionator": "^0.8.2", - "servez-lib": "^2.7.0" - }, - "dependencies": { - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "dev": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - } + "license": "ISC", + "dependencies": { + "agentkeepalive": "^4.2.1", + "cacache": "^17.0.0", + "http-cache-semantics": "^4.1.1", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^5.0.0", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "servez-lib": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/servez-lib/-/servez-lib-2.7.0.tgz", - "integrity": "sha512-mWF7AVrFMQ8C2Jzh8LQGeY8c97dh5mXGdUu8OzcFf1yQBfTdhcTaeHwVylzR/oMibrnze9fjkd+9MJHDJiN6sw==", + "node_modules/sigstore/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", "dev": true, - "requires": { - "basic-auth": "^2.0.1", - "cors": "^2.8.5", - "debug": "^4.3.4", - "express": "^4.18.2", - "secure-compare": "^3.0.1", - "selfsigned": "^2.1.1", - "serve-index": "^1.9.1", - "server-destroy": "^1.0.1" + "license": "ISC", + "engines": { + "node": ">=8" } }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true - }, - "setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "node_modules/sigstore/node_modules/minipass-fetch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz", + "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==", "dev": true, - "requires": { - "shebang-regex": "^3.0.0" + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" } }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "shell-quote": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.0.tgz", - "integrity": "sha512-QHsz8GgQIGKlRi24yFc6a6lN69Idnx634w49ay6+jA5yFh7a1UY+4Rp6HPx/L/1zcEDPEij8cIsiqR6bQsE5VQ==", - "dev": true - }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "node_modules/sigstore/node_modules/minipass-fetch/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" } }, - "signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "sigstore": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-1.0.0.tgz", - "integrity": "sha512-e+qfbn/zf1+rCza/BhIA//Awmf0v1pa5HQS8Xk8iXrn9bgytytVLqYD0P7NSqZ6IELTgq+tcDvLPkQjNHyWLNg==", + "node_modules/sigstore/node_modules/socks-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", + "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", "dev": true, - "requires": { - "make-fetch-happen": "^11.0.1", - "tuf-js": "^1.0.0" - }, + "license": "MIT", "dependencies": { - "lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true - }, - "make-fetch-happen": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.0.3.tgz", - "integrity": "sha512-oPLh5m10lRNNZDjJ2kP8UpboUx2uFXVaVweVe/lWut4iHWcQEmfqSVJt2ihZsFI8HbpwyyocaXbCAWf0g1ukIA==", - "dev": true, - "requires": { - "agentkeepalive": "^4.2.1", - "cacache": "^17.0.0", - "http-cache-semantics": "^4.1.1", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^4.0.0", - "minipass-fetch": "^3.0.0", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", - "ssri": "^10.0.0" - } - }, - "minipass-fetch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.1.tgz", - "integrity": "sha512-t9/wowtf7DYkwz8cfMSt0rMwiyNIBXf5CKZ3S5ZMqRqMYT0oLTp0x1WorMI9WTwvaPg21r1JbFxJMum8JrLGfw==", - "dev": true, - "requires": { - "encoding": "^0.1.13", - "minipass": "^4.0.0", - "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" - } - } + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" } }, - "smart-buffer": { + "node_modules/simple-xml-to-json": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/simple-xml-to-json/-/simple-xml-to-json-1.2.3.tgz", + "integrity": "sha512-kWJDCr9EWtZ+/EYYM5MareWj2cRnZGF93YDNpH4jQiHB+hBIZnfPFSQiVMzZOdk+zXWqTZ/9fTeQNu2DqeiudA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.12.2" + } + }, + "node_modules/smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "dev": true + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } }, - "smob": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/smob/-/smob-0.0.6.tgz", - "integrity": "sha512-V21+XeNni+tTyiST1MHsa84AQhT1aFZipzPpOFAVB8DkHzwJyjjAmt9bgwnuZiZWnIbMo2duE29wybxv/7HWUw==", - "dev": true + "node_modules/smob": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/smob/-/smob-1.5.0.tgz", + "integrity": "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==", + "dev": true, + "license": "MIT" }, - "socks": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", - "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "node_modules/socks": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.5.tgz", + "integrity": "sha512-iF+tNDQla22geJdTyJB1wM/qrX9DMRwWrciEPwWLPRWAUEM8sQiyxgckLxWT1f7+9VabJS0jTGGr4QgBuvi6Ww==", "dev": true, - "requires": { - "ip": "^2.0.0", + "license": "MIT", + "dependencies": { + "ip-address": "^9.0.5", "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" } }, - "socks-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", - "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", "dev": true, - "requires": { - "agent-base": "^6.0.2", - "debug": "^4.3.3", - "socks": "^2.6.2" + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" } }, - "source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "dev": true + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } }, - "source-map-support": { + "node_modules/source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } } }, - "spawn-command": { - "version": "0.0.2-1", - "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz", - "integrity": "sha512-n98l9E2RMSJ9ON1AKisHzz7V42VDiBQGY6PB1BwRglz99wpVsSuGzQ+jOi6lFXBGVTCrRpltvjm+/XA+tpeJrg==", - "dev": true - }, - "spdx-correct": { + "node_modules/spdx-correct": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", "dev": true, - "requires": { + "license": "Apache-2.0", + "dependencies": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" } }, - "spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" }, - "spdx-expression-parse": { + "node_modules/spdx-expression-parse": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, - "spdx-license-ids": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz", - "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==", - "dev": true + "node_modules/spdx-license-ids": { + "version": "3.0.21", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz", + "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true, + "license": "BSD-3-Clause" }, - "ssri": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.1.tgz", - "integrity": "sha512-WVy6di9DlPOeBWEjMScpNipeSX2jIZBGEn5Uuo8Q7aIuFEuDX0pw8RxcOjlD1TWP4obi24ki7m/13+nFpcbXrw==", + "node_modules/ssri": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", + "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", "dev": true, - "requires": { - "minipass": "^4.0.0" + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "statuses": { + "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/streamx": { + "version": "2.22.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.1.tgz", + "integrity": "sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, - "requires": { - "safe-buffer": "~5.1.0" + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "string-width": { + "node_modules/string-width-cjs": { + "name": "string-width", "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" } }, - "string.prototype.trimend": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", - "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "string.prototype.trimstart": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", - "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "strip-ansi": { + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "strip-bom": { + "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } }, - "strip-json-comments": { + "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "strtok3": { + "node_modules/strtok3": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", "integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "@tokenizer/token": "^0.3.0", "peek-readable": "^4.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" } }, - "supports-color": { + "node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "supports-preserve-symlinks-flag": { + "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "tar": { - "version": "6.1.13", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.13.tgz", - "integrity": "sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==", + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", "dev": true, - "requires": { + "license": "ISC", + "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", - "minipass": "^4.0.0", + "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" }, - "dependencies": { - "fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, - "requires": { - "minipass": "^3.0.0" - }, - "dependencies": { - "minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - } - } - } + "engines": { + "node": ">=10" } }, - "tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "node_modules/tar-fs": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.0.tgz", + "integrity": "sha512-5Mty5y/sOF1YWj1J6GiBodjlDc05CUR8PKXrsnFAiSG0xA+GHeWLovaZPYUDXkH/1iKRf2+M5+OrRgzC7O9b7w==", "dev": true, - "requires": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", + "license": "MIT", + "dependencies": { "pump": "^3.0.0", - "tar-stream": "^2.1.4" + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" + } + }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/tar/node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", "dependencies": { - "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true - } + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", "dev": true, - "requires": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" + "license": "ISC", + "engines": { + "node": ">=8" } }, - "terser": { - "version": "5.16.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.1.tgz", - "integrity": "sha512-xvQfyfA1ayT0qdK47zskQgRZeWLoOQ8JQ6mIgRGVNwZKdQMU+5FkCBjmv4QjcrTzyZquRw2FVtlJSRUmMKQslw==", + "node_modules/terser": { + "version": "5.43.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz", + "integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==", "dev": true, - "requires": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.14.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" } }, - "text-table": { + "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true + "dev": true, + "license": "MIT" }, - "through": { + "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true - }, - "timm": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/timm/-/timm-1.7.1.tgz", - "integrity": "sha512-IjZc9KIotudix8bMaBW6QvMuq64BrJWFs1+4V0lXwWGQZwH+LnX87doAYhem4caOEusRP9/g6jVDQmZ8XOk1nw==", - "dev": true + "dev": true, + "license": "MIT" }, - "tiny-glob": { + "node_modules/tiny-glob": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz", "integrity": "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "globalyzer": "0.1.0", "globrex": "^0.1.2" } }, - "tinycolor2": { + "node_modules/tinycolor2": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", - "dev": true + "dev": true, + "license": "MIT" }, - "toidentifier": { + "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dev": true + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/token-types": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", + "integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tuf-js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-1.1.7.tgz", + "integrity": "sha512-i3P9Kgw3ytjELUfpuKVDNBJvk4u5bXL6gskv572mcevPbSKCV3zt3djhmlEQ65yERjIbOSncy7U4cQJaB1CBCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tufjs/models": "1.0.4", + "debug": "^4.3.4", + "make-fetch-happen": "^11.1.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/tuf-js/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/tuf-js/node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/tuf-js/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/tuf-js/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/tuf-js/node_modules/make-fetch-happen": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz", + "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==", + "dev": true, + "license": "ISC", + "dependencies": { + "agentkeepalive": "^4.2.1", + "cacache": "^17.0.0", + "http-cache-semantics": "^4.1.1", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^5.0.0", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } }, - "token-types": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", - "integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==", + "node_modules/tuf-js/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", "dev": true, - "requires": { - "@tokenizer/token": "^0.3.0", - "ieee754": "^1.2.1" + "license": "ISC", + "engines": { + "node": ">=8" } }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true - }, - "tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true - }, - "tsconfig-paths": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", - "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "node_modules/tuf-js/node_modules/minipass-fetch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz", + "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==", "dev": true, - "requires": { - "@types/json5": "^0.0.29", - "json5": "^1.0.1", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" } }, - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "dev": true + "node_modules/tuf-js/node_modules/minipass-fetch/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } }, - "tuf-js": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-1.1.1.tgz", - "integrity": "sha512-WTp382/PR96k0dI4GD5RdiRhgOU0rAC7+lnoih/5pZg3cyb3aNMqDozleEEWwyfT3+FOg7Qz9JU3n6A44tLSHw==", + "node_modules/tuf-js/node_modules/socks-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", + "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", "dev": true, - "requires": { - "@tufjs/models": "1.0.0", - "make-fetch-happen": "^11.0.1" - }, + "license": "MIT", "dependencies": { - "lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true - }, - "make-fetch-happen": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.0.3.tgz", - "integrity": "sha512-oPLh5m10lRNNZDjJ2kP8UpboUx2uFXVaVweVe/lWut4iHWcQEmfqSVJt2ihZsFI8HbpwyyocaXbCAWf0g1ukIA==", - "dev": true, - "requires": { - "agentkeepalive": "^4.2.1", - "cacache": "^17.0.0", - "http-cache-semantics": "^4.1.1", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^4.0.0", - "minipass-fetch": "^3.0.0", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", - "ssri": "^10.0.0" - } - }, - "minipass-fetch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.1.tgz", - "integrity": "sha512-t9/wowtf7DYkwz8cfMSt0rMwiyNIBXf5CKZ3S5ZMqRqMYT0oLTp0x1WorMI9WTwvaPg21r1JbFxJMum8JrLGfw==", - "dev": true, - "requires": { - "encoding": "^0.1.13", - "minipass": "^4.0.0", - "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" - } - } + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" } }, - "type-check": { + "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" } }, - "type-fest": { + "node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "type-is": { + "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" } }, - "typed-array-length": { + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", - "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", "dev": true, - "requires": { - "call-bind": "^1.0.2", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "is-typed-array": "^1.1.9" + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", "dev": true, - "requires": { - "call-bind": "^1.0.2", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "unbzip2-stream": { + "node_modules/unbzip2-stream": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "buffer": "^5.2.1", "through": "^2.3.8" } }, - "unique-filename": { + "node_modules/underscore": { + "version": "1.13.7", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", + "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==", + "dev": true, + "license": "MIT" + }, + "node_modules/unique-filename": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", "dev": true, - "requires": { + "license": "ISC", + "dependencies": { "unique-slug": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "unique-slug": { + "node_modules/unique-slug": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", "dev": true, - "requires": { + "license": "ISC", + "dependencies": { "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" } }, - "unpipe": { + "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } }, - "update-browserslist-db": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", - "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", "dev": true, - "requires": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" } }, - "uri-js": { + "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, - "requires": { + "license": "BSD-2-Clause", + "dependencies": { "punycode": "^2.1.0" } }, - "utif2": { + "node_modules/urlpattern-polyfill": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", + "dev": true, + "license": "MIT" + }, + "node_modules/utif2": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/utif2/-/utif2-4.1.0.tgz", "integrity": "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "pako": "^1.0.11" } }, - "util-deprecate": { + "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "dev": true, + "license": "MIT" }, - "utils-merge": { + "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "dev": true + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } }, - "validate-npm-package-license": { + "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, - "requires": { + "license": "Apache-2.0", + "dependencies": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" } }, - "validate-npm-package-name": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.0.tgz", - "integrity": "sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ==", + "node_modules/validate-npm-package-name": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", + "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", "dev": true, - "requires": { - "builtins": "^5.0.0" + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "vary": { + "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "dev": true - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true - }, - "whatwg-fetch": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", - "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==", - "dev": true + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", "dev": true, - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" } }, - "which": { + "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, - "requires": { - "isexe": "^2.0.0" + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "which-boxed-primitive": { + "node_modules/which-collection": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", "dev": true, - "requires": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "which-typed-array": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", - "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", "dev": true, - "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "wide-align": { + "node_modules/wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", "dev": true, - "requires": { + "license": "ISC", + "dependencies": { "string-width": "^1.0.2 || 2 || 3 || 4" } }, - "widest-line": { + "node_modules/wide-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wide-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/widest-line": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "string-width": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true + "node_modules/widest-line/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/widest-line/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } }, - "wrap-ansi": { + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "wrappy": { + "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", "dev": true, - "requires": {} + "license": "ISC" }, - "xhr": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", - "integrity": "sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==", + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "dev": true, - "requires": { - "global": "~4.4.0", - "is-function": "^1.0.1", - "parse-headers": "^2.0.0", - "xtend": "^4.0.0" + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, - "xml-parse-from-string": { + "node_modules/xml-parse-from-string": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz", "integrity": "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==", - "dev": true + "dev": true, + "license": "MIT" }, - "xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "node_modules/xml2js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" } }, - "xmlbuilder": { + "node_modules/xmlbuilder": { "version": "11.0.1", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "dev": true + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0" + } }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true + "node_modules/xmlcreate": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", + "dev": true, + "license": "Apache-2.0" }, - "y18n": { + "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } }, - "yallist": { + "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "dev": true, + "license": "ISC" }, - "yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", @@ -12567,29 +10846,76 @@ "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" } }, - "yargs-parser": { + "node_modules/yargs-parser": { "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" }, - "yauzl": { + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, - "yocto-queue": { + "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.67", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz", + "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index 0d8a3f676e905f..88eef02a6c8fba 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { "name": "three", - "version": "0.153.0", + "version": "0.178.0", "description": "JavaScript 3D library", "type": "module", - "main": "./build/three.js", + "main": "./build/three.cjs", "module": "./build/three.module.js", "exports": { ".": { @@ -12,15 +12,19 @@ }, "./examples/fonts/*": "./examples/fonts/*", "./examples/jsm/*": "./examples/jsm/*", + "./addons": "./examples/jsm/Addons.js", "./addons/*": "./examples/jsm/*", "./src/*": "./src/*", - "./nodes": "./examples/jsm/nodes/Nodes.js" + "./webgpu": "./build/three.webgpu.js", + "./tsl": "./build/three.tsl.js" }, "repository": { "type": "git", "url": "https://github.com/mrdoob/three.js" }, - "sideEffects": false, + "sideEffects": [ + "./src/nodes/**/*" + ], "files": [ "build", "examples/jsm", @@ -40,10 +44,14 @@ ], "scripts": { "start": "npm run dev", - "test": "npm run lint && npm run test-unit", + "test": "npm run lint && npm run test-unit && npm run test-unit-addons", "build": "rollup -c utils/build/rollup.config.js", "build-module": "rollup -c utils/build/rollup.config.js --configOnlyModule", - "dev": "concurrently --names \"ROLLUP,HTTP\" -c \"bgBlue.bold,bgGreen.bold\" \"rollup -c utils/build/rollup.config.js -w -m inline\" \"servez -p 8080 --ssl\"", + "build-docs": "jsdoc -c utils/docs/jsdoc.config.json", + "dev": "node utils/build/dev.js && servez -p 8080", + "dev-ssl": "node utils/build/dev.js && servez -p 8080 --ssl", + "preview": "concurrently --names \"ROLLUP,HTTP\" -c \"bgBlue.bold,bgGreen.bold\" \"rollup -c utils/build/rollup.config.js -w -m inline\" \"servez -p 8080\"", + "preview-ssl": "concurrently --names \"ROLLUP,HTTPS\" -c \"bgBlue.bold,bgGreen.bold\" \"rollup -c utils/build/rollup.config.js -w -m inline\" \"servez -p 8080 --ssl\"", "lint-core": "eslint src", "lint-addons": "eslint examples/jsm --ext .js --ignore-pattern libs --ignore-pattern ifc", "lint-examples": "eslint examples --ext .html", @@ -52,13 +60,16 @@ "lint-playground": "eslint playground --ignore-pattern libs", "lint-manual": "eslint manual --ignore-pattern 3rdparty --ignore-pattern prettify.js --ignore-pattern shapefile.js", "lint-test": "eslint test --ignore-pattern vendor", - "lint-utils": "eslint utils", + "lint-utils": "eslint utils --ignore-pattern prettify --ignore-pattern fuse", "lint": "npm run lint-core", "lint-fix": "npm run lint-core -- --fix && npm run lint-addons -- --fix && npm run lint-examples -- --fix && npm run lint-docs -- --fix && npm run lint-editor -- --fix && npm run lint-playground -- --fix && npm run lint-manual -- --fix && npm run lint-test -- --fix && npm run lint-utils -- --fix", "test-unit": "qunit -r failonlyreporter -f !-webonly test/unit/three.source.unit.js", + "test-unit-addons": "qunit -r failonlyreporter -f !-webonly test/unit/three.addons.unit.js", "test-e2e": "node test/e2e/puppeteer.js", "test-e2e-cov": "node test/e2e/check-coverage.js", + "test-e2e-webgpu": "node test/e2e/puppeteer.js --webgpu", "test-treeshake": "rollup -c test/rollup.treeshake.config.js", + "test-circular-deps": "dpdm --no-warning --no-tree --exit-code circular:1 src/nodes/Nodes.js", "make-screenshot": "node test/e2e/puppeteer.js --make" }, "keywords": [ @@ -84,25 +95,27 @@ }, "homepage": "https://threejs.org/", "devDependencies": { - "@rollup/plugin-node-resolve": "^15.0.1", + "@rollup/plugin-node-resolve": "^16.0.0", "@rollup/plugin-terser": "^0.4.0", "chalk": "^5.2.0", - "concurrently": "^8.0.1", + "concurrently": "^9.0.0", + "dpdm": "^3.14.0", "eslint": "^8.37.0", "eslint-config-mdcs": "^5.0.0", - "eslint-plugin-compat": "^4.1.2", - "eslint-plugin-html": "^7.1.0", + "eslint-plugin-compat": "^6.0.0", + "eslint-plugin-html": "^8.0.0", "eslint-plugin-import": "^2.27.5", "failonlyreporter": "^1.0.0", - "jimp": "^0.22.7", + "jimp": "^1.6.0", + "jsdoc": "^4.0.4", "magic-string": "^0.30.0", - "pixelmatch": "^5.3.0", - "puppeteer-core": "^19.8.1", + "pixelmatch": "^7.0.0", + "puppeteer": "^22.0.0", "qunit": "^2.19.4", - "rollup": "^3.20.2", + "rollup": "^4.6.0", "rollup-plugin-filesize": "^10.0.0", - "rollup-plugin-visualizer": "^5.9.0", - "servez": "^1.14.2" + "rollup-plugin-visualizer": "^6.0.0", + "servez": "^2.2.4" }, "overrides": { "jpeg-js": "^0.4.4" diff --git a/playground/BaseNodeEditor.js b/playground/BaseNodeEditor.js index 607d03fb3c36f4..79723bf8f08e3a 100644 --- a/playground/BaseNodeEditor.js +++ b/playground/BaseNodeEditor.js @@ -1,5 +1,6 @@ import { Node, ButtonInput, TitleElement, ContextMenu } from 'flow'; -import { exportJSON, onValidNode, getColorFromValue, getTypeFromValue, getColorFromType } from './NodeEditorUtils.js'; +import { exportJSON, onValidNode } from './NodeEditorUtils.js'; +import { setOutputAestheticsFromNode, getColorFromNode, getLengthFromNode } from './DataTypeLib.js'; export class BaseNodeEditor extends Node { @@ -15,12 +16,11 @@ export class BaseNodeEditor extends Node { this.setWidth( width ); - this.outputLength = 1; - const title = new TitleElement( name ) .setObjectCallback( getObjectCallback ) - .setSerializable( false ) - .setOutput( this.outputLength ); + .setSerializable( false ); + + setOutputAestheticsFromNode( title, value ); const contextButton = new ButtonInput().onClick( () => { @@ -78,19 +78,15 @@ export class BaseNodeEditor extends Node { this.onValidElement = onValidNode; - this.updateOutputConnection(); - - } - - getOutputType() { - - return getTypeFromValue( this.value ); + this.outputLength = getLengthFromNode( value ); } getColor() { - return ( getColorFromType( this.getOutputType() ) || '#777777' ) + 'BB'; + const color = getColorFromNode( this.value ); + + return color ? color + 'BB' : null; } @@ -162,16 +158,6 @@ export class BaseNodeEditor extends Node { } - setOutputLength( value ) { - - this.outputLength = value; - - this.updateOutputConnection(); - - return; - - } - setObjectCallback( callback ) { this.title.setObjectCallback( callback ); @@ -194,14 +180,6 @@ export class BaseNodeEditor extends Node { } - updateOutputConnection() { - - this.title.setOutputColor( getColorFromValue( this.value ) ); - - this.title.setOutput( this.value ? this.outputLength : 0 ); - - } - invalidate() { this.title.dispatchEvent( new Event( 'connect' ) ); diff --git a/playground/DataTypeLib.js b/playground/DataTypeLib.js new file mode 100644 index 00000000000000..606adec2b27f75 --- /dev/null +++ b/playground/DataTypeLib.js @@ -0,0 +1,166 @@ +export const typeToLengthLib = { + // gpu + string: 1, + float: 1, + bool: 1, + vec2: 2, + vec3: 3, + vec4: 4, + color: 3, + mat2: 1, + mat3: 1, + mat4: 1, + // cpu + String: 1, + Number: 1, + Vector2: 2, + Vector3: 3, + Vector4: 4, + Color: 3, + // cpu: other stuff + Material: 1, + Object3D: 1, + CodeNode: 1, + Texture: 1, + URL: 1, + node: 1 +}; + +export const defaultLength = 1; + +export function getLengthFromType( type ) { + + return typeToLengthLib[ type ] || defaultLength; + +} + +export function getLengthFromNode( value ) { + + const type = getTypeFromNode( value ); + + return getLengthFromType( type ); + +} + +export const typeToColorLib = { + // gpu + string: '#ff0000', + float: '#eeeeee', + bool: '#0060ff', + mat2: '#d0dc8b', + mat3: '#d0dc8b', + mat4: '#d0dc8b', + // cpu + String: '#ff0000', + Number: '#eeeeee', + // cpu: other stuff + Material: '#228b22', + Object3D: '#00a1ff', + CodeNode: '#ff00ff', + Texture: '#ffa500', + URL: '#ff0080' +}; + +export function getColorFromType( type ) { + + return typeToColorLib[ type ] || null; + +} + +export function getColorFromNode( value ) { + + const type = getTypeFromNode( value ); + + return getColorFromType( type ); + +} + +function getTypeFromNode( value ) { + + if ( value ) { + + if ( value.isMaterial ) return 'Material'; + + return value.nodeType === 'ArrayBuffer' ? 'URL' : ( value.nodeType || getTypeFromValue( value.value ) ); + + } + +} + +function getTypeFromValue( value ) { + + if ( value && value.isScriptableValueNode ) value = value.value; + if ( ! value ) return; + + if ( value.isNode && value.nodeType === 'string' ) return 'string'; + if ( value.isNode && value.nodeType === 'ArrayBuffer' ) return 'URL'; + + for ( const type of Object.keys( typeToLengthLib ).reverse() ) { + + if ( value[ 'is' + type ] === true ) return type; + + } + +} + +export function setInputAestheticsFromType( element, type ) { + + element.setInput( getLengthFromType( type ) ); + + const color = getColorFromType( type ); + + if ( color ) { + + element.setInputColor( color ); + + } + + return element; + +} + +export function setOutputAestheticsFromNode( element, node ) { + + if ( ! node ) { + + element.setOutput( 0 ); + + return element; + + } + + return setOutputAestheticsFromType( element, getTypeFromNode( node ) ); + +} + +export function setOutputAestheticsFromType( element, type ) { + + if ( ! type ) { + + element.setOutput( 1 ); + + return element; + + } + + if ( type == 'void' ) { + + element.setOutput( 0 ); + + return element; + + } + + element.setOutput( getLengthFromType( type ) ); + + const color = getColorFromType( type ); + + if ( color ) { + + element.setOutputColor( color ); + + } + + return element; + +} diff --git a/playground/NodeEditor.js b/playground/NodeEditor.js index 05a084a1f3769c..44a01dc0703a3e 100644 --- a/playground/NodeEditor.js +++ b/playground/NodeEditor.js @@ -1,9 +1,10 @@ import * as THREE from 'three'; -import * as Nodes from 'three/nodes'; +import * as TSL from 'three/tsl'; import { Canvas, CircleMenu, ButtonInput, StringInput, ContextMenu, Tips, Search, Loader, Node, TreeViewNode, TreeViewInput, Element } from 'flow'; import { FileEditor } from './editors/FileEditor.js'; import { exportJSON } from './NodeEditorUtils.js'; import { init, ClassLib, getNodeEditorClass, getNodeList } from './NodeEditorLib.js'; +import { SplitscreenManager } from './SplitscreenManager.js'; init(); @@ -23,14 +24,14 @@ export class NodeEditor extends THREE.EventDispatcher { this.scene = scene; this.renderer = renderer; - const { global } = Nodes; + const { ScriptableNodeResources } = TSL; - global.set( 'THREE', THREE ); - global.set( 'TSL', Nodes ); + ScriptableNodeResources.set( 'THREE', THREE ); + ScriptableNodeResources.set( 'TSL', TSL ); - global.set( 'scene', scene ); - global.set( 'renderer', renderer ); - global.set( 'composer', composer ); + ScriptableNodeResources.set( 'scene', scene ); + ScriptableNodeResources.set( 'renderer', renderer ); + ScriptableNodeResources.set( 'composer', composer ); this.nodeClasses = []; @@ -38,6 +39,7 @@ export class NodeEditor extends THREE.EventDispatcher { this.domElement = domElement; this._preview = false; + this._splitscreen = false; this.search = null; @@ -47,6 +49,7 @@ export class NodeEditor extends THREE.EventDispatcher { this.nodesContext = null; this.examplesContext = null; + this._initSplitview(); this._initUpload(); this._initTips(); this._initMenu(); @@ -113,6 +116,10 @@ export class NodeEditor extends THREE.EventDispatcher { if ( value ) { + this._wasSplitscreen = this.splitscreen; + + this.splitscreen = false; + this.menu.dom.remove(); this.canvas.dom.remove(); this.search.dom.remove(); @@ -129,6 +136,12 @@ export class NodeEditor extends THREE.EventDispatcher { this.previewMenu.dom.remove(); + if ( this._wasSplitscreen == true ) { + + this.splitscreen = true; + + } + } this._preview = value; @@ -141,6 +154,22 @@ export class NodeEditor extends THREE.EventDispatcher { } + set splitscreen( value ) { + + if ( this._splitscreen === value ) return; + + this.splitview.setSplitview( value ); + + this._splitscreen = value; + + } + + get splitscreen() { + + return this._splitscreen; + + } + newProject() { const canvas = this.canvas; @@ -180,6 +209,12 @@ export class NodeEditor extends THREE.EventDispatcher { } + _initSplitview() { + + this.splitview = new SplitscreenManager( this ); + + } + _initUpload() { const canvas = this.canvas; @@ -231,6 +266,7 @@ export class NodeEditor extends THREE.EventDispatcher { previewMenu.setAlign( 'top left' ); const previewButton = new ButtonInput().setIcon( 'ti ti-brand-threejs' ).setToolTip( 'Preview' ); + const splitscreenButton = new ButtonInput().setIcon( 'ti ti-layout-sidebar-right-expand' ).setToolTip( 'Splitscreen' ); const menuButton = new ButtonInput().setIcon( 'ti ti-apps' ).setToolTip( 'Add' ); const examplesButton = new ButtonInput().setIcon( 'ti ti-file-symlink' ).setToolTip( 'Examples' ); const newButton = new ButtonInput().setIcon( 'ti ti-file' ).setToolTip( 'New' ); @@ -242,6 +278,13 @@ export class NodeEditor extends THREE.EventDispatcher { previewButton.onClick( () => this.preview = true ); editorButton.onClick( () => this.preview = false ); + splitscreenButton.onClick( () => { + + this.splitscreen = ! this.splitscreen; + splitscreenButton.setIcon( this.splitscreen ? 'ti ti-layout-sidebar-right-collapse' : 'ti ti-layout-sidebar-right-expand' ); + + } ); + menuButton.onClick( () => this.nodesContext.open() ); examplesButton.onClick( () => this.examplesContext.open() ); @@ -289,6 +332,7 @@ export class NodeEditor extends THREE.EventDispatcher { } ); menu.add( previewButton ) + .add( splitscreenButton ) .add( newButton ) .add( examplesButton ) .add( openButton ) @@ -297,7 +341,7 @@ export class NodeEditor extends THREE.EventDispatcher { previewMenu.add( editorButton ); - this.domElement.append( menu.dom ); + this.domElement.appendChild( menu.dom ); this.menu = menu; this.previewMenu = previewMenu; @@ -348,46 +392,13 @@ export class NodeEditor extends THREE.EventDispatcher { // EXAMPLES //**************// - addExamples( 'Universal', [ + addExamples( 'Basic', [ 'Teapot', 'Matcap', - 'Fresnel' + 'Fresnel', + 'Particles' ] ); - if ( this.renderer.isWebGLRenderer ) { - - addExamples( 'WebGL', [ - 'Car' - ] ); - - context.add( new ButtonInput( 'WebGPU Version' ).onClick( () => { - - if ( confirm( 'Are you sure?' ) === true ) { - - window.location.search = '?backend=webgpu'; - - } - - } ) ); - - } else if ( this.renderer.isWebGPURenderer ) { - - addExamples( 'WebGPU', [ - 'Particle' - ] ); - - context.add( new ButtonInput( 'WebGL Version' ).onClick( () => { - - if ( confirm( 'Are you sure?' ) === true ) { - - window.location.search = ''; - - } - - } ) ); - - } - this.examplesContext = context; } @@ -411,6 +422,14 @@ export class NodeEditor extends THREE.EventDispatcher { this.preview = ! this.preview; + } else if ( key === 'Delete' ) { + + if ( this.canvas.selected ) this.canvas.selected.dispose(); + + } else if ( key === 'Escape' ) { + + this.canvas.select( null ); + } } @@ -423,7 +442,7 @@ export class NodeEditor extends THREE.EventDispatcher { const urlParams = new URLSearchParams( window.location.search ); - const example = urlParams.get( 'example' ) || 'universal/teapot'; + const example = urlParams.get( 'example' ) || 'basic/teapot'; this.loadURL( `./examples/${example}.json` ); @@ -682,7 +701,7 @@ export class NodeEditor extends THREE.EventDispatcher { const visible = buttonLabel.indexOf( value ) !== - 1; - if ( visible && button.parent !== null ) { + if ( visible && button.children.length === 0 ) { nodeButtonsVisible.push( button ); diff --git a/playground/NodeEditorUtils.js b/playground/NodeEditorUtils.js index e55046f4a5452e..a09f2fda2f7bdc 100644 --- a/playground/NodeEditorUtils.js +++ b/playground/NodeEditorUtils.js @@ -1,5 +1,7 @@ import { StringInput, NumberInput, ColorInput, Element, LabelElement } from 'flow'; -import { string, float, vec2, vec3, vec4, color } from 'three/nodes'; +import { string, float, vec2, vec3, vec4, color } from 'three/tsl'; +import { Color } from 'three'; +import { setInputAestheticsFromType, setOutputAestheticsFromType } from './DataTypeLib.js'; export function exportJSON( object, name ) { @@ -40,63 +42,40 @@ export function disposeScene( scene ) { } -export function disposeMaterial( material ) { - - material.dispose(); - - for ( const key of Object.keys( material ) ) { - - const value = material[ key ]; +export function resetScene( scene ) { - if ( value && typeof value === 'object' && typeof value.dispose === 'function' ) { - - value.dispose(); + if ( scene.environment !== null ) { - } + scene.environment.dispose(); + scene.environment = null; } -} + if ( scene.background !== null ) { -export const colorLib = { - // gpu - string: '#ff0000', - // cpu - Material: '#228b22', - Object3D: '#00a1ff', - CodeNode: '#ff00ff', - Texture: '#ffa500', - URL: '#ff0080', - String: '#ff0000' -}; + if ( scene.background.isTexture ) scene.background.dispose(); -export function getColorFromType( type ) { + } - return colorLib[ type ]; + scene.background = new Color( 0x333333 ); } -export function getTypeFromValue( value ) { - - if ( value && value.isScriptableValueNode ) value = value.value; - if ( ! value ) return; - - if ( value.isNode && value.nodeType === 'string' ) return 'string'; - if ( value.isNode && value.nodeType === 'ArrayBuffer' ) return 'URL'; +export function disposeMaterial( material ) { - for ( const type of Object.keys( colorLib ).reverse() ) { + material.dispose(); - if ( value[ 'is' + type ] === true ) return type; + for ( const key of Object.keys( material ) ) { - } + const value = material[ key ]; -} + if ( value && typeof value === 'object' && typeof value.dispose === 'function' ) { -export function getColorFromValue( value ) { + value.dispose(); - const type = getTypeFromValue( value ); + } - return getColorFromType( type ); + } } @@ -306,9 +285,8 @@ export function createElementFromJSON( json ) { if ( inputType && json.inputConnection !== false ) { - element.setInputColor( getColorFromType( inputType ) ); + setInputAestheticsFromType( element, inputType ); //element.setInputStyle( 'dotted' ); // 'border-style: dotted;' - element.setInput( 1 ); element.onValid( onValidType( inputType ) ); @@ -316,9 +294,8 @@ export function createElementFromJSON( json ) { if ( outputType ) { - element.setInputColor( getColorFromType( outputType ) ); + setOutputAestheticsFromType( element, outputType ); //element.setInputStyle( 'dotted' ); // 'border-style: dotted;' - element.setOutput( 1 ); } diff --git a/playground/Nodes.json b/playground/Nodes.json index 9d694323b4d7c4..a9f42c6558dc65 100644 --- a/playground/Nodes.json +++ b/playground/Nodes.json @@ -57,7 +57,7 @@ { "name": "Camera Normal Matrix", "icon": "video", - "nodeType": "mat4", + "nodeType": "mat3", "shaderNode": "cameraNormalMatrix" }, { @@ -89,90 +89,6 @@ } ] }, - { - "name": "Material Elements", - "icon": "circles-filled", - "children": [ - { - "name": "Material Alpha Test", - "icon": "circle-filled", - "description": "Return the `alphaTest` value of the Material.", - "nodeType": "float", - "shaderNode": "materialAlphaTest" - }, - { - "name": "Material Color", - "icon": "circle-filled", - "description": "Return the `emissive * emissiveMap` value of the Material", - "nodeType": "color", - "shaderNode": "materialColor" - }, - { - "name": "Material Emissive", - "icon": "circle-filled", - "description": "Return the `emissive * emissiveMap` value of the Material.", - "nodeType": "color", - "shaderNode": "materialEmissive" - }, - { - "name": "Material Metalness", - "icon": "circle-filled", - "description": "Return the `metalness * metalnessMap.b` value of the Material.", - "nodeType": "float", - "shaderNode": "materialMetalness" - }, - { - "name": "Material Normal", - "icon": "circle-filled", - "nodeType": "float", - "shaderNode": "materialNormal" - }, - { - "name": "Material Opacity", - "icon": "circle-filled", - "description": "Return the `opacity * alphaMap` value of the Material.", - "nodeType": "float", - "shaderNode": "materialOpacity" - }, - { - "name": "Material Reflectivity", - "icon": "circle-filled", - "nodeType": "float", - "shaderNode": "materialReflectivity" - }, - { - "name": "Material Rotation", - "icon": "circle-filled", - "nodeType": "float", - "shaderNode": "materialRotation" - }, - { - "name": "Material Roughness", - "icon": "circle-filled", - "description": "Return the `roughness * roughnessMap.g` value of the Material.", - "nodeType": "float", - "shaderNode": "materialRoughness" - }, - { - "name": "Material Shininess", - "icon": "circle-filled", - "nodeType": "float", - "shaderNode": "materialShininess" - }, - { - "name": "Material Specular Color", - "icon": "circle-filled", - "nodeType": "color", - "shaderNode": "materialSpecularColor" - }, - { - "name": "Material UV", - "icon": "circle-filled", - "nodeType": "vec2", - "shaderNode": "materialUV" - } - ] - }, { "name": "Normal", "icon": "arrow-bar-up", @@ -316,7 +232,7 @@ { "name": "Model Normal Matrix", "icon": "box", - "nodeType": "vec3", + "nodeType": "mat3", "shaderNode": "modelNormalMatrix" }, { @@ -328,7 +244,7 @@ { "name": "Model View Matrix", "icon": "box", - "nodeType": "vec3", + "nodeType": "mat4", "shaderNode": "modelViewMatrix" }, { @@ -340,7 +256,7 @@ { "name": "Model World Matrix", "icon": "box", - "nodeType": "vec3", + "nodeType": "mat4", "shaderNode": "modelWorldMatrix" } ] @@ -364,6 +280,7 @@ "name": "Object Normal Matrix", "icon": "3d-cube-sphere", "shaderNode": "objectNormalMatrix", + "nodeType": "mat3", "properties": [ { "name": "object3d", @@ -386,6 +303,7 @@ "name": "Object View Matrix", "icon": "3d-cube-sphere", "shaderNode": "objectViewMatrix", + "nodeType": "mat4", "properties": [ { "name": "object3d", @@ -505,6 +423,12 @@ "name": "UV", "icon": "chart-treemap", "children": [ + { + "name": "UV", + "icon": "chart-treemap", + "nodeType": "vec2", + "editorClass": "UVEditor" + }, { "name": "Matcap UV", "icon": "chart-treemap", @@ -516,12 +440,6 @@ "icon": "chart-treemap", "nodeType": "vec1", "shaderNode": "pointUV" - }, - { - "name": "UV", - "icon": "chart-treemap", - "nodeType": "vec2", - "editorClass": "UVEditor" } ] }, @@ -1304,7 +1222,7 @@ { "name": "Max", "icon": "math-function", - "description": "Returns the maximun of the two parameters.", + "description": "Returns the maximum of the two parameters.", "shaderNode": "max", "nodeType": "node", "properties": [ @@ -1451,7 +1369,7 @@ "name": "bNode", "nodeType": "node", "label": "y", - "description": "Specify the value to whitch to raise x." + "description": "Specify the value to raise x." } ] }, @@ -1560,6 +1478,21 @@ } ] }, + { + "name": "Normal Map", + "icon": "photo", + "description": "Converts a normal map value to a normal vector.", + "shaderNode": "normalMap", + "nodeType": "node", + "properties": [ + { + "name": "node", + "nodeType": "vec3", + "label": "normal", + "description": "Specifies the vector to convert." + } + ] + }, { "name": "Round", "icon": "math-function", @@ -1598,7 +1531,7 @@ "name": "aNode", "nodeType": "node", "label": "x", - "description": "Specify the value from wich to extract the sign." + "description": "Specify the value from which to extract the sign." } ] }, diff --git a/playground/SplitscreenManager.js b/playground/SplitscreenManager.js new file mode 100644 index 00000000000000..fdbeacf391c1bb --- /dev/null +++ b/playground/SplitscreenManager.js @@ -0,0 +1,91 @@ +export class SplitscreenManager { + + constructor( editor ) { + + this.editor = editor; + this.renderer = editor.renderer; + this.composer = editor.composer; + + this.gutter = null; + this.gutterMoving = false; + this.gutterOffset = 0.6; + + } + + setSplitview( value ) { + + const nodeDOM = this.editor.domElement; + const rendererContainer = this.renderer.domElement.parentNode; + + if ( value ) { + + this.addGutter( rendererContainer, nodeDOM ); + + } else { + + this.removeGutter( rendererContainer, nodeDOM ); + + } + + } + + addGutter( rendererContainer, nodeDOM ) { + + rendererContainer.style[ 'z-index' ] = 20; + + this.gutter = document.createElement( 'f-gutter' ); + + nodeDOM.parentNode.appendChild( this.gutter ); + + const onGutterMovement = () => { + + const offset = this.gutterOffset; + + this.gutter.style[ 'left' ] = 100 * offset + '%'; + rendererContainer.style[ 'left' ] = 100 * offset + '%'; + rendererContainer.style[ 'width' ] = 100 * ( 1 - offset ) + '%'; + nodeDOM.style[ 'width' ] = 100 * offset + '%'; + + }; + + this.gutter.addEventListener( 'mousedown', () => { + + this.gutterMoving = true; + + } ); + + document.addEventListener( 'mousemove', ( event ) => { + + if ( this.gutter && this.gutterMoving ) { + + this.gutterOffset = Math.max( 0, Math.min( 1, event.clientX / window.innerWidth ) ); + onGutterMovement(); + + } + + } ); + + document.addEventListener( 'mouseup', () => { + + this.gutterMoving = false; + + } ); + + onGutterMovement(); + + } + + removeGutter( rendererContainer, nodeDOM ) { + + rendererContainer.style[ 'z-index' ] = 0; + + this.gutter.remove(); + this.gutter = null; + + rendererContainer.style[ 'left' ] = '0%'; + rendererContainer.style[ 'width' ] = '100%'; + nodeDOM.style[ 'width' ] = '100%'; + + } + +} diff --git a/playground/editors/BasicMaterialEditor.js b/playground/editors/BasicMaterialEditor.js index b346f9b69b31da..759547bc5584d4 100644 --- a/playground/editors/BasicMaterialEditor.js +++ b/playground/editors/BasicMaterialEditor.js @@ -1,6 +1,7 @@ import { ColorInput, SliderInput, LabelElement } from 'flow'; import { MaterialEditor } from './MaterialEditor.js'; -import { MeshBasicNodeMaterial } from 'three/nodes'; +import { MeshBasicNodeMaterial } from 'three/webgpu'; +import { setInputAestheticsFromType } from '../DataTypeLib.js'; export class BasicMaterialEditor extends MaterialEditor { @@ -10,9 +11,9 @@ export class BasicMaterialEditor extends MaterialEditor { super( 'Basic Material', material ); - const color = new LabelElement( 'color' ).setInput( 3 ); - const opacity = new LabelElement( 'opacity' ).setInput( 1 ); - const position = new LabelElement( 'position' ).setInput( 3 ); + const color = setInputAestheticsFromType( new LabelElement( 'color' ), 'Color' ); + const opacity = setInputAestheticsFromType( new LabelElement( 'opacity' ), 'Number' ); + const position = setInputAestheticsFromType( new LabelElement( 'position' ), 'Vector3' ); color.add( new ColorInput( material.color.getHex() ).onChange( ( input ) => { diff --git a/playground/editors/ColorEditor.js b/playground/editors/ColorEditor.js index f32990e933a235..f626872b1304ec 100644 --- a/playground/editors/ColorEditor.js +++ b/playground/editors/ColorEditor.js @@ -1,7 +1,7 @@ import { ColorInput, StringInput, NumberInput, LabelElement, Element } from 'flow'; import { BaseNodeEditor } from '../BaseNodeEditor.js'; import { Color } from 'three'; -import { UniformNode } from 'three/nodes'; +import { UniformNode } from 'three/webgpu'; export class ColorEditor extends BaseNodeEditor { @@ -12,8 +12,6 @@ export class ColorEditor extends BaseNodeEditor { super( 'Color', node ); - this.setOutputLength( 3 ); - const updateFields = ( editing ) => { const value = node.value; diff --git a/playground/editors/CustomNodeEditor.js b/playground/editors/CustomNodeEditor.js index f33a0852ed2be5..df359b8f888731 100644 --- a/playground/editors/CustomNodeEditor.js +++ b/playground/editors/CustomNodeEditor.js @@ -1,9 +1,10 @@ import { LabelElement } from 'flow'; import { Color, Vector2, Vector3, Vector4 } from 'three'; -import * as Nodes from 'three/nodes'; -import { uniform } from 'three/nodes'; +import * as Nodes from 'three/tsl'; +import { uniform } from 'three/tsl'; import { BaseNodeEditor } from '../BaseNodeEditor.js'; import { createInputLib } from '../NodeEditorUtils.js'; +import { setInputAestheticsFromType } from '../DataTypeLib.js'; const typeToValue = { 'color': Color, @@ -33,7 +34,7 @@ const createElementFromProperty = ( node, property ) => { node[ property.name ] = defaultValue; - const element = new LabelElement( label ).setInput( property.defaultLength || 1 ); + const element = setInputAestheticsFromType( new LabelElement( label ), nodeType ); if ( createInputLib[ nodeType ] !== undefined ) { @@ -79,6 +80,8 @@ export class CustomNodeEditor extends BaseNodeEditor { } + node.nodeType = node.nodeType || settings.nodeType; + super( settings.name, node, 300 ); this.title.setIcon( 'ti ti-' + settings.icon ); diff --git a/playground/editors/FileEditor.js b/playground/editors/FileEditor.js index 960acce7fdfa45..66819ca6a4246c 100644 --- a/playground/editors/FileEditor.js +++ b/playground/editors/FileEditor.js @@ -1,6 +1,7 @@ import { StringInput, Element } from 'flow'; import { BaseNodeEditor } from '../BaseNodeEditor.js'; -import { arrayBuffer, NodeUtils } from 'three/nodes'; +import { NodeUtils } from 'three/webgpu'; +import { arrayBuffer } from 'three/tsl'; export class FileEditor extends BaseNodeEditor { diff --git a/playground/editors/FloatEditor.js b/playground/editors/FloatEditor.js index 20193ce75052e7..e0f77af9d56205 100644 --- a/playground/editors/FloatEditor.js +++ b/playground/editors/FloatEditor.js @@ -12,8 +12,6 @@ export class FloatEditor extends BaseNodeEditor { super( 'Float', inputNode, 150 ); - this.setOutputLength( 1 ); - element.addEventListener( 'changeInput', () => this.invalidate() ); this.add( element ); diff --git a/playground/editors/JavaScriptEditor.js b/playground/editors/JavaScriptEditor.js index 51a8c2c4589f7d..b4a1f2d39ec549 100644 --- a/playground/editors/JavaScriptEditor.js +++ b/playground/editors/JavaScriptEditor.js @@ -1,6 +1,6 @@ import { BaseNodeEditor } from '../BaseNodeEditor.js'; import { CodeEditorElement } from '../elements/CodeEditorElement.js'; -import { js } from 'three/nodes'; +import { js } from 'three/tsl'; export class JavaScriptEditor extends BaseNodeEditor { diff --git a/playground/editors/JoinEditor.js b/playground/editors/JoinEditor.js index 1f8b16062bef69..c27a1d66cb8c49 100644 --- a/playground/editors/JoinEditor.js +++ b/playground/editors/JoinEditor.js @@ -1,6 +1,8 @@ import { LabelElement } from 'flow'; import { BaseNodeEditor } from '../BaseNodeEditor.js'; -import { JoinNode, UniformNode, float } from 'three/nodes'; +import { JoinNode, UniformNode } from 'three/webgpu'; +import { float } from 'three/tsl'; +import { setInputAestheticsFromType } from '../DataTypeLib.js'; const NULL_VALUE = new UniformNode( 0 ); @@ -39,12 +41,14 @@ export class JoinEditor extends BaseNodeEditor { this.invalidate(); + this.title.setOutput( length ); + }; - const xElement = new LabelElement( 'x | r' ).setInput( 1 ).onConnect( update ); - const yElement = new LabelElement( 'y | g' ).setInput( 1 ).onConnect( update ); - const zElement = new LabelElement( 'z | b' ).setInput( 1 ).onConnect( update ); - const wElement = new LabelElement( 'w | a' ).setInput( 1 ).onConnect( update ); + const xElement = setInputAestheticsFromType( new LabelElement( 'x | r' ), 'Number' ).onConnect( update ); + const yElement = setInputAestheticsFromType( new LabelElement( 'y | g' ), 'Number' ).onConnect( update ); + const zElement = setInputAestheticsFromType( new LabelElement( 'z | b' ), 'Number' ).onConnect( update ); + const wElement = setInputAestheticsFromType( new LabelElement( 'w | a' ), 'Number' ).onConnect( update ); this.add( xElement ) .add( yElement ) diff --git a/playground/editors/NodePrototypeEditor.js b/playground/editors/NodePrototypeEditor.js index 31d764f9c36d72..8f98d501c5c749 100644 --- a/playground/editors/NodePrototypeEditor.js +++ b/playground/editors/NodePrototypeEditor.js @@ -1,6 +1,6 @@ import { JavaScriptEditor } from './JavaScriptEditor.js'; import { ScriptableEditor } from './ScriptableEditor.js'; -import { scriptable } from 'three/nodes'; +import { scriptable } from 'three/tsl'; const defaultCode = `// Addition Node Example // Enjoy! :) @@ -87,6 +87,12 @@ export class NodePrototypeEditor extends JavaScriptEditor { setEditor( editor ) { + if ( editor === null && this.editor ) { + + this.editor.removeClass( this._prototype ); + + } + super.setEditor( editor ); if ( editor === null ) { diff --git a/playground/editors/PointsMaterialEditor.js b/playground/editors/PointsMaterialEditor.js index c238f66d1e5032..c7a164cdf433ee 100644 --- a/playground/editors/PointsMaterialEditor.js +++ b/playground/editors/PointsMaterialEditor.js @@ -1,7 +1,8 @@ import { ColorInput, ToggleInput, SliderInput, LabelElement } from 'flow'; import { MaterialEditor } from './MaterialEditor.js'; -import { PointsNodeMaterial } from 'three/nodes'; +import { PointsNodeMaterial } from 'three/webgpu'; import * as THREE from 'three'; +import { setInputAestheticsFromType } from '../DataTypeLib.js'; export class PointsMaterialEditor extends MaterialEditor { @@ -11,11 +12,11 @@ export class PointsMaterialEditor extends MaterialEditor { super( 'Points Material', material ); - const color = new LabelElement( 'color' ).setInput( 3 ); - const opacity = new LabelElement( 'opacity' ).setInput( 1 ); - const size = new LabelElement( 'size' ).setInput( 1 ); - const position = new LabelElement( 'position' ).setInput( 3 ); - const sizeAttenuation = new LabelElement( 'Size Attenuation' ); + const color = setInputAestheticsFromType( new LabelElement( 'color' ), 'Color' ); + const opacity = setInputAestheticsFromType( new LabelElement( 'opacity' ), 'Number' ); + const size = setInputAestheticsFromType( new LabelElement( 'size' ), 'Number' ); + const position = setInputAestheticsFromType( new LabelElement( 'position' ), 'Vector3' ); + const sizeAttenuation = setInputAestheticsFromType( new LabelElement( 'Size Attenuation' ), 'Number' ); color.add( new ColorInput( material.color.getHex() ).onChange( ( input ) => { diff --git a/playground/editors/PreviewEditor.js b/playground/editors/PreviewEditor.js index 5e0d897f7a8401..2709fba2d3842b 100644 --- a/playground/editors/PreviewEditor.js +++ b/playground/editors/PreviewEditor.js @@ -2,8 +2,10 @@ import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; import { ViewHelper } from 'three/addons/helpers/ViewHelper.js'; import { Element, LabelElement, SelectInput } from 'flow'; import { BaseNodeEditor } from '../BaseNodeEditor.js'; -import { MeshBasicNodeMaterial, float } from 'three/nodes'; -import { WebGLRenderer, PerspectiveCamera, Scene, Mesh, DoubleSide, SphereGeometry, BoxGeometry, PlaneGeometry, TorusKnotGeometry } from 'three'; +import { MeshBasicNodeMaterial } from 'three/webgpu'; +import { vec4 } from 'three/tsl'; +import { PerspectiveCamera, Scene, Mesh, DoubleSide, SphereGeometry, BoxGeometry, PlaneGeometry, TorusKnotGeometry, WebGPURenderer } from 'three'; +import { setInputAestheticsFromType } from '../DataTypeLib.js'; const sceneDict = {}; @@ -53,10 +55,10 @@ export class PreviewEditor extends BaseNodeEditor { const width = 300; const height = 300; - super( 'Preview', null, height ); + super( 'Preview', null, width ); const material = new MeshBasicNodeMaterial(); - material.colorNode = float(); + material.colorNode = vec4( 0, 0, 0, 1 ); material.side = DoubleSide; material.transparent = true; @@ -74,9 +76,9 @@ export class PreviewEditor extends BaseNodeEditor { { name: 'Torus', value: 'torus' } ], 'box' ); - const inputElement = new LabelElement( 'Input' ).setInput( 4 ).onConnect( () => { + const inputElement = setInputAestheticsFromType( new LabelElement( 'Input' ), 'Color' ).onConnect( () => { - material.colorNode = inputElement.getLinkedObject() || float(); + material.colorNode = inputElement.getLinkedObject() || vec4( 0, 0, 0, 1 ); material.dispose(); }, true ); @@ -88,9 +90,10 @@ export class PreviewEditor extends BaseNodeEditor { previewElement.dom.addEventListener( 'wheel', e => e.stopPropagation() ); - const renderer = new WebGLRenderer( { + const renderer = new WebGPURenderer( { canvas, - alpha: true + alpha: true, + antialias: true } ); renderer.autoClear = false; @@ -139,7 +142,7 @@ export class PreviewEditor extends BaseNodeEditor { } - update() { + async update() { const { viewHelper, material, renderer, camera, sceneInput } = this; @@ -158,8 +161,8 @@ export class PreviewEditor extends BaseNodeEditor { } - renderer.clear(); - renderer.render( scene, camera ); + await renderer.clearAsync(); + await renderer.renderAsync( scene, camera ); viewHelper.render( renderer ); diff --git a/playground/editors/ScriptableEditor.js b/playground/editors/ScriptableEditor.js index f0c39d33556321..33ff26c6706325 100644 --- a/playground/editors/ScriptableEditor.js +++ b/playground/editors/ScriptableEditor.js @@ -1,7 +1,8 @@ import { BaseNodeEditor } from '../BaseNodeEditor.js'; import { CodeEditorElement } from '../elements/CodeEditorElement.js'; -import { disposeScene, getColorFromType, createElementFromJSON, isGPUNode, onValidType } from '../NodeEditorUtils.js'; -import { global, scriptable, js, scriptableValue } from 'three/nodes'; +import { disposeScene, resetScene, createElementFromJSON, isGPUNode, onValidType } from '../NodeEditorUtils.js'; +import { ScriptableNodeResources, scriptable, js, scriptableValue } from 'three/tsl'; +import { getColorFromType, setInputAestheticsFromType, setOutputAestheticsFromType } from '../DataTypeLib.js'; const defaultTitle = 'Scriptable'; const defaultWidth = 500; @@ -67,9 +68,11 @@ export class ScriptableEditor extends BaseNodeEditor { } - getOutputType() { + getColor() { - return this.layout ? this.layout.outputType : null; + const color = getColorFromType( this.layout ? this.layout.outputType : null ); + + return color ? color + 'BB' : null; } @@ -170,8 +173,7 @@ export class ScriptableEditor extends BaseNodeEditor { const outputType = layout.outputType; - this.title.setOutputColor( getColorFromType( outputType ) ); - this.title.setOutput( outputType && outputType !== 'void' ? this.outputLength : 0 ); + setOutputAestheticsFromType( this.title, outputType ); } else { @@ -187,8 +189,8 @@ export class ScriptableEditor extends BaseNodeEditor { if ( editor && editorOutput === editorOutputAdded ) return; - const scene = global.get( 'scene' ); - const composer = global.get( 'composer' ); + const scene = ScriptableNodeResources.get( 'scene' ); + const composer = ScriptableNodeResources.get( 'composer' ); if ( editor ) { @@ -198,6 +200,8 @@ export class ScriptableEditor extends BaseNodeEditor { disposeScene( editorOutputAdded ); + resetScene( scene ); + } else if ( composer && editorOutputAdded && editorOutputAdded.isPass === true ) { composer.removePass( editorOutputAdded ); @@ -224,6 +228,8 @@ export class ScriptableEditor extends BaseNodeEditor { disposeScene( editorOutputAdded ); + resetScene( scene ); + } else if ( composer && editorOutputAdded && editorOutputAdded.isPass === true ) { composer.removePass( editorOutputAdded ); @@ -406,7 +412,7 @@ export class ScriptableEditor extends BaseNodeEditor { _initExternalConnection() { - this.title.setInputColor( getColorFromType( 'CodeNode' ) ).setInput( 1 ).onValid( onValidType( 'CodeNode' ) ).onConnect( () => { + setInputAestheticsFromType( this.title, 'CodeNode' ).onValid( onValidType( 'CodeNode' ) ).onConnect( () => { this.hasExternalEditor ? this._toExternal() : this._toInternal(); diff --git a/playground/editors/SliderEditor.js b/playground/editors/SliderEditor.js index 5ac0379a601635..ca7d0ef58d058b 100644 --- a/playground/editors/SliderEditor.js +++ b/playground/editors/SliderEditor.js @@ -1,6 +1,6 @@ import { ButtonInput, SliderInput, NumberInput, LabelElement, Element } from 'flow'; import { BaseNodeEditor } from '../BaseNodeEditor.js'; -import { float } from 'three/nodes'; +import { float } from 'three/tsl'; export class SliderEditor extends BaseNodeEditor { diff --git a/playground/editors/SplitEditor.js b/playground/editors/SplitEditor.js index 02d7c4f335191f..65ddce5b487be7 100644 --- a/playground/editors/SplitEditor.js +++ b/playground/editors/SplitEditor.js @@ -1,6 +1,7 @@ import { LabelElement } from 'flow'; import { BaseNodeEditor } from '../BaseNodeEditor.js'; -import { nodeObject, float } from 'three/nodes'; +import { nodeObject, float } from 'three/tsl'; +import { setInputAestheticsFromType, setOutputAestheticsFromType } from '../DataTypeLib.js'; export class SplitEditor extends BaseNodeEditor { @@ -10,7 +11,7 @@ export class SplitEditor extends BaseNodeEditor { let node = null; - const inputElement = new LabelElement( 'Input' ).setInput( 1 ).onConnect( () => { + const inputElement = setInputAestheticsFromType( new LabelElement( 'Input' ), 'node' ).onConnect( () => { node = inputElement.getLinkedObject(); @@ -32,12 +33,10 @@ export class SplitEditor extends BaseNodeEditor { } ); - this.add( inputElement ); - - const xElement = new LabelElement( 'x | r' ).setOutput( 1 ).setObject( float() ); - const yElement = new LabelElement( 'y | g' ).setOutput( 1 ).setObject( float() ); - const zElement = new LabelElement( 'z | b' ).setOutput( 1 ).setObject( float() ); - const wElement = new LabelElement( 'w | a' ).setOutput( 1 ).setObject( float() ); + const xElement = setOutputAestheticsFromType( new LabelElement( 'x | r' ), 'Number' ).setObject( float() ); + const yElement = setOutputAestheticsFromType( new LabelElement( 'y | g' ), 'Number' ).setObject( float() ); + const zElement = setOutputAestheticsFromType( new LabelElement( 'z | b' ), 'Number' ).setObject( float() ); + const wElement = setOutputAestheticsFromType( new LabelElement( 'w | a' ), 'Number' ).setObject( float() ); this.add( inputElement ) .add( xElement ) diff --git a/playground/editors/StandardMaterialEditor.js b/playground/editors/StandardMaterialEditor.js index 226f4f496f8d8e..4b76e84f0cb04f 100644 --- a/playground/editors/StandardMaterialEditor.js +++ b/playground/editors/StandardMaterialEditor.js @@ -1,6 +1,7 @@ import { ColorInput, SliderInput, LabelElement } from 'flow'; import { MaterialEditor } from './MaterialEditor.js'; -import { MeshStandardNodeMaterial } from 'three/nodes'; +import { MeshStandardNodeMaterial } from 'three/webgpu'; +import { setInputAestheticsFromType } from '../DataTypeLib.js'; export class StandardMaterialEditor extends MaterialEditor { @@ -10,13 +11,13 @@ export class StandardMaterialEditor extends MaterialEditor { super( 'Standard Material', material ); - const color = new LabelElement( 'color' ).setInput( 3 ); - const opacity = new LabelElement( 'opacity' ).setInput( 1 ); - const metalness = new LabelElement( 'metalness' ).setInput( 1 ); - const roughness = new LabelElement( 'roughness' ).setInput( 1 ); - const emissive = new LabelElement( 'emissive' ).setInput( 3 ); - const normal = new LabelElement( 'normal' ).setInput( 3 ); - const position = new LabelElement( 'position' ).setInput( 3 ); + const color = setInputAestheticsFromType( new LabelElement( 'color' ), 'Color' ); + const opacity = setInputAestheticsFromType( new LabelElement( 'opacity' ), 'Number' ); + const metalness = setInputAestheticsFromType( new LabelElement( 'metalness' ), 'Number' ); + const roughness = setInputAestheticsFromType( new LabelElement( 'roughness' ), 'Number' ); + const emissive = setInputAestheticsFromType( new LabelElement( 'emissive' ), 'Color' ); + const normal = setInputAestheticsFromType( new LabelElement( 'normal' ), 'Vector3' ); + const position = setInputAestheticsFromType( new LabelElement( 'position' ), 'Vector3' ); color.add( new ColorInput( material.color.getHex() ).onChange( ( input ) => { diff --git a/playground/editors/StringEditor.js b/playground/editors/StringEditor.js index 93405d42e701b4..6cef4421de7bf0 100644 --- a/playground/editors/StringEditor.js +++ b/playground/editors/StringEditor.js @@ -12,8 +12,6 @@ export class StringEditor extends BaseNodeEditor { super( 'String', inputNode, 350 ); - this.setOutputLength( 1 ); - element.addEventListener( 'changeInput', () => this.invalidate() ); this.add( element ); diff --git a/playground/editors/SwizzleEditor.js b/playground/editors/SwizzleEditor.js index 1d41df2efcb3c3..d80b27fddb2519 100644 --- a/playground/editors/SwizzleEditor.js +++ b/playground/editors/SwizzleEditor.js @@ -1,7 +1,8 @@ import { LabelElement } from 'flow'; import { BaseNodeEditor } from '../BaseNodeEditor.js'; import { createElementFromJSON } from '../NodeEditorUtils.js'; -import { split, float } from 'three/nodes'; +import { split, float } from 'three/tsl'; +import { setInputAestheticsFromType } from '../DataTypeLib.js'; export class SwizzleEditor extends BaseNodeEditor { @@ -11,7 +12,7 @@ export class SwizzleEditor extends BaseNodeEditor { super( 'Swizzle', node, 175 ); - const inputElement = new LabelElement( 'Input' ).setInput( 1 ).onConnect( () => { + const inputElement = setInputAestheticsFromType( new LabelElement( 'Input' ), 'node' ).onConnect( () => { node.node = inputElement.getLinkedObject() || float(); diff --git a/playground/editors/TextureEditor.js b/playground/editors/TextureEditor.js index f9c6a9b77043fb..881c960d7269f3 100644 --- a/playground/editors/TextureEditor.js +++ b/playground/editors/TextureEditor.js @@ -1,8 +1,9 @@ import { LabelElement, ToggleInput, SelectInput } from 'flow'; import { BaseNodeEditor } from '../BaseNodeEditor.js'; -import { onValidNode, onValidType, getColorFromType } from '../NodeEditorUtils.js'; -import { texture, uv } from 'three/nodes'; +import { onValidNode, onValidType } from '../NodeEditorUtils.js'; +import { texture, uv } from 'three/tsl'; import { Texture, TextureLoader, RepeatWrapping, ClampToEdgeWrapping, MirroredRepeatWrapping } from 'three'; +import { setInputAestheticsFromType } from '../DataTypeLib.js'; const textureLoader = new TextureLoader(); const defaultTexture = new Texture(); @@ -23,8 +24,6 @@ export class TextureEditor extends BaseNodeEditor { super( 'Texture', node, 250 ); - this.setOutputLength( 4 ); - this.texture = null; this._initFile(); @@ -36,7 +35,7 @@ export class TextureEditor extends BaseNodeEditor { _initFile() { - const fileElement = new LabelElement( 'File' ).setInputColor( getColorFromType( 'URL' ) ).setInput( 1 ); + const fileElement = setInputAestheticsFromType( new LabelElement( 'File' ), 'URL' ); fileElement.onValid( onValidType( 'URL' ) ).onConnect( () => { @@ -57,7 +56,7 @@ export class TextureEditor extends BaseNodeEditor { _initParams() { - const uvField = new LabelElement( 'UV' ).setInput( 2 ); + const uvField = setInputAestheticsFromType( new LabelElement( 'UV' ), 'Vector2' ); uvField.onValid( onValidNode ).onConnect( () => { diff --git a/playground/editors/TimerEditor.js b/playground/editors/TimerEditor.js index 57dc4bf4359d98..155851c2aece9a 100644 --- a/playground/editors/TimerEditor.js +++ b/playground/editors/TimerEditor.js @@ -1,6 +1,6 @@ import { NumberInput, LabelElement, Element, ButtonInput } from 'flow'; import { BaseNodeEditor } from '../BaseNodeEditor.js'; -import { timerLocal } from 'three/nodes'; +import { timerLocal } from 'three/tsl'; export class TimerEditor extends BaseNodeEditor { diff --git a/playground/editors/UVEditor.js b/playground/editors/UVEditor.js index 61ca767653380a..bfdc1700f81174 100644 --- a/playground/editors/UVEditor.js +++ b/playground/editors/UVEditor.js @@ -1,6 +1,6 @@ import { SelectInput, LabelElement } from 'flow'; import { BaseNodeEditor } from '../BaseNodeEditor.js'; -import { uv } from 'three/nodes'; +import { uv } from 'three/tsl'; export class UVEditor extends BaseNodeEditor { @@ -10,9 +10,7 @@ export class UVEditor extends BaseNodeEditor { super( 'UV', node, 200 ); - this.setOutputLength( 2 ); - - const optionsField = new SelectInput( [ '1', '2' ], 0 ).onChange( () => { + const optionsField = new SelectInput( [ '0', '1', '2', '3' ], 0 ).onChange( () => { node.index = Number( optionsField.getValue() ); diff --git a/playground/editors/Vector2Editor.js b/playground/editors/Vector2Editor.js index 6b8b20b97ec720..d643e8c3c31762 100644 --- a/playground/editors/Vector2Editor.js +++ b/playground/editors/Vector2Editor.js @@ -12,8 +12,6 @@ export class Vector2Editor extends BaseNodeEditor { super( 'Vector 2', inputNode ); - this.setOutputLength( 2 ); - element.addEventListener( 'changeInput', () => this.invalidate() ); this.add( element ); diff --git a/playground/editors/Vector3Editor.js b/playground/editors/Vector3Editor.js index b2ec5894df5a61..f74a241c2d1515 100644 --- a/playground/editors/Vector3Editor.js +++ b/playground/editors/Vector3Editor.js @@ -12,8 +12,6 @@ export class Vector3Editor extends BaseNodeEditor { super( 'Vector 3', inputNode, 325 ); - this.setOutputLength( 3 ); - element.addEventListener( 'changeInput', () => this.invalidate() ); this.add( element ); diff --git a/playground/editors/Vector4Editor.js b/playground/editors/Vector4Editor.js index 425fdeea05fba6..e57ec6cf21d057 100644 --- a/playground/editors/Vector4Editor.js +++ b/playground/editors/Vector4Editor.js @@ -12,8 +12,6 @@ export class Vector4Editor extends BaseNodeEditor { super( 'Vector 4', inputNode, 350 ); - this.setOutputLength( 4 ); - element.addEventListener( 'changeInput', () => this.invalidate() ); this.add( element ); diff --git a/playground/elements/CodeEditorElement.js b/playground/elements/CodeEditorElement.js index a3feef72ba1927..fee1ea57e3f18d 100644 --- a/playground/elements/CodeEditorElement.js +++ b/playground/elements/CodeEditorElement.js @@ -22,7 +22,7 @@ export class CodeEditorElement extends Element { this.editor = null; // async - window.require.config( { paths: { 'vs': 'https://cdn.jsdelivr.net/npm/monaco-editor@latest/min/vs' } } ); + window.require.config( { paths: { 'vs': 'https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs' } } ); require( [ 'vs/editor/editor.main' ], () => { @@ -30,7 +30,8 @@ export class CodeEditorElement extends Element { value: this.source, language: 'javascript', theme: 'vs-dark', - automaticLayout: true + automaticLayout: true, + minimap: { enabled: false } } ); let timeout = null; diff --git a/playground/examples/universal/fresnel.json b/playground/examples/basic/fresnel.json similarity index 100% rename from playground/examples/universal/fresnel.json rename to playground/examples/basic/fresnel.json diff --git a/playground/examples/universal/matcap.json b/playground/examples/basic/matcap.json similarity index 100% rename from playground/examples/universal/matcap.json rename to playground/examples/basic/matcap.json diff --git a/playground/examples/basic/particles.json b/playground/examples/basic/particles.json new file mode 100644 index 00000000000000..3fed9d2e257a0a --- /dev/null +++ b/playground/examples/basic/particles.json @@ -0,0 +1 @@ +{"objects":{"71":{"x":2705,"y":459,"elements":[72,74],"autoResize":true,"source":"layout = {\n\tname: 'Emiter',\n\twidth: 300,\n\telements: [\n\t\t{ name: 'count', inputType: 'Number' },\n\t\t{ name: 'color', inputType: 'node' },\n\t\t{ name: 'opacity', inputType: 'node' },\n\t\t{ name: 'position', inputType: 'node' },\n\t\t{ name: 'rotation', inputType: 'node' },\n\t\t{ name: 'scale', inputType: 'node' }\n\t]\n};\n\nfunction load() {\n\n\tconst fireNodeMaterial = new THREE.SpriteNodeMaterial();\n\tfireNodeMaterial.blending = THREE.AdditiveBlending;\n\tfireNodeMaterial.transparent = true;\n\tfireNodeMaterial.depthWrite = false;\n\n\tconst fireInstancedSprite = new THREE.Mesh( new THREE.PlaneGeometry( 1, 1 ), fireNodeMaterial );\n\tfireInstancedSprite.isInstancedMesh = true;\n\tfireInstancedSprite.count = 100;\n\n\treturn fireInstancedSprite;\n\n}\n\nfunction main() {\n\n\tconst mesh = local.get( 'mesh', load );\n\n\tif ( mesh ) {\n\n\t\tmesh.count = Math.round( parameters.get( 'count' ) || 1 );\n\n\t\tmesh.material.colorNode = parameters.get( 'color' );\n\t\tmesh.material.opacityNode = parameters.get( 'opacity' );\n\t\tmesh.material.positionNode = parameters.get( 'position' );\n\t\tmesh.material.rotationNode = parameters.get( 'rotation' );\n\t\tmesh.material.scaleNode = parameters.get( 'scale' );\n\t\tmesh.material.dispose();\n\n\t}\n\n\treturn mesh;\n\n}\n","id":71,"type":"NodePrototypeEditor"},"72":{"outputLength":1,"height":null,"title":"Node Prototype","icon":"ti ti-ti ti-components","id":72,"type":"TitleElement"},"74":{"height":969,"source":"layout = {\n\tname: 'Emiter',\n\twidth: 300,\n\telements: [\n\t\t{ name: 'count', inputType: 'Number' },\n\t\t{ name: 'color', inputType: 'node' },\n\t\t{ name: 'opacity', inputType: 'node' },\n\t\t{ name: 'position', inputType: 'node' },\n\t\t{ name: 'rotation', inputType: 'node' },\n\t\t{ name: 'scale', inputType: 'node' }\n\t]\n};\n\nfunction load() {\n\n\tconst fireNodeMaterial = new THREE.SpriteNodeMaterial();\n\tfireNodeMaterial.blending = THREE.AdditiveBlending;\n\tfireNodeMaterial.transparent = true;\n\tfireNodeMaterial.depthWrite = false;\n\n\tconst fireInstancedSprite = new THREE.Mesh( new THREE.PlaneGeometry( 1, 1 ), fireNodeMaterial );\n\tfireInstancedSprite.isInstancedMesh = true;\n\tfireInstancedSprite.count = 100;\n\n\treturn fireInstancedSprite;\n\n}\n\nfunction main() {\n\n\tconst mesh = local.get( 'mesh', load );\n\n\tif ( mesh ) {\n\n\t\tmesh.count = Math.round( parameters.get( 'count' ) || 1 );\n\n\t\tmesh.material.colorNode = parameters.get( 'color' );\n\t\tmesh.material.opacityNode = parameters.get( 'opacity' );\n\t\tmesh.material.positionNode = parameters.get( 'position' );\n\t\tmesh.material.rotationNode = parameters.get( 'rotation' );\n\t\tmesh.material.scaleNode = parameters.get( 'scale' );\n\t\tmesh.material.dispose();\n\n\t}\n\n\treturn mesh;\n\n}\n","id":74,"type":"CodeEditorElement"},"77":{"x":2824,"y":-88,"elements":[78,282,284,285,286,287,288],"autoResize":false,"layoutJSON":"{\"name\":\"Emiter\",\"width\":300,\"elements\":[{\"name\":\"count\",\"inputType\":\"Number\"},{\"name\":\"color\",\"inputType\":\"node\"},{\"name\":\"opacity\",\"inputType\":\"node\"},{\"name\":\"position\",\"inputType\":\"node\"},{\"name\":\"rotation\",\"inputType\":\"node\"},{\"name\":\"scale\",\"inputType\":\"node\"}]}","id":77,"type":"Emiter"},"78":{"height":null,"title":"Emiter","icon":"ti ti-ti ti-variable","id":78,"type":"TitleElement"},"92":{"inputs":[93,94,95],"height":null,"id":92,"type":"Element"},"93":{"value":3,"id":93,"type":"NumberInput"},"94":{"value":3,"id":94,"type":"NumberInput"},"95":{"value":3,"id":95,"type":"NumberInput"},"96":{"x":270,"y":87,"elements":[97,92],"autoResize":false,"id":96,"type":"Vector3Editor"},"97":{"outputLength":3,"height":null,"title":"Vector 3","icon":"ti ti-ti ti-box-multiple-3","id":97,"type":"TitleElement"},"102":{"inputLength":1,"links":[117],"height":null,"id":102,"type":"LabelElement"},"103":{"inputLength":1,"links":[97],"height":null,"id":103,"type":"LabelElement"},"104":{"x":860,"y":-93,"elements":[105,102,103],"autoResize":false,"id":104,"type":"Range"},"105":{"outputLength":1,"height":null,"title":"Range","icon":"ti ti-sort-ascending-2","id":105,"type":"TitleElement"},"112":{"inputs":[113,114,115],"height":null,"id":112,"type":"Element"},"113":{"value":-3,"id":113,"type":"NumberInput"},"114":{"value":-3,"id":114,"type":"NumberInput"},"115":{"value":-3,"id":115,"type":"NumberInput"},"116":{"x":256,"y":-136,"elements":[117,112],"autoResize":false,"id":116,"type":"Vector3Editor"},"117":{"outputLength":3,"height":null,"title":"Vector 3","icon":"ti ti-ti ti-box-multiple-3","id":117,"type":"TitleElement"},"120":{"x":735,"y":90,"elements":[121,127,128,125],"autoResize":false,"id":120,"type":"TimerEditor"},"121":{"outputLength":1,"height":null,"title":"Timer","icon":"ti ti-clock","id":121,"type":"TitleElement"},"123":{"value":13.595,"id":123,"type":"NumberInput"},"124":{"value":1,"id":124,"type":"NumberInput"},"125":{"inputs":[126],"height":null,"id":125,"type":"Element"},"126":{"value":"Reset","id":126,"type":"ButtonInput"},"127":{"inputs":[123],"height":null,"id":127,"type":"Element"},"128":{"inputs":[124],"height":null,"id":128,"type":"LabelElement"},"140":{"inputLength":1,"inputs":[141],"links":[105],"height":null,"id":140,"type":"LabelElement"},"141":{"value":0,"id":141,"type":"NumberInput"},"142":{"inputLength":1,"inputs":[143],"links":[165],"height":null,"id":142,"type":"LabelElement"},"143":{"value":0,"id":143,"type":"NumberInput"},"144":{"x":1621,"y":77,"elements":[145,140,142],"autoResize":false,"id":144,"type":"Multiply"},"145":{"outputLength":1,"height":null,"title":"Multiply","icon":"ti ti-x","id":145,"type":"TitleElement"},"151":{"inputLength":1,"links":[209],"height":null,"id":151,"type":"LabelElement"},"152":{"inputLength":1,"links":[201],"height":null,"id":152,"type":"LabelElement"},"153":{"inputLength":1,"links":[217],"height":null,"id":153,"type":"LabelElement"},"154":{"x":1819,"y":301,"elements":[155,151,152,153],"autoResize":false,"id":154,"type":"Mix"},"155":{"outputLength":1,"height":null,"title":"Mix","icon":"ti ti-math-function","id":155,"type":"TitleElement"},"161":{"inputLength":1,"links":[121],"height":null,"id":161,"type":"LabelElement"},"162":{"inputLength":1,"inputs":[163],"links":[225],"height":null,"id":162,"type":"LabelElement"},"163":{"value":2.63,"id":163,"type":"NumberInput"},"164":{"x":1148,"y":167,"elements":[165,161,162],"autoResize":false,"id":164,"type":"Modulo"},"165":{"outputLength":1,"height":null,"title":"Modulo","icon":"ti ti-math-function","id":165,"type":"TitleElement"},"168":{"x":1600,"y":-374,"elements":[169,172],"autoResize":false,"buffer":"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","name":"Lovepik_com-611645078-Particle light effect.png","id":168,"type":"FileEditor"},"169":{"outputLength":1,"height":null,"title":"File","id":169,"type":"TitleElement"},"171":{"value":"Lovepik_com-611645078-Particle light effect.png","id":171,"type":"StringInput"},"172":{"inputs":[171],"height":null,"id":172,"type":"Element"},"176":{"x":1964,"y":-160,"elements":[177,179,180,184,185,186],"autoResize":false,"id":176,"type":"TextureEditor"},"177":{"outputLength":4,"height":null,"title":"Texture","icon":"ti ti-ti ti-photo","id":177,"type":"TitleElement"},"179":{"inputLength":1,"links":[169],"height":null,"id":179,"type":"LabelElement"},"180":{"inputLength":2,"height":null,"id":180,"type":"LabelElement"},"181":{"options":[{"name":"Repeat Wrapping","value":1000},{"name":"Clamp To Edge Wrapping","value":1001},{"name":"Mirrored Repeat Wrapping","value":1002}],"value":"1000","id":181,"type":"SelectInput"},"182":{"options":[{"name":"Repeat Wrapping","value":1000},{"name":"Clamp To Edge Wrapping","value":1001},{"name":"Mirrored Repeat Wrapping","value":1002}],"value":"1000","id":182,"type":"SelectInput"},"183":{"value":false,"id":183,"type":"ToggleInput"},"184":{"inputs":[181],"height":null,"id":184,"type":"LabelElement"},"185":{"inputs":[182],"height":null,"id":185,"type":"LabelElement"},"186":{"inputs":[183],"height":null,"id":186,"type":"LabelElement"},"198":{"inputs":[199],"height":null,"id":198,"type":"Element"},"199":{"value":0,"id":199,"type":"NumberInput"},"200":{"x":1248,"y":450,"elements":[201,198],"autoResize":false,"id":200,"type":"FloatEditor"},"201":{"outputLength":1,"height":null,"title":"Float","icon":"ti ti-ti ti-box-multiple-1","id":201,"type":"TitleElement"},"206":{"inputs":[207],"height":null,"id":206,"type":"Element"},"207":{"value":1.62,"id":207,"type":"NumberInput"},"208":{"x":1255,"y":342,"elements":[209,206],"autoResize":false,"id":208,"type":"FloatEditor"},"209":{"outputLength":1,"height":null,"title":"Float","icon":"ti ti-ti ti-box-multiple-1","id":209,"type":"TitleElement"},"214":{"inputLength":1,"links":[165],"height":null,"id":214,"type":"LabelElement"},"215":{"inputLength":1,"links":[225],"height":null,"id":215,"type":"LabelElement"},"216":{"x":1498,"y":538,"elements":[217,214,215],"autoResize":false,"id":216,"type":"Division"},"217":{"outputLength":1,"height":null,"title":"Division","icon":"ti ti-divide","id":217,"type":"TitleElement"},"222":{"inputs":[223],"height":null,"id":222,"type":"Element"},"223":{"value":2.79,"id":223,"type":"NumberInput"},"224":{"x":641,"y":528,"elements":[225,222],"autoResize":false,"id":224,"type":"FloatEditor"},"225":{"outputLength":1,"height":null,"title":"Float","icon":"ti ti-ti ti-box-multiple-1","id":225,"type":"TitleElement"},"230":{"inputLength":1,"height":null,"id":230,"type":"LabelElement"},"231":{"inputLength":1,"links":[241],"height":null,"id":231,"type":"LabelElement"},"232":{"x":2129,"y":-557,"elements":[233,230,231],"autoResize":false,"id":232,"type":"Range"},"233":{"outputLength":1,"height":null,"title":"Range","icon":"ti ti-sort-ascending-2","id":233,"type":"TitleElement"},"238":{"inputs":[239],"height":null,"id":238,"type":"Element"},"239":{"value":1,"id":239,"type":"NumberInput"},"240":{"x":1895,"y":-506,"elements":[241,238],"autoResize":false,"id":240,"type":"FloatEditor"},"241":{"outputLength":1,"height":null,"title":"Float","icon":"ti ti-ti ti-box-multiple-1","id":241,"type":"TitleElement"},"248":{"inputLength":1,"inputs":[249],"links":[233],"height":null,"id":248,"type":"LabelElement"},"249":{"value":0,"id":249,"type":"NumberInput"},"250":{"inputLength":1,"inputs":[251],"links":[272],"height":null,"id":250,"type":"LabelElement"},"251":{"value":0,"id":251,"type":"NumberInput"},"252":{"x":2527,"y":-425,"elements":[253,248,250],"autoResize":false,"id":252,"type":"Multiply"},"253":{"outputLength":1,"height":null,"title":"Multiply","icon":"ti ti-x","id":253,"type":"TitleElement"},"267":{"inputLength":1,"inputs":[268],"links":[165],"height":null,"id":267,"type":"LabelElement"},"268":{"value":0,"id":268,"type":"NumberInput"},"269":{"inputLength":1,"inputs":[270],"height":null,"id":269,"type":"LabelElement"},"270":{"value":10,"id":270,"type":"NumberInput"},"271":{"x":2122,"y":-371,"elements":[272,267,269],"autoResize":false,"id":271,"type":"Multiply"},"272":{"outputLength":1,"height":null,"title":"Multiply","icon":"ti ti-x","id":272,"type":"TitleElement"},"282":{"inputLength":1,"inputs":[283],"height":null,"id":282,"type":"LabelElement"},"283":{"value":500,"id":283,"type":"NumberInput"},"284":{"inputLength":1,"links":[177],"height":null,"id":284,"type":"LabelElement"},"285":{"inputLength":1,"height":null,"id":285,"type":"LabelElement"},"286":{"inputLength":1,"links":[145],"height":null,"id":286,"type":"LabelElement"},"287":{"inputLength":1,"links":[253],"height":null,"id":287,"type":"LabelElement"},"288":{"inputLength":1,"links":[155],"height":null,"id":288,"type":"LabelElement"}},"nodes":[71,96,104,116,120,144,154,164,168,176,200,208,216,224,232,240,252,271,77],"id":2,"type":"Canvas"} diff --git a/playground/examples/basic/teapot.json b/playground/examples/basic/teapot.json new file mode 100644 index 00000000000000..c1abad075b5c64 --- /dev/null +++ b/playground/examples/basic/teapot.json @@ -0,0 +1 @@ +{"objects":{"71":{"x":1534,"y":591,"elements":[72,74],"autoResize":true,"source":"layout = {\n\tname: 'Teapot Scene',\n\twidth: 300,\n\telements: [\n\t\t{ name: 'Material', inputType: 'Material' }\n\t]\n};\n\nfunction load() {\n\n\tasync function asyncLoad() {\n\n\t\tconst { RGBMLoader } = await import( 'three/addons/loaders/RGBMLoader.js' );\n\n\t\tconst rgbmUrls = [ 'px.png', 'nx.png', 'py.png', 'ny.png', 'pz.png', 'nz.png' ];\n\n\t\tconst cubeMap = await new RGBMLoader()\n\t\t\t.setMaxRange( 16 )\n\t\t\t.setPath( '../examples/textures/cube/pisaRGBM16/' )\n\t\t\t.loadCubemapAsync( rgbmUrls );\n\n\t\tcubeMap.generateMipmaps = true;\n\t\tcubeMap.minFilter = THREE.LinearMipmapLinearFilter;\n\n\t\t//\n\n\t\tconst scene = global.get( 'scene' );\n\n\t\tscene.environment = cubeMap;\n\n\t\t//\n\n\t\tconst { TeapotGeometry } = await import( 'three/addons/geometries/TeapotGeometry.js' );\n\n\t\tconst geometryTeapot = new TeapotGeometry( 1, 18 );\n\t\tconst mesh = new THREE.Mesh( geometryTeapot );\n\n\t\tlocal.set( 'mesh', mesh );\n\n\t\trefresh();\n\n\t}\n\n\tasyncLoad();\n\n}\n\nfunction main() {\n\n\tconst mesh = local.get( 'mesh', load );\n\n\tif ( mesh ) {\n\n\t\tmesh.material = parameters.get( 'Material' ) || new THREE.MeshStandardMaterial();\n\n\t}\n\n\treturn mesh;\n\n}\n","id":71,"type":"NodePrototypeEditor"},"72":{"outputLength":1,"height":null,"title":"Node Prototype","icon":"ti ti-ti ti-components","id":72,"type":"TitleElement"},"74":{"height":507,"source":"layout = {\n\tname: 'Teapot Scene',\n\twidth: 300,\n\telements: [\n\t\t{ name: 'Material', inputType: 'Material' }\n\t]\n};\n\nfunction load() {\n\n\tasync function asyncLoad() {\n\n\t\tconst { RGBMLoader } = await import( 'three/addons/loaders/RGBMLoader.js' );\n\n\t\tconst rgbmUrls = [ 'px.png', 'nx.png', 'py.png', 'ny.png', 'pz.png', 'nz.png' ];\n\n\t\tconst cubeMap = await new RGBMLoader()\n\t\t\t.setMaxRange( 16 )\n\t\t\t.setPath( '../examples/textures/cube/pisaRGBM16/' )\n\t\t\t.loadCubemapAsync( rgbmUrls );\n\n\t\tcubeMap.generateMipmaps = true;\n\t\tcubeMap.minFilter = THREE.LinearMipmapLinearFilter;\n\n\t\t//\n\n\t\tconst scene = global.get( 'scene' );\n\n\t\tscene.environment = cubeMap;\n\n\t\t//\n\n\t\tconst { TeapotGeometry } = await import( 'three/addons/geometries/TeapotGeometry.js' );\n\n\t\tconst geometryTeapot = new TeapotGeometry( 1, 18 );\n\t\tconst mesh = new THREE.Mesh( geometryTeapot );\n\n\t\tlocal.set( 'mesh', mesh );\n\n\t\trefresh();\n\n\t}\n\n\tasyncLoad();\n\n}\n\nfunction main() {\n\n\tconst mesh = local.get( 'mesh', load );\n\n\tif ( mesh ) {\n\n\t\tmesh.material = parameters.get( 'Material' ) || new THREE.MeshStandardMaterial();\n\n\t}\n\n\treturn mesh;\n\n}\n","id":74,"type":"CodeEditorElement"},"77":{"x":1346,"y":362,"elements":[78,120],"autoResize":false,"layoutJSON":"{\"name\":\"Teapot Scene\",\"width\":300,\"elements\":[{\"name\":\"Material\",\"inputType\":\"Material\"}]}","id":77,"type":"Teapot Scene"},"78":{"outputLength":1,"height":null,"title":"Teapot Scene","icon":"ti ti-ti ti-variable","id":78,"type":"TitleElement"},"82":{"x":750,"y":240,"elements":[83,85,86,87,88,89,90,91],"autoResize":false,"id":82,"type":"StandardMaterialEditor"},"83":{"outputLength":1,"height":null,"title":"Standard Material","icon":"ti ti-ti ti-inner-shadow-top-left","id":83,"type":"TitleElement"},"85":{"inputLength":3,"inputs":[92],"links":[115],"height":null,"id":85,"type":"LabelElement"},"86":{"inputLength":1,"inputs":[93],"height":null,"id":86,"type":"LabelElement"},"87":{"inputLength":1,"inputs":[95],"height":null,"id":87,"type":"LabelElement"},"88":{"inputLength":1,"inputs":[97],"height":null,"id":88,"type":"LabelElement"},"89":{"inputLength":3,"height":null,"id":89,"type":"LabelElement"},"90":{"inputLength":3,"height":null,"id":90,"type":"LabelElement"},"91":{"inputLength":3,"height":null,"id":91,"type":"LabelElement"},"92":{"value":15860226,"id":92,"type":"ColorInput"},"93":{"min":0,"max":1,"value":1,"id":93,"type":"SliderInput"},"95":{"min":0,"max":1,"value":1,"id":95,"type":"SliderInput"},"97":{"min":0,"max":1,"value":0,"id":97,"type":"SliderInput"},"114":{"x":140,"y":405,"elements":[115],"autoResize":false,"id":114,"type":"NormalWorld"},"115":{"outputLength":3,"height":null,"title":"Normal World","icon":"ti ti-arrow-bar-up","id":115,"type":"TitleElement"},"120":{"inputLength":1,"links":[83],"height":null,"id":120,"type":"LabelElement"}},"nodes":[71,82,114,77],"id":2,"type":"Canvas"} \ No newline at end of file diff --git a/playground/examples/universal/teapot.json b/playground/examples/universal/teapot.json deleted file mode 100644 index f22e56e877ffa4..00000000000000 --- a/playground/examples/universal/teapot.json +++ /dev/null @@ -1 +0,0 @@ -{"objects":{"21":{"x":1534,"y":591,"elements":[22,24],"autoResize":true,"source":"layout = {\n\tname: 'Teapot Scene',\n\twidth: 300,\n\telements: [\n\t\t{ name: 'Material', inputType: 'Material' }\n\t]\n};\n\nfunction load() {\n\n\tasync function asyncLoad() {\n\n\t\tconst { RGBMLoader } = await import( 'three/addons/loaders/RGBMLoader.js' );\n\n\t\tconst rgbmUrls = [ 'px.png', 'nx.png', 'py.png', 'ny.png', 'pz.png', 'nz.png' ];\n\n\t\tconst cubeMap = new RGBMLoader()\n\t\t\t.setMaxRange( 16 )\n\t\t\t.setPath( '../examples/textures/cube/pisaRGBM16/' )\n\t\t\t.loadCubemap( rgbmUrls );\n\n\t\tcubeMap.generateMipmaps = true;\n\t\tcubeMap.minFilter = THREE.LinearMipmapLinearFilter;\n\n\t\t//\n\n\t\tconst scene = global.get( 'scene' );\n\n\t\tscene.environment = cubeMap;\n\n\t\t//\n\n\t\tconst { TeapotGeometry } = await import( 'three/addons/geometries/TeapotGeometry.js' );\n\n\t\tconst geometryTeapot = new TeapotGeometry( 1, 18 );\n\t\tconst mesh = new THREE.Mesh( geometryTeapot );\n\n\t\tlocal.set( 'mesh', mesh );\n\n\t\trefresh();\n\n\t}\n\n\tasyncLoad();\n\n}\n\nfunction main() {\n\n\tconst mesh = local.get( 'mesh', load );\n\n\tif ( mesh ) {\n\n\t\tmesh.material = parameters.get( 'Material' ) || new THREE.MeshStandardMaterial();\n\n\t}\n\n\treturn mesh;\n\n}\n","id":21,"type":"NodePrototypeEditor"},"22":{"outputLength":1,"height":null,"title":"Node Prototype","id":22,"type":"TitleElement"},"24":{"height":507,"source":"layout = {\n\tname: 'Teapot Scene',\n\twidth: 300,\n\telements: [\n\t\t{ name: 'Material', inputType: 'Material' }\n\t]\n};\n\nfunction load() {\n\n\tasync function asyncLoad() {\n\n\t\tconst { RGBMLoader } = await import( 'three/addons/loaders/RGBMLoader.js' );\n\n\t\tconst rgbmUrls = [ 'px.png', 'nx.png', 'py.png', 'ny.png', 'pz.png', 'nz.png' ];\n\n\t\tconst cubeMap = new RGBMLoader()\n\t\t\t.setMaxRange( 16 )\n\t\t\t.setPath( '../examples/textures/cube/pisaRGBM16/' )\n\t\t\t.loadCubemap( rgbmUrls );\n\n\t\tcubeMap.generateMipmaps = true;\n\t\tcubeMap.minFilter = THREE.LinearMipmapLinearFilter;\n\n\t\t//\n\n\t\tconst scene = global.get( 'scene' );\n\n\t\tscene.environment = cubeMap;\n\n\t\t//\n\n\t\tconst { TeapotGeometry } = await import( 'three/addons/geometries/TeapotGeometry.js' );\n\n\t\tconst geometryTeapot = new TeapotGeometry( 1, 18 );\n\t\tconst mesh = new THREE.Mesh( geometryTeapot );\n\n\t\tlocal.set( 'mesh', mesh );\n\n\t\trefresh();\n\n\t}\n\n\tasyncLoad();\n\n}\n\nfunction main() {\n\n\tconst mesh = local.get( 'mesh', load );\n\n\tif ( mesh ) {\n\n\t\tmesh.material = parameters.get( 'Material' ) || new THREE.MeshStandardMaterial();\n\n\t}\n\n\treturn mesh;\n\n}\n","id":24,"type":"CodeEditorElement"},"27":{"x":1346,"y":362,"elements":[28,70],"autoResize":false,"layoutJSON":"{\"name\":\"Teapot Scene\",\"width\":300,\"elements\":[{\"name\":\"Material\",\"inputType\":\"Material\"}]}","id":27,"type":"Teapot Scene"},"28":{"height":null,"title":"Teapot Scene","id":28,"type":"TitleElement"},"32":{"x":750,"y":240,"elements":[33,35,36,37,38,39,40,41],"autoResize":false,"id":32,"type":"StandardMaterialEditor"},"33":{"outputLength":1,"height":null,"title":"Standard Material","id":33,"type":"TitleElement"},"35":{"inputLength":3,"inputs":[42],"links":[65],"height":null,"id":35,"type":"LabelElement"},"36":{"inputLength":1,"inputs":[43],"height":null,"id":36,"type":"LabelElement"},"37":{"inputLength":1,"inputs":[45],"height":null,"id":37,"type":"LabelElement"},"38":{"inputLength":1,"inputs":[47],"height":null,"id":38,"type":"LabelElement"},"39":{"inputLength":3,"height":null,"id":39,"type":"LabelElement"},"40":{"inputLength":3,"height":null,"id":40,"type":"LabelElement"},"41":{"inputLength":3,"height":null,"id":41,"type":"LabelElement"},"42":{"value":15860226,"id":42,"type":"ColorInput"},"43":{"min":0,"max":1,"value":1,"id":43,"type":"SliderInput"},"45":{"min":0,"max":1,"value":1,"id":45,"type":"SliderInput"},"47":{"min":0,"max":1,"value":0,"id":47,"type":"SliderInput"},"64":{"x":140,"y":405,"elements":[65],"autoResize":false,"id":64,"type":"NormalWorld"},"65":{"outputLength":1,"height":null,"title":"Normal World","icon":"ti ti-arrow-bar-up","id":65,"type":"TitleElement"},"70":{"inputLength":1,"links":[33],"height":null,"id":70,"type":"LabelElement"}},"nodes":[21,32,64,27],"id":2,"type":"Canvas"} \ No newline at end of file diff --git a/playground/examples/webgl/car.json b/playground/examples/webgl/car.json deleted file mode 100644 index d2363ea1af7e2d..00000000000000 --- a/playground/examples/webgl/car.json +++ /dev/null @@ -1 +0,0 @@ -{"objects":{"145":{"inputs":[146],"height":null,"id":145,"type":"Element"},"146":{"value":"../examples/textures/equirectangular/venice_sunset_1k.hdr","id":146,"type":"StringInput"},"147":{"x":-593,"y":34,"elements":[148,145],"autoResize":false,"id":147,"type":"StringEditor"},"148":{"outputLength":1,"height":null,"title":"String","icon":"ti ti-ti ti-forms","id":148,"type":"TitleElement"},"153":{"x":-502,"y":140,"elements":[154,157],"autoResize":false,"buffer":"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AUWERA5FRUVERERGRkdISEdISIhHGkhIR0dFREdJSUpLTExOTUxLS0tMS0tKS0tLhEwHTU1OTk5PT4VOC01NTExMS0tLSkpKhEkYSEdIR0ZGRUVERUREQ0REQ0NDRERDQkNDhEQBRYdECkNDRENDQkNCQ0OEQhFBQkJDQkNDRERERUVFRkVERIRDDEREQ0NDQkJBQD8/P4o+ET8+Pj49Pj4+P0BAQUFBQkNEhUMBQoZBAUKJQ4JCh0GDQoRBBkJCQkNDQ4RCh0EKQkJDQkJDQkFBQYVAAUGEQiJBQUBAQD8/QEBAPz8/Pj8/Pj5AQUBAQEFAQUFBQkJBQUJBhUIMQ0REQ0NDRERFRkZGhUcOSEdISEhJSUpKS0tLTEyETQZOTk5NTU2ETA9NTUxNTU5NTU1OT09QUFCEUgpRUVBQUE9PUFBPhlAIUVFSUlJTU1OFUgNRUVKEUIhPglCETwJQT4VOhE0ETk5NTYROAU2ETAFLh0qESwhMTUxMSklIR4RGC0VEREVGR0dHSElIiEkcSktLTExNTExLS0lISEdGRkZHR0ZFRERFRkZFRIZDAUKFQR5AQD9AQD9AQEFCQkNDRENCQUFCQUA/P0BAQEFCQkKGQw6EQkJCQ0NDRERERkZGR4RGAUeISIRHEUhIRoyLioqJhYWGhoWFhoaHhYk7iImHg4SEhYaHh4iHiImLi4qJioqJiouLioqJiIeJiouMjY2Ojo2NjI2NjYyMjYyNjIyNjYyLjIyLi4iEhiKFhoaFg4OEg4ODhISEhoaHiIqLjIyNjo6OjY6NjY2MjIqIhodEiImIhoeHiIiJiomIiIeJiIiGhoaEhIOAfXx7e3t6enx8fX6AgYKChIWHh4iIiYiDgX5/gIB/f4CBgoKChERERUZHSUmESDBJTE9RU1RWVVNOSkxPV19iaXN1dHBsamVdW1lWU1BQTUtKSkpJSEdGR0hKTExNTE6EUDNPTUtJSEdGRURCQkFAPz9AQUFBQkJDQ0NCQkJDQkJBQUFCQkFBQUJFR0dFRERDQkFCRUYWWVdWV1haWllYV1dWV1hXVVRTU1RUVIdTBFRTVFSHVYZUg1WHVohXFVZWV1hYWVpbWlpZWVhYWVlaXFtbWoVZDFpZWllZWVhZWVpaWoVbBFxbXFyEWwVaWllZWZFYBVlZWlpbhlyIWw9cW1taWVtcXF1eXl9g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iYmKCouLjY2Oj4+Oj5D/gP+A/4D/gP+AxoCFf4WAAX+egAV/gIB/gJp/AYCIfwWAgH+Af4aAjH+CgIp/AYCKfwGAtn+JgIJ//oACAgQAA0ZHSIVJJkpKSktNTk9QUlJUVldYWVxncm9nYFpXUkxKSkhHR0ZGR0ZFRERDhEQFRUREREOERIRDhEQPRUVGRkZHR0dGRkdGRkZFh0YOR0dHSElJSEhISUlKSkuISghLS0pKSklJSoRJAkhJhUoDSUpJhkoGSUlJSEhIhUkOSEhJR0dFRUVGR0hISUmESg5LTE1NTk1OT09PUVFPT4ZQhVEfUFBQT1BPT09OTk1NTEtMS0lISEdHR0ZFRUVGRkVHR4RICUlJSUhHR0ZGR4VGCEVGRUREQ0NDhUIGRERDQkNDhUIIQ0NDREVGRkWFRIRDBERERkaHRQ5EQ0NDQkJCQUJBQEFCQoZDhEQDQ0JChUMJRENDQkNDRENDhkIbQ0RDRERERUREQ0NCQkJBQkFBQUBAQD8/P0A/iEAFPz8/QECHP4RAhT8EQD8+Pok9DD4+PT08PT08PDw9PYU+jD8FQEBAQUKHQwVERUVGR4VID0dHR0hJSUpMS0tKSktLS4RKBElJSkmESgFJhUsHTE1NS01NTIROBE1MS0uESilJSEdISElJSkpLS0xMTEtLS0xLS0tKSUlKSktMTExOT1BTUlNTU1JSUoRTFFJTU1NSUlFQT05PUFBPT05OTUxMh0sESkpJSIRJB0pKSktLS0yFTQFMhEsFSkpJSEeERgRFRUZGhkcCSEmFSgRJSUhHh0aERw1GRkVERUVERURFRUREhUOCQoRDC0RDQkBAQD9AQUFBhkAMQUJCQ0RDQkJBPz8+hHwTfn+AQUJBgYJCQkNDQ0RERUdISoRLBkpKSUhISIRHBEZGRkWERAxFi0WJikWKiYiJi4mFixGMjI1GRkhISUlJSElISElKSoVJBEpKS0qES4NKh0k9kUlMTk9OTUxMmEuXmJhMTJaVlZKSk5KQkI+Oi4mIiYiIh4iHhIOFh4WCgYCBgH9+fX18e3l5eHl4eHh3doR4IHl7fn9/gYCAgISEhYaGiYuLi0ZHR0ZFRUZHR0hHR0dIhEeERgdFRERFRUhJhEpHSEhJSUpKSUlISUhIRkZDQkA/Pz49PT08PT09QEJISkZEQ0RFRUVHSUlNT1JSUVBT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R4VGhkcFSEdHSEiESQRISElKhUuETINNhEwHS0xLS0xMTIRNAUyJS4RKEUtLSkpKSUhJSUlISEhHSElJhUoES0tMT4hQBFFQT06GTzRQUVFSUlNRUVFQT09OT09PTkxMSkpHRkdGRUVFRkZGSEhJSEhISUlKSklJSEhJSEhHR0ZHhkYMRUVERENDQ0RERUVFikQVRUVFRkZFRkVGRUVERERDQ0RFRUZGhEcHRURERENDQoVBB0JCQ0NEQ0OERAhDQ0NCQkNDQ4RChkMEQkJCQ4hCBENCQ0OEQgFBikCCP4Q+AT+FQAs/Pj49Pj4+P0BAP4ZADD8+Pz89PT08PD09PYU+hj0HPj0+Pj4/P4lABz9APz9AQECEQYZCCUNDRERERUVGRYRGC0dHSElJSkpKS0tLhEoKSUhISUlKSkpLSoVLjEwKTU1MTEtLS0pKSYRIgkmESgRLTE1Nh0wTS0xNTk9QT1BSUlFQUVBQT09QUIhRBlJSU1JSUoVRA1BPToRNhEwNS0pKSUlLS0pLS0tMTIRNhU4OTUxLSkpJSEhGR0ZHSEeFSINJhUoESUlJSIZHCkZGRkdHR0ZGRkWFRANDREOLRIVDNkJBQUBAQUFBQEA/Pz4/QEBAQUFBQEFAQD8+Pnt6e3t8foBAQEFBQkFCQUJDREVHSEhISUhIR4VGhUUgRkZGR0WJRUZFRERFRUWKioqJiUVFRUZGjo6OjY2OR0aFR4NIiEkSSkqSSpKRk5OSSUmQj5BJSUlKhEsWlZSWlpRLS0tKko+Pjo+SlJOTk5SRi4SHOoiJiYqKiouLiomFg4KDf4GBf36Af357eHZ0c3N0dnh4eXt9fn5+gH+AgIGChIVDh4lFikVFRkZGR0eESBtGRkVFRUZHSEhISUlISElKS0tLSkpJSEhISUmFSDlHRkVDQkFAQD8+PkBAQUFBQEFBQURHSEhJR0RFRkdHR0lMSUZFSk9RUExMS0hJSEhKTU5KREREQkGFQAJBQ4REDkNDQkJBQUBBQkJCQ0NDhUSFQwlYWVpbXFxcXV2EXiRhY2VoaWlpam50en57d3Z3eHt8d25raWdnZWNhYF5cWllaWlqJWYVahFuHXANdXFyLXQFehF0EXl5eX4ZeB19fX2BgYWCFYQRgYF9fhF6DX4VgA19eXodfiF4MXV1dXl1cXFxbW1xchV4DX19ghmIGY2NiY2NihmEDYmNkhWUFZGRjYWGEYA9fXVxbW1laWVhZWVpaWluEXBJbW1xcXV1dXFtcXFtbWlpaW1qEWQVaWVhYWIVXAlhZjVgTWVlaWVlZWllZWVhYV1dXWFlZWIRZD1hYV1dWV1ZWVlVVVlZXV4RYC1lYWFhXV1dWVldXiFYCVVaHVQFWhFWGVohVg1SFU4RSAVOGVApTUlFSUlJTU1RThVQFU1NSUlOEUoRRhVKFUYZSEFNTVFNTVFNTU1RSUlNTU1SHVQRUVVVVhlYQV1dYV1hYV1hYWVpZWltaWoVbJ1xcXFtaWltbXF1dXF1dXl1eXl5dXl1dXl1dXl5eX19eXl1eXl1dXIRbhVyDXYReCV1dXV5dXl5dXYRfBGFhYF+EYAdhYWJhYGBgiWGHYAJfXohdAVyEWwVcXFxdXYhehF8gXl1dXFxbWllXWFhXV1hZWllZWVpaWltbW1paWllZWViJVw9YWFdWVlZVVVZVVVRVVVSEVQdUVFRVVVZVhVQDU1NShFOCUoVRFFJSU1NTUlJSUVFQUKCenp6foaNRiFILU1NUVldXWFhYV1eEVoZVhFYUVKhUVVRTU1RUVKmpqKinU1RUVVWGqwdVVVVWVVZWh1cSVlZVVldWrFasrK2trVdWq6qrhVYNV1dXr6+vsK9XV1dWrISpMqytra2ur66ppqSko6OkpKanpqanpqWhn56fnqCgn6CgoJ+dm5qZmJeYmZqamp2en5+fhaEIoqOkUqaoVKiEVA1VVlZWV1ZWVVVUVFRVhVYHV1dXWFhZWYVYEFdYWVlYV1dYV1ZVVVNTUlKEUYNThlQlWFlaWltaWFlZWlpaXWBdWlpdYWVjYGBfXVxaW1xeX1xYWVlWVYVUAlVXhVgCV1aHV4VYCVlYWVhXV1hYWASOj5CRhZIkk5SUlZiam56foJ+goqiwtLGtrK6usLGrpKCdnJybmZiVkpGRhJAFj4+PkI+GkAOPkJCEkQmSkZGRkpKSk5KFkYSSBJOSkpOHkg2Tk5OUlJSVlpWVlpaXhJYPl5eVlZSUk5SSkpOTk5SUhJMDkpKUhpMCkpOIkh6TlJKRj4+Njo+OkJCRkZKRkJKUlZaVlZWWlZWWlpeElRuUk5OUlZWWlpaVlJSTkpKRkZKSkZCPjY6MjIyEihuLi4uMjI2NjY6Oj4+Qj46Njo6OjYyLjIyLi4uHigWJiYqKiomLDYqKiYmJiouMjIuLioqKixKMjIyLjIyMi4qKiYmKiYmJiIiHiQeKi4uLioqKhYmJiAeHh4eGh4eHhYYFh4eHiIiEhwOGhYWEhgaFhYaGhYWEhISFBoaGhoWEg4WEB4WEhYaGh4iFhwmGhoeFhIWGhYaHhQqGhYaFhoaFhoaGiYcNhoeFhYWGhoaHhoaHhoWHBIiIiImKig2Li4yNjIyNjIyMjY6OhY0Gjo6Pjo6Oh48MkJCRkZGQkZGRkpKRhZACkZCFjySOjo6MjY2OkJCQkZCQj4+Pjo+Ojo6Qj5CSkZGPkY+Pjo6Pj5CEjweQj5CQjo+Ph5CFjwGQho8tjo2MjI2Mi4yMjY2Njo+Pj46Oj5CQj46OjY2NjIuKiomJioqKiYmKiIiJiYqKhIkBioeJDIiHh4aGh4eGhoeGhoaFA4SFhoaFiIQJg4OCgoOCg4ODhYKEgYWCGIGAgID///79/f//gICBgYKBgYCAgIGBgoWDgoKFgQKAgYaABYGA/4CBhoAF//7///6FgAH+hf+FgAGBjoAQ/4D//v///oCA//7/gICAgYSABf/9/v//hIAU//v6+vz9//38+/z7+/z8/v38/PuF/AH6hPkJ+vj6+vj5+vr6hPgZ9vb29/j4+fr6+/z9//7+///+/f6A//+A/4SAhYGCgoaBAoKDhoKCg4WEBoOEg4SFhoeFA4SDg4SCOIGBg4WFhYSDhIWGiIqLiomIhoiIioqLjpGPjY2PkpSTkpKRkI+OjpCSkY6Ki4yKiYmKiYqKioyPhI4SjYuMjIyLi4uMjY2NjI2Ojo+OhY3/gP+A/4D/gP+AxoCHf6OAAX+IgIV/hYCGf5SAAn+AhX8FgIB/f3+IgIV/hIC/fwWAf3+Af/+AiIACAgQABENEREWERgZHR0hJSUmHShlLT1VQUFFSUlRYWVlbXl5aV1ZUUExLSUdGhUWIRAFFhEYCR0aFRwZISElJSEiESoRJBEdISEiFSQlKSUpKSUpLS0uETAdLTExMTk9Ph04BTYdOhE0FTk5PTk2ETAJKS4VMhEuHSjNLTUxLTE5PT1BRUlJQUFBRUFBQT01NTE1OUFFTVFVUU1RTUlFQUE9PTk5PT09NTEpJR0iFRwlISElKS0xMS0uFSg5JSklJSkpKSEhIR0dGRoRFAUSGRQhGRkZFREREQ4REAkVEhEMDRERFhkYBRYREAUWERwlGRkVEQ0NDQkKGQRZDRERERURERUREQ0JBQUFAQEFBQkJCi0MBRIVDhUIDQUBAiEGCQIU/CD4/Pz9AQD4+hD0JPj49Pj4+Pz8+hj+DPoc9hD6IPQc+Pj9AQUJChEEGQkJCQUFBi0ADQUBAhEGGQgNDQ0SFRQhGR0dIR0dHSIVJi0sHTExLTEtLS4ZMIEpLSktLS0pKSktKSklJSklJSUpJSUpLS0xLS0xMTU5PhE4FT1BRUVGEUCBPT1BQUFFQUFFQUVJSUVNSU1NTVFNTU1JRUE9PT05OT4VOik0ETE1NToRPBU5NTExLhEobSUhHRkZHR0hHSEhISUpMTE1OTUtKSUlIR0hHhEgERkVFRYZGAUSGQw1CQkFCQkJDQ0NERENDhUKEQQRAQD8/iT4BP4VABEFBQECFP4RAhUEHQkRDRUZGRoVHBEZGRkWERA+KiotHSEhHjIpFiIiIRESFhw2GhYaIiYuLRkaLRUVFhUYIR0dHSElKSkqHSUyTkklJSUiOjo2Nj0lKS0tMTEtKSEeLiYiIh4mMjY2Nj45ISUpKlJOQi4eFhIWFhoiJioiIhoaGhIKAgIWIh4SGhYGAfXt4dXV3eXp7hXkZeHh4eXl6en1+f4GEhoeKikRFRERERURERISGDERDREZGSElJSkpLS4VMA0tLSoRJCUhIR0dFRkZFRIRDGkJAQENGRkZFRENCQUJCR1VZVlFKS0dHRkVEhEMLRUdGRkVERENCQUGEQhVER0dFRERDQ0NCQ0NCQkJDQ0NCQkSHQwtER0hIRkRDREREQwRZWVpbhFwDXV5fhWAeX19fYGFiZmtmZmdoaGpucHFydXZwbW1rZ2NjYF1chlqGWwhaW1xcXF1dXIVdhV4KXV5eX19eX19fXolfBF5fX1+FYA5hYWJhYmJhYmJiYWJiYYZggmGEYoNhhGKFYQtfYGBfYF9gX15fX4deHl9hYV9fYGBgYWJjZGNjY2RjY2NiYWFgYGFiY2RlZoRnD2ZlZGJhYWBgYWBgX15dXIlbAlxdhV6FXYRcB11dXFxbW1uEWoJZhVgJWVlZWltaWVlZiFgBV4ZYh1kFWFhZWVmEWgdZWVhYWFdYiFYMV1dYWFlYWFlYWFdXhFYIVVVVVlZXV1eHVgFVhlaHVQ1UVFRVVFVUVVVVVFRUhFMIUlNTU1JSUVGFUg1TUlJTU1RUU1RUU1NThFIBUYVSElFSUVBRUFFRUVJSUlFSUlJTVIRVBVZWVVVVj1QBU4RUhlUFVlZWV1eFWAFZhVqGWwJcW4VchF0bXl5dXl5dXV1eXl5fXl1dXV5eXV1dXF1dXFxbhFwDXVxchl0FXl5eYGCHX4Jghl8PYGFhYWBgYF9fYGBgYWJihmEEYGFgX4ZehF8BXoZdCF5eXl1eXV5ehF8gXl5dXVxcW1taWllYWVlYWVpaWllaW1xcXV1dXFtaWVmHWIhXAlZVjVSHVQZUVFVVVVSEUwVSUlFQUIVRCFBRUVJSUVJShFGFUAxSUlFSUlJTUlNUVVSEVS5WVlZVVVVUVVVUVFVUqaipVVZWVampVamoqFRUp6enpqako6WnqKqpVVWpVFRThFSFVYdWUFdWV1etrVZXV1asq6qpqlZXWFdXWFdYV1esqqinqKipqaqrraxXV1hYsK6rp6akoqOhoqWmpqWlpKSkoqGfoKWnpqSkoqCfnJyamZqanJybhJoRm5ubnJ2dnJ2fn6Cio6WmqaiJVISmDFNTVFVVV1dYWVhZWYVaCVlYWFdXWFlYWIVXAlZVhVQIU1NWWFdYV1aFVQxaZGdlYVxdWVpZWFiEVyVYWlpaWFhYV1VVVVdXWFhaXFpYWFhXWFhXWFhXV1dYWFhXV1hYhFcNWFlaXF1dXFpYWFhZWS6Pj4+QkZGSkpKTlZaXl5eWlZaWl5eYmqCamZycnaClpqepq6ynpKOhnZiWlJOThZEBkIiRhpIGk5OUlZSThJQDk5SUh5WElIOVh5QOlZWVlpaXl5eYmJeXmJiElw2WlZWUlZWWl5eXlpWUhJUWlpaVlZaWlpWVlpaVlpaVlJWVlJOTkoSRD5KTkpKUlJWWmJmZmJeXl4WWG5WTk5SUlZeYmJmYmZiXlpWVk5GRkZOSk4+PjoWNCoyMjYyNjo+Pj5CEkQ6QkI+PkJCQkZCPjo6NjYmLhYwDjY2MhIuDioiJCoqKi4yMjI2MjIyEi4aMF4uKiomKiYmIiIeHiImJiIiIiYqJiYmKhImDiIaGiIeEhoSHCoiHh4eIiIeHhoeOhoaFBYSEhYWFhIQQhYWFh4aGhoWGh4eHhoaGhISFgoaEh4aGDYWFhoWGh4iJiomIh4eEiAqHiIeHhoaHh4iIhIcBhouHBIiIiYmGioWLhY0Ejo+Oj4eOA4+P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Xl1cXFtbW1pZWFlZWYVaEVlaW1tcXF1dXVxbWllZWFlYhFmCWIRXBVZXV1dWhlUBVIRTA1RTU4dUg1WEVAVSUlFRUYlQAVGHUgtRUVFQUFBSU1NSU4lUhlWEVohVBldXVlZWVYRUEaanpaWkpqanqahUqqqpqKinhKgIqalUplRVVVaFV4RWHVdXV1hYWFdXVaqpVVZXV1lYWFdXVlZVVaeko6OlhaQjpqmqq6pVVFRUpqWjo6SlqKmqqqusq6qopqenqaikoaGfnp2FnBadnp6dm5ubnJ2cnZ2dn6CkpaSkpVNThFQBU4RSBlNUVVVWVoRVg1aEVw5YWVpbWlpZV1dYV1ZWVodVLlRTU1RUVVVVVldYWVlYWFdXWFhWVVZXWVteXVtaWFdWVlZXWFlbXVtbWVhXVlWEVoNXhFgKWVlZWFhZWVhYWYVbA1xcX4RgBWJiYF5dhVwBXSWUkpCPj46OjY2Ojo+RkpKSk5SWlpWVlZeXl5mbnZ2dnJmYmJmYhJkLlZWVlpaWlJOSkpOEkoeThJQHlZeYmZiXl4aWgpWElgiVlpaXmJeYmYWYA5eWl4WYA5mYmIaZDJiYl5eWl5iYmJmZmYWYA5mam4SaDJmYlpaWl5eWlpeWlYSWAZSHky+UlZaWlpiYmZmZmJaVlpaWlZSUlZaXmJmbm5ydnJuZmZeXlpWTk5OSk5STkpGRkYSQB4+Qj4+QkZGIkg2RkZKSkZKRkJCPjY6OhI8BjoaNB46Pj46OjY2EiweKiouKi4uLhoyCjYaOA42MjIaLB4qLi4uKiYmJiIaJB4iIiImKiYiFhwGGhIeDhoaHE4iIiIeHhoaGh4eGhYaGhoWFhoaEhweIh4aGhYWFhIQPhYWFhISFhYaHh4eIh4eHhIYDhYaGhIULhoaHh4eGh4aFh4eEhgqHhoeIiIeHhoeHjoiGh4SIg4eGiIWJB4qKi4uLjIyFjQmOjo2Ojo+QkJCEjwGQhI+EjgGPhJAakZCQkI+Pjo6Oj5CQkpGRkZKRkJGSkpGRkI+EkQGQhpGFkAuPkI+Pj5GSk5KRkISRApCPhZGEkoKRhZCEj4ORhJASj4+Qj46Oj4+QkI+Pj46Pj46OhI0FjIyLiomEig6JiImKi4yLi4uMi4uKi4WKBIuLi4qHhyKGh4eHhoaHhoaFhYSEhYWEg4SFhoWGhYWFhoaGhYWEg4OD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A1OmpYSkF6KhoaGfn56fnZydnZ6fnp2dnqCho6OkhFIDU1KkhFIEU1RVVYhWJ1VVVVRVVVZXVlVWVlVVVVdYVVRUU1JTU1JRUlJSU1JTVFRVV1hYWIRZKlhYWFdWVlVWXF1bWVlXVlZXWFtcXF9fW1taWVhXVldYWVpaXF5bWFlaWYRXD1hYWlxeXl9hYmNjY2ZlYoRgCF9gX15eX15dAZKJkQGQhJEnkJCQkZGSkZGSkpOVlZSUlJOSkJCQkZKRkpKRkpOTlZSUk5OUk5SUhJOFlBOVlZWUlZWWmJmZmpmZmZiYl5iXhZgBl4WYH5mZmpqZmZiXmJmZmZiYmZqbm5ycm5ucm5qamZiampqEm4SaD5ucnZ2enZ6enZuZmZucnISbCJmXlpeXlpaWhJUvlpaXmJiZmpqbm5qZmJiXl5aVlZWXmZmcnp+goqGhoJ+dm5qYl5WUlJaVlZSTk5OFlIOShJOElIqTAZKEkYeSEpGPj5CQkZGRkI+Pjo6NjIuKi4SMD42NjI2Ojo+QkZCPkJCOjoaNB4yMjIuKi4uEigSJiYiIhomEiAmJiImJiIiHh4aFhweGhoeHh4aGiocHhoaHhoeHhYmGBYeGh4eGhYcEhoWEhY6GhYeDhoWFhoYHh4eIiIeHh4uIF4mJiIeIiImJiYiIiYmIiIeHh4aGh4iHh4gSh4iJiYqKiouLi4yMjYyNjI2NhI4BkIeRHo+QkJCPj4+QkI+Pj5CPj5CQkJGRkpCQj4+Ojo6QkIWRCZCQkJGSkpKTk4aSEJOTk5GSkpCQkJGQkJCRkpOEkoSRI5CRkJGQkZGSkpSUlJOTkpGQkJCPj5CSkpKTkZKSkZCQj4+PhJAHj5CPkI+PjoWNhIwSjYyMi4uJiYqKjY6Mi4yLi4qLhIwPi4uLioqJioqJiYeHiIeHhoYDh4WFhIQKg4ODhIWEhYSFhYSGDIWFhISEg4OCgoKBgYWCDIOEg4ODgoGBgYCAgIWBA4KCgYSCEIGCgYD//4GBgoODg4KBgIGGggmBgYGAgYKCgYGFgAX//v///5+AAYGEgAT/gICAhIEqgICA//7//4CA/4D/gID//v76/Pz+/f+AgP/+/4D+/v+BgYCBg4SDg4KBh4Ad//79//79/P39/Pr7/Pz6+fn6+vz7+vr6+fv7/P2GgAP/gICGgQmCgoKDhISDg4OEgg6Dg4WFhISFhISDhYWDg4SEUoODgoOEhISDg4SEhYaIiYiKiYmIh4iIiIeJiIqOjoyLi4mHhoiJjY+QkpKMjI2LiomJiouNjo6QkI2KjIyLi4uKioyMjpCRkpOVlpiZmZuamJeFlgaVlZWWlpP/gP+A/4D/gP+A24CCf56AhX+kgAF/ioCEfweAgH+Af4CAiX8JgIB/f3+Af39/kYCdf4aAAX//gIWAAgIEAAVIR0ZGRoRFA0RDRIVDHkREREVERUVGRkZHR0dGRkVFREVFRUZGRUNDQ0RDRIdFgkaGRwNISUmFSgxLS0pMTU5PT09OT0+EToVPE05OTk9QUFBRUFBQUVFRUFFQUlGEUgNTUlKEURRSUlNUVFRTUVFRUlJSU1NSUlRUU4dUEFNSUVBQUVJSU1RTUlFSUVGFUghUV1ZUVVRSUoRTE1FQUVJVWFlaWllYV1VVVFRTU1GEUBZRUlFQUFFQUVBPTk5OT1FRUlFQUE9Phk4HT09PTk1MTIRNBk5NTUxLS4VKCEtKS0tJR0ZGhEUBRoZIAUeHSARHR0ZGh0UBRoRFBEZFRUSEQxVCQUJCQ0NDREREQ0NDRENDQkFBQUCGQQxCQkJBQUFAQEBBQECGP4M+iD8EPj4+P4Q+Dj0+PT0+Pj09PDw8PT0+hT8KPj4+Pz8/QEA/P4c+Hj8+Pj8/QEBAQUFBQEFBQkFBQkJDREVGRURCQUJCQYRCEEFAQUBAQUFAQEBBQUFCQkKFQwZERUdISEiHRw9GR0lJS0tLTExLSkpKS0uETBNLS0pJSEdGRkVERERFRkhIR0ZHhEaERwNJSUuITA1NTU5PTk9OT05OTk1NhEwMTU1OT1BSUlNSU1JThFIWUVFRUlNUVlZYWFhXVVRTUlJRUVNVVoVXE1ZVVFNSUE9MS0xMS0tJS0pIR0eESBpHR0hISEpKSUhIR0dJSkpLTEtLSklJSEdHR4RGD0VDQ0NEQ0NCQD8/QEBBQYRCAkFAhT8HPj8/P0BAQYdCKEFBQD8/Pj09PT4+Pj8/P0BBQUFAPz8/Pj8/P0BCQ0VGR0ZFRUdGRkaFRQ9EREVHRkVFREVFR0dGRkeHSAtHSElKSkhHRkZHR4RGCEdHR0ZGRkdHhEkGSEhISUlIhUdYjEVFRYuKRERFikaMjZBIR0dHRkhISEdGjUdIR4+PjY5HR0eNR0lKTExMTk5MSkpKS0qSSUpKS0xLTE1NTUxKSUdFRUWIh4iIiYmIh4iGgoKCgX58fXx+fYZ8DXt8f4CAgECBQUFBgkGEQiZDQ0JDQ0VFRUZFRUNDQ0RDQ0JDQ0JBPj9CREVCQUE+PTw8PTw8PIQ7Uzw9QEVGRUVGRkVDQ0NERERCQUFCRkNDQkVGR0VGSUtLTExIRUNCQkJDR0hIS0tIRURCQkFAQEFBQkNCQkNFRUdISElJSkpKTE1OTk9PTEpJSElICV5dXVxcXFtbWoVZBlhZWVlaWoRbAlxbhFwDW1xchVsEXFxcW4RaCVtbW1xbW1xcXIhdhF4IX19fYGFgYWKHY4Rih2OEZIRjG2RlZWVkZWVmZmZnZ2doZ2dmZWVlZ2dnaGhnZ4VmGmdoaGdnaGloaGhqaWlqaWhnZmVkZGVlZ2hnhmWEZgZoaWloaGiEZhVnZmVkZWZpa21tbm1sa2lpaGhmZmSFYw1kZGNjZWRlY2JhYWFihmQYY2NiYmJhYWFiY2NiYmFgYWFhYmJiYWBghl+EXgFdhVuDWoVbBVxcXV1ehF0NXFtZWlpaWVpaWVlaWYdYhFeEVoRXCVhYV1dWV1ZVVYhUAVWEVAZVVVVUVVWGVAZTU1NSUlKEUwRSUlNTilIEUVFRUoVRClJRUlNTVFRUU1KHU4ZSBVFSU1NThVQEVVVVVIRVhlYEV1dWVoRVhVaCVYRUCFNTU1RUVVVVhFYGV1dYWFlZh1qGWwNcW1uEXQVeXV1dXoRdIFxdXFtbWlpaWVhYWVpbW1taW1tbWltbW1xcXV1eXl5fhV4XX19gYGBhYGFgYWFgX19eXl5fX2BgYWGHYhJhYWFgYGBhYmJkZGVlZmVkY2KFYRRjZGVlZmVlZWRjYmJhYF9eX11dXYRcB1tbXFtcXFqEWxFcW1taWVlZW1xcXF1dXFtbW4RaAVmEWBRXV1ZXVlZVVFRUVVRUVVVVVlVTU4ZShlMEVFVVVYRUClNSUVFRUFBQUVCEUYVShlEhUlJTVFVVVlZVVVZWVlVVVFRVVVVWVVdWVVVUVVVWV1ZVhVYVV1dWVlZXWFhWVVVVVlVVVFRUVVZWhFUEVldXV4tWD1WqVFRUqqlUVFSoVaurrIRWNVVWVldWVaxWV1esq6mrVlZWqlZYWFhZWVpaWVdXV1ZXrVdXWFlZWVpbWlpaWVhXVFRUpqamhqcNpaOjo6Kgn6Cgo6GhoYSgNp6foaOiolGhUVFRpFJTUlNTU1RTVFRVVlZVVVRUVFVWVVRUVVVUU1BSVVdYVlVUUlFQT1BQUIRPDVBQUVNXWVdYWlpZWFeEWDBWVVVWWldWVllbXFtcXmBfYF5bWFhXWFhZW1tbXl9dWlhWVVVVV1dXWFlZWVpbW1yEXQ9eX2FjY2RkZWViYF9eXl4JlZOTkpOSk5ORhI8CkI+EkISRA5KUk4SUg5OEkYSShJEBkoaTC5STk5SVlJSUk5SUh5UDl5iYhJkEmpmZmoSZh5oImZmam5ycnZ2FnASbnJuchZ0Wnp2dnZuam5ycnZ6enZ2cnJ2dnp6foIWfhKAIn6Cfnp2cnJuEnCudnZybm5qZmpucnJ2eoJ+dnZyampmYmZmZmJmbnqGjpKWjoqGgoJ2amZiXhJYamJmZl5aXlpeWlZSTk5SWlpeYl5iXl5eWl5eHlgGVhZYJl5eWlZSUlJOThJIzk5OSj46OjY2LjI2PkI+PkJCQkpKTkpGSkZCPj4+Ojo2NjYyNjYyMi4yNjIyLiomKiYmIhIkKiIiJiIiIh4mIiISHAYaEhw2Ih4eHhoaHh4aHhoeHhoYHh4eGhoeHh4WGhYUGhoaFhoaGiIUDhoWFioYTh4eIh4eIh4eHhoaGh4aGiIeHiIWJA4qIiISHC4iIiImLiomHiIeIhImFiAOJiIeFiAqJiYqJiYiJiYmKjIwMjY2Pj46PkJCPkJCPiZAWj4+Pjo6Pjo6Oj4+QkI+Oj46Ojo+RkISRD5KSkZKSkZKRkpKSlJWVlYWUgpOEkgyTk5KRkZKTk5OSkZKFkQeQkJGRkZKThJQJk5KSkZGQkZKThJSEkxGSkpKRkI+OkJCPj46Pj42Mi4SMDYuLjIyNjo6Oi4qKioyFjYWMAouMhYsBiYiIhoYGh4eHiIeGhISCg4aEBIWGhoaEhQmEhIODg4KCgoOFghSDhISEg4KCgYCAgIGBgoKCg4SDgoSDCoKCgYGBgIGBgIKGgQaCgoKBgYKEgQyCgoKBgYKCgYCAgYKIgYWAioGFgA7/gICA//+AgID/gP///4qAMP+AgID///3+gICA/oCBgYGCgoOEg4KBgYGA/4CBgoODgoODg4KCgIGCgYCA/fz9/oX/BP79/f6E/Bf7/P39/Pv8/f37/P7//v+A/4CAgP+AgIaBC4KCg4SEhYSEg4ODhIQNhYSEg4KEhYeHhYWFgoaDPYKCgoOEhIWGiYqIiYuLiYeIiIeHh4aGiImNiomJjY2Mi46QkpGTkY2Li4qLi4uOjo+RkY+NjYuKi4qLjIyEjRKOj4+RkpKTk5SUlZeZmJiZmpeFlf+A/4D/gP+A/4D/gJqADn+AgIB/f4CAgH+Af39/ioAEf4CAgIR/BICAgH+OgAF/kYCgfwaAf4CAgH//gIOAAgIEAAJHRoVECEVERURFRERDhUQCRUSERYJHhUgBR4RGEUVGRURERUVGRUVFRkZHR0hIhEkVSEhKSklISElKSUlJS0tMTU1OT05OhU8BUIRPAVCIUoZTBVJSU1NUhVUNVFRTVFRTU1RUVVZWVoVXhVYMV1hZWFdXVlVVVFNThFGEUilUVVVWVldXVlVVVlVVVFNSU1RVVldYWFhXV1lZWVpZWFZUVFNTU1RTU4RSK1NSU1JSUVFRUE9PTk9PT1BRU1NUU1NRUVFSUVFSUlJRUVFQUVFQUE9PTk2ETAhLS0tMS0xLSoZIG0dHRkdHSElISUhHR0hISEdHRkVGR0ZFRUVGRYRGhEUMRERDQ0JBQkFBQkJChUMFRERDQ0KEQYRChEGGQAw/QEBAPz8/Pj4+PT2EPoY/Cj4+Pj8/Pj49Pj2EPoQ9Bj4+Pj9AQIhBCEBBQEA/Pj09hT4FPz8+Pz+IQRVCQkJDQ0RERUNDQ0JDQkJCQUFBQ0KFQRFCQ0NCQ0RERUVFRkZGR0hIR4dIjUkESEhHR4RIEUdISUhHSEdHRkZGRUZGRUVFhEYQR0dHSEtMTExLS0tMTE1OToZNFk5PUVJSUVBQT05NTExMTU1OTk9QUFGEUg1TVFNTU1JSUlNUVldYhFkKV1ZVVFNUVVZYWoVbB1pZV1RRT0yGSgNJSEeERgZFRkdHR0iFSRpISElJS0xMSklJSUdGRkZFREVFRENCQkFBQYRACj8/QEA/QEBBQkGFP4c+Az9AQIZBBUBAPz8+hT0FPj4/Pz+FQIU/BUBCREZFhUYUR0dISEdGSEpLS0pKSEdHRkdHSEiHRxRISElJSkpJSUtLSkhHRkdIR0ZGRohHAUiESQVKSklISIVHFY1HSEhIRkVFRUZHSElJSEiNi4qJiIWHBIaHiYuEjQeMi4uMRkdIhUo4S0pJSI2LRYuMjEdKS0tLSklIR0dHRkZFRUSGhYSEg4KBgX9/f4B/fn16e3t8fX1+fX+Bg0JDQ0SEQ4VCAUGEQAlBQkNDRERDQkKERA5FREJBQkVFRUdFQkA9PIQ7hDoMOzs8PD5DRUNDRkpFhEEOQEBAPz9BQkJDQkREQ0GFQhBBQUFCQ0RDRUdJS0xKSEVDhEESQEFBQkNDQkNFRkVFRkdHRkZGhEeESARJSUhIAl1chFuJWgtZWVpaWVpaWltbXIRdB15dXV1cXFyFWwxaWltbWlpbW1xcXF2GXgtfX15dXl5eX19fYIRhEWJiY2NkZGRlZWRlZGNkZGVlhGYBZ4Vmh2cBaIdpCWhoaWhoaWlqaohrEGpqa2pqbG1sa2tqaWloaGiGZxNoaGlqa2trbGtramprampqaWhoiWkKa2xsbWxsamhoaIRnFmZmZWZmZ2ZnZmZlZGVkY2NiYmNjZGWEZxtmZWRlZmVlZWZmZWRlZGRlZGRjY2JhYGBhYWCEYQVgX15eXoRdDFxcW1tbXFxbXVxcXIRdglyEWxRaWllZWFhZWVlYWFlYWFhXV1dWVoRVBFZWVleHVotVA1RUU4hUAVOHUgZTU1JSU1KJU4dSB1FRUlFSUlKFUwVUVFRVVIVVB1RUVFNSUlKEUwdUU1NUU1RUhFUBVoRXBVhXV1hYh1aEVQJXVoRVAlRVhFaFVwNYV1iEWYNahlsBXIZbDFxcXFtbXFtbW1pbW4RcBVtaW1pahFkEWlpZWYRaCFxcXF1eX19fhV6GXxZgYWFhYmNkZGNiYmJhX19eXl9fX2BghWGHYg9hYWFiZGVmZmZnZ2dmZWWEZARlZmhohGkIaGdmZGNhX12EXCJdXFxbWVlZWFlZWVtcXFxbW1taWlpbW1xcXVxbWltZWVlYhVeGVoJUhFMKVFNUVFRVVVVUU4VSAVGEUgRTU1RVhVQFU1NTUlGFUIJRiFIBUYRQBVFSVVZVhVaEVxZWVlhYWVpZWVdWVlVWVVZWVVZWV1dWhFcKWFhYV1dZWFhXV4RWBVVUVFVUhlUCV1iFVwRWVldWhFUFqVVWV1aGVQhWVlZXV6yrqISmEqenp6amqKeoqKqpqKepqlVWV4VYEllZWFaqqVSpqqpXWVlYWFdXVoRVhFQIpaWko6KhoKGEogShn56dhJ4Hn5+en6KjUohTAVSEU4VSHVNUVFNUVFRVVlVWVldWVVNUVlZYWVhWVFFQT09PhU4RT09QUVNXWVdXWl1ZV1dXVlWEVA5VVldYVldXV1ZWV1dXVoRVDVZXV1laW11fXl1bWVeEVhVYWFlZWlpaXF1cW1xcW1tcXF5dXV2IXgKUkoqRipASkZKRkpKTlJSVlJWTkpOTk5KShJMKlJSTk5STk5SUlYiWJZWXlZWXlpaXl5eYmJmZmZqampubmpubnJybnJydnZ2cm5ucnJyFnRGenp6fn5+gn6Cgn5+fnp6enYSeBZ+goKKihqEQoqKjpaWkpKSjoqGhoKCenYaeIZ+goKChoaGioqKjop+enZydnZ+enp+enp2cn6GhoqKioIadIpybmpmZmpubm5yampmamJiXlpeYmJianJycm5uZmJmamJmEmguZmZmampqZmJiXloqVBJSTkZGEkISPDZCQkI+RkJCRkZKSkZGFkA+Pj46NjIyMjY2Li4uKiomFiAGHiIiFiQ2Ih4eGhoeHiIiHhoeHhIYEh4aGhYSGCYWFhoaFhYWGhYeGDIWGhYWGhoWFhoWGhoSFBYaGhoeHhoiEhweIiIeHhoaHhIaHh4SJAYiGiQuIiIiJioqJioqJioWJA4qLioSJC4iJiYmIiYmKi4uKiIuCjIWNBoyNjY6OjoiPDI6Oj4+Pjo+PkI+OjoWPBpCQkI+Pj4SQg5GEkoKThJILk5KTkpGRk5SUk5SHlQeUk5KTk5STh5IdkZKSk5OSkpKRkJGRkpOUlJSVlJSTkpKRkZKTlJSHlwiWlZSSkI+OjoWPgo6FjQSMjYyMho0fjIuMi4yNjY2MjI2MjIyLioqLi4qKiIiHh4eGhoiHh4aGCIeHh4aGhYWFh4QIhYWFhoWFhoaEhQKEg4eCBIODhISEgw6CgoGBgYCBg4SCg4KDgoWDA4KBgoWEDIKBgYCBgoODgoKCg4WBCoKDg4OCgoODgoGFggWBgICBgYmABYGCgoOChoEGgP+AgYGBhYAfgYGAgYGB///+/f3+/f39/v3+//78+/v8/P7+/4CBgYSDE4KCgoGA//+A////gIKDgoGCgYGIgIL/hP4B/Yb+Dfz//fz8/P39/fz+/v+EgAaBgICBgYKEgTiAgIGBgYKCg4SEg4KDhYWGhYeGhIOFiIeHiYeFhYKDg4SDg4KCgoGCg4SEhYqLiYiLj4mGiImIiISHBYmJioqJhIsCjI2Hiy6MjYyOkJGTk5GPjYyLjIyLjI2Njo+OjY+RkpKRkpKSkZKSk5SSkpOTk5SVlJSV/4D/gP+A/4D/gP+AmoABf4+Aln+MgAZ/f4B/f3+QgJp//4CLgAICBAAHRkVEREVGRoRFA0RFRIRFC0ZFRkVFRkZHSElJhEoRSUhGSEhIR0dGRkZHRkZFRkaGRwRISUlKhEuGSh5LS0xNTExNTU1OT1BQUVJRUlFQUFFTU1RVVVVWVlaFVQFUhFUYVldZWVlYV1ZXWFdYV1laW1xdXl1dXFtahFgaWVlaV1RUVFNRUVJSUVFRUlNUVldWV1dXVlWEVgdVVFRWVlRThFYSV1dYWFhZWltaWVhXV1ZVVFNShFEPUlNTVFVWVlRVVFJSU1RUiFMIUlJTU1NUVFOFVAFThVIHUVBQT09PToRPClBPTk1MS0pJSUmESCRJSUpKSklISEhJSUhIRkZGRUVFRkZFRUVERUREQ0RDQ0JCQkGEQAJBQIhBBUJCQkNCh0EfQEBAPz8+Pj8/Pj8+Pj8+Pj0+PT09Pj4+Pz4/Pz4+PYY+Cz8+Pj49Pj0+PT4+hD8GQEFCQkNDhEIHQ0VDQ0JBQYRACz8+PT0+Pz9AP0BAh0EFQkNDQ0SFQwlCQ0JCQUJCRESFQwdERERFRUVGhEeESINJhkiCSYhIhEcFSEdHR0aERwxISEhHSEhIR0dISUmHSg9LTExNTk9PTk1OTU5PT0+GThRNTk9RU1NUVlZVVFFQTkxLS0pLTIROEk9QUVJTU1RTUlFRUlRVV1hYWIRZCFhYV1dXWVpbhFwWW1tZVlRSUE5NTUxLTExKSkhGRUZFRoRHF0hJSUpKSUhKSktLSklIR0ZGRURFREREhEUOQ0NCQkJBQUFAQEA/Pz+EQIU/hD4GPT4+Pz9AhEGFQCc/Pz49PDs8PD0+Pj8/Pj4+Pz8/Pj4/QkRGR0dHRkZHSUlKS0tLTEyETQRMSklIhEkFSklKSkiERgFJhUoTSUpLS0lJSEhJSUhISEdHSElKSopJAkhHhEhCSUlISEhHRkWLRURFRUZHR0ZGi4mLSEeMjIqJiIeIh4mLikWKi4qJjI5HSEdGiYmHh4dDhYWGhoeKi4qKR0lKSUdHhUUkRIiGhIKCg4OCgYGAfn59e3p7ent9f4CBgoNDREVFRkZFQ0JChUEBQodBHEJEREJCREZFR0lISktJR0lKSktNS0pKQzw6OjuGPBA9PT5AREdDQ0pRUEtGRURChT8wQEFDQkJCQUFBPz9AQEBBQkNCQkFCQUBCQ0NERERFRERDQ0JCQkNERUZFRUREREVFhUQKRUVGRkdIR0ZFRgJcXYVbglqEW4laGFtbXFxdXV1eXl9eXl5dXl5dXV1cW1tbXIVbBFxdXl6MXwVgYGFhYYRiD2NjY2RkZWVmZ2ZmZWVmZ4VoBGlpamqEaSpqampra2tsbG1tbGxsa2xtbGxsbW5vcHJycXFwcG9tbW1ub25vbWpqammEaENmZWZnZ2lqa2xsbW1rampra2pra2pramloa2xsa2tqa2tqa21ubWxsamlpaWhmZmVkZGRlZmZoaWtqaGpoZmZnaGhoh2cJZWVmZmZnZ2dphmgIZ2dnaGdmZWSJYwZiYWBfXl6GXQdeXl5fX11ehV0DXFtbhloIWVlYV1dXVlaEVwVYV1dWVoVVBFZVVVWEVgNVVVaEVYZUAlNShlMQVFNTU1JSUlNSU1JSUlNSUoZTDFRTU1NSU1NSU1JTUoRThVQFVVZWV1eEVgtXV1ZWVlVVVFNTVIVTFFRUU1NTVFVWVlZVVVZXV1dWV1dXh1YEVVZWV4ZWg1eEWIVZhFoBW4VaBFtbW1qIWwlaWltbXFtbW1qGWwNaWVuFWoNchl0QXl9fYGFiYmJhYWFgYWBgYIZhDmJjY2RlZWVmZWRiYV9ehF0JX2FhYGBhYWJihGMHYmJhYmNkZoVngmiFZx5oaGlqamppaGhnZmNiYF9eXl5dXl5dXVtaWVlZWluEWhBbWltbWltbXFxdXFxbWllYh1cDVldWhlUBVIpTEFRTU1RUU1NSUlJRUVJSU1OEVIVTCVJSUVBQT1BQUIRRAVCFUSBQUFBSVFVWVlZVVVZYWFlYWFhZWltbW11bWlhWV1hYWIRZBldWVlVWV4VYBFdYWViJV4JWhVeDVoRXCVhXVlZWVVZWV4ZWZFWqVVRUVVVWVlVVqaiqVlWpqKempqenpqeop1Onqaqqq6xWV1dWq6upqqpVp6SlpaWoqamqVlZXVlVVVFRVVFRTpqWjoaKjo6KhoKCfnp6enJ2enp+goaOjpFNUVVZWVlVTUlKHUyFSUlJRUVJTVVRTVFZXV1laWVpbWlhaW1pcYF5dXVZQTk6FTyhQUFBRUlRYWldXXWRjXltaWlhVVVRUVFVVWFZXV1hXVlRUVlVVVldXhVYEVVdYWIZZH1hYWFlYWlpbXFtbWllZWlpZWVpbWltbW1xcXV1cW1wFk5OSkpOGkguTk5OSkpKRkpGSkoSRCpKUlZaWl5aUlJSHlQ6UlZaVlJSVlJSVlZaWl4WYA5eYl4aYCJmZmpqam5ubhJwInZ6enpycnJ6Gn0Sgn5+en56foKChoqKhoqKjo6KioaChoqGioaKjpKamp6anp6elpKSkpaWlpqOfoKCfnp+fn56dnZ6foKGioqOkpaOhooWjTqKioaGgoKCenp6dnp6dn6Kko6OioaGfnpyamZmZmpmZm5qbnZ6dnJ6dm5ucnZ2dnJycnZ2enZqampybnJycnp2enp6dnZycnZ2cm5qZmISZCZiYmpiYl5WUk4WRhJAFkZGRkJCFkgSRj4+PhI4DjIyLhIoPiYmJiIiHiIiHh4eGhoiIhIcChoeEhgOHiIiGh4aGhIUGhIWFhoaGhYWChoeHB4aGhoeHhoeGhgaHh4eGhoeIiAOJiYiFiYWICoeHhoeHh4aHh4eEiAOJiYqFiQuKiYiIiYiJiYmKioSJBoiJi4uKioSJBoqJioqKi4SMAouMhY2EjIiNA46Oj4SOBo+Pjo+OjoSPDZCRkZCRkI+Ojo+RkZGEkBGRkZKSkpSVlpWVlJSTk5SUlIWTKpSTk5SVlpeXlpeXlpSTk5KSkpGRkpKRkZCRkZKSk5OUk5KSkZGTk5WVlIaVJZaVlZWXl5iYmJmZl5eWlJSTkZGQkJCPkJCPj46NjIyMjo6OjY2FjhONjI2Oj46OjY2Mi4uKiouLi4qKhYkEiImIiISHhoaDh4SGA4WEhYSEBIWFhYaEhQuEhIWFhISDg4KBgYSCFoOCgYGBgoGBgYCBgoOFhYSEgoKChISEhYaGDoSDg4KDg4OEhYSEhYSBhIIMg4SEhIODg4SDg4KCiIEIgoODg4KBgYGFgjCDgoGBgIGBgoGAgIGBgYD/gICAgYCBgYCA//7/gYD//v79/v7+/f7+/YD+/v39//+EgAb+/v7//4CH/y/+/4GCgoGAgYCAgYGAgP7//fz8/f7+/Pz7/Pr6+fn7+vr8/Pz+/v6AgIKDhIODgoiBI4KBgoKBgYGChISDhIaGhYeHh4iKiIaIiYmKjYuLi4eCgYGCh4MPhISGiYyJiI2Tko2MjIuKhYgEiYqLioSMBIqJiouEioSLC4yMjI2Ojo+Pjo+OhY0EjI6PkIWREpCRkJCQkZCRkpGSkZKTk5KSk/+A/4D/gP+A/4D/gKCAAX+JgAV/f3+AgIt/AYCGf4SAhX8BgIl/jICZf/+AjoACAgQAgkOERIRFCUZGRkdHR0ZFRIRGhkeJSApJSUlIR0dGR0ZGh0cRSElJSkpKS0tLSkpKS0xMTE2EThZPT05PT09QUFFSUlNTVFVVVlZWV1dYhFcMVVVWV1hYWllZWlpahVs2XV5gYWJlamtsbGplYF5dW1xcW19fXl1cWFhXVlVUU1JSUlRVWVtbWVlaWllXVVZYVlZWV1dWh1gQWVpbWlpaW1taWlpYV1ZUU4ZRCVJSU1VVVlVWVoRVglSFUwhSUVFSU1RUVIhVFVZVVlZVVVJSUlFRUFBRUVFQT05NTYRMB0tLS0pJSkuFSh9LSkpJSUhIR0ZGREVFRUREQ0NDQkJDQ0REQ0NCQUFChkEFQEBAQUCFQQRAQUBAjD+NPgc9PT4+Pj8/hj4BPYQ+iD0KPj4+Pz9AQUFBQoRBDEBAP0BBQUFCQUFAQIs/B0BBQkNCQkKEQwJCQYVCDUFCQUFCRENCQkNCQkOERAVFRkVGRoZHg0iER4VGBUdHSEhIhEcCRkeERoVFDkZHR0dISEdISEpKS0xNhkyISwNMTU2ETwhQT09PUFFRUoRUJFVVVVRSUU9NTEtLS0xNTE1NTlFTUlJTU1JSUVJUVFVWV1dXWIRZHlpZWVhYWVpaWltaWldVVFJRUE5OTU1NTExKSUhIR4VGAkdIhkksSElKSUlISEdGRkVEQ0JCQkNFRkZFRENCQkJDQ0JBQUBAQD8/Pj4/Pz9AQECEP4Q+gj+JQAY/Pj08PDyFPQE+hz0qPkBBREVGR0ZGRUZHSUtNTUtLTE1OTk9QTk1LSklISElJSUhGRkVFRUZIhUsSSkpKSUlKTExMS0pKSUhHRkdHhEiFSQZIR0hHR0eFSIBHRkZGR0dGRUVEQ4WFhYODgkJERYlFiYWBf32Ch4mJikVFRERFiUVFRomHhIKBgoOCgoKDgYGBg4OFiEVFRYtFRUREiESIhkSGhYWEg4N/f31+fXx9fX6AQUJDhkVFRUZFRURDQkFAQEBBQUNDQ0JCQ0NDREZHSEpIRkdISEtNThNPUVBNTEtMTlNVVlZLQT49PT4+hT8RQEJCREZHTVhaXV1ZT0dBQEGGQgRDREJAhj+EQIdBAkJDhEQhRURERENDQ0RERUZGRUVERENCQ0REREVGR0hIR0dGRkVFAVqMWxBcXFxbW1tcXFtbXFxdXl5eiF+CXoddFFxcXV1dXl1eXl5fX19gYWFhYGBfhGACYWKFY4RkBGVlZmaEZwxoaWlpamtra2xrbGuEbAVtbW5ubYVvNXBwcHFyc3V1dnl9foCAfnh0cnJxcXFwdHRzcnBubW1sa2poZmVmZ2ltb29ubnBvbmxrbG5thWwmb29ubm1tbG5ubm1tbW9wbnBvbWxraWZkY2NiYmJjZGZoaWlpaGmFaBJnZmdnZmVkZWVlZmhoaGlqa2qEaYJqhGsZaWhnZmVkZGRlZmVlY2JhYGBfX15eX15eXoZfCmBfXl1dXFxbWlqEWYJXh1YMV1hXWFdXVldVVVVUjFUTVFRTU1JSU1NSUlJTUlNSU1JTU45Si1MHUlNTUlNTU4RShFMEVFRVVYhWClVVVldWV1ZWVVWEVIdTD1RVVlZWVVZWV1dXVlVWVYZWCFVWWFZVVVZVhVYBV4dYglmHWodZClpbW1tcW1tbWlmHWoRbCFxbW1xcW1xchF2PXgNfYGGEYgNjYmKFYxdkZGRlZWZmZWRjYl9fXl5eX2BeX19gYYVjCGJjYmJkY2RkhGYoZ2dnaGhpaWloaGppaWppaGdmZGJhYF9eXl9eXl5dXFtbWllaW1paWoRbJlxcW1tbXFxcW1taWVlZWFdXVlZWV1dYWFdWVVZVVlZVVVVUVFNThFIJU1NUVFRTU1RThFIGU1NUVFNThlIFUVFQT0+IUD5PUFBPT1BRUlRVVlZXVlVWWFlaW1tbWlpbXFtdXlxcWVhYV1dZWVpZV1dWVVVVVldYWFhZWFhZWVhZWllZWIRXEFZVVlZXV1ZXV1dYWFhXVleIViBVVlZVVlZWVVVUUqOjpKOkpFFSU6VUqKajoZ+ipaanqIVUGqlUVFSpqKempaalpKOjo6Gio6Wmp6dUVFSphFQip1OmpFOlpaalpKShoqGhoJ6goKChUVJTplRUVVZWVVVUU4VSQFNVVFRTU1RTU1RVVldZV1ZXWVpcXV1eYGBdXFxeYWVoaGheVFFQUFBRUVFSUlJTVVZYWVpfaWxubWpiXFdWV1iFVwNYWViEVodVg1aEV4ZYAVmGWAdZWFlaWllahlkNWlpaW1xdXV1cXFtbW4KRhJKFk4WUEZOSkpOSkpKTkpSVlZWWl5eXhJYElZWUlYSUiZUBloaXAZiEmYSaF5ucnJybm5ycnJ2cnZ6enp+enp2fn6CghKEloqKioaCgoaKjpKSlpKWmpaWmpqempqeoqausr7O1trazrqqpqYSnMaurq6impaWko6Genp2cm56gpaiop6eop6WjoqKlpKOjpaalpqalo6OjoaGioqGhoqOEpBCjoZ6bmZeWl5aVlZaYmpydhJ4OnJydnZybm5qampuampqFmxaen6Cgn5+en5+foKGhoJ6dnJqZmJiYhJkamJeXlpaVk5KTk5KSkpOSkpOSk5STk5KRj46HjRGLiomKiYmIiYiIiYiJiIeHh4eGBIWFhoaGhYWGhIUBhoaFA4aGhYSGhIWFhgWHh4eGh4yGBYeHhoeGh4cEiIiJh4WIBoeIiImJioSJDoiJiIiIh4iHiIiHh4iJiYoHiYqKiYmKiYSKAouKiYmCioaLBIyLi4uEjIKLiIyCjYaOhY8Gjo+Pj46PhZAej5CQkZGSkpOSk5OSk5STlJSUlZSTkpOTlJaWlZWVhJQQlZWVlpaXl5eYmJeWlZWUk4SSF5OSkpGRk5WUlJWVk5OSkpOTlJWWlZSUhJUglpeXl5aWl5eYmZiYl5aVlJKRkJGQkZGQkI+Ojo6NjI2FjoKPhI4DjY6PhI4IjYyMi4qKiomFigOLioqFiQqIiIiGh4eGhoWFhIYGh4aFhYaGhoUChoWEhISFBoSDgoGBgoWDAYKFgQSAgIGChYQSg4KCg4WGiIiGhYWGhoaHiYaGhYULhoWGhYODgoGBgoOFhQGEhYMIhIODg4KCgoGEgAmBgoKBgoODg4KEgSeAgIGBgYKCgYCAgIKBgYCBgYD//v79/v+AgID+gP/9+/r4/P3+/v+FgDr/gICA//39+/r9/v7///38+/v8/f7/gICA/4CAgYD/gP/+gP///v37/Pz8+/r5+fz8/f+AgID/gICAhIEsgoKBgYCAgIGDgoGBgoODgoOEhIaIh4aGh4eJiouLjY6Li4qMjpGTk5OMhIOJhT6Gh4eJiouPl5mcnJqTjoqJiouLi4qLi4uMjIqJioqJiomKiomKjIyLjIyNjY6OkI+PkJGQkI+Ojo6Pj5CRkYWQEI+PkJCQkZGTk5KTk5OSk5P/gP+A/4D/gP+A/4CmgIZ/BYCAgH+Ain+FgAR/gICAkn8EgICAf4SABX+Af3+AkH8EgICAf/+AkIACAgQAiUUtRkVGR0dGRUVGRUVFRkZGR0dHSEhJSUlKSkhJSElJSklISEdISEhHR0hISUlJhkqGSwFMhE0FTk5PT0+EUIRREVJSU1NUVVRVVlZWWVpaW1tahFk7WlpaXF1dXl5eX2BgYGFjY2Zoa21vcnV3dW9raWhmZGNjZWhoZWZlY19ZWVdUU1NUV1pdYmJgX15eXl2FXihdXVxaWVpbW1pZV1dYW1xbWlpbXF1cXFxaV1RSUlFSUlJTVFVVVlVUhVWGVAVVU1JSVIRTAVSFVQRWVldXhFYEVFJSUoRRBlBPT09OToRNCkxNTExNTUxLS0uETARNS0pIhEcURkVEREVERERCQ0JCQkNERERDQ0OIQgJBQoVBhEABP4U+gz2KPgE9iT4BPYU+gj+LPgM9PT6EPYQ+BT9AQUFBhEIGQUJCQkFBiUAKQUFAQD8+Pj4/QIdBAUKEQwZEQ0NCQkKHQwhCQ0JDQkNDRIZFBEZHR0eFSIJHhUYHRURFRUdHR4VIEEdHR0ZFREVGR0dHSEpKS0yGTQpOTU1OTk1OTk1LhEkfSEpLTE5PUFFSUlFRUVJUVVVVVlVWVVRUU1JRUFBOTIVLFEpLTlFUVFRTU1JSUlNUVVVVVFZXiFkBWIZZD1dVVFJRUVFSUlFPTkxKSIRGAUeFSIRJAUiFSSpKSEhJSEhHRkRCQUFBQ0RFRUZEREJCQ0NDQkFBQEA/Pz9AQD8/Pj4/Pj6EP4Q+CD8/QEA/Pj4+hD8EPj08O4U8DT09PDw7Ozs8PkBCREWGRglFRUZGSEpLSEiFSwRMS0lJhEgOR0lKSklIRkZHSElKTEyHTR5MTUxLSkpJSUhISUlISElJSkpKSUpJSklIRkZGR0eFSFVHR0ZFRUVEiIaCfn18fHx+gYKBgH9+fXt8f4KEhIOAf35+e3x7fn19fHl2d3x7eXh4eXl6fH6AhENERUVFRIaCgX+BgYCCgYGAf4GDhoeGh4aFhUNDhURXQ0NCQkJBQEA/QEFBQUJERENDRERDQ0RGR0hLTEtLSktNUFJUVVVTUlFSUFNWWF9nY1FNTEpIR0dFREJCQkNERkxVW2FnaWNSTElFRERFRURCQ0JCQUA/iEAVQUJBQkFCQkJDQ0REREVERUdHRUVFhUQERUZGRYVEDEVFRkZISUpKSUdGRoVbhFoHW1tcXV1cW4RcBVtcXF1dhF4QX19fYGBfYF9fX15eX19eX4ZeAl9ehl+DYIRhhWMPZGRlZWZlZmVlZmdnZ2hohGkMamprbGxtbm5ub25uhm91cHFycnNzc3R1dnd5eX1+f4GDhYiKiYR/fXt6eHd3eXt7ent5d3RwcG5raGdpbG9wdnZ1dHR0c3JydHN0c3JycnFvb3Bwb29ubW1ub25vb3BxcXFwb21qZ2VkY2NjZGVmaGhpaGdoaGhpZ2ZmZmVmZmdmZmZnhGgJaWxramlpampqhWsHaWhnZ2ZlZoRlAWSEYghhYGBhYmJgYIRfhGAXYWBfXVxcW1paWVlYWVhYV1ZWVlVWVleGWAZXVlZVVVWFVANVVFSEU4NSh1GVUpFTA1JSU4RShFMFVFRUVVWHVgZVVVRUVVaEVQFUhFUGVFRTVFNThFQKVVZWV1hYWFdXV4dWAVWEVglVVlVWVlZXV1eEWAVZWVpZWYRaBFtaWlqEWQVYV1hZWoRbHFxbW1paWllZWFpaW1tbXF1dXV5fX15fX19gX1+EYANfX16EXQdcXV5fYWFihWMWZGRlZmZlZWZnZWZlZGNjYmFgXl5eX4ReDWBiY2RkY2NiYmJjZGSEZQlmZ2hnZ2hpaWmHaDVnZmVkYmFiYmNiYV9eXVxaWVlaWltcW1tbXFxbW1pbW1xcXV1dXFxbW1paWFdVVVVXWFlYWYhXBVZWVVRUhFOEUgFThFIHU1NSUVFSUoRTAVKHURlQT05PUFBPUFBPT05OTk9PUFFUVVZXVlZVhFYkV1dYWltZWVpbWlpbXFpYV1hXV1hYWFlZWFhXV1hYWFlaWlpbhVoFWVpaWViIVwZWV1ZYWFiEVwpWVlVVVVZWV1ZXhFYiVVRUVFOlo6Gfnp2dnZ6goaGgn56dnZ6ho6WlpKKioaKhoIWfKp2bm5+enJucnJ2cn6GjpFNTVFVUVKaioqCioKCioaCgoaCjpaSjpaSjpYVTglSFUwlSUlJRUVJSU1OEVD5VVFRUVVZYWFpbW1taW11fYWJjY2JhYmNiZWdpbnRxYFxdWlhXV1dWVVVWV1dZXWVtcnd5c2VeW1hXWFhYV4RWhFUMVFRVVVZVVVVWVldXiFgHWVpaW1tbWoVZA1paW4VahFsKXFxdXl5fXVxcXAWSkpKTk4SSNJOSk5SUk5KSk5KSkpOTlJWVlZaWl5iYmJeXmJeXlpaVlJSUlZWVlJSVlZaWl5iZmJiYmZmJmgGchp0Tnp6enZ6enZ6foJ+foaGio6OjpISlC6Sio6SlpqenqKiohKk6q6ytrq6usbK1tri6vr+9uLWzs7Cvrq6ws7Gvr6+uq6emo6CcnJ6ip6mwsK+uraysq6ytra6urKysqYSoAqalhKQapaSkpaeoqauqp6OemZiXmJiXmJmanJydnJuEnIKbhZoLm5mamp2cm5ubnJ6GnyKgoaGgoaGfnZycm5qampmZmZiWl5eXlpaVlZOUlJSTk5OUhJUFlpSSj46EjQyMjIuLioqJiYiKioqKiYWIhocEhoWFhoWFhIQEg4SEhJCFBYaFhoaGjYeEhhiHhoaHh4iIh4iHiIeHh4iHiImIioqKiIeHiIeJBIeIh4aEhwSIiYmKhIsEjIyLioiJIIqKiYmIiYiIiYmKiouKi4yLjIyNjY2MjI2MjIuLjIyNhIyCjoaPC46Oj4+OjY6PkZGRhJIbk5OTkpGRkpKSkZSUlJWVlJORkpKTk5STlJWWhJcElpeXl4WYDJmampmYlpeWlpWUkoeRIJOUlZWVlJSTk5SVlpWVlpaVlZaXl5aXl5iXlpeWlpeWhJcPlpSTk5OUlJOSkZCQj42Nh44Yj5CQj46Oj4+PkJCPj4+OjY2Mi4qKiYqKhIsfioqKiYmKiomJiYiIh4iHiIeGhoWFhoWFh4aHh4aFhYSGC4WEhISFhIWGhYSDhIIIg4KCg4KCgYGEgAKChIWFI4SFhIKDg4OFhoaEg4WFhYaGhoWEhISFhYaFhoaGhISDhISEh4UKhIWEhIOEhIOCgoSBCYKCgYGCgoOEhIWCBIGAgICIgTeCgoGAgID+/fz6+vv6+vv7/Pz7+fj49/j6+vz8+/r5+vv7/f38+/r6+Pb29/v7+vr6+fj7/P3/hoAE//z9/Yb+C/r3+/v7/f3//v7/hICDgYWACYGAgICBgYGCgYSChYMehISFiImIiYmKi42Oj5CQjo6OkI+SlZabop+QjYyJhYggh4iJiYmLjZWbn6SnopaRj4yLi4yLi4mKiomKiYmKioqEiwKKi4SMKo6NjY6Ojo+Pj5CRkpGSkZCQkI+Pj5CSkZGPj4+QkJGRkpOUlZWVlJOSkv+A/4D/gP+A/4D/gKWAsX+GgJV//4CWgAICBAADR0hHhUaERYJGhUUSREVFRkdHSEhISUpKS0tMTEtLhUqCSIZJAUiESYNKhEsiTEtMTExNTU5OT1BQUVFRUlNUVFNUVFRVVVZVVVZXWFdZW4ZcAVuFXBpdXl9gYGFiY2RlZmdoamxucnV3eXd3eHd3d4V2T3V0b2xra2poZmJeXV5fYWVoaWZkYmFgYF5fX19hY2RlZGBdW1lZWVhYWFlbW1pbW11dXV5eXVxcWlhXV1ZXVVVVVldWVlZXWFZVVVRTVFSGVQpWVlZVVVRVVVVWhVcOVlRTU1JSUVJRUlFSUVGFTw9QUFBPUFBQT05OTUxMTU2ETAVLS0lGRoVFCEZGRENCQUJBhEIJQUJDQ0RDQkJChEOEQgZBQEA/QD+KPgY9Pj49PT6FPQg+PT4+PT0+Pog/iz4DPT0+hD0NPj0+Pj9AQEFBQUBAQIZBhUCHPxRAPz4/QD9AQD8/QEFBQUJDQ0RFRYRDCUJDQkNERUVEQ4VCAUOFRAVFRUZGR4VIhEcBRoVFG0RDQ0VFR0hISElJSUdFRUZGR0dJSUtLTE1OT4ROhE8ZTk5NTk9OTkxLTExLS0xMTU5PT1BQUlJTU4RUDVNTU1RTU1FRUVBPTk6GTQROUFFShFMDVFNUh1UUVldYWVlZWFdXVlVVVVZWVVVUVFSEVQ1UU1FPS0pKSUhJSUpJhEqESQhISElLSktKSoRJGUZGRUVGRUVGR0hIR0dFRUNCQkJBQUFAQD+GQIg/CT4/Pj49Pj49Poc9JD49PDw7Ozw8PT08PDw7PDs7PT9BQkNERUVERUVGRkVGR0hKSoRJA0hGRopHFEhISUhHSElKS01PUFFRUFBPTkxLhEoXSUlJSkxLS0tKS0tKSkpJSUpJSUhIR0eGSFFJSEdGRkdFRkSHhIB+f317ent9foCAfn56ent8fX5/fnx6eXZ1eHd5ent6d3RzdHV3eHp7fn5/f4RGR0lIRkSGgYKAgIKFiItGRkZHSEpKSkiERgFFhESGQ05CQYFAQD8/QUREREJAQEFBQkVHSUtNTk9OTUtLTE5RVVlZV1dXVlZWXWdyfnlqZmJYUE1MTEtLSk1SWV1iZm5tZ2BaU09NTEtJSEdGRUSFQoJDhEQVQ0RERENERERDRERERURFRERFRkVFhkQDQ0RFiUYJR0lJSktLSUhHBlxdXV1cXIZbh1wGW1xcXV1dhF4DX2BghGEGYGFgX19fhl6GX4ZgDWFhYmFiY2NkZGVlZGWFZgJnZoRnDWhoaWtqamtsbW5vb2+EcIJxh3JBc3V2dnd3eHl6fH1/gYOGioyNjIyMioqJiYiIiIqKiYV/fn59enl3c3Bxcnd8fn57enh3dnZ1dnV2eHh6e3l2c3GEbxltbW1vcG9wcHJzdHNycXBvbWxra2pqaWhohGkEaGlpaIdnGmhpaWhpamppaWhoaWlpamprbGxqaWloaGhniGaIZB1jZGVlZGNhYWFgYWFgYWFhYF9eW1lYWFlYWFhXV4VWCVdXV1ZXV1dYWIVXAVaHVQFUhFMCUlOFUo1RjFIEU1NTUopTAlJTh1KEUwZUVFRVVVaKVYZUA1NUVIZTEVRUU1NTUlNUVVVWV1dYWVhXhVYIVVVXV1dWVVWFVoNXhFiDWYpaAVmHWBVZWVpbW1tcXFtaWVlaW1xcXV1eXl+EYAFhhGALYWFgYGFhYWBgYF+EXhxfX19hYWFiYmNjZGRkZWVlZmZmZWVlZGNkY2Nhh2APYWJiZGVlZGNkY2NkZGVmhGUeZmdnZ2hoZ2dnZmVlZmZlZWRkZGVmZWVkY2JhXl5djlyDW4ldAltahlkHWltbWllYV4ZWClVVVlVVVVRTU1OKUhBRUVFSUlFRUFBRUVFQUFFQhU8kUFBQT09OTk9OTU5RU1RVVlZVVFRVVlZVVVZXWllZWVhYV1VWhFcdWFhXVlZXV1hZWFhYWVpaW1xcXV5dXVxbWllYWFmEWIVZBlhYWVhYWIVXhFYCV1aEVwNYV1aFVBBTpKKfnZ6dnZycnp+hoaCghZ4/oKKhnp2cmpmbmpqbnJqYl5eZmpydnqGio6SjpFRUVlZVU6WhoqGho6Skp1RUVFVXWFhXVlRVVlVUVFNTU1JShVMHUqRSUVFRUoRUCVJSU1JUVlhaW4VdLVxcXV5hZWdnZGVnZ2ZnbXV9iYV1cW5lX15cXVtaW1xhaGxxdn19eHFrZWFeXIRbB1lXVlZXV1eIWIJZiViFWQdaW1taWVlZhFqEWwFchFsLXFxdX19eX2BeXVwJlJSUk5KTkpOUi5MQlJWVlZaXl5eYmZiZmZiZmYaYhZYbl5eZmJiZmJiZmZqbm5uampqbm5ucnJ2cnZ6ehJ8EoKGgoIShFKKjoqGjo6OlpqaoqKenqKemp6iphKoSrK2sra6tr7GxsrK1t7m8vr/BhL8xwMC9vb6/wL+9ure3t7a0sq6sq6utsLS3ube1srGvsK6vr6+ytLa4trGtqqioqKempoanIKmqq6urqaimo6GenpydnJucnZ2dnJydnp2cnJubnJybhJwgnZ6fn56dnJ2dnp+goaKjoqGfnp6dnJqbmpubnJuamZiHlwmYmJmXlpeWlpSGlRSTkY+MjYyMi4uLioqJiIiIiYiJioWJE4qLiomJh4eHiIiJiYmHh4aFhYaEhYOEhIUVhISFhISEhYSFhYaGhYWFhIWFhYaHhIiEh4WGA4WGhYSGBIeGhoaFh4SIDYmIh4eGh4eHiIiHh4aKhwqGhoiIh4eHhoaHhIiCiYSLh4oUi4uKiomIiYmJiImKiomJiouLi4yEjQSOjY6OhI0Ljo6NjY2MjI2Nj46GjweOjo+PkJCRhJIJk5SVlZSTlJOThpQGlZSUlJWThpQGlpWVl5eXiJgGmZqamZmYhJc5lpWUlJOUk5STk5SVlZWTk5SUlJWUlZWUlJSVlZeYmJiXl5eWlpWVl5iXl5eWlpaYl5eVlpSRkJCRhI8DkZCQhI8HkJCQj4+PkYSQC4+Qj5COjYyNjIyMhI0HjIyLioiJioSJCIiJiIiJiIiIhIeEiA2GhoeGhoWGhYWGhYWFhYQBg4SCAYOGggyBgoGBgoOEhYaGhoWFhAeDg4SFhYWEhIWCg4WEA4WFhoWFB4SDhYSEhIWEhgSFhoWFhIQYhYSDg4GDhIOEg4ODhIODg4KDg4KCgYGAioFFgICBgYGA/vz6+vv49vX4+vv9+/r59vf3+vv9//78+/v49/r5+vv6+Pb19fb2+Pn6+/z7/f3+gICBgYGA//v9/v3///7/hYAIgYGBgICAgYKFgYWADYGBgP+AgIGBgYKCgoGEgAmChISGiIqKiomEii+Mj5KTk5KTlZaVlZabo7Crn5yakouKiouMjIuMkpeZnqKpqaSdmZaUkI+Pj46NjYSMFIuLjIyMjo6Pjo2Pjo6Njo6Pjo+QhZEOkJCRkZCQj4+PkJGRkpOEkg6RkZGSlJWWlpaXlpSUlP+A/4D/gP+A/4D/gKaAsH+GgIl/moABf/+Ah4ACAgQAA0hIR4tGhEWDRoVHEkhISElJS01NTUtKSkpMS0tLSoVJBUpJSUpKiEsGTExNTU1PhFCEUQZSUlNUVFSFVkpXV1hYWFlbXF1dXV5dXl9fX2BgX2BhYmNkZWVmZ2hpa21ucHN2eXp8gIKDhoaBgICBfn19enh3dHV3dnZxamdoaWpqaWpnY2JiX4VeDF9hY2ZmZWJeWldXWIdZBVpcXmBfhV4LXV1cXF1cW1lXWFiGWQVYV1hXV4VVDFZXVlVWVlVUVVZZWYVYA1dWVIVTFFJTU1JTUlJRUlJSU1NUU1RRUE9PhU4fTUtMS0tKSUZFRkdISEdHR0ZGRUVERERDQ0JCQkNDQ4VEBEVEQ0OFQgpBQUFAQEA+Pj49iT4KPT0+Pj8+Pj09PYQ+Aj8+iT8EPj8/PoY9BTw9PTw8hD0LPj4/P0FAQEFAP0CKPwI+P4Q+BD0+PT2IPgFAhEKEQwJCQ4RECENEQ0NCQkJDhUKEQwdEREVFRUZGiUcFRkVEQkKFQ4REE0VFR0dGRkZHR0lJSkxMTE1OT1CETxBQUFFQUE9NTk5QT1BPTk5OhE0JTExMTU5OTk9PhVGEUiJRUlFRUE9RU1JRUVBQUFFRUlNTVFRUVVZWVlRVVFRTVFRVhVYiVVVUVFRVVFRUVVVVVldXV1ZUUU1NTUtKSEhKS0tLSklJSYhIG0lJSkpKSUhHRUZGR0dHRkVHSEZFREJBQUFCQoRBg0CEQQRAPz8+hj8YPj08PD09PDw8Ozs7Ojs7Ozw8PDs7Ozw8hTsaOjo7PkBAQUJCQUFCREVFREZHR0hISEdHRkaIRRFERkdGR0dISEhJSkxPTk5PUIRSB1BOTU1NTEyESwRMTEtLh0oSSUpKTExKSkpJSUhJSElISEZGhEU5RkZFRIWFhoaFhoeHhoeGhoOBfXt7eXl6e3x8fHl6eHh8f4F/e3p5eHh5e3x7e31/f4RERUVFRIqLhEQDRUdIhEoSS0xLS0pJR0dGRkVEQ0JBQUGDhEFVQkJCQUBBQUFCQ0NDQkRCREVHSk5PUE5NTk5NTVBTVltcXV1dYGNna3F6enNsbXVtX15eXl9hY2FgYmRiYF1cW1hUU1JQT09OTkxJR0RDQkNDREZGRoRIBEZFQ0WGRgJFRI5FgkaIRwpISkxMTEtKSklJgl6FXYZcBF1cXFyHXRNcXV1eXl5fYWJiY2NjYmFiYmFghV8uYGBhYWBgYWFhYmJiY2NjZGVkZGVmZmdmZ2loZ2dnaGlpaWpqa2trbGxtbW5vcIRygHNyc3R0dXV2dXZ2d3h5eXl6enx9f4GDhYeKjY+RlZaXmJiXlpaWk5GQj46Mh4iJiYmGf3x9f4CBgIB+enh4dXR0c3V1dnh6fH17eHNwbW5ub25tbm1ubm9xc3Vzc3NxcW9vbm5ub29ubGtra2pqamlqaWloaWlpaGhpaGlpamppD2pqaWhpam1tbGxtbWtraoZohGcVaGhpaGhmZmdmZ2hpaGdkY2FiYWBghV8NXlxbWVlaW1taWlpZWIdXBVZWV1hXhlgDWVhXhlYKVVZVVFRUU1NSUotRA1JSU4ZSg1OEUghTU1JTU1NSUoRThlIJU1NSUlJTUlJThFQDVVZViFSCVYVUglOEUoNRh1IhVFZWVldYWFlYV1hXV1dWVlZXVldWVldWV1dWVVZWV1dYhFmMWgtZV1VWVldXWFhZWYRaEFtbXFtaW1xdXV1eXl5fYWGFYhljYmJhYmFgX19hYWFiYWFgYF9fYGBfX2BghGEeY2RkZGNkZGVlZWZlZWNiZGVkY2NiYmFjY2RjY2RkimUHZGRlZWVmZoRlGmRkZGNkZGVlZmdoZ2dlZGFfYGBeXVxcXV5ehV0dXF1dXFxbXFxdXF1cXF1dXVtaWlpbW1pZWltaWFiFVgRXVVRWhFWEVBBTU1JRUVJSUlNTUlBQUVFRhVAET09QUIVPBFBPUFCFTxFOTk5QUlJTVFRTU1RVV1ZWV4RYBllYV1ZWVYZWBlVVVlZWV4VYDllaXF1cXV5fX15dXFtahFuEWgVbWlpbWoVZClhYWFlaW1lYWFiEVw1YV1dVVVVUVFRVVVRThqNMpKSlpaSkoqCfnZycnp6en5+em5uamp+ipJ+enpydnZ2goJ+fo6Skp1RVVFVUqapUU1JTU1NVV1dWV1lZWllYV1ZVVVRUVFJSU1NTpodSBlFRUlJSU4RUB1VVVlZYWlyIXTVfY2RnaGlqa29wdXl/hod/dniAeGtra2prb3JycXJ0c3FubmxqZmRjYmFhYF9dXFpZWFhZWYRaCVtcXFxbWlhZWoZbCFpbW1paWltahluDXIddC15eYGFhYWBgYF5eCJWUlJSTlJWVhJQEk5STlIWVhZYFl5iZmpqEmwSZmZiYh5mImiqbm5ucnZydnJydnp6dn5+gn5+goKChoaKho6Kjo6SjpKOlpaanp6ipqquEqoSrOaysrK6ur7CysbGwsbO1tri6vL7Aw8TGyMnJysrJx8fJycjHxMPDv8DAv7+8t7S0tbe4t7m4tbOxroWtJa+ytrm5t7Ktqqenp6inpqalpaSlqKmsqqmop6alpaOio6SioJ6EnFOdnZ2fnZ2bnJydnJydnZ6en56dnp6dnZ2foqKhoaKjoqGhn56cnZycnJ2cnJybm5qbnJucm5ydn52bmJeXlpaVlZSTk5KRjo2Mi4uNjo+OjY2Mi4SKiYkDiomKh4kdiomIiYiIh4eGh4aFhYSEhYWGhYWFhoWEhYWFhoWIhoSHEYiIh4eIiIeHiIeHhoaGh4eHhYaEh4SGDYeHiIiHh4eIiYmJiImEiIaHhIaFhwqGhoaHiYmIiYmJhYoLi4yLiouKiYuKiouHiguLiouLjIyMjY2NjISNCI6Ojo2OjYyMhI0Yjo6Pj5CPkJCRkZGPkJCRkZKTk5OUlZaWhJQNlZWWlZaWlZSUlZWWloSVhZQElZaXmIaXBZiXl5iZhpgPlpiZmJeXlpWVlJSVlZWWhpWHlBmVlZaXl5eWlpeXlpeWlpeXmJiZmpmZmZeUh5CEkYSQCo+QkI+Qj4+PkI+EkQKQj4SNEo6OjYyNjYyMi4uJiImKiYiJiISJCYiHiIiIh4eHhoWHCYaEhIWFhYSEhIWDjYITgYGAgIGBg4OEhYWEg4OFhoWEhoeHBoWEg4SDhIWDh4QVg4SFhoiIiYaHiIiHh4WEhYWGhoWEhIMGhYSEhIOEiYOLggWBgYGAgIWBH/39/f7+/f79/f38/fz7+fj4+Pr6+/r6+fj6+fn7/fyE+Rn7/P3+/vz6/P39/4CBgIGA/f6BgICBgICAhIESg4OCgoCCgYGBgICBgICBgID/iYCEgYaAToGCg4aIiYqJiYqKiYmLjo+RkpOVlpebnqCkrq6lnp6nopeYmJeXmp2bm52fn52bmpqYlZSTkpKSkZCPjo6OjY2Njo6Oj4+RkZCQkJGQkIiRBZKRkZGSh5GEkhKTlJOUk5SUlZWXmJeXl5aXlpX/gP+A/4D/gP+A/4CpgK5/hYCCf5yAAX//gIyAAgIEAIRJCkhISEdGRkdHR0aERQhGRkdHSEhISYRIB0lKS0tMTEuESgVLTEtKSoRJB0pKS0xNTk+EToRPBlBRUVFSUoZTE1RVVVZWV1ZXV1dYWFlZW1xdXV6EXwNgYWGEYzlkY2VmZ2doaWttcHJ0d3p9f4KEiIyOk5aYl5SQjo2KhoWAfn+AgYGBgH9+eXFxcW9vbWpnZWJeYF+EXiRdX15gYF5eWldYWVhXV1dYW15fYGFgXl1dXVxdXl5fX2FgX1yFWzFcWltbWlxbV1dWVlVVVFZWVlVVVVZWWFtbWVlaWFhXVlZXVlZWVVRUVFNTU1RUVFZWhFUqU1JRUE9PT01NTExLSklISEZFRUZHR0ZFRUZFRUVGRUZGRkVDQkJCQ0REhUUJRENDRENCQkJBhEAFPz8/Pj6LPYQ+hj8FQEA/Pz+EPgU/QEA/P4Q+BD09PDyJPQo+Pz8/Pj9AQUFAhz+DPoc/AT6EPYQ+Cz09PT8/QEBAQkJChEMMRENERENDQ0JCQ0REhUMFQkNEREWERhxHRkZHSEdHR0ZERERCQUBAQUFBQ0RDQ0RFRkZGhUcXSElKS0xMTE1PT1BSUlJRUVJSUVBPTk6ET4VOFk1NTEtKSktMTEtLS0xMTU9QUFFRT1CETwtRUlJTVFJRUVBRUoRUClVWVlZXVlZVVFSGUiZTU1NSUlJTU1JTVVRUVFZWVVZXV1NQT09NTEtLTEtLS0pIRkVEQ4REBkVGR0hISIRHDkZFRERFRUZFRkdGRUNDhUINQ0JCQUFBQEBAPz8/QIQ+Bj9APz49PIY7hDoDOTk6iTuEPAE9hjyEPg49P0BBQUJDREVFRkdGRodFIEZGRkVEREVFRkZHR0lISEpMTU1OUFFRUVBQUE9PUE5OhE0TTExLTEtLS0pKSklJSUpLS0pJSoRJH0hIR0dHSElJSUhJSUlIR0aLioeDgoSIjIqEgIB7d3aFdTl2dnd2d3l5enl4d3Z2dXV4e3x+fn19fn5+gIVERkZGRUVERUdGRkdHR0hISklJSUhISUlIR0ZGiISEQQyEg4RDQ0RDQ0RGRkaERx5IRURFRkdJS01OT1BQUFJUVVZXVldbXmNobnFze3mEdC1xamhpamtraWhjXllYWlpZWVhWVFRTUlJRUVFNSUhGRkZHSElISEhJSkpJSEiERghHSEhHR0ZGRYRGG0dGRkZHSEdJSEhISUlKSUpKS0tKSklJSElISQVeX19eXoVdg16JXYRehF8HYGFiY2NjYoVhAmJhhmAXYWFhYmJjZGNjZGRkZWVlZmdnZ2hoaWmEagNramuEbANtbW6EbwRxcnN0hHUedHV2dnh4eXp6ent7fHx9fn+BhIaIi46RlJeZnaGkhKdKpqWjoJ+cmZWSk5SVk5OSkpOQioiHhoaEgn56d3R1dHNycnNzdXV3d3N0cm9vb25tbGxsb3JzdHZ2dHNycXBxb3BxcXNycG5tbW2EbAxtbGtra2pqaWloaGiFaQtqbGttcXBubW5tbIRrDWpqamloaGdnZ2hpaWiFaRhqaWhmZWRkZGFgX19eXVxcXFpZWVpbWlqFWQdYWVlZWFhXhFYEV1hYWIRZAViEVwRWVlVVhVQBU4VSAVGGUgJTUoZThVQEU1NTVIVTglSFUwVSUlFRUYdSBVFSUlNThFSEVYZUKlNTU1RUVFNTVFRSUlJRUlNTU1JSUVJTU1RUVFVVVlZXV1ZXVldXWFhYV4RYCldXWFhXVldXV1iEWYhaC1lYWFhXVlVVVlZXhFgEWVpaWoRbDVxbXFxdXl5fX2FiYWKFYwdkZGRjYmJihGEMYGFgYWJhYWBfX15fhGAgX2FhYWJjY2RkY2RkY2NkZGZlZGVkZGNjYmRkZWVkZWeGZgZlZWRjYmKEY4RkFmNiY2NlZGRlZ2hnZ2ZlY2JhYWFgXl6EXwVeXVxbWoZZDVpbXFtbWlpbW1tZWVqFWQdaWllYWFdWhFcGVlZWVVVVhlQBU4RSBFNSUlKHUIVPAU6FTwlOT09PTk9QUFCFTxpOT1BRUFBQUlJUVVZWV1dXWFhXV1ZWVlVWVYZWGVVWVlZXWFdYWFdZWltbXF1eXl1dXFtbXF2HW4VaW1lYV1dWV1hYWllYWFlYV1dXVldWVVZWVldXV1hYV1ZVVKempaKipKaop6Oiop6bmpmam5uampubmpudnZ2cm5qbm5qbnJ6eoJ+goKGhoaKmVFZVVVVUU1RWVVWEVg9XWFhYV1ZWVldWVVVVqaeEUwympaVUVVVTVFVWVlWEVkBXVVRUVVZZWVtcXV5eXmBhYmNkY2RnbHJ1enx+hoN/f3+Afnh2dnh6eXh3c25qaWxsamhnZmVlZGRjYmFhXlxchFsLXFtbWltdXl5dXV2FW4ldCVxcXV1cXFxdXYVeD19fXl5fX2BgYF9fX2BfX4SWgpWElIuVhZYNl5eYmZqbm5ycnJucm4qahJuCnISdDJ6en56fn5+goaGgoYSgBqGipKSlpYWkGaanp6ipqqutra6vr66trq+vsLCvrq+vsbKEtDC1t7m7vb7CxcfJys3Q09bY2dnW1NbV1dHNyMfJyMrIycrKysa/v8C/wMC+urazrrCGrwuwr7CwrKypqKenpYSkIKisra2urKimpKOjpKKipKWmpaOgn5+goaChoKChn6CghJ4DnZ2chJ4On5+hoKOnpqSjpaOioaGEoC6fnp2cnJubnJucnZ6dnZucnJuamJeUlZWUlZWTkY+OjYyLi4yLjI2MjIuMi4uLhowBi4eKhYuFigSJiYiIhIeEhgKFhoSHBYaFhoaFhIYGh4eHiIiHhogHiYiIiImIiIeHhIYJh4aFhYWEhIWGhYcBiIaHHYiIiIeHh4mJiIeIiImIh4aFhoeHiIeHhoeIiImIhIkGioqKiYqKhIsXioqLiouLioqLi4uJiouMjIuMjIyNjIyFjQGOhY0KjI2NjIyNjo6Oj4qRAZKEkwWUlZWVloSYBJeXl5iFlwiWlpeWl5eWl4SWBZWUlZaWhpUKl5mZmJmZmJmZmISZG5iZmJaWlpWVlpiYl5eYl5iYlpaXlpeWlZOTlIaVI5aWl5aXmJiXl5iYl5eYmZiXlZWUlJOTlJSUk5KQj46Ojo+PhZCEkReQj4+OjYyNjo6Pjo6OjYyLioqJioqKi4SKBImJiIiEhwuGhoaHh4iIh4aFhYWEhIMGgoKCg4KDioIDg4KChIESgoODg4KDg4SEhIODhIWFhoaGhIWDhIyDCISEhYWEhYWGhIgJh4aFhoeHhYSEhYWFhAODg4SEgwKFhIWCMoODgoKCgYKCg4ODgoODg4KBgP7+/fv7/P3+/vz8/Pv5+Pf4+Pf4+vn6+fn7+vr4+fj5hPoN+/z9/Pv7+/r7/P+AgoSBGICCg4KBgYGAgYGDgoKCgYGBgoGBgID//4SARP/+/4GCgoGCgYKCgYKCgoODgYGCg4SFhoiJiYuLi4yNjo+QkZGVlpufpaWmrqynqKinpqOfn6GjoqGgnZuXl5iZmJiWhJQHk5ORkpKPj4SQF5GRkJCPkJGSk5GRkpGRkpKSlJOTlJOUhZICkZKFkxSUlZSUlZaWlZaWlpeWlpaYmJiXl/+A/4D/gP+A/4D/gKuAr3+cgIJ/hICDf/+AiIACAgQABkpLSkpJSoRJCEhISEdHR0hHhEgDSUlKhkkHSktLS0xMTIRLEExLTEtMS0tLTExNTU5PUFCEUQlSUVJRUVFSU1OEVIRVVlZYWFlZWlpaWVpaW1xdXl5eX2FiYmNjZWVlZmZnZ2hpamxtbnB1eXuAhYeJjI+TmJygpamusbGyr6ilnpmTioeKi4yMjIqEf3l3c3FwcXFtaWhlZGFdhVwHXl9hYWFgXIRZDVpcXV9fYGBhYGBhYmCEXxdgYWNkY2JhX19eXl9eXVxbWlpZWFhXV4VWC1VWVldXWVpYWVhYhFYEV1dWVoRVhVQBVYRWElVUVVNRU1NTUlFOTk1NTEtJSIVGFEVGRUVFRkVFRkVGR0ZFRURDQkFChUQHQ0REQ0NCQYhABz8/Pj49PT2FPgY9PT0+Pj6EPwNAP0CHPwpAPz4/QEA/Pz4+hD0IPDw8Ozw9PTyEPYU+Az9AQIo/ED4+Pz8+Pj4/Pz8+Oz0+P0GFPwVAPz8/QIdBBkBBQkJDQ4RCCUNDREVEREVERYVGDkVFRUZISEhHR0ZFRUNChkEGQkJCQ0NEhUeFSApJSUlLTE5NTk9PhVCETw5QUFFQT09OTUxMTE1MSoVJhEobS0tMTU5NTUxLSktMTk5OUFBQUVJSUVFSU1RWhFVKVFRVVlRTUlBPUFBQT05OT1FRUVJSU1JSUVBPUFJUVVVVVlVSUFBNS0pLS0xLSkpIRkRCQEBBQkNERUZGR0dISElHRkNCQkJDREOFQoRDAUSGQwVCQT8/QIU/hD4LPTw7Ojk6Ojs7OjqGOQM6OzuEOgg7Oz09PDs7O4U9ATuEPBg9Pj0+PkBAQkJBQUNERERFRkdHSEdISEeERj1HR0hJS0tKS0tMTU5QUFFQT09PUFFQT09PTk9PT1BOTk5NTExLSkpJSUlKSUlJSklISEhHR0hJSUtKSkdHhUgnR0aGgoGDhoaFhoJ8eHV0dXV0dHV0dnd2dXd5e3h2dXR1eHl6fX6BhIAbf4GEiISGREOFhENERUZGR0ZHR0hJSkpISUhIhUlcSEdFQ0NDRERFRUNDRUdHSElISUpLS0pJSUlKSklKS01NTU5OUFJSUVBRUlRYXGFncXZ4enl1cnV1cm9wcXFzcWxnZV9eWlpbW1paWltbWVhWVFJSUk9MSUdHRkiGShFIR0ZFRkZFRUZIS1BOS0pIR4VGAkdGhEcTSEdISElJSkpKSUpJSUhJSUpJSYhfAV6EXwReXl9fhV4KX15fYF9fX2BgYYRiBGNjYmKEYQJiYYViCWNjY2RlZWZmZoRnB2hnaGlqamqGawNsa2yGboBwcHFxcnNzdHV2eHp5eHd4e3t7fHx9fH1+gIKChIWJjI2TmZydn6OnrK+zuLzAxMXEwLy2saylnJqen5+fnpyXk4+Lh4aFh4eCfnx6end0c3Jyc3N1dnd3eHZ0cG9ubW5xc3R0dHV2dnV2dnVzcnBwcXN0dXRzcnFxcHBxcG9ubQZtbWxsa2qGaTloaWtrbG9wbm1sa2prbGxsa2tqamloaWhoaGlpaWpra2tqaWlnZWZnZ2dlYWFhYF9eXFtbXFtaWlqEWQpaWlpbWlpaWVhYhVeEWQ1XV1hXVlZVVlZVVVVUhFOLUgNTUlKHU4hUDFVUVVVUVFVUVFRTU4RSBVFRUVBRhFICU1KGUwNUVFWJVANTUlKGUwhUVVRSUlNTVYRTBVRUVFNThFSGVYJWhleGWAdZWFhZWVlahVkYWltaWVpaWVlXV1ZXV1dYV1hYV1lZWVtbiFwLXV1dXl9gYGFiYmKIYxNiY2RjY2JiYWBgYGFfXl1dXl5eh18ZYWJiYmFhYGBhYmFiZGRkZWVkY2NkZGVmZYRmEmVmZmRjY2JhYWFgYF9gYWJjY4RkI2NjY2JjZWZnaGZmZmRiYmBfXl5eYGBfXl5cWldVVldYWFhZhlsTXFtaV1ZXVldYV1ZXV1ZWV1dWVodXA1ZVVIdTCVJRUlJRUFBPToRPiU4BT4dOBk9QT05OT4VQAU+FUIRRDFJTVFRTVFVXVlVWVoRXhFgGV1hXWFhYhVkCWluEXBBdXFtbW1xdXVxdXVxdXFxchFuCWohYCVdXWFdXV1ZWVoVXA1hXVoVXO1ZVpKOipKamp6akn5ybmpqamZqamJmam5qcnZ6bmpiXmJydnZ+foqGhoaCfo6appqdUU6OjU1NUVVVWhFUHVldYV1dWVoVXEVZVVFRTU1RVVVVUVFZXV1hZhViEWSBaWVhaW1tcXFxdX2FhYWBgYGJmanB0eX+ChIOAf4GBfoR8NH59enZzbm1qamtramlpamppaWdmYmJhYF9dXFxbXV5dXV1eXV1dXFtcXVtaW11eY2JgYF+FXRVcXFxdXV5fX19eX15fX15fX2BfX16FX4SYh5cGmJmZmZiYhJcKlpeWl5iYmZmamoSbH5ydnJucm5ubnJycm5udnZydnZ2en5+foKCho6KioaGEooKjhaQUpaWmpqWmp6enqKioqamrrK2tra6FsCmysrO0tLSzs7S2t7i6u7/Cw8fLzc/S1Nfc3eDk6Ozv7/Dw7erm49zS0ITUE9PSz8vGxMLBwcPDv7m2tLWyrq6ErSOwsbKwsbCsqKakoqSnqausrKytrKqqqaelo6GgoqSmp6empYWkA6OioYWgHZ+fn56enp+enp2enp+ipKGhoaCgoaGioqKhoJ+fhJ4EnZ2bm4SdGJyam5qYmJmYl5WTlJOSkZGPjYuLi4yMjYiMA4uMjIWLDoqJi4yLjIyMi4uKiomJhogNiYeHh4aGhoWFhoeHh4WGB4eGh4eHiIiEiYSIhIkIh4mJiYiIh4iEhwiFhYSEhYWFhISFCIaFhoaGiIiJhYgQh4eIiIiHh4iHhoeHh4iJiISHAYmEhwmIiIiHh4eIh4iHiRKLi4uKioqMjY2Pjo2MjYyNjo2EjIKNhI4BjYSOB42NjY6Njo6EjTSOj4+RkJGRkZKSkZGRkpGSk5SVlZeXl5iYl5eWlpiWmJmZmpmZmJiXlpeWl5aVlZSVlZWWhZWHlhqVlpeZmZeZl5aYmJiXlpeXl5mYmZmYmJeYl4WVMpSUlJOSk5SVlZWWlpeXl5aVlZaYmZqamZmZl5aVlJKSk5OUkpKRkI6NjYuMjo+PkI+OiI8KjY2NjI2Oj42NjYyLEoyLioiIiYiIh4aHhoWGhoaHhoSECYWEg4OCg4KCgoSDDIKCgYGBgoODg4KBgYWCAYGFggeDg4SDhISFhIQahYSEhYWGhoeGhoaFg4ODhISEhYaHh4aFhoaFhwuGhoaHh4iIhoaHhYSGH4WFhoWEhYSFhYSDg4OCgoODg4KCgYGCg4ODhYSDgoGEgjuBgID9/Pv9/v79/vv6+Pf3+Pj3+Pf29vj49/r8/Pn59/f4+vr6/Pz+/f37+/v8/f/9/oCA//+AgICCgoWBDYOEg4GCgYGCg4KCgoGFgDWBgoKCgYGCg4ODhISEhYaFhIWGhoaFhYWGh4mJioqMjY6OjY6PkZOVm6CkqKqsq6inqquopYSmCKWhnp6ZmJWXhJgTl5eYlZWWlJKTk5KRkI+QkJKTk4SREpKRkZGSk5OSlJWWmpiWlpOTkoSTC5SSlJSVlpeXlpaXhJYGl5eXlpaXhJj/gP+A/4D/gP+A/4CsgK9/BICAf3//gKiAAgIEAIdMgkuFShtJSUpJSElHSUpJSkxMS0pKSktLTExNTUxNTUyFTQdMTE1MTk5PhFAKUVFRUlNUVFNUVYRWEFdYWFlYVlZYWFpaWltcXF2EXmRfX2BgYGFiY2RlZWZoZ2doaGlsbG1wdXh7gYKFjZWYnqOkpqqvtLm8vL28vsLEwbmvq6admZmWlZKPjo6MhoJ4cnFwb2xpaWlnYV9eXl1dXl5kZ2lnZmJdW1pZWltcXF1fX2BihGMuYWFhYmNjZGRjYmBgXl5eXVxaWllZWVhYWVhYV1ZXV1hYWVpaWlhXWFhXV1hXV4xWhFcXVlRVVlVUU1NSUVFQT09OTEpJSEdHRkaKRQlGRkdHSEZFRUWEQ4REB0NDQkJBQUGJQAw/Pz4+PT0+PT0+Pj2EPog/CUBAQUFBQEFAQIVBF0A/Pj49PDw9PTw7PDw7PDw9PT49Pj4+iD8bPj49Pj4+Pz8/Pj4/P0BBQkJBQUBAQUJCQEBAiD8ZQEBAPz4+P0BBQEFCREVERERFRkZFRURDQ4hFhUYFRUVERESEQ4REEUVGRkhJSkpJSUpKSUpKS0xOhE+ETgZPT09QUFGFUAVPT09OTIhLHkpLS0pKSklKS0tMTEtKSUlKSkxLS0xNTlBSUlFSUoVTDlRUVVVUVVNRTk1MTU1NhEsNTk9QUFFRUFFPTlBRUolTIVJQTk1NTk1MS0pJR0RBQEBBQ0VFRUhISUpKSkdEQUJCQoVBG0BBQkNERENEQ0NCQkJDQkBAPz9AQEA/Pj48PIU7hjoLOTk4ODk5Ojs7OjqEOQM6OzyEO4Q8Kj08PDw7PDw8Ozw9Pj9AQUFAQEBBREVFRkdJS0tLSkpLSklJSk1PUFFQT4ROF09OTk1OT1BQUVFQUFBRUVBQUVBPTk1NhUwNTUxMTUtKSUlIR0hISYVIBUdHRkVFhUcESEZFRYRGMEWHhYF9eXl6eXt9fn55eXp/QkREgX+DhYN/QUJCQ0RCQYJDRUWIhYWHRkZHR0dISIZJGkhISElISElKSklIR0dISElJSEhJSklIR0dHhEkTSklHR0dJSUpKSktKSUhJS01PUIVPS1JUWV1gZWx1dnlzb29zdHd4eXd0cnBtZ2BdXFpaWVhXWFlYWFdWVFRTUVBPTk1KSktMTExLSUhHRkZFRERFRUVGTVVSUE9LSUlISIVHhUYRR0hJSElKS0pLSkpJSUlKS0sBYIVhhmAVX19gX2BgX2BfYGBfYGJiYmFhYmJjhWKKYxZkY2NkZGVmZ2hpaGlqamtsa2trbGxrhGx1bm1sbG9vcHBxcXFyc3NzdHR0dXV1d3h5e3x8fX5/fn5/f3+BgoOFiYySl5eZoamssba5u7/Dx8zP0dHR1NfZ0sjBvbivrKyqqqWhn6CfmpWMiIeGhIF/f4B9eHd1dXR0c3R5fYB+fHdxb29ub3BwcnNzc3R2hHcTdXNyc3V2d3d2dHNzcnJycG9tbYRsF2tramppamtra2psbW1ubWxsbG1tb25thGuJahZra2tqaWlpaGZmZ2ZlZGNhYmFgXl1bhFoRWVlYWFlZWVpaWVpaW1taWViEWYZYhFeDVoZVhlQBU4VSiVODVIpVDFZWVldWVVVUU1NSUoVRAVCEUYZShlMBVIVTA1JTUoRTElJSU1RVVldXVlVUVVVWVVRUVIRTFVRUU1NUVFRTVFNUVFVVVldYWFdYWIVaBllZWFpaWoRZhVoMW1paWVlYWVhYWFlZhFoEW1xeX4peAWCEYYRihGOCZIVjg2KEYQdgYGBfX19ehF9IYF9gX19gYGBfX15fYGFgYGBhYmRlZGNkZWVlZmdmZmVmZWVlZGJhX19fYF9eXl9fYGFiY2RjY2NhYWJkZWZnZmRjY2NkZWRihGAbX19eXl5bWFZVVVZXWVpbXF1dXl5dW1lXWFdXhVYGVVZWV1hYhVYGV1dXVVRUhFMFVFNSUlGEUIRPAk5PhE4ETU1NToRPgk6ETQZOT09OTk6ETwVQUE9PT4VQglGGUglTVFVVVVdYWVuGWgZZWVpcXV6GXQdcXVxcW1xdiF6EXRBcXFxbXFxcW1tbXFtbW1lYh1cKVldXVldXVlVVVYZWIlVVVVZWVlVUpaOgn56dnJqcn6Cgm5ycoFJUU6CgoqSkolKEVA1SUaFTVVWpp6eoVFVVhVYUV1hYWFZWV1dXVlZXWFhXVlZVVlaEVwdYWVhXVldXhFgBWYRYDVlZWVpaW1pZWVpbXF6GXzphYmVqb3N4f4GEgH19f3+Bg4WCf359e3Zua2ppaWhoaGlpaWhoZ2ZlZGJiYWFgXl1fYGBfXl1dXFxchVsnWlphaWVlZGFfYF5eXVxcW1xcXF1dXV5eX15fX2BfYGBgXl9eX19gDZiZmZiZmpqam5qam5uEmgaZmJmYmpuEmgSZmZqahJuDnISehJ0Tnp6fnp6enZ6enp+goKGio6Slo4SkAaWEpl6op6alqKipqamqqqqrq6usq6ytrq+wsLCxs7Oztba2tre2tri4ubzBw8bMy83T2tzg4+Tn6+7x9fn6/Pv8///69fDs5+Dd3dzc2dbW1tXRzMTDw8LBvry8vLmzsrCvhK4ztLe4t7WvqaWko6Wmp6mqqqmqqquqqqqlo6Slp6ipqaiop6akpKOhoJ+goKGhoJ+goKKhhqAQoqKioJ+goaKipKOioJ+fn4eeAZ2EngGdhJwcmpmYlpWUlJOUk5GPj4+OjIuMjIuMjIyNjIyNjYWOEo2MjIyLjIuLjIyLjIqKiYiIh4SIhIcBiIWHBYaFhYaGhIcFhoaHiIiFhwaIiIiJiYmEig6Li4qJiYiIiIeHhoaGh4SFDYSFhoeGh4eGhoaHh4eGiIWHFIaHiIeIiIiJiouLi4qIiImJioiIhoeFiBuJiYmIiIiJiImKio2MjYyNjY6Oj46NjY6NjYyFjoSPhY4YjY2Ojo+PkI+QkJCRkpSVlZOTk5KTkpOTiJaElwOYmJmHmAOZmJeHlhSVlZWWlZWWlZaWlpeXl5aVlZaXmYSXLZiZmZmYmJmZmJiZmJmXl5eWmJaVlJSUlZaVk5KTk5WWl5aYmJeXlpWVlpeYmISZCZiWlZWTlJOTk4SSA5GRjoSMAo6PhZAXkpOSkY+NjY2Ojo2NjYyLi4yMjY2MjY2FjAOKiYmEiASJiIeHhoUGhIODg4SEhIMHgoKDhISEg4eCBYSDg4KBhoIIgYGBgoKDgoKEgyWEhISDgoOEhIWGhoeJiIeHhoaGhYSFh4iJiYmIiIeHh4iHh4aGhYgBh4SGAYWGhgeHhoWFhYSFhIQBg4aCAYGHggOBgICFgQGCiIAC//uG+SL6/P39+/z8/YCBgPv7/f7//oCBgYGCgID+gICA//79/4CAhYGKggmBgYKDg4KBgIGEggSDhISFhIQXg4SFhYSFhYSFhYaGhoWFhoaFhoeJiouFjCeNjo+SlJicoqeoq6akpKioq6urqqempqSgm5iYl5eWlpaXmJeXl5aElQGUhJMCkpOElAKTkoWRHpKTkpKSmJ6bmpmYlpaUlZSUk5OVlJWWlpaXlpaXl4yY/4D/gP+A/4D/gP+AtYCQf4OAhn+HgAR/gICAhH//gKqAAgIEAAVLS0tMTIVNhEwES01MS4VKCUtMT05MS0xMTYVOhk2CTodPBVBRUVJThFQDVldXhFZAV1dXWFlZWlpYV1laW1lbW1xdXV5fYWJhYmJkY2RlZ2hoaWlpaGpra2xvcnR5fYGEiImKj5aco6errrG2usFjZoZnMGbCuLW3t66gm5uen5qUioF8enhxb21qZ2ZmZmViX15dXl9kZmhoZ2ReW1lZWlpbXIRdBV9gYWFjhGQSZWVmZmVjYmBfYF9fXVpZWVlYhFo/WVhXV1hZW1tbWllZWFpZWFhZWFdXWFhZWFhYWVhZWVlbWldVVFVVVVZVVlVTUVBQUE9NS0hHRkVGRUVFRERDhUQFRUZGR0eFRYREh0IIQUA/Pz4/Pz+GQIU/hz4EPz49Pog/BUBAQD8/hEAUQUJBQUBAPz48Ozs7PDs7Ozw8PT2HPo8/hkAIQUJERUREQ0KFQRVAQEA/QD8+Pj8/P0A/Pz9AQEBCQ0OERAJFRoVFBEZGRUWERgVFRUVERIhFBEREQ0WERgZJSkxOT02GSxdMTE1PUVFRT05OTk1NTk9QUE9OTk1NTYdMAU2ETgRNTExMhUuFTAdLSkpKTE1NhExoTlBRUVFQUE9PUFBSU1RUVFNRUE1LS0xLS0tMS0xNTk9QUVFRUlNRUVBQT1BRUlJTVFRTU1JTUlNTU1JRT05LRkVEQ0NERUVER0lLS0hGREJBQkNDQ0FAQD8/QUNERURERENDQkFBQECFPwg+PT08PDs8O4c6Azk5OoQ5Bjo7Ozs6OoQ5ETg5Ojs8PDs8PDw9PT0+Pj48hDsEPD09PIQ9HT5AQUJDQ0RHSUpKSklIR0dIS05PT1BRT01LTEtLhEwDTk9Ph1AET05OTYROhk+EThNMSUlISEhHRkhJSUhGR0dHRkWKhEUVR0lKSktMS5GPSElKSkZEhoaDg0JChEMfRIRCREVFRkRBgH1+fn5BQ0RCRERDREVHSEhHR0dISIRJEkdGRUVGR0dHSUtLSklIR0hKSoRMHUtMSkhHRkZHRkZHSElJSEdGR0hKSkpLSkpJSkxNhE9QUFBTVltjZmlrbnBzc3FxcnZ1d3p/e3JtaGFbV1dWVlVRUVFSUVBRUVFQT05OTU1LS0xMTU5OTUtIR0dHRkZFRkZISUhNTk1OTk1OS0hHR0eHRgZISklJSkqFSQZKSUpLS0sFYWFhYmKEY4JihGEuY2NiYmFhYGFhYmVkYmJjYmNkZWRkZGNkZGRjY2RjZGRlZmVlZWZnZ2hqa2pqa4hsA21tboRvgm6EcGFxc3N0dHR1dnd3eHh6ent8fH1+f3+Af4CBgoOFh4qNkpaanp+fo6uxuLzAwsXKz9Zub3BxcXFycW/UzMfIx760r6+wsa2on5aSkIyFhIJ+fHt8fHx5dnV0dXV5fH19e3dxhHAIb29wcHFycnKEc4R1MHZ2eHh2dHNycnNzcW9sa2xtbG1ub29tbGxsbW1vcG9ubm5tbWxsbG1tbGxsbWxsa4ZsGW1ta2ppaWlnZ2ZnZmVjYmJjY2FfXVxbWVmHWIZZBlpbWllYWIVZAVeEVoRVBFRUVFOEVAZVVFRUVVSGU4NUh1MFVFRVVFSEVRBUVFVVVVRVVVZWVVVUU1NShVEBUIZRh1KNUw5SU1NUVFRVVlZXWFdXVoRVAlZVhlSEUxBUVFNUU1NUVVZXV1hZWFhYhFmEWgVZWltaWoZZB1pbW1taW1uFWgpbW1tcXV5fYGFhiWAHYWJjZGNjY4RiC2NjY2RjY2NiY2Jih2EKYGBgX19fYGFgYIRhB2BhYWBfX1+FYANhYWKIYwtkZGVlZWRkZGJiYIVfB15fX2BhYWKGY4JkhGMEZGNkZIRlHGRlZWVmZmRjYmFeW1pZWFhZWllaXF1gYF1bWliFVwtVVVVUVFZXWFlYWIRXBVZVVVRUhFMaUVFRUFBPUFBPTk5OT09PTk9PTk5OT09QUE+ETgdNTExNTk9QhU+CUIRRhk8sUVBQUVFRUFBSU1RVVVZYW1xbW1pZWFhYWVxdXl9fXl1cXFxbXFtbW1xdXV2GXg5cXFxbXFtbW1xcW1tbXIRbXFlXWFdXV1ZWWFhXVlVWVlZVValUVVVVVldYWFlZWKyrVldYV1ZUp6alplNSU1RUVFWnU1RVVlZUU6Sio6OiU1NUUlRUU1NUVVdYWFdXWFdWV1hYV1dVVVVWVlZXiFgMWVpaWltaWllYV1ZWhFcIWFlYV1dXWFmEWjRZWVpbXF1fXl5fX2BhZWlvcnV3e32AgH59foB/gYWIhX98d3JtamhnZ2dkZGVlZGRkY2RihWELX19hYGJiYWBfXlyEXQ5cXV1eXl5hYmJlZGJjYIddCFxdXV1eX15fhGAEX2BgYYRgAWEBmYSaDZubm5ycnJubm5yenZyEmxecnJydnJubnJydnp+enZ6dnZ6enZ6foIafD6CgoaGho6Sjo6Okpqemp4SmEqeoqKmqq6ioqaqqqauqq66troWvXq6xsrO0tbW1t7i4t7i5ubq7vL/Cx8rO0tPS1dvg5ejs7e/y9v2Cg4SEhIWEg4P99vHx8uzk4eLj4+Dc083JyMfCwL68ubi5uLa0srCvr6+0t7i4trOspKOkpaSkpqiEpgOnp6WEphmnqKmqq6qpqKalpqWkoZ+goaChoqKjoqSkh6MLoqKioaKhoaOkpKGJoBCfnp+hoJ2bmpqbmZmXl5eVhZMHkpCPjo2MjIWLDoqLi4yLi4qLjI2NjIyMh4sIioqKiYiIiIaGh4KIiYcKhoaGh4iHiIeHhoSHAoiHiYgMiYiJiouKi4qJiIiHhIYGhYWEhYWFhIYDh4eGhIcFhoeHh4iEh4iICIqLioyMi4uLhIkCiomEiISJDoqJiYmIiYiIiYmKi4uLhIyEjoaPBpCQj4+PjoSPhZCDj4SQCJGQkJKSlJWWhJUKlpWVlpWUlpeXmIeXBJiZmJmHmAOZmJmEmIeXHJiYl5eYl5eWl5eXlpaWmJmYl5eXmJmZmpqamZmEmAOXmJiElwOWlZOElAqTk5OUlZWWmJeXhJgNl5eYmJeYmJmampmZmISXB5iYl5aUk5KEjxuOj4+PjpGRlJSSkY6NjI6Pj46NjIyLiYuMjI2FjAGNhIwIi4qJiYmIh4eFhgSFhISFhYQOhYSDg4OEhYSEg4OEhIOEggWDg4OCgYWCKYODhIKCgYCBgYODgoOEhIODg4SEhYSFhoiJiIiIh4aGhoeIiYqKiYiIhocDhoaHh4iCh4SGAYiFh4SGhYUUg4OBg4KCgYKCgoOBgYKBgYD/gICFgQyCgoKB//6AgoODgYCE/x2AgIGBgICA/4CBgoGCgYD//P7+/oCBgYCBgYGAgYaCCoOCgoODg4KBgICEgQuCg4SEg4KBgoOEhIiFEoSEhIODhIWGhoaEhISFh4eIiISHLImKi4yLjIyNj5CSmp6goqWkpaalpaapqKqsrq2opKKfm5eXlpeXlJSVlpWWh5UElJSTk4SWBJeXlpOEkg+RkZKSlJaVmZqZmJmYmZaIlAqVlZWXmJeXmJiYhJcGmJeYmJmZ/4CDgImB/4D/gP+A/4D/gJqAAX+LgIJ/hoCEf4eAAX+HgIV//4C1gAICBAAETExMTYpOD09RUE5NTUxNTU5OT1BQUIRPAVCETxFQUFBRUVFQUFFRUVBQUVBSUYRTAVWEV4RWB1dYWVpaWlyEWwpcXFxdXl9gYWJjhWWAZmdnZmdoaWprbG1ubnBxdHd6gIKGh4mLjY+TmJ+mrLK4u8DDZWhpbGxpaGdkwsDAwsK4qJ+cmpubm5mVi4aBenNua2dmZGNiYV1YV1thZWdoaGdjXl5bW1taW11eXmBiY2NkZGNlZmZmZ2hnZ2ZmZGNiYF9eXV1dXFtaW1xdXVwXW1tZWVpaWltaWVhYV1hZWVpaWVpZWlqEWQdaW1tcXVtWhVQQVVZVU1JQUVFQTktKSUhGRY1EAkVGhEUOREVERENDQ0JCQ0JBQUCEPw1AQUBAQD8/Pz4+Pj8/hj4KPT4+Pj0+Pj4/P4RAij8NQEA/Pz9APz89PDs8PIQ9hj6EPwNAQEGKQAhBQEBAQUFCQoRDgkKIQQZAQEFAQECIPwdAQD9AQ0NEhEUERkVFRYVGA0VGRoVFBkRFRUZGRoZFCUZGSElKS0tLTodQBk9OT05NTYRPhE4FTE1MTEyES4JKhEsLTU5PT09QT05OTU2FTBhNTUxMS0tKSktKSktLTExLS0xOT1BPTk+FTg5PUFFRT05NTElISUpMTYRPWlBRUFBTVFRST01NTU5QUlRUVlZVVVVWVlZVVFNRUFBOTEpIRkZGRERGSEpMTEpIR0ZFREVEREJAQEA/QEFDQ0REQ0JCQ0JBQEBAPz49PTw8Ozw8PDs7Ojo5OoY5hDgHOTo7Ojo5OYY4ATmHOwE9hD6CPYQ7hTwbPT09Pj9AQEJDREZISElJSUhISUpOUFBQT05Nh0wNS0xOTk9OTk1MTEtLSoVLEEpKS01OT09OTk1NTkxJR0eFSAJJSoRJhEeERidHSUtMS0pJR0ZFQ0ZHRkZHSEdFRERGRkVFRURDg0JAP36BQkVFREGEfg2CQ0VGRkZISUlJR0hHhkYORUVFR0dISUlJTE1MTEyFTTxLSklJSUhFRERGR0lKS0xKSEdISUtMTUxMTE1OT1BUVlhXVVRWV1xlaWpqaGxubm9zd3h5eHl7eXBoYVuIUxdRUE5OT05OUE5MS0tNTU1OTUxMTEtKSYdHHUhJS0pJS0xNT1NSTklKSUhHSEdHRkdHSElKS0tLhkwFS0tLTEspYmNjZGRlZWRlZGVkZWVmaGdlZWRjZGVlZWZmZmVkZWVmZ2ZlZGNkZWWGZgplZmZmZ2doZ2hphmsMbGxtbW1ubm5vcHByhHGEcgx0dXZ3dnd4eXp6enyEfX9+fn+AgYKDhIaHiYyRlpmcnZ6goqSprrS6wMbN0dTYbnFzdXVzcnBu19TU19fLvbGvr7Gxsa6poJ2XjoeDgH19fHx7eHRubHB2enx9fX13c3Jwb3BvcHFycnJ0dHR1dHN0dXZ2d3h3eHd2dHNzcnFxb29ubW1ucHFxcHBvb25vhHAFb21sbGuFbBhrbG1tbW5tbm1ubm5vb25qaGhnZmZmZ2WEYwxkZGJeXVtbWllYWFiEWQlYV1dYWFlZWViHV4ZWA1VUVIlTA1RTVIhThVKKU4RUglWJVAhTU1NUVFNTU4hSAlNUh1McVFRUVVRUVVRVVVZVVlVVVFVVVVZWV1hYWFdXVodVhlQBU4VUB1NTVFRTVVeEWIRZBFpaWluIWoZZIFpbW1pbW1paWltbW1xdXl5fYGJjZGNjY2JhYGFgYGBhhWIFYWJhYWCJYQVgYGBhYoRjBWJiYGBghGEDYmJjhGIFYWBgX1+HYAhhY2NiYWJgYYRiQmNjY2JhYF9dXV5eX2BhYmFiY2NjYmRlZmZkYmFiY2RlZmVnZ2ZlZ2dnaGZmZWRkY2FgX11cW1taWVtbXl9fXl1cWoVZAVeEVQ9WVlhYWFdXVldYV1ZVVVSEUwlSUVBQUVBPUE+FTgRPTk9OhU0FT09PTk6ETQVMTU1OT4RQCk9PUFFRUVJSUVCFTw9QT1BRUFFRU1RTVVVWWFmEWgxZWVpaXF1dXl9eXVuIWoZcKVtbWllZWVpaW1pZWVlaXFxcW1taWlpZV1VWV1ZVVVVWV1ZWV1dWVVVWhFUVVldYWlpZWFZVVFNWVldXV1hXVFNThFU6VFRTplRUUqKkVFZWVVKfn5+gpFRUVVRVVldXWFdXVlRUVFVWVVVVVlZXWFhYWVpaWllaWlpbW1taWYRYN1ZVVVZWWFlaWllYV1hZW1xcW1tbXF1eX2FkZWVkZGVnbXJ1d3Z0eHp6e36Cg4SDhIaEe3RxbmaFZRJmZWNjYmFiYWFjYmFgYGFiYmKFYSlgX15eXV5eXV5eXmBgX2FiY2RnZ2NfX15dXFxcXVxdXV1eX19gYWFhYIVhA2JiYQubm5ucnJydnZ2cnISeC6Cgn56cnJydnZ2ehJ8voKCgoaCfn5+goJ6goaKio6Kio6Oio6OjpKSlpaWkpaanp6anpqanqampqqqtrayFq4Csra+wsLGxsrOztLS2t7a1tba4uLm5urq6u7y/wcXMztLT1NXX2Nve4+ft8vf5+/2BhISGhoSDgoH8+fj8+vTp5OLg4eHj4uDa1M7Hwr67uLe1tLOyr6inq6+0tba3trGtrKmnp6OkqKiop6enqamnpqanp6iqq6urqqqpqainpiClpaSkoqGio6anp6alpKOkpaalpaSioqGgoaKjpKOiooWhE6CfoKCgoaKfmpiYl5eWlpeWl5aElAaSkI+PjoyFiwWMi4uLiYSKDYuKioqLi4qLioqJiYqEiQOIh4iEhgWHh4aHh4aFgoaEhwWGhoaHh4SIAYeJiBGHh4iJiYmIiYiIiIeIiIiHh4WGBoWFhYaHh4aGhIeHiAKHiIWJDoqKi4uMjIyLi4qJiYmIhokeiIiIiYiJiImJioqLiomKi4uMjIyNjY6Ojo+QkZCQhI+FkASOj4+PhJANkZCRkZCQkZKTlJOUlYSXFpaXmJiYl5eXlpiZmZqZmZiZl5eWl5eLmIWaBJmZmJeEmASXmJiZhpgGmZeYmZiYhJcxmJqamZiamZmYmJeYmJmZmJeWlZOSkpSVlpeXlZWXmJeWmZmamZiWlpaXmJmZmZubmoaZLJqamZiYlZOSkI6Oj46PkJCSlJWUk5KQj4+Qj5COi4uLiouMjYyNjYyMjI2NhIwEiomIiIaHBoaFhYWEhYaEhYMGhIWFhYSEhIMEgoKCg4aCC4ODg4SEhYWDg4KBhYIQg4ODhIWGhIWFhoeIiYiIiISHB4iKioqLiomFiCqHh4eGhoeHhoaFhoaGhYSEhYSFhYSEhYeIiIeHhoaFhoSBgIGCgoKBgYKEgx6EgoGBgoGCgYGCgoOEgoKBgIGBgIKDgoGBgoKBgYCGgRaA/4CAgP7/gIKCgYD//v79/4GCgoGChIODgoWBBYCAgYGChIMGhIWFhYSEhIUDhoeHhIU4hIODhISFh4iIh4aFhoaIiImIiYmJioqMjpCRkZCRkZOWnKCioqGjpaWmp6moqqyvsrGopJ6alpWEloSXF5aWlpWUl5WUk5SWlpeXl5WXlpaWlJOThZQYlZaXmJeam5ycoJ2Zl5eWlZWXlpaWlZaWhJgEmZqZmYSYBJmam5r/gIOAiYH/gP+A/4D/gP+Au4AGf4CAgH9/hYCFf/+AsIACAgQAQk5OTlBQUVBQUVFRUFBPUFFTVFVXWFRSU1NUVFVTUlBPT05PTlFRUFBQUVNVV1dUVFNUVlZWWFhXVlVUVVZXVVZVV4RZCVpbXF1eX15dXIRdc15gYWJjY2RlZWZmaGdnaGlpamxtbnBxc3Z5en2AhImMj5GVlpeYl5ifp6+2vL3AxWNkZ2ptbWpmxcDDY2Vlt6ahnpuXlJGLiYyIenRua2dkY2FgYF1aXF5gYmRmZ2RjYV9eXFtcXV1eYWJiY2RmZmdmZ2iHZwZlZGJgYF+FXBldXV1cW1xaWVpaW1hYV1hZWFdWV1hZWFlZiFogXF1dXVxbWFdUU1NUVVZVUk9OTU5OTEpJSUdGRUVFRESFRYNEhEOGQghBQEBBQEA/QIk/AUCFPxA+Pj09Pj8+Pj49PT08PT09hT4BP4RAiT8LPj4+Pz4+PT08PDyEPQo+Pj8/QEBAQUBBh0CCP4VAAUGFQA5BQkNDRENBQUJBQEA/P4RAB0FBQEFAQUGKQBFCQkJDQ0RFRkZISUhGRURERIpFh0YYR0dHSEtMTUxMTU5PT09QUVBQUVFRT05NhUwJS0pLS0pKSUlJhEgQSkpJSUhISElLTlBQT05OToVNCU9QT05NTUxLS4VKFElJSktLTE1OTUxMTU5PTk9PUE9PhE4JTU1OT1FSUlFRhFIRU1FQT05NUFFQUVNVVVVWVlaFVzFVVFJQT01JSEdGR0hISUtMTEtKSUlKSUhIRkVEREJCQUFCQkJDQ0NBQUJDQ0NCQT89hDwDOzo4hDkQODg4OTg4ODc3ODc3Nzg4OIQ5gziENxU4OTo7Oz1APz8/Pj8/Pz4+Pj08OzyEPhU9Pj8/P0FCQ0ZHSEhJSEhIR0hKTE2FTgxNS0tJSUhISEpLS0yES4VKEEtLSkpISUpLTE1PT1BQUE6GTARLSktLhEwMS0lJSUhIR0VHR0hHhEYKR0dGRUZISUhIR4RGD0hGRUVEQ0JCgoCCg4VERYRICkdGRkdGRURERUiESilIR0ZHSUlJR0ZFRUVISktMS0tMS0pJSUlISEdFQ0NDQkNCQ0VGR0lLTYVOBE1NTU6FTyRQU1ZXVlVTU1VWV1leZmhoZmhqcHJ0dHR1dHVxamVjYFtXUlCHTxpOTk5PT01NTk5OTU5PTk1NTEtLSklISUlKSoRLCkxOTU5OUVRSTkuESYVKB0tKSkpLS0uGTQVOTk1NTSFkZGRlZmdmZmdnZ2ZnZmdnaWtqa21qaWlpamtraWhoZ2eEZoBlZmZmZ2psbW5sa2prbG1sbm1sa2tsbW1ubG1tbm9vb25vcHFydHZ0c3Jyc3NzdHV3d3l5ent8fn5/fn5/gICAgYKEhoaJjI6PkpSan6GjpaipqqytrrS8w8zS09TabW1wcnR0cm7W1dhtbm7LurSysK2qqKOen5qPioWCfHl5dx52dXJwcXR2eHp8fXp6eHZyb25vcnJydHV1dnd3eHiFdyR2d3d3dnR1dHNzcW5tbnBwcXNzcnFxcG9ub3BubWxsa2ppaWyGbQFuhm0cbm9vbm1sampoZmVmZ2dnZWNiYWFhYF5dXFpaWYZYBllYWVhYWIVXhVaGVYJUhVMGUlJSU1NTjVKDUYRSh1OEVARTVFRUhVOEUoNRiFIRU1NUVVVUVVVVVFRUU1RUVVSEVQRWVVZVhFQsVVdXV1hXVlZXVlVUVFNVVFRUVVRUVVRVVVVUVFVUVVVUVVVVVlZXWFhZWVqEWwNaWluNWoVbhFwWX19gYF9gYWJiY2RkY2RkY2NiYWBfX4RgBV9gYF9ghV+HXgxdXmBhYmNjYmFiYmGEYgljZGJiYmFgYGCFXw1eXl9gYGJiYmFgYGBihGMDZGRihGEFYGBhYmOIZAllZWRjYmJkZGSFZRpnZ2hpaWppaWdmZWRiYF5cXFxdXV5eX2BgX4VeBl1dXFtaWYRXD1hXV1hZWFZWV1dYWFdXVIVSB1FQT09OTk6JTQdOTUxMTUxNhE4NTU1NTExMTU1OT1BQUYZTCVJSUlFQUE9PUIRRAVKFUyBVVVhZWVpaWllZWFlbXV5dXF5dXFtZWVlYWFlYWlpbW4RaFVlZWVhYWVlZWFdYWFlaW1xcXV1dXIRbFlpaWFdYWFhZWVlYV1dXVlZVVVZXWFeGVhBVVFVXWFZWVlVVVlZXVlVVhFQHpaKio6ZUVYVXIVVVVlZVVVRVVlhZWVlYVlZWWFlYV1ZVVVVXWFlZWlpbWoRZBlhYV1ZVVYVUBlVWV1haXIVdBVtbXF1dhV4UYGNkZGNiY2VmZmhsdHd2dXd4fX+EgRmAgn94dHJvamdlZGNjZGRjY2FhYWNkZGNih2OFYgJgX4ReGF9fX2BhY2RjZWVnaWdkYV9eXV1eXl9fX4RghmEIYmJiY2NjYmMMnJ2dnp6gn56goJ+fhKCAoaSlpailoqOjpaWlpKOioaChoaKjo6KioqOlp6inpqemqKiopaenpqanp6ioqKeoqKipqqmpqqytra6wr66tra6urq+wsLCzsrS1tre4ube2t7i5ubu8vr6/v8PFx8nLztPW2dvd3t/g3+Dj6e/3+/v7/4CAgoSFhISC/vv+gIFDgvfq5+Xg3tza1tXY1MnDvru1s7Kwrq+rp6msr7K1trezsrCuqaakpKSlpqmqqaqrrKyrq6qrqqqqq6usq6mpqKeop4WlGqenpqWlpqWjoqOkpKOhoKCenZ6goqGhoqKih6AcoaCgoJ6cmpmXl5aXmJiYlpSTkpKRkY+Pjo2MjISLCIqKi4uLioqLhIoGiYqKiYmJhogGh4iHhoWEhIUFhoeGhoWKhhqHhoeHhYaGh4iIiYiHiIiJiYmIiIiHiIiIh4SGAYeFhgKFhoaHBoaHh4iIiIqHAYmEigiJiomJiYiIiYeLC4yKiomIiImJiIiIhIkBioSJh4oNi4uMjY2Njo6PkJGRkYiQho+DkIWSBZGTk5SVhJY5l5iWlpeYl5iZmpmZmZiXl5iXl5aWlpeWlpWWlpaYmZiZmZeXl5aWl5iam5ubmpubmZqampmZm5qahZkNmJiYl5aWlpeYmJqbmoSZhZgdmZiXlpWWl5aWlpiZmJiXl5iZmZqbmpmYlpWXmJiFmoWbI5ybnJqamZiYlpORkJCRkpKTlJWVlJOTlJSUk5ORkI+Qjo2MhI0Tjo6NjIyMjo2NjIuJiIeIh4eHhoaFhoQFg4OEhISEgwGEhIMHgoKBgYGCgoSDE4SIhoWFhYaGhoWFhIWDgoOEhISHhQSEhIeHhYgQh4aGiImLioqLi4uKiIiGhoWFBoaHhoWFhYWEB4WFhIKBg4WGhgSHhoWFhYQBg4SCJYOEhIOBgYKCgoGBgoKDgoGCgYGBgoGAgIKCgYGBgIGBgIKBgYGEgDT+/P7//4CBg4ODhIKCgoODgoKBgoODg4KDgoKCg4SEg4KBgYCAg4WFhoWEhYSEhIWEhIOChIMLhISEg4WGhYaIiYiIiSeKi4uLjI2OkJGRkZCQkZKVmJyipKKho6OnqKmqq6usr6qkn56dmpWElASVlZaYhZcDmJeWhJcsmJmZmJiXl5eWlpWWlpaXmJeZmJubmpydnqCfnJiYl5aWmJiZmpqam5qampyFmweampucm5yc/4CEgIiBBoCAgIGBgf+A/4D/gP+A/4C2gIV//4C6gAICBABNT09PUFBRUVFTUlNTUlNUVVhYWFtbWlpaXF5cXFtTUlNTUlFTVFJRU1ZYWVpaWVdWV1peXFtaWldXV1ZWVldYWVtaWltcXFxbXV5fX2CGXwVgYWNkZYVmVmdoaGlqa2ttbnFydHZ3eXt+gYSJjY2PkZWYmpudoKOnrbW9w8NjZca/wGVua2VixGRmaWtmuKiempSOhYOGi4h/eG5nYWBdXF1cW1pbXF5gYWNjZGNghF8zXV9gYmNlZGVmZmlpaGloZ2hpaGhmZmRlY2FfXl1dXWBgX2BeWllZWFdZW1lWVlZXV1dWhVgFWVpbXF2EWyZcXVxcW1lYVldWVFRVV1VSUU9OTE1LSkpJSUhHRkdHRkZFRUZGRoVFBURDQ0NCh0EDQD8+hD8BPog/iT4SPTw8PD09PDw9Pj8/QD8+Pj4/hD4CPz6EPwFAhT8CPj2GPIQ9Az4+P4xAhD8PPj8/QEFCQj8/QEFBQUJBhkACPz6IP4dABUFCQ0JChEEKQkNDRERERUdHRoRIEEdHR0ZFRkZGRUVGRkdJSUqFSRtKSkpLS0tNTk9PTk5NTk5OT09NTEtKSUhISEmEShhLSklJSUpKSktLS0lJSEdISUpOTk1NTEyETQ9PT05OTU1MTExKSUlKSkqESxNMTU5NTEtMTE5OUFBPTk1NTk9RhFJHU1NSUVFSUlNTUlBPT1BRVFVSUVJSUVBQUFNVVVdWU1RTU1BOTElISUlKTExMS0tLSklJS0tKSUhGRERDQkJCQ0RDQ0NCQkGEQgpBQEA/Pjw8Ozo6hDkKODg3Nzg4OTk4N4Y2Ezc2Njc4ODc2NjY3Nzg4OTo7Oz6HPwU+Pj8/PoQ9DTw9PT0+P0BBQkJERUaERxJGR0dISUpLTExNTUxJSUlIR0mESwlKSkpISUpJSUmFSjFJSkxNTE1NUFJTVFJOS0lJSUpJSUlLTE1NTk1MS0tMTUtKSklJSElJSUpKSklHRkdIhEknSElKSUpJR0RDQkKEQ0RFRklKS0tISEpLS0lJSEdHRkdISUtLSkpJhUcjSUpKSUtLTE1PTktKSkpJSUdHR0VERENCQUFCREVHSEpMTU6GTQNPUFCETypRUlNRUVBRUFJVV1ldYWNiYmRma25ucW1paWpqZWBfXVhVUlFQTk5NTE2FTBNOTk9OTUxMTlBPTk1MS0xLSkpKhUseTE1OTk5PT1BQUE9NTEtKSktMS0xNTUxLTE1MTU5OhE0FTk9PTk8IZGRjZWZnaGiEaRJoaGlsb25ucXJwcHBzc3JycmuEahVpamppaGpsbW5wcXBubW5xdnNxcXCFbYBubm5wcXJycnR0c3J0dXZ3dnV0dXV2dnd3eHp7fHx7e3x9fn5/gIGBhISHiIqNjpCRlZibn6KjpqerrbCxsrW5u8HJ09nYbG7X0dJvd3VwbNVscHN0b8q8tK+po5qZm5+clYyCfHZ0cXBxcXFwcnN1d3h6ent6d3V0c3NxcXN0dQp2dnd4eHp7e3p6hXkLeHd1dXRzcXBvcHCEcz1xbm5ubWxtbm5sbGtramloa2xtbG1tbm1ubm1sbW1ubm5tbGtqaWppZmdoamhkY2JgX19eXVxbW1taWVpah1mFWAZXV1dYV1aGVYRUhVMGUlNTUlJRhlKEUwFSh1EFUlNTU1SEU4RUBFNUU1SFUxpSU1JSUVFQUVFSUVFRUlFSU1NUVFRVVFVUVYRUDlVVVFNTU1RUVVVWV1VVhFYJV1ZVVVZVVlVUhVMZVFRTVFRUVVRUVFVWVldWVlVVVFVWV1hYWYRaBFtcXF2EXB9bWltbW1paWltbXFxcXV1dXl5fXl5fX19gYmJiYWFhhWIFYWFgX1+FXhdfYF9gYF9fYF5eXmBgYF9eXl5fYGFiY4ZigmOEZAdjY2FgYF9ehF8WYWFfYWFiYWBgX2BgYWJkY2NjYmFhYoVkBGVlZWSGZTdkY2RlZGVmZGRlZmVkZGRmZ2doaGdnZmVjYWBdXV5eXmBgYF9fXl5eXV5eXl1cXFtaWVdWVlhZhViFVwZVVVRUU1KEUQhPT09OTk5NTYVOBE1MTE2GTIRNh0wKTk9QUVNTU1RSUodRAk9QhlGCU4RVBVZXWFlZhVgJWVpbXFxcW1tbhFk4WFlaWllZWlpZWFlZWFhXWFhYV1hYWVpaWltbXV9gYF5cWllZWVpZWVhZWltbW1paWVlaW1lYWFiFVw5YWFlYV1dWWFhXV1hXWIRZFldVVFRTp1RUVVZZWltbWFdYWlpZWVeEVhhXWFpbWVlXV1ZWV1dYWVlYWFlZW1xbWlmEWBNXV1hWVVRUVFNTVFVVV1hZW1xdhlwDXV9fhF5UYGFhX2BgYWFiZWdpbXFzcnJ0d3p7fH58eHl6enVwcG1pZmRkY2Fi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S0pGhEcNRUZHS0xOTk1MTE5NToVMGUpISElISEZGR0dHSElKSktKTExLS0tNTE2HTwtQUVNTU1BPT09TWoVbM11dX2JjY2RkYmFhX1pXVVNTVFNTU1JSUE5MTEtLS0xOTk9PUFBRUVJSUk9OTUtLSktLS4RMBE1NTEyETgFPhFEaUE9PT05PUFBQTk5OTU5PTk9NTU5PT1BQUVI3Z2VlZmdoZ2hpamtqamprbnJyc3V4en1+e3+BeHd0cXFxcHBwcnFxc3N1dXBvcHJ0dXV2cnJxcIVvBHBxcnKEcw91d3h3d3Z3eHh5eXh4eXqFe3l8fHx+fn6AgIKChISEhYiKjI6QkpOZnp+ho6amqauus7S2ub3Aw8jN0m1wdXh2c3NydXRwbWxtb3Juwbm0raOgnJibnaCemYt8d3RxcHBwcXNzdHV1dnZ0dHRzcnJzc3Jzc3N0c3R1dXh5enp4d3Z0dXV0c3RzdXVzhHEFcHBwbmuFag1rbGtqa2pqa2tsbW9whG8EcHBwboRtE2xtbGxtbGpsbGppamppZ2ViYl+EXQdcXFxbW1pZjFoGWVlYWFdXhFaCVYpUhlMBUYdShVGCUIRRg1KMUwdUU1RTU1JThVIHUVFSUVFRUoVRB1JSUlNSUlOFVAZVVFRTU1OFVIdVCVZVVVVWVlVUVIRTBlRTU1NUU4ZUglWEVg5VVVZWV1dXWFlZWVpbXIRdhFwCW1yHWxlcXV1dXl9fYGFgYWFgX2BgYWJiYWJiYGBfhF4MXV1dXl9fXl9fYGFfhF4FXV5fX1+GXgpfYGJiYWJiY2NjhmQFY2NiYmGEYA5hYWFiYWFgYGBhYmJjY4lkRmVlZGVlZWNiYmNkZGVlZGJjZWZmZWVkZmZmYmFiZGVkZGJhYmNiYWBeXl5fX19gYGFfXl9fX15eXV1dXFtZWFhXVVdYWVmEWA1WVldXVlVUU1JSUVFQhk8LTk9PTk5PT09OTk2ES4VMg02FTA9NTlBRUVFSUlFRUVBQUE+GUA1RUVJSUVNTVFVUVVZWh1cHWFlZWVdYWoRbCFlZWllZWFhYhFkYWFhXV1ZWV1hYWFlZWlpaW1tcW1tcXFtahFkGWllZWVtciF0GXFtaW1pZhFgKWVlZWFdWVlVWV4RYGVpaWVhYV1ZWVVVVV1hZWl1fXltaWVpaWViEV4RYFVlaWVZXWFhXVlZXWltc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TEtLS4RMKE1NTEtLS0pLSUhISUlJSkpKSEhIR0hJSEdGRUVFRkdGRURFRURGRkaESoVLE0pJSkpJSkpLTEpJSEhHRUdGR0eFSAdKS01PTk5OhE8TUFBRUE9NTk9QUVBOTEpMTE1RUoRTL1VVVldcXVpaWltbWlhWVVVVV1hYVVRUVFNST09QTk9QUFJSU1VXV1dWU1FQUE9MhEsCTE2HTg1QUlRWV1dYV1dYV1dXhFYEVFBNToRNCU5NTlFSU1JTVDFpaGhpaGloamtrbG1ub29xcnZ4d3l7fH1+f3t1c3V0c3d5eHx6dnVxc3Z0cnRzcnNyhG+Abm9tbm1ub29yc3N1dXV2dnd4fHt6enx6ent7fHx8fn5/fn19fn5/gIKCgoODhIWHiImMjo6RlZebnJ+goKKkp6qts7W2trm+wMPJ0m1xdHd2dHFwcnJt0MvDwmdmva2mo5yZlpWXmqGhnY+BfXt4dnVzcnNycXFzc3NxcXFycXImc3R0c3J1dXV2dnZ3eHh3dnV1dXNzc3Jzc3NxcG9wcW5ramlpaGiEaQhqa2lqamprbYRvIHBwcXFxcnFvbm1sbGxtbm9vb21ubm5sampqaGRiYWBfhF6DXYRbC1xcXV1cW1taWVpahVkGV1dWVlVVhVQCVVSIU4RSB1FRUVBRUVGEUIZRhFIGU1NTVFRVhFSEUxBSUVJRUVFSUlJRUlFRUVJShVEBUIRRhVIKU1NSUlNSUVJSU4VUh1UGVlZVVlZVhlQBVYlUDFNSVFVVVFRVVVZWVoVXElhZWFhZWVlbXFxcW1tbXFxdXYRcgl2FXhlhYmNjYWBgX2BgX2BgYGFgXVxcW1tcW1tbhl0LXl5fX15eXV5eX1+EYApfX15eXmBgYGFih2MEYmJiY4RkHWVkY2NiYmJhYGBhYGFiYmRlZmZnZmZmZWVkY2NihGMiYmNkZGJjYmNlZmdmZ2dmZWVnZmViYGFhYWBfXl9gYGBhYIVfEF5eX19eX19fXl1dW1pZWFeFVhZXVlZVVldWVVZWV1dWVFNSUlBPUFBPi1AIT05OTUxMTEuFTAFNhEyETQFPhVAeUVFQUE5OT09QUFBRUVFSUlNTU1JTU1RUVFVWVlZXhlgOWVhYV1hZWltaWVlZWFiEVxZYWFhXWFhZWFhZWVlYWVlYWFZYWVhYhFkKWltbXFxdXV1cXIRdA1xc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QEFDQkJERUZGR4RIA0lJSIVJB0hJSUhISEmETB9LSkpKSUlJSEhJSUhIR0dISUpLTE1OTU1NTk5PT05MhE6ATU1MS0tKSEhGRkdJSkpKTE1OTkxLTE1OUlJSUFBOTk1MTEtNTk9OTExLSktMTEpJSEhHRkZFREJDQ0RERUVFRkdISktLTE5OTEtKSkpJSUlISEdHR0ZGR0dGRkRDQ0RGRkZISUpJSk1OT09OTU5PTU1PT1FRUU9NTUxMTUxKSUhnSEpLTE5QUVJTU1RUU1dXVFRWV1dXVlRTU1RWV1hXV1ZVVVZUU1JRUVBSU1ZYWFlZWVdSUVJRT05NTExNTExNTU9RUlJSU1VXWlxdW11gY2FhYF9fXlxWUE9OTk5PT1BRVFdZWFZWVTFoaGlra2pqamlqbG1vcXJ0dnRxcW9xc3NzdHR0dXZ2dnl6fX95dnR2enZzcnBvcHJzhHATb3Bvbm5vb3FycnJ1dnZ2eXt8fYR+En19fHt9foCCgoOBgYB/f3+AgYSDa4aHiY2QlZeZm5ucnJydnp+hoqSnqa2xs7W3u7/CxctrbXB1d3Vu12xt0sbAtK6xsK+pnpmVk5KUm56dnJmMf319e3p4d3d1c3Nxc3NycnFxcXJzdXZ2dXV2dXZ2dnV1d3d2dHJyc3JycXByhHEIcHBvbGlpaGeEaQpoaWpraWprbW5whHGEcwVycXBwboRtNm5vcXBvb3Bxb2xpaGZjYmBgYF9eXl9eXl9eXVxdXV1eXV1cXFxaWVpbWlxaWlpZWFZWVVRVVYZUglOFUoRRilACT1CEUQdSUlJRUlNTiVQFU1NTUlKEUYRQBlFRUFFSUYRQBFFSUVGIUoJThFKCUYRSA1NUVIZVAVaFVYVUglWEVARTVFRUh1MDVFZWiVeCWIVZhFo7W1pcW1xcW1tbXF5eXV1dXl5eYGFiYmFhYWBhYmFgYWBhX11dXFtbW1paXFxbXV1dXl9fYF9eXl9fYF+HYAFfhF4EX2BiYohjFmRkZWVkZWRlYmJjY2JhYWJhYmNkY2SGZSRkYWBgX19gX2BiY2RkY2FhYWVnaWloaGZkY2JiY2JgXl9gX1+EXgxfYF9fXl9gX19eX1+FYA9eXFtaWllYV1ZUVVVWVlWGVA5VVVZVVFRTUVBQT1BQUIZRglCFTwxNTU5NTExMTU1MTUyITYRPIU5PT09OTU1O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hE0ES0pMTYZOCE1MS0pJSEhJhEoVS01MSkhGREVGRkVFRURCQUFAQUE/ij4EP0A+PIQ7hjyEOxQ6Ojo5ODk4Nzc3Njc2NjU1NTY3OIU5CTo7Ojk4OTk5OIQ3Azg5OYQ6Fjw8PT0+Pj49Pj4/P0BAQUJERUdISUmHSAxJSkpKS0tMTExNTk6GTRxMS0pIR0ZHSEhJSUpKSUpPUFBOTEpKS0lISUlJhEsISkhISElJSEiERzJISUpNUFFRUlJRUVJTUlNTUlFQTk1MTlBQT05LS05PT05NTEtLSklGRURDQUBBQkRGSIVJV0pKSktMTEtJSUhJSklISEdFQ0JCQUJDREVGR0dIR0lKTUxMUFFQUE9PUVFQUE9PT1BPTk1LSklKSkpIR0dGRkpMTU9RU1FRUlJSU1NVVFRWVlZVVFRTVYRWHFlaWVdXWVtcWFdWVVZXWFdWVldXVlVSUU9OUE+ETilQT09RVFRVVldaWVlbXl9hZGVpamhmYl1XVFJSUlFRU1VYWVpZWFZVVVlqampsbGtrbG5vb29wcnR3dnNua2tqbW1ucHJ4fIGFiIqIgX5+fHx8dnp1c3N2d3VzcnJzdHNzcnFycXFycnR1dnh5enp7fn+Afn59fn5/gIB/gISFhISDgoSDgIKEhoaGh4mMj5KTlZiampqcnZ2dnqCgoqOjp6ywtLe7wcXGwsnQ1HF3c9HJwcC+s6qkn56cmJKMiYqMjIyOjYqIgHp4eXp7eHh3dXd2dnR1dXR1dHN0dXV2d3d2dnd3dXZ2dXZ3dnV1c3NzcnJycXFwb25wcHBvbWxsa2xramtrCWxsa2xra25vc4d0H3V1c3JxcG5tbG1tb29wcHFzcXFsamlmY2FgX19eXV2FXhVdXV1eXl9fXl5dXFxbWlpbWlpaWViEVoNVhVSEUwFShVGEUIZPAU6ETwpQUE9QUFBRUlJSiVSCU4RSBFFRUFCFTwVQUFFRUYhQg1GFUgNTU1SEUgFRhVIBVIZVBlZVVFVUVYZUA1NTVIdVhVQEU1NTVIRWBFVWV1aFVwlYWFhZWlpZWlqFWwdcXV1cXF1dhF4DX2FhhWIOYWBhYWJhYWFgX15dXFuEXARdXl9fhWCFYYRihWEMYF9eXV5fX2FiYmRjhmQMZmZlZGJjYmFhYGFhiWIIY2RjY2JhYGCFXgZfX2FiY2OEYQ9iZGVkZWZlYmFhYWBgX2CEYQtiYWFgYGBfXl9eXoVfGWFgX11cW1paW1paWltYV1hXWFZVVFNUVFSEUxpUVFVUUlFRUFFRUVJRUVFQUFFQUE9PTk1OToVNCE5NTExMTU1NhE4ETU5PToRNCU5NTU1MTE1NTYROglCHUQxSUlNTU1RWV1hZWVqEWQpaWllZWVpaWltbhVwBXYZcBltbWVlXVoRXAViEWRlbXFxcW1paWlhYWFdXWVpaWllYWFhZWVhYhFcLWFlZW11dXl9fXl2EX2JgXl1dXFtaXF1dXFtaWlxdXVxcXFtaWVhWVlVVU1JTVFVWV1lYWFhZWVlaWltaWVhYWFlaWlhYWFZVVFRUVlZXV1hZWVlYWVpbW1teX11dXl1fX15eXl9fYF9eXl1cXF1cXIVaBl5fYGFkZoVlWmZlZ2ZnaGhpaWhnZmhpampqbW1tamttbm1ramlpamtsbGprbWxraWdmZWRlZGNkY2NkZGVna2pqa2xubW1vcnV3eXl8fHp4dXFva2lpaGdmaGptb3Bwbm1sa4KlhaY1p6eoqKmrq62sraypqaqpqaiqrLCzt77Dx8fGvru7uLe4tLeyr7C0trKwr6+wr6+uraytrq6ErxOxsrS0s7W5u7y7ubm6uru8vby8hb1jvL69v7++vsDAwcLDxsfKzM7T1dXU1dXV1NbY19na297i5enr7fL09PP2+vyDiIT69O/x8OTf2tfX1tPKxcHCxcTFyMbEwru3tba3t7SzsrCwr66traysrqytr6+vrq6tra6thK41rK2urq2sqaipp6eopqWkpKOlpqempaOjo6Sko6OioqGhoqGio6SoqqqrrKysq6yrqKalo6GEoBWhoaKio6Sjo5+cm5mYlpWTk5KSkpCFkRuQkZGSkpGQkZCQj4+Ojo+OjIyLi4mJiomIh4iFhwuGhYWFhISGhoWFhYWEB4ODg4KCgoOEggqDgoOEhoeHiIiJhYeGhoSFg4SIhQ6GhYaGhYWFhIWFhYaGhYSGHIeHhoeGhoWFhoeIiImKiomIiYmKiYqKiYuKiomEigWJiYqKi4WKHouLioqJiouLjIyMi4uLjIyMjY2Ojo+Pj5CQkI+RkoWTA5SVlISVC5SUk5WVl5iamJmZhZgYmZiYmZiYlpaWlZWWlpeYmJmZmZqbm5qahJkLmpuam52cm5qampuGmkWbmpybm5ycnZ2dnp+enp2dnJubm5ydnJ2dm5ycnJubm5ybm5uamZmYmJeXl5iYmJubm5mZmJmanJybnJycm5qbm5uamJiFmQaYmJiXmJeElQGWhJc5mJeWlZSTkpOTkpGSkpCOjo2OjY2MjI2NjoyMjIuMi4uLiomJiIiKioqLiomJiIiHh4aGhYSDhISEhIWFgwGEhIULhISFhYSEg4ODgoKEgQOCg4OEhAKFhImFhIYDhYaHhImGiAOHh4iEhwaIiIeIiYmHiGGJh4WFhIWEhISDhISEhYiJiYiGhYaGhYSFhISFhYWGhoSEhIWFhYSEg4KCg4SFhoiIiImIh4aGhYWGhomIiIaFhYaIiIeGhIWHiIeGhoaFhYaFhIODg4KAgoKEhIWFhoWFh4aEhSeHiIiGhoaFhIWEhIWFhYeIiYmJiIqKiYiIi4yKi4uKjYyMjI2NjY6GjQGOhI0VjI2NkJCRk5WWlpeWmJiZmZuampubhJwim52dnp2dn5+enJ2foKCenqCfoKGhoKChoaCfnZucm5udnoScKZ2cnp+jo6OkpqelpKerqquura+vrq6sqKWko6OjoqKio6Wmp6alpKSl/4CFgIOB/4D/gP+A/4D/gP+A/4ACAgQAS1hYWFdWV1dZWFpaWV1gYGBdWVVVVVZWWFhaXGJjYmpwc3NxbGhqb3BmX11eYGRiXV1dXl9cXV5eXVxdXV5fXl5fYWNnZ2lqaWtraIRlV2hqbW5vcHFwbW1ubXBwb29vbm9wcXN1dnd3eHp7fYB/gICChIaIi46Slpyjqa2usK6qqq+vt66uopmUk5STioSBgYJ+enhzbWpqa2xsaWdmZmdoaGhnY4RiD2FhYGBgX19dX2BhY2VlY4RiA2NiYoRhQ2BfXVxcW1pZWVhZWFhYV1hYWVlaW1lZWFdXWFdYWVxdYGJhYGBgYWJjY2FeXl5dXFtbXV1eX19iYWFeWlhXVFFQTkyIS4JKhEsHTEtKSklISIVHB0VFQ0NBQUGHQAY/Pz4+PT2HPAg7Ojo6OTo5OYU6HDk4OTk5Ojo7PD4/Pz9AP0A/P0A/QEA/Pj4+PT2HPAg9Pj4/P0A/QIQ/gz6FPQE+hD+GQAFBhUAOQUFBQkRDQUBAQD9AQD+EQIM/hECEQRRAPz8/Pj0+Pj49PT4+QEBAQUJDQ4REVkNEREVFRUZGR0lKSklJSUhJSUpKSktLTExNTUxNTk5NTk1LSkpJSEhHRkREREdKTE1OTk5PT1BQUE9PTk5OT09NTU1LSUhHR0ZHR0dIS0xOTk9PT05PhVEDT05NhEweS0tKSUlKSktLTEtLTEpJSEhIR0ZGR0hISUlLTExNhEwgTUtISExPT05OTEpKSUpKSktNTU1MS0tKSkpJSEdGSEmESgNJSUeERgxHSEhHRkVFQ0NCQUCEPwE+hD0HPj9APz49PYU8gz2EPAg7Ojo5OTk4N4k2Azc3OIQ5gziFNwQ2NjU2hTcYODk7PDw9Pj0+Pj9APz9AQUJDRERERkhIhEmCSoRJBEpKS0yETUhMTEtLSkpJSkpLSklGRUVGSEpLTE9PT1BRUlFPTUtIR0ZFRkVFRkdIR0dGR0hHR0dISUhIR0hLTk9SVFRTVFRWVVVUUlNTVFKHTzdOT1BQUE9OTU5OTEtKSUdFQ0JBQUJER0hKSkhHRkZGRUVGSElISEdFRkdHRkJBQEBBQUJERUZHhEkFSktLTU+GUhBUU1NSUE5NTEtKSklISElKhEsDSktMhE0eTk9PT1BRUVFSU1RWVlVUUlFRUVJUVVZYW15dXV1ehF8XXl1cWlpZWVlYWFdWVFJSUVBPT09QVFWFVCJVVldYWlteYmJkZ2hoZmNiYF1cXFtWVFVVVlZWVVZVVlhXgG5ubW1sbW1ubnBvb3J0dXVzcW1tbW5ubm1vc3l5eH6DiImIhYCBhYV8eHV1d3t6dXR1dnh1dXZ1dHN0dHV2dXZ3eXx9fH5/f4CAfnt8fH2AgYOEhYeIh4SEhYSHh4aHh4aHiYqMjY6PjpCRkpOXlZaYmpucnqCipamwt73Cw8PCTr/BxcTLxMG1r6ysqqigmZaWlpOPioWBf4CAgoOAfnx7fHt8fHt6eHZ3d3Z3d3Z2dnV0dXZ3d3h4dnd3d3Z2dnV1dXZ2dXRzcnFwb25uboVtBm5tbm9vcIRtCmxtbW1ucXN1d3eEdjJ3d3Z1c3Jwb25tbW5vb3Bxc3Nyb2xqaWdkY2FhX15eX19fYF9fXl5eX2BgYF9eXVxbW4RaB1hYV1dWVlaGVYJUhFOEUgpRUVFQUE9QT09PhE6ETwhOT09OT09QUoVTBVRUVFNUhVMEUlFRUYdQglGEUoNThFIBUYRSBlFRUVJTU4dUBlNSU1RUVYRWAlhXhFaCVYZUA1NUVIVVA1RVVYhUD1NS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hjoJOzs8PT0+Pz8/hkADP0BAhD+DPoQ9Dz4/P0BAQUJBQEBAQUBBQIRBGj9AP0A/Pj8/QUA/QUFBQkFAQEBBQUBBQUNDhUIBQ4RCAUGFQgRBQUJChUMSQj9AQD8+PT08PT4+Pj8/QEJChEOERAJGR4RIg0mHSiBLS0pKSkxMTU1OTk9OTk5MS0pJR0hHRkVGSExQU1RUU4dUClNRTk5PTk1MTEqHSCRKTE9RUVBPTk5PT05PUFBPTk1NTk5MS0lJSUhJSUpLSkxLS0mESIhHFEhJSkpKSUpKSklJS01MTExNTU5NhEocS0tNTk5NSklJSUhGRkVGR0dISUpKSkhGRUVGRoRHAUWERA9DQ0JCQUA/Pz4+PT08PT6FPRU8PDs9PT0+Pj08PDo6Ojk4Nzc2NjaHNw04ODg5OTk4ODc3Nzg4hTcgODg4Nzg4Ojs7PDw9PD0+P0FAQEJDRERFRUZGR0hISUqES4RKBUtMTk1Mh0s9SUpKS0tKR0VFRUhLTExMTU1OT09QT05NSkZEQkNDQkJDRUhKS01OT01LSklJSUpKS1FUVFNSUlNTVVVUVIRSDlFRUVJSUVFSUlJRUE5OhE0TTk1LSkhDQkJEREZHRkZGR0dIR4RGeEhJSUhIR0dGR0dIRkVFRUZGRERISktKSkpJSUlLTE9RUVJUVlVSUE5OT0xKSUpLSkpLTExKSktJS01NTExLSklKTE1PUVNXWVtbWlhWV1ZVU1JRUVJSU1VWWV1fYGJjZWVkY2RiYF5eXFxdXlpZWFhWVlRUU1JUVYVYE1dVVldaW11fYWBhYmNkZmNhYF2FXAxbW1hVV1pdXVtaW1wVc3NycnFycXNzcnJzdXV2dnZ0cG9vhHBBcXN2d3l8goKDgoODhIeDfnp4dnV0dXh5d3d5dXZ5eHh4eXh3eHl4eX1/gIB/fX+BgoJ8en1/gYSFhYaHiYuKh4aEhWGGh4iHh4mKi4uMjY6QkpOUlpaXmZycnJ6jpquzusLHx8bEv7i1s7CqpKSinJyenp6amJeVkYuGgHx9fXx8fX98fHx9fn9/fn57enl5eXp5eHZ2dnd3d3h3d3V0dnd2dXVyhHEhcnJycXBubWxsbWxra2xtbm1sbW5vb21ubm1sbWxvcnR2hXcMdnd3d3Z0c3NzcXBwhnEWcnJvbGloZ2VkY2JhYGBgX19fYGBfX4deDlxcXFtbWlpaWVhXV1ZWhVWCVIZTBFJSUVKEUYJQh0+GTghPT09QUFBSUolUhVODUoVRGFJSU1NTUlJTU1NUU1NUVFVVVVRTVFNTUoRThFQEVVVVVoRVCVZWVVZWWFlXWIdXBVZWVlVVhFYiV1hYWFdXV1ZUVVVVVFRTU1NUU1NUVVZXV1lYWFlZWVpaW4Vcgl2JXghfX19eX19gYYZjA2JgYIRfCV5dXl5hZWdoaIZnDGhoaGZkZGVlZGNiYYZgDF9hYWRlZmZlZGVmZoVlAmRjhGQSY2JgYGBfYGBiY2JiYmBeXl5fhV6EXRReX15eXl9fYF9fYGFhYWJhYWJgX4ReJV9gYWFgXl1dXl5cXFtbXFxdXl5eXFxbW1tcXF1dXVxaWlpZWFiEVwVWVlVUU4ZSB1FSUlNSUlKGU4JRhE8PTUxMTU1NTk1NTExMTU5NhE6KTYJMhk0VTk9PT1BRUVFSU1VUVFdWV1ZWV1dYhFkDWltbhFwoW1pbXF5dXFtZWVhYWFlZWVpbW1pYVlZXWFpcW1tcXF1eXl5dXVxZVoRUDlNSVFZYWlpbXF1cWllYhFkNWl1fYWFgX19fYWFgYIRfg12FXwReXl5dhlwiXVxaWVdUU1RVVVZXVldXWFhZWVhYWFdYWVlYWFdXVldXWIRWHldXVVVYWVpaW1tbWltcXFxeXl9hYmFfYF9eYF1bW4RcXF1dXVxdXVxdX2BfX19eXl5fYWJkZ2lqa2tqamlqaWhmZWVmZ2hpa2xtcHFzdHV1dnV0dXRzcnFwcHFycG9tbGtqaWppaGlpa2trbGxsamtsbm9xdHZ2d3d4eHl2hXQPc3NzcXFta21wc3RxcHFxhayCq4SsFq+vsLCxsa+rqqqrq6yrq6+xsrK4wb6FvznCv7u3tLSzsbK0tLO1t7KztLO1tbWzsbOzs7W5u7q7u7m6vL29uLi6vL2/wMLCw8THxsTBwL/AwMGEwlTExcfHx8jJy8zMzc7Pz9DS0tLV2dve5Orx9fPz8e7q6Obj3djZ2dXW2tra1tLQzszIxL63tre2t7e5tra1t7m7u7q4tLOztbSzsbGwr6+vrq6wsLGErx2urayrqaioqKmpq6yqp6alpaakoqGkpaWkpaWmp4SlF6Sio6Smqq2vsLCwr6ysrK6urKmnp6emhaUPpqWkpaSfnZuamZiWlpaThZQGlZSUlJOThJKFkQqQj46Ojo2Li4qJhYgFiYmIiIeFhgaFhYWEhISFgwGEhYMBgoeDFoSEhIWGh4eHhoeGh4eHhoaHhoeGhoaEhYaGB4WFhoaHh4eFhg2HiImJiIiHiIeHh4iKhYgDiYmKhIkRiImKjI2LjIyLioqKiYmKiYmEiwOKiYqEiwiMjIyLjI2MjISLCIyMjI2Njo+PhZAFkZGSk5SEkweUlZWVlJWWhpUTl5eYmJiZmpuamZmamZmampqbmoSZB5udn5+fnp6FnwKgn4adD5ydnZycm5ycnJudnZ+goISeB5+fnqCgoJ+FngSdnZydhpyEnQebmpmampqZipgRmZiZmZmYmJiZmZmbm5ucnJmEmBCXmJiYl5aWlZaWlJWUlZWUhpULlJOSkpOUlJOTkZGGkAKPjoSNAYyFi4SKBIuJiIiGig2JiYiIiIeHhoWEhIWGhIUEhISFhIWFCYSEg4SEhYSEhIWDA4KCg4SEAYaEhwyIiIiHiYmIh4eHiIeEiQaKi4uKi4mEiBiKi4qJiIeHh4iHhoaGh4iIh4SDgoOGiImFiBOJiYmHh4aFhIOEhISCgYKEhYaGhIeDhYSGDIeJiouKiomKioqJiYWICoeHiImKiYiIiYmHiBKJiYiGhoWDgoOEg4OEg4SEhYWGhhOHh4eFhoaHhoaHiIeGhoaHh4aHiYkei4yLjIuMjY+Pj42MjI6NjIyNjo2Njo6Pjo+Pjo+RhJMSkpKSlJWWlpaZmpudnZ2bnJychJsgnJydn6CfoaSlpqeoqKinqainpqampaanpKOjo6GjoaGEnxmhoaOjpKOio6WnpqeqrKurra2tr66trayrhqoKp6Wnqqysqqmqqf+A/4D/gP+A/4D/gP+A/4CIgAICBABCZ2hjYWFiYV9gYF5fYmZoZWFgXlpYWVxfX19gYWRlZWdnaG9ybmprampqaGdlZ2ZjYWJlY2JhYmBgX2FhYmFhYWJjhGQXZmlrbWtmZmdoa21wcnZ0dHN0dHJwb3CFcURyc3V0c3R1d3p7e3x+fn1+f3+Ch4uRlpufpKOkoJuYlZOTlJGMiYqMkJCMioiCfnp5dm9ramxsaGdnZmVlZ2hqa2pqaIZmA2VkY4VhEF9eXl1dXVxdXVtYWFdXVleFWBNXWFdXVlZXWFlZWVtcW1paW1pbhVw3Xl9fYGBhYGFiZGNjYmBfYF9fX2BhYGBfYGBfW1hYVlRUUE9OTUxMS0tLTE1NTUxLSkpJSkpKSYVIAkdFhEQIQ0NDQkFBQUCKPwQ+PT09hjwPPTw7Ozs8Ozw8Ozw8PD09hD4DPz9Ah0EBQIs/hkAPPz9AQEFBQUJBQkJCQ0NChEEFQkJDQ0OHRQtDRENDRENDRERFRoRHDUhHRUVEQkJCQ0REREOEQgxERkZEQ0JDRERDQ0GHQAtBQkNDQkFCQkJDRYRGCEdHR0hJSkpKhEsbTEtKSUpJSkpKTE1NTk9PT01LS0tKSktMTVFThlQjU1NTVFNTU1FPTUxMTU1MTEpJSUlISUtPUVFRUE5OTk9PTU2ETgZMTU1MS0qFSAJJSoRLP0pJSEdHSEdHRkVEQ0JCQ0RFR0lMTUxMS05QTk5NSkpMTExLSkpKS0xNT1BQTEtJSEdFQ0RFRUZGR0dHSEhGRoRFGEdGRkZEQ0RFRkRDQ0JAPz8/Pj4+PT0+PoQ9gzyEPgI9PIQ7Czk4NjY2NzY3ODg3hziENwM2NziHOQk6Ozo5OTk6OzuGPBU9PT8/QUNEREJDREZGR0hKS0xMS0yESx9KSktMS0xMTU1MTUxLSkpLS0tKSUlISElJSUhISktLhEwuS0pGQ0JDRUZGR0hLTEtLTE1NTUxLSkpLTE9QT05OT1FSVFVWVFJQTk5QUE5PUIZRIlBPT09RVVRUUU5NS0lHRERFRkdISEdFRUZHSEhHSEhKS0uETCdLS0tMTEtKSUhJSEhISkxLSUhJS0tMTk9SUk9SU1BPTkxLS0xNTkyESwFKhEwdS0lJSkpKSUlJSkxPTk9PUlRXW1xbWFZWVlRUU1KEU0pUVllbXmBiZWZmZWViYWFiY2JgYGJhXVxbWVhZWVpbWlpaW1tdX11eYGBgYmhramhoZGNkY19eXmBhYWBhY2NiXV1eYWBdW1tcYCZ4eXV0dHZ1dHRzc3N3eXt5d3Z0cnBxcnN0c3N2eXp6fH1+g4eDgISBfIB9eX19eXd5enh4eXl4eHd5eXt5eXl6fHx7fHx9f4KDgn18fn6Bg4WHioqJiImIh4aGhoeHh4iHiIqLiomKi42Nj5GSlJSVlpeYmp+gpqyxtLm5uravq6inp6imoZ6eoaWkoqKgm5aSj4qEgYCCgn9/fn59e31/gICAf32Eew96e3p5eHZ3d3h4dnV0cnGEc2Rxbm5tbWxsbW9vb25tbGxsa2tsbm5vcHBycXFwcXFxb29xcHBzdXV3d3d2dnZ3d3h2dHN0dHRzdHRzcXFyc3Jva2poZ2ZjY2FhYWBgX15eX2BfX19eX11eXV1cW1xbW1pZWVhYhFcEVlZVVYhUCFNTU1JSUVFRh1CCT4VQBVFQUVFRhFKCU4ZUg1WEVAJTVIZThFQFU1NUVFOFVAJWVYVWhVUBVoRXAlhZhFiGVxJWV1dYWVpbWllZWlpZWVlXV1aEVwpWV1dYWFhZWFhYhFcCVleHVgNVVleGWANZWlqEWwRcXF1chF6GXxpeXl9eX19fYGFjZGVlZGNiYmFhYGFhYmVnZ4loBmloaGZkZIRjBGJiYWGEYAZiZGRlZWaFZQJkY4RkCGJiY2NjYmFghV8GYWJiYV9ehV9NXl1cXFtaWlpbXV1fYWJhYWBhZGJiYV9fYWFhX19eX2BhYmNlZGJgX15cW1paW1tcXF1dXV5eXFtbWltbXFxcW1paWVpaWVlYV1ZWVVSHU4RSGlFRUlJSU1RSUlFQUFBPTkxLTE1NTU5OTU1NhU4ITU1NTExMTU6JTwhOTU1OT09RUoRRElJTVFRVV1hXVldXV1hYWVpbXYZcA1taWoRbLVxcXFtbW1pZWlpaW1paWVlYWFlaWllaWltbW1pZWVpXVFNTVFVWVldZW1taW4RcNVpZWVpbXF1cW1xdXl9gYGFfXl5dXV5eXl9fX15eX19fXl1dXV9jYmJgXl1bWVdVVlZWV1hYhFcJWVlaWVhYWVpahVsGWltcW1tahlkmWltcW1tbXFxdX19gYF9gYV9fXl1dXV5eX11cXFxdXF5eXl9eXV2HXjlgYmJjZGVmaGxtbGpoaGlnaGdmZmdoaGlrbW5ydHZ3d3d2dXR0dHZ2dHFydXRxcXBubW5vb3BubG6FcCNyc3R0dnt+gH58eHh4dnJycnN1dXV2dnd3cnJzdnRyb3BydTywsq+ura6vr7Cvr7CysrKxr66tq6usrq+wsLCxs7S1t7i5vr+8ury+v7+9vbu7ure0trq3trS2tbe1tbWEti63uLm4t7i5ury+vbm5u7u9vsDCxcXFxMXDw8HBwsLCw8PDxMXHxcXFxsjKy8vMhM4oz8/R1dfd4OTm6urp5uTh3uDh4t3Y1dXa3d7d29jV0MzJx8G+uru6uIS3Eba3uru7u7q4tbW1tLOzsbCxhLJAsbCvrqyrrKusrKqpqaiop6anqKmpqKampKSko6SnqKmpq6yqqqiop6ioqKmpqq2urq+vr66ur66tra2pqKmnqISnFaalpaWkoJ6dm5mYlZWVk5OUk5SUlYSUIpOSkpCRkZGQkI+QkY+OjIuLiouKioqJiYqJioiIh4eHhoaEhwGFhYQChYSJhYWECIWGhYWFhoeGhIeEiIWHhoaIhwOGh4eEiAqHhoaGh4iIh4iHhYgViYuLiouLioqKi4qMjIuKiYqLjY2NhIwBi4WMhI4UjIyNjY2Ojo+NjY2Ojo6Njo6Njo+FjgGPhZAIkZGRkpOUk5OFlIWVgpaGlwWYmJmam4acBZubnJychJ0Dnp6fhZ4Pn56goaGhoJ6dnZ2enp2dhJwJnZ2dn6GgoKCehJ8Cnp+GoAahn56enp2EnDmdnZ6enpybm5qZmpqamZiXlpaWl5aWl5mbm5uamZmampqbmpmampqZmZqYmZmam5ycm5ubmZiVlJSFlhmVlZSVk5OSkpOTlJOSkpCQj5CRkI+Pjo2Nhowfi42Mi4qKi4mJiYuLjIyLiomIiYiIh4WFhoaFhYaGhYSGg4WIhIaFC4aGhYWEhISFhYWHhIYeh4aIh4mJiomIiIeIiYmJioqMi4qLi4qKiYeIiYqJhIoDiIiHhIYLh4eFhYWEhIaHh4eEhguIiIeHiIeFg4ODhIWFiIchhoaGh4eIiYeHiIiJi42Mi4mIh4WGh4iHiIqKiYiKioyLhIoQjIyNi4mIh4eFhIOEhIWFhoSFCYeHh4aHh4eIh4SIG4mIiYqJiIiIh4iHh4eJi4yKiYmKiYqLjI6OjYSOB42Li4yOj4+GjgePj4+QkI+PhJE5kpOTlJeWlpaYmpqam5ycm5ubmpubm5ydnZ2enp+go6Wmqamqqammpqapqqupp6mop6ampaSlpqWnhaUXpqenqaqqrK2ws7Ozsq6vr62qq6usr66Erwuwra2tr62rqKiprP+A/4D/gP+A/4D/gP+A/4CIgAICBACEaF5qbWtnZWVnZ2ttbWlqbmlaWlxfYWVra2psbW1ubm9vbGNhX15gZmlvcnNwbmxramhmY2FfYmRlZWRjYGRlZGRkZmhqa29taGVlZ2pucG9zdXR0dHNycHJwcXFzdHNzhHU6dnl7fH19fX5+fX1+f3+EhoqOkZCRkpSWlZGPkJGRlJSSiomKjpOUlpaRi4Z/eXZzcnNvaWdnZ2hpa4RtC2tqaWhpZ2ZlY2NhhGA5Xl1eXl9eXFtZWVdXVVVUVFRVVVZWVlVWV1lZWVhZWVlYWFlaWVpaW1tcXV1dXF5fYGFiYWFiY2RkhWOEYhdhX15eXVxbWVlXVVRSUE9OTU1NS0xMTIVNBExKSUmGSA9HR0ZFRURERENDQ0JCQUGFQIY/Cz09PTw8PDs8PDs8hT0QPj0+Pj4/Pz8+Pj4/Pz9AQYZCBEFBQECEPwdAPz8/QEBBhEKCQ4VEDkVFRUREREVFRkVFRUZFhUYSR0hJSEhHR0hISUlISElJSktKhUgDSUhHhEgXR0ZGREREQ0NEQ0NDRENFRERDQ0JDQkGFQg9DRENDQkJDQ0RERUZHSEmFSgFLhExGTUxMTEpKSktLSkpKS0tLSkpKTExLS05SVFZVVVVUU1NTUlFSU1JSUVFOTk1NTU5OTEtKSUlJSElMTk9QUFBPUFFRUU9OToRMR0pJSElIR0dGR0hKTExLSklIR0ZFRkZGRURDQkJCQ0RGSElLTU9PUE5PTExLS0xNTUtLSkpKS0xMTlBPTUtKSUhGRERFRUdHiEYORURERENDQ0JDREVFRESEQhNBQD4+QD8+PDs7Ozo6Ozs8PTw9hjwEOjk4OIU2CDc4ODc4ODc2hDcgNjY1NTc4OTk5Ojs7PDw7Ojg3OTs7PD08PT09Pz5AQUGEQglDQ0NERkhISUqHSylJSktNT05PUFFRUFBPTk5OTU9PT0xKSUhJSklJSElKS0lISUlIR0ZGR4VIEkpLSkpISEpMTUxLS0xOTk9PT4ZQNlFQT01NTUxNTExNT1FRUlFQTU5PT1BRUVJTUE1KSElISEdGSElJSUhFRkhISEdHR0hJS0xOT4VQJFNQTEtLS0pKTE1MTEpKSUpKS0pLTExLTE1NTk5OTUtLTExNTIZNHk9RUE5KS0xLSUlKSk1OTlBQUlZYW1tYWFdVVVhXVYVUGlVUV1teYWVnamppaGdnaGhnY2BhYGBhYmFghF8sXl9gYWNiY2NkaGpqZ2RnbGloZ2dkYF1cXF9kZWJfXl5fYGJnaWZhXV1eYGNUe3p6eXt+fXt5eHp6foCAfXx+fHJzdHV2en5+fn9/f4CBgYF/eHd2dnh+gYWIioaDgoGAfn18enh6e3x8e3t4fH18fHx9f4GBhIN+fHx+gYSIhomLhIqAiYeHhoeHiYmIiIqKi4qLjo+QkZKVlJOTlJOUlJibnqOlpaWnqaqno6Okpqaoqaehnp+lqKqsrKijnpSPi4iGh4aBfn5+f3+BgoODhIJ+fXx8e3p6eXl3dnd2dnRzc3N0c3NzcXBtbGtqamlpamtsbW1tbG1ubm5tbW5vb25vcXACcHGHcgJ1doV3BHh5eXiEdxV2dXV1dnVzcnFxcG9sa2toZ2VjYmGEYIVhEmBhYGBfXl1cXFxbW1taWVhZWIZXBFZVVVSEVQRUVFRThlIEUVFQUIVRAVCEUQVSUlNTVIdTDVRUVFVVVldWVVVVVFSFUwdSUlNUVVVWhFUIVldXV1ZXWFeFWAFXhFgCWViEWQZaW1xcW1uEWglbXFpaWltcXV2HWwRaWlpZhFoBWIRXhlgBWYZYgleFWA5ZWFdXV1hZWVpaW1xcXYZeAl9ghGEEYGFhYIRfCV5eXl9gYWBgYIRiC2RnaGppaWhoZ2hohWcWZmZmZGRjY2JiY2JiYWBgYF9gYmNkZoZnBmZlZWVkY4RhBGJiYWCEXwdhYmFgYF9fhl4aXV1bWlpaW1xdXl9gYmNkZWRjYmJhYWJiYmGEYBJhYWFkZGNiYWBfXl1cW1xaXF2FXINbhFkKWlpbWllaWlpYWIRXC1ZVVVRVVFRSUlJRhFAEUVFRUoVRBVBQUE5Ohk2ITg1NTU1MTEtLTE1OTk5PhFAGT09OTU5QhFEIUlNSVFNUVVWEVoRXClhZWVpbXFtbXF2HXBxdXl9fX15eXV1dXFxeXl5cWlhYWVpZWVhYWlpZhFgEV1ZXV4RYHFlaWlpZWVlaW1xbW1pbXF1dXl5fXl9eXl1dXl2EXCNdXFxdXl9fX2BfXl9fXmBgYGFiXltYWVlYWFdWWFlYWVlYV4RZL1hYWFlaW1xdXl5eX15gXlpZW1taWlxdXVxcXFtbWlxcXV5dXFtcXV5fXl1cXV1dhl5GX19gYWBfXV5fX15eXl9hYWJkZWZoamxta2tpaGhrbGtpaGhoaWppa25wc3Z3eXl6eXl3eHl5dXN0c3R0dnV0dHV0dHN0dIR2JXV2d3p8eHZ5fXx8e3t4dXJwcXN2d3V0c3N0dXh7fXp1cHFydHc4s7GzsbO1tbOysrSztLW2tLa5tq2ur7CvsrS0tLW1trm5uru5tLSzsrS6vcHGyMXCwb++vbu4t7aEuRi4trO4ubm6ubq7vL3AwLu4uby+wMLBxMWEw4DBv8HBwsLDw8TEx8fIxsbIycrMzM7Pzs3NzM3O1NXY2tzc3N7f393Z2Nrb3N7f3tnX2t7g4uTj4NvV0MzIxMHBvrm3uLe5u7/Avr6+uri3tra1tLOxsa+vsLCxrq2urq6trKupqqiopqampaKjpKWnp6alp6eop6enqKiop6mrqi+rq6yrq6ysrKutra6vr66usLCwrq6urKyrqaqpqailpKSjo6Kfn52bmpiVlZOTk4WUhZYGlJKRkY+PhI4QjI2Mi4uKiomKi4uLioqKiYSIFIeIh4eGhoaFhYWEhIODg4KCg4OEhIUYhoaGh4eGhoaHh4aHh4iJiImKiomJiIeIhIcBiIaHhIgBiYWIhIkWiIiJioqLioqJioqKi4yMjI2Oj46PjYSODo+NjYyNjY+Pjo6OjY2PhI4KjY6PkI+NjI6OjYSOhY8ZkJGRkZCQkI+Pjo2PkZCQj5CRkpOTk5SUlIaVh5eCmISXgpiEmQ2amZmZmpucnZ2en6CghJ+CoISfDKCfn6CdnJycnZ6enoWdEZycnp+goKChoKGhoJ+fnp2dhZ4Zn5+dnZ2cnJucnZ6enZybm5mZmpqamZiXloSVFZeYmZmbnJubmpyam5uam5ubmpuamoSZEJqcnJuampmZl5aVlpWWl5aElQaTlJOTkpKEkSCQkJGSkpGRkI6Pjo6NjYyNjYyMi4uLiYmJioqLi4uKioSJFIiIh4eFhYWGhoaHhoeGhoWEhYWFhIQDg4SEhIULhoaGh4aGhYWGhoaEhxKGh4iHiIiIiYmJiImJiYiIiYqFiweMjIuKiYqKhYuCioeJEIuLiomIhoaHiIeHhYWGh4WGhA6FhIaGhoWFhoeGhoaFhoiHBYiJiImIhokFiIiIh4eEiAGJhIsPiomJiomJioqKjIqJhoaGhIUBhoSHgoWFhxWGhoaIiIqKjIyLi4uNi4iIiomIh4mFiyKKiomLi4yNjIuKjYyOjo6NjY6Oj4+Oj4+Pjo+PkZKSkZCRhZIQk5SVlpiYmZyam5ycnZybm4edCZ6fnp6ho6WoqYSqPqiprKysq6qqqamqrKuqqqurqqipqamqqqqpqqyur6yqrbKxsrSzr6upqaqtr7Cur66vr7CytLazsKusra+w/4D/gP+A/4D/gP+A/4D/gIiAAgIEAFxra2xtbGxraGZkZGRnamlma2tnYmJjZGdrbnBxcW9vcG5qaGdlYmBgYWRsbnBwbWloZWdmY2FhYmJjZmdlZGJhYmNkZ2tvcnJwa2dmaGpvcnR1dXZ2dnp6eHV2doV3DXZ2dnV2eHl8fH19fn6EfTl+foKDhomJiouOj4+OjY2NjI2Njo6OjYyNkZWWlY2JhH99fHx5eHZ1b25ubm9vcHFwcG5ramhoZmOFYh1hYGBfYGFiY2NfXFpYV1ZWVVVVVlZVVlZXV1hZWoRbElpaW1pbW1pZWllaXF1dXl9fYIRhMmJiY2RmZ2ZmZmVmZmZjYV5cWlhZWVhYVVVUUlBPTU1OTk9RUFBPTk1NTExLSUhISUlJhEeERoRFM0REQ0NCQ0JCQkBAPz8/Pj4+PTw8PDs6Ozs8PD0+Pj4/P0FDQ0NCQkFBQkJERkRFRUVERIVDBUFAQEBBhEIWQ0JDREZFRURDRERERUVGRkZFRUZGRoZHB0ZGRkVFRUaERwZISUpMS0qESR1KSklISElISElJSkpKSEdHRkVERUREQ0NDQkNDQ4REiEMpRERDQ0JCQkNCQ0NEREVGR0dHSUhJSUlKSktLTExLS0pKS0tNTExLSkuESRZKTE9QUFFTU1NVVldXVlVTUVBOT05OhU0QTExMS0pKSUlJSEZJSkxNT4ZQOE1NTEtLS0pJSEdISEdHSElJSkxMTEpIR0ZGRkdHSElIRkVFRUZISUlISElMTk1MTEtKSUpKS0xKhEkTSktMTU5OTUxLS0pISUZGR0hHR4VGAUWERAdCQkJDQ0REhEUYRkZFRENCQkRDQD8+PT48Ozs7PD09PTw7hToDOTk4hTcHNjY3Nzg4OIU3hTYfNzg4ODk5Ojs8Ozo6OTk5Ojs8PDw9PT5AQUFBPz9AQYVDFkRFR0hJSUlKSkpLS0xNTE5PTk9PUVKFUQ9QUVBQTktISEhJSUhGR0mISgNJSUeERhNHR0dGRUVGR0lLS0pLTE1NTE1Nhk4ETU1LS4VMHktMTU9QUlBPTUxNT09PUVNUUk9JSUtLTEtJSUpKS4ZJAktMhU0BT4VRD1JTUlBOTk5PUlRRUE9NTYRMN01MTEtLTE1NTk9OT1BQT05PUFFRUE5OT05PUE5PUFFQTE5PT09QUVJTVFVWVlZUVldWVVdXV1aEV0pYWVxgZWttbnJzdHJxcmtoZ2dlZWZgXl9fYWFhYmZnaGhpa2ttbXFyd3h1bm9ybmppZ2NhYWBgYmNiYF1dXmNlam1rZ2ZnamhlZ1B8fHx+fX18eXh3eHl8fXx6fHx6eHd2eHl8f4KCgoODg4KAfn16eHd4eX6FhoaGgn9+e318e3l6enp7fX59fHl4enx9f4KEhoeGgn19gIKFh4WJCYqNjouJioqLioSLG4yMjI2Oj5CRkZKTlJOSkpKTk5aXmp2en5+ho4WkhKMZpKWjoaKnq6yspaCalJKSkpCOjYyHhYaFhYSGCIWEgX58fHx6hHkCeHeFdRZ3eHl2dHJvbWtsa2trbGxrbGxtbW1uhm+EcA1vb29wcXFzc3N0dXZ3h3g0enp6eXp6eXl5eHZzcW9tbG1tbGxqamhlYmJgYWFiZGVlZWNjYGBgX19eXFxdXFxaW1paWIRZhFgFV1ZWVVaEVQNUU1OFUgdRUVBQT1BQhFEQUlFSU1VWVldXVlVVVlZYWYZYgleEVoNVhFQQVVVWVlZXVldWV1hXV1hXWIZZH1paWltbWllZWFdXWVlaW1taW1tcXV9eXl1cW1xcXVyFWw5cW1xcW1pZWVhZV1hYWIVXAlhXhFiCV4dYDllZWVhXV1hYWVlZWltchV0EXl5eX4RgAWGEYAhhYWFiYWBgYIRfFGBhY2RmZ2hnaGlqamppaGhnZmVlhmQHY2JjY2NiYoRhBmBiY2RmZ4RoDGdnZWVkZGRhYGBfX4dgBWJjY2JghF8YXl9eX19fXVxcW1xeX19eXV5gZGRkYmJhhWAVX19fYF9fYGFiY2RkY2JiYF9fXVxdhF4QXV1cW1pZWllYV1hYWVpaWoRZD1paWFhXV1dYV1VUU1NTUoRRA1JTUodQhE8HTk1NTUxNTYVOg02FTApNTk5NTU5PUFBRhFAOT1BQUVFRUlJSU1NVVlWEVBRVVlVWVlVWV1laWltcXF1cW1xdXIRdA15gYIVeE11eXl5cW1lYWFhZWFdXWVpZWlmFWgZZVlZXV1aEVwVVVVZYWYZbB1xbXF1eXV2FXCBbWltcW1xbWltcXV1fX15eXl1fX19iYmJhXlhZWltcW4VaAVmFWiBbXF1dXVxcXV5eX19fYGBfXlxcXF5gYWBfXl1cXFxbXIZdJFxdX19eX2BgX19fYGBhYF9gYF9fYF9fYWJhXmBhYWFiZGVmZ4RoV2doamppampramtrampsbW9xdXl7e35/gH5/f3t4eHh3eHl2dHR0dXZ2d3h5enp7e3t8fH+Ag4WCfH6Df319e3d1dnR0dXZ2dHNyc3Z5fX9+eXh7fXp5eoS1TLS0tLOzsbGxtLW0s7e5trSzsrK0tre2ubm3ubq6urm4tbW0tLS4v8HExcLAv76/vbu5uLm4ury9vLu4t7m6uru9wMPExMC8u7y/wsSGxQjIyMfFx8bHxobHIcbGx8jKysvLzc7NzczNzc7T09bX19jZ29va2djZ29zd3ITdF9ze4OLk49zY1tPPzs7KyMbFwcDBv8DBhMIJv7u5t7e2s7OxhLCFsQyys7Wwq6mnpaWmpqeEpQ2mpqemp6mqq6qqqamphaoHp6ioqKqsrIWtA6+xsYWwMbGwrq6trq+urKelpKKgoJ+enpycmpiWlZSUlZWXmJiYmZmXl5WVlJGQkJCPj42OjY2FjIeLNYqKiYmKiYiIh4eIh4aFhIWFhIOCg4OEhIWFhYaGh4mKiomIh4mKiYqLiouLjIuKiYmKiYmJiIiEiYKIiIkBioSLIIyNjY6Ojo2NjIyMi4qLjIyNjYyOjo6PkJCPkI+Pj46QhI8Ejo+Qj4WQA4+OjYWOD4+Ojo+Pjo+PkJCPj5GRkYSQAZGHkIKShJMIlJWVlpWWl5aFlwOYmJeGmAGZhJoUmZqbm5yenZ6eoJ+foKKkpaOhoJ+EngmdnZ2en5+en56EnRWenp2cnp6foaGioqKhoaKgoKGfnp2EnISdhJ4hn5+fnZubnJubnJucnJuZmZmYmZqamZmZm5ydnZydnZubhJoXm5uampmampucnJybmZmampmZmJeXl5aFlYKUhJMCkZCEkSuSkpOSkpOTkpGQjo6Pj42MiouMiomJiomJioqKiYiIiImJiIiHh4aGhoeHhYYHhYaFhYaGhoSFBIaFhIOEhAmGhYWFhIWFhIWFhguHiImKiYaGh4iJiYWIH4mKiomJiouLi4qKi4mKi4qKiYqLioqLi4uJi4qLioiFhgOFhIWHhwmGh4eHhYWFhISFhQSEhYaHhoiEiQqKiYqKiomJiYiHhYiEhxaIi4qKiIiIioqLjIyMjY2IiImKioiHh4gih4eHiIqKiomJiYqLi4yMjI2NjYyLi4qMjY+NjY2Li4uMi4WML42NjY6PkI+QkI+Pj5CRkpKSkJGQj5GTkpKUlZSRlJWUlZaYmZqbnJqbm5udnp2chJ4moKGgoKGho6Wmq62rra+vra2trKurrK2tramoqKiqq6usr7Cvrq+EsCSxsrSysK2ytbOxsa+trKyrrK2urq2trK6wsra5uLW0trm2tLX/gP+A/4D/gP+A/4D/gP+AiIACAgQAYHh7eXl7fH16enZuZ2lqamZpamxtaWxvdXZ0dnZ0bGttbWxpamhlZWVhYmlraGpnZmhoZWRjY2VmZ2lra2lqamtqaGlrbnV1dndxampqbHB0eXh2dnd3eXp5dnZ3eXp6eoR4L3d3d3Z8fX6AgIF+fn6Af39/gIOFh4mLjYyMjYyNjIyJh4SDhYeJiY2Qk5GMhoB9hXslenp5d3VzcnBsa21sbWtqamhmZGRkZWZlZmZnZmVlZ2dlYl5aWIhXG1ZWVlhYWVtaW1paW1taW1pbXF1eX19fYWFhYoRhFmJiY2NkZWZnaGlpamlqaWdkYl9cWluFWgRXVlRShFGEUBJPT05NTEpJSkpJSEhJSUpISEeFRhRHRkZFRUNCQkFBQkJBQUBAQD89PYQ8Qjs7PDw8PT09Pj4/QEBBQ0NERURFRUVGRkdISEhHRkZFRURERENDQ0RDRERERURFRkdHR0ZGRkdISElKS0tKSkpJSYRIFElJSklJSEhIR0hISEdKS0xMTEtLhEwGS0pLS0pKhEkDSkhHhUUCREOGQgpEREVFRkVFRERFhUQGRUVEQ0NEhUMMREVFRklKSkhJSElKhEuEShhLS0xMTE1MS0xLS0pKSUpMT1BPUFNTU1SEVQtUVFFPTk1LSUpKS4VMN0tKSUhISEdHSUtNTk5OT1BPTk1MS0pJSUdGRkVGR0dKSUpKS0xNTUtISEhJSkpKS0tKSkhHR0eFSBFJS0xMTUxNS0tKS0pLS0pKSoRLBk5PT01MTIRNAUuFSQJHRIVDCEJCQkBBQkNFhEaCR4RGC0VDQ0JAPz4/QD8/hD4OPT08PDs6Ojo5OTo4OTmFOAo3Nzg5OTk4Nzc4hDcIODg5OTg5OjqGOws6Ojk6Ozw7PDw8PoRAAj9AhUEwQkNERUZHSEpKS0xLSkpLS0xNTUxNTU9QUFBSUlFRUE9OTU1MS0pLSkdISUtMTEtJhEgZRkVERUZHSEdGRUVERUZISUhJSktLTExMS4RKI0lKSUlJSktLTE1NTUxNTU1PUVJSUE1MS0xOUFBRU1BNTk5PhFIZU1BOTEtLS0xNTlBQUE9PT1BSUlRTVVRSUoRQElNUVFRSUlFRUE5MTU1NS0tMToRQe09QUFJTUU9PUVNTU1RUU1BQU1RWVlVVVlhXVFJSU1RWV1VVVVZVVlhVVFVUVVZXWFdaXGBkZ2pvc3V4eHNvbWtrbnN2cnBoYF9gYmNkZGdpbnF3e4CAfXp8g4mJgn16d3Z3dnFraGViYGBiYmRnZ2lucG9sampsbm5wcx6Gh4WHh4iIiIeGgnt9fHt6fH1/f3x9gIWGhoiJiYOEgRqAgH99fn57e4GCfoB9fX9+fHp6enx9fX6AgId/X4CDiIiJi4V/f4CBhImOjIuMjIyNj46LjIyNjo+Qj4+Pjo2NjYyQkZKUlJWUk5OVlZWUlJaanZ6foKGio6GhoZ+enJuam5uenaGlqaehm5aTkJCRkpOTko+OjImIhoOBhIMHgH59fHt7eoV7HHp5eXl8fXt3dHFubG5ubm1tbWxra2ttbW5wcHGFcIZyBHR0dHiHdzl4eXl5ent7fH59fn59fX17d3VxcG9vbWxtbm5raGZlZGRkY2RjY2RjY2NhYV9dXl5dXVxcXF1bW1qFWRRaWVlYWFZXVlZVVlZVVVRUU1NTUoVRglCGUQhSU1RUVVdYWYVYAllahFsKWllZWFhXV1dWVoVXAlhZhliEWRhaWltbXFxbW1xbW1tcXVxdXV1cW1taW1qEWwFdhl8FXl5eX16EXYRcCltdXFtaWllaWVmJWIJZhFoPWVlYWFlZWFlbWlpZWVlYhFkTWlpbXV5eXV5dXl9fYGBhYF9fX4VgE2FhYGFhYGBhYWFiZGdlZWdnZ2iGaQlnZmRiYWFhYmKFYwtiYmFgYGFhYmJkZoRnJWhmZWVkZGNjYmBfX15eX19hYWJhYmJjY2FfXl5fX2BgYWJhYWCEXgtfX15eX2FiY2NjYoRhBGBhYWCGYRdiYmNiYWJjY2NiYWBfXl9fXlxaWllZWYZYAVmEWwFahFsPWlpZWFdXVlRTVFRTU1NShVMIUlBPUFBPT1CET4dOAU+HToRNCk5PTk5OT09PUFCGTwtQUVFRUlFSVFVVVYhUC1VWV1hZWVpbW11dhVwGXV1dXFxdhF44YGBgXl1dXFtbW1pZWlpZWVpaW1taV1dZWVlXVlVWVlhYWFdVVVRVVldYWFhaWlpbXFxbWlpaWVmEWklbW1xdXl1dXFtcXF5gYF9fXlxbW11fX2BiX1xeXl5hYWBhYV9dXFtaXFxdXl9fX15dXV5gYGFiY2JgYV5fX19gYmJhYGFgX19ehF0RW1xdXl9fX2BfYGFiY2FgYGGEYwVkY2BiZIZlY2dmZWNkZmdqamhpaGhnaWtqaGlpamprbGxucXR1dnh8f4CBgX99e3t7foGEgH96dnZ3d3h5eXp7gIOGiImHhoWGi46Pi4mHhYaGhX97enh1c3R1dXd6enx/gIB8ent+gYCAgjy7vLu6urq5ubm6trK0tbWytbe4uLe3t7y9vL29vLy8vb28u728ubq6trW+wLy/vLq8u7q5urm6uru8vr6EvYC+vb6/wMPDxMfDv7+/wMPHy8nGycrKy8zMysnKycnJysnJycjHyMjHysvLzs/Qzs7O0M/Q0NHT1dbW1tjY19fX2drZ2dfW1NbW19ja3uHf2dPRzsvKyszNzc3LysfDwsG+vL69vbq4uLe2tbW0tba1tra1tLOztrW0sa6pp6eoqASpqKenhKY7qKeoq6qrqqmqqqmqq6ysra2urq6ysrGxsK+ur7CwsbCxsrO1tra1tLKysa+tq6iloqOioJ+gn52cm5mGmBSXmJiXl5aVk5GSk5KQj46PkI6OjoWNCI6OjY2Mi4uLhYoHiYiIiImHhoSFCoSFhIWEhIWFhYaEhxGKiouLi4yNjYyLjIyNjo2Li4mKg4mEiiCLi4yMi4qKiouMjY6Ojo2Oj4+Pjo+QkZGSkY+Pj46Oj4SOCJCQkJGSkZKThJIQkZGRkI+Pj46OkI+Qj4+PjoWPApCPhY6GkAKRkIWRD5KRkZGUk5OSkpKTk5SUlYSWKZWVl5eXmJiXl5eYmJiZmpqbm5qbm5qam5ubnJ6fnZ6fn5+go6Sjo6KghJ4UnZucm52enp6fnp6dnZ2enp2enqCGoiahoaGgoJ+enZycnZydnp2enp+foJ+ioZ+cm5ucnZ2en5+enp2cnISbBJqam5uFnQWbnJucm4acD5ubm52dnZuZmpqbmpqZmISXBpaWlZWUk4SSGJCQkJGSk5KSkpSUk5OSkZGRkJCNjIuMjYSMB4uLi4yLi4qEiASJiIiIhIcIiIiGhoaHh4eFhoSFCoaHhoWEhISFhoaFhQ6EhYaHhoeGh4eIiYqJiISJAYiEiYSKCIuLi42LiYqKhIsNiYqKi4uKioyMjIuKioWJHYiIiIWFhoiJiIiGhYaIiIiHhYaFhoaFhYaGhYSFhIcHiImIiImJiIeHgoaEiB+KiYmIiYiJiYyLi4qJiYiKjIuLi42LiIqJiouMi4uKiIkRiouMi4uKi4uMjI2Njo6OjYyFjQmOjo6Pjo6OjYyFjQ+Ojo+QkJCRkJGRkpKSkZGElAaTkpGSlJWFlgmYl5aWl5iZnJ2FmzidoJ6en56foKCjoqOipaanqKqrra6vrayqrKyusLGwsa2qqquurq6vsbO1t7i8vby5t7i8vLy7u4S4A7e1soSwEq+wr7GzsrO2uLi2tLS1ubi5uf+A/4D/gP+A/4D/gP+A/4CIgAICBABQgH18fYGGhoeDgYN7cXB1cnJ2foaBf3+ChYJ8d2ZmbHJzdXFrZ2VlZmVlZWZlZGRlZ2hoZ2hpbW9vcXJvbW1vbGpqamtvd3d1dnJvcnNzdXeEdhd1d3p6e3x6eHd3eHh3d3h5eXp6enx/gIZ/CICBgoGDhIWGhIdAio2OjIeHh4SDg4SFhYaHi4yMioiIhIKBgoOCgYCBfndzcm9vcW9sa2tsa2tqamtrbGxsb3Bva2hoaWViYWBeXIRbCVlZWVhXVlVXWIRZhFqEWwZcXl9hYmGEYCxiY2JiY2RkY2RmZ2psbGxra2pqaWdlY2JiYV9fXl9dWldVVFRTUVFPT01MTIRLhkkeR0hJSUhHRkZFRkZIRkVFRERDQ0JCQkFBQEFBP0A/hT0GPDw8Ozw8hD6EPxNAQkRFR0hISElISUhIR0dIR0dGhEUNRERERUVERENDRUZHR4RIJUlLS0tMTExNS0xMS0tKSUhJSUpKSktKSkpJSUpKSEpLTU9QTk2ETg5NTU1MTUxLSktKSUhHRoZHBEVERESEQw5ERUZHSEhHR0hGRkZFRoRFEEZGRUVERENDREVGSEpKS0yETixNTUxNTExLS0xMS0xMTUxMTE5NS0pLTk9PT1BSUlJRUlJTUlJRUE9PTU1MTIRNGkxMS0tKSUhISUpLTE1OT09PTk5NTUtKSkpIhEYGR0lKS0xMhEsGSkpJSElKhkkXSklISEdISElISEhJSktLTEtKSkpLTEyESzRKSklKTE1OTkxNT1BPTk1MS0pKSUdFREJAQEFCQ0JCQ0NDRkdHR0VGSEhHRUVERENCQT8/hUAKPz8/Pj09PDs6Ooc5DDg4ODc4Nzc4ODk3OIc3Hjg5ODk4OTo6OTk6Ojo5ODg4Ojs8PDw7Ozw9Pj49PYU/UkBAQEJDREVHSUlKSUlJSElLTU1OTUxNT1FSUlJRUFBPT09QUVJTUk9NS0pKSklJSUhGRkVFRENFRkZGR0ZEREVFRkZHSEhISUlJS0xLSkhISUiFSSBKTExMTU1NS0tKSkxPUlNSUlBNS0tPT1FRUE5QUVJVWIRZG1NQTk9NTU5PUE9QT09QUVNXWVlYVlNSUlJQUoRUgFVWVVRVVFBOTU1MT1JTUlNTUlRUVFVXVlRUUlNTVVdaXFtaWFdYWFpaWl1hZ2JZVVldW1hXWVtaWVhYV1lZVlVXWVtdX2Jnb25xc3J0dHJxb25xcnd+g3xxZWRjYmZpaWlrb3h9goWIioqIipKXlIp9eXh2eHd2cmpmY2ZmZ2puDXp/e316dG1vcnJzdn0wjouKi4yQkJGOjY+JgYGFg4OGi5GOjIyNkJKNin5+gYWGiIV/fXp6e3t8fH17enp7hX86gIOEg4aIhYKCg4GAgICBhYmKiouGgYWHiYqMi4uNjIuNj4+QkJCOjo6Pjo2Nj5GPkJCPkZSVlJSTlISWgJeWmJiZm5ydnZ6goKCenJydm5qZmZiZm52goaCenZ6al5WWl5iYmJeTjYiHhYSGhYOBgYGAgH9/gICAgYKEhIJ+fHx9e3d2dHNxcXJzc3Bwb25sa2xtbm9wb29wcHFxcnFxcnN0dXZ4d3d4d3d4eXd4eXl5enl7fYCCgYB/gH9+GX57eXZ0dHNxcXFzcW5qaGdnZ2VmZGNhYWGEXxZeXV5eXl1cXV1cXFtaWllZWlpZWVhYhVeGVgVVVVRTUoxRF1JTUlNTVVZYWVlbXF1dW1xcXFtaW1pahFmKWApZWVpZW1tcXFxdhF6HXRpcXFxdXV1eXV1cXV1cXF1cXFxdX2BhYF9gX4RgBF9eXl6EXQRcXFtahlsNWllZWllZWVhZWVpbXIRbBVpaW1pbhFocW1taWllaWVlaWlxfX19gYWJiYmNjZGJgYGFhYIlhG2NiYWBgY2VkZWVmZmZlZmZnaGhmZmZlY2NiY4RkhGIiYWFfX2JiY2RmZmdoZ2dmZmVkY2NjYmBgYF9gYWJiY2RjY4VihGALX2BgYGFiYWBgX16FXwFghGEkYGBhYWFjY2JhYmJhYGBgYmNkY2JjZGVlZGNiYV9fXlxaWVhXhVgUV1hZW1tcXFxaW1xcXFlZWFhXV1aGVQpUVFNUU1NTUVBQiU8FTk5OTU2GTiBNTU1OTk1NTU5OTU1OT09OTk9PUE9NTU5RUVFQUFBRUoRTAVKEUzlUVVVUVVdYWFlbXFxbWlpaW1xeXV5dXFxeX2BhYGBgX15dXV5fYGBgX15dW1tbWllZWFdXVlVVVFaFV4RWAVeFWAZZWVpbW1qEWTBYWVlaWlpbXV5eXF1cW1taW11fYWFgYF5cW1teXmBgX11eX2FiZGRlZmdiX11dXV6EXydeXl5fYWNlZmZmZGJgYWFgYmNjYmJjZWNjY2JfXV1eXV9gYWFiYWGEYw1lZWNjYmNjZWZoamlohGcyaGhpbG90b2lnamxraWlqbGtqamprbG1qamxubnBydHl+fH1/f4GBgIB+fn+Ag4mMh4CEdzB6fH18foKIjZCRk5SUkZOanJmTiYaFhIeGhYJ9enh6enp8f4aKiImJg3x/g4SFh4xXwL69vr/CwMC/wMC7t7u7ubq9wcfDwL/CxMTBvri4ur2/wb+/vbi2ubq6uru7ubm7vr6+vby9wMHBwsPBwMDCwb/AwMDDyMjExsTDxcbHyMrJyszKycrMhM0cy8rJy8zKycrKycrNzs3Ozs3Ozs7Pz9DR0tHU1YXWL9fY2NnZ2Nja19XU09LS1NbZ2tvY1tbS0c/Q0tHR0tLPysTCwcDAv7y7u7y7urq6hLsdvL+/vLe1t7i0sLCvraqpq6ytq6urqqinpqipqqyFqxOsra2rq6ytrrCzsrKysbGys7GxhLMosrS2ubm5uLa1tLSzsK+rqainpqalp6Wjn5yampqYmJeXlZWVk5STkoSQFpGQjo6Pjo6NjY2Ojo6PjIuKi4yLi4uEigaJioqIiYiGhoOFhIYPhYWGhoeIiYmKi4uMjY6OhY0GjI2NjYyMhosLjIuKi4qKioyNjY2EjAeNjo6Pj4+QhI8QkI+Pj5CQkZGRkpCQkJGQkISRCJOVlZSTlJSThJSDk4WRFJCQkJGRkZKTlJSSkpKRkI+Oj5GShZMElZOTk4aShJMPkpOSk5SVlZWWl5mZmpqahJgEmZmZmISZhJsPmpqcnJuam56fnp6eoKCghKECoqCEnxaenp2enp6fn56fnp6enZ2en5+goaGhhqIHoaGgoJ+enYWeBJ+fn6GGnw+e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W4ZcCVtbWltbW1paWoRbJFpaW1pcXV5gYmJjY2RjY2RjY2NiYWFiYWFhYmJjY2NkZGRlZYZkCGVlZmdnZ2lohWcEZmRiYYdiK2FgYWJjYmNkZWVmZmVmZWRjY2NiYmJhYWJiYmRkZGJjZGNiYmJjY2NiYWCFX4VgEl9gYF9fYGFhYGBfYGFhYWBhYIZfEWBgYGFhYGBhYmNiYWFgX15chVssWllYWFhZWFtbXFxbWlpaW1paWllYWVpaWFhWVVVWVlVVVFRUU1NSUlFRUFCGT4ZOA09OToZNgk6ETQpMTE5PT05OT1BQhE8JUFBQTk9OTk9QhlIMU1NUVVVUVVVVV1hYhFkLWlpaXFxdXl5dXF2FXhldXFtbXV5fYGFiZGJfXVtaWVZVVVZWWFhYhFcYVlZVVlZXWVlZWFdXV1hYWVlYWVlaXFtbhVooW1tbXVtbWltZWVlaXV5fX19gX11cXFxbW1tcXV5gYWNkZWZmZWNhYIVfgGBhYV9fXl9gYmNlZmVkY2NhY2ZnZ2VlZmVlZWNiY2RkY2RjY2RkZGNjZGVlZGZnZ2loZmVkY2VpamxramlrbG1tbXJ1c2xsbW9ybmpsbm1sa290dnRxbm5vcHFydHd4e3+BgIODhIJ+fIKGjJCQjIR7e31/f39+f4CEh4mMj5SXJJ2enJ+iopmRioiIh4eMhn99fH1/goWIkJaZlZONhIGBf36Bh1bBvby9wcHAv8HBwsO+urq8vsLGyMbCw8LFwr28t7m9v8HGxsC8u7q6vLy9vLy8u729v8LEw8PCw8PDwsLEw8HBxsPCxMXGxMXFxMXHx8fIy8zMzM7OzYTMgM3MzM3LzczNzc7Ozs3Q0dDPz8/Q0c/P0NDQ0tTW19bY2NfY2dnZ2trY2drY2NbT0dHV2Nve3djX09XY2NjZ19HNy8nHx8XEwsLAvr6+v8HCw8LAwsHBwMG/vLu6uLazsK+vsLCxsrGxsKyqqaqsrKupqausrK2sq6ytrq2usLGzCbOztLKwsLCxs4WyM7O1ubu6ure2tra1sa2sq62srKurrKqkop+enJqYl5eXlpWUk5GQj4+QkpCSkpGSkpGQj4SOBo2NjIuLiomLNYqLiomJiIeHh4iHiIqIiIeHhoaGh4iJioqKi4yMjo+QkZCNjIyNjo6Pj46Pj46NjY2Ojo2NhI4NjY2Oj46OjoyOj46Pj4SOBY2Nj4+QhI88kJGRlJOSk5OSk5WVlJWWl5WWlJWVlZaVlpaVlZWUk5KRkpKSkZGQkZKSk5KRkpOTk5KRkZKSkpOUlJSVhJQvkpOTlJOTkpOTlJWUlZWUlZSVmJqam5ycm5qbm5ybmpiYmZmZmpqbm52cnp2dnZ6FnQeen6ChoaKihaEEoqGhn4meBp2dnp+goIaiC6GjoqGhoaCgn6CghJ8HoKGhoKChoYWgBp+fnp2dnYaeGJ+fnp+dnJubnZydnJucnZ6dnZ2cnJydnYacGJuam5ydnZybm5qamJeWlpSUlJOTkpKSk4SSIZOSkZGSlJSTk5KSkpOSkI+Ojo+Pjo6NjYyNjIqKi4yLioWJh4gHh4eGhoeHh4WGCIWFhYaFhISGhIUDhoWFh4YBhYWGgoeGiAeJioiJiYiIh4ohi4qLi4uMjIuMiomKi4uLioqJiouLjIyNjY6OjYyKioiGhIUBhoeHDYaGh4eGhoeIh4eIh4eFiAyHiImKiYeHiIeHiYiEiSOIiIeHh4iKi4yNjIuMi4qKiomJiIiJi42Ojo6PkJGQjYyMjYSMIo2OjoyMjI2OkJCRkJCQj5CPkZKSk5GRkpGSk5KQkZGSkpKEkRiSkpKTlJSUlZWWmJiWlZaVl5manJubmpuEnGKeoZ+dnJ6hoqCdn5+en5+ipKWkoqGio6Smp6eoqq2urq+ysbOysa+0tbi7vLq5tLO0tLW1s7S1t7i6vL3AwsXFw8XExMO/vb29vL7Avbm4trW1ubu9wMbGw8O/u7m5uLe6vv+A/4D/gP+A/4D/gP+A/4CIgAICBABcgntydH19eXR5g4yJgYKBhImNjoV9fn95d3hxbW1tb3d7enh0bWtra2poZmdoZmlramxsbGppamlpbXJ1dnRzbnBzdXVzc3N1dHJxcnN1eHp7fHt7e3l4eXl6eXmFel58fnx8fX+Ch4qJhIODh4eGhIeHiYuMi4yMi4uOkJKQjYuKjI+RkY6NjoyNjIyMjYuIiIuLjIuJhYJ+fXh4eXl5d3d2dnV0dXRzdXNwc3Z1dHNxb25samhmZF9fXl5ehF0JXFxcWlpcWlpchF0MXFtbW1xcXl9gYGFghF8KYF9fYGBhYmJlaoVvFW5ubmxqZmVlZmZjYWBfXl1cWVVUUoZPBk5NTUtLS4dNEktKSklHSEhHRkZGRUNERERFRYVEhkIxQUJCQkFBQkJDREJBQUJCQ0NEREVHSUpLS0xMS0lISElLS0lJSUpKSUlISUlJSElKSYVKIkxLSktMS0xLS0xNTk5NTExKSkpJSkpKS0tLTExNTU5NTVCFTwlQUlNTU1FQT02GThFNTExLSkpJSUhIR0dHSEdHR4VGg0WFRoJHh0UORERERUVFRkZFRUVGSUyGTgxPUFBPUFBPTUxMTU2ETgFPhlAHTk5PTk9QUYdSEVFRUlFOS0lJSUhISkpJSUlKhUsJTE5PT09QT05MhEqESQhKSktKSktLTIRLDEpMS0tKSkhHRkZHSIVKEElJSUhJSktKSUpMTEtKSUeERgRHR0dIhEkCSkuETTFLSklKSEdFQ0NCQT9AQEJDREVFRUZGRENDREVFRERDRERFRURCQUFBQkJCQUFAQD89hDwOOzs5OTk4NzY2Njc3NzaEOAU3NzY2N4U4Njc3ODg4OTo7Ozo7Ozo6Ozs7Ojo5Ojs7PD0+Pj0+Pj09PkBCQ0NCQkNEQ0RERkhISElJSk1OTYROE0xMTEtMS0pLTE1OTk9QUE9MSkiERRNHR0hJS0tKSUdGRUVFRkdISEhJhEgNSUpJSUlIR0hISUlISYRKA0hISYRIGEdHSEpLTExMTUxNTU1LSklISUtPUFJUVIVTgFRVVlZVVFRTVVVUVFVVVVZVVVNQT1FTVVdYWVhYWFpcW1laWVhZWFZUVVRWVlZUVVVWVlVVU1FVWVxcW1dXV1hYWVpUVlhbXl9kaGlqX11gZWloYV5bWVphY2FhYWBcWllZXmFkaGpxd3p6end0cWxqcHiBhYSEhHZvb3N0d3h4LHh5eX6DiIyPlJ+jpKajlIOCgX98en18d3Fyc3Z7ipGSkYuJiYFzdHh4fIGEJY+Kg4aMjIiEho+XlpCQj5KWmZqUi4yPioeKh4OCgYKHi4uKhoKFgYB/gIB8foB/gYGCgoGDg4GDhomJiIiEhYiLioeHiIuKiYeIiYmMjo+Rj4+QkI6Pjo+Oj5CRkZKRkpKRkZKWmZqenZmYl5mamZicnJudn5+gn5+fo6WmpaGgn6GlpqaioaKio6OjoqOgnZ2goKKhn5mVkpKOjo+Qjo2Mi4uKi4uLimKLiYaIiomIhoOCgYB+fXp4dHR0dXZ1dXR0dHJycnN1dHNzdHNycnFwb3BxcnN1d3d4eHd2dnV2dXV2eHh6ent/goGCg4OCgoF/fnp5eXp7eXd3dHRzcW1qZ2VjY2JiYmFgYIRfD2BgX19gYWFfXV1cW1xcW4RaA1hZWIVZBFpYWFiFVwFWhVUWVlZXWFZVVVVWV1hXWFhaXF5gX2BfXoRcA15fXYtcEF1dXF1dXF1cXl1cXF1dXl6FXwpeXl5dXVxcXV1dhV4IX19gYGFjYmGEYgpjZmZmY2NiYWFhhGAIX15eXV1dXF2FXARdXF1dhFwHW1paW1tcW4Rch1sCWlmEWglbW1xcXV5fYWKGYwtkY2NlZGNiYWFhYoRjhGQIZWZmZWVlZGSHZhNnaGdoaGdkYmFhYWBgYmJhYmJjhGQKY2RlZ2dmZ2ZlZIRiEWFiYmJjY2NiYmJjY2JhYmJihGMIYmBfXl5fYGCFYQJiYYVgCF9hY2JiYWFfhF0ZX19gYGFfX15gYmJjYmJgYF9fXVxbW1taWoRYAVmEWhJbXFpZWVlaWllZWVhYWVpaWFWFVgVVVVRUU4VSglGETyBOTk1NTU5OTU5PTk5NTk1NTU5NTk1NTExNTk9PUFBQT4dQC05PT09QUFFSUlNRhVMCVFWFVg1XVldXWVtbWlpbXF1ehV0qXFxbXFtZWVtcXV5dXl9fXlxbWlhWVVZXWFlaXFxaWVhXVlZXV1hZWlpahVkPWllZWVpZWVlaWllbWlpZhVoKWVlZWFhZXF1dXoZdFVtaWVpaW15eYGJhYWBhYWJjZGRkY4diFGNiYmNjZGNhYGFiY2RmZ2ZmZmlohGYKZWVlZGJjY2VlZYRkJ2VkZGJhZGhqamlmZWZnZ2hoZGdoamxscHR1dm5ubnB2dnFvbWxtcIRyIXNwbm1ucXN1eHl+goWHh4OCgHx9gIWMkI+OjYN/gISFh4WIKYyOkpeanqOkpaWkmo6NjYuKiIyLiISEhYiMlJqbmpWUlI2DhYaHioyRgMG/vb7Dwb+9v8LFxsLCwsTFyMrGw8TEwsLDwL6/wMHExsbFw8HAwb+/vr6/v729v72/wcTCwMHBwcLGycrIyMXHyMrKxcTHyMjGx8jJycvMztHQz8/Ozc7NzcvMzM3NzMvMzszMzdHU1dfW0tHR1tfX1dbW2Nnb29zc29vd3uDeP9zb2tve4N/e3d7d3t7d3NzZ1dTY2dza2NXSzs7IycvLyMfFxcTDw8XGxsfFwcLCwcDAv769u7m4trWwr7CwsYSwQa+ur6+wsrCvr7Cvr66trKutrq6vsbS0tra1srGvsK+wsbGws7O0t7u6u7y8u7m5t7Wwr6+xsrCsrKmnpqSin5yahJYBlYSTA5KRkYSSH5OTk5GRkY+Ojo6NjYyMjIqLi4uMi4uMjYyLiYmJioqHiQWKiouMi4WKDIuMjIuNjY6QkJKSkYWPA5CQj4WQhI8Tjo6Pjo+Pjo+PkI+Ojo+PkZCRkYSQKY+Qj4+Pjo+PjpCRkZKTk5SVlZWXl5iXl5aWlpmZmZaWlZSUlZSVlZaVhJQCk5KHkw6Sk5STk5KTkpKTk5SUlISVjJQBk4aVEpeXmZmbnJybnZ2cmpybm5uamYWaApydhJ4Gn5+enp+fhKCCoYSiBaGhoqGfhJ4cnZ2fn56foKChoKGhoaKjo6SkpaWko6KhoaGgoIWhKaCgoKGioaKioqGioqGgn56dnZydnp+foKCfnp2cnJydnp2cnZ6en56dhpwJm5ucnpyamZqchJ0Km5qZmZiXlpaVlYeUGZOSkpOUk5GRkpOUk5KSkZKTkpGPj5CQj42EjgONjIuFig+LioqJiYiHh4eIh4eHiIiIh4KGhIUPhIWGhoeHh4aGh4aHh4mIhIYRh4eHiIiIh4iIh4eHiYqLioqGiQKKjIWKAYyEjhaMjIuKiYmJiIeJioqMi4qLi4qKiYiHhYYOh4iJiIiJiIiGhoeIiImEiAuJiYiIiYiJiIeGiISHFYmJiYiIiYmJiomIiIeGiImKi4uLjISLMoqKiYmJiouLjY6Ojo2OjY2OkJCRkJCPj5CPj46RkI+QkJGRj46PkJGSk5STkpKVlZSShpOAkZGQkpKSkZKTlJOTlJKSk5aYmJiVlpeYmJiZmJmZmp2dnp+gop6dn6KmpaGhn5+go6Sjo6anpaWkpaeoqqytr7GysbGwr6+vsbO2ubu8u724tLS4uLm5ubq6ur7AwcXGxsnKyszKxcHDw8PBwMPCvbq8vb/BxcfHycfFw8C+wMAEwMHCwv+A/4D/gP+A/4D/gP+A/4CIgAICBABIkY2IjIqFfX2Fjo+NkJCMgoyVkoZ3dHh7fHl4d3h3d3x+fHp4d3h1cWxqaWhqa21sam1wb2tpaWltcnZ4dnV0c3N1dHN1d3l3hXZ1d3l+f39+fn59e3t6e3t5fH2AhH+Bg4J+gIWIi4yMiIiJkJWTj5OUlJKSkZKRj46SlZeXmJWRkJGYmJqbmJWSkI+NjY2Mi4uMjIqIhoSDgHl2dnd3dnh6eXZ0eHh2d3V0dXZ1dHNxcHBwb2toZF9dXV1cXFxdhFwRXVxcW1tcXl5dXFxbW1xdXl+EXg9dXFxcW1xcXl9gYmJjZ2uEbh1sa2tqaWhmZWRkZGJfXFxeXFhVUlJRUVBPT05PT4ROHk9OTU1OTk5NTEtKSEhJSEdHRkVEREVFRkZHRkdFRIVDAkFChUMIRENDRkdHRkWERk5HSEhMTUxOTUxMS0pKS01OTExLS0tMTEtLS0xMTEtLSkpJSkxMTE1MTE5OTk1NTE1NTk1NTUxMTEtLTExMTUxMTE1OT1BQT09OTk9PTk6ETwNOTEuETAhLTEtLSkpKSYZIhEcMSEhISUlIR0dISElIhEkBSIRHgkaFRCFDRERERUVGSElKTE1NTExMTk5OT09PTk1MTUxMTU9OTk2FTx9QUFFRUFBQUVFQUVFQUVBPT05OSklISUhISUlJSkpLhUwRS0tLTE1OTU1MS0pHSElISEiFSRxKS0xNTExMS0tLSkpJSUhISUpLTE1NTEtJSUpKhkmESjxJR0ZGRERERUdJSUhJS0xNTk5NTU5NTEtKSUdEQ0NCQkFCREZHRkVGRkZFRUZGRkdGRERDQ0REQ0JBQUGEQgRBQUE/hT0SPDo6OTk5Nzc3Njc3NzY2Nzg4hjcKODg3ODg5OTk6O4U8Djs7Ojo5OTk6Ozw9PD09hT5EPT0+Pz4/QUJDQ0JDQ0NGSElISEhLTU1LSUxMTU1MTEtMTE5PTk1OT09RUU5NS0pJSElKS0pIR0hJR0VEQ0JCQkNERUaFR4BISEpLTE1KSUlKSktMTEtLSktKSUlKSklIR0lKTE1OTU1LSkxNTk9OTk5PUVJTVFRVVFRVVlZYWVlZWllZWVpbXV5eXFpZV1VUUk9PT1FVV1dWVlhZWFVWVlVWWFhYV1ZVVVZXVlZXWFhXVVRTVVhYWVpYV1haW1pXVlhZWFtdX2JfY2NmZ2RjZGFfX2BiY2ZqYlxcXVtXV1ldY2pwdHd6e3p8em9raG1zeXuBiouLj4x9fHt9gIaIh4aNkJGXmZykqqqrqJ+XlZCDiIiLkImCfoGDfoCJnKagkId7dHN3goqUlxabmZSWlZGLi5CXmJaampiTmJ2bk4iGhIosi4uLioqOkY6NioqKiYWDgYF/gIGDgn+ChYSBgIB/goaKjIqKiYmKjIqJiouEjICLi4yLjZOUlJKSk5CQj46Oj42Rk5aYlZaXl5OWnJ2en56cnJyipqOhpaampKWjpKOjoqWpq6yrqKWkpaqqrKyqqKako6KioqGgnqCin5yamJeVj4yLi4qKjY+OiomNjIuKiomJiomHhoOCgoKBfnx5dXR0dXR0dHNycnNydHN0dAp0dXZ1dHNzcXFyhHMudXZ2dnRzcW9xcnN1dnZ2eHyAg4KBgoGAgH99fXt6eXt8eXRwcHFvbWlmZWRkY4RiLWFgYGFiY2JhYGFhYWBfXl1bW1xcW1taWllYWVlaW1paWllZWVhYWVlYWFhZWIRXBFhaWlmEWBNaWltcXF9hYGFgYF5eXFxeX19eiV0LXl5dXl1dXVtcXl2EXglfYGFhYGBfYGCEXw1gYF9eX19eX15eX2BghWELYGBiYWFgYWJiYWGEX4ZeBF9fXl6EXQdcXFxdXV1ehV2EXAFdhl4MXV1cXFtbWlpZWlpahVsRXF1eX19hYmJjY2JiYmNjZGSEYw1iY2RjY2JkZGVlZWZlhGYFZ2dmZmaFZwRmZmVihWEFYmJhY2OEZBBjY2NkZGRlZmVlZGNiYGFihGOEYgVjZGRlZIVjHmJiYmFgX19hYmNkZGNjYmJhYWFgX2BgYGJiY2NhYYReH11eYGFhX19gYWNjY2JiYmFhYF9eXVxaWlhXWFhZW1uEWhFbWlpbW1tcWllZWFhZWVlYV4VWLVhXVlVUU1NSUVJRUFBPT05OTU1MTE1OTU5OT05OTk1OTk5PTk5NTk1OT09QUoVRC1JQT05PT1BRUVJRhFKHUxpSU1VWVlZVVVZWWFpaWllaXF1dXFpcXV1eXYRbOV1eXVxdXl5fX15dXFtaWVpbW1tZWFlZV1ZWVVVVVlZXV1hZWFlZWFlZW1xcW1paWVtaW11cXFxbXIVbDVpaWFpbXF1dXl5dXF2EXoRfQ2FiYmJjYmFiY2RmZmdnaGdmZWZmaGlqaGdnZWNjYV9fYGJjZGRjZGZnZWJjY2RlZmdmZmVkZGVmZGRlZmZmZGNjZGeEaD1lZmhpaWdnaGloam1ub3Fwc3VycnRzcXBwcXJ0eHJubm9ubGxucHR5fH+ChYaFhYR/fXp/goaIjJKUkpSThIoujJGTk5KYmpueoKKnqqqsqKKenpuQlJSXm5aQjpCRjpCWpKqlmpSKg4OHjpWdnirKyMjIycfCw8XGxsbJysnFyMvNyMG/wsbIxcXFyMfHyMjGxcXHycbDwb+EwIDBwL/CxcPAv7/AwsXJy8nJycjIycfGyMnKysrJycnKzc7S0tHPz9DPzs7Pz8/Oz9DS1NHR1NTQ0NTX2dnY19bV2+Hf3t/g4ODh4ODg397f4uTk4+Dc3N3i4+bn5uLe3t7a2NbW1tjZ2tfV1NLRzsrHx8jHx8fJycTCyMjHx8bFxF3EwsC9vLu8vLu2trOwsrKysbGvsK+ur7CxsLCxsbCysbCvrqyrra+wsbGysrKxsK6sq6usrK6wsbCytbm7urm7u7m3trS0srGwsLCtqaamp6WhnZqamJeXlpWUlJWHlCKTlJSUk5KRkI+PkI+OjY2NjIuMjI2NjI2OjY2MjIqLi4uMhYsTjIyNjo+Pj42NjI2NjY+PkpOSk4SRCpCRk5OTkpGQkZCFkQ6SkpGSkZGRjo+QkI+QkYaShJEDkJGRhZCGkQaSkpOWl5eElgqXlpWVlZaXlpeVhZQKk5SUlJWVlZSTkoaTDZSUlpWWlpeWlZaWlZaElRCWlpWWlZaVlZSVlZSUk5SVhJQtlpeYmZqZmZqam5qampucnJuam5qam5ycnJudnZ2enp+foaChoaGioaGioaGihKEGoJ+fnp6ehJ8EoKChoYSiF6GioqKkpKOjo6KioKGioaKgoaChoKChhqIYo6KhoaCgn5+en6CgoqKioZ+en5+enp2dhJ4FoJ+fnp2EnA6ampudnZ2cnJ2dnZybm4ScIpuamZeWlZWUkpOTlJWVlJSVlJOTlZWVlJOSkpGSk5KQkJCFjwWRj5CQjYWLAYqFiYaIBoeIh4eIiISHDYiIiIeGhoWGhoaHiImEiAKHiISHJoaHiIiJiImIiIiJiIiHiIiJiIiKi4yKiYmKiouLjIuKi42OjYuLhIwUiomIiYmKjIuJiouLjIyKi4qJioqEiYSIZoeHh4aGhYWGhoaHh4eIiIiJiYqLiouJiIiKioqMi4qKiYqKiouLi4qJh4iKioyMjI2Mi4yNjY2MjIyNj4+QkI+Pj5CPjY2PkZKSlJOSkZKSlJSUk5KSkZCQj42NjpCSk5KRkJOWlYaShJM+kpOTlJOTlZaWlpWUlJSXl5iYl5aWl5iYmJmcm5qbnJ2dnp6goaCgoqCfoKKlpqeqpqKjpKOjpKWmp6uwsLGEs0C1s7Gvs7W3uLu+wL/Cwby8u7u8wMLCwsXExcjIysvP0NHQzsnJyMbHx8nKxsbFxsbDxcnO0s7HxMO/vsDCxszM/4D/gP+A/4D/gP+A/4D/gIiAAgIEAICGg4SKiIKBg4SOkpGPjo2Kjod4cHJ0fYKEgoOFhYaBf3x8fXt5enx7eXFwb29wbG5tcHJ3dW9tbnF1eXd1dnZ3c3N1eHh6e3p5d3d4enl8foGFhYaHh4eDfXl7foCDhIiMjIqFgYSIiomOkY6MjZGUl5eampSUk5SSkpOSlJaZmgycm5qWl5WUlZiZmJeFlkCUkI6MjYqDgH98end2d3l6e3t9e3l6e3t5dnd6fHp3dHRycXBxb25qaGZjY2JgX15eXV5cXV1eXl5dXVxeYGBehlwwXVxbWlpaWVtbXVxcXV1eXmBkZ2tubWtqamppaWhnZWNiY19bXFtbWFVST1BRUU5NhE8bUFFQUFBRUVJRT01LSkpJSkpJSUlISEdFRUZHhEgBRoRFD0ZGRkVGR0ZHSEdHRkVGR4RIMElKS0pKTE1PT09NTU9PTk1OTk1MSklJSUpLSktLTE5OTUxLS0xMS0tKSktMTU1NToVPFE5OTk9PTk9OTUxMTU5OTk9PTk9NhEwzTk1LS0pLS0tKSUlJSktLSkhISUlISEhJSElISEhHR0dISEdHSEhHSEdISEdHR0lKS0lIhUYER0ZFRYdEE0NERkhKS0xNTUxNTk5PUFBPTk6ET4JNhEw2TUxNTU1OT09OT05PUE9QUE9MS0pJSEdISUlKSUhJSUpLTE1QT05MS0tLTE1MTEtLS0lISElJhEheSUlKSktMTExLS0pJSUhJSEhISUpLTE1NTExLSkpMS0pHR0ZFRkdJSUhIR0ZFRERERkhISUlKSktLTE1OTUxNTUxLSkhGRENDRENDRUZHRkREQ0JCREZFRUVGRkVDQ4VCBEFBQUKEQxNCQD4+Pj09PDs6OTk4ODg3Nzc2hjeGOIU5Bjg3ODg5OoQ7HDw8Ozk4ODg5Ojs9PDs8PDw+PTw9PT4/Pz4+QEOERDBFSEpMS0pKSktKSktMTUxNTUxMUFJTUlFQUVFQT01MSklKSUlKR0VERERFRENDQkKFQQJCQ4REBEVGR0mHS3ZKS0xNTU5OTk1NTExNTk1LSUtMTE1OTkxLS0tMTU1PUFJUU1RUV1lZWVhYWFlaXF1eXV1cXV1eX19eXFlXVlZWU1RUVFVWVlhYWVlXV1dYVVRWWFdXVVVVVFRWWFlbXV5cWlhYWVxcXFtbYGFiXFhXVVRWWFlZhFpeW11iYFlaXF1dXmFhYl5eW2BjXlpcX2JpcnZ6fX6BdXBqa21tdHh5fYKSmY+FgoOAgYSIh4eMlZOVoKWttbavpp+dnJSPkJOQlJqYkpGRjoyQn6eimpGGgHt4eXiAhoCUkZOXlZGSk5OZmpuamZiWmpWLhYSGjZCRkZGTlJaRkI2OjoyKjI6NjoeFhIOFg4SDhYeLiYWDg4iMjo2MjY2NioqLjIyNjo2Mi4yNj46Qk5aampqbm5uXkY6OkZSXl5mcnJuXlZmcnpugpKGioqSnqKisq6alpKWko6Okp6qsrQ2urKupqaeoqauqqqmohKctpaCfnp+emJWTkI6NjI2NjY+PkpCOj46NjIqLjY6NioaGhYSChIOBfnx5d3h4hHcGdXV1c3JyhHMIdHd4eHZ0c3SFczJycnJxcHBvcXJyc3R0dHZ6foGDgn9+fn9+fn17end3dnJvbm1tamZkY2JjY2FgYmJiYYViEGNiY2NiYV5dXVxcXV1cXFuEWQdaW1tcXFtbh1oBWYRaAVuFWgFbhVwyXV1cXV9hYmFiYWFiYF9fYGBfXl1dXVxcXV1eXl9gYGBeXl5fX15eXV1eXl9fX2BhYGGFYIRhCWBgYF9eX19fYYRgCl9fYGBhYV9fXl6GXxheXl5dXV1cXVxcXF1dXl1dXVxcXF1dXVyFXQteXl1eXV5eX15dXYZcBltaWltbWoVbD1xeX2FiY2NhYmNkZWVmZYRkgmOFYoZjEWRkZmZnZmZmZWZnZmVkY2JihGEJYmFhYWJiY2VmhGWDZIRlBmRjZGJiYYViCWNjYmJjZGVkZIdihGEnYmJjZGVlZGNiY2RjYV9fX15fX2BhYWFgX15eXl1fYGFgX2BhYWJihGMkZGNiYWFfXlxbW1taWltcXFxaWlpZWVpbWlpaW1paWVpZWFhXhFYHV1dYWVlYVYRTBVJRUFBQhE8ETk5OTYdOA09PToZPCU5OTU5PT1BRUYRSKlFOTk9PUFFRUlJRUVBRU1NSUlNTU1RSUlRVVldXWFhaW1xbWlpbXFxaW4ZcBl1gYWFgYIRfFV5dXFtaW1taWlhX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Y2JhYmNlZWRkY2RkZGNhX11cXF2EXjFfX15eXV1eXl5fYGBgYWFhYmJjY2NlZ2ZkYmJhYF5dW1taWlxcW1pZWVpaW1taWltahFmFWANWVleFWAlXVVRUU1JRUFCHTwZOTk9OT0+ITgZPUFBPT0+ETgpPT1BRU1NTUlJRhU8GUFBQT1BQhFJVUVFSUlNTU1VVVVdXV1laXF5dXFtcXV5dXVxdXV1eX19gYGBiYmJhX19eXVxaWllYWFdXV1ZWVlVVVFVWVlVUU1NTVFZVVVZWV1hYWFpbW1taWVlcXIRdA1xbXIRdBV5eXV1ehl81XVxdXV1fYGJiYmNjZGdoaGhnZ2doaWlpaGdmZ2dnaGhqamhnZmdnZmNjZGdpaWdnZmZmaGeEZIBjY2RlZWVmZ2ZlZ2psbm1tbG1sbm9ua2pqamtramhnZWVmaGlpamppaGZnaGtsbW9tbW1ub3FxcW9uc3d1c3Z3fICCg4eKjYuEhISBfoGGjY2Ql5iYlpCPj5CRlJeXl5igpKSnrK+zrKWnqKKfoJyYmZ2kqKiop6CenZ2en6qxqAeZjoaHiY6VgM7R0c/Py8rKzczKycrMzcrFxMPGys/NzMrJytHTz8rGxMLGyMrKyszMysrMysjExcnIys3O0c3O0tHP0c/KzNDQ0NHS0tPTzc/Pz9DQ0dTU1dXV19bT09TT0M/S1tnW1djX19bW1tjY2dre4uLi4eXp5+fk4eHg4N/e4ePm6+rnY+fo6Ofl3+Di5unr6eno6enj3tjV09LRz87OzcjHyMjJyczM09fU1NLQzM3PzsjEwb+/vr28vLu7uru6ubq5uLa2tbS1tLSztLO0tLK1t7i1s7Kxsa+urayrq6yvrqyrrKurrIStOrCytLa3uLe2trOytLe3s6+qpqSkoaCenp2bmZeYlpWXmZmZmpiXl5eYmJmYlpOSkZKSkZGQkZCPjoyEjRqOjo+Pj42Ojo+PkJKRkpGSkZKTkpGRkZCRkoSREpKTlJOTk5WVlJOTk5SWlJOSkYSPD5CQkZGSkpOTlJOTkpOUlYaSEZOTk5SUlJWVlJOUk5OUk5OShJMKlJWVlpWVlZOTlISVA5SUlYSUBZWVlpWUhZWElAaVk5WWlZWIlhiVlZSVl5eWlpWWl5iXlpWVlpaWl5eYmJeElhqVlZaWl5mZmJmam5ybnJ2enp+enZ6dnp+fn4SeB5+enp6fn5+EoAuhoKGhoaKhoJ+foIihFaKjo6SlpqWlpKSioqGjo6Oko6OiooSjDaSkoqKhoqKjo6Sko6KEowyioqGhoaCgoaKjo6KEoQiioqCenJucnYeeDp2cnJydn5+fnp2cnZ2fhJ4cn5+fnp2dnJqYmJeXlpaYmJeUk5KTk5WWlZSUlISTh5IVkZKSk5GRj46Ojo2NjYyMi4uLioqKh4kDioqJhogIiYmJiIiHhoaEhxSIiYuKiomIh4iHiIiIh4aFhYWHiYSIFoeIiYiIiYmJioqKi4yOkI6NjI6Pjo2EjBiNjo+OjY2Ojo+PjoyNjIuKiYmIiomJh4iGhheHiYmIhoWGh4eJh4eIiIiJiYmKi4uLioWLBo2NjYyMjISLIYyNjIqMjY6Pjo+Oi4qLjIyNjY+Pj5CRkZOVlJSTkpKSk4iUGJWUlpaVlJKTk5ORkZOUlZWUlJOTlJaWk4SSJJOTk5SUk5SVlJeYmpybm5qbm5ydnJqam5ycnJqZmZiXmJucnISdBZydnZ+ghKJVoaOlp6ampaWprKupra6vsbO0t7m8vbe3uLi2ubzCwcLFxsjGxMPBw8PEx8bHx83Pzc/R09XTztDRzs7Pz8/Kys/R0NLU0M/NzM3O09jWz8rGx8jKzP+A/4D/gP+A/4D/gP+A/4CIgAICBACAh4eCgYmLhX16dnRzfoiOiYWCgISEiI+TmpOMjo2Df3x3dnh7foGAf357eHyAeHh3eHd6f4B8e319fn1+fXl8e3t9g4WGh4iEgH9+f4B/goeLjZSNhX9+f4eFgIGKkpOSj4qKiouMiY2QkZSWmJueoaSkpaSemJual5mboKarqaqAqailpKCcnp+ipaWoqqinoZuZlZGOjIuKiIeIh4V9eXp7gIqXm5iZmJaWmqKYjIN7d3RxcXBvcG9ua2loaGhmY2FfXVxcXVtdXl9fYGNlZGJgX19eXV1dXFtaW1xdXFxcWllZWVhZW11fYmdqaWlnZmVkZmZmYmFcWllYV1ZVU1EiUFFQUFBRU1VTUlFQUlJSU1RST05MTExNTUxLSklISEZISYdKEUlJSktLSkpJSktMTExKS0tKhEwFS0pLS0uETQlOT1BRUlFQT0yESw1JSEhISUlKS0tMTE1OhU+DUIROEU9QT1BQUE9PUFFQT1BPT05NhEwKTU5OUE9QT09OTYRMAk1MhU0eS0tLSklISEhHR0dIR0ZFRURERENDQ0JCQkNCQkJBhEKEQwtEREVERENCQ0NERYRGCUVFRkdHRkdGR4RJF0pLTExNTU5OTk1OT09PUVJRUVBRUE9PhE4yTU9QUE9NS0tLSUlISkpJSkpKSUhISUhJSUlISUpLTEtMS0xMS0xMTE1LSklJSUhJSEiERwdISkpMTEtLhUlsSktMTk5LSklKTExNTUxMTUxLSUdFQ0JERkVGRkRFRUVEQ0RFR0hHSUlKSUhISElJS01OT1BQUU9NTEpIR0ZFRUREQ0FBQ0NDRERDREZFREREQkFBQkFAQEFCQUFAPz4+Pj09PTw8Ojk4Nzc5hziGNxI4Nzg4OTo6Ojk4Nzg5ODg5OTuFPAI7OIQ5bDg4ODk6PD07Ozo7PD0/QEFCQkJDREVHSUtMTExLS0tNTk5OT1BPUFBRUFBRUlNTUlFQUFBPTU1LSEdGRUVGRkdFRUVERERCQkBAQUJDRERERUVFRkZHSElJSEhISUlMTU1LSEdJSkxMSkpJTIRNFU9QTkxKS0tLTE9RU1VXV1haW1tbWoVZEFpaWllYV1dXWFhYV1lbXV2FWxxcW1taWlxaWllYV1lXWFlZWVhZWVdZW1lbXF1fhGB1X2BfXlxaW1teX2BaVlRVV1pbXFxbV1JRUVJWW15fXVtYVldYW19cWFlibG5paWxxc3R0dn19c291e3ducHaBg4+XmJaPiIF+foSJj5WTn6elpau3vaiUnJ2YlZCJkZ+cnaaxsaqhpaifmZKOlaaehX1/gYKEgJiXlJSZnJeQjYuKiI6VnJeTkZCTkpSbnKGblJWWkZGPi4qMjpCTkpGQjYqOkYyNjYyLjZGSkI+Tk5STk5KPkY+NkZeamZudmZaUk5SVk5eboKKmn5mTk5aal5SWnKKho6CbnJydnpufoaKjpqeqra+xtLWzraiqqqeprLG2vLu6Frm6t7WxsLCxs7a1t7i4uLOuq6eioJ2EnCCbmpiSj4+QlZ+pr6yqqKeorrOpnZePi4mGhoaEhYSCf4R8FXp4d3d2dXR0c3V1dnZ4e3x6eHd2dYR0CnNzcnNzc3JxcnCEbxpwcXN1eHx+fX17enl5e3t6dnVxb21ramlpZ4RlKWNkZmdoZ2ZmZWZmZmdnZWJhYF9fYGBfX15dXVxbXF1eXl5fX15eXF1dhF4KXV5e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uby8ubWxq6ekpKOioaKgnqGmnZSTlJafpq+xsrCurre8wLy0q56XkI6LiIiGg356eXh3dnV0c4R0EnV2dXV3d3h5eHd3d3Z3d3Z1dYRzLHFxb25ubG1tbnBwcXJzeHt7enl3d3Z2dXRzdHBua2traWhmZWdnZWZmZ2dmhGchaGhnZWNhYGBfX19gYWBfXl1cXFxdXl9eX19dXl1eX2BghF+DYYRgCmFhYGBhYWBgX1+FYBFhYF9gYmJiY19cW1tbXFxdXYVeEl9fYGFhYmNiYmRlY2JiYWJiY4diAWOFYgRhYWFghGEMYmJjZGRiYWBfYGBgh2EIYGBfXl5dXV2EXINbhFqGWQdYWVlYWFhZhVqFWwdcW1paWllahFsqXFtcXV5eX15fX2BfX19gYWFiY2JiYmNjY2RkZGVlZmZlZWZmZWVkY2NkhWUEZGNhYYRgCWFhYGBhYGJjYoRhAmJjh2QmZWVlZGNiYWJhYGBfX2BgYGFiYmNlZWVkZGNjZGRkZWVlZGNjYmOEYoRjDWRiYmBeXFxdXl9gX1+FXgFfhWITYWBfXl5fYWJkZ2dnZmZnZmVjYIRfDF5dW1tbWlpZWlpaWYRaEllYV1dXWFdYWFhXVlRUVFNTUoRRglCGTwZOTk5PT0+FToRPClBQUVFQUFBRUVGEUIVRAlBRh1ArUVFSUlJRUVFSUlNUVFVXV1hXWFtcXl9gX19eX2BeXl9gX19dXl9fYGFjYoZhB19dW1xbWViEWQJYV4RWCldWVVVWVlVUVVSEVQVWVlhYWYZaVllbXFtbW1xeXl1cXV1eXlxbXFtbW1paW11eYGJjZGdnaWlnZ2ZmZ2hmZWVlZmVmZ2ZmZmdnZ2ZnaWtra2xubmxtbGtqa2xsbW5sa2tqbG5sbGtpaGZnhWgNaWpsbGtqa21ubmxra4VqaWhmZ2hqbGxraWdoaGdoamptb3BubGtsam1wcG1ucXZ4dnh3dnh7e32Bgnx9foCEhoaMj46UnJ2alZOVj4+Unp+en6Ckq7GyrqejoqWgnaKgnJ6goqOlp6+ws7KxraulnpyioZyZl5eanIDRz83Q09HOztHVz8vN0NHQ0tfX0NHR0dPQzcrOz83NzcvHx8zQz9DV2NPOzdDR0NDQ0dPX2dnV1NTV1dbU09XX1tfW1dTW19bV19TU09LX2eHl3tvZ2t3a2tfX2NfW2Nrc29ja2d3g4t/h5+jn5+/19vv7+fXs6Ojm5+jq8vXy8z/29fXx7u3r6+rq7e3y9O7n5OHc2drY2Nna2djb4drQ0NLW3d/n6+zs6urx+Pz27+fb0s3LysXFxMC7uLi3trWEtC2xsbK0trS1t7e4uLe2trW0s7S0tLOxsLCvrKuqp6inp6ioqaqqq62xs7Oysa+ErhKtraulo6Cfn52cmpibm5qam5yGmxacnJyYlZSTk5KSk5OUkpKSkZCRkJKShJMOkZKRkZKSk5OUlJSVlpaFlRGUlJaXl5aVlZSTlJOUlJWWlYSUHZeUkY+PkJGQkZCQj5CRkpOTk5SVlZaVlZeYlpaVhJQFk5SVlZaElQSUk5SUhJOClIaWBJeXmJaElSuWlZaWlpeYmJeXlpaWlZWUk5SUlJWVlZSVlZSUlZSTk5KTlZWUk5SVlZaWh5cTlpaXlpWWl5eXmJmZmZqZmZqamoSZBZqZm5qdhJ6Fnxaen5+fnp6en6CfoJ+goKGioqGioqGghJ8RoKGio6KgoqKjpKSjo6Kio6OEpA+jo6OlpqWlpKSjo6KhoaGFojejo6SmpqakpKWkpKWlpqenpqSjoqOjo6Kio6OioaGgoJ+dmpqdnZ6fn56dn5+fnp6goaCfoJ+ehJ0lnp6eoaChnp6fnp6enJuampmYmJeXlpeXlpWWlpWXlpaUlZSTkoaTCJKSkI+Pj5CPhI4JjYyLiouLi4mJhIoLiYmIiImJiYiIiYmEihWJiYqKiomJiIeIioqJiYqKiYqJiYmFiCCJiomJiIiJioqJiYuLi4mLjY2Pj5CPj4+QkZCQkZGRj4WOA4+RkYaQFY6Mi4yLiYmKiomIiIiGhoaHiIiIh4iIhIcciYqKi4yMiomJiYqLioqMjI2NjYyNjY6OjYyMi4SKGIyNjpCRkpOVlJaVk5OTlJSUk5OTlJSTk4SUDJWSkpKUlpeYmJiZmYWXDpmamJmbmpiXl5qcnJyahJgLmZmZmpqbmpucnJyEnYWcbJ2cm52cnJydnp6dnZ6gn56goaKlpqWjoaGjoqWoqKaoqKuurKytrrC0s7S3uba2uLq7vb/FxsXFy83MyMfKxcbJzc3Nz8/S09bY2dbV09bT0dTSz9HT0tPV1tjZ29ra2tjT0NDS0tLT09DS1P+A/4D/gP+A/4D/gP+A/4CIgAICBAA9iYB/gn59gIWQmZ2VkY+ZoJWXnJiUjoyMioeAgoSCen53dHyGiH18foOBiY+OjIuHhIiMj4mEgoCAgIKEhoSFSYJ/gYKFiImOkpKNgn+AhYqWnqKkpKChlY6MkI+Fh4eKkpKVmp6cnJeenaCorLS6ur28uLGwraqnqKuyubi2tLKztLSysK2trq2Er0KooZiXlpebmpeWl5SUk5eTjoqFhouNlpmXl5iZoqqrq6eflo6KgHp1b2tmYmFgX2BgX11cXFxdXl9fX2BhYWFgYGGEYAthX15eXVxcWVZWVoVVHFZWV1lbXmBiY2NiYF9fYmJgX11bWllYWFdUVFSEVRZWVldXVVRUVVRTUU9MSkpLS0xNTUxMhEoNS0xMTUxLS0xMTE1NTYROEU1OT09OTk1NTk1OT1BQUE9Nh0waS0tKSktNTU5NS0lJSUpLTE1PT09OT09QUFGEUhhRUlRTU1NRUVFQUVFQUVFRUFBRUFBQUVCGTwZQUE9QUFCETw5NTUtLS0xLS0xMTU1MSoRJF0pJSUdHRkZFR0dGRkVFREJAQEBBQkJBhEIFQUJBQkKEQ4RBB0JCQ0NERUOERBZGR0dHSElJSUhJS0xMS0tMTk5NTk9PhVBUTk9PTk5OTUxNTU1OTk5PT05NS0lHR0hISUhJSUlKSklHR0lKS0tKS0xLSkhJSUpJR0ZGR0ZFRURFRUZFRUdISktMS0pJSkpKS0tMTExKSUlKSUhJiEoHSUdFQkBBQoRDN0JERUVGSElISElJSUhHR0dJS0xPUVVWVlNSUVFQTkpJSUpJR0RCQ0NCQUBCQkNERUNCQkFAQECEPwhAPj09Pj4+PIU7ATyEOxA5OTg4ODk4NzY2Njc3Nzg4hDkFOjs7OjqGOxA6Ozs8PT0+PT08PT09Pj08hj1ePj49PkBBQkVKSUdISklMTE1PT05OT05PUlNTUlFOTExOTk5PUFJTUVBPTEpISklJSEdGRUZDQkNERENDREVGRENCQkJBQUJCQkNDRUZGRENDQkNFRUZHSEtNTk5PT4ROD01MSkpKSUdHSUtNT1BTVYRYIllZWFdXWFhYV1dWVlVWV1hZWVtbXF1eXF1dXmBjYmFhYmOEYgVhYmJjY4RiGmFfW1hZWVpbXVxaW11dW15fX2BhX11cWldWhFcLVlZXV1dYV1hXV1aEU1lWVVRUVVZVVllaWltbXmBgYWNjYmVlZmdmZmdsc3Z3d3p8fXt+hYmGgY6Ke36JlZGRlZSanpqbm5uYk46PkZSWlJiZnaKmqqmmpaqzt7mnnJWNioaKlZaNjICZk5OUkY+TmJ2jp6KfnqSpoqKmpKOdm5qZl5GQk5KOkYyJkJeckJCQlZOZnZ6dnJiVmJ6im5eWlJWWlpial5eYmZeVl5eanp6ho6Sgl5WVm5+mq66urKmpoqCeoaCZnJyepKOmqqyqqqasq6+1ucHHx8rJxcDAvLe2t7q/xsfEwk7AwMC/vr28vLy6vLy9vbiyqqmnqKqppaWnpaWmqqWgnpqZnaCqrKqqra62vL7Avrqto52VkIuGgn15eHd2dXZ1dHN0dXZ2dnd4d3h5d3aEd0B4eHl4d3Z0cnNxb25tbGxsa2xsbG1vcHJ1eHl4dnVzc3V1c3JxbmtqamlpZmdnaGhpaGlqamppaGhpZ2VjYl9fh2ATX19eXV1eXl9hYF9fXl5eYGFhYoRhCmJjY2FhYGBhYGGEYgNhX16GX4ReDV1fX19eXVtcXF1eXl+EYARhYWFihWMGYmNlZGRkhmMCYmOGYoRjBmFhYGFhYYRiAmNihmEBX4Rgh18HXl5eX19eXYRcAVqFWw9aWllYWFdYWVlYWllZWlqGWwNaWliEWRBaWltbXFpbW1tcXV9eX19fhGCCYYRiFmNkZGRjY2RjZGVmZWVlZGRjY2NkZGSEZQdkZGNiYWBgh2EPY2JiYWFiY2NkY2RlZGNihGMEYWFgYIdfFmBgYGJjZWVkY2FjY2NkZGVlZWRjYmOHYgtjZGRjYmFfXVpaW4VdBF5fXl+HYR9fXl9fX2BiZGVoaWlnZmVlZWNhYGBgX15cWlpaWVlZhFoXW1pZWVhYV1dWVlVWVlRTU1RUVFNSUVCEURFQUVFQUFBPT1BPTk5NTU5OToZPBVBRUVBQhlGDUoRTBlJRUVFSUYZShFM/UlJTVFVVWFtbWVtcW15eXmBgXl9gX19hYWJhYF5dXV9fX2BgYWJgX19cW1pbWlpZWFdXWFZVVldXVlZWWFhXhFWCVIRVFFZXV1hXV1dWVldXWFlaXF5eXl9fhF4QXVxbXFxaWlpbXF1eYGJkZoVnB2ZlZmZnZmWEZEJlZ2hoaGppaWpramtra21vbm1tbm5tbm5tbW5tbW5ub29vbWtpaGppamtramhoamtqbG1tbW5ta2pqaWhoaWloZmeFaRFqamtqZ2ZnaGtqaWlra2prbYRvTHJ1dXN2d3d4eHl5enp7foSIh4iMjI2LjpSXk5Cal4+QmJ6cnZ+eo6akpaKko6CdnZ6ioqCjpKeqrrCurK2xt7q7rqagnJmWmqOjnJxY08/Q0tHPz9LU2drW1NTZ2tbX3NjV0M/Ozs7JzM/NzM/LyMvR0c7P0NTS2Nva2dnX1Nfb3dnW1tbX2NjZ29rZ2tnY1tbX19rZ3N/f3NXV1dfb4OLh4N7d3oTadNvV2dna3uDg4ePk5eLl5ebp7fH4+P38+fX08e7u7fD2+/z69/T09PLx8O3t7u7x8/P17ujk4uDf393a2t3b3d7k4dzZ1tnd3eTm5Obs7vL7/f379+rk4tfQy8fDvre2t7a2tbWysbGxs7W2t7e3uLe4trW1hLYwt7W0srGvr6uoqKinqKinpqamp6mrra+wsbCvraysrayqqaekoaGhoJ6bm5ucnZ2dhJ4SnZydn52al5aUlJWVlJSVlZSThJIlk5OUlZOTlJOTk5WWlpeWl5eXmZiYlpWVlZaVlpiZmZeXlZWVlIWTG5STkpKTkpORkZCQkJGSkpOUlJOTlJSUlZaXloWXC5aXl5aVlZWWlZWWhZWElAGVhJQHlZSWl5eYl4SYBpeWlpWVlYWWC5eYl5SUlZWWl5eYhJYFlZaWlpWElAaTlJSVlpWElAuVlZaXmJeXl5iXloaXE5aXl5eYmZqampucm5ucm5uam5yEnRmeoJ+fn6Cen5+foKGhoKCgoaGgoKGioqOkhaMGoqGgoaKihaMDpKWlhqQso6SlpaSkpaWlpKOjoqOioaKhoaGgoaGio6OlpqWlpKWlp6elpaWnpqalpKOEok2hoqGhoqGhoJ+dm52cnZydnZ2fn5+goaGhoKCfn56enp2enp+hoaSkpKKioaGhnpycm5ybmpmYl5aWlpWWlZWWl5aVlJSUk5OUk5OTkoSRFpCQj46OjY6Oj46NjY2Mi4qKiouLi4qEiYaKD4mKi4uKiYqKi4uLiomJioaLhIoGi4qIiYmJhIqDiYSKOo2Ni4yNjY6OjpGRkJCRj4+RkpKRkI+NjI6Pjo+PkJCPj4+NjIqMioqJiYmIiIeGh4iIh4eHiYqJiIeFiAGHhIgNiYmIiImIiIeHiYuLjYSOhI8CkI+EjRCMioqKjI2Oj5GTlJWUlJSVhpQBk4SUPZWVlpWWl5aWl5mXmJiYmZuamJiZmpqbm5uZmpqam5qbm5ycm5mYmpmZmpucnJqam5mbnJ2en52dnJycm5yFnYSeYp+gn6GgoaGjo6OkoqGipKOkpaamp6epqq2ur7CwsbK0tbW3tre8vr29wMHDwcTIy8fFz8zFxszRz8/T0NTW1NXV1tXU0tPU1dbT19XU1djZ2NjY2t3f4NzW09HPzNDY2NTS/4D/gP+A/4D/gP+A/4D/gIiAAgIEADaIhYJ9e3t/g4aRpKGcmZuWkZOTmaKhkoeHhoSDfnuAg4CAiZSQf32FkJufoZmUlpOOjY2GhoSEhYCDgH6Bg4aFgoGHlZuTj5aWlI6SkY6QkJmlraupnp2al5KKiIyPkZGSl5uipaekpKKmqrG3t7u8vbq8ury7ubi7vr+/vb6+vby7urazsrKzsrKvr6mkoJ2dnqGjoaGdm5malI+Ni42JiIuRkZCPkpOTnaWpqKehloh+dHFta2ZiYhhiX19fYGBgX15dXl5fXl5fX2FgYGFhYGGEYgthXl5dXFtZWVhXVYVURlVVV1tgYmNiYWBfYGFgYF9cWltaWlhWVFRWV1hYWVpZWVhWVlVTUlFPTUtLS0xNTU5MS0tKS0pLTExNTk5OTU1PUE9RUlGJUBZPTk1OTk5PTk1NS0xMTUxMTEtLSkpLhEwRS0tKSktOT1BPT1FSUlJRUlOFVIdVCVRUVFNRUFFSU4VShlFKUFBPT09QT09PTkxOTUxLS0pKSUpJSUlIR0hISEdISEhHR0dGRURFRkZGRURDQ0FAQEFAQEFCQkJDQkFBQUJCQUFBQkJDQkNCQkOEQgpBQkJCQ0VERUVGhEcnSUpKS0tLTExMTU5PT1FQUFBPTk9OTEtLS0pKSktMTU9OTk1LSktJhEgeR0hISUhIR0hJSUlKSUhJSklJSUhJSEdGRkZFRUVEhEWERg1HR0dGRkZISUlJSkpLjEkISElIRkZGRUOFQgtDREVGR0hIR0hJSYVICklKTU9RUlNUVFGETwpNSklKSUlIRkNChEE+QkNEQkFBQEFBP0A+PT08PT08Ozw+Pj07Ozs8PT09PDw8Ozs6OTo5Ojk3NjY2Nzc4OTk5Ojo5Ojs9PDw8OjqFO4k8CT4+Pz89PT5BQohBbUJGSkhISUtMTU5PUE9PT1BQU1RVU1FOTUtNTU5OT09QUE9N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TUxOT09OTUxMTUxLhEwDS0tKhEmESx1MTE5PUVJTUlJRVFZWV1hXWFhaWlpbXFxbW1paWYVYA1ZVVoRVEFRSUlFRU1JQUVBQT01OTk2ETARKSkhGhEWIRgRFRERDhUIBQYdAAT+EQBBBQUE/Pz9AQEFAQD8+Pz9BhUMlQkNDQ0REQ0RDQkJDQkJBQUBBQkRERUVGR0dHRkdJS0xNTk1NTYROIU9PTU5OTUxLSktMTU1OT05MS0lISUlJSElISktKSklJSIVHBkhIR0ZGR4ZGAkVGhEdjSUlIR0ZHSElISElLTEtKSkpJSUhISUlJSEhHRkpMTEpJSUdEQ0FAQUNDRENERUZGRUVHSUhIR0ZFRUlMTk9QUVNSUU5MTE1NTk5MSkhGRUVEREVEREVFR0ZFQkBBQUA/Pz4+hj8JQUBBQUJCQEA+hD0LPDw7Ojs7Ojk5ODeEOB85OTg4OTo5OTs8Ozs9PT07Ozs8PDs8PD09PT4/P0BAhEFJQkFBQkFBQkJDQ0NERUZGR0lJSUhMTUtLSktMT09OT1BPUE1NTFBSU1FPTUtKSEZGRkVHSkpJSEhHSEtKSEhGRkVERERDQ0RFRoVHgEVEQkNCQUA+PkFAQUJCQ0RFRkdJSktNT1JTVFVUU1JTVFZXWFhZWVpbWlpZWFZWV1dWVVVVVldXWFlZWV1eX19fYGFhXl1eXV1eX15dXFtbWltdX19hYWBfX2BhZmloZ2FdXV1cWVlbW1tcXFxdXl5eXV1eX19fYWNoa2plYmBdEVlWVFNQUE9OTU9SVFVTVVtehF9MYWFhZWptbW1saGpoa3Bua2hobG9ydHNzdnl7e36Bg4mXo6CTiYyPm6aqoJWXm52co6uurKertLKemZmgssbDysjAvMLNyr6/ubGjmYCkpqSdmZmanKOtt7m1q6Ccn6eur660vbennJeVk5SVlZOUmJiXmJygn52nrqyztLOxrq2koqelpKuxtKylpJ+anJ2cpKmsqaainZ6foqSrtLKqrrW1sKmmpaGipauusbazrbK+v7/ExcPBxr+5uLu5ucDBx2fRaWhobG5vbm5tbWFrz9LSzsrGxsbHx8fKx8LGaHB2d3Rvame7sKijoqOmpaKlraqknZiZnKSsq6qlopyYlpGOioWBfXt5eHZ0c3NycnFxcnJycXFyc3R1dnZ4d3h4eXl6enp5eXZ0cnFwcG5thGwaa2pqa21xdXd3dXV0dHNzcnJxb25tbm5sbGyFbjhsbW9xbmtnZWNiY2JiY2NjYmJiY2JiYWFiYWFiYWNkZmZoZ2ZkZWdqbG5samhmY2FgYWFiYmJhYYZggl+GXoJdhF4hX19gYWJjZGRjY2VnaGhpamlpaWpqa2xsamtqamppaWdnhGgaaWhnZ2ZlZWRkZWVkZGJiYmFiYmFfXmBfXl6EXYJchFsDXFtbhVoFWVhYWVmFWAFZhViEWQJXWIRXh1gCWVqEWwNaW1qEWwRcW1pbhVoDWVpbhFwJXV1eXmBhYmNjh2QTZWRlZmVlZWRjY2NiZGVmZmVlZIRjDGJjY2NiY2RjY2JjYoRhAWCLYgdhYWJjYmJhhGIDYWJjhGRUZmZlZWVkY2JhYWFiYWFhYGBiZGRjYmJhX15bWlpcXV5eXl9gYWBhYWFfYF9fXl5gYmNkZmdpaGhlZGNjZGRjYmBgXl1dXFxdXVxdXV5dXFlYWFlYilYTV1hYWFdXVlZVVVRUU1NTUlFSUYRQF09QUFBPUFBPUFBRUFFRUlJSU1RUUlFRhVIIU1NUVFVVVlaEVwtYWFhXVldXV1hYWIRZC1pcXV1dX19dXV5ehV8WYGBgX15eX2FiYWBeXVxbWllZWFpcW4VaDlxbWlpaWVlYWVlXVldYhlkmWFhWVlZVVVNTVVRUVVZWV1hYWVtcXV5fYmJkZWRiYWJjZGVlZmaFaB5mZmVlZWZmZWVlZmZmZ2hoZ2prbW1tbm9vbWtsamqFaxpqampsbG5tb25tbWxubnFzc3NvbGpra2pqa4VsLW1ub29ubW9wcHBydHd4eXRzcm9samlpZ2dmZWVmaGlram1xc3R0c3N1dnh6fIR+Q31+fn+CgX9/gIKDhYeHh4iKjY6RlZabpKuon5mbmqixs6ylpqepqa+0t7WytLm4raqqr7jGxsvJxsDDzcvAwb65rqeA297f3trY2tre4uTk4+Dd3N3g4eDf4+fk39vZ1tPU1NPT09fY2Nfa293b4OLi5Obm5ePj3tzg4N3g5+rm4+Le29ze3eHh5OPh3d7h4eLg4+ro4uPm5uTg3t7c3eHl5ebp7Ors7vDy+PX18vTx8O/x7u/z8/iA/4CAgYSFhYSEhINQgf79/PXz9PX29/j5/fv2+YCFiYmIhoKA7+bh3t7g5ubh5ezp49rX2d3l7e7p5OPf2tjV087Hw767uLa1s7KysbGvrrCwsK+wsLCytba3uLiEuUe4uLi3trKwr66trauqqaioqKempaanqayvsK6trKyrq6qoqKempKWko6SkpaSio6Wjo6WkoaCdnZybmpmZmpqamZiampiXl4SVB5eWmpqbnJyEmw2cnqCioaGem5iXlpaWhJgIl5eYl5eWlZWElIOThJSEkxuVlpiZmZiXl5iZmpqbm5ydnZ2en6CgnZ2bm5yFm0eZmZqampubmpmZmJiampqbmpmYmJiZmZeXmJiXl5aVlZSUlJaWlZWVlpaWlZSTlJSUk5OTkpOTlJSVlZSUlJWUlJWWlJWUlIWVDZaVlpaXmZiZmJmYmZiGmYKXhJkEmJeZmoWbCpydnZ2en6CgoaGGogyhoaKhoaKhoaGioqSEpQOmpaSFowWio6OlpoalhaQBpoWlBqampaWlpISlFaSmpqWkpaalpqanp6ipqKempaWlpIajDKKipKakoqOioqGhn4SeDJ+hoJ+hoaGioqKhoYSeF6Cio6KioqOjo6Ggn5+goKCfnp2bm5uahJsKmpmbmpmXlZWVloSUDpOUk5OSkZKUlZSTkpGRhJCEjwmOjo6NjIyMi4uFjIKKhIsKjI2MjIuNjYyMi4WMCo2MjI2NjI6PkI+GjoWNho4BjYSPCpGRkJCPjpCRkI+EkByOjo+RkpSSj46NjY2MjIuLi42NjIqKiouNjYuLh4oKiYmJiouMjIuLioSJB4iHhoaJiYmGigaMjo6QkJCEkgWRkZCSk4WUBpWVlpeWlYaUJJOTk5SVlZaXlpaYmJmZmJqcnJuam5mZmpqam5ubmpqbnJycnYScFp2en6GhoJ6dnJ2enJ2enp+foJ+eoKCEoQ+joqKjpKepqainpaOjpKSEowqioaOkpKakpqiqha1Lrq+xtLa2t7i2t7a4vLu8vby+wMPDwsPExcjIysvLztTY19XR1NTY29za2dnZ2tnc3uDg3uLm5d/f4OLm7ezu7Onq6uzq5efl5eHe9IACgYCLgY+AiIH/gP+A/4D/gP+A/4DugAICBACAv8C6sqqtrZ6XnZ6XlZ6elouTmZyhrLiwopKWlpWSioyOlJadn5GapKWhnaGmsa6prKqzqqupq6emq7GvoJOPj5WamZeUkIqHh4mMjpmbmZ6ooaGckoqJiIyOk5mcmZqgoJ+su8HAvLu6vL+0rKenrbCzs7i8vcLFYmVnaWlpamphaWfJysbAwL67vb++vLxiZ2tsbWhlYsC1pZuVkJGSlZaan56Zj4SBgICBgYCAf3x4dnNycnFva2hmYmJhX11cWltbW1pZWVlYWVpcXV5fX19eX2FjYmJhYGBeXV1bWllXV4ZWJlVVWFxeX2FiYWFgYWBfXl1cW1tbWltbXF1cXVxaW11eXFhUUlFPiFAEUVFQT4RQhFEdU1RVVVRSUVFQUlVZWlpZV1NQTk5OT1BQUE9PTk2ETC9NTEpLSkpLSkpKS0tLTExMTU1PUFFSU1NUVldYWVlZW1xcXV1eXl1cW1paWllZWIRZFVhXWFZUU1NTVVZTUVBOTU1MTExLS4RJA0pJR4RGgkWGRARFRENDhUIGQD8/QD8/hEADQkJBhEAVPz9AQkNDQ0JAPz8/QEFBQUJCQUFBhEIGQUJDQ0REhEMBRIRFGEZGRkVFRkZHSktMTU5NTk5NTlBPT09OTYVLAUyHSwdJSkpMTEpHhUkkSEhIR0dIR0dHRkZGRUZHR0hIR0hJSkpKSUhJSEhJSUpLS0pJhEoFSUlJSEiERg1HRkdIS01PTUpIR0hGhkQJQ0NFRUZFR0dHhEgpR0VFTFFQUVFSUlBNS0pKSU1NTUpIR0dERUVFR0hIR0ZEQj89PT89PT+GQA4/QUJDQ0JCQ0JCQUA/PoQ9ATuEOoU5ATqEOYQ6hTuGPAM9PDyFPYc8CT0+QEBBQkNCQ4REBENERkeGSSVIR0dISkxNTU1MTU1MS0tNTk5MS0lLS0tJRkVFR0lJSktJSEhIhEeARURDQ0NBQUJCQ0RERUZGR0hIR0VDQkA/Pz9AQUFDREVGSElKSUlNT1BSVFVWVVZXWltcWlpZWltbWlhYWFlbW1lZV1dWWltcWlpaWVtcXV9fX15dW1tbXFxcW1dVVlVWV1hZW1xeXV1dX2FkZ2dkZGRjY2BeXF1cYGBfX19eX19KXl5eXV5gYGJkZmVjYGFfXFlVU1JTUlNSUlFRUlNTVFVaXFtcXV1eX2JnaWtrbm1sa2pucXFubW9yd3d9gX9/hIaGg4OHlJubkJOElyOknpKTk5ijrbWxq6ywv7ufnJy2v8C+vMDBx8rR2Nvh4My4u4DDw764tLa0qqSpqaSkrKykm6GnqqyzvLatoqWnp6OdnaCjpamro6y0sq2rrLG6uLS2tbm1t7a3tLO5vbuyqqamqKuqp6SjoZ6en6Ciqqyrr7Svrqyhm5uanqGlqKupq7CwsbrDyMvJycfExcC4tbW4ur6+wMXHzM9oa21ubW5ub01va9DQzcnJx8XHycrJyWdrb3NybWpozMC1rKajpKesrLC1tKyfl5WWl5qamJeWlJGOiYmKiomFgX16enl3dHNyc3NycnBwcXFycnN0doV4O3l6enp5d3h1dHRxcW9ubm5tbGtsa2prbXB0dnd4d3Z1dXRzcnFwb29vbm5ub3BwcXBub3FxcGtpZ2ZjimQyY2NkY2NlZWVmZmdnZ2hpZ2ZmZWdqbW5tamhmY2JhYmNjZGRjY2JgX2BfYF9fX15eXl+GXhNfX19gYGFiY2RlZGZoaWpqa2xthG4kb25tbWtqa2tqaWhpaWpsbGprZ2ZmZmVnZmVkY2NiYmBhYF9fhV4SXV1cXVxbWlpaW1taWlpbWllahFkEWFdXVoRXh1gRWVhYWFlZWVpaWllZV1dXWFmHWgZbW1taWlqEWwRcXFxbh10LXl1eX19gYWJjZGWEZAplZmZmZWRkYmJjhWSIY0tkY2FgYWFiY2NjYmNjYmJiY2FgYWFhYmFhYmJjY2RlZWVkY2RjY2JkZWZmZWRlZWRlY2NiYWFgYGBhYWBgYWVmZ2ViYV9hYF1eXl6EXYRfMGFhYGBhYWFgX15iZWVmZmdoZmNiYmFhYmNiYWBfX1xdXV1eX19eXVxaV1ZWV1VVVohXglmFWBVWVlVVVFRVVFRTUlBRUVBQUFFQUVGEUIVRhFKCU4VSi1MKVFRUVVZWV1dYV4ZYL1laWlpcXFtbW1paWltcXl9eXl1eXl1bXF5eX15eW11cXFpXV1hZWltaW1tbWlpZhFgWV1ZXV1VWVVVWV1ZXV1hZWllZWFdWVYRUSlVVV1dYWVpcW1tbXl9gYmRkZWRlZmhoaWhpaGhoaWlpZ2hpampnZ2ZmZmlpamppaWlqa2ttbW1sa2loaWpqa2lmZWZmZ2hqa2trhWwEbm9xc4Rwf3FwbG1tbHBvbm9ubm5vbm5vb29xcHJ0dnVzcXJxbmtraWhoaWlpaGdoaWlqa2tucHBwcnN1dnl8fX9+gYKBgYCDhISCg4OFiYmPkpGRlZeXlZabpaqnnZ6hoqSkrqqho6Opr7a7uLW3usXArKytvMLCw8PGyc3O09TV2tnKvMCA6ejo6Obm5OHd4eDe3+Lg3trd3+Dh5ebk4d7k4N/a2Nrc3d7h5d/k5uXi5OTn7Ovp6+rt6Onm5+bo7PDv7Ojk5uXl5uLg3t7d3uDi4+bl5Oft6efj4Nzc293f4uXp5+fs6u3z9ff6+/z7+fj07+3u7u3x8fP3+P3/gIKDhYSDg4RfgoD7+vXy9PX29/n6+/6AhIaHh4OCgPzz6ufk4OHl6enu9PTs4dbV1dfa29vb2dfV0c3Mzs3Kx8O9urm4trOysbKxsK+vrq+urq6wsrS3uLe2uLi5urm3tbazsbGurquEqWSoqKinpqeqq6+wsbGvrq2trayqqKenpqalpqamqKiopqSlp6ilop+dnJubnJ2dnJucm5ubmpmZmpqbmZmampucnJ2dnJuamp2eoaKhn52amJeWlpiZmpmZl5eWlpaVlpWUlJSThJQhlZSTlJWVlJSUlpeYmJmYmZucnZ2dnp6fn6CgoqGhn56ciJ0Unp+fn5ycm5ubnp6cm5qZmJiXl5eGlgWYl5WTk4WUC5OUlJOUlZWUlpaVjZQZlpeWlpeXl5aWl5iZmJeWlpeWlpaXmJmZmYSYhJmEmoWZAZuFnCidnpyenp6foKGhoqGhoqKipKSjo6KioaChoqKjo6Slo6Oko6WkpaSlhqSGpSqkpaSkpqanpqWmpqanpqWmqKmop6amp6amp6eoqqqqqKeop6inp6ampaSGowykp6enpKOjoaKgn5+EoAahoqKhoaGEohahn6Cfn5+io6OjoqOjo6GgoZ+foaGghJ4Vm5uampyenZ2cmpiWlJWXlZSVlZSThZQSlpaWlZWTk5KSkpGQkI+Ojo+Oho0EjIyMjYmMIY2NjYyNjY2PjY2NjI2Pj42NjIyLjIuLjIyNjY2MjY6NjoSQH4+PkJCRkZGQkJCPjo+QkJGRkI+Ojo6NjY2QkJGPjoyEjhGNjIuKi4uMjIqKiYqLioqKiYSICoeIiYiIiYmKi4uFjBaKiYiHhoiIiImKiouLjY6Njo2PkJCRhJIFk5SVl5iFlkGXl5aVlZeXl5WXl5aVl5eYl5iYl5iZmpybm5qbmZiZmZmamZeXmJeXl5iZmZqZmpucnJyen5+dnp6fn56enZ6foIahcqKhoaKgoKOkpqamp6enqKempaalpaampaWlpKSlpqanp6mrq6ytra+wsrW2t7e5urm5ubu8vb6+v8LFxsjJycvO0NDNzdDY2tvZ29ra2tne3djb297j4+Pi4+Tk6efj4d/o6+3s7O3s7e3u8fHx7+7p6viAioGMgIiB/4D/gP+A/4D/gP+A8IACAgQAgNXMxcjNzMO4rKioqaWrpZ6SmJ+msre9v8KyqaKblpGZoaOlpp+WmpiVlZ6ptLKmpai1wbq9xLeqo52cnp2XlpqjqqakmJCMjYiHioyZnZubnpWVl5qVj5ahnpulpJyYnJ+frbq+ur2+vb+9s7OvqbW7uLq7wMDDxMVjZWdpamlqP2xsaM3Lx8XCwMC+u7u6wmdqa2diwb2xp5yWkI+Mi46Sl5aShoGBf358e3t5enp5dnRxcHN1dHBsZ2RkYF9dXIRbAlxahFkIW1xdXmBgYWKEYQtiYmBgX19eXlpZV4ZWEldWVlldYWJiYWBfYGBfXl1cXIRbDlxeYGBfX19eX15cWFVUhFICU1SEUwVSUVFPUIRRH1JRUlVXVldWVFFRUFFSVVpaWFlZVlNSUVBRUlNSUFCETQ9MTEtLSktLSkpJSUpKSUqESxZMTU1PUlRWVlhYWVpbW1xbXV1dXl9ehVwNW1pYWFlXVVZYWVhVVIRVH1ZUUU9OTUxKSkpJSklISUlJSEZFRUREQ0NCQkJDQ0SFRQNEQ0KEQYJAi0EEQkFAP4ZACz8/QEA/QEBAQUJDhUIBQ4VEM0VFRURFRkdHRkZGRUZHRkZHSElKS0tMTE1OUFBRUE9OTUxLS0tMS0pISUlISkpKTE1NSoVHIUhJSElHRkZFRUZGRkVHSEhHSEhJSUtLSklJSEZHSEhJSoRJWUhJSUlIR0dIR0ZFRkZHSEpLTE1OTkxIR0ZFRUdHR0VERERFREVFR0hHR0dISUhHR0xLS09RUU5LSEdJS0tNTk5NS0pJSEhGRkdJSkpJSEQ8Oz4+Pj0/QEFAhD8UQUJBQkJCQ0JBQUBAQD8+Pj49PTyEOwg6Ojk5OTo7O4o8ATuFPAg9PDw7Ozs6O4Q6CTs8PT4/QEBBQYRCE0RGR0ZHSUhJSUhJSUlHR0dJSUuFTAxNTk9PTk5NSkhISUeERgJHRoRFD0RGRUVGRUVERUNCQ0NERIdFTUZISUdGR0dFRENCQkNFR0dHSElLTEtLTE1MTlNUVVVVV1haXV9eXFtaWFdXWFtdXl9eW1pbXV9iZGNiYF9dXF1dW1pZWVpbW1xaWFZVhFOAVVVWV1hWVVZaYWNiYmFhYWJkZWVmZmRhYmFgXl9eXV9fYWBhYGJhYl9cW1xbWVdXWVpZV1RUVVNTUlJTU1JSU1VXV1laXV5hY2ZpamlnZmVlZ2ZnamtscXN2eHp+fn6EhouSkZCTl5eVmJudoKqpoqWmrK2tsrixqKSnsr6+srURv8O3uL7Cv729z9zl5tzW0dOA0szIy8/Ox7+1srOzsbaxq6OprbC2vMHFyb27s62qo6ivsLGyrKWqrKaor7W+vrW0t7/HwcXLwbizsLCxsKurrrO3tLSpoqChnZufoKmtq6utpqWmqqWhpq+uqrS0r6uusLK6xMnHx8rKy8bBvry4vsPAw8LHyczOz2dqbG5ubm9Fb29r0tHOzcvIycjHx8bOa21ubGjLx72zrKiko6Cfo6isqaaalZSSkpGRkY+QkY+LiYeGiYyLhoJ9ent4d3V0c3NzcnJxhHAXcXJ0dnh4eXp6eXl5enl4d3Z2dXRxcG+FbgZtbWtsbXKFdgd1dXVzc3JxhHADb3BxhHMXcnJzcnBtamhnZmZmaGhnZmZmZWRkY2WFZihlZmhpaGlpaGdnZWZnaW1tampqaGZkY2RkZWZlY2NiYmFhX2BgYF9fh16CXYReEF9gYWNmZmhoaWlqamttbW2EbhRwb25ubW5tbWxqamppaWlqa2poZ4RoFmlnZWNiYF9fX15eXl1dXl1dXFxaWlmEWg9ZWVlaW1paW1tcWlpZWFeIWApZWFhYWVlaWltah1gEV1dYWIVZBlpbWltbW4RcAVuFXAFdh14UXV5fX19gYGFhYmJjY2VmZ2hoZmWFZA9mZWNiYWJhYWJjY2VlZWSGYghjYmNiYmFhYYRgDGJjY2JjY2RjZmZlY4ZiA2NkZIVlJmRkY2NjYmJhYWBhYmJiY2VmZmZkYmBgX19gYF9dXl9fX2BgYGFhhGAZYWBfX2NjY2VmZmRiYF9hYmJjZGVlYWBfXoRfI2BgYWBfW1ZVWFhXVldYWVhYV1dYWFlYV1hYWlpYV1ZWVlVVhVQGU1JSUlFShVGIUglTUlNSUlJTU1OGUgpRUlJRUlJTVFVVhFaEVwVYWFpaWoRbAVyEWypaWlpcXF1eXl1dXl5eX2BfXl1bWlpbWVdXV1hZWFdXWFdXWFZWWFhXVleEVoZXgFhXWFlaWllZWllYV1ZVU1VYWlpYWlxdXV1cXl9eX2JjZGVmZmdoa25ta2ppaGdmZ2psbGxramlqbG5ucG9vbm5ubGxsa2ppaWpqamxpaGZlZGVlZWdoaGhpZmZnam5wb25ubW9vcXFxcnNwb3Bwb25ubW1ub3FwcHBycXBvbm9vZW5ta2xtbGtraWlraGhoaWlqaWlrbW5vcHF0dXd5e35/fn58fX1+fX6AgYKFhoqLjpGRkZaXm6CioqWmo6OnqKeqsbGrr7C1uLa5vrizsLO9xcG6vcbLvr/Dx8bFxNDb39/X09HRgPHu7uzw8O3r5+fo5+fo5uLe3+Hj6Ojs7/Lz9e/p5eDj5+fp6ufj5eXk4+fp7u7r6uzw9fL09fDr6urs7O3q6u/w7+3u5uHf4d7e4eHn5+Xl6OLi4uTh3uPn5eXq7Orp7O3t8fj7+/3+///89vXy8fP08vPz9/j7/v+AgIGDg4SEB4SDgf349/iF+Rz9/v+ChIWDgf/77ujn5eHh393g5ero5tvX1tTShNA01NbU0MzJyM7R0cvFv7y9ube1s7Gys7KysrCvr66usLK1ubq5ubm3uLm6urm4t7Wzsa+uq4Wpaqipp6irrbGwsbGxsK+urauqqainp6amp6irqamoqaepqaejoaCfnp6en6Cfn56dnJycmpycnZycnJucnp6en6Cem5ucnZ2doaKgoJ+cmZmYmJiZmpqYmJeXlpeWlpaVlJWUlJOTlJSTkpOHlAmWmJmamZucnZ6FnzChoKKio6KhoKGfn5+en5+enp+hoZ6dnJ2dnp6fnpyampiXlpaWlZaWl5iYl5WUlZaFlQyUlJSVk5OVlpWWlZaElSSUk5OUlJaVl5eXmJiZmZmYmJaYmZeWl5eVlZaXl5iZmZqZmZmGmg6ZmpqbnJ2enZydnp2dnoSdFp6en6ChoaGioqGio6SlpaSkpKOjoqGEogiho6Kho6SjpYamM6WlpqelpqWlpqWlp6empqeopqanqKioqqqpp6anpqioqKeoqKmpp6Wmpqempqeop6alpYSmF6eop6empKOioqGhoqOkoqKio6OkpKKihKESoqOioqKjoqKio6OioZ+en6GghKEaoKCfnp2bm5ucnJybm5mVlJWWlZSVlpeWlJSEloSVgpSEkwOSkpGEkA+Pjo2Njo6OjY2Njo6NjYyFjQaMjI2MjI2EjhONjo6OjYyNjIuMjIuMjI2Njo6OhI8/jo6PkJCRkpGQkZCQj4+OjY+PkI+Qj46Ojo+Pj5CPkI6NjIyNjIyLi4qKiomKiYqJioqLi4qJiIiHh4iJioqLhIowiYqMjY6Mi4yMi4qHh4eKi4yNi42Ojo+Ojo+QkJCTk5STlJSWl5qdm5iWlpaVlZaYhJqAmZeYmpubnJycm5uamJmampmYmZmampuZl5eWl5iXl5iYmJmamZiZm56enZycnJ6goaKhoqOjoaGgoJ+goaGioqOjo6SmpqakoqOkpqakpqiop6enqKipqaiop6inp6mpqqmrrbCwsbS3uLi3uLe4ubu8vL+/wMXFxsjJy8vM0M8w1Nfa2tzd3N3g3d3e4ODf4uPm5eTm6efl4+Pn6+vo6ezt6Ofr7u7v7/H19PTx7/Dx+YCKgYyAhYH/gP+A/4D/gP+A/4DygAICBACAxcbEzdDNztPUycS4o5mUl52jqr/HyMfGw7qtq6aioaGhnqGdmZ6kraekrLzEr6y0tr++t7a6rqKfoKKio5mVmqKkoJ+Yj4+RkJGTkpyemZmhpKmzsKiblpaanaCgrLazqrjFymdsa2hmZWTFx8jHxsjHxsnLyMfIxsbJZmhpaWllamtqz87Oz8rHwry6urm8v8Rkwbm2tKeblJKQjouIiYyMiYiAfHl4eHd2eHx/fXp4dHRycHBua2hnY2JgX2BfXlxcW1tbXV1cW1taXWJiYmFiYmJhYmRkYmFgX15dXFtaWVdXWFiEVghZXmBhYWFgYIRfC11dXVxcXGBgYWFfhGAFX11ZV1aEVTlXV1hXVVRUU1RTUVJSU1NUVFVXWFhXVlVUU1JSVFRYWlpaWVlYVFJTVFRUU1NRUU9OTU1MTExLS0uESl9LTEtKS0xMS0xMTE5PUVNVV1hZWVpcXV5fYWFgX2BiYF9fXl5eXVxZV1dWV1hZWlhXWFhXV1dWU1FPTkxLS0tMTEtKSUhIR0ZFRUVEQkJBQkNEQ0RFREVGRkVEQ0NCQoVEBUNCQkFChEMEQkJBQYdABUFAQD8/hkEFQkFBQkOERAxDRUVFRkdFRUZFRUWEREVDREVGR0hKS0tMTE5PT05NTk9PTkxMTEtHRkhISElIR0lLS0pJR0ZHR0ZISEhGRUdGRkZHRkZGR0dISUpKSktMS0xLSkqESQxISElIRkdISEhHR0eGRgRIR0hKhUwHS0hGR0dHRYVCB0RERUVGR0eESTBLTEtJSUlNTkxMS0lJSktLTU9QT0xKS01NSklJS0tLTUxLRkRGQ0A9PT8/Pz4/P0CHQQpDQkFBPz4+PT49hzwBO4Q6BDs8PD2EPoU9hDwQPT49Pj09PDw7Ozs9PTs8PoU/N0BBQkFCQ0VHR0ZGR0lJSktLS0lIR0dISEpLS0tKS01OT1FQT01LSUlHR0hIRkREREVFRENCQ0OFRAtGR0ZGRkdFRURFRYVHSkZHR0dJR0hHRERGSUlISEpKS01NTUtMTE1PUVBRUlVVWl1eXl1dXFpZWVhXWFteXl5fX19gYmVkY2NjYmBdW1pZWFlZW1lYV1dWhFWAWFlaYGFbWlhZX2VkZGZpaWhmZGdnZmhqZ2RkZGNhYGJiY2NgXl1eXl9bWFhWVVhaXFxZVlRTU1JUU1NSVFNTU1RUVVhYWVpcXl5gYmNkY2RlZmdoZ2lqam10fXx8fH2AgomQjpGXo6urqqSgoKq5saemqa+xrLC2tK2ssbW+x80SzMbDt7OzsKuwtba5wszKxcPBfsjJx83Qzc3Q0szJwbavp6ittbzGyszNzcvGvbu3tLKxsa6vraqus7u4tLjByb27wMLIx8HCxr22tLOzs7StqqyytbGvqaKipaanp6arq6ansbK1urmzqaanqqyxsbm/v7vEztJqbG1sampozs3Pz8/Qzs/R09DQ0M/P0WpsbYRuaW3V0tLSz83Lx8fHxsfMzmjMyMXAtayopaSioJ6foqKfnZWRj46NjIuQk5aUkI6LioiIiIeDgH57e3h2d3Z1dHNycnJzc3JxcXF0d3l5eHl5eXh4ent5eHd1c3NycXBwbm1ubmxtbGxuc4Z2PHV0c3NycnBvcHBzc3N0dXN0c3Nzcm1ramhoaGpsbGxraWhoZ2hnZWdnaGhpZ2hpamtqa2poaGdnaGhqbIRrEGpnZWVnZ2dmZWRjY2FgYF+EYAFfhl4EX19gYIRfHmBhYmNmZ2hoaWprbW5vb3Bwb3BxcnJycW9vb25ta4RpI2psbGxramtpamppZ2RiYmFgYF9fX15eXl1dXVxbWlpaWVlYhFkBWoZbBVpaWllZhluEWgVbWltbWoZZAViGWYhaE1xcWlpbXFxcW1tdXV1eX15dX1+GXg9dXl9fYGBgYWNjZGZnZ2aHZQ9kY2JhYmBhYWJhY2RkY2OEYgRhYWNjhGJBYWFhYGBgYWJjZGRlZWZlZWRjZGVkY2NjZGNjY2JjZGNjYmNjYmJhYmJhYmFhYmNkZGVlZGJhYWBgXl1bXV1eX1+EYAZhYmJhYWGEYhJhY2RjY2FfYGJiYWJkZmRiYGCEYgxhYmJiY2NiXVxdW1iFVhNVVldXWFlYWFdYWFpZWFhXVlVViFSDU4RSg1OHVBFTU1JTUlNUVFNTVFNTUlFRUYRThFUVVFVWVldXWFhaW1xaWVpbW1tcXV1chFscXF1eXV1bXF5fYWJhX11bWllZWVpZWFdWVVhYV4RWElVVVldXWFlYWVlZWFhXV1lbWoRZJlpaWltaWllWVllbW1taXF1eXl9eXl5fX2BgYmNkZWVobG5vbWxrhGlHaGlra2tsbW1vb3F0cG9wcXBubGtqaWlpamppaWdmZmVmZmZnaGhtb2poaGhucnFxdHV1dHNzdXVzdXZ0cXFycnFwcXJycnCEbxBwbGtsa2ttb3BwbmtrampqhGsRbGxrbGxtbXBwcHJ0dnd3eHqEe0F8fX5/gIGChIuSkZGQkJOVm6KfoqWttLa2sq+utb65srK1urqzt8C+uri7wMXKz87KysG8vLu3u76+wsnQy8nJx2bw8fDz9PHy9fj08vHw6Obq7Ors8PLz+Pn39fPx7u7r6uzr7Oro7O7v7uvr7fTx8fP0+Pj3+Prz7u7t7u/u6+nt7+/s6+jk5OXm5ebm6ejj4ujp7O7u6+Xj4+Tm6env8vTz+P3/gIGEgnuB/v/9+/n9+/n6/Pz8/v7+/4GDhIODg4KB/vz9/v/+/fz8/fz9/P6A/fv69+3o5OHg39za3OHi3t3S0s7NzMzL0NTY2NXSzs7MysrJxcK/vLy5uLe1tLSzsrO0tbWzsbGxtLm7u7m6urm4ubq7uLe0tLKxsK+tq6mqq6uEqSmqrrGxsrGwsbCvrq2rqqqoqKmrqaqqqKmpqqmpp6SioJ+fn6CioqOioYSgS56cnZ6fnp+en6ChoKCgn52cm5yenqChoKCfoJ+dnJqbm5yampmYl5eWl5WWlpaVlZSTk5OUlJWVlJWVlZSUlJWWl5mampucnZ2eoIShhKIRo6SjoqSjoqKgnp6en5+goKCEng2fnp6cm5mZmJeWlZaWhJcGmJeWlZWVh5QXk5SVlJWVlpWVlpWVlpeWlpaXlpaXl5eEmISZA5iZmISXBpiYl5eXmISZC5qbmpqcnJybmpqbhZ1Tm5udnp6fnp6en56fn6CgoqOioqOjpKWlpaSlpaalpKWkpKKho6KipKSkpaanp6empaenpaanp6amp6amp6empqWmpqeoqaurq6qoqKmoqamoqqiEp1WlpqWlpqWlpaanpqenpqalpqempqinpaWkpKSjpKOhoKCho6Sko6Kko6SlpKSjpKSjo6KhoqKhoaKhoqKhn6GjpKKhnp+hoJ6enp+en5+fnpqampmYhJYRlZSVlpaXl5aVk5SUlpWVlJOEkoWRBZCPjo6PhI4UjY6Oj46Oj4+OjY2Ojo6NjY6Pjo6FjQSMjZCPiI4Kj4+PkI+PkJGSkYWQJ5GSkpCOjo+Qj5CRkI+Nj5CQkJKRkY+Ojo2Li4yMi4iIiYuKiomJiYSKg4mEigmLioqJiouMjIuGjBWNjIyKiImKjY2MjI6Pj5CRkZCQkJGEklqTlZSXmpycm5qamJiYl5aXmZqam52enp6goZ+dnp+fnJqam5qZmpqbmpmYmZmYmZiYmZuam5yam5manaKhn6Gjo6KioaSkpKanpqOhoqGhoqOko6Oio6OkpKWEpGWmp6ipqamnqKiop6ioqamrrKyrrKysra2tr7Cxs7W3uLi6urm8vL2+wMDBxcvOzMzMztDR193b29/m6Ofn5ePi5Ojm5OTm6Onn6evq6enq7/Hx8/Lv7uzp6Ovq7O7t7Ozu7+3v7+SAh4GQgIiBjoABgf+A/4D/gP+A/4D/gPSAAgIEAIDCu7m/y8jK1djazq+jn6CltMC9sri8wcbDs6eopqenop6XmJibm56foKqss66prK6xtre0sK6qqay0ubmto52gqKqko56SjpKYnaisubqtqKSqsaijnqGsraekoKOtrrS4vmBjY2ZnZmJhYmZmZ2ZlZ2ppaWdkycfExWRmaGloZ2RnZ2jNz2hpZ2RiYb6+v7+6uLWurq6yqqGZj4qHhIKDg4OAfnl3dXZ2eX+GhIF/fXp3c3BtbGtpZ2ZiYWFgX15eXV1cXV1eXl9fX2BjY2NiYWJhYWBgX15fYWFiYl9dXVtaWVhXhFYrWFhcX2BfYV9fXl9fXl1dXl5fYGFiY2JhYmFgYF1aWFhWVldZW1taWllXVoVUhVUGVldYVlhVhFQ7VldXVlhZWltaW1tZVlZWVVVTUU9OTU1MS0xMTEtLS0xLS0pLTExLTE1OTk5NTU5QVFZYWVlaW1tcXl+EYT9gYWNjY2JhYF9dXVpZWFlZWVpbWltcW1lYWFVSUE5OTU1OTk5NTEtMS0tJR0ZGRUVDQkNCQ0RFREREQ0RDQkKEQRRCQ0RDQkJCQUFBQkFCQkJBQUFCQYRCDUNEQ0FBQUJDQkFBQUKEQSZCQ0RDQ0RFR0hISUlIR0ZGRUVEQ0JDRERERUVGSElKTE1NS0tMTIVNB05LSkpKS0uGSCNHRkZFRUdHR0ZGRUZGRkVGRkZFRUVHSEdISUpKSUpLS0tJSIRHC0ZGRUZGRkVGSEhHhUUKRkdFRkdIR0lJSIRHL0ZEQ0REQkJERUZISEZHSUhISUxPTk1LTVBRUE5MSkpMTUxMTU5NTUxNTUxKSEpMhE0gTEtKSkhCPT09Pz8/QD8/QEBBQEBAQUJEREE/Pz49PT2EPIM9hTyEPYI/hj4QPT0+Pj49Pj4/Pz49PT48PYQ/hEAMP0BAQUFBQkRFRUVGhUiESS5HRkVGSElLTEtLSkpLTE5QUE9QT0xHRkZISUtKR0REQ0NERUNCQ0RFRkdHRkVEhEcMSEdIR0dIR0dJS0xMhEsPTExMTUtKSktNT09PTk9PhFATUVFSVVlcXVxbW1xcWlhXV1haXIVdgF9gYWFhYGFgX15cW1taW1lWVFRVVVpeZGJhX15fZGdkY2FgYmRmaWhmaGhkYmFiZGZnZ2ZoZ2VjYWFiYmRjYmJgXlxaWFdUVVlbXF9cVlZVVFRUU1RTVFVWVVZWV1lbW1xcXF1eYGFhYWJma25ucHNxcXR8hYWJkIyNkI+RkpqbLJ2fprC0sKqotLa1u8O2tr7Cv7q8yNLO19zY08nDtaypn6Cpr7K+ztrYz8TAgMjDwsfPzc3U1tnSvbOxsrfDysjAwsXIzczFvLu3uLizsKusra6vs7O0ubi+vbq8vb/CwsC/vLq4u8DDwru1sLK3uLSzraShpqyvtbjDwrezsba7s66rrbO2tLSws7u8wcTKZmdoaWtraGVna2tramprbGtramnQz87QaWtsbW1sPmtqa9XWbG1raWhnzMzLycbDwb6/wMO8tK2koJ2bmpqbmpaWko6MjIyPl52bmJaTkI6Jh4eFg4F/fXp5eHd2hHUNdHR2d3d4eHd4eXh4eYR4HnZ2dXV3d3Z3dnVzcnFwb25ubGxtbW5ucXN0dXd2doR0OXJycXFzc3R2d3V0dXVzc3JubGxrbG1ub29ubmxramhnZmdoaWpra2pqa2tqamlpaGdoampramxsbIRtC2poaGhnZ2VjYmFfhGCCYYRgh1+DYIRhLWJjZWZoaWpqbG1ub3BwcXFxcnJzcnJxcXFvb29sbGtsa2xtbW1ubm1sa2toZoRjIGJiYWBgX2BgX19eXV1cWlpaWFlZWltbWltaWVlYWVhYhFkMWltaWltbWllaW1pbh1qEWxRcXVtaWlpbXFxbWlpbW1pZWVpcXYRcBl9hYWFgYIZfEV1bXF1dXV5eX2BiY2RlZmVkh2UBZIVjBGRhYmKIYQFihGGDYoVhBmBhYGJjY4VkEWVmZmZkY2JjY2JhYmFiYmFhhGKFYYRgRWFhYWJjYmFgYWFgX15fX11dX2BgYWBgYGJiYWFjZmVkY2RlZ2ZlY2FiY2VkY2RkY2RjZGRjYWBhZGVkZWRjYmBfXlpYV4RWBFdXV1iFVwZYWVlaWFaEVYhUhlMGVFVUVVRThlQBU4VUAlVWhFUJVFNUVVZVVVZWhlUmVlZYWVpZWlpbWlpaW1xbXFxbWllaW1xdXl1cXV1cXmBiYmBfX12EWh1bXFtYVlhYV1hXVlVWV1dYWlpZWFhZWVpaWllaWYRaBlxdXl1cXYZeCV1cW1xeX19fYIVhCGJiY2NmamxshWpjaWhoaWlqa2xsbW1tb29wb29ub25ubWxrampramdlZWZmaW1xb21tbG1xdHJvbGxvcXN2dXR1c3JxcXFyc3R0c3Z2dXNxcXJyc3Jyc3Jwbm1ra2lqbXBydHFsbW5tbW1sa2xshW4HcHBxc3R1doR3RXh5en6ChYWGiIiIjJWcnJ6goKGkoqWlrKytr7i/wcC8tr2/vcPIv7/FyMfExs7V09jc2NXNycG6ubGxuLy9xc/Y18/JyGLz7u7w8/P1+Pr6+PXx7u/y9vb18fT2+f39+Pb38/Py7+3q6+nr7O7s7fHv8vPy8vPz9/n59/Ty8vT09PXw8fDw8fLv7uro5efr7vHv7u/r6Obq7+zr6OXo6unr6eru8Pb4/YSACIGAgICBg4KCiIAR//37/oCBg4ODgoGAgP3+gIKEgVn//v38+ff28/L0+vXv6ePe29jY2NrY1tTQzcvLztTX3d3a2tjV0tDMycbDwcC/u7q5uLe2tbW1tLW3uLm5ubi4ubm7u7q6uLe2trSztLW1t7e0srGvrq2sq4WqKqutrq+vs7GxsLCvraurq6qsq6yurquqrKuqq6qlo6Oio6Wmp6akpKOiooWghKEHn5+goaCgn4SeGaChoaCioaChoKGhnp2dnp2dm5mYl5aXlpWElgaVlZaVlZSElRSWl5eWlpWVlJWYmpubnJyenp+goYaihKRLpaWkoqKhoaCgoaCgoaChoqKhoZ+dnZyamZmZmJmZmZqYmZmZl5eWlpSUlJWVlZaWlZWVlJSVlJWVlZaVlZaYl5aXmJeWlpeYlpiYhJcPmZmZmJmZmZqZmpmZmZqZhJoOm5ybm5ycnZycnJucnp6FnwienZ6en52fn4SgEaGio6SlpqalpaalpKWkpaSlhKMDpKWjhaQKpaamp6empqeop4amIqempaamqKioqaqrq6mpqaqpp6enqKempqemp6ampqenqKiFpwSmpaSkhKUfpqWmpaWkpKKhoaChoaSko6Slo6OlpKOipKWko6GhooajD6KjoqKio6Cgn6CgoJ+en4Sggp+EnQSZlpWVhJcDlpaVhJYQlZaWl5aUk5OTkpKSkZCQkIeRAZCEjweRkI+Qj46OhpAaj4+QkI+Pjo6OjY6RkZGQkZCPjo+PkI+Pj46IkCSPkJGSkZGPjo2PkJCRkpGQkJGQkZGSkpGRkpGMiouMjY6Oi4mFixKKiIiJiouMjIuJioyNjIqKi42EjASNjo+Oh49BkJGQj4+PkJGRkZCSkZKSk5OTlJWWl5qampmam5yamJeXl5iampqbm5udnp+enZydnZ6dm5qbmpybmpmYmZebnqCEnoCfoJ+enp2dnp+hoqGfoaOioaGipKSlpaenpKSlpKSlpaanqKmopqWlpaalp6mrrK6sqKqrqqqrqqmpqqytra6ur7CxsbKztLW2t7i5uru+wcPDxcjHyMzS2NfZ2tra3N3h4eXn6Ojp7O/u7Ons7Ort8uzu7e/w7/H09/f5+vXy9BD08e3r6urq7O/y8/j49/Tz4oCUgYSAiYGCgIaB/4D/gP+A/4D/gP+A+4ACAgQAfq2rsLrAvMPY2djJwruypp2eqbi4sba6vrmdl5ubnp+alpWZnqGmq6qrsbWxsKmrqKaqrq+wsbCvrK+0rqain6Ggn6ChnJGSk5qepauvrq22ta2sp5+hoqSnpqitsbm7XmJeXbe2tKuttrS0v8JkZWhqa9HOzc3NxsLCYmVmZoRlbctmZmZlZWNhYF5as7Kxp6OkoqWqrKKTi4mFg398fX+Bf3t4eHd5fYWKiYuOi4J5cnBtbGloZ2ZkY2JiYF5dXFxeX2BfXVxcXV9iY2RjZGJhXl5cXF1dXl9iY2FgX15cW1pYV1dYV1hZXFxdXV+FXiNfX2BhYWBfX2NjY2RkY2FdWlhZW1xdXV5dWltbWllYVlVVVYRXKVhZWVlYWVdWVlZVVFVVVVZWV1lbXFtaWVdVVVVUUlFPT01NTU5OTU1MhU0YTk5OTU1OTk9PUFJUVlZXWVpbXF1dXl5fhGEMYmNkZGVjY2FhYF5chFtSWlpdXV1bXFtaWVdUUVBQT09NTU1LSkpJSklJR0ZGRUVDRENDQ0JCQkFCQkNCQkFBQUJCQkFBQ0JCQkFCQUFCQ0VGRURFRENCQ0NDRERDQ0NERIhDDkRCQUFCQkNEREZGR0hJhEoQSUhIR0ZFREREQ0NFRkZISYlLREpKSk5PTk1NUE5JSUhIR0hGRERERUVFR0dIRkZHR0ZGRENCQ0ZHR0dJSUtKSUlKS0tKSkhHR0VDQ0NFR0dISUlKSUZFhERORUVGRkVEQ0VHR0ZHSUlHRUREQ0NDREZIR0hHSUtKSkxOT1JWV1dYU0xJSElKSkxNTk1OTk1NTUxJR0lMS0pKSUdHREREQ0JBQD8+QEA+hkAMQUJBQUFAQEA+PTw8hD2HPiA9Pj4+P0BBQEBAPz4/Pz4+PT08PT8/P0BAQEJCQD8/P4RADz9AQD4/QEJDQ0JDQ0NERYVGC0dIR0ZGRUNISklHhEiASkxMTU5NTE1LSElJSkxLSEVFRUREREFCRERERUdIR0ZGR0dISUhISUpLSUhJS01NTUxOTEtLS0xNTUxNUFJSUVFQUU9QUFFRU1VVVVZXWFhZWVlYWVdXVlhaW1xdXV1eX2JjYWFfYWFgXFxfY2RhWFxcW1xcXF9jZGNjZ2xubWuAa2hoZGJjYV9gX2BhYWRlaGlpZ2VmZ2hoZ2dnZWRjZGRjYWBeW1lZWV1cW1paWFhWV1dYV1dWV1ZXV1ZXWlxdXl1eXV5gY2VmbHV7fn+Cgnx8fXp+hImPlZebnZeOjpOWlpiiq7Czurq2trfCz8LGxcHAtbnFxL/I4/Xny7y0q6EMnaaqr7K7wMbDvLazgL28v8bKx8vX2drPysS+t7CyusLEwMLFx8W0rbCusLGsq6qus7S3uLe5vcLAv7m6uLa5vb69vr27ur7Avbi2s7OxsLKzraenp6+xtLi8u7rAv7e3s660s7GysrW6vsbJZWpnZcXAvba7xMDBy8xoamxubtfU09PRzs/QaGpra2pqTGlp1GtqaWlqaWhnZmTFxMO9t7e2t72+tqignJqZlpSTlJWUko6Ojo+VnqOjo6SgmI+IhoSDgH5+fnx6eXl3dnRzdHd4eHd3dXV1d3iEeh54d3V0dXR1dHV2eHl4d3Z0c3FxcG9vb21ub3JxcnOFdCRzdHR1dnZ1dHZ4eHd1d3Z1cnBubW1vcXFxcG5vb25sa2ppaWqEaytsbW1sa2xra2pqaWlqaWlqamtsbm5tbGppaGhnZmRiYWFgYGBhYmFiYWFhhGInYWBfYGFiY2JjZGdoaGhpamtsbW5ubnBxcnFycnJzdHZ0cnFxcW9thG4pbG1vb25sbW1tbGlnZWRkY2NhYWFgX19fYF9fXV1cW1paWllaWllZWlqIWYVaF1taWlpZWllZWltdXl1cXFpaW1xcXFtbhVwBW4RcBltcXFxbWoVbEFxdXmBgX2BhYWJhYGBgX16FXQVeX19gYYdjhWQHZWZmZWRlZIRiDmBgYF5fYGFhYWJjY2FhhGIWYWBfYGFjY2NkZWZlZWVmZmVkZGNjYoRgBGFiYWKEY4RhNGBgYF9gYF9fXl9hYWBgY2JhX19eXl5fYGBhYWFgYWJgYWNkZmhpaWlqaGVhYGBiYmRkZGOFZARjYF9ghGILYF1dW1tcXFtaWViEVxBYWVhYV1dYWVhXV1dWVlRVilQKVVVUVFRVVVZWVodVEVRTU1RTVFVVVVdXVldXVlVVhVYIVVVWVFVWVleFWAFZh1o1W1taWllXW1xcWlpbW1pbXFxeXl1dXl1cW1pbXVxZWFdYWFhXVFVXV1ZXWVlaWVlZWltcW1uEXANbXV6EXyhgYF9fXl9fX15eYGJiYWJiYmFhYWJiY2RlZmhoaGlqamppaWdoZ2hphWsTbG1vcG9vcG9tbWxtbnFycGlsbIRrKG5xcnBxc3d5enh2c3NycHFwbm9ub3Bxc3N0dnh1dHV2d3d1dnV0dHSEdR90cnBvb3BxcnJvcG5vb29wcG9ub3Bubm9vb3J0dXZ2hHhHe3x+gYqQlJWYmJGRk5KXm56kp6msraWho6eqra6zu7/BxcPAwcLJ0cjLysjIwcTKy8nP4+/izcXAvLaxt7m9vcTKzMvGwsFP7+3x8/Tz9fv5+vb5+PTz8O/x9PTy8/X39vHu8e/w8e/s7O3v7/L09PX49/b28fLy8/T09vf29fLz9vn18e/u7u3t7u7r6ejn7O7w8fDw7YXugOzr6+rr7O7z9/n8gIOBgf369vDz+Pf6//+BgYGCgv/+///7+fr/gYKCgoGBgID/gICCg4SDgoKBgP38+/bx8fD0/P7z5t/b29nU0tPU1dTRzc7P0dji5OTl6OTa0czJxcXCwL/Avr69vrq4tbS2ubq6ubm2tra3t7i4ubq4tbOxcbCwsrGys7S1tLW0srCvr66tq6ysrK2vra6wsbCxsbCvr62ur6+tra6xrq2vr6+uq6ikp6eoqqmop6Wmp6WjoqKhoaGjpKSjo6SjoqKioKChoaGgoaGgoKCen6Chn5+enp6dnZybmpmZmJeXmJiYmZeZhJhAlpaYl5iWlpeXl5iam5ubnJ2enp+goKCipKSjo6OlpqWlpaalpaSjo6SjoqOho6SkpKKkpKSjn52cnJuamZiZmoSYEZmYmZiXlpWUlJWWlpWUk5OThJUIlJSVlJaWlpWElg+Xl5eWl5eYmZmZmJmYmZmFmgWbnJydnYabBpycnZybm4WcDZ2dnp+goaKioqGgoaGFoAifn6GhoaOjooSkA6OkpImlUKinpKSjo6SlpaSlpqempqeoqKamp6eoqKelo6Snqampq6utq6mpqqqqqKmop6alpaampaipqaqpqquop6empqanpaalpaSlpqempaWmpqWkhaMOpaWlpKWkpaakpaanp6aEpwqlo6KhoqKho6KhhKApoaGhn52foJ+fnp2cnJubmpqZmJeXl5mZl5iXmJiXlpeXl5aWlZSUk5KEkYaSCZOTkpGRkZCRkISRBZCPkJCQhI+HkIORhJAKj4+Ojo+Pj46PkIWPIo6Ojo+Ojo+QkJKRj46Ni4+QkI6Pj46Oj4+QkZGQj5COi4yEjSiMi4uMi4uLioqJiYmKjIyMi4uNjY6NjIyNjo+OjY6QkJGRkJGQkI+OhJBxkZKTlJKTk5OSk5OTlJWXlpaWl5iYmZqamZmYl5eYmpucnJuam5ucnpydnZ6enpycnqCgn5ubnJudm5ydoJ+dn6GkpKSioaGioaChoJ+gn6GgoKOkpqenpqWnqKmpqKqrqamqrK2ura2srKuqqq2ur62GrlqwsbGwsbCwrq6wsrS1trW2tba4ury+xMvNztLU1NDR09LX2tvf4OHj5ePf4eXm5ujt8fP09vHt7e7z+PL09PLz8fT59fL3/f/4+fj29PDt8PDx8vLz9Pb08/PfgISBioCFgYiAiIEBgIqB/4D/gP+A/4D/gP+A+YACAgQAgKCktb6/yuV5eXp45OHYuaGan7Cyqaaho5uQkZKVlpicnqCjo6msrLS5u7mysbe8w8zPy8rH0NbUz8ayra6yt8K7qqKhnpeYnJyam5ycoKepp6qsq62ys7S0trK0X1tarqukoaa2tqejrbG1trxnam1ta2fIxMLAwcPEwmJjY2RlOWVlY2BfYGFiYF9dWlmvp6Kgn5uen6KfkYuHhoWBfXx8fX+ChYB/fnp5fH+DhoyNhHZwb25raWdlZIZjJGFfXV5eX19dW1xdXl9jY2RkYmJhYGBfX15fYGFkaGRfXl5cWoVZCFpbW1paW1xdhF4lX19hYmJjY2VlY2RlZGJfXV1dXmBhX15dXVxeXV1bWllZWFhbWoVZKlpZWVlYV1VVU1RUVVRVVVdZW1tbWVZVVVJSUE9OTk1NT1BRUlFRUVBRUYRQKk9QUVJTU1VWWFxcXV1eXl5fYGBhYWBiZGNkZGVmZmZlY2FfXl5dXV1bWoRcLFlZWVxaVlRTUlFRUE9OTEtJSkpJSUhHRkVFRkZFREREQ0JAQEA/QD8/QEBAhUENQkJCQUJCQkNCREdISIRHAUaGRwdGRURERENChEMTRENDQ0JAQEBBQ0NERUdISEhJSYVIPkVEQ0NDRERERUdISUlHSUlKS0pLS0pKTE1OTk9NSklISEhGRUREREVEREVFRkhHRkRFRUZGRkVFRkVGRkdJhEo1S0xLS0tJSEZGRURERkdISEtMS0pIRURDREVFRUdGRURDQkJDRUVFRkZHRUNCQkJDRUZGR0mESylOUFRYWlhXVU9LSUdISUpMS0tNTk5MTEtKSUhKTElHRkRCQkFCREREQopBF0BAQUE+PT4+P0BAPj09PT4+Pz4/QEA/hT4KP0BAQD9AQD8/QIc+Ez9APj4/QEJDQT8+Pz4/Pz4+P0CEQQRAQUJDhEQ7RUVFRkZGR0ZGSEpISElHSEhIR0hJSkpMTU1OT09PTkxMTEtLS0pLRkREQkJCQ0ZHSUlISUpKSUlISEmESxRMTU5RUE9OTk9NTExNT1BPUVJUVIRTOFFRUlJSU1ZYV1VVVVZWV1hYWFdWVVZXWFlaXV9iYmNlZmdiYWVoZGVoa2xjXVtfZGFgX19gY2ZmhGcGZmRjY2FghV8IYWZmZ2dmZmeEZnZlZmhoaGdlZGNjY2RiYF5dXVxeXl1eXVlaW1tdXl9dXFtbWlpZWltdXV1cXmFiZGZrdoCDhoiNi4qGg398fX+Ah5CVmqCimpCQlJeYnqilqbXCy8fDztPKxbuxtbO1u7u5w9t4dcS0rKikrLK3t7exraqmo6OggLa5w8nL0OJ1dXd14uLexLKusr6/uLa0t7Cnp6eqq6uusbS1tLi6ub/Ex8bAvsLFzNHS0NDP1trY082+ury/wsvFurKxr6qtr6+urKytsba2tLi6ubzAwcC/vr7DZmNiwL62tbW8urCxub7Cw8hqbm9ubGrOy8nHyc7PzmdoaGhpHWlqaWdmZ2hoZmVkY2PDvbm1srCzs7WyqaOem5qWhJNOlZealZeVkpGVmJyfpKaajoeGhIJ/fn18e3p7eXl5eHZ1dnZ3d3Vzc3R2dnh5enp4eHh3dnV2dXV3eHt+enZ1dHNwcG9ubm5vcXFwcXJzhXQHdXV2eHd3doR4Hnl5dnNycXFxc3RzcnFxb3FwcG5ubW1tbm5tbm5vboVtAWuHaSFoaWhqa2xsbGpoaGhmZmRiYmJhYWJjZGVlZWRkZWRkY2OFYh9lZmdoaWxsbG1ubm5wcHFxcXJ0dHN0dHV2dnZ1c3FwhW8cbm1vcG9ta2tsbmxoZ2ZmZWVkY2JhYF5fYF9fXoRcCl1dXFxaWlpZWFiEV4RYEFdYWFlZWVpaWVpaWltbXF6EXwVeXl1eX4ReB11cW1xcW1uGXAtdXVtaWlpbXV5eXoRfA2BhYYRgW19fXl5dXV1eX2BhYmJhYmJjZGJjZGNjZGVmZWVlY2JhYmJgYF9fYGFhYGFiY2NhYWBgYGFhYmFhYmJjZGRlZmZlZWZmZWZlYmFgYWBfX2JiY2NlZ2ZlY2JgYGCEYQNgXl+EXj9fX2BgYGFgXl1dXl9gYGBiY2RkY2NlZmlsa2tqamZjYV9gYWFjYmNlZWVkY2NiYWBhYmBeXVxbWllaW1xcW1qGWQhaWllYWVlYV4VWBFdWVlaEVQ5UVVVWVlVVVVRVVldXV4RW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Q0VEhEcHSEhIR0dFRYZEG0VGR0hKS0xMTUxLS0pKSUtNTUpJSEhJR0VEQ4VED0VFR0hISEdGR0hISUhISIRHAkpJhEodS0xKSUdHSEhGR0dHRkhKSktKSUdFRENDREVHR0aFQz5EQ0NDRERFRERDQ0NERUZGSEhJSktMTVBRVFVUUk1LSkhHR0lLS0xNTU1OS0lIR0ZHSEhFQ0RDQUBAQEJERYRDHkJBQUFAQUJCQ0NBPTw+QEFFR0VDQkFBQUJBQkJCP4U+hj8BPoQ/CUBAQEFAQD8/PoQ9FT49PT0+PTw9PkBCRERDQkFBQURDQ4REIEVGR0hHRkZHSElJSElHSktKSktLSklKTExOTk1MS0tLhU0TTkpKSUdGRENERklISUpLTExKSYVKGkxNT1BPT0xLTU5OTU1OUFFSVFVWVlRTVFNThFQEVVVVVoRVbFRVVldXWFVVVlZVV1lYWl5jY2ZmZWNgYGFjY2FiYmBhXV5eYGFfXFxdX19fXVxcXF5eX15fXltbXF5gYmRkZmloa2loZ2ZnZ2ZlZWVjYmJiYGBfXV5fXmJiYl9eXmBhYmNlZmdlYmRlYl9eX4RgTGFnbW1vc3yGiYqHiYqHiI+QjIeIh4aGhoeImJydmpiYnKGlqqqqsrzL0NHPycbGxLmurqqnqLK/y87Txreys7S3ubatpaisq7GxrK+Av8HJycvZcHJzcnN0c2/Ry8W/vLm4t7OtqKamq7C3srKytLm8wMbEvb66uLrDy9Xa3ODb2dfc3dnTzcG9yczN02vXzb22s7a5vry5trSurq6wsrq+wcLFysjCvsDBvbu4s62uq6mvrKm1u7/H0GtvcnBubWvQzc/Nzc3Mzc7OzmiAaM7Kw8NixcTFxGLDwsK+trCpp6qqqaeinp2cm5qbm5ugnqCdnZmVkY2Lj5eeo6eflJKJhYOBgX99fHt7e3p5eXh3d3l6eHV1d3l6ent6eHd4eXd3eXh4d3p7gX59enZ1dHJwbm5ucHNzcnFwcXJ0c3V1dHV1dnd2dnd5enp7enqAeXZ1dHR2d3h2c3JxcXJzdHJwcXBvb25vb3JzcnBubW5ubGxqaWlpamlpaGdnZ2lpampqaWhoZ2VkYmJjY2RnaGhoaWhoZ2dnZmZnZWdoZ2ZmaGlqbG5xcG9wcXFyc3NzdHN0dnd3dnZ1dXZ2dnRxcHFwcHBvb29w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ioF+eG9rampoZWRjY2JhX19hX19fYWNnaWRkZGVkY2JhYWJmZWNkZmhrbGhkYF5cWldXWFpcXV1chVoKW1xcXV5hYmJjZIVlMGNjZGRjY2RjY2BeXl5fYGFgX19eXF1eXV5fYF5eXV1cW1paV1ZVU1NUVlZVVlZVVoVXC1VUU1FRUFBQUVJUhFYBV4pWCVVWV1laXWBiY4ViA2NkZIRmg2WEYzRiYmNjY2JhX2BfX19dXFxbWFlXV1VUU1JSUlNSUVBPTk1MTUxNTEtKSUhIR0ZERENDQkFBhECFP4RBB0BBQUFDRESERhFHSEhHR0VEQ0RDREVERUVGRoRFBURERUVGhEcQSEhIR0VEREVERENERkRDRIRHIkZGRURDQ0JCQ0JFSUtMTEtLS0hIRkZJSkpIR0ZGRUVERUWFRIRFOEZHSEdGR0dISEhJSEhJSktKS0tKS0tKS0pJR0dISUdFRENFR0hGRkdGRURDQ0RGR0hHRkVEQ0NChkOFRAVDREVGRoRHEEhJS05RUE5MS0lJSEdISUqETChLS0pIRUZFSEpJSUdFRERCQUFAQEBDRUVERENDQkJCQ0RERUVEQkJEhkWDRIRDCEJBPz8+PT0+hT+FQIU/hT4qPDw9PT0+Pz8/Pj5AQkRGRUVGRUNCQ0ZHRkVFRkZHSEpKSUhIR0dIR0ZHhEgBSYRLF0pNT09NS0tNTE1NTU5PTk1NTk1LSUlJhUgMSUlKSkpLTEtMTExNhE4PTUxNTk9OTU1OUFFSU1RUhFMlVVVWV1pZV1ZVVVVWVFNTVFVWV1ZVVVVSUlRWWFxfX11eXlxcXoRgLl9eX2FiX15eXFtbWlxdXVxcW1paXF5fXV1fXl5eX2BhYmJna2ppZmVkYmJhYWGEYG9fXl5dXFtaXV9dX2BgYF5cXV5hZGVnaWlqb3FycXBvbm9vc3d/i5CNkJKNiYSFg4WHipCcmpqZlpiXkYuKjpWWmJmcn6Gho6yrsbS+zMy8ubW3v8CypaGgoqywr621wWFgvLZZWa+pp6+0u7q5v8MKyMfIyMrS1M/PaYRrgNTTa2rJv77AvrGqqa2vs7azs7e5uLi/wra2ubi4vMPJbW/dcnRx19LOy8rHwcPHy9BsbdPMx8TFwry7u7m0sLGzt7a1u8HGycfFwb/ByMnBt7Syrqmnq6+wsbfAzW5wb3J0cXBva2pra2hnz9DQ0M7NysXCwMHAwsLBv8HEwr28gLq0qqSio6SjoqGgoJ6eoKOmp6emoqCgm5SRkpOVn6qyq6axrJ6Vko6HgX9/fXx7enp5eHd3enh4d3l6foB7enp4eHd3eHl7fXx5e32BgoJ/enh2c3Fwb29xdHRzc3FxcXBxc3R0dXZ3d3h4eHl6enl5eHl6eXh6enh3dXR0dHV2HHV0dHRzcnNzc3R1dnNzcnFwb29vbGppaGlra2qGaQFqhGkPaGdkZGNkZGVmaGlqaWlphGqIaRJqa2xucHJ0c3N0dHNzdHR1dXSFdQV0dHV0dIR1L3NycnFxcXBub21ra2pqaWloZ2ZnaGdmZWVkY2JiYWJhYF9fX15dXVtbWltaWVhYiFcUWFhZWFhZWVlbW1xcXF1eXl9fXl2FXAVdXl1eXYZeGVxdXV5fYF9fYGFhYF9fXl5eXV1dXl9eXV6GYB9fXl1dXV5eXl9iZGZmZmVkYmJhYWNkY2JiYWBfYGBhiGAkYWFiY2NjYmJjZGRkZWRjZGZnZ2hoZ2dmZWZlZGNjZGRiYWFhhWIRY2NiYWBgYGJiY2NiYV9eXl2GXgdfXl5eX15fhGATYWFgYmJjZWdmZWRjY2JhYWJiY4RkH2NjY2JgYF9hYmFgX15dXVtaWlpbWVxeXV1cXFtbWlqGXAVZWVtbXIRbC1paWllZWllZWFZWhFWJVgFXhFaGVRlUU1RTVFVWVlZUVFZXWVtcXF1cWllZW1xbhVoOW11dXFxdXFtbWlpaW1uEXRxeXl1dX2BgXlxdXl5fX15fYF9eXV5eXFpaWllZhVoQW1tcXV5cXV5dXl9fYF9fXoRgBV9gYGFihWNSYmJjZGVmZ2hoZmVkZGVmZGRkZWVnZ2dmZmdlZGVnaWxubWtrbWxsbW9ub29ubm5wcW5ub25sa2trbGxqa2tqaWxub21tb2xtbnBxcHBxdXh3d4V0BXNzc3JyhHNtcnJxcHBxc3Jzc3V2dHN0dnl9fn+BgoOHioqJiYmIh4iMkJWgpKKnqKSfm5yZm5yeo6ysrKqpq6ukoJ+iqqutrrCxsrGyubvBwsjS0srJxsjNzsS5tLO1v8LAvcTNZ2bMyGNjwr29wcPKysrNzgn8/Pr5+v79/P+FgGv//4CA/vv6/Pn18/Lz8/X59vT19vX0+Pr18/b29vn8/4CA/oGBgP7+/Pv7+vn7+/7/gIH/+/v4+fj18vTz7+3u7/Dv8PP0+Pn7+/X3+v79+PPv7evp6Ovt7+/w9/2Bg4KEhoSEg4KCgoOBgIX/gP7++/r5/Pr8/Pv6+////fz58+jk4+Tm5ePg4N/e3+Pm5+bm5+Ti49/Y1dbX1+Ht8+zo8+3h2NXQy8fGxMG+vr29vLu5ury6ubi7vcDBvby6urq4tre4u768ubm8wMPDwb+6tbOwra2ur7K0s7Kwr6+vsLGysrOztLOysrO0tLSzQrSysbKysbKzsbCurKysra+vr66urKusraytr6+sqqqpqKenp6SioJ6foaCgn5+fnqCgoaCgn56enZucm5ubnJydnoWfRp2enp6bnJubnZ2en5+foaSlp6WkpaWlpqenqKimpKSlpqWlpqioqKmoqKinp6iop6elpKSjoaGgoJ+fn56dnZ2enp2cnJuEnAaamZmYl5iEmSyYl5aWlZaVlZWUlJSVlZaWlpWVlpeXmJiYmZqampuen56dm5mZmpqdnp2dnYaeBp2dnZ6foYugDZ+fnZ6hoqGipKKko6OHoiGjoqKkpqinpqanpqenp6ipqKempaalpaWmpqanpqamqKiFqSyqqaqrra2sq6urrq+wr7Cwr66traytraurq6yqqKenqKmqqqmqq6qpqamoqISpSainpqWkpaampaSlpaSkpKampqWmpqenp6ampaWmqKinp6ako6Gho6OkpaWko6OjoqGhoqGhoKCfnp6dnp2dnJuampucnJucnJyFmxqam5qal5iZmZmYl5eXlpaXlpWWl5eWlJOTk4WShJMNkpKTkpGSkpGRkpKRkYWQI5GTk5KQkZKSlJWUlZaUkpGRk5OSkZCRkpKSk5OSkZKRkpKRhpANj5CQkZCRk5SSkZGSkoSQBJOTkZCEjySNjY2MjI2NjI2MjY2Njo+Oj5CQkZKSkZGQkJGQkpKRkpKUlZWFlgOVlZeEmE6ZmJiXlpaYl5eXmJmZmZiWlpeXl5iZmpyfn52en56dnqCfn5+en6ChoZ+enpycnJucnp+enp2dnJ2foZ+go6CgoKGjoqKhpqqqqKenqKiEqjqpqausrK2uraurrK6wsLCys7S0srW4u72+wMPFx8vNzs3KysrLzNHV2uLm4uPm4t7c3dnf397g6OTjhOU14eHi5Orq6+ro6u3s7PHx9/b4/fz29fj7+/n29PPz8/j49/b4/YCA//+AgP/9+/z7/fz8/v6JgIWBBICAgYGcgAaBgYCBgYGLgIKBqoCOgf+A/4D/gP+A/4D/gP2ABoGBgICBgYqAAgIEAIBhXbi9trC2uWFnYr1hZ2lub3Rxv7GzsaymoKCip6agoq2xrqyvqKeqs7Kop7C6YGVqbGxs09bX187ExsTGyWt6gHK2r7e2t7i1saelsbS5ubOytbrC2XNw0cK5u763qK27tailqauvtb1ia3BxcnJwamlnZ2hqaWdlZWPGxMG8toCvsLK2sa+uqamtr65WqKWclJGQjo2Kh4aIh4eIi46Rj4uLjo6IgX17e3t6gI6YmJ1UVqihn5WIe3Nva2ZiYGBhX2FhX19eYGFlaGdlaGdlYmFhYWRpa2pmYWJlaGdmYF5cW1pYWFlaW1tbWlpYWFlaW1xcXV5fYWFhZGRlZGNiZS9nZ2doZ2NiYmJjYmNjYmJiY2JjY2NkY2VlYmFhYGBfXl1bWFZVVVVXWFhYWVdXWIVXFVZUUlFRUVBRU1VXWFlYWVlaWVpZWYVYIVlaW1xfYWNlZWRkZGViYmNjZGRjY2FfYWJhYmNkZGRjY4RiF2FiYV9eW1tZWVhYVVRUU1NSUlFSUlJRhFAFT05MS0qESQxIRURDQ0JBQkJBQkKEQQtAQEFBQkNERkdHRoRHA0lLS4RKFElGRkhIRUVFRkZGRURFRUVDREVGhEc6RkdHRkVFRUREQ0NEREVFR0lJSUhHSEdFRUNDREVGSUxMSkpJR0VFRENFR0hHRUZEQ0NDRERGRUVFRoRIhUceSEhJSEdHSElKS0xNTEtOTkxKS0tMS0tLSUVERENEhUUWREVEREVGSEhJRkRERENCQUJDQ0JDRIZFOUZHSElLS0pISEhKS0xMSkpJSUhHSUtLTE1NTExLSkhHR0hJSUpJSEdHSEhGRUNBQENFRkZISEdFRYRECkVEREVFRkdHRUWERg1FRURCQ0JBQD8/Pj4+jj8cPj9AQEFBQD4+Pj9AQEA/Pj9BQ0RGR0ZGRUVERYVGHkVGRkZISUhISElISUhHRkdGR0ZISUlJSktMTE5NTYVMG01LTFBRT09PTk1MS0xOTUxKSUlKSkpLS0xNTYZMDktMS0tKS0tMTE1OT1BPhFCAUVNUVVVXWlpZWVlaWFdXVlVVVldXWFpZVlNUVFRVWFlbW1pbWVlcXl5dW15eX2BgYF5bWVhZWlxdW1lcXV9gYV9eXF1gYmFhYWBfX2NnZ2ZlYmBcW1tcXl5eW1tbWltcXF1eYF9aWVtaX2BeXlxcXV1fX2BgZGhqb3uAg4OGjpNMk5SZmJORjIqLiouNjoySmJ2goJ2YlpealZKOkJKVlJqgo56iqaaoqbW3uLuvpKSvtreqpJ2foqastrteX11ZWVhYV1eurVleYmNkZYBoZcnMycTIyWdrac1obGxvcHRyysHExcC8tre4vLu1tr3Avbu+u7i7wsC6ucDHZmpsbWxs19fa3NLOz83Nz213enPEvcTCw8O/vLe0vL7Dwr++wcfN2XBv08nDxsnBt7zDu7S0uby/xMpmbXFycnJxbmtqamxubWppaWfPzszKxnnBwMHDwsK/vL3AwcBfvbqzqqWkoqGfnZudnZyeoKGlpqShpKSgmZOSkpOTmKexsLReYLu3taufkYuHg357enp6eHl4d3Z2d3h8fn17fXx6enl4eHyBhIJ8dnd9gICAeXd0c3JxcXFyc3NzcnFvcHBxcnNzdXZ2dnd4hHkSeHh6fH18fXx5d3V1dnZ4eHd2hHcHdnZ3d3p6doRzCXFwcG5ramloaYdrEmxra2tqampoZWRlZWVmZ2lqa4VtgmyIaxtsbm5xcnR1dXV2dnZ1dXR0c3R0dHN0dHRzc3WFdht1dXV2dnVycXBubWtqampoaGdnZ2ZmZmhnZWWEZBRjYmFgX19fYGBeXFtaW1paWllYWIVZElhZWVpaWltdXV5eXl9fX2BhYoRhCGBfXl9eXF1diF4EXV1eX4Zggl+FXhxdXV5eXl9gYmJjYmFhYF5fXl1dXmBjZWVkY2NihGCFYQNiYWCEXwdgYGFhY2NkhmMbZGNjY2JjY2RlZmdpamppaWhnZmdnaGdoZmVihGEBYoRhM2BgYWBhYmRlZWJhYWBfXl5fYF9eXl5fYGFgYGBhYmJiZGRjYWFhY2RkZGNjYmJhYGJkZIRlHmRjYmFhYWJhYWJhYF9fYGBeXVxcW11eXl1fYF9eXYhchF0SWltdXFxcW1tbWlpZWFdXVlZVhFYBVYdWXldXVlZXV1hXV1ZVVVVWVldXVlVWWFpbW11cXFtcW1tbXFxbWlpbW1tcXV1cXF5eXlxbWVpaW1taWlxcXV5fXl9fX15eXl1dXlxeYWNgX2BfX11cXl9fXl1cW1xdXV2K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R0ZERERCQkFBQEFBQUJEREVFRkdHSElJSkpKSUdGRkZISUpJSUlISEhJS4RMOUtLS0pJSEhISUpJSkpLS0lIR0ZEREVFRkdJSkdGRURFRUVEQ0RGRkVHSEdHRkZHRkVFRUREQ0I/P4U+gj+IPg4/QENCQkNCRERFQ0E/P4RABT9AQUJCiEQNRUVERUVFRERGSEhIR4VIhUkiSElJSUpKS09OTk5NTExLSUhMS0pLSkxOTU5OS0pKSk1OToZNBk5OT09OTYRMCklMTU1MTEtKTEyETS9OTk5NTk9RUVNVWFhWV1tgYWFfXFhYWFlbXFtaWlpZWVlYWlpZWFhZV1hYWllZWoRdKF5dXFlYWltbXV1bW11gYmFfYGFgX2BhY2VeVldeYmBfX2JeWVhZWViEWXFcXFxeX2JjYGBeW1pdX2FeXVxcXV1dW1xdYGVseoKLk5WWl5ubmZeUj46PkJCQkpGTkImKkJaepaSen5+hqaOcm5ygoJuampqcoJ+ipampqa+uqq2tr6mkoJ6cnqZVVq1XWFpbWllZV1hcXVtaXV9iZShqaGpoZGRnaGZmZWVnbHBxb21rZ2ZmY2JiYmNiwL67u76/ZWhra2dnh2iAa3B2eHl3e3x7d23Pz2hqb3V5cs3HxsjJysbGw76+vs/QzcXFyMrTbm3Uy7/Fw7i1vbq1tLO3vWJrcW9wcXJv1dNrbGxsbW5ua2ppZ2hoaGXKx8XEwcDDwby+v7u7vWG9tKmlpaShnpyampqYmJibnZucnJ2fnJmWkZGTlZigoKU5qKyxtba0saufkomAfHt8e3l3dnV1dXR2d3h7eXh5enl7eXl6fX99eHd2eX17e3p3dHNzc3JycXJyhHE9cHBwcXJycnR0dXZ3enp6eXl7fX+BgH9+e3d3eHd3eHh5eHl5eHh6e3p8fHx6eXd2d3d1c3JvbGtqa2tsbYVsJG1sbGpqamlpZ2hoZ2ZnaGpsbW5ucHBubm1sbm1tbW5wcHFzdIR2Gnd2dXV2dHRzc3Nyc3N0c3N0dnZ4eXh3d3Z2hHcTdXJxb2tqa2tqaWlpaGhnZmdnZ4VmBmRjYmJhYIRfBF5eXV2FXARbWlpahlkbWlpbXF1dXl5eX2BhYWJjYmNkZGNkZGNjYWBghV8HYF5eXV5eXoZfA2BgX4RgHWFhX19fYGFgYWJiY2JhYWJiYWBfYGJkZWRh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Y2BeXV1bWoVZCVpbXFtbWlpZVoRXSlZVVFRWVlhaW1pbXF5fX19dXFxdXl9fYGFjZWhpaGdnZmVkY2VkYmFhYF9eX19gYGJkZ2hpaWZnaGVkY2JiYF9cWVdXV1VWVldVhlMOUlFQUFBPTk1LSkpKSUmESAFHiUaERQFEhEMFREVGRkaFSE5JSktMTk9OTU5OTU1LSUlIR0hHR0hISEdGRENERENCQkNERUdHSUlKSUtLSklISEdGR0tMS0pLSkhIS09QTEtKR0VDQ0RERENDQkNERUWFRhtHSEdGRkdJTEpKTExJSEdHRkdHR0hISUpKSUuETSROTE1OTU1LR0ZGRUVERkdKSEVDQ0NERURFREVERENERERDQ0KEQQFChEMbRUVFRkdISUpJR0ZFQ0NERkZISEdHSElJSElIhkotSElJSUpLS0pJSUtKSkpJR0ZHSEdISktJSEhHR0ZGR0hISUpJSEdISUZGSEhHhEUDREJAiD8MPj4/P0A/Pj4/QEJEhEMJRUZFQkJBQEBAhj8YQD8/QD8/QUJDQ0NBQkNFRUVGRkhISkxLhEoBS4VMPktLS0xNTk9OTk1KSUpJSUtLSUpJSkpKS0pJSEhJS0xNTk9QT05NTExOT09OTk5NTEtNTUxPUE9PS0lLS0pJhUoaTVBQUlNUU1NVWV5hY2RjYFtaW11eXFteXl6EX4BbW1taWFdXWFlZWFlaW1xdXFpZV1hZW1xeXVtdX2JjYF5fYGFeXl9cWFdXWltaWltdWlVUUlJTV1hYWVpcW1xeXmFjYF9gXl1dXmFhYGNkZGNjZGdnbW93g4SGh4WIjJKRjo6MjIyRlpmWlpaZnJqWl5aaoKOmqKqqrqujoaaqqy2qqKGeoqWfnZydn5+iqFarqaSlo6GioqGipVRWV1leYF5fY2RrcG5lYGBiYmJwa2xsaWdpaWlnZmVnaGtsa2ppbGxramlmY2FiY2JgYGJpcHmAfHVtcXdzcm9rbnByeYGChYODgn97cmloam9ycnJnaGpqa25u1dTSz87Mzs3QzMzJxtDSysa+usPGvri8u728srbAxWhqbG1ubWpp1oVrgG1ta2pqaGdpamlmyWTFw8DAv7+/vLu9vru2r6mmpqeinp2bmZaVlJWXmZmam5ycmpWSj4+RkZOamZuhpKarrq+yp52Vi4B9enl4d3h3dnV1dHR0dXR1dHV1dXh6eXuCgYB/fnx6fHt3dnV1dHN0dHRzc3NycnJxcXBwcXFyc3V1HXd3enp7e3p7fX6BgoB+e3p6enl4d3Z5ent6ent8hX4SfXx6eHh4d3VzcW9ubWxsbW1uhW8Dbm1shmopaWhnaGlrbW5sbW5wcXFxcG9vcG9wcXN0dnd7e3l5eXh3dnZ3dnRzdHOFchZzc3V4eXl5eHl5d3d1dXR0cm9saWpqhGkJaGdoaGhnZmVkhWUHY2JhX19eYIRfgl6EXQteXV1dXF1cXFtbW4RcA11eXoRfcGBhYmJiY2RkY2RlZGVjY2JiYWFgYGBhYWBfXl1eXl5dXl5fX2BiY2NjYmNjYmFgYWFgYGNkYmNjY2JiZGdoZWRjYV9fX2BgX19fXl5fYGFhYmFhYWJiYmFiY2RmZGVmZmRjY2NiY2NjZGVmZ2dmaGqEaQhoaWtqamhlY4RiC2NlZ2ViYWBgYWJhhGIRY2JhYWBgYF5dXV1eXl1eX2CEYQ1iYmNjY2JhYF5eXl9ghGEHYmJiYWFhYoVjhWIMY2NhYWFjYmFiYV9ehF8DYWFghF+EXhdfYGFfXl5eX11dXl5dW1xbXFtaWVhXV4RWDFdWVldXV1ZWVldXWIVaDFxdW1lZWVhXV1ZWVodXCVZVV1paWVlYWYZaBF1dXl+FXhFdXl5dXV5dXFxdX2BhYGBfXoRcA11dXIddhFwQXl9fX2BgYV9eXV5fX2BgYIRfgGBfYGFhYWBfXl5eXVxdXF1eXWBiYmNjZGNkZmlsb3Fzcm9samtsbWtqbW1sbWxsbWxqamppaWhqamloamtrbW5tamloa2tsa2xsam1wcXFvbW9wcW9ub21qaGlra2tsbnBva2pqamttbm9wb3BxcnR1dnd3dnZ1dHV2eXl5e3x8XXx+foGBhYeQm5qdnZujp6qop6inpqWprLGurKuusa+pqayvs7e6vcG/w722tLm7vLu6tbS3urWysbK0tLi9YL+9ubu8ubq6ubu/YWJiY2hqaGpubnFzcW1qaWpsbCeHh4eGhYaGhYWFhIWFh4iHh4eGhYSFhYOBgoODgoCAgIOHiImIhYSFhkWFh4iIiYmIioqKiYmHhYGAgYOFhYSAgIGBgYKB/////f38/f79/Pv5+vr8/Pz5+Pv79vP39/X09PX7/4GBgoKCgYCA/4CEgnODhYSDgoGAgoKBgP+A/vv6+/v9/vv6+/z49O7n5Obm4dvb29rX1tXW2dvb3uDg4d/a1NHT1tfY3tzf6Ovs8fP19u7j2tHIw7+7urm6urm4t7e4trW2trW0s7O2uLm7wsPDwsC+u7y7uLe2tra1tLSzs7SzhLImsbCwsbKys7S0tba4uLm4t7i5u7y8u7i2tba1s7OysbOztLOztLaEuBS2tLOysK6urKyrqqinp6WkpaWlpoWnWKSjoqGhoKCfn56enp+io6OjpKSkpaaopaSkpaSmpqipqqmtrKqsrKyqq6qrqainqKimpaamp6ipq6ytrq6traysrayrrKqopKOhoaGgoaGgoJ+foJ+gn5+GngadnZybmpqGm4maAZmEmgGZhJqEmwScnZ+fhJ4Rn6Gjo6OioqGioqKhoKGhoaCFoTSgoKGioJ+goKKjpaSkpKWlp6empqanp6amp6emp6mop6ioqKmpqaqpqKaoqaiopaemqKeohKkCqqmEqgirq62vrK2uroarFqyrrK6wsrGvs7W1srGysrO1s7OxrayEqxesrK2sqqqqqaqqqaqqqqmqqqqpqqmoqIemCKenqKioqamphKoXqaempqWlpqempaWmpqWkpaWnpqWlpKOGpGOjoaGho6GhoqGfnp+gn5+eoJ6en5+fnp2dnJ6enp2dm5ybnJudnJuZmpqam5qXlJWWlZWUlZWVlJWVlZOTkpKTlJaVlZaWmJiWlZWVlJSSk5KSkpOTk5KSk5KRkpSTk5OSkpKEky6SkpKUlZSTk5SUk5OTkZGSkpKTkpKSlJWVlJKRkZCQkZGPkJCQj5CRkZCRkZCQhJIGk5ORj4+RiZIqk5OTlJSVlJGQkZGSkpOSk5ORlJWVlpeYlpeYmp2foKGhnpubnJ2em5mehp+Am5ucnJucnJ2dnJucnZ2en6Cfnpudnp+foJ+en6Cio6OhoqOjoqGio6ChoaGioqOlpqWkpaWlpqepqqqrrKytr7CwsrO0t7W1trW3uLa3uru9v8DDw8fK0Nvb3t/d5Onu7evr6+rp7fHz7+7t8PHv6+ru7fL19/f5+vr19fL3+Pgo9fPy8vj89vTz9Pb2+v6A/vz5+vn8/v/+/f+BgoGBg4SEh4mIh4eIhoSFAYbIgZ+AiIEBgJCBAoCB/4D/gP+A/4D/gP+A5YABgYuAk4ECAgQAgGFhYmBeXl1eX2BiYV5fYGBgY2hvcGxmZGBdW1hYWF9sdXqAhX90cnd6c2lkYWFpbH6LjY6GgoB7eHduZGpzgXVmY2dyd3l8goR8bs7J1nh7cdHSyshoZ8W7tbO6wbSrssG1t6ywr7O7wMNkZ2dnam9vbGpoaGlnZGRjYmNlZWNgRF24s66tr7CysKioqKehnZiVlZWWkoqFhIKAf4GBgoWEf3+ChYJ/fHl5e32ChoeHhoKDjZiXmo+CeXBpZGFfXl5dXFxchF0BXIVdVF5eZGt3eXd1dHNuaWViYGJjY2NgX15eXV1cWlxcW1tbWlpaW1xeYGJkZ2doZ2dna25vbWpmZmVmZGNhYGFkZWRmZ2pramtra2poZmVkZGRjYV5eXoRdH1tcXV9eXVxbXFxbWFhWVldVVFRVWFlZWlpZWltcXV2EXjhfYGFhYmNjZWlrbGpra2lnZmZkZGRiYmFiYGFhYWRlZmZmaGpta2hmZGFfW1pZWVZWVFNTVFVUU4RSFFFRT1BQT09OTkxLS0pJSkpJSElJikiDRodFDkZGSEdGR0dISktLTExMhU5LTEtKSUlKSUhISElKSUhISUlIR0ZHR0hJSEdISUtMTlBOTEtJSUlKTE9QTk1LSUlMUE5KSEhHRUNEREVERkZFREVERklHRkdJSktIhEY7SEtNTU1MTEtKSEhHSEhJSUpMS0xOTUpMTUtNTk5OS0dGR0hISUpMTUpEQ0NERENDRERFRURFRUVERESEQ1pEQ0NCQUBCRUVFRkdJSUlIRkNCQkFCRkhGRkRFRkZHR0hISElISUlKSkpJSktMTEtMTU1LTEtKSUhJSkxPT05KSUlISElJSktLSklHR0dISEdISEhHRURFREGFPxxAQUBAPz9BQUJCQEBBQkJCQ0JERUZGRkRDQkFAhD+FPjc9PT0/QUFBQkFBQUJDREVFRkZJSUlKSUpJSEpMTU5PT09OTUxPT09OTU9NSkhJSElJSktOTkxMhUsHSk1OTUxNTIVLdkxOT1BPT05OTkxMTU9RUFFPT09NTElJSktLSktNTk9RVVVUVlpdXl5eXV1dXl1fXl9fXV5hY2JiY2FhXl5eXVlaWllaVldZWltdWlhZXV9cXV9hYWFgYF9jZmNiX1xcW1xdXF1cW1tgY15UU1FRUFBRVVdXWFiEWmxZXl9fXl9gX2BhYmFiZWVmamtqa2tueIB8e3x9eX+AgoGCg4CChIaOlJOPkJWYmJuamZmZn6Kmpqmqp6OkpqmzW7a1tqmgnJqcnZ+hoJ+jU1VYV1hWVlKjpFNXWV9jZmZrcHR1eXRvZGRlY2OAamtsa2ppaGlqamxraGlqaGdqbXJzcG1samhmY2NiaG91eX2BfHV0d3l1b2tpaG5xfYWHh4F+fHp4d3BpbXR7dGpoa3N1d3h8fnhu0s3XdXdw1tbRzmtpy8bAwMXJwrm/y8HCur+/wsjLzWlqamptcHBubWxrbWxqamppaWtsamaAZMfDwL/Bw8TCu7q8u7WxrKqpqKmnoZuZmJaVlZWXm5uYlpibmJaSj4+PkZibm5uZlpqkrayto5qQhn99e3l4eHd2dnZ1dHV1dHRzc3N0dXV8g42SkY6KiYR/fHp5eXp6enh3d3d2dXNyc3NycnFxcXJzdHV3eXp8fX59fH5/gYIjgYB9e3p6eXl3dXd6e3p7fH+AgIKAgH9+fHp5enh3dnNycnGEcCFxcnJycG9vb21sbGxra2tqaWlpamxsbW5tbW5wcXBwcXGEchRzdXZ3eHt9fXx8fHp5eXh3d3d2dIVzN3R2eHl5eXp8fnx6eHd2c29tbGxpaWhnZ2hoaGdmZ2dmZmVlZmZkZGNjYmJiYWBgYF9fYF9eXl6HXwZeXl1dXlyGXQxeXl5gX2BhYmJkZGSFZQpkZGRjY2NiYWFhhGIVYWJiYWBfYGFjYmJhYmNiY2VmZWRjhGJVZGZnZmZlY2FmZ2ZkY2JhX15gYWFgYWFgX2BgYmRhYGFjZWZkY2FjY2NlZ2lpaGhnZWVkY2RkZWZoamlpa2pnaWlpamxsamdkYmNlZWVmaGhmY2JgYIVhBGJiYWKFYQFfiV4CX2GEYCBhYmNiYF5dXl5eYGFgYF9gYWBgYGJhYWJhYmJiY2NiYoVjFGRjYWNjYWBgX2FjZWVkYmFhYF9ghmEDX15ehl8MXlxcXVtZWFlZWFhXhVgGWVpZWllZhVoLWVpbXFxcW1pZWFiEVwpWVlZVVlZUU1VXhlg3WVlaWltcXF1dXV5dXVxbXV5fX19gYF9fX2FiX19fYWBcW1xcXFtdXV9fX15eXV1dXl5fX19eX4deAl9ghGERYGBfX19gYWJiYV9gYF9cW1yEXgpgYWNjZWVmaGtsh22AbG1tbm9tbm9wb29xb29tbGxsamtqampnaGlrbW5qaGptbmxtb3FycnJxcHJzcXBvbm5sbW5tbWxsbW9yb2ppaWloZ2hrbW1ubm9wcnFvdHZ3dXZ3dXZ5enl6fX1+hISCgoKFkJiUlJOVkpiYmpmam5qcnZ+mq6upqa2ura6urq85sbS3wL6/vrq4uLq9xmPHxse+tbKytLW2uLi3vF9gYmFiYWBevL5gYmNna25ucHV4eHp3dG1tbm1sAoaHhIhlh4iJiYqIhoaHh4aHiImKiIaGhIWEhIWEhoiJiYqKioiIiYmJhoSEhYiIi4yMjYyKiYmIiIWDhIWKh4KBgoSFhYSFiIWB//3/gYOB//37+4CA//z7/P3//Pb6/ff58/X1+vz9/4GEgoCBg4OCgoKEg4SDg4KCg4SDgYD//Pn6/P3//vr6+fbx7unm6Orr59/c29jX1dfZ297e29vc3tza1dHR0tXa3Nzb29rf6vTz8+vf1s3Ewb67urq5uLm4tra4uLe2tba2tba1usTO09LRz83HwL27ubq8u7y7uri4t7azsrS0srKys0ixsrO0tbW2t7i6u7q5ury9vry5t7e1trW0srGxs7W0tba6vLu8u7u5t7Sys7Oxr66rq6uqqamop6ipqamnpqampaSjoqCgoaGEoE6io6Sko6Slpaalp6akpaanp6ipqqysrrCwr66vrq2trauqq6mop6emp6ioqqutrK2usbWzsK+sqamopqWloqGgn5+goZ+enqCfn6Cfn5+FnhGdnZ2cm5ucm5udnZydnZ2cnIibBZqampubhZwKm56fn6ChoKGhooWkAqKhhaIjoaKioqGioqKjpKKioqOjpaampaSlpqepqqqpqKanp6emqKmFqBWpqqqpp6eop6epqainqKmpqKmoqauEqjarrKusrayrq6ytr7Cvr6+tra2sra2vsLO1s7S2tbKzs7O1tre1sq6sq6ytra6wsK6sq6yrrayEqwmqrKyrqaipqamGqB6mpaanqqmpqaqrq6qrqaenpaOkpqinpqSlpqSlpaWEpAOmpqWGpiClpaSkoqChoaKgoKChoaKio6GgoJ6enZ6foaGfnp2cnISdAp6chZtJmpeXlpaWlZWWlpaVl5iXmJaVl5eXlpWVlpeYl5eWlpWUlJOTlJSSk5OSkZGQkJKTk5OUk5OTkpOTlJSVlZOVlZaVlZSSk5SUk4WUJ5KVlZSUk5SUkpCRj4+PkJCRkpKRkZGSkZKRkpOSkJCRkJCQj5CRkYaTDZSSkpOTlJSVlJSUk5OEkhuRkZKUlJSWmZmXmZyen56enZ2dn56enZ6enZ+EoIChnp6dnZ6fnJ6dnZ2bm5ucnZ6enJyfoJ+goaKko6KioKKmo6KioaKhoqKio6GhoqappqKjo6Sjo6Soqampqqutra2ssLO0s7S1s7O2ubi6vb6+w8PCxMXJ0trX1tjY1Nvc4N/f4uDg4uHk6+vq7e7t7u/u7e7w8/b8/v///fr6+y78/4D+/v/8+Pf1+Pn6+/v7/oGBgoGCgYKA//+Bg4SDhIaGh4uKiouJioeIiYeHy4EGgICAgYGBhICCgZOAl4H/gP+A/4D/gP+A/4DZgAGBjoCIgYKAk4ECAgQAgGFhYGBhYF9iYmBhYWBfXV5hZW11e4F+cGRhX1tcWlxkam5xbm52fHVuaWpramNhZm55g4SBfXZ2d3RtbGxzeW1pcHp9goSGhoqCcnZ45Hd8c21tbNXIw8VnY7u8wry0uLvFuLW7tMFozmFkaWxucHJzcnBtamdnZGNiY2ZmZF+5gLSysVmxsFlZrKeioJ2bm5eVlZiWk5KLiYeDgYB/f39+fX2AgH9+enl7f4ODhY2MhoB/gYqKiYR6cWxoZWJhX15cWlpcXV5cW1tbXF1eX19haW94foB+dXBtamdnaGdmZmVjYmBfXlxbWFlaWltbWltbW11fYWNlZmdoZ2Zoa21tLmtpaGhmZWNiYGFiYmVmZ2tsa2tsbGxqampnZWRjYmJgX19hYmNiYWNjYl9dXVyGWwxaWFdWVldYWVlaWluEXBtdXV5eX19gYWFjZWdoaWxtbW1sa2poZ2ZmZmSEZTNkY2JjY2VkZGRmaGhkY2NhYF1aWFZVUlNTU1RUVFNSUlNSUlNSUVBRUU9OTUtLSkpKSUmFSARHR0hHhEgGR0dGRUZGhkUvRkhHR0dISElJSkxMTE1PT05OTUxLS0pKSklKSklJSUpLSklKSklKS0tKSUlJSkuFTShMS0tLTlNRTUxMTU9TT0pJSkpJR0VFRkZGR0lJSEdHSUlISEpIR0pJhEgWR0hMT09PTk5MS0tKSUlKS0xOT01OToRLGE1NTUpJSEZJSUlLTE1NSUZERENDREVGRoRFL0REQ0RDQ0NERUZFRENCQkJDRERFRkZFRUZGRUZFQkFBREREQ0REREVFR0lISEdIhkkISkxOT1BPT06ETRhLSktMTUxLSElKSUhHR0dISUlHR0VHSEeERgpFRENCQUBAP0BAhUEGQkFBQ0REiEMKRENERUZGRENCQYVABT8+PD4+hT8tQEFBQkJDQ0VGR0dHSEhJSktMTEpMTE1PUFBRUVFOTVBPUFBRUE5MSUlOTk5NhU44T05NTE9PTU1MSklKS0tKSklKS01OTk5NTk5NTlBSUVJQUE9QTk5PT09OTk5PT1BRVFZXWVlaWlqEW4BcXl5eX15fX2FjYWBhYWNgXl5eXFxbWldWV1tcXGBeXFtcXV5hYWJiYWFhZGVnZ2NfXFxdXl5eXF1gYmFfXVdVU1JSUVFSU1VVVlZXVVRWWFtdXmJkY2NiY2Rna2xqbGxrbW9ua21wcnN1enl3dXd4eXt5en6GkI6Hh4qPlZWXnxeYk5SWm52eo6Wjo6amqFVZr6qppKKdm4aeHVBRUlZYW11YV1dVV1xhZWdqbG5xb25xaGFfX2BggGtra2prampsbGpra2tpaGdqbXJ4e358dG1saWVlY2Vrb3F0cnJ2eXVxbW5wbmlna3F4f4B9e3Z2d3Vwbm52eXFscXd6fX5/f4F7cHN14XN3cW1ubdjNysxoZsbHzMfAxcbLwcHHxMxp02VpbW9wcnJycXBwbmtsa2lpamtsa2fJaMTCwWHBwmJjv7q1s7Kwr6qpqaqop6ein52ZlZWUlJeWlZSVmJaUkY+RlpmanJ+empaWmKGhoJyRh4J/fXx6eHZ0c3NzdXV0dHV0c3N1dnd5gIiOk5aUjYeDgX59f35+fnx7enp5d3VzhHGFclFzdHZ4enx8fn59fH+Bg4KAf318enl4eHZ2eHl7fH2AgYGBg4OBf359fHt6eHZ3dXRzc3R2dXR2dnVzcXFwb21tbm5tbWtrampqa2xrbG1ub2+EcQ1ycnJzc3V3d3h5eXp9hH4JfHt7enl5eXd3hHYEdXV1doR3EHh5eXd3eHZ0cW5sa2poaGeFaIVnDGhoZ2ZmZmVlZGNjYoRhBF9eX1+EYAZfX15fX16FXQFchV0EXl9gYYVgAWKEZIhlE2RkY2NjYmFiY2NkY2JjYmNkZGSEYi5jZGVmZWZmZmVlZGVpaGZlZWRnamhlZGRkY2FhYmJiYWJkZGJhYWRkY2JjY2NlhmQCZWaEag9pZ2ZnZ2ZmZmdpa2tqa2qEaAdrbGxo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YWJiY2RlZ2hqa21samtqamlqamlpaWhnZWZlZ2VlZWZlY2RkZGVkZGFfX15eXVpXVlRSUlKFVAZVVVRTVFSEUhBQUE9OTU1NS0tKSkpLSkhJhEgaRkdIR0ZGRURFRURFRURERUVHR0hKSkpLTEyHTQVOTk1NTYRLAkxLhEwlSkpJSk1OTUtKS0xMS0xLTEtLSkxMTEtLS0pJSk1PUVFOS0tMTIRKQEtMTEtNTUxLSkxPTk1NS0pLSUhHR0hISEpMTk9PTktMS0pISElJTE5NTU5NS0pKTU5PS0lKSUlKSklJSkhJSEWERAtFR0hIR0ZFRUVEQ4lCMUNBQUFCRENDQ0VERURERENDQkJCQ0RDQ0JDRERFRkhIR0VGRkhJSUhJTE1OT09OTk6ETSNLSkpMTU1MSkpJS0pKSUdGRkhHR0ZFREVGRUVFRERDQ0FAQIZBS0JCQUFBQkNDQ0RDQ0JDRENDQkFCQ0VFRENDQ0JBQUJCQkREQkE/Pj8/QEFCQkNEREZHSkpJSUpNT09PTk1PT1BOT09QUFBPT1FSUIRPPU1NT1JUUlFQUVJRUFFQT05PTkxKSUpKTE1LSUlJSktLTExNTEpLTUxOUVFQT1FSUU9OT1FSUlNRUVFTV1iEV4BWV1dXVllbXF1eYGNjZGRlZmVjY2NkY2BfX19eXl1bWlxcWlpaW1taWFtcXV1dYGJiY2JiYmFlam9sa2dlZmNhYWBgXFpZV1ZUUlJTU1JTVFRTUlJRUVJWWV9iZWRlZGNiZGtramlnZWVlZGRmaWtvdHd7e3t5enp7f3+AiY6Pj0GcmZKRkpKUkpOVm6GblJOVmZqipaNUVlSoVFJRn5ufUKCfn56cnZ2eUFRaXFtaWVlbYGFhYGJpaGVnZ2NeX2FjZYBwb25ubmxra2xnZmdqbGpsbWxxfH6Af3FoZWdpZ2RiY2Rpam56hImGf3t1cW5ub3FvdXx3dnh8fYCCfnl0cW1ueoySi4F9fHt6d3R0dXRxdHJucnl6b9JqcG5oycvPzcfGys7MysXLbGppbG9ub3N2d3Z1cW5sa2praWlqamtnZh1lxcDBwmJkY722sKyqqqqnqaimpKOgn5uXlZWUlIWTRpGSkpCOjpKWmJqdmZaUkpGOkZGQjo2Lhn98enp4dXV0dnZ2dXV0dHR3en2Bh4OFkJiRjpCMh4OBgH+Bfnx7enl4eHh3dnSFcoVzMHV3ent7fHx9fXyAgoF+fn18fHl4d3Z0dnh7fX6Bg4OCgYGCgoGAf39+enl4d3d3eIV5G3h3dnVycnFvbm5tbm5ubWxtbGxqa2xtbnBwcIVyEHN1dXV3enp6e31+f359f3+EfA97enp6eXh5eHh3eHh4d3aEdxl2dnVzc3Fwb25tbGpnZ2doaGhpaWppZ2dohWcHZmZlZGNiYoRhAWCEYQdgYF9fX15fhF4XXV1dXF1dXF5eX19fYGFhYmJjY2VkY2OEZBdlZWZlZWRlZGRkZWVkZGNjYmJlZmVkY4ZkL2ZlZWVmZWVlZGVkY2NkZmlqZ2RkZWZkZGRjZGZlZWdoZ2VjZGdnZWRkZGVlZGRkhGUIZ2hpampoaGeFZh9pa2pqa2ppaGhqbGxpaGdmZWZnZmZmZWRkYmJhYWJjhmQCYmGHXwteX15eXl9fXl9gYIVfA2BfX4ReAV2FXhhfYF9eX2BhYV9gYGFhYmJkZWVmZ2ZmZmWEZANjYmOEZBpiYmFiYWFgX15fYF9eXl9eX19eXV1dXFxcW4lahVmFWzZaWltcW1tbWltaXF1cW1paWVhZWlpaW1tZWFdWV1dYWFlZWlpaW1xeXl1dX2FhYWNjYmFhYWCEYWxjYWBhYmFgYF9gXl9gYmNiYWBiY2JiYmBfYGFgX1xdXl5fX15cXl5dXl5eX19fXl9fYGJiY2NiY2NjYmFjY2NkZGNiYWRnaGhoaWhnZ2doaGpqa2tsb3FxcnJyc3JxcHFxcG5tbW1sbG1raWqFaxhsamhrbG1ubnBxcHBxcnJwcnZ7d3d0cnKFcQxubWxra2loaGppaGqFa21qamttb3R2eXl5e3l4eoGBgIB/fn59fX1/goWIjY+SkpOTlJWWmJeYn6KlprSyqqeqqa2oqKyyubOsrK+xs7q8u2BiX71fXVy4trldu7m5uLW2t7leYWVmZGRjZGVpamppbHBvbW9ubGlqbG1vAYmEioSLBIiHiImGimKLjo6MiIeFh4eGhIOEg4SEhImMjoyLi4mIhoeHiIeJi4mJiouLjY6MiYiIhoeKj4+NioiGh4aEhISDgoKCgYCBhISA/4CCg4H8+v7//fz6/Pv7+/6AgICDhoeIh4eGhYWEhYmEgIKBgP38/f2AgYL79e/q5+jp5ejo6OXj4ODd2tjX1tbV1dbY2tnY1tLT1NfY2dnc2NfX19XT2NnX1tPQy8TBv728ubq6vLy8urm3tra7vsDEy8nL1NrU0dPPycbEw8LBwL6+vLu7u7y6uLa0s7Kzs7S0s7OztLa5ubq6uru7vLy9gL28u7m5uLa1sbKztLa4u7y8vb6+vb29vLy7uri2tbOysLCwsbKxsLGxsa6trKmop6alpaWmpqWko6WkpaWjo6Omp6inp6eoqqiprKuqra6ur66vsLOysbKysbGvsLCvr66tra6urqysra2trK2trKuqrKqpqqmpp6elpKKhoaGgHKCgoaGgoJ+foKCfn5+gn5+goJ+en56enp2dnZ6EnQienp6dnp2cnYScHJqbnJ2cnp6fn6ChoaChoqGioaGioqOjpKWlp6iFphClpqalpqWmp6epqqqopqanhKkJqqqpqauqqKiohakgqqutraqqq6yqq6uqrK6tra6vrq2srK6tra6tra6tra2ErkSxsrCwsrKwsbCwsK+wsLS3tre3trSztLe4uLWysbCvsLGwsLCvsLCurKytra6vr6+urq6trauqqqurq6mpqaioqKenp4WoDKmpqqqpqaioqKelpoSnEaalpaamp6empKWkpaampaWnhKgppaalpKSjo6Sko6OipKSio6KkoaKioJ6foKCfn56dnp+dnJ2dnZubmpmEmhyZmZiZmZiXl5eYmZiZmZmYmJmYl5eWl5eZmZiXhZZOlZaWmJiWlJOSk5OVlZWUlJSTlJSWlpWTl5mYmJmZl5aWl5WWlZWVlpaXl5eVlJSUlpORk5WWlJOUlJWUlJWUkpGVlJOQj5GRkpKQkJKThJIhk5OTkpOTk5WVlpWVlpeWlZSWmJiZm5eWlpmbmpmZm5ybhJoInJ6en5+goaCEoRqgoKKhoaCen5+enZ2enZucm5ycnJ2dnZyen4SggKGioqSlpaSkp6qoqainp6alpKWmo6Kjo6Wko6OmpqWmp6ioqamrq6yurrG0uLe3t7i4usDBwL++wsLCwMDBxcnM0dLT1NfY2tra3d3c4+Xm6fPv7Ozt7fDs7fD1+/bx8PH09/z+/oGDgP+AgID//f+A//7+/v3+//+AgYOEhIOCA4GDhYSGC4iJiImJiYeIiYmJ1oEBgISBjICagYSAg4H/gP+A/4D/gP+A/4DSgAuBgYGAgYGBgICAgYiAmYECAgQAeWdoaWhmYmJgX1xaW1xeXl5hYmRpb3t8dm1gYWNhW1dWV1lfaXiBgYeDendzcGhobnJ0d32BhYuRl5mWl4uCeXB5iZGOgoaSkYeGgHZudXyJjI2CfoODeW9wbGfFwsjEvbSwwcbBubxiyMRpcHBtcHd6fHhwaWJgY2WEZA9lZWBcXFxZWlpcsaOcl5WEliyUj42KiIeFg4KAfn9/f35/gYJ9fHt7enp8f3+ChH96eXl3dHRzdXRzcW1pZYRigGFiY2NfX2BhYmNkZ2psb3R5gZCGe3NwbmpoaWpra2hkYmFfXmBgXl1cW1laW11dW1xdX2BjZWZnZ2ZmaGpramhlZGRiYF9dXmBiZmpsbW5vbm5vbnBwcHFxcW1saGdmZmdoamppaWloZ2VjYV9eXl1dXF5dXVxaWFlZWFlZW11ehV8kYWJjZGZlZmhoaWtrbW5tbGtrbGxramlpamtoZ2dmZmdmZmdnhWYWZ2ViX15eXVtbW1lWVFJSU1RWV1dWVoRVHFRUVFNTUlBPT05MSkpKSUpLTExMS0pKSklJSEiFRwtIR0ZHR0ZGRkVISIRJKEpLTE5OTkxMTE5OTE1NTExLS0xLTExLS0tNTEtMTU5PUE9OT09PTk2ESxFMTUtKSUhHSUxNTk5OTEtLTIRLAU2ETh1QT05OTlBQUU1KSkpHRUNFR0dJTE1LTEtLS01OSYVIGkpKTE1LSUpLTU9PTUtKS0pJSUdGSEhHRkVFhEYdR0hJSEZGRkREQkFBQkJCQUBAPz5AQUA/Pj9BQkSHQyREREJDQkNDQkNERENDRUZFQ0RGR0dISEpLTExOT09QUFFQT0+ETh9PT01MSklHSEdGRUZFRUVERENCQ0RERURERUZEQkBAhUELQkJBQUFAQUJCQ0OEQgpFRENCQkFERkdGhEWERBpGRkVFQj8/Pj49P0FCQkJDREZJSkpKS0xPUIRPC05PTk1QTlBST05OhU8cUFFSUlJUVFNTU1JPT05NTU5PTEtJSklJSktNTIVKCElKSktMTE5QhFEgUlNSUE9PT05RVFVUVFZZWVhYVllbWFdWV1hYV1leYGCEY4BlZWZmZGVjY19dXWBfYWNlYF1bWltbWVdYVVVXV1haX2NkZF1XWGBobm9wcWxnaWhiYF5cWVdVU1JQU1NVVFNSUVJSU1NUU1NTVVdaX2FjY2ZjYWJlYWBgYGJjYmNkZmdoa29zdHh6fH1+goWFipOcoKOpo5CMkZKRlZianp6YlTKUlZido6RTWFhYVVNTU1FSU1NSUVBPnJ2enE9SVlhYWltbWlhWVldaWlpdYmJiZ2lpaIRwgG5tbWtraWZnaGloaGprbXBze3t3cWZoa2plYWBhYmdveH1+gYB5dnVzbm1xcnV4e3+BhIiLjYyNhH55c3iCh4Z9f4iHgH97c25zeICCg3t5fX12b29taczKz8zIwcDLzsrFx2bQy2txcW9xdXd5d3BraGdpampqaWlra2djY2NhgGJiY8G3rqqnqKinp6ahn56dnZuZl5WUlJWVlpaYmpWTkZGQkZSWlZeYlJCQkI+MjYyNi4uIg398eHh5enl6fHt4d3l5enp8f4KFiIqRm6mek4uHhIF/f3+BgoB9e3h3dnd4dnRzc3Jyc3V1dHV2d3h6e3x7e3t8fX9/fn18fHt4EXd2c3V1d3t+gICDhIODg4KEhINBgoCAfXt6e3t7fHx7fHx7end2dXNycHBxcHFxcXBvbm5tbWxsbnFycnJzc3N0dXV2d3Z4ent7fX1+f31+f359fXyEewZ8e3p7enqLeQV3dHNycoRwDW5saWhnZmdpamtqammEaB1paGhnZ2RkZGNiYmJhYWBgYWJiYWBgYF9gYF9fXoVfAV6FXxRdYGBgYWFhYmNjZWVlZGRkZWVkZIdlgmaEZYBkYmNlZmdnZ2VmZmdlZWZmZWZmZmVlZGNiYmRlZ2dmZWRlZmVlZWRnaGdnZ2lpaGdnaGlqZ2VmZ2ViYWNlZGVoaWhpaGhnaGllZWVmZmZnaGpraWhoaWtsbGppaGhnZ2dlZGZlZGNiYmNiY2NkZWVmZGRiYWBfX19gX15dXV1cXAJdXoRdgl6FXwZeXl9fX16GXxJgX15eX2FgXWBhYWBhYmRlZWaEZxJoaGZlZGRkZWZnZ2VjYmFgYWCGXw9dXl5dXl5cXV1eXl5cW1qFW4NahFmCWoVbDFpbXFtaWVpaW1xdXYRcFlpbXFxeXlxaWFVVVlZWV1dYWFlZW12EXwNgYGGFYhdhYmFfYWFiZGJhYWNiYWBgYWJjYmJkZYRjFWJiYF9eX2BfXlxdXF1eXl9eXV1dXoRdGF5eX2FjY2JjZGVlZGNiYmJhY2VmZmZnaoRpgGpsaWlnaGlpaGptbm5xcnJyc3N0c3NzcnFua2tubm5wcW5sa2tsbGpoaWdoaWlqbG9ycnFuamtxdXp7e3x4dXd2cnFvbWxqaWdnZmlpa2ppamlpamprbWxrbG5xcnd3eXl7eXh5fHp6enl7fX5+f3+AgoWIio2RlZeYmJqenqOqQ7O4u8K7p6OoqamtsbK1tq+sqq2xt7u+YWNkZGJgX19eX19fYF5dXLW3t7hdX2BiYmRlZWVjYGBjZmZnaGtsbG5wcXACi4qEiyKMi4mIiImIiYiIiYmKiYmLi4qKhoaHh4aDgoKChIaJi4uMhIpMiYiHiImKiouMjI2OkZGPj4yLiYiKjI2NiYmLioiIh4WCg4WHh4iFhIaFgoGCgYD9+/7+/v35/P77+v+A//yAg4OCg4aHh4aDg4GBg4eEbIKAgYKBgYKD/vXu6enp6OXk5ePh3t3d29rY1dTU1tbX2dvc2NbU09TW2dvZ29zZ1tXV1dPT0NHS0s7JxcC+v769vb7BwL69vry8vb7BxsnMz9bf6uTYzcrGwb/BwsPDwL28urm5vLy6uLa2tIS1P7S1tba4u7y8vLu8u7y9vb28urm5trSzsLGzs7i7vb6/wcDAwL/Avr2+vry5ube2tba2tLOysrKzsa6trayqqISnNampqKakpKSjoqOkpqenp6ipqKiqq6usraytr7CysbGytrO0tbOysrKxsbGwsK6ur66vrq6vhK4Zra2trq2rqamqqainpqWko6OioKGhoqSiooahD6CgoaGfn6CfoJ2enZ2enoSgBp+goaCfn4aeN5+fnp+fnp+fn6Cfn6CgoKGgoaKipKOlpaampqWkpaampqenqKempqaoqKemqKqpqqqpq6ysqqqEqRmqqqioqainqaqqq6ysq6urrKurrKusra2uhK9Frq6ur7GurKytra6trq+ur7Gxr6+wsbGztbGwsK+wsrW2ubq3tbW1uLe4tbSztbOwsbCwsrGwsK+vrq2vsLCxsbCvr66thqwBqoWoE6eop6amp6mqqqmqqqqpqKiop6aEpxKmpqenpqWmqKimpqalp6aoqKiEqYSoJ6empqanpqanp6WkpKSjoqGgn6ChoJ+eoJ+dnZ2cnJ2enp6dnJubm4eaAZmGmAaXmZqYl5iElxKWmJqampmZmpqZmZmYmZmYl5WFkyaUlJWVlJOUlZeYmJaYmZiZmJmYl5aXlpWWlZaXlpaVlZaVlZSUlIiWBpWUlJOUkoSTCJKSkJCRkZKRhpIVk5OUlJSVlZWUlZeYmJiXlZSUlZeYhJkSnJyampmbnZycmpqbm5qdn6GghKKAo6OipKOkoqGfnp6en56eoJ6dnJ2enJubnJqbnp6fnqKjoqSjoKCio6ioqauoqaqqpqSlpKSjoqKioaOkpqWlpqaoqaqqrKysra6ws7a2t7i7u7m5vbu7u7y+wcDBwsLEx8rO0NLT19ra293g4OXs9Pb6//vt6e7v7O/09fn58+8y7/Dz+P3+gYWGhYOCgoGAgYKCgYGBgP7///+AgIKDgoODg4KDg4OEhoiHh4iHiIqMjIvbgYyAA4GAgJ2B/4D/gP+A/4D/gP+A04CQgYSAmIECAgQAgGlqaGZnZmZiXV1bWlxeZGhlZWZjZHGEiHxtYWFhX1taXWJmamxxe4GAfXx7c21veXt1dICOiIeNiISIkI+HkYVycXyJkp2bm5WUi39ydHqEiod8enyBgHh2c3V0dHFkX7exwGdkv2JpZ2ZpbGtrcXd4d3RsZmFhY2VlZGRiYWBeZ11cXFtZWFmqopuZmJmZl5KOjIqJiYeFhYOFi5CRjYaDgH56en1/e3p5d3h6e3t5eHd4d3p3cnJwbWhlY2FhY2VmZGNjZGVlZWRlZmdpbW9yeIiRjYJvamdlZmhpaWpnZmRhYGFiYl+EXAFdhF4TX19hYmNlZWRjZGVoampoZmVkYYReY19hY2drbnBxcXBwcHJzcnNzc3FwcG9ubGprbGxsbW1sa2hmZGBfX19gYF5dXFxbWllYWFlaW11eXl9gYmNlZ2ZnZmdnaGdoampsa2xtbGpqaWhpaGdnaGlpaWdlZmZmZWdnaIVnF2ZhX15dXVtbWlhWVFNTVFRVWFdWVVZWhlcUVFNRUE9NTk1MTExNTU1MTU1MS0uGSgxJSUhHR0hISEdISEeGSApJSktLSktMS0tLhkqESyhNTk1MTE1NTk5NTk5PTk5PT1BRUExLTU1OTkxJSEhJSk5PTk5PUE5OhE06T1BTUU9PUFFTUlFRUE9OTExMSUVERUZHSExNTUtJR0hJSUhISklJSEdKS01LSkpKTVBOS0tKSUlIR4RGBEVFRkaERwlISEhHRkVFRUSGQYJAhT9EQD8/P0BAQkJCQUFCQ0RERUVEQ0NDQkJCQ0JBQkRFRUZHSEZHSEpMTEtNT1FQTk9QUFBTUlRUU1FOS0hIR0dJSEdGRUWERCRDRERFRkZGRUVEQ0JCQkFBQkFBQkFBQUJBQUFCQkFBQ0VFRkWERBNGR0ZGR0dGRkVFRUdHR0lGQ0NChUEMQkJERUZGR0hJTExPiFBdUVFPUFFQT1BPTk5PUFFSVFRTU1JSVFNRUE5NTExPTk1LSklKSkxNS0tKSktMTUxKSEhISUtNT1NUVFRTUU9OTlBSUVJVV1lbXFxdXFtbWllWU1NSUldbW1xcXVxghGE4Y2RkY2JeXF1dW1xfYF5dW1hXVVNTU1FSUlRWXGJjZF1XVlldYGRmZmZjYWBgY2BbVlVST09QVFeEVVFTUlNWWFhXV1dVV1hYW2BgY2ViY2RkYmBgYGFiYmRkZGVlZ2psb3F0eH6Eh4qPkpKPjY+Vnp6lpJeVlZSWnaCiVFVYq6SfnJ+lVVZUU1Oim56EUB5RUVFSUlJRU1VVVFVXWVRRUFFRVFVUWF9gY2pua2yCcoRwgG9saWlnZmdoa25tbW5ra3SBg3xyaGpqaWVkZmptb3F0enx9e3p6dXFzent3dXyIgoKFgoGDiIeCiYF0cnqDh46OjImIg3pycnd/goB4d3h8e3Z1cnNycnBoZMTBymloymdqaWhqa2ttcXV2dXRvamhoaWppamppZ2dlZGNkY2FhYWK8ta6rqqmpqKSioJ6dnp2ampibn6CjoJyZl5eTk5SVkpGQj4+RkZKQj4+Pjo+OiomHg4B9fXt5e35+fXx8fH9/f31+fn+Bh4mLkaKqpZmHgn99foCBgIB+fXx5eHd5eneEdDd1dXV2dXZ2eHl7fHx7e3t8fX5+fn17enh2dnZ1dXd6fX+ChoeGhYSEhoeFhYaFhIKCgYB/fX5+hH9VfHx6eHZ0c3RzdHNxcG5ub25tbGxsbm9xcnNzcnR1d3h4eHd3eHl5enx7fH5/gH99fXx6enl5eXt7e3p6eHh4eXl6eXp6e3t7eXh2c3JxcXBxbmxqaYRoCWlra2ppampra4RqCmhmZWZlZGRjY2OEYoRjCmJhYGFiYWFhYGGEYIJhiGAHYWFiYmJjY4tkhGWCZoVlD2ZmZmdnaWhnZ2doaWhnZoVnTWVjYmNkZmdnZ2hnZ2hoZmVlZ2lramlpampsbWtramhoZmdnZWJiY2NkZGdpaGdmZWRkZWVmZ2dmZWVnaWppaGhpa21raWloZ2hnZmVliGSCY4RlBWRjY2JihF8PXl5eXV1cXV1dXl5dXF1dhF8LYF9fX2BgYF9gYGCEXwZeXV5gYGCFYQJjZIRlGGdoZmdnZ2ZmamhqamlnZmRiYWFhY2FgX4ReDl9eXV5dXl5eX15eXV1chVuFWhNZWllZWltbW1pbW1xcW1tcXFxdhF4BXYRcCl1fXl1eW1lZWViGWQ5aW1xeXl5fYGFiYmNjYoRjEGRiYWJjY2FiYmJjY2RjZGSEYxxkZmVjYWFgX19gX19fXl1dXV5eXl9eXV5fX19ehF0dX2FjZmdmZWVjY2BgYmRjZWZoam1ta2xsa2tsa2iEZgFqhG2Abm5wcHBxcXNyc3Nyb21ubWtqbW5ubWtpaGZkZWVkZWZnaG1xcnJuaWhrb3BzdHZ2cnFwcXJwbWtqZ2VmZmlsa2xsbGlqa25xcG5vb29wcHF1dnd4e3h5enp6eXp6e3x8fn5/gICBg4WIiY6SmZ6hpKiqqqimpq+2tb2+sa6tq602tLi7YGFkxL23tLa9YWJgYF+6tbhdXV9eXl5fX2BfX2BhYWBgYWJgXl1eXWFiYWVpaW1zdXJ1CI2Ni4qMi4yLhYgNiYqLiomJiIeKjI2MioSICIaFhYaFhoaHhIoOiYqJiYmKiYqJjI6MjI6EjTuOjI6NiYiKi4yNjY2Mi4qIhYWHiIiIhISFhoaEhIOEg4SEgYD//P+AgP6AgoCAgIGAgIKFhoeGhIOCgoWEBIODg4KEgWSAgIH69fDs6+vq6OHh393c3d3a2trd4+fo5uDe3NvW1tja2NjW1NPU19jV1NPV1dfVzs3LxsPAvr6/wcPCwsPCwMHCwsDCwsTIzc/R1ufv6d3LxMK/wMLEwsO/vby4uLq9vrq4hLchuLa3t7i4uru8vb27ubi6u7y9u7q5uLe0tLS1tba3ubq9hL9awMDAwb+/wL+9u7u5ubi3tre3tre3tLKxr6+tq6urqqmpp6enpqWko6OkpaSmp6ioqaqrra6ur66vrq+vsLKws7KztLOysrGwsbGvr7CwsK+wr66usLCxsLCwhK8vsKupqaqsqamopqWjoqKgoaKlpaSjo6GhoqOioqOioqGhoaCgn5+fnp+en5+goaCEnw6hoqOioJ+eoKCgoZ+goISfhaAaoaGioaKjpKWlpaalpaWmp6ioqKqqqainqKiFqRqrq6yrrK2uraqoqqysq6uqqaiqq62trKysrYSsPaurrayvr66ur7Cxsa+wsK+urK2vr6+urq+ur7GysbGvr7CxsrGwsLGysrO1uLm3trW1uLu6t7W0tLSysbCFsQ+ysrKxs7GysrGxsa+wsa+ErByrq6qqqqmpqKeoqKenqKepqKiop6iop6emp6iohacYqKelpaenpqWmpqakpqeoqKenqqupqaiphKgGqampqKalhKMJpKOhoaChoJ+ghJ8Lnp6fn56dnZycnJuGmgSZmZmah5lAmpmbm5qZmZiZmpubmpuZmZqamZibmZmbl5aWlpWVlJWVlZSTlJWWl5eXmJmampqZmJiXl5eYmJeXmJiXl5aVloSXOZmYmJaVlZeWlZWUlJOTlZSTkZKRk5KSk5KSkZGSk5SUlJOTkZKUlZWXmJiYmZmYlpWVl5aXmZqbnIadA56dm4SZA52enoSfGKGioKGhoqOkpKSgn6Cfnp6goJ+enZydm4WZDpubm6ClpqWioJ+hoaKlhKYFp6anqaeEo16ioqKkqKanqKmoqKutr66ur6+usbKytbe2uby6u7y9vLu6vL3AwMHCw8XFxsnMzc3Q1tzh5OXn7e7t6+zx9/j+//Px8fDy+Pj5gICB//v7+Pr/goOBgID/+/6AgIGBhoIYgYKDgoGCgoOCgYKCgoOEhISHiIqLjIyN4IEGgICAgYGAoYH/gP+A/4D/gP+A/4DMgIOBhoCFgYOAooECAgQAC2pqaGNkY2FhZGRfhFtuXl9kamtmaXd9e2thY2VjZWVmaGVlaXJ7fHh6e317eHZ6iYmDhYqQjoyNjZmgopGHf3WCkZ+vs6yVg4KCgXp3ent7f4GFkJqSfXd2cnJ2e266s2BrasRlcWhkwsLAw2lucXNzcWljY2NkZGZlYmCFXoBcW7Gtramhm5eYl5aUkI6MioqHiIeFhomOioaAf39+fXx9f4B8fH1+fHx6enp5d3Z4enp2dm9rZWNgYGBiYWRgZGhra2hlZGNlaW1vdHuAiYmDeWxoZGNlaGllY2JkZmVjY2JjY2BfX19gYGBhYGFhYmNkZGNiYmJlaGhqaGdlYgxfX19gYmFkZmdscXKEcQFyhHMJcXFycnR0cnFvhG4RbW1qaWdlY2FgYWJgX15cXFuEWA1ZW11eX2BhYWNmaGlphWqFaYRqCmtoZ2dmZWZmZ2aFZSRmZWZlZmRkZmZlZWViX15dXFpZWVdWVVRUVVVTU1RUVFVWV1eEWApWVFNSUlBPT09OhU0BTIhNBkxMTEtLSoRJYEpKSUpKSklKS0tLTExNTk5NTU1MTU1MTEtKSUtLTE1OTk5NS01NTE1OTUxNTk1NTk9QT01NTU5PT01LSktMTU5RUVBQUVNUUlJTVFZVVlNSUVJWW1dVUlFQT09QUEtHSIRHG0lKSkpJSEhHSEdJS0tJR0dIS0xLSkpKTU5NTIRLDkpKSUhHR0dGR0dISEdHhEYMRURERENDQkJCQUFAiT8HPkBAQEFBQYRDPURFRkZFRUVEQ0NDRENCQkRFRkZFRUVGSElKSktNTlBQUFFRUlNSU1RUUk9OS0lJSUtKSUhHSEZGRUZIR0eERhFFRUREQ0NCQEBBQUFAQUFCQoZBCUJCQ0RFRkVERIRGGUVDQkNDRENDRUZHSEdISUdFRENDREVGSEmFRw1KSk1PUVFSU1RTU1VThVINU1NSUVFSUVJSU1JSU4RSMFFRT05PT01OTk1MTE1NSklJSElKS0xNTktKSkpMTlBSUlNUUlBPTk9SU1ZWVFRXWYRagFtcW1dTUlRUV1pbWFdZXFxeXl5cXmJkYmFdXVpZWllaW11dW1pYWFVWVlRUVVVWV1ZVW11UUlNZXF9gYF9eXFhYW1xaVlZUVVNUU1ZVU1VYWFhWVVRUVlZWV1hYWlpaW15gY2hqZ2VkY2RiYGNkY2RlZmdpam1ucHqBgoSJjY2MRIyPlJedn5+lp6ObmpiXqltdXmBgXVelo6Gip1RTVFOfm52emptPUVFRUlRSUlNVVVJRUVNTUVFQUVJUVFVbXmJnbHJxfXNzcm5ubW1sbW1paGdnZ2hpbXFzbW95fHxybG1ubG1sbm9sbHB1enp3eXl7enh3e4SEgIGEiYeEhYWMkZKIgn52f4qSm52Zin59fn55d3l5eHp8f4WLhHh1dHFydXhvxsFlbGvMaHFqZ8rIycxrb3F0dHJtaWhpampra2lnhWVqY2PBvb27tK6qqainpqOhn52dnZybmp6jop6blpWVlJWTlZealZOUlZSUkpCQj42MjpCQjYuFgn17eXl5e3p9enx/goKBf3x6fICFiI2TmKGhmo6EgX18fYGCfnx7e3x8e3t8fHt4d3d3eIR3HXl5ent8fHp5eXl7fn5/f318eXZ2dnh2dnl9f4CEhYZoh4eHhoeGhoaHh4eEgoGAgYCAf358fHp4dnV0dHRzc3Jxb21sbm1sbW5wcXFzdXV2eHl6e3t7fHt7e3p6enl6fH59fHp6eXh4d3d4d3d4eHh5eXh6enp4d3l4eHl5dnNzcXBvbm5sa2qHaQNoaWqEaxtsa2tpaGdmZmVkZGVkZGNjYmJiY2NkZGRjZGSEY4JihmGCYIRhBWJiYmNkhGUTZmZkZWVmZmVkY2RkZWZnZ2dmZIRmXWdmZWhoZmdoaGlpaGhnaGdnZ2ZlZWZmZ2loZ2hpa2xqaWpqbGxtbGtqam1ycG5ramppaWlqZ2VlZGNjZGZoZ2ZlZGRkZWRmZ2hnZmVmamtpaGhpbG1rampqaWhnZ4VmC2RlZWZmZWVlZGNkhGMFYmFgX1+GXoRdhl4DX15ehV81YGBgYWFiYWBfX19eXl5fYGFgYF9gYWNjZGRlZWVnZ2doaWhpaWlra2lnZmRiY2NkYmFgYWGEYAVhYGBfYIRfA15eXIRbBFpaWlmFWgFZhFofW1tcXF1eXl5dXl5eX11bWlpbXFtbXF1eXl5fX15eXYVbAlxdhF41X2BgYmNlZmVlZWRkZmRjZGRjY2RkZGNjZGNjZGRjZGRkZWRjYmJhYGFhX2BhYF5eX2BeXl6EXYBeX2JfXl5eX2JjZWdnZ2VjY2JiZGRnZ2dmaGpsbGtrbG1saWZlZmZoa2tqaGlrbnBwb25ucXJxcG5tamlqamtsbm1qamhoZ2dnZmdoaWlqaWpub2dlZmtucHFxcG9tamtvb21qa2lqaGlpbGtpa25wb2xrbG1ub25vcHBzc3R1d2N5e35/fX18ent6eXx8fH1/gIGDhIiIipGYoKCkp6ajpKatsbi6uL2/urOysLC/ZWdpa2xoYbq6ubu/YGBhYLi2t7i0tVxeXl9gYGBfX2FhXl5eYGBfX15eX2FhYmVnam5zd3cwj4+NjIyLi4uMjIuLi4qKiYqKjIyKiouMjIqKiouKioiHiIaGh4iJiYqLiouLioqLhI9JkJCPjo+OkJGRjo2MiYuOkJOSko6LiYqKiIeHiIeIiIeJjIuHhYWGhYOEgv/8gYKB/oCCgID+/v39gIGDhYaGg4ODhIOCg4ODgoWBgICB/v38+vXv6eno5uXh4N/f397d3Nvf4uPj4dra3tzb2drd4NzZ2tvZ2dfV1dbU09TV1NHPysbAwL2+vr+/w8HFx8nJxsLBv8HFys7R2d/n5+HWysTAvsDEx8K+vcDBwL6/v8C/vLy7urq5ubu6vLu7vLy9u7m5ubzAwMG+vLq4Dra2tba2t7i5urzAwcHChMNhwsDAv7/AwsLBv767ubm5urq5trW0sq+sq6qrq6uop6alo6OlpaanpqioqKmoqq2usK+vr66ur6+vsK+vsLGysbOxsbCwr6+ur66trq+vrq6ur6+vsLGyr66usK2qqqmopoSlE6Sjo6Gio6OkoqOjpKSjoqOjpKSEoxGioaGioaCfn6CfnqGioZ+fn4ShEaKioaCfn5+goqKhoaCfn5+hhKJJo6Sko6Wmp6inpqempaWnp6ipqqqpqaioqqqqq6uqrK2rq6ytr66srKutra2sraysraytra2sra+vr62tra6wr7Kzs7Gys7azs4mwBrGwsK+ur4mwEbGxtLSzs7O4uLe2tre6vLq5hLYRtbW0s7GysrKzs7OysrKxsbCFsROvr66trKuqqqmpqKioqampp6iphKgJqaqrqaioqKmqhakEp6anpoSnEqinp6alpaipqKeoqKipqKipqISpDaurqKalpaOkpaelpaSEohehoaOhoKChoJ+fn56dnZ6dmpqbnJuamoWbI5ycm5uampubm5ybnJubmpuampmYmJmamZmamZqbmZqbm5qZhJcBmISXCZaWlpiYmZubm4SaBpmbmZiZmoWZJpiYmJeYmJmYmJeVlpaWlZaVlJWVlJOTlJSUlZWTk5GRkZKSk5SWhJQGlZWWl5eXhJhSl5aYmJuamJqbmpqbnJ2enp6dm5qbm52enp2cnqGgoaGhn6Cio6KjoaCfnp6fn56enp2enZ2anJybnJycnZ6dnqKjoZ+ho6OkpqelpaSjpKampIWja6Sjpqelp62sraurrK2vsLCwsrO0tbS1tba5vcC/v76+vr+9wcLBw8XFxsjKzM/Q1Nzh4ujq6ejo6u7x9/n5//79+Pby8f+DhYWHiIWC//79/f+AgIGB//z9/vv9gIGBgYKEg4KDhISBgYCCh4MJhISFh4qLi4yP34EGgICBgYGAhIGEgJeB/4D/gP+A/4D/gP+AzYCHgYWAhIGGgJ+BAgIEAIBpamllZmRiZ2plZGRhYWJlZmptb21taGtybGJkZ2Zrb3h5dHBtcnl+e3+Fh4B6dnmPkX+EhomRmaChqKippoqCiZCZo6uumoWCjI+VmZWJg4OEhI2TmZB8c21qa29sa2lpaWxrzsdqv6y6Z2rVbXFycW9qZGNhY2VnaWZjYWFgX2xeXVtasa2sqKKclpeXlZSSkI+Oi4qIiIeGg4F+fX18e3x9gYGAfXp3d3Z1dXR2dnV1dHV2dnV0bmtlYV9dXV9fYGBhYmRnZWRjY2VqbnBxeoCFg3x0b2tnZ2ZoaWdmZWVmZmRjY2RjYF9gYWCFYQRiY2NlhGQyZWdpamloZWNfX15cXF9hZGZobnBxcnJxcHFyc3N1c3R1dnVzcnJycXBwbm1samlmY2KEYBZhYV9dWltaW1tcXV5fYGJjZWVnaGlqhGkVaGlqamppampqaWpoZmZkYWFhYmNjhGIPY2JhYWNlZGNhYF9fXlxbhFkhWFdWU1NTVFRTUlJSU1RVVVZWV1VVU1NSUVBPTk5NTUxMhE0ETE1NToRPBU5OT05NiUoCTE2JTgZPT05NTU2ETgFNhUwJTk9QT05OTU1MhE0VTk9PTk1NTU5NTU1UUU1OTk1NTlFShVETVFZYWFlZVlFQTk5PUVZWVVJRUYRSLE9OTUxLSUlJSktJSUlKSUlISEpKSUlIR0hKS0xLSkxOTk1MS0tLSklJSEhIhUcESElIRoRFGEZFRERDQkJDQ0RDQkFAQUBBQUFAQEFBQoRBFkJDQ0JBQkRDQkRDQ0NCQkNERENERUaERQtERUdJSkpMTlFTU4VSI1NUVFJQT05MS0tMTEpJSUhHSEhJSUlIR0ZFRERDQ0NCQkJBh0AEPz8/QIZBD0JERUZHRUZFRUdHREJBQoRDCkRDRUZHSUlJR0eERg5HSElKTEtJSElJTE9RU4VUg1KGVDFTUlNVVFJTVFRUVVRUUlFSU1NQUE9NTUxNTExMTk1KR0ZHSEpMTk9PTU5PUFFQUlNRhE+AUlVVVldYWFdZXV1cW1pcXF1dXFtbXF1fXVpbWVpdXVxcXl5fYWBdWldXWFtcX11dWltdX19fXl5eXFhWVFJOT1RSUVJVWl9hYF1dXFxZV1lZV1ZWU1JRUlRTUVBQUlJTVFNTVFVVWFhXV1hYWVpdXmBgYWZnZ2ZmZWNiZGZqa2xSbG1sc3Z4goaJh4mOko+NkJKVl52cmpyem5ebn6hWW1xbW1paWVhXVlZUU1NSUVCfnZyXl5tPT09QT56eT1BQUVJSU1RUVldYWFhXWFpdYmVpbIBxc3NxcXBucHJwcG9ubW5vcHJ0dnRzcHJ3cm1ucG5ydXp6d3RxdHd6eX6Bg316eHqHiH2BgoSKjpOSl5eYl4J/hIeNk5ibjYB+hYaIjImBfX5+foSHi4V4cm9ubnFubWpsbW1s1c1qyLvFaGvXbnBxcHBtaWdnaWprbGppaGdmZV5lZGNiwb/AurSuqKmqp6akoqKhn56cnZ2cmpmWlJOTlJaWmJmYlJOPj46Li4uMjIyLioyNjYuKhYN+e3h3d3h4ent7fH1/fn17fH2BhYiJkpqenJONiIOBgICDg4GAhH8xfXx8fXx5eXl6eXp5eXh5ent8fXx6e3t7fH5+f357enZ2dXR1d3h7fn+DhIaGhYWGhoaHEoiJiYeFg4SEg4KAf359e3l4d4V1NXRycW9vbm1tbnFycnJzdnh4e3p6e3t6enx8e3x7e3l6e3t6e3t6eXd2dXV2dnZ1dnZ1dnd2hXcGdXR0dHJwhG4pbW1sbGtqaWlpaGhpaGlpampramtqamhpZ2ZlZWRlZWRjY2NiYmNjY2SEZRpmZGRlZGRjY2JhYWJhYWJjZGVmZWVlZGVlZYZmAWeFZjxlZWVmZ2hoaGZmZmdnZ2ZlZWdpaWloZ2doZ2dnbGlnaGhoZ2hpamlpaGlpbG5ubW9vbWppaGlpam9vbm2EaxBsbGpqaWhnZmdnaGhnZmVmhGWFZjFlZmlra2ppam1ta2pqamloaGdnZmZlZWVmZWZnZWRjY2NiY2NkY2JhYWFgYWFgX15fhF4DX15ehl8BXoRfAWCFYQNiYWGEYARfXl9fhGAJX2FjZWVlZmdoh2oQaWtqaGdmZmRkZGVmY2JjYoRhBmJjYmFgX4VeA1xcW4Rag1mEWgFZh1oPXF1eXl5fX15eXl1bWltahVsoXV1eYGFhX15eXVxcXV1fYGFgX15fX2FjZWZmZWRlZmRkZGVlZGVlZoVlgGRlZmVmZ2ZnZmRkZGNiYWBfYGBgX15fYWBeXFtdXV5fYWJhYWFiYmNjZGZlY2JiYWRnaGlqamtqa29wbm1sbW5tbGtrbW1ubmxqa2tsb29ubW5vcHJxbGtpaWpsbW9tbWpqa25ucG5tbmxraWhnZGVoZ2VmaW1wcXFvcG5ubWxud21sa2tpaGhpa2ppaGhqamtta2tsbW5wcXBwcnNzdnZ2eHl6fX59fHx9fHt9gISFhoeHh4uOkpyipaOlpqinpqiprLC1tbK1trSwtLe+YWZnZmdmZmNiYWJiYF9fXl1ct7a1sbG1XF1cXV25uFxdXV5fX2BhYWNkhmUFaGtucnQEj46OjoaPKJGRj42Njo6Njo6NjIyOj42LjIyLjYyNjYuLioqLjIuMjY6NjIqMj5CEjkGQkZSVlJSUk42Mjo6PkZKTkYyLjY2MjY6MioqJiIuMjYyJhoSEhIWCg4OCgoGA/v+B//r9gYD/gIGDhISDgoKBgoWDhoItgYH//Pz49Ozm5+jl5eTi4+Pg3t3f4ODd3dzZ19na3Nzi4N7b2dfX1tPT0tPThtI50M/LycK/vby8vb7AwcLCxMbDwsHAw8jMzs/Y3uTi29TPx8TDw8bIxcPFxcXDv7/AwsG+vr29vLy6hLsbvLy9vLq6u7y9v7++vbq5tre2tba3ubu7vL/BhcMbwsLBwsLCwcPDwsC/vr69vLu5ubi1tbSwr62thKssqaelpqamp6epqamqq62trK+vsLGxrq6trK6vr66ur6+wsLGxsK+trKytrq2FrBqur62trq6vrq2qqqurqaimpqalpKWmpKOjo4SkBqOjo6SkpISjBqKioqGhoYigBJ+gn5+GoAGhhaIwoaCfoKGhoqKhn6CgoaOjpKOjo6Wko6WmpqempqinqKmpqKipq6uqqaqqq6mqq62shK5qra6trq2usLCwra6ura2usK+vsK+ysrGwr7CysbGvsK+xsrOzsrOzsrGxsbKysbKzs7KwsLCxsLCxsbKxsbGytLO0tLSztLe5ube3t7q7u7u6t7e1tbW0tLOxsbK0tbW0s7OxsbKxs7OysYSvCK2ura2sq6qphaoEq6urqYaqgqmHqwOpqKeGqA+pqKempqanqKipqqmqq6yHqymsrKmoqKempaWmqKalpKOipKSko6OioaCfn5+gnp+enZycm5ucnJybmoSbPp2cm5uampucm5ycm5ycm52cm5iWl5eYmZmZmJmZmpubm5mYmJiXmJmYmJeZmZeVlpaXmZqbm5qZm5uZl5eZhZpomZeYmpiXmJqZmJiYl5eXmJiXlpaWlZWUlZWUlZaUkpCQkZKTlJWWl5aWlpeYl5eXlZWVlpaZm5ucnJybm5yen5+enp6fn6CgoJ+goKGgnZ6dn6GgoKChoaKjo6Cfnp6foaChoKGenp6EoYSfLZ6enp2dn5+fnqCjpaWmpqempqalpqajpKalpKOkpaampqepqqusrK2trq+vsoSzB7S2t7m7uruEvi+/wcDBwsXJysvLzMrQ1djf5ebk5uns6ujr7fDx9vf3+Pn39fb3/ICEhIOFhISCgoSBG4CBgYGA//37+fn9gICBgYD//4CAgYGCgYSFhYWGCIWEhYeKioyO5IEJgICBgICAgYGAl4H/gP+A/4D/gP+A/4DNgJKBhoCFgYKAloECAgQAOWJoamdqaWpsaWloaGloaGxxc3V1c3Bta3Z2dX5+foF/houDiIuDfnRxenx4dHuBfYN8e4KDkZiZpISob6WFl5qZnKGgk4CJmp+fl5KNjIyPlo2OjY6Lf3d4cWlycWtrcHBsaW1na3NtzW1r1Gtvcm5oYmNgX2JmZ2hlZWZlYl9fXl1bWlmsp6ShnJeXlpORkJGRjoqIhoSCgH+Af319fn17ent5eXd0c3NzcoR0OnZ1dnd3dHFuamViYV5fYGJhYWFkZ2dmZmZpamttbW1wdHp5d3Z2dHBsZmVmZmhnaGZkY2FhYGBfX1+EYQRgYWJjh2QiZWVmaGdmZWViYF5cXmNmaWprb29vcHFxcXBxc3FycnR1dYRyMnFwb25tbGpoZWNhYWBgYF9fXl5cW11dYGJkZGFhYWJkaGlra21paWloaGhpamtsbGxrhGouaGRhYF9fX2BfX15eXl9gX15hYWBfXlxdXl1cWVlZWlxcWlpZV1RSUVJRUVFSUoRUIFVUU1JSUE9OTk5PUVFQT01NTUxNTU5NTUxNTk5PT09NhEoBS4RMCk5PUVBQUVFQUFCEUQJQUYZPhE5FTU5QUFBRUU9OUFFQTk1NTlBRT01MTUxOTk5PTk9QUVNVVlZRUFBRU1dXWFhXVFFMS0hISktMTExOTlFSU1JPTExKSEdJhko0SUlJSEZGRkdHSEhHSElKTEtLTE5OTUxNTExLSkpJSEdHSElJSEhHRkZFRUVEREJDQ0RDQ4REGUNCQkFBQUJDQUFCQkJDQkFBQUJDRUNDQ0KFQy9ERUZGRURERkZGRUVFRERGSEpKS0xPUVJPTk9QUVJTU09MSkxNTk9OTU1MSkhISIVJCkhHRENCQ0FBQECNPwlAQUFBQEBBQkKERFZGRkZIR0ZFREVERURFRERERUZHSUpJR0ZFRUVGSEpNT05OTU1OT1BRUlJSU1NUVFRWV1hXVVRVVVVWVlZVVVVTVVRVU1NSU1JSUU9OTk1NUFNRTk1MS4RKAUyFTQZOTk9PUVKEVA9VVFdYW1pbXFxbW1paXV+EYAFfhGAmX15cXFtaW11gYWJjYGFhX1pZWFlZWl1fYmVmZ2ZkZGNlZGBdW1mEWAtUU1RXV1lZW11cV4RSC1FSUlNVU1FSU1JShFFzUlFTVFNVVlZVV1haXV5eX2JgYF9mbnhycnJ3en+Ag394eHd4fYKEgoyQjIWEiIuMkJOUlZmZnJybmpedn6GkU1dZWV1bXVxaWllYWVVVUqBQUlJSoJ9UVFFPnZtOTk9QUlNTVFRWWVxeYmRmZGRnZ2RnZoBtcnRzdHN0dnV1dHN0dHR2eHl7fHp3dXR6eXqBgX+CgISGgIKGgHx1dXp9end7fnx/enuBgYmOj5aXmJiYloGNjY2QkpGIfYKNkpKLiISEhIiMhISDhIJ6dXZxbHJxbW1xcW1qb2lscmzQbmzWbHBxb2tnaGZmaGpra2pqa2tpZixmZWRjYmG/ubazrqurqqajo6OioZ6dnJuamJeWlZOSk5OSkpORkI6Mi4uLiYSKOoyLjY6NioeFg399enh4ent7fHp+gYB+fX6BgoOFhoaKjZOSj5CRi4eDf4B/f4GAgX99fHx8enl5enmEei15eXp7e3t9fX1+fXx9fX59fXt7eHd1dXZ5e39/gYOEhISFhYaGhoeFhYaHhoaEhRuEg4GAgYB+fHp4d3Z1dHR0c3Fxb25wcHN1dnWEdAh1eHl9fHx5eYd7hnwHe3x6d3V1dYVzEXJzc3N0dXV2dHNycnFxcnJyhG8QcG9ubm1samloZ2doaGhnaIVpEWhoZ2VkY2NjZmZmZWRkZGNihGMJZGRkZWVlZmZkhGIKY2NkZGRlZmdoaIRnQWZnaGdoaGdmZ2hmZ2ZnZmdnZ2hpaWlqamhpampoZ2ZmZ2tqaGZmZ2doaWlqaWlqa2xtbm9samlpa29vcG9ubGpohWeEaAFqhGwlamloZ2ZmZmhoaWhnZ2ZnZmVkZGVmZmdnZmdoamtqamptbWxsa4RqHWloaGdnZmdnZmdnZmVjY2JjZGVlY2JiYWJhYmFghF8WXl5gX19gYF9gYGBfXl9gYWBgYWBgYIZhgmKFYYRgIWFhY2RkZWZnaWpoZ2hoaWpqamdmZWZmZmdnZWVkY2JiYoVjDGJgXl5dXl1cW1taWodZhVoBW4VagluEXShfXl5fXl1bW1xbXF1dXFxbXF1fYGBeXl5dXV1eX2BjZGNiY2NjZGRkhGYJZWZmZ2dnaGhnhWYQZ2dmZmZlZ2ZnZWVkZWVlYoVgC2FkZGJgX15dXV5ehWAHYWJjY2JjZYRmgGdlaGlqa2xtbWxsbW1vcHBxcG5ub29xcXBubG1tbG5vcXFycnFycnBtbGprbG1tbnByc3Rzc3RzdHJvbWxrampra2lnaWxrbGxub25raWloaWhoaWhqamlpamlqa2pqa2xsbW5tbm9xcHFyc3V2dnd7enp5f4WMiYuKjpCWlpqXU5CRkJGYnJ+dpainn5ygpKWprK2usrO1trSyr7S2ubtfZGZkaGZnZ2VlZWJkYWBduFxfX164tmBgXl24tltbXV1fX2BiY2RmaWxvcG9ub3FwbXFwB4yOkJCSkZKElFOTlJOTlJORkpORkI+PkZGPkZGQkpCRko6RkY6MiouNjIuLjY2MjoyMjY6QkZGUlpWVlZOMj5CRkZKRkI2OkpGRj4+NjIyNjoqLi4yKiYaGhYKFhYSEFoOCg4KBg4L/gYH/gIGBg4KBgoKBgYKEgwOEhYSFgjuBgPz28/Du6enp5+bl5uXj4uLh397d3Nza2dja2tnZ2dfW1tXU09PQ0dHS0dLS0dPSz87NysbBv76+v4TBLMXJxsPDw8XHyszMzdHV29rX19fSzcnExMXFyMbHxcPDwcC+vby9u7y9vLu7hbyFvR68vL2/vru6urm3tba3uru8vL2/wsTFxMTDxMPFwsGEwjLAwL+/v769u7q6t7a0sq+uraysq6qrq6mnp6aprKuqqqqpqqusrbCwsa+vrq2sq6yur4WwDrGxs7KvrKusq6usrKyqhawaq62srKurqqqrq6qmp6ioqaempqeloqGioqKFoweko6Kjo6KihKECoJ+FoYKghp8JoKGioaKjo6OihKGEohihoqKjpKSmpaSnpqinp6emqKempqepqaiEqSyqq6urrKysraysra6trK2wsK+tra6vsLGwsq+vr7CxsrOzsbCws7Szs7KzsoSxAa+EsASxtLW1hLQws7KysrCwsrSzs7Kxs7Kzs7Oys7O0tLW1tLW2ubu6uru9vLy7urq4tra1tLS0srO0hbVqtLOysrGysbGwsa+wsK+vrq2trayrq6utrKytrausq6qqqqusrKurq6ysrK2sqqqqqamoqKeoqKmnp6anpqaoqampqqqrrKuqq6urrKqqqainp6eoqKinp6ampaSlpaSko6OioqCgoKGenoSdApybhJyCm4acLp2cnJybmpubm5ydnJydnJqYmJmZmpqbmpmYmZqanJuampqZmJeWmJianZybmpiEmROampqbmpqZmZqbnJubmZmYmpubhJo8mJmYmJeXl5iXl5eWlpWUlJeYmJaVk5OSk5STlJSWlpeXlpaVlZeYmZiYm5ybnJ2enZ2dnp6enZ6goaGhhKAeoaKhoqGgoJ+foKGjo6Slo6SjoZ+enp6foKCgoqOkhKUIpKSkoaChoKCEoQugoqKjpKSjpqWmpoSlaqSkpKenpqiop6ipqqqrrKyur66usLKytLS1t7e3ub69vbzAw8nJy8zQ0dbc39zW1dTV2d7f3Ofq6ePg5ejo6+7u8fX1+Pj39PT4+Pr9gIKDg4WDhYSDg4OChIKCgP6AgoKA//6CgoGB//6EgBODhYSFhoeIiYyNjY+OjI2MjI2M6YEEgIGBgJmB/4D/gP+A/4D/gP+AzICQgQGAhIGCgISBgoCXgQICBACAamhmaGpvcGxsbWtvcXBvdHt8gYN8gHp7hHp2f4GDhoWQlZSVl5iNgHFyc3J2d3p0bXqGj5CRkZScpKWkmpF8jJCQloyHfYKPkpWWmZSHiI6KlZuYkZKOiYyMjYl2cnR0g394dmhpdmpqaGrPaGZoa2hiXl5eX2VnZ2ZlZmRiYV9qX19dWllYrq6rqaafnJmWlpORj4uJiIeFhYOEhIKAgH5+eXZ2dnl5dnV1dXR0c3V5e3x8e3t4dXFsaGZmZWRiZGVjZmlqaWxxamlqa25ra29zdXl1d3RzdXNtaGhpa2pnY2FjY2FeXVxbXIReQF9gYWJjZGRkY2NkZGVkZWVkY2NhYWBgYmZpam1ubm5tbnFxcXBxb29ucHBxb29vcXBubW1sbGxqaGdlYmBgYF+EXSZeX19gYWFgYGBhZWdqbW9wcHFvbWxra2xra2tsbm1qaGhoZ2NgX4heHl1cXF1dXWBgX11cXFxeYF5aWlxeYF9eXV5bWFNRUYRPOlFSU1VXVFJRUVJRUlRSUVJSUVJSUVFPT1BQUVBPTU1OTU1OT09OTExNTk1NTlBRUVJSU1NTUVFTU1SEU1JRUE9QUE9OTU1OTk5RUVFSUlNSUlNTUlBOTE5RUU9NTlBPTk1NTk5OT1FTVVRSU1RUVVZXVFNUU09NTEpHSEtKSklKS01OUE5MSkpJSEhJSkpKhkkLSEZFRkZHR0lJSUqFSxdOT05NTk1MSkpKSUlJSEhISUlISEhHRoVFCURFRUVEQ0JDQoZDREJBQkNDRUNDQkNCQUBCRERDQkJCQUJDRUVGRkZHRkVFRkdGRERERkZISUhJS05OT09PUFBRU1NTUE1NTE9PT05NTE1NhEwBToRLEExJRkRDQkA/Pz8+Pj4/Pj+GPgRAQUFAhUEoQkJEREZGRklJSEdIR0ZGRUZFRUVERURGSEZEREVFRkdJSUpNTlBTVIRVgFRSUlJRVVVXV1dWVldYV1ZVVlZVVVVWVldYWVdTU1NUVFRTU1NUVVVVVFJPTkxLS0tMTUxMTUxNTlBRU1dZWllaWlhVVlhaWlpdW1lZVllbXGFiYWBkZmZkYmBfYGBgXl1dX2BhY2JiZGBbWllZWVpdX2RnZ2dmZGNgYF5cWltbgFpbXFlYWlpbW11eXFVUVVRTTk1NT1FVVlVWVFVWVlNRUlBQUFFQUVJRUlRXWFpgYF9gYGNmaGlramxzfH9/g4OEgoOGhn1/h4aBgYOEhYWNj5CSlqGlnZyen6Ccmp+joqSkp1ZXWVhaWVlYWVhVUVNRoZ6doVJXW2FeVFFRUlJRFVFUVVRXWV1gYmhtcXZ3dHJxbGptbIB0c3FydHh5d3d4d3p7enl8gYKEhYGEfn6EfnuBgoOFg4qNjIyOj4eAd3d3dnd4endzeH+HiImKi5GVlpeQi32Eh4iNhoN7f4eJi4uOioGChoWLjouIiIaDhISEgXVyc3N8enZzam10a2ppa9NqaGpta2hlZWVmamtqamlqaWhnZnhmZWRjYWHAv7y4tK+traqopKKhoJ6dnZybmZqYl5WWlZWRj4+NkJCNjY2Mi4uKjJCSkZORkY2Kh4OAf359fXx9fXt+g4SDhomEg4SDh4aFiYuPk4+Qj4+Qi4SBg4OEg4B9enx7eXd3d3Z2eHl3d3h4eXp6fH19fHyFe1x8e3p6eXh2d3l9gIGEhYWDgYOGhoiHhoWFhIWEhIODhIaFg4KBgIGAf3x7enh3dXRzcnFycnJxcnN0c3NzdXZ4eXt8f4GCgH18fXx8fHt7e3x9fHx7e3t6eHV1dIRzhXIjcXJycnR0dHFwcHBydHNwcHFxc3JycnNvbWpnZWVmZmVmZ2eEaYRnEWhqaGdnZ2ZnZ2dmZWVlZmdnhGUeY2NkZWdmZWRkZGNkZmdnaGlqaWlpaGhpaWlqaWlph2caZmZnaGlpamtrbGxramtra2lpZ2lsa2loaWqFaQdoaWpsbW9uhG0mbm5vbWxtbGlpaGhmZmdmZmZnaGlrbGppaGhnZmZnaGlpaGhpaGeEZRdmZmdmZ2dnaGprbGtqbG1sbG1ramhoaIZnhGgDZ2dlhGQTZWRkY2RjYWFiYWFhYGBgYV9fYIRhhGAGX19gYWFghl8yYGBhYWFjYmJhYWJhYGBgYWFiZGNkZWdnaGhoaWlqa2traWZlZWdnaGhmZWZmZmVmZmaFZQpjYF5eXVxbXFtahFkEWllZWYVaIVtaW1taW1pbW1xdXl5eX2BgX19eXV5eXl1dXl1dXF5fXYRcCl1eX19hY2VkZWeEaAlnZ2ZmZmhpaWmGaAJnaIVmDGdoaWlpZ2ZlZWVkY4RkcGVlZ2dlY2JhX19fYGBfX2BgYGJjZWZpa2trbGtpZ2lqamtsbm1sbGprbW9wcXFwdHZ1dHRycXNycG9ubnBwcXNzcnRxbmxsa2trbW9ydXd2dXNzcXBvbm1tbGxtbmtrbW1ubXByb2pqamloZWRkZ2iEawdpa2xsa2psiGtTbW9wcXN3d3d4eHyAgYKEgYSMlZeXnJyem52fnpiYoJ+amZugoaGjpKmprrvAuLa2tre0sba6ubq7wGJjZWRmZWRjZGNhXl9eure2uV5jZ2xpYV+EXhVfYWJiY2VpbG5zeX6BgH58e3d0d3VvkZCPkJGUlZWXmJeYmJeXmJmZmZiWl5STlpWSk5KQkpGVlZSUlZSSkI6NjYyOjY2LiY2OkI+RkpKSk5WUk5KNj46OkY+OjYyPkJGRko+NjY6Mjo+Pjo6Ni4yLi4uGhYaEiYeFhIKDhIKBgYH/gICAhIIFg4KBg4SGg3KEg4KCgoGAgP/8+fXy7+7t6unm5uXh4OLi4ODe393b3Nza2tfU1NTY2dbV1NLR0dDS1dfX1tTV09DQzMjDw8HBwMPFw8XJysvO0MrIycnOy8zR2dfZ1dfU0tXTy8XHyczLx8PDxsS/vLu7ubi5u7q6u7yHvTi+vb2+vr28u7q6ubm4ubq9v7/BwsLCw8TFw8TExMPCw8LCwsG/wL++vr28vLy7uLa0sq+traysrISrGqmpqqurq6ysrK2trrCys7OzsrKxsK6vr7CvhLAIr7Cxs6+srKyGqySsq6uqqqqrrKurqqqqq6utqqioqaqsqqmoqaqlpKOioqKhoaKFoxSioqGhoqOko6GioqKjoqKjoaGhoIShAaCEoR6ipKKhoaKjoqKkpaSkpaenpqekpqinp6mpqKmpqaiFqRiqq6usra2ur6+wr66ur6+vrqyusK+vrq+EsR+wsLCxsrOxtLKytLOztLO0s7O1tbKysrGwsLGxsbK0hLUTtLWztLKxs7O0tLOztLK0tLW1tYW0GrW2tre5urq6uby9vLu8u7u5t7e1tra1tLW2hbUWs7GysrKzs7KwsLGxsLCurq2trK2urIStC66trKurrKyrq6yshKsBqoerDaqpqKmoqKmoqKipqamEqyqqq6yrrKyrq6uqqqmoqKepqKmpqKenqKenpqalpKWlpqeko6Ggn56dnZ6EnQmcnJybm5ucnJ2HnBSbmpubnJ2cnJ2enJubnJubm52cm4WaDJuamJiZmJiYmpqam4SdFp6fnp6dm5mZmJucnZ2cnJucnZycm5uEmoWbhZmGmAiZmZmamZaWloWVMZSVlpaXl5eYmpyenp2dnZyam5ucnZ2enZ2dm5yeoKKjoaGjp6elpKKipKSjoqGho6OEpIClpaCfn5+en6GioqOlpqalpaSjoqGgoqKjo6OioqOjpKWmp6ajo6WlpaKhoaOkpqenqKmrrKuqqqyrq6ysra6trK6xsbK0uLm6vLy/xcbExcXIz9bZ2dze4eHh4+Lb3eLg3dzf5eXl6uzu7vD3/Pr3+Pr89vb4+/v9/P+BgoODhC2Eg4KDgoKAgYD//fv/gYWKjImDgoKDg4KBg4SEhYWJjY6RlpicnZmYmJSRkZHrgQGAmoH/gP+A/4D/gP+A/4DOgI6BhICggQICBACAbG5ta2lsbGtub3BydnZ1d3uAgYCDiYSFhXp/h4aDg4KFho6YnqGSh3t2dXd3eoF5eIyOlqOfl5meoaCbind9gIR8hoyKio2Li4uSk4iFk5mUiI+VjJaenpyVjIaCgH97fXN3cmNiZGZmZGZmyMhkY15eX2BhZW1saWhoZmRkYl94Xl9eW1tbWbOxrKmin52bmZeUkY+NiYeGhYODhIaEhIWAd3d3eHt7enp5eHV3dnZ4eHl8fn17eHRua2ppZmRiY2Zqamptd317cGttbnBxbm9yc3V4eXV4enx3cXN2cWtoYmBjZmNhXVpaW1tbXFxcXmBhYmRkZGJhhGMDYmJhhGBdYWFjZmhpbW9vbW1ub25ubW1sa2trbG1sbW9xcG5wcG9ubWtoZ2ViYF5eXF1dXl1bW1pcXV5fYGBkZ2lsb3FycnFvbmxsbm1sa2xsbGloZ2dkY2FfXl1eXV1cXF1dhFwkXV5eXl9eXV1dXl9fXl9fX15cXFtZWFdWVVNRUE9OUVJVWVhXhFYJVVNSUVBQUVFRhFB0UVZXVVRST05OTk9RVFRSUE5PUFFSVVlaWFVUVFRWVlVVVlZVVFNSU1NSUU9OTk1NTUxNT1FRUVNUVFRTVFVTUU9PUFJRT05PTk9PTk9PT1BRUFFRUVBSU1RUVVJRUlJQUE1MS0xMTkxLSktKS01NS0lISkmHSghJSUhHRkZGSIVHE0pKS0xNSklMTk9PTk1LSklJSkqLSYRHGUZFRUVEQ0JCQ0JCQ0NEQ0NCQUFDQ0REREOEQg9BQkJERENCQkFCQ0VGRkWGRiNIR0ZGR0dISEZISUtNT09OT1BQUlNTU1JRUE1PUFBRUlJRUYRQOE9OS0lIR0VDQ0JCQkFAPz49PT4+Pz8/Pj4/QEFBQUA/Pz9AQEJDRERFRkdJSUhJSElISEZGRUVFhkQZQ0RGR0dJSkxNTk9TVVRVWVtZVlRVVVdXV4VYCllXV1daWllYV1eEWRRYVlVVVVZVVFVUVFRTVFRTT09OT4RObUxNTk5PUlRTVFdYWltbW1lYWFdYWFhZWFdYWVtaWl1iY2VnbG1rZ2NjYWJhYWFeXl5dXmBiYmFgYF9fXmBfX2BgY2JgXF1bW1paXF1eXFxdW1haW1lXWVxYVFRVVFBLS0xNT1RYWVhYV1JPUFCGTktPUVJTVVVXWF5iZmZjYmNpbnN1dXR6fXl7fICDhYmDgoSCf4B/foWLjo6QkpOVnpybm5+jpaaln52dm5ybmpWfpFNTU6dTU1JSVVSEUiBVWFlbVlFQVFtZV1dZXFxgZmp3fXt1cnBtbG1vcHFtbIB2eHh2dHZ2d3l6e3t9fn1+gYSFg4WKhoaGfoKHhoKCgoSFiZCTlouGfXl4eXl7gHt6hYeMlJKNjpKUk5CHe36AgnyChoWDhYWFhoqKg4GKjouCh4qDipCPjYmDgH59e3h5cnRxaGdpamhmaGjP0GhoZGVlZmdrb25sbGtqaWloZntlZmRiYmNhw8G7uLOysK2sqqiko6CenJybmZqam5qbmpeSkJCQk5GPjo6OjI2LjI6PkJOVk5GOi4aCgoF/fXx9gIKCgoaPlZSJhYeHi42KiouKjZGSj5GUl5GKjI6JhIF9e35+e3h2dHR1dXV2dXZ3eHh6fHx+fHt7e3qEeRZ4d3d3eHl6fX5/g4SEg4OEhYWFhIOBhII/g4ODhIaEgoKDgoCAfXt7enl3dHNxcXFzcW9ubnBwcXNzdXd4e35/gYKCgH9+fX1/fXx8fXx8enp6eXd2dXNzhHIrcXFxcnFxcHByc3NzdHNycXBxcnNzdHNycnFwcG9tbWtpaWdmZmVnZ2lsbIVrAmloiWc2ZmVnamxsamhmZmVkZWdra2hmZGVlZ2dqbW5sa2tqa2tqamlqa2tqaWpqaWdoZ2dnZmZnZ2hqhGsRbGtsbGxtbGtpaWptbGloaWmHahFra21ubWxtbW5ubmtsbGxqaoZpEmhnZmdnZ2lpZmZmaGZoZ2doZ4RoKWdlZmZmZ2ZnZmZmaGhqamtqaWxubm5tbGtqaWhpZ2doaGloaGhnZ2dmhWUwZGRlZGNjYmJiYWFhYmBiYGBfYWBhYmJiYGBgX19eX2BgYGFgX2BgYWJiYmNjYmJhhWIbY2JjY2JjZGVnaGhnZ2hoamtramlpaGZnaWlphWoXaWlpZ2dlZGJiX15eXFxcXV1bWllZWVqFWYJahFuFWgJbXYReCV9gYGBfX19gX4ReAV2FXIBbW1xdXV5fX2BhY2RnaWhpbG5samhpaGlpaWppaWpqamlpaGpqamlpaWtqamppaGdnZ2ZlZWdnZ2ZlZWZlZGRiYmFhYmJgYWFhYmRlZWZpaWprbWtqaWhoaWlpa2tqa2tsa2tucXFzdXp7enVydHNzcXFwb29ubnBwcnJxcXFwcIBub25wcHF0c3FwcG1tbGttb29ubnBubG1ua2psbWpoaWpqZmJjZGZoam1vb25uamlqaGhnaWlpamtra21vcHBxd3p8fXt8foKFi4yOj5SWk5OUmJqfoZ2cnpqYmpiYoKWnpqmtrKy3tbOztrm8vbu3tra0tbS0sbe6Xl9fwGBgXydeYWBfX15dYGNlaGNfX2FmY2NkZmhoa3F1gIaFgX99enh4ent7d3d0kpSTkpGSk5SXmJeWmJmYmZqbmpqZmpiZmpeYl5aTkpKTk5OXmJiUlJGQj5CPjo+NjJGQkJSUk5KUlZWUkY+Pj46LjpCQjo6QkJGRkY+PkZKRjY+PjI+QkJCOi4qKioiHiIWGhYODg4KBgYGA//+AgYGBgoOFhAaDgoKCg4SEg4CCgYGA///7+PLw7+3r6unn5eXl4uHg3t7d3t/e3trX19fZ2tvY1dTU09TS0tTV1tna1tbU0s7Kx8fFw8LEyMvKyc3Y3tvRzMvM0NLQ0NXV19rb2Nvd3tjQ0tbRycbBwcbHwr68ube4ubm5urq6vL29v8DBvry9vr29vLu6ubi4uBe6u7u9vsDDxMPBwcHCwsPDwsHCw8PCwYS/LL6+vr++vbu6uLWzsa6sq6qqq6yrqKeoqamqq6uqrrCwsbK0tbWzsrKxsLGxhLJAsbCvsK+vsa2rq6qqqquqqaqrqamqqausq6qsrKurrKysqqqqqaytq6ioqKenp6WkoqKioaGho6alpaWko6SkooShDaKjo6OkpKOjpaalo6KEoR+io6WlpKKio6SlpKaoqamnpqeoqaioqKqqqamoqKiqhKlHqqmpqqqrra6tsa+xsbCusLGvr66ur7GxsLCysbKzs7S1tLS0s7S1tLO0s7Szs7KztbSzs7O0s7S0trW1tba1tLW2tbW0tbKGtAi1tLa1tbW0tIW1Cba4uLq7u7m4u4a9Arq4hLcFuLe2treEtgm1tLS0tbSzsrKFsQ+wr66tra2urq6trq2usK+ErgKtroStBKysq6qEqwuqqqusq6uqqayqqoWrB6qrq6utra2ErAGthKwiqaipqqqqq62sq6qpqqmop6elpKWlo6OioJ+foJ+dnZydnYWcBZubnJ2dhJwMm5qanJydnJ2dnZ+ehJwFnZ6cm5uEmoSZLZiZmpqZm5ucnZ6en6CfnqChoZ6bm5yenp6fnp6dnJ2dnJydnZ2cnJudnZycnIabhJkJmpqZmpiWmJeYhJcBloSXBpmZmZucnISdB5ycnJucnp6InS2foqOlpamrq6ijpKOkpKWkoqKjo6OipaSjoqKkpaOjoqOjo6Sko6GioqCfn6KEo12kpKOlpqWkpqakoqKkpKKgoKCipKaoqKirrampqaeoqaurqqytr62tr7CztLi7vsC9vsHExsvO0dLX29bW2d7h5uji4uPi3d7f3+Po6+3v7+7t+fb29fn8/v/9+fmE+Az59v7/gIGB/4CAgYGEgiKBgIOFh4mFgICFiYeGhoiKio2QlJyhoZuZm5iYmJeVlZOT7IGCgJmB/4D/gP+A/4D/gP+A0YAEgYGBgKqBAgIEAIB0dnh6d3V0cG5wcXR1fICHj4+Mi4uMiIJ/fIOOk52fmpaXlpukpY+Denh3doWbo5WCg4OYo56dn6KnpZaQkIR8f4uWm5qckouEjI6LhImKiYmCjpGPm6Cmm4V5e3x2gZaTfGpmY2BiZWlubXFlYV9eYGFiYmZrc3JubW1raWdkYyFiYWJhXVxcW1qxrailoqCenJmVko+Mi4mHhoaKkpGPjIeEgoCBgH99fHt2c3R3d3l8fX19e3p3cm1rZ2dpa2tsbWxrbXN2eHV2dnh8enl3cnBvbXB0dXyBfHZ8gHpsaWhmZGRiXlpZWVpZWVpbXF1eX2BgYWJiYmRkY2NgXl9gYGFiYWJlZ2lqa21sbWttbW1sa2lqampra2tsbG5vcHBycG9ta0BpZWVkYmBcXFxdXVtbW1xgZmlpamhnaGppbG5ub29vbm1ubm1ub29vbmxpaGdnZGJgXlxcXV1bWlpaW1tbXF5fhGBVYV9dW1pdXVxbWlpbXFxcWltbXFhYV1JRUlNTVVdXV1lcWltcWVdUUlFRUlFQT09PUFNUVVRTUVBPT1BRUlBPUFFTVltZXF9fW1hYVlRUV1ZWVVVVU4RUGFJRUE9OTU5OTE1NT1BQUlNSVFRWVVJRUYRQL09OT09QTk5OT09OT09QUFJRU1JTVFNSUVFOTk1LSktMTU1MSktKSUlISUhISEpKhEsqSktKSUpLSEdGSElIR0dHSEpKS0tMS0xNTk9PTk5MS0tMS0tLSkpKS0tKhUkCSEmERwJGRYREhUM5RERDQkNERUZIR0ZDRERDQkNDRERDQ0JCQ0RDRUZFRUZGR0ZHSEhHR0ZFRkdISEdISUtLTU9RUVJThFQcU1JTVFNUV1ZTU09OTk1NTk5MSkhFRENCQUFBQIc9BT4+Pj9AhUEFP0BAQUKGRIBFSEhJSkpLSUlJSEdHRkRDQ0FCQ0NER0lJSktMTE1PUVJTU1VZWVZVVldYV1dWVlRVVlZYV1lZWltcXVxdXV1cWVdYV1dVVldZWVdWVFRTU1VUUlFRUU9NTE5PT1BUV1hXVlhXWFtcW1pbXF1eX11cXF5gYV9gYWNnaGlpaGdmY0ViYmJeWltaW19gYF9gYWJjYV9fYGBfXl5gYV1aWVhWVVZYWVlbWltdXl1bWVhYW1xaV1VSUU1KS0tNUVVZWVVUUU5NS0yETXFOT09SU1RVVFZXXF5jZGNiZWdwdHB1eHFvc3t4dHR3dnNycnR7fHx/g4mJiomKjouSlJaan1Wkm5qcm56gn52bmVBSVlhWU1JUVFRbW1dWVVZaXFhXV1ZXWV5fYGdiZG93dnV2c3Jzc3FubW9wbmxxcoB7fH5/fXx8e3p8fX5+goSJjo6OjIuMiYaCgIWMj5SWkZCRkJKZmYqEfXp5eIORloyAgIGOlJGQkZSZl46KiYF9foaNkI6QioaBh4iFgISGhIWAiImHjZKXjn94enp2fomHeWtpaGdpaWtubW9nZWRlZ2hnaGtvdHJwbm5tbGtqaoBoZ2ZlZGNjYWHAvLi2tLKxsK+qp6SioJ+dm5qepaWhnpuZmZeXl5aTkZGQjYyMkI6Pk5STk5GQjYmEg3+AgYKCg4SGhYWLkZKQkZGUlZSTk42KioiKjI2VnJeQlZmRhYKBgH19fHl0cnNzcnN0dHR1dnd4eXt8fHt8fHt7eHZ3dh54eXl5e31+f3+AgoGCgoOEhIODgoGBgIGBgoODhYWEhA6DgoB8enp6d3Vyc3JycoRxE3R3eXl6eXp6e3x+f4CAgH9+fH2FfnV9fHt6enh4d3Z0cnBwcnFwb3BwcHFxcXN0dXV1dHVzcXBxcXJycXBwcHFwcG5vb3Bubm1oZ2hpaGpra2ttb21ubmxqaGdmZmhnZ2ZmZWdqbGxqaWhnZmVmaGlnZmdnaWxubW9yc3Bubmxqa2xsamprbGtsa2qEaQFohWcTaGlqa2prbGxtbm9ubWxrampra4hqgmuEbANtbm2EbgZtbG1sa2uEaAlpamppaGloaGiEZxBmZ2hpaGhpaWppaWlqZ2ZmhWcPaGhpampqa2xrbW5vbm1shGsEamppaIVpBWhpaGdnh2YVZWRkZGNiYmFhY2NiYWBgYWFiZGRkhGENYGBfX2BfX2BgYWFhYoRjBGJjY2KEY4JihWMvZmdnZmdoa2pra2xrbGxqaGpra2ttbWtsamloZ2ZnZ2VkYmBfXlxcXF1cW1pZWViEWQJYWoZbBVpaWltchF0FXl1dX1+EYIdfHV1dXFtbXFtcXl9gYGJiYWNlZ2dnaGlsbGpoaWpqiGkHamlrampqbIRtgGxsamhpaGloaGhpaWloZ2ZmZWZmZWRkY2JiYWJjYmNmaWloZ2hnaGxubWtsbW1sbm1sbW9xcm9vcHFzdnZ4eXh0cnJzc29tbm1tb3JycXFxcnRzcHBwb29vcHJzb21tbGtpamtsbG1sbnBwbm1ta21ubm1qaWZmZGNjY2Zpam1tfGtra2hnZmdoaGhpaWprbG1ucHBxcXZ4fHx7e36BiI6KkJaOi42SkY6OkZCOjY6OlZianJ6jo6OipaelrK+vsbdgvbWys7G3ure3tbVdXWJlZGBeYWFiaWhkY2JjZmdkZGRjZGVpam10b3F6gYF/gX5/f398enl7e3l2eHkNlZWWl5aVlpWXmZmamYSbUpydnJybmpqbm5uam5uamJeXl5iZmZeUkZCQkJKWmJSQj5CTlZSUlJaWl5WUlJKQkJCSk5KTkZCPkpOSjpCSkJCNkJCPj5GSkY6KioqIio2LiIaEgxSEhISDg4KBgYKDgoOEhoaHhYWEhYWEgIODgoKCgYGBgP77+fb08vLw8O7t6ufm5ePi4ODk5eTj39zd3t7d3tzZ1tfV09PW1dfZ2dnY19bU0c3Kx8bHyMnMzs7NztTc3NjZ2Nzg3tzZ1dTV0dPX2d/j3tbd4tvOzs3IxsbFv7q3t7a2uLm5uru8vL6+vr+/vr++vr67uLm4K7m6u7u7vb6/wMDDwcLBwsHCxMLAwcLDw8LBv8HDwsDBwcC/vbm5trW1srCErS6sqqqpqKqsr66wr6+wsbKys7O0tLOzs7GxsLO0tbW0sbGxsK+vrq6rqqmrq6qqhakdqqusrK2sra6trKuqq6yrqqmqqKenqKeoqKqmpqaEoy+ho6WlpKWmpaako6Kjo6Kio6OioqKhoaWmp6Wko6KioqOkpaSio6Olp6mnqausrISrhKoFqKmpqaqEq4SqFqurrKurrKysrq6vr7GvsrOxsbGwr7CFsSuysrK0tbW0tLW1tLa1trW2tbW0tbS0tbSzs7S1tra2tba1tLSztLa1tLS0hLYst7a0tLW3tre2tra3tra3t7a4uLm8vLy+wMC+vr69u7q5uLq6uLi5ubm4uLiEtxK4tre2trW0srKzs7Kwr6+wr7CErgmvr7GxsK6ura2Erhetra6tq6ytra2urK2sq6ysra6sq6qrq4SsD6usrK2traytrKytra2urYWsKa6wr6urqqmpqKeoqaempKOjoqChoJ+fn56dnZ2cnJ2enZydn56enZ2bhJwHnp6enZybnIaeh52AmpqamZqbmpqbnJycnZ2cnZ6fnp+foKKhn52dnJ2dn5+enZ2cnJ6enZ6enp+gn6Cfnp6dnJ2cnZybnJ2dnZybm5mam5uampqZmJeXmJiYmZqcnZyam5qbnJ6fn56enp+hoJ+eoaKioKCioqSmpqiqqqilo6SkoqKhoKGlqKempaaApaWlpKWlo6KhoqSlo6Kjo6GgoaKjo6SipKanpqWko6SnqKempKGhoKChoqWmp6qqqaqrqainp6iqrKusra6vsLK0srO0uLm7vb29wMPL0M3U29LQ09rW0tLX2NXW1dbb3N3g4ebn6uro7u3v7/L3/YH9+fr79/v9+/r6+YCAgoUIhIKAgoKDiIeEhiCIiYaGh4aGh4yMjZCOkZednZycnJucmpqYlpiYlZOUlf+BioH/gP+A/4D/gP+A/4DBgAGBi4CwgQICBACAcXZ6fXp+dG1tcXZ5eoGKj5ONjZqhmpySgH2KnJ+kpqappJ+jnpqPiICBhoKFmKKXh4uKiYiWm6ahpZKIjIJ6dIGMjI2LiYaCe4iLhYeJg4KIiIiLlZ6lnJKDgISFfn+HgXFkZGlwbmRwenBxbGNiYmJlZGJla21ub25tampnZWN/Y2RkY2BgX15dtrOwr6ujoqGalZSUkZGOi4iJjI+Oj5CNi4uFgoJ9ent8enRzdnl8gIGAfnx7eHh1cm9ucXBwbW9ubmxsbW9xcW5wcXN0dnh6cnJva2tqbHJ2dHh7eXBqZGBeXl1aWllYV1dXWFlZW1xdXl9gYWJjY2RjYmFgYIRhD2JjZWZnaGlramtpa2xrbIRpBGttb2+EbiNvcHBvbmtqZ2ZkYmBfXV5gYWFkaWlpbG5vbW5vb25tb25tbIdre2lqa2tsa2pqaWVkY2BfXVxcXFtbWllZW1xdXl5eX19fYGFgX19eWlhXWFhZW15eXV5dXl1ZWFVQUVNWV1RUVllbXl1eYWFgXltUUE5NTUtLTVBUVVVVU1FQUE9PUVBQUlRVVldbW1taW1lYV1dWVldXV1ZVVFNTU1JSUIVRHE9OTkxNTk9OTk1PUFJUVlZVU1BPTk5PT1BQT06FTR9PUFFTVVRUUlFTUlNUU1FOTU5MS0pLS0xLTEtKSktKhUkPSkpLSklJSEhJSUlISEdIhEcCSUuFTA5NTE5PT05NTU5OTk1LSoVLCEpKSktLSkhJhUgDRkZFhEYDRUVGhUUFRkdHRkaFRRhDQ0REQ0NEQ0NCQ0RERUVFRkdFRUZHSEeERgtHRkVGR0lKSkxOUYZSAVCGTxZOT0xMTk5MTE1NS0pIR0dFQ0FAQD8+iD0GPj4/P0BAhUERQkNDQ0RERUZHR0lKS0tMTEmESg5LSklIR0hHRERFR0lKS4RMAk1RhFMLVVVWWFhYWVdXV1aEVS1XWFlZWVpZWVpaWltaW1hXWVpZWFhZWlpYVlVTU1JTUlNUUlBNTU5QUlJUVleFVhlYWltdXl5gZGZnZWJhYmNfXmBjZWZlYWNlhGESYFpZWVhYWVtbXl9gXl5eXV1dhWEiXlpZWVdXWFhXVVRWV1dXWlxXWFpXVVRUVFNSUVBNTk9QUIVSD1FQT01NTE1OTlBRUVJTVIRVZ1pgYmRiYmNlZGZrbnJ0c3Nzb21ua21wcnN0d3h6fH5/hISFhYaOkY+RlJWSk5qfop+iUlKjU1ZYWFhZW1teX2FhX15dWVZXWFtbWVlbW1pZW2VvcXB0d3RtZGNgY2VqbGxtbWxscHCAeXyAgX+Dfnl5fICDg4eMj5GMjZWalpeQgoGKlpebnJqcmZWXlZOKhYCBhYKEj5WNhIeGhIOMj5eTmI2Eh4F8eICGhoaFhIOBfIWHgoOEgYGEhISGi4+Tjod9fICBe3yAfHNpaW1xcGhxeHBwbWdnaGhqaWhrb3FxcHBubW1ramg/aGloaGZmZWVkxcHAvbm0s7OvqqqopqWjoJudn6KhoqOhoKCbmJaTkpKSkIyMjo+SlpiXlJOSj4+MiYWFiIiJhIc0hoWGiYuLiYuOj4+QlJWLjImGhYSGjJKOkpSTioN/fHh4eHVzcnFxcHFxcnJ0dnZ3eHp7eoR8HXt5eHd4eXh5enx9fn5/gYKCgoGDhIKCgICBgYODhYQZg4SFhISCgX98e3l4dnRzc3N0dnh7fHt8fYR+gn+HfjJ9fXx8fXx9fXx9e3l6e3h2dXRzcXFwcXBwb29ucHFydHN0dHRzdHV1c3Nyb21tbW9vcYRzVnFxcG1samdoaWtrampsbG9xcXFzc3JwbWlnZWNkZGRlZ2pra2tqaGdlZGRmZmdoaWprbW9vcHBwbm1tbWxtbm5tbWxra2pqaWppampqaWloZ2hnaGlphGosa25vb29tbWtqaWlqa2tsamppampra21sbm9wb29ubm5tbW5ubWxra2tpaGmFagRpaGlohGcXaWlnaGhoaWhoaGloZ2hnaGhpaGlpamuHbBhubm1tbm5ubWxramppaWpqaWlpamlpZ2iEZwNoZ2aEZQFkhWMJZGNiY2RjZGRkhGMGYmFgYWBghWE/YmJiY2NjZGNjY2RlZWVkYmJjY2NlZWZmZ2hpa2tramlqamhoaGloaGhnaGZoaGhmZmdoZmRjY2NiYF1cXFtahFkJWFhZWVpZWltbh1yGXYBeX19gYWJjYWFgYWFgYWFgX15fX19cXV5fYGBhYmJiYWJkZmdnZ2hoamtqaWtpaWppaWlnZ2hqa2pqa2trbGpqa2praWlra2xqaWtra2lpaGdmZWRlZmZkY2JiYmNlZWdoaWhoZ2dmam1ub25vcHN0dnNycnN0cW9xdHV2dXN1dT5xcXJzcW1sbWtra21ub3BxcHBvbm5tcXBxcnJvbG1sampra2ppZ2lrbGxub2trbGtqaWlpaGdnZ2ZmZmdoaoRpemhoaGdoaGlpaWxtbG1ub29wcXJ2eXt9e3t9gH9/g4aMj46OjYmHiIaIioyOj5KUlZeYmZ2cnp+hpamnqa6xrLCzuLu4vF9fvGBjZWRkZmhoa2ttbW1samVkZmZoaGVmZ2dmZWhze3x8gIN/eXNwbnFydnl5eXh3dXl5SZSUlpiXl5eVl5ibnJucnZ6fnZ2enpyenpybnKCfnp6enZuampeYl5aVlZaUlJWYlpOTk5GQlJSXlpiUk5SRkY6QlJGRkJGRkY+EkSmSkI+RkpGQkZOTkJCOjo+PjIuMiomGhoWGhoOGhoSDhICDhYSFhYWGh4WGhIWEg3CCgYKBgID//v349vb19PTx8PDu7Onl4+Tk5eXk5+Tj4+Df4t7b2tjX1dbX2Nrc3dva2trV1NPRzs3Ozc3O0NDPzc3O0dPU0dTY2tnZ3NzT19HPz8/Q1NnX3N/e1tHKxMC/v7y5t7a1tba3tre6u729hL44wMHBv768u7q7vLu7vLy+v77Bw8TDwsLExMTCwMDCwsPDw8LBwsLBwcLCwr+7ure2trOysK2ur6+ErgmvsbKzsbKys7KEsy2ysrOysrCxsa+vsLCzsbCwsa+urayrqqurq6qpqaipqqurraysraysrq6trKyEqTuoqKmqq6uqqqmqqaempaOjpKWlpKSlpqaop6aoqaipp6SioKChoaCgo6WmpqakoqGioqKkpKSlpaanqISqB6uqqqmrq6qEq4aqA6uqq4esAauErQqsq66xsLK0s7OzhrIRs7OztLO0tLS1tra3t7m5ubeFtgy3t7a1tLKztbW2trWEtha3tra3t7a2tbW2t7e3trW1t7a2t7e3hLgNubm6vL29vL69vr+/voW9gryFui65uLi4ubm4tra2t7e2t7a2tba2tbSzsrKxsLKxsK6vr7Cwr66vsLCvsLCura2uhK2ErgmtrK2rq6yurq+ErYSuLa2trq2trq+vr66ur66traysq6yrq6unqKysqamqqqinpqWlpKOgoaGgoJ6fnoadD5+goJ+enp+fnZ6enp+enoSdCJ+goaCgnp+dhp4NnZydnZ2bm5ucnZ6enYaehJ8Onp6fn56fn5+gn5+enZ2EnyqenZ2dnqCfn56gnpydnp+fnp+fn52cnJucmpubnJyamZiYmJmampucnpyEmxmcnp+hn6ChpKOlpqSjo6ShoKGkpaanpqeohKQro6Ghop+foKKjpaWmo6KjoqSjo6Kjo6WjoqKhoaGioaKgn6KjoqOmp6SkpYSjD6SjoqOio6Wlpqanp6ioqYWqdamrra6wsbGys7S1tLO1ubq7vb29vsTExcjO0tPT1NTQz8/Q0tXW1tbZ2tvd3t/i5efn6Ozu7O/19PPz9/7//P+BgP6Ag4WEhIaHh4qLjIuKiomGh4iIiomIiIqKiIeJkJeZmp6fnJiTkZCSkpWWlpiXlpSVlP+BioH/gP+A/4D/gP+A/4DIgAOBgYCygQICBACAen95e3t7cW1ucHJ1eoGBfYKDg5CamqGhlYuEipiboqu2q6mupbC8pZCSgIKVpaGViZWViIuZoJ+KhXh7gn57eHF7ioh/dXZ6cXB3en99eoCEh5KVj5eSj4uIg4KCeXFyb2dkYW14b2ltbW93bWRmZ2dpZ2FmbG9tbG1ua2toZ2lpaWZkZWVhXl5eXVxcXLmuq6einJmWlpeUioiLjI2NjZCPjYqFgoCBfnx9fXp6fHx+gH9/fn1/fnt5dnd4d3V0dHJwbm1ta2xucG1sbW1ucnZ1cXV3dXRtb25ra2xwbWpnYV5cXFtaWlpYhVcMWFlaXF5fYF9iYmNjhGKEY0FgYWJiYmNlZ2hpaWpqaWpoaGlqam1tbm5vb3BxcG9vbmpnZWVjYF5eX19gZGltbW1ucXV3dnNxb25ub3BvbGtqaIRncGZlZWNlZGVmZmZjYF5dXVxcW1tbWlpZWVpaW1xcXFtaWVldYGFiYF1YVFNUVFZbXVxaV1ZWVlRQT1JTUVFSU1ZYW1xdYmNkYl5YVFBOTk5NTU5RVllYWVVST01NTlJTVldZWFhZWVlaWVhWV1ZWV1eEViBVVFNTUlFTUlJRUlNTUlBOTU5PUE9PT1BPT1BTVFVUUYRPhU4jTU1MTE5PUVFSU1NSUVFQUFJSU1VSUE9OTU1OTk1MTExNTE2FSg1JS0xMSkhIR0hISUlJhUgGR0dISUtMhE0ETlFRUYRQBVFPT01MhEsBTIROE01MSkpJSUlISUlJSEhJSUhISEeFRkFIR0ZFRUZFRkVFRUZGRkVEREVFRUZFRUVGRUVGR0hIRkZHSEhHSEdGRkZISUpLTU5PUFBQUVBQTktKTE1NS0lKS4ZNJ0tJR0ZHRURCQD8/Pj09PTw9PDw9PT0+Pz9AQEFCRERERUVGRUZHSIRKB0tLTU5NSkqESytKS0tKSUhHR0dISElKSUlKTE1OUVFRUFBSVllXV1lYWFhWVVVYWVpYWVlYhVeEWFxUVFdXV1haWlxcWVdXWFdWVlRTUVFRUE9RU1RUVVVVVldWV1haW1paWl5iZGRiYmNhX2FfXl5eX2BgYGFiYGFhYF9bV1dYWFlYWFlaW11cW1xbWVtdXV1eXl1aWIVWDlNUVFNUU1ZcWllVVVRUhFMIUFFRUFBQUVKFUQNQUFGEUHJRUVJSVFVWV1hXWFpdYGJjYmFiZWhtbW1vcHBtbGtsbGxwdXh4dXV1eXh6f4CBi5ObkpGSkZCNjJCXlpSZUVRUVlpcWllXWVxeYGFiYlxcXl5eXFlXVVdYWltdYmlxbW5nYWRiXVxbXF5gX2JkaW1xeXeAgYOAgICBfHl6e31/g4iHg4eIiI+VlZqZkYuHipOVmZ6knp2fl56lmIyNg4OPl5aOhI2NhYaPlZOGg3t9gn97e3Z8hYR+eHh6dHR5fH59en6BgoqLiIuJh4SDf35+eXN0cmxrZ252cGtub3B1bmhqbGxta2hsb3JwcHBxb29sa2wJbGpnaWlnZWRkhGJtw7y8ubWwraqoqaWfnqGhoaCio6Ohn5uYlZeUkZOSkJCRkJOWlpeVlZeVkpCNjo6OjYyMi4mIh4eFhYeJhoWGiImLkI+Lj5GQjoiIiIaGiIyKhYF8eXZ1dXR0c3BxcnJxcXJzdHZ4eXp5e3t9fYR8MXp6ent6ent7fH1/gIGBgYKCgIF/f4CCgYSDg4SEhoWGhIODgoB/fHp5d3V1dnV1dnqEfg9/goODgoB+fn9/fn59fHuHeQx4d3l4eXl4eXd1dXOEcgJxcIRvUnBwcXFycXBvbm5wc3R0c3JuamprbG5wcnFubm1sbGtoZmhpaGhoaWxtb3Bwc3V0cm9ramhmZmVlZGVna21tbmtpZ2VkZmhpa2xsbG1ub29wb26FbRRubWxtbGxsa2tpaGlqa2tqa2pqaoRpgmqFazJsbm9vbm1sa2tqaWlqaWlqa2pqa2xtbW9wb25ubm1tbm9vb21sbGtqamtsbGtsbGtpaoZoCmlqaGdnZ2ZnZ2eFaA5paGlqaWlqbG1tbWxsb4ZwFW9ubmxsampqa2ttbGxsa2tpaWpqaYRoBmZmZ2hnZoVlNWRkZWRkYmNkZGRjY2RkY2NiYmJjY2NkZGNkZGRiYmJjZGRkZWVkY2RjZGRmZmZlZ2hqamtrhGocaGdmZ2dmZWRlZmlnaGhoZ2ZjYmFjYWBeXVxcW4lZDVpaW1tcXFxdXl1eX1+EYIRhCGJiY2VlZWNjhWIrY2NiYWFhYGBhYGBgYWFhYmRlZmVlZGVmaWtqaWxramppaWhqbGxsa2pqaoRpQWppaWhnZ2traWprbG1tamloaWloZ2VlZGRlZGNlZmdmZmZnaWloaGlrbGtrbG5xc3RycnRxcHJwcHFvcHBxcnNzhHICcW2FagxrbGxsbXBubW1tbG2EbgpvbW1ramtramlohGkFZ2pubGyEagdpaWloZ2hohGkCammEanlpaWpqa2pra2xtbW9wcXJycnR1eHp7enx9foCBhYaIi4yLiIeFhoeHiY2RkY+PkJOSlJiam6WtsamoqqmopqSos7Kvs11fYGNlZ2ZlZGZpamxtbm5paGxsbGhmZWNlZWdnanB2fHt6c29xcGtqaWptbm5xcnV5e39+UpeXlZeYmZeYmJiZm5udm5udnJqdn52foJ6em5ygoJ2dn56dnpudnZmVmJWVl5qal5eWlpOTlZaXlJOPkpKRkZCNj5KSkY6Pj42Oj4+QkZCRkZGEkyKSkY+PjY6OjImLioeHhoaJh4aGhYWIhoKEhYaHh4WIiYmIhYeEhRmDgoOCgoGBgYCAgYD++/v7+vXz8vPw7OXkhOaA5+jo5uXj4uHg3tnb2tna2tnb3Nvb2tnZ19bW1dTU09LS1NLQzs7Ozc7Q0s7N0NLU19za1drc2tjT09HOz9LX1M/JxMG8ubu7uri2tbe1tbW2t7i7vr6/vcC/wL++vb2+vb2+vb2+v8DAv8DBw8PCw8TDxMDBwsTDxMHBwsTFw8I3wcHBwL27ubi3tLGysrKxsbKzs7KytLa3trSxsbKysbGxsrKysK+vr7Cwr66tr62ur7GxsK2qrIWrW6qoqKipqqqrq6upqamoqamrq6uqqqilpaanqKiqq6mnpqampaOho6Oio6KipaampaSoqamrqaWjo6GioaKhoaOnqaippqSjoqKjpaWmqKmoqKmqqqqpqKeoqKmEqh2rq6qrrK2sqqyrrKutrq2traytrq6ur6+wsbCvsYS0A7W1s4SyD7O0tLO0s7S1tra3uLm6uYW4Cbe3uLe2t7a2toW4Cre4uLm4t7m5ubiEtwi4uLe2t7i3t4S4Pbm5urq6u72/vr69vb/Av7/Av7++vr69vLu6uru9vLy8vbu7ubm6urm5uLe3tre3t7a2trWzsbOzsbKysrGHsoOxhbAgr6+urq6trq2trrCvrKyura6urq2tra6ur66ur7CwsbGEryGtraytrq2rq62trqysq6uqqqinpqemo6OioaGhn5+enp+Inhefn5+goJ+foKGin6ChoaCgoKGhoKChoYWfC56en5+fnZ6dnJydhJ4WnJyenp2enZ6dnJ2en56eoZ+foKCfnYefFZ2dnZycnZ2enp2cnJ2en6CfoJ+dnISdCZybmZiZmpmYm4ScGp2cnp2cnJ2en56enaCho6Sjo6akoqOioqSjhKQOpaalpaWko6Gfnp6foKCEoYCkoqGgoJ+hoqKipKWjo6KhoaKjo6Kgn6CgoaKmpaWio6OjpKWlpqSlpqanpqenqKeoqaurra6tra6usLGzs7W1tre5uLm8ubu+v8LDxMXIzdDQ0dPT0M/Pz87Q1Njb3NjZ2dra29/i5Ovy9fHx8vLv7+7w9vb1+oCAgYSHiYaFgyyGiYmMjYyMiYqMi4yKiIeGiImLi4uPlJmYmJSQkZCNjYyNjo+OkZKVlZaXlv+BjoH/gP+A/4D/gP+A/4DEgLWBAgIEAIB+hYCBfXV2dnR3fH18gISCgoeEhIKLipagpIiBh42XpqmelKOmuMaulo+LjI+MjYp+iZSMipWTfm50eIWNkJCIfHaAfHd7foB7anV+g3p9gYuMkpiNkpqhpZmNhoOBenRqbG9oc39/e3Zxb3FrZGRnaGdiY2pxc3FwcG9vbWtqbkdtaWVlZF9dXV1cXLddXl1asKigoKKdk4+Kjo+OkZ6gmY6HhoiGg4OBgYGCgIF/foCDgoGCgoKDgH59fXp5d3d3c3BvbnBvboRvLW5ubXBvbmxqbnB3cW9ramttbmxpZWJgXV1cW1hYV1ZWWFdXWFlZXF1eX19gYoRjHmBfX19gX2BhYWJiY2RlZmdnaGhoaWhnaGlpaGpra4RuCGxra2lnZWRihF4RYGNlaW9xcnF0eXl3dXNxcG6EbAlqaWdlY2RkYmGFYAxhYWJiYF9dW1tcXFuFWlVZWFhYVldVVFRWWFteXl5cWlhVVFNTUlNVU1NSUVFRUE5QVVhXVFVXW1xeXl9hYmJhX1tWU1FQT05NTk5QVVhZVlJQTk5QUlRXWVpZWFlaWltaWldWhFmEVghVVFRUUVJTU4ZUElJSUVBQUVFRUlJSUVBRU1RRUIVRBVJRUE1NhE4YT1FRUVJSU1NQT1BRUVNTU1FQTk1NTExMhE0/Tk9PTk1MTUtLS0xMS0pJSUdGSEhJSUpKSUhISElKS05PUVFTVVVUVFRVU1NSUVBOTU5OT09QUVFRUE9PTk1LhEwES0pJSYVKC0lIR0lJSEdFRkZGh0eDSIVGBEVFRkeERjVHR0hHRkZHR0ZERkZISEdGSEtLTE9QUFBRUU9OTU5OTUtMTExKSEpMTEpHRkVEREJAPz4/PoQ9BDw9PT2EPhk/QEBBQ0NFRERERUdISUpLS0lJSUxMT1BPhE0kTExLS0tKSUhGRkdHSUpJSUlLTExNTk9QU1VXWVpZWFlXV1hXhFYaV1hYWFlZVlVWVldZVlVUVVVXWFhZXFxbW1qEWCFZWFZTUFBQUVJTVVZWV1hZWFZXWFlbWldYXGFjY2NiYGCEX4BcXV1eXl9fXl5fYF9eXFZWWFlZWl1cWltbXFxcWlhYWVlaXGBfXlxXVFBQT05PUVFSVl5hYFxWVlZVVlVVVVRUVVVSU1VUU1JRUVBPT1BPUFFRUlJTVFVVVVdYW1pbXGFhYmJiY2VlZmVmaWtubW1tbm9ydnd2dHZ2d3l6foOGh0SSmZWPj42Li4uMjo+RkZObolJTWV9bVllZWlteYmZhWVdZYGFgXVhZWVpbW15gZmVdWFlZW11dW1xbXV9eXl1gZWt6fICCh4OEgn1+fn6Ag4SEhomHh4uJiIWNi5SanIqEiIySnJ6WkZmapK6fj4qJiYuHioiAh46Hho6Nf3V3e4KJioqGfHl+fHp9fn98cHh9gHt8foaGio6HiI2Slo2GgX9+eXVub3FrdHt6d3RycXNuaWlrbGtoaW9zdXRzc3JxcG5ucIBva2lpaGVkY2NjYsFiZGNhv7qysrSxqKOgoqKipKquqKGcnJ6bmJiWlpaXlpaUk5aZmJiZmZmal5WVlJGQj5CPjYqIh4mIiImJiIiJiYiLiomIh4mLj42MhoSGiIiFgn99enV2dnRxcXBwcHFwcHFyc3Z4ent6e3x9fXx8e3p5eAN5eXqEezJ9fn5/gICBgYCAf3+BgoKBgoOEhYWFhIKBgX9+fXt4dnR0dXZ4eXt+gYKBhIaGhYSCgIR+D31+fHp4d3Z2dnV1dHNzc4R0IHV2dHNxcXBxcW9wcHBvbm1tbWxtbGtrbGxub3FycW9shmo/a2pqaWdoaWhmZmpra2lpa25vcHFxc3RzcnBua2ppaGVkZGRlZmpsbGpoZmVlZ2hqbW5tbW5wcG5vb29ubm5viW0BbIRqCGtsa2xsbWxshmuFbA9ubm5sa2tsbGtsbW1sammEagRrbW5uhHACbmyGbhhsa2tqamtrbGxtbWtrbG1sa2pqaWppamqEaAlnZ2hoZ2doaWiEaRZqam1ub29wcnN0dHJzcnFwb29tbGxtiG4SbW1samtqaWhoaGdoaWhoaGdnhGYKZWRkZWRlZWRjZIZlAWSFZYRkhGMBZIRlL2RkY2JkZGVmZWRlaGhoamtrbGxsa2ppampoZmhnaGZlZ2hnZmRiYWBhX15dXFtbh1oqWVpaW1taW1xcXV1fXl5eX2FiYmFiYmFgYWNlZmdmZWRkZGNjY2RkY2JihGAGYWJhYWJihGMFZGVnaWqFawNqamuFaBpqamtqamloaGloaWpoZ2doaGtqaWttbW1sbIVqCWdnZmNjY2VmZ4VoHWpqaWpqa2tpaWttcXV0dHNxcG9xcXJvb29ub3BwhHEEcG9saYRqBWxvb21uhG+AbWpra2xsbnBwb21ramdnZmRkZ2hoanBzdHBsa2xqa2prbGtqa2tpamtqamppamlqamtqa2xtbm5ub3BxcnN0dnZ4eXx7e3x9foB/gICBgoSIiImIiYmLkJKQj5CQkZOWm5+goKu0sKiop6Smp6ipqq+vr7e7X2BkamZjZmZnaGoob3NuZmRmbG5tamVlZ2ZoaGtucXBpZWZnaWpqaWppa21rbGtucHV+gDuZm5mZmZiZmZeZm5ubnJ2dnp6cm5ycnJ6gn5ybn6GgoaCdm5ycnp+cmZmWlpeWmJeVlpiVlZaXkpCQkoSUDpKQkJKSkJGSkpGMkJCQhZEyk5WTlJSVlZKQkI+PjY2JiYqHiYyMiYmHh4iHhIWFhoWFhomJiomKi4qKiIaFhISFhIKFgSeAgP+AgYGA/fr3+Pn07evp6Ofm5+3u7ebj4+Ti39/e3tzc3N/d29uE3ULc3N7b2djY1tbV1tbT0M/Nz87P0NDP0NLT1NfV09LR1Nbd2dfPy87R0s3Kw8C+uru7ure3trW1tra1tri5vb6/v72FwAm/vr28u76/wcGEvwTAwcPEhMMdwr/Aw8XFxcTCwsTFxMK/vLy7uri3trSzs7S0srOEtTm0tbm5t7e0sbKysrGwsbCwrq2srq6traytra6urKuur62sqqurqqysq6urqqqqqamnp6impqanpqiEqgOpp6aEpRKmp6ampaOjoqKipKSlpqSjpKeEqIWpNKelpKOjpKKio6Okp6mqqKinpKOlpaWnqaqqqaqqqaqqqqiprKysraysrK2rq6usrK2tra6Ery+ur7Cwr7CxsbGwsLGxs7S2t7W2t7Szs7S1tba1s7S0tbW3uLi5urq6u7q5ubm6uoa5BLi3triFuQa6urm4uLqFuQW4t7i5uIS3Brm6urm6uoS7Bb6/wL/AhsIGwL/Av7++hb0gvr6/vr29vby8u7u6urq5uLe2t7e4ubi3tbS1tLS0s7OEsgqztbWzs7Kzs7KyhLGCsISvBLCvr66FryKurK6vsLGvrq6vrq2usbCxs7Kxr66vr66sra2tqqmqrKyrhKgJp6WjoqKjoaGhhKACn56EnxGenp+fn6ChoaChoqOhoqOjo4WhBKKioqGEoB2hoKChn5+fnp2dnZ6enJycnZ6enZycnJ2eoaKhoISfMaCgoJ+enp+fnp6fn56dnZ2en52dm52dn56enqCfn56en56enZ6dnJuZmZmam5ucnp6EnQKcnYSfDpydn6KkpKOkpKSipKSkhKIlo6OjpKSlpqSioZ6en6CfoaKioKCho6KjoqGgoKGipKampaSiooaggKGhpKioqKajpKelpqanqKanqKinqKipqqqpqaqrrK2rra6vsbKzs7S2uLm4uru8vr29vsHExcbGycjJzc/R0dLR0dHV2dvY2Nna3N7f4uTo6fD49PDw7+3s7e7y8vb19Pr/gIGGi4eEhoeIiIuOkY2Ih4qNjo6LiIiKiouLjo+UFZSNiYmJi42NjI2MjY6Njo+QkZKWl/+BjIEBgISB/4D/gP+A/4D/gP+Aw4CzgQICBACAd35/enp1cXN0eX98foWFiZCSkY+Ml5CXssiskIiLjZalqqempqitoKWys5iEiI2Fd3WCiIODg3t6goaJi5ecl4t6eXB0eXt8fHdtaW+Ai5Ogl4WQkZadnJyhnZeWhHluZmVvaXB/fHh1bnJ3cXBuamlmYmVtcXJycnBtbGppbG6AbGhhX15hYV5fXl1fYmFbsaunpKeklI6MjJOcn6SmpKCaj4eHgX2CioqKiIaHhoODhoaFiIiGhYSFhIN+eXh4d3d2dXR0dHNycW9vbm1sbGtqamlpb3V4dG5rbXBvamhlYl1bW1pZWVdXV1hXV1dWV1lbXF5fX19gYWBgYF5eXV5HYGFiZGJhYWJjY2ZmZWVmZmZoZmZnZ2hqaWtubGxraWlraWdlY2BfX2FjZWhpa21ycnN1eHl5dnVzcXBvb21qaGhmZGJiYF6EXSleXl1dXl5dXF1cW1xbWlpZWlpaWVlXVVVWVVRUVlhZWltaWlpYV1ZVU4VSYVFSUlBPUFJXW11aV1hcX2FgYGBfX2BgXVpWVVJQUE9PT1BRUlNUUlFSUlJTVFVWWVlaXF1eXl1cW1pZWlpZV1ZVVVRTVFVTU1JUVVZWVlRUVFNTU1JTVFNTVFVUU1NTVVOEUAZRUlNTUk+EUIZPHFFSUlJQUFFQUFJRUE5OTk9OTUxMS0xNT1FRUE+GTQFMhEsgSkpJSUhISUlJSEpJSktNT1JTVldYV1dXWFZUUlJSUVKGURhSVFNTUlJRUE5PTk9PTk1NTExMS0pLSkqESSdISEhHRkdHR0hISUpLSklISEdISEhHRkhHSEhHR0dJSEdGRkdGRUWERgZISUlKTEyETSBOTU1OT09NTExMSkhJTU1LSEdGRUVDQkFAQD89PDw9PYY+Oj09P0BAP0BDQ0RDRUdISUlJSklJSEhLTE9RUVFQUE5MS0pLS0pJSEhIR0hISUtJS0xMTU1OUFNXV1mEWgRZWFdXhFYxVVRWVldYWVhXV1dYWFdWVlZZW1xcXV1cXFxaWVlaV1hZV1RSUlNUVFVXVlVUVlhYWYRagFlZW15hY2VlY2NiYV9dXl5dW1xeXlxcXV1gX11bWFZVVVVbXFtbXF5eXVxXVlRUVVdYWFhaWVZWVVFOT1FUWFtdYGFeWVVTVFNTU1RXWFlbXFlbXFhXVFJRUlJRUVFQUFFRUVJSU1JTVVZVV1hbXV9gYmVlZGRlZmZpbW9wcnFyU3N2dXRzcnV2eH5/gYOFiIuJhoqPj46QlZeYmpmZnaKhUVddV1dVWFxbXmFpZl5ZVlVYWlxfXVxcW1dYWFlZV1ZYWFlcX19iZGRkYWJhYWNlbHd7gH6Dg4CBfXp9fYGFhIWJiYuOkJCQjZSPkqO1oY+MjY6SnJ+dm5qcoZeaoaCShIeKhXt6gYWBgoJ9e4GEhoiQko+HfHt2eXx8fH17c3B0f4SJk46EioqNkZCRk5GNjIB5cWtrcGxxfHp3dXF0d3NzcW9ta2dqcXV1dHRycW9tbW9wfG9sZmVkZmZkZWRjZWdmYsG9uri5tqmkoKKmq66ytLKuqqOdnZWSmJycnJ2bm5qYmZycnJ6enZ2cnZyZlZKRkJCPjo2MjY2Mi4mJiomIh4aFhYWEhImPkY6IhYeLiYOBf3x4dXRzc3Jwb25wcG9ucHFzdXZ4eXp6e3x7enmFeA56e3x7fHt8fX1+fn+AgIR/ZoCBgICBgoSFg4OCgYCAf318eXh3dnZ4eXx8fn+BgYOEhYeGg4KBf35+fn17enp5d3Z0cnFxcnJxcnNycnJzc3FycXBxcXBwb3BwcG9vbWprbGtramttbW5vb3BvbGxramloaGlpaYVnCmhpa21vbmxsbnGEcjdxcXJycG1saWhnZmVmZmdoaWlpaGhpaWlqa25ub29wc3NycnFxcHBwcW9ubW5tbW1sbW5sa2tshG47bW5ubW1tbG1sbG1ub25tbm1vbWtr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a21tbWxramloZ2ZkZGNjY2RlZmZnam9xcnN1d3d5d3Vzb21sa2lpaGlnZGJhX15cW1yHXVxcW1xdXVxaWVlZWlpZWllYW1tdWlZUVFNUV1dYWVhXV1ZWVVVWV1VSU1RVV1dWVVZXWFpZWVpdX11dXl5dXl1cWlhXVFNST01NTU5QUFJTU1FSVFRVV1VVV1lbXIRdD1xbWlpaWVdVU1NTVFRTUoRTEVRVVFRUVVRVVVRUU1RUVVZVhVQhU1BPUFFSUlFSUlFRUlJQTk9OTU1NT1BRUE9QUVBRUVBPhFBJT01NT09QUlZVUE9OTU1NTk5PTk9QT09MS0pKSUpKS0tLTVBOT1JXV19lZWFeXVxcWVZWVFRVVFRUU1NUVFRVVVNSUlNSU1NRUYVSLlBPTk5OTUxMS0tMTElISUlJSEhISUpKSklISUlKSktKSkpJSklKS0tKSUdHR0WGRydISEpKS0tLSkpLS0tJSkpKTE1MS05PTE1OTU1MSklISEdHRUJBQD+GPgE/hj4EPz9AQoRENkVGSEhGRkhISUlISEhMT09QT09MSUhISUpLS0pJSUhJS0xPT05NTExOUVFTVlhaXV1bWlpYV4ZVgFZXV1hZWVdWWFhXV1hZWlxcXF9fXl1cXFtaWltdXFxbWlhWVVRVVVZWVFNTVVdZWlxdXFtZW19hYWJkYmBjY2FgX15dX15dXl5dWlhXWFtcXFxXVlRXW1hZWlxcXFpUUlNUVVRVVlVXW1dWV1RTVFJSVFpfX19bVlRSUlNTU1RWBVldXV1chFd+WllYVFNUU1NRUVJTUlJSU1RVV1dYWFtcXV9hYGNjZWZmaWxtcnV2fHl6eHl4dnZ1dnd4en9/f4GBgISGh4eKjZSaUVFRT5mdnVBTYWtlXFpXWl5hZ25wamJZVlZXWFdbXl1dX11cV1VVV1hbWlteY2pubnBvampqa21vdH57gH5+fXx9fnx/hIyPj4mHhYmPm6GcnJ6dpLLJvpyPjpKVmpyhqaevn6CZmJSUlY6Kko+Fg4KCk5iXk42NiX+EkpmWjYaDgHx6fH6AfXWBj46Fio2IiY2QlJKYmJWQjIJ+dXhzenNwdHNtb25vcXBxcnJwbmxucHJycnBubmxraWlrL2tnZWdrbGtraGVkZWdnaGO/YLq3tbSwrquxsrK1tba0q6KemZCQmJqcnZ2cm5udhJ44n6ChoKCenJiUk5CRk5KSkZCQkJGPjIuJiYeGhISIiYiGh4mNjI6Lh4WCgX97eHZzc3NycW9tbW+EcB1xcnR2d3h5eXp5enl4eHd3dnd5ent6enp5ent8fIV/P3x9f4CBgYKDhYaEg4KBgH59e3l4eHh5eHp7e3x+gIGBg4SFhoeFg4J+fnx7eXl6e3l3dnVzcXFwcXFycnJzc4RyA3FxcIlvKm1ub3BvbGtra2psbGxubm1ta2prbG1tbGppa2tsbWtqamtsbm1sbW5ycodwHW9tbWtpZ2ZlZWZmaGlpamppamtrbG5sbG5vcnJyhXERcHBvb25ubGxsa2tsbW1ubW2JbglsbW1ubm5wb26EbwhtbGxtbW1ubYVuDm9ubm5tbGtsbW5vbm1uhG8TbW1ubm5tbWxsbWxtb3Jxb25uboRtDW5sbW5tbGtqamppamqEaz1sbW1wdHR5fX17eXl4eHV0c3JxcnJzc3FycnFxcnNycG9vbm9wbm5vb21tbWxsbGtramlpaGhoaWdnaGhohWcaaGdmZmdmZ2doZ2dnZmdnaGhnaGhmZmZjZGWFZgJlZoRoBWdnZ2ZmhGceaWhnZmhpaGhpaGdmZWRkZWNkYV9eXFxcXV1bW1tchVs6XFxcXV5fYF9fYGJjYmFgYWFhYmFiY2VmZmdmZWNhYWFiYmJjY2JiYWBjZGVlZGNkZGVmZ2lqa2xuboVsgGppamppaWlqampra2ppaWpqaWlrbG5tbm9wb25ubWxsbG1ubm5tbGpoZ2doaGloZ2dnaWprbG5vbmxrbHBycnR2dHJzc3JxcG9vcXBwcXJwbWtrbW9vbm5qamhqbWtrbW5vbmtoZmhoaWlpamprbWtqa2hoaWlqam9ydHRwa2ppgGhqamtrbG5zc3Jxbm5vbnBwb21sbG1ta2tsbGxtbnBwcHFyc3R2dnZ5fHt+foCBgYOGh4yOkJWUlZOUk5KRkZKTlJebnJudnp2fo6SkpqmvtV5fX122ubheYG13cWpnY2drbXJ6e3dvZmNjZGVkaGtramxpaGVjY2RmaWlpbXB3DXt6fHt1dnd3eXp9goAempmYmJiZmZqcnp2dnJudnJ6joqGhoqCkqKqmpKChhKKApKSgoZ+enZubmpqamZuZl5aWl5uZmJiXmJWTlpiZmJeVlJOTkpKTkpCQkZSVkpSVk5SWlZaVl5eWlJSQj4uLio2KiouKiYqKiImIiIqLioqIiYqKiomIiIiGhYSEhYWDgYKEhIODg4KCgoOBgYD9gPv5+Pfz8/Dz8vDw7/Dv7OhZ5eDd3+Hh4eLi4d/f3t/i4+Pj5OTi4N7c2tjW09TW1tfX19jX1tTR0M7Q0NHPzdLT0tHS0dbX2tbRz8rIx8TBv7y5uLe2s7SztLa1tLW2uLq8vr6/v7++vr6FvYS/V76/vry+wcHBxMPCwsTCwsPExcTFxsfGxcPBv7++vLq4trW1tre3trS0tbe4t7i3uLi5uLi3tLOzsa+wsbGvrq2sq6urqqurqqurrKurrKuqq6ysqqqqqYSqPamoq6urqKempqSnqaipqaioqKampqipqKenpqenqKimpaSmqKmnpqeoqqqpqaemqainpqamp6WkpKWkpKWHpgioqKmtq6mpq4StKK6urrCvr66vrayrrK2tra6vr7Cvr6+wsK+vsLGwsbK1s7Kys7S0s7WEth61tbW2tri2t7e3tbe4tre5ubi5ubq7vLu7vLy7vLyFuwK8u4W6B7u9v76/v76FvBS7vLy8u7u6u7y8vLu7vL2+v73AwoTFEMbHxcXFxsXExMPCwsHDxMOEwQzCw8LBwcPBv7++v7+EvQK6u4S6BLm4t7eHtiK1tra2tba1tLS2tra1trS0tLO0tLa3trW0sbCxsLGxsrOyiLFDsLCwsbGxsK6ur6+urrGxrq2trK2tq6inqKipqKalpKOko6KhoqGhoaCgoaGgoKChoqOkoqKipKSjo6Kjo6Khn6ChooSjJKWioKCfoaGgoKCfnp+gn5+ioaGfn6ChoKChoqOipKSjoqGhoYSgEp+gn5+goaGgn5+goaCfn5+goYaigqGEoESioaGgn5+enZ6dnZ6enZybnZ6en6Cgn5+en6OkpKampaSnp6alpKOjpKOio6SkoqGho6KjoqSgoJ6hop+foqOhoaGgn4SggKGioqSmpKOmoaGkoqOkqKqrqqinp6alp6enqKqtsK+tr62trq6tra+trK2trrCwsbOzs7S1tba5urq7vb/AwcLExcbJysnMz9DU19ne293d3dva29zd3t/g4uTk5eXl5+rr7O7x9vqAgIGA/f/+gIKMlJCJh4WHi42Rl5mVj4iGG4aIiYmLjoyNjoyLiIaHiYuNjI2PkZWYmZybloSXBJiXmJn/gZGBAoCB/4D/gP+A/4D/gP+AuICEgYOAtYECAgQAgHZ4dHJycnh7en+DiIeWmYyLn5+cmpmgp8bhypONkJm5xba5xbLGvqSmqamro4+PjId9go2ToZ2Xj42FfX2Nm6anjpCJeHJvdX18f3+CjY2GjpORkZaXsbWsqKKTfHlxbnV0cW5qY11aY2VtcXBua2toX2BmaWpnZmlqamllYWJmXGZiXWNqcGttbWprbmxraGFfsKWmr62mpKaqqqiko6OclpKMgHl5e4KOkY+MioqLjI6Oj46OjY2Lh4SAfXt4en19fHt7fXt4dnNvbGtpaWhoa2tqamxsaWlqamZkhGUKZF9aWVdWVFNTVYRWB1VWWFlbXF2GXoVdDV5fX19gYGBfYGFiZGOEZD9lZmZnZ2hpa2xtbGxqaWhmZ2ZmZWNhYmNjZGRlaWxucXJzc3V4eHNyb2tqamloaGhnZGNiYWBgX15eX15dXV6EX2VeXVxbWltbWlpYWFdWV1dWVVVUVVRVVlVUVVRUVFNTVllYVlRVVVdbX2BdV1dWVlZZW15gYF5cXF1dW1pZWFZVVFNRT05OTU1NTlBRU1JUVVVUVFRTU1VaXF1bWVhYV1dYV1VWVIVTFFJTVFRUVVVWVVVVVlZWVVRVVlZWhVUSVFRUVVVUUlJSU1RVVVRUU1JRhFAGT01OUFBPhVAeUVBQT09OUFFSUVJSUlNXWVhXUlBQT1FRUE9QUFBOhkw8S0xNTk5SUVJXX2ZvdXJwb2VhX1xbWVlZV1hWVVVVVldXVlVUVFVWVlZXV1hYV1VVVFNTUlFRUE9OTk1MhEoLTE1LS0xLS0pKSkuFTAVLS01LSoRMD0tKSklJSEdGRkhISElJSoVLA0pJSodJC0pLTU9PUE5MTEtKhEcGRkREQUFAhT+FPg4/P0BBQUJDREZGRkdGRYRGAUeESA5JS0xOTUxMSUhHR0dJSoVMA01NT4VOCU9QUlRVVldYWoRZSVhYV1dXVldXV1hYWVlaW1pbW1pbXF1dXV5eYGBhXl1dW1xdX19bXFtaWVtaV1dZV1ZVVlZXWVtcX2BfYGBgX2BhYGBgYV9eYF+EYYBiYV9dW1lWVFRUVlhYWltcXlxaXF5fYWJgXVtYV1dYWFdVVVNSUU9QUFJRVVhZWVxcV1ZYWFRUVVVWV1pbW1pbWlhZWVlbWlZXVlVVVVRUU1RVVVZXWFpZWl1eYWFiZGRmZ2lqa292eHd6f4F+fXt+gn96eHh5e36AgoSIj5CTkEKKh4eLlZ1UYWBUUVVXVl9jYGJiYF5eZGhtcnJpYF5fXl5gYmJkaGhiXltZVlhdX19gXmBjZmxvcHFydHd7eXZ6fXaAfn99fHx8f4GAhIeKi5WWjI2ampeVlpedsMK1ko6Rl6qxpqiwpLCqmZucnZ6ZjY6Mh36Cio2Xk5CLioN+f4mSmJiJiId7d3R5fn1/gICGhoKHiomIjI2dn5ualIp8enRwdXRycG5pZWRqbHBycW9ub2xmZ2ttbmxsbm1sa2lmZmouamhkZ21xbm1tbG5vbm1qZWS+uLi8ura0tre2tbKys66ppJ+XkI+QmKCjop+en4WgSKGioqGgnJmXlJKQkZWUlZOTlJKQj4yIhYWEhIOEhoeEhIaHhIGChYKAf39/fn15dXNycG5ubm9xcXBwb3Bxc3R2d3h5eHh3d4R2DnV2eXp6eXl6eXt7e319hH5PfYCAgYGCg4WHh4WDgoCAfn17enl4d3h5enl5eXx+gIKDg4SEhYWCgH19fn59enl6eXd2dnRzc3NycXJxcXFyc3R0dHJycG5ub3Bvb25ubYVshGuEahNsa2pqamlsbm1vbW1rbHBzc3FshGsfbW1vcnNyb29wcXBvbmxra2ppZ2dmZmVlZmZnaGpqaoRrEmppamtucHNxb29vbm5vb25ubYRsCGpqbG1ubW5thG4Kb25vbm5ub29wcIZvDm5ub29ubm9wcG9vb3BwhG8SbmxsbG1vbm5vb29ubm9ubm9vhG4YbW9vb3B0dXRycG9vb3Bvbmxtb3BtbGtshGsBbYVuDm9zeH2DiIaFhn98enh2h3UDdHN0hHMHcnFxcnJyc4RyAXGEbyBubWxtbGtramloaWppamppaWppaWhpaWlqaWlpamloaYRoCGlpaWhoZ2dnhWYEZWZnZ4RpIGhnZ2dmZ2dnZmZmZ2dpampraWdnZmZjZGRlZGJhX15dh1wWW1tbXFxdXV5fYGBhYmFiYmBgYF9fYIRiJWNkZWVkY2NjYmBgYGFiZWVkZGVlZWZmZWVlZmdnaGlpamtsbWyGawxqampramlqbG1sbW2FbAFthm+FcA9ubm5wcG5vbGpqbGtpaWqGaYBqbW5wcW9wcG9vcHFxcXJycHBxcXNzdHN0c3FwbWxraGhpamtrbnBwcXBucHBwcnNwb21ramlrbGtqamppaGZnZ2hpbG5vcHFwbWxtbmprbG1tbW9wcHBxcHBwb29xcW5vbm5vbm1ubm5vcHFyc3V1dnl5fHx8gICCgoOFhoqSk1qTlZucmZmWmZ2bl5WVlZicnZ+ho6mqrKqnpaSpsLdhbG1iX2JjY2xxb3BvbGprcHR5gH51bWpra2psb29xdXVva2hmZWZqbGxta25xdHl7e31+gIKFgn+BhH8CmZqEmYCbm5ucnZ6en56dnKGfoJ+goKKmqKShoaGipqekpKahpKKdn6Cfn56bm5mYlpeZmZyamZmYlpWVl5iZmpaWl5ORj5GSkZKTkZKTkZKUk5KUlpubmJiVk4+PjYqMjIuKioiGh4mJioqJiIeIiIWFiIiKiYeIhoaGhYSFhISCgYOFhASEhoWDhIROg4CA/Pn3+fn29ff29vLw7/Dt6+nm4tzc3eDj5OPi4uHg4eHi4uPk5eXk4Nza2dbU1trY2NbX2NjV09HPzs/Nzs7Q0tHOztDQy8rM0MzIhMcUxb+8uba3tbKytba2trW1tri6u72Gvoa9Bb6/v769hL9GwMHDw8PCw8TDxMXGxsfHxsfHxcPBv769vLu4t7a1tre3t7W0s7W2uLi5ubm4ure2tbOys7KysbKwrq6traytraysrKqsq4WsAqqrh6ozqamop6ioqKanpqinqKeoqKmnp6alpaiqqammp6enqaysqaeop6emqKipq6upp6ipqqiohqcdpaSkpaWlpqWlpaenqKmqqqurqqqqrK2vr66trq+ErhOvrKusrq2ura+wsK+wsLGxsbCwhbIms7O0s7O0tLa1tbS2uLe3t7i3t7a3t7i4uLe3uLm5ubq6vL28vr2GvAi7u7q7vb6+voS9Cr7AwcHAv8DBvr2EvIS9hL5zv7++vsHAwsTGyMrNzMvKyMnJx8bHx8bGx8bFxcPDw8TFxMPCwsPCwsPCwsHAv8C/v76+vby+vLu6ubm5urm3uLi5ubq4t7a3t7e4uLa2trW0tLW1tre3trW1tbS0s7Gzs7W0sbGwsbKys7OzsbGwsLKxsIWvFrCxsbGvra2trKurqqqpp6empaWlpKSFowOio6GEogajo6OlpKOFpCGioqOko6KipKSjpKSkpaKioqGgoJ+ioqGhoKGgoKChoqGHohSjoqSjoqKhoaOioqGhoqKgoaGioYiihKMWpKSlpaSjo6KioqSkoKKfnp6goKCfoIWeB5+goKGhoqGEooCjpKSlpaalpKWkp6empaenpaSjo6Gfn6CipKSjoqKkpKSmpqanqKekpKSjoqOjoqGio6OioaKkpaWnqamqq6qnqKurqqqrqqqsr6+wr6+trK6vr66wsLKxsbGys7S0tba3uLi8vr29v8DExMXHx8rJy87O1Nna2dzg5OLi4OTl5FDg3uHi5OXl5+rt8PL19O/t7PD5/oKMjISChIWFjJCOjo6Ni4uPk5ibm5WPjY6NjY+RkZKUlJKQjYuKi4+QkZGQkZKUmJqcnZyen6CdnJycmf+BkoH/gP+A/4D/gP+A/4C5gLyBAgIEAICDfXl5dnZ7gIWHh4CHjZCVmZaSn6CgqbjEwaaXl46arrO0vbyxw6Cgl56wqqalpKOpkIKAgIaFfH1+gYuIjZOOipSkmIJ5dXt7en+BjJSYjISZoaCUlZCapJmFdnZ+e4l7d3ZsYFxiXWVqcXd2dXFrZF9hZmZkZWZoaGhpaGRmZ31lYV5mcnFtbGxsb3NvamhkZMS9uba0srGrpqGdlpeWkZCUkod+gIWPlpiXlpKRkpKTkpGRk5KRjIqHgn99fHx8f31+fXx5dG9tbWxpZWhqaWdpa21tbGpnZ2llZmZjYWFiXVlYV1ZVVFVWVlZVVFVVV1haW1xdXF1dXV5dXYRcGF1eX15eXl9hYmNjZGRjY2VmZ2dnaWprbIRtCGpoZmZnZGRjhWEnY2RkZWdrb3Fyc3N0dHJzcXBvbW1ramhoaGdmZGRiYWFgYGBeXl5fhF4LXVxbWllZWVhYV1WFVDRTVFVWVldVU1NRU1RTVFdXWFdZXVxcXmFeXVtbW1lWVlhaXl5cWVdZWVlVUVJRUFFQT09NhEwfTk5PUFJSVFRVVVRUU1VWV1dYV1ZWV1hXVlRUVFNRUoVRLVJRUVRVVlZWV1dWVlZVVVZVVlZWVVVUVVRVVlZVVFVWVldXWFdWVlZUVFRSUYVPZVBQUVBSUlJRUFBRUFFRUVJSUVFUVlxfXldTU1dWVVJQTk9QTk1LTExMTU5PUVJTVFZZX2drbnR2dWhhYF9eXl5cW1paV1hYWFpYVlZWV1lZWlpbW1xbWlpZWlxaV1ZUU1NSUlNRhE4bT1BPTk9PTk1NTUxMTE1NTU5OT09PUE9OTk1NhEsTSUlISktKSklKSktOTk5NTUxKSoRJC0hHSElKS0tMS0tKhEkMRkREQ0JCQUA/Pj4+hT8gQEBBQUJDREVGRkVFRERFREVHR0hIR0dKSUxNTEtLSUeESC5JTU5OT1FRTk5NTU9OT09QUlRVVldYV1VWV1hYWFdXWFlYWVlYV1haW1pZWVtchFtbXF5fX2BgYF9eXl9eX19dXVtaXF5dW1taWltbWldUWF5eX2BhYWFgXVtbWllYV1dZXV5eYGFhYV9gYF9eWlhWVFRVVlleY2ViZGVnaWhmYmBeXFtbW1pYWFhWVYVUI1VYW19gXlxcXV1bXVxaWFhXVVdYWl1eX19dXF5eWlZVVVVWhFdxVlZXWFlbWltdX2FjZWhtb3JubW12fHl7g4aDfXt7gISHi4N+foGGioaGio+UmpqYkpSZUlJaampeWl9dXV9fXV1iZWZlZ2puaWhpZ2xycm5vb296g3ttZ2RiY2ZnamtoZWVkZmhpbm5zdHR7fXyChYJ4h4OAgH5+gYSHioqFio6QkZSTkJmZmqCpsK+elpaQlaGjpaurpK2XmJKVoZ2bmpqZnYyDgYKGhX9/f4GIhomNi4eNl5CBe3h9fXx+f4aMjYaBj5STi4qHj5aQgnd4fXuDend2b2djaWZrbnJ1dXVzb2llaGtqaWpqhWtQaWpqamdlanJxb25ubW9ycG1rZ2fNysLAwMC/ubWwraipqKSjpqSblJWYo6ipp6WioqSlpaampqelpaGgnJmXlZSTkpSUlZWUkY2Ih4eGhICEghuFhYaHiISCgYOAgH9+fHp5dXNycXBvbW5wcHCEbwVxcnR1dYV3A3h3dYR2Rnd4eXh4eHp7e3x8fX19fn6AgYGBgoOFh4iHh4aDgYB/fXx6eXh5enl5enl5eXt9foCBgoKDg4KBgH9/f358e3l5eXh3dnWEdA5zcnFycnNzdHRzcnFxcIRvM25ta2ppamtra2pra2tsa2pqamtqamttbW5tbXBvcHF1c3Jvb3BvbW1tbnBxb21rbm9vbIVoDmdmZmVkZWVmZmdnaGlphGsIampqbGxtbm+GbhRwbm5ta2xsa2pqamttbW1ubm9vb4RwBm9ubnBwcYVwAXGEcIJvhnEMcnFxcnFwb29vbm5thW4Mb3Bwb29ubm9ubm9vhHAncXJ2eHd0cnN0c3JvbWxsbW5tbG1sa2xsbm9vcHBzdnp/gYSIiYeBhHwGe3l5eHd3hHUGd3Z1dHR0hHUCdnWEdg51dXZ0c3Jwbm5tbm9ubYRsCm1tbG1sa2tsa2qFawJsbYZsBm1ra2ppaYVoOmloaGhpaWlrbGxramppaGdmZmZlZWZmZ2dnaGhnZ2VlZGRjY2JhYF9eXVxcXV1dXFtbW1xdXl5fYGCGYSpgYF9gYWFhYmFhYmJkZWRjY2NiYmNiYWNlZmdoaWhmZmVlZmZnZ2hpamuEbBJramtsa2xra2xtbGxsa2trbGyFbSZsbW5ub3FxcHBxcXBvbm9vcHFwb2xrbW5ubWxra2xtbGtoam5ucIRxQHJwb25sa2tsbG1vcXFzdHJycXBwbm5tbGppZ2hqbnN1d3R1d3h6eXdzcnBubG1vbm1tbGtqaWpqamlrbnFzdHKEcRVycXBubW5ubG9vcHJ0dnVyc3R1c3CFb3RwcXJycnN0dXZ4enp7fX+BhIiIioeHiJKWlJeeoZ6WlZWboKOmn5qanqKloqOmq6+ytLKwsbVgX2d0dGtna2lqbW1qa29xdHN0dnl1dHVzeH19ent7fIeNiHl0cW9vcXR2d3Vzc3JzdHZ7fIGBf4WHhYiKh2qenJqbmpqbnZ2dnJydnp2foKCdn6CioqSlo6CfoqKlpaalpaOhpJ2enZ+hn5+fnJ6dmZeWlpiXlZWUlpeYl5iWlJeZmJWSkZOTkZKSlZaWlJGVl5mVl5SWl5WSjo+Qj5GOjYyJiIaHh4iKhIuAiomHhIWHiIaFhYaFhYaFhIWEhIKChIeGhoWFhIaGhISDgID+//39/Pz79/Xz8Ovv8uzq6ujk3+Di5Obm5eTl5uXk5eXk5efm6OTj4N3b2dfW19rZ2tjW19TPzc7Py8nLzc3Mzs7Pz9DPysjLyMjJxcLCwr26ubi2trSztLa2tbQOtba4ury9vb69vb69vb6FvSK+wMC/wMHCwsLDxMPCwsTFxcXGx8jJycjIx8XGxMLBvry6hbgDt7a1hLMYtba3t7m4ubi2tba1tLKys7KxsbGwsK+vha4BrIStGaysra2rqqqrqqmqqaqqqKemp6eop6inqaiEp1ump6inp6mpqaioq6inqq6sqqiqrKqnp6eoqqupp6SnqamopaWko6SlpaSjpKakpaSlpqenp6mqqquqqqurrKysra2srK+wr66trqysrK6tra2vrq6ur7GxsrGxhLIBs4a0I7a3t7a1tba3t7a2uLi3uLi4ubi5ubm7u7q6ubu7vLy9v7++hL9/vby9vL29vr6+vb27vb7AwcG+v8G/vr27vL6/vr+8vr6+v8DBwsDBwsPCxsnJy83OzczKysnJycvJycjJxsbFxcjHxcXFxMPEw8XHxsXExMTCwsPCwcDAv76+vr+9vLy7u7y8vL29vLu6u7u7vbq5ubi5urm5uru7urq5ube4t4a1hLRUs7O0tbW1s7S0tLCwsLGwr6+vsLGwr66urq2sq6upqKmoqKempaSjpKSko6OjpKSko6OjpKWkpqalpKOjpKSjpKOipKSjo6KipKWko6Oho6OioaGjhaSEogGkhKMDpKWlhKSEow+ioqGioqSipKOioaGipKWJoyKkpaSkpaWko6Oko6OkoqKfoKKioqGgoKChoaCenZ6goaKjhKRGo6KioqGhoaKjpKWkpaempqWmpqWlpKKfnp6ho6Slpqinp6mpqaysqKalpqWlpaSjpKWlpKOko6SlpqiqrK6trKytraytroSsBqmrrK6vsYSyfbS3tbO0tLS2tre4ubq5u7y9vr7AwcLFx8jKz9HU09LT2t7b3ubp6eLf3+Tm6+3n5ebq7O7r6+7z+fv7+fX3+oKCh5KTioeLiomLjIuLj5GSkZKVmJSTk5OWm5uZm5uboaqjm5eTkJGUlpeZmJaWlZWXmJycnp+eoaCfoJ+f/4GSgf+A/4D/gP+A/4D/gLeAvoECAgQAgIaIg39+e4CBgomWioOGh5aekZinqKSxz+O2o5qemp2sucHIvKetl46Qm6ShsL/EsLOlloJ7goGJmpOPm6aeoKGel5KHhH17fYSTkYiKkpCJh5ehm5GVmImCgXpzd3t1c21xbWRdUF5ja3R8end3cGpgXmBiZGdpamloZWlsbnBwM3BvaW9+fnh1dHZ6fXZxb3Bxa2G4tLS1s7CuqaCcmZqUkpKSjoyOlpyfnp2cmpeXlpaUkYSQQIyJiIeGhYOCgYB/fXx6dnFsamloZ2ZnaGhlZmhpbGxsamlqaGdkYF5fYV9ZV1ZXVlVUVlZUU1NUVFVXV1laW1yEXRBcXF5dXV1cXF1eXl5fYGJihGNHZGVmaGhoamttbm5ubWxramloZmNgXl5dXV1gY2ZnZ2hsb29xcnJyc3Nycm9ubm5tbmxqaWhnZmViYmRlZGJgYF9fX15dXFmEWiFZWFhXVVRUVFVUU1NSVlhYVlRUU1JTVFNUV1lZWFpbWluEXBFaWFlZVVRWWFpZWVZUUlNTUIRNGk5NTExNT01MTEtMTk9RUlJSU1RSUlRUUlNUiFUTVFNSUE5QUVBQUVFQTk5SU1RVVoRXKlZVVFVWVldXVlZVVFVXVlRUVVdWVlhZWVhWWFhYV1dWU1FSUVBQUVBQUIRRgFJRUVBOTlBSU1RUVVZYWltYVlVXWVhTUUxNTU5NTE5PT09RUVNTU1RVV1teZGdoaGlmZGVnamhnYl5eX15eXl1dW1lZWltbXF1cXF1fYWFgYWFgYF9dWllZV1dVUU9RUVJUVVVWV1hWU1JRT01NTU5OT09QUVJSUlNTUVJRUE9NBUxMSkpLhEpPTE1NTU9OTUtKSkpJSUpJR0VHR0dISUlKSUtLSklJSEdEQ0FBQkA/QEBAPz8/QEBBQkNDQ0JDREREQ0NERURFRUVHR0dJSklKS0xLS0tJSIRJLEtKSkpNT0tLS01MS0xOUVFVVVVUU1JUVFZYWVhXWFlZWVpaWVlXWFhZWVhZhFyAXV5eX2BgX2FgYGBhYWJhXVtaW1tcXF1fYGJjX1tXWFtdX19gYGBfYF9cWllZV1ZXWVtcXFtbWVlYV1lbW1lZV1VUU1dbW11eYWVmZmhlY2JhYmRkYl9aWVpdXFlWVVRXWFldXl5eYWJiYF9bW19hXFpcW11fYmNmZWRjX1xaWlkIWFlZWFdYWFiEWW1bXFxdXl9gZGhrbHF0c3J0enyCgoWHhH5/f4WIiYiGhYiMj5qTj5CVT1RYYmJgX1pbX2RkZGNdXGFjY2NlY2VkY2NlZWdvdHqDgoKDgoOCgn58dnV5fH2BgHdzbWpqa2tqam94dnd1d32Bg4eHfYqKh4WFgoWFhouTjIaIiJKZj5Oen5yltsSlmpaZmJiiq66zqp+hkoyOlZmXoaqtoqSakIN9goGIkYyLk5iUlZeTjYqDgnx7fYOLi4WFioiDg42Tj4mMjoSAgXt1eHt3dXF1cGpkXGVpbnN5eHZ1cm1mZGZoaWtsbWxramtthHB5b2xwenl3dXJ0dnl0cXBwcWxlw7/Awr+7u7awraqrp6SkpaGgoKWqrKyrqqikpqiop6Sjo6SkoJ2dnZycmZiVlZWUkpKNiYaFg4KAf4GCgYCBgoOGh4aEg4OBgH96eXl6d3JwcHJxbm5vcG9ubm5vcHFxc3N1dnZ2dYR2BHV1dnaEdy14enx9fXx8fX5/gIGCgoKEhYiJiIeHhIOBgH9+fHh1dnV1d3h4eXt8fX+AgICFghWBgoCAgH5+fn19e3l4eHd1dXZ2dXSEcw10dHNycXJxb3Bvbm5uhmxka2pqbW5ubWtra2pqa2trbW5vbW9vbm9wcHFycG5ubmtrbW5vbm9saWhpamhmZmZnZ2dlZWVmZWVmZWVmZ2lra2lqamlqa2xqbGxub25ubW1ub25ta2pqbGxsampqa2trbG5ub4ZwNW9vcHBwcnFwcXBwcHFwcG9wcnFxcnJycXFzdHRzcnFwbm9ubm1ubm9vcHBxcHBvb29ubm9whXEvdHR2dHNzdHV0cG9sbGxubG1vbm5tbm5vb3BydHV4e3+AgYGBgH9/gYOBgH16e3uEegR7eXh3hXgkd3Z4eXl5eHl7e3p5eHZ1c3JzcnBub3BxcnJydHR0cnBubW1shG0Fbm5tbnGEcgtwcXBwbWtra2pqaoRpgmuEbBFramhoZ2dmZ2ZkZGVkZGZnZYVmFWVlZGRhX19fYF9eXl1dXFxcXV1eXo1gNWFiYWFiYmNjYmRlZmVlZGJhYmJiY2NiYmNkZmRjZGVkZWZnaWhqamtramlpampsa2pqbG1uhG0NbGtra2xtbGxtbm5vcIZxgHJxcXJycnNycG5sbWxubnBwcHJzcW5ra21wcG9xcXFwcnFvbWxsa2prbm5vbm1tbW5ta2xubm1ta2ppaGxvb3Fxc3Z3d3h2dHNzc3V1c3Fvbm9xcW1raWlrbG1xcXJxdHV3dnNwcHN1cW9xcXN1d3d6enl3dnRzcnNzdHNyc3JyYXN0dXV1dnZ4eXt8fYCEhoeKjYyLjpaZoJ+goJ2am5qgo6Wko6Glqay1rautr1xhZ25tbGtnZmpwcHBvaWltbm9wcW9xcHBwcnN0fIKFjY6Njo2Ojo6Kh4KAg4aJjIyEgHqEeA12dnqBgYOBgoaLjI+NeJ+fnpycm52bm52enZydnaCgn5+hoaGkpqqhn5+io6Slp6iqp6Kinpydn6Gen6GioJ+cmpeWl5iYmpmZm5yam5uamZeUlZSSk5OVlZOTlJWUkpaXl5aWlZORk5COjo+NjYqMi4qIhYeIiYuMi4uLiomFhIaHiIeGhoSFCoaGhoWFhYSFiYmEh3aGhoWEhIWFg4H+/f39+vj49fLw7u/r6+np5uTl6Orq6ejn5+bl5ebn5uXl5uTi4N/d29vZ2NnZ2tvZ1tPQzczNzs3LzM3MycrMzM3Oz8zKy8jHxsPAwMC/ure4u7q2tba1s7KztLW3ubm7vL2+vr29vby8vby8hb0mv8DBwsLCxcTDxMbGxsfIyMrKzMzLyMjGxMPBvry7uLa1trW0tbWFthy3t7m6ubi4t7W2tbW0s7OzsrGxsa+vr66ur6+vhK51r66trKyqqqurq6qpqqqpqKempqelpqepqamoqKinp6anp6anqKioqqqpqKqqq6urqampqKemp6qoqKalpaanpaOko6OlpqWlpKelo6Sjo6Wnp6mrq6utqqqsrayurq6vr6+ur66vr7Cura2ur66ur6+wr66whLIBs4e0BrW0tLa2t4e2AbeEuoS5Dbq7vL68u7q6vby8vL2Evgq/wL+/vr69vLy9h741v8HBwMDCwsHAwL2+vsHAv8DBwcDCw8TCwsLDxMfIysvMzM3OzczNzczMy8vLycfKy8rLysmEyGvHx8fIyMjGxsfHyMfGxMPExMXBwcDAv8C/v8HCwsLDxcLAv7+9vLu7u7q7vLy+vr6/wL++wL+9vLi3t7a2t7W3tra2tbS0tbW0tbW1s7GwsK+urq+vra2trK2sra6tra2srKmop6enpqWlpYSkDKWlpaanpqSlpqampYWjeqSlo6OkpKWloqOkpaSko6OlpKOioqOhoqKipKKhoaKhoaKio6SmpaWkoqOko6OkpKOkpKSjpKSjo6OkpKSjpKKipaampaWmpKWmp6WmpaalpaWmpaKgoaKioaCho6KkpaKhoJ+hoqOjo6SkpKWkoqOjoqGhoaOkpaSjhqKApKWlpaKhoaCjpaSmpqioqaqrqqmoqKipqqmppqanqqmnpaWlp6iprKutra6vr62tqquvsa+ur66wsrS1ubm4uLW3trS1t7i4t7e4ubm7vb6+v8HBwcLExcjMz9HT1NTX2d7f5Obo6efj5OTq7e7u6+ru8fP69/Ty+ICEiIuMi4o+hoiJj4+PjomKjo2NkJCPkZGQkJKSkpicn6anp6iop6aopaSgn6CjpKamop+cmpmamZiYmp6foZ+foaOkpaP/gZSB/4D/gP+A/4D/gP+AroDFgQICBACAhYmHhoV/f4WHkYSHiZCIl56UjZWgqbPCw7e1mJOam5SWpqGwrq2lkpeRhYyeqqmop6qhl4p5eo6kl4SJmqWrsqWTi4aSlJCHg4KBg4GIl4aFh4qJhYKGfXBpbXd/n4t4eX90ZFpjW1xocnt+d3FrZmJgYWRpa2lqa21scHR8fIBUgH+Ki4aGfnt5dXh9enZ1cWxoY8C4treyrKuppZ+fop+ZlZSYoKOmqaeloqCenZydnZ2amJiWlZCPkJGPjYqGhoOAfXp3dXJubG1ubGloZ2ZlZWZnhGkSbGtpaWdkX19iYVtbWlpYVlVUhFMGVFRVVldYhlo1XFxcXV1bW1pcXF1dXl5fYWJiY2NkZWZnaGlrbG1vcHBxbWtqamlnZWFfXl1dXGBmbG1ub26Gb4VwD3FwcG9ubWtra2ppZ2VlZIRjCWJhYGBgXl1cW4RaQltZV1dWVVRVVVRSUVNVVFNTU1RSUFFSU1NUU1NTVVlcXVtbWVhZVlVUVFVZXFtZVFNTUFBNTU9OTk9PTUtLS0xOT4RRDU9OUE9PT1FRUFBSUVGGUiJTU1JSU1NSUVBQUVFQT09PUFJTVFVXVlZWV1hYVlZXV1ZWhVWDV4RYOFlYWFhWV1hZWlpbWldVU1JSUVBQUFFQUFFSUlFQTk5QUVFTVFRUUk9QUVRVUVFTUk9NTEtLS01OhE8sUFBQUlNUVlZXWVxfX19gYWFkZWRkY2FgX2BhX19dXl1cXVxcXV5fXl5fYGKEZR9mZGJhYF5bWllXVFRVVlhZXF1bW11dXllZVlVTU1JShFE8Tk9QUFFRUFBQT09PTUxNTk5NTEtNTlBPT05MTExNTEpJSUhHSEhGRkdGR0hMTk9NTEpJSEZFQkFBQUJBhUALQUJDQ0NERkVEREWJRA9GSEhJSUdHSEpKSklISEiFSgRJR0lLhUoqS0xNT1FSUVNTVFRUU1VXVldYWlpaW1paWllXWFhYWVlZW1tcXl5eX19fh2EwYmNiX11bW1tdYGFiZWZkYmBeX19hYGBgXl9gX15eXlxZWFhYWVpaWFtaWFRTUVBShVM/VFVXWltbXF5iZGRkZWRjYWJjYmJfWlpaWFdYVVVWWFxeXl9eX2FlaGVgXFxbXF5dXmBgYWRoa2tpZGFfXl9ehFs5XFxaV1hZWVpcXmBhYWJiZGRoa3J1dXNze46Oh4WCgYCBgYOFhX6AhYyUn1BQUlNUU1RYY2traF5dhGA4X1xZWF1iZGVrbGhhYGBhaniFi4uVjYyPioWEhYWJioR9gYB/fHh4dXZ0c21ubnJ5eXl6gIB8foKAjI+Li4qGhoqLkYmMi5GMlZqRjpKan6SusKemlZKWmZOUnpqloqGbkJGOhoqUnZ2cmpyWkYh8fYyYjoSGkZicoZeLhoOKjYmDgoCAgH+Di4GAgYSFg4CCfHVwc3l9koV5eHx0aWJoY2VtdHh5dXJua2hmZmltbWxsbW5ucHN5d3pne3qCgX5+enh2c3V4dnNzcG1qZ8nDwcK/u7q3trGws7Cqpqeqr7G0tbOyr66sq6utra2qqquqp6Oho6OioZ+cm5mXlJGOjImIhoeHhYKCgoB/gICBgoOCg4WEgoKBfXh4e3p2dXN0coRvE25tbW5vcHBxcnN0dXZ1dHV3dXaEdTN3d3h4eXl6e318fX5/gIGBgoKFhoiJiYmIhoWDgYB+fXp4dnR0dHh7fX+AgYCBgYB/gICEgRSCgoGBf359fX17enp5d3d4d3d2doV0KXNzcnJxcHBvb29ubW1sbGxtbGpqa2xsa2pqbGtpaWpra2tqamtsbnBxhW80a2tramxwcG9tamlpaGhmZmdnZmdnZWRkZWVmZ2lpaWhnZ2pqaWlqamloampra2tsbG1sbIVtG2xra2tsa2pqamxsbW5ub29wcHFxcnFwcHBxcYZwG3FwcHFycnJzc3JycXJycnV2dXRycXFwcG9vb4RwGHFxcXBwb29wcHFycnJxcG9vcHFycXFxb4VtA2xuboVvCnBwcHFydHV1d3qEfAJ9f4SACH59fXx8fXx8hHtuenp5eXh5eHl5e3x9fH5+gH18fHx5d3Z2dHJxcnR1dnl6eXh6enl3d3Z1c3JwcG9wcG9ub29ucHFxcXBubGxsa2xsbWxsa2xtb25ubWtqamtqaGZmZWRlZWRkZWVmZ2hpamloZmVlY2JhX19fYF+GXgVfX19gX4pgGmFhYGBhYmNkY2JiY2VkZGNhYGJkZGNkZGJhh2MtZWZmZ2lpaGlpampqa2xsa2xtbm5ub25tbW1sbGxtbW5tbm5vcXFwcnNzc3RzhHJCc3Rzc3FtbW5wcnJydHV1dHJvb3BzcXFycHFxcnFwb29tbWxrbG1ta2xsa2loZ2ZnaWloaWlqamttbm1vcXN1dnZ3hHaAdXR1cm5vb25sbWpqa2xvcXFycnN1d3p3c3NzcHJzcXJ0dHV5fH1+fXl4dnV2dXV1dHR1dnV0c3V2d3h5fH1/f4CBgoWIjY6OjI6WpaahoJ6dm5ycnp+fm56gpbC6XV5gYWFgYGVxeHdza2lubm1samhmZ2pvcXJ3eHRubGxueIUnkJaWn5eWmJWQkJGQlJaQiY6Mi4eEhIGBfn55eXl9hYSEhYqJhoiKSaOioZ+fnp+enqGdn5+gnaGgoKChoqKipqelpJ+eoqShoKWkpaSioZ6gnpmbnqCfnpydnpyblpaZnpqWl5ucnJ+bl5eWl5eUlJGFkl+Vk5KTk5ORkZSUkI6PkY+WkY2Nj4yJh4iHh4iKjIyLiYiHhoWGh4eHhoaHh4aHh4mHiImJiYqKiYmIiIaFh4aFhoWEg4L//P37+vj59/Xx8PDw7urq6ezt7u7t6+rp6ITpUern5+fo6OXj4uHg397c29nZ2NbU0tLPzc/QzszLy8nIycnJysvLys3MysrIxsHBw8K8vbu8ure2tLOys7O0tbe4ubq8vL2+vr28vby9vLu9voS/BcDAwsPEhMUdyMnJyMjLzM3NzczNysfEw8G+vbq4trW1s7S1uLiEtwW4ubm5uIS3DLa4t7i2s7OztLKysoSxDLCvr6+wr66urqysq4SsFqusq6qpqaiop6ipp6apq6moqKipqKeEpiOlpqioqaqsq6inp6isqamopqaoqamop6eop6akpKWmpaalpYWkEaaoqaeop6iqqqioq6yrqqyshK5Jra6trq6vr7GxsK+urq+xsK+wsK+wsbKytLW1tba2t7W2tba1tba3tra3t7a2uLi3uLm5urq5uru7vb28vLy9vr29vLu8vr++v4XABb6+vr++hr8OwMDBw8HAwcPCwMDAv7+Ewh/Ew8TExMPDw8XHyMnMzczLzMzNzc7OzszMzc3OzczNhcsJysnKysnKysnKhcsJzMvKysrJyMXFhcSAx8jJycbHysvMxsbEw8HBwL+8vb2+vL2+vb2+vr++vLy7u7q8vLu5uLe4uLi3ubi2tre4t7WysrGwsbGvra2srK2vsLGxsK+trquqqKipqainpqWlpaSkpqioqKepp6empaampqWkpaWlpKWmpqakoqKjpaSko6GjpKSioaKjoqEdoqOko6KhoKKko6SlpaSkpKKjpKSlpaSlpqalpqaEpQ6kpaSjoqKipqaoqaempoanhKY9p6WjoqGgoaOlpaWmp6WlpaSkpKWkpKSjpKWmpaSlpaOioqOjpKSjo6SjoKCenp6goaKjoqOjpKWlpaamqYWqAauEqjCrqqeoqaimqKWlpqeqrK2trKyusbKwrq2ura+xs7W1tLS3uby+vbq5ubi7u7u6urqEu3G8vb6/wMLEx8nKysvKztHU1dXX2eDt7ejn5ebk5ebo6erm6Ovw9/+AgIGBgoGCho2UlJOLiYyMjI2MiYiIi42RkJaWk4+Ojo+UoKmtrLOur7KtqKipqa2vqqWopqWkoaGgoJ6dm5ycnaCho6OmpaSlpv+BlIH/gP+A/4D/gP+A/4CpgMqBAgIEAICIiouLi4eIi4yHh4mOmZymsK+jpajA2MWevcKpnqqkqbSymKO7vKygkYuCi4qTmK3Cu5OLlYOCkKCnlIaWn5uYoI2OjZaVnp2ZmIyGhImQio2FipWDgIJ9cWxtfIaKi3x3eG9nY11dZ3Fzc3Fva2NiY2JiZmlsbm5ucXR+h5ujpn2lpauhlZGMenN1fXp2dnFua2ttamXEvrizs7ayrbCvrKmloaGmra+vrq6rqKalpKGgn52bm5qYlpaamZaVko+MhoJ/eXRzdXZ4dXNwa2loaGloaGloZWRkZmdpaWhkYV9fX1xdXVpYVlRTUVJSU1RVVVVUVldYWVpZWVpbXIRbP1xdXV5eX19gYGFjZGRjZmZnaWxubm9wcW9tbW1ubm1qZmNgX1xdYmZqa21tb3Bvbm9vbm1sbWxrbG5vb25tbIVrH2pqaWdlZWJhYGFiYV9fX11cXFtbWlpZWFdWVldYVVOEUTxQUVFRUlFQUFBSUlNSUlVYWFZUUVBPTlBPUlJRV1tbV1VRUlNSUFBOTk5QUVBNS0pLT1NUU1JTUlFRU1KFUC5PTU5PUFBQUVFRUlFQUE9PUFFQT1BRUFBSVFdYWVhZWlhVVldYWVhWVVVWVlZXhVYMVVVWWFlaWlpYVlZXhFgtWlpZV1VVVlZUU1JRUFFSUVFQTk5OT05QUVJTUlBPT05PUE9PTk9OTExLTE1NhE8MUFFRUlRVVlZXWFlbhFwEXl5eXYReI19gYF9eXV5eXlxcXF1eXl9gYWJkaGtra25saWdkY2FhYmFhhGAcYWVnaWpqamlqZ2ReW1pYVldVVFNRUVBRUlNTUYRSDFFQUVJRUlNSUlFRUIVPKE1MTExJSUpJSEdISEpLS01OTk1LSkpJR0ZFQ0JCQ0NDQkJDRERDQkSGRYJEhEMpRUZFRUZHSEhHRkVHR0hHR0ZGRkhJSkpKS0tMTEpKSktMTU5PTk9QUVGEUwZUVFVWWFiFWQdbWlpZV1hZhFhjWVxdXl9hYmJhYmJjZGNlZmdmZGFgXmFjZGJhY2NjYmNjYmNiYF9eXl5dXV1cW1pZW1paWllZWVhWVFNUVFNSVFNUVVZYWl5fXl1eX2NiYWRnaGViYGBfX15dXVpXV1hZWFldhF6AYGRmZGRlZGNgXVxcXV9fXl1eZWVkY2JlZGFiZWRgX1taXFtbWVpcXV5kamloZ2dpa2tqbXJ0dHJ0hJGKh5CUjYV7e35/gYSGi5SbUFVaWlZSU1RdZWhkXV1eXlxaW11bWlthZnBwaGJiYmFoe4yOkI2SjpKHiI+Qj5STjIV9dnoVh4SAhIOAd3BubHB3eHd4e3x3eHt+AY6Ej4CMjI6QiouMj5aYn6ilnJ6grbyxm6uun5igm56ko5SbqKmgmY6KhIyKjpKfqqaMh4+Dgo2Xm4+Ej5SSkJSJiomPjpGQjo2Gg4GEiYWGgIWNgX9/fXZxcnyChIV8dndybGplZGtydHRycG5oZ2dnaGpsbW9ubnByeX+NkJGRkZWPiHWFhHhzc3h2dHRxbmxsbmxpzMfDwL/Dvbm8vLq2s7CxtLq7u7m4trSzsrGvr6+trKysq6ioq6qop6WioJyZl5GNi42OkI2MiYWCgYGCgYGDg4F/foCBgoCCf316e3t3d3ZzcnBvbm1tbG1tb3BwcHFxcXN1dHSGdlF3d3d4eXl5enl7fH5/gICDgoGChIWGiImKiIaGhYSDgX99e3l2c3V3en5+gICCgYCAgIGAf39+fX1+gICAf39+fXx7e3x7enp5eXh3dXZ2dnWEcxRxcXFwcHBvb25tbW5vbGtqaWlqaIZpQWhoaWhpaGhrbWxramhnZ2dpZ2lpaGxwcW5raGlraWdmZWVmZ2hnZWVjY2ZpampsbWtqaWtraWlpamloaGlqaWlphWscampra2tqamtsbGtrbG1wcnNycnJxcHFycnFxcYVwhHGEcBhxcnN0dHVzcXFyc3R1dHV1dXNyc3Rzc3KEcRJycnFwb25ub25vbm9xcXBvb26Eb4VuHGxsbm5vb29wcHFxcnNzdHV3d3d5ent6e319fHuFfQh+fX18e3x7e4Z6WHt8fX5/gYSEhIeFg4OBfn19fn19fn59fX2Bg4SGhoWEhYSDfnp4dnV1dHNxb3Bvb3BwcnFycnFwb3BwcXFyc3Bub3Bvb25ubWxra2tqaGdnZmVmZmZoaWmEaiFpZ2dlZGNiYmFgYWFhYGBgYWBgX2BiYmFiYWFgYF9gX2CGYSxiY2NiYWNiY2JiYWBhYmRmZmVlZGRkY2NiZGRlZ2ZnaGlpaGlpamlra2tsbYduAmxuhG0Ob25tbm5wcXFydHR0c3SEdS12dnd2dXNxb3FydHNyc3R1dXV0c3R0cnBxcG9vcHBvbm1tbm1sbWxsbWtpaGiFaVJoamtqbG1wcnFwc3N1c3N2enp5dnNycXJycnBvbW1ubm1ucHFycnJ0dnl4eHp4d3VycnJzdHR1dXd7e3p5eHx7eXh4e3l3dHR2dnZ1dnh4eYCHhYZlh4iIiYyPkI2PnqijoqiqpJ+ZmJmbnp+hpbC4XWJnaGRfX2Fqc3Rwa2prbGpmZ2poZ2luc3p7dG9wcG50iJeYm5icmJySk5mbm52clpCJgYaSkYyPkIuCfHp4fIGCgoWHh4SDhYhLpKOjoqKgoKGhoKCgoaGjo6alo6Ojp6qpoaemo6OnpqWmpaOjpqaioJ2bmZubnZ6goqCbmZuZmJmbnJuYm5ybmZqYl5eXmJqZmJiVhZMelJGSlpKSk5KQj4+RkpKTjo2PjIqJiIaJi4qKiomJhIcBhoSHdYiHiIiJio6Pjo2Njo2Mi4qIhoeHh4SFhIODg4SCgP/9/fz8+vj19vXy8vDv7fDy8vHw7+vr6+zr6err6enp6uvo5ufl4+Lg4N/c2dfT0tLV1tTU0tDMy8rLzMzKy8rIyMjJysvLyMbFxMXEv7+8ubm3tbOys4S1Eba3t7m6u7y+vb6+vb28vr2+hsAmv8LExcbHx8bKysrJy8zNzs7OzMrJxsXDwcC9urW0s7S1tri4t7eEuIS5griFtwS4t7a1hLQBtYSzVbGwsLCxsLCvr6+trKyurqyrrKysq6qqq6uqqKaoqKinp6Wmp6alpaSmpqalpqipqKampKOjo6Wlp6elqaqrqaemqKmop6ekpKWnp6ako6KjpqmpqKiEqgqtrKqqqqurq6qqhKyHrgKtr4WxNLKxsbKztbW0tLW3trW0tba3trW1tre2tre2tre2tre3t7i5u7q8urm4uru7vLu9vr+/vr+FwAfBw8PCwcHBhL8Cvr+EwITBFcLExMLCwsPDw8HBwsHCw8TExMXFxYTHEsjJysvMzczMzs/Q0M/Oz8/OzoTNA87OzYXMEMvMy8vM0NDQ0dHV09HQz86EzSfO0NDPz87Q0dLU1NTS1tTSzcrIxsXBwcG/vr6+vb6/wb+/v8C+vr2Evhq/vby8u7u7urm5u7m4t7eztLSzsrKysLCxsIWxOLCvrauqq6uqqaqpqainp6inp6inpqmpqaimpqanpqakpKWkpKWmpqalo6KkpKakpKOio6SjoqOjhKQRo6Oio6Kko6OjpKSkpaWlpKSEpQqmpqWmpqWlpqamh6U8pqepqKmpqaiop6mpqKanp6epqKelpKOlp6elpKanp6eop6anpqSkpaSlpKSkpaWko6Sko6OkpKOioqGfhaFJoKGhoqOlqKmopqenq6qpqaytrKqrqqqrq6qpqKamp6mqqaqrrK2urrCysbKysbGvsLGxsrW0tba2uLm5uLm7u7q8vL29vLy6vIS9b77AwsXJys7NzM/Qz87Q1NXW19vn7+ro6+vn4+Lh4+Xm6Oru9vqAhIiHhIKBhIqQko+KiouLioiKjImIiY+SmZmVj5GRkJWgra+ysLGvs6ussbKws7KwqqOgo6yrqKqrqKCdnJycnp+go6WmoqOjov+BloH/gP+A/4D/gP+A/4CngMqBAgIEAICLm6KimJKSkJiSiIeKjI+dq62zscLBysKdqbOvo6Clv8rBqKO2r6OrqJmXkJqcpKGrv6iaq5SJnqGhnaKjlI+Yn52MiIeLjJqhnpSGhIKHh4WEg4qEaGpub3Jyd4OAf3VxaGNeX2RucHN0dnRtZWdnZ2ViY2hqbm5xdniEl6i4w2fIzL6rlJiUjY2SkYiDg4B8enZ0cmplYmG8ubWztLa0tLOxsK+ytri4t7WysK2rqaSenJ2en56cnZ6foaCalpKPi4J/fXZzdHp5dnhzcG1qaGdoaWhpZmRlZmRlZGVlY2FfXVtcW1hWhFRIUlJSU1RUU1NUVldXWVlaWltdXFxdXl5eX19gYWBgYWFjZWdmaGdnaWtsbW9vcHFxdXZ4d3Zzb2tpaGdmZ2tsa2trbm9vb25uhGwnamdnaWpsbm5sbGxra2tqbGxraWdlZWRjYF9gX1xcXF1cWllZWFdWh1VNVlRVU1VTU1NSUlBRUVJWVVNRUVFQUVJWV1dWUlBPUFBUVVJOTk9QU1NVVFVTTk5MSktNUVdbW1pYVlVST1RWVFFQT05OTk1NTk9PT1CET2NOTE1MTU1OT09QUVJTVFZYWFtcWldXWFlYVlVVVFVWV1hYVlRWWFdWVldXV1hYV1ZVV1laW1hYWVlaWVdXWFdVU1JQUFJSU1FQUE9OTU9QUFBPUFFRT05QT1BRUE5NTEtLTEyETh5PT1JSVFVWVldXWFlaXF5dXV5dXV5eXl9gYF9gX2CEXglfYGBgYmNlaGyEc2dvbW1vb25sampsbWxoZ2dnZWVmZmdnaGVkYmdnZWRjYmNiYF1aWlhVVVNVVlhYWFdWWVtaWFhgYltWVVVWVVNSUVBOTEtJSktMSkpLS0xNTE1NTUxLSklIRkZFRENCQ0NFRUZFRkVFikSEQwZERkdISEeERoZHDkZGR0dJS0tKSUlLTEtLhUwMTU5OT1BQUVJUVVVVhVaEWAhZWVlYWFlZWoRZGVpbXF1fYWBgYF9iZWRlam1tbmxnZGZnZWSEY0VkZWZmZmNhX19gXVpXVVRUVVdZWlpYVlVVUk5QUVNVVFJTU1ZZWlxhY2RlZWZra2doaWppZ2ZlZGJfXl1cW1pZWVpdX16EX4BhZWdkZmhoY2FgX15eXVxbWVpdX19gZGpqZmRmampmYVxdXl1eYWNgYGNmZWVobnZ2c29ranByc3Z5en+Eio6IgX1/goSRmqCcnVJTVFhaV1ZUV1xeX2BiYWFiXVlZW19hZmpramZlaWdnaHWDg4WEg4KBhIOGiIuRlpybjYB9hBWSlo6LhH93c3VzdHZ4eHh1dXZ4gIiAkpmdnZiUk5KYlI2Mj5CRmqKjp6Wwr7WxmqGopJuYm6uyrZ6cpqKbn56Uko2TlJmZn6qbkp6PiJWXl5SXmY+LkJSSiIWFiIiQlZOMg4GAg4OBgIGGgW9xdHR2dnp/fX53c29rZ2dqcXJ0dXVzb2pqamtpaGhrbW9vcXR1foqWoKd/qauilYeKiYKChIOAfHx6eHZ0cnNuaWZlxsTAvr7Av7++vby7vMDBwMC+vLq4uLa0sK+vra6vra+wsLCvq6ekop+ZlZOOi46SkI2PioiFgYKCg4OCgoF+f4B/f36Af3x7enh1dnVzcXBvb29ubGxubm9vcG9wcnN0c3R0dXZ2doR3E3l6fHx7e3x9f4GDgoOCgoOFhoeEiB2HiIiKiYiGgn99fHt7fH5/gH9+f4CAgYGAf35+fYR8In5/gIB+fn59fX5+fX17e3l4eXh4dXR1dHNzcnJwcHBvbm6FbYRsTWtsa2xqamppamhpaGltbGlnaGhoZ2lsbW1raGdnZWdqbWpmZmdnampra2xqZWZlZGRlZ21vcXFubW1qaWxvbmtqaGdnZ2ZnaGhpampqhmk4aGlpampqa21ub29xcnJzdHNwcHBycnFwcG9wb3BxcnJwcHFxcXBxcXJxcnJxcXJ0dnZ0c3N1d3WEdA1zcnJxcHFwcnJxcHBuhW8pcHFxcW9vcG9wcXBvb25ubW5ucXBvb29wcnN0dXZ2eHd3eHl6e3t8fHyGfWZ+fH19fnx8e3t9fX1/gH+BhIiKjI6MiomIiYqJh4aGiYmIhYSFhYKCg4SFhoeCgoKGhYKDgYB/gH98d3Z1c3NydHV3eHh3dnd5d3d3fX14dHNzdHRycG9vbmpqaWlqamhoaWlqamuEag9oaGZlZGVkY2JhYWFiYmOIYj1hYmFgYGFfX2BgYWFiY2NiYWJiYWFiYmNiY2JhYmNkZWVkZGNlZmVkZGVkZGVmZmdoaWhoaWpra2prbW1th26AbWxtbW1vb25ub3BxcXJzdHN0c3N0dnV2enx7e3p1c3Z2dXR0dHV0dXV1dnd1c3FxcW1sa2tpaGlqbG1ubWtpaWdkZmhpamlpaWhqbW5wdHV2dnZ3e3t3eXt7enl4eHZ0cnNycXBvb3BwcnJxcnJzdHZ4end5e3t4dnR0dHV0c3KAcnJ1d3h4en+Afnt8f399e3Z3d3d5fH17fICCgYGEh4yOjYmHhouNjpKUlZmcoKWhm5iZnaCosbeztl5fYWRnZGJhZGlsbW5wbm5vamZmaWxuc3d5d3NxdXV0dYGQj5GOjoyMkY+RlJmfoqalmY2KkJ2gmZWQi4OAgn+AgIGDhIEFgYKCiJCApKeop6Wjo6OmpqSho6SkpaempqWqq62rpaWnp6Wko6epqKOipaOgoqCdnpyfnp6en6KfnJ6bmJybmpydnZmZmpycmJeWl5aZmpuXlJWUlJSTk5KSkY6QkZGRkJGSkpGOjoyLiYmKi4uMjIuKiYiHh4eGhIWHiImIiYmJi46Qk5RulZaUkIuMjIqKjYuJiImJhoaGhYaDgoGB/v35+Pj49vb49fLy8/Lz8vLx8O/t7Ozr6unp6evs6+nm5+jn5eHh4d7c2NXT0dPX1tPX09LPzMvKy8zJycnHyMnGx8bJyMbEw8K+v7+8uri4t7e2tbSEtgq3t7i5u729vr69h7+FwgfDxMTGx8jHhcouy83Ozc3MycnHx8XEwb+8uLe3tre4uLe2tbi5u7u5ubm6uri3tra1t7e4uLe3t4e1Q7SzsbGxsK+wsrCtrK2vrq2sraysq6qqqqmqqKmqqqqoqaenqKenpaalpqempqWmpqWlpqeopqako6SkpKippaKjo6SEp4CpqKOko6KipKerrK2trK2sq6qtr66sq6qpqaqpqqysq6ysra6wsLCvr66urrCxsbKzs7O0tre2t7m4tre1tre2tra0tra4uLi3tba4uLm4uLi5uru7urm6vL29vL2+vb/AwMDBwcHAwsLCxMPDwsLCwcC/wcHCwsHDxMTEw8TDxIDFxMXGxcPBwsPDxMXGxsbHxsbGyMrMy8vMzc/Pzc7Qz8/R0dDR0dHQ0M/Pz9DQ0dLR0NHR0NHU19ze3dzZ2djY2dnY1tXX2NjY19bU09HS1dbV09DPz9TT0s/Ozc3My8nHxsXDwsPExMbGxcXGyMfFxMTKy8PAv8DAv728vby6uAq3tbW1t7W1tLOyhLMJsrGwsK+ura2thKwJq6yrq6mqqamphKgvqaempaampqWlpaamp6empaampaSlpKWkpaSjpKOjpaSkoqGjo6Kjo6Oio6KkpKSFpQSmpqalhKYNp6ampaWmp6elpqemqYSohKkTqquoqKemp6ipqautrayrqKanqIWnF6amp6eop6enqKempKSioKGgoKKkpaWjhKIgoKGhoaKio6Kjo6Wlp6mrrK2rqa2ura2ur66vrq6rq6mEqoCrqqqqrK2trK2ur6+xsbCws7Szs7O0tLSzsrKxs7W2tre5ubu7vLy/wcC9vb6/wMLDxMTFyMrJycrMz9HRzc7P19XV2d7f3uHl5+Xj4eTn6Ozv9Pf6gIKDhYiFhIOGiYuMjI+OjY2Kh4eJi46TlZeXkpOWlJSVnqamqKenqKirqCCqra+ztrq7samlqrW2sbGqpaGhop+dnZ2foaChoqKipf+BmYH/gP+A/4D/gP+A/4CjgMuBAgIEAICRmKCfq6iNiZKXjYuTmJuoqqKqssrQu7Cipr/CnpqvxtLAp5uzwru708u1rK69qZqes72xqpKhpKGloJeWkZeUkpKHe4SHiJOQlpSGhIKKh4mHj36BcGdmZm5tcnRzc21lZmRkZ3FvdHpwaWRiZ2VpamhkYVteZGdoZ256hpOgqnC+zcC6tbKrnZyel42Iio2Lgn55cmplYV9eu7y7vLy+v725ubi6vcDAv7y4tbSzsamfnZyho6KnqammpaGcl5OOi4R+e3l3dXZ2eXt1cm9raWdmaGtraWhqa2lnZWJgX19eW1lYWFdWVlhXVVRTUlNThVQFVVZXWFmEWghcW1teX2FiY4ZiRmNlZWhoaGlsbW9xc3h7fYGDh4iIhHx3dHJycGxra2lpaWpsbGtpaGpramloZ2ZmZ2hra2xtbWxubm1sbGtpaGdlY2NeXl6EXVRcWllZWFdWVVVUVFVVV1hZWFdWVlVWVlVVUlBPVFlYVlNPUVJRUFRYW1lWUU9RT05PUVJRUFFWWllXU1RRT09PUldYXGBhX2BgW1dSUVNVUU1LSUuETgJPToVNN0xNS0xLTExNTU1OTU5PUFFTVVdZWFdVVVVWVlZXVlZWV1hYWl5dWldWVlZXWFdVVVRXWVtdXFiGVgFVhFQUUVBPUVJTU1JSUlFQUFBRUE9PUFKEUEpSUVBOTk1NTUxMTk5PTk9PUFJUVlhYWVhaW1xdX19eYGFhYWBeX2BhYWJjZWNjYmRlZmdpZ2lsbm9ycnFzdnd4d318eXd1cm1rZoRkXmFiZGVobGtqamtucW1tb25xcnFwbWxkXlpaXF9gX11cXWRncXBrc3NoYF5eXVtYVVNQTUxMS0xLTExNTk5OTU1LSUlJSEdGRUVFRENDRERFRkZGRUVFREJERERDQ0OFRBJDQ0VHSUhIRkZFR0dHSEhISUiFRxZJSElKS0xMS0xMTU1MTU5OT09QUVRWhFcBWIRXhVgjV1ZXWFdaW1pZWVpbW1tdXl9gYGJjZWVma3BxcG5ramlraWiFZoRnPWRiYV9fXVtZWVlXVVZZXWBcV1ZSUFBQUVNVVVZXWlxeXl9gY2Zma21xcG5tamZmY2FgYWFgX1xaWltdXF2EXoBgYWRnZ2VlZmdjYmFhX1xaWlpZWFhZWl1haWxsamlseHNvZ2RlY2JlZ2VkYWJjaG5yeHZxcHBtbG1xc3Nyc3d4en18fICHlZmipqpWVVNVWFdVVldcXV5cYGNkZGFeX1xfYGVqbWZmZWJjZGdqcHx8enyChIuFh4yTl5iTkpqbkRiEf4SFjJKMh4KFg35/e3uDgHx0dXhzdY6AlZmdnKOhkI6WmpGQlpmZoaOgoqi2uaumnZ+tsJmXpLG4rZ6Xpa+rqri1paCfp5yVl6GnoJ2OlpiWmJeSkIuQj4yMhX2DhYWNio+Ng4OBhoSFhId9gXRubG51cnZ3d3dza21ram10cXR5cWxpaGtpbG1tamZiZGhqa2tvdn6HkJhmoqylnpyblo2MjIeCf4GCgXx5dnJtaWZkY8XFxcbGyMjGxMPCwsTGxcTCwL69vLu2sK6usLKytLe3tbSwrKikoZ+Zk5GPjo6RkJKTjYmIhYKAgIGEhYSChYaDgX9+e3l4eXZ0c3NxhHAGbm5ub29vhnA0cXNzc3R0dXR3dXZ5ent8fn59fX1+foCAgYODhISGh4iKjI6Pj5KRk5OTkIyHhYODgoB/f4Z+Bn9+fX5+fYV8gn2Ff1h+f39+fn59fHt6eHd4dnV1dHV0c3NxcHBvb25ubm1tbGxtbm9tbm9vbW1sa2xpZ2Zqb25samdpaWhoam5wbWpnZmZnZmdqamlnaGxvbmxrbGpnZ2lqbG5yhXQMcG5qamttamdlZGVohGkBaoRpQ2hnZ2ZoZ2dnaGhpaWpsbW5ub3Fyc3JycHFxcXJycnFwcHBxc3R3dnRxcHBwcnNxcHFxcnR1dnZ1cnNzdHV1c3NzcnKEcD5xcnJxcXFwb29wcXBvbnBxcHFxcXJxcXBvb29ubm9wcHFxcHFxdHV3d3h5eXp7fX59fnx/gICBf35/f39+gISBU4CBgoKDhoSGiImLjo6Oj5CRkZKWlZORjoyIhoKCgYCBf4CCg4aKiYiHh4qMioqKiY2PjYyKiH97eHd6fH58enp7gIePjIePjoN8eXl6eXVycW9shWsPampqa2xra2poZmdnZmVkhGMVYmFhYmNjY2JiY2NiYWFgYGBhYWBghGEJYGJjZmVjYmNhhWIPY2RjYmNiYWFjY2RlZWZnhGUSZmVmZmZoZ2lqbG5tbm5tbW5vhW4nbW1tbG1tbW9wb29vcHFxcXJycnNzdXZ3dnd7fX5+fnl5eXp5d3Z2hXcWdnZ1c3JycnBubWxtbGpqa3BzcGxraIRnImhqa2tsbW5wcXN1dnZ3en2AgH59e3h3dXV2dnV0dHFxcHCEcoBxcXJ2dnh6enl5ent4dnZ3dnNxcnJyc3Nyc3d5f4CBgH+CioWDf319fHx/gX9/fX5/gYaJjY2Mi4qIiImMjo2Nj5KTlZmYl5ugrK+3vMFhYF5gZGRjY2Roa2tqbnBycG5sbmhrbXV5enR0c3BxcXR3fIiIhoaMkZaRkpecn6KcnRujpJmOjI+QlZ2Yk46SjomJhYaMioV/gYN+gJV1qKimp6mmpKSlpqemqaenqKmoqamsrKuqpqWnp6SjpqipqKSip6elpaempKKhop+dnaChoZ+bnZ6dnZybnZycnJuamJaVlpaYlpiYlZWUlpSUk5SRko+Oj4+RkJGQkJGQjYyLioqNi4yOjIqIiImHiYmHhoaGhYeAiYuMjpCRlZiVkpKRj46NjYuJiIqMi4mIhoWCgYCAgP/+/f38+vj49/f09fb29PPx7/Dx7+/s6unp7O3r6+vs7Orn5+Pi4N/c2dbX1NLU1NXW09HQzMrLysvMysrKzMzKycfFxcPCwL++vb27urm7uri3tra3uLm5uLi3uLm7vLxYvb2+vr+/vsHDxMfIx8fIx8bGxsfIysrKy83Pz83My83My8jIyMfFw768u7y7urq5uLi3t7q6urm4ubm4t7a2tbW2tri5uLa3t7i4t7e4t7WztLKxsLCxsYWwNq6urq2srKuqq6qpqKqrq6ysq6qpqqmoqKempaeqqKimo6anpqWnq6upp6ajpaOjpaanpaWlqISqCqupp6anp6urrK6EsBCvrqurrK2rq6inqausrKushK0crq+vsK+vrq6tr7CwsbGxsrO1tba2tre2tba3uYS4Fbe3uLi5uru8u7m5ubq5urq5urq8vYW/Db6+vr/AwsPDwsPCw8OHxBPDxMPExMPDxMXGxcbGxsfHxsbGhMQLxsbHx8fIycjJysuEzWvPz9DR0dLQ09XV1dPS09PS0dLT1NTU09TU1NXX19ja2tvd3dzc3N7f3uLk4uDe29jW09PS0dLPz9PV1tjX19bX2NrW2NjW29rZ2dTUzsnJyMrKy8rKysvO0trZ1NvZ08vHyMbGw8G9ubm4uIW3AbWFtB+ysbKxsK+traytrKysra2srKyrq6qqqqmqqqqpqaimhKcXqKiop6iop6anpaampaSkpKalpKSjo6OJpAGlhKQbo6OkpaanqKinqKinp6amp6ampqWmp6alp6emhKgFqampqKiEqRGoqaqrqaqvrq+urayrqauqqoSpJKipq6qqqqinpqempaSjo6OioqSnqKaio6OhoaKjpKSjpKSmp4SqGqytra2us7Gwr6+urq2sq6urrKupqKqsrKushK5Pr7Cys7SzsrO1tLW0tLOzs7S0tbS1tre4ur29vr6+wMfGxcHBw8TExsfGx8fHyMnLztHQz8/Q0NPU19fY2Nnb2tvg4ePo7O7y9vv9gICAgoSFRYaJi4uKjI+Rj4yLjYuMjZOWlpOUk5GTk5KVm6OkoaKnq66qq62ytreztLi6tKqorKyvs7GuqayqpaWjoqOioaGio6CgqP+BmoH/gP+A/4D/gP+A/4CggM2BAgIEAICHj5KTlo6Oi4qXmp2iqru/tbivprO/mpWXosW+mp+qtbuyoIyMoKKZsqqmnrC3paW3uK6or6ypoKeuqaOUkpCQmZWLkJSXmJqamo6NjYWChIGAgnVzdGdra25ramtxbXFrW2Foc3BsbmxpaWprbWlqamNfWltdY2VmY2JkcHmFkICcnqCyt7Orp66rn5qbm5qShoB4cm1oZGRlZWRjYsTCvru4ur6+wsTFxsbGwrq2sayjnqKnqKuur62qp6OemJOPiYaDgH15dXN1eHh1cnJvbGhnZ2dpaGdnaWdkYV9eX15cWltYWFhXVlhcWVhXVlZVVVZWVVRVVldYWlpbXFxdXS9eX2BhYmJhYWFiYmJkZWVoaWpsbnB0e4OHiYaHh4mKiYR/fXp4dHFubGtra2pqaYZoKWdnZ2ZlZGZnampsbWxsbG1ubWxra2loZWNhYF5dXV5eXVxbWlpYVlVUhFVHVlZWV1dYWVlcW1tXVFNRU1RVU1FTV1dWU1FQUVNVWFpaWVVOTVZXVVZVVVlVU01NTlBQUFJUVlldXl1cW1lYVFJNTU1LTU+EUAVPT05NTIVLgkqESz9NTkxLTE1NT1BRUlRUU1NUVVdXV1hXV1ZXWlxcXFpXVldXVllZW1tYVFRYWl1aV1VTVFVUVVRTVFVVU1JSU1KEVAhTUlJRUVFQUYRQAk9RhFKAUE5MS0xMS0xOT09PUFFRUlVcXVxeXl1dXWBgY2NjZWRjY2NkZWlsa2pqa2xra25wcnJxcnN1d3p+gIGCgYCBf358e3x7dnNxbm1raGpqZmZnZWNiZWlubm5tbG9yd3p7eHNvaWNhYF5fXmBgYmZrb3d4fXdxa2ZjY19dWVZTUk8BTYRMhE0VTEtKSkpISEZFRUREQ0NDQkNFREREhEUHRENDRERDQ4RED0VFRUZHSUpJR0ZGSEhHR4VIIEZFRkZJSktLTE1MTExNTk1OT09QT09RUlRWWFhYW1pZhVgxV1dWVVZWVldZWlpZWllYWVpbXV5gYmNlZ2lrbW5vb25ram1sa2tramprbGpnZWJiYYRgNl9fXltZV1dYXF9gX19XVFJTVFZaXV9gY2RkY2FfXl5eYmlubm9taGBdV1ZYXFxdXVtcW15fXoVdgF9iZ2hmZWRkY19eX2BfW1pbW1pYWFhZWl1fZWpsb2xqb3Fqa2xraGVkY2JiZGhrbWxra25vbWtra3BycHFydHV6fX6AgYKEiZGWmp6fnlFTVFJUVmBiY19gYmBfX11cXlxeYGJiZWZlY2VobHBydnyAgYWIkpKSk56aj4qOkpGOGIeCf4CChH9/fHx/hoqQj46DgXx2eXV4hICNk5WUlpCQjo6Xmpufpa+zrrCppaqzm5WWnK+tlpqgp6qkmo2Om5uTpKCdmKGlm5qlpZ+bn52alJqem5iOjIuMko6Gi46PkJCSkYiHh4OBgn9+gXZ1dnBxcXRycXJ2c3ZwZWpudnJvcW9tbW1ucG1ubWhlYmNkaGlqaGdpcHd+hRaNjo+ZnJuWk5iWkIuMi4uFf3t2cm9rhWg3Z2bMysbEw8XIxcjLy8rJycbBv7y4s6+xsrW4u7y7uLayr6ijoqCbmJaTkIyLjpGRjYqKiIWBf4WAGYKDgn98enh5eHV0dnR0c3JxcnV0c3JxcnKEcThwcHFzcnJzdXZ2d3d4enp6fH19fXx9fX2AgICChISHiIqMj5OWmJaXlpWVlJKOjIqIhIKAf35/foR9MX5+fX18fHt7e3p6e3x+fn5/fn5+f3+Af39+fHt4d3Z2dXV0dXRzc3JxcG9vb25ubWyEbUVubm5vb3Fwb2xramhpamtqaGpsbWxqaWdoaWlsb29ua2VkbG1tbmxrbmxqZmZnaWdoaWttcHFycnJxcHBsa2ZmZWVmaGmGahhpaGdnZmZnZ2ZnZmdnaWppaWpra2tsbW6FbwVwcXJzc4RxInV2dXRzcnFycXFzdXh2dHBvc3V2dnRzcXJyc3NycnNzdHOEcgd0c3Nyc3NyhHGAcnFwcHJyc3R0c3NycW9vbW5tbm9ycXFyc3R2eXp8fH5/fX5+f4CCgoOEg4KCg4ODhIeGhYWHiIiJioyMi4uMjY6RlZqampubmZmYl5SSlJOQjYyKiYWEhYaDgoGAgICChYmJiYiIjJGUlZaUjouHgH5+fH18fX1/g4mNlZSYko0Nh4KBf356d3Vwb25sbIRrD2xsa2loZ2hoZ2ZlZGRjY4piBGNiYmKJYQ9iYmFiYmRlZmRjY2NkY2KEYw9kZGNhYmJkZGVlZWdnZmaGZw5oaGhpa21tbm9vcXFwcIRuL21tbGtsbW1ub3Bwb29vbnBxcnJzc3R2d3l5ent8fX59fHp8e3p5eXl7enl5dnRyhnOAcnJxb21rbG1vcnRzc25saWpqam5ydHV3d3h2dXRzc3J0en5+gHx4dHNvbnBycXFxcHFxc3R0c3JycnR2eHp7enl3dnZ0dXd4dnRzc3R0c3NydHR2d3t+gYOCgYaHgIOEg4B/f39+f4CDhomHh4eKiomIiYmMjo2Oj5GTl5qbnJ5Tn6GmrbK0t7i4XmBhX2FkbG9xbW1vbm5tamlraWttcG9zdXJwcXV5fH6BhouNkJSenpycqKSalJienJeSjYuNjpOMi4mJi5CTl5WUjoqGgoWChIszpaelpKWio6Okp6qrrK6wsK6vrq+wrqilpKapp6KjpqSmpqShoaOioqakpaKjop+foqKghJ84nZ6gn56cm5qam5mYmZmbm5qamJeYmJaUlZOUlZKSk5GSkZKRkJCSkZGPioyLjY2LjIuKiouLjIqEiSSGhYWGh4eHhoeIiouNkJCQlJSSjo+SkI6NjYyMjIqIh4aEg4OFgTSA/v75+Pf49/f4+ff29vX08vHv7u3r6+3t7/Dw8O7t6ufl4uHg3dza2dfV0tTX1tXU09DNhcoaycjIysnGxMPCw8LAvr68vL27ubu9vLq6ubmFuh+4uLm7vLy8vsDAwcHCxMXGyMjGxsXGxsXHx8bKy8rNhM8Q0M/QzM3MyMfGxMLAvr28vIa6iLkNuLe2tbW1tra4uLe3t4W5Hri4trW2tbOysbGwr7GxsbCvr6+trayrrKuqqqqpqoSrH6qtrauopqenqaeoqKWnqaiop6alpKWmqKmpqaahoaeFqCasqqikpqeqp6eprKyqrK2ura6usKytqqqrqamqq6yrrKytra2ur4auBK+urrCEsQiys7O0tLe2t4S2MLi5ubm6ubi4uLm7u7q7urm5ury9vb69vLy8v77AwcHAvsDBv7+/wMLDxMPBwcLDxYTGgMXIx8bFxMbExMTFxcfIycnKycfExMXFw8TGyMnJycrKy87T1dPU1tLR0dPU1tbY2dnX19XV1tbZ2djY19rZ2dna3N3b3d7g4ePm6Onp5+bl5OXi4eLh4N/e2tfU09PU09LS0dDP09fZ2dfU09ba3+Hf4N3a1dHQz83My83MzdDVJNnd4ePf2NHOzMvKx8PAvr28vLq5uLi3tba0s7OysrKxsrCtromshasHqqqrq6qqqYSoT6mpqaqpqamqqaioqKmmpaWlpqamp6WkpKSmpqanp6alpKOjpKSmpqWlpaamp6ipqaipqqmnqKeop6enqKempqanp6ioqaipqaioqaiqq6uErFOtrq6vrq6tq6qsq6usrKusraqqqKmnp6eop6ipqKiop6WkpKSnqaqpqqajoaOlpaiqqq2vra2qqaqqq6qsrrKxsrGvrKupqKmpqqurqqysraytrYSuArCyh7UHs7K0trW0tIW1Gre2t7q6vsDAw8TCxcjEx8nJysjIyMfIys3NhM8H0dHP0tTW2YTaWtzd4uXl5+rr7vD09vr8/fyBg4SCg4WMj5GNjo+OjY2Li4yKi42Pj5KTkpGRlJebmp6ipaWorLW2tLK7uLKusLS0saypqaqqrauqp6iprK6wrqylpaSjpaOiov+BnoH/gP+A/4D/gP+A/4CfgMqBAgIEAICNiY+PlJuZlJOQk5+to6yyu8W5ubG2t7SutsW7sq28sra5sJSaqKaTlZqjoaawtLWrpa+4sbSjpKShpqKakYmSjJCSjY6RkJSXpaifjY+ShH54bHFvamhqbm5sZWdpaGdkW2FrcGdmaGlsbWtpZmZlYl5YWl9hZGReW1tia3F7gW2Ok5acn52fp6qnoKChm5iPiIF9eXVzcW5sbGpmxcPDwMDBwsLExcjMzMzKyMG3sKunp6uwsLK3tbW0s6ulnpeUjo6MiYN8eHV2e3t5dXZ1bmpoaWpqaGdpamxqZWFiZmFdWlpbWlpZWVlaWllaiFhPV1dWV1dbXV5eXl9gYGFiYmFgYWJiY2RmZmlucHF0eHt+hImIiIaFhIWGgnx6e3p6dnNvbGtqamlqaWhnZ2ZmZmdnZ2ZlZGNlaGlqamlpaYRqgGlpaWdmZWRiYF9eXl1dWllZWVhWVVRUVVRUV1ZcXl1aWFlbXF5aUlJVVVNRT01OTk9RUVBNTldaW19eVlJQTlFWVFVVVFRUUU9QUFJVVFJRVFVYVlNUVFVWVVBMTUxNUFRVVFZWVVFPTEtLS0xMSktLS0pKS0xMTU5MTU5OT1BROlJRUlJUVVZWWFhVVVZZW15eXFlYWFlaXF5eXVtaV1VVV1lZV1ZVVVRVVldXVVVVVlZWVVVUVFJSUlOGUoBRUVFQUVFSUlRTUVBPTk5OTUxOTlBRT1NXXGFjZWhoYl9eYGFjZGRmaWloamtra21wcnJyc3V1dXd6enl3eHx/hIOAgYGAgIGDhISDgYSFfnt5fHdzeH56dXNubWloaWpqZ2ZmZ2Vnb3Ftc3d6enRsZmBcXFxbXF5faXd5gX91biZraWhmY2BZWFdVUk9OS0pKS0pJSkpJSkpHRkRFRkVGRUREREJCQoZDH0JERUVFRERFRUZHR0dISEpMS0tGR0dIR0dIR0dHSEmESApJS0tMS0tMTU5PhFCEURhSU1RWWFlaXVxbW1pZWVhXVVRTVVZVV1mEWoBbWVhYWlxeX2JlZmVpbG1vb29sampra2xucG9vbmxqaGVjZGVkY2NjZWdkYmJeW1tcXl9hYV1bWllYWV1fX2NmZmRgXVxbW1xdYmRlZmZkYV1aWFdXV1hYWlpZXV5eXl9eXV1eYGJkZGZjX2BgX15fXl5eXVtbXF5gXV1gZGZlZQJnaYVsdGpoZmdnZmZlZmlsb21sa2tsbW5vcHJzc3N0d3h8f4GChIWKipCTkZWbT5ucUFJWW19gZWVjY2RnZ2ZmamZjY2Jpamlocn6EiYSAiI6RjYaHhYuQlpWRioKDg36HjImEhIGBfHd3eXx+g4uRjoaHhXp3eoaQgJGOkZGUmZmWlpOWoKahqK2xt7Cyq6+wqqaqs6qlpK2lpqikkpafnZGSlpybnKOkpZyZoKahpJiYmZeZmJKMh4yJjIyIio2Mj5GZmpOJiYuCf3xzdXRwb3F0dHNvb3Bvbm1manBzbGxubm9wb25ta2pnZWFiZWdpaWVjYmhucXd7IoWIiIyOjZCUl5WQkJGMi4WBfHl3dHJycG5ubGnOzMvJycmEygHLhc1Fx8C7uba1t7u7vsLAvr6+t7OtqKWhoZ+dmZSPjI6TlJGNjo2IhIKEg4KAgIKEhoN/ent+enh2dnV0dXV1dHZ1dHNzdHN0hHNKcnFyc3N2d3l6enl6e3t8fX18fHt8fX6AgYSHiIiLjpCRlJeXmJeUk5SUkYyJiouKhoSDgH9+fn59fX58fHx7fHx7e3t6enp8fX2EfG59fn5/f35+fXt6eXh3dXZ0dXR0cnJxcXBvb25tbGttb25yc3NvbW9wb3FuamptbGpoZ2ZnaGhqaGhnZ2xubnJybWpnZGdsbGxraWpqaGdnZ2psa2lpbW1vbm1tbW5vb2tnaGhoam1sbG5vb2xraIRnA2ZlZoRnDmhoaWlqaGlrbGxsbW5uhG8zcnNzcnFwcXR2d3Z1cnJyc3V2eHh5dnZzcHFzdHV1dHNzcnN0c3NzdHR1dXV0dHV1c3NyhXMHdHNzc3Jyc4R0gHNzc3JxcXFwb3Fyc3NydXh8gYOFh4iEgH+AgYSFhYWHh4eIiImIiYuMjIyNj5CQkJOTlJKTl5qenZuampiYmJydnJubnJuXlJKXkY6Ul5SQj4uJhoSGh4eEg4KDgYKLjYuRlJaXkomCfnt6e3p7fH6Gl5mfm5OLiYmIhIF9eHZ0D3NvbGtramtramlnZ2hoZ4VlB2RkY2JiYmGHYiBhYGJiYmNiY2NiYmNjY2RkZWVlZmNkZGRiYmNiY2NkZYVkTmVmZ2ZmZ2dnaGlpaWhpamlqa2ttbm9wcHJxcHFvb29tbWxra2xsbG1vb3FycHBubnBxcnJzdXd3d3p8e3x+fnx6enx7e3x9fX18enl4doR1EXR0dXd4eHd1cW5ucXN0dXZyhW4WcnR2d3l5d3VzcnFxcHF1d3h5eXh2coZuhG8Pc3Rzc3R0c3N0dXZ4eXx5hXYld3Z2dXZ1dHZ4d3V2eXt+fH2BgoSFg4SEg4CAgoOBgYKDhYeKioSJY4qMjY6PkJCRkZWXnJ6foKGkqamtsbC0uV24ul5fYmlsbnJycXFxdHRzdHdzcXFxdnZ3dn6KkJSQjJKYmZaRkZCWm5+fm5WOj5CLlJaUkZGPj4mDg4WHiY6VmJaQkY2Fg4WNk0WnpaSkpqenp6mqq62wsLGysbKzubSzsrCsqquoqKappaSlpqKho6KhoaCioKKhoqOioaGjoaGcnp6cn56dm5mcmpqYmZmEmlubm5qWl5iWlJOSkZGRkJGSk5KQkJGRkJGNjoyOjIyMi4uLioqKiYmKiYeGh4eIiIeGhoiJiouNjo6PkZGQj5GRkY6Pj46MjIuJiIeGhYWEg4KDgf/+//z8+vv6hPlF+Pj39fPx7+/u7+/w7vDv7+/t7uvo5uXk4eDe3NnW1dPV2dnY19jW0c/MzczMy8rLy8zKxsHDyMTBv7++vb6+v7+9vby8hbsRvb29vLy7u7y/wMLDw8PExcaExyDGxcbHx8nJy87Ozc7Pz8/R0M/OzMvLysnGxMLCwMC/vYW7Bbq6ubq5hLgLube2trW2tre4uLiFuQO4t7eFtgW1s7OztISyH7Cvr6+ura2srKyrq6yrrq+tq6qrq6utqaenqqmop6aEpS+np6ajo6ipqqyrqKeloqWpqKmpqKeopaWlp6mrq6inrKytraysrq+uraypqamoqoStBq6vra+trYSvO66ur7Cvrq+vsLGzs7Kys7O0trW0tbW2t7i4urm3uLm6u728vLy7u728vb++vr2/v72+v8DBwL6+wMC/hMALwsTExcTDxMXGxMWGxoDHx8fGxcXIyMrLy8nKycjIyMbExMfIysvKzM7S2Nna3t7Z1tXX19na29vc29zb2trb29rb29ze3t/f3+Hh4uDg4+br6ejo5+Tk5ufm5eXj5Ofm5OHj393f4+Df39vZ1dHU19fS0dHT0M/V2Nfa3eHj3tjUzsrJycfIyMvV4uPp6B/e19bV1NLPysPBwMC+u7i6ubm3trW1tLKztLGxsLGwha6CrYSsI6urqqmprKuqq6moqamqq6urrKurqaioqKqpp6amp6anpqenhaZBp6enpqWlpaamp6ampqeop6enqKqsqqqqrKuqq6moqaioqKemp6enqKmqq6uqqqmoqampq6usrK2srq+vr7CwrqyErQqurq6vrqyrrKqqh6sNrKuqqqmmpqiqq6qqqoWnNKmqq6yvsK2qpqipqaipra6vsK+urKuqqainqKmpq6urra6urrCwsK+wsrK1tri4tLS1tLWEtoC3tre4uby7u7y/wsHBw8TGx8fIysrKycnLzM3O0NHQ0tPT0dPU1NbY2tra29ze4eHl6Orq7e7z8vT5+vn+gP7/gIKEiIyOkpGQkJCSk5GSlZORkJCUlZWWmqSqraimqa6xrqmpqa+ys7SxrampqqassK6rrKqrpqGio6Wnq6+wrAiop6akoqSkp/+BnYH/gP+A/4D/gP+A/4CfgAOBgIDIgQICBACAi5ONk5mipp+Ym5mitsW2rrjOwLW6r6ilr7mom6GiqMfg0tXIuLrLzseypLHAv8jDuLCps8TNwq2mpaajmYN/i4GAhoqGhYGPkpSSjoFwa3eBfHZ5c3B6fm5qaXZ5c2tlYmlvamdjY2doaGRjYF9hZmhgYGVkXVpfYGBlZWNicHRmcnh/gIWPkZCYnJiZmJOOiYeHhn57eXVxcG5saWZlxsbEx8bHx8rO0M7MyMXAu7Sxr66zuLy+vrm5vMC9raKZl5WTj42KiYV/fHp7fHp7dm5ucXFubGlnZWVlZGNkZWVkYV1bWllahFmAW1paWltbWltaV1dWV1hZXFxdXmFiYGFhYmNjYmVmZWRjZWhrcXd7f4OJjo+NjIiHhIOCgH17enp6e3d1cnFvbm1ra2ppaGhnZmVmZmdoZ2ZlZGRlZ2hnZWVmZmdnZ2ZmZ2dmZWRjYl9fXlxbWVpaWVhXVlZXWFhXVldaW1xaWFdPV1hXVlRTVVVUUlBNTU5PT09RUlJXWltaVk9OUFBPVVNXWlhYV1dZWVRQVVlYV1VWVlNQTUtLT1NSU1BOT09RVVhbWVhVU1NPSklKS0tMS4VKU0hISUpKS01PUFFTU1NVVVRTVFhYV1daXWFgYF1ZV1dXW15jY2NfW1tZVldaWVpZWVdWVldYWFhWVVZYWVhYV1ZVVVNTUlJUVVJSUFJTUlJSU1JThFKDUIVRgFNVWl5wcnF0cm9oZGRobmxqamlqbm9zdnR0dnl7foOFhIODg4F/f4OJiYeFhIaEg4OGh4iKioqIiIeDfnp9d3R2eHd1cnBwbW1ub3BtaWNlZmdqamltdHV1d3RuZl9dXl1bWltaaW90f4J7cGpoZ2dnYl5aWFRSUU1LS0lISElKGkpISUhJSEhIRkZFRUVEREVERENCQkFBREVGhEURRkVFRkdISUpKS0tLSUhIR0eGSB5HR0hHSElLTE1NTE1PUVBRUVJRUVFSUVFRVFZZXF6EXQxbWlhXVlVVVFRUVVeEWRhaWlpZWlpbXFxgZWZnaWhrcHJ2cW1qa22FboBsamhnZmZmaGhlZWlrbGtpamZjXl1dXmBiYl9eXltbWllYWl5eXlxbWllZWVpcXV5gX19fXVtXU1NVV1ZXWFhbXV5eXl9fX2BiY2RkYl5cXWBgX15fXl5dXWBiYmBgY2ViX19gZGZpZmRna21qaWxramtqaWtubW5vcHJzdXd3d192dnl6e31+f4KGiIqPjpOWmJaWTlNUVldXWmFiZm1ucnZ4dHl4dXRybmpqbWtxf4eLkZeWlZSQjpOSk5eZmJaUjImIkpOQjZORhoeGhYKAgH9/f4aJhoOFiYt+f4GCioCPlZGVmZ+inZicnKKtta6qsLyyrLCqpqSqrqOYnJ6gs8K5urOrq7W2s6WboqyssK2nop6lrrSsn5mYmpmSg4KJg4GFiIWGgouNjYyJgHdze4N+eXx3dXp9dHJweXx3cW1scXRwbmtrbW1uamtoaGltbWdma2plY2ZmZ2pqaGdwc4BxdXp7foSGhYuNi4uLh4WCf4CAenh3dHFxb25saWjOzczOzc7Mz9HS0M7My8nDvb28u77CxMXFwMDEx8S5sKmnpqShoJ6fm5WTkpOUkZOOiomLjYqGg4J/gIGAfn5/gH97d3Z1dHV0dHNzdnZ2dXR1dnV1c3NycnN0d3h5ent9ezF7fH18fX1+f359fn+AhIuOkZKVmZycnJuXlpSTkpGPjoyKioqIiIaEgoCAgH9/f359hXwOe3x8fHt6ent8fXx7e3yFfRJ8e3t6enh4d3Z1dXRzcnJxcW+JbmxvcHBvbm1vbWtsa2psa2poaGdmZ2dmZ2pqam1vcHBsZ2dpaWlqa2xubW1sbnBwbGltcG5sbG1tbGpnZWdrbWxta2lpaWxtb3JycXBubmlmZmZnZmdnZmdnaGhmZWRmZ2lqa2xtbm9wcXBvb3CEcSR0dXh5eHZ1c3Jxdnl7e3t5dnRzcXN1dnd3d3Zzc3V1dXZ0dXWFdw52dXRzcnN1dnd0dHJ0dIRzgHJ0dHV1dXR0dHNzdHR0dXd8gJGSkJWQjIaDhYmNi4qJh4qOjpCRj46RlJWYnJycnZydnJubnqKioaCdnpycnaCgoqSjoqGgoJ2ZlJaSkJKTk5KPjo2KiYuMi4iGgYOCg4aGhouPkZKWkImDfnt8fHt5eXqIjJGdopmOiIaEhIN+HHl3dnRwb21ramhoaGloaGdoZ2hnZmZkZGNjYmKEYwdiYmFgYGJjhGSDY4RkD2VmZWdoZmZmZWRjZGRkY4RkCmVkZGRmZ2hnaGiFaoJphmoEa21vcYRzB3JxcXBvbmyEawVsbnBvb4RwQm9wb29xcXN3eHl6ent+gYOAfnx8fH19fHx9e3l5eHh3d3h4d3d5enx8ent4dXJycnF0dXRyc3Zzc3Fwb3FzcnJxcYVvAnByhHQOc3NybmtqbW5vbm1vcnOGdYB2eHp6e3p3dXZ5eXd2eHd3d3h6fHx5eXt+fHl4en+AgoKAg4WFg4OFhoaHiIiJiouMjY6Pj5KUlZWUlZeZmZudnqCkpqmsrLGztLKzXWFiY2NkZ29wc3p6foOGgYaFgX9+e3l5end9ipCVm6Kin5+bl5+dm6Ciop+clpWVnJ2cmRidnJOTkpKOjIyLioqRlJGOjpGSiImKio9lpaalpaepq6qtrq2vsbSxsbSzsrCxsbCvr62op6enpaqtqampp6epqammo6OkpKalpKKgoqWlop+fn6Cfn5qZm5mXmpuZmpmbm5ybmpeVlJWWlZWVlJOVlpOTkZSUk5GQkJCRkY+EjBuNjIuKiYqLi4mIiomHh4eGh4mIh4eJi4mKi4uEjiuPkI6Ojo2Mi4uKiYmHiIeFhYSDgoGB///9/v39+/r6+/j39vb09fPy8O7vhPEz7+/x8e/r5+bm5eLe3tzc2tfW19nb2drX0dDU09HPzMvJyMjIx8bHyMjEwMDAvr69vb28hL6FvRC7u7q7vL2/v8HCw8XExsbHhcggxsbGycrLzs/R0dLU1NPS0s7Ny8rIyMbExMXFxcG/vr6EvQq8urm6vLq5uLi5hLgEt7e4uYW4Gre4uLm4uLa3t7a2tbS0srOysbGvsLCvrq2thKw3qqqqrK2trKusqamoqampqqmpqqmmpaampqempqapqqysqqWkpqWmqKerrKqqqKmtr6uoqq+uroStEKysq6urrKytrKutrK6wsLGGsBmurKyurrCwr6+trq6ur6+wsLKytLS2tra3hLgBt4S5A7i5uYS8Gbu7vbu9vr6/vr6/v72+v7+/vr+/vsDCwsKEw3PFxcXGxsjHyMbIx8bHyMfHxsbGyMjJy8zMysvLzMrJyMnKy8zMzc/S1+bo5Ojm4tza29/g4OHi39/g4+Xk4N7g4ODh5ubn6Ojq6Ojp7PHx7uvp6+jm5unr6+zt7Orp6+nm5Obi3d3f4N/d29vX19ra2dbShNA70dLT1dzd3uLe2tTOy8nJx8XGx9Xa3efr49jV1dTS0crGw8LBv727ubi2tbO1tbSztLKzsbKxsLGwr66ErQesrKuqqqishK0aq6uqqausrK2sq6uqqquqqamnp6ioqaioqKeEpkWlpqipp6anqKmop6ioqKenqKeoqKipqqyrrK6urKyrqainp6empqaoqqqpqq2rqqqpqqqqq6qrra2srKutsLK1s6+sra6FrxKtrq2sq6qrrKysra+trq+vr6yEqxGqqqurqqqrqamop6iqrKupqYWoUKeoqausq6ytq6upqKepq6usq6ysr7GxsbKysbO1tra3t7a1tbe3t7a6ubq5ury+vb2+wMLBwcHCxMbIyMfJys3LzM/Q0NLT09XW1tjZ2tzdhd5e4OLj4+Xn6Orw8PP39/n7/fz9gYSFhYaGiI6PkpeYnKCfm5+enJybmZeXmZicpKqtsba3tLKwr7Sysba4uLW0r62tsrOxr7SzrK2traqop6anqKyurKqpqaekpKakpP+Bn4H/gP+A/4D/gP+A/4CcgMyBAgIEAICOk5ibnJ6osJeVj5alpZeZnK+4u8W+oomTkY2NjpmerKegyMmyxsi9opOmubu0uLK2u7G31tfEsp6jp6illH5+gYOEfn6Eg4qKkpF5amdrdWtscW1wcXBvb2ppb25sa2lnZWNjZGJgYGNkYmFgXmFnaWlpZWhlaGhkZmZhZGdjX2xeaGt1fIN/d3yEiYuKiZGWl4+BfHt5d3VzcW9vb2xqZ2jS0dLRz9DS1tbRy8W+urW1t7q+xMjEvru7vLy2q6ScnJyXkJSZlo6IgoB+f3t0cG9tbm1qbGppZ2djZWVjY2FfXFtZWFdYWFdaWlyEW09cXFxbWVdXWFhaXF5fYGJjYWFgYWJhY2RjZWRobHZ6fIGEio+SlJOPiYaEgoB9fHt7enl3dnZ1c3FwbmxqamdlZmVlZGRlZmZlZmVkY2VlhGYEZ2ZmZoRnC2ZlY2FgYF9eXVtahFhkV1hYV1dYWllWWFtZWFhZWVhVU1NQUVRTVVVUUE9SUE9TVVVaWVVUU1NWVlNRVFZYXmFhX1taWFZSUFBWWVpdXVlWVFBMSlBYWVRSUU9OUFNYWVhUUVNUUk9LTFFMTU1MSkpLTYRMNUpJS09RU1RUVVVVVFNVVlZWWFxfYmNiYF5eXlxfY2lramdiXFlXWFpcXl9fXlxYWFlaW1tZhVgVV1hYWVlUU1VXVlZUVFVUU1NUVFNThFSAVVRTVVRWV1hZW15sfICDfHNycnR2cnFsbG9ub3BycnR3eXqAhIqQjpCQjouKjIuNkJCKiImJiImMkJWUkpOQjYyGgn95d3V1dXR0bmtvcXBsbW1samVkZmhramlnaGhrbnF3cW1qY11cXmBeXF1lbnJ4d3V2dG1ramdlYVxZVVEUTEpJSkpMS0lISEdHSUpJSElISEeESAxGRUREQkJDRkVFRUaER0hFRkdISUlLTEpLS0lHSUlISEdGR0dISUpKS0pKS01OTlBRUlRTVFNTUVFTVFNSVFhaW1xbW1xcXFpXVVVVVFVUVFZYWlxcW1uEWoBbW1tcX2JmZ2lrbXBxb29wcXBxcnBvbm9uamhnZ2lsbm1qa2psbGpoZmJfXVxdXWNpamJfY2BbWFhZWlxeW1dbXF1cXFxeYF9dXFxdXV1bWFZXWFpbWVZZXF5eX19gYWJkZWVkY15cXF1gYmBdXl5gX2BhYmNjZmZiYWFlZ2dlYwdjZGpua2pshGsKbW5vb3FydHV5e4R6XnuAf4GDhYuQlJWXm05QUlRWU1JTVVhbXWFka250dXmAhYWDfnt3dXBsa2hpa36EgoKHjZGUmJeUk5icnp6goaCalJaZmZiYkIyKioWGiJCSmpCKiYaBfX6AhoiDg4qAkpWZmpudpaiZmJSYo6SanJ6orrC4sqCRl5SRkJCYmqKgnLO0qLKzrJySnaippqijpqmkprq6rqSWmZydnJCBgoSEhIGBhYSHh4yNfXRydHlycnZ0dHV1dXZxcXV0c3Fwb21ra21rampra2lqaGdpbG5tbWptamxraWtqZ2lraWV/ZWxudnp9fHd5f4KDgoCGioqEfHl4eHV0c3FwcHBtbWpq1tfY1tTU1dXU0s/Mx8PAwMLDxsrMycTCxcbFwrmwrK2tp6OmqaeinpmYl5mVjoqKiImHhoeFg4KDgYGAf399e3d1dHR0c3RzdXZ3dXV2dnd2dnZzcnJzdHV2eXl9fIR9LXx7fH1+fX5+goWLj5KVl5uenp+gnpmWlJORj4yNjIqJiIaGhoWEgoKBf358fYZ8D318fHx7enp7fH18e3x8fIV9dHx8e3p5eHd3dnV0c3JxcG9ub29vbnBwb2xucG5vbm9wcG1ra2hpa2tsbGtqaGppaGtsbXBvbGtqamxsa2ltbXBzdXRzcHBvbmtqam1vb3NzcG5samZla3FwbWxqaGhqbXBycG1rbG1samdmamdpamlmZ2hphGg0ZmZoa21ub29wcXBwb29wcXFydXd6e3t5eHh4d3l9gYKBfnt2dXN0dnh6e3p5eHV2dnZ4eIR3E3h4d3Z3d3d1dHZ3d3d2dXZ0dHSEdYB2d3Z2d3d3eHh5enp5fIKRm5+hnJSTkpSXkpKNjY6Ojo+RkZKUlpecn6Spp6inpaOjpaWnqaiko6OkoqKkp62tqqunpKOgnpqVkpGRkpKQjImLjYuIiomHhYKAg4WHhoSEhYWHjI6Qi4qHgXx6e3x8fH2Eio6TkZCRkYqIhoOBfRV6d3Jva2ppaWpraWhpaGdmZ2hnZmWHZghkY2JhYWFiY4RkAWOFZAplZmZmZ2hnZ2dmhGQaZWVkZGRlZmdnZ2ZmZ2hpaWtramxsbWxsa2uEbB1tb3BxcnJyc3JycnFubWxra2xsbW5vc3NzcnBxcYRwgHJzdnh6fH1+f4B/f4CAf39/fn5+fX17eXl5eHx/fXt7ent7enh4dXNxcHFyd3t7dXN2dXBtbm5wcXNwbXByc3JycnR2dnRycnNzdXNwb3BwcXFvb3BydXV2dnd4eXt8fHx7d3Z2d3l5eXZ3eXt7fHx8fX6AgX99foGBgYB/gIKHdImGhoeHh4iJi4yNjo+QkZKUl5iYmZmbnp6foKSprbO0tbdeX15fYmBgYWNlZ2luc3p8gIOHjpGQjomHg4F+e3p2d3iJj46Ok5qeoaOin56jpaioqqurpZ6fpKOjopqXlpWRkpSZnKWZlZSSjYmJi46QjY6RLKioqKmpq6uuq6upqq6wra6wsK+wsbCuq6uop6WkpqWpqKaqq6irqaekoqamhKUOpqejpKanpKKioqGio5+GmoCbm5qampuamJaVlZiVlZWUlZWTk5STkpSUlJKSkZCOjo+OjY2OjoyMjI2NjYyKiomKiouKiIiJiIeIh4eHiYmLjY6NjI2MjY2Mi4yNjo6KiIeHh4aGhYSCgoGBgID//f/+/Pv7+/r5+Pj28/L08/T19fTz8fDy8vHt5+jp5+Th33Xi5uTg3tzc3t/b1tTT0dHSz9DOzszLycnIyMfFxL++v7+9vL6+vb2/v7++vb6+vry7u7u9vr+/wcHGxsfHyMjJycjHx8bJyMvMzc7Q0dLS1NXV1dPQzcvHx8XFxcbDw8HAwcHAvb28u7q7ubi5ubm4ubi6urmFuhe4uLm5ubi4ubi4uLe4uLe2tbW1srKxsYSvJa2sra2trKyurqqrrausrK2trKmoqaeoqqqqqammpainp6mqqauFqUeoqaimq6qqrq6srq2urq2rqqmtra2vr66tq6yrqa2wsa6trKytrq+vsLGwr7Cys7GurrGvsbKwrq2vsbCxsrGvsLK1tra3uIW5HLe5urm5uru8vb69vLu9vL2+wMHCwsG/vr69v8GFwxrBwsTFxsXExcXExsfHyMfJysrJysrJysjHyITKgMvNzc3OztDRz8/Pzs/P0M/P1ufz9vXx6unp6ezo5+Lj5eLi4+Tm5uXm5ejr8/Px8vLw7u7x8PP3+PTy8fDu7u/x9fPx8vDu7+/o5OHg39/f3t7c2dzb2NTW2dbT0NDT0dPT0tHR0dPX3N7a2NXQycnLzMvKy9TZ3OPg29za1dTUEc7KyMbDwL65uLa5uLm3tba0hrMDsrGyhLEksK6ura2srK2urq6traytrK2trKusraytrqysrKuqqaipqqmnhKgUqampqKeoqaqoqqqqq6qrqqmoqKiEqRyqrK2urq2trKyqqainqKinpqepqquurq6tqquqhKlmqqytrq2trq+wsbGysbCwsbGxsLCvr7Gwr62tsLKxr7CvsbGurq6trKuqqqmsr7GrqayrqqenqKqrrKmnq6yrq6qrra+trKurrK2vraqqq6yurq2rq6yvsbGytLa4ubq5uLm2tba4hLsQvb2+vb+/wMLCxcbFxcbHyIXJEs7R0M/R0dLT09bW2Nrc3N3e4ITiX+Pl6unq7fDz9fn6/P6AgoOEhYSEg4aIiouOkJWYnp2fo6ipp6Oin52ampmWlpakqqmnrLCxs7W1tLS2uLy7vLy8uLa2uLi5uLGwr6+trq+0tbqyrq2tqqenpqiqp6ap/4Gigf+A/4D/gP+A/4D/gJOA0oECAgQAgI6VnKGsqaedjpGTnKCklZObnai3v7ymiYJ6ho+Xk5ijo8DJ3Pn55sWbjI6lsKfDw5mnpsjSv6mop6uinY+DhpORkouKf4GBko2RlINzaWhvZmhydG9tb3FtaWdoaGptbWtramhlYFtbW1paWmJraGNcWVthYmZmZGJlY2NnZFxagFxgZ3Fydnx7gIKFjpagpKKUiIB/fn19fHl1dnZ3dXNvbmxramlqbG3Y19XQycK6trW6u7vDyMjGwbm1t7ewqqelpaKfmpmWj4yBfXp4dHJwcXFxbWZraWlnZl9eXmBfXl1cWVhXVlhZWltbXFxdXVpcXF1dXFpYV1ZXWVtdXmBhMGFgYGFjYWJjZGdrcXZ7fH2BgoeJi46Oi4iGhYWCgYB+e3l2dnNwcXFxbm5ubWlmaYRqJmlpZ2VkZWVlZmZmZ2dmZWZmZ2dmZ2dnZWVjYWBfXl1eXVtZWVhWhFcfWFlaWlZXV1RVVlRTU1JSU1FSVVVUU1NST1BRUVJQUYRSTFBPVlhVU1JTVFhaWlhYW1taWllWUVddYWFeW1hTUlFRVVpaWVlWU1RSUFNRT01PUVBPUFVTTk1QUlFRU1JTVFNQTUxNTk9SU1VWVVWEVoBYW1pdYGNlZWRlZGNiYmRqbGxpZ2NdW1tdX2JiY2RjXVlaXFtbXFtbWVhYWFZYWlpaV1dWWFdXWFZUU1NUVVZWVlVWV1lYWFhXV1paXGBtd3mKS4OAhYB1dXFubXFzdHZydHl7eHiCjY2RlJiZl5KRkI+PkZOWlpaUk5aanZ6fn1Obm5ucm5iTkoyLhYN+enp0b25tbWppa2xsa2dmY2dqampra2prbW9ubWpoYV1eXl9eXFxiam9ycHB0cnBubGllXltXUk5MSklJSkhHR0ZGSUpISIRHI0lLSklIR0ZFRERERkZGRUZGR0hIR0dISU1KS0tMS0pLSklJhEcMRURGSElLTExMS0tOhFEEUlNTUoRRflJUVFRWWFlaXV5dXFtaWVZVVlRTVVZZWlxdXl5dXVxaW1paW1teY2dqa25ycnNzcXFxcnJzc3JycG9ua2hnam5xcnJsaWhra2loZ2RjX11bXF5hYl5iamdjZmtpYmBhXFxdXl5eX2FhYmFcXVxZWFhYV1RaXl5iX15dYGJkZYVkD2VmZmRhYGFjY2JhX2FiY4RmgGlraGZjY2RlZmVkY2FlaGlpamppampra2xtb3F1dXZ3e3x8foGBgoOFh4yRmk9RU1ZWWFhdYGFhYWBfZGdqb3mAg4SHiomIhoJ+c3JzcHB0d36EgH+CiJKTlaOgn5yamp2jnpeZmJeRkJCOi4WDhICJj5WTkZKckImIgn1+foGHA4yHhYCSmJ2fp6WlnpSWlp2fo5iXnJ6lrbGwo5CLhIySmJSXnZ2wtsTV08e0l46QnaOer7CVnpy0ua2bn56fnJiNhYiQj5CJiYKEhI6Ji4+DenRzdW9xeHp2dHV3dHFwcXFycnJxcXFvbWpnZmZkZGVqcG5qZ2RlaWlrbGppamlpa2ljYhRkZmxzdHZ6eX19gIWLkJOQiIF8e4R6gHd1dXZ2dHJwbm1tbGtsbW7Z2dfUz8rEwMHExMXMzs3LxsG/wMC9uba0s7OvqaqopKGXlJGQjoyMjY2MiIOGhYWCgHt6ent7eXh3dnVzcnNzdXd4d3Z3d3Z2d3d3dnRycnFzdXd4ent8fXx8fH18fX1+f4OIjJCRkZWWmZqcnp+dLZmWlJSTkpCPi4qIh4WEhYWFg4KCgX18fH5+f39/fn59fHx7e319fn59fHx9fYZ8EXt7enl4d3Z1dHNycXFvbm5uhW95cG1ubmxsbWxtbGpra2prbm5sa2xqZ2hpaWppamtraWhnaG5vbGlqa25vcG9vb3NycXFwbmlvcnV1dHJxbGpqam5ycnFwb21tbGtubGloamxramtvbmlpa2xrbG5tbW9vbGtoamtsbm9wcXFxcnJxcnFyc3V5fH18e4V8DH2AgoF/fnx4d3V3eoV9Cnp4eHp5eHp6eXmEeAd5eXp5eHd3hXgGdnV1d3h5hHgBeYR7V3p5fHx/g5CZmq1apaOmopaVkY6NkJWVlJKTl5iWmKGqq62usbGuq6qpqautr7Kxsa6usbS1tre2srGxs7Guqqmmo56cl5WTjoyKiIeGh4iIh4WDgoGDhYWHLYiJjIuKiYV+fHt7fXx7fIGIjI6LjJGPjIqHhH97eHRxb2xoaGpraGdnZmdnaIRmCWdmZmdoZ2ZmZYRjhmRIZWZnZWZnZ2loaWhpaGZnaGZlZWRkZGNiZGVnaGloaGdnaWpra2tsbW1sbGtsa2ttbW1vcHBxc3RzcnJycW9ubm1sbW5vcHJzhHQNc3JycXFycXN2eXt8f4SBAYCEgUiAgIB/f359enh4enx+gYF9e3p8fHp5eHZ1c3FxcXJ1dnN1fHx3eHt6d3Z2cXJzdHNzdHZ1d3Zyc3NwcHFxcG1ydXV3dXV1eHmEeoB7e3t8fH17eXp8fn18e3p7fH2AgYGCg4SBgICBgIGBgYB/gIKEhIOFhYaGh4mKi4yNkJOTk5WXmpqdoKGhoqOlqa+4XmBgZWVnZmlub29ubW1wdHh9hoyOj5OVlZORjYmBf4B9fYCEipGMi4+TnJ2grKmopqSlqK2moaOioJubnBmbmJGPkI6UmqCenJ2lmZSSjYiJiIyRlZCNfqqqrK2ur66uq6usrq+xrq6xr62wsbCxramnpaepqKipqa2tsLKxrqqloaKmp6aqqaOko6eppqKhpKWhoJ+dnqCenZubmpydnpucnpuZlpWYlZWWmJeWlpaUk5OUlJSTk5KSk5KRjo2Njo2NjI2PjoyLioqKi4yMiomJiYqJiYSHBImLi42EjguNjZCRkpCOi4qKiYSICYeGhYWEg4KCgYaAQf38/Pr59vXz9PX29PX39/bz8e/w8O7r6urp6ebl5eTg4N3b2dnW09TW1dTQztHOzczLxcPExsTDwsLAv729vb6/hMATv769vr6/v769u7q7vL2/wcPFxoTHCMjGxsbFyMrPhNAvz9DS09PS0tDPzszLycjHxcPBv8DCwcHBwL69vby6ubm7vLy7u7u6ubi6urq7urmGulO5uru8urm3t7e2tbWysrKxsK+vrqysra6urq2ur6ytrKmqq6uqqqmpqKeprKyqqKiopqinqaqpqaioqaenpqmqqamqqKmrq6utrK+vr7GwramtsISyc7CurKqqrLGxsLGvr6+urbGwr66vsrKxs7Wzr7CysrCwsrKztLWzsrKztLW2t7i5ubq7ubi4ubq6vb/Awb++v7+/wL6/wsLCwcHAwMC9v8LEw8TGxcXExMXExMbGxcXGx8fIyMrKysnKy8zKycjHysvLzM2Ez4DQ0tTU1NPS0dLT1tfl8O36g/r3+vbr6+fj4uXp7uzl5+nr6+3x+Pr++/v79/T09PX19/v9/Pv6+vr9/v7+/Pj5+fz69/Ly8e7q6Ofj5ODd3NnX1dPU1tbV09LN0dXW1dbU09PV19bX1tbNycrLzMrIytPX2tvX19nY19TQzsrGwyDCwL27t7a3ube2trOztba0s7OzsbGysbGxsK+vrq2trYWuAa2Erjutra6wr6+ura2sra2sqqmqqqqoqamqq6usqqqoqKqrqqqpqqusq6uqqqqpqqqpqaurra+vrqysrKupp4WoA6qrrYSuDq+urKuqqamqq62ur7CxhLMCsrOGsjawsbGxsK+ur7G0tbWysK+wsK6vsK6urayqq6ytr6yusrCtrrCwr62uq6ytrqurrbCwsK+sr6+FrCCrrbGwsbGxsLKztbe3uLm6vLy7vLu6u7y/v7++vcDBw4TFAcaEx37IyMrKycjJyczNzs/Qz9DS0tTU1tjb3+Hf4ODi5OTn6urr7e/x9Pj8gIKDhoiIiIqOjo2OjY2RlJWZn6Smp6mrrKyrpqOenJ2bm5ygpamlpqirsrO1v7y7ubi5u766tre3trK0s7Oyraytq6+yuLa1tbmyrq2qp6eoqayuqaf/gamB/4D/gP2AAYH/gP+A/4CJgNaBAgIEAICNlJmgo6uoqJiSl5KeqJyirqieobS5rZucjn6HiIicqKzGsLva5bq1taCXoqutvMHCoqWyoK3Hv7eWjpOkmIR+dYaLio+DiKKIhIVxZ3BydG1oa290b3Bxb21qZ2hoaWppaWdkXVhTUU9PUVZfYWBcWVNXXFhbX2NjYGJiY15ZWGpfZmx0eXd3d3V4gY+do6eimZKIhoSBf4B/fHp6fHp2dHJxb21rbG1s12vV0s/JvLiztLe3wMvR2NHJvbizrKiloqWpq6Cem5aOf3p4d3h1cXBvbmxqZ2VjYmFdXV5dXV9fXVtZV1dZW1pYhFofW1xdXVxdX19cV1VVWFlbXF1fX2BgYWFhYmVoa3F0eIR8GH+AgoWGh4eIiIqLioeCgH57eHNycnJwb4RsD2tqa2tpa2xubWppZ2dmZYZnDmhnZmdnZmZlZGZjYmFhhF4GXVtbWVpZhViAWVlYVldVVFRUU1BRU1JSUFFUVVVTUlFPT1JUVVNPUFNTU1hbWl5gYWBcXGFgXmJmYV9fW1VWWFxgYmFgWFRTUlNUVVZVVFdVVVVTUFBSUk9PUVFSU1RQTk1RVVhaWllZV1ZUVFNTVVZUUlRVVVVXV1ZXWlxZXF5gYmNkZGRhYWKAZmhqamlnZWFgY2RjZGVkY2JhX15eXl9fXVtbWllZV1pcXV1dXFlYWFZXV1ZWVFNUVVZXWFhZWltcXFtbXF1ga3+BhYtNTEmJhH56dHuAf4aIgn6Dh4eDipSWkkxOUJ2cl5SVkZGTlpxPT52cnk5PUJ+fnZybnJmWlZKQjoyDgH97fXp2bmxsa21zc29raGRkZmZoam1sampqa2hmaGhnZmNiYV9dXV9jY2dtbWptbm9taGBcV1JQTk1KS0pJSEhIR0lJSUhHR0ZFR0dJSkdHR0VFRkZHRkZGR0hISUpJSEpMTU1LSkpJSElLTEpHR0ZGRUVFSElJTExNTU5RhFMQUlFTUlJRUFBSU1NTVldXWoRcBV1cW1lWhFUcV1pbW1xdXV1cW1tbWltbW11hZmprbnBzdHNxcYRya3N0c3JxcG9samxvc3Nxb21sbG1ta2ZmZGRjYGBhY2VoaGlpaWdqbm9rY2JhYGBfXl5hYWJjY2RnZF9eYWRlXVpaW2RjX15gYmRkZWVlZGRlZWdnZmVmZmVkYmBhY2VlZmdnZ2ZjYmFiYmNihmF0ZGVmZ2doaGlqa2tsbnF1dXZ6fHyChYiIi46QmZtPVFhaXVpdZW9xbW1xcnZ6eXp+f4WPlpSTl5KOjIeCf354dXyChIaJiIqMjJCQnZSGi5eYlpWTmpybm5yYlo+HgH9/goyOj46RlJuYlY+Lh4OAhIyNiYldk5iboKOpp6SblZiUnqSco6umn6Crr6icnpOIjY2OmqKntaizxcytqqufmZ2mp66wsp2dpZigsKyoko6RmpSGg36Ii4qMhYmah4WGeHF4eXp1c3R3eXZ3eHd0cnFyhXEzb21pZWJgX19gZGlqaGVjYWNmY2RnaWlnaWhoZWJiZmpudHh3d3d2d32Hj5KVkIuHgYB/hHyAeXd5eXh1dHJxb21tbm5t2GzY19TPxcG+v8LByNHU2NPLw8C9ubWysLO5uq6srKqkmJSRj5CNjIyLiYaEg4GAfnx5eXp4eHt6d3Z1c3N0dXRzdXZ1dHV2d3h3d3h4dXJxcXN0dnh5e3t8e319fn+AgYOJi46QkJGRk5SWmZqbmZcQl5mamJaSkI6Mi4eFhYaFg4SBAYCEf4WAB399fXx8fX2Ffid9fX18e3t8e3x6eXl4dnZ2dXRycnFwcHBvb25ub3Bwbm9ubGtsa2qEa1FqbG1sa2pqamhnamtta2hoamtrbXFydHN1dHFxdHRzdnl2dXZybm5wdHZ2dnVwbm1sbW1tbm9ucG5vb21rbG9ua2ptbWxucGxqaGxucHJzcnKEcBZubW9wb25wcHFxcnFxcnN1dHV3eXp7hHwTe3t/gICAf39/e3p8fn5+f39/foZ8BXt7ent7hHoMe3x7fHx6enp4eXh2hXeAeHl5enp8fn1+fn19gISPoaSosF5cWKqlnpiUmp6dpqmkn6GlpKCosrGtW1xdt7Swrq2pq6+ztlxctrS3XFxctre2tLO0sa6sqqempZ6amZaTkYyKioiIjY2Kh4aDgoKChIWIh4aGh4mHhYeHhYSBgH99fHx+gYKGioqHjI2MiIMgfXp2c3FvbWpramhnZ2dmaGhnZmZnZmZoZ2hoZmZmZWSFZYRmIWdoZ2ZoamppaGdnZ2ZmZ2dmZGVkZGNjY2VmZ2lpampqa4ZsDm5sbGtqamxsbm5vcHBxhHMFdHRzcG+EbgVwcnNzcoZ0AXOEcVtydnh8fn+BgoSDgoGAgIGCgYCAgH9+fXx7fX+Cg4KAfn1+fn18enl3d3d0dHV3ent7ent9e3x/gH13dnd2dXV1dHZ2d3h4eXt5d3Z4eXl0c3N0enl3eHl6ent9hX4Vf39/fn9/gH9/fn5+f4CAgIGCgoF/hX57fXx9fX5/gIGBgoOEhYaHiImKi4uQlJSVmpyboKOmp6qsrra4XWJmZ2tpbHN7f3t5fX+DhoaHioySm6GfnaCcmJWTj4uIhYOJjpGUlZSWmZiZmaifkpaho6CfnaKlpKamoqCak4yLi4+WmZubnJ6loZ2ZlpOOi46TlZGQXaqqrK+wsrCwrKusqq6wrrG0s7CvsrSxra+qpqipqaqtrrGxtri3r6ysqairsK6vrKulpaWjpKelpaGgoaSjn56bnp2dnpycoJ2cnJqZmZmbmJaXl5eZl5eWl5eUlYSUOZWUkZCOjo2NjI2Nj46NjIuKiouJiouKi4qJioqJiIiIiYqLjY2Njo6NjpCSkpKPj42Mi4uKiYqKiISHToaEg4KCgYGAgYD/gP78+/v28/Dy8/P1+Pj5+PTx7+3q6eno6enq5+fm5+Td2djW19TT1NPT0c/NzcrIx8PExcTDxcTCwb++v7/Av72+wIW/PsDAwMHBvbq6ur29v8DCxcXGxsfGxsfGyMrOz9DQ0NHQ0dLT0tLS0c7NzczLysjHxsPCwcDBwsC/vb28vLu6hbw8vb28u7m7urm7vLy7urq7u7u6uru7uri5tre2trWzsrKzsrGvr62srq+urq2trqusrKuqqqqpqquqqaiphaoRqainqaqrqqioqaqoqqqrrKuErVGur62vsrCvsbCtra+ws7OztLKxsLCvsK+wr7CxsbGysK+vsbOwr6+xsbK0sa+vsrO1tra1tba1tLW1tba3t7e4ubq5uLm5ubu8vL29vr6/wMCEvxTBwcLCwcLDwcHDxMTDx8fGx8fGxYTGAciExw7JycvMy8rLzMvMzczNzYTMAc6Ez1DR1dXX2djW1tfX2uH0+vn9hIOC/vry7+vx9/b4/vnz9Pf59vn+/vqAgIL/+/n4+ff2+P3/gIH//f+AgIH+/fv6+vz59vXy8vHw6eXl5OLf24TXatvc2dbV0M/R0tTW1tXT0tPV09DT09TTz87NzMvMzdDQ1NbU0dXX1NDNysjDwL69vLm5uLi2tra0tba2tbSzsrGysLKysbCwr66vsK+ur7Cwsa+vsLCvsbOzsrCurq2sra+vramrqamoqKmEqxqqqamqq6ysq6urqq2rq6uqqamqq6qrrKusrYWuBa2rqqmphKobrK2trq6ur66urausq6utr7CxsrO0tbW0s7SzhLVBtLOysrK0s7GysrW2trWzs7S0s7Kysq+urqytra+wsbKxsrOxsrO2tbKura2tsK+usK+xs7S1tbOxsrO0tLCur66Esx20tri6u72+v7/AwMHBwMDBwsTDwsPExcfHx8bHx4TGfsfIyMfFxcfHysrLzM3Oz9HR0tLU19na3eDh4OTj5Ovu8fH09/j//oCEiImKioyTmJyXlpqbnqCgoKKkqa+ysLG1sa+tqqeko6Gfo6ioqaurra+vsLC7tqywtre2tbS5ubq6urm4s66oqamss7O0s7S2vLm1sa+urKqqrKyqqP+BqYECgIH/gP+A+4CDgZSAg4GKgAiBgYCAgIGBgf+A/4DcgNeBAgIEAICRk5Obn6WfqqKim6Ciqqm3xrOtsr68sJWSoY19gpCZqKqjp7zDube6zsSzvdPUv8bIrqOxo7C5n7ywoJSOiZCGeH+Kl5aKjHZxeHduYnlyZmFkZWhvb3JxbWpqaWlsamhoaGVjV1NOTk5PU1NTUlJSU1BQVllZXGRiXV9hY19bXBllb3iBkpCMfnd6fYaYrbGonpaQh4SCgYB9hXxhenh1dnRxb21samjQ09XTy8C9wsnPzcvQ2NrZz8C+vLitpKSwsa6eko+NhYB+eXt7d3JubG1ta2hnZWZnZWFfX2BiYV5aV1ZXWltZV1dZWllaW1xeXl5gZGBbWVlaW1tcX4RgEmJkZWZoa25wdHZ5fHp7foCChoSHKoaHiIeEgYB/e3h1dHRzc3Jzc3BubGxsbW1sbW1ubGpoZ2ZmZmRkZGVmZoRlgGZlZGRlZGJgYF9dXl9eXlxbW1lZWllZWVhYVlVUVlhYU1ZWVFZXWFdUUlJSUVJTUlNcYF9dWlhaXV1eYF9jZ2tsa21ubWpramlnZGFfWVZcX2FfWlVTUVFXWFVVWV5bWFVTUlBSU1RTUlBQUVJSTk1QVVxlZmNgW1hWWVlYWFlZgFhWVlZYWVpbW1tcXV1cYGRmZmRlZWJhYmRpa25vbWpnZmRlZGVlZGNjYmNiYmRkYWBfX19hXVxdXl9eX19dW1lZWVhaWFZWV1ZXWFpcXl5eYGJlZGRjanqFiktWW1BHiIOEhoOHikpSUk9OUU1PTlBSUVJVUlJOTpuamZyeUFBQFFFTVFNPTU2ampmWlZSWk5KPjIaChIRMf318eXh0cW5saWhoaGdlZWVjY2Zoampra21qZ2dsbWtoZGJgX2BgX15gYmJjZGZnZV5XUlBOTUxLSUhHR0dIR0dISEdHRkZHRkNFRoVFCUdJSUpLS0pJSYRKB0xMSklJSUiESidLS0pJSEdHR0hKSktMTlFRU1RVVFZWVFRUU1FQT1BSVFZXV1hZWluEXDdbW1pZV1dXWFhbXFxcW11eXl5cW1tcW1xdYGVpbG5wcHJxbm9ydnZ1dXR0dHNycXFydXd1c3FxhG85bW1raGhpam5ucHZzb2xraGVnaW1ucG5oaGZmZmVdXV9iYF9hY19gY2ZmZ2ZgXl5fYF9fYmRkZWZmhGcSZWVmZmVkYmFfX2BiY2FgYGFfiF44X2BfX2BhYmNiY2RlZWZnaGlqbW5vcXR2d3l/gYKEjJKWTU9RUlhVWGNmZmhscnd2c3d6fH+Ag4mEjz+LiYmMioeHh4iIi42PkJGTkZGWmJuajoOBgYSMjZGYoqWmpqObkIeEg4eMi46Uk5igp6mopaGWlJOOjYqKjJBtlZiZnaCkoaajo52goaepsbyvqquysqqZl56RhomUmqSmoqa6vrWwrry2rLK/wbW4t6WcpZqiqpmqo5iRjYqOiH+DipKRiot7eX19eG5+enNvcXFzdnd4eHVzc3JydHJxcXBubGViX19fYGFhYYRgKF5eYmRkZmpoZWZnaGZjZGtyd32Ih4N7d3h7gYyYmpWQi4eDf319fXuFeWF3dnR0cnBwb21ratbY2tfQyMXIztHQztPZ2djSx8bEwrmys7y+uq+moqGdnJqVlZKQjIiGiIiHhYOBgYF/e3p6e317eHZ0c3R2d3VzcnR3dHV1dnh4eHp7eHZzc3R0dnh6hHwtfX+AgYGDhoiMjpCSkZGTlpeZmZmYl5iZmJeXlJGPjYuIh4iIiIeHhoSDgoGAhIIOgYGAgIB/f359e3t9fH2KfBF6eXh3d3Z1dnV1dHNzcnFxcIRvK21ubW5ub2xubmxtbm9wbmxramlqamlpcHN0c3JvcHNzcnV0dnl7fHx9f36EfGB6eHZ1cG1xdHd2cG1tbG1xcm9vcnN0c29tbGxtbnBvbmxra2xramprb3R7fHl3c3JvcXRzcnNzcnFwcHJzdHR1dXV2dnV3e359fX1+fHt7fYCBhIWEgoB/f4B+f35/fn6EfwKAf4R+Bn1+fHt8fYR+gH18e3t6eXl4eHd3eHl7fX1+gICEhYaFhoaOmqarW2VpX1irpqenpKaoWmFhXl1gXV5dYGJgYGNgX1xbtbS0trdcXV5fYWFgXVtatLOysLCsrqyqp6Wgnp+goJ+cmZiWlZGNiYeGhYWFhIKDg4GBg4SGhoaHiYWDgoaHhoSDgH9+ZYB/fn2AgH6AgoKEg314c3Jwb25ta2hnZ2doZ2hoZ2dpaGhnZmVlZ2ZmZWNkZmdnaGlpaWhoaGloaGpqZ2ZnZmZmZ2hoaGloZ2VkZGRlZ2hoaGpsbW5vbm9vb25tbm5sa2pqa21vhHABcYRyBnNzc3JxcIRvhXMFdXd1dXOFcghzdXp8fYCBgISBE4KEhYWFg4KCgYGAgIKEhYSCgYKEgDd+f397ent9f36AhYSCf318eXp7f4CBf3t8ent7e3V0dnh4d3h6d3h6e3t9fHh2dnd4eHh8fX1+h3+EgA1/fXx9fXx9fX18fHx7hXoDfH18hX12fn+BgIGCg4OFhoeIiYuLjI+SlZaYnZ6fpKqtsVxdX2BlY2dydXR2eoCEgn+DhoiNjY+Um5uZmZeWl5mWkpOTk5WYmZqdnZ6dnKCipaOakI2NkZiYnKOsrq6vraSako+Pk5iYmp+fo6qvtLGvqaCenZiXlJKTlVurrK6vrrCvsLCvra6xtba3uri1tLa1tK+srquoqKqssLKzucHAvbi0tbKytrm3tLW0rqmqpqeoo6emo6KioKKfnp6foqGen5yanZ2amJ2cmpaYl5eYmZmYlpaXhJYGlZWVlJORhI+EjoCNjIuLioqKi4qLjIyKiouMi4qJioyOjpGQjo+MjY+PkZOUk5GOjIuLi4qJiYiIh4eHhoWFhIOBgYCBgYD+///+/Pf18/T39/b1+fr59fPw7+3s6evv7u3n5OPi397d29za2NXR0NLR0M7Ny8rLysjExMXIxcLAwMC/wcPBvr2+wTfAvr+/wMC+wsTAvLy9vr6/wcPGxcbGx8jJysvLzM7R0dDR0NDQ0dHS0tDP0NDQzcvKycfFwsPDhMIVwcLAvr28vL2/wL+/vb69vLu6u7u7hLwEvby8u4S6A7i5uYW4abW0tLOzsrKwrq+vr62traysq6usra6rrKyqq6ysrKurqquqqqmpqK+xsa+vrq2ura2trq+ws7Oys7OxsrW1tLOxsLGurbGztLOzsK6urrOzsa+ytLa1s7GwsLGxs7KysLCwsrOwr7CztIW3d7a0tLa2t7i4uLe3uLm6uLi5u7u9vr+/v8LBwMHCwcDBwcPDw8TFxMTEw8XGxsfHxsfHx8bGyMnIx8jIycrKysvMzc7Oz8/Ozc3Ozc7Ozc7Pz8/Q0dPW2Njc3t3b3Nzk8vv/g4qNh4H++fr6+Pr9goeJhYWHhIODhIVmiYeFgYD//f3//oCBgYKEhoSCgID//fr5+vb49PLv7uvq7ezs7erp5uTj39zY09TT1NPS0dHS0dDQ0dTT1dXW09HQ1dbV1dLQ0NDPzs3Lzc3Lzc7Ozs3LxsLAvr28vLq5uLi4uba2hbU4tra0srO0srKxr7CxsbCxsbKysLCwsbKxs7OxsbCura6tra+trayrqaiop6qrrKysraysrK2ura6ErRSsqqqrq6qrrKusra2ur6+vrq+uroWtLqysra6ura2wsbCvrqurrKytrq+wsbS1tra1tbSztLa3t7e1tbS1tba1tbe5uLeEtj+1trS1tbSysLG0tbW4uLe1tLOxsrO1tri2sLKxsbOzrq+xtLOys7axs7O1tLa2s7S0tba3t7m6ury9vcDBwsKEwYTCEMHDwsPCwsPExMLCw8PDxMWExnnFxsfJysvMzc7P0NHT09TW2dnZ29/h4eTo6+3v8/f8gIGBg4iGiI+UkpOXm56em5yfoaSkpqmvsLCwrautrqupqqusra2vsLGws7Kxs7W5t7CsqamqsLCzuL7BwsPBubOuqquusrK0tri6vsLFxMK/ube3srCuq6ur/4Gqgf+A/4D6gIWBh4CSgYWAioH/gP+A2oDagQICBACAj5GOjo2Ok6qTkJWrvsKysbKzrqazwbqvnp6fj4abp7vHtqirr8a9uMTMxcS6t73UxtPHssCii4WWqbCQj6Cfj4N2fJqWiHNtZWpraWhpa2VhZnBwbXZ5eHZ4dW9ub3Bta2lnX1JPT1BRVFhXVlRRVFVQUVJWYV5bXFxVVlpbXWCAcX9+fJq5t52OhX+EkqWloZuXk4yIhIB+fXt8fX19fnx7fHp2cnBvatDPzs/PzMnK0tbZ2tvf3dTUzsPExcO3q6KdnJmWjYmGhYGBgH9+e3h3eHhxbWpqaGdoZmNhX15dXFtYWFpbXV9eWVhYWltcXV1fYF9gZ2hkX15eXl1eX2ElY2RkZWZqaGhqcXBwc3h7fH1/gYOFh4eIhoeIiIaEg4F/fHh2dYR2EXd0cXBvb29ubW1sbGtqaWhmhGMQYmNlZmZlZGRjY2NiYmJgX4RegF1eX19fXl5dW1lXV1dWVVdVVldWV1ZUVFVXWltbVlNUVVRWWlpdX2BeWFVWXmBfYmhrbG1xc29qamtmaGllZGJkZGJcWVtcYGFiYVxVV1hVVFdZWFdVUlNUVldUUk9OT1BQUVJUW2FpcXNwbWhgXWRmXlpbW1paWVpbXmBjY2FiHGhrbGpoZWRiYmJjZWRiZGRpbG1raWdoaGdnZWSFZQtkZmdnaGVhYWJgYIVfEmFgXltaW1paW1tbWlxdXl5fYoRkgGNobXJ5gYpQX2BcUU5Ki4pJT1NVV1RXWl1cWFdeYFxaV1FPTU9SU1NTVldXV1VTVFNRTkyYlpWXlZKRj4+NjImGhH99fXt1c3NzdXV2c3FtamZmZmdoZ2ZlZmhqaW1ucG5paGpqZmFhYmFhYFxaWllcX2BeXFlUVVNQT09PTktKYkhJSElIR0dGRkVGRkZFRUVGSkhGRkZISUpJSUlLTE5QTk1NTUpIRkdKSktMS0lMT01MS0dFRklLTExLS05QUlRVVVZYVlZVVFRTU1JRUlRUVVVZWlpaXFxbW1xbWlpZWVhbhF2AXFtcXF5dW1taW1tcXWBkaGpsa2ttb21vcnR1dnd4d3Z1c3J0dXZ3dXNzdHZ2cnJzdXVzcG9wcnRzdnRvb3BtaWhobGtta2VjY2RkY11WV1paXF1eXV1eY2NmZmZlZWNjYl9iY2ViYmRmZmRjYmNlZGRiXl9eXF1fYV9dXFxdXV6AXFxdXV1fYF9fX2BgYWFhY2NkZWVnaWprbW9vcXBxcnR4dnd5fYCFh46cTlJWYG5vbWtxdX+HhYSLi4mNj4uHiIuPkpCOlJiTkpOSkZicm56foKSinpyenZeUj5CPkJGXoqOip6ScmZeZlpqdpKWqsre6tLOzsrK3saagmpaRjYgBjYCWmZeXlZaZqJqYnKy8wbSysrGtp663saqfnp2TjZyjr7mwqa6xv7eyu8G8urSwtcS9wrmpsJyNiJSfoo+PmZmRiH6ClJKJfHZyeHd1c3R1cW9yeXl2fH19e3x7d3Z2d3VzcnBqYmBgYWJkZmNjYV9hYl9fYGJpZ2VmZWBhY2RlZ3hzfHt5jKGgj4aBfYCHlJSRjoyKhYKAfnx7ent7enp6eXh5d3VycW9r1NTV2NbU0dPW2Nna2t3c09XSzMvLy8G5s6+traqinZucmpqZmJeUko+Pj4yJh4aDg4OBf317eXl3dnV0dXZ5e3t2c3N2d3Z3d3p6eXp9fnyEdxx4eXp8fX5/gYGCgoSEiYiKi46QkZOVlpeZmZiZhJgel5WTkY+Ni4qIiomJiomHhYSEhIOEg4SCgYB/f4B+h3wEfXt7fIV7T3p6eXh4dnZ2dXZ2dnV0dXVzcXBwcG5tbm5vbm1ubWxrbG5wcHFtbGxsbW5wb3BydHJvbW10dnN1eXt+gIOEgHt8fXp9fXl4d3t6eHJwcnSEdydzbm9xb29wcnJycG5ub29xb21qamppaWxtb3N3foWGhIF+eHV7fXeEdCZzc3J0d3h7fHt6fYCBgH99e3t8fX1/fn1+foKEhYODgYGAgIGAgIR/CYCAgYOCgoF/foZ/AYCEf4B8fHx7fHx9fX1+f4CBgYOFhoaGh4uPk5mhqV9tbmtgX1utrVpfYmNmY2Zpa2pnZmxva2lmYF1bXF9gYGBiZGVkY2BhYV9dW7OwsLKxrKuqqKampKKfm5mamJSSkZCRkpSRjoqGg4OEhIOCgYKDhYeGh4iMioWDhYSBf4CBgYF/fSp8e3p8f357enh1dnRycHBvbmxqaWloaWhoaGdmZWdnZmVmZWZpZ2ZlZWeFaARpamxthGuAaWdkZWhoaGlpaGpsamlnZGJjZWhqaWlpa21ub3BwcXNycHBubW1sbGtsbm9wcHJycXFycnJzdHNzc3Jyb3JydHV0dHR1dXV0c3NycnJzdHZ3e31+fn1+f3+AgoSEhIWGhYWEgoKEhIWGhIKDhIaFg4OEh4aGhIODg4SDhoSBgYKAf3t6en59f396eHd4enl1b3B0cnR1d3Z2d3p7fX1+fX58fHt5fH1+fn5/gIB+fn19f39/fXt7fHl5enx7e3p6eXl6enl6ent8fX19fH1+foCBgYKDhISFh4eKjI2OkJCQkZKWlZaYnJ+kpam5XWBkbXt7eXl+gouSkY+WlpOampdDkpKXmp6enJ+knpyenp2jpaSnqqqtqqenqaein5qcm5ydpKytrK+rpaOioqGjpqutsrrAwr26urm8vrewqKSgm5WTlUiur6+vrq+us7Cvs7vDxcC+u726tre4uLexrq2qqqmtsLW3uLu/wL27vL27vLu4trm4uLWxr6ehoKOnp6KipKOioJ2do6OfnZyEmxucm5uamJmampmam5qYmpmXmJeYmJaUlJOTkZCEkYKPhI0KjIyMioyMjIuLiYSKgIyNkI+OlJiWk4+OjY6Rk5SRkJCOjY2Mi4qKiYqJiYiHhoeGhIKCgYGA///+//z9+/r6+vv7+vr6+Pjz8vP08u7q6enp6OXi4eDi4ODf3d3b2tjY19PR0dLPzM/OysjHxcXDwr/AwcHExsTAvb3AwMDCwsPBwMHFw8LAv76/wMPFCsbGyMjJyMvNzcuFzwbR0tHS0dCE0QvQ0dDOzcvKycjEwoXDEcLAv76+v769v7+/vb6+vby9hryEvSG8vLu6uru6ubm3tra1tba0tbOytLO0srGwr66urauurKuErYCrq6ysr62uraurq6qqq6qrr7CurauqsLGusbKzsrW5ube1tbSztbazs7G0tbOwsLOztba3tLGxsrKwr7GysrSysLKztLazsbCvsLGxsrGxtba2u769vb24tbm6t7i6urm5uLe6u7u7vLy8v7/AwcHAv7+/wcLDwsLExcXGxsXFxlvIx8XGyMnHx8nJycjJysrMy8vLzczLzM3P0M/R0dDQztDR0dDQ0dLV1NXV1tnc3Nzf4OLk5/D3/ISPkI6HhoT//YCDh4mMi4yOkI2Kio+RkY6KhoSDg4SEhIOFhIZChYWGhIKA/fr7/fv29vTx8fHv7Orp5ufn5ePj4eLi4t7c2tXT0tHS0dLS0dHR09LW1dfX1NHRzs/O0NLS0dHNy8nIhMtKysjFxsTCwMDAvry8urq5ubi2t7W0s7O2trS0srK0tLOysrSzsrOxsrOzs7S0tbSysrGtrrKwsLCurK6xsK+tqaeqrK2traytrq6FrxWwrq+wrq2srayrrKysra2vr66ur6+EroSvGq2vr6+wsLCurq+xr6yrq6ysrK2wsbKztbSzhbQHtre2t7e3toW3Rbi5t7a3t7q4uLq7u7y6ubi1t7i3ubm2t7m3s7OztbW3uLKvr7G0tbGsrrGwsbKysbG0tra5uLm5uri6vLu7u729vb/BwoXAKL/Cwb++wMDBwcDAwcHCwcHCwsLDxcXFxsjGxsjIycnKzMzN0NDS1NWE2Wbb3N3f4eXj5Obp7fLz9f+BhIiOmJiWlZqdpKmmp6usqq6wrKmqrbG0srC0trKztLKwtbe3u729vby6uLu6t7OxsrKys7e+vb3Bvrq4uLm2uLvAwMPJzc7JycrJys3Iwb+7uLWxr67/gamB/4D/gPqAh4GCgKSB/4D/gN6A14ECAgQAgJqfmJiWlJuemJORl6CnpaanrKyln520xMGmoZ2Wm5ybrsWzrbTTv7a6zcHFvq26w7Tg2ruyrKOakp2Yj4uaoqeehn6KjoZzZF1gZGRgYGduc3Fta2xqdHd0cnRxcHZxb2VhXFVRUVBRUlNRUFFQUVFPT01PVFZVVFNRUlpbW15ngHiCfoGLn7GkmIqDi5Ggp6WgmJSRjYWBfX6BgYGAgIB/fn58eXZ0cnFuasnLztDPz9LS19ja3tnU083Gw7u0sKeemZmVjYuHhoWDgoKCfnt4d3Z2c3Bsa2hmZ2VjYF5dXFxeXFxhY2ZnZWJfWllaWlteYGVmZWhqamZkZGJfX2JjF2NjZWdpa2prb3Byc3N4fH5/gICChYeHhoghhYKBfXt6fHx5eXd3eHh0cHFvbm5ubGlnZ2hoZ2ZlZWRjhWQQY2NjYmJiYF9eXl5cXV5fYIRfgF1dXVpZWVhXV1xdXVxaWVtfXFpZW15dXV9ZWlxfYWJiYmVlY2BeYmVjYGJnaGZqa2tqZV5hY2JiXVpeYl9eXFlbYGNiYWFcV1hYV1lcXl1aWlleWllZWFhWV1FOT1RXXGBjbHR0cGllXltfZmZfXF5fYWNiXmJkZmhoaW1vb3FvgGpqbGVjZmhnZmhnZ2hoaWlpbHBwa2dpaGhnZ2ZmZ2hpa2lmY2NiYmJhYGFhYGJfXV1eXFxcXl9eXmBgYGJkZWlqam5xd3p+h0tWX15eVlBNTlJZYWJfXldaYWZjZGVoZV5XVFJQU1lZWl1fXFxcW1lWVVJTU1JQT06Yk5CNh4OBKn+Ag4SCgH59enl5dXFwcHJxbmtramxpaWpqaGhobXFxdXZwb21ra2ZfXIVaXVlYV15iZF9aVlVUU1BPTUtKSUlJSkpLS0lHRkZHR0ZFRUdKS0pKSUtLTEtJSElKTFFRT05NSkhHR0hLTExNTktMUVFQS0ZDREdLTExLS0xNTlFUVVhZWVhYV1ZUU4VSClNUV1lbW1tdXVyEW4BcW1tcYGJjYmBfXFtdXVxcW1pZWl1eYWJkZWdnZ2ltb3F1dnd7enh4d3d1dXR2d3d4eXl4eXl6e31+fHRwbm5wcXN0c3Fzc3BubGxtamloZWRlZWFfXV1fX15kZ2djYmJiY2doZ2RjY2Zna2RiZGRiYWFiYmNhXl9gYV9eXVxcWwVbXF1dXIRdgFxbW1pcXl9fXV5fYWNlZGRmZ2lubm1sbm9wcHV2eHl6fn5+gYOHiZCbVVhbXGVuc3V5enh/iY6SipGPiIeIi4yIioaKj5ebl5ebpJ6coqWipaKio52cnp2goZuWk5SWlZSUmZ6ZmJyen52gpKm0u73AxMbKx723sLCrpqObl5WZAZyAoaSenpuan6Kfm5qfpamqqamrq6mjoa63taWgnpibnJupua+ts8S5tLjFvr63rLK5rs3JtKykn5eTmZaPjJecn5mLg4yNiHxybnBzcm9vdHh8e3h2d3Z7fHt6enp4fHh3cW5pZGNjYmNjZGJgYGBfX11eXmBgYmFgYF9fZWVkZ2uAeH97fIOQnZWNg36EhpCVkpCNi4mGgX97fH5+fn19fHp6enh3dnVycW5r0NLV19bV1tbY2Nnc2dXV0svJxL+9uLCpqaikop6dnZ2cnJyZl5SRj46OioWFg4OEgH57enl4eHp5eHt+f3+Af3t1dXZ0dXl7f39+fn9/fn18e3p6e3s1fn6AgoODg4WGh4mKi46PkpSVlJWXmJeXmJiZmJiVkpGNi4uNjouKioqLioeFhYSEhIODgH+EfoZ9Wnx7fH19fHx6enp5eXh4d3Z2dnd4d3Z3dXR0dXNycXFwb3Jzc3Jwb3Fzc3FwcXJxcXNxcnFzdXV2d3p5eHZ0dnh4dXh7fHt+gIB/e3V3enp4dHN3enZ1c3BydoR4XnNvcHFwc3R1dXNzcnVzc3Nyc3Jxa2lqb3N1eHuAhISDfnx4dnl/fXh1d3h6e3p3ent9fn5+goSEhYSAgIJ/fX6AgH+AgH+AgoODg4SHiYWDhYOCgYCBgYKEhYaEgoCFgYCAgYGAgYB/fn9+fX1/gYB/gYKDg4WHiouMj5SanJ6mWmZubGxmYF1dY2hwb21sZmlwdXJzc3VzbWViYF5hZ2dnaWxpaGhnZmRiYGFhXl1cW7KwrKmin56dnp+fnZyamZaWlJGOjY6Pj4yIh4eHg4OEhIODg4eLjJCRjIuJh4iDfgF8hnpkeXd9gIJ8eHV0dHNycW5tbWxra2ppa2tpaGdnZ2hnZmZoaWhpaWdpamtqaGdoaGltb21sa2hnZmZnampqa2tpam9vbWlkYmJlamtqaWlqa21ucXFyc3Nyc3Jyb25tbGxsbW9wcoVzgHR0dHV0dHV0dHN2d3h4d3Z1c3R1dHNzcnFydHV3d3l6fHx7fYCBg4WGh4eIh4eGhoSEhIWFhYaHiIeIiImLjY6NiYSBgYOEhIWFhIaGgoB+fn9+fn16eXp7eHd0dnd3dnp9fnt6enp7foB/fn5+f4CDfHt9fnx8fX19fnx5enx9DXx7eXh5eXl4eXp6e3qFeVt6e3t8fX19fn6Ag4ODhIWHi4uKiouLjpGTlJaYmJyenqCgpaiutWJlaWtyen+BhIWDi5OYnJacmpOUlZaYlJWUlpyiqKKhpq6pp62uqq+trKunp6mpq6uloZ+fhKAfpaeioqWpqaeqrrO8wcTJzMrPy8O/t7i1sKykoJ6holy3t7W1tLOztLS0tbi6vLq6ubm6uba1tbe1sK+urq2srLK2t7u9v76/wcXCwb64uLy3u7+6sayppqOjpKSio6SlpaKgoaKhnZybnJybm5uanJybm5qcmpybmpqbmoSZBpeWlJKSkoSRCZCQj42Ojo2Mi4WMJ4uKioyKioyMjpCQkZKUmJWTko+RkZCTkZGPj46MjIyJiYqKi4qJiISHgIWEhIODgYD+/v79/fv6+vv7+/z5+fn18/Tx8PDt6Ofm5OLg3+Dg4eHh4t3d3NnY19nV0dHNzM7OzcnHxcTFxcPDx8jIyMfFxb+/wL/AwcPFxMTDw8PCwcXCwsTGxsbFycvNzszMzs/R0M/P0dLT09HQ0dHQ0NHR0M3MysrLyMbEScXGw8TEw8HBwb/Bv7++vr28vb29vr69vb28vb2+vLy8u7y9vLq6ubm3uLa0tbS1tbS0tLW0tLOxsLCvr66vsK+tra6vr66uq62EsTStq6yurq+vrrGysa+usLKysLO0tra2uLi4trGztLS1s6+ytLK0sLC0tba2tbWzsLGysrK0hbUdt7W2tbS0tLazsLKys7W4ubzBwb26vLm4ubq8ubmEuyO8ur29vb69vsDAwcLBv8LFwsHDxcPCxMXFxcbHyMjHy8zIyIXKAcmEyw3NzszLzczMzc3P0NHQhNGA0tHR0tXW1dXW19jZ297i4+Pn7O7w9fmBiZGPj4mHhYWIjZGRkZKNjZKVkpSVl5WRi4iGhIeLioqLjIuLi4mIh4aFhoSCgYGA/vz39PDt6ujp7Ovr6efo5uXk4d/d3t7d2tbW0tLO0NHRz9DR1tfW2tvY19TT0tDOzc3Oz8/PzcoOy87Q0s7KxsXFxMLBwL6EvQa8uru7uriFtgy0srS2t7a0tLW0tbSEsy20tra1tbWzsK+vr7Kys7OzsLGzs7Kwqqemq66vr66trq6usLGwsLGxsbKwr66ErBSrq62srK2wr6+wsK+trq6wsbCwsISyZ7Cwr6+xsa+urq2trq6ur7CxsrSysbK2tra3t7i4ubm4uLm5ubi6uri5uru6u7y9wMLExL+5tra4uLm5uLe5urm4tre4t7e2tbS1tbS1srG0tLO3uLi2uLi4uby8u7q7vL6+v729vsCEvhy/wL69vr/Av7++vsDAvr7AwcHDwsLBwsLDw8TFhMd3ycvMzc3NztHS1dbW19nZ293g4uLi4+nq6uzu8/T6/4SHioqSmZucnp6eo6qusa2vrqmpqayuqqyqrLC2uba2ur64t7y+vr++vr66urq5vL26t7W0tba1tbm8ubi7vb27vr/DyszP0tXU1tTQzMfHxsPAuri4ubj/gauB/4D/gPeAsYH/gP+A2oDYgQICBACAlJSbop+go5CXm5yRl5WTnKWxu6qyoafE4b+0rLKul5WZsqWmqsLEs7e6ssDNu7W/wczFv7amrKuXj4GJn7OnpaSikX16d25qZGNiY2Rma3Z2cmtsZ255endzcXBxcW5pYFlVVFVUU1JRUVJXXFxcWFJPTU5PU1RTUVBSVlxfYWSAaXiHkZWeoJOQiIeQmKStq6ahoJmQh4B+foGCgoOEgX9+fXt4d3Z1dHJsZs7T1dbW1NLX19jX0M7Kx8W9s62qpqWkop+alI6JiIaDf318d3RycXBvbm5ubGloZ2NiX19gZWtta2trb2toaWdiXFpbXV5gY2dqa2dlZmNgYWFiY2MPY2FiY2VnaGdnbXF0dHd6hHwqfn+ChYmIhYSDg4F/f4B/gYB/fHp5enx7enl2c3FxcGtqamppZ2dmZGRkhGMJZGNiYmNiYV9ehF0DXl1dhF6AX19dXl5dXFtbWlpbW11eXlxcXVpZW11fXlxhaWlkZGhqa2ZiZmhmZWdmY2RlaWllYWFhYFtaWV1cXFpdY2RiX19iYGNkYltWVFlbWlhaXF1cXV5eXVxaW1xbWlhVVFVYWVpcXWFmaWZmZGBWVlpfX11eYGVnaGRiZGZmZmtucHErdHRwbGxsa2tkY2RnaWpqaGhpa29xcG9ramppampnZWRkZmtraWhnZWNiYoVggF5eXmFfXltaW1xcXVxcX2NkZ2ptb3F5goaPTVRaXFxYWFJTVmFqcXVyZmNgY2ZpZ2JgYV5aWFthYmVmZmRhYmBdW1lXVFNUVVRRTUqSkpCJgn58enh3dnV4enl4dnR0cnFvb3FvcXNxbGttbWtqbHFwcXRybXR1b2ZeWVdXWVlXElhdXVxdX2FgX1xZV1NRUU5NTIVLP0pLSkhISUlISEpKSEpMTEtLTU1NSkpMTlBSUlBMTEpISEpLTVBQUE9MS1BSUExIRkVHSk5OTUxNTk9QU1VXWIRZA1hWVIRTQlRVVVRWWV1cW11dXVxcXV1cXV1gYmZoZmRhX11dXlxdXFxbXV5gZWZmZ2loa21ucnZ6fH19e3p4d3Z2d3d4d3p8eoR5VHp7fYB+c3BxcG5wcXJzdHFycm1qam1qZWRlZmhiXltaW11fY2ptbWhlY2JkaGtsamRjZ2xva2dlZmdiYGFiYmBgX1xdXl9ZWFtcW1xcXFtbXFxcXYRcfF1eX2BgYWJkZmdtb21vcXFxcnV1dnZ9hI2Tl0yXmlVYWWFnYWNscWxyhIuPg4CBjIiEh4mKiISOmJqYko6Kio+em5WSlJmaoKqtsLS1s6+poaCfoaKdmpmempWQkpWYl5GTlZWYmp+pvMLV1szCwLm1sa2ttLzKybqsmpKAnZyhpqOjpZmeoaWcnp2aoKWttamto6S5xrOuqKyomZiaq6Kkqbu9sbW4tb7Dt7K7v8K+vrmnqKiYkYiNmqmfnp2dkYSAf3t4c3NxcXJ1eX5+fHh5dXiAgHx7e3p5eXd1bmhlZWVkZGNiYmRmaGppZWBeXl9eYGFgYF5gYmZoaWuAbneBiYqRkYmIgoGHjJOZlpSSkY2JgX16e35+foB/fHt6enl3dnZ0dHNvadHW2tra2NfZ2dnY1NLOzcvEv7y5t7S0s7GtpqKfnp2dmZiYk4+Mi4uKiIiIh4WEgn58e3p8gIWGgoODhoOBhIN+d3Z2d3p7fYCCgX58fXt6e3t8fn45fn1/gICBgoKCh4mMjI+QkZCPj5GSlZiamZeXl5aUkZCRkJGRkI2Mi4yNi4uLiIaEg4OCgYCAgH5+hX0TfHx7fHx8e3p5eXh3d3d2d3Z1doR3JHZ1dHV2dXNycnFxcnJzdHRzc3Ryc3R0dXRzd3x7eHh8fX16eYR7gH18eXh5fH58enl5eXVzcnZ2c3N1ent5dnd4d3t7eXRwbnJzcnBzdXZ1dnZ1dXR0dXZ1c3Fubm9xc3NzdXp9fn1+fXlycnV4eXZ3eXt+fnx6enx8fn+BhIaIiIaDg4KAgH18fX+BgoKCgYSEh4mJh4WEhYSEhYOBgYKEh4aFhISDC4KCgoCAf4CAf39+hX+Afn5+f3+Bg4WHiImOkZSboqewXmVqaWllZ2JiZnB4foJ+dHJtcnV5dnBub21nZmpvcXN0c3Fvb21qaGZkY2JiYmFeW1mvr6ymoZ2al5WVk5KTlJSWlZORj4+Ojo6Ki42KhoaHh4aFh4mJjI+MiY6OioV/enh4enp5eXx8e3x+gIAIf3x5d3VydHCFbQZra2pqammEaARpaWpohGova2tsbGppa2xucHBua2poZ2Zpa2xtbW5ta2lsbm1qZ2VkZWlra2ppamxsbW9wcXKEc0lxcW9vbm9vb25vcHJydnV1dXR1dHV2dnV2dXZ4e318enl3dXZ3dnZ1dHN1dnd6e3x7fX1+gYGEh4iIi4uKioiHhoaGh4eHiYuKhotRjZCOhoKCg4GDhIOFh4SFhIF+foB+fHp6e355dnNzdXd7fIKEhIB9fHp9gIKDgn59f4OHg4CAgH97ent9fXt7enh5ent4d3h4eHl4eXl4eXl5hntQfX5/gICAgoSEiIyJio2Pj4+QkpWVmqGqsrVct7lkZmVtdG5wd3t5fo2UmI2LjZeTkJKUlpORmqOjop2ZlpabqaWin6Clpauztrm8vby5sqyEqyenpaWppZ+bnqGioJyen6Gkpamyw8fX29TKxsK/urO0ucPPzL2xopxat7W3uLa1s7KzuL63uLa0tLa3uru4trS3ubSzs7Oxrq6tsbC1t73Avb++wMTCvb3BwsHDyse4s7CppqWmqaump6mnpaGioZ+fnZ6cnJ2dnZ6enZybm5yenpubhJwMmpiYlpWUk5STk5GQhpEwkI+OjYyNjIyLiouMjI2NjY6Sk5KTlJaSkpGQkZGSk5OSkZGQj46NioqMi4qLi4mIhIcthYSEg4KCgP7+///+/v38+/z7+Pf18/Pz8O3r6+vs6+nm4eHg4uDg3t7e29nWhdU/09HPz83Ny8jGx83OzMrLzM7LycvJxsLBwcLCwsTFxsbEw8TDwsPExcfGx8bHyMjKysrJy83PztDQ0c/Q0dLThNI20dDNy8rKycjHx8XGxMPEw8LCw8LDw8LCwb6+v7++vr6/v7+9vr69vLy8u7q7vLq5ubi3tra3hbYUtbS0srOzsrKysbGvsLCwsbOysLGGsDmvsbS0srGys7SysLKzsrO0tLO0tba3tbS0tbSzsa+ysrGwsrOztbS1srK3t7a0s7G0trW0s7S0tLaFty24uLe3t7WysrS1tba3ur2+vb6/vbm3ubu5uLu6vL6/vry9v7++wcPBwsPDwcOGxAzDxMbHx8fJysvNz86EzBvKysvLysrKy83Oz87OzczMzs3P0NHR0NHS1NKG0znV1dba3d3g4ubo6fH2+f+EiY+Pj4yMh4iMkpmfoZ6XlZKUl5iWkY+Qj4uKjpGSk5OTkI+SjoyKiomEhoCFhIGA/Pv68+/r6unn5+bk5uXl5OXk4+De3dzc2NjZ19LS09LQztDU1NTV1tfb29jTz8zMzs7Nzc3Qz87O0NHPz8zJyMfExsPAv76/vr28u7u7uri6uri3uLe1t7i3tra3t7i3s7Kztbi2tba0srGxsrO0tLO0tLGxtbezsK6sqhirrbGxsK6usLCxsrKzsrKxsrSysrGwr66ErVSsra+wsLCxsrGwr7CwsLGys7W3uLazsbKysrOvsLCvrq6ur7KysrGzs7K0tba3ubq7u7q6urm5urq7vLy9v727vb29vsHCw8K8urq5t7m5uLm6t7eEuC+5t7a1tre5trKzsrO0tbi9v766uru5ury+wMG+vr7AwsC/wcHAvby9vr++v767u4S9Hr/Avr++wcHCxMPCw8PFxcbHx8jIyszMzc/Q09TU1oTYat3f4eDm6/X5/YD9/4SFhYuQjI2UmZSXo6qspaKjrKmmqaqqqaiwtre3sa+urrG6urWytLa2vMHDxMbGxsXBvb28vb67u7q8ubaztbe5ubW2t7e5u73DztHb3NfU0s7LycXFys7V1MzFurX/gayB/4D/gPaAsoH/gP+A0IADgYCA3oECAgQAYpCSlZqbpZmSkZaZkI6NkJudo7OioqW1ttLZva2rp6GdqZ2csK2rra3AxLK8w7exq7G3w8K8upyXnJaGfouxwqmRi49+cXNva2Zpa2VkZ2p0bWlsaWNud3h2dHd0cW1nYVtYhFaAVVVUVlZZWlZSUFBQU1pZW1lVUVFPUFhfYmdrb36HhXeChYmSkpqfp66tqauso5aJgHp8ent+gYOBf4KAfHp5dnR0dHFpaGtra2pq09ba3+Dc1M3LyMC6s7i3r6elop6cloyIh4R9fHp6eXh3dXJvb3Fxb25ubmxpZ2pvdXRydHVac3FsbGpkXltaW15iZm11dW5mYmFiY2RkY2NiYmFiYmRmZ2pucXJ0dnt7eXV2d3x/gYSFg4KBf4CChYqJhYB8eXt8e3x8fHp4d3Z3dHFvbWtqaGVlZGNiYmBihWEFYF9fXl2FXIVdDF5gX19gYF9gX15dXIRbclpZWltcXF1eX2BeY2dkZGllZmZmZWNiX1teYmFeYGBhXlteXl1XVVlcXF9hYWNmZWFfYGBfX11bV1xjY2FcXFtZWFhZXl5cWFZVVVVXWWFkY2FfX1tbXF5hYmBgX1lXW15hYmNkZWVjYmZnaGloaWxtb4VxDXBva2VjZmhpamtsaGmEaoBpamlpamtqaGZlZWZoaGxta2lnZWJfXl5fX15hYmFgX11dXFpZW11gYmRlaGtvc3uCiEhOVFVXV1JTWFlZXGNzfXlxaWNmb3d3bGdpZ2dqbG9vb25qZWRmZGJeXFxcWlhZV1ZWU1FQTJKLhH53dHFvbGxxdHJwbW9ycnFzdXRzcjtva2dobG9xcm9rbm9wb2xraWFdWl1dW1pYWFxeYmJlZmFhX11cWVZTU1JRT05OTUtKSklISUpJSEpMTIRJREtLTE5OTExQUlNTUU9LSEhISkxPUE9PTk5MTU9MSUdHR0ZJTExKSUpNUFFRVFZXV1lZWVhXV1VUVFNVVVVWV1daXFxchF0QXF1eXV9fYWRnZ2hlY2JfXYReGlxcXV5iY2VnZ2prbG1wdHh+f358e3p6eXh4hXlceHl5e3l9fXl6e3t5eHh4d3Z0cHBubW9ubGppaWFfY2lwbGNfYGBeYGVlZmhsa2ptZmBiaWx3dGdlZmlrZ2NiY2RjYGBfXl9hX15cXVpYWltcW1xcW1xcXF1eXl+EXnpfX2BjZmlqbHBwcnZ5eHh8f4GDhIqWU1lcWFxhY2ZpamtsbnN1f4aTkot/gISLiYmLkI2Bg4uSmpmenJqan56kpKCfoaelpKmwr6yqqaiioaOinp2anJqXlpKRlZeXlI+NjpOVm6a5xdPNxsDBv7i4tbS5uL28saSXkmObm52ioqifmpmeopuZlpeenqSvo6Okrq7AxrGrqqehnqWenKqpq62wu76zur+3srG0tr2+vLujnaCZjYeOqLKikY6Sh31/fXp2eHl0cnV3fXp2eHd0eX5+fXx+fHl3dHBraGeEZj5lZGZmZ2dlY2FhYWJmZmdlYmBfXl9jaGptcHJ8goF5f4CDiomPkpabmZeZmpWMg315enl6fH5/fnx9fnx5d4R0VXJsa25ubWtr2dnd3+Dd1c/PzcbEwMPCvLa0s7CvqKGgnpyYmJeVkpKSj42KiYyMioqKiYeFgoSJjYyKjI6LiYaFg4B6dnZ3eXx/hIqJg399e3x8fn+Ffip9fX6AgYSHiYuMjY+PjoyNj5CTlpeXlpWUkZGTl5mXlJCNi4yMjo6NjYyEig+HhoWDgoGAfn59fHt7e3yFewp5d3h2dnV1dnZ3hHYGdXV3dnZ3hHYCdXSEcxF0cnFycnN0dHR1dnV5fHp5fYV8gHt4dnR2eHh1d3h4dXN2dXVwbnFzdHZ5eXp9fHl2d3d4eHV0cXR6eHh0dHV0c3N0d3Z1c3FxcHBxc3h6eXd2dnV2dnh6fHt5eHRxdHZ5ent7fX17ent9fn9+foKEhYaHhoaHhYWDf32AgYOEg4SDhIWEhISDhISFhoeGhIODg4SGgIaIiIaFg4KBf39/gIB/gIKCgoB/f35/foB/goSGiIqMkZWdpalZXmNkZWRhYWdoaGxzgoqGfnZwdX2EhXt2dnV2d3p+fHx7eHNxcnJxa2pqamhmZmRlZGFgX1uwqKOclJGPjoyNjpGPjoyOkJCPkJCPjY2Kh4SEiYqLjIuHi4yMgIqIiIeDf3t9fn18enp8foKChYWBgoF/fnp3dXZ0cXFwb21rampraGhqaWhpbGxpaWlqa2trbGxqa3BxcXJvbWlmZmdpbG5ubm1sbGtsbGtoZ2ZmZWdqamhnaWprbG1wcXJzdHN0c3JycG9ubW9wcHBxcXN1dHR0dXR0dHV2dnd3CXh7fn19enl5d4Z2cXd3d3h5enx8fn9/f4GEiYyMjIuMiomKiIiJiouKioqMjY6MjY2Ki4yOi4iJiYeHhYOEgoGCgYF/f314eHt+hYJ7eXp6eXp9fX+BhYOBhIB7foKDi4p/fn+DhYJ+fX59fHt7enl8fXt6eXl4dnd4eXh5hXqAe31+fXx8fX6AgIKEhomJio2NjpKUlpebnZ+io6m0Y2lrZmpvcXN1dnd4e36Ai5KdnJWLjZKWlJaXnZuOkJaepKKnpqanq6msrKurrLKvrrO5uLS1tLOsqqysqqqoqaWioJ6eoaKinpubm56hprDCzNbRzMXGxcHBvb3AwMTEt60Cop1mtra3uLi5trW0t7m0s7KztLCxtbKys7e2uLe0srOzsLCwr660tLa5vcHBwMLCv7u9v8DBwsnHubW2saunqa2vqqenp6Sjo6OioJ+fnp+gnqGfn5+enJ6hoJ6eoJ6dnJqamJaXlpWVhJQ8k5KRkZGQkI+PkJCPj46NjIyLjY6Pjo+Qk5KRjZGRkZOSk5OUlZWUlJOSkY+Oi4yKiouKiomIioqIhoaFhIR5gYCBgYCAgP79/v7//Pj29vXz8u/y8u/t7Ovp6ebh4+Lh3t7d3Nrc2tjZ1tXU09PR0dHQzcnMz9PR0NHR0dDMzMrGw8C/wMLDxcjKy8fFw8LDxsfHxsfHyMvKyMnJyMfJzM7Pz9HQz83Oz9LS0dHV0tHPzMzMysrKyITFXcTDwsLDwsHCwsPCwb++v76+vr+/v72+vb28u7y8u7q6urm5uLe4t7i4t7a2trW2tbS2tbS0s7KztLOzsrCwsK6vsbGzsrKysLKzsbO3srKys7Szs7OysbOys7a1toSyC7Owra+xsbO1s7S3hLY1tbS1tbWvsba3t7W1trW1tbe4trW2tra3tra1ubq3ubq5trm6uru9vbu5uLm4ubu8vb2+v7+Eviu/v8HAwsLDwsTGxsXExsbDxsfJycjLzczMzczNzMzLy8zMzs3Nzc7Pzc/QhNFM0NDQ0dHS1NPV1NTU09PV1dTU1tbY2t3f4ePo7PH3/YGFiYqMi4iIi46OkZWgp6WfmJSWnKGhmZaXlZWWmZybmpqYkpCUlJKOjI2Ni4SKWImGhYSC/ffx7enm5OPf4OHi5OLf4ePh3t7f3dvb2dbV19bU1dfW0tXX2NXU1tfU0c/R0c7OzMvPz9LU2drU1dLPzMvKyMfFwsLBwL+9vLy8u7u7urm5ubiGtz+4urm3t7a3urq3t7OxsbKztLe4trW0tLGztbKwrq6trK+ysq+ur7GzsrKztLOztbS0s7GzsLCvrq+vsK+vr7CGsXKysbGysrOztLa2tre0tLW0srGxsrKwr6+vsrKys7Kzs7O0trm5u7y8u7u7vLy7ury9vr69vr+/wcHBwsDBwb++vLy8ubu9ubm4uLm4ubm6uLSztru8vLi1t7e2t7m5ury/vb7AvLu8v8DHxL+8vr+/v8CEvoC8vr69vb++vb29vr2/wMC/wcLCw8TDxMXHx8fIx8jJyszNz9DS1NjX2Nze3uDj5urs7PL5hIiLh4mNkJGRkZOUlZmboqixsKujpaesqqysr6ykp6qzuLe7ubm7vLq9vLu6vMC+vcHExMLBwsG+vb6+vby8vLm4uLW1ubi3trSzsxi1trvDzdTc2dTQ0M7MzMrKzs3OzcnAurf/gbKB/4D/gO+AtoH/gP+AyoDkgQICBABtmZianaKnopmRlpaUkYuNkpWbk5mepKi5zcXMvqWxsqifkpSWpqKoqK+rprq6qbmvq6GsvKegn6ednJaUjpSNn5t7fYN2cm1scXFwbG1qam5pZmVqZ3Fzd3l6eHNsY19cWllYWFlZWFZVVlZUU4RPgFBSVVlWUVBRUVBQVl1hZWdocnNyb213jpaWl5yepairqKOdk4yCf3+Afn6AgYB/gH99fHt4dHFua2loaWlqaGlsbd9x497b1tLMxsLAvL24rqmmnp2bj4aGhICAf35+fHh3c3J4gn55eHl0cW5sdXZ1dHJvbW1ub2pkYmBdW15fC2Jnam9vamJgYGBfhWA7X2BgY2RlaW10d3l3dXl5eHh4e35/gYGBgoKCg4ePkZSOh4N/fXp6fHx9fHt6enp5d3ZzcG5raGVlZGOHYgRhYl9fhV4MXV1eXl1eX2BhYmJjhWJoYWBfXV1eX15dXVxbXGFkYmBfX2NjYmJlaGdiXl1bWlhbXV9fX15cWVpbWldVVlhcYGJiX11gYV5cWlpaXV1bXmpqZ2JhYVxcXWBrbmxrZ2BdWVlbX2hqZWBfX1xcYmVjYV9iZWNdXmGHYgNjZmeEZh9oaGdoaG1vcHJwa2dlZGVmaGhnZ2lpampqa2poaGhnhGgEZ2ZnaYRqDmllY2BeXV5fYWJiYmBghl41X2JlZWVnbnNzd3uCiExKSExMTE9RU1JWXmt4f4F/fXV1eX17bm5xenh2cm9raWVhXlpaWViEVoBXVVNSUVBOTUtJkouCe3Nwbmtqa25ubWtqbW5vc3VycG1taGNjanFvbWpnZWVma2liX2BcWVlaWltaWVtcXmNnamdgXFtbXFlXVVNRUlJQTU5NS0tLTExLSUpLSklISElKTU5OTU1QUlNVVFVQSUhKS05PUVVTUk5NTk5LR0hKSCRJSUpLS0lJS01SUlJUVlhZWVlXVlZVVFRVVldYV1dYWlxcXVyHXSteYGJlZmdnZ2ZkYF9eX19fYGBgYWBiZGZnZ2lqa2xwc3l+f357eXl7fX58hHuAeXp9f358fnx4eHp9fHt7enl4dnJwb3BxcXFtaWJgYmdoamhnZmRkZWVnZWhtaWhvcW9oaW5wcWxoZWVlaGhmY2FmbGNhYmBdXV1bWllaWltaWlxcW1tbXFxdXV1fYGFhY2RnaWtsbWxwdXV1d3t/hoeKjY+QlE9RUVphaGNkb3Nbc3uDhIOFfnt9eXlydX19f4OMkpeTjoiIjY2Oj5KQlqSrpJ+foaaioKOhnqChnJmbn5ubnKesr6ymqaWgnJqenZqWkZiZmZ6orLi1tLS9v7q0rrCyp52isKWenGKlo6Klqq2ooJyenJqZlJWYm56YnaCjpa69tru1pa2vqKGYmZmio6emrKmotreqt6+spq24rKikqKCfmpiTl5KdmoOFioF/fHt+f316enh3eXZ0dHd1e3yAgYGAfHdyb21saoRpgGhmZ2dnZWNhYWBgYGFiZWRfX2BgXl5iZ2lsbG10dXNxcXiGjIyLjpCUl5mXlJGJhX99fX18fX5/fnx9fHt6eXd0cnBubGtsbG1rbG5u4HHi393Y1dDLycjGxcG7trSvr6yknZ6cmZqamJiWlZGNjZObl5KRko6LiYiNjo2Li4iHFoaGh4N+fXp4dnl5e3+AhIaBfXx8fHuFfD19fHx9gIGDhYmLjYyMjo6Mjo+RkpKUlZaWlpOSlZ6goJuVkY6OjIyOjo2MjIyNjYyKiYiGhIKAf359fHt6hHyAe3t7eHd4d3d2d3d3eHd2dnd3eHh5enl5eHh4d3Z2dXZ2dnV0dHNzdHZ5eHZ1dXl7eXd7fn15dnZ0c3FydHV2dnZ0cXFzcnJwcHF1eHp7eXZ5end1c3N0eHdzd3+Afnl4enZ1dXh/goGBfnh4dnR2eX+AfXp4eHh3eXx7enl8f3sNdXd4eHl6e3t7ent9foR9OX5/f4GBg4WHh4aCgX5+gIGDgoKChIWFhYaGhIODg4SFhoSEhIOEhoaGh4mIhoKAgH+AgYKCgoODgoeBNISHiIiKj5OUmZ6lq1taV1teXWBhY2JmbnuHjY6LiYSDhouGfHt9hYWEgH16d3NvbGloZ2aFZYBjYWJhX11cW1mwqqGZk5COjIyMjY2Mi4yNjI2QkZCPi4qGg4OIjYqIh4aGhYWIh4J/gHx7ent8fX17fH1+hIaKiIKAf35+end2dnV2dXJvb25tbGxsbWtqa2tqaWloaWptbm1sbG9xcXJycW5pZ2lqbGxvcXBvbGtsbGlnaGhnZw9naGlpaGhqa25ub3Byc3SEcz5ycXFwcHFxc3JxcnN1dXZ0dHV2dnZ1dnd4eXt9f39+fHt5eHd4eHd3dnh5eHl6fHx8fn6AgIOFioyMjIuJi4iMUoqLjY+NjI+Ni4qLjY2NjIuLioiGhIOEhYSEgX56eXt+gIKCgYB+fn59f32BhYKBhoeFf4KGhoeEgH+AgISDgHx7foF9fH58eXp5eXh3d3d4eHmFeoB7e3x+fX6AgoODhIaHiYqLi4+SlJSWmZyio6arrq+zXWBhZm13cHJ8gH+FjY+PkIuKiYaGf4GJiYuPlp2jn5qUk5eZmZuenaKutK6qqqyxq6quramrrKilp6uop6mxtbi2sLSxqaWmqaimop+lpqWrs7fAvb28wsPAvLi5urGmrgS7sKmnYb26u7u8vbu3s7a1s7OwsbGwsbGysLGxsri4uLaztrSzsrCwrrKxs7O2t7m7u7q8urq4ur+/vbu4tbSzsbCuraytp6anp6Wjpaamo6KhoKCgn5+foJ6goKChoJ+dnZ2cm5qEmQyXl5aVlZSSkpKTkpGEkAeOjY6OjYyNhI4Nj46Qj4+Ojo+SlZSTlIaTHJKQj4+NjY2Mi4qKiomKioiHh4aFhISDgoGCgYGEgEb/gP79/Pn39vPz8/Hy8PDs7Onp6eXh4eDf4ODe39/b29jY2t3c2tjZ1dLPz9HS0tLQ0tHMzc/MyMfEwcHCwsXIx8fIx8XEhMURxMXGyMfIx8jIyMrJys3Qz82E0BXP0tTT1dTT0tDNzMzMzc3OycfFxsWFxgrFw8PCwsPCwcHAhL8Nvr6+vb28vLu7uru6uoW5E7i4ubm3tre3tre3t7a3t7a2tbSEtYC2tLOys7Kxs7SzsrOytLS1tbS2t7Szs7GxsLGzsrO0tLSwsbGwsLGwsLO1tre3s7W2tbOysrKztrW1t7e1t7i6t7a3uLu7vLy6ubq5t7e2u727urq7urm8vr29vby9vbu7vLy9vb28vb6+wMC/v8DAwcLCwsPExMTFxMXFxMXIyhLKycvMzM3OzczNzszNzs3Ozs6Ez07R0tPT09TU09XV09PU1dXU1tXW1dbX19jY2dvc3t/i5urq7PL5/YODgYSEg4eHiIeKj5qjp6qoqKKgo6akm5uboKCfnZuZmJWRj42NjIuFinaIiIiHhIODgYD99/Hr5uTh3t7f4OHg393f397e4eHe29rY09PV2NbX1dXV1NTV09LQ0c/PztHT0c/Mzc7P1drd2tXR0NDPzcvKx8TExcPAwMG/v769vby6u7u5t7a3ubi5urq4uLq4ubu6ubeztLSztbS4urm3hLQlsK6ur6+vsLCwsa+vr7KzsbGztrWztLS0s7KysLCwsrOysK+xsYSyM7OzsrGysbOzs7S1t7i3uLe2tLOzs7Kys7SzsrGys7Kzs7S0tLa3uby9vLu8ury+vr28vIS/gMDCwsHCw8C+vr7Avr7AwMC/vry6ubu8u7u8uba1uLq8vby8vby7vLu8ur7DwL3CxMK+vsHCwsDAvb6/wcDAvby9wb+/wL++v768vL3AwsHAwMDBwsPExMTGx8jIysvLzc3Q0tPW19XZ29zc3uLn7e7y9fj4/YKEhIiMk46QmJuZWp6kpaWnoqGin56anaKioqatsrKvraqssLCxsrSyt77BvLq7vcC9vL6+vL29uri6vbq6u8DExcTAwcC9urq9vLu5tru8u8DFyM/NzMvP0M7Kx8nKxL7CysTBvv+BtIECgIH/gP+A7IC2gf+A/4DIgOWBAgIEAGKcoqWkp66ztKeVlpyUlZqUnKSTmZebm5+ks9TLxLCmrZCLi4eHopmOlpeYpqKQn6apl5GYmZKMhIOCkJGUkJeOgHl8e3hwbGxwb3BtcWtnZmVobWxvcnJucXJqZWNgX11bW4VagFldZWhfV1JRVFpiYl9bWVZSUVFQUlhgXVhWZGdob25ofpGMi5GXmqGnn5mVj4qIhIWGgYCAgH58e3p5eXp7eHVwa2hlZGVnZmVoamzh4uDf39/a1NLQ1tLIw7ywp6Kcm5OEhIKGiIWEgX96d3V0eX5+fHp/fnx8fHt5dnJvbGloJ2dnZGNgXl1cXWBgYGFkY2JfX19gYGFgX2BfXl5eYmZoam9ydXZ0dYR2F3h7fH19f4CAgYKEiI2OjY2KhIB9enx9hXwae399fHt4dHFvbmlnZmRjZGVkZWNiYmFhYWCEX4BeXV9fX2FiYWJjY2JiYmVkZGNhYV5eYGJiYl5cXl5fYF9fXl1hYWFiZGNjYV5ZV1ZYWVpaXFpXVlhcW1lXV1dYXF1cWlpYWlxbW1xcXl9bWl9pamdjW1xdYWNqcXRzbGlkX11dYGJiZGJeXFtcW1teXlxcXmFiYWFjYmNmZmRkZQNjZWWEZBVlZmdmaGlqamxramdmZWJkZWVnZ2iFaQNrammEaIBqa2loZ2dmZGRmZWViYF5dXV1eYGBfX2BiYGBfYGJjZWZnZWZuc3V3eHyDjY6LiIpGSVBTUlZdZ3WEiImJgHt7fYGBe3d1cnBubWtoZGBeWlhZVk9OUFJTUVFPTUxLkY2IhISDfnVxbmtnZ2hqampoaWxsa2xra2xra2prbGxucidxZmdjam1oXl5fXFpZXF9fXFlaXFheX2BgYFpXWVpYV1dZW1xaU0+GTTdQTExNTEpLTU5OTlBRTU1SUlBSVFRPS0lJSU5RUlZZV1RRUlRUT05OS0lISElKSktLTE1QUlJThFeGVg5XV1hZWltaWltcXVxdXYRegGBeX2RmZ2hqamhoaGViYWFgYmRkY2JlZGVnamlqbHNydHh9fX58eXh6fH+Af36AgH99fHx9eXd5e3p6e3x9fXt8fHx6dXFvb3d6eHJua2poamtoaWlnZWhra21wdHNpbHd0dndxbXBzcm9xb2dkZmtlYGJmZF5cXV1cXFpaWlhXA1hZWYRagFtbW1xdYGJkY2RmaGlrbnVzcXJ2eXl6fH6BhIiFhoaFjJJMXGZnX2pzamlrbnBxb2xscHV6e3h8foGAhYeNlZeQjpGPlZKMiouTmpeTlZqdmpmampWSk5iWl5iUl6Ctt7m3t7m5rqWnq6KhoJqWlZidoqaoqK2moZmZm56elpSZBaeklpOWY6esrqyttLi5rp+coZqbnpugpZmenJ6fn6Ktwb65rKarlpSTkJCgm5Sampyko5mjpaidmJ6hmpWPjoyWlZiWm5WLhYeGgn99fX99fnt/enZ1dHR4eXt9fXt9fnh0cnBvbm1tbIRrgGltc3NrZmRkZWhubGpoZ2ViYGBfYGRqaGNia21tcnJtfImHhIiMjpSXko6MiIWEgIGDfn1+fn18enl5eXp6eHZybmxrampramlra23h4eDi4ODc2NTT2NbNy8a9t7GtraecnZueoJ+cmZmVko+Pk5eXlJOWlpWVlZSRjoqHhoSDI4F/fn17enh2eHt7enx/fn17enp6e319e3x8fXt7fYCDhIaIhItAjIyMjY6Sk5KSlZaVlZaWmZycm5uZl5OPjY2Ojo+Ojo+OkI+OjIqJh4WFgX9+fXx9fn5+fXx9fHp6eXl4eXh4d4R4BXl4eHh5hnpQeXh4d3d4eHl5dXNzc3V3d3V0c3d4eHd5enp4dnRxcHJzc3J0c3FwcXNzcnFyc3R2d3V1dXR1dnR1d3d3eHV0eX+Afnt0dnZ5fICHiIaBf3yGeUZ7enh3dXZ1dnl5d3Z4e3t5eXp6fH59fH1+fX5+fHx9fH19f3+BgoKEhYSCgYCAfn+BgoKDhIWEhoWFhoaGhIaHhoeIiIeFhIQEhYSDgYaAVoGCgYODhIOCgoOEhoeJioeJkZWYmpueprCvqqiqV1pfYmFlbXaEkpWUlY6JiYuPjYiEgoB/fXx5d3JubGlnZ2VgYGFhYWBhX11cWrCsp6OjoZ2Yk5CMhIoJi4qJio2Ni4yLhIxgiYiKjIyPjYaHh4eJhn9/f3x8e36AgH57fHx5foCAgYF9enp7eXh6fH9/fHZzcHBvbW1ucW5tbWxqa21tbW5wcW1tcnJvcHBxbWppaWlsbm5yc3NwbW5vcGxsbGpnZ2dohGkea2xtcHFxc3N0dHNzc3JxcnJyc3N1dXR0dXV2dnZ1hHYVeHh5e3x9foCBf39/e3p5eHd6fXx9hHtlfH19gIKHh4eJjY2OjIuKi46Pj4+Oj4+Pjo2NjYqJiouMjIyNjo6MjY2OjImFhISLjouGg4GBgICDgYKCgH6Bg4ODhoqJgoOMiIqLiYWHiomGiIaCgICDfnt8f397enp6eHd2eHmFdwN4eXqEe4B9foCCg4OFhoiIio6UkY+QlJeYmZqcoKKlpKSkpq2zXWl1dG13fnd0eHp8fn56e32Bh4iFiYqNjZGTmaGinJibm6GemZWYnqajn6Glp6Skpqahn5+koqOkoaOrt8DAv7/CwbeusLKsraumoqKkp6yus7S3sqqlpaaoqKKfpLCvoQKeom6+wMC+vsLFxcG5tra1tLS0s7SxsbCwsLGysbe1traztLCxsa+vsa+vsrGytbSytrW2t7S1uLS1sbGwsbCxr7Cuq6mqqaempaSmpqajpaShoaOioqGhoaKhoqCgn5+enpyam5uam5qamZiZmZaUlISTBpSTkJGQj4SOMo+Pjo2NjpCPj4+Qk5OSkpOUlJaXlZOSk5KSj4+QjYyMjIqLiomJiIeIh4WEhIOCgYGBhYCA//79/fv5+Pb19Pb08/Dw7ezq6Onl4eHg4+Th39/f3NzZ19jb3NrY3NvY2djX1NLR0M/Ozc3MyMfFxMPCwsPDxMPGxMTEw8XGx8bExcbEw8PGxsfIyczNzM3MzM7PztHS09LU09PS0NDOzMzNzc3OzMvJx8bHx8bGxsfGxMbEw8MPxMTDw8LAwMDBv8C+vb68hbsLubi5uLi4ubm3treEuIW3C7a3trW1tbS1tra2hrNHsbKztLa1tLSztLWzsrGxsbOzsrK0tLSzsrOysbKzsrK2trS0tLO2tre2t7a1trW1tbi7urm2tra5uLq9wMC9vb27u7q6ubqEvIC7vLq7vb28u7q7vLy9vr2/v7+9vb+/vr6+v8HBwsLDxMXExcXExMbFxcfIycvKysrLzs7Pzc/Qzs3Pz9LT1dTT0tLT0tHS0tPU1NTS1NXV1tfY1tbW19fY2NrZ2tzf4eDi5+nr7e/y9v7//Pz+gIKHiYaKj5igq6+vsKmlpaaoqXOmo5+dnZ2cmpeUkpCPjY2Lh4aIiIiHh4aEgoD9+/n19PPv6ufj4N3d3uDg4N/f4eHf397f397f3NrY29vb2tne2dnZ1dHQ0c/Q0NPT09DMzM3Lz9DS09PPzc7OzMrKzNDPzcjEwsHBv769v727vLy7u7y8hL0murm8vLi5u7u5trS2tra3t7m7ure1t7m3s7W2s7Cvr7CwsLGwsbKEsw62tbS1s7S0tLOysLK0s4ayGLOysrKzs7KytLO0tri4uLm6urm6t7W2toS1ArazhLQEtbW3t4S6Gb28vL29u72+wcLBwMHBwsHCw8PAwL+/v8CEwQHAhMEJv728vMDBwMG9hLtRvb29v768vr+/v8LGxb/AyMXHxsTBw8LCwcPDv76+wL29vL/Bvr3AwL+/vb2/v76/wMHBwsLDxMXFxsfJyszNz9HT0tLU3dvZ2t/g4OLl6OruhPE48/j/goqRkY2UmpSRlZeZmpmYmJmcoJ6doaOmpamrrrO1sa+zsrazr6yvtLq3tLW5u7i4urm3trWEuAS3ub3FhMsizM3HwsPGwMHAvbq5urzAwcTEyMTAu7y9vr+8uLzFw7u6vP+BtIH/gP+A84Cugf+A/4DNgOOBAgIEAGOTl6KrsLW/wbWknJWPkpubnJGVkpuno5uepanHuc2ZjYmGjY+XmY5+ioKLnKKUl6aqn5i3sqONhYeCipaZnpaCfH9+eHZxcW9vbG1sb29raGZnaGdqaWloZ2RjYmJjYV9cXFyEWy9ebXF0aV1XVlZZYWhvZ19aVVRTUlRYWldVVFpkZ3FucoWOkJKYoZ6Ylo+NjIeCfoR8gHt7enl4eHl5fXt3dXFsaWtpZWZmZGZnaWtv4uft9ff07+ro8fTmzca/tq6in5qNiouKi4eGiIeDf3x6gIqMioOCf3h9hYmDfnVzdHJvaWdgXl5eXV1eY2NhX19gX19gYmFgYWJhYmFgYWRoamlpaWpra2tvc3d+gYF/fHl2d3l9TX9/gIKFg4aFg4B+fXx8fX6AgIKBgYGAfnx6eHZ0cnBta2lpZ2ZmZWZlY2NiYWBgX2BhYV9hYGBhYWBhY2NiZGZnZmZlY2FhZGVlY2FfhGBzYWFiYF5dXV5gY2VlYV5aV1RWWFhaW1xeW1xfXlpaWltaW1lXVFZWV1dYWVpbXlxXVl1iZWRhX2JiYmRsa2hlYWJgXFtdY2NnamhmY2FcXFpaWVlYWFpcXWBkZGVoaWdlZWlvbmdlYWJiYmNkZmZmZ2ZmZ4ZlgGZoamtqamxoa21tbWxraWZlZWZlZGNkY19hYmBgYF9cWltcXl9gX15gYWBhY2RjZGZmZ2hqbG1ubm1tcHV7foKHSExQUVVcYmp3gomJjZCKh4eBeXZxbnFtbGtqZmNeV1NRT01LSUpKTEtKSI2MioiEgH19fHp0b2xnZmZkZWdoQmhqa2ttb21oZ2VkZWZjZGdmY15haWpoZGNfW1tbWmJjYVxaWlpkY19aW1pXV1ZVVlhbXF1cVk9PT01NUVFSUVFSUYRNNE5NUFBNT1RVVVdWVlBNTE5SVFRYX1xZVVBVWFlTUE1MSkdHSUpKS0tMTlBRU1RWWFdWVleEWIBZWllaW1tbXV5cXFxeXl1eXV5fYGFkZ2dpampqaWhmZGRjY2NlZmZlZmZlZ2tscXR2d3l+gH6Bf359fX5/f3+Bf39/fn17eXZ3eHx+gX18fn57enx/fHdzcnZ7e3p3c3J1dXRycnRuaGhrb3F0fX9xbHB4e3p3cnZ6fXp6d3VzcBFwcm1nY2BfXlxbXFtbWllYWIRZgFpaWltdXl5gYWJkZmZmaGttcnV7fHl3eXp6ent8gIGBhYiIiIaIjphQWGVsaWRkZmVlaGprcnd6e4SEf3x5eHyBg4SKjI6OhoOBf4CCg4SBgYKFh4iMk5SNjJOVmJ+cnJ+uusTExMPAvrCqn5ePjoqGhoeJjZSUk5WVjYqMkpKTCZiVkpGNkJKSkICcoauzuLrBxLqqpJ2Zm6CgoJmbmZ2lo52eoaW4sbydlZKQlJaZmpSLk46VnaKZnKWooZ6xrqSVj5CNkpiboJqNiIqJhYOBgX9/fX98fnx6eHZ2d3d4eHh3dnRzcnJzcXBub25tbW1sb3l7fHVsaGhnaGxwdW9qZ2RjYmJiZWZlZIBiZGttdXJ1gYiIio2TkY6NiIeHg399fH19fHt7enp6eHh5e3p5dnJvbW9uamtraWlqa2xv4uXo7vDw6+Tj6erh0MzHwbqxr6qkoqKhoqCen5+cmZWTmaGhnpmXlZCWnZ6alY6Ni4uKhIB8e3p7e3p6fn17enp7fHx8fn18fX59fm18fH1/goOCgoODhYSFiIqNkZWVlJKRkJCRkpOSk5SXlpeXlpSRj46Oj4+SkpORkZCRkI6NjIuJh4aDgoGBgH9/fX59fXx7fHt7eXp6enl5eXh5eXl6e3p5e319fHx7enh3ent7enh3dnd3d3h4hHZ2dXV2eXt7eXZzcW9yc3JzdXd4dnR2dHJyc3R0dXNycXNzc3JydHZ1eHdzcXh7fH18ent8e3+Eg4B+e3t5d3Z4e3p9gH99fHt3d3V2d3Z1dHV3eHt+fX2AgH19fYCFg319e3x9fX5+f4CAg4KBgYCAgoGBgoOEhYSIgIqLjIyMi4qIhYSDhYSEg4KDgoOCgYGCgH5+f4CAgoOEg4SEg4SGhoWFiYqKi46SkZGRkJCVmJ6jpalZXF9gZW1ye4aRl5aanJeUlI6IhYB9f3x7e3p2cW1nYmBfXVtaWllbXVtYraqpqaSgnZubmpWRjYmIiIiJioqKjIyMjZCPO4yLioeHh4SGh4eGgIKIiomFg4B8fHt7hISCfXt6eoOCf3x/fnt6eHh6ent9fX13c3JycG5xcnNycnNxhG4ib25xcW5vc3NycnJzb2xqbHBycXV5dnRxbnJ1dG9ubWxpZ4RoDmlpa21vb3Fyc3NzdHR1hXMGdHV1dnV1hXaAd3d2d3d4eHl7fH1+f4CBgoF/fnx9fn19fX9/fn58e32AgYSHiIqMjo+PkI+NjY2QkI+PkZGRkI+NjIuJiYmLjpCPjpGQjY6Qko+MiIeKkJCNi4mIioqIh4iJhYCAg4aHiZGSiYWIjZGPjIuNkJGOjo2LioeHiYWBfXt6e3t6eXiFdxt4d3h5ent8fH1+foCAgoWGhIWIioyOkpmYlpaFmQWboKKipYSmZKiut19mcXh1cXJzc3N2eHl+hIeJkZKNiYaFiI6PkZeYmZiUkI2MjZCQkI2Nj5OUlJifoJuboKGlqqepq7bBy8zLysjGurOqopuamJSTlJiaoJ+foZ+alpicnZ6ioZ6dmJ2dnZpDt7rAxcbJzMzJwLu4tbW1tLKztbKztLKwsLK0tra5srOxsLGwsbCurK+tsbKysLG1trW1uLa2tLGysLCxsbGwrq2urYSqD6iopqelp6Wko6OkpKKiooSjCaKhoKCenZ2dnISbE5qanJ2bmJaWlpWUlpiUk5GQkI+EkISOLpGQkpGRlZWUlJSXlpSVlJOSkpKRj46PjoyMi4uKiYiHiYiIh4aFhISDgoKCgYGEgGL//f7//v/8+fr+/fn18u/t7Orp6OPj5ebl4eHj4uHg3dve4eLh3NrZ1Nrb3dvZ1NLR0dLOy8XExcbFxcTGx8XDw8XGxsXGx8bHx8bIx8TExsjKycrJysnJy83Nz9HS09TU1ITRAtDNhMyAzcvLy8rJxsfHxsbFxsfJyMbFxMXFw8PCw8LBwMC/v769vr29vby8vLu6urm6ubu5tri4ubq6ubm4ubm4ubi2tre4t7e2s7KztLSztLKztbW2tLO0tLa2s7KysrGzs7K0tbS1s7S3trS0tri3uLa0tLSztLS2tre1tLSzs7i5u7wGuri7u7q7hL8bvLu7uru7u7q8vr+/v76+vbu8vLy7urq7vb7AhMENwL+/wMHDwMDAwsLBw4TEGsXFxsjGx8jKy8zMy8zOz9HT0tTV1NXV1NLRhdKA09TS0NPV19XV09TV1dbW2NjZ2Nna2dzd3Nvf4uLj4+bn5ubm5efs8PP4+f2DhYaGipCUnKassbCys7OvrqumpJ+dn5ycm5qYlJCMioeGhYSDhISEhYSB/v38+vby7+3s7Onm4uDg4OHg4OHe39/g4ePj4+Th3t3c29rX2dvU1tot3NvX1tPR0dHS2djV0M/P0NbW1NHS0c7NzMvMzc/S0NDJx8PDwb/AwsPCwMHAhL4lvLu/vru8vr69vLy9urm4ubq6uby9vby5tbi7u7a2trWyrq6xsYSyAbOEtQa2tba2tbaFtAi1tLOzs7S1tYSzAbSEsy+1tre3ubm6ubq8vLq5uLm4t7e5urm3tra1t7e1uLq7vL2/v769vr+/wL/AwcHCwoXBgMC8vb2/wcPCw8PCwMHExcPCwMDDxsbGxMG/wsHCwsHCwcC/v8LDxMfJxMHEyczKycfHysnIx8fIx8XFxcPBv76+vr2+wcC+v7+/wMLCwcLDxMTFx8nIycvNztDQ0dTV1dfb4eDe3uDh4uTl5err6u3w8fLy8/j/goiPlZKQkJOTP5KVlZeanaCjp6ekoqCho6aoqa6vsLCsqKalpqmrqqioqayvr7K1tbGzuLi5vbq8vsbN1NTU09DPyMbAvLW1soSwFbK5uLi4ubWys7a3uby6uLezt7i5uP+BtYH/gP+A84Crgf+A/4DRgOGBAgIEAFeeo6Wut7zCyMO2r6KamJaZmJmWjpefrLO0rZihuMKtlo+UlI2PioV9f32AhpONk6GloY+nt6+gj46Dg4uIgnyAfH14c3J0dXNvbG1rbG9sa2hnZ2VkZWWEZE5obmxjYF9eXl1dXVxbXF5dXl5aV1ZXW19oZmVdW1pXV1VXV1VUU1ZdYml5hYybk5OjoZuTkoqFgX53dHZ5enx8fHt5dnV1eHl3dnBvb2qFZnJnaW1vc3l7e36AgH/68Ojo693W19PGvq+gnJmQkJKQioWGhYaFhoiQmpaYmpeVkYqGiYV8dnV2dnVvZl9fXl5eX19gY2FgX2BgYmJiYGBhYmFjZGNjZWdpZ2ZpbHJ8foaEgYWNkY+DeXZ2eHp6enyAgYGEfxx8fH+AgYOEhomHh4OBf3t6eXh1c3BtamloaGlnhmU7Y2JjY2RkYmBfYGJiYmRmaGloZmdoaWhpZWRkZWRiYmFiYmVjYmJiY2BcWVlbXmRlY19bWFZWWFtcW1uEWnRcXV1bWFdWVldZV1dWV1dYV1pbYFxZXF5gYF5fYGNjZGJjZ2dgWlhZXl5fYmVramlpYmBdX2JiY2ZobWheXWFkYmFgYmhmZGZrbW9qZmZmZGJiZGRmZmVnZ2dmaGlqa21xc3NzcnBsbWxsamdlY2FgYWJgYIdfgGBgX19bWlxcX2JhYWFiYmJjZGRkZmdrbWpnZWVlZ2poaWxsbnBzdH+HRUtVXGZvdHl+gIF6c21qaGRgY2VjYmNfXmFbUk9MS0dFRIeGREWNiYSDgX99fXx6enZzbWpnZ2ZlZGRoaGpqaGVlZWJjYmBhY2VpaW5uamJjZGlqaWNfH11WV2FfXFxgYmhnaF9bV1ZWVFZZWVhbXVtZU1JQT06FUGBPTkxMS0xKSkxMTVBUV1hYWlRRTlJbW1tdYGRcWVtgXFpTUVJQTEhHSElKSkpLS05PUlNTVFVXVlVWWFhXV1dZWVpaW1xdXV5dW1tbXFxbWl1eYWNnaGlpamtpamlpZ2qEaYBoZ2dmZmdrb3Z4en2AgYCCg4GAgX9+f4CBg4GCgX99fHp4eHh6fn5/gICBfn5/gHt4d3d3eHh2dnh7fn17dnR2c29vb2xueX+Ae3l7fX57fXt9e3d4ent5eHNub29qZ2JeXFtaWlpbWllZWVpbXFxdXV1eYWFhZGRnaWhrbGtsbj52foF9fHt6e3x8f4GAgYaIjIyLi4qNSlReZWdjXV1fYF9gZ2lucnd3eXp4eHl5enl6fHp8fXl3dXR0dHl5eYR7Nnp9hImGiI2Wmp2ZlqGkq7W+vr27ubmmnJSMiYSDgoGDhoeMi4eHhoWFgoiOlJSQjoyRlZGNkYCpra62vsLFyse6taqioJ2en5+cl52gqq2tqJuhsLirnJmbm5SWlI+JjIqMj5mVmaGlopensKuhlpSOjpSRjYmNiouHhISFhYJ/fX58fH99fXl4d3Z1dXZ1dXV2eXx6dHFwcG9vb25ubW5vbW5ua2lnZ2pscXBvaWhnZWVjZGRjYoBhYmZsb3uDh5CLipSTj4uLhYKAfnl2eXt7fHx8e3p4d3d5enl3dHNybmtsa2trbG1vcHF1d3Z4ent68erj5Obb1tnXzsW8sa2spqaoqKSen56en6Chp6yoqaqmpaKgnJ2Zko6Njo2MiYJ8e3t8e3x8fX59e3x8fH5/fnx8fX59fn5+fX1/gYKBgYKFipGRlpaWmZ6ioJiTkI+Qjo6PkJKUlJKSkpGQj5CRkpOXmZiWlpOTko6OjY2KiIaEg4KBgIB/fX19fn59fX1+fX18e3t6ent7e3x+f39+fX1+f39+fHt6e3t6eXh5eXt6eXl6eHd2cnFzdnt8enZ1c29xcnWEdgh1dHZ3dnZ2dIRyanNyc3JzcnJydXV5eXZ3e3t6eHl6fX5/fX2Af3t3dXV4eXp8foSDgoF8e3l6fXx9fn+Df3h4fX99fHt7f318foGCgoB+gIB/fn1/gIGBgIGBgoODhYeHiIqMjo6OjYyMiYmIhYOCgoGBgX+GgA2BgYKAgYKAf4B/gYODhIWAhIWGh4WIjJCSj4yJiYqLjI2MjY6Rk5WXoalWXWdtdn+GiI2Qj4iCfnt4c3F0dXRzc3BucmxjYF1cWFZVqKdWV66rpKKhn52amZmYlpOOi4qKiYmHiImJi4qIiImHhYaGhYWFhoqJjIyJgoOFiYqIhIB+enyDgHx/goKGhoeBfnpMeXl3eXx7en5/fXx3dnRyb3Jyc3NxcHFvbm5vbW1vb25wc3R1dXZwcG1vdnd4eXx+eHV3enh2cHBwb2xpaGloaGpqamtubnBxcXR0dYl0gHV2dnZ3d3h3d3Z2dXV2dnZ4enp8fn+AgYKDgIGCgoGDgYGBgH9+fX19foGEiIqMjZCSkZOUkpGRj4+RkZGTkpORj46NjIqKiouPj4+RkZKQkZKTkY6MjI2OjYuLjI6QkJCNi4yJhoaHhYeOkpOQkJGSlJCTkZSQjY6PkY6MiYWHgIiDgX16enp5eHh5eXl4eHh5enp7fX5+foCAgoOGiYiJi4uMj5OZnZqamZmbm5qeo6Ohpairqqmqq65aYWxzdXFra25vbXB2eHqAhISGh4WFh4eHhYaJiIqMhYSDgoGChoWGh4iJiImMkZWSlZyipqmkoqyutb7Ix8XEw8Kwpp+ZHJaSkpGQkZSUmJeVlZKQkpKVmqCfnJqXnKGemZxLv8LEyMzN0dTTycXAuri1s7O0tLK2tLW1tbSzs7e4trS0s7O0s7GvrKytsLGysLG0trWztLa2tbOxsLCxsq+vsrGyr62sra6tqqiphacTpqalpKSjoqKjo6OipKOhn56enYScCJ2cm5qamZiXhZYhlZSTk5KSkZGRkI+Pjo6QkJGTlZWWlJWYl5WTk5OSkZGRhY81jYyMi4uKiYmJiIiHhoaFhISDg4OBgICBgYGAgICBgYD//vv7/fv5+PPw7+zo5ebm5+bl5OKG4w7k5eHk5eDg3d7b3tvY1YTUDNLOxsfGx8bGxcbHx4TGDsfJx8fGyMjIysnHxcbIhMkby83Pz9PS0dHV19bV0tDO0NDOzc3NzMzKy8rJhsggycfIycrHxsXFxsXFxMTEw8LCwsDBwL68vb6+vby8vLuFvAq5ubq8vbu7vLu5hboBuIS2J7W2tbe3uLaytre3tbKxs7W2uLi3tbSzsbKytba0tLKzs7S3ubm3toW1RrS1tLSztLS2t7m3tbe3uLm5u7q7vb28vL6/vru6ubu8u7q+wsHAw8C/vb2/vr7AwL/AvL2/wL/Av77Av7/AwcPFw8HDw8SFxYDGxcjJyMnNzc/Ozs/P0tTW19bW1dPS09PPzc7O0M/Q0dLS09bW1dXV1tfU1dbV2Nrb2tnc3dzb3dvd4eTo6ebj4ODh4uTi4+Xl6u3v7/T7gIaNkZmfo6irrK2po5+cm5iVl5mYlpaTk5WQiIaFhYKAgP79gID//Pn39PHv7uzr7RHr6ebk4eDg4N7d393f397d34TeQN3c3Nvb2drX1tbY29ra2dfW1NHS19TT0dLV2dra09HOzszJyczMzc/Q0M/JxsXDwcPDxcTCwMHAv7++vL2/vr2Ev4C9vru8urzBvr6+v7+9u7y/u7e3uLm4trKwsrKztLS0srO1tba2tre3trS1tre2tba2tbW1trS1tbSzs7S1tbSzsrS2ubm6ubq6vL69vb2+vLy9vr27ubi2tra3uLm8vb6+v8DBwsHBwcLAv8HCwsTExcLDwcG/vL2+v8PCwcLDxQvExcbJx8bExcXExYTEgMfHxsPBw8LCw8LBwcfKysjJysvNys3MzczLysjKycrIw8XFw8TEwcC/v7/AwcHAwMDCxMXFxsfHyMrNzM3O0NLR1dfY2Nne5Ojk4+Tj5ufn6+7u7e/x9fX3+fn7gIaMkZKQjYyPkpCQlJeYm5+foKGhn6CgoZ+ipKOlpqKhoaChPqCioqOkpaalpKerr62wsrm7vbu5wMHFzNHR0c/NzsK+uLKxr66tq62xsLOzsbGvrq+tsra5uri2tLe7uLa4/4G7gf+A/4DxgKKBBICAgYH/gP+A0YDigQICBACAm56kpqurqKaup6Okn5OSlpqgl52hnpuov72bnrzLvaWWkYuDhYaCfnt4e3+Ji5mdn5eJipGZlo2PjYyPhoWAe3h2cnJ0dXZ1dXRzcG5qamtra2lnZ2dmZWVlaG5taGRhYWFgYWNsYl5fY2ZlZGFcWlpaWVlcX1tZV1dbVllYV1WAVFVaYGlweICTmpmZkIuJiYR9enl2d3h2e4CBfnt3d3Z0c3JzdHFsaGhpaWxvcXJxcXV6f4OCf4CA/YCD/vHo6erl2dHSxa6dl5WWlpOJhomJiYiGipCdop6anpaMh4aIhn55eXdvbmxlYmFgX19gYmNmZWViYWFiYmRjYmJiZWdFaGpsaGZqbW56jZebnqGglY+PiomCenl4eHh6ent+foCAgIKBgH+BgoOFhIeKhoSAfXt6eXh3dHJwbmxramlqaWlpaGZmhGeAZWRjYWBgYmNkY2Rna2tsamtsbGtqaGhoZmVkZGZlZWRhYWNiYGBfXFxcXmJjYVtaWFhaW1xbWltZV1ZXWVtbWFdWVldZXVtYWF9gXFhYWF5dXWFiYl9dW1xdXl9eXmBjX1xZX2ZoZ2ZjZWNhYV9hYF5ja2tqcnRxaGJnbmpiYGIrbGxkZWhrbGppaGdlYmFfYWVmaGlqa2psbW1udHd4eHd0b2tnZWVjYWFhYIRfIl5gYmFeXV1eYGBfYF1cWltdX2BgYV9gYmFeYWVmY2NjYmKEZBZoZmhqamtraWxwdHV5Q0dLVFlZW1xfhGYcYltWWFpbWFVVVFdeVlBNSklHRkSGhIWFhH98e4R6cnh4dXRxbmppaGZoamxtbWxqZWRfXl9eW1xjaWtvbXZ1cGJgYV1fY2FZV1hcYVtYWl9haHt2XVZTVVZYWFpeXlxZU1JTU1JQUlBNT1BQTktNTUxJSkpMTlBUVlpfWlBMT1ddWlxhX1pYXWdjV1BPUE9OS4ZKGExMTU9QUVNTVVZWVVNTVFVWV1VWV1hZXIRePF1bW1lYWlteX2BiY2NmZ2dpaWZoam1pamtta2tqaWhpaWxvc3p9goWHh4WEhICAgoOCgoODhISGhYF+fIR6Fn2Cgn9/goGAf4CBfnx7eXd3eXp7fH2EfiZ7enVycXByeH2AgH6Cf39/fXRyc25rcHVzd3hvcG9wb21oYmFfXoVdJVxbXFxeXmBgYWRkZWxxcG1rbXF5fnt2eX19fn+BgICChYWDhIiEjmaNjo5LUVNZXFxdXmBgX15eY2hpbHF4fHt9fX58e3t9fHd4eHx3c3FwbnF1dnd4ent8gIaJiY6TmqCioJ2bmpieqrS2tKugkouGg4OChIWFhYmJhoaPl5uakYiFhoqPlI+NjY2MjZRNpqmusLS0sbC2sKysqZ6dnp+knaGkoqCpubOforS/t6mfmpeRkpOQjYuIi42TlZ6hoZ2TlJienZeXlZWXkpKPioiHgoOFhYeEhYOCgX+EfR98e3l4eHd2dnd6fnx4dXNycnJzc3hyb29zdHRycGxrhGoza2xpaGZmaGVmZWVkY2JlanB0eYCKkI6OioaFhYF+fHt5eXt6fH+AfXt5eXh3dXV1dnRwhG6AcHFyc3Jyc3V7fXx5enrzenz06+Tk5OLa1dXMua2qqqysqaKfoqKhoJ6hpq+zsa6vqKGfn52clZKRkImIhoF8fX5+fX1+f4GBgH99fX1+gH9+fn6AgYKCg4F/goSGjpukpaaqqqSgoJ2blZKSkY6Nj4+PkJGTk5KTkpKSk5OVl5UZl5mWlpORj46Ojo2KiYiGhIOCgoOBgoKBgIR/GYF/fnx7e3t8fH18fX+BgIGAgYGAgH9+fX2EfBl9e3t7eXl6eXh6eXZ1dnh7fHp1dHNzdXd3hHWAcnFzdHZ2dHRycXN0d3Vyc3d5dXJzdHh4eHp7fHp4dnd5enp7e31/fXh1e4CBgH99f359fXp8fHyAhISDiYmGgHyBhYN+fH2Dg31+gIGCgYGBgoF/fnx+gYOEg4WFhoaIiYyOkJGTkY+MiYaFhYSCgICAf4B/f3+AgoGAf3+AgYKAgYOBgH9/gIKDhYWEhIWEg4SHiomJiYaFhoeIiIuLjIyNjo6MjpGUlJlWW19ma2ttbXB3eHd3c2toaWtsaWZmZWhtZmBdWlpZWFSmpKWlpqCdm5mZmpmXlpWVk5CNi4uKi42Mj4+Oi4eGgoCBgoGBhYmKjYmQj4yDgYKAgYSCfn2AfX+DfXt9gYCHl5SAeXh4eXt6fH5/fXt2dnl4dXN0c3FycXFwbm9vb21tbW5vcXR0eHx3b2tudHh3eXx7d3V4fnx1cG5xcW9sa2traWlqa2xtbm9wcnJzdHRzcnFxdHV2dHNzdHV3eXp5eHd1dXR1dnV3eHp8fX1+f3+AgYCChYZsg4OEhYODgYB+fn+BhIeLj5KUlZWUlJSRkJOUk5OTlJWVl5aSkI2NjIyNkZSTkJCTkpKSlJaUkY+NjI2Oj5CRkJGRkpORjoqIh4eKj5KTlJSXlZSTkYyKioeFiIyKjY+JiIaKiYiEf359fHt6h3uAfX1+f4CCgoSIjIyLiouQl5ual5qbnJydn5+foKKkoqWprq+xsK+urlpgY2lsamtsbm5ubW5yd3h6f4SIh4iJjIuJiImIhYaHiIaCf35+f4OEhIeJiImNlJaWm6Cmq62pp6Wlpaqzvr++tquflpOQj4+QkpOTlpaUlJqjp6WdlpMLk5ecn5uamJmamqBov8DBw8fGxcTGw8HCv7i4t7a1tba3t7a6vLm2tbi5ubi2tre0tLWzsa+usLCxsrW0tbWwsLK0tbW0s7S2tLe0s7Kxr66urq+urq6tq6qoqaipqKinpqalpKWlpqelpaOhoKGgn5+gn56EnQecm5mYl5eWhZSAk5OTkpORkY+Oj5CRkpOTlJaXl5eVk5OTkpGQkI+Qj46Pj4+MjIuMi4qJiIiIh4aFhYWEhIKCgoGAgoODg4GAgYD/gID//Pr7/Pn38/Tx7Onn6Ojp6OTk5eTk4+Hk6Ofn5uTk39/e3N7d2tfW19TSz8zJysrJyMnJyszMzMnIxsc+yczLyMbGyMnJyMnJycrKyczU2NjX2djW1dbT1NPQ0dDPzc7Nzc7Ly8rKy8rJx8jJy8rHx8nIyMjHxcbGxcWFxDzDwsDAv8DAwL+/wMC/v729vLu8u7u7vLy9vb28vbu8vLu6ubm5uLe3tra5ubq4t7e5uLW1tLS2t7e4uLiGtIS2K7S0tLW2ubq3uLWztbe5t7e5ube2tre3u7u5ubi6ury7uru9vr29vr+/vr2GwGjCwcDBwMC/vsDDwsHFx8jCwMHFxMPCwcPEv8DCwsPBwcPFxsfHxcXHyMrMzczNz9DR0tTW1NbX2NfV09PQzs7Pzc3Mzs7Q0dHT0tPU1NTV1tfZ19jX1tja29vc3d7f3dvd4OHh4uLg4YTiTubj4+Hk5+Xi5ujp7fSBhIeOkZOVk5acm5qZlpGPkJKSkI6NjI6TjIeGhIOCgID9/Pz7+vXx8e7t6+zr7Orq5+Xj4uHg4uPk5eXk4d7d2oTcOtvc2tnc19vb2tjX19bX2dfT0tHT2NPP0NbX1+Tl1dDNzMzNzM3OztHPysjJycfFyMfFxMPDwsHCwL+GvR++wMHFvbq7u7zAvr/BwL27vcLBure3uLi3tbS0tbOzhLQQs7OztbW3t7i3tra3tra2tIS1WLa1trW1tLOzsrOztLa1tbm5uLq6uby+vb/AxMDAwcLAv726uLi3t7m8vr/BwsTFwsLDwcHDxcPDxMTExsfGxMPBwL6+wMTIxsTFyMfIycrMysjIyMfFxseEyIDJyMfHxsXFxMTHx8rNzcvMy8rJycjIycbDxMfHys3KysfJy8zIw8PEw8PDxMTFxMPExMfIycrLzc7Q0tXU1NfX2d/j4+Di5ebn5+rq6uzv8PDx9Pn3+Pn6+/uBhYeMjo6Njo+Qj46NkZWWmJugo6Gjo6Wko6SmpKChoaWioJ6dnD2eoaGipKampqmrrq+1tru/wL69vLu6vsTKysrGwLmzsKysrK6vsLCzsrCwtru9vbixsbK1uLq1tba2trW6/4G6gQOAgYH/gP+A8YChgf+A/4DTgOOBAgIEAFGKkZSbn6CeoKKho6SyoJGRlpudlJGUn6a5vJuRmqaysY2HiIaGgoKEg4B/gYSCiYeIk5WEg4uGg4aQjIWCg396dXRycnNzcnN1eHh0b21vcXWEbIBrbGtqamttbGhlYmNjZGp8fXJoYmJlaGpqZWJdXWJoZ2hhXFtcWllaWFdYWFVaXGt9cW55gIiLiIaEhIWEf4CHiomJiomGgn16eXp6enl3dXFtaWdnbXV4ent6eHp/hIaJh4aDgoKIjIiDgn3r4+bm0biusaueko6LiImOj4+JhEyGjpSVk5eMh4WBf4CAfXl3dndybGdkZGVlZ2loaGZlZGNkY2JjZGRkZmhqbG5rZ2dscn6Pl5qgnZuZlJCKgX58fX58enl7fH2ChISDhoKAg4OEhISCgH56eXh3d3d1cXBvb21sa2trbGtqamloaWpqaGdkY2RkY2RmaGdoamprbG1sbW1ra2pqamlpaWdnZGJgYWFhYGBgXlxcXF1fYF5aWFhYW11dWllZWFlXVVVWVlRYWllZWFhbWldaXVtVWVlcXV5dXFxcWllXVlhZWVtOXV5cXF1gZmZiYF5fYV9cXWFkYWNlZGJpaWVjZGdqaWdmZWhpZmNndHh3cWpmZGNnY2JlaGpra21tbnBwcnN1dXRzb2lmZGJgYGBhYmJghF+AXmFhYF5eX15eX19fXlxeX15dXmBhY2NiYWFiY2JfX19gYmNkZGZlZmxqa2tpZ2hucHl4gURITElLUFNXWFlYV1ZSUlVXVlRPTlFbWVRNSUhIR0VGRYSDhIB9e3p7e3h2dXR2dXZzcW5raWhmZGRna2tlY2BcW1xbWmNpbmd0dHReaF9aVVNXWFhZWVdYWFdYV1lnZ2RoXFdYWFtaVlVVUlRQUVJSUU1PUVBQU1NPTkxLTk5NS0xOUVRYXFtTTUxUYF5ZWltbXF9mYVlSTlNUU05NS0tKTE1NTk9QUlJSU4RUBlNSU1JSVYRYN1laW1xeXV5cWlpZWVpcXmBgYWNlZ2Zna2ppa2xsbGtra2pqamdnZ2tvcXd7foGDh4qLiomFg4SFhkuIiYmFg4F+fX18f4KGhIODgoGChIeDgYCBf4CChYeFgX9/hIeJhH96d3h4dXV1e4CAf31+e3Zzb29tam1ydHRwbG1ybWxuamBeXl+EXoBfXl1eX2BgYmNkZmdsc3V0dXZyd31+hYuEgX+BhIODhIWGiImKi42PkJJLTU5RVl5dXFhaXV9iY2NocXV4enp3e4SAfHl5en1+fHl5fH16enp4eXt8fHx9gYeHg4SCgIKIkJaUlpiVkpGTlpqbm5mPh4OAgIF/gIGCgYCFjJujnRCTi4aCgoSIj5KQjIiGhIWHgJedoKWoq6msq6qsrburnZydoaOdnJ2lqbW1oJqfp7CvmZaWlJSRkZOTkI+QkZCUkpSbmpGPk5GQkpeVkpCRj4mFhoODhISDg4SGhoSAf3+AhH1+fX18fXx6e3x9fHh2dHN0dnqEhH53cnJzdXZ2cnBta29zc3NtaWlqZ2ZnZmZmFWVjZWdyfnV0fICEhoWEg4KCgn9/g4WFA4OAfIV6THl4dnNwbW1tcnd4eXl3dnd6fX5/fn58e32Agn97e3fm4ePm1MC5urevp6ajoaKmp6egm56lqqupq6OfnZqYmZmWkZCRkIuHg4KBgoGEg4SBM4B/fn+AgIGCg4WGhoOBgYSJk52kpqqopqWinpqVk5GSlJKQj5CRkJSWlJSTkpOUlJOUk4SUDpKQjoyLjIyMi4iHh4aFhoODgoWBAoB/hX4Gf4B/gIGBhoIDgYB/hIAhf35+e3p6enl5eHl6eXd2dnd5eHZ1dHNzd3l4dnV0dHRzhHIScXR3dnV0dHd3c3R2dXN1dXd3hHhdd3Z1dHR2dnV2en17eXp8f4B/fHt8fXx5en2Afn+CgH+EhIB+fn+BgoCAf4GCgH6AiYuKh4KAgYGEgYGEhYaHh4mJio2PkZCPkJGQjYiEgoKCgYB/gYF/fn+AgH+BhoBZgYKCg4KBgYKBgoOFhYeGhYODhIaHhYSDhYeIiIiKiYqOjo+OjIqLj5GXmaFUWl1bXWFlaGlpamloZGJlZ2dkX19ibGpkXlhYWVdVV1amo6WhnZybm5yZmJaElYCSkI6Oi4qJiImLjpCJh4OAf4B/foOIjYeOj46Hgn55eHt8fH18e3x7ent6fIaFh42AfHp7fXt3dnd3eHR1dXV0cHJ1dHJ0dHFxb25wcXBubm9xdHV6enJta3F7end4eXh5e357d3Jwc3Rzb21ra2xsbGtsbW1ucHFxcnJzc3JxcYBwcnN0c3R0dHZ4eHl4eHZ1dXR1dnd3eXt8fX5/gICBg4OFhISFhYWEg4SEf32AgoSFioyPkJKVmJmYl5OTlZeWl5aWmJmal5WTkZCPj5GUmZeWlJSVlpaZlpaWlZSUlpial5OSk5iZm5eUj42Njo2OjJCXl5eUlZKNioeGg4CFi4CMjIuGh4uJiImFf319fn18e3x9fn1+f4CBgYKEhoaKjpCRkpSSmJ+fpKmknp+ipKOipaWlqKmrrK6xtLZbXFxgZm1sa2dna25xcXF3f4OGh4WDiJCMiYeGh4qMiYeHioqJiIiHiIiJiYmLj5OUkZKRkJGVnaGfoqOgnp2ho6anqCKknZWRjY2Pjo6Oj46OkpynrKeemJOQkJKWm52cmZWTk5SUEbW6ur6/wcDCwsLEwsvCubm6hbgsubq/vLi3t7m8u7i5ure2tba5ube1tbSys7O0uLWzsbOysrKzs7O1t7W0sbGGrwWtsLCvrISrJ6qrqqmoqKemp6empqeloqOioqKjpKKgn56dnZ6enJqYmJiamZiWlYSUf5OSkJGRj5KSkpWSk5OTlZWUk5OTkpKSkZKTkpCQj4+NjIyLi4uKioqIiIaGhYWGhYSFhoSDhIWFhIODg4KCgoGCgoGBgP76+Pj07e7u6ujn5+bm5efm5+Ti5OTm5ePl4uDe3d3c3NvZ1tTU0tDOzcvMzM3MzM3NzcvLy8rJy8qEyU7Ky8zKycrMztHY2drY19jW19bT0dDQ0tTS0M7Qzs/NzczMy8vLysrJysnJyMjHyMfIycfGx8nIx8bGxsXFxMPCwsLBwcG/wMHCwL+9u7yEvQu+vsDAv76+vr28u4W6B7u7ubi6uriEt262tre3tbS1t7i4t7e1trW4ubq5t7a3t7e0tLa2tLa3uLm4uLq5tre6ubW4uLm6urq5uru8urm5u7y9vr/Avr/AwL/AwL+/wcLBv8DDxMLBwsTFxcbExcTExcXFxMPDw8LAwsbHxcTGyMnJy8jJzITNfM/Q0tXU1tfZ1dPU1tTRz87Nzs7O0NDNzc/S09PU09PU1dXV1tfY2dnX19fZ29zd3d/f3tzd3t7f4ODf3+Hi4+Ll5eTl5OTk4+Pl5efs7viAhImGiIuNkJGRkI+OjIuOj4+NiYmMkpCLhoSDg4OBgID9+vn38/Hv7e7r6OmE6IDl4+Dh4N7h4uLi5efg393a2dnY2dnY29nc3NvX2NjU09TX1tfU0NHQ0tLR0NXV2+LX0c/P0M/LysvKy8fHx8bGxMbHxsTGxMLDwcDAwL+9vr6+v8HEw766u77Cwb6+v7++v8PAvbm3ubq5uLe1tbS0tbW2t7W2t7W2t7i2t7i4t1y2tLW2tbW2tra3uLi3t7a0tbOzs7W2t7e4uLm5u7y/wL/CwcLDwcC/vr++u7q6ury8wMHAwcLDxcbExcTDxMbFx8fGyMnJxsXFw8LAwcXKzcrKysvMzc3Qzc3MzITNgM/NysnJzczLy8rIyMnLycnJy83Ny8jLysnIxMTDwcPHzMzMycvPy83Qy8XFxcbExsbHxsfHx8nJysvOztDQ09bY2Nrf3eDm5uzx7ufp7O/u7vDy8vP29/f3+Pv+gYKDhYqNj42Kio2PkZKTlZuen6GhnqKop6WioaKlp6WkoqOkBKKjo6OFpDWlqq6urKyrq62wtbm5u7u4tra3u72+vbm2sa2rq6uprK2trayvtL7Bvbi0sa6usLO4ubi2tISy/4HCgf+A/4DugKGB/4D/gM6A5oECAgQARISHkJWgoqKhn6Knq7Sjk5GVm5SUkJKZnq7ErZOSkpmoloiMjY2NioyOiI2PioSDg4iIlY6Dho2Nh4iCfXl6eXl6dnNzhHKAc3V2dXRxc398cGxvd3J0c3JvamlqbXFtaGhob3+Ki3tuZ2twcXFxb2lmZ2ZlZWFgX19dXF9mbm1tY1xkgJFwY2N0gISMjIqKjY6Pk5ucmpSRkIqDf4GDhIN/fXp1c3R2dW50fn+BgoB+f39/fX5/fnx+goJ/e3ry7+nn5tnFxL5yuLanm5KMiYeKi46LiI2QjpCUlIyGhYJ/gIB9fIGJhntzamdlaGpqa21sa2lnZmRjY2RlZ2htb21qbGtqam1xdn2Di4uIi5iXk42Hg3t6foSGhYOChIaFhoWEhYSCgoODgoJ+fXt6eXh2dHJzdHNwb25thW40bGxta2tqa2xtbGpnZmZnZmZpamxra2xsbG1tbm1sampraGdmZWRlZGJhYGJiYV9eXl1bXIRdgFxcWlhYWFZWVVlaXFpXVldZXGJkYFtXVVZZWVtbWldZXl5cXFpZWllaWVlYWVdWVlhaWltcXF9fX2BgX2BgW1lbXFtbXFxcXV9fXl1cXV9hY2RjYmJjaHF5dnJuamtqaWlpa21tbm5vb3Bvbm5vb29ubWtoZ2diYF9gYWNkZGRmZWVnZmNgX15eX2BeYGJiX1xcXVxbXWFlaGpqZWRiYmRiYF5dYGNiYmVoaG1xc3dvaWVnaG1udXl5fkJERkpIRkpNTExMT0xQU09JRUtTVlZRTUpIRUZISUSFgn97e3h1dHZ3dnZ2hXeAdXNybm1raWdlZWNjYVtZWltbXl5fZXt+a2dpWlRUUlJWVlNVVldZWllaV1dbWVdaXV5VU1FPTlBUVVVVU01SVldTVlJSUVFQUVFQT05QVFhcXFNNTVNfYF5bWFZgZ2hkWVNSU1RUUk5MS0xNTU1QUFJTUlJTU1RUU1NUU1JSUlUfVldYWVpaWlxcXFtbWlpaW1tcXl9gYWNlaGhmZmpra4VsgGtqaGdnaWptcHN4fX+AhIaIiYyMjY6OjYyNjYyNjIuKh4SDgoKChomOjoqJh4SFhomIhIGChImNjIqHhoGAhIWJiIaHhX99fYB8gH5+gIB+d3Fubm5wcHBxcnJuaWtraGlnY2FeXmBfXl5gYGFgYGJiY2RlaGppbXJzc3N2g5KNe4eGgoGFi4qHhoWGhoeJi46Sk5ZPVFtfXl5jY2ZjZGJhZWZrbm90fIaPjoqHf3t1d3h4eHp9d3Z9hI2PioaJi4eJiIeIioyKjY6IhYWFh46WlYyIioyMjYyJiIWDg4mGg4KDgH5+foGIjJSUjYqIhICAg4WHio+Oi4eDhDySlZ2irK6sq6issbS8raCdoKWfn5ydoaSvu62enJyiqp+YmZqbm5mam5ebnZmTkpGUlJ2ZkJGXl5OSjoyFim+IhoSEg4ODhIWGhYSBgoqHgH5+hYGCgYB+e3p7fX58eXl4fYeMjIN6dXh8e3t8enVycnJxcG1tbGxqaWxxdnZ2b2ltf4x1bW13f4OHh4eGh4iIipCQj4yKiISAfX+AgIB+fHp3dXV3dXF1fH1+fnyEe2x5eXt5d3l8fHl3duvo5eTk2svMxsLCt7CqpaGho6Smo6ClpqWmqqihn52amJqal5ebnJuTjYeDgYWHhoaIh4WFg4GAf35/f4OFh4iGhoaEgoOGiIyQl52dnJ2ko5+amJaQkJSYmpmXlZWYmZeEloSUKJKUkpGPjo2NjIqJiYqJhoeHhoWGhoWFhISFhISDg4KDg4KAgH+AgICFghODgoKEg4OCgYCBgX9+fX17fHx7hnoveXl4d3Z3eHh3dnV0dHV1cnJydHV2dXNycnR4fX14d3VzdHV0dHV0dHZ5d3V2dXWGdj13dHR0d3h5eXp6e3t9fn17fX16eXt8e3t6e318fX17eXl5fH1/f39+fn+DiY6MioaFh4eGh4eIiYmKi4yMhI0ii4uNjYuIhYOCgoGAgIGCg4OEhoWGhIGAgYKBgoKBg4SEgoSBgICChIeKi4qIiIeHh4aDgoKFh4iIiouKj5KSlZCLiYqMj5CVmJuiVFZXWllYXF5fXl9gXmJjYVtXXWNnaGReW1hWV1laVqekoZ2dm5mYl5eXlpWVlpaUlZORkI6NjYyJh4eJiIV/fX5+foGBgoeUlYaEhXx3eXd3enp5enp6e319G316fIB+fH6AgXl2c3FydHd3d3h2cHN3eXV3dIZzVXJxcHB0d3p6dG9uc31+fHl2dH2Cgn52cnJzc3Nxb21rbG1ubW5ucHFxcnJyc3Jzc3JwcHFyc3NzdXZ2d3d4eXh4d3d1dHZ2eHp8fn5+f4CBgIGDg4SEhhGFg4KBgH+AhISGiY2QkZKVl4WZAZuFnCWbnpybm5mXl5SUlJeaoaGbmpmWl5ebm5iWlpianp6dmZmUlJeYhJuAmpWUlZWTl5aUlpeXkIuJiIaJiYmLi4uIhYmLh4aFgoB9foB/fn1+fn9/f4CAgoOFiImKjZCQkpOXo7Ktp6aioaaqqaampaampqirrbCytl9la25tbHFydXJycHB0dHl8fYKKk5mYlZSMh4KFhoaGiIuHhIyTmZuXlZeXlJaVlpc3l5qYmpqVlJSUlZqhoZiUlpmZm5qWlZOSkpaTkI+PjYuMjI+VmqGgm5mWko6PkZOVl5ybmZWTkUSxtLq9wsPDwsHDxsjMxLu4ur26uri4ubq+wry4ubm5u7m6vLy7vLy+vrq9vrq2tbO2trm3srKztLGysbKytLSztLSzsoSwEa+xsrKwra2uraqpqq2sq6qqhKksqKelo6SjpKWop6OhoaGgoKGenZuampyZmZiXlpaWlZWWl5eVk5OVlZiUk5OElB2VlZSUk5SUlpWTk5KRj46NjY2MjIuLi4iJiYmIhYaGRYSFhYWDgoOCgYKCgYGAgP7+/fz79vPy8vH07+zp6Obn5ubm5OXk5OXk5eTj4eDg39/e3NrZ3dvX1dLQzdDQz87Pz8/OzITLR8zLysvNzszLzMzLzM7O0dTU19bV2NnZ1tXU0tHR09PS0tLQ0NHPz83OzczIysvMy8zLysjJyMnIxsbHx8fExsbFxcXEw8PDhMI0wcLCw8LBwL++wMC/wL/Av7/Av76/vr29vLu7vLy8urq6u7u5uLe3uLm4t7e2t7a3uLi2uIa3Q7a1t7m6uLe1tra2uLi5u7m4uLe2t7i4trm8vLq5ubm6uru6u7y8u7q6vL2/vsDBwsLCw8PCw8PCwcPCw8TDxMPDxcWFxArDxMTFw8LBxMfKhMgVzc/O0NHS0tDR0tPU1tjY2NbU1NTShNCAzs7NzdDRz87Q0dPR0dLU1NXV1dbW19nY19jY2trb3d7e39/g3uDf4ODe3t3d3+Di4+Pj5OXl5OXn5uPl5ujo6+71/4KDhIaEhIiKiYiIioiLjYuHg4iNjo+Lh4aEgYKDg4H9+/j09PHt7e/v7uvp5+jo5uTj4+Hl5OPh3+Li4+R04tvZ2NfX1tbY2d7d19ja1tLR0NHV1NLRz8/P0M/Q0NTZ1dLX2NfMysnIyMnKycjIxsHFxsfFyMTFxcPEw8PBv7+/wcLFxb67u77FxcK/vrzBxcbDvLq4urq7uri4uLe3trW3uLm4t7i5uLi4t7a3trW1tLWEthu3uLi6uLi2tre2tba0tbW3uLm5ury+vb3AwsKEwxXBv727ubm6u7u+wMHBw8LExMbGx8aEyBrKzMzKzM3LysnHx8bHx8vP09TR0dLPz87Qz4TNgNHU09HOzs3KzMzO0NLT09DPz87Kzs3Mzs3MysjGxsXIx8jIy83Kx8vNzM3Mx8bFxMbFxsbIyMrJyszLzc7Q09PS1djZ29zh6Pj07+/u7e/09PDx8vP09vb4+fz9/4KGjI2NjZKRlJKSkY+UlJaXmJ2iqLCvraulop+ho6SipaejCaGlqrGysK6xsoSvOLCxsq+ws7Cur66xtby7tLGxsrO0s7KxsrCusK6trKyrq6yrrbGzuLm0tLKwra6vsLK0uLe1srKw/4HAgf+A/4DzgJ+B/4D/gMuA6IECAgQAgI6NkI+Qj5CSmqmmqKerramhnpybnJufnZumuqyjoaakmoyQk5ORkY+Ql52XmIuHhoOGi5aShIWGgYR8d3Z2d3l6enx5dnV0dXd2dXZ1dXRxb21sb3JxdXNvbmxscXyGfXR1cGpxhYB/ent9enVucG5uc2xjZGRgYWFjZWFjZF5ggGhyaWt1eWlhb36DjZSSkZOUk5ebo6ShoKCenJSRlJOQjYeBfn1+fXp2dHyBhIR/fn9/f356foJ9fHx8fnvzduHe4ePg1cbEtqqgm5iSj4yKjI6OjIqKiYmLj4yKiIiHgX9/goiQnJyLdWhmZmhrbnFxcHBvbm1tamdoZ2ZkZ2tuPW1rb3FtbGxwc42SioB9io6MgXd2c3N+i5KOiYaFhIOCg4SEhYSEhIOBfnp4dnd3dnV1c3R1dnV0dHJxcG+EbiZvb25tbW1ubWxpaWtraWlrbmxtbWxsbGtrbG1tbGppZ2VlZGJkZIVjgGBeXl1cW1xdXFtbXV5cWllYWVlWWFhaWllaW15gYWRgXVtYV1laX2NgXVtaW11dXFlYWFhZWVlYWFdWV1hYWVxcXV1eXV1dXFtbWllaWVhYWlxdX19dXVxdXl9iY2NhYmVqbG1tbG5ubm1ramtrbWxub25tbmxpampoZ2VjY2RmgGdlYmNkYmNkY2RoaGtsbGVhXl1gYWFiZGdlYV9dXF1dYGJlaGhobGZiYmNhX19iYWJkZmlpamxzdnR1b250enh6cWtpanF9gYBBQURHSUlJR0lKSEdETkxJSkxLTEpEREJCQX9+fXd0dHJyc3NzdHJzdHd4d3Z1c3NycnNxbWhjW19eW1tcYWBgX15ganF3cWdfV1JSUVFPT1FSU1RZXV5dXVhVVlZXV1VUVFVWU1RWV1lVUFRYVlNVUlFSUlNTUU9QUlRWV1hTT09RWV5bWVpVV11dWlZTVlVUVFCETYBOT05OUFFTVFRUU1RVV1RTVVRTU1JUVVdYW1tcWlpbW1laW1tcXFxdYWJiYmNjYmJhY2VnZWZkZWhoZ2doaGpubW9ydnx+gISHh4mLjo6PkZKRkJGRj4+NjZCNiIaFiIuKjZSWkYyLiIqLi4iHhoiHhYiKiImIhoSFiIqIh4mLiSaHg4KAgX15fH96d3Jwb3Fxc3VycHFwbHBvbWtpaGZjYV9fYGFgY4RiFWNjZWdqbm1ub3BwcnqHj4iCgYSHg4WCM4OFiIqOk5VNVVtaW2dtcXV0cHBtaWlsaGtye4SDfnt4dHB1d3N3e316eXt2dnt/g4SChYSKOoiJh4WEh4eGiIeDg4iMjoiDhIiNiISCgYOFiYyLioiEf31/fn6AhImNjo2LiIKAf4GCg4eNjo2PjZKAmpucm5ucnZ+ms7CysLO2sqqpp6WlpKalo6q5sKurraylm5+jop+fnp+lqqWmmpeWk5SZop2RkZKPkYqHh4iKjI2NjoqIhYWGh4aFhoWFhIKBf36BgoCEg4B/fH1/h42GgIF+e4GLiIeDhIWBf3p6eHh8d3BxcG1vb3Bwbm5uamwjc3lyc3p9c2x0foGIjIuKi4yMjpGWl5STlJKQi4iLi4mHgn6EfHh6dnV6foCAfXt7fHt6eXp8eXh4eXp37HTe3eDi39fOy8G5s7Cuqqmlo6SmpaSioqCgoqWioKChnpqZl5qepKuqnY+GhISFh4qMjYyLiYmHh4WDg4KDgYOGh4aEiImGhYaJipyhmpSRmZ2clI6OjY2VnaGempeWlpaElRuXl5eWlZORj46MjIyLioqJiImKiYmJiIeIh4eEhheFhoWEg4SEg4KCg4SDhISEg4OEg4ODgoSDgIKBgIB/f4B8fHx6e3x8fHp4eXh3dnd3dnZ2eHl2dHR1d3VydHV2dnZ1dnp8fX57eHd1dHR1eHx5d3d2dnh3d3Z1dHV2d3h3d3VydHZ2eHp6ent9fHx8e3t6enl4eXl3ent8fX17ent7fH1+f399foCEhomJiYqMi4uJiImKi4qLBIuLiouEiICGhoSCgoODg4KBg4OBgoSEhYeIiomIhYSCgYODhIWGiIeFg4KCgYGDhoiLi4yNiYaFhoWFhYaGhomKjIuOkZOUk5SRjpOYlpWQjY2QmKGjo1NUV1pbW1taW1xaWVdgXlpcXlxeXFVWVFRSoqCfmZeXlpeWlZWUlJWVlpeWlZSSkgyRkZGQjImGg4OAf3+EghiBg4mLj4uDgHt3d3Z3dXZ2d3d5fX9+fX6FekF8eXl3d3h2eHh3endydnl5d3d1dXRzdnRzcnN0c3Z4eHRxcXJ4e3l4eHN0ent6dnN2dXRzcnFwbm1vcHBvb3Byc4Z0DHNycnBwcHFycnR0doZ3AnV2hXcTeXt9fX1+fn9+fX5+gICCgYKCgYSAQ4OFhIWIjZCSk5aYmZuanJ2en6GgnqCfnp6dnaGemZaWmp2cnqaqpqCemp6enp2amJqbmpyenJycmpmZm52cnJ2dnJuElwmUkZKXkpCMiomEjC2KiYuKiYyKiIiHhoOCgH5/f3+AgoGBgoODg4SHi46Oj4+QkZObqK+koaGjpqWEo22io6Wnqq2ys1xiZ2lqdXp+gYB9f3t3eHl2eYCHkI+LiYeCfYGDgYaKjIiHiYSEiY2QkZCTlpWXl5WWlZKRlJWUlJWSkpaZm5eSkpWalpKPjpCTmJmWlpSQjYyMjI2PkpebmpmYlZCOj5CPkZSZhJsBnhO4uLu5ubm6u77HxsXHyMfGwsC/hL4fvb3Aw8LExMPAvby/wcLBwL/Aw8jFxb67uri4u766tYS0VLK0s7S0trW2t7a0tLO0tbSztLGwsK+trayrra6vraqqqampq6upqKempaeqp6Wjo6SioKCfnZ2enJqamJiZmJiYl5eWlpaXmZaXl5eUk5WVlZeWloaVDpaWlZWUkpGQkJGQjY2MhIoEi4uKiISHAYaFhQSDg4SChIEzgP+A/Pv+//769vXx8fDt7Ojq6ujq6+fl5ufl5OXk4+Pi4uHe3dze3uHl4t3W0c/P0NHShNNJ0dHQ0M3Lzs3MycrNzs7Mz9DOzs7P0NXY1dLU1NbW0NHS0M7T1NTT0dLR0M/Nzs/OzsrLzc3MysnKycrKy8jIxsbGx8bFxcTFxYTEAcOHwg3Bwb/AwcC+v8HBwMDAhL8GwL6+vb28hrtavbu5u7u7uri3uLe2tri5uLe1uLm4uLe3ubi2t7i4uLm5ubq5u7u8ubi3tre3ubu8u7q5ury8u7q5urq8u7u6uri4uLm7vb/BwcLDwsLDwsPExMPCw8PExcPDhMQtxsbFxcbGxsXDxMfJyMvNz9DT1NPT1tTU0dLS0M/R0tPV09PS0tDOzc7PzszNiNAI0tLS09TV1deF2IDX2tra29zc3d3e4ODh5OPi4eDg4OHk4+Xl5Obn6Onp6Onp5Obp6urp6uvt8PT7/f2AgYKFhoaGhYWHhYWCioqGh4iIiYeAgoGCgPz59/Hv8O7v7u7u7+zq6uvq6Ofn5OXl5ebl5eHf4eDc2trc29rX1dbZ2tra2NfU0tHR0dDQ0IDP0NDR0tHR0tHR0dDQ0MzNysrLysrLy8rJxMjJysnIxMTFxMTFxMLCwcHDw8XCv7/Aw8XCwcO/v8HDw7+7v769vLq5uLi4ubm5tre4uLm5uru5t7i4uLm2tbW1t7i3tri5uri4uLm3uLi3tbW1tri6urq7u7y7ubu8vb2+vL2+vHW7vLu8u7y7vL6/wcLExMTGyMjJycvLy8rLzc3Nz87Nz87KysvNz9DS19nY1dTR0tLT0dDQ0M/R09PQ0M/P0NHS1NPU1dfX1dLRz8/Ny8/SzszKycjKycrMycjLzMrPzczMzM3NycjIyMnJycvLzM3Ozc3O0daE2nDb3N3m7vXw7+/y9PLw7u3u7/Dz9fT4/P2AhYmJjJOXmZ6enJqYlpeYlZedoqiopaShn52goJ6hpKijo6WjoqSnqqqqra6usK+wsa6sq6+vrrCwrq2ytLW0r6+ytbGvrq2usLS1srKxr6upq6usrK+yhLUOs7Curq+ur7O3trW2trr/gb6BAoCB/4D/gPiAmoH/gP+AyYDqgQICBACAkZealo6HhYeRpq6ip6ipsbmpop2dnaGhnJqXnaSstrOsoZiUlpiYl5iluqijnpaWkIuPkouHhoeDf3l3dnd3eHp9fX9+e3t7f4F8enh2dXZzcG5ub3FxcHBxcHJ8ipCNgnpwa2pqcn6IhYOEgHx4cGxzfIWHem1raG5zf4qmpnaAa25uZmNkZm2LkJKXnZmWlZWWmJqkpaerqquup6WhmpialIyHg311bW10foKCf319e3p7eHmBhImMiIF/fnjn3tfX3dzPv7etpJyYlI6LiomHh4mHhYOEhIaGhoiIiIeGgn+Ikp+jloRxZ2ZlZ2lrbnBwcXFxdHd3d3JubW5xb20sbG5ydG9sa3KMmpyZjYeHhXtwamlqcn+Kj4yGgX98fX1+gIOEhoaCgHx6eXOEcRxyc3N1d3h3dXRzcW5tbGxtb3BxcW9ubW1tbGxshG0Fbm1sbGyEah5rbGxsamhmZWRkZGNjZGNgX2BgYmFgW1laXF1aWFqEW0dcW1tbXVxaXl9fXl1dXVtZW11dXl5bW2FkZF1bWVdaWl5dW1lWVVZXWVhVW2JiX11bXF1eXV1dXl1bXGFjY2BbWVtdXVxcXIVfPGBiY2VobG1wcG1tb25ubm9wcXFxcHBvb21raGVjY2JfX15fYmNkZWVnaGRiYmJmaGxtb3Vxa2NfYWNkZIRogGVkYF1eX2JkZ2lnamhlZGJhYWJkY2ZoaWhqam1tb3B0eXyAg4SPhmxpaWxuen5+gEJDSEhDREZCQkJBQkNCQkNFSUZDQHo/gIJ+eXZ0c3JxcnN1dHR0c3d4d3VycHBvcHN2dHBqYFxbW1tdXWBjZWdocnl2bmRZU1BSUlFQUFBSTVNXYGRma2NeV1hZV1dXW15dWVhWU1JRUlVUUVJVU1JRUlRVU1NWV1VWVVJPUFBXWVlYWFtcW1hWVFZcWldWVFNRUVBQUVFOTVBSUVJThFQlVlZUVFRTU1RVVlhaXF1dW1taWllaW1tcXF1fYmJiZGRkY2JiY4RkgGNiZGZmZmdnam5vcnV6gIOEh4iJi4qPlJWUlZSTlJORj5GUk5GPjIyNj5CTmJiVkpGPjYuKjIuKiomJjZCSkpOOh4aEhoaJioqJhoKBgIOBf358eXRzcnVzbmxxdXNycnB2eHZycXBpZGFgYGFjZWRkZmpta2doam1ubWxtcXZ4Z3qAhIKBgIB/fnx7fX+Cg4KGh4mSTExLT1ZjbG5sZmRfXV1aW2Blbnh0cGppZ2Zpa2ppa21ub29zeHl6gIeNi4mHiYmDgIKDhoWEhIGAiYiFiYuJiYSGhYSEfX+DhYiPlpCMiIeHhIGEgBSDhIaFhISCgIB/f3+Bg4eKi4uMjICdo6WjmZWTlp6wuK6ysbG3vbKsqKenqammo6KnrrW9u7SrpaKkpaWjpLHDs66ro6OdmJqdmJSTk5GOiYiIiYmLjY6OkJCNi4qNj4yLiYWGh4OAf3+AgoGBgYOCgoiQlJGLhn98e3t/h42LiomHhIB7d32DiIiAdnV0d3qDiZyefFR0dnZwbnBxdYmMjI+UkY2NjY6QkJWWmJubmpyYlZOPjo+LhYOBfXdycXZ7f398e3t6eHl3eHx+gYKAfXt6dePe2trf3dTJwry2sa6sp6alpKGhoqCEnmShoJ+goqOhnpyYoKWusaeZjYaDg4WGiIuMjIuKioyOjo6KiIeIiYiGhYiLjYiEhYmcpqijnpeWlpCLhIWFjZWdn56alpWSk5KTlJaXl5eVlZKRjouKiYmIiIqKiouNjIqKioiHhIUShoaGh4eHhoWFhIODhIWEhYSEhINagoODgoGCg4KAgH9/fn9+fHx8e3p5enx7e3Z1dnd2dHN1dXR0dXh4eHd4eHZ5eXp4eHl6enl5eHh5eHV2e31+enh2dHZ3enh2dXNzc3V2dnR4fX17eXl5enp6hHsLeXl9f39+enl7e3yFez98fX1+f3+Ag4aIioqKi46NjYyMi4yOjo2NjIyLiYaFg4KBgIB/f4GChIWFhoaDg4OChoeKjI2RjYqFgoOEhYaEiA+GhoOChISHiImMjI6MiYiEh1SIiIqKjIyOj5KVkpGTl5ianp6jnY+OjJCSn6Kfo1ZWWVhUVVZUVFRTVVVTU1ZXW1hVU6BRo6WhnJmWlZWWlpWWlZeXlZiXl5WUk5GQkJKTkI6LhYKFgICEhoaHiY2TkYqEfXl3eHh3dXV2dnh7gYOEiIF/e3t7ent7fH5+enp4dnh1d3h4dHV4d3Z1dHV1dHR3d3V3dnNwcXJ2eHl3d3p6eHZ1c3Z7eHV1dHJwcG9vcHFvb3BxcXJzc3Rzc3N0dHNycXJzc3N1dXd4eHd2dnV1dnd2d3d6ewx9fH1+f35+fX59fn+EfoCAgYGAgIKEhYaJi4+UlpaYmJmbmZyio6Oko6GioqCfoKKjoqCenp+hoaasrqqnpqOhnpydnJucnJ2goqOipKCcmpqcm56fn56dmJaWl5eWlZSSjo2NjoyJh4qNjY2OjJCTkY6NjIaDgICAgYOFg4KDh4uKhoeKjo+Ojo+TlpiaoGWloaGhoJ+gnZyfoKKko6WmqLBcXFpfZXF7fHp0cm5sbGlrb3N9hYF+eHh3dnh5eHZ4fH1+fYGGh4iNlZqYlpSWlZKOkJGTkZCSj4+WlZKXmJaVkpSTkpKKjJKTlpuinZmWlZORj4SOEJGTlZSRkY+OjY2NjpCSlZiEmYC6vr+8t7O0trrGycTGxsfL0MjEwMHBw8G/vr7ExczNy8vGxMLFxcTDxM3XzMrJwcC8vLy7u7i1tre2trW0tbS1tri4urm3tre5ubi2tLKys7Kxrqytr6+vrq2srKytra6rqqqpqKiop6WmpqSjo6Ggnp6goJ+cnJuampqcnKGfmSSXmJiXlpWVl5iYmJeZmJeXl5aWlpiXl5iXlZWTkpKRkJCOjIuEiguIioqJiIeFhYaFhoSEc4OFg4CBgID//fn6/v749/Xy8O7s6uno6ejm6Ojl5eTj4+Xk5OTi4+Hi3t7j4eXl4tvV0c/P0dHR0tPT09LR1NPT09LOzs7Qz9DQz83OzMzN0dbX1tbV09HRzc3Ly8vN0tTV09PS0c7Qzc7Nzs/NzczMy8qFyYDIyMrJx8bFxsXExcfFxMXFxsXExcXFw8LCwcDAwcHCwcLCwcDAv8C/v76/v76+vr28vL29vL29vLu6u7u5u7u8tre4urq4tri4t7m6u7q3uLm5ur28vLq5ubq7vL27urq5uLi6u7y8vLm3ubq8u7q5ubq7u727uLm7vb69vr/AwA7BwcHCxMPExcfHxcXDw4TEIcbGxsfGyMbGx8jHx8jKys3U19bV1dXS09bX1NPS0dDQ0YXQE8/Ozc7Nz9HS09PR0dPS1NTT1NSF1jzX1tja29ra29vc3d3b3N/g4uPj5OXk5OLj4+Tl5ubm5efq6+vr6eno6Onq7Ovp6eXn6O7x+v39/4KChIOHgC2Cg4KCgYKHhoOB/4D7/fr18vDw7+/u7O/u8O/v7Ovp6Ofm5uXl4+bn5OLi3d2F3F7b3Nzb3N3c2djX1dPT0tHOz9DQ0dLU1dXX1dTRz8/Ozs3Nz9DOzczKysjGyMnGx8rIx8bFx8jGxcbFw8XDwr/BwcXFxcPCxMPCwL+9wMPBvr++vby6urq7u7i3t7m5hboFuLu6ubiFtwa5ubm6ubqGuRe6uLi4ubm6uru8vb28urq7u7q7u7q7voW9Lb6+vb6/wcTHyMfGx8fHys3Ozs3Nzc/Pzs3R09LRz83O0NLV2N3e3Nza19XT0IbSLdXV1NPV0c7O0NLS1tbU1NPV09HT0c/Q0NDNzMrNy8nKys7My83O09XU09PT0IXLgM3Pzs7Q09XRz9LV2trb2tnc4ePk6u7s7u/v7e3r6Onr7/Dx9PX2/ICAgYOIkZiZl5STkZCOjI+Sk5mfnpyZmZeWmZqYl5mcnZ6cn6OkpaitsrCwrrCwrKqtrK2srK6srLCwr7KzsLCvsbCwr6qssbGztry3tLKxsrGurK2tra+wErGwr66urq2sra6usbS1tLW2t/+Bv4H/gP+A+4CVgQKAgf+A/4DJgOuBAgIEAICIiY2QjYuKiIWHlZyfpaqsuruyq6qssra0s6aZnZ6jp6ikop+epKarrK+ztbOtq6WZko2IiYmGgIJ9eXl4eHh5eXp8foCCgX1/gYF9eXl2dnRxcG9vb3BxcXN2d3p+goiFfX5+d253fn+BgYB/eHh6dXd+h4Z5cG1rcXyMjJSUjYB5ZGNgW1tpeoeTmZmcnJmXl5WUmJ+lqaytrK2uqZ+enJqUjIuJgHVtbnN2eHd6e3p5eHRzc3R4eXZ2cuPg2dXW1dDJx8O8taeho6KclI6KhoWFiY+QjIiBgYCBg4SCgH9/fYSQmpeJfnJsamtpaGptcXRycXJ3d3h6eHh2dG9scEF0dnN2dnJvcnp/go2QkZSKjIhxa251foGCgX98eXd6e32BhYaFgoKCf3l5dHJwb3BzdHZ5enp4d3d2dXFvbnBvcYV0DXJxb21rbG1ra2pra22Faxhqa2tramloZmVlZGJgYF5cXF1hYmNiYV6EW4BZWFlZWVpZWllXWltaW15fX19dWldYWFlcXV1eXFpbXmJgW1dXWFlcXltaVlVWWVdXWWBjY2BbWVlcXV1dXmFhYF9hYmFgW1dcX19eXV9kYV9fYGFjZ2ptb3BxcnBvbm5tbGttbm1ubmxsa2poZWJhYmNhYGBgYWBiYmNmZmRhYYBjZ2doam1ucHJtaWdlZmVmaGttZ2JgX19gYWNocnRwbmxramZmZ2dla3BydHN3eHd0cHOBhYF8foKDeXFzdXh7fX9/fYFEQ4WNjoiCg4RBQn17eHl+QEFBQEFBf359eHZ0dHNzcXN0c3dzc3h4dnJwcG9vdHZ2cm9pY2FeXl1dYwtmZWZrcXV3dG1lWIRSIVFQUFFSVVZeZ2tkX1lZWV1bXWNfWllYVlJPTlBSUlFTVYVTKVRVWllXVVdRT05PVV1ZWFZVV1pXV1VUVldXVVVTUVBQUlNRT01NT1BRhFMUUlRVVVZVVFRWVlZYWVtcXV1bXF2FW2xdXl9gYWJhYWJjY2RkZWNkY2JiY2VmZ2hqaWttbnB0e4GHiYuJiY6Oj5GUlpiXlpWVlZaYmJiWl5OQj5CVmpyYlJCOiomKi4yMjI+RkpSXlZiYk46MjIqKi4mKhoSGiIKFioqGg39+e3x/e3eEb4Bwbm5ub2tpa2xmYmFiY2dmZWVlaGloaGttbWxscHR0dHV1d3l4fn9+fnl5eHd5enp6fH18fYGEiI5NVlxeXVhVVFJQUFFWYGVoY1pXWFxeXWBmaGxxcnV3d3V3en6ChISEh4SJiouNj42Lh4aKjIyNjoyPj5CRkJOQh4aAfHt9fg+Bg4SDg4eFgX9/gIGBgoKEgw6EhIODhIGCgoSFh4iJiTuWlpqcmZiYlpOVo6qqsLS3wcG7trS1u769urCkp6iqr7GwrqyrsLG1tru+wLy3tLCnoZuXmJiUkJCOjIWLgIyLjo+Rk5GOkJGRj4uIhYaFgoKBf4GBg4KEhYaHi4yPjYmJiIWAhYiHiYmIhoGCgn6AhIqIf3p3dnmAiouQj4l+cXBuamlyfYaMkJGUk5CPj42NkJOYm5ucnZ2cl5KSkZCLhoSDfnVxc3Z4eHd6enl4dnV0dHR2d3V0cuLh3dnZc9nW0tHNx8O5tba1sKumop+fn6KlpqShmpycnZ6gn5yamZidpaqnnpiPioiIhoaHiYyOjIuNkJCPkZCQj46Kh4qNj4yNjYqIio+Vl5yfn6KbnZmMh4mPlJeXl5STkI+SkpOUl5iXlpaVlJCQjIiJiYmLi4yEjg2NjYyKh4eGh4eIiYqJhIgEhoSDg4SEN4ODhIODgoODg4KCgoGBgH9+fn59e3t5eHh5e3t8fXx6d3d3dnV0dHR1dXV2dnV3eHZ3eXp6eniEdjl3eHl5eXd3eHp9enZ0c3R2eXt3dnRycnV1dnd7fn99e3h3eXt6e3x+fn17fX5/fnt6fX1+fXx+gH+EfoCAg4aIioqLjY2Mi4uLioqLiomJiYiIh4iHhYKBgYKBgYCAgYGDg4OFhYSDg4OGh4iKi42Oj4uJiIiHh4iIioqHhoWEhIWGh4uSk5GRj42Pi4uKi4mNkZOUlJmbmpiTlJ6fm5ianJ6Xk5SVl5mdn6GgpFRUp6urqKOlp1RToqGcnRKiU1RVU1NToqChnZmXlZeWlZaEl4CWmJiXlZKRj5CTk5ORj4yGhYODgoKHiYiJi4+RkpCLhHx5eXl3d3Z2d3d5en+FiIOAfH19fnx+g4F9fHx4d3V0dnd2dXV3dnV1dHNzd3t6d3V4dHBvcXd7eHd1dHZ5dnZ1dHZ3dnR0dHFwcHJzcG9vb3BwcXR0dHNxc3R1dXR0c4B1dHV2dnd3eXp3d3h3eHh4eXl6e3x9fn1+f39/fn5/f35+fX1/gYGCgoOEhIWGh4uRlZiam5uZnZ2dn6GipKSlpaWkpKalpqenoaCgoaarrqumo6Oem5ydnp6eoKKjpaalpaaloZ+fnp+gnqCbmp6dmJqfoZ6bmJiVlpmUkIqJiYCKi4mKioyKiYqLhYKBgYOHh4WEhIaHiIiLj46MjJCTlJaYl5mbmp2gn52ampqZmpubm56fn6Cho6ivXmdsbGtmZGRiYGBgZnF0d3FpZ2lrbWxvdXd6f4CDhYWDhomLj5GQkZSSmZmam52dmpaUmJqbm5qZnJ6fnZ2fnZWUjoqKix6NkJOSkJCUk4+NjY+PkJGSk5KRkZKRkJGSkJGRkpOElj20tbm6uLi4trS3v8HCyMnM0tHMy8zMzs7PzcnAv8DGycnKysjIzMzQ0NHT1NHNy8jEwsG+vLu7ubi4uLa3hbiAubm6urq5u7q7uba1s7SysbGvrq6vsK+urq+ura2trKusrayoqqmnqKinpqSjo6KioqGhnp6dm5ycnZ2fnp6blpeWlJSVlpiZmZeZmZiYl5WWl5mZmZeXlpWVk5KUk5GOjYyNi4qJiYqKiYeIh4aGh4aFhYWEg4KBgP/9/P78/Poz+vr49vX08fLx7u3q6efl5unp5+Xj4ePj4+Tj4d/f4N3g4+jm4NrX1NPU0tDS0tPW1dTUhNYd1NTT0tDP0tLSz9HQz83Oz9HS1NTT1dPU087KzdCE1AvQ0M3Mzc7Ozs/PzoTMD8rKycrKysnHyMrLysnIxoXFHMTGx8fHxcXFw8XFw8HAwcDBwcDBwMG/vr+/v72EvoK9h7wkurm4uLq6uru7uri4ubm4uLe2trm4ubq5u7q4uby9vr69u7q6hbuFuj29vby6urq7vb68u7m6uru6u7u9vb/Av7+/vr+/wcPExcTDxMPExMXExMPExcTHyMjGxcfIysrMzM7P0NLShNUG1NPQzs7OhM8Q0M/Oz8/Pzs7Pzs/PztDQ0IXShNMe1tjY2dvZ2NfX2tvc3Nzd3t7d3t7f3+Lk5OTm5ebnhOYD6ObphOuA7u/u7Onq6urt6uvr6ero6ers7vT2+fn8gID9/vv4+fz/gID+/Pr6/4KEhIKBgP78/PXy8fHx8O7w8PDy8u3u7Ozq6ejn5+bm6OTi4eHf3t/d3t7d3d7g3t3c29vY1dTS0tPU0dDQ0NPT1dfa1tXS0NDR0M/S0M/PzszLyMrJy8lTx8fLyMfGxsnJxsrJxsTGwsLBwcLHw8XDwsTEwcHAv8HAvr2+v7y8vL29vLu6urq7u7y7u7u6urq7vLm5ubq5ubq5urq7urm6urm6u7q6vLy7u7uEvQ28vLy7u7m6u7u7vLy8hL4awL+9vsDBw8bIyMjHycnLzM3Oz87P0NHQ0NKE04DS0NDT2Nzg3NjX2NPR0dLU1dXV19bX19bY19TT09TT1NXU1NPU1tXT1NfX1tTT1NLT19TQzMvMzM3N0NDR0M/Q0s7Mzc3Q0tLQ0NHS0dLR1Nnb2djc4eLg4uLj5eTn6u3t6eno5+fn6Ojr7O7u8PL3/oSLjpCOiomJiIaGhouTlk+WlI+NjpGSkZOYmJqcnqCjo6Gjpqmsq6ytsK6ysbKztLWzsK+ytLSytbO2t7e2t7m3sbCrqqqqq62vsK+vsrGvra2ur66vr7Cwr66wsLCxhLAGsbKztLa2/4G8gf+A/4D/gAOAgYGHgIKBhYCGgf+A/4DLgOiBAgIEAD6Ni4mKiYiIiIWEhoeLlqevsra4tbK1tbK1uLOzp5uYmZ6gop6eqKysrru+vLm/wbmto5qNi4uHgYB+fX16eoR5gHp7fX6AgICDhIKDgH14dnNzcnFydHh7kpySlYqLmp2LlJGWiYmHgYKEhoSAg4J8foGBfXVsaW2Fm5yHinR0dWFeXF1fcoyYoaedko6UkI2NjpSXnqerrK2so5+gn5uenZORj4R2bm5vcXF1d3l7fXx1cm9samjO029w29tv1cvEgMXHwrqyp6ClrKedl5GOkpOUmaqypY2IfX+EhYODf3l5f4yViHl3dnFubm1ramxucHBxc3d2eX9+d3V0c3V4eXh5enp8cm5ydHmCiIWDiI+Rinx3eXx/fHl5e36BhIeGh4iGg4GAfn59fXh1c3N1dXh9foF+e3t6eXd2dnd3d3Z4Fnp5eHh1dHFvbmxsa2tqamlqa2xtbW2EbBdqaWhnZ2VkYV9eXV5fYmJgXl5eXV1cXYRcgFtYV1VUVVVXWVhaXV1aWVpbXFtcXVxdXWBgXmBeX2FeWltfXl1gXV1eXVxdX2BiY2FfXVhXWFpZWV5gYGRkX11fX11cXF9gX19eYGRlYl1dYWNpbG9vcG9ubG1ubGpnaWtsbWtqaWpqaWhnY2BhYmFhYWNjY2JiZGdmZWJiZmpsgG5tb2xtb21sbGVkYmRlZ2loZGFhYmZoam10fHxzcXFydXNvdXFwdH99e3x+gIJ/e3+DgHuBgYyMjpCVmJWDen6BfoGChI2MiIF/fnx8fXt3dnd4Pj9CQ0JChIN+e3d2dnR0dnd4eHZzdnl5dHBubnBzdHRzc3BtaWVhYGFoampoXGpwdnd2cWVcVFRUUlBQUFFSU1RWWl5jYV5YV11bW19bV1ZWVFJRUVJTVFlZV1ZUVFZXWV5dV1VVVFJRUVNXVVZZWlVTT1FTVVpZYWNcV1FUWFlVU09OT09QUVJShFMCVFaGVwRYWVtchF0KW1tcXVxcXV1dX4RgPF9gX2JjY2FgY2JhYWJkZWVoam5wcXBxdHl+g4WJjI6QkZCSlJaXmZqZmpqZm5qam52dl5OVmZudmpqUjYSLaZCRk5SSkpWWlZCPlJOMiYmIhoaIjYqJh4eHiIqIhYCBhIODgoJ/fHNtamlqa2tqZmVlZ2VkZWVlZGVmZGRmZ2lvbmpqbnFwb25vcHJycnV5e3p4ent7fIOEhImFgoOBgIKHipBKUFFOS4RKSkxOVFVUUlBSU1VXXGFja3FzeHx9fHd0cnN0dnd4e4B/f4CBg4iHhoaHiouKh4KBhYqUl5yfk5OMg3p5eHh7fn9+fX5/f3+AgIKFhYYBiISJCoiIh4SDhIeMkJECmpiGlkqUk5SYmaOzuby+v726vb+9vb+6urKoo6Wqra6pqbW3trnDxcTCxsrEubClnJqZlpSSj4+OjY2Mi4uMjI2OjZGQj5KUk5OQjImHhYSDQYWHiZecl5mRkpyekZeTmJGQjouLjIyLiImIhIWFhYJ8d3V4h5WXiIp9fHxwbmtrbHiKkpmak42KjoyIiIqNj5WbhJyAlpOTk5CTkoqJiIF3c3NzdnV3eHl7e3t2dHFvbm7Z2nFx395v2dTP0dDNyMK6tLe7t7Gtqaepqquwu8C1o6Camp6gnp2ZlZSZoqeelJKRjIyMiomIiYqMjY2OkZCTlpWRkY6MjY+Pj5CSkZOKiIyNj5aamJWYnqCck4+RlJWTkJI4lJWWmJqZmZqYlpWUkpKRko+Mi4uNjY+SkpORkI+Pjo2Li4uMjY2Oj46MjIuKiIeGhYSDg4KCgoOHhBSDg4GAgIB/fn19e3p5eXp8fHp5eYV4hHcNeHZ1dHNzc3R1c3Z4eIR2Unh3eHp5eXh5enh6eXp7eHZ4eHl6fHh5enh2d3l7fX18e3l3d3d5eXl7e3yAgHx7fn98e31+fn1+fn6AgoF+fX+ChYiMjYyNjIqJiYiHhYeJiYiEhoSFgISCgYKBgIGCg4OBgYKDhIWDg4WIiouMjoyNj4uKioiIh4iIiImKiIeHh4yMjI+Ul5iVlJOVl5ORk5GRlZ2cm5uen5+cmp6fnZmcnaWlpqenqKedmJ+joKCjpKmnpKGgoaCeoaGdnJ2dT1FUVVRUp6OgnpqXmJeWl5iYmZmXmJuafJaSkI+SlJWUk5KRjo2IhYWGiouLiYuQk5OTkIZ/enp6eHh3d3h5eXl6fYCEgn98fH99fIB9enp5eHd1dXZ3d3p6eHd2dXd4eHx9eXd3dXRzcnV6d3d6eXZ1cnN0dnl2e355d3N0dnh1c3BwcXFxcnJzdHRzdHR2dnh3dnaEdwd4eXl6e3p5hnqAe3t8fn59fH19fn99fH1/f319fX+BgYKEhYeIh4mLj5KWl5qdnp+goKChpKWmqKioqampp6ipq62opKWoq62srKWenZycnqCkpaWkpaampaGhpaWgnp+em5ydoZ6fnp+en6CfnpmZn5ybnJ6al5GJhoaIiYmJh4WEhoSDhYaGhYV9hoWFhYaKkJCOjZCTkpGRkpOTlZWXm52cmJmbnJ6kpKappqSmpKOkp6yyWmBhXl1cXFxbXF9jZGViYGJjZWdrb3J6gIKEiYqLh4OCg4SFhoeKkI+Pj46TmJaVlJWYmpmWkY+TmKCkqKugoZqSioiHhoqMjYyLjI6Oj4+PkJOFlA+VlpaWlZWVlJKTk5aanZ2Aubi3tra1tba2tbW2ub/Jzc7P0M7N0NDP0dPQz8bCw8TIycnHyM/R0dHW2NjV1tnU0MvGwb+/vbu7urq5t7i4uLe4uLi5uby8ur29vb68ubW0s7OysLGys7O0tbKzsLGysrCysK+srqyqqamqqaelpaSkpKOgn56dnZ+ioJ2fnJwbnZmXlpaVl5ucnJyZmZiYl5eWlpeYmZmYmJeWhJQIk5KRj46Ni4qEiYSIgIeIh4aGhIKCgP/+gID9/oD9+/X4+vn29vLw8/n18e3s6+3t7O3w8ern5uTi5OXj4d/c3eHm6ePd29vY1tbV09PR09XW19nZ1dbZ19XT09LU1dLTz9HR0s7KzMzP0dPS0dTY1tPS0dHS1NLQzs7Q0dLQz87Q0M7PzcvLysvLy8rLD8rIyczKysrJx8bGxcXGxoTHHcbFw8LDw8HBwsHAwcLBwcLBv7/AwcHAwb++vr28hL0CvLuEuUO7u7q5u7q5ubm7u7q4uLm4ubi4uLm6uri5u7y8u7u7vLy7vr2+vb28vL28vb6/vry9vb2+vLy7u7q6u7y9vb/BwMHBhb8BwYXEF8bFw8fDwcHBwsHCxcbJysnKzM3O0tXWhNSA0c/Pz9HQzs/Nzc3Oz87NztDQz8/O0NDR0tDS09PR09PU1NTW2NjV19bX2trb3Nna29zc3t7f3uDg4uDh5eTl6Ovs6ejn6uro5+jo7PDv7+/y8/Lv7u3t7+3s6+7t6+zt7e7u7vT5+fr8+/z69/n5+vn5/P359/j8gIGDg4KB/vsi+fb08/Ly8fHy8vTz7+7t7u3s6unq6Onp5uXl5OPh4OLj4oXhWuDd3d3a2NbX1tbW1NTR0NHT09PV1tbU0s/R0dHT0s7PzsvKyMrKysvMzcrKycrIycjJycfGx8TEw8PFxsHCycjFwsC/vr/BvsTFwsC+v7+/vr6+vL28u729vIW7Bry8vLu7uoW8Eru6u7y7uru7urq7u7u9vby9v4S9gLy8u729vL28u72+v7+/wcPDwcHCxMPEx8rKysvMy83Oz9DQ0tPT1NTW1NPV19nX1dfa3N7d29fT0dDQ0dTW2NnY19jX1tPU2NfV09PT1NLV2djY1dXV1tnZ2NTW2tnZ297b19PQzszP0NDQz8/Pzs3Nz9HRztDRz8/S09ba3NvZI9ze3t3c3N7g4N/j5ujo5+no6ert8PD08vHy8fHz9vn/goaGh4NdhYqMi4iHiIqNj5GTlZugoaOlp6ekoqKjo6Okpaesq6usrbCxr7CwsbS2tLCurbG1ury/wrm6t7Cqqaenqaysq6usra2ur6+wsbKysbGxsrKztLS0tbOxsbO1uLm6/4G5gQeAgIGBgICB/4D/gP+AjoCGgf+A/4DLgOeBAgIEAD2SkpKPj4+Ojo2LjIuJipGep6ywrq+trq2trK2rrayln6GhoqGcm5yfpay6vby9wcTDtKKYjImIhYSBfnx7hHqAeHl6fHx8fX5/hYuPj4qBgXp2dHJzdX6GhIeLj4yOlJaCipidkYyOiIuNlpqVkIyIhoiHgHZtamtygZCMg4BxaHF1ZWd4foudqaael4qJjI2TmZeXn6ausbCnoJ+hop+koZaYkX91b21sbW5yen19fHdvamdnZ2lrbG1ra2vTZ8dWy2tpxbWspqCboaOknJGQlJeip6KorJqPhH2FiImJh4N/hIeFf3t6eHNvcHBwb3BubW5wcXJ6enl2cXBzdHZ7gISOk4B4eH6PkIB3dnV3f4eIgnl3eHqEew5+g4aJjIyKhYF/fHt6eoV5KXd4eHp+gYODgoB+fXt6eXp7fX5+fnx6eHd3dHJwbW1sa2tqaWlqamtrhWwPa2xramloY2FgX2BfYGBfhF2AXFxcW1paW1tYVlVWVlZXWVxcXV9gX1tcXl5dW15eXVxgYF9iYmFgX2BlaWZlaWJeX2NmZWdqbWxrYF1ZXWBhYF5laWZnaWplZGJgYmhoZGJkZmdscW9naWxydnFubWtraWptbWxpZmdra2tpZ2ZnZ2dmZmRjZGFiYmNmZWVkY2WAZ2pqaGdlZ2xsbWxsa21ta2hmZmRkZGZnaGdmaGlpbm9yeX5/fnt4cnJ2fYF/fn97enyAg4SHg3p3eYOHkJCQk5maoqiln459e3p7gIWLioeBfnx8fXp5d3Z4ent+gUFDREVFhoOBgIB/fHp5eHd2dnR4fHVxb29xdnVwc3VzbmsUaGRhYWNjZWZnanB2d3NvZFxZVlSEUmVUVFRVWFthZ2ZaVltfW1haVlRUUFVaV1RUXV5dW1taXWBhYWJcVlRSUlNUVl1aV1hXVVRUVVVWW2BcY2FcWVhaXVhTUlVTUU5OUVJTU1RVVVZXWFlZWVhZXF1dXl9eXVxcXF1eX4RegF1fYV9fYGBhYmNiYWFiYGBhY2RlaGhna25wc3d7f4SFiY2PkJGRj5GVmZqZlpiZmpqdn5+en5+dmpmcmpmamJWQjo+PkZSWlpeTkpKQjIiKjI+Tk42Fg4SFh4SCg4SHiYuEgoGAgYB+enp3c21raGZlZWZmZWVlZmdoZ2ZmZmVkI2RnamppaWhpaWpraWhpamxub29vcnN1eYOEj01LjouKiIeGhIdpiotHSEhHRoxGR0hISUtNTU1OTlBTVlpeYmVoam5ubW5ubm1rbG5ubXJzdXZ6fHx7fH5/fnt7gYSAgoqYp6qhl4yCgH19fH19fn+BgYKDgoCBgoGBg4SGhoeIiouMjIqLjI2Mi4qMjo6QgJ+fnp2cnJubm5qYl5eXn6qwtLm5uLe5ube3uLS3trKsrq6vrqeoq62vtsLFxsfMz83AsaadmZiXlZKQkI+NjY2MiouMjYyMjpCRl5yenZmSkoyIhoSEh42VkY+TlpOUmZqMkpuelZGTkJGSl5qVkY+Ni4uKhX95d3d7hJCNiIV6bHN7fHJzfoKKlZyblZGIh4mIjJCPjpabnZ+emJOTlJSTl5SNjop+d3NzcnJzdnt9fXp3cm9sbGxucHBwbm9v3GvS021sz8W+uLSxtri4sqqpq621ura5vLClnZieoKOjn52anKCemJWVlI6NjYSMXYuKi4yNjpSUkZCOjI2NjpGUl5+hlI+Pkp6fk42Mi46VmpyYkY+RkpKSkZGUl5ibnpyal5SUkpCPkJCQj46OjY+PkJOVlpaUkpGQj4+Ojo+QkJCRkI+OjIyJiIeFhYSEg4OIhAeDhIOCgX99hHwRe3t7enl5eHd3d3h4eHZ2dnWFc2R0dXh4eXt7enl4eHl4dnl6eXh6enl9fX18enx/gX9/gX56en5/fH6DhYSDfXt4ent8fHt/g4KBhIaBgIB9foKCgH+BgoKGiYiEhYeMkI2MjYuJiIiJiomHhYeKiYeFhIOFhYSDhISAg4OChIaEhIOEhIWHiIeHhYaLi4yMjIuOjIqKiYmIiYqJiYqKioyMjI6PkpednZybmJOVl5udnJydnJyeoKKkpaOemZmho6eop6isrbG3tbKmnp2cnKCjp6ajoKCfnp+enp2dn5+eoaVTVFVWVailoqCgnpybmpuamZqZm52Yk5BTkJKWlpKSlJOQjYuIhoaIh4eGi46QkpOPjIWAf3x7enl6eXp7e3t9foKFhH57fYF9fH57eXl3eXx6d3l+fnx7e3t8f4B+gHt4dnR1dXd6fnt4eHeGdSV4fXp+fXp4eHp8eHNxc3NycG9xcXNzdHV2d3d4eXl5d3d5eXl6h3uAfHx7fHx8e31/fnx+fX1/f358fX99fX5/f4CDhIKFh4iKjZGSlpiZm52foKChoqSnqaimqKmpqaqrrKyur66qqauurKyqpqKfn6CipaanqKalpaGfnJ6go6iooZyZmpyenJqcnZ6foJ2bm5ucm5uYlZKPjYmHhYWGhoWGh4aFh4hxiIiHhoWFh4iLjY2KiouMjY6Li42Oj4+QkJGTlpidpqWuXVywrayqqKepqaqqrq5YWVlYV7BYWFlbW1xdXV1eX2FjZWltcXR2eX18fH1+fn18fH5+foGEhoWIiouLjI+PjYuMkpOQkZelsrWuo5qRj42EjCGOjpCPkJGRj5CQj4+RkpOUlpeYmZiYl5iZmpmZmJqamp2Du4S6ELm5uLi4ubm8xMjJzs3Nzc6EzYDLzMzKx8rJycrHx8nKzdHX2NnY2t3d1c7Kw8DAvby8vLu6urq5uLe4uLq5uru7vL/BxcTBu7y5trSxs7O5u7a0s7KytLKxsbKysa+wrqysq62tqqiopqWlpKKgn5+en6CioJ+fnJqbmpmZmZqbnJ2dnJuXmJiXmJiXmJqbmpmZlxKWlZSUlJWUkJKOi4uKioqJiYqEiYCHhoWEhIOBg4KCgICA/4D//oCA+vX29PLw9fTz8O3t7uzw8Ovu7+vo5OTl5eXi4+Dg4uXk4d7e3dnX2NjZ19XV1NTW19XX19fW1tTU0tLT09LX2NLPz8/S09LNzc7P0tXV1dPS0dLR0NHP0NPU1dPRz9DOzs/Oy8vKyMjKysnJyQfKy8rMzMrIhscQycjHx8bFxcPDwsHExMHCwojBCMLBwcHAwL++hL0XvLy8uru6urq5ubq7u7q5urq7urq5urmEuAi7u7y6u7y9voS8Db28vb6+vb+/vsDBwL+Evge8vr/BvLu8hL11v8DBvr/AwcHAwL/BwsXGyMbFxsTCwsPCwsPExcPEyMjIy87P0NLV19nV09HPzs/R0NDRz9DQzs/P0M7NztDR0NDR0NHR0tPS0tPS0tPV1tbV19fZ2drZ2dnd3dzd3dzd39/g4OLi4uTk5eXm6Ors7u3q6+vshO0N7u/y9fb29/b18vHz74XugOzt8fLx8/Lz8/T29/v7+fj4+fn7/f37+Pr7/v7+gIGBgoD+/Pj29fb08vLz9PPz8PDw7+3r6enq6uXn6enk5OXj4t/g4ePk5OHg4ODf3d3c3dnY19bX1dTT1NXU1NbY2NPR1NXRz9LQzs3LzM7OzczNz87My8rLy8vMzcrJycfGNMbFxMvIxcXFxMPDw8DAwcTCx8LCv8DBxMK+vsHAv7y8vb29vL6+vr+/vr69vby7vb6+vr2GvBS7vLu8vLu7vb69vb69vb6/vr29vYS7Q7y8vsC+wMLCwsTHxcbHycvMysvLy8zO0NLS0NTW1tXY2NnZ2tva2NjX2tnZ19bV0dHS1dfY2NjV1tfX1NHT1djd3NeE0x/V1tbV1tfZ3NrZ2drc3NrY2dbV0dHPzszMzs/Q0dLRhNM50tLS0dHV2drZ19bX19nZ2NjZ2tze393d3uLk6vPx+YKB/Pr59/f29vj39/z+gIGBgID/gIGCgoOFhYYyiYuMj5SWlpmanZ6foJ+dn52dn5+eoaSmpqipqamqq6ysqqqvr6yttLzGycO7tbCvrayErQmur66usLCur6+EsBOysrKzs7S2trS1tba2tbW3t7e5/4G/gQaAgYCAgYH/gP+A/4CMgIWB/4D/gLuAgoGMgIWBAYDhgQICBABBlJSSkpSWlpWVlZSRkI+QkpSXmJufoaGipaevtrqxrKupq66uqqGcm5udpaqsrLHCxsOzo5iRjYqHgn9/fnx7fHuGeoB7fH19f4WGhoeFf358eXh6eoaXj4uFgIGBhIGEfX+MmJeVlpKSlZycmpGQjIeBenFta2tvdnh0Z3KWraeIlZaDh4+RkY6TkYmMk5yqrKmnra6ysKKgo6yxqKihmZOJf317cnBwcXmAgX15dW9qZ2hmY2Nma3RxaWbEw8bGwLu2tGevppydn6SinJaXn7OypZiSkY2LiY2XlpOSlpKHhoSDh4J/fXp1dXV2d3V0dXR1dXuBhYd7cnWNmp2TlpibmpSYoKmmknxzc3d/hIF8e3t9enl6fYCDhYqKiYiHhYJ/fHl1dnd3eHl5hHozfH+Bg4KBgICAf319fn9+f399fHp6d3VzcXFvbm5tbG1sa2ppaWpsbWxtbm5tbGpoZGJhhWKAYF9dX11bXV1bWVhZWVhXWVtcW1xdXmFgX19dXl9fXl5iYmBfXlxcX2JgX11dX2BjZGVkYF5fZW50cGtob3JxaWhoZGBfZGtpZ2dmaGpramxuc3JtZ2prb3h8eXd7e3p3cWxqaWlqa2xraWVkZGdoZmVkY2NlZmdpaWdmZmZlZWcMZmZkY2RlaWlnZWVnhGiAZ2lqamdnaGhoZmdoaWpqcHJva3SAhIeIg4B6dnFzdn2FhoiQjHx7fH58d3d2eHd/hIaNj5CSlpifn6Khk4iIgYOKiYWCfX16dnRzcnV0dnl7fns+Pz9AQUGDgoKDgoGCg4J/fH19f4F7dnR1d3dybnFzdXJua2dmZGFdX2NkZ2uAdHh3cmtpY19YVlVVVVZWVldcYWNvcWhgXmBeWllYWFlYWV9ZWF5iX11fYmVlY2VmYFpWU1JTVFZYV1VVVlVWV2FnZ2VsbmhkYltbXmBcVlVZWVJPUFFSU1VXWFdXV1haWlxcWltdXl5eX15eXV5eX2FiYWBgYWFiYmBhYWJiYWGAYGBiYmFgYmVnaWpqa25yd3t/gYOIiYuOlJWVk5OSk5eZmZqcnZ+foKCipKOkpKGfnZ+enZmYmJWWlpmcnZqZl5KPi4qFhIWIj5ygkoSBf359hIiGhouKgoF/gIGBe3NxcG9samloaGlrbGpsbm9ramlnZmZnZmdmZWVnZ2VlZWYqZmZnZ2lqbG1wc3V3d3d+goh/f4B+f3+AgYSHiYuLRkVFRkhHR0dISElKhEs5TE5RU1RVVVlaXF9iYmJhX2JkZ2praWhqcHJ1eHh4eXl5en1/g4SIh5CUlJiXkYiDgX5/f4CCg4KEhIYbhYWGhIOCg4SEhoeIiYuMjI6OjY6PkJGTlZeVQaCgn6Cho6OioaKhn52dnZ+go6Smqq2trbCyuMDBu7e2tbe5ubWuqqinqbG1uLi8zc/Nv7GnoZ6amJWTk5GOjY6OhYyAiouNjo6PlJeYmZWQj4uJiIqKk6KblpCNjoyOi4+KjJOZmZiZlpWYmpqYk5GOjIiDfHl3eHp/gH50epKgnImRkYWHjI6Ni46MhomNk5ydm5qenp+elpWWmp2amJSQi4V9fHt2dHN0en9/fHl2cm9tbmxpamtudHFua8/P0dPPysWAxMC5tLW2vLmyra+1w8O4rKimo6KgpayrqairqJ+fnZyenJqYlJKQkJGSkpGSkZGRlZmanZSNkZ6rrKSkpqmopKWrsq+gk4mKj5WZl5OQkZKRkZKVlpiZm5ydm5mYl5SRj4yMjo+QkI+Qj4+PkZSVlZWUkpGRkZCQkZGRkpKSkY8Gjo2LiYiIhYYohYSFhYSEg4SEhIaGhoWEgoF/fHt8fXx8fXx8eXl3dXd3d3Z2dHV1doR3Unl5enx7ent5ent6eXp9fXt6enl6fH59fXt6fHx+f39/fXp7gIaKiYWCh4iGgYGBfXt7f4SDgoKBg4SFhIWHi4mGgoSGiI6SjoyQkpKSjYqKiomEiA+HhISGh4eGhIOBgoOEhYaFhWaEhYaGhYSEhYaIiYiHhoaHiYiJiImLi4mLi4uKiouLjI6NkZKRj5Scn6GjoZ+bl5WWmZ2ioqCnp52enp+enJmbnZufoKGnp6ipq6yxsLKzq6SloqKnp6ajoKCdmpiYl5uanJ+go6KFUjNTpaOio6GgoKGgnp2enqCjnpqYl5iVkpCRlJWTj46LiomGhIWIiYuNkpOSjoiJhYN+fHyFe2x9f4KDiouFgX+BgH18fHp8fX2Ae3l+gn9+f4CDgoKDhH97eHZ1dnd5e3p4dnh2d3Z9gYJ/g4aDgX96e31/fHZ0dnh0cXFyc3N1dnd3d3Z3eHl7enl6ent7e3x8e3p8fX1+fXx8fH1+fn99fn2Efnx9fX5/fn1/gYGDg4WHiIqNkZSWlpmbnJ+jo6Oio6Okp6mpqausrq2trbGztLa1srCur6+uq6mmpaanqaytq6uppqKenpqam52jsLOnmpmalpSdoJ6doaKdm5ucnZ2ZkY+OjouKiIiJiouLiYyOjYqKiomHhoiJioeFhYaHhIhLiYqLi4yNj5GTlpiZmZqgpKmgoKGgoKGipKepq62uV1hYWVlYWFhZWlpcXF1dXV5fYmNjZWZoa21wc3JycG9ydHZ6fHp5e4CDhoeHhYgYjY+SlJeWnqGipKOdlZGPjY2Oj5KSkJSVhZMLlJOTkpKSk5SVlpeEmQqam5ydnZ6foKGghLwHvb6/vb6/voS8gL6/v8HDxsnJx8nLztPUz83My87R0c/LyMfIys7Q1NTU3eHf1c7JxcPBwL6+v728u7u7urq6ubm6uru8u72/v8DAvby7t7a0tra4u7m4tbOzs7GxsrKzsbGvr6+tra6urauqqaemo6OhoKCgn6Cenp2doaKinqCfnZ2dnJuZmpiYGpmZmpubmpydm5uamJeXmJeTk5KRkI6MjIyLhIoFi4qJiIeEhA6DgoCBgIGBgYD////9+IT3UfXy8/T39fHu8PT29fPu7ezq6efo6ejn5+jo4+Pk5OPh3t3b3Nra2dnW1dbZ2tna29zb19TX2drb2Nja29nX19rb2tfQztDO0tTT1NPT09LS0ITSFdbX1NLR0c/Nzc7MzMzKysvJycrKyoTLFMrKx8fIx8fGyMjIx8XEw8PEwsHChcMpwsLBwMHBwsHCwcHAwsHBv7+/wL+9vb28u7q7u728vLy6uru8vLy7u7uEuhK7ury8vby8vby9vr28vL67v7+EwIDBwcC/vr++vr/AwMC+vsHDxsXCwMPEwsDCxMTAwMDCwcPDxMXGxsTExcbDw8bFxsfKy8nKzc7Q0NLT1NXV0s/Mzc7S09LRz8zNzs7Qz9DQ0NHQ0NHR0NLS1NbX1tbW1NXX19fY2Nja2tvb3Nrc3d7d3d/g3+Dg4OPl4+Xn6ejr7Ebr6+zt6uvt7ezu7u3w9Pf28/X29fP08/Ty8PDx8PDx8fPy8fL29vb3+fv5+fj2+fv7+fj3+vj7/f7++4CAgIGBgP/9+vr4hvYl+fr8+vTw7+vr6unm5unq6Ojo4+Lh4eTl5ubi4OHi4d/e3uDe2YTYSNfX1tXW2Nra29jW1NXU0tDQ0dHQz9PQ0NLRzszNz8zOz8/Py8rLyMfHyMjIxsXExcPEwsfJyMbIycjGw8LCw8TCvr3Bwr68vYS8Hb7Av8C+vb6+v727vL7Av76+vb28vr69vr69vb6+hL0Xv72+vr6/vb2+v728vb7AwsHCxMPExcaFx0zIyczNzcvMzc7R0tLT1djZ2NjZ3d/e3+De3Nra2dnX19bT09bZ3N7c29vY1NbU0dHS1Nnk5tvV09XU09jZ19fc3dnb2tzd3trV09PShNER0NHU1dTX2NnX1tbW1dPT1tSE1XPU1NTW1tfX2dna29ve3+Ll5+fp7fH17/Dz8PHw8vT29/v9/4GAgYGBgICCgoODhIWFhoWGiIqLjIyMkJGTlpeWmJeXmJmbnp+dnJ6ho6aoqKipqKenq62usLKyuLu5vb24tLCvra2vsK+wsLKzsbCxsbCyhLAHsbKys7O0toW3hLgEury8vP+BwIH/gP+A/4CRgIaB/4D/gMeA6IECAgQAgJmZmJaVlZeWlpmanJ2bmJaWmJmdnZ+kqKemq7KxsbCvr7K2vbaur6qioJ2bnKClr7q4u7yxpZyUj4iEgYB/fHt9fHt6eXl6ent7e3x9gYCChoOKm5yNgoKLh6CkoZSRn5WHgX58fYaUmZqak4uKh398hYqJiYR5cG9tamlnZW+BgJGelJWNh4B/gYiHlZqWi5CVmqSoqKytr7CxqKWnq6+spZubk3x6eHd3dnd6gIN+fH17dW9tbGppbnB2eXVsysLFxry6vby6tbCopJycoqCamJmblJOTk5KSkpSgp6uemJmSjIyRkJGMg358fHp5enx+fH15dXd6goF/fXBzjpqZG52flI6OlJqcn5yWjH99fn9/foCDh4iFgYGDhISFOYOEhoeGhYJ+enZ2d3d3eHl5en1+gIGBg4SCgYKBgH9+fn99fX59fHp4eHZycXFwcHBxcHBubGtrbIRtG2xsa2pnZmNiYWRjYWBgYF9fX15dXVxbW1xbW4VdgF5eXV9fXlxeXV5eYGJhYmdlYWBfYmZnZGJfXmNmZGBiZmBeZGVnZ2hlX2Ztb21wbmxnZHF9fHNrZmdrcHJwcHR3d3Nvbm9ydnl6eXt5dG9saWlrbGxsaWViY2RmZmhoZWRnbW1rbGtnaGloZ2RjZGNiYmNjZWRkY2ZrbXN2eHV0gHd0cG5udHRwbmhnZmdsb21veYqRko2HgHtzcnN0fIaMj42KjYV+fX18eHZ8gHp6fIaJhoOFhYeGh46UlpOPj5GOiX94dnNxcXJzc3R2eX5+e3l5eDw9e3h5enyAg4eHhIB5d3h6e3t6eHd4eXRzdXd3c3Jya2VhX19gY2Zxc3JygHR1dHFsZ2JfXV5cWllaX2BjZG10bmVkZWVeWldZX2NhYVxbYGBfYGFiaWtrZ2FbWVdUVFRWVlZYVlZXV1pbX19kZGdmZ2JiX19jXVlaX15XUFBTVFdZWVpZWVdYWVtdX15dW11eX15eXl9fYGFiY2JhYWFjZWZlY2RkYmNjYF5dgGBiY2RkZWdqbG9wcnR3e4KIiYuMjY+TlZaUlJOUlpaYm56fpKamqKuqq6qmpKOjoqGhn5ybnJ6io6SnoZyXkY6OioiJh4mLj6KYioaDgoOJhoWFhoWEg4WGiYN4cG5xcm9pZWRla3F0dHFubGpnZmVlZWRhYGFhYmJkZGZlZmZoKWhpam9yc3d2dHN0dXh4eHl6enp7fH5/gYSHiIiIREZHSUpJSUlKS0tLhE1XT1JSU1NUVlhYWlpaWVtdXl9gYmNlZmdqbHByc3V8g4WFgoKEf3yCg4iNi4aFg4KDhYSHiIiGhIaIjY+UlpSPjIuKiIaHiIiIioqMjI+RkJCQkpKSk5WXQqSlpKKhoaSko6Wmp6mopKOjoqWnqauvsbCxtbu6uru6ury/xMC7u7Wtq6qoqq2xu8XDxsi9tKyln5qWlZSSkZCQjoSNgIyLjI2MjI2QkZOXk5ehopeQj5WTpaeknJihm5CNjYuLkJiampqWkJCPiYeNj46MiIJ8enl4eHZ0eoaQmZKRjImFgoSKh5CUkYmNkJOXmp2enaChoJqYmpudm5aRkIt8enl3eHh4e3+AfXx6eXZycXBubnBxdnh1b9bQ0dHJycrKZcrFwb25tba3ta+tr7Gsq6usq6inqLS4ubGurqajo6amqKKcmJaWlZOVlpeWlZGQkZOZl5eUi46iqqepq6Odn6Onqayoo5yUkpSWlpaXmJubmpiYmZqbmpmamZmampqYlpOQjY2Oho8KkpKTk5SWlpaVlIaTCpKSkpGPjoyMi4mHhxOGhYWFhISFhYaGhoSDg4F/fXx7hn0Ne3x7e3p5eHd4eXh3eIR5gHt8e3t8e3t7eHp6fX9+foKAfHx7gIKCgH19e4GBgHx+gn17f4CCg4OAeoGEhoOFhIOBf4eQj4qFgoKHioqIiIyQjouIiIqMj5CQkJKQj4yIiImHh4eGhYSCgoOEhYaGg4GDh4iIiYqGh4iHhYSChISFhYWGh4aGhoiLjZGSkpKRCZKSjo6RlJSRkISMcY6RkJGapKiqpqOem5aUlpicpKanpqWno5+goKCdnJ+gnJ2fo6WhoaKipKOiqK6vrKqrrKunoZ6cmpiXmpqZmpueoKGgn6CgT1Chnp6doKGho6Ohn5ybm52fn52cmZmYlJSVlZWUlZSPioeFhoaHipWVhJEykI2Jh4SCgIGAgH9+gYOFho2QjIaEhoaBfXt8gYSCgn59f39/gYKCh4iIhoF9e3l3dneEeIB5eXl3enp+foJ/gIGEf4B+fYF8d3d9fXhycXR1d3l5eHl5d3Z3eXt9fHt6e3x9fHx8fX1+f35+fX19fn+AgYGBgoGAf39/fXx9fn+AgIGChIaJiYuMj5GYnJ6dnp6foaOkoqKio6anqausrrKzs7a7vMC9t7SysbCxs7Crqqqsr2mytLezrquloqKem52cnp+jtayfnZqbnKOgn56enp2dnqCjn5SOjY+QjomGhYaLkJSVk4+LioqJhoaFhIOCg4SFhIaGh4eHiImKi42QlJaZmZeWmJmbmpqcnZ2dnqChoqSmqKusrVdYWVqFW0pcXV1eX2BgYWJjY2VmZ2lqa2tqaWtsbm9wc3V2dnh6fIGEhIWMkZOUkZGUj4yRkpeamJaWk5GSlJSVl5aVlJaXmp2ipKKem5mYl4SWApiZhJoKnJyenp6fn5+goyi+v76+vb6/wL/AwcPDw8LBwcHCw8TEyMrKyszP0NDR0NDT1tjW1NPPhMtUys3O1tvY29zW0cvIxsLAv7+/vr2+vLu7urm6ubq7u7u8vLy9wb6+v726t7e2t7u7vLu6uba1tLKxsrKxsrGxr6+urq2rq6upp6eko6KhoJ+enp+hhKIFoJ+dm52FnByam5uampubnZydnJqYl5mYmJaUkpKQjIuNjI2MhIuAiYiJiYiGhYSEgoKDg4KCgf///v38+vz7+vr69/b28/Ty8fHw8e/u7ezq6+zt7O7u6+jp6Obm5+bo5+He3t/d3N3d3Nvb2tbW19za2dfT1Nze29vc19TW2Nvb2tnZ19XV1NXW1dXV1tbV1NXV1tbV1dbU0tHR0M7Pz8vMysvMzM0UzMzLzM3NzMzLzMvKyMfGxcbHxsSExQ3GxcXFxMPDwsLEwsHAhcEhwL+/wMHBv8DAwb++vr69u7y8vLu7vL28u7u8vLu6u7y8hb0Cvr+Evm28vL2/wL6/wcHAwcDAwsLCw8G/wcXEwsPEwMDDw8TExsTDwsPDwMLCxMXDxMTGx8fFx8nIyMbGyMrLycjJysrMzc3Nzs7Oz9HS087Kx8jL0NLS0dHQzs7O0tLOztDRz8/R0dHS1NPV1tnZ2djYhNka2trd3d/f4eLe4ODe4OLj4+Hi4ePk5ubq7OyE7zfu7+vs7/Dv7e3u7e/x9ff4+Pj18/X28vHx8/Ly8fHy8/T19PT4+vn5+/v7/f36+fn6+vr5+Pr7hv1RgIH/+/v7+vr5+fj3+Pb19vn6+vj17u3t6erq6+rq7O3o5OLh5OTk5uzr5eHh4d/e3uDe3Nzc2trZ2NnZ2tnZ29nW19fY19LS09PT0tLR0NLShNAJ0tHT0c3LyszLhMk/yMnIxsXExsXGx8fFycjJxcTDwsXDv8HGxMG9vr+/v76/v8HAvr2+wcLCv729vr+/vr6+v7+/wL/AwMHAv8DAhMF+wL+/v769vL6/vsDAwcHDwsTFxsTGx8nJx8jJycrOz8/Oz8/P0dLU1tfY29vd4OTm5+bh393d3dzc2NjY2trd4OLn5ODd2tbW1dPU09bX2uXf2djX19jd2dra2tvc3eDh5eLc1NHV1tTS0NDP09rd3t3Z19fU09bW09DQ0dLShNNl1tfW19nZ2tzg4+Xo6ebk5ejr6ens7e7u7/Hz9Pb5+vv9/oCAgYOEgoODg4SFhoaHh4eIiouLjI2Oj5GTk5GRkpWVlpiZmZmbnZ6fo6SlpquvsbGur7CtrK+ws7a1s7OwsLCysLKGswa1trq8ureEtYS0ELO1tba3uLm5uLm6urq7vL7/gb+B/4D/gP+AlYCCgf+A/4DJgOeBAgIEAEeYm52dm5eWlZaanZydnZydnJyen52cnqKkpqimpaeoqqqsr7W5uLa2r6mjnpubmp2nrquwubGurKeajomFg4B9fn18e3t7eoZ7gH1+f4GFjJOYl5aMi4eOlZyXo7yxm5CJiIyVnJmLiYN5dnd4fIGEhYF6dnJtbGtqaGZnamlydHNwcnN0cnl9hImIg4eLlp2hn6OusbayrKajo6Wfm52poI+GgXl1eH+FiIKChYOAfnx3dHJ2eHh3c2pkxctozsjGwriysKqnpJ2VVZqfmZGNj5CUnJuep6epp6KalY6KkZihoJaPjIqHg4F+e3p6e39+fH6De32AgX1wdJGoraCbmJKSlJSPioqLi4OAgIGBgYOIjI2Li4mGgH+AgoSEg4WEhICAe3p5eHh5ent8f4CAgYGEhYWGiImKiYeFhIGAf399enh3d3RycXJycW9vcHBwbm5sbGxtbW1ra2hmYmFhYGBfXl1eX2FhYV9eX15dXFtbWltaW1xcW11dXFtcXFpbXFxeYGFhYGJjY2dmZWlqaGloaGlnY2JhZWhlZ2tsbWhhXyRmZ2doa21sa2txe3t3b2tmZW9yb29ub3FwbWxucHJ2d3Z0dHOEcoB1d3Z4dGtlZGtsa2pqaGdrbm9ucG9ucHRzcGpnZWRjYWBhYGFiZWpucXV3eHx+gIB4cXF0dnV0dHBucXNybXF+h4qKiod+end0dHp6e3x5fH+HhoJ7e3x7e36Cf3x8goOGioODhYB/hIuPlZqYkouDfHZvbW9vb3Fyc3V2d3Z2dYB1dHR1eHh4d3h7gYSAfX16e318fXx9goOEh4eFhod/enZ2bmVlY2JlamlnbXJzf4J+dW9sanFwaWZlYmJjY2VlbHt3bWpubGRfXltgaGZjXF1eX2FlZGVoa2xiXlxcWldWVlhVVVxZV1pdYmFgZGlra2hpZV9eYF9cWVxdVlRSUh5WWVpZWVlaV1dZXF1eX19fXl5fXl5fXl5hX2BhY2OEZYBmZWVmZWZlZWRjY2JhYmNjZGdqampscXJ1d3yFiY2PkpGRkpKUk5CQkZOWm5+ipKmrrrCxr7Cuqaepq6ekpaKhoKGjpKasv7GhmZSQjo2IhoeKjI6SlZCKh4aLi4mKiIaBgISGh4l+dXJwcXRybmppbHN2dHBxcG1nY2JgYWBgYUBhYGBhYWJkZWVoam9wc3R0c3JxcHBycnN0dHV2d3l6e3x+gIKDhYeIikVFRUhKSkpLTE1NTU9QUFBRU1RVVVZWhVdLWFpbXV1eX2JjZmdpa3F3eHp/hYiJiYZ/fHt6e4CChIiJhYOEh4qMjYuJiIiLkZqgn52bmZiTjYqJioqKi46QkI6PkZCSk5STk5OVDqSnqKmmpKOjpKeop6mqhagHqaipqaytsISxgLS2t7e6v8PAv7+6trGrqqmpqra8ur3Dvbm5taqgm5eWlJGQkI+OjY2MjIuMi4uMjY+QkpSZnKCfnZaWk5ecoJ6mta6fmJOSlZufm5ORjoeGhoeJi42MiISBfXp6eXh3dXV3d31+fnx8fX17gYKFh4iGiIqPlZaVmqCipaKemZiYgJmUkZKbk4mCgHp3eX2BhIB/gYB9e3p3dXN2dnd3dG1q09hs1tLRzsfEw768uLSvsrSxrKenqauwsLK4ubu5ta6qpqSnrLKxq6SioaGenJqVlJaXlpSTlZqUlJeXlYuNorK2raimoaKlpaGdnZ6emJaUl5aXmZ2foJ+enZuXlpaXF5iXl5iYmJeYlZKQjo+QkJGRkJKTlZWVhJcqmJmamZeWlpSTkpGQj46NjYqJiImJiIiHh4eGhYaGhoWGhoWEhIKBf319hXuEfAV9e3p6eoR5hHhxeXl3ent6eHl6d3h6enx9f399fn59gYF/g4SDgoGBg4J+fX+CgX+ChIaHhH58g4KChIWGhYWEiI6Rj4iGg4KKjImKioyLioiIiYuMjo+PjYyLi4qKiYqLjI2Lh4ODhoaGh4iFhIeIiImLi4mKjY2KiISEhYCDg4SFhoiLkJGUlZaYmpyblpGSlZaWlJSRkJGSk5GTnaKlpaWjn5yZlpicnJ2fnJ2epKOkoKChn56hpKKhoaSipKeioaKfn6SprK2ysa2ppaKfmZeYmJiZmJmbnZ2cm5ydnJubnZ2dnJ6go6Sgnp6cnp+dnJufo6Kio6Gen5+ZmICXmJKLi4qIiY2Nio+SkZmamJGNiomNjoqHhoaFhISGhYqVkoqIi4mFgX9+goeHhH9/gICAg4SFh4iJgX59fX16eXh5eHh9e3l6fYF/gISHh4aDhIJ+fX58eXd6e3Z1c3N1eHp4eHh5eHh5fHx9fn5/f319fHx8e3t+fn9/f35/gICBgIGBgoSDgoGAgYGAf35+f3+BgoSEhYaIioyOkpibnp+ioaSjoaKhoKGipaeqr7Kytrm8vr/AwsG9uLe4tLS0sbCur7K1t73QxrWrpaGfn52bnJ+hoqeqpqGen6SloaGgnZmcnqCho5ySjo2Ok5OOiYmNk5WVk5ORjYmIh4KCgTCBgYKCg4SEhoeHiIqPkpKVl5WWlZSUlJWVl5iZmZmanJ2fn6GjpaenqaqsV1dYWluEXQxeX19gYWJiY2RkZWaHaEtpa2ttbm5xdHR1eHt8goaHiY6TlpaWlIyLi4mKkJGUl5iUkpKVl5mamJiXlpmeqK2qqamnpaKcmJeXmJiZm52dm5uenp6goZ+eoKIOvb/CwsHAwb+/wsLCw8OExBLFxsTFxsfJy8vLysvLzc7Q0dWE10XU0c7MycrLzdLV1djd2dfW083GxMTDwsC/vr28vLy7u7u8u7u8vb28vb+9vb69vbq4uLm5u7u+vb68ubazs7W0tLGxr6+EsAyuq6qpp6Wko6OioaKEoBqhn6Cfn52dnJyam5uamZubm5ycnZycnZyamISXF5SUlJaTj4yMjIqLjI6OjImKiImJiIeGhYUygYGB//+A/fv7+/n4+fn49/jy8/Py8O7u7e3v7O/y8vHw7uzq6Obo6u/s6ebl5eXj4d6E3DLe3t3a3dja29fY1dXb4d/c3NrY2dvd29fX1tfV1dbX19bW1dja2djY2NfW1dXV09LR0ITPgs2EyzHMzM3Ny8zMy8zLysvKyMjIx8bGxcXCw8TFxsXFxMTDwcHCxMLCwcPCwb+/vr6/wL/Ahb8BvoW9Ab+Evoa9gryEvoS/c8DAvr6+v8G+vcDAv7/Awb/Cwb/CxMTFw8DBxMXEw8LDwsLExsjGw8LDw8TFxcbFxsXDxsjIx8fGyMrNzMrKzMzNy8rLzc7Pz87NzMzLysnGxsXDxcjLz87LzdDRz87OzcrJy8/R0dHS0dPW1dfX2dna3NqE2wXc29ze34ThOeTh4ePj4+Tl5eXm5+jn7e/u8O/y8/Hu6+/w7+/u7+/y8/T1+Pf3+Pb4+fv39vX08/Py8/Hy9fT394T5gPr7/f39+/n6+fj6/P35+Pr7/Pz7+/z9/fz7+/r6+fn6+fr5+vn4+vn7/Pby8O/u7u/u7e7w6ufo5+Tk4+bq6+nk5+Xi4OLh4eLf397g3t7d29vb3eDe2tvd29nV1dTV1tTTz9HS0dLU0dDS09PPzszNzs3KycrJycvJx8jJysrILcjKzMzJysnFxMXEwsLDxcPAv7+/wcPCwcDCwL/AwcLCwb+/wMDBwcDAv7/CwYTAAcGEwIC/wMHBwb++wL6+vr2+v72+wcTDwsLExMPFxcfIysrMzM7Oz87NzM7O0tLX2t3e4ePl5+nq6ejk4eLk4d/f2tvb3N7g5er47+Pb2tnY19XU1djZ293g4Nvb2+Dg3uDf3dnb3+Lj5uDb19XY29rX1NPX3eDg3t/d29fT0NDR0M/R0DTQ0dLU1NXW19fc3+Hl5eTl5uTk4+Tl5+jp6+vs7e/x8vX09ff5+vz9gICBg4SEhIWFhoeIhIlEioyNjY6Ojo+QkJCRkZKRk5WWl5qZm52foKWpqaqtsLOzs7Ctq6usrK6vsbW1srCytLW1t7a1trW0uL7EwsC/vr27ubaEtQ+2uLi5uLm6ubq7u7u6u7z/gb+BA4CAgf+A/4D/gP+A/4DdgOeBAgIEAEWXmZueoJ+amZiYm5+enp+cm5qdoJ6en5+foKOkpaSlqKysrK2usbK0s7Kxq6aim5qbmpmZnKClpqusppuQjYaCf39+fXyGeyF6ent7fH+BhIiLjY+Mi4mNkJKcr7/Bn4uNmqCjo5qTioCEfIB9fXt6dXJxcG9ubm9uamtva2tqbW9qbXN2f4OGgHh4d3d6fYORnaewsa+ppKOlopyYqa6hnZaOfnh+goWIipOYkYqGhX98e36BgHlwamhpbXFyd3hxz7+wqKmtpaKio6yroZ2doqympauooqGdl5KLlZ6jp6qjnpuXkIeAfXt8fzp+fXt6iIZ2fISToqypo52hn5KLhIGBhIJ/foGAf39+f4GCgoWGhYaJh4F+fYGDg4SGi4iFhIJ/f4KBhX+EgAaBgYKEhYeFiT6HhoODgH59e3h3dXZ1c3J0c3Fwb25wcHBvb21sa2ppZ2RiYV9fX15fXl9fYGFiYF9dXFxbW1taWVpZWVpcXYRcgF5dXFtbWltdXlxdX19fYmFgZWdlZWtubGpoZ2htb2tpcnFrZGNkZ2ZlYWJnaGdpaGpxcmxraGVmaGtsampsbm1ucHN1d3h4eHp+f4GBfH+GgHt7f3x3dXZ2en94cnN1dHN0d3Z2dXV1cnFqZmVlaGlqZ2RlZ2pubnBwcnNxb25sgG5yd3lxcXN3cnd1c3FwfIWCfnh1d3x5dnt3dXRzdHd+iIeAeXh5g4eDg4mLjIeHipKVj4iIi5SXlpaXloZ9ez1zcnNzcnJxcnJzc3N0dXV0d3l4d3h5e3V0c3JzdXV3d3p9fn+HiouLh4yQlIt+eHl4a2ppZ2hqZWNncXd/iIR/ZnRxdn57dG5pZWNjZGZmaHJ1bW5uaGdiX15fcH5uXl9fYGNkZmlpaWxhXF5hX1taWlpXWFpZW11eXl5fYWFlZmRlY2NkYWBeX1xXWFdWWFpbWlZWWllYV1hcXV5gYWJhX19eXV1dX4RggGNkZmdnaWhnZmVkZWZkY2NkZGRmZ2ttbW1scnV5fH2AhYyNkJCPj5CRj4+PjY2OkZOboqirrrKztba1srCrr66rqaanqKWoqamprbO0sKWXk5GOi4iHiIqKjY+QkI6Lio2Mi4uJg3t7foWLhHt2dHBwe3JvdHd5eHNubW5uamZkF2JhYGFgX15fYWNkZWdsc3h8e3t2dXVxhG4cb3BydHV2eHt7e31/gIGEh4hFRUVGR0hJS0tLTYRPCVBRUlJTVFVWVoZXTFhZW1xdXl9gZGVrbnFzdnp9fH2ChYaHjIeJhH5+g4OFhomEg4OEhoeJiIiIiYySl5iXmJWUkpGPjo2MjY+NjY6QkJCSk5OTlJWWlZYgpKWnqquqpqSlpqipqqqqqKenqaqqqqurrK2vsLGwsrWFuCO6vL6+vLy3s7Grq6uop6mssLK0uLi1qaKempWSkJCPjo6NjYeMII+QkZSWmZqamJeVl5qcoK65u6KXlp6hoqOemZKNi4qKhIkFh4F/f32EfIB7eXl7eHh3eXp3eXx+hYeIg35/fn5/gYWOlZyhoaCbl5eZmJOQmpuVk4+Jf3l9gIGEhouOioSBgX17ent9fHdybm1ub3Fzdndx2c7Cu73AvLi3t76+trO0t765try7trWyraekq7C0uLq1sa+sqKCamJaWmZiWlZOdnJCUm6attC+zramtraKempmYmZeVlZiXlpaUlpmZl5manJyenJiWlJiZmJmbnJqZmZeTlJaWlYSUAZWGli6XmJianJuamZiYlpWSkZCPjo6MjIqJiYuLiomIh4eHhoaHh4aFhIOCgH59e3t7iHwGe3t6eXh3hngEd3d6eoR5gHt8e3p6eXp8fXx8fX19f4B/gYOCgIKFhoWDgYKGiIWDi4uHgX9/g4KBf3+Bg4OFhIaJjIiHhIODhoiJiIiJiYqKiouOkJCPj5CSk5OTkJKXlJCPkZCLjI2NkJSQjYyNjYyNkZCPjo6Pj42Jh4aGiImKiYeHiY2PjpGRkpORkZGPWpKVmJmTk5WYlZmWlJSUnaKhnpqZmp2amJyZmJiXl5uhqamkn56epKmmo6anqaWkpqutqqalqLCyr6+wsamjolGdm5ubmpubmpmampqbm5ycnZycnJ2dn5uZmISZgJqbnJ2en6SlpaWhpainopuWlpePjo+Mi4uJiIqSlZmdmpeRjpKYlpGMioiGhYWHh4iRlI2OjoiHg4GAgo6XjIKBgYGDg4SIiIiKgX5/gX99e3x7eXp8enx9fn19f4B/g4KBgoCAgYB+fHx7eHl4d3h6ent4eHp6eXp7fH5/f4CAF4B9fXx8fH19f39/foB/gIGDg4KCgYKBhIJzgYGBgIKDhoaGh4eKjI+TkpSYnJ+hoKCfoqOhoaCfnqCjpauxt7q9wMLDxMTCwb2+vLu4tre3tri5ubi8xMbCuaqmpKKgnp6goqKko6OkpKOjp6WkpKKdlZiboaigmJSTjo+bk4+UmJyalZCOjo6MiIaFhIaCPIOEhYaJjpianZ2bm5qYk5GSkpOUlJaZmZqbnJ6foKKjpKepq1ZWVldZWltcXV5eXl9fYGFiYmNlZmZnZ4ZoKGlra2xubnBydXZ7f4KEh4uNjI2RlJOUm5eZlY6Pk5WVlpeUkpKUlZWElxeYm5+jpaWloqGgn52cmJmam5qbnJ6enoWgBKKioqNIvb6/wcTEwsLBwcLEw8LDxMPCxcfGxsfHxsfJy8vKy87Qz9DR0tXV1tXU09LPzszMzMvMzM/R1NXY2dPPysnGw8HAv769vLy9iLwDvr+/hb1Tu7q5uru9vsHCv7y7ube3t7a1sbCysrOysK+trKunp6WkpaWkpKOjpKOhoJ+gnp+fnZ2dnJybmpmZmZqZmpycnJuamJaXl5eVlJeWkpKQjo2MjYyFjYSLV4mIh4iHhoWDgYGAgIGBgIGB//z7+Pj5+Pf09ff49fPy8/Xx8PLx8fHu6+vp7ezu7e7t7Ovp5uXg3t3e397d29rg39bY2d3k4d7f3N3e2djW19bX1NTV14TWG9fZ2dja2tva2NbY2NbV1NPS09PQ0NDPzs3OzYfMJsvMy8rJyszNysrJycfGxsXGxMXExMTDwsPDwsDBwcDAwMLCwcHAhr8SwMC+vb6/vby8v7+/wMDBv7+9hLx/vb69vb++vr/BwMHBwcDAv8HBwL/BwsHBw8PBwcLBw8XGxsXCw8TFxMXHx8PCx8jKx8XExcbDw8XFxsbJxsfJy8nJyMrLzM3Ozs3Nzs7Ly87PzczOzszKxsfIx8bHyMXExMbHxsfKys3MzMzJysvQ1NPR0c/Q09PU1dfY2NnZ3ITdgN7g4uHg4eLh5OPk5uXn5eTm5ubo6evr6+3t6+3u7u/u7u3w8O/u7vDy9/r8+/f5+v7++vb2+Pf09PTz8/Lz9ff4+Pj5+vr+/f+A/vz8/Pv7/P77+/r7+vz+/f3+/v39/fz6+/v6+Pr5+fj39/b3/Pz28vDv7u/u6uzu7+zr6+nnY+Tk5uTn5ubo5OTi4ePl4+Lh4N7d3dzd3d3e39zc29nZ2tvZ2dra19PT09LV1dPT09LTzs3Oz8/PzM3MysnKycrKysvKy8nJy8rJyMbFxsfFw8TDwsTCwMLDw8TBwsTDwcHCwYTCDcTDwcHBwMG/wcLBwcCFwhHEw8TCw8LDwcDCwsHCwMDAwYXDgMXExsjJyMnKy8vKzM3Ny83Ozs7Nz9LZ3eDj5uno6uzs7Ovn5uXi4ODg3+Di5OPm6e7y7+Xd3dvZ2drb3Nva293e397d4OLh4eLi3tna3uTq5N7Z2Nba5N7Z4OLj4t/b2tzc2tfU0tHS1NPT0tHT1NXW2N/i5enr7enp6eTh4uLjK+Tl5+nq7O7u7/Dz9PX2+Pr8gICAgYKDg4WFhoeIiYiJiYqLi4yNjo+QkI+EkEOSkpOUlpWVl5man6KkpqirrKytr7GysraytbOur7CysrK0tLKys7OztLS0tbW1ub2+vb29vLq5urm2tre4uLi5urm6hLsFuru7vL3/gcaB/4D/gPyAAYH/gP+A2IDqgQICBACAmJqbnKCioqGfpKGfoKCfn5ybm5yen5+hoqKio6Slp6qrrKyurqyusLKysbKwq6mlnpmWlJKUlJaaoKamoqCVkImCf318fX18fX17e3t6ent8f4CEh46MioyPl5SOkJqZl4+UmZqgqrayn5aMgn9/gH99fH1+fXp1c3N7fnZubW+AcHd4dG5sbXB1en6DfoKNhn1yf46OjZCiq6mopp6kn5aSmJiQjH95dnqGiImJio6KipGRj4iFgoCGjYyFenJ1eXp4dG9tZ8RctFxhv8rRyL+tpZyYn6qtoJqcmJKRkZOYnqSora6pn5mZloyBf319goN8f4CBhomRmJOapqCakIo4iYWBfXl9g42KgX59e3t5eHmAg4ODf35/fXx7fYCCg4eKi4uIh4SGiYyIh4aFhIOBgYB/gYGBg4aEhxeIiYmJh4SBfXt6eXZ2dXRzcnJzcnBvb4RwI29ubGpoZ2ZkYmJfYF9fX2FgX2BhX19dXFtbWVpbW1pbW1lahFuEXQteXV1eXV5fX15fX4RggGFiY2Zoa2tpZGRoamtqZmdlYGFiZWhkYmFjZmZjXl9iZmlqaWpra2xtcXh0cG9wcXJzc3l7e3yAgYWEg32AiIaAfoGFg4GBfnt8fn16eHZ2dXR0dXd2dXdyb3BsbG1wcm1paGdnamtpZ2doa2pra2xtc3l0aWtwb3Byc3Rzd3x4gHx3eXV6fXt/gHp0c3Nzen6FhH96d3yAgIKFiYyHg4uYmJOLh4aOmZ2cmI2Gfj4+enh4d3Z1c3JycXF0c3h7Pj8/fnx8enh1cW9ubnN1dnl+fn+Ch46PiYOCh4iEgYKDgHJwa2hrbGdpbm93hY6JhXp5fH9+fHdya2tpaGhqaXBygG5rampoaGNgX2yAcmFfYGBkZGlqa25xZGBiY2FeXVxbW1lZW15eY2JfYmNjaGtsbmtoZWNiYmJdW19cV1laWFdUVVdaWlpZWl5iZGRkYV9gYV5fYGFhYGFhZGZnaGpqaGdnZ2ZmZWRjY2RlZ2hrbG5vc3Z8fX+DhoiNk5eYmJaWgJWRkJKRjoyOk5edpKuwtLm8v8HDwLi0tLOwrK+ytLKtr7K0uLu0pJyXlpSQjYyKi4iJiYqKi4uKio6Rjo+JhH9/gouQfXdzd3R8gXp4c3J2eHBsbG1qZmVlZGJhYV9gX2JkZWhrcXyGjpdLkn92c3Bvbm5wcnF1d3h4eXp8fH5/EIGDhYdFRUZGSEhHSElKS0yETTRPUFFSUlNTVVVUVFVVVlhZWl1dX2JjaGxxdXd3e36AgYGIioyKgoGJj5KOh4aDf4WIhIGBhYMih4qIiYySlpmYl5aWk5ORkZKSkZGQlZWUk5KTlZSUlJaYl0WlpaaprK2tq6uurKqqq6urqainqKqrq62urq6vsLKzt7e4uLq5uLq8vb29v724trOtp6WjoqSlpamvtbWyr6agnJSSkZCEjwGOho2Ajo+QlJWamZeYmp6cmZuioJ+cn6GfoaivrKGblo+MjI2Oi4mIiIeFgX9/hIeCfHt7fICBf3t5eXt+goaHgoWOiYJ5gouLi4+an56dm5aYlI+MkZCKiH96eHuDhISFhYiEhIqJh4KBgH6BhYSAenR2eHd2c3BvbNNlxmRozdXY0MqAwbmzsLW9vrWxsrCqqqmqrLC1uLy7ubKurayjnJmXl5ycl5iZmZygpKimq6+uqqKdnpuZlpSWm6CemZaVlJWTk5OXmZqal5eYlpWTlZeZmZudnpybmpiZmp2cnJuZmJaVlZSUlZaVl5iYmpubmpqamZiVk5GPj4+OjYyMi4uLjIwCiomEiBCGh4eFhIKBgIB+fXt8e3x7hHwZe3p7eXl4eHd3eHh3eHl5eXh4eXl6ent7fIR7gH1+fXt9fX18fX1+f4CCg4WEhIB/goOFhICDg39/f4KDgX9/gIKCgX1+gYOFhoWGh4eIioyRjYuMjY6NjI2QkpGRk5SXlZORlJqYlZSVmZeVlZWTkpOTkI+Njo2OjpCRkJCRjoyOjIuMj5GOjIuKiI2PjYyMjY+PkJCSkpeZlY+RgJWUlZSUlpWYm5qcmpyZm56cn6CdmZiYmaCip6WioJ6foqKjpaiqp6Smrq+sqKWlrbK0s7OtqaNRUaKfnp2dnJybmpmanJuen1BRUaGhoZ6cmpiWlpeZm5udnp2doKOnpaGenaKinp2dn56Uko+Mjo6NjY+QlJ2jn5yWlZeZl5WSaY6KiYmJiouKkZGOjIuLioiFgoOMmo+FgYOBhYSIiYqMj4WCgYOBgH5+f358ent+foKBfoGCgYSGh4mHhYOCgH9+fHx/fXl6enl5eHd5e3p6e3t+gYKCgX5+f399fX5+fn+AgIKDgoOFhIWDgIKCgoGAgYKDhIaGiImMjpOSlJiZm56ipqenpqWkoqGhoJ+eoaSnrbK4vsPIzM7Q0M7HwsLCwLy/wsTBvb7CxcrNx7iwqqmopqWjoaGgoaKjoqKko6SnqKWopJ+ZmZ6nrJyVk5iVnKKbmJWVmZqTj46Oi4iHh4aFhYSDg4OFh4eJNo2Unaeut1uzo5qXlJOTk5SWlpmZmpudnp+goaOkp6eqVldXV1laWltcXV5eXl9fX2BiYmNkZYdmTmhpamttbXBzdXl8gYaHh4yPkJOSl5iamJCPlpyfnZeWk5CVl5SRkZOTkpSUmZqXmZygpaempaOioKCenZ6enp+foKCgoaCgoaGgoKGkpEm+v8DAw8TDxMPGxsXDxMTGxMPExcbHx8jJyMnJycvMzs7P0dHR0tPT1dXU1dbW09DPy8rLysvMzNDT1tXT08/Kx8TCwcC/vr6/hL6DvYW+J7+/vb69vb28vb7Av7++vry7ubu6uLW0tbOzs7KxsK+urauop6enpoWlKaSjoqCfn6CenZ2enJ2cnJyZm52cmpqcnJuYmJiXlpWUlJOPjo6Mi4yOhY0Hi4qMjIyLiYSIA4eFg4WCGYGAgID+gP+Agv/7/v389/Tx8fT39/Px8PCE7ALu7YTuMu3q6enl5uDf39/g39zc3d/e3N3d2d7h4N/b2drX19XT2Nja2NfV19bW19bX2tvb29nWhdc01dXU09TV1NHR0NHR0s7P0M7NzcvMzMvLysrLzMrJyMjIxsfGxsbFxcbFxcTDwsTEwsHAv4fBA8C/v4XALr6/v8C+vb2+vb6/v7++vr69vr29vr+/vcC/wMC/v8DCwsHBwcDBwsHAwMHCxcOFwoTEWsPExsbFxMTFxsfIyMfHxcPExcXGxsfHx8bGyMrLysfIycrKy8/Szc/Q0M/Ozs3MzczLzMvKycjJysvIxsbHysnKycnJysrMzc3MzNDS0tLT1dLT1NXV1NfY2oTbgN3e3+Hi4+Pi4uLk5ubk5enn5efn6ers6+3t8PDt7evt7Ozw8PLz8e7x8vP5+fr4+fr6+/z5+Pb3+Pf29PX18fT19/j5+fv9/P7/gID//v/+/v79/f37+/37/f2AgYD////+/Pv7+/r5+/v6/Pv5+fr6/fjz8fPx8O/t6+nr6ujrIe3t6uXo6ero6ern5eLh5eXk4+Ti4OHg3t7f3t/f397b3YTbgNra3NrT1NXU1tXW1NPU2NTR0tHQ0M/Qz8zLysnKyc7My83IyczNzMvKyMjJyMjIx8bHxcPDw8LDwcHDxcXEw8LExMXDxMPDxMTCw8PExMLBwcTEw8PDxMXFw8TGxsXEwsLBwcLCwsHCw8XFx8bHx8jKzMzMz8/P0M/Q0dDQzszPFNPV29/k5uns7vHy8/Tw6+nn5eTmhOho6u3v9fny6eTf3t7c3d/d3drb3N7d3d7f3+Lm4+fk4t3c4Ons4NzZ3t7m7OTh39/h4tza2dvZ19bV1dXU09PT1NXX19rd5O70+P+A//Hp6OTi4eLk5ufs7Ozt7/Dx8vT29/n6+4CBgYGEgw+EhYWGh4iJiImKi4uMjY2EjlCQj5CRkpSVlJaZmp2gpKaoqKutrq+usrW4ta+wtLi7ubW0sa+0trSysrOzsrKztbe1tre7v7++vr28u7u6ubq6uru6vLy8u7q7vLu7vLy9vv+Bx4EFgIGAgYH/gP+A9YCCgY+Ag4H/gP+Aq4ABgZmA64ECAgQAFZqbnJ2foaSmqKqlpKOgoaCfn52doIShLKCipKSjo6aqqqyusLGwsbKzuL29uLi6tqedmZufmZOTk5ifo6ino6CYjIKAhH+Afnx7e3t8fH5/f4aNj4+TmpqZioSAg4yMiY2RkZiamIyKjImIhoWDhIWHlJyckop8eHt+g31xb3FycHV8gIV9cnJ1eYCJi4yBcnaLkIqHlJ+opqKfo6KYioWIjYN5c4SYnJWJh4N8g4mKiI6OiIGCh4uMjIp9fYCBfHZuaGJcWllvW19kZ8/Ctauel42LkqGgnZial5GRl5SXmp6em5qYlZGMj4eEgIOLkYyKhIGFlqammI2JhoeOkZKIgn9/hImXlY19enx+e3t/goSFgn57enh6fH+ChYqPkpCPjo2PkZSQjYyJhoWEg4ODgoKCgYGDhIVDh4iJh4aBf3t5d3Z2dXV0cnFxcXJxcHBubWtsa2ppZ2ZmZmNiYWJhYF9fYGBfX19dXVxcW1tbWlpbW1xbW1tcW1pcXYRbgFxeX19fYF5fYWBjYWFiY2BiZGVoZ2diYGFjY2BgX11dYGVlaGhpZmNmamxoZWhoaGlsb3BwcHFyeX56dnV3dnN1fn16f4B/gYKBgX9+gIJ+e31+f353eHp8end4enp9gH59f3t6e3l6eXh3d3dvaGNkZ2hqbWtmaGxxc3Zxbm5tTHB1cWxtbGpqbW9xdHRydXd6eH1+dXl8e3Z4d3RzcHR/gn95eoGBg4SDgoB7gZaUk4qAgIiVmZiVi39+P0FBQUJ+dzt2c3Fwc3h5enyEey54dHRzcHBxcHBvc3d4en6Af4GIh4WEh4OAgoSLj4x8b2hma2xzeXx+h5SZiYGAhIKAfn19d3Vybm9ycHBub2ppbWtsaWVjaHx0YV5gZ29ua2lucnNsZWRkYmNjYGJhXlxcXl9kYF5oamhla29vbGViZWRlZmRiYVxaWltZV1daW1taXF1dYmNkZWNiY2NiYGBgYmRlZmVlZ2hpamlnZ2ZmZmRkZGNjZGdpbG9xcXJ2eX18f4KFio2QlJeZm5ubmpWUlZaUk5OYn6Kmq7K5vcHCwcLBvbi4urq3usXNaMnLY2K/uK2gmJWWlZKTlZWOioeFh4iJjI+NkpmUlY2NjIyOk4V+f39+fIGJhIB5dHiBeGxqa2hlY2RnZmNiYmNkZm1zeICKSk1PU1JOiHh0coRxJXR1dnh6eXl7fH1+f4GDhIaJRUZFR0VGR0hJSUtLTExNTk5PT1GEUlJTVFZXWFpcXF5iZWlscXN3fX1+h4+XkZOYko2Ig4CIjouJjoqIhoeGgoGCg4KBgoWJioiHh4iLkZadoaKjopyam5qYlZaXlpaVlpaWl5iYmJmaEaWmp6mqrbCwsrSwrq6trq2rhKqArK2urq2vsbGwsbS2tre5u729vby9wsbGwsHCw7WtqKqtqaSkpamvsra2srGonZWTkpKSkZCPj4+Ojo6PkZGVmpubnqGjoJeSj5KZmJaZnJugoJ6YlZWVk5KRj5GSkpidnpePhoSFh4uGfnx9fn2BhIaJg318f4GFjI2MhXt+io6AioeQl52cmJaZl5CHhIWIg3t2gY+SjYODgXuAhISChoaCf4CDhISEg3t7fHx6d3FsaGRjYmRnamzYz8S8tK6op663t7Otr6ypqa6rrrCzs7Gwr6uppqWfnZmdoqajopyZm6qwsqmhn5uaoaWknJiWlpufq6ihlpOVlpSUl5mbnJoXlZSUkZKVlpiZnKCioaCgnp+hoqCfnZqEmIOWhJVAlpiXmJiampqZmJSTkY6NjY2MjI2KioqLioqJiIeGhYWFhISCgYGAf359fX59fX5+fn18fHt5eXh4eHl5eXp5eYd6Ant5hHoZfHx9fX58fX5+f31+f398foCBg4KCgH5+f4Z9R36CgYODhYSChIaHhYGCg4OFh4qKiomLjZCTkI+Rk5CMjpSTkJOUk5SUlJWTkpWWlJGSlJSUkJGSk5KPkJGRlJWWlJWUlJWShpOAjouJiYqKjI6Oi4uOkZSWlJOTkpOUk5CSkZGQkpSVl5iWmZqbmZydmZucnZqbm5qbmp2ipaOen6SlpqWko6KforCuraqjoqmusrGwqaGjUlNUVFOjoFCgnZqanJ6fn6ChoJ+goJ2bm5mZmZiYl5iZnJ2dn56go6Gfn6Khn5+hpKSApJiRjY2QkJWXmJmepqmgmpiam5qal5aXkpCPj5CQjo+NjoqKjIuNioaFipaPg4GEiY6NiYiMkI+Ig4OEg4ODgoKCgX5+f3+EgICFh4WEh4qJiYSBg4KDhIJ/gH17e3t6eXh6e3t6e3x8f4CCg4F/f4CAfn9/gIGCg4OChIWFhYSAgoOCg4KDgoKBgYKEhYaKiouLjpGTlpianaCipaiqq6urqaajpaakpaSpr7K0usDHzM/Q0NDPy8XGyMjHy9Xcb9nZa2vSy8K2rKmqqaeqrKmkoqGfoKGhpKanrLGtr6mop6irsaOdnp6dnKGppaGclZqjmo+LjIqHh4iJh4eGhoeAh4qPlJuiq1teYGNjYK2cmJWUk5SWmJiampucnp6foKGjpaepq61XWFdZWVpZWltcXV1eXl9gYGFhYmNkZGVlZmZnaWtsbXBzdXp9gIOHjIyNl5+kn6KloJyYkpCXnJiWnJmXlpeVkpCRkpGRk5WYmpeWl5eZn6Sqra+urKmmpqYQpKOjpKKjo6OioqOko6OkpYC/wMHBw8bGx8nKysjGxMXGxsXExMbHyMfIyMrMzMvMzs7Q0tPU19XV1tjZ29zc29va1NHP0NHOysvN0dXW2tnV1dDJw8LCwcHAwL/Av76/wMC+vr+/wMDBwsDAvb28vL6+vb7Avby7urm4tri3tbSztLWzsrGxrq2qqainqKeop4CmpKKjoaChoZ6enJ2enp+enJqam5uZmZubnJqZmJiYl5WSkZCOjIyPkpKSjY6Ni4uNjYyMjImJioqJiYeGhISEg4KDgYKBgYCAgYGBgP779/Py8PLx9Pbz8e7v7+7t7uzt7u3s6+vr6ejm5eXk4OHi5OLg3t7e3+Lh3dna2Nja21Xb2NnY2NjZ3dzc2NjX2dfW2dva2tnX1tXW1tbV1NPV1dbW1dTS1NXV09DQz8/OzczLzMzMzczMysrKycnHx8fJxsXExMbFw8PCwsbFxMLBv8DBwcHChMAFwcHCwsGEwA+/vr2+vr++vr6/v8C/v7+Evga/wcDAwcGFwA3BwcK/wMHCwcHBxMTChMN/wsLGxcTFxMXGx8fHxsbHxsTExMbGycfIyMjJysvJxsbHyMrKysnJycrMzMnJzM7Lz87Ky8zJysrKy8rKzMvKy8rKycvNz87NzszN0M/R0s3O09TT1NXV1dbX1NTW19rb3dzc3t/j5uXk5OPk5efm5ujp6ejn5+nq6+3t7u7v8YbuKfDw8PHy8PP19fX29/r7+/r7+/r59/j49/b2+Pj29vf5+vj4+P3+/f+AhIGA/v+A//37+/z9/f79/v////78/v79/Pz8+/n8+/v6+vz49/Xz8fLy8PDy8/Dt7ejn6uzu7uvp6evp6+nn5OXm5uXm4+Lk5ePi4uHh4uDf4N7d393e3drZ2tva1NTX2NnY1dTV1tbV09PT0tTV1NLQzczMzcvOy8zOzszLzc3My8gMx8rHyMnKyMnIx8bGhcVlxMPFxcXHxcbGxcbGx8XDxMbExcXGxcTFxMPExMXGxcXFxsXEw8LDw8PFw8HCxMbIycnLzM3Nz9DR0dHS1NXU1NXU0tDR2Nvd3+To6+3w8fHy9PHs6+7v7fH3/ID9/4CB//nx6OCE3m7h4+He3N3c39/e4ePj5uzp7Ojq6ers8Ojh5OXl5erx7Onj4ebu5dza2tfW1dfa2dbU1dnZ297i6PD2gIGChoeE++rm5uXl5ebp6uzv7+7x8fHz9Pb6+/v8/oCAgYKBgoOEhISGhoeGh4iJioqMjISNRI6QkJKUlJSXmpqeoKSmqK2trrS5vbm6vru4tLCxtri1tbi3tbW2tbKzs7OysrK0tri1tLWztrq+wsPExcPAv8DAvbu8i70Cvr//gc6B/4D/gPOAhYEDgICB/4DqgAWBgICBgcOAhoGZgOqBAgIEABGcnJ6goKGipKerrKqopqSlpIWiC6OjoKChpKSkpaaohamArK6xsLK1vMO/vMG7uLKtp52YlZOSlZeanaCnq6KXjIaFhIOCgH9/gICAf4CCiI+SjpSgnZmPh4OMlJaTnrCkl5CNjYqOkpudm5aOkZWdmJWdk4R9fH+HgHJ2dHd+hYyLioh+dnZ6fnl3d3ZzfoiIh4WHkaGpop2dmZiRi4OBgICAeoGKhX9+fnt/h4eFjJGOjIOBiYyOkpOMhIR/d3JuZ2JcW1qvraytrLTJ18mzpZmOjpqcnJudlI2Mj5KRlpWZmI6IhoaGgYKGiI+Qj4+Ig4ifpaqgoZyTlJuSiXt0c3yGkpaWlIeFg4OBf4GAgIGAfHp4eHp/g4eLj5WYl5manJpOmJWVko+Lh4WDgoOEg4GCg4OChYWGhISFhYSDf358e3p5d3Z2dXRycXFwcG5sa2lnZWVmZmVlZGRlZGNiYWFeYF9fXl5dXFxdXFtbXFxdhF4HXV5dXVtcXIRbHF1eXl1dXVtaWlxiZWtraWZhYWFiYWJjYmBfX16EX4BjaGdpb29rZWducG1qcHNucHJydHZxb3B4e3t6eXp5d3x7e36Cg4GEhYWEfn1+gIF8eHl5dHN3enx6dXZ4eoCBf4GFi4mIjZCPiIN7endydXx1bW9wbm5zfH19fXdwbm1vc3FvbGprc3h5e3t2dXd5cXVxbG5wbnJzdXV2dnZydSt9gH56d3l7fn19fHqClJCGgX9+f4KIi46Mgn9BRklFRIQ9PXt3cnN1dXd2hHV8dnNvbm5tbGxtcXZ3c3R8goKDhoaHioqGgH5/en6IkYqHgW1qaW9+g4mWnJ2Th4GDgoWEgX+CgX96d3Z3dHJxc291dnBsaGhmaXl8Z19hZnNzam1ycnRxamZlZGhnZmdlY2FdXWBiXl9oa2hla3BraGhua2ZmZmNfXFtcXIRaF1xdXl5fYWJkZGNiYWFiY2RjX2BhZWZnhWgDaWlnhGaAZGVlZGRkZWhsb3FydXl8fH+Fi46PkJOWl5eYmZmYlZWXl5manqKjqK6zuL3Awb++vb3BxMPDw8bLaW5wZ8S3p6Cem5aXl5iXm5+ckomEg4SHh4yQjZGYlJCSlpCPjoh/gIWDgYKEg4mCf3+Kh3t0cWxraWptbWxqaGhqbXSAh5OATlFQUVNUT4h5dHJycXJycnR2dnZ4eHd4enp7fX5/f4NDREVERUVHR0hISEpLTExNTk9PUFJTU1NVVlZXV1dYW1xdYGRoamxvcnd1e36CkZiPkpWPhoJ/f3x9fXx+hIWGhYSDgoSEg4aIio6MiomLjI6PlJmdnp2en6OkpZ+bmZkLmJiYl5iZm5qam5uAqKipq6ytr6+ytra0s7GwsbCtra2sra+vrKysrrCxsbGztba3trS3ubu6vb/FzcnIzMfEvLm1rKilpKOmqKqtr7a6saWbl5eVlJSSkJGTkpCQkpOXnJ2anqWkopqVkpednpyksKmgm5mal5mcoKGfnJeanZ+bmZ2XjYiGiI2KgIKAgYOHio+PjoyGgH6BhYF/f358g4mJiIiIj5icmJWTkpCLiIOBgIB6gYeDf35+fH6FhYKHiYWFgH+DhYaIiYR/f3p3dHFsaWVlY8TBwcHAxdHZ0cO4sKmos7Syr7Cqpqapq6yvra+vqKOioqCenZ+hpaWlp6GZna6zt66vq6SjqaQQm5KOjZKdpKmpp56dm5uamISZG5eVlJOUk5eZnJ6go6alpqeqqKako6KgnZmZmYSXI5WVlZaXmZiYl5iZmJaVkpCQkI+OjY2NjIuJiYqKiYiGhYSDhIGCgIR/C35+fX58fX18fHx7hHoUeXp6e3x9fHx8e3x7e3p6ent7enqFew58e3l4en6AhYaFgX5+f4SAgIGAfn18fn5+f4GEg4WKiYeDhIiLiIWHioiKiouNjYqJiJCRkZGQkY+PkJCRlJaWlZaXmJeTkpOWlpKQkZGOj5GTlJKPkZGTl5eXmZufnZyfoJ6cmpaVkpCTmZSOj5CQkZWam5ubl5OSk5WYlJKRkZGVmpydnJqanJ2YmpaTlZaVgJiZnJubm5ybnaOlop6coKOioaKhnqOwr6ijoaGhoqeqrKqkpFNXWVdWqFFRo5+cnJ6enp2cnZ2dnJybmpmYl5aWmZydmpudn5+foqKjpKSinp2empyhqqKgnJGPj5GbnqCnrKumn5mcnJ2cmpmbm5mWlJSTkZCQkY6RlJCMiomHNIiUloiChYmRkImMkJCSjomHhoWHhoWGhYWDf3+Bg39/hoiFhYeNiYaFioiFhYWBfn19fn6Eex98fn1+fn+AgYKDgYCAgIGBgH+AgIODhISEhYWFhIWEh4MWgYKDhIWGiYqLjpGRk5aanaChoaSlpoSobamop6upqqyusbW4vcLGys3PzcvOzs7Q0NHT1tpxdXhx2M29tLKwqqmoq6yxtbGqo6CenqGgpKinrrOuqa60rausqaGfpKOho6SjqKOhoaunnJWUjYyMjY+Pjo2Mi4yPmKSqs19iYWJkZF+unJeFlnyXl5mZmZqcnJ2doKGho6Wmp1VWVldXWFpaWltbXV1dXl9gYGJiZGRkZWZnaWlpamtrbW9ydXl7fICDh4aLj5Oepp6hopyWkY+QjIyNjZCUlJWUk5KRlJOTlZeam5qYmJmbnJ2hpqurqqurrrCxq6inpqWkpaSlpqemp6enUMDBwsLExcTGysvMysnIx8jIxcbHyMnJysnIyszMzM7Oz9HS0tLR09TX19jY3OHg3eDe3djY1dHOzc7O0NHS0tTa29TNyMbFxcbFw8TCxMPChsETwMLEw8G/vr2+vr+/wMLBwL68vIS7ILq4ube5uLWysrGwrqyrqqqoqKmmpaSkpKOjoqCfnp6ehJ0EnJybmoWZH5qZl5iXl5STkY+OjY2Ojo2OjYyMjY2Ni4yLioqJioqEiQWGhYWEhISCdoCAgP7+/v37+/v89u/t7/Pz8/Lv7/Hy8O7v7u3u6+zs7Onn5+Xk4+Tj5ePi4eDf4uTk49/f3dvb3dva2NXV2Nre39/d2trZ2trZ2dnY2tnX19bW09XW1tbX19jW1dTU1NPR1NPS0M/Pz87Ny8vLzMzKycvKy8qEyFfGxcTFxMTDw8PExcXDxMPCw8PDwsHBw8HBwsLDwcDBwL/BwMC+vr/Av7++v76+wMC/v7/AwMHCw8TEw8LAwMDCwsHBwMDBwsLBwsTEw8PExcbEw8TDwsaExV/Gx8jGx8TExcXExsjKycjHyMnMyMfIycfIycnJysjJycrJycvLzM3MycnMzczNy8rMzczLzM7Pz83OztHQzc7P0dLR0tPOz9PT1NbZ2NbX2NfX2Nna3dzc3d/g4eHi44TkK+Xn6urq6+rq6+vt7vDx8fPz8fHx8u/w8PP08/H18/b49/f29vn+//39/vqH+YD69/b2+Pn7+vv6+/z9/4CBgYGA/4CA//38+/3+/////v39///9+v39/P39/fz9+/38+/r6+vf39vb28vP18vDw7+zt7evr7Orq7Ozq7Orp6Ofo6Onn5uTm5+bm5uTg4uLh4N3h4t3c3Nva2tvc19fX2Nvb1tjZ2NfW1dXU1NfW1RrT0dLRzs3NzszNzM/Ny83QzszMzs3JysvKyIXJHsfHxsXHxsTCxsfHxsfHxsbGxcTEw8TExcXGx8bGxoTFgMTFxMTFxcXEw8PExMXGxcXGx8nIyczNz9HR0tHQ0dHU1NXV19nX2drb2tvg4+fq7vDy8e/x8PDx8vT3+vyAhIaC/vrv6efk3uDf4ODl6ebg3d3c3uHh4+jn6vHu7e7y7O7v7Obl7Orq6u3u8uzp6vPx5t/f29ra2tze3t3a2d3gPefv9fqBhISGiIeE/O3o5+fm5+fn6ezt7u7u7/Hx8vT1+Pr6/ICBgYCBgoOEg4SEhYeHh4iJiYqMjI2NjpCEkU6TlJSWl5mbnZ+hpKeqqa2wsrfAury9urWysLCtrq2usLS1t7SztbS1tLO0tba5t7a2tre4uby/w8PCw8TFxsbDwcC/vb2+v7+/wL6+wL//gcqB/4D/gPeAhYEDgIGB/4DpgISBw4CHgZmA64ECAgQAgJ6fn5+goKChpaets7GvqqurqKWlpaanp6ako6OlpaampqirqainqKqrq6utrbC6vMHDycS3qKOnoJeVkpKXmJyeoaOloZeSjomIio6JhYaIi4mHh4eLkZSYkY+JhIWJl6CWl52UjYuRlZGQkJmXlZGPjY2MiYuMjIiDg4iFfHh8gHp5fYeUmZGHfHd5fXh9fXVzd4KEiIaChJOio52dq6mYkYmDgYB7fH96eXx4d3yAdHiDkZOTi4GAh4mIjZCQiIR9d3NxZ2BeXVhWVaipstfi3dHIvKyYipSYnJ2XjYWHiIqNkpSRjY6KiIWFhIWGipGWk46Kh4WQprKmp6+on5uSQYuAfX17f4OHkpiXkoeBgH9/fn+Bf313d3h7foaNkJOYm5uamJeXlpORkI+Oi4qIiIqLioaGhoeFhoWGhYN/fX19hHwXeXl3dnZ1cnFwcG5ta2ltcG1oZWRjY2KEYwliYmFgX15eXV2FXAZbW1pcXl+EYIBfX1xaWlpZWVtcXFtfZGJjZGZnZmVjY2VmaGxpZ2ZnZWVlZGRiYmBiYmBhYmFma29vbGhnaWppaGtsbG1ucXZ4d3FxeHh5e3+Fh4R/fn6CgYKDhISGhYOFhX5+f3+Cfnl3eHd3enl4eHd1c3N3f4mJjJeWlJCGhX16e4uKgHZ5e4B3dHZ5eXl7enZzbm1ubnBua216fXd5eHRzeXh4cWxrb25sb3J0c3d2dXR6fn59fXx8eXh8gYOAhJGSiYB8eHV2fomKkImDQkRIRUFAPj4+PTpxcXBvcHFwcG9uN2xsbG1sbW5ydHZ5fIKMi4qFg4WEg3x7gIKAhJSnn6aqnIV8eniAipCTnZyVhoSEh42Ni4mIh4aEgYB8eXNydHZ+fXZybW9qand2Z2Zoa3Fzcnp3cXd5bGtqampmamlnaGpmYGJfXGJpa2hqbGZjZGpramtpY11cXVtcW1tcXV5gYGJhYWJkZmRiYGFhX2BiYmBhYmRkZGVoZ2dnaGmEaIBqampoZ2hqamtrbXBzdXl8fYCEio2Njo+QkpeYm56enpqamJuio6Sqr7O2u7/BwcDAv8TExcTHymhpa2tktqqkoJ+alpiZmJqcoKGZkYmIhoiKiYiFh42HhYSOjY6Lh4J/gIuPjoiLio2JkZSTinx8eHNzdnZ1cW5rbnF5g4uSTQFShFMzUE2MgHVxcXFycHBwc3R2dHR1dnd5eXx+f4CCg4ZDRERERUZHR0dISUpKTE1NT1FSU1NUhFVIVlZYWVpaW19iZWZoZ2hrcHZ0cXx/foKDhYeEfXx6eHp4eX1+f3+BgYCBhIaJjI6Sk5CPkJOXmpqZlpWXl5iamp+in5ubm52chJ0EnJycnYCqqqusra2urrCyuLy7uLW1tLKwsLCxsbKwsK6vsbGysrK0uLe2tba3tre4urq8xcnNzdHLwbWxtbGppqOjp6irrrGzs6+oop6amp2gm5mYmJuZl5eXmZ6foJ2cl5OUl6ClnqCln5uanp+dm5uhn52amZiZlpOUlZSRjYyPjIiFiICHhYaLlJeTi4SBgYWAg4J+fICIiImIhoeQmZqWk52bkI2HhIGBfn1+e3p9enl8f3d7gYqLioR+foKDg4aHh4F+enZ0cmxmZWViYV+7vMTd4eDZ0se7rqeusbS0r6ejpKWmqqyuq6ipo6KhoZ+hoaOoq6ikoZ6dprS7sbS6s6yrpkuhmJSVlJmcnqapqaafmpmYmJeYmJeVkpGSlJacn6GipaiopqSkpKOioqGgn52cmpqanJ2ampmZmJqampiXlJKQkZKSj4+Oj46NjYuEiQqIh4WEh4mHg4GAhH8XgH9/fn59e3t8e3x9fHt7enp6eXp6e32EfgN9fXqEeVl4enp7enx+fX5/gIGBgX9+gYOFhoOCgoSDgoGCgn9/fYCAfn9/gISHi4qHhoWGhoWFh4eHiImLj46Oi4uQkJCRlZqamJWTlJiWl5eYl5mZl5iZlJOUlZiVk4SRgJOUlJSSkpGRk5ienZ6mpKOjnZ2XlZeioZuWl5mXlpiZmZmamZeVk5OUlZaTk5Gbnpudm5qZnZ2dl5SSlJWVl5iamp2cnJ2fo6OhoaGioJ+ip6ikp6+vqqOhn5ydpKqqrqupVVdZV1NTUlJRUE6bm5uamZmYmJeXS5iYl5aWl5iagJucnqCiqKimoqGioaCcnJ+fnqCotK6ztayempmcoaWnrKyonpydnqCgn52enp2cm5uYlpKQk5GZmpOQjY2Li5OSiIiKjJCPjpWUkJSUioqIiIiHiYiGh4iGgoOBf4KHiIWHiIOChImJh4eHg3x8fn5+fXx9fn6AgIKBgYGDhoSCA4GBg4aABYKDhISDhYVIhoSFhYaHh4WEhIWHhoeIiYuOj5KTk5SYnqCfn6GipKiqrK2vr62sq62ws7e6vcHDx8zR0c/PztHR0tLU2HBxc3VuzL+7uLSvhKxtr7G0ta+po6GhoaOjo56iq6SjoK+srammoZ+gqq2tp6upqaqws7GonZ2alZWZmZeUko+RlZ2mr7ZeYmNlZWRhXbCkl5WVlZaVlpeYl5qampucnp6foaGjpaanqVVWVldXWVpaWltcXV1fYGBiY4VlU2ZoaGlpamprbG1wdHZ3eXl6fIGGhYOLj5CSk5SXlI6Ni4mLiYmNjo+NkJGRk5WXmZudoaGenZ2gpaelpqSjpKOkpaesrqqpqKmoqamoqaqop6ipJ8LCwsPFxMTGycvNz8/Oy8vKycjJy8zLzczLy8zNzc3Oz9HT09PR0YTTgNTW197f4uPn4t3X1NfUz87NztLS1NXX19bV0MvKx8jLzMnHxsfJxsPDwsLCw8XEwsC+vb/Av77AwMC/vb7AwcC9u7y8urm5ubi2tLOxsa6rrKupqKmpqKWkpKSjo6Khn52bnJ2cnJ2cmZqamZqcm5qYl5mYmJaUkpGQjo2NjIyNCouLjI6NjIyNjIyEizKKiImJiIeEhIOEhIKDgoGAgID9/Pz//fj08vLx9PXz8vT19fHv8PDu7+/u6+jq6uno5oTkgOXn5ePi4t/i5OPf4OLf3t7c29nY2dfa3Nzd3t3c3NnZ2NjX2Nvb2tfW1tXV1tfX19jW1dfW2NXV1dTU0dHQ0M/R0c7Pzs7NzMrLysvKycnIysjGxcXGxMXFw8TDw8PEw8TEwsHDwsPCwcDAwcHAwcLCwsPDwL+/v8C/wMDBwMHAVMHAwcLCxMPExcTEw8LBwMDCwcHDwsLFxcPCxMTDxsXFx8fIyMjHxcjJycjHyMfGxsjHxsbGxMfJysrLy8rJy8rJyMjIx8jJzMzNysfIycrLys3N0YTONM3OzMvLzM3Mz9DPzc7Q0dLS0dLR0dPS0tXW1dXX1tXZ2dnb2tvb3Nvd39/d3t/f3+Hk5OWE5Ezp7e7t7u3s7e3u8vTz8/P09PP29vPw8vX29PP29vn4+ff39/v//f39/Pz7/Pv8/Pv6+Pn39/j7+/r6+/z8/P+AgIKBgICBgoGAgPz8hP81/v7+/4D9/f/+/v39/v7+/f/+/fv7+vn39vb19fT09vb28O7w8fHx7u3u7Ozq6unq6+zq6umE6G3n5+no5+Tl5eHg4uXj4N7d29ra29rZ2NfZ3NrZ3NrW2dnW19bW2NXV09HS1NHNzczMzs7Pzs7OzcvMz87MzM3NzszKycvLy8rIyMjHycbFx8nLyMfJycfFxsfHxsbGx8fHxsfGx8fGx8bFxcXGhMUBxITGR8fHxcXHysnJzc7Qz8/Qz8/S09bZ29va2tna39/f4uPk6ezv8O/w8PHz8vT1+PyAgYOEgPfw7evo5ODh4uDi4+fo5ODf397ghOQt6O7n6Ovz7u/v6+nm5e3x9O3w8PLt8fX48Ojn5d/g4+Tl4N7d3+Hp7/j/goSGhIcHhPz06ebn6ITpA+vq7YTub/Dy8/f3+Pv9/f+AgoGBgoKDg4OFhoaGiImKi4uNjo6PkJCRkZKSk5SUlZeZm5yeoJ+hoqaop6iur6+xsrW3tLCvrautra2vsLCvsbOzsrW1t7i5urq7u7q6vr+/v769vr6+v8HCxMLAv77AwcLBwYTAAcL/gcyB/4D/gPWAi4GKgAGB/4DZgIWBwoCIgZuA6oECAgQAVqGioqOjpKOkpKSnr7W3trKwrqyrqamsr7Kyr62sqqalpqeqqamnqrCwr62rrK2vs77L0dHOxrmxramfnJqcmJibnJyfo6SkoZeXkpummJORkZaWmJKLhYmAiIaGi5WVj4qNkZSUkpKUlJGSkZCOjI+RkJGTkZKXj4+RlZGQjYB9i5eXl5GKenmLlYqMgXl8ho2WkYOBg42epaOnwayTj5CKgHuKlpSHenl6eHVtanF5e319en57eXp5goaGf359e3RvZl9fX2BhvbnI1N/h1MO7tKifm5mWlZOAi4aGiIuNj5GSlpaVlZCKiYmLi4yRlJWHiIqJmbC9sp+WlI+Jh4uKioaFiYaRmJSLhH98fHp6eXl6e3h0dnp9foKKjpGRkY+OjIqJiIiIiYyNjYyMi42NiYaHioqHhoaIh4N+e3p6e3l4eXl6eHV1dXRzcnBsampqbG1sa2hlYmOEYoBjYmNjYl9fXl5dXVxdXV5dXF1cXF1dX2BfX11aWlpbWltcXF1cXV5cXmNlaGxqa2hiYGFkaGhoZGRkZWZlZWVkZGRjZ2hjY2p2d3NtbW5sbGptc3Jua250dXJvcXR6eX6GiIaDgXx+fn9/f4CChIKCgIGBgX9/gIB8ent9hYSBfIB5dHBwb3F1foiSkoyAfnp8eHd+jYmEfHx9enh0dHV3eX5+e3h1c25tbm1zf5WMg4Z7fYaUppx1bHN0dHN1cnFxcnN0d3x9fYCCgH59fX5/gYKGjIqBeHNzeHyFjZWRi0JDQ0JAPz8+Pj47cXNxbm02bTc3Nzg3NzZrN3FydT1Ah4BERI+MREKBfHl2dn+EhoyTmKOnqqufkYmLlZqioaCbjYeIjI+TkY+Oi4mGhoeHgn1yent8fn98eHZ0c3Z4d3Jwbm1rbXR2cXJ3dG9ubm9vcXdzbmtrZ2RnY2NqcnJtbWhjYGJjZ21xal9dX15dXV1fXl9hY2VkYmNjZWlnZGNhYIBgYWRlY2RjZWRkZmhoZ2ZmZ2hqbW5ubGtrbGxtbm5ub3JzdXZ4fYCDhYqMjpGSlpufn6GjoZ+fo6aqra6zuLy+vsLFxsXExcfFxcjKaWtqZMO2rqKgpqSenZ2enJydoKGclpSOi4uKhYB6gYWBgIWZkJaMgYGHjJeVjoaMkI2XUwZZUZOEhn2EdTR2dnl+g4uWTU9QU1VUUk9KioV6gn5ycHFxcHJzdHV2d3d2d3l6e3t8fX9/gYNCQkNERkZHhElfS0xNTU5PUFFRUlNVVVVWV1lZW1xdXl5gYWNkZGdoaWhnam1ucHBwcXR5e3p4eHl8foGBgoCAgYKDhYeJjI+Slpianp2goaOhnJmYmZqcm52goaGfn6CfoKCgn56foJ4GrK2trq+whK+As7q+v766uLa1tbW0trm8vbq5t7WzsbK0t7e2tba7urq6uLm5u7/I0tnZ1tDGvrq2r6urrauqra2urrKzsa6mp6OqtqmloaClpqihm5mam5qZl5aXmqCempibnKCfnp+gn5ydm5qal5qbmpmbmZibl5WWl5aVkYuIj5iXlpKOg4KAjpOKjIeCg4mOlJCGhYaMmJyanK2gkI2MiIB9h42LhHt6fHp4c3F1e31+fHp8enp7en+BgXx8enl1cWpnZ2ZnZ8vL0trh4tzNx8K6tbOwra2uqKOjpaeoqausrq2uraqko6Oko6Snqamgn6GirL3EvK+opaGfnqKgn5ybn52mqqZPoZyal5aWlJOUk5OTkJGVlpaZnaGioaGgnp2cnJqam5ydnZ6enZ2en5yZm5ucmZiZmZiWko+PkZGQjY6PkY+Li4yLiomJh4SEhYeIhoWCgIp/EH18fH18fHt6enl6e3p7e3uFfFd9e3l5eXt5eXt7fHt7fHx9f4GChISFg39+gIKEhISDg4KCg4OBgYGCgYKEg4CBho6PjImKioiHhoiNjImHiY6OiomJjJGQlJqdnJmYlJWWlZSVlpeYl5mEl32WlZaXlZKTlZuamJaWkpCPjo+Smp6ipJ+ZmZaYlpiaoqKfmpqbmZqYlpaXmZ2enZuYl5OUlZSXoa6oo6Wen6Wst7GYk5iampqbmJiZmpudoKOio6WmpaSjo6Slpqepraukn5ydoaOnrLGwrlVWVlVUU1NSUlJQm5uamplMmIRMgEtMSpRLmZmbUFKoU1OsqFJRoZ6amJqgoqKlqKuxs7S1rqijpKqssK+uq6KgoKSmpKSjoaKhnZ2foJyZkpaWl5iZmJSSkZGSlJWSkI6NjY2Qk5CQk5CNjYuMi42QjouJiYaEh4SDiI2OiYqGgoCCgoWKjYiAfX5/f399f36AgoKEgISChIOEh4WEg4KBgYKEg4KCgoSDhIWGhoaEhIWFh4iJiomHiImHiImKi4uMjY+PkJOVmJqfn6GipKerrbCztbOys7S3uby+wcTJzM3Q0tPS0tLV09TV2nJ0cG3VysG4uLu4sa+wsbCxs7S1sKurp6Wlo5+cmJ2in52ht7C3rZ+fVKWqs7GqpKmuq7ViaGCvo6WfmZmZmpuanaKnsbVcXmFkZWVjYVywqZ+no5eVlZaWlpiYmZubnJydnp6goaKjpaanqlVVVVdYWFpcXF1dXl5gYGBhYoRkT2VnaGhoamxtbm9wcHJyc3V3eXp7enl8f4GCgYOEhouNi4iIio2NkJGRj5CRk5SVmJqcnqGipKepqayurq2ppqSkpaeoqqytra2srKusrayEqwGqGsPEw8XGyMfIyMjLztLV08/OzMvKy8zO0dPRhNIJ0NHQ0dPU09LThNU11NXV19zg5+rp6Obh3tva1dLS1dPT1dXT1dfY19TQz87U2NDNy83Qzc7JxsXExMLBwMHAwsKEwBvDw8LAwcPCv76+vr26uru6ubi1tLSxr62tq6yEqyunpqSjpaShoqKfn56fn56enpyampqZmpuampybl5aVkpGQkZGPjI2Njo2NhIpdiYmJh4iIh4iHiIaHhoWEg4ODgoKBgICA//39/v/++PX18vT49fPz9PXz8fHy8O7t7Ozu6+vs6+rq5+bl5ubl5OHh4uHl6Ojl4N3d3Nzb3Nrb2trd2t/g3t3b3NrahNhu2dnZ19bX19bY19fY19bX1dXU1NTT0tLPz8/Oz9HQz87Ozc3Pzc3MzMzJyMjJx8jFw8TExsTDw8TDwsPCwMHBwMPDwcLBv7/BwcHCwsLAwMLBw8HAwcDAwMLDwsHBwsHAwsLDxcTExMPDwsLBw8SEwxHFxcPFx8bGx8nJx8bIysrJyIbKWMnHyMnIycrLyMjJzczLy8jKycvLycnKysrMzs3NzMzJy8vMzNDS0dHR0tHR0M7Nzs7My87Q0NDOz9DS0dLR0NLS0dXX2dna29za2tvd3Nzd3tzc4N7h4uWE5Bnl5unp6Ojp6ert7fDv7e7u7vDx8/Lz8PT2hPcb9Pj3+fj3+vj5+vr5+vv//v7//Pz9/v7+/Pz7hPoV+fr7/Pz7+/v8/4CBgoOCgIGCg4KAhf+AgP+AgICBgICA/4D//v+AgP+AgP7/gID++fn5+Pn49/j39vTv7+/x8vDw8O/t7evq6evs6unp6urq6ejn6Ojo5ufn5OPj5OPe397d3d3c3Nva2tvb29rb2trb2NjZ2NnY1tfV1NPV0s/Qz9DOz8/Nzc3MzMzLy87Qz8vLy8nKy8uAysjKy8rKycbIyMnMycrLysfFxsjJx8fGx8fIx8XFx8bFx8nIysvKx8jJyMbExMbHx8fGxsfIycrMzM/Pzc/P0dTY2Nvf3t3d39/i4+Pm6Ort7/Hz8/Lw8/X19/r+g4aDgP/28Ozq7Onl5OTj4uPk6Onl5OXk4+fo5ODc4+fm5uaA+PH28ubm6e309u/o7vPw9oCFgPfu7+zk5OXk5uXo6/D6/4CBhIaHh4eFgv737vb05+fp6erq6+vs7u3v8PHy8/X29/j6+/z9gIGBgoOChIWFhoaHiIqKiouMjY6OkJGQkJGSlJWWl5eZmZmbnZ2eoKGioaGjpaaoqKipqq+xr6wprK2wr7Kzs7CxsbO1tra5uru8vb7AwcDBxMXEwb+/vr7AwcHDxMTDwcKEwwXCwcLDw/+By4H/gP+A9oCLgYWAAoGAh4EOgIGAgICBgYCBgYCAgYH/gMeAhIGvgIOBj4CJgZ2A6oECAgQAgKamqKalpqanqKiqrKywtbSwr7CwsK+usbS2tbSzs6+sq6mqsLGvrrCxsrKxsK2srbS9ytLV087EvrS2tK+tppucnZ2amJ6emJSbnpahnqGfnpWVlZeUj42NkI2MiYmTlY2MjZGTnZ+alJOWlpaUkpKTlZSTk5aZnJqXkY6PjYuKgH6AgISfxs7BlYGGhYiKgX1/iJCOiIJ/g5Wdn52ntp2Sk5KNhHqAj5KIenN0c2xla3d6f4iGiYqBg3+AhIyNg36Af3NvamVjZ3F1cnJ57ODVxbqxq6SYl5eXlJCNiouNkJWdpaSinZeXmJWRj4+RkpOTkY2Ni4mOmJ2clYqFgoGOGJaWlpCJhoWVjoZ9end1dHNycnNycnF1e4R+GIODg35+foCBgIGAf36Bg4SGhomMjYyKioSHGoaGhYWDgH17e3l1dHl7enh0c3NzcW9tbGtrhGkHaGZnZ2VkY4RhFmJhYWBfXl1dX15cXFxbXFtdXl1cXV2EXCFbW1tcXl5dXVxdXl5dYGJjYmNlZmViYGBkZmdlZ2dnaWiEZIBjYmZoZWNodHt5goN/end3dXZ0cW1tb3Fubm9zdXh7f4ODgH9/fn9+fH1+f3+BgoB+fH19f399fX2BhouJhnx5enp2c3J1iKSllop+enRvbW1zeXl4dnh5dnN2d31/gYGEg394eHFrbnB1d3+DgYqYj4ycpaOZf3N9gn90cnBwbztwOTo7PHx8fn5/QEBAPn6Dg4CAe3d3dnl9g4yTlJNIREdGQz8+Pj9APTk6OG5uNzY2Nzk4ODo8OT1CRoRHgEZEQT16eXRxcnJ2foKHj5aYnKWto5mSmJ+ZmqKppZ+TkJCTlpmZl5aUkIuKjY6Mg4GEgICCg4N5d3h6e3d2dXVxbm1tb3JtdHZ1dHZzc3R8goB9c2hlamhjaG12cm5namloZ2twbmhhX2BfXl5eYGBgYWJjY2RkZWZlZmdoZGFfgGBhY2VnaGhoZ2VnaGhoZmdpbXFycGpoampqbG5xc3Z1dXh5eXl7gISFiI2TlpueoKKlpaemoqOmqa+xtru+wMHGx8jHxMTFx8XGymhnZsS4sq+qp6israKem5mZmp2emZiWj4mEhn9+g4eJiX6HmZ6VkouNkZWVl5uWkJeXlZVTgF1UkIeIiIGAgoWGikpNUFNXV1VWU09IioN8fXx4en53dHNzc3Rzc3V2d3h3d3l6enp7fX+AgYNDRERFRUZHSEhJSktNTk9QUVJSUlRWV1ZXV1laW1teX2FgYmNiYmNlZWVnaGpra2xsbG5vc3Z2enp/gYF/gIOCgYCChIaKjI6RIZSWmpucnqOkpqmvr6yop6SkpKKjpaeloqGjo6KhoaGiohOysrOzsbCwsbKztra2ur69urq7hLqAvL7AwcC/vru4uLi5vr68ubu9vb69u7u7vMDJ1tvd39vNysHDwb6+uKytra6rqq6uqaWrrqevsLGurKWlpqejnp6eoJ6cmZmeoJubm52epKajnp6hoZ+enZ6dnZycnZ6goJ6cl5WUkpGQiImIi5uzuLGWiYqKjIyHhYaLkY+LhoSAhpKYmJabpZaOkI2Kg32CiouFfHd4d3Jucnp7f4OChIR9f31+gIWEfnt8fHRxbmtpbHN0cnJ25+Hb0MfAurqzsrGwrqypp6eoq6+0u7m3s66urquopaWmp6iop6Wko6KjrK+vqJ+cmpqhqaippJ6dnqijn5eUkpGQkJCPkI+PjpFElpiYmJaampqWl5aXlpaVlJSUlpeZm5mdn6CfnZybmZmamZiXl5WTkZCQj42Kjo+PjouKi4uKiIeFhYWDhIWEgoGCgYCEfwR+fX1+hH0Pe3t7enl5ent7ent8fH18hXsZenp7ent7e3x6fH1+fX5/gICBgoODgYCAg4SEBIODhYWEgoCBgYGCgYCFjZGQlJWUko+Pjo6NioiIiYuJiYqOj5CTlZmYl5aWlZeWlJSVlZiZmZeWlpeVlpWVlJSXnJ+dnJeWmJeTkZCTn6+xpqCZl5OQkZKXm5ubmJqYmZeZmpyeoKCioqCcm5eSlZeanKGjoaavqqizurmxopqhpKGZmpmZlzGaT09PUKSjo6OkU1NTUqaqqKaloZ+goKGkp6yxsrFYVlhZVlNTUlNUUUxOTZqaTUxMhE0GTk9OUVNVhFZpVVRRT56dmpiXl5qfoaSnq6yvtLm0raitsauqsLSxrqalpaanqKmqqKekoJ+ho6GdmpyZmpydnJaTlJaXlJOUlI+OjY2PkY2SkpGQko+QkJSXl5aPiYaIiIWIi5CQi4eHhYaFh4uLh4F/hICAf4CAgYKDhIWFg4SEhIaGh4SCgICBgoOFhISGhoWFhoWEg4WGio2NjIiHh4eIiYmLjI+Pj5GTkpKUl5ibnaClp6mssLK2uLi3tLa4u7+/xcnLzc/T1djV0dLU19TW23BvcNfNxsG9u7y/wLixr7CvsLO0rq6uqaWhop2bnaKnpp+Apbe4sq6qqKyytbe7tLC2tbW1YmxksKmpqqSjpKiqrlxgYmRmZ2ZnZWBarKafoZ+dnqKcmZiYlpiZmpucnZ2enZ6en6GhoqWmp6hVVldXWFlZW1xdXV1eX2FiY2RkZWZnaGhpa2xrbW5xcXNydHV0dXZ4eHd6e3x8fn9+fn+BhIg0iIuLj5KSkZOVlJKSk5SWmZyeoaOjp6iprLCwsbS7vLezsrCwrq2usbOwrq2vsLCtrKytrR/HyMnIyMnKy8vLzMzNz9HQzs7Oz8/P0dLT1NXU19XUhNMF1tbW1daE2IDZ19ja3OHo7e7u7ebl4OLh397b1tXW1dPS1tfT0NPW0tjU1NPU0M/OzczJycfGxcPCw8TEw8PCwsPFx8XDwsHBwcC/v727vLu6urq3tbSzsK6urayrq6qnpaioqaajo6OioqCgnp+enZycm5ycnJuZmpuZl5eVk5KRkZKRkI+NjByMi4qJi4uKi4qJioiHh4eIiIiGhYaFg4SDgoKBhIBZgf/9+vr69vX4+/j39/X19PT08/Du7+/w8O7s7e3s6+rp6Ofl5OTj5ePi4uTk4uDd3d3b3t/g4N3c3Nvg3trZ2drb29rZ2NfV1dXY2djY2NfX1tfV1dTU0tKE0TrQz87Q0NHS0tDOzs3Ozc3Ly8zLysnHxcXExMLExcTCw8TEw8DCwcHCw8LBwcC/v8HCwsC/v8DBwcHDhMEEwMLDwoTDJcLBwsPDwsXEw8LExsTDw8TExcXFw8PExcTHx8bGxsnJycfIycqEy4DKycvNzMrJycrKycrJx8jLzcrLy8vMzMrJzczKyMrMzMvLy8nKzM3O0NHQ0dTT09TS0NDQzs7O0NHQ0NDR0dLS0M/S09LU1dTX2Nzd3dzb2drd3d3e3t/g4efq6+zq6Ofo5+jo6urp6ent7u/w8fDw8PP09vPz9Pfy9ff39vr8+Qn7+vj5+vv8/f6EgBv////+/4CAgYH+/f38/f/+/f79/vz8/P3/goKEgxKCgYOEgYCAgP/+gICAgYGAgIGHgIWBVoD//Pz7+/r5+/z8+/r49/b18fHx8/Px7u7s6+vt7e3p6evq6Onn6Ovs6ujp6ejl5OTk4eDh4N/e3dza29zc3Nvc3dzb3Nzc29nZ2tjX2dbV09DQ0dHPhNJA0c3Ozc7Oz9HPzs3NzcvLysrLysjJzM3NycrLy8rKyszKysjHxsbGyMjIy8nHx8bGx8bGyMvNzMzKycnJyMfFxoTIgMnGx8nJyszMztHT0tTW19fb3d/f3t7h4eLj5uzu7e/09Pf28/T19/j7/oKBgP318+/t6+zv7+bm5OPk5ubn5ubl5OLf5OLe4+bp7uXp+Pv08u/s7/L09/359Pj39viDiYP28PPz7u/w8/X4gIKEh4iJiYqHhID99e/w8O7w9uzqeers7e3s7O7v7+/x8/P09vf4+Pr7/v6AgYKBg4ODhYSFhoeJiouLjI6PjpCRkZGSkpKUlpaYmpuam52enp+foKChoqOlpaampqenqayrr66ws7OysrSzs7Kztba4u76/vb7BwcHDxcXHycrKycfHxcbExMTGxsbFxMWFxALDxf+BzoH/gP+A1oCEgYWAhIGQgI6BgoCVgf+AyICDgbGAg4GKgIuBoIDqgQICBACApqisr6+rqq2trq6wsrO0t7KxsrKztba4uru6uLi5trKvq6mutLSxsbG0t7m5tbCurrC2vL7Bx83Lubi0uLi3raihnJqZmpqYmJmVlJeUlJiZlo6OjI+PjYuMkJGQjo2NjpCSkpScn5iXmZeXmJeXlJSWlZOWnJ2empuVmKahoJKAhoeOpLK9uLWllpCQhYaNkZGSlJCIg36Fj5CUmqirk4yKjJCOfnmCf3lzcXFvbm1yeXl8d3d7f4iQkY2LlJSPh4GBemxiYmh3e3l3c+Hc0cXAubKtopuVlJSXlpKQjJKYoKOmpKCen6mtrrCropyZmJeXk42OiomPkI2LjZCLiIkpioqLin9+gIWBfHh1cXJ6fHd2ent5en6Afn17eXd5dnV3foWJiYiEhYSEhSGBgIWHhoSDhYaGh4eJiomFgoF/f359eXl6d3Rxb29ubm2EbChraWdoZ2ZmZ2ZlY2JgYGBhYWFgX15dXl1dXF1cW1pcXF1cXFxbW1tahFwJXWJiYV5cXmBghGGFYIBhYmNjZGhtcnFuaWRhZGlrbW9nZmlsa29zfX5/fn18fHZvcHR0dHZ8fX2BhHx0dXl7fHl4ent8fXx8fHt6fHt7end4eXl6eXx8fYKHg316e3+Cf315eoSVlI6EfX55dnl5d3h7enh4eHZ1d39/e3l5gH59eHFxc3dzbnJxc3yUpxSklpeYkHp0do61YZp5cW43Nzg4O4Q8gD0+QEBAPjx9gYF8eXh3ezw8fEGJj0pMTE1OTEdBQUNAPz08OjhtNjU4Ojs6Oz0/OjpBSUhDPz0+PHNxcTo6dXV2eHp6gYuPnKOpr6eamZOSnKWlqqafnpqZm6OloaCempiVkpGSkI2Jh4WEhYiHe3V3ent8fHRycG9ucXd1dHNxgHF1dHR1eH+GhYV/d21ubGlscW9tb3Z2cXFvc3VtZ2VqZmJfXl9fYGJkY2NiY2RkZWNkZWVjYmBiZGRlZmlramppa2tsbGxrbGxsa2tnaGxsbW5wc3l7ent7ent9foCChomNkJOVm56ipaempqipqayytru9wMLFyczNy8zQz8nEb79laMnDwbawrqqrr7Gvo5uXm5uYl5iUlJGNhoeCgIKNlZWUl6aimpOUlZmdo1NUUp2cllCdUVFQlpKNjI+QSkxOUFFVWlZTUlJOS46Dfn14dXV5e3p7eXZ0c3RzdHR1dnZ4eHd5e3t9fX6Ag0JERIRFZUZISElJSk1OT1BRUlNUVFVWVldYWVtdXl9gYWJlZmRkZWVnZ2pvbm51eXl2c3NzdXh7fXx+gISEh4yMi4uKjJCTlJSUmJ2dm5qbnqSsr6ywtLGxsq6vq6emqampqquqqquqqKelgLCytrq5trW3t7i4uby9v8G9u7y7vL6/wsPDxMPEw8G+uri5u8HCvr28v8LDxMG8urq9wsfJzdPW1MbGwsTDxLu3sayrqqqqqamqp6appqaoqKehoJ+gnp2cnqChn52cm5yfoJ6fpaajoqOioaGgoZ+foJ6cn6Giop+fmpykoaCWgI6Okp+or6uqoJaQkYuKj5KRkZKPioeCh42NkJSen4+KioqNi4B8g397d3V2dXR0d3t7fXh4e36CiImGhImJhoF+fnlvaGltdnl3dnLh3tbRzcjDvrq3s7CusrGtqqessLa3urizsrW7vby9uLOuq62sqqikpqKhp6ejoqOlop6fTqGioqCamZudmpaUkY+QlJiUkpWVlJWYl5aWlZORko+OkZWanJ2bl5mXmJmamZWVmZucmZiYmZmbmpqZmZeVlJSTkpKPjo+Oi4mHiIeHh4SGHoWEg4KCgYKCgoGAf35+fX19f319fHt8e3p5enp7e4R8GH19fHt7e3x7e3x8fn5+e3l8fn9/gICAf4WAgIKEhIOEiIyMiYaDgIOHh4eIhIOFhoSIjZOTlJWTlZWOioyMjI2NkpKRlJmSjY6Rk5SUk5SVlpeVlJSVlZaVlZSTlJSTlZOTlJabnpyXlpecnpyalpifqKeln5qZmJeYmJqbn5+cnJuamJqgnpubnaKhoJyYmJmdmpaZmJqfr7q6ErCwsaygnJ2sxWa0n5uYTE1NToVRgFBRU1RTUVGlpqajoaGho1BQpFOssFlcXF1eXFlVVFVUUlFQT06aTExNT09OT1FST09SWFdTUE9QTpqamk1Nm5qanZ6eoaepsLW3vLatrqqqrbOytLKtraurrLCvrqyrqammo6Wlo6Gfn5ycnaCfl5OUlpiZmJOSkI+OkZSSkpGQgI+SkZGRk5ecm5uYk4qLi4iJjYuFiZCQjIyLjo6LhIOHhYOBf4GBgoOEg4ODhISDg4OFhoWDg4GCg4SGhoiLioiGiIiJiomJioqJiYqHh4iHh4iMjpGSkZKSk5SUlZaYmp2goqSnq7Cztri1tbe6u77Aw8rNztHT193d2tvg3tjUgNJtbtfU08rCv7y9wcTDubGusbCur7CsrKqoo6SfnJ6psbKvsr+9tq+wsbW5wWJiYby7t2C+YWFht7Sur7KzW11gYWJmamhlZGNgXbCno6KenJqcn56fnpuamZmYmZubnJyenp6foKCho6WnqFRWV1dXWFhZWVpbXVxfYGFkZGVmWGZmaGlpaWpsbm5wcXJzdHd4d3d4eXt6fIGCgYeLioeFg4OGiY2OjY+RlZWYnJuZmpqcnaCho6Woq6qopqirs7i5uLy+urm7u7q2srK1tbW0tLS1tbWzsrBrx8jLzc3Ly87Nzs7P0NHS09DP0NDS09XX19jX1tjY19XV1NPV19fW2dja3N3e3Nvb29ze4+Tm5+jp4+Lg5OTl3trY19XU1dXU1dbS0dPR0dDS0c7MyszLycfGyMfIxsTExcfHxMPFxcXExcSEwgO/vr6EvAy7ura1s7OxsK6sq6qFqICnpqOjpaGhoaChoaCenZucm5qZmZibmpiWk5STkpGQkZCPjo2Mi4yMi4uKi4iJiImKiouJiIqJhoWEhYODgoKDhIOBgoL//Pv7+/n3+f3//f379/f39fT08/Lx8vHw8PHw8O/r7Ovq6ejn5ubl5ePj5uXh4ODh3tvb3d/f393d3Svg3tva2drY3N3Z19jZ2dfX2dfX1dbU1tXT1NXU1NLT0tPR0M/Pzs7P0dHQhs4gzcvLysnJyMbFxsfFwsPDw8XDxMPBwsLCw8XFw8HAwL+EwG+/wMDBwcHAvr+/v8HDw8TFxMTDw8LBw8TFxcTCwsLGxcTExcbExsbFxcbGxsfIx8fIyMnKzMzNzc7LysrMysnLy8rLysrMycnKy8jJyszLzMvLycvLzc3MycjKx8rKy8vMzM/S0NHS09TW19fX1tWE1ILVhdMP1dXT0tLS1tfZ2dja293dhN4Z4eHi4d7g5OXq7fHw8PHu6+rq7u3u7/Du7ofxG/P09fb49vr4+Pj09fj19fr9gPz///+AgIGCg4SBCYCAgoKCg4H//4T+G///gID+gP/+gIOEhISCgoODhYOCgYGBgP6AgIaBK4KCgYCAgYCBgYKB//7/gID+/fz9/f7+/Pr6+fn29fP28vHw7u7t7Orp6+yG6hbp6erp6+jq6efn6OTj4eHi4eDf3t7fhdwm39/c3dzd3N7c2tvY1tfX2NfW1NHQz8/Rz9HR0tHP0M7Mzs3NzdCGy4LMhM6AzM3LzMvLzc3Ly8nLy8rJx8rNysvLzMnJysrJysvNzcrJysnJyMbHycvKysrIx8fKysvMzc7S0tHT1tja3d/e3uHi5OTl6eru8PHz9/v6+/r8/fv594CB//r58/Dt7O3x9PXq5ePo6Obl5+Xm5eTf397g4enw8vHw/fv07/Ly9PiA/oCBgfz8+oD+goKB/fz29vj6gIGDhYWHi4iJh4iEgv349PPt6+zu8PHz8O3t7O3s7e3v8PLx8fL09vf6+/v8/oCBgYKCg4ODhIWGhoaKio2Ojo6Pj4+QkZKTlJSWl5iZmpudnZ+en6CgoKGjp6amrK+tq6qrqquvsbGwsrO1traEuiC5u73Av8DAwsPEwsLDxcfKzcvNzsvMzcvNy8nHycnJx4bJAsjH/4HNgf+A/4DQgAGBhICQgYiABoGBgIGAgJCBAYCTgQWAgICBgf+AxICCgaqAC4GBgYCAgIGAgYGBhoCNgaGA7IECAgQAXK+rq66ztLe+vr29u7/Bw8/IsrG0tri6u7y8ubm5ure2trSzs7G1vLi0tLe3uLi0sq+tsLS3ur7GxsK8t7a2srGurqSdm5yZmJiZmZaUkZCPj46Oj42MjYyJjI6OhY2Aj5KTlJWbmZmYl5iZnZ2iqqqvrKGmp6mrrKqmnpqfnZqWkZCPkZejo5eRjI+MjYuZpJ6YlY2Dg4ePnKmwr52Vl5WZkIp/fHt+hIJ5cnByeYWBhoaJioqIioyIiYuRk4+JenNiZHB6hIV7ctDH2HZya8nCt66moZ+gp6imm5OUm6BroZ+dn6S1vru8wbuuoZuampqXjYuJh4WKi4iKkIuIhISDhH97e3t8goOBfHR7i5CLfXl8f4CAgH16eHVzdXN3fH+ChomKhoaIiIeGhYWBfnx7fYCFhoeLj4+OjImHi4WBgYB+fHh2dG9vbmyIazZqaWdlZGRjZGRjY2NiYF9gYF5dXVxbXF1dXFxcXV5fXl9fXlxbW19gYmJhZGZlY19cXV1dXl+EXj5fYGNnZWJfZnB0cmxqaGFlcnt6fHZwbnZ4cXB+f3x5fHZ0c29vdoGZmpWRiIOAe3ZzdXZ3dnNxc3V3d3l5eYV3VXh4eHl5eHd4eXt8e3h1dnd4eHV2d3d7fXx7eoKCfHh7e31+f3t+fHVydXh6eHd5e3x+fHhxb29ta2xub3KBgoGAf318e3l3j6iznntzczg3Nzk7PD+FPoA/Pz08Oj1AfDw7PDw7Ozw+QkRHS01NTElIRUNDPz4+PTs6ODc2OTs6Oz1BPTw7P0RMRUE8Pz08PT8/Pz14eHd4e4KPm5+gprCrnI6FiJGanKSmpamqqqesrKajoZ+dm52emIyJiYmLj5CMjYV5dnp5fX+AeXJwcXR0c3Nyb3N1ciVydX+FhoeJh350cW1rcXRtcXV1cnFvdHZybmZra2ppZ2NgX2Fjh2IJYWRkZWRiY2JihGUEaGttaoRogGlqbWxqZ2dnaWttcnd6ent7foGAfn58fX5+gYOGi46RlJueoKGkpqepqqyvsLa8vsDAxMnMz85pa9DMyMBlZsXAv7muqaqurKmvo5qWmZaSkZKQjpCRioqIjJqeoqxYV1ZXoaGopldZXFtdWVlZXlxRUJSRkJKWlkxMTlFTU1BQF09NSUpOT0uEfXl4dXVzdXt6c3Jyc3N0hHZ5d3d2eXp7fH5/gIJCQ0NDREVFRUZHSEhJSkxNTU9RUVNVVVVWVldYWFldX2NkZ2hqaGhqaWprb25xeH+ChZGUlJSTkIqKjI2NjZKUk5OSj5CSk5SWnZ+gnZ6eoaKhoaKmqq+ysrO6trCytLW0sq+ur6+ur6yusba1s1C4tbW4vb/BxsbFxMTIyc3Vzb29vr/AwcTGxcPDwsTCwcLBwMC/wsfDwL/Bw8TDwb+9u77Bw8XK0NDOyMTDwsC+vb20rqysqqqpqqmnpaOioYSgBp2cnZ2cnYWcgJ2cnaChoaGlpKOjpKOipqWnrq6wrKWnp6mqqaikn5ydnJqYlZWUlZefn5eSkJGPkI6WnZmVlI6HhoiMlp+joZWRkpCSjId/fX2AgoB7d3Z3e4N/g4KEhYSDhIeDhIWKioeCeHVpanJ4fX13ctjP3HVybdLNyMG8ubW3u7y7sqysgLK2trSytLfEysXFxcG7tK6ura2qpKWjoaCjo6GipaGfnJycnZuXlpWWm5yalpGXoaSimJSWmZqZmJaUk5GPj42Qk5WXmpydmpqbm5mZmJiVlJSTk5WYmJqdoJ+cnJmYmpiUlJSSj4uLioeHh4WFhoWEhIWGhoWFgoGAgICBgICABn99fX18fIV7hnwCfXyFfVJ8fHt9fn9/fn+BgX99e319fX+Af39+fn+AgoWFgoCEio2LiIeGgYSOko+QjYuKj5CLi5OUkZCQjo6Mi4qOlqSjoJ+bmZqSjo+RkpGQj4+PkpOThZRlk5OTkpOTlJSTk5OUlpaXlpWWlpaXl5eYl5mcnJyanp6cmZ2dn6GioKKgmZebnZ2bm52goaKgnZmYmJaWlpeYmqOjpqajoaGgn52uvcK0oJ2bTU1OUFJSVFRSUlJTU1NSUlFRU6SHUCZRVVdYW11fXVtZV1ZWU1NSUlBPTUxMTU9PT1FUUVBQUVRaVVNPUoVQgE9PnJycnaCkq7GztLi+urCnoaOoq6yxsrCztbW0s7Swr6+urKqsq6mjoKChoaOko6OdlZOVlpqampWSkI+SkZGSkY+Rk5KRkpicnJqcnJePj4yJjI2KjY+PjIuMj5CNioOJiomIhoSCgYOFg4OEhISDgYCEhoaGhIODgoWGhYWIBIqMioiEhwyIi4qHhIaIiIiKjI6Ek4CVmJeWlpSUlZaYmZyfoqSnrK+xsrW1tbi6vL/CxMvNzs/T19nc23Bz3tvZ1G5v2dLQysG+vsG/v8W7sq6yr6uprKqoqaqmpqSosrm+xWVlZGS8u8PCZWhraWtoZ2dta2Bgs7KxtLi4Xl5gYmRlYmJgXltdYmFdqKCdnZuamZygn4CZl5eYmZqamZucnJ2dn6ChoaOkpadUVVZXV1hYWFlaWlpbXV5gYWNkZGVmZ2doaWlqa21wcnZ2eXp8e3t8fH1+gYCCiY+TlqCioKGhnpmanJ6enaKko6Ggnp+hoaGkq62sq6ysrq2rq6+ztbq9vr7EwLq9v76+u7m4urq4t7W3uQO+vrsrysnKy87P1NXV09XW19jZ3tnQ0dLU1dbX2NjY2djZ2Nja2dbY1tnd3NnZ3ITdK9vb3N7g4eLk6Oro5OPh4eLg3t3a2NbW1dTU1tbU0tHPz87Nzc3LysrJx8aEx4DGxcXGx8bExMbFxcTExMPEw8LAwMC+vLu7u7m4trSysK2sq6qqqKenp6alpKSjo6Ggn6Ghn6CgnpybmZmampycnJeXlZSSkZCQj4+Qj46MjIyNjoqMjIyLi4yKi4qJiIqJiYWEg4KChIWFhIKA//7/gIGA+vj7+/39/fnz9Pb09Qz18fP08/Ly9PXz7+yE6wnq6efn5ubm5eSE4iLg4d/e29vc3d7e3N3f3t3e3trb4ODe2dna2tjW1tfW19bVhdYR1NPV1dTU0tLR0NHS0M7NzMyFzRfMy8nJx8fIxsPExcXExMXFw8TEw8LCwYXDBcHAwMHAhMEEwMHCwYbAHsHCw8PExMHBwsPCxMTFxcTEw8THxcbFxcXHycnIyYTHgMjHx8nMzc3Oz87NzMvNz87MyszLyszOzM3Ky8rNzMfGzM3LysvLzc3My8jIzczNzs/Nzs7Oz9LU1tbU1dXW19jY19fV1tfW09XW1NTU1tbX1tXV19nb3Nzd3+Li4uPj5OPl5uPj4ubp7PH19/n69/Hs7/Lz8/Tz8/Py9PTw8vX1IPX29/f4+Pz8+/v7+fr4+f38+/v8/f+AgICBg4SEhIOChIMsgoKBgID/gIGBgIGBgYKAgICBhIaFhIOEhYWDg4SDgoGAgICBgoGCg4OEgoGEggSAgIKBhoAV//7+/v3+/fv7+vn29/f08PHu7OrqhOk96unp6Ofn6Ojo5+fn6enq6Ofm5+Xi5OLi4d/e3t3f3t3d3d/d3d7d3d3b2trb29nX19nX19TS0M7Q0s7Q0oXQDdLQzs3Pzs3Nzs3Mzc2EzxLOzs3My83Nzs7MzMzLzMzMy82EzBfLycnKyszNzcvKycrIyMnLzMvNzMvMy4XKgMvMzM/S0tLV2Nnb3N7f39/h5OXn6Onr7O/z+Pr7/ICD//78+YCC//v79/Lt7fLy8vnw6OPp6Obm6Ojm5ufk4+Hm8fX5/oGBgIL6+P78goSHhomFhISKioOC/fv5+/38gIGDhYaHhoaFgoGChYWD+PPz8O3t7PD18+7t7u7u7+7ue+7x8/T19vb3+Pv8/f6AgICBgoKDg4SFhYaHh4iKioyNjo+QkZGSk5SUlJWYmZycnqChoKCioqKjpqamrrKytLy9vr69u7e4ubm3uLu+vr2+vb6+vr/AxcXGw8PDxcbGxsfLys3Qz8/Sz8zMz8/PzczMzMvMzMrMzs/Pzf+BzIEGgICAgYGB/4D/gNCAk4EBgLSB/4C9gIKBhICCgZ+AhIGEgIyBhoCPgZ+A7oECAgQAgLq3s7Oytbi7vL7DwcbJy9HYzsXHwb+9vLy+vbq6u7i4ubi3uLe3ucC+wL+4uLe5uLW0s7a2uLi5u7++vbu5trSysrOtp6ShnpmYl5eXlpWUk5GRkI+Qj4+Oj5CPjo+Njo+PkI+SlpaUlpeXl5ianqy+w7Wtq66xr7q+uMbLyLG5gK2XjoyOjZGVoZ6WgoaPiZeprKSbk5KSk5efpqmno5ykrbGzqZeDf4WNko57cnF0iIqIhoWJhIqAenN6en+HiImEc2Zkd4aFfnVu2NXX5+/hzsK9tra7vMLGxrS0saeYmZydnp+en6WwxsK6tquknZmal5KNiIiHiYyLh4eIioqIbIaBfn2BhoiIiYqIg314en+CfH+AgYB9fn59fX9+fICDgoKFhYmGhYaKko+Ih4SDgoOCgoOIiYqOkJGQjIaCgYB+fHt7eHRycG9va2prbGtqa2xsamtpZ2RkY2NjZGRkZWNhYWBhYF9dXF1fYYRgb2JkZGNhYV1aWlxeYWNjYmVnZ2RgXV1fXV1eX2JjYmBgXmFjZWNnbXBxbnBvZWRwd3+EgXpvbHJxd3+Bfnl2c3B2enmAi5CQjYiAfHl2dnNxcXJ0dXNycXN0dHV1dXZ4eXh2dnh3d3Z1dXV3enl3doR0A3N0dIV2Onh7enl2dnl/gH57fHx1cXByb3J1dnd7gHp2c3BxdHFvcHZxcHF0d3d2eHt/f4mVlIx+eXZ3Ozo7PD6EQIQ+gD08PD0+Pjs6Ojo5Ojs+QERGSEtLSEdGSkhDQEA+Pjw6OTc2NjY7Pj5GQj5ESUlKTUtFQ0VFREZHR0ZDgT49foSPlZqeoaKekIaNmKGspqy3x7u4tbS2tayqqqeioqSkm5OPiIuRlZeZl5CCent5f4KIfnx5eXp6d3Jwb3F0dHN3gIKDg4mQkI6HeXFvdnl9f354b2lwdHRtZ2ZnaW1wcGplYmNjYmJkY2JkZGJkZWRkZWRlZWdmZmdnaGZkZWZmZ2doaGhnZWVqcnh8e3p6eXh8fn+Afnx7fXx7fYGFjI+SlZqcoaWlp6msrrG1t7y/v8HCw8XJyszOzMzJx2RlamTDgL26r6msqqeqramdlJqSkJCLio2Mj5CTmJ6pVlVYXWFgXVtbW1pcYmNcWFVXWlxRTpaWl5SXmk9MS09OTEtNTUpJSlJXUoR1cXN0d3VzeXl0cHFycnJ0dXZ3d3l5enx9fn+AgIFBQkJCQ0RERUZHR0hJSUpMTU5PUFJTVFVWV1hcU15eYmVoaGlqbGpmaGtub3B0eH1/f4CGiZCUlJqcnJiamJWMjIyOkZOZn5+dnKOenp2foaenpZ+goqGjpKarsbS3u7y7ubm3tLGysrCwsrS2tra5SsLAvby8vsHDxMbLy87S09fb1MvMysfGxcXGx8XExcTDxcTDxMLBw8zJysjDw8LEw8HAv8LCxcbHycvLycjGxMLAwMK8trOyr6uphKgGpqWko6GihKCAn5+fnp6enZydnZ6eoaOioKGio6Skpamwu7qzrayur6yztbC4vLiorqaZkpGSkZOWnpuXiIuRjJWgoZ2XkpGTkpOXm56cmZSan6Ghm4+Cf4SIjYl+dnZ3hIWEhIKFgYR+fHd6en6CgoKAdGtrd4B/enRv3dva5ung0szLxcXKycx20M/Ewr62r7C0s7K1s7S4vsnHwb+6trCtraqopKCioKKjop+gn6CgoZ2cm5ianZ+fn6CempiVlpuclpiYmpmWlpaVlJiXk5eYmZiamp2bmpqcoJ+amZiYl5aWlpiamZqen5+em5eVlJORkJCQjYuKiYeHhYSFhoWFDoSGhIJ/gX9+f3+AgIB/hH4ofX18fHx+f359fX6AgIGAf358e3x8foCBgYGDg4OCgH5+fn1+fn+AgYSAMYKDhIKEiIyMiouLhYOMj5WZlpGKiYuLjpOWlJCOjYuPkY+Tm6CgnZqVkpCOjo+PkI+FjgePkZKSkpOThJSFkwiRkpOTlJSUloSUUZWXlpiYmZmYmZybnZybm6Cio6GjopyZl5qanJ6dnqGkn52bmZqamJeZnZqYmZygoJ+foqWjrLOwq6GenaBQT1BRU1VXVFRUU1JTU1JTUlJSUIVPb1BTVVdYWl1eW1pYW1lWVVVTU1FPTk1NTExPUVFWVFFVWFlaW1pWVFVVVVdYV1ZSo1BQo6arrrK0trazqqSor7K4tLi9xr+8u7q7u7WysrGur7Cvq6iloKOlpqeop6Sbl5aXm5ygmJaUlZiWkpGQkISRgJObnJqdoqOgmpKOjI+SlZWUkYyIjI+Pi4eGiImKi4uIh4WFhIODhYWEhYSChoaFhYWEhISGhYWGh4mIhoaFhYeHiIeHhoSFiY2QlpSSkpOTlZeYmZeWlZWUk5WZnKCjpairr7O2tre3ur2/w8TGysvO0NHU19fZ29rc2NdtbnRtgNPPz8O+wsG+wsTBtKu2rauqpqWqqqysrrO4w2JiZWpubmtoaGlpa3Bxa2dkZ2hqYF22traztrpfXl1gX15dX19cXFxjaGOompeXmJyamJ+fmpaWl5iYmZqanZ2en6Cho6SlpqanVFRVVlZXWFhZWlpbW1xdXl9hYmNkZWZnZ2hpVG5wcnV3enp6fH19eXx+gIGChomOkJGRl5qgpKSpqamnq6mknZ2cnp+ip6ytq6qxq6uqra+0s7GrrK6srbGyt7y9wMPExMLCwL26vLy7ubq7vb6/wGnSz87Ozs/U09XV2Njb29vf49/a2drZ2NjY2djZ2drZ2tza2NnZ2dzg3+Hg3dzc3d7d3t/h4ePi4+Xm5ubk5ePi4eLk39va2djV1dbV1dTT0tDPz8/OzszLysnJysnIxsfGxsbFxsfFxMWExBrFxsXDwsDAvr28urm6t7S0sq2srKqqqKenp4alHqOgoaKioKCgoZ+enZybm5ucnJybmZiWlZORkpKRkISOdpCQj42NjIuNjImJiYiJiYqKiIWDhIeHhoOCgf/9///+/fr59fn5+vn29vXz9PLv8vH09PPx8vLy8+zr6+rq6+vq6ejm5+fo6Ofk4+Hf3uHg3t3f4ODg4+Hh397e3Nzc3d7d293b19XX2djX2NnY1NbY19XV09eF1C/Sz9LQz83My8vKycnIysrLysrGxMXHx8bHyMbFxcXDw8PExMTDwsXFxMPCwMC/v4TBBcDAwL+/hMArwcDCw8TEw8LDw8PExcXGxMTCw8TFxMXFxMTHysvKycnHyMnLy8rKy83OzITNc8vMz8/OyszOzszLzM7NzszMzMvLy87PzczLy8rKyMjMzc7Oz9DP0dDQ0c/R0tLU0dLU1tfY2trY19fX1tfY19XW19jY19fY2Nnb3dvc3uPl5efm5OTk5uno6u3v8vb39/v+/vf19vn6+/v39/n3+fn49viF+yr8+/z9/////Pz9/P3+/f38/f3+gYKDg4WGhoWDgoOFhYSEg4KBgoGCgoGFggmBgoSGiIeHhIWFhAOFg4KEgRGCg4SDg4SDg4KCgYKCgYGCgoWBJoD+gID//v7//f7++/n59/Ty7/Lt6+ro6ufn5ufm5eXn5+bn5+jnhOgv5ujl5OPl5ePf397c3t3f3t/h4d7e3d3d3Nva2dra19na2djW09LP0dLS0dLR0NCGz4DNztDR0c/Pz87Pzs7Pzs/PzMrNz87Nzc3Mzc7My8vLzMzKzcvLysrMzc3LysnIy8zNzMrKycnLy8rMz8zJy8vMzM7Q0tPW19ra293d3t/f4uTk5ufn6ezv8PP39vn8+fv7+4CBhoH++/v28vX08/X39e3k8Ono6ebm5+fq6uzv84D+gICBhoiHh4SFhYWJi46JhoSFhomCgPz9/v3+/4GAgIOEgoOEg4GAgYiLiPjr6uzt8e7q8/Pv7e7v7vHw8fHz8/T29/n5+vv9/v+AgICBgoKDg4WFhYaHh4mLjIyNjY6QkZKSk5SWl5menqGgoKKko5+ipKWlp6ussbS1tbe4vDW/v8LDw8LFw7+7urq8vb/CxcXFw8jFxMPFx8rKycXGyMbIyMnMz8/Q0tLS0dDPz8zNzczLzITOAdD/gcuB/4D/gNiAyIEDgIGB/4C+gISBm4CWgYaAj4GfgO+BAgIEACC5uru7urm3uLa5wb69v8XKzM3Lzs3Jx8PBwL+8u7u8vIS9N7/Av8DEx8jJy8vLxcC9ube2ubq4t7i7vLu8vLq3s7Czs7GsqaafnJmYmZmanJydop2cmZiamZWEk4CSkZCRj4+Qk5eYmJibnZqcnaKhqKersbKzwMTQ5PXu5NvRspiYpKKwtLSsqpKKjY6apKemn5iXpKmtsK2rp6Wip56jr7WroaSipaOagmx5h4mDgYSGiYF+fXiBiH95fH14bGZhdXt8fndta3F7+u7p3crCu7KsrLK7v8TDwsK2pICemJifo5ubrsfOxramp6GcmZiYm6GWjoqIi4+Njo2IhoKAfXuFioyQkI2MioWGgHt7enp7fYeMh4B7eXl4e3+CgoOEhoeNkZCSkpOQjYqHg4WGiIuRl5eTjYqIiYiCfHZ1dHJycXFvbW5ubmxpaGpvcnFsamZnaWhlZWVkY2RnaBBoZmVlY2FhX19eYGJkYmJihGNKYV9dXF1fYmVmZmVkZGRjYWBeXV5eX2BjZWZkZWRiaG1saWhraWdpbW1raHB3eX1+eW5lb3N1eHp6eHd0dXl5eoGDgoKBe3Vzc3GFchJ0dnZ2dHN0dHR1dHN1dXd2c3SFcwF1hHYJc3FycnFxcnN0hHeAdnd5eHl4dX2Bfn5+PnVzc25wdTw9ej5APTx1c3J3enV2dnVycnM6OnV4en18fYuLg4I/fo6dm0o/Pj4/QkI/PDw9Pj4+PT89PD0+PTs6Oj4/QkRGSklISkpMS0hCQD09PTk4NzY1NTY5QD5APD5ARURDQUVDQ0FDRUZGRkVBQEB6QUNDQ4qQlaWilYCFkaGossPGw76/wMLAurSytbavq6mooZqUjpCamZiboKGThoSCg4iMhH+Egnp1cnBwb3FzdHN2fH6BiJCRjpCLeHN2eoGLiYB0eYB8dnBrbWxqcXFwbGdjYmNjY2dnZWRlZGJjZWdoaGZmZmhqaGeEZnBnaGdnZmZmaG5wcnV4enl4e3p5e35/goCCg4WFfn6FiI2QlJmanqKlp6qsrrG1t7m8v8DBw8XIycvOztHQycdlZmhjY16zsbCmoaSlpKKfnJuRkZCOjY2XmJ2fpK9aWVldZWlqaGJjYl5dYFmcmpqYhJdXlplNTkyTk49KTk1OTk1LR0hMT1NHeXl5eHl3cnN4d3RzcnNzdnd4d3d5e35+fH1+f3+AgEBBQUNDREVFRkdHSUpLTE1NT1BRU1RVVVdXV1laXF5gY2RmhGiEaURqbXFwcHJ3f4OEhISGi5OUk5KMh4iLkJOYoqWjo6KlpaSfo6mtrqmnpaajpKmurbGysbS3ubu+vLi3tbO0tLW2uLy7u0zBwsPEw8HAwcHCycfGyMvQ0tTT1dXSz8zJyMjGxMXHyMjHyMjJysnL0NLS0tTW1tDMyMjEw8bHx8bGyMnHyMnHw8C/wsG/vLm1sa6rhKoLq6ussKyqqKiopqSEoYSggJ6enqGjo6Slpailpqerqa2srrKwsLe6wMvVz8jCu6qbmZ+epqippKKUjpCRl52fn5uWlJ2goKOhn5yamJ2WmqCjnZeZl5iXkIJzfIWGg4GCgoR/fXx6f4N9eXx9eXFsaHZ5ent2bm1zefTq5d3SzcbAv8HCyM3PzszKwLezsLG1dbexsLzN0MnAtLezsK+trK2wq6ajoaOkpaSjoJ2bmJiYnqCipqWioqCdnpuYmZaVlJadoZ2XlJSUk5aXmJiampycoKKgo6OioJ+cmpeYmJqbn6KjoJuZmJiZlpGNi4uKiomJiIeIh4eGhYSEiImIhYWCgoKBf4SAb4GCgYKCgoCAfn58fX5/gICAf4CBgYCAf359fX1+gICBg4OCgoKBgIB+fn9/f4CEhYSDhYSBh4yKh4SHh4WGh4mHhYuOkZaVkImFiYyPj5GSkZGOjpCQkZWXl5aVko+Ojo2Oj46Ojo+PkJCPj5GRkoqThJIOk5KTk5SUk5OSkpSVlpeFmUacn6Cgnpqhp6Oio1Gdm5uZnKBRUaJSU1BQnp2bnqCdnZ6dm5yeT0+goaKko6Wtq6emUaOwuLhaU1NUVVdXVFJSU1RVVFJThFIMUVBQUFNUVldZXVxchF0dWldWU1NTT05NTExMTU5SUlNQUVJWVVVTVlVVVFWEVwRVU1JShFNnqq6wtrSsoaOqsbe8xMTDwcHBw8G9uri5uba1s7Kuq6ilp6qqqquuraadnJucnqCcmJyblpSRkJCPj5CQj5KXmJicoaKhoZ2TjpGSmJ+clo6Sl5SQi4iKiYmOj4yKh4SFhoWEhoWEg4SEEIWGh4eGhoaHiIiJiIeHhoaEh4CGhoiNj4+Rk5SUkpSUk5SWl5mWmJqbmpiYnJ6ipKerrLG1uLi5u7y/w8bHyMzNz9HS1tfZ3Nne4NnYbnFzbWxpy8fFv7u9vr26t7W1rK2sqaiqtbO3usDJZ2Rma3N2dnRwcXBsbG5murm6t7a3t7a2ul5eXLW1sVtfXl9fX11ZW1xeYWNZn56fnZ2clpienJmYmJqam5ydnp+foKKio6Skpqanp1RVVlZWV1hYWlpbXF1eX19gYWJkZWZoaGlpam1ub3J0dnZ4e3t8e3t8e3t9f4KCgoWJkJWWlZWYnYSiMpuZmZufoqews7Kxr7Kzsa6xt7i5trSxtLCwtbq5vL27vsDCxMbEwL68vL2+vr7Aw8TBG9DQ0tTT0tPT09TX1tfY2t3h4N7f397e3dza2oTZgtuE3Cnf3+Di5Obn6Ojo5uTj4+Lj4+Tk5eTk5eXm5uXk4+Lj4uHf3dza2NbW14XWF9jV09DPz87NzMzLysjFxcbGxcXGx8bGhMVNxMXFw8PCv76+vLq5t7a3tLGvsK2sqamoqKeop6elpaWkpKWko6KioaGhn5+dnJudnZ6cmZqamZiWlpaVk5GPkZKTkI6Pjo+NjYuKiYqFiUOGhIOFh4eFgoKAgID+/P36+Pj19/n49vX29/b18+/y9fPy9PHx7/Hs6uvn6Onp6Ono6Obn5eTj4eLi5OTj4eLh4OHhhONp4t/e3d7g397d29va2dfV19XW19bX2NjZ2NjW1tXW1tTV1tTSz9DQzc3Ny8vMzMvJysrKycrHxcTFxcXGxsXFxsTDxMPCw8TGxcTEwsHCwb/BwMHBwMDAwcHBwMC+v7/AwsLDw8TDxMTGhMUyx8bGxsfIx8bGx8fGyMjJysvKycvNzczNzczOzczPz87Pzc/Pzs3MzMzOycrNzc/OzcqEzArO0M/PzM3NzMrLhM8Fzs3P0dCF0QzQz9DQ0dPU1tfZ19aE2ITagNvc2tjY29vd3t7f4OHj5ufo5uXm5ubr7u/w8/H4/fv8/oD6+fn7/P2AgP+AgYCA/v37/fv+/v/+/v7/gYD////+///+/f7/gP7+/PyAg4SFhoiIhYOEhYWGhIODgoKCg4KCg4OEhIWFhoiHiIeFhYSFhoaFhoaCg4OCgoKDgoSFhYQMgoOCgoOCgYKDgoGChIGEgIT9J/v7+PXz8e7t7ejm5uXm5+Xj5OTl5ubn6Ojn6Ojs6+Xl5uTi4ePk4YTgYd3d3eHh4d7d3t3f3dva2dnY2NnZ29nY19PQ0dDR1tPR0NDR0NDPztDQz9PT09HOz8/Qz83P0NDPzc7Ozc3Ozs7Mzs7MzMzNzMvKy8vMy8rMy8vMzMzNzs/OzczMy8rLzMuEzIDOzs3Oz9DU1NXa2t3d397f4OHl5+ro6uns7fHz9ff5+fr+//39gYSFgoKA/vz79vPz9fX08O3w6Orr6efq8u/z9Pj+g4OEhoyQkI+Ji4uJiIqH/vz8/Pr8+vr6/4CBgP79+4CEgoSFhYOAgIOGiIDz8fLx8/Ht7vTy7+/v8fHy8zD19fb39/f5+fv7+/z+/4GBgYKDg4SEhYaGh4iIiouMjY6PkJGSkpOUlZaXmJubnZ+EoUmgoaKho6OlqKmqq6+ztLW2t7m6v7+/vrm4uLu8vsPJysjJx8rLyMXHyszNzMrKysjHys3Nzs7P0M/Q09TT0M/NzM3Mzs7P0dHQ/4HNgf+A/4CtgAGBhoADgYGAhIGMgIKBioABgYSAzYH/gLqAhoGYgI+BioAGgYGBgICAjYGfgO+BAgIEAIDFwsHCxL+6ubi5u7zAvr6+v8LCxMTIzcrHxMS+u7u+vry8v8HAwsbL0NPX2tva19LLwb69vby5uLa4trW2tbO1uLi2tbS0s7Gvq6Win52bmpycn6Cen6Skp6GZmJmampiblZORkZCQk6HP2tXEpJ+irLeyqba4v9Tl693q7ePJu4C6taycpre8uraqopySkZaZoLjHv7exrqqossS5qaukmpuqsrixqKqlo6Geg3R5enyGiomDgn97d3h8fXh4d25ucXV2hImBenV3eX+Bfenf49rKxsS6s7a9xriyvse6qKagnp+jqLHX8e3d08KqoZ6hp6ijoJmRj5OUk5GQi4N9eIB5e3t+hYaJjYuIhICAgYCEhouSlJWamYt8eHVzen+Ag4WIjY+TkZGPj46KioiIiY2PkZKWmJualo2FfHx8eXZzc3Fwb3Bxb25tbXBvbWpuc3Jva2xsbWtoZ2VlZmlsa2tpZWRjY2NkYmRlZmdmYV9eXV1dXl1dX2JlaGtramxraEVkY2BfYWFiYWJjZGZkYmJjZmlpaGltbG5xcXF0d3d6dnR4dXFwcHBtbXF7enl3dXV1dnmCg399eHNvb25ubG1xcXNzdHSHdQN2dXWEdit1cnFycXBydHRzdXNxcW9vbW9wc3V2dHV1dnl7e3p0d0A/QEA+ens9eHk+hD8rQD8/PTt1eD17PkJFQz48PDw9Pj8/QUFCQ0VIT6CpWVqvUkdAPj4+Ozo6O4U+BD09PT6EPQg+QUJCRUpJSoRLFUlFPz4+PDk5NzY1NjY2OztHSEE8NoQ4Aj0/hUEaQkBAPz8/QUJBiI2SnJqYj4uUpqqttLm9u7iEuoC7ubm5tK+qqKajn5KTmp6ioqmsoJCJiYuLjoWEjYyCe3RxcHFydXd1eHx+gYeIiI6MhIOBgoGDhYOAj5GMfHR0cXJxcXBxb2xpZ2VnaGhqaGdoZ2lramtpaGdoaWpqbGxraWhoa2hoZ2lqbG9wcXFzc3R2eHuAfHx+f4GCg4mIiSKLioeJi42PkJOXnKGkp6qtsbK1t7q9wMLExMTIy8nLzc3KhGSAYV65uLi2raCeoKShnZubk4+Lio6PkpOaqFphZGJhX2FiZGVjZGNfWVhXUZKZmpuZlZCNkJGUk0qSkZCNSElKSkpJiohHUVJLhYR/eXt5dXR1c3JzdHV2dnZ3eXl7e3t9fX5+QIBAQUFCQkNERUZHR0hJSktMTU5PUVJSU1RWV1dWWFxeX2BiY2RlZmdnaGloaGprb3R4eXp9fYCDhIWFh4qKj5GUlJWcnZ+cm5yko52bnp6ho6Sprqytrausq6epr7O0tbS1t7e5u7u8vLi3trS3ubzBxsZOy8nJysvHwsHBwsPFx8XGx8fMy83O0NPQzcvNysfGyMnHxsjLysvR1tnc4OHh4N3b1c3KycnJxsbExMXGxsTCwsXGxMPBwsG/vbu4tLCthKyArq+trrGxs6+ppqWmpqaoo6Gfn56foKjEzMe9qqeqsLazrbS2usbQ08rS0cu5sK2qpZyhrK6sq6OemZOSlZaaqbOspqSin56jr6ieoJuWlZ6jp6KcnpqYlpODen1+f4WIhYGBfnt5eX19enl3c3JzdnZ/g315dnV3enx56uLi3tSAz87KxcXL0sjFzNDGurq3trW3ub/Y6OXZ0se4srGzt7i0sq2ppqipp6WkoJ2Zl5iZmZqeoKGioZ+dnJudnJ6cn6OjpaimnpiUko+Ul5eZnJ6goKGhoqKgnpycm5uanZ6goKKjpaaim5WRkZGPjYqKiYeHh4mIh4WGiIeHhYiMiYczhYaHhoSCgYKBg4WFhoWDgH9/f4CBgIGDgoGCgH59fXx8fX59foCChIWFhYeGhIKCgICBhIIhg4WFhIODg4WIiYeHiYiKjYuKjY+OkI+OkpCNi4yKioqNhJIfj46Oj5GXmJWVko6Li4uKiYuOjY6Pj4+RkI+QkZGSkoSTCJSUlJOTkpKThJSAlZSTk5OSkZSWmJqbmZmYmZ2foKCbn1NSVFNSoaJRoKJSVFRTU1RUU1JQn6BRpVJUV1VSUVFRUlJSU1NUVVVXWV26vWJiwV1YU1JTU1FQUFFTVFNTU1JSU1NSUlNTVVdXV1ldXF1eXV1cW1hVVVRTT09PTk1OTk5RUVhYVFFNTk4GTk9SU1RVhFQ+U1JSUVJSU1Ooq62xsa+ppqy1uLq9vsG/v7++v729u7y7ura0srGvraalq66vsLOyrKWgoaKfoJyboKCZlpOEkYCTlJOUlpeYm5ycoaCamZeXlZiZmJagoJ6Tj46MjYyNjpCOjIiHhYiKiImIh4eHiIqIiYiIiIeIiomKioqIiIeIh4eHiImKjY6Pjo6PkJKTlJaUlZeXmJmZnp2dn5+dnqCipKeprLG0t7m7vb/AxMfIy8/Q0tTT19nY2dzc22xub4BwbGrRz87LxLe1uLu6tbS0rqqoqKmrrq+1wGZucm9ta25xc3Nxc3NtaGhnYbO5ury6tbGvsrO3tVu2tLOvWltcXV1br69aZGRerKikoaGfmpibmpmampyen56dn56goaKjo6alU6hUVVVVV1dXWFlaW1tdXl9gYWJjZGRmZmdpaldqa25xc3N0dXd4eXp7e3x8fH5+gYWJjI6PjpGUlpiXmZyZnqGlpKasrK2qq6uysayrrq2ws7S2uri5ube3uLW2ur2+vr6/wsLDxcTExMDAwL+/wMLIzMw61tXV19jV1NTT09XW2NfY2Nrc29ze39/f3t7d29zc3d3c3N7f3uHk5unq7vDw8O/u6+bl5ubn5eTj44nkEOPj5OTi4uDe3NrY19bY2NeE1hbX2NXRz8/NzcvLysnJyMfGxsjJycrIhMZMxMPDwb+9vLy8urq4tbGur6+vrausq6qpqKipqKalpqSmqKeloqKhoKChnZyfn52cm5ycm5mbmpaUlJKSkpGSkZGQj4+OjIqKiomJiYSIE4eGhoeFhIOBgoGAgP7+//76+PeE9oD5+ff39/j2+Pb18/Hv6+3u8O/q6evq6unp5+bl5eTj5OLh4ODd3+Dg4eHi4+Lf3t/g3t7g4eHf3dzb29nX19fV2djW2dnZ2NnY19bX1tTT0tLR0NHQ0M7O0M/Ozs3Ny8nJxsfHxcTExsjHxsbFxsbFw8PEwsLCxcfEw8HBwMHAwWPBwMDBv8HBwsK/wL+/wMDBwsLDw8TDxcbFxcTFxsjHx8jHx8fGx8jJycrLzM3P0M/Ozc7Oz83Ozs/Pz87P0dDPzcrLzczKys3P0c/MzM3MzMzO0tLT0c3Nzs7M0NHPz87Q0dKE0RbS0dHR0NDP0tHQ09PU1dbW1tTV2tvchN4839/e3t/i4+Ll5uXo6Onq7Ovr7O7x9Pb69fmAgIGAgP7/gP39gIKEhIOEhYSDgf//gP6BgoGCg4OCgYGBhIABgYSAOf//gID/gYKDg4WFhISEhYaGhYSFhISEg4OEhYaHiIeGiIqHiIiHhoaHhoaHh4aEhIODg4WFhIaGhYSEDYWEhISDg4KDg4KDgoKGgRiA/v/++/r6+ffz8O3r6efn5uXm4+Lh4+SE5mPn5+fo5+zr5+fn5eTl5ebj4uLh4ODh39/i4d/d3d7c29rZ2tnY19nZ2NrX1tTR0dDR0dDR0tPT0dDRzdHS0tLV1NLPz8/Ozs7P0NDPzs7Pzc7Nzc7Nzs7Oz8/Ozc3Lzs3Ny8qGzIDOz87NzM7QzM3Lzs3OztDO0NLSz9DR1dfX2Nve3+Dg4OHi5Ofq7O7v8fT39vb5+fr9//+BgYOEgYD///779u/t7/Lx7/Dv7Oro5+jr7u71+4GGiomIiIqMjo6Mi4yJhoaGgfn7/f79+vXz+fv//YD+/v79gICBgoGA/f6AiImE/zT8+PX09PDt8PHv8PLz8vL09vn6+vn5+vv9/oD/gICBgoODhIWFhoeHiImKi4yNjpGQkZKShJQIl5mam52en6CEoUmioqKlpqaqra+xsrKztbe3uLm6u7y+wMDAxcbGxcXGysrGxcbHxsjJzM7Nzs/MzczLy83P0NDP0NDQ0dLR09PQ0NDP0NLT1tjX/4HOgf+A/4CogIWBBYCAgYCAioEEgICBgJOBBYCAgYGAzIH/gLiAhoGXgJKBjIABgYSAhoGCgISBm4ACgYDxgQICBACAzMvJx8jKyMPDxcPAxsnJxcLFxsbExcXHxsPCwcC+v8C/v8DFw8C/wcfP1tja2tjP0MvCvLu9vb28ure2tba2tLKzs7W1s7W1s7Cuq6elpKOjpKGgnp+ipKirqqiimpqamZmZl5SSkpOYrMXih4i0qKiqq7DL1MDH2drVx9DW0M6Ax7yzr6+zvsDArqi0u6ulqLzAubKorq2pr8TLu6y4sKmmrsXBsqagmJKJi4KEiIN/gYaDenJwcHF5eXp2dm5jdoSCh4eBdnZ/goKCgHxycXZxcdna2dDEwb21trutpJubnp2dnrXT5N/Rzs/Ftamjpq+xpZ6an6avsLKunpWKgXqAfIGCgoKGhYiHh4eIh4iHjZykpKKYmpmQiIF7enp+goeNkZORjo2Nj5CPkJGQjZCTlJOTlpeXl5aQiH15eHt8fHp5eHd2eHt7dG5tb3BvcHFxbm5zc29samloa21tbWxqaGZlZ2hoaWppZmZmZWFfXV5eYGJjZGVmZ2psb29ubmuAZ2VkZmVlZGJjZmlpaGhlZmltbW9zd3Vyc3l6fXt9fHZzdXBwcXFycG5wdHR2dnV0cnJ1fX9+fXp0cnFwbm1ub3FzdHN0c3JzdXV1dnZ2d3h5ent9eXd4d3Z1dXVzcnJxcnBta21ucHR1dHRxc3R6fX91cz4/REVDQEBCQD48PT9lQD9AQUFBPj08PT0+QENDQUFGRUdFQEFDRENBQENQVU9LSEhKSkVCPzw7Ozs6Oz0+PT4/PT0+Pz9AQUFCRERHS0tLTEpISUdFQkE+Ozo5ODY2NzY3OTs/R0Q6Nzc7Ojk7PT9APz6EP3g+Pj9Bg0FBhUabpaKbmJidoKKprKmrrK+vtMG/ubi2sK2tra6spqCeoaSmqKyxq6CVkZGWlJCNlpSHgX55dnV2dnh3eXp9hIeHiYR/i5aQkpmOgICDi5CQjImDgIGBd3V1cXBvaWhrbW5ubGtrbXR5eXRwcHBubW2EbIBra2pqZ2drbW5vcHFxcnR0dXZ3eX1+foCAhIaHiIiJjYyKjI6Njo+Um5yipauvs7S2uLe6vb2+wcPFycrIxMPAv8FkY2FhXl23t7GnpaOjpaCdmJCNjZCNjpKamZSZplZXV1hbWlpZWVVUWFtZVFFOmZmUko+JhYKEhoaBf4KISIBNT1JPSklNUU9QVVZJg4SDg355dnRydHV1dXR2dnZ4eXp7fH1+f4BAQUFBQkJDREVFRkdJSktLTExPUFFRU1RVVVZXWFlbXF5gYWNlZWdpam1ucHFzdXR1eX2DhYaFhYqJiYqNjpSanZyepay3uK2hmpmbmZSUmZyhpaepqKutrhu0tbKoqKyyubu9u7m6u7u+wb25uLe1usDDyMsR0dHPzs7QzMnKy8nIy83Ny8mEzQrOzc/Oy8vMzczLhMoQzczKys7R2eDh4d/f29vVzYTJgMrIx8bGxMPCwcDCwsPDwsPCwcC/u7e1s7OzsrCvrq6vsLO2tLSuqKiop6ampKKgoaGjr73PdHS0rq6usLTCxbm/yMnEu7/Cvby2sKmmpamusLCmoqmso6ChrK+qpZ6kop+ksLKpn6iin5ygr6qim5eTj4eJg4WIgoCBhIJ7dnV1EHZ8fHt5eXJreIKBg4N9d3eEfYB7eXJydXFw3d7e187NzMfIxr66s7O3tbG0w9Xg2tLOzcnAuLW2vL22rquwtLq6urWvqKGcmZmfnpqbnp2fnp2doKCgn6KssrGvqauoopyalZSUl5qdoaOko5+en6GhoKGhoJ6foqOioaKjo6Oin5iRj42OkJCOj46MjI2Pj4qFhB6HiYiJi4uIh4mKiYaFhIOFh4eHhoOCgoGBgoOCg4SEg1yAfXx8fX+BgYKDhISFh4mKiYmHhYODhYSEg4KEhYeIiYeEhYeMjImNkI+MjI+QkpGTko+OkI2Mi4yMi4uLjo6QkI+OjY2QlZaUk5KOjY2Mi4qLjI6PkJCQj46PkIWSB5OTlJaWmJiIlYCWlZOTk5KRk5SUmJmZmZeYnKCjpJydUlNYWFZTU1ZUVFJSVVVUVVZWVlNSUVFSU1RWVlVWWFdYV1VVVlZWVFRWXmBdWlhZWVhXVVNSUVBRUVJTU1RVVVNTU1RUVVZXWFhZW15dXV1cW1xbV1dXVFFQUVBPTk5OT09RUldXUE1NUAdPT1BSU1RThVKAUFFSU6dTUqZVsbe1sa6vsbO0uLi2uLe4ubvDv7y6ure1trW1tbKvrrCysrO1t7SvqKWkpqWioqemnpuZlJWUk5OUlZaVlpucm5yalp+loaOlnZWVmJufoJ+dmZeYmJSSlI+OjIiIio2MjYyMiYuOk5OQjY2Ni4uKi4uMi4qJiYo+h4eLjIyNjo+Oj5CQkZKSkpWWlpeXm5ycnp6foqGgoqOio6WqsLK0t7y+v8HDxMTHzM7P0NPW2NjX09PR0dSEbIBqas7Lxr68vLy9uLSwrKqqraqrrbKxsLS/Y2ZmZWhpaWhnZGRnaWlkYV67urKwrqunpqaoqKKgo6pbX2FjYVxcYGFhY2ZnXKuqqaqjnZuam5ucm5ucnp6foKChoqKkpaWnVFRVVVZWV1hYWVpbXF1eX2BhY2RkZWZnZ2hpamttbVVvcXN1dnh6e3x9gICChIeHh4iLkJWYl5aXm5ubnJ6fo6qsq620usPEu7CrqaqppKSoqq6ztba2uLq7vsC9trW4vMTGyMbExMbFyMnFwsPBwMPIy87QAduE2Rnc29fW2NXW2dvc29vd3Nzd3dzf393e3uDehN0W3N/g4OHj5urv7/Hw8O7v6ujm5ubl5ofkJ+Pi4+Lk5OXl5OTj4+Hf3dva2drY1tbU1dbW2dnW08/Pzs/OzczKyYTHbMnKZWXIxsXEw8XEwMC/vry7uri2tLOxsbCvra2sraupqqqrqaeop6ilpKOkpKShoaGenKCfnp+eoJ6cm5qYlpWUkpOUk5STkpGNjo6Mi4yMiomJh4aIiYeJiYaEgoSEg4KBgYCBgoCA/v36+4X4gPf5+vj4+Pf28vLv8O/t7ezq6unn6ejm5OXi4+Pi4uTh293f4uTi4+Lg3t3b3Nzd3uDf4N7c3N7e3tra2tja2dnc2trZ2trZ2NjW1NPT0dDR0c/Nzs7Pz87OzMzLy8jJyMXCwsTFxcTExMPFxsbFwsLDxMTGxcTCwcTDw8HBwsLCOMTBwsPFw8DAwcHAwcHEw8TExcTGyMfHxsfIycrLysrIx8fIysrLzc3OztHRz9DQ0NHQ0M7Pzs/OhM8LzcvMzc/Q0dHQztCEzhfQz87Nzc7Pz83O0NDO0tPR0tDQ0NLS04fRCtPT09LQ0dHT09KE03fV1tnZ2dzd3t3e4OHj5Ofn6Ofo6evu7u7v7/P1+Pz/+vqAgYKDgoKChIGDgoOGhoWGiIiHh4WDhIOEhIWEhISDhISCgYOBgYGCg4SDgoGBgoKAgIGBgoODhIWFhoaGhYeGhYSGhoaHiYiJiIiJioiHiYqJiYmHh4SIFIaFhIaHh4eGh4eHhYaHh4aFhoWFhIQjg4SDgoKBgYD/gID/gPv29fb08/Lw7eno6Obm5uPk4eLi4uOF5mnn5+np5+fo6Obj4eXl5OPk4eDf4eDh4uPe39/c2trZ2dnY19jY2dfU19jU09PV0tDQztHU1dXS0dPT1NPW1NPU0c7P0NDRz9HQ0dHU1NLQ0NHQzs7P0NDOzs3Nz8zMzczNzc3Ozs/Ozs6EzYDMy8zOzs/Q0NDS1NTS0tPT1dja39/g4uPk5ePk5ejr7e3v9ff49/r6+Pf3+v2BgoGAgIH//ff08/Hy9PDu7Oro6Oro5+nw8e/z/YGEg4SFhYaEg4GChYeHhIKA/f35+fbw7e7w8/Ty8vX5gIOFh4WDgYWGhoeKi4P+/vv79/Tz8IDx8fLz9PPy8vT4+fn6+/z9/f+AgICBgoODhISEhYWIiYmLjI2Njo+QkJGTlJSVlZaYmpqbnJ6goKGjpKalqKmrrausr7K2uLi3t7q6uru8vL/DxcXFys/U1dDJxsXHxsLBxsbJy8zMzM7Pz9PT0s7MzdDU1dbV1dTU1NfZ1dLU0wbR09bY2dvsgYKC5IH/gP+Ao4D6gQWAgYGAgf+AtoCGgZeAkYGPgI6BmoD0gQICBACAysvKysvLy8fFxsbIy8zMzc3P1tnVyMPCwsTExMLDxMXFxsfGxcXEwsTFy9HW2uHg19POysbDwsG+vsC+u7q9t7S0tra5tra1tLS7sK2rsK+wta+oqqysrqqkpaiwpJ6enZucnJyYlZSVlJuotrSywdPJ1+jl4sjBv7qyrL3g4+WA2c3Qy7u30dbSya6kqratsMLJuqyorre6uKyytrq7sKeqqqy3sJqIh42YlIyRjXt1eoCEh4Z7eHp8enp9fWxrfX+Hf3Z9gYGChYSCfnlwcm9vcHB16s/Cwce+wMW3o5qYm5u2zO/y7OXTzru9vbmtsLa+u6qqrbO/uq+rn5iMg3+AgYeKjoyLiouLiYSDhYmMiYmOkJCSlpaPiIKBf4GGiYyQlZaTjYmJjpKSj5OTkpKSkY+OkpORkI6JhH97fH19foB9enp6eHl6dnFwcHFvbm9ubnBxcW5sa2tsb29uamdnaGtvcnBtbWxraGZkY2NhYWJjZmdoa2xtbm9wcHJxb2yAaWhramhnaGlra2hoaGlrbm9wcXB0eHd2dXl9fXp8e3lycHBvcXR2c29tcXN1dndzcHFzeXp+f3t4dnV2dHFzdHN0dnd2dnh5eXl6e3p6fH18fH5/fn18e3l5ent4dnVzcnFxbWlpbW9yc3RwbnJ5foF/ez9ARUtHQ0BCQ0E+P0AIPj1AREZFQ0CEPoRAgEJDRUlIQ0JCQkNCQkBDRkVDQT9BQ0NCQkA9PDw8PT4/Pj49PT49PDw9P0BDRERHSUdISEZGRkVFRkU/PTw6OTg4OTc4ODk3Nzg4Nzg5OTc4ODs9PT48PEBGSEhHRkNDgYKNoKiun5aWlJacp6ulpKSlpqmssbGvrKusrauurKigd56goqSorK+urKeel5qbmZmbkoqGhYF7enl3dnZ3eX6Fh4qJf4CKkJiblIKKl5SKjpKPjImCgXt4dXRvb29ua25ycnFxcG9xdHl8eXRzcm9ubm1sbWxsa2poZ2hrbG1ta2xvcXJzc3N0dnl7gIKChYiJiYiHioqJhIp+jZOZn6Wpr7O3uLm7vcDBwcLExMbJycjGw76/v19dXLe0sbOqpKejpaGjm5iUkpCSmZmVjpadnJugpqlWVllYVFRUVVheYlpXUpmRioeKhYaCfHp7e35/ho5KSUpGSEmKRkdMT0tFh0NJSkR7d3Z0cnN0dHZ4eXl6eXt7fH1+hEBzQUJCQ0RFRkZGR0lKS0xMTU9QUVJUVFZXV1dYWlxdXV5hYmNmaGpwcnF1eX1+f4aGhYODiI+TkpKUm5ycnqW0t7e3uLvBwL65rp2dlpafqqiopqWipKestrvCxL66urm4usDEw8C+vru8v7y6u7q7v8PIyh7P0dDPz9HSzszMzM7Qz8/R09XZ3tvSzMvMzc/Pzs+E0IDR0NHQzs3R0NTZ3eLo5d7c19PRz83Ny8vNy8rIy8bEw8TFxsTEwsLCx7+8ur29vsO9t7e4uLm3s7K1u6+rqqupqaiopaSjoqKnrrS1tb7HwMnR0My+urm2sKy2ycjLwbu9uLCsu8C8t6ago6ylp7G0q6SipKqrqqKlqaqpop2en3ehqKKViomMkpCLjYp+e32ChIaEfXx9fnx8fX1zcXx+hH94fYB+foB/fXt3cXNxcnFxdOrYz87Ry8zPx7iysrW0xNHn6OPe09DExsXDu7y/xsK4t7e8xcC5t7Gqo56cnaGho6KioaKhnpydn6GkoaGhpKSlqKWhnYSaD5ueoKSnpaGfnZygoqKfoIShVKCen6GjoaCdmpWRj5CSkZKUko+Oj46PkY6KiYiJiYiJiIeIiImIh4aGhoeIiIaDgoSGh4qJh4aGhoSDhIKBf4CCgoOFhoiJiIiJioqLi4uJh4SIiISHNomIhoaIiIiKi42Mio+RkI+OkZSTkpSTko6NjYuLjo6Pi4yNjI6PkI6NjY6RkpeWlJKQkJCOjYaPBpCQkpOTk4eUApaXhJhSl5eYmJiXmJaVlJSSkZGQk5aZmZmXmJyhpaimolJUWV5aV1VXV1VTVVZTUlZaXFpYV1VVVFVVVVZWV1hZW1pXVlZXV1ZWVVdYWFdVVFVWV1ZVVIRSBVNUVVVUhVMPVFRVV1lZWVxdXF1dW1tchFqAVVNSUVBPT1BPUE9QT09PTk5PUE9OTk9RUlJRUVBTV1hXV1dVVaWmrba6vLStrqussLe5tLKvsbS2uLm4trW0tba1tra0sa+wsbK0tre2tbGsqKqqp6iqpKCenpqXlpWUk5SUlZeanJ6dmJeeo6iopJWbo6OcoaShnp2Yl5SSkJApjI6Oi4qNkZCQj4+MjpCTlpWRkZGPjI2MjIyLjIqKiImJi4yMjIuMjo+FjoCRk5SXmpmcn6Cenp6hoaGgoqGipKqvsra7vr/Bw8bIyszO0dLU1dbX2NjX1dLT02loaNDMycu/u767vby9s7GvraywtbOwq7O4tbW6wMRlZGdnY2NjZGducGpmYbmxqaappaamop2enqKjqbJbWltYWluuWFpeYV1YrVZbXVehnoSagJycnZ6fn6Cho6OkpaZTVFRVVVZWV1dYWVpaW11fX2BgYWNkZWVmZ2lqamtrbW5wcXN1dnZ6fX6DhIaIjI+RkpiZl5WXmqCjoqKjqqysrLPCyMXDw8XLy8vEuqutqKatt7W3tLSys7W6wcXLzcrGw8PExsvNzMnIx8XGysfExcLDBMbJzc4B2ITZEdzc2djY2dvc3d3e3uDj5eTfhN4039/f4OHi4ODg4ePj5ebn5ejs7vL29PDv7erp5+fo5+fq6efm5+Tj4+Pk5uXl4+Tk5+Pi4IThgN3b3Nvb2tjW1tfa1NDQz87Oz83MysnJyMfKycnIycfFxcTEw8HAv768u7m4uLa0sbGxsK6srauqq6urrKmpqaunpKSlpKSjoqOhoKCfn6GhoKCempiXmJqXlZaVlJGRkpGQkJCPjo2OjIuLiYmKiYqJh4eHhoSFhIOCgoGCgYGAaYCA/f77+/r49ff6+vv6+Pf29vX08e/r6uns7Onn6Ojp5+bj4uLj5OHg3N/g4+Xl4d/e3t3a3N3c3dzf4ePf3+He3Nvb2dfY2dzc29ra2dfX1tXU09LR0M/Pz83My83Ozs3Nz8zLy8vJx4bFgMTDxMTCw8TExMPDxcbHxsXFxsbFxcPDxMLCw8PDxcXEw8PDwsLDxMTHyMfHyMfHyMnJy8rKy8rKysjJysrKy83O0NDS1dXS0NHT0tLPz9DOzs7Nzs7OzMvLzM3P0NDQz87Nz87Oz9PSzs7MzM3Oz8/Q0NLR0NLR0dHQ0dLQ0dHRSNLQz9HS0tHS0dDR0dDQ09LT09PU2dnZ29zd3eHi4eLm6Oro6urr7/Dx8/H0+fz+//79gIKDh4aEg4SDhISFhoWEh4mKi4uJiYSICYeHhoaHh4aFhISDhYQVg4KDhIOCgoGChISFhYeIiIeHh4aFhIYSh4eIiomJi4uJi4uKioqJiIiKhIkEiIeIiYWIA4eIh4SGAYeGhVCEhISDgYGCgICA//7//fr49PLz8O/t6+vr6OXk4+Lh4eDh5OTl5ubm5efr6ebm6Ofl4t/h4uPi4eDh4d/e4uHf3dvb3Nrb2tjY2NfX2NfS04XWFtTT09PQ0tbU1NPT1NXV09PR0tTRz9GF1CvT1NTT1NLR0tTT0dLR0dPQz8/NzczMzczNzs3Ozs/NzcrIysvKzMvNzM3QhNGA09TW1dbU19re4eLi5Obn6Ofo6ert7/L1+Pf4+fv7+/r5/P+AgID//fn69fD28vTy+PDu7u3s7fHx7ezx9fL0+f3/gYGEhYGBgoOEiIyJhYT+9vHx8uzt7uzp7e7y8/f9gYCEgICC/4GChIeEgf+Ag4SA+fT08/Ly8/T19vf3+PkF+/z9/v+EgHOBgoKDhIWGhYWGiIqKjIyNj4+QkJKSk5SVlpaXmJmbnJ2en6Cio6eqqqyvsbO0uLm5t7e7vsDAwMHExcbHy9HU09PU1tjZ2tfQx8nGxMbOzs7My8vMz9HW2dzc19XV1NTV2NnZ2NjY1tbY1tXV1dbW19nZ/4HVgf+A/4ChgPyB/4C5gIOBnICOgZCAhoEBgIaBAYCEgZOA94ECAgQAMdDOysbHycnKyMnHx8bGzdDV3+Tl5tjJyMXEyMjIxcbMzsnMzM3RzMrLzcvMy8bIzdCE1IDS0M/NzcrIxsbazry5uLi4t7q5trS1s7Gwsba6urm0tba3ur2zraqrqaOgoJ6eoKGdnJqYlJSWmaPG69rm8YGE79jEvMvBtLnS7N/UzNPRtcTV3s+vrKajt7rJ2tbAt7mzqqeilY6Nnq6wpKGcn6GPgYCGgn2Ci4Nybn2ao6alnYCWjYiCfXFuamtve3t5eHyAgYuRgXt2cWtpZGJkZmpwZ8jW8IB84tTAsKCcobDH2N/m7+7i1sW/vLmytLzBwLa2tbWyq6ignpyRiYSHjZWTjYuNi4iHhIODg4eKioeBfX+Eh4iJhYWOlpqXl5SXmpyWlpWVl5aRk5GQkZOUlI6MiYCJiISBgICCf3t6e3x9enh4dnR1dHJycnR2dnNwb3BxcW9vcXFycm1samtvc3NzcGxra2tqaWZjYmRmbXFzdHFub3BwcnJzcnJwcG9ucG9ubWpsb21tbW9tbm5wcXJ0dXN2eXt8f35+fHx7fHd3dXZ1dnh2d3ZzeoCAgHt1dXZ3e1J/f357e31+fn59fHp5eXt7en19fH1+fn19fHx8fn+BgoF/e3l5ent5eXh2dHR0cW1pamptcXJwcHR4f0JBQEBDR0lGQ0FCREVCQkM/P0NJS0lHhEUFRENDQ0GFQB4+Pj4/QEFBP0BBQkJBQT8/QUJAPT08PDw9Pj48PD2GPoA/QENEREZGRkdGRUZFRUZGREI+PD08PDs8Ojo+SEpDPT4/PT0+Pz4+PkBDRUZHTE9QT01NTE2bnKWuraqnoZ+cmpyam5qYnKCfoqSnqKimpqWlpKWkn5qZm56hpKiqq6qoo6CenJuhoZqXkpCJhIF6eHZ1dXl8gYaLi4F8hZCSj4CKkpmTi4mSk5CUk46Lg3x5dm9wc3BucXV1c3N1dnV1dnd2dHNycHBxb29wbm1tbWxucG9sbGxvbm5xc3V1d3p5enx/g4aIioqLi4mJiYqMiYiLj5OYn6aqsLO3ury+v8XGxsbFxsfJymVlx8TBvrmuqKapqKWlqaemqaSkn52cmU6TkpSboKCgp6OlqalVVVVWU1GgVVheYVpWUJaOj4uIg4SAfHt7fHt/gIOIhoiEfYODgYaJiEtHgX57h0WGgnp2dnV0dXd4ent9fX5/fz+EQHVBQUJDRERFRkdHSUlKTU5OT1FUU1NUVFZXWFhYWltdX2BjZGVnaWxsbm5ucHR2e4OBgYKChY6Wmp6iop+eoam3xMbD0NPL19LGwLmmn6CiqbG3u7e0r62ws62trrS6v8bIx8TBxczIx8vIwr7BwsG+vr/BxctD0tDOzM3P0NHPzs3Nzc7R1dnj5ufo3tTS0dDT09DP0dbY1tXT1dnV1NXY19bV0tLV2Nzd3Nza2dva19XV1NPh2MnIx4TGgMXEw8LDwr+/w8bExMDAwMLExbu3tbezrq2trKurq6qnpqWio6SlrMHWzNLYcHPXx724wLmxtMHOxr+5vb2utb3Du6mmoaCsr7bAvbCpqqeioZ+Vj4+Zo6OcmpaZmo+FhYmFgYWMhHh2gZGXm5mTj4mFgH52dXJydXx8eXl7fn6FgIl9eXZycG5ra2xsb3Nr1Nrte3ng1czCube7wdDa3+Hp6N7WzMnGw7/CxsfGwMC9vby5trKxraainqCkqaeioqKhoJ+enZydoKOjn5qXmJyenp6dnaKnqaeopqeoqKSko6OkpKGio6KhoqOjnp6dnJqXlpaTlJGQkJGSk5COjo2LD4yMi4uLjY+PjImIiImIiYSKH4mHhYSFh4uLiYiHhoaGhYSEg4KEhomMjI2LiIuMjIyEizyJioqJi4yLioiIiYmLiouLjIyNjo+Pj5CPkZOUlZWUlJWTlJKSj42OkJKQkZCMkZaXmJWQkJCRk5aXl5WElAqVlZORkpKTkpOVhpYLlZeXl5iZmpmYmJqGmQGYhJcylpORkZSXmJiYmZygplVUU1NWW15aV1VXWVlXWFhWVllfYV5dXFtaWltaWVlXV1dWVlaEVRpWVlZVVVZXVlZWVVVVVlRTU1NSU1NUVFRTU4RUIVVUVVZZWlpcXFtcXFtbWllbW1hXVVRUU1JSU1JSVFpbVoVTgFRUU1NTVFZXV1hbXV1dW1xaW7a2vL+9vLm0tLCusK+urKusrqyvsrOztLKzsrKys7Kxraysr7CztrW2s7CtrKyrqq2vqqikpJ6cm5WUkpOTlZaYnKCgmpado6SinKGmo52ao6Ogo6KenZmUk5KNjI6NjZCTk5KTlJSTkZGTk5GQB4+Ojo6Njo6IjYWMEo2Pj5KTk5STk5SXmp2eoKChooShEqKgoaOlqrC0ubu/w8bJy8zP1ITWPtfX2Npub9rZ1tHMxMDBw8G9vMG+vMK+vLi4trazsLK3uLm7w72/wsJiZGRkYWC+Y2VscGhlXrSvraelo6aihJ6An6SlpquorKigpqanrK+vXlqopKCsWq+qo56cmpydnp+hoaGjpKWlU1NTVFVWVlZXWFhZWlpaXF1eYWJiYmVnZmZmZ2lqa2xsbW5wcnR1eHl6fX6AgoGChIeIj5WTk5SVmJ+nq66wsKysr7bDz9HN2N7X4dvQy8Ozrq+xt73DxcMhwb26vb+8u7zAxcrP0dHPzNDU0dDTz8vIysvLyMfHyMvPMdjX2NjY2drc29vb2tzd3+Di6Ovs7ejh4eDg4uPi4+Pl5OTm5ubp6enq6+nr6+rs7e2E8BTv7u7u7ezs7Or37+bl5OXm5+jn5oTlfuPj5OXj5OHg393e393a1tfX1NLQz8/Qzs/PzMvJyMrMy8rJy8nHY2HEw8LAv7y9u7e4trW0tLOwr66vraurqquvrq6wr6uqpqako6SjoaCfoaGgoJ+goJqYmJmZmZeXlpWUlJmXlpWVk5KQj42Mi4qMioqJiIiHh4eJiYSCgYSCFIGCgYGBgP///n+A/fv4+fr7+vr6hPNb8PHt7evq6+rr6+rq6Ofm5ePi4+Pi4OLl5eHh397d3dra2tvc3uDg4eLg3t/b29vZ2Nrb293e3NnZ19fW1NPS0dDOzszMz83Nzs/Ozc7OzMvKycjGx8bGx8bExYbEgMXFxcPFyMjGxcfIxsbFx8nHxcPExMTFxcPBwsLCw8TFxMbKycjLzcvKycjKzM3Ny8rKycnMy8rKycvO0NPS0tHPz9HQ0dDQ0tTS0M7N0M/NzMzMzs7Ozc/R0c/PzszO0NHQzszNzc7Oz9DQ0dLS09HR09HPz8/O0NLS0M/P0NHSKNPR0NDR09PV19fU0tPX2dze4ODh4ODh4ePj5Ofr7ezu8PX49PX5+v2EgGCChoiGhYKEhoeHiIiHiIqMjYyMjI2Mi4uLjIyLiomIh4eGhoeGhoWGhYWGhYSFh4eFhISEhoaGh4eIiYiIh4iHh4eIh4eIiYqKiouLiouLjIyMiouKioqJiYqJiomJiYiEiQqHh4eIh4eHhoaGhIUxhIOCgoKBgYCA///+/fz59/X08e7t6+rp5ubl4uHg4eHh4uXk4+Tk4uXo6efl5+bk44XiIeHf3+Hh39/e3dza29za2dnY19fW19bX1NLX2dfW1dbV2IXUD9bW19TU1dXT0NLS0tHV14TWH9XV1NPV1dPW1tXW1dXT0tHPzc/Q0dHPzc/Ozc3NzsyEzSPKyszMz87O0dLT0tLT09TX1tbY29/g4uPm5ujn5urs7fX2+IT6gPv9gIH//v///Pf09ff28/L29vT49/nz8/Py8e/v8fT4+f/8/f7/gIGCg4KB/4OEiYuGhYD68/Hv8e3v7e7v7O3v8/T2+vn8+PP1+Pf6/v2Egv759fyA//359vX09PP19fj5/Pz9/f+AgYCAgYGCg4ODhIWGh4eHiIqMjY2Nj5KSYZGSk5SVlpaWl5iam5yen6ChoqWlp6ipq66vtLe3uLi2uL/DxcjKysjHyc3V29za4OLg5+Hd29jOysrJzdPW19XV1NHT09HT0tPU19rc29nY2t3d3d7b19fX1tbW2NfW19jzgYKC4IEFgICAgYH/gP+AmID/gf+As4CCgaKAhoEBgIeBm4CCgYSAAYGRgPqBAgIEAIDRycbHx8nKzdDPzMnDxMnQ197l5+za2N7XzMrHw8TI0tTU0tPS1dXV09LU1NPS0dHNy8zNztLW193d2tTQzc3OysTFxMC8urq5t7W2zMW/s7a5vcHBt7OytK+trq2sp6SioaCkpp+io6alsKql0oSRkaWst6OQ8dfP1tbF0+vOvYC6vbu7ssfe1bevrKqmpLvFu7Wtq6yhk5ekpZaRkpegrK2impSKiIV/fHuAfXt2hJSepaamnIqDeGxkYmNmZ3l/eHh6eHVzdHl5d3NuYVxWWl1jaGlkdH5/g3vr0rqyq7C2t7i+ydTn5M7Jw7+5sq+4vr61srS7u7q0r6qgmJKLi4CRl5qgn6isp6KQhIGBgoaHioh/e32AgoWNkJyjpqWfmpaWmZ+hoaCamJSRlJKPkZOUko+LiIWEg4SHhYF+eXh3eXh5dnZ2dHZ2dXZ2d3h6eXZycHJ0dXVzc3BubGprcnl3cnBsaWtoaGdnZmdqb3V6en18end1dXJzdHVzcG9uboRxRm9tbXBxcHBxcW9xcHJ0dnd5eXx+gIKDf39+e3l5f359fH6Agnx2f4uRjYR9eXd3d3l9fX1+f4CDg4aEf318fn18e3x9fn6EfQ57enl8foCAf398fHt6eYR3GHR1c3Jxb21ra3Bzc3R4fUJGRUJDRUhHRoRFDERFSEhFRUpOT01LSYRIBUdFQ0JBhEAWPz9AQEFCQUFAQkNCREVCREVDQ0E+PYY8Aj0+hT+AQEFBQ0ZHSEVFRkZFRkZFRURCQT49PT5APT1ATllgXVJLR0FAQENFQ0JERkhKTEtMS0pKS05SW7q4rqenpqekn5yZk5KVl5yfnZyho6GioaGhoJ+goJ6ZlZOVl5ygoaWop6ajoJybnKWmoZ2amZiXkoZ/fHp7fX1+gYaDe32BhYaAiImMjYqGjpaYlJSTkpGLgHp4enx6dHR0dnZzc3d5eHl4eHZ1dHJwcG9ubnFucHR0cW9vcHBxcXFvbm9wcnN4fXx8fYKChYiKi42NjYuLi4yMj5CRlZuhqK2xtLm6v7/DxsbFxcbHyMfIZGLCwLu5ubOppaavr66oqbSyp6agn6BMoqSclZaeWldWVKWgoKGlm5Sam1FVWlpVV0+Jh4eFiYqFiIeCfX6EiZSUTEyLhoF/g4KHjISFR01GfXuISUmCe3l5eXd2d3p8fn5+f4RAeEFCQkJDRERFRUZHSEhISUxOUFBQU1RVVVZXV1hZWlpdXl9hYmNkZmhpa2tqa21ucXJ1dnd5fYGEio6WpayrqqmssrzIys3U1cjOzcS5rKOipa6zt7m4vMHAurOtqayxt73Bx8nKyszOzMzR09PPyMXGxsXDwsPEzoDTzc3Ozs/P0tXT0c7MzM/V3OPp7e/g2t7b1dTS0NDT293c2tra3d7d3N3d3Nzb3NvY1tjY2Nre4OXk4d7b19fZ1tHRzsvIxsfHxsLE2NHLwMPFyMrMw8HAv7q3t7i3tLKvrq2vsKytrK6ts6+tyXR6e4WJj4R41sjFyMa8wtG8tICysrGwqrfEv66op6Wko6+0rqulo6OdlJafn5aSkpWYoaKclpGLioiEgoGDgYB8hI+VmZuak4iCfHVwbW5vcH2BeXl6eHd1dnl6eHZwamZiZWZrbm5qdHp6fXjp2cjDwcXGx8bHz9fj387MysfEwL7EyMbAvb3Dw8LAvbiwq6ajoyOkqK2yr7S2tLGlnZubnJ+foaCamJiZnJ6hpKmusK+sq6akp4SqVammo6Gko6ChoaOin5ual5aWl5mXlJOQkI+Pj5CPjY2MjY2MjZCRkZGSkI2Ki4uMjYuLiYeHhYWKjY6MiIaFh4SFhoSDg4aLj5GQkpGSjo6NjIyNjo2EizmMjYyNi4uKjIyOjY6PjZCOj5GRkZKRk5aYmJiVlpaUkpKWlZOSlZealY+UnaCdmpWRkZCRlJaWl5eEmAqZmZeVlJWWlpaVhJcQmJeWlpiXl5iZmZmampqbm4SZNZiYl5aXlZSTlJSYnJqbnqRVWVdWVlhdXVtZWVpaWltfX1tbYGNlY2FfXl1eXl1cW1lZWVhZhFeDWIRXC1lXWFlXWFdYV1ZUhFMCUlOHVE5VVldXWlxcXVtbW1xcXFtaW1pZWFZVVFRVVFRWX2ZqaF9cWlVUVFdZV1dXWFlaXFtbW1paW1xdYsjIwLy7ubm3tbKuqaipq62vrq2wsa6IrymurKimqKqssLKytLKyr66sq6yvr62rqKempaSbmJiWlZeYmJubmZSXm4SdIp+fnpqhpqelpKOioZ6YlJKUlZORkpOUlZOTlZaWlpSUkpKEkCKOjo+RjpCSkZCNjY6OjY6OjYyMjpCSk5eVlZaamp2en5+hhKKAo6Wkpqeoq661vL/CxMbJzc7T19bW1dTX2dnbbmvT09HOy8fBv8HIxsW/v8rKwb+8vL2+v7izsrpnZGNhwby9vcC5trm7YWVpamNlX6mlp6Wnp6SpqKSio6est7ZdXayqpqOnpKqxrK1bYFmko69dXaqlo6Gfn6ChoaOjpKanU1R6U1RVVlZWV1hZWVpaWltcXF5gYWNjZGdoaGlpampra21tbm9ydHV1eHl7fX5+f4CBgoWGiYmJjJCUl5ufqLa7ube3ub/K0tbZ3d7T19XPxLu0sbO8wMPGxcjKysbAvLq6vsPJzNHS09PT1NLS1tna1tDNzc7Ny8rJytIq2Nna2tvb3Nze3dzc293f4uTn6+3t6OPj5ebn5uPj5ert7Ovr6+zt6+zthO5a7Ozs7e7u7u/x8fT19PLx7u7u7Onp6ujn6Ofm5uXk7+zp5OXk5eXm4N7e3tva2tnZ19XT0tDQ0M/Pzc3MzMvMzGZlZWZnZ2RixMTDwcC9vLq4t7W0tLKxsLGwhq98sK6urKmopaOio6WlpaKhn6Cgn56dnJqampmampiXlpWWmJmZmZaTkpKRjo2NjIuMj46KiYmIiIeHh4aEg4KDhYWEg4GDgoKCgYGCgP/9+/z8+/n29PHw8fHw8u7t7O3s7u7v7enr6Ojn5+fl5uTj5ubj4eLg4uDi4t/g3oXgL9/g393d3Nvc3t7b2tvb2dXV1NXV1NTU09HQzszPzczPzs7OzczLycjJysrFxMXEhMUexsXFxcTFxsfIx8fHxsjJycXFxMXGxMbExMXDwsXFhMM2xMXFxsbFx8jJycvKx8jHx8vMy83My8vMzc7NzdDR0M/P1NHQz87S0tPT1tfX1dPT0tLT0NDPhNAZzs7Q0tHPzs3N0NDOzc3Ozc/Q0tLS09TS04TSAdOF0T3Q0M/Q09XW19XS1dfW1NfZ19bW2NfZ3d/l6efl5OTk4+Xm6ert8PHz9fn6+Pn9gISCgIGDiIeGhYaHh4iJhIoKjZCQj46Oj4+QkIWOBI2Mi4uFihOJiIeHiIiHiIiHhYSFhoeHh4iIhYkLh4mIh4iIiIqLi4yEixGMi4yNjYyNjYuKiYmKiouJiYaKaIiIiImIiImIh4aFhIWEg4OCg4SDgoKA//38+/r5+ffz8u/t6+fl4+Tk4+Pi4+Hf4OLh4eLi4uTm6Ono6ejn5uPh4N/h4uDg4N/e3dzc3t7d29nY2djX2NbV1NbX1tbW1dTT0tLV1tXVhtcJ1dTS0c/R1NLVhNgm1tbX19bW2NfX19bU09HR0dLQ0M/Pz9HR0M7Ozs/OzczMzc3Ozs6EzYDP0NHR0tHS09TW19ja293g4eLk5ebn6uzu8PX7+vr5+fr8/P6AgP77+vv59vT19vv5+fX0+vz19PH09vf38/Ly+YOBgYD99/r8/vv6/v+ChIeHgoSA8e7w7fDv7vP08fLx9Pj9/YKB/Pj29fn4+v77/IGFgfj1/oWE/vz7+vn3+CH3+vz+/v3/gIGAgIGBgoKDhISEhYaGh4iJioyNjo6OkZOElA+VlpeXmJqam52enqCho6WEp0uoqaqsrq+xsrO3uby+w83Rz87OztPX3t7f4uTd4N/c2NPOzMzR1NbY19ja2tnV0dHT1NjZ3Nzd3N3e39zd3+Lh3tvZ2drZ2NjX19nugYiC5IH/gP+Al4D/gQGB/4CzgIKBm4CEgYmAh4GQgIKBioAIgYGBgICAgYGOgPyBAgIEAIDCwsbJyMnNzs7Q1tfSzsnM0dXghYWYsY330s7KxMXL0dPY19HNztTZ2NnZ3N3c2tjZ1c/Q0dHV1tvc2tnY2NXT0s7Ky8rFxMO9ure1t7e5tbS5usTEt7SzsrOysa6srKqnpqSltK7b0qWoqajG0cnbhJyamp2S+fbt3+bq1te+tYC/u66zs7e3q6WqvK+UpbXCw8OvnqKfmZ+iraqjmpmgo56ipJyYjIN+f4CAgn+AiIOHiYqFeHh9fXp5fHRpb4GBcW9xd3Vwe4B4dHBrYFpWWVleZGZoanBrbGxv0bW5wcrBqqiuudnu7Nq/vca6t7XBxL++tMLJw7u6saiako6OlDago6WrraysqKGWi4KAhIuLi4mKh4eDgouSkpqjqKqqo5mSlJebn56akY2Nj42Mi4yNj5GOi4aEhYCEgH57e3t4dXNzdHV2eHp6d3Z3eX2CgXhvcnR1dHFwb21sa2xvcXJvbm1vcnFnZ21vcHN1eHp7ent5d3V0c3R2d3VzcW9ubm9wcXNycHNzb3Fzc3R1dHV4e31+gH5/gYWHhoOAf316e3t+gIaJhoB7io+KhX97enl6e3l3d3t+f3l9foCCg4B9fX9/fXt7fH19fH17fH18e3x9fHx8fX1+f356eXp5eXl3c3Bvbm1tbnF2d3l/Q0dLSkpKS0xNTElJSkhJS1FTTk9RUVBOTUpIR0dHRkVCQkNCQkFCQ0NCQkNERERFRkZEQ0RGRURBQUBAPz8+Pj49PDw9hD6AP0FCQkRHR0dGRUZGRkdJRkVDQUA/PkBHSERBR1VWU05JS0dGR0Q/Pj4+QEJESEhGRURERUhKUFZat7KvrKunoZyYko6QlZiWlpWYnqCfn6Gin52bm5qalZCPj5CTl5ugo6Sjop+amJqioqCdnp6doZ6RiYSAgIKDg4B9eHR4g4iAhYKGiIyKioySlZKPjo6LhXx7hIeCenR0dnh5enh6fHp6end4eHVzcnJyc3V1d3l6eHRzdHV0dHNzcnJzc3N1d3l7fYGCgoOFiY2Rk5CNjYuKjJGTl5yfpKmws7e8vr/CxsbHyMvMy8nIY8LAwbe1t7S0rq6wWrCttbi2squqq6xWra2mo6KsWVSfnZqYl5ago52cnJ1PUVFUTomFgoJ+fIGHiIOCjYeGjJFMTUxKjoiFjIuFiIiKikxXVkhGSUtLTUZDf4B+eXp7fX+Af0BAQUFCQkJDRESERW9HR0hJSkxNTk9RUlNUVVZXV1dYWVpcXV5fYWJjZmdoanF0dXh6ent8f4eMjZCTlp2pqZ+dpKqrrrC3wMzT0snKyMe6t7Ctrq2ws7e9u7q+wbq+wL6+wMPGyc7T1NLQ0NLS0MzOzMfFxsTFxsTEw8EDysrNhdAy09Xb3djSztLX3uR9fomWgvHc2NTQ0dba3uDg2dbY3OHh4ODh4uHh4N/d2trb29/g4eGE4IDd3NvZ1dTU0c/NysjFxMXExsPAxcfPzsLAwL6/vLq5uLe2s7KxsLm2z8itr7CvwcfCz3SAf4CBetvb1czQ0cTDtbC1sqisrK2vqKSmsKmYo6uzs7OmnJ6cl5udpaOemZeZnJiam5eUjIeFhISEhYODiYWHiImFfXx/f317fXlzdnKCgXR0dXd4dHt+eXd0cGhkYmVlZ2tsbW5ybm9wctfIys3SzL+9vsXZ5+XZycjNxMTCyczIx8DJzMjExL+5rqimp6uxsbW5urm4trSqop6anaOioqGin5+dnKGjpKivsrGxrqijo6Wpqqupo6GhoqGgn52EnwidmJiYmZeWk4SRco+Ni4yNjY6QkZGPkJCSlZmXkYuLi4yMjIuKiIaFh4mJioiHh4iLi4WFh4qKjI6PkJGQkZGQkI+Oj5CQkI+NjIyMjYyOjY6NjpCNj5GRkJKSkpSVlpaXlZaXmZuYmJeVk5KTlJWUmpybmJOcn52al5OSkoSTFJSVlpeVlpiZmpiXlpiZl5WVlpiYhJc6lpeWl5eYmJibm5ydnZ6bm5qamZmYlZeXlZWXm5+ho6ZWWV5dXl1dX2BgXl5eXV1gZWZiY2VlZWNiYIReAl1bhVoGWVlaWllZhVoKW1pZWFlZWVhXVodVA1NUVIRVgFZWV1hYWl1dXVxbXFxdXV1bXFtZWFdWV1paWFdbY2RgXltdWllbWVZVVVRVVlhaWVhWVlZXWVpdYGPIw8G+vrq2sa6rqaqpqaqrqqyvr62tr6+srKqqqaqopqamp6irra+wr7CvrKqqq62vrqurrKyuraWdnJmZmpubmZaUkpWbgJ+cmZydn5ydoKOlo6GhoZ+blZSanJqUk5KTlZaWlpeXl5aWk5WWk5KSkZGSk5OVlpWUlJKRkZKRkZGQj5CQkZGSlJSXmpubnJ2foKOlpKSjpKOjp6uusrW6vcHEx8nM0NLV2NjZ2tvb29xt19TVzcvNysrGyctmysjMzs3JxcPDgMPExsG9vMVnY726trS0tLy/vLu8vl9hYWReq6ejpaGeoqaopqauq6uvtF1eXVyxqqqvramsra+xYGtoW1ldX15fWVaop6aho6OlpaeoVFRVVVZWV1hYWFlZWVpbW11eX2BhYmJkZmdoaGpsbGxtbW5vcHJzdXZ4enx8foOHiYuMSo2PkJGYnJ6hpKeuuLqxrbK4ur2/xMrX3dvU1NHQyMO9vL27v8HFycfFyMzGycrKyszMztHV29za19na2NfU1tTRzs7Nzs/MysrJStjZ2t3d3d7e3t/h5OHg3uHj5up1dXd1dOnq6+jl5+rq7e7v6uvs7/Hy8vLx8vHw7/Dw7u7t7vHx8vT08/T08vHx7uzs7Ovs6unnhOY/6ejl5ufq6eHf393d3Nva2dnZ1tXV1NPR0c/Ozs3NzczMzWZmZWVlZMbFxMLBv769ure2t7SysbGvsLCxsbGwhK85rKqopaOhoqOkpKOjoZ+gnp+fnZybmZmYmZmZl5aZmZmXlZSTkZCRkI+OjY2Ojo2LiYeIh4aHiIWDhIQEhYWEg4SCAYGEgC3++/v8+Pj3+fTz9Pj09PLw7e/y8vHy8O3p6Orq6ero6Ofo6+rn5OPi5OPj4+KE4yTi4uDf3t/e39zb3N3c2tnZ2dbV0tHU1NXV1tbU0dDQ0tHPzMuEzYDLycnJysnHxMPFxsXDxcbGxsXExcXHycrJyMjLy8zIx8XHyMbGxsfGxMPCw8XCwsLDxcXIycjIx8nJysjJycvNzc7OzczMy8vLzs/R09PR0NHV1dTV1dbV1tXW1dXS0tTT1dTS0M/Qzs3P0NDP0M/O0M3Qz9LQ0NHR0NHS0dDP0Q7T1dPS0dHPz9DOz9HR0YTSQdbY2NfV2NvZ1tna29rb29zf4N7i6uzp6Ojo5+js7O7v8fP19fn7+/yAgoSEhYaGiImJiYqJiYuMjpCNjZGRkpCQiY8Tjo6OjYyNjY2MjIyLiomJiYiHh4WGAYiEh1OIiYmKiomKiYmIioqLi4yNjY2Mi4yLjI2OjY6NjIyNjIyNi4uLjIyNjI2LiomKioqIiYiHiIiHhoaFhIOEhIODgoCA/Pr7+/r49vbz8O3q5uXi4oTjAeKH4YDi4eTn6Ofm5+jo6OTg4eHk5ebj3N3d29ra3Nrb2drZ2djX1tfW1dja1dTX1NPR0tXV1tbX1tTT1NXV1dPT0tHS1dTW2NjX19jY2NfZ2dfY2NbW1dXT09bV1dXU1NPS0NDPzs3Pz8/OzcvMzc7Ozc7Nzs7P0NLV1tXX2NjY2dzd4IDk5ujp6err7O/y9fj6+/v8/P3//4D//v/5+Pr5+/j9/YD/+vv6+fXy8/T2+fz79/b9goH9+Pby8vT5+vz8/v+AgYGEgfTx7+3s7PDx8vP1+Pb3+v2BgYCA/fr4/v35/Pv+/oWNjYSBhYeFhoKB/v79/Pz9/v7//4CAgYGCgoKDg3SEhYWGhoiIiYqKi42Oj4+QkpOUlZaVlpeYmZqbnJ6en6Cio6Smqqusr7CwsbKzt7q7vsHCx87OycjM0NHT09fZ4OTj3+Hd3tnX1NLT0tTV19rZ19jZ19rb29rb3Nzf3+Hj4uLh4OHf3t7e3Nra2dra2tnY15OBhYLYgYaC5YH/gP+AlYD/gYOB/4CxgAGBi4ABgZCAgoGMgIWBkICEgYqAi4GKgP2BAgIEAIDEw8TGzM7Q0szMzM3Q0NLV2dvw/fmGioLm4N7bz8/Oz9PW1tXPy87S1trc5d/d2djY19jY19bX2tzY1t7j49rZ19XT0dDLysjGw7+9vMO/u7W0srfAvbq7tra2t7Wzr66vsrPB8vH/9cnOrMHv7t3h6vHi0dDU09nbz83S4ffjzIDn/+7U2dHV3N7a3auQsb7Iz8uws7q2ubKxrqefmJCfwb7Av62cgImNmp6YkIaHgHp9goeKkJSTlJKQkIyDeoONjoJ7gYR+d3VwamZlZFtSUFNcYmJoaXB0cWhrcHdsatLRztDc29fN0NPCt8LCwsTL0dPT0svJw769saCTj5Sbn3ChoKGpqKCZko2Kh4SChIaJjo6Pj42Hg4+fpKalpamlnZqYlpaVlpOQjYuMi4+VlY2Ki4yLi4mHhYSDg4B9foF/fHl2dnd3eHx9fXx4eXt9gH58d3Nwb25ub29tbXBzcnB0dXJxbmlqam1wc3R2eHp4hHREd3h2dnV2dXRzcXFycnJ0d3d3dnd2d3h5e3p6e35/gICAhIOBgoSGhIWAe3l5eXh6en19f3+Bf3+AfXx9fX57enl4e3uFfRt8fn5/fn58fn59fn59fX1+fn19enl7fHx9fn2Efh17ent5eHZ0cnBxcXJydnl8f0NHSk9UWlhXXGNZTYRLF01QUVBVVVRPTExIRkZGRUVFRENEQ0JChEGAQkRGRkVERkZDQUBAPz8/Pj8/Pz4+Pj8/Pjw8PT09Pj9BQkJER0ZHR0ZISUtJSUhGRENGR0ZIS0pHR0dJSkpJRUZGSkhFPj4/QEJFS09KQkJEQ0FDRklMT6eqraqnoJuWk5COjo6QkpKTmp2foaSlop2cmpeWlZSTj4+QkJCUmp+AoaGgnpmXmZ2fn56cnqaqpJSQiYSDg4WCfHlzcXmDf3p/hISDgH+CiYuNkZCNjoyFg4iKhHp4d3h9goODgoF/e3h2d3h3dnV1dXZ3eXt8gH14eHp7fHx6eXd2eXl6e3t6fYCAf4CBhoqKjY+QkI2MkJGUlpyfoKSssbS4vL/BxMcUycrLzM7NzMrGwb23t7e2XFxatV2EXlS4tbaysLW2s69WVldbsauempSTkpWclpeZkoyIi5aQhomIhYSGhYmLj4+Ulpqbm5mVk5GJhoiHi4qEd36AiFJST0tISExPUElCQEJAfT9+Pz9AQEGEQhdDREVERUZHSEhISUpLTE1OT1BTVVRUVoRXV1hZW1xdYGJlZ2ptbnJ2eXZ1eHx9foOHi5KboaOen5+Yl5man6WstLy/yNrKx8jJw7+6urixsLOzub28vry4t7W5u7y/x83N0dvi5+3u5+Db083JxMbGw4TBAr/DgMrKys3T09LV0tLT09TX2Nnc4Orx8X6Bfuzn5OPa2Nrc39/g39rW2Nze4uTq5OHf3+Df4eHg4ODi5ODf5+rr5OLh39zY19TU1NPQzMrK0MzIwb/AxMnIx8fBvr6+vbu5ubm6usHe3OLcw8SxwNraztHW2tHIx8jIzMzDwsTL2c29gMzd0b7Cv8HFxcLHqZeqs7i6tqeqrqmqpqakoJuYkpqvrK6sopeEi46VmJSPiImEgYGEh4mMjo6PjoyKiYR9gomJgH1/f316eXNybm1sZl9fYWZpa29uc3ZzbG9yd29t2dbT1dza2tbV18/Fy8vMzdLV1dXU0M/Jx8W9s6qmqrCycLOysre2sq2opqSin52en6ClpaWko56cpa2vsLCvsbCqqKempqWlpKGfn5+eoaSknZqcm5ycm5mXl5aWk5GSlZSTkI6Oj4+PkZWUlJKSlJeYlJOPjYuJh4iJiomIiYyKiYuMioqJhoaGiIqMjo+PkZCEjlSQkI6Pj4+Oj5CPjY6Oj4+QkJKTk5OUlJWXl5WXl5mXlpeZmZaWmJqampeTkpKSk5OUlpaXl5iWlpeWlZWUlpSTk5SUk5SVlpeXl5iXl5aXl5iWlZaElwKYmYSYQZeYmZmcnJ2enp+enZycm5qYl5eYmZmanJ+kqldZXGFla2pobXJsYF5fX19iZGVkaWhnZWNiX11dXFxcW1taW1tahVkTWFtdXVtaW1pZV1dWV1ZWVVZVVoRVAVaEVR9UVVVWWFlZWl1dXFxcXV1fXV5cW1tbXFtcXV5dXF1chF05WVpaXFtYVFRVV1dZXF9cVlZXV1ZWWFlaXb2/wL67trOvrKqop6Wlpaaoq6yvr7Gxr6yrqqinqKiohKZFqKmsrq6traypqamrrKyrqaqvtK+mop6bmpucm5eUkY6UmpeVmp6cmpiXmp6foKOioKGfmpqfoJuWlpSUmZ2foJ+dmpiXhpSAk5SVlpeXl5uZl5aVlpeXlpWUk5SUlZaVlJaZmpmampygoaOlp6ampqeprK6ztbe8wMPFys3O0dPW2Nna3N7d29vY0c7IyMvNaGdmzWlqamlq0MzMy8jOz8rJY2NlacvGu7axsLCyvLe2t7OwrK23s6isq6enqqaqrLGvs7W4ursluLe3tK2oqaqtraqhpKWtZmZjX11dX2JjXlZVVlWkUqVTU1RVVYRWeFdXWVpaWltbXF1dXl9gYWJjZGVnZ2hpa2xrbG1ub3Bxc3V5fH6AgYWIjImJi46QkZSYnaKqsbOwsa+pqamrsLW8wsfJ1OjV0NLV0c7IxsS/v8DByMrJyMfFw8LGx8jL0dbW2OHm6u/w6eTf2tTSz8/NzMrJycjHyjTY2Nrb3uDh4N7f3+Di5OTk5ufp6ep2d3jy8/Du6+zs7u7v7+7s6+zt8fLz9/Xy8PDx8PHyhPGA8vLx9Pf39vXz8vDu7u3t7e7r6+rr7+vp5uXk5ejm5eTi4d7d3Nva2dnZ19bV09DR0NDP0M7Mzc/NysrJycjIyMbEwr6+wb26uri0s7W0sbO0trWys7OxsLGuq6qopKSio6OjoqOgoKWjoKCgn52dnJubm5qYmZmYl5aVlJSTk5OAkpCSkY+Pj46NiYeIiIiEhYaGhoeIhoeFhYSEgoKCg4SDgYCCg4GA/Pv6+Pfz9/b09fX18/Dz9PPy7+7s6evr6+3q6uzu6ubl5uTk5OLh4N/i4+Li4ePi3t7f3dze3tza2dfY19TU1NPT09TX19bU09LT09LQz87MzM/Ny8rJx8YMxsbHxcTFxcbFxsXGhMULyMjLy8rLy8vIycmGxoDHx8bFwsPDw8TDxMXHxsnIyMnHxsjJycrLzM3Oz83Nzc7OztHQ0dHQ0NLV1tfY2djZ2dbU0tbS0dLS09PS0c/Pzc/P0M/R0NDRz87OzM3Pzs/N0NLRztDOzc7O0NTSzs/Pz8zP0dLQ0tLU1dTT09TU1tXV2NfX1djd3NrZ29/h41rm6u3t6+vr7O7y8vLz9PX2+fv9/4CBg4aJjo2OkZOQi4qKioyOj5GPk5STkZCRj4+Ojo6Pj4+QkI+Pjo+Pj5CPkZKQjoyLi4qJiYqKiYqJiomJiYqJiomJioqEiwqKioqLjI6NjoyMjI4HjY2MjIyLjIWNeYuJiouKiomJiIiHiIaGhYSFhYSEg4GB//39+/f29vXz8vDt6OLh4uXj4+Ti4eHf3d7g4uLh4eLk5OPo7Orm5eHg4ODk5uDc29rb2tra29vd29vc2NXU1dfY29rX1NfW1tTU1tPT1NXU1NbV1dfV0tTU09LS1dXW1tiG24Da1tfY1tfW1dTU1NXW1dXV1M/O0NHS09LQz9DPzczMzc3Ozs7Pzs/R0tXY2tva293f4eHj5Obq7Ovs7+/v8PL4+vr5/P7///78+/n3+vv7gICA/4GDg4GC//r5+fr7////gICCgv/+9vTx8fH2+/j19/b08fX79fDz8vDv7/Hy9Cf49/j5/Pv8+/v7+fXz9fb6+vby9fj/ioqIh4SFh4mKh4GAgYD/gP+EgAGBhIJ4g4SEhYaHiIeIiYqKi4yNj4+PkJKSlJWWlpeXl5iZmpuenqGjpaeoqqywrq2vsrK0t7i8v8TJyMbIyMPExcbJztDU19jf6eHf39/e3NnZ2NTV2NXY2dra2dfW1tjZ2Nrd3+Dh5enp7O/r6Obi393c3Nra2dnY2NbXlYGDgv+ByIH/gP+AkID/gYSB/4C4gASBgYGAhYGJgISBsYCOgQOAgYD/gYKBAgIEAIDLzMjKztDU1dDPycvT0tvx6N3b3OHg4ebs8urj39vn/O7d2drX1tXW2dze4ODg4eHh393e3Nvc4N7c2tzf4ufp4eDe29bPzcjHx8fGyczOyb+5tbK1vMbFu7e3urm7vL/Bt7Cxtbi+/aCf1r2/2/Ts7YLsxsbHxsXFw8HN3OPf0IDe7Ozf7e34gfbyz7unoa/At6ehsLzA0Mm2paqnnJOrxc3HtJuIf4+akpOJfXh2dX+IiIuMiIF9enp5eH98en+MkpGUlY6DdWpeVlZUVVNPUFVhZF5bXGBgY3eAe3Z2dHHh4OTi1svDyMrFvr3HyMzOzMnGys7HvbSsnpyZk4+XmFeamJedoJ+ajomKiYWDgYOHiYuMiouLkqSxsrCup5+dmZeWl5mXlZOQjoyOjZOcm5mUjouLioiFhIKCg4F7e35+eXh5eXh7fH6AgoR/fXt7fHx9e3VxbnCEboBvb3BzeHp5dnFtbmxtb3FzdXd4d3Z2dnNyc3Z3dnV2dnZ1dHRzc3R2dnl6enh4ent/fXx8fn+AgYKDg4SEg4SCgYB6enl5eX2Aent6fX6AhIyFfn2AgIB/fHt9f399fX5/fnx9fX59fHx9fn59fX59fn59foB+e3p8f4B+foGBgFeBgH18eXd1c3N0dHRzdHh8P0JGSEtQVltaWFhWU1BNS0tNTU1PUFVWVlVQTUlIR0VEQ0RFRUVEQ0NCQUBAQEFCREVEQkFAQEA/QEA/Pz9AQD8/Pz4/Pj6EPQg+P0FDQ0VHR4RGaEhISElLTElISkxRUU5MTExKSElLSUlISEdCQT8+P0JERUpIRUNFRkNAQkNERkhLnJ2fnpyYlZKQj42Lj5SYmp2en6ChoqGgoZ2ZmJeWk5KQkI6PkpWan6ChoaCenZydn5+eoKShmJaQhIo7h4aGf3h1eHlzd4CAf31+gYSJjJKZlo6Mh4ePlJCRhH5/g4WHiYiGhIF9eXV2dnZyc3R1eHp7e3yAgX+FgBKCgoJ/f3+Bg4J/f4B/foOFiIuFilSMjpCUl5ufoaaorbK3ub7CxsfIys3My8vKxsPAvLaxs7a3t7e4Xl+7XLe1tLO0s7VbXFpYsLCsqKWknpaVk5aRhoOGjYyCfXl+hYqQkJCRlpuZmZiEUIBPTqCin52bmZGQkYuMg4B8f0NFR0hIS01JQ0NDQUA/fj8/P0BAQUFCQkNDREVGRkZISUlLSkpLTExNTk9QUlRTU1VWVldYWVpbXV9gYmRmaGxvcnF1dXt6foB/gYeOjpGPlZKSkpWampicmqKusrK5vry6uLezt7zEvb28u8HJySPGxcTFx8nKx8nM0tXb3ePr8v6BgPvv4Nva2dXQy8zIx8jIzIDP0M/R1NXY2dXU0tLb2t/x6eHg4uTl5+vx9fDq6OXq9e7k4OHg39/e4OPm6Obn6Ojn5eTk4uLk5+Xk4+Tl6u/w6ufl5ODa2dbV1NTS1Nfa1szHwsDBx9DQxsPBwcLCwMHEvbq6vb2/4oSCzLy+zdzX2HPXxMLDwcHAvbvDyMzKwYDJ0dDH0dHWbtPSv7Kkoqm0raSfpq2xubapn6Oim5OgsLSxp5iMho+VkpKLg4B/foWKioyMiISBfn9+fYKBf4GIi4uNjYeAeHJpZWVkYmFfX2JqbGhmZmhoanZ7eHV1dHHg4ePh2tXQ0tPQzMrQ0NPV1NDN0NLNxsC6s7GuqqetriCurKuwsLCspKOjop+dnJ2go6Sjo6GgpbC4ubi4sKuqqYSogKelo6KgoKCfpKiop6OfnJuamZeXlpeYlZGTlJSQj46Pj5GSlZeXl5aUk5KTkpKQjoyJioiJiYqKiYmLjpCQj4qIioiIioyNj5GQkI+Ojo6Pj5CQj46PkZGRkJCRkZGSkpSVlZSWl5eYl5aWl5mYmJeampqZmJiXlpaTk5GRkZaYIZSUk5aVlpmfmZWUlZWXlpSVlpeXlpWWmJeYl5eXlpWVlYSWEJiXl5iZmpqbm5mam5ycnZ6EoAOfnp2HmyicnJ6hUlVYWl5jaGtsampqZ2NhYGBhYWFjZGhpamllYmBfXVxcW1pbhFwQW1taWFhYWVpbXFxaWVhXWIRXCVZWV1dXVlVWVodVC1ZXWVpbXV1cXF1chF4RX19eXl5fYmJgX19fXl1dX12EXIBZWFZUVlhYWVxbWVdYWVdVVVVWV1hZtra3t7Wxr62rqqelpqeqq6utrq+vsK+urq2rqampqKalpaepqamrra2urq6rq6mqrayqrK+tqKejoJ+fn52dnJiUkpSVkpObmpiXl5ianp6hpqWgnpyeoqaiopuYmZydn6ChoqCdmpiVlFaUlZOTlJSVlpiZmpudnJuampqdn56em5qZm52dmZmamZmbnp+hoKKjo6KjpairrrK1uL2/wsXIzM/S1Nfa29za29za2NXTz8rGycvMzc7MaGnRaM3OzoTMW2hqaGXJy8bBv7y4sbGytLCopKevrqehnaCoq7Kzs7C1ury9uV9fYGFgX7vAvry5t7CwsK2up6Wio1daXFtcX2BdV1ZWVVVUpVJTVFRVVlZWV1hXWFlaWltcXF2EXmxgYGFiY2RmaGdoaWpqa21ub3Bxc3R2eHp9gYODhIiIjYyQkpKVm5+foKClo6OjpaupqayrtL3AwMbJx8bExMLHys/LyMnHzNLU0s/P0tHT09HU1tna4OPq7/T+gX/78ufi393a1tLRz87Oz9CA2tra3ODi4+Lg4t/f5OPn8u/p6evt7u/y9fn39vb19PHz9PP08vDw7vDw8fT09PX09PTz9PP09PX08/L09/j5+vX19fLy7+/w8fHw7vHy8+7o6Obj4+Xp6OLg397d3dzd29rZ2NjX1dNradHPzs3My8xly83Ly8rLy8jDwsC+vL09u7q2trm4t1y3ube3tLKvrqyqp6ekpaimpKanp6WhoaKjoZ6dnJ2en5ybm5mXmJeYmZeXlpWUkpKTk5GSkISPgIyLiomHhoaJiomIh4iGhoWHhoSDg4SCg4SFhYSDgoD//vv6+Pn59fb3+Pbz9fb39fPy8fDt7e3u7u3t6+vn5OPi4uPj4+Hg5OXi4eLj4d7g3t3d3dna29vb2tfW1tfX19bV1tbT0tTW09HRz9HQ0dDOzcvKycXDwsPFxsXHyMjHPMbHx8TExMfKysvMy8rKyMbHyMXFxMXGyMfHxsbGxcPEw8TFxsfHy8nKyMjJy8vLzczNzc3Ozc3O0NHS1ITTgNXU1dbV1tjX19fV2NjX09PU1NLS0dHNzM3OztDQzs7NzMzOz83Oz87OztPR0M7QzMvMz9LU0dDP0NLS09TU0tLT0tPS0dLS09TU1NXW2NjX3d7b2Nzg4OXo6Ont8O/u7vLz9PP19vb4/P6AgYGChIeLjo+Pjo+QjYuLioyNjpCQJZKSkpOSkI6Ojo2Oj4+QkZGQkI+PkI+QkJGRkpORkJCOjYyMjY2EjC6LioqLi4qKiouLjY2NjIuLiouOjY2Njo6OjY6Ojo+Pj5COj4+Ojo6NjY2Ojo2NhIyAi4yLioqJiYeIiIeGhoaFhYWEgoGA/v77+Pf08/Lx8e7q4t/i4+Xl4uLf293d3t7g4d7f3+Pi4ubp6Obm4t/e3d/f3Nvb2dzZ2djZ29zb29rW09TV19fb2NXW19bW1NXU09TU09PU1NPT1NPT1NTU1dHS1NTX2dra29rb3NrZ3NlA2dfZ2NbW1dPV1dbW1NPQ0dPV2NjX1NPR0NHQz9DRz8/R0tLU09TW2dna2tze4OLk5ujq6+3u7/Ly8/f4+Pr4+YX8gPj29vn8/Pz+/4CA/4D7+vn5+vn8gYKBgP7++/r59/T09fP18u/u7/Hx7e7t7fDz9/r6+v37/f/9gIGBgYKA///9+/n6+ff3+Pr5+fb3gYKDhYWHiYeCgYGBgID/gIGBgYKCg4ODhIOEhIWGh4mJiYqKi4yMjo+QkJCSk5OTlZaXhJlbmpyen6CipKWoqqqrra6ysbW1s7e7vr7Av8LCw8PDxsXEx8fM0NLU19va2dfY19rb4Nzc29vc3+Df3t3c3t/g3t3e4eLl5ens7/R9e/Tv6efm5OLg3t3d3dza2uiBgoKHgQGClYEBgtiB/4D/gI2A/4GHgf+AuoAEgYGAgYeAhIGggIaBj4COgQGA8oGCgo+BAgIEAIDW3+De2tbX1c7L1unv7Ozm2dPNzdXX2tzp6+rw6PeUoKPv4N7e4tzc3uDg4+fn5Ovv7Ojk4t7d4OHg4Nrb3eDm6urr6Obf2tTJx8fGxcPOycPFvrm3tbm8urq6vM/0lqWhjvzG1fiJiJCTzMDt6/7n39rGvry7wsG+vsXT1NrQxoDS0+nn4d3w8NbEsKuvt8C4pqCiq7S+w7Sgo56YmpeUoZqOhYB/goqNfHt9fnx8gYyKg4J/fnp7f3pvc3yAgH1+goSMkI2Bb2hlYmBkZ2VhWlpcWFZWV1ZRVGNmX19wd3h5d/Pf29TS1tPSyMPS09LMxsfJycjBraeprr3AvraooWqfpKaenZqXkYuOioaEg4CEhYeJioeMlp2jqK2oo5qWkpOZn6CfnJiXl5mdmpKWmJ2el46MjYqCg4ODgX98fHx7eXh7fnx8fn5/hImFgX16eXt7fHhydXd0dHJwcnFwcnZ3d3h2cnNzc3JyhHSEcyNxcXR1e317eHl6enl3dXR1eHh5e3x7ent6fX56e3x9gICCgYSAhIEZfnt7fXx7fH5/fX+BgoSGhYR/gIF+fn58foV/gnyFegd5ent+fn1+hX0+fn9/f319foF/f4OCgYCAfn16eXd3eXp4d3d4e39CRUhJSUxRUlFST05PUE9OTUxMTE5RVFdZV1RRTktLSUeERhBFRUZHSEhHRkhHRkdHR0NCh0CAQkJDRERBQUA/Pj8/Pz4+Pz9AQkNDRUVFREVFRkdHSU1LSUhLT1JUU1NUUEtISk5TUk9LSkhIRUI/P0FDREVFQkFBQUBBQkFDRkiVlZWWl5mZmZeSjo6QkpSWmZuampyipaWlop+fnpuWkpCPj5CSlJmcnqKio6Ccmpqcnp6jp6WAn5uUjo+Sko2LiYSEgHx8gIR/fH2BhYaJjpSaoaOXkouOlJCMhYOHi4yJioqJh4J/fn18eHV0c3N0eXx9fn1+gICAgoKAgH9/gYaFhYKChoiEgYB+g4qIioqJiomHh4mMjpCVm6CjqKuxtrq9wsbIycnLzMrKysbEwL25tLKwWVxpX165uri5u7m6vru5t7peXlqxr7K3tKiknpSPkpKQhIGCfnt9e3l4gI6YUVRSWFpUUU+eTVFQTU+cm5ufo5mQlY+GhYN/fnx5eXt+f0NEQHp6eXl6ez4+Pj9BQUJCQ0NDREZGR0hJSktLhExsTU9RUVBRUlJTVFZXV1hYWlxdXmBiZGVnaWlscHFydHZ5e36DhIGDh4mKhoeJkZOUlZeXmZuvrbKpq62vr7GvsLS+zcrIzNLS0M3Jyc3IyM/S1OLp6uri5eXx9vf7/v3/8+3s4trW0c7NzM/TgNrg4uDd2tvY09Ld7fLv7+nc19TU29zg5Ozw8PTu+YqQkfLo5eXn4uPl5+jq6+vq7/Py7+vn5OPm5+Xl4uPm5+zw7/Hu6eXj39jW1NLQ0drV0NLMx8PCxsrGxMTEzuSAiIh85sbQ43h2e3zFwtnY4tTQz8PAvb3BwL29v8fFycK7gMHBzczKydTUxLqsqaqwtK+in6Clq7GzqZ6gnJiZmJScmJCKh4eJjI2Eg4SFg4OHjIuHhoKBf4CDf3l7gIKDf4CDhImKiH91cW5tbG9vbmtmZmdlY2NjYl9haWxnaHF2eHd27eDe2djd29nSz9jZ2NPPz8/Qzsq9uLi8xsjHwrazTrG0tK+trKumoqWioJ+enJ6goKGin6OorbG0trOwqaemp6uurKuqpqWmqKqooqaoqaqlnpydmpaXmJeWlJOTkpKQj5GSkZOWlpeZm5mVkoWRBo+LjY6Li4SKD4mKjI2Oj46NjY6NjY2OkISPDY6NjI2PkJKTk5OVlZSEkxeSlJaWl5eWl5iXmJeVlpeYmJiZmJiXl4SWH5eWk5KTkpKUlpeWl5iYmZqamZWWlZSVlJWVlpiYmJeElhCVlZSUlJWWl5aWlpeYmJiahJsPmpudnJyen6CgoqCgnp2dhZ4nn6CipVVYWlxcX2VlY2RjYmNjYmJhYGFhY2VmaWxqZ2VkYmFgXl5ehV0OXl9fXV1eXl1dXl5bWlmFWIdZA1hXV4hWBFhZW1uGXIBdXV5fYGBeXV9hY2RjY2RhX11eYGJiYV9dXFxaV1ZWV1hZWllXVVZWVVVWVVZXWLGxsbKxsLGxr6yop6enqKmqqqurrK+xsK+ura2tqqelpqWmqKmqq62tsLGwramoqKqqqa2wr6unpKGho6OgoJ2bmpeVmZqZmZeYmpydnqCkqD+rraWin6Gmo6Ccm52goZ+foKGhnp2cm5qXlpWTkpOWmJmamZqcm5ycm5ucnZydoJ+fnp2goZ6dnZueo6ChoqGEooCjpqiqrrW3u7/Aw8nM0NTW19nb3NvZ2tzZ2NXRzcrKymdpaWnNz83O0c/Q0tHPzc9qa2fKyMnKyMC8tq+srq6tpqKkop+hn52bpa63YWRhaWtlYWC/X2JgXl+7u7u9vrivs6+pqaikoqKhoqSnqFVXVKSkpKOlplNTVFVWVVZWVwtYWFhZWltcXV5fX4RgbGFjZWRkZWZnZ2hpa2tsbm9wcnN1d3h6e31+gISEhYaIjI6Sl5iWlJmbnZmbm6SlpqWoqKuuvrvAubu9v77Av7/Cy9bS09ba2NnY09PY0tLZ3N7l7u/u6evq8vj5+Pz9/fbx7eXe3NjV1dPV14Dg4uXm5ePi4uDf4+/y8O/v5+Tk5ejq6+7x9fn4+Pl7enz3+fb19PTx8fHz9ff49vj6+fj29fb08vTz9PT09ff5/Pv7+vv49PPy8O/v7u3y8Ozt6ujm5OXl4uHg4eHgbm9ubNvZ2dZqamlq0dHQzcrKyszPzc3NzMvLyMXDwMC9vSy9u7q6urm7urq6t7e1sq+urq6qqamsraqpqKalpaSjoZ+enp2cnJ6enJubmoSbJZmXl5WWlJOUk5OUkpKRj4+PjY2Ni4iHiImKioqLiYiIh4eGhYWEgz+Fg4OCgoGCgoD7+Pb6+/r7/f389fb49/f18vHv7u3u7Ozp6urq6efl5OTi4uPh4uPm5N/h4uHh397d3tvc3NyE2i3c2dnY2tjY19TW1NPT0tDQz9HP0dLPzsrKyMbFxMTGyMjIycrKyMfJxsXGysqEyYDKysfHxsbIyMbFxcbGx8jIyMbFxsbFx8rLzMzLyszLzM7OzczP0NDRz8zO0NDS0tPV2NfY2Nna2tva2trb2NbX2NnW1NTS09HPz8/NzM3Ozs3NzMvLy8zNy8nKy83Nzs7Pzc/Qzs7Pz9HQ0NDS1dXV1tbV1NXV1NTT0tPT1NTX2UvY2dza3Nzc3+Lj4uDk6Ofr8PLy8fPz8/T3+fn5/P+Bg4SEhYeKioqLi4qLjYyMi4qMjI+Pj5KUk5KRkI6Pj5CQj46QkZKRkJCQkZCEkgiTk5KRkZCOjoSPDo6NjIyMjY2NjoyLjY6Oho0oj46Ojo+Pj5CPkJCQkZCRj4+Qj4+Qj46Njo2MjI2OjYyMjIuLi4qKiYSIbIeHhoaGhIOBgPz6+ff29PLy8fDr5eLg4N/h4d/e29vb2tvd397c3d3g4ODi5eXl5uLg3tzc29rb29nY2NfY2Nzc2trY1dXV19fY2NfW2dnU1NXV1tXS09TS09TV09TU19jW1tXT09PU1dbX2ITbK9rc3NnZ2NjZ19fU1dbV1tXW1dPU1dbX2NfV1dXW19jW1NPR0tTU1NXU1diF3IDd4OPm6Orr7fHx8vP2+Pr8/Pn5/P/9///79/j9/oCAgIH//vv7/fz7/Pz8+/+BgoD9+Pj39/b08e/t7+/t6+3y8u3r7O3s8PX5gYKCiImFgoD/gYODgYH//vz7+/v5+vr29/f3+Pj29/n7/oGCgf39///+/oCAgYGCgoOEg4OEhHiFhYiIiYqLi4yMjY2Nj5GRkJKTk5SUlpeYmZqZnJ2doKGjo6Wlp6mrq6ytr7G0t7m5t7i7vr67vb7Ew8PExcfHyNLQ1dDT1dbW1NTW1tvg3+Dh4uLj4d/e4N/f4uLi6Ozs6+jp6fDy8vT29vbx7+3o5OLh4ODf39+egYOCvYGEgoSBhIL4gf+A/4CLgP+Bh4H/gLWAhIGMgIOBmYCIgQGAhYGUgIOBhoD/gYWBAgIEAIDg4tnb29nZ0tHX6vr37erh0svQ4ePs5uTh3uz9nLSxw8mZgYD88uTi5OXl5ujs6+rt8PLz7ejm5efo5+bo5ePg4ePp7+3o5OHd1NLOycXFwcDDycXBvry6urq8v96RpqaG3s3H4IOBg52W4+KB84HYuq6prq21wsK9wNnq2uDl1YDBydHR1cvDvL60xMfBv7++s6qmq6Sfk42Mj46JjJSipIyHgn6AfX1/foF/d3R8gYB9fH1/g4eHhHxzeX57f4B4eXR4e3FsZ2pudXp9e29mYmVfU1VVVlVSVWZva2ZwaWl0eOrNymRmaW5z49fT0tTS0M/HwL+4rLLEzcrLy8fBuICjpqqlmZaXlpKVmZaUkYeEiIyPkY6TnaKmp5+WkY6Ni46VnaOnpKCfnZ2el4+NkJaZlYuEhIWCgYKAgH98f4CAf3x9gYB+f4CBhoeFgX57e3p+gHt5e35+fXl0dXV2dnZ4eXt4dXh7fXt3c3Jzc3FwcXJycXd5fX19fHx9e3p4dzV4eHl5ent7enp6fYB/e31/f4CGh4OCgYKCgYKDg4B+fHx7fH5/fXt7gICBg4aGgoB9e3t6e4R+CH9+fXx7fHx8hHs0fHt7fH19fHx8fXx9fn1+fX1/f4KCgYCAf357eXh5e36Af35+gUFERkdISUlKS0tNTUxMTYVOJE1QUFJTVVNTUVBPT01MSkdGR0dHSEpNUVRVVFJQTkxLSkdGRIRBDUNEQ0NDQkBBQD9AQkGFPylBQkNDRUVERERFRkhJSUVFREVMUVNUVVRSUlBNTlFUVVNPTU1OTkxJRIRBgEA/QUA/Pj8/QEJERpCTmJWZm5ufqbCrl5WUlZqdnZ6go6amp6moqKainZmVkpOUlJWVmJydnZ+hoJ6cnJ2bnaKlpqWjn5mXlZKMh4WEhYWGjYuFfnyAg4iJkJSSk5OQjIuKj5GOipCTlpiTjIqKiIWCf35+fXh3dnd4fH+AgoCAF4B/f4GBgH9/fX6FjIyGg4SFhYOBgYGEhIaFiICKj5SVmZugpqyvtLrAw8jJycnIysrMzMrFw7+6uLe3uLm6vb26t7e4ubu/v769vmBhvbq2trSxrZ+ZnpuNiY+LiIOAfn9+f4CDik5XY2ZmbGlpYlxYV1ZXVldWVVSlo5udmZeYkY+PhoKBf4F/fn18fD8/PT4+Pj8/QEFCQkJDRBlFRkZHR0lLTE9PTlBOTk9QUlNRUlNUVVVWhFheWVtdX2BhYmRkZ2prbXd/e3t+f39/goWHho6PjomMk5KTmqGmq7C7wq64r6Glpqmqq7C0vsnLycrQ1tHS1NfT0dLT0tPa4uv08vL3+Pfy7vH29/Xy8Onh39nW1dbX2oDi49zd393c1dba6vj37+3l19HV4OHm5eXh3+r2i5mbpKeLf3348+rq7Ovr7O3u8PDy9Pb18Ozr6evr6urs6ufl5+rw9PLt6+nm3tzb1tLSzc3R1dHOy8nHxsXFx9h+iYh41MzK13V0dIR91ddz3HPNvLWytrW5wMC9vsvVysvNxYC5vsPBxL65tbWvuLm2tLOyq6WipqGel5ORk5OPk5aeoJCMiIaIhoaHhYeGgH6EhoaEgoODhoiJiIN9gYOAg4N8fXl7fHd0cnJ0eHx8enRua25qYmNhY2NgYmxyb21yb291eOnW1GlrbXBz49zY19nZ2NbRy8jEvb/L0NDQzcrGwSe3t7m1raysq6eprKimpqCfoaSlpaOnrrCztK6ppqOjoqOmqq6xsKyEqi+moaCipKaloJubmpaWlpWVlpOTlZSUk5OWlZWWlpaZm5mWk5GRkZOUkY+QkpGQjoSMDo2OkJCSkI6Qk5OTj46NhI4HjY2Oj5GSlISWAZeElheVlJaWl5iZmJWWmZqamZiZmZiam5mZmISXNZiZmJWUk5OTlZaWlZWYmJeYmpyXlpSTlJOUlZaXl5iXlpaWl5aWl5aWlpeXlpaWl5eXmJmahpsjnZ6en6Gio6Ggnp+enqCjpKOkpKhWVlhaW1xcXl9eYF9fX2CGYjRkZWdnamhmZWRkY2JhYF9eXl9eX2FiZWdmZ2VjYmJgYF5dXFlZWlpbW1pZWVlYWVhYWFlYhFdJWFlZW1tcW1tbXFxdXmBfXV1cXWBjY2RkZGJhYWBgYWNkY2FgX19fXlxZWFhXV1ZVV1ZUVFRVVVZWV6+wsrGzsrO2ur24qaenqYSrDayvsbCxs7KxsK+sqqiEp0+pqaqsrKutraupqKiqqqqsrbCuraqopqWkn56dnJuamp+fm5eWmp2enqGjo6OkoZ6dnaCioZyio6appaGgoaGfnp2cm5qYlpWVlpiZmp2ahpuAmpucmpyfoaKgn5+goJ+dnp6foKChoqKio6OkpqmusLO1uLy/wcbN0dTY2dna2tvb3d3b2NbUz8zLy8rOz9HQzszMzs/R1dXT0dFrbNPMycvJxsO7tbe1rautqqmlo6KjpKOkpq1eaHN1dnx3eHNtaWVlZmRkY2JiwL+8vLe3t7OAsK+tqqilp6enpaSlVFRSU1NTVFVVVlZWV1dYWVlaXFxdXmBiY2JjYmFiY2ZnZWZnZ2hoamtsbW1wcHJzdXd4eXp7fH+BiZCOjpGSkpGVl5maoKGhm5+lo6Wrsba6v8jOvMS8sLW1uLu8vsPL0tTT1Nnd2dve4t3b3NzZ2+Lp8fUW9fP3+vn18fL29/bz8Ovm497b29vd3gLk5oXkgOPj5O719fHw7ePi4uHh5ebn6e3v8Hp7en17e3589/f39PX19/j3+vr5+/v8+vn5+Pf4+Pf49/b19vf4+/39+vn5+vX08vHw8O/v7vHu7Onn5ePj4uLhb29vbtvb3Npra2xsa9TUaM5nzszOzs7NzczMysnGwsLBwL29vLy6uru5Lrm7ubi3uLSwrq2sra2srKuqqainp6alpqOioaCgn52en52dnp2cnJyamZiYmJaEmDOWmJWUk5OPjoyLi4yKi4mHiomIiIiGiIiJh4iGg4ODhIaEhYWDgoKDgvz9/YCCgICA/v6E+lP5+PTz8/Dx7+3v7Ovr6ejm5ujp5uTl4uLg4ODf4ODf4OHg3Nzd3Nra3Nza393c29ra19bY19jV0tLR0dDQ0M7Q0dHRzs3NycfGxsnKysnJyMjIyYbKZMvLy8jJy8nGyMjHx8bFxcXGx8bGx8bHyMjIyszMy8zKzc7Mzc7Ozs/T1dTV1dPP0tPT1NPV2Nva2drc3d3c2drY2NnY1tTU09XR0M/PzMjJy8vKzs/OzczKy8rLysjKzMvLzM2FzlLQ0M/Oz87P0tXV09PS09PT1dXU1dXU0tLU1dbZ2dze3Nne4uLj4uDl6+/w7+/x8/Xz9Pf6/f/9/4CDhIKDhoaHh4iKiYmKi4yMi4yMjI+Pj5CThJEtkJGRkpKQj5CRkpKSkZKSkZKRkZKUk5OSkpKQkJGQkZGQkJCOjo+Qjo+Qjo2OhI8GjpCPkI+PhJAekZGRkJGSk5KQkJCRkY+Pj46OjYyNjY+OjYyMioqLhIoliYqKiIeGhoaEgoGA/Pfz8/Px8O/v7erj4OHj4N7d3d3c2tnY2YfbgN/h4eLk5eTg4N3a2NfZ2dnX1dbW19fZ2NnX1tXW2Nna29jX09LU09XW09TV0tTV1NXT09PU09fX19PU1dPU09XW19jb3dzb29zd3dzb2djY19fW19bW1tfX1tbX2djX1tXY19jX2NfU1NXX1tTV19jb3uDg4OHj4+bm6Ovr7e7vX/L19/j7/Pv+/v79/fv8/vv4+Pj3+Pn7+/r6+fv+/v///fz8gYH7/fn49vXy7u7s7Orq7+zs7vHw7+3w8vT4gIWOj5GTkZCNioWEhYaEgoGAgP38+vz7//z6+fn6+vr7hPyC/YaAAYGEggGDhIV2h4WHiImKi46OjI6Ojo+PkpORkpKTlZaWl5iZmpubnZ6foKGjpKWnqKmus7GytbS1trm7vLrAwcG+wMbFxsrLzs/U2dzU2dTO0NDS0tPW1tzh4N/f4uXk5OXl4uPh4+Lh5Ovu8fDt8PT08O3u8vLx8e/r6efl5ITjnIGIgrmBhIKEgYWCBYGBgoGC84GDgIWB/4D/gIKA/4GIgf+AxICCgZmAk4GUgP+Bi4ECAgQAgNze29zg3NfY1dfn59vUz8zLzdnc2dLgg5meoYuVq6Oh2bydopb+5ebo5+rq6uzt6urv7/T09vh89/Tx9PX18enk4uTl7O3n4eHe2NPU09LPycLCw8PCw8TCwcXKy/SN8s3H0/yJloaC9cy45PnjyLOxubiws7W0sbOzu9DP2efYgM/U0dzdxL7Hyt7o5dfIy9vWxaefnJiZpJ+YlZWJh4aEhIGAgIF/gomJg4GBhImHf3l0dXVycXFwb2xpZmZmamtsdn95cGpoaWtyc29sZmNfW1VVWl1bWVlbW15eXF5fV11dubVXVVdZYWbX19DPyMO9tr22r66kqbnAvL7Dwby7gLKgo6Cbl5ibmpugoJmUjYyKi42OkJabn6Gek4yJiYqRmpygoqampZ6en6CckoqHiYqLgXt9f4B/gIGFiIeJiYeEgH+AgH9/gIGCg4OEhYWCgYCBg4J/fXt7eXh4d3Z4e3x9gIB+f4KCgX13dHFzdHZ4d3V2eXl6e3t8fX18e3p2b3Z3eHp8eXVzc3V5foODg399fYCBgYOEg4KCg4OBgIB/fn19fHp7fn58f4GEhoeHhoN/fXt7fHx9fXx9fn17fX9/fn1+fX17fH1+fn1+fnx6e31+fn+Bf3+AgYWEhIKAfXp5e36ChIVCQkNDREZHR4RIIklKS0pLS01NTU5NTE5OT1BRU1BPT1BQTEtKSUlKSUhJTVOEVTBUU1FMTk5NTElJSklKTE5LS0ZHSUZCQUJDQUBDQ0FBQkJER0ZHR0VDRkdHSEdFRkmETzdSUlNWVlZYWVlXV1ZWVFJRUVBPSkZFREJCQUFAQUFBQ0VGRo+SlZ6rraCdpK66zbGknp6jpKarhKqArK+wr6ypp6Shn5ybmpmampucnJ+ioZ+am56gpaaoqaempqKfm5OMiIaEh4iJi4uGg4OEg4SGh4mKiomHhoeLjIuIi5SWmpSSjoqJiIeBgH56eXl3d3h8f4GBhIWDgIB/fn+AgYKFg4aKi4iHhIODhISBfH5/fn19foOIi42OjpN9lpico6essrm9wsbJysnLymRkx8jIxcG9urq7uLe3t7m6trW2trddXmDCwr++v723tLeztLCcmJyen6Ofl5majo2QkJCWTk+dUllma3JycGpmZGNhXVtaXFemqq2dm5icUVFNmZWTlZORR0ZFRENERERCQkFCQUFCQ0NEREaESHdJS0tNTUtMTU1PT1FTUVNXVFRUVlhaXFtbW1xfYWJjZGZmZ2lsbXF+ipGMjI6Qj46SkI+Mj6KcmJOXn6mrtbfMw6WnpKKjrbWuq7S2vMHIysvM19LS0tXX1djX1d7o7u7w7O3v9PHq7fH3//v07enk4eDg3dnX2IDg4N7f4eDd3drb5+rh29fV0tTb3NvY4HyIi46ChZSQjrCgjI6H+Orp7vDx7+7u7+3v8/T29vb3fPn39fX5+vXu6ens7fH07+rp5uDb3N3d2tPR0NHQ0dDOzMvLzs/mfeHNydHoeYB3dd/IvNbi1cW4t7u5tLe4uLa3uLrFxMjPx4DBxcPIybq1urzHzMvDu7zEv7aknp2cm6CenJiYkI+OjIuJh4eIh4mNi4mHh4iLiYaCfn5+fXx8e3l4d3Z1dHV2dXuAfHdzcXFyd3h1c29samdjZGZoZmVlZWZnZ2ZoaGNoZsvIYmJjZWhr3NvY1tHPzMfKxcC/uLzFycbGyMbFxFi/tLW0r6ysrqyusrGrqaSioaSlpaaqq66vraejoKGipqqsrq+wsK6rqqusqaOdnZ2foZmVlpaVlJeYmpuanJybl5WVl5eWl5iYmJmZmpqZlZKSlJSUkZCQhI86jo6Oj5CSlpWSk5WXlpKPjoyOkJKUlJOTlJSUl5eXmJiZmJeVlpaVl5qZlpOSk5aYnZ6dmpeVlZaXmISXK5iYmZiWlpSTlJWUlJWUk5aXlpmbm5qYl5aUlJWWlpWWlpeXl5aYmJmYmJiElwmWl5eXmJiZmpqEmxmcnp+hpKampKOioaCho6SmqlZWV1hYWVpbhFwaXV5fXl9gYWFhYmJiY2JkZWZnZWRjZWVjYWGGYA9jZmhoZ2dnZmRhY2NjYmCEXypgYWBgXV1eXVtaWltZWFpaWVlaW1xdXF1dXVxeXl5fXl1eX2NiYmJkZGWEZhJnaGdmZWVkYmFiYWBeW1pZWFeEVitVVldXWFeusbK3vL22tLa8xMy7sa2vrq+xtbOys7KztLW0s7Kxr66tqqmphKofq6qrrKuqqKiqq66usLGvr7Ctq6mloZ+enJydnZ2cnISaCpucnp6dn56dm5uEnh2go6WppqWioKCgn5ycmpiXlZaXl5iZnJ2gn56dnYScKZ6fn6CgoaKhoZ+foaKhnpucnJ2cnZ6gpaipq6uur7K2ur7AxMrP1NjahNtlbW3b2trZ1tLPzs7MzM7Nz87Ly8vMzmhrbNjW0tHS0cnJzcnIxbiztLa6vLiztrevrrGxsrddXbpianV5gYJ/eXRycW9qZ2ZoZMHDxrq7ubtfX124t7W2tbVaWVhWVldXWFdWV1iEV39YWVlaXF1cXV5fX2FhYGFhYmRjZWdlZmppaWlqbG9wcHFyc3R1dnd5ent8fH6BhZCaoKCenqCgoKOjoqChsayppqiuuLrDxtfNtrazsLO6wb+6wcPKztLU1dbe29rc3t/e4N/e4+zx8vPv8PP19O7w8vf/+vTv6+jk5OPh3t3eDeXl5OTl5OTm5eXt7OeE5Afj4uLh4+R0hHYPd3l4eX17enp69vX2+Pj4hPlG+Pr8/P38/f+A//38/P38+/n5+vz8/v/9+/r6+Pf39vb18e7u7+/u7Oro5+Xk4eFw397g3d1ubWxt1dfV1NPR0dDPzs7NzoTNF8vJxsTCwLy7vb29vLu8vLq6urm4t7W0hLBGrKuopqepqqqqqKilo6Gjo6Genp2dnp2cmpmampubmpiXmJmYl5mZmZiVkpOQjIuMjYyLiYiIiYqKioiHh4iIh4WFhIWFhYSEDIWEhIL+/4GChYKBgIT+gPv8/Pr49fPz9PDu7uvr7evp6ero6ejm5OHg4eDh4eDh4eHf393c3Nza3Nzf3d7e397c29nX2NfW1tPQ0NHQ0dHP0NPT0tHNy8nGx8nKysrMycjJyMnMzczKzM3KyszKys3KxMXIycbGyMnIx8bHxsXGxcXGx8jLysrJycvNztDQgNDR0tXV1tnY19fZ2tna2tva3Nzd3d7h397b2dnY2dvY1tbU0s7MzMzLycjJysrNzczLysnJyMnKysrIycvKysvMzczNzs/Pzs/Q0NHS0tPU1dPU1tbX1tfX1tXS0tTU1tzf4N3b3N3b4OLo7e/v8e/v8/X19vf4+v6AgIGBg4SECoOFhoaHiIiJiIuEjBONjIyOj46QkpKQkZCSkpGRkpKShZEEk5STk4WShJMSkpGQkJGSk5GRkpKRkJGSkZGQhI8FkJCRkZKGkYmSC5GQkZKSkZCPjo6PhI4Gj46NjYyMhItbiomJiYiHiIeFg4KB/vv38vDz8e/v7Onj4uDg4eDd29ra2djZ2NfY2tnX2dja293f4N/g397d3NjV1NbU1NTV1dTW19XV1tXV19ra29va1dHO0NPS1dPT1NTU1YTUG9XU09PV1dXU09TU1dXW19jZ293c3Nva2Nfa2YTYBNrb2taF1w7Y2dvY19XT2dnX19jY14TWedfY2tne4OTn5+Xn6Onp6urq7vLz9vn7/Pz9/4CA/fz9/Pz6+vj49/j6+vz8+/v9/v6AgID//fn5+fb19fj38/Hv7u3s8PLx7/H08/T3+Pv9gID/gomRkZWXl5KQjYuJhIOBgYD9/v/5/P3/gICA//79/v//gICAgYGFgmaDhISEg4OEhYaHiImIiImLio2NjIyNjY+QkZOTk5aUlJaXmJmanJydnp+goqKjpKOlpqeprLS8wb+8vsC+vsHBwcC/zMnFxMbK0M/W2OLez8/PztDT19XU2Njb3t/g4ODk5OPj4+WE5hvp7O3u7vDv8PHx7e7w8/b08e7r6ejn5uXj4uOXgY6CkoEBgqaBAYKFgYSC+oGCgIaB/4D+gP+BioH/gKmAgoGUgIOBnIADgYGAkYGHgIOBhoD/gZCBAgIEAIDY3uHo6+fh4Nzb3NvV1dHNzc7T8vva6ZOfprK7tZeJrcmym5eQ9d7g4+Xq7+/s6+3w8vLz8fP3evf49vf5fX9+e3h5eXXp5ePh3trR0NHT1tnUz87Oy8nG0OntgvnUz8jI1dzr8+bKxcu+usfGu767ubm4s8Xh0LGzsbnBwdHf3YDR2uXk+IDy/uTs79rI1uTUvq+woZqZm5uSh4J/fXx6fIB/enqDiIeFg4WKnbCijoN5dHBwbnBvb21qZ2doZmdobG9ubWtqcG5oaWhkYl1cXFpZX2NhYmtqZWBgXFlaWlNYWFtgWlxdWllYWrrBwr+2s7K1sauspaKms76/wratqICmoJqdoqSmp6mmqaWeko6OjIqQkJSUk4+JhoiMi4+UmJiVm5+gpKOem5aTkYyHgYKGi4iHiISCf39/g4aGhoqHg4ODhIKEhIOCg4SDhYiKiYeGg4KDg4J+fHt8e3l4d3h7fH59fHx9e3h0bm5vcXN1d3Z3eHl6fnt8fX17eXh3dWh2eHh5eHd2eHp5e36AhIWDgX9/fn+AgoKBgYKCgoOEgoB+f4F+f4CBgIGDhoaFhISCf358fX5+gIKAf39/fn6AgH+Afn18e3t9fX1+fn59fHx9fn5/gIB/gIOGiouGgn9+fX6AgoRDRIRFFkZHSEhJSUpLS0xLSklKS0tLSkpLTE2ETihNTU9QS0lISUlLSkpKS05RUVBQUlFSUlFRUFBPTU5QTk5MS0lJSktHhEQdRkpMTklFQ0RFSEpLSkpJS0xLS0tKTExIRkdJTVKEVoBXWVpZWlpWUlJSUVFTUE1OT09JRUVGSkpKSUhJSkiTnaiqqqGcsMfV1sS2xdK7sq6xsrGys7GytbW7w720sq6qqKKfnp6dnZ2enpyamJecn6Okp6enpqeloJmVk5CSkJCSko+NkY2FhISGhYSDgoCCh4eGhIOFhouUmJuako6LioCJiYeCfXl3dnh2eHyAgoOGhYWDg4KBgYGFh4uOjY6PjIqIgX6Ag4KBfn57e3t9foGIkJWVlpqfpamrr7W5vcHGxsfJy2ZmZGNjZGNiv726uLWysrOvtLO2tlxevcDDw8LEwsC9vL67trSyrKSjoZ6ZnaOppY+PlZlPVlRUU1dbXStdZWtqZ2RhX1xaV1agoa6qWF6poKFRUVBPUE1MT1JQTUpISElISElKSkhGhEV+RkZHSkxKS0tKSktLTE1OTk5PUFFSUlRVVVZYWVxeXV5eX2FjZGRmaGlsbm9wcnV6goWHjI+NjYuGiIeMnqeip7OwqbGuqqaimpianKKlqKanqrDAx8jLytDY2tjR0NPU1Nbb4Obx8/Pw5+bm5eXl6fL39fPt6uXj4uHg393agNzf4ufq6OXj3t3e4Nza2dbX19ns8d/pg4qPmJ2aiICUpZiJh4Py5OXp7PDx8fDv8fX29vf19fd9+/r7+v6AgH9+e3x7ePDt6uro5N7b3d7f4N3Z2NfW1NLX4+R46tPQzMvU1t3g28nGyMK+xsS9v727vLu4wtHIt7i3u7++xMzKgMPHzs7Zb9PbzNHRxbrByb+yqqqfm5mZmZaQjYuJiIeIiomFhouMjIqHiYyYopqNh4F/fX17fHt7eHZ1d3Z0c3N2eHd2c3J3dnFycW5saWhoZ2drbWtrcnFtaWlnZWZlYWNiZWhkZmdlZGNky8/QzsjFxMTCvr+5t7rBx8jJwry4d7i2s7O0tLa3trS4trGqpKOio6amp6enpaKgoaSjpaiqqqipq6utrKqno6OjoZ2amZqcm5qbmZeVlZeZm5uanZyYl5eZmpubm5mYmZiam5uamZiXl5aVlJORkJGRkJCQj5CRlJSTk5WTkY+LjI2PkJKVlZaWmJqdhJkZmJeWlpaYmZeXlpeWlZWVlpaZnZ2cmJaVlYSWg5eFmECXlpWVlJSVlZWWmJmam5qbmpeWlpWWl5iZmZeXmJiYmZqam5qamZiYl5eXmJmZmZqbmpubm52cnqClpaerpqSkhKMrpKlWV1hYWFlaW1xcXV1eX19gYF9eX19gX2BgX2FhY2NkY2NjZWViYF9fYIVhGmNmZWVlZmVmZmZlZGRjYmJkY2NiYV9fYF9dhFwQXV9gYV5bW1tdXl9hYGBfYIdhgF9eXl9hZGZlZmZmaGhnaGdlYmJjYmFjYl9fYF9cWVlaXFxcW1lZWliwtb29vLe0v8vT08a+xcq9ura3tbS1trW1tri5vbu2tbOxr62rrKurq6ysqamnpqepqautr62vrq+tqqempaKjn56goZ+fop+ampucm5ucm5uanJ6dm5ucgJyfo6epqKSioKCgoZ+cmZeWlZeXmZqcnp+gn6Cfn56dnJ6ho6WoqKipp6SinZyeoJ+fnJybmZmcnqKnq7CxsbW4vsLCxcjKztLW2Nna3W9vbW1ubm5s1NPPzczLysvKysrMzWdo0tXZ2NTV09HOy87NycbEv7q6uri0ub7DwLGwgLe6X2ZlZGRna21tdHh4dXFubGlnZGK+vsTCZGnEvr9gYGBfX15cXWBgXlxbWlpaW1tcXVtaWVlaWVpaXF9gXl9fXl5fYGBiYmNkY2RlZmdpamprbG9xcnJzc3R1d3h5enx9f4GChIaIjZSYmp2fn5+dm5ybn622s7e+vrrBwLq4N7OrqaqttLW3tre5v83S09XV2uHi39vb3d7d3+Tn6/T19vPt6+no6unr8/j18+/r5+bl5OPi4d4o4uTl5+jp6Ojm5ufn5eXl5ubl5+fl5+h1dnd5eXl4eHp7eXh6evL09oT5Evr4+Pr8/Pz+/Pz/gP////7+f4SAgIGBgP///v78+/f39vX39/b08/Ty8O3s6+dy5eXn5eHh4N/d2tvb2dnY2NbT0s/Qz83NzMrLzs7NysfEw8C8vL++vL1eubm7u7u5t7e2tLOzsKqno6Kjpqmrq6ysq6ikpKeno6Ggnp6enJubm5iZnJ6enpybmpaWmZmZl5ORk5GNGoyMi4yMi4uLiYqJioqJiIqIhYWGh4aHhYWGhIUpg4GAgYOAgIODgoD//f7+/Pz6+Pb19vXx8/Dw7O3r6urp7evp5+Pg4uOE4lHj4+Pi39/e3Nzd3t7e39/g4N3c3NrW1tXS0tDP0M/Q0NHQz87NzczMyMXFyMrLy87MzMvMy8vMzs/S0tDMy8vJy8zKyMjIx8fIyMfHycjHxcWExoDIysvMzM/Q09XV1Nba2tnZ3d3b3OHe3t/d29ze3uHh4uLg3NrX29rU1NbV1dbX1s/KycnJyMnHx8nKycrKycjIx8fFxcXHx8jLysnMzs/Ozs3Nz83Nzs7Q0NHR09bU1djZ2NfX19jX1tbX2dvc3d/d29zc3d7j6e7s7PX08/X29hH39/j8gIKCg4SEhYSFhYaHh4SJBYqKiouMhI0Ejo+QkISRBZOUkZGShZMFkpOUlZWFkxKSkZOTkpKTkpOTkpGSkpKTk5OFkguRk5KRkZKSkpSSkYSSBJSTkpKFkw6UkpKSk5KRkY+Oj46NjoSPAY6EjUSMi4qKiomIiIaFhIGBgYD8+fbx8vHu7O7o5eLh4N7e3dnZ2NjW19bX19jY1dXV1tjX2Nza2tzc29rX1NLR0dHS0dLR0YXSLdbY2tvb2NDQz8/S0M/Q09PU1NXT0tDQ0tLT0dLS0dHT0tTX2NbW19rZ2trb2YTYG9nZ2NfY2NrZ2djX1tfW1tnb3d/e29rd3NnX2ITZbdfW1dnZ29vd4ujs6unp6urq6+7w8fL4/Pz9/f2AgYCAgYGBgP39+fv7+/3//Pz9//+AgP/+//76+Pf18vP4+PXz8PHv7+/w7/T59/n29/v+gYaEgoKFh4mJjI6OjIqIhYOAgID8+///gID+/P+HgAuBg4CAgYGDhIODhIWFhIYEhYeJjIWKdIuNjY6Oj5CQkZKTk5SVlpeYmZucnZ6foKGioqOkpaanqaqsrq6zuLq6vsC+wMC9vb2/yc7KztLRztPS0M/OycnJy8/Q0NDR0tTb3eDi4OTn5uTh4uPj4+bo6uzx8fHv6+3s7Orr7PHz8vDt6+rp6Obl5ebjl4GOgpKBAYKFgYiCloEBgqmBAYLlgf+A/ID/gYyB/4CngIiBjYCCgZ2AlIGEgAWBgYCAgP+BmYECAgQAgN3n7urp5+Pi4uXo497e39rY2dfZ2+Dg6ODw+4iMh4+XloHy6eje39/i5u3z8/Hx8vP49vd7e/J5fHx8e31+f35+foGDgn13debi3drZ2dvb2tnZ1tjS0dDP0d+D/vTS4+fP08jBwcbN9YWC0b++vLe7ys7F4Ifw0NLOycC/xdTlgODg397n69vQ0M3R3+br3r20yc3BqaGgnJCIh4WDgX2Ana+lnpOdusGytcHMtqKMgXl3eHt6dHBvfIF1c3Rxb29wamhnZmJhYV9gZmprZVtbXl9jY2Rob3BpYmZhXmFnaWNgYWNjzdDO1NDOyMC9uLOysra3trm4rrG+xMa+rp+XXpSXnaesrq2usLGysq+ki4yPjZGUkpKYmZyZm4+PjpCZpaqrsK6sp6SbjIaEhYaEiImFg4SIiYiFgoKFhICBgoSFh4aFhoiIhYWMjo2MjIyKiYmGhIODgoB+fn18fHuEekx8fHp4d3Rwbm1tcXJzdXd2eHl6fH17eXl5eHl5eXd2dXV2eXp6eXl6e3t9gIGBgoCAgH5/f4GBgYCBgoKDg4KBgoODgYCCgYCBgoOBhH84gX5/gH+AgYCAf35/fn5/f3+BgH58fH19e3t7ent7fH1+fn5/f3+Ag4mLi4aEgoF/gIGDQkNFRkiFRwNISUuFTIBKSEhJSUdISktLTEtMTU1NTE9PSkhJSEhJS0tLTExNTk9PTk5NTU1PTk5MSUlJSElJSUpLTU1MT1JTUE5PT01OTEhHR0tPTElITVZWUlBSVFNOTExKS0xNTk5QU1dYWFZXV1NRUlJUV1lZWFlXUkxLUlJRTk9KSZKUlJOXoampo4Chsb/IxLi9ztbLu7OxsrW2vcG+vMDIy8e9tbCvrKilo6Ognp6bnJuamZmXmZudoaiurq+xtKmdmpmbnJ6ZmJeTkIyIh4eKiYaCgH+AgYGAgIaJjI6Um5+el46MiomJhYF9fHp3eHl8fn+AgIKDg4KDhIWEhIaIjY+RkY+Kh4eFgoCCg4ODgoKCg399f4SHio+Tlp6iqa2wsrW6vcDDxMdkZmbJZGNiYsK/u7a0srGysrCvsbS4vL/BwcbHw8LAwMG+v8K+ube2sKuppKWen1mxp5iTmpxPU1RaXltZXGVlZmRfW1tcWVimV62upKCxZWJfYFtZXV5dWVlRUFFRUlFRToBNS01QUFBOSkhGR0dISUpNTk1TUlROT09OT09PUFFTVFVWV1hZW11eX2BhYmNkZWZnaGpseXdycHJ3e32CgYCAgYeNk5een6uqsLjBwbawsbKtp56cpaatu8K6uL3AxtTY1t/f4OTk493a2dvk5+7r7O7y8Ors7uzp6Ojt7/Hx6wnu7u3w9/bt4+A44Obq6eno5ePj5unn4eHk4uDf397f5OXo4uvzgIF/g4iIfO/r6+Xk5eju8/b18/P19/r4+3189nyEfQF+hICAgYODgn57ee7r5+bl5OXk4+Hh3t7a2dvY1t547ubU3t/P0svHxcjL4nd1zMC/vru9xsjB0nXZyMrGxL29wMjQzc7Ny9HSyMHBwMHHy87EsKy3uLChnJyalJCQjoyLiYqZpZ6blZqqr6anrrOmnI+IhIKEhIR/fHyChHx6e3p5eHmAdHNycW5vbm1ucHJzcGloamtubW5xdXZwam9raWptcGtpaWpq19nY2dfW087Ny8fFw8bGxcfFv8HHyszHu7OvrrK1ury8vL28vb2/vLSlo6SkpqimpaqrraqspaWkpKqys7O0s7Gtraidm5qbm5mbmpaWmZqampmXlpqZlpeZmpoZmpmam52cmpmcnp+enp2cnJubmpiYlpWUlIWTBJKSk5SEkxaQjo6NjY+SkpWWlpiYmpucmpiYmJeZhJgql5WUlpaXlpaXlpSWmZqampmWlZWUlJSVlZWXmJmZmpmXl5eYl5eXlpaYhZkHmJeYlpaYmISahJkLmpqam5ycmpqamJiGmTuam5ybm52dnqGkpqirp6Wmp6WlpadWV1laWllZWlpbXF1eX19gYGBeXVxdXl5eX2BhYWFiYmNjY2VlYYRfAmBhhGIEY2RkZIdjgGJgX2BfXl9gYF9hYmJhYmRkY2FiYmFhYF1dXmBiYF9fYWdnZmRlZWVjYmFgYWFhYmJjZGZmZmVmZWRiYWJjZWZmZWVlYl9eYWFfXl5aWLGzsrCzubu5t7a8xMrIvcHMz8a8uLe2t7i9vbu6u7/Cv7q2tLOxr62trq2rq6qop6amEKalp6eprK6ys7O0ta6op6aFpRyjoqGgnZycnpyampqYmZiZmZicnZ+hpqqtrKejhKEQnpyamZiWmJibm5ydnJ6foIafOaGjp6mrqqmmoqOinp2eoKCfnqKinZ6fo6Wmq66yuL2/xMbGys3Q0tTW2W1vb9xtbW1s19XSz83Ly4XKSMvN0NHS09jZ1dHQzs/NztDPy8fGw76/vL27vWbMxLa0uLxhZGVrbWpoa3Nxc3JuaWloZmXDYsbIv7zKbmtpamdlaWloZmhgYIVhgF9eXV5gYWFgXlxbW1tcXl9hYWJmZWhjYmNiZGRkZWZnaGlqa2xtb3Byc3R1dXd4enx8fH2AjYuGhYaLj5CUk5OUlpufpKmxsry6v8bOzsXAwcK+ubCttbe9yM7JxcrO0+Dj3+fp6+zp6eXk5ebp7/Px8fL18+zw8fDt6+zw8fPzCu7u7e3x9PHr5eMk5ebn6Onp5+jo6u3q6Ojq6+3s6+rp7Ovs7O3veHl5enp5ePH0hPdb+Pj5+vv6+/3+/vz+gID/gICAgYCAgIKBgoOEhISDgYD//vz8+vj29/f19/X28/Pw8O3sdOnn6OTk4+Lg4N7c2ttsbdrX1NPS0c7Ly8plyMzLysnHx8TBwMHCv4S9gL6/vru4tLOws7KuqaeloaKipaeqqaepq6ulo6empKanpqOgn52cnJ6foKChoaCcmZmWlZOSlZOQkJKQkI6MjY2Ojo2Mi4mLjIyLi4qIiIeHhoaEhYiHhYWDgoGAgoH++vz7+vf3+fv8/Pv49fP08/Py8u/u7ezt7vDv7e7r6OblC+Tk5eLk5OXm5uTgh9+A3d3c29vY2dfVz8/Q0MzMzdLT0dHQzs3KyMjJx8bFx8nJy8vNy83Ozs3NzNDS08/NysjJyMrMy8zKyMjJysnIyMjHxsbIycrKyszPz9DR0tXY2NrZ2d3h4eDh4eDf3t3e3d/d4eXm5OTh4N/e3dvb2dfW1dPU1NXU0MnIxsXFw8ROxsfHycnIysrJycbGxMTGyszOzs3Nz9DQz9DS0M3Ly87Q0tPS09XZ2dja3NvZ2drc293f3+Df3uDi4uHf4OLl6uzs7PHz8vL2+Pn7/4CChYMghISDhYeHiIqKiomJiomJioyMjo+Oj4+RkZGSkZSVk5KGkx2UlJOTlJWVlJSTkpGSkpOUlJWVlZOTlJSTk5SUkoSTCpKTkpOUk5OSlJSGkxSVk5KUlJOTlJSTkpKUlJKRjo6NjYSQfI+Pj4yMi4uLiouLi4qIhoWEg4H//v77+fbz8+/t6+vo5uPh3d3b29va19bV1NXU09TV0tTS0tTV2dnX19nX19TT09DQzs7Oz9DR0M/P0M/P0NHS1NHMzs/Ozc3P0dPU1NXU09PU1NLR0dLS0dHQ0dLU1NbX19bX2dna2dqE2A7Z2dna2trZ2tva2trZ2YTaAd+F4HHe3dva2trZ29va2dva29ze4ODj5OXl6ert7/Lx8/X4+vn+gICA/oCBgYD///37/fv7/f37+vv6+vr7+Pj6/fn29PHv7/T39/Ly8fDv8PDy8vWA/vn59/v/goOChoWFg4aLiYmJhoSFg4KA/4H+//79/YSBE4KBgoKCgYOCgoGBgoOEhYWEhYaGh3+IiIiJiYyNjZGRkY6PkI+QkJGRkpOVlZaXmJmanJ2cnqChoqSkpaWmqKiwrqurrK+zs7a3uLi7vsDDxcrK0s/S19ra19HU1NPPysnO0NHZ29fV2dvc4ePi5ujq6+vq5+bl5uzu8O7u7/Hw7e/u7evq6u3u7+/s7ezs7u7q5ubmm4GHgpKBA4KCgZGCk4EBgo2BgoKKgQGC74H/gP+AgoD/gYqB/4CogASBgYGAhIGngAGBhoCSgQKAgYWA/4GdgQICBACA6vTr5uXo6evq7evo5ebn5eXk4N7z/YL8gJKJ+fLy6Nre3drg4ODk5Ojo7vf69/b2fH36foCBgYKCgH5+fX59fn16e36AgH59e3hzc3Pn5OTi3tvb2tbV1NTV4vbn2+XazMjJ0M3m9JKrqZT848S6uLOzwMXS2tbp6+LX0s/Y+PSA2s7Dz9/dxMTEx9zu6eTaxr/Guq2jmZaWnqirpZ6cnMzu6LCstbO1vMvRz7uono6Uj42TjoqJgHd4fnxxbG1ub29qZ2RkY2NjZG5ydnNrYFxdXl5dYGRoZWFjY2ZrcXj79m9kY2XX4Ofs6ODWycO9tbO1t7u7vLu3vcHAu7WspphUlp6ipaSnrKmnqKqkn56Sk5WPjZCUmp6enZubm5+km5eRnKanqKmoo5yPg4CDhomMjY2KiYqHhYKAgICCg4WGh4iMjZCPjYyMjJCRkZCQjouIiYeHhYU6hIODgoGAf317e3p4dXNycnFwcHN1d3d4eXt+fX9/gYB/fn57eHh2dHJzen6AgX+Af4F/fn5+f4CCgYaAFYGBgoKBgoODg4WDhYSCgYCBgoOCgYR/OoB/f4CAgH9/f359fH1+f3+AgYJ/f359e3x8e3t7fHx9fX18fX5/gIOHiIeHhoWEgoOFQ0RFRkdJSUmESA9JSkpLS0xLSUdHRkZHSEiESghLS0xLS09PS4RGE0dHSElKS0xNUE5NTU1OT1BPT06ETAhLTk5QUlJSVoRXIFVSUU5MS01OT05OTElLT1JVWVhYWFdVUE5OTU5PT1FVhFeAVVNRT09RVlteYWRmZl5UVFVXUkxKTk9QUE2YlJKTlJeYn6Oirb3DwMnJw7G5wLi6yMzOxMPDxcbCvrSxr6yqrKuro6CenZ2dnJmWlJaZnKCmr7KxtbCuqqWjpqimpaCclpGTlZWNjomFgn9/gICBhIWIiouQlqClpZqVk5CLhYKAf359fnt6e359fXx8foCAgYKEhYSFh4iJi5CTkY+Ni4mJiIaFhIGAgoSDg4SJi42QlZmeoKarrrK1ubu9vsNjx8rIZGNiYWC9uLWysbGwsbG0trm9vr7BwsXCvr6/vb7Bw8XFv7m2t7i3t6+qpKSos1y6uVhZYFxcYmJiYGNnYV8zXFtYXFdRUqWkU6WpValaW1lcX1lYW1dcXFxVU1FUWVdWV1dWVlZUUlBOS0xKTE5LS0tNhE5hT1BQUVJSUlRVVldZWltcX2FgYWJjZGZnZ2lqbG1wfYaFgYGHlpOOj4+LjpqgpKGnr7K4u7y7t7SusrOuq6ilpaWwvMjU2OXy9fPx8vPv8O/19vl9ffj07+3t7ejq5uXm6ITsDu7t7u358u719fj9+e3pPeft6Obo6uvr6uzr6efp6+nq6OPj7/d99nuFgPXw8Ozl5+fm6eno6urt7vP4/Pr4+H1//4CAgYGDg4F/f36Ef4B9fYCDg4KAfnt3d3fv6+ro5uPh4N/f3dzb4evi3ODa0c7N0M7d4n+OjH/m18XAvLm6wMLJzsvV1dDLx8fM3d3MxL3CzMu9vb2+ydPNycO3s7Wupp+YlpWZn56cmZeYt8vIpaOppqettrq3q56YkJaTkJOPjIqFgYCDg3t2d3d4eBB0c3JycHBwcnh6enp0bmtqhGuAbnFvbGxrbnFzd/bxcWxrbN3j5ern4drS0M3JxsfHyMjJyMTJysjHwr25sbC2ubu6ury6uLm6t7OyraqppaSmqKutrq6trKuvsq2mpa2xsbCwsK6poJuYmJqcn56em5qbmZiWlpeYmZmampudn5+ioJ+goJ6goaKgoJ6cm5ybm5tZmpqamZiXl5eYl5eWlZSTkpKRkZCPj5CSlJaXl5iZmpudnqCenp2dnJqYl5aUk5aamZqZmpmbmJeYmZmZmpiWlZWUlZSUlpeYmZqbmZiZmZubm5qYmJeYmJmEmASZmZiYiJmEmjacnqCfnpubmZqampuampucnJ2cnZ6en6Glp6WnqaqqqaqpVVdZWlpbW1taWltcXV5eXl9gYF2EXAddXl5fYGFhhWIVZGVjX15eXV9fYGFiYmJkZWRjYmJihWMqYmJhYmJjY2RlZWVnZ2hnZ2VkZGJhYGBhYmJiYF9hYmVnaWlpaGhnZGNjhGIBZIRngGZlZGNhYWJlaGlrbG5uaWRjZGRhXVxeXl5dXLaysbGwr6+0tLS6xMXBxsbCur2/vL3Cw8S/vry9vb26trSzsrGxsbCrqqmoqaempaSkpaWoqqyys7K2sbCvrKqtrKusqaaloaKiop+gn5uamJiZmJqcnJ2dn6KnrbGvqaekoqCeZ5yam5ubmZqbnJubm5ydnZ6fn6ChoKCjpKWpq6urqqmopaWjo6KhoKCjo6Kio6eoqqyws7a5vcDEx8rO0NHU2Gza3NxubW1sa9TPzMrKzMzMys3NzNDR0tLT1dLOzs7MzM/R09PRysiEyTbDw77AxMxo0NJlZ29sbHBvbmxvdnBua2pnZ2RgYMHAYcLFYsRmZmVoa2ZlaGVoaWhkYmBkaGeGZoBkY2JhX19fYGFgX2BhYWJjYmNkZGVlZWZnaGlrbW5vcHN1dHV3eHl6fH1+f4CBhI+Yl5OTmqejn6KhnqCtsLGxuL7BxcnKyMXDvcDBvrq4t7i3v8rU3uLr9fj59/f49vb0+vv9gIH9+vby8vLt7+vp7O3w7+7u8fDy8Pnz8PXz9QT39e7pC+jn5+fp6+3s6+3thO4b7/Pz7+7v7nj1enl49fb29/X3+PX29vj7+fr6hP2A/P6AgP+AgYKCgoODgoKBgoKCg4KDhIWGhYOCgoCAgP77/Pv5+Pj29fTz8vDx7Ovr6+jm5uTh4OHdbm5ubdvZ2NjW1dTRz83Ny8jHxsbFyMrJxsjHw8C/vsDExMC/u7i1srGvraunp6Wjop+cmpqbnaKlpqelo6Slp6Wko6Genp5coKGioJ6amZeWlZSUkpOTkZCQkY+QkI+QkZGRkpCNi4uNj42MioqJioiGhoWEhISCgP/+gYGCgv/7+vn39/b1+Pv9+/n29vXy8fDy7+7t7O/x9PXx8u7t6+no6eqF5zvm5eHh393f3t/e3d3c2tnY2NfV0NHS0s/MztHW0s/Qzs7My8zLyMXFxsjKyszO0NDQzszLy83Nz87LyoTLgMzMy8zKysvKysrJycnKy8vMzM3O0dLV19jY2Nrb3N3f4ePk5OLk4uHh4+Tj4uLj5OTj4uDe3d3e3Nna19fW1NTT0tLRycbFxcTDxMTExcbIyczLysjHysrJy87Ozc/Q09LR0NHS1NPPz9HS09TW2NnZ2tzb3N/h4N/d3tze4uTkPePj5Obm5uPk5+Xl6Ovt6fD09ff6/PyBg4OEg4KCgoGDg4eIh4eJiouLioqJiYqMjY6Pjo+QkpKSkZGTlJOEkoSTJ5SVlZSWlJOUk5OTkpKSk5SUlZWUlZSUlJOUlJOTk5KSkpSTk5OUk4SUAZOFlAGThZSAlZWUlJOUlJKSkI6OjI+QkJGRj46NjYuLjI2MjImJiIeFhIOCgID/+/f48/Du6+jo6eHf3t7c3Nna2djW09DPz8/Ozs/Rz8/R09XV09DR09TS0dHQz87Ozc7P0M7Lzc3MzszOzs3Lzc3Ly8vMztLQz9DU0tPT09TU09HS0tHOzs8Rz9TU1dbU1dfY19jZ2dfW2NmE20Hc29ra29ra2tna293b3eHj4uLi4+Lh4N/d393c3N3e397d3+Hh4uPk5ubn6+7w8vT29/r8/4D///6AgYGBgP/7+4T8Rf39/Pv5+PT29/j69/Tz8fDv7+/x8O/u7fDx8fH08/L3/P+A//+Bg4mHh4yHhIOHj4qJh4aEg4GAgP//gP7/gP+AgYGCg4SCCIOFhIODgoaJhYeAiYmIiYmJiouJi4yKi4uMjY6Ojo+QkpCQkpKUlpaWmJmbm5yenp6fn6Gjpaampqepq7G4uLW1usDAvr+/vcDFysvKzdLU2NrZ19TV0tTV0tHQzs/P1Nnd4+Tp7e7t7+/v7PDu8fH0eXny8vHv7e3t7uzq6+rs7O3s7e7u7/Pv7O4G7e7r6enoloEFgoGCgoKVgQOCgoGagpqBhIL0gYKAhIH/gP+AAYD/gY2B/4ClgASBgICAhYGpgAOBgICUgQeAgIGAgIGA/IGCgp6BAgIEADH8/vLz9/Xx8evn5+fq7/Hx7Ono9IyXmI6XqqSO/fmB8+rq5+rq7e7s7fD0evj6+/yAhIOAhIWEg4ODhIOBf3+AfHt7fHyBgoGEfnl3dnbv7+fn3tvZ19fW1dTU0tPS1NbW7YifmoaF/vbd3t3JvcvDvMvi3N7t/IT24uDz+vLf5+jugIf47O3p7unTysrRzr+1p6SlqLHD2dnMvaqlsNiBg9jF2Mu/uLC8ppiSj4yDeXd3d3aAdHJycXFxb2xqamppaWpoaGhmZWdsbWxpY2FjZGVkYWFjY2VqamhteHuDio6HfXJpbXHp6uTc19PLv7Sxs7e8vbm4vb++uba0tLOzpp6bnJqhqrSnnJqPi4qKj4qJkZ+joJ6co6SioqOfn56QkJOYmZmam5uTioGGhoWJjI6Ni4tYhoSDgoKAg4eJio6MjY6SkpOTkZGSkI+Ojo6Ni4iIiIqGiIeIiYiHhoaFg4J/fXt5dnV0dHNyc3V3fHt7fH+EhYSEiYuKiIKAgH18eXh8foCDhYWFgoKCgYSAW3+BgoGBgIB/gICBgoODhIWFhoaFhoaFhYWEhYSEgYGAgYB/gIGAgH59fX18e3x/fn5/gICBgIGAfn18fX1+fHx9fn59fn5+goOGh4iLiYiKi4tGRUZGRkhJSUmESAFHhEksSkpJR0dGRUVGRkdHSEhISUlJSktNSUVFRkZHSEhISkpKS0tMTU5NTk5PUFCEUQdTU1NUVVVUhFOAUlJPTUtMS0tOT1FQUFFTVVRRU1RVVFVVUlFPTU5QU1ZXVlVUVFNRT05NT1JVWmBiZWRkYWBkZWJdU0xSU1FPn5yYlI+NkZKUm6qmqa6wrrS9tbS6ubm+w8jGxcLBv726t7Wzs7CxsqyopKSgoKCfnJeUk5mfpKamrLGyrKysq6yAr7KwrqqjoaCdl5SPi4WFhIB9fH6AhYeJjI+SmaGkpKGenp2YjoWDgH59fHx9fX1/gIGBg4OEhIOEg4WIjI2PkZKSkY2Ki4yLhoGChIOAfX1/goOIjI+Vm5+hoqersLS2ub6/wmFhYWJhYWFfXrm1sa+vrq+wsbW2trq+wMHAxMZhw8C+v7/Bwr+8ubKysbW2tLGvq6WqW1tbX1xdYWNjYFpVWVxeX1xcXFdUUlGgUqZTVFdbWV5cXlxaW1laWlZaWVxbXltfW1RUVFVUUlJTUk1LS0tMTU1OTk9RT05PUlJTVIRVa1dYWFlbXF5jZmNjZGZoam1ubW1ub3F1d3h5eXx9f4qUkJemp6OqpqOqrrK1sK+vramprqqoq6qrrbjAzPD3f4KEhYaFg4B+goqJgoSHh4aE/fHx6ejo6efq6evt7u3t8/b18Ov0/YX99/LzNfLy7vD19PDx6+nr6uvw8/Pv7ezygoqMhoqWkob593308fLw8fDx8/Lx8/Z8/P39/oCCg4ODhoR8g4OCgYGCf359f3+DhIKFgX17eXjy8e7q5ePh397d3NrZ2tnZ2drX43qIhHl46uXW19bJwsrEwMfTztHZ4XTc0dDb3trQ1NTVcHTa09XS2NPFvr3AvLKspKGhoaawvbyzq6Cdpb5sbr6zvrWwq6WtoJiTkI+IgoGBgH99fIR7AXmEdoB1dXZ1dHNzcnV3d3V0cXBxcXFvbm1ubW5ycnBzeXt/g4N/enRvcXPq6+Pc2NfUzsjFxsjJyMfGyMnJxsTCw8PEvLi3t7O2u8K4sbGsqKenp6Ojpq6ysa+tsbKvr7Ctrayjo6Smp6eoqqmjn5mZmZqdoKCenZ2amJeXmJianZ+goRehoqOlo6Kko6OjoqOiop+gn5ycnZ6bm4ScU5ubm5qZmpiXl5iWlJSSkpOUl5mbmpqbnKCjo6Smp6iloZ+gnZuYmJmZnJ6enp2cnJuZmJiZmJaWlZWWlpaVlpeYmZucnJubnJ2cnZ+fn52dnJqbhJoKmZqamZuampmYmISZLJqbnZ2enpycmpqbmpucnJybnJ2dnJ2en6Chpaioqqqsrq+wWFhYWVpZW1xchFsSXFtcXV5fX15cXFtbXV1dXl5fhGAFYWFhY2GEXgRfYF9ghmIEY2RkY4RkBWVkZGVmhWcIZmZlZmVlZGOFYQxiYmRkY2RkZmdlZmeEaIBmZWRjY2VlZ2dnZmVlZGNiYWFiY2Vna21tbm5sam1ta2hiXWBhX127urazsK6trKuwuba2t7e2uL66ubu7uru+wMDAvLq5uri3trS0s7Kyr6ysq6mpp6elo6GipKiqqqywsbKurq+vr7Gzs7GuqampqKWjoJ6cm5qYlpeYmZycnQ+foKOpra+wraysqqmjnpyEmzucnp6dm56fnZ6eoKGhoKCio6WmqKmsra2qp6eopqWioqOjop+en6Kjp6ussba5u7y/w8fKzdHU1ddrbIRtEmxqas/LyMfGyMnIx8rLzM/R0oTTStHPzczNz8/OzMvGx8fKysjHxsXCxmdnZ2poaW1vcG5pZWdpbGxrampmY2Fgv2DBYGJjZmVqaGppZ2hmaGhlaGdqaGxrbWplZGVlhWQBYoRgfGFhYWJjZWNjZGZlZ2doaWpqamxsbm9xcnd5eHd4enx+f4GAgYODhYeJiYqLj5CRmqShqLa3s7i2tLm9wcK9v7+9uLm+uLe7u7y+xs7Z9PuAg4WEhoWDgYGEiYmDhIeGhoP/9vXu7e/t7O7s7e3w7+/z9PTy8PT4f/f28O4l6Ofp7fDw7u7s6+3s7fHy9PPz8vF4enx9fX98ffj4fPr6/Pr7+4T9A/7/gIT/AYKEgy6EhIWEhYWFhoaFhIaFhYSFhoeHh4mFg4GAgP78/Pz59vb09PTy8e/t7ezr6OfkhHCAb93c2tvb2drY19TQzszNzMhkx8fIy8rKycrEwF9fv7/Cw8PBvry5trOvraypp6OioqGgnpydnKKlVFaopaipp6Wjo6Gfn56fnJubmpeWlpSTkpOUk5OTkZCQkJKRkJGRkZKPjY+Oj5CRj42NjIuLiYiJhoaEg4KDgYKCgICCgYBN/v749vX19Pj+/fz49/bz8fLx8fHw7/Ly8/X49/bz7+7u6+vq7evt6ejm5OPg4N7g3tvb2trX19bU1tbSz9DS0c/Q0NLTzszMzMvKzMmFxyvLzM7S09LR0dLQ0c/Oz87Pz83P0M/P0dHOzs7My8zLycrLzM3P0M/Q0tbZhNoa29ze3+Hj5+bk5eXp6efo6ejn6eXk5eHh39+E3RjZ2dra2NfV09HS0MrGxcbGxcbFx8jIysuFzXzOz9DQ0tXT0dPS0dHR0NLS09PS0tXX2NjY2tra2drc3uDg39va29vf4eXm5Obk4uPk5eXm6Ofm6+7v9fn5+fv8gIGBgoSDgoKCg4ODhYeFhIaIiouLiouKioyNjo6Oj5CSkpGRkZKTkpKRkZGTlZSVlpaWlZaVlZaVlJOThZQGlZWUlJWVhJQJk5STk5OUlZSTiZRLlZSUlJWVlJSVlpaVlJSVlZSTko+Pjo6PkJCPj42OjIuKiouNiomIiImIhoWEgoD//vv5+fbw7evp6Obi3tzd29va2dfW1dPS0M7Nhswszc7Q0c7Mzs7Rz8/PzszLzs7NzszMzM3NysjJy8vKycjJysnJzc3OztHR09WE0xrS0tLQz8/Oz9DR0tPU1dXV1tbX2dza2tnZ3YXfgN3b29zb3N3c29rd3d/i4eHi4+Lj4uHg4ODe3t7g4eLh4uPj4uPl6Onq6uzv8vb5+vr7/P+AgICBgIGAgIH/+ff6+fn6+Pn8+ff39vf4+Pf29PPw7u3t7fDw8fHv7/Hy8vb3+Pj/goKBgYGAgYOFh4SAgIOHiYaFhYSCgID/gP6ARICAgYGCgoSDhIWDg4SDhoSFhYeJiomHhoiJiYmKi4yLiouLi42MjI2OkI6Oj5GRkpKUlJSVlpeXmJqbnKChn6CgoqOmhKhAqausrq+vsLGzs7W7wL7DysrKzcvKztHT1NDP0M/NzdHOzs/O0NLW2d/s8Hp8fH1+fXx6eXx/fn19fX99fPPu7YTsFOrr6uvs7ezu7+7t7evs7Xbs7evplIGIggOBgYKMgQGChIGfgpSBhYKQgQGCioGCgpuBgoLFgf+A/YD/gY2B/4CmgImBqICXgQOAgYD0gZKCloEBgoSBAgIEAICC/vLy9f2B+oSI+/77/PT59ff+g5KQjoKNpp+WpLOqjpabiICDh/qHmJiBf3+AgYGDhIWGiIqKi4qIiYqKhIKAgYB+fn16e3+Af357e3l2dvLy8efo4d3b29fW19zi5ebu/Y2Hiojz5fqBg4KDh4b429Ha49jV097j6uvp/oPs+ICE+vHx6eqHgNPT3tfX19XLtqumqbO70PH76NbEsa+qr8fZycrCvLq3rqudj4aDh4F/eXZ3dnNycXJwbGptb3Bta2tramttamdoamhnaWRkZmtoZ2VhYWVlaGhnaXB0d3+EhoWAcmxtb+Ll5NzSzsi8tLS0t7y+vb6+vLe3tra1tiC0qaCcl5ypraimno2IhoiTm56koqGqq6iqqKWgoJ6Ym4ScKJeRk5WXlI2FhoaEiY6OjImHg4OFhISDhIaIi5CVlZaVlJWWk5ORj4+EjgKNjISNUouJiomIh4eHhoSAfnx8e3x8fHl5ent7e319foOQiomJioyMioqJhYKEg4KBgoOEiIqJhoSDgoKBf39+foGCgYCAgYGBgIGBgoODhoaFh4iGh4iEiQeIhoWEg4OBhIACf32EfDJ7fH1+f4CAfn6BgYGAf3+Af4B+fn1+fn19fn+Bg4WJjZCMiYqNjkdHjY1Hj0hJSUhHSIRHFEhISUpJSEhHRkVERERFR0hISUlKhEkMRkVFRUdKTU1JSUlIhEmASktMTk9RU1RVVFNUVFJSUlNRT05QUE9OTU1NT1BRUE9QU1VVVFFRUlBPUFBQT09PUlVUU1RUUlBNTk5PUFFSVFVXXGFiYF9fYGBhY2NgWlRSUU9PUaKfnJeSkZCXm52bl5qbm5+hoKSoq6mqq7Gzub2/vrm7vr6/urKurKurqqYQpqakop6alpaan6Olpaaqq4WqgK2xs7OtqaqvqpuSkIuKiIaEf35/goSIioyPk5idnp+en6Chn5qSjoqGg357fH6BhIeKi4uKiIWHiouLjo+QkpSUlZOSkZCQkI+Kh4WEgYCAgYWIio6RlZianaKor7G0t7i7vL9dXV1evLu6ubq4s6ysra+ws7W1uLq9wcTGw8LAgMC+u7y+xcXCv7exuLq7sKyws6mrXmBiXFpYXmNlY11ZVlZUWFZWVVhVUk+eUFFYWVRWVlVbWFhbV1hXVldWV1lYWltYVVNRVVZVU1BNTE1LSkpLTk5NTlFRUFFRVFRVVlZXV1hXWFlaW11gY2NkZmVpbmxtcXJvcHJzdXd5eXp+UIKGjJahoqyxsbG2vb28u7Ktp6alq7CvtLa4vMPFw8XI0eLq8vyDhYSDgoCBgoWIiYmMiImCgH/+/fL27u3v7+7w8/r7/f/+9O/2houB/Pj7gH317vH0+X/3f4L6/fr69/v18vd+hoaGfoWUkIqRm5eFi42Df4KE/IOMjICAf4CBgYGDhYeIiYqKiYeIiYqFg4KDg4GBgH5+goGBgH19fHl59PTz7evn5OHh3drb3+Li4ubvf3t9e+Xd63d4dnZ5eOTTzdHUzcrK0NLX19nlddjdgHPd2NfT1HRxyMfNx8bGxL2uqKOjqKy6ztTIu7Glo6Kms8C2trKuraqkopuSi4iLh4SBf4B+fXx7fHp4d3h6eXd2dnd1d3h2c3R1dHN1cnJ0dXRycG5ucG9ycXBwdXd4fX9/f3t0b29y5uTj3NfW08zHx8jIysvKy8rHxcbGxcbHSsW/uLWztby9urmzqaakpqqtr7OzsbW2tbSyr62traioqqmoqKWio6Slo6CbmpmanqKhnZ2bmpqZmZqdnp6goqSnpqWmpqWlpKSjhKIEoaGioYSgBJ+enp6EnVacnJqamJiZmJeYl5iYmZqbnJudo62nqKmoqKamp6WjoaKioKCfn56goKCfnp2bmpmXmJeVlpaVlZaWlZaXmZmZmp2dnp2en56goqKio6Kgnp2enZybm4ScCZuamZqZmZiZm4ScJ5uam5qbm52enp2dnp6fnp2dnp+hoqarrq+srq6tr1lZr7BZsVlbXIdbIFxcXV5eXV1cXFtcXF1dXl9gYWBgYWFiYV9eXl9gYmVkhGEBYIVhNmJjZGVmZ2hoZ2doZ2ZlZWVjY2RkY2NiYmNkZGRjY2RmZ2dmZWVmZGRmZGVkZGRmZmdmZmZlZIVidGNjY2RmaGtsa2ppa2pqa2tpZmJgYF5eX727tbKwr66ysbSxrqusrK2urbCys7CxsrS1t7m5ure2ubi4tbKwrq+vrqyrqqmop6WhoaOlqausrK2ura2trq6wsbO0sa6vs7GqpKKfnp2bmpeXmZmZnJ2foKSohKuArK2tq6mopKKgnp6dnZ6foKGioaSioqGipaWmp6ioq62urq2qqausrKqnpaWlpKKioqSmp6yusrW2ur7CyMvNz8/S09Vqa2pq0tLRz9HQzMfFxcbHyMnLzc/Q1NXV1NLPzs7Ky8zQ0dHNycXJzM/Jx8jJxMhpa25qaGZrbm9taWcnZWRjZWRlZWdkYV/AX19kZmNkZGNoZmZoZWZmZWZmZmdmZ2hnZWRjhGWAYmFgYWBgX2BiY2FjZWVkZWZoZ2lqaWpqa2tsbm5wcnV3dnh7en2AgIGFhoKDhYaIio2MjJGVmZ2lsbG6wMC/xMrKycnBu7e3trq+vcLFxsnO0dDR09vn7fT+hIWEg4GBg4SGiIiHiYiJg4GA/v319/Hx8vLw8vL2+Pv8/PPx9IAFg3v29PU6d+zq7e/zfPJ5evX49vTy9/Tx8Hh6fX59fH58fX9+fn1+fn9/f4D/gIGBgICAgYGBgoOFhoaHh4iHh4SIgIaFh4aFhoWFhoeGhoWEg4KAgP/9/v37+fj39vPy8O/u7evq6HJycXHj4uFvbWxtbGvW1tfSzs3Ny8nJyMrPz2bHxmLDwsDAwWFhxcbEwb67ubWxr66qqKWkpqqrp6SioaGjqauoqaqpp6WjoaCfnpuam5uZlpSTlZSSkpOWl5WSJpCQkJGTkpKQj5KSkI+QkJKTkY+Njo+OjouJiYeHhoWEhIGBfn5/hIBZ/Pr59/b1+Pz+/Pv7+Pb29PPx8/Tz9PT19vf5+Pf08fDu7Ovt7Ovr5uLf397f3dvc29nZ2NbX09TS0s/MzM/Pzc3P0dDNysnMzMrLy8vMzMzLzs/Q0tXW0tGE0gfQ0M/T0tHRhNJ809HQz87Q0M3Nzs3MzdDQ0NLY2NrZ2dnb3N3d4OTn6Ofn7PPx7+3q6Ojp6eno5OXm5OLh4N/d3NvY19jW1dLP0M/LycjIx8bGyMrMzs7P0dPV09HR0dDU19jY2dnX1dPU1tXU1dfY293d29vc29jW19vd3dzZ1dTV2dvc4oTlOefo5+bo6Ofo7O7x9fv68/n39viBgvr9gv+AgoOCgoOEhoaHiImJjIqJi4qKjI2Njo6QkJGRkZCRkoSTBpKSlJSXl4SVBZaVlpaVhpQClZSIlQOUlZWElAiVlpWUlJSVlIWVBpSVlZWWmISXC5aVlZWWlZWUk5OShJAFj46MjIyFimeJiIeIiIiJhoSDgoGA/v37+/j08e3r6efh3dva2tjZ2dbV09LQz9HOzMnKycnHx8vPzsvKysrMzc3MysnJy8rLy8jHysnJycjIycnJx8jJysvKysvN0dfY19nY1dLR0NLRz83Pzs/PhNAb09TT1dTU1tjY297e4OLf3+De2tra3Nnb3d/ehN8n4N7f4eLi5OTj5OXm4+Hj5OXn5ePl5ubn6ers7u/y9vn6+/z6+vv/hICA/Pn4+f///Pj39vj69/X19vf5+vn29vb19PHu7e3u7vDv8fLz9PX29vj5+v+Dg4SEg4OBgoOCgYGAgYGAgYODhIOCgf+AgIGBgICBgIKCg4SDhISEhYWFhoWGh4iIh4eJi4yKiomJiouLi4yOjo2Pj5GQkJGTkpSUk5WVlpaYmZkNmpqdn5+foqKkpqWmqYWrQ6yusbGxs7a4vMLJyM3Q0dLU19fW1dHPzMvKzc/P0dLT09bZ2djY3uXm6vF7e3p7ent7e3x+f35/f398e3r08u7x7u2E7A/t7e/v8PDs6ut2dnXr6usBgoWBBIKBgoKJgZOCAYGpgpKBhIKDgYaCjoEEgoGBgoWBgoLggf+A/IAGgYGAgIGA/4GIgf+ApYCEgayAl4EBgPmBkoKTgQaCgoKBgYECAgQAK/v6+PyAgIeOiIeLiY+TjIqQkpWOjYeIgoedlqHEyfLvg/PPwJWNiqKzn4mFhYCGh4eJjZCSlJSTlJaSjoiCgoOEhIJ/fn99fX16ent6enx/e/H57+nl6OXg3uHygYyH//bt6uXi8Yaep6+6t6WWg+Pn1s7W0tDIyNDc6eDQ2N7g2NPI5vXVwsPKysK5vL26tLbFz974hYv/4MO5r6a6vqmjqbPT0bemmo2CfH1/fhF7eXp9gXl2dHFvbW5ub29ubYRrf2xtbG90bWtsa2trb21nYmFiZWZkY2JkaG5zfH76d+fha3H5+fTt4dfRy8Vdt7a1tre5ube0sbW4uLi6ua2gmpeUl5yenZiOhIaNmKCkoJ2coqapr66rop6dl5WZnJiZl5STlZSSjYqHhYODiIuLioaEg4aGh4eJioyMj5KUl5eElVSUkpCPj4+NjYyMi42Njo+QjoyLiomJhIB+fXt6eXl6e3x9f3+AgoWIiYmJi4mHh4aFhISFhISDhIWFiIuMjYuHhIOCgn9+fn9+f4CCg4GBgoF/gYOEggmDhYWEhYSFh4iEihWJiIaFhISDgoCAgH9+fn9+fX5+f3+EgASDg4OEhIICgX+EgCN/f35/gYKDhoqMjYuLjIyNjo2NRo6PkEhIR0dHRkZHR0hISIRHCEZFRUVEREVHiEkLR0dGRkdKT09KRkaERw5ISElLTE1NTVBSUE9QUYRQiE8LTk5PUFFRUVJUVVOFT4BOT05OT1JTU1RUUlFQT05NTU5QVFZWVldXWVlaWFdcXVtbXV5eW1ROTU1QVlSsxbatqZSUmpyfnJaUkpWhpKGfn6Kno6SmqLG6trS1ube3s6+usK6sq6msq6eno52ZmZqcoaSjoaKjo6Okpqersba3s6uptLeyq6KWlZKPjYqIiICIiYmKjJKWmZydn56foKGin5qVlJOSiYWDhIWIjY+Rj46LiYqLi4yOjpGTlJWVlZaWkpGTlpaUkJCOi4aGh4qNjZCRlJmfo6eusbOxsbS1tLW1tLa2t7e3ubi0sK+wrrG1ubq8u7q/xcjMyb+8vL7DyMfFx8e9sKmvra2ztL1iYyFmZV1bWVlXWFlcXl9eV1ZYXF1bWFpbVlVUVlJVU1NVWFiEV4BYWFZWVlVXVVJTVFNSU1hXUlFQTUxLSkpLTExNTlBSUlJVVFVWVldYWFlZWlxdXl9gYmRlZmdobHJxcHR3dHR2d3t/f35+gIaPk5mZo7G2u7i2uLi3trWupqSnq6+0vMfKzNba3+Lt9vp/gISLj5CMiIWBgYCDhIWHhIGDhICBgxeA/v/6+fv5+vz+gIL9+fqBg4mMi4P19yv39vX6fn6EiIWEiIaKioWEh4aHhISDhYCDjouQpKfBv2bCr6SNioiSnJGHhYQHhYeHiYyOj4SQgJGPjIiEhIWFhYSBf4B/fn58fH19fH1/fPT28O3o6Obj4eLseX988Ovk493c5HqIjZGYlYqCdtbZz8rOysnFxsrR2NPIy8/QzMfB09rHvby/v7u0tbWyrq62u8PSb3LZxrOup6GusKSipKm9u6uhmZGJhYaHhIOBgYKFgX18e3l3BHh4eXeEdhF3d3h5eHp9eHV2dnV1d3dzcIRvgG5ubW9xdXd9e/R36eZwc/P17+vk3NnW02bJycjIyMfIx8XDxsjHx8nJw7q2tLCytbWzsamio6ivsbWxr66xsrW5t7SvrKupp6mqpqemo6OioaCfnZuZmJqen52dnZuam5ydnqGjo6SkpKanqKinpqanpKSioqKhoJ+hoKGio6WlXKSjoaGgn52dm5iYl5iYmZmbnJ2eoKGipqenp6qpp6Wjo6KhoqKhoaOko6Sko6OhoJ6enJqamZeYmJeWlpaXlpeYmJmampucnZ2enZ2gn6ChoqKjpKSioKCgn5+dhZwOm5qamZqampudnJudnaCFoQagn52en6CEnxagoKKipKerq62sra2tr7KwsVesrrFZhFqEWxBcXF1dXVxcXFtbXFxcXl9fhGAWYWFhX15eX2FjZmdiYWBhYWBgYWFhYoRjAmVnhGYKZWVlZGRkZWRkZIVjC2RkZGVlZ2dmZGRlhmQPZWdoaGhnZ2VlZGNjYmNkhmaAZ2dnZmZoaWdmZ2lpaGNfXl5fYWDC0MbBv7GusrOzsaqop6mvsK2sqq2wrKytrbK2tLKytbW1s7GwsK+urayuq6qrqKShoaOlqKqqqamqqqmqq6utsLW2tbCwt7i1squmpKOgn5uampucnJ2eoaSnqquqqaytra2sq6iop6elo6ELoKGhpKWmpqelo6OEpwSoqaytha6Ar7GvsK6trq2sqaamqKqrrK6ws7e9wcTIyszMy83NzM3Ozc/Mzc/P0M/NycbFxMbKzc/Qz8/T19bY1s/Ny8zQ0tHQ09TNxsHHxsfLy9JsbnJxamhnaGdnaGlrbGpkZGZqa2lmZ2hkY2RkYWViYmRmZ2VmZWZmZmVlZWNlZGNjZGOAY2VpaGRkYmFhYF9gYWFhYmNkZmZmaGlqamprbGxtbW5vcXN0dHZ3eHl6fH+FhIOHiYeIiYuOkZKRkJKaoaarqrK8w8jHwsXExcTDvba0tru+wsrS09be4ubq8vr9gICEi42MioeEg4KCgoOFhYOBgoOAgIF//f37+vv5+vv8f4AL+vf4fX6DhIJ99PQ/7+/u8np6fn99foB/gn99fXt5eHh5fX9+fX18foGBg4NChoSEgYWEhIKBgoKDhIOEhIWGh4eJi4uLjY2Ni4uJhIhziYiHhoeGhYWEgoSDgoKDgP///fz5+fbz8/LxeHZ26ufn5OLh4XJxcHBwb2xsatPS09LRzs3P0M7Nz83KyMjJx8bExMTFxsPAv769ure0sbGvq6qqVViur6uopaeqra2uraqppqOio6Winp2ZmJiWlJORk4SRLpKSkI+Pjo6PkpKSk5WWlZKRjYyQkI2LjJKSkI6KiYiJiomJh4SDgf2A/f+Dgv+E/UT6+Pr+gP/+//37+vn4+Pf39vb49/j5+vr8+PTy8O/u8e/u7Ojk4N/e3dva2tnY19TU1dTU09LQzszMzs7Ly8/Qz8rKzITNgM7Pz8zN0dLV19fW1NXX1NLU1Nba2dbV09LQzs3Ozs/S1djb29nY1dXV1tnb2tna3Nze397g4uXl6Ors7+/x8vPv7uvn5ubm5+fm5ebm5eTi397d29jX19XW1dTQzcnIycnGxsjLzM7Q0tTT0dPV1dLS09XX2Nrb3d7c2trc2tjYQtna29zd3Nzd29na297f3tvc3ODh4+Lh4+Pi4uTl5OXn5uXn6e3w6/D19PLz9/j2+//5/ID19/2AgoGBgoSIiIiJiYSKEImJi4yMjY6PkJCRkpGSk5OFlAmVlpiYl5aXmJeEloiVG5SWlZaVlZWWlZSVlZSVlZaWlZaWlZaWlpWVlYWWGpeYmJeZmZiXlpaXlpaVlJSTkpGSkpCOjYuLhYopiYiIiImIiIeGhIKBgoD/+vn39PLu7Ork4t/g3dzZ2NfV09HPzc3NzMmFxw3Gx8jLysjGyMnJyMfGhMgjxsfGx8jHx8fIyMjHx8jIycvNzc/QztHW1dTV0tPQz87Nz82FzFDNzc/Rz9DS0tTU1dbY2dzd3+Df4N7c3Nzb29rb3d/f4eHf4OHj4+Pi5OXm5Obm6Ojp6Ojp5+bm5enr7Ozt7/D09/r8/f38/vz6+fv9/P77+oX7Kvf08u/y8/Pz9/j7+/r09fTz8O7u7/Dw7u7v8fT09/f5/v7/gIOIhoOCgoSDFYKCg4GBgYKChIOFhIOBgYGCgoODgoSDgISFhYaGhYWGh4mGhoeHh4iJjY2LioqLioqLi4yMjY6NkJCRkpOSk5STlJWWlpaXmZucnJ2en6CgoaKlqKqoq62sra6vsbKzsrK0uL3Aw8PJz9PW09LU09PT0s/JyszNztLV2Njc3eDi5Ojs7nh3en1+fn59e3l6eXt9fX18e3p7Gnl5eXjy8e/u7u3u7u13d+zs63Z2dnV1devshIGcggGDsoKLgYOCh4GJgqaBgoLIgQaAgYCAgYGJgAGB/4D3gASBgICA/4GHgf+A0oD/gZGBmIKJgQWCgoGBgYaCgoECAgQAgIKCgoOBhIeJiIaMi5CcjpGci5KpqIWDh5KJi5bHstDu6eC5pI2Ru9XKnpuYjI2NjI6Mi4uOkJCPkJOVmZiXkIiHioeHhoF/fn59fHx7eXh5g4N/fvbu6ejo5+Ti4/L6+Ozl3d/n5OyPtLy/w7WXg/Di29/t9vzz5dnYzsvFxs3PP9zYzdTR2M7EwtTh3uDj2c7IyNPh6+vw8/rz3cuxpKSvn4uWmKq7sZmNhYB/fXt8e3t6fYGAfnx8eXRwbnBxcIRvD25ubW1ucHFxb21ubGxsa4VmgGhoZWNjaHZ7d3Z4d3RzcXp/enXj4dbJx2BdWltas7Kwr7CztLzDwsLCv7amnpuZmJ2hopiUlZKTmqGfo6akpKWnp6iloqalmpGVl5eXmpqYlZGLh4iJiYaDgoOHi4uHhYiKiIiKjY2Pk5OWlZaVlpeWk5KRj42Ni4uMjIuKiYqLQIyMjo+PjYmFhIJ9eXd3dnZ4eHt/hIiQlJOSkpGOioeGhIGBgoKDhISEh4qMj5KTj4uIh4SCgYCAgYKCgIOEg4KGhFGFhISFhYSFhISEhYeIiImKi4uIh4iIhoaFg4KBgX9+fn59fn+AgH+Ag4GDhYSCgoKDg4GBgYKCgYGBgoKDhIWHiYqLi4uIioyLjY2OjI6Oj5CERwtGR0dGR0dISEdHR4VGgEdJSUlISElKSklIR0dJTU5OSkdHSEdHSElJSUtLS0xOTk9OTU9QT09QT09QUVBQUE9QUE9PUE9OUFBRUU9OTU5PT05QUVBRUVJSUlFRT05OTk1NTVFUVlZVVFNSU1JRU1ZWVlhbXmRcVVBOTFBVVbOyubSyoZOTjpGYnpygpKWgTpqenqCkn5yfpLG2trSxq6mrrKqur66tra6traurq6Kcm5eWm5ycm5iZmZugoqSorLS4u7att8C+urSqo6KdmZWUlJKSkI+Rk5WYmZ2hooSjgKKgmZiXlI6KiIuMjI6PkpGQj42Nj46NjI6RlJiYl5WWmJaXm5+hn5yampiUkY6MkZWXl5qfn6Knq6urqKiqrK2ura2ur6+xsrKuraurr6+usrrBw8LBwcHDyMnIxcG/vsTGwcHFxLu2s7BZWlpfYV9ZVlVUVldaV1tfYGJkZGVgBmRiXl5dXYRbH1pWVVdYW1xcW1pYVVRUU1NSUlRUVVRUVFZXVVNQTk6ETX9QUFBVVFNUV1haWVhYWFlaW1xcXmBgYGFjZWdoam1vcHJ0dnp+e3t9fX+AgIKGiZGVlpmboq60sra3u7u6t7Kyrq2usLW5wdDZ4uPt9Pv+goeDgoSGjYyNhoH4/oGAf4CEgoCAgP+Dg4D8/P3+/v+A//78/f+AgIiIhouJgPz+gH9/gICAgoSFhYOIiImOhYeNg4iXloCDhIiChIqomq6/u7mhlYuNprKsk5ORiYqKioyLiouOkI+OjpGTlpaVjomIiYiJiISBgYGAgH59fHt9hYN/ffXx7urq6ebk5Ozu7eXi3dzg3eF/lZmdnZOCdt/Y0dPc4efi2NLQysfExcfIgM7LxcrHycO8vMTKysvOx8C6u8HIzc3P0dTRxrqsoqKpn5OamqKrppiQi4iGhYSDgoKCg4WEgoGBf3x5eHh5eHh4d3h5enp7e3t5eHd2d3d1dXV0c3JycXJxcG9vcnp+enh5eHd1dHl7eHXp5d3X1GhnZWVkx8bExMTFxsvPz87PgMzGvbe0s7K1t7iwra2qqq2wsLK0s7Kzs7Kzsa6ysKumpaWlpKanpqSjoJyenZybmZmanJ+gnZyfoJ+foaKjpaeoqqmqqqmo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iZOjqpiSjImFgYuG9IOHh/r8/evT0s7P2eNH/YPz3ube2s7O5vTn4+PZ1NjUy8rI1+jt7tzIubKemZSTlpuoqaaZjYyKi4uLiYiJhoiMiYB/fn6AgXt1d3h0cnNzcXBxcG+EcWZwb29ubm5ta2loaGlqamlmZ2xxcW1scGRibHFwbmprcnLgZ2BeXFtcXLe1WlhYWbi8wb28vLmzqaOqqqiqrK2tqqWfmpiXmZyfoqSmqKenqaWfnpabm5mYmZmampiQhYSHiYqLioiEhx6JiImNjI6SkZGSk5OTl5WVlpeWk4+QkI6OjIqKiYmEiD6JiomKiomGhoeHhoJ/fHl2d3l6fIGFh4qOjoyLjI2IhICDhYaEhYqLjJKRkZOWlpKPjYyJh4SCgoKEhIWDhIWFB4aHh4eIhoaEhTyEhIWGh4iJiYuMi4mJiYqKiYiFgoGAf39/fn5+f4B/gIGBgoODhYSDg4OFhIOEhIOEg4OEg4SFhoeHiIqFixBGjIyMRo6Qj5CORkZFRkZGiUcoSEdHR0hISUlKS0xMS0lJTE9QUVFNTU5OTUtLTE1MTExNTU9OTk1OTodPgFFRUFBRUlJST09PTk1QUlJRUFBQUVFRUlRTVFNUVFNRUVBPT05OTU1PUlNUU1JQT05OT1FSUFFXWlpbV1NTUk9OTE1OTFKorp2QkJGbq7nJta6cnKCgoZuhqqCXmJ6nqa63qqmnqKiqrrGvsbW2s7CuqaOdm5mXmJmXlZKQkZWYNJ2go6mxtba3srW3trOvrKimpKGfn56amJeVlpeZmp2jpqiqrK2sp6SgnZuXko2LjI6PkJKHk4CRj46OkJKVmZubmZmcoaOkpaahnZ2em5aRk5qbl5KTmJucoKWnpKGho6VUraurq62trq6urKypqquwtbvBwsPCw8XDwMHEx8PEwL7Dw8TExMXCwLe2W1mxt1amr1irV1tiYWNnaWxqbG9ycXJrZGFfW1lYWlxbW1tcXl9dWllWVAJSUIROgFBTVVZWVVRTUlJRUVJQUlRUU1NTVFZVVldcXl1cW1tdXmBiZGhpaWdmZ2hqa3FzdXZ6fYGIiY+Ni4qIhoOChYqNlpmboaSsvMC7tbq8uLi6uLa3uLvAwsXO1uDr+IH9+4GFh4iIhoKBgYH+/f7+goGAgoKDhYODhYWEgoKChISCEYGDg4OCgoGDhYmNjY2Mi4aEgIKDhYeIh4iIhYKHkp6cjYiHg4WHh4CCg4WLqLpuuJydmo2Lio2XpaWcl5iXkI+PjpCPjY6SlJORkJGTl5eVlZGPjIuNjIqHhIKCgoN/fXp6fYB/fXnu7Ovq6Ofl5ep6e+zr8Hl5eXyCioyDf3x7eHV8d+B1eHnm5+ndzczJy9HXPuF03dHTzMrDw8/Wz83Ox8LDwb69vMTNzs7CuK+uoZ2ampucpKOfl4+Ni4yKi4uJioeJioiFgoODgoF+fHx8hHsSent+fX5/fnt5eHh5eHh6eXh1hHQvc3JxcnR3d3RydW5scXV1cm5vc3PkbGhnZmVmZcvLZGRjY8rO0M7NzMjEwLu9vLqEu4C5uLOsq66urq+wsrW1s7O0saysp6qqpqaoqKqqqKKenJ2cnp6enZyfnp+fn6CioaOnqKiqqKioqampqKenpaOkpKOhoKCgn56enqChoqKhoaGio6OioqKgnJqamZicmpyho6Wrr6+sq6yqpqWjpKWmpKOlpqanqKmpq62rpqSjoTSfnZ2dnJuZmpqYmZqcnaChoJ+hoaChoJ+foKCgoaCgoqOjpqilo6KhoqOjoaCgn52dnJ2chJs0nJ6enqChoaGfoKOjpaSioaCjpaampqiop6enqamqq6usrK9YsbCyWbCvr6+zW1pZWVpaXIZdJF5dXl5fYGBgYWFiY2RkY2JiY2VmaGhlZWZmZWRjYmNkZGRjZItlhGYGZWVlZmdmhWQNZmZnZmVmZWZmZmdoaIRpEWhnZmVlZWRkY2NjZWVkZGNihGGEYoBlZ2dmZWNgYF9eXV1dXF++wrmxrq2wuMDIvbisq66rq6eqrqijpamrrbC1rKyqq6ysra6ur7Kzr66tq6ikoqGho6SjoaCgoaKjpKaorbCztbe2tra1s7GvrauqqaempaOjoaCio6Slp6qsr7CxsrGvr62sq6uopqWmp6epqqqrq4Csq6uqqqqoqausrrGys7GwsrS3uLm3trS1trSysrS5u7i0tLe6vL/DxMHAwMHDYsnIxcTGxsjIxsTDwMHCx8vR1dXU0dTV1M/O0dPPzs3Mz87T09PV1dTNzmdmy9Bkwslmy2ZpcHBxdHh6eHh6fX19eHFubGlnZ2lpaGpqamtsagZpaGZkY2GEYAJiZYRmhGV3ZGRlZWdoaGZmZmlqaGlsb3Jwb25vcHJ0d3l7fHx7e3t8fH6Dh4iJjI+TmpmfnZ2cnJqWlpaanqaprbG1vMjMx8LFx8TFyMXBxMfIzMzP19/n8P2D/vyChYeHhoSCgYGA/v///4KCgIKBgoOCgYGCgYCAf4GAgH+FgAt/f4GDhYWGhYWCgIB4d3l6fH1+fX17fX+Af39+fn19fHt6fXx8foGDRYODg4SEh4iMjpGRj46QkIyMjIuMjIuMj5CPj46OkJKTkpKRjYuMjY+Ni4mJioiIiIeEhIWGhIOA//79+/r6+fj2ennx7ux1dHRycnBvbm5ra2xsbGnRaWps2NnZ1tXT09TU0XbGZMvLx8PAwMHAvL29u7q4t7a2uLa0tLCtrbCytLS0s7Gwraqmop+cnJqXlZOSlJWTko+PkZOTkY6MjI2MjI2Rk4+RlJmfnp2WkY6Pko2OkZaYl5CNjY2LiYiJiomJiIiHiI6JhIWGhYOCgoD9gIGCgoGAgP//hICA/fr7+vr69fT39vPx7uvp6Ojk4+bl4t3g3t3b29vc29va19XW1dLPzc7Pzs3Q0dHT09LQzc/Q0tHS09LT1NbY19fY2dnc2trZ29va1tbX2NfTz9HS0tTS0NLU1trb29rY2Njb4OLh4OLi5eTh3+Dj5Obp7Ovv8vTy8vP08O/t6+qA6uro5uTj4d/e3uDi4+Lc19bU09PV09HOzcrKysvMz9ba2NbV1dbY2NbX1tbW2NfZ2tnb39/d3NvY2dna293f4eDh4ePh39zb3eHi4eLk5ePj39/j6O3t5uXm7PL29fX29vTz8u3t8PL09vn+gP/+/4D49vf8/4KDgoOFhIaIiYoeiYqKjI+QkZGSk5STkpOTk5SVlpWVl5iampiYmZqZhJiFlwmWlpeXl5iYl5eIlgeXlpaXl5iYhpcBmIeXA5iXl4eYEJeWlZSUkpCQj4+Mi4uMioqEiWKHiIiKiIeFhIOBg4OCgYD8+fby8O7o5OHj4N/d2dXS0s/Pz8zLyMfGxMHCxcbFxMPDwsLCw8HBwcDCw8PBwcTGx8jIyMrMzMnHxcXExsTGytDQzs3P0NLNycjHyMfHyMjJyobJGsrLzMzOz9HU1tfY2Nra3N/g3dvd3d7e3+DfheF/4+Xk5OPk5eXk4+Hh4uHi4+Tn6Obr8PP49/Ty8/T19fr6+/n6+/v9gP769vf6+vr39fTz8fDv8PL09vb29fP09vLw7urs7u7t7u7w8vX3+fz//4CA//+A//+A/4GDiIiLjo6MiYuOj46Pi4eFh4WEhYWGhYaFiIiJiIiHiIeHh4SGAYeEiRSKi4qLjI2Njo6QkpKRkJCSk5KSk4aWV5eYmpycn6GhoKCgoqOipqmqq62vs7e3urq6ubi5t7a3uby/wcPHyc7T1dLQ1NTS0tHP0NHR0tTV1tre4uXqd+3reHp7enp6eHd3d+3r7e54eHd4dnd4d4R2BHV1dXaEdQ92dnV2dXV2dnd3d3Z3eHecggGDuIKJgQWCgoGBgY+CBIGCgoKLgQGC6oEBgIeBgoCEgf+A7YAFgYCAgIGFgP+BhYH/gKCAAYGqgAmBgYCAgYCAgYD/gYaBA4KBgYqChIGjggICBABtkI6MjI2MjZCPk6e1wqOZlZOWkJqeiIODiIyVvt/Al5OKjJOQkaPC59K7trKjm5mSj5GRkZOdm5mbmJicnp6bnZiQkJiOjouIhIOEhIN/fHh1dHRzc3Pm6Ojn5OLh6/X79/3/+vn4iZmnrJuWn4WcgILr/PDg4+/l2eHk8YaGioX16vTm18zS5YD56uLf5eLezsTEzdDKycOyqKShmoyNnJyjnJmbnaaysqqhnpqZlpaanZqJgoCEioF7hH94eHt3dHNzdXZ0dHRycnBwcHFxcG9wbmxsbGtpaGlqbWtqb2xoaGxucHN2b29s3dtmYWBfgGHAX2C/vWBjxMTFwb69vrquqrK4vsLEyL+4raGgnpyiqKumoqCfoaKloZydoaCamJmZlZOQj4uHhoeMkpKPkJCMi42Ojo+QkZSWlpaVlZKSk5OVlZWXlpKQj42NjYuIhoWEh4mIiYmJiIiIh4mGhYWEhYWGgoKDhouKjJGOjouIG4eIiIaGg4KDhYeMkpOTkpKUk5KSko+Mi4uKh4SFCoaFhIeIiYqJiouFiRqIiIeGhISGiImJioqKiYyMi4uMjI2NjYmHg4SCCYB/f4CCg4OEhoSHJoaFhYWGhoiIiIeGh4WFhYaGiIeGh4iJi42Mi4yMRkZHR0ePkUdHh0YGSElISElJhkgNSUpKTU5OTU1MS01QT4RQB1FQT1BPT0+FTghPT09NTk9QUIVPOVBPT09RUVJQUFJRT05RUVFSUFBQUVZYWl1aW1tZWFdVVFRTVFRTUU9QUVNTUlFPTk5NTk5OTU1RU4RRgFNYWVJPUU1KTJyln5SgsK+sqq6hmpSPjI6RkJial5WYoKm5tbKtrq2pqKmqqqipq66urKqmnZiWlZaZnJyVkY2Mj5GVmZylq66vsK+wsbSzsa+qp6eopKOjoaCem5qam56gpKqus7W2s6+rpqKfm5qWk5GRkJKUlpeYmZiWlZOPgJCQkpOUl5yenqOmpqWjoqCgnJyZl5mco6qlnpqZmpmanaCempman6Kkpaeopqeoqamrr7CxsrGytbu7vr7CxcPDwLy6wb/AwLy/vry4uru7ubm5uF5dXrpbXmJlZWZraW1yd3t+fn9+foB8dWpgXlxeX2FiX15bXFxdWlZUUE9MhEoDTFBRhVSAU1RTVFNSUlNTVlhWVVdYXGNmZGJgYGBiZGdqbnF0dHd0cnl6fH+BhpOQioqTnp6WmZqNiYqSmJqhqK66zNfe3tfKycjEv8DJ0MjHxcjJ0eHt9/x/fvr5gIWHiIqKh4SB+/j7gYOEhIOGhoiMiYiHiYiIiImMjIuIh4mJiouJiIsJkJGMjpSbpJ+UbYmIiIaHhoaHiYyXnqWUj4yLjIqQlIWDgYSHiqK5po+Qi42Sj5Kcrsa5q6mqn5iVj4+RkZCRmZeXl5aWmZydmZqXkI+Uj4+Ni4eFiIiGgn98enl4d3d37Ovq6ujn5urx8/Hy8u3t7H6Gjo+FgoeEhICDdNzl39fZ4NrT1tbed3Z3dNzV29TKw8bQb9jRzcnKyMe/urq/wLq5taynpKOdlpWbnJ+YlZaWmqGhnZeVkZGQkJKSk4qFg4OFgX+DgX+AgX99fH2Cg4GAfnx6eHl5e317eXh3dXV1dHJyc3R2dXV1c3FxcXJ1d3lzc3Di421qaoBoadBoaNDPaGnT1NTSz87MysO/w8fIysrLxcC7trSysrK1uLWxsLCwsbSxrK2wrqioqammpKWkoJ6fn6GkpKKlpaOhoqSjo6amp6mpra6qp6empqSlpqalo6Slo6KgoJ6enJyen5+gn56en6CgpKKgoKChoqajoqWmqaiqrqqqqSympKWmpqanpKOkpqirrKysqqmpqainpaKho6Ogn5+fnZ2bm5qcnqGipKaiooSkJ6KhoKCgoaKjo6SlpaampaSlpKWop6ilpKCdnp6fnZqampudnqCio4WkDKamp6iqqaeoqaqoqYSqC6mpq6qrrKytra6whVkHsbFaW1paWoRbIV1eXl1dXl5eX19gYWFhYmRjZGRjY2NlZ2ZnZmZnaGdnZ4RmA2VmZYRmAWWKZgxlZWVmZmZlZmdmZWSEZiJlZmZnaWpsbm1tbWxramloaGhnZ2dlZGVlZWRjYmFgYGBhhGCAY2RiYmJhYWRlYV9gXVxdur66srfAvrq2ubOvqaWjoqOipaWjoaOorbKxsauvrqurqqurqqqrrayrqqijoaGgoaKjpqOfnp6en6CipKmtsLK0tbWztLOysK6rq6uqqainpqWkpKSmqKmrr7G1tri3tbOxr66tq6qpqKioqqusra5Ur6+tq6qqqqmrrK2wsrS1t7e4uLa1tri2trW0trvDxsK9ube3t7i6vbu5uru9wMLDwsDAwcLExMTHxsbIx8fMz87R0tTU0dHPzMvPzczMy83Pz83OhM8u0M9paWnSaGtvcXJzd3Z7f4SIiYmLi4uNiYN5cG5tbW9wcG1raWtqa2pmZmJhYIReB19iY2VlZmaGZ2pmZ2hoamtqamtscXZ4d3Z0c3R2d3t9gYOGhoqGhYuMjZGTmKWhmpyjrK2lqamfnJ2mqaqyuLzH1+Hm5N3V1dTPyszT2tPR0dLV2uTz+f6AgP36f4SHiIiIhoOA/fv+gIGCg4GEhIWHhYSDhYQVh4aGhYSFhYaFhYOFiImGh4yQlZOLBHt7e3qEe4B9fn5+f35/f4CBgoKCgIF/fX5/g4WFhIeHiYyLjI+RlZKSlpqVj42NjI2OjY6RkpKRkZGTlZWTlZOPjo6Pj46NjIuMjYyKiIaEhIOBgID//vz8/Pv6+ff28u/s6unoc3NycW9ubWtra2pqa9TV19TX2djY1NHRaWdlZMfGxcbFxYDGw1+9vr27uLa3ubq5trWysK2wtLa0s7GwrKmnoZ+bmpmXlJORkJGSkY+Pj5KUko+IiouPjoyRk5KSlJKVoqSgnJeQjo2MjpOXlZCLi4uMiomLjIuKjIyMioeIhoOEhYaGhIOA/v+BhIOAgP+AgP//gID//fz9/fn49fj28vHq6Ufn5OPj4uTm4+Hg3t7f3d3d2tja2djX1tTQ0NHS1NbU09PT1tbT1NXU1NPS1dbV1dbZ2NnZ2eDj4dzW09PS0tHQ0tLS0dLT04TWgNfW1tXV1NLS1Nja4N/f3uDh5ebn6Orr7O3v8vDv7uvt7/Dv8O/q6enp5uTj4+Ti3t7f4N/f2tjY2djY2djSz8zLy8vN09XZ393d3dze4N7Z2dnb2trZ2dzc3N7e3drc3d3f3+Dh4+He397e3dfX19rd3d3g5efk5ebl5urv8/XxRvHx8vb19/v8+vn4+fn19vj39fj8/4GCg4KA/f+ChISDg4WDhIaJjIqGiI2PkJCRk5OSkZKSk5OVlZaWl5mYmZiYmZqbmpqGmQSYl5eYhZkBmIaXhJiCl4SYBZmZmJiYhJldmJiYl5iXl5eYmJqamJiYl5aVlZOSkY+Ojo2NjIuKiouMjImIiImKh4aFhIODg4WEgoH++vf39O/p5OLj3+Dd2tfU0c7NzMvJycjGxL+/wcPBwsLCwcHAwb6+wMDChMQcxcfGxsbFxsrNysfEwsHDw8bL0dXT0NPTzsnHxoTER8bHx8fGxsfFxcbIx8rNy83P0dHT1tbX19vd3Nvc3tzd3d/g397e4N/f4eLh4eLh4uLh4ODi5OTl6Ofp6+7w9vr79fPy9PPzhPSA9ff4+vv5+fXz8fP29/f39vLw8PDv8vLz9PT29fLy8O7t6+vr7u/w8vP09vn7/P7//4CAgP+AgYWHhoeKi46SlpaYmZuampybl5CMi4mLi4uMioiIiYiIiomJiYiJhoaHiIiKi4yMi4yMjY6Oj5CPkJGRk5SUk5OUmJucm5mZmZozm5yeoKOkpaaqqKWqqqyusLS8urm6vcLCv8HAu7u7vsLCx8nM09nf4uLe2dnZ1tbX2dvZhNgL2+Dm6+53du3rd3qEexJ6eHbr6ut2d3h4d3l5d3Z3dXWEdg13eHh3d3Z3eHp6eXd4hHcFeXt9fXvWgpCBjYKLgYSCiIEBguGBgoCFgQeAgYGAgIGB/4DvgIWBgoD/gYaB/4DMgASBgYGA/4GIgQSCgoGBiYKDgaaCAgIEADGUko+Mi4qLjZCVs7S2sqGamZqYlpyPkJ6nt8O6s6OvwKeep6ShqbDNyMO/xsvDtqaehZmAmJeamZ2foKOjoaCnnpGNjI2MjIiJiIWGiIB6d3d1dXd1dOnn5uXm6/L6/v+DgIKJlqKksLS1sbKnoJKRgvPx8/T0/vXt4+jygYLv493b3M/Qz+Px9Ord4uDV1Nzbyr+/urSxsLC60tGpoKrF2dbGwM3Uz9LHwa2loaCcl4+NjocQgYiMh4N+f31+fHh2dXVzdIdzgHJzdHZ0dHJxcG9vbW1ub2tqampsamttcHRyb2xqbGtsbWllZGVnZmNkY2JjZWRgX1+/v7i0srO0ucHIyMfDsKOZm6WpsLKwra+up6GempiYm5qamZeSjIuJh4iKkJWWlpuclpORj4+Sk5GSlZaVk5OUlpOSkpSUkZGRkpGPjYyLcIqFiImJiIiLjo2KjIqKiIqPl5aOi4iGhISEhYmOmZqamp6amJOQjoqIiouJiIqQk5STkpKSlJSTkpGOi4qJiYeJioiHiYeHiYqJiouOkI2LjIuLi4qJiIiJioqKjI2NjIyMjY6NjY2MjY+NiYaFg4SEgy+ChIWHiYqLjY2MiIWEhIaHiImJiomLi4mKiYmIiIeIiYuNjo2OkI+PSEdHR49HR4VGBkdHR0lISYRLGkpJSUpKS0xNT09OTk9NTlBRTlBSVFNST01MhEsOTU5OTUxNTk1OT1BSU1GHT4BSVFVTVVZVVFNUVFJQUVNSU1dbX2FiY2JgXVxaWVlZWFdWVFNRUlNTUE9NTU5OTkxMS0xNTU1PUVJUVVZWV1lbUFGhqKemq7emmJCQjYuLi4yMjI2UmJaWnqOlqKuwraioqKenqaekpKKio6akoZyYlpiZmJiYlZGNjIyPkpOXnYCipqaprK2trKqrq6qqq6qmo6GgoaKhn5+doKGjpqyxtrW1s7Gwrammop6ZmpuZmZudnp+enZmWlJOSkZSXm5+ipqirqqikn52dnp+fnZyhpaqrpqKenJybnZ6cmZiZnKGgoKKjoqChoaOnqa6ur7CvsLG1tbW0tbW2tbSxsLOxtUe1s7S5uLa2tbe2tl1gZGBhYWFla3BvcXBtb3qBf4KFhHx/gISDdG1sdXp8dGVhZGhkYmVcV1JSS0hHR0dISkxNTlJUWFlWVIZTgFRXWlpYWVtfZGZna2xrbnJ4f4GFgoF+foOIi4yNjo+WmJiVmaOnn52ppZyjoLjBvLWvuMTU4vX/+PHp3tfT0tLb4d3Z2Njb4O319/bz9YCEhoSEg4ODhIKBhIaJjIqIiZCKiI2Oj4+QkZOTkpWXmJWUk5SSkY6PkJKWqZqSkJKTAZIPjIyJhoWFhIWIi56en56ThZCAlIuOlJeiqaKelp6rnJmdnJujpbe1srC7wLqtopuXlpeYlpWWl5eYm52goJ+eoZqRjY2OjY2JioqHiImDfnt7enh5d3Xs6+ro6evv8vT1fHp6foWMjpKUlJCRioZ/f3Th4uTl5evl4Nvd43d339bQzc3GxsXQ2NrSyczKxMPHxrw0trazrqyrq7LAvqagpLPAvK+rtbm0t6+qnZiVlpOQi4qLhoOHhoSCfn+AgoKAf399fH1+foR8HXt7e36AfXt6eXd3d3Z1dnd0dnZ0dHN0dHN0dHNyhXGAb21sbG1ta2traWpsa2ppadHQzMjFxcfKzM/PzsvAubGxtba8vru5vLy1sK2sq6usqquqqaelo6GioaOlp6enqKqmpaSko6SlpaanqqmmpqqsqqinqKenpqalpaSkpKKioaCgn5+goKKioKGfn5+ip66up6WkpKOio6Onq7i5t7MdtrS0sayrqKenqqmnp6mqrKytrKurq6qoqKWjoqOEokWgoKGfnp6foKGjp6uopaSjpaWkpKSjoqOioqSmp6alpKWnp6ampaanp6Sin5ydnZ2cnJudnZ+io6WoqKajoqSlp6ipqamEqgWtrq2trYSrEa2vsLGwsa+vWVpaWrNZWlpahFsDXFxdhF6EXwZgYGFhYmOEZCFlZGZnaGZoaWlpaGhmZWVkZWVmZmVlZWZnZmZnZ2doZ2eEZjRlZWdoaGdpamlpaGlpaGdoaWhoamxvcXFycXBubWxsa2tqaWhnZmVlZmRjYWBgYmFhYGBfhGABYYRiNGRkZGVlYF++wb+7vca6sKuqp6Wjo6GgoKCjpKOjpamrqq2vrKmpqaioqqmoqKWlpqenpqKGoSyioZ+dnp2dnqChpKirra6ys7KxrqytrKusraupp6enqaempqaoqaqssLO3uYS4X7a0s7CtrK2trK2ur7Gzs7OxrqyqqaqtrrGytbi6u7u6uLSys7a3t7i4u7/FxcG9urq5uby8u7m5u76/vr2+vr28vb2/wMHFxcbIyMnMzMrKy8rIyMfGxcPDw8XGxcbKhMwwzc7PaWxwbW1ub3J5fHt+fn1+iI6OkJGQio6Ok5OEfn2Hi42Fd3Fzd3RzdW5pZWVfhV2AXmBhYmVnamtpaGhnZ2doaGlrbm5tbW9zd3p7fH5+gISJj5KVk5KPj5OXm5ucnp+kpaelqrK1rqy3s6qyrcTNyMK+xM/d6Pb/+vXu497a29zk6eXh4eDj5vH3+vj4+oGEhISDg4OCgoGBg4SGh4iGhomFhYiJiYqKjI2NjY6PkI8Rjo2NjIyJiYmKjZaNi4uMjYx1fX18enl5eXt8e318fn5+gIKCg4SGhYeFhIOEg4OEiI6Lj5CPkJOTl5mcnKWnoZmTlJOSkpKRkJGTk5OVlpeZmJiZlZGPj4+QkI+Pjo2NjomJh4WDgYKCgP/+/v38+/j49fN4dnR0dXR0c3Fwbm1tbGxratjahNwY29va29pta87OysjEw8XFw8G/vLu6ubi4hbkkuLazs7O2tLOxr6yoo6CenJqcmpeUlI+LjY+Oj4+Oj4+Oi4iIhIo0jZKTlZSSkZOXl5OSj46MjI6Ul5SPjIyLiYiIi4uMjI+Pi4eKiYaCgoKDgoKBgoCAgYOCgYaAeoGCgoGBgf/9+/b1+fn08O/s6efn6evp5OHh4eDg4N3d3Nnb3dvY19fW1tXW3N/g29ra1dTT0dLR0tPS0tLQ09TX2dfU0tfd3drY2NfZ1tPU1dbY2Nna29jW1dbW1NXU1NXU1dTY3eTl4+Hj5OXl5+jq6/Hy8vT08/Twhe0r7+3r6ejo5ubl4+De3+De29jX19XX29vb19PU0c/P0dXW2d7i4N3g397h3oXbatnb3d7f3tzb29vd397c3t/f4d/d29rY2dfV1dfa3N3f4OHh4uPk6Ozu7/Hv7fHy8vH1+v///fv6+Pr8/fz+/v79/YCChIL/gIKFh4aHhoWJiYmLi4aHjI2NjY6Pjo2PkI+RlZaVlpaXmZiFmQKbnIabAZqFm4SahZkMmJmZmpqZmJiZmZmYhJmGmhuZl5eYl5aXmJiamZiYlpSTkpSUk5KQjo+Pjo2EiwmMi4qKiYqKiIWEhD+DgoGCgP78+vbx7uvo5OPf3dvZ1dHRzc3MycjJycbDwMHAwMC/wcC/v7/Av7+/wL/BwsTFxcTExcXHx8rLyMaEwhXFxcjL0dLRzszHxcXDw8PFxMXGxcOFxIDDxcbHyMrMz9HQ0dPT0tTX2djZ2tzb3N/f4eHg3+Hh4N7d4ODg3t7f39/i5Ofm5ebo6err7vT29vTz8vHy8/X28/T1+Pv59vf39fLv8fP09Pb28/Lx8/X5+fb19/bx8PHv7ezr6+3v7/Hz9Pb5+/3+/4CDhYSFhoaIjY+Qk5STlC2doKGjpKOeoaOmpZuWlpueoJuRjY6SkZCSjo+Mi4iJiIiJiYmKi4uMjZGRj5CEj0uQkJGUlZWVlpaZm5ydn5+foaSnrK2vra2rrK+xsrS1tri8u7y7vsPGwsDHxMHEws7T0M3M0Nbe5Ovv6+jm4Nvb29zg4t/f393f4OeE6QbqeHl7enqEeRR3eHd3eHl4eHl4eHd4eXh4eXt7e4R8Bnt7e3x8fId6BXt7fH592IKKgZGCi4GCgv6B/4DsgISBAYD/gYeB/4DKgP+BkYGzggICBACAkZOUk5KQkJKTl6ilr7Oxq56YlZecmaG6wrymmLHD2OG/pKmovsK0tLe9yefk4NfHx62npqalo5+dnJudoaOpuLizsqWfmJSSlqKblJeZlImGgoF/e3t8eHd28vb7/fqBgP+Dg42SjpCmxce7s6eenYyIiJCQhIqI/fTv7Pr59vWA6+Xh5Obr+uLs8uvX0tXTzMjO1u7nxcC+uLSttrWvuMbJ1trMsqiqra21t7C1t62mqJuPjpGHiY+Kh4F/gHx3dnh3dnRzdHV0c3R0dHV1dHZ4d3V0cnJycW9ucHFxbmxsa2hpbG5zb2ppa2ppamtpZWRkZmhnyGNlZWTEYWFfv78/vLq6u72/vLi7vLutopmjrbW2tbOvrKypoqGlo6GbmpeVkYyJhoOIjZKYmp2fnJuXkZCOkJOWkpSUlpSVlZaWhZMfkI6Oj4yKiIaHh4mJi46Ojo+Oi4uNioqLk6GknpSVjISJgIqJi5KXm5yhop6blpKSk5OUlJKQj5GXmJiYl5eZlJGOjo+NjIyJiYyOjpCLiouMjI2Njo+Qj42Pj46OjoyMjY2Mi42NjI2Qj4+Qj4+OjoyLjo2Ni4qJh4iJiomLjo6KioyKiYuJhoeIiomKi4uNjY2PjI2MjYyLiouMjUdISklKAkpJhEgNR0dHRkVGR0ZHSElKSoRLhUxaS0tMTk9PTk9QUlNSUU9PTk9OTE1LSklJS0xMS0tMTE1OTk5PT1BQT05OT09PUFFUV1lYWFdXVlVUU1RVVVNSVFRXX2FgYWJhYF9dXFtbWllZWFdWVVRSUU5NhEyAS0pKS0tLSk1QUlBRU1dXVlhTUlWfm5eYmpeOjY6PkZCNjYuKjZCZlpeYnJiboauspqaqq6mtqKeloaGjpqWioJuan6CfmpaVkYyKjI6OjpCWmZ2goaSlpaSlpKWlpqelpKOioqKhoJ+em52eoaKnq7Cvr6+trq6sqqikoJ2cnZ2An6ChoJ+dmpiYmJaXmpykqauusrGtqKSgmZiZmZyhq7Gura2rp6aioZ+enZuZmZ6fn6CgoqSjpqajoaSlqquts6+tqqanqaWpra+sqauusba6vb2+vrm1sa6wWVxgZWlmamprbG1raW5ub3N1dHl8dnJxb3F4e4CDh4F8eXFsbXAYbGxsYlpQTUdGR0hISUlJTE9RUlVVVFNUhVWAVldcX19fXmBjYmVqbG9vc3h6fX2BhIOIio2VmqGiprS6t7GwraunqbW2tbS5usLGxcLIzdLf6/f8+Pbu6uTj3+Dl5uPi4+Ph4+/39vj0+oOHh4aEg4WFg4OBhY+SkZSMjZeTkJSXmJiZmZubnJydoJ+cm5qZmJWRkJKYybWYjIwCj48FjI6OjIuEiYCNl5SanpyYk5KQj5SUm6qtqpqRn6u4vaudoKGvsqqrr7XC1tXSzLu3p6Kio6KgnJqZmJqdnqOwrqurop2Xk5OUm5aRlJSPiYeDgn99fX15eHfw8vb29Hx89318gIOBg4+fn5eSjIaFe3l5fn12ennp5ePi6+7s6uHZ0tHR1N7S2IDZ1MfDxMTAvcDE0c25t7WxrqmvrquvtrW6vrWlnp+hoKKinqCinJmbkouKjIeKjIaEgoCCf319gYJ+e3t8fXx7fH1/fn19gIF/fXx5e3p5eHZ2dnd2dnVzcXFxcnRzcXBycXBxcW9ubWxtbm3Ya2trbNVpamnR0M3Ly83OzsrHyUHKx7+4sbe8v8DAvrq6ubWxsbOysa2rqaino6GioaOmqqurra2qqaelo6OkpqmoqauurKusrKyrqqqpqKemp6ako4SiAaOEohKjo6KioaKhoaWtuL25sbGppaaEqCipr7O2trq7ubWxr6+vrrCvrKutrK2ur66tra6tqqenpqWkpaOjpKemhqQ9paWlpqaloqSlpqaoqKamp6impaalpaanpqenp6ampKSlpqahn6CgoKGhoaWnp6Wmp6iqp6WkpqWop6eqqoWsDq2ur6+ura2vr1pbW1xchFsKXFtaWlpbW1xcXIRdAV6JXxhhYmNkZGRlZWZmZ2hnZmZmZ2dlZmVlZGSGZYJmhGc7aGhoZ2dmZmdnZ2hpa2xra2pramppaWlqamloaWlrb3FwcXFxcG9vbm1sbGtramloZmZlZGJhYWFgYGCFX2xeX2BiYF9gY2NjZGFgYry5trS1s6upqaippqOhoKCgoaSjpKSloqSnraypqauqqayqqaikpaeoqKakoaKkpaWioKCfnp2cnZ2cnqCipqiprKyrqqmoqKipq6uqqaenp6alpaSjpaeoqq2vsrSEtYC2trW0sq6tra6vsLKzs7Kxr66trq2vsbG3ury9v7+9uri3srGxs7W5vsHExsfFwcG+u7m6u7m6u76+vb69vr+/wsG/vb6+w8TGy8jFxL/Awr2+wcTBvb7BxMfJzM3Ozs7MysnLZ2hscnZ0eHh5enx6eX5/f4OFg4mLhYGCf4KIjICQk5WOi4mBfX+BfX18dG1kYV5dXV5dXl9fYWRlZ2lpaGdoaWpqamlqa3Bzc3JydHZ2eHx+gICFiYqNjZGTlJiYm6Omra+yvMK/vLy5ubS1v8HCwMXFzNDQzdHX2+bv9vr7+PDr6unm6Ovt6+nr6ujr8/n6+/j8hIaHhoWEhYWDgw2ChYyOjI2JiY+Li46RhJIYk5SVlJWWlZWVlJOSj4yLjJGrnY2JiouLcYB/f359e3t7fHx9fH+Afn+BgoKDh4iMjI2Mh4aJjI6Nj5KRkJWWl52goqe0tLKtpJybmpiYmJeVlJOTlZaVl5ucnZ6Yl5WTk5GRkJCPj4+OjYqJh4WEhYKBgP///vz6fHz0eHh2dHV0dHJxcHBubm1thGwXamxt29zf4d/l49/V0srGwL7BxsTDv72FvIC4t7i4tri3trW0tLKwrauppaKgnpybmJaTkI6Oj4+Pko+NjY+Rko2JiYqMiomMkJeZko6PkZCPjo6Qk5KRkJWalpGPjZGRjouKioiHio+NiIaEg4GAgoKDhYODgoKCg4OBgYGA/4CAgIH/gIOD/vv5+fr69/X08vHu7e7u7ujh4Svi4+Ti49/f3djb29jY1tfZ2tvd5Ojh3tzZ1tTS0tHS1dbV09PT19nZ3N3dhdyA2tnc3dzd3N3f4N/e3+Db19XW1tXW2Nja2drc4u3y8Orv6unp6urr6+rv7/Hz9Pb08ezq6urp6efn5+nn5OLh4+Hh4uPf2dna2tna29vc3t3d2djZ293d2trY2NXT1dja3ODd293f3dja29nY2djZ29zd3NrY19nb29jW2Nna29xa29/k5+Pj5+jr6ujm6ero5+js7/P08e/u8/j8/fz5+/7/gICBgoKBgYKDhYSCgoSIiIiJiIiHhoiKiomKiIiJiouLjY6QkJCRk5SUlJWVmJmam5mampucnZ2dhZwKnZycnZycnJubnIabFZqamZiYmJmZmZuam5ubnJuam5ycm4SYRpmYmZqZmJiWlJSTlJSTk5KRkI+PkI+NjIyNjIuKiomIhoSEhIODg4KChIOA/fv48/Ds6ebi3t7b19XRzs/NzMjIxsXCwcCFvwXAv76+voTAKr+/v8DAw8K/v8HCxMbLzMfDwcLDwsDEyMrPzcnEw8PCwcHBwsHDxMPBwYTCgMTFxMXGx8rMzs/Q0tLR0NPV1tXY2NfY2tzb3uDf3+Df3+Pi4uHg397f4ePk5ebo5ubn6urp7e/y9vPy8u/w8PDy8/b4+vr29fTz8/Lx9PX09PLz9PX5+Pf28/Lx8PDv7+zr6+zu7u/u8PX3+Pr8/v6AgoWHiomOj4+RkpGRl5eYKZqbm5+gnZmamZqfoKSmqKSin5qYmJyZmZqUkY2LiYiJiYqLioqLjI2OhJCFkQWSk5SWl4SYc5mZmp2foKCjpqeqqqysrrGwtLi5vb6/xcjIxsbFxMTEyMrKys3N0dPS0dPU2eHk6enp6OXj4uDe3+Lk4uHh4OLl6ezr7Ontent8e3p5enp5eHh5eXp6eXl6eXp7fH1+fX5+fn9/fn+AgYGAf4B/f359fn+GfgF/2oKFgQOCgoGWgv2BAYCEgQSAgYGB/4DlgP+BlIH/gMeA/4GTgbOCAgIEAICQlZmenZuZl6mtoqChopqZnZubnKCquLO12eLZvb/AtKamqMfS1b65tra9x9Lf29TNxtXPs6aqrLCtrKiyuLS1srjj1LavoJ2ot6SdrMeekJGXntXVkYF/eXl7foGEhoaGhIeHiYeTscfR4NSyop+tsquxr6aXj4SD/PmAhv75703u6eXc0tXi2OHs6eLa4N3d1Nfo+ePHx76zrq2trq2utLO8wrmqm5eWlpaVlZyhmZWYjImKiYeHh4OCgXt5d3h5d3Z0c3N0dHV1dnh3eIR3gHh4d3d2dHNycnFxcW9ubmxra2xxcm9sbWxqaWppaWhnZ2dmZmfLZmdmZGRjYr+/wMDBwL++u7a0tLGtraeqrq+urqyooZ6hoaKlp6ihnJORjYmKioiMkpORkpWam5eUkZKRk5SWlJaWl5eWmZmXmZiVk5GRkY+MjIiHhYWGiIqKaY2PkI+Pjo6NjI2Pnaedko+Pi4iIiIyOj5GQjo6SlZeXlpORlJWVl5mUkJKTlJWXl5STmJiTj46Pjo6NjY6QkZKUk5GOj4+NjoyNjpCQkZCRkpCPkJCTk5KTkJGTlpaTk5KUmJWamJSTlYSTEZCRkZKXmpaTj46OioqJio6PhJARlZOUlZeVlJORj46Oj0hKSkuETARLS0pJhEgOR0dISEdISUtMS0tLTEuFTApLS0xNTk9PUFBQhE4QTU1MTk1NTEtLSkxLSktMTYVOOE9QUFBPT1BQUFFTVFVWV1haWlhWVVRVVVVTUlJTVFZaXl9eXl1cXFxaW1tcXV1cWlhVU1JRT05NhUyATUxMS0tNW1dRVVNXWlZYVlRST5+dl5eUjpmfmJuSkKCVipOgn52WlJ+fnJ+loqKlqquora2srqakqKemp6ShoqCcmpmWlZCNj5COi4uMk5SUmJyen6GhoZ+enp+hoKChoqSmo6Kgnp2enqCho6apqqurp6ipqaqpqKeioKGio6WApKKdl5OSkZWYmp2goqitra6uqqijopucnaGhpaqqq62tqamtp6CdmJiWlZaamZmdn52foKCio6GhpKaqrbCsqqimqaqtq6iutba9vsLFxsK9vLy5XFmpq1dYWmFlaXN3cXBra2pram51d3d3dXFrZGBdWFdYWFxdXV1ZV1VXWFIyUU9LSUhHSElJS1NVU1RXV1hZWFZVVlhZWVpbX2Nrb3R3cnJzamtucnR+hYeNkJaXk5iEqGesrLa3t7i7ubO2s7a6ysS8uri6vcDEx9HY3ujq5+fn6e7x5+Ll6/f78uns7ujr8fTz+H+BhIaIioiFgoCBhIWMl5qXmJGSlZmamp2en56fn6Cio6KjoqOhpKKdl5KPj5zYxqKZkZCOgI2QkZSTkpCPmJuUlJWWkZCTkpKUmKGqp6nDxr+pq66noaKjtbq9s7Kysba6xNDOyMO6v7qqpKWoqaemo6qrqKqqrsa8rKadmqCqn5ihsZiRkpOXs7SMg4B7eXp8fn+AgH9+f35/fYSVoaeupZOJho6RjZCPioF9d3jq6nd77+rlcOPb2NDKydDL0dfVzsjKysjCxc/Xy7u7tq+sqqmqqaipp6uvqp+Wk5KRj4+QlJiTkZONiYeEhIeHhIOAf35+gYB/fXp4e3t7fX5+gIGCf3+Af4CAf35+fHt5eXd3d3Z1dnRzcnJ0dXRzc3RycnJwcG+GbgnZbW1tbGxsa9KGz07OysjHxsK/vrq7vby8urm2s7G0s7KztbWwraeopaSkpKOmqqqoqKqsrKqpp6ioqKmqq62tsLCur6+ur66rqqiop6alpaSlo6KipKSkpaSEoyOkpKOlqbXAuK+rqqikpqWoqqyuraysrrGys7Kvrq+wsLKyr4Ssaq2urausr7GqqKiop6moqKmqqqysqqenqqmmpKGio6anpaWmqaqpqqmqrKqopqaoq6upqamqq6qurKmnqaenqKimqautsbazsKyrrayqqqurq6+rq62urrCxsbCxsrOysK+wWlxdXV5dXl6EXQdcXFtaW1tchF0YXl5eX19fXmBgX19gYWFiY2VlZWZmZmVmhGcCZmiHZg1lZWZmZ2hnZ2doaGlph2gfaWpqa2xsbWxsa2tpampramlpaWprbW9vcHBubm5vboZtgGxqaWdmZWRjYmFhYWBgYWBgX19gZmVhY2JkZGJkZGFfXry6tLKxrLK0ra6npq6noKSqp6ako6enpKaqpqapqquqrKyqq6elqaioqKWkpaSjoaGfn52dnJybmpmZnZ2gpKWlpqempqWkpKWnp6ampqiop6WkpKOlpqeoq6yusLGxHLCwsbKzs7KxsLCwsbKztLKwraurra+wsrS2t7qEvVG6urm5tbOzt7i7vsDCxsfDw8S8ubi1t7W0tri6uLq8uru9vb6+vb2/wcTHycPCwb/AwcC/vsLGx8zP0NHS0M7P0tFpaMfJZWZpbXJ2gIV/fnqEexx9hYiIh4SBfHZyb2tramxvcG9wbGpoamplZWNghF56YGBgaWtoaGprbG5saWpsbW1tbm9zd36Ch4mFhYZ9foCDhY6VlpufpaWipbGxtbS4t8DBwcLCw77AvcDF0c7HxMPFxsrN0Njg5Ovt6uvt7vPz7ejp8Pn89e/y8u/w9ff5/IGCg4WJiYeFgoGDhYWJkJGPj46PkJGTlZaEmBiXl5qbmZuam5qcm5eSj4yLlLGnk4+Li4oCgIGEgEh/fn59fYCCgoGBgYKEhIeMj5KTnJqWkZSUk5OYmJeXmp6hoaGipqyvr6yopqCbmZmZmpybm5eYlpmbnJ6gn5iWmJeVl5SSkZOEkoCNjImJh4aDgYCAgH9/fn16eXl3d3d2dXNzcXBubW5sbG1ra2ppaWva3HBw393d2NLOysjDw8bEw8HBvry9vLu7u7i3t7e2trWzsrGuqqekoaKgm5aVlJORkZCTlZSTlZSQioaJjo6MjIqLjY6VkpKSi4mMjY6Pjo+TlZiVk5ORkxeTkJCSkI2Li4mIh4iKjYiFg4KBgoSGh4SGgIODg4GAgIGBgf+AgIGAgYOD//v2+fv6+Pj3+vby8fHw7Ofl5OTi4uHl4uHg2tjc29jW2tzc3t/h5ODe29rZ2NfY2djZ2NfY2trc3+Dj5ubn5ODh4uDc29vc3N3e4uTk5OHg4N3a2djY2dna2tvf5u749/Tu7Ojo6ens7e3u7u3tgO7x8O7t7Ovq6ens7enm5ePk5eXk4uTl5+Dc3N3f4d7d3d/h4uPg29nd39rX1NHQ0tTS0tXe4ODg3t/g29jV1tfb2tja29zb2N3Z2NfY1tfa3eDm5+nw8/Hv6+3w8O/w8fHw7unq7PDv8fHw7/P5/f79/PyAhIWEhISFhISEhYeFMoOCgYSFhomJiIiLiomJiIeGiImIiYuNjpCSkpSVlZWXmJiYmpubmpqanJycnZ2enZ6dhp6KnQWcnJubm4WaAZuEnIadDpyamJiZmZqZmpqZmZiXhJaAlJSUk5GQkJGRkI6MjIyLi4qJiISEg4OCgYKCg4KBgYD8+fby7enm49/g3dfX08/QzsrIxcTEw8HBwL2+wMDAvr6+vb6/v7++vbu8vb6/v77Bw8bIycXCwcDAwL6/v8LFw8HBwMHAwMDBwcC/wMHBwMC/wcLBwsLExMTFyMnMy80Iz9DP0NHU0tSE1RfW19rc3N3f39ze4OHi4eHg3d7f4+Xj5oTnTujo5+rt8/j28e3r7O3u8fHy8vP08vHx8/Lz8vPz9PLy8/j49/Px7+/x7uzq6uzr7O7u7/Dw8fT3/P6Bgfz9gIGCh4mMk5aUlJOUk5WUmoSeKZ2cl5SRkI2Oj4+QkZCRj46OkJCNjYuKiYqKiYqLjJGSkI+QkJGSkZGRhZNalJeanaCjpaKiop6en6Kkqa2tsbO2tra4vr6+wMPCxcXHx8rKxcjFx8nPzsrKysvMzc7P09bZ3t/g4OHi4+Th4OHm6+vm4uXn5ebo6ertd3l7e3x9e3p4eXh5hHolfH18fXx9f3+AgoKCgYCBgYGCgoOEhYaGhIKAf39+fn1/gH9/gPmCBIGBgoL5gQGAh4H/gOOA/4GXgf+AwoAEgYGAgP+BkYG1ggICBACAk5ebnZ6hoKDY1bqnn56en6KxwM3Y6tbO2+TV2dvVt62wsbCuxuHnsbW3t8zg3uTd5/2chMe9usrEuKuwuLW5z4Lz7s63tKyjo6OgoKCclJOXmLTXxpyNhYN/f4CDhIqSkpmenZmbrb7DyrOcl5qdtbu5sqqfoJ2PiYL+//ru7O+A6uDb2dLP09fZ39/x8fPo2tTQ6ePIwcXIysbGwr68u7y7t7GwqaCXl5iTkpKOi4eGhYOEg4ODhIOCg4B/fnx8e3Z0dXZ2d3d2d3t8e3p5e3t6enx9e3p5d3VzcXFxcnJyb25tcHJxb25tamlrbmtqaGhnZ2ZnZmZnZ2VkZWRixMU4xsTCwL/BwL27u7u6vL63sq+vsrOqnpyipaSfmJiVkpGSkpebk4yRl5iYkY+Rk5SVm5uZlpeYmZqEmUGbnZyamZiXmJaWlZSRjYyKiYmLjo+PkJGRkZKRkpOVmZ+jn5aQjIqJioyLiImNkJGPkZSVlZORkZKWmJmYlZaVkYaOYpGUk5KQkJGTkZCSk5WVl5aXm5iUj4+PkZOUk5OUlZmYlJKSlZaWl5ianqCgnqCnoaClpKCeoZ2ampmYlZaVlJebnZiRj42OjIyNjpCSlpiZmZqdoZ2bmpiWSUhJSUpLS0xNhkwHS0pKSklISoVJAUqES4JMhUsgSklKS0xNTU5OTU1NTlBSTlBTUlJOT01NTUtLTExNTU6FTwhOTk9QUFBPUIRRElJTU1RVVldZWlxdW1lYVldYWYRagFlaWVhXV1hZW1taWVhVVFJSUlFPTk1OTUxLSk5QUVVZVlNWVFhevLrJyr24rbqknZqXn6SurKiutbaosK6iqZ2Ym5aVn6Oko6mrsbGzrq6vrLSuqamrp6Skop2bmpiVlZOTk4+MiYqMkJGTlpeamJeYl5eYmJqbm5yfoKKkoJ2ebJycnqKjo6Olp6emo6Onq6yrqqqnpaSkpKempKCZlZOUmJ2goaGip6mpp6alo6OfoaSkqKyop6GjqauopaSakpWUlpSQkZSWmJqcmp2cm5yio6GipaalpqmrqqytsLOzsrOytbvAw8K/vLu6uIRbgFpYqlZZXF5iZGZqZGVjXl9fYGRiYmNhXV9fW2JiXl5dXVlUUU1MTk9PT01PTk1LTE1OTVJWWFhaWlxaWVlcXl5dXl5gaXN+fYGEgoSFf3h2eXp/hImPk5edoqCipqeusbS2tLO5xcvGxsfOzczCxMLGwsTHzdXZ3eHl6Onj4eTpRvDy8u3w8/n38vPz9fj19/yCgP37f4GFhoOAgIKEhYiUnZ2alpKSmJqbnJ6fn6GhoqSmqKekpKapq6unmpWQsofUsLWSjo9ZjpKUlJSWlZW2tKSbl5aXl5qkrbW9xru2wcrBw8S9sKqsrKuotMXJq6+ysbzKztLR1uWCcr62sry4sKeqsa2xv3LVz7utq6igoKGenp2blJKVlae5rZeKg4KFf4CDiIeKi4uJipSdoKSWh4SFhpSZlpKOhYeEfnt47fDs5ePm5NrV0s7Kys3Mz8/Z1tjQx8TC0My9ubm7ure3trSyr66sp6OjnZeRkJGOjY2MjYuIhoWDgYCAg4KBgoGBgICBf3t5eHl5eXx+f4ODg4KAgYKBgYOFgoKCf3p4d3h3dxN5eXd1dHZ3dXR0dHJzdHVzcXFwhW+DboRtNGvT0tPS0dLR0M/QzszKycjIxMG9vsG+t7Kytre1sq6urKqpqqqtr6qnqqysqaanqquqq66FrQGuhbGAs7Kxr62sq6urqqqpqKimpKSlp6irq6qnp6ampqiprLG4vrmzrKiop6mrqqanqayvrq+xsLGwrq6vsbK0tK+urKuoqqimqKaprK2rq6qqrqypqqutrLCvsLSwqqakpKenqKmnqKiurqupqautrq2trrGztbG0urWytbSyrrKvrasbrK6ssLGws7i6ta+ura2rqqusrKytr7Cxs7S2hbQGWlpbW1tdhV4MX19eXl5dXVxbW1tchV2EXoVfK2BhYWFiY2RlZWVmZmVmaGlqaGhpaWlnaGdnaGZmZ2hoaGdoaGhpaWhoaGmEaIRphGoJa2xtbW5vb29thGwBbYVuim1mbGtqaWhnZmRjY2JiYWFgYGFjY2RmY2FiYWRmzMzW1s3Kw8q7t7Sxtbi8ubW3urqxtLKrraSipaKgpaeop6qrrq2urKysqq2rqamqp6WkpKKhoqGgn56dnZuamZiZmpucnZ+ioaGghKGAoqOjo6Wmp6impKOjpaeoqqurra2ur66usLKys7Kys7KxsbGzs7Oyr66urrGztbi4uLm6vLq4uLi5t7a2ur2/vLy6vMPIxcG+t7KzsrSysbKztLa3uLi6ube4vLy7vcDBv76/wsHBwcPFxcTFxcjN0NLS0M7Nz89oaWloaGfIZWaAamxxdHZ5dHd2cXFycnV0dXVzb3JybnV1cnJycWxoZGJiY2RkZGNkY2NhYmNjZGdrbGxubnBubW5xc3NycnJ1fIWNjpKVkpOUkImJiouPlJidoaWqrqutsLS6vL2+vbzBzNLNy87U1dHLzczMyszP1dve4ubp6+rn5ujt9Pb39PZB+Pn59Pb19vr5/P+BgP//goOFh4OAgIOFhYiNkpSSkI+Pk5SWlpiYmZqbnJ2fn56cnaChoqKelpGNoGewnaCNi4sEgYOEg4SBZoSDgoKDhIaHiYyNjY6TkpOWm5ydnZuamZudnZqXmp2en6KgpKastbO1uV5Wp6Ggp6WempuenZ+lVqSfnJuenpmZmZiZmZiVlJSUlZWSkYqHhoOCgoGAf359enl5enl5eXh3dXFvb4RugG1ta2tqa2xu4OPj4uDg4NvV09LNyMfGxcTDwL69vb28uri6ubi0sbCvsLKwrKeloJ2cmJWTkZGNjpCQlJiWkY6Lh4SDh4eJjIqMjY2SkIuIhoaEhImMjpaYl5SRlJWTkpaXlZaWlY6KiIiHhomLiYaEg4OEhYaGhYaFhYWEhIODKYKDhIOBgoGAgoOEgPv6+fj6+/r5+f349PPx7Ozo4+Tk4+Pk5ujm4eDehOAU397c29vh4d/b2tnZ2t3d3+Dg3NyE3YDh4+Pl5+nl4N3b2NjY2tzc2t3i4eLk5OXl4+Hh3Nrc3d3e3+Ln7vPz8e/t7O3u7u7q6uvs8O/v8fDv7ezr6u3u8O7q6OPj4+Xj3+De3+bk4d/e4Ofi3t/i5OPn5eTm4NvV09LT1dbW1NTV3N3a2dvd393b2dnc3t/e4OTb2Nzc2izZ4N7c2tvf4e3u8PP29vLu7e7x7Ozv7uvm5ent8O/x8vDv8/b7gIGBgIGEg4SEX4WFh4eHhIOEgoGBg4eJhoSFiImIhoWGiYiJjI6PkZOUlZaWl5iYmJmZm5yamZqam5ycnZ+gnp+goJ+fnp+enZ6dnqCfn56en56enp2enZ2enp2enZ2dnJycnZ6enp2chJsXnJybm5uampqZmpmXlpaXl5aTkZCSkZCEjSqMjImIhYSDg4GBgP///vz+/Pj09PDt6+Xj3d7d2tjT0s/Mx8fFw8PBwb6Evyy9vb6+vr29vr++vr28u729vr6+v8LFxsTDwsHAvr28urq5u73CwL6+v8HBwYW+Lb+/vr2/wcPDwcLDw8XHycvMzczMzc3Q0dHS1NPT09TW19nb3N/e3N3h5OTk4YTgcuLj5Ofl5OTl6OXm7e/w9/fu6+/t7e/u7e7u8O/w8O/x8O/u7O7v7vP09PLw7e3t8O3r7Ovr6uvt7/L08/P29vr/gYKCg4SB/4CDhoqNkJGTkZOTkZGTk5aUk5SRjpCPjJGSkpKTko+NioiJioqLi4qMjISLgI2Nj5GRkJKSk5OSk5aXlpaXlpidoqenqq6pq6uppaSkpairrrO1t7m9vLy9vcDCw8LDxMXJzcvMzM3OzszMzczKzM7P1NTX2dve4N7e3uHj5Obj5ubp6Ofn5+jt7Oztdnbu7nh5e3p5eXl6e3t7fHt8fn59fn+AgIGCg4OCg4OEAYWEhhCIi4yNiYWDgYFAf4CBgH+AsoKCg4yCAYO5gv+BhYH/gN+A/4GVgf+Ax4CGgQGA/4GOgQSCgoGBq4IBg4aCAgIEAICSlZyho6SlvP/7h7+jn6KnrLC1xMvphv/r6c7N39vFvL/AxdHh0NC8urjD1tTM0tLZ6oqbkIf57uHUz8yFg/f//PHh2si8uszVxKijo5+fl5eeoa22tKubko6NipGanaGimpORn7C5v7CwoZ2cp6ulpaKdnKGkoJKQi4eAgfv9/Fj39uzp5Of8gYf78uXh3cnDvcTV29Xi8fj+ge/X09LPy8bGwrionZeanqKipJuTjIWEgYGDgYCAf4GIjZGKf3x6eXd4eXp/fnx8fX19gIGBf3t6ent6e3l2hHWEdGFzcXBwb25wb29tbW5vbmtra2xta2psamloaWdlY2HDx8bEYGDAw8PFw7++t7S2uLS1trWtn5ienpymqpeTl5SSkZSXmJWRkpSVnJeRlZqbnZ+fnZqZmJqbmpqbmp2fn56ehKGAoJ2cl5aUkJCNjpmcmJeWlJaWk5KUlpqeoKChnpeYn5ubmo2Ojo2OkZKXmp2cm5SRkJCSlpeXlZGOjIyNjY2OjY2PkZOVlpSSkpabnJqVl52al5WVlJSZnpuam5udn5mYlpiamp6goaChoaOotbm5t7KqoZ+gm5iZmpqXlZSXnJwimpiVkY6MjZGSkpaYmpuZmp+hn56amZlKS0xLSktLS0xMTYdMAktKh0kHSklLS0pKS4VKMUlJSUpLTExNTUxNUFFSVVNUVFRRTkxNTk9OTUxNTU5PUE9PT05OTk1NTk9QT09QT0+EUA9RVFZXWV1dXV5eXVxZWFiFWWNYV1dWVlZYWFdWVVNSUlJUU1JRUlJUVlJMWFVZUk9bYllZYsjgzMHCt7i1uq+hmpqam620t8DJ3czFuam8t7Cio6KuqqKfpKOorauoq7C4uLGsqqqqpKKfmpqbmpeXmpmTko+EjICOj5CSkpGQkJKTk5OVlpeZm52fn52bm5udoaOlpaalpaWjoqSlqKmqq62rq6qqqqunpaOgnZubnaGjpKGjpaWkpKSgoJ+gpaurqKaknpqboqemoqKglYuUk4+OkZWXm5ucm5eZm5yen6Olpaiqrq+vsLCwsbO0tbSurrG4uru7uICxr1pbW1pXU6NSUk9QTktLS0pLTE5MTEtKUFhaX2tscH13d3dyZl9eW1pVU1BTVldXVVZWVFNUUlNVWFlcX15dXl1dXmBeXmBhYWJnaW51d3R3fIaGiYeHhYuOlpqhoqKfnZ6goqenqq6sucXQ1NHRz9DSz8vIzNLV1trc4ebt703u7+3v7Ofp6u3y9fn4+Pn29vX19vl/goKDgYB/gYOGhYSAgoWLjI2TmpiXlZWam5ydnZ+hpKOkpKSloqGio6GgoqGclpO68ezfy6GQj4CNj5WYmZmap83KaqqamJ2hpKWnsLfFbtXJyru6xcS4sra5u77Hvb6zsbK7wsXCxsfK13iGg3vk29HEv71zcNfb3NjOybu1s7u/s6Shop6dlZadoKaurKKVj4qKh4mMjZCRjIeFjpieopiXjYiIjpCNi4mGhomKiYODf315eO3t7WXr7OLf29rkc3Xe2tLOzMK+ur3HysXL1NXXbM3AwL67trKvraeclZCRlJaVlpOQjIaEg4SEgX9/fn+FiYqFgH99e3p6enyAgYGAg4OCg4OEgn9/gIKAgoB8e3t7fHt7e3x6d3d4d4R1V3RzdHV1c3R1dnZzcnNxcHBycG5ratTV1dRpadHS09XU0M7JxsbGwsLBwLu1sLO0sri7r6utq6qqrK6vrKyura2wraqtsK+ys7OysrGys7O0s7Szt7i0sYSwgLGwrq6rq6qoqKeqs7axsa6ur6yqqqyus7a5ubq4sbK6t7a2raysqquvr7K3ubi2sa2srrCys7KxrKmmpKanp6moqKuqq6+xrq2ssLa3tLCwtbGsqqqoqa6xsK2trrGyrKurrK6ws7S0tbS1ubzJzcvGw7uzsrWxrrGytLKys7S4Dbm2trOvrqysr7GwsbGEsxm1t7i5t7e2W11eXVtcXFxdXV5fYF9fYF9ehF0EXl5dXYRehF8LYF9fYGFhYWJjZGSEZRJmaGhpa2lpa2tpaGhnaWloaGiEaQpqaWlpaGloaGlohGkBaoZpBWprbG1vhnEDcG1thm6EbYVsA2traoRpgGhnZmVkZWdlYWdlaGNgaGplZWrV4dbR0MnJx8vCura0sbG8vb3DxtPHwLmvu7awqamnrqynpaanqKmpp6qsr6+sqqmpp6Wlop+goaCgn6Cgnp6dmpiZmJiYmpydnJydnZ6enp+goaGjpKSmpqWjpKWpqausra2ur6+ura+wsbKygLO0tLO1tbS1tLOzsrKztLe5urm5u7q4ube2tra3uby8u7u7uri4vsTEvbu6s62ysq+wsbO0t7e4uLW2tre4ur6/vsDCw8PDwsLDw8TGxcXCxMbMzc/PzcfGZ2pqaWZjwmFiYGFgXl5dXV9hYmBfXl9ka21yfHyAi4eFhIF4c3BtgGxpZ2ZnaWlqamtraWhpaGhqbW1vc3FxcnBwc3VzcnR1dXZ6fICHiYaIjJWWmJaXlZudpKesrq2qqamrrLGwsrSzv8nU2dbU1dbX1dLQ0dfZ297f5eju8+/w8fHv7O3u8fb4+/z8/Pr6+Pf6/YCBgYKBgYGCg4WFg4GDhYmKi4+TJJKRkJGUlZeXmJqbnJucnZ2dnJucnZybnJuXko+lw762qpSNjICCgoWFhISFiIWFQoWHiIuNkZGRkpCTS5WWmpqeo6CcnqCio6OhnZ6ho6Kmq6yqrKuus15kY2C6ta+ppaNUVJ+gpqiopqGjo6Ogmpmbm5qZlZSWlZufnJeOi4mIh4SCgICAfn18fXx9fHx5eHVycXBwcG5tbGxsa29ycnFycN7d3IDg4tnW0s7LZWTFxcK/v8HBwsHAwL24tK2pVKmssK6ppaGdmpiUk5KOjYuJi46UkpCOjIuKh4aEhISIioqKiYiHhYWEg4OGiYyPlZaUjYuMjYuNkZOSlZOOioqLjYuKi46MiYiKiYaFhYSFhYWEhYaIiYuJhYWFgoGDhoWBgYD+/YD8/YCA/Pn5/fv29fby7url5eXm5u3t6ujl5ePn5+Dg4uHg4OHh4eDh4d/g4+Th39/g4OHi4+bm5+fl5ejs6OHc2NjW1tbX19va3+Hh4eLk7e3n5eLg5ePg4eXk6Ozu7/Dw7vD18/Pz8O7s7O/x7/Hy9fTz7err7Ovp6ebk5d/c22Hf4uLi5OPj4+Lm6eLf3+Lm6efl5ujg29jX09La29jW19ja29TX19jc3t7d3t3c3ODi6/Du6urk3t7f3+Di5+vu8O/0+vj39vLy8u7u8vTy7O3u7Ozt7fHz9fb5/IGDhIOAhYE1hYaHh4iHhYSFhYSEh4iHhoeGhoiIh4iJi4iIjZKTlZeWlJOUlZeXlpiXmJeXl5iZm52dnp+HoAefoKCfoJ+fiaANn5+enp+foKCfnp2dnISdYJ6fnp2dnJydnZycnJ2cnJubnJuampubmpeVk5KQj46LjYyLi4qJiIWCgoKA/fn5//38+vn09PHs6ubl4t/b2dfW1NDMyMfFw8DAw8LBwMC+vb6+vb69vby7u7y8u7q6u4W9FL6/v8DEw8G+vbu4ubi7vr+/wMC/hL4yvb6+vr/Avr7AwMLCwcLCxMfHyMzNzMzLy83OztDQz87Q0dHS1NTW3N7e3uDk5ufm4+SE4knk5ebj5OXm6Ons7Ozv+fnw7O7x7/Hv7fDv8O7v7Ovs7e3q6uzu7/Dt7Ozt6+zu7ezs7e3u7e7w8fT19vf5+PmBgYOEhIL+gIGBhYItg4SGiIeHhoeLjo6QlJWYn5ubnpyWko+NjIuLjI2Oj4+PkJCOj4+Mj5KSkpOWhJNTlJWXlZWXl5iYm5yfoaKio6Wrq62tq6qur7O2urq5t7W1t7i8ury+u8HHys7OzM7Ozc3Ny87Q0NDT1dfY3N7g4OHh4N7g3uHk5Ofp6Ono6Ojn7O+HeAh5e3p6e3p7fIR9BX5/f3+AhIECgoOEhAKFhoSHhIkMiIeEgoKAgYKCgYKBioIBg4uCAYObgoSDhoKCg7+CioGCgpCBAYLkgYSAgoH/gNmA/4GUgf+Ax4CGgQGA/4GNgbiCAgIEAICWn6Ojo6Kl4vfV8tu3nKirqa+4t7vH1+ff2MXEy9rdxcTL5eTn8+bMysm+xdfa2dTQ0vKJkZijsa2jk5Cml/fl2NLX7tvU8pukpYDuwaqorbC1tba/xr24p52cmpujqKiinZ+nsrKxtbS4w8fFu7Kooam8y8q+ppmXkIyKk5SPh4CDhYL+8e7z/4Hq4NrS0MTDwMXU4trX0NHa5+fe29vPxMrIyL+toqWnp6uroZ+Yk5COjo6NjYqGh4uPlpWIfXx7e3h3eXyBf318fHt9fX18e3t6eXh3dnd2dnZ1dXV2d3Z2cnNycXFyc3NxcXNxcG9ucHFxb29tbGxraWZlY8TFZYBlZGNkx8fExMO+vbezs7e5urWyraGfoaGho6WgmpualZGQkZORkJGWl5eYnKGkqKijnpyZmJibm5qdnZ2goaSmp6uqqKajoZ6cnZqXlpOXn6SgnJmXmZqZl5eYmZyjpaKkoZ+go6WhmZeVlJOSlZuho6WgnZmZmJmbnJuZlpSSkmOQjo2NiYuMj5GUlJWUlpiZl5qYmZyiopydo6atr6eloJ6hnZqbnJ2dnZ6ioaKlq6qwvr27vbaooaCjn52am5mXmZqZmZ2bl5aUkpGRk5WTl5iYmpucn6Cfn6CeTUtLTUxKS0yGS4VMC0tKSklJSElJSEhIhEmESiVLSkpJSUtMTU1OT05OUFJUVVVVVFVST09OT1JSUlBQUFFRUlJQiU8BUIdPhVCAUlZXWV1gXVtZWVlaWltbXFxaWVhZWFhZV1ZVU1NUVlZVVldWVVZYWldWWFxXVFldXFlewcW/ubS2tbOro5ual5aTlp2irrXF1dKxpavAxsnMybyspaKdmp2go6WkqauwtLW5trCqo5+enJucnp2enZqTko+Ojo2Ojo+OjY2MjpAgjo+QlJKTlpeanJybmpucnZ+foqOjpKSjoqGhoaSnqKqEqYCqrK6qp6alpKGfoJ+goKKgoJ+enp6cnJ6jqKytq6eno6CioaWjo6KgmJWZmpGJjJKQk5iYlpaXl5ygpaepqayur66uqKeqr7CvsLKuraqtsbGvrbBdZGprZGBaVFFRUVJRUE1KS0tOTEpKTVBUVFVZZ2VeXWBiZGVjYFxbWlpaWH1VVldaX2FfXWFbWlxdXWJqbWhmZGRnZ2hmZGNjZGZsc4CDg4J/gYCDiY6RkpKWnqGjoZ+goKanp6mus7a5xMzR0tPS1dvd2tfZ19nf4ubs9Pv7+/j19fPu7O/v7fH4gID69/v7+n6AgIGBgoODgoOFiIiHhoeJi4+PkZOWmoWdHp6foKCho6Oio6SinpudnZyampuZlZWi3e3R6MWkloCRlpqampmZusa3xLmklp6goKatrK60vMXBwre0ucPJubi+0M3L08q+vr22ucbOzMfAxdh1foaSnpuPfnyMgNnLw8PH183H0XqAgG3QsaWkq6uvrq+2ubGtn5eWlJOVlpaTj5CSmJqanJqZnqCdmpWOi46YoqKZjYiGgX17f4B9ekx6fHrx5+Hg5XLX0M3GxMC+vL7Fy8fGwb/DysjDwcC5s7Swr6mdlpeZmJiYlJKPjYyJiIiJioaDg4WJjo2GgoB+fHt6e32DhIOCgoGBhH9XgH5+fX16e3t7fHt8fX5+fXx5ent6eHl5eHh4eXd3dnV4eHZ0dXNycnNyb21s1dZsbGxrbNbV1dTTzszJxsTExMXBv7+4tbi2tra4s6+xr62srK2urKyuhLF6tbe3t7i2sLGwsbS1tLS0tbe5t7i3tra3uba2tLO0trSxsLC1u764s7CvsbGvra6usLW7vbu+u7m6vb68trSxr6+wsrm+v8C8uLW0srO1tra0rqusq6mop6alpaanqayvsLCwr7Cwsa2usbe3sK+3usPFu7qysbKwrKyEsDqyt7W1uL2+xNLSz9LLvLa3ure1sbGxtLe3tri9vLi1srGvsLK0s7Oys7W4t7m6u7y8vF1dXV5eXV1ehF0IXl5fX2BgYV+IXidfXl5fX2BgYWFhYmJiYWJiY2NkZWVmZ2hqa2tqamtsa2lpaGlsa2yFaoNri2qHaYRqD2tsbW9wcnJwbm5vcG9wcIRvgG5ubm1tbWxsbGtrbGxra2toZ2doaGdmZ2lmZGhpaGVn0dTQzMjJx8XCvLm4tbGvsLGyt7nDzcm1rK+7vby/v7asqqeko6Okpqenqamrra2vrKinpaKhn6CioaKjo6OfnpqZmZmYmJmbmpqZmpubnJyen5+go6Oio6Sko6SlqKipRaqrq62srKurrK2vsLKxsbKys7S2tLW1tra1tLS2tra4uLe3trW0tbW2uLy+vry6vLy4urrAv7u6ubW1uLm0rKywr7K1toS0gLm7vr/AwcLExMXEv77Aw8PCw8TDw8PFycvJyMtrcXZ5c3BqY2FhYWJjY2BeYGBiYF9fYWZnZ2hsd3VvbnJzdHVzcnBta2xtbWprbG90dXNxdXBwcXNzdnt/end2dnl6e3l4d3Z3eX+FkJOUkpCRkZOYnJ+goKOqrK2qqqqrrrCxOLO3ur2/x8/T1dbV2d7f3tzc293i5Oju8/v8/vv5+fXy8fL08vT8gID+/P7//oCBgoOChISFhIWGhIcJiImKjYyOkZOThJYfl5iZmpuam5ubnp2bmJWXmJiWl5eVkZCWtsGwu6WXkYCEhoeHiIeHiIeFh4aHiYyOkpKWlZOUlZeYnJucnqKhn5+jpKSlpKWnq6ikpauurKurqqtZXmVrc3FnXVpeWqWjoqmmr6umpFJTUk+hnZ2cnp+hoqKkpqCek4+QjImGg4OCgYB+f3+AgX56dnVzc3NycnFvcG9ub3FwbmxqaWZmaRltcm/g29fQzGTEw8TDwsLCxMK/u7m5trGshKkIp6iln5ublpGEjlaJhoeMj4yOjImIiIqKh4ODh4qLjo+Mh4WEhYaGipCRkpOTj4mJi4yNjIyLi4eIi42NjY6PkZCOjYmLjIuGh4iHhoaHhoaHiIqJhoSFg4KFhoaCgYD9/YWAgP/7+Pr79vT18u3q6efo6Onu7Oro6OXj5eXi4uXo6erp5+fm5+no5+Tj4t/d3t7g5uzs7Ovr7u3r6+ji3t3Z2d3c4ODh5Ojn5Ojp7PDx7Ofk4uTj4uTm5efq7+/v8/Pz8vX29PLx8O/w8fH09vj59PDw7+vq6uno5+Ph5OLi5eXiXt7e3+Hg4eTi4OHj49/i3drc4OHc29zf5ejf3dfW2NbQ09fY2Nnb3dzc3+Tm6vb59fLt5ODj5+jr5ufp7PL19v7+/Pr38fTz9Pn69/Ty9fT19fb29/f5/YCCg4aGg4KEgxKEhYWGhoiIiYmKiYiKi42Ni4uEijOMjY2Pj5CRk5OVlZSSkpKTlZWYl5eWlpeYm5ydnp+foKCenp+fn6CgoqGhoKCgoaKioaCJoQejoqOjoqCfhJ6An5+fnp2dnp2dnJydnZ2enZ2cnZ2en52cmpmVk5ORj42NjIuLiIeHhoWEhIH/+/3+/f369fX49vTw7Onl4NrX1NXT0s/LyMnFxcPDwcPBwb++vr69vbu8vby6uLm6urm6u7u9vLy7vL2+wsLCvbq4ubq5ury9vr+/vru9vr+9vsAawMDBwMDAwcLAwsHDx8jHx8jKy8vKysvMzcyEzQ7Oz9HT09bZ3N3d4OLh4oTjJuTl5ubk4+Pj5Obn6uzq6u708e7t6+/y8u7v8vDt7u3s7Ovq6urshO0k7Ovs7/Dw7u7t7u/w8fDw8vP19/n6/f2Ch4qMiYeEgoGCgoOEhIZfhYiHhoaIioyMi42TkY2NkI+Rk5KRjo6MjI6Oj4+QkpWWlZOWlJSUlpaYmpuYl5aXmZmamJiYl5manqCmqamopqenqayurq+vsbW2t7WztLW2t7i5u7u9vsHFyMrMzM2G0DrS1dfX2d3j5OPk4+Li4eLi5OTk53V26+vt7O13eHl5eHh4eXl5ent7e3x9fX1+f3+AgIGCgYGBgoKDhIQYhoaIiIiHhoeHiIeHh4WEgoOCgIGDgoKDs4KLg4mChIO4goWBAYL0gYKAhYH/gNiA/4GVgf+AxoD/gYuBgoKFgbuCAgIEAICpu7ewrair1fzj+Z6Qwc3OysO7vcK9vMC8wL69wMjLzNHa5M/EyMnGysfN1M/NzdDNz8nQ9oKEjpalsLG1tZ2QiISB+PeClLLJ0KiagsO5u9bd497Z1NHZyca/uMvZ4evy6NW/s7S72Pf+9OPOvLS1p6e9y72lj4WFhYmNkZmalmWKhIeLh/30+Onn6+7ygofmy8rO08/BwcbFy9XX1dbYzMrFu7SwrbK3tbOqpaWfmJGPj4+MiYuGiZGWlJSHf317e3p5enp8fn5+end2d3l5e3x7eXd4eXt4dnd3d3h3eXh3dXR1c4R0A3N0dYRzT3RzcnBvbWxsbWxnZsrLzMvKZWZnZsvGw8K+u7i1tLWzsrCrp6KhoJ+enqGgnJmam5qbnZqYlpaTkpWbn6CfoaCfnpuam5udnp2foqSmpaiEqoCrqauno6SjoqKjpKyvqaOdmpiWmZuam52enqGjn6Sim5mYl5qdmJWTl5eXmZyeoaCbnJ2mpaSfnZuam5mVk5GQkIyMjY+RkZGSlJeWmJmZmJyiqKenq6+zta20r6eipKGen5+foKCipqqrra2pqrS1srGop6qmoaCgnpycm5uamB+XmZqamZiVlJZLS5aYmpyenp+hoKOjoZ6cTUtMTExLhUwKS0tMTE1MTEtKSoVJgkiESYRKhUssSktMTU1OT1BRUlNVWFdWVlZVUVFRU1ZWV1RTU1NSUlRSUFBQUVFQUVNSUVCETwFQhE8LTk9PUVFTVldWWFiEV4BZWVpbWllZWFdYWVlVVFVVVlhXVl1fXFpYV1dXVVJUVVVVUlNTUFRWVr66uLezqKO1s6Cqop6vpaaoq6+spqGfucbHwLa1uby0rq6mp6OgoKKmq66ytrSxsa+qpaGgoKGgnZuXk5KQkZCQkI6MjIuIh4iJiYuMjo+PkJCUlpeWliKYmpydnZ+fn6ChoJ+cnp6hoaOmpqWkpamsrq+urqyrq62qhKWAoqGioaSko6OkpqytrKyppqKlpqekoqKhoJ2dlqCjmZOQjY+UlZaXmZmeoaanpqmpq6yopJ+goaKipqmrrbKxsbO1t1xgZWhnaGdral9aVlVYVVFQTUxMT09PUFFQT1FRUlZSUVRYXFxbW11dW1thZmdlYmFeXmBkaGZhYGNrc3KAdXJvbnFyb21vb25sampscnmEjZWWjouLiIqPlZqgpaalpaWjqamtra6ytLS4vMPJz9DQ0tPT1dja3N7f4uXq7fL3+vv89/z27O3y9Pp9/H7+fn7+/H9/f4GDg4GBhIOFh4iKi46Rk5OUlJaXmp2jpKOko6KipKSioaOjoJ+enJsDmpuahJgKlZmszdTP4vC4moCcpqSioZ6essy/yHhwqrW0tK+usbOyr7Oxsq+ysre9vr/Gz8K5uru7v77AxsLCwsPCxL7D3HR3f4STnZ+dmoqAeHRw1tdveouXm4R9cbmztMvP1NHLxMHFubSup7C5ur7DvLCjm52hscDCvLOnm5aXjpCcpJuMfnh2d3p8fYGCgYB9e32CgPDm5drW2NfVb3PRwcDCxcS7ury6vL/Avr6+ubSvp6KgnZ+gnZyYlZaSjoiFhIOChImGiIuOjY6Ig4J/fXt8f4CBg4KDf3x8fH5+f4KAfnx9fX98e3x7e3p7fX5+e3x+fX58fXt6e3p5enl7fHt3dXV0c3N1dHBv29rZ2YDZbW9ubdvX0tDOzMrIxsXExMC9vLe2trW0tLe3tLGxs7a1uLazsrKzs7KzuLm4tba2tbO0tre3uLe5u7y+vLu8u7q9vr/Cvbq7vLu7vL3FyMK6tLKxsLGysbG1trW4u7u/vbe1s7O0t7SxsLOzs7e4ur28tre3u7y8ubazsLG0sVStqaenpaipqaurqqytr62vr62tsbe8u7q+xMfKwsbDvLa1sa6vsLCxsrS5vb6/vrq9y8zGxr+8v7y2tbe2tLO0tba1tbi2t7m2s7O3XFu3t7e5vL2GvB67vF1cXFxdXF1dXV5dXV1eX2BhYmBgYF9fX15eX2CFXzRgYGFhYWJjYmJjZGVlZWZnaWlpbG5sbG1tbWtramxubm9tbG1tbG1ubGtra2xra2xtbGtriGoIa2pqamtrbG2FbolvBm5ubW5vb4VtWm5tbW9vbm1raWloZmRlZWRkY2RjYWJkZNPOycnHwb/Ix7y/urW9trSztbezrqupt7+9uLGytLayrqynqKakpKanqautrqyqq6unpKGio6KjoqCfnJ2cmpmbm4SahZkempqdnZ6foKChoqKio6SkpqeoqKmoqqqpqKmpq62uhLBEsbO2tbe4ubm6ubq6uLm5uru6u7m5ubq7uru9vr69u7y7vLy9vb27urq5urW7vLWxr6+ws7O0tba2ub3AwL7AwsPEw7+Eu4C8v8HBw8jJysvNz2hscXR0dHN4eG9pZmZnZ2VjYmFhZGNjZGVkY2RkZmhnZmdqbW1sbG1tbGxwdXh1dHNxcnR4e3l2dXiBhoWGhH9/gYKAf4GBf359fX6DipSco6OdmZqVmJ2hpauvsK+urq6ysrS0tbe6u7/CxsrQ09PV1tbY3C7d4eHj5ujs7/P4+vv9/P779fX3+v+A/4D/gID+/oCBgYOEhIKChYWGhoiJi46PhJAVkpKVl5qcm5ycnJqcnZybnZ2ampqYhZYNlZWUkpOdr7KuuMCgkoCEhoiJjIuIiYaIikVDh4yNkpKUlpmYlpuZmpuanJ+gpKWnqKejpKSmqqiqq6ipqKurqaamr1xfYmZuc3FvbGNeW1pWo6NRU1ZWVVRYV6eio7C0tLKtq6ennJiVkY6LioqKiIOBgYKEhIB8eXZ0dHRzdHRzcW9ta2lnZ2hoZmVmZ4Brb3F2duPY083IyMK/XmDBwsC9vb++vLq2sKqmpKOjpqCZmJSSjo2MiIaFhIiIhYJ/fX5/hI2LioiHi4yPkI6LhoWGio6Qko+QjYuKio6OjpGNioiIiYyJiYiHh4WIjIyMioyQkJCMjIuJiIiIiYqMjYqFhISDg4SIiIOC//79/FP+gYOCgP779vX29fX18/Du6+rr7ezp6Ono5+bm6Onq6+3r7e3p6e/z8u/p5+Ph4N7f4ufs7+/v6+ns7u3q6OLd4eHk5+rw6ujr7u3u9ff6+vTu54XmgOfp6uvt7/Dv8/Ty8e/v8PPx7u3v7u7y9vf39PDu6u3t7evo5eHi6Ojm4N7f3eHi4N/e3dzg4d7e393Y193k4+Hi5+vs5+nj3dfZ19TU1dXX2dzf4+Tl5eDl8vPv7+nm6+nm5ujp6Onq7e/w8/f4+Pj08/b7gID/+fj+///+/vz5BPb3+v+EgASBgoSDhYJHhYaIiYuLjI2Ki4uNjpCQjYyMjY+Pj5GPjpCSk5OTlZWTk5SWlpaYmJeVl5idnp2fn5+goJ+enZ6foaKjoqKhoaGjo6GhoaKEo4SiC6OkpKWlo6OioqGghZ9OoKCfn52dnp6en5+enp+goJ6enJycmJeXl5aUko+NjIiIiIeIh4eGg4KB//39/Pj6+vj08u/s6ePc2dXU0tHQzcnKx8PCwsLDwsDAv769hbwWu7q6ubm5uLi5ubu8u729vL69vb24uYW6Gry+vby8vb2+wMLBwcHAwMHAv77AwcDBwsTEhMVExsnJyMjJysrKy8zMzs3Nz9HT09XZ2d3c3t7g5eXo5OLj5ufm4+Pi4uXl6Onr6ers8PLu7u/w7+3u7/Dt7u7t7Ovq6uyF7Rjv8PH19PPw8PHx8PHz8vP29/n6+/6Bg4WEiICMjYaDg4SGh4mIiIeHiYmJiomHh4qJioyKiIiKi4uKiouMi4uMj5ORkpGRkZKVmJmXlpidoJ6fnZqam5ycm52dnJybm5yeoqiusrGsqquqqautrrO2trWzsrGytLW0tbe3t7u8vsHCxcbJyMrLztDS0tTW19rc4OTl5enp6ejm6Cfo6e137nfwdnfw8Xp6ent7e3p6fHp6ent8fX5/f3+AgYGBgoODhISEhQqGhoaHiIiHh4iHhIgOh4eHhoWEg4KAgIGCgoOLgoKDqIKOg4KCiIO4goiBgoLtgYWAhIH/gMiAgoGOgP+Bl4H/gMGA/4GLgQiCgYKBgoKBgbyCAgIEAICoraerubesyfff66ij8oOGopb+1MrEwcbIwrvCv7y7v8rR1tPLyMvLzNHMz9/Y09DS1dbT5/Dx496Ci5Olsbi/vK6Uh/+VxsO5l4Pv5+zq2+Tr6vGB9O378vHY1NbZ74GD/uzSyM/S/4WC/NjAwry7ubewo4uEhoqOlZKRkZOUkFeIgv2D/4Dz59/c3+z4gPfcz83Y3tHXzs3Gy8nKx8TCvrvAvr3GyszEt6+rqKSelJGOioiFg4KCgoODg4SEg4Z/fH19e3p7fHx8eXd2dXZ2d3p8fXt7e3yFeoB7fn59e39/e3p5eHh3d3Z0c3R0dnd1c3FwcG9xb21pZ2jP0M/Oz89nZsnGwsXDwb25tbSzsKypqKSioKGipKOjn6Cip6ikoqCen6CfoaSop6WloZ+dnqCgoaGho6Wmqqyurayurq6rq6qrqKepq6mopaSmpaOhnZuamZmZmpqbnICbmZ2hpKOdnZ2YlZSTkpCSlZWUlJaWmZqdnqCgoqCbmJiZmZeUk5WVlJSUl5eWmJycnJugn6ChoqSoq660ubq1sa2qramop6WlpKaoqKqvt7i0tLGrrq6trK6tr6uioqWmpKarpqCenp2fnp2en5+fUVFUV1aioVGjpKKnqammogifnJqcnU5OToRPCk5NT1BPTk1MS0uISkZLS0xLS0pLS0tMTEtMTU1OT1BQUVJTVVdWVlRUVVVUU1NUVVdVVFVVVFRVVFJTU1RVVVRVVVVTUVFRUlJRUlFSUVBPUFBRhVIUU1NUVFVVVldXWFhZWltbWllXVlaEVYJWhFhwXGFiW1tWVlpWWV9cXFlXt8Owx8fAs8DSv6+rpKinpKKlpqKinZ2hq7S7v727wMG8sq2rpKCdnJ6jqayvrqyqqKimpKSipKehm5iVk5OVk4+Oj46MiYeHhoWFh4mLjY+PkJKTlZWXmZqam5ybm5ycnYScgJ2fo6SjpKKjpaerrq+vr7CwsrKwraimoqCgoaeqq6utr7C0sLC3rqWioKOlop2amZeZmZ2hoKOXlJOWl5qfnp2hpKWnp6akoaKhoaGfn6CgoaSnrLO3tra2t1xcXWNoaGdiXl5fYF1TUE1LTlFQTVJTUlRTWFZSVFVVUVFQVFhYgF5fYGNjYWRmZmhlZWRjZGpoZGVrc3N1c3l2c3h5dXV0cG5vb3Fyc3Z8iIyPkpaZmpyYl5mZnaGkpaipqqqusLO0tbW1uLu9xMnNztHV293g4uXl5Orr7Oro6Orw8O/x8efp8fT07+3p5eXl5OPr7u3r8vLx7fD4foGGi4yNjY2PKZGVmJqcnqKlpqmrqKWjoqOgn5+dnJqamZiYl5aVlZaWnrPFs7XR2MKhgJqdnJ+pqaCtyLvAfHrGaGp6ddK8uLi2u7y3sbWysbO1vMPLycK9vb3BxMDE0cnEwsbJyMTT293UzHV+hZGboaWhlYJ23XuZlY58ctXQ19jO2drX3XTf19/X0cO8urvFaGnNwbKus7POaGbDsKGgmpmXl5KLfnl5enyAf35+f399Snp57331eefe19LS1ttv2s3GxMjKxMnCwru8urm1s7CtqqupqK6wsaqgm5iWk5CLh4OBgYB/gYaGhYWEhoWEhX99foCBgIGBgH99hHsJenyAgYGBgICBhX6AgIODgYKHh4GBgoB/f359enl6en1+fHl4d3d1d3VzcG9w3d3b2t7fb27Y1dHU0tDPzcrHxsPBv766t7a3uLq6ube3t72+uru6ub2+vcHCw8G/u7i4uLm4ubq6ur2/vsLCxcTCw8HBwcLCwsDBw8bFwL28vry6ubazs7OysrSztLSAs7O3u7/Au7q6tLCwr66srrGxsbK0tLO0t7e3tri2sa6usLKurKuusK2ur7CwsLK1tLOzt7S0tbe4u7/DyMzOysa/u726urq3uLa3urq7wcrMyMjDu8DDw8TDwsXCt7i/vri5vru1tba3uLi5ubi6ul9hZGVjvb1fvr68vr+/vbwdvbu6urxfX2BgYGFgYGFiY2NiYmFgYF9eX19gYWGFXzhgYmJiY2RjY2RlY2RlZ2dpaWlrbW1tbGxtbWxsbW5vb25tbm1ubnBubW1tbm5tbW5ubW1sbGxra4ZsCGtsa2xsbW1thG6EbwVwcG9vb4VwBG9vbm6FbVFubm1sbXBvamlmZmhlZ2pnZ2VkzNPH0tTRyc7Yy8HAuru6s7Gysa2rqaqrsLS3ubm2ubm3sKuqpqSioqOmqaqrqqqopqalo6OkpKWjoJ+dnJuGnBubmZmYmJiZmpudnp6dnp6foaGhpKWlpaenp6iEp4Cmp6iqq62tra6vsLKztre3t7i6uru8vLu6uLa4uLq7vL2+v7/BwMHEv7u6ubu8u7u6ubi3tri5t7u0sbK1tre6urq8v8DAwcHAvr69vr27ury8vL3AxMjMzc7P0Whpa29zdHNvbGxtbWtlY2JiY2ZmY2dnZ2hpbGlmaGloZWRjZ31paW5vcHNzcnR1dnh2d3V2d3t4eHl9hYWGhIiGg4eJh4WEgoGAgYOEhYeMlpqcn6Okpaeko6SjpqqtrrCysrK1trm7u7y8vcDCxsvP09TY3N/i4+fo5+zt7ezt7e7z9PP39/Dx9/n6+PPx7u/u7Ozz9fTz+Pj39Pf+gYKGiYSMGI2OkpSVl5mcnp+goqKenJyenJuZmJiXl4WUDZOSkpKWoKuiobCzp5WAhYWIioyMiYuKiIpHRolGR0lKlpaZnp+hop6anZ6cnp+lqq2sqaeoqKqsqqquraqpq62sqKu1ta+uXmJobG1ubm5pXlipVVhZWldWrbGysq6yuLW4X7WurqilnpeVkI9IRoqKio2OjIxDQoB+fHZycHBxb25vbWpoZmdnZ2ZlZWdJa27hduly3NfSzcnFwV+9wsPDwsHCwb+7tq6qp6WhoJ6cmZiWlJSTj4eFhoWFiIeBfXx9f4CKlZGMjY6OjoqIhoSIjJCQkY+LioSIYomIiY6Pjo6LjI2JiIiIiYuQko6PlZiRkZSSj42Li4mHh4iMjYmFhYSFhIeGg4CAgfv69/r9/oCA/Pn09vX19/j18vHv7u7u6+nq6Ofm5efp6eXq6+zs7+/09/n79/jx7evpheoN6evt8fLv7vL28+zp64TsgPLy9fn8+ffx7/Du7/Dt6+zr6+vs6+rr6+zu8PP28vP28+7u7O3r6u3v7/Dw7e7t6ejp6Orl29vd4eTg39/g4uLk5eTi4uHj5OXi4+Dg4N7f5OXn7O7v7Ojj3uDc4N7c3Nrb3d3f5e7w6+vo5ers7e/v7/Tx5ufv7+fn6+no6uztKu/v7O/y9PaAgoSCgf/+gP/9+ff3+Pn7/f7+/P+Ag4SEhYWGiYqKiouMi4SKN4mKi46QjoqJiIeJjZGTkpOSkpKTlJCPkZKTlpaXmpqam5ucnZ2en56eoKCfnp+hoqSkpKOhoKCEoSqio6OjpKSjoqKjo6SkpaampaalpKSjo6OkpaSko6OhoKCgn56enqChoaGFoEmfn52bmpiUkY2Ni4mHhomFhIOCgYD//f/6/Pv79vLu7uzp5d/d29fU0dHNy8nGwsHBwsLAwcG/vr28vLy9vLy8u7q4uLm3uLi5hbsVvLu7urm5u7u6ury9vby9vb69wMHChMB2v8DBvr7CwsDCxMXExcXExMXIyMjHxMfJysnMzMzLzM7P0M7R1NXX2Nze3+Dk4+Dg4eLi4uDh4+Pn5ubm6Ofm6e7z8+/u7u/s6erq7Ovt7fHt7O3v8vHw8PL09Pf08/Py8/Xx8fLz8/T4+fr7/YCBg4WIh4eGhYSGgISFhoiJi4uIiouKioyPjYuKi4uJiIeJiYqJiouLjIyNjpGTk5STkpSWlpaXmJ2en52hn52foJ+gnpybnJydnp+foqiqq62ur7CvrKysq62vr7CxsLCvsrKys7S1tbW3uby/wsXHyMzO0NHT1dXZ2tze3t/h5OXl6Ojn6e3v7uvqFOno6enq6+zw7+7x8fHv8PJ5ent9hH4qf3+BgYKEhYaGhoiKiIiHh4iHiIiJiIeGh4aHh4eGh4aFhYODgYKCg4ODi4IDg4OChIOngouDAYKGg4mCAYOKgoKDh4KCg5iCBIGCgYKHgQGC74GGgIKB/4DIgIWBA4CAgY2A/4GSgf+AwID/gZ6BsoICAgQAgJibnqO2rq+90svepJ6PnLi1mYb+yMTIyMTFx8/FxcfJyc3S2uLe3Nzb2d7a3uLu5dfT0s/Q3vSQgoWEiY2PmrbDzNfDnYyKobalnJOD8Ov25dnR2+b9g4aIhoiKiIX/+e7g1cq9ssXm8OTZ5uTQvrWvpJybnJqWko+Plp2gmY+NgIf78/eBgu/r59zh6vPw7Oji5eLl6+vi18rBvLnBzM3OzNrY0Nvh3s/EwcO+t6eZkY+TkYuLjIiIhoSEhIiMjIWCgH18fHt8fX58fHt5d3Z2dnd5e3x6eXp6e4CEhoiFgYCGhIF8fX58fHl5d3d2dnd3dnZ0cXJxcW5s1mrU1NHSaGppaWlnycnFxsPBv728urexrKutraypo6OkpKKgo6ampKSlpqeoVFZYWluyrKmmpqepq6mqqqmqr7Kwrq2wr66vr6qpqKilpaempKOhop+dmZeTk5STkpOWmJqampiYnqSorrGpmpeXhJIXkY+QkJGTlZeanZ6enJudm5aUlZSUlZqEm4CcnaCfoKChoKOhoqWpqq2vs7W3tq+ysbKztLGzsbO3uLewra+0tbOtp6CenaCnqKmnpqWjo6KipKWhn6GipKKlpaejoKOkpqlVVqmnpqmopaaqrKmnqFJQTp2gn6BPUE9NTU1PUFBPT05OTU9PTUxMTEtLTE1NTEtMTEtLTE1MTCZNTk9QUVBRUlJTVFVUVFVUVVZVVVVWV1ZVVlZXWFlZV1VTVVdXVYVTAVKEUwlUU1NTUlFRUlKGUwNSU1SGVYBWWFtdXV5dWlhXVVRUVVZVVVhZXFpbW1tYW15cXWBiYWFdXrKutr+zqKu7zM+8vcK6r6uorK2koamzp6i3wNzeyb/CwrevubatqaikoaOlp6SjpqakoZ+foJ+fnJubnaGlpqGal5OQjIqIiIeHhoWHiYuNj4+PkZOUl5iWlpqamYCZm52cm5ycnZ2goqSjpKSlpamrqqysq6ytra2uq6elo6WkpKepqKuxtLe4s7Gzr6iinZ+goZuZl5KUl52foKqooJuampmeoqKioJ6dnqCfn52enp6fn6Gkp6mrrbK3ubm5XFtdXl5hZmZjXFxbVVNRT05RVl9maXBsa3F0dGdeWoBaWFhYV1hZWl9hZGpsa2ZgY2VubWltdG5obG5xeXd4fIF5dHZ6fXdzcnJydHl8gIOGh4qNkpOXmp2hoaCkqKmusLO0tLGxsrO0tba0tbm/w8rQ2d3Z2tve4uDg4eLo5+Pj6Ovt8PHy7u7z+H338vLr6OPh29rY2NfX2Nve4OHj6THy+H2Ag4WJjZOWmZygoKGhn6CkpqajpaOfn52bm52dnpybmZWTkpaSkpuepKquuaaVgJKUl5qmoaGos7C6endvdoaFd23Wt7S5vLq6ur65uru8vb/BzNXPy8vIxsvKzNHY0cfEw8DAztx6dXl4fICAh56prreoin16h5OKhH5w0NHc0snDys/idXZ2dHR0cm/X0Me/uLKpoaq/wbezu7qsoJaRi4SEhYJ/fXx7gISGgnt6gHbm6O15eePd29PU19vY1NPPz8zO0dHIv7exrqyxubi3tLq6tbu9u7KqpqijnpSMh4OGh4aHiYiJh4eHhoaFhIKDgYGAgH9/fn59fn17e3l6e3p9gIB/fn9/gISHjI+LhYaNi4aCg4SBgoB/fXx8fH19fHt5eHl3d3Vy4nHi3t3fgHBxcXFv2djU1dLPz9DOy8jFwcHAv728u7u8vLm4urq7vr++wsTFZGZoaWnLxcLBwMC/v8DBv7/Cx8rJxsTDw8LDxsLCxMPAwMC/vLu5u7i2s7Kwrq+urrCwsbO0s7S0ur/DyMnCtbOzsK6trq2sraytrq6vsrOys7GvsLCsrKyqc6qss7OytbW0t7i3uLe3tri2t7u9vcHFyMnLyMLExMXHxsXIw8TJysjAvcHIx8S+s62tsbW9vr28vLq5t7a0t7m2tLe7vLu/wL+4tba1vMNiYsC+vcDAvL2/wL+/wWBgXru9vb5fYWFgYWFjY2NiYmFhYWKFYQhiYmFhYGFhYYRiDmNjZGVlZGVnZ2hpaWpshW0Zbm5tbm9vcHBvcHBxcXNycW9ub3Bwb25tbYRuBG1tbW6HbQNubm2FbgFvhnAZb3BxcnNyc3NxcG9ubm5vb25ub29vbm5tbIVqO2xrampoaMrHys/Jw8TL09TJycrFv7m1ubawrrK2ra63ucnJvbi5urOtsrCsqKinpaenqKalpKWko6GihqOApaWnpqSjo6OfnZyZmpmam5qcnJ2en56foKGhoqSjo6Wmpqanp6eoqKenqaqrra6ur6+wsbGytLO1tre5ubq5uLi4tre3uLq6vb7AwMPBwsPBvrq4urm8u7m2s7S0tre4wcC5tre3t7y+vr++vLu8vby8ubq7u729v8HBwsTGyc2A0NDSaWhqamxvcnJwbGpqZmVjZGRmanR6fYJ/fYSHhXpxbWxqa2pra2prb3ByeHp6eHJ1eH5+en+Ffnl+f4KJh4eLkImGhouOh4SEg4SGiY2Rk5WWmJqeoKGkp6urqq2wsrW3ubm6t7e4ubq8vLm6vsPIzdPa3dvd4OPk4uLj5upT6ujo7fH19/n48/b6/oD++vn18u/s6Ofj5OTl4+Xo6uzt8Pb8gIKDhYmMkJKUl5qcnJuanJ6hoJ6enZycmpeXmJmZmJeVkpGQkpCPlJWZnJ2kmZGAhYaHiIuKiouKiolGRkdISkpJSpqWmp2fn5+ho6ChoaOkp6mrsq2sq6qqraytr7OuqamnpqWtsllZXl5hY2FmcHV0dnNoXl1eX11bW1Wloqywq6errrJYWllYVVNRUJyWko+Njo2Hh4qFgoGGhX94cnFva2pqaWhnZ2ZmZmVmY2KAY9Dg5nFs1dLS0MvGw8G/v8G9vLm4tLKrpqOlpKWkoZ2amJmamZWTkImIh4ODgYJ+fH+AhYeKjY6OkJCNiYWDhoyLjI+PjYqIh4aHhoWFhYaGhoiMjIqJiYmKkJOXm5eRkZ2elpGTlJCPi4uJiIiJi4mHh4WDhYSEgYD/gP77+fsWgIGBgYD6+PH29fP1+Pfy8O/v8O3r6YTogOnp6+rm6vP18fT8/4GDg4KA+fb19fPv7Ort7uzt8ff59vTw7evn7fHw8fT09/f39fDw7evr7Ozt7Ozs6+nq7Ozs6+vs7fH2+/37+fLt7Orp5+jp6Orq6ejm4OHg3+Dj3Nvd2drc2tze4+Tk5uXl6Orp5+Pk4uLf3+Tk5ejo6uvseevk5OTl5+rp6eXm6erm397g5+fn49jU1dje6Ojq6Ojm5OLh3+Hk4+Hl6+7u9fTw5+Tm5/H+gIH8+fv8+vP28vHz9vuAgYD7+/z/gIKGiImKi4uKiomJiImJiImKi42NiomJiImLj4+NjY+Pj5KVk4+OkpSUlpiYmZqEnBCdnp6eoKKioaGhoqOjpKSkiKMFpKWlpaSEoyGkpKSlp6enpqamp6Wmp6eoqKenpqWkpKOhoKCgoaCgoqKEo4CioqGfnpuZlpOQj4uIh4eEg4KBgoD///78/vv49vHv7evp5+He3NnX1NDPzcjGxMHCwsLBv7/Avr67vLu8vb27u7u5ubm4ubi5u7y8v7/Av7+9vr6+wcLFw8HAvr+/wMHDxcXFw8PCwsPCwMDFw8HExcPExMTDw8TFxMXFxcbHyALJy4TJN8vNzc7R09TW2Nvd3dzh39zd29zc3d3f4OHj5OXl6err7/Pz8vDs6+zr6ujr6ufs7u/u8fLy9PKG8xny8vLz9PTz8/T09fj6+/3+gICCg4KFiIiHhYUUhIaHiY2Sl5icm5idnp+YkY6NioqFiYCKioyOjo+PjpKSlZaUlpmXlZiZmp+en6GjoJ6foqWgn56dnZ2goqOkpaWmp6mpqqusraysra+vsbGys7KxsrGxsrO1tbW2uru/w8nMy87P0NLR09TY3d7c3ODj5ebn6ebp7PB58fHx7u3s6ujo5ebn6Ojp6ejr6+3w83t6e3x9fguAgYKDhIaHhoaHiYSKhokJiIiIiYmIh4aHhoUFhoWEgoOLgoiDpIKWg4mCiIOjggWBgYGCgvSBAoCBhICFgaGAhYH/gKWAgoGMgIOBhID/gY+B/4C/gP+Bi4EBgpaBr4ICAgQAgKinpqmnpamrprfmhoaJj5qY+NX32cTDwsvQ2OTs39rc4ejg3vL26PH37ebn6Orv+vnw6uXvgpGajoeEjJChs8vh8Iz96dzFpqmwvbifi4eD4dDR0tfq9oGOjIaFhID29vj0+di4pb/K087i9fPgyKmer7Szur+yopaIh46RjIuIPIT+9f6EgPXq4OPg6+Pu+v/66NjX3N3WzsK9urnBwsXM5vHk39nQxL+8ubi7tKWWjouJh4mKjoqMkZGGg4SBLYCCgICAf39+f4GAf3x5dnV0dnh5enl6fH2AgoWKiIaDgYSBgIGDg4B/fn18eoR4fHd4eHh3dnJv29jX09fV1G1q0tPSy8rFwsHAv7+8uba0sKqrr6+tqammqKakpqimqKenp6muWlxeXbazrqutrKysrqutsLCxsLK0sa2tq6mopqano6OjoqOioJ6cnZuZmJeVlJOSkpKRkpGSkpSWmJugqKijm5mcmpiXlZSEkYCTlJWVlpmZl5aYmJWVlJOWlpqcnZ2dnqKkoqOgoKOlpaisr66tsri8vrm7vLy9wL67t7O0tLOytLOztrSmnJqZm6WopaGko5+jo5+bnJ+mp6WhoKCgoqWmpKGhpqepq6yvsLGxsrOwrq6tqKanUlFRoaGkq6dPTU1NTk9RUFBQUQFQhE8GUE9OTU5OiE1DTkxMTk5QUVFRUlJTU1RVVVVWVldYWVhXWFlYWltbXmBjZGJdW1pbWlhUU1RWVlZVVFVUU1RTU1NRUlRVVVVUVFVVVIVVb1RVVVZXWVtcXV1bWFdWVlhYV1dWWV1cXGJjZV9bWVdWWFpbXF1dXMPIw7jDuLK4uLi2tK2vub2sqauppKasr7W7xtbb0snR1M67vLq8urm4sa6rpaGipaOdnJyamJiWlZedpqajoZ+blJCOjYyLiISHgImLjI2Pj5GSk5WVk5SVl5eanJyam52dm5ucnqGjo6WmpqmqqqqtrKuppaOjoqKjpKepq62wr7G2trm6sq6po6GgoKSmopuenpabp6ennKKmp6GZmpuWmJubmJaXmpydnZ2bnKClp6upqKirrrO4XVxaWl1eXl9gZGNhXVxaV1RXgF9bYmx0cW1uZWBfYl1aXV1eWlpdWVhZW19iYmRpamZjZGdqaW97hIFwbXB4fH17eHl0en1+hH6Ag4B/fn6BhImMkJCPk5eYm5+ipKivtLi8v8HBvr28uba1trm4usHGzM3U2t7c2dfa3t/f3Nzb4OTl5ebq7u3t7O3s7O3v8ffyJevm5N/b2Nna2tze3+Hm6u/z+H6BhoqOkpaZnqmwr6mloaSmpKWEphinpaGio6Cen5yal5WTlZWWl5idobC5qamAoJ2dn56cn5+bor1pamxvdnXPus/Atbe4vcHIzNPMysvP1MzO4OLW3eHY0dLT1trj49vV0dRwfIB7dnh+gYyaqrrCbc/Jv6yUjpWel4RzcHDNv8DCxdPdc3t8dnNxbtXR0tHTuqWZqK+xq7jExLalkoiRk5OWmJCHgXl4e3x5eHaAdOTk6Xh249vV19bZ0tfd3tzRxsPExL63sa6trbO1tbfEysPAurKrqKSioKGck4uFgYGAgIGGhIeLjoiHg4F+f4ODgoKCf39+f39+fX17eXh3eHp7fH1/gYKFh4qPjIqIhYiGhYeKiIWFg4KAf358fH19fn5+fXt4defk4+Dg4eF+c3Hf4N/Y2NTRzs3P0c3IxsXDv77Awb68vLu9vby9vbq8vr6+xMpnamxrzsrEwsPDw7/AwcPGx8fEyMzKxMC/v7++wcPAvry8u7i4tre2tLSzsa+urq2usK2trrCvsLKzt73Bwbu1s7W1srGvsK2rq6usrK2ur66vsK+wr62thKxPsLS2tra5u7y7u7a2uLu7vr/Bv77Fyc3Ry8zOzdDT0c/LxsbHxcPCwMLGxLasqKeuubq5trq3sba1rqusr7q6t7OxsrK2uby5tra2t7i8vYS/K8HCwcC/v76+wGBfYL+9vsTDYF9gYWJjY2JhYWJhYmNjYmRkZGJiY2JiY2KEYwZkZWVmZ2aEZwpoaGprbW1ubm1uhG8acXJycnNzdnh6enh2dHNzdHJvbm5wcHBubm+IbgVvb3Bvb4VwiXF1cnJzdHRzcXBwcHFwcHBvcHJxcHNzc25ramloaGhpaWhoZtPV0MvRysbJyMfExL/AxcW6tba0sbCxsrW4vMTHwbzAwb2ys7OzsbGwrqupp6OkpqWhoaGjo6KhoaKlqqyqqqmnpKGgoJ2dnJ2enZ2fn5+goJ+fhaEIoqSlpKWmpqaGqICprK2sra6ur7Gzs7S1tLW0tLO1tbS0tre5u726vcHAw8XCwL66ubi5vLy7t7m4tLa+vr23u7+/u7e3uLe3uLm4tba5urm6ubi6vL/BxMPCxMbHys9paGdnaWxtbW1wcG9tbGtpZ2xzb3d/hoOBgHh0dHVxbnJycW1tb2xrbG1xcoBydHl6eHZ3enx7gIuSj4F/gIaKioqHh4WLjo+UjpCSkJCOjpGTl5qdnJueoaGjpqqusbe6vcDExcTCwsC+vLy+v77AxMjP0dfc397e3d3g4eHh4uLn6+rr7PD09PLx8/b3+Pn4/fv49O/q6ebm5ufn6evt8PT4+/6AhIeKjJCTlSeaoaemop+dnp+foJ+hoqGgn52dnpuZm5qYlZOSkpKTk5SXmqKnn6CAjIqLjYyLjIyKiYpGRUZHSEiNk5ibm5ydoKGlpKSmpaeprqyrtbSssbSxr6+ur7K1tLGvrKlVWVhZWVxfYWNpb3JzP4CJhnxrZGJnZFlQTlKqpaWmpqqvWFtbV1RSUJ+dnJuck4uJiYmHg4SEgn54c25tbWppaGdnaGppaGZjYmFyYMjV12xq0c3R0M7Iw8G9vLy6trGup6ShoaGjpKamo6CfnZubmJSSjomHhYKBgH6Af39+fH2DhIiKjZCQjYqDho2LioqJiIeHhoWEg4SFhIKBgISHh4eLjY2PkJWal5SRj5SUkZOWlJGSkI+MiomHh4iHhIkliIOA//7++fz8/YKA/vv8+fbz8vLz8/Xw6Ovu7ezs6ejl4+Ll6ITpL+bo7O3u9/6AgYKB+fPw7/H08erq6+/y8fHx8/v58Ovp6uzx9vj39fTz8e7s7+7thOuA6unn5ubp6evs6+rs7O/z9vn28ezq7Ovq6ebn5efl4uLg4uHf3+Hi4N/e293d3N3e4uTm5efo6+3q6OHf4OHh4uXl4d/k6O3w6uvs6+zu7u3p5ePk4d/g397i4tfMys3S4OLh3uPh2t/e1NDS2OTn4t3d3eDk5+zr5uLh5Obq7e5K7u7t7uzv7/Dw8vj9gICA/fz7/f6DhIWHiYqKh4WEhIOHiImJiouNi4mLi42NjI6OjY+Rk5WWlZKRj5CRkpSWmJqbm5ybm5ucnZ+EoQaioqOjoqCEpBelpKWlpqampaelpKOkpKSlpqanp6empoSnFKioqaqpqKinqKenp6WlpaSjoqOlhKSAo6Okop+enZmWkpKQjo6MioeFhIKBgP36+fr29vTy7ezp6unl4N7c2NfRz83Lx8XBwcHCwb+9vby8vL69vLy7ubm8vLu5ube4urzAwsTDxMTExcXEyMvKysbFysfHyMjHycnKysnLx8TDw8HBwcDCxMTEw8PExMbHxcXGxsbHxsUrx8fFxMfHx8nKztDV1tbX19jV1NbV1dra2drb3eDi4uXn5+fm5+ru7+zu7ITqIevo7Ovs7u/x8vLv7/Dy8/Py8fHz8fLy8vP09fb39fb7/ISAJYGDg4OEhoeGhoeIh4mMkZCVmZ+enZ2ZlZSVkY6QkZGNjY+Mi46EjYCOkJKRkpOVlpWYnaKhmpqZnJ+enp2cm5+io6eho6WjoqGgoqOkpqempqanp6iqq6yusbO1t7i5uri5uLa2tre2tre6vL/BxMfKysvMztDT1NXX2N3d3d/g4+jo6unq6+vv8vL19PHv7e3r6Onp6uvs7e3t7/Dx9Xt7fX+AgYOEhQqJjY6LioiJiYmKhIsYjYyLjY6LiYqJiomHhoeHiImIiYmKi4yNi4KGg6SCjYMBhI2Dh4KHg6OCBYGBgYKC84GHgIKBpICEgf+AtYCDgYWA/4GOgf+Au4D/gaWBr4ICAgQAgLa8trKupqShnaO2y9bZ0N3NydPfzMrPzMvQ19nh8/j4//38/oqLgvv9goL58O/1/4OGhYSDo8qkk5GGiZyp1IT1iKiZjY2E59LJ29DJwL2s5dPVzc3P1Nrm6fOFif/u74D98/DZur/Hztbg3dTIsKrBv7i1s6WbmpeJ+/iAg46TgIuHj4WDhoCAhoLz++vi3N3Tzs/X4eTVz8fHy8S9urfU7t/e6ejVxse+vbu1rqGSi4yPjouKhoSChIiKioV/fn6BhYWHhoSBgH99fX16enjtdXd5e3x9fX6BgoOGiIuJiYaDgoWKjIiEgH58fXt8e3p6e3t8enl2dnVxcG/d29nZWNnU0tPQy8rGw8HEwb+7t7Wzsa+usLCur7CqqKerrq2rqqqrq7C0tLSzs7Wyr66tqqytra2xsbGwr6+urqyrqKimpaKhnJyen6Cgnp2bmpqZl5aXlJOUk5KEk4SSGpSWmpybnJycmpmWlZSRkZKRkpKRkZOUlZOUhZdtmJqbm5ydoKCjpqWipKSmqa2wsrW8vLe4vcPGxsnGv73BwLe2uLa1s7Kvsbe1sJ+WkZSZn5+Zl5WWmJudnqGkpKWlpqWjop+fn6CjpKOlp6ipq62ytLe6uLOuraympKampaJRqaiopqRRUU9PUIVRAlBPhU4UUE5NTk9PT1BPTk5PT05PUFFSUlOFVCRVVlZVV1hYWFlZWVpbXV9hYWVpaGVjX1xbXFtZWFZXW1xaV1WEVgdVVFNVVVVWhlUNVlZXV1ZWV1dWV1dXWYVaaFhZW2BiX1xdWllYV1lcXlxcXV1dYl9gXV1aV7nHzMXGxbzAxsC0t66yyMCyt7mwsrGvqaanrLTFzMvN1NfOvLe5v7y+vLqyraekqKmln5+fmpaUlJWYnJ6fn56enZiTkZCNi4uLjIuOhJCAkZCSkpKRkZOTk5eYmZiZmZqcm5ycnJ+ipKenp6utrq+vraypoqCfnJ+fo6qvs7W3tre4uLW0sK6onqCho6KjpqWko6Ggn52gnZmWm5iUmZ2cmZiampucnJ2fnp2dnaKlp6uur6+urrNcXV5cXV1fY2BhZGJcWFdZWVdbW15iYWCAXVlXV1RRU1ZgZWVlYl9hXVtbXmVhXlxdYGJjZ2tucnd2c3FzcnNyeXh2dnl7eXx7goSBgoGBhYaGiY6XmJeWmJ2goKOorK+0uby/wcHAvL3CxsbAvLzBxsrO09XX19nd3Nnb4OPl6OXk5eXm6Ojp5+Tm5+jj3tvZ1tbV1dbW1dU81tba2dzc3+Lm6u/0fH6BhIiNk5ebnqm3ubCnoqCio6Omp6WkpKKin5+eoamlnp+jqZ+goKOoqqyyr6qvgKuuq6eknpyYl5qjsLW3s7u0srnBuLrAv7y/w8bO3N/e5ePi43p+duPldXXj2tje5nV4eHh1h52Ifnx2eIONqWXCaH13cHNvxrmxuqeglpKK0MLDvr2+w8fM09t1eN3PzWzTzM29pqmusLS4t66llZGcm5eVk4qEg4F55uNyc3l8gHd1e3d1dXN0dXLa3tfSz83FwL7Dx8a8t7KztrOurK2/yb6+xcK2raulpaGdmI+Hg4GEgYCAgIGDhYmMkYd/fXyAhISEg4F/fn18fHt6ennweXp7fX6AgYKHiImMjo+MjImGhouQlI6KiISCgYB/f35/gICBf397fHt3dnXm5OPlYuXg3eDf2tnX09HRzs7Kx8XEw8LAwcG/v7+9vsDEwsC9vL6/v8fNzs3Jx8fDxMTCvsDCwcPHxcbFxMXFxcPAvr+/vru7uLi3uLi2tra1s7KxsbKyr62urquurq6vrq6ura6yh7WAtLGvr6+tq6utq6qrra2ur6+xsLGysrGytLa3t7m6vL68uLu6ur3Aw8LGzcvHyc7U2Njb2dLP09LIxsnGxcLAu77FxMCvpaCkp66vp6WjpqirrK2wtLa3t7m2srKxsLGzubu7ube3ubq8vr/Cw8PDwsC/vby+wMDAYcbAwsLBYGEBYYRiAmBihGMLYmJjY2RiYmNkZGSFZTBmZ2dpaGhoaWhoaWpra21ubm5vb3BxcXBxcXN1eHh7fn57eXd1dXV0c3FwcXNycnGFcINvh3CDcYlyA3FycoVzhHKAdHZ0cnNxcHBvcHFxcG9ubW1ubGtqaWdkz9XW0tLRzM3PzMTHwMDMyL/Bwbm6t7Wxrayus7u+vr7Cwr2zsbG1s7W0s62pp6Woqaajpaimo6Ojpqepq66ura2rqKWjo6CgoKGioqOjo6ShoaGioaChoqOjpKSlp6anqKenqKepqqqArK2vr66vsrO0tLO0tbOxsLGysrO2uLu9vr7BwsPCwb/Avbe4uLq4ur68vLu4uLm3uru2tLm2s7e4t7e3uLi4ubm5uru6uru+v8HGyMjIycrOaWtramtrbXBub3FxbWlpa2trb3BydXV0cm9sbGlmaG10eXp5d3N0cG9vcndycW8bcHN1d3p9f4KGhoWEhYWEg4eGhoWIiYiKipOThJFJlJWUlpuioqKhoqeoqauvtLe6vsDDxcXEwcHHysrGw8PHzc3Q1NXZ29ze4N3e4eTo6Onp6+vr7e/x8O7w8PHu6+jn5uTk4+Tk5ITlOefq6ert7/P4/YCBg4aJjZGUmJqirK+ooJybnZ6eoKGioJ+cnZucmpqem5manJ6ZmpyeoqSkp6ajpYCTlJOSkY6Ni4uKi4uLjYuMj5KVl5ugo6CfnqKhoq6ysba1s7VgYFuyslpasq6tsLRcXFxdWVpdW1tZWFhcXmU3cTo+QEFER4d9en5uY1pWWKympKGhoKKkpaapWFeknp1PnJqbl46NjYqHgYF7eHFvbW5tbGtqZ2hpaNTSZmNhYIBhZGZoZmRlZmJgwsPJxcTCvbaxr62oop6foKChoqWqp6Ccm5iXl5SOioeFhIB+f318fXt6e3yEiY2TmJ+RhYGBg4eIh4WFg4SEgYGAgIGB/oGCg4aHiYuMkJCSlZWXlpWTkZGVnKCalZGOi4uKiYmJioqLi4qKh4eHg4GA/fv6/Xz++fX5+vj49vPz9fDu6Obm6enp6ujp4+Hj5ujs8ufi4eTn5+nw+/788evq6+7u6Obp6+vt9PHu7e/w8PHw7ezv8vPz8O/v7ezr6+3s6ufl5eXm5+bl5OTm6Onr6+zp6unr7Ovp6Onp6u7t6ejm4uXj4ubl4t7f4eHf4OPlhOSA5efo5+Xn6unp6OLi4eHi5ebn6Ozp5OTp7vPy9fTr6Ozr4+Tn4+Hf3Nnc4ODez8TAw8nT1MvKyc3R1NTU2+Hi4ePl4dvb3d3g5O3w8Ori4eHk5+nq6enm6/Dz9PP19/v9/oD++vr8/YCAhIeIiIaBhIaHh4uMioqKi4mKjIyMjZBEkJGRkpSWl5eUlJOSkpGSlZeXmJycm5ydnp+fnZ2bnJ+jo6KkpKKjo6SlpKOjpKamoqKkpaWkpKSlpqeopqeop6eoqKeGqAWnqKeoqIWngKWlpKOkpKOfn6Cjo6OioqKfnJmYlJORjomHhISDg4L+9/b39PLz8e7r6+vp6ePf3dnW0tDNysbGw8LBwcDAvb27vL29vLq6u7m6vL28u7q7vL/BxcXGyMvNzdDT2NfW19XQzczN0NHRztDP0MzLy8fHxsXDw8TDw8TExcbGxcbFRcTGycjJyMbGxsPCwsPFxcbIycvN0NLT09XV0s7Pz9DU19nb3t/g4ePm5+bl5Ofn5ubm5+jm6e3t7+7t6+7u7u/w7/Hx8IXvRfDy8/Ly8/X19vj3+fv+gYKCgYKDhYeFhYaHhoaJi4yOkpCTlJSTk5COkI6MjZCUmZaWlZKTkZCRkJKRkI+Pj5CSlZaWmISaD5ucmpmbnZycnJubnp6jpIShP6Oin6CkqKinpaaoqqqrrbCxs7S3ubq7u7m6vL7Avry6vLy+wMPExcfJy8vJy9DT1dja293c3uDj5ubm6Onq6YboJenp6unp6unr7O7t7e7w8PL0fHt8fX+BgoOFhYiOkI2LiYmKiouFjBeLjIqMioqKiYmKiouLjY+QkpOTk5GQkaSCB4ODg4KCg4OFgo+DAoSDhoSJg4uCBoODgoKCg5mCgoGOgsWBAYCsgf+A5oABgYWA/4GNgf+AuoD/gaWBsIICAgQAgLu6t7Cuq6aipbCuqaursLm4wc7R2tvc2+Xw8vjn6P+DiJKXkZKgrJ+QhoqQlpaVl5WTlpSYqqibl46MkJ+x+JGKjJeprJ+alI/93dne7t+h/f71yLq1vtHd3Oj7iIKRmoyG98u5ury82sXKxb++vMHKyb+4trKglpaSioWHkJ2YgIuDg4KEiPaBiYyJg/vn1tfe4On4gPzt4N3b3tfEvrq61Nvc28m+uL6+uLWzr6ielZKRkYyIhYOAhI2NjYmE/fh+gIWHhIJ+e3p5ePJ583l5eXp5fHp9gIGDhouNjo6Li4eEh4mKjYmFhYeCfn19fXx+gYSCf3t6eHl1dHFyc3DbgNvV0dNratDNyMbGwb68uLOxsbGwsLGxrqyprK+vr7Cvrqytra2ws7GxsrCxsLGvrK2trK6ysbCvrayrqaalpaKhoZ6bmpqZm5ybmpqcm5mYmJeXlpaUk5GQkZKSkpGQkJKTlZeZmpucmpqYl5STkJGRkJGRkZKTlJSTlJaYnJybZp2dnJ2eoaKjpKWnqautrrG3vLy7uLO4vcG/v8PAv8PBure4ubi0sbCqqq2no56al5qal5aYmJidoZ+cnJygqKOfn6GhoaCfnp+hn5+ipKWlpKSorK+zsrCtqaelo6Gio6+tpaOiToZPEU5QUFBRUE9OTk5PTk9RT09QhVEOT09QUFBRU1NTVFNUVVWEVjZXV1haW1tbXFxcXmBhYmNnaWViX11cXl5eX11dX19fXFlYV1dYWFdWV1dYWFdXV1hWWFlYWVmEWGBWVldXWVlZWFdYW1xdXV9fXWBeXVpbYF1cXFpaW1teYGFfXVtatLq0sbzEurvO2s/Ly8C+vbS1tbW/xcC0sLe5uru5vry+v9TNu7q6ta6qqKyurqyvt7anpKOfn56dmZqEnSKfn56blpSTj4+Rj42NjI6OkJCRkpGRkY6OkJCRlZaXlpaXhZmAnJ6gpKmqrK+xsrKxrauoo6Gjpaitrq+ytrm7tri2sa+xsaymoKCgoZ2hpaWlo6SpqJ6enJyXl5mZmpuZl5eXmpyfoqOioJ+foKOorLCxsLGwtFpbWlpbW1pZXmFhX1pWVFBNTU1SVVZYWVpZWFdWVVdcYmp4g3x1b2poZ2ZjZGaAZ2ZkZmxwbWtucnR6hICAenh3dn16enp2dXqAhomDgoOEh4iNj5ecnZ6goaSpqaywr7K6vr+/v76+vsDBxsS/vcTJzdHX3Nzb2tjZ2Nja3uLn5eTm6Onr6erm5uHZ2NPT09LRzMjHxsjKz9DQz8/T09bb4efy+H6BgoSHio6SlpwnpbS1trCtoJ+emZmXl5aVmJiZmJqdo662tLa4qqKfnp+foKersbq5LK+tqqWkop+bnaakoaCgpKmosLy/yczOys/Y2+HW1OV1eoKGgYCNl4x/eHt/hYOAgoWEhpCNhIJ8enyFkbtqaW11f4KFhH141LWurrSphOjo2Levq7K/xcbN2HNvd3tybs+0qKipqLaqqqihnpyeoJ+alpOPhYB/fnl2dnqCgHh0c3Rzd99ydnl3c+DUysjKyM3VbNbLwsDAw8C1sq+xur29vbGoo6aloJ2cmZWNiIZghoaDgH99foOPkpGIgPr2fYCEhYOBfnx7ennyevN6fH19fH59gIOEiYuPkJKRjo6Mh4uNkJaNiYqNh4OBgYCBg4SJhoOAfn1+e3p2d3d15uXf29xwcN7d2dXTzcvKx8PDhMI6wcC+vr/Aw8PCxMPBwsPEw8XKx8bGw8TExcTCw8LCxcnHxcPBwMC+u7u7vLu6uLW2trW1trW0tLWzsoWwM6+trauqra2urq2trKyur7Cxs7W2tbWzsa2tq6urqqqqq6ysrautr7Gzt7a0tba2tre6vIS7Xr7Avr7CyczNy8jAxM3Pzs7S0dDT0srGx8fFw8C9t7i6tbKtqairpqKkpqeorbKvqqqrtL20rq+2tbOzs7GytbW4u7u7urq7u7+/wMHBvLy9vb27vL7GxMHAv19fX2CEYQRjYmNkhmMGZGVmZWRlhGYDZ2ZlhWcsaWlqaWprbGttbWxra2xucG9wcnJydXd4eXl8fnt5d3V2dnd1dHR1dnV1dHKHcQNycnGGcgNzc3KGcwpyc3NzdHRzc3JyhHRqdXV0dXRzcnJ0cnFxb29vbm9vbm1raWfNz8vJzdDMy9Ta09DPysnHwcG/vcHDvrextba2trW3tre4wr2zsrKxrKqoqqurqqyxsauqqqiqq6yqqqytr62ur66rqKWlo6OlpaOjoqKhoqKipISjOqKio6SlpaanqKinqKioqaqsrq6vsbGytLSztLOzsrCvsbS0t7i4ur2/wb/Bwb++v8C9u7e4uLm4uLuEvIC+v7m5ubq3t7e2uLm4tra3ubq8vr+/vbu9vcDDxsnKysnLzWhpaGlpaGhnbG9wb2xpZmNiYWJnaWprbXBwbWtramxyd32Kk4yGgXx6eXd3eXp5eHd6foF/fX+ChYqSj42IiIaGi4qIiIaGi5CUl5OSkpOUlZmcoaWmqKqprbCws3i2tbm+wsPExMPCwcTIysfExcjN0dTZ3N3e3dzc3N3e4uTo5+nr7e7v8PDu7uvk4eDg39/f2tfX2Nnb3t/g397g4uTo7fH5/4GDhIaIi46RlJmdp6mqpqKcnJqXmJaVlpaXl5eVl5icoKWlp6ehnZubnJydoaOprq4ymJeWkpGPj46QkZGPjIqLjpGVmZ2nq6ylp6qtsKurtVteYmNiYWluZl9cXWBhYmFiYWCEYSZeXFtZWFlZXGQ2OT1BQURSVVBMi3VsZGFeW7m1p5uXlpqcm5udoIRQgE5OmpWQjo2KiYWAf3t2dHFtbGtqaWhoZ2dmZWVkYWJkZWNjZWJiyWNgYF9gwsHEvrq0sa1VpqKhoaGjp6ypqaienJuZlpKPjYqHg4KAf3x9fX2Af3x8f3+HmJuYjIP//YGEiIiGg4OCgoGA/4D+gIKGh4WIh4qNjpKUmJqamJOUb5KPk5ibn5eTk5aQi4qKiYmKjZGPjYmJiImFhYGCg4H8/vXy9oCA/vz48/Dn5efp5uXo6ujm5uXk6Orp6+vn6enp7u3t7fDz7uro5+rt7Orp6urq7fPu6+ro6Ojm5OXp7+/u7+3s7e3r6+zr6evo5YTjBeTk5OXkhOeA6OXn5unm5ebo6+vs6+vo5+Xl4uLj4t7e3+Df4N/h5Ojp6+nn6Onn5ujo6Ofl4+Lj4+Lf4ubp6efh3ODl5+bm6ejp6efh3uDg39zZ19HT19LPzMjGycfFyMvNzdTd2tLPz97r39fY4OHd3+Dg4+np8vby9O3r7uvs6efn6uzu8fR89vf3+Pb6/f3/gICAgYSGiYiIh4eIiIiJjIqLjY6Pj46OjYyOj4+Oj5SWlpKPkpKTkZKVl5iYl5WRkJGTl5WXm5uanZ+hoqGio6OkpKSlpKKin6CioqOioqWnp6ampaanpqanp6inp6iop6anqKenqKipqqmpqaiop6inpoSjgKGgoZ6foKKhm5ycmpqYl5aSjYqJh4eC//v8+/bz9fPt5+fm5Ofl4OPg29XSz8vJxsPBwcHAvr68vLu7vLu6u7u8vL29vby9vsDFw8TGzdHS09TW2NvZ2djY1dDMzs7R09XT09HPzczLzM3Jx8jIycfHyMrJycnLy8fJysvKyMjHNMbBwMDDw8PExsjJyczNz9LQ0M/Nzs7R1Nja297f4ODg4uXk5OXr6eXk5ufn5+nt7+7u7u2E7iTv8PHx8PDv8PDx8vT09fT29/n5+vr8/4CBgIGBgoGChIWGhoaFhyKJjY2MjpCSkY+Ojo6PkpeZoKahn5yYlJKSk5OUkpOTlZiYhJcLmZyin52dnJqbnZyEmhyfoaOmo6GhoKCgoqKnqaipqqqtrq6vsbKzt7m5hLpMubq8v769vb/Cw8THycvMy8rLy8rK0NHT1dfY3N3g4eTk5uPh4t/i4uLk4+Lh4eHj5ufo6Ojq6+zs7vH19nt8fX1+gIKChIaFh4qLiYSKCImJiIiJiYmIhIkJiomJioyMjYyMhI4FkJGUl5ifgqCDioSHg4yChoOkggGBhYKIgQGCqYGCgIuBA4CBgLGBhYCCgf+A4ID/gY+B/4C5gP+BpYGxggICBACAubu+uba0s7GytrvBz93XzM7Ry9nd2vGAhoeLkouCgomOk6CinqSmsq2dmJykp6qsqqekoaOmtL+4tq+tvdDvhYj+7JCcmYmKmpyL7Nfj0baei5eClvnd84r55OX0/4qI9/LnwLa2wsfc6+je1dzn7vDp5ujr1KWhmZONk56ViIKAgoOAgISC6fj57uPn7eXe3tfp7/Ty6uTe3tvc3NXFvbbBzsu9tba/z9HIwLu2saeel5KQjYiHh4iJkZSMhoH//X+Cg4SCgH58end2dnd3d3l5fHl2eHx6en6EipadlpCPjIuMjZCRjYuGh4eCf39+fn+DiIaCgH5+fHt5dXVzc3F8cNrS0dRraWnOx8bBu7m3t7W1srGysK+uq6uus7S0tLW2t7e3tre3tre4uba2trOusK+vsLCxsbGtrKqrqqimpaOfnJqXlpeYl5aWlpiYlpSVlJaXl5eVlJKRkJGTlJGQkZCQkpSUlpiYl5aVlJOSk5OSlJWWlJSVlZWXmISZhJxtnqCgoaapqqyvsba5vcK/v7q+vsDAvcLEwMPHwr68uLWtra6pnJqioaGinqCgnpuZmJaWmKChn52Zl5uamZmZnJ6hoZ+enZ6fn6GhoqNRUqalpaioqamlpKCfnp6hnk9PUVFQT05NT1NST09PUIZPBFBQUFGFUoJThVJDU1hfWFdVV11gX1lYWFlZWVtdXl1eX19fYGBhYWRmZGJhYF9fYWFfYGFkZmZjXltaWVlaWFlZWlpaWVlaWlhZWlpbW4RZgFhZWVpbW1paWVlcXl1cW11eXF1fXl9gYF5hXltZWFhaWltcW1xdv8DAw8nEt8POw8TEwLe1tLGzuLy4sLbGydPW1s/W0ry/ydLPzsrFu7CmpaaprK2rq6uqq6urpqGen5+fnJycm5yZlpaWk5OWk5GOjIyNj4+RkpOVlJCOkJCSgJaZmJaWlpeXl5ibnaGjpamtr7Cxr66sqqelpqaorK+0uLu8v8C+vLy5tLCuqqiloaChoaCempeXm6Gho6Olp6Ccm5qam5yamJmdo6Khn5+dnJ6goaSmqquusbGyslhZWlpZWVtdX2FgXVxZWFROTktOUFJVVlpbWVhYW1xibX2DgISDf3h2cXFyamRnb21samtvbWtucXWElZeMgHt3dHp+eHZ4e3+Bg4KDhomKi42Rlp2ho6SjoaKyt62trrO6vr/BwL28vb2/wMDCxMjJzdba3eDg39zY1NPW2t7g4uLi5enu6uzu7Obg3NrZ1tPLxsXFx8nJy83P0NLT1tvg5+z2Mn6ChIaHh4uLjI+VnKWmoJ+jlZeZlZCMjo+OkJSRkZSVmZysp6vIuKifnZ2dmpqboKu0gK2tsKupqamoqayws7zGxb28u7nDxcrfdXl5fYN+dnV6f4GLjoqOkZqWiIWKjpGUlpaUj46PkpuhnpyXlZ2puWVnxsJvd3x2d355bLmrsKeTh3yBcHjPws1vzsTH09lzcc7LwqunpKmrtr27s62tsrS2srCxsaKJiIF9enyDf3l2gHV0cnJ1c9rf39rV1dXPy8vH0NHU0MvHw8LBwsK/tbCssLWxqaanqrGxq6WhnpqUjYiFhIF+f4GChpSRiIJ++vl/gYKCgX9+fHt4eHl5eXp8fH58eXuBfX2BiI6aoZuTk5GOkJKVlpGPjI2Mh4OCgYGDh4yJhoOCg4GAfnp7eXl3gHfm3t3hc3Fx2tPRzcfGxcjIysXDw8C/v8DCw8bIy8nLz87OzszMzczNzc7Ky8nJxsbGxcbGx8fJxsLAwL+9vLy6ubi2tbW0srKxsbCysbCvr66vsLCvr66tra2urq2srausra6vsbSzs7Szsa+urKytrK2vr62srq+ws7W2trS0Y7W2trW2ubq5vb+/wcPExsnQ1c3LxsnL0M/L0NLQ09PQzMnHxLu6u7Wpp66ur7GtsLCsqaiop6Wos7Wvq6enq6qoqKyvsba1srCvrrGzt7m8vmBgwcHAvLm7vry9vbu7vb28X4RghGEZZWVjY2RlZWRiYWJjZWZkZWdnZmZnaGloZ4RoNmtva2tqam5wcGxsbW5vcG9vb3FydHR1d3d4eHl7e3l5d3d4eXl4d3l7fXx6dnRzc3JzcnNzdIdzBHR0dHWIdIR1D3R0c3R2dXR1dXV0c3R0dIRze3Jxb25ubW1tbGppaNLSzs/T0MnP0szOzcvFxMK9vcDAvLe6wb/ExsbAxsK2t7u/vb27uLOsqainqq2ura+vra+vsa+trK2tr62srKyurKinp6WmqKinpKKhoaKhoqOkpqWko6Okpqirqqqqqaioqaqrq62urq6wsbGzsoSxgK+vsbS1trm+wcHDw8TCw8TDwb69vbq3t7m5ubi2tLO3vLu9vsDBvLq5uLm6u7u5uLu/wL+9vby7vb/BwcPHx8nMy83NZ2doaGdnaWprbm9ubmtqZ2JiYGNlZmlrbnBtbG5wcHZ/jJOSkI6LiYGAgHx3e4F/f319gX9/gIGFk6GjgJmNiIeFio2HhoiLjpGUkpOVlpiZmp2iqKutrq2srrq8tbS0uL/DxMbFw8HBw8XExcjLzc/T2Nze4eLi393c293g4uTl5ufp7vTy8PP08Ozo5+bj4NvX1tfY2drc3d7g4ePl6O3z9/6BhIaHiIiKi4yPkpacnJmZm5aXmJSRj5CQGY+Pk5KSk5SWlp+dn66lnZqampmWlpeboquAlpSVlJKSlJSWl5manaCempuZmZ2iqrdeXl1gY2BbXF5hY2ZnZGdnbWtlY2ZpaGlrZ2ZlZWZmbHFta2hjY2ZqODh4fEBBSk1NSUE8bWVoaWFfXVpVUJWUmEuRmZqbm09PlZKPjY6KhoWDgXx6dnN0cnBubmxramtpZWNjYWFjaGtLZ2RkY2NjzcO/w8bAvLm6vL22sq6qp6alpaSnqKuop6adm5iYmJeRjouJh4WDgX9/fn59enh9gYGElpOIgn/8/oSHhoSDgoKDg4KChIGAhYaJhYKEioWFiY+VoKefmZmWlJaanJyYl5SVlI6KiIeHiY2SkI6Mi4uKiIaDhIODgoL/9vX7gYCA9u7t6ODh5unq8uzp6uTh4+bv7e3t7+/1+fj3+PPy8fDx8/Pw7u/v7O/w7u7w8fL08O3r7ezr7Ozt7u7v7u7u7erp6Ofn5uWA4+Ph4uLj5ejo5+fo5uTk4eHj4uTn6Orr7Ors6+nm5ePe4N/g4+Pi4ODi5uvs7u7p5+fm5Obp5+fn5ebj5OXl5ujs7ujm4uHk6OXh5ufj5efk4d/d2dPS1NHDwszMzM7Q0tHNzMzMy8vO3uHc1MrK0s/Mztbc4OPh4d/f3d3i5+5t9fuAgP79+Ovk5+/y9vj5+f77/YGBgICCg4WFhomLiYqKi4yLh4aGio2Ni42Qj46MjY+PkJGTk5COkZCPk5GOjZCQkpSVlZaVlJKSlZeZmp2foaGgoKGjo6Sjo6SkpaSlpaenpaWlpqSlpaWnp4SmgKenpqanp6anpaanqKipqqqpqaenp6anqKajpKWlo6GhoKCfnpuZmZaYmpubmJWRj42JhID9+vXy8vLw7efp6Obl5eXj3tvW1M7My8XCwr++vr29vLu7ubq8urq8vL2/vr7CxMPFxMPFys7Q09TV1tfW1NTT1NXP0NHP0tTX1dLRH8/NzMvLy8rKy83Ozs3Nzc/Q0tPRzs3NzcrHyMbCwb6Ev1PCxMXGx8nLz8/MycvLzdHU19fZ3N7f39/h5efm5uXm6Ozu7uzq6urs7Ozu8e/u8PDv8/Pz8fHx8/X29fX09fb3+Pn4+fr7/v+AgYCBgYKCgoOEh4SIgIeHh4aIioyNjI+SkI6PkZGTlZqdnZ6cnJyXlZWUk5aYl5eXmJqYmZmZmqGpqqOgnZyam5ybm5ydoKGioaGhoqKhoqOmqqytrqysrbW1srKxs7a4uLm5ubi5uLq7vL6/wcLFyMzO0dHS0tHPz9DS1NTV19nb3eHj5OXn5uXl5OPlK+Th4OHh4uPl5ufn6Onq6+zv8fL3fHx9fn9/gIGAgIOCgYOFh4eHiYmIh4aEhwmJiYiJiImJiYiEigqMjY6Ni4qLjpKUl4KogwSEhIODiISKgwSCgoKDhYKCg6aCuIGCgMGBhICDgf+AyoCCgY+A/4GTgf+AuID/gaSBsoICAgQAgLi5ury8vcDNyc7R0dbd4uqA/OHm8+/t9IOLjZGbnZKFgf+Dh4WJjZ6ipqeenamrrKunwbKwrra9wtHa19P8/IiE7+Ti3Onf5enu9PfXwsbMlqG2t+DkzryxoJH14/eA8/Hs7OXF1+rl4+z8+evi8/r04+Dp3rajnJmXkJeYkZCVgJqQg/T08vHx7uro8fLj2tvm6ebd2N7h29bOy8jDvLrL2NfKwcXP2NLNysW/urSrpKGblY+MjYyLi4+SioSFiYuMi4mGgPv5+Hvxd3Z2dXZ4enx1dHh7eXp+g42SlpiUlp6alY+UlpCNiYeGhIWGhoiFhISFhYWEhYOBfnh1c3FxgHJu1NLNzs7PysbGwLy6ubi2tbKysa+urKutsbO0tru/YWG/vru5urq9wL/Avr68tbCvrq2srKytq6mpqqimpKGenZqZmZmYmJeWlZSUlJKSlZaWl5iYl5aTk5KTlJOSkpGQkZKRkZOVlZOTkpOSk5STk5SUk5OVlZaXmJmZmJeYapqbnJ6dnqCjp6yvsbO1u76/wcG9tba1ubq7vL6/xcfAvru2q6Khpqeem52eoaWem5yampubmJSUlJaVkpKTkpKVlZaWl5aYmZuamJeanaKnqqmopaKjoqGgoqGin5yboZybnZ6golBQTk+EUAZRUFFRUFCGUQlSUlRUVFVXWFWEVIBWWVxaWVhbW11eXVtbXF5fXV1hYWBkZGNkZGJjZ2dlY2JjYmFiZGZnam5sZGJeXF5fX11ZWVpZWltbWlpaWVpdXV9eXVxbWlpcXWBgXVxdW1tcXmFjZWVoZGNmYl9dXV9eW1pZWVhYWFlYWFldXlxeysXBuri6v8zZxrWtrLfEv4CyraWmqa22wcrM1NbT3ePh5dLJx8S/s6+sqKqsq6qzs6+rqaamp6ainJ2dnZybl5aWlZOVmJiUk5GRkZSTk5GTlZWTkpKTk5aXl5aVlZWXmJueoKSmqKywsbCtq6empaSmqK2xtLi8v8HCxMPCwsPBvbq0q6eoo6Ogn5yZlpWVlw2cnaSnn56dl5WWl5qdhJ+AoJ2dnJmYm5ydoqaorK+ztre1s65ZWlpcXF1fXVlYWVpZVlRSUlBQUlRXWFhZW1xdXVtfanR5hpCNgnpycXNwbWxvdHNub29tc3NzeoWJiId/fHp5d3RzcnJ0dXqAg4iNkZKVmJmepKSkpaSqssHAta6xtru8vb27ury+wMHDycxozc7S2Nra3N7d2tPLycvU2Nre4uPk6eju8vX3+PPw6uTc2NTNxcDCwsPExsnN0NPX2t7l6/H3foGCg4KCgoaIiYuNko6NjIyKiYmHh4eKjIyOjpGQk5SXnKWptcrOqpqaoKKprKepqrKAq6qtr66wtL27wcPAxsjKzW7bzdDZ2NXfd3x+gomKgXhz33N3dXl9iYuNjYiIkpaWk5GfmJeXm6Gmsrq1rsPGbGnHwLu1v7/Ex8TEwK+ioKKEh4+NoaKUiIB4cczH0mvMyMPDvKmwubi5u8PAubK8v7uwsLSrlYmEgYB9gIB/foCAg3t04N/d3t7Z1dTX1s/Jys7Oz8jExMXCvry6t7WxrrS7t66rrLK1s66tqaWhnJWRj4iEgYCEhYaHjY2GgIOGiIiHhYF+9fX2evB3d3h4eHx9fnZ3e398fYGHkZaam5iaoZ6ZlZmZk5GNi4qJiYmKioiHiImKiYiIiIeDfnt5dnZaenXe29ra29vX1djOycXHyMfHw8HBv769vsPHycnM09dtbtXU09DT09TX1tfW1dPMyMfGxMPExMXDwMDBwL27u7q5t7W1tLKxsbGysLCvra+xsLCwsbKysbCvhK6Arayrra6vrq6xsbOxr6+vrausrq+wsK2sra6ztbW2tbWzsrKys7Oztbi6vb/DxcfIys7Q0s/JwsXCxsbIy87N0tLLysnEuK6tsrKrpqeqsbKura6qqaysp6OkpqekoaChoJ+eoKKkpqaoqamopqaprrO6vr+8ubi3tbKxs7a6u7pIur+9vL2+vsBgYGBhYmNkZGVlZ2dmZWVlZmdnZmZnaGZmaWtraGZnaWdmaWtpa2tsbG9vcHFwcXJzcG5ydHV3eHd4eHh3eHl6hHkXeHp8fn6Ag4J9e3h2d3Z1dHR0dXR0dXWEdA51dXZ1dnZ2dXV2dXZ2d4V2anV2eHh5eXl4d3d1dHNyc3JxcXBvbm9ubm1ra2tpaGnW0tDLysrM0dbMxsG/w8jDu7mzsrGztbq+v8TDwsfJx8u/urq4tLCurayvsbGxtLOysbCvsLKxsK6traysq6elpqemp6qqp6WlpKSEpYCmpqemqKipp6ipqqqpqamqq6yrrK6ur7Gzs7Kysa+vrq+wsbO1uLm+wsTFxsXFxsfIx8XCvry9urq6ubi2tba3uLm6v8C8vLq2t7e4u72+v72+wLy9vbu7vL2+wcPGyMrMz9DQz81naGhpaWpsa2pqa2xraWhnZmVmZ2lrbGxubjhvcXFwc32EiZScmY6IgoKCgX9+gYSCf4KCgIWFhIiTl5WUjYqJiIaEg4SFhomLkJOWmp6fo6SlqYSvfq6zusXFvbe5vcDCwsLBwMHExsfJzNHT0tfb3d7f4eDf29XT1Nvd4OTn6uvu7vP2+vz++/j07+nl4NrX09PU1dbX2tzh4+fp7PD1+/6Ag4WFhISFh4qKi42Rjo2MjYyLi4qKjI2NjY6PkpKSlJWXnJ2ksbKclZabm6GjoaOiqICUkpCRk5WZm5yhoJ+ioaGkUqakpquusLZeYGJkaGljXlyzWFpZW1tkY2VkYmFmaWloZmhnaGlucXN3enhucXU+P39+eXV3foaFfHVvbG1mZGFfW1ZSUE1JRkVHlpiYS5SOi4iFg4B9fX58end1d3d1cnBwcG5raWZlZWRkY2hqaXVmZWbOzMrLyMPBwbu4tbi7t7O2tKunqKiprqupqqimoJmUkZSTkpGNiYiIhoSCf4F/eXd2d36EhYaPi4R/hIaKiIWDgX/4+/6A/oCAgoKCiIqLgYCEiIOEiI6Wmp6em56lo5+coKCamJSRkY+Oj5CQjYyNj46Ej4CNioaDgoGChoD08vLy9PTz9frt6OLo6uzu5eLk4eHi5e7x8vHz+/+Bgfv6+Pj7/Pz9+/v6/Pz39PHw7ezt7vDt6+zw8e3r6+zu7evq6efm5+bn5+bk4+Xk5eXm5ufp6OXl5OTk4+Hi5eXm6Onp6+7t7evp6Obh4ePj5ujl4eLl6IDq6uvs6+jn4uDf4eHg5Ofm5ufo5ePn6uzr5eHb3Nvd3d7c3+Dj5eDf3djNxsbLzMHAxMbLzsvMzMrLz9HMyMnKzcjDwsLAv8DCxMbJyszO0NDNys3W4Ojt8O7r6Obg29vg5vD19vn5/P/+/fz/gYKDhIaJi4yMjI6OjoyJiYyPjSSLjI2LiYmOj4+LiYqOh4KEhYaLj46Mjo+UlpeWlpOQi5GUlpmFnYCbmJifoaGjo6Kkpaanqqysqaiop6ShoaSoqaipqKinqKipqailpaKjo6Wlpaelo6KjpKamqKqppaOjoJ+bnZ+bnJ6enZuampqbnJqYlpOTjYmEgoKA9vTy8O3r6+Pe5+rp5eDe2NfV0s/IyMK+vL6+vLu6ubi4u7q5u7y/wMHGykXNycrIx8jNzM/R0tPU1dPR09LRzsrMzs3R1NDPz9DPzMzMzczOz9HQ0tTU0dDQ0NHT1NLQz83Jx8jFwr69v8DAwsLDw8WGxzHKz9DN0NTV1tXY293e4OPk5ujp6erw9fLu7+7u7/Dx8fLy8vPz8/Lx8vPz9ff29vj4hPcK+Pb3+Pn8/f3+/4SAgIGAgYOEhYeIiImIh4iIiYqLjIyNjo6OkJGQkZWYmpygnpaWlpeYl5aWlJSWlpaXl5qbmpyhoaCfnpycm5qamZmbnJudn6Cho6Smp6iqrK6ura+tsLW7urays7S2tre3t7i4uru9v8HCw8XHzM7Ozs/Q0NDPz9LW1dXY2t3c3t/hDeTn6urs7Ovq5uXk4+KE4T/i5Ofo6evs7e7x8fP2e3x8fX5+fn9/f4GBgYKDhIaFhYaFhYaHiIeHiImKiomJiImIiYmKiIqLjIuPkZCSkpKQggGDh4KJgwGCnYOChJqDBIKCgoOkgsKBBYCAgIGAuIGegIKB/4DFgP+BkoH/gLaA/4GjgbKCAgIEAIDIvbm4s7O1u7m7ydjj4tzi797X2OX3/4KEh46QjJCUg/j09fDs5en0gIuRnKOnpaKgp62rvMe8xsbK0t7h3ubs+ob669PR2N7n4dPGvc62opuhmp2ZqcfGv8zDwqzvhYaD9Ovq5ebQ1uXs5+XRwMPJ09W6ubuxsLK0pKCtnpianm6blIX8gIaJhoWOifn25uLx7+bh2Njb1dDV2szJ1dTZ2tbQxcDFzdPHxMC9t66qq6ijm5eQjI+VjouHh4aHiIuMjYyJgvrz8fHy8/Hu7evpdHR0dXR0eHd5gIyan56ln5ukoJmXmZiSjImJiIaFhYaHeIaHiIeJhX53dXZzb3DZ09HNyMvIycbEwr6/vby8ube1srKvra6wsrS2wGJjxcO+uLWztLW1uLq7YWC4sbCvsK+vrayrq6qoqKakoqCenpuZmJmZmJeXlpWVlJOTlpeUmJiZlpOUl5mamZiYl5WSlZSUkpGRkpKUlIWTg5KFk3+Vl5iXmJeYmpqcnp6fnqGlqK2ztrSytbS2u7m3srG5s7GwtrS/urivqK2roZybo5iYl5SWmpqZmJeWl5eUk5CPj46Nj5GQkZCQkI6Oj4+QlJOTlpiZnJ6fo6OfnJydoKKjoqarsKyhnZubm5yeTk5PT1FUVFNSU1JSU1NSUlNShFMPqKqsWFpcWVepplRVVVRVhFdFWl1bWllbXl9dXWBgX2JkY2NkZGZpbG1saGhnZ2lraWhnaWpnY2JhYWBfXlxbW1xcXV5eXFpaW11dXVxbW1xcXF5hYWFihF9pYWJjZGRhZmNjY2VnaGhta2lnYF5bWlpaWVpaWlxdX8XFyLi3tbjJ0s3NzcjAw+PKrqmvr7O1uLvDx8S/y9jf5+rb3NLKwry6ta+ts7e7v7q5tK2rqailn5+ioqGcmJmXl5qcnZqZmJaVhJaAmJqbnJqdnZqZmpiWlZSTkpWZnZ+ipKWqrK2trKimpKOkpqirsra5vL+/wMLCwcHCxMXCv762sKyknpqWlpqen6GgnZudn5uZl5WTkZCUmqOkpaOenKCdm5ucnZygpKqwrq2usLG0tFlZWl5fXl1aWFdYWFlXUlBQUlJSVFZWV1aAV1pbXV1ocnZ3iZyQg3hxcXJwdHh1d3d2dnVydXd0eHyChod+e3d3dnNzcnN1eHyBhIiLj5SWmJqepqinqK23t7i6tbK2u77Bw8G+vb/AxcrN09bU1dnf3Nvf3dbTz8zP0dTZ3OHk6O/0+Pr69/T09PLv5tzW0dDLy8rIycnMzs8Z1dja5+rl5ery9X19fX58e3x9gIOChISDgYaAHIKGi5CTlpSVlJegpKatu7Oupp2ho6Smq7CwtcSAtKqnp6anqayrrrrDzc3GyNHFv8HN3+V0d3p9f319gHXf29nU0c3T23J5fISJioyMi5GVk5+moqmsrbK7vrnCx89szMOxsrq+xb+zqqSwnoqGhn18eYKRj4uTjYyByW1tasjBvrm4qq22ura0p56hpaenl5eYko+QkIiGjoeDhISAg3525XJ0d3VzeXXa1s3L0s/JysXCwsLBwcO9u8C9vru4sammrLG0q6impqKbl5WSjoiEgYGFjoaDgoODhIaKiYiHhH7w6ufn6vLz8e7t7XZ3dnd3dnl6e4SRn6SjqqOgpqSenZ2clpCOjYyKiIiKiouKioyLioyLjImDfHt8eXWAduTe2tjS09PT0dLPzNDNy8vKxsTCw8DAwsXHycrUbm7Z19LNysjMzs/Q09RubM/Jx8fGx8bFxMTCwL6+vb28vLq6uLe1tbSzs7OysbGxsK+xsrCysbOxr7CytLW1s7OxsK+wr6+vrq2ur6+vrq2tra+wsK+ura6tr7KysrO0srM9srGysrOztbe6vL/DxsXDw8LFycfGwb/Gv768wr7IxcS6tbq4raior6Wlo6Cjp6urqKimp6ahn52cnZyam4WcH52bmpubm56cnJ+ho6WprLGwq6eoq6+ztbO2vMDAvb2EvA29X2BhYmNkZWVmZWVmh2gNZmVkw8LFZ2lraWXHx4VlNmhpaWtrbm1ra29wcG1tcHNydXh4d3d4eHp7e3p6e3t6e3x9e3t9gH57enp6eXh3dnZ3dXV1d4R2e3d3dnZ3dnZ3dnZ3eHh3eHd4d3d4d3l5eXh5eHh4d3h4dnh3d3RxcG9wcG9ubWtramtr1dTTycfFxs7R0NLRzsrJ18m5tbi2trW2uLy+u7e9w8XLzMTEvru4trW0srKztbe7uLm2tLOysbGurrCvraupqaiqq6yrqaqqqYSnDqiqq6ysq66vra2tq6mphKgxqqytra+wsLKzsrKxr66sq6yusrO4u72+wcPFxcbGxsnLysjHxcHAvrm4uLm6ury+voS8Kbq5uLa0trS4vMDBwcG/vsC/vb2+v77AwsbKy8rLzc/R0WhnaGtsa2tphGiAa2pmZWRmZ2doaWpsamttcHJzfYWJi5qpn5OHgoKCgYSGhYaFhISDg4aIhYeLkpSWjouHh4aEg4SGh4mMkJKWmp6io6OlqrGzsrK4v72/wLy6vMHDx8jGxcTDxcnP09jZ19nc4eDe4OHf29fW19nb3uHl6Ozw9vv9/Pv7+vn49Owo5uTg393c29nZ2tze3+Tn6vP38/H2+/2AgIGBgICAgYSGhYeHhoWEhISFHIeKjpGTlpSUk5abnp6iqqOinZicnZ2do6eorLSAkYuKiouOkJKTlZyjpqShnqCbmJyirrRcX2BiYl1bXlqvqaempKGjqVZaWlxdXGNjYmNmZmpub3R2eHt+fXZ8f39Af3x2eX2BhH93cW96cGVgXVhSTk9OS0hISUtKlEpIRoiFgX9+e3t7eXd2c3N0c3Nwb25ramdkZGRlaGhqZ2QGY2NjyWNhhGCAXre0trWyrKuxs6uqsLGtq62tqKehmZOPjIyQkY+LioqMi4eDf3x5dHNzd3+Ngn5/goGEh4yIhoSCe+/n6ezx+v/+/P3/gIGBgYCAgoGCi5mjpqWqpaGoqKKhpKGblZGSkY+Njo+Pjo+PkI+PkZGRkIuEgoSDgIH68/Dv5ujq7etA7+/s8O7u7+zp6OTn5ufp7fDw8fyDgfv39PHu7/j8+/z8/YGA+/f08/Lx8fHv7u3r6ezu7O3v8fLv7u3s7Oro6IbpgOvr6Ojm5uXo5+jp6unn5ufn5ujn6evr6uno5ubl4+Tm5+rr6efl5ubl5ufn6Onp5uHf3d3d29ve4ePi5OPg3d7d3d7e3NfY3NbT0tbW3dna0svQ0MbBwMm9u7u6v8PHyMjHyMvJwsK+vb68urm6u7u6uru6ubi4t7m4usDExcnNgM/W1c3Hys7W2t/c3+To7vT5/fv8/PyAg4SGh4KGioyJiYuPkJGQj5COi4mF+/P6hYaFhIH+/YKCgIGCh4qIiImLi4iKkI2LiIeNkI+UmJmZl5eXlpWVl5udnp2bnKCenaKnpqakp6empqmrq6mkpqSmp6isrKqop6enqKinpqWjCaGhpKKjpaenpIWhgJ2fn52amJiXkZKTk5iYmJmYlpSPiYiGh4T79e7x8O3o397g3t/f3t3Z1dXU0c3FwsG/wsG+u7m3t7i7urq6vL/CxcfIycfIx8XFyMrQ0dDR0tPQ0tHOzs/Nzs3Qz87Kzc/Oz87Ozs3P0tHS1NbT0tTX1NHT0tLQ0c/Ix8fJxsPBPcHDxMTGxcHAwcLDwsLDxsjLy8vP0tbX2dra3d3f4uPl6/Hx8fX18vLz8vPy8vX19PX19vf39fX29fb4+fyE+4D5+Pf4+fj6+vz+/v//gICBgIGBgoSEhYaHiIeHiIiKiouMjY2OjY2PkZKTmJufn6awpp+alZSVlZeYlZWVlJSVl5mamZucnqCjn52amZiXmJiZmpqbnZ+hpKapqqqsrbGysbKztra3t7Sztbe4ury6uru7vL7Aw8fIxsjL0NHQ0TzR0NHP0M/S1dbY3N3e4Obo6err6+3t7Ozp5+Xk5uTl5OTk5ujp6uzu7fP08PDy9PV6e3x9fH5+fn+AgYKGgx+EhYWFh4iKiouMioqKi4yMjIqKiouMi4yJh4yQkJKRl4KJg4iCmYMBhJuDBIKDg4OiggGBh4K5gYuAsoGdgIKBjICCgf+AuYCWgYOAhYGCgPGB/4C2gP+BpIGxggICBACAuLy6tbW4uLm3uMve6eLl3eHa0tLW3uH0/4OHj5KMg4CLhe7r7fL5/PT8g4CBjZqmqKakoqavtru9vsLN09LW4eXe2tC9v8jO1drV0c7LxKSPiIqTmomMhYivw62IiYePjYT9gYDrzr+4yNTYzsrY1tna29Po69nItp2VpqeakpOAkYyDhpSfn5ONh4H7+/SEhYH99vLp5Nvf4dnO0tTZ2tbQzMfIyMXGwr+7tbCsqqqmnpmWkYyLlpCIh4eCg4CCgYGA/4D++/v49/b59+zp5+bg4OTi5OXq7nyFi5GbtLStpKKgmZeQkY2NjoyFg4SJioiIiIaEhIWKhYB7d3Ny4d5q39zW09HQz83Ny8fGxcPAwL67urq0tLKwsrKytLe5vb++urm2sbGxsrCwr7K3t7O0tbW1srCurq6sq6qnpaOioJ6goJ+fnp+dnJqZmpqZlpaUk5SVlpaUlJacnp2ZmJiYlpSUlZWVk5KSkYSQBI+RkZGEkICSkpOTlZeXmZmZmpucnZ2foKKlpaipqqusraumpqinp6SlpKixtbO3tK6im5qhlpKRmp6anJ6dm5KPjpGQkpOUkpCOjY2JiYmIioyNi4qJiYmKioqLj5SVlJOUl5aWmJqbnJ+jpKKiq8S2pZydm5ianE9PTk+hpayura1VVVZXVoBVVFZYVaWpV1dZWVhXVlVVVVRVVlZXVldYWVtcW1pbW1tcXV1fX2BjY2RmZmdoaWlpZ2dpaWptbmppaWpqaGVjZGRjYWBhYWFgX2BeXV1eX2FgX11bW1xdXV5gX19eXl9gYWFiYWFiY2ZrZ2NmaGhla2lpaWZpamtoamdmZWJgX4BhYL/Lxry4urizvLnCvbi2t7q5t7a2vsbKy9XXzMfE0eHe4erh4Nvg3tfRysHBw8PFy8rBtK+qo56dnqKoqaainpyeoKKjoJ6bmpiZmpubm5+gn5ybm5uampmYmZiWl5mbn6SlpqioqKalo6KjoqOlqayvtLe7vsC/wMHCwb/AwoDCwcHBvbarop2cn5+kpaWqrqqhn5yXk5SXlZSSlJienaCeoKCfnZycnZ+hpqemqqyqqVRSU1ZYVlVVVFRWVFNTVFRSUlFQUFJSUVJUV1pYWVpdYWhve42VjIuKgHVucHJxdnl4enx5c3FxcnJzdnh6gIGCf3p1c3N1dHV2eHt+fw6GjZCUmJqdnqOsrq+zt4S9eL/Fx8rLysTBvr7CyNDX2trZ293d2trc3NnW1dPX2+Hl5+nt9vv58+nl493UzcrHxMTHy8zKyMnLyszO0NPV19jj5eLX3Ofr7+/x8vF5e32AgoGBfn18enp7fX1/g4WIjpOYnJ6goJ+lqK64qaCipqioqqmpra2tsICoqqmlpampqqqrucXLx8nCwb25vL3AxNDZb3J5fXhybnRwzs3T1t7d2dpycXJ6g5CSkY6LjJGfoqKhpa6ysbW9wry5sqKjq7C1t7OzsrKpj394eHx8cHBsbIKNgWxubXBvactmZb6sopykq6yloqqoqqqqprGwpp2ShYCKioJ+fYB8enN1fICAendzbtnZ1G9ua9TPzsrJw8XGw76+vb+9ubSxra2sqqqnpaSjoZ2ZlY+JhYOAf4CPh31+f31+fH17e3rxee3q7Ors7vT26ubl5d7e4uPn6e7ygIqRlZ21ta+npKSdm5OUkpGTj4eHiYyOjY2MiomIiY+KhYB8eHjt7Ens5uHf3drb29vY1dTT1dHMy8rKzcXGyMTHx8fJzdHU1dPOzczLysvMy8vLzdHPzM7Ozc3LycjHx8XDwb+/vru7u7y9vru5urm3hLVFtrSxr6+xsrKxsLO0uLq5trSzsrGvsLCwsbGwsK+trausrK2urq6traysq62usLKztba1srGxsrGxs7S0tbe5u7u6u7i1hLResbGytr7CwcLBua2npq2inZyoq6SjqKqopJ2anZydn6Cem5mVlJKRkZGUlZaTkZKQkJCSlJSYnJ6dnZ2hoqCipqiorLCysLK6y8a8sbi3tLC4YGFhYby+xcjDwmRoaoVrJ2pmwcdnaGlqamloZmZmZGVnZmdoamprbGxra2xsbm9ucHJzdHd4eIR5HXt6enl6fH19fX59e3t9fnx7enp5eXp5eHh2dXR2hHcFeHh3d3iHd4R4AXmFeGx3eHh5enl4d3d5eHh4d3d1dnZ2c3Rxb21tbWxradHUz8rIyMbEycbIxMPAw8K/vby7vcHBwMXHvru5wczGydDKycbIx8PCvru6u7u9wMC9uLazsK6urrCzsbCvrayur66tr6+urKurq6qrr7GEr2Kwrq2urKurqqioqaqsr6+vsLCxsa+wsa6qq6yvsLO1uLu9wMLDw8TExsfJycnLy8rHwb68u76/wsLCxcfFwL28ubi4ube3tri8wL/Avr/AwL69vr6/wcTFxcjJycllY2RmZ4VlgGZmZWVmaGhoZmZmZ2doaGptb25tbnJ2fIKMnqeenJuRiIGCgoKGiIiJi4iDg4OFhYaGiIuOkZGPioaFhYWGh4eKjI+PlZyfo6Wmqauvtre3u77Cw8PDxMvMzdDOycfFxsnN09zd29rd3+Df3t/f39zc293h5Onr7PD6//z48evpTubh3NnW1dbZ3NvY2Nna2tze4OPk5Obw8vDn6vL2+vr6+/6AgYGDhIWGhIKBgICBg4SFh4mMkJGVmJmcnZ2foKOnn5ucn5+foqCeoKKjpYCIiYiFh4yNj5CSlpqem5WVkI+OkJKUlJidUFFVWlhXVFFOmZ+jpqmppqlWVVZaXmdoZmVjZGVucXN0dnl7eXt+fn18eXBxdXl7enl5enh0aF9ZVlNQS0hGRUlLS0lKSUpGRIVCQ4N+fHp4eXh1dHJwcHFwbm1rZ2dmZmlpZ2loZYBjYWFiYFxbW1xbWrKysVZUU6Wmp6erramnrrCppaKdmpaUkY+PjYmIiIuSlJGHfXd0cXBwcnmKgHV3eXl8en16d3jqdeTi5+bn6vT68fDw8ujp8PD09vz/h5GWmqG1tK6nqKeioJmalpWXlI2LjZGRkJGQjYyNjZGOiYWCgID//jv/+/X08/Dz9fj28fLx9e3p6urs9ejp7e3w8O/v9Pr9/fr19PXz9Pr9+vj6+/7++/n49/f08vLy8/Lx8ITuU+/x9PX08vHw7uzr6uvs7e3t6+vq6uzq6u3s7/Dw7uro5+Tk5ufq7Ozq6uno6OTm5+fo6unp5eTj4uLi4+fq7Ozm3t3a2djW1tbX2NfW1dTU1dPMhMqAyMnIzNXY1tbTzsK+vcW6tbXAwr2/w8TDv7i1urq8vsDAu7W0sauoqamusbKuqaelpqanqauzu726uLq/vry/xMjJz9XY1tni5/Dv6PLy8ur1goWGhPb1+//x8ISOkZGUk5OQjYb3/YeHiImKiYaCgoKAgoSCg4WJiYmIiYuJi4oLjI6Lj5KTlZaYl5WEmICWlpeam52cm5yenZyen5+goKOio6mopKGen6Cio6eop6ajpKSnrKuqqKimo6Smp6WmpaSjoaGgn56cmZyemJWYnJSWkZOUj4yNjYmIhYGAg4WCgPrw6+zp5eXo4+Df3t7c3djV09DNxMTCvr/Bvrq5ubi5u7u7vL69wMXCwcTGxIDGxMXHy8/OzdDT1dXV0czMzM7Q0M/NzM3Q0tLS0c/P0dbX09PV2dbV1dfS0M/Qz83HxcTFxMTEw8PGysrKx8G/vr2+v8HCwcTIycnM0NLV19na3N3c3uPp8PP09vb3+PTy8vL19vf49/X19ff3+Pj7+vr7+vn7+/n4+fj6+vr7+QT7//6AhYGAgoODhIWFhoeGhoiJi4qJi4yMjI2Oj46PkJKUl5qfqK+pp6Wgm5WUlZSUlpaWmJiWlpeZmZiYmpuen56dmpiYl5mYmJqcnZ6goqapqqyrrK2us7OztLa5uLi6u76+wMHBvry8vb69wMXIycnLzc/Qzs/S0dHS0tTV19nb3uHn6+tS6ufm5OPj4eDg3t/h4+Lh4+Tl5ufp6uvs7u/z9PPu8fPz9PP09/d9fn6AgIKCg4ODgoKChIWEhoaGh4mKi4uLjIuLi4qJiImJiYuLi4iFh4mIh5mCiYOIgriDA4KDg6aCBoGBgYKCgrGBAoCBlICmgf+A6oCEgYaAioGCgPuB/4CxgP+BrYGsggICBACAv8TDwLq/vcHFzdfg7P2C7+/d2d7W3+vt94GKjYuMk4aE/fLu+P2AhICBiYiC/IOOm6OlrbS1uLy8tKipq6GmsrWxqqSdnKClprOjobHDxLqmk4aWi4+Xhdfm38v4jIiZn5yYh4yJ+N7X0treyuP59Pz16cjEy87KwKqmubWgmZaAlZmfoqOimI6IgoD9/vyA9u/u8vbv4d7d29HO1trg4NLGxsO8uLvBxL+/tK+sraumoaCdmZSRkouHh4mE//z5+ff1/oOEgf77+vj48uvp5+Th4ODg5eLn5+98gISJlaKkmpaVlZaUkpGQioeFhISGiImIg4OCgIKGfXl37Ojl4N4n397Z19bQzM3PzcvKyMXCvb26uLm2tbSxsra3t7m9vLm2tre2tbOwhbIDtLW3hLgOt7SysK6rqqmno6Ghn6CEogShoZ+ghJ6AmpiWk5OUk5OUlZSVmJudnZyamJeUkpSWk5GRlJSSkJGQkZGSkJGRkZKTlJOUlpiZmpmbnJ2doKGgnp6gpKeopaWiop+cnp+dnp+kpaqurrCuqpmOjY6YmJWQlJuYlJSWlJCNjY6Pj4+QjYmGhIKDg4OEhIWGh4aGh4iJi4qKiowujpCSkZOWl5iXmp6go6SkpamtraaruMa4rqqtVlFRVrW5u7q3uVusU1RYWl5droRXElhYVlZXV1dWVKhUVldWV1hYWIRagFtcXV5eX2BgYWRkZWZnaWhpaGZnbG5sbm9sampraWVmZWRkZGNiYWJiYWBhYV9gYWFiYWBfX11eXV1cXFxdXV5fXl5fYGFhYmBgY2NiY2JjZWttbGxqa2lmY2JgYWRhY83Kys3e18zEw8G4yMnHxMfFyL/Gvr29w83JyMnP3OXrgPD//u3e4Onm5ezr5dbTzs3KztDNzcS9tq+mqqOdnZycnqCho6OhoqGgnp2cmZiYmZufpKOfnZ2cmZiZmZiWlpieo6ampqmrqqmmpaKfo6KkpqywtLm9wcPFx8fGxcTCvr28vr28ubKrqKinrK2pp6GnrrOrpaGgn6Gfm5qjop6egJyTkpiampyfoKChoqappqaoUVFSUlFQUlNWV1VUU1FNTU5OT1NTUlJUVldYW15kZGRjaGxyeHl+hYSEg3xxbnF3dXN2dHJ3eHJubW9yc3Rzd3t/gX54cXNzc3JydHZ8g4WHiY6Wnp2doamtsrW4ur/Dw8fNycnKyMbEwb+/w87WL9ra2djY2NfV1djY19rc4OXn6uvr6uvu597TysbDv8DDxMfIy83N0NDQ09TNzM3NhNAj0tri5+zz9PT1e3x+gH9+fn58end2dnh5en2BhIiLj5KXmJmEmhGco6ampKuxt7q+xcO+uru7voCusa+rp6ysra+3vcDJ0mrIx7y6wLy/x8nTbHFycnZ6cW/Y1dLX2m9xbW5zdXLfcnyHjpCUmZqeo6KbkpKUjI+XmZaSjYaDhYmLkYiFlaSnoJB+dHtydHdsucC6sMxvbHZ5eHJpbWrCs6ypra+ksru5vrqzoZ2gop+ZjIqUkoWCgYB+f4GEhIN+eHRwbtvb2m3TzszN0M3FwsLGwsDAwMLAt7KyrailpqanpKWkoJ2Xko2KiYeFhIOEfXl6fHjo5efm5OPpeHp47+zr7Ozl3t/f3trY293k4efp836Dh4yapaadmZiYmZiWlZONiYiHiImMjIuIh4aEhYmAfXvz8O7o52Tq6eXk49rU2Nzb2NvW083JysfGycXFxcLEysrLzdTT0s7Nz8/OzMvOzs7Nzs/OztHR0dLPzczKyMbEwsC+vLy8u7y+vr68vbu7ubi4t7a3tLKxsrGxsbKzs7a5u7q4tbOysbCwhLFqsrGura6sra6urq2sra6ur6+vsLK0tbW0srGys7KxsLGxtLa1tLSwr6ypqqmqqqqvtLe8vLy6taWWlpiio6CdoqainZ+ioJ2al5ianJ6fmZOOiYmKiYmKioqMjI2Oj4+PkZGTlJWXmJqcn4SjZKeqra6usLO4uLixtcHLxsHEx2RgYWTLzMrJx8xox2NnbWxtbMdmZ2tsbGtnZ2dpa2hlx2VnamprbG1rbG1ubm9wcHJzdHR0dXZ3dnZ3eHl7eXl7f39+fXt7fn59fHp6enl5eXuFegl3d3h5eXl4eHmGeAJ5d4R5hXiEeQl4eHd3d3Z3dneEeF12dXNycHBwbm5tbdrX1tbc19HLzM3Hz8/Myc3KysXGv729wMbAv73Ax8vP0trZz8fJzczLz87Kw8K/v77AwMHCv7y4trG1sq+wr66ura6xsbCxsrKwr66sq6usr7KFs4Cvrqyqqqmop6eprK+wsLCxsrKxsK2trKusrbCztre7vcDCxMXExcbGxcXGx8jJx8XCwcPDxMXFw8HDxsnEwL++v7+9vLvAwL/Bv7q6u729vb/BwcLDxsfHxsdiYmNkY2NkZGdoZmZmZWNjY2RmaWloaGlqbGxvcnd4d3Z5fIKIioCPmJeWk46FgYSJh4SHhYWHiISCgYGEhYWFiIyPkY6Jg4aGhoWFh4qNk5SWmJukqqmprLK2uby+v8THyczS0M7Pzs3LyMbGytPa3Nzb2tzc29nX2t3d3+Hl6ert7u/v8PPv5t3W09LQ0NLU2drc3t7f39/h49/e39/i4uDg4+rw9jT4/P///4CBg4SEg4ODgoGAfn5/gICChomLjZCSlJaXmJiYl5icnp6do6eqrK6ysq+qq6uugIuMi4mIi4yLjJORkJKPR4uLiYmKjZCTlJVJS0pOVFdUUKCioaGkUFBTVFRYWKtZXmRlZmlubnBzc3BubmxmZ2traWdmYF9dXl9gXV1lcHJuZlxVUU9NS0aLjYuMkklISElIRkRFQ4F+e3p4d3h2dHJxcXFwbWppZ2dnaGZlZWdngGRhX19fXl5dXFpZsrOyVqmopqSjpKSkpq+5tqqloJydoJ6XkZCPiIeHiZSRj4F2cnFwcHJ0dnl0bm1ycd3b2tnX1tpwc3Po5eTj5eDd3+Hg29vf5e7r8PH6g4eLkJymqaCcm52enJubmZGOjYqLjJCRkIuKioiIjYWCgP7+/fr8gP389/n47ebu+Pjy9vLs5eLn5OTp5OPh4OTs7u7x+/389vb5+/z48/r7/P79/P3++/z7+/j4+Pf18/Ly8vHv7O7x9PTx8fDw7ezq6+vr7/Hv7u3v7e/w7+7u8PLz8e/s6OTl5ejr6uzt7Ozp5uXk5OXo6ejo5+Xk5ePj4+bo6ujhgNvZ1tjZ1NHOztHS0c3MyMfGwcHAwL/CyczN0NHSz8y+rK2wurq3tbvAvLi5vLu4s7Gztbi5vLSspJ+enZ2en5+foaKjpKSmqKmqrK6vsbK2uLvDxMK/xczQ1dLU2d3c3tPU4OTs7Pf/goGBgv389fLu/YP+g4qWk4yK+oKGkZOUFI2FgYWKjIiD/4KFi42MjI2IjpGQhJGAk5WXlpWUlJOSk5CRlZmZmZqcnJuWlJifn56enJyen5+gpKalp6ako6Ghp6WjoqKkpaWnqqupqaqqqquopaWoqKanpKOhoqGenJydnJyakI2PkI+Li4qKiouHg4KA/v799unh5OXn5+ji3+Hg29nd2dXPzMrHxcPAvbu6ubm4u7sfvL6/vr6/wsLAwMLDw8HDxcfIycvLz9LT1NfY1dDP0ITPgNDV1tXV1tPU1dbZ2dbX2dva2NbS0M3My8nFwcHDxMPDxMPEx8jHxcO/v728vb6+v8LExsfJztDQ0tbZ293e3+Hn7O/v8fHy9Pr38u/u8vP09vbz9Pb39Pj6/P///fv7/fz7+fv8+/v6/P3/gIGAgYKDgoOEhYSFiImKiomIiYuLTouMjIyNjo6Oj5GRkJKTlpudoKWkpKSfmZaXm5eUlpaXmpqXlpeYmZmZmJmam52bmZiZmJmZm5yenqGio6Snq62qqqytr7G0tra5uru+w4XBTb++vr2/wMTHyMnKysnIysnLzc7Q1Nfb3Nva293f4+Lh3dzc29rb3d7h4OLl5ebm5+ns6uvr7Ozs7u7v8PDz9fb4+vp9fn1+f3+AgIGBhYIgg4SEhYaHh4eJiYiJiIeHhoeGh4iLjY6RlJSSj4uMi4uOggGDioKIg4WCh4MBgqqDhYKJg6WCBIGBgYKtgYeAg4GTgKKB/4DugISBhoACgYCGgQGAjYEBgOyB/4CygP+BrIGvggICBACAz8vHx8bHx8XEx9Tb5ffg3t7X2N7f4eLuhpuonYyHiYHw6eP7gP39gP75+fP4/oGGi5GXmqWusbWtpZWQiYGAgoP/9/2DhZmtnaWYjI6QrraelYiEkIb76MnAsLC/ucXU28/Qvr/yi43x28W8s8jQx8zCraWmqrnKw7+5s6ubnqOAs7Kmraytp52UjIiJjYb56uz0+/v47/D17uHW3Ofr39XPzMnLz9LW1dPYyr27vLm0qqKcm5qUjoiGhYKA/v79/Pn6/Pv++vbw7e3u7uvr5+Pl5ujq7OXn7vLzfYSNlKCZko+Nj5SSjo2Li4mIhoWGh4eDgYGGiIeHg398eevn4+WAc+Lc2dTPz87RatLQx8S/vb++vLm5uLe0tbW5vbu9vbu4uLi1tra3t7a2t7e2t7i4ubq7uLe1s7Gtq6qopqWkoqGio6Oho6KhoKGfnp+bmJaTkpKRkpWWlpiZmpudnp2amJiWlZWRkZKTk5ORkJGTlJKQkJCRk5SWl5aXmJmcnJ2AnJydn6CenZucnKCdm5uamZeZmJmZlqCfnqWioqeYk46JjJGUlZSTlZeVkZGRko+NioeDg4SEgYF/gIGDg4KCg4SFhoWGh4aHiouJiYiKjY+QlJOVkpGPk5ecoKCfnp2co7TAxsS+vbWpqK3DbtzYycvKxLe0tV1gY2JgX19dWlsfW1pbWllZWVhXVldZWVdXWa9YWlpaW15eXl9fYGFhY4RlH2hoZ2hoaGtubm1ta2xsamdmZ2dkZWRiYWNkY2NjYmGFYIBhYWBgXl9fX15dXl9eXmBfXl5fYGFiY2BgYWFkY2VlZGJiZWdnZmJhYWJjaNLS1tjS1dPNzNDTysTHydDDycXNwsLNyc/L1uPY0NLb5OLt94D97fHy7eXZ19rY0czMyMTHycfDxb2zsru2qKGdnJubnZyfoaGgo6GgnpuYl5aYnGmgoqKhnJ2cmZeWlZaVmaGlpqepqqmmpKKjoKKipKWorrG2u7/CxcnLysrJyMfDv7u5ubm4uLWzs7W5u7q3sq6rq6mnopmdpKqimqCjnZ2aj5KVlZeeoZ+enJxOn6SmVFNSU1NRUFJUVFKEUYBNS0xMTFFSUVRXWlxdX2RkY2ZlaG1weoB7eHd6enhzb3Bzdnh4dXJyc29sbW5xc3J0eH1+fn14cnN0dnd2eX2ChIaJjpWdn5yfpaqvsrS4u77FyMfNzs3Pzs7Oy8bHy9LX2dTS0tLV1NTU1trb4eXm5eTn6ubf29bMwru4urq6u0y9vr/CxMnKz9DJw8HBwcDBxcrP09XZ3+Xq7O7w8fDw7ezp5efndHN0d3h7foKFiI+QkJGTlZWWmJmanpydoKOor7e9wMTP1c7JyMrRgLi0sbCwsbCurK21usDJvbq3tLi7vMHBynB9hH1ycnNu0MjE02va3XDb2NzU0dpuc3d+hIaPlpmdlo+CfHdxcHJx3tfYbm98h32CenF2eI+Vhn52cXdy08a0raGgp6Ostbeur6WmwWpqvLCjnZmjp6KlnZCMjI2WnZqYlZGMhISGgI+OiIuKioV/enVzdHZx183N0NPT0s3Nz9DNx8fJysC6t7KvrrGytLOytq2in56blpGMiIiIhIB5dHJyceLk5+bj5Ojq7eno5OLg4eHe3tvY297k5+jl5+3y9X+Gj5ajnZaSj5OXlZGPjY2MioiIiIqMh4WEiYyLioeDf3307+3vbnjt5+Th2dra3XLf4dXQx8nMzszKy8nHw8TGy9HQ09LR0NHRzs/Q0dHQ0NDPzs/Q0dLT09DQzs3Lx8XEwsC/v7++v728vL28vLu8urm4uLa1srKxsLKysrS2t7e4ubm4trSzsrKysK+vr66trK2tha5Era2urrCxsbCwsbGysbCvsLGyrqysra2sqaiopaWho6OjpqSrrKqzrq+zo52Yk5acnp+gn6GioJqbm5uZl5SOi4uKioeFhmiHh4aHiIqLi4yOjo6QkZGQkJGUl5ibnp+blZaanqWprKqusa+ywMvQy8nGxcC9wthy4t7U1dXRxsbJbG5ub21vbm5tbW9tbGxsbWtqaGhnaWpqaGjKZm1saWpubm9ydXV1dHR3d3d4eYR6KXt+f359fH1+gH58e3p6e3t6enp4d3t8enh4eXp7enh5eXl4eXl4eXl6hHlneHh5eXp5enl5eHh3d3d4eHd1dXR0c3JxcG5ubW1u3d3e3tza1M/P0NLPy87P0cnLycvCwcjFx8TIzcbBwsfLydDVbdjR0NHPy8bGx8bCwL++vb/Bwb++u7i4vLu0srCvrq2tra+wsoSzbLKwr62srK+ytre2s7GxrqqoqKmoqaqrra+vsbGwsK6tqquqq62usrS2uby/wsTFxsbIycrIxsXFxsfHycjGxsfKy8zMyMTDxcTDwLq8wsXCvsDCv8C+uru7u7zAwsLBwMFgw8fHY2NjZGVkY4VlgGZnZmJiY2NlaWhoa21ucXFzeXd1d3Z5fYCKkIuJiYuLioaDhYaJi4qIhYSEgYCAgYSFhIaJjY6PjYqHhoeJiomMj5KUlJaaoaiqpqqvtLi6u77AxMnMzNHS0tPU1dLQzc7R1dnZ19XW1tfa2tnb39/j5unq6evv6+fk4NnRycfKHcvLzc3O0dTX3N3g4drX1tXV1tfa3uHk5+nv9Pj6hfwt+vn49vj4fHx9fX+Bg4aIio6Qj5CRkpKTlJWWmJeYmpyhp6qvsrW7v7y4tba7d4yKiYmJioqGhIKEhIWEg4OCgH+BhIqQlk1QT1BPUlNToZuXm06kp1SnoqGgnqRVWFldYWNna21wbGlgXVpXVVVUqKKdTU1OUVBPTEtNU2FkX1pXVFNPlZOTko2JiYiLi4qJh4aGhEA/e3p4d3h3d3Vzcm9ubWxphGiAZ2ZpaGZkZGNiYGBdXV1bW1taWK2tq6ekpKWkpaWxubiupaShnpyVkY+Oi4qKio6KhYB8eHh2dnV2dXVzamZkY2bO09jY1dfa3ODe4d7b2dzb2dnX1dvg6e7x7O/2+/2DipSbpqCalpSXm5mVlJGQj42LioyPkYyKiY2Rj46KhoOAgv36+/+A//r69+3v8fmA+Pvt5Nve5enq6erp5d/h5e/39/v8/Pr9//39/Pv+///+/Pv5+Pf4+fr49vb39vP08/T19vTz8vHw7+zs7O7r7e3t7vHw8O/t7u7u8fDv8PDw7+7u7uzo6Ojp6uno6efk5eXj4uPk5ejp6ejj4ePk4+F+4OHf3tnW1dPV1crHxsjIycbCwcC9ur6+vsC+xcTDyMTEyrizr6ittLa2t7e6u7q1s7W2tLGqpaKjoJ2ZmZeYmZqbm5ucnZ+hoaGjpKWoqamopaeusrS6ubiyqqavuMTKzs7W3dnY4+nv6efk7PXs7f+A+/z4/Pv07fL7jI6GhIuAjZGPkY+NjY2Oi4iGhoaIiYaDg/+Bi4yFhYqLjZGXl5eTkZWVlZSSlZeXmJqamZiWl5ianp2cnZydoaGgo6SenKSoo5+gqKmnpqWjpKSkp6qnpamqq6mpqKeoqaqpqaSfnqOin6OcnZiYl5eVkYyIhoeFhIOCgPr59vj17eHd3d6A3uDk4+Lg3t7d29PO0c7NysS/vrq5uLe4uLldu769vL2+wcHCw8PBwcPGx8nKysjLztHOz9TW19bV09HQ1NTU1tnW1tjZ2tnX1tXX2tvc3dnW0s7LycnHw8K/vb6+wMPDxMbHxMG9vby6vL2+wMDBwsPFx8vPz8/V2Nve4OHi5+o26unq6+3s8PLx8fHy8fb6+PXz+Pn5+vz6/v3+//7+/fz8/v//gP7+/oCAgIGCg4SEhYWFh4iKhYs5jI2Njo+Pj5CQkJKRjo+Nj5OVnJ+dm5ydnJiXlpeXmJmZmJeXl5aVlpaXl5eYmZqam5ubmpqbnJychJtenJ+hpKeopKWpq6+wsbO1trm8vcHCwsTExMPBwcLCxMfHxsbHxsfHx8jJzM7S1djZ293e29vb2trW09LT1tfY2dna293h4+Xn5+fm5ujo6Ors7/Dw8vL09fb4+fj4+YT6D/z+gICBgYKCg4ODhIWEhIWFA4eFhoSFDoeJjI+QkZOVlpWSjo6PmIKIg4SCBIOCgoOGgpODg4KSg5CCgoOmgq6BnoCigYSAAYGIgAGB/4DjgAGBiYCagQGA4oGkgAGB/4CKgASBgICA/4G1gaeCAgIEAIDX1dPS1Nnf4+DU2tzk4tvX0NLm+fz9hv+HlJGRgION+O3j3ODo7O73+fT29PL5+4eQj5Wdnpyfp6imlob+9PP37Obh9YSAi5SC/4aXlY31g6ebgqC+xbqh5s69saqoq6eloqKmqayzy8vBwMS9uq+gtd3r7eHSsrKwqq6uuL+yr1Wzp6ewtre0raWemKCckYWCiIuHgoOFhYL339nh5tvY1NPU3d3X0cvK0dfIvLayraehnZycnZePi4uHhYKCgYD//P339PX08u/u7unq7Ojm5ejq6e3vhO1m7u17iI6JiIWChYeJiYmIiYiIh4WHhYSChIaDhYaFhYWBfXt3dnV0c97f2tbU1NPR08/Jw7+8wMLCwry/vbu3uLq8vru8u7q4uLm4ubm6ubm6urm4ubm8vb2/vrq2tLKwrquqqKamhaRFpaSjoaChoaCenpuamZiVk5SWlpeYmpubnZ2bm5qZmJaWlZSUlZOSkZCQkpKRkpCRkpSUlpeWl5eZmpiamZmYmJeYmpmZhJgulpWYmJqYlJOVmJaUlZaUlo+KjJCUlJKQkI6NjpGYlpKQioWDgH5+fn9+fn9/foR/ZYCBgoKCg4WDhIaGhYeHhoeIiYuMjIuNjY2QlJeZmqCsuNLm6t7j5t/LxcW+v8bS19fa3t/g6O/lc25qamhhXVtbW1lZXFxbWlmtrayurllZV69XWFlaW15eX2BgYWJjYmVmZ2dohWoRa2xubm1sbG1saWZlZmZmZWSEYwFkhGN4ZGVkYWFiYmFhYWBgX19gYWFgYF9eX2BfYGBhYF9fYGBgYWJiY2NlZ2VjZGRmyc7U09HV2djSysS8ubu9wsra4+fx4N3V0dvVxMHG1NjZ2tnb3uDm8vv39Pb38uLY2NPLv8PHw8HDwL2+urOvs7qyqqWin56gm5uchZ2AnJqamJeYmZudnZ2bm56hop6ZlpWYmp+hoqanp6SjoaGioqOmqKyvsrW5vcLFyMrKyMbGxb+9uraysrGys7O2t7q+wL++vry5t7Gwpp+lrbGqnZuanqGdn5+dn6hPTU5PT05PT1FRUlRVU1RVVVVUUVJQT05MTExOUVJQUVNWWWJLamdoY11eX2FgZXB7eXN0dnd3dHF2fnt6eXZzcnBtbGxvcnR1d3l6e3l5d3l5ent9foCBhYeIjJGWm5+foaSnrLC1uLi9w8fIysrMhM5xzcvMy87O0dLS0tPW1dTU1tzd3+Hg4N3b1dDJxL67tbKzt7m5vL7Cw8TK2uPe1crDwb69wcK+wcfP0tXY29/h5Ojn6Obk5OPl5eTndHZ6fYCEiI2Rlp2ioKGin56goKWnqKqsrrCytbvDxc7W19fT0tSAwL27vLy/wsTAuLq7v766uLKyvMjLzmvRb3d1dWltddTNxb3Bx87S2NnT19HR2dp2fX2CiYyIipGRj4J039TU1tDJxMxraXF1a9Jrc3Rx0HCEfW+AkZaOfsa2q6KcmZuYlpWVl5eZm6epoqGjn52WjZissbSro5GSkY2PjpOXkI9kkYqJjo+QjYmFgH2BfndwbnFycG1ubm1s0snFyMnAvbi1tbu8t7KvrrK2q6OfnJeRjomIiIqIgHt6d3NxcnNz5+fo4+Pl5uXj4t/d3t7b2tne5OXr6+rr7e/x8H2KkYuLh4WIiYSLX4qKioiHiYiHhomJhomJiYiKhoKAfHt6eXjp6ufj4ODh3+Pf2M/Kx9HS0tLM0M7LxcfN09XT09HNy87T0tPU1NTT0tHS0NHS1NTV1tXTz8vLy8jGxcPCwcC/wL++v7++hb0mu7u5t7W0srGysrO0tra3uLm4tre2tbSzs7Kwr6+ur62srKytrrCGrz2wr66sq6unqKemqKmopainp6ekpKShoaSjpaSgoKKjoqCgoJ2gmpSWmZ6fnJqbmJeZnaSknpmSioiFg4SDhYIGg4OEhYaGh4cCiYqEi1aMjo2Njo+Qj4+RkpabnqGhp7K+zNjf3N7b18vKycXI0Nnd3t/j4N/j6Ol2c3BwcW1rbG1uamhscXBtasnJx8nMbGpnzWZoaWpsb3Fzc3R1dnV0dnh6e4Z8D31+f35/f35/f359e3t9fYR8HXt8e3t6eHh4d3h5eHh4d3d5eXl6eXl5enl5eHl6hHmFeFt5eXl4eHd1dHRxb3Bvb9ze3dvb3dzX0s/My8rJyMnO1dfY3tfUzcvSzcK9vcTFxsbFxsfIzNLY1dPS1NHKx8fDv7q8vr69vb27u7q5t7q8ube1s7Kxsa+usLKzhbIQsbCvrq6xtLe3tbS1tbGsqYSnCamrrK2vra2urISpPKusrrO1trm8wMPFxsfHxsbHyMbFxMTEw8bIysvKy83Pz87Nz83Ly8rEv8LIy8a+vr7Bw8PDwcDCxmFgYIVhaGJjZGVlZWZnaGdnZmdlZWVkY2NlaGloaWpsbnZ/fH53cnJzdXZ4go2KhIWGiImGhYiPjYyLiYaEg4KAgIKFhoeJi4qLi4uKi4uMjYyOkJGSlJWYnaKlqKirrbC0tru+vsPIy83P0NHRhNJw0dLR09PU1tXW1tna2dnd3t/h4+Pm5N/b19TQzcvGxMTGyMrMztDR0dnp7uzk29fW1NXX19XY3OLm5+js8PL09vX29fPy8vX29vh9f4CChYiLjpKUmJycnJuZmJmanZ+goKGjpKWorbO1usHDxMG/wAyPjo+QkI+NjIiEgoKEg06Af3+Dg4hFjUlMS0xGSlGgnZiPkpeanaSloKGenqenWV5eYWVlY2VpaWdeVKSop6OhoJmWSUdJSkmRR0hISpVPU1BPUVNTUEyVk4+Kh4WFhoCFhIGAfnt6enh5eXl3dXFubGxtamtramhmZmlpamhmZ2ZjYV9eXl5dW1lXWFhWVVVVVlRTUaiusKunop+ZlZOVk5GOjIuMjYqLhYF/fHl4dnZ3d3JsaWZjYWRnadba3NfW3ODf3tvY19vc19bY3+jq8fLy9PX19/eBjpaQkI2JjQGOhI+AjY2Ni4qMi4uMjo6MjY6NjY2JhYOAgoGAgP38+/b3+Pj3/PXx6N7c6uzv7+ru7Ong4+/4/Pv89+/p8v3+/Pv+/////vz6+/r69/f5+vn18/T08/Pz9PPy8/Lw7u7t7Ozs7vHz8vDy8e7u6+vq6u3s7O3t7uzr6+vs6eno6enm5eWA5eTl5ubk4+Xn6uvr5+Pg4eLf2tXS0cjIycnGxcLBw8LBwL++vbu6urm+vLi4u7y8uLa3s7ewqqyvtLOxsLGvrq61vbu3sqifnJiUlJKRk5OTlJOVlpaXl5mZmJeYl5iZnJ2cnJ2en5+hoaKioJ+hpKuztrm7w87W1t3l6unf3do63uHh5efs8vb18u/s6+77gYCBg4WEh42NkIiFjJCPi4T8/Pn7/omHgP2Bg4SFiI6PkZOUl5iTj5KUl4SYLJqamZmamZebnJucn6GioKChpKWmqKakpaWko5+dmpeboaChnp+go6alp6emhaWAp6alpqWkoqOmpaWopqOgnZqXk4uJiIaEgv799PT4+vPq5OXo6Obk4uLj3dfY2NjUzM3Q0MvGwLy7ubm5t7a2uLq7vLu4uLzBwcLCw8PFx8fJycnKysvO0s/O0tXW2NbT0tTW19jZ2tvc3d3f3trZ2Nvg4uDh3tvW0MrHxcXCwMBFvry8vcDAv769vLu5uri5u7u7vr+/wcPExs3P0dTY297e4uPk5ujq7O3t6+np6+3s7vDu8vr89/Pz+Pz9/Pr5+fr9/vz+hIABgYSAQoGBgIGCg4WGiImJiouMjIyLjI6QkY+PjpCQlpiXmZOPkJCRk5OXnZyXlpaXl5eWmJ2dnJqal5aVlpaWl5iYmZmamYSaAZuEnICbmpqZmZqdnp+foqKjpKaqrbCysbS3ubu9vr/AwcLCwsPEw8PDxMXFxcTGx8bGyM3N0NHU1dXW1dPT0tHRz9DQ0dLS1tbY2dnd5Orq6ujm5ufn6enp6+zv8fLy8/T09PXz9Pf5+fr7+vz/gIGCgoOEhIODhYeIiYmJh4eFhYeHiBCJiIeHiYmLj5GTlJaXk5KSloICg4KHg5CCjYOIgoWDAYKEgwGCiYPAgqeBnICmgf+A94CRgYWABIGBgYDfgf+ArYD/gb+BpoICAgQAgNHP0tzl8Pj97OHX2d/n69/X1NHT1tvm+Ymgooz6gIj7hPDe4uTk6Ovm8ID35urp7/n+gYeKjJWhpKKbk4uD/Pfs5uDa4++KkIb/j42F9tXYhJKH8/Ln2dPSy8W6sq6roqKdnJydnqCnrqynpqazwcLIy87d0c+/u66mqbLQy7KwgKyptrqxsaylm5OQlI+Lg4SLjYeHhYqGg/Hj4+rn2s3Hz+X07NrPycrKxsK9sKaem5ycn6Omn5aRkIuIhoWEg4GA/fv4+fn29/Xw7uvr7Ovj5OTg4OPj5uXn5eXrfvR8fXz0e36BgoGBgoOEhoqLioiGhoWGhYWFhoaCfn55eOzqIObkc3JycG3V09LPzczGw8TEw8LAv766uri4ur++X76+hr96vLu8vL29vr++v7/Bw7y4trazsbGwrqupqainpqamp6akoqOkoqKhoZ+enZucnJuamJmZmpyenpybmpqbmZmWlpWTkpCQkJGRk5KSkpOTlJSXl5eYmZeWl5iWlZaUlpSTk5SXlpaTkpSSkpKQkY6Nj5CPjoqPj5OVjo+EiyeHioqHhYmLhoWGg39/fn19fn59fHx9fX99fn5+f3+AgICBgYKDgoOEhV+Gh4mKi4uMjI+Rk5ebn6exvsnW2uDg5urc2NbNx8G9vsHJzdTX2uDf4OHczchkY15cW62sWltbWbW2XFteXl1gX15cXVxdXV5eXl9gYWJiY2VlaGloaWpqamlqbXBwb4VsEGtoamxtamlnZ2VjY2NiYmKEY3hiYWBhYWFgYGFiYmJjYmFhYmNkZWZmZGNiY2NoZWJhY2NhYWFgYGJjZMjI0dbV08zIzcfG0M7CvLq8wcjN0cvGxMPGzMfT08jJyMzMzNHU1trZ19jb3t7Z087My8TFx8fEv7q5urevrammpaKhoqGgoJ6enZ2cm5qEmRiam5uam5uampmZmZucmpiVlpWWmZ2foKGEooChoqGjp6mrrrK1t7q+wcPHycnCwb+8ubSxrKqpqaitsLS3ur29u7m0ub7Bwr2wpJ6lqamjm5mdnqOloaSno1KiUE9QUVFSUlFSVFZXV1hYV1FPTUxMTU9OTU1OTlBSVVhlc2xmYVxbW2BgY2p0eXJwc3d3dnV0cnR1dXNxbm1uboBucHB1fH5/f3p6eXd3eHd8gIiJiImJio2Um56goKKlq7G3u7y/w8TGys3OzM3Mzs7My8jGxsvP0dLQ0dDQ0dTW2NfZ19bT0MzGwL26t7KxsrO3u73BxM/ZzdTq5eHbzMLAwsbKztDU2NfX1NXW19rd3+Lh4eXr83x6e4OIi5CWmyGdoaWnq6ysra+0s7S2s7Cwr7Ozt7q7u7/H0djg3NzZ1NCAurq+xMjM0NPIwrq7vsHBvLWzsbO1t77Kbnx/cc5qcNdwzbrAxMLGx8LGZ8jExMbM2N1ydnp7gYuNi4R8d2/X1MzEwLzAxm1xas1vbmrLub1qc2zMzMS8urq0sKiinZmUlJKRkJCRkpSYlpOTkpihn6KjpKyjoZqYkIuMkaGhl5SAkZGXlpCNi4eCfHt8d3RxcXR1cXFwcm9t0MvIysvEvbe4xMzHvLaxsbGtqqebko6Mi4mKjI6LhYF+enh2dHV0c3Pl4uDh4uPo5eDe29vf39rb3Nvb3t/i4+bj5Ot/+H1+ffd7f4OEg4OEhYaIioyNiomKiYqKiImKioeEg3178/It8PB5eXl3c+Hg39/c2dHO09HV1dDNzMnIx8jN1tZr1tXX1NXX1tfT09fU1NXWhNeA2NnU0c7My8vKycjFxMTBv7+/wcHAv76/v72/vby7ube2t7a1tbS0tba3ubi3trO0tLO0s7KysLCurq2urq+vsLCxsLGvr66tqqqop6ampaSlo6Wio6KhoaKin56fnZ2dmpybmp2dmZqTlpWZnZqYk5STko+UlZCNkZSPjYyJhoMBgYSAgIGAgICBgoKDhIODhIODhIOEhYWHiIiIioqKi42PkJGRkpSYm5+ipaqxvMPMzdPU2d3V0tPNzMvFxMbQ09fY2t3e3uLf0tFvcnFvacbHa21raM/Ramltbm9wcG5tb3BvcnFyc3R3d3h3eHh5fHx6en1+fXx9f4B+fHx+f35+f31+AX2FfgV/f319fYV8gHt4eXh4d3l4eHh5eXp6eXp6enl5eXd3enh5d3l5ent5eXh3dXRzcXFwbtva3d7e3NrU0tHR1tPMx8bHyc/PzsrGxMPFx8HFxb28vL69vcDCw8XFw8TExsfFw8HBv72/wcG/vLm6u7u4tbOytLS1tLOysrGxsrO0tLSzsrOzs7KzF7OztLW1tLS0s7CtqqimpaanqKmqq6uqhakNqqutsLK0tri7vsDDxYXGK8XEw8TDxMTGxcjIycrMzczMy8vO0NHS0cvEv8TGyMO/v8HBxMTCxsjFY8WEYk9jY2RkZWZoaWlqamlmZWVkZGVmZmVmZmdoaWtteoaBe3ZzcnJ2dnl+h4uFg4aKiYiIhYSGiImHg4GAgYGCg4OHjY+QkIuMjImJiouNkZiYhJYSmqClqKqpqq2zuLy+wcTHycvPhdFb09LQzs7Ny8/V1dPU1tXU1Njb3Nvc29vc2tbRzcvHxsTBw8TGyMzO09zk2+Pz7+vm39fW2Nvd4eTo6ujn5+bo6urt7/Lx8vT4/4KBgoeMjZCUmJmcoKGkpaWmpoSpFKelpqWoqKurra2wtbzBxcbHxcC6EZKPkJSSkJCPjIuGhoaFhIOAhH6AgIKGRUpLRopITJtRlomNkZGSko2KRImJjZSdqK5bXl9eYWZmZV9aVVOjnpqYlpKLiEhIRodFRkWRi49HRkeRlJOQjo+Pj4uHhIKDhISDg4OBf3x7e3p5eHZ1cXFwbW1sbGpra2xraWlscm9sb25pZ2ZlY2FgXl1aWFlZWFdYV1UoVFJRp7CqoaaoqaahnZuXl5uYl5eQjpGHfn19enh2dnh4d3FtaWdmZYRoMNDPztDR1t/b1tXS1djY1NbZ2Nvh4+Xm6+rq84P/gYGA/oCChoeGhYeHiYqNjo6NjISOY42Njo+Lh4iCgf/+//+AgoSDgPf09fTz8enn7O3x7+nn49/f3+Ls/f+B//z/+/z//fv3+f78/f/7+/z5+Pj39PX08vHz8/Lx8PHw8e/u7e7u7e3u8fLw8PDv7u3p5+jp6erp6oTrZOrp6ejn5uXn5+bl5ebo5+jo5ePk5ufp6OXg3djW0s7KxcPDwcLEv8K+vb28u7u7t7W1s7SysLKxrrGzsK6mrauus66vq6upp6OoqqShpqqkoqGblpOQj5CRkY+Oj4+Qk5OUlJGFk4CSk5SVlpmZm5uamp2goaOko6Wss7S5urq+wsnOzs7R0dbc2Nfa3uHh29rb4+Xp5+Xo5+ju8Onrgo6SkIj3/IuMiYP+/4GDiouNjoqHi46QkJWSk5SWmZucmZeXlpiZk5SZnJuYmpqYlpWWmp6cnJ+enpuenqKnp6qtq6usq6inpoCmop6goZ+go6OgoaOipaGhpaShn56blJadnqGbnZ2kp6einZyXlJOOiISB+vj18vLx8u3p6uno5eLi5OPi4trT1NHNzMvLwr29urm3t7a1tre4uru6ubi6vb/CwsHDxMfHx8rLysrLzdHQz9HU19rY2NfW19fY3N3e4ODf3uDg3hjf3t/k5ePj4+Da1c7Kx8bEw8LAvr6+vbyEuwW6urm5uoS7TL7BxcjJyszQ0tXX3ODj5efr8vDx7uvq6urr7vHz8PDw8fb6/f75+Pj5/v39/fz+/v39/4D/gIGAgIGBgYOEhIaHiIiJioqMjY2Oj5CEj4SQKZKaoJ2ZlpOSkpSTlpicnZmXmJqZmZmXlpeZmpiWlZWXmJiWl5mbm52dhJokm52cnJ+fnpybmpqcn6GhoqGipaissLKztLe4uLu9vr6/v8DAhr+EwmfBwsLDxsjLyczMzdDQ0c/OzszNysvNz8/Q0tTU2d3Y3efn6Ojl4+Pm6Oru7/Ly8vDx8PDx8vLy9PT3+vz/gICBhIWGhYeIiYmLi42NjI2Njo2LiomIiYmMioqKi4mKjZOVlpeYl5WSmIKEgwWCg4OCg4mCAYOHgoyDiIINg4ODgoODg4KCgoODg8WCqYGbgAaBgIGBgYCcgYSAhYGWgAGB/4DfgIWBgoCEgYKA54H/gK6AAoGA/4G4gaqCAgIEAIDNzMvQ2t7i7vXz8e/q7OXl6erh29nX4fmVwa6E5trb6vPr5OLl7eXl5ezu5OHY2d7h9IKGgoKHj5GRkIyIg4GB8+vo+5+e6+LT1dvX0s/JzM7Z5dPNyMrN0M/Ct7asqKa0oZydnZygpKGqs7KxsqKWl5ylurKioqu+tqeqs66upYCovL6olZGRkIuGgYD++fPy9PXz8/r9/P35+v389ezc1uHz9erSxsHByMnNx7iwqqmnpqajoZ2Zk5GMioiHh4mKiomHhYOA+vr6+PTz8/Ps6OTl4N7f3+Xm6u/y8PDx9vz58vP3fn+AgYKDhIWEiI6LiYmJioiHiIeIhH9/fXh4eBx35eTjcnJu29jV0M7TycjIzs/Hw8XFwsC9vb2/hMEpwsHCxGNjxcXCv769wsHGxsbCv7u4trOysLCwrquqqainp6emp6Wko6OEohOgnp6en5+enZ6bm5ucnJ6dm5qahJiAlpaVlJGRkJGSkZOTk5KTlZSVl5aWlpSUlZSTk5OSkpGQj4+QkpOWmZSPkY+Ni4uKiomJiIaGhYaEh4uKhISFg4GAgoF/f4CAgYKAfXx7e3t8e3p6enl7e3x9fn19foB/f359f3+AgYGCg4SEhIaGiIuMjpGSkpeco6qvtLnAwbxLurm3trW1u7mvqq6uqqq1u8XI0NLV0MvCwry5XVy4sqyutLdhY2LBYmNiZmdpZWFeX15eXFxgYWBhYWJjZGZnaWhpamlqbG5ubm1rhWwNa21ucHBvbWtpZ2VkZIRiYmNjYmJjYmJiYWJjY2VlZGNhYmFiZGRmaGhlZGRnaWtqamtuamhnZGJgXl1eYMLFxcTEzNna0s/P0M3Gw8DCv8DBxsbLwL/AxsvIwr+9vsDDw8TEwcTIz9LPzczSzcS/vsHAhL6Awb67tbSqqKempqSkp6SjoZ6cmpqamZucnJuampuZl5eVlJSWmJeVk5SUlJOVl5qdn6Ghop+hoqKkqKuvs7S3ur2/wcLExMLBv7y7uLi2sa6qqayxtLCwsLOwra+ztrq9v7iqp6Gho0xLSUpMTZ5PpapTU1FRUFBRUlRUU1FRU1ZfVlZXU1BSUFBRT05OTk1MTE1OUVZcYmNiW1pWWF1fYGZrc3BucXR4eXhzcXF0dXFvb29wcXN0c3J2f4aEe3p4dXp9gYqNkpeRj42Qk5aboKGipaqvtbm7vb7Ew8XGys2Ey3LJycfExMTJy83NztDS0s/Oz8/OzMvIxcK+vLm3tbSytbi5vMK/wsjO4NjR19fRz8fDxMbHyMvN1dvf4uDf3t7g4+fr7+54e36BhYuPk5qcnJ6ipqanqK2sra6xs7S2t7a3vMDDxcPGxcPGy9DV1tbX08+AtLS0tru9v8fOzcrHw8XBwL/AuLS0s7nGc42CasK4ucLFv7m6vcK8u7y/wb29t7e5u9FxdXJ0d3x9fHx4dG5ub9DGxNB7ecbAtbS1tLOwsLSyuMC4tLGztbe2q6KimpeVn5ORkZCPkZKRlZqZlpiOiIiJjpiRiYiOmZSMkJWTl5AvkJ2fj4OAf315dXJx39zZ2NrZ2tfY2NTW1NXT0M/MyMXHz87Hu7ezsLKtrquclZKEkYCPjImIg4B8enp4eHl7fHt5dnNx4OXp6OXi4+Xd2tna1tTW1tzf5Orv7Ovv9vr48/P4f4CBg4WGhYeGiZGOjI2NjoyOjouMiYWEgX58e3nv7u15enXo5uPf3+LZ2dnf4drV1dbT0dDR09bY2dna3NrX225v3dvb2NHU2Nfd3dzY1DvSzs3Ly8rJx8fFxMPCv7/AwcDAwL6+vr29vby6ubm4t7a2trW0tba2uLe2tbOzsrGysrGwsK+tra6uroSvUa6uraurqainpqampaOhoaCgoJ6dnZydn6KmoJyfm5eWlpSWlJKRkIyMkI2Ok5KMi4yKiImKiYaGh4iJiYaCf39/fn9+f35+f3+AgYGBgoGCgoSBgIKCg4SEhYWGh4iLi42Rk5WXmZqdn6Soq7C0u7u5t7WzsrK0vr+4r7SzsbG+xcvR19bb2NTO0crMa2jOyb7DzNFsbW3Yb3FwcnN2dHJxdHNva210d3Z2d3h4e3t8fXx9fn18fX+AgX9/gH9/fn57ent7fHx9fHt8fX59fX58e3x7bHl4enp6eHp5d3h4eXt8e3p6eXh5e3t6eHh2eHl5enp7end3d3RycG9ubWzY2NfV1tbZ2dfTz9DOysjIycfGx8nHx8G+v7+/vrq2tLW3urq5ubq7vMDCwcDAwsC9vLy+vb69vby+v726ure4t4S4SLm4t7a3uLe3t7W1tra2t7e3uLi2tbOysK6sqqenpaWnp6ipqqurqqqqqamqrK+wsrS4ur2+v8LGx8fIx8fHxsbHyMnJx8jHyYTIEMrKycnLzM/S1dHKx8TDxmCEXwhgw2HGyWRkY4VkgGZmZmVmaGlpaWpoZmdnaGhnZmVmZmVmZmdobHN5eXhycm5vdHV4fX+HhIOEhYmLiYWEhIeIhYOCg4SEhoeGhYiQlZOMjIuJjI+Ump2go56bmpucn6Soqqutsra8v8HCxMjHycvOz87Oz87OzcvLysjLzc/P0tTX1tTT1NTU09PSXNDOycnHxcPCw8TGx8vNzM/T2+jj3uPl393a19fa3Nzd4efr7/Lw7+7t8PT2+fz9gIGDhoqNj5OYmZqanaCioqKkpKWmpqepqqurrLGxs7aytLOysrm8wMHBwL24EoiIioiFhIaKjY2MiYWEhYOAfoR8UYCFRElLRYiEg4SDgX1/g4aBfn6BhYWFgoSHip9YW1pZW15fXlxXVVBRUpeQjo1JSI+Nh4SEhIiHiI6JiZCOjIyOjo+QiYWFhIOAgoSDg4KAfoR9gHd0c3Z4eHV0b21rZ2hqaWpsbG93cm92dHFsamdlYmFfXru6vb68uryzrKilpqaknZqepLO1rqWfnKOpp5+VioiEfHp4eXp6enh2dXVycWxqaWlpa2xtbWpmZWPL1dzc2Nbb3tXS0NPR0dTV3N/l7PDt7vH4/fz29/yBg4OFhoeHR4mIjJOSj5GRk5GRkY+QjYmJh4KCgYD7+/2Cg4D9+/by7vvx8PL9/fPt7vLw7uvx9/z///39/fn2/YCA/vz7+PPz9/b7/f36hPMI8e/w8O/v7+6F7YDu7evs7O3s6+7t7Ovq6OXl5uXk5eTk5efn5ufm5OTj4eLk4+Tk5Obk4+Ti4+Tk4d/c2tXRzsvHxMLDwr+9v729u7m2tLW1t7q8trCzsKyrq6mqpqWjoqCdoqGjp6ihoZ+dmpqbmpiZmpiampWQjo2NjpCPjY2Ojo+PkJGRkZCPkICQkI+Oj5CQkpOUlJaXmJycoKSorbCzsba0s7O1ub7FxMHAv768vsLT1svByMfBwtPc5Orx7u7r7e306++BgPv35+z//4KCgP+Fh4eFhYqKi5CWlI2GiJWYlpOXl5aYl5iZmZqbmpiXmZudnJ2dnZycnJaUkpGTlZeZmaCnqairrGCpp6einZygoaSjpqGdnZmboqippaOfmp2bmpqYl5OSlZSWmpqSkpKRlI+JiomFgPv7+/nz7Obk5+Hd39rW2N/i3tnZ1MvLzcfDwb68uLe2tba3t7m6u7q4uLy+wMHAw8WEyYDIyMjJyszO0NLU2Nzf3+De3Nzd3+Tm6efm5uPj4uTn5+nt7Ovq5uHc2NPOycfGxsTAvr/Avry8u7u9u7u7urq7u7y/wMTHzM7P0NLT1tvd4eHl6+/y9PLt7O/v7u709/Xz9fb1+fv////+/4CBgoGBgP+A/v+AgIGCg4ODhIOEhgOHh4mEil6MjpCPkI+Oj46Pj5CPkJGRlJecm5uWlpOTlJOVmJmcm5iZmJmampiVlJeYl5aWl5eXmJmYmJqfoJ6dnZybnaChoaKko6CemZiam56hoaGjp6mssLKytLa1t7m7vLu7hLwYu7u7ury8vr28vsDBwsLExcbIycrLy8nLhMoQy8zNzs/Ozc/T1t/c3ODg34ThGObn5+rs7vP09fPy8/Lz9fX4+ft9f4GChISFDoeIh4iJioqKjIuLjIyMhIsSjI2Pj46NjYyJiYqMj5GSko+MmIKEg5aCjoOEgoKDxoK6gZ6AnYEGgICAgYGBnYCCgf+A2oCCgYaABIGBgYDngf+AoYCGgQSAgYCA/4G4ga2CAgIEAIDYzc7k6fn6+fjy7Pfq5f6EiY6SlIv12tbd7fn56feAgOHd5u/08+Hi5ebh3t/a1trY4OuDjI6OlJKLiIX/+Pn9/Pn08vHt39/d6ujUyMjJztXJyMXAvL6/vsDBvbu1sqyqpZ+enp2fn5yepa6clJGQlaayraGWnaasppqbobKnoICZo6eal5GLh4ODgoGEgoWEhYWD8/Du7/T8/f/47t3Z3Oj9gf/x5NrSydDOyMG8ubWzr6ulnpyZmJeRkY+PkpCTmJiSjoyIhIKBgPz38/Lt6+jn5uPi4uPm6ez3/fr7/YH++fPy935+gICBg4WKk5CLiYqPjYuMiYmKiYiEgoB/foB8eXTicnNx19LJxsbJysrIxMbJxcbEv8C/wMLDwsTFxsbFxMbKyWXMz8/Kx8XCw8LCvr25tbOxsK+uraqqqainp6elpaalpaSjoqKgoKCfn6Chn56dnJqam52dnZybmpqZmJiXlZWTkpKSkZGRkpKTlJOTk5SVlJOUk5SVkY+RkjCQkJCOj4+NjpCTkJCPjYyJi46Ni4mGhIOEgYGFhYSGhoOBf39/fn9/fn5/gH9/fHuHeg97enp7ent8fX19fH19e3yGfWF/gYODgoSEhoiIiouLjJCTlZWWnKOqqKempKGfpamsrK2rqaefnKKurrO6tri2vMG+s6mwt7qwr6uxXmJjYmJgu7WysLm3s7O1WlxcXF1fXV5fYWNmZmhpaGhpaWlqbW1uhWwSbW1ra2tsa2lqaWlpaGZlZWRkhmMDYmNjhGKAZWRkZWRkY2NjZWdoaGZmZ2VmZ2lpaWpqaWppZ2NjyWFgv11dXl7DxsjJztDJxsrNycTFyMC4tra0tLi9vr/BxcjEwr+7wMDCxMbO0NHRzs7U083Dvr26t7i6ur7Au7q1tK2npaKioaCgoJ+dnJuampmampmYmJiZmZmXlZWUlZYKl5SSkZKSk5mZnoWfgKCioaOlrK+wsrS3ury9vb6/wL68u7m3t7azsKyrrrO8vbqzsK+usa6nUqqxtlZSTkpHR0hKT05NT1KrVVRSUlFSUVNUVFNTUVJRUVJTU1FRU1FQUU9PUFFQTk1NT1BTV1dXW1tXVldZX2BgZGhoaW52enp4dHJ1eHdycnRzdHd6gHh1dnl6fYCAfn57fYKLkZOPkJGSkZKVmZ6ipKaqrrO4vMDCwsfJysrMy8nJysnIxsXDwMDDxsnNzs/S1dHKyMXCvr25uLi3trSzs7S3vLy/vL3Ays3M09bMzcvKycrLzc/Q0M3NztDW3ODg4eXl5ens7PN7fH+Cg4WIiYuPkpaaIJ+kqa2urq6xtLa4u77AwcfIzdbX1tXTz8vP1NXX09TXgLexsb7AysvLycTCxb++zWlsbnBxasKysLS9ycvBxmVltrO6wcXFubzAvrq3tbGusrK7xnB6e3x+fnh1c9vS19nU08/LyMbCwrvCwrWwsLO2urOyr6mmqKinqamloZyZl5eUkZCPj5CPjY+UmIyHhYSGjpSRiYKFi5CLhYiNmpOPRomNjoeEf3t3dHNycXJzdnR0c3Pb0s7N0NXV2NPPysvLztds1MvGvbizsq6qpqKgnZqYl5OMiYeGhn9/f4KGiIuOjIN+e3iEdYDl4OHg3NrY2dnX19fa3uHl8fn2+fyA//fx8vh/gIGCg4aGi5KTj4yNk5GRk4+PkI6LiYeGhIOCfXnreHt44trSz9DV2dbV0tbb1dTRztHR1dna2Nrd3d3c293f4XLj5uXg3dvY2dnX1NLOy8rJyMnIx8TDwsLBwMC/wMC/v728vXO8vb27urq5uri3trWztLS2tba1tbOzs7KysrGvr66ura2urq+vrqyqqamnp6emp6SlpaGfoJ+fn56cnZyZm56gnJyZmJiUlZiWlZOPjIqLiYmNjYuNjoqHhoaFhIaFhIOGh4eGg399fn5+f4B/f39+fn5/hYADgYCBhIBfgYGBg4OFhoeHiImKjJCRj46QkpSUlJugpqWjo6KfnqOtr7KxsbCuop+mtLO9xL/DxdDV07+xu8XIuby9yG5xb29wbdLNyMTQzMvNzmlqam1ubmxrbHByd3h7fn5+f3+EfjSAgYGDg4KBgH17fH1/foCAe3l7fHx8fXx4eXp6eHd6enx7eHd4eXd6ent8fXx6enx+fXt7hHx4e3t6eHd3d3Vzb2/dbW3YamlrbNjZ1tLQz8zJyczNycvOycPBwMDDv8C9u7m6vLq6ube5ubm6vMDCwsLAwMPEwr68u7q5uby9v8LBwsDBv7+/vry8vby8vb6+vr28urm3uLi3t7q7ure0sq+tq6upp6anp6epqKushKuAqqurrbCxs7a4ury/wcPFxsjLycvLysrJysrJyMnJzNDS0MzLy8rKyMhky87RZmRiX19fYGBiYWFiZMxlZWVkZGVlZWZmZmdnaGdnZ2hpaGhpaGhpaGdoaGhnZ2doaGxvb3Bzc29ub3J3d3d5fn5+goiMi4mFhYiKioWFhoeIiItoiYeJi4yOj5GQkI2Ok5mdn5ucnJ2cnZ+hpqqsr7CzuL3BxMfHy8zOztDOzM3OzczKycjHxsnKzM7P0dbX1dDNy8rHx8XEwsPDwcHAwsXKyczKyszU19jf4tva2trZ2tvf4OLh4OHi5OmE8TT19fb4+fn9gIKDhoiIiYqLjpCUl5qdoaWlpaenqaytrq+xs7W3u8HCwMC9vLi5u7u7t7e4gIOBgICBgYOEgoB/f3+AhEFBQD9AP3l3d3p9hISCgkA/eXl8f4CDgIOGhIOAfnh4e3uGkFJbXFxfXlxaWKWdnJqXl5WRjIqRkYeIiIiKjo2PjY+PjoqHiIaDhISBfXl4e35/f4CCgH5/f359enh6eXh2dHNxbmxqamtrbW9vd3d0gHJvbWtraWZiYF9fX15gZGJdXWC9sKqmpKSipKWotLu6sKlSnqChnJmZjIaDg4SDf35/gH95dHFyc25tbXJ7fYGFfnJtamhpamts1dLU1dLQztDR0NLS1dvg4+/19Pj7gP328fT6gICBg4SGiIyUk5GPkZaVlJWSkpSTkY6Ni4mJaoiFgP2Bg4Dy6N/d4uvv7uzo8PXu6+ro7Ozz+/v5+/7+/Pn4/P7+gP7///v39/b39/bz8+/s7e7u7ezt7u7s7Ovs7evq6+rp6uno6evs6+ro5eTj4+Li4eHh4uPj4uHg4eHf4d/g4ODh4uKE4RDf393V08/NzMvJycXEwry7hLo1ubi2tbGxtLavsK+sqaenqqimpaCdnJybmp+gn5+gnJiXmJeWl5eVlpibmpiTj4yMi42Oj4+EjoCNjY+RkZGOjYyLjY+Pjo2Oj5KUlZeYl5icnp+kpZ+cnqGioaGlqq+vrKmqqaq0vMDGyMPDv62ruMnJ0dzY3+Lz/vnZzNjm5dfb3++Ii4aDg4T/+u/p+fLy+P6Cg4OEhoaCgICIiZGRlpudnJ2dnpuamp2foaOjoJ6dmJWXmZ2ho4CkmJWcn6Kip6abnKOim5iio6amnZqdnZufoKOmpqWgnJ6jpKCgn6KinJmYl5GOjo6Lh4WC/oKA/4CBg4H49O/m29jb19TU2djb3NvX1tjW39XPxb64t7e3uLe5vLu7vL29vb++v7/Cw8TGx8bJysnMzM7R1Njc3eLk5uzs6+3u7Sjy9Pb6+vPv6+rq7Ozr7+/t6ufh2tTOy8rJyMnIxsbBwsHAv7+/vr27hL04vsHExcjL0NLU1trc4OHj4+Xp6+/z9/bx8PLx9vj5+fr8/4D+/v6AgoOEh4WDhIGDgYGA/4CCgoOEhBeFhYeIiYmKjI6Ojo+RkI+PkI+QkJCRkYSSaZSVlpWWlpSUlZaWl5iXmJeXl5qamJmXl5iZmJeYmJiZmpqXl5qcm5ucm5ydnJ2enp2el5iYmpmZmpyeoqOjpaepra+ytbW3uLi6uri3uLq5urq5ubi5uLm6urq8wMHDwcDAwsLExMbExYTGH8jKy8zOysnLz9LT2NrZ2tvd3uDh4eTm5+jp6e3w8vOE9Aj19ff2+Hx+gISBDoCAgYGDhISIiYmKjI2OhYwTjY2OkI+SkZCOjYuIiIiHh4OBgo+ChoOJgoKDk4KJg9eCj4EBgqeBlYABgYWAnoEEgIGBgZ+AAYH/gOCAhoGJgNqBBICBgYCEgf+Ak4AEgYCAgI2BAYD/gbmBrYICAgQAgITqhIWMnY+B9/788+yCj6a0qJWXhvDj3uiG/e7o/oSD3tXU1tfZ2d/m7uzn3tvh3t7k6fmEhYWMkJOarIn9+oCChYeG8uDa1tfj5dfIwMnNzci7tbe1s7e2v7y4t7KwrauppKalpKy3p6Kcl5aVlJ2qmJORkpabmZyamJmiq52bB5SPkYyFhISEgWeDhYGDgIiLgYGG/fH4goX+7efa2eP7gYCFjIf58u3l5tzNxsK8t7Kxq5+XlZeZlZSXn5yWm5iUkI6PjIeHiIeFhIH79/Pv6+bi4+bm5enr6veCgYGDgPz39vb5/H+AgYKEh4aIhoSJhI6AiYiMjIqHhoaDgH55dnRyc9zQzMnGwMHIysXAw8fIwsC9wMDAw8TFxMPIycjHycvJy8/My8nIycXCvbu4trW1tLKvsK+uqqmoqKelo6Kjo6GhoqOioZ6en5+en5+fnZybmpubm5ycnJqZmpiYl5aVk5KSk5STlJSUlpWWlpOSlZUNlJSTk5GQkJGRkpGOj4SOgIyLiomJi5GSjZKWlZCOiYiMiYqMjImEg4OEgX9/fn5/gH59fHx9fHt6eXh4eXd3eHl5enp5e318fXx7fHt6e3t8fXx9fn+Bg4GBgoSEhIaJiImJjI6PkZKXnJ2hoJ6cmZiZnJ+gpaWkoJuWmZ2cnaSrq6eosLKsp6epqqiwrKmyEGNmYr+8tK+pqq2wslpcXl+EYBJeXl9gY2NkZmlpZ2psbW1tbm2GbAZra2toamqEaQZoaGZlZmeHZARjY2NijGNaZGRkZWVmZWVpa2poZGJiY2RjZGRhwGBfXlxdXl9hwr++vL29v8PJzM7Mxr+6tbq7tre3tLS1tbW5u7q7wsTGyMnMzM3KzMvNx8bEwL/Avrm2trayrq2rqaakhKJro6NQUKCfT09OTZmZl5aXlpeXl5iWlZWSkZKSkZGTk5eamp2gnp6goaKjo6KkqKusrrK1ubq8u7u8vb69u7q3s7Gvq1KlU6i1ube0tLK3t7ZVVFZXVlVTTk1NTk9Sr1BOUFRVVVZUU1FSUlOFVBpSUVBQT1BQUVBQT09QU1RUUk9OUVBQUVRUVYRWgFdYXV9fY2VnbHJ1dnl4dHN0cHBvcnR0dHqCh4WBhYWGh4mGh4WFhoiMjpCTlZiXl5mbnqOmqquvtLm8wMPExsbHyszLx8jJysnHxMG+u73CxsjKy8zMy8rGwb65t7a1tbOys7K0t77DwL29vsHGy83S1tLT0M3O0dPT0s/Nzc7RH9LV1tbY2dve4OTm6/DyfYSLkJWfqK6vr7O0trvAxMSEwRfDwsbKy87U1dTZ3Nzc29vc6PLk5O7+ioBowmhpbndvZsbJxsLBZm58hH1xcGa8tbO7aMrDvMVlY7Ovrq6xtLK3v8G/urSysbG3wMfQb3N0eXt8fop23dxwcHFycs/Bu7m5wMO5q6SoqampoZuampqdnqOkoZ+bmZeVlJKTkpGWm5KQj4yKiIeKkIeDgYCChIOHhoaIkJeNjICFg4J+e3l3dXNzcnN2dXZzdXZvcHPb09Zub9rQzsjIzthta21xbMvGxcHAvLKsqKOem5qWi4aEh4qFhoyVlpCWkoqEgIB9eHh5eHd2dOLf3dvY1dbX2tvb3+Hi8oCBgIF/+fX09vj/gIGCg4aHiYyLiY2SlZOTkI6SkY+OjIuHhkCEf356eXvq2NPOy8jK1tfU0NPY2NHNzNLT1Nja2tfZ3t7c29/g3uDk4d/e3d7b2dbTz83MzMvJyMjGxsPCwMC/hL4Ivby8u7u8uriFuQm3t7a2tLW1s7KGtFiysrKxsK+vrq2urq2trKupqamnp6ampaWlpKOfnp+enZ6enZydm5iVlpWSkpScnZeboJ+ZmJKRlZKSk5OQi4qIiYeEhoSEhIWDgoODgoKBfn59fn19fX5+hH+EgAaBf39+fn+EgICBg4SEhoaGh4iIh4qNjY6MjI6PkZGXmpufnpybmZibnqKlp6iqp5yZm5+foaqys66zvr20rq+2uLTBurXDb3Jt08/FwL3AxMrOaW5xcnd2dXRta21tbXBxdXl6eXt6en18fX19f4GCgYB/fn18foCAgoKBfXt9fn5+fHl7fH19e156eXd4e3t6d3d5eXp6fHp3d3d5fH5+e3t9gIOBe3t6eXd1dHFt2m5sa2ttbm9u2NDOzMnIycvOzc3Qz8rIyMzJyMW/trWysrG1trW2uru7vb/BwcDAwb/Bv729vLy+hLwmvr+/wcHCw8XExMTDxMRiYsTDYWFgYMC+vLq6u7y9u7q3tLCtqamEqDSnqKmpq6ysra+wrq+vr7Gztbi6u73AwsXGxsjLzc7Nzs/Ny8nJZMdjyc7R0dDPztHT0mZlhGYBZYRiB2NkzmNiYmWEZgRlZWVmhGcIaGhoZ2dnZmeEaA1naGlqa2tqaGhpaWlrhG4gcG9vb3F1d3d5e32BhoiIiYiFhYeEg4SGh4eIi4+SkI+Ek1mVlJWTk5SWl5ibn5+hoKCio6asrrKxs7m9wcPGyMrLy83PzMrLzc7Oy8jHxMHDyMnMz87Q0dLPy8nGxMLBwMC/wMHAwMPJ0MzLzMzO0tfa3eLe397e3uDh4YTfPODk5ejp6uvp6+7w8/X4/P+Ch4yQk5uipqiqq6urr7O3t7W1s7S2tbW4ubvBwcHCxMTDwcTExsvDxMfMa4BBgT9AQEJBP3x7e31+Pz9DREE+Pjx1dnZ5P3+CeXQ6O3h6eXh4fHp+hISAe3p4dnZ+ipKbUldXW1pXVVpXqqdUVVRTUJaNjpCPlJePiYB8e3h8e3l3d3l8fIGDgn57eXx8e3t6enx6eXt8fn98e3l3dXRycWxoaGtsbXBzd3p4d4BzcW9tbGpnZmNgYWFhYmNiXlpZWlqyratUU6qvsbW1tKxTUE9NSZCRkpGPj5KNh4OBgn97dnFwdHdzdX+Nj4eQin91cXBsaWtramtrac/NzszNzM7R1tjX3eHg7X+AgIB9+PX09vn+gIKDhIaHio2NipCVlpiZkpGWlpWUk5KMjICMh4WCgYP2493X1dTX5urn4+rw7+bl5+3u7/X29fP0/Pz6+Pv9+fv8+/v49vf29PHy8O7v7u/v7uzr7Ozr6ejn6Orr7Ozp6Ojo5+bn6ero5ubk4+Lj4uLh39zd4OHg4eHh4OHg3uDh4d/e393b19TSz8zLy83MycjHxsG8u7u5uIC3trW0tbSvq6yno6KnrrCmq7Kxq6mjoaeko6SjoZ6am5ual5iWlZWXlpSUlZSUkY6Mi42NjYuMjY6Pjo6PkZKRkI6Mi4qMj5GQjpCTlZeYl5aVl5iXmp6fn5qZm52enaKlpaino6SjpKets7i9u725qaOorq2vvszMwc3b3M7KyoDV19Hh2NHmhYaA9/Ho4N3i6vP8goeOlJqXlZKFgoaEgYOFi5GSk5KPkZSSlZeXmp6ioaCdm5qZnqOnqqqln5ykpKOmo52gpaeloZ6empyjo6KbnJ+eoKCjn5qWl5yjpqOdmpuepKehpKKfmZCLiIL/gICAgoaHh4L78uzl4d7e24DZ1tfc4OLk6ezn6eHQw723t7a2tre5ubi4uLy+v7++vr/AwL/AwsPFyMrMztXc3+Xr8vX3+v39/Pv/gID//4KCgoH9+vb08/Pz9PHu6ePZ09DPzcvIx8TFxsHAwMHBw8bFxMHBwsTGxsjKyczP1djV1tzn6uns7fHy8PiB/4D/90H29vr7+vr8/oGBgICBgYKFhoWEgoH/goOBgICBgoOEhYWGh4iJiIiIiYuNj46Ojo2Oj5CPjo+QkJGSk5STk5SVlIaVCpaVlpeXmJiYmZqElgWYmpeVl4SZRpiZl5eZnJubl5eZl5aUlJWWlpeYmZqbmpydoKKjpaWlqKytsLO1tre2uLm3tba4ubq6ubm4trW4ubi4uby+wMLAwMC/v8CEwVjCw8TExsvLysnJyczP0tbZ2dvc3N7e3uDj5OTm5ujr7e3w8PHy8/T19fT09nx/goKFiIqJi4qLi42RkpKTk5KRkZCPkJCPkJOTkJCRkJCOjIuHhYmJh4FAAoOChoOFgoiDhIIBg4SCgoOUgomDgoKFg9GCBYGBgYKCh4GFgqeBj4CFgYaAn4H/gP+ABoCAgIGBgYmA3IEBgIiBwoAEgYGAgISBuYADgYCBioCNgQGA/4G/gauCAYMCAgQAgIyFkZWgl/Dp+ZGVkI+SoLCRko2Egvb0/IeSkPbvg+vk3dbc4ufm6fbw/fvp5uHg4+Pl9IudiYeDhpGOsruYhIH7/ID/8+nu19vm8eHY6Orwge/Fv9LTvrGvs7GxsK2sq6qpqKinrLy7tbGsqKCZn6ihl5iYl5eamJiUlZasq6SegI+Ih4aCgoD+/vv49ff5+Pf59vHv/f72+YH99PH67+Tk3trY4en09Ovn5eTg3trUzsa7sKKZko6OkI6QmZ2kpp+TjIiHhISGiIyOjoyIhID89vDq6OXn5+jr8fOBh4aFhYSBgPz/goSB/f349vt9f37v6fN9g394f4aJiIeEg4KAgHx5dnd1dHLd1tDMx8TBvry+wb22sLK0urvIwrays7a6xMvMyby2sLi6wc7NxsPAvLm3trW0tLOwrq2sqqmop6empKOjoaGjoqKhoJ2enpycnJ2dmpmYmpubmpmZmpiYmZiYl5iWlpSVlpeWlZSUlZWTkpCRkpOUlJKUk5ORkpOTIZKRj5GSkY+MjY+OiouNlZicnp2XlpGTkZOUj4mFgn9/g4WACH9+fXx6enp5hHd+eHd3eHl5eXp8fX5+fXx7enp6e3x9fX17fHx+f359fX+Bg4KDhIaHiImLjIyMj5CUlpeYmJeYl5eYmJqcnZybmJmbn6CjqaOhpKOlpKmusauqsLS2tLW8vbuztrS0sa+us7Cyt19eXl5gX19fYGNiY2ZoamhpamprbG1sa2prhGwPa2toaGlqa2tqaWdmZ2dnhGaEZYJkhWMfYmNiYmNjY2JiYmNjY2VlZmlrbGpnZmRjYWBfX2BiYIVeQV9eurm3tbe8v8DBwsvQ0sm9t7Sys7Gvr62ur7Gwtbe6vsbMzM/T29zZ3Nzd29LSzcrFw7y5uLq7vLu4tVdVU1JShFEHUlFQT09PToVNTUxNTEyYl5WUlJSSk5OUlZaYnJ2dnqCgoqOjpKSjpaeoqq2vsra6vb6+vry+wL64trKuq1JSUlBPUFNWsra1uLm7tFhWUVJXuldNT1NShFERVFdXVlRUUlJSU1NTVFNTVFSEUyhSUlFRUVBRVFZWVlVSUVBRUVJVVlVVVlZXWFtdX2BiY2drbW5yc3JyiHWAdH6EhoiJi4mOj4+Oi4mPkI+SlZeXnaGfnJucnqGkp6yxtLi8v8LFxcTEw8TExMXGxsTBv768u7q8vMDBwcHDxMG8u7q6t7W1tLSytLS5wc3PzMnEw8bJz9XZ2djZ19XU1dXU1dXW1dTV1tbV19nc3d/l7fB8gIaOlZuiqayztbYjury+vsLCxMfIxs/V0NHY4ODk4uLj49/b3ODn5eLf4d7i7INnbGpvcXl0xMDHbnBtbW54gG9va2ZlwMDFZ21svrtkuLKvr7Ozube2v77Ixry7t7W4uLjAbHhvcW9xdXCImIFzcNjZa9nOxca3u7/Dtayzub1kvKOfqqmdlpWXlZWWlpWTkpKSkZGSmoSbgJiQjI+UjYaFhIOCg4SGg4WIl5aQioF+fXp5enjq5eTk5ODd3NvZ19XY4uXg3G/Z09DW1tPRy8nIy83Rz8jHx8S+vLaxraeel4yDfnuAhIKFkpmiopuMhH98eXh4eXt9fn56dXPk39zY19XZ2dzi6+1/h4WFhIJ/fvr8gYOA+/36YPj8f4KB9uv3goiDfIOJj46MiomIhoOBfX98fHrk2tXRysrJyMbHz8nDvb/CyM7a0MTAw8bN1+Dg287Hv8rO0uDf3dnX09DOzczMy8rHxsXEwsHAwL++vb28u7y8u7u8uoS3Dra3tra1srCysrKwsLCxhLAFr66urq2EqxSqqqmop6WjpKKkpKWlpqWlpKOhoYSggJ+gnp2al5mblZGTlp+jqKqpoaCcnZudnJiQi4mHhomHh4eGhYSEg4KBgH9+fn19fH18fH1+fn+BgoSFhYOBf319fX+Cg4SAf4GCg4OCgYGDhYaEhYeKi4yNjYyMjI6PlJeXmJeYmJeYnJyeoaCfnZ2bnaOkqa+pqK6srKqvusC6gLi/w8LAwsrQyb3AwcXEwcHOzM3RcG5tbXFwb29ucG9xc3Z3dnd3eXp9f317eXt9gIKCgH14eX1/gYODgX19gYCAgoOCf4GCgH58e3x8e3p6eHp5enx+e3p5eXt8fHx7fXt8f4N9e318enh2dHFwbm5tbW5vbmzQzMjHycnNzs/NIdDS1NPPysfGxcC4tbKxsbCws7S2u7y+wMPGycjIysrLyYTEGMLBv8DBxcfLy8zOZmZmZWZnZmVnaGZmZYRkE2NiYWFhYmFhwb63tLGurayrq6uErEWur6+ws7Oys7Oztba2uLq8v8DExsnJyszNzs/O0NHPzWRkZGNjZGRmz9DP0dPU0mhmZGVn1WhiY2VkZGVkZGRnaGhmZ2aEZ4RohGkUamloaGloaGhpa21sbW1sa2tqamuEb2BwcHBxc3V4eHp7foGEg4WFhYaIiYqJioqKiYmPk5KTk5WUlpiYl5aWm52anaChoqeqqKWlpaaqrK6ytbm+wcPGycrJycfIyMjJysvJx8XEw8LAwsLFx8fHyMnIxcPDxMKFwFjCwsXL1dfW09DP0dPY3eLi4uTj4+Lj4+Lk5OPj5Ofq6Ojq7e7v7/P5/IKFi5CVmZ+jpaqtr7Szs7K1trm6urq+wsDAxMrM0MzJysrGxMXFysjDwsTCwsRogEE/Pz9BQYB9ej0+PT08P0JAPz08PHp5eTw+PXZ0OnNzcnV3d3h1cnV2gIF+gHx6fHx6fURIS1BRUE1GT15bVVWlokyYlpKPioqMhHp0dnd4PXl3dXd2dnZzcnBxdHh3dnZ4enh3dnJxd3uAgYB+e3t4dG9raGlnaGxvcnZ5dnJxgHFxb21ubmvOycnMzsG3ubmxra+4v8XDt1aqqaaotby2ra6uq6WcmJidnZaTkYqEgn97dW9saWhxd3Z7jJScnpiEfHdxbGpoaGlrbm1raWjPzczJy8vS1Nfe5+p9hISDgYB+fPf7gIKA/v75+f2AhYT59P6Ei4eAho6UlJSSkI6MgImHg4aFhIHz5+Hb0tLU1tjZ49/X0NLX3+P159jS1tvm8Pf48+jh2eDl6fT19fPx7u7u7ezr7Ozr6+vq6enn5uXl6Ofo6ejp6Ofm5uTk5+fl5OTj4uDd3Nza2NfZ3N3c3NvZ2dvZ2dnX1NHQz83LysjFxsfIycvLy8nHwb+/vr27Lrq5ubi1sq+rq6ynoaSns7W5vL20s6+urK+vqKCdmpaWm5mZmZeXl5aVlJGQj42Ei4CMjIuKi42PkZOWmJiVkY2LiouNkpSVkIyPk5OUkI6NkJKVkpKUmZubm52dm5qanKGlpaSioqSmp6qssbKwr6yqqbG2tr7Kv7vEwcG/yNnj3Nrh5ODc2+Xn4dTY3N/h5eb5+Pb+jImGho6LiIeFh4SFiYyNjIyLj5KXnJiTjZWZnYChoZ+akJSdo6arrKqfo62nqbGzsqmwsKyooqGkpaKgn5udnqGlp6KenJyipKKgnZ+ZmZ6poJ+jpKGem5KHg4GGhISHh4SB9ezk5Obk5+ro4dvZ2+fs5t/m59zEvru5uri1t7i6uri6ur6/v7++wMHCwcHDwsTJzM3S2OHk6e30+xOBhIeHiIuLiYyOiomIiIiJiIWDhYJKgf726eDb2NTSz83My8nIx8XFxMXJzMrJyMnMztDQz83O0dXX2trc4ePl6u30/P38gYKBhIOCgYD8/f37/P7/gICDg4D8goeGhISEgweBgYKDhYaGhIcbiYmKiomJi4yNjY2Oj46Pjo6Oj5CRkZKUlZSUhJUDlJWVhJZHl5eYmJmZmpiXlZWZm5ybmpucm5qZmZiVlJOUlJSVlZaWl5iYlpiZmpmcn52bmp2en6Cho6Wmqa2usbS1tLW0tbWztbW2t7eEtWy0tbOztbi5ury9u7u8vb2+v8C/v8DAwcPIysvKycnKzM/T1dbY2trb3d7f3+Hi5OXl5unq6uvr7vDx8vT2fH5/goSGh4qLjY2PkI+PkJGRkZOTkpaYlZOXmJeZlpWUlJKPj4+RioSGiYaEgECGg4OCjIMJgoKCg4ODgoKDlYKNgwOCgoONggGDtoKRgQGCtoGMgIiBBYCAgYGBhYAGgYGBgICAlIH/gP+AkYDigbqAmYGvgIiBh4CFgQGA/4HDga6CAYMCAgQAgIGMqreYmo36+IuVkpmrnaWnhImEhYmLkJ+stZSSkI6MgP6LjYWFi6CqmPXm39/h5+/1/YqYkZj/gIKJmLiljoKCiILx54WsiIDc0+LwrsyQ/ejWzcjU9M65sbCytbCur7CssMLL07+9sbWyrKyssKyhnp2bmpydmpOWmpqblIyGgISFhYSDgoD//vv4+ff19/bx7uro7fr8+/Hr7e3j2tfU0czHytPZ3eHh5erh1MnEwr2zq6GYko6RkpWXnJ2ioZeOioWEhoeIio2SlI+LhYGB//r18e3n5OTk5ebyg4iEgfv29v2Dg4D28+ni2tTO0Nnr7d3j7nt/hIeHhoJ/e3t2V9zX4HV0cnLc2tHJxsTAvLq6uLa2tsK7ubO7sKuprK+zs8XFu7W3uru9y8/GxsG/vbm4t7W0tLKwr66sqqioqaenpqOioaKjo6Sgnp2dm5ucnJ2dmpmYmoSZgJiXl5aXl5eWl5aVlZaWlpWTlJKSkJCQjo6PkJKSkI+QkZKTk5KUk5GPj42JiouLjpGSlZaYoqOfmZmWko6LiYiGhYOCg4SCgYGAgH59fnx8enl4d3d4d3h4eXt7eXl6fH18fHt6eXl6eXp9fX17eXl6fH5/f35/f3+AgYGEhoeJQ4qLjZCNjpCSk5OSlJeXl5aWlZeanJqWmJyfn5ygoqaioKSmpKSqsq62u7u4v8HAwLqyt7/Cu727u73Bv19dXmFjYmKEYwRmZ2dohGkfa2xsa2prbGxubmxqaWhoaWtsa2poZ2doZ2ZnZ2ZmZodlImNkY2JiY2RjZGNhYWFjY2VnaGpqaGZmZ2ZlZGJgXl9hX12EXha8t7W0tbS4t7dcu8DGx8C/vLm2s7CthKsqr7CxtLe5urzAxsvQ1djb3eHf397f39zZ19PMx8K+v8K9uLFWVVRTU1JShVEGUE9PT05PhU4UTU2ampqYmJaVlpeYmZqbnZ+hoqOFpgukpKWlqKqsr7S6vYS/OsDBv7q2WFhYU1FWUlFSVLC4wsO+ube1slZQUVNXUU5OTlFSUVBRUlJWV1ZVU1NUVVRUVVVUVFNSUVGHU1xUVVZWWVpWVVVVVFVVVVZWV1dZWl1eYGJiZWhsa2pqamx5eoGCfXt9fn99gYWGiIyRk5ORkJKSkJCTlpucmpqcoaCfn6Gio6aprLCytry+vsDAv768vb2+wcLBv4S9D766t7e5u72+wMTAube4uIW3Wba6uL7M1NfX0czN0NHT2uHk5ODe29vZ1tPT1NPS0tLT1tne4ubp7/R7fH+Dh4yTm52ho6SjpKyqsbe5ur/Bw8rY5ubo9/n39fDo5d/Z3+Tn7evh4eDf4ubwgGZsfoRxdG3FxGpvbnJ6dHh7aGtoaGloanR9g25sa2hnYMBnaGNjZ3R6b7y1srCwsbW3v2ZxcnzPZ2hqeZaEc21sb2rIw2t/aWa4tbe8f45txLaqpKGouKOXk5OTlZSRk5STlJufppyemJycmpmanJiQioiGhYaIhoODh4mKh4F+Qnx7eXd3dnPh4eHj6eDb3d/b1tTb4e/r5NzU09XTz83OzsnGx8TFx8fHy8vCt66opJ+WkIiBfHqAhYqPk5OYmY+FgIR8gHt8foODf313dHTn49/c2dbX2tzf4O2DiIN+9vLy+oGCf/Tx6OLZ0crL2e/v3+fzf4OJjIyKh4WBgHvk3up9ent66+PZzsrJxsLAwL+9vsHRycXBx7uzs7a9wsPb2szGx8vL0N/j3dvX1dPPz83NzMvLycfFw8LAv7++vr68vLy7Gby9vbu6uLW0srCwsbGvr6uqqausrK2urq2ErE2rqqqpqamoqKenpKOioaGgoaCio6OhoaKioaGjoaGhnZ2blZKVk5SYmZ2hoaKwsaqipKKemZOQj4uMi4qLjIuKiIaFhISCgYGBf358fIR9DH5+f39/gYWFhIF/foR8CYCCgn99fX5/gYSCgIOCgICBg4aHh4qJioyPjY6PkZORkZSWmZmWmJqbnKChn56gpKShpKmxqqWprKusuMTAyc7HwsfLzMzGwMXO1dHS0tLV1tdtbG50eXd3dXJycnR3eHh6eXt9fH18e3p7foCCg4B9fHp6fYCEhIJ+fX5/f4CDg4GCgIB/fn5/fX17Pnt4dnV4eXl6d3R0dnp6fYB/fX56e3x9fXt6eHZ0c3Nwb29ubW3X0c7Lx8bLz9Jq1dXW1tLS0M/LyMS+uLWxhLMutre5ubu8wMTIyMnKy87OzczO0M/Nzc7Ozs3O0NDP0M1nZ2ZnaGlnaGppaGhnZoRnTmZlZmVkY2LBv7q5tbW0srGwsLCvr7CysrK1t7a2t7e5uru8vcHCw8jKy83P0M/R0tTUaWhoZmVmZWRlZc7S1dbU0tHSz2ZkZGZoZmRkZIVlgmeEaIBnaGhpaGhpamlpaWhoaWlqamlpampsbW1ub3FvbW5ubW9vb3FwcXJzcnV2eHl6fH6AgIB/gIGNkJWVkI+PkI+Nj5GSkpSYmpmZmpuampyfoaWmpKSlqKmop6iqrK2wsrW5vb/Cw8XFxMPCw8PExcbFxcTDw8PEwb+/wMLDxMfKxgPCwsKFwVrCwsTCydPZ3d3Z1NbZ2d3h5+rr6+no5+bk4+Pj4uLi4+Tm6e3w9Pf6/YCBgoaLj5WYmpydnp+gpaSorbCws7a4vsfS0dDZ3t7a1M7Nx8PGyczPzMTEwr69v8RzPj5AQDw+PXh2PD07PD07PkFAQUE/Ozo5Oz5BPDk4ODc3bjc4Nzc4Ojs7dXh3c3Btbm9wOkJIUYlEREJPYlhQTkxLSZGNRklERYuFfnhAREB9dHFxcXByb25wcG5tbmxvcXRyampwcXZ0eX6AgIB/fHlzbYVrgGxucXNzcnBwcHFqZ2RjYb+/wcnSwrm8xb+0t8fS3NHFu7Kttb++v8LHycfDr6elpZ+hn5WOiYF7dnNwamdkZHF4f4aLiI2Qh3x3c3Fwb2xsbHFybWxpZ2nS0c3Ly8zP09jc3+uAhoB88e3w939/fvTy6uPa0s3S3vP15Ov4goaMF5CRkI6LhoWB7+r5hISEgfnx6dzX1M/OhMyA0NPl3NnV3dHFxcjO19fv8uPc3OPl6PT29PXy8O7s6+vs6urq6Ojn5ubl4+Pi5eXl5ufm5eXn5ePh4uDX09PW1NfX0M7Nz9HU1NTW1NLR0M/Q0tLT0s/MzMrIxsTBwMC/wcLGx8fDv729vr69vL26trWwqaSnpqSqrbCytbfCwb6At7e0r6mkoJ+dnZybnJ2cmpqXl5aVlJKRkZCNi4yMi42NjY6Pj5CTl5eWk46MioqLjJCSkY2JiIuNj5KSkZGQjYuNj4+TlZaYl5WWmpqbm5+fnJqboauqn6Wnqayysq2vtbm4tLvAx764vb6/xNXp4e7y4trg5ubl3tjj6fP0+PSA9fv8/oOEiJCXk5SRjIiJi46PjZCRlZmWlpaTkZaZnqChn5mXlpeco6urqKKgoqOmqq6yrK6rqqinqKqlop2fl5SVmZ2cn5mSkpmfnaKmnpudmZ2foaKdm5uYk5CNiImHhYKD/vPt6+Xi6fD6gf/38+7r7vLx6evk2c/KwMC/vLuAu728vL6/wsTEwsDAwcPExcXGx8rMztLa4OXu7/D0+fuAhIWGi46MkJOSkI6Mi4yNjY2LiYuJh4WC/vbs5uLg3NnX1NLPzMrKycfIytDRz9DT19ra2Nja29rd397j5ufl6O70/oKBgIaGgYOEgoH/+vn3+Pv9+/+AhoWCgoeJiYcuhIOEhYWFhISFhoaHiIiHiIqKi4uLjIyNjY6Njo6Pj4+Qj5CRk5OTlZWVlpaWl4SWhZcplpWXlpaTlZeWmJ2eoKCenZ2cmpmWkY+Oj5KVlZaWlJSUmJqbnJ2ampuFnRSeoKChoqSkqKytr7KxsbKysrGwsoW0b7O1tbWzs7O0tra1ubm4ubm6uru7vLy8vr/DxsjJysrHycvLztHV19nZ29zf4N7d3d7g4+Tm5+nq7O3u7/P0e3x8fX+BgoWFh4eIh4iKiImLi4yPkpOVm56enqGhoJ2bl5SSjY+Qk5SOiIiGgn99fIeDgoKYgwGCiIOJgoSDAYKLg4KChIOEgoODt4LJgYyAhIGEgIOBjoCLgYOAhIH/gP+AkoDfgYmAAYGzgJmBq4CKgYmA/4HGgbGCAgIEAHyjr7a/qpWJjpemtLe0sp6RjJOqpqOjtcTh896op66xsLTB1cq4tK22pJSLhPv4/Iyjsq6elZeQh4mPncLMyrmopq2vopGTodDdyMa2iYCam4mC99vq+dbGwr24ubq6u7i1utDn3dnIvbi4tbe7s62nqqmtraOalpSYmJGMhIuAiYmHhYWFhIOCgf/9+ff4/PXw6+Xi4ePj4Ojo7vvx4NfY3NXT1NHV1dHZ3tnRyL/Au7SwqKCYkY+LjpaVkI+QkJKSjY2KjZKSkZOPjo6Oi4uHgv/38ufg39nV09Lg8Pf07ejn6vOBgfr18uXZ09HP0+Hven6AhIOHh4SBfufX191M2c3N083Tct/d2M/MycO5trW0tra7ubGvqqqinp+zx8i/ury6wMTJyc7PzMjGw767ubm4trSzsrGwraupqKmpp6alo6KhoaKjoZ+enYScDJ2bm5uZmJiYlpiYmYWYD5mZlpWVlZOUlJOSkpGRkISPNI2Njo+Rj4+QlJOQkY+PkY6Mi4iIh4qRlJabnZyVkpGPjo6MioiGhISEg4GCg4J+fn19e3uEejN5eHd3dnZ4eXh4dnV2dnd5e3p4d3h5enx7fHt7eXp6ent+fn5/f359fn+AgIGDhIaGh4qEi4CMjIuMjo+Qj4+Qk5OUl5iZnJ2cmp6gpJ6bn6CcnaCnpaapoqewu72+vrKtra+3ure3trm4uLlfYGNkZGVnaGdnaGhoaWppamxtbWtqa2xra2tqamZmZmhsbm1samhlaGtraGZnZmdoaGhnaGdmZWVmZ2RlZWJiY2NgYGRkZWZnaAJoZoRlgGZlY2FfYGFgX15cXVtbtre1trS3t7hdXWDHysTBwFyxsKytqainp6uurrCvsLGzt7m9wsfKzMzP0tLT1NbW09HRysS+uVm0WVhYWFdXWFhWVVRTUlFSUlFSUVFSUVBQT05OT09PTU1Om5qamZmanJ2eoaSnqaurqqqoqKmpqaeoO6uvs7m9xcnIx8nHvr67ureyr1RWrrW4tri+wsO7uLSzsVdUUFFST1BRVVdVUlFSUlZWV1dYWFVTUlRVhFYDV1dVhVeAWFdWVVVYW1xbWFhaWVdXVlZXWVpaW11eX2BhY2ZnaGpsbGttd3aBg4B/hYaGg4WJi5GWmJWUkpKUl5iYmp2fn6Kipqemo6KkpaeorLG0t7m8vLy6u729vLq7vL7CxcbKy8O8u7i1tLa3vL7BwsO8ure2tbW2t77FzsrGxsbN1NZQ09fZ1tne3dzc29jV09LT09TU09PU193l6vHy8fR/h4uOlJebnZ2doKKlpqestLW5vMHDxcfNz9vq9IGIjImFg/2CgPH08+3u6viBg4SJjY+AeICFiX1xaWtweICCgYB1bGlue3p4d3+JmqWaeXd8fn5/h5SNgX54f3RrZV+6t7llc316cm9xbmZnaXGLkI6Genl9f3dub3WRmo6Mgmllc3NmY7qqtLmlm5mXl5iYlZSTkZWisKmnoJyZmpqfo56ZkpeWmJiPiIWDg4KAfnx7eXmAeXh3d3Z2dXRycuXj4+Pp7t/S0tHN0dXR0NbW4fLl29XV19DQzMbFxMXLysC3raWjnpaSjIeBfn56fouJgoKDhYeHg4KAgoqHhoaBf39/fXx5dOPc2dHNz8zGxcfX6PHt5uLg4+9/f/n07+PXz83K0eL0fICDiIiNjYqFg/Da3OZm3dHT2tfiee3q49rV0sm9vL29wMDFxbu6trWppai+29/Sy87Jz9jd3uDi4d7c2tXSz8/NzMvLy8nHxcPCwcC/vr69vLy6ubq7urm3tbSzsrGxrayqqqmqqausrK2urq2srKqrq6qohKeApqalo6KhoaCgoJ+gn5+hoKChoqGfoZ+enpyXlJGPkZWbnqKoq6mhnpyYmpqXlJKQjoyMjIqLjIqKhYKCgYB+fX+Af358e3x7fX99e3t6e3x9f39+fHt8fX5/gH99fXx9fn+AgoKDhYOAf3+AgYKCg4SFhIWIiomIiYuLioqMkZCAjpCRk5ean52coaSjoaOnraGfpaeho6qxraywp6y5xMjHxru1uLvFy8zMysfFx9BscXZ7eHZ5fXt+fnt5fX5/gIGCgH59fn+AgoCAfHV3dnh9goSDgn95gIaHgn+Af4CDg4OAgH98eXl+fnl5eXZ3enhvcnl5e359fX18fH5+enuAe3p6enh0cXFxcHBtatPT0M/N0tHUa2xu3d/Y19lrzMnDwL21sbGysrS1tLe4uru9wMPFxsjIycvLzM7Q0dLQ0dPR0NFo0GhoaWppamtsa2xtbG1tbGxra2pqamhnaGdnZmZlZmVkYcG9u7i3t7Wzs7O0t7i5ubu9vr7BwsTCxMMrxMXJzNDR0dHS0dHS0tLRzs1lZczP0dLS1dXV0tLS0c9oZmVlZmVmZ2doZ4RmgGhoaGlpamhoZ2hpampra2xsbG1tbG1sbW5ubm1vcnJycXBycXBxcXFycnNzdXd3d3h5en19fn+BgYGDjYuTlZOSlZaVk5SWl5udnp2cnJ2eoKChpKapqausr6+uq6urrK6ws7W4vL7AwMC/wMHBwsHAwcPGycvOzcnCwL69vb2/Z8LFx8jIw8LBwL/AwMHHzdTRzszO0tna2tze3d7k5OPk5OPg39/h4uLh4ePk5+zx9vz9//6CiYyQkZSYmpqbnJ6foKGkq6yusrS3ubvAwsrW3XBydHNxcNxubNLV0czLyc9paGhqbG2AQEFBQkA+Ojo7PT4/Pz48Ozs7Pj49Ozw9P0FBPDs8PDw7PD8+Ozk5Ozc2NTRnZmU0Nzk5Oj1CQTk2OT1GRkRCQD4/QkE+Pz9DRkVEQ0NDQUM+OW9sbW1qZ2dpbGxqZ2VmZmdqa2xta3FwcnJ7fXh2cnZ2eHl0cHFtZmNoaWdlY2MGY2JhZGFhhGCAwsLHx9XavqqvsrC7wLm1t7rJ29PPzc7LyMS8s6qora6qm5KIg354c21qaGZmaWdtgXtxdHd2fHx3dXJ4fnl1dXFvcG9tbGlny8jGwsHFxMDAw9Lj7eri3dzg7H1+9/Pw5djQz87U5vmAhIeNjI+PjoqH9eDj8Oja3enk8IH7+PI95uPf1snGx8nOz9TUy8rFwre0udLw8+bd397n6/H09fX09PPw7e3u7Ozr6+vq6Obk4+Tk4uLh4uLi4eDf4ITfFt7e29fV1M/IyMnKzc/P0dHT1dfT0c+GzEfNz83LycbDwL++vb2+vr69vsC9vb3Bvrm7uLW2sKqno6KiqLCytbu9u7Wxrqyrqqmoo6Ggnp6enJ2dnZmVk5KRj4yLjY6NjISKgI6QjYuIiIuNjY+PjouLjI6QkZCPjIuJiYyOjpKTlJSSjImLjYyNjpCQjoyMj5STj5GSk5KSlpyblZmcoKWpsK6vtLi2tbq7wLKwuLq0tb7HwL/CtrvL2Nnb2dDP0dTh7Ors6+Xj5/qCipKcl5WYmZibm5aQmZqdoKGhnZiYm52fP6OgoJmMkJCRmKCmp6WhmKaysqunp6Olra6wqqmmoJmaoqCZmZuUmZ6ZiI2cmpyemZuen6GkpZybnJ6ioJqQioSMgIaA/Pv38+718/qBhIP//vb8/4Dx7ufi4MvBwL6/wcLCxMXDw8TJycbHycvKyMrLz9HS1drf6O3y+oD/gIKFhYSGiYuOkpaZm5qXlJOTkpCQjY2Ni4uKiomIh4WB+vDo4uDd2NPNzczKyszN0djc3eDj5eTp5uDb3uDg4N7e3uLuJfP0+fn6/IKA/Pn3+vj29fP2+Pn6/4CDhoWGh4eHhIODhoaGhYOFhAOHiIqEiQWKiouMjYSOD4+Qj5CSkZKRkZKTlJSVloiXgJiYmJeVl5iXl5aXmJmanp6foaCen5uYlJWVlpeZmpiWlpaXl5ibnJ6cnJ2eoKCenZ2enqCfoKOlp6mqq6ytrq+xsbGvr7Gztba3uLW0s7OysbKzs7S1tre4uLe2tre4uby+wL/AwMDExcbFyczMzdDR09PU1tnb3d3c3t/g4eToOurs7fDx8vJ6fX+AgYKDhoaFh4eIiIiJioqKjI6PkZOVlpmen09MTE1MTZtLSZKWk46Mh4lDQD49Pj6sg4OCo4O4gsiBk4CCgYuAioGKgAGB/4D/gJSA3oGIgIOBhYABgaaAAoGAoYGqgIKBjYD/gcSBnYKGgwOCg4OHgoaDAgIEAIC4w9jNv6yurau8z/SD6unS0MfigoP14Njk9//bx7uvpqy75uznwLmloaWrs7CwtLjO9evdx9DL1+nskZ2Tg4iUiPbk7/3O+aHGwKOW7rnnzZ+VhIX98v772MrJwcvTz9Hh6Ozo1ufg29TKwL2oo6KfoaKpqamenJeRkI+OjIuKiYCIhoWEhIWEg4KBgP769/b59vHy7+nn4t3b3Nzh5+7m497j6N7Z1drh39rY2c/Gvbeyqqijn5mVlZKKh4iGhoeHjZCSlpWam5qYlZSTk5SRi4WD/fft5uDc29na2OT9gYL48u73/4D8/oD25uDe4eny8Op7gIOIiYaEhYLk0tHc3VbWzsrIzNHS0c7MycPAvrq5ubi7vLasqqeqqKCcmJ6rvMS2r7azt8LKzs/Nxb27uLu6uLe0tbSxr66trKinqKmopaSjoqKhoaCgn56enJybmpqcmpqZloSXUpiamZmYl5iZmJWTk5OSkpCRkZKRkY+Pjo6NjIuMjYqMjpCQj4yQj46Ni4uKiomIiYuOjYuKi4uIiIeIiIeGhoeHhYSFgoB/fnx8e3l5enh4dneEdn91dHV2dXR1dXZ3eXp6eXZ0d3l6e3x8e3t7enp6e3x9fn59fH1+fX9+fn5/gIGCgoWFhoaIiYmKiYqMjIuPkpGTkpSZmJmdm5mcoKGjop6foZ+mrKmtpqWnp6eoqqytsra1tbOvsbe+x8VjZGRlZmZnaWtqaWdnamlrbGtsbWxrhGoFaWhmY2SEaRBlZmVlZ2hoaGdnaGppbWtqhmgKZ2dmZWRkYmJkZIVjAmRjhmVUZGRlY2RkZWNgXlxcXF1dW11bWlpcXV5fX19hYmBeXbm0rqyop6mpqqytrKyurK6xtLa4vsLExMbIysrLztDPzdDSysLBvl1cW1lYV1dXVlVVVFRUiVMEUlJRUYRQgE9PT05OnZubm52dnqOmqKysq66sqqusrKuopqivsra9xcXJycnGwr/Bwr67urKzusbMzsvEwLu3tK+uVlVYV1tZVlZXV1ZVU1BRVlZVWFlYWFdWVlZXWVtcXl5eXV1cW1paWldXVldaXV1dW1tcW1pZWVlbXF5dXmBfYGBhYWRngGVmZ2hoanF1e3x+iJSRi42PjpKYnaCgnpiXmJiboKKipqiqrLCvrq2rqaurrq+0t7q9vb7CxMXDvLu6ubi7v8TGx8bAura1tLa3uby9vr+/vbe1tre5u7/GztDRzsvIy9PZ2tzf5Ofp6efh3NvX19jW1Nna2t3i5ezx8/N8f4aKMo6Wm5+foqKjoaGhpKamrbGytbm9v8LHztLh/I2OgIWJk5mao6abm5aRl67Nx7yroZqigIOJlpKKf399fIaRpliipZOSjpxYWaiZlZ+qq5qNhX14eoObnZuEgXVydHh8enp8f4yloZePko6Um55fZmFYW2Jbp52iq5GnaX97aWSliKGSeG9kZL62vbynnp+bn6GfoaqvtrWqsKqmoZyYnJKOjIqMjZOTlIqJgn99e3p5eXl4gHh3d3Z3enh3dXV16ejn5eXc2dfQzMjDwMHDwtTj6ODe19ne1dPR09XVzMPCt7ClnpqSjomGgoGCfnp4eXd5enuChYeLio+UkY6KiIiIiYZ+eHXf2tXOy8vJyMrL2fV+f/Hr6PP8f/r7f/Xm3dre6fPx6n6EiI2OjIqKh+vW0uHhgNnTz87U3d7c2tbSzcvGvr/CwcPFvrOzsrOxpqKiqbvN1cW/w8HF1N3f4uDa0s/O0M7LycjLy8rJxcTDwcHAv769vbu6ubm5t7e2tLKxsK6urquqqqurrKusrq6vrq+trKqqqKelpaWko6OkpKWko6Cgnp2bnJ2cnpyen5+dnJyfgJyal5WSkpORk5OXmJeWlZeVkpGRkZCQj5CQjoyMi4mGhIKBgH99fX17fHp8e3p6eHh4eXl5eHh5enx9gIB9enl7fH+AgH99fn59fn5+f4CAgH99fn9+foB/f3+AgYGDhISEhYiJiYmKjI2MjJGTlZuXmJ2cnqSgnqCppqyopKOmTqWvtbK0q6mtr6yrsrW2vcTCwMC8v8fL2NtubW9ycHByeHl8eHRyeXp+gn9+fn99f35/gH98dnJzenl6enV3d3l9f39+e31+g4GGhYJ/f4R+gHt5enp3eXZ3eHR1dnd2d3d1d3l8fn59fHp4d3l5d3Vwbm1vcHFxcnJvbm5vb29wcXJycHBwbtXNxcPAuLe3trW4urq7u7u9v8DDxsjKyszNz8/R0tTU0tXZ19fY1Wptbm1ub3FycHBwcXBxcXBwcG9ubm5tbGxra2pramppaGZkPmLBwL25trS0tLe4u728vsC/wMHBwcPEwcHCxcfMzM3Q0dHQztDRz87PzMzP1NfZ2djU09PQzs1nZ2hpampphWgXZ2ZmZ2doaWpqamlpaWpqbW5ub29wcHCEb4Bubm9vcHF0dHRzcnNzcnJyc3R0dXV2eHl6enp7fX9+foCAgYKIio+Qkpmhn5qbnJyen6Cio6WhoaGipairrK6usbS3tre3tLSzs7S2uLzAw8PDxsfIx8HBwL6+wMPIy8rKxsK+vby9vsDCwsPFxcPBv77Aw8PGzNLU1dPRztLZ3lDe4OPn6ezt7Ofj5ePj5eTi5ebo7PDx9vv+/4CEiIuOlZmcnZ6eoJ+fnp6goqSoqKqssbO2ur3Bytp1dWxvdHp7fH+BfHp2c3eDkpKMf3p2eSlBQUNGRUNBQD9AQUYjRkdEQEBDIyJDQUBDRkVDPz49PDw8QUJAPDw5OIU3gDg5PUFBQkNDQD09PSIkJCMjJCNFQ0VHREkoLCwoKEtHS0hEPzs5cW5tbWpoaWlmZGZnZ2hwc3JsaWVjY2NucW5samxtc3NybG9pZWNhYGFhZGRiYWNkZ2lnZGVlZc/Q0M3Lury1qqSfm5mcnqO/2NbRzsPFysTExcTAvrGinZKMgIJ9fHRua2loaGxoZmdoZmlqa3V5fH59goaBf3t5eXt8eG9pZsfDwL29vb/Bw8TT8Xt87eXi7/Z9+Pl/9ubd2+Lt+fbvgIaLj5CNjI2M89zZ6+zj3dnY3unq6OXj3trW0snKz87R0sy+wMHEwLWxsbnO3+rY1NjT1+bv8/X17ufkOuXs6Ojn5efo6ejl5OPi4uLh4eHg397e3Nvb29nY19fV0s/RzcnJy8vP0dHT0tHQ0M/MysnIx8fIx8aFxT7Cwb2+vLq4u7q5ubW2uLm5t7S4tLGsp6alpqOjp6usqqinqaimpaOjpKOioaKgnp+empeVlJGOjIuMi4iGhoSHLIaEg4aHhoSFhoiLjZCOjYiGiYyRkI6NjYuLi4yLjI2Pj42LiYmKioqLiomJhIqAjIuNjJCTk5SVmJqYlpyhpKulqK+tr7iwr7K9ur+7trW4tsTJxcS5tr69urzBxsvW4N7c29bY3+P2/4GBgYeDg4WOkpWOiIaPkpiemZiYmpmem56ioJmQiYqVk5KTjZKUl6CjoqGbn6Gqpa6tp6CioaChoZyZmpiSl5OVl5SSlJKAkZGRjJGWnaaooZ+fmJaZl46LhYOFiIuMjY6OioeHiYiGh4yMiYSIh4P78Ovo49DOy8nJzNLSz8/Oz9DS1dXX2NfZ2dna3N3e4uPm6/L5/P+CiY6Qk5ebnZudn6CgoKGgnp2cm5mXlpSUk5SUlJKSko6KhYD38erh2tTPztHPz9Ew0dXY29vZ3N7o6+Xa19rX1dTV1tne4+jo6Onp6/Lx7urn6O3x9PX5+v79gIKCgoCChYQehYeJiIWFhYSDhIWGh4iIiIeHh4aIiYmKjI2Njo+QhJERkpKSk5STlJeYmJeYmJeYl5eEmBuZmpiZmpubnJ2enp+enqChm5manJual5WWl5mEmB6anp+foKCipKWkpKOhoaKhoqOnp6qsqqutsLGwr6+ErgewsrS1tLKyhLE/srOztLSztLW0tLS1tre5urq7vL29vsHExsbJy87Q0dLV1dXX2dze4ODi4+Tl5ejr7e/xenx9f4CBgoSFhoaJhYYlh4iJh4iKjY6RkpWWlpVMTEtLTExNS0lLSUdHR0ZISUhGREE/QIyDAYSGg4KEpYOHhIaDhYSIg7eCx4GMgIKBhYAEgYCAgYmAiYH/gP+AoIDvgaeApIG2gP+BwoGfgpeDAgIEAIDU2t/T1tDe9Prr3ub3ipqelJaLg/mJg4iHjIjx9/fiwL3L5PeB3db6hIqTm52WmJmeq7S1ssG+29/T1/6IgPX01butu8unlPjU38S75K6UjYeF6eTq6uXf0MbEw8XGyMnT2snPwur488q6sKymo6agn5+in5mVk5GNjIyLiYqKioCIh4aEgoKA/v37+/v6+vn08vT18+7s6+rr6efi5OTm7v2Ehf3y5ejw7t/d4NfKubO1tKyjnpuXlpGMiYqPjI2MkJqmsLKso5ufpKKjoJmQhoH++PPq5N3Y19fX1N3r9uff397j7Pj/9ezo7u/2+X+ChYD17+jsfH18fe/s7uzhz0PKxMXAxdLTz8bGxcC9ur/Ex8O5raWkoqSlo6GfnJ2kqbCyubu5vbq/yszJv7e3ubu5uLa2trSxr62rqqimp6emp6WlhKQKoqGgoZ+cnJqbmoSZG5iXmJmZmZqZmZiYmJeXlpORkY+QkZCQkI+Pj4SMVIqJh4eHiIqLi4yIioqLh4aHhoaHiYqJhoeJiIiFhIeGhISEg4KBgYKCgoB/fnp4eXl4eHd3d3Z2dHPm5uVzdHNzc3V4eXp7eHh2dHV0dnl6fHx6eYR3BXh4eHl5hHp5e319fHx8fX+AgoKChIWFhoaHh4iIiIeIioqMjY6SkZGTlZSUlJugn5+dpZ+eo6CroqSnp6Sio6Wlqq+xuLm2tLe8wsrOzdZra2pra21saMjGZmlra2ppZ8zIx8dlZmdnw2FgYmRmZmZjYmVnaWpnZWZpa21ta2tqaYVoImdnZGNjYmFfYWBgYWJiYmBhY2NkZWRkY2NiYmNjYV9eXl6EYAFfhF49X15eYGBeX2BeXl9dWrCuraurrbKxsrKys7S1tbS3ur3BxMXIycvLy8rKy9HU2NbWaGVgXlxaWVdXVlZVVYRWDFVUVFVVVVRUVFNSUoZRTFBPn5+foJ+foKOmp6mqqqmpqKinqaiopqmrrbO5vLy/wcLCw8PCxMXHyMfFytbVzsW8t7e6vbu6t1dUUlVUVVpbW1pYWVtaWFhaW1qEWSZaWllaWltcXFtbXV9fXl9eXl5dXl5gX19dXFpZWVlbXFtcX2NkY4RigGNkZmdmZmlucHd7gH+CiJGSjouLjJCUl5ucm5mZmp6hpqurra2utrm6ur25ubi0tLe8vr2+wMDCxsbDvLq3tre6vL6/wMC8ube2t7m6u729vL6/vcDCwMbS08vP1NPT0c/P0tfc4ePl6/L2+Pj29vb6+/j17Ovt6Onr7fHze3+DBYeKjpKXhJksnJ2fnqClqamvsbW5v8PDw8bN1OTx/4yEgYWT//uChIiRkZWalqCXjpqxztqAlZealpiUm6iropugql5namNkXlqrXFlbW15cpKeonIqFi5qnVpeTqFhbYGRmYWJkZ291dXJ7e4qNh4mjV1KbnIp5cXqDbmKplp6Nhp+BcWxqabu0tbOxsKahnp2dnp+hqK2lqqO3u7Wdl5WTj42Oi4mJioeCgYB9fHp5eHd4d3SAdHNzdXh4ePDv8O3r7Ofk5ePi39vX1dPT1NLQ0tni5uv2gID47uHk5+TUysq+tqCamJWSjIeEgYB9eHZ5fn6AgIOOo7CupZiQl56cnZiPhHp15t3Y0cvHxcbHycjV5O7g19fY4On1/vDq5u3v9vuAh4mE+vXr8YCCgIL58ff26tRB0M7OxMzf39zPz8/JxcTKztTNwrSsqqquraqpp6amr7e/wsjJys7Hzt7e29LIyc3NzMvKysrIycjEwsLBwL+9vLuEuhC5uLe3trWzr62rqqqoqausha1Hrq6sq6qqqqinpqSjoaCgoJ+gn5+fnpuampqXlZeXlpeZmpeWlJaWlI+Oj4+Oj5KUlpOSlJOSjo6Qj42NjYuKiomIiYeGgoCEfSZ7enp5enh3dXXq6ul1dXZ3eHl6fIB/fX57eHl4eX2AgYB9e3p6eoR7g3yEe4B8fXx8fn6AgoODg4SDhIaGiIiJiIqJiouNj46QlZWXmJiYmZmdqKelo6ympquotKmprLCrqqytr7e+vsfHxMLFyNHa3drkdHZ1dXZ4d3Lb2HF3e3t2dHHd29rbcHN2ddZrbHF0eHp3cXN5fYGCenl7gIaFiISFg4CAgH9+f35+eSR0dnVzb3FwbnB0dHRycXR2eHt8fXt3dXR1dXJvcHJ2ent7fHuEeT94d3V2dnV3eHd2dXJry8W+vLy+wcLDw8PFx8nKyczOz9TZ2trZ2tnc3Nra3N3e3+JxcnN0cnN0dHRzc3N0dHSEc4VyZ3Fwb29tbm5tbWtpaGbHxMG/u7m4uLm5u7y7u7u9vbu8u7u6vLy+wcLExcfKycvMzs/P0NHS0dDT19nY1dLQ0NLT1NTUZ2dnaGhpampra2lra2tqamtra2pra2xtbGxtbm5vb25vcHGEcgVxcnN0dYR2U3R1dHN0dnd2dnd6e3p7e3x8fX1/gICBgoeIjI+TkpWZn5+bmZiZm5udoaGhoqGipqmusbOztLa7vr7AwsDAv7y8vcHDw8TExcfKysfBvr69vb7BhMRvwr+/v8DBwcLDw8LDxMPGx8fO1dfS1djX19XU1Nfa3+Pl5+vx9ff49/f3+/z6+fPy8/Dz9fb5/ICDhomMjpGUl5mZmJianJ2dnqGipaiqrrO3tba4vsLM1Nx1b25xeNnXbm9ydnV3eHV8dnN4hZabekVFR0ZIR0ZISEZFR0kmKCgmJiUlSCUkJSQlJUhGRUI/P0BBQiE/PkAgISIiIiEiIiUmKCgnKSgrKyosMRkZMTEsKikqLCgmTk1MRkRKREVEQkB9eHRwcXJxcG5vbGpqbXFwd3t6dnFtZWlvb25tbWpqamtnY2JkZGNhhGCAX1tcXF1ia21s3OLj3NfbzsvP0M7Fvbm1sLCzs6+5xtfa2tpvcd7Z09XRy7qqpJmSfXd0cnFubWxqamhkY2htbnBwc4GcqqOXioSMko+Qin91bGjMw765t7a3vL/BwdHg6t/V1tjg6fT98Orm7vD5/4GHiof8+u3zgoWDhP37/Poz8t/a19XO2evs6t7d3djS0tXa4dvOwLe0tru9uba1tbfAx9DU29zb3trg7/Pv5N3e5OnnhOaA5Ofn4+Di4uDh4N7e3d3c2tva2tnZ2dXPycXFxsfHys3Pzs7Pzs7NzczLycjGw8K/v7++vr29vbu7urm2tbSzr6qurautr7GwraisrKqjn6Kho6Omqaqko6WlpaCgoqOhoZ+dnJubm5qZlpOMiIiJh4WFhYSDgoGAgP7+/oCCgoOAg4aKi5GOi4yJhoSFiI2RkY+MiIeGhoaHh4aIh4eHhoWFhoeGhYaHiYyMjI2NjIyPkJKUlJKTkZOTl5qYnKKio6SkpKanrr28urjCuLi+u8W4u7/CvLm8wMPO19nh4t3a3uLu9vn0/YKDg4OGiYqF+veCipCRiYeB/Pj2+oOHjoyA+4CBh42TlpKJjZmepKWamJyjrKyyrqyooaKioJ6gnp+VjZGPi4WJh4WHjIuMiYiLj5mhnaCdlJGNjoqGhYiOl52enaChnZ6eoJ2ZlZWTlZeZlpiUjoLw6OLa2drb2t3e2Nvh5ebl6uzo7fb19PDx7/P09vXz7+70/YGGkZWXm59loaKjpKWlpKKioqOioqGfn56dm5qZl5iZmJaUkIqF//nx6+Ha1tXU0dHU0dPV19jX19TX1tLQ09XS09XS09XZ2dzd39/g3uDg393f5evv9ff19Pb3/YKGhoSFhYKBgYCCgoGDhYaFhQWHh4aGh4WIhIsEiouNj4SQDpGSk5WUlpeZmZqam5qZhZgwmZqbm5ucm5ucnJ+enZudm56fn52alpWVk5KRk5SWlpeYmJqcn6GipaapqqmprKiqhKkTqqqrra6trrCwsa6tq6ytrrCxsYSwV7GxsbKztLS1s7GysbW2tLW5u7i5urq9vb6/v8LFx8nKzdHT1tfW2Nvc4ODh4ePk5OXl5+vueHt7fH6AgIKDg4OCg4SEhYaFh4aHh4eKi42Nj5GTlpqbmoRLEkiPk0pMS0lHRkVEREVEREZHR42Dh4QBg4aEiYMEhIODg5SEgoWJhIuDtYKagYKCroGdgISBhICEgf+AtYCDf+uAiIGCgIeBhICEgQGA1YGjgKWBt4D/gb+BooKFg4KCj4MCAgQAgMTf9vPGwLjC3YGHio+D8oSAhoHUv8XXhJuxoJKnu7exsMLS6s2Xm8DJ0er7hYGEhYXyydr3jYj/+OXY6Orl4LzEt8vMm5Li+fH27I+kqPzP1u7t5u3u4/L/3dHW5uTjy8Ta2NnigNTBvLevraytrqytsauinZSQjYyMjIuKi4mHgIaFg4GAgP77+fj5+PT08vPx8PHu7ufj4+bj4d3b3d7r8ICC6+Li4tjb39jNx8K7u8PAt6uloZuZlJGNjI2Kkpadsr29tLKulp2urqiXjYeEg4H/+PLq493a08zIy9PTzMPCv8XQ5P/26+3094SIiIPx2MO8v8C8ube4tLG9z8C8gLWurq6ttMjT2NjY1tDOz8vDvLWqpqanq62sq6mpq7C6wsLFvrzBzcnHx8G4ubu8vLy5uLa1tK+urKuoqaioqKanp6ampaWmpKKhoqCgoJ6dnZ2cmpqZmZuamZiZl5eXlpeWlpWVlJORkZCQj46OjIuLiYmJiIaHhYOChoiHh4SFIYKBgYCBg4SGhoWCgH+BgoOBgIGDhYOCgoF/f3x9fXx7eYV4JXfu6+vs6enn5+Xj4+RxcnJzdHZ0c+d0c3N0dHR1dnd2eHd3d3iEeYB4eHd3eHp5ent7e3x8fX1+f35+gIGCgoKEhoeGhYSFhoaIiIqKi4yNjYyNkJOWmJadmpmanJybn6Cho6KipKOlpqmusbmyra2zub27wMTE0NNrcXV20s9qa2pramhmyMbIZGNjZGNhwL+/YWRnaGZkY2RpamtpaGloamxtbG1saBtmamppa2pnaGhlYmFgYGBfYLq6Xl9gYWJjZGSEYwtiYWFfYGFiYWNkY4RiBWFhYGBihGE8YF9fXbOztrKytbpeXl69vb+9vLy6Xl9gYWJhYmNlZWZlY2NmZ2hoaWppaWVhXVpYV1ZWVVZXV1hYWFdVhlYNVFNUVFNTUlJRUVFQoYSggJ+goqSkpaWkpKShoqSjpKasr6+ws7OztLa1trq8vb/FycvNz9LS0MfAvLq4ur69vru4WVdWVlVWsbO2ubu9XVhXWFpbWllZWltcXV9fXV5gX15fYGBgYWFjZGZmZWVjYGBfXl1cXFxeXl5fYmVnaGZnZmRkZWZmaXN0ent+gICBNoOIio2LjY6QlJaYnJ+eoKWorrO3t7e9wcXKyc3MxsG+ur2+wsC+v7++v8DAv7q4tba3uLq7u4W6bLu9vLy+wb++v8HBxMnN193d19fW19XS0dTY3uHh5+3z+Pv8/v+Ag4WDgYF++fx+fnz3fX5/goSHiYqNkZabnJyen6irrbS6v8G/wcXN09bU0NXb3+jo6e718uzw/4KHkJKLhIGGkIz4gpCOmnmNm6mnj4uHjZxYXF5hW6paWFtYlYqRmltndGpibXh3dHJ9hpWCYmR7gIaWoVVTVFRVmoGLnlpXo5+TipSXk5B6fnmFg2ZincrEyMFwe33Ir7XBvbe5t7S7w62kp7Ctr6Skr66xtWSrn5mYl5WUlZSQj5KOiIR+e3l4hHeAeHh1dHNyc3h48/Py8O7s6Ofp6+zp5+Lf3t7e3NjU2dre4OjpfH7n3t3g3N3bzLqzqqKgoqCZkYuHhIN/e3l5e3uEjJWwwb+toJmIlayqo41+eXh3dOPc19HLxsTBvLi9xsfAtrKyvcrg/vbp6/P7houMh/bcwbm8v7m2trm0ssJV08W/ubKysrG50uHp6ejk29re2c/GvrCrrK61t7S1tLS4vcnT0tTPzdLd2tra0crMzs/Pz87NzMrJxcXEw8HAv769u7q6u7y7u7q4t7a1tLOxr6+urYWuNK2sq6qqqKenpqelpaSjoqGgoJ+fnZyamZiXlJWWlZOTkI+QkZOSko+OjImKi4yNjZGSjoyEilKLiouLjIqJiIaGhYOBgH9+fXx6enl4eHfu7u7t6unn6Obl5+l1dXV2d3l5d+t2d3h5eHd4ent6enl5eXp7e3l5ent6eHl5eXt8fHx9fH19fn5/hICAgYOFhYWGh4eHiYiIiYuMjY2OkJGRkpibm5ulop6en6CgpqSnrKuqrq6vsLO5vcS8trjAx8jFx87P3+J1fICA4d1ydnZ3d3Zy3tvbb29ucG9s2NjYbnR6fHlzc3eAg4J+fn59goaIiYmFfnp/gH+DgXx9fnpzc3FvcG5v19ZsbG9Vc3Z2dXR0dHZ4eHd1dXZ8f36AgoKDhIWDg4KBf4GBgIB+fn58cc/P0crIytRtbm3Y1tnZ2NbVbG1ucHFvbm9wcHJxb29wcXJ0dXd3eHl4d3d3dnV0dYR2hnWEdBpzcXFwb25sbWtqaGXGwsC/vby6ubq6u7m5uIW5G7q5u72+vr/BwcLDw8THycnLz9DR09TW1tbU0YTPFdPU1dXUamloaGdo0tPV19fYbGppa4ZsH21tbm9wcHFxcHFycnNzc3R1dXd4eXl6eXl4eHd3d3iEeUl7fX5+f4B/fn9/gIGCiYqPkZOUk5OUl5iZmJuampyeoaOlpqerrrO4uru9wsXJzc3Q0MzIxsHDxcfFw8XFxMPFxcO/vb29vsDAhsETwsTFxMPFyMbFxMXFyM7R2t3e2YTYEdfW193h4uPp7vP2+vv7/H+AhIJBgP3+gICA/4CBgoOHiYqMjpGVmZubm5yho6aprrO0srS3vsHBwb/Ey87S0M/Q1NDN0NhtbnR1cm1rbHJwy2lzcXaARUdJSkdIRkZIJSYmJyZMKCcmJUdGSEomJyoqKCgqKCgmKCotKiQjJikqLjAZGBgYGTEsLzIbGjQzMTAzNDMyLS4rLi4nJkmGio6GREVEgICCg4F9eHd5eHZvbm5ra2t0fHp6enw9dXNwc3d0cnJua2poZGFhX19dXVtbXV9iYV2AXVxdYW5v5ebm5OHc2Nve4eXd2tXK0NTTzcfAztLW09PPa27SzMvS0tTIs56SiYF/eHZybm1raWloZGJkaGh0fomnvLmfiXx2iqKelH1tampoZ8nDvbi1tLW2sbC3wsW9srGxusjd/PTn6vP6iI2OifXdxLzCxcLAwMO+u8vazslJw728vL3H3+76+fbz7enr5NvUyry3t7vCxMHCwcPJztrj4uXe3uLw6+rr497d4uXq6ujm5eTi3+Df3t/g397e3tzc3Nva2tnW1oTUgNHMycnKy8zNzc3LysnGxcTCwcHCwMHAvr6+vLu6uLe2tLGvsaysrKmnqaempaapqamiop+bnJycn5+kpaKfm5qdnp6cnJ2fn52Zl5aTko+OjYiIhYODg4GBgP///vz49vX4+Pb4/ICBgIKDhoaA/4KEhYaFg4SHhoaGhISFhIaIgIeHh4aEg4ODhIWGiIaFhIaGh4iHh4mKiomLjYuMj5KQkZKTk5SVlJeZmZqbm52kq6yrt7Kvra+urbe1uL69vMHDxMbJz9Xe0szL1t/b19nc3fX9g4mPkPv4goeIiYmIg//7/oCBgIWEgP/+/oONlpqWjoyUo6iooZ+fnqiwtLOzgKmemaCgnqWhm5ygl4yJhoaGg4b9/IKBg4qQkI+Njo2QlJSTkJGVoKilp6yusbW2s7Kxr62urKysqKaopovx9Pfu6ef8goOB+/b//v78+oGBgYSFgYGCgoOEgoGEgIGEh4mLkJKXm5+jpaanqKinpqSko6KjpaWlpKOioZ6dnZuaVJiUlJKNh4H48ezp5d7a2djW19PT1NTV19jW1tfQzdLV0tfU0tPX2NfY29zc2tjW1tna3OHm6ezy8vHz9Pj8gICAgoKB/vz7+vj8gISGhYSEhYWGhYSGBoWHiIiKiYSKhIwRi42PkZSWlZaXmpqam5uZmZqEmRibnp6dnZ2enJ2enZ+enZ+em5eVlJWVlZSEkxqVl5mZmJueoKGjp6qur6+vsrSysK+usLCxroStfqytra2srKytrq+vsLCvsLCxsrO1trW2t7SzsrOytbe4ury8u7u6u729v8LDw8XGyc3Oz9LT1NVsbW9vcHFw4eJxc3Pndnd5e3p8fn5+gYOFhYSDg4WHiYmLjY2LiouOkZGTkZOXmJqamJmUj42Oj0ZGR0hHRkVEQkKGQkNBQomDhYQBg4SEhIOVhIWFhISChY+EAYOEgoODl4IBg6CCm4GCgq+BmoCEgf+AuICMf4iAAX/kgISBgoCHgYOAhoGDgKeBgoCogYeAg4GHgLmBt4CGgYaA/4GjgYeCBoGBgoKCgauCioMBgoSDAgIEAICeoLC3vdKwp7fI2oSShJCCgY+P6ejh6p+/zbq2pqy40+6BhoPKrcmBhoeB9fT14u+Ei5iYjdmhqLW1rrujsK2Yn6yHjtOHkueowZmctJ6wv7GW9eeCo62znYDz7vr89uPW09Tf5OTWy8i+u7ezsqumoJ2bmZeRjYyLi4uJiYeGhYCDgID/gP///Pr49/f08e/t6+rp5+jk4d/f3tzb2dfa5PWA8NPNyczOztHMx8PAvby6tK6npKShoZ+cmpmYlZmfvs/VwJeRioWHop2PioWFhYOD/fLq49zY1dHMx8LI08zFwsHAxMzS19XZ29nzf33fx7mwrq2ysamppKKgn52dqUC4tq6vrqmpuNTT0dHCu7muqaqqrrG5xsjKxcbBubu8vsK9vr/Av8C/ub3BwL69vL27ubi3tLKxsK6uraurqqqohakQpqWkoqGhoaCgoJ+fnZybm4WZgpiElkqVlJSUk5KQkJCPj46Mi4qKioeHh4iIhoeGhISDhIOBhYWDg4KBgYB+fn9+fX1+f39+fX5+fn19fXt8fHt6enp4d+7t7O7u7+3r6ITngOTi4uHh397g4eTi3+HjcnFwcXN0dXZ2dnd3dnV2d3h3d3h5eHh5eXl4enp7enp7ent7fX5+f35/gH+BgoOBgoOEhYeGhoeGh4iJiIqMi4yNj46RlJKUl5WWmZicm5ufn52bnqGkqKSkpqmrra6wsbW8vL7Cy9TMz2tva2hoaWhnUmNkZWZmZWZjYWJhYWNlZmdmZGNkaGptbGtoZ2dna29taGRlZGNlZmFiYWJjYmFeYGBfX11fYGJhYmJjYmJjY2RkZGNiYmJhYWJkZWRiYmNhYWGHYiJhYV5dXl9fYGBhYmNkZGRlY2FgX19gYWJkZGRnaGdlZ2lmhGQVYmJfX11cWllZWlpZWFhZWVpaWVlYhVdZVlVVVFRUVVRTUlJSUaGioaCfnZ+goqChoKChoaKipKWmqa2wr6+xsa2rrK20tbrCw8XEw8zX1dLQzMXBv7mzsVVVVVavtLO3trW7v8DAwcbLzcjEwsNfXV6EXYBeYGNjZWZlY2JjY2JkZGZra2lmZWJiY2JiYF9gZWVjY2RmaGloZ2dnZmVoam9zd32AgYGChIWHiYyNjZGTlpmbnqGipauytLm8v8TN1trb2dPZ1Ma7uLu+v729vr27urm5ube3t7i4ur29vr29wMLDw8PExMbFxMPFxcfKztXc41zk39vb3dzW09Xb4Obq6u3u8fqAgYaJjY+SlZOMiIiGhoOAgoOFiIqLjZCSlpmgpaqywc/XzMK+wcPAxcjLzdHS09TZ5e7o3uPl6uDe5eru7/OA9oWVkYeAgoSIjYB5eoSKjZeDf4iRnFtjXGFbWV9go6Shpmt+hnp3bnB4iJdRVVOCcYFSVVZRnJubj5ZUWWFhWYtrb3h3dIh7gIF0eIBobZZcYp98inV1g3eDioJwvLNhdHl7bl63t76/vLSura+3vLyyqqaenZqYl4+LhYGCgH17end2dXV2d3d0coBwbGzcc/Du7vDv7+3s7Orp6Ofm5ePi4N/d2dPV2drZ4O545MzGxszR1M/Gua6rqamkm5WOjIyLiomGhIOEg4mUuc7Uu4x/eXd7k4+BfHl6enh459zSzcbCv765tLK6xsC4tLS1uMTL0s7Y29n5g3/fxrSsp6iurqaioZ+enZqaq4C8urK0s6ysvuPh3+DLxce2srKztrrD0dLW09PNxcrLzdTPzc/R0M7Sys3S0tLR0NDPzczMysjHx8PDw8C/vry4ury9vby7uri2tLW0tLSzsrCvrauqqamoqainpqSkpaWjoqChoJ+fnZ2cm5qYl5iXlZOTlJWUk5KQjo+PkI+NjXCMiYuKiYmHhYaGhoWGhoeGhoaEhIOCgYCAf359fXt7ennv7u/t6+vp6uno5+bn5ebl5OXk5uXl5+fo6Od0dXZ2d3d4eXl6enp5eHd3eHd4ent6eXl6enl7enp6e3p6e3x9fn1/f4CAgYCAgYKChIeGhIeAhoeIiYuNjo+RkJGQk5eWmJeXmZ2eoaGhpqWjo6aorLGsq62vtLOzs7i8w8XHztvn3OF1enRzc3V1dHBxdXV1c3Rvb3FxcXN2eXx6d3R3foCGhoN9fHp7g4mIfnZ1dnV5eXFzdHZ1dXNucHFwbm5xdHd1dXZ2dHR1eHh5fX+Af4A4fX5/goWEg4OEhIOEhoeHhYODg4B8cnJzdXJydHV6fX19fnx5dnFwcHBxc3Rzc3d3dXN1dXV0dXWEeAx5eHh5eHl4d3d3eHiFd152dXV1dHV1dXRzcnBwb21ramdlyMbEwsC+vbu8urq7u7q5uLm3t7q8v7++v8DAwL69wMLDxcjJzMzO0tjX1tXT0tHQ0M/QZ2hoaNDS0NHR09bW2dna3N3f3d3e325thG4Fb3BxcnKEdFF1dXR1dnZ4e3t7enp6e3t6e3p6en9/fn1+gIKBgYGCgoCAgoSGh4qPkZGSk5OUlZaYmZmcnqCipKepqayytrq9wMPI0tjb3dvY3djNxcHCxMaEw3/CwMC/vr2+vsDBwcLDxMTExsjJyszKy8zLysnLyszO0tjd4uLf3t3e3NrY2t/k6erq7/Dz+n+AhIaJjI6QjYqIiIeHhYSEhoiLjY6PkZKVl5yfo6iwt7u3tLKztbS3ubu9v8DAwMbO19DGycrLxMHHysvLzmrQbHNwbGhpaW1vgEVHRkhJS0VFRkhLJykoKignKShLTExOKy4wLi0rKiotLxkZGCooKhgZGhoyMzIxMhocHR0cMSosLi4sSUVHSEVFRUFBSCYnSURHRUVHRUhIRT53djo8Ozo4NWxxc3V2eX1+foGCg356eHR0c3JxaGZiX2BfXV5eXFtbWlxfYVxagFhVVbJk2Nja3uHh3t3h4uLj4+Lg29va2tbNw8zW1M7O02fHtbS6wcvQxLemlJCNjoZ6c21tb21tbmtpamxtd4SswMWvfGxlZWt+fXJvbG5ubW3Txr66s7Gxsa2pp7LCu7Cur7G1wcvQzNXa2PmCgODFtqypq7C0rKenpaSioaO0MMfHvsC/t7jK7e3s6tbR0sO/v73Cxc7c3eLf4N7V29vg5d7e3+Dg4eLZ3+Xl5eTn54TlH+Lg4eHe3Nrd3NvW1tfZ2djX1tbU0tHS0tLRz87LysaFwynCwb68vLy+vru5uLm5uLe1tLSysrKxr6yqqKmqqamrqqekoqCjop6gn4ScCJuYl5eXlpaXhJiAlpSSkY+OjYuKi4mGhoWEgPz7+/n4+ff39PXz8PL19fX29/n6+fr7/fz7+4CAgYGCgoOFhoaHhoaEgYGEg4WHiIaEhISFhYeGhIODgoKDhIaGhomJiIeIhoaJiYqNj5CQkZCPjpCQk5aXmJuenJ6coaSho6emp6qprrCxuLa1tLSAuL7Evru9w8bDwcLFzNTY2+Py//T5g4mCgoSJiIeCg4qLioiKhoWIhoeMkZebmJKOlaCirK2lnJuYmam0r52RkpKPlZWIioyPjo6JhYWGhoWDh46Rk5OUko6NjpKUlpyipKSmo6KlrLOxsLG0sa+zt7q8ubW1tK2mjYqPk4yNjpETnKGcnp6ZlZCIhoWFhYqMiYmNi4SKGI2MjY+VmZqgoaOkpqenpqSlp6enpqSkooWkfaChoqKinpuZmZaTjouEgPr28e7p5+Lg3tnY3NrW09HS0dLT1NLOz9PO1NvZ1tnZ1tTR1NbY4NrX1tnc3uHm6/P2+YGDgoP99vj2+fj19PLy8vTx8vX7//6Bg4ODhISFhoOCg4SEhYaHiYqLi4uMjo6PkpKVlpiZmpqam52dhJuAnJ2dnZ+dnp6enZybmpqXlpaWlZORkZKSkpSTlJWXmJmampygoqamqK20ubm7u7q8urWxra+wsa+vrq6trKqqq6usrq+vr7CxsrKytLW1t7m7urm3uLm5uLm6u7y+v7+9vLy+v7/BwsbIy8zNz87Qz2lsbW5wcXJ0dXR1dHV2dncOeHp7e3t8foCAgYKEhoiEhyiIioqMjIqIiIqLjo6PkpSYmpmTko+Oi4uLiYqLikWLRUZEQkNCQ0NEi4OIhISDioQGhYWFhISEhIWFhIWFhoSKg4KEi4OCgoaDqYICgYKcgQGCsIGbgIKB/4CzgJt/6oD/ga+Bs4CEgZKA/4Gagb+CAoOCiYMCAgQAgNC2nZedqbSltrqzravI1dKfmpWRlaGqxMCrq8D4jaCji4yMjfG/8tWM9Pr9r7CwxdHH2Mq3j/TkxKqrjuvr8PP6//Hx/6mroZ+rg4vD3NCD8dfZ8PmJgdK7qJ64sqOEh4OE8t7e3tjWzb65samloZuYlJKSkIuLioqMi4mFgoD9gPj39PL19/v6+/n49fLx7+zs6uro6OTi4d7f3tnY19bW1dTKzMrIyMjJysfFwbyzrKiioJ2eo6GfnqOop6u6ua64ta2Ti/rcybuwrKakqrS9yd3d3djV08/MyMbDv8bY1MS9u7vE1ODh1tLOw7q6t7CsqaakoJ6fnpuanJyZl5aXWJibna3Gx8PEzr+3srCxsra7vL/Ax8fJxcbP1dTLx8PHx8bKycnLy8fEwsHAwL++vbq4uLa2tbOwrrCvrq6xsa6rqamqqqinpaWlpKOhoZ+enZybmZqZmJeEloSVBZSUk5GQhI4MjYyMjIqJiYmIhoWFhIQRgoKCgYCAgX9/fn9+fXx9fX2EfAN7enmGeoZ4J+/w7e3r7Onr6+vo5eTm4+Lh39zc3Nvc29zd397c3+FwcXBycnJ0doV3EnV27+vq6u3tdnd3eXp7eXh6eoR5hHoHfH19fn1+foR/FYGBgoKDhISDgoKEhIiJi4uLjIqKjoSPapCRk5SXlpeYmJmam52eoqWop6WmqrK1tbi4vb3Av7/Iy83OaGhpZsfIZWhoamtnZWNlZmdmZ2dmZWVjZWZnZWZlZWJhY2VlZWJfu7m7X19gXmBiYF5ct19gYF5gYGBiZWZlYmNjZmZoZmaEZIVlA2JjZYVkJmVkZGNhYGJhYWFiY2NkZGZnZ2hoZ2VlZWNgYWNkZmVlZmVkZGNkhGUJZmVmZWNiXltbiFpoW1tbWllZWFhYV1dXVlZXVlRUU1FSUlFRoqGgoJ6eoZ6dnKCeoKGjpaanqautrKutq6qpqKyvtLi9yM3NzcjO1tbTzcvKy87Qy8FbVVNUVVdXWLO3sre6vsHAw8jJycZhYF9fXl5fYF+EYAthYWJiYmRjY2NkZYRjhWSAZmhqbWxqaWlrbW1vbm1ub21tb3F5gIWDhYWHiYqKjIyNkJOWmp2goaOmrLS6vsHIzM7T297n497az8O9tra3tbW3ube3ubq7ury8vb/BwcLDw8PExcjKys3Qzs3My8rIys3W3+nw8e7t7Ojo4drY2t3i5+ro5ejq8vr/goeNkZNDmqKgnJaWk4yIiIeHiIqOkpiisLzGw9Xm4dvSwby5t7zDy9bf5ebk7vL1+vX98Ozb19jZ1tPPztPf5O+p5erc7eLY4YCViHp2eYCHfomMh4SCkpiaend2dHd/g5GPg4OOr2Bqa11dXV6kfJqJXaaoqoB/fYiQi5SKf2uln4x8fm28vMTCyM7BwMl9fXd3f2ltjpuSV6GTk6OnWVSNf3VxgHx1Zmxnasi4tLOysqudloyHgn96eHVxcnBvcG9ydXVzcG5qzYDJxcXKz9fd4ejq7OTf393h5OTk4ePi4N3Y0tDX19TRzsrDub2+wcnMzc/IvbWsopuVjo2Ki5KPjIuSmJaaq6ygraqjh4DfxLipoJuXl56mrbfMysbAvbu5trKwrq24ysi1r66vuc3Z3NHOyry0tbCppaGem5qXlZWUl5mYlJKTlmWZnKCwy83Iy9nJwbu6ury+xMXLzNPU19HR3+Xi3djU2NrY2tvd3d7c2dXV1NLQ0NDOzczLysjIxsTEw8TExcK/vbq7vb28u7q5ube1srGvrqyrqqiopqSioqGio6KioaGgoJ6enISahJhQlJSVlJSSko+OjoyNi4yKiouKi4mHhoeGg4OEhYSEhIODgoKAgH59fHx8e3p6eXl58fDw7evs6uno5ubk4OLj4uLg4OHh4N7f39/i5OXk53SFdoB3eXt8fHp4d3fu7Ovq7e94eHl7e3x5eHh5enp5eXl6enp8fX1+fX19f4B/f4GCgoODhIODg4SHiIqNj5CPjI6OkJGSkpSVlpianJ2dnZydnqGkpqiprq6rrLC5vLzAwcbGyMbK1tnb4HJydHDb3XF1d3l8dXV0dnd5e3t7eXd2dC92eHl3eHd3cW5ydnh3cW3W1tRvcXFvcXVwbGzVb3JzcnJ0dXh9gH55enp9fYCAhYSDB4WFg4SFgIWEiSSIiImHhoV8eX97fn5/f39+f4OEhISDgoB9fHh1dHd5e3l6e3uEeRh7e31+fn19fHt6eXl5enp5eXl6enp5eXmEeGp3d3Z2d3Z2c3FtbGtnaGdlZcjGxcTEvb26uri5t7m5uLm4uLq8wL29v769vr28v8DCxMrMzs/N0NTX1dPT09XX2trZbWtoZ2doaWnT1dXX2dnb3N7h4eLgb29vcHBvb3FxcXJzcnN0dHR1hHYBeIR5gHp7e3x9fX1/goWEgoKCg4aGh4eGhoeGh4iIjI+SlJaXmJqamJeXmZyeoKOmqKmrrbO5vsHGys/T193f5uPi3NXKxsC/vr6/wMDBv8DBwsLCxMXHx8bHycjJys3P0NLV1dPT0tLR0NDU2uDo8O/r7Ovo5+Ld3N7i5unr6+zs7vT6Sf6BhYuPkJSbmZeTlJGNiomJiImMjpGWnaeus7G9xcLAurSvrKyxtrrAwsbLy9TX2NjX18/Kwb/AwL26t7i6wMHHf5+hmqOdmJ+ASklHR0VGR0ZISUlKS05QT0hGRkZITEpNTUtLT1MqKikmJSYlRiswLiZJSkxFRERFR0VHREI/S0lGQ0NBe3t/f4KBgH5+Q0JBQ0ZDREhIRiREQEBCQiEfOTc1Njo8PT5DQUSEgHl1d3h1bmhiXllVVFRRT1BOTU9PVVhaWFVTTpSAkY2PmKGwusHM09jMxMfIztXY2dng29fSyr+5zc7Mxbmxpp2kqrHBxcrPw62mmouDfnVzcXN7eXZ2e4CAhJKSipaQjndxwaacjoeFgoWMlZuluri0sq6sqqeko6Kir8PCsKmqq7bI1tnNy8m8srWxq6ejnpqXlZWUlZmdnJiVl5tcn6WovNfZ1Nni08zEwsTGzdHP1tbc3t/b3ejv7+fm4+jn5ejq6+zu7eno6unq5+fl4+Pl4+Lh4eDe3t3e3t7Y1tTQ0tTV1tfV09LQzszKx8bEwb+/vr27uLi3uLmEuIC5t7a0srCxsbCvrq2trKuqqKiop6WjoaChn5+dm5uam5uYl5mXlJOVlpWVlJOTkpKPjYqHhYaGg4OCgYGA/fv7+vb08/Hu7ezu7uzt6+vq6+/w8fHy8/T2+Pj7/oCAgIGBgoWGiYmKiISDgf78/P3//4CBgYSFh4SBgoKBgYKCgoCBgYGEhISFg4GDhISFhomKi4yKi4yMjo+QkZaanZ+cmZuam52foKChoaSlpqapqqqrrLCytLi5wb+6ur7Jzc7R0dfX2tne6u/2+4GDhIH8/oSKjJCUjIqJjpGWl5iXlZKRjZGUlpKSkZGHgomQlZKHgvv5/IaHiYWIj4WBgfuFh4CKiIqNkJiipJ6WlpKam6GhrausrautsK2tsamxu7q5u7u7vbu5tqKap5+kpaWmqKWmqqusqaWjopyalI+NkJGYlpaYlpOVlpeZmaCioZ+en56fo6SmqKinqKqqqaimpaSko6WlpKOio6OioZ2Yko+NhISDgYD59PTz8+Hc2tra2FrU3NfP0NHMy8/W1dTW09Pa1s/MzM/My8zLz9rV1tbW2d3e4eXo7vyEiYaDgYGAgP35/v/9+vn7+/z9//+AgoKDhYaGhIWFhYaJiYqKi4uMjI6Oj5CSkpOUlZaElwSYnJ2dhJxTnqCfn52cnJydnp2Zk5KVmZycmJaTkpKSk5WVlpeYmZqanqGip6qtr7K3u72/v769uLOyra2usK+vrq2srK2trq+wsbKztLS1tba3uLq8vb6/v7+FvV2/wcLDxMTDwMHCxMXFxMXGysrOzs7Q0tLU1mxtcHJzdnl6eXp8fHp5eXp5eXp7foGCg4SDhYSFhYeIiIeHiImJiYeLjI+QmJual5KPi4iJioqKiImHhYODhoZFS0mESAFKnYOHhAGDhISTg4mCioMBhIWDgoSLg52CxoH/gNmAn3+PgIZ/2YCEgYKAoYGDgImBAYD3gbKAiIGNgP+BnYHAgoiDAgIEAICusLLU1ta+v8fJydnl3KyJhoKCltSJl76VmPDYw8TH5NPagLeKsMyW9/b39/Lx8vHw7+rl4+Xn6ezu6+rw9/6Dj5aewZSxsafFwJ+SkofZtsT3hKeYh4iP/uLHu7WgkIuLiYn65NXR0MS3rKShoZ2XlZKQjoyLiomJiYeDgP37+oD49/Pv8fHv7u3u6+rs7uzq5uTk4+Li4ODc29fW1tLMysrJycrJx8XHxcTCvbCzsaqjoZ+dnaGmoaCvusrN2aC4mISykLiE5tPq7uPY3e7h+dvuxM/Tz83MzcvHw8G/vsS/vLu4trzHx7Wysq+rqKSlpaOhoqCgn56cmJeXlZWUlUWam5qesMrRzsy6s7G0sLC/w8C+vsbM0sy+u7rCxMnMy8rJysW9ura6vrq8vrSqqK+zt7i2tbSzsbCyr7O3uLWysK+tramEpzGmpKGgn5ybnJyamZiXl5aVlZSTlJSVlJKSkY+Pjo+NjIyLiomJiImIhoWGhISDgYB/hn5ufHx8enp6fH17e3t6eXp6eXl6enp4ePHx7+/s6+vs6+np6Ojo5ebl5ePh4d/g3d3c2trb3Nvd3d7b29zd3d7hcnJz5udzc3R0dXXr7e/u8Ozr6urq63bu7et4eXl5ePDw8Xl5efP0e3z5fXx7fHyEfwuBgYGAgYKCg4SFhoaHQoiLi4qMjI2OkJGRk5SUlpicnJ2goqGhoqaqrbCwsLOytLe5v8HJ1dRrcHByb2prb29ubWppaGxwbWZnaGloaGZmZYRjPGJgwF9hYGC9ure6Xl1gYF9fX7hcXF5fYGBgX19gYWJiY2RmaGlqa2poaGZlZWZnZmdlZWZmZmVlZWdmZYRkMWVkZWVlZGRmaGhqaWloZWJhYGFjZWVlZmZnZmZmZWZmZmVlZmRkZmNjYV9eX15dXFyHWwVaWlpZWYZXHVRUU1KioqSioqOfnp6en56fn5+doKKlpqiqraqohKmAp6ajpKeqr7O3vcTKzMzO0MvGwsDDyc3OzsbBv7i5wMbHyc/Pwl1bW1laWlpcXF5eX2BhYWFfX19cXF1fYWFiY2VlY2NjYmFhYmFiZWZnZ2ptb25tbnFzdHR1dnh1dHV2d3V2eXyBhIWHhoaKjY6PkJGUmJqdn6GjqLC2ub3EyMyA1NfY3N/g2tPPwry3s7KxsrO0trm8vb/AwMHExcbKzMzKycrLzdHT1trX1dLR0tLW2dvi6fT7+fbw7Ozo4Nvc3ODk5+jp5+fq7e/3gISKkJSaoKGhnZqUkZKPjo+RlZeao6msrq+ztr7Bxt3vg9bK0t/vhYaE9/uCh4mFhYDu4dIT0NXUz8zLy93p6YCbmZmRur/LvICEhIaXmZqMjZKUk5+noIZyb2tqdZtgaX5narCfkZOSoJSYVoNqfYxwwL7Awb69vbu6u7i1s7W3ubm7urzAxsxqcnZ6j2V2dW5/fGhjYluUg4qnWGtjWFldppaKg4B2bWttbG3CsKWjpJmPg3x4eHRycG1samloZ2dpbWxqZ8nGxnLEwr/AxMnIxsjKycrUzs3MxcbLz9bY1NPRzsjMxLy2tra3uby+vb/Ew8HEuaSkoJqRjo+NjJCWkpOlssDEzpKninqmdaZ3z7zX39PEzt7U7Mrdtrq6uLa0trOvq6qqq7OuqaqpqbG9vqyrqaWgn5udnJmEl4CVlZSUkpKOjo2Tl5qbnbHU2tbUv7q1ura3yM/LysjS2uPZy8jFzdTY3d3c29zX0M3FytLPz9DGuLbBx8zNy8vJyMXFxsPJzMvJxcPBwb+9u7u6ubi2s6+sqaepqKampKOioaCfn5+hoaGgnp6cm5qYmJeXl5aWlpWTkpKQj4+Niw6LiYiIh4WFhoaHhYSDgoSDUYKBgYB/fn59fX17enl58O7u7erq6erq6efm5uTi4uLh39/f3t7d3dzd3d3e3+Hg4OLg3uHi5uh2dXXn6XZ3d3Z2duvt7Ort7Ovq6OrueO/v74V4gO/v8Hh5efLzenr0e3t9fX1+fn9/gICAgYOEhYWHiImLiomJiomJi4yOkJGTlJSVlpiYlpeZnqCipaampqirr7O1tLS3t7q9v8TJ0+TkdHp7fXx3eX6BgH17enuBioN3eXt+fHt5eHRwcnR0cW7ZbXBwbtXRz9Nsa3BwcG9u1mxtgG5xcnV1c3R2dnl5eHx+gIKIiomIi4eFhYeGhoeFiImJi4yLiomGgYGGhoeGhIeGg4SBg4SDhYSEgHl0dXN1eHx/fn18fn5/fn5/gYGCgoGAf39+fn19fH19fH18e3p7e3p6eXl4eHl3dnV1dnZ1bmtqaMnHy8vLycK9vcG+vL7ANLy4t7m8urq9vrq5urq7u7y7ubm5vL3BxMjKy83Nz9DOzcrMztHU19fW1tbS0tbZ2t7f39yFbQJubYRuBW9wcXFzhHIkc3NzdHV2d3d3eHh3eHl5eXp7fH5+f4GChISDhYeJi4uMjY+NhYyAjY6RlJeXmZiYmZydnJycn6GkpqeqrrC2vL/Bx8vP1tjY3ODg29XSyMXAvL27u72+v8HDxcbGx8jJzM3P0dHQz9DR1NfZ3N3c29nY2Nnb3N7j6fL29PHt6+vq5ODg4ePm6uvs7Ozu7/P5gISIjJCTmZqbmZeSkJCOjo+QlJeYnaExoqOjpqesr7K/yGq7tbnAy2ttbNDUbXBwbm1pycG5t7q4trW0tbzBwml4d3h1iYySi1lISEZJSklJSUpMTU5RUk5JRkVDRk8qKzAsLlVTT05LSUVEI0E7P0I9dnV2dnV0dHJycnBvcHBxcnV3d3d8gYVDRkhJSiksLCktKigoJyRDQ0JCISUkIiIhP4U+Ez8+QUA/dWxoZ2hiW1RNSklHSEaERIBCQUFFS01MSoyMjIqIh4mPmZmXmp2fn7CsqaifpK+3w8e/v765r7alm5iXnJ2hqKuqsLe3tbqsjo+Mg3t4eHV3fIJ8fJKbpaWqdohvYolUh2GklLi7r6qwvr7UtMSjqamnpaSmop6cnJ6gq6agoaGjrbm8qqioo6CfmpualpGSk4CUk5ORk5STjo+Smp6ioqe63eTh3crFwMPBwtXa1tTT3OPn4tbS0t3e6O3s6enp597d09ng3+Pl2cnH1dvg4+Pj4d/e3NzZ3+Lh3dzb2djX19bX09DNy8bCvbq6vbu5uLe2trSztLSztLa3trS0sbCurK2sq6qrqqmnpqamoqKioHqfnpuZl5eVlpeWlpWVk5KSkZKTk5KPjo2NjIqIh4WCgID//vz6+fn49PLy8u/t6unn5eTl5eXk5ebn6ers7+/z9vX29PPz9fb4/YGAgP3+gYGBgIGB+vn5+f/++fb4+P6A///+gYOBgID//v+AgYD//4GA/oCBgYKChYSGI4eIiYyOjY+Rk5OVlpWUkZGTlJaZnJ2dnJyen6KjoaKora+xhLKAtru+wMPFxMbGxsrN09jl9/uBiImMjYqKkpeYlpORk56upZGSlZmYlpKSi4aIi4uGgf+BhoWD/PXz+oGAh4WGhoT9gIKFiIuRko2PlJWYlZaZm56kr7G2uLqyr62vsLCysbW6uL3Cwb67s6ams7S5tLC2tK+xpaenp6qnpaCTjI+Ai4+Um5+enZqfo6Kfn6Ooqquqqqmno6Wkp6aoqaqsraypqamnpqWjpKSko6KfnqCjop2UjImE+Pb+/f337OXl7OLe4Ojg2NPU083M0NDIy8/Qzs/R0tPSzczL0NDOzcvMzM3O0NHU19vc3eDl7fD2+/ju7/Dx8vP7gYOGhoWEh4WAg4OCgYGChIiJiYiNjo6MiouLi4yNjpCRkpKTlJSVlJSWlpeWmJqam52dn5+hoaKhn56dm52dnZubnp6em5mXl5aVlJSVlZeZm5ycnqClpqaqrbCztba4vLy5tbSvrq2rq6qrrK2trq6tsLGys7S2t7m7u7q6u7u9v8LDwsPEw8NWw8TExcXHyMnKycbCwsTGxsXFxMXIycrLzM/Q1NbYbG1ucHN2eXp7fHt6enx6e3p7e3x9f4GCgoGBgICCg4OCQYSGhoaFQUJCiI9JSEZFQ0GBgICBgoOEhQiCgIJCQ0ZHRoRIlYOFhIiDAYSFg5eChYOKhISDhoSLg5qCwoEHgICAf4B/f4x+AX//gMSAqX8FgICAf3+GgIt/BIB/f3+FgAt/f3+AgIB/f4CAf8KAoIEBgISBhICHgQGA9IHCgP+Bp4GhggGDhYIFg4ODgoKGg42CiYMCAgQAgKbJ6ZSR+dK6seLrhKOP37GYhouu1vunt5OC7P785ovpxLS3ooH79fTz8/Hx8/T6h4X38/Lv+PPw8/j9+f2AgYqaqqrEh4fF2vGB59DLycvHqaOTipKVjY+Pg+bHuKSbkoqDgPXh1c7Eura1raimo6GenpuVk5KQjouGg4GA/Pz8gPjz8/Pv6uvp6Ojm5+jm5OPg4d/f3t3c2tjU0dLW1dHSzs3NzM3LwruwqKqoqq2mo6GfoKamo6qtuLaZieCktPj29vH34/Hn3e338uXq+Oj039aJxtTRz8/MzMnGxcfDwcS+uri3t7e0r6yrqqimp6akoqGgn56cnJqYl5WSkZKSPZGRkpSYprzEvrmooZ2am6KvqaympK2zpZeSkZifo6yzr7Ouq5+gmZWWmZqnsK2gmqWyt7e2tbKura2qq62Eryaxr66sqqmop6WkoqCem5mYmZqZlpaVlJSTlJSSkpKRj4+NjIyMi4SKE4mHh4aIiIeHhoWDgYGAfn5+fHuEeih7enl6enl5eXh3eHd3d3h4ePDw7+7u6evr6OPk5OPg4OHi4N/f3t7chNpA2djW2NjX29nY2trZ2Nvd39/h4+Pk5ebl5efp6+3s7u/v7Ovp6ejp63bs63Z2eHh37u/u7/Lx8fLx9PX2e3l5e4R9TH5+f35+gIGCgYCBg4KCg4SFhYWHhoeHiIqLjI2Oj5GSkpGTmZygo6arsayusa+sr7O0uLzCyM3MysrVcXh5dnBubm9sbG1vbG9xcW6Ea4BqaWhoaGdnZWVnZmZlZF9gX7m4YGFgYGJiYF5dXFxdXl5cX2BeXmBiYWNkZWZmaGhmaGdmZ2ZmZmdpaWppZ2ZmZ2ZmZ2ZlZmVmZmRkZmdpaWhqaWhqZGJlZGZlZ2doaGZnZ2hnaGdnaGhlZ2hnZ2ZlYl9hYmBeXV1cXFtbW1pZWoBZWVhWVVVYWFdWVlOkn56kVKKfoaChpKGfnp+eoJ+go6aqrauopaapq6uloqGhoaWnqquvs7e9wsPEw8K9vLzCyMvLztHOysvJyMrP1NbVy2BdW1xdW1pbXV9gYWJkaGlnY2FeW1tcXV5eXl9gYGBiYWJiYmNkZWdqbXF3d3VzcoBzdXd6f39+fH2Ag4ODhIaKiImHiIeJi42Oj5GVmJueoKSoqq61u7/BwsbIy83N1djUzMjGvbWzsbCxsbO1ub3Aw8XHycvNz9LW1tTQzNHV2d3g393c29rZ2tve5ejp7fb++/f07+zo4uHh4OPn6+7y9/7/+vj/goaJj5SXm5uZlz6Sk5aWlZaWlpeYl5mbmpqepaqwzu/i4veA+PKGk5ianZWcmZyQlIuEgfLk4ubo6u3w8vD1gP2C+fDx7oGFg4CAlKVjY6+ZjIakqF1wYqKHeW1xhZuwcHtoXa62taZfpY6Cg3dkxMG/vby9vr6+wWZlwL6+vMC+u7/DyMjLZmhueYGBkVxcf4yZUZKFhIKDgm9sYltgYl1eXlegjYJ5dG1oYV2tn5mSi4WDhH97end2dHNxbm1sa2tpZ2RiYsTHxYDBvb29u7q9vLu8vsHExsLAvb/AwMHFx8TCvri7xMPAwLe5vb/Ev7aonpWXl5iblZCNjZCYl5WboayzlIPTmJrY2tzY1NHn3dHo9vHm7Prr+t/Qg666t7a0tbOvrKutrKyxq6ilpaiqpqCenpyampmYl5SUlZSSkJCOjo6Ni4uLjX+Nj5CSl6fDy8K8rKGem5umurK0rqy3vKyempqfqbC6xMLCv7ioq6KeoaWpucG9q6e2yMzLysrHwL7AvLu9vr69wcXCwL+9vLu5tbOwrqmlo6KjpKOgnp6cnZ2enp2cm5mXlZSVlZSUk5STlJSTk5OSkpGSj46MiomHhoWFhIOChYFtgoGBgYB+fnx9e3t7enl48O7s7Onn5uXj4uPi4d/f3t3b29rZ2tnZ2dra2dnZ29va3Nzc3d7f4OHj5efm5ebo6uno6ers7uzp6+vr7Orp6Ojs7Hft7nd3eHh37ezt7vDu7vHw8fPzenp7e3x9fYV+BH+BgoKEgUCCgoWGhYWFhoeJiYuNjY+Sk5KTk5SVmJ2hp6qssraxs7WzsbG1ur3BydLX1tLS4HmBg4F7en1/fXx+gH+Eh4eEhYBkf319fHx6d3Z5enh2dW1vbtTVcHFwcXRycG9ta2ptcXNwcnVzcnZ3d3l7fYOBhYeHiISEhYSDhoiLjY6NjImJioiGiIyLi4qKioWBhYaGhoOFg4GBdnV5e317f4GBgX9+gIKDgoSDgIKBgYCAgH9/fn5/f359fXt8enZ4dHJzdXVzbWtrcnR0b3BqyL29yGnDvr+/w8nEvr3Bvr+9uLm8u728ube5ury+vby9u7u7vLu7vMDDxsjJy8vKx8jJzM7R09XW1tXW2Nrb3+Pl5uRwb25ubm1ubm9wcHFydHV3dnV1dXR0dXV1J3Z2dnd3eHh5ent7fH1+f4CChYmKiYiJioyOj5KUlJOSk5WWlpiamoSYc5eZmZqcnZ6ho6aprK6ytbi7v8LEx8nLzc/R19jV0M3KxL+8urq7uru+wsTFyMvNz9DS1Nba2djW1Nfb3eDk4uDf397e39/i5ujs7/T6+PTy7uro5eTk5Obo7O/y9vz+/Pn8gYSIjJCSlZWUlJKTk5OSk5OElYSWL5idn6O0x8DD0GrPzW1zd3Z4dnp4enN0bmpoyL68vb3Aw8PFxMdmzGrSzc3Ka21rgElJTScoT05MS1BRKS0qUEpHRkdMT1MuMi8tVlhWUSpMR0NBPjt0cnNycnNzcXJ0PDt0dHNzc3R1eHt+gYNCREVJS0pMKSktLjEZLywtKysrKCclJCUlJSYlI0VCQkFAPTs2MFZTUk9LSUpNTEpIRkZEQkFAQUJCQ0NDQUFChYqJgIaBf39/goiHh4uPlp2dlpSRmJiboKSqqaWelp6qqamomp6lq7Wrn5KEfIJ/foF/e3l6fIOCgIaMl5x9ba54b6autLOqvNfJw9/r5t/n8un13Mx7mqWioZ+goJyZm56en6ehnpycoaKhm5mal5WWlpaTkY+OjI2Li4qLjIuGiY+RVpGSlZmgscvSzMeyp6Shoa/Ev8G4tsDFt6eio6uzu8nRz9HMx7q6sauutrnK1c+9tcTZ3+Df3dvV09PNztDS0tXX3NjY2dTT0M7KxsLAu7W0s7W2s7CvhK4psLCvrq2sqaenp6ilpKOkpaeopqWipaakpKKfnZyamJWVlJKSkpGQk5KEkWmPjo6MiomGhIKBgYD9+vTz8e7u7+7r6Onr6ufk4uDf4ODg3+Dh4eTl5+jr7uzu7+7w8fLy9Pf7+vj5/f38/v38/f7/+ff4+fr7+fP4+Pr/gf//gICBgoD+/f3+//r4/P38/P6AgIGChIWGhAuGiouLiYaGh4eKi4SNgI+PkJGSlJWXnJ6cnJ2foaOpr7W2uMPHvr7Avr7AwsbJz9fg4+Hi4vCCi46OiYqRlZKSlZqYn6ano52cnp+fnJiYmJeVkI6TlZOPjIGDgvn8hoeFiIyKhYKBgIGFjI6JjZGOjJCRkZOVmqOjq66usayoqaior7K6v8G/vrm2urizgLi/vr67vLywqLCsqqijpaOenoyLk5WZlpyho6KdnKSnqKeprKmnqqekpaSjo6anp6iqrK2tpqejmp2Vkpabm5eLh4eZm5qRkYPz3Nz2gurj5eXs9e3k4ubl5N3R0M/OzM7OzM3Kz87W1tfa2dXQzM7N0dHPzs3P0NHW1tTX19fZEtnZ2uTo6uzs7vH08vqBhIWEgoSEEYOBgoKDgoOFi4+SlJOSkpGQhJEIkpOTk5SVlZaElVmXmZmam52enqCgnqGjoqGfnZ+ioZ+hn56fnJiXmJiXlpaYmJmbnp+hoaSkpKamp6uur66vtLi1srCvrquqqamrrK2vr66wsrO0tbm6u76+vb6/v8DCw8TExYbELMXGycvKzM7OzMjGxMLDw8PExMXHycvN0NLV19jYbG1ucHJ2eHp6e3l5e3t7hHwHfXx8fH9/gIR/KoKEhYZEiYdCQkFBQkFCQkRFREFAQH57eXl5e32AgYGAP4FCioyNi0ZGRgWDg4OEhIaDg4SIg4SEhIMBhIaDioKCg4yCh4OFhAGFkISJg5qCv4EDgIB/k34Bf/+AwYDDfwOAf3+FgIx/w4CmgYKA+YGEgAGBv4D/gaeBooIDg4KCjoOLggODgoOEgoODAgIEAICVsvPMtaqejpWkzcGqk5SKjIqLk5Wnw+HW3NLQz+Xat5+al4uHhYP//oGFiYmGjJaPgoGBiYeUj5Cbjv/9goyVnJeQmJusrsq0uLHG0MDEyrKWm4qPh/Di+omIiO/Nt6ygmY6E9+zk2M/GvbWzsrKwrKejoZ2al5KNiomIhoOCgICAgf/9+vb18u7n4+Hg3eLm5OTi3t/a19bU0c/LyMrHysXEyMvLvLavq6ako6KioZ+gn56kp6OrsbiKlPfw49OAlZ/j2c7GvrbMyLzLysjNysXI1qzY1tXU0c/OzMvOy8bCwb68ubm2sq+urayqp6emo6GfnZycnJ2cmZiVlZWSknCSlJaWl6GssrCinZ2bmZian6Gdl5SSj46NiomIhoWEhoaNkpiaj5KOjY6PkZegoJ6hpq+ztbKvqKKlpqSho6Geoa+onqSopqOjpKSjoZ+dnJybm5iWlpWUk5KRkI6PjYyMjIqHiIiHhoaFhoeIh4eGhYVCg4GAf318e3t6eXp5enl5eHh3d3d2dXZ3d3d48e/v7ezt6efm5OHf29nZ2NnZ2NjX19bU09PU1NTT09TU09LR1dTWhNkU29zd29ve3+Di5OLi5OPh5Ojr6+uE6Qrq6uno6+rp7O13hO6A7fHy8O14efPz83l5ent7ent9fX18e319fH59foCAgYODgYCAgYKCg4OEhoeGiIqMjo2PkpWZm5+jqKyws7i5tLa5ub3Gzdvk3NTW2eDe2tnZ02xvb2xrbGhpa2xpZmZmamtsbGpoZ2hpaGhnZmVjYWC6urm5t7ldX1+7urmzsrUUW15cW1xgYF9gYGBiZWRmaGZlZmeFaQhrbGpramlpaYhoHWloZ2hoaWppZ2ZfYmdjYmRoaGhramloaGlqamlohGeAZmVlZ2dmZWFgYF9eXVxbW1pbWlqxWrpcWVhXV1VVV1eooZ+kpqaopaWlpqdTpJ+en5+dn6Cio6aoqqilp6eqqqajoaSjoqSoqKiqq62xtba2tbGytbm8wcfN0NLV19vd2NLPzM/NY2FgXFlYWVxdXmFjZWZmaGpraWhmYl5fXl1OXmBhX2BgYWJjZWZnaGhqbXF0d3h2dXZ3eX1/gIKChoOHjY2MjY+QkI6KjpGPj5GTk5aXmZyeoqetsrW3uLy+wcbHx8nJycW+ubq8u7i1hLCAtLi5vL/DyM7S1tjY29rZ19PS19re4ePh4eLh39/g4eTp7vb6/Pz39PT08/Dt6unp6vDz+P2AgYKA/4CEiIuOkZSTkpOVlJeYmqGkpaOioZ+fn6Cgp7K92PHgzcrS3uLxgIqSmZ6RlI2Fh5qglYj7/Y2XmKOrusHFytXVzt2Ll4wDuoqFgHqLrZuMhX50eH+XkYR1d3FycXJ4eoWYqqSlo6CepqCLfHd1bWlnZcfHZWdqaWhrcW1lZWZraXFsbXRrxsZnbnV7d3J3d4JzgnV3c4GGfX+Dc2NnXV9bpZ6oWlpan4l9dnBqY1yrop6XkYyHgoGCgH98eXd1c3Jwb2xoZmZlY2VjemRjxsTCwcPCvri4ubm4wMbFyMXBwL26uri1srCwtLO2r7G7w8GnnpaUkI6NjIuKiYuLi5WXkZ6kq3x40Me9r3RsitrQw7muo7u4rsG/uMO/usDRl7q4ubi1tLKwsbSyrqysqqilpaOgnZycnJqZmJiWk5GQj4+Oj46NhIx9i4yOkpaVlaGvtbKinJyamZeZn6Cgm5malZKQjo2KiomIiYyVnaOjmJiVkpaZm6KurquutsHGyMXDurK1tbWxsK6qrsG3rbe4tbOzs7Kwraqmp6ako6CenJybm5qZmJeWlJKRkI+Pj46MjI6OkJOUlJORj4+Qj4yKiIeFhIKHgSF/gYGAfn19fXx7e3p5eO7s6+vr6Obk4uDe3Nvc2trY1taH1ITVRdbW19bW1tfY2tvc3N/g4ODh4N/i5OXn5eTk5OXm5urt7evq6ejn6enp6urq6+x37uzt7+/u7e7vd3ny8fF5enp6e3t8fYR8h36Af4CEhYKBgYKDhIWHiImKi4yNj5GSlJeanqGlqK2xubq9vry9v8LEzdTj7ufd4Ofu6+nq5+J0en16ent2d3x+e3d3eYGDhIWCf35/gH9+fHl4dXJu09PR09DRbG9v29bUz83QbW9tbnF1dnN2dHN3fXt+goB+gIKGhYWGiY6PkJFlkpGQkJCPjo6NjY6NjYyKiIWFhIJ/fHFzeXRzfIKBgoWGhIODhISFhYSDhIKCgYB/gH98fHt4eHZ3d3BxcnRxcG/Wa9hvc21ramlqcHHNxb7Fx8fOysfLys1oysG8vb65ury9vL2EuxK/wL+/vr69w8PCwb68u72/v8CEwhTBwcTExsnM0dTW2dzf4OHi4eLm5oRxB29vb3BwcXKEdCV3eHd4eXl4d3Z2dnd3eHl5enp7fH1+fn+AgYKFiIuMiouNjpCThJV0l5eZnZ6dnp+fn52cnp6dnZ6foaKkpqmsrrK2u769vsDEyMnJy83NzcrDwMHBwcC9u7m5ur2/wcPGyc7S19ra3N3d3NzY19vf4ePk4+Tm5eTk5eXo6+72+fj39fTz8vHu7Orq6uzv8/f7f4CAf/x/goWHio6Ejz+Sk5WWl5qdn52cm5qbmpqcn6Wuv8vCuLm/x8rRbXB0d3p1d3RvcHh7dGzMy2xxc3h9hYqNj5SUkZtdZF2McW+AS01SUE9OTEpKTE5NS0pJSEhISUtMUVZZWVtaV1RSUEtHRENAPj08dnU7PD09PT4/Pz49PT8/QD4+Pz96eT9ESElIRUVGSCwvLCsrLS4sLC0qJicmJiZKRkckIyJAOjc1MzAtLFRQUE1KSEhISkxJSUdFRENDREZIRkJAQUBAQ0MCRUSEhYCNjYeChoqNjJednaGfmp6XlpeYlY6MkJaUnJGSpK6riH52dHRycnJwb3F0dHZ/gXuHjpBlVZeSjYBdP3DHvq6kloyjoJSvr6Wxr6+8yoKfoKChoJ6em5yioZ2enZycmZqYlpOSk5SSkJGRkI6LioiIh4mIhoSGhoiKi46Ul5mbp4C3u7anoaGem52fpamnoqKjnJmYlZSUk5KTk5qiq7KzpaehoaKnqrLAwb7Ax9La3NjVy8LGx8XBwsC8wtPKv8vMysfHx8XAvrq1tra1s66rqqqpqKiop6alo6Cfn5+enp2bnJ6hpKenpaOhoKCgn52bmJWUkZGPj4+QkJGSkJCQj4COjIqJiYiHhYOA+vn39PHt6ebn5+Xk4uDi4uDe3dvb29zd3d7f3+Hh5eXm5+fm5ujr7vLz8/Py8/Py8vT39/n49vf29fT0+fv7+vj5+fb4+/n3+/r6/ID+/Pz8+/r6+/2AgP/+/4CBgYKCgYGCgoOCgoWEgoGChIaHioqMjImIiYCLjI2Mjo+Rk5aYmZudoaKlrK+ztr3DxsjJysfJy8zP2+Dt9vDp7/T7/P3//veAiY2LjZCKipGXk46Ok5+lpqehnZyfoJ6bl5ORjIeD+vj19PHzgIOC//n48/H4hIeChIqRko2Oi4qQm5ibop6bn6Gop6ets7q8wcTFxsTDxMTEwnLCw8LBwL24r6WmpaCblYGGj4eEk56en6WmpaSlpqeqqaioqKSlpqOgn52WmJqWlZSamouOkJSNjIj9gP2Hk4mEgoGFkpD97uDt8vP+9e719/yA9OXf3+HZ2N3b1tPOy8vN19fT1djd3+jp5uDW0c/S09CEzz3R0dDR0NDS09TX2dze4OPn8ff5+v6BgoSJjo6LiIeFhISDg4SDgoKGiIuOkZKSkpGSk5SUlZWVlpaXlpaXhZgSmZycnZ+goqGgnqCeoJ+goaCghp0Dm5mahJkWmpyfoaKkpqenpaWmpqiqq6yurrGxr4SrUKqnqKmqqqqsra6xtLW2ubu8vL6/v8DAv8HCw8TDxMXFxcbHyMnKy83R0dDQzczKyMfFxsbHycrKy83NaGlpatZsbG5vb3F0dnZ4eXp7fHx9hH4qf35+foCAgH+BhISGhYmLjY2NRURERUVFRkVGR0VDQkF+fT0+PD0+QUBChEMHRSUnJ0pISKeDgoKSg4KCiYOQhAaDg4OEhISIg56CuIEBf4R+A3+Af5F+/4C+gMx/AYCJfwWAgH9/f8iAn4GGgIOBhoDegQOAgYCJgYyAAYG5gP+BooGEggGBpoKOg4KCjYMGhISEg4ODAgIEAICYpaKem5mSjY6Sj46UlZWXmZeXlpSdpqqvrKy1ur25s7OuqaKdm5mWlpqkt6CttOjhmIqLi6W8ycuH683ahKKc966kpZ6fpLW1qLiAj6KNlqmF5sG1tMDI1OyAiouD69HHuqmfk4f/7eTb087NysXBuLCsqJ6ZlpWPjo2NjIqIhoCFhYSFh4H57ebh3tnY1NTV1tvc1tvd2NbTzszLxsPCv7y4uby4sq+uqqempaGgo6Kjpaikn5mWnJuOwIiJ/OyIz5Ta3c/BvLnc89bz9ueAgcizwavj4uHf3dnY1NHOysnKx8TCvr69uLSysK6trqqloJ6enJqbnJ6fnJqYmJaTk4CUk5OUlJaZnJ2Xl5ibmpyamJaTkJCMiYiIiIaEgoKAf3+Ag4OGioqJipSblpiVkJGXmZ2jrrKrra2rq6mopJyamJyXl5ieoqCeoJydn5+dmpmZmZeUk5SUkI2LiYiHh4aFhYSDhISDgoKChoeIh4SDhISFhYKCgICAfnx8e3t6elt6eXh3dnZ3d3d1dnZ2d+/u7u3s6+jm49/h3tzZ2NXU09LT0tLS0NLQz8/S0tDQ0dPS0dLT09PW1tfX1tra29zf3t3e4ODh4OLk5OJzdXTo5+fn5efq6et3d3d2hHcM7u7t6+zs7e7w7+/vhHkE8/Hy9IR6eHt7ent7fH1/f39+fn1+f4CAgYKDhYaGh4mNjY6QlJeXlZqeoKKlqK2tq6+vs7vD1ODg0dHU33Th3nNzcW5tacdlZmlvbWxlY2VobGxraWhnZWZnaGZlZGBiYmBgYWC+Xl5iYl5eYGFhYWBfXl5eXV5fX19hY2FhYIRiGGRpbWxrbW5ubm1ubG1samppamppaWpqaoRpF2VjYmFkZ2psa2xsbG5vcXBvb21vbmxqhGhHZ2ZmZ2lnZmRiYmFfX15eXV5dXFtbWlq1tbBXrKqsVlWqqaSlo6Sppqisp6SjoqOjoqOhoqGho6Kmpaiop6iqrauoqKipq6iFp4CorK2sq6urra+ysrS5v8bL0dfZ2+Hi3tfR0GVly2RfXVtdX2Biz9fYbGtrampqbGxpZ2ZmY2FhYGFjZGRkZWZoamtucHFzdHV2eHuAhIWDgYKHiY2SmJqbl5KUlJKRj4+Okpeam5mbmpmdnZ+kp7O2uru8v8HFx8rLxb64t7i9vYC3tLKwr6+wsbO2u77Dx83R19ra2Nna2dbY293g4uXn6ejq7Ojl5+ns7e/y9PPw8Pf+//v6+vj5+vv8/4CAgoWDhIWGiY2PkpaXmJibnaOkpqikoaGhoKChp6insbvCytjc9PHui4Dl7PH9jo2D+/n57I6kmZGKjp+al6vKz9vOzQrQ0M/rhoO4j4uRgH6FgX99e3d0dXd3dXp9e3x/e3t5d32FiYuIiY+TlJCOjYmEgHt5d3V0eH2GeYGEoqB0bGxse4iQkV2jkJlZamirgn5/e3yAh4V+iVhibF9kb1uhi4WHjpGWo1VbWlWajYiAdm5lX7Gnn5mUkpKRj4uFgX17dXFwb2tpaWpqaGZmgGhoZmhrZ8a9t7GuqaqssLG0vr+4v8G8u7ixr62qpaajo6CkqaOcl5WRj4+Oi4qJiY6QlpONhYWLi36jeXnbznuLgcTDuKukoNHux/H334KCsp6ulsLBwMK/u7q3tLOzsbCvrq2qqaeioJ6dnJydm5eUkpCPj4+Qk5OSkI2MjIyNgI6PkJKTlpqenpaVmZybmpeWlZKQj46LioeJiISDgoCAgoSIio+SkZGRn6ego56WmKCjqbC+wry+vru6uLSwqaakqaKho6qvrK2uqKqrq6ajoqGgn5yZmZeVkpCNjYyMi4qIiIiJiYiHiImNkJGSkY+Oj5COjIqIh4eEg4OCg4KBLoCBgX9+fn19fXt6enl47u3p6Ofm4+Lg393b2dbV09XU0tDR0NDQ0dPU1NTT1NSE1TnW19na2dvZ2Nrc29zf397e3+Dh4eHi5unmdXd37Orq5+jo6evteHh4eXl5eHjw7+3r6uvt7O7u7vCEeYDz8vP1enp7e3p5eXt8fX1+f39/fn5/gIKDhISEhYeKiYuPkZKUl5mamp+ipaarr7GzsbW2usLO3+/s2t/g7Hvv7nt9enh3dN1ydHd/f310cXV9g4aBfn17ent9fnt5eHNzc3BwcW3abW5zdG9ucHJ0c3FycnFwcHJycXN2end2dBZ1dXZ4eoGIh4eKjY6OjY+SkpGQkZKRhJARj4+Oi4aCeXVyc3Z5foKCiIeEiICGhoWEg4SEhoSEgoKDg4OCgX9/f31+fHx6eHl4e3t1cW9sbdbR1W3LyMxpa9LOyMnCx8rEyNDNycXCwcC9vLu7uru/wsW/wsHCx83FwsLCwMLHx8fFwcHFxMHAwb+9vr6+wcLExsvP09fc3uDi5OTj4uNxcuRxcHBvb29wcenu7wJ3d4R5CXh6enp7e3p5eoR7gHx9fX6AgYOHiYqJioyMjZGWmpuamJmcnJ+jpqipp6SnpJ+enp+en6Gkp6aoqKisrK+ws7y+v8LDxsnNz8/QzMbBwMLFxL+9u7q5uLm7vsDCxcnM0dXZ293c29zc29ve4ePk5ebq6+zt6unp6+/u8fP08/Ly9fn69/f49/f3+Pv7F31+gIKBgoSGh4iMj5GSk5WWmJyen56ehpwyn6Chp66zusTE0MzMcmzKz9HYdHNt1NXUznJ8d3RucXh1dH+RlJyWlZaWlaRbWot1dHiATVBOTEtKSUlKSktKTU5OTk9NTEtKTU9SU1JRUlNVVFFQTktHR0ZFRENERUZFRkZJSUJBQkNFSElHJkpHSCYpKkxISEtKSUpLSEVIKCkqJygqJkpHSEhIRkZGIyQiIT47Ozk0MjAtVVFPTUtKTE5QT0xLSUdFQ0NDQD9AQUJAP0GARERDRktJi4J+enh2d3uChouVmJCbnpWVk4yLiYSBhYOCgYeOh313dXJwb29tbG1tdXl/e3VtbXZ1aX5mZK+iZURsqaeUjoeAvNur5OzRe3ubgpF8pKOjpqahoZ6dnZ2en56dnpqamZWSkZCQkZSTjoyKiYmJioyQkY2LiYmIio1kkZOTlpadoaSimpufoqKgm5qbl5OUko+Qjo6NiYiIh4iKjpWWmZ+enJ2rtrCzraapr7O6wc3Tzc3OzMzIxsC4tbG3sbCzvMK+vr+3uLy6trGvr66sqaampKCempmZmJmYlpWVlYSXgJmdoKSlpaCfoKCfnZybmJWVk5KRkZCQkZCOjo6PjYyLioiGhYSC/PXz8e/q6OXh3+Lg3dvb2drb29zb3NrZ293f39/h4+Xm5uXl5ebn6erp6evq6+vt7vDw8fDx8fLx9Pn6+4CCgf/8/Pv5+fz+/4CBgoKCgYGB//379/j6+vr9d/z9/4GBgID+/f3/gIGCgIGCgYGChYWGhoWEhYWFh4qMjIuMjY+SlZWYmp2ho6WoqK2ws7O7v7++vMLBxs/X5vP16Ozq+IH9/4WHhoSDgfeAhYuVlpOJhYuXoqahm5uYl5mcnJaSkImLjYaGhYL+gIGJiIGChoeKhImAh4aFh4qJio2Sj42LjY6Oj5Kcqqupr7e3uLa3wsTDwsbGxMDBwsC/v7u1qZ6Mh4eGioyRmJukpKino6GgoqKgmZyiqailoqKlpqWjnpqcn6CioJ6al5iZoaGSi4iChf3w+YXw6faAhP748PHp8PPp8f/59Ovn5d7X1NPW1dbd4OFL1dXW3enu397g3djc4ejp5tva4dzW09PRz8zLy83P0tLT1NbY2dvf4OLm8fX4gID9gIKHioeDg4H9//+BgoSGhoaFhoeKjY6SlJWVhZaAmJeZmZmbnZ2dnJudnp+ipKWloqSmoaGkpqWmpaKjn5uanJ2bmpiYnJ6fn6GjpKWmp6qpqKmoq6ytrrGzsa+sq6uuramopqanqKipqqyur7Gztba5u728vL6+vsDBwsPExsfJyMnKy8rLzc7Q0dDPz83MzczMy8vMzc3NzM3NZWZNaGlpaWxub3BwcXN1d3Z3eXt8fX5+fn9/f4CAgYKDg4WGiYqIhoeIRkWMjoyORkVFio6MiUJERERCQkFBQEJERUdGSElJSEomJklHSE25gweEg4ODhISEi4OHhIiDhISIg6CCtIEIf39/fn5/gH+MfgV/f35+fv+AvIC9f4OAiX+IgIx/hICEf7qAA4GAgIaBAYCegQGA64EJgICAgYCAgIGBxoADgYGAiIGDgP+BlIGpgoKDhIKDg4SCk4OChISDAgIEAICdoaCempaTkpSem5aYmpqZm5iYmZugoaarqqars8LHz9PZ2dfHuKyloZuaoKyirsexlpSNi5PE8unw8fmMmq+zs66vh7i+oJqdnqWos6ueqqezy6KPk5iVmJ2dq7m/vb/CtrGnmpiWkP307ePW0cfBv7uzrqehnpyXlZORkJGOjoCNjIqJhP7z6uPf3NrV0M7Ny8vMzM3R1NXW08rGwL27t7W0sbCvrrGvra2uq6mqrKioo5+cnaCnqa2rrJ+ahoONsoXSx8G5tq/CwK2oq6evtba+yI/28/Hu7Ojo4t7a2NjX1dHQycLAvbq2sq+tqqalnp6en5+gnZ2fm5mXlZSTkoCSkJCRkZOTk5GRj46UoaCYkpGPjY2MiYiIiIaFg4KAfn+Ag4WFh4WCg5GTjIaGhoiQpK20t7e1t7W3trCzqqampaSlp6msrayopaOcnJyamZaVlZOSkI6NiomHh4aEgoF/fn+AgICBgYKDgoKCgIGCgoGCgoOCgIB/fX18e3p5eh15eXh4dnZ2dXV2dnbs7e3t7Ozr6+bi4NzY1tTU04TQZs7Pzs3Oz83MzM3Ozc7Nz8/Q0tLT1NbX1tXX19nb3N3c3d/j4uLj43Fx4+Tk5Obl5XR3d3h4eHl3dnZ2eHl6e/Lv7XZ3eXnw7+93d3j08vPzeXt7e3rxent6e3p7fHx9fX1+fX1+f4SAZoKEh4qLjI6RlZqbmJigoqapqqyurKuusbC0wMC8tra2vcfU2N1xdnp12Lutra6tsbOytri7wmJiYWBhZWZlZmVjYmFfYGFiYWJhYmNjZGNfYGFjY2NlZGJiYWFiYWJjY2JiYWBfYIRiP2Voa21vb21ucHJwb25xc3Jwbm1tbGxramloaGdkZWlqamprbG1xcW5wcXFyc3h3dXFxbmxqamppaWdnZ2VkY4VibmFgX15cW1pbW1taWllZr66uqaenp6ysqKmqqqymo6SkpKWnqaiopqWmp6erq6ipqqmsrq+urKqpqKiqqqipqaqqq66vsbSzsrO2vcjP0NPW0M7LysjJysnLzsnCwWHGx87R1dpsa2loaGdlZ2hnhGiAaWdkY2RmZ2Zna25yeIKIjJWXj46UmJufo56anZyYlpicmpqYmJyempSUlpWWnJ+em5ubnJ6eoKSmqq6zuL7Bx8jKyMbDuri5urq3sa6trKyurrC0t7q/wsTJzdLW19fY3d/e4uDg4+Tl5unt8fTy7Ort8fLx8fT19/r+gYGDgoJVgYGBgIGBgoSGh4eIiIqMj5GVmp2enZ2enp2foJyZl5ibn6Slqq+4xMzQ2umFgYiPjIyA+fn8hZWeo6OhtsTDs6ijq6WdwdTQuqiwxbCzwr+3sqCboH6AgoJ/fHp3dniAf3x8fn18fXt6eXyAgIOIh4OIjZmcpKisqqaakIZ/e3d3eIB7gJSDdHNta26LpqOlpqpeZnJ1dHJ0XImNe3l6e31/hYB4gH2DkHdvcnR0dnh4f4eKioqJgH12bWtpZbWvqaKcmJOQjIiCf3p1c3FubmxsbW2Ea4Bpa2fDvLWxrauopKOioaGipaans7a7u7WtqKOgn5uamZaVlJSWlpSUlpKPlZiWlZGOiouQmZ2kopmVj3l2f3l1vrSwqKectLCel5eVoauqs7qA1tPPzs7Jx8K9ubu8vLu3ta+qp6ajoJ2bmpiWlJKSkpGRlJKRkpKQjYuLi4qKim2LiouNj4+Ojo2OlKWimZKQjY2Mi4qIh4aFg4OCgYGBhIeMjZCNioybnpWOjI2RnK29w8bGxsfFx8jBwriysbCxsra8u7y8t7OxqKalop+dm5uZmJaVko+MiomJiIaEg4KCg4WGh4eJiouMi4qLhI0pi4qJh4aFhIWDgoGBf39/fn59fXx8fHp4eO7u7Oro5ePg3Nvb2NTT0tCFz4TOVs/P0NLT0tLR0tPR0dPW2NjY2dna2Nnb3N7j4uDh4+bm6enodnbs7Onn6Ojrdnh5e3t8e3l4eHd5e3x68O7teHp6eO7v73h5ePHx8vV7fHx7e/N5eXl7hXyAfXx8fn5/gICBg4OEhouNjY+Rk5ednZyepKirrrCwsbCws7W0t8XFw7q4u8XU3uTsfIKGf+nEuLi6vMHDxcnL0NpwcHBvcXh7ent6dHJzcHFyc3J1cnN1dHV0cHBzeXl6fHp3dXZ1d3V0dnd2d3Vzc3N1dHN0eXx9gIWJjo+JjZAzkpGNjo+QkI+Pj46LhYOAfnt3d3t8f4CDhoKEgn9/gIODhIeHhoOEhYWEhIaHhYKDhIODhYJSg4F7eXZ0b21vb29wcXBw1NPSyMbHyNLTxMbOz9PJwsLAv77BxsfFwsbLyMnMzczIx8TJz8rFxMbIycrJyMjGxsDAv8DAwcTCw8XIy9DT1djb24TaY9vc3d/i397gcOPk5+rt73h4eHl7enl5e3t8fXx9fn99fX1+gH+AhIiKj5idoaiqo6KnqKqvsq2pq6uopqepqqmnp6qrp6Kio6Okp6mqqaioqqysrrKzt7u+wcXIy87OzMrIwoTAgL66ubi3t7e5ury+wMXIys7S1dna2tzf4eHj4uPl5ufm6u3w8/Hu7vH09fX09vb4+f2AgIGBgYB/f35/f3+Bg4SEhYaIioyOkJSWl5eWmJqam5qXlZWWmZueoKOnrbe9vsXQcG10d3N1btbZ2W54fH5+foiOj4aAf4J/eoyYl4yCC4WSh4uTkYyLgYCCV05MS0tJSEdISk1NTk5OTUxMS0tKS01MTk9PTk9TWFlcXmBfXVlSTUlIRkNCRUVHTUdDREJBQUVJSUpJSygoKywqKi0pSkpIR0hISUhIRkNFREVHQkFCQoRDgERER0VEQj47OTc0MzFcWlhXU1NVU09MSUdFQ0FAP0FBQUNDQkJCQUNHQ397eHdzcXFvcXFxc3V5fH6Mk5eXkYiEgH19enl3dXZ0dHZ2c3R4c3F5fn5/endzdnuFiZCOgIN9ZWFpP12onZiTkoihmoWBg3+KlZifp22xraysrqmpP6ShoKKjpaSioZ6amJaTkI6Ojo2Mi4mJiYqMj4yNj42MioiIh4iKioqLjZGTkpCRjpCarKeclpSRj4+NjIuLiYaIXYuPlpmZnJqWl6qspZqZmpypvsrS09TX19TV19HRxMC+wMHGyc3Ozs3JxMG2tLOvq6elpaSkoqCempeUlZWTkZCNjI2PkZGTl5mam5uampqcnJybm5uZlpOTk5KRkIWNMoyMioqJioiGhIL++PDt6urm4d/e3Nva2dfX19bX2Nrc29vc3N3d4OTi4ODh4eLi4+TmhOlE5unq6u/y9fTz9Pb5/f/9/IGB///8+vv6/ICEhIWHiIiFgICBg4WGhP/+/YCBgYD+/v+AgYH+/f7+gIKBgoH+gIGBgoSEg4CCgoODg4WHh4iJiouNkpeXmJuepKmpp6qysbW5ur2+vLu+wcDDz8/MyMbJ0t/q7/qEi5GI/NbJy9HS297g5ufs/IKFg4KFkpeWl5SLiYeFhomKiYuHhomIi4mCgoePkJKXlZGOj42PjIuNjYyNioeIiYuIhoaPlJaZoqqzt6iut4C+vrCysry7ury5t7Gin5eSj4mIjo2Rk5uhlZOTk4+QlJWWlpmYlpyhn6CnqqumoaSnpqioqKmqq6yonpqUj4SBhoeHiYuJifz6/Ovn6uz9/eXo9fj+6+Hf3NXS197h3t3j6efp7vDs6ubh6O/i2N3k6e3t5+bn4+TY1NHSzs3Rz3DU19XR0NPV19nf4uXo7evu9PT09fj9gP7//fr9/YCChoeKi4yJio2NkI+Sk5WWl5mZmpmam52eoKSmp6mop6anp6mrrKmoqainpqWkpaOhoqKhnZuam5ycm5yeoKGho6SlpKamqKurq6yvsLCwr66thKkkqqmpp6amp6ioqKmqq6yvsLK0tre6u7u+v7/CwsLDxcXGxsfJhMwKzc/P0NHQz8/Q0oVpAWiEZ0dmZ2hqamtrbW5vcHBxcXR3d3d4eXl7fHx9fX1/gIGChIWGioiJiIZFRUZGRkdGjY6LRENERURDRUZHRkZFRkZFR0hJSEZIS4RKBU1NTk5QvIOIhKSDnYK1gQGAhX8CgH+Rfv+AuoC8f4KAh3+PgIN/hIAGf39/gICAhH+FgAF/uYCEgY2A/4GHgc+AAYGGgP+BioGxgoeDg4KfgwICBACAnpuam52bnp6coaGcm5mWlZibnZ+jo5+dnZ+fpaq4zNXd3+Xj1sTBtKqrycOxsq7IqpyhlZWVo9b6072yr6jI9bfCkda3pKSrpp+cmZWVlZmXlZOPkpWNipCQlJuvyL+5ubSnppKLlIT06eLb1Mu/vLu5tbKro6CdmpaTkY2Oko58jI6MhPv28uvp6eXh3dnTz83LycbGx8jN0MjBvry5ubaztrS0tb+zraqmq6qqpqGem5mZmp2irLTCxcO1rIqNh73ysLKom42c0ue1wNPG2NvI5++XhoD/+fb39fDq6Ofk393e29HJxMXCvLWwq6inpqOhpKenop+cmpiWlISScpGOjY+OjY6NjIuMjIqNkZiYnZeXlpCOjYuKiIWCf399fH1+gIKDgH9/fH1+gYGChoqWs7+4vL66trCnpKmlnJmYlZqepK6vrq+xr6mgnZmUkZCQjoyLioiHhoSEhIB/fn19fHx9fn59fn+AgYWDgoKDgYSAPn9+fXx8fHp4eXt6eXh3d3Zzc3R0dOjq6+vq6+zq5+Ld3NjV09PU0dHPz8/LzM3Ozs7Ny8rMzc7Nz8/PztDThNVB1tbY3Nzb2dve4d/g3+Dg4eLh5OXlc3R0dHN1d3d4d3ft6nZ4eHh5eXh4d3h4eO/v73l7fO/z8/X2e3t78/F6fHyEe1t8fX19fn18fHx+fn+AgYGDhoqMj5WZnZ2anaGnsLyzrK6sq6uoqKSnqailrLGxsLa2s7W4u8O0rKussLKyr7GxtLrBxL27ubi6YGBiY2VjYmJiYV9gYmNqZ2VkhGMwZGRkZWNjYl9hY2RlaWlpaGppa2hoZ2VkY2JkZmdqamxwcnN0c3J1dXd0eXRxc3JvhG2AbGxsbWxsbm5sbG1tb29wcHBxdHd2c3V0b21sa2xpaGhoZ2dlZWRkY2JiYWBgYWBgYWRkYV5dXVtbWbOyWVlYWFeuq6uvWK2rq62vr66urq2qrK+rqq2traytrrK3uLSuq6pUqKeoqKqsrrCytLe7vLy+vsHFxMTEx8XDwMDAxMITxMXGxsbHysrQ1djcbWpqaGdmZIRjgGRlaGtsa2loZmltdXqMoqq3y8vBv8XWx8KzsbatqauvsK2mqrO0r6mop6ShnqCkpKampaKfnZ2krammq6alqbG2ury8v8PBw726u7y6urOvrK2trq6vsra5vL7AxMjM0dPV19nb4ejt6+jp6uzs7/H2+ff19fTz8vLz8/T2+fx+Wf6BgoODg4aFhIOEhoWGh4iIio2Rk5aZm5ubnJ+ipqapqaalp6qsr7W/y9HZ4uz4/P/38/Tl4N/h5eft/Ij9iaSmmY2F+vmCgpfGyrPKpKCRg4WMkpmgn6KjgIF+fHx9fH5/f4KDgH98eXh5en1/goJ9e3x9fIOHkp+orq+xsqabmI6CgpCKgYOCkX53eHJzc3mWqpSJg4B8j6p2fWKaiX5/g397d3d0c3J1c3FwbXBxbGpvb3F0f42IhIKAeXdqZ2tfsamln5uUjY2MiIOAfHd0cnFubWtqa29sgGpsamXAubWzsbCuqqakoqCfnp2dnaClsbWpo6CcnJ2XlpqYmZuqmZGQjJSSk4+LiIWDhYaLkqGrvcG2r6aBhHZ9zZWUin9xf8bhqK/GsszTtd7qjnx15N3a3NrTy8fIxL6/wr+2sKysqaWem5mXlZSVlpeam5eTkI+PjoyJiYmIBYeGh4eHhYiAh4eHjZKamZ6YlpKMi4qIhoWDgoCAf3+AhIeKiYWFhIODhIeIiIqTosDNxsrNy8a/t7G2sqiin56lrbS+vr3Av7uxp6OdmpWUlJKRkI+Mi4qIhoaEg4KBgH9/gIKDgoOEh4mMjIyLi4qLiYeFg4ODgoGAf35/f359fXx8e3p6eXlSeO/t6unm4+He29nY1dPS0c/Ozc3Nzs/Oz87Oz87O0NDQz8/Q0tDU1dbZ29rZ2trb3+Xi4ePm5ufj6Ojn5+Xj5ufo63Z2d3h3eHl5enl48O93eYR6Fnh4eHp7efDv8Xt8evDv8fLyent79vaEe3l8fX18fH19fX59fX5/f4KDhISGio2OkZqgoqGgoqWrtcG4sbOvq6yqrKWmqammrrW3tru7uLvAxcy8sbG0ur27ur/AxM3V2tbT0M7UcHBzdHh2dXRzcG5yd3aAeXV0c3NzdXh3d3l2eHRzeHh6fH9+gYGCgYOBgX17hHYjdXd8foGLioiHhYmIiYqLjI2Pj42Kh4eGhYKBf4SFh4iJh4aEh1mIiYmKi4yMiomIh4WEg4SFhYSGhYaGhYODgn9+e3p4e3x+fnl5fHNxcnJxbNbWbWxtbm7Szs/UbNHLycrMzc7P0tHNz9HS0dPT0dHP0NDSz8/Nz9Fq0cvLyITGI8fHx8jIyc7S0dHT1NPU0tPW1tja2Nja2tze3uHi5Ojs7Xl5hHqAe3p6fHt7e31+gIKBgICChoyPnbC3wNHRycjM2M7Kvbq+ubW2ub28tLW7vru2trOxraurrq6wsbCuraytsri1s7iztLi+wcPExMbIyMvEwMHDwsK9ubi3uLi5u72/wMLFxsjLz9PV19jZ3uLo6+zp6ers6+7x8/b29PT09fT09vcH+Pf5/oD/gYSCU4ODg4KDhISFhISFh4mMj5CTlZWVlpmcn5+hoZ+goqSmqKy0vMHGy9La3t/c29zPy8rLy8zO1m/ScH+BeXNv1dVtbniTlYiUgX92b3B1eHuBgoSFgExKSEdISEpMTE5PTk1LSUlJSkxLTU5LSUpMSk1PVVxeYWNkZGBaV1BLSUpGQkVHSkRCRERDQUJGSUZFREVERkosLihLSEhJS0lHRURDQkFAQUA/Pz9APz5AQEFAQUNCQEA+Ozk4NzUyX11cWFVSUVNST0tIRkRCQEBBQUBBQ0VDgEJEQ0F8dXNzdXRvbWtqa2xubWxtb3N8jJKCfHl2dnhzcXd1d3uNd29vbXh2eHRvbWppbG5zfI6Yr7SanZVtbmA9nHRzbGFXYrfUmZe1mq+zmMPTd2tkxL++v722ramopqOjp6Sfm5iZlpKOjYuKioqLjZCSlJCNi4mIh4WFhYaFgIODhoaJiYiHh4aHiIqRl52eo5yalpCPjImHhoeGh4mHiIqOk5aVkZCPjIyPkpKUl5+tzNnQ1djZ1szEvsLAta6rq7W6xM7Oy87NycC2rqijnp2enZuamJeVk5CQkI+OjIqKiYqMjY+OkZaWmJ2cm5uamJeXlpSRkJCPj4yLi4qLgIuLiomIiIWFhIKC/ffv7ebi4uDe2tjY19bX1tjY2dnZ297b3N7e3N3e3d7e39/h4ebm6Ort6+rq6u3z+Pj3+vn7+/f7/fz39/n7+vz/gIGDg4KDhIWFg4H9+oCEhYKDg4CCg4SFgv78/oSGgvr6+/v+gIGA//yAgoGAgYKCg4KDgIKDg4GBgYWGiIqKi42SlpiZoqqtrKirrba+x8C7vLaytbGyrKyzsa63vb+/xMXCx87S2cjAwcTL0c/O1dfc5fD38fP07/eEhIqMkI2LiYiEgoePjpqPiYiHh4eIjIyNko6PjIiPj5GTmJebm5yanpucmZWOjIqLiouTlJinqaKeIp+jnqWfpqGrtK+tqZ+enZuWkI+Zmp+ipqKgoJ6fn6CkpKOEpICdnKChnJuho6Wmp6eqrKyqqaejoZiZlZmboaKTj5yKhYmKioD9+4GCgoOE9u/u+oH46eHf4ubn6e7s6e308/D3+/f17+zr6eXn7/j9gvnu7+bg2NXU2tjPztHT2t7Z19vh39zc4efp7vDq6uvs8O/t8fPy9vj5gISHiIqMjpGQkSiSkpKRkJGTl5iam52dnqOorbG2t7Szs7exsKupq6qqqqurqqmlp6mnhKRWop6fn56eoaOjo6anpqinqKioqaqtrq+urq6vrq+rqaiqq6qpqKempaWmqKipq6uqrK6vsbO0trm7vL2+wcLDw8TExsjHycrMzs7NztDR0tPT1NXWa9WEalRpamlqaWpqaWlqa2xsbXBxcnFycnR2d3h6e3x9fX5/goODhYmMjI+Qj5CRlJWVlZORj42KiYqJRYlFRUZFRkeOjUdHSElJSElGRkdHR0hJTE1OTk3Ag4OEpYOcgraBAYCFfwGAkn4DgIGB/4C2gMJ/i4CCf4yABn9/f4CAgIV/BYCAgH9/0ID/gYWBgoCFgYSAAYGcgAGBqYD/gYuBAoKBu4ICg4KGg4KCk4MCAgQAgKypqKipqqypo6GfmpiXmpqcn6KkoZ2bm5+fn6WrvMLR2uPv6t/h0r+xua+qrbbN26+spqaopqe68OnHx8vIgpOJ/9H/1J+lrqmknpuXlJWSjYmJiIiIhYWLlpeVk5mVo6SglYaCiYeC/vXo2tTRx8bCubaxrKWfnJqYlZOOjYyLgImMiYP99O3r6+rp3OPg29bSz87KyMnHwsC+v768ura3uLe4uLSxqaakpKGgop2am5qbpJ+eoqeuxsyJi4n6+IapicjJv7mwubm2trS+v8a/vMPF/oSEh4eKiIH38+rl5eHe3tvXz8vIx8O6tbKzsK6sq660s62kop2bmJaUlJWVJJORkY+Ojo2MjIuLioiJio2Qk4+Pj42Lh4aGg4D+fvf0e35/f4V+QoCBgoOEg4ubrrO4ubmroqOclZGam6Wkm5aSj5amqKmlp6yup6Gblo+Oj42Jh4aGhIF/f359fHx7e3p7e3x8fX6Ag4WEgoKFgYB+fXx7enp6eXl5eHd2dnRyc3R0c+fo6urp6OXm4t3Z1tXT1NXS0dPQ0dDQ0tDOz8/Ozc7Oz87Oz83Oz8/R0tTU2NbZbm7a2dzc3dze4OHi5eRzdHRzc3V0dHXqdHXq6Ol2d3l4eHfweO52d+7v7+7v7+7v9PLy8vHw8/b19PT09mR7fHx9fX17fH5+fn1+fn59f4CCg4WFiIqLj5aXmpmWl5ykqa+nqLKzt7S0saypqKqrsbSxrqmstMHDuraxs7q+ycm6t7m9v8XDvri0s7O1v2FiZmZiYmNlZmhoaWhoZ2ZnaGhphGqAa2tqZmNeXl9eX2FhYmNmbG1vbmxoZWBhY2NjZmpsbmxrbW9xcnN2end0eXx6dnRzcm9ubm5tbG1tbm9vbm5vb3BvcHBvcHFwcW9ubW5tbGtpaWhnZmdnaGZlZGRjYmNiYmBfX11cX2BiX12zs1laXF1bs7Cysllbt7i3uLq5XF0SuLS3uLSyWFhYWVhYWllbWbBXhFUqVq6vsbKys7S3vr6/vr+8urq6uby/v8HAwMLGyMjKysfHycrN0dPUamlnhGaAZWZmZWVmZ2ltb2xpbXV5iZOhrrzK3eHPtrzK1d/Tz83GwcPFwb/DzcTKz8nEvrm0r7Cxtbq7ubWtpqOmqKWmpKSorbO0tbS1uby8vru8uLOysLCvsK+wsbGztLa5ur3AxcnL0Nbb3t/h5ubv8/b29fTy8vT0+PyAgYH+9vDu7fFd9Pn9gICChoeKjZGSj42MjY2MjY6QkZKVl5mZm5ydoaatr66xsa6vsbS5vsbO1uTr7PeA+ez19u/r5uPe3uP3iZmPnKeyou7h4d/l7/yCgoyKh4SGiIqTnJ6hpKmsgIqIh4eGiImGg4GAfHp6ent8foCBgH57e4B9foSJlZumrbG7ua+vpZSIi4N/gYWTnISBe32Af36Ip6KPj5COWGJbrZStkXp+goF+enh0c3FvbGlpZ2dnZWVpb3FvbnFvdnV0bGNiZWFdtrCmnJqWj5COiISAfXZzcXBvbmxqampogGhoZmG8trOysbCxp6yqp6Kgn5+enJyamZiXmpqXlpWXl5mdn5mWj4mHh4WGiIWDgoOHlYyNkZqhv8h+goDi33V0eLW3raieoqinqKazsb23sbq56Hp5fHt/fnXd1c7IxcHCwr+7sq6sq6ihnpydnZybm56lp56VkY+NjIuLi4yNYIyJiYiHhoeHhoaFhIWHiI2RlpGPjouKh4SEgH/7fPb2fIGEhYSDgoODhIaIiImJkqe6vcPExbarraWdm6Ons66jnpqYoLKzs7C0ubiwp6CblJKTkY2Mi4mHhYKAgIF/f4V+FoCBhISGiYyNkI+Oi4qJiIaFhISDgYCEf1p+fX19fHt6eXl4d3bs6ubl4t/d29nW1NPR0tPU09PT0dDS0tLR0NDQz8/P0NHQ0tTU1tfW19jb3N7g5HR04eDj4d/h4+Xk5+vrdnd1dXZ4d3Z37Xd47+7teHqEeSTxeO93d+7w7Ovv8O/w8O/w8O/v8fPy8vL09nt9f39+fn19fXyEfYB/foCBhIWGiIyNj5OYm56dmZqepauyqqu2t7m1sq+rp6eprLC2sq6rrrjIyb63tLnDx9XWxcPJz9Pa1s/Lx8bGydhwcnZ5c3N1eHp+foB+fXp5ent7foGCgYOEhIB8dm9ubm5yc3J1d36FiIqNjX97dXV1dHR6foGEgH1+gH+Ag4CGi42OjYyMioqKiIWFh4mJiImIiYqKiImJiYqLjI6OjoyLi4qJiYeHh4mHhoWEg4WFhYGAe3l5eHp+fHZyc3Bwdnh5dG/U1W1udHt01NHU1mxv3dra3N3ecHDf2trc2tpubnBxcXBwcG9s121rbG1ratTV19jW1tTS09LU1dza1CDX2NnY2NfZ2Nnb3d/c3d7i4eLj5ufr7nh5enp6e319fIR9gH6Ag4OBgYSKjZqirLbCzNrg0by/ydXc1c/OycbJzMnHyNDHztPPzcfCv7y7ur/CwsG/urOytbWzsrOztrq/v8DAwcTFxsfExcK+vr68ubm6u7q8vb2/wcHEx8nLztLV2d7g4uTm7O7y8/Lx7/Dy8/f6fn9//fn08vP19/v+f4CCV4SGh4mMjYyLiomKiYmJi4yNj5GSk5SUlZmdoaSkpqelpqirr7O5vsTP1NXccd7V3N3a1c/LycfJ03B5c3uAh3/Nx8bIzNLZb25zc3Fvb3Fyen9/goaJioBOTEtKS0tNTk1NS0pJSUpJSUpLTU1LSUpNS0xQU1lcYmZnbGljYVtTTUpHRURFSktGR0ZISUhHRktKR0dHRSUnJkxITUNDR0lIR0ZFQkFAPz48Ozs6Ojk6Ozw8PDs8PDw6OTc1NTUzMWBcV1NRT05PT01LSUdDQkA/QEFBQUJCQAc/Pz07dHBwhHEGaW1sa2lphGpuaWlramxwcG5ubXBwc3l8eHZtaWdnZWVqaGZnaG19dnV8hI2utGhvb7y6XTtdnJqamIeFk5STkaGepqeeqazJaWhsa25uZ8K5sKuppaampZ+YlZaXlY+NjZCPjY+QlJ6gl42JhYSEg4SGiYqJhoWEhICFhIWGhYeJjJCWmZSTkY+NiIWEgoD+gP7/g4mNj42MjIyNjo+Rk5OTnbHBxs7QzsO3tbOrp7Cyvruxqqikrb7CxMDBxMS8s6ulnZucm5eVlJOQjomJiImJiYiIiImJjIyPkZaYnJ2cnZyamZiWlpSSkY+OjoyMjIqJiYmIiIeGhICDgoGB/PPs6uPg3d3a2djY2dnd3t7e3Nzg4OHh4N/e3t7f39/g4uPj5ebo6Orr7vDx9fyBgPj19/bz8/T3+fz+/4GCgICChIOBgP+Bgf///4GDg4KCgf+A/4CA+v34+Pv9+v3+9vT19fT4/Pv7/Pz/gIGCg4KBgICCgYGAgYGCg4CEh4qLjY+Tlpiao6SmpaKipaywtq+zvLy9ubWzsK6ur7O4vbq2sra/0dPIwr7G0dXm7dnW3OXo8O7o4uHi4eP6g4WMj4aJipKUmZqcl5SQi46Pj5SYm5yeoaGbk4uDgoCAhIaGiYuWoKSora+ak4mKiYeHj5SYnJiRkJONkZWXnYCkqaGdnZycnJ2Zl5ugoqGhoaKko56foKKlqKqvsK6rp6Slo6GioaOpp6SjpKWlpqWeoJaPkZCSm5mOiIaChI+TkYqB9fWBgouYi/Tu8PmAgv729/j6/ICA//v7/fv+g4WKi42Jh4SEgf+EgoOEgYD6+Pj89/jy6eLf4eX09ev0+GT68ejr7urt8Ozr6Ort9PTz8vb1+f6BhoiLjI6PkZKTk5SUlJWUlZmampqZnqCipKmutbevp6eqr7Owr6yrqayuraqsrqisrqyrqqqqqKelp6mpqaqpqKeopqWlpaapqqurrK6vhLA3rq6tq6mpqaamp6imp6mpq6qqqqutr7CxtLe4uru9vcDBxMTDw8TGxsnMzmhoaNPT0tPU1NfX14VrBGxtb2+EbghtbG1sbGxtb4ZwP3Fzd3t9fH59f4CCg4WHiIuPkZCUS5aVl5eVlJGNjYuKikVGRkdISEeNi4mKjI+ORkRFRkZEREZGSEtMTU1OTr6Dg4Sng5yCtoEIgH9/fn5/gH+RfgF/h4HBgAR/gH9/64Cvf4KAjH+JgAZ/gIB/f3+GgAV/gH+AgJV/0ID/gYSBgoCFgYSAgoGGgIKBhoCKgQGAhoGmgP6Bg4KJga+CAYOMgoeDh4KQgwICBACAvr6/wb6/wLqzrKempKWnpqOlp6impqanpaGkpqi1xtTg5u+AhIb7583Evr7GyrrEz8b8rf65xcu4sbDr4tvc1cCvnJmVkJOXmZqamZiWk5COjYqJh4WGjpClkpWMo56WpqOfkoqPjIb/9vXu5NrRycfBw7qxq6aioJ6alJKVl5aAkY6LiIWA+/Pw6+vp5N/d29LL0M7My8nIxb/Cwb6/vLm3t7ayrqqnpqWlo6Ojn5ygn5ucnaato5+dh4ubmYPggsODvLytpdDQ6b/BxrjC3dDI3cegkpOOjpWFgfjt5eLe4d7c29XPy8jFw8HCw8jJx8C+xcO1raWjn52dnJiWlpZglJOSkpCQj46OjYuKioyNjI6MiIiIhoSCgYGAfvp9fHt7fHt8foGBf39/fn1/g4aVrammp5ygnJCMjY2NlZmVlJWXnJaRlZqio66rppiWk5CRj42Ni4eEg4OBgH59e3p7hHoVe31+fn+Bg4SFh4WFhYOCgoGBgX99hHyAe3p7e3p4dnZ1dXR05+bn5ebn5uPi3t3Z1tPT0dHS0NDS0tDS1NPQz8/P0M/Q0tLR0NDQz9DS0tTW2NjW2Nnb3dvb3XBwcOLj4+bj5Ofp5+jo5+nr63V2dnV17e7w7e7t7u7u7ezu7u7w7e7t7/Ly7/Dy8O/x8PD09PX5ffr7+H2A+Pl9f35+fX9/foCChoqNkZGSmZianZyan6OnqKiosrW4ubm7zs62sK2rr7GqqKmus7W3vrizusXI0tXJyMvJyM3LyMS8tre0u7y3uL6/YWRkYmdnaGhsbXBvbmtsbnBzcnRzdG1paGdnZGJivbe2trdeYWNiY2FjYmFhZWRkZGNwZGRlaGxvb3F3fHh4eXl5eHd4c3Fvb25tbm5tbm5vbm9wcHJycXF0dHN1dnRxcHBubWxsa2xqaGdqamdlZGZmZ2ZlZWVmaGZnZWRjZmpsX15eXLdeX2BeX15eX2BgYGFgY2NhXl9eXl5cWllaWVlaWYVaB1laWVlaWlqEW4C5urq4uLi3ury9wcfDxcbFxcfO0NDPzs3NaGhoaWloZ2hoZ2dmZmZnZ2ZlZ2hpa2trbnF3fIKIkJqotbexr66yu8HHzM/Q1Nzg39zb3Nzd4eLb1dbTzsPDxM7R09HPxL21r6qjo6SlpaaprK6vsa6trq6zuLmzr66xsbKztLW0toC3t7m6vcLGx8vQ2N3f4OLp6u/2+Pr49ff5+/r+gIH/gIOC/fj1+PuBhYeHioyPkpSUk5KQj5CSkZCRlZeanp+fn52doKWrsbe8vby1sbe+x9Pc5vLz9fXz7ujq9fb28fmE6uDq8faA9O3q8/r29vj5gIWDgYGEh4mNkJOUmaGkqQOvu76EmICXmJiTjomFhIODhYWDhIWEg4OEhIJ+gIKFj56qsLW9ZGdoxbGdlpGPlJWMk5qSsXOyio+TiIOBo52YmJSIf3VzcG1ucnR1dnV0c3BubGppaGZjZWpqdmtsZ3JwbXVycWljZmVgt7GwqqKclpCQjYyFfnp3dHNzcGxtcHBua2lnZIBhXrm0srGysK2qqKWgnKGfnp2bm5qVl5mWl5WUk5OSj4yKiYmIh4aGhoSDhoSDh4iWoJSMjHd5lph4wW5/a6alk47Cvt6yt7aqsbqts7+wlYaIhIWNenTg08jGxMPCwL24sK2sqqinqay1s7GrqrCtopyWlJCQlJCOjo2NjYyKigGJhIiAh4eGh4qKioyKiIeGhIKCgYB/ffl8fH18gICBgYSEg4OEgoKEh42burSxs6iqpZeTlJSUn6Genp6gpp+anqesrLm1rZ+ampeUk5CRjouJh4eEgoB/fn19fX59foCBhIaHh4mLj5GRkI2KioiIh4aGhYOCgYKCgIF/f318e3l4d3dK7Onn5uTh393b2NbT0dDP0NPU09TW1tXX19bV09LS09TU1dTU1dfW1tjY2Nnc3t7g5eLi4+Xi5HV3denn6u3p6Ozv6+nq6uvt7XeEeIDv7e/u8O/v7u7s7u7t7vDs7Orp6+3s7u/t7e3s7O7u8fZ8+Pj3e/f5fH5+fn1+foCChIeOkJKUlp2dnqOin6Omq62rrLi4uru6vdTQu7OvrbGzrKmrsba4usG5tcDM1eDk1tTc2tnh39zWzcjLxsvPy8vT1m90dXR5e319hIaGhD2Cfn6DipGSlZSSiYJ+e3l2dHLb0tLS1W50d3l7d3p4dXd8eHd2dHRzdHV9goKEh4eLiouLi4qKi4mHiIqKhIk/iomIh4mKioyOjYyMioiJioqJiYqJiYmFhIeFg4KGg314dXd4fX6AgH2AhIOHhoF9gIWDcnJyb951d3p5eXR0hHVAdnd6eXh1dXR1dnZzcnR0dXZ1dnRycG5wcnJwbm5tbW1ubt3X19XU19nZ1tbb3t3e3t/g4+bm5ebl5+t2d3d5eYR6Bnt7fH19fYR+AoCChIOAhYiMkZabo664uLS0s7a9w8fLzdHT2dza2drd3d3f4NvX2dbUysnM0tTW1NTMx8O+t7Oys7SztLa4vL2/vLq6u7zCw7+8vL+9vb6/v76+vsDAwsXJyszP0tfc3+Dh5eru8fP08/Lz9/j4+n5//n+Bgf76+Pr/goSGhoiJjI6Pj5ACj46EjUuMjY+Rk5aYl5eWlZeanqOqrrCvqqersrrAyM/X2dzd29fS1d3d2dTXcNDKz9PWbdTR0tbZ19nZ2m9ycG5tcHJ0dXd5en6DhYiOlZiEUw9SU1NSUU9NTEtMTUxLTE2EToBPTktMUFNZX2ZnaGw5OTlpYFRPTEtMS0pLT01PLFFJSUdGRkVJR0ZHRkNBQD8+PT4/QEFCQ0JBPz49Ozo6OTg4OTk6ODc2ODc3ODg2NTQzMjFgXFtYVFJRT05NTUpGQ0FAQEFBQUJFREI/Pjw6ODhtbW5wcm9ta2ppZ2ZpaWloZxNnZ2RnaWptaWhnZ2hmZWVkZGVmhWNWZ2dmam6AjHx0dWJhiYllmVU+UYSBc3OuosmlpZ+ZmJeRlpmUfnR2c3R8bGbFt7CtqqqopqSelpOTlJSVl5qjop+dnKKgl4+LiYaGjIqIiImJiYiJiIaEh4CGhoeJi4yMkIyJh4aFg4OCgYKA/4GAgIKHiYqJjI2LjI2MjI6TmqfCvrq7sbavop2hnp6qrKmpqqyyq6Sqsrm3wL24rKWjoJ6dmpqWk5CPj42KiIeFhYWGiIeIio2RkZKUmZyeoJ+enJqZl5WUlJGQkZGQj46Oj46KiYiGhYOCgID+/fjw6+fj393b2djZ2drc3uDe3uHj5OPj5OPh4+Lh4uTl5ePj5OXl5ujo7PDx9PT69vHy9vX6goWA/vv9/vr7/f78/v78+/7+gIGBgYD+//7++/z7/Pz89/b2+Pr18/Ly9vXy8/Pw8vPx8fL2+v2A///+gP7/gIKBgYGCg4SFiYCNlJmbnJ6lpKarqqasrbGysLC7vL/AwMHT0sC6t7O4u7Sxs7i/wMLGwb7I2eLv9ubn8PDs9vb07eLb4N/m6OXm8/aAhoiKkZOZmaChoZ6ZlJKZpbS5vbu4p5uUj42Jh4T47+/w9YCKj5KZkJORjY6VjIuKh4WEgoWRmJaYl5acmoCZmZmYmpmam5ygpaKhoqKjn5ybnZ6go6mopqKemZeZnJ+hpKSkop2bn56bmqKdkoqGiIqSmJuZlZqioqmnnZOXnpaDhYaB/4mRlZOSiIaFhYWEhYeKiYiGh4WHiY2Ki46QkpSQlJKMhoGFjI2IgYOAgYGCgP7y9fHz9vr38PHx7x/v8/T09ff59/f5+fz/gYGAgYKEhoaJio2PkJGSlJSVhJZLl5eVk5GRkpGVlpujop2dmpueoaOlp6mqra+ura6usLGysa6vsLGxraupra+vr7GtrK2tq6ioqKmoqKmpq6yurquqq62vr6qnpqiohKlDqKioqamprKytr7CztLe4uLm8vb/AwcPDxMbHyMvNZmfRaGlq1NPT1dZrbG1sbW1ub3JxcG9vbm9vbm5tbW5vcXJzc4RyPnN2eX1+fn19f4GEhouMj4+SlJWVlZaYlpOOjUaKio6Qj0aOkJKSkZCRkI9IR0ZFRUVGRkZHSEhJTE5PUFNTo4ODhI2DAYS0g56CtIEIgH9/f35/gH+RfgGAh4HBgAF/7IC3f4OAj3+FgKF/B4B/f3+Af3/MgJ6BhYDhgQGArYGZgP+BhIEGgoKBgoKChYG5ggGDhYIBg4mCk4MCAgQAgM3Q1NXVztHQxr26u7y9vLm5sayrr6+tqqijpaSmrbS9zNzt+YaHhoHv7eDY2ePi29/bgZyI5pvElM7Eysngx8PJvLGel5WUlZKTlZSSkZGRjoyMiIaDg4OFmaezrbWsk5GRi4SAgICFiffk3tnUzcvIxcC6uLStqKSXnJqWnKOlgJySjouHhfz49vHr6OXf3tzc29na1tPOzM/N0NLOzMnGw8G+ubOxsa+rrKyrqqalp6inpaShoJ2WjoSghYDqxYKzkNDNu7a5tbKsqq2xs7y3v8XI7oiTkZCSh//z6uXe2tjW1tTT0MzLycnK0Nvl4eDVxcK8sqmkn56doKCbm5uce5iVk5WWlZWSkpCQkpWamJGNjYqHhYKA/fr6/Pl9fX1+f4KAf36BgX99fHt7e3+PiYiEgYKFh4aFhIeKj46MlJygmpKLiImKkJiOjIqIh4WIkI+MiYeGhISBf35+fXt6fH1+gIF/fnx+goODgoKEg4SDhIOCgoODgYCBf4WANX99enh4d3Z27Ovp6OTj5OLg3dvb1tTS0tHS0dHR09jW2djY19bV1dXS0tLU1dXW1tPS09XWhNgS2drb2dbX2Nvb4OLl5XTl5+R1hOWA5uXo6u7u6+Xm6Onp6uvs6urr6enr6erq6+ns7evs7vHu7u3t7O7u7vHz9PXz9fb49vR7e31/f4CBgoKDh42Oio6Sk5acoKWpsr/Ar7KtqbC6s7KytbW5uLOys7atrrC0tbq2srCwtre0tLKvsLC0tbO0urG0ura1vL7Bx7y3trZPu2Jla21ramNkY2Rpb3V2dG1mYWdoZ2hlY2O9t7e7X7i4s7OzsLOztLW3ul9evLi+wWRpbnBxdHd1dHRzc3JzdHd3dHJwb29wb25vb3BxcYRyCnNzcnFycXN0cXGEbgFthGyFbR1sdHJubnFybGxramlpaGlscXBvaGNgYGFiYWBhYIRigGNlZmdnZGRjYmJgXV1cXF1cXVtdXl5fXl1cXV1bXFxcuri1tLO1t7e5v8LFx8jGzc/TzmRna3Bvb9vac3FwcG9sa2ppaGdnaWlpaGhpaWlqamtsb3F0d3yCh46UmZyfoaettLm9wsjKzdPW2N3h5OTi3uHf3+fx8unj5ujo4t7dDtrSyMK6sauppaKhoqSmhKWAp6uvra61tbSzs7KxsbCwsrW4ury8wMHEyM7S19rc4Ofs8Pr+/Pby9Pr8/IGCgYD9//748/P3/ICDh4mKiouNjI2MjY+TkpSUlJaYlpibm5ycnqGjqK+1tbm9vrW2wcrR4PH3+vPx9fnz9vz68eXd2dbT1t7a2dzj4+by/oKHiYsTkpGNiYqLj5CSlpeZnKKpsbzHy4Cjpaenp6Kko5yWlJSVl5aTkY2Ih4mKioiGg4OEhouPlp+rusNnZ2Zis7CnoaOqqqWnoVtqX6hqgWSVj5KRnY+Oj4iBdHBvbm5sbW9wcG5tbWtqaWZkYmFiY251fnl8eGloZ2RgX15dX2CxqKKcmpWTkY+Mh4J/e3h3b3Jwb3Z7foB1bWlmZGG6t7e0sK2rp6iop6alpaShoJ+goKSopKOgnZuZlZKQkI+OjY+Qj42KiYqNi4qLioyHf3pxiX14zZ9td3vDwK2or6ihnpqepKWqq7O2uuV/iYeGiX3o2tLLw7+9u7u4urOwrqytsbnFz8rLvK6oopuVk5GQk5mZlJOTkoCQj42Oj4+NjY6NjI+VmpaPi4iHg4F+fvr6+vv4fX+AgoKFhYOBhISCgH99foCFlJOQiomKi4yNjIuNjpaWlJ2mqqObk4+RkpiglZGOjIuKjZWUkIyLiYaGhIGAf39/fX+Bg4SHhIODhoiJiouMjo2NjIuLjIyKiYiHh4eGh4iHhieEgYB+fXt5eO/r5+Xj4d7d29nX1NDQzs7P0tXU1Nnd3dzd39rc2deF2IDW1tzb2NbY29zd3d7f4eHl4d/e4OHi5OTl6nfr6el47Ofm6Ojm6Ozy8evm5Ofp6unp6uns7uro5+jq5ufp6enr6+vt6+zq6+rs7O3u8PHz9PT09fb2e3x+gICChYWFhouSlZGUmJufpKiqrbbBw7W4sKuzvLm3uLu8wb22tba5r3ywsLa0ubexs7K4urm4t7a1tru/vcHIwMbKx8fR1dbg0MvJyNFveH+FgH51cXFzfYWUmpaKfHd9f31+e3Vz3dPV23DY18/R0szPzM7U2NtwbtrR19pyeYF/fXx+hIeJiImIh4iJiouMjIyLjIuJiomIioyMjY+OjouJh4eIhIl+ioqHhoWEhoaFhYKDhYSEg4OEi46NhomIh4WFhYiIiYqJgnt4ent9fXt4d3h3d3h5e35/gIB/fnx8e3l5d3R6enl1d3V0cnFycXJxcHBubtvZ2tvW1dbV1djb29zg3+Tm6Od1eHt9fHz19H+Afn9/fXt7e3x8fX9/f35/gICBhYKAg4SHi46TmJueoaOnq7G3u7/CxsnM0NLV2d3g4N7a3Nzc4+rt6OPm5eTh3t7d1tDLxb66t7SzsrO1t7e1tbS1uby6vcPCv7++vr28vLy9wMHDxcTIycvP09bY2tzf5Onv9vf28vDx9fj5foB/f/z+/vr49/r/gYOGh4eHiImIiIdRiIuNjo+QkJGSkpOVlJWVlpianaKmp6mur6mqs7rAydbd29jb3t/c3eDf18/Lx8TBw8jHxsjMzc/V3XF0dnh8e3d1dXV3eHl7fX6BhImPmJ+iTFhZWllYV1lZV1RUVFNUU1NTUU9PUVJRUFBQUVFTU1dbXmJobDg3NTJfW1dTVFdXVFZTKCwrUisvKkpISEdGRUZEQkE9PDw9PTw8PT6EPUU7Ojo4NzY2Njc4Ojo5ODY0NDQzMzQzMjAvW1tZVVNSUlFQTklGREJCQz9BQkJJS0xGQD48OjlubG5tbGtqamlqa2poaGuEaQpqbnFucXBta2lnhGWAZ2dpaWhmZWZoaWlqbGtvbWhiV2lqaap3VTxfqqSXlZyQi46KjpeVnZ2lpKbAbXd2dndszMK6tKulo6Kjop+YlJOTlZyksrmytKeak4+Kh4aIiYySlJCQkY+MjIqMjY2OjY2Li5CVm5WNiYeFg4F+fPj6+/7+gIOEhoiNjYqIiok0h4aFhIWHj6OdmJSSk5aWlpSTlpqgoaKnr7aupp6am5yhrJ+blpSSkJeenZmUkpGOjYuKiISHgIiKjZGTkI+OkpWXmZycnZqbm5ybmpmXlpWWlpSUlpaUkpCNioiGhYOA/fn18+vl5OPg3dzZ2NjX2drc3uDh5uvr7e/w7e3r6Obm5uXl5ebs6+jn6Ors7u7w7/Dy9vHu7vP19vX2+P+B/vr5gfn08vP08/P3/f/28fHy8fLz9fTzgPb59PLw8fHu7u7x8fLy8vPz8/Hu7vDx8PP19fn6+vr9/f+AgYOGh4iLi4uQlpyhmZ6ipamusrO1vsfFu764tbvCvby9wMPHxL+7vcC1t7e7vL++ubq9v8XBw8PDxMXMz87Q2tHa39fY5+vu/ezl4+Ltgo2XnpeUhYKCg5CftMO7gKaSiZWUkJKOiIX98PD7gfr68vP06+7u8fb7+4KA/Ozy9YGKl5SIhIaPlZqanJmWlpWZn6OlpaSnpaCfnJyeoqKlqKaln5qXl5iZm5yeoqKampubnZ2cm5SVmZmXjo6aqqmmoaWkop+goaWgm6Gfl42JjZKUlpKMhoeGhYaHio+RgJSWlZOPkpKUko+Ml5iWkZOQiIGChYiHgoGEg4D5+f3/+/Hy8e3u8Ovr9fDx9vX8g4SGhoSB/v6DhoeHh4aFhoiLjo+OjpCTk5OVlZaWlpONjI2LioqKjI2PkJGSlJWYmpydoKCipKWnqaqsrK2sra6usbW2s7OztbW1tLW1s7GwA6ysqoSpgKqsraysq6moqaqqqa2sqqqsq6qpqKmpqqurq6ytrq+ytLS1t7i4ur2/w8TExMbFxsjKZmdnaNDS0tHS1NXWa2ttbG1sbGtsbGtrbGxub3BwcG9ubm9wcHBxcnN0dHZ2d3p8fX6AhIaIjZCRkJSWmJaYmZeRjYqJiYmLjo2NjY+OGo+SlUlKS0xMSklHR0dJSUhJSUpKTE5QVFZXpIOEhIqDB4SEhIOEhISwg52Cs4EJgH9/f35+f4B/kX4Bf4aBvYCFf+yAvn8FgH9/f4C2f8+AmYGEgAGBjICCgYSA+4GTgIaBgoD/gYOBhIKIgcWCl4MCAgQAgNfj5+Xi4d3a1NDN0dHQ2dnQwru2tLOysa+vsbGxtb3BzOHw8/T6gYOKjIqHgf3y6ePY1uHjzM/m8t3Zxry/v761qqajn5qWlJCPjo+RkpKQjYuLiIaGiIiIhYWYqqmmoqKem5eMjIP58ebg3NrY1NDRy8G4s7KwqaWinpqXl5qWfZiYkYyGg4D8+PLu6+bk4+nw8uzn4Nva39/b2tnY2tjOxsTCvr+9urm4ube3ubazsLSyq6afmZOOiavTlZH1yYDEgrSyqaSgm8C9ts3Nz7i3vsbGl5ScmI2HgPXt5+Hh4uPg3Nzb19XU1tjb2tfWz8nDvbezr6mjnZuZmJWRhJJIlJWWmpuenpqYmJ6npJ2Ujo2JhoSCgYKCgYCA/X1+fYCJjo6HhYOCgH99fX6BioeCf399f4F/gYSGg4SEhYyfraqdj5CPiIaFhIQVhYSEhoWFhoSCgYB/fn19fXx9fX1+hH85fX1/gYOEhYeHh4iJiYiKi4mIh4WEg4SGhoWDf3t5eXjt7Ovo5+jk4uDe3tzY1dPS0tPX19bY2N3bhNiA1dbV1dXW1dXU1NXX2NbU1NbW2NjX19bY2NbV1dbZ2dra3eHn5OTk6Obk4uTk4+Xp6ujm5OXk5ubm6ebk5uno5ujn5+fm5ujo6eno7Orp5+vs6+rq7u3u8PHx8PLzeXp7e3p8fHt8fX6AgIGDiYuOlJydo6u2vrmsqaipsrK0tKmAqq2oqqumo6WkoqWnqaempKSjp6WipKeopKWlpqamp6WmqKekpaWipaWkpKKlqaqwtsC8sa20vGJoaWZfs6yvur28u7WwsK+4vba3ra2qsLW0Xa2ppqemo6OkqrO8xMfJy87W5Xd3dnZ0cnJydXh5eXh5dnJwb29ucHFxcXJzc3JKcnFxcHBxcG9vbm1vbm5tbW5vcXFvcnJweHh5end9d3RycnJwb21ucm9qamVlZGRoZ2NkY2RkZWdnaGhoaWdpaGVjYl9fYF9eXl+EXoBdXV5eXVxbXb67u7aztbO1t7q8wcTJyMnKy83T1NducG9ubm1wc3R1dXNwbmxramtventwb2xqa2tqa2ttb3B0eHyBhImOlJmeoqessLi8wMTIy87Q09fa2tvd3eDf4efr8PHy/P347+vq6ePZz8jEurKvqqinpaKio6OhoaGkp4CoqaqqqrCztbGwsLCytLe5usHGyMrMz9TX2d7k6/L4+/f19Pb5+Pf5/v/+gID78/Dx9fn/g4ODhYaIiYyPkJGTk5SVlpaXmOKXnJ6Z/facqq+ytbSqk5SxxdTf5e34/vfu7vT28/H09ere2Nja2tnW09jd5e73gYmSm52ampWUlw+Zm5uZmZ6ho6mutbm/xs2ArLW2tLGwrqynpaKlpqaqq6SclpKQkI+OjYyMjI2Qk5idsLm7ub1hYmdpZ2Rfu7StqaCepqaWl6Srn5uOiIuMjYZ/e3h1c29saWhoam1tbGtpaGZkY2NkZWViYm53d3RxcG1saWNjXrStpqGcm5qal5WSioWAf397eHZzcW9ydXOAcnFsaWRgXrq3tLKvrKurtLu9t7Crqqu1tbGzs7K4taegm5mXmJiXmpmYmZqbnJmZnJiRi4aBfXd0jbSQituqbYJqmJWNioWArq+pvbG4oZ+qsK6PipaPg31129LMx8fIy8XBwL+8uru7vb++xL+3r6ukn5uXkY2LjI2NioiIiYqAjIyOkJWampqVk5SfqqOclI2KhoOCgIB/gH9/fvt+f3+EjpWVjYuIhoKBgICCiJCOiIWEgoOFh4qLi4iJiIuVqrq2pZWYl4+Ni4qJiIiJiIiLi4iIh4aDgoKBgICAgYCAgYOFhYSDgoKEhoiKjJGRkZKTk5KSk5OSjoyKiYuNjYw0i4WBfn168O/t6ebk4N7c29vZ1tTS0dDT19nb29zg3tvc3dvY2tfY2tzb2tjZ29va2djY24TdBt7d3t7c2oTbIN7g3+Xr6Ojo7Onn5OHj5Ofp6eXi4uPk4+Pm5+Hg4uTkhOKA4eLl5OTm5+fp6Onq6+nq6enq6uzu7vDv8PN6e3t8fHt7fH1+gISFh4mOkJKZoaGnr7zBv7Gurq22uLi4r7Czr6+sqKappKKkpaimo6GgoaenoqOlqKSkp6inqa6urq2rq6+uq6+ura+qrLW5wcrWzsS+x9Rxen58cM3CxdPY3Noi0snJxtLc0tLGxcTM1thwxr25ubq4tbW7ytXb3d7Z3ej0gIaEBIWFh4qEixiKiIeHiImLjY2PkJCLiIaGhoeIiYiIh4WEhnKIiYqMi4mLjouQkpSWkZaSkI+Pj46LhoWKgnx8fX5+fnl7fHp6e3t8fX+Bg4SGgoSFhYSDfHp6e3h3eXd3eHRwc3BvcnN0ctzW0dLT19LU0tXX2Nvd3d/i5OXq6/F7fX58fHx9fn+AgX9+fXx9fX1+gIOEf4KBhIKAgYGBhIaKjZKWmp6ipquvs7e8wMLFyMvO0dPU1dfZ2tzb3eHl6uzv9Pby7Onn5uPd19PPxsC+uri3uLW0tLOzsrS0tri4ubq6v8HCvry8vb/BwsPEyc7O0NLW19jc3+Xp7fP29PPy8/b29Pj8/f1/f/z39vb4+v+CgoKDhYaHiIpSioyNjo+Pj5GQkd2QlJWS9OmSn6OlqKigiYqlt8HJztTc49zW2Nzb2dfb29TJxcbJyMPCwsPFzNLZcXV6hIWBgH18fX+AgH9/g4WGio+RlJqfpARdXmBfhF1xW1taW1tbXV1dWFZWV1ZVVVRUVFZWVVdWW2JkZGFjMzM0NDIxMF9bWFVSUVNTS0xQUU5KR0VHSElEQUA/Pj08Ozk3ODk7Ozs6Ojk3NjY2Nzc4NjU3ODc2NTMzMzIyMTFgW1ZUU1JRUVJPT0tIRUVGRUOEQoBFSEZEQkA/Ozk4bm5ubGtqamt0fX52cW5xc31+e36AgIiHeG9raWhramtvb21vcnV1dHZ6dW5pZmNgXFpriH15toBVPEx1cGtpY2GZmJqflZ6NiY+SkHp4gn1ya2W/ubOura6xq6empqOgoaKhpqmtq6Kcl5GKh4SBf4CEhYWDg0yDhYaJiYuOk5WYmJSTlqKspZyRjIeEgoCAgYKAf39//oKDhIuVnJuUkYyKiIeFhomRnJeRi4yKjI6QkpWWkpKSlp+ywL6wnp+elZWShpGAkpaUkY+OjoqIiYiHh4eIiouLjpCQj4+OjpCUlpicnp6goqKioKCioJ6cmZeXmJqbmZaQi4eFgv77+/bw6+bj4eDg393Z19jZ3ODj5ubn7uvo6u/t5OXm5ebo6Ojm5+vr6unn6ezv7e7v7uvr6ujn6Ofn5urw8vX39/X4/PXy8PGA8O/w8/Lu6+bo6+fm7O7r6Ofq6+rr7evp6err6urs7O7u7/Dw8PHw8PHw7/D09fb4+4CBgYCAgYKCgYSIjI2OkJean6OrrbC5wcfDt7O0tbm6vr+1uLm2trGrqa2qp6mqrKmloqOlqqynqauuqKuvsLC0ubi4urm3u7u5vr+/v7yAwcjM2OPx59rX4vGBjZSSg+zc3/P4+/fv5+Pi6/ny8uHh4u36/ILg1s/Nz8zJyM7f7/b08unq8PuIj5CPj4+QkZCRk5WYmJmbmZmZmJqeoqOnqaifmJORk5SYnZydm5aXmZqbnaCio6KfoaSkpaaoraWnp6empqiqpJuZmY6HhI1FkZOSgomPioqLi42Lj5SXmpyYmZugpKWZlpSXj5CUjZCTi4KGg4CIiYuJ++7i7O/y7Ovn7u/r6ujo7vP29/v7/YCChYSChIBNgoOCgoSGiImJhn9+iIqNkJGRlZeTjoqHg4KBgoODhoeKjI6PkJOWmJmbnZ6foaOmpqWmpqepqqyusLO0tbm6uLi3t7e2trWxs7CurK6FrT2rqqioqqqrqausrK2wsLCtq6qqq6utrKyvsrS0tbW2uLi6vL7AwcTExcbHycjHyMvNzmdozs7Qz9DR0mlphmpTa2trbGxtbW1ubm6ka25wbbarbHV2dnd2cmJkdoCGioyNkJKTkpOUlJSVlpeSj42Njo+OjYyLjJCSk0tNT1FSUE5LSktMTExKSktLTE5QUlRWV1qmg4eEuoOggrKBCYB/f39+fn+Af5F+AYCGgcKAAX/pgPp/2oCFgZWAAYGSgPKBloD/gY6BgoKHgZOCAYGEgoKBqIKZgwICBACA2uHo8O7v7+vn6evv8O3r7OPSzMO/vLu8wMC/w8bP2/P9/4H7/oCBgISJkZaVjoeA7t/Z1tDNzsvKv7m8vb6/ubCopaKdmJWUkpGPjo6QkZGPjYmIiYqNl6iulrC5t+Pe2NLUxa+aiv/18e/y7OTe1dDNxLmxq6mopKGgnJuamJSAkIyJhoODgYD99fP08fD4gvXw7enk4uLg2NXU0M7QzsnIxsS+urm4tri5t7SwtLSwrKOdmpiVk42I6I/88OTCgK2W09jKwdC+tqyyu7bBtry3wcvV+f+Hj4f49O3v7u3v8O/t49/Z2NXRzs7Kw8K/ure2trKuqKKgnJ2Zl5WRj4xujIqMjpippqylnZeUjYuHhoSDgoGA/X1+gf/+/Pn39PN6e4CBg4GBf39+fX1/goKBgH18fHt6e3x/gICAfoGFiZCZm5WTjoiIjIyJhYSDgIB/f4CBf3+Afn5/fn59fX5/fn1+gICBgoGBgoODgYCEgz+EhYaGhYSEgoGBf3+Afn17eXjt7O3p5ubm5eDe3t3d29nX1NTX2t/d3Nzb3dza2dfW1tXW1dTV1dXU0tPV1tiF1wbZ2drW2duE3HLf3d/i4eLi5uh16OPh4eLi4uPl6ebj4+Xm5uPi5OPk5OPn6Obl5ebn5eXn6Ojn5+bn6uzr6unp6u3v7+/t6+3v8PL08/P39vd8f3+AgYWGh4iLkZenq6mqqayysa+2urq5t7K3u7u0tLq5tbOtrqimpKKEoICfnp6foJ6bnJybnJycoJ6dm5mZmZualpeWl5mZm5ydnqGmrKyqsqyioaasrbGwo5+goaSrrKqgn5ydnp+hoJ2foqOrrKejpqyyt7q6vcPL1tze3+DgcG9wb3J3eXt7fH18dXRycXFycnN0c3JxcHBwcXN2dXRzcnBwb25vb3BwcTBzdHR0d3h8fHt5fX98e3p2cXNubWxqaWlpa2tmZmVlZmZmZ2ZnZ2hoamloa2pnZWSEYoBhYGBgX15dvrq2tra7ubm5uLi4tre3u7u8vcLGx8jHys7P0tbf5uflcXFxdHV2eHZ0c3Bua2ve5PeB+vBxbXBvamxv4d7g6Hd6fYOIjpSZnqSnqrC0ub3BxcjM0NTV2Nrd4N/f4uXl6e3x8vP0+Pb29e/r7evh29PIwLiwsbCqqCuoqaenpqSmrbGvrbC1tra1tLKys7O0t7m9wcXGyc3Q1d7g5Oju8vX19vb3hPZn+fv8///++/r6+/39/oCChIeIioyPk5aanJ2foKGS4saEicC5ks/+2rfQkaSloKPD7bTf6fL69+/q6/j//v3x5t3Y2NfW09TY2t7l8PeCiI+UmZydnKCjo6SmqKiop6u1vL/DxszS00its7e7urq9ure3uby7ubi4tKmlnpqZmJWYmpiYmaGot77BYr6+YGBgYmZrcG9oYl6upJ+al5WVlJKKh4mJioyGgXx6eHRwbmuFaYBra2ppZ2VjZGVobXZ5a3qAfpWSkIyLgnZqYLKrqKeppqGfnJiUi4R+fHt7eXZ1dHFzc25raGZjYF9eXbi0tbS1tb5nwr67ubGwtbWvqqmoqaupo6GdnJqZmZqZm5qamJadn5qVjISAf3x6dnTAeuvex5xsdIDAxLm1waikoKWwqICzqq+ntL7F5et8hX7h2tbW2Nja3d7cz8bAwb+7t7WxrayppaOinpqVkI2MjI+MiYeFhYSEhIaJkqenrKGZlpWPh4SCgICAfn77fn6A/vv59PTz9Ht/hIeIh4eEgoGCgYWIiIWDgX99fX2Ag4WHhoaFh4yRm6KjnZuWj46RkpCKiAyGg4CAgIKDgoKAgICFgRGCgoGBgoSEhYeGh4iHh4mLjYaOCI+PjYyKiIeGhIWAgn578ezq6ejo5uXh3Nra2tjV1NLT2N7h4N/f3+Df3t3a2drZ2dnY19bV1dTV2Nvc29rZ2dnb29va3d7e3+Hg4ODj6Ofl5ejsd+3j4+Lh4OLm5uTi4OHi4uDe3uHe3d/e4OHg4N/g4uLh4+Hi4+bk5Obn5ubk5eXm5ufp6+7u7++A7/Dx8/P1+X1+gIKEhoeJjJCXn7CxrKuus7a2tLm/wL68uL7DxLq6wr26ta+uqKaioJ2enpycmZmanZqbm5mZm56foaCgnZudnZ6cmJiam52go6Olp6uxur67yL2ys7nCxMjGt7CxtLfAwcG0s6ysr7G3u7eytLrFxr24u8HHzc2AyMvP2OHm7O/w8Hl9f4GChYmKi4uOjYuJiImMjY6QkZCMiIaGhoeJjI2Mi4qHh4aHh4iKiouMjY2PkZKWk5GRl5qYmZiUjoyCg4N/fX6BhYd/fXp8f4GBgICAgYOFh4eGi4uJiIaCf36AgH17dnZ3eOPV09TU2tfU0MzN09HP0NSA0tjY2tvb3N3e4ePn7PP2+Pl9fX1/f4GCgoCAfn17evf3+YD+/n5/gICAfn35+fv/gYSHi5CUmZ2ipqmtsLS3vL7CxsjMz9HU1tna29zf4eHk6O3t7u7y8vLx7ers6+Pf2tLKxb/BwLu6t7i5urm3ub2+vby/xMTCwb++v8DAwcIQxMjMzs7Q09ba3t/l5+rt74TzB/T08/T3+PmE+2D5+vv8/P5/gIKFhoeJi42PkpSVlpaXjNi+gIPAuJDB6NGtxoyim4+Jtd2lxtHY3t7Y1Nbe5OXh2tDJxcbFxMHBxMXIzNLZcXV6foGDg4KFh4eIiImJiYqOlJmbnqGlqqmAXV9hY2JiZGVjZGRjY2NiZGJhYF5cWlhXWFpaWVhaXGBjYzJhYDAxMDEyNTc2MzAuV1JQTk5MTEtKREJDRUZHRkI/Pj49Ozo4NzY3Nzg6Ojk4NzY1NjY3ODk5Njk4ODo7OTg5NjQyLVdVVFRTVFRTUlBOSkZERUVGRENEREFDRUOAQDw8Ojg3NjZsbW1vb3B5QYGAfXp0dnx+enV1dXd7enRwbm1sbXByc3RxcnJyfH58dmtkYF5dW1pYjVrCuaN2Uzdio6OcnJ2Ej46XnJejnaCUo6qqx8xrdG3Fvrq8vL6/xMXEta+pq6qloqGem5uYk4+OioWBfXp8gIN/e3x9fX2AfoCDh5KmpayhmZeTjYWBgYCAgH9//4CCg//7+PP4+vyBhIuOko+MiYiHh4iLkJCMi4eGhYWGh4uQkpCPjpCXm6SsraelnpeWmpqYlJKPi4mJiImJiomHh4iJiYmKioyMiomNkJGSkpKUlJSWlpaZmpqbm5qbnJyYl5aVkpKRkI8OjoqFgf359PHv8O3o5OGE34Dd2drb3+z08u7s7e7p5+bn5+Xj5OXk5OPi4+Di5Ofq5+bm5uXn5+bm6erq6+zq7evs8/Px8fX7gP3t7e3r6uvu7ezp5uXo5uLg4eTk4uDg4eDi5+Xi4uPk5uXk5eno5+rt7Onm5+fp6Ons7Ovu8PP39/b4+/z/gIKDhYiNjpCTmYCfp7e5trO1uLu5ucHGxcTEwcjJysC/xcG+ubO0ramoop6foJ+cmpueoZycnJucn6CipaSjoJ2hoaOhn52fo6eqr66zuLvE0NXO3NLEx9Lc3uHdzMTFy83Z2tfJx7/ByMvS1dLLzdDg49bP0Nfc5N/b3N3g5ezw9fj7gIaKi4qNkYCUl5WUlZmYmJmdoaKlqaadlpKQj5GTmp2bnZ2Zl5eampudnp6en6GkpqaloqGiqautrKyspqCQkZKKiYuQlpmQjYeMkZSVkpGSlZqcoKCfpaaprKeZlJWcnJaSh4uOkf3o5urp9fHp3tjb5+Pe4ebj7ezr6uXo6evo7fX4+vn6/QaCgoGAgIGEglODhoSC//jtdvP5g4iJjJCJgv39+fh7fH1/gISFh4mLjI2QkpSVl5manZ+goaGipaempqiqq66wsrO0t7i4ubm3t7q5uLe2s7GwrrGxrqypqKqtsISvB66tra+urq2FrByur6+wsbSztbi5ubq8vr2/v8DDxMXGxsbFxsjJhcoIzMzNzdDOZ2mEaixra2xtbW1ubW1tZaCcYWSUm4Ciupx9jWZ4fXJhfZdvhIiNkZGSkJGUl5mXkoSPJIqJiYqMjIuOkJBKTE1NTk5PTk9QT1BQUE9PTlFTVVdYWVtcW6KDA4SDg4uEt4OhgoeBAYKpgYJ/hH4Df4B/kX4Gf4CAgYGBvIAEf4CAgId/5IDFfwGAvn//gISA5oGhgI6BBoCAgIGAgIeBhID1gZGCBYF/gYGAhH8KgIGBgYB/f3+AgZyCmoMCAgQAgOrp6fDz9fDi4OTv+YCEg/747uTn6uTo6erl3NXX4eb0g4iGgf39gYiMlZqamJWNhfzy7eXf1MrEw8LDwL68ubWvqqainZmWlZSTkZCOjY2MiomIiIiKiImTp56fn6COnZ2axdXHsJ2WjoiIgvTn39TNyce7sayop6Wkpaqpn5aRgI+Mh4WDgoGA//v3+IH68+/v7uji4N7c2trW0c/Q0c7O0NDLxb+4s6+tra2rp6KioJ6cmZmWk5GNib7hjYXvx/fA+LSzsrndxtra6tLA0q+tvre1/PmDgYuQiIH18O/y7+Td2NHOycnJysTAvb3CvrvDwsC6tLCsqaaloJ2bmJOQXI6Li42Pl5yglY+MiYiFhIKB/v38+/v19fb29/f08/Lw8PDv8Xx9e3rz8PB4eXx+gYF+fXt6e3p4eXt6eHd5ent7fH19gYKAgX9+fX18fn5+fX1+fn19fn18fH18hX01f35+f4GBfX19fHt3dXZ1dHN1dnZ4eXl6eHh6eXp4d+/w7+vp7Ovn4uLh4ODf3tva2tjY19aH2gvb2dbX19bX19bW14TYPtXW1tfZ2dfW2Nra2+Dh393f4ODh5OTm5+Li4+Pj5OXj4d/h4ePj5efm5OXn5+Xk4uPi4+Pi4+Pm6unn5uTmhOeA6Ojq7O3t7ezs7u/u7+3u7vDy8/Ly8/X3+fl8fX1+f4GFiIuPk5Sip6qqsLyzuLy9uri7vbe3uLK0s7K1tK2trqmnpqKgnZ2amZmbmpiVlJSTk5SVlZmXlZSTkpOTkpGSkZOUlJSTlJWTlZaVlZaYl5iXlJKSk5KXlpaYl5OTkpEtko+QkJKSkJOVmJ2enZyiq7Kzt7a2vcPIy83S1dnf29nbcnV3eXp6fX57enVzhXQPcXFwcXFycnJ0dHZ0dXRyhXACcXKEdIB1eHd5d3d5enp3eHR0dHBtbWtoZ2psamtpaWloaGdoaWlqaWlraWhnaWppaWdnZmRiYmNjYV9evLq6ure2tre5uLe6uLm+vr68vb+/wMLHzNHU1tne5Ojqc3V2eHd3eHh4dnFx29vf4un3gH98c3BubNva3Obm6Xp9gIWJj5Sann+hpaiusre7v8PKztPV2d7j5+jq7fD1+f77/Pj8+fn8/P7+/ICA9uzm3M3Evru0rqyusrOxsK+zw8G9ube3ubq3t7a2tre5u72/xMjLzdDW2+Dk5OTo6/Dx8fLz9fX49vb3+v3///3/gIGBgoKCg4aIio2RlJibnqGkoYXDqpKPhI5DjZaL+OPQxsHclpSRlqzP26jb4uTq9Pz58ezo4N7b3+Tk5efr7/P8gIGEiY2TmZ+lqa6ytra5vcDCwL/Fys/a4d/a4IC6ubi9vr+9tbS2vcRlZ2fHxL63uLm2t7e1squkpauuuGJkYl+6vF9kZ25xcXBtZ2G3saynoZqSjYuLjIqJiIWCf3x4dnFvbWxqaWlpaGdnZmVkYmNkZmVlanRxcHJxaHBvbYSOhXVqZ2NfXlytpqCblZCLg3x6eXl5enyBg3duaIBnZWNiYF9eXrq3tbhjwLi3uLm3s7Gwrq6wrKioqqunpqanpKGblpORj4+Ni4iFh4aFgn9+fHp4dnOauIqB2KXUfcqamZmkz7PM0uHBssGhnKqkouPed3eChn924dzY39vSyMK7uLS2trevqaapramps7Krp52XlJKSkY+LiYeFg0+ChIWHipKXm42IhIKBf318evT19vX08e/z9vbz8fDy8fDy8fV/gX179PL0e3yAgoSEgH5+fH18fH1+fX5+fH5+f4CAgoWHhoWDg4OCgYGBhH8jfn+AgIB/f4CAgIGAf4CDg4SFhoaGg4KBfnx7enl6eXl6e32Ef4B+fXx9fHv08+/q5+bl5uTj4uTf29ra2djX1tTV2Nzd3Nvb2trb2djZ2NfY2NnZ2Nre29fY2tvd3Nvb2dvd3uDi4+Pi5Obl5ens6OPi4+Pk5OTi4N/h4uXm5ePi5OTj4+Hd3N3c3d3c397f4uPi4t/h4+Pj5OXl5ubn5ubn5ufm51Hp6ers7O7w8fLz8/b3+n1+f4CCg4aJjZKWmKasrq21w7nBx8bDwcHFwcC+tbe1tri3sbCwqqWloZyZmZaTk5SUlZKSkpCQkJOVmJaUkpKSk5SEkh6UlJOVl5eXmZyem5yeoqCioJ2dnJ2fpKOko6Ofn52FnICdnp+ipquytLGwtr/JztLJxsjP2Nzf4uXn6+3w9Xx/gYOEhoiKiouJjI6OkJGPiYeFhYeHh4mLjI2Ni4mIiIiHh4mLjpKUkpCRj4yNkZOVlZCQkY2RkYyHhoN+foSHhYSBgYOEg36ChYSEhomLioiJi46NjoyLi4uJiYiDfHZ14WLe5N7Y1tjU1dPN0NTU1tbW1dXY1NbV1trc3+Hm7PHz9n6AgICBg4SEhIF/fvby8/Pz936BgX9+f3z28/T5/P6BhIiLj5OYnaGjqKuvsra6vsHGys7Q09jc4OLk5+vu8fXz9oT0YPX3+Pj3fH3z6ebg18/JycK+vb7BwsDBv8HNy8jFxMTFxcTDw8LBw8XFyMrMztHT19zf4eLl5ujq7e/v7/Hy8/T09vf5+/r7+/x+f39/gYCBg4WIiYqOkJKVl5iWf7ujjYWLQ4qPh+zSvrGsx4Z+fYKWvMmZxcvP093j4dvV0c3LysvNzc3Q0tTX225vcnV5foGEiI2PkpSUl5qbnJ2bn6Wpr7KzsLVJY2NjZmlpaWdlZmhpNTc3bm5taWhpZmVlY2JfXFlbXF8wMTAvXFwuMTI1Nzg2NTEuVlJRUU9LSEVEQ0NERENCQUA+PTw7OTg3N4Y2CzU1NDQ1NjY2NTY3hDaANTY2NTk6NjQwLi4uLSxXV1VRTktIRUNCQkRERklOT0dAPTs5OTg3NzY2a2tqcD53c3R3eHh2dnZ3d3p2dHR5endzcnJvb21raWhoaGZkY2JkZmVjX15cWVlYVXSJdXG1fqU8lnVydoCxk7GyxqebqJGKj42Kwb5mZW90bmfDv744xcO8s6mkoZ6io6WdmJaanZqZoqCZkoiBf3+CgX57ent5eXp9f4GGj5abioKBf357enl48fT2+PiF9YDz8fD29/n6+v+FhoKA/f7/gYOHiYuLh4SDgoSDgoSGhIODhIWFhYeJio+RkI6Ni4qKiYeHhoWEhYWEhYeGhYWHiIiKiomKjo6PkZGTkI+OjYqEg4ODhYOBg4SHiImJiYiGhIWFg//99/Lx7+zq6Ozs6eTg3t/g4Nva29zg5+no5Vnm5+fk4uLi4eHg3+Hi4+Tq5+Pk4+Pm5uPj4+Xp6+3v7+zt7/Dx8vT69u3q6u3u8Ozn5+Xo5+3t7Onl5ujq5uPh4N/e4N7c3N3k6OXk5eXk5OTj4+Tn6Ojp6oXogObn5ufr7/Dx8vP2+vr7/oCBg4SGiI2Rlpyfoa+0t7a8yMHHys3JyMvQysfDvb69vr+/trW0r6qnop+cmpeUlJWWl5STk5CQk5WYmpmWlZSUlZSTlpWUlpeVmJucnKGpqaanp6+vsq2qqKqsr7azsrKzrK2sq6uqrKywsLC2u8DJgMzHxMza5Onp39jY3OHl6Ovs6+7z+v+Cg4WKiYuKjZKTlZqenqGkoZeSj4+SkZCTl5qamZiYlpeYl5ean6asr6mkpKCZmqClpKGZnqGfpaOdl5eTioyUl5SSj5CWmJWNkZeYmJqfo6Sjo6etrq6ur7Cwrq+toJKHhvf0/vTq6evmgOnl3OPo5eLi5ebn7OTi3tzb2dzj5Ojr7vWBg4GBgYKDhISEg4L/+vX07ux1d3uBg4SA+/f18vDyenp8fX6Bg4WGh4mLjY+Sk5WXmZudn6CjpaanqqyusrS2tra3ubi4urq7u7teXry5uri1s7Kzsa6srK6vr7CvsLSysbCvr7CvLq+tra2urq6vsLCwsbO0uLu7u7y+vL+/v8DBwsPExMXFxcfHx8nJyMlkZWZoZ2iEaVVqa2tsbGxubmxahY6BgICAfn99e3PHqZKHhJ5sXFpdaH2EZIaMjZGWl5aUkpOQkI+NiYmKi42Ojo9ISEhKS01NTk9RU1NUVFVWVlZXVlhbXF9hYmFhjIODhJGDhISCg4qEvIOcgoSBAYKsgQiAf39/fn5+gJJ+An+AhoG2gJN/hICDf9WA/3+Jf/+AiIDigaGAjIGGgIeBhoCsgYKCwoGUggGBin+GfoV/AoCBl4KcgwICBACA5OPo5+fbzcjO1OT0gYWD+PDw9oCDg4eHg4L98fTx7/X+hI2RlpeWmZqdoZ+dmJWRjo2LhPvv4dnU1NDLycS9ubKvrKehnJqYl5iXlJORjo6Mi4qKiYiHhYSChIyDgoCHkIidq6qgmJibn5mI+vL78eDaz8S8tbCrqKapq6SclpKAlIuJiYqIhoWDhIODhIH89O7s5+Th3tzb19HOz9Pa3+Lcz8jBubSwrq2uraqnpaKgnZqZmZiXlJGN1oqTie3J+KGS2tnX4P7av7zFxL/OvL7IxcDym4j5/vvn4eDb29jSzsjCvbm4t7i8urq6u7/CwsbJx8XAubOuq6qopqGdmplnlZGQj46PkpCPjouJiIaEg4H8/fv7+/b19PX29PDv7u7r6ezs6uvt7u7s7nh48Xl7fHt6enp58e/t7u/u7e7r7/Dw8fF5ent8fH1/f36ChIaIh4OBfnx8fX18fHt8fHt5d3br7Ovs64TqV+bl5OPg3t/h5Ofp6ejn5+ns7O/v7Ovt7+zp6ujj4uDf3tzc2trZ2djY2dvZ2tvc2tra29nY2dfY2dnZ2trb3t3b2dnY293e3tze3+Hh4+Tg4eTj5OLj44XkOOLj4uPi4uPh3+Di5Onq5+jr7evq6Ofm5eXi4ePl6Obl5+ns7e3t7/Dt7e7v8fLv8PDx9PTy8vHzhfSA9/f29/j6fH5/f4OEhomMjpGRkZOXmpeYmZmcoaeqpKGfnqKin6Cin6CjoJyYm5uYlZWVlpaUk5GRkY+Oi4yLjI6NjYyLjIyNjIyNjIuLjI6Pjo+Pj46PkZKRkI+OjIuOjY2MjY2MjY6OjY2LioqLiouNjo+RkJSWm5+jp6elqaodq6+ztLe7wsfL0dfh5+71f4CBfXl5eXt9end3dHOGcjNzc3Jzc3V2dnV1c3Nyc3N0cnJzc3Jyc3RxcHB0d3Z0dXBvb25ubGxsamtubGtsa2lqbG2HbAxramlra2lpZ2ZlZWWFZIBjZGNiYGBfYL68vLu9vLq6vb7Bw8PAxcjJzdDU3OHj5ufo6u52dXd6eXfm3tzc3d3h7fHm4eHf39rb3uN+g4KAgoWJi4+TmJugpKarsLS8wcXLz9PW3OHl6+7y9fn7+////oD/+4GAhIH4+fj08/Py79/Y2c3FwL+3uLevrq6tsYC3uby4t7m8vr++vb7AxMTEx8vN0NPX2tve3+Hi4+Xo7PDy9Pb39/j5+vv+gICAgYKChIaHiYuNj5GWmJyfkuWmyLySj42LiImHgfuG+7ifo7XjhN6Rj46Qmb3Hy4igisrk6Ofm6eno6u32+f6Bg4WIh4uOj5KXmp6hqK+3wMTFyQ7Lzs/T2N3i5uft9vny7YC6t7m4uLCnpqmtucNnamjHwcLEZWdnaWllY8G3tra1ucBjaGptb3BxcHR2dHJta2dmZWNfsqmfmZaWk5COi4aDgH57d3Jwb21sa2xsa2lnZmVlZGRlZGNhYF9hZWBfXmJnYm53dW9qaWpuaF+wqq2lmpWOhoF9e3p5eX2CeXBraAFphGSAY2JhYWFgYmRhv7m1trWzsK+uraqmpqettby/uauknpeSkY+Oj46LiYaEgYB/fn5+fXt3dapwf3jMptJsfMvIyNP4ybGyvbi2xbG2vbyz4pF94ObkysbIw8LCvbmzrKikoqOlp6Wlpqqtr6+2ubSyraWempeZl5KOiYeIh4WFh4caiIqHhoaEgH99e3p68/Tz8/Tx7+7v8fHy7+yE7U3u8PHw8PDv8Xp683t/fn59fHx79vXz8vTz8PDx8fPy8/R7fX5+f3+BgYGFi4qMiomDgH5/gIB/f35+f317ennw7+3v7uzu7e3r6+vo5oTlKubo6uvs7u/v7e7u6+rs6ubk5ePg3t3d3dza19jX19bW2Nra29vd29zZ14TWKtfZ2dna29vd39zZ2drd3d7b293f4N/h5OHg4ufm4+Pl5+bi4eDi4+Hh4ITfHeHj5ufm6OXk5ebk4uDf39/c3N7f4eLi4uPm5unrhO1b6+jo6uno6+vr6ujq7O3t7vHy8vP09fj5+31/gIGBg4aJjI+Tk5WUmJyWl5uboKesrKaioJ+lpqGioqGho56ZmZqYl5WUk5GQkJCPkJCOjIqKi4yNjIyMi4qLjISLH42MjY6Pj5CSkpCSlZqal5WVlZSVlJSUk5SVlZWWlpaFlDeWl5mcn56hpauxt7u7ubm4ur7CwcbLz9PY4ujs9Pn+gYSIiIiJioyOj5COiYeFhISGhoaIiYiJhIt2jYyMjI2PlJWTkJCOiIeIiYiJh4uQkI+RjYqIiIaCg4aCfIGDhIWGgoKGiIiJioyOkI+Pjo6QkJCPjo2Ni4eGhYaFhYODfnt3d3Z039zZ2t3a2tnW2N7a1dTV2Nrd3uDk5+zx9Pj8/4CAgoKAgP328u/t7O3w9YT2gPXy8fP1gIODhYeJjI+TlpidoaaprbC1ur3BxcnO0dXY3OLn6ezw8vT29/Z89/N8fH588fHx8PDx7uzj3+DXz83NxsXFwL++vL/CxMjFxMTHx8rIxsfJy83Nz9DR1dnb3t/f4OLk5ufp6+7w8fP08/X3+Pn7fn5+f4CBgoSFhoiJV4uMjpGTlYvdoMW7kI6LiYaGhH72gvCniY6lz33PgXl6fISepKd3lYC5z9PU1dbV1NbX29zfcXN1d3V3eXl7foGEh4yRmJ6io6SlqKmrrrK1t7i+xcnCv0llZmdoaGZnZmZmaG04OjpycnJxOTs7Ojk1M2RfX19dXV4wMzQ0Njc3ODk5NzUzMS4tLS0rVE5LSEZGRkVFREFAPz89PDo4Nzc2hDcDNjU1hTQfNTQ0NDMzNDMyMjMzMzU1NTQyMjExLyxXVFNPSkdEQoRBNkNESU1EPjw6OTg4ODk5Ojk5OTg7Pjx2c3FzdHNzc3R0cW9wc3uDiouEenRvamdmZmZnZWNhX4ReW11dXFtaWFZ9UGJeoXyjNmKwrK66262cnamnpbKkqq6uo8N9bMLFxK+ssK6qq6ilnpaTkpCRlJeUlJeanZ+ip6qjoZuTjImGhoeDfnp6fX17e35/g4SAgIB+enuEd4Du8e/w8fHw8PL29vDv7+/x8/T1+f369vX4/ICA/4OFhYSCgYGA/fz5+vv+//35/P38/v+Bg4WGiIeJiYqNjo2RkpCKh4SGh4aFhYWGh4SCgoD9/Pz+//z5+/38+ff19vXz8vTx8/f7+/z8+vr9/Pby8fDs6uvm4eHi5OTi393d3Cbc29zf4uHk5OTj4eDe3d3c3uDi4d/h4uPo5+Pf4OLj5eXl5OTl6oTsEevs8PHw8O/v7unj5urt6ufmhORn5+zs7e7t7e3s7Onk4d/f3tva3OHl5OLj4ufo6Onr6+rr6urr5+fl5ufs6OXm6Ovs7vDy8vX29vn8/oGCg4WIi4yPkpaam5iZn6Sfn6Kip6+0t6+qpKWrqqWmpKOkpqKcmZqalpOTkYWSGpOSj42MjYyNj46NjIqLioyOjYyLjI2PkJGShJYWmp2jpKOhn5ydoqGgnpyfoaChoaGgnoSfgKCmqautrLS5v8jO09DNzMbGys7M0dbY3ePt8PL3+f5+f4WJjo+Rk5WanZuUkI2LjIyNjZCSkpWYl5WUl5mZm56krrCtpqSglJGTlpSUkpWan56loJyZlpSNj5KOg4iQlpmalJKUmJqdoKWqra6tra+wr7Czs7KysKeioKKgoJqdgJSNhYmFgfbu6u3y6+3u6Ozy6N7c2Nnd39/e3t7i6u73+/+AgYF+fH79+/Tv6+vq5ePw9/j39vPt6+l1dnh4eHp8e36BgYKEh4mKjI+RlJWYmZudnqKlp6qsrrGzs7W3t1y4t11dXl21t7m5urm6ura2uLWzs7Ovr66srq6ura+tFK6trq6usLCvsLCvsK+xs7Szs7e5hLp0u7y9vb6/wMHCw8PExcXFxsdkZGNkZWVnZ2hoaWprbGxsbW1koXaYnoOBgH98fHlx3XfShWltgqxmqmlaWVthcHJ1UmhYgI6SkpSVlpSTk5STkUhJSUhJSktLS0xNTk5QUVRXWltbXF1eX19hYmJjZmpramiMg4OEhIOHhIeDk4S4g6KCq4GEfwV+fn6Af5F+A3+BgbuAmn8DgIB/iICOf56A/3+pf/+AioDngZyAhoGSgKaBA4KBgYSCwoGTggOBgYCJfwJ+f4Z+An9+iH8CgIGOgqKDAgIEAICF//neyMXJys7V3uTo3tjS0N7r6faAiY+Ii4+OjpCQjouOl6asra+ytbK0tLKvsbO0raOcj4X68e/r4tvY0szHw7q0saykop6dnZqXlZOSkZCPjY2KiomHhYOBgYGAgIKDhKbJwsS9wbSelYqDgIKA+evf1MjAurOtqaenpZ+bmICXk46LiomJioqMioiEgoH+9e/v8Oji3NjV0s/QztXbz8jDwsK6uba2sa2qqKakoqGem5mXlpORjovK4J6Q8Mv3wfetrKWjxci8uK+5qLjCvLvEx4T97OTi4tfQy7SPkpWZnKGkqK6wsri2t7Wysre6u8nOzcS9uLOwsK+tpp+fnYCalpOSkpSUkZGOjIuKh4SDgf78+fn28/Lv8PHx8PHx7+3t6ufl5eXk5eXo6Ot4fnt68e7s7Ovo6uzs6+3t8Hp6eXl6enp7enh5ent8fH5/gIB+fHp38PT0e3vz7+3s6+rn6OXj4uTj4uTj4uHf3d3d3N7f4OLj5OPi4+bo6uzt7SXs7evp4+Pl4uDg3t/d3dzY1tjZ2trc2tjY3N3e3t7b19zc3d3dhd+A3t3d3uDi5OPi4eLh4OHl5+bk4uPl4uHh3+Hj4eHj4uPj5OLg4N7e3eDk5+jn6urq5+fm5+bl5eTk5ubs6+vo7PDv7vP19fb6//37+Pf59/n6+vj5+fv6+fv+/n+AgIGAgYOEhIWEhYaIioyLjpGUlZSTlZWUlZuUlpWVlpaXlpYil5WVl5eVk5SXl5mWlpSTk5GQkJCPjouLi4mKiYmIh4eIiYSIIIeHiImKjIyLi4qLjY+RkY+Ni4qLiomKiomJiYiIh4aIhIdAiYqNjY+Nj5KYm5ycmpmcnZ2doaGkqK2ysri9x9Da4O/3gH9+gYOEgH59e3x6eXd2dnh4ent4dHR0c3Z2dnl5d4R2gHNxcXJydHNyc3N0dXd6d3Nxb25tbG9tamxvcG5ub3BxcnFwb2xsbW5tbWxrbWtpbGloZ2dmZ2dmZ2doZ2dlZWRkY2O/wb69vsC/wcTDxcXGxsXGzM/U3ePk4t7d4OXo6/L29erl39vb3Nzc3t7Y3OTf3t7o7uzzfoOIhIaIio2QgJSWm5+jqKuvur27vL26vru+xsLJ1dvk7uvr4d7g59nV2efk5eXr6uvs7ezn5ODV0MvGvry7wbyxr7CysrSztLS4vL3CyMXEx8jKy8/S09ba3ODj5ejp6+rs7/L1+Pr5+fn8/YCAgIKDg4SFhoaHiIqOk5aJ1JWonZSRjoyKiYf9SMXK27207uy6vMfR3OL0j5iTj5W5wsLFzdzGz87R1Nrf5ur4gIePk5acnJ+go6OkpKaoqq+zu8HLzdHZ4Ofw/P3+h4qKjIqMhYBqzse2paOnqKqvtLi7uLOxr7fAv8hnbW9oa2trbGxsamhpcHuBgYSIi4aGhIGAf4CBfHRuZV6xqaaknpmYlI+MiYWBfnt2c3FwcG1samloZ2dmZWVjY2JgX15eXl1dXV9fX3OGgoSBgXpvaGBcWVlXqqKbkouFg397end4dnFvbIBraGZlZGNkZ2hqaGlmZWTFu7a4u7awq6inpqWlprC4raSgn6CamJaWko+Mi4iGhYSBfn58fHp4dXKjt5CG1KvTe8yTkIqHsLOrqKCllqWxrKa3uHnk08TGxbeyr5p+goaJjI2Qlpudn6GhoqGhoaamqri8urGopJ+cnp+cmJOTkz+PjIuMjY2MiIqIhoF/fHp6evPy7+3s6+zu7u3u8e/v7Ovq5+fo6Ojn5+jr7vN8gX999vPv7/Dw7e3t7vDw832EfAF9hHx3fX1+f36BgoSFgH57evP19nt89vLv7u7s6unn5uPh4ePj4eDg39/f3dzc3t7f3+Lk5ejn6enp6+zs6eTj4+Ph397d3N7c29rY1tfW1tjb2tjY2dnb2tnX1tfY2tra3N3d3+De3d3e4uPi4uHg4eLi4+Tm5+bk4+KE4Dbi5OLh4eDg397c29zc3Nvd4OPj4uTk5OLg3+Hh4uDe3t/f4uTl5ufq7O3w8u/y9fb08vHy7++E8U3z9fX09fj6+36Af4CBgoOEhISDhIWHioqLj5SUlZOTlpeVlZqVmZeVlZWWlpeWlJWXlpWUlJWXmZSVkpGQjo+Pj46OiYmKiYiHhoWGhoWIUIeIiIqLjI6PjYyMjpCTl5aUkZCPj4+OjYyLjI2Oj5CPj4+QkJCRlJaXmZqcoKarrqypqaipqaqur7G0trvAxMfO09zn8v+ChIWGiImKi4uIhIaChYWHRYiIiYeJiouOkJGQk5SRj4uIiYmLi4qJh4iNkJORj42Jg4KAgYOChImKiIiJh4iLjY6NjpCRkZGSkY+QiYKCiYmHiIuMjYaMgImHhYR+euHh39/k4+Hg497a2Nzf3dzZ297j5ujp6urr7/Lz9Pf5+/f17+zq5+rq7u7t6/Hv7vDz9/yAgoSFiIqNkJKUl5ygpqqrrra5t7m4tri2uMC8wcvR2eTh4NfV1d/RzdHh39/f5+Xm6enp5+Xh3drW0MvJy9DKwb/AwcDBgL/AwcLFyMrOz83O0dLT1tfZ3N/f4uPm6enq6u3w8vLz9vf3+Pn7fn5/f4CBgoODhIWGiYyPkYTOj6SbkI6Mi4qIhvm9wNG1ruvirrHEydPY6H9/fXqBmp+go6q1tcDBxMXKztLV4HN2e3+Bg4SGhYiJiYqLjI2RlZugp6mtsrm9C8DFyMxrbm5ubW9rTzpwb2pnZmhpamtrbXBwcG9xdXh3eT0+Ojc3ODc3NTU0MjM3PD8+QERFQkE/PDo5Ojo3MzAtKk9LS0lHRkVFQkFBPz09PDk6ODc4Nzc2NjWFNIQzgjKIMYA1Nzc3Njc2NDAsKikpJ0xKSUdEQ0RDQUJBQj49PDs5ODg4OTo7Pj5BQEE/Pz98dXJ2enZzcG5ubG1ucX+FenVzcXJsa2xtaWZkY2FfYF9eXVxbWllZWFV2gnJvsYClO5Zxbmlok5eTkYmOgYyZlYyeoGfBr6mnqZ2YlYVtcHR5eoB6fYSKi42Qj5CRkZGXmJqoqqadlZKOjJCTko2Hi4yHhYSHiIiGgoWCf3t4dnV1denm5ufn6erp6+/w7evr6enp5+jr7Ovr7vD2+v6AhIKB/fr39/b09PP09/r6/oOBgICBgoGBg4KCg4WHh4iIiImHgoGA/v7/gID++/n49vv59oDz8O3v7evt7Ovq6ejp6uvr6efo6u3x8/Pw7/Dy9fXz8Ovq6Ofj4d7f4eHg3tza19na3N7h4d/b3uDg3tzb2dnZ3d/g4ePk5ebj4eHk5ufo6Onp6Ojp6+vs7Ozt6uno6Obk6ezo5efm5OLg3dvc3t/g4+Xq6ufn6Orn4uHh3+Hf3IDb3uDj5OLk5uvq6e7z8/T3+vby7/Du7vLw7erq8/T09Pf6/ICAgIKCg4WFhYmIh4eLkJGQlJmZm5qYm5qam5+bnZuam5mZmZqZl5iZmpiVlpeZm5eVlJGSkZCRkZCPi4qJiYiHh4aFhomLiomJiImLjIuPlJSRj4+Rl5yen5yZl4CWl5WTkpKRkpKTlZaWl5iam5yfoKWmpKaorbe/wb+4t7e5t7e9vry9wcXGys3Q1Nne6fR8f4KDhIWKkJCKh4iIioqHiIiHiIuPkZCPkJKVl5yen6eoo6GalZeWl5WTkpCRl56hoZ+dmIyKiYiPjZCYmpiXlJCRlZmfoqirrK2ur1Swr7CllZCkqaSmq66vrq+urayqpaSioJSN9/Lv9fr48/Hz7OXg5e7r5dja3Nrb3+Pn6+jr7ejl5erw8vbs6uTi5+fp7enk6ern4eHn6nR0dHZ3eHqEfTWBg4aHiIqOj46Qj46PjpGUkpSZnKKpp6ahoqKnn52fqqiqq7GxsLKztra2t7a1s7Gvr7CysIStKayrq6utrK6trrOzsLKytLS1tba4ubq8urq7vL6+vsHCwsLExMTFxsdkhWVfZmhoaWlqa2tsbWKYa4CKhIKBf39+fOWlp7mnotvFkJKmqau3xWZdXVldbW5vcnZ6gI2Ojo6QkpSVl0xNTk1OT05QUFFRUVBRUlNUVlhbX2BiY2Rnamxrbjg5Ojs7PDsBhJSDoYS6g5+CqoEIgH9/f35+foCSfr6AnH+EgI1/l4AFf39/gID/f6x//4CRgOaBtIDsgZGCA4GBgIh/j36LfwGBiYKeg4eEAgIEAID45ezX1s3Y29/o4tXOy9XZ3eDm6fmChIqQnKajm5ibmZWTnbfCwsbGyMG9xdHV1tjUy8C2ppmTjoiGg//26+HXzcW+u7awq6mlo56dmZeWlJSRkI6Mi4uKh4WFhIKB//7/gIWWlpeavb+mmZOLh//369zV0c/Cu7Wzsa2rpqKfnYCemZaUlI+Kh4aGgoD/+/r8/PTt6eHZ1dPQzszKx8PAvr67ubi1s7CvrKqppqOioqGenJqXlpSSj4rlhpWr/NX9n5vX08K9xbnAvrO0rKi4vb/S49vn7uLY1NHJxPSx0cm0urSorrXDka2nq6y1tsDT19HNzsfAt66po5+clpOSkE6NjIqKiomJiIaGhoWGhISCgP779/Xy8/Dx8vTz8vPz8e/s7OXj5ePk4+Pl5+nse/Ds6+nq6+ro6Onq8H9+fX18e/X19PLy7+vr6urr7OuF6k/o5ubq6urr6ufk4+Lg4uLe3N7f3+Df3t/e3d3c3d/c3Nze4eDj4+Pm6enn6Orr6+nk4eDh4uHi5uTh4eHi4eDf3t7c2tvc3N/j5OPj4uXkhOMa5OPk4uPj5OTm5ujl5OLk5OPi5OXk5urm5OKE4UXk4+Li4uPk5eTi4d/d3uLk5ePk5efp5eXm4+Xl5ubk5enq6uzo7fDw8vPy9PT2+fr7gID//v78/4GAgYKBgYWDg4WGhoeFiFWKiYyKi4mKi4yPkJCPjo+QkJKRkpGQkY6RkpWXlZKSkpSWk5KRkZGSlJaWlJKRkI+QkZCOj5CPj42LiIeIiYiHiIeFhoiIiYuJh4eGiImJiYqKiYiIhIeAhYWGiIeHhoeIh4eGh4eIiouNjY2Ojo+Rj5CSlJeWl5aYm5+ho6isrbK3v8jP2OLi6/R8gYGFi4uEg399f4GAgISGhoh9eXZ1dnd5eXl6eXd1dXNzdXZ0c3Ryc3N1dnZyc3JvcG9tbm5ub29vcHBxdHR1cnFvbW1ubm1sbWxqZWQDaGhnh2gBaYRogGZlZWVjYmNjYmFgwL6/wL++wL+8wcbK0tjZ1NXV2N7k4eLn7evi4t/c2dTT09TW2Nja3ODl6Ovr7fL2e3+DhYeJjY+TlJmeoqawsbK2t7a7t7S3u72+xL23u7/FzNPa2d/j8Pb06+rp4uPl7urm4N3h29TW1NLNysC3tLK0tbW0gLW2uLu7wMTFxcjJytDU1NTX29/i6Ojq7e7w8/b4+fv9/4GDg4SEg4WGhYSEhIGBgYLksv6ImJKPjomKhezm492Dj6jjhNq97enZ39zf3djijaSorKu0vb3Bx9bJ19XT3d/k7PuGjZaepqyys7a5ubi5t7S0uL3Bw8XL1eX2goSGComJjZSPmI+Gh4GAybzDsrCqsrW3vbiwq6qytbi6vsHKampucnqBfXZydHJubHWIk5SXl5eQjJGan56enJSLg3ZsZmJeXVquq6Sdl5CMhoF+e3d2dXRwbmxramloZ2VkY2NiYmBfX19dXLi3uV1gampqbX5+cGplYF2wq6KalZSQh4J/fn17enZ0c3GAcW1sa2toZmVkY2Jhv77Aw8W9trGrp6OioaGgn56bmJaWlJOUkpGPjYyKiIaFhIWEgn99e3p5dnRxt2iDseGx12mCxcKsr7apsK+lp5+Zq7S0zODHzNW+t7WyrKm0mMrAqbKrnqarsoGZlZmaoqSvvsPAvbi0rKaclZKPi4mGhYN3g4KBgYCAgH9/f4B+fnx7ennv7Oro6ezq7e7v7u3w7+vp5ubj4+Li5OXl5uns7nzy7+3q6+rq6+rr7PCEgYGCfnz4+Pj29PDv7+7u7+3r7Ozr6+rn5ePj5ujp6Ofl4uDh4eHi4uDe3N3b3N3c3N7c29zb3d3d3+GE5WDo6Ofo6uvp6OTi397f4OHh4uDf3dzb29za2tva2dnc3d7g4N/e397e4N/f4N/f3uHi4uLk4uLj4uHh4OHi4+Hj5uTi4d/e3d7f3+Hh4N/g4eHf3dvZ2dvd397f4OHh4eCE3xDg4OLg3+Li4ubn6enr7vDyhPMu9fd8ffr4+vr7fX6AgoSBgoOEhYeIiomIiImKioqOiouKiouLkJGPjo+Pj5CPj4SSKJCRkZSVk5GRkJGTkZCPkJGRk5WUko+OjY2OjoyKioqJiYiGhoaHh4eFhhWJioqNjYmIiYmKjI2Qj42LiouKioqEiWaKi4uMjo6Pj46Rk5WUl5eYmJmbnZydoKGkoqKkp6iqrbW5u7q8w8rS2eDo7vX6gIKGh4mJiIeEg4OEhIWHiImKh4eFhoiJjI+SlJWTkI+Pj46OjIyNiIeIjJCSjY6Lg4SDf4GHi4uEiYCIioyQjo+Rk5KSkpOSkpKMfHh/gIGHjI2Oj46Njo2NjYuIhYKCfHh4eXl4d+ff3ePa09bX1dbX2NjZ2tva2+Li4+nr6u7v8PHw7+7p5uXn6+zp7Ort8fHy9fn7/oCDhoeKi46QlJWanqKlrrCwsrSyubSyt7m7ub64sbO2usHK0R/O193p7+zl5efh4OPr5+Tg3uPf2dvc2dbWzMXCwcLBhMKAw8bGys3Pz9DR09fZ2drc3+Hk6enr7O3v8vT29/j6/H+AgYGCgoSEgoKDgoKBgoPntf6JlpGPjYmJg+ni3dmDgJzjhdi26+HP1dLTy8bUfYaNkI+XmpugpbC4x8jGy83R1t93fIGGi5CUlpmbm5uamZeYmp2foaWprrrIaWtrbW8IcXZyenRsbWlYcWttaWlna21vcG5tbGxvcXN1d3Z8QD8/QEJEQj06Ozg0NDlCSElKS0xGQ0RISUlKSEM/OjUvLCooKCdNTEpGRUNCQD08Ojo5Ojk4NzY2NTQ0MzQzMjIyMYYwKmBfXzAxMjIyNDg1MS8uLSxTUVBQTExIREJCQUFAPz9AQD49PDs6Ozo7PIQ7GXV1eHx+eHNxa2hnZ2hpaWlqZ2VlZmZlZmWEY1VhYF5eXl9fXVxaWVlXVlVUg0dlmbSDpTZkrKeUlqSYn52Ulo6KmqSnwNKrrLShmZmXko93fbuyk5qVjJWboXKJhIeKkJKera+spaCcl5KLiImEgn99hXuAfHx8e3x7e3t6enZ2dHHi4uLh5Obn6uvt7Ons7Ovo5eXf4OHh5enr7/T4+4D59PLv7/Dy8fDw8/qHhoWHhIL////+/fj19ff5+vv59PHx8e/r6err7e/w7+vr6unq7e7u7Ojn5Obm5ujm5unl6Onm5eXj5Ofu7u7s6ejo7O7x8u4D5+PjhOKA5OTi3+Df3d3e3d3c293f3t/g4eHg3d7f4OPj4+Li4+Li4OPj5+fl5+fl5eTl5OXm5urp5ePi4uDi4+Th4eHj4eDf3tzb2trd4N/g3t/h4eLf3d7e3+Hg3dzg4+Ph4+Pl5+fr8fT08/T09faAffj3+Pf4fX+ChIWDhYKFiIqLjYyAjIqKioyMj4yNjoyMjpOUlJKRkpKUk5CUlZSUkZGRlZiXk5OSk5aRkJCRkpKUl5aUkY+OjpCQjYmJiYeHhoSDhIaIiYiIiYiIi4uOkY+MjYyLj5SUlZOSj46NjYyLioyLi46PkJGVlpaWl5qfoaKjoqKlpaeop6esr7Kxrq+zs7Vjt77Ex8TGydDU193j6u/ven2AgX9+fn9/f318fXx7foKChYeIi4yNkJado6eknZ2enp2cmpqZkY6RlZ2dmpyXjIqJhoeUmpuYlZOSkZGWnJ2hqa2tr7CwsbCxpouBi46QnKqthK6Araurq6iknpqaj4aHh4eGhf7y6/bm3uLk39vb19LMzNDT1N3U1OHf297j6Onp7O7r5+Tl6unl5ePf4eHi4uPl53N0dXV3eHp6fHx/gYOEiImJi4uLj4yMjY+QkJKOiYqMjZGXm5ugpa+zs6+ws66ur7Wzs7KxtrOwsbKzsrKvrKt0rayrqaqrq6utrK2xs7OzsrO1tba2t7e7vcC+vb3AwcLDxMPFx8hkZWVmZmdoaGhpamlqa2tquo7JcoqFhIN+fnjNxcLHemqL2H7Mpt3FtbeysaqksGRgaWlma2xtcHF1gI+QkJGSlJWVS01OTlBSVFZXV1iGWRVbXF1eX2Nnazg5OTs7PD49QD07OzmVg6aEo4ODgo2DnoKtgYR/BX5+foB/kX4Bf4iAAX+KfqqAnX8BgIx/hoD/f7V/goCFf/+AnoDogbCA5oGQggSBgYCAh3+EfgV/fn5+f4t+i38BgYiCmYONhAICBACAtcuvsru9wsfS39ze5ODn8oCCgPf39fmJlJaWkpeQi4qNlaKwwM/Qy87U1+Pt8u7u9fPq49K9sKain5iQiYH26ODXzcO6t6+tqqekop+dnJmXlZORj4+MioeIhoWEhIKA//v7/P6BkLbAxr+onpmLjP/h1M7IxcG+u7m1r6uppaeAnJWTkYyKiomFgoD/+fXy7/Do5ODa0dDOzsrGw8G9urm3tra1sq+urayqqKajoKCem5mYl5SRkIyIhoOLpvjD+8rzo6acj4iOyrGtvJemu8K0ws7r0c/NyMTAvrm5xYGBx87Ov9Pf87z7z6eMu8bl6d3Hvbq0raWemZaUk4+Ni4pXioeEg4SEg4KCgoGA/f7//fz6+fn39PPy8fP08vT09fXw7/Lv7u3q6enp7e7t7e3q6Ofn6Ofm6Ozv7/J8foB9fHzz8fLx8fDu7e7u7+zr6+rr6ujo6ejohOUL5OHi4uDd3d3e396E4YDf3N3d3t7e3Nzg4uHi5OTm5+fn6Ofp6urn4+Pl5eXk4ubo6Obn6+3z9Ovm5eTl5ubm6+zs6+rp6ero5+fn6unp6Ojn6Ofm6Orq6+vq6uzr6unr6+rp6Orq6eno5eXl5uXl5+jo5ePi5OXl5uXl4+Xn5ujp6Ort7e3u8fHz8/Hz9ALy84T3Wn1+/n+DgoWEgf+B/3+AgoaEhIWGh4yKiouQlJCPjpCRk5aWmpiVlJWXmZ2Ym5qYl5eZlpOUlpiXmJmYlZWUlJWUk5KRkJGSk5STkJCQjo+RkZGTlZaVk5CNi4SJG4qJiYqJiYqLjImHh4iJiIiHiIeHhoaHh4iHhYaHXIaHiYqKiouNjIyMiouKjIyNjZCQkZKUlZWYm52foqOlqq2xtbi8w8zW3N/m7PHz8O3v7ezy932Cg4aHiIyKiIB8e3p5enp4dXR1c3Bydnp7eXp7e3h3eHZzcG9vhXAPcXFycnN0dnR0d3Ryb25uhG0HbGxsbW1sbIRqhGmAaGhpaGdnaGdlZmVkYWFgwMC+u7i3urm8v8LFxsbEx8fN0NLT1NbX3N3d5OLg3trW0c/Q1dbY2t/h5Ofq8u/w9n2AhIaIi4yPkZaYmqCmpqWmqKiqrrzCwMfExLq4vL/Fy8zS1+Hs8fT07u7m4OPh5ePk4ODh4ubi4dzb2c/HxsGAwsO/vb28vsDBw8bGx8jJy8zP0dLW2+Di5evw8fP1/P7//fz/gYKEhoeIh4aCf358fdeh2eeUkY6Ni4iDhobSxn6ShdSBosrlgPP6gvXDoquA78DAjKSorLC0rLjAxtTqgYOCgoaLj5GVmaWqrbW5ur/DxsnMz8/P0dLW3OHo7/YOgouVlpWQk4+JhPbm2cGAmaKSlp2fo6autrS2ubi9x2doZ8vIxcZsc3R0cXJsaGZobniCj5ydmpqfn6iws7Cvt7WtppWFfHRwbmhjX1qooZyUjYWAfnt4dnVzcW9ubWtqaGZkZGViYWBgYF9eXV1btrW2trddZnuBgn5vaWRcXKuako2JiIaDgoKAe3h2c3SAbmppaGVkY2JgX167uLSys7iwramkoJ6cm5qYlpSSkI+Ojo+OjYuLioqJhoaEgoF/fnx8e3l3dXNvbWl9rd+g04LFiImAdm1yuKCapYCPqLOessHLtbKtqailo5NuqoOExNDRwdrj4qjjupyAqLTQ1su2q6Sfl5GNioiFgoKBgYGAf318e3p7enp7e3p48fDv7e3q5+fr7Ovs7u7v7e3v7+/u7Ozr7Onn6Obm6/Du7uzr6+rn5ufo6+7y8POAgYOAf3749vb18u/u7vHw7+7s6+ro5+fn6Obk4uPi4uXj4eDe3Nzd3uHe3tzc29rb3N3c293d3t7c3eDj5OXk4+Pl5ukx7Ovp5eTi4uLj5Ofp6Ojl6e309Ojk4uDf4ePl5uno5OLj5OPi4uLh4uHi5ebk5OPk5oTnIebn6unn6Obp6ejo5+bm5+bl4+Dg4eLk5OHf397e3+Df34TggOHi4+Xm5ebn6ezv7+7r7u/v8fT4+Pd+f/5/goKFg4H/gP+AgISIhIKDhoiMi42NkZWPkJGQj5KUk5aalpeUlpmdmJuamJiTlpaUlJaXlZSWlpSUkZCPkJCPj4+QkZGRj42NjYuMjY2MjY+NjYyJiYiJiomKioqIiImKio2Pi4qKB4mIiYuLi4qFiXiIiYiIiIqLjI2NkJSSkpKTlJWVlJSVlZaWlpeZmpucnp6ho6WorLGys7e5vb/FyM3V2+Hm6vH3+vz6/Pj1+f2AgoSGh4iJiYqIiIqMjpKXkI2Li4qDhY6TlZGPkJKTlJWTjYWCgoWHiouKioiJiYmKjI+Tl5eWlJSEk4CSkI2LjZGSkpGRkJCQj46OjYyMi4qKiYeEgoB9eXh35+Pi1s/MztHT1Njd19PS1dLV2Nrc2+Di4uTl6Orr6+np6Obn5ubo6ezt7/H1/Pr6/oGChIaJio2SlJibnaGmp6ampqeorLi+vMS/vre1uLrAwcHHzdvm6u3s5ufk4OLg4YDh4uDg4ePo4uHe3tzUz87Jy8vJx8bFxsfJyszOz87P0dPW19nb3uHl6Ovu8PL1+vr7/P3/gIGEhYaGh4aDgoCAgN2m4OyUkI6LioiDhITJw4abhs9/kr/kf+3vf+q2lJ9716mqfIiNjpKVjZeepK/NcnRzcnZ6fH2AhIuPkZaZnB6fo6WnqqutrK+usLS4u8DFaG93eHh1dXNva8u+sqNGYmZfYWNjZmhrbm9ucXF0eD0+PXl5dXU+QD4+PDs3MzIzNDg+RExMS0pLS05RUVBQU1FNSUA5NDAuLSspKCZJRkVDQD07OoQ4EDc2NjY1NDQzMzMyMjExMDCGLydeXV5eXzAyNTQzMi8sKignTElHRUNCQUBBQkJAPT08PDo5ODg4NzeENg5sampqa3FsaWVjZGJgYIRhBF5eXl+EYFhfYGBgX15eXVtcW1paWVhXVVRTUlFPY5e4daI7j2doY1tTV5yGgIVncoiRgpOippORjouMi4hyLox8gLC3uK7Ex8iSvqKTcZaht7iwoZaQiYSBfnx4d3d3hHYZdHR1dnV0dHR1dXLi4+Pg3d3g4uPj5enq54TqSevt6ejr6ejn5uXo6/H09PX08e/t6ujp7fL1+Pf8hYaHhIOC/vv8+/b08vT3+fn49fLv7uzq7fDs6ufo6ejp5+fm5OLj5ujq5ueE5TTj4uTk4+Tj4ubj4uTp7Ozq5+jr6u/z9O/p6Ojo5+jp6+zq5+jv9/z56uPh4OLl5ubo6+flhuMM4uHh4+bo5ebl5OXnhOqA6Oju6+nq7O3t6+vq6Ors6eTj4uLh4eLh397d3eDj4d7d3dzd3t3g4eLk5uXl5u3w8+3q8O7v8vb6+/yBgf6AhYSHhID/gf5/gYaKhYODhYiNjIuMk5iSkZCRkZWWlZmZlZaVl5mfnZybmJiUmZiTlJeYlpaYl5SUkZCRj46Oj46AjpCQkpCNjIuKiouLiYiJiYaFg4OEhomKiouKiYiKio2QkY2Mi4yLjY+QkI2LiomJioqKiYmKjY6Qk5WXmpmZmp2en5+enZ6fn52dnp+ho6Smp6qrrrK3ury9wMHFxsnKztLV2d3d4ujs7+3y7enr7Hh2d3l6fHx8gIOFiY+VnKYonZaTlZSLjpufnpiVlpueoaGgmIyJh4yPkpaWlJCOj4+RlZufp62uroSvgLCvqaKfoaerrq6vsK+traurqqmopqSjpKKemJKRiIiE+/b25tfS0tfb2t7l29PP0szQ0dPU1Nja2NbW1dvf4eTn5uPh39/f4N7e3uDi5Obl5XJyc3R1d3d6e31/gIGDhIOEhIWGh4+RkJSRkYyKiYqNj42SmaOrrrCvrq+urK2tF6+tsK6ur7CysrKvsLCtqqurrK6rq6uqhKsira6xsrGvsbK1tre4ur2/v7/Aw8bIyMnKzc9oaWpqa2xsbIRtXmy4ir/Nh4WFg4F8d3p5q6V4jnvCdnyu1Hnd33fRl3iEa7eCh2NgZ2dnaWFobXBzh0tNTU1MTk5NTlBRUlNVVllbXV1eX2BfYGFgYmJlZmhqNjo+PT4+Pz49PHNvamUCg4KOg4OEhIOnhKKDhYKLg5uCr4EHgH9/fn5+gJJ+AX+JgAN+f3+HfgR/fn5+oYCvf4aA/3+wfwOAgH+GgAN/gH//gKqA3IGxgOSBjYIDgYGAin8Ofn5/f39+f35+fn9+fn+EfgR/fn5+i38BgaCDioSEgwICBACAvZKxrLGxtsDHxcfOz8/W2OTo6e/w/oeLiYiLjpKSlJeXm6i2vMTOzNHi5/GDhoL69vn/gPji08e/vbm0qpuRhP7u39fPyMG9t7Ktqqmlo5+dnJqWlpWQjouKh4WFhISCgYD9/v3+gIiZscfO0cu4r5iF9OPa1NDKw766tK2no56AmpqWlJGPkI2IhIH//Pn07erl397b1dTRz8zHxL+8ube1s7GwrquqqqimpKKin52ampaUko+Ni4mGg4OUi/fZ/K2t087DwLGtuLG3x7S51uLN3OW5zMvJxsPApZvRy3+EyMO8qLCpptfA2MrTu7y9uLSyrqqnpJ+bmZSRj42JiIiAiIWEhIOBgoGBgf79+vr7+fj59vX09PPy7u/w8vf08/Px7/P1+v1+f3739PHv6+vp5+no6eru8fLyfnx9e3t79PHw8PDx8fLy8PHt6uvr6unp6erp5+Tm5OLh4ODg393a2tzd3dzb2t3d3t/d2tze4eHf3uDh4eTh5OXl4+fn6OtL6+ro6Onp6uzv7u7w8vDv7+7x9PHv7/Lx8u/u8PPx8e7x7e7t7e3u7u/u6evu8O3u8PDx8PLy8vHv7ezu7u3u7u/u7+7w8vHw7+3thO4D6enqhOuA6enr7evs7O7x8vT19vf4f/v4+4OGhoaEhoODhYqJiIaJhIGAgYGAgYSIh4mKjYyUl5SSlJmXmpqfp6SlqKuur6uut7Ovqqelp6ikoJ6fnqChoJ+enZqcm5mYmJiXmJycnJiWl5OSkpKQj5KSk5WXlpOTkZOTkY+OkI6MjIyNi4sciYiJiIiJiIiHh4mIh4eGh4iIhoaHh4iIiYmLjYSOgI+OjoyNjI2QkI+Ojo+SlZSXlpqbnZ2goqSlp6issLS2vMHFx83QztDQ09jd4+nv8/yBgYKJi46Khn9+fX19eXd3d3Z3dnZ5e319gIGChIN/fHdwb3FzdHJwcXN1d3h4enx9fnx3dXNycG9vb25xcXNwcnBvbmxsb21ramtqa2ppgGhoaWhoaGdnZmZkZmRhwcHDwsHBw8XHyMfKysvPzs7NztDQ09TY2Nvd3tzW1NPU2Nzi6+vm5+bq7Ozv932ChIaHiYqMjpSYm5+jp6mprrK4usDHzdHU19nf4+vq6+vp6ezw8/Hz8PDt6u7t7e3u6+bp7O/o5eXj4dzVz8nIyMK9Xry/wsPDxsjJysvN0NLT1NXX297g4+np5+nt8/b18fH09vd8fHt8e3v3e3x9f8uVkY6MioTO2djlhoDchIuKgemB89HD95Gz3eq2ycuE/NjiiaKoqa+znZ6+u8SKoaWEqyipo6Ots7G1tra3vcbN09rl7fX5+/7+/oWJi4uIioqA6+LY3e7t5M3IgJ16k5GVlZmgpaSlqqursbO6vL3BwclrbWtqa21tbm9wb3R9ho2Um5qdqKqyX2Jgt7O1uV20o5WKhYN/e3VrY1utopiSjIiFgX17eHZ0cnBubGtqaGdnZGNiYV9eXl5dXVtbtbW1tFxhbHiEiYiEeHFkWKScl5KPjIiFgn56dXFvgG1samlnZmVkYV9euri2s6+uqqalo6CfnZuZlpSRj42LioqJiYmHh4eFhIOBgX9+fHp4d3Z1c3FvbGtpeoDYsNNyiMC4r7KemKajqbekqNHew9rnpayrqaeloox3j7CAhr68tJ+tpaPhwcvLxqmrraahnJiUkI2JiIWDgX9+fHx8DXt6enh3eHl4eHfv7u2E61Xq6Ojp6+rp6enq7O/u7u3u7/L0+f2AgX/49PDv6+nn5ebl6Ozz+PT3f31/f3189/b19PT29PPz8PLv7e/s6ebn5ufm5ufl4uDf3t3b2tjY2Nfa29vahNk429va2tvc3Nzd3t3e4ePi4+Lj5ufn6u3q5+bn5+rp6u3x9fbz8O3s8fXx7ezr6+rq6uvt6+rm5uaE5w3m5uTk5ujq6ufo6urrhOyA7uzq6+ns7e/u7Orr6+vu7err6ejp7Onp6OXn5uPk5eLl5ufl5ujr7e/x8/b4+X749vmDhYeIhYaGhIaLi4mIiISBgH+BgYKEh4aGiIyNk5qXkpWal5uboammqKqqra6qrbaxrqqopqeooZybnZucnZuampyZmpmVkZOVk5aampsil5WUkZCOjYyMjY2Njo6Oj46Nj5GSj46Qj4yMjY6Mi4qKioaJgIqLi4qKiomJiomJiIqLjI+Rk5WXlZaYmJWWlJaYmJeWlpaXmJudnqChpKWmqKutsLK1uLq8v8LHyczO09jY2tnb5Obq7/X6/oGCg4SGiImJiYqLkJGMiImHio6Li42PkZGWl5uenpqSi4KBhYmOjoyKiYqLjI2Qk5aampiYlpaVhJSAlZWXlpeWlJKSkJKSj46NjIyLjIuLioiIh4WDgX57f3135ODj4d7Y2t7g3tvc3tvc4OLd297g4+Xl5enr7O7u7Orp6urt8vHy8vH3+vn7/4GFhoeJio2QlJibnqCkp6iprLC2uL3Dys7N0dTZ3eLi5OTi4uTo6enr6enm5ejn5udx6ejl5+nt5uPj5OLe2dXQz8/MyMfJzMvMzs/Q0tLT1dbY2tzd3+Lk6Ovt7PDy9/n5+Pr8/f6AgYGAgID/gICAgsuTkIyKioXJ0dPtiX7UipiTgOSA78y67ICg1dykxcaD7sfNfIiNjpGTgYadmqJ4io6EkiiQi4uVmJWYmJmcn6SrsLa8wcjNzc7Pz2tucG9vcHBowrq0t8TDvaulUGJNXl1eX2FlZ2dnamtsbG5xc3R1dng9PTw7Ojo6ODg4Nzo+QkZKTEtMUE9RKysqUU9PUShMQz45NjQyMjArKSZJRkJAPjw7Ojo5ODg3NjU0hDMIMjMyMTAwLy+GLkNcXFxdLi8xNDU1NDEuLCkmS0hGRURDQ0JBPz07Ozo5OTk4NzY2NjU0NGhpaWdmZmViYmBgYF5eX11cW1paWVlaWVlahFuEWk9ZWFZVVVRTU1FQT05NTlZsq4OiOGGlnZieg3qPlZWfk5G7xK7F0YqLiYeGhYRyU1CReYK0rKKPnJaVza2zvbOWmJuTjImDf3p5d3ZzcHBvhXAKb3BwcXFxcHBw4oXdP9ze3+Dg4eLj5OPn7O7r6+zs7PL2+ft+gIH89/Tx6unn5uvp6e76/vv7goCCgYCA/Pr8/fz+/Pr39/r38vPz8YXvgPDt6ejl4+Hh4N7e3N3f5OPg4N/h4N/h4+Hh4ePi3+Hk4uHl5ebo6Ort7u3w8+/p6+vp7vDy8/T6+/Tz8O/1+PTt7Ozs8O7r6uzr6OTl5+Ti4+Tl5eXp5Obq6ufo6+zt7u7r7O3s7O7t7vDx7+7t7Ozu8O7u7ejo6uvp6uXl6ejmgOPj4uPk5eLl5+vw8vPy+Pz+gfr7/4SJiImHiYeHh4yNjIqJhIGAgH9/gYWHiYmLjo6Um5iSlpqXm5qfpqKkp6urraqrsrKtqaelp6ehnZyenJ6dm5qam5mamJKQkJKRlJqampaVk4+NjIuJiImJh4iHiImKioyQk5CPkpCMjpCRgI+MioqLjIuLjI2MjI6OjYyMi4qKiYqLjY+SlZeanJ+en6Cgnp6bnZ+fn52cnZ6foaSlqaqtrq6wsrS5uby+v8LDwsXJysrR1tjZ2Nfd3+Hg5Ojmc3V2c3Z3en+Fh4qSlpCKjYuQlZGPk5OVlZqboaWmoJeQhYSJkJWXlZGOjI2OEZGVmZ2go6qurKytrq6vr66uhK9orq6trKyrq6qpqKWkpaampqOhnpuYk4+Ij46C9+/y7Ovg4uTq5d/f39jX3OLb1trc3N3a2ODg4+nu6+bh4N3c3N7f3+Di4uPl5HJyc3R2dnh5fYCBgYGDhYSFhomMjo+TlpiYmpyfoKWEpCGlp6qsqqysrq6trq6usLCwrrGytLGvr7Gwr6+sq62uraqErCWrq62tra+vsLGztre5ury9wMHEx8nLzs/Q0dTW19ltbm1ubm7dhG5ItIeFgoGBe7K2ttJ8c7h8i4h71nbft6HPaIPExISqqHbRqa1iYmdnZ2lWXGpma0lTVFZYVlZVU1NVVlZXWFlZW15hY2Voam5vhG4BOIQ6DDs6OW5saGpwcGtnZZaDloSDhYSEAYWMhKKDhIKMg5mCr4EIgH9/fn5+gH+RfgF/iIAEf35/f4t+AX+egJx/g4CQf4aA/3+nfwSAf39//4C/gN2BroDmgYaCAYGEgod/hH4Df39+hH8Cfn+LfgR/fn5+i38Bgp2DiISJgwICBACA1KnNyMjHytDc3NfTz9Ha6Ofq/Y6Njo6Ok5ecoaOkpquwub3CztXm8PmAhIWQmZeUkJmgo6OVhv79/vnt59PGurWul4b969zRycK9urSyr62ooZ6enJmXlJGOhteJh4aEg4OBgP/+/vz4+f6Ej6rAzcGxmouC+uvh2tbSx7y1r6mAo5+dmZSSkI6KhoWDgf348u7q5eLh3dnX087LycTAvrq3tLKvraqpqKWlop+fnZmXlpSTkI2LioiFgoSGhuy7gNiDpaaemrau1c7TvsW+rsivrrvTwMLDwb+8l4HV0c+6ts3AzOvj2uPW3eCF1Z7Cs7Guqailop+cmZaRjouJiIcGhoWFg4KBhIAI/fz7+vn7+/yE94D49PDw7+7t7vDu7vDw7u/ygYR/+fj19vHv7evt6vD4fX319Pf59PHx8fDx7+7v7/Lz8e3s6+ro6err5+Tj4+Ti4uHh4N7d3dra2Nnb29zd3tve4N/g3+Hg3+Dh39/h3+Dj4+Tk5unq6Ovv7uzt7e7x7O3u8ffy8/X08u7x8/Ly8oD19PX18/P09vf19/f19vf49/b29vX39/b4+ff29PX39vb49/b19PPw7vHz+Pr7+Pn5+vz59vb08vL29n/59vb39/f5+Pb7+vn5+f2A/oCChISEgoSGiImGhISFhImKiIaEg4iLh4WFhI2ZkZCSlJKTmJ6knJqXn6Oqr7i7wcbAwU/JzsvMy9C7r7W3urmtp6uqpqWhn56dnp6fm5qZmZ2cmpiYlJWSkpKTkpGSkpGQkJGRkJCRkZKSlJKQjo2PkZGPjYuMjY2LiIeGh4iJiYmIhYdOiIqKiouNjo+PkZOVkpGVlJeWmJqbmZiYmJmZmZudn6Cio6Snqqmoq62usbK1t7i4uLq8v8fM0tri5u3w7u/09YGIjIiFgoCAgYKFgoJ8hHo6foCCgoqRmJeNhH16dXZ9fnp1c3R2d3d2d3l7enh2d3x3enx7fXx5dXRzcXBvb21vc3V3cnBvbGtraoVpAWqEaYBoZ2ZmZ2dmZcfJycrKzc/S0dLW2drW0dHS1NbWztfa29zb3uLp6OTk5ufl5ejs8PDze36AgYOFh4qNkJKWmZygo6ets7q/xcrN0dbX29vc3uTj4+Th4eLk5eXo6enl5efq6+zr6OTm5OHb3en09Ofn7+vd1Me/vLzAwsXIzNDS0YDR0tPS1dbY29vb3N7c3N3e4eDi5Obm5+rr7O/zent8foLhq6uH4PLm+NCcf3+Ahv3s9uDMzMuBtt2JmKnf6eTZ3+fz5+TziqOpqa6yranBubONoqy0usS+sKSmr7Kztq2xtbS9ytDj7fHs5+LY3M/LztfSxcPGzdTM09bS0tXP1ROtjKmmo6OlqbGxrquoqbG6ur7MhXCAcnV4fH18fYGFio2SmqCrsbdfYWFqcm9sanB1d3ZrYLOxsa6mopWLf3x4Z1utoJWNiISBfnp5d3Zxbm1sa2lnZWRjXJRfXl1dXFxcW7azs7S0tbdfZnaBh4B0Z11ZqqGbl5WRioR/end0cW9saWdmZWJgYF5duLSwrqqopqakoZ9rnZmXlZKPj4yKiIeGhIODg4GAfn19e3l3dnR0cnFwb21raWpye86YaoZmiouHg6KZu7e6p7CnmbianKq0oqKjoqCbYmi6t7anoryvutzc0NvJzNN9xo+uo5mWk5CMiomIhYN/fXx6eXl4eHiEd3p2dnfu7u3s7O3s7evr7O7t6ujo6Ono6enp6+vr6u3yhIeA+/j19vDu7Ovs6/L9gYL49/n59vX18fHw8fHx8PPz8u/u7uzp6Obm5OLi4eHf4N3c29nY19bV1tbW19nY19XZ29ra29zb3NzZ2dzc3N3f4ePi5ejp6enu7oTsKe3q6+7y+Pbz9PHu7O7s6+zs7O7t7Ors7Ors7e3u7e7t7O3s6+vr6uvshO2A7/Dw7/D09PHw8O3t7vHz9fb29Pb4+fj07+7v7+/y83758PD09PLz8u/y8/b4+P6A/X+CgoKEgoaHjIyIhYeIhoqMioiGhYqNh4WGhoyXkpCQlJOUmZ6lnJqXn6Wrrbe6wsm/wsrPzMrNz7qytLe5t6qlqKahn5uam5qZmZqWlZUJlJeYl5WWkpGOhI1tjIuKiYqKi4uMjIyOkJKVlJCQj5CTk5COjY6PjoyLi4qKi4uMioiJiImKiYmMjo+SlZWWl5ufnpqYnaCgoKKlo6Kjo6OioqOlp6yur66wsra4t7i6u72+wcLCw8bIys3Q1Nnf5evw8/T3/P6BhYSIgImKjIyLi4mMioiLjY6QlJScpa2ropeLhYOFkJSTj4yKi4uKioyPlJeYmJibmJuenJ+empeXlpaVlJKSk5aXmJSSkY+Ojo2MjIyLiouKioqIhoWCgoGBfnzo5eDf4OLj5+jj5Ofq5OXo6ufo6eXx9PDx9PX19/X08/T39/j4+v39If+Bg4SFh4qMj5GUlpudn6Wnqq6yub3CxsrO0dPW19nb3ITdgN7d39/g4uPk4eLk5ebm5+bj5eXi3t7o8fHl5u3q39rPy8nIyszNztLV2Nna2trc3N7f4OLk5uro6ers7u7w8vP09vf5+/3+gICBgoPlsKyF2O3i88mPgYGHi/nn+dzIxMZ+ptmMmpnR4tjJ09zl2dXjfIiMjI+Si4mdlpd5jJSaK5ylopiQkZeZmpuWmZyboqyvusLIx8O8trqvrbC4sqeoqq2xqq+ysK6wra4UZlRlY2JgYWNkZWVlZGRnbW5wdj6GP0JAQkJCQUJCREVIS05RU1MqKysvMTAvLC8xMTArJkdHRkZDQj05NDIxKiZIQz89PDs5ODg3NTU1MzIyMjExMTAwKkiFLigtLS1aW1pcW1xeMDE0NjUzLyspKE5LSUdHRkNAPj48Ozo5OTc2NjY1hDR/Z2RiYWFgYmFeXl1cXFtaWVhYV1ZVVVRUVFZXVlZVVVVUU1JRUVBPT05OTUxKTVdpqG5QOkhta2tpg3mampuIk4x9nH5/jJCCg4OCgHszUZiZlo+KoJebv8S2v6yut2uvf5qLgn56dnNxc3NwbWxsa2tsbGtqa2xub21ubm7e3oTfgN7f4ODf4uTi4uDh4uPk5ebo6enp7fKEiYD7+/Xy7uvp6ezr8/6Fhfz5+/z7+Pb09vb09fb3+vn29fPx8O7t7Ovp5uXj5OPg4+Hd29ra29zc3dze4d/d3N/g3N3e3d7c2Nja2dzc4uXo6efr7vHx8vX29PPy8/Pw8fT4//v18/HuO+zv8uzo6err7O3o5+Xm5+bn6Ojn5+fo5+bn5ufo6ero6uzt7vDv8fX08u7u7vDx8vT4+vr49/n6+PTxhPCA8/aA+PHy9PT09fbz8/T09/f9f/x/hIWEh4SHi4+Oi4mIiIaMj4yJh4eLjYaDg4WNlJGOkJWSk5mepJyZlZ6kqauztLi+t7e9wcHCwcW1rrKzsrGno6aln5yXlpeYlpaWk5KRkZWXlpOSj46KiYmJiIeHhoSDg4WFhYaIjI+RlpSAkY+Pk5WTkI6OjpCQjo2Li4yPkJGOjIuKioyMjI+TlZaYnJ2eoqioo56jqKmoq6ysqqmqqaqpqKuwsbO1t7m6u769v7++wMHCxcTEx8nIyszP0tPa3uDg4+Tq63R2d3p8foKDh4WBhoSKh4iJi4uOkpKbpKmlnpWFgIKFkJWYl5GAjYyKjYyOk5ujp6mpqaqrra6urq2trq6ura2sqqqsrKuqp6alpaWnpaSjpaalo6KhnpqYk5SSko6J9evi5OLi5ejm3t/g497h5+rj5eTg7/Ho6/Hy6uLi4+Pi5OXm4+Lh4eRyc3N0dnZ4eXt9gIGBgoaIiYuOkZOUlZeam5yfn6AEoKOioYSjRaSnpqepqaiqq6ysra6ura+wsK+vsra2s7S2tbOyrqqqra2sra6vsLGzs7S1uLu7vr7BxcfJy8zOztLS09XW19ja2tzc3oVuX8GSmnvD0sfksnR3dHp+49LlybGwt3iMun2RgbTIvKu5vcW5s8NiY2dmZmhfX2lgYktWWFpcXFxbWVhYWVtbWVpcW19iY2ptbm5wbGlraGhqa2tnZmdoaWdoaGlpamhok4OWhI6FjYSXgwGCiIOHgoqDmIKtgQiAf39+fn+Af5F+AX+HgAF/j36Cf52AnH+DgIx/goD/f5t/AYCPfwKAf/+AyIDegamA64GFggSBgX9/hn6Ef4d+BX9+fn9/i36LfwGCroMCAgQAgPrDg4SKioyLjYmGhIKAg4eKjZGWoaytrqymoaauuLOwr66ws7a+xM7b6f2FjZWco6Wopqatr6Gan6usn4756u3z5sWik4rw4dzUzsjCvcLa0r2vpqOgnZqXlJDvi4qKh4aGhYSCgYH//Pr5+vn+kKW0samknIyIhoT78/Dm3tLEgLevqKOdmpeTj4yKiIaC/vnz8O3o5ODe29bSz8zIxsTAvbq2s7GtrKmloqCenZyZl5WTlJKQjo2LiYWDkorw3fyqndvOy8PKtsHDys/d0L/s1tvptb/AwcCr/rTu19XKyrrMoM388d7Z7ti93vPwrq6wraupqaWinZmXlJGMjIyIGIeGhYSDg4KBgID+/f3+/fz7+vn39vj39oTyWvP09PHx8/Ht7fH19vb29/Ty8e/r6+7x+/r29PP1+X999vj49fX0+Pj08vPx7u3q6ejl5eTj4uDh4+Lg3dzc3Nvc3Nvb3Nza3N7f3uDf3+Dh39/f4ODd3+Lh4oTjLObl6ers7e3v8fDw9PX09PLy8vP19fb39vn39vf5/Pz9/vz8/f7+gID+gIGChIGAgoKBgIOCgID9+/7+///+//38+vr5/f7///7+/f3+gYD8/4CA/4CAgP/+/P38+/v6/Pz8/oCAg4WGhIeFhISEhYWIi4uNkJKRkI6OjZCOioqLlI2MkpublZiamJyanqKgpKmyuLy/0tjb3M/d5N/c2Njz6sXAws/LyMK6vL66tbETqaCgoKKhnZybnJycm5qampeVlISTEZSTkZCSlJSVlpWWlJGRj42NhI8Ujo6PkJGOjY6NiomJi42MiYmIiIiEiUKLi4+SkpOVkZSYmpqdnZueoqWlpKalo6Slp6eoqquvsrO1sLGytLK1tbi3t7e6vb/FzdPZ4OXt7vL2/P2AhYmEgoKFg0SBfn17fH1/hIeIiZilpZ2Xh4F/e3x9e3d0dHV2dXV2dnV2dnd6e3t/gIB7d3d3dnV1dXd5e3t5d3R0dXRzcW9vcG9tbIRrgGppamlnaWtrampqaWpoaGhpamzZ2dfW1dPU0tTV19vb2t3b3eDn7/J37e7s6enp7e53enx9gIGDh4qKio6QkpeZnJ2foqiqsLS4v8THyszO0M/S1NTV1tnb3Nzd3t/g4uHd39/f4eHg3dvZ1dXc29va4eTr7ODPzM7MxsXCw8fLCNTb29vZ2NbWhNd31tfW19na2trd4+fl5eTj5Ons8Hzlnc/C5Z+B0dmR+u3Vyvb06/OAtYK70cir/MrzgIvZ+frvy6ym8dC0sYulqaqssLC5xMTA6oOKlKKuo4qDhYuNkpaUmZqTj5OUm5uirr3VzLi0s7O0usDBwb+8xdbe19Lf6PKAyZxnaWxtbWxuamlnZWRmaGlsbnJ7hIaGhYB+gYeNiYiGhoeIjJGUn6exvmRqcHR6fH18fYKBd3Fzfn5zaLGnqayhiWxkXqKWko+LhoJ/go6Kf3Zwb21samdmZKVgX19eXl5dXVxbW7a1tLO0trpmcnt3c3FrYV5eXa6opZ+ZkYdTgHx3cm9ta2lmZGNhX122tLCvrKmnpaSin5yal5STko+NioiHhoSDgoB+fHt6enh2dHR0c3Fwb25taml5ftC10W9/xbmzsLGgsLS7wtHFsOrJ0OGEoS6gjtJzwbu7sbqmu5K/7fXUzOPVtM7u2Zual5ORkI6MjIiFgn99fHt5eHh4d3d2hHUTdu3u7+7t7Ovr7O3t7uvp6Ojp64TsNu3t6+zs8PX29PPz8O7s6ejq7O/8+/T08/X7gX/4+fn19fb29fXz9PHu6+np5+Xj4eHf3t7b2YTYPdbV1tXU1dbW1tna2NfY2trY2NnZ2NjY29vc3d/g4uPi5Ofo6eru7u7s7PDy8/Lx8vHu7+7u7+7t8O3s7O6E8Ajx8fDw83t79YZ7L3x6enp7fHp6e/b09/j6/Pv39fTy9Pf39/j6+/n3+v+Afvr5fX35fn59+/79/Pv4hPl3+vyAgoOGh4SIhoOFhYaGio2Nj5GTkpKOjYuNjYuLi5KNjZGZmpaXmZecm5+ioKOosra8usvR1tjK193Y1dLS4ti7ubzFxL+8trO1saykoJqZmZuZl5WUlZaVlZaXk5CPjY2OjY2NjIqKi4uMjY2PkZCPjo6NjI6GkICPkJCPj42Li4uOjo2LjYuKioqLjI2Pk5aYmZqdmp2ipqWpqKaor7OysbKxr6+ysrW2tba8v8PEwL/AwcDCw8TDx8fKy8vO1tnc4OTs7vL1+ft+gYWGhISFhoWGhoeFhIWHiYuQlJeYqLa0rKeVjYyKjJCPjYqIiYuKiYqOk5WWmCGYmZqdn6CcmpqZmZqZmJmZnJyalpOVlpSUk5GRkpGPjo2EjICKiIaEh4qJh4OAgIF+fH1+fXz18/Dv7+rr6erp6e3w6/P19/b3+v2A/v38/P3+/v6AgYKEh4iJjJCRkZOUlpueoaSlqKyusrW5vsDEyMjLzM7P0dHS0tXY19na3N3d4N/d3t3f4ODg3t/e29ve3d3d4ePp6+HX0tTV0tDPzdHV2oDe4ODh397e4ODi4+Pk5Obp6enr7PHz8vPz8/X4+f2B7qPaw96dfcPclfzpzL/48+n0f6t6uMq/ofm79IGLz/T258OdluK9npx7io2NjpGQl5+endZ3fYOMl499eHp/gYSGhYiKhIGEhoiIjZmjtKyinpyenqOnp6alpKm2u7WwugK+xC9wWDo6Ojk4OTk4ODc2NjY3Nzg6PUFFRkZGRUVGSUxKSUdGR0hJS01QVFdcLzIzNYQ3KTY4NzAuMDMzLypJRUVGQjcsKSVBPj08Ozk4Nzc4ODY0MzIyMjExMC9ShC+CLoUtKVxbXFxeXmEyNDUzMjEvLCwsKk9OTElGREFAPjw7Ojk4NjY2NTQ0MmNihGEIYF9eXVxbWVmEWAlWVFRUU1JTU1KEUQRQT09OhU2ATExLSUtUaayIoDhgp5ubmI+DmqGor72unNi5u8yFfn9/f3KlNJKcn5Oij6SLps3pw7fHw6Ou2L+GhH96d3V0cnFvb25sbGpqaGpqamlpamtramxt2dvf3N3e3d3f4OHj4eDe3d7g5efn5efp5+jp7fPx8vP28ezs6+jn6fH+/PQt8/T4/YKC/fz5+fv4+vv29Pf08O3s6unl4+Pj4d/d29fX19nZ2NrY2Nnb29jahd482tna2djX19fZ2drc3uDk5+rp6+3w8vL19fbz8vT39vT08fPv7evs8O7t7ujm5unr6uro6erq6u13ePB5hXiAeXh5eHh5eHh58vT1+P38+fnz8vHz9vj3+Pn7+/j6/4F++/t9fvx+gX/8/Pr6+vv7/fz5+fyAgoWGhYOJiIWFhoeHi46Oj5GTkpGPjoyNjIqKipCMjJOZmJKVl5aZmJ2gnp+hqK2vsLy/xMS7wcrDwsC+x76tra+2trSvrq2uqqQUnZqTkpKVk5CRkI+PkpGQkY+KiIeEiICGhYOCg4SDhIeJjo6MioqJio6Qj5CQj5CQkY+Pjo2Ki42RkI6MjoyMjY6NjpCUmJygn6CkoKOorbCtr66xuby8u7m4uLe4uLu8urvCxMXGv7/CxMPGxsfFyMjJyMfJys3O0NTX2Nrc3+BxcnV4d3d4ent7enx8fH2AgYCHi46RnoCno52aioSFhIeMj5CLiIyOjYyOlqCkpqSkpKarrKysqqusra2tq6upq6ysqqinqKempqenpqalpKSko6OioJyYlZqfn5qQioqLhYGDhIOA+fDs7O7n5uPk4d7l7eXv8fDr5+bld+vo6Ozq6ebkcXJ0dXR1eHl8fX5/fn2AgYSIioCKi42Oj5KTlJWWlpiZmZubnJudn6CgoaKjpKanqKmrqaiqrKytr6+urq+vrq+xsbO0sKysra6wsLGytLOytLa2t7m8vb/Cw8XGycvLzM3P0NLT1NXW2Nja3NzcbcWGtaW6gnClr4bd1rin5+DU43WYaKa2p4/ppNt1g73h482kgD91wJl6fGNkZWRlZmNma2lmj09RVFdaWVVUVFVWV1hYWFlXV1dWWVlaXmVpaGVlZWZnaGhoaWlnaGxtbGtub3GCg6mEkoWJhJWDAYKLg4eCi4OVgqyBCIB/f35+foB/kX4Bf4WAAn+Akn4Bf5yAsH+CgOR/A4CAf4+Aln8KgIB/f4CAf4CAgIx//4DNgOaBlYABgYiA7oEDgoGBhIAEf35/f4h+AX+JfoJ/i36LfwGBroMCAgQAgITfoqm0u7mura2srLKzs66rr7e5v8jJzMnBwb68wMnDubGxuamkpaq82++DiqSwrqa5yMLCvruxp6/Eva2S+efe0b6nlouC9+7o4NrTy8jEvsC9sq+ppqOenJj5k5KQjoyKioqHh4eGhID+/Pz9gYeOk6CqpZeTjYmHhoWOjPnggNDJv7WurKWdmZmWko2IhIH8+fXv6+jn5+Hb2NfSz8vHxMO+uri0sa6qp6WjoqCenJuamZeVlJSRjYqI9/vfs/LUiqqnnqC7t9Cau7OoraCWoJuV0MLDw8KGrKj+1dO5pKW/vsjYwsHJ3NfV4OjmqKqqp6mqqamloZ6cmZeSkI6LgIqIhoeFhYaFgoKBgID8/P36+vf29/j18vLz8fH19/f29fPx8PP5+/j69vTy8O/u7O3t7/Ly8fD19vb29PX09vHz8/Py8vHv7+zt7Ovr5+fk4eDf4OPj4d/d3t7e3d7c29ra2tzc29zd3d7e3t3d3d/h3d3i4ePk4+bn5ufq6+zvGvDx8/Du8PD09/f3+ff2+fn39/f6/Pr9gYCChIGAgoKDg4WHiYiHh4aFhYWGiImIhoaGg4SCg4OEg4ODhISDg4KCgYKGg4GBhISEg4GBg4KBgf+Ag4OBgID+/4D/gP+AgoWIhYSHhoiGipKUlpmZl5iWlZmbm5WUlpWWlZSQkJSVk5qZl5qbmp2hs8LLzs7U2dbT0uX8jZGahomaoJxFkqGgmI+Tjurc2svbzrS6ubSyrLCztrW0s6yrrqmloqCfn56bmpmXlJSWl5iYlpSUk5GPkJCQkpGPj42Rk5CPjo6Mi4qMhI5PjZCXl5STkJGUk5GQkpGTlpqgoqOlqKqqsrO0srCztLe2tLS0uL7BxMC9vbW0sLKzt7i3uLq9wMXIzNDV2d7h5err8PX29/Z6env4f399e4R6R3t8f4GGiZKUkY6LhoF8enp3d3Vzc3RzdHZ1dHV2dHZ6gYGGiouHgX+CgIB7ent8fH59fHh0c3J0dXJxcnJwbm1tbmxra2tshWsCamuHbIBtb29wcG9v3t7d2dbZ293d4HFzdXR1dnZ3d3d2d3p7fX2Bg4iLj5GSkpGVl5manqCgoaKipamtr7K3ub3AwsXGysvO0dXW1tXZ29rc39/c2tnZ2tvd4eDf3tzd3dbY2t7c2NbUzsPBw8zIv8XNztXj7OHZ0snJy9DS1tnc3d7g43Hk4+Xp7uzq7Oro6Onr86Cw2tuqk/L06oH//vXEhs/R1firo8fZ49H79dvF8J+34+KmwMaG7tbAiqarrK6wqqS/wcvb7vHxgYeF8OTl5Ozt9ff9goOEgoGCiZaaq9LexK6qpKKksbu8usHQ5ubl5efk8GRnsn6EjJGNhoaGhISHiYmFgYOJi4+YmZyak5ORkpSalY6IiI2AfX2BjqOyYWd7hIN9ipWRj4yKg3qBkYt+aK+hmo+Ab2NcVqSdmZWQi4eGgoCAfnd3dHJubGpnrGZkZGNiYWBghF+AXl25uLe4XmNoa3J3c21pZWNjYF9jX6ycko2Gf3p4dG9tbWpnZWFfXbe0sK2rqquopaKfnpyZlpSSkI2KiYiFhIF/fn18enl4eHd3dnZ1dHJvbG3b5MSRyYVqkpGLjaihv4WlnpSZiYOMh4GwpKWjoGuIZsm6uZ6Okq6ut8SysbiAzszFz9zOkZGSkZKRkJCNi4mGg4F/fXx7eXp5eXh3d3d2d3Z2duzq7Orp6eno6uro6Ojm6Ozx8e/x8O7u7/P29PTx7+3s7Orn6ens7e7x8vb5+Pb29PPz8fHx7u/u7e7u7evs6+bj4t7c3Nva3Nzc29ra2NjX1tXV1NTV1NXV1dYB1YTULtbW1tja2trc3uDg4eLi5Ofp6ert7/Dv7+7u9PX08fDu7O3u7O3v7/Hw8Hp6e3qEexx8fX5/gIGAgIB/gH9/f4CCgoGAgH5+gIGAgYCAhYEhf35/gYN/f4CBgYJ/f3+AgYCA/4CCgH9/f//7fPl+/X+ChIWAiIaIh4ySlJmZnJiZlpiZmJmRkpOTlZGPjo6Sk5KWlZOWmJeZna+9w8HBw8jEwcPP3HV3fnN1fYF/e4WCfnl7edDJxbzGu6uvraejoKOlqaqppqGipaCamJiZmJiVlJKQjo2NjI2Pjo2MjI2OjI6PkJCPj4+QkZGPjo2Mi4yOkZFPkpOSlpuZmJeVlZeWlpaXmJueoqmtr7C2t7e+wcDAvsHFycbExMPFztTY1NDMx8G8vsLFxMXGx8bJztDU19nd4ubp7vDy9vf6/4CAgf+Bg4SCWIODhIWJi5CYoaWhnJiSjIeGiYuLiYmKi4uMjpKUlJWTlpmgoKOlpqSgoKKioZ2bnJ6dnZybl5aWlJWWlZOSkZCRj5CQj46NjIuKiYeHhIB/goSEhYWEg4KEgBWBgv/6+/bv7vDz9feAgYGBgoKBgYCEgWeCg4SHiY2QlJeYmJeanJ6fo6WkpaenqayvsbO3u72/wsPGyMvNz9PU1tTY2tna3N3c2tra293f4eLi4N7f4dzd3eDe3NzZ1c/Nz9XSy8/W2Nvl7Obh3NbX2Nvd3+Hk5efq7e7v8fT2hPdo9vX2+P2mp8vdsJfr8OyC/P3zvXvKz930oZ3G19/N+PHbwOuUqtrWjrq+hN/Bp3mKjo+QkY+MnZ+ly93h4nZ6euPb293f4OXo7Xl5eXp6en6HipauuKmbmJOSlZ6kpKKptcHBwMDAvMNcO11CRUdIRkRDQ0JCQkNCQD9AQ0JFSUtNTUtLS0xPUk9MSUlMRkVFRkpSVy4wODs6OT1APjw5OTYzNTs4MypGQD45MSomJCFAPz89PDo5ODg3NTU1NDMyMjExMFSHL4UuQi8uXV1eXi8xNDU1NTIzMzIxMS8tLClNSUdFQT8+PTw6OTk3NjUzMzJiYmFgX19fXlxbW1pZWVlYVlVVVFNSUlFQToRPQk5OTk1OT1BRUU9MS0+zwaFplzpJdnZydIuInGyNhHeAcm9xbWiKfn9+flNpK5KanX52eY6UnaaSlJyxta6ww7F7eYR4D3d3c3NycG1sa2pqa2tqaYRqgGlqa2psbtrY2Nrb3N3d4OHf397d3+Po6efo6ejn6e/z8fHt6uro6Obm5+ft8PDw8/j6+fr59vPy8fHz8O/u7e3s6enp6OTj4t7d3Nva3dzc2trc29rX19XU09TV0dPU1NPT1NLPz9HS1tjZ2t3f4uTk5ufp6+3r6+vt8fDu7u3tgPP08/Dr6Ofo6+jm6Onp5+d0dXV1dnd2dnh5e319fXx8fHt7fHp6fX+Afn18fX1/gH9/fn6AgX9/f359foGDf35/gYGBf35/f35/fvt+gH99fX78+3v2ffp9gISHg4OHhoeHjJOUmJiYlpeVlZaXlpCQkI+Sj46Lio+QjJGRj5KUgJOWmaSwsK2rrK6urq+vs1laX1xbXFxdXWFfX19gYLOzs66xqp+ko5yal5ianZ6fnpmYmpaUkZGSkpKQjIuHhoSDg4SGiIiHh4iHiIuMjo6Mjo2Rk5KPjIuKiYuQk5OUlJWaoJ6cmpmanJydm5ycnqGnr7S1tLu8vcTJycfEyMrNZc/KyMbI0dTX19DMxsG9v8PFxMPExcTGyMjKzc7R0dPU19nb3eDj6nZ1d+l0dXV3eHh4ent5fYCHj5qblJCMhoJ+f4SKjIqLjI+PkJaboKGhoKKkqqurqqqrq6utraysq6mrq6qphahFp6empaSin6Oko6OjoqKgnpuZl5eTiISIj5CRkpGNiISCgYCCg/73+fTp5ers7e18fHl5enp3dnR1dnV0c3N0dXZ3eXx8hH4qgIGChIWFhoiIh4mLjI6PkJCRk5WWmJianZ6foaKjpKSmp6ipq6qqq6yuhrCAsa6vsLGwsLGxsa+vr7GxsrGxsbO2ubm6u7q7u7u8vb7CxcfIysvOztDQ0dLU1dfZ2dnYi4qjtY551NbUeOvt46ZmtrfI4IyLtMbRverlx6nPeo3IvHCXnHa/n4ViZGRkZWZkYWppa42bnp5RUlSko6Klp6aopqlWVldXVlZYW1sWXmdraGNjYmNkZmlpaW1vc3Nxb3FvcgKEg6mEk4WJhJSDAYKOg4SCkIOSgqqBAYCFfgKAf5F+AX+FgAJ/gJJ+AX+fgP9/i3+7gAF/hoAGf3+Af4B/t4CPgf+AioAEgYGBgOSBioD+gQl/f3+AgH5+fn+bfgR/fn5+i38HgYKCgoODg4mCn4MCAgQAgKj1qbK3wsfAx87O0NLW1NDO0NXe4enm4N/o4+La0MbN0Lm9wb/Bxs3P0tjq+4ujp6aqvr/DxL65raCjsK6b/uLXzcGxo5eNiIaD+/Lp4djTzsnBxsC4ubGqp6L8n5+cm5mYmJiUkI6MioeFhISImI6Ii46Ok5eYlZCSm6CXiPDlgOTZycTCu762saifmZeUkI6MiIWDgPv59fHr5uXk3tjTzszLyMbExcXAvLm1sa6qp6OgnZiWlJGOi4GIjYL45fapnM7TvbrAwtiy0NPIyLm5zMXDvMTDwsGs48773OfDz7/FpcLV28jM3M+/zuz9sra5uLq4sK+rpaCdl5aTkZCOWYyMjIqJiYiHh4WEgoKBgP/7+fj6+/j59/r9/Pb3+fz8/P39+/z69PTx8fDu7Ovq6+rp6urq7vH19vXz8/P09PPv7/Hx8PDu7u3s6unn5+fm5ubk5eLh4N/ehd873d3c3Nvc2tvb3N7d3d3e3d3e3eDf4uPj5OXn6Obl5+fp6+/w7/Ly9PTz9fj9/vr7/P7++/v9//+AgICEgyqEhYaHhoeKjIyNkI+NjYuNjY2LioyNjoyLiIiKiYeHiIiGhoeIiYqJhoSGgyWEhISGiYuKiIiFhYWDhIWHiYqMjpCRk5OSkpGSlZualpSTlJSThJQMkpCSk5GQkY+Oi4qMhY8+k5GQl52prKyvsrq/wcTL6PPk193f7IH8+/zt5+ru4M3GwLu2tri3ubq9wsC8u7zBvLfDv7a1tLGrpJ+cm5uFmg6cmpeXmJaTkpGQkpGRkYSPPJCOi42NjIyMjo+Qj5GTlJSTk5OWlpSUlJOVl5ygpaemp6utsbW1t7m4urq7vcHIy9HZ4NzW0MS8urm5uIS3a7i7v8LCwsXK0Nbb3t/i5urw8/HzfH1/fn19fXt5eHp9fX1/gIKAf39/fHl5enl3dnV0dHN0dXZ2dXV1dnZ2eHp/iI2MiYeAgIGAfXt7enp5eXl4dnR1c3R4dXR3dnRzdHFubm1ubW5tbW1uhm2AbGzf5eHfb29ub3Fz4N3c4+t45eZzdXR0dXV2eHd3eHt8foOFh4uOkpKRk5OVmJqbn56eoKKjo6OlqKmusbS4u7/Cx8rN0NLV1dHR0tLT1NLR0M7KyMjKycjKzc/T08rHxsLAvb7Cwru6ubm9vsPKztHV08zKxsLDxMXIyc7S1NgE297i54Tpa+rr6+fo6Ozyn6SP5OrugeKp2LDm07aR2dPa9OXl2MbEzP2sxeqYq9Tq2MjN3P7v5eiJpaurr7Cvo7zKwuDn6Ofl4uTo6u3g3On3gIGCgPz5/ICBhYqYsrvAztj1zKmqrrjB0vGHipmho6amgITAg4yPl5qXm56en6CjoJ2cm56lqbCtqqquraulnpedoIyRkY+Pk5mbnZ+tuWd5fHp+jI2Qj4uHfXR2f3xsrZqQin90amJcWVhWpJ+ZlJCKiISBhYB9fndycW+vbWxra2tqa2pnZWNiYmFgX11hbWdjZWhpbG9va2dqb3FsYa6kgKKakI6Oh4qDgXlybmtqaGdlYmBgXbi2s6+tqqinpKGem5mXlpWUlJWTj42Kh4SCgH58e3h1dHFvbWdvcmvWvM9ufbe+qaewssuhwsm3uKmpvLm1pqSko6GSv4rEyNWmvq+zmbTD07q/0sy3uuXknJ+io6SinJqWkIuIhYKAf35+AX6EfXN7e3p6enl5eHZ16+jp6+rq6enu8PLw7e7y9ff7+f359vTw7+3t6+rp6ebm5OXm5+nu8/X4+fX28/Ly8e7v7ezs7Ovr6enm4uPl5OPg4d/e3Nvb2tna2djX1dXW1tXU09LS0tHS09LS0tXY2dvd3dzc39/fhOF04uLk5uvt7/Lw7u/u8/X08/Lw7+7u7u/w8fB4eXp7fH19fH1+fn6AgYWHh4iIiIeHh4aFhISFhoiHhoWEhoWDhIaHhIODhYWIhoOAgYB/f4CAgYKDhIaJiYWFg4OEg4ODhIeJio2QkpOTkpOSj5OYmJSSkpSEklCRkY+Oj4+PjYyKiIiIiYuLiouNkIyLkJifnJyeoqaqra+0xcnEvcHAx2rU1dTLyMrLwbexrqqnpaekpaWorKqnqKmtrKi1sampqKWfmpaUkoaRBJKRj46IjRWOj4+Oj46NjIyLi4uNjo+Rk5OUlpeKmICbnqGpsbKxsre7wMTDxsfKzc3Oz9HY4Ov2/fbs39TMxsXGxsTCwsHBw8fJzMvKzdXc4OTm6Ovv8/f5/YGDhYWEhYSDgoKDhIaGiYuMi4mKioiGh4iJiYiJi4uMjY+TlJGQj5GVmJqZnaOpqKemoaGgoJ6cm5mYmJiZmJiVlpaWmGOVkpSVlJOTkpCPjo6NjIuJh4eHhIWHhoGBgP39/P6Ag4GBg4P8+PL0/IL7/YCDgoGBgIGBgoODhIWHiYuNjo+TlpaXlpeanZ6goaCho6SlpqirrbCztbi8v8LGy8zO0dPS0NGE04DS09PSz87R09HR0dTW2dfSz8/MycrJy8nFw8PEx8bKztPW2djV09HO0NHS09ba3N/h5Ojr7/Hx8fLy9fb09PX2+6Wditng53/codGp6MuqgdbT2/Pj4tS+u8b4pMjznJ7K5s+1xtH14tTXeomNjo+RkIiaqKPN2NnZ2dja297f2yTY3uV2dnh37OzveXp8gIuboaSwuM2vlpiaoaezzG9zfYODg4IjQ2RDR0dLTEpLTExMTU1NS0pJSktNUVBRU1VUU1FPTVBRSUuESjdMTU1NU1cvNjc2Njo6Ozs5NzMvMDIwKUI6NzQwKiclIyIiIUA/Pjw7Ojg3Nzg3NTU0NDMzUzIzhzI0MTAvLy8uLi8wMjIyMzQ0NTU1MjAyMzMxLFRTUExJSUpERUJDPzs5ODc3NjU0NDIzZWRhYIRfgF1cXFlZV1dYV1dXVlZVVFNTUVJRUFBPT09NTEtHTVBNqoqeNluZnY+RmJ+1k7K6oqealaimnoV9fXx8cZhLjKzIh6GWlombpLqkqba8pJjWxoSFiouJiYWBfnl0cm5tbG1ubW1tbm5ubWxsbWxsbW1satPV2Nvb3d3e4uXn5+PjD+bq7vDx9/Dt7ejn4+fl44ThgOLj5ebn7PP2+Pn18/L28+3q7O3r7Oro6Obl5ePi4eDe3d3b2dvc2tjY2tnX1NXV1NXV08/Mzc7OztDPzc3T1dbb3t7f3+Dg3+Hj5eXi4OXn6+7v8u/s7/Du8vLv6ubl5+jm5ubo6HR1dXV3eXh3eHh4enx+gYGChIKCg4KBgoKBQICBgYaDg4GBhIOBgYSEgoGBhISGg4B+fn19fX5+foCAgYSGhYODgYCBgH+AgYaHiI2Pj5CPkZGPjJCVlZKPjpCFj4CNi4qKiYmIhoSEhIODhISFhYeLhoOJkJWPjpGSkpOVlZibnZudnp2eUKKkpqOkpKSgnZuamJWUlZGTlJSWlZSXmJ2dnaSkoJ6empWQjYyKiYqHiIeFhoaGh4aFhYaHh4qKjIyLi4yNjImHiIiJi4+RkpOWl5qbnJucm56enJqcnICeoKautba3uLu/xMnHyMrKzs7Q0dPZ4erz+fTp3NLKxMXIxcLAwL6+v8HCw8LDx8nM0tbY19bY2+Dk5nV2dnV2eHl4dnZ3eHp7fYGDgH1+gH59f4GDhoaJjY2NkJScnpmVlJqgpaajpKSnqausqqurrKmnpqOio6WkpqempaakpDain6Cho6Gio6GioaCenJqWkpKQi4+SkYmHhPz6+/+ChoOChYT/9Onm63jr73p9e3l6d3Z2dXWEdoR3A3Z4eoR7HHx9f4CAgIGBg4OEhIaIioqMjo+SlJeZmZydn6GFoICio6Smp6enqqmpqaqrqqupqaurq6qpqainpqenpqenp6ipqq2vsbKxsrS1t7e4uru+v8HExMbIyMjKy8zO0dHR0tGHf2+zwM11yI65mNq1jmvIvcjf0tK/qaa57Yyk1JKDrtG2mqmx2sS3uWJjZGRlZmFeaG5tjJeanJ2dn6GipCWioaKiUVNTU6qtrldYWVtcYmVlaGx1bWRmaGprcHg/P0JDRENDAoSDq4SRhYyEkYMBgqKDl4KlgQiAf39+fn6Af5F+AX+FgIJ/kn4Bf6GA/3+If/+AiIABgf+Ak4DfgYSAhoGFgAOBgID9gQZ/f35+fn+WfgF/i36LfwGBjYKEg4OCk4OHhAICBACAleqeprGzwsLFxtLY3eXm5ujt9fyCiIWGhImIiYWA+Pft5u+ChoqOjoiA9PqAiZGUmp+pqK+6uLOyp6CjopyM++TY1Mm6r6aem5WRjoaB+PHm39jW0s/Jw767t4yxr62rqaainZmXl5eUkpGSk5imqqOlp62qqqekpqSgmJCEg/GA4t7Wyr+5sa2pp6KdmpeUk5KOjo2Lh4P/gf78/fnx6uLc29nmg4Dy59rQycG7ta6rp6KdmpeVkpCNpLbv4Nqo5dOGp52Wk5GHrLCnq8G3ubips7XGzMvJxIqr5PXJwrSqqr+/xsrBwbnV29re7vq1u7u9u7etpp+bmZiXk5KQkI6Ai4uLiYmLjIqJiIeFhIKA//77+/z9/v+A/vv69vb4/fuAgID//fz39fX09PHv7u3t7evp6ury9vX08/Dy8O7v8/Hv8fPv7PD18Ovr7Ozr6uno5uXk4uPk4+Lh4ODh4d/e3t7d293g39/e3t/d3t7d3N/k5uPh4ubm5uvx7+rt6+wy7fDy9fb39vb3+Pj6/P79/v+AgYGBgoODhIeHh4aHiouMjo6Nj5CQkpGSk5SUlZSUk5KElB2XlpWVkpGSkI2Njo2Mi4yJiomIhoaGh4iHh4iIioSJf4uOkJWamZeWlJWXlZaTj5CQkY2OjoqJiIaGhomKi4uOjo6PjouNjouLiomIioiKjIyNjo6PkpWYmpyhoaOoqq+2urq7vcDQ1trd1trQzc/PzczJw8fGxL+/x8jHw8LCvbi5uLa1trvAvLeurK+urKqkoaCin5ycnZ2cm5iWlpWEkxWSkpGQjo6OjI2MjY6OkJKTkpOTk5SElYCUk5SVmJianKCkpaarsLS2vL69v8HEx8vOz9PY2djW1NLQw7y7urm1tba3tba3ur3DzdPZ2dvf5uru8PV7fHx9fXx8fX57enl5enp6fH5+f39+e3t6eHd3dXZ1cnJydHR0dXd2eHd2dnd8f4WLiIKDhoB+fHx+fn57eXh5eHV3dh95fH57eoGEfnp4dHBwb25tbW1ubm5vbm5ubW5vcG9whG+AcHFw4ePj5eLk5ORzdXXqdXV2dnd4enx+gIOIiYqNjpCRkpOTk5SUlJWXmZueoqOlp6Wnqq2usLa6vcDCxMfFxcTExsfJyMTExMK/u7m4tba4ury7vL6/v7+9vL6+vry9v8PGxcfP1trX0MzJxsLCxMXGx8jKzM/T1tnb3d3f4udq6evr7e/2+6X+3u7O4Nyli+3C48rMytvV6/uslNHJxL32wNrki9Dr9ObavKzYybfDiKaqqK2vsba+xsHq+oCGgoD++/bu6OPl5efr9PLu7+/x7/WAh5Cbs8jkg42E5c7IyMzL0c3b6PSFkIB2uXyBioyWmZqaoaaqr66urbK2u2FkY2RiZGRiYV21s62orFteYGFiX1yutFtiaGtvc3t7gIiFgYB2cXNxa1+ol5CMgnlya2dkYV5cWFSjnZaSj46KiYaDgH58YXl4d3Z1c3JubGtpaWloaWlpbHV4c3V2eXd3dnV2dnNuaGBhsoCloZqTi4iDf3t5dXJxb2xsa2loZ2ZjYL1fvLu8uLGuqaWlprFmYbavpp+ak4+LhoOAfXp3dXRxcG6Else9vYnAhmuUjISBe3GWmZOctKirqpmjpKyrqaajbYiVwK2pmJeZrK62uK+spMfSz8zi5Z6lqKikoJmSi4eEhIOBgH9+fQF9h34HfXx7eXh4doXsUu3u73nx8fHv7/H0+X5+f/j18vLv7+7u6unn5uXl5OXm6u/09vTy7+/t7u7z8fDt7Ovr7O3p6+vp6erq5+Ph3t7f3t3e29rY2NnZ2NfY2NfW1NWE1CLV1NPU2Nve4eLd3d3f3+Pn7+3q5+Tm6Ovv8vHv7+3u7/DxhfKAenl7fHt7fHx+gYCAgIGDhIaHhoeIh4iJi4yMjY6Pj4+NjpCRkpSVlZSRj5CQjImIh4iJi4iEhYWEhISFhYSEhIWGhoeIh4mMj5Sam5eXlpaYl5aSjo6Pj4yMjImIhoWEhIeIiYiIiYmKiYiJiIaHh4WCg4SHiYmIh4iIiImMjpCAkpOVmpqcoKOkpqipsLW4trS4sK+xra2tq6iqqqmkpautrKmqqaWkpKOkpqirrq2po6Olo6OhnZmYmZeTkZCQkI+OjYyMjY6OjYuLioqJioqLjY2Ojo6SlJWVlpaWl5eXmJeXl5mam52gpKaqra+zusDEycvO09TY3t/i5erv7/BV7unm4dHKx8XEwb28vL2+wcPFydHb4eXl5uvv9Pr/gYODg4KDg4WFg4GBgIGCg4SFiYuLiomJioiFhoaIiIaHiImIiImNkZSWlpSVmZ2jpqaioqSem4SZO5uZl5iZmJaXk5SWl5WUnJ+bmJeUkI+Ni4qKiYmJh4iIiIaGhYWGhIOAf4CAgYOC/vj1+vz8+vqAgoD/hIAwgYOCg4WGh4yLjI2NkJSUlJaVl5eWmJqcnqGkpaepqqyusLO3ubzAwsXGx8bFxsbGhcgRx8bFxMPCwsHBwsTFxsfHxseFxnDExcfJyszN0NTa2dTR0NDPztDQ0dLT1NXW2t3e3+Lj5ejs7vDz9fb6/6jy1uXM4daWfey84sC+wNbT6fijjs7Bt7b1ut7mi8Xm8eDRrZrItaGweoqNjI6PkpOaoJzR4HN3d3Xo5eXe3dnc3N3f5+fmhOcY7Hl9ho6crL5sdG3CsayusK+1sbnDy210ZD9hQUNGRkpKTExOUFBSUlFQUE9QKSwsLCssKyorKlJRUE9OJykpKCkpKVFSKSstLjAxNDM1ODY0Mi4sLConITw2NTMwKygmJSUkIyIhIUFAPTs7PDo7Ozk5ODgtNzY3Nzc2NTSEMwkyMjIzMjM0NDaFN4U1gDQwLjBaU1BNS0hIRkNBPz4+PTw6Ojo5OTg3NjVoNWdoaGViYWBfYF9rPDhnZGFeW1lXVVNSUU9OTU1MTEpIWWyYkptklDtLeXFra2JYeXl6g5iSk4x/g4GFgX9+e1FkS4uOinp8gpKXl5iVlpCtvLWtzMaIjZGSjIZ9dnJxcG9uCm5tbm5ubGxubm+FcDdvbWxsa9bV2dvd39/gcePj4+Li5ejueXl67urn5+fl5efj4d7e39/h4uTm7fL09O/u7Ovs6+/uhO2A6uzs5+Xm5uXn5uPf3Nra3d3b29vY1dXY19bY2tnV09LS1NPRz83Mz9PY297i4t3c2drd4ebw7efm5Obn7O7w7uzs7Onr6eno6Ojp6nV1dHZ4eHd3enx7enp9fn6Cg4KDg4GDg4aHiYqMjIqKiIuOkJCSkJGSjouMjIqIhoaFhohshIOEgoGAgYKCgoODgoOFhISFhoiKkJSVk5ORk5WSko+Ki4yMh4aHhYOAgH+AgoSEg4OEhIeEg4OCgYOCf317fICEg4GAgoGCgYKBg4SFh4eJiIiJjI+Rjo+RkpSSko+NjYyLiomIiouLioqPhJCAj5GOk5OVlJebnZyblpaZmZmakY6NkIqGhoSCg4SCgYOEhYaGhYSFhIOCgoSHiYuMjY6TlJeYl5eXlpiYmJeXmJmZm52eo6aqra+0usHEycrM0NPX29/j4OTq7Orl4+LdzMnGwcG/ubm6ube4u8DDx83S1tXU1tjb3+NzdHR0cnMNcnV2dXV0c3N0dHZ5fYV/b4KCf4GDh4eGh4iMiYmLkJieoJ+cn6Ckp6inqamoo6Gfnp2fnp+hoaOjoqKbmZmYmJqdn6Kjop+fnZyZlpWUlZSRkpSTkI6Mi4qIhYGBgYCBg4H26+ju7+/r6nh6d+x1dHV0dHR2dXV0dXd1dXRzdId5Mnt6e35+f4GBg4WGiIqNjpCTk5WUlpeZmJmYmJiZm5ycnJ2dnZyen6CgoKGfoKKioaKjhKSAoqGioaGhoqGkpaanqKqtrrCxsbKysrGys7S2t7e4ubq7vL/BxcfLy8rKhNK5x7LKwHlo3qfRsKytxcLZ5413u6icpueiwc6EsdPcx7aUfKWUgI9hY2RjZWZmZ2hpZImTTE1OTp+eoaGhoqGipqaoqKqsra2tsFlbXF9kZ286PjwBcIVtB29tcHN1PD4ChIOShIqFhYSHhYKEk4WPhLGDmIICgYKLgYKCk4ECgH+FfgKAf5F+AX+FgIJ/kn4Bf6GAiH8BgIh/g4Duf/+A/4CggOqBiIAEgYGBgPmBnH4Bf4t+i38CgYKEg5KCh4ODhIuDgoQCAgQAgIfakZSjrbS6wMXK0tva2ev5/IKIgY2SjJOampyjrLKvsLGwrrO4ubq8trfBx9Le3+Pq6ePZ/tDTz8rCt6+ooZSIgvnx5N3V0Ma8tq6jl4+Jgfj08+zm4t3X08uZwb25tbOvraurrKuqqaalpqKio6GgoKSooqawvcPFxLiklIPtgOTZ28vLwbGqqKShnp6cm5qamJKQiYmFgoD++///+/j4+/z/goH77uPc1c/Kx8O+tq+qqKSfnJqVkL/Vhv7l2uyym8XEwsy5ocXAuNL13OLmzeXqyM/Pz8y07cLwys+7y7nBob/RwMzN1bStyuWFtbS0tLCwq6aioZyYlpSRjoyLSomJiYiHiImHhYSFhYOBgYD//fz7/Pz7+/z4+Pf49/X09/+A//7/+vb08e/v7ezs7O3s6err7Ozp6u7v8fDu8PPx8fHy9fP08/HvhOuE6YDq6OTk4uPh4eHi4uPi393e39/g4eDg4N/h4d/g4+Pm5OXp6ubp6+3y9PX19vb08vPz8/T19fb2+fz7//79//+AgoKChIaGiImKiYmLjI2MjpKVlJSVlpeYl5iZmZqbnZ2dmpudnZmZmJaanJmWlZORkZCRj4+Ojo6Pjo6NjpCUloCTkI+UlZOZmpmalY2MjIuLi4iJiIiOjYuKjYuMiYaFh4uOjo6QlJaZmZeUj46Pj4+OjY6NjI2Nj42OkZWVl5mcnp6goKCenp+jp6qxs7fByMi/urm4ur3Bw8TCwsLFx8jJysrJw8G9vsK/wMC8trKurKuqq6qopqSlp6ajoqCfn12fnJubmZmZlpWVk5KQkJCOjIyOkJGUk5WVlJSVl5iXmZqamJmZnJubm5yeoaKlqauutrq9vMDByM3Q19ra3Nzd2tHLw8G/vbu6urm4t7a2uLzAxs3V3OPo8fd9fn6FfRV8fHx7fH18fHp6enx9fH1+fHx6eHeEdoB3dnd2dXN1d3h3dXZ1dHZ4enx+fn57eXp8fXx7enx+fX17end2dnh5fIKGiYuLg4B9eXVycG9ubnBucHFxcnFxdHFxcHFxcHByc3NzdnRzdHR0dXPm5eXm5+jq7Xd4enx+gYWIioyPkJGRkZOUkZGUmJqcoKCjqKelp6ioqaqpqgWrrrK1uIW6Kry8u7q3tbe1s7KxsbCxsrOztLS1uL7FxMTExcnNzMvK0NbZ3N7f39jOyoTHe8bFxsjJysvN0NLW19nb3d7j6u7r6ejt76HVwIfo2821xebq+uKs4MnosO+xx+LR4Nby7eHH46a75duUrMWG79DNh6apq66ypKi5vc/7kaCdnJeRi4WDgYKDhIKDhITu7O7y9PuChI2arcXxkaSmnJuP/vXxgv748YCAg4BsrHN1foSJjpSWmJ2joqCqtbZdX1xhZF9jaGhobXR5d3h4eXl8gIKDhYGBiIyUnZ6gpqShmrSTlJGNhn54c25jWlamnZaPjIeBenZxamJdWFSkop+cmZeWkY6KaYGAf317eHh3eHl4d3Z2dnd1dHVzdHV3end6gomMjoqAc2teqYCmn6CUk4qBfXp4dXN1dHJxcXJsaWVmY2Bevb7Cwby6vMTFxmVmxbmvqaKfnJmVkYyGg4B9fHl2c3CUomfHwrbJdX2yr66/q4y4rajI8tLb4MHX46+srKyplcGAu7O3oMSxtJqxv7zFxMmro7fXdp6dn5+amJSQjImGhIGBf318fQF8iXuAenp4eHd37e3s7Ozt7O3v7u3t7u/t7vH6ffr49/Hv7Orp6efm5ebm6Ofn6evq6evs7e3t7O7w7/Du7fDv7+zt6+fm5eXj4uPh39/e3tzc2trZ293d2tfY2tvc2trY19fY2NbX2eDf4+Lm5uTi4+Pp7u7w8e/v7+vs7e/u7Ovs7vBB8vDy9PX19Ht8fH1+f3+BgoOCgYKChYaHi4yMjY6Nj46Oj5KRkZKYmJaUlpmalZaWlpmbmJaUkI6OjY6Ni4yMi42EjDmNj5OSjo6SlJObmZiXkYyLioiIiIeHhYWKioeFi4mIhYSEh4qNjo2Ok5SVlZKPiomJi4qKiIeIh4iEh0iIjIyNj5KSk5STkpGRkpeamp2foqWqqKOgoJ6go6anp6ampqirrKusrKuqqqalqKqqqqakoqCgnp6goJ+cmpqcmpeUk5KRkZCGjg+Ni4uKiYmJi4uLjI6Rk5SElzGYmZqanJydnZycm52en6GkpqersbW4wcnMy87S2+Dm7vHu8fLz7uPd0M3LyMbEw8PChMCAwsXL1d/m7PX6/oCBgoKDg4SFhYWEg4SEhYSDg4KDhIaIiIiJiIeEgoGDh4qMkI+LiY2QkpGQj42PkpWWmZ2dnJiUlJWWlpSUlpmYmJaVkY+PkJGVmaCjpaafm5qWko6Li4qJioiIiYiGhoaIhoSBgYGAgIKDgoOGgoGBgIGCgPyA+vr7/Pv9/oCAgYKFh4mLjI6QkZOTlJSVkpSWmpyeoaKkqKmoqqurrK6usLCztbm8vb6+vb6/v8C+vb6/vr28vLy9vb6/v77Bw8bJzMzNzc3Q09PS0tjb293f39/Y08/Ozs/Oz9HR0tLU1NbY2tze3uDh4+bq7Ozr7O/xos23he1e3sWqvOLo+OGmysrzvO6lx9/J2NLy7d6/1puv4dKCp8OD4b+5eomLjY6Rh4yYmqndfImHh4SBfXl3d3d6enh5fHvo5eXn6e16fYWNmqzJd4eIgH521MzLbNHOyWpqaxM7Wzw9P0FDRUhJSEhJSklKTE0mhCU+IyQkJSUmKCsrKywtLi8xMTIzMzM1NTk8PD0/Pj07Rzc3NDIvLSooJiIfHTk3NDIwMC0rKyknJSQiIUFAQUCEPwg+PDA6OTk4OIQ3hTiAOTk4ODg3Nzg4OTk7PkFAPzw3NDIsUlNQUUpLRkNBQT8+Pj8/Pj09Pjw6Nzo3NTVsbnFwamttdHV1PD91bGdkY2JhXVhXVVJRUE9PTUxKSWNsS42Zhps4W5SQjZ2KbJ6Wk7PYubu9qbS6iYKCgoBzlkOFm6mBrZ2ai5ietsC4r5oXkZfJZIaEg4J/fnl0cnNxb21raWpqamuEbAFuhW0ibm1sbGvZ2dja2tvc3uLh397f3+Dh5O547erq5eTi4uHh4oThCOPj5Ofm5ebphOuA6Onr7O3q6e3u7uvr5+Pj4eLi4eDd3t3c29jZ2trV2NrX1dbY2dva2NnW0tDQ0NHS197f4uDi4eHe3+Hq7/Hx7vDu7Onp6Obk5OTm5+rq5ujq6urrd3h4d3l7enx+f3x7fH1/gIKFh4eHiIiIiYiLjIyLjZGSkpGUl5aSkpGRl5oblJKRjYyMjYyLioqIh4iIiYiIiIyPjYmIjY+OhJICjYeFhW6DgoGCh4aDgYaEgX9/f4KGiYqJio+PkZCNioSDhYaFhIGBgYCBgYF/f4CEg4KFiIiHiIaEgoKDhouLioeJiYuJhoSCgoOEhoaGh4iIiYmLjY+NjI2Mjo+QjpCSk5GSkpGTkpSWlJGPkZGOi4aDg4iCM4GAgYKBgIGChYaIiYuNk5SXl5WWl5iZmJqZmpqamJqZmpqdoKOlp62ytbzExMTJztfd3oXngOni2dLKx8TAwL+8vLy7ubi5vL7Cx8/U19vc3W9wcXFxcnNzc3RzdHR2eHh1c3N0dXd6fH1/fXt5e3p9hIaMk5CJhoyQk5aRkY6Ql5mbnqKjoJyYl5aWlZSVmJubmZmYlJCQkJKVmKChoqOhoJ+dmZWUlJKRk4+Qj42KiImMiYSAEYB+fX6Af35+gH58fHt8fXnphOUH5uXlcnJxcoRzKXR2dnV2dnd4eHZ2eHl7e31/gISDgoWGh4mLi4yLjI6OkJKTk5SUlZWVhJcamJmampqbnJydnp+ho6OkpaWmpaanqaqqqaqEqwKsq4Wpfaqsra6xsbGwsbKztLW0tba2tri5u7y+wMG/fq2be9bJs5alz9npzpCquNCi2o6ozrfHw+Hcy6e9gpXLu2eIn3jEnpZhYWNkZWVcYWdnbI1KTk9SUlNTUVJTU1NUVFVVV6moqautsFpZXF5la3ZARUdDREF6eXc9eHd1PDs7AoSDkISjhQGEjIWPhK6DmoKKgYKClIEDgH9/hH4CgH+RfgF/hYCCf5J+o4CSfwGA7n//gP+AnYD1gYiA9YEDfn5/in4EfX5+f5N+BH9+fn6LfwGBkYOGgoeDhoQKg4ODhIODg4SEhAICBABxl+eps8PAzubr5Ofr9vuBiIqOko2Snp6XqKqmsrOxt8fQ1tzd4+jr6u31+fv/goSAg4qNkJHyiIaGgPbl0sfAtaaem5ONiomB8+jczb2xqqCalI+MiIaFg/vx46vVz8rJx8bKycfEwcC/vbq1r62sra2Es4DA09zn5OPUuJ2RiPnxgODQxL23s66opqWrqqWipKKakIyLiIWFg4CHmpiLjIX/hYH9+enf29fRzMnErpCln6umoJ2Zlp6u8+DSq+rdkKeln5yyp73HuK64s6GcopycutPR0M2MqtDywrywr6+8tLvFsLi919DU3+7+sLCtr6+wqnelo6GdmpWSj42NjIuMi4qJiIaEhISFhYaDgYGAgP78/v37+/7+gIGAgoGBh4mGgPv69PHw8e/v7O3t7Ovp6Ojp6+7u7+/w9PHw7/Hv7evs7PDy8e7t6ujo6eno5+jq6eXi4uPi4uHg3t7d3t3d3uDi4+Th39/i4YTgO+Lm5ebl5+nq6uvt7e3v8fLy8/b19Pb29ff5+fr+/oCBgICBgoKDhYqKiYuLjI6PkpSUlZiYmZuenp6dhJ6AoKCgoqKioJ+enJucnJqZmZaVlJWUk5OSkZOTkpOWmJybmpiWlpSSkZOTlJORjoiGhoaFg4KEhoqSlJCOlZWTko+Pj5GSl5mYmJ6fn5+enpmYl5eYmJiZmZqcm5yfoaako6ipqK2qp6akn52doKKioKOlqa6vsbKyqbG7vbezs7UbtrW1trq+w8vNysnKzM7MyMO8u7evrKyqqKejhKVdop6enpuam5ycm5mWlZaTlJOTkpOSk5OTlJWTlJebnJyeoKGnp6mrqqysqaeqqKalpaeoq6ysrK+ytb29wMTM09rh53XodOTe3NvSzs3LxsPAvb6+vsDDyMzV3+v3hn2Afn59fXx9fHx8enl4eXl5enp5e3p5eHd2dnV2dnV2dnV1dHV1dHV1dHV3d3l7enl5eXp2d3h4e3+BhIWCgHx6d3h+g4eNmZiWj4mFgH5/gn53cnR2eXp5enp5enZ0cd/f4XFxcnR2d3Z2d3d6eXZ0dHTp5+bl5ujt7u95en2Ag4c0ioqMjY6QkZKTlpiboqKjpKOkpqelpaalpqenqamqrLC0tLSzs7S1tLOzsLCwr7Cvr6+urYSugLC0uL3Aw8XFx8nMzc/S1NDOzcvOzsrGw8HCxMTFx8fJy8/R0tHU1tjc4OXp7vH09PPy9vmq19rx4+ntvYzjyva1po7U6IHv5d7PxMfQ9qa/5pKkzuvUsLrM+/Xh4Iejpqmrsaikud7K9YKJkI+SkZKQjoqJhoSB//bt7evz9/uAF4WFh4+aoqauwdTyh4X29uzv7+fkgIaBgHCufIGKjZSfpKCgo6qsWFxeX2FhY2lqZnB0c3l7fYGOlpqdn6OnqKepr7KxtV1cWVtgZGVmqV9dXVmqnpGHhHtva2diXlxbVqGYkIZ7cm5oY2BeXFpZV1aooJl0j4yJh4eKjY2KiYmIiIeFgX99fX5+gYKDhY6coquoopWBbmVdcKuqWqCTiYaDgH15eHl+f3p4fHlvaWVmZWNkZGJqfn1xcmvMa2jGxLeuq6ain52YhW9+fISAfHl2c3yKx7u0h8OIcZWSj42llK+pqqCkqJaQlY2Noq+urapxiYe7qaOVmpyqpqqyn6qtycTHzeHmmpeElhSTkI2JhoSCgH5+fXx8e3p6eXp5eIR5gHh5eHd3d+zs7uzr7fDzfHx7fHx+goWDfvbv6+rn6unp6ejl5eXk5eXn6evt7u7t8O3s7O7u8O3s6+3u7erq5+bm5ePi4eHh393b2djZ19jY2NbU1dfY2Nzd3dvZ19fY2NnZ29ze4+Pk4+Pj5OTm5ufp6+3t7+7s7uzr6+zu7u3ugPLzeXp7fHx9fX5/goGDhIWFhYeJjI2Ljo+RkpSUlZSVl5eXmZmam5ubnJ2dm5qZmpiVlZSTkpKPjo+OjpCQkZSVmJ2cmpeUlJOSkZKSkZCLiYaEg4GCgoCCg4eMkI2PlJOPjYqLjZCTlZaWlpydnJybmZOTkpOTkpOVlJWYl5maUJqenZydnqClop6ZlZKSkpOVlZOWmZ2kpamop56msK6moJ2bnJydnaCjp62urKussLGxsK2mpqagnp+enZyZmpqbmZWTk5KRj4+Pjo+Pjo2MhIttjIyNjo+PkJKUlZebnqCipKaqq6yvr7Cwrq2trKqqq62srq6ws7W4vsjKy9HZ5+/3/YH/gfjy6+be29nV0M3LyMfFxsjKzdXb5fH+gYKChISFhYWGh4aGhYSEg4KBgoGBgYOEhIWFhIOCgYKChYSHR4aGiYiIiomIio+SlpmYl5SSkY2MjY6RlZmcnJiVko+OkJaaoKWurKuln5yZlZWWk42Ki4yOjoyMjYqKiISA/v39gYKGiIqNhY6AiYOCgID//Pv6+fn6/f2AgYKDhYeIiouNj5CRkZKSlpmen6Cjo6Kkp6enqKioqaqsrq6xtLa5ubm4ubq7u7q6urm6u7y9vb2+v8C/wcPGx8nNzs/Q0dLT1dfZ2dfQ0tTV1NDMysnJycvMz87P0NHT1tna293e4eTm6evt7u/w8vM5pdDR7+To7LR/3sv9t6OF0umE7OLby7/Ay/SbsOKUlsTnyKW3xfbn0895h4mLjJCIjJi9q9x1en5+hIImgX5+fHt47+nj5OPn6u96fXx+hI2SlJ2rus9zcNDPx8rHv75maGVSNlc5PD0/Pz9AQkBBQUMhIiIiIyMiIyUkJSkqKi0uMjY6Ozw9Pj9BPz9BQkBBISEgICIjJCQ8ICAgHzo3Mi8tKiYkIyEgHx4dNzUyLyspKCYlJIQiDyMiREJAMz49PDs7Oz0+P4U+gD09PDw8PT08PT5AREpNUE1JQTkxLShMTipMRkNCQkA/Pj4/QkNAQEJAOjc3ODg4Ojk5P0xLRUVBfUJAeHZuaGdmZGJfXVBFTE1RUE9NTElSYZSOkGKSO01/enl6hXSQf42JiJOEgX98fH2Dg4KAVWVDhoqHeIGFkpCRlI2Yl6+vG66tzMB/fHp8fX95dXNxbW1ramlpampra2praoRpBWpsbm5thGuA2Nna2tvd4eV1dHJ0dXd9fnx15OHf3dzj4uDc3uDf393f4OLk5efp6Ort6+nm6err6enq6unm4+Pl4eDg39/e3Nzc2tXU1dbV09PR0dLU1dfY3Nzb1tPR0NTT1dfb2t3j4d/f3+Dh4uPk5OXs7ezr6Obl5OXl5ebm5efs63R1dXYXdnh5eXp9fH19fn1+gIKFhYaIiImLjI2EjkuPkJOTlJaWmJeXl5SVlZeVk5KQjo6OjYuLioqLjIyPkZKWlJOSj46Ni4mLi4uIh4N/fn59fX18fH6DioyHiI6MiIeGhYaKjI+QkJCElEiSkIyMiouNi4qNjIyOjpGSkpeVkpSVl5mWkY2HhIOEh4iJhoiNj5SVl5OTj5acmZCIhYGAf4CChYaHiYeHi4yPkpWUlZOTlJKFkISOBY2Jh4WEhoNvgoKDg4GBgIGBg4SFhomMjo+SlZmbnJ+hoqmoqqyqqqqpp6qopqSlp6Wqqqqrr7O3v8HEydLb4OTuevF45+La1c/NysfFwb++vru8vb7CxcvP19xvcHFycnNzc3R1dnh2dHRzc3FwcHBxcnR3eXl4hHdieX1/goSBfn+Dg4OIh4WHjpSYnZ2ZlZCNioqKi46SlZaWkpCNjYuMkJaan6Kjop6ZmZeWlZSQjYyLjo2LiIeJhoSCgX318/V8f4SHiY2Njo+PjIN8e3Z36eTi4ODe3d3ebm6EbyJwcXJyc3J0dHN0dXd6eXp7fH+AgYGCgoSEhIWGh4iKjI2PhJAVkZKTlJWXmJianJudn6Gjpaaoqamoh6qAq6ytrqyrqaqsq6mopqenpKOjo6Wmpqinq6yur7CvsLCxsLCxs7S0tbW0ea601cvT2Z1nw6zjooluxNF33dTOuauuu+aCibyJfKXPsYeWquPRu7FgYWJjY2RcYmaDcpFMTk9QUlNSUlNUVFRTU6impaamqKqsV1lZWVpeYWJma3ANdj4+eHd1dHFwbDY2NQKEg4yEn4WIhgGFhIaOhZCEqIMDgoKDn4IDgYKClIECgH+FfgKAf5F+AX+FgIJ/kn4Bf6SAiH+KgOp//4D/gIWAA4GAgZeA5IGDgJCBiYDzgZB+AX+LfgF/i36LfwGBjoOIgoyDgoSHg4OEAgIEAICN6qCvwce/xMPFu7y+x9LW1NjY5vDm6uzn8fyIjJSksbjCz9be6veAhIaKj5SXnaGkqaurq5CioKSgmY6E9+vbzca+tq+tpZiOhPbo3NPLyMK8ubKtqaikm5KJzf3x5+Th4+Pf393a3NnRy8bBvbi1tbrAyMzZ4u/6g4X/gP/74IDcx6aMhf/y7+HMvLzHv7qyqaeorqOak5CKhYL69fb5/oOGg/iFgfXn493W0MzJysbBu6mpraijn87mkP7k49rtw7u+wrq4rKrqvszU196tr8Gxu8Tb2NXUwPrD5M7btr6mraS4zpWfm8WrobzI+amprK+wr7Gwq6WhnZqXlZORkTWOjY2Mi4qIhoaFhISFg4OCgICAgYD/gP7//4GDgoWEgYKD/vz69vXy8/Hv7+7t7evp5+jn6IXpgOvs6+3u8O/w7+7x8vHw7O3u6unp5+Xl5ubm4+Di4+Dg3tzc29ze3d3c4OPk5eLh4eHg4eDi4uXk5Obl4uTp6Ojs8PDz8O/w9Pf19vj29fb5+/n7/YGBgICChIaGh4qLjI2PkJKVlZeYmpubnZ2enp+fn6OjoqOjoqGgn56fnp+dJJ2cm5qal5eVlpSTk5KSkI+QkZKTlpaVk5OVkpCOjo2NjIuJh4SGgIeJh4iKjIyLjZGWnKKlpKWmpaiko6SlpqeprrCys7Kyr7GxsLO2uLi7vb7Bwr68uLStrrGwrKuoq6qlpaShoqWorKyxtK+wtba3urGnpKWnpqqur7KzuL3DyNLb1s3JxcfAu7Syra2vrqqop6Ohn56foKGenp+gnp2enqCdm5qaPZycnJucnZycn5+fnp+ho6Wnq6+xtba1tr3Dvry2s7KxsrS0tba4ub7Fy9fb3OHte36Cg4KBfHp58u7p49+F3Ana3uPp7fP19PWEfA19fX18fHt8fHt6eXl5hHgMeXl4d3l6eHZ0dHRzhHSAdXZ3d3d4eXh4eXh3d3d4d3Z2eHt9gIOEgX98ent7gIePlY+SlpWQioWEfnl2dnZ5fX17foKEhYF/fHl0c3R0dHNzdXiEr6+KeHuZnIqDdnTp6Ojq6u7w8fT3e/l/f4CCg4WFh4aIjpGVmp6jpaSsra6prq6trq6trKusrKyvsbGAsbCysrCxsa+uraytra+urq+wsrKxsLGyusDCwb6+wMLDw7+/wsfP0MzJxsjGw8PFxsfJy83Q0tfa4OLl5+vx8vT29vr9/YGCgYGCseSy37/V2LWs+9rxysq8ysXh75mNydrKvPC44+SGwt3m297QuNPDusaGoqOlp6yqq7jBwIIuh5CQjo2QiIH6+fr/gIGB/4CDhIiIio2YrMPigILFnJrPxb2v18i+1OD0ho6GgoBrsneCjpOPk5OUjY+Rl56gn6OkrLKrq62psbtmam97g4iQmJyiqbJbXV9gY2ZpbXBxdHZ4dmRxcHFwa2JaqKCVjIaBe3Z1b2VeV6KZkYqFgn98eXRyb29sZmFci6qjnZqZn6CfnZuYmpqVkY2Jh4WEhIaMlJihqbK8YGC1W7Wxm4CXhXBgXLCqqJyQhomQioSAeXh8gHZwa2llY2K8ur3Ax2lsacdsaMO2ta2loqCdnZmXk4OChoJ+epq1dde/wrjKeYymqKOmmpbjrr/HxdCeobOfqqq3tbKyoM+AsbTEnamWnpequYOPjLGgj6eq2JOUlZaYmJmZk46Lh4WDgoB+fgd8fHx7e3t6hXkZenl5eXh4eHd47njv8PN8fn+Afn5+f/f08YTtWevq5+Xk4+Hi4ePk4+Xm5ebn6Ojo6+nr6+3t7O3r7e7t6ufm5OPi4ODf3t3b2trZ19bW1NXU1NbX2Nnb3NvZ19fX2djZ19ve4OHi4uLh4eDg4uLm5+rs7e7thOyA7ezr6uvr7fF6eXp7e31+f4CCg4SHiYiIi4uNjY6RkZOSk5OUlZaampqcnZuamZiZmZiZmZiYl5aVlJORkI6Oj4+Pjo2OkJKUlpWQkJCRjYyLiomJiYeGg4OCgYKDhYOFhoiIh4mNk5ufoKCioqOkn56foKOlp6mqq6usrausrKuArrCxsLG0tre4tLKwqqOipKKhoJ2fnpmZmJWXnKGjo6eopqiur66wppaTk5GUl5mbmpmcoaaqsre2r62sr6yoo6GenZ2cm5qYmJWSk5KTlJOUlpiXlpWUlJSTkpKTk5WVlpeYmZqbnaGio6WprK+ytbq5u7zCx8TEvLm5tra2t7dEur/Axs/W6O7v8/6DhYmMi4eCgID//fj07+jo5uPj5ent8/f7/v7+gIKDg4SEhYWFhIWGhYOCgoKBgYCAgYGChIaJiIWGgICBgoWIioyOkpKRkpWUkY2KiomJiYqPlZmbmZWRjYyNkJWdo6ahpaekoJyYlpGOjYyMjI2NjIyRk5OQjYqIhISDg4SGiImLlLSymYqRpqGVjoGA//37+vn6+/39/4D+gIKCg4OEhIaGh4yNkJWZnqKfqKurqa2sra+vrq2trq+xsgW1t7e4uYW4J7m6u7u9vb2+wL/BwMLBw8TJzc/My8zOz9DPzc3P09bX09PR0c7NzITLe8zP0NHV1dja4OHk6Ovr7e3v8PJ6e3p6eqfdpdfC2NGpoPjZ8s7LusjC3euPeLzPv7XtrdvjiLjY4dLWyq/Itay7eIaHiYqMjIuWnp1zeH6Af4CAfHfq6Ojsd3h58nl6fH5/gIKLlqa6aGmqj429pKGasKikr7fAZ2xpZic1Vjs+QUJCRkZGREVGSElKSUpMTU5NTE1NT1IuMDE0Nzk7PT8/QUOEIoAjIyQmJiYoKCkoIiYlJyYkIB45NjIvLSspJyclIiAeODUzMDAuLi0sKiopKyooJSQ6RURDQ0JDRUVGRENEREJCQUFAQD8/QUNHSk5TV1ksK08nS0Y9PjIsKChOS0xIREFFSENBPz0+QEI9OTg4Nzc3a2tvdHlAQUB4Qj53cG5qZVZiYmFhXVtXUFJTUE9NTW5MmI2XiZk0XIWCgoJ7d8iZqLOnuImJm4iPhImGhIR5mUF+lqN9i3yHjJKcd3t4lY58iZWzdXh7e319fn13cnBubGtqamlpa4Vqamlqamlra2pra2xrbGxtbNht3N7idHV1dnZ1dXXl5OTh4d7g3t7d2tna2drY2tvd3+Dg4eHi4+Pk4+Xn6+rp6unn5+Ti4d/e3d7b3NvZ2dbT09XU0tDP0NDR1NTV1NbZ1tTS0tPV1NTY2dqE3AHdhNsE3ODj44TlDefm4+Tk5OXk4N/g4uOEdCF1d3l4eHt8fH2AgIGChIOEhoiIiYiIiYqKjJGSkZOUk5KFkwKUlYSUgJKPjYyMi4mKi4qIiYuNjo6Qj4uJioyJhoSDg4KBgH59fHx7e31/fX2Ag4KBhIiNkZOWlpeXmJqWlpWYlpeZmp2dnZydnJ2dnp6fn5+io6KkpaOioZ+WlZaTkpOSlZKMioqJiY2SlpidnZqcoKGgn5aFgX58fn+AgYB9fX+ChYqOEo+PjpGWk5GQjIqMjoyKiouIiISFgIeFhomKiomJiImJiIWFh4iJioyNjpKVl5ianZ+hpaaqrrGztLS1ur+7uLOxsrCvr66wsrS2vcTN3ODh4up3d3h7endzcHHj4NvY1tPU0czNztDS1tnc3NrbbnBwcXJyc3JzdHV2dHNycnJxcXBwcXN0d3t/fXp1dHV1d3h4eXyAgISHi5GSj5GTlJGHg4SCg4OFiY+UlZKLiIWDhYmOk5aWlJialZSQj5CMiouKiYmEg4WChoaEg4KAf35/f3+BhYeIiYqUlpCKj46Kg391debj393b29va29xt2Gxra2xrbW1sbWxub3Bzdnh6eX2AgX+BgoSEhYaHh4iIiYyNjY6QgJKTkpOUlpaYmZycnZ+goaKjo6KlpqeoqKqnqKqqq62urausra2tqquqqaelpKOioJ+enp+goqOjpaepqqurrK2urrCwV1hXVlZ2u4m+qcO6iYHmweC/vKyzrczefWWrtKGd4ZO2xoKkxs+7v7OYrJuRn19gYmFhYmJfZWlqSUxOLE5PT1FQT56hoaNSU1OnU1RUVVVWWFteY2s3OGZeXnxfYmJkZWVlZ2k0NTU1AoSDmYSMhZaGjYWRhJ6DBISEg4SIg5aChYEGgoKCgYKCk4GCf4V+AoB/kX4Bf4WAgn+SfgF/p4AFf4B/f3+IgOx//4D/gIWAiYGTgPWBioACgYDqgYWCAYGcfgF/i36LfwGCiIOEggSDg4OCi4MGhISDg4OCiYOEhAICBACAgcyVpqiYnKSdlZmfpqqtsre8vsPK0NPRzMrP1+Dp8oGKlaOzxNLh6veDipKapauvtb3GyMajw8bKysW8sqeXhoD27uXf1cm7q5uPhIH99evj5OPi3djQxbatpeuUj4yLi4mGgoKB+v74693PysbLztTb7f+HhYSMmKClrLCwp5yAkIeF+Niwi4yD+enX38zn9uvXxb6yrqqglYuGg//8+PmGgevl49vY1dDQysjKxcK+ubiymqyqpaGIloLv28qg2uCTpZ+am7Kss6erpquspZmbo6aw2Nza25Sw1Oq/vK6yqrq7w8e3ua/H0s3e2/SoqKissLG3s62qqaeknZmWk5EJkI2NjIuJiYeGhYSAhYOAgIGAgYGCgYGAgYGChIaEgoGA/vz7+/j18u/z8O7s7Ovp6eXm5ujp6uzt7e3u7+7x9PTz8fDv7evs6+nn6Obk5ebl5uLf397d3Nze3dzd3uDf4eLi4uHi4uHi4uTk5uPj4uLm5eXk5+bm6err7u/v8PHw8/f49/j5+P3+/YCAgYKBgoSGiYmKjIyMj5CSlJWXmpmbn56fn6GhoqOio6SkpKOjoqKhoaGfnpybm5uZmJWVlJOSkpKRkJCPkJCQj4+Qj4+Qj42MjI6Ni4uMi42MjI2Li4qKjpOVlpiZnaKmqaipqaiqqaeio6aorK6urrG1uLm4tbGwsLKysrOzsK6ArauqqKqsq6qpqKissK6tqq6srKuqrqysqaajo6Slp6Wem5manZ+hoKKipKWoqrPAyM3O0crIx8TBwcC9vLm1sK2sqauop6alo6SipKanpqSioqGgoaOkpqqsrKqoqaekpqWlpaepqqytrq6xs7a3uLq8wcbHztLW2d3h4uHn4OIX4+59g4uOkZKSlZeYkYWCf3x9f3x8fHuFfAh9fHx8e3p6eoV7AXqGeR54eHh5eXh5eXp4dnNzc+Tk5ud0dXZ3eXl6enl4d3iGd4B5fHx+gH18fXx9foGGi4yLi42PjYiGh4eCgXx5eHl3dnV2eHx9fX58enl4dHNzc3Jyc+Xn6Xh56+h3fHl6eOvr73nv8PN8fX+BgYKFiImLjY+SlpqdnqKnqaiqq7Cwsbe3u766tri5tre3t7WxsbGzs7OxrqqpqamoqamrrLGur4C2vby3tLO1v8vT2tfU0czFv7u7vL2+wMLDxMXHyMjIzM7Q09ba5erv9/6Bg4aHioqLi4qMjpGRk5WXlszQwPTY3OPfxdLO6tKT6c/M4uWjqNTb3tHw4+bO2rfG5dOSkqiB2LOghKGipaSonJu3usvy+fuA+/n/goOFh4aGiIuMkB6Um52Yl52rvMvfg5GH2LOzl6ChsrW1xszM4uLi2vdeZKV0gIJ4e4F9d3l8gYOGiIuQkZOXm5+cnJmcoKattF9lbnaCjpSepLBcYmZsc3d6foOKjIpxiYmMjouDe3RpXFiqpJ6blIuAdWpgWFappJyXmJiXlJCLgnlzbZ9iYIRegF1aWlmws62nnZSQj5KUlpytuWJiYmhvdXqAg4J8cWlgX62Vdl9mXrGjm6SUqa2lmo+Kg4B7dGtmY2LAwL3AaWa2sLCqqKShoZ2cnpqXlpKQinmHhYB9WHRs08Syg7eLc5SLiIuhmaOXnZqeopyPjpWYnLi5ubZ5jIi1paGUoZmnO6q1tKCnnbbEvMvB05CSk5WZnaCdmJWTkIyHhIKAfnx7enp7enl3eHh3eHl5enl3d3h2d3d5e3p5eXx9hH5efHv19PPx7+3r6Ofk5OPh4ODg3uDg4ubo6ejp6urr7O3t7/Lv7Orr6Obk4+Hg4eDd3Nze3NnY1tbV1NXV1dbY2drc2tjV1dfY2dzc3d7f3d3d3Nzd4eHg4OHh4eTm6YTsJOvr7e7t7+3v7/F5enp6fH5/f4CBgYOEhoiIiYqMjY2PkpKTlISWH5eZmZucnJybm5qZmZeYl5eVlZWUkpGQkJCPjo6NjIuGjAOLioqEiIWJf4iKiIiJh4eIh4uPk5SUlpifoqSkpaWkpaWknp+ipKaoqqytrrCxsK2pp6ioqKmop6SloqCenZ6en52bnJ2fo6GhoKGen6Cho6GgnZqYmJaYnJePjYuKjY+RkZKQkJCSlZukqaysra6uraqrraqmo6CfnZubm5qZmZeXl5manZ6EnUmbmpmam52foKCgoaCgoqKioaKlpqeoqKqtr7K2ubm7vsLIzdDW2tzg5Ovu8fXw7vH7hIiNkI+QkJGTlZOOjImHiYuHhYaFhISEhINdhIOBgICBgoKCg4KBgoOCgoODgoKBgoKDhIeJh4SBgYD/+/v9gIKEh4uPkZGQj4uKiYeIiYmJi5CSk5SRj46Nj5GUmZ2fnZycnZyYlZmZlJGNjIuJh4SDg4eKi4uMhIqAiYiGhIGCgvz6/oGB//2BhIKCgP37/4D//v+BgoKCg4WGh4qNj5GSlZqcnaCkpaSlp6ytrLK0uLq2tLa5ubm4uLi3t7i6u729vLu6u7y9vb2+v729v8LHxsPDwsfN1dzf3drZ19PQzs7Nzs7P0NDP0dHQ0M/P0NHT1djd4OTo73gEe3x8foSAa4GChIWGh4mIusOy8Nzd3tnBzc/r0IrXzsbb4ZeYytXbzOzh5MvSsL7izYeOpoLPpZF3hYaIiIqCf5SXotri53Xp5+x3eHp7e3t8fn+DhYiKiYmNlKGrt2dybLWem4OPj5qenqiqqre5ubXDgDVWOz5APkBCQUBAQEFCQ0NERkdISUpLS0tKTE9QUlMrLC8yNTc6PD1AISIkJScpKistLzAvJi0uLzAvLCkmIh8dOTc2NDIvLCgkIR8ePTo5Njc4ODc3NDEuLCs/JiYlJiYnJiYmJUpNTElHQ0JCQURER0xRKywsLzIzNDc3NjQvgCwnJkY6LigvKlBKSlJKVFFMSUVFQ0I9Ojg3NjVqbW5xPjxpaGplZWNhYmFhYl5dXFpaV05UU1FPJ0lMnZmPYY05T4B1dHWGfYZ7hIWKjYqBgIOGeYeJiolaaEN/h4V3hH+PmKCai4uDmq2jrauxcnR2eHyAg4B8eXd0cW5raWloamhnZ2doZ2hnZ2ZoaWpqa2ppampqbGxvb25ubnFzdHd3dXN05uXl4t/f3drb2tnW2NjV1dbY2Nvd4OLh4ODh4+Lk6Onp5uTj49/e3NzZ2drY2NbW2dbS0dHOz87R0tPU1NXV1NHPz9DS1deE2SXb2djW1dfa29jZ2trZ2tve4uPj4+Dh4uTj5ubg4eHhcXR1dXV3hnoWe3x+gICAgoKFh4iIiomKi4qLjI6QkYSSDZSTkZGRkpKRkJCQjYuFioCJiIiIh4eIiYiHhoWEhIOCgYCBgX+AgICBgIGBf4B/gIKFiIqNjZCTlZiYmZmXmJmYlJWXl5iZmpydnp2cnJyYlZeXl5iYmJeWlJGQkZGQkY6MjI6Rk5OSkJKRkZKVl5SUj42LiouMjYiAfnx8fX1/f35/fnt5eXyAg4aIjI6PkRqQkZKOjIuLiYiHhoWGh4aHh4iMjY6PkJKRj4SNWZCRkJOSk5SUlJWWl5iam5ycnqCgo6Woq6+xsbO3u7/Bx8jJztHZ2tvh29rb43R0eHd2dnZ3dnd4d3d2dnl7dXRzc3Jxb29wcG9vcG9ubm9ubm9wcHFwcHFxh3KAdHZ7fX16d3Z05+Xj5HN2enyCiIuLi4qFgn9+f4CAgISJi42LiYSCgoSGh4yRko+Nio+Pi4mOj4mGhoiHhIB+eXl8f4CAgYGChYaJh4R/e35+6eTkcnPk43JzcnNx4eDhcd/d3W9vbm1sbW9ub3FycnJ0dnd3eXt8fHx9f39/goQWhoeHhoiJi4uLjY2Mj5GSkpaZmZqcnoSgJqGgnZ+fnZ2foKKkpqeqqqyrrK2tq62ur66sq6qtq6qqqaelpKOihKAKoaGipaZTVVZWVoRXMlhYWFlaWltafJ2R38PIy8auvMDdvHCuvaq81YWEusPKuuLV1729na/VtXJ2jHq7iHZehWE0WFhkZGqSl5ZMmZmbTk5PT1BQUlNVVVVWVldXWVtdXmE0NjdmXl1TXF5gYGFjYmNlZWZoawKEg52EioWYhoyFjoQBg4qEjoOPhIaDk4KEgYKCl4GCf4V+AoB/kX4Bf4WAgn+SfgF/tYDqf/+A/4CFgLyBhIDEgQeAgICBgYCAhYEHgICAgYCAgOWBkYIBgY1+AX2WfgR/fn5+i38HgYKCg4KCgpSDg4SRgwICBACAy6bc54qRiP2DjZOir7+8v8nb2dnl6uTW2Nrr/YiMhouWnKWts7y9uLvCz9/r+oCChIiNkZHwlZyipamrsbWroZ6SjZCJ/ungyLmtp6KfmpKMlpWJgv/y39TOyZTDwL23s62rpaGflY2Ggfr6/4SJio+Sj4+Xo6SfoaOhn5qTjY2Agvr88+HOu723tKSboKe2nPnbysLMybmqnZialIyIhoKA9e3m49/X083Jvbu9vru8vLmzna+tqKP6iqOklOfU4KuZsbGutbi72sjPv8rBtLSt1+HEw9Da2sSAw+DTxLClrq6oucn2x9LY08S88O+rq6uqrLCwrquoqKajoqCcmJVek5GQj42NjYuJh4aFhISDg4KBgYCAgYGBgoOCgPz+goGCg4H////++vbz8vLw7uzr7Ovo6Ons7Ozv8PP09fX08vL18/Hw8O3s7ezs6uno5ufo5ePh3Nzf3t3e3d3c3ITeht+A4eDi4ODf4OHh4OLk5eXj5OTn5+vu7/Dv7+7s8fX39vn5+fr8/f+BgoKDhYaJi4yOjo6RkpSWl5ibnJ6fn6ChpKKjoqSlpqWko6SjoqChoaCfnJycm5mZl5WVl5mampmbmpeXk5KRkpKQkZGRkJSWmZuam5qbmpufoaGjpqmsq60NrqqmpaWnpqWoqKiqqISlgKanqKiprbGzsrKxq6mnqKuqqamppaOioJ6goaKho6KipKanp6epqaqpq6ysq6enpaWnpqWlo6Ohn56foaSop6impaSnqKits73BxMXEwsHDxcW/vby6uLi2sK+trKimpKKjo6GhoqKkpKanqquwsrW5vLu4tre5uLi1tLi7vsDGOcPFxsvQzM7S2ePrfIiOk5iWmpqUkI+LiouMjY6LjJCUmp+Xj4qCgoCAgoB/fXt7fHt8fHx7enp5eIR5JHp5eHh3d3d4d3d4d3h4eXp5d3Vz5OTj5efodHR3enl3dnV0dYZ2Jnh5enl5eHd4e3+Bg4SFh4R/f35/gH5+fH17eXd2dXV0dHR1dXR1hXaAdXNzdHR0cuPh4uTp7u7p6ezu6unp6ezz+/t9f4KFh4iMj5GUmJyio6arsra9wcLExcfJzMnDxMPAvby5t7WysrGxr66wsa+zsrCsqaipqq6xr7Cxtba4vMDCxsbGy8/T2N3j5OXi39vT087M0tPT0tTU2uTn5uns7vD0/IOKjpF1lJaZmJianJ6foKOkpaWpq6ys6urX1cHU2pnk5sbbuqGe29Dc5t/Wx76/zfCgh6WIk7nQyKuztPLXw63+m5+ko6aYirawpuPl5uXo9f779PP47PeAgYCCgoKHiYmYpLPG4YSTlfDDp42htbOrqqu2y9PN2OHfgKqNt8Fsc2zMaG1xeoCIh4eMmJeYoKSkn6KirLdgYl9iaGtxd3uBhIKDiJGco61ZW1xfYmVlpmdscnR2d3t+dnFuZmFjX7CgmYl9dHBta2liXmdnXVmvpJSNioVkg4F+fHp3dnBtbGZhXVmsrbNcX2FkZ2ZmbHZ3dnZ4d3d1b2tpbmK+vKyakIGBgHpxcHR5fmmsmZCMlpCEeXJvcWxnZGJhYLezraupop+cmpKSk5ORk5OSjHqMioaBp2mBlIzOsr9ve5yblp6hqcy7wbG7s6mmm73TqqOttrWkaYCuwriXmKSgn62597zKy8u6qufQhJJolZualpSSkpKQjIiHhIJ+fnx8e3t8e3p5eHh5eHd3dnZ3d3h4eXp5enp48PR8ent8e/b29PPw7Ono5uXj4uLi4+Lj4eLk5ujp7O/x7+/v7u7t7Orp6Ofn5eXk4d/g397d3Nra2NjX19eE2IDX19nX1tXT1NXV19jc3Nna2tjY2Nna3d/g4d/h4eHj4+bm5+no6evs7Ozt7u7v8fN6ent8fX+Bg4WEhYeIiouNjY+Sk5SVmJmam5ubnJ2cnJuampqbnJqamZeYmJeWlZWTkpGRkpWXmZmXlJaUkZGPjo6NjI2OjZCSlZaVl5eXlhKYm5ydoqOlp6ampqSgnp6foaOEpIChoKCfn5+hpKanqKqsq6qppKKhoaOioKCgnJiVlZWUk5OUlJSXl5eYmJmZmZycnqCfnZqYl5aXlpeXlJKQj46Pk5WYl5aWlZSVmZWWm5+mqqusqqytrKumpaalpaSioJ6cnJuamZiYmZmampudn5+ipKWprbG2ura1tba6u7m1tYC4uLy+xMPGyc3Sz9LY3ev1hI+Slpmco6mnop6ZmJuampqUkpSanqCem5mQj42Ojo2LiIaEhISFhISDgYKBgICAgYCBgYGAgICBgYCAgoKCg4aIhoSCgP36+Pv9/oCBhYmJh4eGhIOFhoaGh4iIio2Mi4qHh4qQlJiYmJeUjo2LjmKPjI+OkI2JhoWDg4OCgYSEh4mKjI2OjIqGh4eJhYD89fPz9/v5+Pj6+/r5+fj5+/7/gYSHiomKj5KTlpmcn6Gjp66wt7u9vb7AwsTBvb6/v727uri3tre6urq7vL7AwsPBv4S9gLq3uLm7vL3BxMbIzMnM0NPU2Nri4uXi4uDd3NnZ29nY2drb3uPk4uLk5ufp73t+gIKEhoiKiYqLjY2PkZGTlJaXmJnT4M7RwdLWj9Hj0eGylJba0Nzl4NfGvL3L75qApIaKss3Boriy88+6n+WChIeHiX90kpGK0dfY2Nvk6efkJuPl3+R1dnZ3d3d6fH2Hj5mmumh0d8anlH+QnZyYl5ieqq2rsLe3gGVSam08PTpwODo6Ozs9PDw8P0BAQ0VHSUtLTU4oKCcnKCorLC4vMjIzNDY5Oz0gICEhIyQjOyQmJygpKCoqKCUlIyEhIDw3NC4pJyYlJSQiISUlIiE/PDY0MjEmMDEwLy8uLiwsKygnJSRISUkmJycoKiorLTEyMjM0NDUzMS4vgC1YU0c9OzU2NDAuMjU3NSpKRURGTUU/Ozo6Ojc2NjU0NGViYGFgXFxbWlVXWFhZWltbWE5YV1VRTEBTaW2hiJYzV395dHx/iaiWnZOcmpKNhZatgHiAhoZ3TT56o6F7f42GipGX66q9rLShi9CqcXJzdHh9fnl3dnZ2dXNvbWtqAmlphGg/aWhnZmdpaWhoaGlpaWprbG5ubm9tbNzgc3FydHPk5eTf3d3a2tja2dXW2NfV19nZ2dzg4uPl5uTk5OXn5uPghN4D3NrahNdn1tTU08/Q09HQ0NHR0tPS09DNzczMzs/R1dbY19XS0dDR09XW1tbX2dja2NjZ297c3d7d3uDi4+Xh4N/g4uNydHV1dnl6e3x7e3x/gYKEg4WHiouLjY+QkI+PkZWTkpGQkZGRk5KRkYWQCI+OjYyLi4yQhJGAj4+NiYmIhoWEhYaGhYeJjI2NjIuMi4yOjo+Rk5aXl5aXlZSTkZGTlJaWlZWTk5KSk5KSlZaVlpmZmZiXlJGRkpORkpOSj42LiIiHh4eFhYSGiImIiImJi4yLjo6PjIiGh4WEg4ODgX+Afnx9gYOGhoSDgH+BgX5/gISFh4qMjo8HkJCPjIuNjISLV4mIiYqLioqLjIuNjo6RlJWXmJqdoaapq6inpqmtrKikpKusrK6xsrS1uMC8v8XK09t1fX5/gYKKlZOPi4aFh4eGhn16fHx+goKEhH16e3t8e3h0cm9vb4ZwgG9ubm1ub3BwcG5vbm9vcHBxcXFzeHt4d3Rx4eDf4OHicnR4fX99fnx5eHp7e3t8fX+AhYaEf3t8f4SJjYyIh4WAfXt9f3+DhoeFgH18e3l6eHl6fIGGh4iMjoyKhYWGh4N769/Z2Nvb2tjZ2tna29jY19TU121wcXJxcHJzdHV2SXh6enp8f4CDhoeIiIiKjIuJioqLjYyLi4yNkZOTlJSXmp6goaGhn6CenZaRk5WVlZiampmcoJ6foqGhoaCmqKqpq6ytr66srKiEqRKqqaqpp6elpKWlU1NUVldXWFmFWkRbXFxcXV5fYGCFv7K6qr7GeLDVuc+hfX3LvcrZ0sy3rbHD5Yprj3x0nL6qjpuX5bylhLhfYGFgYVVQZF5ZjZCSk5WYmoSZBZqbTU9QhFEbUlJVVlldYjU3N2hhXVJcYWBeYGFgYWFiZWZnhIMEhISEg5SEkoWHhgGFj4aQhZWEg4OUhI+DkYKXgQGAhH8Ffn5+gH+RfgF/hoABf5J+AX+ugIJ/hYDrf/+A94DGgYaAwoGTgOKBloIBgYd+AX2UfgF/jH6KfwGBjIKOg4OEkYMCAgQAgLqUxczR3fL+g5GltbuyucLRzMTK3dG+wMbH39zj6/X+jJmeoZ+bnaWprbS2urS6vLu8xMzUtd/h5vORj5mhoKCdkJaOhoCC++LXzc7Vyb+uqaihnZqUkIj/9/K26+Th29bPzMjDvbaxqqmnpKOjpammoKGhn5yYkYj869nQ1NTXgNPPzMS/uK2qrrrCxby5t7CilpOQkI+Igffs4My5sqCSioP69PDm4dnCoIPO5aXahYykp5CYhN+B/5z+9+LFkdTQk6qapJ32mriupJiWk4yJkJ+tw9/S0diTrMjnv7mosLjBwczH177L1fHy6v/2ra+urauqqKain52bm5ybmZaVWJORkZCPj42MioiHhoeHhIKBgYCAgP////yAgoGAg4WEgf//gYOCgP359vTx8PDv7u3r6+vs8PDy9Pb3+fj5+fT29PPx7+7t6uvp6enn5+Xm5eTi4eDi4eGE4BLd2t7c293g397e29vd3d3b3d2E3gHfhOKA4+Xl5enp7O3s7ezr8PP19ff4+f3/gYCAg4SGh4iJi4uOkJKTlJWXmpqam5+ioqKhpKKioqSko6SlpaSkoaKioaGenZycmZiXl5aWl5SVl5iXl5aXlpSVlpWUl5mZnKGioKCjpKanqrC4ury+vby4tLKxsLK0s7OzsrO3tbSysrKAtLa2ubi5uLm4uLi6vLy/v8DAwL64tbGrqKSjo6Cfn56en6CgoaGhoqSjpaanp6ipqaemqainp6ioqqmoqqyxucLGzMvGwr+7s7Cwr7nBwsC6u77Avr2/vry8u7m2trOxsKyvraqoqK2wtba8xMvV2Nvj4+zt8vV9e/Xx8vj2gIM6hoiLjo+MioR9+PT3hIqOkpWbnJ+fnp2enJmamZaUk5WYmZiYl5aNiIaDgYeGhIF+f4B/gIF/fn59e4Z6gnmFeIDvd3d3eHl6eXl47eno5eXn5+h0dXR1c3N0c3N1dnZ1dXZ5e3p5d3Z2d3l7ent9e3p6e3x8fX18enl3dnV2dXR1d3d3dnd2d3d5enl3dnV1dnd3dXTn5uXk5+rs7O7v7/Lz8e/y8vV9gYSGjI+RmJyep6utsLKytLi8v8DCv7/CwoDAv766s62qqaOlpaOkpqqtrq+wtrSzt7W0srCysrW2t7a4ub2/v8DFxMLEy83T2Nba3N3c2tnc3+Lo7/X4/f6Ag4aIi42PifGgq7C0u7W4saumnpubnZ+jqKanqqypquvOp+mtx8+4nOTV5dPOs8XJ9eeLh7Sssp7nn6e0hq7T0ELFwNTX1cfH1/uWmp6ho56dsq2i29fW2d3f4ebn7IuA+PDx9Pb6goaJkqKywL7Es83SycG6mqWlpaersLXFz/vezs+AnoCnrLC1w8tnb3iAhICDh5CPi42Yk4qKjZCfoaaprbRgZ2tubm1tc3V4fX6AfH9+fn+DiI55l5ibpGNia25vb21jZ2FYVFallo+JjJKLg3hzdHBramdiXa+qpn2kn5qYlJCOioSAfXl2dXRycnJ0dXVyc3RzcW5rZLuxp6SjpKVuop+clpCLhoWGjpOWkY6JgXpxb2xsaWNcr6adkIR+cmplYLq2tKynopR7Z62+h69qcYOFc3trtGemddPZyq10s4Bzm4iOSrOJrqSajYyGgX6ElaSouKuttXeKf7KnppCjra+zvbXOr7/I7Orc+9iEkxGSkY+Oi4qKiIeGhYWDgoB/f4R+UHx6eXl4eXl4d3Z2d3d47+/w7Xl6enx+fX179fN8fn178e3q6Ojn5eTk5Obl5efo6evt8PL08vT18vDu7evr6enn5eTi4uDe397e3t3c29vahNmA2NrY1tXV1NPT0tPU09HS1dXW1dTU1tbX19rc3d/g3t/g4OLi4+Tm5urq7e7u7/Hy8nl7fH19fn+Cg4SGiIiKi46PkpKUlZaanJ+hoKKgnp2fnp2dnZycnZybmZiYmJeWlZOSkZGRkpGQkpSXk5OSk5OSkZOSk5SWlpqfn52dn6KApKSmrLW4ur27trGsq6ytrq+vrq+wsrKwra2tr7Kztbe2tbW2tLa3t7i5u7y+wL66s66qo56bmpeSkZKQkpKRkpKTk5STlJSVlpeYmJiXlpaYmJiZmZqampueo6uyub29uba0raSenJyhpKempaWnrKqqq6qqqqiloqKfnp+go6RaoqGjqKqxtbrBz9fa3+Ph6+zz/oWD//f09/yGi4yQmJqbmZGJgf76+YSQmaClq662ube0tLOxsrCrpaOmqKilo6WlnZeSjpCWlJKNiomJi4yMi4mIh4SDgoKChIMbgoGBgYD/gICAgYOGhYSC/vz8+vz9/f2AgoKBhIAhgYSFhoWGh4ySj46KhoWHiouLj46LiomJi4uNjYyMi4mHhIOAhYeJiouNjI6Rk5OSkI6Kh4mIhoKA/vr18vP2+Pj6/v///v79/vz9gIaIiY2NkZmbnqWpqauurq+ytbi6u7m6vb29vLu1r6mmpqGjo6GipqaqrK+0vLq9wr66uLW3tbe6t7q+vsDCwL3CwcDBycnN09DV1dfY1tjc4eLn6+3w8/OAe31+gIGCgn3ajZaZnKCeoZ2YlpKPkJKUlpiXmZmbm5vVyJ3rrcjMspTi1+nXybPFzPnphoKxp6uc55entYenzsy+utXTz8C/yuGAgYSGh4GCkY6Jy9DQ0dPV1dja33t26OTl5uXodnp+go2YoaGllamtqaahiJKTlJWWmZ2mq8EDs6ysPWBOZWdnampqNjc3OTk6Ojs8Ozw9Pj4+QEBCREZISkpKJicoKSoqKiwsLS4tLi0uLSwtLTAxKTMzNDchICOEJYAhIh8bGhozLi0tMDMwLiopKSgnJyUjIUA+PjE+PTw7OTc2NDMxMC8uLi4tLi4vLy0sLzAwMTAuLFZUUVBQT1BPTUpIRURCQkNFRkdEQkA8ODU2NDQxLSpPSkdBPjs4NzUzY2JgX11bVEg+cXtWcERHU1NLTURzQEhGlaKbiVaINYBSiHV4EXd3k5GIfXt2cnB5h5aEhHx+g1dlPXyIiHaMlZWep5rCm6ur1M2/5bJyc3NzcnJxb25tbm1tbWxqaWtraWppaGhnaGdlZmdoaGhnZmdnaGnT1NbUbW9ub3JxcnDf4XN0c3Hd2trY2NfX1dXX2dnZ297e3+Dk5uno6Ofl5W7i4eDe3d3d29nZ19fV09TW1NLR0dLQ0dDPz9HQz87MysrIy8zLycnKzM/NzczLzM3O0M/T1NXY2tbV1tfb3Nvb3Nzf4OPj5uPk5eRycnN2d3Z3enp9fn9/gIOGhomKiomMkJSWmJeYl5SRlJOUlISTBZKRkI+OhI0QjIqLjIuKjIuMjpGNjIyLioSJBYiKi4qOhJIkk5SUk5SYnJ2en52cmZiXl5iYmZqam5ucnJuZmJmam56fn6CghZ0cnp+doKKjpKOgnZmXkZCNi4mFhIOBgYKCg4OBgYSCgIOEhYSDhIOChIOEhIWGhYWGiIuRlpudo6Wlop+ZkIiEg4SFhoeHi4+UkpKUk5CPj4+NjIuLjI+UlZKSlZufoqittLvCxsfLyc7O1eJ3dN/X0dPWdHl5foeJiIZ/dm/c1dFweoWMkZWWn6OinZ6empqalpCLjpGQi4iKjIiAfHh7gICBfXl0dHV2d3h1dnZ1cnBubm5vb3FxcHBwb27bbW5vcHN3dHJx3tzc3N3g4OFyc3NycnR0c3R3eXp6e32DiIaDf3x6en1/gIOCfnt6fH18fn9/gYF/e3h4eXh7f3+BhIiHjJGUk5KPjYiDhIJ/e3bn49vW1dTV1dbY19rb29rYW9XUam5wcHFvcXd3eHt9fn9/f4CBgoOFhoWGiYqMi4qHhIF/f3t9fnx+f4CDhYmNkpCYnJePj46Ni4yPjI+TkZGTkYyPkI6Pk5SWmpmbmZuenqGkqaqrqqqqrKmHVnRSjVhaW1xgYGJhYWJhYGBiYmJjY2NkZGRliqeE35i0uZmA1cPbyLyir7Tl3XR0opeXkdyGj6OClsC+qqe/vbWnqLO3Xl9fYGBdW2NdWImNjo6Qk5SVlZZMTZuZmpmZnE9QUVFTVlhZWVBcXV1eXlJbW1xcXoRfBGFjY2OIg5iEmoWNhpGFoYSfg4qCiIEFgH9/gICHgQSAgYB/hn4GgH9+fn6CjX4Bf4WAgn+SfgF/p4CEf4iAgn+EgOV//4DjgIKBhYCLgYOAu4EBgImBiIDCgZKA2YGIggGBl4IBgZx+AX+Mfop/AYGJgoKDhoKdgwICBACAuZbFy8/V3d7i7/SCm46eo6Khpa+xpa7BsK6wt8LQ5PH8g46FhI6QmZucoaakp6i6v8TI1fPO9+bq8oKFh4+MiYXYzMDFv7Cmn6Kdpq6uqaSinJSQio6QjoiD/LTh2tXP0M3Jx8O7r62rpZ+YlZSQioL+9+7m3NfQ0dTV1dPVy8OAvrCxr6y4wcPKw7m5t6yKgMXM/vbAs6irjPOt2/uknYqYkIqLgOSouqTpyMDR5fTr9oaOgrablbCukL3J3NO837a5r7+pwaSG3tLEwNq4oZ2p1OrV6ejQ38uFxunQ4KuXiarCpcGbkpG6rKGyuvWurKmrq6inpaCcnJqZmpqal5YRlZSTkY6NjIqJiYiHh4aEg4KEgRuCgYGBgoOEg4SFhIOFiYeJiIaE/vv79/X3+PWE9Cj2+vj6+Pn//f36+ff5+PX28vLw6uzs6+3r6enl5eTh4uHj4uDf39/ehd9t3Nzb3NvZ2Nzd2tvb2dna29zc3uDg4eLm5ePn5+rr6+7v7vL09vb4+/z9/f+BgYOFhoaIiYqMjpCSlpiZmpyam5yfoJ6goqSjpKOkoqKho6OjoqCgoJ+enJuamJiYl5WVlZeVlZWUl5mZm5mYmoWbKp6hpqepqqutr7K0trq6u77CycvEvbq4urvAwsC9vLy6uba1tbq8uru3toS4WLq8vr7AwMG/vbu4uLOvqqupqKajo6Sjo6Kho6Wkp6inp6iopqmrra+urKyusLC0uLa1s7K0tbW3u7eysrGyr6+sqaqtsLK1tbS3urm4uLi3t7e1tbi8vL2EwFXIydHe6vWJk46JhYSGh4ODg/z18feAiJCWmZudnqKhnp2ZlZiYmJmYnKOnp6inpqWnp6aorKugnJydnp2enZqVkZKTlpeVkIqHiImKiouLi4aDgH59hHxNe3l4d3ju7O7t7ezu8Hd2du/w8O3s6Ofo6ObmcnRzc3Lkc3Z1dnZ1dXd3eXl3d3h3eHh5eHd3eH2Ag4F/fn99fHp3dXd4eH6Dhoh/e3mEeBd6enh2eHl3derr7e3r6enr7O3t8Hl7e4R6gHt8fX9/gYSKj5OboqitsbKxsrGytLa1tLO1s66uramdmZyfo6WqrbGvr7O1t7m5ubi3tbCtrKusrqyorK+srLCur6uvr66usbK3ur7Cyc7R1tvf6evv9Pf5+fj7/f7hgYPliZiArrO0samkoqKhnJaTkI2Njo6PkI+OkMbUweDRXtjl0avKueLFlufV6JTdl6PAu8iv5Nbm29vE0uXPpKa+8buanviVmJqcpJ2cq7a91N3p9fn//4GBg/nv7e7v8vTz+f738O3j5u7325y20+Tx0evW0r63q4+2xtLAuryAn4Goqq6xtre5wsRldGx2eHh3en+AeoCLgoCChoyVoaerWF9aWl9hZGVnaWtsbm13eXt9hZyCmYyPl1JVVlxbWleLhHyBfnNrZ2lmb3V2cm9ua2ViXmFjY19bsH6fm5aUlJGPjYiDfXt5dHBtampoY1+6ta6ppKCbm5yen6CgmpaAkoeIh4SLlJaalo+PjodtZqGo0c2ilo6MdMmLrsqCdGNtZ2NlX6uAkY3w0bzK4O7k7oKGe7ajoLCkhqa6wrefvnSDoKmNloZu1ci+ttSsj5CZyeO3vr+quapvgLbD35GNf5/Ala+SgYCrpZifqdCRkZCQjo2NioaFhoWEhYSDg4QLg4KBf358fHt6enmGeIB3d3d4eXl6eXt8fHx9f39/gIKEhISBf/Xx8O7t7Ozs6+3t7vH08fLz9vr5+PX08vLw7u/t6uno5+bm5ePi4d/g397e3tzb2trZ19fY1tbW1dLR0tLS0dDQ0dLS09LS09XW19ja3d7e4OHi4uLk5OXn6urt7vLy8vT19fb3fX6AgyqDgoOEhoiKjY+QlZiam5eVmJuenZ6eoJ+fnZ2bnJqamJmYl5aVlZSUk5OFkVSSkZGRkpOSlJiYmpaVmZqWlpiZm56ipaipqautsLO2uLq7vb/AvraysK+zt7m8ure3tbKvra6wtbe2tbKysbS2t7i5ury9vb69uLSyr6ejoKCenJiEl4CVlJWUl5iZmJaWlpiXmJqbnJ2bm5yeoaSmpaSio6WmpquvqKSmp6akoZ6amZucnqGgoaSnqKiop6iop6Sjo6anqq+ytbi+wsvY4u6Fj4qFgX+Bgn16e/f18/eDipWgqqyxtbm5tLKrpqmqrKuosLnAwMC/v7y9vby/w8CzrK2vsxq0srGspqKhoKCinpmTkpSYmZydnZ6XkYyJiIWFf4SCgID//f7+///+/oCAgP79/f3+/v////3/gYGAgID/goeGhoWEhYiNj4+LiIqKi4uKiIaGiI+Tl5KOkJGRkIyIgoSEh4+VmJuVlZOTlJOUlZOQi4qJhYH+/vz6+Pb29/f3+fyAgYGAgYGAgYKBgoCAgYWLjpifpKiqq6ytra2HrlqsraynmpWWmp2hpqmurq+ys7q8vL3Bvb68tbCts7KvrrS5sbCxra2oq6uqqKmprK6yt73Bx8zR1d/f4ujp6+3t7+/w0nd614KKc5ebnJyYlZSTlJCNi4qIh4eEiEGHiLzMueLR1+PRrMm758aN09rvot6QnMG7ya7l0eXc2MDP5Mudpr/0s4+U4X6AgYKGgIKNlZzFzNbd4eXmdHV244XfH+Hj5efi29TJzNTdv4WWrbzCrMKysKOelH2ep62koqKAXkxjZGVnZ2doaWo1Nzc4ODg5OTs8Ozw+PT4+P0FCQ0REISIjIyMkJCMjJCQkJSQlJiUlJy0lKiUmKRcYGRsbGxopKSYoKCQhICEhJSgpKCYnJiQjIiQlJCMjQzM/Pj08PDs5OTk2MzMyMDAuLi4tKytTUVBPTUtKSkpLTEtNS0iARkREQ0JERUZIR0REREA2M1dfdXNdVlBOQHFNXHlGOC8zMDEzMl5JXFiFdaSx0OLR2nZyXYp9hIJ8X2N/h414kzNQgYdra2RTs6eblKmLdHV/pbuJhYh7hHhQPX6qzHh2bICrfI9+a2yPj36ClKVwbm1ubm1ramloaGhpaWlqamsMa2loZ2dmZWZkZGVlhGaEZ01oaWtra2xvb29wc3R0c3Z3d3h1cuHf3dza29vc29zd3uHm5ufm5+vr6+jm5ePj4eLg39zY19jb2dnX1dPV1tPS09PPz83MzMzOzc3Ny4TITsbGxsjKysrLysrLzM7NzNPU1tja2tfY2dvb3N7e3uHj5+bl5ufo6el1d3l7fHx8fX5/goSGiY6Oj5OLio+SlJKSk5WVlJKPjY+Pj46NjoSLC4mIiYmKioiJiomJhIoHiYyOj5CNjIeNAY+ElICXmpmZmpydnp6fnp2bmZeYmJmbnqCenZybm5mXl5ianZubmpiYmZqam52enp+foKGgn5uZlZGQjY2Mh4aFhIKEgoOCgoOGhIOCgoSEhYWFh4aFhIWIiY2Qj42LjJCPkpaalZKSlJORkIuHhYSFhoWEh4yPkJGSkpOSj4yLjIyOkXiZnqClqq60vMTIbXRwbWtpamplY2TMy8zRbXV8hpCVmJ2ioJuYko+RkpOSkJagpqinpqejpKOioqOil4+QlJeZl5aTjIiGhIWHhIB5enyAhYiGiImEfXh1dHNycnJzcm9tbdnb2dfY2NvecG5u2dna2tva3d/g3+KFcoDkdHl4eXh5eX6Dg4N+fH9+fn9+fHp6e4GFiIJ/goSEhIB8dXZ3eoKHio2OkJGTlZOUk5GMhoSAfHbn5ODb2dbU1NPT09hub25ub25tbGxsa2hoZ2ltb3R3eXt9fX59fX5+fn9+f4CAfn9/fXVyc3R3eXt+goGBhYaLjY2QlZOYlTmPiYeNioeGjJSIhYaCgn5/f398fXx9fn+Bh4ePk5WXnpyeoKOkpKWmpqOOT1CWXmBNWVxcX19gYF+OYF6DsJ7Tv8fXw5y3q9u1dbDFzYfQgIy1rbug2sfb08y0wde4iI6l3Zx1fbZeXV1dX1laYmVngIaIioqLj0hISZOSk5OTlJSTk5SQiYN6fX+Ec0lQV2BkWmRgYF1dWUtehF8BYIuDloSZhYeGmYWXhJ+DiYIGgYGAf4CBhoIGgYGAf319hn6Df4x+AoB/kX4Bf4aAAX+SfgF/uoDlf/+A14CLgYSAyYGIgIOBi4CFgQGAuIGMgOaBBoKCgICAgZaCAYGNfgR9fn5/mH6KfwGBhoKDg5GCAYGTgwICBACA1pnT2Nri7/X5+4OcjY6Tl5ucoaurqLCytLvEyuDuiIeWpKmuuL2xvL+/u9Cf95Xg9/qC/e7F9pKJiYGD7/X028KuqLWllYyC+PTn4tPEtqujoZ2gnqiqrrGoneaLxISCg4mKiYSEhoyOjYqIh4aC+/Hr5+Le1tPQ0NDM09PQv6GAmcq+3cbytrK4yb+ssMPo1+LXjLPK/4D1xOHat6OYgIKMmoSUhbGRk9qownnQgpWXwer2zZSC4uB24KacwJWC9ojKpqGipauXp6Won6ucl5SQqLS5gPnf8Z60x+m8v6a3s7rDwcWytKjJzMfY1Iavra6vr6yrq6ikoqGfnpuZmJcRl5aVk5KRjIqJiIeGhoWEhIOFggaBgIGCg4OEgj6EhYSCg4iQmJmVlY+LiIWDgP+AgICBgoSFhYaFhIH//f78+fXz8vT19/Lx8/Lw7+3t6ufm5OXj4eDf3d3c24TagNva3NvY2tvb2dfZ29za29ze4d/i5eXo6ujs7O/v7u7w8/T2+fn6+vv9/P+BgYOEhYaJioqMjY6PkpWXmJuZm5yenZ+ho6OjoaGkpqKjo6CgoJ6enp2dnZuZmZmamZiYlpiYl5aTlJibmpucnp6dnp6goKKjpKWlq6+xs7e4urzBgMfNzszIxMLCwMDExsTCvrm3s7KztLa6vb6+v8C/v76+v76/wMHDw8XExMbFx8rJx8jHx8fFxMXHx8rKysXGxsXAvLy3tbSzsLGwsbGxsrGwsbS1tLS0srGwrKuqq6urqaioqKmqrbCxtLi7vr27uLa1t7i3uLm+wtHX4ebv8/j6gPv69vL5/oOJiYqMiIeMlZmdn6GjpKOjo5+eoaKampWhqJ6iqa+xr62pqKWlpqiutLWzr6utra6vr7Cupp+bnJqUk5OWlZOWlZGRj5GOjYqDf31+fn+Afnx8e3l47u3s7e3p6ert7ezq6Ofnc3R3eXl8fHlzdnp6dnV2eHt9fn16H3h8e3p6eXp7fH+Cg4KCgX5+fnx3dHPldHV2eXt8fXuEeit5eXh3dnVz5+Xh4eTm5+rv8O7t7vDx8nv19PPy8/Z9f4GDhoaKjZKXnKCkhaaApKaop6mqq62srq6uramko6Kkpaepp6ioqqywrqysq6qnpaSjoqajn52enZydnJ+hoqOmqq+zt73AxMXIysvNyMjR09HS1tvg58a/8sT7wrK2zKWuraifm5udnp2bmpiUkY6KiIWDg4Kz5rvNuc/Ml/fjsdXEvMnd5u/i3tPJwMNLzOetrsXxgrDTybnneIHMtKj1lpmZmZ+qmKe1tYeRkpCOifvk1ceqiO7QsJXztIS1koyNlI+Tlo6cpa/49ayLr4eou7jjw8nN0eP9gKuBsLCyt77Dx8hmdG1tcHJ0dHh8e3yAgoOHi5Cbo1taX2drbHF0bHNxcWx+fbRVgI+QS5KJdJBYUVFMT4+Vk4NzZmNrYVZQSYyLgoB3bWdgXV1dX19naG5ybGiZXYJXVVhcXltZWl5jZWVjYWFgXbSuqaakoJ2bmpqZmJ6fmpF7fHWem7WivY2Kj5uUhYugxLvJxIWju+l25rjMyq6gnIF6iJFyd3KeiIfQpcKD54yaksLj7sWLgOz0g+ywpNCVf+1usZSQkJebhJ6doZWoko+Jg6OuoWrOt8eAkIG1qLGRqqaturO0o6CWu8W/xsN0kpOTk5KRkI+OjIqJhoWEhBODgoGBgH58enp5eHh3d3Z3dnZ2hndHeHl5eXp8fn9/fnx9g42am5WTjYmFgH9893x9fX1+gIODgoKBf/bz8/Pw7u3p7e3u8O/u7Ojn5OPh3t/f3tzb2NfX1tPR0dCE0WDQ0NHR0NLT0tPU1dfY2Nnb3N/f4ePm5+fm6uno6ers7vDy8/Ly9fj5+35/gYODg4WHh4iIio2OkZSVmJaXlpqam5yfoKGcnZ6empqZl5iXlZaWlZaVk5OTlJaXlpaXl5aElXOWmpqZmpucmpmbnZ6fn6KjpKessLO0tbi7vcLCwb68ube3tre3uri1sq+sqKirrrG1t7a3uLi6u7u7vLy+vr29v8DAv7/Av8HCwcC/vL28ubq7u7/Av7i3ubaxrKilpKOhn6ChoaCen6OfoKCipKSkoaKhhZ2AnJybm5qanJ+goqWorK+uq6qrqquppqeprbG8wcnN19ni5Onu7er0/oOMkJOVi4uVoqivq62ys7GsrKyttLGmp52ptquruMDDwry2tLGys7a9wsO/ure6vL28vr++t7Gvsa6mpKSsqqWrrKamoqeioZyTjYqKiYyNi4eEg4GA//9A+/r6+fj6/P39/Pz+/4CBhoqLjpKMg4eRj4eEhYmPl5eSjoySkY+OjI6NkJWam5mWlI+QkI2Fg4H/gIOJkZOVloSVLZSVlJSRjIaC/fTw7+7s8PP29fb19vf7/oD//vz7+/2AgoSGh4eKi5CXm56gooShgKCho6Smpqiqq62urq2ppqSjpKWoqqmrra2xt7SwuLOzsayppaarp6Gdn5yYlpWYmJmbnaCjpaqtsbS3ury9v77BxsfGyc3R1Ni4t+S567ilobWQl5eVkI2OkZCOjo+NiomGhYSCgYCAsN2wxLjSyY7m5rfZwbS23On64t3Wyb7DS87mp63I8Her08O0+oCFxq6j4H5/gICDk4KKmZlweHp7enTXx7qrl37dwaaP4616p4V/gIR/goJ3h42U1deVeph0kqGbs6apq623wyJcSGFiY2NmZ2doNDY2Njc3Nzg3ODg5Ozw8PT4/QkMhIB4fhCE7HyAeHBsgNlMUHR8eDx8dGR8UEhIRESAiIh4bFxcaFxQTESEhIB8cGxoZGRsbHR8iJCYpJyY8IzMiISOFJQMnKiyEK4AqKVBNSklJSUdHR0hIR0pKSEM6N0xOWlJbQj5BR0RARVp5f5GBVmiCxGe7i6yviXl4TktKSzs+TYJ5daaEp1CEYHp0qdTer3BltMFrt2ZciHpdp06OdHp6gIJniI2PhJODgn15kJx/SY+Ai1lmPX+PnXWRjZOmnJeTiYCgr6Sqsxlbbm1tbm5sbWxsa2ppaWpoaGhpaWhoZ2dmhGMDZGRlhmSEZoBnaGlrbGxtbnBycnBxcnZ+h4qHhYB8eXZ0cuZzcnNzdXd4eHl5eHbl4+Lg39zc3d/f4Nvg4dzZ19fV09LR09PQzszLy8rKyMbFxsXFxcjGxsjJysrLzM3NzMzNztHT1dbZ3N3b3drc3t3d3+Hk5+fm5OTm6Ontd3d5ent7fX1+fyqAgoOGiImKjIiKi46Oj5GUlJWPkJGTjo6Oi4uLiYqKiYmMiIiLi42NjI6EjSKMiouMj46OkJGQjoyMjI2PkJKRkpaZmpubm56goJ6enpyZhJcGmJibmpiWhJEQkpaXmJmampucnqCdnZ6enYSeBqCfoJ6fn4ShgJ+foJ+dn5yen6GgmpmZmJSUkI6Ni4mIiYiIh4iKjIuLiouMjo6OjYyJiIqJiIiIh4iGh4mKi46RlJiamJWWl5mYlZCPkJWanqKlqK6wtbm+wMDBydFtdXh8fXZzfoeLkYuMkZGTiIuOkZWRiYiAiJOLiZacnZ6ZlZKQkI6Sl5uZK5eSjpGUlpeXmJmWlpSVkImJiY+RjpSTjI6Lj4yLhnx4dXV1eHl3cnFvbWuF1IDT1NXT1dXW1djdb3B3fHp+gnx0d4CAenZ2e4KKjIWBf4WEgoF/gICBhomKioeEgYKBfnd0c+V1eH6KjZCSkpGSkpOSkZGNh4F76tzW1NHNzs/Pz9HPz9DS1m3Y19bU0tJqamtsbGtsbW9ydHZ4d3d4eHh2dnl5e3t8fn9/gYKCf4B/fXx8fH6BgIGEhYiOjYmUjo6OioZ/gIaBfHh5dnJycXFxcHFzc3V2eXp7foCBg4SHh4iJi4yOj5CSlH6Ep3+ekIN6glZaXFxcXV1dXFtcXl1dXl1eXl1cXV1+wJapobq7esDaocuvnJzP2enX1Mm9sbTC3JWWsdlnk8SvneB1gA+0mIm2XFxdXV5pXF9lZD+FQSl7dGtkXFCSgHJfnXxXgGdjZmlhYWFOY2Vni4daR1lDU1tcYFteX15fX4qDlISNhQiEgYCEhYWFhoSFhYaMhZOEA4OEg5GEkoMFgoGBgYKGgwKCgIR+BX9/fn5/i34Gf4CAf39/hH4BfYl+A319foR9BH5+fX+SfgJ/gYSAgn+SfsSAAX+MgM9//4DcgNaBj4CrgQGAk4GQgAGBhoDcgQSAf4CAhH+WggGBh34BfZt+gn+Efop/AYKFg4aChIGDgI1/CYCBgoKDg4OCgYaDAgIEAICG1IiCgYiUmpabn6OamZeXnJ6kpKius7m5vs7Y4unp6vL7gIOLkJWboqixupiai+mAhID++sXy8e7x7Nvc38/Csaefl5KJiIT18Ovg183CtqujnZaQjIqJhoeHvoqKo6WYjY2NiYuJh4X/7+Tos623v9bZ2sHnyMaA1ZSZkNHqnoCLwbqzuMvf3s2Jjab8rZWnrayayP752/yJ3LHH0K6K9d3a29Pl4ZCRiuqNloGgn5qak+itgJjft8+qxZnijZiNoIvRxcCewKfM0sKNorGdl6CdneCEg+aA3o/M4cHFo7Onrbuwtbm9s7y7s7fF+Li3tbW0sa6rqKalop+fnJqamoCZmJaTkZCOjIyNioeHiIeFg4OCgYCAgYCA//38/P7++/+Cg4SFh4iNjY+ZoaGkoJiMiISDg4OCg4OBgIGCgYCBgoKBgP78+/r7+/v49ffy8O7u6urn5uTk4uLg397d393b29va2tvb3d3f3t7f3uDi4eDh4+Pm6uvo6Ors6+/x8W3z9vf2+Pn79/n6/vz/gIKDhISGh4mKioyNjZCTlZaXmZubmpyeoqSlq62qqqqjpKempKOhoaejoKCio6SmpKKlqaurqKWmpqenpKWkpquxsa2nqainpqanp6iqrLC1uLy/wcLCwcPExcLEw8LChMEEv768vITASL+/wMDAvr+/v8DAwsTDxcbExcTHx8fKycnKy83Nzs/MztHR0dLT09XY2djZ2dfX19jT1dLNysrFwb28u7azs7KxsK6ur7Cwr4StgKyvtLa8vLq+wr+8u7q8vb6/wcbL2uXt8/2Cho6KlJmbnp+foaOmqKqvr7G2trOtqqOdmJWSi4aDgoOKkZOUlZiWlpKUlZ2fn6KtsrO1t7e2uLq+ubi5urnCyb2lnpqbmJmdmZaXmJaRk5aVlI+Ki4mJipCQjo2Lh4N9ee7t6+jnK+np6ejn5ubn6Xd7gICDgHx7fHt6e3x8fHt6fXx5en17e3t9fnx7enl7enuEeYB4enh2eHh6gIKBfnt6eXh5eHVzc+Hd29rc2djY3OHl7PL09PPw7/Ly8PH09PT3/ISLiYmKi5CPkJWXnJ6ho6KjpKSmqautrq+qpKCcm5qamZqYmpydoKKmraysp6aoo52dmpmXmJmYmZianJ6ipKarq62tq660ubu9v6b7h4uQmoC1wcXHys/V2t/RxK6qqqappqy9i5OVlpebnKGmpqeknJiSi4eEgoKCgbGzqu3LycWoqdvY4tvJq8DH5uWOpcWyu67ioOTk9ZvHw7/I3uLmy8fG85OWmJiblpGjrKyBi4LSrI2B/O/r5ebj4+Tl5efm5+zy/vn3/oD/h4qPlJmzsgywu8DN3ee/+f/67IGAY51nZWZpb3JxdHd4c3FwcXJzdnV2eXyBgYWQlZygoJ+jpVNTWFpcX2Nma3FsbVGMTVBNmJZ0jY2Mj4t/gYZ8cWdgW1hUTk1LiYWEfXZxa2VfW1hWU1FPTk5QUHRVVmZnYVtbWlpdXl1arKKdon18gYWTlpWFp5OXZa53fnintn6Ab6OppKi5zsm0dHWO4JeFkJaUjMP38tj2g8ydusOsj//q5+bm+eaOjYfnmZ6FpaKmrJflo3WR3MXfrMKa3o2ajKCEycG4l7yZvMS5iJyolouSj5HCb23AbL96gaykqYysoJ6yo6SvraWssammttObmpqZmJaTkI6Ni4qIh4WEhYVjhIOCgH9+fHx8e3l4dnZ3dnV0dHV1dnZ2devq6evr7fDze319foCFioqOnKSgpKGWiISBfn5/fn9+fn19fn9+f35/fX359/b19Pb29PHw7Onl5OLh397c3NvZ2NfV09LS0tHQhNN+1NXX2Nra2dvb29ze3+Di5eXl5unr6u3v7/Dy8vPz8/f09/b6+vx/gICChISEhoaIiouLjo+QkpSWmpmYmpyfo6exsq2sraOipaaioKCdqqWdoaOkp6mqqaqusbGuqKmtrKqmqKWmrLSyq6ako6KhoKKjoqSmqa2zubzAwcHAhL4Uvb29vr27vLy7vLu7vby7vLy9u7mEuhK7vLy9vr7AwcHCw8LExcbGx8iExwbJyMfFxseEyIDMzsvLzMnKy8nGxsTAvLq2sa2sqqejoqGhoZ+fn6ChoJ+fnp+fo6issbCutLWyr6+vra+wsbW7wc7a5u/+gYWLipymqa6ytLe6vLy/wb67v76/uLmupJyWj398eXd5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r6ytqqWin56dnICamJeVkY+Pjo6Ni4mJioqHhIKBgP/8/v79/Pv6/Pz6+fr5+/n5+fv/gISGhIGBg4OBgP79+vyAgoeLjpOVko2Mi4mFg4KCgID9/Pr6+Pj29PHv6+np6Ovo5OXj4uLg4eHf4ODg3+Hi5OXn6OXl4uXl5+fm6Ont7+/s7fDv7vLy8GTy9vj4+Pn4+/z//v3/goSFh4eKi4yOj46SkZCYm56jpamopqqvsra5urq+vLvAwMDCwsHBwL2/wsHDxcLExsXGyMbHyMW/ubWwrrCqqammqa6urq2pp6anp6usrK6xuLzBw8LDhMQFw8PDwsOEwoDBwsPExMXIyMjHyMnIyMbDxsfOzs/QzczLy8vMzMzN0NHR0tPV1tfa2dvb3d3c3+Pm5+nr7Ozq6uzr6evt7u7t7e3u7Ov8hPrs49TZ2cm/t7a0sa+vsLGytba5vcDFysvKz9Pi5uv9ipeem5ybnJ6alJKQkJScoaWrsLS0tLe4un+9wcTCsJOEgYqUhez9iJ+lmo6UkpGNhI6VkIyQjY+QlJiboKKprqy1uru3uL/Eyca6r6mjoJqZmpucmpqZlpWVkpWWlpWWlpWUkY2NiYyMiod/7+vs6unp6OXm5+jn6Xd8fn57en59f4CDhIOBf317eHZ2dnd3dnd4eHl8fn9/hH6AfXx7e3l3eHd6eXl3dHLj5ODf3dzY19nW1tXW1dXY3eLn6eru6uvt7+7s7vHz+YGChYqPk5aZmpydoKCio6WnqqqusrO0tLKuqaSnpqOalpaYmZqam5ydnp+dnZ+enpeWmJiYmpucnZ6hpKinqKutr7W4uLi5uru6t6+HopmgnZyAuMDGy9HW3ODgla2ko6OenaWqsrK4kZGTlJeO65KPi4qGhYaIhoSCgf6vwsTc3NrfuozGtdq8mYne5ojam7zQwM6618Xb3uLE3d/GqZqhxqGNofCSk5iXmI6UpKao4LWj3NXR0tDNycnJysrOztLR1NTc3+7i6Ovt8ff+goWIk5IMmKGjrbvO05LJzdb3gKqNYmXFw2t2eHpzbGRkYGFkWFZcXFpdX2Vnb3V6foOEiZOTlU9WVFRXW19aYWJRi5aQdFx0cIOLiYyIhoB6dGtmX1lTTkpIjImGgXx4cmtlYF1XiFFTUVJYXl1wXF1lY2NjWKOblIZfZoOOaIiHoYKGcHmzcX6+uqSkr7XMurvAgNilsbePgI26ubO+t6elndbxztDlyY39jq+wu7GtpvLA6PGIv+yJuP6DzcbW19XHzJyewsKTvaHKueaHg6PIm6d6kI2nyfPC4NvpgpSRuY2Sm5mciciAwryNhpOcrp2PxZ2MsLerpKapttDNwtF9oZ+dmpiYl5eVk5KQjYqIiIeGVYWEg4F/fX19e3t6enl3eHd1dHRz5+fo6enq5+jp6enq6ujp6enu8vl9fn6Afn1/f3189fHw8Xt/hYmKjZKSjYqGhYOAfn9+fPPy8/Py8e7u6+fj4+GE3xfe3d3a2djX1dXW2dra29zd3uLg397h44XmgOjq7ezq7O7u8fLw8vLy9PX29/n5+vv+/4CChIiIiIqMj5GOkpOTmJ+jp6+yra21u7/Eyc7O09DQ1tDV1tjW1NXS0NHQ1NnV1t3d3Nzb3tzWzsa/tbW2qqako6SrrKakoZ6dn6CjoqKmq7G0tri5vsLCwcHCwcDAwb++vb28vb2+L76/wL6/v7/Bwb++vL++wsHDxMTFxMTGxcXIx8nKy8vNzs/P0tDQ0tPU1Nba29vchN6A39/f3d3f39/h4eDh393cbtXLyL2/vLKsqKejoaCfoaKjpKWnq66ztri3vMTT2OD+kKOsqaurqqunn52al5qkrLG6v8LEw8TFxcTHysi2mYaDjJOF6vSGnaaTho+Pi4qBiZOOhomHiYiKjI2YnZ6oqbO0tLW4vsPGw7y4t7ezqqSApqqsq62uqqippqusrq2sq6upqKSjnZ+hn5mN//j5+fn49vf4+vv8/YGJk5OMi5OTlZecn56bl5OQioeHh4iIh4eIiYmPlJeZlZOUk5GPjYuHhIWFh4aMioWB/Pv29Ozp5+Tj4+Lf3tza2+Hl5+rs7+3u8fHw6urt7/mBgIGHjZIblJeYmpydnp6hoqSmpqqvrrCwr6yppaurqaShhKKAo6Oop6SjnJuenJuVk5KTkpKTlJWWl5mbnJ+ho6Soqqyur7CxsLCqhqiWmZiYrbi8wMXIzM/Pi6qelZeZkpSWn6Sng4OEhod/1YeFg4KAgYGCgoGAfvutubva2djguInDud27kIDh65DZnMPXvtDB2cXf4OHD297FppahwJeEmdo5ent+fX93e4eHisOekNLNysrHxMDBwMHAwb/CwcPDyszazc/R0dDX3nBydHx5fIKEi5Wo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jJaYnqOnqq+yube4uba4u7u5spuJi5ecgJaCipCRhoKCgYOQkY2MmKKpqKWor7CurrK4vb+9vr+/wMC/wcbKxL+4rKagnZubnJ2dmpudnZuamZeUkY+OjIqIiYyLiIiEgH5/fXl4eXjq5ufm5ul3e3bndXd4e3t7gISCgIGBgHx6e3d1dHZ6fXl4d3h9fn58enh2dnd65tLQgNHMysrOztDS2NbU1djZ3dzc29ze2tjX297h5urx8/X4/oCAgf/+goeNjo2QkZGSkpWdoaOlpqeqra2uqqeqqKelpKGcmZeUk5SWl5aVlpiam5ubnZ2dmpmcoKSlp6yusbSzr6+srayura+0tra3trKwro68mKyysbaGsr7BxMfIgMap16uYiaSRk6akqbmCgoWEg4LvhYWDg4aGhYSEhYeLjYzB0JzLtci9mZHntdC+trPj5+za3NHFt73O3aiAh9Tuksm4luV7frmgmu6QkpWWmbmSn6+fguLEvr+7v7u9ucLGzMzW2t3d3ODi3+Lm6ezw8/n/gYWHj42Ql5qgoqu6BdLc8tfngKSGvL/Av2RtbGhlaHh8cmpiXltYWl5mZF1sbXN2fItLTVBPVFxfZmFYXFtha2yFYl1YV2J2bXZmVIh3X6FYW5qPx4peZ5SoYGp/gHZ8d1tTVlSnoLappJWoo3aqj2dogGnAmJt9qZ2QlrCMwI2PjJGWr4yIipCLgYKEio+aqJibgJOMkJGIj5GKjH9+gOzk6HyJiYmLiPTfg+DngoDzv+2HhPaB5baI65nd/KzBs7avpb6qpaT3wZPt1ch+gMGUi4Zygo+xloSz+ILS0N6RnbCV1HZ7hIatqqrTzLay1+b/p9bLr6CTkpeXr7KsusnZp6egjpeWnZ+gn52blpOPjImGH4WDgX59fHx7enp7fHx7enl4dnV2dnZ1dHRz4+bo5uWG5EPm5eLi4eLk4+Li4+Li4uPl5OXn6err7oCChIuNj4+SkZSTkJKMg39/gPmCfvDx7+vp5+Ti4d/d3t3b2tzd29vc4OHhhOBQ4eLj5ebo6u3s6+7v7uzp6+zt8fr/iJWZm5qamp+iqa6opquutre9wMPEwr+9vcDHzdDW19bV19fa2tzf4eXn5OLc3NbV3uLo7O3s6+np7e6E7THu7ezp5NjRyrqwqqGdmpmYmZiXl5mam5ycnJ2eoKKlpaSnq7C3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vpWM/ezs/YSIiYiDgIGIiIWFhIeKiIiHg4CFiIuLiPrp08Crm5qhoJ+lo6Wmo66xra2sq6qrpqGim52Zl5yfmJ2ampial5yXmJWTkpWSl5SZmZqcmJqeooCbmZueoJuUmJSVmZWWk5aSkI2Uk5OUk4yTk5ORkJOSkZKWkJKSk46KiYyOh/vv393OwqqhlrjpvYHY7qj0xsKGo4+jpvL575Glpsuy9syvtfGKjpOVloqZtaiQioSChYqoiIqI46z+yODShN/2jsbEwsC+u6y0tre1sqy1j5ienTeamJOUitiQjpGRkY2MjIqJiIeIh4SEg4OBgIH9/Pr6+/j19vHv8PHz9PDv7Orr6urq7O3u7e7shOpV6+vs7e7r7e/z8/Py9Pf49vT2/PqA+/369vLx7uzs6+no6Onn5ubp6Ort6ufp6+zs6+zs7Ors6+np7vDw7+/y9/uAgoSPlJOfqbK2uLi6ury+v769v4TAMcLCw8XExMfIysvLzM3Ozs7P0M/Q0tHOzM3MztDQz9DQz9DP0M/Mx8nKzM3My8nLzM6EzSLKzMi8s6ynp6ioq6+wtbi5vL2+wcTIzMzMzc/Rz9TW1dTThNE2z83NzMzMzc3LysrLzs3Ozc7Ozc7Pz87Pzs7S1dXX1dfY2trd39/f4ODh4ePk5ebn6+3w8fP2hPiA+vr5+fz9/P+AgYCBgYKCgYD9+u7s7e6FhvuKk5GIhY+Sj4T27Ofb2N7q+oCC+f//gYGEh4aB+/H2g4yZqZ6iqa6vq6+vop6XlJKTmZGG+/n//v3y7/Dv8O6Ah4eJjpeerbvAw8PDxcXExMfKycnIycnFw8PFy83Mwru3trGqqKZKpqajoqSnqKajoJ2amZeUlJWQjIyMi4eGhoeKiomEf319fn59fHt4d3l4en1/fn59fXt5enRz5HJx4eDi4uN0eHl6eXl4enh3eHeGdoBy3d7eysTFwsfDxtHRz9HRzs3NzdDR1NfY2t7i5+/y8fT5/YGEhIWHiIuKi4yMjYqIi4+SlpuampydnJmXk5KTkpGRkY+Oi4yLjIyOjY6Pj5GVlJKTk5WYmpyfoqaoqKuvsbCwrKqnqaurq62wsK+tra2sq/6Th5mQjJm4nLS8wYDExZmsqZyFiomPoZmXqLaDgYCAgIHvg4L8/fv6+IKC+PuIif6lyL7lys/CqLLc4+fZt5Kys9PZjKTSpKax14GRpviTsbyzqs7S4LyvqemPkZKTm5iGm6TZwsLExMfIwsrOzsrKzM/MzdTc0+Hp/ffW4ePr9vj0+YGElIaDjJKZnAejqrbP3tGfNqBzt7m6wGJmbnV5hHt/j4aBiIN7fHltbWNwa2djYmBei4qFZWh2zHKUcWvHvb3HZGpqa2dlZYVpLmxsa2tqZ2Voamxsa8i/sqWbjo6TkZCTj5KUkJeXmJeXmJaYko2Qi4yJio6RjZCEjICKjouMi4mHiYiMiY6OkJKPkJOXkZCRlZaRjY+Li4+MjYuOjIuIjoyLjoyGjY+MjIuPjY2Oj4qMjI6KhoWLi4Lv6dna0MWwo5y757h71uWg3rC3gJF7j6Hh6euYpJzFsO7JvMn3ioyQk5OHmrSnjoOGjJGVqYWGg9Kf+sjXy3F781B6paWjoZ6dkJebnJmWk456gomGhYR9fnfBfnt+fX19fHx7enl4eHl4eHZ2dnN06Ofl5eTh4d/e3t/f4ODd3t3c3Nzd3d3f3uDg393e39/f4ITiYeTk5+ru7e3v7e7t8PfzffPz8uzq6ejl4eDf39/g4N/g4+Hj5OLg4ODi5OTm5OXk5Obn6ezs7e7u8fr9f4KJl5uYrbzI0NHS1NTX19fa2trb3N3b3N7h4+Lk5uXn6uvs7e6E7YDs7u3u7Obk5eno6Ovo6urp6uvq5Nzd3+Dh3t3c3t/h4OHf3Nva0MCwp6KgoKGmqKmrsra7vL7CxcnMztLS09bV2NvX2NXT0M/QzcvJycnKysvHxsTExcXHyMjHxcTEw8TEw8LDx8jLy8vMzs/P0NLR0dLT1dXX2Nnb3d/h4uTl5Q3l5+fm5+bo6erq63V3hXiAdnbm5NfS1NBzddd0enl1c3qAgHrf1s3EwsTQ2m9w3ODgcHB0eHZ05ePoe36OnpWapKyurLGwnpqVkpGSlo+E9PH3+PPq497j5eZ6fn+Aho+VpK+ztbW4ubm4ubu9vb+9vL68vLy9wMLCvby4ubi2tbS0s7CusrW1tLKxr6yqp6Rso6WhmZeXl5OPjo6Sl5iTi4WDhYmJhoOAgoSFh42SkpGQkI6MjISA/4CA/Pr8/f6DioqMi4uLjIqJi4eFhoWDhYWA9vj61MrKxMzHx83P0NTU1dHT1dXW1dbX3N/j5+3w8vT5+4CFhoaHiYqKhIuAiISGi42VnZydoaOjoqGempuXkZCRkI6NjI6NjYyNi4uLjpCPjo2Nj5KTlJWYm52eoKKipKWjo6KjpaWmqKmqqaqpqKem+JKDl4eFjaSUq7K1t7eOraqbgISCjJ6OiJWfdXR1dXV323p57Ozt6+Z3duTmeHrkmL+048jPv56s3OZY69y2kbG31tyGo8+enabVe5Ot+o6uv7Gp2NrmuKui1Hh6eXp+gHF/jdC6u769vLu0u769uba4ubW1vcK3x87i27/Gxc7W0c/TbW98amZscnh7gIWNoKusflJMNldYV1gsLC0rKywuLC4uKioqKCYlIiIfIiAfHBwbGicoJR0mKUk1U0VDgoWGh0FDQkNCQkJEQ0JDQkVERERFQ0JDRUVERYaFg4GAenl9eXZ7hHqAfn98fn19e356d3l0eHZ2eHp2enh3d3h4enZ3eHV1eXl+ent+f4F8foCBfH6Ag4SBfIB7fYB8fnp+fXx7goF9fnx5g4OAgICBgoKChYKBgoR/e3yEgnfc1sLJuqqTgnie2aRirs+Pv5GNbXdidXO9ybyBl5C7mMGfiIjnh4eJi4s8fXNcWVNOSUZHUYx8gHm1hLmwtKJPLclddXR1dHJwaW1wcXFvbV1cYWVlZWNdYVmbX19iZGRkY2RjY2JhhGIHY2FhYWLCwITCBcTCw8PEhcZEw8LExcbGycrLyMjHyMvKysvOzc3Pz9DS09bX1dfX2dra2t7db93d2tbV1NPQz87Nzc/Qzs7O0dDR09LQ0dPV1dTU1NaF1SzY2Nra29/o63h6f4qOjJ6psbm7ury8v8C/wMHCw8TDwcPDxsfHyMvMzc7Pz4TRU9LQz9DS0c3KysjLy8zMzs3NzszMzMjBwsPDw8HAv8DBwcDAv7y5uLKilo+LioqLjY6OlZmYm5ueoaOnq6utra6wrrCzsK+tqammpaOioaChn5+ehZwbm5ycm5ubnJybmZmYmJmZmZibnJ2eoKCfoJ+ghKEWo6SkpKWmqKiqq6uqqamqqamoqauqq4VWYFdWVlarqqWgoZ1UVaBSVlVUVVZbXVmrpqGcmZujp1NVqqyrVFNWWVhYtLO1XV5ncGpsdXx7fX99c29ubGprb2tlwr7AwL64srCxs7RdX19gY2hrcnZ4eHl6e3t6eXp8fYR+gICBgYCBgYODh4WIiIeKiYqLh4WIiYqKiYuJhoWEg4KAfXp3eHl1cnBwdXp6d3Brampub25raWttbnF2e3p6eXl3dnZxbdltbNjX2dzecXh5enh4eHp2dnh2dHR0c3Z1cNrc3r60s7G2sa+usLK4uru2uLm4t7a3t7q8vr7CxMLECcbGZWdnZ2hoaYVqFmlmZmlrcXZ1dnp8fX9/fHp6dG9ubm6EbQpubWtramloamxrhGmAa2pra2tubm9vcXByc3N0c3R1dnd2d3l5eHh3d3azeG+Jc29xfGd3ent8fGKLi4FubmxyhG5lb3VLSElKS0yMT1CdnJ2bmExMk5JLTJFjmpXUrbeyjZvT3OHRq4agosXRdYewkYuTzWx7lu18nbKhm8LI3qqdj7BYWFhZWldPWWAwrJ6dnpmakYuMkIiFgYCCe3t8hnuKkKOehIuIj5SOi41JSFM/Oz5CRklMUFZgal4+hoOahISDB4KCgYGAgICEf5iA5X+Nfgd9fX59fX1+h32FfoR9AX6GfwF+iH0Nfn9/f35+fX5+fn+AfpiAAX+VgLR/AYCsf/+AtoCJgYaAA4GBgImBiIAFgYGAgICGgYOAlYGLgN2BA4CBgYWAk4GigNGBAYCHf4eBjH8BgYWCA4GCgoWBCIKCgYGCgoGBqX6Jf6B+jn8CgYMCAgQAgLDamJyqpI/lx87CyoKs8Piih4Dw6Ovn6Ozm5trU19LZ0NLZ2dnd4NjV0tTOyczQ1tvc2+Hr4uTm5PDp7/Hv6eru8/Hs9fnz5NHCsqCXkZSZlJ2XnZyhoZ6boZ2bo6GhoZydnZiTkpOblJCSlpmQkpmVkY+RjpaVkJSVmJuZm56egJyal5WVlJGUl5GSlJKSlpKUk5GOk4+Rjo6VkY+PjoqMjoqGi4aJiomKh4SJjoyIjI2JiYyNi42NjImJiIDq2ubts6mcqK6tzej29bHugdLn9fj8gIP51+6g7LSgm4mVy4+N/oTMpvXIuY3j4O2EsdmKpLC1oI+usbCxrtbX5d6EPpCehZGRhuO1+ImSkI2NjIyLiYiHhYWEhIKAgID/gPz6+fb49fPz8vLw7+3s7Ozr7Orp6urp6+vq6enq6uvqhOxj7ezs6+nq6+rp6uvq6ujo6+bm5ujo5+jn5ubm5eTl5ebl5eno6eno6Onp6Ojq7vT8go2XqKuqpaSnpaistLm6u7u8vb6+vL6/v8HBwMLExcXHyMjJycrJycrMzMvNzczNzc/RhNJh09PT0tHPzs3Ny8nKzc3JycvMzM3Ny8vOzczOz8/Q0dPU09bX0c7Oz9HT1NXV1tXU1tna2tzb29zc29vc3d/e3t7g4d7e39/d29vd3N3c3d/f2tzZ2NTW3+Lg3d3d3NnZ24TdGtzd3+Dh4+Pl5Ofq6uvq6err7e/w9Pb4+/r5hPsI/P7+/v+BgoKEg4CEhYWEgPv18fHo4eju7v+Mi4mIh4WA8vrz+4P89PuGj4qHiIGKjYWB/fX3+oKHiY+Qk5uVlZSTl5qejoH1+vXt6+fy6efo8YSKk5ymrLCxtbm8vr/Avry6ubi4t7e3trW1trW2uLrBw8LFxsXAt7m+vb/BwsLAubm3s6+ooZyXjoCKjY+TlZOQjpCPj4+Oi4iAg4aEgn7u7Onpd3l5eH1/f4GCgX16eHh7fXx7eXZz5XJ1d3Zzc3d2d3Rzcd/e4eLk5XPh2dTKwrvFxsLIx8jJytHT1tfX2tzf4+Xt8/r39fX5/v6AgoWIiouMjYyLi4uMjI2PkJCSkpGRkZCPjo6OjwWPjo6PkIaPH46PjpGQkZKSk5WYmZqfoaOjo6Wmpqmsqqeop6qrrKyGq4Cqqqytrsf/9/2B9IeHkKrsvcHGm/2GkNyD/ImCgJSksPHo6ezs7dns6uvs7e3cyfHy8fX496fZwMe3ytah8cC61qiI5sa1x8+dvNKwu7LQp7/S67/K0r+9oJa/qp2x5I2QlY6Sk4yTk9qWjNLIw8vtzdXa4tHU7OTU2e3a6OeA/hi8y8fV8+7b2+vvmvDX4fqLg4+Yz4fa09yAk693eXx5bbGcnY6TYozCzoVxacrDx8XGx8K+uLO2rbOsqK2rqq6yrKuprKajqKmus7O0vMC7u7u6wb/DxMW/v8HFwr/GyMe/s6idkYuIio6JkYiNjJCOjoyQjoySkZKPi4yLioeHiI2FhIeJjYaGi4mHhYaGi4mHi4uOkJKTk5OAlJGPjo+OiYuNiYmKi4qMi42Mi4iLiYqIio2JiYmKhoaIhIGFgYWHhYaGgoiIiIeIioeHiYqIjImHhYWCe+XP2+aunJGgpqPK5vTzs/N2zuP1+ft/g/LL24rJnIqJf4nElIz5gcGX5NC4h9bW3nqevXaNlZmJepSWlJWTlqvc03tAhpB3iomDzaXgeH1+fXx8fHt5eXp6eXd4d3V0c+l15+Xk4+Dh4uLh4N3e3dzc3Nrc29vb2tna2tnc3d3d3t3f34TgBuHi4d/g34XgFeHi4OHe3t/g397e3d3c3Nze3t7f34ThdOLg4uPk5OTo7vaCkaC1ubizsrWztL7J0tPT09XX1tbX2dva3Nzd39/h4+Pk5ebm5+fo6Ofo6Onp6+rp6+rr6+zt7e3s6+ro5ubk4eDg4N/f4ODf3+Dg3d7f4ODg3t7f4ePl5eXj2dHU09Xa2drb2tvc397ghOFY4uLh3+Dj5OTj5eTj4+Ph4eDf3t3b2dfW1tPOy8nJx8nNzcvHxcbIyMnJyMjJycjKzM3Nz9DR0dPW19fW2Nna293f4ePm5+jo6Obo5+fp6uvsd3d3eXl4eIR5U3bl3tjW0cnP1NXkfX18e3t6d+Dk3+N44drbdYB/fX11f4B8eO7h5eh3e36ChImMhoiIhImQkIR87vDm3tzY49ra2+B7gYmRmZ6io6esrq+wsbGwhqyArauqqqupqauus7S1uLi5trCzt7W3uLq7ura3t7WzsaymoJiSk5idoaGdm5ubnJ2enJeNj5aTkIn/+Pj8g4eGho+SlJSWlZCMiomNj5CNiYaB/oGGiYiCg4aGiIWFgfz5+Pr+/4D47OXXzsLOzMXLycnMzdTY2NrZ3d7g4+bu8/lH9fXz+f7/gYOGiImLjI2MjIyLi4uNkJKUlZiZmZubmZWWlZGQkpSUlJiXlZOTko+NjY2MjIuMjY6RlJSWl5iZmZubnJ6hoaGEoimjo6OkpKWmpqalpaWmvvjr8HvtfoCHmdyxtbiO74KKyn/tg3l1hpCc2oTVc9fC2NbW19fYx7ba3Nzb3N2Y0bi/tMjVnOS7w9yngtbFuczPlrzSrLmu0KXF1+vAzNK+vp+Su6OVqtF4eHt4enp2enrLjoPHvL7B5cHL0NbIyeDWxsrdxNXUeeuxvrXE4NfKyNXYh8ass891b3d9kGGhn6yAUGA7Ozk3MllOTUNCMk52fFdOSo+Kjo6LjoyLiIOFgYR+eX19fH5/e3x8fXl5fHt8fX59g4iEgoOAg4OIiIeEgoSHhoKFiImGg4J/fHh1eXt1fHR6eHt6eHh4d3V7fHx6eHh7d3N1d3t1dXZ4e3Z3enh3dnd3enl3enx+f3+AgIOAgoB9fn9/fn9+fH19e3x/fn9/fX1/fX98foB9fXx/fXx8eHt6dXx8e317d36Bfn99f3t9fX6AgYB/fHt1cs/Ax9Sein2Km5TA3ejnn75qyNvu8vJ7fOKluHCYd25mVVyHU3TteaV9rIykb5Suu2N0h1NjaW1iV2psbG5sU3rBt2wlc3did3N0ooexXmJiY2NiYmNjY2JiYmFhYWJhYcNiwsLCw8PExIXDGMLAwMHExMHFxcPCxMTDxMXGyMjIysvMzITLAsjLh8wFy83MycqEy4DMzMrJyMnJzMzMy8vPzs/Pz9HS0dLS09ba4XeAjKCjn5ubn5yfpa22trW1tre5urm6u7u9vr2+wcLCw8PDxMbGxsfGxsfHyMjIycrKycjHycnJysnHxsbEw8HBvr7Avr6/wMHBv766ur2/vry6urq8u729vbmvrKuqrK+wsLCxsAmwsrGztbS2tbaEtUy0tbW1tLOztLS1sbCtra2sqqilo6KfnZybm5yenpuZl5mZmJeXmJiXmJiZmpqbm5ucnJ6fn5+goaGio6Sio6SmpqioqKenqKenqamphFVYVlZVVldXVlWopqSkoJqgo6StWllZWlpbWauwrbJarKimVl1eXVtXXF5bWrWtrq5ZXF5fYGJjYGFjX2NnZWBeuLmzrq+ssK2tq6xaXmJlaGptb3BxcnJzc4RyGHFxcXJxcnFycnN0c3R3d3h5enp7eXp9foR/XoB/f4CCgoKAfnt1cXF0en18e3p3eXt8fn14cHB1dXRw0MzLzWxvb290eXt7fXx6dnJzeHd2d3VybttucXRzb292dHVycnDY1dXX3Nxu187IurOotLOssaytrrC2ubiEuUW7vL7AxsbCwcDEyMllZWdoaWlqa2traWlqamttb3JzdnZ3enp4c3Rzcm9vcXJydXVycnFwbWppamloZ2doaGpramprbGuEbAhub3BxcnJycYVzh3SAc4rUytdqzmdscXaYeHl5X7FpcKJnwmhfW2VtcpCHh4qJiXyKi4uLiot/douLiouLi2GvnamftMeMzamny5hvs7ubrcWDoLyjr5/HmrjM3rfDy6+ukYKnkYiZr1hYWFdYWFdXVppeU5aJiYyyio+VoIeHpKKFhZ2JmJNbtoGRgJAUpp2Si5qYY4BoaXhJQ0VJOyxQVWOCgYWDhoIBgYWA/3+rf49+AX2GfoJ/hH6IfQ9+fn9+fn19fn59fn5/f3+MgAN/fn6Kf5OAAn+A1H//gMCAjIGKgIeBhIAEgYCAgIqBhICQgYuAzoGEgJWBAYCMgYaAAYGhgNKBBoB+fn5/foR/AYCEgQZ/f39+f36GfwGAlYGHfgF9hX4BfZt+iX8Dfn9/k34Bf4t+AX+EfoR/BYCAf39/AgIEAIC9wsbJx8/NyMfIyMrCxcDCycnDx8HDxcvJzMTFv7i5v7++w8G/w8TCwb/Dvr7CvsHF0szJz9TZ2Njb19ba19jY3uTm2+vt6NXJuLClmZWTlJGTl5SamZeclpiXmJuUkZaalJaTkZKTj5CQipKJiJSQjJKMjY+NjZSPlZiaoJealjWZl5aRlZWTkJaSl5COj5CRlJGQko6PjYuLj4yOjY6Kh4qGh4eIhImFjYyFiYeJiYmHhIKDhoSHgIaIioaGi4iJhIiNh42NkpOOjo2JhIOGi4mLjI6Oj4rx0YS716+aq5jKxazc2L7QrqX+87eOioaEhIu+1NaByNS93tWVwtPUp6/f8ODg6uD8pf+Qj46NiomIh4aDhYWFhIKCgP/99/T08vLy8/Py8e/v7e3s7Ovr6Ofp6ejn6OfmBObo6uuE7IDr7Ovr7erq6ejo5+bk6OTm6Obn5+fo5+bm5OPl5eXm6Orn6Ovp6+jq6uzw8vTz+/2AhIaDhYuRkZGUmpiYlZKVmaGutby5vb6+wMPExMTFxMfJyMnJy8vKy8vO0NHT1NPT09jZ297e3NfZ29jX1NbNwb69v8LCwsjL0NDT0M7P04DPzc/My9DPyNnc1Ors7e3s7u/u7+/y7+zu7u3t7uvq5+Xj4uPj4eDd3d7c2dra29na19HPzMzKx8XCwsDBwcfJytLTz9DT1tnc3+Di4uPk5eXo6+zs7O7u7e7v7vDw8fLy9ff4+vv9/P3/gP+AgICBgoSIjIyIhoeHiYiEgPnt6kfm3tnY1tXX1M/NzMrKztPT29zY3u7q8//7iIuOjoL9gYH4/Pz//4eOg//59/2F+/L5+fX/+Pz4/YCBgIKOm6Wnq66urK2vr4SuB7S0tK+rqKmFpoCnqKioqamtsLGxsbW4urq4ubq0sq+qra2sqKeflI6NkpSam5qXlpaSkpGQjo2LiYiKiYeFg397fHx9fXl16HR1fXx3dXh/f3x5dHJ1dnV0dnZ3c+Df4nLg2tjW19fa3d3d3N3d3djY2d3b19TU19jd4+jv8fHy9PD09/z+gYCAgQ6DhYWGh4eJiouNjpCSkoWUKpOSkZCPkJCPjo6Pj5CQj46Oj42LiouKi4uMjYuMjI6Pk5WYnp2fnqKioYSggKOlpaeoqaqpqKmop6eoqqqrq+vo9IDp8PGIjISJm6Wet7aM7vWP4YL4iYWBoKWs7Ojr9Pn62vj4+fv9//zn+/2BhoWDs7qX27G/wKWW38PRo5KWztHU0tDMx7i/09mwkJXP4JzLte6Ik+u0tfLni4+SjY6E7I2O96zp2cnAw/LRJ9vY3MLG5OHIzePR3d339LrBvs7Y3PD7//uEg4iXoqeco6vgwLq6ugeqsLK0sbi0hLGAs66wqayxsauwqqqusq+xrK2mnqCio56ioZ2en6KgoKGeoaWjoqewrqixsra2tbiysra0tLW5vb+3wsTBs66jnpiOjIyJhIiKh42LiI2JioyLjYiGiYqHiYaGh4eGhoWCh4CAiYWDh4GDhISDioaLjpCUjI6MkY+NiY2MioiMh4oWh4aHh4mLiYmLiIiHhoWIhYeHh4aFh4SDD4GFg4iHg4aGhYWGhYOBgYSEgIWEg4WEhIeFhYKEiYSKio2NioqLhoGAg4mIiYqMjIuH7Mh2oL6YkKWfwrKi0su1xqmk7uW0jIiCfXp/veDkfLa9pr6EaL3Nz6Gp2fDh5Ozh/Z3cfX18e3t5eXl4d3d2dnZ1dHPk4+Hg4N7e3+Dh4N7d3Nzc2tvb29na29rZ2draI9rb3Nzc3d3d3t/e3+Df3+Dg39/f3Nnd3d/e397d3d7c3N3dhNyA3d7g3t/i4uTk5OPl6ezw8Pj7gIaIhIaOlZmbnaahoJ6XmZ+uwsrQzdPW19fY2dze3+Dh4eHj5efm5ujn6+vs7O7t7u7v7e7u7u3o6u/t6+bl2cjAxMHBxsrW3uPl5+Db3uHb2dvY19rWy+br2ff4+Pn5+Pn49/n59/Xz8vDu7u076+nk4uHi4+Ph3dze3NrZ2drX19DHvbm5trOysK2urrGztbq8vby9wMPIys7O0NHT09PU19nZ2tna2tqE22fc3N7f4eLk5eXm5ufnc+l0dHV1dnd5fHx5d3h4eXh1ct/U0c7Hwr++vsG8trO0tLa5u7/Fw8DF1tPc7el8goWEeet3duXm5ujpfIF66OHd3nTj3uPm6vLt8enwent6eYGKlpmanJ6dhJ6AnZ2doaOko5+enpycm5ycnZ2dnp6goaKjo6Soq62tqqytqqqnpKeoqKemopaNjZCUnKKjoqOin6CgnpubmpeXmpmXk5GLhoaJi4uGgf+Ago2LhoOKk5GNioWChISEhoeJiIP7+/2A+O7s6uvs7/Hw7+3r7u3n5+br5eHc3NnZ29855uzu7/Dw7vP2+vt/fn9/gIGBg4OEhoiHioyNjo+SkpSVlpeZmZeUl5eWlZOVk5GTlJSTk5KPioiGhoUbhIeJi4yPk5OVlZeZm5iZmJiam5ydnp+foKCfhKCAoqKkpN7a633l5eOBgn1/jJiUq6qD6OaK0n7shHt2j5KY087R2Nzfwd/e3+Dh4uHP4uRzdXRzn6qG1rPBu5yQ4svUoJCS0dTX1NHQybjB19yxkJTK0pfNs+GEjvWypubRdXd4dndsyHV07qTY1sO9v/HN1NbWuLra1bq918HOy+wZ5LG6ts7a3ery9PSAfoCLlpuQmJ3CrKaop4CJjYyLiY6NioiIi4yHiIKGiomEiIKDhoiFh4SHgXl8f356fHx5d3l6eX1+eHp9e3l6gHx3f3+Cg4OFgH6CgYKEhoiJgomLiYODgIB/e3p5eHV3d3V6eXZ6d3d2d3d1dXl5d3l1c3V2d3d3dHRzcnh3dXl2dnd1dnl3e39/gnt9fYB/fX17fH16en56e3l6eX18fnx8f3p9e3t5fXx8fXx7ent4eXp6eHt7fnt5fHx7fX18enh5fHx8fn5+fX5+fn9+gHx8gn6FhISHhISEf3t6fYWChISGg4SC4LRgfJ93XmZYhomLv7uhjnyPv7+lhoF5cGxmeoGVXo+YfYs5N6m8vxSWncne1NHc0ueGrmJiYWFgYGBhYIdhCl++vr29vb6/v8CFwgPBwL+EwCbBw8TDxcTCwcLDw8THxsfHycrJyMnKy8vLycfHxMjGyMjHyMnJyYTIgMfHyMvLzcvJy87Oz8/Q0NDU2tza3+RzeHl2eH+EhIaJj46MjIaHi5emrLKvsrO1tbe4uLe6ury9vL28u7+/wMDBwsLDw8XEw8TEw8PCwL/BxMPAvb2zpqSloqWoqbO6vb69uLK0tLGws6+ts6+kuL6ywMHBwcC/vby9vru8u7m6Pbm4t7a0tbSysbGwr66sra2sq6ipqqenn5mVkY6Mi4uLiomJi4uNjo6QkJGUlJeYmJqbmpucm5udn6Cgn6CEnxegoKGhoJ+goqOjpKWmpaVSpFJSU1NTVIRVWlRVVVZWVVOkoaCem5iXmJiamJOSk5OTlJiXmpmYmqSiqK+wW1xeX1qzW1qwsK2usFpcWrGppKVVq6mqrrG3tbiytVtcW1pbYGRlZmdoaGlqamlnaGlqa2tsbIZrBWpra2xrhGwObW5vcHJzc3N0dnR1c3OEdYB2dW9raWpsdHl7e3t8fXt7eXh3d3Z2eHh3dXNvbGxtb3BtadBqa3NzcG1xeHh2c29ubm9vb3Jycm3S09lt1c3KyMfJyszNy8nIysnDwsHGwLy4t7a1tri6vMDAv8C/wsPCxGNhYWFiY2NkZGRlZ2ZoaWlqa2xsbm9xcnV1dHFzdCpycnJzcXBwcnJwcW9taWdkY2JhYmFiYmNkZWRmaGlqaGlsb2trbG1tbGyEbQFuhXCFcYCcmLFux8bAbmxraW1qZHNzWa61baVlvmtiWnBtcImAgISGh3eHiYuLioqKgIqKRUZGRWKHasSYqaiFgt2zxZGDhcjHzczKy76vutDTqYKAs7iIwaTCa3PtqY7Iq1VVVlZVSI9TTrpfeaWRhorEnqGgpYB/pKN/fp+PlpC3uYaShhWjpqm9v7m5Y19ibHV9dXx8iYKCiIn/f9l/g36IfYR+Bn19fn19foZ/A359fYV/goCMfoJ/kYDVf/+Au4ACgYCRgZyAhYEDgIGBhYCDgYSAAYGKgNyBAYCVgQSAgICBpYDXgQeAgH9/fn5+hX8BgISBBn9+f35/foZ/AYCQgYSCAYGdfgV9fn5+fYR+A319foZ/Bn5/f36AgJ9+jn8CAgQAgK+utLCyvLy6vLa8sLe8tbm/vbrBure/wsC5t7i6vLe4sq6vtLG0urOvtLWzs7S1uLq/yMDKxMvJydDS0svH0c7Oys7F0NLRy8KwsJ+bl5qck5ePnZmUk5qYl6CboZ2SjpeTmZqPl5eTkpGIjYWPjJGNiIeIjI6Mj46UlpiUlZeagJiUk46TkpWPj46Sj5CPjpCPkJCJkY6MiIyKj4iMio2Ji4qGiISHg4SGiYiLhYWHg4OIhIOIhoeGh4OKh4iKiIeHhIaJhIeFhoiEh4aFhYqOjI6KkIySko+NkI6KiPW8qYrw1bHT1/zT1crPtbLxi42Tmoqml4+IfraRmJLRqMvXgLKs4OHl1+PU3O7U/IKMi4uMioiHh4aEhYKBgf/8+/j39vT08PHw8PLv7e7t7evq6eno6unp6urp6ejr7Ovr7O3v6+rq6+/s6+7s7uzt8PPw7u/v7Orr7Ovq6Ofo5+fm5ufm5+fn6Ovn6ern5+no5+jp6ufp6evr7O3t8e/z8vb4a/f4+/uAg4qPk5aUkpShp6esra2xsrO1tLO0tL3DzdHV2trW2tvb4ury8/Ps49fCvrmuop2gpKWfnpyenp+coaKiraSorbK1tK+sp6nF4ejp6efn6vDx8fDu7+7u7uvo5uTg29rZ2tzd29vbhNgQ1dXX2dna3Nzd3d3e2tzd3oXffuDg4eLi4uPm5ubo6uzu7/Dy8fLz9PT19vT19vb19vb4+vv9/f7//4GChIaHio2QjomLiIL//YOE9+3o4d3b19fX2Nrb3NnX1tTPzcrIyszO0NHX2tfZ4N/l5ubi5efl7PD7goeWlZGPjIqH/4L58/T18fLz9Pf/gYSJjo+PkoSWDJqWl5ibnaGioqCgn4SggKGhoaOlpaalqK+ys7Kvrq6sqqioo6SjoKCkoqGjn52dm5qWlZSRkpiVkI6OjImHhIaFiIqGhYKBgIGAgoKAgH15e39+e3Pa29nV1dfa4uTc3eDi4XHg29fV1NHS1NTV1tnY293e29rZ2djZ1tba3uLo7O3v7unn6O7w8/qAgoWHLoeGhYWEhYaGh4SFh4eKjY6UlZaWlZaWlpSUk5GRj46Oj4+OjImJiouJiYiHhYWEhhWIiYuPkpSWmJudn6ChoaKio6Wlo6OEoSmio6SjpaWmmNOxoJqM9OP6+pyX+I+T8a2xsYnm84jX/vaGgf+boqqGiISMcu+Njo2NkJKOgIuNkJKQjr3QwtzNydDNscrW39OyhLi+3NOFtNGkoKbW9LK78oapwq2Yk5nlwfWh5IqOjouM++GKifGpudTHwsTzusHD28TQ6ufj6oD7hYCPjYmPkpGfoaCenaGjo6anoqSoqqmprKuqrICgoKOdoamoqaumqZ+lqKClqqikq6ajq6yrpaOjpaWhoJyXlZqWmJyYk5ebm5mcnJygpKujrqWurqmwsrOpp6+ur6uspLCysK+onJ2Sj4yNkIeJhI+Oh4iNi4uTjpKNhoKKh42NhIuLh4eGfoF8hYKGhIB+f4OFgoWFioqNjIuNj4CPjImFiYmMhYaFiYaHh4aIiIiHg4mIhoOFg4eChYOGhIaGg4SBg4KBg4WFhYKCgn9/g4KBhYSDgoGAhYKDh4WEhIKDhYKEgoSEg4WEhISHiYmLiIuJjIyLjI2HhIXrtKKG5ce50cz2ydDFy66r7YmIjZOGpZCNjYm9jJKIwqTN2Cyzr+Dk6Njm2ODzw+Rzenp7e3l4eHh3dnZ2dHTk4uLh4ODf39/g3t3d3dzZ2IXZhNqA29rb2trb29vc29vc3t/f397f3+Df4uLj5eTm5eTl5eXk4eDf397d3Nrd3dzd3t7f3d7g3t/f4OHg4ODh4eLk5OXp6enr7/H08vb5+Pf9/4GCjZWZnpqWmquvtLy2t728vr29wMLFztXc4ejr6+vq6eTd2NPVzMa8saqrpJqUkpNAkpKRj5GTlZeUl5eXm5ilo52dmpuZnJfC5ejo6+nl5vDz8/Dw7uvq5+Ph3trT0NHR0dbX1tTV09PR0tHR0NHS1ITTgNLQ0dHR0tLR0NDR0dDQ0NLT1NTU1djY2dvb3N3d3t7e3+Hi4ODf4ODg4eHh4uPk5eXmc3R0dnZ3eHp4cnJyb9rabm/Xz8rEwL6+vLu9v729vbu6ure0s7KxsbO2uL2/wMTJys7P0c/R08/S1d50eYWFg4OAfnvteOXh5eXi5ebmVOfseHt+gIGBg4SFiIeGhYaGi46QkpOTlJSVlJWVlZeXmZmZm5ydoKKko6GhoaCenZ2ZmpmXmJuampuYlpeVlpSTk5SUmJmXlpiXkoyKi46SlZSSj4SMH4+Pjo6MhYmQj4uB7/Dt6ejs7vv/9fb7//6A/Pbv6ueE5YDn5+fo6evr5+Pj5OLg29ra3uLm5+js6+Xo5ujo7fZ+f4CDhIOCgoKDhYSEgICBgYOHi4+QkpSUlpeYl5aVk5SQj5GSkI+MiomKi4mFgoKAf39/gICBgYOFh4qMjpGTlJeYmZqam5ucnJuampqbnJ2enp+foJHIpoyGgPTo9++WjYDmhobhoqWkftjjgs3354J55YuQlnR0d3h3edB6e3t6e3x8cXt9fn9+fam8tdbHxs7Iq8jV4dSzhLnF4taAsMugmKPa46+583ymxbCWkJTswOqgznN2dnR2z7x0cOmfp9PDvL/uuLq81LrF39rR23jpfniGg4CIjIyUl5mUk5eZmAyanJeanJ2cnJ6cnZ2AhIKGf4OFiIiKhYmAhYZ+hYiGhIeDgoiKhoOBgYOCgYJ9eXh6eHp8d3J5enp4e3p5fXuDeoB6gH98fX+Bfn6DgYF+f3iBg4ODgXyAeXp6fH13enN6eHV1e3l5fXl+e3VyeHZ4eXZ4enh3eHBwcHV0d3RzcXN2dHN2dnl5enp8fXuAe3p4dnt7fHh4eHl4eHh5e3t6end6e3t3eXl6d3t5e3p6end5d3l5eHp5eHx3eHl3dXh4dnl4e3t5eHx5en59fn17e356f359fn2Af35+gYSDg4GFg4OEhYSFgHx93aGEarWSdbu98b7FubmcmOSDfoWJd3hjZG1zq4iKe5t9t8gOpaXOy9fN2s3P4ZyyW1+GYB9hYGBhYF9eu7u7vLy9vr6/v8DAwL/Av72+v769vb3AhcGAwsPCw8PExsfIyMfIx8jKyMnIyMvNzM/MzM3MzMzJycvKycrJx8jJycvJycjJyMrKysvMzc7Oz87Ozc/R0dLR0dTW19jc3uLi3t/j43N2fIGEiIOBhI+WmJybm56ampubnKCkqa6ztbi5uru8urWrpJ6imJOMiIqLh4KAfn99fn5KfX9/gYKAgYGCg4GLhoB+fX19gX+cr7KxsK6trrKzs7Kwrq2trKmmpKOhn5+dn6Ojo6KhoaChoaCgoaCfnqCfnp2enZyamZubm5yFmg6bnJucm5udnZ2enp+goYSgDaGhoKChoqGgoaChoaGEogWjUlFRUYRSHVFPT09Nnp1PT5ybmZaTkpKPj5KRkpOSkJCRkZCQhI4Uj5CTlZeZnJqdnp+foKCio6KnVleEXDVbWlmuV6uprq6srqyrq7FZWVtcXFxdXV5fX11cXF1fYmNjZGVmZWVlZmZnZmdoaGhpaWprbIVtBWxsbGtphGplbGxsbWprbGpra2tsbG1ucHFzc3FvbGlrbm9wcXFvbW1ubnBwcXFwbGxyc3Fqx8jIxcbHytPVzs3R1dNpz8zJx8bBwcPBwsHBwsLExMC+vby8ura1tba3uru7vL26ubi5uby/YGKEYztiYmJjZGRjYGBhYWJkZmhpamxub3BxcXBwb29ra21ubGxoaGZnaGZkYWBgXl5eX19eX2BgYWFjZWZnaIRpC2tsbW1rbGxsa2xshm5fZY10aWRl1cnTz4N4v29qoWxublWftWumyL1qYa5ua29FQkNDREZ5RkdGRkVGR0NISElISUpnmZLGs7PEvKDA0N3LqXurtNTPb6eylImR0cuKnOhwk7OhiXp96rjLl6qFVDSJhlRJulVco5CFiciHiIumiZKzsZynZMZrZXJycHd3eYCDg4J+gYWDhIWBg4SDgoWFgYCD/3/ef4R+g32KfoV/hn6Df45+gn+PgOl//4CogI2BBICAgYGqgImBAoCBioDhgY6AAYGngNeBBYCAf39/hH4Gf39+f39/hIENf35/fn5+f39+f39/gYWCAYGOggGBmX4BfYx+A31+foV/B35+f39+gICPfgJ/fpx/AgIEAICpqaqtra2vqKijr6ywrq+vs7CxsLGysbW2s7ezsLKsrbWwqqapqqqqqa+qqK6tqrGosbq9ycG8xcjIyczBu8TFy9fQwszBw8rAs6mhnJqXk56OlpOPkZ+SlZaWmJaWkpCPlJaYjZCLi4yMj5OJiIiPhIqLjYmMkI2OkZaYk5eTnICTlJiZk5CRjpGNko+Tj46Kk5GNjI6PjpGHi4qHhoWLiYqJiYaHiYmGgoeDhoOEhYWEhIaDhIeEhIODh4aIiYaChoeIhoaIg4iIiIaGiIaIiYeIiYeEh4aEh4mLiYqMioyKj5CKhoqQkpKSkJGPjouRlpSZjuSShb6XgouI95ffnoClsdze3tDVu9/d1tGD34WDhYiHhYSCgYD///759/f28/Lx7/Dx7u3t7Ovq6+np5+fl5+jp6erq6+vp6+vr6ubu8PDt8PH29/yAgPyBgISKiomQkJOTlZGJg/779PHu7O/1+Yb68/bw8fDv7O3v8vH09Pn8gIiOlZecnaKjqqqnqHGura2rrbGtqKelqa+ypZmTlJORk5KXm5uXnJiaqa2st8PJy8/f8IKLlpyViv/63MbQ2r2mk4yKjouPjI6ZlJGZnq2wrai3ysS7rqCyray90NHV1dfX2tra29za3t/i4ODe3dvZ19jY2dnX1tbV1dbV04TWM9fZ2tna3N3d397g4OHi4+Li4uTl5+jq6erq7Orr7O/w8vP19/n5+Pn6+fn6+vv9/v//gIWBXYKCg4WHiYmGg4KA/f7/gIGCgYGA/P6A8O7v8Pfu5+Xl5Ofi4+no5+fn6ern5ufr94SAgoOCgYOFhYWMi4qMg4T89vb49vTz8vX09fn+goOEhomKi46Ojo2Ul5qfm4ScB56Xm5mempqEm4Cdnp6cnZ2enp6cmpaVlZWUlZeXmZaXmJeZlpWTkY2JhoWFiIuOjo2NjImAe3+Fh4aDe+/u7/B4enl9f318eubf2djU0tLR0dTV1NPS3HN04t3d3tvX1djV1dbY19fY2dnZ2tjZ2tna3Nzf5Onu8PXw5t7b19vc3+De3+Hn6/Dx9YD3+4CBgIKFh4iFhIOGiouOj42Qk5iYlpWSkpOOi4uNjY2JiIeGhISEg4KCgoOEhYeJjpKUlpmcn6GioaGgoJ+en6Ggn56foKKio6OjovqaopCWmZaH8tnj6pCglYeUzKqytYvd6oPa+uz7//uWm6eChouNkJDzjo2Njo6QlISUkmeRkI6MuNiwxrrFworUwbTQtq/30Mzky5fBzbKmk8+mw9DsqsbJtLrIwMS3tsTfhoyNiYuEiYmE9rmv9vj6g4uLkY+UkpiXlZOXlZOUkpSTk5GTkZCTl5qampeXmp+jo6Kfn6WjoaWhgJmbmpydnJyYmJWdnJ+dnZ+gnZ+enZ6foaKhpKGgn5qan5uVkJGSj4+Ql5OTlpaWm5KZnqCtqKOprKuusKehp6yut7OmrqWmrqedmJOPjoyLkIKJhoOGkImJi4uMioqIhIOHi4uBg4CBgoKEiIGBgIR7gYKEgIGEg4SGio2JjYqQgImJjI2IhYmEhoOJhomFhYKJhoWEh4iGiIKEg4GCgYSEhYODgYODhYKAhICEf4CBgYGAgH+Ag4F/fYCFgoSFg3+Bg4SBgoR+g4OEgoOEg4WFhoaGg4GDg4GEhIeGhoiHiIaJioaCh4uQjpCMjYuKh4yRjpGH1oKFx5qAhYLok/WigKav2+Hj09q+4uDZ1oLMdXN2eHd3dnRzc+bl5OHf3+De3t7d3Nzb29va2djX19jV1dXX19fY2dna29vc3Nzb2t/f4eLi5ebq73t883t7gISFho+Ok5SUjoV+8/Pn5+Xk5erug/Dm6Ofm5ePj5ufo5+vr7/R8g4qRmZ6fpquvsaysc7KwtLK2urawraqvtbWnlpCPjouOjZKUlZKWlpiiqKi0wLu4tLS4YGVqbmplvbqsn6Wqm4+HgoGDgYSBg4qHhoeMlJSSkZmfnZmTipSWm67HxsjJzM7R09DQ0M/Q0dLS0tDOzMvLysrLysnJyMjIycrIyMmEyyLKycvMzc/Q0M/Q0NHS0dHR09LU1tbV19fX2Nna29vc3t7fhOEL4OLh4eLi4+Pk5HKEc4R0gHV2dnZ1c3Fv3N3ecHBwcXBv291u0M7Q09jTysbIxcjFyM/NysrLzdDNzc7R23Ryc3V0cnN1dHd9e3t/d3jp4ePn4+Hg4OXk5ebodXd5e31+foB/gYKFhoeLjI6OjY2PjI2Oj4+QkJCSkpOUkpGSk5STkZGPjYuJiIeIiImLiYyNgI2Pj42MiomHhYSEh4uOkpOUk5GHgISKjo6Mg/78/P6AhISJi4qHhv/07ujl5ePi4uPl5ePl84CB+vf08fDu6uno5+bi5OTj5OTk4uHg4ODf39/h4+bq7e7t5N3Z1tvZ297d4OHj6Ovs7/H2fX9+foKDg4GAfH6EhoqLiY2QlZeWIpSRk5WOiImOjIyIhoSCf4B+fXt7fHx8fX6Ag4aJi46RlJeEmYWYgJeYl5iYmpucnZyb75GajIaEg4Lv09/ejZmIe4i9naSmftHYfc3x3fTy5IeKknJxdXd5etB6enp7fH6AcoGBgYCAf6jRp727xcKDwr6w0rSp7dbX9cyNtcqwqI7Ro8PR6qfH0LW6ycLCs6+8y3F0dHJzb3Fzbuy0pvXy836GhoqKJY2LkY+OjI2MiYyMjYuLiIqHiIyQj4+OjY6Qk5eYl5OUmJaUmZYWgIKEg4SCgX5+eYB/g4CAgIOAgH+AgIWCgIGBgH19gX15dnd4dHVzeXh4e3l4fHV5e36Fgn2BgoCEhnx6f4CCiIR9gXx+hoF9fHp5eXp5fXR2dXN1fXh4enl5d3l1dXV3eXtydW9xc3R1eHJyc3ZtcnR4cnJ1c3Z3eXt3fXp+eXl5e3l3eXd5dHd5enZ4dnl4eXh5e3l5dnp3dnZ4dXl5eXh5eXd4eHZ3eHV4dXZ2dnd3eHd3eXd1dnd6eXp6eHd2eHl3eXt5e3x8e3x8fH1/fn5+fXt7fXt/f4GAgIKBgH+Bg314gYeJiYqFhoWEgoOJiIh7sFhifYJ6f3jVb66NmqHJz9LIz7XT1cvGd6RcW12EXhVdXV68u7m5uLi3ubq4ub29vr27u7yFuwK9vIW9ML+/wcLDw8TDwsXHyMjJycjMz2pq02tqbnJycXZ2enp5dW9r1NTOy8rJy87Rb9LMzoTNUszP0NHR0NLV2G1xdXh/goSMj4+OjI6QkJGSlJmSkJCOj5GRh397e3p4eHd6fHx6fn6AiIuIj5WQjIeCfz9AQUJBQH59eXd5enh2dXFvcnByb3GEdB9zdXR2dHV3dnRvbnN4fomWlpiZmpubnJubmpmZmZuahZkQmpqal5eXlpaWmJiYl5iYmIWXDpiYmJaXl5iYmZqZmZiYhZkcmpuanJubm5ydnp2dnqCfnp+goaGgoJ+hoKCfT4lQP1FQUFFQT0+cnZ5PTk9PT06bnE6ZmpqbnpuVlZaVlpeYmpmWlpaZm5mampqgUlFRU1FSU1RUVFdXWFpWV6qnpoSoHqenqaqoqFZWV1dYWVpaWltcXV1cXmBhYWBhYmFgYYRiG2NjZGRkZWVkZGRjZGNkY2NhYWBgYGFiYWJjY4ZkPGNiYWFkZ2hrbW5ubGZiZWlra2pmycbFyWRnZ2psbGtpy8bBv8G/vr++vr/AwcHIZ2jOy8fFx8bCwsDAvoS9S7y8u7m5uLe1tLOztLS3t7e5t7OxsbGvrrCysLKztba3uLm3u19gYF9iY2JhYF9eYWNlZ2Zoam1ubWxrbG1pZWZoZ2dkY2JgXl5eXIRbEFpbXFtdXl9hY2RnZ2hpamqFaWxoaGdoaGhpa2traqdoeHFmYWBqzra7u3iGbWRqjWVqalKcrWWlx7bGw69qaWxEP0BBQ0R1RERFRkdHSEFJSklJSktmsI2soq+5c6ixjMOmltDRxeDCf5a6qZ6AzJ2zxeOgvcWnr7y1s6KeqqmEUzVUUlNUUMJ8eNDS0253d3t7fX2CgIB/gH5+f35+fH13e3h4e3x8fn57fHyAhYOBfX6CgH2Aff9/9X8Lfn5+fX5/f39+fn2NfoJ/ioCwfwOAgH+OgIl/AYCQf7OAhoH7gJGBg4CGgQOAgIGZgJCBjYDRgYSAiIGPgIKBs4DLgYKAhn+EfoZ/hIEDf35/hn4Ef39/gYWCAYGOggGBh34BfYV+AX2bfol/Bn5/f35+fqp/AgIEAICgnqSgqqemsaqtrKqsqq6mrK60t6+qsLC0srCpoqOorKurpqamqayoqKKZpameqauwt7mvt7a+wrK+vL2+s7O4vLq6vs/EwLuzs6OhnpWYmJyfm5OYlJKYkZaSjZWPk4qWlJORjYyNjJWQjJCJi4eHiY+GjpaHi4aIj5aVkZiUllyVmpKQjoqPnJuLk4+Mj5CNjIyIi4iMi42GjI+JiYyIhYiC/oOGh42Ii4SFhoeEi4L+/Oj3goGEhISDg4KEg4iDhoaKiYeFh4SEhoiGhfuAhIWDiYyFioqHiYqKh4SIWIqKiYuNiYyPjYyPkJOWlpaZmJiVkvrQj7C2lZSP5JO/nZKah9Wp063T1M7P2vfY0dTm7v2BgYD+/Pz6+Pb08+7v8O7u8O7r6+ro5uXl4+Lj4+Pk4+Tk5eSE44Dk5OXo6enp6+3x+f6BgoeIipGUl52bnKCdoZ+bmZeVlJaUlZaXmaGipKaknpmZmJeXmp6jo6Wprba3t66srK2vtLWzuLe6uLi6u7/BwL/Hys7Mz9PW2Nrc3d3c29nX1tbU19jPysrIxs/c9YmVq7qmjvXl4dvoyKmTi4iKg/+E/jyFi4qJl6eivtSAgeLct6XZq8CusLm6vsDBxMfIzM/Q0tTX2NjX1tjX19XV1dbW1dTT09XV1dbY2trX29uE3CDd3t7f3ODj4+Lj4+Pk5+vs7O3u8fL09fHx9PX3+vz9/4X+Bv/9/P2AgIWBDYKDg4OFhYaHh4iIiImHigqJiYqLi4uKi42NhY5YjIyMjY6Oj4+PkJKRk5WYmp6ipqemp6iqp6SgoJyXlJOUlZKSk5KSk5ORj4+NjIyNi4qJiIeGh4eIioqLj5GSkJCUmJ6cnpuXlpaapKagn5ubmZiclJCGg4SBgIiOjI6RjI2MiomJg4P/gYKBgP/+/v+A+veAhIKDgYPz9/n+8/Tz5/J6d9vS1NbW0tHV0dHW3+Pi4ePj4d7U0NPV1tLS09LS1dbX2Nrb3N3f4uHh4ODi5urt7/P19fP3+/fy7uvp5Obu7ujl5ebq7vX49vv9goOBgoKGhoWGhomLKY2OkJGQjI2Lh4eFg4OGh4SFg4SEhIOCgoKEhYWHiY6TlpeXmZ2cnp+fhaCAn5+ejPP2+svSp6utrMKWko6UlZSG/N6A/oaNj4SRnq3NzJnh5P3L7+D++vqblqH6+4CAh4nrjY6Lj42Bh+uFh4mNjo24wLHcvL+slZbbuc3Ay63V2tzNzsrItL/S1rygqL6Oqc2zi7HL4bilr+WGiouNi42PkZGNjpKRkJCSjYsnjZKOi4yMjYqNi5CQjIyPkI+TlpGOj5mVl5ibmpudo5+loqOqo6OrgJOTlZCcmZiemZ2cnJybnZWanKKkn5mdnaKgnZmUlJaampeTk5GUl5OTjYiQk4yWl5qcoJugnaWpm6Sjpqabm5+koZ+itKmnpaCgkpORio2NkJCNiIuJiI2FioaEioWIgYuHiIeDgoOCiYWDhoCEf4CAhn+Fi3+Ef4CGjIqJi4mLgIuOiIaEgoaRjoKIhoOHiYaFhYOEgYSEh4GEhoKEiYKAg376gICChoOFgICDg3+Gfvjz4+9+fYGCgIB/foGAhIGBgoWDg4GDf3+CgoKB8Ht/gYGEhoGFhYOGhYWEhIOEhIWGhYeKiYiKiImLjI2RkpKUk5SQi+O7jr67kI+J2ZfUgJ2EjX/HotSx19XT1eL2wsC/ztXjcnJy5OPi4N/e3d3Z293b2trb2tjX1tbU09TU09PT0tTV09PU1dbV1tfX19rb293f4OLt8nt/g4KFjJCUlZeYmZmcnJuYlJKQlpWTkpOXn6CgoqGdlZKQkZOVmqGio6anq6yrqamprK+xs7S1gLa2sbK1t7m2tra6vcLDwcTFxcfHyMfGx8XFxMHAwsO8tbSwqq6wuWNpc3txZbSrpqSrm4yDf3x8eet57nl9fnyDiYSQnVpcp6KPhaCHkZGkrq6xtLe5vL6/wcLDw8bGx8jHxsXGxcXGxsXEw8LCxMXFxcjJycnKycrKy8nKy8vLEs3Ozs7Q0NDR0dLV1tfY2dvb24TcE93f4OLj4+Ll5uXl5OPl5HFxc3OHdAh1dXV2dnd3d4Z4AXeGeIB5ent7fHx7fHt8e3t7fH19fX9/f4GBg4SHiY2OkJGRkZKSkpGOj42Kh4WHiIeHh4aHiYiFhISDg4KDgX9+fXx8fH18fX1/gIGBgIGDg4aGiIeHh4aHjYyKioeJiomKhYJ8eXZ1dnZ5fH1+gYCAf35+f3x783p7e3z3+fv5fvbudlp5d3p6e+7v8fj5/Pvz/4SA6eDh5+ni4uXd3+fu9fX2+Pnz7+Dc3uPj393d3t7g4OHi4+Lh4uLi4+Lh4eHk5+nr7O3u7fHy7+zp5ubk5O3p5uXh4OPn7vPx9viFfjCBgIGAgoaIi4yNjY2IiYiFhIB9fX+Bfn99fn58fHx7e3x8fX6AhIaIiouNkZKUlpeEmICXl5eWhubr7cHFnKChobeRjIeCgICF/d2B+3+FhHyGj5q0tIjT1vK74tP76OKQho7b125uc3TJd3p3e3tze9d6e3t9f36muKjewcSnjo/dtc++yq3Z3+LP0MzNtcHT2L2hp6qJqtOxiLfR47Kbp9p1eHx/gIOFiIqJioyNiouLiCiGiI2JhYaHhoWFhoaKiIiIh4aKjomFiI+Lj46Qj5CTmJSYmZiel5eagH59fnuEgIKEgIGDgoGAg3x/gYeIgXyCgISEgXx5eHx/f315end4e3p4cm13enp5ent9f3p/fIGBd3x8gX52dnuAfnt9hoKBf35/eXt6d3p9f358eXl4eHx1enZ0end6cXh3d3h1c3Jyend2eHF2cXVyd3N2fHN1cHV5ent4eHp9gHx+eHd1c3Z/fnV4eHZ2enp5eXd3dXh4eXZ4eHd4e3h3dnXodHZ4eHd6dnV4eXR7dOnf0d5xc3Z3d3Z0dnZ3d3d2dnx6enh4dnd6e3d54HN2dXZ7fXl8e3t9fnx9fHx8fn6Af4GCgYGBgIGCgoaKiYmMi4uHhMGadH6fiImExm+HLn50e3S0kcunycrGyNTfnZmepqq0W1tbuLi3t7e1tba4uLe3urq6t7e3uLm4t7iFuUC4ubu8u7q7u7y9vLy+v8LCw8XFzM5oam5tcHN0d3h4d3h4enp6eXh2dHd2dXZ2eX1+f39+fHd1dXZ0d3x/f4CAhIEPg4OGiImJi4uMi4yIiI2NhooFjZCSkJCEkQOQkZGEkGCPkJCNiomGgoB/ez4+QEE/PXRxcXJ0b2psbmxsac1qymdsamdnaGJmajk4bmxmYWhgZW9/h4mLiomLjI2Ojo2PkJCRkpGRkZKRkpGPkJGQkJGSkpGSk5OSk5OTkpKSlJSEkyiSlJaVlJWWlZSVlpaZmpmZmZqbmZucm52enZ6gn5+enp6fnp6fUE9OhE8ETk9PToRPD1BQT09PUFBQT1BQUE9QUIRRCVJSUlNSUlJTUoRTEVRUVVRUVVVWV1hZWFlaW1tbiFwhWlpbW1pbW1tcXFxbWlpZWVlaWVlYWFdXV1ZWV1dYWVhZhFgFWlpZWluEXBddXF1cXV1cXl5cW1lYVlZWV1hZWVtbXIZbRrdcXFxdu76+vV+8uFlZWFpaXLa3t7q/wMO+xGZlvLa3ury4tbq3t7vCxMbHycnGwby0trm6trW2t7a2t7i4t7e4tra1tLOEsjiztLS2trW1t7e3tbSzsrKzuba0tLGwsrK2uLi6vF9gXl5eYGBhYWFjZWZmZ2dmZGRkYWFfXVxdX4VcFVtbWllZWVpbW1tcXV5fX19iY2VmZoZngGZlW6Kio4aHaWpqaYtycG5jX19y3b1t12tybmlpbFprbFSYq8eUtarSubF3ZWqCeT09Pz9wQUJCREVCR39HSEdGR0hgmIvVoqmbf4LYose2wKXU2dzHyMPFrLnN1beVmJJ9m8qleJm0zZ2EkbteYWZrb3J1eXx7fH6Bf39+fHp7Jn98ent6e3Z3d3V8e3h5eXh7fXhzeH55fnx+fX1+g3+CgoOIg4KE/3+jfwF+jX+Efpl/AX6nfw9+fn59fn9/f35+fX5/f3+KfoZ/g4Cxf9+AhoGMgAN/gH+JgIKB3oD/gZSBAYCEgYSAA4GAgIaBiYCCgcqAvoGFgISBiH8Efn5/foZ/hIEBf4h+BX9/f4CBhIIBgYeCAYGGggGBqX65fwICBAAVp6ShnaSmo6yon6GhqqWsraaiqKGhhaKAoaipo6Ggm56dnJujpqChmaSdnqqtpKyqsayysLC3vbq5va+trbO4tLu/ubu8sKqqpZ2Xl5WWlpaVlJCLjIyYmpaPk5WSkpeVk4+MjZSQkJOOkouGh4+QjIqLiouLkpCMkpiQmZiXk42PkJOWlpCQjo6Uk4+Uj4+QjI2MjZaKkI5Fio2OiIyGiI2Lh4SEhYCBioSFgoKIgfqDgIGEg4SGhYSA/4D9goKGgIKFgYSHhYWEhoWDgoSEiYSFhIaHjYWIiomJhoSGhItRjo2Ki42Lj4yJgIaIiYiNjo+QkpGPjoSMjYyGxpJ90IGRhrOepdewqbnQiMTW73+Ntt/g5vb2+Pj29fPx7ezq6+ro6Ojm4uPi4eHi4OHg4OHihOGA4+Tj5OXm5ufj6+bv9Pjy6+fo5+vw8vmA/fv7gIGEiIyQjo2Rlpqepaenp6msr7Csqauqra6vrqmnqKisrq2tq6uqq662uLS0tbvBwbm5vLq9vsDBvru7wL29vr69v8DAwsG9vcHAwsHDv73DxtjshKC4wpnx2Nv4hIC6kIWChpUwpuv28/L3/YKFpqPahYiPg/e1j+SWvJycnpydqsnT1tra297d29zc3Nna29nZ2NbZhdgU19jW1tfb3N7c3N3f4eTj4+Pk5OKF4Rzg4uTl5ufm6Ort7u/v8PDy9Pb4+Pv7+vr8/f79hPwX/4CAgYGCgoODg4SEhYaGh4mIiYmKiYmEiAyHh4eIiIiHiImIh4eEiB+JiYuMjI6PkZKSk5SVlZWUlZeZmZucm5ydnZyZlpSUhZMVkpKTlJOTkpKSkI+Ojo+Pjo6QkJCRhJCAkZCOjo+OjY2NjIuJioqIhoeHhoSEgoKDgoCBgoCAg4WJi4uLiYaHhoKA+fTs6ejc39zTz9DV1dfY1djTzs/R09XZ29fW2dnSzcrGw7/Bw8LBwcC+vr69vr2/w8bHyMzQ09PU1NbZ2dvc3eDi5efl5OPj5eru8fL2+vz9/oGB//mA8+3t7Obl4+Di4+fr8fT3+/z+goSEhYeIiYmJhoWFhYiKiYyMi4yNi4uJiYeEhIKEhIOEg4SEhYeJio2SlpeXl5mbm52foKGgoaGgn5uM0pKBhoaJoLG0tMCSkIqRkpD0geeGgYOEjoePlaq93JPs2/bL6dfq3Nf0gIehoImhnedyqamA3+yA9ILmhoiLiYnCvdTO0cvKz7aguL3VwZnyrLrWrt3rguum0NGT0+aAsNTz6OqFiYqJiIWEh4iLi4eGi4qIjIyJhoiFgYKNiIWLjIuKi46IjJCMj5GIkI+Pj5qWl5WblZidoZ6doJ6joZ2mop6ngJmZlZCXmJWemJGSlJqWm5yVk5iRkpOSkpOSkZeZk5OSi46Ni4qQk4+Mh4+JjZibk5eYnJugnZyjpqSjp5qamZ6ioaaqoaSnnZmblpGKjIqKjIqJi4eCgoKKjouFhoqFhoqJh4WCg4mFhYeDhoF+foaGhICDgYKDiIaCiIyJjo2KX4mEhoaIiomFiYWFiYqHioaJiYWFhIaNhIiGgoSIg4eChIWGg4CAgH1/hICBfX6CffZ+e32Bf4CAgoJ7+n35gICEf3+AfH+BgYF/gH99foCAgn5/f4GChoCChYSEgoGDhIVAh4eFiIiGioiEeYKGh4SJiomLjY2Min+JiYmGwJWBz3+Kg6mUoeGyp7/Ve8DY8oGJrMnIz97d3dza2dnY19fW1oTUYdPS0tHR0NHR0dDPz9DQz9HS0tPU1tbX19jV2tnh4+Ti4N7f3+Hk5ep47uzweXp/hoqOi4uOkpKTmJmam52goaCdnJ6cnJ6fn52bnJqcnZucnZ+goaOnqKelpqmrq6enqKeEqQaqq6urqaqErEmtrK2sqKerqKuqqqejo6Oqs2FteH1or6Cgr1tbkn13dXd/htnj397i6XV1hH+eW1xgWaqGc510hnyKkI+Rm7e8v8LDxMXGw8TEhMOEwkTExMPCw8PDwsLDw8XGx8XGyMnLy8vMy8vLzMvKy83MzMvNzs/O0NHR0tTU09XX2NjZ2tvc3Nzd4N/f3+Hg39/gcHFycoVzB3R1dHV2dneIdh91dXZ2dXV2dnZ3d3d2dnZ3dnZ3eHp6e319fn+AgYKDhIQQhYeIiImKiouLioiHhoaGhYSGh4cMhoaFhYSEhIWFhYSEioVehIOCgoGAgIB/fn18e3t6enp5eXh3dnd2dnh6e31/f318fX16evDq4tvb2dva0MvJysrLzMvKx8bGxMjKzNDR09na2NXTysjFxsjFw8XFxsfHxcbFxsrNztHV19rb3YTfgODh4uHj4+Tj4uLh4uTm6Orr7fDx8Xl69O/o5Ofk4+Ti397e4OLo7e/z9vl+goGEhoeIiYqHhISEhoeHiIiHiYqHh4WEgn99fH19fHx8e3t9fn+Bg4WIiouOkJSWmZqampmYl5aVkYLFj358gIKUo6WmsouJhIF+f+SD5oeBfn6FgH2Fh5unxYbazO683cvkzcHbdXmRkHySkNWcnHfP13Ted9J4ent7e7GvzsjOzMnMtKG1vdfDluSqudiv2/GEwZK/1ZDk+IKqz+/e44WJh4SFhIKEhoeGhYKHhoOHiIWChYJ+f4eDgoaFh4SChoKFioiKiYOIh4mIj42PjJGLjpKVC5SUlZSXlpKYlZOagISDgHyBgH6FgXt8fYJ+hIJ9e4F6eXp6eXl6fIB/e3t6dXd1dHF2eXZzbXRyc3x+enl5fnl9e32Bg4F/hHp5eX1/f4GGgYGHgHt9e3p1eHd5enp4eXVxcnF7enh1dXZ1dXp2dnVxcnZ0dnl1eHJxcXZ4eHR1dnN0d3d0eX17fXh5gHh3d3d5fXt3enl4eX18enh6fHh6eHh8eHp5d3h5eHp3eHx5eHd2eHR1enZ4dHV4deR0cXN2dXZ2dXVw5HHleHd6cnR2dHd1dnh2dnV1dXZ2eHR1dXd3eXZ4eXh7eHh5e3x+fH6Af39/foGAe218foB/goODhoaHhoJ5hISFgqh2MWTEeIJ8mYKVy6aetMpmr8rkdnWOpKOpsrS0tbS0tbO0s7O0tbW1tLW2tbW0tLa2tbeEuC25urm5urm7vLy9vbq/usHCxMXDwcTDxsfJzGbMzsxmZmpsb3Jxb3Fzc3Fyc3OEdgt3d3Z1dnV0dnh3d4R2C3V2dnZ4ent8fXx+hH1ZfH19fH19f39+fn6Af39/gH9/gYGCgoCAgH5/gIB+e3p5eng8PT5APHBraGw4OGllZGVmZma/xsHBw8JgW2FbZDQzNTJjXFJZUFRabHd4eX6HiYqMjIuMjY2JjASNjYuLhYyEjYSOOY+QkZGRj5CQkI+PkI6Pj4+RkJCSkpGRkpGRk5STlJWUlZaWmZmYl5iYmZmYmJqamUxMTU1OTk5NTYROhE0ETk5OT4dOAk9OhU+IUCBRUFFRUlJTU1RUVFVVVVZWV1hYV1hYWVlaWllYWVlYWIZZh1oBW4lahFsOWltcWlpbWltbWlpaWVmEWARZWFdYhFcNWFhXWFZXVldYWlpZWYRagLGwramoqq2rp6OipKOioJ6fnpyenZ+goqOlpqyur6urp6Wjo6SkpKWmpaWmp6ilp6eoqqussLKxtLGxsrOzsrO0tLSzsbCvr6+wr7GxsrOztLRaWrW1srGzsK+wraurq62wsLS2tre5XmBgYWNkZWZmZGJiYmNjYWJiYWJiYWFfBF5dXFuEWoZZgFpaW1xdXl9hYmRlZmdnZ2ZlZWRjX1eHd2xqbVtfZ2dniHBva2FeXrpyyHhyamxxaW5pZGp7U6Cmx5axpLujlKVZXV1VSlhXe19eRXl7Qn9Dd0REQ0REaHGzsb+9usOsmK210ruLzZynyKzS5Xi3aZnQh8fhfZe7387Ren18eXt5L3p7e3x8enh+e3h7fHt3e3dzdnx3dHl4eXZ0enZ3fXt/e3N4eHx6gYCAfoN8f4KEhYEGgIGFg4CD/3+xfwF+in8Dfn9+wH+EfoN/iH4Ef35+fr5/BIB/f3/UgIWBhICCgYeAhn+FgISB34D/gZKB0oCCgZSAvIEBgIR/AYCEgYd/A35/foh/BYCAgIF/iX4Gf3+AgYGAhYEGgIGCgYKBhYICgYCNfgF9hH4EfX1+fYZ+AX+FfsB/AgIEAICZmJigmZqmoZ2opp6VmqKjop2en6SgoaWjo6Wmn6ejp5+coKyvr5ufm5qfoqCmmJ2tr6+5r62tt7+8ure+tbiyoaikq7Csq6aioaGnoJ6coJaXl5OUlpSUl5qUkp6clqCPjo6WlIyTnJSQlJWMj4iDkouUipaKiJCNkJmRjZWSloCYnZuUl4+SjY6Nho+TiY6FjoyLiYuNjYqKiI6DiJOTioaHkY2IkIaEiIiCg4mIiYGDiYSKhIuHhYGBhYP+hIWEgoGA/4CGg4OFhIKFhYWEiIeGiYeHhYeGhYaGhYaJhIeIiYiLioiGiIuNj42Qj4yKjYuKkZCNjY6PiY2LiYyMg0T6w+nJr7mev6Sas8TGpZ7PzuHU4oOBtPXy8vHs7u3q4+Po5uTi4uHf39/d3Nzd3t/c3d7e39/f4ePi4unr8fDx9fb7/oaAMf6Eh4mG/P2A/f37/oOFiIiLkJGUnaGioZ+foaGjop2ampmjpqimoqCenp+goqOlpqaEp4SfgKCgn6CgoaOhnp6eoKGgoKChoaOjoqOhoaGjo6Sor7G0u+WYkZCen6qMu5aYzM7NraiOo5mAh+zh6OXe6POQor7K9ena6Nmhpcajq4+msJyZl6C9ztDS1NbW2dre3t7f397e3dzb3Nvb29rZ19jY1tna2NfX1s3W2dvc3eDh3+DfPeDh4eLj5ebn6Ojo6ezu7+7w8PH09fb29/j4+fr6/Pv8/Pz9/oCAgIGBgoKDgoSGhoeHh4iIiImJiYiGh4eJhiiHhoaFhIWGh4aHiYqKjI2Oj5KTlJSTk5OUk5STkpOSk5SUk5KRkJCRhJCEjwuOjY2MjI2OjY2Oj4WODo+Ojo2MjIqKiYmKiYmJhYiAh4eHiIiIh4WFhYSEg4KEiIqPkZKTkY6MjIuHhP/4+fnz6+ba083HxsTAvry+vby8vr/DxMTCv73AvL3AxsTBx8vNzc7Pzs3Nzs7Pz83P0NHP0NDT09TV19fZ3d3e4ePl4+Hf3+Di5ufn5ufj397j5ODk6O7y9/X2+/yAgYKDhIQchoeHh4iJiYqMi4uKiomJiIiHiIqLiIWCgoKBgIWCFYOFhoeJioyOkpWXmp+ioZ+fn56bmYSagJycnIHYlYuIh/mMkpixtLG8kY6Kj4+J7NXD6+r/+4mEjomRk+Tt1MrawMq3squmsbnAt/D735SBlb6rwfa89IesjY+OjP6igKTyt6S1rYPDq6zCoIiQwMm6lpi0v7/wn4eFiYmGhoeGhYmHhYSGh4iEhYCGhYSHhoSIgoWEhoaEKoeKhYOEiYaFhYWMkoeGjYuPj4qMkJaSnJWOjpGVjZeYlZGSkpWco6CWkoCOjo2Rjo6Xk46YlpCIjJSWlI6PkZSRlJWSk5SXkZaTmI+MjpmbnYqPiIaNkZCSiI2ampqlnJmbpayop6Ooo6agkZWPmJ6Xl5WTkZOZkpGRlYyMi4iKi4uNjI6KiJGOi5SFhIOLi4KIj4qHioqDh4F9ioOKg4uBf4WEho2HhIyHioCMkI+LjYaHhIiDgIiLgoV/h4WEg4SHhoaDg4d8gouKhYGBiYiDiYKAg4N+fYODg32AhH+EgIOBgH1+gX/3f39/fH19+XuCf39/fn2AgYCAgoF+goKCgIKAgIGAgYCDgICBhIKDhIODgYSGiYaJiYiGiYWFiYmGiIqJhYmHhoqJgYD3wuzNucarwqagtcnMi4fU0+fZ5oF/p9XV2NfW1tTTz87Qz83Ozs/OzM7Nzc7Ny83NzczNzc3P0NHS09jZ3d/h4uLp7Hh3d3Z2d+55e3x77O547+3w8nx+gICDhoeJj5KSk5CQkZCPjoyKiYiPkpORj4+OjY6PkJGTlZSVlZaVkgORk5SEkw6Sk5ORkpKTlJSTk5WUlYSURpGOj46OjpCSlZibrmhkYmlpbV+Pe3iRl5WGhXeCfHF11M3Sz8zS2Hd+i5Gro5mdlHl7jHh6bnmBfoOGkKm0uLm6uru9vsGIwjvAwcDAwMLCwMHBv8DBwMDAvra+wMLDxMXExMbGyMjIysnKzMvNzMzNz8/Q0dLS09TU1dbX19jY2tna24TcCttub29wcHFxcXKEcwp0dHV1dHV1dXR0hnMEdHR0c4R0E3NzdHR1dXZ3eHl6e3x9fn5+gICEgYWCBIOEg4ONggKBgoiBioIGgYGAgIB/kH6AfX18e3t7ent9foCChIWEg4KCgH589O/x8O7l3tTPyMHBv7y8u7m4uLi5ubq8v8C/vsG/v8PJxcPJzcvM0NLR09XV1NXW1tjY2dva2dvb3Nzc3d7f39/h4eDf3tvb293e3+Dg4NvW1drc2N3g5evu7e/y83t7fH6AgYODg4SFhoaAiYqJioqJioiGhYSEhYaDf31+fHt6ent6ent7fHx9f4CCg4eJi4+Ul5eXmZmYl5aVlJOTk5KRd8uQhYOB84SHjKCkpLSMh4N/fHrj0rvq4vPtgXqGgISE1t3PxNi4wbKup6Cptbiv4eLEhHSGp52w4KPTdJV3eHh32qB2m+WzobZdrHqxqq3EnH2GwcvAlZSuubbdkYOBh4aChIWCgoWDgYGDhISBg36BgoKEgIGEgoSBhoOAhIiDgoCFg4J/gYeMgoGHiYqJhIWIj4uTjYWFiY2Fi4+Kh4iJjJOWk4yIgHp8eX16eoF8d4KCe3R3fX9+dnl5fHp9fXh8fn96f31/eXZ2f4KDdHducXZ5eHpydH19fYN+enmCiYaFgIWBg390d3J8fHl6e3p5eX58fH6Cenp5dnh7eXt7fXl4fHx3gHZ0dXt4c3d8enV5eXR3c3B5dHl1eHJweXd1enl3eXV6bHx9fHl/d3p4fHdze313eHN4eXh4d3l6e3Z3eW90e317d3d6fHd5eHJ5dnR1eHp4c3Z5dnd2d3d2dHR1deV0dXVzdHPmc3h0dHV1dHR1dXN1dHR4d3d0dnd3dXV3eHh2d3d5eHl5d3d3eXt9fIR+M4B9f4OAf4CCg4GDgoCFgn3vsuG7o7SUs4+JrL3DZ2fFxdfN2Xh1j66vr6+ur6+ura6wsoSwBrGxsrKxsYSyBLS0tbWEtoC5uru8vb2+wcPHxmNjZGRlZclkZWdnyMxlx8zHzGdnaGhoamtrbW1ubGtsa2tsbWxsa2ttbm9tbW5tbG1tbm5vcHFwcHFxb3FwcXFycXJxc3N0dHRzdHV1dnV1dHZ2dXV1c3JxcXBydHNzdHY8Ojg6OTk2Yl1ZXl5gYF9fYmBdXiG1tLa0rrC1WlpcXmJeWllXUU9STU5OUFdganB0foODhISEhguIh4eIiIiJiYmHiISJDYqJioqJi4mIiYqIhIiFiS6KiYqLiouLjIyLjIyNj4+Oj4+QkJGQj4+RkJCSkpOTlJOUlZSUlZWUSktKS0tKhUuKTAdLS0xMS0xLh0yETYVOB01OT09QUVGFUgZTU1RTU1OEVANVVlaGVRhWVlZXVlZWVVZWVldWVldXWFhXV1hXV1eEWAlXV1dYV1hYV1ePWFdXWFlYWFlZWVpbWlpaW1paWlmwsLGxsK2qpKOgnp2bnJmYlpSUlpeWmJianJ2dn56eoaSioaKipaalqKiqqqytra6uq62ura6vr62trq6wrq+vsK6usKyFqiKrraurq6mmpqipqKqrra6wsrKzsVlaWltcXV5dXl9gYWFjhmUPZGJhYF9gYF5cW1paWVlZiVhVWVlZW1tcXV9iYmNjZGRkY2JiYWBhYF9Pmndwb27PbnFfYmNkiG9tamBcXcC3oMvBzMdoYnRlZmWVjJWftJebj42FgIiYmoy0qopgTlpzZ2yBWnI+UoQ+ZXSSaoO7nYejn2qYn5K3i2lwsLSof4Sbp6m9dXl3e315eXp4eXp5dXZ5eXt4enR2enl5dXV5dXZ3end2eHt2d3N4dXVzc3l/d3R4e317dXd2fn2EgHh3enx2fH54dHV5eH+Df3t2/3+9fwF+hn8Bfrx/jX6Cf4V+sH+GgAF/hIADf3+AhH/PgIeBjYCHf+eA/4GSgd6AxIGFfwd+f3+AgYGBh3+Hfod/AoB/j34Lf4CAfn5+f4GBgoGEggWBf39+fYV+AX2Pfst/AgIEAICblZiYmpyVm52ZmZ2ampiVnp+gn6Ojn5qapaKemJ+lnp6epKObm5WgpaSpl6mnpJ+po52ypaCruKigqbOrnrKsoJ6pqbKqkZ2draKtpqqmq6CWpaeJjY6bk6ecioeRj5ijnZaXl52Pi42SlJGJj5D/iI2Vl5KYlouRlKGYlI2YoYCXlZWckY6OjpKQkJiDi5GQipKFioeQj4mMjY2FkYqMkIuGi4aDhoaLjJSNio2Kg4mEg/39g4GCgYWFh4WD+fbvhIKGhIKAhYCFhIP+hIWKg4SDh4aDh4mFhoaFhIaHh4eGhoiKh4aNi4mJiY6Mi4yMj4uLkY2MkpGSj4qQkYOMhID0gYf987OdnOF7mcS/qZK2qr7G2ejk6cvk5urn5eXk5Nzi3dvc3Nzb3d3d29rb2dfX2Nna2dvb3uLo8Pb4+fv8/Pv6+fv++/6CgoKEh4iEhYSDhISCg4aHh4iIioqNjo+OjpCUlJOVlJSVlpSUlJaXlpWWlZORjo6Mi4uIh4aGhRiFhYaHh4eIh4eJiImKjI2Ojo6PkI+Qj5CEkoCWl5miuazI2ID01uX1/v7DkYOAk5OKjY/5ipuF49nHzc7Pzc/W2JKwwbqYpKiak4CKpImOo5aVjZCTm6Wttrm9wcPFx8/T1NTRy87R0tHR0tHS0tLQxMjV1dTT0MnGxNTW1NTW19jX2dvb29ze4ePj5ujp6+3u7/Dx8/Pz9ff39x35+fr7+fn6/P39/v+AgICBgoODhISGhoeHiIiJiISJBoeHhoaFhoWFAYaEhQqEhYaHh4eJiouNhY8LkJGQkI+QkJGRkJCGj4KOhY0BjIWLBoqKiYuLjISLCYqMiouKiomJiISHhIYIhYSFhYWEg4KEg12CgYCAgP+A//37+/38/fn58/Dv6+vs7Ovq6+jl4t/d2NTS09LOzM3Ix8XCv7+/wsPDwr++vL+/wMLCw8TEw8XFwr/CxcbIxsTExMPDwcPGyMrM0tLR0tTY2d3e3NmE2iLY19jZ1tjd3uTm6e/09PPx8fX5/oKEh4iIiIaIh4aHhoODhoQBgoWBgICAgYKDhIWJi4+WmZqbnp6foJ6ZlZCMkpaWl5aTkJSTkZKTk5SVlNOFy6qlppme+oyZqZmjo7uSi4uOjoTo58Ts2tj2g/fz28bCvLq8usbC2sDNzZ7J2Ly0+aqYoNTGpIqSn863l7mx9d21zr64qKfgmZLMjZqtu+Dj5/mFi4mKUIaHioeKioqIhImEgoaAgYOAgoKChYKBgoWDh4iFhYSFgoKB/YCHgoeHgoeIiYmGlIeNiZCJkIuGiJCLjZOTlZWNjfyPmJOQn5qXmpuZmpqUgI+Ljo6Pj4mQkY6OkY+OjImQkZGQlJWRjY+YlZKOkpaQkpGWkouNh5CWk5eHlpWQj5mQi6CVkJiklpCaoJiPn5uRkZaYoZqCj4+dlJ+ZnJeelo2YmX6AhZKKmY+CgYmGjJSSjY6Mj4SChoiJh4GHhvODhYmMh4uMgYeIk4yJhIuRWouKiY6IhIWFiIaFi3yCh4aDiH6DgYiIhIWFh3+Kg4aJhYCEgn+DgoWGioeFh4OAhIB9+PV/fn5/gIGCgIDx7+d9fYJ+fH1/fX+Afvl/gYN9fXyAgH6BgX+AgISBgH+BgICBgoGBhYODgoSHhISGhYmFhYeGh4qKi4uEiIl8hoHtfob88Lqnp+SGmsLEj3i1rMPJ2+vp7LrMztHSzc/P0MjMycfLysrJyMnLy8nJycjJycnKyMvMz9PY4OTp6Ofo6urr7Ovs7e13eHl6e3p5eXh5eXp7ent7e3x8fX5/gIGBgIGCg4ODhIOFhYSDg4KEhYSFhYOBgICBgX9/fn+AgIB/f4CBgICAf3+AgYODhIWFhoeIiIiGhYWHhoKAgYODhpCMmqFZrJyhqausj3RraHFxbXBy0XB5cM/Ftbm7vba4vb5xgYqFcnh6cW5maXVrbHVwcXl/hY2VnKOjqKqsH66ws7W0tbWtsra3t7i5ubq6ubitr7u8vLy4sKuturuEvCC+v77AwMHCw8TFx8fIycrLzM3Oz8/Q0NPT09TT1dbX1oXXFNjYbGxtbm5vcHBwcXFxcnNzdHR0hHMFcnFycXKHcRRwcXJxcnJyc3R1dnd4eXl6ent7fIR9hX6GfwF+hH+CfoZ9hn4BfYZ+hH2EfAp7e3x7enp7enp6iHlHeHl4eHbsduvq6Onq6erp6OTj4uHg3+De3dzb2tjV1NLPzMvJyMbDwb+8vLq4ubu8vr++vLu8vL7AwcLExcfJx8TCxMbIyciExzjIyMjLzMzO0dPU1dTU19fY19bV1NPU0NHQ0tDQ0tXY29/l6erp6ens8PV8foCEhIaEhYSEhIOBgISBCIKCgH19fXx8hHuAfH19gIGFjI+QkZKSkZCOi4iDf4SMjpCQjYqNjIyLi4qLiojCe7ymop6TmPCEkZ2PmZmxjYeFf3t55Oe/6trW7H/w6NDAvLi6u7jEvdG/0M+cxde3sfaljpK1rJOBipWxm4CgmM25l7C9qp+l7J+X0Ymcq73c1djrf4SDg3+DhIFMhISFhYKEgYCDfn+Afn6BgISAf4CDf4SGhYKBgn59fvt9gn6Egn2DhYWEgY6Ch4KIgoqHgoaMhYaKiY6MhITvh4+Lh5KNjpGRj4+Qi4B9ent8e3t4fXx6enx3eXh1fHt9fH5+fHd5gH59d3x+e3t7fHl3eXJ5e3t/c31+eXV7dnGBeXV8g3l1e4B7dYF+d3V6e4N/a3d4gn2Gg4iCh4N9h4hvbXaDfYiAdnV7dnmAgIB9fX91c3h6d3h1e3nYeHV4e3h7fHR3dn59d3R4fDV8e3Z9eXd4d3t5d3pzd3l5dnl0eHV5fHd5e3tzeHl6e3p3eHV0eHd4eHl7eXp3eHp3derrdoR0YnZ4cnPe19d0dHh0cnJ2dHZ1dOVzdXZycXB0dHN1dXV2dXV1dnZ1eHRzdHd1c3h3d3Z4enh5e3p9en1+fn6CgIKAe3+BdH5843uB9eKmkpDaZIS1tmdbqaS2uszd3+CaqKqshKs/rKqtramsrK2ur6+vrq6ur7CwsbGysrO0tLa6vL2/wMHCwsHEw8LExMVjY2NiY2NhYmJiYWJjZGNiY2RlZWNjhGQRZWVkZmZmZWVlZmdmZmdmZmWFZghlZ2hnZ2hoaIRpgGpqa2xsbGtqbG1vbm1ub25tbW5tbW1samlqaWpubWxsNmhjYmJgYlxWU09OT1JUValUWFitq6GjpaWgn56bUVRTUk1OTEtKSkpLS0xNTE5haW1wcnZ5eXt7fX5+f4CAgX93f4GBgoKDg4SFg4N/gISEhYaCgH18g4SEhIOEhIWFC4aGhoWGh4eIiouKhIsDjIyLhIwFjY2Ojo6FjwmQkJCPSEhJSEiGSYRKgkmOSgNJSkqES4hMAk1Ohk8FUFBQT1CGUYVSBVNTUlJSilMCVFOHVAFVhVQFVVVUVFSEVYJUh1WCVIZVYKpVq6uqqqmoqamoqKenpqempaWmpqalo6SkoqKhoJ+dm5uamJiXlpaWl5eYmpqamJqampudnZ6foaOhoKCgoaCipaSioKCioqOioqSko6SkpKanpaanpqWjpaSjoaKhooShE6KkpKirrKurq66vr1lbXFxdXV2FXg9dXl9gX2BfX11bXFtbW1qGWRpaWlxeX2BfX19eXVxZWFVUVltdXl5dW15eXYRcgFpZhFmAiIWEfYDQcnmAWV9fhXBsa19cYMXMose7tNFvxL2vnpeTkZWTopipnrKpgaW3m5LRhmtxh4ZrWlxhZVVIX1JrYFJkrZeDnvKTkMJ5lZy21sHD1HN6eXp2d3l2enp7e3h5eHZ3dXZ1cnV2d3p2dnh6dXp7enh3eHNzdedyK3VyeXVzeXp4dnV+dXx2enV7eXZ4e3h5fHt/e3R21HV9e3h+fHp9fn57fnrvfwF+wn+Cfol/g36LfwF+tH8Dfn9/in6Cf4h+sX/cgAGBj4AEf4CAgIp/5oD+gQKAge6AwYGDgIV/B35/f3+BgYGHf4d+AX+XfoN/hH4Ef4GBgISBAYCQfqd/AX6efwF+jX8CAgQAgJOUnYuZoJWUlpmXmpmUkJiYm5OSmp+boZqbmpadmZqUmJmam5+fnZuem56WoKaqoqKip66pn7espqe3o66amKSwq7a8qKShprHAyb7FxMHCtbq2p6Krucm/vLemmaKwt7G1qqydk5WWm6CUj5CfkYSDh/yNlZ6flJCKj5OKm5KVgI+Uj5WOjJGRmZqVioSSjo6NjYaDhIyIjoiQio2Ph46QhYyXjo6NjYyNkIqQi4GFkYiIiIeHioeFgoOEhoaEgf2Gh4SFgoeIgYKDg4KGiYWAhoKGiYyHhoeFhYmLh4aPiYqMioaKiYiKiYuFh4mJjIqOiIuLi4mLjIuOjo2Ni4mJJoSI/r7y6ebP5+DW2Iud1ri/u8PN2srdn8fP2tja3NfSztjY09TMhNWA1tTV1tbT1tXU1NXW19jZ3uHg3+Hl4d/g4N/h4N/g4uTj4+Hp6Onp7/T29/r9/vz9/Pz6+vn7/P7+gID+/vr5+/n29/j39vn5/Pv7+vr4+fj5/Pn4+fr2+Pn5/fr39vf4/v/8/f2AgoKGiYqLi4yPkpSdlpWXo7nCzMrDsqCjmaN+sIzb09Hi6ODQv8DC0OXdzLu5u7u7vcHEzdb8jZiGjZqPgpSZmYeojoiKh4aJj5OUk5aZnqavuNzv0b/DpcTGx8jJycvNzczAvcfOzc/P0NHQ0M/S0NLS1dXW2dnZ2tzd4OPm5ufp6+zs7fHx8/X19vb3+fj5+vn6+Pr7/P7/hIAwgYKCg4WGhYeIiIiJiIiJiIeHhoaFhISEg4ODhISDg4OCg4WFhYaHiYmLjI2OjY2MhI0Fjo+OjI2FjgqNjIyMi4uJiomJhIqAiYmJh4eJiYeHh4aGhoeGh4aGhISDhIOEhIOCgoKBgYGA//7+/Pz6+Pj39fTz8fDu7Ozs6ufl4+Pj4uHg3t/e3dvW2NTV1NTQzcvIyMjHyMjExMXEw8PCw8PBwL/Avr+/u7S1tLSztLGxsrS1tbW0s7Gys7SzsbO1tbO2ubi1s7eAubrAxNDV0dTW3OTo8/b39fT1+fr5+/v9////gIGBgoGA//z28fDv7/T0+fn19Pj4/f+AgIKChIWGh4mKjI+RkZKUlpiYmZqZlpDqkeHY/ejny+2Gmbf8homLjY356KSxpqWqk4eEhIWGh42l0pq8kY6Mjoz/4+/K7t7t/4bry8GA1rqI0Oag27PoqoCJhMSmvM3Ui4zynIOVnJ+ek5izv9rv8qubscjj1/f5g4iPkYyJioqMjIeHiIyJhoeGg4OCg4SGgYOBgP6CgIWCg4P//ID+gPyEhYb/gYmChYSGhoCEgYOEgYSJgoCAipSIiYuNjYyMkJOPhIaOjouOk4+RkpoImpyXmpiTlJGAiIuTgZGViYiKjYyOj4iFjI2Ph4aOko6RjY6NiY+NjYiLjI2Oj4+OjZCNj4aRl5iSkJKXm5qPpJqWmaWWm4uKlp+aoKWXkpCXoa20q7Gwrq+nqqadl56otKyspZyQl6GloqWdnpOJi4yTlIuIipWJf4CC9ISNkZKJhX+Eh4CMiomAhImFioODh4iMjYqDfYmGhoWDgH9/hYGHg4iFh4iAh4mBhY6KiIWGhYWHhYmHf4GKg4ODgYOGgoB+f3+Bg3999YCBgIB+goF8fH9+fICEf3qAfoCBhIGAgX+Ag4KBgIaBgYODf4ODgYKCg36BgoGFg4WBgoKEgoOFhIaHhoeFgoWAf4P3sOnk38ji3NjWlqnNt7u8xNPezuOXtb7FwsXGw8C8xMK/wrzDxMPExsLEw8XExsbFxcbIycrMz8/Nzc3Oz8/Q0tDR0dLR0tLU1dDY2dja3N/i4+Tm6Ofn5uXj5Obn5efndHTm5eXm5uTl5OLj5ejn5+nq5+jp6+rq6ejq7O2A7ezt7vDv7e7w8O/z8vP0e31+f3+AgIB/gYKAgnx8foWPkZaUjYJ5enR5gGy8tbO3ura0rKyts7+8tqyqqqqpqqyvs7rJa29na29qZG1vcGh3bGpxdnh6foOEhIWHjJScobO6r5WEhqyvr6+wsbOzs7Krpa22tLW1tba1tLS3trgZuLq5uby8vb6/wMHBwsPFx8fJy8zMzc7Nz4TRhNIC1NOF1Q1qa2tsbG1tbm5vb3BwhnGCcIVvAm5vhG6EbQxub29wcHFxcnN0dXaEdwZ4eHh5eXmEeoV7BHx7e3uJegF7h3oTeXl4eXh4d3h4eHd4d3Z3d3Z2doR1E3R06uno5+bl5OXj4uDf397e29mE2h/X19bU0tLR0tDR0c7MzMvKyMfFxcPBwL6+vr+/v72/hb4avL29vb6+vLe1tLS0trW1tLa1tre2t7a2treFtYC0tbi3uLi4t7e5vMPHyMrKz9XX4ePk5OPk5+ru7/Hz9fX3fH1+fnx79vPx6+jp6u3v9fTx7vHx9fR7e3x8f3+AgYODhYeJiYqLjI2Ojo+MiYTXh9XL4a3Gv99/kJ7uf4KDg4Po3JylnJmikIWBgIODg4qfwo2wioiFfnrq3fHI7oDd7PqD7MzBz7SJ1vCh5rHnroGLhculwsPQh4bok3mLkZ6clZWnt8vf6qiWqLvXzvPufYSGiIWDg4KGhYKCg4eEgoGBgH2AgoGCfoF+f/uAf4F/gIL/+nz3f/iAgIP7gIV/g4GDhX6Afn+BfX+Ffnx7ho2DhoeHiIeHiYyJfYCGhQ+EhYqGiIuRkZGNj42JioiAd3x/bnyCd3Z2eXl8e3Z0eXl7dHF5fHt9eHl5dHh3eHV3eHl4eXl4eXp5eHF6fYB5d3d7fX12hH96e4Z7fnN0e4N/g4V7dXJ+hY2Rj5WWlpaPjo6Hh46QmJKSjoZ/gomPjZGLjIR7e3+EhHx7foR6c3h44XZ/gYN5dGx1dXJ3d3qAeXt2e3Z2d3t/fnt2cHt7enZzc3VzeHV5dHt6e3x3fH13dn18eHV4d3h4eHt6dHd6enp5dnd5eHV1dHZ1dnRy4nV0c3Z2eHd0cnVzcXN3dG92c3F0eXZ1dXV0eHd1dHh2dnd1dHZzdXV2dnB0dXR3eHh3eHh3d3h6enx9fH5+eX1Wd3nmqOHa28DUz7/Ago+sqbKyvcjUwNmElZ6lpKSmpaKiqqqmqKWpqqqpqKurrKysrq6ur7CwsbKytLW0tLa1tLW2uLi4ubm6uru9u7S8vLu9vby8vL2Evia/wMDAv7+/vr1fX8DBwMHBw8LFxcXDwsPGxcTGyMjGxsjJyMjJyYXKSc3OzMzOz87O0NBoaWppaWlqaWhnZ2djZGRjZGRiYVxaWFZUUlFTUJmYkpGRkZWWl5aWmZ6dmJaYlpeWlZWWlJRKSUlKSklJSkmESjpMVmBiZWhqamlqa21xdHd6e3hiT1t6e3t8fX1+f39+enl+f35/fn5+fH99gICAgYF/gIKCgYGCg4SEhIUGhoiIh4eHhogOiYmKi4qKi4uKi4yMRUWHRolHBUhHSEdHikgYR0dISEhJSkpKSUpKSktKS0tMTE1NTU5NhU6ETwFOhk8HUFBPT1BQUYpQA1FRUIZRBVJRUlJShVERUFFRUaOio6Oio6Oio6OkpKSFooShA6Cfn4SgL56fn5+enZyanJuamZmZmpmXlpeWl5WXmJeYl5aXmJqZmZqYk5OUlJOUk5aYlZOWhJgqlpaXmJaWl5aVlpaVlpaUlJWWlpiYmZydnp6goqOlpKWlp6anqqqurq2uhVgZV6+vrautr7CusLS1tLK0sbSzWVlaWVtbW4RcAV2IXoBdXFpYkmWppqljgIOXVmpqn1RWWFhYnKaAhoF+gXpzb21vcXF3gn5XhG5ta19buLzZq83EzOF3zq+ut5d3uNKLyZjRmmZsbrCToZyuaGK0eGFgYWtpXGJ4iJmxv4qBkaTBvObYc3h5e3l5eHZ6enh4eHt5d3R2dXN3eXd3dHN0dULqd3R4dXd46+hy5nbmdnZ56Hd6dnd2eXtzd3V0dHJ1eHFxcnuAdXh5eXt6fH1+eG9wdnh2d3p0dnuBgX58fnx3enXyfwF+zH8BfsJ/lX7Mf4KAqn+dgJl/44DxgfqAhoGRgJiBBYCAf39/hIAEgYCAgIWBAYCPfwKAgYZ/iH4Bf4V+AX+SfoJ/k36cfwF+hn8Kfn5/fn9+f39/frF/AgIEAICOiYuUlJaVj5CVmI+XopWQkJackpWSl6Gfn5mPio+XnZyelpqjnZiSnqChoaKWkqmgraaNkausq5+qtae+rbK9vLzG2r2rpKO3w9K8tK6mwbq3tri3ubepq62loay1rbO2srKwub3Fx77Gvry0tKuqtKGQqqG3qaWlppiLi5iVjoCTnJqQkYeUl4yPiYqIj42Sk5GMj4qJiY2SjpKHjpOUiY6UkoePlYePjoWHg/yCh5CKjoiPi4aGhoeGhoKJiouBhoWB/4CHiYGJhYSIhIiOhoCDh4mEhoWNjYmGhomQioiMioiKi4uJjI6Iho2Ki4yMi4yMjo+JgYH94YSNjIyJiFOKh4eIiuT+9t7S+e7p9+na8OF94Picg+H+lYOQlaSsqLC7wcXMxszLzsvPzcvNzcnMzc3My8zNztHQ0dHR09TV1tbV1dbW19jW19fX2NfV2dva2YTagN3e3tze3d/e3t7g393g4uHg4uHh4uPj4uHj5OXn5eTj5efo6unp6Onp5+zv7+3u6+vq6/P19fn7/f35hIiFiYmHhoODgoCFhoWRko2I+9ze39rd2dHAxMfW0srDuLS0ucjUx7m1tLW2uLq8wcfU4vrx74aE+oSFgv+HjoiLi/n5J4CDiIeIiIqNkJWgpaecmIyesry/v8DExMHDvbzBwLnJysvKzM3OzoXNA87P0ITSE9PV2Nna3uHk5Obo6uzt7O/v8fOE9Er19fX09/j4+fr6/P3/gYKDg4SFh4iIiIaHh4aGhYSEgoGBgYD///39/v39/4CBgoKEhYWHh4aIiYmJiomJiYqLioqJioqKi4qJiIeHDIaGhoeGhYaFhYSDg4SCCoOCg4OBgYGAgP6E/WD+/f37+vr39vf29PLx8e/u6+vq6uno5uXj4+De3tzc29rY19XSz87KyMbGxMLBvr28vLi2trW0trSysbGxsrKxsbKwsLGwr66uq6qqp6mop6anqqqqqampq6usrKytra2Frz6wsLKztbzDycrNz87OzMvLycnMysjHy8/P1Nba4OHh5OTp8v6AgP769vHt7e7u7u/x8vX3+fn7gIiOk5eYmYSagJmYl5eWlZOQjYeBs9fP0oLXs+ul2eKZh+r/gPrIppyKp6ehpY78ioualZSXl6/KnJKQhYyJ79XNtt7H3svE4ce+ms2N78CE+4yc7OKZ+ISO9/yQ4dLo0eDRg6yku9D4g4WNkpORkI6PkpCOiYeIjoyLjIaJiIuIh4mJhYOHg4GASf+FiIKDgYOC/PiBh4CB/4GChPn8io2DhIGBgf6I/4CGhIqEiYiXi4iDiYiKh4aLiYaPjJWQlJGOi46KjJGRmJyWk5WXk5iSlZiAhH+CiYqLioWFio2GjpSJhYWKjYaKhoySkJGMg32Di5CPkYuOlY2KhpCTkpKSiIWZkZ2WfoOanJqTm6OaqZ6gqqupsMKrm5GSpq69qaOcla6opqeno6monZ+el5aepKGjpaKjoaaqsLCpsKuno6Oem6SSgpyVpZqXmJiMgIGKioeAjJCQh4h+hoqDhYGEgYeDiIiIhomEgoOHi4iLg4iMjIWHjImCiYyBiIaChoD0fYKIhIeEiIeDg4GAgYN/g4aEfYJ/fvt+gYN9g39/gYCBg4B7fYGBfYCAhISBgH+Chn2Cg4KBg4OEg4SGgX+GgYSDhYOCgoWFgXp679J9hYWEg4EqhIKBgoXd8+raz/Xn5/Pk2u7bgub5nYbu+IuAipGaoJ2krLO2u7i+vLy7hbwKu72+vr6/wL/AwYTCA8PDxITGIMfGxsfHyMjIycvLysvLy8zNzs7N0NDNz8/Q0NDR0NHThNEd09PV1NPT1NTW1tfW19nY2NjZ3Nzb29ze3t7g392E327g4eTl5+jp6eh1dnV1dnNzcnFubW9wcHNzcG3Lubq5uLq2tKurrLCvrauko6SlrLGspqWlpKWmp6mrrrW8xcHAZWPCZGRkyGhqZ2lr095xdHh6d3d5fH6DioyDcnJthJykpqeoqaupqqWkqKejrYavBLCwsbGEsDmxsrOzs7W2uLm6u7y8v8LCxMTDw8XGxsnKycrLy8vMzM3Nzs3Pz8/Q0mlqamtsbG1tbm1tbm1sbGyEaxVqamnS09HR0tLS1Gpqa2xtbW5vb2+EcQFyhHODdId1gnaFdQV2dnV1doR1hXSEc0dycnNzc3JycuTj5OLj4uDg4eHf3t3d3Nva2dnZ2NfX1dXU09TU0tHP0NHNzM3MycbGxsPBv727u7u6ubW0s7KxsK+trKysroSsD6uqqqytr6+ura2srK2sqoerCqysqqusrK2urq2FroCwsbCxr7Cytbi7vcPFxMPDwcDBwsHAv8DCxsjKz9HY1tfZ2+Dl73l68u/q5eTk4+Pi5ebm6evs7O96gIWKjo+QkZKTk5GQj46Ni4mHhH55qdDGx3Kgj9iXy9OCetjtd+m5lZuKnZ2YpJD5h4iWjo6RkqO5k4yKgX155NbQtN3I2YDJw+zJvZvQlPzFhPuLmuXgkvJ9lP/8kePW1sPMvHagoLHE53t7hYmJh4eGhomIhoOCg4aEg4J+g4KEg4KBgoF/f359fPiBhX9+fn999/J9hH57+X5/f/X2hId/goB8fvuF+n2CgIeChISOhoOAhYOFg4KGhYKHg4yMjImJg4WAhQ2Hh42SjIqNi4uOiI2PYHNvcnl5e3l0dHd8dHt+dnR2eHpydnN6fn17d21lcXh8fHx3en94dnN6fHt5enNwfnd/e2hrfoB+e3+HgYmAg42NjI+ZiX90d4iNl4qFgn2Vjo2Pj4yRj4mLiYSDhoqLi4SMZpGUmJaRl5OOjI2Kio99a4uGjoqHg4N+b29yeHyBgYJ6eHB4eHV3dX12end6eHp4eXVzdXh7e314e3x9en19e3d6e3V6eXd4duN0dnt6fHp5eHd2dXN3eXN3enhzdXR05XN1dXR2dIV1gHNxcXR2cXV2d3d1dHR3dm10dHV2dXV2dXV2dXN4dXd2d3Z1c3d2dW1w1L1yeHp5eHd4eXh4fNLl386/59jW4cnF4MyE3/KehtjTc211fYKIh46Ump6joKOjpKKnp6ampaaoqKenqaqqq6ytrK6ur7Cwr6+xs7S1t7e2tLa2tLW2FLS0s7O1tra3uLe1tbe4urq6u7m2hLlauLq5ubq9vL29vLy7vLu9vr69vL6+v7/AwL+/vr7AwsHDw8LCw8LEwsHCxWFgX15cXVtaWllZWlhZV1RSUZ2UlZWTlZOTkpCPj46OkJKSkpGPkJGSk5STkpOShZE4jo+QSEePSEhKk0lKSkpNq7VdYGJhYWFiYmNmamlaTEtMXm90dnZ3eHh3eHd1d3h0e3l5eXp6eXuHfC97e318e3x+fn9/foCAgYCCgoGBgYOEg4OEhIOEhYSFhIWFhISFhomHh0REQ0RERIRFgkSERYJEhUUJioqLjIuLi4xFhkYSR0dISEdISUhISElJSUpLSkpKhksDTEtLiEwFTU1NTEyPTTGbm5ycnJucnZybmZubnJucnZubm5ybm5qbnJybnJycm5mZmpqZmZiYmJeXmJaVlZOThJEbjo+OjY2Njo6Mi4yLio2Ojo2Njo+Qj4+PjouMhI1Ujo6NjYuMjY6Oj5CQkI+PkI+Pj5CRj46PkZCQj5KSlJWXlpWUlJSVlZWUlpSXmJiZnJ2foJ+goKKlqVVWrKqrqqiop6epqKmpqaurq6xXWVtdXl9fhmCAX15eXVxbWVZTeqiio1RgXa1wjaZXV5OjUZ6BZIB0goB+iHnVc3J8dHJ3dn6NdXBvaF9bu760m8Sxuq+3zLWdhbSK37Nmv3KGs6Rvp1pprK1smoyMiJmKWnx7iqLDbGtzeXp5eHh5e3p6d3Z2eHh3d3R2d3l4dnV2dnR1dHV053hHeXR0dHVz5eN1e3Ry5XZ1c+HkeXx1enZzdeZ66XN3dnt0eHZ+enZxdnZ3d3Z6dnN6d35+enh5dHZydnh4en5+e318en13e3v/f61/AX6WfwF+tH+Cfot/jX4Hf35+f39+fvl/koClfweAgH+AgIB/hYCCf9eAl4GIgL6B/4CPgIKBkYCWgQuAf39/gICAf4CAf4SABIGAgICHfwF+j3+OfhB/fn5+fX5+fX1+fX5+fX1+hn2GfqF/AX6Hf4J+hH8Gfn9/f35+h38Dfn9+q38CAgQAgJKXkZeOl46Mj5eXj5GXlJCRkpWYoZiQmZuVlKCem5eZm6akm5WWnZGRjoyWqJmfuLKSnq21vcHLw8DFz8Orqqueu8nAr6i2wMC+vru6s621wbTAyqicwsS4taqxws3MvrnDwLe4vr26wb64sbK4vLailJiorKGVlZ+mn5aUl5OOgJ+mopyUioGGl5ySjpyem4qRi4yNioaFiIyEhJKTj42Llo2RlImOh4SKgoOFkIuGkImHhYOMjYqQiYiPioiFg4WThYSCioGJhoiHgYaDj4eIgIGCioeAh4aDhIODh4mKiY2EhImKjYuLi4yLjo6OiYqIio2Mj46Pi4uKiYqMi42MXomJh4qHiImJ9vP5gPL8hfHjgoXe7drt7e/17uzv+/2BhID77/Lmh4eXma24wMXFwMbGx8bFxcbIyszMy83P0M/Qzc/P0NDR0dDQz8/Oz9DR0M/Q09HQ0dDR0NDP0NOH0YDT0tLU1NLR09PT1NXW1tbV1tPS0dXS0dXV0dHQ0NLR0tLP0NDV19rY2trV1tbY2drZ2Nfa2dTU1NHS1NrY0s3LyMfLzs3KwLnCw8XDvcC1ra60wMG7ta6ur7Cys7e5u7/E19zd3uvr5Ofr8/Ds7+/u6efu5u3w8vX6homNjISDhHX/+YCEk52jp6qsrayorK2sqrG4vL2/wMHBwsLExMPExsjHysvLzc3P0dXW2dvc3N3f3+Hi4+Tl5+jp6Ors7Ovu7u/x8fLx8vX2+Pv+/v+BgYKDg4KDgoKCgf/+/Pr49/T09PDv8fL19Pb4/Pr9gICBgoGCgoKEgw2EhISFhISFhISEg4ODhoIRgYCAgP/8/f79/Pv8+vr7+/mE+yL8+vj29PLz9fPy8/Hw8PDu6+vq6Ojo5+fm5eHg4eDf3t7ahNg61tTS0c7OzMnHx8bDw8G+vbu5t7W0s7Gxsa6sq6uqqKenpqenp6anpqekpKWmpqWkpKOkpKOjpKOjpISlA6enpoSogKmpqaqpqqytrrGys7KxsbKztLW2tre6vL6/v8LGyc3P09nb3uLk5efl5Ors7fLy8PH29vj7+/z9/v39gIWLj5KTlZaDh5OTkpCNjIqIhID06J3b09KFvYDkjIbQhfPM47HXlJGdiqalqJ2VjZWJm5qVmZilkJSTjYOHgObMy8DYM9S6q4KAmMCTiX6N7ue4v8W88Ivd8I6M233yqcPi4/KIkJKQko+Mk5KOk4qMiYiLioiGhYSIWIaEioqFgoOD/4qHhICBgYKBhYD/gP7++YOEgv/+//PvgPiBhISFgYmFgf6C/YGDiIKF9fqCh4uCgYeHhYuIipKNkoiKjoeOkJKSm5GUmKCYk5WXkJWRmJeAiI6IjoKKg4OGjYuEh4yJhYWHioyUiYWNjomJlZSPiY2Ol5aNiYyRh4aBf4qYjZKmooOQm6KtrbawsLS5sZ6enZGpta2cl6Wrra2vq6ujn6Sspa21kYqusKinnaGttLSrp6uppqWrqaitq6mioaiqpZiMj5qeloyOl5uTjIuKiIOAk5uXk4yBen2LkIqGkpSRhYmEhoeFgX+Bh3+AiomJioaPiYuLhYeCfYKAgH+IhoKHhYWDf4eIiIyFgYqGhIN/f4t/gH6AfIOAgYF8f3yGgYF7fH2GgXp+gHx9fn6CgYGCg359goGEg4SDg4OFhIWDg4CAhISFg4WDg4OBgYWDhYQDgoOAhYIw7OvzfO7yf+vhgIHW59Xr6Ons5OLq+Ph/gX725+3fgoOTkKKssre2tLe2uLi5ubq8hL2Av7++v8C/wcHDwb/CwsHCwsLDxMTDxMHCw8TEwsTDw8PExMPDw8TExMbGxMTDw8bFxsbGxcXExMXFxsXEw8TEw8TFw8TDw8LDxMTDw8PFxsjHx8bGxsXFxsfFxMLEwsHAvr69vb69ubKwra2usK+tqqepqqqopaWioKChp6ampKGAoaKio6OkpaanqrG0tra7vLq7vcLAwMDBwsHH0cvP09TU2nZ5e3htZ2bJymdreIWPkpOVl5eSlZSUlJieoaKio6SjpaWlpqinqKmpq6urra2tr7GysrK0tba3ubm6u7u8vLy9vb+/wMDBwcPDw8TExcXHyMnKy8xmZ2hoaGdoZ2geZmbNzMvJyMnIxsTEw8XGyMjKzM3Oz2doaGpqamtrhWwJbm5vb25ubm9uhm8ecHBvb25tbt3c29zb29va2trZ2trb2trZ2NnZ2djXhNY31NTU09LS0dDQz8/Qz87Ozs3NzMvLysrIyMfGxMPCwcC/vr68vLq4t7a1s7Oxrq2sq6qpqaekpIWjBaKhoaGihKOApKOkpKWmpKSkpaWkpaSkpqalpaWmp6enqKioqamoqaqqqqmpqqysra2srKutrayur7Cxs7W3trm8v8PGy83P09jZ2tva293e4ePk5OTn6ezv7+/u8fDwen6Dh4mKi4x9fIuKiYeFg4F/fHjn3JbTysl1knLXg37IdNy+1KfOfHiAmoidn6icl4yWiJiUjpGQmImPjYl/enPi0M7B19G7p4F/lsGUi4CN7uWytbu54YHO24yL5YD7o7LQ0uB8hYqIiYmFiomGi4ODgoCEg4F/gICBgYF/foWDgXx7fPeGgn98fYB/gIJ++n379vaBgX74+f3x7H31f4KDgX+GgX/4f/UZfICEgILq9n2Dhn5+gYJ/hoOGioSLgoWHgYSJDpCIjJKVj4yOkIuNiY6LgHd6eX11d3J0d3l6c3V7eHR0d3l3fnV0en14d4GBe3V4eoCCfHp7fnZzbWx0gXh8ioZpdH6Ci42Pjo6SlpCFhIV7jZWNfHmEioyQk46PjYiLj4qSlnlwk5KQjoaLlJeXkY2UlI2QkpGRlJSSjI+QkpCIfICJi4N/g4qMhn55dHVxdISHiIR9dW1vd39/fYOFhHt8dnl7eXZ1dXt3dnp9fX15hHx8fXd2eHJ0dnZydHd5enl7eHR5ent7eHZ9end1dHN5c3ZzdnN2dXd3dHVweXV2c3RweHZxdHVycnNzdXRzdHZycnZ1dXR0dHV1d3Z2dXV1dHZ3hXZpd3V1d3h5d3V3dXV4eHd32dzhdODleOHZeXvN08Tc2dDWz87X6Oh3e3bq19zPdnOAgo+Wm5+enaCioqOkpqamqKqpqqmqq6qrra6vsK6xsrGxsbOysK6vr62wsa+wsK+wrq+usK+vsrKvhK6Ar6+xs7Gws7O0s7Cwr66vsK6vrq6usK2tr7Cwrq6trq2usrCxr66tr62tr6+vrayrqqmlp6alpKSko6OfmZOPj4+Qj5CQkY+Pjo2Oj46OkJGRjpCOjpCQj5CQkY+Pj42PkI+Oj42PkJGRkJCQkZKSlp+rp6qrra2yX2BhXE9KSZIsk0tNVWBqbG1vb29ub29vbW9xcnJzdHVzc3R1dnV2dnZ1dXd3d3l4eXh5e3qEexd8fXx8fX19fn59fn59fn9+f39/gH+AgIWBJYJBQEFBQkFCQkFBQYODg4SEhIWFhISGhoWGhoaEhoaGQ0NERESGRQRGRkdGiUcaSEhIR0dISEdISEiQkZKRkJGSk5OTlZKTkpKEkwOUk5OElAKTlYSTA5STk4SUC5OTlJSTk5SUk5OUhZOEkhmRkJCQj4+Pjo6Pjo2MjYyKiYqIh4iHh4aGhIULhoeFhoaGh4mJiIiGh4KJhogGiYqKiYqKhIsFiImLi4uEiiyIiYuKjIqKiomJi4yLi4yMjY2OkJKTlZaWmZucnZ2goaCeoKGipKOkpqipqYSqgKurqlZYWVtcXV1dU1BeXV1dW1tZWFZUpZ5yrqalVV5VrGVioVGihJR6p1ZQgXSDh5CHgHeAdn53cnNydG1ycW5nXlm+rbCktLaWgl5jdqVwa2Jwsal/nKGQtW+hqmJklFamgJa0t8NtdXp5e3p4e3t3enZ2dHR3dnV0dnR1dnd1V3R7eXhycHPle3h1cnR2dnh3dOl05+HheHh25ujs4d5z4nR5eXl2fHd24nXdcXZ6d3XQ3XF2fHJzd3d0eXh6e3l+d3l4dXp4e3yAent/gn58fYB9fHl8ef9//3+Kfwt+fn5/fn5/fn5/f4x+g3+Efv9/uX+HgIJ/zICLgZSAo4H/gLiAlIEVgICAf39/gICAf4CAf4B/gICAf4CAl3+Qfod9CH59fX5+fX59hX6gfwF+in8Ifn9+fn5/f3+FfgJ/foh/A35/foV/gn6kfwICBACAlZeZkImQmZWSnJ2SkZGXk5WKipSWlY+Tm46KkZKMiZaYo6KfqbOqraympamtu8jIvbi5ubfAuLasqsTf2c26ku/K3N3awszS09PDq6+3s7jHzMfBuJait8vRw7nBx8XIxsTAs7fFycK8vr6wo5ydqKidmKiul4+Po7CwtrqqoKqAmpSTm52ZkpWVkYyUpJ2XkZafmJePkIL36vyDivyD8oH6/PLy/oyFgYj59oCPiIaHho+Dh4ODkYv9+IqDhZiKipOHhoOFgI2PiImGjY6FgYWIjJSGioyJh4WLiouJhYiDh4+EgIqLi46Kio2NkIyLjYuNjI6LiY2NhoyMiY6Oi4t3i4yMjIiEiIiMiImKh4mKiYT/iomDiYeC9vLc+YOGhoiLjYaGh4WD7/rsxLOxvtPyiZaap7O7vsHCw8LCw8XHycjHx8jJysrKyMnJyMfHx8jIycnKyMnIysnIx8fGxsbHxcXHxMTExcTFxsTExcbGxMPDwsHDwsGEvoC9vLy8v7y6vLy8u7m4ubu5ubu8vLu7vb/Av72+wMDAv8DBwcG+u7e4ury9vr/Aury2s7i3t7euubSvq6iqrbK1s62rq62vsrS1u8vMzMvR1tfZ3d3f3N/k397f397f3N3d3uHv9vb4+f+A/fXq4Ozz/ouWmZeXmJufnp6hpKWqrICsq62urbKzsrO2urq8vr2/wsPDxcfJysvP0NHS1dfX2dna2tva3OHg4eDh4+bm5ujp6+7w9Pb29vn7/YD/+/v6+vf18/Px7u3q5+bo5eXk5ujp6u3w7/Dz9fb39fb5+vj4+fr8/Pz9/fz9/fr5+fr6+fj4+PX08vHy8vDw8e/u7gXt7u7u7YXrUOzq6Ono5+bn5eXk5uXk5ePi4eHh3dzc2tra2NbW2NbU09HQ0M/NzMrIx8TEwsC/vL26ubi3t7e0srKwsK2qqqmmpaSkoqOhoKGfnp6en56ehJ2InoedgJ6gn5+foaCgoKGho6SkpaWnp6epqairq6usrq6wsbK1tre4urq9vsHDwsjJzdHS0tTV1tja293d3Nzg5efo6efm5ufn6vH4/oCCg4K4/4OCg4H89/Lt5t7Zz5Hb09Lrk9jIsLOvyML86NG//Nmbh5WQkI6GhYSLiYiIkJCU7ffoO83Twr2sn7yrqIeCiY2gwKXS6oHv0sHu8OWPoqSp2OT1g4qQk5KTkI+Tj5COj4+OjouMiIqPhoSKiYeKhIdZiIuFhoiJgoCJh4KCgoX+hoP4/4L9/oT8gYCGgv74gYOCgvmBhoOA8viBg4WBhYX6i4mGiIaIgIiF/4CFiYSBhI2LjouKj5CKjpSQk5aajpCOkpmNi4+PjpeAjJCPin+HjYuIkZGGh4eLiIqAf4mKiYaJjoSBh4iDgI6PlpeTmqCcn5yYmJmbqbSyq6Onpaevp6Wem7DFwLiog9O0wMPArLi7vL2vnqKqpqm0tLOtpouVprO3rqassbC0s7Cso6qxsq6pqqujmJWSmpuUkJqfjIeGmKOho6eZkpmAjouLkpKNiIiLiIWLlZOQiouUjY2Jin3v5fh+hPV+637z+u7p8YaCfYP08X6KhIGBgYd/goGBiof79YaAgI2FhI2CgYCAe4aHg4SChYZ9fICDhouAg4KCgYGFhIWDgIF+gYZ9eoKEg4aDg4SFiIWEhYOEhIWCgYaGf4ODgIWFhINRhISChoF9g4KFgoSEgoSFhH72hYWAhoaA8u3Y8oOCg4WIioODhYOB7fXku6+qu9DngYySnaqys7S1tba4tra4ubq8u729vLy8vby9vL29vLy9hLwCvbyEuwy6ubm6urq5ube4ubmEuAm5ubi3uLi2traGtQO0tbWEtAuzs7SysrO1tLO0tYqzDrKztLOzsrGxr6yopqWlhKcVqKalo6GjoqKinqCgnZ2dnJ2goqCfhJ5doaGipausrq6xsrKztba4t7u6uLq7u7y+vL6/wMLP08nFwsRiw8G9v8bK1niDhYWFhoeJiYqMjY6QkZGTlJWUl5iYmpybnJ2foKKio6KkpaenqamqrKysrq+wsLCxhbNPtLS3tri4ubm5u729v8DCwsTFxmPGxsbFxMPDwsC/vby9vLu6ubq6u7y9vsC/v8HDxcXFxsfJycvMy8vMzM7Pz87OztDQ0dDP0NDS0M/QzoXNCczMy8zNzMvKzITLDcrJy8rJyMnIx8bHx8eFxgbFxMTDw8KEwT7AwL69vb69u7q6urm4t7e1tLOxsLCwrq6tq6qqqainpaSioKCenp6cnZybm5uanJubmpucnJubnJ2enZ2dnoadGp6enp+enp+goJ+goqKhoKCgoaKhoqOko6WkhaWApqioqamrrq+xsrO1t7q8vcDCxcfJy8zNzs/S0dLT1dnd3uDg3t3c3N3g5+30fH1+fa7VfXx7efDs5eDa1M7FjNTLytl+zr+rrqa8tOvcx7fWwZmFkJKTjoiHhoyJiYmNi4zn9eTK1Ly+saC6rKqHgYmNmLik0+R/8tC+3efag5R9mJ7F2Op8gomKi4mIiIuIiYaHiIaFhYWCgoeBf4SBgIGAgYKCgYR/gYOEf32BgHx8fYLyf332/YH49oH3fnyDgfj5f39+fvF9gYF+8vZ/gYOAgYP3h4WChISEfoeB9X2BhYF9gIeDiIiFh4mHiI6Li4yQh4eHjJCHhIeHhI2AfH5/enB1fHp4f350dXZ6eHlxcXd4eHV3e3NxeXl2c36Ag4WCg4uFh4OAgoWFjJCRioSJiYeNiYWEgY6ampWMaqiRlpeUhZGUl5mMfYSJioyRkpCPiHiCjZOWko+WlI+SlZKSjI+Tk5GQkZGNiIeDiYuGhIiLf3l4iI6PjouBgYNVgH1+gYN+eHR9fX1+hYSDfX2EfH59gHXl2ed2eOJ033Pl693d1Hh3bnba3XR8eHd0eXl0dnZ0enrm4nx1d316d3x0dHV4cnx8enp4d3h1cXJ5eXt2eIR1fnl4d3Z2dHV1eXFxdXZ0eHh2dnd6eHV4dXR2eHV1d3d1dnZzeHh1dXZ3dXd1cXd2dnZ3d3h3eXh133t6dnt7d+HcyeJ1d3l9gIJ8f35+e+Lr16WYk5+vx3F8gYuWnqCgoaOkpKSmqaiprK2sqqqtr7Cvrq2trKurrKurrKyrq4SqEampqKmqqqmpqKiop6ipqqmphKo3qKmop6alpaaoqamnpqenpqakpKenpqWmpqeopaSlpaanpqempaWlop+gn56en52Yk5GRkI+OjoWPBo6NjYyOj4SOFY+PkI2NjY6Njo+Rjo6NjY6NjIuNjYWPJY6OkJCPj5GSlpiZnJ6epqWXkI+QR5CRkZWVmaFeZmdmZmZoaGiEaQxqbGtrbG1rb29ubm6GcB5xcnJzc3Nyc3N0dHV1dHV2dnd3d3Z1d3Z3eXh4eHeEeQ17e3p7fHx9fHw+fX1+hHwXfX1+f39+fn1+fn+Af4CAf3+BgYGAgoKEg0yCgoOEhYWEhoWFhYeHh4iHh4iHh4aHiIiHiYmJioqJiYqJiYuLi4qKi4mJi4yJiYuKi4uLioyLjIuJioqMi4uMi4qKioyMjIqLjI2Nhos0ioqJiYiIiIeIh4iGh4aGhoWGhoaFhYWDgoKCgYGBgH9/gYB/foCBgYCAgoGCgYCBgYKCgomDCoSDg4OEhYWEgoOEhAGFhISAhYaFhISDg4SFhISGiIiIiYmKi4yNjo6QkZSWlZWXl5eZm5ubnJ2foKGio6KjoqKhoaOmqq1XWVlYf4NZWVhYraqopaGempRusamnqlupoJOUjZmRtK6jmJqRfnKAhICAfHx4fXp6enVva7TFt5mqlZGJdZKIf2lZZXWAnouctF+ArJKLubKibH1+gaK0xGxwent9fHt8fHp6eHl7eXd2d3R2eHVzdnRzc3R2dnV2enV3eHh0dHd2cXR0eeF4dePnduboeeR0c3t45ul3d3R033V3d3bf5XN2eHd6eeN9enZ5d3lzenfjdHV4dXF1eXd8end7fXh5fn57e4J5eHl7fHcFc3d4dnzAfwF+1n8Jfn5+f39+f35/hX6Ef4J+jX+Cftd/AX6Gf4R+i3+Jfv9/tX8BgId/yoABgf+A+oCEgYKAhIGJgIR/AYCdf5N+A319foZ9h36tfwp+f39+fn9+fn9+hH+CfoR/AX6Ef4J+hn8Bfol/AX6ffwICBACAjY6SiYKIlpSYmZ6WkIqQm5SPj5aSjI2QnJ2em/idoKaroaawsri7t6zFyMnKrbLHzsi4pLKzsdHP0d7T2+Dg1NPG0ufR1se8xMW0lKWstb7Fw8jHv7y1sb3Fxc3Rw5i7xsfArqSxuMPLxcG2rKuorKmdorOvuLi0sK2tsKWfn5uAmqiwsaialIyGiZaVlJ6jpaGcnKKjnJCJi5uij42UkZGYjIeEgPfz4f2ShIGFlYz/8YaNg/Xz//+IhYKCgYOBi4OFjYaIj5WNhIb/goGFgv6JiYmRioiNiYmGiZGKh4iMiov9i4iM/ImGhYKMjYqLi4yJjI2NjImJiomLi4eLio9XioyPjYqJjYiOi4qFioyLi4qMioiJio6Li4yJhYaIgYSJh4yIiYyNhISLiPOE4sTt4vmBhYDk4I6lqrKqq7K2v7/AwcPDxMPDxMTDwcHCwsLBwsPExcTDhMIev7/Av76/vr69vLu7u729vLy9u7y6urq5ubm6ubi2hLUHtLKzs7GysoSwAbGGsCiysLCysbGysbGysq+sq6yqqqimp6yvrq2ur66vqausra6sq66sra2phKhjq6yrwLCwqqutsrrCw8fK0dPT0dbW1dLS1Nfa2tvZ1t3d19nc4Onq4+vx9fbz7eLe293n+YOHjI6QkZKVlZaZmp2dnqCgoqOlqKqsr7CxsbKztrm6vbu8wMDBwsTIx8fGyMzNhs+A0dHT1NbZ2Nrc3+Di5ebn6u7x8+/w8O3t7Onm5eHf3Nva2NXU1NbX2drZ29ze3N7f4OHj5Ofp6enn5+jp6Ono6uro6Ofm5eXk4+Hj4uDf3uDf397e3d7e3dvb2tvc2dja2dzY2drZ2NjZ1tbV1dve1NHQz9HQzczMzMvLyMjGxsVMxcXDwMC/v7+9urm3tbWzsa+ur66srKurqaenpaWjoqKgn52dm5yamJeXl5aWlZWVlJWVlZSVlZSTlJSUlpSTlZSUlJOUl5aWl5aWl4WYMZmam52dm52eoJ+foKChoKGgo6WlqKqsrLCysrS3uLi8vL3Aw8LCxcbExsnJy83Q0tOE1GjW2Nne5Onr7uzqsefG29/X0dPPzcjCu7KA2dHP1ufDuK2tpK2nrKimnZeSjISM/IT88YKD3dnMy9/o0ODbo7r2+Pvq1b/K36+Tmon3ksvY3cfDxtPy+4SPkJSVlJKTk4qRkJKOjI6LiYSGaoeOjY6GhoyDg4SKjIaIjY2HhoqF/oGA/oKAgoCAgfz8hIeEgPiAgP/28v/39f7zgvmBgoGBgoKCgIH7+P+Bgvr7goj9gv6L9/+EhIX6hIeCgfyKiouRi4n9gYiFhI+SjpaRlY6GiZGMi4uAhYaHgHmBi4mMjZSLhYCEjoqGhYuJg4SGkZGSk+6TlJidlJqhoaOmn5iytba1m5yxuLKnlqKloLq6u8S8wsbGu7ertsi1vbStsrGmipugpauyqrCwqKmlpayxsba2r4Smr7Gtn5egpauyr6yknqCeoJ2QkZ+hpqWjoaCfo5iRj45wj5ufoZuQi4Z+go2LipGUmZiTk5eXkYZ/gZGXiImOiouRiYN/evHv3PKMgX6Bi4X37IGGgfLu9/aBgYB/f4B9hoCBhoGCiIyHf4L5fH1/e/ODhYWJgoGFhISCg4qEgIKGhIT1hIGD7IOCgHyGh4SEhISDgIWFgYKCf4KEgoWFiYKCh4WEhIeBhoSFgoSFhoWFhoWEhISHhYeIhICEhX6BhIOIhoeIiYGCioTrfta/5drzf4J85OGKnaKmn6OqrrS1t7a3uLi5uLi4ubm4t7e3uLi4ubi5uLe3tra0tLW0srOysbGysrGysrGzsrKxsrKxsLCwB6+urq+urq6GrYOshasHqaqpqqmpqoSpEKqpqaioqKekop+cnZycm52Enwqgn56cnZ2dnp2bhJwmmpqbm52enZyonZ2cnJ6gpKeoqqqsr6+tsLGysbGys7S0tba2uLqEt3m6ubi7vL6+vr26uru/ydx1eXx9foCAgYKDhYaHiYqLioyNjY2PkZOTlJWWl5iZmpucnJ2dn5+foKCioqKjo6WkpaWmqKeoqampq6usr6+wsrS1tre5u727u7q4uLi2tLSysa+vrq6trK2srrCwsbGxsrGztba1tre5hLsTvLy9vb69vr68vb29vr6+v72+vYS8Br28vb29vIS7Ybq6u7q7u7m6uru6ubq6ure4uLmztLi2t7a3tbW0s7SzsrKvsrKxsLCvrq2srauqqamnp6ako6OioaCgoJ+enZ6enZyZmJeXlpWVk5STlJOSkZOTk5KSkpOTkpSUkpOUlJaFlQmUlJWVlpaVlpaEl4CWlpiXmJmYl5iYmZmZm5qampucm5ydnp2goqOlpqiqrK6wsrS1tri6uru9vr/AwsTFx8nKyszLzc7Q0dPX3OHj5eTjq8nAzdXPzMzIxL+7tKt908vJzda8sqion6SfpaOimpOPiYKI8n/v64OG4d/W1eTr1N3YpLvt7+3hyrjH3Fmtl6GF54fAzNO+vbzE3+V6g4iMi4qJiYuEioiKg4OFhIGBgIB/gYaGh39/hXt9f4SEf4GFhYKBhYD3fHzzfnx+e3x78fN/gn5993x++/Lv9/Tw9e+A836AgIR/Mnx99/L5f4D09ICE9oD6h+T5fn5/84CDfX7yhYWIiYOD9HuAf32HiYWNi4yIfoKHgoOCgHV3eHJscnx4eXyAeXRwcnp4dXZ7fHd4eH+Cg4LggYGFh4KGiImKioB3k5CTk39+kJSRi3yChH+RkZOYk5iYmZGLhIuZiJGSj5OSkHOFh4yPj4qPjouRi4qRlZOVlYtlhJWUj4F/homOlI6RioqMi4uGfn+LjJCPjY2Mi42Denh5P36GiY2Ifnt3cXiCe3qBg4eJiIeHh4R7cXGAhnx8g35/gn17dWrh4cfWenh1d3t45d13eHbb2uzgdnh3dXd2coR3NHN2eX16dnfqcHR2cd52enl6dnR1d3d3eHl2c3Z7eXjhdnRz1Xh4dHJ6end4eHV2dnZ5e3aEdRF3dHl5fHV1e3l5eXx3eXh4doR5hXhUdnl5ent5dnl5cXV6eX18fICAdnmBe9dxwKvMxuJ3eXHDwXiNkJWQkpmepKWmp6ioqausrqyrq6qrq6qqqquqrKyqqamop6ampqWkoqKkpKSlpKSlhaYWp6OjpKSko6OjpKSkpaOhoqKjoaChooSgGKGhoJ+goKCfn6CfoJ+fnpycmpiWlJGPj4SOB4yMjY6OjY+EjQ6MjY2MjIyNjY+PjY2Li4iML4uLiouMjIyNjY6NjYyNjY2OkJCPkZGPkI+OjYuMjYyLi4yMjpGTlJeisF1gYWJjhWQHZWVmZWZnZ4VohGlIa2tqa2xtbWxtbW1ubW5ubm9ub29wcG9vbm5vcHBxcHBxcnNzdHN0dHV1dnV1dHV1dnZ3dXV2dnd2dnd2dnV3d3Z5d3d3eHh3hHgFeXp7enuGfAF7hHwBfoR/Dn5/f4B/gYGBgICAgoKBhIIag4OCgYKBgIGDg4KBgoOCgoODgoODfHeDg4KEgQ2Cg4KCgoOCgYOCgIKDhIEhgoKAgIF/gIB/f4CAfn59fX99fX18fX59fHt7eXl6fHp5h3iEeSN6eXx8e3t8e3t6fHx+fn59fH19fH19f39/foB+fn19fn9/gIR/AX2HfoB/gIGBgoKDhIaHh4eJiYuLjY6PkJKTkpOUlJWXmJmZmpqam5ybnJ2go6WoqKmnf3+Ql6Gem52Yl5WSjYdltK2qqq6fmpOSiI2Hi4uKhH54dG1yyGnJz3d1s6ugmqe2o7WvgJDJys3Cq5WlwYl0gGm7cqGvtJiRlp27xmtxeHx9fHp4eXt1end7dXd2dnN1dHVydXd4enJxdXBwcXZ3dHV4eHR1eXblcXLidnR1cXNz4OF2eHVz6HV06+Le5OPd3N524nV1eHZ1c3lzdOPh6XZ23d11dt1z4nnP43R0dN90d29x3Hl5enh3eOJvc3JveHh4fXt8dm1ydnR0cpx/AX7/f4l/hH6GfwV+fn9/f4R+kn8BfoR/AX6SfwV+f39/fsR/An5/hX4Ff39/fn7/f7l//4D/gNaAlX8Gfn9+fn9/l34BfYp+qn8Efn9/foZ/gn6EfwN+f3+IfgJ/fol/E35+fn9/fn5/f35/fn9+fn9/f36EfwF+hn8BfpF/AgIEAICHlJOYiIeIjJeMiIqNmqOWlpSajpuepaGjlpeOr4ylr5+Una2+ydTMwcLR2trV3+Tf5ebL2NPBxcGztbG4xtXZ4OXu9PT3+O3e0L6su8bOw7rNzs/MzMrFsbXFvK2Nga+0wsnNwrqtrbe3x8W4u7m7wcm+uL29wquurayhmZypsoCsnZiRi4eNk5meopiMj56M942Wnp2XmpmM/fr8hpibk4yNlo6Lg42H5IOPhoaDgIP9+IH5g4H+iIOCjIT6gPT8g4CIhomTjf6AgvjyjoyGhoWJgYKJ/IKLiIaEg4CEhYiIjIuPiYeEg4aHiYqKi42LgomBh4uKiIqKkIqKh4iGhkKNhY6Nio6Qi4qJkI2OjIyJiYqJiIeLjIuMi4qMiYuOj5KQi4uNhYmOj5CYl5ybnJmdnZuanJyRiZCMkoiGlpKntLaEuSC6vLy8uru5vL6+vL7BwL/Avr29vLy6ubm5uLi1tbS4toS3FLa2tbW0tLSztbS0s7SzsrKysK+uhK0BroWsgK2sqqqqrKqpqKqrp6aoqKelpZ6gn6GhoqOlpKWopaWnqayvrrK4sqyrqailpqaqqqm8v73AtrixtriwsLGztcjYxMj4pPCRh6eR4crP1t718dva083Q0d/08vrx6uvu6e7l8uDb5efg3vCPgYeFhoeFiYqLjY6RlJOUlpeYnZ+gJKKjpKOlpqmqrKutsLGytLSztLa3uLnBz727u/HSv77AxMTEyITNUdHR0dXY2trZ2NfW1tTRz83Kx8TAwsLBv7/AwcLCxMXExsbFx8nKy8zMzszOzc7P0dHQzs3OzczLysjKycvKx8jIyMbExcfHxsTHx8bExMPFxYXEFcKyo6y8qbjEt8Wzg4zVyry+vry7uoS5T7i5tq+twrmnp5q+5bGrq6inp6WioaOfnp+dnJuZmZiXl5aVlpSSkpGQjo6Ni4qNjYyKiomIiYuJiYiHh4iIhomHhoeHh4iHiIeHh4iIiYqFixOMjIqMjY+QkZCRkpOSkJGRkpWWhJiAm5ydnp+hoqWmqKipq62wsbKws7O2uLm9vcDCwsPDw8C/wLm9vr+yo56im6qesKqnop+dlY+E8OXMzL+6uLaqoJiVkpCWkpmE+vD89YWB5uPkhI6Kl5OBi/SQlpHAr9uLlJWYlY2M7efsiICFkZSVlZaWj4yLg4qJj4uLjY2NiYV2houFgoeMg4mHiY2IioqDioeJiIaJjYf+h4SBg4SIgYaAg4OFgv75gYCA9oSAhP/8+Yb/i/zxgP/09Oz06fz/goX/hoGAgYCDhoL+9eqAhoT8iYuBgv2G/4uPjIKEg4OIi5KLioqJh46Lj4mHjpWdl5CPlYuOh4CBi4uPf4B/g4yEfn+DkJSKi4uPgo+Rl5SXi4yEnIGXnpSJkJuosbm2sbC7wb68wsbCysixv7yvtK+lpKGotMDCx8rQ1tXY2dLFuKubp6+4rKm3t7aztbSznqKwqJx+c5+lsbS1raqfn6eltLGlqaqqrbKqo6qqrZqenp+YkpGbooCckoyHg4KEi5CSlI5/hJWG74eNkpKPkZCG8/X7hpGTjYeIkImIgoaB3n2KgoOAfYD69H7zf377hIB/iYD1fe74f32Cf4KHg/h9fvPsh4SBgYKDfHuB9n2BgX9+fn1+gICChYWGgYB/fICBg4OEhISCfYN6f4aEg4SEhoSEgoN/gFOIgoiIhomKhYSEiIeJiIiFhYSDhIOFhYSGhoaHhoeKiImJh4aHf4KJiomPj5SVlZGVlZSVl5iRiIyJjYSEko+irK2ur7Kws7OztLKzs7K0s7S2uIW3HbW0s7KysbCur62trK2trq+wsLGwr6+ur66ura2thKyDq4SqEKmoqKipqamoqKampaWmpaWEpAijo6GgnZ2amoeYgJmZmZqam52enqGin52bmpqYmZqbmpmlpaSloKKfoaGenp6goKiwpqm6bLloYXxvtqmtsrW+vLKxr6usrbO9vcG7t7e7uL27wrm3v8XDw852c3Z1d3h4e3p7e3x+fn5/gYGDhIWGh4iJiouLjI2Ojo6Pj5CTk5KTk5WVlZmel5eXEbChmZqbmpucnqCioqSlpaephaoNqaiopqalo6KgoJ+dnYSeEZ+fn6CgoaGgoaOjpKSmpqemhKcEqqmoqYSqMKurqquqq6upqqmpqaqqqampqqmnqKipqqipqKmoqKmomouQmYyapJqjmF5irqqmpYSkhKMXoqGim5KglYiIf520mJqZm5qZl5aVlZSEkw+SkZGRkJCPjoyMi4qKiomEiAiHh4aIh4iIh4SIA4eHiIiJhoqHiwWMjIuMjIiNBY+Pjo+PhJCAkZOVlZeYmZqdnqGipKWnqamqq66vsrGztLa4uru9vr6/v7y7v7e6u7uwoZ6jmqSdqqain5yYkYyD8eDIx7m3sq+knZeQjYuRjJOB9On17oN5z9rUfomFkI6AiOeLkY7CtNWFjI+Rj4eG4d3ggXh9iIqMjIuKhYaEe3+BhoWGh4V5hYOBgYWAfICFfYKBgoWBg4N8gn+EgX+Cg4D0f3x8fn+Ae4B8fHx+ffjxfXt97357f/P29IP2hfbtffvw8erx5fv7gIL8gn1+f3+Dhn/38ud9goH5hol+ffuA+YaKhHyAfn6EhoyGhIODgYaDiIOAh42TjYmJi4KDgYByeXp9cXFwdHt2cXB0foJ5e3t9coB/goKDfn52i3CCiIB0fISMk5iVk5SYnJmXmZqXnZyJlpaLj4yHg4aKkJaWmpudpKKmqaafloh7hYyVioyWlZaWlJCPgYWPi4JhV4SLkZKSjY2Li4yHk5SNkZGQjpKMiI+NkIGHh4mGgICHi4CIfHRzeHd6gICDhXpwdoJ73XeAhISAhoN54ubueoSBfnt/hXx9e31xxXF8eHh1c3nq43Tfd3Ppd3d4fXboctvldXR3cHR1cOB1dOHbd3d1dHZ5c25143Bzdnd2dHJxc3N5eHt7dnFzcXd2eXh5eHh1cnZwdHp4d3l5enx5eHd3d259ent8e35+eXh6fnx9e3p5eXd1d3Z6end5eXl7fHx9enx7enl6cnd+f32CgYSGhoOIh4eIi4yEe4B7g3p5ioaWoaGipaaoqamqqqmpqKipqamqrK6vrq2rqqmnpqWlo6Kjo6OhoqSkpKWkpaWjpYSkgKOho6OkpaOio6GfoJ+fn56fn5+enqCfn5+gn52cm5ycmpuamJeXlZGPj4+OjY2Njo6NjYyNjY6Nj4+Pjo+Pj42MjY2MjY6OjI2OjY2OjY2NjIyLjIuKiYyLiESJR0VGRYmLjIyOjo2Mi4qJiYmKjIyMi4uMjo2Oj5KSlZmcnZ6oAldchl4DX2BghWEHYmJiY2NiZIRjAWSEZQVmaGZkZYVmhWUzZGVpaGdoaGhpamppamtrbGxrbGxtbW5tbW5ubm1tbm5vbm5wb29wb29ubW5xcG9vcXFxhHIJc3NycnN0dHNzhnUTdnV0dnd2dXZ4eHd2dnZ4enh2eIR5G3h5eXh5eXl6eG1maGhjbXZwcGw2N3V3d3h5eYR4HXl6enp5c29xamJnYW51dXp6eHd4eHh2d3d4d3Z3hHYrdXZ3dnZ0dXV0dHRzc3Jyc3N0c3RzcnR2dXV1dnV1c3V1dHV2dXV2eHd4eIV3C3h5d3l5enl3eHh4hnmAeHh3d3d4eXt5ent8fHx+f4B/gIGCgoWFhoeIiouKjo2Oj5CSk5OUlZWUk5GTjZCPkId+fH54e3uFg4KAfnp2c2rEvaqpnZqUkoyGgXx6dnl3fWvIwszDbGOms65sd295d2hxv3Z7eZaJr3F5fX59dnW9uL9vZm10eXt6e312dHWAbHBzeHd4eHZ4eHZ0eXZxcndxdXRzd3N2dG90cnd0c3R0cdpzcHF0cnZxdXNxcXR15t92cXPgdnJ14ebieeJ449xz6OHh3OLT6+12dut4dXd2dnp7c+Lg2XJ4eOh7fnRz63fien56b3Vzcnh4fnh3d3d1d3V6dXF4fIJ+eHh5c3QBcJx/AX7zfwF+iH+Dfox/AX6Hfwd+fn9+f39+hX8Efn9+fod/BX5/f35+iX8Bfv9/938CgH+EgKB//4CggIKB/4CugJJ/hH4Ff39+fn6Hfwd+f39/fn5+h3+Dfq5/AX6NfxJ+fn9/f35/f39+fn5/fn9+fn+IfgN/f36Ifwd+fn5/f39+hH8Dfn9+nn8CAgQAgImLi4SHmZSLkKOmp6aut7zBwaGXra2psbSnoqKqtr/Eyca9vraxtLi3t7/FxMnW4+fq597h5+rs5+Pt5c/U6ubr6efe39Tg2MrOvMLIyca8u7a1trCtr7K7v7iwrK+vqam0vsHAwcPCwr28wbq9vb++t7W4u7iytra0rayzr6SngKminpGMjouFho6UlpKTi4aAhLLwhouJjIeQlqCYjZWSg4Pwk5GEhYWJ/PD0hJKWk/788ueEifHy5viA+u/5+/ny6YKD6ICK+YCFh4ePhPj/hYyF+vf+hIaAhYf69oOElYqHh4CDkImBhoWMhY6CiYKHiJnUvYGGiIyGgomMjISIQY2Gjo2OipCNi46IjIyPj46PjIyOio2QjI2JjIyMh4mLjI2LiYuOjY+IjZCPkI+OkpCSlJeampueoKGioqCfn6CfhKCAoaStsrGwsK+wsra4vL27vby7vLu7uLS0srGzrayop6qpqqyurKytq6qrqaiopqippKOgpKSjop+fnKGgnqGdnp6cmp2bm5ucnZqanZyfoqOgm6WfoZ6ZoJ6koqOjoaChoqalqqepqamsr6unp6mhpKanqqqpsbLBuLu5t8LKxLhjysXX/NbT4/HcgYTb4frZ0P/z49fW3OzthOL26O6A7c/a/o+V9f717oL8/v75nvSRhe/x5ubn5efm5+zt7O/y9fr7/f6BgIGBgoWHh4aHiomKi4uKiYyYm5fO1N/17pv8946OhJEqk5WWmJmZmZqamp2ampmZmZiVlJSSkpGQj46NjY2MjouMjo2NjpCPkpGRhJKGkw6SkpGSkZGRkJKPj5KSkISOBI+Qj46EjICPkI+Rj46NjI+RkY+O2p2dra64hoXvve+A9Orn6evv7erq9u2AhIH80OOm9vrp5O+Bk+719fr9+/by7e/n5ubm3d7h4+Tm6+zt7Orq5uTk4+Hh39/f3d7f3dza3Nva3tzd3dvd29vc29zc39/f4OLg397i4+Xl5ebm6Ozv7uzt7oDu7/Dz9PPw8PPy9fr9/X+AgYOFhYeLjo6PkJGUlpeYmZ2doKOlpaisra2srKyjlpOQkY+MkZaVlpaanaKkqKalnZSJgu/r5uDc09DHxsG8uby/u7e5t7aztLKxra6Jh5GYq7Gvr66pqKSSmpyXmZyZmZqXkJqanJyPlJKRkJKOiYCGh4mPio+RhZmUmZqUmZmOhI6HiYmEiI+MhYmLi4eGh5GLiZCKkoWJhoeJi4mGiYmG/4KDhIiCgYKGjYeB9ICB84D3+PT39ezx/vvz74Lv+Pz3g4WLg4V/hIeEiImOhIOA+/yAiYiFhI2Ih4qIhoWJjYiFjYeGj5OJiYiHgYCFkAaJh5KJh4aAg4SDfYCPi4SHlpqamp+kqayuj4qenpqhpJmWlZykqbC0sqmoo6GlqKSlrLOyt73IyszKxcXMz9PNydHJurzRzc7Ny8jIvMjEt7ipsbW3s6yppaKkoZ2ipKuup6SdoKCdn6etrq+wsbGxq6qtqqypq6yppaanpaGlp6Whn6WhmpyAnZiViYWFg4CBiI2PjY2FgHyBrOqEh4SJhIuQlZGHkY6AgOeOj4GEhofz6OmAjZCP/ffs4n+H8e/i8n335vL28+vifX3nfIT0fX+BgIWA9PiAhX/x7PZ+f3yAgvTvgoCMhYGBe32GhH2AfoR/g3yCfoKAjMixe4GCg4B/gYOEgYOAh4GHiIiEiYWFiYSHiIqIiouKioyHiouJi4WIiYiFhoiGiIiGiImIiIWKiomKiImMiYqOkZWVlpqbnJycm5mampqZmZqYm6KorKysrq2ur7G0t7i2ubm3t7S0sq+urqurqKejpKanqamoqqqqqaenpqempaenpKKgoaGhoJ+fnp6An52fnZ6dnpyem5ucm5yamZyZmpydmZmemJiZl5iXmZiamJaXmJmcm5yam52dnZ+cmZmclpiampqbm5+gp6Wko6Ckq6WhqKmzwa6tt7qxY2K2ucOyrcfAubS0uMC/Zrq/ubxiu6+wxmtuxsrFxGfJyM/Kd85xbdHU1NTW1dTX19YP19jZ29vc3t/hcnFzdHNzhnWEdlp3dWlqan6Ctce+cMPMeHl4ent7e31+fn+Af4GCgYKBgIKAf35+fX59fHx7e3x9fHt7fHx7e3x9fXx9fn19fn6Af4CAf4CBgoKBgoKCgYGCg4OCg4OCgoOCgoGFgmSDgoKDgoODhIOEhIOEg4TChoiQj5lmY7mewmTBv9XZ2uDc3Nnf23V4dunH3YOuqKCfrVp86Ojp7/Hz7urn6eLj5eHd3d7h4+Tq6+rr6ejp5+bk5eTh4+Tj4uTk5ubm5eTl5ufmheiA6enp6urq7Ozt7ezt7u7w8PLx9PT08vT2+Pf39PX19fbz9vb39vn+gICCg4WGiIuMjI6QkpWWl5mbnJuhpaenqauurqysrKSWkpGTkY2QlJeYmJmcoKKmpKGakoV85+Pb1dDKyMC9uLSxs7Wxr66wrKmqqqmlpYaDi4+ip6ampaKAoJuLkpWRkpWQkJCNhpGTlJSJjYiKh4iIgoB/gomDhYmAkY6Uk4+Skol9ioKCgX6BhoSAg4SGgX+AiIWCh4GIfYB+f4GGg3+BgH/xfX6Bg359fICHgn3nfHrpffDx7vLx6u/59u3tf+739/aCgoWAg4GChYKDhYuCgHzz9H2Ggn8fgYmFg4aCgn+BhoKCh4KDi4yBhIOCfXx8iIKEjYSBgYB2dnZwcX96eHqFiIiIjI6RkpB6eIeJiIuNhoKDh4mRlJaWkZGOi46OiYuRlZKVlp2dnpybm5+hpKGcoJySlKCeoaGgnaCco5+VkYqQlpmWjoyIg4eLh4mKjo6PiomMjYuMkJKTlJSVkpORkpORmJOSk5KPjI6NjI+OjoyJj46Lj4CNiYV+eXd1eXiAg4SDgn13cnmj3XuAf4N7gIGCgn2EgHV20oGDfX1+fdfNzneBg4Py7+DYdX3k3tDfcuTX3OHg29Rxb9V0eOBydXNydXXh4XN3cuDd5HN1dHZ45dZ3dnx7c3Jxb3Z2cnZydnN1cXlyc3R8tZ9wdXh3dHZ2eHp6e4B7dnx9fHh+fHx8en59fn6AgX9/f3t9fnx+eX19fnx9fXx7fHx7fX1+dnx8fX17fn99foCGiYmMjo+RkpKQj46Qj4+SkpGTmqKmpqanpqepq6+xsrGzsrOyr6+rqKalpKaioZ6go6Oko6OjpKSjpKSjo6GgoaCenZydnJual5aWmB+YlpeXmJeXmJaUlpaUk5SUlJOSj5KTkZCOjo6Qj42PhY1hjo6Pj4+OkI+QkZGQjo2Ojo2OjY6MjI6PjIyNjYuKi4qLiYqNjIuMi4uJR0WLjIyPjZCQjo+PkZOPR42OjYxGjY6Nj0hIk5WSkkyXmJuaTppQUqmorbGxsLGzsbOysa+vsoSzKFlZWllaWlpZWlpbW1pbW1taWEtKSkxOiI+NSYyZWVpaW1taWltbXV6EXYRcDl1dX19eXl5fYWBgYWBghGETYGBhYWJhYmFiYmNkY2NiYWJiZIVjHmRjZGRlZGVlZGVmZWVlZmRkZWVmZmZnZ2ZmaGhnaIRngGhmond4eXl/R0SAf4xGjI+rsa6zsLGyt7hfYGC+pKhWW1pbXGAvWsPHx8rOzMvJx8fHxMbExMPHycjM0tPS09LS0dLR0s7P0dDNzs/Pz87Q0NHP0NPR0NDS0tTU0tTT1NTW19jY1tfX1dbb2dja2NnZ29zZ2dbZ2Nrc2tjY19fWgNrf3nBxcnJzdHV2eHh4e3x+fX5/goSDhoqLi42QkpKSkI+KgH17fHl0eHt7ent+f4OFiYiGgHhvZ8S/ubW0r62mpJ6bmZycl5WVlZOOj4+NiYhzcHd5iY6NjYyKhoBze356e315eXl2cnx9f4J5fXZ5d3l4dHJ0dXp4en50goGEe4eEhIWAcIJ5dXRydXl2c3V2eXRxcnl3dHd0d3BycXR0eXhzdHV033R1eHl3dHF3fHp01HNt1nXg39zg3drf6eTd33rd6OHjeXt5d3p1eHt4eHp9eHdy3eJxeHdzeH97enp1dnN1enh4e3V1fX5zdHZ0cnBveHV0fXV0dP9/k3+Cfo5/AX6Gf4N+hH+EfoJ/hH4Bf4d+Bn9/fn9/foZ/CH5+f39/fn5+hX+CfpV/g37/f9p/goCNfwGAhH8BgIR/goCEfwGAhH8EgH+AgJN/l4AGf39/gH9/4YCGfwaAgH9/f4CLfwaAgIB/f3+GgAKBgNx/toDtfwF+i38Ffn9/fn+LfgF/hH6Pf4J+o38CAgQAgJ2js7qxpJCQoa6wray4uLS4s7Gtp6itrqegoKOmq626wsfN0sXJwbnEysrFxcfLv8LEzczN2d7l5N/d28zM2fPx7uvi3s3a09DYytXZ1b7M0Mq2t8XHycfGycnCw7y2pqWbobS/s767wLq1so+YsLq9wMC2sbS1tLCuqaSjpqeogKmenZ2ZpKWemJCZpqyusquqrKb2gYePjpaaoZaB7fN/gICIjpTmhpOKhoKGiY+JjZGUks37jYzo54eGgouD8IqQg4aE+4WDg4eG/qXvgoiBh8aFiYaJgP//2ff7h4yJhP77/4eKhIT8gIiMiIOEgISAgIOEi4SChYP+iomMivP6gIbv+/mFvISLiIXs+YSEgIiA8YSHi4aFiYiOjpeKjYuNjY6Og46KhIKIhfqIiZCLgYqKhoOHi4yRk5WUmY6YmJeXm56ioKKipKiqq6mqq6+prKqnpaSpsK+1trq+ssPB0cO9wr7CvM7JzdbR1NbR0cfV4MfEv7y9vrjKvKGUj5GLPYiHhYjHt6yaj4OCgIN9mqyYiXu2samcooR/fX14rJ7AnYKAgYCAfX5/fZCbp6GqlpuGf32NfXx9fHt6enuEeQN4eXiEeR57fH19fH9+fnx+fX9/gICBgIB/gIB+fn59fn18fH+EgBqBgIGCgYCBgoKDgoKDgoCBgYGDgH+DgoKBgISCJoWCg4SFhYOEhIaFh4iFiIaKiomKiIeIh4uNjIqLjY2PjY6Qk5OQhJUJmJeamJmWlJWXhZl/mpiamZybmZeanJmVlpaUl5eXlZWWl5mUlpSUkZCTlZWUlJOTlJWVk5OVlJOVlJKTkZCUkJGPkJCSkpGXlZSSkpSTmZmVlpiXlpaVl5iYmpucnJ2dmZqdnp+goJ6gn56fnp6goaWko6SlpqajoaCgoJ+enZydm5qamZmbmZybnISagJiXmJWVlpOTkpWWlpSVl5iVl5WXmJiWmZqZmZuanJ2fnqKioaKjrKiqqaaoo6eusbzGx8PBwMjT6+fw5+bn2+Lr6/mEioqDhIJ9hIqLj4qKjZehnZ2ZnZ+Zm56blZmYm5ubnp2hm5qZmJmTlJaYnZ+lsLKjmpiTkIWIhYKA9O7sgOvn6Ojo6unp6ubl4Nve1tPOy87Rz83IwLW1t7q8vLOsq7C4srO9uMO9uq+vqaCgopybnpqkopegn5Smq6+zrKSrrKigl5CEhoT+hYSOiYqHhoOOjoqMhISO/YGDjISHh4qHhomNjo76gYD8+v/8gYj+hPn9+/f5+uPs9+nk8Pj4hII5g4J+9or9+fWA8eT7g/X4+v7+9Y757/P2hovwg+nq//35+oiG/PuNjoODgI+VhoeMkouSo6afkZSdgJKYpKmkmYiHlZ6hoZ2opqKloZ+em5yfn5mUk5eXnJ6psLO2uLG1r6qxtraxs7O3rrGyuLm4wsfOy8TExLi3wdXW0tHLyLjEv73Ct8DEwKm6vbanpbOzsrKxtLGvr6mjk5GLkKWuoa2praqmo4OOo6mrraynpKWmpqSjnpqZm5udgJ+XlZKQmJqVj4mPmp6ho52goZrke4GKio+QmIx/6PeAf32Fi4/agYyFg4KEhIqGi4yPi8D2iIjj5oSBgImB74WLgIOA+ICAgYOC9J/of4N8g7p+g4CCevn50PPygIOEffTy9YCDgH/yfYGFgnt+e397en9/g399gH74hYKBge72gIPp8+2DtX+JhYDl8YKAfIV+7oSFh4SDiIaLi5GHiomNiYqIgouGfX6EgfKFhoqHfIWEgH2BgoaJjZCMkIeOkZGOkJaYmJudnqCjpKaop6qora2pp6autLG2uLq/ssO/zMK7vby/tszEydDNz9TKz8LQ3MXBvLq7vbfKvKqcmJmSPZGPjo7JurGhl46Ni4yGn7CfkYS5tK2hqI2JiIeAraK+poyLioqJh4mKhpWgqqSsm5+PiYaTh4aHhYWFhISGgwOCg4KEgwyEhIOEhoaFhYWGhoaEhxWIiIiGhYaGh4aHhoaIiIiKi4uMjIyEjYSOaI2Njo6Mjo2Njo+Pjo2Pj46PkZCRkZOTkZGRk5GTk5OUkpSUk5KPkpGPlJaWlpOVlJaUlZaZmpicmpucnqCgn6Cfnp2foJ+goaKioqOipKSjo6SloaKio6Kjo6GhoqGgoJ+io6Gfn5+ghKKFoWWjoqOjpKSioaGgo56ioaOfoKOhpKKjoqKkoKampaOjpKWko6SkpaikpaepqaaoqKmrq6uqq6ysrKirrq2xr66wsbKzsrGvsK+urq6sqqipqqapq6msqaqqqaipp6ampaKmpaWkpYSnWKmrqayrrKyurK6vr62vsLCws7S3t7W2uL67vLy6u7m9wMLK0dLLzc7U2+/t9/Dv8uXp8vH/h4yMhYeFgomQkJSOjpGbo6GgnaGjoKGjoJuenaChoaShpqCEn4CampyeoaOosLKkmZeSjYKDf3x66uLh39zd29vc2tvb2tnTztPLysfBxMbCwb22q6utsLGyqKSipayoprCrt7KwpKahmJiblZSWk5udkpmYj52ipamkm6KioJqPi39/fvd9fIaBg4KBfoaGgYR8fIXud3mCfn6AgX5/goSEg+x5eVPy7fP0e4P1fvD39fDy7tns8Oni7vX3gH6AgICBgPGC+e/wf/Dh9YDw8vj6+/GG7evv8YKH7IHi5Pz08O6Bgvr3iIqAfHqIj4ODh42Fi5eel4SNk4B+g46PjYV6eoOGjIyJj46Ji4iKiYaGi4qDgYCBgIeHj5GVlpiVlpKRk5aWkpKRk46RkpSUlJqeo6Kdm5uSkJajoqKjoaGZop6cl5WfoJ6JmJqWioiSkZOSkZSUkpSQin53dXmKkYaSkZSRkI93gY6OkJKTj46RkpGQjoyLiYuKjICOi4eGgoqKiISBiIyMjI+JkI6Hvm17hYKBgoh+d9PpeHZ0eYOGx3qBeXp5eXiAe4J+g3+r435/1Nt7eHWAeuN8f3Z6eOZ1eHt5eeCV23l3c3mqcXd3d3Lo6sPo7XR3d3Hg2911dnh133R3eHNwc3BycHF1c3VxcXV16Xt2cHTd6YB44+zfe694f3162eR3d3F5dt97e3x6eX19f4CEf4OBgn9+e3l/enNyenXle3x9fnN8e3Vzdnd6fYCDgIZ8hIaEgYeNjo6SlJiYmp6foKKnpqutqammrrOwuLa5vrPBwMe9uLm4u7fFwcHOysnLy83EydLCwLy3tbWuv7ikmZealGCSkI2MwrWvopmNjYuMg5uwno+As7CroaeNiomIgKabtZ+NjIqJiYWHioaTnKScopGZjIaFkoaFh4aFhYWGhYaFhYSEg4SCg4SEhIWEgoSEg4SFhYSFhoWGhYWGh4aFh4eEhniHiYuKi4qLi4uMjY6OjY6Ojo+Pjo6Pj5CSj46Qj5CPj5GRkJCSkJCSlJKPkpGTkZSVlJSSlZOTko+PkI6TlZSVk5SUlZKRk5WXlZiXmJebmpyanJubnJ6enZ+goaOmo6Knp6Skp6ekpKOmo6Sjo6SmpKKioaWnpKKIo4SkcqOlpaSlpqSkoqGkn6Kgo6ChoqKkoqSho6Ofo6OloqOio6OhoqKkpJ+ioaGhnqGjoqOkp6SlpKSnpainp6moqKmrqqurqamqqaiqqaqpp6impqmpqaqoqaqpqaunpqakpqelpainqKeoq6uqqaqpqqirq4StgK6vsLGxsrWzsbG0vLm5t7W4tbW4vMPJxr7Ew8TJ3+Hs4uLi1tri5e58gYF7fH14gIWFh4OChYyUkY+PkpSOkZOPioyLj4+OkI+Sjo2JiYmDg4OChomOlpWKf356dm1vbGdmxr+8vLu7urq6uLi3tbWvrK6qqqehoqSioZ6akZCSgJaXlY2KhomOi4qRjpeTlY6Oi4OEiIOChIKJi3+IhYCPj5KVkouOjo6Kg4B1c3PhbG92c3R1c3B4d3J3b293zmZrdnFxcXNxdXZ1dnXcb3De2+Hfcnrkc9zl5eDc2sTa4dvW4ObseHR5eXp5eN526eHidODO4Hff3eLh6eJ62NjdItp2fdl30tTo39vQc3fp6Hx+dHFveX93dnh8d3mCiYVyfID/f5R/AX6Jf4J+hn8Bfo1/Bn5+f39+foV/AX6FfwF+hX+DfoR/AX6Ff4V+hH+DfoR/AX6RfwF+hH8Ifn5/fn5+f36Ef4J+hX8Bfph/AX7/f/9//3/ef7qAzH8Bfo9/AX6NfwN+f3+EfgR/f35/jn6Hfwp+f35+fn9+fn5/hn4Bf4R+BH9/fn+GfgR/f35+k38CAgQAgKCao62qpqmvuL++t7m8ury2tLOvsbnAxL6wqKm8xsbBtrOvsLW8xMDMysjHzdPY29jJ08TBx9jh5ubhw73S1tPX3ePf29TTxcTGr8DBwL+9wsa+vLzJzNLU0dDMyMbA88HBwcTHyL7Cvrixuaaow7u7rKOhoKWppKChp7G6sLW2gLWwrrGupqKdm52XnJ2enpyZnqSimpCLkJSXoZ+bmJWVkZ6bm5+Xio6WmZWWjYnbh4+EiIqKjIeIi4+LjZGTlJeWjY6MmJ2RhZWOh4OHkY2Jj4mAgouQjpKF8f6GhImIgvWMi42Ojo6DgY/344GJjvL6goSIh4iDhPz/gIOPion7SILljYf6iIySlJ2alpeWlJOWlo+ZmJOamJqcn6Ccl4OPjo+Hg4eHh4qMi4GJhIqUkJyZmZmempqeoaipqaeoqqWkqKmlpaijpoSngKanqKaop6impaO5oKSbnp2XmKWdmJqZmpqUn52uvK6jqaKcq6mxr5+boJWqn5evnYyWqJeYjJCjtamZl6Cnm5mTnMXd2snN2ubzf/PT3dLPy9/Z+ujmgM7Bt7bAysHEv73FwrXct7Cxp6ysrbS7uLOzr7GopJulpaSgn5yuopuggKGkl6KYlo+Oi42UipOhutjStKy2usOfk4uGhIWEhIODhISFhIaFhYOEhYWEhYaGhYWDhISGhYSCgYGCgYSHiIWHiIiKiomOjpCOlpiXkZGTlZKWlI6PkI+Ni4mIipGMkJGPk5WXlZaYmJmamZKSlpWWlJWTkpOWlpSRkI6QkJGTgJKSkI+Pi4iEgH+ChoaHhoWGkJGQkpOVlJKVl5iSk5eSlJKOkJCQk5KRjY+OkpORlpiZmZeSlZibmpyZlpmZlpugnKGeoKOipainqKaoqaiorKuppqisrLCws7GytbS1t7ewtrezsrCxs7a7urfBzMDBz8nKtc/U1c/l7OXR0NXYgOfW0trt5uTo8PDzgPTtho7pz766x7e9seGAg3vte39/iIaGhH6EiouNj+7leoSCiox+g/bw7njw8evu8HyAfn2Gh4SGiouSnZ6gn6ailYuKio6OjIuJ74GJgfR5en18fH+Ah4aIiomLioqNkJmcno+TkIX59/X19PHr6OTi4N7ZgN3d293Z29zd3+Lh29rc4ODj5OHf2+Dm5trZ2M7SyMe/sLGxwL27tre2ura0qqyus6+wsqelqJ2Zm4aYkImCge+EjJaOiY+Fh/qOmIeFg/iB+f+HhYOOhvz6gIKBgfWA/PKFjYaA+P7u3PTr/4H5+eTz+/Pwf4WC9+nq7ufr7n+GNOn26/fs4+v5gfz4+fnm6/Xn9/D99/z2joCHh/XzjYuCiI6MipGKipSXmKWkp6KmsaifnqOAk5KaoZyanaKorqunpqqoqaWjo6Gjp62wqp+am6mvr62npJ+hpqqxrri4s7W6vL3AwLXAsq+3xMnNzcqyrb/CwcPGysjGwL+0sbOer7Gysq+ysqqoq7S2vb+6uLezs7DZr62usrK2sLGtqqSqlJSvp6qfl5aWmJ6bl5icpKigpaaApKKfo6GbmZSTlJGTlJOVkpGVmZmTioaLkZGamJSSj4+MlpKVmJGJjJSWkJOKhNWDiIGHiImKh4aJjIqKkI6PkpOLjImSlYyCkouFgYWMiYeNhH1+hoiIi4Hv/YGAhIR46YiHiIeJiIB7ie/YfYSH6e59foCAgn6A9vB7fYaEgvOAf92IgvGCh5CTk5OSlJGQkpSSjZSUkJaUl5eZl5aVgY+Mi4SCh4ODh4mJfYaDh5CKlpaUkpmVlpqboZ+hn56fnJ2fn5yfopygoaChoaGgoKCjoqSioJ63pKWfoaOcnqijnaCfoaGZpKCwvLSrr6eksbC4sqSippyupp+1p5Wgr5+An5Waqr2zpZ+prqSjn6fK4N7S1uDr94D23OTa183Z2vPl33zPxL69x8zGxMXBysW53L+1tK6wtLS3wr65t7K2rqehrauqp6Okr6eho6SlnaadnJWTkZOYj5ihuM3Jt6qwtb2fmpOOjY6MjYyOj42Pjo+Pj46Qj4+PkZKRkJGPkZCAkZKQkI+PkJCSlJORk5SVlpSTmJmbm6CjoJyZm5ybnZyXlpeZl5OSkJOYlZeXmJebm5ucnZ6io6KcnaCeoJ6gnpucoKKgnJiXmJydnpybmpmZlZOPjIeKkJKTkpCSnJ2dnp+goaGjpqWhoKShoqKfn6ChoqGfm56doaKhpKapqKaAoKOnqKqrpqSopqaqrqqsqqyvrbCzsbOxsbO1tri2trS0trq+ur2/wcC+wMTBucLGwL7Aw8LExsfIztPMzdnV1MPX2tvX5u3s2dff4u7i3eT37u3x+vj4gvj5iI/s1snG1cHEvueFioP4hYiHjoyNioaMj4+Pkv/2gIiHjo+Eif+A+vmA/fv6/P2DhYSDioyKjI+Qlp+ioqSqpZmTkZGUlZKPjf2Hj4j+goCDg4OFhouOkJGRkpKRlZefoJ6PjouA7urn5eTh3dnX09PR0tLR0dDQ0dHT1NTU0M7Q1NPU1NHRzsrS1snJyr/CurmzpqeotLGwq6yur62spqSmqqenqaCAoKKXkZaAk4yGfXrhfoaOhYOIgIHuho+Bfn3we+f2fn57hXrs7Xp8fXrrefHjfYF/euv149Tu4PZ88vLZ6/Px7oCDg/fp5ujk6vGAgefy6/fs3+Pwffj1/Pni4ebl+Or39PXtiHyFhO3nhomCh4uHhIiEg4uPkpqanZqbpJyWlJhYgoKHjYqIi4+SlJOOjZGQkI6NjouNkJGSjYeFh4+UlJGKiomKkJKUkpiZlpWWmZaanJGYj5CUnqChoZ+Oi5menZ6dn5+dm52WlJaDk5aYl5aUlYyLiZSYm4ScgJmal7iSj4+RkpSUmJWUjpF8e5OJkY6Ig4ODioWEh4qPkIqNjIyMjZCOiYmHhoiJiIeFiYaHiI2NiYKAhIOEjomIhoSDfoaCh4iFfoOIiYSEfnzEdXV3en5/f359gIKCg4eEg4iHgoJ/h4iAfIeCfXl/g39/hHp0dn98e3944/F7dnd7emrWfn5+f3+Ad3B93Mh0dnva23N0dnd1cHXl2290d3h15HbUgH3ifX+HiYqJiouIhomLiIaKiYaNi4yOkI6MinuIgX95fHx5d3h/fnV6d36EgY2Lh4aNiIuNj5aVk5WTlJOTlZWTlZmSmJiXmpmZmZqbm5uEnYC2o6ago6Wio6yooaKho6adqqexwLSws6+pt7i7uKuoqp6yp6K1q5ehr6Ggl5mrvbWmoKqxqaikrMzf28/R2ebufO/U3tfTztHL5djFcsC7vb3EycHCw77EwbDSu7S1srGzsrjBv7q9srmyrqawrK6ppKOwqJ2foaWapZ+dl5ORkoCZi5GYqLKvrJ+kqK6Zm5eQkJGPkI6OjpCRkpOSkY+Rk5KRk5WTk5WTk5CRkZGSkpKRj5WVlpKUlZSYlpWZl5uZnqCempydn5qfnZmXm5uZlJGPkZWQlJOUlpqXlpaYmp2enpmbn5+fnp6bmp6fop+ZkY2Rl5ecnJqVk5STkYqEfkKAio2Qjo2RnZ6doaGgnp6kpqafm6GeoqKdn52en56fmJqan6CdoaSopqScoqanqaejoKOjoqapo6ajqamoq6uqrKqEq4Csra6trq2wtbK0tbe2t7a+urS6vLi5ur69v8LFw8rNxcfQzMy+09LX0tbh5dfP1NTo2dbf6+ro6vTw8Hvs8IOC18G8u7+pr63TfH986n6Eh4yKiYWCiI2LjI358X2FhIuOgoP07O98+vn1+PuBg4B/hYSDhYiIjZWXmJifm5CJh4CJi4uIhYPleX954XNydHN1dXZ6eHl6ent6en6Ci4+NendyacXCwL28vLi2tLKxsa+wsbGwra6vr66vrquoq62urKysp6SjqKmgo6OboJiYlouNjpeXlpOTlpaUk5CRkpeVkpKPjY2Hgod4hoB4cWzPb3d9eHh6dHHWeH90cXPYbmTI3W9xcHho0tNucHBu1m7dy3B0cm7W3M/C2crgcd7gw9vj4t55e3zh2NbZ2Nrjen3Z5dvl29HR3HTk3+vo087S1+rX4+To3H5zeXnbz3l/en2BfHd6eXR7fYOIh4iIiI6LhoOE338Bfs5/AX6qf4J+hX8Bfol/B35+f39/fn6Hf4J+hX8Gfn9+f39+/3+SfwGAi38BgP9//3+2fwWAf3+AgIl/BICAgH+NgIJ/h4AEf39/gIV/moAFf4CAgH+YgMx/AX6IfwF+hX8Efn9+foV/gn6EfwR+f35+hH+HfgF/h36Df4d+gn+IfgF/jn6Ef4J+l38CAgQAgLjAsqyyvcC0sKm1v8XGxsO9vr/CxsK9u7/Lzs3Qzc7L09DMzdXVzbm8vcLHzNbX0MzMytva3+De29TEzOHo8fHt6NvP1NfX4N7g2NfSz8mimMfDvbi9vru9pKq1ucG/rLe8uLa8tby9urS2u7myt7K7wcTAur26u7y9v8C+tLa2gLSyq66ppaaZpa6qq6mpqaacmaCinqGfnZ6lpKSgmpObnZqYmpaXmJmam56em5ucmZqYmJuUk5ealpWTlJudoJuUlZ+hn5+YkZKQj5SbmZuak5KQi42Vl5qZj46Oj5Who6ejmJWWmJuSlJuMion0h4OHjZve0IXrioeGio6H5oSCVd+Ki42MhOWE8Y2LlJudnZSVmoOXlpaPnqCdnqKgo6ObnqKhnKCZnZ2amp2ZnZmbmJGeoqeqq6OorKytrKysraupqqajoaGipKaoqamoqamqqaeoqaiEqYCmo6GemZibnqKooJuLj6mVjp2Xm5qhn7GloKWjpqGfm5qbmZiTkI2OjYmAs6+5sqipnot+fnx6d3Z5en5+gICGf4WFioiOk5GTmJ2go6evyM7Z3N/V3uPn6ejw6O/o8fHp5ubj4NLX4Nrf7Oro4t3g5Nfh5ejb2P6D/YGAg4CA9oCGiY7//+rn3+jb1bq5wa6ouKm2pKuetLewrL7T1dDJwsLWyc65tq60say2s7PAxMLFwcXExMHDwbu9sLC+ucjDxcvJyMnGxsjIy8G7vr68trPG89bTz8jJx8fL0crHzMrLzdbk1sTAtsTPwcm9wcfKycnMysnPzMvL09DPzMXBvYC9ubS1r7O8v766u7e3uLiztrO2r7O6t7Gwr7GytbGio6iwxK2loqKjo6WqsLSuqauqrbGuqq+ztLa7xM/R0tHU2tzZ2Nfr5+XU5uLR09/g5+57f+3n28bIx7+91Nfmz83uh5ijqIDv84qRkomJlPrrzNTr9X+FhoCCiYeNkJegn0n+3+bKwM7x0MHn5OfR0MzCycbSzMXh5M/oe3t+9/qUpZylo6Lc+ODe8ISOlpmal5CVmpeYnZeKhoqRmZuJgIGFiIWFgIB+fX1+hH2Af35/fHt8fHyDf5WJhoaEg4KGhYCDipmpnvbt6+zq5OPg3Nva1dfa3Nrf3uPi5+nq6+nq7Ovm3uDl5LTF3+rs6+no4dnZ08zIwsjLy8rLwL66vb++vbavrqqnqKilqaSemZaLhIyBkJKNjo2VkPyG9YiJ+/vUjIiAgYfTgYT05u9a6Oz88fnv1N/v7d78//6B9fHu5vn99Of0/oD5g/T0/ez25Ozf5uz5gOzjza7U6e+FiPr0h4aChoL/88j6/4H7/4T9kZaQmZWKlJ2moJmatMG3qaObk5CUmJikgKmqpqClrKykopumrrKzsbCrq66usq6rqayzuLi4tbSyvLu3uMHAuaisrLG1uL6/u7i4tcTEyMrHw72vt8jO1NTSzsW/wMDByMbGwsK8uLORirSyqaOnrqysmZ6oqq+vn6itq6eqp7CurKioq6qjp6Orr7Gxqa2srautra+upaajgKGkm52bmpqNmJ+dnpydnJqWlJmYlZeXlZSempmWk46Tk5SSlJKTlJOVmJqbmJeXl5aVl5ePkJSVkI+KkJeXmpWPkZqZl5eQjY+MjJCVkpOTj46MhYiPkZKTi4yLio6WmZuZkZCRkJONjo+IiIbsg4KFhpDPxn/ghIKChIOD0oGAgNWEhYeJgdyD6ImHkZeXl5GUlYGUlZWNnJ2amZybnp2XnJ2bl52UmZmWl5aVl5OWkYyXm6Gjo5yipKOjo6Sko6GioZ+enZyen6GioqGgoqOioaCio6KhoqKin56cm5iYm56lqaGdjJKtm5ajnaCgp6e5raiuqq2pqKOjo6GfnZ2ZgJmYlIy8t8K8s6+oloqLiomFhIiJi4yNj5KNkZOWlZqenJ+ip6uusrbP09nf5Nrk6urs7Pbw8uzx8ejo5+fh2dnh3OXu7ezn4+Lo2uLl5tza9X/6fX5+fXv0g4KG9/fk4N7k2Na9vca2sLquuK2ypbO4trTCz9LPysXB2cvPv723gLi8tLu7usXFw8vHycnKzMvIwsW4t8HAz8vL0M3Ny8vLzs3Kx8XFxMO5usfp1NHOx8fDxsrRzMvOy8nR0+HVyce/ztnR1sjI0tXX1tXS09XU1NDX2dbR0M3KyMS/wb2+xszLyMfFyMjGwcC+wb3DyMm+vcC/vr+9rrCzvc23sa6ugK+ws7e+xLu0t7i6vru1uL6+wcfO2d7a197m6OLg4e/u7d3s5t3d6+7z/YCE+PDn09PUzMje4/HY1uyDjpqjf+3xh46NhYON+O/V3PD7goiHg4aMio+Slp2c/+jt28/i/ubZ9vL04+Lg1NvW5N/W7+/Y8oCAg/78lKGXpKKh3vvmJOLzhI2UmZyalZebnJqdnJCKkJadnIyFh4uOjIuHh4WFh4WFhIWFgIKChYCCiYWZjouMjImIi42EhImVppzm29zb29bV0tDQzsrLztDR1NXV1drb29zc3Nre2dLS19emsc/X2NrY1tDJxsK+urS5vb6+vbOzsLSxsrGspaaioKKhn6KelpORhX+IfIuLhYaFjo31guuCg+7uxYaEfHt+xH595tnk4uP0V+3z6s/Z4+PS7+zye+zq6uX19urj9Pl++oDv8fvp8ePt5evu+H7s6cut1+rsgob6+IWDf4SA+/HG9/h98vN+94uRi5WQho6TnJmSkqavp5uZlIyKi5CPmYCQkY+NjZGSkI2FjpOVlJSTk5SUkpeVkpKVl5mampaWk5qYl5mhoJuOkZGSlZecm5qYlpGenZ+gnpyVjJGcoaWko6Kdmpqcm52dn5qbl5aUc2+Tk4iEjZSTk4GGkZOYmYqRk5OPkYyVmJeSkpeUj5OOlZeZl5KWlJSSk5aVlI6OiYCLioWKiIyJfYeMjY2Nj46MjIiLjIiKiIWEiIaHhYWBg4SGhYeGhoqKioyNjYyKiYyMioqIgoSHiIWDfYaJioyJhYaKiomJg4KEgoKGiYaGhoOCfnyBg4OEhYKDgoGEioyKiYWGh4WGg4OAf3583nt7e3yEvblyy3t8eH14eLJ4eYDOfHuAhHvLfNqAfoeOjYuGiIt8jIuMhZCSkZCSkJCRjZGRkI2PiIuMiouMioqEiYV/iY+UlpWPlpmWl5aYmJiXmZmXlZaUl5eXmJqZmZmampmZmJqbm5uampmZmJqZmp+ip6+loY+WsaCbraWnqrGuxLazure4s7OsqqqpqaajnoCenpmPwr7IwLq1qpuPjo6MhoiKjZCOkJKXkpiZnZyhpKCkqa6xs7S2ytHR2uDW4+Xj5Ojx6urm6urf293k29rX4d3h7erq4+Hl5d3j5+XV1+d78XR1dnZx74B6gO/r29vU2M/QvMLMtqutpayjqpuprayuub/KyMG3udPJyLSyroCvsKy1tra2sbi9trnDw8bOyr6/sKq+u8vKxs3RzsrGyMrPy8TCw8XAtrO74dDHxsPEv7+/x8LEyMfJy9HYy8XHwdTg0tjLy9nZ29bZ1tjW1dfW3NvZ1NbQy8vIxMK+vsnNz8rEwsXFw77Av8G6v8fEvby8vry8taqrrbfEsq+spoCpqrK1vL21r7Kzs7ayrrC2t7e8xc/Rys7T2NTS0dDg3t/P1tbQ0t3m6/2Ahfrw59DP0MnF3uTv08rgd4CIjnbi5H2Dg3pxeujs09jw9oGBgoCBhYOHjImTjeLa6NTM3vvh0+nm6+Hj4tDY1uTc1OjqzeJ8fHzi6I+Uh5qcm8zg11PS5n2HkJOYk4+Sl5OVlZeNiIyPlZeGfn2Bg4GCfn57fHt8fHx7enx8fHp4enh6fneGenZ2d3V1eHhzdHR7h33DuLe3trOys6+vrq2srLCvsbKzs4S0gLW1tLa0r7CyrYWIpa6ur66rp6OinpyYlZmcnaCfl5aWmZmampaQjoyMj4+MjIyGhIJ5dHxzfH15eneAgeN413h529i1enpwb3Cxc2/Lv87Qz+Hd59u4xdPSvNjS23DU19vZ5+jZ0uTodO133+Pj1d7V393j5Ol13tvBqM7c4nt+Keznfnp2e3js3rvp5HPf4HXjfIR+hYV6foOKiIJ+i5KQiImFgX5/goGI/3/ufwF+hX8Efn5/foZ/BH5/f36FfwN+f37/f8l/AoB/hYAEf4CAgP9/z3+CgI5/hYCCf4aAhn+MgJl/BYCAgH9/hoCFf72A1H8Ifn9+f39+fn6FfwN+f3+RfgF/in4Df35/i34Bf4d+BH9/fn6Ff4V+BX9+fn9+mH8CAgQAgKqenKOttbu3q6aqr7S5v8LDwsG/wcbGzM3R2t7i39/g29nW1MnP0NHU19re3OXn7O7y7+rm7+3s1Mff6vTc1uj19fDY3KCr3d/Z393c19bV1NHPxr7Aw7+zsri7uLm9vsXKxL2+s7a4vsLDwsPDwru5vsDBrby3trS1srSwraqugLGsqqKloZ+Vk5WVmJ+ipaeloKalopipnZmhpKSimY6FjYyYmZeVlJSaoZ+blpDzmJ2fmZGSlJOUl5mZnKKioKCfnZ6gnJ+enpmbmZqcmZyelpGWoJ2ZmZ+knZWdpKChk5Sfop+jnZuYlJeenY+DjJKYmpSQiZCTmKCWhJ6cm5iYgJaSjpST9fuIi5WYlpWanZqUm52dmpWFlqGal5+enKWfo6anpqSkpqWhpamnqaurrKusq6emrKupq62uq6urrKupqaikpKWlpqampaWnqKipqainp6eoqKmqqqqopKWhm5SOlJygn6Cjo56jo6Cep6Shoqa8wbSag6e8rqCF5dDTgId8dHZ4fX6AgYSBeHh4dHRycXJyc3LZeJqCkZ+SgH1+eKWkpp+am52Zmp2ioZ6bm5mbnqOjn5SXlJCOiYWHhoWFiIeLjYyNl5qdnaGioqKgop+lpKqtsbGxtrvEu7rBwaWxwKCdk5CVlZOUnpOXiZKfl4jemc3ozP6MlZ6nifuAgJyZ/M+Sw92Xno2VqZOXp5yslY+OjY+YoKqwvKe1yb6+s6q3ta2nrLKloaXwhI+TjJOWrqq5v62vsauZgfPH2t/a4d/Z6NW/ycTM14Pj29rZ3d/s2dze2d3a0dLKvr+9xtPUzdPZ2uH709TUys3f5PaD4dLZ09bk0tK/wc7d0MLJgMG6v/CfwvbwgPzlgIatrYiblpyio6Gim6urpqS0s9PJxryupOyEiqaqsLy9vb27xbzG1ezg2uXl6Xvu9X6GiqOi68TM2LfMzs7xh/Pi0/N+5uvff4KC7X1+1srO1tXK2m/Y4eDpgHTh3+p1eHnNyMfZyuJ8foPO6+3OzcPC1emkgMDZ4uPq63uA7tDGyeWGjIWC9X9+fX+AfnzveXh5e318ent9enbq69/e49za5uDex9SDhILy9X/5f4aTmI/s5N3h4N3a3NvZ2Nrd3eDj5+bl5Ojl7e7o6ero5+Db4Ons7e3s7Orq6OHb4OTm5OHe3dnV0NPSxb6usbq0s7Otq66tgLCtqquYo6ufp569m5aKjpWaoJyb5JKUlpqSjpOMkYyPlZKRiIeG3eHv8vqDgffa7ezj5Ovx4u706u/w1cvg9vTs5PTg4tvyovV/gYGB/fmHiomC+eCXnJOPg4SLlJSKjIqhoKugnquvppyYlpOSoquxra6wvcO7s6yttbS3tLe3gJ+WlZqepqupoJqgo6apr7Gwr62rrrOztba4v8TEwsLBwMC8u7S4urq8wcPFw8nL0NDU09DL09HRvrHFz9bFws7X19TExYuYxcbByMfFwcDAv728tK6vsK2ho6ipqaiqrLK7s6yso6iqr7KysLGvrqupra+vn6unqKWmpqikop+ggKCgnZiblZSKiouNkJWZmpyalZuXmpKhk5Kbm5yako2DiomUlJSRkZGXnJuZlJDulJqblo6PkpGSk5WUmJqam5uamZqalpiYmJSXlJWWlJSVkY6Rl5aUkpeal5CWmpeYkJCYmpialpWTj4+VlYh/iY2SkoyLg4qLj5eQfpSVk5GRRpCNi5CO6/CEh46SkZGVmZWQl5iZmJOCkp2WlZqamJ2an6Ggn5+eoJ+an6SioqGhoaKjo5+gpaWioaKko6Sko6KhoaCdn56GoAmfoKKhoqGgoJ+HoYCcnZqUkIuRnKGho6impKmop6KqpqSnqsPFtaCOsMS4qY775+iTjICEhYiLjY+QjYaEhoOBf3+AgYKA9YekjZ2qm46KioWvra+sp6mnpKeorKuopqaipaerrKicn56al5WSkpKRkZKRmJiZmqGkp6isrq2trK2rsrKytri7ur7Ey4DBwcjGrbfDpqWbmZ+gnp+onZ6Um6WgkPKdxtrB8oWKkJyH83ySkvLGkbXJlJ2Nmq2ZnrCns5+Zl5mcpam4v8m1xNbHx8G2xcS8tre+sq+u5nyHi4KEiKCbp6qeoKCbi3jrxNre2+np4+rgytTN1duB5+Tl4ubp9OPm5uPl5trd1oDLzMrQ3NzW3eDl6vra2drR0t/o/YTp3+Tg4e7e3M7T2ubbzc/Kwsjwq8jw6n736HyBtbOGl5KZmp2dnpiopqOfq6nBubayoZ3+iZKvtbvFxsjKyNLI1eb67ufu8fOA8/iBiIienvHL1NO8ztHP9Ynx59r8gfL16IGHhfSBg+Pd5IDp6Nvseuru7/mGfPXx+X6BgN7Z1+Xb7oGGhtj499fU0Mzb8q3I3+3s8vh/hfjf1dXsh4+Ihv+CgYKEhISD/oCAgIOFhIODhIKA/Pnu7/Dt7PTw69TgiY2K/v+E/ICEkpaK3dTN0NLOzM3OztDS0tPT1NfX2Nna193d2dza2dnU0oDW2tvd2tnb2trX08zR1tfV0dDOzMe/wcC1r6GhrKioqaKgpaWnpKKjk5yjmp2YuZeShIqQlJualtyMjpKWjYqOh4yKiIqMiYOFhNva5ezzenjx0tXb0tHi7dbn7uXr6M3H4PTz6Ob14OPX8pz5gIKCgff7iIuLhPrYjZSOjX+CiiSSkYmJhJuYoZaWn6SelpOQjYyZoKShoqKssKqmoaKnqamnp6aAjouIi4uLkZKLiI+Sk5SWlpaVkpGVmZqZl5mbn6CdnJ+dnpycmZyamJifnp2doKCioqOlop2ioqKVjJqhpZmZn6ampZyfb32hoJ2fn56dm56dm5uWkZOWlIiJjpKQi5CUmZ6Xk5OKkZGVlpiWk5SUkpKWlpaKlpKTkJKRko+Pjo4NjY+Oi4yHgnx6fn1/h4SLJ4OIgoSAjIKEio2OjYiEeoJ/ioqKiIeHipCQjYqK6IyNkI2Eh4qIiISJf4qLjI+Qjo2MiIyOjIiKi42LhYeIhoWHiYuKhoaKi4iLjIiKhIaKjIqKiIiHhoOHiXx1goOEhYOCe4F+g4mFd4uJiImHhoSChoXN3Xp8goaGiImMiYWMjZCPiHaJkIyLjI6LkI6SlJOSkpGSk42PlJOUkpKTk5WVkpKYmJaVmJmGmAaZmJiXl5eGloCXl5iWl5eYl5iXmJeYmpmUlJOOioaRoaitsLW0sbS2ta+3sKuvs87NvKGMssa+rpD+5eWQiYKEh42Oj5KVkYmJiISEgoKCg4SC94enjJ6pm5GMjYa0ubiysLOwqq+rsbCrqqekqKisqqecn56cmJORk5KSk5aVnJmdn6amrbCxsoCytbKysrq7ur3AwMLDydDGwsvHr7a9p6qhm6CgnqOtnZ6QlaGgjOGVsbiYxW5yeIFwymd7er+YcZGieIR7h52Tm6+fsKCYl5ecpK27xdG8z9/O0Mi8zs7DvMDIu7Oq0W52f3NudYWEj42Ki4WGemjNs83a3PDz7+/m0dvS19t86YDu7uvs7fvs8fHq8Ozj4tvLycvQ2tnX3tvb3ePFwcS/uL/L73rg1NvZ2+jc1sjK0t/MwcG6sbrcl7DLyG/fwmVuqKJ3iIOJjIqKko6akZOSnJihmZqbjIbaeoKfp6+7wcTHw9DE0eH06ebv7/N88vh+hXyOkeTFx8WtusG+5YHo4YDe/YP08uB8fnzvhYTn6O708uX9gfX27/uHgfX3+oCBe+Le3ura6YGEgtDw89DQ0czN5p/B3Onp8/aAhPfZ0M7jhYyEgPJ7eHh5enl46nV0dXd6eXd5e3t26erf3d/d2d7d2cPOe3d25ed0329xd3hwtq+qr7Guraysra+vsLGxsICysrOztK+zs7KxsrKysa+vsbCxs6+wrq6tq6SqrrCwr66tq6afoKGZkYeJkI+PkouKj4+RkJCPg4qQh4uGpoWFdn+Dh4yNjNJ/gISIgH2BfYKAeXt+fXh7e9bP1dnebGrgw7fGu7fS3MbZ39XY1b+41Ojj1dLo1NXC5Zv0fX59fDPj7IKEg3/qxn6BgIF0eIKHhIB9eImIjIOFjY+LhoWBfn2FiY2LiYaQkY2PjI2PkJGQkJH/f69/AX7Wf4J+9n+DfpZ/AX7cfwF+hX+FgAR/gICAqn+QgI9/AYCkfwGAl38HgH9/gIB/f5SAAn9+lH8DgH9/hYCJfwGAhH8KgH9/f4CAgH+AgId/AYCEfwiAgH9/f4CAgIZ/g4CQf4KAhX+EgAF/h4ABf4uAjH8HgICAf3+Af4WAzX8Bfol/AX6Rf4V+gn+cfoR/gn6Ef4J+q38CAgQAgNDP0MjGxcvbzcjJysvJxsrLz9LJxsjJyr64vL68wsTGxsvMzczNzNPW2t7h3eLk4uHe2NjV1tbb2t7f3dbNycCzzNfUyfSb0trW2NjV1dPS0szPzcvKzMvLy8nOube6xL7CxcHBwMLEvpmkwMLCwMK+wMDAu7KrrrO5t7e2trOvaq6urKObmpuZmqOboaagnpucl52jpqOgnZiZmJeYk5GRlJWWmZiWlpOQk5GPkJCKlZmXkIqNlpqdn56dnJ2dnJycmp2cnpqZlpqenJ6enqKhnZuepqKam6ampKKlpKCen6CjoJ6fnZuam5qEogGkhKVgp6Smp6WfoZWboqCgnJqboJ2Nkpuampn8mZWPg5udmJieoJ+bnZ+goKOjpaOen6GjpqenpqekpKaoqKutra2srKqqq6uqqaqrqaioqKempqanqKenp6inpqamp6anqainhKaEqYCqqaempaGfoZiajo6Oj46RmZmcn6Ojqa+soZuam5eWkYyHhoSKhH5+e3ZzcnF0dXJy4t13cNnS09HOx8XGx9nZwseWjnrecXF1dnl9foKDipCSmp6al5mYlpSUkZCPioeCgoWCgYCChYmOjpCam5iWmZudnpeZnJybnZyVkJKQj4CLjYiMi4uQk5aQjpCRkp+Xlpqfn6KioaGjpq29v8PBx8vEw7y4t7ayra2yv7KzsbS4vrq7wb68usLBxsXDzsvM0c7P0NPN0svNycbFwr+6vLrFwr6+0s/Y0tHV0tbZytfR2/WNkdOLmq6bpdShq8DcmKyxr7C9usCuqbCwqZuZwYDAk/b4h5ifnau6uMXIooaMh4Xxrr7Nva2zqa2mtrTDuLStt6+ZqcCsjOLLjI3JhpWS04yNmsDQ+I+OipSYlJqXjZSXlYn7+uDMxsTO2ufg4uPf5oGHh4ycrKSknKCfoJuXnpyhnZTPtbXGucbM0tPT2NXS1XBycXFxcNXQ0cvJv4DC3nV78bKYx8HFwbaknp+0uJqXnJO82dnJ9Lrn1o2jzNV+wrO3rq7FxcTEwsy9q7K3wcTLzs7OyczP1dvf5OLi3tXa1NPY0s/PzNTb1M7Kz8TCzcjL0N31h66ijomFhILx6ufn6Ojn4+Dd3t/c3Nze3eLf4ePm6Obk5+bn6Obo54Dl5ufm4uPl5ePg4NLO0NLR2Nnc29rb2drX1NLQzMzHxL65s6ekrq2YoaeqlZSIn6qtpKytrKqsqqmsr7Gwra6opaiIpqaek5aIjozi3ZOBhYuQiPTa4fH69tyu/YHi88/Ly+/6hvzt+Yb8hIrL9IGChoeLkoWDf4OMhoGLko+blCWUlYWEj5OQhIyxt7KnpaeppKivv8THwb+qtMTHxr++wsPHycrMgLu8u7SxsbXCubW0tLWztLa1uLq0srS0ta2pq6yrr7G1s7W1t7e3uLu9wcTGw8bIycjFwMPCwsLExcnJycS7uLCmuMLAt+SPvsXBw8TBwL29vru6u7m2t7e2trS5qqeorKuxs6+vsbCzr46VrrGvrq+urq+vrKSbo6eqqqqnpqWigKKhn5mUkpKTk5mQkpuYl5SUkJeanJubmZOTkpSTj4+PkZKSlZWSkpGMk5KNjY+HkZWWjYiJkpWXl5eYmJiXl5eWlZmWl5WUlJeYlpeWl5mZl5WWm5mUlZucnZucnZmampmZmZiZlpSVlZKampmZmpuam5ybmZycm5OWi5GYlpmUE5SVmpeGipSVlJP1lo6JfpWXkpWEmTKbnJydnp6fnZycnZ2foKGfoJ+goqKipKSkpaSko6KkoqGioaKhoqKgn5+goKChoKGhoYagB5+hoKCfn5+GoDmfnp6bl5eakpSGh4qMipOZm5yfpaeus6+mop+in52YlJGRj5SOiYiGg4GAgIGDgH/394Z+9/Hy8O2E5oD37dnhoZqG+n9+g4SIjYuQkpqboKWqp6Sko6GdnZiXlpSTkI6OjYuLjY+Rl5maoaCgn6Chpaeio6ako6aknpmcnJuWl5WYl5abnqCdm5qbn6iin6WrrK6sra2ysrfDxszM0tXQy8LCwsO7t7i6x8LBvcDEy8jK0MvIxs7P0NLR24DY2+Dg3+Hi2NvU19bQzc3Lxca/zdDHxdjT3dnY3Nrf3s/e2+D1iYnKjp+unqnSo6m1zIiZoKKhraqsoZump6GTjqytivLzhI+XmqKvsbW2lHyAe3nbn667rqWroqSgrq24railrKGOna6ig+XLiYXAfomHyY+RncPP8IyMiJKTkYCYlY2SlJGH+vvi087M0+Dv6enn6e6DiomMmaaen5mfn6CYmZ2anp6T1b++0cXQ3Nzh5Obo4eZ3enp6eHrr6ePi49bX83+C/8Ck1tPa08++tLLHy6ynrqfI5uPX/8D+6pWx1OOBz8PIvbzP0M7PztnLvL/I0NLa3d3c2Nvf5eru84D08vDk5OPl5+Hd3tzj6+Te2N7W0tnX1tzp/4uwoZKLg4SA59zZ2NjW1tXQ0M/OztDR0s/S1NXW1tfY1tjV1dnX2NnY2NfW1dXU1dXV1MjCw8XFzczNzMzLycnHxMPBv764trCrp6CapKaQm52gjoqCmaGknKGmpaKlpaKipqmnomeko5+hgp6emIyOgIeH19SMfIGEi4T01+Du+PjcofaB4+3KyMTt+Yf+8PeE+YGIxu+AgIOCho+Eg4CFjYV8gomHk4+OkYWEjI6LfYOmqqScm56gnqCkq7Cyrq2do7KytLGusLS1tre5a5qenJeWlZibnJuYmZmZl5mZmZuYlpaYmJORkZSUlpSUlJWWmJmXmJubnZ2dm5+goZ+enJydnZycnJ+enZyXlY6Ikpubmc18naOdnp2doJ6gnpuanJuamJmYlZSXkI2NkI+VmZSUlpWTk4CBhJR9lpOWlZWTkYuPkZaUlJSSkI+Njo+OioeFh4eHfoOMiYqJh4GKjo+PjoyIiYeJh4iHhoeGiIqJiIiIhIiHhYWGfIeLioJ+foeKiYqJiYiJiYmKi4qMiouIiouLjIuLi4qLi4uJi4yKh4mOjY6Pj46PkJCOjo2Li4uKi4mDi4yEi1eMioyMiImMjYaHfYWLi4yJh4iLiXt+ioiJiOGIhoBzioyGi46Njo+RkY+RkJCRkpGRkZKTk5ORk5GQk5WUlpaWl5aWlJSWlJOVl5iYl5aYl5aYl5eYmJiEl4CWlZWVlpeXl5aXlpWWl5iYl5aVlZKRj46IioCBhYmKk5yjp6uxsrq9vrKuqayqqKCclZaWnJWOi4iGg4ODhIWBgP/6hID58/Py7+Tk4t7u6djfoJuI/oKAhoiPkpKWmp+mq6+1r6ilpaGfm5eXmJOQjo2PjI2Mjo+SmJmZoqGenICeoKSon6KopqWmp6Cbnpucl5mYmpubn6Ckn5+enqGupqOmq6uxsbW0tbrAzc3R1dnb19TMyc3Ivbq+xNTK0c3R1NzW1d/e2dbh4eHj3/Dr5+/w8fDx5O3e4uHZ2djSyczF0dPGxdrV3d3d4uPr59Pp5ObsfXuve46aipy+iI6VqYBzgImKh5CNkoyLlZmRf32Qjnbj7Xd9i4mOmJaViW1aYV9ZqIqWmI6JjIuVk5yam4+NiIyGbX+NiGuwnWtjkmlzcKuEipO2wNl/f3+IiIePjoSFiYx/5OTXx8rI0N3n5ubj5Od+hIOEkJKPj5CWmpmTkZeRlJeR1sPH1s3c5+3z74D0+fb6hYSBhIOA+vzy9ffk5f+DhP62mNLU2djOvba1ycysp62iw+Lfzeuz5s2IptHggci/xru0ysrIycnQw7O5vsXIz9HS0s7P0NTX2d3d3NrS1NHS1dHP0M7V29LOydPIv8bEwMbQ33ugkYJ5cG1pvbWztLazsbCwr66vrq+vrgSur66vhLCArq+wr7CvsLGysbCwrq2tsK+urqafoqSnrKurqqmppqWlpKSjoKCZmZWRkYuFj5ODh4iQf3d1ho+Si42UlpCSkpGRl5WVk5ORkZB1j5GMgYBxdnnHxIFxdneBfObS2OHy79OV5HvR2b7AuOHugPLi637rfH6z43t6eHd/iHx8fYAth39zdXx8h4OEhn19hIZ+b3WPkI6JiY2RjI2Nj5KVj5OKjZSUmZiXmZqcnZydxX8Bfv9/xX8Bfv9/Bn9/fn5/f41+BH9/f37/f55/goCLf5KAgn+OgAF/loAIfn5/f3+AgICHf42Ajn+TgI5/hoCIf4KAl3+CfoR/AYC2f4iA63+CfoZ/iX4Bf4d+Cn9+fn5/fn9/fn63fwICBACAycezrba8u8XPvNDT1NPS0MzN0NLS1dfPyMG+wMTIyc3R1Nna3uHi5ODa19nY2tfT1NTT1Nja2Nzc29rX1tHMxcjGzNbc0MrL09XPy8rEx83Pzs/P0NHOzMvLy8zBsbCvv7y6v8C/wb++wL+6traxr6qnpKWko6awtLO1trS1s6yAoZ2bnqSto6mrq6agoo2PmIHclZ+jnZydmaelpKajoaShoaKfn5+MnKCenp2ai5SSk5eZlYuJmaOlpaGgn6CfnqCZnp6goZ6hnaCkpKGmp6ikpKWnoqKip6alpKenp6Wkqaqoqaqoqqerq6yqqqqoqaiopaKblKOipKCZmp6elqJOoaCioqOjoqKjoaGen56dnqGfnp2BpKGhoqOhoqKkpKGko6WlpKWnpqamp6ioqKenqKmoqamopqWnqKampqWkpaWlpKenqamnqKioqaqphaqAq6qlqamqqamop6qqqaipp6Wlo5+amZqcm5qVk4+apKWur6+qpKCeopqenKGYjoqIhoWCgX5/fnp7e3Vw2dTU0tDKydDU29ra09d93tjb1drecXPj33Fwb3p8fYSGgYaLi5ORlZCRi5GRj42Qj46Ljo6Qj5GQkI+Pk5KPkJKVlZaAmZqYmp2gopydnZyamp2dnpyal5uZm5yal5qdmZmVlZWSlZqalaWtn62u2NXQwsDG1NDK27qxwcK/yMrEubm6t7G3uLStpK/t9eHYwMDBwMC31dnw4+Hp2uDo6fDh5Ojp7Op89ICFhIeA8fLjzoaA9+zg+ID+iOnM7NvI5ub+g4WAlYWVjpmPjonzu7TZcNne0ddvc+bhz8zJzM3M5ul+7+V16fHs8e7o5ufj39PQ2Nfc18vN0934fn17fnyEhoV/fnyGiYqHf4SC/IOAhICDhYuHjJCGgJaN9viLgYKKfHr06ud34XTn4r/DqJ+UkpqXrsvL4OD23tDhzJmZn669vsx6zb+mqJaTmpien7Oxqqmnr7WwtLevop2hmZCdsZ2ksa+praWrsq6gudLd5dHL1NS6tbm7vb3DxsfExsPGxsrGxLvAydPY2tvb3dng19za1tzj08TFtLfAxtaDgomOmJCHhYHw5+jp7e3u7enl5OHg39zb3dza3N/e3d+F4IDb4eXn6Onn5+bk5OXi4eHf397g3dzb19LU0svL0c/K09LNycfKx8LGx8jGy8bEwru/wL+4t7qwqbW5uLu8ub25sLS3u7i2rp+era6qraqWhJOZmKCQkJqglIv+iuXq19/kjdX++eX6g/7x+H+LkJeI44OCgH6HjoyEh5GLi4yQky6QhoWWn6atsK2lkJCSh4eSlqKptL7Awbu/wsO/uKu1wMHAv7/Ew8G9v8rS0tHEgLm2pqGorqyzt6i6ubq7u7m3t7e5ubu8tbCtq6ywtLW3urvAwcPHyMjEwMDDwcLAvr6+v8HDxMLExsXEw8G9ure4tbrDycC5ur/AvLq5tri7vLu7vLy7ubm4t7S0rqOgn6ysrK2tra6vra+wrqmppqWgnZycnZucpqmoqqmoqKihaJeVlJaboZagn5+amJqEipJ5y5CXmpiYmJCcnJyenZydm5uampqbhpibm5yZloaRjpCUl5KEhpWdnp+enJuam5qalZiWmJmZm5iZnJubnJ6fnJudnZqbmZydnZudnp2dm52foKCgn6CdhKEpn56dnZ6enJqSi5uamZiQkJWUjJeXl5mam5qbm5uampiYl5iZm5qal3qEnAGehZ0GnJ6dn5+fhaAboaGhoKGfoaKho6OioaChoJygoJ+fn56en6CfhaGGooChoqGhoZ6ioaCfoKCgn5+eoKCenpycl5KRk5SVk5CNjJihpKesraqmpKOlnqSho5yWkY+Pj4mKiImHhoaGgn728/Lx8Orp7/P59/Xw9Iv88vbw8vh/gf37f39/iYmNkZOQlZian6CgnqCbnJ2bmZqampeXmpqZmpmampqbmpmcnICdnp+ioaCjpqiqpaempKOjpKWmpaWipKOko6OjpqilpKGhn5yfpKOdrbWosbHW1NPHxcrXzszZwLrFy8fP0s2/wcDAvL7Cwbastubu5d7Ix8bHxbzY2ezf4ePY2+jn9eLn7fHu6372gYOBg37x8OfTgXjw5dvvfvuE5szo2srj4oD7gISQh5SNmI2Lg/PDweh34ere4nZ68ejW09DNztDp74H28H74/vn//PLu8Orl1Nbd3ubj2N3f6P6DhISEgomLioKCf4uKi4qAhYP+g4OFgYSFiIWMjYJ/k4n09oWBgouBf/jz8X3ue/fvzNS1sKWlqam91M/h4vvi1OXQoKSwwX7LxdbYx7S3paeurra4yMjBwcHJzs7S1c26trmvpLLFtLjDw7++tb3Iw7LJ4Ozx4Nrk48zIyczQy9HU1dTY09LU2dXTzdPa4+bo7ezu7/Hp7+7o7PTl1NbCxM3S4YaFi5CZkomFf+fb19nc3dza2NTR0dDOzc3OzczNz87O0s+E0oDP1tfX2dnX19fW1NTU09XR0NDS0dDPysbGw729xcG8w8TAvbu7ura6uby5vLu4ubC2uLWur7CloKqwrbGxrrKspKisr66rpJWVpKWgo56PfYuPkpuMiZSYjIT3htvl0dLVisz49uL3gPXt94CJjZOF3IKCgoCFj4yAgoyKioqPkS6QhoSPl56lpqOej46NgYOLj5ufqK2us66wsLCuqaCmr66vsbGztLKvsre7vLy0C5ubkY2PmJibkomZhZo7l5iZmpqbnJaTkJCSlJaWl5icnpyen5+gm5ucnZ2enJ6bmpudnp6dnp6gnp2dnJmVmJWaoaigm5uen52Em4CdnZydm5qbmZmZl5WVkYuHhpOTkpSTk5KSk5aYlZGRkI+OjIuLjY2Nk5eWlpeYmJeSi4eGh4uPg4+OjIqJjH6DiG67hYqOjY6Ng46OjpGSkpSRkZCPj5B+jpKRkI6LfIaFh4uMhHl5iZGRkZCQkJGQkJCJioeOjIyOioyNj42MjR2Ni46PkIuNio6NjIuMjY+OjpOSkY+QkJCNkpKSkYSPVZCQj46Ff46OjYqDgYmKfomKioyNj4+OjpCPjouLi4yMj46PjXCPkZKPkZCQkZGRj5CPkpGQkJCSk5GTlJSTk5OVl5WWlpeXlpaXlJaUlJSWlZaWlpWElwWYmJeXmISZgJeYmJSWl5eXlpWVlpeWl5aVk5OSj4uMjI6NjouKjJieoaSmqKqqqaqxqK2xsamjmZWWlY+QjYyKioiJhID68vLx7+jn7vX9+vry9oz69frz9/yAgP38gIOCjo+VnpuWnKShpKmwrKuio6OloqKgoZybnJ6dnp2bm5mam5idnZ2egJ+foJ6hpKakoqakpaOjpaalqKelpaOlpqOjpqelpKKnop6hp6GYqK+fqqPKysnCvcTMwr3KwLbFx8rP0cy5wsO+v8HEwrWpr93q5d3JxMHDxLvVz+PW1tbN0OLi6+Dt8/X174H9g4SAgXzu8+TMeG3jzsDYdd982Mvs487k4/l8gIKMh5OHloqEfObCyvCD7/3w8YCC/PDSzcvGw8ri5nvy9IP6/fz8+Ovn7OTh087V1eDf09vf5/2HioyNipGSkYOAgoqJhYV+hIL5fHp8eHt6e32EhXV2hoPq6n98fIiAgP70+IL8gPv11dW4sqWlraq51c/a3/Tl0drOnqSuvsy/ZdTewrS8qKy1tLm6yMnBvb/M1NHY2s+4sLKto62+rLS/wLy+tL3Ev7LL5PD15OHq583FxsTIyc3Pz8zQzc3M0MnGv8bQ1tfg5efr7O/s8/Tt9P3m0dK7ur7Bz3t5f4GNhXt2br+0hLKAs7Gwr7Ctq6utrKysraytrK2qra6vr6+tsrCwsK+wsrGxsK+ur6+trq2urq2sqaWmpJ2epKCcpKWioJ6fnZqcnaKio6GgoJufoaCYl5mUj5ebmZ2dm5yVkJOXmpmXkoSGk5SOkY6Bb3p/hZCAfYaLenbofsjzy77ChL/q6NbpeeVF3ep4goWLe9F6enx5foiEeXqEhIODhYuJhH2DiI2Rko6PhYaCdnh7gIqLjZCSlpSVlpeVk46PlJiamJqYmZmZmpyfnp+Y/3+SfwF+/3/+f45+AX+GfgR/f35+/3+OfwKAf4WAhH+CgIR/A4B/gIh/ioCEfwGAhH+CgIp/BIB/f4CVf5KAAX+OgIJ/hoAGf39/gH+A9H+JgPd/CH5/fn1+fn5/hX4Ef35+foV/AX69fwICBACAztLV0Ma/wcTEqLa/wMLDxcLFxcrP0tTX2t3g3dvZ19XY1dbNycbDwcPDwsPBwcbHx8nIyMbGw8fIyc7S0dLZx77E2t3X1d7Y2tbU0s7Kzc/Py8fEvrm3t7q7u7euoqq2t7m9wL+/v7u9urm5t7WzsKuoo6WmpbK3tK2trKuqqqhopKCioKOqrKqurKinpqWfop+SkZWcnJidnZ6ho6OioqKhoaSfpKShoKCNoaCgoYuboqOiop+goKKio6OipqafpaOjo6Wko6Kgn56goqGcnqGfoKKlpaSmpaSmpqimpqWjpaOjpKSlq6qEqICnpaSjpqiqpKanpqalpKCcmp+ipKKioaGipKOmpqWlpaakqKilpqampKKlpKKkoKGlppifpqWmpqSlpKalpaamoqWmo6enpaampaOjpKWlpaeoqKako6OipKamp6eoqqmpqKWoqqurqqepq6uqq6mqq6ysq6mqqKeqqaenpqOjo4CioqCgnpyWlZiqt7e0rKWalpGOj5eYoKyUkYSBf3py2djZ19rT2M3IxcTBx8vGzNXT3HZzdHJxcdzb2uTe3eB3e3h1dnJ0cHBzcG5zcnZ8en6BgIKGiIuNh4yMjI2QkZKPjo6Pk5OWk5CLiIeEg4OJhYmPl6Cnsa+wsrCuqqqpo4CipKSmqKelq6qqp6KiqKSko6WlqqejoaOnpaWkpaGkqayppaasqqqoqamprayprq2rqKasrKqsqaSkqK2qrKyvubq7vsXEx8rR093X4vHy7Ofe3NXTydTy8PCBgYaGh/X7/4OBioyDhpSO/vnl38TY5Ov47n+C9YCOj4mIhIqKjoCNiY2GjY+KhfaAgYOBhIeIh4iJkI2Vm5iNr7rE1NDY0Nzj59HI1c7Uz8y2xr7F28app7Kys7C5yMLEwcbN0dLh4N7TzN/yg+B+i7G2nKaZoqarq6qrqKaprbClqK2trqOVmp6dn6eps7i1ppuVnqGqqqSurrGusLOln5qWnZqdn4Chp7Otr7Gvr7C5t7a1sbaxs7OktsjLy7u7v768wbu3trCwsLOwsa2nrbi6tLKqoKarq6eqrK61tczcz/uA5ISOl6WgievZ2+Hs7O3u7Ozr7ezq6efl5Obg3Nze3d/f4eDh39zd3N7d3Nzi5OPh3+Hi4uLh4ODg3d/f3tza19XX1oDV0tPRz8/GyMvKzMW+wLLEw7uzt7+8uri6yMjJx8fJx8nIx8XExLC6v7SxpqittqucnKKlpaOgmZiYmqGgl5aemJCIjZ2dmY6Pm5eJlJH8i5KTl5qYi4uZm+STmpmXi4eMlpCXmpqcm5qVlpablqKqsaSiooadm56YmaCZkpSXmxSmsLW8wMjHxL+0w8bBxcbBprTMzVa7vL26tLGxs66Opq6tr7GysbGxtbe6vL2/wMTCwcC+vb+8u7a0srGvsLGxtbKwtLW1t7i5trW2tri4ur/BwcK0sbfFycTCx8PFwsHAvLm8u7u5trWwrISre6qonZedpaeqrK+ur7CsrbCtrKupp6Win5ydnJunqqqjo6SkpKOinpmXmZmgoKCkoJyenJqTmZeMi46WlZKUlZaYm5ybnJycnZ6anp6cnJuInZyam4aWmZydnJicnJubnp6bnZ6YnZudnZ6dnZyam5mam5uVmJuZmpybnISdL56dn5+enZydmpucnZ2gn5+en56dnJybnJ6fm5uenZ2em5qWlZmampiZmpqanJ2chZ0tm56fnZ+enZ2enp2dnZmdoKCSmaChoaKhoZ6fn6Cfn5yfoJ6hoaGgoaCgoKGihaERoJ+enp2foaChoaKjo6KhnaGEooShCKCgoKGioaGhhKCAn56enZybnJubmpiZl5WSkZWlsra0q6SbmJOQlZ2gp7SdmIuJiYV/9vT19fnx8+vn4+Lg5enl7PLx+ISBgoB+fvf29P/49fqChYSChH+Bf32Bf32Bf4SJiYuPjpOWlpqblZeZmZmenZ2cmZqanpyem5mVlJGQj46SkJWboamstLUhtrWzs7Guraqrrausr66ssbGxr6qpsKytrK6trq6urK2whK6ArLGytLCxsLSxrrOytLWysbC1tbWwrbKzs7CvrqqtsLCzs7W6vry/y8fFx9PR2tjk6ezm4d/f1dDI1e7t8IF/gYCD6/j3gX6Dgn1/i4bz6+LdyN7q8//1gIP7gIuNiIeBiYqMioaLhomMiYX9hISIhIeHh4mIiI6KkJKVjLC8xtiA193X5evs2s/a2dnV0r7LxMjdzrCxvL2/vsnS0NDQ1Nzf4e7t7OHX6PmE4omVusGrsqq0t72+vLy6tri9wbq6wr+/uKastbK5vcHO0tDAta65usnGwMrJzc3P0r+7tLC3sra1t8DKysrIxMTH0M3KycXMx8nFtMfX1NfKzNLSztGAzMjEwMHBwr7Bvbe8xcvFwbyyt7u7uLq5usPH1+XZ/oLmhI6Xpp+F38zQ09jY2tvb2tnZ2djX1NPT0tDP0M7Nz9DP0NHS0M/O09LS09PU1tTT1NXU09LR0dDP0s/Qz87MycrIx8XGxMPCur2/vr+7s7aru7qvqqyxsK+usLu7vLp3uru8vLy7ubm3o66zqqWdnJ+ropSUmpyenpmTkZKUmZiNjpiWjYWKlpWSioyUk4WPjfCHkZGVmJKIhZOY5JCXmZaJhYmVkpSVlZeYlpCQk5aUnqOlmZecgZaUlpKUmJKOjpGWnqKmrK+ztLOxpLKzr7K1sJalubuAnp6foJuZmJuNa42WlZSVlpSWlpiZm5ycm56fnp+enJyem5qVl5WVlZaXlpeXlZiYlZaXmJaXlpeYmJianZyflZWZn6KgnqKgoJ+goJ6bnZ2cnJuZlpSSkJGPko2DfYKKjpCSk5OVmJaZm5qYlpWUlJGNjI6LipSWlpSUl5aVlJMUkYuLh4yQj5GTjo6PjYqFiot/gYKEiYCIh4mKjY+RkZGSko6Tk5GSkH6PjoyQe4qPjo6Oio6Oj46PkIyRkYuRkI+PkZKQkI+PjZCQkImLjoyOj46Oj46Oj46Nj46Oj42PjI2Ojo6RkY6OkI+Pj5CPjo+Qj46RkJCQjo+Lio2NjImKi4yPj46Oj4+Pjo6Lj5CPkZGSkI+RkCKPkI2PkpKEjZSUlJORko6QkJGRj4ySko6UlZaWl5eYl5eYhZcVlpaWlZSUmJaXl5eWl5aWlZiZmpiZhpcQlZWWlpeXl5aWlZaVlZaTkoSTgJCTkZCOjpOhpqelo6KenZqYn6mrsMCqoZCOjoiC+Pb49Pry9Ork4eLh6Orm7fL1/oiEhoWCgP369//59/2Eh4aFh4KFgoGEgICGg4mPkZOVlZqcn6ejnJ+in6Cpp6WjnqOfpqKlnJyXl5WQjY6TkZibpaqssq+urayqqKalpKWngKWnqampqqmnp6akqaalp6isqqeopqarqKirrKerrbStr6+wrqirrLCwraynrq6vrq2tsq2vrKyrrK2nsLCyt7SxtMfAtbvEw83I2dfTzc/P1MjFv8zj5ep+fH5/gOfv8Xx5fHVud4J/4OLWzcPa7fT/84CA9H2EhIB+eIOEiIOAAYeEfYB87X+FhH2CgoKDgX6Be36FiH+XprjJy9DH2OPh0MPOz9TJxrPAt8LQxaipuru6u8TXz87O09nY4O3m7OTY6fZ/1oORtbynsKWxtru6uLi5s7e9wLy5wL2+taWqs7G2vb3HysvDtqy5usnHvsnMzczN0MC4tK+0rrOztb7IxsbHxGLFxM3Lx8TEycbHxrTF19jYyc/OzMrMxr27urq4uri2s62zvsS+wbqusrKxrq+vs7e5yNfN73nRe4KKlIpwvauqrbKwr7KxsbGwsK+vr62usK6sq62rq6utraysra2srK2vroWvgLGwsLGuraysqqysraqqqamoqaimpqSjpJ+jpKSkn5ydkqKhl5GYmpyamJqhoqKkpaSlpKKgoJ+ejZidlJGJiI2ZkoWGjI2Pj42DhIeHjIp+goyLgXt9i4mIgoKLh32FhOKAhoiKj4t/fYeO1oaOj4uBgIGPi4yKi4mLiIKEh4qFJo+Rk4mHjHaIhoaDhIiGg4OFh4qOjpOUmZmZmI6ZmpWYm5yDi5+d/3//f/9/jn+TfoZ/h37/f5x/hYCDf4iAin8DgIB/kYABf5CArX8BgPV/AoB/hoD/f4t/AX6KfwF+un8CAgQAgLzDxcnLycfBvqmNtby8vcHDyMrJyMnJyMjLzczNycjCwMK/v7q7wMPIy8jJyMnJxcbEwsTDxsvKytHW3uDj0quyrs/W1dbV0c7Mzs/RycnIx8O/vri4t7q6u728raSutLq9v72qm6OusLS0r6yvtri0sa+ytrOzr66tr6+sraytDq6utLGwraWdqaurp6ushK0urKytq6yvr66trKmkop6enqGipKWkpaeooaSlpqekoaOjpKWlpainpqempKCdnYScPpSPnaGioaOlo6OlpKKjpaehpKampKanqKimpaWnqKilpqaprqunpKagpqipqaqrqqimp6ynpaOppaSgnKSlhKQXo6GfoqOdo6aloqWloKClo6KkpaWlpqOFpYCmpqinpqimnaWnpqOmo6OgpqenqKakpqWmpaalpaalpKOkpaSSpKanqKipqqmoqqurrKqpqqysrKuqq6Wqqqurqaiqqqqpqqiop6enpaOhnZybmpaZnKGrsK6noJmWl5KgopydjYF9fnZy43Z4c3Z7enl4dHTa2Nrb3N7ccNzZzBfP13J4dnh2dXFwc3l3dXh6dnp7d+Td2YR0gHl3dnR1d3d0cnBwbm9zbnF0cXB3eHNwe3p8gX58fXh0eoN/h5Wnq7+0pq6dq6iVnJ6ipauinJ+dpaelqaWip6aytLq1srK0sra1r7K5tbS1sbG4t7ivsbCts7Ozt7O0trOusLa7vbS1uby8uLGzsr/MuL28ubvBwsHHvbvExsXBgLu4s7Gws7e3usW+xcvLxsLJz8bJvbzCx8PBxsXAv768wcDDyMrJyMnW29nL1+Tl29TUx8q/z9fb19Dc1tXT1c3Oyc7I1dfX19La3+Li19PS1dzg1dna2tPV1N3a2dbZ1dXQ3tzc3eft4d7aydvf5eXj3tna2dbS0tDVztLOyc7MgMXIy8TCzcrAvr3Fv7vEvrOwt7usqJqTjJibnqWsqaeioJaXnaOpr6amwMnLu56ZpKqur6+lnqGiqaOhmZ+en52ioKeyrqyopZuXkpOYl5aTlY2TmaKtr6mmo5aGgYeOpqa/u7/m68aJkZSSj4eD9OHRzNfe4ufs7e7w8Ort7+/sgOnl5OPm5OTi3t3f4N7d2tnW1dDX09Ta3t/g4N7c3eHf4ODf3tzd3dvY1NHR0tbU0tTQz8zOzc/Pzs7JxsK8tr2/sbKxxcbFxMbAwcG3q6m7vcfDxMK+u6Klqbi2trizury2sqqprq+xrJaRmaKmqJ+VkJ+npqClo5WMj5qmo5uTQ46Rk5ehpZ+clpihn5manaOUkpeUmZydn6OjnZOGg4mSlpusrLW0qZyYm52fnqCipKiqqq+zsLCysbe6wcXHxcO/vbyArrKytre3s7Ctlnqmqqutr7KztrW0tbS2tre4uLi2tLKvr6ysqaqvsre4tre2t7eztLKxsrO1ubm8v8LIy83BoaemvMHCw8G/vbu8vr24uLe2s7Gvqqqpq62tr62dl6CkqK+vrZ2SmaKnqqekoqapqqelo6SopaelpaSlo6GjoqMmpKSnpaKfmJOgoJ+ZnZ+foaCfoKChn6ChoKKhoJ+dnJiYmp6enqCEnyKZnZ+gnp2anJycnqCen6CgoJ6cmpmZl5eYmJONmJmbnJ2fhJ6AnJydnpmdnp6dnp+gn56dnp6fn52enp+hoZ+dnJabn6Cfn5+gnp+goaCem56dm5eQm52dnJyampqZmZuYm5ydm56emZmenp6goJ+en56fnp+goaGio6Khop6WnqGhnqCfnpugoaOioqCgoKGgoJ+gn5+enqChoIqen6ChoaKioaGAoqKio6KioKKhoaGfoZygoaCgoJ+gn56fnp+fnp2dnJuYlZaVl5OUl56mrayoopmYm5mmqqSjl42JioOA/YGEgIOGiIeDgYD28fT1+vr4fvr27OzzfoWBhIKCfn6AhYSDhYSBhYWD/fj0gYGBgoaGhoKEhYWCgX5+fH2AfX2AfX2Ag4aCgIuKi5COjIqGgoiSi4+cq7PFv7C4p7SyoKiorrC3raerqqyxr7SyrbCwub68uLi4ubW5vre4v7y9vbq7v72+t7m4t7m3tru5t7u4srK3ur+4trm6u7q2t7W9y769vLu6v8HCxL+/wcTGwr++uLazt7m2tsO/wcjIwsPIy8mAyb69xMjHxsrKxMTEwcTFxsrNzcjH09jZz9fi5tvU1czQxdna4eDT393b1dfQ0c/Szdjb3NzY3+Tq7eXi4OPm6N7m6Ofi4t3p5uLg4t7j3ebl6Ofu9fHs5tXo7fXz7vDo6uvp5ubm6OTn4t3i39rb4tzX39zU09LX09Hb1sjHz9CAxL+yqaWytLjAycXEvbuusbnCyM3GwNbg4dC4tMHFyMjJv7W4uL64ta2ysrOvtLC4w768u7isqKOjqainpaefpamxu7+6trGmlpCWnLGwxsLH5+nHi5GSkY6Hg/DYxb7FztDV3Nvd3d/a3dzb2djV1dXR0dHS0c/P0NDOysrKyMQIy8vMztDS0tGE0BXP0NDRz8zPzsvKysrIx8rIxcfExMGGwoC/u7eyrLG0qqinube2t7e0s7Sqnp2tsbq4uLezsJqbnaypqqymrrGsqaOhpKaqppCLkpqdn5eRi5menpmbmo2GiZOfnZePi5CRlJ2em5aSlJqZlZeYnZOQkpGWmZman52ZkYV/ho2OkJ+gpaWelZOVlJaWmZucn6CgpKWlpqqoqQmtsLK0s7KvsK4CmZqEm4CalpN6Yo6Tk5aWl5qbmZmanJucnpyam5qVlpaTkZKQk5aWl5iXmZeXmZiYlZaWlpWZmJmbnKChpJyHjIyaoKCfoZ2bm5ydnpiampmYl5aTkpCRkpOUj4F+hYiRlZaVh4GFjZSYl5SRkpeYlpSSkJKRkpOTk5SVk5OTkpKRlJGPjQmDgY6PjomMjYyEjySQkY+Pj42QkI6PjoyMjI2Pj5CTkZCQkIiNj4+Ojo2NjYyOj46GjwuOjY2MjIyOioWNj4WQG5GRkI2PkJCJj46Pj5CPjo+Qj4+PkJGOj5GQj4SQCoiNkZGQkpGSkpKEkFyOkZCQjIKOj5CQjouKiYiJiYiMjo6Mj5CMjZGQkJGQkI6Qj5KSk5SVlJSVlZSVkoqRlJSRlJOTj5WVl5eYlpmYl5eXlpeWl5qXmJiWgZOWlpiXl5eYl5eXmJiYl4WWgJSWkpeXlpaVlZWWlpiWlpeVlZWTk5GPkJGRj5CTl6Cio6GdmpukpbC1r66eko2MgX/7gIN/gYSHhISDgff09/j9/f6A/fzy8fSBhoSIhoSBgYOFhYSFhIGDhIL+/PiDg4OEiomLhYiKioeEg4GAgYWAgYSCgIiOiYaUk5SYlJSRgIuGjJeQlaC0usnLucOvubOktK2xtryzraynq6yusrWsqqWrsa2prqusqqqwrbK0tbe2s7O1truzs7Gysa2trq6tr62pqamqq6epq62rqamrq7XArqupqqyvr7O0sbe0tre3s7Ksqquuraqss66xuLazubi7uLu1sLW+vLy7vbu5gLm5t7q5v7y8ua/Av8G7ydPSxMC/truyxsfJycTMzsm/w7y/vMK8xsXKzcPM0dPUz8/R1dnY19Ta4tzY093e2Nja1trX3dvc3ODh4N7bz+Pr+e/n6ePi5ePd3+Lg4eXf3ODd2drd3N7e2dbd19vX1t7YzczS0snCtq6pt7i6wsrEgMi+vrCvt8PIzMG9zNjbyrWxu8DExcW8sre2vbe1q7KzsK+0s7bBvry5t6qloZ6mpKGfoJebnqattLGrp56NiI6SqqS4sLPIwqt+hIV/eG9rxrSmoKSpqa+zsbSzsq+xs7KxsLCvrq+urq2sra2vrauqqqmqp62rrK2tsLCwrq6wgLCur62sqairrKusqqmqqqmpqKmmpqWmpqenp6ajoZ6YkpWckpKRop6cnqCfnJuVh4STl6CgoJ6bmYeIiZWVlJiSmJ2cmJWQlJaYl4WBiIuPkYeCfouPjomNjH97gIWRkY6GgYWIioyQjYqIiI+PiYqNkYeGiIaLjYyMj5CNhXp1JXl/foGMi5COiYaFhoaHhomJi46Pj5CRjpKTk5GYmpucnJ2bm5r/f/9//3+PfwF+in+HfgF/hX6Sf4N+/3//f+1/h4D/f9Z/AgIEAFe5urzBxMXDwr26uLy8u7eysa6vsrW1s7SztLW0tLKztbW7vsHDx8XDvsHCwL69v8PFztDR0tPY2t3i3t3a1My0u7LLycvKxsfFx8vLyMbEw769wMCmvsCEv4C5rauzq7C5t7e3tri2rbWvsbm6ubi3s66qqq6tsrS0trW3tLS2tbKvqammp6amp6epqKuop6Oko6OkqKqnqqapqKmoqKinpKKenaOkpqempqakpaampqelpqahoKCenJmal5eam56enZ6bn6KioaCfn6CgoaCenqChoKCio6OjpUempqWmp6alqaipp6qrqqSen5+oqKeoqKmrqKiop6Wmp6mmp6Smp6enpaSmpKSko6Shn6KjoKSjo6SkoqOjpKOio6OfpKKipISlEKSlpqanpqalpaakpKWlpqWEpICioaGjo6WlpKKio6SlpaeoqKeoqaqpqqurqqipqqqrrKusq6mpqaqqqamqqKiop6mnpqampaOkpKSfmZWPjZSTkpeZm5qVlJaUj46QmZygoI6Ognp6dXh04d/c3Nvb2tPRx8fCwb/EysDCvc/V2nB1dXh2cnZzdnZ6fHx3dHVzcoB0bnN3dHJxcHR1enp6eHx7enh3eHZ5fXp9en5/g4KEf4GDg4KAhoaG7bySyuTfxMjBvqyrn7Kzv7WmoZ2aqqCcpKOXn5+noZKXoKCUj4yLh42bnZqYnp2Um5aYnKamp6SSm5ObnZuno4qTkZKgs9i6wqfo3qSvj5SNjpKbpLGdqoCnpamzs7W7xsbSzMrM0dDU2NbSzMnLz87T1tPQ1dTU187b4tzY3Nrj397n5ube0NjU3dza4N3g3N/Y3drb2tja29bZ393X1tna19rd3ePm6PHt5eLb1eTi5+Hk7+fo3erl59rf393T3Nre1Njm3NrUztTa087UztHLzdDR1tjN0IDQy8nIyczHyc3MyLq4uLO0tay1tLW3tK+srZ+anqamp6KloJ6fm49+fICKj6a61+eGztJxfJGSk5SUlp+WmJeXnKScnZ+ak5KSmLHHlpKRlJ2Zn5qdoqeoq6qsoZiWhn2Pw+ru48i83/eEgPTn0MG5v8jU2uLt8PDv7/Hx8/Hw74Du7u3r5+bl5ePi393W2NbW2NnY2djX1tPT19rc3N/i4uDc393e29zc3d7c2tfX19rb2dfU0cnOz83LzsfHx8DDxMLDwcC+wMLCtbews662vsK9xsK+wb29rqaorL/Bv7/Bwb66taeurKanr7Shoa6uop6WkpmQm6WdmpGHh4iNoEafoqGhmpORl52enKCiqKqmpKWipKempaGdmJaRmI6DhoyQk5KUkpKXnaevpaiyra6ztre3tra3tra5vMDDxMLDw8HBvb29FaytrbC0tLGwrKqoq6qrqKWlpKaoqYSogKmop6ikpKWnqaqtsLSysayvsK+ura2ys7q6vLy+wsTFycbGxcG7pK2mu7q8uri2tbm5ubi2tbOxsLOvmq2vsLGwsKmioKagpKyqqaqrrq2kqKWlq62sqammo6Kho6OnqKmpqaqop6empqWenZqanJ2enp6anJubmZmam5ucnpyeAZ6EoA2hoZ6cm5eXnJ+foKGfhJ5xnZ6fn5+enJqZmpiWlpWUlpeamZmYl5qcnJuamZmam5qcnJucnJucm5ucnJ2fnp6foJ+dn5+hoKGhoJyYmZmfnp2foaChoJ6fn52enp+en5qbnZ2fnp6fnJ2enp6cmZydmp6dnZ2enJyen56dn56anp6EnxqgoJ+goaKhoaGioqKgoKChoaCgoJ+fnZ+enoefAp6fhaAKoaGioqKhoaCgoIahE6CfoKGgn6ChoKCfn52enp2dnp2EnICYlI6KipCQkJOWmJaVlpeWlJCVoKSpqZiWjIeHg4SC/v35+fr39vHs5ubi397l6t/f3u3y9X+Fg4aDgYWChISIiouFg4KAgIF+goaEgX9+gYKHh4eGioeGhIOGg4aNjY2KjY2TkZONj5KQjoyQkJD/0ZrN3dzJzcjDtrOsu8DJxIC1r6usvLSxtrSorbC8sqOmsLGknpybmZ6rq6mlqrClp6Olpq6vsq6doqCnpqKwq5WcnJ6suda8w6vg1aqxlJ2Xmp+pr7ios7Gqsb66u8DIyc7Lys3Q0tTX1tTQys/S0NrZ09TY2tja1Nzj4N3i3uTi4+rp7+nb4OHs6ufs7O7n6oDm6ebk5+Dk6eHh5ubf4OXn4+Tp6PH18/z27u3m5fDw8uvx9+/y6fXt8uXt7OTh7OXp4ebw6Orn4ebo5ODn4ePf5Ofo6evf4+Hd3N7f5N7e4eDc0M/NyMnOyMzJysvLyMTGuLO2vL/CvMG6u7qxppaTk5uetsbh75P0+YeQpqmoqGymq7Sssa6tsrmvsLKrpKWkpr3VpqaipKyorqqrsLa3uri8rqShj4eWyOzt6My+4PiDgfDhzLats7nEyc/Y2d7e3t/e3t/e3Nva2djW1dXT09HOy8bHxcfIycrLzMvKycrNz9DR0tXT0c7OzM+GzYDMy8vLzM3My8nGvsPCwb/Au7y8tri4trm1tbO1t7Worqaloqqzt7G3tq+zsbGkm5uhsLGwtLSzsa2rnqSjoKKoqpaUn6GWk42OloqVnpmXi4KDg4qamJiYmpSMiJCYmpibm6Chnp6gnZ6hoJ6dmZeSkJOLhIWIjZCQkI6OkZadoxuanaWgoqioqaqlqKmqq66wsrOzsbGysLKwr64Ul5aXmZqZmpaSkI+SkpOSkpGQkpWElhqXlpSTk5CRkZOTk5SUlpaWkpOWlpWUlJaUmYWacZ2eoZ+fn5yXho2LmJeZmZeYl5iZmpubm5yampuWhZWVl5mZmZCHiI+GipSUk5SWmZaQkpWTlpeYlJeWko+PkpCTk5OVlJWTlJORkJKMioeJioqMi4uKi4uLiYuKjY2PkIuMj5KRk5KSkI+NjYyKj5CQhI+EjRiOkI+QkIyMjY2Li4uJi42Nj42NjoqNj4+EjgyNjo6Pjo6QkZGQkJCFjxmQkZGPjo+PkZGRkJKOi46Kj4+QkJCPkJCOhJFLkpORkY2OkJCRkpKSkZGRj5CPi4+Pi4+Oj5CPj5GRkZCRkZCNkZGSk5OTlJSTlJWVlZaXlZaWlJSUlZWVlJWVlpSVlZaXl5iZmJiZhJcHlpWVlpaVlIaVC5aWl5eWlpaVlpeWhZUBloWVgJaVlZWWlpSSjomHh4uKjY+RlJOSk5SVlpSdq62vr5uYj4iHhIWC//35+/v39/Tv6Ojn4uHo7uPi4O73/IOHh4iHhYmFiIiLkJCKhYeEg4SBhIqHhYKAg4WMjImJi4qLiImLio6VlpiVlpedm5qRlJmblZCUj478zJzL1dfI0s3HgL/BtMLO1NPIxcC91cvKy8i6xcTNv660u76xramqp6y6vrqvt8KusKitrLO0tK+Yn5ynqKOqp5Kal5iqt9Cxt6DOwZuqjpyXmJmmqrmmtLCttr61tLjAvb+/w8TDxcnKx8zHwcnOyNTGxsjMy8nPxs7Qy87QzdPY1tnX5enY1NjigODW29vb2NTZ1tXUzs3N0M3O0NHW19rX1N3a1eHc3ufg4N7V0t3U2uHh4N3a2d3Y3Nff3tvZ4N7h1uHo3+Tg3+Xm4d7n3uHZ39/b3t/Y19vW09PS19XY2NbW0cjNyMnKws3KzNDPyMbGv7W/yMnGw8q+wL22ppSPj5aYqLXV4YvpgOmAj6KopqmmqbKsraurrLStr7Cpo6GhpL/MoKGcn6ehpaGgp6usrquwnZaTgnqLu+Hc18S2ydhwbcu8rJuUmJykpqqvsbOzsrS0tLOzsbGysa+ur66trauqqKaop6mqqamqrKusq6yurrOxr6+urKqqq6qrrKqrrK2srK2trKyugKyopqClpaShoZ+foJ6goaCjoJ+bnZ+dkZSPjY2Smp+anJyUmpmZjoWDi5qam5yfoJ+dm42SlZKVmZmHhI2QiYN+g4l8h5GNjIB2dnR9joqJhouIgHqAioyMj5CSk4+OkY6QkZCOjoyKiYaJgnp9gIOIh4eFhIaHi46HiY+Lj5CQFJKTj5GSk5WXm52bm5mZmpiamZiZ/3//f/9/lX+Wfrp/gn7/f/9/iX+CfrZ/goD/f9x/AgIEADG+urW0s7W3tba6v8TFxsO+u7u5trKvrq6ppqaqq7O4vsDEyMrHysjGx8nJy87NzdHUhNeA2NvZ2tbZ2dXOxLa+yrCtucbHx8bIxsfGx8fCvLmztbi8vsHAwsLAu7y8uLi6ubu7vL69vruzora5trSzsrKzs7W2ubm4t7Oxsq6wsK2ura2sq62rqqepp6eoqKanqKipo6mmpaakpKSjo6Slpqamp6empqSjoZ+hoaCgnp2cmp0Pmp2bnJubmZqcoKCgn5+jhKIWoaKio6OipaOhoqOio6OjpKSko6KjooSkWqWmpaSjo6KhpKaoqKmpqqqoqKaqqKinqKipqaipqKqoqKelpqeoqKipp6SlpaOhmJqhoqOko6SkpJyWo6KhoaGioqGgmKChoKCio6OkpqanqKempKSlpKShnoWfgKChoqOjpaWmqKeoqKipqKqqq6upqaioqKqqq6uqqKiqqamoqKmop6empqenpqWlpqWkoqKhnpyVlI6Oko+WlZWSlJqhqK2uqKCcnpiSj4qFgHx8enXkc+HX2tjPyszDvL7DyNHe39vZ4tzW0c3PydDVzszQ1NpxbtTS2djQz8vYgNdvcXd24G/ednd6f3uBgX5+f4CFiX95dnh4fICCgYF/fn6AgHx8mLKwrZWBeXTl7Zqvp5Skp5qEpau3xOXm7e/08/bp8vHn5ODlhIDx7uTr2r/Dzs3Q3PLa3Ojr2ODn8ufy5d702ujv0tbm6Nbe3ePU6Oje4v3c2NPBvs/U4NnXgMjCwsXKx8XEyM3LxcjHx8PEwr+8vcPP19bXzc/PyMnLzMvMzdPPxcvLy9LNy8/Lyc7Gw8XDxMXIzNLT19fV19LZ29jY2t7R0t/X1dfm5uPb2dfRztHQ1dDQxtba19TLzMvNw8vHy8PDw7+6t7a2tbe6tr26uLvF0NXTxNLPydbUgMrBvLOhpKaro5+aoaOmpqSfoqOnmpikpZ+hpJ6gm56UjYF8fn2Df4KGhoyJhYKKi46Pi5WTiI2IhIaJjZeSjo2bssX0wZ2bqqytqa2xs7Gvp6OgqLi5xN7x9+fW2OLx5tzIvLK3tb/E0N7f5Ojq7e/v8PLx8fHw7uzr6+zr5+baCs7c3dnX1tfX1taE14DY19XX2tna3NrY2drY29na2tnb3Nvc2tbU0c++xcLOzMjGw8fJysnLxsjHwbq8vMDGwMDAsLO9wL+6tbq5r627vcC5tsPByMfCsbi/uru7st2BrpKxpZapsq2YkZiZpKuspqSsq6utr6+uqqyqqKGipaemp6ampaCho6WoqKelnDGkqamrrayppaOjoqChoJ+XlpWVlZieoqiurayurqqrqKqutra4uLq7u7/AwsXDwsTFgK6sqammqamnp6mrsLCysK2rrK2qpqalo6CenZ+fpamurrCztLS1srSztrW1ubu5u7zBw8LAwMTCwsLEw8K/tKawu6Skrbe4uLe5uLq5ubi0rq2pqa6usLOys7KxsK+uqqyura6usLKwsqylmKirqaeop6ampKiqrKuqqaempqOjH6KgoaCgoJ+gn5+fnpycm52anJydnZednJucm5ycnJ2FnjGfn5+dnZ6dnJybmpqZl5iXmJmZl5eWlpWWlpqamZmZmpucmpuanJubnJycnZ2dnJuchp0Em5uam4SdFJ6dnJycm5ydnqCfn56en56enp2dhJ4Gn5+fnZ2fhJ6EnxagoJ+fn6CenZOUnJ6dnZ2enp+WkZ6ch50MlZ2enp2enp+goqGhhKINoJ+fn52cnJ2cnJydnYSeBJ+goaCFoYCioaKhoJ+goJ+fn6GhoaCgoJ+foJ+goJ2fnpyenZ6dnZ6dnZybmpeWkZGMio6MkZGSkpOboauxs7CkpaWgnZmSjouIh4SC/4H99Pb07enq39ve4OXt+Pn29/769fDr7urw9e3t8vb5f3708PTy7vDq9PZ+f4OE/n/6hIWIioePj4CLjI6NkpaLhoWGhouNjoyNi4qJiIWDgJm0tLKdjIiA9fyftLCgqrOlka2yvMvn5+rs/f357/b47+nm6YSC9fHr9+fIzNLOz93v3ePq7uDj6PPo9Oni9+Du7tPa5+nX3ODr3uzp5er/3N/YxMjS2N7Y18/Ixs/Tz8vO0NLT0NDQy4DKzcvFxMXP2t7a4NnZ2NbY2dbZ2tje2dHU2Nvd29fc2tra1tPRztXR1Njc3uDf3ODk5eXn6Obo397w4+Hj8PDu6+bk3tne4ODe4dfj5ebj3ODf4NXc1tzR1NXPzMvIyMbJy8nPycfM2uPm5dbm5t7o59/VzMa0uby/t7W2uLm7u4C7uru9v7Gvubi0uL66u7W3saaZk5aWm5WanpyinpuaoqOnp6OurJ6mnJWZnJ6npKCcqr/R+c6srbe7t7i5v8LBvbezrbPCwcnh9Pvq19nh8OfbwrKpq6mvs7/LztTX2dnb29vd3t3c3dvb19fW1tPSyL3NzMrHx8bGycnJysrKy4DLys/NzM7Oz87Ny8rMzM3MzcvNzc7MysjHxba7uMLBvru5u76/v7+7vry3sbOxtbq1tLGkp66xsayorK6joK2ws6qps7O3t7Kiq7Kur7GoyXWjh6ebj52oppKIi5GcpKCam6Sko6SlpaWioaChm5ygoqCgoJ+enJudnqCgoJ2XnhehoqSmoqGenJ2bmZmYmJCQj4+PkpeanoWhFJ+ioKGlq62vra2srbCysbKys7KxOZeVk5SSk5WRkZGUl5aWlpWUlpeWlJSUko+Mi4yNkZOUlJSWl5aXl5WUlpaXmZiZmZmcnZubnJ2anISdbJqSjZSci4uTm5ycnZ6dnJucnJuYmJWTlZaWmJiamZiWmJeSk5aXmJeYmpqblpCBk5iWlZWTkpKPlJSVlJKSkZKRj4+OjY6Njo6Li4uMi4yKiouOio2NjI2Jjo6Mjo6Pj5CPj5CRjo6Nj4+OjoSPF42MjIqKiomLioyLi4uKi4qKjIyMjY6PhI0ajI6Pj42MjY+Ojo6PkJCPj4+OjoyOjY6PjY2Ej4SOCI+QkZCPkJGQhI5Qj5GQkZGQkI+Pj5CQkZGSkpOSkpOUk5WUk5GQhoiPj5GSkZGRkoiGkI2PkJGSkpGRiJCTk5GTlJSXlpaXl5aXl5aXlpeWlJSVlZSVl5aXl5eEmQeXmJeVlZSWhJUYlpWUlJWUlJaWlpeWlJOUk5WVk5aVlJWUhpWAlpWUkI6LiYmJjYiOkI+PkJWaqbS8vbGsrKWgm5OOjIaHg4L9gPzy9fPo5+re19zf5e74+fb1//r38O7u6fP48fD1+P2Bgffy9fb19PH494CBiIX+gP2GiYyNi5ublJSWlZmekIuHh4qRkpKOjoyKiYaCfHiYsK+smIyHgfT2ma6Aqp6psaSRp6u0xeXn4+Hx9vTq7+/v7ebngnzw7unt6s7T0MnN2ufg5ODr4ubo6OTv5N7w2uzp0tjk5tTf4eri6uvp7PvZ19TCxtLU0M7OycXBzdfUydLW1tfT0c/HytTLvru90+DY1+LW3Nra3drP2Nnf49LFztDU1NbN0NPQ0suAzcjGy8nKx83T1dHR1Nvc2N3e2dvS0NrX0tPc2tfRzs7Qyc/V0czRztXU29jW4Njbz9fS0sfMx83Mxb7AwMLDwMbAwcTLztDTxdDSztXRzMXAvLCzubixsbK1tru5ubi5u72yrLu6tLnBvL25vLSpmZWZlZqUmJ6cn5yamqKkpKNeo6upnaOalZibm6ahnZmnu830xainsrOtqayztLWyramkq7u6x9vu8d3KwcDIv7emmZGSkpiYpKqsrrCys7OzsbKztLSxsbCvrq2srayknampqaenqKipqKqpqq2uroWtb6ysqqioqKmpqqqrq6qrq62tq6qompyapKOhoJ+go6KkpaGkop6bnJidn5ycnI6QkpeZlZKVkoqNlZiZkpObmJyemY2WnJuZm5WeX452lYd8jpeXhnx4hJCXkoqMlpeWlZOSkZCRkZKNkJOTkZKRkoWRIJKRkI2FjpKPkZGSj4+QkI+NjIuHg4SDg4OFh4yMi4uMhY4HkZOXmJmYmISWBpialpeYm/9//3//f5l/An5/n36Cf4l+hH8Dfn9+pn+Cfpp/goD/f/9//3+VfwF+4H8CAgQAgLm8wcTFw8XHyMjKx8PFw8HDxMTDwby6urm3usDFxMXFyMzKycvP0dHW1NXa2dnUzMbBvcLM0NXX1NTQzcW/sbOhpYujqa23wL26vMHEx8rLx8TBwcLDwsC7vr2+vr28vr++v726uKmrtry7uLa4t7W0t7S0t7a2trO0sK+sqqyrBquqqqusrYSrU6ipqaapqKepqaqpqKWjo6Kho6SkpaSlpKWnpqWmoqCioaCgn5+cm5yampmampyfoJ6hoJ6hoZ+foaGfn6CfoKCioaSkpKOkoqampaSkpKalpqamhKcvpqenqKempqaoqKqsramop6mrqqmqqqioqaqpp6eqqqinpqampaalpaWmpqaop6eEpQ+ko6Oko6Oko6Kjop+gn56FnxOgoKGboKKioqGcmqKkpKGgo6OihKMbpKSioaChoqOkp6inpqSoqaipqaqrrKqnqamphKpjqaurqKqqq6moqaimoaiop6empaWko6Wko6KjoJeZmJaXl5OUk5WVk5GUlZueprKur66jnpuOioB9d9nj4eHb19fT23h2dnV2dnd23+Xf3+Hh4NvW0djc19bZ4OV1enl4cuF1hXqA5Ofm4nN5fHuAfn1/f4OAfXt9fX6DfH16dHJz5eLhd3d2dHLgcnd5enx/gH+BfXdzb3Bycm/Y2dfZdHV2fXp3e4eHjpKgoJafqa6vrbCspLCyrZSfobHAzs/Hs7Slgo2doKmrrca+t6mdioOOgYF/gYaRjY6DkKaqqrG3vMXEw8aAxsDCvr3CwcHOwcbHzsnMw8HAxce9vcHGyMvH08/O1szRzc7OxM3JwsTIwMPHyMjOyc3R0M3NzczHxs3QxMW/vb66vsHExLq3sru+vLi6rqSoo56bnZKRnJufpaWal5SOi5CMkZaPk52jsKyutsC7urasqrKuqKSnrqu4u8K6uL9/vLKws7Stn6Ompauns7y5sqyorqyin5mcoJ6uopqRiYeHgXx/hIGDgn56foOAf4GAgoGDf4mNjI+Ii46cqr6/p6mrraGYlY+eoqOfoqets7Wwq6uurrS2r7CkpaGvucTP1tnc3eLk5ufp6+3t7u/t7Ozp5+nq6ebk5eLg3d3Z2YTYgNPS0M/P0tPS0NDT1tna2tjZ29va2trb2tvY1tbSzcTCxsTFw77HycnNzszJxMbBvry5r7KzqbWysKyxurSusrSys7Wxsr/CwsK/sLSxwcC2vb+/vr24s7mqo5upubi4tK2pr6+ckJeTjaOprKyvrqyrsK2rqamrqamrqKysqaikN6Khm5GGjpiYmJeboqSmpaipp6Wpq6qnqqmsra2ur7S0uLizsbO2tbWzsK6usLO4tre4ubu7ubuAq66ws7SztLS1tLSzsbCvsLGys7GwrauqqamrrLGys7K0t7a2tri5ur29vr+/v724tLCxsre6wcDAv7y8trKpqZucg5yhpK60sbCxsre5urq3trWztLOysq6wrq+xsa+wsbGxsKyrn6KrrKysqamqqaiqqKioqamppqejoaGfn54wn56en5+fnp+enZ2enZ2cm52enZ6enJucmZqam5ycnZ2dnJ2enp2enZubm5ycm5yYhZYrl5eYmZqZmJqYm5uamJmZl5eZmZqam5ubnZybnZidnZ2cnJydnZ6enZ6dnISeBZ+enp2ehJ8EoJ+gnoWfFZ6enp+foJ+dnp6en52enZyenp6dnoSfAaCEn4Cgn56enZ2enpyenpycm5ubnJycnZydnZednp+fn5iYnZ+gn52enp6goJ+fnp6enZ2dnJyeoKChoZ+hoaCgoaKioqCdoaCfn6CgoaCgoZ6hoJ+fn56fn5afn5+enp2dnJydnZ2bm5mVlZOSkpGPkZKSkpCQk5eboKm1s7mzqqejloCQioiC7/r6+vbz8+73g4GAgIGBgoL5/vv3+Pv59vLt8fj08fP6/4SHhoV//YKGhYSDhPz///x/hIeKj4yJi4uOjYuGiIqMj4qNiYJ/gP77+YSFgoCB/YCFhoaIiouLjImDgX+AgYB/+fr19YKBg4uHhYqWmpygramiq7a6v7m4tICpsratmqeks8PS08extqqJkaCor6+vxcXArqaWk5qOj4+Sl6Gfn5Ogtre4vcLCzdLOzc/Izs3FzNHN2M7T19zV29jQz9HTyMrM0NXV093d2eDb3dja2NLX1dDT08rN1tfT29XY3NvZ2tnZ1NXe3NHTzs3MzdHTz9PLycLLzMfFxYC6tbexrrCso6Cvr7C2tKypqaCdoJ+ip6GosLnCvsHJ083Mxr+9xsG6uLzEvsvO1M7N1NDIxMfHw7e6vrvCvs/X083HwMfGvbm2uru6zMa0qaKfoJeTmJyanZqWkZOYlpSXlZeVlZCfoZyim52gqrjKyrW6u7quop2epaisqKyvs4C3ubOrq6yssbGpp5yZmKOtuMDGx8rO0NDT1NfY2Nja2dra2djX19XU0dHRz83NzczLyMnIyMbEwsPFxcXHx8fIycrKy8vLysnJy8zMzMvKy8jDv7m3vLu9uLS8v77BwcG+uby6t7Ospqenn6upppylrqqjpqqmpqekprO2trKwpWynorOxp7Czs7Owq6eropmQna6urqyknqKlloqPi4iboqOipaSioqWkoqKhoqOioZ+jpKGfnpuclYuBjZeXlpaYnJ2enp+enZqen56boKChoqOkpaWmpqWkpaepq6mpp6Wlpqapqquqqqysq6s4lJWVlZeXlpWXl5iYlZWVl5qbm5qZmZeXlJGSlZeYlpaXmJeXl5mZl5qZmZyampiWlZWTlJeZnp2EnjCZmJOUiIlyio2OlZmamZmam5ydn5ybm5mYmJmYlJmWmZiXl5qZmJial5eQk5aYlpSElTmUlpKSk5OTkY6OjY2MjIyKjYyNi4uLiIqKjIyMiouLjIqPjo6OjI2NjY6MjY6OkI+Pj42Pj4+Ojo6Ej2GNjYyLioqJiIiKioyMi4uMioyMjYyOj4yMjYyNi42MjI2NjY6JjY6Ojo2Nj4+Pjo+Qj42PjpCOjo6Pjo6Qj4+PkJCPjo+Qj4+QkJGRkJGQjpCSkpGQj4+Rj5CQkZCQkZKThZKFkweSkpKRkZGQhY8akJCQkZGSko2RlJaXlo6OlJaWlpOWlpeXlpWEli+VlJOUlZWXmJeXlZeVlpiXlpWXk5CUlJWUk5WVlZaVk5STlJKVlZWUi5WVlJSUk4SVgJSSkpSSjY+Oj46PioyMjo6NjJGQl52ltLrDwLmuqJ2UjIl+7/b09vDv8ezwgH+AfoCBgYH7+/v39vv5+PPt8/n29fX7/4KJiYWA/4CFiIaFhPz+/v2Ag4iPlZOPj46VlJKNj5CQlpSWkIaCgv7494KCfoCB/4KGh4aIiImKjIiFgIKCgYODgf/99/mCg4WMiISMmp6jpa6poK+4vcPBvbCipqqfk6KlssHOzsanraiEiJegpqWmwcXBtKaUkp2OkZCRkqKdn5Ohvby6wsPA1N/Z0tfO2tXI2NzP4NHj6OHU5+DW3tzfysbIy8/PytXTyN3S0czPzcfNysfDv7vBwcLAgMfEyMnHysrOz83J1NTIycfEw8DGxMDDvbq2vr26uLespaijoqShmpinpqWsraeioJmXmJebn5uirrC2sra8w8DBura1vbe0tLK4tL/DxMPEyMbDvL/AubO4vLzBvcjNyMTBwMLAtrS2uLq3zMK3rKSfoJaTmp6Ym5mVkZWXlZKWO5KUk5ORnZycoZeamqayw8Oxt7W0pJeWmaGkpqKnqq20ta6knZyam5mSkYiGgo2VnaKoqqutra+urq6vhrAZr62uraytq6mpq6qpqaupqKenp6anpaWmqISqaKurqqqpqamoqampqKqqqampq6mjnJqfoJ+cmqKkpaamo6Kdop+cmpSOj5CLlZOPgY2WkYqPkJCQj42Om52blpWOkYmZmI6anp+fnpqTmI+EfIiZnJ+dlI+Tl4t/gYB8jpSUkJCTkZCUhJM8lZKQj4yQkpGPjY2OiYB2gY6NiYiLjIyOjo+NjYiMi4qKjY6Ojo+PjY+Pj5CRkpWXlZWUkY+RkpCRlJSRhJABk/9//3//f5t/iX6If5F+hX8BfoZ/hH6Xf4N+hX8BfpF/hH7/f/9//3//f4V/AgIEAHLJxcbKx8nGyMjDw8LCxMjKztDQz9DNz87Jzs/QzcvIycrLz9LU1djc3Nnb2djU1tvi5ePi4d/e4NnNz9fGvbvBvru5tqyquMXBwMG/uLOvqbK2uLq7vru9vr69vLm2sbWysa6vsrW0sauturm3t7i2tbiEtzi4trW1r7KxsrKvsK6wtLCwra2urqysq6iqqqmsq6qnp6impaOlpqepqainpqenpqWVn6CioZ+fn4WgBJ+goKCEnYCfoJ+hpKSkpaOhoaGgoKChoqWkpqWlpaanpqWnqKipq62urq6xsbCwq6usraysr7Cwq66pq6+xr6yrra2qq6urqqmoqKmpp6elpaeoqaalpqamp6empqWkpqalpKOjpKKjo6KioaKhoZ+foKCfoKKhnZ+hoqSkoqGjoqGbnZyeoBOhoaGio6KjpKOkpqaoqKmopqeqhKsfqqusrKuqqqurqamqqqyrqqepq6upqqqpqqmopqanpoSlgKSho6OhoKGfm5WVko+PkpaSlZKTkY6PkZmfop2YlJuhlZSRkpKNheTHwsB9jo2Ignx5d3TSzsvO0tba497gctvX3nFx3NvccnNzdXN1dXd5en59fXx5eHV5eHRzdnR5d3N3d3R03Nrc3tjLwryxq6uur7Cwu8PJytdx2W9ydXZ0Bd9w29PRhNCAaGhoamtqamjNzM3MzWlvcnFsa2ppaGdpa3J2eH2Ag4OGgHx8hYOGh4eGiI+QjpGRjYaGhoeKioqQkpSWl5ihoaOjoaKjoaSlpaKgoaOmrauyuLevsbq5r7awtLe6sra5tqussaiwrayttLewsri8vra2uLSvrq+mj5uTkY2SlpNxlJijtLClmpKJi4R2b3l8gImOioyMiYiKj5KLiomFhISEh42PkJGQj5CPlZqgrqmco6iwqKKhp6i6sayptrOvrLW3traztrKtqqqoqq+ptK+sqqOtp6KgmpKPjoqBgYKIiYSCgoWHfYGKioiGioyLkJCEjoCPkZWOjpSQk5SdoKuupqWbnJqaoZ+YmpybmpiZmJWZnJ+foJ+ipa61ubC2sbfHzdDT1tjY29vd3uDi5uno6Obo5uXk6+nm4+Xk4+Lh4OHd29jX1tTV09DR0dDQ0dTT0dLT19ja2NfX2dnb3NrPysrN09LTzMXIy8fCx8fHxsbDw4DFw8LBvqyVl5yhi5+2u7q5uKyqvL27u7ewsrevt6attru7wMPDwsG+v723rK+MkLK5r7u7ubq5ube0sa6hiZ6ura2sr66sqquqq6iloqGipKSmpqenqKutrKuopaOko6Cio6Smp6Wnp6msra6sr6iwsrKxraurqKOmqKuvsbSzuQe8u7zBxMTGhMcCycd/t7W2uLa1tLS1srCwr7Czt7e7u7u9uri2tbi5ure3tra1t7m6vLy/wsPBwsK/vsHEyMvLysfKycvGvL3Dt7CutLGvrKmioqq4tbK0s62qqJ+mrK2vsK+tsLGwsK6srKuqqKekpKepqaahoaurqKeoqaipqKqqqamnpqajoqKlo4SiFKOio6CfoqGgoKCfnp6en52dnZydhJyCnYSfXJ6fn56cj5ucm5ubnJucm5ucm5mamZmXmZqZmJuampmam5yamZmampqZmZqdnJ2cm5qcnp6dnp6fn6GioqOipKSlpKGhoqKhoaOiop6hn6GhoqKhoqGhoJ+goJ6fhJ4NnZ6cnZ6fn56cnZ6cnoWfBKCfn5+Enh+dnZ6dnZ6enZubnZycnJ2dl5mcnZ+fnZ6enZ2ZnJychJ0Enp6dnYWeBqCfn6CgooahBKKjoqKFoIShB6ChnqCfn6CFnwaenZ6dnZ2EnAGahJyAm5mWkI6Mi4uNkpGSj4+QkJCSmaGloJqXoaefnZmVlpSP89jV2omZl5OOiIWEgO/q6Ozu8vP9+/1/9/L4f3/5+feAgYGDgYGBgoWGiYmKhoSEgYaHgoGCgYeFgIOCgYH49/r49enk3tTQz9LS1Nbf5+zr94D4f4CEhIL9fvv29fOA8/X2e3p6fX5+fXv29fTz9Hx/gIB+fn19fHx+gYSHiYyQkI+RjYqLko+Tk5OUk52cm52dmJKTlZeZmJianaChoqSrrK+srKqrrK+uq6qqq62yuru/w8K8vMfFusO9v77Bvb/Hwrq7wLrAu724xsPBw8fIzsjFxsO9ur21pKunpp2Aoaalqqq3xsC2rKacnJmJgYyOkpugmZydmZicn6WcnJiUlJOTl52hoqKgn6KjqK6wvbyuuLrDvLm1u77PxcC/ycXBwcfJycjGx8O+vMG+vcTBxsXBwbvCwbq3samnpKOYl5ujo52bnJ6jl5mjoZ+go6WkqaynpaWlo6aroqOopKpKqayyvL61tKusp6SopaCgoaCbl5iVkpSYmZmXlJibo6iro6ilqba+wsXHx8nJy8zMztDR1NXU1NPT1NPU0dLT0tDR0dHPzszLyciExlzFxMHDxcfGxsXFx8rKyszJx8fJy8vLwr6/v8bFxL64vcG/ur6/vry8uri5uLi1sqOOkpWYg5ersa+traGgsLCvsKylp6ukrZuhq66vsrS2tbSxtLKroZ6Bh6eroYSwV62tq6qmp5t/laempaOlo6Sio6KkoqCdnJqdnqGgoqKko6alpKGgnZ6dmpqbm5yeoJ+en5+foaGloKWnqKako6GfnJ+io6WnqaissbKxsrO0tba0tLS1tBiYlpeWlZaXlpeTkpKQkpWYmp2cnJydm5uEmhSYl5aYl5eXmJibmZudnJyenZycnYSgTKKho6Sfm56fmpWSmJeWlpKKiY6dnZqam5iXlZCTlJWXl5aWmJiZmJaVlpWXlZSSkpSVlJGLjpKUkpOVlJOSkpGSk5OQjo2Lj46Nj4+EkBuOj46Mjo+Njo2MjY2Mjo2NjIuNjY6Pjo+Oj5GFkDiOj4OOj5COjo2LjIyOjYyLjIyLioqLjI2Mi4yMi4yNjo2LjIyNi4qLjY2LjIyKjY6NjI2Oj4+PkYSSBZGRkY+RhZAukZCNkJCRjpCQj46Qj5CRkZCQj4+RkJGQkY+QkpGQkI+QkI+QkZGRkJCSkpOUlIWRHZKSkZOTkZCRkI+Pj5CQiYuQlJSVlZaWlpWRk5SUhZUGk5OUk5OThJQIlZaVlpeXl5iEl4KVhJSAk5OUlJOVlJKTlJSTk5OVlZSUkpSVlJWUlJOUkZSUk5STkY2IiYeFhomLi46NjI2KjI+VnaCcmZqstqmknZmZlY7x2s7RiJyalI2HhIOA7uXj6+7y9Pz7/YD59PmAgv79/ISEhIaFhYGBgoaKjI6Gg4ODh4mFhYiGiYiChYWCgv0r+f/9+uzm3tPOz9LV1tTh6/Hw+4H8goOEg4L/gPz6+fv9/P+AgICBgoKBgYT/gP2BhYaFg4KAgIGAg4SJioyMjo6OkY6KjZGOkZCRj4+Xlpiam5eTk5SVl5WVlZmdnp6epqmrqaqppqqtqaimpKaqsb3Bx8nFvsDKyr/FvLy7urXBxcG8wsG6v8HEvc3Nw8DEwdHGvcC9srS+uKOqpqKgoqOmqKewwLmxp6CWlJOBgHmEh4yWmJOVk5ORk5SblJGQjY6Ni5CYm5ydm5ucnqKoqbKxpq+xtrKwrrS2xcC9u8PBu7rAv8HDwcPBuba7uL2/usG+uru3v7y3tq2npaWjmZueo6SfnJyfopiXpKShoKOjo6eppqOioZ+jp5yfp6GnqKuwtLiwsKiqpqaooZqegKGgmpSTj4qLi4yLiIaIipCTlY6Tj5Geo6SlqKeoqqmrqqyqrK2trKutrKysraysq6urqqqqq6upqKelpKKjpKWlpqWlpqepqKqpqKirqaanqKipqKeioJ2hpaWmoJyhpqajpKSko6OkoaCdnJmYjnt/gIVzgpSalZWVjY6YmpiYLpSOk5OPloiKlJiXmpyenZ2cnZyWi4NteZOVjZqcnZ6dnZybl5mMcomUlJSRkZOEkkSVlJGOjIuNjo+PkZKTkpWVlJORj4qLiouLioqMi4mKi42Mj4+Qio6RkpGPkJCMjZCRkZOTlZOWmJeYm5ybm5qYlpeYmf9//3//f59/hH6Jf4p+CX9+fn5/f35+fp5/lH4Cf36FfwJ+f4d+iH+Ffv9//3//f/h/AgIEAA/KycrKyMbEwsPCxMXIys2EzoDNzc3PzczKzM3P0dHLytnc39rk5ebr7urm8fHw7Ovv6OXOztXUz8/O1c3Fx8/Uys7Gv6q1wcnLy8K7sq2su7/DxMbHxsTAvb68u7e5uru9vry8ury8vrmvsLO3ur6/wLy5u8G9u7u5t7W1tLe0ubSytLKysbKurKyrrKurqqusqxipqainqaenqKampqWnqKempaSioqOhpKOEoCajnJWaoKKfn52Ym5ydnp2coKKhoaKioqOko6Wmp6urqKWmqKqrqISnCautr66vra6vroSwP62tra+ysq+trKutrausrqyrqamqq6qpqKeoqaioqKeop6emp6empaaopaWmpKSlpKWjoaGioKGhoaKhoZ+goIShgKKioaOioaakpaSii4ubn6GfoaKioqOhoqOjpKSjpampqaqqqqusra2tq6qrrK2trKyrq6uqrK2rq6usrKuqrKyrqqWpqaimpqinoKSlpKKioqGhoaCioqGgoJmYkJaOj42Ok5SUlpejpqWenJaRkI6Tio2KiYuPk46MmpeNg317gHlz2dPb2dPWzc/KzNLOysPKzNHL0NLRx8XLysa6r8DEx9LRzsS7s7/FuLKvsLGwucfOycS7wMbIw8XJzM/R1d50eHl4dnZ3d3Fyb29vdHBvcXJ2cnBxcnF1dnJ1dHJwc3N0d3p6enx9e3h9eHx+gYWDiIeIjY+MjIiFiZGQlJOSgI6Oj5CPkIuOj4+NjJOVmJmjqamin6ezxs7izdrMwLavoZygoqaqs7atp7GjpKKtq6aenZuYmJCFg3l4fYF/fn55fIB3dXRyeHx8gYF8e35+d356dXZzdXh2cm9u2m1ucHN0eHx7f4KJiIWDjZGTmJePkZKQlZmenJKTkZanp7C2dLW0r6y1w7+/wsnOysq1rqqmp6+xp6mwqaWtrquvr6yflpmZko6OjZaJiomEj4GAgX13dnJydnZ9e4GGlaCXjo+MjpGOkoyHiI+Xn56cnZ2emJmesLmZlpOWl56ssKaXnJ+mqqejpKeqrrO5uby9wMLBx8jOhM+A0NTX1tbW2dbR3Nvd3uDh4eLj5eTm6efp5+Xl4d/d3Nza2NfW19bU0tLR09HS09PT0tHQ0tPW1NTV08zM0dDMv7u9xMvOycrHyMjGysrIw77AwsLCwb25vb22qbC2vr+9trCxsbe1r62vs7Sysqyttbq8vLq6u76/vry/u6SjsL9fv7Wyvbe3tba1tLSpppmRj6qvr66sraytra6toI6rrKysraysrq2trKyrqquoqKalpqWkqKikpaepq6yrp6erra2wsbK0tLW5u7u8vLq6vL68v76/wMPEx8bJz8jJzMt7uLa1t7Wzs7GysbGytLa4urq6uLi4t7e2t7e6uLi5u7W1wMLFwMjJy8/Qz8zR09TSztXQzLa6wL+5vL3Du7W4vsC5vbauoKu1ubq4s6+pop+ssLO0t7a2tLKvsK+tra+ur66vr6+qq6utqKCkpqmqrq+wrqqrra2sqqmohKYNpammpqWlpKOkoaChn4SgD5+gnp2enZ6fnZ6fn56dnYSfAp2chJsOnJybnJ2am5WPkpicmpmGlwOYlpiEmhGbmpqbmpydnqChnpucnp+foISeCqCio6KioaKko6OHogujo6SioqGhoaChoISeBZ+fnZ2eiZ2CnoWdCZ6dnp6dnZ6cnoWdBZybnJybhJwDnZydhJwUnp2coJ+foJ2GiJmfn52fnp+dnp6EnQmcnZ6goKChoaCEoQGihKMLoqOjo6GhoKChoaCGoYSggp2FnoCdmZydnJ2dnJucnJubmpybmZOSjJGJi4qKkZCRlJafpaSfm5eUkpOakZSSkpGWm5eWpKCYjomGhYD28/b07/Dq7Ojq7Ozm4ubs7efu8u/k4+jm49jU4OTl7+7r4drQ3+XY0tHPztHa4+rp49zh5efi5Ojr7fDz+4KGhoaFhoaEgYCCgICAg4F/g4KDgoGBgoKFhYKEhoSChISFiImJi4yNi4eKh4uMjZKSlJKTlpiXlZOTlpqanpybmJibmpmYl5mbmpiXnqCfoqqsq6elrLjJ0eHQ3dPFv7itqbCxtbnAxbiyvrW1tcO7squtqqemopeVjYuSlZSSkI2OkYiIh4iMj4CQlJSQkZGRjZGOhoaGh4qHhIGA/oCBg4SJiY6Mj5OamZWTnqKiqaeeo6Ohp6utrKamo6e4ucDFxsW/vsfTz9DU3OLd3szHvbu6xsa6vcK7usPBv8PFwLSrrq6mpKKlrZueoZ2omZmalI6OiYqOjpaUmZ6wvbOpqaenq6itpaCepyyuuLWwsbO1ra6tvsWkoJycn6OxrqeYnqClpqSgoaKkqK2wr7Gys7Syur2/woXBgMTDw8TFxsPKy8zOz9DP0dHR0tTT0NLS0tDQz83MysrGxsXFx8bFw8PCw8TExcbGxcXGx8jIx8jDvMDEw76xrLC3vsO9wMLAwLu+vr65tra3uLW2trG1tK+fqK20tbOtpqalrauko6KqqqennqKqr7KxsrCysrKzsLOxnJqksK6oXaevqq6urKqpp56Zj4qKn6Slo6Kho6SjpKKXh6OlpKWmpaanpaWkpKOiop+gnZqem5uhoZucn6Gho6GeoKSmpqipqqmoq62tr7GzsLCwsbCys7W0tbW1s7a4t7i4t3eZnJ2amJmYl5WWlpaYmJqbmpyamZiXmJiYlpmYl5iYkpKcm52coaGho6OhoqSipaWkpqKhk5WZm5iZmpybmJmcn5qdmJaJj5icnZyYlpGMiZOVl5iZmZiYlpSWlZSUlpaYmJWUlZCTlJGOho6PkJKUlJaTkJCRkoSRJpKRkZOSlJKSk5GRkJCOj5GPkI+Pj46Pj42Oj46Pj46QjpCRkJGQhI9Ijo6PjY6OjY2OjIyHgoOLj42Mi4mKiYmLiomLjIqMjI2Mi4yNjY2Ojo6NjY6NjI2Pj46Pjo+QkZCRkJCRkZKSkpGQkZGQkJGRhZAGj4+Pjo6NhI4CjY6FjweQj5CQkI+QhZEOkI+Pj5CQkZGQkZKRkJGEkCCPkJGRkZCPkJCQj4+PkJSSk5ORfYCPl5eVlZWXlZSUk4WSg5OHlAuVl5aVlpaXl5aVlYWTApWUhJOAlJSTlJSRlJSVlZWWlI+UlZOUk5OTlJOTlJOUk5GJiYWKhYiGgoiMj5GSm6CdmJiWk5SXoZebnJ2Zm6Gdmaymm5GJiIWA9e/38u7x6u7p5+zr5+Tr8O3r8fX15eXp6+jf1+bp7vLx8Ofb0N7o29bQz9PU2+nt6urg5efp5eju8fOA8vb+hIaGhYWGhYSCg4GCgoWDg4WEhYSEhYWGiImGh4iGhoiHiIqNi4qMi4yKi4mNjY+SkpSRkpSVkpSQkZSVlZWUlJOVl5aUlZOXl5aVk5aZmZqfn5+dm6KuwsnaytPJv7u2raizuMDGzMy9usm7wr7PysK4ubWzqqWbmo2MkZOAkI6Mi4ySh4iGhYyNjpSWk5GRlY2TjoWGhYWGgX14d+t2eHt9gYKIh4mMlpKNjZaamZ6fmZucnJ+jpKOdnZufrq+wtbW0sLC0vrq/xMzPzc7CvbS1uMDCtrzAura/vbu9uLqzq62spqCdoqaen52bp5uZl5WPjIeLjpCXlpqisLxUtKqqpqirqKukm5ulrrSwqaytr6isrb3Bnp2am5+jsaukk5WYnZ+dl5eZmJicn5ycmp2dmp+go6OipKampKSkpaanp6KnpqipqaqrqaqrrK2tq6yrhKoJq6mnpqWkpaWkhKNHpKOlp6WnqKmnpqioqammo52eoKGelI2RmKGnpaWlpKWjpKKjoJ6fnp6dnp2Znp+bjJKWnJ2dmJKQjZWUkY+PlpaTkoiLlZqGnGienZyenYiHj5mZlpGZl5mZmpubmYyFgH5/kZSTkJKRkZOTk5KIeJCTkZKUlZaWlZSTkpGQjouIiYeLi4mMjYmJi42PkI+PkZSVlJWWlZaVlZiYm52dnJqam5ycm52dnp6dnJqZmpqZmf9//3//f65/vn7/f51/AX7/f/9//H8CAgQAgMTDw8LAwL/Dw8LDw8XFxcPDwcTFwsbHxcfHy87Pzs/U09XV1tTZ2drX1dna4OHj4t7f0cy8yM7FzczGy9bO1dzT0srGzs3LzMrE0M/Oy8vKy8fLxcXBxcTEvba5u7i/xMXEwsK/u7+9t7Czury9vb2/vruvsre+urm4rbS2tbS1Hra0tLSysrKvrq+vrayrq6usqqqrqaejoaKkoqKmpoSnCKajoaGipKWmhKNzpqajoaKempqkpKGlpaampKaoqaeko6Skp6enqKinqKqopqeoqqytqqmnq6+zsa2rq7CwsK2trrCvsLGztLOysa+ur7CvsK+urq6tra6sqairqqmoqKanp6WlpqalpaWmpaOmpp+ipaOio6OhoKCfn56gn4SggKGioaGioqOjoqKkpaOipKOhoKCVoqCgoqGioaCioqSjpaanqKqpq6ysqqysqqusrKutra2ura+urq2trayrq6uqrK2sq6ikpKmopKSlpqampKOioaKjo6KhoKCgoaGhn6GhoJ2YlI+JgYGLjIuRmJqWl5aVl5KOiYuPkZGWpK+ngJyckIyGgXZzeHl8jYp24d/Z1M/HyL+5uLOutbOwta6trK6wtLCws7u4v8jR09TV2Njc2Nnl6Hh149jNyMrJysnHysvL0NHK0NLb4OTh43N1c3Vx4HLfcXRxcXFycXR0cHFydnh0fX53f3h3eXt/gX9/g4aIg4eFhYiJh4yMiIuMgIqOkJCRkpKXm5OKkpGVnJiSlZSVmZ2hnZaLhH+EmrLDtaeWl4KQiod7hIufq6+hrca6xKOfp4+Ed3jefnR1dXBwcHFwdHd5fHh6eHpzc29ubW1ucnN1dnx9f4F/fn6BgH6DhomMjo+bn6Wup6WZkY2aopujq6elrrezpKCopJ+egK2pnp6jrK6lrLCrr7GptLCsqqujm5+bn6Wem5aPipCRh4aLgYiNh4aChIiOh4WHfHp+fHl8fHyBiIeQk4qLi4yPh4aFiYeDfn+DgIaMipGZoay4r6qxrq+yr7W4xM3Ozca7t7Susa+us7O3vcLBwsbKxs3NzszMzM3MzMvIzs7PgM/S0tXY2dve4OHg4OHk5eXl5OTk4eHh39/f3d3b2dnY2NbU09DT0tLT0dLR0tHR0s3IyMPGyMrGwbO+wsrO0M/LysbIysjIx8PCwb7BwsPCxMDAvLy8u7u7uri2t7i5ubC3ura3vbayuLu8vLm6ube4uLq5uLi6u729vr/BwL6/W7y2u7i5tqyljoSmsK+wr66usK2soautrq6srq6sraisra6uraytq66vra6vraypqKaqrKywsbCusK+wsbW3uLq6vb+/wL++vsDAwcG9vsC/vr+/wcHCxMHDxsYMs7OzsrKxsLGxsrKyhLOAsrCytLCztLS3trm6ubm6vL/Av8G/wsTFwcHDxcjKy8rIyL25rLW7tLm5trjBu8HIv7+3tby7vLu5sry9vbm7t7m2ubS1sbS0tq6lqKypr7W2s7Gzsq2urqeip6qura+trayroaSnrKmpqaCmp6WlpaimqKalpKSkoqKioKGhoJ8aoJ+fn56cm5mZmpubnp6gn5+fnp2dm5ycn6CFnQecm5ualpOVhZwDnZ6dhJ46nJycm52dnZ+fn56enpydnp+hoZ+gn6GjpKShn6CipKOhoaOioqOjpKWko6SjoqKjoaGfoaGgoJ+fnoSdF5yenp2dnJydnZ2cnZ6enp2dnZqbnJydhZwjmpubnJubmpubm5ycnZ2cnZ2dnp+fnp6fnpydnI6dnJ2fnp6InQieoKCgoZ+goIihC6Kio6OjoqGhoqGghqEFoKCem5yEnVKenp+dnZybnJycm5qampmam5ycmpuamZiVjoqEf32DhoWOlZeWmJeYl5OOi42QlJmgrLqxpqWalJGOhICCg4eYlIL3+PPv6uXm3trY09LW1NLUhM+A0dPQ0dPa1dvj6u3u7fLv8PLu+v6CgPvx6ufn5uTm5unp6O3s6e3x9/z///2AgIKBgf6B/4CCgn9+gYGCg4CBg4aIhIqMhouIiIiKjY+QjpCSlZCTkZGSlZCVl5WWl5SYmZmanJ2doJyXnJudpKCbm5ydn6CloZ6UjIWLobbCsquAm5+JmJaWiZCVq7a4qrPKw8ytqbGakIiJ/42HiYmIhoWGhYmLjpKOjoyOiIaEgoSBgYaHiYuPkpOVkpGTk5KTl5qcnqKiq7C3vrm4qaGfqbKstb23tMHIw7WyuLOrrbu2qa2zvbu2wcK9wMK+x8O/vb62sbWzt7u1squknqWmnZyAoJaeopuYmJicpJybn5STlpaSk5SYnKKhq6yjpqanqqGfn6KgmZKUl5WaoKClqq65w7mxuLOysrS7vcTM0M7HvriyrKqppqmsrrO4t7i7vLe+vr+9vr69vb6+ub6/wL7AwMTGyMnLzs3Ozs7P0NHS0dHR0NDOzczKycnIx8bHxsRQxMXCw8LDwcHExcbFx8XAu7y1ubu8ubSmrba/v8PDwsG+v7++vry5ube2ube5uLm3uLSytLKysrCwrq2trqynra6rrLCpp62urrCur7Cvrq2Eriywr7Cxsa+xsrKzrqitqKqpopuFeJ2hpKOjpKSjo6KYoKGkpKSmpaWkoKSkoYSjNaCioaKkpKOioaGho6SlpqampKmoqKaoqqyvrrCxs7KxsrOztLSysrKzs7OysrOxsrGvsbSzLJiZmZeXl5WXlZeYmJeWmJiXlpeXlJeXlpeYmZmam5qbnZ+foJ+goKGgn56ihKF1oKCbmpGUmJOYl5aXnZidn52cl5ecm5qbm5adnZuampual5uYm5iZmZmVjY+SkZOZmZiWmJiUlZSNho6TlJOUkpOSkIyPjZGTkZKLk5OSk5KUk5OTkZCQkI+Rko+Qj4+PkI6Pjo6NjYyMjY2OkZKTkZGQj4+PhY4tjY2Pj46NjYuLhoSGjo6MjIuMjY2OjY6MjI6NjY6NjY6OjYuMi4yOjo2Njo2QhI8ekZGQj46Nj4+QkI+Qj4+QkJCPj5CRkZKSkY2Oj5CPhI0CjI2Ejx6OjY6Ojo+OjpCTkJCPkJCLjo6Oj5CRkJCRkJCPkJCEjyaQkI+QkJCRkJCRkZSTk5STkpOThpWUlpeXl5aWlJOUk5OUk5KSkYSSA5STkoSUCZWUlJWVlZSVlYWTEpKTk5OUlJSQkZOTlJSTk5WUlISTeZSUlJORkpOTlJOSk5STkIyIhYB6en6BgYmNkZCRkJKTkIyLkJedoqq3yL6xrZ2XkpCFfoGBhpaTgfT28O/q4OTd2dfTz9bX0dLP0M7N0dXS0NPZ09rj6u7v6u/x8vDt9fd/fvXv6ujq6ujo5+ro6PDu6fDy+Pr///uEgDJ//YD9gYWEgIGCgoSDgIKEhoaFi4uHi4mIiIqMjY2Nj5CSj5KOkZCQkJSTkpWSkZKTlIWWgJSQlZaXmpaUk5WXlpeYl5SKhHuBm7C0npmRmIKUkZWHjpKos7Oor8bAyqmms5mLhYj8jYaIiYeFg4WEiouPlY+OjY6HhoKCgX+BhIaIi5GSkpOQj5GPj5CUl5mdnp2lqqu0ra2jm5WfpaKprKalsbWzqaanop6fqaOeoaWsrKi0f7exsbayu7m0trizrrGwsrGvrqagmqGjmZigl52fmJiWl5uhnJqelJSYl5KUlpibpKGsq6SoqKipoZ+bn52ZjpKVkZWenaCpsrzJvLS7ubm0sbW2ury8vLqwq6egnp2bnZ6fn6Olo6Skn6OioqGioqOioaCfoKCgoaOio6Wmp6mGqherrKyrq6qqq6qpqaeop6ampKSmpqSkoYSigKSnp6qpqKaknp6ZnZudmJWLjZifn6Omp6elo6WkpKGenp2cn5+hn6Cfn5+enZybm5yamZmXlpSTlpeVlZiVk5aYmZmXnJybmZmcm5ucnZycm5qYnJybmZeTlpOWlI+IdmuMkZSUkpGRk5GRh5CQkpKTlZOUlJCRkpCPjo2Ni42OMo+SkpGRkZCQkpOUlJaVkZWVlpWVl5aYmpqam5qamZqcm5ycm5qcnJmYmJiXlpaWlZeX/3//f/9/tn+ofoJ/ln6FfwN+f37kfwF+/3//f/9/o38CAgQAgMLDxMK+vMfGw8XFwcTEwcG/ub7BwMC/wMDFxcXDwcHDwsO+tLayr6urrK6tr7O1u7y8ycvDyMjKycu9prHL0M3T1M7S09PR0tLN0NTT0c7MysHBy8rHx8fCxcvGycjJx8bIx8bDxMXGxMPBwsCwrbe/urG0s7WytrK1trW3tra4Ube2qrKzs7OxsK+ysbCtsa+vrq2traymqKiopqeprKqnqKmqpqSlqamnpqKjo6Slo6OjpaKho6OjpKSkpqeoqaqrqKampqimqKeoqKmpqqytrISrG6mop6mus66pqa2wsK+sq6urra+xrrCvq62uroStDa6trauqqqqrrKmoqKiEpQinoaOjpqWlpoSlDKakpKOjoaOjo6Khm4ShGaKhoaGio6OipKOjoqOipaSko6KhoKGgoaKFoBCem5+gpKOkpaamp6mqrKyshK0Oq6uqq6qoq6yrq6qqq62ErICqqqurqaiop6inpaWlpqWmpKWlo6OkpaSjoaGen6Cfn6CgnZqenJ+enJyblpWOjYiBgIeDjpSUkZaZmpiSkJafo6u2wb63qJujr8fXyZeOi4SIlpCCeXTg2NnX1NbSysTAvMPHxszS0NTY3eLf18/Ow7WmoaGcn6awt73E0t7icoBydXZ5dNzS0M/Q0MrGx83My8TDycjT2G5t2dTT2OB04XFw1t3d2sjLzdbfb9zedXl4en5/f4KAhIeHio6VkImNlZaZmJeWlp+dmJuYmJyns7u6tLa4urStm5WNg3l4c3V35XBtcW5vcXFu19DS19jV2NxzcnFwbm9xdXh6eHZ6e4B6eXt7fHyAhIiOj4+KioWKjYmKiYySkZKWk5KWkZWamJeanaiqo6OsqaupraiooqOmp6mpqq+4uKegpKCanp+ckpCVjImQhYCHi5Wmr6ypsbmihJWim39/d36Gg46QioqJi4uIgoN9eHyAhoeEfXx3d3h1eX+FiY2Uk42LjIiFe4B9iI6PlJqco7Gxsr/EwLy4tLG2trrBx8fFxL21tLCsraqur6+vtbaxrLW9wsC+xsfKycnLycjIx8nLzM3O0dLX0tTU1drb3d3f4eHh4OHi4uTj4eDg3+Dg3d7c29nV09PS09LT09TV09TT0M/Lw8jPyMTFv7Svw8LNxsbCyc7EzYDLycTIx8XExcTDw8C+v7+9v726ubq5u7q8u7q6uLSvtLa6tbO5u7Cpubu8uLm4t7Wwsq2krLG3m5CWpK+1tLW0tLW5ura1sqqrpamnpJmOkquwrq2rraytra2qqqmnp6Klpainqamqp6agsLCwsrKws7Kzs7O1t7S2urS6vLOywAO9vsCEwhTGx8fHycfIyMfGxcPAwcG/wsDBwYCzs7K0rqq0tLKys6+xsrGysKuvsK+vr7GztLO2tLKwsLKzr6erp6OgoKCjoqWoqq+xrba5tLe2ube6rpujub27wMC8v8HAv7++u7/Avr+8u7ivsLu5t7e2srW4s7a2t7a1trW0s7Sys7KysrCuoqGpramjpaOkoaWjp6inp6elpzimppumpaWlpKOjo6GkoqKgoqOjoJ+fnJycnZydn6Gfnp+goJ6en6ChoJ6bnJycnZydnJ2bnJ6bnISdBp6eoKCgn4SeBpyfnZ+foIWfEKCfoKGfn56foqOin56hoqGEoDqhoaOjoqOin6CgoaKhoKChoaGfoJ+en5+enZydnZycm5yXm5ucnZydnZ6dnZ6fnp2dnJubnJyalZuchZ0InJydnJ2cnJyFnSqenp6dm5ubnJ2cnJ2cnJuam5yenp6dnp+foKGgoaGgoaGioaGgoaCfoKCGoQ2goaGhoKGgn5+foJ+fhJ4CnZyEnYWchZsBmoSbgJqUmpeZmZqal5KTjoiBfoCFhImQkJKVlpaWko6WoqeuusnIwLCkq7bO39SkmZaNlKOcjYOA+vHz9PD07efg3tzi5OTn7Ovs7fL4+PHr5t7TxL/Au8LJ09ne5e76/H5/gYOFf/n08fHw8Ozq6e3t7+jn6+z0+39++Pj29/iB/4B/gPb8/vfm7u/4/4D8/4SHhYeKi4mMi5GSkZWYnJmUmJ+fn6CgoaCnpaKjn5+irLO4t7W2uLWyq6Oel46GhoGCgf6BgIOChIeIhP/5+Pr89vX1gIGBg4GDh4qNj4+OkpGQj5GQkpKWmZ6hpKSgn5ygo52enqKnpKWqp6erpaitqquvgLG6vLW1vLq9ur62uLO1tra7urq+xcW3sbavqKqqqaGcopyVopaSlpymuMLCvcTLtJittq6UkoqTmpWho56fm5+gnJeXkoyTmZyem5SSjo2PjZCXnKGorqyioqajn5OTnKSmp66ts77Cv8vTzsbIvbvAvMPIy8zLxsC6tbCtqaapDKiopqurqaGrsrS2toW7FLq7urq7u7u9vby/wMLBwsTGx8jNhM89zc7Pz9DQ0M/OzczPzczJycjHxsPDwsPDwsPDxMXExMG/v7e+wrm1t7SppLS2wLq6tr3CucC/vry9u7q5uYW4fba2s7S1s7GysLCvsbGxr62po6utsauqra6mn62vsK+vr66tp6qkm6SlqY6IjZujqaqrqqupqqunqaWfoZygn5yPho6ipaOioaOioqGhoKChnp6an56goaKioJ+emKanqKqqp6eoqaqrqKqpra6ora6mprOwr7K0tbSytba3hLgNt7a1trWzs7Ows7Gxsh+bm5iXkY+Zl5WXmJaXlpaZmJWYmpuZlpWVmZiZmpqYhJkwkpSSkYyNjo6PkJKSk5SSmZqWlZaXl5eRhImWm5qdm5ydnZ2cnp2bnZ6cnZybmJGRhZqAmJucl5qbmpqXmJeYmJeXlpWWlpWViYyTlY6LjYmKipGMkJKTkZCRkpCRh5GRkpKRkZOSjpCRkpGRkJGOjo+Pj42NjI+PkpGRkJCQkZCQkI6Njo6PjY6Oj4+Njo2NjIyNjY6OjYuMjY6PjZCOjo6Njo6Pj46PjY2NjI2Ojo+Mjo89jo+Pjo+RkY+PjY6Ojo2Ojo6Njo6Oj4+QkI+QkJCOjo+Pjo2Ojo2Njo6NjYyLjYmNjY2OjpCPj5CRkY+QjoePCIqOjo+Qj4+PhJA4kZGQkY+Qj46QkpKSkZKSkpOUk5OUlZSTk5WUlJSTkZOSkpKTk5KTk5KRk5KSk5ORkZORkZKUlJKGkxSUk5OSk5OSk5OTkZKUk5OSkpOSkoWTgJGRkZOSkpORkoyTj5KRkZKQjIyGg355e3+Ag4mLjJCSkZGMjpimrbTC0cnArqOsudPo16KVkIqQnpmKg4D48fLx7/Do5uLd3OLl4+rs5+rr7vP38enl2sy5sLKvu8vX3OPo8/37gYCBgYOB/Pb38/Xy8e7v7/Dz7evu7fj8gYD7Kfz3+fmB/X+B+/r69eLv7/r9gP38g4aEhYeJioqIi4yNj5GVkpCSl5eWhJeAmpqZlpaWmJ2foJ+dn6Ghnpyal5GIgoV9f3z1fX+EgYWLi4X87+ru8ujn5Xt8fYB9gYeOkpSUkZWVlpKUlZWVmaCgo6SloqCdoaCdoKCkpaWnqqWkqaWnqamqqaqysq6utK+ysbCrrKupqqqtrqytsrSqoKSinaKXlpKTmJKMmI2Ai5CVnay2t7e8vKmRo7CqkZCGj5eQnJyYm5idn5uUl4+LkJWanZmSkZCOko2PlZyiqK2qoqOkoZ+TkZukqaessrbCxcTQ2tbNy8W8wrq9wMPCv7iysKuloqCbmpqam5ial5OboKGhoKSjpKKgoJ+goaCiop+hoaOio6KjpKWoqagZp6iqq6epqaqqq6qqqamoqKmmpqiopqOjoYSjgKSlpKSmp6ahopqhppybmpePi5eZoqCem6KknKOkoqKjoqGgoJ+foJ2dnp+dnZ6cnJyam5qampmZmZKLlZaXlpWVlJKPl5qamZydnJqSlZOLkpGRd3eAiZSYl5iUlpaWlZOVk4+Pio6OjIB5f4+Sk5OSko6SkZGPjo2NjIiMjpCPN5GRkpKQiJWUlJeXlZWVl5WWlpaVmZqWmpuSjp2bmZ2enZyZm52dn5+dnZ6gnJydnJuamJycnZv/f/9//3+/f6h+hn+SfoJ/hX4Ef35/f4l+A39+frN/AX6If4h+/3//f/9/rH8CAgQAe8bHycnJxsnJysjJycvIysXGw8LGxcPHxMPBvru9uLW3trOwrainqKqnp6OhpbC0sbS7w8bOxLC9rrq/sb3KyMjS1tTT1NXT0dPW1czU0NPT0dLOzcvKy8vRz87Ozc7My8jKx8PGxsfHwcLDxMG4vr3Aua+mpKS1sbG1uIS3F7i6uLezs7S0s7Gysq+xsrW0tLGwra+whK05r6+tqamrqqmqqqimp6alpaSmpqSlo6KjpKWgo6KioaSko6Wnp6anpaOioqOlo6KlpqeoqaioqKmohKk1ra6trqypqq2tr66vrq6ur7GysK6tq6qrqquqqqqrq6qrqqmnp6mmp6enpaSlp6Wlo6Slo6GEoxiioaGgoaGhoqKgoaCenqCfnp+goJ+goKCEoVegoKGin5qfnZ+fnp2en6GfnpyeoaGfn5+ho6Wlpqalqaqsr62tq6yrq6utrKusraurqZupqquqqquqqampq6qip6inp6ilpaSlo6KioaOkpKOkop6hnp6Gn4CgoKGfk5qem5yakZORioiKhYeRlZaVkpCOlpago6Ccj4qNlZ62urfHz9ba1M3Q19HTx7GpiJOUl5qHf3Rz187Jy9LS09XW19PU08vBuLSpoJiZnKOtt8PU29xzc3V2dnqCgYB9e3l7eXp6eXZxcnLhcW9xc3Jyd3Zw3NjQztLTzoDMzdVtb290dHR5eXd5f31+fHx/foSBgIGCe3+EiYeMj5CVlpCMiI+IjY2IjJCVjZmfjoyOi4yIi4iEg4GDgn9+gHx+gYB+f4GCg39/g4aJiYmHh4eIioqOkJGSkZCTlZiZoaSnpqShn5uTnpmjpaOhp5yUlJqVmZOVjY+IiIqGh4CKi4uKjZeWko+QkZCOmriyp6+kqp+tsLeorrG3qqujq7Swr6eutaminJqTjouKhn+Ki4SCgYmKhYV4dXx5c3J0eXt7cm52eXZ6fX99gH+Cg4mIkI2VkJGIg31+foONranDwLm2raOrsra2uL7Bwb29taujnp2hpKeqqqemq6mjqSWxtri5urvBw8PExsbIx8rMyszNzM3Qz9DQ09LR0tLR09TZ3NzbhN2A4OHf4OHf3t7b3N3a2NnRz8rCr63E0dDS0MvQ0MnDx8zKxcHNzsvMzM3MzMrKysW1tMbKx8XExsfIx8bFw8PBwb6+vby5uLmws7q4trCrnpidmpqhq62rqKWjsLm1srayr7a0rqmdk4WPjo6LkKChlpSapq+ws7KssrGuraukmaQKpJ2irK2pqqyopoWpP6igqaqpqaaprq6srbCuq6WusLKztbS0ube3t7S4ubq5u7a7vb+/wMDAwcLDxMXFxsbEw8PFxcfGxsbHx8bGx4C1tba3t7S2t7a2t7W1tbe3t7WztrOytLOzsrCtr6yqqqmopqSfnp2fnZ6bmp2kqKSorrO1urGkr6WrsKayuLe2wMPBv8DBwL/AwsG4vry+vr29urq4uLm3u7q7urm5t7a0trWvtLS0srCxsbGtqa6traijmpeYpKOjp6enqammpyCnp6WlpKSlpKOkpaOkpKSjpKKjoaGgn6CgoaGhop+foYWgHZ+enp2dnJ2enZ2bm5ydnJqdnJ2dnp6dnp+fnp2dhJsempubm5ycnZ6enp+fnp2enp+io6GhoJ+eoaChoKCghaIIo6Ggn5+goKCEngSfoKCghZ4BnYacDZqbmJqbnJuam52bm5qEnAabm5ybm5uGmh6bm5ucm5ubnJubnJybm5yXnJubm5qam5ucnJqam52EnA+enp+dnqCgoKGgoqGhoaKHoQmgoaGhkqCfoJ+EoB2fn6Cgm5+fnp6enZydnJucnZycm5ucnZuXm5qamYWagJuamZqNlZmXmJaNkY6GhYeChI+RkJCOjI6Tk5mcm5mNjY+XoL3Au8fX3+Te19XX0NDJurGToKCko5SNgIDx6Obo7e7v7+/y7e7u6N7Y1crCvL7AxdDY4/D2+ICBgoODiIyLi4mHh4eGiIaIhoKCgf+AgICCg4OGhoD39/Pw9PbygPH0+X6BgIOFhIiHhIeLiYmJioyMj4uNjoyIjJGUkZSYmZ2emJeWmJSXlpCTlJqVnqSZl5mXmJeZlpWVk5OTlJOTkpKUk5OSlJSVk5GUl5qZmpqZmJubm56goaOjoqSnqauwtbe3srOzrKWxrLOysa20raSkqaanpqWdoZqZnJmYgJubmpqfqaShnpygn5+qx7+5vLK1rL++xLe+wMS8urG8wr/CuL7EurKsq6ejnZyWk6CfmpaWnJ6Yl4qIjIyGhoaLkI6GgoyPjJCUlZGVlZqcoKGoqLCqqqGbkpORlJq2tMvLx8S7r7q/wsLCxsnKxMK4raafnJ6goqOjoZ+joZqgCqatrrCysre4ubqEux26u7y8vb2+vr/AvsHBwsLBwcTGx8jJycvMy8zMzobNHMvLy8jHx8C+uLCgm7K/wMLBu8HCvre6v767t72Hv4C+v8C7qKe4vb26ubu7u7q5ubi5uLm2tbOysrKvrKuwrq2noZWSk5OTmqOjop2cnKmwramtqqmuq6ahl5CBi4mHhIeSk46PlaCmp6imoKWlp6Whm5SamZSZo6OfoKKgnqGgn6CgoZqhoqOjoaOmpaalqKaln6inqKipqKmtq6ytqxCura6trquvsbKxsrKytLO0hLUPtrW0tba2t7a1tra3t7a3JZydnJqampybm5ucm5uanJydnJubmpmcmpqbmpmbmpeYlpSSkY6EjS+Oi4mKj5CNkJSYmJmVi5OKjZSPlpiVl5ydnZycnZ6dnp+fmJyanJybm5iYmZmYl4SaIZmYl5eVl5aRlZWVlJKTlJOTkJSVk4yKhIGDj42NkJCQkoSRBJCQkZGGki2QkZGSj5CPkZCRkZGQkI6PkJGQj5CRjYyPkI+Ojo2Oj46MjI6Njo2NjIqNjYuEjlaMjIyLjIuNjouKi4mMjI2NjI2Ljo6NjY2OjY2Ojo+Njo2PkY6Ojo+Oj4+OjY6Pj4+OjY6NjYyNjI6Pj5CPjo+Oj46OjYyMjIuNjIyKjIyNjY2Mjo6Pj4WOLY+OkI+PjY+Ojo2Njo+Oj4+Oj4+Oj46Ojo2Pi4+PkJKRkpKSlJOSkpOVlZOSkoSUF5KTkpOTkpOTkpGSkpOUlZOSk5KQk5OGhJKFkRGSlJSPkpOUk5KSk5KRkJCPkYSSA5STjoWRhpITlJKFjpKPkZCHiIOAgIJ9f4WJiYSIgI2OlZSTk42PkpypwcG8zt7p7ePTzM7HxMCyrZOem6GjlY+Afu7o5ebv7+3u7vDt7+/o49zVyL+2uLvEzdjg7/X5f3+AgIGFiYeHh4aFhoWHhoeFgYKA/X9/fYGCgoSDgfb39vT5+fb3+f+Bg4GChYWHh4aFiYiJh4iKiYuIiIqJKIWJjIyNi4+RlJaUlJGUkpKQioqLj4uPlZGQkpSVlZiZm5uZnJiXmJuEmoCZlpiWlpaTlpeamZmbmZudnZ2eoKCjo6OkpamoqqytrKusq6ejrKitrKikraagnaKdoKCemZ2XmJeTlZWTk5WWoZyYlpGWlZOasKmlqqKlnauwsqetr7GvrqWrsq+wq62xqKGbn6CYlZiPiZSZkpOTmJ2WloiEjIuDgoaKkY6FgEKLjo6RlZWQlZWXnaChqKWtp6mgmZKSkpGatLTO0c/Kwrm+wMLCvbzAv7q6sqegmJWVlpiYmJWTlJKOkZebm56gn6OEpEKjoqGhoKChoqKhoqOioaOhoqOjpKWmp6inpqiopqmnp6inp6moqKempqSkpKCdmJKDfZSfoqSkoKOkopyfoaKfmqKEowqioqWlo6Khj4yZhKABoYSgcJ+foqChn5+fnpycmpmXmpmYj4qCgX6DhIaOkI2Ji4iWm5mVmZmYnJuWkIaAc3x6d3Z2fX9/gImQlZSWlI2Sk5ORkI2GiIiGiJKTkJOUkIyPjo6Pj5CLkpSVlJGSlJSWl5eXlo+UlpaXl5WWmpeZmpeFmReWmpydnJuam5ucnZ6em5yenJ6goZ6gn4SeA5+cnv9//3//f8l/nX6VfwF+iX+Kfv9//3//f/d/AgIEAIDGyMvJysnKysnHxcrKzcrHx8bIyMfHyMXExcPCu7q7ube3tbK0sLGvqquwtrm6vcHBxsbKyryour24sLe0xMnI19nW09LU1dja2drb1tfY1tbW09TQztDRztDPzc/NzMrIys3HysjCxcHCv8DBvbm6uLi1tbq5t7W5uru7tre5ujO3s7Wys7WztbW2tbKzs7W1srCura+vrayssLGur6qqrKyqp6inpqelpaalp6WlpaOlpqOEpTekoaOlpaampKSloJ6goKGhn6Gmpquuq6qnpaWnqKqpqqupqqyqraytrayrrq6srqysr62sq62rhKovqKinp6eoqKqop6inp6eopqWmpaWlpKSioaGjo6SjoaGfoaCenqCgoKGgn6GhoKCEn4SeNZ2foKCenp+dnp6dnZ6cnJ2an6Cfnp+hoKKhn56foqOkpKeoqKysq6yrq62tq6yrrKusra2thKsXrK2sq6qpqaqqqaioqKalo6empaSjo6GFo4ChoqOioaOioqGfm5+hoJ+gn56fnpyenp6dnJiZmZiUlpmamJWQi4yQkZKVmZ+dmpOLg4GCjJ2rra2suL/Dvb68y6ms2cTAnJB/f3p7dnR34tTc3dvc2t/g3tbLx8fK0tjd2NHWz9x04XV2dXbi4dfT33Li4XPd3+FydHV4dXZzdoB3dHd4enh1cnZzcWxwcnR1dXFyd3d4enV0eX6DfH9+fH99foKFio+QjY+SkImKiZKVm5aUlJOIjYV8hIOAgoqKjZGRjZKPj5SNj5KQkJGSl5ORlJOPj5KMlJugoJ+YlZOTk5ylo6KjoKapqLCxq6qzt7qnoKWjpZyZoqGdpJ6amICWmpKMjJKQk4uOlpKcoaakrrG0rba8rq+5sMHRwLy0vrS3xM3I0NHLxcGuo5qNkY+HhH9/d3Nv2NLVy8LExdRucXN8fH+CipCVkpGMl6Ckp62qqKKoo6ypnJmVioN8goF5ent8gIWNmKunrqupqK64ubi8u7iyqKKbl5ydnZ+flYCYmZ2joqGjsLO5vLy+wMG/wcHBw8TGxsbIx8bIycvLzMzOztDS0NHR1NXU19fX1dbW2Nrd3tzb29zc29vc29rY1dPKx8a/v8nOz8jMycbDtLXCysfIycvJycnLysvLzMnKwr7Fzs3OysfGx8bFvru3sKmkrbu6tKikq7Cmtrizp06fo52em5Ocm5SNjpWFjpekpKqvpKuxrqmdn6KipKKckZKUiIeDlqmzs7GwsLGurK+wr66trq6uraqiq6uqq6qsrKytra2sraqtrq+wsa6EsDCxsLGztbO1tra4trm5ubu9urq7vLy7vbu8vr7Av8DBwsHBwsTFw8fKycm8xsrGxsYEtre3uIa3gLW2tri3t7a2t7e2t7ays7Wzsq2tr6yqqqekp6SlpKKhpKirq6ytrbS0t7irnqytq6WnpLG0tMDDwb7Av8DExMPCw8C/v769vr6+u7m7urm6ubi6t7e3tLa4s7a0tLWws62uraunqaiopqenp6alqKmpqKanp6imo6WlpqWkpaWkJ6SjpqelpaOjoqGhoaCgoaKkoaKgoJ+fn56fnp6fn56enZ6en56bnISeKJ2dnJqbnZ6dnJycm5mXmJiZmZeZm5ueoZ+em5ucnZ2enZ+gnaCgnqGGoAGhhKAboaCgn6Cen56enZ2enZ2cnZ2enZ2dnJybnZychpsGmpqamZqbiJoHm5qam5ubnIWbhJoIm5qZmZqbmpqEmx+ampuampqVnJ6bnJydmp2dnJubm52enZ+goKGhoaKghKEToKChoaChoqKgoaGgoKGhoJ+en4SeDJ+enZycnZ2bm5ucnISbgJqbm5ucnZycnJubmZqZmpqbm5qampmampmYmZWWlZSQj5SUk5CNiYqMjo2RlZuZl5KMhIKIjKCws7Otub/Ev8C7zrCw3cnGpZiKi4WFhIKD/fH59vb49fb49+/l4+Xn7PL39e/y7vuD/4OFg4L9+vTx/oL8/ID8+/6Bg4SFhIWBgISFg4eGiIWEgoWDgX6BgYOEhoODh4aHhoSDh4uQiI2KiIyMjJCTlZeYlpibmZWVk5ugo6Genp2UmJSMkpCPkZiZm5udmp+dnJ+anZ+enZ+foqCeoaCfnqGboaipqaqno6Gjo6eur7CwrLS0srm4uLm+wsi3r7Sxs62qsLGstq2sgK2qrKaenqOjpZ6iqKausre3wLy8u8HIur7Hv8zczci/xsK+ztPR2djUzsy9rqedoJ6XlJKRiYOA/vX48Ofr6vmBhYiRj5KXoKWnpqairLi6ucC8u7a+vMLBtbCqm5qRl5aNioqJjpKbo7q1urm2t73CwsfJxr+2raSdmJqampycEJCTlpecnZqcpqmusLK0tLaEt4C5ubu7uru4uLy8u7u8vL2+vb/AwcHDxMTFxcTExcXHyMrKy8rKy8vKysnJyMTDwba3tbKwtry+uby7uLeoqba+u7y+v769vL29vr+/vry3s7i/v768vLu8vLqyr6yloZums7Csnpqkp5+ssaidl5iYkYyGkpSQgYOIe4iVn6Clp16epaimoZSanJqbmpOJiYp8fHmKnKanpqampaSjpaWjpKKjpKOioJmgoaCio6SjpKSjpaanpaenqKmqpaipqKiopqenqamsq6qsra6sra2wra6vr66usK6wsLCxs7OzhbQMt7a5uLe5r7S3uLm4BJ+gnp2Fnludnp2anJycnp2dnJudnpqbnZybmJmZmJeXlJOUk5KQj42PkZGSj5GQlJaYlo+GjY6Pi4yJkZKUnJ2cmpudnZ2enZ+fnJucnZybm5qampmZmJqZl5mYmJeVlZaUhJYulJWRk5KOjZGQj42OkZGQj5CRk5OPkZKTko+Oj5CQj5CRkZGQkJCRkY6Nj4+RkYSQDY+OkI+Pj5CQjo2OkJCEjwaQjY6PjIyEjRCOjoyKjI2OjY2NiYmKiouKhIsaiomKjI2MjYyKi4yNjY6Mio6PjY2OkI+Njo2IjhSNjo2OjYyNjYyNjI2Ni42Nj4+OjoWNV4yOjouNjYyNjY2OjYyMjI6OjY6Qj5CQj46PkI+OjY2Ojo2NjY6OjY2MjY6Pjo+QkJGSkJCQjJKVlJOTlpOWlZGPjpCRkpKUlZOTkpOTkZGTk5OUkpKTk4WSAZOEkhWRkJCRkJGSkZKSkZGSlJKRkZGQkpKIkYCSkpOUkpGPkpOTkZOTkpKSkZOSkZGRjIyNjIqKio2Li4mGh4mKiIyQl5aVkIyFiImNore5s6myuLy1trbLq63ayMemmImLh4aGg4b+8/j49vn19/n27OPh5Obt9Pn17/Pv+oH8gYOBgfz59vb+gfz7f/z7/YCDhISChIOGh4SGh4CJhoWChYWEgYKChIeHhIWHhoiHhoWIi4uFiIaGiYmMjY6QkJCPjpKQjI+QmJqgn5qZl5OVk46Qjo6Qlpmampybn56foJmbnZ6foJ2fn5ydn56dnZucoKKjpKKioaGfoqampaajp6Wmrq2pqK6vsaigpqaqp6apqaaupqampaikm4CdoKGjmqGjoaeprKyxr7CssLKqqLCrsbevrqaop6evtbC3trGzsaidlouSkIiGg392cG3c2+DXzNPW53yAg4yLjZOeoqGen5unsbOxtrGvq7Ozt7auqqiYlo+Uk4iIiIeKjpmnwbrDwbq4u768ur+9uLClnpeTlZaWlZOKi4uOkgiRjY6Xmp6hooSjGqWkpKSlpaKhoJ6doaGhoqCgoaOjo6SjpKOmhKQIo6SkpaalpqWFpimlpqWkoqKglpiZl5aWn6Kdn5+dm5COm6GgoKCioaKjo6GjpKWjoJmXnIWggKGhoJ+XlZOPjIiSnZyYi4iRkoqYmZKIh4WHenRuf4OCbXJzaneJk5KVmY+VlZGNg4uNi4qIgHZ1d2lqaHiNlJWVlJSSkpCRkZCRkZGPkJCPiZGTk5WVlZeXmJWVl5mUl5eVlZaWmJeXlpaXlpWXl5mZmJqYmpmYmpyamJuamJeYGZeanJudnZucm5ycnp6goJ+dnp+YnZ+gnp//f/9//3/Qf5d+An9+hH+Ffgd/fn5/fn5+/3/Af4h+/3//f8V/AgIEAIDCw8TDucHCw8PBwsPFvsbEx8fGxMbFxMXDxcLBwcLCw8LAv769vLm5uLu8vsHDw8PEycfEwsO8wsW4oa6wxdLS1tfZ2t3f4d3c39rZ2dre3Nzb2NnV0tPP0c7NzsvMzczNzc/IysbHx8TCvry9vb2/sKOcnJqVlJ6osqa5uLa1tja0trSztLe0tLO1tbWysrGwsa+tr7Cwrq6trq6urKmrrKypqKmnpKOjpaenpaalpKGhpKampqSEo4Clo6GjpKSkoqCfnp+eoaOoq6yopqKgpKapqamqqaioqaqsqauqra2rqamnqKinqqmoq6yrqaqpqaqpqKanqKiopqWlpqampaSjpaWlpKWio6KgoaGioaCgnp+gn6CeoKKfnp2eoJ6fn6Cfn5+dnp2dnJ2enp6foJ6dnp6enZydnh+dnZ6fn6GgoqKio6SjpKSlpaanq6uqrKurrKysqqqrhKyDrYSsgKutrKurrKqqqKmnpaalpKWlpaSjoaOjpKSmpaOko6Kko6OioaChoqKioaCgn5+en6ChoJ6boKCfoKCenJycmJiWkpOVkIqJiYmNlZmipKGemZKNh3/x6PR9jKO6wszN0LS4qK+Nk5iMjJeXnYeHj4uVnJqfoZuYf3l5ceLbzM/TgNfX0MrHxMnaceB0dXPg3tra23Bv2tXYbnFvbtzfc3Z7e318end0dnh4dnZ2dXp9goF+fYOGh4iIhYaNiYiCgoF6fHx9enl5eoN7cn2BgIB9dnJ3foGFhIKHjo+VmZ2cmpWSj4uNjIuRkYyLipGZm5GTiIyKm6Kjn5uXlJmlo6yrgK2rs7Ctp6ywvrHEua+ws7K6sbGusra6tbi0rq+urayrtK+ysr+3u8m6ta++u7i4xsGzpq2tnpKOhIiNkJiXkJCToqKZjqKnpqukn4yGfXZxb3B1eHh9f4KEg4aJj5CQkJKMj4qKj42MiIGDhXx9gICCg4J6eX1+hoKHjZecraChgKSprbG2wMC2sqynoJqUlZaanZ2cmpyVnZufpaOoqaessbK2uLq9vb66vL6/wsXEx8bFx8jJycrLy8nJy83Nzs7P0dHS09TPzNPS09bX2dnY2dnb3NrY1NPOz8/DwMG7vsjIv7q/wLy8ysvMy8rJycrLycjIyczKyMnJwa2usKbHWMjBv7q/wsK5tbCutr29u7y8vr60ra+xsLesjI+EkIuQkZKLiJ6noJyipK+1tbCupJuenZGXoqWjn5iQkZGRmquzsrCysrGwra2jsrSklZ2osa6vra6trKuEqkCsr6+sra2vrq2rqqyusK6usbKwsLS4t7O2uri4t7q4uLi6uru8v72+vb2/wcLCxcTGx8bGyMjIxsjGxMXDxcTDgLS1travs7O0tLGztLSttbW2tbW0t7azs7O1tLSysrKzsbCurayrp6ipq6yusbCwsbKzs7Oxsaqtrqqao6KyvLy/vsDDxsXFxcPFwcHEwsPCwcHCwr+7u7m6uLe3tbe5uLe4uLS3s7OysbCuq6qrq62gmZSQjYqIkJmimKmnp6WmIKanpqampaWlpKSjpKOkpKSjo6Kio6GhoqGioaCgoKKghJ8Dnp6dhp4LnJ2bmpyfnp6dnJuEnEKam52cm5qamJaYmJqbnZ6fnp2bmZqdnp+en56foKCfn56gn5+hoaCenZ2enZ+en6Cfnp6dnp6dnp2bnJyanJubmpqEnA6bm5qbm5uYmpuZmJmamYSYA5mZmoSZHJiZmZqbmpqZmpqam5uampmYmJqbm5uamZmZmpqEmQeanJydnZydhJ4QnJ2dnJ2fn6Ghn6GhoqCgoYSgDKGhoqGioaGgoaGioYSgDp+fnp+cnZydnZybnJychJsHnJybm5ycm4ScBZubnZyahpuAnJuampqbl5qalpmZmZaVlpOSko6RkYyHh4mGi5SZnqKgnZaRi4WA9O79gIymwcrR1di9v7K4lpmflpahoqmRkZiUn6WmqqqkoYqFhoD9+e3v8fT37+jo6O34gfyChIH8/vv3/ICA+vf6foCAgPv9goaIiYqJh4SEhYWGhIWDhIk0i4+MjIuQk5ORk5KTmZWSj4+OiIiJiYmHiYmRiYSLj4+Mi4OBiY6RlJKSlpqanqKlpKKfm4SZgJeampqZlp2hpZ6clpaYpqmuqqehoqWvrLS2trS9ure0ubnGvc3Evb3Bv8W9vrq/xMPCxsC8vr++ubXBu7+/ysPGzcPBuMrEwsHMzr+yuLuqoZ6Vlp2gqqmgoaKws6efs7azubWunpiQiYOAgoiMjZGTl5eYm56kpaamp5+inpyhP6GdmJGUlY+QlJSVlJOLiYyPlJSXnqaruKyurrS4vMPJysXAtKylnZaWl5iamJeXmpWcmpegn6Kko6Sqq62vsISzArS1hLeAubi6urm6urm8vby8u72+v8C/wcDAwcPBvMPExMbHxsbGyMjKysnHw8C8vL61sbCsr7i3rqmwsa6vvb/Av8C+vb2+vb28vr/Avr28t6GdoZy3trOxrba4tq6spqaqsrOzsbCzs6ymp6SorKWIjIGLg4iLjYWBlaGXlZyep6ytqqcpm5KTloiNmJ6cmJGJiYmHkaGnpqWnpaampqSZp6eaj5Sep6SlpKSjo6OEpISmM6WlpqenpqWkpaenpqipqaepraytrayrramuraysra6vra+wsrKys7KztLW0tbe3t7q5uYS3Bba2uLa0KJ6enZybnJ2dnZuenZuVnZydnZybnZ6dnZydnJyampqcmpeVlZOUlZSEklaTk5GTkpWSk5KPh4mNi4GKhpGZmJubmpygnp6dnqCcnZycoJ2cnpydnJmZl5qWlJaVl5eYlpeYlZSUlJeWlpOSkZGSkouFgX15d3Z5fYeBkpGSkI+Qj4SRJZCQj5GSk4+QkpGQj42Pj4+QkpGPjo+Pj5GRkZCQjo6Nj46Njo+Eji2Li42Pj42NjouMjY+NjI2OjIuMi4qIiYqMjIqLjI2NjIqKjYuMjY+NjY6Ni42EjgmPkI2MjIyLjIuEjSCMjY6Njo2OjY2MjIqMjIuMjIyOjY2Nj42Mjo2LjIyMjYWMOIuMjYyMjY2NjoyMjY6OjY2Njo6NjI6Njo2Mio2QkpGQkI+PkpGQj5GQkpSTlZWUlZWUlZOSkI6RhZOAkpOTkZKRkpOSkpKTkpOTlJOSkZOSk5OUlJSVk5GQkZCQkZKRkJCQj4+QkZCRkZKRkpORkZKSk5KSkpOUk5OTkpKTk5KTkpKSkZCRkpCSkpGOjY+MiYyLjIuHhIOIiYuTlpufnJmVkY6KgvXt+H+Krs/Z29/mxcW1vJqboZiaqamArpSPlJSepaSqqKajioSFgPr37O3s8fLs7Ozq7PmA/oGBgf36/Pn/goH9/P2BgYGA//2Cg4WFhYaGhYSDg4OCg4KEhYeKiImIi4yMiYqMjJCOjouKi4eFhYSEhomKj4iEjZGOjI2FhIuQkZSSk5WXmZuenZ6dnJiYmJWWlpaZlpiAlZianpuZkpKVm56gnp2am56ioKWmqKetqqumqaqyqLCrq6yvsLCqrqyusbays7Svr7GusKqxsa6xtrO0ubCvq7SrqaqwsqigpaaZk5GKi5KWnZyYl5ego5qRo6mjqaKfkYqAenJvc3uDhoqLkZKSl5qhoaOjoZmdmZmgnZiVjpJAkYqMkpKTkpSNiIuNlZGUn621x7a0tbi2t72/vre1r6qhmpOSkZKSkY+Qk4ySkI+Uk5aYlJecnJ6foaKgoaChoYSiXqOhoaCgoKGhoqKhoaKhoqKho6OjpKOjop2jpKOjpKSko6Smp6ino5+fmpygl5OSkZGbmZKPlpaUlKGho6SjoqKjoqKio6OjoaKioJyJg4OIm5ual5Gcn56Xl5OUlpyFm0yalZKTkJKYlH2Dd3xzeHx/eXOCj4eDjY+VmpmYk4Z9fYNzfYWPj4mBe3p3dn+NlZSTlJSUk5KSiZOTi4OGjZKSk5KVlpeYmJeXl5iXh5g3lpaVlZeWlpeZmJeYmpqXmJqZmZaamJqZmZmYl5yanZucnZ6dnJ+dnqCgn6Ggn56en56enp+en/9//3//f7x/g36jf41+BX9+f39/hX4Ff39+fn6Ef4J+/3//f/9//3+DfwICBAAKxsXDw8PExsXIx4TGgMXFxcTEw8HDxcfFxMLExMLGx8XDxMLExcLCw8PIyMjKw8LHvqq5t6u0ucPCxsfL0dTX1dHX1dXX1tTX1dvX1tXX1dTU1djS09LQztDMzczIyMXGw8PHxsfFwcHBwMHAwMHCsLG6wcvOx7usoJqZkZautLK1t7e0tLOxtLSysrSyZrKysLCvraytrq2srK6urKqpqqmpqKilo6Sio6SjpKSjoqKipaWlpKWlpaSkoqWlo6WlpaalpaaoqKmrqamoqaajo6aoqKaoqKenpqeqqqmpqq2qqKioqaqqqqyqqaqqqKmnpaeoqYSnE6ilp6Wjo6OkpKOko6WlpKSioaOEoRagn5+gn5+gnqCenp6fnp2enZydoJ6ehJ8Pnp6cnJ2cn56dnJ2enpabhJ0Fnp+en6CEoQKio4SkDqanqKmrqqqqrKurqqqrhK2Ar62trayslKqsq6qpqayqqqmop6ampqWjo6KjpKKjpaOlpKOio6Kio6KioqGdn6Cfn6CfoJ+foKCfnZ2eoKGfn52foJ6doZ+emZmWnZqZkZKRj4qJjJKRnpuhpaOgmpeVlI+HfnmDhIqdoqK3w9jY39fY0L3UrKK0vJKaoo514OSA4eHheHZ6enh3dHV3dnl+gYSCfX6Af39+fn1/goOCfoOKh4SKiICIiYaDfXh4d3N3eHV1dHR0cnNye3x8gHx9e3x/fXZyd319foN6eH+ChYN9eXt3gIOAf3+AgoSIjIaHiI2SlJmVlJOTjYyIh4iFioiKiYSCiIZ+foOEg4iKh4iAi4iNioyKjo6GjZWLl5WNjJKKhYyUmpSNiYuTl5WanZSYnKCVmZaepKawsa+vp7KsoJuXsaOgrbGrq667t62ooKeknKintL+0tcW+sbu+wbypo52Ylo2GgoB/eoKRmZiOgX58foCCe4GCf3x+g4B9f35/iIeKj5SdoZuho6ilqaVNq6qpqrG7vr28ubGqpJ2alJOTkZaYnJyfn5uanJ+no5+gqa2sq6+ytLe5ury9vr++wMDAwsXGxsXGxsfJxsnKycnIycnIysrO0tHR09KE0YDS1dfX2NnW19DS0cvNzMvOz8vQ0tDKycnQyszLzsvLysnJycjGycrKysnJysrJx7+ytsLFvK+osLnAwcLBwsHBvL28vLu4tLKzrKu6t7W0tK2np6Knsqqpt66Vl6Omo6WzsKWqqqKorKersq+yq5OQnqaoqKOhmZGlsK2usrKus1KYmaCmraysp6Srrq6qqautrq2tq6+vrauuq62srKuqra6vsK+wsbKysbC0tbe3ubq7vL28vL2/wcPBwsPExcPDxMPGxcjIxsfHxcbDxcjIw8HEgLa1tra2t7a1t7e2tbS0tbW1tLKxsrO1tbSzsrSysrO0s7KysLCxsK+xsbSzs7WysLOtmqqon6Wpsa+2tri6vcK+vcHAv76/vsC/xMDAvcC8vb2/wb2+vLm4u7i5uLO0s7OxsbOzsrGvr6+srq2rraydoqapsLWyqZ2UkZCHjKOmA6OmpISlJqOkpKOjpaSkpKOhoZ+goKKhoJ+gn56gn5+eoJ+enZucnZycm52dhJxTnZ6dnpycm5ucm5ydm5ydnJ6dnJ2en5+gn5+enp2cm5ydnp6fn52enZ6dnp2en6Ggn5+goJ+enp+en5+enp+dnJubnZydnZycm52cm5uZmZqZnJyEmyeZmpuamZiZmZiZmpiZmpmZmJiYmZmZmpqamZqZmpucm5qYmpiYl5iEmgWbm5uTmISZBJydnJyInRecnJ2dnp+foKGioqGhoKCfoKChoaGiooShAouehKEIoKGgoJ+dnp2FnAabmpqam5yJmwuZm5ubmZuamZubmoabgpqEmXKal5qZlpiZmZmVlJKZlpSMjY2Lh4aKkpCZmKKinp2bmZaUkIiAfIaIjqSqqbTC2+Ll2uLZxNi1qbu/m6CrmYHz+/v594WChYaDgYCBgoKGioyOjIiIi4yLi4uJi46PjoyNkpKQlJKNkZORjomGhoSChYaFhICBgYGGh4mLiYqHh4uIg4GFi4uJjoqKjY+SkImHiYaNkI+NkJCQkpSYlJOVmZyeoqCdnZ+bmpeYlpSamJmXlZOWmI+PlJWUmZuWl5uZnJqbmJ2alpygmaWknJyhm5WcpKmkn5qcoaWjqaykpqmvp6imq7Ozu72/vbK+vK6opbyyrIC4vru4u8XFuraxtrCrt7jBycHD08q/x8vMyLmxqqajnZWSj4+KkaKqqp6QjouMj5GIj4+NjI+SjoqMjIyUkpieo6iwqq+xtLS4tLaztbe8xcnJyMK8tK2knpmWkpGWl5iZnJuXlpaZoZ2Xm6OlpqSoq62vsbGys7S0tLa1tba3uIC4t7i3uLq6ubi4urm6uru7ur2/v77AwL+/wMHAwsLFxsXGxLvAvrq7t7e+wLm9wsO/ubi+vL+/v8DAv769vb28vb3Avb6+v728uraoq7S4saWdpau0t7W0tbSzr7OysbCuraqppaSvrq6trKaepJycpqKhqaOOkZycnJ2rp5yhnzqZnaKaoaelp6CIh5ObnpyWmJKMnaWkpKWopaWNkJico6KjnpyipKWhoaWmp6eopaalpqWnpaampqSlhKchqampqKmpqKmpqquqrK6urq+wsLGzs7S1s7W1tLS1tLi2hbcJtri3ubi5trOyXaKgn56dnp+doJ6enZucm5ycm5ybm5yenpycnZ2bmZqamZeYlpWVlZSTk5OUlZSSkJSQg5COhY6QlJWVlpiampuamJqbnJybmZyam5yZmZuZmpuZm5iZmpeYmpeXl4SVKpOTlZSVlZOTlJCSkpKTkIaFjI2SlZOPiICBf3h7jo+OkY6RkJCQj5KRkYaQZY6NjY2Oj42Ojo+Ni4yOj46Pjo2Ni42Mi4yOj46NjY2MjI6MjIuLiomMi46Oi46OjY2OjIuMjIyOjYuLjImKioqLjIyMjo2LioqKjI2NjY6NjY6NjoyMjYyLjIyNi4uLjIyNjI2NhIwfjoyLiomMjIuNjYyNjIyMjY6OjYyLjYyMjo2LjIqLi4SMFI2Ojo2Njo2OjY2Ojo2MjYuLjJCQhZEUi46PkZGRk5STk5OUlJOTlJSSkZCEkQeSkpORkZKShJGEkoKUhJMCfpCFkwOUkpKEkYSQFo+Oj5GRkJKRkpGRkZKSkZKRk5OTkZOFkoCTk5KTkpKSkZKSk5KSj5CQj5CRkJGMjYqQj46IiIaJhYWKjoqUk5udm5mYlpWVkoyEgI2Rla60qrPA1+3u5O3l0d23q8DEl5uonYDu9PTv7H9+gIB+fnx8fn6Bg4WGhoKEhYODhIWGh4eIh4SGiomGiIqGio2LioaChIOAgYKAgoCDg4OBgIGDg4SGhIOCg4WDgYCDiomJi4mJioyOioaGh4eNj46OkpKSkJKXk4+Ul5iam5mXmZqZmJaYl5abmJiWlZOVlpCQk5WTlpaRk5eUlpOTlJeTjZaak5yal5OXlI6Vmp2bl5KWmZ2an6KZmp6knp2coKepqautqqayrKKfmXqspJ6pr6msq6+vp6SipKKepKWssKyrtaymrLGyraSfmZeWjoyIiIqGj6GrqJ+Oi4mLjZCGjI2KiI6PjYmNi42VlJqip6y0sLa6wbvBuru1trW2vL68ure0samim5eTkIuPkJGSlJOPjYuRl5KOjpWYmpianJ2foaChoYSgFKGhoaChoaGgn6ChoKChoKGgoaGehKCAoaGgoaOioqGgoqOkpaamoJqfnZiYmJmfoZyhpaOgnJqioKSko6KhoaGjo6Kho6Kio6SjoqCgn52QkpmcmZCIj5KanJ2cn52dnZ6ampuZl5WVkpKdm5yampWOlYmIj5KMj5J/gYyJioyXlIuNjISGjoaNkY6WjXV2g4iMiIWIgnpai5KTk5SUkY57goiMkZCRkJCSl5aUlZaXmZmYlpmYmZaYlpaVlpWWmJaWl5aWl5eXlpaWl5eYmJiZmJeYmpqbnZ6cnJudnpyam52gn56eoKCgn5+dn6Gfnp6f/3//f/9/3n+Ffv9//3//f/9/pH8CAgQAgL7BwbzAxcXAvsG+sbCytra+tKmgs8XBw8TGw8LEwsbJzMW5s6DDxMfHycGypJePmcfT08rOzczHucLByNHU09LV0NHLzc7T1M+6x9nZ19TT0tDRzcvLytDPzMXGxsTExMbDxMbFxcXDw8XGxsbFwr+soZKOhv6IguLs+uyQqa+yBLS0tbSEtU6zsbGxs7GxsK2tra6usK+sq6qrrKyqqKmop6ampqelpaSlpaWkpKajpaampaSjpKWkpaWlpqampKWmpqenp6moqKemp6epqqeoqaqpp6iEp1emp6eopqaopqmqq6qoqqyrq6usrKuoqqemqKepqKeopaakpKSlpqOioqKjo6OioqGhoaChoKGgoJ+gn6Cgn52dnJ2cnp2dnp6en56dnp+enZ2dnJucnJ2EngGchJ0/np6goJ+goqKipKOjo6WlpaSmp6inqamrra2tq6usra6vrqyuraupqqurrKurqaipqKmoqKmnpqenpqWko6SjhKIMo6Kko6KjpKSinqKihKEboKCgoaGfoKCfnp6goaGen6CfoKCgnp6foKCihKCAn6CemZickIyPio6OkZeSmJyanaCjo6KdmpmRhYHx74mRoJ6yuLu7x+XmzcTasLCgkoKCgYmQjIaEhYGIgHyAfYB9f3l4d3Tn5ed2dXZ7foKEjpiOjoqJhIeGh4R6eXPhdnZ2dXJ1dnhydnVw1N9yc3Rycd11en6AgYSRiYJ/hIuAi46TjYyVmpKSjJKWkIWFjpKLhZKUlpmfnZyTko+Rl56emY2dm5OPio+MhoiJiYuMioWOj42Jh46RkZecmZiUmJiflYuWlJaemZeZnpelnJaanqGimqagqqmjrKu2vLuusaywr7S3w8fAx7rH0cu6srW+vbytucPHvcGzu7q9s6RtoJyVlJCMioqGgX9/gZCRo56NkoyNkZaanqimrKmstbG1q6K0sKurraWnqKiss7a6vb26urSqqKajn5uYmZqXmJmamJyfoqSnpqampKeoqqymq6+vr7O1t7e5uru8vL2/v769vr2/w8XGxsXGx4XDRsfHx8jIy83PzM3Nz9HR0dTW1tnX1M/KzM7N0MvU1tPS09XV09LMxMnMy8vHxsfHx8jIysfFx8jJyMfIx8fHyMjIxMbExMWGw4DAv8C9u7m4vLK5uba3tra4ubq4t7S0sJmaqqKZlZynq6y2trCtq6usrLKysrmym5iblZmjrLKysKqrr6mlr7Gon5SWo5mbn6inqauim5uZn5uiq62urq6vsLGtp6Wpr66wra2ur62qrauvsbGysrO1tre4t7m7ury8vr++vr+/whbCxsPDv7uzuMO6srm9urm7wsbJqKe3gLK0sq6ytLWyrq+uo6GipqatpZ2TorOysrKzs7S0srS1tbGppZOwsbOztauglImCiq+9u7W5ureyrLGytr2+v72+vL64uLq9vrums8LAwb6+vby9ubi3tLi3t7O0tLKwsbKwsbKysbGxsLGwsbGvr6ybk4J9d+eAeczc7N6KnqOlCKempqWnpqOkhaMWoqKhoaCgn5+goaGfnp6eoJ6en56enoWdBpydm52dnoadA5ucnIedO5ybm52dnZ6dnp6dnp6enZ2enZ6enZ+fnpyenZ2dnJ2enZucnJ6foKCcnp+foKCfoJ+enpubnZudnJychJuFmgqZmZqZmpmZmpqahpkKmJeXmZqYl5iYmoSZQ5qampmYmZmbmpmZmZiXmJmZm5ubnJqamZmZmpqcnJ2cnZybnZ6dnp6enZydnqCfn5+goaGioqGhoqGioqOioaKgoKCEoYKgh58Onp6dnZ2cm5qbmpmampqEmwacnJubmZuEnASbmpuaiJuCmoabgJqZmZqbmpucmpybnJybmJaSlY2KjYiMjY+Vkpeal5ycnZ6dm5qakYiB9fONlKalt8HAwc/l59PO37SzppuIiYiRl5SQj4+NkoqIjIiLh4qFhIKA/v3+gIKDh4qPj5WfmJaTlY+RkZGQioaC/4SDg4OCg4OEgISDgPj+g4SEgoL+gISIi42PkZiVjoyQlZWYm5WXnqCampmbnJmTkpaZlpKam52fpaSloJ+dn6Cmp6Odp6OgnZudmpiZmZqcm5uVn6CemZmfoaCmqqimpKiqr6Wfp6enr6qmqayotaumq6yusKq0sLe2sbu6xMnJv8G3vLzDxMvRztPGz9bRx77EysjFFbjFycvHzL/FyMa5r66qoqKdmpaYlISMS5mcrKmXnJeYm6Cipa6xs7Cyure6sqm3t7O2tq2trrK1ucDEyMfFxb20r6uppaCcnZuamJmZl5qdoKGioaGen6GjpqWip6ipq6ytr4awgK+xsrOys7G0tbW2t7i3uLe3tra2t7i6urq7vL28vLy9v7+/wcTExsbCvbi2ubq/uMDDw8LCw8LCw8C5vL68vbu5urq6u7q7u7u8vby9vbu7ubm5urq1t7i5ubi3t7a1uLW0s7Kwr66wqa+yrrCurrCvsa6sp6mmjpOemJKOkpqhdqOpq6ijn6Gjoaaop6qkkpCUjY6ZoaempaGhpaGdpaeflo2SlpGRlp+fnJ+alJSRlpacpaWnqKinpqWloqCjp6anqKimqKakpqSnp6anqKmpqqusq62trq+wsbGxsrGxsbK0s7SyrKastKumrbGsq6uyt7mcoK1PnZ+dm52cnpuXmJeRjY2MjpWQiX6LmpeZmJubmpuampialpCPfZaUlZSWj4d9dGxvkJibmJiZmZaRlZKWmpuam5yYmpiZmpqamYqRnJubmYSYfZeXl5WWlpaTlJWTkpOTkpOVlZOUlZOUkpSUlJKQg3xrY2C9bWisvs69eoyOj5GRkY+QkI+Qjo2PjpCPj46Pjo+OjY2PkI+OjY2NjI+Pjo6MjY6OjY6Mjo6OjY6Njo2Mjo2NjIyMi4qLjI6NjoyLjIyNjo2NjYuMioyLjIyKhIxUjY2MjIyLjIqKjIuLiouMjo+Oi4uNjYyLjo6NjI2Li4uKi4yMjYqLioyKioyLi4qKiouMi4yNjY6NjIyLjI2LjIqKi4uKiomKiYuNjIyMjo6NjI2PhI4tjY2MjI+RkZGQj4+OkI+Qj4+PkZKUlJOTkpOUk5OSj5CRkZCRkpGQkJGRkJGRhJIjlJOTkZGSkZKSkZOSkpOSkZGSkpGRkZCQkI+RkZCPjpCRkZGFkgmTj5OTkpGSk5OFkguTk5OSkpKTk5GSkIaRgJKSkpSUlJOUlJKQjYyQhoSJhYqKi5GMkZSUlJeZnJybnJyUiob9/ZKRq67Dzs7Q1evx3tLcr62gl4aIhZKWlI6KjYyOh4WJhIiFhIODgX77+vh9fn+CgoeGi5KOiomKhYyJiouHhYL9gYKEgoGCgYSBg4GA+/yDg4OCgf6Eh4qNDo2PkY6Lh4qOjZGSkJKThJURk5STkIyOkY+NkpSWl5qcnJuEnICfoKGeoZ2bnJucmpeYmpmZmZeVmp2bmJecnp6foZ6enaKfpJ+Yn52do6GgoqSfp6KdoaKjpaGnoqWmpqqnsLS3rK2lrKmvr7GysrOqrrOxrKaprKuso6uqqq6voKWorKCcm5WSk5GQj5SQiomKipuerquXm5aanaClp7GwtbO1wCm9vrezwsG8vbmurq6ws7e3ury6uLm3rquppqCcm5mYlJKUlJCTlZeYmISWKZiam5qWm5ubnp+foJ+foJ+fn52enp+eoJ2fn6Cgn5+enZ6enp2doJ+fhJ6Gn0mho6OkpaWjoZ2YlZiZnZihpKSkpaWlo6OinZ+ioaGioqGfoKCfoaGioqKhoaGenp2enZ2fmpydnZycnZyenZyenZyamZiYmJaZhJyAm5ucnJmZlJSSgIOLiYN+f4SMj5WWlJGNjpGPk5OSlo5/gIN6fIiOkpSUkJCQjoqRk4yGf4SBf4OIj42Ji4yGhoSIi4+Ul5iYl5iYmJeTkpOWlZWVmJeWlJSUkpWXlZaXl5eUlZaXmZiZmpucm5ucm5mbmp6cnZ2Zk5Wdl5OZm5gIlpieoZ+Ljpj1fwN+f3+Efv9//3/Pf4J+qH+DfpV/AX6Mf4J+hX8Bfv9//3//f+N/AgIEAIC4u7C0sb/EwMHDw8K5xMbBta2pucLCw8TGyMfCs62wusG/ubzAxcrMy83Q0s/X09HNzMDLzL/Gx9HR0M/R09LV0tHS0dHV1NDP0NDP0c/PysfLys/LysjLysbGw8XGw8LCxMTGw8HBwL/ErZWIh42Ijp+wsJ+XlZiboaaorrG1t3i1tba2tLCwr7OwsrCzsa6trKyrrK2qqqupqqqoqailp6alpqOlpaOjoaCioaSkpKKjoqGioaOkoqKjo6Khn56fpKSlpKakpKampaanp6qpp6imp6elpaenp6ampqWmp6eoqqmqqaioqquoqqepq6iop6anqaqnp6iFpRajo6OloaGjoaOioKCgnp6hoKGgnp6fhJ5Gn56dnJ2dnp2bn56dnZybnJ6enp2dnZyam5udnp+enZ2cnZ6dnZ2enp+goqKjpKOipaWnpqOlqKmpq6ytrqyrq6usra2trISrHqqqq6mqq6ysqqmpqaioqKenp6alpaSlpaSjpKKhoYSiCqGhoqCgoaKioaGGoAefoKCgn6CghKEDoKChhKCEoYCfoJudn6CfoKGgoKGhn5yXj4eKiYqMj5Kcoqamp6OjpJ6gn52YlJKJg3x6e358i5eRkZ6kraWipZiOh4R+gaCjtMq0qa+qraGNhImMgHuEdYF+dnx8e4WGhoB9dHd7cm90cHBxcXFveXl3dnV3e3p1dHt2c3x/dnN0eH16gXh0cIB4fH15foeIjJSKiI2RiYWJjY6GiYCDg4B7fomHgYOFipCTm5qcmZyempydmpqanKSinqSrraajopuZnqGkpZ2YnaKjmp+WlaCal5iWnp2amo6GkZ+VmJyal5yelJCTpKSUoKeZnJecl5aMhpKkpqesmZKan6Wsq6ewtrSjoKGcpoCfoJ+amJOUl5yipJWQq6OipK+9vbe4ycfI0LqjmJePk5Kam6CkpKmssLC0t7u8uL+9ubCqpJ6YmZqZmJiZnJqYm5qcm6ChoqKoqKqqqq2ur66vsbCxsrK0t7a3uLm6vL28vb7AwcC/wMHCwsPCwsLDxMLCwsXIx8bHycrKzc/OzoDPz8/R1NbYzczSzM7QzsvN0NHT1NXT0M7MzMnKyMrHxMLExcbHx8jHxcPExMXFxMXExsXGxsnGxcTCxMTDwsPCwcK/vr3Bs7Ozpreytrm6uru6ubu5tLCyuLa2tLO0tLW2t7e3ta6qtrO0s6qmqaysrrKysrO2tLOysbCurrCtr1eurKafo6KssK2prKmuqZ+hqK6wr62tqqGdo6+jpaGlrq2trKmej4uHj5+bl5yys7GxtbW2t7e4urq0qaenyb28trvAvrK0ubaxvri2sq2vt72+vr6wsrWAq66lp6avtLKzsrSwqLG0sKefnKuxsrOztLa1saSgpKyvraitrrC0uLi3uLm5vby6ubmotriutLS9vb28u769vr28vL29wL67ubq9ubm5ure2t7i6trWzt7Wys7CysrCxr7GxsbCura6ssJ2KfX2AfYWUo6CUjo2QlJibn6GipKYmpaWmpaWioqCjoqSioqGhoKGgn6Cenp+fn56dnp6bm5ycm52bm5uEmg2bmp2dnJybm5qbm5uchZsLmpeWl5ubnZydnJuGnA+dnp6dnJ2dnJycnp2cnJuEnCKdnp2dnJydnp+en56enp2cnJubm52cnZyampucmpiYmJmXhZksmJiYl5mamZmZmJmZmJiXmZmYl5iZmJiZl5mZl5eYl5eYmJeYmZmYl5eXmJiEmhyZmZqbm5uampubnJucnZ2fnp6en5yen5+goaKih6EPoKGhoKGgn5+goJ+goaCghJ+CnoadCJycnJubm5qZhJoEm5ubmoSbCZydnJubm5qbmoWbDZqam5ubmpqam5ybnJyGm4CcmJqcnZycnZybnJualpKMhIeFhouMjpSZm5qbm5ucmZ2cm5iUkYuFf35/f32Pm5WVo6iyq6apn5ePjIaIo6W3yrast7O0rJiRlZ6RipSDkI2EiIiGjpGQjImChYiDgYSAgIKBgYGIiIWDg4aJiIODiIWEi4yEgoOGioiOhoR/iICLjIiOkpWYm5WUlZqUkZeXmJKTjJGQjYiKk5CLkZKVnJyhoKOgpKWkp6akpaWlq6yorLK1sq+uqKarrK6vqqessK+mq6Smr6inqqawq6mpnZWfrKSmq6mnrK2loaSysaSvt6impKynpZqRnrCys7WmnqWrsLa2tb2/u6ytqqmwrkyrraeloqKnqa6unpixqqirtsDAvb7OztDVv6aampKXlJ2ipqirsbS2uLq9wsS+xcjBuK2oopubmpiYmpucmZqcm5yanZ6eoKWkpqanhKgHqamqrKurrYSuDq+vsLGys7K0tLO0tLa1hLQ1tbW1tLW2uLi4urm5ubu7u72+vr6/wMDCxb27vru8vb2+wMHBxMPDwsHAvb6+u7q6uLa4uLmEugq7urm5ubi4t7m3hbkXurm6uLe2t7a1tLK0tLKvsqmpqJ+uqKyEsAqvr66up6Slq6yrhKl4q6usra+rop+qp6aloZyfoKGkp6inp6ipqaeop6WlpaSkpKGclpaZoKKhoKGho5+XmqKmp6amp6KZlp2kmp6anqWmpqail4iEgomZk5GUpqinqKqpqqqsra6uqZ+cn7eurqaqr62ipamopLCtqaekpq60tLKxo6aogJaakpWSlpycnZybmI6WmpiRjImVmZmZmJmcnJmPjI6UlpSQkZSWl5mWlpiXlZmWl5aYiJWXkJaUm5qZmZucmZuampqYnJybmpmYmZmZl5iVlZaVmZaWkpWVlJSRlZWRkpOUkpKTk5KSkpOFdGZobGlwf4uKgX1+gIKHi4yPj5GRRJCQkJGPjI6Oj42Pj5COj42NjYuNi4qNjY2MjI2NioqMjIyNjIuLi4yMjYuKjIyLjIyMi4yMjYyKi4qKi4qGhYiLiouKhIwliomJiYqMiomLiYqLi4uKioyLi4qIiomJi4yJi4yMjI2NjIyLioWLN4yLjIqLi4qLi4uKiImJioqJiYuKiouLjIqMjYuNjYyMi4uKioyLjIuLi4qKi4mKi4uMi4qMjIyFjRiMjIuNjZCOjY+PkI+QkJCPjo+RkpOSkZKEkz6RjZCRkpKRkpCQj5CRkJCRkJKSkpOTkpGRkJCSkpSTk5KSkpGSkY+QkJGQj4+RkI+Pjo+QkI6PkJGRj5KRkoWRFZKSk5KSkpGUlJOSkpSTk5OSk5KSkYSSgJOTlJOSjpCTlJWVlJOSk5OSj4yHgIWEhYeIiYuQkI6Pjo6UlJeYl5WWlo6KgoKEhIaZoZqbqLG5sKqupZyWkomKnp6vyrWourW7sZuSmaSUkJiHlpOCg4KAg4SFhYN/gYOAf4J/gYKBg4KHiISEgYGFhoGBhoKEiYaCg4GDhYWKgIWEgYWLjIWLj5KSk5CQjJGRkJOPjoqMiIqKiYOGi4qGjpGRkpOVk5eZm5ycnJuam5ucnJ+foKGloqKin56foaOin56go6GcnJqboJ+dn5uhnpuclJKXoJyan5+enqCamJilopmhppqZl5uZlo+Gkp6cnJyVjpKVmZ6dn6ekoZiZZ5iVnZybm5iWlJicoqytnpi1rKqwucHDwcDQ09bewqucnJOVlJ2foaanqq6wsbS2ubmyu7u3rqWim5aXlpSTk5SUkZKUj5OSlZSUlJybm5qam5ycnJ2cnJ2cnJ2dnZ6cnZ2enp6gn5+EoDifoJ+enZ6enpybmpucnpydnZ6dnZ+fnp+en6Cho6KhnJufnJyfoKChoqSlpqajo6KhoJ+hoJ+fnoSfgKCgoqGgoJ+fnp2cnJucnZ2dnJybnpubm5qbm5qam5malpqTkpONmZKVmZmYlpWVl5aSjo+Uk5SVlJWTkpSWl5eSj4qSkpCRj4qMjYuPk5SVlZSTlJWUkpGRkpGSko+JhYaHjpKQjZGQkIyIjJOWlpWYmJSLh46Vjo2LjpSUlpWRM4l6dXN6i4aFhZOVlJSWk5WYmJmampePjI2amJiRlZmXj46RkYyXl5OTk5Wanp6cm5KUkv9//3//f/9//3//f/9//3+IfwICBACAvMXEwsHExcPGx8TGtcPBwsDBwMPCxMXIv7nAwcvLxMLCxsrMyMvKzM3Lybq+y8vMzs/Hu9DNztHS09DQ0NHO0M7R09TT0tDRz8/S0M3JzM3P0c/Q0c7OzcnEwcHCw8HExMjFw8DAvb6/wbOus7nAwLu5ubarsrGysbK0srKztLYtt7i1t7SysLCxs7GwrK6ssa2srKytq6iqp6enpaiopKWlo6ShoaOin56dnZ6fhaA9n5+en6KhoKChoKGfnqChoqKhoJ+fo6SmpainpqakpaKipKWlpqWko6Slpqalp6epqqmnp6moqKmpqKeoqYSoH6elp6eopaWkpaCjoqSjoaOjoqKgoKCfnp+doJ+enZ6En2Kdn52bnZ2cnaCfoJ+dm5qdnZucnJ2bmpycmZqcnZyenp2cnJ2dnJyenp+hoqGioaKjpKSlp6WnqKioqaiprKypq6qrq6qsrKurrKupqquqq62rra2sraqqqaenqKinp6alp4SkB6Ojo6GioaKEoSegoaKhoqKioaGgn56en56foKCgoaCioaGgn56fn6GhoKGhoKGioaKFoYCioqOjoqCem5aYl5eYl5SRjYmMjZKUl5eioaenqaGkpaGhn5aWlJOMhYKCh4uPjpSRko+MkI6Ii4Whr6q2pKigkq6FgpCXjYR1e4B9eHh6fHR2fXnebnV8eYN+dGrL1dDXc3l6eHXW3NZvcHR2btvY3Nx7e313fYSBeHd8hIKOjICIjIKCgoN/fIGEgHt/e3x+fYCBgYR8foaIhYF/goKFjJKVnpeUn6abmJKPi5OfoqSam5KVlpWanJiVnJeHjp6SkIyOj4qRioyMi4mSkIWSkpSOhIuOi4eUlpKOlpWRmJ+XnJaVl6CloZ2bnaOqr6yno6GyuMO4sK6xqqGtsKukqDuzxce/v8C7t7WrmZiTk5SYm6Cio6ersLGzs7i5ub67vL27taykoJ6bmJubnZyQm5mYmZ6doJ6imaGpqoSsgK2urq+urqytrrGzs7S0tLW2ubq6uby9vL68vb6+v7/AwMHCwsHAwMLDw8LDw8TFxsXJycnMzM3Mzc/Q0tHRz9HRz9XT0NHU1NTSz8/LycjHx8fJysrIxsXGxcbGxsPExMbFxcfGxcXDxcTEwsXExMTGx8XDxcTBv768vL26r6utgLi5tq6wsre0s7axuLm4urW4u7m4u7u5ubixrLK3tLOkoq60tbW2trSztbSzt7OztLW1s7Kzr62rqaeooJ2ZjqOihISGrK6sraytrKupqaaqqqysra2tr7CurKerqqOhoKWpra+vr7KxsrOkoJueprSxtre2u729v7/Avr/Awb6/DsHAwsTAwMHAv8DDvru8gK22tLKzs7SztLW0sqawrrKysrGxsrW2tq6lrK62trKusLO1uLW3t7a4t7Wmqra2tLi5sai7urm9vb68u7y+ury6vL2+v766vLy+vbu3t7m6u7u7ubu2uLe0sq+wsLKvsrC0sbCsraqtrK2kn6OoraysqKinn6WkpKOlpaWkpqakJ6alpqWkpKOjo6Sko6GjoKOgoJ+hnp6dnZycnZucm5ycm5qbmpmamYWYAZeEmUybmpqZl5qamZmampqZmJmZmpmamZqYmpucnJybnJyam5uZm5ybm5ucm5ubnZybnZydnZycnJ2cnp2dnp2dnp6dm5ubnJ2cnJuam5yXhJkImJmYmZmXmJiElhiYmJiXmJiZmJeXmZmXmJiXl5iXl5iYlpWKlwOWlpiEmoSZhpoLm5ubnJycnZ2dnp6FnxugoJ+hoaChn6GhoKGhoKCfoJ+foJ+foaCioaCFnwSenp2dhZwEm5qbmYWaCpuampubm5qampyGmwicmpybmpqZmoabBJybm5yEmwScm5yci52Am5qYlZKUkZOUko+NiYaIiY2OkI6ZmJ6dn5uenpycmpaZlpSNh4WHi46SkZeWl5KPlpKOi4Sgs628rK+olbKMipmel5CEiY2JiIaJi4WIj4j9gIeOi5KOhn3x+PL6goaGhoL3/feAgYOGgPz6/f+KioqGjZGOiYaJkI6WlpSZj5CAkI6NjJCTkImOjY6OjY6NjZCJi5GWk42NkZCSmJqcpJ6dpqqioZ6amJ6orK2ipJ+ip6OjpaOipaSYnKqioJ+goJidmJ6bmpign5KenqKck5iamJejpqCcoqKepKmmqqSio6yxq6moqq+4vbqysa3AwszFvLm7ta+1uLKsrbbKyMFFwsbAvritm5yVlZaZnaGkqKywsrS3uby+vsPCwsPDu7CloZ2Yl5qbm5uTmZeUm52cnpyflp6kpqemp6ioqampqKilqKiphascrayurq+wsbGwsrGysrKzs7O0tLW1tLOys7S3toW3crW4ubq8vLy9vr/AwL/AwMC/wMLAv8HDxMPDwcC8ury6ur27vLy7urm4uLm5ube3t7i5uLq4tri1tra2tbe2t7i4uLe4ube2tLOzsbSxpaGirq2po6Ooq6mprKaqrK2trKytrq6ura2tq6OfqKupp5qXooSphKoaqaipqKeoq6moqKalpKGenJ6WlY+BmZZ7e4GFpEWlpaSjoaChoaKlpaSlp6enpaGkoZyamJ+ipaSlpqioqKmblpWYoamnq6mrsK6xsrGwsrKzsbCxsrG3t7KxtbW0tLGtrawElZ2cnISbPJ2bmpqKmpiamZmZnJqcm5mQjJOTmJmVlZaYmJiZmJeYl5SUiYyVmZaXmpOLmJmZmpqbmZuamZiamJqam4WZA5iamYSXMJmZlpeZl5iXlpSTk5KUkpOTlZOSkZKQkpGPh4eJj5KSkJGPj4yRkZCOj5GRkJCRjYSQgI+PjY+Ojo6Nj4+Njo6OjY6MjIuLjYyMi46MjIyKiouKi4qNjYyNi4mIi4qKiouKi4qJi4yKiYuJioqJioiKiYmJi4mLioqJi4mJiomJiIiKi4mKioyLi4mLjIqLiouLjIuLjIuMjIyNi4uMjoyKjI2Ki4qMjIyLjIiKiouKiomKFIqKiYqJiomJiYuKi4yLioqLi4uKhIsEiIiKioSLBIqJi4qHi0aKjI2NjY6OjY6NjY6Ojo+Ojo+RkZCPj4+QkpGRkJCRkZKSkZGRkJCRjpCQj5CQkZGSkZGTk5GRkpKRkpOUlJOSkpKRkZGPhJEEkI6OjoSQAY+GkA2RkZCRkZGSkpGRkZCQhJEEkpOUlIaSBZGSk5OThJSAk5OUlZWTlJSVlpSUkpGOi42LjY2MiomFgoWGiYeKhY2JjpCSjZaVk5mZlp+enZiPjZCTl52bo6GinZSamZOQh6i6tsiztKaSrYuHmp+XloWOkoyMi46Rh4yVjv+CipSQmJOLgfj69v6Bg4KCgfb/+oCCgoeE/v39/YeGhoSHiomAhoKFi4mOj4+WioqNi42Lj5KOho2NjYuJi4mKioGGjY+OiIaKi4uPkI+TjpKWmZWVlJKPlJiam5iZlZidm5ebmZiZmo6Qn5mWlZaYj5SRlpKSkpWViZaWlpGLjpGPiZOUk5GVko+Vm5mak5OTmp6Zl5aanaOmpKCemaamrqempqdBo6CpqaajqbPKycbGzMjCu7GdmZKSk5aYnZ2fpKmqq62wsrOztrW2trWxqKCcmJOSk5KTlI6Tko+SlZOUj5SOj5iEmh+cnpybnJubmZucm5ycm5uampqbnZ2cnZ2enp2dnZ6fhJ4UnZ2dnJycnZydnZqcnZ2bnp2enZ6EnyGgoqGgnp+foKKioqWlo6SioaKgoKCen5+goaGgn5+fnp2Fnk2fnp2enZqam52cnJyampubm5yampuamZqamZqZmpKMjJaTkYuMj5KRkZKOkpSVl5SVlpaVl5eWlpWNi5OWlJKJhY2VlZSTkpSTlpaTlYSUFpWUk5OTkI6KiYqGhYBvh4FqanOSlZWGlCmTkZGSkpOUk5SVlJaUkZSRjo2LjY6Rk5STlZaXl4uFhoqOlpSVlpeYl4SaFJuanJycnZ6boJ2dm56fnp2alJST/3//f/9//3+LfwF+iH+EfoV/g36Ff4R+/3//f/9/3n8CAgQAgMHDw8XHx8nGyMnGyLrDx8fKx8nMyMrFxsPAwr+9v8HEx8nHzMrJxMrLysvOzM7HzNDQztDP0dfOzs7P0NTP0NDR0tPU0dDNysLGzdPV29fW09TP1NbNycfIxsLFzMnKxMnIxcLDwcK9ubKimp2jp5+hpKairbW6s7a3tra2t7W1f7W3s7awr7CysbCwrq2ura6uq66qq6moqKinp6ampaSloqGgoKChoqGhoZ+doaKgoKCenpycnqChn5+en56bnZ6goJ6dnqChoaGkoqShoqSipKSjo6SmpKSlpKOko6Snp6anqamnpqelp6imp6anqKiop6elpKWkpKSio6Kko6OFoguhoaKhoZ+en56en4WcMZ2dm5yem5ydnpydnZydmpqanJucm5ucm5ucnJucnJuanJ2dm5ucnZ6fnp6doaCgoqGEpA6lpqeoqKipqqmqq6usrISrIaqoqKysqqmqqqytraqrrKysq6inpaaopqanpqWnpaWkpYWjgKKhoqKgn56goaCgoKKhoqCgoaGgnp+hoJ+gn6Cgn5+fnp6foKCeoKCfoqGhj6CgoqGioqKjpaOjnJ2hoaKgnJqXk5eYko+Ni4yPlZqhp6impqSlqqmmqKmrrKqsqKKfm5uYlZKRkI6SlJWRkIqNjI6LjIqGf4KLkY6Kn5iUk5GAgHyEd5mboaCNi4GBl394eXRubHFtcHHX2NBxb9RxbXZ8d3V2eHyAgH2DiIGEhI6Jh356eoB/fnd1f399fH15eHZ3dnV4eHZ4d3l5fX6ChoiKi4mTlpGKhY+OkJSdkoiEjpCPjYmHgX16fn17e4B7g39/eHl5enN2eHl3e4WMi4+Qa42KkJOSioyWopSRmqOlqKm0r7ayprG0sLm+vcTI0M/Qzbm1sqWruL26urzAvLOtoJaOiIiIio6Sl5qdn6SorK+1t7e3vMG/u727vbmyr66soqKgnp+dnpqOlpWcnZqhnKGnp6usqqqprayrhKyAq6usra2sra+vsLCws7S0tbS2t7i5ubm6ury+vLy+vr29v7++v769v8C+vsDAvb/AxMfGx8fHxsnHyczP0NHQ0dHR1NXV09HR0NDPzMnHxMPEx8jIy8rJyMjJycTFxsXGxcXFxMTExcTDw8bFxsfJycfIx8XGw8TFxMPAur/AvrOAt7Clpqaltba3t7m0sri2tri4uby8u7q3t7a1tra0trSus7a1tbStr7a2s7a3tre3tLWzs7K3t7Ouq6yprKyZhZSYm4qHh6GrrK2ura2qq6qrra2rq6ytraqtrK2ppKarra6trq2sr7CwsrKst7i3t7i7uLq6ury9vry8u7i8u7oQuru7ury9wcC/wb+/wMDBwxuytbO1t7a3tbW1tLWmrrKztLW0uLW3sbOzsbCErlqzs7WztbS2s7W2tbe5tbiztru9u7q7vL+7uby8vb68uru9vLy/vr26t7O3ub+/xcPCv8C7vr+6uLSysLCyuLa2sbSzsa+wrq6rp6KWjZOYnJeWlZeUn6Ooo6WEpimnp6ajpKOloqGho6Sjo6Gho6GgoKChn5+enZycnJqcmpuanJqbmpmZmISZNJiZm5qZmJmYmJeYmJiZmJiYl5aVl5iYl5aWl5iZmpqbmZmampqZmZqamZucmpybm5ucm5uEnC2bmpubmpuenJqdnJ2dnJybm5ybnJqampmZmZqYmZqampiZmpeYmJiXlpiYl5iIlxSWmJeYmJeWl5eWmJeWlZeWl5WWl4aWCZiYl5mZmJiZmIeZg5uEnQWen6Cfn4egBqGgoKGhoYSgGJ+goKCfn5+goJ+goaCgn5+fnp6enJ6enoSdgpyEmwOampuEmgaZmpqam5mEmw2cnJubmpmampqbmpqahJuHmoCbnJucipubnZycnZ6enp2elpicnJqZl5WTjpKTkY2Jh4eKj5OXmpycnZ6fo6KgpKWnqairp6GdmpmVlI+Ojo6TlpWTkI+OjpORkYqJhIeQkI6Mo5+amZiJiJGBpKawqpmZjo6njoaJhn99hn6AgPf68oJ/9YOAh4mHhoeHiY+Ni4CPko2QjpaSkYmFiIyKjYeHjo6NiouKiYiJiIeJiYiKioyNkJGUl5mZmJedn5qVkZeXnaCnoJeTmp2cl5aYko2Jjo2JjpKOlY+Ri4qIiYaHiYqJi5acnJ2fmpudoaGYm6avpJ+msLG1tL66wMG0vb+6xMnEy9DW1dXUw765rLXCw0S+vsHGwbmxo5qQioyLjZGUmZ2foaaprLG1ubu+vr+/vcHDwb63sK2rpaKdmpuampeNlJOZmpebmZ2go6WmpqimqKeop4WmI6enp6aoqaipqamrqqmrqqusrq6ur6+wsLGxsbKysrOzs7KyhbGAr7GwsLO0tba4uLi5uLq6u728vsDAwcLExsTDwsHAwL/Avby7uri5vLu8vb27urq5uLi5uLi6ubm6uri2uLa2t7e2uru6uri6ubm5tri3tra0sLS2sqqspp2cl5qqqqmqrKuorKusra2ur62wrquqqaioq6irqKGnqaipqaKhqKo2p6qpqaurqKenp6anqaemoqKeoqOQf46QiIKCgZmjpKWmo6OhoaGio6KhpKOjpKSko6OinZ6ihaQrpaSmpKemo6usrK2traytrKyura+trq6sr6+ura6trrCwsbSytLOxsrKysQGchJ13m5uanJqZmo6XmpmbmpmbnZ6ZmJiXlpWTlJSYmJeWmZiXlZiWlJWYl5iXl5ibmpmYmp6YmZmYmpyampmZmZiampqYl5SXmpuZn5ydmpiYmJuXl5WUkpKTlpaVkJWTlJKSkZGQjol+dIGGiIOBgoKCio6UjY+QkJCEjwKOj4SOVI+RkI2Oi42OjI2OjY6MjY6Li4uKjIuKi4yMiYqJiYuJi4yNjIuJjIuKiYqKiYiJiIiKiYqKiYeIiYmIh4eHhoiJioqLiIeJi4iIioqJiYuKiYmIiYWKGYiLioqJioqLiYqJiIqLioyMjIuKjIuKiYmFihKJi4yMiYmKiomKiYmKiIqKiYmEihaIioyMi4yLioqKiYuKioqIiImLiYqKhItEiouMjYyKio+Qj42MjI2OjY2Njo+Pjo6QkZCQkpKSkZKRkZOTkpGRkZKRkJGRkZCQkJGRkI+RkJCSkZGRkpOUlJGSkpKFkR+SkpOTkY+QkJCRkZCPkZGQj46QkJGRkJGRkpGRkpOThZKCk4SRD5CRkZGSkpKTk5SVlJOEk4SUgJWUlJaVlY6Rk5KRkI2NjoqMjYqIhIOFhoiJi4yOi46OkZWVlJiao6unqaWdmZaVkpCLi46QlZqYl5eTlpaalpWNi4eGkI+MiKSgn5qajY6ZhK+4wrmfnJGUt5aLjY2DgIuFhob6/PWDgPmFgouJiIqMi4iJh4SJiYaHhouLi4SCEYOEhYmEhY6Li4iKiYmHioqIhIqAiYqOkJGVlpeVk5KUkpCKho2Ql5ebl5COlJOTkY6RjImGiYqGi4+KloyOh4aBhIOBhYeEhY6VlpiYlJKXm5qSlpuhmZSbn6KlpKypsa+iqqekrrWxs7O2s7azqqakmp+lp6Wlp6qoo52TjYeHioiKjY6TlpaYnaCjp6mtra6ysbEhr7Gxsa6ooqOgmpqVk5STlJGHj46RkY6Vj5KXmJmbmpyZhJsCmpmEmgabm5ucnJmEnA2am5qampuanJybm5uciJ5MnZybm5uamZqbmZqamZybm5ycnJ6en5+foaCgo6SjpKSlpKOjpaOioqGfn5+goKGhoaChoJ+goKChnZ+gn6Cen5+dnp2enZybm52dn4WdgJycm5ubnJublpqal5OTkoyIfYWSkpCTlZKTlJKTlZWVl5aWlpeWlJSTlJWTkY6SlJOUk46Ok5WTlpSSlZSTk5SUk5SUlJORkIqOj4BvfH5xdnh2iZOSk5OSkpKVkpSTk5KUk5STkpSTkpGOjpKTk5KTkZGVlJaYlZKWlpeYl5qYFJmYmZmYmZeZmJSZm5qZnJuam5qchJ0Fm5ubmpz/f/9//3//f5t/Bn5+fn9/fv9//3//f+Z/AgIEAIDEycbIyMXGxsXFxcfExcvKx8nJxcfGxMbDw8PFx8LEw8LFycvNzMrQy8bJycTGxcPLzcvRzs3Qz83Nzs/Q0dHS1dHSzc3KxsG+u7u7w8fLzMrIxsTDwMPOz9LMzs7Iw8TAwMHAwcG/uru7t7S1rq2zt7i0trq5s7Cwtbi1tbSytAi1trS0sq6xsoSwRa6usKyqq6urqKekpaSmpaOmpaSjo6KioaKjoJ6fn5+enqChoZ+enZqXmZubmpmampyZm56boaWkoqSmo6GenJ+goqSjpISjT6SkpKOkpKOlpaalp6ioqaempKWlpqanpaako6SlpaalpaSmpqGhoqSjpKOjoqOjoqOioaGgoqCfnp2dnZ6dnZ2cmZqampubnpycm5ucmpqEm2aamZiZmpmbmpmam5mam5ycnJ2cnZ2fn56fnp+goaOkpaamqKipqaipq6utqqqqq6usrKyrqqysrKmqq62tq6qoqaqrq6mop6WmpaWmpqalpqSkpKOkpaSjo6Kio6SjpKKio6KhoaGEoBOhoKGhoKKhoKGhoaCdnp6en5+dhJ+AnZ6eoKKjoaOjoqKjpKOkpKKioaGgnZ+gn56cmpmWl5eWlZGOjIuKjJCXnqGoqamqp6ajoqWnqaqpqaiop6alpKOjpKOinpuZmpaamJaXmJuanJuWmZubmpiip6yelZmVlJ+kmZaZiYSCfHiBhY6MjYh/dHyEhY6Eg4x+iYmNj4iAgX59goWDeXh8gH92c3t8foB8hIiGg4GGhYSBgYODiI6NhoN+gIWHiYmEiZiVk42Qk5KckZGOjIiCgYWBhoSGjIyCfYiPlJGSlI6CgIORj5CSjouKnZSQjpCQnaSdmpunssDHwsS4sq+3trm9y9HKyMW9wbzBvbq/v7zFw8a+wMVAyszIvbrAt66opKGip6yvs7a2vsDDxcjKy8nCvbW0rayoqqampqinqKanpaajpJSSmZOdoJ6jpqerq6ytq6yqqYSrgKqqq6qsra6wsbGwsK6usrOxsbK0tri5urm5urq8vLu9vLu7urq7vL29vL++v8HCwrnBw8THx8bGx8nLzM/R0NDR0c7Ozc3Q0dHR0M/S1NHPzMnIx8bGxcXFxMTDxcTDw8TDwsTFxMPCxMXCxMbHw8PFxMXDxMHDxMXFw8TCv729Eby1s7m4s7a7u7q5tLe4t7i5hLiEtmK3tLa2t764qKatq7G2tLe2trOysrKtl56utra1s7OytLOwrauoqKenko6FipKPlJyfp6qnpqinoqepq6qpp6mpqKmrrLGura+srqmsrqutq7Kur7OzsLKys7m4urm5vbm8voS9FL/AwMHCwMPAusPCw8DCwcLCwcHDBLO4tbWEtDSys7SzsLK0s7O0trS0tbGysrGwsbOvsLCvsbW2uLi0urm1s7Sys7Syubm4u7u6u7u1urm5hLxsvby+ube2s6+trK2wsrW5uLi2tLWzr7G3uLu3ubq1sLOur7Cur6+tqamoqaWkoJ6mpqakpqino6KipaalpaOlpaWko6KhoKGioaOhoaKhoqCfn5+enZ2am5ubnJqbm5uamZmamJmZmZiYmpmYhJkSmJeVk5OUlJWVlJWVlpOWl5OYhpsKmpiXmJiYmZiYmYaaKJubm5qcm5uanJybnJubmpybm5qcm5ybmZmbm5ucm5qbnJuamZqXmJqEmTWYmJmamZmYlpeWlpaVlZWWlpWUlZWVl5eXlpeXlpeWlZaVlZWUlJWUlZSVlZWWlpaXmJiYl4iZG5qam5ubnJ2en5+goKChoaGfoaGhoKCgoaGgoYSgCaGgn6Cgn5+fnoSfhZ6EnQGahJwBnYWchJsEmpycnIebCJqam5uanJubhJoYm5ubmpuampqYmpuam5qamZubnZydnp2dhZ4onZ6dnZyZmpqYlpaVlJOTkpGQjYuHhYaIjZSZmZ+goaKfn52dnZ+io4ShgKCgnZ+fnp6foJuYl5iUl5WUlpqcmZ2emp2hoaGfqrG7rKGko6Cvs6ikppaSjYiHj5KbmpiWjYKLk5CYkpGZjpaVmJyXjoqJjpGNhYSIi4uCgYiLjY+Nk5WVk4+SlZKRkJCSl52blZGNkJWWmZeSlqOfnpmcnp2ln5+dnZmVlJeUGpeYmJqdlJGcoKikoqOelpCUoZ+gop6bmq6lhJ9oqK+sqaiywMvQycvEv7rBv8HEztbQzsvExcHGwb/DwcDGwsjEx8fMz8rDv8S8sKulo6WqrbCyuLvAwcbHys3LysTBtrGrqqSkoqChoqKjpKOhop6gko6WkZqZm6ChoaSlpaamp6enqKeFpoOnhKgUqaqpqqmpqaqrrK2urq+vsLCwsbGEsICvsK+vsK+wr7Gxs7S0tKu1trW3t7e2t7q8vr/BwcLBwsLCwcHAwcDAwL7AwsHAvr27uru7ubi4ube2uLe2t7e3tre4t7W1uLi1ubi5t7i3tbe3uLa3uLe4t7a2tLOxrqmpr62nq7Cvrq2rq62tra6urausq6qqq6upqqurrqqcm1qenaOpp6iop6aipaehi5Kfqaiop6inp6elpKKfoJ+fiod/hIqGjJGan6GgnaCfmp6foaKioKGhoaCjo6akpaWkpqGjpqOkoaekpqiop6ipqqysrKussK2trrGFsBKxs7Sys7Gus7GysrSzs7OxsrMUmp2bm5uamZmamZmal5ecm5mampiEmYSWR5eVlpaVlJaYmJWVmZWUkpWUl5eVmJiYm5qYm5qXmZiYmpqanJyZmpmZlpOUk5OTlJeXl5iYlpaVlpOVmZiXk5eXlZKSkJOUhZISkY+QjY6KhIyPkI2Pj4+NjIyNhY4vj4+MjY2OjY2Pjo2NjY6OjYyMjY+Mi46KioqMjImMi42LioqKiImLiomKjIyNiomEihCIiIeIhYiHh4iIiIWIiIKHhImEioOIhIkBh4WJM4iJiomLi4uKiImIh4eJiomKiYmIioiKiomKioqLi4qKi4yKiYqKiImKi4mLiomJi4mKioSJLYiJiYiHiImKiYqKiomJioqKiYmKiYmLiIiJiImKiYmIiomMiomJjY6Pj46OjYWOB4+OjIyNj5CFkweUk5OTkpKShJEjkJGSkZGQkI+Qj4+QkJGRkJCRkZKTko+QkZCQkZKSkpGRkZKEkQiQkpKRkI+Rj4SRDJKRkpGRkpGSkpKTk4SSCZGSkpKRk5SUkoWRBpKTk5KRkoeUgJWWl5aWlpWUk5CRkY+Oj46NjY2Mi4qIhYODg4WIjY6NkZCRkJCQjIuOj5KUkpGQkZGTlJSVlZWUlZOSkpOPkZGSlpmcmZ6gnaepqq6xucLVwqy2trG/yb+ytZ6XlZCNlJmhnp6glYuUnpijmZagl5uXl52bi4aBh42Hfn+ChoV+gHyGiIiMjJGTkpCLjY+Qj4+OkpWZm5eUjo+TlZaTjpKZlZSRlpWWmpealpaVkpOVkpWVl5uZkZCdo6qloaSgl5GWn5qdnJiXlKmam5iZlZ+lpaCepq+xtrS2sa6psK+vq7O2rq6tqKqnq6ulqaijq6qvpqius7OvrquwqqOin56fTaKkpqqtr7K0tra3urm2rqyppJ6dmpuZmZeXmZqYmJaXlZaLiI6LkpOUl5iZm5qcnZycm5uampycm5uZm52bm5ybm5ybmpmam5qbm5uch50un56dnZycm5qZmpqZmZqamZqbm5uRmpybmpucnZ6foaGjpKOkpqinpKOgoKKiooSjC6SlpKKhoJ+gn56fhJ4Bn4adKp6fnJmanJqcnZ2bnJ2cm5ucm5ybmpubm5qZmZmVkZOYl5KSlZaXlZGTlImVfJaTlpSVlZSWkoeFhoWNlJOUkpGOi42QinZ/ipOTk5aUk5KTkpGQkJCPjnh6cnd6eYCCjI+PkIyRko6RkpKSkZCRkY+PkpSYlZSVk5WQkJOSk5KWlZWWlZSWlZaYmJeWl5uYl5iZm5uampqcnaCen5yanZycm5uanJ2bmpv/f/9//3//f/9//3//f/9/iH8CAgQAKcbGxsXIycbHxsXEyMfGxcbExcXCxMDBw8bBvsPFv8PFxsbIxcfJxsTFhMhOycDL0MnOzszNztDOy9DPzM/SztDJzsnFvrXDx9HW0cq6s7W7u7Sxt73Ex7nBwL7AwcC9vLm6u7m6uLa3uLm8vbi4tLa5u7i8t7G0s7KzhbYKtbOysrCur6+vroWvS62pqaqrp6Wjp6inpqSjo6KhoqOhoaOhoZ+fnKGhoJ+hn56dnZ2ZmZ6fm5ydnZ+goaKlpKWlo6OinpybnaGkpKSjo6KkpKSjo6KjpYSkEKWlpaaoqKelpqajp6anpaeFpjelpKSlpaSjpKKjoaGho6OioKKhoJ+gnp6en56dnJ2enJuZmpqam5qampuampibm5qYmJiXmZqZhJgtmZiXmJmYl5mYmZmcnJ2dnJyenp+en6GhoaSkp6ioqKmoqausrKmqqqqpqquqhayAq5aQqaqrqaqpqamnp6emp6inpKWmpqWlpaSko6Slo6KipKOlpKKhoaKhoaGgoKChoKChoaKioaChoaChoqCfoKCfnp+foaGgoaKhoKGho6OjpKSjpKWlpaOioqGioqCioqKhoqCenpucm5mZmpqXlpOOjIuKjZKZnJ+goaenqKeApaOkpqKioqSko6epqqusrqupp6enpKOioqKgn5+alZWTjo6JiIGEhIB5ent+f4CAg5GUjXXp5uiGkImHg37qeYCBen16f319eXd4f4SGiYKHe3t7f4CAeXx/h4yKh4yWmZSSlJSakpygoqKomJeglZKUkpGXl5SQi5ebnaKfm6CAm5CLlp6bm5mjoqurqqupqaicnKKnpaKsta+xsrG1sbuytL7Jv76/v7e4wLzAwL26sKmusbe6t7uyx8zMyczIx83Mz83JxsrMxsS5r62zubK4usHDxsjMzM7Pz83GvbG1sqyrqq2tq66vr6+trKuqpqigoJ6am4qci4+eoaWpqq2ArKysq6usq6urrKyrq6msrq6tr66wsrGwsbGysbOzsrS0tbW2t7e6t7e3tre5ubi2uLi5u7y8vb6/wsC/w8PDxMbIyczNzs3N0NDPzs/Pzs/Qz8zRztDPzc7NzcvJxcbDw8TDxcLBv76/wcDAwMPCwsPFx8bCxMPCwsHCwsHCxMRBwcPBwcDCwMLCwcK9vsDCvby+u7i5t7i3t7q6tri5t7e0uLq4ubm3trawqLeakKKxp5iPlKOtsbi3trCvsa+vsK+Frh2vsa+sqqqmnpydoqCgn6Gio6elqKaqqaWnpqSnqYSrOayrq6qqq62qrqyuq6WxsK+wtbe1tLOytbW0tri7ury/vr29wMLAwcLDxcPGw8XHx8bHxMLExszGxny0srOys7SztLOxsra1tbKys7S0srGwsrKzsamwsq2xsbKytLK0trSysrOztLS1rrO6tLq6uLq7uru5vL26u7y5ura5t7OuqLK0ur29t66pqaysqaiqrrO0qa+urK6urq2tqaqqqqioqKenqaupqKilpKipqKqnpKSko6SlhKQPpaShoqGgoKGhoaChoaCihZ4Rm5uanJucm5qYmJmZmZqZmZmEmC+Xm5mamZiXlpaVlZSUmJiVlpaXmJmZmZybmpubmZmXlpaXmJqamZmZmJmamZmamoabGJybm5qcnJqanJuZm5qbm5uZmpybmpubmYWaIpiamJiZmZiYl5iZmJeZl5iYl5eVl5aWlZWTlJSVlZWUlpaElQWUlJOVlIaVDZSUk5OUlJSTk5aVlpeEmAWXlpiZmYSaCJubnZ6foKCghKEUoqGgoKGgoKGgoaGgn5+gjIagoKCFn4KehJ0HnJydnp6cm4ScBp2dnJucnISbCJqcnJubm5qah5uGnIibDZybm5ucnZ2bmpuanJyEngSdnZ2ehJ2Anp6dnJydnJyamZeWl5aUlZWTkJCNioiHh4iOk5iZmZyfn5+dm5qbnJubnZ6goaGipaeoqaakoaGgnZ6enJydnJuZlpSTj42IiYSGhoN+gIGChYaHipedlXv39fiMl5CQi4j8gomMh4uGioiFhIKFjJOVmZCVioeIjY2OhomMkpaAlJOYn6Ccmp2doZqjqKmrrqGhq6Gdn5yboaCem5iho6Sop6SnpZyZoaemqKasrra5uLi4trSqp7C2tK+6v7u8vbu9u8C7vMTNxsXHxr2+xMLGx8TBubW1t7u8vsW3yc7Nyc/KyM7Q09HNzdPVzcq7uLW3wba/vsDFxsfNztDR0MsqxLyxs62ppqOmpqKop6enpqako52impyZlZWIl4uOmp2foqSmpqanpqeohKcfpqanp6ipqKmoqKepqamqqqurq6yrrKytrK6tr6+qrIatJ6ytra6vsLCxsbKzs7S1t7a2t7q6vb29v8DAwMHBwsLAwMG/vsC/wIS/C768u7m5t7i3uLi1iLRPtra2tbi3t7W3tbSzsrS2tLO0tba2trSztbW3tbW0sbGxs7Kysa6srqysrKutr6utrK2sqqusraysqqqqpp+ljIKUpZmIgYSSn6SprKumo4akIKOko6akpaSkoaCdmpeUmZqal5ucnp+eoJ6fn56fn52ghKEqoqKio6KjoqOjpKOmpJ6np6eoqqmoqamnqqmpq6ytrq6vsLCvsrKxsrSzhLSEtQi3tbS1triztAOcm5qEmSGamJaXmpqYmJmXl5WXmJeXlJWTjZSWkZWUlZWVlJeXlpOElXSWlpWXmZeampiZm5qXmZmYl5mamZuXmZaUko+VlJqbmpiUkZGTk42QkpKWl5CSlJSTkZGQkZCQj5GRj46Pjo2Qj5COjY2Oj46Pjo6NjIyLjI6Pjo6PjYuOjY6PjIyMjo6MjI2Li4yMjoyOi4yMjYyJiIiLiISJCIiJiImKiY2MhIoBiISJBoiIiIeIiISJFoiIiImLiomLioqIh4eKiYiIiIaIiIiEiQOKiYqEiRGKiImJioiKiIiJiIiJiYiIiISKC4iKi4uKiomLiYmIhYoGiImIiYiKhIkgh4iKiIiGiImJiomJiYiJiYmHiYmJi4mIh4iJiIiJioiFhwGJhIwHjY6Ojo2LjIeNAZCHkoKThJKEkYSQIZGRkJB+e5GQj4+QkZGSkpCPj5CQkZCRkI+Pj5CQkY+RkoSRA4+QkISREpCRkZGQkZKSkZGSkpKRkJGSkYSShZMSkpKSk5SUkpKTlJWUk5OUlJSVhZYllZWUlJSTk5KTk5GPjo6Mi42MjYqKhoOEhIKFioyPj46Ljo6Mi4SKgIuLjY+QkpOSlJaXl5aVlZSUk5OUlJSWlZWTkI+Pjo6MjYeJjIeBg4WHi4+Sk6Gnn4D++vyQnZGTjYf+gomNiY2JiIiDgX6DjJWdnpKWiIiJjI2KhIiMkZKPjpGXl5WUlJOXk5ygoKapnJ6kmpmYlJKWk5KRkZSWlpeYmpmZk5GXY5ybmZigoqepp6esqKqkpaurrKuxsa+vra2tpqimqKywr6+tramqr62ur6ytp6Gio6Olpq2iq7Gwr7KvrbKzvMLDxMjIxMa3sa2xwrS6uLi3t7e8uru6uLWxrKKjn5ual5mZl4acF5qZlJiPkpCMjIGOg4aSlJWampucnJuchJ0gnJydnp2dnZucnZ2dm5ybm5ydnJydnJydnZybnJydnJiImTyYmJiZmZmampmbm5qcm5ydn5+hoaGio6SmpqWlpqampKGhoKGio6Kio6KhoJ+enp2en52cmpqam5ycmpqGm0ycm5mdm5uZmJqcmZmcm5mampuZmZmam5qbmZiYmpiYl5WUlZSVlJWWlZSVlZWUk5SVlJSUk5OUk4qIcW1/joR0a217io6QlZSRj4+PhJEikJGRj5CSkI6Pj42Jh4SKiYqIi42OkI2Pj5GPkI+PjZCPkYWSC5OSkZOSkZSSlJKMhJUYlpaVlZaVl5WVlZaWl5aWmJqanZuZm5ybhZ0Lm52dnJybnZ2enJv/f/9//3//f5l/g36GfwF+/3//f/9/5H8CAgQAgMPFx8TFx8fDw8XExsbGw8LBwL++wMHBxMLAwcPDxMnIwsLEw8XIyMXFxsTJyM3L0M7Lz83Nzc/P0MTJx8nLy8nKyMLExsvG0NDS0MjFwr+5s66zu7u5t7q7vr2/v7u+vry5urq7uri1sbW5uLu7vLm6uba2t7Syp6SiorC2t7W1gLSxsbCvrK6wrqysq6uqqKqoqKanp6WlqaakpKOlpKGhoaKjoaKfoKGhoKGgoZ2goKGgnp6fnqGgoaKjoaGioaCioqKhoJ+cnZyfoKOkoKGhn6OkpaOko6OjpKOkpKWlpqanp6anpaOlpqenqKamp6WmpaSmo6KioqOloqOioqSfGKGhoZ6gn5+gnp6fnpydnZudm5qZmpmZmISZFpqZmZeXmZiXmJmXmZqYl5iYl5iXl5eEliCXmJmbm5ydnJ2enp+enqCgoaSlpKaoqKmpqqqqq6yqqYSqMquura2rqqqrqqipqKipqKilpqinpqWlqKinpqWlpKOko6KioqOjoaKio6OhoqCioaKghKGAoKGioqGioaCgoKGhoqCioqKgoJ+goKGhoJ+goaKkpKWko6SkpKWnp6ampKOgj56foqOjoqOjpKSjoqGioqKhn5uampiYmJaSjoiJiIyPkZKTko+OkJSTk5WZnJuanaClpKaqqq6trK6wraWrq6mlpaWjnqKemZiUlpaRkI6SjoyAjo6KjIuKipCTjpWRjpGOh42PjJOVlpCMioqHjYyRiYmMl5qdnZWUlaCYk4+LjIuQmaalrKuqpKOppqekpqaxqqyuqa21tbO3t7a6uL/BuLGmqqOsta2orre/w8fHv7/JyMLBxMrIxbrFvb/JxbnCwbOztba5v7zCvcjCvru6sLRPu7+4vLm+sLa3ssbGz9fSzsfCwsbOxtbW0MXSzMTOxM3S1tzY0cW+s7OyrbCytLOwtLWvsrKwrqqqnJ+kpKekkpaSkJufpKKkpamsqK2trISuBquqqqmoqYSqBamrrKuthK5Brayur7CxsrGys7Oztra2t7e4tra5ubi3t7S1tra4urq7u7/AwMLFx8fJycrMzc7NyszLycnLzMvJyMfGycvJysWFwxrExMPCwcDCwr7Bv76/wsTBv8LDw73BwsTCwIS/B72/vbzCvr2Evwq+wL/AwL3Avr+9hLpht7m5uLi5trm5t7a3uLa2trW2t7azr6ulr7Gor5+AgZGmr7KysrGusLGwr7KzsbCvramtrqurqamppqGfoaukp6SnqKijpqalqKajp6epqaurqampp6mqrKqsr66uq6uqq4WyJ7S1trOztre6u7q6uru7vLy/vr7Bvb/CxMTHxMbHxsjHxcTEw8PEw4Czs7Sys7O0srGysbSys7GvrrCvrq6vsLCvrK2vsLC1s6+wr7Gxs7Kws7SwtLS5ubu4trq5uri7uruzt7W2uLi5uLWzs7O4s7u6vbq1s7Kxq6ahpaqsqqmprK2tr66qra2sqqmprKmppqOlqKiqq6qnqaekp6ajopuYlpagpqamo3KloqGgoaCgoaCenZ6enJ2enZ2cnJudnZ2cm5ubnJuampiWmJaXlpiXmJibmZmYmZeZmJeYmZiYmZqZmZmYmZmXmZeYmJmYk5aWl5iYmpqYmJiamZqbmpmZmpqZmpubm5ybnJybm5qYmZucnJybmZqZnJuFmQSYmZmYhpkZmJmXmJaWl5aWl5aVlZWUlZSVlZSSk5KTk4aUDJKUk5KSlZSUlZaUlISTDZKSkZOTlJWVl5aWlpeEmBiZmpmamZubnJ2dnp+goKChoaKioaGgoKCEoSKioKCfn56en56en52enZ2dm5ubnJycnZ6cnZucnZyanJychJsEnJubnYScCZqam5ubmpubm4echJsLmpqam5ucnJubnJuEnAGdhJ4Dn56ehJ8JnZyaipqZnJ2dhZyAnZybm5mamJeVlJSSkZGQjoqEh4eIi42Oj46Li4yOj5CTk5eXmZqcoKGhoqOmpaSmqaiepaSjoKCgn5qenZqYlJSUkZGOkY+MjZCPkIyLjpOXkpmTjZSQi46Tj5SYm5WSj4+OlZWWkJKUnqKioJycnKifmpeSlJSXnqenrqyqpqR6qKWopaistqyws62us7KwtLOws7O4vLSvqa+psLWwrbC4wMXJyL7CyMW/wcbOzMXDysHDzMi8wsK2trq7v8K8xsLJxMTBu7W6wsa/v7zCt72/uMjK1NzX1dDJyMzTzt7f18zX0szUys/T2NnVzcG5sK+rqqurq62qqquFqBeko5WZnp2fnYuRkYuYmp+gn5+jpKKmpoenDqWmpaWkpaWmp6eop6enhKgop6enqampqqurq6ysrausrKyrra6trayrq6usra2trq+usbO0tbi5uYS7Ob2/wL69vr6+vb27u7u8vb67ubu5t7e4t7a3t7a1tLK0tbSzs7O0tba0tba1tLG0tbWzsrKysbKys4SyP7OzsrO0s7OytLWxr6+ysa6ur66srq2rq6yqrKysqqysqaqoq6qqqaekop+hpZujknRzhpqhpaemp6ampqWkpoSlNKKdo6KhoaCfnpqZmJiemp6cnp6fmZ6fnp6dnJ6foJ+goZ+gn5+hoaSho6Sjo6WkpKOnqamEpyWop6eqqqytrK2trK2tra+wsbGvsbKzs7S1tbW2trSztLW0srOzgJqYmpiXl5iYl5aVmZiXmJSSlJWUl5aWlZSRkJSVlJWWk5OSkpeWlZOSk5KVlZmXmpiWmZiYlpmZm5OXl5eYmJeamJSTlZeXmJeamZaVlZaSkI2PkJKRkJGRkpCSkZCRkZGPjpCRkZGOjI2Pj4+QkI+Rj4yPjYyMh4aFgoWNjo2NFY6NjIyNjY2Mi4uMjIqKiYyLi4uMjYWMbImKjIyMiYqJiIeHiImIiIiKiYqKi4qIh4eLi4qLiImJiIqJiouIh4iIh4eIhoiIiYiHh4aHiIaIh4iKi4iJh4mIh4mKiIiJiYiIiYeHioqKiYmHiYiJi4qIh4mIiYqKioiJioqKiYmKjIqLioSHEImKiIiJh4eIiYmIhoeJiIeEiAeJiIaIiIeGhIkWiImIh4mGhoSDhIWGiYqKi4uLjY6MjISNFIyNjI2OjY+QkI+QkZKTk5KTk5KShJESkJCSkY+QkI6QkI2Nj4+Qj4+PhY4bkZGRkJCQj5CRj5GQkpOTkJGRkJCRkpGQkpKShJEIkJCSkpKRkJGEkhORkZKSk5OSkpOUlJSTk5OUlJWVhJYVlJWWlZSUk5KRgpGRk5OSkpOUlJSThJEGkpCOjI2LhIqAh4SDhYSGh4eHiIiIhoeJiYqMjIyNjZCQk5SSk5KRkZKUlJSTl5WWl5aUlJCVlJSTkZGQjo2MjouLjpGRkY2Mj5WclpuXj5aPjI+RjZWWlI+Jg4SBiIySjIyOmJ6fnZaTlKSdl5ONi4yOk5aWnZeXlJKYlJiYnKSunqWnnZqfnJOAlJaWlZaZm5iYlJmVmJuZl5mdoaOmp6Kkq6ypqay0trGxurKrsrGmp6eioqOkqKypq6muqKesqqenrK2sr663q7a6q7q8ytLMyc3Ax8ja0ejp4M/f2dLa0dDRzMe9uK2qoqOfm5ycnZ6cnJyYnJudm5iZjJCVlJSUgomHgY6QlZUPk5OXmpaampmcnJubm52dhpwanZ2dm5uenp2cnJubm5ybm52cm56cmZqZmZqEmQOampmEmCKXl5eZmZmam5udnZ+hoqChoqSko6SjpKWjoqGhn52dnp6ehp+CnoScCJubmpqamJuahZsUmpubmpiZmpmamZiYlpiYmZmZm5qEmX6amZmZmpmXmJeYlpeXlpSUlJOUlJaVlZSSlJOUk5OSk5OUkpGQj4yJjIiPgGFec4eMj5CRkI+SkZGRkpGSk5GOjJCQjo+OjYyJiYeGiomLiouMioaMj42Njo2Ojo+PkJCQkZGQkJGRkJOTk5STkpKSlZSVlJSVlJWUlJWVlpeFlRmXl5iYmZqamZmbmp2bnJuanJmZmpqZmZqY/3//f/9//3//f/9//3//f4h/AgIEAIDBwsTDxMPEubrExcTBw8TEwsXEw8e/wcTEx8bGyMfIx8bIycbFw8LHxcjMzMbIyczMzMvPzMnKzsvIxcDIxcTGxcTLy8vKztPS0M3HwbWwsLXAzM7KyL+2tLi+vsLBwL7Bv8DDwb7AwcHEv8G+uri2s7SytLm5t6uss7e9tLGzsW+wsa+vraysr7CuraysrKqppqSjpKampaKjpKSko6SioaKkoqOin6GioaGgn5+dn6Chnp6dnqCdnpyampqbm52eoaKjpaSjn52doKCko6WjpKSmpaSko6Kio6KjpKWmp6eopKWmpaempqenpqelpaWEphekoaGhoKGin6Cfn6Cfnp+fnp6dnJ6cnISac5mam5mYm5qXmZmYmJiVlZaVlZOWlpaXmJeWlpWVlpWVlpiYmZiYm5uampqbnZybnJ2dn5+foKCio6WkpKWkpqenqKqqq62sqqqqrKusq6usq6usqqqpqaioqKepp6WkpKOhpKSnp6alo6OkpKOioaKjoqGFog2hoqOhoKCfoaCgoJ+ghaEQoKKkoqKioaKjo6GioqKhoYSigKOjoqKjo6SlpKSmpqenpqWkpaSio6OhoqSkpKOho6KjpKOio6GgoJycnZ6fnZ6amJmTlZaNkZWUkpSUk5CSkZCPj42NjY6Si5eZnKCjp6ussK6usbOysa2vs7WysLGwq6qqqqyqpqano6SmpaOnqqmttbi3wK+tq6enpZ6dmJiXgJWsr7e+vru7vbq4sq+3t6mjmpeWnpecmY6TlZecppaTkpGbkZaNlJaZmZCUnJGGip6Yq7e5sauZl5Scva+ms7K5xsnAwLy4vcHEwsvNysG/xcK3v7m1srm8uMCvrqqio6SqrKSmrLW0rKm1uLDDxdHPx8THwL7JeXt53s62zMq9L7KurrWsr6qvr6Wvr6uplKmkpqOnqaqnrquqqqedm6CVo5yhqKOoqq2lqKqpoamthKxIrq2qrKyqqauqq6yrq6qrq6uqqausra2urLCxsLKys7O0s7Kztbe3uLq6uru7urq7urq8u7y9vb2/wMDAw8PCxMfGx8jKycjIhMkeyMjHx8TDw8PFxcLEwb2/v8DDw8LDwcHEwr/Cw8HBhsNxwsTEwb7Cwbu/v726uby8vr+8wsDBwsC+wb++u7q7uru8u7i4uLe3tba1trW2srKztrS0trCys7SzsbGvsLOxq7CsrrOys7Cur7Gtr7Gxsa6vraunqKurqq2vr6uhmpWMip6hm5yhpaSmpqepqKippqaEpzyoqaurrKypqqyxsK2rrKysra6usK+ysa+zsrO2trS2ubm4uLy+vr/Awr/AwsHCwcTBwcTFxMXFxsPDxMSAsbKzsbCwsqiosLKzsLKxsa+wsbCzsLCxr7OysrW0s7KxtLWzsrGxs7OztrWztba5tre4u7i3t7m3tbKvtrW1t7Sytri4trm8u7u5tbCoo6Onrra5t7WuqKeprq+zsK6trayusa+rrK+vsKyuq6impqSjpKWkpaWenqOlqKSho6IDoaKfhKBdoaGfnp6enZ2enJqampycmpmam5yamZqamJmYl5iZlpeYl5iZmJiYl5eZl5eYmJiWl5aUk5SWlZaWmJiZmpqamJeWl5eZmZuYmpmampmYmZiYmJmam5qam5ucmZubhJoLm5ybmpmZmZuZmZqEmBSXmJeXl5aXmJaWl5eXlpWUlpWWk4SUGJWVlJKTlJKTkpOTlJKSkpGSkJKSkZKTk4SSAZOEkgaRk5SVlpWEloSXgpiFmQKam4ScBpudnp6en4eghKEEoqGgoIWfCJ2enp2enZ6chZoMm5ucm5ubnJycm5ubhpoRm5qam5uampubm5qampmbm5qEmxWcnJucnJubnJycm5ycnZ2dnJ6enZ2HnoCfn5+gn5+fnZ6cmpydnJucnJubm52cnJqbmZqZmJiVlJSWl5eYlJKPjY2Nh4uPj4+Qj5CNjo2MjIyLi4uNkIuUlpmbnaCipKinpqmop6eko6qqqqmppqSlpKOjo6ChoJ6fnqGipaanqbi4tr6tq6yqrKSenpiZmZeqr7S5uLO0uIC1tLOutrinpp6cmZ2boZ2UmpeboauamZqaoZabkpufoqCYm6OXjpCfnaq9u7ixop6cosC1q7a0ucXFvry7ur3DyMXKzMvGwsbHusG6ubvCxMbHt7Oxq6qusrGsrLC7t7Cwur21x8rY19LOz8XH0XyBe+LPu83HubKpqrKoqaSqq06dpaajpZCknp6boaOin6WjoqKgmJWbj5yXnaKdoqKkoKCmpZ2jpaWnpqemp6ampqWlpKSlpaanpqioqainp6ampqinqKioqqqrq6qqq6uErHCur6+ur66ur66tr6+vsLCxsbK2tbW1t7m6uru8vLu8vLu7vLq5urm6uLa3tra2tbezsrKytLW1s7S0s7S0tLO0tra2t7a2tbW1tLSys7Wzr7Kxsq6usbCysbC0s7O0s7S0srKurq+tr6+ura6trKuqhKs+qamoqKuqqqmnqaipp6WmpKSlpKGkn56mpaalpaOko6WmpaWkpKSin6KhoaCjpKKflpONgoOXmZOSlpubnp2Enz+hn5+enp6foKGhoKGioaKipKOjpaako6SlpKakpaalpaapqaqpqqysq6ytsLGxsrKwsbOxsrGzsbGys7S0s7WEs2WXmJiWlZaYjY+Vl5aWl5eYl5WUlJeWlJSTlZWUlpeVl5aXl5aXlZSUkpSWmJaWlpmZmZqZmJmXmZiWlpSYl5eVlpOYmJeWl5mYmZmWlZKOj5GUmJiUl5WRjo6QkJKTk5GTkpGTkoSRGZKRkJCQjo6Pi4yMjY6OiYmMjIuLio2MjY6EixqMjY2MioqIiIuLiomKiouJiYuLjIuLi4yKh4SIT4mHiImIiomKi4yJi4mHh4mIiYmIh4aFhYWEh4aHiIeHh4mIiYmLiImIh4aJiImJh4iIh4eFiYeIiIiJiYmHiYqJiYiJiYqJiYiIiIqIh4eFiAOHiYmFiA6JiYiIiYqIh4aJhoiGh4SIGIeHiIaIiIaHh4aGiIeHhoeHiIiHiIeHh4SIHoaEg4eJiIqLi4uKi4uMjIuKi42MjYyMjI2Ni42OjYSOBZGRkJCQhpKEkQaQj4+Ojo6EjRGOj5COjI2MjY6Oj4+OkJGRkYSQFI+PkJGRkZCQkJGSkJCRkJKRkJCPh5EHkpKSk5OSkoiTA5SVlIaVhJYZlZaVlJWVlpSUkZGSkpKRk5KTk5GTlJWUlISRgJCNjIuNkIyOjIqJhISHgoeLiomLiouIioiHhoaGh4mKjYOLjY+QkZOUkpOTk5STlJSSkpSWlpSUlZWUlJOSk5SUlZWXl5mbnqOmqLq3uMCprK2rrKegm5SUk4+hqKywraWmrKWpq6WvrqCgmZWOj46Yl4qRkJaep5SSlZOfj5SOgJibnJuQkZmQh4iYkJ20ta+ul5aSk6uhm5+ZnqOkoaOin6Krtq6ysrazsbCxq7Kur7a8xcnLuamqp6mssK2no6m6tLC0wb+4zdTj4eDa2s7M4IWIhPPexdjVwrSsq6qenpednpSamJeWhpWPkJCUl5WUmZiXlpOOi46FkIuPlpKWD5aZk5WXl5KYmZiZmZqdnIabBpybnJybmoSbD5ybmpqamZmam5ucnJuam4SZRZqZmZqamZqampmZmJeamZmampucnJ2fn6CgoJ+foaGhoKGho6Shnp2dnZydnJuanJ2cnJybm5yZmJmZmpuampiam5ucm4WaOpuam5ybmJqZlpqYmZaWl5aYmJeZl5mZmJibmJiUlpaVlpeWlZSVlJOTlJSUk5GQkZOUkpKTkpORkpGEjxGQkI2PiYmQkZGQj46PjpGSk4SRAZCFjlmQkI6LhYN/dXaEhoOAhIiKjIyMjYyPj46Pj46Pj4+Qk5OTkpKSkZOTkpSUk5KUk5KTkpOTkpSUlZWVlJWWlpWWmJeXmZqamJqamJiYmZmZmJiYm5iamJmXmP9//3//f/9//3+of4OA/3//f9x/AgIEABvBwL/AwMG/wMLDw8bIxMHCwcLAxMO9wMDGxcOFxCvCw8PDxcXHx8rKy8vIyMrJxcTDxcvOzMvOxcfEyMbExsXJy8zO0dHR0MvFhMB2xcnMyL+9vLu5vLu9vL7CxMbHxcLDv77AxcbGw8G7t7a0s7S1ta+0tLOvrLGhmqmtrK6rqq2rrK6urqytq6eopqaloqSlpKekpKOgoKKjpKSio6Kho6OgnZ+en5+hoJ6foZ6enpycnZycmpyfoKGgoZ+goKOjo4WggKOioqGhoqKioKOjo6alo6SlpKOmpqanpKWjo6OkpaenoaOkpaalpqalpKOioqCgoqCgoKGfn52cnJ2cnJudm5uamZibmZqampmZmZiWlZeYl5aUlZeUlJWUlJSXlpaVlpeXl5iXmZiZmpycnZyenZ6enZ2enp+gn5+gn6Kio6SkDaSjpaenqaqpqaqrq6qFq1aqq6urrKuqqKmpp6eoqaimpqWjoqGjoqSjo6SjoqSjoqKgoKGhoKGioqKhoaKioqGgoZ+foKCfoJ6en6Ggn6GgoaChoqSjoqKhoaOjoqKjpaSko6WmpYSmPqWlpKanpaanp6WmpaOipKOjoaOjoZqjo6OgpKWmpKSioqOioJ6dn6GenKKfnpqcn5ybmJSWk5WSkJSPiI+ShJGAjo+RkZSSkZSWl5mbnJygoZ+mqaytsLO2tbW0t7Szubi4tri3uLq6trS2vrm3u7y7u7izvrq3r7G2tL+7usq5vrm2tbu4uMmzvLW3sKy7uLixsqinqrunmaiuuq+gpJ6noJuhjISMlKOnvMDBybykmKOhnaOoq6isrKqyr62lpqlap6iqpKiio6Smp6mrqrGyucC8x8/Sz8bMx8/Fw8bQ2Xd06tzWyMe/vr3CvbCbkI+RlZufnoWeoaCqprGxsrCorqOjrairqKunqaylra6pp6ehmqWpqqysqZushKsRqaSpqamoq6qqq6qknKuqqayFqk6pqKinp6qqqqytrK2usLGwsbGys7O1tba2tbi6u7m7vLy8vb28vr69vsDAv8DCw8bFw8TFxMTGyMXIycfGxsbHxcTAv8LBxMPCxMHAvb+Ewg3Dw8TFwsDCwr+/wcLChMCAv7/Av7y+wLq4ur28t7m+v7+wnK7AvLy6u7q7ur25tba3tri5ubi4t7m4uLW2tbi2s7a1trSztLa3tbW2trCkoaWxsa+wr7CvsrWxsrCysK+vrayrr7KxoZWlrKye+uL8mrCfioiWpaWop6ioqKalpaepqKmop6anp6iqq6usrq0xrKqoq6usrausrq+wsrOvsLSztbS5uru9vL28vr/AvcC9wcDAwsTBw8bDw8LBwcDBwg2xsK2tra+urq6vsLOxhK8Prq6vrqysq7GxsLKysbCwhK+AsrOzs7S0tre1tbe3srKws7e2t7e5tba0tra0trW4tLe6vLy7vLm0sK+urrO2uLOvrayrqaysrq6srq+ys7Cxr6qqrbGwsK6sqqempKKjpKSgpKOknp2ekI6dn5+gn5+fnp6hoJ6fn56ZnJqbmpmampqcmpqZl5mbmJmampmYmJoqmpeWl5aWl5iYl5iZlpeXlpeWlJWWlZaXmJeXl5aXmJmamJiXmJqZl5mahZkTmpqYm5mYmpmbmZmampuampiZmISaZ5eZmZmamZmZmpmZmZeXl5iXlpWWk5SVlZaVlZaVlZWUk5KSk5OUlJSSkpOSk5GTk5ORkZCRkJGRkJGRkpKTkZKTk5OUk5STlpWWlpeYmJiXl5mZmJmYmJeXmZmampubnJubnJ2enp+FoAGehqAWoaCgn6Cgn6Cgnp6fnp6bnJyampqZmYaaBpuam5uanISaCJmamZqbm5ychJuImoObhJqDm4WcCJ2dnp2dnp6ehJ+DnoqfgJ6dnp2cnZubmZubmpWcnZyYnJ2dnZyamZmamJeWlpmVk5mXlJOVl5WVk5CRjpKQjJCKgoyQj4+OjYmLjY2Nj5CSkpSVl5iYnJ2boaKlpqenqampqKqoqK2sq6msrKusrqupq7KzrrKzsbCvrbW0sKussbG+vLTBs7q4tri6tbnIgLTAuLq0r7+6u7W0qqmpvKqbqa66r6WooKWjn5+Ph4uSn6jAxMfRxK2eq6WiqKuuq7CurLSztq6vraqvsqqtrK2trK6ws665t7/Fw9DX3dfM1MzUzs3Q2OJ5ee3h3szKxMO9wr+qk4iHi5GUl5Z+lpqYo5+pqaqmoKadnKaipqGjFKChoZuipaOfoJmRnqGipaSglKalhKQjn6WlpaSmpqalpaCWpqalpaamp6Wlpaampaalpaamp6ioqKmHqoSrgK2urq+ura6vr6+wsK+xsLGxsbKztLa2tbW3t7e4ubm6urq4ubm4uLa2tbS2tba2tbazsrCxs7OztLW1t7a2tLWzs7SytLW1s7KxsrCysrKxsq2rrbCurK+xs7CgjqSzsK+wsK2ura+trKqsq6ytrq2trKyrqqurqqyqqKuqqamnhKh3qainpJmXm6Sko6OjpaSmp6eko6WkpKKioqGjpqeUiZqfnpHm1vORoJCChJGcnZ2dn5+gnp2en6GgoZ+fn56foKGio6KkpKOioqSko6Sio6OkpKanqKepqKinq66srq2vrq2ur6+wrrCvrrCysLGzsrOxs7KxsbEDmJaUhpVClpeWlZWVk5SSk5STkpSTlZWSlZaUlJSTlJSUlpWTlJSVlpeXmJiYl5SWlZeYl5eYlJmWmJeWmJSXlZiYmpuXmJaVhJMKlJaWlJOQkJGSk4SQDpGRk5aTlZSPkZGSkZGRhI8XjYyNjYuIjoyKg4SEdnuIioqMioqJiIiFiziKhYeKi4uKi4mJioqJiYmKjImKiomJiIiJi4aHiYmIh4uKiomKiIiHh4eGhYeJiIiHhoaGhIaHiISHB4iJiIeHh4aEh0eGiImHiImGh4eIiIiHiImIioiIiImJiImFh4iJiomIiImIiYiIhoeJiIaGh4WHh4eIhoiJiIiIiYmIhoiHhoeHh4aHhoiGhoeHKYaGhYaHiIeIhoaHhoiIh4eGiYmLi42OjY2Njo2Li4uMjIyLjIqKiYqMhI2DjoWPHpCQkJGRkJCQkZKSkY+Ojo6Pj46Pjo+Oj46MjIuMjISNC4+Pj42PkI+QkJGQhJEBkIyRJZCRkpGRkJGSkpOTkpCSk5KSlJSSlJSUlZaUlZSUlZaXl5WWlZWHloCVlZWTkpORkY6QkZGMlJOUkZSWlZOUk5KTkZCPjo6OioqRjo2Mj5GPkI6LjYmMi4iLhn+Ki4uMioqGiYqLi4qLjI2NjpCPjpCQjZGRkZOTk5KTlJOSkZKVlpWVl5iXlpmZmpqhoqKjoaGjpqKloamlpK6wurixvKy1t7m5ure9zny6x77DurTHxMC6uKynrL+nk6Krtq6koZicl5aXgnuAhpGivcDI0MKvnqOblJeanqGloqCmpayopaSkqq+praarra2utbezwL3Hzc7c4+fm29/X493Y2un1g4H7+u7Z19DPxczGpoqAfoCFiYyKd4uPipSVnJuamZSak5GZhZRNk5WQlpiVkpKPiJGTlZiXlYeYmpiYmZaSmJqampyamp2clYucnJubmpubm5qam5qZmZqampmamZqZmpuam5qZmZqbmpqbmpubmpiZmZmFmhWbm5udnJycn56foKCfnpyfoKGfoaCFnhmbmpuampqbnZydnJubnJqamZmbnJ2dnJqahJmAmJeYmpiZmpiZmJeYmJSTlZiWkZSVlpSBdombl5aWlpWWlZWUlZWTlJSUlZWVlJWUk5KTk5SUkZOSlJGQkpOTk5KRko2FhIeOjo2Oj5CRjpKSkY+QkJGOkI+OjpGRfXSGiId3xbzYf4p7dXaCjI2MjI6Ojo2Mj5CRkJGRkpGQj5I3k5KSkpOTkpGQk5GSko+RkpGQlJSRkZSTlJOWl5WWl5eVl5iYlpeYmJiXmJqYmZmXmJeYlpWVl/9//3//f/9//3+cf4KA/3//f5V/g37RfwICBACAw8HEwsPCw8HAv76+vcDAvLy+vMHDwMXGw8C/wcPDwcHCvMHEwsbIyMfFycTExsfGxcPBw8HCxMbLxMjKx8bDycvLwsTLzcjDv7u6ub3Hx8K+ur27uba6xMnIxcnFwsbEwMK8vb3BwcS/u7e1tLe4uLq4s7aytLWyoqetp6OpqqcFqqiop6qHqXyopaSipKGjoqOin6KjoaWkpqSipKOjn6Cen5+cn52eoJ+fn6Cgm6CenZ6gn5+fnp2eoJ2en5+ho6GgoJ+goKKkoqGgn6KioqOjoaOipKWlo6GkpaOkpqWkpqWnpqalnaGjpKSgoqSioqGioJ2goJ+cnJ6enZuampycm5mahJlbmJiZmJuamZmZl5aVmJqXlJWWlpWTlZSSk5SWk5WWmJmZmJiZmZeYm52bm52dnp+en5+goKChoqKioaGipKOio6ampqeppqmop6ipqKmpq6uqqqqpq6qpqaqpqISnDaakpKSjoqKho6SioaOEoh6hoKGioKChoKGioaCfoKChoZ+eoKCioKCen5+fnqCEnxWhoaCgoKGioqGio6OjoqOjoqOkpKWEpoWkFqampqWlpKWkpKWmpKWjoqShoqKgo6OEpVykpaWkpKampqWkpKOgoqOjoqOin6Cfnp+Znp6Uk5mNmpmZnp2Ok5GVlZeVlJOQkoqQlJOTlJSVlZaZmJmdnJyho6WipqmlrbKytbS5uru1v7/BwLrAvLu7uLzCv4S5gMLEvbW5t7OxsLO1sq+ys7Krr7m5sLSsr7SrrrK1tq6sqq20t764tra0tqy9x8vK0Mi3tcCvs6qqrbeysq6kp6+vs7fDv8DBs8LFzttz3HLi49jf5NjCucK/t7++uLDAtLWon5qbn6Cio6Ksq6qsra+xr66qpqispK6trLC3tLWvL66xraW2trCgpaCzsrKroq6vqKyrrq2trKiTq6ytra2sq6eqqairqqqqqampqqmohKkdp6epqqmqq6uqqqusra6vsLGwsbGzs7W1tra4uLmEuoC7vLy9vr+/vr6/wcLBwMHAwMHCwsHFw8XExcPEx8jGx8fIyMbGwcLBw8jGw8G/v7/Dw8HAwMLDw7/BwcHCwcC/v76+v7/Bv8HAvr7Avqepsra1sJacurStsbu6ubu5ubi4uLe3ubi3ube1tLWzsbGxtLS3srS1tLSwtLe3tLKyskOwr7Czr7CysrOzsrCysbGtra+xsK+vr62usa+2tK+X6emIj5emo6WmqKiop6ampaSlpqemp6aop6upqKapq6msq6mnhakuqKapra2urrCytLS2trW1uLe3t7q6vLq8vLy+u729vcDAw8LAw8TAvsDBwcHEw1KxsbKvsK+wr66uq6ysq62rqqqqra6tsLGwrK2vsa+tra+qr7KwsrSzs7C0sLGxs7SysrCxsbC0tLi0traztLG3t7iysra5ta+tq6mrrbO0sauphKpHqbCysrSzsrGzsK6xrK2qrKywq6iko6WmqKamp6Klo6OkopGYnpiVnZ2bnJybm52cnJ2cnZ2cm5qZlpiXmZiYl5aYmZebmpqEmRmal5eWlpaVmJeXmJeXl5mYlJeVlpWUlZaXhJZBlZiYlpeYmJeYmJiXmJqamZeYmZiYmZiYmZmXmpmZmJiZl5mbm5qZmZqZmpmTlpiYmJeXmJiYl5iXlZaXlpaVlpWElAmTlJSTk5OUk5OEkhuUk5OSko+RkpKTlJGQkJGRkJGQkJCRko6QkZOElCmTkpKTlJWVlpiYmJqamZiZmpqamZmZmpqbm5uampubnZ2enqCfoJ+fnoSfA6ChoIWfH56enZydnJybmpmZmJmZmpqamZmamJmZmpqbm5uampqEmYKah5sDmpubhpqEmwGahpsGnJycnZ2dip4Kn5+fnp+fn56enoifDJ6enpybnJybm5mbnISdg56HnSicmZmcnJyam5yampmam5WXmJCPk4qUlZSWlYmRkJSUk5GRko+PiZGRhpCAkpOUlZeXmJqcnJueoZ+lpqaoqK2sraqwsrGwq6+vrq6usLWyrq6tr7W4s62uq6impaqqp6Snqaikp6+wqqymqa2mpaiurqmmpqmusbWwra+ur6e1vsTEyMazs7uttK2usru3tq+nrLazt7zMycbIvsnH1OR45Hbp5Nnk5NrEwMZPw7y/vrmzvrSxoZmVl5mZm5yboaKjpKWmpaaloJ6goZykpKKmrquspaWppJ2rq6iZnpipqaigmqWln6SipaWnp6KOpainpqelpaSmpaampoWlAqalhKQJpqWkpaWkpKWlhKaFp4CoqamqqaqrrKytrq+vrq6usK+wsbGysrKxsrOztLOzs7S1tba2t7a5t7a2t7i4uLe3uLm4t7W1tLW4t7S0srGytLa1tbO0tbSytLOys7KwsLKysLGvsq+0srCys66bn6aoqaCKkKako6etrKywrq2vrqyqqqyura6rqqqrqainpjupqKinqqqqqaOop6mnpqamo56hpqSlpaWopqWlpaSlo6Kjo6Slo6OipKagpKKehc7PfIeQnpyYmp2en4SdDp6enp+en56fnqCenp+ghKI1oJ6goJ6hoqGgoaOkpKSmpqenqqeoqamnqKmqqq6trqusrq2vr66wr7Gxr7GxsLCysrGysrCAlZWYl5WUlZaWlZOTk5STkZKTkpOUkpSWlJSTlZaVk5OUkpSVk5KUlpWUlpWVl5aVlZWRlZSUlZaalZiXl5eVmZeYlJaXmJaSk5OQkJGXlpSSjpCRkpKPkpSVlJSRkZSVkpOQkpCQkJGPj46MjY6Qjo2PioyMjoyJeoKEgIGKiYdiiYmIiYqIiIqJioiHh4iIhomHiomIhoeJiYmKiImJiImIiYiHiIWHhomGh4mIiYiKiYeJh4aGhoeIiIeFg4WFh4eGhoiIh4eGh4eHiIeHhoiGhoWHh4eJh4aGhoWGh4eGiYmFiB2HiIiDh4aHhoaHiIeIiIiHhoeHh4WFh4eFh4aFhoSHFIiIh4eHhoeFiIiHhoaEhYWGhoiHhYYfiIaEhISFgoWFhoiJh4mIh4aHiImIio2NjI+PjY2NjISNBYyMi4qKhIwLjYyNj46Ojo+Pjo6FjyOOj5CQj4+Oj4+NjY6Njo+NjIyLjIyNjY6MjY+NjIyLjo+RkYWPDpCQkZGSkZKSkI+RkJCRhZCEkRyTkpOSkpGQkZKSkpGRkpOTkpSUlpSUlZeUlZWVhJaAlJSUk5aWlJSWlZWUlJKRkZCQkY6RkpOSk5SVlpaUlJWUk5ORk4+PkZKRkZKQj5GQkpSPkpKKio+GkI6OkZCIjoqNjY6NjoyJiYWJjIuLjIqLi4uMjYyOjY6Njo+NkJGNk5WTk5WWlpWUmZiamZaalZWXl5qanJudnJ6ioKKenZ2AnJyanZycm5ufnp+go6OcnZ6enpmdoKOjnZucoaCnrKahop+hnKittLW+v6uttqezq665xry6saivubS+xNTQzMu/0c7g8oH3gfry5fDl5cbD0M3DyMm/tcOxqZiPiY6QkI6OkJaWlZSVl5eVlZSPkpONlpSRl5ycnJSVmZaQnZovmI2Qipmam5WOmZqRl5aXl5mZl4SYm5ycm5ubmJqampuampqbm5ucnJubm5qampmGmDSamZqamZmYmpmYmJmamJiYmZmampuamZmam5uam5qcnJucnJ6enZ2enZ2foKCcnpydnp6fhJ4gnZybm5ycmZmam5+dmpuZmZmam5mampybm5mamJiYl5eEmR2al5mYm5mWl5ePhoqOjpGGd3mHiI2NlZaUlZSVlYSUKZaWlZaUlJSTkpGRkpOTk5GRkJKSjpGSk5GQkJCPhomTkJGRkJKPj5GUhZAYkZCQj46PkI+LioaCa6aqa3uBjYqDhIqLhYwnjoyNjYyOjpCQkI+QkZCUkpORkI+QkZCPj4+Nj5GRkZOTlJSTlZSVh5QZlpaXlZWWlJSVlZiXmJiWl5aVlpiWlpiVlP9//3//f/9//3+HfwOAf4D/f/9/pn+CftV/AgIEAIDFwsLCv8G/v8C+vb+7u7m6u77Ax8TFwsDAwsPFxMHCwsHCxsjAxMLDxsbDxcXDwr+/v8XEw8TDv8PEw8bKy8vJx8fKzMTBwL+7v7/CxsfHxsbDwcG7tsDFycjIxr+5tLe4ubq8vb+8vLu6uby3uLS2t7i1tra2r66jq62qpqeqpRepp6alqayrqaqppKOno6OjoKKhoqGio4SkIKOjoaGioqCfn52fn5+dnJ6dnp6fn6Chop6enpyfoJ+ehp9QoqSjoaCfnp+ioqKmpKOgpaShpaWhoqOko6Oko6Sjo6elpaSkpKWlo6OkpKenpKWkoqKhoKCgnqGfn52cnZybnJybmpqZmZqYlpeXl5iXmZiFlw+Yl5eXlZSVl5aUlJOUlJSFlmCXl5iZmZmYmJqbnJuZmJucnZ2dn6GioqKkoqGhoaKjpKSkpaWlqaipqaqoqqqpqaqpqqqrqaeoqaqpqqmpqKanp6Wko6OioqKfo6OioaChoaGgoaGhoKCfoKCgn6CfoKGEoA2foJ+gn56eoKCfn5+hhaCGoYCipKOjpKOhoKCho6SlpqWlpaSko6WlpaSjpKampqSmpaaopqSlpaaipKSkoaSgo6OipKWmpaanpqempqakpaSlpKWlo6KhoqSio6GdnpycmZudoJ2OlJmdnKGem5mTmZiYmZmWmZabnZydnp2fnJuZlpaVmJeXl5mZlpmcnJudooCfn6GenqGho6ioqKWpsL7Awb6/vb/Ew8bAurzAvrq8wb+8uby7vr7AwL7AxMbCv7/DwsPDxcTBvcTFwcDAv8LCwr28urm3ubu9vbe7tbK7tbSzrqumpqmnpaKtrJ+YmZmVmJueoKOlqamqqquxsrKysbGxs7OxtLOxrqyysbO0rTirq62qp6mupaeysq6ssLWqqre0sbGpqK+us7Crr6+xsLCwsrCvr6+trKutq6ysrKurq6moqKqpp4SoAqmohKowqKiqqqyrrK2tq6usrq+upYyvsLGytLS1t7m6urm5urq5ubm6u7y7vL/Av8DAwcHChMF5xcXFxMPDxMXGxsXGxcPFyMjHwsTEw8TGxcXExMLFxcLEw8HDw7/BxMHAwb29urq9vr69vb6+u7ezubW5qqesrLK5vqqoq7i6vLm4ubm6ury5trW0tLe1tLOzsrW3tLKvsLS3srW1tbKztLWxtKmqsK6vsbKxs7Wzs4SxZ7Kys7GysbCrloyWl5eZsY/6g4+fop+gnZ6koaKip6anp6alpaWjpaako6WnqaWmp6Skp6mppaWoq6qqrK6trayurrGzsrOvra61t7W1t7m2tq+4u7q8vMC8vr+/wMHDxMTBxMTFxMWAsrGwsK2uq6ytrausrKmpq6msrbGtr6+srq+wr7GtrrCvr7G0rrOwr7GxsLGysK+ur66ysrSzsa6zsrGztLe2trO1tLe2srCurK+vsrW1tbS1tK+tqKaus7WytbKuqqWoqaurrKurqqqnp6WqpqWlo6SmpKWkp6Ohl52dm5WZnJkQnJqamp2dnJydm5mYmpeXl4SWFZeamJuZmpmamJmamZmZl5iVmJiWlYaWhJcQlZaWlZiXl5aXlpWYl5aXmISXEZaWmJmZm5mamJqZmJmZl5eYhJlEmJiZmJiXmJiZmJeYmZmYl5mamJiXl5eZl5aWlZWWlpWVlZSUlJWSkpORkZKSkZKSkZKTk5OSk5KRkJGSkpKRkJCRkZGEkBWPkZGTk5KSk5KUlZSUlpaVlpWVlpeElheYmJibmpqam5ubmpqcnJubm52dnZ6enoqfAaCGng+dnZybnZ6bmpqZmJeYl5mGmAGah5kMm5mYmZqampmZmpqbhJoJm5qampmampmZh5oGm5ubnJych50EnJ2enYaeAZ+LnoSfgJ6fnp2anZ2dmpyYnJybnZydnJ2enp+fnp6cnp+enp6fnp2dm52bnJqYl5aWlJaWmJeLkpWZmJmXmJeQlZWWlZSTlJGVl5iZmZeXl5aUkZGQkpGTk5SUk5SXlpaXmpiXmpaVmpqboJ2hnp2hra+xsLGvr7SztrKtrrGxq66xsa6tf7Kys7S1tLGztLa0s7W0s7W2ubezsLa3uLm7ub29u7e2tLSztLe3t7a5tLO5tbOwsK2oo6uopaCuq5uUlpWPlZaXm52foaSmpKWnqaqqqampqqqpqKeppaKpqainoaGgoZ+cnqOcnaaopaKoq5+irKqlp5+dpKOop6KmpamnpqeEpgOlpaaFpQSmpaWjhKQNo6OkpKSjo6OkoqKjpIalhaaAp6innYamqampqqqqrK2urq6vsLCvr6+wr7CwsbOzs7SztbW0tra1tra1t7W2t7W1trW2tra3t7e5trS1tre2t7a4tba1trW0tbWztLOxtLW0srKvr66usbGysLCwsa+sqKyoqZ+bo6Smqq2bnqSura6srKyur6+vrqupqKaqqaeAp6emqKqpp6OlqKmlqampp6anp6annZ6kpKWkpqWnpqWlpKSlp6amp6ampaWhj4SPj46NmnvmfIeWmpiXlZaamJqbnp2enp2dnZ6dnZ2anJ6foZ2enpycnZ+fnp+hoaKhpKOio6Slo6WmqKakpqapqKipqqqoqqGoq6yrra2sra8Mr62vsbCxsbGwsrOzgJaTlZWSlJKSlJSTk5SSj5GSk5OTkZOUk5KSkpOWlJSVk5WWl5GUlJWYl5SWlZSTk5GUmJeWlpaTlJWXlJeYl5eYmJeWlZKUkI+VkpOTlZaWlZOSkY6QkpSYl5aVkZCPkJCPj5GQkI+Pjo+OkpCLjYuMkIyNi46MioKIhIJ+h4qHG4iIh4eLiomJiomIh4eGiIaHhoaHhoiHiIeJh4SJQ4iIh4iHhYeIiIeHiISHh4eIiYmIiIeHhoiIiIaHhoWGhIWFhoeHiIeHhoiJh4qJiYWHh4SGiIaHiIaHiYeHhoWGh4aFhwSGhoiHhIYaiIiIhoeGhoeGh4aHhoaHhoaHiIWGhYWFh4eEhiOHhYaGhoiHh4aGh4eGhoWGhoeGhoaFhYSGhoeGhYWGhYaGh4SIBIqJiIiEiRqKi4uMjo2Pjo6NjIqLjI6Mi4yNjYyNj5CQkIWPC5CPjo+Oj46Pjo2OhIwpjYyMjI2Mi4uLjYyNi4uMjYyMjIuNjIyMjo6Njo6QkJKQj5CQj5CPj5GEkBCRkpCRkZGSj5CQj5CRkZCQhpEakpKSlJSUlZSTlJOUlpWWlJSUlZWUk5SVlZSElYCUk5CTkpKQkY6QkZKTlJSTlZWUlZSVlpOUlZSVlJSTkZGUlZOUkpCSj4+Oj46PjYaLjpKRkpGRj4iOjY6Ojo2Oio6QkJKTkZGQjo6LiYmMjIyLjY6NjY6PkJCRkI6RjY2RkZGTkpWVkZSUmZqZmpiZm5abm5ycm5ybn56eoJ6em3+cnZ+enJydn5+enZmam52dm5mXm6Cipampqaqop6iopaWoq62ur6+rrLWurqyuq6ahqaelna6tmZGPioSJi4yPkZSWmJqbm5ybm5uampqcnJuamZiWk5qZl5iTkJKUk4yMkIyLlZeVkpmbkpKbmpiYkY6TkpmYlJeXmJmYmZqZhJgWmZmZmJmZmZqamZiXl5iYmJeYmZiXloWYB5mZl5qamZiGmgSZj32XhJgJmZqYmZqZmZqbhZoMm5ubnJ2cnZ2enp6fhZ4BnIWdDJ6dnJqam5ycnJ2amoScZ52bnJucnJuampqZmpucmJiYlpWWlZmWlZaXmJeZmJiWlpGQi4yJhYuOjpOQgYiOlpWVk5OWlZWVlpWTk5GTlJKSkZKRkpOSkpGPkJKQlJGRj46SkZCOh4qOj5CSko+SkpGPkJGQkZGEkmKRkI9/cnx8eXl7X8RxeoaJiIaDhYeJiIqNjo+NjIyNjo2OjoyNj5KSkJKQj46PkJCPj5CSkZGRkI+SkZKTk5KTlpKSkZSSkpOVlZKTjZOUkpKTlZOVlpeUlZeXlpaXl5iWlf9//3//f/9//3//f/9/sH8BftZ/AgIEAIC8vL++vb/Avr29vLy5uru/vb+4u7y9vsHBxMK/v76/v8DAvsLBwsC9v8C+wcC+v8LAv766vL3AwcLFxMfIysnJx8bBv7+6vr69w8jLzsbDvrW3vsrLysbFvrSpqa21uL27ubezs7S1tLa2t7W1uba3t7u3tbCgnJ+lsa2mo6apqICoqKqpqaisq6akoKKlpqKkoKGgoaKioqOjoqCfoJ+foJ+enZyanKCgnp6dn5+foaGfnp6dm5idnZ+foKCfoZ2eoaKin6CgnqGgoKCjo6WjoaKioqSmpaSjoqOio6Sio6OjoqSkpaWhoqOioqWlpKWkpKOjoaCioaGgoJ+fnp6enBicm5ydnJmZmJeXmZeWmJmYmJmZmJiXl5aElQSWlZWWhJUBlIaZM5qbmpqZmZmanJydnJubnZ2dnp6foqGgn6Cgo6KioqOkp6ano6SmqaenqKipqqqqqampqoSpM6eopqaoqKenpaWlo6OhoqKipKKboKGgoKCfn6GgoKCfoJ+hoJ+goKChoJ+foKGhoZ+fn4SgCKGfoJ+fnqChhaCDoYSjgKKipKKjpKWlpKSkpaSlpqamo6OkpaWkpKampKOkpaalpaalo6SkopSipKOko6SkpaWknaanpqWknaSmpqakpaWmpqaloqOioqOho6SinqCgn5ygoJ+fmZ+Yn56dnpyQnqShoaGko6KioJScm56foaGhoqKio6Ojoqekqaurq6qpP6qrq6ipqaekkaanpqeiqK2qqqqop6Snp6eqp6Wdpaegp6utrKenqa+ura2oqa2rrq6vrKuop6anpaSjpaSko4SkTaOfnp2foqGfo6KmqqmrrrCusK+vsLKzsbOstrWwsbKzs7OvtLS1trWxtbOyr7KxsrGuqqmxra6xsrOxr7Cssa2xrquqtrm5uLeyrY6ihLIRrrGyqrGwrq+ura2ura2srKyFqwWqq6uqqoaph6uArKyuq6uqrq6ur66trq6vsbCxs7S1t7a2uLq5ubq5uru7urm6urq5uLm5uri4uLq7v769v8DDw8LAwsbGx8bHxMPFxsjIxsXCxcXExL++v77AwL/Dw8LEw8TCxcLCwcC+wL+9vb3Bvr+9ubKhpKu2ubu3tbW+vre3q7u6urm0tLsZubO4ube6uri2trSwtLCws7KxsLSytLOzsoSzE7KxsbG1tLOxsLKys7Sys7W1tLSEsmOrrqynopKHhYWLhpySlZSepaaorKqno6empqalpaWnpaeoqKinpqSjpKWkpqSlpqaop6qpqKimp6emqKmop6urra2sr6+ysbKvsLK2sbS4uLi7ub28u729wcDAxcLBwcDBvr2AqquuraqsrKuqq6qqqKqqra2uqKmqrK2urq+wraysrautra6vsa+vrq6urLCurrCusK6urK6trbCxtLO1tbi0t7S0sKyvra+rrLK1uLqys62oqKqytbWxsaumnp2hpqipqaqppaWlpKWmpaWkpqimpaWnpaKglJKWmaKdl5aYnJsVm5ycnJubnp2ampeZmZmXmJaXlZaZhJhQl5aXl5aXl5eWlZWVlJaXlpeWl5eXlpiYl5aWlJGVk5iXl5aWl5aVlpaXlZeXlZeWl5iamJqZmJeWl5iZmZmYlpmZmJiYmZiYl5iYmZiXl5iFlwyamJmYmJiXmJaYlpaFlYaUE5GRkZKRkZKSk5OSk5STk5KSkpOEkQ2QkZGSkZCQkY+TlJOTiZQflZWVlpeWlpWXl5eYl5eZmJiZmZiZmZuamp2cnJmbnYWeBJ+foKCEn4KehJ0Em5ydnYScGJqZm5mZmZiZmZSYmZiZmJiXmJiZmpmamIeZFJqZmpubmZubm5qamZmYmZiZmpmZhpoJm5ucnJucnJ6ehZ0Kn5+enp6fn5+en4qeFJ2enp2enp2dnZydnZmNmp2cnZychJ0Slp+en56dlp2fnp6en52dnp6ehJ0RnJqcm5qYmpmXlpmYm5qTmpKEmYCXi5icm5yenp6dnJuPmJiYmZycmpucnJ2cnZyfnqCioqGhoqOjoaCgn56cip2cn6Cdn6OjoaCgoJ6gn56fnJ2VnqCXoKOlpKCio6inpKOhoqWkqKinpaSjoKGhoaCenp2cnZ6foKCcm5qbnJ6cm5+eoKSlpqioqamop6qqq6utpTutq6apq6yrq6Wrraurqaaqp6mjpaaoqKOinqOio6ampaampqOno6ekoKCqra6traeliJmpqaiopaemooSmKKWlpaSlpqWlpaSlpaSko6Sko6OjpKOkpKSlpKWkpaWmpKWnp6emqKiEpweop6anp6qrhKwPra6vr66wr6+vrq6vr66uhK1vrq2trq6vsbCwsrO0s7WztLS1t7e4trW2ubi5uLa1t7e1trW0srGysbG0tbO0s7WztbO0tLKwsLGwsLGysrOyrqiYl56qrqysqqutraaqoa6tr62qqKysqKytra2sramqqKWoo6Wnp6elp6aop6emhKcLqKalp6alpqWlpqWEph+nqKenp6ilpqGko56ZjIB9f4R8iIaKjZacnZ6fnpuXhZwXm5yfnqCfoKCfn5+cnJ2dnpydn5+goKCEnyygoKChoaCfoaCipaKkpaWlpqKkpaekp6urqqurrKytsK2wr7Cxr7Cwrq+uqzOSkJOUkpOVk5KSkZOSkJCSkZOPkZKRk5KUlJWSkpOTkZORkpWVk5ORkpORk5KTlJOUlJSEkweVlZeVlZWYhJdqlZSUkZORj5OVlZiUlZKPjpCUk5OQkpGNi4uMjo2PkI+PjoyMjY2Nj46MjY+MjYyPjYuJg4KFhYmDgIGEiIeIiYqIh4iKiYiJiIiHh4iGh4aGiYeIiYmIiIaIiIiJiYeGhoeGhYeIhoeHh4WIFoeIiIWFhYSHhoeHhoeFhYaEhYSFhoWEh1CIiIiHhoaGh4aHh4mIhYeHhoaHh4eGg4WHh4eGhoeGhoiGhIeHiIiIh4WIhYeGiIeGhoaHhoeHh4iIhoSEhYaFhoaHhoaHiIeJiIiHiIWGh4SGB4iHhYaGhIaEhy+GhoiGhoeIiIiHiYiIh4iIiYiJiYiJiYqKiouJi4uMjI2OjY6Ki42OjY6Qj4+QkYSQB5GQkI6OjI2HjASLi4uNhIsxjIuIi4uKjIuKiouMjIuLjIuMjIyOj5COjY6PkJCPkI+Qj4+OkJGQkJGRkZCRkI+QkYWQhJETkpKUk5OTlJWTlJSTlJWUlJWUlYSUBpWUlZSUloWUgJWUlJKOhY6RkZKRk5OUlJSMlZaVlZSOk5OTlZWVlJaXlZWVk5KSk5KTkpGOkpOQjZOQk5OLj4yTkZGTj4KRlZSUlZSUk5OSh4+PkpKUk5KVlJSSlJSVlZWXlpeXl5iZl5eVlZaWk4KYl5aXkpWYl5eXlJSUmJaUmJOWipOWjpWYDZqXk5eampmYmJeYmpiEmwWamJeXloSVb5SUlZSUlpaUkJGPkpSRkZSSlJeXmZucm5qbm5ydnJ2dlZ2emJmcnZ2dmJycmpqYmJqZmJaXlZeWk5GNkpKSlJWWlJOUkZWTlpSPkJqdnp2dmpV5ipmYmpqWmZmUmpmZmJiYmpqcm5mZmZiYl5mal4SYB5eYl5eZmZqHmSWampmZmJqbmpqbmJmYmJiXmJmZmJmampqbm5ucmpqbm5uamZmZhZgDmZmYhpsympubm5qbnZ6enZ6cmZmanZybnJyam5ybmpmampmYmZmampuZmZiXmJiYl5iZmZiYl5iElz2Wg4CHkpaUlJWVk46QlI2WlpWVj4yTlZOTlJOUlJSSk5OPkY+QkZKRkZKQkZCPjY6Oj5CQj46QkI+QkZCRhZBrkZGSkZGSkI6Kjo+LiHtubW5zaG90eX2Ei4yNjI2IhYyKioyLi42PjpGRkI+PkI6OkJCQj4+PkpGPkJCPj4+QkJGQkJCNjY+OkpGOkZGSkpCPj5CRjZGVlZSUk5STlJaVlpSWlpSVlZSVk5H/f/9//3//f/9//3//f/9/iH8CAgQAZb+9vb25ury8u7u3uLu9vr65ubq8u7+/wMDAvr++wcDExMDAwcPBv728v8C/vr27ur7AwMDCxMTIw8bExcjEwsLBvbu5ubu+wsTFxMDAuLu2wMjX2NDW0cnFu7m3u724tLC1tba1hLZfuLK0s7S1ubq2srGknqimqKyspp+kpKapq6mrqKipqaelp6anpKOgnZueo6GhoaOhn6CgoKGgn52enZ2doKCgn5+fnaCfnp+en5ycnZebnp+goJyen5+fop6gnqCfoKCEoQiio6Gho6Oio4WiVKGhoKOkoqKfoKSloqCgoaChoKGgn6GhoJ+fnqGhoZ+gnp+dnpybnZycm5ybnJiWmZeamJmZm5qXmZiZl5WWlZOUlZaVlpOWlZaXl5iXl5eZmpmZmYSaSZuam5qcm5ycnp6fn5+goaGioaGho6SkpKKkpKOlpKakpKSlpaanpqWlp6qpqaqsq6upqaipqKinp6empaSjpKOhoqKioaGioZ+EoAGhhKADnqCghJ8Ynp2fn5+goaGgoaGgoKGgn6Cfnp+goKCfhaAboaGgoKKjoaGhoqOhoqOkpqWkpKSlpqalpaamhaUGpKWlpKWlhqcBpoWlMqalpaenpaWlpqalo6OmpaampqWlp6elpqSkpaSmo6OmpaOimJygnZ6dn52ZmJ+fmpeahKEmoKCWoqOYlZmfmqWmpqajpJyfo6Kjnp+kpqanqaeprK6srq+xr7CEshyxsK6xsbOzsrGvs7SvsrKzsrCxs7KxrrCxr7GyhbR+trWzs7KwtLKwtLSzsLKztLK0tba2t7e1tLG0s6q0pLKwta+vs7CvsrKsr6qksba0s66itLOxtrW3tLGtn7KzsqypmaOyr66fp6isrqassJ+nsbGqo7S1tbCvtbW1tLSys7GwsbGwr7CwsrKxr62usLCurK2uraysq6usrKuphKoOq6uqqqurrayrq6ysrKuErRqur6+ur6+wrq6wsrGysbKxs7W2trW4t7e3uIS5hbsEvLu8vIS9Hby9v8C/wsPDxMTDwMHBv8PExMTFxcPBw8LBwL/AhL+Avb29v8C+v8C+vr29v8HAvr/Bv8DBwb++vbi5vL29uLq3rq2+v7ayt7e5uLOpq6yut7q5uLe3trW0tLa3trazsrOztLa0saGyt7eysLS0tbS2tLGysK+wtLOysrWysrCxsK+wrKypp6Kfm5WLlZqanqGhoKalpqeloqampaSmpKdGqaanpaejoaKioqOioZyjpKWmoqShoaSlo6anqKmopqioqaqtrq2srq+xs7Oysra1ubq7urzAvr6/vr/AvLm8vb+9vb6+wBisraysp6mqq6mnp6apqaqpqaiqq6qurq2Erkivrq6ysK+ur6+vra2rra+wraytrK2vr7Gys7G2s7SztLW1sbCwrKuqrK2vr7OzsrCvqq2nr7K7vLi9ubSwq6qpq6yopqSnp6eEpT+kpqKjo6OkpqelpKOYk5uZmZ6dmJKWmJicnJydnJucnJmam5qbmpiYlpSVl5aYlpaWlZaWlpeXl5aWk5OUlpaEmISVFpaWl5WUlpKTlpeXl5WVlpaUlpSXlpeEliGXmJiYmpiYl5iXmZiYl5iXlpaWmJqYmJWUl5iXlpaXlpeFlROWlpWVlJiYl5eVlZaVlZSTlZSUhZMFkpGRkZCEk0GSkpKTkpCRkZKRkZGQkY+RkZKSkpSUlJOUlJOSkpSTlJSTlJWVlpSWlpeWl5iYl5iam5mYmJmamJuampybm5ydnIedB5ycnZ+foKCEn4SeBp2cnZ2dm4SaCZmZl5mYmZiYmYWYAZeEmA2XmZiYl5eXmZiZmZiYhJkampqZmZiampqZmZqamZqam5ucnJubm5ycnJ2EnIKdhZ4Ln5+fnp6fnp+goKCGngidnZ+enZ6enoWdB56enZ2enp2GnmidnZ2fnZ6enZ2dnp6en5+dnZydnJybm5CUmJeZl5iXlJSZm5iUkpqbmZqampGbnJSRk5iUnp+enpyelpecm5uUmJyfn56fn6KipKOjpKWnp6Wmp6iop6SoqKmopqanqqekp6epqaioqISneqinqKmpqaurqaurqqqpqKyqqKusqqmpq6qoqKqtrK6urausrKyjqpypp6unp6uoqqmpo6iknKusq6qkl6ipqayrq6qpo5epqaegoo6ZpqanmJ6goqWboaWWnaamo5ioqamlpaqrq6qrqqmqqqmpqaaoqKenp6amp6enhKYHpaWkpaSlpYWkCaWlpqanp6ampYSmDaelpqanpqinqKiop6eEqIKphKoBrIWtAa6GrYSuKq+vr7CxsbCwsrGxsrKztLSztLSys7S0s7W0tra1trW0tLSzsbGysbCwsYSwSrKwsLKxr6+wsLOxsbKys7O0srKxsLCvsLCvra6tpaausKuprausrKOeoqOlq62trKmpqquop6mpqKqoqKmopqmnpJelqqmnpqimhKdXpqalpaSmp6amp6WmpKSjoqOhoJ6el5SRjoWOkI+RmZmXnpycnpuXnJ6cm5ybnaCen52gnJydnZ2em5qYnZydnpucm5udn52fn5+iop+hoJ6goaKioqOjhKYZp6inq62trKyvra2urq6trKeqq62srK2srSKSkJOVkJCSkY+Oj4+RkJCPkI+OkI2QkpGSk5STkpOTlpWShJMKkpKRk5SSkZOUkoSTV5SUlJiWlZWVlpiVlJWRkZCQkZCRk5WTkpOPkY6QkpiXlZeVk5CRkZCQko+OjI2Njo6OjYuKjoqLjIuMj4+NiYmDg4mFhYiHgnyDgoWJioeJiIeIiYiHiISHXoWIhYaHhoeFh4aEhoaHh4eIh4WFhIaHhIaGiYmGhoaEhoeHhoaGgoaGiIeIhYWHhoaHg4SDhoWEhYaIiIiHh4aHhoeGiIaGhYWGhoSEhomHh4WEhYWEhYSFhIeGhoWEhhuHh4aHhoeGhYaIh4aFhIaFhoWGh4eGhYWFhIOEhgSHiIiJhIhUhoeGhoWGhIiGhYeHiIeIh4iHh4aFhYaIh4eHiIeHiIiKi4qKiomJiouLiouLioqJi4qMjYuNjIyNjo6Pj5CRj42Oj5CRk5KRkI+Ojo6NjIuLjYyMhIskjIyMi4uKjIyKjIuLioqLioyMjY2MjYyNjIyNjo2Mjo+PkJCQhI+DkISPC5CRkJCRkI+PkJCRhJCEkxiRkZKSk5SUk5OUlJaUk5SVlJOUlZaWlpeElgiVlZWUlJOSlIWTFJSVlZSTlJSTkpKRkpOUk5WVlZaVhZQlk5SSlJOSkZKGipGPkY6Qj4yLkJCNi4qQkpGTlJKJk5OLiYqPjISWH5WVjo+Tk5KMkZWWk5WXlpiZmJiZmpucnJ2cnZybmpmEnAaZmpufnZqEnEabnJ2bmpqbm5qbnJ2dnp6eoJ+cnJqbnp2bnZ6bnJ2enZydnZ6eoKCfnZyfnZSekJuanZianZubmpqWmpaPmZuZmZaImJeWhJotmZOKl5iXk5GDi5aUlIiNjY+ShY+UiI2VlZGIl5iZlJSanJybm5uam5qbmpqahJkbm5mYmpuZmZiZmJmYl5mYmJqZl5iYmZiZmpqahJuDmoWZLpqanJubm5qamZmXlpeWl5eXlpeYmZqYmZmYl5iZmJmYmZmamZmam5ycnZuanJ2EnAibmpucm5yamYWbBpqam5qbnISZA5eYmIWZNJeXlpeZl5aXl5iampqYmZmZl5mZl5eXlJiXmJiXl5GQkZWSkJaUlZKNiYmNkJOTk5WUk5OEkRSSkpKRkpORkJGMi4SMkY+Oj4+Oj4aQg4+HkGeRkY6NjIuLioSAf310fH96fYaHhYyKioyIhYqMi4qMjI6Qj46Nj42Mjo6NjY6Nio+Ojo+MjoyNjo6Njo+PkpCNj46Kj4+Qjo+PkZKRkpGTk5KUlJaVlJeVlJaVk5STkZSTlJGTkpKT/3//f/9//3//f/9//3//f4h/AgIEAIC4uLW2tri2trq7vLq5ubi5uLm7vby+vr+8vru+vr68wr29ubm4trm6ure5tbe3t7y+wb6+w8LFxsLHxL6/vLu8vb67vrq6tsLEwb68vL7Ew8rNz87BvLWusbK1tLKysbe3urm5u7u4t7e4tLa0s7SxsrOmmZmnr7SpqKmkpKSnpSerq6qrqqqpqamnpaSgnqCfnKCho6CioJ+dnZ2emp+enZ2am5ybnZ2EniOfnpycm52dmpyanJyfn56cnJyenJ6fnp+boJ+ho6GioZ+hoYWiLKOho6Kjn6Oho6KipKGioaSkop+io6GfoqGgn6Khn6GfnZ6enp+cn5+dnJudhZwXm5uZlpiWl5qYl5iYmJeXlZaUlJSTk5aElAWTlJeVl4SWHpeTmZiZmpucmZiam52em52fnZydnZ6en5+hoaGgoYWjM6KhoqWmo6SkpaWkp6alpKSopqeop6mpp6epp6enqainpqanp6ako6OkpKOioZ+foaKgoIWhFJ+foKCen6Genp2foKGhoaOhoJ+hh5+CnoSfAaCEoQSio6KjhKQFo6OjpKWEpAKipISlH6anpqenp6mnqKampqenp6mpp6ipqamoqKeoqKeop6aGp3yop6akpKSjo6SkpaWlo6Kjo6SjpaWlpKGkpaSgo6OjoaCTn6CgoaCgn5+eoJ2Xo6GcopmXoJ+iopCepqanoKaoqKirqqusqqmrqKqmp6Onsa+ur6+wr66yra6yraaoq663tbOnsrS1uLG4tbSzsLKwt7a4t7S2uLm5uLO0hLaAtbG3tLKsra+rs6qzsa+xrbS0sraytrazqqqPqK+wsamuqaeysqWvpaqyq6awsq2oppatsK6boaWyr7CzsbOwsbGvsrOxsLCxra2xs7Ovs7OzsbGwsK+wsbGvr7CxsbGwra6uraysrKqqrKyrqamnqKmqqaqqqqmqqqmpqqusrK0mrKyrqqqsq6yrrK2srK6usbGwrrCvsbKztLS0s7W2uLe4uLm6vLyGu4C8vL2+vb6/vb/BwL++v7+/wcHBw8C/v8HAwcLBwL++vsDBwMC+vb7Bwb6/wcPBwL+/v8DBwr68vLy7v728u7y7urq4urm2t7W1tbSwr6artreyrrS2wLerpbO2tLO0trW0tLa0trO0s7KytLu3rq+xtbi0sbO1srKys7GztrewsEW1tbKytLKwr7GvsK6uqaejnJuipqusqKulrK6pqqqqqaWmpaWlpqWlo6OloqGnpqOio6KipqSjoZ+hoqOkpKKmpqanqKaEpyakp6msq6qqrK+tr7K1s7e4uLy4uru5u7y7vb2+vL2/vsC9wL26umqoqaWmpqinp6moqainpqipqKmqqqisq6yqrausrauqr6qrqKqppqmqqqiqqKmmqKutrq6vsq+ztLG0sq6wra2urqyqrqytp66zr6ysq6uxsba2t7euqqajo6SnpaWkpaimp6ioqqinpKWlhKQXpaGho5qOkJedo5ubnJWXl5mZnZ2dnp2GmwuZlZaYlZOVlpiZmISVJJSVk5WVlJSUkpOSlZWVlpWWlZWUlZOVlJOUlZaVlZWUlZSTlYaWLpiWlpiXmJeVmJiZmZiXmJiVl5eYlZmWl5iYmpWWmJeYl5aXl5iWl5aVlZaWlJSFlRiXlJaWlJOTlJSTk5KTlJOTk5KRkpKRkJCEkReQkZCPkI+RkpGQkJCPkZGQlJKSk5OTkIWUBZWTkpOUhJU+l5aWl5aWlpmZmZiXmJmampuamZuam5ucm52cnJ2cnp2cnJydnp+fnp6dnp2dnp6dnp6dnJ2cm5ubmZmZmJiFmSOYmJmXl5iYmJmZmpmYmJmZmZeXmJmYmJqZmpqamZqam5uamYSaA5uamoSbB5ydnJ2enZyEnQOcnZyEnRaenp2enZ2enp+fn6CfoKCfn56fn6CfhaCFn4SeB5+fnp2enp+EnoSdgJ6dnJudnp6cnZydnZ2cmZycnZqbm5uZmo6anJuYm5uYmpmZlo+bm5ebk5ObmJqaipidnqCZnZ+gn6CgoaGgoaKhoaChnZ+mpKOjo6Wlpaijpaikm5+io6mrqp+oqqurpqyqqauop6arqqysqauuq6yuq6ytrKurq6qsq6mlpKahNqqgqqioqaSrq6erpqqrqqGig52nqKmfpZ+gqKmepp6iq6SfqKmjn5uQo6mnkpqdpqWmpqWmp4emGKWloqSnqKmlqqmpqqqopqWnqamoqKinqIWmDKWlpKWlpKSlpKOjo4WkhKUSpqanpaamp6enpqenpqempqaohqcHqKinqKepqYSrDaqqq6ysrK2ura6vrq6Fr4WwBq+vsa+wsYSyDbSysrSysrOzs7Gzs7OFshixsbGvsLCwr6+ysbGxsrCxs7S0sa+wsK2Er2aur7Cxrq6tq6ytqauppqSbnqqrqaSrq7Grop2oqamop6iqqqmpqainqKanpqarp6Kkpaeop6enpqWnpaWmpqmppqenp6Wmp6alpKSjo6OinZqYkJGYm6Cgm5qboaCeoKCgnZudnZ6EnSKbm52cmJ2cnJucnZyfnpybmpucnJucmp2fn56fnp+gn6CfhKAMoaGioqGlp6mnp6iphaoDq6unhKsJrK6srautq6irZ5CPjpGNjo+QkI+Pjo+Pjo+OjpGSjpGQkpCTkZOSkZCTkZCQk4+PkI+QkZKPkI+RkZKTk5SUk5eVk5iWkpSSkZOSk5GRkJCPk5KSkZCPj5OSk5OVlY6QjY2MjI6Njo2Mj46Pj4+Qjo6EjDONjIyMi4uLhX5/hoiHh4iKgoKFg4SKiomKiIeGiIiJiIeBhYaGg4aGhoSFh4aFh4WHhYeEhnSFh4WGhoaHh4mFhoSGhoiGhYWFhoaFh4aGhYSGhYaHh4eEhIODhoSHiIaIh4eIh4aGiIaIhYWEh4SFhoaJhoSGhYaFhIWFhoSHh4aFhoWFiIaFhoWHh4WGh4aEhYaGhIaEhoeHhoaGg4OFhYWEhoeEhoSGh4SGLYiHhYWGhYWEhIeHiIiHh4OHiIiHh4eFhISGiImIiImJioqJiIiIiYqKi4uNi4iKA4uKjISNAY6FjyCOjpCMjo6Pj5COjY2Pj46OjYyLjIyOjYyLjIyLi4uNjISLD4yNjY2MjI6OjpCOjYyOjoePDZCOj5CPkJCPkJCRkZCFkROQkJGRkJGSk5GRkZKRk5KTlJORhZIHk5OSk5SVloWViJYMlZSVlZSUlJOUlZWVhJQnk5SUlZOUk5STlJSVlJSVlZWUlJWVlJKSkJGVlJGSkZGSkoWOj4+MhJCAjo6LiZGQi5CIiI6NkJCBjJSUlI+Tk5WVlZSWl5WXmJaWlJSQk5mZl5mYmZiYmpaYmpWRlJaYm5ubkpucnZ6Xnp2bmpiamZ2cnp6cn6CenZ6cmp6dnZ2bmp2cnJWWmJWck5ubmZuYm5uYnJidnJqRlHOPl5ibk5iPkZiZjpeTkpktlZCXmZSSjYKTlpaGiY6WkpSSlZWTlJOSk5aWlZSVk5GWl5iVmpubm5qamZiZhJopmZmYl5iYmJeXmJiXmJiYmpiXmJiYmZmYmJmZmpubm5qcm5qbmpqamZqFmRybmZeWlpaXmJaVl5aXl5iYlpWWl5eYl5eYmJiZh5oEmZubm4WahJuAnJuampmampiZmpmYl5mZmZiXl5iYmJmZmJmcm5aVlpeXmJiYmpqbmZiZmpiVlpeVlpeVlZWWl5WUlZSUlJOQkYmHkZGTjpCSlZCPipKTkZCRk5KRkpKSk5GRj5GQkJCJiI2PkJOQj5CQj4+Pjo6PkpOPkZKSj4+RkY6Pj46OjY1IiIiDe3yFhYqLiIGHjYqLjY2MiomMiouLjY6PjI6OjIuOjYyMjYyNkY+PjYyLjI2Pjo2Oj5CPkY6Oj42OjI6OjY6Nj46Oj5CRhZIVk5KTlZOSlJGSkZOTk5SSk5GWko+S/3//f/9//3//f/9//3//f4h/AgIEAIC2t7e6ubi3u7m3uLq6u7u4trm4u7u5uru7ury+vry6vL26ureztrO3t7a0ubu6vMG+vsPBwcHDwr+9vL25uLS3uLu7vMG9vb3Dv8LCwcfDzcfEwLm2t7e0s7O5tbS2t7q5ubi4ubi2treysq+0sq6vsa6Yl5qfoq2sqqiqqaWipU6mp6Wnp6iqqaqppqaioKKgoKKfnpubmJqcnJ+cnZ2dnpyampibmZyem5+fnp+dnJubnZqbmpqcnJ6cnZycnJ2en56emZugoKKioJ6foJ+EoYCioqGjo6KfoqChoaChoKOhoqSkoaKjoaCgnZ+goJ+dnqGfnp+enZ2dm5qcm5uZmZqcmpmXlpaYlpaZl5eWlJSSlJOTkpOTlJSUk5OTkpOVmJaUlJWWl5iYmZmampqZmpmcm5ydnJycnp2dnp6foKOkoqGhoqKjpKSjpaWkpaWlpgumpaamp6anqaenpoSoBKmmpqaEpzqop6enpqelo6OioqKko6GgoaGgoaGhoKChoqGgoqKhoaGjn6CgoaGgoKKhnKCgn56foKGhoJ6en6CfhaEloqKkoqSko6OjpKSkpaalpaalpKSmpaWmqKelpqenqKanqKemqYWnGqipqKipqKinqKmpqKipqaenpqiop6inpqWlhKQFpaSjoKKEo4CgoKOio6OkpaampKSko6Oio5+hnpGioqGhoKGfnJOgn6Sco6Ofm6Wbn6COp5yap6uspJWdpqaaoKyrq6ymq66jpK+zsa+urZmtsqSqoraxs6Spsbm5t7KqsbCtrq6vtLOtrLW2tLOysKmztba0s7ays7KztLOysbGwrKmwrayztAG1hLOAsa2ppaWsrqutsLKvqqiprKmjrbKysZ6ksK+vpq+wsbOysbGxr66ysa+uqrGwsLGvra+vsLCwr7KysrGxsLCysa+vrq6vsK+tra2srK2sq6urqqupqqqsrKuqq6uqqausq6qsrq6ur6+ura2rq62tq62sr66usrKzsbGwsLCxtLYTtbSzsrS1tbq5ubm4ubu5ubm4u4S6dbu6vb2+vb68vsDBwMLBv8G/wcHCwcK9wL6/wMHBwMPEwb3CwcTFxMPBwcC9wL28vLq5urm6u7u8vLq5ubi2ubm3tbWztLW0tba1tbe3tLezsa+zsbKzsrCura+ysa+zs7Kxsaytsba0qZynrLCwsa6vs7O0s4W0abOys7Kurq+usK+wrKusqqanoaGen5OYo6SoppydlIqXp6aqq6uoqaqopqOkpKKhnqKjoqOioKKlpaeopaWmp6Wnpqepp6anqqqnqaqpqamtrq2wsrSws7K0srK0tri3uLi7uru7v8C6vYS5BLW0t7QLp6enqKiop6uopqeFqHWmqKiqqKepqqmpq6usq6qpqqenpqWnpqinqKiprKqsr62vsbCxrrGysK6srquqqKmrrK2rr6+srK6usLGvsbG4s7KvqaempKWlpailpaWoqainp6mop6alpqKjoKKioaGgoI6Nj5OWnJ6dmZucmJaampmZmpyEm1ecm5qYlpiXlpeWlJOUkpKTk5WVlJSUlpWTk5KTlJWVlJWYlZaWlZOTlZOVlJSUk5eVlpWUlZWUl5aVkpSWlpeXlpWVlpaXmJiXmJiXmZmXlZeWl5aVlZWFl1qWl5aWlpWRlZWWlpWUlZaVlpWUlJWUk5STk5KSlJSSkpCRkpGRkJGRkI+QkI+Qj5CPjo2QkJCPkZCPkJGSkI+PkZOUlJKSk5SUlJWUlZWVlJSUlZWWlpWXl5eEmgGbhJoUm5qanJybm5ucnJ2cnZ2dnJydnJyKngGdhJw7nZybnJybnJqZmZiZmZmYmJeYmZeXmZmYmZiZmZqZmZyamZmamZmZmpqXmpmZmpuam5uampmampqbmpqEm4SdB5ydnJ2dnp6FnYSegp2Engyfnp+en5+goKCfn5+GoAOhoJ+FoIWfg6CInweenZ2cnZudhJwEmZqcm4ScAp2ehJ0Jnp2dmJyYiJmbhJpImJeNmZqdl5ybmJWdkpeahp6Tk52foZuOlp6bkZeioqKjnKCinJulpqSko6CQo6mboZWopqeZn6arq6mkn6inoaaloqioo6OrhKoZp6Gpq6urqaqpqqqqq6irqqeopKGopqOprISrPKinpaCbnKWno6OkpqOhoKGkopykqKinl5qmqKiepqiop6iop6ampKampqOepqalpaampqeopqenqKipqISnAqanhaaEpQampaalpaaFpQGkhaUCpKWFphmlpqanqKiop6elpaempqemp6amqKipqaiohKmEqkepqaqsrK2sraysra2ur66trq6tra6urq2vrq+vs7WzsrOys7Oxs7OysrKxsrKzs7Oys7S0srCys7O1s7OxsrKwsq+vr66srYSsQK2tr66urKusrK2rrKyqq6qrq6uqrayoq6mmpaimp6iopaOkpaamo6aop6alo6OlqaOclJ+jpaOkpKSmpqelp6aGpx+lo6Oko6SioqCgoZ+em5eWlZiNkJiZnZ2UkYyEjZ2chZ81oJ6enZycm5qWmp6enZubnJ6dnp+dnp2enp+en6Cfn5+gn52goaCgoKGio6OlqKanqKimpqaFqBCqqausrauorKupqKekpaemgI+Pj5CPj4+TkJCPj46PkI6Nj5COjpGRj4+Oj5CQkZCRk5CRkIyPjpGQkI+QkpCRlJCTlpWTk5WWkpORkpKRj5CRj4+PkpOSkJKRk5SSlZOZk5GPjI6Ojo2MipCPjo6NkI6Oj5CQjYuMj4mKiYyLiYqIiX1+gYSFhYiIhoaGg4KGEIiHhoaGh4eIiIiHhoWFhoeEhg6EhISChIWHhYSFhYeGhoaFEYaGiIaHh4aFhYeGh4WEhISGh4VIhoaGg4KFhYWGhIWGh4aFh4eGh4eEh4mGg4aFhYSFhoWDhIWGhoWHhoaFhn+FhYWGhIWGhYWGhoWEhYWEhoeGhoSEhYWGhYWFhoRLg4SDgoSEhoSDgYSFh4aGhYSFhYaDgoSHh4iJiIWIiIeIh4eGh4eIh4iIh4eHiYuKiYqLi4uMjIyLiouLiouLioqLi4yMi4yNjo6NhY8tkI6Nj42OkZCPjY2Pj4+Njo2NjI2Ji42MjYyMjIqLi4yLioyNjY6Mjo6NjpCRhY+EkAqNj5CQj5CPkI+PhJAQkZKRkJCRkZGSkJCRkZKRkoSRApKTh5IFkZKTkZGFkyGUk5OVlZWUk5SVlZWUlJSWlZWVlJWWlpWWl5aXlpaVlZaElICSkZOSko6RkpOTk4+SkZKUk5OUlZaQk5OSkpKTj5KLfpGRkZCQj46NhZCPko6SkY6MlImNjnuSh4WQkpSOgoyRkIWMlJSUlY6TmJKPl5iWl5SRhJSZj5KGl5eZjpCUnZ6bl5KXl5SVlpSZmJWVm5qampmYk5qcnJybm5qam5ycm0ucm5iZlpKZmZSbnJybnJuamZiTkI+Wl5WUlpiVlJGRkpKQlZaWloiMlZWVjpOVlpaVlpWVk5OUk5KSj5OVlZaWlZaWmJiYmpqZmJeHmASXl5iXhpiEmQ6YmZiYmJeYmpuamZqbm4SaBJubm5yFmmSZmJmZmJmXmJaWl5aYlpaXl5aUlZaWlZiXmJiZmZiXl5iXmZqZmJmZl5iXl5iYmZiZmZuampuamJmam5uZmZmYlpeXmZuZmZuam5ybmpiamJmYmZiYmZmXmZmZmJWUlpSVl5WUhJYDlJOVhJQvkpOVlJKUlJOVlZGTkpCPkZGRkpGQjpGQkJKQkZKRkI6Mjo+Ph4iDi4yOjpCNjpCEj4WQRJKRj4yOj42Njo6LjI6JhoSDhIKHfH6Fh4eIgYF9dn6Lio6NjYyNjo+OjI6PjIyHi4yOj46Njo+Pjo+NjpGQkJCPjpCPhI4ojJCQj46OkJCQkZGSkJCQkpGRkZCSk5OQkpKSkJOSj5OSkJGPjpCPjf9//3//f/9//3//f/9//3+IfwICBAAXtbW3trS1tbS1tre2tbW4uLq7vLq4treEuoC7u7y4tba1trWztLG1tre5vcG+v727uby/wMHAv7y8ure6u7m4t7W4uba5tri6wcLBv8LBwb28tbW5trKysLG1t7KzsrW0tbe2tLWwtLG0r7SxsbGurKuxra+rsLWqqKWop6SjpaalqaeoqaqpqKmmpKCfnZ+foJ+in5ydnJubnUOcnJ2bmZmXmpudn5ycnZ2anJuam5ybm5qZnZ2enZ2cnp2dn5yenZ2fnZudnp+gn6GioKGho6GioqOgoqCgoZ+hn6KghKEVop+eoKGhoKCgn5+eoKCdn5+gnpydhZyAmZqamJuZl5iVl5aYl5WWlZSWlpOTkpOSlJSTk5SUkZCSk5GTlZWUk5WWl5aXmZqampmam5qam5ybm5qcnZ2enZ+en6Cio6GgoqKhpKWkpKWmpKWmpKampaamqKioqamop6inqKmop6enqKampqinpqakpKKjpKSkpaWkoqOio6ANoKGio6GhoaCgoKGgoISfG6CfnqCgoaChn6CfnqGio6Ggn56fnp+goaCgoIShEKKho6SkpaakpaSlpKeko6aFpRmkp6empaempqenpqaoqKempqiopqWmp6amhakEqKenqIanA6ampYWjgKSjo6Oho6OjpaSjpKOioqGhoaCioaOjoqGjpKSmpaSjo6Ken6GgoqGimqGjoqWkpaaflaWnpqCrj4WfnqSiq6Krq62uqZ2trpeosbOnrbOzs6+ztbe6tKOvtbK3tLaus7Kwp620q6+tr7G1r6ejsbWosba2qLKzrqqko6Wyp6etZ6quqbCtsaasrKKtsqqqq66ys7GxsrO0pa2xsrOrrbKtprSxrJCusbO0r6uxobKzs7OxsbKxr7CwsLGyr6+ur6+vsbKxsrKxsLCysLCvrq6sra6ur66tq6ytraurqaepqauqqqysq6qFq4CsrKyurqytrK6vr6+urK2trrCtra2ssbCur6+wsbGysre2trO1tre0t7i5ube2tre5ubq7u7u6u7u9u7u9vsDAwL6/v8HCwcHBwr/CxMLAwMDBwsPCwMDBwsHBwMC6vb2/vry9vby8vL29u7y6vLq6t7a2tre3uLe5ubq4t7m4uoC4urq4urq5t7e2sK6zt7Ozs7Kxrq2tsbGwraqdlqGppqSpqKmura6usLGxrrCxsbKzr66trrCwsLKzs7Cvra+vrqyrrKepqKiop6qqsLGgpJ+YpKCjpZ+jpaWioqKjn6KjoqGhoqKjoaSko6ampKKio6OlpKWjpKKjpaWmqaWnqCOpraqpq66trq+xsK+vsbCzs7W5uLe5uLu9ube3uLe2uLm5tkykpaakpKWkpaalpaSjpKanqKipqammpqmpp6mqq6yopKelpqWkpaOmpqqsqq+rrqyqq6+ur7Ctsa2trKutrKuqqaerq6itqaursbKwhK+Aq6yno6imo6OkpKWnpKWipaWjqKampqKko6ShpKGjpKCdnaGioJ2gop6cmJubmZibm5ibmZ2cm5uanZqalpWTlpSVlpWVlJWTk5KTkZKUlJOUk5STlJaVlZWWlJOTlJWWlZOSk5aUlZaVlZWUlJWUlZWVlJORk5SWmJWWl5aYlpgel5iYmJaXlZaYlpeWlpWWlpSTl5STlpWWlZWVlJOUhZUIlpSTlJSTk5OEkjaRkpGQj46QkJKRkJCPkJGQkI+Oj4+Qj4+QkJCPjo6Oj4+PkJCPkJCRkZKTk5OUk5OUkpOUlZSElQiUlZWXl5aYmYSaF5mam5uam5ubmpqbmpybm52cnZ2cnZ2dhJ6EnQycnZybnJ2cnJ2bm5qEmxOampqZmpiYmJqZmZmamZmYmZmahZkQmpmbmpqZm5qamZqam5qam4iahpuEnISdh54DnZ2fh54LnZ2dnp6dnZ2enp+GoIWfBaGgoaCghp8BoIefEZ6fn56enp2dnZycnp+enZychJuAnJybnJqcnJ2bnJ6dnZybnZycmZqbmpmZmpOanJyenZ2fl42eoZ6ZoYiBlpWXlp+aoaCiop+VoqKMnqWnnKKlpqimqKiqramapqmmqKipoqimp52gqaOjo6epq6ejm6ipnqaqq6GoqKaimpycqKSgo5+joKejqJ2joJqnqaOkpaUfp6mpqaioqZ2jp6mooaWoo5yqp6OIpKeoqKWgpZaoqIWnEaalpaenpqWlpqalpaamqKiohaeCpoSlhKYepaWkpaWlpKSioaOkpaSkpaSkpKWlpaenp6WmpqWlhacfqKinp6alqKiopaSmpqaoqKaop6mnrKqrqquqq6uqq4asIK2trq2urq2trq+trq+vsLGzs7OwsbKys7O0s7OysrKzhbQOsrGytrOxsbKur6+wr66Er4Curq+ura2urKyqqqqrq6ysqqysrK2trautrK2trK2urKyrqaamqqypp6enpqWkoqWlpaSilpGanpiZnZ2fo6OjoqSlpaOlpaSkpqOioqOko6OlpKOjo6KjoaKfnp6bnp2enp2enqOgk5qYkJuWmJuXm52dmZybm5eYmpucnZ2cnBianp2cnp6dnZudnJ2fnJyem52dnJugnp+FoAiho6Slo6OipISmE6Wlqaioqaipqqmop6mnpqenqKUtjI6RjYuOjZCQjY2Pj46PkI6PkY+Pj46QkI+RkJGTjY6PjI6PjYyNkZCRkZCShJFQkJSSk5WUlJOTkZKSkpGRj46QkZCUjY2OkpSTkpGQkY2Ni4qOjoqJiYyNj4yNjI6Ni46NjIyJi4qLh4yKi4uKioeHh4uJiYyJiIaHiIaFiIiFhyWIh4aIh4mFhYaHg4WGhoSDhISCgoWDhIeGhYaEhIKDiIOFh4eFhYYGh4aEhYSFhIZbh4WEhIKEhIaFg4GDhYWGhIaGh4eGh4eIiIiGh4SGh4aGhYaFhYWDhIeGhYWHh4aFhoODgoSEhIWFh4aGhoWFhISEhYSFhIeEg4KDhYOEhIOEhIOEhoSDgoOChIWFBoSDhIKEhYSEOoWDg4SFh4iIh4eGiIaGh4iHh4aHh4eIh4mJiImJi4uKi4uKi4qJi4yMioqMi4uMjI2MjY6Njo6Nj46EjRCMj46Pjo2MjI2MjIuOjI2OhIwDjYuLhYwfi4yMjY6OjY2LjY6Pjo6Pj5CPkI+RkI+Nj4+OkI+Qj4eQIpGQj5CRkZGQkJCSkpGTlJKRkZKSk5KSkZCSkpCRkpKRkpGFkw+SkpOTkpOUlJSWlZSUlZaFlS+UlJWWl5eVlZaXmJeWl5eWlpSVk5OUk5SUlJOUkpOTlJSSkpKQkZCSkZKRk5SRkoSRgJCPkJGQkI+QiY6Qj5KUk5SLhJGTko+Uf3WKiomKkZCWlJSUkoeUlIGSl5mPk5eZmZeZmpucmY2YmpabmZqVmZmXj5WalJaVl5ialpKNl5qRl5qbk5qalpOQj5CalZKWkpaQmJeZjpWUjJaZk5OVl5iamJeXl5iNlJeXlZKVl5OMF5aVk3mUlZaXk5GUg5SUlZWUlZWUlJOVhJYsl5aYl5iYmZmZmpmYmJmZmZqZmZeXl5aYmJmYmJmZmJiXlZaWl5iXmJmamZiEmSyamZmampmbmpqYmZucmZiXlpeWl5aWl5WVlJSVlZaYlZmYmZeYmJmWlZaXl4eYN5eYmJeYmJiZm5qZnJ2bmZiYl5ibnJqbmJeWmJiZnJybm5qampiYmZmXmJeXl5iXlpeXmJiXl5iEliSVlpSVk5KVlZWUlZWXlpWWlpaUlJWUlJOUlJKSkJCSk5KRkZGEkFSSkJGPjYaBiYiBh4mJjY6OjoyOkI+Mjo6Oj4+PjY2PkI+Ojo6NjY2LjY2Oi4mKiYuKi4uJiYqOiICIiIGMhomMh4uNi4mNi42KioyNj46Njo6Oj4+GjjOQkI+Ojo2OjI2OjIyQjo2Ojo+Pjo6Qjo+Ojo+QkI+PkZGQj4+RkY+RkpCQkZGPjY+Oj43/f/9//3//f/9//3//f/9/iH8CAgQAgLazs7O2tLWxtba3tba5tru7vLu5vLi7u7y8ubq5uLm4s7S1tbW3urm5vr68vry9v7y/v7+8vbm3ubS2sK+utLa4t726v73AwcLAv769vL65ubW2trazsqytra+0r7Ozs7e1uLq2tbW2tba2tbKwrK2sp6mlqrazpKmqpaWlpKWmgKmopqelo6alpaOinp2eoJ6foKGfn5ydn5+cnpyenZudnZ+enp2enp+gm52enZ2bm5ydnJ6fn6Chn56bnJudnJ2coJubnZ2enp+enp6doKCioaChoqCgpJ+hnqGhoqGhoZyIoJ+fnpydnp+gn5+foJ2enp6dnJucnJ+fnZyamZmZKJqYl5aXlZSTkZGSkZGSkpGRk5OVk5SSkZOSk5KTlpeVlZaUlZWWlpaElzyZmZqZmZiampycnJ2dnZyenp2foKChoaOjoqKjpaaloqSlqKqpqKelpaeopqamp6amqKmopqalpqanpKWEphGlpaSjoqSioqOkoqOjoaGgoIShLaKhoaGgoaGgn6Cgn5+fnp+en56foKGhn5+goKGhoZ+gn6Cgn6CgoaGgoaGhpISjB6SkpKWmpqiEphWlpKWlpKanp6Wnpqamp6anqKiqqKeEqQSop6anhKYlp6enpqiopqenpqWmpqako6KjoqKjoqKipKOjpKOjpKSioqSio4SiG6GjoqKjoaKho6Oko6Oiop+foKGdnJygoaKkoISigKalqKuiqaakm6eqq6ueoZytrLGvqKqzsrKyr7Wxt7ixuLOztLWvs7Wzs7CrrLO2t7a1s7O0s6yumq6tnau0tLGsrqmrq6+wq42mr66xrLO0sq+wraiytLOys7Ktq7OxmKmysrC0s7Sys6+ztLSrs7S0s7OztLO0s7SzsrW0s7KyFLCzsrCvr66usLKxrq+vr62trqyshK1IrK6tra+vrqysq6ysq6mqqpuqqaurq6yrqKqsraytq6urrKutrq6ura+vraurq6yvraysrK2wr66vsbCysrGtra+ur7K0tbW2hLcNuLe3uri5uru6uru8voW9Ub6+v8G/wL/Avr++v8LEwMDAvr2+v8HBwsLAv728v7+9vry/vry7u7q7vL26t7q8urm6uLq7urq4u7u5t7m4uLi2uLm6tJyftri2ubGorLCysoWvJ62qq6qpqaWio6erqqqtrK+tsLCvsK+wsK6urq+usbGxr6+vsbK0sYSwT6uopKSmqquqqqeln5aWlI6LiaOioKOfo6ChoqKjpaakpaSjpKOjpKCfn6GioaCio6GkoqGjoaSmpKeqq6mnpaampqmqq62urauurrCvsrCEsQq1tLO0ubi3t7izhLQCsrOAqKSjpKelpKOlpKalp6ilqKiqqKesqKmqq6umqKeoqKako6Snpqaqqqmvrquuq6utqqyurq2tqaqrp6mmpKSnqaurq6mura+usLCwra2rramppaelpqOinqGhoaShpaSkp6WmqaWkpaSkpaSkoaGeoZ2bnJqco5+WnZ2YmJiZmJmAm5qYmZmYmZiZmJaUlZSWlJWVlpSWlJSUk5KUkpOUk5WUlpWUlZSVlpeTlpWTlpWUlZSUlJaVlpeVlZOUk5OVlpWVkJKTkpSTlJSUlZSVlpiYl5iXlZaXlZeWlpWXlpWVkXqUlZSTlJWTk5SUlZSVlJWVlJOTk5SSlZSTk5KRkZEskpGQjo+Oj4+NjIyNjo2Njo6Pj4+Oj4+Nj4+Pjo+RkpCRkpGRkZCRkZCSkZGEkiGTkpOTk5WVlpeWlJWVlZeXmJmam5qampuam5qam5ucnZ2MnAOdnZ6HnQ6bm5ybm5ycm5uam5qbmYSaA5iYl4SZCJiZmZqZmZiYhZkBmISZBZqZmZmahJkKmpmam5ucmpqbmoSbEZqcnJybnZycnJ2enZyenp2dhZ4bn5+enp2dnp6enZ2enp+fnqCgn5+gn6CfoKCghZ8BnoafBKCfnp+EnoSdYp6dnZ2cnZ2dnJydnJ2dnJmcnJ2dnZudnJycm5ucnJydnZ2cnJubmpqWl5mbm5ydmZydm5menqCgmZ2cm5GdoKCfkpeVoKGlpJyep6alpaGnpqqppKuoqaemoqaoqKenoaKnhKsyqquoqKSmkaWkl6OpqqmjpqOhoaanpYeepaSooaqqqaioo6Cpqqqrqqijoaqnj6KpqaWFqg2lqampoqmop6ioqamohakcqqmqpqempqenpaWmpKWmpqalpqeop6enpaampIalD6SkpKWlpKSjpKOilKOjpISjEaKjpaamp6ampaWkpqamp6alhKYKpaSlpaWmpqWlpoSnDaqppqWkpaanp6mqq6uErAitrq2sq62trYSuAq+uhK8KsLCxs7GxsbKwsISyGbGwsa+wr7Cxs7OzsrGvsK+ur6+vsK+vrayFrROura2sq62tsLCwr66wra6trqyshauApZWYqKqrqaOhpaimp6anpaWloqGhoJ+fm5mYnKChoKGio6OkpqOlo6SjoqOjpKOjpaShoqOlpaSko6Skop6cm5qcn6Cenp6dmI+JioiFg5mYmJqXmpmYmpubnZycnZ2cnZydnZubmpubmpucnJqcm5ydnZ6enp+goJ+dnp2dnqAgoKKjpKShoqGipKWkpKSio6anpaeopqamqKOjpKako6SAj42MjI6Mjo2PjI+MjpCOkI6QkI+SkZGSkZGPkI+QjpCOjI2QjY6RkZGTkpKSkJKRkpOSk5KUk5KSj5CRj46Qj5CQj42TkpOQkpGTkpGOj46Pjo2LjIyIh4eJi46MjY2NjouNkYyLi42LiouKiIiHi4mHhIKCh4KFiYqFhYeHhoc4iYiGiIeFh4aGh4iGhIaHhYaEhoaFhIWFhoKFhISFg4WEiIWEhYaHh4iFh4eFhYaGhoeFhoeFhYeEhjGEhIWEhIaDgoODhISDg4SEg4OEhoeIiIiGhoeHhYOFg4SEhIWCaoSGhoWEhYWDhoSFhIQshoOEhYSGhYaGhYSEhIWGh4SDgoWDgYGCgYGCgoGDgYSEg4WDhIOCgoKDg4SFhYCEhIWFhYOEhYSFhoWGhYOEhoWGhYaHh4eGh4eGiIiIiomKi4uKi4mKi4mLjIyOjY2NjI2Mjo2MjYuMjo6OjY2PjYyOj46OjY2NjI6NjY6LjYyMiYyMjI2Mi4yMjYyMi42OjYuMjI2Njo6PjY6Oj4+PkI+Pjo+Oj5CPjY6RkJCQkQGRhJA7j4+Qj4+QkpGQkJCRkZCQkpGTk5OSkpKRkZOTk5KSkJGSkpGTkpOTkpOTk5KTk5WVlZSWlJSVlZWUlZaElYWWhJcIlpWVlpaVlZaElYSUfZWUkJORkZGQkJSTk5WSkZGTk5KTkpKRkZCPjoyMjY2OkZKNkI+PjpGSlJOMkZCNhJKWlJOJi4qSkpeWj5KZmJeXk5mYm5qVm5eXmZWSl5eYmZeTkpebm5ycmpqZmJOWhJWXjJGZmpiSl5WTk5iZln2OlJSWkJiZmZialpCYhJkjl5aTm5h/kJWVlJmZmpiVlJeYl5GYl5aWlZaXlpaVlpeXmJaElwuWl5eWl5eWlpeXmIWaJJuZmJqampiXl5aWlpeWlZeXmJiWlpaVhpWVlpaVlZaWmJeYl4WYC5aZmZqZmJiYl5iXhJYblZWVlpaUlpWVlJWWlZOSlJaWlpeXl5iYmJeXhJgZl5iZmpmZmZqampuamZqZmZaZmZmYmpqYmISZA5iZl4SYSpmZmJeYmJiWl5iYl5iYl5WWl5eWlpWXlpaXlpSUmJeWl5eXlZWUlpOTlpOVlJSLgYWTkY+NjpCQkJGRkZCOjpGPjo2Mi4yKiIiLhYx/jo2Pjo2Pjo6PjoyOjYyQj4+Mjo2PkI6Ojo2NjIeGhoaIi46MiouJh4F3e3p4dYmIioyJi4uJi4yLjo+Nj4+OjYyPjo2MjI2MjIuMjoyOjIyNjY2MjY2QkY+NjIuLjY2OkI+OjoyNjY6Oj46Pj5GOkZCOjo+OjoyPi42Njo6Mjv9//3//f/9//3//f/9//3+IfwICBACAtbe2s7Sxs7SztbS0ubi1tbS3tbe3tba2tbe2tLi0srKvsrO1uLq7vLi6u7a8v8PEwr+/ubu3tbStrK+tsrK2t7i7u7y7vr6+vbq6vLy7ure5s7Wxr7Swr62zs7O3s7a4t7a1srK1s7S0sbOzsq+tr66rqKWlo6igpammp6impaSApKOgo6SjoqGfoKCfoKGgoaOgop+enJ2fnp2dnJ2bnp2enp+fnpygnp2enZ2dnJuXmZqbnZ6dnJubmJmam5ydnZydnJudnZ+dnZycnqCgn6CgoKGin5+gnJ+goKCfoKChoJudnZ6fn56fnp+gnpydnJycm5ydnJqampycnJqYl5kNmZmYlpWWk5CQkZOSkISRZJCRkpOUk5GSk5SRkZOVlpKXl5aUlpeWlZSWmJiZmpiZmpqbnJucnZ6bnJydoKChoKChoaCfoaSlpqWkpKWlpaeop6WjpKanpqenqKilpqeopqalpKWlpaOkpaWlpqWmpqSjpKOEohygoKGhn52fn6CgoKGgoJ6foKCenZ6dnZ6dnp+ehJ8loJ+fn6GioaGgoaGgoJ+fnp+ioaCioqKjoaKio6SlpaWnqKinp4amgKenpqamqKinp6amqKemqKmop6enpqenpqenpqenpqanp6empqanqKaloqOlpaKjoqKio6Kio6KjpKWkpKSjo6KioqOjpKOko6Sko6SjoqGioaOjoqOkpKSjo6Sko6SlpKSjpKOio6alpqWmqaqqrKymqqulra2rrrCvsbO0tLKzCbS0tbOzsbK0tIS1L7a1tba1tLW0tLC0tLKtsbKns7O0tLSxsbSwr7GwqrGsorSvsrOysrKzsbKys6+0hLMPtLOysLOzs7K0tLSzs7SyhLMXsrSys7GzsrOys7SzsrGwsrCwr6+tra2GsAKur4SuJq+wsbCvr7CurK2trKyqrKyrq6qsrKurqqurrKmqq6ysqauqqKmqhawKq6yrrKurqqyurYasIquqrLC0tLa3tbS0tLW2tLS2tre3tre4uLq5ubm7vLq9u7uFvBu+wsTBv8C9vb6/v7+9vb6+v8HAv77AwLy8u8CEvYC7urq9uru6ube5uLm3ubm7vr66ubi5uLW3trazsrW1s66ssLTBr6qotbKPjpKdsbCwsa6srK6tra2qrK6sr66vrrCvr7KyrrGvraysra6trq+vr7GzsbO0srKvs7Kwr6qoq7S2ra2sqamnqKOfoKeYlpaUm56hnpudoqSioaKioxujoaGjo6Sjo6GgoqGioaCioqOio6OhoaOho6SEpRWjqKmqp6irqqyqrq2srbCwrq6wsLKEtA2wsbKysrGxsbCzs7K2c6OmpKSlo6Slo6SkpqmmpqWkpqSnpaWmp6SlpKOmo6SioaOkpqeoqaqnqaupra2usa2traqrqKeoo6Ojoaemp6errKuqqa+sq6yqqqysqqenp6anpKKkoqCfo6Ohp6WnqqmnpqWkpaOko6KipaGfn6CfnpmEl4CTmp2ZmZuampiWlpSXmZeXlpSWlpWWlpaVl5eXlZOTlJSUk5KSk5OVlZaWlZWVk5iWlZaVlZSUlJGTk5WVlJSVlpORkJGSlJWUkpWTlJOTlJKUlZOUlpaVlZaWmJeVk5WTlpaXlpWVlZSUkpSSk5STlZWUlZSTkpKTk5OSkJORkR2SkpKTlZSSkJCRkpGPjo+OiouLjY2Mi4yNjYuNjYSOL4+Qj46OjpGRjJKRkZGTk5GQkZKRkpKTkpOSkpOUlJSVlZWWlZWWlpeXl5iXmZiYhJoGmZmampqchJsCnJuInAGbhpwPm5ybm5qcm5ybm5uampqZhJoVmZiYmZiWmZeYmJiZmJmYmJmZmZiZhJgDmZqahJkBm4SaFZubm5mam5ubmpqam5uam5ucnZucnYWcBJ2enZ2Engqfn56dnp6en56fi54En6Cgn4agDZ+fn56en5+hoJ+goJ+EnoWdBpubnJ2dnIadApyeh50EnpydnYScBpudnZ6enYWeI5+dnJydnp2dnJybnZydnJ2dnaChn6CgnKKinKKjoaGlo6SlhKYhp6iopqimp6aoqamnqKmpqaqpqqqpqqapqaWipaifqqmrhKpGqaepqKimqaOZq6Woqqmqqqmpqqmrp6mpqaipqaqpp6ioqaqpqausqquoq6moqaqrqqqqrKqpqaipqKmoqKenpqWlpaSlpYSmBqemp6emp4emFKWjo6Sjo6Wjo6Oio6Sko6SkpKOihaSEpQikpaSkpKampoelH6enpaSlpKOko6Oio6apqqytrKurq6mpqqqqq6urqqyGrYCur62vrq2urq6vsbKztLKvsK+wsbCxsbGvsbCwsLGwsK+vrq2tsK+wr6+ur62sra2tq6mrqquqra6ur6+ura6tq6mrqqupqKmqqKSkpaeuo6OeoZiFhoqYpqampaOioqSioKKfoaKgpKKkoqSkpaampKWlpKKhoqKjpKSkoqWmpEqko6WloaWkoqKenqCkpJ+eoJ+enJyZmJiZjoyOjpGVmJeVlpqbmJqcm5ycmpqbm52bnJuanJqanJqbmpqbnJucnJyanJ6dnZyenISgIaGioaCgo6GhoqKmoqKho6SmpqemoqOko6Oio6Kho6OjpAiMjouMkI2Oj4SNB5CPjY2OjYuEj4CQjo+NjI2Oj46MjIyOkJCSk4+SkY6TkpOWk5OUkZOQkZKMjZCNj42Pj5GRkI6OkZKPkY+PkZGQjo2QjoyMioyKiYqKi4uNiouOjo6NjIyNjIyKioqLiomIiYiGf4SCg36Ah4mHh4eIh4eGh4WGiImGhIOFhoeFhoWFhoaGh4eFg1eEhYOCg4OChYWFhoaEhYWIhoaHhoaEhYaDhoWFhoaEhYWEg4GDhISGg4KDg4SCg4SCgoOChIWFhISFhoaGhYOGhIaFhYWEhoSDhIOEg4SGhYaGhYaFg4KEgw2Eg4SFg4aGhYWHhoWEhIUShIKDg4CBgYODgoCBgYOAhISDhYQtgYOEg4WFgoaFhIWGh4KDhIaHhoSGhIWFhIaFg4WHhoaHiIaGhoiIiImJioqLhIobiYmLi4yMio2Mi4uMjY2NjIyMjYyMjY2Njo6PhI4HjIyNjYyNjISLEI2Ni4yNjYyNjImNjI2NjI6EjQ2Oj46Ojo+QkI+QkI6Oho8ujY+PkJGRkJGSkpGQkJGRkI+QkY+Oj5CPkJGQj5GSkpKRkpGRkpSVk5KTkpWTk4SSGpOTlJSTlJOTk5SUlZSVlpWVlpeXlpeWlpaXh5YElZSVlYWUB5aWlZSTk5SElWCUlZSUlZaUk5OUk5SVlJOTk5KTk5KRkJKTkpGRk5GRkZCRkpGQkI+Pj5GRj5KTkpWVjpKTjZOUk5OVlJWXl5iXl5iXl5aXlZWVlpeYlpiamZiamZqbmZqWmpmYkpWYjpiFmSGXmZiYmJeWmZSLmpOWmZmampmYl5WYlpmZmJiYlpaWlZiFlyaYl5eYlZeYmJeXmJeZmJmYmpmZmZiYmZmZmJeWlZaVlZeWl5iZmoSYDJeXmZqZmJeWl5aWloSVHJaVlJaVl5WUlpWXmJeYmJeYmJeYmJiXl5mYlpWEl4KYhJYBl4SVApSThZQQl5mZmJmYmJeWl5eYl5eYmIWZKJiZmpuYmZqZmpqZmJmYmZqYmJiXl5qYmZqYlpeWlpmYmJeVlpeXlpiEl0CVlpWWl5SUlZSUlZaWlZWVmJiXlpWWlpWWlZORkpOTk5CQk5KOjY2EgnFwdniEkJCPj46NjY+Oi46MjY+Nj4+PhI5cjY6NkI+NjI2Pjo2Oj46NkJCPjo6OjYyPj4yOiomLjoyJiIyMi4qKiYeIgn1+gICBhIqIhoiMjYqLjI2NjoyLjo6OjI2MjI2NjIyNjYyMjI2MjImNi4yMjY6Nj42FjiCPjo6Mj46MjY2Pjo6NjpCQj46OjoyNjYyKjYuLjoyMjv9//3//f/9//3//f/9//3+IfwICBACAtbW2s7i1s7O1s7Gytbe1t7OzsbGzs7W1srOxs7Sxq7K1s7Kysra3u7/Av8K9vru6tbazt7W2s6+ts7e6tre2s7S7vr+7vbm3uri7u7e5tbawtLW0t7Gys7C2t7i5tbe0trWysrK3tbKysLCxtLKwrq2sq6qoraGWoqempqippaJCoqOlo6OjpqKho6OhoaOfoqOjoJudnZucn6ChnZydnp2cm5udnJ2dn52cm5qcnZ6em5ycnJ2cm5yZmJydnp6Zmp2ehJ1Lnp6dnZ+hoJ+foKKioKOgn5+gnZ+foJ+gn56gn5+dnZ6dnZ2cm5ybm5udnJucmZuanJqYm5ybmJeXl5aWlZaTlJWVkpGSkpGTk5GRhJMiko+PkJCRkJKTk5WVlJWWlJWVlZSVl5qYmJiXmJqbm5ycnoWcJJ6en6CfoJ+gnp+hoqSko6OkpaSlo6OhoqKjo6WkpaWoqKWlpYSmAaOEpCGloqOkpKSlpaSkqKakoqGjoaOkpaOhoKKhoqCfn5+eoJ+EngqgoKCeoJ6en56ehJ8yoKGioqGhoaCfoKChoKGioaKjoqKioaKjo6OkpaOmp6ampqWmqKimp6elpKWnp6inqKeEpgqnqKmopqanqKiohqeEpgalo6KkpKSEow2koqKko6KjoqGgoaKihKMLpKKhoqKio6Kjo6OGpBempaWlpKSkp6WmpaWnp6ioqqqpqaqsrYmshK0Ur66ur6ytra2vsLGxsLGysrOzsrKItRG0tLW1tba1s7S0sLSvsrKvtISzFq+wsJmutLKwsa6wsLGvrq6tr66wsbGEsDmytLO0srOzsrS0s7KysrSzs7W1tLOysbK0sbCxsLOys7KxsLCxsrGxsK+wsbCwsbGvrq6vr7Cxr7CErhytrq6trautrqysq6yrrKyqrKusq6qqq6qqqqushKsOrK6urKyrq6qtrKyrrK2ErEuuraytra2sq6+yr7O0s7S0t7WztLa3uLm2t7i4u7m6u7u8vLu7vLy7vLu7usG/vbu9vr6+vb28vr29u76/vL67ooS9uLm9vLu6wL2EvxK8ubq6tbm2uLa2t7i2tre2tbiEt4C6t7m5tbavq6inppeUlJ2gmZehsa+wsrGzsbO1s7S0tLKysKywrrCvr6+wra2sra6vsLOwsrGxsLKzsrKusq+vsK+ur62rqqSgqKKmq6mrp6SelpqlqJ2elpijo6GhoKGgoqOeoKGgoKGgoaGho6GhoKGgn6CgnqCdnJ2eoaKjpAqhp6Olo6SlpaalhKgdq6yrqq2urqytra2xsbCvsLGytLKzs7G0sLO0tLZRo6SloqWlpKOloqKkpaWjpqOkoqKjpKWko6Oio6WjnqKlpKOjo6aoqq2urLCrrqmrpqWjqaenpKWiqKyrqaeopqmqra6pr6qmqKmsq6iqp6akhKWAoqKko6WnqaqnqaWopaOjoqWlo6OhoKCloJ6dnp2am5uckIqXnZiZm5uZlZWWl5WXmJmWlpeYlZeWkpaXlpSSkpOTlJWTlpKTlJWUk5WTlJSUk5aUlZWTlpOVlJOVlJSVk5KUk5KUlJWVk5OTlJSVkpSUlJWVlJaUlZSWl5aVl5UllZSVlZaWl5WWk5KVlJSSkZORk5KSkpSSkZGSk5KTj5CQkpKRkIaRV4+PjpCOj4+OjYyNjI2LjIuMjo6Oj4+Ni4uMi4yOj4+PkI+QkZCRkZGQkJKTkpKTkpKSk5OUlZaVlZOVlZWWlpaXmJiXlZeYmZiZmZmam5qbmZmampubmoSbAZyEnTKbm5qbm5ucnJubmpqbnZubnJybm5qbmpqamZuampqZmpqZmpmYmJmZmZiZmJmYmZiYmIqZCJqZmZqam5uchpsTnJucm5ubnJycm5ucnJubnJybnIedBZ6enZ2dhZ4Qn5+fnp6fn5+en5+goJ+foIWfBJ2en5+EnhKdnJydnZucnZydnJydnZucnZyEnQicnZ2enZycnIWdB56dnZydnZ2EnoWfDaCfn6ChoaGioaKjo6KFo1Kko6KioaKipKSko6OhoqOkpaWmp6alp6eoqampqKmpqqqqqamqq6ysqamqqKWoo6iopqqpqKmpqKmokaeqpqippaenqKinpqWnp6WoqKeoqKeohakXqKipqKipqamrqqmpqqqpqaqrq6qpqaiFqSinqKmop6inpaeop6iop6alpaampqekpKWkpKalpaSlpKSlpKOko6OjhqQPo6KkpKOjpKSjo6SlpqamhaUHpKempqWlpoSlJaalpKOjpKSjpaenpqmpqaqrqqqqq6usrKusra2trK6vra6tra2Eriytra2wsK+tra+wr7Cwr7CwsbCxr6+ysJl9raqtrq6ur7Cur7Cwr66srayrrYSrgK2rqqurqqmtqqmsra+rrK6qq6ejoKCeko+Kj5GNj5WnpqelpaWkpqenpqWmpqWloqOjpqajoqOgoqGgoKKkpqSkpKWko6WlpKGipKOioqGhn5+el5OZlZmfnqCcmZaPkpiZkpWOjpubmpyZmpicnJeam5mbm5qam5ucm5ubmpmaN5mamJmWlpeam5qanpuem5+anZ2cnZ2enp+foKChoKKhoJ+hoaGkpaOioaOjpKKjpaOkoaOipKSAjI6Ni46OjY2MjIuMjo6Mj4uLjIyMjo+OjY2MjYyOio6PjI2Njo6OkJCTk5ORkZCRjo+Nj46RjY6Mj5ORkZCRkJCOkZGOk46OjpCRkY6PjY2LiouKjIiLioiKjYyOjo+KjI2KioqNi4qKiYmIjIqJiIiGgoSGhHd4hYiGhoiHh4UChIKEhQ2HhIaGh4aGh4SGh4aFhYQYhoOFg4OEhISChoWFhoaDh4WGh4SFhYaEhIUsh4KDhIODhoWFhoODg4WEhIOFg4OCgoOFg4KDhYWGg4WFhYSGhoeIh4aGhIKEhASDg4OFhIQzg4KEhYSDhYCCg4SFhISEgoOFhYWDg4KFg4SEg4GCgoKDgIB/gYOEg4OBgYKAgICBhYSEhIUOhIOEhIOEhISFhYWHhYWEhhaFh4WGhYWGh4eGh4eHiIiHiIqKiYmJhIgmioqJi4yMi4qNjI2MjY6NjYyMjY2OjYyMi4yOj46MjYyLiouMjIyGjxyOj4+Nj4+OjYyOjI2Njo6Pjo2Ojo+Pj5COjo6PhJAHj4+OjpCQj4SRAZCEkYKShZGHkCGRkZGQkI+QkZKRkZKTkpOUlJSTkZKTkpOUlZWVlJOUlJSIlQeWlZaWlJSUhZUSlJSUk5STlJSUk5OVlZSSlJSThJRLk5KTlJSTkpGRkZKTlJOUlJSTk5SSk5STkZKTk5STk5SUlZWUkpWWl5aVlZSUlJWVlZaWlJSWlZSTlZWWl5eWlpeVl5aYmZmYmJmah5s/nJucm5ybmJqUmJmYmpqZmZmXlpeClpmYl5eWl5mYl5aWlZaWlZiYmJmYl5eYmJaXmJiYmZiXlpaXmJiYmZmahJkJm5mZmZiamZqZh5gHl5eXmZmYmYSYCpeWlpeWl5eWlpWFlg+Xl5aWl5eWlZaWlZaWl5eElhmYmJeWlpeYmZmamZeWlpeVl5eYmJaWlpSVhJQJk5OTlJWWlpaXhJYRl5eYl5iXl5eYmZmZmJeZmJeEmYCamZiZmJiXlpaWlZeWl5eXmJiXlpWVlpaVlpaFcJSTl5eWlpWWlJeYmZaVkpWUlJWTlZWVlpSTlJWWk5SVlpWWl5WWlJKUkI+PjoyBgHR5enZ8gY2Nj4+QkI6RkpCPjo6Qj4yMjo6PkI6Oj42NjIuLjY+RjY+Pj46Mj5COjY2OjB2Ki42Mi4uKhIGDfoOLi4yLiYZ/gYWFgoN6fYqKiYSKSo6Ni4yNjI2Ojo6Mjo2Njo2MjIyOjYuMiomJi4uLjI+OjYuPi4uMi4yMjI2OjY2NjoyOjo2Ljo2Mjo+PjY2Oj42LjYyLjIyMi4yM/3//f/9//3//f/9//3//f4h/AgIEAG2xsrKytLG0s7K3srGxtbOysrO0tbWzsq6vs7S0sbO0tLW5t7m3ube3vbq+vb2/uLq4tLawsrOytre6uLezsre8vMC9urezs7e6uLW2s7SxtbGxtLSxtrKzr7C0tLe0r7Oxsq6wtLGzs6+urq+rhKoZp6WnqayhnaKlpqSjlYWEiZKlqKynpqWlpIahQ6CinZydnZ2enp6dnqGfnp+am5mcm5ugnZ6dmpqdnJudmZqYmZubnZybmpuampuZm52fnp6gn52dnp+dnZ2fnp6foKCEnyCenp6fnp2dnp+cn56bnJ2dnZ6dm5yenJ2bm5uampuYl4eYIpeXl5WWlJWVlZSSk5KUk5WVk5KUkZKUlJCQkpKRkZGTk5OFlIKXhJaElxKYmZqanJ6dm5qdnp6cn52foJ6EnzSgoKChoaOio6KjoqCioaGkpaWjoaKjpqWjo6OlpqalpKalpKSlpKSjo6SkpaWkpaOjoKGihKEdnp+hoaKgnZ+goJ+goKCenp+goJ+en6Cem52dnZ6HnyOenp+gn6CgoaChoqKio6KhoaKio6Oko6Sko6OlpaakpKenpoalLKanqainpqaop6eop6imp6anqKiop6enqKalpaaio6OhoaOio6ShoqWlpaGghKIBpIajBKSko6OEoT2goaOioaSko6Smp6ampaalpaemp6ioqqurq6qrrKytrq6ur7CysbCwr7CvsrOzsbOysbOysbCwsbCvsLOzhLIRtLW1s7S1tbS1tLS0s7OzsrSEszWxsayxsrS1tLSzsbGxsLKxr7Ctpqytr66usbCvsLGwsLCusbGxsrGysbGytLOzsrGysLG0sYeyB7OzsbKysrOGsRmwsbGwrq6ur6+urrCwrrGvrq6vrq6tra6shK8drq+tra2srK2sq6upqaurq6qpqaqqrKuqrKysrauErICurq6srK6srK2trK6srK2wr62vr7Cys7OztLS1tbS0tLe2tLW1tbm4t7i5uLm5ubi3uLi8urq/vLy7vb28ur6+vr28vb27vLu5taClu767vr69v8C9vr6+wLy8vLq4ubm4tbm3t7e2tLK2t7WzsrCwsbe1tLKwramkopGLjp2foVyorK+ysbGupJifsba0s7HGwqSUsK+wrq+vr66xs7KxsbOvsrKztbK2srKwr66vrbCur6+vrqyrpKaqrKumpaOkp6Wpo56ajZimqKamo5+goJ6dnqChoaCdnp+foYSeNJ+cnZ6foKCkn5+dn6ChoqKgnJ+hoqGkpqempKalpaKnqaqrq62qraqsr6+trrKvr7Gxr62EtIKyEqGjoqKjoaSloqaio6Kko6CjpYSkV6OgoaSipKSlpaWnqqWpp6qoqayqrautrqiqqaWlpKWmpamqraupp6eprKywraupqKaoqKeoqKSkpaWioaKkoqejo6ChpaWlpKGkoqOgoaSho6Ogn5+fnYScHJiYmpuakpGXmpqYlYt/f4KIl5qemZmYlpaUlpeElSOWk5KTkpWVk5STkpeUk5WTlJCRlJSVlZSTk5OWlZSUkJKSkoaTQ5KTk5SRk5OVlZWYlpOUlJaTlJWVlJWVlpaUlpOUlJaUlZSUlZWVkZSTkZKUk5OSk5GTlJGSkJKQkZGRj46Qj4+Ojo+EkIaPAY6GjYSOOo2NjY+NjIyOjYyOj4+Ojo6QkJCSkpGPkZCRkZGQkZOUkpOUlZWVlpWVk5WVlpaWl5WWl5WWlpaXmJmEmhCZmJmZmpqamZmZmJqbnJuahZsJnJyam5uam5ubhJoMmZiYmZuamZqZmZmYhZkemJmYmJmYmJiXmJiYl5iYmJmXlZmZmZeZmJiYmZqZhJoImZuam5ydnZ2GnBGbnJybmpubm5ydnZ2cnJ2enYeeAp+giZ8Jnp2enp+foKCghJ8WoJ+enZ6cnJydnZydnJucnJ2ZmpydnoSdgpyEnQWcnZ2dnIadAZ6EnYaeEZ+gn5+goKChoqKhoaOjoqOkhKUJpKWmpqSjpaWlhKYBp4WmhKcRpaamqKmqqKmqqquqqamoqqqEqRGqqaipqKeoo6ioqaqpqqmpqISpRqqopZ6lp6inp6Wnp6ipqKiopqeoqampqKioqamqqampqqmoqaepqKmqqqmoqamoqaioqKeoqainqKinqKempqalpaWkpKWFpISlLqSlpaalpaSkpaSko6SkpKWkpKOio6SkpKOipKOjpKWko6Oko6WkpKOlpqalpaWFpISlGqampaanp6ipqaipq6upqquqq6qprKuqq6uqhqyAq6uqq66tq66ur6+urq2tr7Cysa6vsK6urq2pkparr66vrq6xsK6vr66vraysrKqqrKuprKuqqqupqKqsq6moqKeoqqmqqKWjoZ2aioSHkZWUnaCkqKelo5mPlqapp6emtK+Vi6SkpaOkpKOjpKalo6Sno6WlpaakpqSjo6Gho6AnoKChoZ+hoJ+WlZyfoJ2Zl5ednJ2XkY6CjZyenJuamZqamJeYmpqZhZg/mZeXmJqal5iZmZqam5qal5iZmpuamZiZmpubm5ydnJqcm5yanp6fn5+goKGfoKCioZ+joKGioqGeoqOkpaOhgIqMi4uOi42Oi4+LjIyOjYuMjY2OjpCOjI2MjI2MjIuOjY2MkI+PjY2Pj5KPkZGNjo6Pj4qMjY2OkJKRkJCOj4+PkpCQkpCOj5COjZCNjY2MiYiLi4mMiIiIioyLjY2KjIqLiomMiYqJiYiJiYeJiIeGg4OEhoB7foSHhoWEfnV1DXh7hIWKiIWFh4eDhoeFhReCg4KDhYWDhIOChoSEhIOFgoSFhYeFhISFDISEhIGDgoSDg4KEhISFJYaChYWFhIOGhYSEhIWDg4KEgoKFhYWDhYSGhIaEhYSFhoWDgoWFhCSFhYWEg4WCgoOEgYOBgoKCg4KCgoCChISEhoODhYSFhYOBg4OGgoSBCIKBgYKCgYCChYM1hYSEhoaEg4WDgoOEhYSEhYaGhYaFhYeHh4WHhoaHh4eGhomJiIiHiImJiomJiIiHiouLi4yEigSLjIuLhI0BjIWNT4yMjYuMi4qJiYqLjYyOjo6NjY2OjY2Ojo6Njo6NjI2MjY2MjY2MjY6Oj4+Nio6OkI6Pj4+Qj4+Pjo6Oj4+PkZGQk5SVk5SUk5OTkpGRkZCEkQaSkJGQkZKEkBWSk5OSkpKRkpOSk5WUlJSVlJSVlZWGlASVlpaVhJQHk5OUk5OTkoSTC5GQkpGTlJaUk5SWhJQEk5OTlISTAZGGkw+Uk5KSkpWTlJSTkpOSlZaElISWGpWXlpeXlpeXl5iXmZmZmJiamJiZmZqbmpqZhJghl5iZmZeampucm5ubmpqbm5qam5yamZqamJmWmZqanJubhZkNmpmZmZaQl5iWl5mYl4WYIJaXmJeWmJiYl5eZmZiWlpWZmJeZmJmYmpiXmJmXlpeYhZcImJiYl5eYmJiIlwuWlZWVlpaXl5aVloyXiZYNl5aWlZaWlpWXl5WWmISXMJaXl5aVlJWWk5STkpSUlZSVlJWWl5SVlpaVlZaYl5iYl5aYlpWXlpeYl5iYl5mYmIaXJZaVlZWUl5aXlpiZmJaUlJWVlZaWlYGElZeWlZaWl5eVmJeWl5OElAOVlZSEk2aUlJOTlZaWlZKRkpSTkZORkY+PjYt8dnN8gn+Dg4uPj4+NhXyAjo+Oj46Tjn15jo+OjIyNjo6Nj46Njo+OkI6NkI6Ni4yNjIyPioqKi4yNjI2JgH6IiYmHh4aEi4uKhX55cXmIjIuFjA+Ni4uMjIyLiYqMi4yJiYuFjDaKiouMiouJi4qLi4qJi4iKi4uJiouKiIuKi4eKiouKiouJiYmKjI6Li42MjYyLiYeOjIuMiov/f/9//3//f/9//3//f/9/iH8CAgQAgLWzsbK1tLOzsrOysbOysrCytbO1tLO0tLS2sbOxsrW3t7W3vLm5t7m6t7e2s7G0tbKztbG1tbW0sbK0ube7vr21s7CysbO2t7q3s7W3s7Wzr7K0s6+xsrWusLKysrSzs7W0s7KysbCvsbCsq6enpqapoqKlpKCVnaOjpqWmopebcp6bn6yrqqakpaKgnp2cnp6dnZ+anJ2gopydnqGfnZ6dm5qdm52bn52bm5qYmZubnJubnp2enJmamJabmZmcm5yenp2enJ2enKGgnZ6fn52dnp6dnp2fnZ2bnp+dnpycnpuYnJqbnJuZm52Yl5ebm5qZmYWXgJOXmJmWl5eVlZSTlZKSlJOUlJWRkpOUk5OVlJCRkpGRkJGSkZOSkpOTlJWUkZWWlZWWlpeWlpeZmpmam5ucnJ2cnJudnZ6dn56eoKGfoKGgoJ+goaGioqKfnaChoqOhoqGio6Sko6Sjo6OkpaSkpaKkpKWlpKKjpKWnpqShoqOgCKGhoqGgn6GhhKAQn6CfoKCgn56dnp+goKCdn4SeBp2dnJ2cnYSeA5+fnYSfE6CgoqGhoaCioKGioqKjo6KkpKSEpR+jpKSlpqWmpaeop6amp6mnqKeoqamqqKenp6anqKenhKiEph6lpKWmpKSjpKShoaGjo5ueoqKkpaOkpKSioaGio6KHoQKioYSiBKOkpKWFpjulp6eoqKqpqaurqqusrK6wr66vsLCvr7CxsbSzsa+xsLCxsK+wsbCvsa+xsrKzsq2ysrCxsLOzsrOxs4WxK7Oysa6wsK+urq6wsbGxr6+trKysrqyrqqqqrKyrq6utrbCvr7Cwr7CwsLGEsDyzsbCxr7Gwsa+wr7CwsLGvsLCxsrGwsbGysbKysbGxr7Gvrq+xsLCvrq+wr7GxsK+tr6+vrq+vr7Cwr66ErCmrrKuqq6qqqqusq6yrqKmpqqqrqqurq6qrqqmqqqqrrKqrqaqpq62trIatEK+wsLCytbSzsrCytLS1tbWEtha4ubi2t7i4ubi3tra2uLm7ubm5urq7hLk6vLy9vb+7u7y+u7u8v72+v72+vby7u7y+v769vbq+v72+u7y7vbm7u7e3tLW3tbS2tbS0tLKzsbKwr4SugLGglZalra+zsbO8s56PpKaWj6Ctr66wsK6vsrOysLO0sa+vqa2tsLKyra2uqq2uq66vrq+uq6qqpqiorKuqq6iqq6enpp2mpJubn6WnpKOjpKGhoKChoqGdnZybnZmVmp6en6GgoKGin6CenJ+enp+goqCioaOjopqkpaSipKWkHKqnqKurqqusqquoqaqrr6usrrCvq6+ysbCys7EGpaOioqOihaOAoqOho6GipaWlpKSjpaWmoqKipKWmqKWnqKenp6alp6ippaWmpqSlpaSopqalpKSoq6qpq6yopqWlpqenqKimpaeno6SkoqOjop+io6OeoaKjpKSjpaakpKOjoqGioqCenJucmZiZlJaYlpCKkpiWmZiYlYuRko+Rm5ycmpeXlZWAk5OSk5STk5WRkpKUlZOUk5eVk5OTkpKUkpSSlJWSk5KQkZOTkpOSlJOUk5KSkI+TlJOTkZKTlpWVk5SWlJWWk5WUlZOUk5STlJSVlJOSlJaVlJOSk5KPkpGPkZGQkpOQj4+RkY+Pjo+Pjo+PjY6PkI6PkI+Pjo2PjI6Pjo+Njo0ZjY2MjZCPj42NjYuMjIuNjY6Pjo+Ojo+PjISQFJGQkpCQkZGRkpOUk5OUlJWVlZaWhJcalZeXlpeXlpeXlpaYmJeYl5eWl5iamZeXmZmFmhqbmJmbmZmbmZmampqbmpuampyamZmampmYl4SZApqZhZgNmpiZmZmYl5aXl5iXmISWC5eZmJeXmJaXmJiYh5kWmpubm5ycm5udm52cnJ2dm5ycm5ycnISdHZ6enZ2enp+fnp6fn5+enp6fn5+gn56enp+fn6ChhaAEn56enoSdDJ6cnJ2dnJucnJOWm4ScDJ2dnJycm5ycnJucm4WcCJ2dnJ2dnJ2dhZ4Fn56fn6GEoA2hoaGjo6Oio6SmpqSkhaUspqalpKWlpqenpqenpqWlpaamp6inpKioqKmpqqmoqaeop6ioqaioqamnqKiFpwGohKkWqKempaempaSlpKWkpaWmpqaoqKeop4SmhKeCqISnCqipqaeop6eoqKiGpwGohKcBqISnEqalpaamp6ampqWlpKSlpaSlpIalhaRYpaSio6OioqGio6KioqOio6Kio6OjoqOio6OkpKOkpKSjo6KjoqOioaGio6WkpKWkpKSlp6emp6iqqampqKmpqqmqq6qrq6ysq6qrqqusrKurqqipqauvrIStGKyurKysrq6ur7Ctrq+xrrCvr66xr6+urYWugK+vrq6trq2vr66urq+urq2sraqqqqmoqquoqKinpqampKOjo6ShpJSKkZ6kpKalpKqklImcn5CElKCko6Wko6WmpqWipqako6WeoaGkoqGioKGgoKKhoaCgoKKgnJ2Ymp2fnZ2gm5ygnZ2bkZmXkZGWm5ucmZucmZmZmpmal5WWQpaWl5ORlJiZmZqbmpmamZqYlpiamZuYmpiampycm5OcnZuYmpuanpyeoJ6fn6CfoJucm5ygnZ2foqCcnqKhoaKjoQuOjI2Mj4yMjIuMjoSMNYqLjIyNjo+NjY+Oio2NjYuOkY2Nj4+RjY6Qjo+OjI6Pj42Mjo2PjY2PjY2PkpCRk5KQj4qLhI6Aj46Mjo2LjYyIiYuKiYqLioWIiYmKiomKi4qLioqLioqKiYaJh4eGhIZ+goOCeneAhYOFhYWCfoOFgX2Eh4mGh4eFhISEgoOEg4OFg4OCg4SDhIKFhIOCg4KEhYKGg4aGhYWFg4SEhYOEgoSGhYOCg4KChYWFhIGDhYaEhISFhYQlhISDgoOEgYGDgoKFhIeGhIOFhIWEgoSGhIGDg4CCg4KDhYKCf4SCAYCFgheBgoKDgoODhIODhISCgYOEhYOEgoCDgYSDgIKCgX+BgYKCgYOCg4SDgYGDgYOEg4SDg4SDhIWDg4OEhoaHhYWHiIeGhoiJiIaGiIeGhoiGh4eHhoiIhoiHhoWHio2MiYiKi4uLioqKi4qMjIqLjIuNjY6NjYyNjIuMioqLjY2MjYyMjI2Njo2MjYyOjYuLjY2NjIuLjIyMi42LH4uMjo2OjY2Oj42Oj46Njo+Qjo6Pjo+RkJGRkZKTlJKFkw2RkZOSkZKSkpCQkJGShJE6lJSTk5OSkpGTlJWUlJSTk5WUk5WWl5WUlZSUlZSUk5OTlJWUlJOSkpOUk5STiYuRkJKUk5KSk5OSkoWTCZKSk5OTkpGSkoSTgJKSk5WVkpOUlJKTlJOSk5SUk5SVlZWUlpWWlpWXl5WWl5iWl5iXlZiYmJmZmpqYl5iZmJiZmJWbmZmamJybm5uZm5ybmpuampmZmZqamJmZmpubmpubnJqZmJiZmJeWlpeXl5iXl5eYl5eWl5eYmJeXmJiXl5eYlpaVlJaXmJaWEZeXmJeWl5iWlpeWlpiYmJeYhJcZmJeYl5WWl5eYl5eXlpaXlpWVlpeXlpaVlYSWCpeWlZWWlZSUlZWFlICVlZaUlJWUlJSWlJWWl5aUlZOTlJSSkpKTkpOSk5WWlZWTk5OVlZSUlZeXlpaVlpiXlpWYl5eWlpeZmJeWl5iYl5aWlJSVlJWUlJWUlpaVlZWXmJaUlJaVlZeXmJWUl5aVl5WWlpeXlZWVlpaVlZaamJaVlZaVlZWXlZaXlZaUlAuTk5OUk5OQkI+QkISPc4uMgHl/iIyNkIyMjYh+e4eKgHR/io2OjpCPjo2Pj4yRkI+Pj4mNjY2MjIyKiImJjIuMjYuLjIqHh4KHiIuLioqHioyKiYZ6gYN+f4aKi4yKjY2KiomLi4yJiImIioiHhoeLjIyOjIuLjIuNi4iLi4uMiomEiiCLioKJi4mGiYmHioeJiYqLiImJiYiKiYeJiYuKjImFiISLAoyK/3//f/9//3//f/9//3//f4h/AgIEAICysbS1r6+ysrSxsbSytLCys7KytLOgsbGvr62ysLSysrO4uL27tLC2s7GssK+qq6upr7Cvs7OytLO1srS2trWxs7Kyurm0urqzsrKxsrCvsrGvsrOxsrKysa+zsrGysbOvsa+xsrS0sK+pq6uoqKakpqGio5+Yh4mcoaalpaanp4CmpaOZmqGjoqOio6KjoaKfn6CfoJ+enZ+enJ+enpybnJ+ZmZubnJuamZeZmJmbmpyamZucnJiYnJeZmZiYmpyanZydnJ2bm5yfm5ycoKKenZudnZyanp2foJyenZyenp+amp2YmZeYmpmZmZqal5iXmZmYlpiXlZOUk5WVlJWSkwSSkpKUhJFHkpKSlJOTkpORj5GQkZCSkpOTlJOUlZOUlZWUkZWXlpaWlZWXmJmam5mXmJmampubnJ+fnp+dnp6cnZ6goaKgoaCeoJ+hpKGEoASjoaKkhKMjpKSmpaSipKOho6OjpKSlo6WkpKWmoqGioaKjoJ+hoKGioKCGnwGehZ8Qnp2cnp2fnp2dnp+dnJucnISdEZydnp2dnZ+fnp6foaGhn56ehKAIoaOko6OipKSFow2kpaSnpqelpqampaalhKYLp6eoqKemp6emqKmGpzKop6mop6ampaSkpKWjpKSjoqKio6GioKKhpKSlpKKhoaKhoaKhoqGioqKjpKOjoqKjo4SlE6alpqaop6ioqaqqrKusra2trq+Erhavr7Gzs7K0srOysrSzs7GxsrO0tLKyhrQJtbOysbCysrKxhLIktLCvrq6trq2usLCvr7Gvrq+vr66ur62srK2tr7Cwr66urbCvhbAisa+tr66vr6+xsK+wsLCvsrGxsrGysrKxsa6vsbCxsbKysYeyIbGwsbCxsbCwsK+vr7Kxr62ur66trqytrK6ura6tq6urqoWsAa2ErICqqaqrrKurrKutrKyrrKuqqquqrKyrrKyrqamrrK2urq2trq6wsrGwsbO0tbOytLa1tba1tba1tri5uLm4urm5ubi4ubq8vLu3uLq7vLy7uru9ury+u7y6urm7vbu8vb2+u77Avb29urm6vb29u7y7uru5uLW2t7i5t7i0tbe2tTSytLWysrG0s7GxsbKys7XAu7itr7OnmaCms7WypqalqKysrK2tqqywr7CxsKyvrquura6phapyp6qvrK2trK2sra2oqamrq6+tpqGfoKmjmZaam5uUj5adpKmopKWkopycn52foZ6cm5menpmenZ+hoaGgoaGgnqGhoaCgoqOipKKkpKWkpaSlp6OlpaenqKmnq6uoqqqqrKypqquura2rrrCwrq+urrGxfKGgpKSgoaGho6Cho6Gln6OhoqSko4+hpKGhoaOipaKkpqmorKyloqSjo6CjoZ6fn5+joqGlpqWnpqmnqKipqKWmpqOpq6arq6WlpKWmo6GjoqOlpKGjo6GhoKOioKGho6KioKKipKOgoJydnpuamZeXk5aWlIp9f5GVmZiEmQuYl5ePkJaXlpaWl4SWYZWVlJOUk5OUlZSSk5SUkpGUlJCQkZOUk5KRkZOQkZOSkpGRk5KSkJGTjpCRkpOUlZKUkpWTlpSTk5WTk5KTlpSSkpSTkpGTlJWWk5SRk5SUk5GSkY+Pjo6Rj5CPkZCPjpCGjyWNjo2Lj46Mjo2OjIyMjoyMi4yMjo+PjY2Njo2LjIyOjI6Ojo2Qho9jkI+Mj5KRkZCPj5GTkpKTkpKTkpOUlJSVlpaVlpaXl5aWl5eWl5aXl5aYlpeZl5eWlpaZmJmbmZmYmJmYmZqamZmYl5qamZmZnJmbmpqZmZqbm5mZmpiXmpmYmZmZmJiXmJeXhpgQl5eWmJeXlpaXlZaWl5eXloWXhJgZmZiampqbmpmam5qbm5ydnJucnZ2cnJ2dnYScBZ2dnJ2dhZ4CnZ+FngegoKCfnp6ehJ8LoJ+foJ+foKCfn5+FnoSdB5ycnZ2dm5uEnAGdhZwLm5ybnJybnJucnJuGnIaeJp+eoJ+foKGgoKGhoaKhoqOjoqKjpKOjpKWkpaampqWnp6empaWmh6cIqKenqKipqamEqBenqKaoqamoqKenpqenp6amqKiop6ipqYSoGqempqWlpqWmp6anqKinp6ioqKmnp6iop6emiacUqKqpqamoqKipqKelpqinqKeop6eEqAWnqKempYWmhKUKpqWmpqalpaWkpISjAaSGowahoqKjo6KEowGkhaMBooSkK6WlpKSko6OjoaKlpKOko6OjpKWkpaSlo6WlpaampqioqampqKipqqqsqqqFq0+qqqqsq6qrqq2srKusrKmqrKutrq+srK2rrq+vsK2trq+wrq6xsa+tr6+tra6trKyura2srqysrK2trKurq6ysrqurq6qqp6ioqKanqqiphKd5qqiwraykpqmflZibo6SknJucoKKho6KkoaGko6Sko6GjoqGhoKCeoJ+enp6cnqOgoaCfoJ+fop2enp+gop+bmZaVnJiRjY6Qj4yHjpScnpuampublZWYmZmYlpaWk5iYlJiXl5iam5mYmZiYmpqZmZmampibmpubnISbIZ2am5qcnZ6enJ6fnZygn6CfnZ2foKCfnZ+hoaCfoJ+io1SLio2MiYqLi4yMi4yLjomJio2Ni4t6jIyKjIuOjI+NjY2Qj5GRkouLjI6KjY6JjIuKi4uLj46Nj4yPj4+QkZCNkI2KjpGMjo+MjIuLjYuIjIuJiYqEiSKIhYmJiYqJi4qKiYmKi4mHh4aGiIeFhoSEf4OAgHdvcYGFhoYhhYSCenyFh4aEhIaDhYWEhIWEhISCg4eEhIOChIWBg4WEhoMZgoODhIGDg4OCgYGChIOBgYOAgYKDg4KDgYSCToSEgoOEg4GCgoWCgYODgoF/goSHiIODgYSFhYOAg4GDgICBg3+Cg4WCgoKDgoGBgoGBf399gIOBgH+Bg4GCgoOAgYCCg4KEhIKBgoOBgISCD4ODg3+FgoOEg4KDg4OBg4SEBIODhYaEhBGChYWGhIWFhYmIhoeHhoWHiIaHWYaFhoeHiIeHhoaGi4uMjIuKiomJioqLi4uKiYmLi4yOjI2LjIuLiouKjIyKi42MjI2NiouMjY2MjI6MjYyMi4yNjIyLioyLi4yLjI2NjIyLjIyNjIyMjY2Nho4Jj5GRkI+QkZCRhpKHkYKShJA9kZCRkZGTk5KSkpGRk5OVlJSTk5KTk5SVlZSUlZaWlJWTk5SUlJWVlZSVlZSUlJOTlJKSkpOTkpKSkZKSkoSRBZKTkpKRhJKEkxmSkpGSk5OUkpKUk5SUlJOTlJSUk5KTlZWThJQYk5aXlpeYl5aXl5WVlpaVl5iYmZeXmJiZhJgFmZmZmpqEmRKYmpmampybmpmZmZiYmZqampuEmhGbm5uampiXmJeXmJmZmJeWloiXBZWVl5WWhJUFlpeWl5aEl4KYhJcKlZaYmJiXmZiYmYSYEpmYl5aWlpeWlpWWlZWVlpaXmIWXhJYKl5WWlZWVlJSVk4WUNJOVlpWWlZSUlZaXl5iWlpWUlZSTk5OUlZOUlJOSkpOUlZaVlZOVlZSVlJWWlpWYmJWXmJeEloaXLpiYmJaWlpWXlpaVlZOSk5SUmZmXlpWVlJeWl5iXmJeYl5eWmZiWlZWVlJSUlZaElVCUlZSWlZWTlJOTlZSVlJOVlZeVkpGTlJKRk5GRkJCTkJKSjo+Rj5CRjYSGiIqNi4aGiIqOjY2NjoyNkY6Njo+Mjo+LiouLiYuMiYqJiIiNjISLLYyMjIeKiYqLjouHhoODiIWAe318enx2gIiKjIqIio2OhoWLioqKiIeHh4qKh4SLOYqLiYmKioqLi4qKi4yLioyKiouLi4iJiouJiYiJiYyJio2LiYmLiYqKiomJioqIh4qKi4qJiomLi/9//3//f/9//3//f/9//3+IfwICBAAvrq6ytLWxsLCysrG1tLS0srG0srKvrK2tqbGvsLOztLO2sLS2tK+vsrGsrK6tqqmEroCqr7CusbS3s6+3sbO0tbi6uLS1uLW0s7Kqq6yxtbS1sq+vra+xsbCwsLKwr6+ysa+wsbGuraunpqmnp6mmpqinpKChm5uhpaelpaagop+fmJaMi5+ooqOlo6Who6Ggnp+fnZybnp2YlZSYmZeYmpqWmpiZm5uZmZuampydm52cnA6Zm5mamZqbmpqbm5qbm4ScVZudnZycnJ2fnp6bmpycnZ6cm52am5ydnp2enZycm5qbmZudmpuZmpqal5WQlZSWl5aWlpWUlZaXlpKTkJOTk5GRk5OVlZKQkZKTk5KQkJGRlJSTk5OHlCeXmJSTlJWVlZaXlpmampydmpmYmZmamp2enZ6fn56dnp+goKSho6GFoBOiop+hoqCfn6KlpKOkoqKmp6WlhKMppKOio6SipKSjo6Ohn6CjoKCfn6GhoJ+gn56goJ+doJ6dnZ6foJ6dnZ2EngudnaCenp2enZ6dnoSdSp+dnJ2gn6Cfnp6fn56enp+fn6GhoqOio6Wlo6OjoqOkpKSjpqWmpqWmpqalpqakpaWlpqemp6anqKemp6empqeoqKemp6ioqKemhqUTo6KhoqKioaCgo6OioqOko6GhoYSiJqOioaChoKGjoaKio6Kjo6SmpqSjpaanqampqqqpq62srKytra6thK4bsbCwsLG0s7GytbaysrO0tLO0tra1s7S0s7W3hLaAtbSztLS1trW0tLKysrGysrOys7CxsrGwrrCvsLCwr7Cwrq6vsK+vsLKysLKwsLGys7KxsbGwsbOxsrGvsbGysbSzsrGysbKzs7GwsrOzsrGysrK0s7O0s7O0tLKysrGysrOysbGwrq6vrq2traysrKurrK2sra2rq6yrrKysq6werKyqrKyrqqqsrKutq6ytrqyrqqypqqqpqqqrq6ythKoXq62trK2srq6wr7O0s7O0tLO0tLW2ubiEtyS4uLq5ubm4ubu5uLe5ubu4uby9u7u5uLi4ury5urq5vb27vLyEu4C8u7y8vLu8u7m8uri6u7u6uLq4ubm5tri5t7e0s7OytrS3tLS0s7O0tLKurayqqbKrqpiqrKmkoqenp6i3srOxs7SxtLKur6uvr7GvrausqqyvraypqKutqq6wrq6uq6yrqqurqaimqq2zsqympJ6gpKahn5iWlpqan6Slo6GfnIShR52foaCfoZ+foJ2cnp+foaKhoaKhoaOho6Sio6GhoaKjoqaloqOhpaaop6qppqmnqqimpqeoqKmrqqyqq6ysrqytr6+sqq6vgJ6eo6OkoqGhpKSipaOio6KhoqKhoZ6fn5yioaKjoqSjpqCkpaSioqKkoKCgn6Ggo6OioqCjoqOkqKmno6ikpKWlqamoqKipp6Wlp6Cgn6KmoqWjoKChoqKhn6GhpKGgoKKhoKChoJ+enp2bm5qanJqZmZeVkpOOkpaYmZeXmpOUd5ORjY6FgpOalpaYl5eUlpaWk5SVlJORkZOQj46Pjo+PkZKLkpCRkpORkpOSkpSVkZKTlJKTkJKRkZKSkZGRk5OTlJOTlJOTlJSUk5KTk5OSkZKRkZGQkpKRk5OTlJWUk5GSkZCRjo+Rjo+PkZGQj4+Hjo2Pj42Nh44fjI2KjYyMi4uMjY+OjYqMjYyNjo2LjIyPj42Oj4+OkISOA4+Qj4WQEpKSkpWUk5WTkpSSkpKRlZWUlYaWKZeXmJqXmZeYl5eYlpeXlZaWl5eWmZqZmZuZmJqamJmampiYmZqZmJmYhJkfmJiZmZqYmJeXmZmXl5iYl5iYl5aYl5eXlpeYl5iXlomXDJiYl5aXmJiYl5iXl4SZBpqbmpqbm4SaBJubnJuFnAKdnISbC5ydnJudnp+fnp6fjJ4JnZ6fn56fn5+ehZ+DoISfCp6dnZ2cnJydnZyJmxacm5ybnJubmpubmpubnJubnJucnJ2chJ0FnqCen5+EoAeioqKjo6SkhaMUpKOjo6SkpKWlpaanqKempaWmp6aEpzympqenp6mqqqqpqaqqqampqquqqaqrqamqq6qpqKinp6ipqaeoqKinqKenpqeop6inp6ioqaeoqKenqaqEqQmoqamnqKeoqKeEqAmnqKmpqKinp6eEqAGniKiIp4WmEaWkpaSkpaOkpKOjpKWkoqOihKMRpKKhoqKjo6Oio6Sko6OkpKOHpBujo6KjoqGjpaWlpKWjpKSioqOko6SlpqanpqeFqAynqamqqqurq6qqq6qFqxuqq62srKqsq6yrrK6urK+wrausrKyrrKysra6FrYCurq2tr6urr66rra6srKytra6qra2wrq2pqqyrrKmqqaerp6inqKqpqKmppqSlpKGgpJ6flKKkoJqYnZ+enKelpKWmp6Wop6SkoKKhoaGinp+foaKhoZ+en6CeoaOhoqCen56dn5+enpyfoqWhnZubk5GVm5eVjoqMkJCWm5qZmDiXlZmZmZiXl5iZmZiXmZiWl5aXmZqZmZuamZqdm5uamZuZmZqbmpmcnJuamJycnp2dnZufnp6cm4WcEJ+dn56enJ6gnqChoJ2bn6CAiYmNjI6Mi4uMjIuOjIuLiouMjIuLioiKiY2MjYyLjYyPiI6PjYyMjI2KiYuNjImMjIuLio6KjI2OjoyLkY2Mjo6NkI+NjY2Mi4yQiouHiY6KjImIiImJiIeHiYiKiYqKi4qKiIuJioiJioeHhIWIhoSGgnyAgnuAhoWFhYSFgYR2goB9fndzgIaDhIWEhIOFhoWDhISDgoKDhYJ/f4KAgIGCg3+BgYCBhIKDg4OEgYKBgoGDgoSAgYGCg4OCgoGCgoGCgoGDg4CCgYSBgYKCgYKBgYGAgoKEgoGEg4SEg4aEg4SDg4J/gIOAgYGEg4OCgnyCgIKBgISBboKCg4KAgYGDgoKDgYOCg4KCgIGBgYKDgoKCgIKDg4KChIGCgYKCgoOEg4KCgoSDhISFhYWEhoWDg4SFhISFhoWGhoeHiIeIiYmIi4uKhoeGh4eGhoeFhoeGiIiKiomJjIuKi4uJioqKiYqJiYmKhYkciIiJi4uMi4uLioyMjIqKiouMjY2MjY2OjIuLjYaMKoqLjIyNjI2OjouMjY6NjIyOjYyMjY6OjY2Oj5GRkJGQkZGRkpKSkZGSkoeQAZGEkAyRkZGTkpKRkpGRkZKEkxyUk5STk5SUk5OUlJWWlZSTlJSVlZaUk5STkpKShZMkkpKTk5KSkZGRkJGQkJGQkZGRkJCQkZCRk5CRkpOSk5OSkpOShZMYlJGTlZSUlJWWlpWUlpWUk5WVl5eWlZaWhJUVmJWWlpeYl5eYmZiXl5eYmJqYmpqchZkUmpubmpqam5uZmpubmpuam5qZmZeFmF+ZmpiYl5eYl5eXlpaWl5aWl5eYmJeXmpmXmJiXlpaYl5eXmJiXlpeXmJeXlpaXmJiXl5iYmJmYl5iXl5aWlpWWlpaVlZaXlpeWlpaXl5WWlZWVlJSTk5SUlJOVlZSUlYWUDJWVlpaVlZSWlpeXl4SWDZSUlJOUk5SVlZSTlJOLlAmVlJWXl5aWlpSFlgGXhJgXlpeXmJiXlZaXl5eVlpSVlZaXl5WXlpeElheUlZaWlpeWmJaYlpeXlZWVlJaVlZWWloSVbpOVlZWWlpOUk5SUk5STlJSSk5KTkZKSkpCSkZCOkI+NjYyHi4SJjIuJhYmKjYWMjpCOj5GQkpCOjoqMjI2OjIqMi4uNjYuKioqLioyOi4yKiIqJi4yIioqHio2MioaGiIF+gYiGgXt6fX9/iIyLhIgCi4qEiSSKiIiLjIuJiIqLiYqLioqLjIuKi4qKiYmMiYmIiYiKjYuHioiEiYSKGYmLioiKiouHh4yJiYiGh4mKiImLioeGiYj/f/9//3//f/9//3//f/9/iH8CAgQAgLCzs7KysrOxrrCysLKyrq2tsbGysLCrrq6voq+xsbC0sbKysLOsr6+tq6qrrK6us7GwsK6ytLaztLe4tLOzt7m1sreyra2wqq6vrrGtr7Kxs7K0srSysK+rra2qr7Gysa6yrq2orKmsp6inp6ampaajpaWioaOlpKakpaapqKmkgKCalpSYmp+vpaOenZ6fnpubnJybm5ufmpmZkpWVlJKWmZqYl5ubnJqcnJyamJiZmZuYm5mbmpuYmpmYmpqcm5ucnpqanJqenJqdnp+fn56cnJuen52anJubm56anZycnJucm5mam5yenZ2cmpqZmpiXlJWVlJaWlpeWlZaWlZSUBpKVlZGQkIaTgJKUlJGSkpKRk5STlpSVlZOUlJaWlpeYl5OVlpWVmZmbmZuam5ybmJeYmpmanZ+enp6cnZ2en5+eoKGhoqChn5+gpKGioqKhoKCgo6SkpqOio6SlpKOioKGgo6Kio6WkpKOioqGhoaKkoaChn5qfn6CdoJ6eoKCfnZ6dnJ2fnp6dCJ2fnqCfnp6dhJ4HnZ+enp2en4SegJ+foKGgoaCfnZ+foJ+goaGhoqKhoqOjoaOkpKOkpqakpKWlpKWnpaSlpaWko6ampaempqWnp6ampqenpqinpaampaWnp6impKalpKKko6KhoaKioaCgoqGjo6SioqKjoqKjoqKkoZ+goqSjoqChoqOjpaWlpqWjpKWlp6mnqauqHKmqq6usraytr6+vrq+vsbGxsLCxsrKzs7OytLSEs3K1tbOztLW1trW2tbW3t7a2tbW0tbW0tbS0tLOysbOxsbCxsa+urrCwsLGxsLGxsLCvr6+wr66ur7CvsK+xsbOxsrOysbGysbGwsLGysbOytLS0srOztLO0tLSzsrOzs7KztLWysrW0tLSzsbGysbGwsK+EsA6ur6+tq62tq6yvrKutrISrN6yqrayqqqiqra2rq6uqq6ysqqqrraqpqqqoqauoqKmpqq2srKyqqaurq62vr7Cura6ytLSysrOEtIC2traztLW2t7a3uLi6ubm8u7m5ubq8v7y7uri6ube3ubm7t7i4t7a2uri5uLi7urq6vLy6urq+vbu7uri8vLu4t7a4ure4ubq3ubi2tra3uLa1s7SzsbGxra2tqqqnr7Gwrq+zsrKzsbOys7eysLG1tK+xsKyqra6wrq6ura+rqy6urausra2srK6sp6utrKqqqqyrq6iloqCfoqaqpZ+coKqioKKiop6fo6KinqCfhKMtop2hoJ6ho6KkpKSjoaKhoKGjoKKgnp+en6Cjo6KioqGhn6Chnp+eoaOlo6WohKkXqqqop6SnqKqsqqyrrK2trKutrqytrK2AoKKkoaChoqKgoaKipKKgnp+joqGhoZ2fn5+WoKGjoKShoqOhpJ+gn6Gdn6Cfo6ClpaOjoaOmpqaoqKmmqaeoq6elqKWhoqOfoqKio6CfoaShoaKhoaGioqCdnpyfo6Chn6GfnpqdnZ6cnZuZmZiYl5aXlpOTlpiYmpiXl5uam5cLlI6Mio2Oj5uXlpGEkjqQkZORkZKTlZGQko2PjY2OjpCQjo6SkpOSk5KRkpKRko6RkZKTkpGTj5ORkZKSkpOUk5OSk5ORlJOShJMmlJOUkpGQkZGRkJCRj5OSk5KSk5GQkpCPkJGRkZKRkZCPkI+Oj4+EjYWOP4yMjI6Mj46Li4uNjo2Mjo2MjY2Njo6OjI6NjpGOj5COj4+Oj46QkpGQkZCRkJOTk5GVlZOTkpKSkZKRlJWWloSVDJSVlZaWl5iZmJaXmIaXFZaXl5aXmJmZmpqZmpmZmJqamJiXmoWZR5qZmZiZmZmYmZmampiSl5iYl5iXlZWXl5aWlpWVlpaYl5eXmJmWlZeVmJiYlpaXl5iYmJeYmJeZmZiampqbmpuampqbmpqbhJwUmpydnpudnZ6dnp6dnZ2enp2en56InQ6cnZ2dnJ2enp6fnp+en4SehJ8FnZ2dnp2GnAWbnJubnISbCJycm5ucnJuahJsMmpqbm5uanZycnZychp0Wnp+eoJ+foKKhoaKhoaGio6Wjo6SkpISjCaSlpaWmp6alpYSmAaeEpgqnp6ioqKeoqamphKoGq6qpqamqhKkBqoSoF6mop6iop6eop6iop6eoqKinqKenqKinhKgRp6moqaioqainqKmoqKenqaqFqAqpqKinp6iop6enh6gIp6enqKanp6aEpwimpqWmpKWlpYSkhqMMoqKioaKioaOjoqKjiKKFpBKjo6Oko6KioqGio6KhoaGjpKSEozakpKOjo6SlpKWmqKeoqKipqaqpqaqoqaeoqaurqqurqaysrK6tra2sra6usK+trK+urKysq6yEq4Cqqqyqq6mrrKurq62qqqusrK2srK2prKysq6qqra2srKqtq6ytrKqrrKyqqqmoqKWlpqSjoqCgnqOmpqSkpaSlp6SmpqapqKajp6ikpaSgoKKhoqGho6KjoJ6hoJ+hoKGgoKCfnKCfn5+enqCgnp6cl5mYl5qemZWQlJ2YlZeZmB6VlpqYl5WYmJybnJqamJiXlpucmpucmpqZm5uampuEmQSYl5iXhJoqmZmamJqZl5iYmpmcm5qcnp6cmpydnpqZnJyfoJ2goJ2foKCdnp+dnp+eCoqMjIqKjIyLiouEjAOLiIiFiw+Hi4yLh4uLjIuNi4qMjY6FioCJjIyMiIyMioyKj46OjY+PkI6NjY6OjIqOjImJioeKiYyNiIaJjImIi4uMiYqJh4aHhYiKiImIioeFhYWHiYaFh4eEgoOEhISAe4GDhIOHhISEhYaIg4F/fn2AfnuEhYSAgoKDg3+BhIOCg4KGgYKDgYGAf4CAgH9/f4SChISFg0WDgoKAgICCgYKDg4KEg4OCgoKDgoKEgoKCg4KChYOCgoOEgYKCg4KCgYKBgYCBgoGCgoSCgoODgoSBgoSDgoGDgoODgoOEgUGAgIGDgoCAgoKBgoCDgYSCgIKCg4GAf4GCgoKDgIKDgoGCgYKEgoKEgoKBgIGCg4WEgoKDhISFhYaDhYSDhIWGhYSEE4aGhoWHhoeHiImHhYaIiYiFh4eEhoSHCoaGiImJiYuLioyFiyiHiIiJi4qHh4mLiYmJiouLjI2Mi4yJhoyLi4qLi4qLjIyKi4yLjIyLhIwPi4yLioqKjI2NjI2NjI6Oh402jI2Njo+Pj4yPkJKQkJGSkZGQkZKSkpOTkpKRkZGQkJCRk5KSkpGPj5CQkZCQkZGSkpOSk5OTh5Qdk5OUk5SUk5KSk5OSkpGSkpOTkZGRkpGRkpGSkpKEkTiPj5CPkJGQj5CQkZGSkJCSkpKTk5OSkZKTlJOTk5KSk5OUlZWVlpaUlJeYl5aXlpWXlpaXl5aWloSVhZYVmJiVl5aWl5eYmZmXmJiYmpqam5qahJkRmpqam5qam5qamJeWlpeYl5eEmBqZmJeXmJmYmJeYmJmYlpeWmJaXmJiXl5iYmISXHJiYlpaWlZaVlZaWlpeXlpaXlpeXl5aWlpWVlpaElxyWlpeXl5WXl5eVlpSVlpOTlJOTk5KRkpKTlJSThZSFlQyUlJWUlpaVlpaVlZSGkwWSk5STk4WUDZOUlJOVlJOVlJSUlpeFloSXD5SVlJWXlpaVl5eWlpeXmISXBpaVl5aWloSUZ5WVlJWVlJWUlJSWlpaVlJeUlJOVk5SVlZaXlZWVkZOUlZSSlJSUk5OSkpKTlZSUk5OTkpKTkpGQjo+QjY6MjYuMj4+Oi5CPkZOPkI+Oj4+Oi46Sj4+OjIyNi46Mi4yNjYuKjYuKjIyEijuJhYmJjIqIi42Li4uJh4eHhYaIh4SAhIqGgoSHh4aGi4iIh4mKjoyKiYqIiYeHioyKiYqMjIuNjYuLjISKMomJiImKiYmLiYiJiIqJhoiHh4aLiYeJiouJhoqIioeGiYeNjYmLioiJi4qKiImJiIuJ/3//f/9//3//f/9//3//f4h/AgIEAGmvsK6usLKztbGvsK6tsK6wrKytr6+urK6rqaerraywsa+sq6yqqKmqqquqqq6vrq6ur7G0sraytbO1sbG0s7O0t7WytbGysaytrbCvsbCwsbKwsbKzsKqppaeqrrOtr62rr62qqKqpramEqIClp6aooaScnqKopKSjpKKko6SjoqWgoKmrs66eoJ2cnJuempqcm5ucmJqbmZiXlZeUk5OWl5iYnJybnZybnJiam5ybmZqZmJaVmJqXmJeamp2anJmanJuenp2cm5ucm52am56cnJ6enJydn5mcm5manJuZmJudm5ucnJydmZqbmV+ZlpiYlpWWlZWTk5SUlZOVlZOTk5STkpGRlJSUk5STlZSVkpCQkZGTk5SVlJWVlpaXlJaXl5WVlJaXmJeZl5yampual5qampybmp2bnZ6dnZydoKCgop+fn6CioaGin4SiTaGjop+foaGhoKKko6ChpKOioKKioKOjpKKkpaSjoqKho6CjoqKgn52dnp6gnZ2fnp+fnpyfnJ2dnZyfnp+dnZ6bnZ2en52bmpyenZ2dhJ4Xn5+foJ+fnp6foKCfn6CioqKhn6GjoqOEpBujpKalpqempKSko6WkpaWkpKOjpKSYoaalpaaEpQWmpaampYSmFqenpaWlpqalpqWkpaSioqGio6KjoqGEohShoqGio6GioaCioqGhpKSjoaGio4ekOaOlpaSlp6ipq62rrKurrK6ur7Ctr6+tr7GxsrCysbGysrOxsbKwsLO0tLOxsbKytLS0s7O0tbW1s4W0ILW1tbS0s7OysrOysbKzsrKxsbGwsbKxr7GwsK+wsLCvhbAtrbCxsLCxsbCvsLCvr7GvrbCwsbKztbSzsrKztLOztbSzsrGys7GzsrKzsbKzhLIjsbGxsLCwr66vsLCvraywra2vr66ur6ytr62qq6ysrKqsq62Eqw+sqqqpqamqrKuqqaqrrayEqoCpqamnqKqrq6upqKiqq6utrK6ur62xsa+wr7Cxs7W0trW0s7S1tba2tre4uLe4ubq6uLe2ub24uLq6ubu6t7e4ubi3u7i3t7u6uri5ury9uLq6uLm5uLu4uLe2ubq8uba3t7i4uLm5tri1tbS0sbOzs7aysrGurq+vq6uqqqqusR2ysa6usbCvmomCq66trKyrq6mpqaqtrKurrK2xroSreKqsqaenqKmopqmpp6apqayqraurrayrrq+njoKGi6KnpqOgoaCkpaOjpKmmo6Gfn6CcnqCho6Oio6OhoqGhn5+enKCgn6KioKKgnZ+fnqCenJyenqChoKCioKKno6OmpKOlpKSlqamqqKapramnqa6rrq6wq66usWigoqCgoKKipKGfoJ+foaCgnZ+gn6GhnaCenJqfoZ+ho6CcnZ2dm52en56dnaGhoqGioqOlpKajo6OnpKSop6Wmp6WlpqOjo56foKKho56goqKgo6OioZ6empqdnaGdn6CdoJ6cm5ydnYSbgJqYmZeYlJWNkZaalpmWlpaYlZaYl5iSlJqcn56Tk5KSkZCUkpGSkZKSj5CRkI+Qjo+MjYyOjpCPkJKUk5KRk5KSkZOTj5GRkY+OkZKNj4+RkpOTlJGRkpGUlJSTkZGRkpSRkpOSkpGRkJKSko6QkpKQkZKRj5KRkY+Tk5KSkI+RBJCOjY+Ejg6NjYyNjo2Oi42Mi4yNjoSLE42OjYyNjI6Mjo2MjI2NjoyOjo6EkGmRjpGQkJCRkZCPkJGRkZSSkZOSj5OSk5SVlJWSk5SVlJWUlpWVl5eWlpaXl5eYlpmZl5eXmJeVlZeWmJaXmZmXmJqZmJeZmJeZmZubmpmamZmampqYmZmYmJiXl5eWl5aYl5aXmJeWlZWElh+Xl5iXl5eVlpaWl5eXlZeWlpeXmJeYmZiZmJqZmpiYhJoHm5ubnJubmoScB52enZ6en5+EngWdnZ2en4WdEpycnZ6TmZ2enZycnZ6enp2en4SehZ0EnJ2dnoachpsLmpqbm5uampubm5yEmoSbh5winZ2cnJ2enZydnp6en6CgoKGhoqChoaKjpKOho6OioqOkpYWkFaampKSlo6anpqenpqanp6aoqKenqoSpCKiqqqqpqqmohKkCqKmFqBqpqqqpqamnqKmoqKeop6enqKanp6emp6WnqISnAaaEp4KohacGpqenqKmph6gBp4SoAaeEqBWmp6empqWmpqWlpaalpKSko6OjpKKEoxukpKKjoqKkoqOkoaKjo6Oho6KioaGioqOhoaGGoi6jpKOhoqGhoKGgoaCjo6OioqKjoqKjpKOkpaajp6emp6aoqKiqqamqqqmpqquqhassqqytra2sq6mrrKurq6yrq6ypqqurrautrKqqq6qrqqurrK2qrKurqq2qrKmFq0CsrKqtrKyrrK2tq6yoqaqqp6mpqKinpqakpaSjoaOko6CjpaSko6OlpaSSgnucoqOeoJ+hn52enqCgoaGgn6Oihp8CnZuFnG+dnJyanZ2enqCfn6Ggnp6floF6gISYnJqamJmbm52ZmpufnZyZl5eYlpeZmpubmZmampubm5mZmZiamZiamZmbmJaYmJaWlpeWlpaXmJeXmZman5ycm5qamp2cm5ycn5ybnJ+cm5ufnaCfoJ2hoKMFi4yKiYqEiwiKi4uKi4iKh4WKgImMiYqHi4uJjI2Li4mLioiJjYqKiIeMjIqLjIuNjYyNi4uNjouJjZCNjI6Li42Ni4uKiYmPjIqJioqKiYmLi4iIh4eDg4aJhYaHh4mHhYaGiYiGh4WGh4SEgoKAgneAhIaDiIODg4SDgoaFhoCChIeDgIOFgoKDg4KAgYOAgoOAZ4GCgYGBfn9+gH9/gIB+f4CEhYODhIGBgYODgYOCgoKBgIN/gYGCgoODg4J/gYCDhIODg4GBgoJ/goOCgYKCgYGCgYCBgIKBg4OCgoSGg4GFg4OCgYGDg4CAg4SDgoGBgICBgH+CgYKFgT2AgoGAgoKAf4GBgoKDg4CAgoOCgYODg4SCg4ODgIGCgoOEgoWEg4SDg4WCg4SEf4WFhoeGhYWDg4WHh4WGhIcLhYaGiIiGiYmGiIiFhzCGhoeFh4eIi4mKioyMioiKiYiJiIuLjY6Oi4uNjYyLjYyMjIuJiYqLi4qMjIyLi4uEih6Li4mKi4uMjIyKi4qMjIuKiYqKi4yMjIuMjI2OjIyEjQOPj46FkISRJJKTk5OSkpSVlZSTk5KSkZKTkpOSkZCPkZCPkJCKj5CRkJGSkYSTEJKTkpOSkpOSk5OSkZGSkZCEkRaSkpCQkZCRkZCRkZCQkZGRkpGQj46OhI8CkI+HkFORkZCRkZGSk5KSkpOSkpSVlpWUlJaVlpWUlZSUlpaVlpSXl5aWlpeWlpaVl5iXmJeXlpeXlpeYmJmYmpmZmJiZmZqbmpqamZmYmpmbmpqbmpqZmYSaI5mbm5qamJqZmZmYl5mXlpeXlpiXl5eWlZSWl5eWlJaXlZWUhJValpWWl5eWl5eWlpeWlpaXl5aWl5aVlpaVlZaWl5aVlpaVlJWUlJOUlJKUk5SRk5STk5STlJSVlpWWlpaUk5WUk5OSk5OUk5OTlJSVlJSTk5WUkpKRkZKTkpKRiJNYkpKUkpSUlJOWlpWVk5WVlJeWl5aWlpiYmJaWlpeYl5aXl5eZmJeTlJWRk5WVlZSVlZWUlZaVlpSTlJWVlpSUlJaUk5aVlpOWlJWUlJKSk5SVlZWWlZWTlIWSgJSUkpCRkJKUkJCPjo6OjIuOj46LjY6Ojo+PkpCOfXNsiY6MiYyMi4mIi4mMi4yMjIqMjIuKi4qHioiIh4eIiYeIiYmIiImLi42KioyKiImJfm5rdHaHi4qIiYyLi4yIiYqOi4uJiIiJiImJiYyMiomKiYuNjYuKiomLioiJi4iJL4uHhoiIiIaHiIeGh4eGhYeHiouIiYaHh4iHh4aJiouJh4eKiYeHi4eKiomHi4yN/3//f/9//3//f/9//3//f4h/AgIEABeura2tr6ytsLO0r66urrCxr62sqqurqoSsgK+rrK+sqaeloqepqaenq6+trKqtrayuq62rs7GytK+0tbi0tLOxurGzsrOusa6vqq2sr66xra+uqayurrCss66trKurqKurq6mqqqepqKaqp6Wop6anqKehnqGgn6Chn56fo5+foKCioJ2iq5+Zk5iamZmdm5yem5uZmZqanJuZGJuZmZiYmJqam5manZybmZiWnJubmJmXmYSaH5iampiZm5udmZmanJqbm5yenJ2cmpudm5ycnZqcnZ2EmwSZmZqahZsPmpybmpmXl5mYmJaUl5aVhZY0lZSVk5WUkpKSlJSTkpKQk5aUk5WVlZSSk5KRk5WVlJSWlZeVl5iXl5iXmJmZlpiYmJWamoebWpqam5uam5ycm5yfoJ6enZ+fnZ+fn6CfoKCenqChn56foKCdoKCgoaGfoKGioqGhoKGjoKSkpKKkpqWmo6KgoqGkpKOfnp+goJ+fn52cnp2enZucnJ2cnp2enISdHJydnp2enp2cnZ6dnZ+fnqCfn5+en52gn5+goKCEnwqgoKChoKKio6KjhKRAo6OkpqWkpKWkpaWlpqSlpaSkpKOkpaSlpaamp6ampqenpqWmpaSmpqWkpKOkoqSjpKWkoaGio6KioaGioqGho4ShGKOio6GgoaKhoKGio6GioqKgoaOko6Slo4SmgKeoqaqqq6mpq6usrK6urK2trq6tr7Cvr7Cxsa+vsLCysrGys7SxsbGzs7Sys7Kys7S0tbSzs7SztLWztLS0sbO1s7S0srGxsbKysbGysLCvrrGwsbGwr6+vsLGvsLCvsLGxsLCxsLCvr6+wsa6usLCwsbO0srCxsLGwsbOzsLGyUrOzsrKysLGxsrKwsLKxsrCxsLGwsK+uraytra2urqyusK6rra6sra2rqqqsq6yqqqqsrKuqqamnqamqqKenqqioqaqqqamrqquoqKqqq6mqqquFqoCpqquvrqysrrCwsK6vsLOys7S1tre1s7S1tra1tbi2t7i3uLe2tre5t7e2ubu7ubi3tre2ubq5ubm7vbi7t7q5uLW2ura6t7m6ubi3t7i4trW0trW3tbS3trO0s7OwsbKysrGysrGxsbKwsLGwsbKxtbKxn5aZrbS5q6KgrKyrsYCtrKqnp6mrrKyrqKinqaysp6eoqKqnqqqpp6ipqqqrq6qqqKqrrayrrqimoJmZmo6RpqempaOjoJyaoKOjoqCgoJ+goaGioKCgnp6goZ+dnp+foJ+foJ+hoKCen5+bm5mbnJubn52fo6GfoaKenqCjoJ+lo6ampaatrKypqqisqwuoq6utsKywprCwr4KghZ6AoaOhn56goKCfn56fnZ6dnZ6fnp2inKCin5yamZeanpycnp6gnp+doaGjoZ+ioaWjpKSjqamppaWmpamkpaOloaOioZ6in6Cho6Gjop2eoaGhm6OenZucnpybnJ2cnJyZnZqYnJqWmZmYmJiXkZGUlJOUlpSUlZeRkpSVlpSQkpkaj4yNj5GPj5KTk5WRkpGQkZCTk4+QkI+Qj4+FkRGTkZGRko+TkZKRkY+SkZGSkoaRUpKSkZGQkpGSkpSVk5OTkZGTkpOSk5GRkpKRkZCRkY+SkpGRkZKQkZKRko+PjpCOjo2Mjo2MjI6Nj4+PjI+NjYyKjYyOjYuMjYqMj4yNjo6NjI6FjVmOjo2PjpCPkJGQkZGRkJGSkpCQkI+RkpKSk5OUkpOSkpKUlJWUk5OVlZSVlpSVlJSWlZWVlpaXlpWXl5aVlpWVlJWWlpeXlpaWl5iYmZiWmJaampubnJqZm4SaCJiampqYmJiXhJYjl5WWlZaWlpeWlZWVlpeXmJeWl5WWlpWXlpaWlZaWlpeXlpmEmDSZmJqZmJmampqZmpqbmpqbnJybm52dnJ2dnZ6fn56enp2enp6dnZydnpydnZ2cnJydnZyehZ0Onp6enZ2dnJ2dnZydnJ2FnISbhpoQmZqampmbm5qampmZmpqbmoSbCJycm5qbmpqbhJyFnhafn6GgoaKhoJ+hoqKioaKhoqKio6SjhKQVpaWkpKSmpqalpqalpqanqKiopqanhakEqqqpqIipFaioqKenqKeoqqqpqqenpqeop6enpoWnB6anp6eoqKeHpgmlpqamp6alpaaEpwiop6amp6eopoSnC6iop6eop6enpqWmhKUCpKWEpA6jo6OioqKjo6Oko6OmpYWjBaKioaKhhqIcoaGgoKGhn6CgoqChoqGhoqGioaKioaKio6KiooSjgKGjo6GjpKWlpKamp6impqeoqampqqmpqKirqqqrq6qqqausqqqpqKiqqqmqqausrKuqqqiqqamrq6yrrK6rrqmrqqupqKypq6qrrKyqqaqrqamqqauqqqinqqmnpqenpqenpqalpqalp6alpKSkpqampKmloZWQkKKopp2YmKGhHp6ioKCem52foKGhoZ+em52goZycnJ2dnJ6en5ucnoSfa52dm56eoKCen5mVjomPkYSEm5ucm5ybmJKTmpuamZiXmJaYmJmZmZqal5eZmJeYl5eYmZiZmZeZmZmYmZiWlZWUk5OVmJeYmpaXmpuWlZaYmJibmZucmpqdn6Cdnp6fnZucnZ2fnaGXoaGggIqLiIiJiIiKjIyHiIuJioqJiYiHiomIiYmIiIyHiIuJiYiIhomIh4mJiouIioiHi4qLioyKjo2NjYuPjI+OjYuIi42Mjo2KjIyMioyJiYiMhoqJhoWIiIqGioWFh4WGhYaGiIeHiIaIhoSEhYOFg4WFhIJ7fYSCgYKDgoOEhYKCFIKEhYF8fX12eH+BgYKAgIKChoCDhIEjg4N/gIF/gH9/gX+AgIGEg4KBgoCDgoKEgn+BgoKCgIGBgYOEgiKBgoCBgYOCg4WDhYOBgoGCg4KBgoGBgICAg4GBf4KEgoODhIRjg4SBg4GCgYOBgoOCgIGCgoODgX+CgIGAf4GCgoCAgYN/gIGAgYKBgoGCgoODgoGCgYCCgoKBgoWEg4OFg4SEg4OEg4CDg4KCg4SCg4OEhISGhYWEhIWFhoWHhoSGhoWHhoiHhIgshoiHh4eGhoiHhYSFhoWFhoiKiomJiIaHiIqKi4yOj4yOjIyNjo2Ojo6LioyGiiyJiYqKi4uLiYqKi4mJiouMi4uKi4yMjIuKiouLiomLjIuNjIuNjI2MjY2NjoWPL5CQkJGRkZKQkZCSkpOSkpOUlJOSkZGTkpKRkpKRkpKSkJCRkY+QkI+SkZCRkpKRhpKEkwWRkJGQkYSQAo+RhJAEj4+QjoSPCZGRkZCPkJCOjoWQGo+Pj5CNkJCQj4+PkJGRkZCRkZGQkpKRkpOThZQKk5WUlJSVlZSTlYWWJZeWlpeXlpWXlpaXmJiZmJiYl5eZmZycm5qZmZqamZqampmampqGmSuam5qZmpmZmJeZmZmYmJeXl5aYlZSVlpeWlZaVlZWWlZWVlpWVlpWUk5KShJYPl5aVlZaVlpeWlpeXmJaVhJYDlZWWhJUXlJSUk5OTkpKQkZGRk5OVlZSTlZSVlpaFlBOVlJOTlJSSkZOTkZOUk5OUkpORhZM3kJGRk5GSlJOTk5STkpKTlJSTkpGSlJSTkpSVlZWUlJSWlpWWlZaYmZeXmJeXl5aWlZaXlJaVk4SSgJSUk5WVk5WVk5aTlJWVlJSUmJKWlJWVk5KRlJOUlJaUlJKSkpOUk5OUlpWWk5GSkpKQkZGRkJGRkZCQko+QkI2OjY6Nj5GQj42EfH6CjpCJiYiJjIyJjYuLioiKi4yMi4yLioiKi4yIiYeHiIeKi4qGiIiKi4yLiYqJi4qLiYeKUIN/c3KAgXJyhoiMioyLiIGCiYuLiomJioeKi4qLiIqJiYiIiYmJi4yLi4qJiomJiIqKi4qIhoeGhIWHiIWGioSGh4aGh4aGhYSGh4iHhoeJhIoOiIuJh4aHh4qHi4KMi4r/f/9//3//f/9//3//f/9/iH8CAgQAd6ywr7Gwsa2urLCxsqyxsK2tr6yrra6sqqytsa6wrKqqqaioqKepq6urqa2rqKqrrayur7Sws7Kxtbe1uLW3trS4srCwsKyvsa2srKyoqq+urqamqKetrK+orK6urKqsqaqtq6qqraipqammqKaopqWlpZ2Zn6CfhJ6AnJ6goaKkoJ+gnpedk4WGk5qclpycnZqbnJudnZmbmpmZm52bnaCdo6GcnJqbmZiWk5WZlpeYmZaWjpSZmpqblpaWm5qYmJiXmZmampiYmJeamJucl5eZm5qbm5ycmZiZmJeXmZmamJubm5mZmJiYmpmZmJaXlZWVlJWUlJeVkpUnl5SRk5STk5KSkpGSkZKUlZSUk5KUkZCRlJSTlZaVlZWYl5aXlZWYhJZBl5iampuam5uXmJiYmpuamJmbnJ2bm52dnp2bnp+cnJ+dn5ygoaCgoqKhoJ+hn56goKCfoKGfoKSjoqGgoaOioqOEog2kpKOjn6CgoaKioaGhhZ8Inp6gnp6dnZ2Fnh2fnJ6dnJucnpybn52dnZ+cnJydn5+eoKCen56enoeggKKgoaOhoKCioqOhoKOkpqWjo6SmpKWmpqWlpqWlo6SkoqOmpaOkpKWlpqampaSlpaampqWmpqanp6WlpKSjo6OkpaSjo6KjoqGhoaOioaKhoKChoKChoqGfoaGhoqKhn6Cio6Wlo6SkpaSjpKSlpKSmp6ioqaipqKmqq6urrK2tT62sq62usLCsrrCwra2usbGwsbGysbGysrCxsrCwsLGzs7KytLO0tLG0tLOztLKytLS0s7OysbGysbKwr7Cxr7Cxs7KzsbGvrq6xsa+wsbCErWaurayvrrCvr7CxsLCvr7CwsbKvr7GwsK+xsLCwsrKysLGysbGysbCxsLKzsbCwr7GwsLGvrK2sraysr66urq2traurra2qqqusrq6srKypqqipqKmnqKiop6amp6elp6mopquqqKeEqQyop6qnqKeoqaurrKyErYCwr7CwsLO0sbKysrOys7W0s7S1trW2tLS1tbe2tra3ubi3t7i5t7a3t7W0trq5ubm4t7i3u7q5ubm6urW1tra6ube5ura2srW2tba1trS5t7OztbW2trO0s7OxsrKxsa+vr7Ows7KztLS2wbinnZGRoLCvr6aprqyrsK6prKelqRiqqqiopKGlp6iqp6ipqaqrqKeop6qnp6qEqSKqn6CekImD/YeboaKhoqakpKOjpKSnrayqp6Wko6Kjo6Oihp9BnpubnJ6cnJqdn56enJubmpqcm5ucm5udnpyen6CioqCgnp+hpaOlpaalpaSlpKiprKimqaitraussa2rrKywsKw0nqCfoqGgnp6en6GjnqCfn56gnpygoJ+dnp6gnaCfmpyenJubnJ2cnpycn52bnp+gnqKjqYSjPKanp6qmqaimqqSjo6OgoqKhn5+fnqCgn6Gbm5+bnp2gmJydoZubnJubn52cnJ2bm5yamZuampiYmJWOjYaTFJKRk5WVlpaTk5SRi5GHfIGMkZKMhZIRkZGTk5CTkpCQkZKTlZWUl5OEkhmRkI+Mj5GOj5CRjo6GjZCRkZKPj4+Tko+QhJEekJGQkZCQlJCRk5CPkJKQk5OSkpCPj5COj5CQkY+RhZAWjo+Qj46OjY+NjIyNjY2OkI2Mjo2LioSNgIuMjYuNi42Ojo2Njo6Ni4yNjo2Oj5CPkI+TkpGSkJGQjpGRj4+RkpGRkpKSkZGRk5KRkZKSkpSUk5SUlZWUlZWVk5SUk5WUlJaXl5mYmJWWl5WUlZaWlZaVlpaWmJeXmJaXmJiYmZiYl5iZmJiXmJaXmZiYl5iWl5aXlpaVlpWWApaWhpVFlpaXlpWVlZaVlZeVl5iYlpWWl5iXlpeZmJiXl5mbmpmYmZmYmZqbnJubmpybnJyanJydnZydnZ6dnZ6dnZydnZ2cnp2ch50HnJ2dnZydnoWdE56enZydnJycm5ucm5ycm5ycnJqFmwOampuFmgqZmZmYmZmYmJmZhZoIm5qbmpqbm5uFnAGehZ+CoIafBqCgoaKhoYSihaMbpKOkpaWlpqWmpqWlpqeop6anqKeop6inqKimhqgBp4uoDqmoqKeoqaenpqenp6amhKcOpqanpqempaWjpaalpqWEpwympaanpqanpaamp6eFph+npqalpqenpqalpqalpaWkpaWkpaSjpaOjpKSjpKOlhKQbpaSko6OioaGioaKio6OjoaKhoaCgnqCfn5+ghJ8EoaGgoIShIKKhoKGioaKhoqKioaKjpKSlpaWnp6WmpqepqKeoqaephKqAqKmpqaipqKepqKmpqKmqqqmoqqqrqqqoqKinqKyqq6urqaqprq6rqquqq6moqaqrq6mqqaqqp6uoqqyoqaasrKipqaapqKaop6akpqelpaSlpammp6enqKapsKKXkoeImKGko5mhop+doaCdop6coKCfnp6bmJqbnJ6cnZyen591nJydnJ6cnaCfnJ2enZeWlIV+eeh8kZeXlpWampuYmJmYmqCeoJ2bmpqanJuamJeYmZiYl5aVlZaYlZeUlpmYl5SWlpSUl5STlZSUlJaXl5iYl5mYlpWVlpqZmpucmpqXmZiZm52bmp2coJ6dnaGenp+doKGfOYmLh4qLiYmIiIuJioaLiomIi4mIjIuIiImJiouMioqIiImLjIuKh4qJiouLiYuJi4iJio2KjYyMjYWOgI2Nj4uJioqLjI2Ki4uMi4iKiIiEg4aDh4aJhIeHiYaGh4aGiYaGhoeGhoeIhoiGhoOGg355eYCBgYGDhICBg4KEgYOCgoN9d350aXV8gYOAgYKBgoGBg4OEgYSDgYGDgoOFh4WFgYKCg4CAgoGAgIGAgYCCf4F6foGBgYKAgoCDOoKAgoOAgYGCgoGBgYCCf4GDf4CBg4GDgoKCf3+BgYGAf4GFgoSCg4ODgoGDg4OBgoGDgn+BgoB/gIKEgTZ/f4GAgYGAgYGAgn+AgYGBf3+AgYCCgYOBgoKDg4SBhIKChYGEgoKEhIKEhYWDg4SEg4SEgoOEhAaDhIWEg4SEhQyGhoaDhoWFhoeFiIeEiF6HhoiHhoWGhYODg4WHh4iIh4eGhYeHiImIiIqJi4qKiYuJi42Mi4uMiYiIiImJiYqKiYmJiouLi4qJiYqLiomJiYqKiYuLi42Ni4uLiouLjIyLiouLjIyOj46OjY+PhZCEkRCSkZCRkZKRkZKRkZKSkZGQhJEjj5CRkZCRkZGQkJGRkpKTkZCQj5CQkZGRkpGSk5KSkJGQkY+EkEaRkI+Pjo6Pjo6Oj46PkZGQj46Oj46Njo+QkZCOj5CQj5CPj42OkI+Qj5CQjo+QkJCRkJCRkZGSkpKRkpOTkpOVk5OUkpSUhJU3lJSWlpWWlJaWlZaWl5eWlpaYmJmZmZiZmJeZmJmYmZiYmJeYl5eYl5eZmJmZmZiWmJiWmJiYloaXDpaVl5iWl5aXlZSWlZOWhJUnlJSTk5SVlZaUlZaWlJSXlpWVlpWXlZWVlJWUlJSTlJOTkZKSkZGShJMJlJSVlZOTlJaWhJUllJaWlJSUk5OSlJOUkpOSkZGRj5KTkpKSkJCQj5CRkpGTkpKRkYWSJ5SUlJKSk5SUlJOSlJOUlJSWlpWWlpWWl5aXlpeYmJaWlpWSlJSUlYSUgJOSk5OSk5SVk5OTlJKRkpSUlpSUlJaTlpWTk5SUk5OTkpGTlJGSk5KSkJOVk5SSk5KVlJOSkpOSkJCSkpGQkpOQkI+Qj5CNkY+QkJOUkX58gHd8h4aLjIiLjImKiYuJi4mJjY2MioqIhYeIiIiHiIiKiomIiIeHi4mJjIuJiYmKF4SCfnJsZcRrfYSFhoWIiIeGhomIh4uKhIsaiomKiomKiImKiYeKiIaIiYuKiIaJiYmIh4iEhy+GhomHhoaGhYaHhYWFiIaGhYaKhoiIhYeGhYeGhYeIiYeHh4qJiImLiomJiYuKiv9//3//f/9//3//f/9/pX8BfuF/AgIEAISvgLKysa6tr6uvra+rp6msrK2trKytq7Gtra6tsKqqrq+0q6+urauqra2wrq6xsbCzsq+0tbW0s7a3t7O6sq+xr6uuq62pp6ypqqmpq62pqKurqayqq6mtrq+qraWrq6utraqsqamrqKeopKejpqOfoKCfoqCfoKGgo6Kio6ShoKOfgJeTlZKQlJuanpybnZudmp2cmp2am5qamZual5ecnJqYmpuYmJeWlpaRmJSYmJqYmJiXmJmamZeYlpiYmZWYmpucnJmXm5WZmpiamJuZl5aZmZibnZqal5qampiYmZmYmpmZmZiXlZeXlpeWlpaSkpSUk5WUlZSWlZWVlJSTk5STU5KSkZOSlJWTlJSVk5ORkJKWlZWWl5aXlpaXlZSVlpeXlpaWlZeXmJmZmJmZmpiYmp2ampycnZuanZudnp+fnqCfn56foZ6hoKGin6CioKChn6Cgh6MQoqGgoKOioqKgoqSko6Oio4ShAaKEoBSfn5+enp6dnp6dnJ2dnpycm5ydnoSdL5yanZ2dnJ2dmp2bm5ydnZ2cnZ2en5+enp+fnp+enJ+foKGioaGipaOiop6ipKSkhqNepaSjo6Oko6Sjo6OloqWlpKWlpKSjo6Wlp6alpqempaSlpKajo6Oko6OjoqSkpKOjo6ChoaKhoqKgoaGgoaCgoaKhoKChoKChn6CgoaGgoqKhoqSlpKOjo6KjpaWnpYWnBqipqqqrqoWrUK6vsK6uraysq62urq+vsrGxsLCxsLGysrGysbGys7OzsrKys7KztLKxsbK0s7OzsK2ysbGwsLGwr6+urq+wsrOxsK+usK+vrK2vrqyurayshK0Tr7Ctrq2vsrCwsK6xsbGwsbGzsoSxOLKxsrKxsbGwsLGwsrGwsLCvsK+vr6z+n66ura6tq6ytra+tqqusq6usrKurrK+trqyrqaqpqqqphKiAp6anpqamp6akpqioqKenqKmpqKinqaipqKmqq6ysq6qrra+wsK+usrGwsLCxsLKysbKzsrS0tLW0tLS1srW2tLa2trm0trO0tbO0s7OytLa2trm6uLi6vbq5uLe3t7a3ubi3uLi6t7a3tLOysbS2t7e5tba2uLS0s7O1s66urq+Arq2ura2usLGsqqOox7ewsbersKyrpaSsra+so6+qqqemp6mmpaaipKSoqKanqqypqKiqp6WlpKWmp6mrqKOVg4CJlY2Bio6Sm6Smp6uqqqWmqq6koqWkpKOjo6Ghn52goKCfoJ6enpycn5ydnqCem5ycm5yamZqcm52dnZ6en54nnZ2foJ6en56hpKGlpKWko6Smp6aopqioqqmsqqyqrKysra2sq62tSaGfn6GioaGgn6CeoJ2gnZqcnpyeoKCeoJ2gnJ2gnqCdnqGgo5ufoKCeoaCfoKKgpKGio6OjqKampaWpp6ekqqWioqOfoJ+hnp2En4Cen6CenJ6emp2cn52fn56bnZabnJydnZyem5uem5mal5mXl5SRlJOTlpWTlJSVl5eXlpeWlJaRiIeJhoiKkJCSkJGTkpOPk5SRlJGRkJGPkY+PkZOUkY+Sk5CRj5CQjouQjJCQkpGRkY+Rj4+RkJGPkZGQj5GRkZKSkZCSjpGSjxuPkZGSjo+QkZGTkZGSj46Sko6Oj5COj5CRkI6EjxaNjY6OjoyNjY6Mjo2Ojo2NjYyMjYyNhYwojY2PjY6Pjo+NjIyMjo+OjpGRkJCPkJCQj5CRkJGQkI+QkJCSkpKRk4WSCpSSkpOTlJWVlJOElAKTloSVRZaVlpaXmJaXmJeWmJeXlpiXl5iXl5eWl5SVl5eYmJiZmZiXl5eYl5iYmJeWmZiXlZWVlJaWlZaVlJSVlJWUlJOUlpWUlISVDJaUlZaUlpSWlZWWl4SWKpeVmJiYmZqZmZmYl5mZmpqbm5ucnJubm5ebnJydnJycm5ubnJycm5ucm4WcDpqdnZ2enZydnZydnp6eh52EnAubnJuam5ucm5ybm4SaD5uampqZm5uZmJmampmamoSZiJoYm5qbm5ycnJ2cm5ydnZ+gn56fnp6en5+hhKADoaCfhKEIoqKio6OioqOEpQ2mpKSlo6SlpqampaamhqcaqKenqKenqKiop6eopKKnpqeoqKempqWlpqaHpyuop6enpqenpqampaWlpqWlpqempqSmqKamp6anp6amqKenqKenp6ampaWmhKUHpqWlpKWlpoWkNaWg7pSkpKKjpKOlpaSkpKKjpKSko6Oio6Kio6OgoKChoaGgoaCgoJ+en56eoJ+goKCioaGghqEDoqGihKEIoqOio6SkpKWEpg6lpqenqKmnqKmpqKmop4SpgKioqKmoqaimqaioq6appqanpqimpaWmqKqqq6uqqqquqayrqKmqqqqpqamqqampqqqnp6moqaqoqa+pqamrqamnqKmopaOkpqKjpKKjpaaloqGbmKiko6OpoaKgn5qZn6CjoJqin56enZ2dm5qbmZubnJubnJ2fn56dnZ2bnZqbWZubnZyaloh6eH+KfneBh42Sm5qYnpudm5ucoJmXm5qcmZqZl5mWl5iZmZmYl5eXlpWZlJeZmJeUlZSVlZSVlZaUlZWVl5aWl5aVl5aWlZWWmJmXmZiampmahJsSmZqbnZyfnJ6dnpyeoJ6enqCeYoqJhoqMiYqIiYmHiYeKh4WIioiLi4qIioqLiouLiYmHi46MjYqLiomMjI6Li42Lj4yLjIuLjI2Mi4yOjY6OkouIi4uJiouLiYqLioqKi4mLhoeKhoSIhomFh4iGhoiDhYeHhIYnhYSJhoSGhISBg39+f4GChYF/goSDg4SDgoKAhIV+dHJ2dXl8gICChIEwg3+EhYGEgoKBg4GCfoCBgoWBf4KEgoKBgIGCgIF+gYGCgIF/gIGBgIGAgoGCg4OChIE0goJ+g4CAgYB/goCCgIGCgoKEgYGCf4GEgn1+gYB/gYOEg4GDgYGDgH+CgYGAgIGBgH9/g4SCKYF/gICBgYGAgH+AgIF/gIKAgYKAgICCg4GAg4ODhIODgoGBgYSDhISDhYIYg4OEhoSCg4SEhoWEhYWFhIWDg4SChYWEhIUZhoWFh4aHiIeIioiIiYiIh4iFhYSFhoeHh4SEHIeJiImKjYmIiIqJh4iIiomKioqJiIeHiIiHhoeEiCWJiomIiImHiImIiIiJioqKiYmKioyJiYuMi4qJi4uLioqLjIyMhI2Cj4SQB5GRkZCRkY6EkQWSkpKRkYWQAo6PhpAGjJCQj4+PhJAKj4+SkpCQkZCPj4WQA46QkYaQTY+Qj4+Pjo+Pjo6Oj46Ojo+Pjo+Qjo2Ojo+Oj4+QkI+QkJCOjo+QkI+QkJCPkJGSkJCQkZKSkZGQkZGTk5OSkZCRkpGRk5ORkpOUk5OThJUTlJSUkpWVlpWWlZeWlpmYl5aXmISXCZaXl5iXl5eTk4WYB5eYl5SVlpaFlzCWlZiYmJeXl5aWlpeWlZaXlZWWlZSTkpOUk5WVlZaVlZWWlZWWlpWVk5OUkpOTkpKEkxmSkpKTk5KUk5OTkdyKk5OTlJSTk5SUlZSUhpUglJOSkpKUk5KQkpKTkpKSkZGRkJCRkJGRkJGRk5KSkZGEkh+RkZKUkpKTlJSUkpKQkpSUlZWWl5SVlpWVl5eWlZWVhZSAlZWUlZSUk5STj5KTlJWPlJGQk5GSkZGQkJOTk5WVk5aTlpKTkpGRlJSTkpGRkJOUk5OUkZCSkpSWlJSTkpGUlI+RkZCRj42Njo+PjpCNkI+QkY+RjIGDh4yLko2KiYiHh4uKi42HiomIiImIiYeGh4eIiYqIhYeJioqJh4eJh4pMiIeIh4qIh4FzZ2hxeGhndnp9goiGhYmJiYiHjI+KhomJjIiJiYiJiIiKiYqJh4aGiYeHioaJiIiIhoeFiYiGh4WGhIWGiIiFhoaFhoSFEISDhoeFhoWHhoSHh4mHhoSEhw2JiImHiYeJjYuLiYmI/3//f/9//3//f7h/AX7/f/9/zn8CAgQAS6ysrbCvsK+urqyoqqmnpqqnpqqnqq2oqaWvrKmnqaqpqKqqqqmpqqysr6mqrqywr7Gsrq+tsbGyr6+0trSxsqysqaaqq6qpp6ioo4SoYqqmqKirqKqtq6ypp6isqammp6inqaqoo6akpqalpaWjpaWimpycoKCgoqOgpKOio6Oio5+bm4+UlZiTlpmfnZydmpycnZydm5mampmYlJyZmJmYmZaVlpeXlpWUlJWTk5WUhJZWlJaVjoSVl5qXlJWXmJiZmZebmpiWl5WZmZeYmZqYmZmZm52Zm5iZmpuZm5ebnJiXmJeWlZeYlpeWlpaVk5OTlZGUlpOVlJOVlJSTlJSTk5SSlZKTlJWElICRkpKSk5WUlZWYlpaZmZqXl5qYl5iWlpiWl5WWmZuampmampeanJmZnJqcmpuanJ6dnpyen6Cen6CgoqGhoaKiop+hoaChoKOio6OjpaSjo6KioaGjoaOgn6KjoqKgpKOioqGgoJ+ioJ2dnp6dn56anJ2dmpybnJucnZyampqdnVebmZucnJubnJuenZycnZ2enpycnJ2dnp6fn56hnpyfoJ+hoaKhoqKhoaChoqGio6Cfo6OjoqGgoqKhkpOhoqSipKSkpaSlpqalpKSlpaaopqWkpKWkpKaEpCSlo6Wko6KioaKioqCgoaOio6GhoaKioZ+goaGgn6Ghn5+gn6CEnyOgoaGioqGhoaKkoqWlpqinqamoqKipqaenqKmqqqurq66sq4StG66vrq+tr7GwsK6vsa+ysbCysrGysbGzsbOzsoSzEbKysbCys7OysrGwsLGxsK6vhK4Dra6vhLBirq6wr6ytra2ur66tra2sra+vrq6wrq6tsLCvr7CysbKysbKzsrGysa+vsbGwsrKwsLCvr6+wsK+vrq+vrq+urrCtr62trq2tq62trKuqraurrK6urKytrKipqainp6ipqKmEqBunpqinqKeppqeop6ipqKipqqeoqKipqaipqquErAmtra6xsrGysbGEsICvsrGztLSys7Oys7SztbWzs7S0tbW1ubW0trOxr6+xsbO1sq+5tra2t7m5ure2uLe5u7u3uLm5uLi5tLe4s7W0tbO1t7O2ubS0ubKzsbKytLOwr66vsK+trausqqalq62fmqemoJm0raisqqiuq6Slqqmrqqiqp6qopqioqaesq2WsqqelqailpqGlpaSwuLSfiISEkZimk5iVlZ2op6alrK6tq6ypnqKkoaWkpKOlpKKfnp+ioaCfm6Gin5+hoZ6dnZyenJudnJmbmpucnJ+enJaZm56fm5ydm52cn6GhoaSmpKelpoSoEaeoq6iqq6qpraypq6qsqaqtIp2fnqCfoZ6fn56cnJyamZyamZyam52cnpidnJuZnp6dnZ+EnHidnp6hnJ+goKCioqCjo6Cko6WjoqSmpKKmoaGfnZ+dnpydnp6ZnZ2cnJ2bnZyem5yenJucmpqem5qWmZmampuamJmYm5mXmZiXl5WTjY+RlZOUlJORl5eVlpeXlpORj4OHi4yHjo6Sk5KUj5GRkpKUkpKRkpCNjZKEkTSSkIyOj5GPjY6Oj4yKjYyPjo+Qjo+OhH+Qj5GOj5CQkZCRkZCTkJCOj46RkI+QkpOPjpCRhJCEjx+OkIyPkI+Oj46Ojo+Pj46Nj46NjYyLjYyNjYuOjIyOhI0Qjo2Mjo6PjIyOjo2Ojo2LjISOM42OjpCPkpKRkZCRkY+QkZGQkZCQjpCRkpGSkZKSkJCTkZGTlJSTlJKTlZSTk5SUlpSWlYSWBZiXlpeVhZcXmZmYmJiZmJaXlpeWlpiWmJiWl5iXmJaFmAWXlpSWmISWJJWVlZOUlJWTlZOUlJaUlJSVk5STlJSVlZWUlJWUlpWUlpWUlYSWhJcPlpmYmZiZmpqYmpmamZmZhJoRm5ubnJybm5qcmpubmpqZiIuEmxCcnJydnJ2cnZydnJ2enZ2dhJwMnZubmp2cnZuam5uchJsLmpmbnJucm5uampmHmhCZmpuampmampqZmpuamZqahZwInZybnZ2dnp6FnwSenp6fhaCEoQWgoqGhooSjhKQQpaWjo6OkpKSlpqWmpaaopoSnAqanhKg6p6inpqamp6emp6anpqamp6anpqanp6anp6emp6Wjp6WkpqalpqampKWmpaWlpqalo6enpaamp6ampYSmBaWmpaamiqWGpAqjoqKko6Kho6WjhKQMo6OioaKio6OjoqKhhKIFoaCfoJ+FoISfXp6en5+gn6CgoaCgoKKioaKioaChoKGhoKKio6OjpKSlpqamp6iop6amp6inpqinqKeoqKenp6inp6iop6enqKinqKqmp6mnpqWkpqWop6WmraipqaqrqquqqKmpqaqErICrqqqsqKqrp6urqqipqqmqq6moqaepqKqoqKeko6OlpqSkpKKioJ2anZ6XkpiamIygnZmenpyhn5uXnZ6fn52enJ6cnJ2cnZyfnp6enZuenJucmJyamqOnoZF9fH2Kj5CFjIuMlJ2cl5ieoKGfop2TmZiXmpubnJuampiXmJuYl0SYlJiamZiXmJiXlZaVlJWTkpCUlZaVlZeVlJGTk5SUkpWUk5SUlZeYmZqbmZmam5qcnZuam56bnZ+dm5+dm52doJycoBKIiYeIiIiHioyIhomIh4aJiIaEh4CGiYWIiYeFiImIiYqIiIqJiYmKiomLjIqLi4qIi4yKio2MjImNjYyKjYqKh4aHiImJh4iIhYeHiYaIhYeJiISEh4WFhYSEh4aFg4WEhYSGhISEgoaFg4ODgYODf3qAfoSCgYKBf4SEgoGCg4aBgH5zdXt3dXx+goGDg39/gYWEhGmDg4OEgX+AgYGBgoGCgH5+g4OBfoCAgX99gH+Af4CCgICAdnOCgIN/gIKBgn+AgICCgH+AgX+BgICAgoKAgH+BgIGCfoJ/gH6AgXyBgYKBgYKCgYGCg4KBhIODgX+BgX+AgX+BgYGCgoGEgCp/g4ODf4CBgIGCgoGBgYKAgYKAgICCgoKDg4WDg4KBgoSEg4OChIKCg4KEgxqCgoOEgoOHhISChISDh4aGhYSFhYOHhoWFhoWHIYWHiIiJiYmIiIeGhoWFhYSGhYaJh4iHhomKiYqIiYuKiYSIEIqKioiHiImIiYiHiYmGiIeEiFSHh4eJiImJiYqKiYmJiomKioqLjImJi4qLioiJioqKiYqOjo6Pj46Pjo6Oj4+PkJCPkJCRj4+Oj46Qj4+QkI6Men6NjY2Pj46OkI+QkJGQkZCPj4+EkCGRkZCOj5CQj4+QkI+PkJCPj46Njo+Qj5CQj4+QkI+OjY6EjySNj5COjo6Pj4+Qj46OkI+QkI+Pj46Pj4+QkpKPkJGRkpKRj5CEkRKQkZCRk5KQkpORkZGSk5STkpWFkwKSlISVHpSUlpaXl5aWlpeXl5aWl5eXmJiXlpWWmJiYl5iYmYeYEJeXmJiWlZaXlZeVlpeYlpeElSGUk5KRkpKRkJWUlJSVlpWVlZSVlJOTk5GTk5STk5WTk5SEk0qSkpKRkZKTkpOSkpOSlJOUlZSVlJWVlJOUlZOSk5OSkZCQkZKSko+PkJCPj5GQj4+QkI6Qj5GQkpGSkZOTk5KTk5KRkpOSk5ORlIWTApSThpQmlZeWlZaVk5WVlpWWlZWVk5WWlZSUlJKSk5KSkZOPj5GQkZCRk5GEkxCVkpWTk5OSlJOTlJSVlJWRhpOAkZKRkJKSk5OUlpSUlpKRlI+SkZGQkpGSj46QkI6QkI2PkIqIh4aEfn6EhHaCg4CKjIeLi4d/iouKiYaKiImIiImIioiLiouKiYiJh4iLhYiGho2Mhnlsb3J6fXRzfn5/g4iIgoKKjIyLjI2Gh4iIi4yKi4qKi4qJioyJiYqGh4tAiYiIiYqIhoaGhYaGhYSFhIaFhYeHhYOFhIWFg4SEhIaEhIWFhYeIhYSGh4WIh4eGhoiFh4mHhouIhoqIioeHiv9//3//f/9//3//f/9//3+IfwICBACAra6ura2vrK2qq6alpKelqKipq6qqramnqKirqKeoqaempqaop6enqKKpqaqsqK6tsa6rrK2urq2qq6uoqqyqqKeppqeoqKmqqKiop6uppqumqaerqqatraypqKimqamrqaiipaWjpKSopaaooqKjoaCknKCdn6Ggn6KipaWkoKFgn6KfnqCajZiXmJadn56ampednZmbmZmYm5iZm5aUlpeYmZubmJuXmJeYl5aWl5aSkpWamJmYmJqWlZaXmZqYl5SYmZSXlpiZl5iYlpaXmJqXmZmamJial5mYlZeYmZiahJhGlZiVlpSTlJSXkZaVlZeWlZKUk5OVlZOWlZSVlZSSk5aVlpOSkpSUlpOTlJKSlJKUk5OSk5OVlJWXlpeYjZWZl5iYmZaYmYWaD5uamZiampuam5ycnpucnISeVaCgnp6fn5+hoJ+gpKOgoKKhoKGipKKgo6Sjo6GgoKKhoqOhoKCjoqOho5+hoqGgoqKioaGhn6Ggnp6enZ2dnJ2cm5ycmZucnJybm52bm5qcnJ2bm5qEnAOdnZuEnDibnJ2enp+enp+en6Cfn6ChoZ+gn6GioKGgoaKioaChoqOio6KhoqOko6KjpaSko6Oko6OkpqWko4SlE6OlpKWko6SkpqWlo6WjoqGhpKSEowmhoaKio6KjoqOEoguhoqKgoaCenqKioIWffaCioqGgoJ6goqako6Slp6anqKioqausq6epq6yqq62rq62sra6tra+vsK+vsbGwr62vr6+wsbCxsbGysa+xsrKzsrOzsrGzs7CwsLGzs7GysrGxsLCurrCtrq+wsbCur6+sra2tr7Curq6vrq2srK2trq2trauvra6vsLGxhK8LsK+vsLCwsbCvr7CGsRmwsa+tsLGwrq6tr6+wr6+trq+vrq6urKyshKslqqusq6qrq6mnp6qqq6mnqaimqKempqiop6eopqanpqenqKenp4SoAqephaqAqKinp6qtq6uurq+xsLCxsLGxsLCys7Gws7Gxs7GusbC0sbGusrKxsLOys7S0tbSztLKxtLKysrOxsbK2tbO4t7m6uLq3t7q2t7W0tLO2tbazt7i5trO1tLa0s7e1srq0trSzsq+vsq+vra6xq6qqqaWpqrKuoJyfoZWeraiuq6Y1p6WXmKOnn6qqq6msq62qqquopqWlqKenqainpKmoo5mcoq20rKebkaett6mjpaajop+fo6WEpASlpaWnhKNOpKWjpaKhoaGin5qeoJ+dnZ2cnZycmp+fm5qYmp2cm52cnJiZlpqXmpeVlZeWl5ueoaGgoaWjpKWlpKWnpaapp6uprK6qrqyqraurq6qsNJyfn5+dn52cm5uZmJiampqbm5ycnJ6cm5qbnZyam5ydnJucnJqbnJyanJyfoZygnqGgnp6EoYCenp6coaGfnpucnJ2cnJ6fnZycnJ+cm5+bnZ2fnZqgn5yamZqZmpudmpqXm5mVmJmbl5idlZeWlpOWjpORlJaVkpWTlpeXlJaSlZSUlY+DjY6OjJOSk5CRjZSRkJGPkZCRkJKRj4yOjo6PkJKPkZCQj5CPjY6Rj4yNj5ORkZCPkgSQjpCQhJFpjZCOjJGNj5CNj46OjY+PkY6QkJGPj5GNj46Ojo+PjZCPj42NjY+LjoyLi42Pi42Mjo+OjYyOjYyOjo2OjYyOjo2LjI2Oj42Njo2Nj42Mjo2Njo2Qjo6NjY2Pj4+RkJKShY2SkJGSkZGRhJIWk5KTkpOSk5KUk5OUk5STk5KVlZOVlYSUApOUhJUjl5eVlpiXlZeWl5eWmJmXl5WVl5iWlpiXlpeWlJiVlpaXmJeFmBSXmJaXlpaXlpWWlZSUlJWUlJOUlISTApWThpQXlZOUlZSUlZWVlpWVlpaVlZaWmJmXmJiEmRyamZmYmZiampmbmZqbmpqbm5qamJqampmam5qahJsXnJycm5ubnJ2cmpycm5ybnZ2cm5ydnJyEnYSbA52cm4WahZsOmpuampubmZmbmZqampmHmhKZmpqbnJucmpubnJuanJ6dnp6Fnxqgn5+goaGfn6GhoaCgn6GhoqKjpKSjpKOjo4SkFKWlpKWlpqalpaanpqempqWmpqanhKUYpqamqKempaWmpaWmp6empqempaamo6WmhaUIpKSlpKWmpqSEow6kpqWmpaalpaWmpaSlpISlPqalpaWmpaWlpKWlpaajo6SlpKOjo6SioqOko6Sko6KjpKSjoqGhoaKhoqKhoKCgn5+goJ+goJ6fn52goJ6eiJ9boKGgoaChn6CgoKGgoqKhoqKhoKChoaKko6SkpaWlqKaop6empaenp6anqaanqKamp6SnpqakpaWlpKWkpqenpqWkpaemp6inpqmpp6apqqWqqKqqqqmoqaqoqISngKqoqKipqa6sqaqpqqipqqamqaipqqmkpaampaWkoqWioqGgm56fpaWXk5eZi5Cbmp2enpuajYqVn5ieoJ+dn5+goaCgnZuampybnZ6em5qenZqRkpacoZyYkIaYm6CZlZebl5eWlJiampuam5mampqYmpmanZycm5uZmZmalpCXQpiYl5iVlZeWlJKVlJOTkZOVlJOVlZWRkZGUkpORj46PkZGTlZiYmJeZmJmampiam5iZmpmdnJ6em6CdnZ6bnZ6cnYCHiYeIh4mGiIeGhYWFhoeIh4iHiIWJiIaIh4mIiYmIioeJi4mHiYmJh4mLioyJiomMioiJh4mLiImHiYaIioiIhoaFh4mIiIiGiIeJiYiGiYWGhomGg4iJhoOCg4GChIaGh4KIhoCFhIWFhYyCgoSCgoF9gH+Cg4KBgoGCg4OAgIB/hISBhX92fHx7fIOBgX+AfoGBgoOAgoKEgIKDgYCAfoCAf4GAgn+AgIGBf4GAgH9/f4KCgoGAgYB/gYGCgoKAfoF/fn9+f4B+gX+AgIGAgX6BfoF+foF9f4B+gIB/fn+AgH9/f4B/gn9/fYGCf4CCg4KCgX+BgX6Cg4KCgYCBgQ6Af3+Bg4OAgIF/gIKBgISBLH+CgIJ/fn+BgYKEhIWFeoCFhISGhIOEg4SCg4aEhYSEgoOChIWDhYSGhISEhYVYhoWFhoSEhYWEhIeIhoaIiIWIh4aGhoiIhoWGh4eHhoiKh4eHiIaKh4iGhoiHiYiHiImHiYqLioqLi4mJiImJh4iHh4eJh4eIiYmJh4mKiomHiIiJiIeIioWJA4qLi4WJBIuJjIyFjhCNjYyNjI6Qj4+Oj4+Oj42OiI0DjI2NhI6CjYaOCY+Nj5CPj46Qj4SQBI+RkJGFkBKOj4+OjY2Oj4+Oj5CPkY+Qj5CEj4WOAo2PiI4bj4+OjY+Ojo6Pj46PkZKRkJKSkpSSkZCQkZKThZEFkJCRkpGEkwGShJMGkpSUlJOThJUilpWVlZaWlZWWlZWWlpeYlZaWl5aWlpeWl5eXlZWXl5aXmIWXH5aUlpaXl5eWlZSVlZSVlJSUlZOTkY+RkpKTlJOSk5SFkw2Uk5KTk5OSk5STlJOShJOAkZOTk5GQkZKTk5OUlJWVlJSVlpSUk5STk5STk5KQj5CQko+QkY+RkY2Nj4+Oj5GQj5CQj5GQkZGRkpKTkpORkpGSk5CSk5SUk5GRkpOSk5SSk5OVlZWWlJWWlZOUlZaWlZSWlJSVlJSWk5aVlZKQkJCPkI+RkJCQko+Qk5OUlJNzkpSUkZKTkpCTkpWUk5OQk5SSk5GRkI2UkpOUk5KXlpKVlZSVlJSRkpOQk5SQkJGRkpGPj46Sjo6MjIqKiYuPhYCGhnt5gYWEiYiDhHpze4eFhoiIg4iJjIqLjIqIiIiKiIqKiYeGi4qGf3yAgoSDg4F1g4SCYoaIhYeGf4WJiouKi4mHiYqJiomLjIyLi4yKiouLiIKIiYmJioeGh4WFhYaFhYSDhYeFhISFhoODgoWDg4KCgICChISFh4WFhIaEhIeGh4mGgoWFhYiHiYmHi4mHiYeIiYeI/3//f/9//3//f/9//3//f4h/AgIEAICsqqqpq6qqqaempqenpqutqKutra+tq6app6qoqKmpo6aoqKmnqaanpaisqa6vr62yq7Kwrq2urqanp6Ompaamqaynq6epqKenp6impammqaamqaipq6qpqKWkoaOkpqWmqaekpqamp6ako6ShpKKhnpycnp2goaKgoaOlpaWjooCho6KhnJ+dn5qem5aZnJmYnJydm5yXmZaXmJaZmZeUmpqbm52dmpmYl5eVmJaYlpeTlpeZmpiXmZiZlpSVlpiWlpaXl5aWmJeZmZiamZaVmJqYm5iZmJmZl5aWlZiZlpeXmpeWlpaVlJKTkpSUlZOSk5aXlZSVk5CSk5WWlJSUk3WRkZSTlJaUk5SUlJOWlJSTkpOXlJaWk5GVl5WWlpmXlZOTmpmXlZeYmZeXmZqZmpuam5qZmJmbnZybnp6dm5ucnJ2dnp+fnZ6fnJ6dn6GhoZ+hoKKfoKOioaCfoKCgn56goaGho6GhoaKjoaKgnp+joqOhoqGEnjmfoJ6dnp2bnJ6cnJ2cnZydnZ2cm5ycnZmcnJybmpqbnJ2cnJuem52dmp2cnJ2cnJ2fn5+en5+en6GEoAuhoqCgn5+foaCfoIWhHaCgoqKjpKKjpKOjoqOjo6Slo6SkpqalpqWkpKWmhKRRpaOipKOjoqOioaKio6OioqKho6Gio6OjoqGioaGgnqCfnZ6hoaCfnqCgoaGioqKhoaOio6OmpaWmoqSnp6aoqamqrKuqqqusra2srq6trbCwha8grq+vrq+ur7CwsLKwsbCxsbCxs7KysbCwsbGxs7OvsLGEsEOxsLGxsbCwsbCurq6vr7Gurq+vr66srq6trK2trK2trK6urayrq6usrK6tr7Gwr6+wsLCvsLCxr7CxsbGvsLCvr66vhq6Ar6+ura6wr6+tra6urq2vra2urqqqq6qpra2qq6qqqamnqKmpqaiop6empqalp6inp6inpqalpqemqKanqaqpqKmopqinqqurqqmsq6ysra6trrCwr6+ysK+wsbKysbKxsrO0s7GxsrGxr6+usK2xsrO1tLOzsrKztbW3t7Gys7IYsbW1tLS3t7e6t7ayt7W1tLWysbGvr7GzhLR9sbOxr7KysbS1ubS0tbWys6+urbGtqKysqKSjpaSmqqypqKagnJuhra2sraqX44qSlpmcoKOnqKmrpKaopaOkpaSoqailp6WkopuJhpSLh5SmmpuWoqKgpqKhoaKjpKOjoaGjo6WlpKaio6OfoZ+goZ+ioaCjoKGgnZ+dnJ6EnRWbnJucmpqbm5qbmJWalZmYlpWUjZaEkSCbnqCipKKnpKWnpaalpqmpqa2rrauusKyurqutsK+trAmdnJycnZucnZqFmHGenpmbnp+gnZ2ZnJudm5qcm5uZm5ydnJ6dnJqdnpyhoKCgpKClo6CfoaOdnZ2bnJydnZ+gmZ2cnZ2dnJyam5mem52bnZ6bm5ydnJqampaVl5iXmJmYlpmYlpqXl5eVlZeVlZSQkJGSlZWVkpKUmJWVlYWUHZCSkpKPko6Nj5GPjZKSkZCRjZCMjY6OkJGOi5KPhJIakZGQjY2Nj42Qjo+Mjo6QkY+PkZCQjo6NjI+Fjk6PjI+Ojo+Pj46OjY+Rj5CPkY6Oj46NjY2Qj4yOjY2OjI2MjYyKjIuNjY2MjI2OjoyMjo2Ki42PjoyMjYyLi4yMj4+NjI6OjoyOjo2Ojo2Fjz6NjpCOj4+Qjo6Ni5GQkZOTkpGSkZGSkZKTkpKTk5KSk5STkZOUkpKRk5OVlZSVlJSUlZGUk5WVlpeTlpWWlYeWGJeXlpWXl5eWmJiXlZaXlpiWlpaYl5eWl4SWA5WUloSVEpSUlZSVlJSVlJSTlJOSlJSTkoWUIJWVlJSVlJOUk5WUk5WVlZaWl5WXl5iXmJeYmJmYmJeZhpoImZqZmZqZmZqFmQeampqbmpuchZsInJybnJqcnZyEnSKcnZydnZubm5qbmpucnJubm5qZmpubmpqcm5qbmpubm5qahZkImpmZmpmamZuEmg+bnJqbnJubmpybnZ2anZ6FnwieoKGgn6CfoIShF6Kho6OjpKSjoqOioaKjoqOkpaWko6SkhaUtpKWmpaSlpaWmp6ampqWlpqWnpqWlpaSmpqWlpKWmpaWlpKWmpqWlpKSlpaSlhKQMpaSkpKKjpKWmpqWkh6UBpIelhKQMpaOkpaOjoaOjpKSkh6MEoqOko4aiFp+hoKCgoaGgoaCgn6Cen56en5+enp2Eng+foKCgn6Cfn5+hoJ+hn6CEoQaioKCgoqGEohujoqSjpKWkpaWop6eopqWkpaWmpqemp6anqKiEpiikoqGjoaSmp6inpqWlpKaoqKippKampaOlp6eoqKmnqaippqmmp6eohKVppqSlqKinqKeppaaop6WoqaqmqquspaalpKSnoqCjo6Ccmpybnp+gnZ+dmZaQlZ2goaCeiNSBiI+Qk5aXnJydn5udm5yZm5uanp6em5uZmpmVhX6Lhn6MmZCPiZaSjpSVl5eYmJiZmZiXhJlUm5yamZmXmZeXmJmamZeZl5ealZeVlZWUk5WWlZOSk5SVk5OTlJSPko2SkZGSjoePioyMjZSWlpmZl5uZmZybnJmZmpqcoJyenaChn6CgnJ2ioJ6dgIeHh4aHhYeLiIWEhYeFiImFh4eKi4eJhYiFh4WHh4eGh4uJh4mKhoeEh4uJiYuJiY2LjIqGh4uLiIiKiIeIh4WJioSLiYiGh4iGhoaFiYaGhoWHhYWEhoeFhoeCgYODgYSEhYKFg4GEhIODgoKEgoGBfX6BgIKDgoGAgoKCgYCAgIKBg4J+gYCAfn58f4GBfX2AgICCg4CBfn9/gIGCfn2DgYKCgYKBfn9/f36BgIJ/gH1/fn6Bf36BgIF/gICAgYCAgH6BgH1/gH+BgIB/gICBgn+Af4F+f35+gH5+goB/f3x/gX+Af39/foB+gYJ/f4CAf4GAgIKAfn6Cg4GCgIB/L35/f4CDgYF/gn+Bf4KCgYGCgoOAgoGBfoCCgIKCg4GAgH+Eg4SGhoaFg4KDhYSEhINEhIOEhoaDgIWGg4SFhISGh4WGhoWFhYKEhIWFhYaDhoaHhoeFhoeHh4iJiYiHh4eGh4iIiIaIiIeHhoiGiIeIh4eIhoeGiEKJiYeIiYeHhoeJiIiHiIeGiYiIhoeJiIiIiYiHioqJh4iIioqGiImJiomJiYuLjIyLjIuMjo6NjY2Pjo2Pjo6NjY2FjAGLh4xWjYyNjo2Nj5COjo6Mjo2OjZCQkI+Pj5GOj4+Oj5GRkI+PkJCOj4+Pjo6OkI6OkI+Qj5CRkI+QkZCOjo2Nj42Njo2OjY+Pjo6PkZCOj46Pjo6Pj5GRjpCEkReSkZGSkpKRkpKSkZGSkpOSlJSUk5WUlIaTHpSVlJOWlJSWl5WVlpaVlpaWlJWWlpeXlpeXlZWWlYSWLpWVlpeXlZWWl5eXlpaUlZSVlpaVlJaUk5SUlZSVlJSUk5CSlJWVlJWVk5OSkpOElIaTTJKSkpOUkpOTkpCSkpKTk5KSkpSSk5OTlZWTkpKSk5KSkZKRkZCTkpCSkpGQkI2Ojo+Pjo+Qjo2OkI6OkJGQkJGQkZGSkpKRkZKTk5KEkTWSk5OUk5KTlZOTlJWVlJWVlpWVlpWTkpOUlZWUlJSVlJWWlZSUlJKPjY6NkJGQkI+PjpCTk4SVFJCTk5KQkJKQkZKSkJGTkY6Sj5CPhpBjkZKUk5STkZOQkJORkpGSk5GRk5GOkJGQj4+Pi5GQi4iGiIWJiYyKi4mHhXuBhouOjIdzvHB4gIGAgoOGiImMiImGh4eJiYiKioqJiIeIh4V4bnx6bnyGfHZ1gHxye4OFhYaHhIgOhoaHh4iKi4mIiYiKiYmEikeJioiIioaJhoaGh4aHiIaFhoaDhoSFhIaGgYN/hISEg4B7gX6AfYCEhIWHhoSGhYaHhYeGhYWFh4mGh4eKiYaJioeIjImIif9//3//f/9//3//f/l/AX7/f41/AgIEAICpqaenp6WmpqWjpKqqqampqqqrp6uqpqaoo6Wppqeop6inpqiqqqurr62tsa+zsa+xsK2qp6mpp6mqpaWmoqekp6yorKqmqaajpKanp6WkrKipp6mtq6iqp6Sioqiop6Wmpqeppqelo6CgoaChpKGfoJ2fnJ+dnqGgoqCkoqSnp3Kip6CSkpaYoJ+empqbnJebnZybm5uZmZmYmJqZnJmZmJqam5uZm5qWlpeUlZWRmJeVmZqXmZqXlpeVlJOUk5OVlJiZl5iYlpubl5uZl5eXmZiVlpmXmpqYmJiZmJaZl5WWkpWWk5GUk5KSkpCSkZSTlJWElEiWk5ORkZOTlJSRkJKTk5KWlpWRk5SSlJWUk5aUlZOVlpKUlZSWlpeZlZaWlpeXlZWXl5iYmJmamJmbmZqbmpmbm5mZnJucnJyGnTSfoZ6goJ+fnp+hoZ2hoJ+ioaCen6ChoaCgoaGioaKioqGhoaKjoZ6ioKGgnqCipaGgoJ+ghJ8Knp2cm5ucnJ2cnYecL5uam52dnJybmpqZm5uanJydm5ycnZ6dnZ2en56enpygoKGgn6ChoaGgoKGfoKGhhaAPoaGho6Sjo6GhoaOjpKWliaQqpaSkpKWkpaWlpqWmpqWlo6GlpKKjpaKioqOko6Gho6KioqOioaGgoKKihqEFoKCgoqKEoBiho6OioaKipKChpKWlp6amp6inp6eoqaiEqQqqq6qrrK6tq62uhK8lrq6ura2usK+ur7Cwrq6vsbCwsLOxrq+wr7CwsLKxsK+wsK+vsISvfbCvrq6sra6urK+urq+urK6trKutrKusrK2srq2ura6sra6tra2vrq+wsK+ura6wr6+xsa+wr7Ctra6vsK+ura+wr62vr66ur66ur7Cxr6yvrqysra2srKuqq6yqq6urqqqoqKmrqqenqamopqioqKampaeop6WmpaWmpaanhKZcp6epqqupp6iqq6qpqKqqq6ysra2sra+wsK+wsbCwsrCxsbKysbS0tbOysbCurrCwsbGwsrK0tLeztbSxtLGytrKwsrOztri1sLO0tLe1trKxtLKwsrOzsrCws7CGsYCzsrCzs7a0s6+0s66xsLGurK6tramnqamoq6uoqaqsqqqoqaqoqKmltMCXoqWrq62ckIuRoqOnpqanqKiopKakoqalpaehn5qP7e3qmJfu+v6HhoCFiJGfoaGho6SjpKSlo6OmpKSjo6GgoqCenJ2fnp+fnp+enp+dn6Cdnp2dnDianJ2bm52emZyampiXmZiWk5KTkZKUlJujpKWkpaKlp6irp6imqqqsqKerqKqvq66rq6utq66sqoCcnZuampiZmpmXlp2cm5ycm5ycm52bmJmbl5ecmpubmpyamJudnJ6doaGgoZ6kop+io5+cmp2enZ6fm5udmZyZnJ+dn56bnZ2cm5uem5mXnpubmpqdm5ycl5eXlpqamJiXmZmbmJiWlpKVlpSWl5SUk4+TkJGTk5SSlZOUk5aZmDuTmJOHiIqOlJSRjZCPkI2PkpKQj5GQkY6Ojo+Pk4+Qj5GSkpSQkpGOj46NjY6Lj5COj5GOkJCOjo+NjYSMA42NjoSPDo2Sk42SkIyNi4+PjY6QhI6AkI+PkI2OjoyMiIuMi4mLi4uKi4mMio2NjYyLjI2Mjo2NjYyNi4yMjIqNjIyNjo6Oi42Nio2OjI2Pjo+Ojo+MjY6OjpCQko+PkZCPkJCRkpGSkY+RkZGSkpKRk5KSk5OSk5SSk5KTlJSUk5OUk5SUlZeWlZSUlZaSlpWUl5iWlZWFlmCXlpeXmJeYlZeXlpaVlZiXmJaWl5eYlpaXlZaWlZSTlJWUlJOUlJWTlJSTlJOUlJSTkpKUlZOVk5OUk5OUlZWUlZOVlZWXlpaVl5eVlZeVmJiZmZiZmZmamJqbmZeZmZmGmhObm5mZmpmbmpqbmpubmpqbnJydhZwYm5ycm5ybm5uanJybnJyXmpqbm5ybnJybhJqCmYWag5uEmg2ZmpmZmpqZmpuZmpmZhZoGm5qbm5uchp0Rnp2enZ6en6CfoJ+foKCgoaGEooajIKKio6Oko6SkpKWkpqWlpaampqSkpaWkpKWlpqanpaWmiaUFpqWjpaaGpQekpKSjpKOjhKQVpaSkpKWkpKKjpaSko6WkpaSlpaSjhqUwpKWlpaOioqOkpKSlpKOjoqKjo6Oko6KjpKSjoqKhoKGhop+goKCfoKCgn5+fnpydhJ8Gnp6en52ehJxlnp+foJ+gn6Cfnp+en5+gn5+foKKioqGioqOioqCioaKko6OkpKWkpqWkpqempKWkpqWnp6akpaampaWlo6Gjo6SkpaalpqepqKinp6alpqilo6Smpairqaakp6SmpaakpKelpaaFpQSmpaWmhKV6qKemqqipqaajp6mlp6WkpKGioqOinp+enaOin6GgoaCgn5+enp2fm6Gih5Sanp+dkYqFi5manJycm52cnZudmZabm5qbmJeVi+Pi3pCO2uzuf3t2fH2ElJiZmJmZmpmZm5qampiZmpiZmJmXlZaWl5eYmJaWlZaWlJeGlTiUlJWTk5WUkpWUk5GRkZCQj42LjIyPjpWbmpmYm5mZmpqcmpuamp6dm5uem5ugnJ6dnp6gm5+cnCiHiYaGhoWFh4aEhIiHiIaGhoeIhomHhIeJhISJhIWJh4mGhoiJiImIhIpzh4yJiYmKiYeFiImIiImJh4mGhoSFiIWIiIWIiImIhoqIhISKiIeHhIWFhoWDhIOBhYOCgYGEhYaFhYSBf4KEg4SFg4GBfoJ/gIGBgoCDgoOBg4KDf4KAdnt9foB/fXt/f4B+gIGCgoCBg4N+gICAgYV/gIWBBICBgoCEgS6CfYCAfoGAfoCAfn+Afn1+f399fn5+gYB/f32BgHyBgH59fH1/fn6Afn6AgIGBhH8UgH9/e3x+f3x+f4B+f32AgIGBgX+EgECBgoGAgYOCgYGAfoGAgICBgYF/gIF/gYGBgIKBgYCBg4CAgYGCgoOFgoGEg4ODgoSFhYaFgoKDhIOCg4OFhISHhIQZg4WDg4SEg4OEhoSFg4WHhoWEhIaFhIWEg4WGYYeGhoeIh4aHiYiGiIaHiIiGhoaHh4qJiIiJi4qIiIiHh4iHhoeIh4mJiYeHh4iJh4mIiYiIh4aFh4iHiYmIiYeIiIeIiIeGiYqKiIiJiIqJh4iJh4uKioyNjo6LjYyNjYyFjTSOjYyNjo+OjY2NjI6NjY6NjY+OjY6PjpCPkJCPjo2Pjo6Pj46Pjo+Ojo+PjJCNj5CQjo+PhI4rj42Mjo6Pjo6Njo+Pj46Ojo2Mjo2OjY6Pjo6OjY2OkY+Nj46PjI6Pj5CPj4WQHJKRkJCSkZCRkZKRk5KSk5OTlZWUlZWUlJOUk5OElA6VlpSVlZaVlpaWlZSUloSVhZYFlZSVlZWHlgWVlpaVlISWDpSUlJWWlJSVlJWVlpSUhJMrkJGSk5STlJOUlJWUkpGSk5SUlJOTkpKTkZGQkZKSkpOUk5KRkpOTk5KTkYSTLJKTkpGRkpKSkZGRkJCPkJCQkZCNjY2Ojo+Pj42OjY+Ojo2Njo+PkZCSkZGQhJICkZOEkgmRkZCQkpOSk5OEkjCTkpOVk5OUlZSUlJWUlJORk5OTlJSSkZSTkpKSkI2Pj5CQkJGPkI6RkZSUk5KQkZSEkAeRkpOQkZCQhI+AjY6Pj4+QkpGRkpKRkpOQk5KQkJGRj5OTk5GRjZKSjJCPkY+NjY2MjImKi4qMjYmLiouKjIuLiYmJjIaCeXJ/h4aJhn57d3uIiIiJiIeJiomGiYeFiYmKiYeHh37Mx8R/fb/O0XFraGtqcoGGh4eIiYiHh4qLiomGh4mIh4iIhoZLhoeJiIiGiIiHh4eEhoeGhoeHhYSEhYSEhoWChoSFgoGDg4OAgn+Af4CAhoqIiIeJhYSHhIeGh4WGiYeGhImHhoqGiIiJh4iHioeH/3//f/9//3//f/9//3+Yfwh+fn5/f35+fud/AgIEAICmpaOho6CjoqSgp6ampqmopqaqqaeoqaeiqqSjoqKkpKWlp6amp6aqqampqqmura2pqqqopqelo6Sip6Gio6KiqqynqqiqrrSmoa+lo6mrqayrq6qqqqGko6Wip6ijpqOlpKSjpKOfn52goJ+cmp6coZ+km6GfoaOioaGjpqaloVOmnYDs9YH8hKanopecnaCdmp2clJiXmpiamZucm5uam5ibm5qamZWVlJWUlJWYk5eUkpSXlpmalpeVlpSUl5WXmZmXmpmXlpSYlpiXl5uYlpeVl4WYgJeXlZiWl5aWmJKWk5eUlJSSkpORk5KUk5STlJWSk5OUk5KSkpSUlZKTkpCPkZGTk5SRkZGUlJWUmJaVl5aUlJWWlpaXlJWWmJeWlpaXl5mYl5eZmpiXl5qYmpmam5ubmZqbmpydnJ2dnJydnp2goJ2bnZ6cnp+fnp6gn5+dnZ6eSaCgnp+goaKen6Cho6Cen5+foJ6fn6Ghn56fnp6fn52fnZ2en5+enp2am5ubnJ2cm5ucmpqbnJ6fnJqbnJqampmam5qbm5ydnJ6EnQaenZydn52Enhifn56fn6KioZ+goaGioqGho6OkoaKipKGEooCko6Olo6OjpaakpKWko6SlpKOkpaalp6ampqWkpKSlo6OjpKSioaGio6GgoaKjoqChoaCgoaCioqKhoaGio6GgoaGioaGioaKgoqShoqKioaGjpKaopaaoqqinqauqqKipqqqqraysrK2rrKutrautra2ura+wr6+vsK+wsK+vrySxsLCxrq+vrq6wr7CwsbGwsbCwsK+xrq+wr66ura6urausrq6ErQqsqqqrq62traurhK0qrKuqqqyuq62urq2tr66usLCtrq+vr66vsLGvrq+wrq6vrq+wsLCvrrCvhK0DrImWhK2Eqyqqq6yrrK2rrampqaquq6usqqeopqioqKaop6WnpqanpqempqWlp6WlpqaEpS+nqaiqp6mrqKapqKenqq2trq2ura+wsK6usa+vsbGzs7K0s7OzsbKzsbGurK6uroSvgLKusrGzsK2zrqyrr6ywrq6vra+ytbS1s7KzsrGxsK6wsrCur6+xra+xsbC1sK2wrrKwsbGzs7CvrrCurK2oqK2uqKiqq6eqr6mpqKusrKmpqKaVj5+hsbS0sqy5p6Sin5KoqKamqKampaakpqinqqWnpKKjoZydmYn6+oyTi4yPZY6JgZSZoJ+foqKjpKWio6OkpaWjop2kpKOioKCfnqChoKGfoJ2cm52fm5ucm56dnJ2bnZybm5ydm56ZmZiXmpyYmZifoKOgoqGjpKOop6qmp6aqq6mpp6Wkp6WoqaeoqKmrpaWkgJqZl5WXlpiWl5SZmZiZm5uZmZybmZucm5Wbl5iZmJmamZecnZqampycnJ6cm6Gen5ydnpuanJqal5iemJibmZmen52cnJ6hpJqVoZyYnJycn52dnZydlpmampWZnZaXlZeWl5mWlpSTkpWVk5GQk5KXkpSQk5KVlJWSk5SXl5eTbpiReubufPR+l5aWjY+Qk4+PkpGKjo2PjpCPkJGQkZGSkZGPkpKRjpCOj42PjY+Mj4yKjpCNjo6Nj4yMi4yPjY6Ojo2Oj4+Oi5GOj46OkI6Mjo6NjY2PjY+NjY6PjY+NjI2Ki4uQjIuMi4qLiYuKhYwLjY2Mi4yMjY2MjY2EjEWKiYuLjIuNi4uLjY2OjY+Pjo+Pj46Pj5CQkI2QjpCQkJGRkZCTkZCQkJKSkpCSkJKSkpOTlJSTlJOUlJKUlJOSkpOSlpaEkx2Sk5SUk5OVlZSTlZOSlpaUlJWWlpSWlZaXlpWXloWVBJiXlpWElg+VlJaUlZOVlJWVlZSUk5OGlC6Tk5SUlJOSlJSVlJKTk5KUlJeWlpSVlpSWlpWVlpaXl5aWlpeXl5iYmJmZmJmZhJoHmZqam5ubmYWaFZubmpubmpuam5ubnJ2cnZ2cnJyam4ScE52cm5yampycm5qbmZqbmpqbmpqGmQOYmZmEmgmZmJiYmpuZmpqHmQOYmpmFmgWbnJycm4edB56en5+fnp+JoIaiBaOio6Kih6MDpKSlhKQGpaSlpaSkhKUepqWmpqWmpaWlpKWlpaSlpaSkpqSkpaSkpKOko6OjhKQio6OkpKOkpKShoqKjo6GhoqSkpKWkpaWkpKSlpaSko6WlpYmjBKSkpKKEozmioqB+jKGioqGgoqGhoZ+goKGhn6Cenp6fn56gn5+enp2enpycnp2dn56fn56enp+fnZ6enp+goKGEoB+foKChoaGioqCioqGkpaOjpaSlpKWkpKalpKampqSkh6aApKSjoaOjoKKkpKSloqWkpqWjpqKjn6Gio6GipKCkpaelpqalpaSkpaSkpaekpKWkpKKlp6WlqKajpaKko6WlpKWmpaSlo6Kjn56ipaCeoKKenqKgoJ6foaOfoZ+dkYmXl6KmpaWYpJmamJaMnZycnJ2cm5ycmpqbm5+am5qam5lwlJaQguvqhIyChImHgHqJkJWWlZiYmpubl5iampuamZqVmpiZmpmZl5eYmJiZmJmYlpWUlpOUlJOVlpSVk5SSkpSVl5OWkpGQkJOTkJSRmJmal5iVl5qXm5qemZqYnJ2anJmZmJqZmpqanJubm5eZmYCGhIaBhYWFhIOBhYWFhomHhYaIh4iHhoWDiIOEhYSFhYiGiIaGiIaJhIeHiIiKhoiJh4mIiIeGhISEioaFhYSDiImGh4eIioqGhIuGhIaGhIiHh4SFhYCEhoSAhYSChIGCgoSEgoKCgH+BhIKBgIF+g35+fYJ+gYSBf4KAg4SDf1CGgW/W3XLibHqBhXt9f4SBfn6BfH+CgICBf4CAf4CBgICAfoCBfn+Bf4KAgn+CfoB9fHyAf39/fn59fnx9gH9+f318fX9/fnyCfn9/fn5/fIR9c36AfoB/gH+BfX5+foF+fn+CfX9/fX1/fX9/gYB/gIGBf4GBgoKCgYKCgYF/gIF/fX5/f3+Afn5/gICBgIKDgYGAgIKBgoOGhIGDgoKDgoOCgoGFhYSEgoODhYKCg4OEhIaFhoSEhIKEhYSFhYSEg4SDhYWEhEaDhoWEg4OGhIKEhYSChYWDg4SFhYOFhYeIhoaGh4aHhYeFiIeGhoeHhoiHh4mGiIeIh4aGiImJh4eJiYeJiIiIiYmIiYiHhIg+hoiIh4iJiIiJiIiKiYuJiIiJiYmKiImJiouKjIyMjY6MjYuNjo6NjI2Njo6PjY6NjIyMjYyLjY2Njo2Ojo2FjxeRj4+Pjo+OkJCQjo2OjY2Ojo+Oj5GRkISOA4+PjYSOBoyOj42MjoSNA4yNjoeNCY6Njo6Nj5CQjoSPH46Ojo+Pjo6PkJCRkZCQkI+QkZCQkpKTkpOSk5OUk5OElBKSk5SUlZSUlJWUk5SUlZSUlZOElAWVlJWVlYWWEpWUlZOVlZWWlpaVk5SWlZWVloSVF5OTlZWVlJWUk5SSk5ORjY+Qk5ORkpGShJMIlJSTkpKUlJSFkgWRk5ORkYSSHZOTk5KSk5GQk5GQcYWRkpKRkZKRkpGRk5GPkI6QhY8TjoyOj46Njo6OjY2Njo6Pj5CPkISRGJCRkZKSkZGSk5KRkZCSkJKSkZGSkpKRkoSUAZaElYOUhpKAkZKUk5KSkJKQkI+PkI+PkJCPj5COkI+RkY+Qjo+MjY+Ojo6Rj42PkI+Oj5GPjo6RkJGSkpKRkZCQj46QkpGSkI+Qi5GRkI+OkJCQj4+MjIyLjIyOi4uKjYqJjIyMiYmKjYyLiouCfISChYuLiHqFg4SGhnyIi4uJiIiIiYmHh4cthomHiYeJi4eFhYJ3z811e3V2e3hxbHiAhISGiIeJiIeHh4mIiYmIiYSKiImJhIgMh4iIiYeHhoWFhoaDhoQph4WGg4OFhoeEg4GCgoGEhICEg4iHiYeGhYaHhoaFiIWFhIeIhoeFhYSEhQiIhoeHhYWEhv9/hH8Efn5/fv9//3//f/9//3//f5N/gn7qfwICBACAo6Cgn5yanaGkpaejpqeqqKqnqKmnqaqqp6WkpqmipKSmpKWpqqmkp6inq6amp6ekp6akrKypqqinp6imqKWjpKmpqaqqrK2wrqemqauprKyrrquqp6ikp6Okpainpaalo6WjpKain5menJ6inZ2doKCimpyfoqSgnZ6gnaWoo6Man6GJ/4mJiYWbj52inJmdmZeYmZiYl5aXlpaEm3qanJuampiYmJWWlJOVlJaYk5WVlJSalpmYlZWXlpSTlJaYmJialpibmZeVlpaUlpWVmJmXl5WUmJiVlJaXlpWVlpKRkZSQkJOUkpKSlJGQkZGRj5CWk5KUlpWVlZSUk5OVlZSTk5OSlJKUlZWUlJKUlJOVk5SXlpOTk4SUA5iUk4SVAZaEl0eYmJqYlpaYlpmYmpiZmZqam5ucnJ2fnJuenZycnJ2cm5ucnZudn5+enp2dn52goaCenZ6enp+enJ6gn6CfoZ+fnp+doaCgnoSfDp6dnJ2enp2fn56cnJ2dhJwFm5qZmZmFmxGZnJubnZucmpqcnJqZmZqbmoecgJ2bnJ6dnp6eoJ+en6ChoaCgoJ6goqOhn6KkoqGhoqKio6GcoaGhoqKhoqOjpKOjpKSjo6SjpaWlpKWjpKWjpqOjo6KhoqOhoKChoKCfoJ+enp6gn5ygn6ChoaGgnp+fn6CfoKGioqKhoaCioKGfoKChn6ChoaKjpaWmqKiop6epF6ysqampqKmqrausraytrausrq2traysha5Ira2urq2usK+urq6wr6+vrq6wsbCvsLCvr6+wr66ur6+ura2urq6srautrK2srKusraqqqqysq6usrKyrqqyrq6upqa2rq6qthKxkrq6trq+wrq+wr6+wsK2urrCxrq6vr7Cvr7Cwrqytrq6ur62tqqqsq6usqqmqq6uqqqmoqKaop6mrq6qqqammp6enpaeopqWlpaenpqWlpaSmpaSkpKOkpaanqKimpqqoqKeoqIWpD6yrq6usrK2srautrq6usYSvgLCwsrGysa+vra2rqqutqamsra6uraysraqsq6unraurra+ws7S2tLOzsrGysLGtr6qurK+trbGsrrCsrq2ssLGyrrKurLCsrKmurKynqaeqqaaqqamrsKyop6ipqqakpKKcj4ieo6aRif2LpbKsnp+cpKWkpqanpaWkpqapp6erG6ippaempqCJiYmNl5qZm5iXmJablKCfo6Gjo4SlBaSgo6ilhKZNo6ChoZ6eoJ6fnp2Zmpucmpycm5ybnJicnpubmpucmpudm56goKWkpKKhpKChn6GipKSnpqSmp6ampqempqalpaSipaeppqimpqago6GAl5SVlJKRkpWYlpqVmJmamp2am5uZmZ2bmZiXmJqXl5mYnJubmpyZmpybnpmZnJqZmpmZn56dnp6dnZ6bnZyamZ+dn56doKGgop2dnp6cnp6dnZycmpqYl5aYmpmYl5mXlJaVl5eTk46Vk5SVk5KUlZWVjI+TlZeUkZOVkpaYlZM9kpWB+ISDg3yMhpKWkY6QkI6Nj4+Oj46Oj46RkI+Qj5GRkJCPkI+Oj46NjY2OjoqNjouLkI2Oj46Pjo6MjISOQ4+QjY2PjY2MjYuLjouLjY6OjIuLjYyLjY6Qjo2KjIuKioyJiYuMi4yKjYuJiYiLiIiNi4yOjY6Njo6OjIyOjY2MjY2EjAaOjoyMi42EjlyPj42Njo2Oj4+NkI2Oj5COj5CQkI+QkJCRkI6QkZCQkZGRkpOTlJOTkpKUlJORlJOSkZKUk5KSkpORkpSWlZWTk5SSlpWVk5SUk5OVk5CTk5GTlJeXlpWWlJeXloWVEpaUk5OSlJSVlpSTk5SUk5OUk4SSB5OUlJOTlJOElDGTlJSSkpSWlZSVlJSVlJOWlpWUlZaVlpaVlpaYl5aYmZqYmJiamZmamZqamZqampuahJkQlZmamZmbmpucmpubm5ycnIebAZyEmwicmpuampmamoSZGJqamZmZmJaWl5iXmZiYl5eYl5eYmJmZmImZGJiZmJiZmpqam5ycmpycnZ6enZ2enp+enoSfhaAGn6ChoKGihaEBo4SiEqOjoqOjo6SjoqOioqOkpKSjo4SkGqWlpqWlpaSkpaWkpKSlpaWjpaSkoaKipKOiiqMlpaOjoqOio6GhpKKioaSjo6SjpKSjpKWko6Slo6WkpKSjo6OkpISjhKQeo6OkpKSjo6KioaGioaChoKGhoKCfnp2cnZyenp2dhJ4TnZydnJycnp+enpydoJ+dnp6fn4WeBZ2foJ+fhKCAoZ+hoqKho6KhoqKjoqGhoqKjoqOio6OjpKSjpKOjpaSmpKWlo6KhoqCgoKOfnqKho6KioaCgoKGenZugn6GioqOkpKakpKWmpKSipKKkn6Kio6Kjp6Cio6Cjo6KjpaajpqGhpaOkoKSjo52gnqOfnaCfn6GhoKGfn6Cgn52cm5cRjYWXm5eJgueEmKCgkpSSmJuEnEGampubm52am5+cnpucnJ2Uf4KBh42QkZGOjo+OkouVl5qWmZiamZqZmpianJuanJycmpeam5iWmZaYl5aUlJWVlISVOZSUkpWWlJOVlJWUlpWUlZaXm5uYl5ibmZqXmZiZmJybmZmamZmYm5mZmpiXmJmZmpuam5iYmJWWl4CEgoSDgoGBg4aEhYKFh4eEiIaGhYaFh4eEhoWHiYWHhIaHhYeGhoOGh4eIhoaFh4aGh4aJiYeIiIWGioiIhYWFh4aIh4aKi4iIiIeHh4WGhoSGh4WFg4GDg4GCg4GAg4KAgoKDg4KCfoOAgYKBgIOEgoF7fYGCgoJ/gIB/goWDgByBgnTpeXZ3bnJ6f4KBfIGBf3x+gH9/f4B+foGAhH8mgYF/f359foCAf39+gH98f4B9fYF+f35+gH5/fX59f399gICBfX+Ffi5/fnt7f358fX58fn18foCBfn98fXx+foB8fX6Afn1+fn9+fn2AfX2Cfn+CgIOChYE2goKAgIF/foF/foCBf3+AgIKCgYCBgYF+gYCAgoKBhIGDgYKAg4OBgoGDgoKBg4GCgoOBgoKChIQ5g4SCg4SGhYKFhoSDg4WCg4GChYKCg4WDhYODgoKGhoWCg4SDg4WDgIGDgoSDh4eHhomGiYmGhYiHhYYKh4WGhoeJh4aHh4SIAYeEhiuIh4iIh4mHiIiIh4eIiIiHiImHh4iHiIqJiImJiIiLioqKiIaIiYuLjIyLhIwUjYyNjY2OjY2Ojo2MjIuLjIuHi4yIjR2MjY6Oj46Pjo2NjY6Pjo6NjY6Nj46Ni42Pjo2MjYSOAY+EjQ6MioyLi42LjI2MjIyNjYSLFYyMi4yMjoyOjY2Oj42Njo6PjI6OjoWPBJCQj5GFkBCRkJGSkZOTkZKSkZKUlJKUhJMClZSEkwiUlZOTk5STk4WUD5WUlJWUlJSVk5SUlJOTkoSUDJOUkpOTlJWVk5OUkoaTCJSTkpGRkpKShpETkJKSk5OSk5KSk5OSkZOUkZKTk4WSE5GSkpCSk5KSkpGRkZKSk5ORkZKFkSWSkJCQjYyNjYyNjY6Pjo6Oj42NjYyOjY2Oj46PjoyOkJCRkZCQhJE5kJCPkJGQkZOUk5GTkZOTk5KSkpOSk5STkpKTk5SSkpCSkZGSkZGQkpKRjpCPkI+Nj5COjoyLjYqMhI+AjoyMjYqMi42Mjo2PkZCPjpCQj4+PjpCPjpGQko6RkJCPjpKNjY+KkI+OjZCRjZCNjJCOj4yPi4yJjIuKi4mMjIqOjYuKiomKiYuLiImIgXiGiIR6cL94goiJgIF+g4iHiIiHh4iJiIiIh4mMiYyIioqKgHF0cXl/gICBfXx9foMifIWGh4aHh4mKioiJiImKiYmKi4uJh4mIhoaKh4mGhIKDhISFO4OEhYSBg4WFhIODhIOEhIOGhYSHh4eGhoWHiIWHh4iHiIeHhoaFh4WGhYSGhoKFhoeGh4aHhYeGg4WF/3+EfwF+/3//f/9//3//f/V/AX7/f4x/AgIEAA+enZ2fm6ChoKakpKOmqaSEpwKqpISmgKeop6elpaaoqKeqp6ikpainpqWqqKqoqqqpqaisqq+qqKyrqamqqqmmpqWlpqampaShrKqrqqWorKmjoqOjp6SloKOkoKOioaOkoaGio6Cfn56koqGioJ2gn56en56anp2hn6CipKOenJSEkZiSlaKJmpygnpaTlZWYl5iYmZqcgJmYmZmbmpqYmJqZmZSWl5WWlJWXlpKUlZiVmJiWlJSTlpOTkpGUlpWXlpKYlpeXl5aXmZiXlpmXlZaWlZeVkpSTlJaWlZSVlZOVlJaVlJWTk5GRlJGSkY+SkZOSkpSTk5GUlJSTlZSTlJWUk5SUlJOUlJOVlZKTk5OVlZaVk5OUR5WUlpaUlJeYl5aUk5aWl5iZmZWWl5eYmpiamJqcmpmbm5mcn56enp2am5yenZufnJubnJ+gnZqanJ+gn56bnZyXnJ6hoaGghKFfn5+enZ6foKCfn5+gnZ2dnp2enp+enJ6dnpubnZuamZqcm5ubmpqZmZqbm5uYmJyYm5qcmpqZmJiZnJmZmpmZm5ybm5ycmpuenp6dnp+foKCfoJ+fn56fn6OhoKGioqKEoSKjo6KhoKKjoqGjo6OioqSioqOjoqOjpKSjo6WlpaSjpKKkhaIFoKGhn6CEnwuen6CfnZ6cn56dnYSeg6CEoRqgoKGhoaCfoJ+goaOio6KipKSjpqenp6amqISpJaisq6usqqusra6tra2urKutrqyrra6tra6ur6+urq2vr6+wsbCErg2wr6+trq6vr7CvsK+vhK0cr66srK6ur6yrqamsq6ysrKmrqqurqampq6yqqYSqHKmrrKurrK2sra2urq2rr6+vrq6usK6urq+ur6uEr4CwsLCxr6+ur6+wrq6trK2qq6yqq6qqq6yoqKenqaemp6eop6eoqKippaSop6imp6anpKWnpaampaalo6SlpaWko6WlpKWlpqanpqWmp6ioqaipp6ioqqmpra2trKysr6ywrq+xrq6wsa6sraysr62vrauop6qtqqitq6qurK2urICwsa6vrKyqq62tsbKur7KvrrCxr62sra6ur6+tsK6ysbGysLK1srGvsrCtra2rq6mqq62rrKyxra6op6SRiomipqWjpaWfnJuOgpOXsaqUmp+kp6aim6Okp6mmqKeopaWoqKmop6Skp6iloaGYmp2ZmpyeoJmYl5ijoqGnoainplumpKWkpqOjp6WlpaSmpKSko56cl5qdn56bmpyenp6fnZ6dnpuZnZ2dm52cnZyeoKGlpaempqWkpKCgpKWjo6SkqKSlpKWkpaakoqKipaOlpKSlpqOkpKOioKCfZJSRkpKRlJWUmpiXlZmbl5qam5ublpiamJmbm5mamZmZmp2bnJidmpmcmpmYnZucm52dnp+en5yhn52fnp2doJ2cmZyYmpqbmpmamJ2cnJybnJ2cmZeVlZmXl5WWl5WWlZWWl5OElICTlJSYlpSVlpOSkJKQk5OPkZOUk5KUl5aRkol9iY6JiJKBkJCTkYyKjoyPjo+Qj5CQjo+PkJGQkY+OkJCSjI2OjY+Mjo+Ni4yLjo2Ojo6MjY2Oi4qLiYyNjI6NjY+OjoyMjI2Qj42MjY2LjI2MjY6LjIqMjo2Oi42MiouMjoyLjT2LjIuLi4mKiomJiIuLjIuLjIyOjI2Mjo2MjI2NjYyNjouMjIyNjYuNjo2Pjo6Pjo6Njo6Qj46Nj4+Rj46OhJA/kpKPkZGQlJOQkI+Sk5KQk5KPkpSTk5OSk5STlJOSlJKRkpOTlZORkJGUlZSUk5SSjpOTlpWUlJaVlJSTlJWUhpaElQKUlYSUDZOSk5STkpOUk5STkpOEkgqVlJOTlJSTkpKVhZMhkZGTkpOUlJSTlJOVlZWUlJaUlZaXlpaWlZaYmJeZmJiahJkFmpmampqEmQyYmJmZmZqZmJmam5mEmgeZmpqam5qbhJoMm5qbmZqcmpqYmZmYhJmFmBqZl5eYmZiYlpiYmJeXmJiWl5iYmZiYmZiYmYSYDJmXmJmbmpubnJubm4SdhJ4inZ6fnp+fn6CfoKCgoaChoKGhoqGhoKCho6KioqGioqKkooSjBKSlpKKEowuko6OkpaalpKWlpYWjJ6SioqOjpKOko6Oko6OioqGjoqOjoqKhoqOioaKgoqCioqKjo6KjooajR6Sjo6Kjo6Oio6OjoaKgpKOko6SjpKSkpaSkpaSjo6OioqGhoKCgn6Gfn52enZ2enp2enJ+dnp6dnJ6dm5+en56fnp+en5+dhZ6An56fnp6fnZ+gn5+foJ+goZ+hoqGgoaCgoJ+goKChoqGhoaKhoqKkpKOlo6OjpaKfoKGhop+ioZ6cnJ6hnZ2hoJ6hnqCioaKjoaKgoaChoqGlpaOjpaKio6Olo6GipKOjoqOkoaOmo6ampaWmpqOlpKSioaChn5+io6Gjo6SioaBInpmGgoWbm52bmp2ZlpWKgZGOn5OIk5eYnJyakZiam52bnZybmpqenZ2cnJmZm56cl5WKkJSRk5OSk4+MioyYl5eamJybm5qZhJpGmZyampubnJmZmpqVlo+UlZeWlJOUlpWWl5WXlZaUkZWXlpSTlJSWl5aXmpucm5qYmZ6Zl5mZmpeZmpyYmZeZmZiZmJeXl4WYCpmZl5iWlpWWlJQChIKEgYCCg4mEg4KFg4KGhoeIiISGhoSGhoiGg4SFg4eIhoeFh4OCh4aFhYeFiIWFh4mIhoqHiYmGioeEhomGh4SEhYWGiIiGiYaIhoeHhYSHhYKBgYGDgoOAgYKAgYCDgoSAgoGDgX+BgoWCgYCAgH9/f4CBgX6CgYN+f4GDgoGAenR8fhh6cnp1gn6BgH9+f39+foF/gYB/fn9+foGEgCR/f4F9foB/f35/gH58fn5+f35/gHt8fn99fn17fX19gIB/gH6EfRuAgYF/fn9/fn+BfX18fX59fn+AgX5+gH1/f4GEfwiAfn9/fX1/foR9Nn+Af39/gIGAgYCBgICBgIB+f39+fn59gYCAgYF/gICBgX+AgIGCg4OBgoKCg4ODgIKCgYKEg4SBJISDgoGAg4WAgISDgoSGg4OFg4SFg4WEgYSDgoCEhIWDgYCBg4SEF4WDgISDhYWFhIWEhIODhYaGhoeIh4eHhIYih4aHiIiGhoWGiIeHhoeGh4WGiIWGh4aIh4eGh4aHh4eIhoSIIIeGhoeIioiHiImGiYmIiImJiIqLiIiHh4iKiouKjIyMhI0Gjo2Njo6OhIwJiouLi4yNjIyNhIxsi4uNjYyNjYyNjo6OjYyNjo6NjI6LjYuLjI2Mi4yMjI6OjYyNjI2NjIqKiYyMjYyNjY2Li4yMjYuLi4yMjIqLi4uMi4uLjIyNjo6Pjo6Oj46Mjo6Pjo2OkI+Pj5CQj5GRkpOQkZKRkpKSkJCRhJIJk5KTk5OUkZKThJQIk5GUlJOUlJKGkwqUk5WTkpOSkpGShJMfkpOSk5OQkpKTk5STkpOTkpCSkpGQkpGRkpCQkZGSkYSSAZOEkoWREJKSkZGSkpGOkpGRkZKSkpOElBSTkpOTkpKRkI+RkZCPj4+Oi4yLjYSOLY2Pi4uNjIyOjouNjZCPj4+QkI+Qj4+PkZCQj4+Qj5CQj5GRkZKSko+RkpKRkoWRCpCQkJGRj5GTkY+EkICRkI+RkJCPjo2Mi42Njo2NjYuKiouNjIuNi4yOi46Oj46Qjo+LjI2PjYyQj4yOj4yNj46RkI2Nj4+RkY+Qj5CNj5GRkY6OjI2OjoyMi4yOjIyOjYyNiI6MjIqGf3JveYqHiIeGi4qIioJ3gHuCd3aBhIOIioiChIOHiYiKiYmIh1yJioqKiISGiYqJhIB2gIOBgYJ+gHt4dnaEhISHhYWFhoeHh4mJiImLioiIiIyKiYqJhoeBhYaIhYSDhYaFhoWEhYWGhIKEhYWEhYSFhoWEhImHh4eGg4aLhoeJh4SIG4mCg4SFh4aFg4OCg4WFhYaGhISChISFgoODg/9//3//f/9//3//f/9//3+IfwICBACAo6GioZykpKOio6Kio6KlpaSinp6dn6GgoJ+hoqGmpqeop6empaKcnKKnqaWnrKaop6WloaOopKaoqquoq6errMWmn6GioKCgoaSfo6enoqSkoaGeop6hp6Sdo6KjoaadpKeloaChpKSkn6GgoKKgn5+fnZybnZ2cnZ6eoJ2fn52Am5mJjJadnKSemJufm52TjpSZm5edmJubm5mcmpmZmZqYmpeal5eUlpSWlJeVl5OWlpWWmJiXlJOTlJSVlpaVlZWWl5SVlZWWmJaVlJaUlpaXlpWVlpaXlJSTlpaUkZKYl5aVlpeSlpOTkpGSkpCOkJORkJORk5OQkJSUlJeUlJVrlJOWkpGQkZKWlpSVmJaWlJWUkJOTkpOWlpaUkZOWlZeXl5iUlpaXmZmalpiamZmYmJiZmpaZnJudnZqcn5ucn56cnJ6fnpyenJ2cnJ+fn56dnp6dnpydnp6cn5+hoKKgn52fn6CdnZ6fn6CEnkefnpycn52enZ2dnqCcnZycnZucnZ2dm5uenJyam5qamZqcnJmbmpqZm5qampibmZmZmpeZmZycnZydnJ2enp+eoJ2enp6foIShMKCgoJ+foKChoaKhoJ+goaCgoKKioaGgoKCioqGgoKSkoqSkpKWjpKOjoqGioaKhoIShDqKgn5+foJ+fnp2cn5yehJ8SoJ6enp2fnp6enZ6dn6Cgn6KfhKA/oaCipKWlo6Sjpqanp6anp6mop6ipqqyqqqurq6yurK6trK2rq6usq6qtrKyrqqytrqutra2urq2vsK6trq6uhK0Lrq+trq2ura2urq6ErQesrKusrauphKoSrKysq6uqqqioqqqrq6ysqqqqhasFrKusrq2HrAytrq6vr62urq6vrq6KrwGwhK47ra6urKuura2sqayrqaqnqKmqqKmnp6ipqqenp6alpqanpqSop6WlpqSjo6OkpKOlpKOjo6Sko6SkpaWEpoSoBqenqKmqp4SoIamoqqqqrayprK2sraysr6+urK2urKytq6mpqquqra6rq4WsgK6uq6+rrqmtqqusra6rsK+usLGur7Ktr7K1srKxsK+yrq6ur6yusLCoqKyoqqapp6usqqmsuLy3wLKJ1dLGzoulp6mlpKWfnp2Thf/xjKWqqKWlqamopqWlo6OkpKimp6ikpKWnoqKhopmVlJydoqGnoKKjm5qVqKeQo6OThOiJXJ6ipaioqKSkpKWhoqGgoKGhnp+jnqKenpuanp6foZ+fnpqcmpqYmp6eoJybnJygoqKhpaWkoqenoJ6ko6Klo6GioqOkpKWjpaSgnqKipqOhoqSjoqCioJ+enqChFpaWlJaRmJaWlpeVlJWWmJeYl5WRkZOElICWmJeamZibmpuamZiSk5eZm5mboJuYm5qbmJicmJydnp2bn56gn7CZl5iamJaZmJuXmZ2dl5mYlpaUlpKVmJeUl5WWlJiSlpeXlJSUlpaXlJeWlJWTkpWSkY+QkZGSk5GQkpGTkJCPjYGCipCQk4+OkZOOk4mFjI6QjZOPkpKQj4CRkJGRj5KRkY2OjY+MjYyMjY+NjouNj4yNjpCOjI2Mi4yNjY6PjY6Pjo2NjY6Oj46Mi42KjY6NjoyLj42Ni4uJjIuMi4uNjIuMjoyIjYuLi4qKi4mHioqKiIuIi4mIiY6NjY+MjI2MjI+Mi4qMjI2NjI6Oj46NjoyKjY2Mjo+PjlKOi4yQj5GPkJGPkZGRkJCPj5KRkI+RkJCSko+SlJKUlJKTlZGRlJOTkZSVlJKUkpKRkpSVlJSTk5SSk5GRk5OSlZSWlpaUlZOVlJWTk5SVlZWRhJQulZOTl5WWlZSTlJaSk5KSkpGTlJSTkpKVlJSRk5OUkZOVlJKTkpOQk5GSk5CTkoaTFJSUlZSVlpSWlpiWmJaXl5aYmZiXhJkTmJeYmZiamZqYmZeXl5iYmZeYmIWZJ5iXmJiZmJqbnJucm5qZmZeZmpmamZmZl5iYmZiXl5eZmJiYl5OXloaXCZiYmJmXl5iXl4aYhJoGmZmamJiahZsNnZycnZ2enZ+enJ6dnoSfBaChoaGfh6AuoaCgoaGgn6CgoKGgoqOhoqKhoqOioqOioqOioaKipKOko6SjpKSjo6Oio6Sjo4SiF6GioqOioqGioqKgoqKhoqKjo6SjoaGhiKOGogKjoYSiDKOioqKjo6SjoqOko4SkPKOkpKWkpKSjoqKioKGhoaCgoKGfnZ+en52fnJ2cnJ2en56enp+enZ6enp2gn56enpycnp2en5+fnp2dnYSeEJ+gn56foJ+foJ+goKGgn6GEn4Cen5+goaGhop+hoKGioaCioaKhoqGgnp+fnZ2foaCioZ+gn6CgoaGjoaGinp+dop+foKCeoKWjoKKlpKGmo6KmqKekpKWipKOio6Ojoqain5+hn5+eoZ6koaCgo6qrpq6ceLy8vMqJnpugnJqcmJiYjX/x24OXnJ6bnJ2cnpqamYCYmJqZnJqcnZqZm5yZmZiYkIuJkpSYmJ2WlZaPjomSmYKQlYl91n+Ul5mdnZyampmamJiamZiZmJaYnJaZl5iVkpaWl5aXlpWUlJKUk5WXlpeSlJOVlpiYmJybmpmdm5eWmpiWmZeXmZmYmJmamJmYlJOWlZiYlZeYl5aVl5aTkgOTlJVPg4SChICEg4SEgoGDgoOFhIiHhoKCgYOBhISDhYSGhYSHhoaFhYeEg4WFh4eFiYeFhYWGhYKHhIaHh4mEiYaJh5SFg4OGgYWIg4aEhYaHgoSEgICAgIKEg4SEgoN/gn6ChIOAgYGCgIOAg4SAgX9/gX59foB/gH2CgIB/gIB9f3x/c3l8f353eX+AgX2Be3h8f4J/gn2Cg32AgYCCgX+AgIF9f36Afn9/gYCAfn18fH99fn6AgH18fn19fn5+f36AgIB/fnx+f4CAf3x+e31+foB+gH2AfX19e3x+fH58foCAfn6AfXqAfoB/fn5+fXx9fXx7fHt+fnx9gYCBg4CBgoF/gYB/fX59f399gICBgYB/fn+Cf39/goOBgH9/gYGDgYGEgYKBg4CAgoGCg4KCgoGDhIKAgoKDhYODg4SBgoWEhIOEhoaDg4KDf4KEhIKCgoODBYGDgYCDhYQHhoeEhYSFhISFF4SFh4SGh4aGhoSEh4eIh4eGiIiFhoeHhIUchoaGhYeHh4SFhoaEhoeHhYiGh4aGhYWFhIiIhoWIgomFiA6KiIuJi4mIiIiMjIuMi4SMDIuMi42NjIuKiouKio6LDYyMjYuNjo2OjIuNjYuEjAeKiouLjYyMhItBjYyMjIuIi4qLiYqLi4yMjI2OjIuLi42LiYuNjYyNio2Ni4qJiIqLjI6Njo2OjY2Njo6Pjo2Mjo2OkY+PkJCRkZGEjxaRkI+PkJCQkZKRj5CQj5CRk5KQkJKThJIVk5KTkpKRkJGTkZKTk5OSkpGSkZGRhZIHk5GRk5KSkYSSF5GQkZCSk5KSkZOUk5KPkpCPj5GSkpGShpGAkJGRkZCRkZCQkJGRkpGTk5KSkZKRkpOTlJORkpOTk5KRkZCQj5GPjYyLjY2OjY6NjYyMjYyNjY2OkI+PjpCPjpCPkI+Pjo+Ojo6RkZCQj5CPkJCPkZGQj46RkZCRkpKRkJGQj5KQkI+OjY6NkI6QkJCPkI+NjY2MjYyMi4yMi40Ji4uKi4uMi42NhIyAjo6Oj42NjYuIipCOi4yLi4uPj42QkIuNjoyNkZKRkY6QjY+NjpGQj42NioiKi4uLjIuJj46MjI+MjIqKe2CZmqK1foqKi4eIioiKiYJ11MNzhYmIh4WIjIqHhYaGhomIiYeIiYaGiYuHhoaGgHt5f4GEhYeBgIJ6eXN0gmlzf3kmcLduhYeGiomKh4eIioeHiImIiYiFiImFiIaGg4KFg4WGiIWDg4SEgzWFhYeChISGhYaGh4qFhYSJioSDiIaEiISFiYWDhISFhIWEg4GCgYWFgoSFg4ODhYWCgYGCg/9//3//f/9//3//f+V/hH6Mf4J+sX8Bft1/AgIEAICinqKkoaOjo6aioKGfoKGen5ycn5+enaCfn6KfoaakpaOjoaemp5KhpqqkpqqpqaSmoqappqSkqKmjo6anpaKhm52gn6CjnZ+ioqWjoqKeop2cnaKeoaOmoqaio6OipKOlo6akpqGioaCmo6GgopydnaCen5yanJuenp+doJ+fnGWfl4GCiZCRkIiXn52dmpiKmJmdmZ2bm5mbmZeamZeYmpaYmJeXlpSVlZiXlZaVj5aRlpeWlZeUlJCRlZOVlJWXk5OWlpWWlJOWlpSSkpKTlJaVlZmWlZiVlJWTlpeVlJWUk5iWloSUDJGTk5GTk5GRkpKRk4SSJ5SUk5WWk5aUlJeVmJSSlJeWlZSUlZKUk5aVk5OSk5SWlpWUlpWTk4iWFZiXlpmamZqZm5mYmJiZm5ybm5ybm4SchJ1Lnpydm5qdnZ6dnJ2dnqChnZ6enZ+eoJ2gnZyen5+fnp2enp+enZ6fnp2dnZ6dnJ+empqbnZydnpydnJ2dm52cnZ2cnJ2cnJmZmpubhJoZnJqYmpuZmpibmpqanJuampybm56enZ2cnYafH56fnp2fnp+fn56enZ+foKGgoKChn6KhoJ6hoaGgn6GEoAehoKChoaGihaEKo6GhoqOhoqCgoYWgFZ2foKGfnp6enZ2en56en6Cgn56cm4aeC6Cgn6Cfn6Cio6GhhaM9paKjpaampqinqKepqaSYqayrqaqpqqutq6ytrq2urqyqq6uqq6usqqytrq2sra6urq2urq+trK2trq2sq4StKqytra+trq2tqqysrK2srKqpqaipqqmrq6qpp6inqKinqaioqaupq6uqq4WsIautrKyrrK2trqysra2sra6urq+urq6vsK+usbCura+trYSuKK2trqurq62srqmqqqipqKepqKqnqKuoqqioqaeoqKempqakpqempaWEpAWlpqOmpoWkY6Omo6SnpKaopqapqKipp6iqqamnqaioqaqnqK2wsa2vr66tsK+xsK6vra6vrK6qqKqnqKysramoq6yrp6qtraytr6+urauqqq2trK6vr7OxrrGvsK6xsa2urK+wqKypq6ykq4SsgKurraqqqaihmp2Xoq+/y8CQn6OZmaupqKqpqKKjnp6koqKlpKOjqainqaqrq62opKamo6SkpaSjoqChoqKbko6SkpGWnZ2jpqGdnaKLlZ+Lzc/U2uT7maWkpqekpaSooqKho6KgoZ6dn6ChoJ6gnp2cmp6cn52bnp6gnJigoKChMqCfo6Wjnp6foJ+cn5+ioKGjo6WhpaKgoaSipKKgop+hn5+jpqOhoKOenJ+dnp2dnqGegJeTlpeUlpWXmpaUk5OVlJOVkJKUlJOSk5SUlZWVl5ibmZiYm5mZi5ebnJiYnZydmZ2XmZ2bmpianJqYm5ubmZiVk5mXmZ2Vl5iXm5ibmJSYk5CSl5OWmJmVmpWVlpOXlZaTl5aZlZKUk5WVlZSUkJSRlJSSkpCQkZSSk5CSk5GQgJKJeHyBh4eGfYySkZCRjIKOjZGNk5CRjZCOjpCRj42PjI2Pjo2Njo2Oj46OjY+Ji4uOj4yNjYyNiYqPjY6MjY6KjI6Oj46OjI2NjIuJioqMjYyLj42KjY2KjIuMjouLjIyKjoqLiouLjIqLjImLi4qJioqLiYqKi4qMjIuNjYuOIYyMj4+Rjo2Njo6NjY2Pio6Njo6MjYuNj46Ojo2PjY6NjoWQFJGRkJCPkZKRkpGTk5GSkpGSlJKShJMDkpKRhZMHkZORkJOTk4WSDZSWkpOTkpOTlJGUlJOMlAuTk5KTk5OVk5OVlIaTBpKTlJKTkYSTCZKSlJWTkJKTkISSP5GSk5OTlZKUkZGTkpKTlJSVlJKTlZaWlJWWl5eWlpeXlZeWlZaXmJmYmJmZmJeZmZiYl5iXmZmZl5iYl5iXmYSXFJaXl5aWlpqYmZmamZubm5qamZmYhZkKmpmXmJiZmZiYmISXGZWVl5iXlpeWlJaWl5eXlpaYlpiZm5qYmpmFmgSZm5mchJ0Nnp2enJ2dmY6foaCen4SgZJ+goKGfoKGhoKCfoKCfoaCgoaGgoaGioaKhoqOjoaGioqKhoaChoaKioaKjo6KjoqOioqOhoqGioqGhoaCgoaKioaKho6GhoKCioaGho6GkpKKhoaKjoqKhoqKhoaKjo6KioqOGog2koqKjo6Wko6SkpKOkhaMPpKKioqGgoKGgop+fn52ehJ0Znpyfn52fnZ2enp2fnp2dnpyen56dnZ6dnYSeGZ+fn56enp+en56en56foJ+goaCgoZ+gn56EoA2foaGfn6Kjo6KjoqGghKIFoaShoKGEn4ChnZ+hnqCenKChn52foaGfoaKioqCgoaGioaGko6KlpKKlpKWio6OgoqOko56jnp+fmJ6foaGioaKjoaGgoJqXmZCXoaqvo4KVlpCPoKGenp+em5uZmJqZmpuamJmfnpydnp6go5yZnJyZmpqbmpqYl5eXlpCHhYqLi42UlJaZk2mPkJWAg4d1tbrC1N3zkZqZm5yZmpmcmZiYmpiWl5WUl5iYl5WWlJWVkpaVl5KSlpaXlZKXmJWXl5aZmpiTlpeZl5WXl5ebm5qYmJWYlZWVmZeYl5OWlJaTk5iZl5WWl5STlZKUkpKSlpSAhIKBg4GDhISDg4GCg4SFg4OAgoWEhICDhYOEgoSFhImHhoKEhYZ/hoeIg4SHiYmFh4WHiYSGhImIhISFhoSDgYKAhIGEiIKDgoGEhYeFgoV/f4GCgYOChH6DgYF/foF/fn6Eg4R/f3+BgoKEgoJ+gICCgIB/foB/gX6Af4F+fH5LgXhucHd5d3FneYF/gH98d39/gn2CgX9+f3+BfoF+foB6foCAf36AfoB/fX9/gH1+f35/f31+fnx7fH59f31+gHx9fn+Afn5+gIB/hHwKfX5+fIB/fX9/e4V+F3x+f32Afn19fX+AfX6AfX5/fn17fXx7hHxegIB/gIB/gYGAg4KFgX+AgYKAgICCfn5/goGAgH6BgoGBgYCBgIGAgIGCgYKCg4KCg4GCg4SFgoODg4SEgYKEgoOChIOEg4KCg4OCgoSDhIKCg4OEg4GDgoGCg4GDgYSDUIGEhYSEhIaEhYSEhYSEhYSEhYKDg4OEg4KGhIWFhoWFhoWGiIWHhIWFh4aFhoiIhoODhYOEg4WFhIaGhoWIhIaDhoaFiIiHh4mIh4eIiImKhIkUi4qLioiJiomKioqLjIuMjYyMjY2FiyGKiYyKjIqJiYiJiImJiouKiomJiIqJiouNjI+Ojo+PjIyEiwGMhIsHjY2Ni4uLjISLgomEixSJiIaJiImLiouJioqLjIqLjI2KioWLH4yKjY2Ojo6Pjo6Nj46Kgo2Pjo6Pj4+QkY6Oj5CPkJCFj4WQKpGRkZKRkJCSkpOSk5KRkZKSkZGPkJCRkZCRkpKRkZCRkJGQkZKRkZKSk4SRCJCQkJOSkpGRhJIOkZOTkZOTkZCQkZGSkY+IkQyQkJGRkZCRkJCQj5KEkwySkpGRkpORkZGSkpOEkYCQkJGQkI6NjIyLjIyMi4yNj4yNj4+Njo+PkZCOjo+Ojo+PjYyOjo6PkJGQkZGQkI+PkZGSj4+OjpCRkI6QkJCPjo+QjpCQjo+Oj4+OjpCRj4+QjYuMjI2Mjo2OjI2NjI2Li4qLi42LjYyJi46Ni4uNjIuNjo2NjI2LjI6PjY6OjwORkIyFkICOjY2IjYyKj4yLjYeLiomKjIyKjo2NjI2LioyAf4WJg3xufoOAf42LioyJiImKh4aLiYeIiIeFiYqJioqIiIuIhYqJh4iIiYeIhoaGh4R+dnZ7fXx8gYOChX96e39tamhYj5ypwszdg4qIiYqIiYeMiIeGiIiGiIaFiYeIiIWFhEOFhYKHhYeDgIaFh4SChoiFhoaFhomFgYOFhoWEhYWEh4eHg4aEh4SDgYWCg4J/g4GDfoCGhoSChISCg4OChIOCgYWC/3//f/9//3//f/9//3+lf4Z+3H8CAgQAgKagpaOfoqKfo6Ojn6Ohn5+en6CkoaKfoaSkpaSjpaGhp6KjpaOpp6uiqaOjo6aipKOnqaWnp6SjoaSnpaOhpaCcnqChnZ6joKGio6ShpaiooaKjoKSkp6SepJ+joaejo6KgpKGjpaCin52go6GgoZ2doJubnZmen52fnp6go6GhJZ2dgYWIiYaMh4eenZ+YhYCTlZKTmJucm5eXmZuVmpmVmJaXmJeElSCSlJWRlpOOlZOWlZOVlZiVlJGSlJaUk5WUlJGRlJSQlYSTgJSSlJWVk5SVk5WUlZOUlZaVlpKUlJWUk5WWk5OTkpOTlZSUlJKRkpOSkZGOk5WUk5OSlZKUk5OVlJOVlJSUlZSUlJORiJCTlJKTk5SXlZWTlpWTlpaYlpaYl5eWmJiXmpmbmpiZmpmZmpybm5mam56cnZqanJydnJ+cnZianJuae5yaoJ+doJ+enqCXnaCgnqCdnZ6enp+enZ2dnp2bnZ6dm52dnZycnZycm5udnJudnJ6enZydm5qdnJ2enJuampqbnJqamJmampmZl5eYmJycmpeYm5mZmpqbm52dnZ6dnp+gnp6hn5+dnp6enJ6foJ+fnJ+hn6Chn6Chn4SgGKKhn6GhoqGioqOhoqKio6Oio6Kho6KhooahFKKgoKGhoZ+eoJ+enp+enJ6enJ2dh5wbnp2cnJ+dnZ6enqCfn5+goaCgoqKjpKOko6WkhaYJp6enqKWnq6qshKqFqwisq62sqq2sqoWrOq2trKysra2trqyrraytq6usq6urrK2urq2tr62trKuqqqmpqauqq6qpqaiprKqpqaqqqKmoqKqnp6eEqQmqrK2srK2sqqmErBWrrayrq62urayura6vrq6ur66vra2GrgetraysrauthKwLqqqsqqqoqaqoqauFqhisqqmpqauppqenp6impqaop6amo6SjpaaEpA+mo6Wjo6SlpKKipKWmp6iEp4Cqqamqq62qqKepqaioq6ytra2vsKqtra2srK+trLCvqa2qp6mppqurrK6qqqutraqsrq2rramtrKuqrrCqrK2tq7Cyra+ur6usq6yrrauqqqmnqqeorKqsqqmoqKylp6afk4n/i4OKj42apaiboaOnpKmmpKmmqaytqKipqKimpoCkpqmpl9/X66CooqahoqChn6OioaaqnZqfoZugoainp6GfmZ+OmYOKmbDu6PL49YOepKKkpqSjoKOioKKgoqKgnJyfoaGfnZ6fnp+cnJqcoJyfnp6en56enaOjoaGfmZqZm5mXmZ2coKChoaqjoKKhopudoJ6hoJ6foqKhoZ+kowyhoZyan5+gnJ2goqCAl5OZlpWXl5WYlZeVlpWSlZGUlJaUlZOVmJeWmJaYlpabl5iXlpuXnJWbmZiYmpebmpucl5ubmpiXmJqYmZmalpeXmpmTlpqVmZiYmZubnZyVlJeVmJaYmJKXlJaTmJSWlZKWlJaYlJWQkJSWlJWVkJGTkJGRj5KSkpSSkZSWlZSAj5F4f4N/foR+fJCRkox8fIuNhoqPkpORjY6PkYuOj4uOjY6Oj42NjoyJjI6JjouHjYuPjYqLjpCNjouJjYyKjI2MjYqJjI6MjoqMi4qLiYyNjYmLi4uNjYyKi4uNi42Ki4yMiYmLjYqMi4qMi4yMi4yKh4mJiYiJhoyNjIuLi40Ki42NjY6Ojo+OjoaNJ4qCi42OjI+Ojo+Ojo2PkI6PkI+Qj5GSkZCQkI+SkZOQkZOTkZGRkoWRd5KRlJCPkpOSkJSRko+RkY+Pk5CUlJKWlZOUlouSk5OSlJOSkpSUk5STlJSUkpKUk5SSlJOTk5SSlJSVk5SSkpOSk5SSkZKSk5SSk5SUk5KSk5OUkJKOkZKSkZKSkpSSk5OUk5KTk5SUk5OUlZSUlZWWl5iVlpiYhZYflZeXl5iYmJmZl5iYl5iYl5iYmZiZmJeZl5iYmZmZl4WYDJmZmZiZmZqbmpqYmIWZBpqZmZiYmoSYBJeWl5eIlj6VlZWWl5aXlZeXlpaYmJeXl5iZmJmanJuZm5udnJ2enZydnZ2enZqcn56fnZ2en6Cen5+goJ+gn5+foJ+gn4agHp+goaGhoKCgoaGhoKChoKChoKGjoqKhoqKjo6KjooehAaKGoSigoqKhoqGhoKChn6CgoKGhoqSioqKjoaCjo6KioaOjoqGhoqKjo6KihaMRoqSjo6Sko6OkpaOjoqKioKKEoUifoKCenp+fnp2enp+fn56foJ2dnp6fnpyenp2enZ6en5+en56fnZ6en52enZ6cnpycnZ6enp2dnZ6fnp6fnp+goKKhoKKfoJ+EoICioqGfoaKinqKioaCgoqKgoqGboJ6bnJycoJ6dn5+goKGinqGioqCgnaGhn6Cjo52hoKGeoqWgoaGinqCfoqGjoKCfnZ2em56fnaKgoKGfop+gnZiOhPOIfYSDfpCZnpOVl56coJybn5ucnp+doKCen5ycm5yen4vOyeCTnJmblmCZmJeWmZiZm5yOjpSVkZaYnZyblZKNlISMenqHltbZ6PHufpWYlpqamZiVmJiWmJeZmZeTlJiYmJaTlZWWlpWSlJWWk5WUlZaYl5iWmJqZmJWRkpGUkI+RlJKXmJeXo5qElRuRkZeUlZaUlJeWlZeTl5iWlJKQkpOUkZOVl5QZhoGFhYOChYOFhIOCg4SBgoCChIaEhICCh4SEH4WDg4eEhIKCiYWHhImEhISHhIeFh4mEh4aEhYSChoOEhSuGgoSBgIOIgoeFhISFhYWEgoKCf4SDfoJ/g4GBfoSBf4B+g4GEhH+AgH2BhINEf3+Bfn+Bf4GAfoB8fYCBgYJ8fGtzd3R0dW1nfX6Afm9zf4J8fX+AgoF9f4CAfX5+fH5+fX5/fn9/f3t9f3t/fXt/en6EfQl/f359eH59fX6EfBx+fn9/gX2AfXx9enx9fHp8fH59gH59fXx+fn58hH0KfH5+fn9+foB/foR9CXp7e357fHl+f4R+coF+gYCBg4GBgoCAgIGBgYOAfnd/gIB/gH5/gYCBgIOEgIGCgYKBg4SCgYOCgIGBg4GAg4WDg4KEgoKDgYKDgYSBf4KDgoCCgIKAg4N/goN/goOBhISBhId+hIOEhIaEhISDhYWFg4SFhYWGiIWFg4aFhIWFCoaEhYSDg4OFhoaEhSaHhoiGh4aFhIWHh4SEg4SGh4WEhIaHhYaGh4aGiIeIiYeHiImHiISJOIqJiYqLi4iIi4uJioiHiYmIi4yKioqJiomIiYeIiIqIiImJiYqJiYqKi4uJi4qLi4qLjYyNjouLhIwfjYyLjI2MjY2Pjo2Njo2MjIyLi4uKiomKioiIioiKiYSLBoqKiouKioWLB4yMjY2Ni42EjiGQkI6Pjo6Ji46Nj4yNjI2OjY2MjY6Oj4+Ojo+Pjo6QkI+FkBWRkI+QkZCSkZGPj5GQkJGQkJKSko+EkS2QkZCQj4+RkZKSkZGPjo+RkpCSkpKTkZGSkpKRkJCSkpGQkpKRkJCPkJGRkZKEkQeQkJKSkJCQhZGAkpOSkZGSkZCRkpORkpKRk5KRj5CQkY6Njo2NjYyMjY2Mjo+Oj5CQjY2NkJGOjI6Ojo+Pjo6OjY2Ni46Oj5GPjY6Nj46Qjo6QkZCOj5CPjpCPjo+Pj5CPkJCOkY6PkZCPj46Pjo2LjIyMiY2MjYuLjY2Ljo6JjIqIiYmKjIuMjo0ijIuMjIqNjoyKjIyOjIyLjY6NjY2QjJGQjo6NjoqLjI6MjISKgIuLh4mKh4yLi4yKkIuNjIqDeNV3bHNsZnmGh398g4mIjYuIi4mKi4yLjIyLi4iIhIaJi3WqqsOAh4WGhYeGhIWIhomJiHt7gIKAg4SKhoiCgXl/cXhrZG9xtcPZ5d1yhYmFiIiIhoWJhoaIh4iIh4GFiIiIhoKFhoaGhIOEhYWCO4WDg4WGhoeEhIiIh4V/goKEgYGDg4GEhoaEj4WDhoOCfX2FhIODgYCEgoKFgoOFhIODgIGBgoGDgoeD/3//f/9//3//f/9/3X8BfqF/g36if4V+3X8CAgQAgKWkoaKioKChoJ+joqOgo56en6Sio6SoqaSmqqaop6Sko6KjpqWhoaSjo5yeoqSop6KipqSlpKiipaWipZ6go6GgpKGjoqWjoqSkpqeioqSfoaOmo6Omo6WgpKWkoKKjnZ+goKGhm6Kfm5yalp+hoKGdnpyZmpmbnZyfnqGipJ+hZJ+ekoyMjomIiYaon5yM746VmZqYmJeYlpmXm5qWlZiWmpiWmZmYl5WXl5WXlZaUlJOWlJGRk5WTlpSTlZGSlJOUk5OTlZWTlJaTlZWRlZaSk5WVlZOVkZCVlJSUlZWTlJSUk5OEkn+Uk5KTkZKTkpOTk5STlJWSkpKVkpOSk5WVkpKTkZSVkpGTkZCRk5OTkpGQkJGTkZKVlpeXl5aWmZWYl5aVlpaYlJiamZqZmpqZmJqXmpubmZmZmpyZnZ6fnZ+bnJ6em5uam5qcnZ2dn52fnJuenpyenp+gnp+gop2fnp+gnZ2chJ1HnJqdnJ2cnZ6dmpubm5qdm5ucmpucnpiZnZ2ampqbmpqbmZqZmZqamJeXmpmXmJibmpiZm5qYmZqZnJuam5ybm5ucnZ6dnZ+Enmmdnp6eoKGgop6foaKgn6CfoKGioKGgn52hoqSko6Kho6OioqOhoKWjoqKhoqGhoaChoqKhn56foJ6gn56dn5+dnZ6fnZ2dnpycnJucnpyam5ydnJydnZ6fnp+enp6foKCfn6ChoqOjpKWEpC+lpqanqampqqiop6moqKiqrayrrKysqaqtqqurrKurra6sra2srKusrK2srayqq4WsFq2tra+vrq6trK2sqqqsqqirq6uqqKiFqjWpqqmpqKipqaqqrKyrqKqnqKqrq6qrrayrq6ytrKusq6usra2vrq6sr6+ur7Cur7CvrK6uroWtK66sra2tq6yqrKyrq6qpqKiop6qpp6anqqmqq6mnqqqqp6elpqimpqmnp6aFpRmkpKaopqSjpqalpaajpaWmpqWop6alpaaohKqAq6upqqynpqqsq66rqq2uraqrq6qtqqyuraqsramqqaqqq6mvrq2srKqqqqmorKqsrqyqqaqpqqysq6mrqaipqqutqqyoraupq6qrrKyoq6mrqq2pqqmsp6elpJyRhoiEh+bfiomNiI2hqamnp6mmqKapqaeoqaeoqqenpKSlm4WAgYiGmairqaehnqKnpKCeorCkqamvraqio6Oin5Wa3oiTmpejuKSbmJGGiZmcn6KfpKOho6Ggo6OhoKSioqCfoJ2dop+dnZ+dnp2cnJ2fn5+eoJ6do6KgoJ6amZeUmpydoaCgoqCdm56foaCgn6GenZ+cn6Kho6KjoKOjoqCdoaAHn5mfpKOioYCXl5SVlpWWl5eUlpeXlZeTkpKXlZaXmpqXmJ2ZmpiYmZiXl5mbmJiZmpmSk5iam5uXmJuXmpidlpqbmZmVmJmZl5aTl5mcmZiamZmbl5aak5eYmpaWmZeVlZiXl5OVlJGSkpWSlZCVk46RjomSk5OVkZCQj42Nj5OQk5KSlJaUlF+Rj4eEhoaAgYF7l5SRg+KIjY+PjY+Njo2OkJGPiouPjZCOjI+Rj46OjY6Mj4yNjY6Mj46KiouNjIuLjIuKiYuMi4uMjIyNjI6Ni4uOiouMioyLi4yLi4mIjIuMi42MioSLgIqKi4qLjIyLjIiLi4qKi4yKioqNiYqKjIqKioyMjYuMjoyNjYyLjo2Mi4yNjIyLi4yLjYqMjo+Pj5GRj5COkZGOjo+PjoyPkJGRkZKPjo+SkJKTkpGQkpKSjpKSlJGTkJKUlJCRkJGQkZCSkpOSlJKPk5SSlJOTlZOUlJaSlJSUNZWTlJKTk5SUlJOUkpOUlJWVkpKTlZKTkpKRkZGSko6OkpORkpKTkpKTkJKSkZGRj5CRkpKQhZEgkpOTkpKUkpWTk5STlJWWlZaWlpWWlpeXlpWWlpaXl5eEmAKZmISXTpiYmZmYl5SYmZmZmpiYmpqYmZmYl5uZmZmYmZiZmZmYmJqYmJaXmZiZmJiYmpmYmJaXl5iXl5eZl5aWl5aVlZWWl5WVlZaWl5eXlpaWl4SYDZmam5qbm5ucnZ2cnZ2Fngednpycm56fhJ4eoKCfnp+gn6Cfn5+eoKCfoaCfoKCfoKGgoKGgn6GhhKAUoaKjoqKko6Kjo6GhoqKgoaGgoJ+FoDyhoaGioaGgn56goKGhoqCfn6Cio6OhoaGgoaGioqGho6GhoqGho6KjoqKjoqOjoqOkpKKipaOjpaSjo6OEon6goJ6fn6Cfn5+en5+en56dn56fn6Chn56gn5+enpydnJ2cnp2dn52enpycnJ2cnp6dnZ6cnJyfnZ+fn6CfoJ2en52dnqChoKCgoaCgoZ6doKGfoaGfoKGfn6CdnqCen6CgnqGhnp6dnZ6fnaCgoJ+goJ+fnp6gnZ+gn5+eoJ6EoICen5ydnaCgoJ2fm6Gfnp+fn6Cgnp+en6CloKGfoJ6fnJuWjIKDf3/U0oKChH+Elp6fnZ+fnZ2cnqCdoJ+cnJ6bnJudnIx7e4OCkp+enZyWlZidmpiVl56WnJyhoJ2ZmZeWlIyRyn6Eh4WQnJCOjomBhZCVlZiVmJiWmZaWmJiZlyyWmZeVlJaTk5WVlpWVlJWTlpeVl5iWlpeXlpiXmJeUj5CSjJCUlZeWlpeWlISVHZSUlZSTk5SSlJSWmJaYlJeYmJSSlpWSjZKYlpWVgIKEgYGDgoaGhYGEhYODg4CCgYODhYSHh4WFiYaEhIKEhYSDhYOFg4WIhn6BhoaFh4KCh4SHg4iEhoWEhISFhoaDhISEg4iGhISGhYeDg4WAgIKGgYKFgYF/g4ODgIGAfn5/g3+DgYOBfoB/foCBg4KBf4B9fX5/hH5/gICBgX+AgH5/d3d8enVydGx7fn5y0nyBgoB9fn1+fX6Afn58e35+gH59fYF+fn9+gH1+fX5/fn1/fn18eX1+fXt9fHx8f359fX1/fn5+g4J+e397fH56fHx9fHt8fnp+fX59fH58fHx+fH1+fXt/f39+gHx+fX19fn9+fXx+e319fn5+fH5/JH9+f4OCgICAfoGAgH1+gICAf359fX99foCCgoGDhIODgYOCf4WAgIKDgYGBgoCBgIOBhISCg4GDg4OAgoOCgYODg4SEgH9/gIGAgIKBgoCDgYCEhIWEgoSDg4OChYKChYSGhYWEhYSFhoaFhYSFhYOGh4SEhoWEhoWBhIOEh4aCgoWGhYeChYSGhYOGhoWFhYSEg4WEg4aGhYaFhYaHh4aIhoiGhYeHdIeIiIeIh4mIiYmJh4aJiouKiYiIioqKi4uJh4iHhoqKiIqJh4SJiouKjYyKiYqKjIuKiYyKiouMjIqMiomKi42MjIuMjImMjI2Pj46MjYyNi42Mi42NjIuMjYqJioiJioqJiYqLjIqKjIuMjI2NjIyNjY2MhI0Fjo2Oj46FjQ+LjI2Mi4uMjYyMjI2Pj4+IjgmPj46Ojo+Qj4+FkDuPj5CQj4+PkJCRk5OSk5KSk5KQkZGOjI+PjpCQkJGSkY+RkJGTkZCQj4+QkZGQj4+QkI+QkJGQj4+PkISRMJCRj4+QkJCRkJCPkJCPkJGQkpGSkJCRkZKTlJOTk5GSkZGNjI2Ojo6NjY2MjIyNjoSND4+OjpCPjpCPj42Oi42MjoWNgIyNjo2MjY2Mjo+PjpCOjo2Oj5GOjY6OjoqNjo6Pj5CPj46Oj5CPjoyMi4yLjouJjIqJiYyKiouJi4yMjIuLiouKi4mNiY6OjYyMi4uKioqLiouOjo2LjIuJi42Li4uIiIqKi4yMiYeJi4yLiomNjYyNiYqKjIuMjIyKjIuLiIJ3ZXdzbrm5c3J0cHSGjYuLi4qIiIaJjIqLi4eLjYeHiYqId2dueHmEiomIiISEhoqIhoSFhYCHhomIh4WGg4OCfH2rb3Fvb3l3eX1+fXd4goaGhYSHhoWHhYaHiYaGhoeHhYSFg4SGhIVAhISDhYaFhYWGhYWFg4aGhISDgYKCgIKFgoOEhIaGg4OEgYSDgYKCgYGBgIGBgoOFhoOEhIWCgIWEgX6ChoSDgv9/j38Bfv9//3//f/9//3/Qf4J+u38Bfuh/AgIEAICfn6Cfn56hnqCcnZ6enKCgpKSlpZ+jp6WmpaSmp6eoqaynpqKkoqOloaCepaaloqmkpKahn6OimqSkoKGfop6dk5Ogo6alp6mko6WlpKKkpKCkpqShpaGcoaGenJ2goZ6aoZ6dnZuenJybnp+anJ2am5yXmZmbnJ2hnZydnJmgnT+hn6CinKCcmJeXtZ6R8YiPlJaWmpubmpaYl5iZl5qXlJWUlpeDkJiWlJSXlpeXlJWUlpOVlZKRlpOTlI+SkZSEkkqUlJGUk5ORj5OSk5aTkpOTkZKUlZSVlJOUkpORkpuVlpOSk5KWk5KSkZKUlJOUk5STk5KSkpWSlJOVlJSVlZKUkI+PlpOTkpKSkISTcJGQkpKVlJaXlpeYkpeWl5aVlpaXmJSWmJaYmJqZmJGXl5aXm5iamZuampubnJyfnZ2bnJ2bnJmcnp6anZ2enZuenpufn5yenp6fn6CioKGhn56dnZ+enZ6enJ2cnZyem5qampmbmZmZnJubmZmam5qEm3SYmpubmJqZmJuYmpyZmpmYmpmYmpqYmpydnJqZmZyanJqbnJybmZycnZ6dnJ2goJ6enp+gnqCfoKGgn6CcoaGgoKCioaKioKCho5+goZ6foaKkoaGioaGhoqChoaGfoaKhoqCgoZ+ZnaCgn6CenZydnp6fnoSdPZyenZudnZ2cnZubm5ydnZ6enZ6dnZ6fnp+foKCgoqGjpKSmpqanqKemp6mqqqepqqmoqaipq6ysraysq6mFqhOsq6ytrausq6utrKqsra+trKyshK2Erg6vrq6srK2sqqmsqqepqYSoBKmnqaqEqQeoqqmpqqqohakCq6mEqgqrrKysq6usrKyrhKyAraysqqusrqusra+trq2ura2urq2traytraytrqysq6qqq6mpqqipqKinqqqoqquqqqmpqKipqKaop6alpqempqWlo6Sko6WlpaKipKWhoqSioqWjpKWlpKSmpqqopKSnp6uqqaj9qqutrqqrraurrKuqrKyrq6yurayrrK6vq6kHqqqtrKqsq4SqgKmrqqisq6quraenqqmopqenqKqrq6msraqsrK2qr6urrqalpqusqqqorKqprKyrqKyrqKSmo6GhoZ+gpKWnqKenqqeqqqimpKamp6ipqKWmpqanqKijqLOxn6SemJWgo5+ZnZ2ko6SloqCfnp6gm5ygoaChnJSTgoGJi5uSjJeXXJKNhPeDk5yhn6GcnqCgoKGioJyhoqOioKCjoKCgoaGfoZqdm5ufoJ2enJ+fn56coJ6bm5eenZyhoqCioaGfn5+gm5yenZyhnp6enaCdnqGioaCgoJ+cnaGdoaCfhKEBpICSkpKTk5KWlJaQkZKTkZOTl5eYl5OXmJaYmJeYmpiam56cm5eal5mblpeSmZqal5yYlpqXlZuYkJmYlpaTl5SVi46XmJ6dm5yYl5qYl5aZmJSZmpmTlpSRlJWRkJKUlJCNlZORko+SkY+PkpOOkZKPkJGQj46NkpGTkJKQkY+VkYCUkpSTkJORjo+JoZOH5YKJjoyLjY+Pj42Ojo6PjI6NiouMjI18iY2Mi42QjY2NjoyMjY2MjIqIj4qMjIiJiYuKjIuLioyLjYuLiIiLioqMioqLi4mKi4yMioqLjIqJiYqQjIyLiYqJjYuJiomJjIuLjIuMjIuKi4qNi4yLjYuKjFiLjIyKiomOjIyMjYyMjI2NjYuLjIuPj5GQkJCPjZGPkI+PkJCQkY6PkI2Pj5KSkIuQjo+OkpGQkJORkJCTkZGUkpOSkpKQkY+SlJSRk5OTkJGRkpGUlJOShJMSlpaTlJSUk5WTlZSUlJWSkpOThJInkZKSkpGTkZOSkpGQkpGQkpGRkpCRkpGQkZGPkI+Qk5GRkJCSkZGRhJJWkZOSkpKVlJWTk5STlJSWlZWWlZWWl5eWlZWXlpWXlpeXlpeXk5iYl5eXmJeZmZiZl5qXl5iXmJmam5mYmZmYl5iXl5iXl5eYmJmXmJqZkZaZmJeYl5eFmAWXlpeXl4SWMZWVl5aXlZaVlJSVlpaXmJiYlpiXmJeYl5iZmZqbnJuam52dnJ2dnZudnZ6enJydnJ2Fn4SeBaCen56fhKCCoYSgEJ+hoaGgoaKgoKChoKGhoqCEooShBp+ioJ6foIefL6CfoKGfoKGgoKChoaCfn6CgoaKgoKGhoaKhoaGioaKioaKioaGioqKhoaGioaOhhKIGo6Oio6OihaMFoqKioZ+EnoOfh54FnZ6enp+FnjSdnZ6enpybnJydnZ+dnZycnZycmJudm5ucnJucn52cnZ2en6CeoKCenp+eoZ+goPmhoaKjhKEonp+foKCfn6CgoaCfn5+hoJ+fnZ6goJ+hoJ6fn5+en5+doZ+eoKCbmoWegJudoJ+enqGgnqCioJ2fn56hnJabn6GfnpufoJ6hoKKgoJ+gnZ6enJqbmJmZm5+enZ6enaOhoKCdnZ2enp+enJyanZ2cnZudn5+Ul5aTk5qXkpCTk5uampmYmJOSk5aRk5aWlZaRi4x6eX99iH13gYiHhX7tgI2UlpWXk5SVlZaWUZaVk5WXl5aXlpiWlZWXl5WYkZOTk5iXlJiUlpeWlZOVlZKSkJaWlJmZl5iXlpWVlZeUlJaTk5WSkpOTlJKUl5aXlZSUk5GRlZOWlJWWlZOVl4B/goB/gYKEhIWAgoKDgIKDhYSDg4KFhISFgoSCg4SEhYmGhIOFgoSGg4OBhoeHg4qFhISBgYSEfoSCgoOBhYJ/fX+Dg4eGiIiDhIeGhIGEgoKEhYR+gn5+gIB/fYCDgXx9gn9+gH1/fX1+goR+gICAgoB/gH9+gH5+fX9+fXyAf4CBfoB+gIR+fX51hIB30nh+foB+fYCBgX19ent9foB+fH19e3xvfX99e36Afn5+f358fn5+fXt6gHx8fnp7fH58f3x+fn98fn19e3t+fXx+e3x9fXx8fH5+e3p8fXt7fHyCgIB9fH17f359fXt8fn5+f35/fn1+fHx/fX5/f358fxd9fn99gH2CgICAgYB/gIGAgH9/f36Af4WBAX6GghSBgYKAgYCAgH6Cg4F+g4GBgISDgoSBKoCDgoKEg4OCg4KAgoGDhISAgoOBgIGAgIGEhIKDg4SEgoWFgoOEg4OFg4SFEYeEhYaEhIGEgoOGg4OEhYOGhIRxg4ODgoSEhIOEhISDhIODhIOGh4WFg4OGhYWFh4WEhISGhIWFh4aHhYWIh4aFiYiHiIiHh4iLh4iIiImIiomJioiIioaIiYeHhomJiouJh4iKiImJiIeJi4yKi4mIiYiKiomJiomIioqKi4uMi4WJi4uFjIWNGoyMjIuLjIuLjIyMioyJiouKiYmJioqLjIyKhYs6jIyOjY6Ojo2LjY6NjY2LjYyLi42OjYyNjI2OjI2MjYyNjYyNjY2Ojo6Pj4+OjY2PkI+Oj5CQj46QkIaPApCOhpAJj46OkJCOjo+OhI8cjpCRkY+Pj5CQkZCPkZCQkZCQkJKRjo6Pj5CPjoSQFZGQkJGQj5CRkZCPkZCQj5CPkI+Rj4SQGpGRkZKSk5GQkJCOjIyNjY6OjI+Njo2NjY6NhY4OjY2OjY6Njo+OjY2LjoyFjSKKi4yLjIqKkI2MjY6Mjo2Mjo+Ojo6Pjo+OjY2QjY6Oj4/xhJAfjo2Li4mLioqKjIyNi4yMi4uNjI2KiImKjo2Li4uKjIWLgIqNjIqMjYmKjIqLi4qLi42MiYmLioqKi4yLjYmHioqJhouNi42KjIqKiouOi4uNjIqKjImJiYeIh4iLjIqIiomMjY6MiIqJiYuNiomIiImIiIqIioKBg4aHhIKHhYGAgoWIiIeGh4Z/gH+Cf4GEhIODgX19bGtsZ21jXWNzdnh1Xtt0f4aGhYeChISDh4WFhYOGhoaHh4aJhoaEhIWFhoCEhIOGh4SGg4WEhYSEhoOAf3+GhIKHh4aFhYSDhYSFg4OHhIODgoGCgYGAgIKAgYKEgoKBgISChIGEhISAhIX/f45/AX7/f/9//3//f+9/AX7/f6l/AX7dfwICBABzn56eoqGgnqKhm5ycnKGenp2bmp+hnp6bn6iloqKopammpaSfoqOfpaShqqWjpKOipKOdmJ+doZ6moJ6ioKKhoZafqKanqKWipqOhpqSgo6Klp6GeoqKen6KhnKGgop+cn5+bnJ2dm5qbm5yemp2anJial4WbdqGdn5+ZoJ2am5qbmqSkpJiVqaHu6YmRlJWZlpuZlpmbmpqTlpSYmpeUmJSVkJGTlpSTlZOVlJKTlJSSlZaRjo2OkZGSkpGTkpGNkZKSlJORjpSRkpSTlZOVkpOUkZSQk5aTlJeXlJSTlJOVk5WUlJOWk5aUkZCEkjGUlZORk5OSkZSUlJaWk5GRkpKRlJSUk5KPkpKRkpCRlZSWlZSWlJmXlZeUlJSWmJaXhJY4l5SWmZmbl5eVlpeXmZiYmJuZm5qbl5ucm5ybmpybnJucnJyenZ2dnp+en5+gn6Cdn6CeoJ2doKKEoHafnZ6dmZubnJydnJ2bnZubn5ycm5ycm5ybmpybnJubnJqcmZqZmJqZm5iZmZiYmpqZmpibmJmZmJmampmZm5qYmpuZmpydmpqZnJ2enpyfoJ6foKCen56gn6GgoKGgoKChoaChoqKgoKChoqOhoKKioqGjo6GhhqIPoaGhoKChoaCfnqCgoZ+ghJ8Rnp2dnZ6dnZ6dnJycnZ6dnZ2GnG+dnJudn56cnp+fn56en6GhoaChoqSjpaSjpqWmp6Wnqaenpqepqainp6mpqqiqqqupqqmrqaqqqaqsrKyrra2trKuuraysraytra6ur62trKqsrKurrKusqqmnp6ioqKmoqKmoqKmopqinqKmqqKiEqXeoqamqqqmpqaqpqquqq6mnqqusq6urrKusqqyrq6usrK6ur6yurK2vrK6ur62srK2trautrayqqqqrqamqqaqqqKmpqaaoqqmqqKipqamnpaenpaSlpqemp6OlpKWjpaampKGmpaOho6OipKWlpKOjpKSlpKWlpoSogKT+tZesq6utrKurq6ysrK2usK2trq+ura2urKurrKeqp6usrqqpq6iqqaipqaurq6msq6iqrKWoqq2qqqiqqKalpauqqauqqKqrrKyqq6qqqKyrqaqsraqpqamnqKmnqKWlo6WjpaeoqqmnqKWlpqinpqWjp6qpqKSop6Wkp7C1ObGvrqCilKCpqqKlqKilp6eknqKfoJuUlZecmZeWi4SSiYGJrZuHiIWGgPXhiJubnJ2eoJ2hoaCfoYWiS5+in5ucnZ+enaCgnpqbnZyan52YmpuZmJqfnp+goZ2dnpycnp+goKGenp6fnJmcnZqgnqChnp2fn5menp2cm5+cmZuaj5SgnZ+eoICWk5KWlZOSlZSRk5ORl5GSlpGNkpOTko6Rm5mUlJmXnJuZlZSWl5aamZafmpeXmJmal5OOlZSXlpyVlpiVl5eXjpabmZudmpabmJeamJWal5qalZSXlJOTlZWQlJWWk5GUk5CQkpKOj5CQj4+Oko6PjJCOkJGPkI6UkpOUjJKPkCiQkI2Ml5aVjIqXkuPegoqNjI6JjouLkJGOkYqNi4+SjIqNioyHiYqLhIxXi4yKi42MiYyOiYeHiIiJioqKjIuKiIuKiY2KiYiMiYmLi4yLjIiJi4iMh4uNi4uMi4mJiouJi4uMi4yKjIqNjYqJiouKiouNioqLi4uJjYuLjYyLiYmLhIxJjouLi46Ni42MjI2OjY2NjoyQjo6QjoyNj5GPkI+QkZCQjI2Pj5OPj46OkI+RkJCPkpGTkZGNkZGQkZGQkpGSkZKRlJSVlJGTlISTM5WUk5WWk5OSk5SWlJWSlJOSkpOQkZKSk5SSlJKSk5OTkZKSkpGSlJOQkZOSkpGSkZORkoSQc5KOjpCRkJGRkJOQk4+RkJGSlJKSk5OTkpOUkpKSlJKUlJSTlJaUlZeVlpWXlZWVlpWYmJaWl5iXmJeWlZeXmZiXl5eYmZeYmJiWmJmZmZiYmJmZmZiXmZaXmJaXlpaYmJeUmZeYl5mYmJaXlpaXl5aVlpeEliiVlJaVlpWWlZaVlpaYlpeWl5eXmZeZmpmampqbmpucmpybmp2bnJ2bhp0KnJ2dnJycnp2fnYSeD5+fn56foKCgoaCfn56hoIehD6ChoJ+foKChn5+goKGfn4SeFZ+fn56fnp6fn56foJ+foJ+foKCgoYSgH6GgoKChoKGioKCfoKGio6KioaChoqChoKChoaGioqSEojWkoKGho6KioaGhoqGhoaCfn6Chnp+fnp2fnp+fnpydn56dnZ2en52dnJydnZycnp2dnpydnISdgJycnJ2bm5qbnJucnZ6enJydnZ2bnp+doJ6foJ31sY6goJ+hoaGgnZ+goJ+hoqGhoqGhoaChn52fn5yfnZ+foZ2fn52fnp+enp+fn56gn5ydoJyeoaKenp2enZmbm56cnZ2enJ6hoqKfn5+hoKGgoKCioZ6dn6KhoKCfn56enpybAZ2Fn4CgmpydnJudnpqbnp6fmp+dmpqanaCgoKGXl4+WnJ2Ym56dnJ6bmZWVlZeRjI6Pk5CPj4V/ioF4fZWDc3p6fHrt2oSTk5KUlpaTlpaVlZaXmJaYl5aZlZKUlJWUlZeVlJGSlJSQlZSRk5OSkJGXlJOVmZWWl5SSlZWWk5eTkpaWkx6OkJKRlpWVlZSUlZWSk5OTkZGUkI+SkIaMlZKUk5RWg4F/hIOCgoOCgYSDf4R/gYOAfYGCgoOBgIeCf4CFg4eFhYSChoWEhYSDiYSDgoaHg4J/eoGCg4OIhYWGgoSEhX+EhYWHhoeChoSDhoSDhIODhYF+gYGEgBd9f4OFf3+CgH99foF9fH9/fX59f3x+e4SAgH1/fIF8foF+gX5+fXt6eX5/fnl4fHfM0Xh+fn1/e317fX+AfYB9fXyChH56fHx+eXt8fX58fHt8fX18fX18fn98eXp8ent7fHx9fnx7fn18fXt+fX98e319e3t+e3p8enx5e3t8fX98fHx9fnx9e31/fnx/fX+AfXx8fn1+fn99gHx9fX17f319gH99fXx/fX+Af4F/gH+CgoGBf4CCgX9+gIF/gX9+gH+AgIGCgYKBgoSBgn6AgYKFgIKAgYKAgoGCgIKChICCf4KCgYKAgIKAgYGCgISFhoSBgYKBgYGChIWBhYaFhYODg4WDhoSEhYKBgoGBhIWFhYSGhIOEhISDJoOEhISFh4WDg4SEhYSGhYaDhIOChIWGg4KCg4ODhYOFhIaCg4OFhIQphoWGhoaHhYWGh4aFhoaHhoeGhoeHh4iIhomIiYeHhoaHh4iIioiHhoiEiQqIiImIiYiIh4aKhIl0jIuJiYqMiouJioqJiYqKi4uLh4yLjIuMjoyLjIyMi4uLioqJioqLiomIiomJiYqKiIiKi4uJi4yMiomKi4uKjI2MjIuLjI2KjY6NjouNjoyMjoyLi4yKi4yLjIuMi46MjI2Oj46Oj42OjI6Oj46Njo6Pj5GEjxSOj46Pj4+Oj5CQjo2Mj5CNjo6Oj4aOJI+Pjo+Oj4+PkJGRkZKSkJCQkZCQkZCPj5CPkZCQkI+PkJGRkYWPOo2NjY6Pj5CRkZGPj5COkY+Qj5KPkZGQj46Oj4+Njo+Ojo6Pjo6PkI6Pj42MjI2Oio2OjoyNjYyLjY6EjU6MjY2OjIuLjIyLjYuNjYuLjo6NjY6Pjo6Mi4yMi4yOjY+NjYyL47p+jo6Mi4yLjI6Mjo2KjY+NjI2Ni4uMjYuJjY2KjYqMjI+MjY2Li4qFi4CKiY2NiouMiouLjYuMjIuJiIaIioiGi4yJiIyNjImIioyNjY2Mi4yOjIqNjo6Mj42NjIuLi4iLiouKi4yLiouLiImJiYiJiYuLh4qIiYiEhn+JiYyGhoCEiIqGiYuLioqIh4OAgoN/fYCAgn9/gXlvfHBnanNiWmhrb2/Zy3aFhFqDhIaGg4aHhoWGh4eEh4aGh4WAhIODgoOGhYWBgYaEgoeCgYKDg4CDhoOAgYWDhYWCgoODhIOEgn+EgoJ+gYKBhYSDgoGCgoWAgYKCgYGDgX+CgnyAg4GBgYD/f4x/gn7/f/9//3//f+9/An59/3+of4J+3X8CAgQAgJygoZ6foKOkpKKgn5qen56anp6eoZ+ioKOkp6epo6Who6OhnKKjnp+goqSlpqaipaSipaKdoJ6gnqCgpaGcoaWhnKGlpqehoqShoKSio6Olpaenpaeno6SipKCcnJmanJuenZ6coKCdn5yfmpudm5ubmJubmpydm5+dn5yamZuWgJWTlpKNlJ6krqKVlJmWkpeVl5mamJmVmZiWlpeVlpWXl5iUlpKWlJKUlpeUk5OTlJKVk5ORkpGPkpSQkZCTlpORk5KSlZCTkpOSkZGWk5eWkpWSlJOTlJWTko6Qk5SUk5GRlpWVlZSWlpOVlJKTlJWRkpCUlJKTkpOSlJSUkpGTW5SRkJGRkJSQjo6Rj5KSkpOUlJKTk5KUlJaWlpWUl5WTlpeUlZaWlZaYl5aYlZiYl5aWlpeWlpeamZqcm5mam5qcnZybm52bmp+dnZ2cnZ2enZ2bnaCfnZqXnZ2FnICan52cnp2enqCenp2Ym5ubmJubnJ6enJudm5uampqbm5mYmpmdm5mamZmXlpiXmJmYmpqYnJmYmZeZmJmamJ2bmpmVlZqcmZqcmZuam52bm56bnp+dnZ+dnZ2cnqCen5+goaKgoqOhoaOenp+hoKCho6GjoKChoaGipKKiop+fnxygn5+ioaKhn5+foaCgoZ6fnp+fn56fnZ6dnZydhJ4enZ2cm5ybm5ucnJubnJ2enZ+enZ2dn6ChoqKhoaOihKMHpaSlpqemqISmD6ipp6epqKWnqainqamoqIWpI6qqqqurqqqrqqmrrKuqqqyrrKurqquprKmpqqqqq6qpq6qphKhtqaeoqKeop6ipqKanqKanpaeop6Smqamqp6epqamqq6qsqqurqqurrKqrqqurq6qqqKqsrK2urq6sqq2trq+urK2sqqysqqqqq6mpqKanqKiqqqmoqKmpqKioqammpqinpqWlpKWmpKSlpaanpoWleaampqOipKSjoqSjoqOkpaWko6SmpKenpqmnqqqop5+lqK6pqKyrrKmusK+usLGura6urq+trK6ppq2pqKmpqKmqqKmppqmpqqinqK2sqqmqqKiqp6mprKupqKenp6iop6ipqqqpqaqoqKqmqquoqamoqamrraymp6mFpWumoqWjpKalpqaipqalpaanpqSkoaWno6KeqayqsKKZoJuamKSmmI6Yrquno6Gko6Ghn56Xk4qOj5qdmI351uHwiYeYm4OGioSD9YGenZycm5ubn6Cgn56hoKGin6Cfnp6cm5qdnJ6anZuanISZOZqemZaWlZubnJ6fn56fnJucnJ+bnZyenp6amJienZ2cnqCgnJmdnZubnZ+empyjlJ6hl6ChoJ2fnoCSk5aSk5WXlpeVlJOPk5OTkpSSkpSTlZKWlpqam5ialpiXlZCVmJSTlZeYmJqcl5qZl5iWlJWUmJaXlJmWlJeZl5OWl5ealZWZlpeZl5iZmpeZmpiamZWWlpeVkZCPj5KQkpOSkZOSkpSQko+TkY6Ljo2Qj4+Pko6SkJORjo6Pi4CMi4yIhIqPkpuRiYqPjoiOjIyNj4yPjI+Ljo6NjY+LjY2OjIyKjYyKjI2PjIuNi46LjIyMi4qKiIyOiYqJiouKi4yKio2Ji4qMi4qJjIuOjImKiIuLi4qLiomGiYmLiouIiIyLjY2MjYyKjIyJjIyMiIqIjIuKjIqJiYuMi4qLjAyLiImJioiLioiJi4qEjEmNjouMjIyPjo+PkI+OkI6NjpCPjo+Nj5CQkI6PjZCPj5COjY+Qj5CRkZGTkZCQkZCSk5KRkpSRkZaSlJORk5OTkpORlJSSkZCNhpIwkY+SkpKTkZSTk5KTk4+RkpKPkpOTkpOUk5ORkpGSkJKRkY6RkZKRkJGRkY+PkY6QhJJUkZWRkpGQkpCRkpCUlJGSkpCTlJOUlJKUlJGTkpOVkpOWk5OWk5OUk5SXlpeXl5aXlpeXlZWZlZaXl5eYmJmXmpeXmZeYmJmYl5iXl5eYmJiZmJmYhZcymJiXmJeXl5iWmJaWl5eVlZaXlpaVlpWVlZaVlZWWlJSVlZaWl5WWlpaXmJiZmpqZmpiEmh6bm5udm5udm52cnp2bm52dnZydnZ2cm5ydnp6dnp6EnwWgn5+foIefhqAooZ+hn5+gn5+gn5+gn5+fnp6eoJ6fn52fn5+gnp+gn56fnp6gn56eoYWfD6CgoKKio6GhoKGhoaKiooWhA6CfoIShCKKjoqCioqGihKFan6CgnqCgn5+en5ycnZ+eoJ6dnp+fnp6dnZ2anJycnZubm5ydm5qcm5yem5ybnJycnZycm5udnZucnZ2cn52gnp2enp+dn6CfoJ6goKCdl5udoaCeoaCgnp+hhKKAoaGgoaCfoJ6fnpyfnZ2enZ+dnZycnJqcnqCenp2gnpybnZ2dnpqdnqCfnp2dnJucm5qcnJ2dnpyen52fnJ6ioKCfoZ+hoKOjnJ+fn52dnZydm52cnJ+enp6bn56dm5yenpucmZqdmpmXn56en46OlZOSkZmYj4aOoJ6cmpmamJV4k5OTjYqDhoiRlZCI9c3Q3oB8hoJxeH98fOt8l5WSkpOTkZSVlpSVlpaWl5SVlZaVlJORk5KVkZSTk5SSkI+SkpWQkJGPkpSVk5WXk5WTkpKTlJGTkJSTk4+QjpSTk5KUlZWSkJOTj5CRlJOPkpiLlJSMlJWVkZOTgH+AhIGAg4SCgYCBgH2Dgn+BgoCCgX+CfoODh4KHhIWDg4OEgISFgYGAg4WEhoeEhoSEhoSCg4OEhIOCiIWAgoSFgYOFg4eEhYSDg4OFhIKEgoSFg4GCgn+Af4B+fX5+gX2Afn5+f4CBgH6BfoKBfHt9fX5+foCBfH58fH18fn54HXx4fXh0dXd8gHh3eoKBeoB8gIB/fX58f32Af399hHwgfXp8e35/e359fnx9fXt+e32Af318e3x/fnt8fH19fXyEfYB6fn1/fX59fn1/fXt9eXx9fH16e3p4fXp6ent7eX58f399f358fn58fX9/e3t7f399fH19fH1/f31/f317e3x9en99e31/f4GAgYCBgn5+gICBgYOBgIKAgYF/gIKAgoF/goGBgoCBfoGCgYCBf4GBgYKBgYGCgoGBgYCBgoCAgoCDgoGEg4SEgYOChIGCgIKCgYGAfoSCg4SDgoCBgoCAhIGDg4KChIOBhIaDg4SEhYWIhoWGhIOEhYSFhIOBg4OFhISDg4SCgYSCg4aFhoWBhYSFhIODhYWFgoaHg4KDg4SGh4iGhIaFhIaFhYaChYiFhYiFh4eHhYeGh4iJh4eIiBCGhoaHhYiIiIeHiIqJi4mHhYgXh4uKiImKi4uKjIqMiouJiYqLi4yKiomEixqMioqJiomJiYqJiomKiomJiYeIh4mJiYiIi4SKCYmKi4uKi42Mi4SMQ42Mi42OjIyNjYqNjYyMi4yLjYyLjo6NjIyLjI+Pj46Pj46PjpCQkI+OjoyNjo6MjJGOjo+QkJCPkJCPkJGQkI+OjoyEjgSNj4+Oio8lkJCPkJKRkZCSj46Oj4+Pjo+RkZGQkZCQkY+Qjo+Oj4+Qj46Oj4SQhI8nkI+QkI+Pj5CPjY2Li42OjI2NjIyLjY2NjIyNjY6MjIuLjIqLi4uMhItJjIuKjIuMjIuNjY6Ki4uMjo6Mjo+OjY+OjI2Oj42NjYyPjI6Nj4+Mj42MiYaCiIyLi46Ni4mMjpCPjY6Njo2Li4uMioqMioyLjYWLgIiKiYmLi4yLioyNi4qIiouKjYqLiY6MiYiHhoiGioaIiouJi4iKiYqNiYqNjIuNjo2LjI+PjIyOjYyLi42OioyLjIyLioqKjI2LiYmKjImKhoeJh4aGiouLhXR8hIOBf4eGgHp/jIqLiIeGhIJ9f4B9fHd4e4GDgH3jt7LAcGhrFWFdZm9wcdlyhISEhYSEgoWFhYSFhISFTIaFhoWEg4CDgYOAhoOCgoSAgIOBhX+AgoGFhISCg4OBhIOBgIKEgYGAg4CAfnx/goKCgYOEhIB+gIB+fn2BgYCAhHyEhnyFhIOAgIH/f/9//3//f/9//3//f5t/hH6JfwF+3n8CAgQAgJ6joaGfnqCjop+in5+inpyfpqOgop2kn6qloqWjnKCgoaCjoJ6eoKKho6KioaKiqKOfnqCenqCgnqShoqOmpqKfmaKhpaOinqShoKemoaSmpaeloqykpaWgnZyfnp+goJ6bnpqcnZ+Zm56hnJuYnJmYnJ6dnaGdm5qdnpybmp2YLJaTj4+Nko2MiIaJlIyOlJWTl5mXmJWXlpeVlJWVlZeXk5eUlo+WlJSTkJKUhJVMkpKTkpKQkJOSkpGQkpSWkZaUlJKQk5CRkpOSk5KSlZKSk5GTk5SUkZKVkJCTk5CTko+Tk5WTk5GUlJWRlZKWlJOUkZSUlJWSlpSRkYSUcJaTkpWSj5KQjZCPkZKTkZKTk5OSkpOUkpOUk5aUlZOTlZWWlZiWlJOWlZWUlZaXl5eVkpOXmZaXmJmZmZecm5ucnpqcm52dm52bnZ2cnZ2em5yen52bnpudnZ2cnp2bn5+dnp6cnZyam52dnZybmpuGmheZmpmampmZmpuYmJibnJqXlpibmJmamYSYgJmZlpiZmJmXmZmYmJqZmpqalpiYmZmYmZmampqYmpmam5qZnJ2fn56cnaCfnp2fn6GgoJ+goJ6eoqCfnqGgn6ChnqCfn6CjoqGgn6GfoJ6fn6Cgn6Chn5+goaCfoKCfoJ6goZ+dmp6dnZycnJ2fn52dnJydm5ydm5ydnp+cnZ6eXZ+en6KenqKhoJ+hoqKko6Slpqanp6Wnp6akp6Smp6ekpKamqKanpqemqKeop6epqqqqrKusq6urqqmrq6usqqmqqauqqaqqqaqpqqmqqainqKelp6iopqamp6anp4SmMKSoqaempKSlpaemp6eop6anqKmqqKqsqqurqquqqampq6uqq6uqrK6tra2sra6trIWtDKyrq6yqqqmpqaqsq4SoF6mpqqiqpqSnqKioqaaio6ampaWlo6WmhKQ8paSlpaelpKKipKOioqGjo6KkpqWlpaaqqaenqKqpqamqqqurrKurrausq6yrq6mrrK2uqaqrrK6uraythKuAqa2nqKusqKurqKqqqKeop6iop6irqa2qqaaoqqmoqaWqqamnlaapqKqqq6anpqemp6arp6mnp6yusK+tqqWpqKamqKilqKWkoaOipaWmp6SnpaSmpaOmpKOjoJ+em4qcrLHBqKCenZWapqqmmZ+2sKOgnZucnqGjnJqXlZegoJhujv7/gP6EhYKSq4mHjpCWlqKjoKCgn52gn6CdoaKkoqCen5+fnJydmp2fm5uamZuYmJiXmJeXlpSVlJWYmJealpmbnZ6cnZ2cmZuXnJqcn5ucnZuYmpycmpuWmZiZoKCdnpydl5SZlpKhnJ6doKCAkZWWlpORk5eWk5SRkZSSkJKZlZSWkpaTm5mVmZWQlJaWlZiSkpKUmJaYlZaXmZidmZWWlZaSlpaWm5aXmpybl5aOl5aXlpiVl5WVnJyXl5mYmZeUnpiYlZKQj5GSkpOSk5GUkZCRk4+QkpSQkI6Rjo2RkpGRlJCNjZGPkZCOko1HjIqIh4SHgoOBgYGIhYaMjIqNj46NioyPjYuMioyLi46LjouMhoyKio2Kio2OjYyMiomLiYuJiYqKiYuLiouKiYyOiouJi4mEi0KMiouNi4qLiYuKioyJiIqHh4mKiYuKiYqJjIuLioqKjImLiI2LiomJjIuKjIqMi4qKi4qLjo2Lio6JiYqJhoqJi4yEjSaOjYyNjI2MjY2Oj46Oj4+OkJCOkI6OjY6Oj46Pj5GOj46MjI6Qj4SQgJGOk5KRkpSPkZCTkpKSkZKTkpOTko+QlJWTkpGRk5KQkJWSkJOTk5GTkZKSkJKRkpKQkY+SkpKTkJGRkZKPkpGQkJCSj5KQkJKRj5CRkJCRkpGQj5CQj5GOkZKQkZCSkJCRkZCSkpCOko+RkpKVkpKUk5KSkZOUkpKUlJWVlZOVIJaUlpWWl5iWlpWWlpWVmJaXlpiXlpeXlZiYl5eXmJeWhJdblpeXmJeXl5qXl5eWl5eXmJeXlpaYlpeVlpaVlpWVlpSUlZaWlZaUlZWVlJSUlZSVl5eWlZaXl5eYl5mYmJeYmpmanJucm5ucnZqbmpybnJqbnJqam5yZnJydnIWdBJ6en56HnxOenp+en56en56gn5+goKChoKGfhaAhnp2en5+enp2dnZ6fnZ2fn56fn56fn5+enp+foJ+goJ+fhKAOoaKgoaCfoaCgoaChoaCGoYaiE6GhoqKhoKChoKGfoJ+foJ+gn56Enw6en52enJyenJucnpuYmIScDZuampubnJ2ampqcmpyEmwacmpqam52FnH6gnZ6hoaChoKKhn56goJ+goqGhoqCgoJ+foJ+hop+hnpygoKGhoJ6gnp2en56dnJ2foJ6enZyenZybnJ2cnJqbnp6gnZyanp2cnZuanp+bmoOcnZucm6Ccm5qdnJ2coJ6fnZ6goqWloqCbn5yfoJ+gm52ampqYmJucnZ6bnJ2Em4Cdm5ybmZmYlouUn6KpmpSVk4ySm52ckZWmoZeUk5GRk5WWkI6NjI+Wlo+G8fZ88n19eYSTd3uChIqLl5iVmJeVk5aVlJOWl5iXl5OUlpWTk5aUlZiUk5GSkpCPj42QkJGOi46Oj4+PkJKPkpOUlJSVkpKRko6UkJGVkpKTkpGSkhiRkZKOkY+OlpSSkpGSjoyPjYiVkZOSlJRif4GEhIGBgoSDgYKBgYOBf3+FhIKCf4OBhYR/g4OBgoOEgIOCgoGFh4OEgYCEg4GGhIKCg4OCgYGChYKFhoaHhIV9hIOCg4WChIOBiYmFgYKBhIOAg3+AgX97fn99fYF+foCEfoCBfHx/gH99fn99foKAfX+Cfnp7gX58fXyAfHt8eXl2d3JzcnJ2dXh9fXx8fYF/fXt+f358fHx/fH16enx6fHh+fH1/e3p9fH19e3x8fXx+eXl9e3p+fnp8fHp8gH17e3x6fHx8fn57fIB8fHx5fXp5fHl8fXl5fHx7fH16fHt/fUF8fXx8f35+e39+fnx8fX58f31/fXl8fX1+gH58fX96en59e398gIKCgIKAgYGAgICBgH9+gIGAgYKCgIGBgoKAgYR/OICAgoGAgYB8fX6CgoGBgIGBgIKBgICCgIKChYODg4KFhoKEhIWAgYODgoGCf4KDgYGHgoGEhIOAhIIjgYSDhIOCgoGDhIKEhIKDgoSBhIOEg4KDg4SCg4WFgoKEhYOEhAuFhoWFhIODhIOEgoSENYOEg4OBgoWCg4SEh4WFhIWEhoSFhISFhoeHhYeFhYaGiYiJiIiGh4eIh4WFhoaGh4iIh4mJhIhLiYuIiYiJiYqKiYqLi4yJi4yKiouKiomJioiJiYiJiouKioqJi4uJioqJiYuKh4mIiYmIiIiJiYiIiYmKiYqLiouMjIuLi4mLjIqLhI0SjIyNjIuJioiLioqKiImJjImLhowLjo2Ojo6Pj4+Qj4+EjoKPhY4Jj4+QkJCRkpGShpAgjo2Ojo2OjoyNjY+Qjo+Pj46PkI+PkI+Oj4+PkJCPjo6Fj4SQVo+QkY6PkY+QkI6QkY+PkI+QkI+PkI+Qj46Pj46OjY2Ni4yMjY6OjoyMjY2OjY2Oi4yMio2MioqMiomLi4qLi4qJi4uMjIyJiomMi42Mi4qKjI2MjIuMhI1xj5GQjo6NjY2OjY2Li42NjIuNjI2NjY+Li42Lio2OjI2MioqKjI2LiYyJiouLiYuJi4uLiImKh4qJiomKioiKioiLi4uJiomLjIqJi4mKjIiGb4aJh4eIiImLioqKi4mNi4yKjY6QkZCNiYeKiIuMjIuEinmIh4aIiouLiIqLiYiKiYqIiYiHiIaIgIKJioODhIODgIKGiYmEgYyKhoOCgX5/g4R/fnx9gIWGfnfc5HDVbm9nbHFiaW9ye3qFhYaIiIaFhoODgoaHhoeIhISFg4ODhIODhoKDgYODf3+BgYCBhIF9goKCgICBgX+BhIIphIGBgIJ/gH9/goCBgIGBf4GAgIF+gH5+hIF+f3+Bf3x/fXqDf4F/gYD/f/9//3//f/9//3//f5p/BH5+f37pfwICBACAnqCjoqCioKKfoqGgoqOfoJ6fn52ipqOnpKSloKCfnKKeoqGgn52doKCcn6Kjop+doqOipKGjo6KinpukpKKjoKaknqGgopyioJ+joKGSoqWppKeko6Gio5ubl5yfnZ+am5qcnZ+amZ2cnZ2am5eZmJaYnZyenJucn5+ZnpuZlpmAmpSRkZOWjI6MipCJjI6UkpWWmJaalJaYl5yZm5iUl5WUmJeVlJWQkpSQkZORk5GSkZOPkJGRjpGSkZSSkpGTkJKRk5OSlJGQkpGQkZORj4+UkZOTk5eSkJGRkpKTj5GSk5GRk5SRlZSUlJKRkZSSj5GVkZGVlJWPkZWSkpWSj5MdkpKWlZOVlY+RjpCOkZGQj5KTlZSUlJOSk5KTlZaElHuWlpSUlZWXl5aWlpmXl5WXlpWVmJiXl5ibnJiWm5udm5udnp6anZ2bm5qbm5ydnJ2fnp6anp6goZ2dn52dnJudnpycnqChnZycm5+cm5uamJqbmZiZl5qZmpqZl5uYmJaYmZibmJeYmJmamJqYmJiUl5iYmJeZl5iXl5iFmQyampebmpqbmJqZmpqEmxqdm5mbnJ+dnpydnp6cnJ2enp6fn5+dnJ+fnoSgEaGgoaGhoJ6hpKKhoKChoJ+fhKAvn6GeoJ+hoKCeoKGfn5ugn56enJ2bm5ycnp2fnpycnJ2empucnJ2bm52em5ydnZ2EnjuioqKgoaGioaSjpaWipKWipqaop6emp6ioqKamp6WlpKanpqanqKaoqaqpq6yqqaupqaqqqaioq6mnqImqCamoqqqpqaepqYSnHaalp6empaWlp6Wlpaanp6eopqepp6inpqemp6eohKmAqqurqamqqKqpqqepqaqqq62srKyura+rraurra+uqqurq6qoqaenq6qpqaipqKmqqqippaWlpqSmpqanpqakpKSlo6Olp6SgoqelpKWkoKGjoZ+goqKioaCgpKCipaKkqKalpqeqqKKnp6qrqKippquoq6mtrKmrqqmtrausra2ArqurrKurq6ipqKmpqquqrKytraurqamrqaimp6urqaepp6elpaanpqmpp6qtrq2urKuqoaimqainp6SkpKeppayro5yWlo6fqKemqKWpqKalq6Wlpqempqeop6Wop6Wlpqeko6SglomZoqvAuK6soaCnqqGgn6SmoKKgoKOhoqV3pqajoKCfnZeViYqEioXz+v2FmaeamZeboKOfo52cnJ6fnpyfn6GfnZ2dnJuhnZqXmp2amZiXmJiXl5aVl5SXl5SWlZKTmJibnJycm6CdnJmal5aWlpeenJ2emZydlp2dnpmdmZ2goJ2bnZ2en52bl56fnaKeoaGAkpWXlJOVk5WQlpSUlJeSlJSUk5OVmJWblpiYlpKTkZaTlJOTk5KRlZeSk5aXl5SUmZeXlpaZlpeYlJKamZmXlJuXlZWUmJKYlJWZl5SFlZacl5eTlZaVlY6RjI+TkpKOkZCRkZOPj5GQkY+NkIuNjouNkZCSj46OkZKMkI2OjI9Cj4qIh4eHgoWDhoZ+hIaMioyNjoqPiYyOjZCOko2MjYyKjY2Li4qJioyKioyKi4mMi4qIioqKh4qLioyJiYqKiYmJhIsLioiMioeLjImIh4yEioCMiYiIhoqJioqJiIqJioqLiouJi4qMiYqMiYeJjYmKjYuMiImMi4uNi4qMioqOjYqMjYiKiIqJioqLi42Mjo2Pjo2OjYuMjpCNj4+OkJCNjo+PkI+QkI+Qjo+Oj46MjY+Pj46PkZOPjZKSko+Sk5OSkJKTj5CQkZCSkpGSk5GSj2uTkpWWkpOUk5SSkZKTkZKTlJWRkpORlZKQkJKPkpGRj5GPkpGPkJGPkJGRj5GSj5GQj4+QkJGPkY+SkY+PkJGTkJCQkZCPj5CRkJKRkpKQk5KSk5KTkpSUkpGSlJWUk5OTlZSTk5SUk5KVlYSWBJWVlJSFlgSXl5mWhJdilZiXl5iXl5eVlpaXlpeYl5eWl5aWlpiXlpaWl5SWlZaWlZWUlZWUlJWWlJWVlZaWkpOTlZSTlJWWk5SVlZWXl5eWlpiamZmYmpiZmpycmZqbm5ybm5mam5ybm5yampuam5qEnAybnJucnZ2dn5+fnp+Engidnp6fnp2enoefH6Cgn6Ggn6GgoJ+fn52fnp2dn56enZ6fnp6en5+fnqCEnzmgn6CfnZ6en56en5+goaCfn6CgoaCfnp6goJ+foaKhoqOhoqChoKCioqGeoJ6fnp6fnZ6fn56enp+EnoCcnZucm5yanZycnJuamZmZm5qZmZybmJmdmpmZmpiam5uampuam5qbmpyXnJ2anJ2dnKCfoJybnp2foJ6gn52gn6CgoqCdop+foaChoKChoJ+gn56dnpydnp6dn56dn5+gn52enJyfnp6bm56fnpqenJyam5ybnZyenJ2fn5+gn3Wen5iem5yenp+dn56coZ+ioJaPi4yGlp6enJ2cnZ6enaGdnJ2dnJ2en6Cdnp6dnJycmZubmpaIkZmeqKWgn5SWnJ2XmJWWl5SWlZWYl5icm5qYl5SUko6MgoN+g4Dg6uh8jJaMjIuOlZiTl5KRkZSVlJOVk5aEkwmQkZWTkpCTlZKEkUCSkY+Ojo+NjpCNkI6Mi5GPkZKTk5KWlpWSkpCPjo6OlJGTlI+Sko2RkpSPk4+QlJWUkZCSk5SSkY2Tk5GXkpSVgIGDhIKAgn+BgIGBg4GFgIOAgoJ/gIWChIGDg4GBgYCDg4KBg4GBg4GBgICChYSBf4WDgoOBhYODhIKBiIaFhYKHg4GDgYN/hYKCgoSBboCChYKCgICBgYB7gHx8gH9/fH58fn1+fX59fn98e318fX16fn18gX19fH6AfX16fn5+IX98e3l4dHN1dnl2bXR7fXt9gIB8gXt+f3x8foF+fX1/fIR9Hnx8fX58fH17fHx7enx7fHp7en1+enx7fHt8fH19foR9Dnp9fHp9fXx6e3x7enp6hHuAeX18fH58enx8fHt8e3x7fX5/fn5/e3t7gHx9f39+fX5/f359e36AfH1/f3t7f3x+enx7fH9/gICBg4GBgoGBfn6AgIF/gYGCgoCAgYGAgYCCgYCCgIGAgIF/gICAf36AgYSAf4KCg3+Bg4OBfoGCgIGCgoGChIGDgYCCgIKDgYSAg4OFhISCgoKDgIKDg4SCg4KAhYOEgoOCg4KCgISAhYKBgIKCg4OCgoWEgoODgoOEg4WChIGEhoSFhYOEg4SEhIODg4SFhIaEhIODhYSGhoWGhYWGhoOGhoeGhIaGiIaFhoaFhYSIh4eHiIiIhoWFhoWEhImLiIuKiomIiIiLi4kLiYmIiYiHh4iHiYuEiiGIiYqLiYuKh4mIioeIiYiHh4iHioqIiYeIiYiIiIaHiImEiBCHhYiKiIaKiomJiYuMjIuLhIoWjIuJi4yLjIyMiomKjoyLi4qJiomKiYSKLYuMi4yNjY6Pj46NjY2Oj4+NjY6Ojo2Ojo+PkJGQkZGQkZCRkZCRkJKQj4+PkYSPC5CQj4+Qj4+Oj5GRhJBBj4+QkI+OjY+OjY6Pjo6QkZCPj5CQkY+Pjo+Pj46Oj46Ojo+QkI2OjYuOkI2JiYmMjIyNi42OjYuMjI6Mjo6Mi4yFi2uNi4uMioyLi4uKiouLjIuIiYqIiYqKiIqLi4uMi4yNjYyLjYqMj4qMjIyLjYyMioiMioyMioyLio+MjI2PjouOiouPjoyMiouLiYuKiYqKioyKi4mKiouKi4yKiomIiYuLjImLi4uKhoqJioSJeIuKi4mJi4uLjomKiIWKio6Li4yKjImIjIyNin14d3l6hIqMiouJiouJjI+Ki4mJioqKjI2MjY2MiIqHhoiJh4R7f4aGh4aHiIWHioeEhoODgn+DgoKDhISJiYiDhYKCgH19dXdxdHC+yMZweX13eHd8g4eDhoCCgoSEA4WDh4SDLIGBg4ODgIKEg4OAgYGCgX99f4F/f4F+f4GAfYJ/gYGDgoGGhYSCgYCCgH2BhIINfoCDfYCAgX+BfYCCgoV/C4F+gH2BgX+DgICA/3//f/9//3//f/9//3+ef4N+5n8CAgQAgJ2cnZydnZ+dnJugnp6hnp6eoZqgn6SooqGfnp+ioJ2hoqSeoqCcnp6fop+enqCgoJ6doaSmoKKin5uhnaGlpqWjoKGipqKgoqWipaejqKOipKSlpaOknZiYmJubmpqal5qdn5+eoKCfnZ2anZyZlJiXlpqdm5yeopmemp2fm5iWJpOWlpORkJGOjpWShImMk5WVkpmamZmXlJWXk5OQk5SWlZqWlZWUhJIqkJOSk5CTko+QjI+MkJKTk5GRlJGSj5COj4+QkI6Pj5CSk5GTlJKUkpOThpKAkI+Rk5OSk5WRkJKUkZORkY+Tj5KUkZGSk5OTkZSRkpORlZKTlZKSlJaXlpWUkpKRkZOSkZGTk5GPkpGRkpKTlZWSlJSSl5eWlpSTlJOWl5mZlpaVlpWXlpiXmZiZmZmam5manZ+dnZqcnp6anZ6cnJyenZyanJuanZ6Zm5yboJtsm5udmpydoJ2ZnZyfnp+em5ycm5qam5uZmpqZm5ucmpqZmJmbmpqZmpeYmZaZl5mWmpiXlZeWl5aXl5uYl5eYlpeZmZeXl5mam5mZm5uZl5iamZmbm5qcm5yam5udnZicnp2cnZ2dnJ2fn6CghJ8VnJ2hoJ6foKCfoKCfn6CioKGhoKCfhaCAoaGgn6KioKCfnqKhoKCfnaGenZ2enZ+dnp6enZydnpubmp2anJqam5uenpuam52dn5+goJ6doKChoqKlpaajpqSkoqOko6SkpaWmpqajoqWlpqamqKampqemp6inqKmop6upqaioqampqKmpqaipqainqaunqqinqKmpqaamp6cZqKimp6anpaelpaempainpaWmpqWmpaelpoangKioqampqKeoqaipp6ipqKqrqKipqautq6ysraqsq6qrrayrq6yrqquqp6emp6qqqamoqqipqqmnp6Woqaqnp6qnp6Wno6SlpKWmp6imo6SmpaaloaCfoZ+fn6CfoqGkoaSjoqGho6KmoqGlpaamo6ehpqWnpqanp6eoqampqKirgKuqq62urKqrq6iqqKioq6moqqiqq6yqrKurqamqqampqKaoqaioqqmlqKenqaeqp6qrpqemqamrpaiqp6anqaWnp6moo6m2p6SjpKenqKmmpqinqaGipqino6WkpaimoqiooqWjpqempZ+fnpyXj5GYqKWvqKaln5qbnKGmoJ2ffaakoaOdlJaYmZuamJmKhoiKiYaDh4aGjYyMkJadnJ+goaGhoJ2enp2bnJqem5ubnJ2anJmbnJicmZqYmZmYmZqVlJWUlpaXlpaXlJSRmJydmpebmZyamJmbmZeYl5mYmpmanZubmpiZmp2bmpuZoJ6dnJ+copminp6hoJ2ggJKQk5GRkJKRj4+UkpGUk5CSlY6WlZWYlpSTkZCWlpOWk5eQl5eQkZKTmZSSlZaUlpiVlpealJWXlpKYlJaZmpmZlpaXmJiWmJmXmZqUm5eVlpeXl5aXk4+OjpGRkI+QjI6RkZKSlZSTkZKPkJCOiYyOjI+Sj5CRlI2RjpSUkI2LRIiKi4iHhoaFhoqHeYCEi42Lio2Qj5CNi42Oi4qIjIuKiY6MjI6Li4mKiYmLjIyJjIuIioWKiIiKjIyKiYqIioiIiYmHhYiAh4qLiouKiIyJjIqKh4mLiYiGh4eJiYmIi4qIiYqJi4mKiYuJiouIiYqLiouJjYmKjIqMioqMioqMjo+MjIyLjIqLjIqLi46Mi4mNjIyNjY6NjYyOjI6RkZGPjo2PjpGQkJGPkI+OjI6Oj46SkJGQj4+Rj4+SkZGSkJOTkpCRlJEqkZGTkpKQkJGQkZOPkJGRk5GRkpKQkJOVk4+SkpORk5SSkpORkJGTkZGQhZEkkJGSjpGTkpGQkZGQkpCTkJOPkpCPjpGOj46Qj5GQkI+Qj5CRhJAKj5GSkZCSkpGQkoeTRJKUkpGRlZWQkZSUk5OUlZSVlZSUk5SVlZSTk5aXlZeXl5aWlpWWlJiWlZaUlpWWl5aWlpWXlpeZl5iXl5aXl5aXl5WYhJYqlZiVlZWWlpWWlZOUk5WSk5SUlJWWlZSUlZWVlpeZmZeYmJmYmpmbmpuZh5oCmJmEmzaZmpqbnJuam5ybm5qcm5ycnJ2dnZyfnZ6dnp+enp2enp6dnp+enp+fm56fnp6goKCfn6Cfn5+EngWdn56dn4aeC52dnp+en56en5+fhZ4Bn4SegKCgnp6eoKCgn6Cenp+dn6GfoaGgn6Ggn6ChoaGfoKCen6Cenp2en6CenZyenJ2dnZydnJ2enZucnZybmpuZmZqcnJubnJyampuampyampqZmpmYmZucmpuanJ2bmpqbm5yZmpydnJybnZudm5+enp2cnJ6cnp6en5+dnJ+foJ+cgJ6enJ2cnZ2fnp2enZ6gn52fn6CdnJ2fn5+cm5ucm52en5mem5ubnJ2dnp6anJugnp+bn6CdnJ6fnZ6enZ2doKKWl5man6Cfnp2dnpyemJydoJ6anZuenJyZnp6aoJucnZqblpiYl5GIiY2Yl6KcmZeXlJSVmJmVk5SbmpWYlYyQJpCPkZGPkIB/goWEgX+CfICFgoOHjJKSlJSTlJeWk5WTk5KSkZKRhJMokZKQkpOQkpCQj5KSkJGPjo2PjZCPkI2OkI2MipGQkpKQkpCTko+RkoSPIJCOj4+SlJCQkY+Pj5KPkJGQlZGRkpSQlY6Xk5WUlZGWJX+Ag39/foB/fH6Df36Af35+hH+Eg4SFhIKAgX6CgH+EgYN+g4OEgICEgYCDhIKDhYOEgoWEgIOEf4J/g4aIhYSCgYSFhYGEhoSFhYCEgIOEg4ODgYF/fHx+f3+AfH96en5+fn+CgX5+gX9/gX53eXt8f39+fn9/eoB8foF/f317fH14eXZ2dHd4dW1xdX2Af3t8goCBfn5+enl7enp6fXx9fXx+fX17elR8fH1+fHt8fHl7eX96ent+fnt8fHp7ent9fHp7enx7e3t9fnx+fHp7enx7fnh8fXx5enp5fXx7eXx6eHt8ent6fX1+fH19e3x9f319fH9+f39+f32FflZ/gH99fX59f359e359f39/foOBgYGCg4B/fn9+foKDg4KBf4CAgYGCgoCBgYB+gICBgIKCgYKAf4KAgIOBgYJ/gYOCgYODgYKBgoGBf3+CgYKBf39/gIWCgICAhIWEgoOCgYGEhIOEg4KCg4OCgoGCgIKEgoGChIKDhIODgoOEg4SDhYOGgoaDgYCEg4KDhIGEg4OCg4ODhYOEg4ODhYWDgYSFhYOEhYWGhoWEhIWFhISDhYaBg4SDhISHh4aGiIeHh4aGhoSEgoSGhoeIiYiHiIaHiYqHh4iHDIaGiIqJiIeIiImJioSJEIiJiImMiYmMiIiJiIeKiomEiDOJioaHhoiGh4iHh4eJiYiHiIiIiYiIiYiKi4uKiouMi4qKi4mKiYqLiomJioyKi4yKiouEijKLiYmJi4yMjIuLi4yLjYyOjY2MjI2Oj46Ojo+Qj42QkI+QkI+PkZKRkJCRkJGRkJCQkYSQPpGQj5COj46Oj4+PkJGQj4+Qj46Njo2Oj4+Pjo6OjY2OjY6Ojo+Pjo6MjY6Mjo+NjIyMi4uNjY6OjIqMjI2MhIsZjY2LjIuNioyNjIqKiYuMjIuMi4qKi4yLi4SNh4t6iouJioqKi4uJiYuLio2MjoyMi4mLiomIiIqLi4uJjIqNioqJjIyLiouLi4mJiouKiIqLiomIiYiIi4mJioyLioqKjIuMh4mKiomJiYqKi4uJioyKioyNiYqKiYqJiIqNiYeHiIuJioiKi4mJiYuJi4iJjIuJg3qBhYaEizmKiIqIi4eJioyNiYuKjImJh4uLiIyJiYqFiYKIh4iDenp7e3+Ig4SFiIOFhIWDfoCCiIeChoR+fn+EgHF9b3J3eHh1c3RrdHl0dXh8gIGCgoGChYaChYSEg4SBgoOFhIOEg4OBgoN/gYCCgIKCgIGCfn6AgIKBgX9/gHx9fH+Ag4KAgYCDgX6AgoCAfoB/foB+goN+f31/gH6AgH+CfoJ9fn+CfoR8hH6Cg4R/hP9//3//f/9//3//f/9//3+IfwICBACAnZqdmZucnJ6fnp+gnZ+nl5+hoZ2bmZ+hm56fnqCgn5ycnqOimJ6Zn6GcoaCdoZ6hnaCdnp+ioZ+goZ+eop2jnqSeoKGlp6KhoqOnp6iloaKkpKSjo52fnZiamJuam5ubnJqcm5+gnZ2dnpqYl5iZlpeYmZqbnp2emZ6VlZmcmZKAk5yWmpiYlJiVlpGUkI6SlZaUlJSVlpKNhfbw+IqOkZeWmJaUk5WQkJKPkY+Qk4+VkpCOkZCPk4+UkZKQkpCQkpGOkZOOjpWWj5KSk5KTkZCUk5KTjpSRk5GSkZGLkJGQkJKSkZOTkZORkI6Rk5CTlpSTlJaUlJOSlZOUmJSUlpGAkpOSlJaRlJOTkZSRkpGSk5OTj5GTlZKSlJaUlpSVkpWWlJaSlZWUlpSVl5aXmZaXl5eYlpuXl5mYmZiZmpycnp6dnpqcmpudmZufmpmanJ2cnpubnJyamp2dnZybn5qYmJyanp6foJ+dmpubmpmampeXmZqanJqYmZiZmZibmZuAmZqZmZeam5mbmpiXl5OVlpeYlpmZl5aWl5eYmJmZmJibmpiZm5maoJqYmZmbm5mampybm52cnJ2cnZycn52coJ2bnpydnp6fnJ+eoJ6fop+gn6Chn6CfoJ6doZ6gn5+dnp+hoJ+hnp+hn5+gnp2fnp2fnp+eoJ6cnZ+cnp2cm5wKnZ6bnJuYm5qcm4SchJ0GnJ6eoJ+ehKBfoaChpKOko6Sio6Kjo6KkpaWkpqSlpaejo6eop6WlpqempaemqKiop6aoqKempqiqqKiop6mop6ipqaioqKmopqanqKenp6iop6ilpqanpqWmpqimpqanp6alo6SkpaeEpoCnpqanp6aop6ipp6ippqemqKinqamoqamrq6ytrqyrqaqqrayqq6usqqmqqqmpqaenqqqop6moqKepqKimp6amqailpaWkpqikpKOkpaakpqelpKSipqSfoKCenp6foKCho6OioaKfoKCin6Kio6SkpKOipqmnpqSoqKmmqqeqqoCoqKipq6irrK2rqqyoqqqnpqapp6mrp6muq6uqqKiprKqoqqioqqqnqqqoqqiprKmqpamnrKiop6mqqqmsqKeqqKqsqqinp6WjsLGwtrOtqKmoqKmlpKmmp6app6mnpKqkqKqnpaampaOloqKjoaKlo6CnmpGSo6ehpaOhn6ChooCkp6amp6OfnZuWkJKFge7jiouVlpOQjY+Wk5WamZyenp6dnp2copyenqGfm5ubmJmampWalpiYl5iYkZaam5yemZiampqXlpOTk5eYmZiXmZucm5WWmpqXmpqYmpSTlJmWlZqYmZ6bn5qZm52Zm5qYnJ2gn5+jo6Oho6CfnJyamwGcEJOPkY+QkZGRk5GQlJKUmIqEk1eSj5SVj5OVkZaVlZSSk5WVjZORlZaSlZWVmJGUkpWVlpWWl5SXl5aUlpKUk5uVlZWZmpqYmJebmpmXlZWYl5iXlo6SkY2QjpGQkY+Pjo+OjZGTkZGQkY+EjoCLjY+Ojo+RkJKNkouLj5GPioiRkJCMjoqLiYuFiYeDiY2MiIqKjI2Kh37u6fCDh4iOjYyNiYuNiIiKiYmIio2HjoyJh4qJh4qIjImKiYuIiImIh4mMiIiLi4iKioiJiYmIiouJi4WKiIqJiomIhIiGhoaIiYiJiYeLiYeFiImIiTuMiomKjYmLi4mNi4uOioqOiYuKiYyNiYyLiomNi4uKi4yNjYmMjI6MjI6OjI+Njo2Oj4+QjI2OjZGOjYSPhI4JkI6Tj4+RkZCPhJCAk5OTlpGSjpCSj5KVkZGRkpGQkY+QkpKQkJCRkJGPk4+PjpGRk5STlJORkZKSkY6Qk4+Nj5GSkZCQkY6QkZGSj5GPj5CSkpCSkZKSko+Pjo6Pj5COkJKPjY6Qj5CQkJKPkZOTkZKSkJGakZGUkpKTkpKRk5KTlZSUlZOUlJOWlJM2lJSTlJSUk5WUkpaTlpOWlpWYlZeWlJWWlZSTlZeYlpWTlpWXl5aXlZaYmJaVl5eVlpWVlpeVhJYsl5WWlZSUlJWVlJWUk5aTlJOVlZaXlpaWl5aWlpmYl5iWmJeYl5mZmpuampmFmhebm5uam5iZmpyZmpydnJqanZ2cm5ycnYScEJudnJyenZ2bnZ6dnp2en56EnSKenZucnp6dnZ2enp6fnZ6en56dn5+enZ2enp6fnZyenZ2fh51Gnp6dnp2fnp2fn5yenZ+fnZ2enZ+foJ+en6Cgn5+goKOinqCfoZ+enp+enp6cnZ+gnZydnZ2cnZydnJybm5ycmpubmZudmoWbgJmbnJuamJmam5iampmZl5iZm5ucm5qamZiZmZqXm5qam5qbm5icn52bm52dnZydm5+dnpycnJ+cnp6fnp2enZ+fm5ucnZyfn5yeoJ2dnp+dnqCfnp+cnaCem56enZ6dnZ+en5qdm52bnZ2fn56doJ6dn52enp+dnJydmqGlpKimgKKen56dn5ybnp2enp+foZ6dnp2dn52dnZycm5yZmZqXm56al5uRioiUl5SXmJeVlZiXmpqcm52XlZSRjouKf3re1n+DjY6MioeHjomLj46SlJOTkpSSkZaSlJOWlZKTkpCQkJGOk46Pj4+RkIqPkZKRlZKQkpCSjpCPjY2Qj5KPM4+RkZGSjo+QkpCQko6QiomMkI6Mj46RlJGUkI+QkY+Qj42RkpSSk5eXlpWVlJGSko+QkwOAf4KGf4CBgYB/goV6gYSBgYCAgIKAgYSCg4GChIGBhIJ/goCEhYCCg4SEgoSAgoKAg4ODgIKFgYCEfoKBhoGBg4aIh4WFgoWDhYWDgoKBg4GBen18eX9+f35+fXx8fX17gIJ/fn9/gIB+e357e4B/fX9+fX97gHt5fICBfHuBfoF8e3Z2doB5dnp4dHp+gHt7fXt8eXp13djbdnd6f4F6fXt7f3x8e3t8en18eX59fHt9e3p8e317e31+enl6e3t7fHt8fXp5e316e3p8enp8en14fHp8fXx7enV6eHh4fHx6fHx5fX17eXx7e3t+fXx9f3t9f35/fn2Afn6AfX1+fIB/fX5+fIB9gX59fX18f4B9gX+BgIGDg3+Af35/gIOCg3+AgoCBgH5/f4CDgX+AgIB/hIKBg4KBgYN/gYCCg4KEgIGBgoOCg4GAgYGBgoCAfoGDgn5/gIGCgX+EgoKBgoKDg4OFhICChISDf4GDgYCAgoKBg4GDgYKCgYKChIGBgoSDhISChWWDhIGCgYKBgoKChIWAgoKChISFg4ODhIWFhISDgoGLgoOGhYWGhIODhIOEhYaGhoOGhISJhoWGhoSHg4SEhoeFhYSEgoSHh4mGiIeHiImIh4aHiYaFh4aJiYmIiIqIiIqLiYqKiYWIHYmJiomJiouJi4mHh4iIiYmJh4aIiIeHh4mJiomKhYmAi4qIiYqMiYmIi4yKi4qMiomKi4qJioqMiomKiomNiomLjIyLiYuLi4qMjY2NjIyNjYyLjIyLi4uNjoyOjY2Qjo6OjY+Pj42MjY6NjY2Pjo6OjY+PkI6Ojo+Pjo6NjY6Ojo2NjY6PjY2MjY6OjY6OjI2Ojo2MjI6MjYyPjIqLjIyAjYuMjIuMjIuLi42OkJCOj4uKjI2NjYyNjImLi4yLi4yMi4uMiouLiomLjIyJi4qLjI2Li4qLi4uJiomLiIaIioqJi4qKi4uKiouLi42Ni4uKiIaJhoiKiouLi4mGi4+NiomJi4yLjImLi4qIiYqKh4qKioiJioqLi4mIiYqKjY2Ah4qMiYuKiomJjYuKi4mKiouKi4qMjYuJjIqJh4uJiYaIioyLi4iMiYmIiYmKjIuKjYyKhoqOkYyKi4qJiIqJiYuJi4qKi46JiY2LjI+Jh4mLiomLh4WGhImHhIWHgnl0eYB+goOGhoWIg4SGh4iKhISDf4B/fHFrxLpudoCAfn08eXZ8eX5+fYKDhIKBg4GAhIKFhIWFg4KEgYGBhICDfoGBfoCBfX+ChIGFgoCDgYF+gIF+fn5/gH5+foCAhYEqf4CCgIB9fH2Af32Af4CDgIKAfn6AfX18fn+BgYCCg4SEg4F/f399fn6B/3+af4N+/3//f/9//3//f/Z/gn7xfwICBACAnJyZmpmcnZ2bnqKdmp2Mj6CjnZyYnJuZmZucmZycnZaenpuemaSgoqGkn6GfoqOfm5+ampydm52cnp6an56doaCfnaOhop6ho6Clp6WioqGhpqCZnJicnp2gl5mcn56ZnJuYmpmcmJucmpeYl5yZl5qamJqZmpuZmpyZmpaVmJWAmZSXm5eZmZKSkZeWlpmSlJaXk5Kcm5GEhIuOh4OJlZOcmJOTlo+SjZGQkZSPjI6Rj5ORjY+OjI6OjZCSkJCSk5KTkJKOkJGTkJOQkpORkpGTkpOXlJSQk5SQko+UjpKRkZKRkpKRkpGTkJKPjpOWk5WTlpSSk5KSlJSRk5OQk5FlkJCTkZSVkpKTk5KPj4uAjpGTkpWVlZaUlpWUlZKTkpWTlZSSlpeUl5aWlpSVl5iXmJeWl5eYk5mYmZqYmZucnJybmpydn56Zmp2cmpuZmJeXmpmampmcm5qanJyfnZmbn5+dnJuEnGienZqYmpiamZmamZeamZmamJiZmJeYmZuYl5iXmJmZmJeWmJeWmJiYl5WXmZeYl5aXlpiXmpmXmJmYmZmYmpuZl5eZmpqYm5ucmpqdmZ6enpydnZycnJudnp2dm5ydn56dnqCen5+foIWfLaKfn56gnZ6enqCfnp+gnp+fnp6en5+gn6CfnZ+enZyenpyan56dnZycmpycm4SaJpmampuam56dmp2fnp6dn5+doJ+en6CeoaGipaOkoqSmpKOlpKejhKREpaWlpKanp6empKamp6aoqKmpqKempqanqKipp6eppqinpqeop6ipqKanp6amqamoqaempaakpKenpqalp6amqKeopaaFpASnpqilhKYTp6Smp6aoqqemp6eoqKmnqqipqYSqIaysqqmqq6upqKqqrKuqqaeoqqioqaipqaemqKekpaioqISngKanqKilqainqKimpqajpKeop6ako6Olp6SioqKfn5ycnqKin5+fnp6doKCfo6KgoqGjqKOko6SopKanqaipp6Wnqqumqqqrq6qsq6qnqaupqaenqaioqKmqqautq6epqainqaioqamrqamppKilqKqprKmtq6epq6mrpqinqqipgKinqaaop6akqKeioqesq6Wkp6enqaappqempKanoaOnqKalqKilo6KioqGkpaOjkI+WkJadmouLl5qmoaCgoKOkp6SgoaWlqqaelZGZmJeMiIuloZ+co5+VkpOkmp2cnJ2dnZucmZ2hn6CcnJyZmJiWmJSUmpaWlZeWl5eYmJeZQpeXmpiWmZaal5aUmJuWmZeYlpiUmJaamJmYmJqgoJiVmpmZnJqbnZuenJuei5SZnZ6anqCenp6moqWemp2enJugnYCRkJCPj5CSko+Rk5KRkYSGlJeRko2Qj4yRlJOQkpGSjZWTkpWOlpOWlZiUlJWYl5WUlJGRlJOUlJOVk5CWlZWVl5SVl5SWlpaYlpmYl5aXk5WYlI6RjpGSkZOOjo+TkoyRkI2MjY+OkJKPjo2NkYyLkI6MkI2Njo2Njo2OjIqNi4COioyMjY2Nh4mIi4qJjoqNjI2IiZCOiIB/hYiCfYCOipKOiouNhoiHiYiKioeGh4qJi4qHh4eGh4eGiomIiIeJi4mIi4iJiIuHioiKjImHiI2MjY6Mi4iJi4iKiIuFiYqIh4eJiIeLiYmHiYeGiouKi4qMioiLioqMi4iKiYeKiTqIiYqLjIyMi4yLiomKhnuIi4yMjo+PjoyOjo6QjI+Mj42OjIyOjYyPkI6Mjo2PkI+RkJCPkJCMkJCQhZFjkpGQkZOTlJGQkJGRj4+Ojo2Pj46Qj42Rj4+QkZGSko6Rk5STk5GRkZKQkpKQj5GOko+PkJGNkZGQkI+PkY+Qj5KSkI6NjpGSkY+Pjo+Ojo6Pjo6MjpGPjpCQkI+Rj5GQkZKQhJFdkpKRkJGRkpGRk5OTkpGUk5aUlJOUlJWUlJKTlZSTlJSUlZSUlJWUlZWUlpSVlJSTlpSVlZWWlpWVlpaWlZeWlpaVlZWWmJmXlpaWlZaWlJeXlZOWlpWWk5WUlJSVhpQQk5OUlJaWk5aXlpaUlpeXmYSXJ5iZl5ibm5uZm5uampyam5qZmpqam52bm5ucmpucm5ydnZydnZ2enoacOp2dnZydm5ycnZ6dnJ2dnZycnJ2bnp2cnZ2cnJ2cnJ2dnp6dnZyen5+enJ2dnZydnJ+cnpycnZ6dnpyEnQyenp2dnZ6dnpygnZ6En4Cdn6CeoKCfoJ+eoJ6fn56cnZ6fn52enZ6enpucnZubnJucmpybnJqbm5ybnJucnZybmpmal5ucm5ubmpmbnJqbmpqXl5aZmZuamZeYl5aWl5iYmpmYm5mZnZuamZubmpmbnJybnZ2bnZ+anZ6enp2enZ2anZ6fnZycnZ2fnZ6enAOdoJ6EnYCcnp6en56enZ2cmp2bnJ+enp2joJudn5yfm5ucn56gn56enZ6cm5igm5iZnZ6emZqcnZ+gmpydnZ6fnZ6bnJ2enZ2fnZybmZqbmZucnJWHhpCEiZaUhX+KjpaWl5iYmJmcmZaYm5qem5KKiI2Ni4J+g5qYlJKYlo2Gh5SOkZGSkhWUkpKRj5OVlJSRkpKQkY+Oj42MkI6Ej0iOj5CSkJCOj5CQj5COkY6NjI6SjpCOkI+RjpCPkJCTkZGSlZSNjJKPkJKQkZSQkpGRk32KkJKTj5GTkZGSmZeYkY+SlJCPlJFqgIJ/f4B/gIF9f4J9f4B3e4OFgoB9gX59gX6Bf4CBf32DgH2Bf4GBg4KEgYCBg4KCgYJ+gIKBgIF/gn9/goKCg4J/gYSCg4SBhIGDgYKCgoGChIB7f3mAfX1+fX17f4B7f397fX5/fH+Cf4R+gHt6fXx8fnx7fnx8fHp6d3t8fYB8fnt7fHx1dnh6fnZ6en1/gHx5gIF7dnR7fnlycnt6gH55en55enp7fH59enh7fHl7enl5e3t8eXh7enp5eHx+fHp9fH57fHd9e32Bf3p8fX19f318e3p+e317e3h6e3x6eXp7eH17fHp8fHh9aH58f319fXx9fn1/f3t8fXx/fnx7e39/f3x8fn59fH57cXx+gYKDgYCBf4GBgYJ/gYCBf39+fYCAf4GBf36BgYKCgYSCgoGBgX+CgoKDhIKAgYKCgYCBgYOCgIGCgX9/fX9+fn99gIF+hH8IgH2Dg4CDhIWEgz6BgIGCgoCBg4CCgH+Dgn+DgoCBgoKAf4GBhISCgIGAg4SEg4B/g4KBf4OAgoCCg4OBhIODgoSDgoKChISDhISDN4KDhIKFhYWGhoaFhYaDh4SEhYeGh4WHhoaFhoWFhoaHiIaFhYSDhISEhYiGhoaHhoeHhoeHhoaFiEKHiImIiYiIioyKiYqIiYmJh4mJhoWJi4qKh4iHiYiKiYmHh4iHh4eIiYqKh4iJiImGiYqJi4qKiomJioqLi4yMiYuIjA+LiouNjoqKjI2Mi4uLjIyEjUOOjo6NjY2MjYuLi4yMjIqMi4yNjYyMjI2MjY2Li46Oi4yMjYyOjIuNjo6OjY2MjY6NjouNjYyMjYyOjI6LjIyMi42LhYyAi4yNjIuLi4qLiYyNjIyNjI2NjIyLjY6NjIyLjIyLi4mJjY6Li4uMjIyKiouLiYqKiYqKiYuJiYyNiouKiouLiYeIiYmMjIuKjIqJjY2MjIuKiYmJioqLi4mIiIaGhYaIioiIiIqLiYuKiYmLh4eIiYmKi4qMi42NiYuLi4qHiIiAiYeKi4yKiIqLioyNi4mJi42KiImKiIqKiImLiomKioqIiomKjImJiYuLiImJiIyJh4iMjYuJi4qKioiJioyIhoSHh4qGiImJi4uHiYuIiouLiYaIjoyLiYyMioiIioqHiIiIhHdxf3N2hYd4bXR4foaIh4eFh4eEhIaHhIqIfndod3p7d3JxdoWEhIKIhn1xdH1+goODgoKBgIF/goSEg4KCg4GCf3+AgH+AgYJ/f35/f3+CgX9/f4CBf4F/gn+Afn+CfYCBgoCBfoCBgYCBgYCBgoJ6fIKAgIGAgIJ+gH+AgG14f4GAfoCEfwuHhIOAfX6DgX2Cf/9//3//f/9//3//f/9//3+IfwICBABmnJyZm5udnaCenp+en6Cdm52bnKOcqJqgnZuZl5mXmJebl56XmpuYm5uam56hm6GbnpydmpufoKOdoqGeo6CeopyjoJ+joqGjoZufoaSfoZ6fo5+dm5iZmpqdn56Xm5qcnZuYlpuahJcUmJ6XlpqXmpuamJianpeYmZmYlJWEmICWkpWal5SRi5SVko+Wk5aZm5eYl5WYjo2SlZaVk5iRkJWTkZOXlJGVlJKQk5ORlZKSkZKTkZKSkYyQj4+PkZCOj5COkpGSk5KSk5ORkI+TkpOUkpGPj46QkJCNkZOTkY+UlJKUkpSRjpaRj5CRkpOTlZKSlJCRkpGQk5GRlZOSkUaTk5WRkpSSkZCSkZKUlpWVkpaTlJSTlJOWlJWSlJSTlJOWlJKUl5iVk5eYl5GWmJWWlpaYmZmYmJiZmJybmpyZm5qbnJqahJuEmICamJyal5eYm5eXn5men52en5yen56bmpuenZqbmp2clpmZmpuZm5uampiYl5eXlZmWlZqYmJmZl5eZmJWWl5ebmZaXmJiWmJaWlpWYmZmZmJmXl5iXl5eVmpmYl5mYl5qZmpqbm5qcnZybm52cnJyYmp2cnZyenp6dnp6fnp2cmwieoaGjoJ6eoIWfHKGdn56goZ+fn6Cfn56fn5+enqCdnJudnZudnJyEnSSbm5qanJuam5qbmpubmpmcm5ubnJ2enp6doJ+hoaChoKCho6KIpGSjpKaho6SkoqKkp6Olpqelpqelpaamp6amp6impKaop6anqKimqKiop6Wnp6eop6empqalqKaop6amqaempKWlpqWkpaWjpKelp6WkpKWlpKOkpqWnpaimp6mopqampaanp6aphKg3qaioqainqaurq6iqqamoqampqqiqp6epqKinp6ilp6empaWmpqaop6WmpqelpKSnpqSoqKWlp4SmE6SmpamlpJ+hpKCho6Kjo5+gn5+EoYCenqOgnZyioKKio6GkpKKmpaalp6imq6imp6elp6eoq6epqaipqqmrq6SkqaenpaekpqinqKWoqaeoqqmnpqinp6moqKanp6mnqKerqKmlp6qpqqufqqmlqqmlqaSoqamrqK2qpqGxrKurq6qqpqenp6mpqKSipqWmo6eoo6WjpYCmp6aloqSloJufmpqcnaKioJ+cm6WioJ2kpqShmpujp6ytr6ydlvyEjYKJkJOcn5ycnpyVkZOYnZ2cnJucnJ2empudnJ2dn52YmZyVmZeYl5iamZaamJaUl5qXmpWVlpaZmJqZl5iWl5KYl5eWlpiXk5iZmJeZm5aYl5iUmJuamx2YlpycmJ6bipafmZubnZqbmZ6Yn5+dnZuhn5icnQeRko2QkpGRhJOAkpWWkJCSkJGVkpmNlJOTkI2Rjo6NkY2Uj5GRj5GRkZKSlY+UkZSRkpCTlZSZk5WVlJiVlJeRmJOSlpaXlZaSlZeZlJWSlJeUko+OjpGNkZGRjI+Pj5CPjouRj4yNjY2OkI2JjoyPj46Ni42QjI2Pj46LjIyNkI2NiImPjImHg4cIiIiGiouLjpCEjWmOhoWKi42Mio6Hh42LiYuOjYmLi4mKio2KjoqKjIqMi4uKiYWIh4eGiYmHh4eGiImMioqKjIyLiIaKiYuLiYmJh4eKiYmHiIiLh4aNi4eKiImIhoyJhoaHiIqKjImJi4iKiomJi4eKjIuEinOLhomNi4uKiouNj5COjoyPjY6OjI6Nj4+Pjo6Pj46Mj42LjY+Rj4yOj4+LjZGPkI+PkZGRkJCPkI+SkZGSj5GRkpGQj5GRkZCOjY+OkI+SkIyNjpCNjZGNkZKSkpOQkpOUkpCRkZGOkJCUk46RkZKTkJCShZA6j5CMkY6Oj4+PkJGQkJGPjY6Pj5GPjY+Ojo6Qj4+PjpCRkZGPkI+QkI+QkIuSkZGQkZGPk5KTkpOTlISTDJSVlJSWk5SVk5OUlYWUhJUWlJWWlZaVlZOVlZWUl5WXlZWWlpaUloSVAZaElXmUlpaVlJWVlZSUlZWWlpWVlJSVlZSUlZSUlZaUlZSVlJaWlJWVlpWVlpaXmJeYmJeZmpiamZmcnJuanJuZm5qbmpqbmpucmpqdnJubm5qcnJ2cnJubnJuanJ2cm5ydnpudnZ2enJ2dnp6cnJ2dnZudnJ6dnJydnJ2chJ1JnJ2bnJ2enJ6cnJucnZycnZ2cnZyfnp6fnZydnpycnp6dnp2cnJ6enZ6fnp6dnp2fnZ+gnp6enZ2enZ6bnp6dnZycnJudnZybm4ScgJqZm5ycm5qZm5ycnZydm5ybmpqbmpuanZuZl5mamJmam5ubmZqamZmampqXl5yZlJWZmJqbmpibmZqdmpybm5uZnZuam5yanJqcn5ucnJucnpyenpmWnJyamJqbnJ6bnZqanp2bnp6cmZ2cnJ6cnZqcnZ+dnJygnZ+bm52dnZ6TH52cnKGfnJ2Zn6CeoJ6hn5yZn5+doKGfoZ+gnJyeoKCEnHKenJ6fnZ2anZ2dnJ2Zm52WlJeRlJWVmZqZlpONl5aXlZqcmpWRkZmdoJ6emZCN736DeX+GipKVk5OTkoyFh4uSlJKSkJKRlJSQkpSTkZOTlJCRlIyPkJGOkJGRjpCPjYuOk5GSkI2Rj5CRkY+Pjo6PjJKEjy+Sj4yOkpGPkpONkY+PjZGRkJCPjZKSj5OQf4uUkJKRkY6Pj5aPk5OQkI+Vk42Pk4CBgXyAgX+BgIGCg4GDhX9/f4GAhIGFgISEgH99gn9/fX99goB+gX+Af39/gYN8gn+BfoB+gYCBhYCCgn+EgoGCf4KAfYaCgYGCf4KDhoGDfoCBgH98e3x/e4B9fnx+fH5/fn17f319fHt+f319e357fn59fXp8f3t+fX57enx8fVN9enx5eX99eHdyc3d2dXl6e3+BfX59fX56e35+fnx3e3l6fHp6fX19e3x7eXt5fnp/enx8en59f317d3l4eXp9enh3enp4e3x7f3x8fnx5eX59fIR7Dnl7fnx9eHl6e3p4fn15hHqAeX15eXl4eXx9f3x8fnp9fn18fHx+f357fXt8fXh8gH5/fn2AgoKDgoOAgoCBgICCgYCCgYGCgoGBfYB/fXx/gYJ+f4CBfX+DgISCgoODhIOCgYKAgoKBgoCAgYGDgH6AgYGBgH+BgIOChIJ+fXx+fnyAfoCDhISDgIKDhYWAgYMrgoGAgYSEgoKDhIKAg4OCgoKDgX+Bf4OAgIGCg4OCgoCDg4GCg4GEgoCCgYWCAoGChoN2gYKBgoKBfISCgYGCgoOEhYSDhYWEhIaFhIWHhYWGhYeHhYSGhYaGhoeHhoWGhYSDhIWGhoaEhYaFhoeIiIeGh4mIhYeIiYqIh4iIhoeHiImIhYiJh4iIh4aIiYiIiImIh4eIiIaHiIeHiIeJhYeJh4eIh4iIiISJEYqKiImLiYuKiouMjY2Mi4mMhosLjI2LjIuMioqLioqGjCmNjIqLjYyMjY2Mi4yNjY2MjYuNjouMjIyPjI+NjYqJio2NjYuLjY2MioSMgI6KjYuMjIyOjYyNjY2MjI6NjY2MjI2Mi4uNjIyMi4uMjIuMjI2MioqLiouKjY2NjIuKiYqLi4mLjIuLi4qMioyLi4mKiomLioqIiouMiYuLjIuIi4qJi4yJiIuLjI2Li4qJiIiLiomJjI2LioyLi4mKi4eFhYmJhoeLiYmKi4mMgIqJiYmIiImLioqJiImMiYmIi4uIiIeIiImJi4uHhoqIh4mLiouLh4mHhYqIh4qLiYiKh4iJhoeHiouMi4mJi4mMhYqJiYmKhIuIh4yLi4qGi4yJioqMi4qIgIiKjIyLjIuMi4uMioqIi4uJjYuMjIuMiYqJiYuLiIiKgYOEgISFXoWHhoWEgnt+g4aFiYqHgoJ/iImKiIN/fXrRb25pbHV6goaFhIOCe3J1eICEgYF+goKDg4CEhIKChIWDgIKDfIGBgH+Bg4J/gX9/fX+CfoKBfoF/gIOBf4B/fn97gYGEgi6AfX5/gH+ChH5/f359gIKBf358gIF+f35xeYB9gH9/foB+gn+CgH58gIOBfICC/3//f/9//3//f/9//3+QfwF+9n8CAgQAgKGdnZmcnaCdn6Cjnp6gm5mZoZ2ZmZ6Xl5ycmJmbm52enZ6bnp2Ym5+bn5+cnZugnKCanZ+ZnKCin6WfoqKdn6OZnp+anqegnqOfmqCdoJ6empycnJuYmJiZmJaWmJaYmZydmZmbmZWcmpiamZedmZiamJ2XmZmbmJeXlZKXmpecOJ2dmpaZmpOTlJOPk5iVlJKTlpaQkZiWmJuemZiclpmUk5OXlpKVlZSQkpWTkJKSko6SjZKUlJORhJAkj5GSjY+QkZGQjJSPk5WSkpGPkY6QkJGTj4yPj46Rk4+QkpCRhJKAkY2Qj46Tk5KSkpOPk5OTko+SkJCRko2RlIySk5COkpWUkpKSk5OUkI+QlJSUk5GTlZSUlJWWlZaVlJKUk5SRlJeXmJeSk5aWlJSRmJaWlpiXmJiYl4+TmJqamZqbmJmcmpqanJuZm5mYmZabl5iYmZqXlpmYmpmZmZybm5ybnJc+nJudmpqWmJmbmpuZm5qcmZycmZmZmJqZmZaamZqZmJiYmZeYlpiYlpWUlpWYmJaVl5eVlZaYl5iYmJmYmpmFmGmZl5qZmZmbmZiWl5ubmJudnJqdnZ2enZyZnZ2enpydnp2cm56em52enJ6fnqGgn5+goJ+hoqCenZ+en6Cfn5ycn6Cenp6fnp+gnJydnZ+enZ2dnpycnJ2dnZqcnZqbnJ6cnZucnZ2enJyFnw6dn6GhoKOhoaKhoaGjpIWlCqOlpKajpaWkpKKFpEmmpqemp6eopaampqWlpqamp6Wnp6emp6iqqKelp6WmpqelpqWlpaelpqalpKWlpqWmpqKlpaWko6Skp6WjpKSmpqSlqKWlpqWnhKYfp6alp6enpqWlqKaoqKemqamnp6ipqaqrqamoqaioqYWnFqimqKenpKampaampqenpqikpKWmpqaEpUampaSno6SjoqWlpaeqqKako6GinaKioaKhoKOjoqCeoqKfoZ+joKGjoZ+ipaOho6OmpaaipqSkpqelp6SoqaamqKanqaqnh6iApKmopKaorKSmqKakp6qopKaoqKWnqKmrqaqnp6iop6mop6Wnp6ilqammpqalq6iopaSmqaqnrqqjn6muq6yoqKSmqKanp6WnpaOhpaGio6GjpKajo5+fo6GjoaKgpKSko6Wio6OmqKejp6KgpKGeoaOkoJmB3u+OmYb//I2Tl4txlJOWmJ+hnZ2epKGgoaGfnqCdnJ2dnJycnZucnJqbnJ+bmpqal5yYmpeYmpuYlpeVlJWXlpaXmpqWlpSVlJSUlpaXlZWUlJaYlJaZl5iZmpiYmpialpydnJqamZubnJmYmpiXnp6cm5uZmpiYmpqdnJ2Al5SSjpGSlJOSkZWQkpWQjI6UkJCPkoqMkpKOjpOSkZKSko+SkYyQlZGSkpGTkJORlpGRkpCTlZSTmJOYlpOWmY+VmJKTmZaUlpOQkpOSkpKRkZCRkI6PjY6OjI6PjIyOkJCMjI+NjJCPjI+MjZKNjI6NkIuNjI6Li4yLiY6OjJBwkZCQi42Oi4mLiYWJi4qJiImNi4iJj42Mj5KRjZCOj4uKio6NiYyMjYmKjIuKiYmKhoqGi4uNjIqIiImIh4qKhoeIiIuIhouIio2KjImIioeIiImLh4SHiYiKi4aIiImJiYiJiomHiYWFi4qKi4iKiISKJoeJiYeIi4WJi4WJioiGiIyMiomKi4yNioiKjo2NjYuLjYyMjI2PhI4jjY6Nj4yNj46Qj4uOjo6MjYqOjo+PkI+QkI+PiIuQkpGQkJKEkFSRkJCPjZGQj5COkIyOjo+Pjo6PjpGPjY2QjpCRk5COk4+RkJCMjY+SkZGPkpGTj5KRjo+Oj5KQkY6SkJKRjo+OkY6Pjo+Pjo6NkI6Pj4+Njo2Ojo2FjwWQkJGRkISRG5KOkZCSkpKRkY+Rk5GRk5STkZSTkpSUk5GUkoSTKZSUlZSWlZSUlpSUlZWWlZWVlpeTlZeWlZWVlpWUlZaVk5aWlJaUlpaVhJZWlZeWlZWTlJOVkpWVlZOUlZOUkpOUlpWVlZSVlJWWlpaXmJWWmJiXmZeXmJeampmZm5ydmpmbnJmbmpuampyam5qbmpqbmpuam5udm5uanJqam5ucnZyFnRCcnp2cm52cnJ2dnJ2cnZufh5xlnpydnZqdnZ2cm52cnZycnZ2enZ6dn52cnZudnp2enZ2bnJ2enZydmpucnp6dnZ6fnZyfnp6goJ+fnp6cnJ+dnZ2cnZ2cnZycmpybmpydnJybmpyam5qbm5uamZmbmpqZm5mamZeEmzydnZyampmZlpmampuamJuZm5iYmJmYmZeamZiamZiam5uamZuenJqYmpqZm52bnZucnJmcnpubnZ2anJyEnRucmJ2dmpydoJmbnZqamZycmZucnJqbnJydnJ2FnAOdnZyFnRqgnZybnpyhn5uen52enp6lo5uWnKGho6GenIWeGJycnJuYnJmcnJmbm5ycm5iYm5iamZmYnYSbgJmamZucmZibmJeZlpOWmpqYj3XL3YGNffXuhYqMhI2Ljo+TlpOQk5aUlZaVlZGVkZGUkpKSkZKQkpKSkJGTk5OQkI6TkZOQkJKSj4+OjI6OkI6Njo6Rjo2Mjo2MjI6Oj46PjYqOkI2Nj42OkZGPj5COkI2Tk5ORkZCQkJKPjY+ND42SkpKMkI+Pj46OjZCRkICDgoB9f4CBgICAhX6AhYB8fYGAf36EfHx/gX5/gYCCgH1+gIKBfYCEf4CDgIJ+f32EgIGAfoGBgYCHgYWDgIKGfoKEfYGDgoODgHyAgoN/foB+foCAfH59fn55fXx5e3x8fHp8fXt8fX58fnt8f358fHx9enx9gHx7enl5fH6AfoB/fYJ6fH96enx2dHZ6dXZ3eHp7eXt/eXqAf4F+f3t6fHh7fn17fH1/fHl7eXl4en55e3p7e359fHh6e3p6fH96eHp6fXp6ent9fnx+e3t8enp5ent6enl8e358eHp6e3p6d3t9fHl5dXZ8e319eXt7e317e3p6e3p7fHp9fXl9e4B6enh8fnx8fX+AgX9+f4KCgYGBgIJ+fn+AgoJ/gYKBgYCBfX2Bf3+AfX5+fn9/fn+AgYKDgYKDg4J8fYKFhIOBg4GBgYKEgIGBf4KBgoB/gX2Af3+AgX+Af4J/fH+Af4CCgoGAg4GBgIF/gIGBgoOBhYOFgYKEgYGAg4WDgYGCgQiDg4GDgoSCgoSBIIOBgoKCgYKAgoCAgIKBg4OAg4OEhISDhISDgoOCg4KBhYI7gYOCgoWEhIOGhYWGhYSChoSGhYSChIWHhYiIh4eIh4aFhIWFhIWFiIaGiIiEhYWJh4eHhoWGiYaGiIaFiHGHiIiIhomKhoWGh4WIiYmIh4eDhIWIhoaHiIiJiYeIiIqJiYqHiYmLiYqJiYmKi4yMi42MjY2LjIqIjIqJi4yMi4qKi4qKjImKiIqLjYqKio6Li4yMjY6NjY2Kio2Nj42Ni4uMjo2Li4yMjYuNjI2NjoSMB42OjYuMjY6FjQ6OjI2OkI+OjY6NjI2MjYSMH4uMiouLjIuMi4yLjY6LiouNjYqLiYmMjY2Ni4uJiY2EjCeNjIuLi4qKiomJi4qLi4qKiomKiouJiIiJiYmIiomLhomJiIuLi4qEi1iKiYqIiYeIjIuKjYqJhoaHh4eJiIqHiYiIhYmJioiIiYuKiIiJiIaJi4qIiIeJh4yMiYeIiIeJiYqLioqJhYiKiYqJi4eIioWIh4qKhoiJh4eIiYmKiYqKhImAiImKio2JiYyMiYuIi4mOjImKjIeIiomMjoiBhYqNjoqLioiLjIuNiYqJioiJiY2LiIuKiouLh4WHhoiKi4eJiouIioiGh4iHhIGGh4WIgX+EiIeHfmOrt2p5buLVdXp6dn99f4GDhoR+gYSCgoODhICEgYKEgoKCgYKBgoKBf4EigoKDgYF/hIGBgICBgn+Af36AfoB/fX18gX1/fn9+fX6Bf4V+K3+Afn1+e3x+gX99fn6AfYCAf39/gIB7fn18f35+goGCe4GAfX5/fn6CgYD/f/9//3//f/9//3//f4t/B35+f39/fn71fwICBACAnJyhnJ2coKGin6GcoKCdm5+dn56YlJudnJ+boJybn56ZoZ6dmp2WmZ2YnJ6anZqem6CinZ2fn5yenaKbnpuYnZubnp2amZuanJ2YnJqal5iZlpiWl5mWkpaWl5iTmZeXmpyan56Vmpubm5mbmJyZmZeZl5uamZ2ZmZuXm5qTl5ZLmJibl5WVjJWVmJ2UioyJkJKSlJeVlJiRkpaWmJWTlpSQiI6VlJeSk5GRjpSSlJSQjpaRkJSUkIyQkI2Mjo2Pjo+QjYyQj5GOkZKUhJIZjpCRkJGQjpOSkI6Pj5GSkpKTkZKRkpOQkISSJZWSko+SkJCRkY+QkJGQjJCQkpGQkZCTkY2QkZGPjpCTlJOTkpKEk3iUlJKUl5aSlZORk5aVk5eZmZeXlpWUlJOUlpeYlpSXlpiZl5aamJiZmpmamZmZmJqZmpmcmpiXmZqYmJiZl5iYlpmYmp2bmJiamZqcmpqZmZybmpyZmJqZmJmVmZeamJacmZmbmJmZmZuZmpmYmJmbmJqWlpmYlpiEloCYlZSYlpeXmpmXl5iZmZeYmJmYl5iYmZiYmpqbm5iamZibm5qbm5udm5ucnZ6cnZ6dnZydnp6enZ2dnJ2enZ2enZ+enp6foqOhoaKhoqGfoJ+hop6dnZ2cm56en56fnZ2bm5ydnJqbnZyanZydnpydnJubnZyenJ2cnJ2dnp6cnwyfn52enp+hoaCgpqGEooCkpKanqKOko6Cko6ChoqOipaSlpKOlpqWkpaanpKWko6SkpKWlp6WlpqakpqanqKenqKempKSop6alpqWmpKWlpqOkpaWkpKSmo6GlpaOkpaSmpKakpaSjoqSmoqSlp6ampaSlpaKkpaSlpqalpainpqenqKempqapqamnqainpwelpaakpqinhKY0pKWlpqWmp6Wnpqeio6OkoqWlpKKjpKWlpKSmoaCjoqCmpqenp6WgoKGioqCkpKKho6KeooSggJ+go6CcpKOkpaSho6SmpaWmpqalpaSlpqWmpaajpaalpKaopaSkpaeoqaWmpqemqKeqpqakp6mkqKaopqSpp6Smp6ajpqaoqainpaWnp6aqq6enq6qlqKioqaenpqajpqalp6SkqKeoq6ippKWnpKamo6Olo6Cjo6Cdo6Kjo56iPaGfnZ6jn6CkoKCmpqakoaSwsJ6PmJeboaGlpqqqpoqJgO/n8uHb4PTziZukoKOmr66vqqOipaOipaGioqCEnludn6CfoJ+bnJuanJmZmZuYmZyXmJWbmZuYmJWYl5aSlJOSlJOSk5aUk5iTkZKRlJeWmZaVl5aampiXlZiWl5qbm5ycnJ2cnJyemp6cnZiZmJmWmZaUm5qbm5ybgJGQk5KSkJSUlZGVkpaVkY6QkJKSjIqPkZGTj5OSkZWTjpOUkpCTjJCRjpSUj5KUl5GWlJKQlpWRkZGXkpWRkJSRkZWUko+SkZKSjpOSkI2PjouQjI6OjIeMioyNjJGOjI6PjpOQiY2Qjo+Oj46Qj4yNjouPjo2PjIyOjI6PiYyNgI2NkIqLiIGLi4yPiYCCgYiKh4qNi4qOiIiNjo2Oi42MiIGIjIqQiYuLiYWKjIuLioiPioiLjIqGiImFhoeGh4WHiIaHiYiKh4iIi4mKiouKiIiIiYiGjIuJhoeGioqIiIqJi4iJjIeHiIiHiY2KiIeJh4eJiIaJhomJhYeIiYiIDYmHiomGiImKiImJjY2EjICNjo6NjI2MjpGPjI2OjIyPkIyQkpGPj46NjIyMjY+PkI+NkI+Qko6Ok5CPkI+Oj5CQkI6RkJGQkZGQjI+Qj4+OjoyNjoyOjo+RkI2Mjo2NkZGRkJCRkZCRkI+Rj42PjJKOko6NkY6OkI+Rj42Pj5CQkY+PkI2QjY6Rj46QjY2NjDmQj4+Rjo+PkI6PkI6Pj46PkY+PjpCRkZGQkZGSko+QkZGRkpKUk5KTkZKSkpOTk5STlJKUlJWUlZSFlYCWlpSVlZSUlJWXlZaVl5eXlZaVlZaVlpaVk5KVlZWWlZSWlZWUlJWVlJaWlpOTlJSTlJSTlZaUlZKVk5OUlJSXlpiZl5SXlpaXl5eYmZeamJmZmZicnJ2YmZqYmZmampuampqZm5uZmpubmpucm5mampubmpqcnJ2cm5ucm52bnAienZydnJybmoSdCZyanJycnZ2bm4ScFJucm5udnJybnJycmp2cnZycm56ehJwrnp+dnJybmZucm5ybm5ucnZ6dnZ2fnZqbnZ6fn52dnZybnJubmpydnZucnIWbSpqanJqbmpyZmpuamJubnJiZmJuamZqamZeZmpmcnJ6dnZuXmZqZmpicmpqam5mXmpmZmZiXmJqZlpqanJuZmpqZm5ubnJqbnZ2ahJsLmpuYnJubmpqcm5uEmmCdnJubm5mcm52am5icnpqcmp2cmp2cmJmbmpibm52enJyZmpydnZ6empuenJqfnp2enJ2fnpmdnZ2em5yen52gnp+cmp2dn52bm5uZmpybmZmdm5yalZuamJaYnJiam5mEmoCYl5uino+GkZKTlZWanJ6dln1/eePc49TV2evrg5GZlpeYn52gnZeWmJWWmJWVlJSUk5OUk5KVlJWUkZKRkZOSkJCTj46SjJCMkZCSj5COkI6Nio2Ki4yMi42OjoyPjYyMi46PjpCOjY2NkJCNjY2PjY6QkZCTkpKRkJGPko6SkA+Rj46Oj4uQjY2Sj5GRkJCAf3+DgYF9goCBgYN+g4N+f39/gYB9e3yBfoJ+gX9/hYB9g4OAfoR9gYB9goN/hICBgIWFgXyEgX9+f4KAg318gX9+goGBfoCBgIF8foCDfn57eXx7fn57dXt5fX1+gHp5fXx7fXx5en19fH1+fX99en1+eXx6fH17enp6e316e3qAe3t+e3t3dH59fH12cnFweHp5e318fH55fHx+fHx6e3t2cnd7fH96e3p5dXh8enx+fX96eHp8e3h6e3p3eXh7eHl6eHl6e317fHp9fH1+fn16fHp7enh+fX15e3l8e3h4e3t+e3t9enp6eXh7fnx7enp4eXt8ent4ent5enp8e3p/e3l7enp8fHx6fX6BgICBgH+DgYB/fYB/f4GCfn+CgYCCg4CAgIKBgYB/fn59fn+AgIB+gYGBhIKBhIGDg4J+gIGBgoGCgoCBgoGCfYKCgIB/gHt+gH5/foGCgoB+fnx+gIKCgH+Agn+CgoGCgYCBf4SAg39/hIKBgYOEgX+BgYSCFYSDgYOAgIKBgIOAgYOBg4GAg4CBgISCBoGCg4OAhISCM4OEhIKEg4SEg4OCgoKEhIWFhYaDhISFhYSFhoWGhYaEhoaHiIqJiImIh4eEhoSEg4KFh4SGB4iIh4iGh4iFh1qFiImIh4mJiIeGhoWGh4aGh4eHhoeHiImHh4aIhoeFhoWGh4eHiYeJi4mIiYeIiYiKjIuKjIqLiouMjIyOiYqMiouKiYqLioqKiIuKiYqLjIuLioqKi4qJi4uGjFGLjImLjIyOjYyNjIyLi4yMjYyLi4yMjY2OjIuNjIyNi4yLi46Njo2PjI2Njo2PjYyLioyKiouMjIyKiYuLiYmJi4uKiouKjIuLjIuLiomKiYuEjAyKiImKiouIiYuLi4qEizKHiImLi4mKiYyIiYmJiIiIiYaIh4iJh4mJiIeJioyMiouLi4qJiImJioiLiomGiIiGiYSILYmJiIqIjImMjIiJiomKioqJhomJiYqKh4eJh4qHiIiIhYeIh4WHhoeIiIeGhoSIGIuHiYeJjYmJh4mHh4mIhoeHiIaKiYqJiISKgIuLjIyIiYuLiIuLiYuKioqIhoyMiouIiomJiIqKi4mHioeKiomIh4mLiomHiIuJjY2DiYiIhYWIiomHh4mIhoeGhYeKg3p3gIGAgYKGiYqHfmxyb9DHysDExtfXc36DhIODioeIhYOEhIKDhIKCg4SCgYGDgoGAg4KDgYF/f4GCT4GBgn5/gXx/fYKAgn5+f4J/f3t8en5+fn1+fn99gH18fX2AgH5/fH5+fn+AfX18f35/fX9/foKAfn59fH98f4CDf35+f31+fX+Dfn6Bf33/f/9//3//f/9//3//f4x/iH7zfwICBACAoZ+gnZ6enp2cmqCgoZmdl5qYmpiZmJubl5qdnZ2cnJeanpyboJ6dmZycmZ2dm5qYnZycn6Geo5yfoJubn5ygnJ2dm5afoJyXmZuamZablZWam5aYlpSYlpeWlZuVmJiZmpmanJmYnZmZmJqZl5eYmZeZmZuZmpiXl5SWmpWWl5WAk5iRk5KTlJaVmJWQlJKQk5aUkZKUkJGRlJWUkZGMkomLjIuGk5OYk5WPk5eQkZGRkJGQi4qFioyNjo6Oj5KQjo6PkpKRkJCPi5KOjpGPj4+Ojo+SkZKUk5GUj5OOk5CSl5OSkpGSkY+Qk5GQkJGPkYSQjo+QkY2TkIyOj4yQjZCAk5GTkouQkY+RkZGSkZKTlJCRlJSSkpGRkZWWk5OSlZeXl5aXmJSVlZOVlZWWmJaUl5WWlZmYlpeWmZmZl5iZmZuampaXl5eampaVmZiYmJeXl5mZl5aVmpeal5eXmpqbmZeamJiYmpqYmpeTlpmblpmYl5iZl5iWmJWamJiZl5hFmZaWl5WYl5aWmZiWlJSWl5WXlpmXlJWXlpiXlpeamZqWlpeYmZmYmJmZmpqYmJmbmZqYm5qcm56cnp2cnZ2cnaCdnZqahJwXnpubnJybnZucnJyanZ+dnp+eoJ+fnqKEoD6eoJ6cnp6dnJycnpydmpubnZycmpubm5ydnJ2em5udm5ydnJydmpucnZydn52cn5+foaGhn6CgoqKkpKKio4SkgKWko6Oko6OkpKSjpaalpKSjpKako6WlpqWlpqSkpaOkpaSlpqejpKSlpqWmpqelpaWmpaWmo6SlpaWko6SjoqKko6OmpKOio6SkpaSnoqGkpaWmpqSmpKSlpKOkpKSjpKOkpaakpaajpKOmp6Wmp6apqaapp6aop6anp6WnqaWmAaeEpoCkoqSlpqempaWmpKSjpKOkpKajpaSko6GnoqWioaKkoqOjpaOipaOhoaOlpaOjoqChoaCioZ+koKGepKKio5+lpqWjoqOko6SmqKWoqqOkpKOmp6Sko6Wmo6aiqKaop6WnqaalpqakpqanpaWmoqWkpKalo6KlpqalpqSoqaelp4CqqamqrKmmp6ioqqinpKempqmnp6ekpaOloqSlpKeqp6eqpaejpaSkpqSppqaloaCeoKShpKSjm6Cgn56gn6KhoKKipaOinp2htZWIj5ePmqehnKCro7GlnJ6jpKafmJeNh4egoKCmqaupq7GypaanpKmnqKajoKChn6CcnZ2enVWdn56ampmcmJuZm5iYlZiYl5aVlpKVlJaZk5SYlpOSl5SUk5OTlpaYlpaUlZaVmJeal5qYlpeRmJeWmZaZnJubm5+cnZ6fnZ6Zl5uXl5mYmpmXnpqbgJSSlJKSkZOSkZCUlJWRk42PjpCOjo2SkYqNkZGTkpCOj5KSj5SSkY+Rk4+Sk5GSj5KQk5WVlJeSlJaTkpSSlJCSlJSNlJOUjpCSkZGOko2MkJGMjouLjYyNjYyRjY6Mjo+MjJCNjJGPjY6Ojo2NjI6LjY6OjJCOi42Kjo+LiouKgIqMhoiHiIiOiYqLh4iIg4qNiomIi4eGiIyNi4iJhIqFhIaFgImJjIuPiYqOiIqJioeIiIWDfYCGh4iHiImJiIeHhomKioaGhoWKh4iKiIeJhoeIiomKiouJjImJhoqHiI2LiIiHiYmIh4mIh4iLiIl8hoWHiYeFioiFhIaEhoaJgIuJiouFiYqJioqLjYyMi4+JiY6NjIyMi4uNjIqMjI2QkI+QkJGMjo6Kjo+MjpKQjI6Njo2QkI6QjpGQj46Oj46QkZGMjI2Pj5CPkJGQj46Nj46OjoyMio6NkI2NjJCQkY6Nj4+QjpCPj5GPjI+QkY2Rj4+PkI6NjZCOkI2Oj46QgJGPjo+MkI+NjY+OjY+Ojo6Nj46SkY6Ojo2Qj5GQkJCSj4+Oj5GRkI+QkJGRkJCRkpGRj5GRkpKSkZSSkZOSkpKUkpSSkpOTlJOUk5SUlJWVk5KUkpGSlJOTlJSVlJSTmJWWlZSUl5eUlZOSk5WUlJSVkZWVlZSUk5WVlJWVk5SVLZSSk5OUlZOSlJOUk5KSlZaXlZeVlpeXmJeXl5mXmJiYmpiYmZqYmpqZmZqZm4SaA5ubmYSaHZubnJybm5uZm5mam5qcnJuampyampucnJqbm5ychJsTmp2bmpubnJubm5qZmpybmp2dnISbE5ybnJqam5ucnZqbnZycm5ucnJyFmwGahJwkm5yanZ6cnZyanJydn56dnJqbnJybnJ2am52cmpucm5qampuchJp1mZmYmpiZm5yZmpqam5icl5mXmZmZmJmYm5qanJuYmZmcnJqbmpiZmJaamZidmpuXnZubm5mcm5uZmJucmp6cnJuenZaXmZmdnJqamZubmJqXnZqcnJudn5qam5qbnJqcmpmcmZuYmZual5iYmZmZmpmdnJubhJ6An5+em52am56enZqcnJyfnZ6cm5ybnpudnJydn56bnpyenJ2cnZ6boJ2bm5iYlpmdmZubmZSYlpeWlpibl5WZmZqZmZOWlpyFgouQiZOalpKVmJWdlZCTlpeZlZCMhYGDlpaXmZqbmpyhoJiZmpWamJmXl5OUlpKTkZGQk5OSk5ILkJGOk4+SkJKQkI6Ej0ONjouNjI+QjI2PjoqMkI2MjIyNjY2PjY+Ojo6PkI6PjI+Ojo+Ljo2NkI6Qko+PjZOQkZOUkZGOjJCNjo+Pj42NlJCQgIKBgoB/gIB/fX6AhIJ/f31+foGCf3yAf3x8foCDgH9/foGAgIKAgX6AgX9/gYKBfoF/foGEfoOAfISAfH9+gX6AgoJ9gYCBgICBgX9+gH99f395fXp6eXt8enyAfn18fH96enx6fn99fX5+fX19en15e318fH5+eXx6foN9eXt7Ynx6d3t5enp9enx4d3R0bnZ6e3t8fnl5eH59eXh5dnl2dnh1cnd5e3t+enp7eXt6enl8e3l3bnB5enp6e3x6fHp7d3p7fXp2d3l9enp8ent7eXp7e3p9fH17fXx7eXt4eH19hHolfHt7fHx6e3x7e3J4dnl7e3h8eXh3e3h4en1+fHt+eHp5fHt9foSAgIJ8fYGAgH9/fn9+gHx/f4CAgoGCgYF/f4B8f4B+f4CCfX99fn6AgH+BgYKBgYB/gYCBgYJ/fn+BgYKBgoGBgX9/fn5+fX1/f35+f39/foCCgX5+f3+Af4B9f4GBf4KCgYCDg4KBgoF/f4GBhHyBgH6Cg4CAgoGCgYCAgYF/g4GBR4F/gICFgoB/fn+DgYKCg4SFgYODgoSEgoGDgYODgYGDhIKDgICBgoOFhIWEg4WEhIWFhIWEhIaFhIOFhoiJh4eGh4SHhYSEhIUjhIWEhYSIh4iHhYaIh4aHhYWGh4WHiImGiImIhoeFh4eGhYiEhwuGh4SGiIeFh4aGhYSGHYmJiYeIiouKiIiHi4iJiYqKiYqJioqLjIqJi4qLhYpci4qKiomKiomMjIuMiouMiouLiYqLjIyLjIqLjIqLiouMjIuLioqKi4yLi4yMi4mLi4uKiouMi42Li4qLi4yNjIyJi42Mi42MioyLiouKiomKi4uMi4qKiouLi4qFixaKi4iJi4uKjYuKh4mKioqLjIqJi4qJhYp0iYqKiomIioeIhoiIiYqKiImJiomJi4aJiIqJioyKiYuLiYmKiImJjIuIhYeIh4iJiIiIjIqMhoyJi4iJiYmLioeLiomMi4qHioqEhoiGiImJiYeIh4aGhoiGh4eGiouHh4eGh4eHiYiHiIiIh4aHh4eFhIWEh4CJiIiHjIyLi4yLjImMiYqKiouJi4iIiomKioiIiY2JjoqJiImJhImHiYiHh4qMiYyLiIiJhoaIiYiIiYqBhYCEhYSFioWGhoiJiYmCg4J6b3Z9gHuCg4KBg4B/gX9/gYODhoOAfHl2doSChISGhoWFhoWEhIaAgoGFhIWAg4OCg1qBgoCCgX9/fn9/gIR/gX+AgX+Af4B+f35+fH19fYF+fX+Aen2Afn17fX1+fn98f359foB/foB7fn5/gHx+fXyAfoF/fH57gH2CgYGAgX98fn57f3+AfXyCfYD/f/9//3//f/9//3//f/9/iH8CAgQAgJ+hn52bnKGenJ2Zmp2cl5qQl5aYmJmbmJiVm5mcmZuYmZmZnZ+bmZigmpygoJufl5+bnp2inp6ZnaCfn5ebn52WnZucnJ6cmZ2YmpqalpeXlpWRmJWWl5aTmJmUlpWUmpeYmpmZl5OVlZmbmJiTmJmWmpqXlJeVmpaTlZKSipaUgJCSlpOVlZOXlJiRjYyPiZWMk5KRkpSRlZSVkZGPh4uDg4eDhYeLkpqUkZKUlJOQkJOSkI6Pjov3houJi42MjYyQjY+Pj46OjpCQkJGRjo6NkI+QkZOSk4+Sk5KSk5SRlJGTk5ORkZGTj5CQj46NkI2Qj4+QkpCPkZCPkJKVk5CQgI6NkZKQko+SkJCPko+QkJOUlJKUkpKTkpCRkZKVk5OSkpSUl5GTlpeUk5aTlZOSk5aXl5aXlpaXlpeXl5mVmZqYl5qanJeWmZaUlZmZmJqYmJmXlpWXmZuamJiYmpmZnJeWlpiXmpmYl5iXlZWWl5eUlJWYlZiZlpiYl5eRlJOYKpeWmJaWmZeUlpaTk5WXl5eVlJaXl5eWmJmYl5iYlpeYmZaYl5aYl5iWmIWZMZuYmJmamZuamZqbnJiZmpyenJ2dnp6enZ6bnp6dnZucnZ2fnZycnZ2em56cnp6enZ2FnyCenpydn5ycnJqbnJ2cnJqcnZycm5uZnJybnJ2cm5mamoWbMZyenJ+dnp2gnp+goqGin5+gpJ+goqKjoaKipZ+ioqSioqSjoaOkoqSkpqalo6alpKKEpD2jpKSmpaWkpKWloqWkpaSjpaWnpqakpKalpKSlo6OloqSlo6SjpaOjoqSmpKSjoqGjpaSio6Sko6Okp6WnhaMKpKSmpaajoqOkpIWlc6elpKWoqKinp6alqKanpqenp6Sko6WlpaOko6OkpaakpqWlpaimpaKlpKOko6Sjo6ChoqKio6KgoaKho6OioqKjoaCioKGhoqSioKKhoqOfn6Kcnp+gn52joqGlp6Sko6SjoaWkpaenpqako6OlpKGhpqaEoyKmp6ampqeloqSjpqejpKenqKWlo6aloKOnpqipqqalp6enhKgsqqWqqaWmpqanqKempamrpqWkpqWmqKWkpqikqKmjpKumpKOjpKWlp6anqqaFoXOipaSinaGhoqOioKChoZ2dmqOeoJmcwJSUm56bnqSooKCtna+uur2trKmllYeEj5makqKmq6uurayooaOrqKSrpaSgoqGfn6CdoaCfnp6enJ+cm5qZmpqZmJmZmpyXnJaVlJOQkJOUmZaWl5OVlpKSjpCUhJUrkZWZlZmbl5mWmpGQm5mXmpyZmpqbmaCcnqGbnp2dmpeXnJienZydmpqfnYCTlZOQkJCVk4+SjpCTkJCNh42Nj42OkI+OjJKQkY6Qjo+PjZOTj4+QlJCPlZSQlJGUjpORk5KTjpCVlJSMkJSSjJOSk5KVkZCUj5COkI6Pjo+MiI+LjY6Mio2NiYyMipCNjI2NjIuIiYyPj4yNh42Ni46NjImLjI2MiouGioKLiU2IiIuIiYqJjYqNh4SChH6IgoqKiYiMh4qKi4mIh3+DgH6Bfn5/gomQjIqKiYyNiYiJiYuHiIiD54GFhIWHhYeGiIeIiIiGiImJiImKiYSGGIWGh4mLiYeIiYiKiYyIiomJiYuKiYmLhoSHeoWGhYiHhomKiIeJiIeIio2KiIiGhoiKioqJi4qJiIuJiIyNjY6LjYyNjY2LjoyNjoyNjoyOjo+LjYuPjY2Qi46NjYyOj5CNkI+Pjo+Ojo+QjJCQjY+RkJGOjo6NjY2Pj46QjZCRjYyNjpCPjo6QkY+OjpGMjI2Oi4+OhI1Gi42Nj5CMjIyPjY+PjY+MjYyJjIyPj42PjY2OjYuNjYuNj4+Ojo2Nj46Oj46RkZCPjo+Oj46NjZCPjpCQj4+QkpCRkpGRkIeRDJKSko6QkpKUkpSTkoSTG5GUk5SUk5WVlZaSk5OSkpWRlJGTlJOSk5aWl4SVC5OUlZSUlJKTk5SVhZQPk5SVlJWVlJSVlpSRk5OThJSAk5WTlZOVlZeUlZWXmZmXmJialpeZl5mXmZiZlZiXmZiYmpmYmZqYmpmbnJqYnJycm5ybmpuampmam5uampucmZqbm5qam5qbm5yampubmpqbm52cmpycmpuanJybmpubnJybm5ucm5mZmpqZm5qdnZydnZ2bm5ydm5ubnpycmps5m5ucm5ycnJubmpydnZydnJydm5ybnJycmpuanJqbmZuZmpubnJqbm5uam5uZmZqZmpqZmZmamJaWhJkEmJeZloWZC5iYmZmZmJiZmpiYhJpulpaYlpaWl5aWmpuZnJyanJudnJibm5ycnJuamZmXm5qZlpybmZeYmJyampqbm5mYm5aZm5mam5mam5qZnJuXmZyanJ2dmpqcmpyenJudn5qfnp2dm52cnJ2fm5yfnpuZnJudn52fnp+enZ+amZ6FnAGbhJ0Tn52ZmpqamZucnZuWmZiam5eXloSXgJOalZeTkaaJjZSUlJWZm5WVm5Kgm6enm5yYmIl+foePkIuWmJ2bnJ6cmpGWnJiUnJaWkpSWlJOUkpSTlJOTkpKWkpKQj4+Rjo2QkJKQj5ONjIyLiomKjJCOjo+LjpCNiomJjY2Pj42JjZGNj5OOjY2PiIiQjo+RkZKRkZGOlJGREpSQkY+QjouNkI6SkZKTjpGVkoCCgH59f3+Egn9/fH6Af399eoF/gX5/fn19e4SAgX5+f4F/fIGBfH5/gX19goF/gX+BfIB8f32Afn+BgYJ8f4F9e39/g39/gH+Ef317gn5/f4B8eX98fn16d3t7d3x5eX57en18eXh6eHt/fXl7d3t7e3x7fHl8fXt8e313fHh+eYB5eH16enx5ent9dnNycm9xcXp8fHp/enh7e3p7e3R3c3R3dHNxcHd+enl7e3x9eXh7e3x7enp3xXB3eHl5dnl7e3l5e3x3eHp4enx9enl7eXl4ent9fnx5fHt5fHt9eXp8fX5+fnt9fXl4eXl5eHh4enp4en16ent5e3t7fXt8fB57e3t9fXt8fn5+e359e39/f4F9gH+BgYB+gYGDgX+FfoCBfX5+gX6AgX5/f358fYCAf3+AgIGBf36AgX+CgoB/gICDgIGBgICAgYGAgX+BgXx9f39+fX1+goJ/f4CAe319f31/f31/fn99f4CAf31+f4KAgoOAg39/gH57f4CBgICBgoB/f4CAfn+BgICBgH+AfoGBgIKDgYOCgoCAf3+AgyqBgIKAgoKBgoKDgoGDgoGDg4KCgoCBg4WEhIWEhoSFg4OEhYSFgoWEhYSEhiSIhoeHhoWGgoSCgoSEg4WGh4eIiIeGhYWHhoWFg4WGh4iJiIiEh1GIhoaFhYWGhoWEhoWGhYiHhoSGg4WFh4aIh4aGiIqJh4mJjIaIiomJh4qIjIiIiYqHh4iIiYiLiIqMi4uKh42MjIyNjYuLi4qJi4uKiomMjImEig+JjIyLi4uJioqJi4uMi42EjCuKi4uMjIqKi4uMjIqLjI2LiYqKjIuMiYqMiouNjYqJi4yLjI2Ni4uJiYqJhIsQioiJiIqLjIuLiIqKiouJioSJN4qLjIyJiomIioqLiYqIiIiKiYmIiImJiIeJiIeFiIiJiYiIh4eIiIiHiYmKiIiIh4mIhoWHh4eEiYCHiIqIh4aGiIeIiIeJiYeJiYuKhYqJh4iJiIiHh4SHiIiFhoiGhIWDh4eGhoiHhoeHhIaGhIaIiYmIiIeKh4SFi4mKi4mGiIuIiImJiIqLiYyNioyKiIqIiYyLiImLi4qJh4iKiYqMjIiFiYOFiYmHiIiHioqJiYiLiYqJh4uJiUSLjIiDhIaIiISEhIOFhoWEiIKDhHx/e3+FgoKCg4SCgYCAh3+HioKDgIJ7cnJ3fIB9goWIhIWGhIR4goR/eYOCg3+Bg4SCWYSCgYGAf4CDgIKAf4CBfHx/gIF/foN9fX59fHl7fYB/gH98fn5/e3t6f32AgH96foB7foJ+fn19en2AgH+AgX6DfoB+goB+g31+fn1+fH6AfoB+f4J+gIF9/3+4fwF+/3//f/9//3//f/9/zn8CAgQAgKKeoJqgnp6bnJmWnpqZlqGSn5yanJmbmJ6emJ6blJmTnpycmZeWmZucnqCcm5qdl6ChoaCeo5ugoJ2fpJ+bm52anJ2en5iZnZ2XmJiTk5aXmZmWlZaSlpaWlZiXk5eZkpaXmZaXlZiTlpeWl5aXlZqXlpeXmJOVmJKXk5aam5iTgJaYkZiVkJGVlZiUjouOjoeHj4+UkpeQlJOTkZeVjoX9+/+FioyKoJKWkZOTkJGNkZaOkpGPkY2M/YWKjI6Pj4uSko+Qjo+QkZGPjZCPjY2RkJGSkZGRkJGRk5GOkpKRkY2Pk46SkpCUjY+QjpGRkI6Nk5OQkZCSkpKLjY+Mk46OO4ySkZCRko6Qjo+RkpKRk5GSkZKVkY+Sj5KQkJCRkpGRkZOTlJWRl5aXk5KTkpOVlpaUl5aWmZmYlZeWhJh+mZiamJeZmJeXmJeWmJmWmZqblpaXlJiYl5malpiZnJeXlZWZl5iVmpiZmJSXmJeTlZeWlpiXlpaZlpaXmJiXlpaVl5iVlZaXkZOWlZSVl5SUlZWWlpiVk5OXlpeXlZWWmZeWl5aVmJiUmJiZl5mbnJuZmZqal5ibnZybmpaXhZtJnJ2dnZuZm56anpubm5qcm5ybm52enZydnJydnJ6gnp+foaGfnZ6dn5ucm56cnJ2anp2am5qbnJubmZqbmpmcm5uZm52bnJuYnYScgJ2en52goqKho6Cdn6Kiop6fn6GkpKWjoKWjpaWmpaChoqGjpKOmo6WmpqWnpqSjo6SlpKakpKSjo6WmpqalpqWlpKSio6OjpaSjo6KlpKSjoqKkpKOjpaSio6KioKOipKKio6GgoKKkpaOjpaSjpKWloqKipqWjo6OipKSkpqWngKOlpaanp6emp6amo6Wlp6WlpaSlpaSlpaWio6Oko6WlpKOkpqakpaSjoqGjoqWjpaCioqGhoaKjoqKjo6Gin6OjoqGgoJ+hn5yboJ2foaGiop6goJyeoJ6dn6GfoKCfoaGipKGhoqako6WlpqaopKSkpaWkpqKop6WlpqWnp6WlVaSooqWmpKSjoaKgpKKhoqOjo6WnqKmpo6aoqqimpqenqaanpqiopKSoqaalpqWlpaimpqajoKGeoqGfpqikqaOipKOioaanoqakoqOdoKCjoaGhp6SEpYCioaGgnpiOoaGjpKW5pKagmZ+km5yWlpagmpWXrbifpJqWjo+Jl5WJpautsK+sraeil4LO3er9j5+eo6CioJ2eoJ+empucnpyamZqbmZuZmpeXlZaWmZaVmpWYlpmYmpmYmZaXlpaVlZaSlZKVlZSVlpiUmpmYlpOZm5uenJqbmhiemZufn52im56cm5+YnZ2an6ShoKCioKFOlZOTjZKTko+TkI6Uj46NlYiRj4+RjY+NkpKNkpCLj4qSjo+OjIuOkJOTlJGRkZOMlZWWlJKXkJSUkpKVk5CUko6Qk5OTj4+TkY2QjoqMhI+AjIuKiIuNjIuMjIqMjoeKi4yJi4qMiYuNjIyMi4uPjo2MjI2Hi42Hi4eKjYuMiIuMhoyKh4aOjI2LhoKGhH1+hYWMio6Ji4uLhoyMh4Dz8fmBhISDkomPiYqKiImFh4yEiIqJi4aE7X2FhoeGhYSKi4eHh4aJiYiGhIiHh4iKh4mAiYmIiYmIh4iIh4eJiIeFiIqHiYqGi4WGh4SHh4eGhouKiImHiImKhIWIhoyHh4WMiYiKioiIh4mJioqJi4uMi42OjIqOi42LjIyMj42Njo+LjY+Mj46PjIuMi42Pj46Mj46Qj5GRj4+NjY6PjY6Oj42Njo2Ojo6Pjo+Pi4+QkI0qi46Njo2NjY+NjI+Ri42Ni4+Lj4yRjY6Ni42NjIuMjo2Mjo6NjI6Mi4yMhI4ZjIyPjYyMjYqLj46MjY+Lio6NjY+PjYyLj4SOPI+Oj4+Oj4+PkI+LkJCQjo+RkY+Pj5GRkI+QkpGQj4qNkZKSkZKRk5STkZCSlZKVkpOUlZSSk5OSkpORkoaTXJSTlZWVlJSVlZOWkpORlZOTlJSVlZKUlpOUlJWVlZSUkZOTlJOUlJKTk5GUlJWTlJaVl5aVmZqYmJWWlZiZmpaXlpecmZiYl5qZmZqbmZiXmZmam5mal5qam5ubhJowmZqZm5qZmZiampqZm5qbm5qZmZiZmZmam5qbmJubnJqbmpubnJubnJybmZuZnJqbhZoBmYSaJJucm5ubmpubmpudm5qZmpuampucm5yZm5ydnJyenJucm5mbmoabBpqam5qbmISZPZqbmZmampqZmZmYmJiZmZqZmpeZlpaXl5iYmZiXmZiYlpqamJiWlZWZlZWVl5WWmpmam5iampSVmJeWmJmEl4CbmZqbmJiam5qZm5mbm5uYm5mZmJmalpubl5iZmZycmpqYmpaZm5iXl5ealpmYlpeZmZebm52cnZqbnJ6bmpqbm56cnZqdnZqZnZ6bm5ucnJucm52empuclpqYl52dmp+bmpucmpqenZudmpqbl5iZm5qbmJ2am5qbm5iXmpiXlICKmJeYl5ielpqVkZeXlJSOjY2TkYiMmpyOkoyKhIiFjo2CmJyeoJ6cnZqVi3e6ydnshZKTl5SUlJKTk5GSkJCRk4+Pj5CSj5GPkY6PjIyNj42NlIuQjZCPkY+PkI2Pj42Pjo+Lj4qNjYuOjo+NkI6OjIqPkpGUkpCRkJOPj5SUkBKUj5OQj5SNkJCPk5WVlJWVlJVTg3+AfoJ/f3+Dfn6Ffn19gHmDf4CEf398gIR9gn9+fHyDf356fH19en+BgX1+e315gYOFgn2Cfn9/f4CAgX59g3p+gYGBfX6CgXx+fnt9fn5/gHuEend7eXl6eHh5enh5enl5eHh5eXp8fXp8eXl8fnx7enp4ent3enh5e35+eHt7dnx9e3p7fH1+dXN3cmttdnh9fH96fH16eX1+eXPa3eV3dHV0end7enp1eHl1eX93eXx7fXl2zm14eXt4dnd9fnh5eXl7eHh5eH16eoV7AXmEe4B5e3p5eXx7eXV9e3l8fHl+eHh4d3p6e3p5fXx7fHp7enp3eXx4fnt7en16fX99fHx6fX18fnx/fX5+foJ/f39+gX+BgH6AgX+Cgn9+gH2DgoF+f398f4B+fH6AgICCgYSCgX+Af39+gH9/f35+f39+gIGAgYF7gIF/fXx/fn9+foB/gX18fX98fHt7fX5/foF8fn99fn5/fX1/fX+Af35/gH19fn6AgIGBf4GCgYF/gH5+gYF/gIN+fICAf4GBgX9+gYCBgYCCgIGCgoKBgIKBfYODgoCBg4GBgoKDg4GBgoOCgoF+gIGDhYODhIWGhISDg4WDg4KEhYWEg4OEhoSHhQmFhIODgoKDhYOGhW+HhoqGh4SGg4SFhomJiIeJh4eIiIeHhoaFhYaHhIaGhYaGhYiHiIaHiYaIh4eIiYmJiIiGiYiHhomGh4qJiYmIjImJiouLiYiIiYuMiouIjIuMiouLi4qJi4uJioqLi4qKi4mJi4mKi4mJiomKiouFihmMjI2LioqLjIyKiouLi4mLiIuMi4iJiomJhIoIi4uMi4qJiYuEihmJioeLiomIiYqJioiKioqLioyKiYiIh4mIhYmAiomLi4uKiYmKiIaIiomHiIiJhoeHiIeHiYaJiImEhYaGh4eIiIiGhoiHiYeJiIeGhIKDiYWEhYmGh4mJiouIh4mFhoiIh4eJh4SFhYiFh4iFhoiKh4iIh4mHiIeIhoWFh4aEiIaFhYeHiYiHhoWEgIWHiISHhoaDiYiEhYaIhYcIh4qJi4aIh4uEiICJjIuLioyHhYSIiomIiIqJiIaFiIqGiImGhYOBhoaFioiIh4eJiIiHh4iIiIuIiYiJiIiFh4aIiImGhYSGh4WEfYSDhoaCfISIg4CFgoSCfnp5foB0eXt7dnl4e3Z7eH5+dIKGh4mIhIaDf3Zll6m90XZ/goOAgoKCf4J8gH9/gFKCfn1+foF/gX+Afn57fX1+e3uCen9+f39/foCAfH5+foCAf3t+e39/fX18fX1+fH1+foCBgIKBf319gH99gYN+gHyBfX2CfHx/fICBgn+CgYCC/3+gf4N+ln8Bfv9//3//f/9//3/nf4R+4X8CAgQAY56cm56hnZ2anJiZmZaWl5ugnKGfnpqem5yYnZmZmZubn6Cem52cn5ienZycm56cm6CgnJycm5uanpqfnp+anJ2gnp+WnZWbmJSYl5iXlZWWmJSWlpWXk5OVl5aUlZOYm5aTlYSWgJeYlpiRlpaXmJeSl5eWlJOUmZSako+UkpKRl5qXl5iTmZOVlpKTkI+OioWPipaQkZKYj5GRlpKHg4CHipGWlZaol5WNk5GUj5KRko+SkIuMi4uKgYSLjY6Nko+OkI+Qk5KPkpGPkY6QjpOTkJKPkI6Ok46MjI6Njo+RkI6NkJGQgJOPko2RlJKOlJGUkpaVkZGUjo2Lj4+Rko6QkJSTkZCQj5OTk4+TkJOQkZGQlJKRlpCSk5OSkpSVkpOSlpWWmZGVmZWWlJaUlpeVmJaWlpmVl5WTl5WWlJaYlZWYl5yZmpmYmJqXk5eXk5GXlpWXmpyXmJyWmJqampiW+pCXmJiZMJeWlpmVl5aWlJaUlJeYlpGWk5WVlpaUlZaTlpSTj4+N6vGPkpSSlZeVlJaXlZeVk4WWgJSXmJmYl5iXlpqZlpiWlpiZm5ubmZmXmZmXmpqbmpmamZqbmZyanZycnZ2cnp2cm5uam5ybnJybnZycnZqdnJ6dnZycmp6gnJ6enJ+cm56enZydnZ6cnJyem5mYmpubmpuamZuampuZm5yZmZmbm5mam5+en52eoJ+dnqChn6KhDqKio6Kjo6Gio6SlpKKlhKQsoqKjo6SjpaWnpaampKakpKOlpKKkpaOjo6SkpaOjo6WkpaSloqOjoqWlo6OEpYSkTaOko6OioaSio6ChoqSgoqKipKSgoaGjoaOjo6KjoqKkpaOio6Kko6SjpKSko6KkpKelpaWjpaWio6OlpKGjoqKjpKOkoaSko6SioqSjhKQ0oaWhoqKipKOjoaChoaKkpKKgoKKho6Kho6Sho6KipKCiop6eoZ+gn5+fnKCipJ+dn6KgnYWfMaGfn5+io6GloqGjo6OhoqOmpKWmoqWko6WkpaWkpaampKikpaakpaelp6ejoqWioKKEpCinpainqKWhpaekp6SpqKWlpaOmpqOhoaekqKampaampKehpqiooaOmhKWApqOkpqWmpKSgoqinpaOfo6Gin6SlpKWkqKiioaSgnZuUj6OaoKWgoKGYlZOalo+D1oiOkpKVmJq6taCipKiqk5Sep6qrp6eqp6eilfrQztPV4oCdoKOjoqSgoaKhoKKenp6cmJqXl5qUl5mXnJSZmpiVlJGSlZaYmJmVl5WWnJUzlpeXkpOVlJWWmZqamJaYm5SSnpyWmJyUnJacmZudnp6cnqKgoZudoqKenqCcn6Kiop2fgJOPkZOVkJGQk5COj4qMjZKWj5STko+Sj5GPkY2NjZGPk5GQjZOSk42SkZKSkJKQkJSUkpKQkpKSlZGVk5OQk5OUkpWOk42TjoyPjo6PjIuNjouMjYyMiImLi4uJi4mNkI2Iio2Li4uNj4qNhYyLjY6Mh4yLi4qJiouJjoaFh4mGgIaKj4uNi4aMioqLh4mJhYN/fYeBi4iJio2GiYmMiYJ/fYODiI6MipiPjYeLiIqHiIaLiYqIhYeDhYB5foSGhoWJhoaHhoaLioeKiYeKiYmHiomIi4aHhYaKhYSEh4WFh4iHhYSIiIaKh4qGiYqIhYqHioiMioaJjIaGg4eJjIuHKomKi4qJiImIjIyMiIyIi4qKi4qMi4yOiY2Njo2NjY+NjouOjY6Pi4+QjoSNYY6PjY+LjY2PjJCNjI2NjoyOj4uLjY2RjpCQj4yQjouMjo6LjY2Nj5KTkI6SjY6Mj4+Qjd+IjI+OjYyMi42KjYyMi42LiYyNi4mMio2MjYyMjYyLjIuLh4eC1tOJjI6Mj46FjTKQjouNjo6Njo2Pjo+PkZGRkJKSjpKQkY+PkI+Pj5OQkJGQkJGRkI+QkZKVkZKRk5GUkoSTD5KTkZKTlJOTlJKVk5GSkYSTgJKSk4+UlJKUl5SUkZOUkpSUlpSTlJWUlJOSkpOSlJSUk5SVk5SUk5OUlJOSk5SRlJKWkpKUlpaWlJeYmJeYl5iYmJeZmJiXmZuamZeampmbmZeZmZuamJmZnJqbmpmamZmZmpmYmpqYmZmZmpuZl5mam5uampiZmJebm5mbnJubA5ybm4WaJZuamZqZm5qamZ2Zm5mZnJyZmZiZmZucm5mam5mcnJqampmcmpyEmgWYmpuanISaIJuamZqanJ2YmpmZmpiYmZiZmJmamZmamZqbm5mXmJaYhJkEmJaXl4SZgJiVlpiXmJaWmpuXmJiYmZeYmJaWmZeYl5aYlZiZm5iVlpiZlpiYlpaXl5aXl5aZl5uYlZiamJaYmJmYmZuYmpmZm5eYmJaampqZnJmZmpmbm5ucm5iZmpmYl5mZmZucmp2cnJqYmpyZmpmdnJubm5ibmpqbmZuanJycm5ycnJ6XgJqdnpmYm5ydnJucm5ydmpqbm5eZnJ2ZmZmampuZm5mam5uenJiZmpeWlJCLmpCSlYyUlZCOjpKNiX3NgYaLjIyPkaWcjpOYmp2MjZObnZyYmZuYmZSI27u/xMfXepGSlZaVl5WTlZOSlZOTkpGOj4yNkI2PkI6TiY+RjoyMi4qPPY6Qj5GLkI2Nk4+PkI6IjI2MjY6PkZGPjo6Qi4qUko+MkYyRjJONkZKVko+QkpOSjpGVlJGRkZCUlpWUj5SAgH5/f4J9fnyAf3x9eHt+g4J/gYGBf4B/gX99fXx+fn1/f396fX+AfH9+f358f3x9goF+fn58fICAfYGAgH1+gIF/gnyBf4B+fH9+fX17e3t8en19e3t4enx6end5eH19fXh7fnt5eHt+enx1fHt8fXt3fHt6enp7fXx/eHd5enZMd3p+e3x7d3h5e3p4eXl0cm1zeHR6fHt8fnh8d3x5dXN1eXh3e3x1fn19dnt3e3h8eXt7e3p6fHh8cmpvdnh5dnp5dnd4eXx5eX16eoR7gH5+e356eHd5fHh2dnl5eHp5eHd2eXt3e3l8eXl7end7eHp6e3t4e3x4eXl7fXx9e3t8e3p8fXx9fX99fH17fn1+f3x/f4CBfoCAgYGAgIJ/goGBfoB/fYGDf4B/gIB/gH+AfIB9fn6CgH9+f4B+gYB8fYB/f3+AgYB9gIF+f36ADX18gH1+g4WCfoN8fX2Efm7Een1/gH98fX1/fH98fX1/fnt/gH15fX1+fH9/fn9+foB+gX58eMu+fYCCgYSBgYCCgoCBgH6AgoGBgoGDf4KBg4ODgoOCgYSBhIGCgoODgoOBgoSDgoODgYGBg4WGgYSDg4GDhIOFhYSDhYKEhISFJYGHhoWGg4WGhISCg4SChIKAgoaChoKEhIaGhYaFhIOGhYiHhYWEh0CIhoeIhoeIh4WFhoeFhoeFhoWIhIWEiIeGhoeIiIeHiIiGhoiIh4aHjYyKi4iKi4qMi4iIiYqLiImKjYqKiYuMhIoiiYeKiouMjIyKi4iIioyMioqKiYmKiYyNi4yMi4uMiomKioWLOoqKiYuJiouMioqIiIyKiIiJiIiKjIqJioyJi4uIiImIi4uLioqJiIqIiYmKiIqKiIiHh4iKiYmIiYqEiAKKh4SJF4iHiYiHh4iGhoeFh4mJiIeIhYWFhoaIhIYkh4aHhoaHiIWFhYSFhoWIhYeMiImHhoaGiYmLiYeIioeHiYeFhoZZhYeFi4aEhYiFhIaGhYSEhYSGhoSGhYaFhIeIh4eIh4WIhoiIiYiIiImHh4aHhoeHiIiHiYiKiIaJiYiJhYqKh4mKiIqIiImHhYeLiImIh4aGiISEh4qEgYOEiICHiYuLiYeIhoWHiYuHioiGh4iIiYWGhoiLiYeHh4SEhIJ9hn+DgHWBg35+gIJ9fXG9dXd9gX9/gIV7d3yDhol+fYGFhoaBgoOAgX9wrpmlrbDEbn+Ag4OCg4F/gYB/goGCgIB+fnt+gH1/gH6Cen2AfoB/fnt/f4F+gHt8fn6CgDOAf354fX59fn19fn19fn1+enuAgX97gHt9eoF9fn+DgX1+gYF+eX2Af31+fXyBgoGBfoP/f/9/438BfqV/gn7/f/9//3/3fwF+m3+GfuF/AgIEAICfnaGhnZeempmfmZeZnpecm5aanJ6gn5mcl5qdmJ2ampicmpmamp+cnpmgnp2enp6cmJqYmpeampmUn5mWmpmamqCZlJmUl5iTmZeYlpiYlpSRlJaWlJOVmZaVlJiRlZaUlZSWlpWSlpOWlJWZmJeXmpuZmJmXlpSTmZWWmZmTlICXmJqXlJGWlpWRjpKTkI2IhoeOjpWOlY+QkZGPj5KQiYaOk5OVlpWupZmJj4qNk42NjY6QjIqMkY6PjoP1iY6MkIyPjZCQjo+Pj46SkJGQjo+Rj5OPkY6OjY6OjY2NkJCRj4+RjpKPjo6PjJCSkY6Qko+PkJGQkJCOj42Rjo6Tj4CRkZCNjZOTj4+TkZKTlJOSkZKPkJCRkZOQj5SQk5WXlZSXlZSVlpSXlpaTk5iWl5iZlpeamZmYmJeTmZmWj5CXlJeYm5qbmJiWmpiXlpOVlZeYlZeWl5qbm5qbm5qal5KZmJmZl5mXmJaUl5aVmJaVlZeal5OVlJWZl5eWmZSXlFWXl5SWyNz2yZPXi42Qk5OUkZSVlpSXlJWWlJiXmJaVl5iWlpeWlpaYl5aVmJWamJiUmJmamZial5iXm5mbl5mbm52dnJ6cnZ2anZuZm5ucnpucm52dhJwDnZydhJ6AnZydn56cnZ+fnZubnJ2bmaCbnJyenZubm52bmZqZmZmYm5mZmpubmpmam5mZnJyenZ+en5+fnaCdn5+goJ+doKGioKWjoaahoqOlpqOmpaOko6OkpaSmpqSlo6Klo6KioqSlo6GjpKSko6OjpKWko6KkoqSjoqSmp6Sko6OkpKGApKKjpKSkoqGipKSipKWjnqWloKKkoqKhn6Sjo6CgpKCjo6KjoaCkpKOioqWipaOjpaSlpaWjoaKjo6OlpKOioqSjo6OkpKKipKOjpKKgoKGjoqSgo6Ggn6OfoaChpKOjoqOho6KkpaSkoaWlpKKjoJ+ioKCjo6Kfn6KhoZ+dn6IFn6ChoqCEn4Cgop6fn56doKCjoJ+jpqGhp6Kho6SlqKSiop+hoaOko6GjpaKkoqSkpKepoqWjpKWlo6OioKKjoqekp6eopqiopqempqeopqenqKempKKjoqSmpKipraejmZ6mp6elpqinpaSjo6SkoKOipKSjpqShpaSkpaKgoqSlpKGjpKCeoA6hn5uck4icqaCblZmdmISULY/p95KWkJOUloCHrbWsnaCWp6Wurq2npaylo6Ob+N7e5e/n4YygoKKhnp2gpIShVJyen5uXmpiampiWl5WTlpaZl5mYlpmWlZaUl5aZlpeYl5WSkZOWlpeXmZuTl5ebmZyal56anZuZkZqcnJyfl5qZl5ycm6ObmqGelpWcn5qen5+dmoCSkJSUk42Rjo6TkI6OkY2QkIuPj5GTkY+RjI6RjI+PkY+Rj42Nj4+Rj42WlJKRkZCSjpKOkY6PkZGKk5CNj5CRjpWQjZGLkI+MkI2Pio2Oi4yKiouNiouLj42Mi4yJi4yKioqNi4uJjIiMioiNjYuMkI+OjY2MiYuKjouMjY6HiHOLi46NioeOi4uHhYmKiIN+fYGEhYqGi4eHhouHh4qHgn6IioyKi4mcmI6CiYSGiYWFh4eJhYSGiYWFhHnigISDh4KIhISHhYeGhIaJiImHhYeJhouGiYeFhYaHhoWEhoaHh4eIhoiGhYWGhYaJioiFh4SFhIg6iYiHhoiHiIuHiYeIhoaKjIiJjIuLjI2Li4qKiIuMi4uNjIuOio2QkI+Ojo6MjpGPkI6QjI2Rjo+PkYSPHY6Nj46LkI+NiIiNioyNkI+QjY2LjY2NjomKjI2QhIxMjo+Qjo+Rj46MiY6Pj4+NjYyNjYuNiouOjI2NjpCNjI2MjI6Mj46Pio+LjI6MjL7L4n9juoeJjY6OjYyNjY6NjouOjYyOjY2MjI+Qj4SOgJCQkI+PkIyRj4+Nj5GQj5CRj4+OkI6RkJGSj5GSlJWTlZSRk5CRk5KSlJOUlJSVk5OSkpWVk5ORlJOSkpOTk5KRlJOVk5GSlJOPlJGSk5WVkZKUk5OSk5KSkpGUkpGSk5STkZKWkpOUlJSVlpOVlZaWmJOWl5eWlZOXl5aUmZWVRZqWmJiZmZmamZeYmZiYmZmampucmpeampmYmJmbmZmZmpqbmZmYmpqZmJmamJuZmZqbm5qbmZqbmpmamJmampqZmZqXmYSagJSZmZeYmpmamZeampuZmJuYmZmZmpmYmpuamZqcmpuZmpqZmpqamZmYmZmZmpqamZmbmZqZmpuZl5mYmZqZl5eXmZmalZeWlpaYlZaTl5mZmZiXl5mYl5mXmJWampmXlpSVmJeVmZmYlZaWlpeWlJWYlZiYmZiXl5eVl5eVlpeVJ5SXlpiXlJmbmJecl5SXl5mdmpmYlZWVl5iYlZiZl5mXmJaYm5qWmoSZMJiYmJaYmZibmJqanZqdnZydm5ucnZycm52cnJmbmZmbnZ2cnqCbmpCUmZqZmZqdnISagJycmZuam5yYmZqYnJubnJqZmJmbnJmZmZaWlpiWlJSNhJOXkY+NkJOOjo6Ki4bf846QiY2Oj3p2kJ2bkJGKmpqfnp6ZlpyXlJOG1snQ3OPc2ISVlJWTkpGUlpKTk5WRkpOQjY+Oj4+Ojo+OioyOkI6Pj46RjY6OjI+Nj42Pj46OMoyMio+NjI+Rk4qOjpSQkZCNk5GVkZCKkJGSkpKMj4+MkY+QlJCNlZGKipCUkJSTkZGPgH+AgH5/fX19fIF9foB+fYB/fH9+gYJ/foF6fX96fn5/foB8e3p/f4N8e4KDf39+f358f36AfX9/fniBfnp+gH59gn57gHh/fn1/fHt7fX97fHl5enx5e3p+fHx5e3d5eXp6fH96enl9eXx6d3x+enp9fH57fHt7fHd8eX19e3Z5gHp7en16en17e3l5e3t7dG5wc3R4eXZ8eHd4fnd6d3t3c3p5end5d3yAf3R7dHd6dnh5eXx5eHt+eXl2asBxdHJ4d3t1dXh4d3V3eXp7fnt4e354fXd6enh4eXd5d3Z2d3l3eXl4e3d2eHh3dnp7e3d4d3h7eXl8fnx8fHt6en16gHp5eXh7f357fX5+fnyAfn59fnp9foB+gX9+gX9+gYGBf4GAf35/fH+AgH1/g4GAgICBgoB/gIB/fnyBgH98fX96f3+CgYB+f3x8gH6AfHx9foB9fX59gIGBf3+Af398enx+fn99fXx+gH2BfH1/fX5+gIGAfn9/fH1/gn59fIB+Qn6AfoTfy9JgU6GCg4SDgn9/gH+AgIF+gIF/gX+BgX+Cg4SAgYGAgoOBgYODgIKDgn+Bg4KBhYOBgoCBgIKBgYOCgYSDJYSCgoWDgoODg4WGhYSHh4eGhYKFh4SGg4aFg4KDgYSCgoSDg4GFgwuFgYODhoaEhIaFhoWFDIaGhYWFhoeHhYaJhIWGG4eGhoiJhoeFiYmKiYaEhoeGhIWFhouJi4iKi4SJBIqIiYeGilyJh4uKioeIi4uKioyKiouJh4iLi4uJiYyIi4uKiouNi4qJi4uKiouJiouLioiJiomKiYqKjIeKiYeHiIaJh4eIiYqHhoqHiYiGiIiJi4uJiYmKiYiIiYqKiYeIiIWHgIqJiIeIioqKiIiKiYiKiIeIh4eFhYiHh4SHh4aGiIWGhIaHh4eGhIWHh4aGhISDh4OFhIODhIaGhIeJiIeGhoWEhoOFiYWHiYuGh4aGgoaGhYaGh4KFhIaFgoiJhoOKiIOGhIeKhYaEgoGDhIWFhIeHhYaEh4SFiIeFh4WGhoaHH4eGhYaHhoiGhomKiYqJiYyJh4mLioqIiYqKiYeHh4mEiiGMiYqAf4N/gYWHh4WFhoWFiIeHioiIhoWIioaJiYmKiYeEiICHiIiHg4aGhoKHg3h/goB8fIOHgoGAen530OKBgn6AgIJqX2p7gn99doKCiIiGhICGgH15aqSpucrSyMJ0gYKCgH9/goR+f4CCf4J/f3x9foF/fX5+fnt8f39/gIGAgX1+fXyBfn57fX1/gHyBfYB/fYCBg3x8foSAf39+gn+Bfx2Aen99fn+BfH1+en58foJ8eoB+e3p9gnx+gX+Aff9/vX8Bfv9/yH8Gfn19fH1+/3//f/9/9X+Cfpp/h37hfwICBACAnp6dm52ampyenZ+al5aSnZibnZ2dmpuYmpuZnp6hmpuamZmfmZqbnZ6dm5ugn56dmZqcmJucm5iWmpyZmpuZlJaZlpWSlpOamJmWlpSZmJWbl5WVlpiRk5iSlJWTk5SUlJWTj4+WjZCWlpSVlJeZmJiQlJeWl5aYmJiTl5KYk5eAlJaSlpKWkpOXj5GPj5COioyMk5KOjpCQjI6Lj4qUlJCRjYmJkI2UmJGMg4eOkZKRkJCOkIuKjYyQjpSPjoGDj42Qj46RkI2PkZGQjo6Nj4+NjZCPj46OkZCRjoyOjpGOjY6SjpCNjo6TlI6TkZKQk5OVk5GRko2QkJKRlZOQk5OAkpWRkJKSkJKRkpOSkJOTj5KTkI+Rk5CQkI+XlpiUkpKRkpWVk5OVlpaXlpaamJiYl5iXmpqZlJWVlZaVlJSQkZKVlpyYmZOVl5eVmJaVmpiXlJeZl5yYlZOZmZqVk5iWlJWXmoWRlpKTmJaWmJSZmJWZlpSSkJKUl5iYmJeXlZiAl5eXlI6MhdbZ/YyPkpKRlJOUlJWUlpSWlpeWlpeYmJmamZmZlpiWm5mWmJqQl5iWl5mZl5iamJqbm5iYmZeal5qdmpubmpmamp2bnZ2gn5ybnpycnJucnJ2dmpyenp+dn56bnZubnZybmpydnZqdnZ2cm5ucnJubnZydmZqbmpgBmoSbgJ2cmpuamZyZm5uampydm52enKCgnaCfn6ChoaGioaKhn6GfnqCjpKKjoqKjo6OlpaOmpaOipaKkpKOkoqSjo6Sko6Wjo6KkoqGjoqOgoqOioqSjoqWjpKGlpaOjpKSjoaSjoqKioKKfoaOloqGjoKKioKGioqGgoaSjo6KlpKKhCaOho6KkoqOjpISjFaGipKOjpKKlo6Kjo6Oko6CioaWipIShQKOhoaCjo6WioqGioZ+lpKWio6OloqKjpKOjpKShoqShoaOkoKChoaKhoZ6fnqCinp+dnpycnJ+dnZ2fn6ChoaCFoWmioaOkpaGfoqCioaGgoqSlo6KjoKCioqWjoaOlpqKjpKKlpaKlpKSio6GinZ2eoaCho6Kmo6SkpaWjpqSmqKShqKSopaenpqOjpqqvr6GijYf+iZyZoKeipaemp6WmpqiopKOjoqSkoqCEoYCgoKKgop6goaKioJ2gpKKhmZmUpZeYmpWVmpmXlpeChYLx//SB+ODlj7ObqaynqqWoqKWsrp2hmJicg/nv9fHq8pChoKGipKCfn6Gfn6Cim5+dmpycmpmYm5eYlpecl5iWl5aXmJiYmZeUkpeTlZOUlJWUlo2UmZiWlpiXlZubmCOcm5ydmZySmZqYmJmblpaWmJeZlJubm5mckpydmJmXmZmdnjuRkpCRkI6Qj5GRlY+MiYeQjY6SkZKPkI+QkI6Tk5aQkY+OjpKOkJCSkZOTkpSTkZGSkJGPk5KPjYyPkoSQdYuPkI6Oio+LkpCRjo2KkI+JkI6MjI2Nh4iPiYuJiIqKi4qJiYaEi4SFioyKioiLjo6OhoqMi42LjI2MiY2IjYaKiY2HjIeLi4aLhYiIh4eFg4SEiYqIh4eGhIaDhX+LiouLhIGAhYWKioWEfoGGiImKiImHiYSFgImHi4eHdnaFhIiHhYiIhYaIiImHhYSFiIWFiIeGh4eJhoiFhIaFh4SFhoqGiIaFh4uOh4qJioiKiouKiYqKhomJi4qLi4mJioiLiIiLiYiKiYqMjImKi4iKi4mJjIuKio2KkI6OjYyNjYuOjoyKjI6QkI2Pko+Sj4+PjpCQkIuMgIuMjoqMjoiJiYqMkI2OiYyNi4uPjo2Pi42LjI2MkYyLio2MjouLjI2Li42Pe4mMiYqNjo2Pio6NjI6Li4mJi4qMjZCOjo2MjYyPj42Ghn3Fxe+GiYyNi4uLjYuOjI6LjYuNjY+OkI+PkI2PkI2Rj5GQj5CRiY+PjY6OkI2OkI6PCZGRjo6Pj5OPkISREZCPkpCUkZGTlpWSj5OTk5SUhJMQkZKUlJSTlJORlJKQkZKRkYSTAZSEkmKRk5ORkZSTk5CUk5SSkpKTk5OVlZKSkpOTkpWUkpOUlZSUlpSWlJOWlZWWmJiZmJiYlpeYlZSWmJmXmJaWl5mamZqam5uZmZqXm5uYmpiYmJmbmpqamZmYmZiYmZialpiZmYSaIpuampibmpqbmZqZmJmYmZqamZqbmpqam5ial5qZmpqbmpuEmRSampqZmZial5mYmZiamZmYmZmYl4WZG5eZmZiam5qamZeZmpuZmpiYmJeZlpmWmZqal4SYgJWYmJmXmJiZl5mYmpiZl5iWl5iXl5iYmJeXl5iXmJWUlJWWlZaSlZSRk5WVl5SWl5qXlpaYl5mYmJeWmZuYl5WWlpmWmJaXmZyZlpeVlpaXmJmXmZmcl5mZl5qal5qYmpaXlpiUlZaXlpeal5uYmZmbm5qdnJubmZidmpuZnp2egJuanJ2in5WUgX31g5GOlJuYmpmanZmcnJ2enJybmZmcm5qZmpuamZmYmZqWmJeYmJSVmJqYmJKRipGLj5GOjpKSjo2Pe4KA6/PmfOzP04KXiJmYlpiVmZmXnJ2RjoqIhG7c4Ojq5OWIlJOVlZeUkpOTkpOVlY+Tko+RkY+PjZCNBo2NjJGNjoSNP46OkZGOi4uPi46MjY2OjY6IjZCQjY6Rj46SkY+Rk5KSkZCGjo+Pj46Pi4uNjYyOipCSkI+TiJCUjo2Oj42VkYB8fnx9fnyAfYB+gYB8eXiAfX6Bfn9/fYB/fHyAfoB/f3x+fYJ8f31+fX+AgYGAgH5+fX17f4F/fXp+gIB9fYF7fYF+fXp+fX9/gYB7fH59eH18en1+fHh4fXh5dnd4eHh6enl4dXl1dnh6fHp4fHx8e3d6enp8eHp7ent/dXh2e4B6fHh9dHl7dHt3en15eXZ1d3Z9fXh3dnh4enh1dHd4fH12dHNzcnRtbnJxdHl5en15e3d+enl3dX16fHh1ZWJ3dXp6eXl4dnd5eHl5d3V5end3enl3d3h5d3l3d3Z3eHV2dnt3eXh2en2AeXx5fHp8fX5+fX1+fH1+gH5+fnx7fYB8f3x6fX18fXt7fn97fH18fIB6e4B+fn+AfYSCgIJ/fn9+f4F+eHt9foB/goJ/goOAgoCCgIF8fX6AgX1/gH16e3x9gn1+fX98fXx/fX6AfH9+fX1+gH5+fn99f3x9enx7e35/bHt9e31/gH9/fHx+fH5+fXx9fXx+foB+f4F/gFGAgYF/fHZtqqXRen5/gYCAf399gX+CfoB/gH+BgYOCg4OBhISBgoGDgoGChHuBg36AgYJ+gIKBgYODgX+AgIJ+gIODgoCAgIOBhIGCgoaFgoKEhRmHhISDg4GDhYaFhYSFg4OBgoKDgoKDg4OBhoKAg4OCgYODhYWGhYSBhYSEhIaIhoWFhYaGgoaIh4aHhoSGhoaHh4aHhYeGhoWHh4mJhomKioaFioqIiYaGh4eKiYuKjIqHh4mGiYmHiIeJiImLioiIiYiHiIeIiomKiYmKiYmLioqKiImJiomKi4qKiYmIiIqLi4iKiIeIiYeGiYU/h4mKiYqHiomKi4qJiIuMioiJiIqIioiJiYiGiImHhIaHiImIhoeJioqLiYmHiImKi4mJhoeHhYmHhYWHiImGhIcWhYiJh4WIiIiGh4aIhoaDhYOFg4WHh4SGgIWFhoaFhYaFhYOEgoSEg4OFhIeDhISHh4iFh4aIiIeGhoiHh4aEhoWGhIeGhoeJhYODgoOEhIWHhYWGi4SEhYWFhoaHhoaFhoaFg4SFhYWGiIaKhYeJioqIioiKi4mHi4eKiIuLjYyKjY2LhoF9cW7ed359g4eEhoaGiYWIiYuKeIqKhoWHiYqIiYiIiIeHhYiHhIaGh4eDhIaKiIeDgnp6e3+Cf3+EhIJ+f216dtLWzHPSt7twc3B+enx7foCDgIOFf3Z4cGRUs8nX29LPd4F/g4OCgX5/gH+AgoB9gX9+gIF+gH6AfHx7fH98fn59fnuAf4CCf3t7foR8M31+foF8gIGCfX2Bfn6Cgn5/gYCAgH54fX17gH17enp+f319eH+Afn6Cen6AfH97fnyBff9//3//f4p/g37/f/9//3+6fwF+vH8Hfn5+f35+fpN/hn7ifwICBAAQoJqbnqCbmp2eopyhmpqemYScgJmcmp2dm5qYnJaam5uempycn5eXm5ianJ6fmp2enpaUl5qYmZ6am5iXnZWVkpmXlpOWl5aQmJWXl5SXmJeYk5WWlpOUk5aSlZGMkY+RkY+RkY+WkZGVlJORmZeZl5Wal5aQlpOVlZGXkpGRlpaVlpWXmJKTlJOOlZORj5CQko6QgJKMkpCRkY7zjo+Qk5WUm5GWi4OI+OyBjImMj46QkJCUjoyXk5CRkY+Qjoz6+4uPj46PkI+Mko+OkI6Qjo2Lj4yRi4yNjI6Qi42Mjo6RkY2Mj5GTjo6QkY6Rk46TkJSSkJKPkZCNjpCRk5OSk5CTjo+SjZGQkJCSkI6PjZOQjpKOIpKRjI+QkJOYl5GUk5WUk5SVl5iWmJiWlZKYmJeVk5SYmJSElkWXmJSXl5eUmJWZmpaXlJaXlZaWl5ubmpiZl5SWkpWXlZiZmJiWlJWXlZKVkZWUk5eVlZiWmJmWlpiYlZaXmJmWmZaYmZiElYCRlZKQkpCTkZSVk5SWl5aWlJaVl5eWlZeXm5iYl5iYlZaWmpqamJeZmZmYmZeanZmWmZ2am5ucnJicmpqZmZ2bnZmZnJyamp2bnZ+cmp2cmpuZm56enJqdnJ6dnZ2fm5ydm52dnZucm5yamJmcmJqYmp2dnJucnJ2cm52em5ybnEGdnZ2anJybnJ2cnZyem5yenZ6dnp6fn56foKKgoKGhoKSgoaKioqGgoqSjo6WipKShpaSmo6KkpKSlo6ajoqGhoYSiLKOjpaWioaKhoqOio6OkpaKhoqOioKKmpKCko6Kio6Kio6Ggn56foaGgn6CghJ8upKSjoqKkpaGhoqKhoqOkoqKjpKCko6OipKSjo6SkpaGjo6GhoKCgoqKhoqGipISigKGho6OjoqKjoKKjoqGio6KkoqShpKChop+goaGgoaCgoqCen6Cfop+fpJ+goKGjnZ+dnZ+fn56fmJ6dn5+goJ6hoKGdoJ+ioKGjpaKhoKGfn6Oho6OhoqOhoKKgpaKho6GjoKKioqGjo6KjoKKfoKGjoJ6fn6GhoaKkpaOjoqOlgKWho6SnpaOlpaeopaejpqOjr66rqKelpaGjpKKjo6KipKampaalpqelo6eloqSlo6OhoaOco6Ggn6KfnqCdnp2dnaGgoqamp6ikpJ+bm5iWj42KkIyBgOqHmZGSpqGJjp+gr6mlrKmlnJqRj4/kgYL/84mfnqGhnZ+cnJmampyaVpqcm56dnpuZl5uemZWXmJeamZiWlZOXmpOVlZiZmJeTj5WXl5qblZ2cl5uUmpSWm5mYl5ucn5iVmJqYmpyal5eVlpaWmZWWmZmTlpSXmZeUlZaenJmYYZSNjpKSjpCRkJWPlIuPk46PkJCQjpCNkpCPj46RjZCOjpOQkJGVio6RkJGRk5OOkZSTjYuOkI2Pk4+SkY+SjIyJj42OiY2PjYiPj5CNjI2Mi4yJioyNiomIi4iLh4OIhYaEh4CEjIWGiYmHhY6NjYuKjo2NhYqIjYqJjImJiImLioqKjIuIioeJiIuJhoWGh4mDhoeEioiHiYXbhIaKh4mLjoWIf3l85OF8iIGEhYaJiYmMh4eNi4eIiYmIh4Lp7IOFhoaGiIaGjYiHiIaHhoaDhoWIgoWFg4aHhISFhIeKiYSEiYCIiYaFiImHh4uHiYeKiYmLiYqIh4eIioyLioyIi4eHioaLiIiJi4mKiYiMiImLioyMiImLjIqOjoqMjY6OjY6LjZCNj4+Nj4qNkJCOjY2Qj4yOjY2Ljo6Lj46Pio2Jj5CNjYyMjo2MjI6Oj46MjY2LjIiKjIyNjY2Oi4qLj4yJi4CHjIuKjYuKjY2OjoyNjo+Mjo6Oj42QjY+OjoyNjY2Li4mJiomLiY2MjI2Njo2NjYyLjY2MjI+PkYyPjY6Ni4+NkJCQjo2Qj4+Ojo6PkpCPkZOPkpGQkZCRjpGQj5KRkpCRk5GOkZKQk5WTkpKRkZKQkZSUkpGUlJaUk5SVkZOUkhOSlJKQkJKSkJGRk5CRkJCUk5GQh5IQk5SSkpSTk5CSkpKTlJOUkoSTMJWUk5aVlJSVlpSXlpeYl5WXlpiamZiYlpiZmZiblpibl5iZm5mZmZqamZmbmJmWloSZXZiYmZqamJmZmZiZmZuZmpuZmJibmZeZm5uYm5qZmZqbnJmZmpiampmYmpiXl5mYl5qbm5mYmZqcmJiZmpeYmJqZmpmalpqampmamZmZm5qalpmal5mXmZiampmYl4SYgJmYlZaYmJqZmJiVmJiYlpeXmJmXmJabl5eWlJSXlpaVlpeWlJOUl5WYlZaZlpaWmJmSlpSUmJeWk5iRlZWWmJiXlZeXmJSYlpeXmJmZlpWVl5WVl5eYl5eZl5aWl5aYlpWYlpiXl5WXl5mZl5iVl5aXmZiXlpeXmJaXlpqamZmYgJmanJmbm52bmpycn6CcnJ2cmJihnaCbnZydm5uamZqZmZibnJucnZ2bnZuXnZqanJyZmpqam5WamJiYmZeYlpSVlZWTmJaWmJianZqamJOUkI6HiIaMhnx64X+QiYeTkn1/kZCamZWblpGLiYN6eMh0evLpgZOTlZSRk4+Sj42PPJGOj5CRkZKSkpGNkJSOjI6OjY+Pj42OipCRio6OkI6Rj4yKjo+PjpGLkZSNk4yRi46Uko6NkJOTjY2Pj4SQGIuNi42Mi46KjI6PiY2Mj5CMioqNkpGNjoB/eX5/gH1+fX1/fYJ6foF7fX5/f31/fYF+fHt7f3t+gHuBf35+gXh8fH19f4B9eX6Af3x5fH57eoJ+f358f3t8eX18fnp+f312fX58en58fXl6eHp8e3l3dXd3eXl0eXZ5eXZ4eHV7dXh4e3h3fHt8eXp8fHx2end9eXp7eHd5ejB7eXl5eHp5eXp7fHx6eXV2eXl1eHd4eXh5enXCcXd9enp9eXN1bWdrw8Nve3Z5dnmFe4B6f395enx9enl1xNF4eXl4eHh5eX16eHp4enl3cnd4eXN3d3h3d3Z3d3Z4ent3eHp5enh4fHt4d3x4enp+fXx+f4B+fXt8f4CAfoB7fXt6fXt+ent7fHx9fX2AfX1+f35+fX2AgH6BgX59fn9/f4F8foB9gX59f3p9f4GAfn+BgIB/f3+BfYF/foGAgHt8e39+fHx7fIB9enp/gIF9e39/fH15e359fH6AfX16en97eX16fXt8gX9+f39/fn1/gH99gX9/gH5/foGAfnyAfn9+fX98fn19fX9/gYGBgIGBf39+gYB/f4CBgn6AgIF/gIN/gYGAgH+CgIB/gYKEhYKBgwSDgIKBhIMggIKBf4KDg4GChIOCgoGAg4KDg4SFg4KCgoOFg4GFhIWEhICDhIaCg4WDgYODgoCBgoOAgYOBg4OCgoOCg4SDg4OChoSEhIODgYODgYSDg4OChIODg4WDhIeHhoeFhYSGhoiHh4WHh4mMi4qFhomJiYeIhYiJhoiJioiHiIiJiYeKioiHhYaIiYqJiYqLi4iIiomIiYiJiYqKiYmHiYiHiIuLiC2Ki4mIiIqLiYqKiYmKh4eGhYeHh4iJioqIiIiKiouIiIiJiImIh4iJiIeFiIuFiA6JioiJhYiHhYeHiYeJiYSHhIh2h4iFh4mIh4eGhYaIh4eIh4iJhoiHiYaHhIGBhISCg4SGhYSDhYSFh4WFhYSFhoaGgoeDg4WFhoSHgYWEhIaGhIaHhoeDiISFg4aHiIWDg4aEg4SHhoaEhoSBgoWFhISChoSGhIWChIOFhYSEg4aGhYaHhoWIhoSHAYmEiICKiouJi4uNiomKi4yNi4uMioWFiIqKh4WKjImJh4WJh4aIiIiGiomMiIuGhoyJh4qJiImGh4iDh4aFh4iHiIaEgYOEgoWDhIKEh4qIiIeDhIF+d358gHtyctFwgnp2d3lraXd0fIKBgH17dHRvXVqlYm7f1nJ+foCAf4J9f316e1h+fH9+gX+BgIB/fICDfn58fXx/fXp8f3t/gnt8fX5+fn17fH99gHyAeYGEgIF8g3t/gIN+foGBgX19gH5/fn5+fH17eX58fnl8fX5+f31/f3t5fX+Afnt8/3+cfwF+jH+CfpV/gn7/f/9//3//f/9/xX8BfpV/BX5/f35+5H8CAgQAgJugnaCelZyfnJihnZuYnp2amJiXmJmampyfmp2fm5mbm5iamJyXmJualZWbmpqdoZ2cmZiVlJaWkZiXlpiVmZeVl5mXlpSSkpWXkZqVl5aTlpWXmZaQlJKXlpWSjY2PjZKTkZCOjZGTkZeWkZeanJWXlJqXk5GQkpSQj5GVlpSWgI+ZlI+UjpGRlZWPk5WQjpGVkY+NkpCNiIuPj5GNiIaLioyOjJqXjYKBiYaMioyNkY6OlpGPjYiPkJGQi42Ljo+L/IiSko2PkY+Pjo+Qj42Oj4uJj46OjJGRjo6LiYqKjI6Qjo+MjY+PkJGQkZSUkY2LlJGRkpCPkpCRj5OUkpGQTpCQko+Qj5GQkI+Qj4yNkpaUk5OUlI6UjpWTlZaVk5KSkpSXk5OVj5KZlpeXlZWYlJaXlpOZlpiVlZWSlZSWlZiWlpeUmZWXlpiVmZeWloSXgJiVkZOTl5eblZWTlZaVlJmUk5mUlZOWl5aWlJaWlpmYlpWYmJaZlZOZlZaWlpWUlZSVlpWWlJWUlJSTlZWSlpaWlJWXk5WUlJSYmJmYmJqXl5qYmpqZmpmYlZqcm5eZmpmXmJiam5uam5ybmJiamJyYmpqamZiZlZmbnZqamJmYgJqYm5eanJ2amp2enZ6dm5ubmpucm52dnJqblpucnJmam5ucnZqam5iamZiXmpmcmJucm5uam5qamZqanZ2dnJydn5+dnqGhnp+eoZ+eoKCfoKGio6Gjo6OkoJ+goqSjoqOipKSio6Oko6KjpaSkoqOho6KfoaWkpKKjoKChoqOkOaGho6ajoqGkoqOgoqOkoqKko6KhoKGioqChoKGioaKeoaCgn6CgoaOhpqKkoaGjoqKjo6ShoKOkpIWigKOio6OipKGgoaOioqGjop+hoqSkpaOjoqChoaKio6GjpKKiop6gn6CgoJ+io6OjoqOhoaCfnp6hn6CkpKSjo56goZ6ioKCgoZ2coZ+dnp6em52dn56aoZ+eoJ6gnaGhpJyloKOgoaGioaOioqGioZ6hoqCgoKGio6Cko6Kko6OiMqWlpaKhoKGgoKGgnKCfoZ+fnqGfoqijpaSkpaOko6SjpaWko56XmJean6KmqKehpaSlhqOAn6Kmpqioqaekp6Wlp6mkpKGkoaCio6KipKKhnZeal56gn52dnaCeqZ6jqaifnJmWlZGVj4eRkpOKgYuNj42XlY6VkJuepJqdpp+Sg+L1/YWEhfiEm56en6Chn5yanJycmJiXmp6enpuamZuZlpmZm5mal5iVm5qVmJmWlpOWlpQ4lpKRm5SRmZWPlZial5eWkpaal5aXnZucnJiVkZOUkZaTkpKUmJeVlpWWlJOYlZeVmpqYm5iZl5l5kZCOkI6JkJKPjJSRkIqUk4yNjouNkZCOkpSPkJSOjY2QjZGQko6NkZCMjZKQjo+TkpGPkY6Ljo+Iko6OkIySkI2Njo+NioqLjY2JkY6PjIqLi4yPi4eLiI2Li4qEhYaEiIqHhoOEhoiGjIyHjY+RioqIj4qIiIWGioSIgIuKjYiQioaLhoeIiomGioyGg4aJh4mDiYiFhIaDhIaFf4CEhISGgo6MgXd3fn6FgoSDioaFjomIhYGIiIqIh4eEhoeF7oKKiISHioeHhoaIhYOGhoSBh4eGhImIhYaEgYSGhYeIh4iDg4eHiYmIiYyLiYaFioqIioqIi4qLhouNgIyKiYmJioiJiIqJiYeIiIeIiouKjIuMjIiOh46Nj46OjYyLi4yPi4qMh4mPjY6MjI6Pi4yNjoyPjZCMjIyKi4uNjI6Ni4uJkI2MjI6NjouLjo2PjoyPjYqJiY+Mj4yMh4qLjIuPi4uNi42MjY2LjYyOjoyNjI2Lj5CPj4uKj4uMaoyLiYuLi46Oi4yLjYuMjIqMi4mNjI2NjY+NjYuNjo6Pjo+QkI+PkI+QkY+OkJCNkJKPjZCRj46Pj4+QkpCRkpKPkJCPk4+QkZCPjpCMkZGTk5KPko+Rj5KQkpGRjpOUkpOUk5KTk5KTk5GEkhmQjZGRkY6PkI+SkpCRkZCOkI+Pj5GUkJGThJJdj4+PkpKRkZGUlZOUlZOUl5aVlZSWlZWYl5aWlpiWmJmYmJiWlpeZm5mWmJiZl5mZmJmZmZiZmpiXmJeZmJaXmpqZmJmXmJmampqXmJmal5eXmZiZl5iam5mZmpmYhJkYmpmWl5qampmYmpmXmJiZmpuZnZiZmJiahJgwmpiXmZiZmJqZmpqamZiXl5mZmZeYlpeXmJeWmJmZm5uXmJiXl5aYl5eVl5uYmJeWhJWAlpSWmJiWlpiVlpSTk5SWlZeamZmXmJOUlZaYlpaWl5SSmJeTlZSVlZaWlZSTl5aTlpiVlpeYl5CZl5qVlpaYl5eTl5aXlZOXlpWXl5eYmJaZlpaZl5WXlpmZlpaWl5eXlpaSlpaYlZWVlpWYnJmbm5ycm5ubnJyenZualI6Pj5KAmJicnpuYnJmcnpybmpiZlpicnZ6fn5+bnpubnZ6bm5mbmZiYmZmamZqXko+TkZSWl5WSk5eUnZaYnZ6Uk4+PjIiOioOLjY2EfIGDiYWMiYKHhYuNk4yNlIuBc8HQ4Xl6fux9jpCQk5OUlJKPkJGTj46OkJOSko+PkI+PjY6OkZBHkI6Qi5GRjpGRjY2Lj42Nj4yLkouLkI2JjJCRjIyOio6SjIyMkJCRko2MiYyMh4+LioiJjYyMjo2MiYmOi42MkJGOko2OjZCAf318e3l4e4F/e4CAfXmBf3p8fHp+gH+AgX97fYB9e3t9fH5+f316f354e4R+e3t9fHx9fn15fH56f3t+gHyBf3x8fX1+fXp7fXp4f399ent7enh+eXd7d3p4d3p1dHZ2eXl2eHd2eHl3ent5fXx+eHh3e3l4eHZ6enl4e3h5enyAeoB8eHx1eXx8eXl7e3h1dnl5end5enZ2eHR1dndycHd1dndxenhqZGRocXZ0eHV7dnV9enp3dHp8fHp6e3Z4eHbQdHt5dnh7eXp7eXp2dHd5dnV5eHZ1e3p4eHZyd3l3eXt5e3d1eHh9e3p6e318e3l9fn1+fnx+f316gIKAfnwCfX2EfAZ+fHt7fH2Eex99gH1+fXqAfH59gH+Af358fX9/fXl8eH1/fX1+fX6BhH0ke35+gH59f39+f39+f318fHmAfXx/fX1/fXyBgX99fIGAfnp5hX1GeXt6e3t+fHx9e399f397fn59f3x+fX59f4F+f318gHx8fn58fX59f399fn1/e36Af39/fICAgoCAg4CAfn+AfoKAf4B/gYSChIENgn6Cg4GAg4OAgIGCgISCgIOCf4GBgISBgoJ/gYCDf4GChYSEgoWBgYCCgYKCg32EhoSFhYWDhYSEhIKBg4KChIKBgoOCgYGDgoKCgYOEgoCBgICAgYWDgoaEhIGCf4GAgoOFgYGDhYSEhoOEhoeFhYeGhoaIh4eHhoeGh4mJiYiFhYmJioiFiYaIhoeIiYmIUImIiImJhoeFiYmGh4uJioiKiIeKi4uKiYqMioiGhoiHiYiIioyJiIuIh4aFh4qKiIiHiomHhoaLiYeHiYqJioeJh4mHiIqIioqHiYeHiYiGhYgIiYiIiIaIi4iEh0KGh4aHiYqJiImJiIWFiIaJh4eGhoeHhoaIhYaFh4aFh4iHg4OEg4SCg4GEhIKFhoeHiImCg4SEhoaEhYaDgoiHg4WEhBiFhIaDiIaDhoiGg4aIh4GIh4eDg4SGg4WEhC2CgIOEgoKFhYWEg4WFhYeGgoWEhoaEhoSFhYSDg4KFhoeEhIWGhIeLiYqKi4uEioCOjoyLh4J+foGChoGGhYWFh4mKi4iHiYmGhIeHh4uOi4mJjYuKioyJiomJiYWGh4eIhYiGhIGDgYKEhoODg4SEhIOFiIyAgn5/fXmAf3p+gIB5cHJ0fnZ7eHBzc3J3fHh2eW9oW5ShwGpscdhxfn57f4GBgYB8foB/f35+fIKBgVF/f35+fXp9fn+Afnx+fIGAfX+Be31/gX19gHx6f3t+f3x5fH+BeHx/enx/fXt9f39+g359eXt7eYB8e3h7gHx8fXt6enp+fX55fn19gX5/en7/f8J/AX7/f/9//3//f/9/138Hfn5+f39/fuV/AgIEAICWnZ6anJ2elZqbm5mYm5WZmZqam5yblZmdmZuVmJmZmJqbmpSYlZiVl5aSl5iZnJaYlpmZl5WVnZmal5iXmJubnJyYmpqWlJqZk5WTmZWUkZSPk5iUlJSTlJOUk42IkI+PjY6NjZCVkpCTkZWVlpGVlpWWlJKTkZWYlpaWkJOYlhSQmJaWlJCVmZaYkJWUlJOPi5eUkoSQgI+Rj5aPi42EiouLgura1uuR9/aOkYiLjI6KiYyRjJKMjI+QkI2LiYyOj4WNlJSQjo2Lh5CQio2QjI6OjI2MjI2MiYuIjIuMjoyPjY+Oj5CQj5KQkJGSkY+OkZCQjY+Rko+RkpGQkJOSkpKRkJCSj5GRkJCTko+MlZGQjo+SkZGWBJGUkZKElICVlpWSlZOVlJSWlJWXlZiUlZWUlZWXl5iYl5SVlZWTlpSXlZiWlpeVkpSWmJeVlZiSk5eUlpiWl5mYlpOWlpSSlpaXlJaVlJaTk5SYlZaSlJeZmJiSlJWXmJWTkpWZlJKSlpGWlZOTlJWUlJOUlpeVlJSVk5WUlZSXl5iXlJeVmGeZlZeZl5eYmZiYmZeYmJuYl5iYmZmal5mampuamZuYlpucmZqZmJiYlpqbm5yYmZeamZyamZqcm5ubnZ2bnJucm5ydnp6cnJubnJmbmpubm52amZuZmpuam5uZmZiZm5yYmZqamZmbhJo/m5uZm5uenZ2fm5udn56enqCkoKCgnqGgoqKeoqShoqSho6KkoqOjoqSkpaKjpaGipqWloaKjoaKjoqShoaShhKIJoKKioaSioKOkhKE6oqGjoqOjo6GipaGin6CfoKCeoaCko5+ioqCjoKSjpKKhoqCgn6Cgo6ChoZ2joqKioaKioKChoqWjo4ShgJ2goaKgnqGjoqSioqWkoaCfoqKioaCjpKSgpKShoaKjoKKhoqWhn6GioJ+in56ioaKioqWgn6Ogn6ShnaCenJ+fnaOfnJ2hnp+foJ2boJ+in6ChoJ+gop+foaGgn6WkpKKhoKCkoqCeoaKinp+epKajoqOjoqOipqSloKGio6OhgKGioKCfnqKhmp2goKOgpKSlpqalpKGepa6jn5uempqgpqynp6ikrKOjpKKgoaKmpaKioKGhpKKjpaGjoqainZ6io6Ojop6io5qTlY6OiZidmaWdnZuanZqYmZqXmpiVlJSVk4b8gZGFhYmIjYr/oZGTlJidm6Wko4rdz9Pl8v6BZ/39kZ6bnZ2hnZ+bmpuZoaCkp6San6CemZaVmZuZm5eVmpqWn5ebmpmampeUkpWRlZiWlJSal5mWm5qdlpmblpqZmJmZmJqempmQkZOOlZaUk5mYlpeXl5WZl5udlpeXmZeYnZubnZuAi5CRjo+QkIuPjo+PjJGNj46Ojo+Mj4uOko+Rio+PkI2QkY+MkI2NiouPjo+Qj5KOjo2OkY2NjpSOkI6NjY2QkpCPj5CQjYuRjouMio+Ni4iKhoqPi4yJiIuJiYqGgYaHhoaFg4GGi4eFiYeKiIqHiYqJioqHiIaJjouKi4aJioqAhoqKjIqHioyJjIaKjIqIhYKOioqFiIeIh4mGjIeEhnyDhIV63s/O4ofm6ISJgYSDhYKBhYmEiYaHh4WKiIeCh4mIe4WJioaGh4OAhoaChYaEhYaEg4KDhYWDhoKEhIOGhIeGh4WGh4mIioiHh4mKh4aIiYiHiIyLiIqJiomJjYuAiYqJiYmMiIuKioqMi4mFjIiKiImKi4mOio2KioyPjo2PjY2LjYuNjYyMioyNi42Ni4qLjo2Njo6PjYuLjI2MjImNjIyLjIyKh46Ojo2Lio2Ji4yKjY+MjI6OjIqOjImJjIqLjIyMi4yKjIuNjI2LjI2Njo6Iio2NjoyLiomOiohziI6IjYuJi42Oi4yLjo2MioyLjIyLi46Mj46NjYyNjY6PjY6Pjo2Oko+Qj4+OjpKPj4+Oj4+QjI6PkJOQj5KPjZCRjpGPkI+PjpCQkJKSk5GRkJORkZGSkZKSk5ORk5GTkZKTk5SUk5GRk5KRkZKQj5KSkIeRDJCOj5CSlY+QkJCRkoSRBpOUko+QkISUNZKTkpOTkpOWmZeWlZWXmJmYlpiZlpeYmZqYmZmamJiZl5eXmJqXlpqZmpaYl5WXl5eYl5eZhJgKl5eXmZeYmZiamoaYgJeYmZmYl5ibl5iWmZaYmJaZlpmZl5mZmJqZmpqbmpmZl5mYmZibmZqZlZuYlZmYmJmXmJmYmZiZl5iYlpSXl5iXl5mamJeXmJmXl5WVmJmXlpaZmZmVmJqWlZaYlJaWlpiWlpeZlpSXlpSVl5iVlZmUlJeUl5mWlJeUlJaWlZiVbJSVmJeXlZaUlJiWmpaYlpiWlZiWl5eVlZSZmZaYlJSVmJiXlJWXl5WWlZiZmJaYmJeYlpiZmZWWlpmYl5eZlZaWlpmXkpOVlZmYm5ubnJuampiWnJ+WlZSWlZSWm56bnJybopqbnJuamZqdnYWYgJybm56bnJyempeXmZiamZqXmJiRi46LioaRk4yYkpaTk5KOjY+OjpGQjY2MjIyC9HuKgX2Dg4eE75CEioqMj4uSkIxyuLG50N/teu3yiZKOj5GVkJGOjo2NkpKWlpKOkpORkIyKjpGQkY6MjpGNlY6SkpCRkI6MiouJjJCPj4yUMpCSjZKRj4uPkY+RkY6PkJCQkpCNh4uMh4uMioqNjo2NjoyJj46PkY2NjI+OjpKPjo+RgHt+f3x9fn17e3x/f3t9enp8foF+fH56fX59fnp9fX9+fn98e316ent8fn6Af31/fHx5e398e3uBfX96en18fn58en18fnt5gH97enp9fn55fXl7gHx7enh7dnh6eHR4eHV2d3VyeHt4dnl4end5dnl7eHl4dnlzenx7eX14eXp9gHh7eXx5eHt9eXt4e319eXl5fnx6eHp4e3p5dX96dXlzdXh3a8a2uNJvwMpxd3J4cnV2dHp7dXh1eXh1f3p5dnl8e212eXt3eHp1dHZ4dXd2dnh4dXZ0dXd3dnl1dnZ2eXd6eXl5enp8ent7fHt7e3p6fX1+fX1/f3x+fH57fH5/gHx8e319gHx/fn1+fn98eH97fXt6fHt6gH1/fXx+goJ/gH1+fn17fXx8f31+gH1/gn57fX5/fX6Bf4B9fn5+fYB8fX9+fX59fHx+f4B+fn1+fH5/fXx+fnt+f357gHx5enx5e318fIB+fX18fn9+fX1+fn6Ae3uAgYB/fXx7gHl5NHmAeXx7e36AgH5/fYB+fn2Af31/fn6DgH+AgH59f3+AgH+Cf4B9goSBgoB/foCCgICBf4CEgTyAgoaCf4SBgIGCf4OAf4GBgICAgYSDg4KFgoSDgH+DgYOBhISDhYOEg4OFhISEhYOEgoOEhIWDgYODhIWHghuAgoODhIOCgYCDg4KCgoSFhIOAg4KFg4OEg4SFg3qFiYeFhoWHhoiKh4mKh4aIh4mHiYiJiIiIhoWGh4qIh4qHiIeJh4WFh4eHhoaJhoWIiIeHh4iIioqIiYuJiIiHiYiHh4mJiImIiYeIhomIiomEiomKioiKi4iLiYmJiomJi4mJi4uKioiGh4SKiYaIh4mIh4mJiIeHiYSHgIWGhYeGh4eJhoaGh4mHhoWEhYiIhoaHhoeDhYiFhoWGhISFg4SChISGg4KGhoWFh4WEhYmFhoaEhoWFhIaChIaHh4qFg4WHh4eFhYOChoWIhoSDhYSFh4SEhYWEgoaFhYSDg4GEhISCgoOEhIWChYaGhIWGhIWEhoaGgoODhYWGK4aGhYaEhIaFgIOFg4WFh4eHi4uKi4iIioiChIWHh4WGiYmIiomIkYmKi4qEiICHiIaFh4iKiYmMiouIiYaFhIaGiIeGhoaDfn1+fHt7fn94gYOHg4J/e3d8e4CCgn5+foF/d99vfHZvdnl7edN1cXl3dnt4eXhrUomHlrTJ02/Z3nh/enx9g39+fHt7enx+gIB4e4CCf357eHqAfX96e3x/fYF8gYF+gH+Bf3x7eTh8gH99e4J/f3yAgH58foF8gH9/gYB9fYGAfXV7fnl9fHp7e358fX17d318fYJ/f31+enp+fX5+gP9/pX+EfgN/fn7/f/9//3//f/9/138Bfoh/AX6Lf4Z+A39+fuV/AgIEAICbmZuUlpiYl5iemZaelpeSnpeYmJybmpubnJuanZyZlpeZmpiYlpuZnZeMnZ6cmp2VnJiXnJmSlpeZm5OZnJaTkZqVn5qalZeWmJucm5OVkYqWlpaYkpSWkpSNjpWWn5GOjJCPi4aIjZCSl5eRkJCSlJeTkJOVk5OUlpWUlpKUlYCTmJWUkZSWlJKSlJCSkpOakZOPkpCTkJCPko2QjZORjpSNjo2LhYOU6tvl4dLVjIiOjI2OjoqPjY2HjZCLkY6Pj5GSjZKSj42MjYuKio6QjY2OjY+Mi4yOjIuLi42OkI2NkZCNj4+QkI+GkZGRlZCSko+RkZCSkpKUkY+RkpKTkTmUk5GSkY+PkpCQkpCSkpSRkJGRkpWTlJCRkZOSlZWYiI2VlJGVlZGVlZeSk5aSl5iZlpOWlpWYlZKElDqVk5aXl5SXlZiXl5KWlJaVmJWUkpSZl5mal5eYl5SamJqZk5aUlZSXl5aVlpWNlpaTkpOVlZOXmZeThJSAkpWYl5WUk5WVlZSUl5OTjpGVlJSUlpSSlZeWlZKUl5iXlpSamJiZmZaYmZaWl5eZlpqZmZiWmJqZmJyZnJmZnJqdmpWdnZqYmpqZmZmYmpyam5ebmpuZmZubmpybnJucm5udm5ycm5qbmpyanZqbnZqbmpmYmJeamZeYmJmYmZlKm5mamZuZmJiYlpibmZuXmJucmZqcnZubnZycnJ+enp2foqCjoZ6fnqKhoqOipKWko6Gjo6Oho6KipKWio6OhoaOioqKlpaGho6GEoiilpKOio6Ogo6KioZ+foaKjoKKhoaGjo6OkoqOioqGfn6OioaCgo6OhhJ8EoKGfoIShTaOiop+coqCfn6CgpKOko6Kgn6OioaGioKGin52goKaioKCioqGioJ+hoaCioaOioaOin6Kio6Snp6emoKGhn6CfoaCenp+goaOioqKjhKBln6GgoJ+hoZ+dnJ2enqGgn52dnqCen56en5+hoKClpKGhnp+eoaOio6Gio6OjpKKioaCgop+hnqKjoqSkoqGioqeioqahpaKjo6Wkn56go6CeoaGfn56moaGho6Olo6GenqCktqmEnh+ioqSioqOjoKOjoKCipKGioqWin5+hnaOioaChnqCjhKCAn6Senp2ko5mhm5OTlZeYlJaem5uZk5eWnZqXlZmZk5KPjIaDhPPQ/IyRj5CHg5WWkpWSmZiwovzi2eTg5PXv74CcnZ6cnqGhnpmXmJiRgPKBmJuem5+ampiZn5mdnZiWmp2ZmpqZmJmZm5iZlZWRk5KWmJWYk5mZmpaal5SXk5gmk5aXmZeVmZmQlpOVlpOWk5WXlZKVl5aVlpSdmpmbmJienqCXl5yAkYyOiIuNjYyLk42Lj4qNiJGJiouPko+NkI+Qj5GQjYqMj46QjYySjZGMhZGUko+TjJGQjJCOjI2OjpCLjpCMjYmPipORj4uKjpKQkZCKjIiDjYyKjYaKi4qIg4SJipGGhIOFh4J/gIOGh4yNh4OHiYmKiYiIioiJiY2Mio2IiotliomJiIeMjIqHiImFiYmLkIeLhIeHiIiIiYyGiIWIiIaKh4eEh4F6iNjM2tbHy4F/h4iIiYaChoaHg4eKg4qFhoeIhoWKioaGhISDgoKFh4aGiIaHhYSEhoWEhYOFhomEhImIhIaEh1B+iImJjIiKiYeKiYeKiomMiomJiYuNi42LiouKh4iJiYiLiYqMjYqJioiLjoyNi4qKi4uOjI9+ho2LioyOi42MjomLjoqNj4+Li42Oi4+KiYWMHYqNjI+Li4qNjo6JjYqNi42Ni4qMj4yNjoyMjY2KhI5FiIyLjIqMjI2MjIyEjIyMiY2NjIqNjouIiouKioqMjY2Li4qLjIuMjI6KjIWKkIyKio6KiYyQjY2Ji42NjIyJkY6Nj4+OhI1NjpCQjZKQkJGOjo+QjpCNkI6PkI+SkIqSko6OkJCQjo+PkZKSkJCTkZKTlJSRkJKRkpKTk5KUkpKSk5GSj5CPkpCRk5CQkJGNjY6QkI+FkR2Qj5CSkJKPjY6Pj5GUkpKQkpKTkJGSkpKTlJOTk4SURJWXlJaVk5eVmJaXmZiampiZmJmYmZaYmJiZl5WYmJeXmZiZmJmZl5eYl5aXl5mamZiYmZmWmJiamZeYmJubmJmYlpaZhJgWmpmXmJiXmJmampebmpmZmpuZmpuYmYSbEp2cm5mUmpeXmJeZmZaamZiXlYSXPZiXl5iYlJaWmpaVmJeXlpiWlZaXlpmWmJaVl5iWmZaVmJqampmVlpWVl5SWlZaXmJaXmJqYl5WUlZeXlJeFlWqSk5STlJWXlpaUlJWWk5SVlpaYmJeVnJmYlpOVk5aWlpeXlpSXlpiWlpaYl5eVlpSXmJiZmZeVl5aalZWZlZqZmZiZmJaVlpmYlpaWlZWQlZaYmJmampqZlpaYnKObk5aWlZiXmJeXmpiXhZmAmpeZm5uZlpeamJyam5qamJmbmpmZmJiblpaUmZiQl5KOjo+PjIqLlJOTkYqNjZOQjYySkIyKiIaBfn/qxu+Hi4qIgHqLjIiKh4yJl4PMwsPT1Njp5OZ9kZKTkJGVk5CNi4qNhHffdIeOk4+SkJGLjZKPkpKQj5GUj5CTkI6Pj5I8jZCNjYmKi4+NjY+KkJCSjpKNiY+LkIyPkZGOjJCQh4yJi42LjIiLjYyIjIyJi4yJko+OkJCNkJGVjY2RgIB9f3Z8e318fIN7fH55e3Z+eXh6gYN+e3t8fHl+fnt8e357fXt8gHt+eHZ/gH97gnt+fXp/fXt7fX19d359e3x5fnd/fn55eH6Bf31/eXx9d359e3t2enh4dHRzeXeAdXNzdXlzdXN2dXV6fHd0d3l6eHl3e3x2dnp8e318dXh2ZHd6enZ4fH59eHd5eHp5fIF5e3V0eXZ3enl9d3p4enp3e3h7dnx2bHCysL+yrLFwcHp7fXx6dXl5eXh6fXN8d3l6enh2ent3eHZ1dnR0dnZ3eHh1d3R1d3h3d3l4eXh6d3d7eXiFeVFze3x6e3p+fHp8fXt+fX6Bfn18fIB+foB+fn9+enx8fXt+e31+gH59fXt9gX58fHx9fn+AfX9seH57fX2Ae3x8fXt8f36Ag4B8foGAe3x8e36EfYB+gX9/fH1+f4F+fH17fn18fXx9fH99fn59fH9/fX18fn94fXt8e3x+f398fXl9f399fH1+e36Afnp6fHx7e31+fXt8fH19e35/f3x9e3+Cfnt9f358fYJ/f319gH5+fnuBf39+gYF/fn59foCCf4OBgoN/f4CAgIGCgYGBg4KDgoB8g4SBgoOBgX1/foCCgoGBhIOGhYaGhIGCgYOFhYaGhoSFhISDhIGCgYSAgoSBg4OCf4CAhISAg4OBg4ODgYKDgYSDf4CBgYKGgoODhIKEgIOEg4OFhIOEhISDhYOEhYSIh4SFhIaFh4iHiImHiIiJh4iGiIiIioWFiIaGh4qIiSCJiImGhomHhoaEh4mHiIeJiYaFh4mKiIqLi4yJioqHhoWICoqJhoaHiYmKiYqGiQmHiIqKiIqMi4uEjICJhYeGhYeIi4uJiomHhoeEhIWHiIWFiIiFhYWHhYWHiYiEhYSDhIiGhYSGhoeGhoOGhYSHhYaJhYOEgYOGg4SDhIWFhoeGiIeGhYOFhYaEhYSFhYaFgoODhIODh4WGhIWFh4OEhYWGh4mGhIeGhYSEhICHhoODhYOCg4SFg4OEg4CDhoSFhIaGhYaHhoKFg4aCg4WEhoWFhYeGhIODhoSEhYWFg32ChIeHiIqJiYmIh4eKhoWEhoWGiIqIhoaGiIeKiYiHiYmFh4mIiIaFioeKiIiJioiHiYiJh4SHiISFgoOAfoSDfoB+f3t8eoOFg4N9e3Z/gX59gYF+fXt5dXR20YC01Xp9fHd0bnd5d3R1dXN2XJCUpb7DyNTS1nGAgYB9fYB+fXt7d3ZsZr5hcn2Af358fnh+hH2Af319f4F+foKAf359gYCBfHt4enuAfX2Ae39/gH2Cfnp9fX59fX+Af3yEgHl9e3t+enp5enx6e3x7enx8e4B9fn5/fH9+gHx9fv9/qX+Gfv9//3//f/9//3/Wf4N+j3+Jfo9/AX7WfwICBACAlZuTlpWWjZCSlZKUmZmYmJqalpeYlZuenJWZmZialZebmpeZnZyXm6Ghm52Wl5qXlpebmJGWm5uamJmYmZyZlZibmZuampibm5iXkpeUj5mWmpeWl5OUlZWRlJSSlpGUlJCUjomKho2SkpiSlZKNkZuTk5GUlZSRlZORlZOUlZGAkpOQkpGSlJiYlZOUkY2TkpSPiY+Qjo2Ol5OUkJOSl5SPjI+KkI+OhOTh/oD7+Yjrh4qLi4yOko6PjYqMj5CPj4yOko2LjI6Njo2Mj5CQjYyPiY2LjYyLjIqJi4yNiouTkJSOj46NkZCLkY+Pj5GRkI+NkJOSlJCRkpKTk5OSk5GAkpGMlJGSk5OTipSVjpKQkpSQkZOUkZSTkZGTkZOTkYqMlJWTkpCRkpWTk5KWkY+TkZKSkpSWl5GWjpCSk5SUlpWRkZWZlJWSlZSUl5SXlZSVlJeXmZeTlJGSlZyQk5eWlpaSlpaSk5aSlJOSlZKRkpOTk5aWlZSWk5KUk5WXlJaAk5SVl5aVlJeTlJeUkpWUk5WWkpOVl5SWlZaXlpiZmJiYl5iXmZiWmZuZmJSXlpiYlo+Wl5mZmZuZm5uWm5qZmpuZmpiamJyam5ualpqZnJubmpqam5eWmpuamZubnJubm5qampucnZyZmpuanJubmpuYmZqWmJmbmJaamZiZm5wYmZmYmpmbmpqbmZmanJuanZqenp+dnZ2bhZ0sn5+ioZ6enZydn5+goaGhoqKipKGhoqOjo6KioqCjpaSioaCio5+ioqOjoqGEooCgoaOhn6GgoaGho6KjoKGhoqGhoKKhoqCgoqGhoqWio6Chn5+goaKgn6GfoKCfnqGioaCfnpygoKGhoaCgo6Kgn6GhnJukoqCgn5+goZ+ho6GioqOhoaGjn6Ghn6CgoZ6eoaCfoaSipaGjoaCfn6Kfn5+inp6hoKCioqOln6WfooCfn5+dnaCfoKCgnp+hoKCbnZ6doaGcnZ2dn5ygoqCfoaCipaOioZ+hoaOmoaKgoZyhoZ+foaKfo52cnp+dn6GgpKWkpKKcoKOhn6GknJ+hop6hnJiVjOWuoJ+hpKSgo6CdmZycnqOjoaSgnpyeop+eo6OfoaGjoqCelKGioJ+gn4Cjn6CdnaCenpmhop6enZeSlpidoZ6fnaOemZGSm5mcmJmWmJmSmZyZoqSgjpKSl5CJhP3s7oeTipaRl4uSjY2YnZWwlPHr6urm4ubm4pGhoJ6en52dop6ZjYKBg4uSnJ2cnpuamZqjnKCdnpudnpWZl5ialJiWlpeYmJaXkZaVlTOampaTlpqVlpSSm5qXl5SVlpKWlZaamJeXmZmYk5WalZaWmJeZlZadm5mampebmJOWmJqAjJKKi4uNhIiKi4qJjIyNj4+Oio2Nio+SkImNjIyOi4yQkIuPjpGLj5aWkZKLjY2Oi4ySkYeLkpCQjY2NjpGQi4uQjo+PkY2QjIuNh42LhY+Kjo6Li4eIioyHiIiHi4eKioiKhYKDf4WIh4yFiImGh4+IiYeKi4qHjImIi4qLiYeAiYaGiYeIiI6LiomIiISKiYqFgoiIh4OEjIeJiYuIjYuHhoaBiYmHfd3b9nvz8HzkhIKDhYaHiIaHhYWGhoqIhoSGiYWDhYSFhISFhYaIhIOIg4iChYWChISEh4aGgoOLh4yGh4eFiIiDiYeGiIqJh4aFiIuKi4iJiomLi4uJjImAiYmEi4mJjYyLg4yOiIyKi4yLjY2MiYyLioqLi4uMjIGGjY2LiYmLi4yLi4qOi4mLiIeJiYqMjYiNh4eKi4uLioqIiYuOiouIi4uJjouNjYyJio2MjYyJioiIjJKHi42Mi4yIi4yIiY6Li4iJjYuKi4yLi4yLiouMiYmMiouOioyAiYyNjYyMiY6Mi46LiYyLjYyMiYqPj4yMjY6OjI2OjY6NjY6Nj42MjpGQj4uPjo2OjIWMj5GQjY+OkY6KkY6Qj5CPkZCQjpCPkZGRj5KSk5GRkJORk4+Pk5OSkZKTk5KTkpGRkpKRkZKQkZGRkpGQj5KPkJGNj5GSkI+Rj4+PkpGAkZGQko+SkpOTkJGQk5KSlJKVk5WUk5WUlZSUlJaWk5WXlpOTk5aWlZeWl5iXl5iYl5eWmZeYl5eXlJeampmYmJiZlpeWl5iZl5eXlpeXl5iZlpqYl5iYmZiZmZmYmZiXlpeXmpaXmZiWlpuZm5eamZeYmZqYl5qYmZqZmZqcm5oTlZWVmZiZmJiWlZaXlpeXmJWSmYWXPJaWlZaXlZaXmJeXmJmVlpeVlpSWlpKXlpaXl5aZlpiUlZOTmJWXlZeUlJaVlZaYl5mUmZaYlpWWlZWWk4WWgJmXmJOWmJaWlZCTlZSVkpWYlpWVlZaZmZeWmZmVlpmYmZWVk5aXlpWVl5SWlJKTlZOVlpSWmJiXl5GVmZeVlZeSk5iXlZmTkY+Bz5iVlZiZmZWZlpWRlJWVmJmXmJWVlJaYmJWamJeZmZiYl5OMmJmZmJiam5iYlpaal5iRmZiTgJSTjYyNkZSYlZWUmZaQiY2QkJSRkI+RkoqRkpCXmZKHi4yRjIV/9OfmgomBjYWNgoiBgoqMhJF4ys/U3NvY3N7dipSQkZGSkJCUko+De3h7gIeQko+Sj46OkJaQk5CSko+SjZCNj5KMjo6Njo+PjY6Kj4+NkJGOiY6Ri42NipGPJ42NiY2OiYyNjI2OjIyMj42Ii5GMi4qNjI+JjJOPjpCQjI6LiY6PkXt4fnl7enx2eXx7dnl6e3x9fn17fXt7f3x8d3t8e397fH59e3x7fXl8gIOAgHp8fYB5eXx/d3x/foB6eXp8f315dX19eX+Aenx4enx6e3t3fXt7fHp6eXd4eXh3enV7eHl4d3h2d3ZydnZ1enR4enh2f3d4d3l8eXd6dnqEezV4eXh3eXV3eX98fXp7eHV6eXh6dX56eXN2fnx8eXx5fXx5eXd0fHx1bszO4XDg3GrUeHR1d4R5D3p5eXh5e3t5dXd5dXR3dYR2IXN0eHJyd3R4c3R3dXZ5eXt6eXV1fXp/e3p5eHt6d316eoR7XXp5en19fnt9fnx/gH59f3x9fHx/fn6Afn14fX98fX9+fX+Af398f318f35/f318cnd/f317e3x7fnt9en99ent5d3x9fXx8en97e3x/fH9+fHx7fH97enp8fXx+e4R8TXt8fX18enx7enyAenp9fXx8eXt7eXx+fX56en99fH19fHx8e3x8fXt8f3x8fnuAfH5/f35+eX57fIB+e35/f32AfH6BgX9/gYGAf3+AhH82gH+BgH9+gIGCfYB/f4B8eICBgIGAgoCDgH6EgoOAf4KDg4CAgX+BgoGAgYOFg4OEhYSFg4OGhYQDhYOEhoOAgoSBgoGAg4SCgoSBg4WAgoKCgICCgoGChYSDgYGCgYKDhISDg4KCg4SFhIaFhoeFhoWDg4OEg4WChIaEhYWHhoeEh4aHh4qJh4eIiIeIiIiGhoeEhYeIiYiIh4iGh4aIh4eFh4aFhYWHiYqJjIyKi4qJiouKiYqJh4iIiImLiIaEiR+MiImHiYiFh4mIh4iKiYqJiYuMjYqIhIWEiYiIiYqHhIYTh4iFg4KIhYWGhoeFhYOEiIeFh4SGBYiFhoWChIQUg4WDgYWEgYOChoOBf4KGgoaEh4OEhICHh4aGg4aFiIaEhoWFh4WGg4WGg4eIiISGhoSGhH+EhYOGgoKGhYWFhISHhYOEhYSDgoWDhIOAgoODg4KEg4WCgYOBhIOGhYKCg4SCg4CDhYWEg4OCgYaFg4iDg4Jqq3SBhIeHh4SJh4eFhYWEhYeGiISHhoeJhYSIiIaIiYaFhICEgIWHhoWFiYqGiIWFiYaGf4SGgYSDfHt+fYKDgYOEhYR/fH59foOBgIKCg3x/gH+FhH56fX5/fHl05NrNcnlxe3V6c3ZwcHZ1cGtUma27xdDIzNLLeYB8f3x8fX2Afn13bGxub3h+gX1/fXt+foKAf3t/gHyBfH18fIB7fX9+fTt8f3x9e4CBfIJ+fHmAgHt+fXmBgH17enyAent8e31+ent6fHp5eH57fHx7e355fIB8e39+fXt6eXx8f/9/qX8Ifn5+f35+f37/f/9//3//f/d/AX7bf4N+j3+JfuZ/AgIEAICZlJmWlJmTkpKTjZOZmpGTmJmVmpuZmpqYmJSYm5eXkpWXm5uemZqYk5CWmZaXmJWZmZWZmpadmpqWmJaXlZ2YmZScnZSXnZmclJCYkJaYlJSRlJWUlpaYkpWUkZOSjo6VmJGNkI2Kj5CVl5SSl5ailZeUkZGRkJSSlY2RmJWVjYCSl5GUlY+Sj5KTlpKRkZGWioyLj46OlY6TkY+Sj5GUlJKNipCNjIqMhY+GjIuF5/eHiYuQjo6NkY6Qj5KHj4yMkJKTj46RjY2SjY6Mjo+MiYqNio6Jio+Mjo2MjIyOjo6QlZGNjY6KkZCQj46PkJSQj5CSkJOSlpORkZKSk5GSkUCVlZKPkJOSkZGSlZCSlJKUk5CQkY+QkZOUk5KTkpSRkYqPk5CTlZGRkpOUkZKWkpKVkpOTl5WYkJCRkJSRlZWThJQpl5KUl5eUlJaXl5eUmY2Wk5qXlZeWlpaVk5OVlJeWlpKTkpORkZSUlJWEklmTlJeVlpWVlZOUk5OXlpSVlJWTlZWXlJSWk5aSkZWWlJSVlJSWl5OWlpeZmpmamJeXlpealpeYl5eal5iVlZaXmZmZlZqXmZiYmpybnJeYnJiamZybmJqYm4SZgJqZm5uanJqamZmcmpycm5qamZuanJmamZqbnJmWmJqbmJqbmJiamZmYl5mZmpeWmZeZm5qamZuamZmXl5mbmpuamZmcnKCcnJqcnJuYmpyenZ+goZ+hoJ+foZ6hoKKioaKho6CioqOkoKKjoaCfoaGgo6OioqChoqCfoKKkop6ggKCioqKhn6KgoKGdoaGgoKGgoqChoqOjoKGgo6Ghop6gmqCho6Oen6ChoKGioqKjoaCdn52hnJ6goaOhn6CfoZ6goKOioaOgoaGfoaCjoqGkoaGdnZ6goqKjoqGhpKKhoZ6gnqOgoqCfoZ+foZ+gnp+goaGfoZ+hnqGgn6CgoZ+egKCgoZ2enp2dn6KeoqOinqKdnp2en56en56fnZ6cmaCenqCjop+loqGfn6KhoqChoaOioKGjpKCgnp+foqKgo6Sio6aooqKjpaSno6Gen6CenpqUjIXToZyenqKfo6Ohnp6enZ6fn6Ginp2bnp6gnaWhoaCin6CgnqKinqCcm5+egKKeoJ+en5+Znqaal42UkpKWnZ6dm5KVkpiSk5ednpqclZiZl6GboZ+frJSTkJWRk46J+IGIgIiPs/rn7/L684efxPz4/fr06+Pc7pOhnZ+cmJ+hn6CanZiVlZiUm5eZmpyamJyalpeXkZaTlJWUk5KVlpebm5mWmpyZm5qZk5iZMpuYmJmVlZaWlJOVlZOWmpOUk5aXlpWVkpSWmJecnJiYm5aWm5ucmZmdlZeXmJaalZeagJCLj4uMjomKioeDio2MhomNjYiNjY2OjY2Li42QjJCKiY2QkI+NkY2JiIuOi42Ki4+Pio6PjJGNj4yNi46KkoyPiI6Qi4qRkJCIiI+GjI2LjYeKjImMi4yIjIqGiIeGhIqOiIWGhYGGh4uLioeMi5WJjYmHiIeGi4iIgoeNi4qCd4eOhoqNiIqHiImLh4eGhoqCg4WFhoWLhYqIh4uIiY2NiIWDh4WGhIOBh32BgX7Y7YKEhIeEhYaIhYmHioKGg4aIiYuJh4eFhYmHhYKEh4SChISBhYODhYSHhoWEhYaHh4eLiIaGhoKKiIiJh4iKi4iIiIqJiomMhIlNiomIiYeMjYuIiouKioiJjYmLjYmLi4uKi4uMioqMjIyNi4uLjIaJioiMjIqLi4yKi42NiImKiYuKj4uPh4iKiIyHjI6LioqLipCLi4qEjCOOjY2KjISOio+Oi46NjIyKiImLi4yLjIqKiYqIiIuLjYyIiYSLDY6Mi4yMjIuLi4mNjoyFizKMjYuLjIuPi4qMjouMjouMjo6JjY2Mj4+Nj46Ojo2NkIyMjo+OkI2PjI6Mi5CPkIqOi4WQTI6QjY6SkJGQkJCMkI6Pj5GQj46QkZCQkpCRjo+SkZGTlJKRkJKRkpCSkZORkpCOkI+Qjo6SkJGQj5GPj5GRkY+PkZCRkZGTkZGRkpOFkiSUkpKTkpOVkpSSlZKUk5GRlJSUlZeVl5aXlpeVmJeYl5eYlpiEl4CYlpmYl5WUl5eVl5eWmZeXl5WVlZeXl5aXl5eWmJmXmJaVl5WYmJeWmJeWlpeZmZiXmJiZlpaXl5WSl5qamJaWmJqZmZqbm5mZlpaXlJmWmJmYmpiVl5eZl5iXmpiXmJiYlpaZlpeYl5iVmZSWlpqalpiXlZeYlZWTlZSUmJWXlhyWl5SVl5WXk5SVlZaXl5WYlJWUl5aWl5aUlZeXhZSAl5eVmZiYlZiSlZSVlpSUlpSXlZaSkJWUlJSXl5WblpaUlJiXlpSUlpiWl5iYmZWWkpSWmJmVlpaWl5idmJeZmpaal5OTlpaVlpOOg3bBlpOWlZiWmZmYlZaWlpWVlpeYlJOUlpaXlJqYmJeZl5eXlZiYl5iWk5mYmpSVlpaXlpF+lpqRjoSLjY2PlZWWkouLipGMjJCTlZSRjZCTkJWRlJKSmIqLiYyLjomD8H2DeoKGnOHd4uXt5n6Noc/W4uTl39fV4IiUkZGNjpGSlJOOkIqJi42Jj4mMjpCPjZCPjY2OhoyKiYqLiouNi42QkI6MkZORj4+Pi4+QkY+Oj4uLhI0oj4yMjZCKiIqLjImMjIiKi42Oko+Lio2Ljo+OkI2NkYmNjoyMkIqNkYB9eH55en94e315dX16end4e3x5fXp7f3x+ent9fX5/end9f317fH56d3p7e3x9eXh9enp9e3p+fH57enl9eoB9e3Z9fnd6fnx+eXh9dXp9fIB6eHp3eXh5dnt4dnZ3eXZ3end1dnZzdHV6e3l4eHqAeX13d3p2d3p6d3Z6eXZ6cnd3gHh6e3l9enh7fHp2dXd8dXV2d3l4fnd5eHd6eHl9fHh5d3Z2dnRxcnpwb3Nwv9V1dnZ3dHZ3enl7ent0d3N3eHp4enl4dXV6eHVzdHd2c3V0cnZ2dnh3eXh5eXl6e3l5fXp5eHl1fHx7fXt7e3x7e3t9fHx8fYV8gHt8fXp/foB8fX58fH18gHx9gH1/f4F7fHx+fnx/fn5+fHx9fnp8e3t9fX1+fX18e35+enp9e3t9fn2AeHh7ent5f4J9fXx5en98enp+fn1+f359fHx4fnt/fX1/f358e3p6fXx+fHx6e3h7eXt8en58enp+fHx8f317fH1+fXx9gHqAgX9+fn19fn9+fX1+fX98fH+BfX+AfX5+fXx9gn2Bfn6BgICAf32AfX5/f4CBfYB+gH19gICAfH5/goOBgoKAgYCAg4GBgIGCfYKAgX6CgoGAgYKAgYOAgIGBg4KCg4SEhYKDgoOBg4KDg4OCgIOCgoGAgn+AgoGEgoKCg4OBKYGDgYGEgoWDg4WDhIOFhISChYSFh4eGh4aHhoiFhIaCgoSGhIWFg4SGhIc2hoSHg4WHhYiHh4iHiIaHiIeFgoSFg4SFh4eFiIiGg4OFiIWFhIaHiYmLi4mHh4iGiImIiYqJhIiAiomIiIiKh4WGiIeCh4iJiYiHiYyMi4qMjImIh4OEhIeGh4eJi4mFiIiKh4eEhYaGh4iHhYeGhYiIhYaGh4WIh4mIhYiGhIKDgoGDf4KAhYOFgoSFg4SDg4WChYWGhYWGhoaEhYSGh4aHhIeEhoWFhISGhoaHg4WGhYWGgYODhIWAh4SFg4aCg4OBgYGDhoeGg4iEhICAhYODgoOEhIKDg4SJhISCgYaGhYGBgoKDhIiIg4SFgoaGgYGDg4WFg4F0YayBgoaEhoSHh4iGh4eHhYWGh4mGhYWGh4mDh4eHhoWEhYaEhoaEhIODh4eIhoWFg4aGf4OEfnp1fX19f4OEhYJ1ent8gH5+gICBhIJ9goWAgn+Cf31+fX17fnyAfXrgb3dsdXR9w8jKy9TObnR1l6nDy9HQycvNdn99fn17fn9+gHp/end6e3d7d3t/gH96fXt7en52fXl6fXx8e3x7foB+fX2Ag318fX99fX+Af39/e3p+fHt5hH0kf3t5ent8enl5dnl6eXx+fHp7f3t7f35/e3yAeXx+fXqAeH5//3+vf4J+/3//f/9//3/3fwF+3H8BfoZ/hn6Df4l+538CAgQAgJKVlpKOlZeZmJSZlpaanJqhnpqdnpWYmJ6bnZiam52alZqbnpqalZWXjpKalpmdmJmWl5mYkZaZmZiTl5qVkJeXnZuZnJ2WkJiYopqSlZKVkpWWlpWZmpmWmpOUlJaPjpKNjoyOj4qLl5aRlJWUj4+QkpSTk5OPlY6TkZOXj5WVgJeTkpaWkZCQko+Pjo+RjZCLkY2QkJOTj5CMkJOSk5GRkZCOk4qNi46Qgd2F8oaGgoyMh4aOkIuMi4eSj4uOiZGRjJGOi42Njo2MjI2LjY2Mj5CNjoyMjoiOio6NjZGPk5GQjo+OjZCPjo6Sjo+LjY2RkpCTkJGUjpCRko+Vj5OSgJCSkI2SkpKRk5OSk5GVlZOTkZGPj5KSkZCRjZKUk5GNk5OUkJCUkpGQkZWRkpGWmJaVlJOUmZOUlJeUkpCTk5SUlpKTk5SWmJOUkpaVl5aWlpOVlpaYlpaTkpeXk5WUl5aWl5aWkpWWlZWWlJWTlo+TkpSVk5OTlpSWk5WVk5eRf5OUlpKUkZWSlZKVk5CSj5GVlJOVl5aXlpialpmWmZecmZWVl5iZmJeVl5WYl5OWmZqYm5qYmZqXlpmbnJiamJiWmJmZmZyZmZqbmZeXnJucm5uXm5mdmpqcm5ubmpqalpeYmpqbmJqZmZqWmZmanJiYmJuZl5eWl5eYmZqZlpmEmICXmpeZmJmYmpyam5mbnJucnpyanaCenJ2enpydn5yfoKCen6CloqCjoKCgoaGgoKGfnqKhpaSjoqGhoqKgo6Cgn6CgoKKhoaGioKCho6CfoKOgnaOjoaCjoJ+goqGjoaKkoqGioZ+en5+dop6cnZ+hoKKfn56ej5Shn6Cfn52goICdo6OfoKKen6GfoqGho6KgoJ6gn52enqCfn56jo6CjoaCfoqCgnaCjop+dmZyfoqCen5+en5+eoKKfn6Khnp6goZ6gn6CenJ+goaCeoJ+cm56iop+foKGdoJ+doJ+ioZ6hn5qdnqChn52em52eoKCgn6SipJ6foaGioaCioJ6alzKWm6Ggo6Gkop6lpqGjoqKjoqKhoKOio6GhoaKioaGhnp2eoKCboKGioaCfn6CgoJ6enoWgNqKfoZ6foaCfn6ChoJ6fn56gnp6gnJ2amZ2cm5qfpZualJGQkZOZmZaYn52ZlJeYnZyflpeWmYSYgIial6KakJaWlY+QjIfs6YiTlbeCiIqD/IOJmK/9gIKB+fL5jqCanpqZlZmamZuXl5abnp2dlpmYmZaXlpiXkpORjIqA/fr3hYqUmJyem5qbmpaYmJmVmJSVl5OUko6UlJGUlpiTjZOVlJSZk5aXlZOYlZaalpiWm5ybm5yZl5uXDJecmpqWmpWVm5eYl4CIjI2Jh42Ojo2Lio2MjI6Ok5CPkZGMjoyPj5CLkJCUkImSkZSRkIyKjoiKkIyOkI2QjoyPjYeMj5GOioyRjImOjZSSjJGRioONjJaQiYyIjIqNjYyKjY6Oi46IioqLhYOIiIWChYSDhIuLiIqLiYeGhoeLiomHhIuEhoeJjISLiYCNioeKi4iFhYeFh4KFh4SIhYiGiIiLi4WFhIqLioqJiYiGhouFiIWDiHfPeuGAfn2EhIKAg4eEhIOAh4WFh4KKjIKIhYWHhYaHhYGDgoSFhYaHh4OEhYWBhYKGhoWJiIuIh4WHh4WIh4eGiYeIhISFiomHiIeJioeIioqGjIWKi2CJi4mGiYqKiYqKioyIjIyMi4yNioiMiomKi4iKjIqKhouKi4iIi4qJiYqLiYuJipCNjIqMjYyJiYmLioyIioyNioyJiomJjIyJjYuNiouKi42Ki46MjoyMiYiMjIiJiI2FjICIioyNi4uKjIuOh4mIi4yKioqMjI2KjIuIjIqKioyKi4mNio2JjIuJi4eIi4yNi46Njo6OkIuPi5CMkY6LjI2NjY+Nio6MkI6Ji46QkI+Qjo2NjIyPjZKPko6OjI2PkI+PjY+PkY+PjpCPkZKRjpCNkY+RkZCPkJCRko2Pj5GPknGRkJGRko6RkJCRj5CPjo+PjoyOkZCRkZGPkY+OkpCOkpKUkJKQkZSRkI+RlJSTlZSRlJaTlZSTk5OVlpKVlJSRlJWYlpeZlpaXlpaYmJeWlJaXmZmZl5aXmJeUmZaWlpeWlJeVlZaWlZeXl5STlpiXk4SWgJiYlZWXl5uamJmYmJmYlpeYl5aZmJWWl5mZmJeXl5aIjZiWl5aYlZiZmJublpiZmJeYlpiXl5qamJeVmJeWl5OTlJaUmZiWmJeWlZeVlpOVlpSSkY+TlpiWlJWVlZaVlpaXlJWXl5SVlZeUl5aXlpOWlpaVlZeVk5KTl5iXlZWXZZaZlJSXlpiWlpiVkpWWl5aWk5SSkpOUlZaUl5WXlJaWl5iXlJeTkpCNjpKXlZmWmJeSmJmWmJeXmJeWl5aZlpiWlpiZmJiXlZSTk5aWk5eWmZmZl5iYmJeUlJOWlpeXlpeVlpOUhJWAl5iXlJWWl5eWlZeTk5SUmJaVkpKWj5COjIyLjZCPjpCVk5KPkZCTkZaQj4+SkJGRkYGPjZKNiY2OjomKhoHi24GMjJt1f4F673t/h5HPcHV36eTmgpCMkI6Mio6Qjo6MjIuQkZCQjY6MjIyOjI2QiIaGhIJ57+vtf4aNkZGSko89kY+OkI6QjJCOj5CLjYyFi4uIi46OioaMjYyLjo2PkI+LkIuMkIuOjI6RjI6Sj4yQi42QjZKOj4uMkY2PioB2fn56eH58e3x8enx6enp7gn5+fnx5f3x8fnt3foGDfXh/g4F+enp7fnl6fX5+fnt8enp+f3h5f398eHt/e3l8eX9/e3+BeXd7e4F+eXp0fHt7eXt6fHt7eX55enl4c3R3eXVzdnV1eHp6eHl7eHh4d3h5eXt4dHh3eXp4e3V+eYB+e3h4fHh1d3l3eHR3e3d5dnp5eXV8e3V3dnl7fXl7eXh4eX12eXVxd2GxaMd2cXB4eHl1c3l0dHh0eHd3dnR6e3N5dnV1c3Z3dXN0dnZ3eHh4eXd5eHh1eHR5eXd7e316eXd6end7eXt6fXl6dnh4fXx5eHl8fHl6e3t5fXh9fYB7fH17fn19fIB9e398f35/fn1/fH59fXt6fXx8f3t9eX18fXl7fHt8fX5+fXp5e319gn19f318e3l7enx6fXx9fH95enp6fX5+gH9+e318e399fYB+fnx8eXt+fXt8e4B+fHt8e3l7fX58fHx+fX96fHl+e3p8fH+Afnx+e3p6e4R8LH17fnt+fH59ent6fHx+fnt/foCBgIF9gH2AfYF/fn+Afn+Af3uAfoGAe3+AhIFFf39/fX9+foF/g3+BgIB/goF/foCBgYB9fYCChIOCf4F9f3+AgYB/gYKCg3+BgYOBhIOChIKDgYOBgYKBgoCBgIOCgIOEhIIggIGDg4SDgYKDhIOGgoOHhoGChIiEhYaFhYiJiIWHhYWHhHaAgoGEhISIhoeKh4WHiIaHiIWGh4aHhoSGhoaEh4SGh4WGhYaEhYaGhYaGhoSEiImIhoaHh4aIiIaGiYeJioiIhYeGiIeHhoeIi4qHhomJiIiHiIeHeX2GhYqJioaJh4eIiYaIiYmHh4eJhoaKiImGhIeGhIWChIOAh4eFhoOAgoWEg4CAg4KBg4CEhIaGhYaHgoWFhYSFhoWGhoWFhYaCiIaHhoKGg4WDg4WFgoODiIiFg4WFhYeHgoeDiIWEiISBhIOEg4OChIOBg4GDhIKFgYODgoKDhIWFhYF+fn1+goSEh4ODg3+ChIOEhYWGhIWFhYqGh4SHh4iAh4aEg4OBgIWFgoaFh4aHh4eIiYaEg4SFhoeGh4aHgoOEgoKEhoaIiIeGhYWEiIeHg4SEhYeGhIF8fHh8f39/fn+AgH1/g3+CgoGBg4KGgH6AgYGBgIFze3p2fX1/gH57fHl2yr5yfHh5Y3Bxa9Rvb3BrmVxmatHRznJ9e3x8eXhifX98fnx8fIB+fX53fXl6fH19fH93d3N2dWvY0tl0d35/goB/fH9+f4F8fnl9foB+enx+eHt9eXx+fnt7fXt9fIB8gH98eXx6fX55fHuAg3l8f39+fXp7fXuAfH97d319fnj/f6t/A35/fv9//3//f/9//3/Zf4J+iH8BfoR/B35/f39+fn6hf4N+xX8CAgQAgJeWm6SMiYuKlZaXk5mXm5eZmpuamJeUmZuem56UmZ2hmZqXmJeSlZuelZmVl5eWlJ6cnZqem52dm5ublpiblpGcl52alpWUg5WTipKTl5WPk5KWkpWQl5eMkJaZkpCSkZCQko6NkZSXkIyTl5GSkpGUlI+Sj5CLkpKRkpOTkJOQeZGQk5KVko+UkZOSjJCTkI6Li42Uj5OSkZOQk5SVko+RjJKUko+Oj4+Oi4GKh4qNiY2Pio6Ojo+QkI2Tj5GOkoyPjo+PkY6Lj4yLjomNjY6Qj46LjY6PjYuMjY6MjI2NkI+MjY2Nj46Mjo+Qjo6OkpOPk46UjpGSkZKEkYCWk5GQjZCSkJKTk5KSk5WTlpWTk5CQjJOQjpKRkZKSlZSVk5KTlJWSj5OTlJaVlJKTk5GTlpiRkI+Xl5aUk5SWk5GTlJSTlJiUlZaWk5WSk5SRl46Zk5SVlJeXl5SXmJKTlJaVlpWXmJaVkJKWl5OVk5STlZKSkpOWlZWVk5aZlVGTlJSWk5WVk5aVk5GTlZaTlpSXlpSTlpaVlZSWlZaVlZWXmJmXmJiXl5aXmJiYlpaUmJaUlpeXlpeal5iXmpmXmZmbm5qamJebmZmWm5qZmJuEmSuYmZycm5ydm5qZmZmYmpiZm5iYmJmamZiYmZmXmZeZmpeZlpqZmZqYmJiahJiAmZmampuamZaYlpmanJiWmpmdnKCenZWfnJ6hnp2en5ygn5+goaKiop+fn56hoaOjo5+ioqKho6Kiop+goaGfo6ChoqCgn6CgoZ+hoJ6goJ+fn6Chn6CeoKKhoaKgoKKhpKKloaGin6GfoJ+foKCioaKioJ+fnZ+en6Ggnp+goKMFo52gnaKEoDyho6KeoaKgn6Chn6KhoqGhoJ6gnpyfoJ+foaKhoKKipKCfm5iZm5ueoZ+hoZ+cn5+en6GgnqKgnZ6hoJ2En4ChoqCgnp+fnJ2hnp6cnp+cnZ2enqCdn56enqGhoJ+hoaChoJySnKCkopygnKCin52gnqGjqJ6dm52alZGSj5Kbn6KgoKGhpKOhoKCgn6KioqGlpKenpqKhn52koZ+hn6KloJ+fpKKio5+foaChoaCeoJ6foKChpZ6goaOfoKChnICen6CfnJuYm5eXnJ6mn6Cek5GMk5eWnJmZn5mdnpudm5ubmZiXmJaXk5ORkZSblJSWm5SKh4SFiYSHh/ucm42JjIb++ZG6hPDr4tv5kYKFgv6ChI6YlpaXlpiWk5iRh4ODhoWBgvyBgYOB8vfv+/2FiImMkJaVmJmVm5eanpWXlzaYlJSTlJaSmpaTk5SXk5OSko+am5CVk5WTlpqWmJWbmJidmZiZlpeWlJiUl5eWl5yalpybm5iAjouOloOCg4OJioyJj4yQi4yMj46MjIqPkJSQkImQkpSNj4uMjYmNj5KKjoqMjIyHkJCSj5CPkpKQkJCLjpGMh5GMkY+NiId7iomEiYyNiYWKiYyHiIWKjYOGiY2HhoiHh4aIgoCEiYqFg4mLh4mJiIqJhoiFhoGFhoWHiYiFiYd4hoWHiYmHh4iHiYmChIyHhYCBg4yIi4mHiIWLjI2IhomGiYqIiYeGhYSDfoOBhYqEhomChIeFg4iJhomFioaIhYiFh4eKh4aFhoSHgoSFhIeHh4aHhoeFg4aGh4WFhoaJhoKFhYWIh4WHiIqGhoeKiYeJhYmFiYmJhYh2jYuKiYiJioqLjIqKi4uMjI2MiouKiYaNi4eIjY6Li46NjI2Ki4qLi4iLiYuOiouJi4uMjIuNh4uFj46NiYyNjYqIi4uLiYqMiYuMjIuNiomKh4mFkImKi4mNjI6Ki4+JiYqLio6Kio2LioiJjI2Ki4qKi42JiYSKLYuKi42Oi4uKi42JiYqKjIuKh4uNjYuNiY6MjYuNjY6PjYyKjYyLi42NjIyPjYSMgI6Qj46Oi4+Mi4yMjo2LjoyPjY+OjY6Mj5GQko+NkJCQi5CPkI+Pj5GQj4+NkJKPkpKPj5CRkI+Rj5CRkI+RjpCOkZCPj46PjpCRjpGPkZCRkY+Qj5CPj5CQkZGRkJGRk5CQjpGRko6OkJCTkZWUkYuUkpOVk5SUlJOUk5OSlpeWFJeTkpOUlZWZmJiVl5WWl5iYl5iVhJaAmJaWmJiWlZeXl5aXmJiYlZWWlpaYlpaTlZiXmZiVlZiWnJiYmZeXl5mYmZeWl5eZmZiYl5eXlpiWlpiYlpeWlZiZlJaUmZiXlpqVl5iTlpeXl5iXlJiVlZaWk5OVk5KVlJOVmJmXlpiXmZaVkpGSk5OWmJSVl5aVlZeUlpWUk5eAlZKSlZaUlJWTlZeYmJeUlZSUk5aVlpaUlJOVlJaVlZWWlZaWmJiXlZiXl5SWlYWSk5mXkZWQlZiWk5aVl5eYk5GSlJGMhoeFh4+SlJGTlJSXmZaWl5eUl5eXmZqanJual5aWlJiXlJeWmJqWlZWZmJmYlJaYlpWXlZKWlJaYl5eAmpSZl5eUl5aYlZiVl5WUlJKUkZKTk5WRk5WOi4WMkY6TkpGVlJaYkpSSkZGQj5CQjpGNjYmLjIyJjY6RjIOCf4CDfoGA7IyIgX+Cfu/tg5lu09fSzumGeXx77np8ho2JioyNj4uKjYV/fH1/enl67Hp7fHjg5+Xv8H+ChYWLjo1Aj5GLkY6QlI2Nj5CLjYyMjYmSjYuJi4yKiomLiJCPiY2JjIuNko6Qio+NjpOPjY+MjIuLjoyNioyNkJCNj5CSjoB8e3uBdnR2dnh4enZ9eHx6fHt9e3p6e318fnx9eH5+gn59e3t9enl8gH1/en18fHV+e3x+gHt+gnt7fXh+gXp3f3x+e3x4enV6d3h5eXt5dnx4e3h5dHh7dnR5fnh3dnV4dnd1cHV3enZ2d3t5eHd6fnt4eHR1cnd4dXl6e3t5d4B4end4enZ5enZ7eXV2e3Z2c3R3e3d6fHR1dXt7end5enR7eXl8enl4c3N0dHR4e3Z2d3R2e3d0eHl3d3N6dXZ1dnN2dXl3dHZ2dXl1dnh2d3l4enl4enp2e3h6eHd4eXx4dnp5eHt7d3t7fHp6eX17eXp4end9e3t5ent6eHx9fYB7en6Afn1/f31/e319f319fn57e39+e3x9fn18fX58f3t8e31/e3p6e4B9e3x8fH58e354fXmBfnx5en97fHt9e3x8fYB9e31+fX99eXp6eniBe3t9fH58gH18gHt8e317fXh7fHp5eHt9fXx7fHt9fnt8fHt8fH16fH1+fHx9fQJ+fIR9HXt6eX1+fHt9e35+fX5+fn1/f397fn5+fH9/fX2AhX4hgYF/f358gn19foCAf36BfICAgYB/gX5/goGBgICBgoJ9hIAxgX+Cgn5/gIKEgISDgYCCg39/goGBg4GBgn+CgIOBgIGAgIGChH+DgYGBgoKBg4KCgYaCgIWEgoWDgoCDhoOAgYSGhYOHhoOBiISChYWDgoOBhISDgoWDgYGAgYKEhIWHh4iDiIeHh4aFhIWEhoaFhoeFhYaHh4WGhoeHhoWGh4eGhIaGh4WFhIWFhIeHhoWGhYmGh4WEhYeJhYiIh4iHh4eJiIWGhYaIhoaIiYiIiIaJiISIhIYwhYuFh4mEhoiIhYiGgoWBgoGAgIGDgYCCg3+Bg4aDg4aFhoKBgoODhIKEhoWGhoWEhIaAh4KDhoWCgYWDhISChYODhYeHhISFhYGEhYWDg4OFhoWGhoSFh4WEhoeHh4aGhYWDhIN6gYGFhIGDf4WCgYGDgoSCgIKBgYSBe3Z1c3Z8f397fn6BhYeFhoaHhIiHhYiIhomHiISEhoWGhYGEhYaJhYSFiImGhIGDh4WFhIWDhoQthIaHhYmFhYSGg4mEhoSGhYaCgoOAhoODgn59fX6CfX14gISAg4F+g4ODhYGBhICAgYKCfoKAfnp9end2f36BfHV0cXR1cnVyz3FxcXB0cdzUb3JVs8HAvc5zaWxt12ttdX14eoB/f3x6eXNwb3ByaGhoympsbWvJycza23N1d3d8fX6Bf3l/f36BfX17fHp+fXt+eIB9fXl7fXp7eXt4fH59fXyAfHx/enx3fn1+f3sTen15fHp3fXt9ent4fH18f3+Bff9//3//f/9//3//f/9/jH8BfoZ/BX5+f39/hX6EfwF+lH8BfoR/hX7HfwICBACAnJaZl6ChlpKVl5mZlZuanpWYnJWUlp2amJibmJiZmZyYlp2Zl5OTlJaWlJGUlpeZnZuWnJucoZeXmZ2dmJWYmJmXlJaVk4+Yl5WXmJaamJWWmpaXl5SXlpKRlJmPjJCMkI2Qk5SRko+Oj4+WlpCUlZSTkpSUkYmOkI2SkJKPjZWAkJGPjo+RkZKQjo6Uk42Tj5KQkYyQkJGLkZeOkZOPkJCSjoWQkYyQkIeMj46QiYmRjYmHjoqLjpGOjY6PkI+QkI2HiI2OjYyMi4yMjJCPio+LiouMkIyOjIuKj4+NjY6RkpCMjJGNjI2RjoyNj4uOj4+SkJGQjpCTjpKTkJKRkZCAjY+RjZGSlY+UkZKVlJSRkJKRlZCRlJGRl5OTlJOXkJCRlJKVkZOTkZWTlZOQk5OUkpKTlIySlJSUl5WWk5OWlpSSl5WSkpOXkZSTlJWUk5OVl5WRkpeamZWTl5aWlZSTk5SWlZeXmpSXlJWUkpSSkZOUjJGSk5WRlJWWl5KVkpdElZeXlJGRlpmUkZWVkZiXkJiUlZWYkpKSlZSOl5aXmJmXmJqSmJeXmJeVmJWYlZeWlpWZl5iYl5eZmJiXmJiZnJmZlpaEmYKbhJo6mJybm5mem5ibmpiZmJqZmJmZmJSXmJ2cmZiam5mamZeampeamJaYmZuZlpuYmpqZmJibmZeamJeYm4SaPJucmZybm52fnJqbmpucnZyenpydnaKgnqCgoJ2foZ6eoKGgoaGdoKSjoaChoaOgn6ChoZ+goaGgoJ+looShgKCen56gnqGen56goaGdm5+hnqOfoZ6dpJ+hnaCgn52dnaGkoJ2fn5+enZ2fn5+hnp6eoJ2eoKCfoZ6fn5+gnqGgo5+fn56fn6KepKCgoZ+hn5qgnZyfnp6en6GdoaKgnaCgoqCio6CenJ6dn6KeoKCjpKKgpaCenZ2enZybn52dgJ2inqKin6Ggn6GenZ+fm5+coJ6dn56dnJygn6Wenp6foZudn56io6Ogn6Kgn6Gen6Chp6aiop2coaKblYqCkJ2enqOfpZ+hoqCfoaGhoqCgnpqVioeNi5SVoqGjn6CkoJ+hoKCfpKOen5+hnKGioZ6fn6GloaKgoaSgn6GgnZ2efZuamJuXl5OYn62mop6cmpaYm5ucmp+foKCgm5udnZ2ZmZqZm5aWmJaTlJOZho2SnpaI5Y6M+vLP4vj4nZ+NjoOG7fSfpdzbzeXt6+Dk497d4+yDnJSSk5KUk5uTjIn+/+nv8/aB/pCRlJiUj4uNiIqIlZOXlJSWl5mVlJKQhI8rjpSUlJaTlJKTlJWTk5aYlZKSl4+SlI2WmJmXl5GWmZSempiZm5eYl5udnIWYA5qdnYCQio6MlJSMh4iMjI2KkI6SjYuRi4iKko+MjI2Mjo+MkI2LkI2Ih4qJjIuKiYqKjo2Sj4uPjo+VjY2OkY+MjI+PjYyKjo2Jho2MioyMi4+NioqMi4yNioqJhomKjYSEiIOFhIWIiIWHhoOEhYyLhouLi4mHi4qFgoaGhImFiIeFiYCEhYWDhYeHiIaFhImIhIiFioaHhYeIhoOHjYWIi4eGh4qGfIeIhIaJgYeIh4iDg4mEgn+Gg4SGiYaGh4WIh4aIhoGChoaGhICBg4SEiIWBhoWEhIWIhoaFhYOGiYWFhoiKiYaFh4WFhYeFhYaJhIaGh4mHiIeEhoiEiYmGioiKiYCIiYmFioqMiIyKi4qKi4mKjIqMiIiNioiNjIuKio+KiouLiYuKjIyKjImNioeKjIuKiYuKhYiKjIyNjo6Li4yNjIqNi4iHio6KjImJjYuLiY6Ni4iJjo6Ni4qMjIyLiomIiYqKjIuOh4+KjIuJi4mIi42FiYmKi4mJi4yLjI2LjoCMjI+KiYeKjo2Ki42JjY2HjIqNio6KioiLiIONjY2OjYyPjoaOjoyOjYuPjI+KjY2OjI+NjY6Oj5CMjo6PkI+RkI+OjI6QkJKTj42QkZCQkpCRj5KRj4+QkJCPj4+OjpCPjI6Ok5KMj4+Qj5CQkJGQjZGNjY6Rk5CPko+PkI+Oj0KSkZCQj42NlJKQkpGRkJCTkpKUlZKSk5KRkZOTk5SUlZSWlJSUlpaVlJWVlZaWk5aWkpSYl5aWl5aXlZSVl5aUlJaElYCYlpaWmZiXlZaVlpWWk5OVl5aXlJOVlpKXlJaYlZiWmZSWl5eWlpWXl5WUlpeWl5aWmpmXlpiYl5iVl5iXlpWUlpaZmJaXlpeUlpeVlJKVlZeUl5eUlZOQlJKSlJOUkpaWk5aYlZSXlpmWmJiWlZKVlpiZlpaVlZeWlJiUk5OSlQyVk5GUkZOVl5OXl5OElwmTkpOUkpaTl5SEloCUkpWVmpSUlJWWkpSVk5aYlpWUl5eVl5SVlZabnJmYk5OVl42LgnuJk5GRlpKXlpeXlpWWl5eYl5WRi4d8foSEiYqXlJqXlpmXlpeWlpaZmJSWlJeRmJiXlpWWmpqVl5qXl5WWl5aVlpiTkpGSkZOPk5OamJaVlJORkpOTk5KVl4CWl5eSkpKTk46PkI+Sj4+PjYuJi4t/hoqSi4DdiIfw5MLT6+yNjYKDfH3e44mLvsa72NvWztTT0s7a4nyQioeJiYuKjoiCf/Lw3eTq63nwhoSIi4mHg4WBhYKMiIyJi42OkY2OiomIiImMiY6Mi5CLi4mMi46MjY+PjYiKjYaJjB2GjY+QjIqJjI+Ij42Nj4+LjoyPkI6NkI6Mi4+TkYB9dn16gH96d3l9eXt6fXl/ent/e3p6gX95enp5fHx5fHl4e3l5dXp2fXh6eXl4fHp9e3Z6enx/e317fXp3enx9fHt4e3x5eXx6eHh6en5/d3l4d3l7e3p6dnl4fHV1d3N0c3N3d3Z2eXV1dHt4dXp6enl4enhzd3l6d3x2eHV2eWd3enV1dnp7e3h3dnp4dHlze3d1dnh6dnZ4e3l3e3R2eH14b3Z5eHh7c3h6d3p2dnhzc3J0eHd3d3Z1d3Z1dHZ3dHNzdXZ3dXNzdXZ1eXhzeHh5dnd6enh4eXZ3fHl3d3p7fHp3e3l4hHlSenx3enp5e3p5eXh6end6eXh8eXt6enl9en99gH1+fXt+foB9fH6BfHx5fnx8f359fH2AfHx8fnl8fHt+enx7fXl6fH1+fnt7enl5ent8fX19fIR9gHuAfnl3fH96fHx6fHx8e4B9fnt7fn9+e31/fX59fXt6fHt6e3t7d356e3t7fHx7fX96fn99e3t6fX19fn57fn5+gXx9enp+fn16fnt9fnl9e359f3l6e319eH19foB9e399eoB+fH9/fYF+gXyAfX9/g31/f35+gH+BgIGAgYJ/gH6Afn+AgoODgX5/gYCAgn+BgIJ/f4GCg4OBgX+Af3+AfoGBhYN/goCBgIGAgYKCgIF/f4KBg4GAgYGDgYCAgYWAg4ODgICEhIOEhIGBgYaCg4aGhISFhIKAgYOEhYSEg4WFhoSEhoSCg4SEhIKAhIOChYiHhYaGhYaFg4SFhIODeIaEhYWEiIaGhYiGh4SGhIeEhYSDhoaHhYOBhYeFh4OFhYKHhIaEh4WEhIWGhoaEgoiIhYWEh4qJiIiIiYiJh4mHiYeGgoOFiYiDiIWGg4KDgYJ+gYGEgISEgYOBfoKBg4SBg4GDhYKEhYOFiIaIhYeGhISDhoeHiISGOYeFg4WEgoSFhoSDf4N/gYSGgoWCgYSGhIWBgYKCg4SChoKDg4WGhYSFgYiDgoSFhX+Cg4GDg4KAgYWDgISDhIWIh4OCgoKDe3p1b3d9fnx+fYKEhYeFhIWGh4eGgntxb2pucnR2coSCh4SFhYWEhIWGhIaGgoWDhYGHh4aGhIeIioOIioaJhoaDhYWDhYGEhIOBhH+EfoB/gH6BhICDhIOCgoeGg4WEgoKEg4KBfoCAg4CAf35+enl0cnh5gH56csl5etzKqbHP0XV0c3JvbsvEa2mhrabBu7ayt7y7usLLb3x7d3p4enx4dnJx2tjGzdPTa9Z5dXV4eXp1dnV3dXx5e3l+gHyAen98e3l7fHx7gHt8gH58eX98fX17foB+eX19d3p+eXp/f3t7enl9d315fH19eHl4e35+e35+BXt4e4F+/3//f/9//3//f/9//3+FfwN+f3+GfoZ/BH5+f3+Nfox/hn4Cf37OfwICBACAlpmal5aWmpucnJSal56bnpeZm5iYmaCamZeZmZaUnpqXnpmZmpeWmJeUl5mcmZabmp2YnpuZn52bl56dm5qWmZuYlJuUl5aTmJWUkZuUk5mUk5WUlpKQjpSOlZOQj5GMlJCRkpCSkpiTkZWUkJGXlJWWkZCTlZOSlo6Oj4yNlJCAkJGSj5OPlJCPlZSRlZGSk4+QjJGRjo6SkYySkI+OiY+NipCLk5GMj5COkpKMiYqQjo+Lh4iOjYmQiYuQkI6Mk5GJkY6Pi42MiYuNio6LjY6Ojo+NkY2OjZGPjIqNj5CQj4+PkY6OkIyLjomOj5GQko6QkJKTk5GOkZGPj5CSkY2AjYuTk5OPkpSWl5WVlJWTkJOQkpGPkJOSlZWRjpKRk5ORk5KSlpORlZWUl5SVlZWXkZCRlJKRlJWUjpKUkpeTkJKWkZOZkpWSko6Tk5KWkpGUlI+RlJSUl5OYlpaUj5GVkpmTk5STlJaYlZOVk5CWkZGVk4+TlZeWlJWRlpGQkZKFlUeWlZSVkJWYlpOUlpqXlpaXlpaWmJSVlpWYlJeWmJiZlpWWl5WYmZeVmJmWlZWZmJWXlpmZl5eYmZeZmZybmZuYmJmYl5WanISZBpuYmJqdnIaZL5uYm5qZmJibmJmal5aYmZmXmpmYlZaVmJmYmZiZmJeXl5mamJiYlJuYl5qXnJuYhJo5mZqbmpucnJuanJudnp6cm5udn6Cdn5+gn52eoJ6fnZ+doKGio56fn56gn52goJ+goKGhoKOho6GihJ+Ao6Cen5+en56goqKjoKCgoZ+eoZ+gop+foaCfn5+dop2gn6Khn6GgnZqeoqCen5+gnp6eoZ+doqGeop+cn5ydop6moJ+ho6Ggn56hn6Chnp6dnJ6bmqCgoJ2dnp+gn6CgoJ+hn6Gin5+empuhoJ+dnqKhn6Cen5+dn5+fmp+fn52AnJyenqCioZ2dnp+enJ6gnJ2hn56hnaGeoKCenp+lnqCdn52enaGfoZ2cnqKgoaChoJyfoZueq5+ioZ6YkpOJlZ2po56foqGgoJ+amZiZmJ2XmZeQmp6ZlIWUm56dnp6coKCgnqCloqGeoJ+foZygoqCfnZygoZ6gn5+foJ+goZ0mnZucl5uYkZqnm6CblZqco5yanJ+dmJmenJ+bmZmZnJiampiXlJaEmICZmIiFmqKVivaUkZGB4M/M6NLtpKmNgPTv/pWO8IL5vsnN1c/N0dHohpaWl5aUl5KWnJ+blpeWk5GUnJ6fnZmVkpKNiZObnpmYl5KTlZSXmpeXl4+PkZSOk5aVk5CRk5GSkpKTlJWVkJOYlpKXnJqUlJSZnpefmJ2emZudl5eemAqcoJiWmJeVm5qagImPj4yMjI+OkJCIjYmRkJGPj46NjY+Tj4uOkY6Mio+Oi4+Mjo+Mi46NioqNkY+Kj5GUjpCPjJORkI2QkI6QjI6PjImPjY2KiI6KiIiOiomOioiIiI6KhYSJhYuLiYiKhIuHhoeEh4mNhoeKiYaGj4uKioaGiIuIh4qGhYaGhomGgIeGh4eIg4mIgouKhouJiImFh4WHiYaFiYmFiYqHhYCJhoOHg4mJhYiKhomKhIOEiIeGhICBhomFiIKEh4iFhIuGgIiEhYSHg36DhYOGgoSGh4eHhYmGhoaIhoODh4iIiIeGh4mFhYeDhIaDhoaIiImGh4iHiIqHhYmJiIeHiYiFCIeFi4qKh4uLhI2AjIyKiY2JjIqKiIuLioyKhouKjYyLi4mKj42MjIyLjIqNiYqPiIiIjIqIi4yKhYmMiY6MioyNiYqMioyJiYWKioeLiYmLjIiJiYqLjIeNjIuJh4iLh42JiIqIiouNiYmLiYaMh4iMjIeMjYyOi4uIjYqHioqMjI2NjI2Ni4yKjI+AjomLjY6MjYuOjY6Mj4qLjIuOio6PkI+Ojo6LjI2OkZCNj5CMi4yRj4uOjI6Oj42Oj46Qj5CQjpCOj5CQjoyRkI6Pj5CRj4+QkpGPj5GPkI+Sj5KQkI6NkI2QkY6PkJCQjZCRjpCQj5GPj5CQkY+PkZCRk5GPj42Qjo6QjpGRkJQxkZKRkpGRkZKQkpOSk5OVlZSSkpKUlZaSlZWWlZSUl5SWk5WTlJOWlpSUlZWUlZSVl4SVOZaXmZeXlZaVlJSWl5WUlpSUlZWWl5eYlpiWl5aWmJOUl5aWmZeWmJiVmZaXl5qXmZebl5KWmJmYloSXTJWYmJaXmJaXlZWYlJWYk5mUlJSWlZWUlZaUlZaWk5OTkpKRlZWVk5SUlJWVl5STlJaVl5eUlZORkZWVlJOTlJWUlZOVlZOUlJSQlZWGlHOVlJWSk5OTlpOYmJSVlpKUlpOXl5iWlJSXnpSVkpSRlJOVk5aTkpSZlZaWl5eVlZeSlJ+TlZSTkI2NhIyRlpSSlJaXlpWUjo+Mi4eJh4uLiJCSjId+jZCSlJWVk5aWlZWWmpeWlJWVlZiUl5iYlpWVl5aViJaAk5WSk5GUlIyTlY2RkIyQlJqTkZOWkpCQlpSVkpGQkJORkpGQkI2Qj4+OjpCLgYCSlYqC7Y2Ki3jXxcDZxNeSlX10497qg3/YeOWvuMLJwb/Hyt+Ai4uMjIuMh4mNj4qLjIuKioqMkpKQi4iIiIWCjJKTj5CNiouMio6Qj42Ph4g1i4uJjJCLi4mKjYiNjIuMjoyMiYqOi4mMkpGKiIiOk46SjJGQjI6Rjo6RjZCUi4qNi4qPj4+AeX2AfX16fXx8f3d+eH15gHx/e3t6fYF7eXx8e3p6fnt5ent9fnt5fX55e318eXh7e357fXx5gH5+eXx+fH16e3t7en56fXp6fXl4eXp7d3l7eHN1eXh2dndze3p4dnZ0endzdnN1dnx0dXh5dnh8eHp6dnZ4e3x2eHp1d3Z1eXaAdnV3end2fXl1d3l1fXp5eXZ5dXh7eHJ5end4eXh1dHp5dnh1eHl3d3x1enh0dXl6eXl2c3V6e3V3cnR1eHd0fHlxeHV4dXp3cnZ3dnlzdnl3eXp3enh5fHx6dnd7fXh5eXh5e3d4eXd4eXd4dXd4enp4eHl5e3d3fHl6eXt8fHlEfHh7fHx4e39/foB+fX1/fX98fXx9fHx+fH16eHx6fn18fXt9fn97fHt4fX1/e3qAeHx9fXx7fHx5d3qAfnt8fX6De3mFekZ4fHx5fH18fHt6fXp7en14fHx6e3p6e3d+fXx8e3t6fHl4eXp3fXl7fX56f319f3x8e357fH17e39+gH19f31+e3x/fnx8hH05fn9+fX2Afn9/fIF9gH+AgH6AgXx/f36BgH+Cgn58foSAe4B+gH+Bf4CAf4OBgYGAgYGAgIB/f4KBhYCAfX+AgYGCgoWCgn+DgoOBg4CAgn9/gICCgoB/f4GBf4F/goOCgoOCgn+Dg4GCg4KDg4GCgYGBfoGBgoOAgYODgoODhIKEhYSChIWGhoODhYSGh4KFhYWEg4SEg4KBg4KCf4ODg4SEg4SGhISFhIOFhYWHh4SEg4WFhYODiYeEhIcShoeGhoiIiYWHhYiHhIaEhoaHhIiAh4eFh4WGhYeHiYWKhYSFiouHhYuGhoiGhoeEhoaFh4WEhoSChIGHfoB/goKDg4KDgoODhIOAg4KBgYOCg4KChYOEhIaGg4OEg4WHhIeEgoGIhoOEg4OChISBg4ODh4WDgISFg4SDhISAhYKDgoOEg4aDhYaCg4OBhISBhoWGhoOAgoqKgYKDg4CCgYKAg4B/gIWDhYWFhIODh4SFhn6BgH9/gIB4dXd+e32AgoSDgoF/fXp4b29xdXl6f4F6dXJ/f32CgoGBhIODhIeIhoSDhYaFiYOHiYqHhIaGiIeHiIeDg4WFg4GDgoOAhYV8fnp0eHt7gYOGgoOChYOCf4WChIMEgYCAgoSBgIJ+gX99fnx9dHV1f355dtl6e3xqx7KovaWsdXlnZsrDymdlsWfEl5+osquntrfNdHt9fn15e3h1d3h2eHt7d3h3d3x8fHlzdXp2dHyAgH+BgH18fHt7f3t8f3p6e3x9fn99fXx8fHd7fHx6fXx9eXl6enp8f394enh9gH5/e399EX5/fnx8e3p9g3t7end1eH19/3//f/9//3//f/9//3+FfwF+hH+GfoR/B35+fn9/fn+KfuJ/AgIEAICUmJWXlZWUmJmYnZuamZyXlJefm5aWlJebmp+al5eZmpeXl5aWlpyZk5WYnpuYnZyfnZqcop2bn5uYnpmal5iXlJacmZaVmZaTl5OXjZOYlpCRlJKXmJSOkpCOjpWUkJCTkZKTlZCTlZGSlJSVl5qXlpKUlZKOi42Pj5CRjI6LknSNkJGQi46RkpSSj4+VkpWPkpGLjoyTjo2Tj5CVk42SjpOOi4qJj4yUkZGSkpSIjJCPjo6Ki4yQio6Ljo2PjY2NjpCMjo+MiomMj4yKjI6LjI+OjY6KjY6MjY2Jio2MjI6Pjo+OjJKNj42JjIyNiZOPjpKQi4SQgJKTjoyNkZKSkY6PkZCQlZOUlI+SkpSUkJOSkZOMkY+PkZKRj5KTl5CSko+WlJGUk5WTlZOVkJSSkpKPk5OWk5WVkZOVlJWVk5KSkZCWlpGTlJSUk5CTlZaUlJGRk5aYk5SSmpSUlI6TkJaTkpKRkZWZlJSSlpWQk5SVj42SlJSWgJWVk5GPkpaTlJKUkZCVk5OTlZOWk5KRl5eVlZaVlZaVlJSYkJiWl5iZmZeUlpeXmZmYl5aWlpWZmJiWlZWWl5iWmJeamZuYmpqWlpiYl5iamJqZmJiXmpiZl5mbmZiZlZmYmpqXmZeYl5ibm5WYmZmamZWYlpWYmZaZl5eZmJqZgJmYmJiVl5mal5qZmZmYmJiamZiZmZmbmJqZmZuYmZuanZ2cnJuanJ2cnpyenp+dnp+hn5ycnp2en52coKSkn52foZ6enqCeoZ6cn52ioJ+fn6Chn6Cen56foJ2foKGdoKGin5+hoJ6foqKkoqGfnqKhoaGcnqCho6Gfn5yhn6OdJ5mcoKGfnJuanZ6fop+inKCfn56loJ2hoqKjoJ6doJ+empyeoaCcn4WdgKCenZ6foJufnqCenp+goZ2inZ2anKCdoJyhn6GeoJ6hnqGfm52fn6CdnqCbnJybmJuenJ2enJ6hoJ6fnpyfoaCioZ+en56enZ6enJyfnqCgoaCeoZ6enJybnqCfnJiZoLGpppeXl46FiJiWnaGop6Ckm6GcnaSnqqquppqVk5afgJ+enqCenZ2coKGioqGgoZ6enqOdoZ+goZ+cnaChnZ6inZ6doJyeoJ+bnpmWmZSRkZ+ioaGcnaOem5qdnJqcm5qdnZqcnJ2YnZydm5yYlp2bm5mdmI2LhImTjv2WlJWC+frnxrT1mp6npJaV9YaE39Hd4d3dxtXW3czumJWUl5OSXJWZlZWapZidmJuanaOeo6Klp6OooZmUkpecmpaZlpmbmZeTl5mLkJWVm5aQlZWRk5OVlI6OmZWVlpeRlpWXmJWWk5eWmZyYl5mamJ2am5icm5qgmpecm5iTlpyZgImMiYyJjImMj4uPjo2Mj4qIjZGOio6IjI2Mk42KjoyQjY2OjYmKkpCLjIyQkI6QkJSSkpCUkI6Tjo2PjZCNjI2JjZCOjYyNjYmMio2DiY6Nh4eKh4yNioWHh4WFjYyHhomJiIeKhIeKhYWIiYqMj4uKhoiKhoODhISHh4iEhYOHgIOHiYeCg4eLi4qGhIyLjIWIh4SFg4mFg4yGh4uJg4eFiYSBg4OJhouKiImHioGFh4iHhIOEhoiDhoWFg4OCgoSGhoKFhYSDg4OFhoWGh4SFhoWEhoOEhoWGhYSCh4SFhoeGh4aDh4SHhYGEg4SBiIaGiYmDhoeEiIiMiIOFiouJgImGiYqJiI2KjYyKjIqKjIqMi4iMhouJiYmIhoaKiY+Ji4uIjIyLjoyNi4yKjIiLioqLiIqJjYqLi4iMjYqMi4yMiYqLjoyHiIuLiouJiouLioyIh4iNjYmLiI6Li4yHioaNjIqKiIiLjYqMiIuKiIuLjIiGjI2LjIuLiouHiouKUoqKjIiHjIqKjI6KjIuJi46OioyKi46QjY2LjYiPjI+Rjo6Pio6OjJCPj4+OjI2Mj46PjY2Ji42PjY6Njo+Rj5GPjY2Rj46Qj4+Qko+MjY6Oj46Ej3eOi4+Pj5COj46NjY6Oj4uOkI+QjoyOjYyPj46QjY6Pj5GQkJCPj4+QkpGPkZGPj5CQkZCQkZKPkJGQkpCRk4+QkpGSlZSVkpGSkpOWkZWUlJSTlJSUkpKWk5OVk5KVlpeTlJSVk5STlJOVlZWWkpiYlJSUmJaVloSUgJaUlZaVk5aWmJSUlpeWlpeWmJiamJiZmJWWlZeZmJeal5aVmZaYlJWSlpiYlZWUlpSWmpWXlJeVlpSYlZOUlZWWlJSSlZWUkpKVl5aTlZWWk5KTlpWUlpWVkpSTlJOUlZaVkpeUk5GTlpOVk5eVmJWVk5eSlpSTkpWVlpSVl5GSgJKRj5CTk5OVkpSWlZOVlZOVl5aYlpSWl5aUk5STkZKWlJWWl5aTlZSUkZKSlZWVko6Pkp6ampCPjoh9foqIjpGXmJKWkJSSkpeZm5yfmY+LioyTlZWUlpSTlJSWmJiYlpaWlZWVmZOYlpeXlpSWmJiVlpmUlZSVk5aUlJSWk5KRgI6MipOTlJeUlJiWlZSVlJKTkpKTkJCTkpKOlJOTkZKQjZCRkY+TjIiGfIGIiPaQjIp56/DhwKrgkJCWkYOD3HV1zr7G0NLMusnN18rli4qJi4mIjI6Ih4uXi46OkY+TlpKVk5KWkpmRioeJj5GOiYqLkJGQjoyPkYSLj4yRj4mML4yKi4yMjIaHkIyNjY2GjoqNkI2Lh4uMj5GQjpCRipKOj42Rj4yTjoyOjouHi5GNgHd6eHt5e3d6fHt/fHx9f3l0eXx/eH13enl8f3x5fnh+fXt8enp4gX13e3t9fXp8e358fHt9eXiAeXd7enx6en14fH58enx7fHl8eHtzd397dnZ7en98end4d3h0e3t3dHp3c3N4cnZ6c3N4eXp5e3h5d3d8d3Z3d3N8eHh2c3Z2gHN4eHh2dXd7fHl3d3p6eXF3d3R4dnl1dHt2eX16dXd3fHl2dnR5eXx8eHt3dnF4eXh2dnJ2dXd3eHZ4dnN2dnV1eXZ3d3p0dXV3d3l5d3V3eHl3d3V3d3d6eXh0eXl4d3t5enh1eXh6dXR3eHR0eHV0eHp2eHp1eHl9eXZ2fHx5gHt7eXx7e3x3fn19fnx7fn1/f3x9eX57e3x+fXt+fX56fHx6fXx8fH57en18fnh7fHp7fHt6fnx9fHt9fnh8en18ent9fnp3eH5+f316en19fHx7e3l9f3x+eHt8fXx6e3l7fn59enp8fXp7dXt6eXt8fHp6f4B/f3x7fH16fHt7gHx7fHl6fnx9fX98fXx9fH9/fH56fX6Bf4B/fnt/f4KEf4B8e4GBfoKAf39+f36AgX2Bf4F5fX6Af4F+gYGDgIKAf3+AgIF/gIKDg4B+f358fn2Af31/gH6BgICCgIB+f35/gYF9fn5+gYF9gH+AgYB/gX5/goCBgYCBgX+AgYOCgIKEg3+CgYGCgYKCg4KBg4OEhIOFgoSEhIKEhYaDgoSDgoWDhoaFhIWGhYWEg4SDgoSEgoGDhIKCgoSCg4OEg4aHhoWDg4WCgoSFhYOHg4OGhoWEhIaFhIeGiYWGiYeHh4iKiYiIh4aFiIWEhIWHiIaGh4WFhoaIh4WEhYaHhoWEgIWEhImHiISGgIF/hIGBhIWDg4SFg4SDgYCDhIaFhYSChYCBg4SDhYaEgoSDg4OCg4SGhoGGg4SEg4aEhIKGhIaEg4SFhIaFgoGDhISDg4aBg4F/gIGDgYGEgoKFgoCFhYODhYKFhYSDhoaEgYSCf4CDgIODhIKBg4KCgYKCg4OFgIOAgH6Dg4R+gH56bnBxdHp4fIF+gX2CgYGEh4iIiIR/e3x6goaFg4SEg4KChIaHiIeHhoaHhYaAhoeFhoaEhoeHhISIhIODhIWCgoKAg4KBfH56d3p4e4B+g4SDg4CBgoOGg4KEgH+BgYJ9hYKCgIKBfXx/gX5+eHx5b3J3e919gHt2bdfi0rKWv3l4enJqbLxgYLWipru+tqe2vMS8z3d7eHl6eXx8dnZ1fHl8fXx9fX96fHx7fXiAdnVxd4GCfnt5fYB+fXt5fH13fIF9gH96fX55e3x/fXh7f319fXx4fXx4fHx7eHx7fIB8en1/eH9+fn1+eniAe3h6enp1eX96/3//f/9//3//f/9//3+FfwF+hH+GfoZ/A35/f4x+4n8CAgQAgJmWl5GbmpyXmZiVlJaamZmWl5eVmpqTlp2Wl5qYlJaZlJSUlpOVnJuel52enJaUmJ6bmZ2eoKGdnJeUlZObnJ6XjpSZlpGWlJmSkZSakpOTkpKWlpmPlJGTkZGMkpaRkpSRkpGVkZCKj4+VlpWVmZaUlpCQj46Qh4qVjYqPioqLgJOQj4+RiY+Tko6Oj5CUkJOQkpeSkYuOlJGTkZCKkI2Lj42SiYmNjpOTkIqRjYyOh46KjI6NioyQi4+RjI2JjImMjo6JjYyJipCMjYuMkI6Qi4uQiIiJiI6Ni4qNjI6Ljo+OjY2NkpCOi46Mj5GNkY6QjI+OkJGMko6RkJGPj4+NgJGPjJWTk5SVlZORkpaSkpGTkpCTko+SkJCSk5SUkpOQkJOPkpSSk5GSk5WSlJKTkpSTko6PlZSUlJCWlpKRj5CNk5KOj5KUkpKRlo+SlpiUmZKTlpiWl5OSlZWVkZSRlZORkpSUlJeTlJKTkZOQko6Rk5OUlJGWkpWTlZSUlZSXMJOUlpKQlZCTkZWVlZCSlJeWlpiTlZaYlJOWmZaRlZiUmZiYlpmZm5iZmJGUlpiZl4WVO5eYlZmWmJeYmZmXl5eYl5eZmpiWlpiZmJqbm5icmpqZmZeVl5qcmZmUl5mYmpqYmJiZl5mZlpiZm5iZiJqAl5mYmpiamZmWmZqXlpeamZ2YmJiXmZiXmJaWlJSRl5ibm5uckpqdnJ6gnqCgoKGfn56dnZybnJyanZ2fnaCgnaCfoaKgn6CioKGdn6Gdn5+dn6Chn6Ghnp6gop+doZ2cnqGgn5+fnp6joqSjoqGfnaCfnp2fnZifn6Cfnp+fn6Arnp2goJycnp+dn5+gnZ+enJ2foaGfoqKenZubnaCdnZ6dnZudoJecnJ+fnoWdAaCEnoCgoaKgnp2fnpianJuioZqdoaCfn56enJ6gpJ6goJ2fnp+enp2foKCen6CcnJ2enp2cnJqUgZugn6GeoqChnp2en5ycoaCgnZ6hnaCdnJehoaCfnZ2fnJqTnJ+oo5eQl5adm5KTlaehoZyfnqOhpaiorrOopaejoKOgoaChnp2foAueoKKjnJufoJucnoWfgJmdn6GgnqCdnqCfnpebmpmZpZqXmZWamaCZmY2gnJ2bm5yanJqYl5qYmJycm5uZlpWYmZmYmZaZlpumlp2coaeej+38l4aIhoD0+4+Qk46ao6evurWqlJSdjv7z9fb4hpuQkJaWlJeWmJeQm5+enZWemZedmpWUkYSLjY+TlJWWBZaZl5aShJM6nJaVl5KRlJCRk5GVkpSTlJeUmpWYlpialpqUl5eXmpqZmJmdmJaZlZucmZyen5mWj5qblpSSkpaWmICNjY6Hj5CQjZCOjIqLjo6QjIqMiYyOhYyRiYyNjIiLjYmKipCMjZCOj42QkI+LiouRj46QkJKSjY6LioiKj5CSioWLjIqJjYqPiYmHjYiKiomKjIqNg4mFioiHgYaMiIiLhYaGioaEfoWEiIiHiI6LiouHiIaEhn2BioWDh4KDg4CKh4WFhYGHhIWDhoiIi4eIh4aNiIaDh4qGiYeGg4iGhYiGi4CBhYeLi4eEhoSEin+FgYSGh4WFiIWGh4KDgoKAhIeDgIOFgYWIhIWEg4eGiYSEiICCgYKHh4SFh4aHhoeHhoSFhYiGhYOGhYmIh4eDhoOGhYaHg4eEiIiIhYeJhBmKhoWMi4mMi42KiomLiYqJi4qHi4qIi4iJhIyAi4uIi4yIi42KjImLiYuMjIqKiouKioiHiouMiYaMi4mHhIeIi4mHhoeLiYmHi4aGi42IjomJi42LjoqIiomMhIqIi4iIiIqKi4uIi4qJh4eIiIWHiIuMjIuLh4yMjI2MjYmNioyNiYiNiIuKjo2MiouMjI2MjoqNjo+OjYyNi4lBjY6JjI2Oi46PkI2NjoqLi4+Ojo2Mi42Ljo+MjoyOjY2Ojo2Njo6PjY6PjoyNkZKOkZCPj5KPj5COjouOkJGRkY2Ejw2QjY2Mj5CRj42NjZCPhZBRjpGPj4yPj4+QkY+PjI6RjY6PkZCSkI6PjpCPkJKPjI2OipCRkZKTlYmRkpKVlZSWlJWWlJOUk5STkpKSkZOTlZKUlZOUk5SWlJOWl5WWk5WXhJOAlZiXk5eXlJWYmJWTlpSUlZeXl5WVlZSXl5mZmZeVlZWWlpWZlY6Xl5WVlZaWlpiXk5eXlZeYl5WVk5WRlpWTk5WWl5eXlZSTkpKSlZSTlZSVk5SYkZSUlZWWlJWSk5OUkZGUlJSVmpSSk5SUkJGUk5eVj5GVlJWVk5ORlZWYk5SAlpOUlJWVlZSUmJeVlJaTkZKUlZSTk5OOfJKWk5WUl5OVk5SUlpOTlpWUlZWWkpWTk4+VlpWUk5GSkpCLk5OYl46Hi4yPjYaGhpiUlZKVlJiXmpuZoKWdm5yZlpeWlZWWlpWWlpWWmZeSkZWXkpWVl5aVlJKSlZaWl5SXlpWVlZSAkJGTko2Zko2Ni46Lj42QhpaSk5OVlJCTkY+OkYyNk5ORk5OOjY+QkI+OjI6NkZeNk5KWm5OI3e+HfoKBfO70hYeHg4mPlZyjnpeFg4x/6uXs7e5+jX+FiomGi46NioWOkJCRi5KPjZGOjIyHfIOFg4uKi46Ljo2LiIqJi4uRjY8bjYmMjIqKjouNi4yKiY6KkouNjo2PjJCNjY6NhI8Yi5KOjI+LkI+MkJOSjYuEj4yKi4WGiomNgHl7e3d9fnt7fnt5dXh+fX15e3h3eXt3e352fHt6d3l7enl4fX19fHt8fH58e3d3d318eH18e3x5e3t4eXh7fH94dHp8e3h9eHx4d3V6d3p3eXt8eH52enZ4d3pxdXp4eHt1cnV5dHJtdHR2eHh1eXl7e3Z5eHZ2bnR5eHZ3dXd2gHl3d3hydHp0dnV4eXV5d3Z0dHx6d3J4eXd6eHVydnd4ent9c3R3dnh6e3V1cXZ4cXV0dnd1dnd5dnN4dHVzdnJ0eHZydHt0dnd1eHh2d3d7eHh5c3Z3d3l6eHp6e3x7eXh3dnd2eXl3dnh2e3x4eHR3dHZ4d3h1dnR5eHl2eHh2gHp4d3p7en98fnx8ent6fX1/fXt9fHuBe36Af36Ae3t6fn56fH16e3h6eX1/enx7enl7fHp3eHt8enp+fHt6eHh5fXp5eXh8fHt3fXp5fHt4fnh5fX58fH15e3h9d3t5e3t7enp8e3l3ent6enl3eXl7eXx9fn9+fIB+fn9+gHuAgH9/fnp7f3x+foB9gH19fn18e357fn+Af4GAfn98fn56fH+AfoGAgYB+f31/f4KBf3+Af399f4B+f3+AgX+AgH5+gH+Ae36AgX9+gYJ/goKBf4V+fn99fX1+gYOBgX6Af4B/gX1+f3+BgX99fH6BgICBg4GBgIB/gX5+gYKCgYCBgH2Bgn9/fYKBgoOBgIKAgIKFgn+BgoCEhIWFg4V6goSFhoaEhoWGiIeEhISFg4OCg4ODgoOCgoKBgH6Ag4SChYiGhIWGhoCBgYSEh4aGhoWFhoiGhYWIhoaDiIeFhoWEhImJhoaFhoeGhYWEhIaGgYWFhYKEhYaFh4eFh4ODhYaFCoSFgoSBg4KBgoOEhICFgYGBgoGDhIKEg4SCg4WCg4ODhIeFgoGEhYOBg4aGhIOIhYODgoKBg4WChYOBgYWEhYWDhIGDhIiEhIWDg4OEg4SFhYiIhoSDgYGBg4OEgoGBfnGBg4GChYSChIGAgIKAf4KCgIGEgoCCg4J+g4WCg4GCgn+CfX98g4d9d3t/eRt4dnFwgIGEgoKBhoeHh4aLjIeIiIaFhIOFhoaEhYCEh4eGgoKFhoCCgYaGhIOBgoOEhYSBhIWDhoeGhICAf3qDf3h6eHp3cnt/eIOAgX6CgIGCgIB9f3t8gYOAgoJ+fn+Afn96e3t8foJ8gX2DhX96wNZycnZ4c9zcc3Z0cm10eX1/e3pvanZx0tbg4t9vfGp2eHd1eHp6eXN2eXl8eFN8fHt+e3t+eHB4eHd8fX2BfX17enh8ent6fXl+fHp9fHl8fnmAenp5d316gHt6eXt7e318fH56foCAg3qAf3x8e4B9fH9/fXt6dH16eXl4eHt7e/9//3//f/9//3//f/9/hn+CfoV/gn6Pf4V+5H8CAgQAgJmVkpSVlZqZlJqYlZWUlJWXlZOanJORk5WZlZWXnJWSl5iWl5eampmZnJeanZ6aoJyalJyWm5uZmpianJ2amJmXmJmZmZiXmZeVlJCUkpKVl5eUlJiWjJOTlJGXlpeVj5SSkZaRlZCQkpOWlpaclJWUlI6Ri4uGk5ePi46Shf+KgJCMjpKKiY+TlpGLkJCSkImPi5GNkI+RkJCRj5SRkouPj42Oi42NkY6MjoyRiIWOjJCSiI6NiJGMi4uQiIqNjYqKj46IiYmRi4yLjYyNjo6Ki4mMjYyIjY6MjZCOjY6Ni42NjIuNjY+Oi5CIio2Ojo+Ni42LjY2LjpCSj4+Oj4yMZo2OkZCOkJKUlpOSkZSSjYyRkJSPjZCRk5GPjpKPkI+SkpWNkJGRkpSWlJWVkI6UkpGSk5GNlJGUlJaTkIyQkI6SkZSRjY6Yk5KUlZaWkpWTkZOVkpSWlpeYlZCYkpKTj5KSmJeSkYSVcpKTlJSRjJCRjZSTkpWRmJeUk5ORkZGXlJWTlJGRj5OUkpWRk5WUkZKVlZWZmJaSk5SXlpeYlpWZmJmWlZSVlpKZlZWVmZmWlZaXlpiWlpeXmJibl5mVlpiZmZiYlpiZmJiZmpiZmZiVmZWXmZmXmJaYmoSXSJiYmpWcm5qam5qZlJaamZeXmZiZl5iZmZqamZiamJmXmJmamZSXmZqampmZmZiXk5OIgdzCroiTmJqam5yen6Gfn5+cnJ+dnYSfgJ6fnpuenZ6cnZ2en6CdoaChoZ+foJ2foJ6hoKCboKCbnJ2doJ6enp+foKGgnZyinaCfoqCenp2gnJ+gn6ChoKGfn5+hoKSko5+dnZycm52goaGdoKKhopqdnZ6goaKgpaGioKKem5udm5qdnJ6em56eoKGdm52anZ+hnp2inqGhgJyenZ+ioJydm52dnJ+fn56gnqCeoKGdn5ydnZ6ZnJycn52eoZ+dm6GhnJyenZ+fnKCemfupmIuZn52enp2fn5uenZubnp2en5yfoJ2foKGhop+akJKfnZ2hnaKfnJ2fnZqYnpyhp6KioZydmp6fop+joqKgn5+eoaCenZ+gm56egKGfoZ2foKCdoKGdmp6goKKgn5+fnp2cnpubnJqcnJqWi4WTi5OLh4eQmpaYmJmWmp6ZmJuZlpmamJuZlpeYmZmWl5eUmZmgl5WTjJ+bmJ+doZyYmJGTjY2IhvOHjJKOkp2dpJyYn6Cvjfn0hbCkoZ+gmJGWlJWXl5mXnJigoJ+jVJ6dm5ealpWUkZSXlpaVk5WTmJmSkZWWl5aTlZ+Qj5aYjYmNjJGSk5OUlZOVl5aZk5WXlJeamZaZlZycmJual5SVmZeWlpKcmpianpCVlJOYmJqYmCaOi4uHi4qMjImOjYuMjYqJjouIj46HhoqKjoqMjI6LiYyMiI2NjoSPgIyPj46Oko+LiI2Kj5CMjo2RkZGOio+NjY+Mi4+OkI2MiIaIh4mKjIuKh4qIgIiIiISJioyKg4mIhYqGh4OFiIiKiYiOiImJiYSGgoR/ioyGhoaHf/KCiISDiICChIeNh4OIioeGf4aEh4aHhIeHhYiDiYqKhIiIgoWChoeIhIOHJYKJgH6IgoiKgIaEgYiCgoWJfoKDhIKChoaBgoGJhoODhISDhYWEhFCFhYGFhoWEh4aFhoaFhYaEg4aGhoWDh4GEh4eFhoaEg4OEhIOFh4qGiISGg4SEhoiIh4eJiIuHiYmKiYeFi4mMiImKi42LiYeLioqIjIuKhoSJgIqOjYuLioaMiYqJjIuHiomLi4yLiYiJioaKiImIhYWOiYmKjI2NiI2KiIqKh4uLjo6MioiMhomKh4mLjo2HiIqJiouIiomIh4WJiYWMiYmLhY6MiomLioqIi4yNi46FiouLjIyMi4yOi4mIjIyNkY2NioqMj42Nj42Ljo2Pi4yLgIyNiZCMjYuPkI2Mjo+Njo2Nj46NjpGMj4uMjY+PjY6Njo6Pjo+Qjo6Pj4yQjI2Pj46NjJCRjoyNjY6Pj4qQj5CRkZGQi42Rj46Nj4+Qj5CQkI+QkI+Sj5GOkJGQjYuQkpKRj4+PkZCQjIyAdMK2p4KMkZKSk5KUlJWVlZSSk5WTgJKWlJWVk5aVkpaUkpKTkZOTlJGVlJWVlJSTkZOUkpSSlI+SlJKTlZWXlZSUlZeXl5iVkpaSl5WXlpSVlpeVk5aWlZeXmJaVlZmamJial5WVlJSSlJaYmJSVlZWUkJGSk5aXlpWZlJWVmZWSj5KTkpOUlpSSkpSWl5OSlpOUlJSTgJKUlJeXk5ORlZeVk5SSlJKUl5KUlZWUlZSWl5OVkpKTlY+Tk5OUkpSXlZOSl5eSk5OTlZSRlZSR9K2ig5CUk5STlJOTjpOTkpKUkZOVkZSWlZWWlZOTko6HipWSkpaRlZSRlJaUkY2QkJWZlpeXkZSQlJSVlJWYlpSVlJOUlZWUCJaWkpSUl5SXhZaAmZaUkZOVl5iVlZWWlJKTlZSTlZWTlJKMhX+EhIqFg32Hko6RkI+OlJaSkJCRjY6PkJOQi4+Qj5GOjo2LkI+UjIuJhpaSj5SSlJCLjYeJhYWDg+6AhIeDiI+OlZGJjI+cg+jed5mRkZCUg4OJiYqLjI2JjYuSkJOXkJGOjI+Mi4sdiYqNjYuMiImIjIyKiIuLj42LjJWIh4yQiIWIiIyEiyuMi4uOjI2KjZCLjI2NiY6JjpCNj46MiouOjouKiJKNio6Rh4uJiI6MjY2MgH55fnZ5eXp5ent7fHl6eHd6e3d8e3d3end7e3p6e3l3fHt4fHyAfnt6e3l7enl5fXp3d3l3fHt4fHp9fXt8dnp7enx9eXp7fHh5eHZ3dnp7fXx7dnd4c3l4eXR3ent5dHh6d3p0dHB0dXZ3eHd8d3d5dnR1c3hydXh2dnh4ceJzgHlydXdzdHZ4e3l1d3l3eHF4dXV0eHV7d3V2dnx8enV7e3d4dXh3d3VzenN8cXF5cnZ7dHR4cXhzc3N2cXNxdnZ2eXp0dXV6d3V0d3V0eHh5eHh3eHl1eHd5eHt3d3t7eXh5d3V6d3h5d3p3d3h5dnd4d3R1dXR0dXZ6d3hzeXd4GHZ4eXh7e3l5e3d7fHl7eHuAen9+e3x9fIR7BX19fHx9hHkzfHl6fHx+e314fHt6eX18enp8fH1+fXp8enx2fHp6fHh2f3l5eX9+fnh9e3t7fHt8e31+hHyAeXx7enx8gX96end5eHx6e3l2d3d6fXl/e3x+eH5/f3l4e318fX5+en94fH5+f31+gX5/fn18fn58gX+AfHx9gH5/fn1+gX+CfX99gIF9gn+AfYB+f3+AgoCCfn6CgIB+gX+Bfn5+gIB+gH+AgYGBgIOAgoCAfYF9fn+Afnl9gINvgHx9fn9/f3t/gYKEgoGAfX+CgIGAgIB+f4B/gIGBhIGEgoOBgYKCgH6AgoKDg4GBgoGCgoF5b8OzoXmAhYeHhIOEhoeFhIWDhIaGhIeGhoeFhoSDhYODgYOCg4KCfoSDhISDgYB/gYKEhX+CgoKFhINshYSEg4SGhYaHhIGFg4aDhoOCgoaHhoSHhYSFhoeFg4SHhoWEhYOCgoOCg4KBhIV/gYGBfn1/gIKFhIWDhoKDhIqEg36Bg4ODhYSEhIKDhIWDgoeDgYOFgIKEhIaJg4KChYWDhYSBg4CBhYCDhIWAhoSEg4aCgIKCfoOBgYKCgoOEhIGGhYGCgoKEg4CDhILows1yf4GCgoOBgIJ8f4F+gIKAgYB+gYOCgoCDgIF9fXh6h4N/gn+Fg4CBg4OCe315foKEg4WAgn6Cg4OAgoOFgoSCgYKFhoSGhYGChIaDh4KEhoaGhYOEhIKEhoeGg4SAhoOCgoOEhIeEhYaEfHZxbXJ2dnhpc359e39/f4KEhH9/gH19gYGDf3uCfoCCfn59fX+Af3h5eHh/f3x+f4B9enx3d3h5dnbYcnV4cXd1d357cXJ7f3LTv2F3d3h+gGZwd3d6enh5eHp5fnx+fnt7enx/ent4eXp6ent8dnt4e3tBenh7fYB9e36AeXp7f3h1e3h7ent6e3p+fX17end6fHp7enx3end9fnx/fXp4fHx8ent3gHt5fH53fHp3e3x9e3n+fwF+/3//f/9/ln8Efnx9fv9/yn+Dfv9/pX8Bfo5/gn7pfwICBACAm5OTl5iZlZaVmZaVlZqYlpqUlZmUkpedlpSclpGXmJSVlJKWmJiZlpiYnJ+bl5KSnqCam5SZl5iXmpeYjpmbmZiYm5iTl5iTlZeSlZGNjJWXmpOWkY+VlZGOj5GVkZKTlJCQkJSVkZCQj5CSmJiUlJmWlZWTkYmYjIiJjYmI/4yAkoyNj46Qk5OMk4uSjZONkY6Qko+RjpKTkZKQkYmChIyHhIeIkZGTjpKOj5GNkIyMkJKMjI+GjYuMi4qKjoyHjoqGjY+Ljo+HjY2KiY2LjIaKjImKiY2Ri4iNjo2PjI2OjYyOio6RjIuJi46HjI6Oj5KMkJGOj5SQjY+Rj5CQjY2AkZGUkJCUlI2SkpCQjpCNkZCSlZKPkZKTkY6TlpCUjY+Uk42QkJKRlJWRlpaTlJGTjZGWkpKRkpSUlJKQkZCOjpCSlJCQj5CQlZKQlI+XlZeUkpSTkY+TkY6SkpeSlJSSmJSSlZSVkZGUlZSPko2T/4+PkZKTj5OUlJKUlY+RlJY3k5KUmZiSmJSRkZKWkJSPlJGSkZiUkZWZlZeUmpeZmJeXlpmamZaVnJeWmpmZmZyYlZeXlpeXk4aYgJSVlpeWmZWWmZmYl5iYmJqXmZiXlpmZlZSWlZiXmJaXl5qVl5WVmJialpqWl5mVmpeWmpiZmJaYlZeZlZmXmJeXmJOVl5aZlpeXmZeYl5qYmpeWjsLYxcny/YmWmZqbnKChn5+ioaCdm52dnp+enZyenp2fnpyenp2dnqChnpyfHJydn52enJ6fo5+foJ2dnJydnp6gn52dnaCcnqGEnx2gnp6fnZ+goqGhn5+goKCcnqCkn6Ggn56goJ2en4SggJ6hoqGdnJuem56dn6CenZ6cnpycm5uenJ2emp2hnZ6bn6GenZ+cmp2Xmp2dn56cnZubnp+dnZidmp+en52ZkJmdnaWenqCfnp6gnJ+cnZ+dnZ2hnZyen56enaCenKGam5eAts6blaCenqCgn6Sln5+dnJ2hn5+fnZ2bm7OepZ+dgJ6cj5mgoZ6kn6Cfnp2hnJmYnqeeop6hn5+bnp6hn56dn6CfoZyfnJ+cn6CamZ6dnp+inp2gn5yaoZycoZ+cn56gnp+cn56hnqGcm5iam5WVnqCZlpaTl5acm5uXlpucmJaYlpqXm5aUmJiampuWl5SUlJaUmZSYkIiFgfORmZ6egJmUkIyKg4D28YmOi5KUmaCcn5mdjZuWlICJmo6ckoqEmJeYl5mcl5ean5yYo52amJSRmpSXl5mXl5WSiI+RlJ2XlZibmZmTkJSPlZGMk5aTkpWPkJmYlpeamJeYmZaVlpaZl5iWmJmVl5iVnJeXmpaamJeWmZaalJudmZeUmZqWApKVgJKJh4uNjYeKi46Mi4mPjYuQioyNioiKjYiKj4qHjI6IjIiIjYuMjYqOjY+Sj4yIiJOUj42KkIyNjZGMjoSMjpCMj5CLiY6OjIqLiIyGhIOKjI2IioeEiImEg4OEi4aIiImFhYSIh4WEh4WEhoyLiYiMi4qJioiAi4SBgoWCgfiELIiCg4SIh4iIg4qDiYOGhIiFh4iHhoWIh4WGh4eDfX6DgYCCg4eHi4aMiIiIhIQqiYeAgoaBhoKBg4OAhYV+hYF+hYeChIWChYWFhISCg4CFhYGDg4WIhIOFhYaAh4SFhoOGiIOCgoKFgIaHhoWKhIWHhISKiIWHiIWGiIaFioiJiIiLiomKiYeIiYeFioeKjIuIiouLioaPkIqOhomMioaIh4eIioyKjYuJiYiKhoiLiYqIiomLjImHioeEhIqJiIeGhIaIjIiHioaMjI+LiomJh4SJh4OLiIuHioqAiY2KiYyLjoeFiYqKhYmDifWHh4mJiIWIiIuIiouHioqNi4iMj46KjYuIiY2Oh4yHjYmIho6NiY6SjI2MkI6PkI6Ni5CPkIyLj4+MkI+OjI+NjJCQjY+MiY6PjY2NjouLjY2LjY2Njo2OjpCOjpGNj4+Ojo+PjIqKjI6Nj42MjY6AjI6NjI2Oj4yQjJCSjZGOjpCPkI6MkI2NkY6Rj5CPj46Mjo+Oj46Pj5COkIyPj5GPjoixsZun0u+Cj5KRkpOUlZOWmJaVk5CTk5WUk5KSlJaTlJSSlJSTk5OVlpKSlZKTlJKUkpGSlZKSk5OSk5OTlJSWlZORk5eSlJiWk5KTlpSAk5STlJWYlZWTkpSUlJKUlZqVl5WUlJaXlJSVl5iXlJaYmZeVlJKSkpSUlpaTkpWTl5SSkpOVkpWVkZSWkpWRlZeUlZWSkJKOkpGTlJWSlJKSlZaUlJCSkJOSlZKOhpCTlpeWlZaWlZWWkpSSlJSUk5KUk5OUlZSUkpWUk5eQkY4OeaK4kIyWk5SXl5aYl5SEkwGVhJKAlZKRopKYkpOUkoaPlpWRmJSVk5WUl5GPjZGWkJWTl5OTkZSTk5KTkpOUlJiSlJOWk5OVkJCVlJWWmJSTlpaRkJiTk5aUk5aUlZWWkpSVl5WYlJSPkpOOi5KXkI+RjpCNk5ORj4+Rk5KQkI+QjJCMi5GQkZCSj4+NjoyNjI+KjIiAg3976IePk5GNi4WEg3588OmChYOJjI6Rj46JjISIiIh3fIl+iIF2d4mKjIuNkIuLjZGQjJaRjo2Jh46Mi42PjI2KiIOGiIuRi4yNjo6QiYmMiIyJiYyMiYqNiYmPjI2OjYyNj5CNjYyLjoqMjI2QjY+NipCMjY6Kjo6OjY6NkIkKjZGKjImMjIqKjIB/eHV5ent2ent+fnt4fXh2e3h6e3l3e3l3eX13d3x7dnt3eHt7fHt3en18fHt6d3l/gHp8d3x5enl7eXlzeXyAe319eHp7eXt3fHp5c3Rzent7eHd2dHp5c3R1dn13eHp7d3R0dHZzdHZ2dXZ5enZ2eXh5d3p6cnx0c3V1cnPod4B1c3Fyd3d2eHN8dnhzd3d7d3h1e3d2dHZ0dnV3dnFzd3JzdnR5eXt2e3l8dXR2eHR5eHNxdnBzcnF0dHR1c3N3c3F4enR4dXN2eXZ5dXZ2dXV3dXd2eHt4dnh4enl6enl4d3d1eXl3dHZ1d3h5d3h3e3Z4eXd1fHp2eXt1eXh4eIB8end4e317ent4ent6eHd9eXx/fnp7fHx8eX9/e355e3x6d3p6d3l6e3t8fHt6eXt5d3x8fHp7e3l+eHd7eXp5enp7e3l3dnd7ent7eX59gH57e3p6d3t5d3t6fXp7e3t+enl8fX95d3l4enZ7dHbgeXp+fH16fHt8enx8eHx6fYB9e3x+fn59e3p6foF5fXp+fnt4f399gYGAgn1/f3+Bf318goKDf32BgoCCf318f3x8gYKBf358f4B+f3+AfHx8fnp+f36Afn+BgoGAgX6Bf35/gn58e31+fH2Af35/fX5+f319fX9+hH6BgX+DgICCgoF/fYF+f4OAhIOBgIB/fwN/gn+FgV+Ag32CgYKAgHyfmoObzPJ5hIaCgoOFhYSGhoeFgoKFhIWFg4ODhYeFhYSChYWEg4KFhoOChYOEhYGBf4CAg35/goCAgYSDgYOGhIOChIiEhYaDg4OEhYSBhIOCg4WEhoWCZoGBhYaBhIKAgX+Afn+AgoOBgISDiYaGgYODgYSBhISBg4OChYWDgoKGgISHhIWGgYWChIWChIWDgYF7goCEhYN/goKBhIWDg4KBgoWEg4F/dICDhIWEhIaEhISBgoWDhIKDgn+EgoSDRoSDgoKBhX+Af2+Qm35+hoKChIOEh4aCgH+BgYF/fn+DhYF/hICEgYKCf3uAhYWAhoKEgoCCh4J+eXiAe399g4B/foGAgICEgoCDhYKDg4SCgYR/gYODhIaGhYOGhYCCiISDhIOChoSDhIJ/gYGGhYiEg4GAgHx5fIF+gYN+gHyAf4B/fYKBgH+AfH97fn19gn2EgoB+fX1/fHx9fnh5eHhwbM11foN+e3lydnZycN7QcXZ2dn54eXpyb3RzbXZ4Z2pvZm1rX2Z5eWB7e3l9eXl5e3x7gHx9fXx4fXt6en98e3x7dnd5e4B8fXx9f4B4ent7enh7fHp6enx5eH57fHx8enp+gHt5ent9eHt6e318fHp8gnx5fXl6fH17enx8d3h8eX55eX15e3z+fwF+/3/xfwF+/3+ifwF+hHwBff9/yn+Cfv9/mH8Bfot/gn75fwICBAAQm5iZlpKRmpqYkpObmZualISWhZWAnJWblJuXl5GYm5eYm5Wam5yVn6OZp5uZnJeYlZaXmJiWmZOVlZyZl5iblZWWlpmSkZaVmZaVk5iWl5SRjJGRlpSSl4+MlZSSipKQko+TkpKSk5KSlJaUlZWSlZWQjoiFhIuLkJGKmZWRi5CMkJCLj5KPjJCOjY6PkZSQj5CVlJKAlpOUioCFjYGIhImPnJGRjZKIk5CSi5KPjYuRjIKLio+Pio+Mi4qLiYyOjIyMiYyLh4yPhYqMi4+Nk4uNjI6LjY+MjIuMjI2NjoyGjI6Lio2Nio6Qjo+OlIyQkY+SjYyNjYuMjIySjo+Qk42QjoySk46Mi46Rk46OlJOSj5KRlJOAjpGVkZSTk5GUkpSPkpKTkZCUlZWRk5ONlZiWk5WVkZWSkJSPiZKRlJCQkZSSi5OTk4yTlZSTlpGOkpOUlZWUlJKPlJCSkZSPj5SMi5OVko6UlZSRkZOQjYqNkJOQkpWUmZiVk5GSkpGXlpiXlpWUlpSTj5WTkpSSlZKUlpeZl5OAlZaal5aYlpaamJubmJqWmJaZlpeam5mZmJOZlpiWkpeYlZSWlpaVlpeWk5mWmZaWlpSXlZWYmZmampiYlpSYmpeXl5qZl5iVkZWRl5mYmZaWmZmYmpaZlJWXmJqYmZubmZqYmJuZlZmYmZeampmYmZqZmZeZmpiWiufXy8jgkZgbmZucnZ6fnp6hn52foqCdnZ2bm5uen56fnJ6dhZ5CnJ2fm52fnZqdmpqbnZ6cnJqbmpicm5+doJ2Vn56cn5ednZ+foJ+jo6GfnZ+gpJ+gnJ6dnZ+dnZ2eoZ+coZyen5+chJ49n6GhoJ+cn5udm52dnZyen6CamZ2dnJubm5qbnJyboZ2fnpiam5ydoJ6enZ+fnp2cnZybnZ2bnpaenZ+coIScgJ+fn6CboKCcmZudpZ+hoJ6cnqGim6Cgn56cnaCcoZqZlZibnZ6fmqKfn6ChnqGdoJ+hoZyfo5uZnJ+kpqekpaWgmZaWmZyeoaGfnZ6cmpWZn5yinZ6XnJmenZ2cn6GgoZyknJ2fnZ+cnpybmpieoJ+gnJ+enp6dnZydoJ6gnJ6fgJ2cnZ2fnJ2empiXj5OSkpebmJeYmJyamp2am5qYk5eblpuclpaZlZiWmJyZl5mVlZOSlI+QkIuMiv6Ynp2dmoqGhIOCgOHhgJGJlp2Zk4uG/IiNjYePj4eQlJuflJCZm5qblZWTlZeWlZiVlZabk5GYk5aTlZOVlZKRl5SRkJGUQJKVlJeWk5OVjpaRkJOTkJGXmpSZmZyblZKYlpaaoKCbnJeXlZeWlZKTlZSUlpaUkpeUlpSQk5KRl5SXl5eSk5iAkI2OioiFjYyMiImPjI+NioyLi4qMi4yMiZCKjomPjYyIjo+Mi5KLjo6Ri5CTjJmOjo2Ki4mMjI6Ojo2Lj42Qj46PkYqKi4uNh4aMio6JjImKiYyHiIWJhouJhouDgouIh3+Fg4eEiYeHiIeHh4qJh4iIhoyJhYSCfX6EgYaIfo2Ai4mDh4WIhoOIiYWEh4SDhIeIhoWFhImJio6Ki4R6gIZ9g4CEh46GiIeHfoiHi4KKiYWCh4R5g4KGh4GGgYODg4CEhoKDg3+Dg4GHhn6CgoSHhYiEhoOEg4aHg4SDhYWFhoaFgIOFg4ODhoWGiIWIhouChIWFh4WDhYaDgoSEi4WAhoeLhYaFhoiJhYKChouMiIaLiImHiomKioWKi4uMi4uKjImMh4qKiomIi4qJhYqKg4uPjImLjIiLiYiLiIKHh4yHiIiLiYCKioqDi4mJiYuJh4qJiouKiouKh4iGiIeIh4SJiIaLiomGi4qJh4eLiIWEh4iKhomMi42KioqJiYmAiI2MjoyMiYmOi4uHjIuLj4mMiYuNjY6Oi4yPkJCPkYyLkJCRkYyPio2NkIyOkY+PkI+Kj4uPjoqNjoyMi4uNjYuNi4mOjY2MjY6MjYuNjY+PkJCOjYyMjo6MjY+QjouOi4eKiI6PjY+Oj5CQj5CNkIyMj42Rjo+RkZCRj4+SkIx6kJCRj5GQko+QkI+Qj5CPj46B18q9uM+JjpCQkZKUlZaVlpOUlZeVkpSVkpGRlJWVlZOVk5SVlJWVkpOXkpSTkZGTj5OSkZORkZCRkI6RjpSSlZOKlJSSk5CVk5SUl5WWl5eVkpOUl5STkpGRkZKRkpSVlJORl5WWlZaElRmUlpiYl5WRlpOVk5WVlJOUlpaSj5OUkZGShZOAkZWTk5CPkJKUlZSTlJKWlpWVk5SSj5KUkpOOlpSUkpaTk5GSl5WVlZKWlJKQkZKalZeUkpGTmJWQlJSTlJKUlpCVjpCLjJCSlZOOlpWVkpSTlZKVlZaXk5SZlJCUlJeamZiYmZePjoyMkZKWl5aVlZOSiouNipGUlIyRjpWSkJIGk5aWlZOahZMNkpSTk5OPlJWXmJSWlISVgJGSlZKVlJWVlJOUk5SRlJWRkI+HiomJjpORkZSRk5GRlZKTk5KNjpOPkpCLjpGOkZCPk5GNko+OjIqMiIeJhIOA8I6PkI+Ofn19f3x72dp6iIONk42Hf3rqfIOAeoWEeoOBjI2BhIqNjo2IiIiLi4qKi4qIi5KKiI2Ii4iKiYmLSIqJjouKh4uNho2Mjo6JiYuGjYuKio2JipGRipCQkpCLiI6NjZCUkpCQjoyJjY6OiYmMioqLi4iHiomLjIqHh4uLiI6PjIeIi4B+e312eXR9e3d0d397ent6dnl4eHl4eHl5e3p8dnx6enh5e3l7f3x+eH95fIB7gHl8enh7d3l5enp8e3t8e3x9e3yAe3t4dn15dXh4e3Z4d3h1enV4eHh5eXt2eHFxd3Z2b3JyeHR3dHN5dnZ1eHd2dXh0eXd1dXdzcnVzdnlveWl4d3N5d3Z0dnl6dXh2dXR1eXl4dnh2enZ6fHh7c2t0d3N5dHZ2eXh8e3hzd3t7dHp3eHV4eHBydHd3cnlzdHV0cnN0c3V2c3V2cnh6c3V1dnt3e3d4dXZ5enp2d3V4d3l4eHdydHd1dHSEeIB2enl9dXZ3d3l2dXh4dnV2eXt3eXl8dnV2d3h6dnZ1eHx9eXp+e3l4eX19fXt8fXp9fYF7fnt8ent9e3x4fHt6eHp6c3x/enp9fXp5d3h+enV6eXx7ent9e3R7fXx1e3l3e3x8en56e3t7fHt8d3h5enh5eXZ3ent8end2enh5d1h2e3l3eHt8fXd6fXt8enp5eXZ7en1/fXx9fXuAfn18fHl9gXx6eX1/f35+fX1+foCChIB8gH6Bg36Cfn+Bg39/gX5+gIB8f3yAgHt/fX59fH6Afn1+fHqAhH+Afnx+f39+gYKDgn9/fH2Af3x8gIB9e4B/e3x5fn9+gH6AgICBgH2Bfn6AfoR/gIOCgYJ/gIKBf4GChYCCgYOAgIKCg4KCgoGAdsW3rKm0foOCgIGChIWFhYaDhIaHhoSEhYCBgoSGhoWEh4SEhIWEg4KEioWFg4SCgX6AgIGAfIGAf39+foB+g4GEgXqCgoCEfYWAgYSCg4OEhYKAf4KCgYGAgIB/gIB+f4CBgH2Fg4WDhIKCg4SEgoSGhIOEhYOFg4WGhYOFhoaDgIOEgoGEg4WFgoKAhoWEfX6DgYOAhoSEgYOEg4WEhYF+goSDgn2GhYWCg4CBgYOEhYODgYOCgoGAg4GIg4SCf36Fh4OAhISCgIGDg3+EfYB9eH5/hIF/goGEgYOAgICCg4ODgYODf3+BgICDg4WChYWBfnx8f4GFhoSGhYKAeXZ3dXl9gXl9fYWBf4GBhIOCgYiDgYCBg4CBgIKCfYKBhYaDhYWDg4eFgYGAgYODgoOCg4ODhYOEhYCAf3t1dHp2fIOBgoN+goB/goF/gn98fYJ9fX58foF9fX98hIB7goB+fHx9fHl6d3Zv0Hp6fnp8am9wcW9txMdpd3N5gHpzaWfQaHRtZ3V0bHJtd3Vqd3l7fXx5eHd7eHp4eXp4d357eHx7fHl7eXl8fnt8e3x2eXt5fn6BfXp4fXk3fXx7enx2eH5/eX98gH99dHt8fHyBfoB9enx6fX19enl/fHx7fXd3eHl9fHt2eHt7d318e3h5ef9//3//f/9/ln+Ffv9//3/kfwF+i3+Cfol/AX7vfwICBABqnJaXlZaZmZmXnJeelJiVlpeYlZKUmJiVl5iXmJmWmJmTlJqalpeZmZuWmZ+ZmaCZmJmZm5iYlpuYkZeVlZ6YmZOUmJyTmJaWmJiWmJmSkpiSlJOUj4ickJ6Tk5GQkZCWj4yQmJCUlJKQkYSPgIyUlZKXkI+RkZSRnImOkpWQj4uIi46Kj5OQio6Qj42MkJKPjJGQj5KQi42Tk5KLg4mOh46Fho+jlJaSlZKTkpKOkY6Oi4qOkI6PkIyOkY2Nj4mEh46Lh4yNjIeIhY+Li4qMjpCIiIuNjIyQi4yPiYqOj46Pj46IjIuNiIqMi4uOgImLko2Njo+RkJCRkouSjo2SjI6LipGNkJKRko6OjpONi46OkY6QjJOTkI+SjpCTkZSQj5ORkJGRk4+QkpGUk5OWk4+WlpSVko+Lj5COkJGNjo+QjoqPj5CVkZGVlJGVlJCSkJKSlpGUlY6TlJSQlJKTkJeVk5KSl5aWk5SSkZCVgJKTkY6TjpKSkJGRkpKSlpWQlpGUlJWVlpeWlZKSkZWTkJGTlI+WlpWSmJSUkpOUlpiUmpuWlpmYmJqYmJqZmZiYlpSXl5Wam5WYlpaYlpSZk5mVlZaZl5ial5mVmJmJkZeWmpWUlpWZmZiVmJiWlpOUl5qUmJWUl5qZlpSRlJuYgJqampOXmpiXmpebl5qYl5ibm5aampiYmZibmZianJqamZqZmpqbl5mYmZqam52enpyfnp6em5qenp6dnpucnJqamZ6doJycm5ybnKCem5ydnpqamp2an5ucnJmam5uYm5iampyenJ+em5qcnp2en56gn6GfnaKfoKCen6CbnZ6egJ6dnZ6cnZqZnJ2dnZ+fn52doJ+gnZufnJqcnZyenJ6dnZ+fnJ6dm5yZmJibm5qbnp2fn6Gfm5uZm5+hm5ybnJaanJubm56fnZuenJuenJ+dnZ2en5yam6OdnqCcnJydnp+cn6KjnqGen56cnJybmJubn5yanZyenZ2fnqGgoqCjLqChop2fnaOppaGen6GhnKqloJqao5eakY2Ym5mbmJmVlpiTopmempacmpybmZmEnICdoZ2enp6anaGbnJqdoKKhn56boKCcn5+hnJuen56em6CdnZucl5qUkpWVlZiZmpeXl5aYmZaXlpmXmJibmZianJmVmZiUl5mWlZaUl5qXl5eUk5GKkZmYk4+DkZSYhICDg4H86urw+YHp2/Xa+ouHjIKEj5KPjfOJiI2Jk5STm2CYlZqblpWVlJCOkZOTkpCOlJGSlJKXl52OlpuRkY+RkpeUk5WTlJGWmZWRkpWXl5iVmJWYmJiWnZiYmZeZmZyVmZaZk5aWk5CVlJWVk5ORkpOUlpWPkpSWl5KWlJiXlpWAj4qKiIuNjYqJjouQh4yIjIyKioeKjYyLio2PjI6KjYyJi5CNi42MjY6MjpOOjpSNjI6Njo2Oi5GOiIuLjpSNjomJjZKKjI2JiouMjo+Jh42Ii4qIhICPhZKJiIaFhYaMhISGjIaHh4mIiIaHhIaBiImHioaGh4aIho+Ag4eLiIaAhoCEhoGCh4eBgoSDg4OHioWDh4iHh4iCg4qLiYN8goiDiIF/h5SJjYmLioeIiYWIhoaDgoaIhYWIhIaIhIeIg35/hIJ/hIWFgIB/h4OCg4KGiIJ/goWEg4aChIWCgoWFh4eGhn+Dg4WChIOFgoWDg4iEg4SFiYaFh4qDh4WFiYWAhYWEiYiHioiIhoiGioSDhoaLhIqHioqIiIiGiYyJi4mIi4qJiomLiYiLiouKiYqIhYuNjIyJh4KIhYSIiIeGhoeFgoaHh4uIh4uJhouNiYiFiYmNiYuKhImKioeIioqGjouJiIeLiouIh4mHiImGiImGjIeKioeGiImHiYuKho6Ah4qIjIuMjY2Qi4uJjIuJi4qMh4yPjo2SjY2NiYqMjo2NkI2Mjo2OjY6Nj46MjIuLjY6Nio6Pi5COjo6Mio6Kj4uLjJGOjo2NjouPj36Fjo2RjYuMjI+QjIuNj4yLi4mNkIyPjIuNkI6MiomMkoyPj5CKjo+QjY+NkY2Qjo6OkZGAjpGQj5COjZKPjpCPkJCPkJGPkJGOjo+PkI+QkJKTkpWUlZSTkZSTk5STkpKSkJGOlJKUkZKRk5KTlpKQkZKSjo2RkpCTkJKSkI+QkZCQjpCRk5WTlJSRkZKRk5WUlZWTkpGSlZSUl5OWmJOWlJOVlZSUlJWSlJWUlJSWlZaTk5WAlJiXlZaVk5SVlJSUlZKUlJaTlpeUkJCRkJSSkZOVlJSVlpOPkpKRlZeUlJKTjJGTkpKQlJaSkJSQkpSTlpSTkpKUkZGQlpOSk5KUk5WVlZKUlpeUlZOSlJKSlJKNkZKVkpKRkJSUk5aUlZSWk5eUl5eTlpKWnJmWkpSVlpCcmJOAj5GUjY+Eg46Qj5GQj4yKiYeOjZOQjZORkZKPjpCQkZGTl5KTkpOPk5iTlZOTl5iWl5WTl5WSlZWXkZKUlZSUkpOTlZSUkJKOiImLjo2Pko+Pj5CQk46RjpGRkJCRj5CRk5COkJCMjpKNjI+NjZKPj4+OjIuDipCOioZ8iImLd3dbfH198uHm7fN83czeyeWCfoF7eoKEg4Ljfnp9eYaHh46LiI2Qi4uMioaFhoiLjIiHi4iKiomNjZODjI+Jh4WJiIuLi4yKiYiNj4qIiIuPjo+Nj4uPjo2LlY+Oj4SQIYiMi5CKjI6Kho2MjY2Ji4eIi4qKjYeHi42KiY2MjIyKioCBd3l4eHp7eHl5eX15end6eXd3dXd8eXV4d3l7fHd8e3l6fXp4eX57eXh8f3h6gHp5fnp5d3l6fXl1eHh+fn58eXd7f3h7fXl4e3t8fnV0eHp+eXp3cXp0fHh2d3Vzd350c3Z6dXR3dnR2dnVydXN3eXd6d3d4eXt4fHJzd3d4eIB5cHN1cm95eHNzdXR2dnh7eXV3enR1eHB0d3p7dnB2eXh6dHF3fHh5eHp6d3h5d3l1enVze3p2dHt4dnh1d3h2c3R2c3J3eXt0c3R6dXN0dnh6c3N0d3Z2eHR1dnZ1dnd6enl3cnR1dXR2dXl0eHV3eHZ3d3d6dnd6e3d3dnh8d4B4d3h4eHd5eXt5e3d6dnZ5eXh5fXp7e3t6e3p+fHx9e3t8enp7fHt6eH18e3l6e3h3ent6fX15dHl3dXp7fHt7eXl3eHh4e3l6fHh3e4B7e3h8en56fXt4eXt5eHp8fXmAfnh6fnt6fHl2d3Z2eHN4enZ/e398enp8e3Z3e3h3foB2enl6enp8fIF8fH19fXx9fX15en6AfH9/gn15fH1+f3uBfXx/fn18f36Af39/fHt8f3x7foB+gn5/f35/fXyAf4CAgn9/f359e3+AdHiBf4J+fn18gH9/f3x/fnt6en1/fH+BfICAf32Ae3yBfX+AgX6AgoJ/gH+Cf4B+fn+DgwyAgYKAgoGAg4F/goGFgnCAg4SAgH+BgoB/gIWDgYSChYODg4aGhYSEg4WEgIGChISGg4ODhoODhoSDhYaCfn6DgIGEf3+AgX+BgIB/f4ODhISCgYWAgYKBgYGDfX+BgYB/gX+DhYCFh4SFgoOEgYCCgoKBg4OCgoOGh4WFgoSFhIaAhIGCg4OEg4SDhYSDhISEg36Ag4KGg4GFhoWEg4WBgYN/goOFhYOCgn+AhIGCgISDgoKEgYGCgoSCg4GDg4OBf4SDgYOBg4GDgoOAgYiHgoSBfoKDgYOBfICChYKBgIGDgICEf4KDg4CGgoSGgoSEh4aEgYCCgoJ/g4N/fH6AenuAcnR9gICCgX18dnVzdXZ6fH6CfX+Cf35/goCBg4SCgoGCfoOFgYWDgoSFhIaEhIeEgIODhn+Ag4CCgn6BgYaDhYGAfHV1eX17f4GAf4B+f4F7f3x+gH5/f3l+gIF9fX5/fn2CfHyAf32Cf35/fHx9dnt/fHl3bnZ3dGNpcHFx3c971dzbbb+puKjEcnFwcGlvcXFuym1na2V1d3Z8fHd8gnx6e3h3d3l6fX55eHp2fXx4fH5/dHyBeHx5end9e3h6enp4f317e3l+f31/fX56gH17eYJ7enx+fYB+dnx7f3d7gHt5fnx8fXl8eHZ8eXl7eXl+fHt5eX15enZ5/3//f/9//3//f/9//3+Kf4V+AX+Ffol/AX7ofwICBACAk5mWlZacl5icmZeZm5iWnJSYkJKTlpaXl5yTl5SVlpiSkJ6WkZmdnJmamJeZlpqTl5eak5SYnpqXmZWUmZONlZSVlZKXmI+Tl5aQnZqUlJiTk5CSnJOViIqOlI+QlI+PkZGRk5KNlJaMjJOTkJWMlJGTk4qTjYuKhYWLjZKQkZGAhomJjZCMi4+RjoqNj4qLioyQj4yQkIyTkZOQk4mCiIyQjouRlaSTkZCXkZCNkY+NkIqNkIuTiZCNjJCRio6KiYuKiJCPiYuJiYmKkIiQiIiOj42Mi4yKj5CQiomMiYqOjo6QkIyKjY+MkIyOj4+Oj4uNjY+Li4+Mjo6NjpKMj5KAjo6TjY6QjYyTkJOLjJCLkY6QkZCPkY+OlpCQkIuSkpKLjI+SkJKTlJGSlpiVk5aRkZmUlpGRk46SkJOUkY2Nj46SjY6PlJGSk5SRlJORjIyTlJSTjZCVlpSTk5WSkZORkJWQkpKQlY+TkpCVlJaQj5GRjI+MkpWUko+OipCUj5KAlI+RlZGTkpWTlJKQkpGTlpSUkpWTlpWTkpWWlIyampeYl5SVkpiWlpiYmJeWlJiYlJOXlJeXmZeYlpqXlZiXmJmWl5OWlJOXlpOXnJWUl5WXlZOTlpiWmJiYlZWVl5SYmZeZnJaWmZqYlpKUmZiam5eYmJuZlpqemJWYk5SZlpd1l5mUm5qamJidm5eZm5qbnZqZm5uanpybmZucnJ+cmqCfnJudmZqdnJuemZmfnJ2cmZuenJyanJudn5yenJ2enZqcnJyfmpyZm5mZnJmbmZyamJqbnJ2dm5ycmpqdnJ6fnZycnZ6dnJ+cnJqboKCgnZ+fnZ6ehZ2Anp2ampqcoZ2goJydnZiam52cnJuempicmJudnpqamZubnJuamZ+gnaCgoJucm5qbmp2bnpqbn5uZnqCdnp6dnZybnZycmpyYnJqZnZ6aoKCcmpifoJ6enZ+dnZyanpyhoZ2cnJybmZ6ZnJyenJuampyclpyinpyioJ+enqqqoZqAmqGZo7CgoJudn5+bj4CVmpmclJeYlpaepZ+Zn6CbnpyanqCanZ2cnp+gopmdnp2emZyaoJ2cm5ybnqChnp+dnJicoKGhn6Kfn6CemJWZmZqXm5ubnJ+fm5yamJadmJuVmJicoJ2XmJWWmZaRkpmWmZWWlJuYk5WRkIuImJqcm5tilJafl4n9gYD3+fv79v+E4sCBp/+GiIP1jIuKjvXa2uSAjJWTm5mUkZSVk5CQl5SUkY+Vk5KPjo+TlZeSkJCQlZOUl46QkpSWl5SUlZObmYiTlpWXlZeWlJSXlZWZmZmXl5WEkyCVlJaZj5OVlZKTjZOTko+OkZCQkI2TlpKUmpWZmJaYloCHjYiIiY6Lio2MiYuMjIqNioqFiIqMi4yKj4iLiomLjIeGkoqJj5CPj4+MiYuIj4eNipCJio2QkI2NiYqPi4aMi4yLh4yNgoiLjIaSjoiKi4uLh4iQhYyEgYOJhYeJhoSBhIeIgoGIioOCiIiEjIGIiIiJgomDg4GAgYWGh4SFiIB/f4OFiIKDhIiDgYSDf4GCg4qIhIeGgoiIiYaKg3qDhoqHg4aJl4mKioyHh4WJhYOHhIaIhYmAhoWEiIiEhoKCg4OBh4OChIOCgoOIgIaBgIeGhYWEhYOEhYWCg4OBgoSFhoeEhIKFhoSGgoaGhYODg4SEhYWChYOEhYOIioSFh4CFhYmFhYiFhIqHiYOFiIaJh4iIiIeJiIeMioeKhouKi4WGiIqIiYqLioyLkI2Ki4aKlY2NhoaLhoqHi4uIiIOGhoeChoaKiYmIi4mJioaGhYqJioqGiIqMi4qJiYmIiYaHjIWKhoSKhoiIhomJiIiGiYuDiIaLjIyJiIWBhoqHiICMh4eKh4yGjY2Pi4iKiIqOjY2KjouNjIuMj46Mgo+PjY2OiY6LjYyLjIuLjouKjYyNi4+KjY6Oi4yKj4yMj4yNj4uOio6Ni46Mio6SjouPjY6MiYqLjoqNj46JiouKiIyOj46QjIuNkJCNi46Sjo6RjpGNjo+Nj5KOjZGLipCOjl+Nj4uQkpCMjpSSjo+Rk5KTkZCSkZKUkpGRkpKSlpKQlpaTkpWSkZSRkZaRkJOQkpGQkpSTko+Uk5OWkpSSk5KTkpKQkJOQk5GSj4+SkJGPkZKQkZGSkZKSk5CSkpSUlISTBZSXlJSWhJKAlpWXk5aXlJSYlpKTlJSUk5STkpOXlJmYlpOUkJKVlpSSkZWSkJSRkpSRkJCRkpKTk5KSl5SUlJWWkJKSkY6Pk5GSkZKVkYySk5GTk5KSk5KVkpKRkY6RkpOSk5KUlJGRjpOVk5OUlJKSk4+UkJaXmJWTkZKQko+TkZSUkpKRj5GAjJCXlJGXmZWSlJqalI6OlI+Yn5OVj5CUlI+He4yOjI+KiouJiY6Uk5KTlJGSkI6Sko6Sk5OTlJWXjpKUkpKRlZKXk5KSkpOUl5aVlZSUkJKVlZaWmJeUlZWSkI+QkpCTk5WTk5aUlpGPj5SRko6RkZKVkpCPjo+QjYuNk42SjY+AjJOQjI6LiIWDkJGUkJGKi5CFffF7e+/w8fLz/H7VtXaZ7Hx/euaBgX+A2cLEzneBiYiOj4qFiImIhoaLi4qIh4qIiIaIiIqLjImJhoaMiIuKhoeJiouOjYuMiZKNgYqMjI+Kj46LjpGMiY2Oj42NiIiJi4uMiY2Qh4eNjIqLhIsUioiGhIiFhoiFiI2HiI6IioqKjYyAeHp0dnd7enZ7enh6end6eXl8dHN2enh5en11eHh0eHt2dX94ent+fHx6eXd4en55eHl7d3l6fXt5e3d4fnx6e315eXZ6eXFzeXlyfXt2eHh6fHV2e3J8d3VyeXZ2eHd2cHd2dXRzdXh0c3Z5dnlzeXl3enR9d3Z1dHZ8eHVydHeAdHF1dHpzdnR5dXV2dHN2dXV7eXh2d3J4enh1eHFueHh7eXV0dH96eHl5dXh1e3N0eHV3dnl3cHR1dXp5dnl2dXR3dXdydHd4d3h2eXZ7dnV5dnh4c3Z4cnd4dnV1dnd1d3l6dXd2eHh0dXV4d3dzdXZzdnh3dXZ0dXV0end2dneAd3Z3dnV7d3V4eXx0d3l2enp5ent3eXp7fXx7fHh9fHx2eHp8ent6fHl+fX58e3p1eot+fXl2enh5eHl7eXl2eXl6dnp4fn17e3l3e3t2dnd8eXl8d3l5e3t9e3l7eXp4eX13enh2d3Z5e3Z4eHd5dnh8c3l6fX5/fnx3dXh8eHiAfHt5enh8dXh+g3t5fHx6gYGCfn56gHx7foF/fnmAfX58f3x+fH19eHx5foB/fX6AgIB+fH2BgH18fYF9foF/f4B9fn2AfX1/fXx8fn9+gX6BgHt8fH99gIF+e39+eXp8fX98f31+gYGCgHx9gH5/goCCgH9/f4CDf36CgH6CgYNHgIB9g4GBfYCFgX+BgIKEgoGChYODg4KBgYKDgoaCgYiHhIKFhIKEgYGFgoGFgIOFgYWGg4OAh4SDhoKGhoSDhIODhIGEgYSEgICBgYF+f4B+gX9+gYKAgoKAgYKFg4WEg4KBg4KEg4B+goGFgIWDhIOBhIKChYSDg4eDgYSEhYWFhoeGg4aEgoOHhoWChYCAg4KDhIOAgIGIg4GDhIOGhISDhoZ/g4ODgICEgoKDgoSBf4KAfoSEgH+Bf4F+gYGBfIGDgn+AgIKBgB19f4ODg4GEhoF/gX1+f4KDgoKCfn6AgX+Cg4OCf4Z+aIOAg4WHh4ODg358fHuAf4KHgIB8foCCfHpvenp3eHR0dXR4dnl+f3+Bf4F/f4KAf4CCgYGCgoV/gYKBgYCEgYWBgoODhYWHhYSFg4KAgoSBgoKHhISDhIJ/e4CCg4CEgoCCg4GCgH9/hYBFfnt/fn97fX19fnx9gHt/foB+gYB9fn13d3h9fn99fnl4d2pr3G5v2Nrb4uDkbr6eZoXNbnFtznFwcG+1pqmyaXV7eHp9hHdcdnh2e3p5eXp8e3l2d3l5eXp6dnl3enl4eHZ4enl6fHx8fXiCeHR6e31+dX19en1/eXh9e3x8fHh4e3x5fHh5fXd3fHt5enV6enl5dnt4eHl3d354eX52e3p4fHn/f/9//3//f/9//3//f4Z/A35/f4Z+Cn9+fn9+fn9/f36Ef4R+5n8CAgQAgJKVm5eSmZWYlpmYl5mWm5uYlZKPjJeZk5eQlpGSnJiTkJGapZyYl5SXlJmZl56Yk5OWlJeYmJ6ampKTmJWWlJKWk5KUlIaRkJGXmJaRlJaYk5eTk4uJgY2Sj4+OkJORlIuQkI+TlpWVkoyElY+MkZOUkZGOj5CQjoyOjI+NjJGRgIqMiY6NioyQkY6OjoyJio6JkJKKjZGOipSOkJmNiI2Sk5OPkqWglpSQjI+KiIuKj46NiYyOjZGTi4mPiZCMjoyMkIiJj4+Lh4iNjI2OiYyMkI2Ki4uLiIyPjIyOjI+MkI6NjIqJiIuMkZCPkZKOiZCKio+Pi46NkI2OjI6Lio6OgJGNjpGPkpGMjZCOj4+NkJCSj46Ok5aTkJKPjY6Pko+QjJCTj4+TkJCPkZSWkpKOk5CRkpSTkJGUkZaSkJGMkI+SkY6RjpOSkJGYkZOSkI+PkI+Mk5aMk5SSj4ycj42Nk5CUlI6Vj5CTkpOTlJWSjpORkJGRkZSSkZSQkpKTk5OUgJKUkJOPlJiTkZKUkZOOj5SQj5GNkZWTk5ORlJiYl5aXlpOUlpSXlJiWmpqUlpOalpuYm5WTmZWYmJeamZiYlpKWmZWUlZaWlZqamZeUlJSVlZOWl5KRlJiWlpWYmJSWlpeZlpeVmJeXlpWUlJOWmpeVlpWYmJiZmpqanJyamJeVeJKWl5mWm5uYmZmZmpiYmZyampycnZubmJmamZubmp2cnJuam5qenZ2dn5udnJuanpyamp6ampuanZ6cm52dnJ6em52cnZudmpuYm5eXmJubnZ6dnZudnJ2bm5qcnJ2fo56ao5ucn5+eoJucmZqbnZ+cm5uempubmoScY5uenJ6dnp2enZ2cnJudnJmYmZygnZmanp+amp6cmZmbnJubmZ2coJ6fmZmenaCenpuaoJyZm5uWmp6ZnJ+enJ+dm52cnZqZmZqdoJuXoZ6YnJ2eoqCcnpyfnZ+boaGgm5uanoSdgJmenJ+fnpmbm5+bnJ6gm5udnam3lZWUjZCenaerqKakoqOhm42YmpeZmZSZlJqko5ucoZ2cnp+enJyfn5uenZydop2gnKKfn56bm52foKCboZedmpycmpqem5ugn56goJ2fn6Oel5ybm52bm52bmZWYnZyTj5CQhfKhpZ6cmJiVS5WYk5GUkZKSl5eTk5SSkoyFhpSYmJ+boZ6sk4eDgP/97u/594K/xNzs6K76/uOLjIHZ2Nv+io6RkJCUlZaXkpCVkpeSk5OWkZKRkISYTpWYlZOTl5mYmpeYkZmWlpiWl5uQlZONmJqXmZeWk5aVkpSWmZeYlJqYlZORkZKQlpaUlpWYk5WTlpaUmZKUkJSWlZWUkZKSnJmVkZabmICIiY6Kho2Hi4mNjIyKiI+OjIqHhoKNjoiLhouHh46MiIWIjpaPjIuIjImPjYyPjYiJiYyNj42Tj5CKiouMjo2JjIyIiot8h4aJjY2MhYqHioqOh4iEgXuGioSChYeIhomAhoaFh4iKh4WAeouEhIeJiImJhYSHh4WFh4aCg4KEiICCg4CGhYKAhoWEhoSCgIKEgYeIgoaGhoKIhIWPhn+HiYiJhYmUk4uLiIKFg4KDhIaGhoSGh4WGiYSBh4GHhImFhoV9hIeFhIGBh4OFhYGDgoaGgYGDhH+EhoOGhoSGgoiHhISBf4CCgoeGhoWHhICHgoCGhoKCgomDhIOGhISGhk2FhYWIhomJhoeJhoeEhYeHiYeHiIqOi4qMh4aHiYyIioeIjIiHjIiJi4qLj4qLiY2KiImKioeGiIaLioaGhYaFiYuFiIWKiYiIjImJiYSIgIWCiIuFioiJh4SMhoWFjIiJiomNiImKiIqJioiGhomHh4iIioqLi4uIi4uOjImMi42HjIaLjYuJi42LioaHjImJi4eIi4iIioiJjY6Ni4yMioyNiI2JjY2PkYyMi42LjouQiYiNiIyNioyOjo+LiIuPjIuOjYyKj4+QjoyLi4yMGYqMjIqIio+MjY2PjYyMjI2PjI2Ljo+NjY6EjDGOjIyPjo+Oj46RkZKSkI2Ojo6LjY+Qj5KQjo6Oj5GRkJGSkZOTlJWUlZCQkZGQkpKThJItkZCTlJSUl5KSkJGRk5WQj5SQkJOSk5SSkZOTkpOTj5KRkpKTj5GPko2QkJGShJMukJWTk5GSkJCOk5WXlpKXkpOWlpOXlZSRk5CSlJKTk5aUlJOVlpWVlZaWlJWVmISVMZKTkpOSj4+RkZaSkJCTlJKSlZGQkJKTk5STlJGUk5SPj5STlZOTkpGVkpCSkYyPko6Ek4CUk5GTlZOQj46Rk5WUjZSSkJKSk5aVkZOVlZGTkpSVk5KPkJOUk5KVkJWRlZWWkY+NlJGSkpSSkpWTl5yGiYqGi5SUm5yampmZmZiShYuNjY2OiZCLj5KSj5KVk5OVlJSTkpSVkJOSkZKZlJWTl5OUlZKSkpSUlY+Xj5WRlZSRkYCSkJGVlpOVlZOVlJWUjJKTk5SUlZOTkI6SlJOKh4qKgfCVl5OTk4+PkY+LiouKjIqPjouMjIuLh4CDi5CNko2VkpqAfX179fLl5Ozxf7S1z+PZoe3w1oF+dsi9xOmBhIeHhYqJi42GiIqIjIaLiomIh4WGjI2OjYmMioiKi4+NkUWKi4iPjYyOjo6RhoyMh4+Rj5CNjYyOjYyLjI+OkImUjYuLiYmKhoyNi4qKjomLiYuKioyFiIaKioqNi4iIiJCLiYeLkI2Ad3Z9eHR7dnp4e3x6eHd9fHl4dXRyenh3eHR3dnZ8eXR3dnx/fHp6d3l4end4e3t2enh4e3p4fHt/eHd1eHx8e318dnl7dHp3eX15enR7d3p5e3V3dXZwd3p1dHV2enh3cXd2dnl4eXZ0cW53cnV4e3t6e3l4e3d2ent3cHVyb3eAdHRwdXRycXVycnl0cXFzdHB1eXR2enl0eHd2f3VxeXZ1d3R6eXx2eXhzd3ZzdHZ3dnVzd3d2dnl0c3d2enV8eHd6eHZ5dnh1dHl4dnd0enV2dnJzdHdydHd0eXV0eXZ6eXV1cnFxdXN3dnZ2d3Nxd3Jxd3d1c3R6c3V3dnd3enaAdXd2eHh7e3h7enh3d3Z5ent5d3p6f3t9fXx7eXh9ent4d3t6eHt6eXp5e314enp9enV1eXt4cnl3fHp5enl3d3t9eX15fHx8enx7e3p5eHd2dXJ6f3d5e314dnZ8d3l6eHl5dnp4eXp4eHd5d3V1end4eXp5ent+fXt9foB/e32AfH96fXd7fH17fXl9e3p5fXp6fnh4fHt6fHl6foF+en59e4B/eoB7f35+g319fn56fXyBe3p/fH1+eXt+fn59eX6AgH1/fn96f4KBf3x9fn6Af35+enh9fH1+fYCAfn6AgYJ8fXp/gIGAf31+fX6Afn+DgH99gH+CgYKBgX+AgYB8gX6AgICCfn5+f4B/gICCgoGDgoSFhYeBgYB/fYGDhoWChIOBgYGCg4SFgoOCgYKGiIOBhYCAhIODhYKEhYWDhoWAg4SFg4SBgoJ/e4J/gYODg4KCf4SBgn+Bgn99goKEhIGIgICEhISFgoF/gn2ChYKBg4WDhYSFhoeGhoSFgIOGhoWHhISEgoOEf4GCg4eCgX+GhYKChYOBgYKDg4KAg4GEhIN+f4SEg4OCgYOEgH+BgHx/gYCEg4GAgn99goGDgH6Cg4OFgYCCgX+Af4CFg4KFhIN/gYODg39+f4GDg4KBhH+GgYKAg36AfYN+gYKEgoKHhoF4cnh4dniEgoaFgIOEg4OHhIB1eXl4eXx1fnx4d3Z9gYKAgYOChISDhIWAg4GAgoeDhIKEgYOEgIGAgoOFgYiAg36EgoKCgYB9goN+g4WDhYKDhH+CgIGEg4KCgYB+gH+AeHd6enXcfoCAf35+f358enp9enx7fn56fX98e3t3dnt+en55gHx9aW5yTm/k3tDM1dhzn5i1zbuPz9i7c25mr5qt0nR2eHp5eHl6end5d3d8dHt8eHl7eHh+fnx9eH17end6fXx+eHt6fnl6fn57f3V7f3t7fXx/eoV8LXp4fH57doB/fnt5enp1dnp5d3l8dnl4enx5fnZ6d3l7en97d3d4fHt4d3x8e/9//3//f/9//3//f+F/AX6lf4Z+AX+JfoN/hH7ofwICBACAk5WQlJSSlpOZl5Cam5eWmpmVko+Tk5CQmJWWlpaak56in5iTlp2WmJeZlpOXl5mVlZSXl56VlZKVkpWUjZSRkJKWmZeWlJeUlZiTk5GWmJCTlJGVi4+Qj46KlJCRkIuUmZWOkJKRkpGQjY2TlJKUkZGQkpCPjY6Pi5CQkI6SkI+AkoySk42SlI+Ok46Uh4qLlI+PlJGQjJKUlpGNioaJi46Ql5aXp5STlYuMi4mJjoyOjo6KiY2OjoyNj42Oi4uPho2LjI6RkIeQi46OjJCKjY+Nio6RjYeJjI+Kj46Qj42Li42MjIuIkJGRi42LiYiMjpCRiYiLjI6Mjo+NkI6Pjo6Aj4uOjJOJjIeOj4+PjY6LjJKNjo2RkZGTj4+PkY6TlJOSk5OUlJSRkZOPkZCUlpWTjpGSlZGW+4qQjo+UkIyOkpGOkZKVj4+Qj5SSk4yPjJGRkpOSkJOTj5KTkJSQiI+Sk5CPj5CQk5aRlY+Tk5GPlY+MkZmUlJCPkJKTkJSVk5WAkJSWkZSRkJWSk5OQkZGRkpKUkpCQkpqVl5KYkpGalJGWlZaYkpeWmJSZlZuYlpWYmJaYl5aZmZOXlp2al5iZlpSYk5SRmJKRmJaWkpeRk5eUlZaVk5WTk5WRmJeVk5aVlZmYkpOYlpeWk4+WlZKXmZmVlZaUlpuTlJWZl5mYl5Zml5aYlpSamZiXmpeWlZmampWWnKabmp6bnpqZmpqcnpmcmpidnJyanJqZm5uenZ6dnqGenJycm56fm5ygnJycn52YnJ2cnp2ZmJqbl5ebnJydn5uam5ybm5ibnJqdnp6cnpyZnZyehJ+Am5mbnpqcmpadnZ+dnZqbnZqcnJyem5+cmpqenJ6elp2bmpmbnJ2dnJucm52YmpuYmZyanJudnJmcnZ+bmp2cnpyZmJufnJuYmZycnJ2bnZyfop2gnqCcmpaOl56dmJuanJ6boZudnpuamqGbnpidn6Odmp6dmZyanpyenKGbnJotnpycnp2goJmdm6G4rpiXmpOHk52eoKGbqKCdm56fnJ6bmpuclZKTmqObnJ2dhKGAnZ2en6CinZ6emp+cnKKgnZiYnZ6hnJugnZuZn5+bnZqdpJ+dnZ+en6Cdn6GfnJybmZublpaampidoZ6VkpmUjICWmZWRkZmTkpeZlpSYkZWTlJOUj5CQhveWkpOZnaOipK+OhomD/4D3/PODgNnv5ubmzKippMjkvuGBh4qUlJVllJOWlpWhlZWbmpKVlJKTlJuak5SSmJmUpaGcop2ZlpmbmJWXmJmUlJabm5aalZOWkpmWnZSVlpOQlpOWm5mRlpGUk5OTkZGVl5eVkpKPjZiVk5KblJqRk5SRkJOYmJeTlpSXkpaAiIqFh4uIi4eLioePjouHjo2LiYaJiIeEjYqNjImMiJKTk42HjJGKio6OjImNjI6LiYmMjZOKi4mLh4yIhIyJh4iLj4yLi42KjIuIioeKjYWLi4eKg4aGhISAiYaHiIKJjIuGiIaFiImGgYOJh4iGhoeEhoaHhYWGgoeHhYOFg4aAiYKJiIGGh4OCiISJgIGEiYiGiYeIg4mNioeDgoCAhYeJjoyMlo2LjYWDhoODh4SGh4aEgoWGhoOFhoSIhIKIfoaChIaIhoCIg4aHgYaBhYWChISHhICCg4eBh4WIh4SBg4SDg4N/hYiHg4OBgICEhIaIgH+DhISDg4aFh4aJhoeAhIGFg4qEhoGJiYiIhYmFhYmGiIeJiYqLiouJioeKjI2LiYyMiouJioyIiYqLi4mKhomIiIeL6IGHhoaKh4WHi4mEhoiLhoiIh4yIiYOIhImHh4mJhYmJg4mKh4aHgIaHiYiHiIeGh4uHjIWIiYaFioODho2KjYmJiYiKioyMi4wMiYqMiYqKhY2Ji4yIhIofiYyJiYmHjoeNiY6JiI+KioyMjY6IjI2QjI6Nko2OjYSOSYuMkI6JkIiPjo2Pj4yKjYmKio2Jh4uJjIqMiIuMi4yMjImMioqNiZGOjYyOjY2PjYqMkI6Ojo2IjY2KkIyPjYqNjI2RjY6OkI2Fj4CLkY+NjpGPjZKPj42RkZCNj5OhkpKVkpOQkZGSkpOPkpGPkpKTkJGQkJCRlZSUlJWWlJGQkpGUlZOSlZCQk5WSj5GQjpGQj4+PkpCQkpOUkpWSkZCQk5GOk5SQkpOVkpORjpSTlZWUlJaUk5OTkZKTkZOTmJWZlZKUk5WUk5WTl4CUkZOVk5eUjZOSkpCSlZSUk5KSkJONkpKRkZOSlZCSkpGRkpWUkZSTlJGQkJKUk5KOjpGRkZKQkpSWl5OVlJiTkYyDjJWTjZKTlJORlJGSlJKQkJOPk4+SlZeWkJKSj5OQkpCSkpaQkpGTj5GTkpSTj5OQlaOcjo2QjH+Kj5CSkoCOl5OQkJWTkZGQkZGQjoiGiZSRlJOVmZaXlpSRlJWWlZKTkpCVkZOWlZOPjpOUl5OQlZOSkJKWkpWQkpeVlJOWk5SUlZWWlJKTlJOUlJGPk5KQlJKSjYuRjoZ+i42KiIeOiYmOkYyLjoqNiYuLjIeIioHzjouKj5OXlJWafn6CfH/wefP37X55zOjd3NvAnp2XucqnynZ/gYqJiomIjImJlIaNj42Ki4qHiouPi4iMh42OipOTkJGQjoqNkI6Li4yNi4qMkJCLkYyLkIqPjZKJjo2Lh4yLj5GQiYuJjo6JiouKjY+Qi4iJhoaNiYyJj4uQh4mNiYaJjouMh4yKjImMgHl5dHJ3dXh1dnt4fX96eHt6e3lzeHp5dn15e3Z2e3Z8gHx6eHZ7eXp8enZ4enp8eHd3eXp9eHd3dnJ4dXJ6eXp8fH58fHl+dXt8eXh3e3x0e3l4enZ6eHR0bnd2dnt1d3l8dnh6dnd9d3F1dXR2eHV5d3d4e3d6eHZ4eXdzdHB0gHtyd3hzd3h0cnp0eHN1dnp3dXt3eHV5fHd1c3d1c3d1eHh4e3x7dnt3dXl4dnR0dnl4dnN1eHd1dXZ3fHh0d3B4cnh7e3h0eXB2e3Z5dXl2d3Z2eXZycnZ1dHp2enl6dnZ1dnV1cXh4eXZ2dHFydnZ1eXRydHl2d3V0c3V1end7gHZ2eHR+dnl1enh3enh8d3Z+ent4eHh7fHx6enx5e35+e3p7e3x6e3h8eXh7fH16eXh6e3p7fNZ0eXd3enh1eHt5dnd5e3h7eXh9en12e3d6eHl7fHh6eHJ6fXx2e3V6eXl4eXd3dXp8d3x1dHZ2c3ZxdXh6eXx5e3t7gH5+gH19gHx9gHp5enmAfnx5d397e3x7fHl6fnh7d358gHp6gXt6fXyBgHp9fYF7fX1/fH57fH5+f3x9fnx7gHh+fn+AgH5+f3x/gH59ent8fXt9e35+fX57f3t9fX1+eX+AfX+Af36Dgnp7fnx8fn19f39/g3uBfnt+fH6Cf4GBgn+BgYOCgIN/goCAgoKAf4SAf35/f4F/g4KSg4CDhYSCgX2Bg4OAgYF+gIKCgYODgYSFiISFhYOGgoB/g4KDhYWEhIGCg4WDgICDgYGCgYCAg4OAgYKEg4SChIB/goGCg4SAgoOEhIKBfoSEgYSEhYOCgoODgn+BgoOAhoKJhIKEgIaEhIeEgIWBgISEg4WAfoODhYGDhIODgoKAgYV/goOCg4SDhn+AgYKCgYKFgYKCg4B/goOCgoJ9fYCAgoF/goGGh4KBgoWDgX12gIODfn+AgIB/gX6AhIODgYJ9goGDhIaDfoCBfIF/gX5/f4J7foCCfoCDgoSDgYWAh4iCf3x+e257e3t/SYB5en9/foGBf4CCf397gHpycYCBg36Dh4SGhISCgISEg4OEg4KGgoODgoJ+fIGDhYN/g4GBgIGGgIF+gYKBgoKEgoWDhIKBf4CEg4CFgn+AgX+Be3t8fIN/d3R3eXl0b3t6enyBfXt/fH15ent9ent+d+B7enh+f4KAgHxrcHVx2G7f4d5warbZzMTCro+LgZuki7JodHV5eXl3dnp4eYFzfn17eXZ3eHt8gHl2fnp6enl3fn93enp5eXt9e3Z3enh5fH58eX58eX17fDN7gnl8e3h5eXp9f395eXx+f3t4e3p6e356eHh5d3t6enZ8eX15ent5eHp8dnt3eXp7dnj/f/9/uH8Bfv9//3//f/9/wH8Bfo1/B35/fn5+f3+Nfut/AgIEAICUk5SUl5eZlZmVmpuRmpaXmZKUjpOSlZKXlp2XlZOVkI+XnZWVmZaenpuWh5eYl5OYk5aRlZKck5eTlZWSlpiLnJeSlJmWlZGSm4+WlpOOjo6SmJWUk5KPjJORkZGSk5OPjISMkoyako+MlY2Rj5OUkIqQkY+Rj42Jj4yQkpKQjoCRkpKQlJCRlZGQk4yOkY+RjY2PjpaQjo2Qj46SiPyJlI6SlZigkI2OjomGh4iHi5CSjIuOjI6MiYuMh4SCjYmPiIyNi4qMiouNjoyGi4iPjYuOi4yLjo+KhouLjYuKioqPjo6Oi42Nj4qKi4uLjI2Qj4yOko2Njo6PkIqRi4+QjoCNkJGSi5OOkJGSk46QkZGMjI6TjpKQjI6NkpCMkJWSj42Tk5GTj5GPkJKRkJGOjpCRk5KNkJWOjZKVk5GQkI+Qko+OkZKTjZGPjJSMkJCPj5ORkpGUkZSVk5KRj5CRj5OTjZOQkJGUjpOQkZWUlY6ZlJGSkpSTkJCOk5KSjpOSkGaTlZSVk5OSj5KSlJSRj4+TkJaTkpKakpePl5eUlJaVkJWUk5OUlJWUlZuVk5mZmJeThpeWlpuYkJGak5qZlZiYmpiVkpWVkpSUl5WVl5aUmJaZlpOWlJKQmJaTlZiXl5mWmJOTmJaEl4CVlJaUkZSZlpeWlpSWlJWVl5qYl5mYlpqVmpicl5eYl5aXmJiZnJuampudnpuZnJubmZiYmpqZn5ybnJyZn5ycnZ2gnp6amp6dm5ycmpmdnZydmqCdnJmZmJmcnpqXmZeYmZuanJ2en56fl5yam5ubmJibm5yeoJ6enpydnZ6fny6fm56enZqbnJucn5+emZuamZ6cnaCfn56ZnZyanaCbm5uZl52bm5qamZ6bmZ2chpqAoJyanZuanZqdn5yenZubl52enZucmZqbmpudm5qdnaGfn52dnJ+bnJ6dnpugoJ6cnZmam52dn52cnJ2fnJqZnqGfnp2enZuhnJ+bmZmanJ2dmJmTlIeYiu7yrJWajJSTm6GknZ6hnJ6cnp2clpadnJiTmqmhnp2coJ2bn52dn54OnqGdnZyenZ6inp2fn5uEn4CYnJ2dop2cnqChmaChoJ2bo6Oin5ucnJqdn52goZuZm5qcm5SjoJuYm5qNjZWTlpWWm5SZlZaVl5eYl5OYmZKSkIyG6PKDjpKSmZenu5eNjImDg4CB/IeAhImHhob4/Pr0+YSMkZGTk5mRl5WXmZuOiZqWmJybm5mclZadl5qbmlCdmIyUmJCSi4iNkJOTkpaZnJeWk5eamZyUmJaYlJSYmo+XlZWQlpOVlJeTkZOYkpSVkJKXlZWUmJSWmpiWk5aWk5WWk5aMkJSWk5OVlZSWlVqHiIiKi4yOio6LjpCHjomMjIWJg4mHjIaLjJSNiYmJhYWLj4qMjYqQk46NgI6Ni4aNiY2HjIiOiY6IiomIi4mDk46JjI+KioaIkIWKiIeFhISKjImLioiGhYiFh4CIhoR/hIeBi4eHhYqDhoaFiIaBhYiGhoeEgIiEhoeHh4WFiImGiIWIioaGiIKEiIaDh4SGg4yGh4SHh4aJgO2AiYSFio6Rh4WGhYGAgIOAhYeIgoOGg4eCgIODgX18iIKEgYODhYKEhIaEhYOAgoKFhIKEg4WEiIqEfIKEh4KDggmChYSFh4OGhIWEgoCDgoOFhIKEhoOEg4CGiIOHgoaIh4SGiImDiIWGiImKhoaIiYaHh4qGi4iFh4iMiISJjYqIh4uMiYqGi4eIioiKiYSHhoaMiISJi4SGiImJhoeGhYmJh4aJiYqEh4SDiYOJh4iHiIiGh4uHiIqHiYiEhoaEh4iHiYiGh4mFhoaGi4CHiYaQiouJioqJh4qJi4eIhouIh4uMi4uKi4qGiIqLiYmGh4uJjYqKh5CJj4aOj42NjYyHjIuKiIqJiIyMkYuKjY6NjIh4jo2Kj4qHipCKj5CKjY+QjoqHioyHi4uOjIyOjImNjo6MioyJiomRjIyKkY+NjoyNi4uMi46Qjo2Lix2Mi4qKjo2QkI6MjouNjI6Rjo2Qj46PjZKOkY6OkISPgJCOkY+RkJGUl5SUkI6QjY+OkZKQk5OPkpOOlJSSkpSVlZOQkpSRkpOTkJCUk5GRkJeUlI+QkI+Rk46Nj46PkJORkpKTlJOVkJKQkZKVlJOSkZKTlpWSk5SVlZOUlJaSlJaTkJGSk5KXlZSTlJGSlZWUlpSWlpCTlJCTlZOSkpGNgJORkZKSkZOSkJKRkpSQkZGRlZOSlJKRlpOUl5KVlZGSjZKVk5CQj4+QkJKTkpKSkJeVlJOSkpSQkpKTkZKUk5SSk5GQkpOSlZOSkZKTkZKPkpSVlJKVlJCWkpWSkpWTlJSVkpKOjoGKcszWkoiMg4iHj5WZj5GUk5ORlJSRjY2QgJGPio2Yk5aUk5aWkZWUlJSTlJWSkZGTkJOXk5GTlZGUk5OVjpKUk5iTkpWYl4+UlpeVkpSUk5GQlJSQlJaUlpeTkpSUlJKLl5OUkJGRhoaMiouKiY2LjouNjY6Ojo+Kj5CLjIqHg+PmfYWJiY2KmKOGg4SDfX5/f/aCeYCDgoCBc/Lv7unqe4KFhomKjYeMjouMj4Z+jYuNj46OjY6Ni46Jjo+Oj46Cho+Ih4OBhIaJioeLjZGNjIqNkIyQio+Mjo2Nj4+IjYyHhouNjIuOioiLjYmKi4aKi4qNio+LjZCNi4mKi4iKjIqLg4iIioeHio2IjYwcd3x6dnp4eXd6eXx9d314enpydnJ3d3l3e3l+eYR4gHR7f3l5ent9gHp/dn95eHh8eXx1d3Z7eHp0dnh4fHdzf392e355eXd3fnV6dnV2dHF3end6eHh0cnR0eHd3dnZ2dHR0d3F4d3Z1eXN4dXh2dnR3dnZ2d3V0end3eHR3dXN2cnN5dXl2dXV1c3V4dXB2dXdxfHp5d3Z3d3hz1nJ7SHR0dHx7dHZ4dXRzdXNwdHl9dXV2dXpzcHh0cXBueXVzc3N0d3Z2eXZ3d3V0c3V3eHd5dXZzdnt2b3N1d3J3dnV2dHd5d3d1d4R0gHJ2dnd0dnd3dXZ4dHR2c3NzdXl3dXh5e3V4eXp7eHp5eHt7d3t5enp9e3l3eHt8eXx/e3p5e317e3l7eXl5eHp7d3p3d358eHt6eHd4eXt4e3l3eHp2eHt7fHh5d3d7d3x6eXl5enh8e3l6eXd5enZ4e3V6enh6eHZ4eXZ1dXR5gHV4eYF5enh4dnd1fHp7enh5fHl7fYB/fHt8fHh4eXt5e3p5enl8e3x5gXmBent9fX9/f3h+fH18fnp6fXyCfn19f316d25/e3t8e3p9gHmAgHt7gX2Ae3l9fnp+fn59foB+enx9gH98fXp6e4J9fYGCgYCBgYB9e319foF+fHt+eIB9fH2BfoB9fn2AfH98fYF+foF/f4B+hICBf39/gIKBgH5+gn+AgoSGhoWFgYCAfX98foKBg4J+hIN+hIWCgYWEg4J/gYaCg4SDgoCFhIKCgYmGhYCAgIKBgoF/g4GBgYKCgYODhYSGgYOCgoGDhIWCgYKEh4eGhoSDgISEhoSGhoKAhIWCgYaCg4OEg4SFhoOGhYWFgoSBgISIgoOEgnyDgoKAgIGBg4GDgIKCgYKDgoWDgoeEg4SEg4SBhYOBgX6Eg4KBf39+foGDg4CBgYCHhIODgYGFg3+BhH9/gYCDgYGAgYKEgISChISBg4KCgIGBgYJ/g4J+gH+CG36ChX9+f4KCgYCBd3lSnK91c3ZxdnZ9goN8eoSBJIWDgX17fH+AeXh8gIaEhIWDgIOChIKCg4WEgoGGfoSGhICCg4SBgISBgoODiIKCg4aGfYGChIJ9enp8fH6Eg4OEhoSFhYKBhICCg3l9en9+gIJ5dXx5end3d3p9fH19fIB+gHuAgHx+fXt30sRwdHd5eXV9g3B0d3ZzcnRz43RsdHh3d3bl493Z1G50eHZ6eXx2eXt4eHp2cHl7fHx9fnp8fHl8fH1/UX5/e3J0fXl4cnB0dXh4dXl6f3x9fH2Be398fnx+fH1+e3x8fHd5fX99e354dXp8eHh3enh6eX13fHl5e3t5eXZ5dXl7eXh1d3h2dnd7fXV5e/9/nn8Bfv9//3//f/9/3H+Cfvt/gn6QfwF+h3+Ffu5/AgIEAICPlZWSlJWWlJSSlpGYj5eUlJKPlpOSlZaWmJOTlpGWlI+ZmZuWlJSWlpylnp6Xl5eUmpuWkZuPlJORl5aYlZqYmZOUlJSVnZKWmJSVlZOPk5SSjZSLkpWTkpaWj5SUk4uNkpSWiZKUjY2Njo6QkZCTkpWVk42Ti4+RkZCNjo2RlICUj5SOkY6Qjo+Rj4+Qjo+UkI6Li4+Ni5ORiJKOiICIi5GRkZOTlJGOi4eFh42NjY6Li4+MiY6QkIuLkJKLjo2Kh4WJiYuLiYqKiouNjpCKhY6NjI2OjZCOjoqLjYqOh4qNio6LjIyLj4uJiY6NjI6Ki4aPjpGPi4yNj42OjY6Sk4CQjY+MlJGTkJKTjI6UkIqMkY2VjpCSjpGSkJGRlJGPjpKTjZSQkZKTkI6PiZKPkpCQkYyHjpeQkpOSlpKTjpGOjJOOko+RjZOOj5CRj5CRjpCB/ouVkY+Rk4+Sj42RjI2Wk5GRkpKRk5KTkpmYkZqZmZqWk5KNkpmQk4+QkJCUj2yRkJKQkpOTj5CRkpKQkY+VjpSWkpeXlJaUlZuZlpWSlJSYlpOZl5iWk5mYl5WYlpWVlo+WmZqYlJmYlZaai5iUlpSVlpSYlZaTlZaWkZSVlJSYkZWUmZaXlZWZlZqXlJWYlpaXmpeWlZSWlpaEl02Vl5iYl5SYmJWanJqWmJeYlJeWl5aamZmWl5iYl5yWmJqcnp2am5WalZqYmZqZmpqanpubnp+cmJqbm52in5yanJmcm5mXnJ6amZiamYSagJuamJialpibl5aamZiXlpuenZ2ampybnZ2ZmZuenp+cnZ2fnp+dmp6fnp2cnZiamZyamJ2cm52cmpybmpebm5uempubnJ+cm5yYm5WYlJmZmpyamJqZm5ibm5uYnZ2cmZ2ZmpqbnJybn52dnJuZm52enZiZmpqamZmenaCam5magJqcmZubnZ6dn6OamZydmZueoZydnZydn5+am6CampycnpucnJ6Zn5iYnJeanZ+dmJqXkJaWlZSalJWZmJGMj6mfopydnZ+bnZmcm5eanpWcm52an52dnp6hnZ6dnZ6goJ2cm5+en6GlnZqcnJmfnp2bnp6foZ+in56Zm52ajY2SgI2dmZ6YnJybnZuampebmpmdoJirnJibnJeWkpaUlIuNoJicmJqXmJeXmZWXl5WUjomE9vqQiY2Pkpigp6eHiImCgID7h4qPkZKPkJCPj4yOjJOQkZWWkpiYkZmVk5yfnZ6Zmp2Zm5Sbm5iXnZyZm5uVnKCfnpCQjpSRk5Cam5eZP5yYmZiUkJSbnJ6kjZiXmpiWlpaRkpORlpGOlZSPkI+ZlZSRkpWbmJWalZGVlpucnZOSk5WUk5OVk5KUkJKPjoCFioqHiIqLiImEioaNh4qJiIiEh4aIi4mLjYmLi4WKioWOjI6Mi4iMjJKYkJKMiouHj46NiZGFiYqJjYqLiY6MjomMiYuLkIiMj4qMiIiFiIeIhIiBhomKiYuKhIeIiIKEiIuKf4aJg4OEgoSGhoiLjIyJhoKIhIeDh4SFhYOEhkKJhoqDhoWGgoSGhYWEg4aJhYaEgoaDgoiGfomFgXmAgoeFiYuKioeGgYB/gYWGhoeEgoSEgISFh4SDhoeBhYWBf4GEg12EhIODgoaGiIOAh4WDhYSEh4SIg4OEgYaAgoOAhoSFhIKEgoGChoKEhIGCfYaDhoWBgISFhYOChYiMiYWGhIqIiIiKiYKFiYZ/hIiEioaHioiLioaHjIyKiYiKiYaEiICJh4aIgYmGiIeGiYN9ho6HjIqJioeJhYmGg4qGioiHgoqHhoeIhYaIh4d47ISMiImIiYaKhoSIg4OIiYaIiYiHioeHiYyLhY+Pi46Ji4qGipCGi4iIiIeKh4mKi4iKi4uHiYqKioiKh4qHi4yLjYuKjYqLkI2LjImMjI+Ki5GQji+OjI2Ljo2LjYeIjIaMjIyOio6Qi4qQg46KjYuKi4mNjI+Li42OiImMiouOiY2MjYSMgJCLkY6Li4+OjY2Oi4uMjo2MjIyLjIyOjYyMj4uOjouPkJGQkZCRjYuOj42Qj5GNjo+PjpGNkY+TlZGRlI6Si46PkI2OkI6OkpGSlZaRkJCTkpOWlJGQk5GUkpCNkpWRkJGTkI+QkJGRkI+PkI2PkI+NkI6PjY2SlJOSkI+RkZOUWpKSkpWVlpOVk5SSlZWQk5OUlJOWk5SSlJSQk5OTl5aRk5STkZSUkpWRkpOUl5OTk4+TjZCMkZKSk5COkY6RkJKTkY+SlJKRkpGRkpKUk5KVkpKSkY+RkpKSj4SRgI6Pk5CVkpGQkpCSj5CSkpSUlJWOkZSUkpWUlpSSkY+SkpORkZOTkZOTlpGQkZOQkZCRko+Tk5WTj4+NhomIgoKIiIeLi4aFiJ2Tl5OSkpWSko6Rjo2QlI2PjpKSlZOVlJSWk5SUk5SWlpOSkZSVlJaclI+RkZCUlZWTlpSVlpaWgJWTj5GTkoaEh4KQj5KQkpOUk5KTk5CTkpGUlI6bk4+RkpCNiIyJioWEj4uTjpCPkY2PkYyNj42NiISB8PCKhIaIi4uUlZN8f4J9fX7ygIGHiYuGh4aFhYKDgYmHh4mKh46Mho2Lio6QkJONjZCPkImOjY2Kj5OOkJGKkpSUkoSJSISMiouIj4+MjpGQkI+Jh4qQkpGTg46PjoyNjY2KiYyHjIaEjYyIh4SPiomHhoeQkIyOioeJjYyQkIqIiouLiYqMiYeKiYeGiIB0e3t0dXl6d3hzd3d+eHt5eXd2dHR0d3Z6fXd7eXR8enV6enp4dnh9e4eEe3x4fHp2dnl6d4B1eXl4enp9fHp5enp7e357fXd5fHl4dXZ2eHR2c3dxeHh4d3t6c3V3dnJ1end1bXZ4c3NydXd2d3p7fHt4d3N4dnlyd3R3dnVxcoB1eHdydXd4dXV4c3VzcnV4dXZ1c3dwc3h0b3t4c25ycnVycnV2d3d4dHJ0c3d2dnh1dHV2cnR2dXZ1d3lzdnVxc3Z2dXd1eHl3dnB5d3p3eHh3dHVzdnh0eHRzdHJ5cnR3cnZ3d3V1dHV4dXdzdnd0dHF1cXZ3cm90dXZzdXh1eQJ6eYR3gHh7fHhyd3p5cnd5eHx5eX17fHp6eYF+fHp7fHt4eXl7ent4eXlydnZ9eXh7dnF4gHl7fHp3eHx5e3p0fXp7enp0eHp3eHt4eHt1dmzVeH15fHp8eH15eH14eHZ6d3h4eXl6eHZ3enp3fn15fXd7eXV5fnh8ent6eHt7fn9+fX5/gH54e3x6e3d5eHd3ent/fnx6fXt/gHx9fnl9foJ8fYKAgIF+e3yAfn19enl8e358foB+f4R9eX54fX58ent9fH19gHx8fn97enx6fH97gH6AfX99fIB5f39+fYCBf4CBf359f4B9f319fn5+fXt7fn6CfnuAgIKDgYCBfn6Afn2AgICCfn+CgoGCfoB+f4KBgYOBhICAfoB7f4J8f4J/g4OFf4KFhYGBg4KAgoWBhIB/f4SGgoGBhIJ/gYOBf39/gIB+f4KAgIOBf4B/gIOEg4KCgYCDg4GCgYOEhoGEg4GBh4SAgIKFg4SEgYSEhoSBhYSDh4mBg4WDgoOEhoSCgoGCgIWFgoGAhH6BgoSChX99gIB+gn6DhIKAgIR/hIGBgYOCgoGAhIKBgH1/gICBgYCAgYGAfoCBgYSFgoB/foB/goKBg4J/g39+gYSBhIODgoOAgIGCgoF/goKAgoGAf31+f3x4eoCBfYKAhIJ+gX55dnVqcG50dnl5dHh9g39+f3+CgIOAgn5+fHt+gH97fHyChIGGhIODgoWGhISHiYOEhYKEg4WNgn6BgYKChIKBhoWDhYaDhIN9gICAdnJzcXx9f32Bg4WFgoWFgoSDgX9+eYKBfX+Dgn54f3t4dnZ9eoR+gYKAfH6BfH5+f4B6d3Xe1Xp4eX16dnt8eWxydHFyduJzenJ3en14e3Z5eHZ2c3p3eHl3eH14dXh2enl+en96fH1+f3h7e3h7fIF8goJ7foF/e3N7c3p4eHd8enp7fn59fnd3en2Af351fHt+e3x5fnt4f3t6enV6fHl3dHp3eHd5en1/eXp4dnh9eIB/enp5eXd1d3p4d3p4dnV1/3//f9R/AX7/f/9//3//f6R/gn6PfwF++38CAgQAgJWUkZKTk5eRipGckpKRk5ORj5CUkJOWmpmdmo6SlZSZlZiWkpWWmJmXl42IkZaWlYuMl5aVlpaYlJKSlZaXkIyQkpiYlpaSkJiUkI+OlJKRkpGNkJCUk5qUkZCQkJKRj4+VjYyLkZGVjo2RjpOPi5KRlZCVlI+Rl5CNkZeVkY+OQJWRk5GWlZSTkJGVi5KMko6OjoeNjo2Ujo+RjY2DgYeDhYyHhoSKjIuLiYiJjpGMiI+Oj46LkJeMjIqJiISKiYmGi4CMjoaKh5COi4yMjYuKjI+NjIyLjI+JjY+NioyQi4qQkIyOjZGLi4yLi4qOj4uLi5CKjoyLkpGLipCMlI6PjZKOkJCOjY6NjZGRjZKOkJCUjpCRk5CRkI+RkpCOj5ORj5aTkJGQk42UkJCSkpWRlZWPjYqRkJSXkI2Vk5CVkZGPkICSj5GRk5SSkpGRlI+RkpCPlJOOjZOOjJKSj5CUkJCTkY2TkZSQk5iXmJaYlZqVk5KSkJKWi5CRk5KVjpCRj5CRjZSUkpSRlJaTkpWVkpGTmpSWk5KVlJSVl5WWlJOTk5mQk5OWmpiRlJWVmpORlJOZlJGZmpmXlZSXlZaWlJOUl4CVmJKXmJeXmZeTlpuVmJWWlZaXl5mZmZWXlpWYmpqXmJqVjpOYl5WXl5iVlpaXmpqWmpuXmJebl5iamJiYlpeamZaWlpqal5qXnaCcmJaXmZydmZeYnpyZmpuXm5ycnZuZmpubmZyeopmYnp+cnpqYmp2ZmJ2dmJ6cmpefm5qXl4Cam5yWlpyZmJycl5ubmpqYmZuenZacmZmdnpuamZqbm52YmaGanZubnKCenpuZn5+amJucnZ+dnZqZnpuboZudnJybnpuemJWUmZyXl5qamZmcmpuampubmp2anZqdm5meoaGfnJecn5iboKCampycnpqdm5ubmpucnJyZmpybloCfn5+ipJ6YnZucm5ybnZqenqKfmZqjmpibnp6anpufnJ6knJubnJidm5udnJuVmqHBnJyZmJaTlIyLoaWelZeanZqbmY+an56cmJqXnaGfmpycnZyfoKCdn52gnZ6eoZ2Yn5ycnJqdnZ6bmpuenqCfn5+gmZiam5mRn5yZn5uenoCcnZ6gnJ6amJqVlZ6cn6Whm5efmJ2YlZGUj5qbn52YlZuXlpeVmJSSlJOPjob7+5GNjY2Wopyep6iUjI+Ej5OTlZiXmJiUlI+Vk5WPkpOXmZSXl5igmpeZnJqcnJqdm5mXmJealpSbnZqcnZuWm6GgkpKTk5eWl5ecnJ2cl5qXmDqYlZmZkJCal5GZlJSPlZOXkZSXmJOUk5GWkpeSk5SUkJiYjpeOl5iUn5iZlJGVl5KWlZiQmZOTkJSRgIqHiYmJiIyHgIaPhoeGiYiKiYWKiYqLjoqOjYaHiIqPi46Nh4qMjY2NjoWAiIyJioSCjY2JjI6PjIuKjIuMiISGh5CLjYyHh42IhYaGioiHhYSEhoaKh4+IhoaHhYWGh4KIhoKCh4aIg4WFhIiGg4eGioaJiIOGjIOEh4uMh4aDP4iFioeIh4iIhoeLgoiBi4WGhIGFh4WJhYaFgYN+fIJ+foB9gX+Dg4OFhIOCh4qEfYSDhYaCh42Cg4OCf3iDhISFRoSEhYWHf4KBiYWDhIaFhIOEh4aEhYSDhoGEhYSDhYeFg4iHg4SDiIGCg4CDgoWEg4KBh4OFgoGGiYOBhYKJhoiEh4aGhYOEhYCGh4WKhYiFi4eHiIqHioqIiIqJiImLh4eNjIeGiIyGiomHiYmMhYqMh4OCh4SHjoiFi4mGi4iIiYiJhImJiYiKiIeGiYWGiIeHioqEg4qGhIiGiIeIh4mKh4eJh4mGiYuKi4iIiYqIiIqKh4eMgoaIiYiMhYmKiomKhouLioyKjICLiYmOjYqLjJCMjIqJjIqMjI6Ni4qJiYmPh4iKjI2Nh4mKi4mJiomLjoqGjY6NjIyOjYyLioyJi42MjYiOjouOj46KjZGMj4yMio2OjZCPkYyNi4yPkJCNjJCNiIuNjouNjI+MjI6Njo2LkI6NjouQjY6OjYuOj46RjouOjo+RjoCSjJGUkpCOj46Rko+NjZGRjZCRkJGRlJORj5OVlJGQk5aOkJOVk5SRjpGVkI+Tko2TkZGNk5KQjIyOkJGOjY+PjpCTjpKRkZGOjpCRkYuTkZCTkpGTkJCRkpSPkJaPl5KTlJSUl5SPlZeTkZWVlpiWlJKSlpKTl5KVkpGSlZSXkWGOjZGUkI6TkJGSkpCRj5GSkZGUkpGRkpCRkpaVlI+NkJSOkZWUkI+SkJSQkZCPkJCSkpORj5CSkZCUk5SWlZSRkpCTkZKSkpGVkZSTj5KXkJGSk5STkY2Uk5aXk5OUk5CQhJKAj4uSlaWMjo6OjIiLhoeTmZaMkJKRj5GQiIqOkZCOkY2QlJWSlJSUkpWXlZOUk5aTlpOYlI2UlJOTkZGSlZSQlJWUlZeWlJeRko6PkoqTkI+UkpSVlJSVlpWUk5GQiI6TkpSYlpGOlZCSjoyIi4iQkJSTjY6Ujo2PjY+MjI6NiIiAg/f0i4WGhoyVkJCUloiCh4CIioiJjY2OjImJhIqKi4SHiYuMiYuMi5KNio2NjI2Oio+Oj4yNjYyLiY2RkJKSkI2QlY6JiYuLjIyPi5GSkZCNkI2KjIuNjoqGjI6Hj4qMg4qJjoqLjY6KjYyIjIiMiIqLioWLjoKOhIyOiZCLjIgOiIyNiIuLjIaPiYqHjIeAent5d3d6dnZvdXx3d3d5eHd3dXZ6eXl7eHx6c3V2enp5e3t1d3d8eXt8eXR1eHl4dHR6fHl4fHl5fnl7eXt4dXZ0fHh8eXd2e3RzdHV7enl0dHV4dXp2fXV2dHZ2dHR1cnV2dHJ5c3hzdHR0d3R0dXZ4d3p3cnh/c3Z4fH55d3SAeHJ5dXZ1dnd1d3pyeHF8dnh0b3d6d3l2dHRzcm9vdnNwbmtzcHJzdnV2dXR6eHZwcnN2eXF4fXV2c3NvanZ2d3d2eHl3eXh4dHh2eXh2dnp5d3R1c3l2eHd2dW93eXl1dnl5dXh3dHZ3eXJ0dG9ydHd1c3NyeHR1dHJzdXV0eHRzenh5dHZ2eXl3dnh3dnh6d3p4eXd7eHt3fHl8enl6fXt4e3p7en97d3d6fHh3enl7en51eXx5dXZ3dHeAfHd8eXZ8e316e3l1eXx6eH97e3V6dnd5eXl7enh1fXd3e3R7eXp5fX15eXt1enZ6eHl4d3R5d4R2gHd2fHV2eXt8fnl8fn5+fHp+fH16eXh8eXx+e3l8e4B9enx9fXt/foB8fnx6eXl+enl6e3uAfHl7e3h5gHp6fnt4gIB+en1/fn99e3t4f399f3yAfn5/f356gIB8f3x+fX+BgYKAgX+Bf4CCgYB/foF/fH97f3x+fX98fX97fn59gIB9fHx+g3+BgIB9fn9/gYB+f39/hICCfoOCgYGAfYCDgoJ/fYGBgIGAgoGChIWEgoOEhYKChIJ9f4WEgoOCgYOEgoGBgn+CgYGBg4KBfXx+gYJ9fYGCgYCFgISBgIKBfoCDgX2EhYCEg3+CgIGDhIWBgIN9hYOFg4GEiIaChIWCAoKGhYWAhIOHhYaEgIODgoSCgIaBgH+DgoF7goGCgYKAgH+BgoGBhYWChIKBgX6Cg4N/f4CCgH6BgoSAgISEfoCCgYCAgoGAf4GChIGAg4KCgn+Cg39/gYCCf4GAgXx/g3+Bf3x9gX9/f318gICBgoCBgYKBgYF/gYKCfYSEfXh+e3t8eXmAe3x+g4J8fICAf4B9eXJ2gHt8f4B/hIOBhYWDgYWGhoKEg4WEhYKHhICCg4KBf4CDhIOBhoeFgoWEgYaAgXl8gHl8fHyDfoGDhIOFh4OEhISBfH+Af4GAgoB8goJ+ent8fXmAfYF/fn+Efnt9fX5+fX9/e3t65dx7dnp5e35+fXyAf3dxd3V5eHd5e31/e3l4dXp6e3V3ent8eHp9fH59ent5fHp4eH18fXl4eXt6eHt+foGAfXp+f3Z5eHp6eHh6d36Cfn99f3p4eXl7f3tyenx3gHp9c3h4fHh7e3x5fXt5fnl5eXt9eXR3e3J+dHh4eXh7fXh7e3l1e3h7c315enMCfHb/f/9//3//f/9//3/5f4J+/3+MfwICBAAgjo6Jlo+PjIuTlpuUlJSSlI+QkpSSm5WTl5mel46YmJSEl4Cbl5mUmZGWkY+Tl5OVk5OYlZGVj5SXlJSQjY6TkpeVlJWVlJaTlpOQlJeWl5OSlJGQjpKVmpOMg4iNjY2Sk4yPjpKSj5COj5KSlZKSkJGNj5CRlpGWj5KQj5eQk5aNk4yOjI6RkY6MjJKQjZCLkI+Oi5COi4ySi4Xw6P39goCA7YCAhoiOiYyJkI+IjYmEkYyOjoqHipOTi4mQjY+MjoiLjIyJioyLkIyJjIaRjJGJjIeMj4+Fho2KhomQjo+Lio2PjouMiZCPio6LjIuKhoyIjouIkI+RiY2NjpCQipCQkIuLjomRkZCPi42Tk4+Kjo+PkJOJj4+RkJGOkZKSkJCPjICTkIuQkI+NkJSTj5OTkJGOlI+RkZKQkZWWkI+Qk5OTkpCRkZOTjpGMj4+PkIqUiYySj5CTj4+UkpKOlI2RjpGRj5KQjpGRlZSUmJWSkZSYk5eUkpCVlJORkZKSkZGSkI6Uk5SSj5GOlJWWkpeVlZORlJWXkZWUkZOWlJWSm5KPjXSZlZWSlZaXl5iXlpaVlZSVkpiZk5KWmZmXkpaZl5iXlpGUlJmWlZeYkZaWkZWWlpeVl5WXmJyalpWXl5qamJqbnJaWl5SRlZSXl5iUl5eTlpSWm52clZmamZmWmJiampuXl4+Xmpeam52Zmpmen5ucmJqbnYeZgJ2anJ2XnZ2amZqZnJybn5yfnJ2dnZqYm6Cbm5udnJqbm5iZmJydmJqZlZeXmpeVmZqanJqbmZqZnJqcmJeYm5ydm5qbmZ6cm5icl56ZmpmcmZybnZycnZ6ZnZyZoZycmZeanJ2gnJ+cm5qYnpyamZmcl5ubnZmXl5uXmJmam5ybgJqbnp2dmpuZnJycm5mcnJybn52cm5KZmpeanJyem5ydnJ6VnJqenJ6enJiaoJeZnZmcmZubnJecnpydmp+dmp+ZnJmbnJ+fm56cnZuempyaoZyalpiYo7idoJeWlI+Ohouhn5+en5qbm52Vlp2Ym5uflp+no5uenaGgnJybnJyegKKbnJyan6CYoJ6en5ucmJqfnJqboZ+eoJ6YmpablpqhoqGgop+cnKCgm5+fmZ2anJiclpqhoZyempqXoZqcnZqYnZmcnJmYmZeYlZqUmJSVkY6Nhv/p6v+HiYqUlaGloZKfo5+dlpiUlZmYk5eZjZKWl5SYlZiZmZibmJ6ZmZqfYZ2bnKCWlJiZlpyanJ2cmpqWmpiboaidmJudnJ2ck56joZ2cmp2Xm5WXnZqTlJeQlpSQk5OTj5SVlo2UlJWRkY+Xk5WRmZiVlZSSk5uXkpWZlpmQlp2XkpSQjpGTlJqWlZaAhYV+jIaGhIOKi46Hh4uJh4WGh4qJjYaJjY2Qi4KLj4eJioyNjo2Oi42FioqGiIqJjImJjYmIioWIi4iJiYSFiomLjImMiomKiouIh4mMjo2Gh4qGh4WGiI+Ignt/gYGDh4eChoaEh4SFg4WIiImFh4eHg4aEhIqIi4WLh4aMg4aAiIKKgYWChIeIhYaDiYeGhYKGh4ODh4WDhYiBfejk9/R7eXvleoCChX+FhIaGgYWBeoSGhYiEf4GKioKDhoOGhYiBgoOGgYOGhYiDgoV/h4CHf4J9goeFfn+DgH2BhYOGgoCEhYaCg4GGhYCEg4SEg36EgIWCgIWGhn6FhIWGh4GAhoOFhX+HgYWIh4WGiImJhYSFiISIioOGhoeGiIeJioqHhoeEioeDiYeHhYeLi4eJiYmHhYuHiouIh4aLjIWFhYmIhoiJiIeHh4WIgoeHhoeDhoSEi4eEioeGiomJhImFiIaIiYiKhoSHhYqJh4qLh4aLjIiLi4eGi4qIh4aGiIgEiIqJiIWMgIqIi4uLhYqJi4mJjY2PiYuLiImLjIuJjIiGg42NjomOjIyMjo6Li4qJiYyKjo+IhouNjY6LioyMkI6MiIqKjo2LjIyIjY2JjY2MjY2PjZCRkpCOjY6NkJGRkJGQjI2PjomLi4uMjYuQjYqNjY2QkI6LkJGQj42Mjo+PjoyOhY+QFo2PkJGPkY+SlpGSkJGQk46Oj5CPj46Ek4COk5OPkZCQlJSRk46Sk5SSkpGOkJKRkJKUkpCPkY6Qjo+TkJGOi4+NjIyNkJCVkZGSkZORkZCTjpCPkpKTkpOTkZKTk5GSjJOUlpGUkZKQlZaTlJSRlJSRmJOSkZGTkpSWkpWTkpORk5KSj4+VjpGQlZCQjpGPj5GSkpSTkY+SlICTkpKRkpGTkZGRk5KRlpWUk4iPjoyOj5CSj5KRkpWLkpCUk5OPkY6Pk4yQko6SkpKPk4+Uk5CSj5OUkZSQlJCRkpaUj5STlJCRkpORlpKRjpGQlZ6OkIuMjYeFgIaVlpWTlI+RkJGNiJCLjI6UjpCWlZCTkZWXkZGSk5OWl5GUkYCOlpSNk5OSlJKSjpCTlJOTl5WWl5SMjo2QjpKWmJiWl5eTk5eXlJeWj5WSlZKTj5KTlZGTkJCMkZGSlpOOk4+Tk4+Pjo2PjZCNkIuNh4aHg/rj3PCBg4GLjJSXk4aSlpGQioyKi42LiYyMgoeKjImNioyMjoyNjJCMio6Sk4+Pkl2Lh4mNi5COkpGPj5GLjouLkZaLjJCTkZCQi5KWlJCPkJKMj4qMkY+Lio2IjouKjImKh4yJioKJiI2IiIWMiouIjo2Li4qHiY+LiIyNiYyIjJGOiIeHhYWKh4uJioqAcXdte3h5dXZ7eXt3eHx5d3Z1eHh4e3d6e3l+dnR4fXJ1eHh7fH1/e3p1fHp2cnl3fHh6e3h4d3V3d3h4eHV1eHh2fHZ9enh5eXl4d3Z5fHt4eHh2d3V3cnx5dHJwcnN0dHdydHV1eHVzdnB2endydHV2c3h0dHt5fXV8eXl9dndld3F+cnN0c3R2c3dzdXZ4eHN3dnB1eHh3eHl0cNTR2t1sbW/cbnF1dXF4dXl4cHV1a3F4d3d1dXN5eXR2eXZ4eHlzc3Z3c3R2dnp2c3Vxc3F5cXRxc3l0c3J1c3N3dnJ1dnN3d3iFdIBydnR4dXRvdnN2dHF1d3dzd3N6d3h2eXR2dnB4dHd7eXV5enx6dnh3eHZ6fXh2eXl5d3h6e3t5eXl3fHp2eHp6eXh8enl7e3p3d315fXp7enl+e3h2dnp8eHt9d3p4eHd3dHl5e3x5dHd3eHh1e3p8e3p9dnt4eXt5enl5d3l6eYB7eXZ4fHl3f3x8eHt1dnh4eHZ2dXl5fH58e35/f4CAfHd6fXx2fHd5d3l+fn58f396fHx9fHt8e3p4e31/e318e31/fn18e3h5enp9fnp6e3x9fX17f4B+f3p7eXx/fn58fXx/f3p+fH9/fn+BgYKCf3+Bfn+DhIF/goF9fX5/fYB9fXyAgYCBf3x7fHt+f356gIR/goR/fn6Afn99eICBgH2BhIGBf4KGgIGAgoOEf3+BhIKBgYOCgYSCgoWCgoGBhIJ/gn6ChIOAgIF/gH+AgYODg4GBgoGBgX+DgICBfX5/f36AgoWDgoWDg4KBgYGDfoKAgoGBgYSDgIODgoOCfD+EhYaCg4GBf4SFhIOFgYOEgYiFg4KEg4CEh4GDgoCEg4CCgX2ChYGDfoOAgH6Df3+AhIKBgIOAhIeEhIOBhYKFgYB/fYWEhYZ7gIB8f36CgIGBg4GCfoKChICBfIB/fYB9gYJ+g4N/f4B/hYJ/gH5/fn+Cf4B/f4B+gX6Cf4CAgoSDgYOAf4CDgoN5eH98en55dnN3gYF/gIF+gIKAfXl5eHd5f319gIKBg4GEiIKBgoODhIaBhoB+hYSBg4KAgIB/fYCBgoSEhYaEhoOAfHt7fn9/goWFhIaEg4OGhYOEgX+DhYSBgX2Afn58f399en6BgIKCfn99foN+fnx+f36AfH97e3Z5enbn073Uc3V1eX1+gHtwen99eXV4eHl8e3l8fHF3eHl3fH18eX98fHl4eHh5f4F6eoB5eHeAfH15fn9+fFKBfHx6eH9+dXp/fnx8fXqBgYGAf4GCen53fH9/enp8ent3eoB4fXd7dnZydnR8eHd2fHl5eX57e3l6eHh7d3Z6fHh6dnd7fnV2fHd1eXV5dXp4/3+df4R+BH9/f37/f/9//3//f/9/1H+Efv9/in8CAgQAgJSRkpOPko+SlJGQlpKMk5STlJKVm5Wcm5qYoJ6YmZSVmJmUmJiSmJmQlJmTlpiXnJiVmJGVlJaUkJaXjJSSk5KUnJSWlJqSmJaVkJOXloqNk5GNkZKYlJGVk5ONmI+WjZCRjpSNlI6QkJSKkI6NlZGUkZePjpKUkpGVlI6QlZORgI+Oj46NkI+QkpCNjI+Oj42NkYuKi46RkoqSkozs+Pn9/PmCgIL/goORk46Ki5COj42RjY6Mi4yKj5GOjJGPj5CQiIiHi4mIhYaJkYqNh4uKiYyKi46QjIeEiI2IjY2Li5KLjpGNi4+QjoyMjIuOh4yQjpCLi46KiZGRjo2OkY+SgJCOiJGMiZCPkIuOjYyPj5GMjo2RjZKPkZaOjZGOjo6MiY2TlY+RkpOQkZCUkY2TkZOOjo6PkI2PkZCRkpOQkJSMkpKTkJGRlZCOj46MkJGQjJKKjY+TkZGRjo2OjZWSkIySlY6PkpGSk5KPk4+RkZCSkZOWmZSTk46TkJOQkpWUdZWOjpGVkJKSkJKTlZaTkpSSkJeRl5OTkZaTk5GTmZiXmZWXkZOVlZKUlZaVmZSVl5mVlZiVl5iZmJWWmpqTlpmYmZiVk5WVlpaWl5WVlZCQk5iZlpaWj5eXpZaXlZmZlJeXmJiXlJaXl5OUlpmWlJWYmJiWmISXbJiZmJWYmpqanZmbnZmXlpeZmZqbmJ2YmpqamJmVmpuZmpiZmJqbm5ybmZufm5uYl5iYlZmdmZ2ZmJ+gnpidnJuamp+Zm5ucm5yaoJealZuYmZeVl5mbl5uYmpmblpuWl5qblpidnJ2bm5qbmoSZgJicnpibmZudnZibn52fmpqam5yfnpuXmZuZmZuanJqanZ+ampmZmpmYlpeZmJmYmJuVmpyZmp2al5uXnJudmJWbm5menJuanJaZmZSXnZqfnZygn5udmp2bmZaYnZ6ioJqYmpmboZqdmpeZnZyanZmanpufnJ2enZmfmp6cnKCcgJ+bn6Cknpudl5KZk5msoqScnpiSjPuOop2fmZudoZudnaWhoaCZnZmVnp+fnKCcnJyemJ6fnZycm56fnJ6enpugoJyVmZuampagoI2XkZiWkJmRioSLoJ6hnqCgn6CbnZ+enZ+dmZqcnJ+dm5uhmJqZlqKdl56Wm56bnJqcm5mTgJeampOVlZSQj4yF/+/dw+CGie7e+4ORjo2Ul5WUl5SVk5CYlpaRjZmblZmem52empunqKylmpeOiouJiICCkJWZmZqZl5mUlp6bm6+emZeamJicm5ujnZydnpydmJiXlJaSl5SVkpGSkpeYk5WVlZSPk5SUlpKYmJWVl5WPk5eXF5ORk5WVlJSEkYySj5ORk5SRmJSalpORgIiFhomFh4SIi4iFiYaFiIeHiYaKjoeLj46Njo6MjouLjoyJi4qIjY2HiY2Hi4yIj42LjIeMh4qJhYqMhIqIh4uKjoyMiZKMjYqGh4mOjIKDhoeDhoeNh4aGh4aAjISJhIeHhIiEh4WEhIh+hIKEiIeIiI2EgoWJh4aHiYWGiomGgIeHhISDhIKHh4WDhIeFhYSFhoSCg4WIhX+FiYLf8PD28e98env4fXyIiIWDg4iHhoWHg4aFhIOCh4eFg4iHhIaJgYCAg4OCgYGCi4OHgYSCgoOBf4aHhH97f4SBg4OCg4qAhYaEgYSFg4KCgoSGf4OFhIaCg4V/f4WHhYWDh4WIgIWDfoWCgIaFh4KHhYSGhYeHhoOIhomHiYqGgomGh4iEgYWKjIeJioqIioaKioeLiIiEhYOGhYeFiYiJiImGh4uDh4eKhYWIiISGh4iHioeGhIaChoeHh4iIhYSEg4mGhYKHiYGFioaKioeHioSGiIWHhomJjYqHi4WKhouHiIyICIqEiIqMiYuLhIyEizeIh42JjYuLjI2JjIeKkI2Nj4yPiIuMjIqMi46MjomKjI+LjI2Mi4uNi4uKjo+Li46Pjo6Ni42LhIwniYqKhoiKjZCNjo2Hjo2hjI6NkJCKj46Oj4+MjpCPioyNkIyKjI+NhI6Aj4yNjo+OjI6Pj5GTjpGSj4uQkZGQj4+NkI6QkZGOjo6Rj4yNjY+PkJGSk5GPkZWQkZCNj5CNkJOOkZCQlJWTj5ORkY+Qk5CTkpKQkY2Tjo+Mjo2Pj4yOkJGPkpCTkZCNkZCRkpOOkZSRk5GTkpOSkZCTk5CSlZCSkpWTlJKVlpSAlZKSkpOUlZaUkJCRj4+PkpSSk5aVk5ORk5STj46OkZGUkI6SjZKTkpOSj46SjZKTkY2Ok5KQlZCRj5KNkpKKjpCNk5KQlJOTko+SkZKPj5KTlpWRj5GPk5aRlJGNj5OUkpKPj5WSlZGSk5KOk46VkpGVkpOSkpSYkpGSjIuQioyAm5SYkpSPioXyh5iTlI+PkpaQkpKWkZGQjZKOipCVlJSVkpCSk46UlJCTk5GUlJKTlJOQlJSRjI6RkpORlZOFjIaMioeNioV/hJaVmJWXlpWWkpSUlZWWkpCQkZOSkpCQlY+PkI6ZlI2SjZGSkJOPkJCOiY2QkYqMjIuKioiC++mA17fPfX/a0Od5iIiEioyLiouJiYmHjImJh4OMjouNj5CRkY2NlpaZlYuLg3+AgYB4eoaMjo2Pi4yPi4uSjIuajo2Mj4yNkZCRlY+Pk5OPj46NjYaKhYuJjImHiImMjYiMi4uJh4mLi42JjY2KjYqLhYqNi4aEiYuMiIh6hoKIiYsKh4iLio+KjouKiIBydnh1dnl1dnp1d3t2dHV4dnp4eX13e3x7eXp8e316eXl5dnl5dnt8d3d8eHx6dHt5eHp3eXd4dXR6fXR5eXV7eXl8eXh9e3t8dXZ3enpyd3N2cnV2eHN0dHh0b3hzd3RzdHR5c3JxdXR0aG5uc3d2dXl8dHJ3eHd2eHt1d3p5d4B3eXZ4dXdweHN1c3N2d3Z3d3h2c3d3d3RzdXZvy93W4NjXb2xr2XRvd3Z4eHN3dnZ3dXN2d3l3dXd3eHR7d3J1enV0cXNydXR1d4Z3enZ1cnF3dHB5dnRzbXJzcnZ1cnF5cnZ3dXR2eHN0dHN1enV2dHZ5dXd2cHJ4dnR2c3d3doBzc290cXJ3dXZ1eHZ0d3d2eHhzeHd6eXl6eHZ6eHh7d3F5enp4fH58ent4e3x4enp6dHV1dnZ4c3l8e3h5eXh6dHh4fHd4fHl3eHp+en17e3d6dnh6eXl5fHZ3d3R4dnd3fXpyd3h4e3l5eHh0dnd3fnl4eHp1dHx2eHh7eHh7eUZ+eH19f3x+fn58fnx5ent9eXd7fH19fX9/fYF8e4J/fn98gX1+f358e3x8en97fnyAfX59fnt7gXx8e39/fnx9fYB+fX1/hH6AfXx8fXx8fICCgoR+fICAj3t8f3+BgIB/foKDf31/gX+Bf4B/en5/e318fH5+fX59fX59fYB/gYKBf39/fYKFgYCCgYCBgYOCf3+Af4CCgH5/gYCAgoF/gICAhYKDgoCBhYCAhH6Dg4OChYSBhIOCgoKAgoWDhIGEf399f3uAfYGAgX6AgYKEhYWEgYJ9gICAgoF9gIN/goKDhIKBgoGCgYOEh3+DhYaAg4GGhIWFg4SBhIaEg4OCgIKBgXyAhIOEhYaCgX6DhISBf3+CgoSAfoF9gYCCgoN/gYN+hIN/eX+DhH+BfoV+hH6DhH+Cgn+EgoKFhIKCgIGBgICBgYCCf4CAf3+DhIZ+gXx9gYKDf4B/f4N9gn5+gIF9gH6DgX6AfoSChYSGgICDe4GCf31+e4KChH57euF3goCAgoGAgoCDgHx3enl6fXl3eoKEhoSAgYOCgIKBgoSBgIWDgoSEgH+BhYF9gIKDhoOEfHZ5dHl6eHx7enR4goSGhYeHg4WEhoGAgoSCfX6CgIF/fH9+gH57f4CHhX5+f4R/foB/f399en5+f3t9fn1+f3t57trGorlvcb24x2l4eHR4eHp7enh3eXd9dnd3dXh7eHt8gIB8eXyBgH9/eHtwcHNydGxsdnx8e3x6e4B7fH97eX96fnx9eXqAfH9+e32BgH58e3x8eHo2c3t4fHl3enx9f3d4enl5dnh5e3l4f3x5fXt6dnp9eXh3eHl5dndxeHV3fH10d3l7fnd8enl3/3+df4Z+BH9/f37/f/9//3//f9x/AX71f4V+BX9/fn5+/3+DfwICBACAlJOTkY6QlZCRk46Qko+UlZeVkpaXlJWVmJackoubk5SZmJGRlJWQk5CPkpqbkZWVkZacm5eZkpSQk5OWlpeVmJmVkpKVlZeZlZSPlY2WlJiRm4uOkJCTl5SZl5qQj5CTk4uRjo6WlpGOkI6PjY+VlpWLk5WQkZGSkY+XlYqWlZSAkpGQkIiNjI+SkZGTlJGKjoeQlo6NiY6SkYuOivr2hIGAgIWEhYuFj5WQjo6Mk5CLi42LiIiJjpCHj4yOi4mLh4qMiouKjImGjYuOj46GhZGNi42KjYWMiIiLjYeGi4uUiouOkY+Ljo6MjY+Mh4yOi4yJi46RjpGQkJKKk4qOkIyAj4uMjo6OlZKSjIuNko6UkZKLjY2PjpCRkZGOj5CRkI6RjZGSkZSQjo+Qj5ePjpCSlpCQkY6SkZKKjJGOkJGQj5OOk5KTlpOUjJGSio6RkpGNjI2NjZCQkZSQkZOUlpSUkI2QlZGRkZaQj5GSkZSSkpeRlJmRk5OTlZWTmJWUkZCAkZCSj4+Qj5KSjJKUmpaXk5aVmZiWkpWUlJWTmJSUlJWYmpmXlZmYlZSWm5WZlpiWl5WVlpaWm5aZmZWWlpaTmpSXmJSUlJWUlJOYlpeUmZSblpWUkpSTlZWYlpqXmJKRkpOWmJWVm5eVmJaWmJaTlpiXmJaUl5iZmJeZmJ6bl4OAlpuak5mdmJeYl5yWmJmbmpubnZmZl5mampedloSLmZqanpiZm5yYmKGcl5ibmpqZlpeampyamZ2bnpmZmpuam5eam5uYl5acmpaXlpiYmZeUmpqYnJ6dm5aXm5qYmJucmpuZmZiUlZial5ubnZ6Znp2cmZuampmbm5qamJuenZiAnZydlJuZl52bnJucmJudmpiXl5eWlpiZmZeZmZianJWZmJqYnJucmp+Ymp6fnZ6enJ6dmZ2cm5ecm5qdoaCenZydnKGamJudnpebmp6cm6ObmZSZm52goJqanJ2bmJecm52jmZ6fnp2cnJ2ZnZ+hn5mbmpucnpinqqShn5SFhJqAmJubmJ6hoKCVnJ2joai0mZifm5ucm5yanZyhnJ6cm5+coZ2dmp2enZ2ZlZycmZqZl5Wwm5qXpJmXmZSWl5COoJydn5+bnp2amZudmJeZnZidl56XmpiZlZuYl6CenJ+fo6aXm5admZqamZuYlZiVlJSVk46MjIbnx+XwgIiOjYmAioeFn46WlpWQlZOTkpiamJaWlpubmZqWlaGjrKijqpGFg4mIj4mEh5OYmZiXkZSWlpaYnqqanJuWl5mdnZ6bm6GbnZuZnZqUmZialpOXmJeUmZeWnZaXlpmYl5Sbl5mXl5eQlpeVkpiUkZaSkZOUmJKajo6Vk5aVlJSQlJiXk4uAioqJhoaGiYaGiISGh4SHiYqIh4yJhoiHi42Mh4SRh4iOjIaEhYeGiIaEhY2QhouLhYyRj4yMh4iIi4mNjI2Ljo6KioqMi4yOiomFioSLiouFkYGDhYaHi4aKio6Ig4KHiICEhYSKiYSEhYOEg4OGiIiBiYqHhYeJhYeNiYCJiIeAh4eEgn+BgIKJiIqIioeChICHi4aFf4aIhX+Fgu7wfnp9fX59gYF6hYqGhYaFioWDgIWEg4GBiYt/h4OFhIKCgIWGhISCg39/iIaHiId+fYeFgYSAhoCFgH+BhX+Ag4SJgoKDhIKDhoSDhYiEfoCCgoJ/goKHhIeEhomCiICGioSAhYOFhoaHjImHhYaGiIWJiIqCg4SGhomHiYmFiYqJh4aGhoiHh4uIhoaIh4yJhYeJi4iHhoOGhImDhoaEiYmKhIiEiIiLi4mIg4mLhIWIhoaDhISDg4WGhYmGhoiJjImJhISHjIeHh4mGhIyJh4iIiY2GiY6JioqLjIuIioyJi4aAiIaJhYiKioyLhIqKjouOiouKj46MjIyNjYuKjoyOjY6MkJCNi46QjY2MkI6PjY6Mi4qNjYyLj4yNjoyMi4qJjIyOjoyMjIqNi4uPjI2Kj4uSjo6NioyMjY6OiY+NjomLjIyNjIyLjo2MkI2Njo6MjY+Mj42Kjo+NjY2OjZCRi3mAjZGPiY2Qi46MjpGMjJCQj5CQlJCOjpGRkI6VjnmBkJGQk5CSk5OOj5OSjo+SkpCQjo+SkpKUkZSQk5GSkpCPj4yPj5COjIqPkIyRj5GPj46MkZOPkZKRkJCSkpOSkJKUkpSQjo+MjI+Rj5OTkpaOkZOWkpSRlJKTj5CRkpKUlJCAkpKUjZSTjpSUlJOUkJSWlJOOjY+PjY6PkI6RkZCTkoyQj5GQkpCTkJWOjpKTkZGUk5STkJGQkYyQkY+SlZWRkY+Rk5eOkJKTk4yRj5KSkJaTkIuQkZGRk4+QkpSQkY2SkJSXj5STk5OSk5CPkpOWlI+SkJCRko2ZmpmVk42AfZGAj5KRkJaWlZOLjo6Uk5mcjY6TkZGTkJSRkZKXk5OSkJSTlZKUkZSUk5WSiouOkJOSkY+cio6Llo6NkYyNj4yLlpOUlpeUlpSQkJOUjY6QlI6SipKNjo6Rj5GPkJaSlJWXmZmKkI2SkI+OjpGPjI+NjIyQjIeFhoLgvtrgeIOIhoKAg4B+kIGKjIqFiomJh4yOjYuNi5CPjo2LiZGUmpSPl397e4CBh4B+gYeOj4yJh46Mio2Nj5eOjY2JjY6Rk5GNjpORko+NkJCKjo6NiYmMjIuMkYqNk42Lio+OjouOjY2Mjo6GiomJiI6JhouJiYiMjIiOh4iKiIuJiYmGi42LioKAdHZ2d3Z2enh2eHd4dnZ0eXl2eHV2d3l0eXh6d3V/d3V2d3V2c3R2dHJxdH2AdXh3dHuBfnl6d3h6enh6e353fXt6enp5enl+fHl1e3d3eXl3fW92dnF1eXZ5d3l7cXF1d3BydHV3dHJzdXJxcnJydXVxeXt6dHd3dXd9dHJ6enWAdXRxcG1xcHN3eHl0e3hyd3F3eXl2cnl5eW92c9jecmxvcW9xdW9udnZydnd1fnZ2cXh4eHZyfH1wdXF4eHZ2cnd5d3d1cm5zfXl3endvcXZ2dHlzdnJ1c3F0c29zdnV3dHR2dnF0eHNzdnh2cXJ1dHNzdnV2dXl3eXt0eG52enWAc3Z2d3d4e3l3dXZ4dHR3d3pzcXV4eXh4enp2eHt7eXp3dXl2dnt6eHp5ent8eXl6e3l2eHV5dnd2eHh1eXl5dXh1eHp8fXp5dH1/eHl4d3V0d3d3dnd2eH14dnl7fHt4eHR4e3l6eHl4dXp7dnd3e351eX17enh6e3ZzeXt6eXcod3Z4d359fX18dXt4fHl/e3x7f3+BgX9/gH6Af3+Bf4B+gYN/foCDgIR+gIGAgn5/fH99fHx9fX+Afn1/e3p+fX2Afn5+fYB8fYB8f3yBfoR/f4GAfYB/f398gH+Af359e358fX6Afn6BfH1+fnt+gX6AeXuAfH1+fn1+gYB6an2Af3p6f3t+e3+CfH+Cfn99gIaBfYGBgYKBhn9qc4KCfoOAgoGFgICAg4GCgICCgYKBf4CEg4ODhYGCgoKDgYB/gYOBgX99gICBf4F/hYCDf4GDhX+BgYKBgYKCg4GCg4SDhIJ+gX18gYKBgoWBhICAgoWDhYCDhIN6f4GEg4WCfYKBgoCFgoCChYWHg4GChoSEg35+fnp+f4GCgoF+gn98gYGBfoOBgYGEgH6BgIKAfoOCg4OBgnyAfX+AfomGgH98f3+AgH2Af4CBfYB9f4B/fn1+f4CAgH6Bfn5+f3t/fYF8f4N/g4GBgYCFf4CCgoKBgYCDg4OAfIGFgoKCf3ZygYGDgYGEgoWFenp5e32Cf3t9fX2Cg4CBf4GBhoSCgoGEhISBg4GFhYSDg3hxgHyBgoOEg4N1fHmAfX6DfoGDgn+FhIWGh4WDhH6BgIB7e32Bfn14fn2Afnx8e4B/goCDgIWFf3p/gIF/gH98goB+f359foGAe3h7eNSwwslud3t4c3RybnZveHl5dnl7dnd5fX19fHl+fHx8eHl7fYF8eoBtbnBzc3l2cnV2fX99VHx8fnl5enp5eHp4d3R7f39/fHZ7gH9/e3l+fnl8f3p3e3t8eXt7eHuBfHh4fnt+fHx+fHt9fnR3enh4fXx5e3p7enh5d4B6eHp3enp8e3R4fnl5c/9/nX+Cfv9//3//f/9//3/ef4R+/3+GfwICBACAkZSQkZOWjpKXkpSUlZKTlpWQmJWSkJSZlpqYkZSXlpaZmZaTnJWYl5WXkZuampWSlJGZk5eVjpmVlpqXlJOQopmgmZiXkZKSk5GUj5mRkpmSjJKTkJGRkZCHlI+Oj4+QmZKUkpCRj5CUi4eQlpKOkpCSl5KYko+XlZOVlJKSkZCAkY2TjoyKk4+Pi5CQjoyLj4yPk4mOjYqQk4uNjYnvhIOEho2OjYuNh5abk5OKjouHjIqQio+PjIuOjo+Ji5KGhYmIioiIiIyKi4iKjIqEhIeLkoyMjYmMjYiMiomFiI6Oi4uQjpeNi42NiZGTi4iLiI6MjouLj46Li5OUjpCNjIyAjYmOkIuLi4+PkY6NjJCSi4yPiYyOj46Rjo6SjpSPk4+Oko6Ujo6Ok5GOjY6Lj5OOlJGQkpCRk5GPlJCRkZSQkJOMjZKUkJiRjIyQjoyKk5CRkZCUj4uOipCSkpGRl5KRkZGOj5GRkJSMk42OlJOTlI+SmZaSkZWUkJKTk5aSj5SAk5OMkZKTjpOSlJaVmJuWk5OXlpWUk5aZlJOSlpOTkpeXlZaWl5iVl5eYlpaWmZmWlJaXm5mUmJiUlJeVkI2VlZeVlpSZmJaTk5KVlJeXlZaXk5iXmJORlpaZl5WYlZSWlpSWmJaWk5iWl5WTk5qamJaXlJiZmJeamJmblpWYmZqAlpeblpmamZaYmJiRl5WWnJmVnZiZlJeZmJeamJqbmZybnJqXnJeYmpaamJuampqbmZybmZuXlZuXkpmZmZubnZecnZmbl5mamZiWl5OVk5qbm5qanJqXlpmbmpqWmZqdn5uamJSbl5uYm5qbnJuenZuYmpqXm5ycmpibm5ydnZ6Amp6am5eZnJmblpmbm52em5ibmZqem5ebm5udm5iYm5mXmZiampubl5mXnpuanJiZmZyenZubmp2cmJiWnaGdnKCgl5abnJ2cnpyZmpucnZuYnJmfmp6enZ+bm5uRnp+enZecnJujnqCfmpufoJ6dnZyfnJuam5uYn6aimZeTl5eAn6Gdmp6clJ2gn6Oen52knK2cn6CdnZ2ZmZ6fmZ2bnJ6fnp2dn5icnpSZl5eYlZuVl5ebsqKkn5+hnJyZmY6JnJyhoJ6jmpudnpydlpeampiYk5uUmpqYlJeVlpmempqYl6ChmZqYm5idmJuemZuamZSWlJKSkpCMjZGQjI2Rj5CAj4qNjo+Sk5SUlpOVk5WQlJuamp2YlZaQi4+erq+utpGRiZaUlpOBgpKQmJeRmJOTkZWYnJmYn5uZm5iYm5uamZyamZqZnZySjJiTmZeXlpuXmJuRl5SUkZSXl5mUk5WSkpGMlpiVlpKUk5OZkJeOkpabkJWTkZOTlpOUmJmSkI+AiYqFhomLgoiLhoqJi4SFiouFjIqHh4iPjZGMhoyLiYqOjIeHjYmNjIuKhY2Qj4uIiIaOiYuIhY2MjY6MiouIk4ySjIuLiIeIioaJg4uIh4yHhIeJhIWGhoZ6iISDhIOEi4WKi4eCg4SGg32Ei4SDhIKHi4eOiIKKiIiLiIaHhoV9h4GGg4F9h4OFgoeIg4KChYOFiYCFg4KGhoGGhX/gfn9/gIaFg4SDfImNiYiFhYN/hIaKhIWHhYOGhoWAg4mAf4KDhoCBgIOCg4KChYJ9fX6CiIOEhn6FhX2BgoB/foWEgoCHhIqEg4ODgYeIgH+Bf4KDhIF/hYWDhYqLgoiEhFd/hoiEhICEhImGhYaHh4SGhoOEhoSFhoWGiYeNh4eFhIqIioWGiY2Hh4WFhYeKhoqIhoiGhYeGiIuGhoiMhoeJhIGIiYWNiYOFh4KDgImHh4mIi4iFhYKEhz+Gi4WHhoeHiImKhYiBiIaEi4aGiIOGjYuIiYmKhoqIiYuGhYmIi4OJhomGioqLioyMjouKi46NjIuKjpGNi4uEjICQjo2Njo2MjI2OjY2NjI6NjoiLjI+MiY6OjIuOioaCi4uOioyLjo2KiouJjIuNiYqMjImMjo+LiY6NjYuJjI2Kjo+MjI2KjouOjI+NiYmOjo+OjYyQkI6Oj4yOkIuJj4+Oi4uPi46RkIuNjYyIjYuPkpCMko2Qi42QkJCTkI+SkICQj5GQjo+Oj5CNkY+Qj5CQj46Pj5GSjouQiomQkI6QkJCOkpKNkY2Qj46NjI6KiomPkZGPkZGQjoyPkZGTj5KQlJWTk5GNko+SjpCQkJOUlZKSj5KTkJOUk5GRkpKSkZWXkZSSko6QlJGSjZGTlJaUkZCSkJCUko6SkZKTj46PkDaOjo+QkZGRko6TkJSQkZOOjo6Rk5GRj5OSko6Oj5OWkpCUk42PkJCUkpORj46PkZOTkJKPk5CElF2RkZCFlJWTkY2Ok5GVkpOUj5KTlZOTkZCUk5CSkpCPk5uXkI+Lj4+VlpGQkpCLk5KQlpCUkpCPmoqOk5SVlJCQkpWQkZCUk5WVlJOUjpGVjJCMjI6Nk46RkJKelZiElYCTkJGHhZOTmZaVmJCQkZWTlI6PkJCLjIiQjJCOjY2OjZCPk5CRkY2QlJCOjJCNko6SkZCSkZCNjoyKjIqJhIaKhoGFiIiGhoKFhIWIiYiJi4mKiY2HiZKQj5CNi4uGg4KOmZqbm4GHgIuKiod6fIqFi42Hi4qIiIuMkIyMkYyLki2PjZCOjY2RkZCMjJCPiYOOiY6MjI2Pi4yOiI+Ji4qLjIyPiYiLh4aIho6Ni4qEiBWNhY2GiIqPhYmIhoiHjYiJi4yHiYSAdndyeHl6cnZ9dXl2e3R0eHh2fHZydXl9en15dXx3e3x7enZ3d3V8d3Z2dXt8e3l0dnZ9e3V2dXt6en13e313fXZ6d3R4eXl3eXl8c3d4d3t5c3R4c3N2dXZsdHV1cXJydXR6enZucnNydm5ueHRxc3J2eHV6eXB3eXp6dHRzdHKAdW55dnJwd3R1d3h5c3N1dXF0eXJ2dnZ1eHN4dHDQcXJwc3h3dXRzbXJ1eHt3dnZydnd7eHd6eHR5eXJ0dnpzc3R1eXN1c3RyeHV1dnJvcm91enZ0d295d21yb3JycHZ1c3F4eHhzc3N0dHV4dXR0dXN5eHFwd3h3eXp4dHhxdHWAeXJ3eXd1c3V0eHV0dnZ2dHR2dnZ3dXV2d3d5dn14enp2fHd7dHZ5fnp4d3N5eXd1e3d1d3h2eXZ6eHRydXt2dnp2c3l0d3p4d3Z8c3hye3p4eHp7enl1dHt6d3h3e3N4e3d4dnZ5dXdxd3d1fXZ5eHN3eX55eXp9eXp3eHd2cndBdnpzeXd5d3h7e3l7fX1+f359fX56eoCAgIKAf36AgIGCgIGAgYCBgYF9fX1/gH5+fHp9fn17fX99fn58eXt+fX6EexJ+fHt8fYF9f316gH18e4CCfn2EgYB7fXx6f4B8f4B9gHyAf4KAfX+EgoCAfnx/fn9+f359gH55gH58e3t/fIB/f4CAfX18fn1/gH9/g35/foCBgoKDgYCCgn9/gYJ7fnx/gX+EgoF+f4GBgoF+gYF/e4F8fYGBgICCgX+AgHuCfICAf358f3h+f3+EhIGEgIB/fX+Df4CDgoSAhYSBhIOAgoCDfoKAg4CDhYOCgoGBgISFg4KFhIOEgIaFgIaBgH58gYCCf4WFg4OEhoCBg4SFgX+BgIODgH5+gHt/foGAgoOEf4KBg4B/g3x/foGCfoJ9hYOBgX5/g4SBgIKDe35+f4F/gH9+fX1/f4CAhH6BfYKBhISAf4CAeISDgn59fYKAg4GChH2BgYOEg32AhYB8goKAf3+Dg4CBgIKAhYV9f4CAen5+fX97foB3entzd36ChYF/f4GCgH+BhIOEhIKEgIGChH2DfX6AgYWEh4SAgYGDg4OGhYODgnt7goSHhoOGfn9/hIGBf39/fHp3enx+e398f36Ag4B7fn+Bg355gn99e4F8gH2Bf36CgYB9f3x+fXp7eHh8d3R3eXp1dnN6eHR1dXd3e3t7d3t5d4F8e3x7eXt2dXN5gIGAe3B5cHx8eXtxcX14fH51eXl2dHh5e3d3e3p3gH57fXx8e4B/fnd7gXt1cH51fHl+fH16eHd1fXh8e3l6eCV+eXl8eHl4dnt7enp5eHd2fHN8eHh2fHR4eHh2d3x4eXt4dXh4/3+efwF+/3//f/9//3//f/9/6H8CAgQAIpGQlJSPk5OTlZSVkY+ZkJCPipSSj5SXjpiWjZGanJiQmpKEkSaPlJOUlZiVm5GUmJOYlI+ZlJeUlZucoKKjnJqUkZaZmJqVm5iSl4SRa5KUk5eUjo6YlpSVj5SQjJeMkZKPkI2SkpOUko6NjI+SkIyLkpWUjZGXl5KWkZGOlY6Ok5KVi5OMioWNiI6MjI2PiomOkI6Kh46NjIqWgoSMjo2Ql5CTk5KYppGOjYyJiImLiIqPioOUkI6OhIuAiImNj4iJi4yHgoeJhYyIiIeKjYyIjZCIhIyNjYuKjJCGlIyOi42Li5CPjIyJj4qOjYyMiI6Gi46Pj4+RkJGTjJGMio+NioqSi46Rj5GRjJGLkJCPjpGTjo2OlpWRkI2MkpGTjJKPjZCLjo2Qi4qSi5GUlYqSl5CTjpKRkY+SlZCAmJSRkpCQj5SVlY+Oj4ySkZCRk4+TkJGPkI2Sk5GVkZKQiZCSkZKNkJOUlJaSk5CVlpOUkpaRlZGTnJGTkpOXmZWPlI6Uk5KSlZGTlpWUlZiVlJeTlpmUl5aUj5OSkpOUl5OWkpKUlpeYlpSYkpWTl5iXmJiVlJaVkZaVmJKWl5WAlpSWlJOUlZaXlZOVl5aXlpOXlJaZmZaXlJiTk5KVl5aWlZWXlpeUlZiXlJWRlZWXl5eYlpWXlZmZmJaVlY+al5mYmZiYmZmXmpSWlZiZl5eem5uWm5mcm5eYnJeSnJean5yfm5ucl5iYm5qem5+ZmpqZlpecnZacmpqZnJiZl5uAmpqdmZ2blpSXmJqblpOXmpSam5qVlZWZm5mcmZeYmpeYnJygm5mXmJuZmp2ampmaoZyenJycmZmbmZubnJqampiZmZaYmZyamJybnpmanZqYnJeampydmJqamZ2ZmpqclZiZlpiXmJealZmbmpyZmp2bn5mam5+fm52Zm5qWnZuAmqOfnZ6ZmpucnK6cn5ecnp6dmpmdnp6cpJqcm5mcn5+en56foKCeo6Cgo5qgm5ybmpyeoaGcn6CXl5OQhpWWl5mdnKKYk5GZpZmcoZ6bk5KqqKuimpianpSYm56coZidnqCfnpqgmqGRhKqgmZiZnZmYmJerr6KjoZ+ioqKamo+AlKKhm5mcmpqdn5ifnI+anKCanJeanZ2Zk5qdmJiamp6clZ6em5iYmpmdnJeemZqWmJiXmJeRk5SVko2Qjo+VlJKOkZCTjZaXmJaRkZScnJeWlZqampKUlJSRipOXl7m+lpKYlZeUlYuFlZSVmZiXl5qWlJiPj5ubmJeSkZGXlp9BmZyYlpWTmJabl5iZkJSWm5qSlpeOm5uRjZuXm5SVl5aXlpaWlJGRl5OTlZWTlI6RkZKVkpObkZORlJWTk5SVkZKAhoaKi4aIioqJiIeFg4uDg4WDiYeEiYqHjomEhoyOioeQiISGhYWFiomLio6JjYaJjomMiIWNh4uKi5CRk5GUj46KiYuMjI+Mko6HiYiIiYaJiomKh4WIi4qKiYWIgoGNgYWIg4WChIaGjIiEg4OGiIaCgYeIh4KHi4uGiYWFgYg9hoWIhYmBioGBfoOBiIOChYeCgYaFh4B/hoODgYt5fYeFg4aKhImHh4qXh4aFhIGDgYSAgoWDfImGhoOCgISCgIaHf4GBhYB8f4F9gn9+f4KFhICDh4B9goSBgIGDhnqIgoOCgYODhYSDgoGHgoaGgoOAgn6ChIaEhIeFiIiEiYSDioaChIiChoiIiIqGiIOHhIaEh42HhYaLi4mGh4OJh4uEiIeFiISEhoqFgoqDhImKgYiLh4uHioaIhoeLho2HgIiHhYiFiYiJhoOGhIaIiImMhIWHiIiHg4eIhYqHiISAh4iGhoOGioiJioeKh4qLh4mIiIeKhomOiIeIiIyNiYSKh4mJiomNiomLioqKj4yMjouPjoiOjIqKjIyMiYyPjI6Li4yMjo6Li5GLjImMjY2NjIuKi4qHi4qMiI6MiIqJCo2LiIqMio2MiouEjSCJj4qKkI+Mi42OiYuLjY2MjYyLjY2OjIyQjImMiYyMjYSOgIyPjpCRj4yOi4SQjY6PjYuNjoyMjoiHi5CPjY6VkZGPkpCSk46Ok46KkY+Ql5KTkJCOjo+NkY+UkpSQj46QjIyQkZGTjo6PkI6Pi4+OkJKPkZCMioyMkJGOjI2Qi5GQkI2OjpGTkJOPjpGRjpKVkpSRkpGQlJCTlJKSkY+Vk5SSgJaWkpKUkpGRk5KTkpCQkZCPkZWRkJOSlpCRlJGOkpCTj5KUj5KPj5OQkI6Ri4+PjI2Oj42RjZCQjpGQj5CQko+Rj5SUkJGPkZGMk4+PlpKTlI6Oj5OUppKQjZGRj4+OjZOTkI6XkI+Qj5GUlZWVkpOVlpOWlJSXj5aSkZSUkJOVgJWRlJSNjIqLgoyKjI2RkpSMiImJkY+OkY+RioiVkZWTj5CRk46RkZWTlY2RlZeUlZKVkJSEe5iTkZCQk5GQj5CcnpiXmJaampqSkoqNl5aTkZOSkJKWj5KRh5CPko6Rj5GSkZCMkpSOj4+QkpSOlJSSj46Rj5KRjZGMj4yQkI+OgIyJi4yOioaIhYaNjIaHioiJhIyNjYqGhoySj42MipCQjoaKjIuJg4iKiaSkh4WMiYuHiIN/jImJkIuMjI+MjI+Fh46NiIyKiIiMh5SNj42Ni4iOi4yKjo6IiY2QkImNjYSPj4eGkI2Ri4uKi4yMi4uKiIiMiYiIioqMh4iGioyGDYmRh4eKjIuLioqNhomAdXZ1e3d7eXl5dnN1cXVzd3d2enVzeXl2eXV2dXl6eHd7eHV7dnZ1dXN4eHt3fHV1eXp6dnN4dXp2doB/fn17enx5e3p9fH17fH15enV2eXV4eHd2cHR5eHh2d3Z5c3B2bXV3cXBydHVxenZycnN1dXVzb3N0dnJ2eHh1dnF3dXeAdnh3dHdweHFzc3V0fHR1dXh0c3Z2fHByenZzc3lqb3x2cnR2c3ZzdXGAdnZ2eHR0dXRyc3d2bXh2d3R3c3V1d3R0eXR1dHN0cHF0bHNxcHFzeXd1d3l0cHR2cXNzd3hpeXJ1c3N1dXdzcnFzeHV1eHN3dHBxdnV3c3Z4d3l3dnyAdnZ8dXRzeXJ3dnZ3eXd4dnp2dnV4e3l4eX17fXp8d3l5fnV5eHZ5dXd5enZ0eXV3enl1d313eXR5dnh0d3p2fHd6d3h4dnZ8e3hzeXp4eHt6fnd2eHp4eHd2dnh4eXh0c3h7eHh1eHp5d3x4e3V7e3Z7eHh6e3l5hXh3eXh5eXuAdnp5eHV5eH18fHp6fXt9fn59fX+AfH18en+Afn99f4KCgX59f39/fnp8fHx/e35/f399fXt6eXl7fHx5f397e3t8fXt6fXx+e3t+f35+fXx9e3uCgYB9f4B+gX6BfXx9fH6Bf4B9eoKBfX5+gH1/f4B+fn1/gICAgX9/fHJ9fn6Af318fn96eHt4eH2Bf31/g4GBgIF+gIOAf4J/e4J+f4SEhoOCgX9/gYN/g4CBfn2Ag4KAf36DhIGAhIKAfXl/foCBf4KEfnh+gYGAe32AfXyAg4R/gn6BgIGDgn2Eg4GChIODgYKCgIOBhYSGg4GAhoOFgoWGg4GFgYSChIGDgoKAgYGBf4GEgYOEhIqAgYKCgYSAgn2Agn+CgoGBgoF/g3yAgHyBfX99gH1+gH99gH+Bf4F8gIGDgYCBfoCBgIF7f4GDgYF7foGHhpaBf36CfX5+fXp+fn58g35/gH9/hIODf3+CgoGChoGEg36EgoOAgn+EhoR/goJ8fHx/e318fXxkf4B/e3p5c3N7enx+f3x1eXF7e36BgH59gH+HgIV+gISFg4SAhIKBcm6CgYOBg4SDgoODhoaEhIaHh4SHgYN9foOFgoGDg3+DhoF/f3d9fn57f3p8gIB8fICEfHl8gIGEfoF/f4R9gICBfIF9f3qBgH5+e3h5fX18eHt4dH58c3h8eXt1eXh8d3V3e396e3x5f3x9dnd7eXx2eHx2gn12eHl6gHp5c3F9end9eXx7fXx7fHRzeHh1eXx4eXx3fnt7eX18eH12dXZ6fXl2e4B+d357dHp5eXl8eH16e3l4fH57fHl2eXx8GHh2eHl7eHd2e3t1eH95eHd5enl3eHp2eP9//3//f/9//3//f/9//3+IfwICBACAjpGVl5SSk5GXlJWWjpSbmZqWi5KompuRlJqgppmTk5aSlZeXjZKWlJiYk5yTkJWUlpWZm5qYlZeVlZOYlJORi5WTkpianZaZlZGPkI+UlJONkZaTjpCTjoOKjI+Sj4mUjY2YkpCSkZCVjY2SkIyRkIqUk4yNkZKRkpaRlZePjIyAjI+PkY6OipCPjIyKiY+NjoyNiY2NjY6KjpGKkZCNgoqOkZWUjIyNi6Oej4uIiIGFj4yNj4qPi5KKjY+Ki4uRi4mLkJCJioeGhoiHjI6MhYiJiY2IhpGMjIyRj42Jh42Lio2QjI6Ki4+Ni42HiYaOjImLkIyMkYyNjI2RkJCQlI9JiomTkIiK94qPkYqTkI6NipKSkY+PkouTkJeQkY2QlI2QkpaTl5KSj5CRkJCTjZKRkJSQkJCUlJGSk4+Mko2Nko+OlpGPkZOUkYWQgJKKjo6Sj46TkZKRj4+RlJKRkpOXkpGUlZCSlI+UlZSMkZqVmI+TkJSVm5aQj5SSk5GUk4+SkZSSlI2TmJKVmJKWlJOVlpWTlJGUlZaUkpCUmJaVlI6Vk5aUk5GUiZGRlpiWk5iWlZqWk5STlZKVmZeWlZiUlZOSlpKWk5GUlZWXVJWUl5eUlpWbmZWYlZaUkpOTmJiWlpOWlZaWlJaRlJiblpeXk5KWmJiXmZmWm52YlJ2bl5aYlZmblJOWmpeVmZmYmpmbmJqanJqalpabnJqanZ+ZmYSaQ5Wdm5ubmpibnZ6dmZybmpiWmpybmZiZmpuZlpiempqblpWZlJiWl5iamZebmZuXnJuYmJ6fk5qYlpiampmYmJuWm52EnC6XnZ6cmp2cmJ2fm5mZnZudmpuYm5uZmpecnZmenp+Zm5ybm56Ynpycnp6al5qXhJmAmpaVlpiampWcm5mZmZyXmJmbnKGfnZmdnZqanpqYl5ibnp+gnJygmZean56fnZecnJicmpucm5ydnJWam56cmJubmqGhnKKjn56hoZ6ampebnp+enpucnJWVkIuPlpmYmJShjamXjpagkpeYko2Ri6KcorKfmp2fmZ2bnJaYmp6AmZeanJ2emZqYlJ+XoZeam5mVnJeboa2mpZ+bm6Cel46FgJq4n52fnJ2Wnp+Xm5ilopqUm52bnpmel5mVmpyXlpWUmJuan56goZ6dnp2bnZ6clpWUlZeVlpGTlZWVj5CTk5KVkpSVmJSSmpqXl5WdnJGYjYmJiZKRkJKUlJ6er7kKr5uZk5+bloyHlYWWMJicmZaUmJeUlJSZk4+WmZmZk5idlpuamY+UlpWWkI+Qk5mSnpiZmKabm5iYlpeZl4SSHpiWkZGTlJOQlZCOkJGRkpOOlo+SlZSRkZGTkYuNkICEhouMioeIhoqHiImDipCNjIuEh5iLkIWIio2UjYeGiYWKi46HiImJjo+Jj4iDjIqPi46SjIyJiIeLiI2IiYiCiYiGi42PiY6KiIeJh4qJiYWIjYiBhYqFfIODhomDf4mChYyHh4aFhIiAgYeFg4eEgYiIgoOHh4mMjIWJjYSCgICHhoaHiIV+hoSEgYGAhoKFhISChIWFhIKGhX2Jh4N9hIeGioeCg4OClpGHgoCBeX6IhYaIg4V+ioOFhoCDgoiEgoGHioCBfn5/gH+EhIN+gICBhYCBiYKCgYuEg35/g4SDhYaChIGBh4SDgX6AfoWEgIKKg4SHhYSCgoiIhoeMiICDg4iHgYTtg4WGg4iFh4eCiIiJhoOKg4mIjIeIhIiKiIiHiouNh4eGiImIh4qFh4aFiIWFhYqKh4eJhIWJhYKIhoWMhoSIiYmHhYeFh4aKhIWIh4OEioaIh4WGh4mHiIiIi4aFiIiIioeFi4uJgoWOiIqFiIeJi4+JiImLiIqHioCHh4eGiYmKhImMiIuMi4mLiIqKi4mJiImMjoqIiYyPjYyNiIuLjIuKiI2CiIiLj42KjIuLj4yJiomLhouPjIyKjoqMh4mMiYqKiIqLjI2Mi42OioyMjYyKjo2NiYiLi46OjY2Ki4yKjYqMiouOk4yMjYyLjY+Pj5CRjpGTj4uQj4CNjI+IjpCMiouPi46Qj46Qj5CQkI6Oj5CNjpORkZGSlI6PkpGPj4uSkZKTkI6Rk5WTkZKRkI2OkZGSjo+PkZCQjI2SjpCUjYuPi5COkI6OkIyPj5GOkZGNkJWRi5OQjo+PkY+PkI+OkZSTkZCRjpOVk5KTk5CSlJKRj5STlZCSkYCQkpGRjpOUkJOTlJCTk5GPkpKVkZKTk5CPkI+QkJCSkI6PkJCPkYuSko+Pk5OOj4+SkpSVkI2Rk4+Qk46PjIqRkpSSkJGUkI2MkpKTkZGPkI6SkI6PkI+RkYuSkpORkJKRjpSWkJSXlJKVlpKQkZCTkpKTk5CRk4uLiYiJjY6MjYCJkYSQhX2Ci4SKjomFiIGMi5Sej5CSk4+SkZGOj5CTj46SkpOWkJCNiZGOl46Sk5OPk46SlZ+am5WUlJaUjomCe46jlpWXkJKOkpCLko6WlI6LkpWTlpCSjpCLkJSOjIuNj5KQk5WWlZSUlJKQkpOQjIyLjI+Kj4iKi4uKiIiJioCJjIuLio2Jh4uOjo+KkJCHj4SAgH6HhoWHiYuSjp6imI2Oh4+PioSAiIqLjImNjpCKioeOjYmLioyKh4yPjI+Ii5CLkI+NhYeKiI2IhYeIkIeRjIuNmYqPjoyOjoyMiIiHiIyNhYiMioiDioeHiIiJiImGjISGi4yJiYeJhIOEh4B1d3h5eXd1dnp2eHhzeHx6eXd1dn92fXJ2dnZ6d3Nzd3F1eXx0dnlxeHx3fHZyend5ent9eHZzdnZ5eHl3fHh0dnVzdnl9d3p3eHh6dnx5eXZ3e3JrdXh1cXhxdndycXhzc3h0d3N1bnRvb3V0dHlycXl1c3Z3dHh6e3N2fXZ0b4B5eXR1eXVvc3Z2cXN0d3Z2dXVycnV2dXN5dW54dnZvdnZ1dHZvc3Bxenp2c3R0a3F8eHl5dnRueXZ4d3F1dHd4d3V8fXR0bm9xdnR4dXdvcW91eXN0enR1dHlzd29vcnZzdXR0dXRzeHd3cnRzcnd1dXR7dHV1dnRzc3h5d3h7eIByd3h4c3PXd3d1eHdzeHd1eHZ4d3V5c3t4fHh3dX17eHh2d3t7eHl4eXV3eHh3d3V2d3Z4eXl5d3h5dXZ4eHV3dnd7eHV3eXl3eHh5eHl5dHd6enl6end4eXh6eHp4eHt2eHd5enh8e3h3fXp6dHV8eXx4e3l5enx4eXx8e3p4fHN6eHd3eHl5dXd+enx5e3p/e3p6e3l7e3Z4fnl7e31/gX19fXt/f359en50enl7f318e3p7fXt5e3p9ent+fH57gX18d3t9eX18enx+fX1+gH6Bf3t+fXp9f36Dfn99e39+f399fnx8fXt7fn5/goB+f4B+hH2Af4J+god/fICCgHyAeoCBfXx9fnt/f4B+f4B/goKAfoGAf32BgYCBhIh7f4OCgH9+hIGEgn59f4GDgICGg399goOCg4CCf4OBgYCAhH+Chn57gH2DgX99fX99g4OEgYF/foGEgXuDfn+Bf4GBg4F+f36Cf4SDg3+ChIKBgICDgIGAgoGDhYKEg4SDf4OBgn6DhIKFgoSBgH98f4KAhH+Cg4OAf35/gYGBgn1/gYCBgX98gH6AfoGAgIF+g4KDgoB8gYB+f4B+gX97f36Dg4GAhH9/eoCBf35/e3+Agn18fYB+f315goGCgYB/fn2FhX6BhoGCgYOCfoGBgIGAgIOBgoSAfn9+fXt+f3p5dnhzb2tjZ29wdH55dnhwc3V+gHaBgIJ/g4GBfIGChIB+goKBhYGAfXZ8gYl/hIaGhIeChoSJh4iDgoKEgn58d3B4hoKEg4GCfn96en59gH98eX6EgYN+f31+en2CfHt5e36AfH+CgoKBgIODf4CBgHt6fH5/en+AeHl6eX16e3x7enx9fXp8eXV2fH1+eHx9dn11cW5scXh1eXx+gHmFgXZ6f3d8f3p1cnh4d3t6enp+fHl3eXp2enp4eHt9f3l8eHd/e3h7enZzeHh8fHV2eH54fHp5foBzeHl4fHx6fnZ5d3h5eXN0e3p6c3l2eXl7dnd4dXlzd3gJeHl2dXhzcnR3/3//f4h/AX7/f/9//3//f/9//n8CAgQAgJaNmJOWkpONjpKOlZKKj4iNmpSUkpGPkJGLipGVk5KQkZiVjZCWl5WZlJeWlJuPlpGak5mQkpGYmJOWlJWYk5eLjpGWlI+SlJKRlpKQjZOSkpGQk5WRmI+Ph5mWko6RkIiLjouQk4uNkYyQkZGMkJCNkY6SlJCRioySj5GOjYuQSo6Nj5KMio6NjY6KjYqIiI2KkYuSkJCTjImNkpWKlPv8+YmLjoqRk5CwloiLhoGCh4eIjo6Ni4aIh46OioyJiIeIj4+Gjo2Hi4uIhI4ojIyLi42OjI+Mj5GRjIyIi4+MjoyPjY2KiYyLj4mHjo6KkYeLiIyMj4SOgIiMjIqLjouPjI6NiYmOi5OLkZCNjoyKj4yPkY2PkYiNioyPjJGUkIuRkZaPkpSPjpGNkY2KkIyUkZKRkoyVjpORj5GVkI2UkJSPk5SRk42Pio2Rj4+PkJSNkpCPkIyRjZOUkZCUkJOTlpGQk5GUkJKUlZSVkpeSlJeZlZOYk5SXgJGQjo6OlpWSk5GSkZSOlJKVj5STkJeWkJaUmJWVl5WUkZSVk5SVlZSTj5CSk5SVlJWRk5eMlZOWmpmWmJaVlJiXl5aalpmYlJKUlZOOlJSVkY+VlJSTk5SVkZGSmJmRlZaXk5SYlJSWl5SUlZaVkpWUlJaXl5aZlZaUmJmcmJmZU5iamZqWl5admpWVmZqZl5mal5eYmJmZnJmXn5yZnJifmpiYmpiXmJicnpualZmamJiak5iampygnJiYnZyYlJaYn5mRlJmWmZWYmZqXlpeYmZeahZeAmJqalZabmJmdm5qamJiVm5edmZqemJqcm5qZnpmam5ybmJqcnZ2cnZualZqbmpycl5yam56ZlpiemJubmpydmpqcnZ6bnZiamZuYmZaXmJaZmZycmJ6bl5mbnZycmZWZm5yhopiXmp+dnZuWnZuhnZylnpacnZyamZWR+a+ImpmAm5ybmpycnZ6YmZuYl5eZnZ6doqKgnJ+hnp2enp6doJ6enZ+dnZeSkoqTk56VjuKPkqCNm5aTmYyD/vr3kZusoqyjnpubnaCZn5qYnZaam52fnaCdnJmZnZyempmXlJSZmZeYmZqdoKWlm5uZlZOJgZiclJaamZifiamXmpiilpWAo5+anp+bmJedmJmUmZqXmZuXmpqam5qYoJianZaYlpaYk5SVlZaYlZSXlZKVlZGRk5WYkZqbmZqhoZiZkoCC7Ozl6e6IjYyJj5CZoqK5qKKdnJ2TiYqXkY6MkZiVlpaSnI+Vk5aTkoaLkZWVmpyUj5abmZaUlZSYkpiSlpCTlpUulpWOm4GRmJmTlJeQlZSKjo+MlJKPkZOTj5KWko6RkJSOkZGVl5CPkY7hgYyNkICLgo+Ii4eIgoOHhImGgYV+hY6HjIiEhIGFgoOGjIeHhYOMiYKFioyIjIqMiomMh4mEjYeOhYiHi4yGiYeKjoaKgYSGioeEhYaGhouIhYKIh4aGhomJh4yFh36Ki4iFhoJ/gYSBg4aCgYWAhoWFg4aGgIeFiYqFh4GFh4WJg4OAhoCHgoWJgoGEgoOHg4SBgH6Fg4aChoeHioSEhYeGfIbv8/GAgoWCiImCnImAh4F+fICBgISEhoV+goCHhYOFgn+CgIWFgISEgYSDgIOEhYSDhIKEhIWHiIKFiYaDg4CDg4SDhIeDhIKBhYOGgH+Cg4KFf4J9goSFhoWChX6Cg4ODhXqFhoSFg4OCh4WJhIeGhIeGhIaDh4mJiIp/hIOEh4KIjIaBhoqNhImNh4eHhYeIf4WDiIiHhoiDiYSKiIWGioaEiIeKhIqLh4iCh4OEiIaGhYiLg4eHhoiEiYaJiYeFiIeJiYuGhYmHiYWIhoeHiISKiImLi4mMjIeIioSHHYaLi4qKhoiIioKKiYqGioqGjIuDi4uNi4qNjI6MhIqAjIyLiIaKi4mKjY2NiImLg42Ki4+Oi42NjoyMioqNj42OjYuLh4mIh4yNjomIjYyLioiLjIeJiYyNio2MjoqLjYuKjI2KioyMjI2NjIuPkI+Lj42Ki4uMkZCQkI2Rj4+MjoySkIyMkJCNjJCSjo2MjpCQj42OlJGRk46VkY6OkI8pjJGQkpKNjo2Oj4+PkIuOkpGRlJGNjpOTkIqOjpSNh4uQio6Ljo+RjoyEjymRjY6PjIyQj4+NjY+PjZGRkJKQkI2TjpSSkZKPkpOSko6UkZORlJOQkYSUgJCSkIyTkpKUk42SkJKVj46PlpGRkpCRk5CQkpSUkZWPj4+Ujo6MjY6PkZCTk5GTj4yQkZKRk42Kj5CSlJSNkJGTkpOSjJGPk5KRmZKNkpKRkI2Lie2kfpCOj5KQj5CPkJKQkpCNj42PkpGOlpiXlJKUk5CTlJSTk5KRkJSSko6MgI6Fi4iOhX7Me3uDgIqBgYd9duvs6IWKmZWZkZSRko+Tj5GSk5SQkZOUlJOWlJGPj5OSlZKRj4+PkZKPkJCSlJOamJGSkY6Nh36Nj42NkI6Qk4CYi46Kk46OlpGPk5KPj4+Sj5CKjY6PkJGOkpCPkI+PlI2QlI2MjIyPiYuMi4qPgIuMj4yIjIqHiYmKjIaOkY6Ql5SLjId4d93g3N/kf4SFhIqHjpKPnJOSj42OiICDi4mEhIeOjY2JiY6IioiMiYZ+hIiMjI+QioSLj4+Ni42LjomNh4qGh46Mio6HkniKjo2IiYyGi4qBhYeEi4qGiIuLhYeKhoSJh4qGiIeMjYiGB4eGzXSBg4aAeHB8d3h1dnBzdXR4eHJ3dHN5dnx3cXZtdnN2dXhyd3NxeHlyc3h7dXd3fHl3enV1dXx1enJ0d3h7dHZyd3xzd25xcXZ0d3V4d3Z4dnR1end3d3R3d3Z3bnRweHl1c3dzb3J2cW50cXBycXd0c3B2dnF4eHp7dXhxdXd0d3V0c3aAeHR1d3R2dXV3dnV4c3FxdHR1c3V2d3hxe3d3cmZy2tXWcHFycHZ1a311c3txcW5vdHJ3dHd4b3J0eXR2dXVvd3N3d3N2dHV0cW91d3Z2dXd0eHV3eHZydnV0dXZxdHV3dHR0cnN0dHh2eXZydHV2dnV4cXR1dXd1dHRvdnVzdXaAeHh2dnV2dHh2enZ2d3d7d3V4dHt7end6c3Z1dXpzeHp0cnV7fHR8fHp5d3l5fHF3dnl8eXZ9d3h2d3t2dXd2dXh4enN5eXh4dXt2dnp6eHV4enV5enZ8dXl5eXt8d3d4enh5eXZ7enl3d3d1dXh2eXd7enl6gHl5eHl6eHp6eHqAeHp2c3d4fHd5eXl3e3l4eHd3enp9fHx5e35/enl7fIB+ent4e3t+fH2Afnp5fHd+fHp/fn5+fXx7fXh6enx9fn98e3h5eXh9fn59e35/fnp6fH17fn6AhH2BgH96foB7eX6BgH99fn19fH5+gH+Ae398e3t8e4J8fnx9gX+Cf3+Aen9/fnx/gX57f4B/f3+Af4GBf4OEgYGCf4R/gH1+g39/f4GBfX9/goN+f4J9f4GCf4SBfn+EgoN7fYCFfnt8f35/fH6BgoGBhIKEg35/gIB/goGBf39/fYB8gICCgoCAgYSBg4OBf39/hISFg4eBgICCgIKBh4KEhX6Bg32EgnyAgIB8goGEhXyBg4aCgIKBg4aBgoWGhX+Af4CBhIB9fX+AgYCBf4B/gX5+gICAf4R+eXyBgX+AfX6Ag4KDhIB/f4B/foeDe3+AgYR9fXzYk3F/fX+Bf36Af3t+fn5/fX97gIF9foKDhIKAgIF+gYCEg4B/gIGFg4ODf4V7e3d2bWcfsWJcY21uampuamfPz85vc4CCfHqCgYF8fH6DgYOFgYSDCYCEhIN+foOEhIWDgISGg4B9gYOBh4WBg4KAgX51eXt+fYCAgH1wf3p7dn18e358fIF9fH9/fnx8dnl8fn9/foKBfIF+fYJ+gISAfHx7gHt5fHt6gnx6f3x5e3t4eXt6enZ8f3p9gYB1d3NoZ8LIyMrLcnR2d3x5fnx5enh9eXl8enJ2eH5zdXh9d3t5T3t7d3Z1fHx5c3l9fYB/f3l3en57d3t/eXx4enN5eHl/e3l/eH9seXt8d3t8eXt4dHN0dHx6eXp5eXh6endzd3V3c3l1e3p2eHt4tmVydHX/f59/g37/f/9//3//f7B/gn6xfwF+in+Dfv9/hH+FfuV/AX6EfwICBACAjoiRi5GTko6WkpWSl5SRkJGLjJCSk4+MkZeOkJCQjZSTlJeWloqMkJOQlJWclpCRlY+UlpWYlJqYl5yTm5WVlJaRkJWQl5OVmJOVjZGMjpKOkpCSmZKRiJeQloyKjZCIio6NkY2Pj5CRjpWOiZKSjIqLkJKQkY2Tko6Ki5GKjJGAiYuAi4qEio2Qio+Kjo+SjIyMkZGOi4yNiY6Mk4iEgf2HhoGGiP6Jop+MhYiIhoaGjImMjY6NjZOQiYyNjYyLi4iOj4iHhouOjYuKjIiNioqJjYyLkIuOj4uLjImIkZKPjo6Kj4qOjYyRj4qQkY2OjZCLj5GUjo6QjI6QkY2SjY83i42Ri42KkpCNj5COlI2MjI2OkJOTjo+WjomNkJWSkY6Sj5OWkpCRjZOSlpGVk5CRjpKWk5OSkoSPgJWRk4yTkZOTk5WMjI+QkZGQjJOQkI+PjpGQjomWko+UjpKPjpKNlJGQkY+UlpKVk5CTkpOUkYuKlpeTk5qUlpKTkpKQk5OQk5KUlI+SkpSXlJKTkJSUlJGWlpeTlJSXlpORlJSUlZOWkpKTk5SUkZCUlpKUmZiUkZaXmI6Qk5KWgJSZmZOSlJKVlZaQk5CSmJeTlZORkpSTlpWQlpWSlJGXlZWWk5aXlJiWk5KWl5eUl5iYk5eSl5OYmJealZeXmpaYmJyZnZeZmJmYkpmYlpiYl5mYl5udlpicl5ubmpaWk52bmJmbmZqZm5eYnZualpmbmJeXmZmZl5qcmpablpqXgJmYlpqVm5qYm5uamJmWm5eVlJaXmZeYmpaXmJiZlpWYlJualpmVnJuZmp2dn5ycm5qYmpyXnJqcnJ2clpydmJeVl5qYmJyclZaYmpuhm5qdnpSWmpuYnZmanZeXmpebmpmZlpuXmZuZmpmampiYmZ2cmpmVnZydmJydm5yim5qVgJedlpmdoJyYnp+cnZmcl5SRvNSpj5Sbm5qamZucoJucnqCflpednpmfn52inJqcnJydm5ycnZ6fnZyclpKQj6GUjouKgISNjY6bmZmGhoL87ODi9YOQsLComZmemJucnp+joZubnKOin56coJ6YmZufoJ+WlZSXlpSYmpyXnp6rgKKbmp+dl5CPmpmbnJiZl5Gcnp+UqaKVmZaak5mblJeWlKOdkZSXmJecmpidnJicmpqcl5acmpiakpGWmZiXk5WRkJKTmJKVlYyOlJqVk5ugoaChhPL6gYD9gfH6jIyMj5eao6Kovp+eoZ+ZkoOFjpKOkpKTjZGTlpOWj5COi4yPRZeUlZSUk5CVkJWUl5GQk5WPkpaXjpOPkJWSmZialJaWmJaTj5OSgoiNlZKTlY6VkpSPkZKQl5GUjpCTkZSQk4qGiJGTkoCHgoiBh4eIhYyHioeNiYSGhoOBiIqJhoCEj4aGh4iEh4eIiouMg4SIioaLi5CMh4aKg4qJiIuJjouLkomPjIuJiYiFjIaMiYmLi4uEhoOEiIGHhYiNhod/jIaLgoKBhX6EhYKFgYWGhoWDioSAhIWBgYKFiYWHf4aIhoKBiIGCh4B/gXiDg35+g4eCh3+Dh4mEhIWIh4SChYWBhYSHfnx784F8en2A8YGRkoN/goKBf36Gg4aFh4WDiYSBhYiHhIOEgISGfoN9gYWDgoCEf4KEgYODg4GFhYiIg4GDgYGGiYSEhoGGgIWEg4aGgoaFg4OChYODh4iChIeDgYWGgYiChoCDhYmBhYSJiYSFioWMhYSIiIeJiIeFhoyFgoeIjIaIh4aEiY2HhouGiomJh4yKiIyGiIqHiYeJhoWHhoqFiISJiYiKh4yDgoaIioiHg4mHhoWIhYiJhX+LiIaJgYeEhYmEiYWHhoSIjYmNg4aJh4iKhX+AiY2Jh46LjYeKioiFioCKh4mIi4mFiIeLjYqJioiLiIqGjIuLiYiMjI6IiIuKjIyKjomGiYqKioiIi46KjY6NiYWMi42HiIuJjImMi4qHioiOjI2IiYeIjoyKjImIi46LjY6IjImIioiNi4uNi4yMio6MiomLjI2Mjo+OjI6Kj4qOjY6PjY2PkYmNkJGNk02Nj4+Qj4mOjY2Njo6Qj46RlI+QkI+PkZGOjYqSkI6RlI2Qjo+MjpKQjoqNkI2QkZKQkYqPk5CNk4uQi46OjJCMkZCNkZOPjY6Ljo6Ki4SPN4ySjY2Pj5GNjI+JkJKNkouTkpCSk5SXlJKTkI6Rk46UlJWSkpKNkpOPjo2QkY+PlJCMjI6TkZSEkoCMkJGSj5OOjpKNjZCOk5OQkI2SkY6Tj5CRkpCMjI6TkI+OjZKSk46SkI6Rk42LjIySjo+QlZKOk5WTk5CTj4uJq62YhIqQj4yQj5OSlJCRkpWVjY6Sk5CTkpKYkZGTk5OUkY+RkpSQkpGSj4qLh5GDeXd3c3l8fICHg4N1d3fp3oDP1uB7h6GdlY6Pk5CSlJSUl5OSkpKYlpGSkZSUj5CSlZeXjo6NkI+Nj5GUj5SUnJeTk5eVkIyJkI+Qk4+PjoqTkZOJl5OLi4uPiYqOio2NjZmSiY2Pj46SkI2RkI+Rj4+TjYuRjY6QiYeKjo2Ni4qHh4uIjYmKjIaFi5CLipCTkHSQj3Xi7Ht6833q8YWEhomMjJKSlKGPkJKSi4Z8f4aHh4qGiIOIioyMjIeJhYWEh4yJi4qJiIiKiIuLjomHioqEiYqMhoqGh46MjYmNjIuLj4uGhYmKfYSGi4aLjoeMh4qGh4mHjYeKhoSGhoiHiIF9fYaGh4B4dHdydXZ2d3t3eXR3dHJ2dndyeX16dnF1fnh4d3l4eHV5enp3c3V1eHV7eX96dHR4cXd3dnd1e3h4fXh5eHl5dnV0d3N9eXh5fHx2dnN1enR1dHl9dXJxeHZ7cnJ0c29ycnVzcHV4dXRyeHNwcXNxcG91e3V2bnV4dnZ0fHBzdYBvbm51dXNydXlyd3Fydnt3dnV4enZ0d3Zzd3Jwa25u3nJrbG1w13J2eXF0d3Z1cnJ3dHp3eHJweHZ1d3Z4dXV2cnV4c3VtdHZycXB1cXB6c3NzcnR2dXp4cW52dHNzd3J0dHF3cXd0dHd5dnd2dXZ1eHVvdnVzeHd1cnR3bnd1eoB0dnpzd3d3e3Z0eHp/eHV6eXh5eHV0dHx0dXl4e3d6eXZ1eX15d313fHt5eXx8en13eXl2fHh6d3Z3eHpzdnZ4end5d3t1dHh6e3Z5c3l6d3V5dnh6d3F9enp8cnd0d3t1d3Z4dnZ5gHt3c3Z2dnd5dHJzeHx4d319f3t8e3t4fCV6d3t2e3t2eHZ5eXd2enl7eHt4e3p6eHZ7fX57eHp7fn59gX96hXyAfX6Bfn19enx6eXh/eHt8fHx7enp5d3x5f32AfXl4eH5+e3x7eX2BfX59fYB6e3p7fX17fH6AfnyCfn56fH1/fn+AgIGBfoN9f3+AgoF/gIV8fH+CfoJ9g4KBgHt+fn99fXt+f31/hH6Ag3+CgoB/gH6DgoB/hX+DgX99gYOAf3uAfX57gYOEgIB8gIJ/foB7gH2BgHx+fIGBfYCEgoKAfn5/e3qAfn+BfoN/gH1/fH6BgnZ+f3+Be4OEgYKBf4OBgYSCfoKCfIKDhYGAg3+Bgn58foGCfoCBgn16foJ9g4KEg4J9gn+CgYV/gIKAf359goJ9f36CgX6Bfn5+gX55fH6Ag4CAfX+AgIF/fn5/gYF7dnx7f398gIB/en+BgH5+f4F6fJudi3N9fX17gHiAfX99fX6Agn99gISBg4GAiH9/f4OBgn5+f3+BfoOCgYOAfn18cF9ZXmBnZmRqamJjXWFn0ci2vsBqdYN/e35/g4B/g4OEhISDgYCGhX6BgIOBf36Ag4SHhoCAf4KCf36BgoCCg4eFg4KFhYF+e4GAfoB+fn59f36Ce317d3V4fHdzeXp7e3yEe3h9gH59gYB9f3yAfnt9f3t8gHt7fn14en56e3t7eHZ6eXp6d3p2cnqAeXh9fnp3dmLJ03Nv4XTV2nh4e315enl7fH94e319eHNxcXlWdnh5dnZ1d3x6eHt5fXd0dnh6ent5eHZ4eHZ5en16dXZ5dXp5enl7d3R9e3x4e393en58eXZ5e3V4d3l0e392eXd9d3Z4dnt0fHh1dnl2entzcW94d3j/f6B/AX6FfwF+/3//f/9//3+qfwN+fX67f4V+/38Jf35+f39+f35+6X8CAgQAgJOXlI+QlJOVko2OkZaSlpCTi5CSkJCQk5KPjY6QkI+Tjo+Ok5OVkpKUkpSUkpGTk5SUlZKTlZuXmpiYm5WSlpWUmJmXko6TkpWWmo+VmI6GjZeRj4+SiJWNiICJi4yNjJSPjZONjpKVkIyQjJOOioySkJWTjpCTjI6MjY+RjY6KgIiLh4KLh4uHi4iPjZGEjYiIjo+OhY6KioiUmIWHhfuDgoCCgYWKk6CFhoCBhIKKioyQj5COiI6Qi4+Pi4mLjpKSjJCPkouRjIqMj4SQhomNiYiFkIiNkYaMhpKMjYuRj5KIjYyPjo+Kj5CKiouDj4yMjJCOjYqOlJOOjI+Rjo+TgI6Mjo6MkI2OjZCOlJCQj4yOj4ySj5COkY+PkpCSkJCMkpKTlJiUjpCYjoyLlJKUk5SWkpKSlpSTkJCTko6UkJGNl5KSjYyRlJCNkJOQlZaTkZCSkJCTkI+Rk5KQko2SkpWVlJCSkJGRjZKWkpOSlpKRkZWVlZOLmJKTkpSQmIybgJONkpSZkZKRlJGVk5SVlJeYkJaVk5iUnpmRk5SRk5SVlpSZk5WWmZeSkpKRkZOWlZWVk5KQlJWVlJaVlpmdmpaUlZSWk5OXkpGTlZSTk5SVlpGVlJOVlJWQk5SWlpSUl5eUkpSUlJKRk5iXk5iWlZWUlpSZmZWXlZeYmpSamZmUTpWYmZ2Wl5ebnZWZmJiYlpeampebmpqXmJiXmaKfmpibnZuZmZaYmJqVmpmXmpiVnJaZm5iZlZybmJuXlpianJaalJubm5aWlpeYmZmTl4SVNpqXk5qamJiVm5+amZmWlpicm5+ZnJ2cnJmdnpuZnJubnKGWmZmfmJqdmJqZm5qZmpubm5iZnYSbYZeYnpacmJqal5uYlpubm52ZmJqXl5WVl5WXmZWXlZabl5eZmJuXmpOdmZmVnJeamJqbm56blJmUlZyanZubl5CRl5yZmJqfm5WZmZqZm5uYmpyfn52enJ6dnaKhnZygm56Em4Cdm5qampaSmqeojZCKioWCi4eHkoqKhYOFgf7u5ODd5fKbqKWho5uZlaGjoa6woaSgpqOjpKGgmaKen52clpWWmpuWmZuelZeZoaidoJqOmZGQl5WdlJyUl5OYk5SXnZikmpGTlaiWmpiXjpSbnJiZl5WZmJKanpiUl5iYlZiWmoCZlJWTlpOWlpiYkJGSk5SYlJWNlJGSk5Sdn6u1of6ChYmDh4GAgISKj5WZmpuhn6mhlZ+YlIuD94aMkpeUkJmWlJWSkZGUlZKRk5eXlZaSkpSUmZCPlpSXjZSPlpOPkY6MjpCPi42SmpiWlJKJj5SWlp6Uk4+QlJKVkYqSkJONjw+SjY+XkJCNi4yPjY6PjJGAi46LhoSIiIqIhYaHjYiLh4d/hYaEhIaKh4SChYiGhYmDh4OIiYuEiYWKiImHhoiIioqJh4mHj4yOjIuOioOLjIeOj4qJhYiJi4mNhI2KgYCGjIiHhYmAi4SBe36BgoOAhoSBhoOGh4iEgoeDiYR/goaFi4iFhYeChYWDg4eEhICAgYJ+d4J/hHuAfoSEh32EgYKHiYSBhoKDgIaOfH186np5d3p6foCEknyBe3t/f4SEhIeIh4R/hISBhoaCgoCGjIqDh4SIgYuFgoaIeoZ/gYOCf36HgISHgIOAh4aDf4eHiYCFh4eEiISIiISBgXuIhISDhYODgISKiIOFhYeEhYhGhIOChYOIhYKEhoaHiImHhYaGhIqHh4aIhYWLiYqIiIeLioiKjoiGiI6FgoKMiYqMjo6JhoiMi4qGiImIhYmHhoWLiIiEg4SHgIaJhYqLh4eHioeGiYaEh4mJh4aEh42LiYmGh4WHh4SHiomHhoqKiYaKiYuJgoqHioqKho6CjYiFiomLiYiGiIWNiIeLiY6OhYqKio6HkpGKi4uGio+Mi4mNi46Mjo2HjIyKiouNjYyMi4uGioqLjI2Ni4yPjImLjImJh4mNiYeLgIyLiomLjY2HjIuMi4mMioqNjYyKi42OiYeKjI2JiIqOj4mNi4uNjI2Lj4+Mj4uNjo6Kj4+Oi4yNjZSNjoqOkIqOjY2Rjo6Qj4+QkI+OkY+MjpaVkJGSk5GQj4+Qjo+QkpCNkY+NkoyPlIyPipKQj5CNjIyRk4qOiY+Sj4yOjY6OUpCPio2Li4uKj4yJj4+Oj4uRlZKPkI6MjZOSl5OVlJGQkZCTko+TkpKTlIuPj5SOkZSOj46SkZGQkZGTjpGRkJKSkpGOkY+TjY+Qj5GQjpKPkZKFj2SLjo6Lj42Lj46OkoyMj46SjZCJk5CRjJGLj46PkI6SkIqNiomOjpKRko6GhoqRjo2OlJGLjo6OkpKPjZGRl5WRkI+SkZCXlJKQlZCSko+PkJGQkZGRjouQlIp6eXp6d3N6dnd+hHuAfHjs4tfRz9bZjJSTl5mSkIyVlZOdn5aalZiWlpeVlZCWlZWUlI+Oj5ORjpCRkouPkZaclZONh5CLjI+PkIqRi42MjYuNjY6Ml46Ki4uXjJCQjIaKjpORkY6MkI6IjpKNio2Mjo2OjY+Qi4uJjIaKjI2OiImJiImMiY2Hi4eJiox3kpGYn4nqeX2BfoF8fXt9hIiMj46LkY6SkIePioeCfO1/g4iNioWLioqJi4iHiYuKioyMkIyLiIeLiYyHhouKjISLh4yIh4iGg4eIiISEiY+PjIuHgoaKio2QjIuIiouLjomAh4aHhIWIh4qOhIWHgoGFg4aGhIuAfH1+d3N2dXp1d3J0e3l5dnhxdXd2c3h5dnV0dnl1dXhydnR6fXxzenh5d3d6dnV4enl4dnZze3p5eXh4d3N8enZ8fHp3dHV1eXp8c3p2c3Z5enh4dXtzeXV2cHF1dHRxenV1dXR4dXZycHRyenhvc3VyenR1eHl0dndzcXZzdXGAcXJva3VzdGxycnh7eHB1dXV4eXV1enVycHR5bW5sz2ppamlrcHFzdG12cXBzc3N0cXh6eHhxdnh1d3dzc3R3fHt3eXZ5cnt2dXd9bnRxdnVzcnF1cnZ2b3Nyent0b3Z4enN2eXh1eHd7eXhydW94dHh1dHR1dHl8enN0dHp0dnsPdXV2eHR6eHN3dXZ2en12hHWAe3h3eXd2dHt6enl0eHl6dnp7dnR6fnZ0c316fHt8fHJ2eX58e3h6fnl3d3l4dXh3enRzd3d2d3R3cnZ6eXt7fXh5eHZ1d3l6eXh2eH57eXp2eHp3eXd4d3Z2dnd7fHp5d3h5c3t4e3l5dXtxenp6gnh6e3p1eXp9eXh7d39+d32Aenl9eYOAeXd5d3p9eXh6gHuAfoCAfH5+fHx+gH99fXp6d3p/fXx+fnx8fXx4ent6eHh8fn54fXx/fXt8fX54fHt+e3mAf35/g4B7gH9/fHt+f4J6eXp/gXx/gH99fIGAgX97gH1/gX97f35/fn59gIJ8gn6CgHt9fHp+f31+f4KAgoKAgoV/fn6Ghn99g4SDhH9/gn6AgIKCfoKDfoF9f4d9gXmBf3+AgH59gIZ8fnh/gX99f36AgIKAen99fXx+f35+f4CAgX6BhoN/gIF/foKDh4OAgoR/gYGDgX+Bg4KDgHl/foKAgYJ9f36BgH2BgIGBe4CCgYKBhYF+gX+CfHwZfX+Bg4CCfn+Dfn6AgH54f3t8f397gIGBgYR+gIJ9fXuEf315fXmAf3uDf4J/ent7fHx6gHx/f3d1eX19eXyDf3t9fH6AgX19f36DgIGFfoCAf4aEgICDf4KDf36AgYOCgoOAfoB4ZmBcYmVkYGNiX2FlZmlwb2zU0r65t7m1cnh+g4WAf3uDgH6FhoGFgYWCgoOBgYCChISFg4OBgICEhH9/f358gIOAhoKCeHp/fH9+fX15f31/fnt6fHx5eH16dXd5enp8f3l3eneBfoR/foB+eXyAfXl8e359fXx8e3t5eHx2d3h5fHp4d3Z3end9d3p1enl/f3x9fmzNbXV3cXRxcGxweHt6enl0fHd0enR8e3NybtdwdXt8enV5UHh5d3p5d3p7eHp7eoJ5eXd2fHl5eHh5eHl2eHd8eHl2eHN0dnd3dXh6gH18d3R4fHd7fXp4eXt5fH13cnZzd3Z0dXh5e3Z3e3VxeHV3dHR+/3+ffwF+/3//f/9//3/wf4d+/H8Bfpl/AX7XfwICBACAkpSako6TlJSalpKQlJeTkpiXkJWVkJGRkZWPkJaUj5eVkI6QkI2JkJSPkZCQkJOQl5iSj5KWlpSVl5aVlpmUk5KSlpCVlZKTlpaSiJaak4+bkoeHi4+PiY6LiYyMj5ORhYWHj5+YjZGWio6NkY+KjIuXjouTjZGOk4yQioiNiI2AkpCOjYWFioeQjY6JjI+Gg4qRio6LioqOkIqAi4mAg9SB9oKRlpegk4aDgIWHgo6OjY+OjJCKi4uLjIaIiomIkI6NiY+RjY6IhIWNiouKlJGOj4ySkoyNjYuEj4yRjYqTjYuOjouQjIWKioqLi4qIj42PkYyOjo+NjI6LjY6RkJCAj42OiY2PkpGPj5CIiouQjYuOj46SkImSkpGUjpGSk5KOj5KMlZGWk5GNkY+MkZGWkJGSj5KQjZCMjo6Uj5GRkI6WjYyOi5KUjIyUk5CVjpGSk5GTjY+Qk46RkY6QjpeVlJaUlZKRlpCTjpKUjpKTlo+Pj5GQjZOSlpCVk5OUkpWAmJKOkZKX9Y6WkpOXkpSYlJWUk5SQk5KRkJKQkZWUk4+Tj5WUkpSVlIyVkZGXmZiTl5WRkpOSkpKWmJSUkpWVkZeTl5aTlJCSkpORkpGXkZaUlZmVlZaVlpSSk5OUmZWQkJSWl5mVk5WUlpmVlJWYmJeXlpqXlZiXmZqUmZmWmJaAmJaZlZiYnZmWnJiVlpWTkpmbmZqTl5qZmJmZmZaYmpiWmJialpyXmJiYl5CWl5qanZmZmJ2bmp2cmJeZlpmWmZmblpeVl5WYl5aXlpOamZmbmJaUmJaYmJeWlZuSmJuZk5iZmZObl56anJabnZuZnZmcnJyXmZialpiamJiWmZmAmZqZlJqWmJaUmZmVmZuXmZmbnJScmpiXmJ+amJudmJ+XlZKYmpyanJaemJuSlpahl5uYjZualpmanZ6bn5man5yam5ucnJqXoZKVlpqem5iSlZebnKGfl5WVmZeamJuanZ6coaCdnqKemp+coJ2fnp6cmJecmZWSq6ufmZ6WmJmAlJOXl5CSjJKMioaEhYLx5eXPjJSam5aalpuan5+jo5yRoqKcoKSjoaKgoaOen5yan5qdn5mVnJicmZumoJSel5eUk4+Sm5ydm5eYl5KUkp+XmJeTlJWTl5iVmZWWmJWbk5aXmpqTnZeamZqcmJmampeclJibn5aUkpOTkpWQl4+AlZKRkY+Rj42Ql5yhrbGNjIeNj42JgPuJkpOMjZmcmaGXmJaTkIaF9e7vjY2WkpeYmJiUmJ2alJWUk4+SlZWUlZOYmZiZl5iTkpeTlpKUk5WVkpOWlZWQkpKVk5ySj5WPhpCamJmWkJSUkJWXlJORko6QkpOOkZKRj4+LjY6SkZAViIiMiISJioiPioeIiouJiY2LhoqKhIaAjIOFjIqEjYuGhYeHhn6Eh4WHh4aFiIWMjoaEiYyMiImMjIqMjYyKioqLhYqKh4eKiod/i42Dg42Hf36BhIJ+hYOAgYOEh4Z9fX+Ej4uDhol/gYGGhH+AgIuEgYeFiYSIgoaBfIJ/hYuGhIV+f4F8hIKHgoWKgX6EhoCGhYSCgYWAgHmAf3p9ynnjeoaJiJCHf4F+gIJ9h4iDhYODiH+Bg4KDfoWGgYKJh4aBhoeFhoV+fIV/gYGHhIOEf4iHg4WDgn2EgoSFgYmDgYaDgoiDfoKEhYKDgXyFhISJg4aFhoR/hIKDhYaGhYaFh3+EhImGhYeJgIKDiIaCg4iGh4aDioiAhoqGhoiJiYaFiYWLiIyJiIKHg4SJhoqGh4iFi4iHioWFhIiHiYiGhoqEg4aBhomFhYuKhouIiYmLh4mAhYaKhoiJhIWEioyKi4qKiIiKhouHh4mEh4mNhYaEhIaFiYeMiYqJi42Ki42Ig4iIjueHiIeJj4iKj4uMioqMiYiFiYiAiYeIjIuOhYmEjo2KiouMhY6JiI6PjYmPi4eIjIqJio6PiouFiIqDjYmOi4iIiomJiomJiY2JjoyMkYqNj4yMjIuLiYuOi4iIi46Pj4qHjIqLjouKio6Rj46Mj4+Nj42Nj4mLj46OjpKNj4yOj5COi4+Li4uKi4qQkI+Qio2PkZGAj46Qjo+Qj42Mj5GNko2Ojo6Nh4uNjo6UkJGLkZCRkJGNjI2KkI6MkJCPjY6NjI6NjY6Nio+OjpGOjYmKi42OjY2OkIiNkpKNjI+MipWRko+Sj5KUk5KTjpKUko6RkJKNkY+PkpCQkZKQkJCRjI6PjY+PjI+RjZGQkpOOkpCPjY6AlI2Ok5KNlI6Mio6QkpCRjpeMkIqMjZONkI6BkJCMj5CQkZGRjY6Vko2RkZCQkY2Rh42NkJGOkIqKjJGRk5GMjIuQjpCPkpCSk5CSkpGSlZKOk46UlpWSlZKOjZCPjouXlo6LjYiKjYeFiImIh4OHhYN+fn976t3cxYSIk5SOkIwOkI+SkpORi4WUlJCSl5eElkGXlJOSjpWQlZaQjI6NlJGRlpKKk42OjYyKjpKSk5CMj42MjImTj5CNi4yHiY6MjZGNkJCNj4uLjY+PiZKMjo6OkISPgIuRioyPlIuLiomJiouLjoWKiImJhoqIhomMj5CWl3+BgIaJh4J78YGMioSHjY6JjoiJioOEfHzt6OeFhI2HiYmLjYeMjo+LjIuNiIiKi4qKiY+Lio2Mj4iJi4iKioqMi4qJi4iMjIaKiY6KkIaFi4aAh4+NjoyDjIyHjIuHiYiKEIaEiIqHiIqKg4aDgYGKiImAdXZ4dnN4eXd+eXd7eXl5d3l6dHd6c3V2dnpzd3x6dHx8dnV4dnRwdHZzdXV3cnZzeXp0c3h3e3d0dnt7e3p3d3V7eXN4d3Z5fHl1cHd5b292d21ucnZvbnN0c3J1dXd3cXJydXZ0b3d1cW9udnRxcHF4dG56d3t4fHN2cnB0cniAfnl1d3J2cm10cnRydHlydHR2cXZ1dXRvdW9sbWxtbbFnyGp1c3N0cnF2dXVzcHV4c3VzdXludHRyc3B5eXN2eXt5cXV0dnZ4cm9zcXVxdHF1dnJ5eHV2dXdzd3Jwc293dHJ2d3N4dHFzdXhxdHRudnZ0fHR3enl2b3h1dnd1eHWAdXl4c3ZzeXZ3d3lwc3R4eHJzeXZ2c3R7dXZ7eXd1d3h3dHl1eHh7eXZzdHN2enh8eXh3dXt5eHt3d3V6eXl1dXh5dHV4c3h7dnV7eXZ6eHx5fHh2dXJ4fHl3e3h3d3Z6en18eXd6fXl5eHV4c3V4e3Z6eHd2dXp3enh8dnx+dnqAenZ0eXl603d4eniBfn1+e3x6enp3dnZ9eXd3eHx5f3l5dn9+fH19fXd/enuBgIB6f3p4en58e3x+fnx+eHt9cnp5f318fHp6enx5fHp9e4B8foR8f398fn58fHx9f3t7en19foB7dXt7e317eXyBgn9+fH9/fYJ9fYJ4e357f36Ahn9/gX1+gH18fX57enp8en5/gYJ6fn+CgoB+gICBgYB+fYKCf4WBfX59fXt+gH18hoCAfH+Bf3+BfHt+fYGAfoGCgHx+f36CgHyAfn6AgH9/fn98enx8gIB/gIN7f4GDgICCfHmGgoKChoCEg4OEhH6BhYJ/f3+CfoJ+gX5/gYGAgoB/gH55gIR9foGAgYJ8gIGAhYGHfoB+fYJ6gIJ/fIR/fXt+f39/e32DfoB9e35+fH5+cYSBe3x9e4GBf4B/f4N/gX18fIF+e3l8fHt6e313e3mAfYB9f3h6gH5+fX99gYR/goOAgIKEf4N+g4WAgIGAfYGAgX59enh1dnd3eXmAeXZ1d3Z0d3h4dnR0dnHb0MiucXaAgn98fn1+fnx8fHVxgH+CgYOFg4WFhIWDgYJ+g3+Egnx5eXyCgX98en2CfX17fHt8gH6AfnuAfHt8d316fHx8eXN1fHh7fnp/fHx9e3p9fX13fnx8fX1/fn5/fHt8eXt+fnp6eXp6enl5enSAd3p5d3d3e3d5fH15eHZvc3N2fXl1bttxfnl0eXl4dXRwc3duc21t3drMdnV9dXl3eHx2eHp+fn15e3p4eHx7eHl9enl6eXt3d3h4eHd5e3d5e3x3d314fXh/d313d3p1cXl7fXx9cXl7dXd7d3h6enRydXl2eX19cXd2cXF8enf/f6B/A35/fv9/5X8Bfv9//3//f4p/hH7/f4V/AX6Qf4N+1n8CAgQAgI+RkZaQj5OTj4+DkJSXkYyRjJKQkpSUkI6Mio+NjIqTj4qKkpCWjZaTk5OSkZSRjJKTlpWZkpKTk5WVkpORlJmRm4uTnJKSjpKUmJaQj52WlJqXioeNioqSj42Nj4qMj5GZlpSQjY+Qjo+TkZKMj4yPjIaKi4yLkI6NjI2QjI6NgJCPi4ySjpaKhoyKj4qOi4qJlo2SjYmMi42JjIf5gICPh4iFj42WjYOMhIeHiI2QjYmQk4+Jh4mKjIiPjIuKh42TkY2Pj4iIkIuJio+Pj46LjIuKjo2Mj46Mko2MjYySjI6Ijo+MjI2RiJGQjZCNg4aRkZCQi5OIj5GQjpKPjJOWBI+IjY6EjYCPj4yPjpCTj5GPjJKTj5CPi4+RkZWTjZONkJGRkZKNi42Tk5CTj46RjpSLkIqMko6NkJKTj5CQkpOIj5SMiY2QlpGRjY2RkJKTlZGVl5OUk46Lk5SPk5OSlpOSk5eTlpSTmJGOkJOOkpKTkpGMkJOQjZGSkZ2PiZKVkpGSlI+Tl4CVlJSWlZWRkJOYmYz/h5KUkpKWk5KVk5OOkJSRjZGVlZOPj5KVlpmTlpWSk5aUkpCXlJeYl5WRk5SUlJKXlJSYlJGVkZOTkpeWj5aSlJaRkZaTlJWRl5KVl5SWlZiYkpOVlZaSkZeZlJeYlZaVkpSYl5WUlZmZmZealpqVlZaXmICXlpSVlpuVmJycmpSYmJqYlpaXlpWWl5qYmZeYmJiZmpuamZeXnJqamp2cmJqamJaTlZmalpaWmJeblZiTlJaSk5WYmJWXmZqalpicnZmYmpeWm5eZlZWUl5qdlpmampyal5ucm5+dm5uanZean5uYmZuYkJmbmJeal5mXmJOVkYCUlJWdmZaWmpeXl5qenpybmpyenJablpuZm5qXlpiXmZ2am5uamJacl5aempqbmJSUmpqdnpmUm5qcn5uZl5uZm5SVnJWZmZWZmJWamZqcnZibmZuam56cm5ufpaWbn5+gm56Zo56eoZyemJaam5aRsKigoJuWm5SZlZaUlpOXlYCTi4qHg4WF7szxk5eSlp6ioaKko62ro4rw+ImfrJ+enp2cmZqXko2UoJ6fmp2glpyalpueoJuYmZaXlpORmZ2dm5uXlpeVkJ2VkZaSl6WYl5mWlJKSm5GXl5eUnJubl5mZl5Wbn5mam5SUmZeZkpiTjpGVk5WUkJiUjJCKkJKMh3iKlJahrLmWkZCNj4qNh4aGhYyVkJWXoZ6YmZGNloyEhPjmgo+NiZmalpiho6GenpaelpKTkZmUl5SWlZeXmJSWk5mYlpSRl5mXkpmOjZeSlZOYlZqak5WZkpGWkJqSkpePj46SkpGMjZCQj46SlJCNjI+Sj5STkY6AhYiHjIWDiIqFhHmGiYuJg4mDiYaHh4eEhYR/hYOBg4iEgYCGh4yDjIaKiYeHioeAh4qJh4yIiIaIjIuGiIeLjIeOgYiPhoqDiIqNi4aFjomHjoyCgIV/gIiGgoOFgIODg4mJiYaEhYaFg4iHiISEgIOBfYGCg4GGg4OBhISBhYSAh4SDhImDi4B9goCHgYWDgoCMhYmGgoOChIKEfed6eoJ8f32DgoiDe4SBgoKFhIeGgIWIhoF+gYGEgYeDhYB/g4iIhIWHfIGFg4KBhYiFgoKBhYCEgYSEg4OHhYaGhIiFhICEhIKBg4R9iImGioV6e4eGg4WCiH+Gh4WEiIaDiIqAhYGGhYSFhYKGhYSHhoeKh4iHhIiJhYeIhIeHiYuKhoqFh4iIh4mIgoSIh4iKh4SIhYqCh4OEioeEh4eLiYiHiIl/hYmDgIaIjYiKg4OJh4eEioiLjIiLh4WAiYmFh4aFjIqJi4uJi4uKjYiGhomFiImJh4WFiIaJhYaHiJSHgIqAiYiHiYuGjI2NiYmMioyHiImQj4LZf4iJhoiMiIeKiYmHh4uKhYmNjIyKiYeNjI6HjYmJiY+LioeMi46Li4yJiYuKioaLioyOiYiLh4qLiY2Mh4yKi46JiYyLi4uIjoqMjYyNjY6OiYmMi42Mio6OjI6OjI6Mio+QjYyLjI+Pj42AkIqNkIuMi46NiomMjZKMjpGOkIyOj4+OjoyOj42MjZCPkI6Nj5CPj46QjYyPkY+Qj5ORjY6OkIyJio2RjIyPjI6Tjo6Jio2GhouPjo2NjY6Rjo6SkI+Oj42NkIyNi42Mjo6SjI+SkpGSkZWVkpWVkJCRlZGSlpCPj5KLiJCSjo2AkIyOkI6KjoqOi4mVj4yMjo2NjpKPlZORkJSVko2TkZOSkI6NjZCPj5OQj46OjoyRjoySjo+Qj4uLjouRj46MkY2PlZCNj5OQkouKkIuNjouMioqQkI+QkY2Sj46Pk5KTkZCUl5WSlJSUkZOQmJSSlo+Ri42OkY+In5iQkY6JjYiAjIqKio6JjYyKg4OBfYF94MfujZCLkZeWlZWVkpuWjnnX4n6TnZKTk5COjpGOi4OJl5WTkpKTjJCPjI+Sk4+Oj4yPjo2Ij5OVkpKQjY+NipKOiY6LjZONjo+Pi4iKkYiOjY6MkZKSjI+OjoyMk4yPkImLjYuPho6JhYiLiouLh46AiYSJg4iKhYKFi4ySl5+FhYeHiISGgYCBf4OHhYeJkIyJjIiBiYF7fO3ee4SFgY2MiomRlZSUkoyTjYeMh4+KjIqKiYuJi4eLio6Li4uIjoyLh5CHhYuHioqOjJGOh4uMhoaNho6Ih46DhIWIiYeBgoiIhoeKioWCgoaJhomKiYWAdXh1eXV0dXh2dWx3eHl5dHh2e3h4c3V1cHR0dHN2c3d2c25zdnlzfHZ3dnd2e3dxc3Z6eXl1eHV0eHl2dnZ7eXR8cXiAdXlyeXl6d3RyeHN4fHdycXZtc3h2cHJ1dHZzc3V0dnZxcnR0cnl0dHV0cHFxbnR1dXJ4dnNzdXh0d3WAeHZ0c3dweW9scnF3dHV2dHF6dHh1dXV2dHJrac1tbXFrbGttb25sZ3R2eHRyeHZ5cHV2dXRxc3R0cXl0dnFxdXZ3dXR3b3J0eHJxdHh2dHFydnF2cHNxc3V4eHV0eHR2dXJ1dXR1dXRyeXx2eXdwcXp6dHlydXR7eHV3end0d3qAd3J4d3N1dXN4d3R1d3l4eHV2dnp7dnd3dnd3en56d3l1d3p7dnl4dHN2eHt4eHV2d3xyeXZ1eXl3eHh6end0d3lycnZ2c3p4f3l5dHd6eXd0eXd2fnd5eXdzend2eXZ2ent7e3h5eX16fXx6eHd5d3h6dnZ6eXN3dHl6e4Z6cnlKeXl3eHp2en1+d3d8fnx4enl9gnOoc3p7d3d7eHp5eXp3e3p7eXyBfX58eXl9fHt3e31/fHt4enl8fH19f398e3h3enp8fH18eH6EfCJ7fHx7fX1/f3t8gHp+fnuAfnx+fn19fn58fX59f4B9gH+BhH+Afn6Bfn1+f4CAfn58gXt8g318fX59fXt6foN8e4B+gH9/fn5+f3x/gH59gH6CgYJ9gYGBgH6CfHx/fn6CfoOCfYCAfXt6fX+AgHuBgX+EfX18e313eH6Afn19f4B/goGDgYJ+fnp/fHx+fHt+fX+BfoCAgX6DgoSDgIaGgH+Cg4IKgoSCgYGAfHmBf4R+gH+AfnyCfH99fIeBf317fHyAg4CEgX9+gICBf4GCg4GAfn1+g317f3yBgn1+e4B+en1/fX5/eHp7e4B+fH17fXyAfnp7gH1/fXl8fHp9eXp5eXx9e35+e31/fH+CgoJ/fX6IgoOBgoR9g32Fgn6BgIF2f32AgnqCfXl9e3R4eXt6gHl8fXp8fXt1eXZvdXPHtd5+gXuAhYGAgH99g311Y7W+a4GHgICCf35+gn16enqGg4GBf395fX17e3+Bf31/fH9/gHh+f4F/gIF+gIB8fX95foB4enh9fHx5eHd+eHx6e3x+gYJ7fHx+enp/eX1+eXt5eX12f3l1d3p7ent3fnl2fnl1d3Z2dnl6eHp9gHB2e3t2d3tzcnVvcnR2dnh3cnZ3dXB4cGtu2chsdHdyfXtzdH1/gYN+en57dX12enl8e3l0eHh5eHp7e3x6e3h9fH51e3dyeXh8e358f3l5enh2c3p1enR2e3R0dHZ5eHNyeHh2dnd2eHF0dnt2dnd3dP9/nX8Bfv9/+H8Bfv9//3/8f4N+jn+Cfv9/h3+CftZ/AgIEAICSjIySkYmNlJGdlZKSjouNkY6Mk5CQjIqTkZCMk5eKkZCLipKWk4yUkYqPkZGNkZaPmpaSkJeVjZaQmpmWjJKYj5aTl5SUj5CPkZGQkY+J/YGRiIaKio2SjYmPi4+PjI2Tk4mJioqGiYiPjYqKhoaNjYuLkYqPj46Mi5CNjpGRiICOi4ePj4SCiIaKjo6PiIqRko+Nh4qLh4yQhoOA8oGJgOj6iIeFg/2BkIKKjYOGiYaLi4yKjo6FiYuLjI2JjouPio+Ni4iRiJGIjIyLj4qMiYyLh4+KjoyIiZGJjY+Nj4+SjImMjpSLi4uQiJCRj5GVjY6Ljo+Qj5CNjJGSjo+KkYCSjY6Qi42Li46OjJCOjI6PlZGMjZOPjI+PjY+KkJKWkpGRlZCQkZCLjI6Vl5KTk46RjY+Ujo6OjYuQko2SkI6QkJWQkZCOkZCRmJGTlIyKkJGWj5OSkI6XkpCVj5KOipOTkI2YlpSVlo6SkZGSkpKUk5GSjpSVlI+Lj5eSlpWSk16Wk5aTkJiTkYyUkZGPkJORlZaUjo6TlJSWl5KQk5OQkpOQkZWYkpOSjouSlJiUk5SXk5OUl5ORkZKXmZaSlpWRk5qSk5eRl5OUkZSVl5OTkZaXk5WRlJOTk5WTk5WWhJOAlpeTk5GXlZaVk5OZmZeUlJOSk5KTl5aWlJOQnZeXlpqWm5eXlZmXlZiXmZaamZmcmJqSl5SWlpmVkpiXmJWWmJ6amZubmZmcmJyZm5eZmpuamJqYmZmYlZmZmZuWmJWUlpmZmZGTmJeXmJWYmZWYm5eZmJeYmpeVmp2XmZeYm5mAm5iYmJefl5iYmKOcnJicmJyZl5ycnJmcm5yZnJiVmZSanKCamZaTmZmbl5WdmJiWlZqTm5aZmZucl5eZlpSXm5eMl5yamJucl5+am5yalpiXmZmYnJaZnZmZnJyVm5yhnJiUkZ2enZ2amZqamZubm5qcnJqWm5WYm5iWm5mdoJmAnpuck5yin5+cnJudnJ6lnpqXmpiWnZiroaeeoJqVmJuYmpuOlpCUkpSL/e3y7o+WnaCgop+koqOisKyJh+PW1+D8mKWjmZ+kn5yXmJmdnZufoZiak5eUmpiaoZqdl5SbnJiVmZyilZaYlpiakpeakZWOjKuhnJmbnJSQlJOTl5SAmJiWlJabmZeZm5mcmpWYmJyVmpSTl5aXlJaUj5OTj5aQi5GMkImJlJahoK+0oJGQjY+OiYf9goKNk5agnZSUlYyOi5CJh4P4/5CYlI6Nk5eZmpmanJaPiZebnJWTmpGbl5aUlJOZlZiYkpSTlZGVlZaPk5mVmpeCh4yLlZiXl5QelZOQmZSRkpKWj5aUjo6RkJSNj4eMlpORjZCQjpOTgIeBhImHgYOIhI2GhoaFg4WGh4SJhYWDf4iGh4OKioGIh4OBhYuKgoqGgoWIh4OHioiOiYaFioeDjIaOjIqBiYyFjIaKh4mEiYaEiIaHhYD0eoeAfIKCgoWEgoSBhYWCg4aGgYOBgX2BgIWCf358foKBgYCGfYSDhYSBhYKDhYmBgISDfoeIfXt/eoCDhIWAg4eJiYSAgIJ+hIZ9e3rreX924PCAf3178XyJfYSHfH+DgIGChIWFhH6ChYODgoGGiIeCiISCgIeAiICFhYOIgoR/goN8g36Dg3+ChYGBgoWEg4aEf4GEiICDgoV+h4eEhoqAhIOEhIaFhYOCiYeEiICGgIiFhoaEhoKBh4WEhYeEhoeNiIKHjIeEh4eEhYKKi4yHiYmKh4SHiIKBhYqLiImKhYaFhomFh4eGhIeJhIeGhIWFi4eEhoGIiIeLhYWKhIKHiIqHioiGhYqIiI2GiYSAiIyJhI2LiYmLh4mKh4mJh4mKiImFiYqIh4KFjYiHioqKgIuJjYyJjouIh4mEgoaFiImKi4iIhYeJio6LiIiLi4iJiYSGiYyHiIiEg4iJjouHio2Ii4iKi4qIi4+QjImMjImKkomKi4aMioqJjY6OioyJjI2Ki4mLi4yLjoqLjI2JjIuMj46LjIiMi4uMiouQkI6LioqJi4uKjo2MioqGj4yOgIyQjJGPjouPjIuNjZGKj4+PkYyQiY+Pj42PjYqOjY+MjY6Sjo2RjpCPkI6Rj5ONj5CPkI6OkZCRjo6Pj5CTjY6Ki46Pjo+Ki46PjY6Ljo6Mjo+Ojo6Mj5GOio2SjpCPj5KPkI6Pjo2Vj5ORkJeSko+UjpKUkJKSkY6TkJKQj46MgJCKkpOUkJGNiJGPkoyMkpCQjoyPh46Lj4+TlZGOkIyLjpKPhIySj4uQkouSj4+Qj4uOjIyNjZKNkJKNj5OQjI6Ok4+MiomTkpKRjo+OkI6Oj5CPkJKPjI6Kj5KPi5COkpWQlo+Ph5CXlZWSkpOVlZWWko+Pko+Ljoydk5iSk4+NgJCPjY+RhY2Hh4qMgvDc4eKJkZOWl5aUlpWSkpiRdXTLxs3X7oyWlI6SlpKSjpCRk5ORlZWMjYmNjJCNjpWPk4+MkpKPjI+Sl4+QkI+SkYuOkIiPiIWYkZGRk4+IhomJio2LkI+Mio6SkY2PkI2RkYmLi4+JjYiHjouMio2LhoiKgIWMiYSLg4mDhI2Oko2boI6Eh4iJhoGB8318hYaKkouGhoiCg4CGgoB88fOHi4eFgIWKjY+Nj4yMhYGNkJGJiY2Gj4yMiIWGjomMjYeLi4mHjIqMiImKiY6LeYKHhoyOio6Ki4eIj4mEiYeKhoyKhISIh4iGhX+Ci4iJhYWFhIiJgHRzdXpzcnN3c3p2dXR1eHZ2eXV5d3ZzcXh2d3V6eHB4dnZudHZ4c3pzcnF5eHZ4d3d7dnNxd3Rye3Z8eHlvd3dzenR7dnhyeHp0eXZ4dW3ibnl0bnNwbnZ2dnRzcnRucnZ2cnR0cm1xcHFwbG5wcXVxcHF3cXFzd3VydXFzdnhyE3JzcXZ4cHBxbG9xdHdzdnd6e3iEcoB1c2trbdRsbmPI229ubW/XcHhyeXlucXlxcXRwdnV5c3N2d3R1cnp8eXJ1dnVweHV8c3Z1dHp1d3JvdHF1b3N2c3V0dXB1d3Z0dnJxdXZ4cXZ2d3Z8eHh7e3J0c3Z2end5dnN5eXV4cnN4c3h7dHV2dHp4dHV8d3p8fHl0eXp3dYB4dXV3dHx9enV7enp0dHd9cG9zenl2d3tydXV0e3l6e3d1eHl1eXd1dnd4dnF2c3h5dHl1d3x4dXd4enh4eHV2eHl5e3t6c3R3e3x3fHp5eH17eX15enl3dXp5enZ1eXV5c3Z+eHZ6e3x6e3p5eHx8eX18d3R9eXt+fHt5e3h9fIB5fHh5dnd6dnh4c3V5fHp5eHV2eHl+enh7fnp9enp7fXh9gX58eX57enqCe3x9eoF7fHuAg4J8fHt9fnx/fH5+fn1+fH9/fnx+e39/fn58e398fH59f4GBfn1+fXx/f36Af4B+fHd/fn17f36BgH97fHt8e3yAf4J9fn17gn2CgYCBfn1+eoJ/gH+Bg4N9fYF9fn+Bf4J9gHh/f36AgICBfoB/fH6BgoWAgn59gIGBgXx5fn18gX1+f399f36Bf3t/gX59fIJ/fXx+fnyAfYJ/fIaAg4GDhYGBgoR9gYWAfYCAf4SAgH9+f4J+e4GCgoCAf3qCgIR+g4J+f35+gHZ7fICAfn9+f3yCfHyAg393eX59eH1+e4B8fYCAe399e3x+hH5+g319hH98fHyDfXt8f35+fH19fXx+e31+fXt7e3h9e3l+gHx7gn9/gYGDfX16gIaDg31/gIKDfoGBgICBfnp8fIN9gnx9e3yAf3x/f3d8dXV6fnXXwcTQfYKEhIOCgoCCf3l5d25ZXK+ywMrTeX56eX2BgoOAgYKCgX+Bf3d4d3l8ent8gnyBfH1+fn17foGCfn6AgYOBfX6BeH14c3d2fYGBfXRzdnd5enp/gH97e3+AenuAen5/enx7fHZ5eHd8eX17fH11dnt2fXt4e3V4dHZ9fXp2f4J1cXh8e3t0cWnacHB4dXh9dHNzdXF0cnV2cnHe2Hd4eXlydXh5f3t8eHh0c3t+fXh3enZ+eHp2dnd7dXV8d3t7e3l6enp5eXt4fXtxeHl2e3x4fHh7eXp/d3F4d3h4e3hwc3d5eHd2cnF4dnl3dnp3dnjPfwF+zH8Gfn9/f35+hH8Bfv9//3//f/9/6X+Efo9/hX7zfwF+kX+CftV/AgIEAICTjo6KlpiUkZGGiIyOjJKWk5KNjYqXlI2TkY6NiImJkZOJk5OXjpKTj5SSloqTl5OUk5GVkpaWk5WQjo+Wj46Pk5iPjIyPk4WHk5KRjYmOiImPjY2HkZWOkoyKjZGTkI+QiYmLi4aGi46OhYuNjIqJiouPjo+QkYqEj4+GiYyQj4CQiYmMi4qKi42RjIuLioyMjIeKjI2JiIWKhYWGhYePiPCFgYORioaThIiGhY2MjIuPjo+NkYeKiY+OiomFiYyNipCHi4yKj4qOjI+QiIyNjIiNi4uNkIyOiZCKjpGJi5CPjouOkJCRjpCLjIyLkpKPi5COkI+RkpCMko6QkJCNj4CRi4yRj42PipGOj4+Tj42OkZKNjYyQlIuRkpKPkZGOlZKMj5KSjpOWkpCKjY6Sk46SiZGNkJOMi5CTjZGLkYqQkJCWlY+Jj5GQlJOSko6OlZOUkJKRjpORjJSPkpCTlJGOlo+UjIuSlpSQjY2PlZeRk5KOkI2OjZGPk5WSk5CQkoCQkZGRk5aTkpGUj5WQkYyZkpGSlJeVlpGWlJKTkpaUkpWPlJSVlJSQlZWTlJCVmJ+RnJKWj5KUl5SXl5iWlZSUlpqVkZKXkpKRkZGSlJGUl5WUlpOQkJKSlJCWkpeWk5SWkpeYlZiWlpWYlpaUlZqYl5eZkJKTkZSXmJiVk5OWmICSlpWYmZaVmZmamZabl5+cmZiYmZiSkpaTlpabmpmal5eYmJeXmpmYmZiZmpaZmJqbmJaXmpyQmZeXkpGTlJqTl5eamJygmZSdmp2UmZqcmZeVmpSalZeel5GXmJaWlZeXm5aal5WZmpaZmpqdmZuZnJuYnJuYnZ6XmpqYlZWXlICXmZmVmJmZmZiZmJaXmZmSlZmYmpianpqYmZuZmZuYlpiZmZmWnJuam5yamJmVmJidmJyWlpCbmpaZm5qPkpiUm52dmpaalJCWmJebl5mbmJebl5qamZOdl5eYmZudn6GfnaGamJ6XoJ6dmKCcnKCeoZ2dn5qcnp6dnJ2inZqdnoCalJibnp2fnaCWk4+QlpmfnJ2joKOjqqWjrbac9N+Ag4Tu6/Dr3IGLirqfl5SXl5qdp5uOhJGLipKQlZ2Ul5aVlpOYm5uVkpuTnpmSl5mXl5ickY6YmJ6akJKempqckpSSlZibnZmbl5mdnpqbnJiWlZOWkpmUlpiUlJSWk5CSmICSlo+MjI+NjICNlJidn5+urJeNkY6Fh4Lp/P2LlaiVkpOLjY2LjYyIh4GGl5aYkpmam5mRm5+Xk5H5kJWTjpWWlZiXlJqWkpWWkpGUkJqXlJmOlZSUlpqWpI2cmpaXlpmQm5STlZaWkJSTk5GTk4qKjoyRj5GNi42GjY+RlJORioCIhIOBi4qLhod/f4WEgoWJh4WEg4CIiYKJi4ODgIJ/hYeBi4iKhIqGhYiKi4GIjIiIiIeHh4uKiY2FhYOKhoeEiouChH6Cin9+iIiJhYGChIGGhIR+iYqChoKCg4eJhoSFfn+BhH99gISCfIF+hYN+gIGHg4SIhYJ6hId9fIOJhYCFf4CEg4KCgoOIg4OCgYCBhICChoWDgYGCf36CfHyCfOJ+fX6Ifn6GeH6AgIWDg4WHh4SFiX+BgYaGf32AgoOGgYV+gYOBhoGEgISHgYSIhIGCgoCChoWDgYZ/g4WAgYWHhIKFhYaGgoaCgoSDhoiFg4aHh4WIioaChoaGhYWEhICHgYOHh4SGgoaFh4WKgoSGh4iEhYWJi4CGiIiJi4eFjImDhYeGhY+OioiBgoSIi4WFgoSCiIqGgoeKg4mGiYKIh4eKioWChoWEiYeFhoSHi4mLh4mEhImHgYqEh4aJiIaFi4OGf3+Jh4mJh4WIjIuGiIeHioSFhYmGiIqIiIeJiYCEiIeIi42LiYiMiIuGh4OOiIeJjI6Mi4WKioiLi46NjIuGi4qLi4qJjIyKi4eKjpqGjoiLh4uNj4uNj4uMi4iJjpCMiIiLiIuIiIeHi4eLjouLj4uKiIqJjIeLiIuNiouLiY2MiI2MjIyOjo6Li46NjIqOiYiIiYmLjo6Mi4uOkICIj4yQjYuMjY6QjY2UjJSQj4+NkY2KiZCNj42Rj4+Qj5CPjouLj42NkJCQj46QjZCQjYyOkJKIkI2NioiJio6Ijo2RjZCUkIiNjZKKj46Sko6Ij4iQjIuTj4mPkI2PkI6NkIyQjomQko6RkJKUkJGOkY+Nk5KPk5OMkpKPjI6QjoCOkZKPkI+QkI2Qjo+Mj5KLjZCPkY+Rk4+OkZCPkZGRj4+RkZGNj5CRkI+Sj46Hio2QjI6KjIaPjYyMjpCGiY2LkZGPjoqQjIeKjoyOjI6Qjo6SjY+Li4mSj42QkZCRlJaUkpSNjpGOkZKTjpSRkJKUlZGSlZGQk5OTj5KYkpCSkoCPipGQk5GWlJaLjIaIjZCWk5WWkpeYmZaUl5SAzst0en3k5Oni0HR6e6SPj4uNjZCRloqAfYmFg4qIjZCMjo+OjYuOkpGMiZGLk5CKj5GOkI+QiYWLi5GPh4mRjpCRiIyLjY6Qk4+SjZGTlJKRkY6LiomKh4+LjI2JiYmMi4mIjoCKjYmEh4iGhnuFi4yNjI6amImGioZ/gX3d8vWChZaDgoWAgYGBhISAfn6Aj4mNiIyLjo6Ij5GKh4nth4uKgoqJi4uKipGMh4mMioiIipOMiY+EiYuJi46Nm4ORkIyMio6FjIeFiouOiIuGiYaKiX+Bh4OHh4mGgoOAhoWEiIiHgIB5dHByeHV7d3hycHl0dXZ7eXRycnN5eHJ7gXJzc3JvdXZwe3Z8dXt2dXl8enJ0eXVzcXdxd3t6dnd0dHR6dXVyeHhydW1ud3FwdHZ5dXRyeHN3eHJuenpydHF0dXR0dnR4cXNwdXBtcHRxb3FxdnVwcHB3cnZ3cHdtdndxcXR6dYB2c25zc3R1c3J2cHJ0cnFyd3F0d3Z0c3JtcXFycWtpZstwcG9zaG50Ym50dnhzdnZ4eXR4e25zcXZ5c3F5dnR4cnFxdXZvdnR1c3V3dHl5eHVydXVycXR0cnhydHl1c3h+dnR3dXh2c3Vzc3h2d3p3eHp4eHh5e3h1eHZ1dXR0c4B2dHV7end4cHd5enR7dXZ6eXZ1dXN5fXF0eXl7eXp3eXh3dHl0dH98dndwdnRzeXZ0dHh1dXd5c3l4c3d0enN4enV5eXZyd3V1eXZ2end3enh5d3l2dnZ1b3h0d3R5eHl3e3Z2bnF7eXp8eXh3e3l3eXd4eXR1ent5eHl4eHp7e211enZ6e3p7eHl+fXp7fHZ7eHd8fXx5d3V6fXp7eX9/gXp2eXt9fn56fnx5enl5gJJ5e3l7dXx8fnx9f3h/enp6fYN8enp8enx8e3p7f3p+f3x7gX97fH96e3h/e3x9fX98e31+fH59fX2AfoB9hH6Aen56fHx8fX+BgYB+fICBfIB9gH5+fXp9fXx7g36Cg39/fYF+fXyCgYJ/gn+BgYGCf318fH97e39+gYCCgX2BgoB+gYGEd4J+fXx7fX2Aen5/hX9+g4B6fXyCfYB/gn98e395gH18gX15fn57f319fH5+gH93fYJ8gX2DhoCBgIRygX2EgX6Bf3qDgIJ9fIF/foCAf319gIR+f32Ce36AfH6Cf39+g4R/f4SAf4GAfX1/f32Af359f39+gnx6eXp8fXx9eXx9fHp8e4GAeHt6fX6Bf316gHh5eX98fHd6e3p+gHx8eXl6gX18foF+f4CCgoOBhH9lgoGDgYN/fXx+hICBgYB9foODfoGCgX+Ag4F5gn+DgIN/g3x7dnp/gISCf4OBhYSCf3t7almbqWVtcdLT1cq5YmFnhnqBfn5/f4B9cG5wenhzeHl7fnx8gX5+fH6AgH98e3qAgX2EgBB+fHl0dHZ9fXl6e3d7eXd5hH2Ag4GBfH5/gYF/fXp6eXp4eH17enx3enl7e3t4e3p9eHZ5eHd5cHl7enl1eYF8d3Z/eXJ0c8nd3XR0fWtzcHBwc3RzeXVzdHN9dnlzend6eXZ6fXV0eeN6enp1fHZ6eXl6fHl0dHd3eXZ3fn53fnV2end7fnyEcH58eXx6fHR6eHccfnt9d3h3fXh2e3Bzd3F3eHt3dXVwd3d5eXd3cP9/oX8Bfv9//3//f/9//38Ffn5/f3+FfvJ/g36efwF+xn8CAgQAEJCMj5CNjJKLhY2OkpGMlpOEkYCQkY6UjZKNlYyLi5CWlZOWjo6Tj5GUlJeRlZSWkZKTmY2NkI6RlpKSjJCVkZCTk4yTj5CQhZmUkY2NjY6PjI6NiZKLiYyRiZCNko+MjY2MjouLkJGLi4WOjomNjY6Nh42NjIqLjJGPjo2Li4+LkpCMjIyJjIyHjI2NhYmLiYyIjHiPi4SHh4qKiYiK94Dq/oKEgoD79/iAhoiHh4mIiISJjY2VjIiNi4qOjYuJjImKkZGPi4yLiIyJiIuHio6MkoeNkIeJiZCTiIWHkIKNjYyFjo6Qi46Pjo2Qko6Njo+PjYuOj4yOkIqPjpORi5CPi5GLjIyOjJKNjoeEioCOlI2JjIuKjpGNko+RjIyRkpKWj5GNlJORjo6Qjo2TjZeOko2PkJCRkpGSj5OMk4+RkZmSkZKSkZKRkY2RkZKOk5KSk5SQlpGUkZGOkpOUmZKWkZCQkZCUk5aVlJGQkY+Sj5KKk5KUiZCSl5OVlZWUlZSQj5GTlJaZmJKQlJSQlICRk5CXlpGQkZKXk5ORk5eSlpWUkpeWkZGSk5KWkpGSkJOYk5WUk5CQkpOSkpWZmZWNlJSUk5KSlJaUlJSQkJOPk5GUk5OUlJeUkpGVlpaTk5eWlZSTlZuWlJiUmJaZmJeWmpSTk5eVlZiYl5iXlZSVmJaXl5qZm5eXnJqYmJqZmYCYm5qZnZiZm5aWnZqZmpiWmJuZmJqbl5qUl5udnZeVl5eYlZqYnZuZmJmanJSZmZual5mcmZuZjpuYl5eXlpuXlpiamZSWlpWTmJqZmJiWk5aWmJSanJqVlJqbm5WXmpqcn6CYmJiaoJ2alpmVmpmampqYlpiZmJWalJeXmJ2blgGNhJeAmZucnpqam5qYlZqamJmXkpKWmJqXlJacmJWYm5eVmZOZnpecnZmUl5WVn5WWmZiSlpeXlpaRmpWYlpeWmZibn56anpqfn5eanJ6bm56YkZuhmpuenp2ZmJqZlp6hoZ+doaGhnZqamZucmJmSlZiZk5KRhoeLio2em6Cfm6WgnKSAqKaqtKeQ+fP07Ojy+oL1/IKImYuSmJuVnJ2gpZ+OkYaGi4yLkJSPmpebmZiUkpmZlpiXmZ+Zk5WYmJWTmpablJaamZqZoJialpKSlpqYlpicm56alZeVmJeVm5aYmZqbm5iYk5iUk5OOj42UjpCSko6Ni4H7iZScmZ6knbOWjZFrio+Cgvf9+4SjnJiWlJWYl46SkIrzlp6bj5WUn5qYkJSYm5qE/4yUkZOUk5WTmpuWmJmXl5abmJuUkZKVlZOSlZiXk46SkJOWlZ6RlpSTmJiZlZeQlJaPkZaRjIyPiYuLkY+Mio+MlpKNi4yAiISEhoWChoF7g4SKiIWMi4aDhYWFhoOIg4iAioOBgIWLh4iMhIWLhYeGhoqGiomKhoSGjIOCh4SHjYeHhIeNh4SIi4aIh4iEfI6Kh4OFhYmGhIWFf4iDgoSGgIaChoSBhIKFhIOCiIiEgn2FhYOGh4OCe4GEhIGBgIOGhYKDgoWAgoaEgoODgIOFf4GBg3yAgoCEgYOGhH6BgISAgn6A6Hfc8Hx+fXvx6ut3f4F/gIKEgn6Ch4OKgn+GgoGGg4F/hYGDhYeFgoKCfoSDgYOBhIaDiYCEhH2EgIiLgX5+hniDhYN7hISGf4OEgoOHiIWDg4OGhIGDh4aDhoGEgoeGgoeAiIOHg4SGhoaJiIeBg4KDgoeOhoSFhISFiIiJiIiDgIaHhoqGhoSMiYeDhYiChIuHjISJg4aFiYaHiIeEiISMiIeEjoeHhoaHiYaGgoWHiISKiImJiIiMhYmJhoSIh4iLiIqGgoSJiYqIi4uKh4aGh4aEiYWKiImDhomLiIqJi4xBiYaGiImHiIuNiYiHi4yJioiJho2MioqJiIyKioaIj4mNi4yIjo6KiYmMiIuKiYmGiYyHi4qLiIqLiomJjI+Oi4aEioCGh4qMiYuLh4aLh4qKiYqMi4qOjIqIi4mNjIiLioqMi4uSjIyPi4+Ojo2QjY+Mi4uNioqMj4+NjIyKi46MjY6OkJCOjpGOjIyQj5CQkI+RkIqOkIyPko6QkY6Pjo6Oj4+RjY+Jj4+RkI2Lj4+Qi4+LkpGPjY6QkoqPjpGOjo6RjziQjISOjYqOj46Rj42RjY2Li4uMi46PkI2NkI2Nio+JjpCRjIySkZSOjY6PkpSWkI+OkJaTkY2QjISQGI+PjY+Oi4uSi4+QkJSRjYGNjpCPkJGSloSRgJCMko+OkY+LiJCRko+JjZGMiY2SjYqNiY6SjJCRkYuKh4qTi4uOjoiMj4yNi4iNi4+OjoyPjpCTko2QkZeWjpGXk5KTk4yFjpWQjpKRko6NkI6NkpSVlZOVk5SSj5GPkpOQk4yLkZGNi4qDgIWGhpWTlZWRmZaQl5eVlpqIdtbegN/Z1+Tvf/H3fHqEeoaQko2TkpCPiYSGgYCChYWIjImSjZGQjoqKj5COj4yPko6Kj46Oj4uPi46IjI6OkY+Uj46NiouNkI+MjZGRlo+JjYyNi4yQjIyMj5GPjo2Jj4uJioaJhYqGiIqLhoaDe/KAiI+JjpOOn4qGioOJfXrp7vF9Y46KiYmGiImKhIeHgu6OkIuGiIiPjIuGio6Pi33yg4uIiomJjImNjIqNjoyJio+Oj4yJiYuOiYeJjIyLiImHjI2KkoWKiYuPjpCLjIeJj4SHiYWDg4aChIOGhISFhoGMh4WFg4B5eHZzdXN0c2x0dHl2eHl7d3B1dnNzdXR1dnJ6dXNwdXhydXl2c312d3JzeHN1d3lvcnV2cnR3dHh6dnhzenp1dXZ9dnd2dnVwfHZ2d3Z2fXl1dndxeXVzdHRxdnB2dHF0dXV0cnJ4eHJ1cXZ2dXJ3cnFtcHV0cHFxdXx2eHdzd4B0eHNydHRzeHpxb3J0b3JycnZydHdyb3RwdnFybG3RZ8fRb3JwbtzO0GhwdnJydXN0cXR3dHVycnp0dXV0dHF1cnR1d3l1dXZxc3N0dXN3dnJ6dXVza3hyd3l2cnF5bnN3eW92dXhtdHNxdXp7eHZzc3h1dHN5e3V3cXp0d3VxeIB7dXl1dXZ3enx6dnN3dHd0eH12d3Z0dXV5enx7eXV1dnh0dHNydXx4dnR4eHFzenZ8cnhydXR3eXh5eHN3dHt4eHV6d3l2dXt6dnV2d3t4dXp4fH57eHt3enx4dnh3eH15enV0dX17enh5fXd2eHJzc3d9eHt7ent3e3t3eHh+foB6eHh8eXV6fH56fHuAf3t+fHp2fXx8fHd3fHl6dnl9eXx6gHh/f4B+fH93enx7enl7fHZ4d3l4eXt5fH19fn98eHx6fHt6enp9e35/e3l7e39+fH1+fnx/fHt9e3p8fXx8fXt+enqAfX6AfH6AgoKEgH+AfX59fnx9gYOAfX98fYB+fH19fH5/f399e3t8gYGDgn97fH55fn9/gYJ/fn5+g318fYF+gX+Ae36BgoF8fYKBf3qAeoJ+fn1/gH97gYGCfYJ/gIKCe3WAgHx+gX5/gH2AgX59enl9fH6EhYF7gH9/e392f4KBfnyCgIKAfX1+gYOEfn59fIN/hH99fXyBf4CBfn57fnx+fIR7gH5+g35+cnx/g39+fn+EgIF+f4J9gX56fX16d3+Cgn55f4F+fH+Cfnx+ent+fH18f354dXeAeneBfXd8enl7eHV2d3x/fXl8fXyBfnx+foKBfXyCg4GCgXx5gYV+e4OAfnx6fn99gYCAhICCf4KBfoB/f4GAg0x6eX6BfXl5eHN5e3qEgYKDgIOAf4OBgIB5X1WsxMK+wMvadeDlc2hoZXGAhICDgXlybnN3dnV1enh0fH2Bfn9/fXx6fH2AgHl9f398hICAeHh5eHZ7fnl+fYF+fXl4eXp8fnp9gn+Fe3d6fHx3eX57eXh/fnx9gHl9eXd3dXp2dXZ5e3x4eHRy2W93eXR6fHl/dXd8dntzbdfV3G52dnRycnV2eXR2d3Xdfn52dnRyd3l4eXd+e3hy43N5eXd4eHl4enl1en16dHZ+e316engweX14dHl8eXx8eXZ7eXyAd3t7eXt+f3t8dnx9d3h4cnJ1dnV4dnl4enh3dHh2d3Z1/3+ffwR+f35+hH+Dfv9//3//f/9/9n+HfgN/fn7kfwF+j3+Dfo1/AX6PfwF+xn8CAgQAgJKUko+NkY+MiI2Pl5qOlpKHjpWYko6QkYiNkJWPk5KRioWKjY6Lk5OSjo6Wj5GRmJiTjY+UmJeRk5KPkZOYkZOVlJaUlIuOmZCUiI6SlImKj42WjZSUmI6VjY2LkZKJj5CHkI2NjYqNjo6NioSKiIeKko+PjImSkYyOio+KkY2OgImKjYePjZGTjImHjImJh4+JhYeGioSCiYuOh4aGjP6A/4KHhIqJjIWHk/iDiY+Jh4mQi4yQkYyNj4yLjYiPiYuPjY2Qj46OhouPjo6OhoeOipKSjo2Jio2Lj4qPjI+MiYqOi5CNjI2Mi4yPjo6Ki4mQjYySk4ySkY+QkpCLjY2KgIqMjoqMjIuMj5GOjpOPjoeKiI+PjpCUlJGSj5CTk5WOkpCQkZKNkpWQjIuJk5CRkpWPjZKNj5KUkZSSjpSPlI6Nk5KPkpOUlI2QjI6PlJGTj5ORiZOPjZSOjpOQk46Ul5WUlJqUkpGNkZOTjpCSkJKSlJCSkJGOjpCRlZOXmZeVgJGRlZOTlJSTjpGVlJKRj42VlZOUk5GQj5SZlZSTj5STkpaPlpaTkZKWlZCWlpOUlJaWlZCWkJaVko6QlpaVl5ebl5aUlJeVl5SPkpKTlZWUk5KSlpWXlZSSkI+Rk46QjpeXlpOTlpKUk5eUl5iUlZOXlpSTlZaWmJSTl5aTkpuZgJqZmJaXmZuYlZiTmJianJeXmJmUmJiYmZeWmZubnZqXlpeWmJWYl5uWmpibmZSRkZiYm5uUmJidmpiXkpiTlZmZmJqYl5eXmJaamJiTlpaVmpKVmJWYlJiXk5WZlZiZmJWYmpiYlJ2Wm5iZl5eYl5eZlJiWnJiXl5eYl5qYmZmbgJqWlpeVlpmXmpiYlpaZlpibmJmZmpqbmpybmZWcmJKZmZqXlpeYl5ubmZyZmpWWl5mZm5ycl5acmJiYmZaYm5WZmZeXmZycm5qfl5qYlZ6bmJaUnZmZmpiUmZicnp6ZoJ6dnqKcoZmanJ6dnpqZnpmenp+Ym5Scop+Zm5ufnJiWgJealZWQjYqBhYKNl5qWm5yboKGkpKSjuqiL9fv8+vr++v2CjpuPnJiXlJaZlpqqppiVlo6Jh4mKjJKKk5SdnZ2YlZaWlJSampmcmZeWlpOUm5STnJiYm5ealJedlpKUmJaal5eZl5Wbl5WXmJSXlpSQl5WYmJeUlJeTmpiUk5mZS5ORmJWOjYaKhZOPlpWXj5aOjJqJi4qJioyDh4eIm6eenpiapqCknJKFiZmUlZKVjZidl5aYkY+fjJOVmpKRlJmTlJyYlpaWl5aRkISTMZeWmJOblpSVj5CTjpSTlpSOkJqZj5WOjJGSi4yPkpCMi4iEioiLiZGQioyPjZKPkJuAiYqHhIWIg4GAhYSLi4OLiYGGh4uGhYOGf4aFi4SIhoeCfIOFhIOIh4qGhIqEh4aNjYmGhoqJi4eHiIWGi42FhoqIi4mJhIWOhYiBh4mJhIOEg4uFh4mLg4qEhYKHh36Dhn6HhIGFgISDhIWBfYCCgIKIhIWEgYiHgYOAhn+IhIOAfH6EfYaEhomDgH2DgoJ/hoB8gH9+fHyCgoJ/gH6A63r3fIF7g4KDf32F532Dh4F/g4d/gYWKhISGhIGDfYaAg4WEhYeHhod7hIWFhIeCfoaBiIeHhYKDhIOGgoeDh4SBg4R+hYOBf4GDgYaEhYCCgoaDgYeHgomGg4eHhYOFh4KAg4WGhIGCg4OGioeHi4mIgoSBiIiJhouIioqHiYmIioSJhoiIh4SJjIeEhYGLh4uJiYWFioOEhomDiYmFiYaIhIWKh4aLiYmGg4eCg4aJiIeFiImBioiEiYWHiIeIg4iKiYmKjIuKiISFiIeEhoiHiImKhoiGiIaIh4mLiYyOjIqAh4aLioiIioeDh4uLioqGg4uMi4qJh4iIioyLjIqGi4mJjIeOjomGiY2Nio2NjIyKi4qLhoqGjIqJhYaNjIqLi46Ii4qKiomMiYeJiomLi4iJiomMi42MjImJhoaKh4mEjY2OiouLhI2LjIuNjoqMjJCMiouOjIyQjIuNjYuLkI6Aj5KQjYyOkY2MjIuOj4+SjY6NkI2OjY2Qjo2OkJGSjo2Ni42OipCOkY+Rj5GOjIaHjo+SkY+RjZCNi46HjYmJjo2Oj4yMjo+Oi4+PjoqMi4yRioyNipGPkY6KiYyKkJGQi42PjpCNk4+Rjo+QjY+Ojo6LkZCTjY6Pjo+Pj46Pj5GAj46Oj42Nj4+SkJKMj5OPj5GNj42Rjo6QkZOQi5CPiY+Mj42Nj5CPkpKQkpCPjIuMkI+OkJGMi5KQjIyNjIyOiY6OjY2OjpCRjpKKj46Lk5CPi4uSjpCPjIuRjpOWk4+Vk5GQlJGVkI+RkpOTjYyPkJGTlI6SipGVkY2Qj5KQjY+AkJGOjImIhH+DfoiSj46PkZGUlJWVlZSeiHPY5Ovu8fXz9H6LkISLiYuNjI6MjpWOiYmLhYSCg4OGi4OMjpaVko2KjY6LjJKRkJGQj5GPjIyPiYaPjJCRjo+KjpGKjIyRjJGOjY6NiZCLiY+QioyLi4SNiY2NjouKjouPi4mIjY2AioaNjIeEf4SAi4WKiImDh4KAi4KEgn+Eh32BgH+JlJCQiouTk5WOh3yGjomHiIuEi46MiYyEho+DiYqOh4iIjoeIjIyLiYqNioWHjImKiIuLkIiMjIyLiIiJhImJjYmEg46PhouGg4mIgYOHh4eEhIB/hn+DgYuFgoGGhYmEh5GAenh6dnh6c3Rzd3N1eXJ5cnV4dHl3cnV1c3dydXN4dHh0bXN2cnV5dXh2c3t0dXh7enh0c3h5eHd1dXF0dXx0cnx2eXh2d3d9eHpwdnp7eHV1cHp2dXl4cXh2dXN2dG90c210dG5xcXRzdXVybnBzcXB3cnN0cnd4dHRydXN5dnSAbm52b3l2d3h2cWxzc3JvdHFsdXRycGxxcHRucW9s1nHgdHVvdHZ0cmxvx3F4e3RyeXhsbnV4d3V0c25zc3ZxdXd0e3Z4dHZwdXp1dXlzcXZydXd3dnJ2dXJ2dHd1eXZ0cnNtd3Nzb3N2cnR0fHRycnZ3c3t5dXl3eHx6dXd1eHKAdnd4d3FzdXZ5e3Z5fXl5dXdzeXl9dnt4fnx6fHp5enJ6dnl5e3d7fHVydnV7eXp3enV3eXR1dnlyeHp4fHh4dnl3eHh9enp5c3lzcHZ3d3V2enx1eHp2dnp6ent6d31+eHZ4eHl5eHZ3eXh4dnt7d3h2dnp0e3h9fHp8eXp8fXuAe3p8fHl6enp1eHx6fX13d3h8fH17eHt7fH17e3l4eHp6e3p+f3p5fX5+fX9+gYB9fHl6dXd1enl3dHN8enl4eHt2eXd2e3t3eXZ7fXt5enh7fHt7foCAf3t8eHl7enx4f3+AfH5+eoF+fX5/gH6Bfn99e35/fn2AfHyAfnx9goEffoJ+fXt9fnt/f3p/gn1/ent7fHx/f318e3x/f4CDfYR+gH98gH2AgYCBhH9+enh+fYB/foB9gn58f3d/enx/f4CCf3+AgIB9goF8eX1+fX58fYB8hIB/e3l7fnuCgn99fX+Agn+DgYB+f4KBf39+fn2CfYF/fX6AfX5/f4B/gIB+fn1+f39+gn9+en6DgH99fIF+gX5/fn2BfXt8f3l9f399IXp+fnt9f4B9foB9enyAf39/fnp7goN9eXx9eHt8fHp6e4R9gHx+d319en56fH2Agnt/fXx4gH+BgoB+goB/fX99hYJ/gIKBgXx+fH6AgYF+gHp/gH58f4CBgH1+gYF9fXt9fXd2dXmDfn59gH6BgoCCg4B/XVSvxM/Y3uHl43aBfHZzdHl/foB7fHhsdHd6d3x4eXl4fHV+goeFf316fH17fYJ+gICBgX+Afn19eXZwenuAfXt9en98dn56fXp/fH19f3h+enmAfXx6eXxxeHZ5eX58d359fXx2d3p5e3h5enl3dXd4eXR5d3Nxb3BteHZ2c3J3eXJwdHNyfXh3dnd9f4F8dm14endzd3l0eHx4d3lzeH12enl+dnZ2e3Z5enl6eHp8OHh1eHx4fHp7eH10d3l5fHp2eHR6eHp5c3J9fnp8dXV6d3Z1e3N5dnVydXlyenV6dXNxdXd8cnV//3+ffwN+f36JfwF+/3//f/9//3/zf4h+/3/ffwICBACAkpONkouOmYaNm5OTi4yRmJuZk5SOkpaOjouQjI6VkI+Ih4+SlZKVkpSRjZWSjo+Uj4+LjI2Sj5KUjpOXjY6RkpKNk4yKj5CNjI6JkJGVkJCPjpOQko2SkJSLiZGKjoqNio+KkouOiYqSkJGNjJCNjI+SjI2NhY6FjoiHjIuLi5CAkIqHjIyOkYyPjIqOiIWPjpCFhoeFjZSOiIeEgY+FgP+IiIb/jIyJioKB8YaGh4qMkI2QhomOi5ORj5CVj5COj5GQlI6Pko+HjoyNjI6Rio6PjI6Oi5GGi4uEjJCMjZSKjo2Nko2Kj5CLjIuNiJKRjY6Lj5OMi46Mi4iLj4aJhYuAjYuPkYyOkIqOjYqOj4+NjI6KjJCQkY2RjpKQl5CPlY+NkZGOjZGQkZSNjo6Ql5CQkZCSlpOPjpGSlI+Sj5aRjpCSjo6OjYySlJWSlI6PkJKVj4uMk4+SkZOTkpqKk5KUk5OSl5aSl5KYk5CPkJGNjY+Ni4z0i4mMjY+RkpCVlJY6io6Rl5KYkJGSk5SXkI+VkpKWkpSSkpGXlpqUl5WSlJCVkZGOkpOWkpOOj46VjZOVko6WlZGRk5OYk4SWgJeWl5uUjJGVlpaWkpWOlJSSl5KUlJSOl5GTk5KSl5OXk46VkJSSk5iSkpOWmJeamJWVlJKYlpWVkpSVl5WYlJOYlZWSlJeXmZeWmpqVl5aXm5OYlZWTlpmSlZSWmJiVjZaXmJiZlpaVmJaZmpmWm5ecmJeUnJWWlpyXmJuclpqagJebm52amJWUmpqRl5SXlpOVlpuYl5qZmpucmZiYlpeYlJqSlJSWmpuYmZmWmpWbl5mbnJmVmJmdnJefmZ2UlpKVnZqUlpWWmJWYlpaXl5iXlJyVmJSTlZeYmJmXn5Wcm5iZm5yYmJOVnJuYmpWXm5iZmpyZnJqamJyZj5mbmJibgJSTkJiZkpiWmpyfn5OcnJ2bmpiYnJ6enZyanpqdm5+hnKKimp2enZ6bnJ+enKCenJaamKGYmpeZmJialpyVmpeaoqCamJeVkYv+4PeVl5ueoaGmpaOjp6Wvv4z7goGA/P/+gISQmJugnJ2WmJaamq2rmJqYlZCLioWJj4uOmZaWgJSYlpObl5aPmJeTk5iZlZWPjpSTn6Sdl5yYlpqbmZmTlpeNlZidm5eWnpSRl5qYmZ+WnJmWkpmVlJGPlJOSlZeVl5OUk5WQjJKJgICRjI2Xlp+inpmKjouNjYyOg42VnKWdoqKcpKKflf+AlpeRkpCSl5ial5ybmpGPlJaSkpCQC5aUl5OVk5WXmJuWhJkzmJKUmpWWj4yYk42Vl5OUl4yMkJOWmpiOkI+PlI2Lj5GSjo+QkIaIi5WSkZGTkJORkY+PgImJhIeEhYx8gpCHiYKEiYqMjYeIgYmLhoaDgoODiYiGgX2EiYuJi4qLh4SNh4OFhoKHhYWBhYaIiIOIioCEhoeJg4iBgYOFgoOGf4aHiYWHh4SIhIWCiYeHf4GHg4OAgX+EgYeBhH9/hoeFgH2GhISEh4KAhHyEfIWCf4SCg4OGgIWAgYSFhYeDh4aCg4F7hISHfX9+fYSGhYB8fXuEe3nxgYB97YSEgYB7eumBg4GDgYeEh3uChYSJiIWDjIeJhYWGhoqFiIqGfomEhYOHh4SFhIWGhICHfoODfYWHhIOKgoOChYeDgISGgYKChH+GhoWHg4WHg4OGhISCgoZ+gX6EgIaHiomFhoeCiIOBiIiGhYaIgYKIh4mIioaHho2Ih4mFhomJhoOGiIiKhIWFh42HhoaHiYiJhYOHiIqGiYSPh4OGiYKDhoaDhIeMh4qFhoeLjISEg4eEiYWJioeOgoiHh4iHiYuMh4yHi4iGhYWIhoKGhIJ93IWDhIaHiIeFiYiLOoGFh4uIjoWGioiKj4uHiYuIi4mLiYyHjIyMioyLiYyHjIaIhIeLjomKiIaJjoaJi4qDjYyGhYmKjYmFjICKio2LgoeKio2Lh4uFiImHiYaHh4iDiYWIi4iIjYqPi4eMioyJioyHiouNkY6Pj4qKi4iNjY2Oi4qOjouOi4yOjouMjo6Oj46Lj5GNjYqLjYiOioqKjY+Ji4mLkI6MhYyNjY+Ri4uOjo6PkI6MkIyPjYyLlImNj5KNjZGTi4+PjFWOjZCOi4qNj42HjIyMi4qLjZCNjo+MkZSXkI+NjI2OjpCJjo+OkpKPkI+Nk42PjZCSkpCMkI+TlJGUkZOMjYqNk46LjoyNkI2Ojo2Mj5GQjZGIjoyMhY2AjI+HkJCNjpGSjoqJipCQjY+IjpGPkY6Qi46Njo6PjoKNkY2Nj4iJh42Oio2KjpCTkoeNjpCPj4+MkI6QkZSNkJCTkJOVjZWSkJCSkZGQj5KSkpSRkImQjJSPjouPko6PjJKKjY2Ql5aTkI2NiYf82O6Qjo+QlJeXlpeWl5Wcm3KA13V3efHz9n1/iouMkY+TjI+MkI+TlYiLjI2IhYV/hImFiJCNjYyMjIqRjo2HkI2Ii5CQjYyHhoeHkJSRjZKOjpSRkI+LjYyCi4ySj4qLkYmJjY6NkJSMj46Lh42Ji4eGiYqIjIuMjomJiIyHhImCe3mIgYCKipGUj4yChYOFh4NnhXyFioyRjpKUjpeTkIXxe46OiIaIiY2Mj4uQi4iHh4aLh4WEhIiKjoeLiYqKjI6NjY6Qj4yJiI2LjIWEjYiCio2Jio2GhIiIjJCPhIaHiIyDgYSIiYaIhoiAgoKKi4eHiYeIhoaEhIB8eHd6dHZ4bnV/dnhyc3h0dnx2dXN5eHZ2dHJ1cnN5d3JtdXl7eXl7e3Z3fHRydXRwdXJ2b3h1dXtxeXpwdHd3enV4c3V2d3J7enJ6eXZ0dnhzdXNydHl3dnBydHNxcHJvcHRzcnhwb3F2dHNwdHV0cXZ3c3Nuc3F4dHB1dHJyeQJ0coR2gHh1e3hzdXBrd3d6bXNtb3J0dnVxcGpyZ27adHFw1nZ2dW5uath2eHZ2cXd5emxydHd7d3NteHZ6dnR2dnp3enxyb3d0eHV6eHp2c3d3cnJzcnZ3cHZ2dnJ5dHRydnp1cXd2dXR2eXJ5eHl5eHZ4eHd4eXd1dXZzcXN3eHp8e3Z1gHl2enpyeHp7d3h5cnR4ent2end6eXl5eHlzdXd4dnV5ent3dHl6eH13dXV3eHV4eHd5eXl3enR9dXRzeXF1dnV3d3d8d3dzd3l7eHZ4dHl2eXZ7enV7c3d8fn13dXp6dnt1eXl4d3h4cnF0dHJqxHl3eXh3d3p1c3R2dnh0eXh7fnV4e3l7fnt3eX13enp9d3x2eXp+eHx+e355enp5dXR8fnp+fHd9f3h7fHt0e393e3l6fHh4fXl6eXd3enh0dnl7e3l2e3t6dnV5enZ5eHh6dXt9e3x9e4B/eX59f3t9fXx7gYCCgISDfn17eX18fIF9e4CBfH57fn59e36AgIR9gH6Afn56fXx3eXl6e36Bfnt4en5/f3R7e31+gn58gH6Af398fYJ+gX5/doJ+fX+Af4F/g4CAhoF/f4KBen6Af397fX1+gH5+foOAgIF+f4OFgoJ8fHuAf356e3x9f4GBfoB+hH+AfYCCgoF9f32BgICEfoJ/f3t+hYJ+fXl6gX9/gH97f4CBfn+Ad4B/f4CAfHqAfnpxfIB6foF/fXt4eICBe314e4F8fX5+e358fX1/fnd8f3l7fnp8eXt6eXx9f32Dfnp+fH18f3x6fXx8gIN5fH2Gg4KBe4J9fn2Ben6AgIB/hIF/fXl+enyAf3h9gHx8en96e3x+f4WEgoF/gYDsgMrXf35+fYCBf4F/gIN9gnJQq2NrcOTg5HN2fnl5fXuBe4B9gH1zc3R3ent5eXx1d314eX59f3x4e3uBe3x4gHp3fH9/fX15dHBxeX58en99gYh/e36Bendye3t/e3d5f3h4fHt8fIB7fnt5eHp4eXd2eXZ5fnt7fHp4eHp1c3p1eHJqd3Fuc3d6fnl3d3h2dnh2dm91eHp7eHt+eH9+d3Labn59dnd4dnt4fHh/d3R5end9d3B0cXR1fXV7e3p2enh5en1+f3t5d3h3eXR2fnl2d3p4eH5ycnh5e39/dXZ3eXpwcnl3eHN1dXl1d3R7enV2enVyd3Zyc/9/oH8Ffn9/f36GfwF+/3/KfwF+/3//f/9/lX+Dfo9/B35/f39+fn7/f4h/AX7YfwICBACAjoaKk5GOlpOTj5CPj4mHg42TjZqSi5CTkY2SjJSVkY+PkYqPjJCVlI2RiouRjo6NlZCYlpKSi4+Mj4yUlI+LjpKOk5OJi5KQhoaIjYqJi5WUjo2OiouRjYuNkI2Ii5WOiIaHiouJio2LjYiQiZCQiYmIi4uJjIiPi4iLj42Hjo6AjI70iIWOjYaPh4CDiYWLio2HiYeJjIiHjYmGg4aXhf2EiYyGiYeLhYWFg4mNiomPifaQjIuKkIaSj42QipCKjo2Jj5CQj5COjpGRj46QiYyJiouMipGMjI6PkIuIj4yLkIiOjpGTioyIjoqKi42RjYiLjImNjo2Pjo+Lio6Mj5SAj4yQjoyKiJCPjomRkZCMkI2MkpSLjZOLi5ORkpKMkZKTkZGQjo6UkZCLkY+Pi5CPkIyTlJSPlJaRj5OUkI2PkpGOkZKVk5SRj5KTkYmRkZGTko2KkpGRkpOUipKTk42Nl5SXkJaTl4+Si5OVmZSNlZOTjYzyg46PjI2OlZOTlpGAkI2Qko+WlJWQkpGUkpKTkpWTl4+OkpOTlpWSmJOUlJaVl5KTkJKRk5KSj5GSlpKWkpmXmJKKjo+VlJeZmJeWlZqKlZaRlpWWlpuYkpWLlpKUl5ORk5OSlZeUkZaXl5eVlZGUk46Ok4+TlY6TlJWTkpKTk5SVlpOWl5WVlZSWmZeAlJiUl5OXl5icl5SZmpOTl5Kbl5aWl5GXjpiSlZOVlZiZmJ2alZiampqWmpWbmJyYmJibmJGdlJ2Xl5aXmpuXmpyamqCZl5qXlZuXlZWXmJyXnZ6YnpWXl5qempmVmpSVmJWXmZ2em5GRl5uZnZqYmZSbl5qblJWWnZyZmZeXlpeAmJaVmZeYlZSVmJmblZSbmZuXl5mUmpycnZeYlJuZmJSYmp6Zl5Obmpmdm5uYmpOZlpaUl5eanJKalJGVlZeUlpWTl5OXlZSUm5aPkpyZm5eZmZ6ZoZ+Zl52YlJicop2em5+cnZaho5ydoZqgnZqbn5ualZegnJmYlZaamZiTnJiAlJSRkIyMh+zm94aVmJyeoaWopKivqsSnhICEhYaCgoKDkpqemZ6WppyYlZeztZmalZiZlY2Ki4uPmJiclpuUl5eVkZaUkZWWlpuampSWiIWJkZKTmJGQlJyNlZORkI6VlJKRlZqcmJealZOVl5WWmZSWmJOUlJWWk5eXj5WSmpSAk5aSlZGUkpCI/+z0gP+Ikpmalo2TjYiSjI6TjY+WrJ+lnJGilov//6CSipiZk5WRkpCRkYqPlZOYlJKVmJaTlJKalZWam5WWlJmWmZmWmJmakpONkJGPko2MjI2VhoiUkpeQk5KRlYeMkYmQkJKPjpaPnpeRjZGXlY+Rj5aUj5GAhnqBi4iFjoaGhIeChYKBeoSIg46FgYWJhoSGgYiJhoSGiICEg4WJhoGGhoWGhISDi4eQiomLhoeDhIKLiYeAg4iCiIyCgoiHf36AhYSCg4mIhYODgYSHhISGh4N+goiFfnx8f4N/gIF/gn6CfYiFf399gIGBg3+EgoCAhYKAg4SAg4PhfX6Eg36Ggn1+gX2Cf4WBgX9+hIKBhYOAfH+IfPd9f4V/gX+DfX98fYSEgIGCgOqHg4WCh36HhoWGgoZ/g4GBhoSFiYiFh4mIh4eHgYR/gYODgYWEgIWHh4OAiIWCh4GGhIeJgoJ+gYF9gYSEg4OCg3+DhYSHh4eAgIWEhoqAh4SDhoWEgIiHhoGJiYWEjIWGioyEhoqFhIyJiYqFh4iLiYmHg4WMiomFioeHgoWIh4aKiYiEiYyIhYeIhoOFhYeGhYaLiYmHg4iFh4GIh4iKhoOBiIeHiIqKgYmJiYWEjYuKhIqIi4WIhIiHi4mBh4aIhIDTfoiHhYaGi4eIjYiAhoWHhoWOi4iFiIiNi4mKiYuKjYeFh4mJjIuIioWKi4yLjYiLhoeKiYeKh4iKjYmKiIyKjYZ+hIWLi4yPjYuKi41+iYmGi4iKiIyPiIuCi4WJjImGh4iHi4qKh4uLiouLjIaMi4mHioaKjYiPjYuKiImLiouMj4yNi4qLjIuNkI+Ai4+JjIqMjI6PjImPjYiJjYiRjoyLjYiKhI6Ki4uNi4yOj5GRjY6RkJCMkIuRj5KOkJGSjYaUjZGOjY2Ni4+KjY+NjpCNjZCLipGOi4yMjJKLkZOLkIqOjZCSjo6Kj4yMjoyMkJGTj4eIkZSTk5GOj4qTkJGOiI2PlZSRkI6OjY47j46Mj4yPjoyLjY+Si4yRkJKPj4+MkJGQlY6PipGSkIyNjpWPj4qRkI6PjZCNjouMiYuIiIyOj4eMiISEiYCMjYuNioyMi4uPiYWHkI2RjY+OkY2Uk46Jj46Nj5GWkZGMlI+SipGWkI+Uj5SSj46UkI2Ji5OSjY+NkJGSj4qRjoyMiYmGhoLl5O1+jIyPkJOYmJWVnZimiXFze3yAfn1+gIuNkYySi5mQjIqNnZ2KjomMj4+HhIeHiZGPko2Ri4CNj46IjY6LjY6NkJGSjo+CgH+DhISKiImNk4WNjYmIhYmLiImLj5GNjZCLiYmLi42PiouOiYiJiYuKi4yIjYmQiIiKiIuIjIuHgPHg6HnqgIiMjIeEiYWBiIOEioeGipqRkoqFk4qA7/CSh4SPjoiKiIWFhoqBg4qMj4mIiY6MhUGKiI+Mi5GSi4uKj4yNjoqMj4+KjYWIh4mJhIeFhI9/gYuKjYeIioeKgIaJgoqIiYeHjoOQjImDh4uKhYeHi4mFiYB5cXV7eHR5c3R2dnN0dHVxdHt2e3RydHpzdnlydXVydHV5cnh0dHZ1cHd1dnh0dHZ5d353dnp5dXNxc3t4eXR0eHF8gXZ0e3pwcHB0cnJwdnl4c3dxdHh1dHR2dG5zd3VtbnBsdG5scW1ucG5sdXZvb29xc3NzcHh0c3JzcXJycyd1cM9wcnd0cH14cXNwbHhtd3ZycW52d3R1dHFxcnVr4XFvc3Byc3SEcIB3eG9ycXDQeXV7eHZvd3l0dHV3b3J0c3Z2dHd4d3l6enZ5e3Z1cnFzeHN2dXN3d3NzcXh5dHl1eXh4eXd2b25yb3R3dnJ2d3lyd3d1eHh6c3R0dHd8dXZ5eXd2dXl6d3J5eXZ5enp3eXx3d3V3eHp9eHx5eHd3d3l3dnZ9e3p1eYB1eHJxd3d3e3d2dHV6eXN2d3h2dHF3end2e3Z8enZ5cnRzd3h6endzdXl6ent9fnd4fHp5eXx3fHV5d3l1d3d6dnl1cXd0d3NwqXJ5eXt6eoB6dnx3d3d0c3d9eXh5eXp8eXp7e3t8fnx1dXx6e3p4dXV+fHt9enV6en1+eXt7eGB4enp8fHt/eX53cnR2e3x8hX57ent7d3t7dXx3dnF4f3h6dn11d312dHp4d317enh8e3t8fH16e3t8eXx7fX58gX57e3t4fn1+fYCAfn18f358f4B+f392fnt8e3p6enaEe4B8d39/e3h5en1zfHx7e318e4B/gH57gIKBgH2AfoJ/gX9/foN9e4CAgn5/e39+gXuAgn9/f3t/gX+AgH17fX5/g3x/gnp9fH+AfoB8fnx9gH18fHp9fYF/dnqAgYN+foCCeoB+gIF5f3+BhIJ/gH56foF/fX98f39/fHyAgHyAhICAgH+Dg4F/fHuDfXx4gYOAfHt8gn99eoB9e356f3p6eXt5e3d4fH19eHp2d3h7eHp8eHx8fHp6eHp5cnh4fX1/e31/gn1+fn14fHx+fn+DgIF2f3yBeH+FfnuAfYKBfXp+fnx6eH9+f399fn1+fHmBfH9+fXt3eXrW2dpufXx8foB/g4F8en9/f2VZYWxxdnh1d3d+fX54f3qIgX19f359eHx5eH9+e3d8fHh/foF+f3t6fn53fH16e399f4GBf4J1dG1sanF2eHt8gXh9fnh4d3V3dnp6fH98e3x7e3h4eHt+e3l9d3l4eXl5d3p5fnmAd3V4eXx2enx3ct3K0WrLcHF0d3Nxdnp2c3lycnh3dXeCfH52dXt1b9TTfXV1fnt4e3dycnp5dXN3en99fXp9e3R4dn17d36AenV3fXt6fHZ6e3t4enZ3d3Z3dHh2dH9zcXl4end4e3Z4cXh4dH16eXh3fG58fnlzdnd4dnZ1e3h3e/9/BH9/f36cfwF+kX8Bfv9/w38Bfv9//3//f5V/g37/fwV+fn5/fph/gn7YfwICBACAhIyQmI6Rh4STlY+LiZWOjI2Qk4+TjZCQi4aUkIuRkoyLipKTjZGOl5SOjY2OkYyUkJCOlJWRlI6OkJKGjZGRjY2Qjo6Oj4yCjIaKko2KjZKRjpGFjo2NkomLio+Si5CXi4eMkI+OhI2Li5KNlI+LkYqOjIuNjI+Kh4uJjIqLiYqAi4uSjoiLh4qMjI6Qi42HgYaGjIeMiIWFiP6Dh4yPjP6HhIOGhoyLjoGKkY+EjpCRkJKLjpWMlIySjJGKjI6Qjo6Pjo+Pjo2OjYWOi4mOjI6MjIaOlpOJjomKjoyJh4mMi5KSi46WkomLjoeKj4+OiIeOlIyKiYySkY2MjouJio+Ai42IjIyTkJOQj5GUkY2Ml4yLiZGOjIiNj4qKlJKNjpKOkI+Oko+Njo+RjpCRko+Tj5KMjI2Qko+JkZSWlJGJlIuTk5aSlpSWlZeMi42Ok5KTkpKUk5OUjpGPjo+Ql5SQjo6VkJaUkY6OlJaOkI+SkpWak5T7g4yQkpCWlY6RkJeAl5WWlZaWlo6YkY+WkpWSkpSRkZWUk5WPk5OXmZyWl5WVl5SRj46WmJaUlpWSkpiRlZSYlpeWlJKXmZWVk5eZlJuVl4+TjI6XlJmbmZKSl5iVlZmWl5mVk5CSlpWXmZSVlpaVlpSVlpaVk5SUl5CUmZeSlZWVk5iWl5qVmJWXnZRjlpiZopqYnJeTmJeXnZWWlJSZmpeTmZaWmpycmZOYlpGXlZeZlZyhl5mZmpiWk5eWlJiYlZ2bl5eVl5WZk5uWnp6Vk5ecmpeZnZqUm5SUmJWVmpiYmpWYmpqYkpeTlZaUlZqWhJmAlpWWmpucm5yZl5iaopqXmZqUmpuempWWlJeZlpSWl5WUlZWVl5qcmJWYmpiYmZWZnZqYl5eXm5qUlZibnJmbnJyXmZmamJmXnJqYmJuXmZeTm5SUnJmRkZWUlJmYnJWUmZWQmZSYlJiYmZyVl5qWmpycoJOclJ6knp6dnJqhm6GAoaCWoJ+do6GWmp2YnZyempiTlpiYmZiXnI+TkpOQi4b+zNnm742goKGnrKmnsLjLmYKAg4KEgv6BkpmRlJKcn5ibpJ2ss6CknpKXmpKRjoyUm5mcoJiWkpmWlpeZlI+TjJOVlZeTjIuMi5eWl5WSlJeck5OTkpKQlpWUmJuTlpCAj5eVmpiWmJSVj5eTmJeUkJaYkZqSk4+TlY+WlJKMlYmTkZGJiIL44vaAhpWSl5iPkJKTkJWVkZyppp2Mk4iDhp6okpCUl5mWkpiWk5KMkJiYmJSXk5WRl5eSlJeVkZCNk5SalZydlpiXlZaUmJSWkJaWkZWRk5GLkI2Tk5SQkIkZjZWLlJGSlJKLhYORl5GOkI+PlJGQkI6Ti4B/goeMhYmBfIeJhX9+iYSEg4WFg4mFhoaDgImFgoWHgYKDh4eDhoaNioWEh4eIg4yHhYaNi4SLhoaJiX+HiIiFhIiGhoSFg3qDfYGIhIKEiIiCh3uDgIGIf4OBhIaDhY+Ae4WGh4V7hYKBhoGKh4OHgIaDgYOChH5/gYGChIN/gYCBfYiDfYF/gYOEhYeDhX15f36Cf4OAfn2A731/gYKA8397en5/hYGEeYKJiXuFiImGiYOFiYOHhouEh4CEhYSFhoOBhYaHg4eFf4WDgoaEhIKEf4iMiX+CgISFhIGEhIOBjYmDhoqGfoCEfoCFhYR9foWIgYF/g4qIhYOEg4GAh4CDhIGFhY2HiIeIiIqKhIOOhYSCiIWGf4aIg4GKiYeFioeHhIKIh4SEhYmJh4iLiIiCiIGChoeKhYOJiYqIhoCLgomIi4aKio2HjYOBgoSHiYmHiYmKiImDiIWEhoiMi4WDg4uHioqGhYSMjIWGg4aGio2Gh9x/hYiMh42JhYiIjICLjI6Ji4+NhY2IhIuHioiKjIeHjYuIioeKio2OjomJiImLi4qHhY2Oi4iIioqLjoaMiYuNi4qKh46QjIyJjY2HkouOhomEh46KjZCPhYiMjYyLjouMj4yKhoqMiouNi4mKi4uMi4qMi4qIiYqOiIuOjYmMjI6Ljo6Oj4uPio6Pi4CMio2SjIuOjomPjY6TiYuLi42OjIiNiomOkpCRjI+Igo2NjY+Lj5SOjY+Qjo2Jjo+Mjo6Nk5GNjYqMjJCKko+Sk4mJjJGNi46SjIqQjIuOioqRjI2RjIyPkI6FjIqMjIuNkI2Rj4+PkI2Oj5GRkJOQjpCQlZGOko2LkI6SjIqNiYCQk5COjY+QjYyMjpCQkpCNjZCOj46LkJSRj42Mio6Pi4ySkpGRkZKSjo+Nj46OjY+OjY2Mi4yMiJGJiI6NiYiMiYqKi4+Lio2GhY+Mi4eNj4+RioqPjZCRkpWKk4yUmJCRlJGSlY+TlJSKkZCSlZaMj5SQlJCRj4+Jjo+PkY2NkYCHiYqMiYWA+cTO2eGElZKTl5uZlp6hpoBzdXl7f375fYyPhomIkJOOkZeSmZyRlpGIjI+LioiIjZGMkZOQj4iOjYyOkIyGioWLjY6QjIeHhIKJiY6Mio2Pk4mKiYeIiI2Li4uOiIyIh4yNkIyNjIuLho2IjouKhY2PhpKJiYaJiICFi4qIg4t+iISGhH967dfldn+Jho2OhYeHioaLjImPlpKNgoR9eYCUmIiHiI2LioaKi4aHg4WMjYqGi4WKiYyNh4eMi4mIhYqKj4aRko2OjIiMi42MjImPi4uKiIuEhYmFioqKhoWAhIyEi4mJiYuEf3uHioaFh4eGioiHh4WJgoB1eXV2d3p1cXd5dXVue3Z1dHVzc31zdXN1cnV2cnd0c3R2dnZ0eHJ6dnV3end3cXx4d3d6dXJ7d3d2dHJ3dnt4cnZ2dnV1dW11cXN6c3J0eHZudWt0cHF1bHFwcHR1cX9wbHRzdXRvc3Bxd3d+d3R0cHd2cnFxdG5wcnNyc3JwcoBybXZxcHFucnh1eHh2c3JtcXFycHNvbWlv1HZzb2xu3nNtanFyd3FvbXl4e3F0eXp0d3R2enh3dnh3dnJ0cXB0eXJudnR1cnd2cXd3dXt1dXV2cnh/fnF0cnRydnFydnVyf310dXh4bnN0cW53d3dwcHZ4dXZyc3p4d3V2dXVyeoBzc3V3dXt6eHl5eHp5dXV+cnR3e3l9c3d7dnh7e3l3e3h4eHJ6d3Z3dnx5eHd9eHZxeHB1dnd7dnZ3dnd6eHN6d3t3e3Z7fHt1f3N0dXZ4enp4en1/fHt1eHV2d3t4eXVxcnp1eHtzd3Z/fHZ2c3Z0eHt3dbd2dnx/fHx6eHt8eoB9e3x6e35+dXt2cnh2eXx8fnh1e393eXx8e3x6eXl6e3p5e3x8en9/f3p8e3p3e3Z8eXp5e3l6fX5/e4F7gHt5gnp+enp1d398gIB/dnl7fnt6fX59fnx5d3t7fH2Aenp5enx8fHl5eHx6eXx/eH+Ae3p5e3p7gXx9fXl9eXp7dw55d3l9d3V5end6e4B+fIZ7gHp6fH99fX6AfX13bn58gH98foF+fH19fXl7f4B/fX6BhH5/fHx9e4N9g4GDhXh6e3+BfoOBenmBfXt/fnuAe32BfH1+fnpxe3p+fn6AgX6Ef4B+hIJ9hIOAfYGCgYKBhYN+g4B5f36Be3t9f4KCgH19g3+Agnt9goKEgX1/gYKCZn57foF7fXx9en19en2Cgn5/gH5+e397fH17e358fXp4d3x7eH15d3t9fHl7ent5eH15eHtzeHp6enh5fX2AeXl9foB+gYR7f3qIhX2AgoB/f3x+foB6fX2CgYF5gId/gXx+fYB5fYSBgHx8eHt7fHt5d+q/u8TDcoR/goGDgn+ChHZfYGlwdHh47HR+gHh3eYCAfIGFgn97e4B8eHl+fHx7fXt/en6AgH55gX19foB7dXp4fXt/f315enRvcXB6e3p5foJ4e3p0dXl8fXt6e3Z9eHd6fX95fnh+e3V6dn96d3N8fXSAd3l0gHl1cnt9enV6cXl1d3lybdrGy2hyeHd8f3d5d3t0eHt7fX9+eHJzbmp3f392eHd7eHd2d3d5dXFzent4dn12e3l7eXd3enl2eXh5eXx1foB8enh1fXx/fXp5gHp5dnp7bnV5c3Z7e3V3cnJ+dnl4d3l8cnBtdXp5dXl2dnh5eXV0Anh3/3+afwF+hX8Bfv9/1X8Bfv9//3//f5V/hX6SfwF+7n+DfvB/AgIEAICcnI6Qj5ONkJCPk5SOlJGSjomNk5SThpOIlZaOk46Qk5WQkJCMkYqQk5GHjpGTkIqSko6PjYyKj4+NioiLj5ORjIiRkI+UkIuFiouQkJOTkI6NioyOkYyNiouPjZGOkYqIjoiRjY6Qko+KkoqIioqKhYmIiImLiIWJiYyJi4SMjICHi4qMioqKjIqMiYWHjYeJh4eIhoaOh5WIi/6JgffvgYaIjIyHiIWDg4iTiYqQj4mRj46Ojo2MhJOSko+Ek5aTlYmQkY+Jj42JjJWNjIuRj4aMi4uPko+KhoqPkYyIlI+Mh4mIhoWJjZKRjY2SiIqQk5CPjoqOj42LiYyMhomEkoCMjIiKi46Lj5CJkJSNi4+NjJCKjYiOjpCPkIuPj5CNkJGPjZKPj42QkpOPjZOPlJKSkZONkZKSlJCYlpOQjpCUkI6Yjo6WjpSUkpCUlZaRlJWSk5CWj5COjpaPk46Rk5SRlpOQl5GTkZOWlpGRlIeMkpKWjpWNlZKQko+Sko6UloCVj46Vl5OTjZKRkJWVl5aSk5GWkJWWlJSWlZWWl5SVlJOXlpOTlZKRmpeQkpSWlJeWlpaXlpCblpSXkpibmJmVkZyXl5WXm5mXlZqXlpqXlpeZnJyWk5mTlo+Xl5aUlZiWmJWSk5SXlJSWlJSSl5SSmpiVlZeVmJeUmJSUmZeWlhKVmpmXmZWamJOTmJeSmJaXmJiEloCXk5eamJiWlpSVlpWVl5WYmZiYlpack5udmpaYmp2YmJiXnJuVm5eTmZudm5qZmpmXk5qZl5qZlZiXlZeWlpqalJ2amZOYl5aUk5KZmaWYnJaQm5qbmpual5qYnJyblJqVlJWUmJqYkJSZl5uYkpiWlpqYl5eYm5mhmJmXmJeal4CXmJmVl5WWlJWWlJKTl5yal5iYmZaUlZebmp6WnJ+amJOVlpebmZeVk5WXmZuXlJWbnZ2XoJSal5aYkpWZmJiYnI+Wm5iemqCYnqGimJ6fpp2boKGdnJidlZeYoZqYmp6elJqanJWel5yXl5iSkJKOioPugYeH/OqWn6CkqKe3zIC8i4aDhYOA+4OSmpeWm5mbnZKJoK+ylZemmI+Tjo+VlpmampyamJWXmJOVn5GXl5SSkpSUlJOXk5ONipCSmJSWkpeVkZOVkJqXk4+SlJSZk5GVmJObl5iUmZSYk5aRkJGWk5OWl5GOko+SkJGMk5WPiouUlIuPi4yNjJSSjpSXk2qPkZeZlZGZioqbuY7yiO/5nJeanZWVmZOXlZSXmZSXlZKSlpORkZSUko6MkZOXkZeYnJialZidlZaTkpiZk5OTjo+MkYyMjo+MjZCWlI2Tko+UjoSSkpaQk5GOk46NjJWNio2OjpOKkaGcgI6PhISDiYSGhoWHhIKHhYaDgISHjIl7h32GjIOHgoaIiYeGhYWIgoaJhoCGiYeIg4aFgYeFhYOHhoWEgIOHi4mDgYaIgYeFgn2DgIWHiIiDhoWCg4WFg4WBf4aCh4SFgn+HgIiAgYaHg4GGgH6DgoB8gYF9fYF9gICBhX6CfoWCgH6CgIWAgYSFgoWEfoCDf4KBgYF/f4aAhXp/8X557ed8gYKEg4CBfn99fYV+g4eJgIeFhYWDhYN/jYmFgnmEiImJfoWJiYGGhIOGjIWCgYaFfIWBgYeHhIKAhYaIhIKLhoN+gYCAf4CFioaDhIh/f4SHg4SFgoWHg4CAg4N/hHyIgIKDgYKDg4OEiIOGiYeFhoaHioKHgYSGiYeHg4eIiISJiomFiYiHhYeIioeFjIeLh4mIiIWIhoeKg4yKioWDhoqEhIyGg4mEioiHhImKioeMi4iIho2IiISFi4OJhYSKioeNi4iLiIeGiIyKiIeGeYKFhImEi4KHh4aIiYmHhIuKgImFhoqMh4iGi4iGiIiLjImJho2Gi42JiYyLjI2MiIiIh4uNhomKh4eMjIiLjo+MjYyLio+MiJCLiY6Lj46Lj4iFjoyKjY6QkI6KjouLj4yKjoyQj4mIjYiNh4uMjIiKi4uOjYqMioyKioyMi4mNjIiRjY2MjYiNjIuLiYqPjYyLDY2Ojo6Qi5COjIuPjImEjICOjIuLjIyLkJKNjY+QjY+PjY2KiIyOjY2Ni5OJj5GRjoyNkY2Ojo2Sk4ySi4iPkZSSjpCQj4qHj46NkJCOjYyKjYuMj5CJk4+Qi46OjYuLio6Om46TjoiSj5COjpCNj46Rk5SNkY6Mjo6QkY+JjI2Nko6Jj42Nj5GRjo+Qj5iOjoCNi4yQjY2Mi4yNjIyIiouNhYiLkY+LjI6SjoyMjZCOkIuPkY+Kio6MiI+NjouMi42MjouIiY2QkoyTio+NjI6Ki42KjZCThoySjZORk42Uk5eQlJSckpCWlZCPjpKKjY2WkZCOk5SKjpKTi5KMko6Pj4qJioeEf+l8goHp2o2RlYCVmZefqJh2enl9fXrxfoeRi4mPj4+RioCLnJuEipeLhYmHiI6PkY+OkI2Pi46OiYyUiY6Qi4mKioyNi4+KjIaChoqRi46IjYyHiouHj42LiIiIiY6Kh4qPho+KjYuPiI2Ii4eHiIyMiY2NiIWKhYiIiISJioeDhIqJgoWDgoSCim+IhIeNiIeIjI+MiI2Ago2dgON73+iSi4iOjIuNhYqKh4yOhIuLiYmNiYuHioqLgoWJiI2HiYyRjZCKjpCJjoiHjo+GioqHh4OJhISFhIOGhYyMg4uIhYmGe4mJi4aLiIaLhYSDi4KCg4SFioOJlIuAenhxdnF3dHRzeHZxcXZ0cm5tdnh7em10cXZ6c3Jzd3t4dXp0c3Rwd3Z1dHd4eXhzdnZ0d3ZzdHd4dXN2dHd7e3NwdnRwdHVybnRwdXV3eHF3dnRzdnZxc25xd3Byb3N0b3Vwd2xvc3Z2c3ZxbnZwcG52c29tc3J1cnJ4b3FtdnSAdXduc25zdHd0eHRzdXZzdnRzdXFsdHBsaW3bcG3Uy29xcHZ0cXBucG9xcW11c3xweXFzeXR0c3N4dHVybHB2enZxdHZ9cnd1c3h7dnV1dnNueHR0dnd2c3R4d3h0dIN3dnBzdHFxcnd7dnZzenNyc3Rzdnl2c3h0dHV1dHF1c3qAdnd2dHN0dnl6dnh4enh2eXt8c3p2eHl7eHl2d3t7dHl6eXR3eHl3eXh8e3h6eH94e3d2dXd0d3p1enZ6dXB0enR3eXV0e3h6eXh0d3t7dnp8eHl7f3l2d3d5cnt2enl3dnp6eXp5eHZ3fHl6dHZ1dXNxd3N7cXZ4d3t6fHd3eHiAeXh4e3x5eXd9end2d3x8eHp3gHV4fHl5e3p8fXx5eHp6enl3f3t2dnh9fH18fn5+gHx3fX94gHt4fHt/gH59gH19fHl+f3yAf3x9fHt/gHiAfH57d3d8e4B5fH9/eH18e318ent5e3t6fX57ent+eH59f39/fHx7enp7fIB7enmAgH1+fYJ7eoB8fXt7e358fXt+fnx+ent8f4B7eoCBf4B/fn18eH1/fH5/fX96f32AfXt8gn57gH1/g32CfXmBgIODf3+BgHt5gIB8f35+fH14fHt8fn14gn59fH5/fH15fH6Ckn2CgH+GhIR+gIF8gn6AgIV8fX59e35/gYB7f3yAfIF/eH17f4CDgX19fX+HfIB9e3uBfnx7d32Af315enp9dHh7fnh7e3+Bf318fIF/fH59ent3en16d3x8fnp6d3l4enh5eHx8gX2AeH19fnt7fXp5fX6CeXp/fX+Bg32CgIJ7goKFf3x/gYJ+eH98gHiFfn58g357fXuBeYF9f3uAfYF8e317eXbYcHd2ysB7fIF/hH1+empdZmxzdHPhd3uBgHl9fn1/fXFufnxvdX96dnp6eX19gHt+e3p8e3t+e3uBen9/enp9e3p+fn96f3pydnx/en52fXt3eXh3fXp7eHh4e3l5eXZ6c3t5e3h8dXt4e3h5d3t7eH16d3Z6dneAd3dzeXh4dnh3eXR2dHJ2dHV6dXZ7eHh2eHx5dXxwdHt5bMhrxM5/eHZ8eXp5dHV3dnl7c3l3dnZ7dXp4e3t5cXV5d3x5eHl7d3p5eX14e3l5en12enZ4eXh5dHd3cnJ3d3t8c3p2dXh5bXd3enV7dnZ4cnZ1e3NydHN1e3J4fHf/f5t/BX5/f35+/3//f/9//3/tfwZ+f39/fn6PfwF+/3+FfwR+f35+2n8CAgQAgIaSj4ySkJCYlZGQkZGYk5KOk5CTkpaOkoyTj46Qj4mQkZaLj4eNjpSNl5iVkY2QkpKRlJGKjpOUkoqJgYqOlZaTjI2OkpOQjY+IjImTl5GUjpKQjYeNjY+KjIeMkY2KjoyQgoyJioqKi4+Ji4aLjISTiYaGk46LiID0iIWJjo+LgIuPjYuJiIqIjIqHhIKHiIqHiYKHioSM+PL8ioP+j4GEhoiGiY2Ph4+ZioTxgYiIj42Qj5GPi4yZl5SWn56fiJCOioyQiIWNio6Mho6JkI2MjZGUj5CRiImKg5qKiYWQko6Pio2NiYeJipCRi46OiY2Wj5CMkI6Qk4+RiI2Ni5KOgImKkY2aj42Mh42UjIuEjo2QjpCSj4uSk5CNi46Tj42LkpCPjY6Rk46Mko+OjY2SlpGQkpGVj5WOjpOUk5SQnJGQkZWRlJaUko6Wk42TkI6Rko+Rk46QjJSNk5WUkY6TkJOUkpGTk4ySkZOMk5KRkZeOlpCQjpWUlZWQj5WVm5iRgJWXk5OSlJOWlZiYk5aXkZWTkpePjIqEho6RkI6UlZSYkZaYlpKUk5WTl5STjZWTl5CSlJaYkZaTlJWSmZmZnJaUlZqXnJSYmZWXlpiYlpWYlpWXmZqTmJaSl5iSlZaQl5SWmJWWlJmakJeVmJubkpianJmWl5qWlpWZlJeZkpeZgJeXmJiTmJqRlJaalpeWl5iYm5eWmZOVmJeZl5WZlZqYmJidm6GZlpyYl5eVmJWUlJebmZqWl5qVmJeampWVmJueoJqWm5OYmZaYmpaVlpeamJaVlpeXlJiUk5iUlI+WlZSYl5eYl5qVlpyXlZycmZuXmZyYl5mYmJOYmZKYm5WZgJaZl5eam5uanJaamZaVmpmYmpubnJaVlpucmZyamZWZlpOXnpmVlZOZmJaXlZWblp2cl56cnJeVmZWXlJSRkpSTlZiUkpGWn5qYlZecnJOVm5yVnZWZmJSfmZ2Tm5qelqOXmJmdm56il5mioZ+enJqdn6CbnJybkqGcm5uYmZmWgJaZmJGQj4qD6ISLioiDip2Rj7rIyMKkiYGCgfT6gYiPlJaTlpmcnJyPjKq5q5eTn5udnpuYk5iamJqckZSXlZOdmpiVmZOZl5mQkZKRm5aVlZqYkZKUkpGWl5STk5SRkpaUkZCWlpaXk4+UlZCTkJSbl5SVkJWWj5uQhY2Hio+JgIuSmJecioySlZKQjZGNjoaOlpOglZSalZGZlZKRjYGQkrO6s5aDiKCdmpiblJKKkZOVjpmZl5KUkpCYlZGOkpyWnJ6alZqYl5SalZSXk5KUk5WVkY+Wko2UlJSNjI6QlouRk5OSkpWSkpGPlJaSlJSPlI6SkZaOhIuQj46Oi4eKgH6IhoOHiIaLioeIhoaLiImEioOHiYqDiIGFhYODg4GGhYqBhX6DhYiDiYeHhIOHh4eGiomCg4iJioGBfIGEiYqMhIKDhoeFhIZ/gn+Ii4aJh4uHhYCFhIV/hH2BhoOBhIGGeoJ+gICCgYWAgnqBgX2Gf318h4SBfnnrgn2AhISAgICGhYF/foGBg4SCgH6BfYGAfnt9gnuB6eHtgXnuhHp9gYKAgoOGgIOKfXjmeoCChoSHgoeEgYKNjIeKkY2QfoiGg4KFfn6GhIiFgIaDhYWEg4iLhoaIgISCe4uBgXuGiYaGg4aDg4CBg4eGgoaFf4KNhomDh4OGiYaEgIaGgoiDgICCh4WJhYaFgISIgoN/hoWJhoqIhYOGioiHg4aJiISGjImKh4mJioSEiYeFhYKFioSHhYaKiImEhomLh4uFkYeHiIuFiIiJiISJiISKh4KGhoWIiIOGg4qBiYiKhoSHiImIiIWHi4WIiIiBiYiGiYyFi4aHhYuJi4mHhouLkY+HgIqLiouJjIiJi46Oio2Nh4uHh42JhIJ6eYeIhYaKjYqNh4uLioiJiIyLjIuKhYyJjYaKiYyNg4qKiouGjI2NkIuKiIyNkImLjouMioyMjIaNiomJi4+JjY6KjY2JjI2JjoqNkI2MjI6Qh4yMkJGRi4+RkpGOjI6LjI2Qi4qPiY2QgI6NjpCJjo6HiYyQjo2Njo6Nj46Nj4+LkI2Pj4yOjJGPjo+PjpOPipCNjIuKjYqJi4uOjpCMjI+Lj4uNkIuKjJKVmI6OlI2OjIyLj4qMi5CRjI2LjYyPjo+LipGNjIaPi4yNi4uNjpGNjpKPi5GUjo6OjJKRjo+PkIqPkYuRj4yNgI2Pjo+RkZCQkouPkI6Ljo2Oj4+OkY6MjZCQjpGRkIiPj4uLkY6MjIaPjYuOi4mRjJCRipKQkIyJjYiKjIyIi42IjY+KioiOlZCOjY2Qj4iJkJGMlI2PjIiQj5CKkY2SjZKGjYySjY+WioyVk5ORkY2TlZaRj4+QipSQkY6NjYuPgI+OjouHhYOA53yDgoN9gY6JhaCgoJuHeXV6eunxfYGHi4uLjI+Pjo6ChJekmYmHlJCRk5COjI2Pi4+PiIyMi4qSkY+NkIqOj5GLiIqKk4+OkJSRiYuLi4eMjYuMjIqIiYyIh4aMjY2Ni4OKi4eKhYuPi4mMhomKh5CFfIR8goaBgISHj42PgYOHiYeFhIeEhH6Fi4mSiomPi4aNjoqKhHqIiKCinIt/gI+KioyOiImCiYuLgouMjIeKiISQioeHi5CNkpCLiY2MjYqRi4qNjIqIiouNhYSMh4WKio2EgoKIj4CHi4iJhoyIiYaHiIuHjIqGjYWHiIyGfICGhYSHg4CFgHJ6c3R2dnJ4eHp4dnZ6dnNxfHF1dn91d3B0d3Fzd3JvdHdyc3BydnRwdHBvc3Z4eHZ1eHd0dHl3eXd2cXN5eXZ5dW9zc3RxcXV0dHR8enJ1eHl5c3J1cnJwd3Fvc3Nxc3N3bXFta25xdHlyc2lvcHB2c3FwdXZxbm3RdHNxc3dxgHJ3d29vdHZzdXVwdnNzcXR1bHJ0cW1wzb/JbGfQdGxudXVxdHNxcXFzamfIcHFwc3V1cnd1b254dnh3fHd7bHh2dHJ1bnB4d3p2cXZ4eXpzdHl8eHlzc3d2bXVzcmx1dnR2dXh2dXFxdXhzcXx7b3R6dHx3eXN6e3dxcHV3cnl0gHR1eXl1dnh2cnR4dHdveHh4d3t6d3N3fXl3dXd8enh6fHl9dnh4e3V6fHl2dXR2enh4d3Z3enx2dXp5dnp1fXl2eXZ2d3t4eXd4fHl6enV3eXp5eXp4dXpye3h5d3h3eHh6fHR4fHl7eHt0e3h5fXx2enh4d314e3p5eXp7gYJ5gHx9eXt3fXt8e35+d3x9dnt3dnt9d3Rva3p1dnl8fXt8d3p9eXh2enx6eXx7dH17fXl/e3t9cXl5eXpyeH5+f3x5enp9gXl6fnt+fH98fXJ6fHp3e4B9gH1+fYJ9e3p8f3p9gHt+gICEen99f4KBeoKBgn95fX59fn1+fXl8eHt+Dn18fn96f314e31+fIB8hH2Afn+BgoCDgH+BfH59goB+fX1/hIB5fHl9e3x7fXl7fIJ/gX59f3d/gIR/f3x9foSIfX+Bfn97fH9/fH1+hH9+fn1+f4GBfnt/g4J+eIV8fYF8fIB/hn5/goF6fYN9fXx6gICAfn18d36BfIB6e35+fH9/gX6Af4F7gn99e357fH0oenuBfnt3foCAfYF9dX96fHx/fYB9eX57fXx6en9+fn14f36AfXh5eIR9gH6AeHl5enp3fIF/gHp6f353en99e4R5f318gH19eHl6gHx6cXh+g3t7gnh6gICBgH95f4B9fnqAf32BgIF9f395gH57gXt3d3Z52G50d3Zzc3x7d3ltamhgZWhvcdLhd3d7enp9e35+enZwdHyFgnZ1gH1+gn19fHx7eXt7eHp5gHt4f399fH15fH+BenZ8eYB9e32CgHl5fH12eXx7eHp6dXd7dXR2eXx9fX5yeHp3e3Z5fnp5end7fHZ+c211cHVzc3V2fXx5cHl5dXZ1dHd1dXB0eHd+eHl7eXZ6fH18dG15dYCEgXVwbnZxdXd7dnp0e3d6cXl7enV4dnWBdnR6Qnx9eYB5eHp6eHt4fnhzeXp5d3p1fXh0eXZ3eXl+cnRzeoBweXp4dXJ8dnl0dXd6eHp7eXx1d3l9eXNvd3d2d3J1ePl/AX6dfwZ+fn5/f36OfwF+/3//f/9//3/gfwF+kn+Cfv9/5H8CAgQAgJWNkpWRlpOPk5aPlZSNjZOLk5CPkY2PkI+Pko6NkYuUkIqSlJCXk5WNj46NkJeOlI+MjZKVlpSNlY+YlJORi5eMkYyOkouRjpKWlpCVjoqIio2SkYqPi4yGio2QkI6HkoWGiIuNhoqSk4qLk4mNiJCNj4mPiYqEgoT9iI2MjY6MgImLh4+LioWChIWGgoSDhIH6hoeCh46Mj4by9eeE9oWAgYOJiYaHiIuFgfaAjI6MkJGOioqRlI+UmZCWjYWCiYyPjI+HioePj42LkJKTkomLjI2OjoyPjIuHjo2Hi42JjI2Rho3+j4WUk46PhYmGjYyQj46Ih4iPio6QiZKJjYmOgI+LjY+SjIeOjo6QipCKkoyPkJKTjpKMjZGRjJCQkpKOipGLiY6Qjo+LjZeVk5CRkZGTkJCOipKTkY2Qlo+UkY6QkJWQjI+PlJGNk5GQkpWRk4uUk5CNkpGWmpCQjpOQjpaVlZCTkZKVl5CSlpeYj5GRkpSSj5aSlJSTj5GTk5iVgJCQkpaWlZSUlZSUlpSTk5aVk5mGt+SYidmNjZCOmJeWmJWWlpealZOUl5SRkZSYlYyUlpSSjZeRmJOTmZWXl5WTmpiUjpOMkpaWmpubl46VmZmXl5OUjo6PlZuWk5WVlZKWmJaVlpSRl5iVk5ickZOXl5WRlJiZl5eYmpqWmJOXaJaWlpyalJiTmZqclpuRnJeZl5yZlpWXlpiWlZWfmpyXmpqem5mVmJebmZOXkZiWlpqYkpeZmJmamp6WlJSWl5abmpWZlZebl5mVl5eYlpeVmJeZmZeVlZeZlJiZlpeYl5eYm5iWl5eZhZqAl5SamJqbnZmYl5eVlpeXmJWPlJKblpWZmJGYlpWYl5iZmpSYjJGXmZWVl5eVl5eUmJmZmpWVlZyRmpqWmJWXlpqRlZqbmZiZnJSTmJ+WlpOTkpeTm5WWkpSenZuYmpqamZeZlZeXnJmclpeYlZmUmJmhoZyempubmJ2cn5ugn5yAnJyYmpWdnJicnJWUm5ydlZqXnJqYlpCOjoyG7+WFhoSM/oKPldXMwZuJhYCBgPbsgIealJSWjo6IqJeSkpSZpZugpZmZmJmTmpiYmpmblJ2am5eXm5aalpeXmZiSmJSXl5iQmJCYkJKVlZWXk5KQipWSmI2RlYySmJOSmZKWlJiAmpaXmZmVlJGYlpWXkY6LjY6ViZGNkIyPm5qRoI6Ui5eSkpKalI+bnJOVlI2EmJCWko6E9pGYnZaKj5Kan5OUl5eWlI+WlpOVlpWRkpGYr5ainJqWlI2amJCZk5KVlY+Tl5mYlo+WkZWTj5GPj46Nk5eOkI6OjZWJkI+SkJGTl5ATkJWUjpKOj46QjJOQlY6Sj46IgYCNgoeKiIuHgoqKg4iIhYCGf4eHhIaDhYaGhoiChIiDioN9h4mGjYeJgoWGg4OMhYmFgoOFiYmIg4uGjIaGiIKLhYeFhYqDh4SEhomEh4OAgoOFioiBhYKFfIGEhISDe4d9f4CCgn6BiIh/gYV9hYGFgoOAg4B+end8736CgoOGh4CBhH+Fg4GAfH1+gn+CfX956n6AeYCEgoJ74OnbeuV+eHt9f4F/gISDeXfqe4OJhYeJhH+BiImCiIuGjIWAfH+Eh4SFf4B+h4mHg4eLiYmBhISFh4aEhIWDf4aIf4OFfoCBh4CE8Id+ioqFhX+DfYSBh4eDgn+ChYCEhoCJgoV+hoCFg4OFiYOBhYaFh4KHgoeFiIiKioeIhYeHiYWIiYuIhYWIhYWHiImIg4SMjoeFh4mGioWGhIKKiYaBhomGi4eFhYWLhYGEhYmIhIiFhomLh4qDi4yFg4aIjJCFhYWHh4WKioqEiIeIiIuGiI+MjISIiYuLioaLhYeIioWGiYuPjYCIhoeMjYyLiouMi4qMi4uMioiPgbHgmInYhoaHh4+OjI2KjYyPjo+JjIyLiYuJjo2CioqJiYGLiI2IiIyLjImJhY6OioWIg4iNio2QkY2BiYuRjoyLioOGiIuPi4qMjIqHjI6LioyLjI6PjY2OkIuLjY2MjI2OkY6PkJGQjo6Mj1CNjYqUkYyQiY2OkY6SipKOj4ySjY2Njo2PjIyNk5GPio6QkY6OioyLjo+JjYmNi4yQjYmMjo2NjpGRi4uLjI2Oko+Lj42Nj4qOio2LjouLiYSOgIuLjYyOioyRjY+QjIuMkpGOkI6RkZCSkY+QkJKPkJGTkZCTj4uOj5GQjIOJiZCOjpCNipCNi42MjY6NjI6GiY6QjY2KjIyKjImQkY2OiYuLkoiNj4yNjI+MjYeJjY+Njo6UjouMkoyLiYmKjYqOi4yMipOTj4+Qi46Qj4+Ljo6UgJCSjIyMio2LjoyTlJGUj5GQjpCSkpGWkY6PkY6RiZGTjpGPh4mQkpCKjYyPj42KhoSGiIHn131/gIbvfYiHq6CZfHZ4dXp68eZ+gI2IiIyGhHyQh4iIio+ZkpWZkZCPjouSjIqOjoyLk5CTjY2Qi5KOjoyQkIyQjpKQkYqRh46EgIuNi4mMioqIg4uGjYOHi4KHjIqIjomMiouQjI6OjYyLho2KiIuHh4SHhoyBh4WKhISOkIWRhoeEjYiJiI+KiJCRiYuKhHuNhoyJhn3rhYqKhX6HhYuNiIqLjYmIhoyLiYeMiYiJiI2ehZWQjo2MhZCOhI6JioyLhYWMjoyMgoyILIuJhoqIiIeCiYuDhoeIhYqAh4aHhomJioaFi4uIioeFhIaEi4iLhIeIh4J6E3t2d3p5eHVzenVzenp1b3Jvd3mFdIB1dndzdXd0e3ZsdXl4e3Z4c3N2dHV8dHh2cm91eHd0cnl0dnZ4eXF3dHd3cnlxdXNzdnl1dHJtcXJze3VvdHJybHV1cHFxanNxdHJxb2xvdXVtbXJseHR2dHRzc3Jqa2xw2m92dHZ3d3F2b3R2cnFyc3R4dXdyb23VcHJtcnZxboBqx83Na8lvbXFtcHBtcnZyZmbXcXR7eHp7c25vfHlxeHt1enNwcXF2d3R1dXNwd3l5dHd7e3pzdnh5e3l4cHd2cnt2b3V2cnFzeHR63Hl0enp4dXV4b3dzeXl2cnJ0dXB2eXR6dXdvfHZ2d3Z7dnN6enR1dHdzdXd5fHt8eHh3e4B4eHV4e3t5d3Z2eHh5e3p8dXd9f3d3ent5eHZ4cHV6eHZxdnR3enh1dHN5dXR4eXt5b3d1cnd6dnt2f394dHp8e35yc3N5fHd7eHl1dXR+d3t6e4F9fnZ5eX19eXV6d3h6fXZ4eHuCfnt5e3x/e31/eH14eX19fn17eHx4pd+slYDQfnp3dH+AfXl7enh+fn53eX17fH57foB1enp7eXF6eXp1eXZ7fXp5d39/enh2cnh/fX59gHxxe32AfXuAf3h5eH6CfXt8fXx7fIF/enx7foCBf399fH18f4F/gIB8goCBgoOAfnp7hIB8eoF9fIF8fXx+e39+gX6CgIB8f36AeoB+e3+Bg4V9en1/fn56fHp5fHt7e3l9e3+Bfnp8e3x8fYGBf395fnx/hIF9fnp8fXx/foB/gX6AeX6Afn18fX1+gHp9h4CBgX2CfIOBf39+gYOAfX58foCAeoCAgoB/g314e3t+gHl0eXt7fX5/fnx+fn2BgX5+gHp7dHt9gYB7eYB9fXt9eX9/en57enuAeHt9en16fXx7eXl+f3x+foGAfn94e3p6fnt6eXt9enx6fX58fXx6eoF7fXl9fX98fXl7eHd6d3t7f4F9f32Df3h8fn9/fnx7fIF+gHuAfn5/fXZ5fX98eX15f359fnh2eHx63L5xdnp93XB7cXdtZFVeZ4BocG/f1XZzfXZ2fnd3a25wd3p8foWAf399f4B+eoB6e3l+eHx+f4F9e357gX5+fH9/fX5/goKCe4F4fHR5e3p6fXt5eHd6c3l1dXl0dHt5fH12eXt5gHl7fXp9fXR7enZ3dHd1eXh3dHV2fXVzenx1eXZ0eHx5eHZ+dnh/fnl6d2h1a3x4fHx3cNNwcnFpbXZwdXNzeH17dXZzenh4dXl7d3l4e4htgn9+f311f392end4dnt5dHl7f35xfHt8eXZ7dnh5cHd7c3Z4eXJ6dnl5dXl4eHd0dnt4d3t1dHF4d355eHR3eXh4bfl/AX6WfwF+iH8Ffn5+f36MfwF+uX8Bfv9/sH8Ffn19fX7/f/9/8X+CfoR/AX6Mf4J+/3+EfwF+338CAgQAgJGPkoyTlJCRlJKNkZCUlJCWkoyMmJmRlJmNkY+RmJKJjIyPlpKSmZGSjZGUlJOLjpWNkY+Sk5OOjo6Nio+OmZOLlY+Mk4yOh4uGiYmal4yMhouDiIiQj42Jk5CPkY2NlYyCgJGOioyFhIqOjJGPlJGKhoqPjYyNi46UjY+NgImKgImGkYWKjYqMioiKhIeHkISGho6Ih4eP//2F+4KHhYGFjIqKjIqIh4r+gvSDhYiKi4uNjI+KjJOM/fvd19rt64KLjYuQjY2IkIuPjpCPkI+NjI2Kk4+LjYeMjoqIipOKioeKjYqJipGNjoyMjYyOkY2LjoyJkI6SjY+QkJOLiI2JVYaIj5CSlIyLiY+Vj4uQkZCQjYiQj4qMio+Jko2OkY+NiIyNjo+OmY+QlpWUlJSSi42MkZKSkZONlIqMkIuRkJKQjZGSj42Tl4uTlJeTipCTkJCQjpGEkICTlZWUj42KlJGRkZOUlZOQipKajpOQkI+XkZOOlo6NkpOWmpKSk5OQkZCRk5aVkZCSkpeXlZSXk5KV232GfquJkYyUlZiVlZORlJuZkZaUmJSUlJGTmpeUlpKUmZeSlZWYm5eZlJeZk5OWlpiXjpeWl5KVnZSYnZmYlZeXmJeWm4CVl5KXk5SWnJucl5WWmZiUmZiTmZeWkpOXlpaYl5eWlpGWmJmYl5WTmZial5Odm5SVlJOZl5mUj5icmpSXmZWXlpudm5acl5mXmZWWmZmXmJqZm5aYmJmZl5abmZedlpmZmZiVlZaVnpqcnJaclpGQmJaUkpaYmpaZl5mXl5WSl2WYkJqYmJqWnJiYnJiZmpmZmJeYm5mVl5aYmZmYlZmSl5eYlpaZlZWYlpqWmJmWmpuYmJqYmJaXk5SUmpmYmJqZmKCdmpeZlJaYmJyYmpqhlZSXlpeQmZidlJmbl5KYmaCbm5mYkYSVgJeamJaZmZaUlZeYnJabmZ+dnZudnaGcmZ6gnJmYnZqcm6CanJebnZuUnJuempiWoJuel56cnpeamZaUmpycmJiSlZKOkIX0gYuJiIODgouhyb6nk4OB/v738OD2/46OkJibmJWVn5mZlJGXlJuUmJycmp2YkpyanpKalpaSmZuWfJyYlpSWmpacmJaXk5GMk42Uj5WXmJeYkpeQkZCKkJCblZWVlpGVk5KUmJWYkZSSlJKZkZedj4+XmpOOkY+Pk5CMjIuWj5SilKGTopicmZGSkoyTmZaYkI+Sk4yIjY6D7/KOiZKIio6djpWVmpCYkpqXmpOWnJWSkYuIjJOFlD2RlZWWlZGVkJKSlJOTkI+TkZSVj42NjpCTkpCUkZeSkpSPl5ybnJaanZaTkpSQlI2Pj5ORlJGVl5SMjo2LgIWEh4WKioWIiYiFhoaHjIWIiIR/iY6GiI2EiIWHjod+goCDiIeHjoeIg4eIh4eFhIh/hYKJiIeDg4SFgYSEioWBiIaDioSFfIB/gH2OjISDfYR7gH6FhYKAiISGh4WEiYJ7doeFgYF9foKFhISCiIWBfX+Fg4GAgIKHgIKEeYKBgIJ9h35/hYGDhIGBfICAint8fYWAgH+D7O9/8nx+e3h+hYKFg4CAgILxfOZ+gIGDgoCCg4eAgomC8OrP1dnn2HiAgoSFg4WChoKFhYiHiIeGhIODioeDhoGDhYOAgouCgX6AhYOBfoeFhYSDg4CEhoSDhoGAhYaIhYWFh42FgYSEgICAhoWKioODgoiMiIKJiIeKhoGIhoKFg4h+hoOGioeFgoWEh4iGj4eIjYuJiomKhIWFiIqEg4qFiYGEhoOHh4qHhYeEhYKGiXuGioyHgoWHhoeHgYSGhoaEh4mJioiDhImHhoaGiIqIhYKHjYWKiIiFjYaKhI+EgIeKjY6IiYqKgIeJioaKjoqJhYqHio2MjYqIio3Qen+GqIOJhIqLj4uMiIiLjI+KjYqNi42Jh4qPjYmMiIuMioiLioyOiY6Kio6HioyLjY6Gj42MiIqTiI2QiouJiomHiomTi46IjYuMiIyPjoqLjY+PiY6NiZGLjoqIjo6PkJCSkI+IjoyOjYiLgIuPjo+PiZSQjY+Mi5ORkoiBjZCRjI6PioqLjpOOipCNkI2MioyQjI6Mj4uPjJCOjY6MiI6Oi5GMjouPjo6NioqRjpCTio+Oh4iMjYqIiouOi46MkI2Oi4iMj4aPjo6Qi5CNjpWQkZCOkY+PkJCQjI6KjY+QkI2Vio2QkI6NkYyKgI6MkIyNkIyRkI6Nj4+RjI2Mjo6QkI6Oj5CPlJKRjY+JipGPk42OkZSJiI6NjYaRjpGJjo+Nio+QlZGPjYyHio+Nh4uPjYqOj46IiIuNlI6Oj5aNj4+QjpWSj5KTkY6NkY+SkZWPkI2Rk5GGjo+Uj42MlYyPi5GNkYmRjoqJjJCPgI+NiY2Kh4mB7nqEg4R/fXh9hqCVhX92d/Lx7OnZ7vWEh4mOjouHh5GNjomGiYeRi42RkI+SjYiRkY+JkIyNiY+QjJKOjo2Pj4yRjo+PjImGi4CLhYuMjo6Oio2Ih4eCiIeRjIqJjYeJiYqMkI2Ph4uKi4mPh4yPhISOj4qGh4eIgIqIhIOBjIWJkoqTipSPjpCJiYiBiYyKjYWHiIiFf4OEfObmfXyEfH+BioOLiI2Hi4mOjI+HjJGMioqEgoKIiomIioyJiIiKi4mJh4qIioiIhYSJiIqLh4SFhoiJioWKho2JiIqFjY+Kj4iOkIqJi4uGioWFg4eIi4WHjIqCg4KAgHN0d3d3dXN1dXh2eXZ5fnV5dnVyeH50dXlxenN5e3ZwcnB1eHh4fXV0dXV2dnZycXVrdG54eHRwdHR1cXR2fHJzd3h0enR3a3BxcnJ+e3NybnRydG1xcm1weHJ0dHJ0enFvanV2c3RucnF3dnJye3ZzcHJ2c3Btb3F2cHR2a3R0gHJvdG1yeXZ4dHVyb3NxeW5ubnZzcXBtzc1u4XBwbWtxdXJ1cW10dHTWbcdxcXNzdXRzdndvdHlz28y7wr7JuWtwd3dzcnZ1d3Z3cnd4enh6eHZ1fHRzdXJ0enR0c3x0c29zd3l1bXd5dnV2dXF1dnZ4d3NzdXd1dXV3eIB6c3Z4gHV0enZ6fHl3d3h9eHN5eHl5d3V7eHV4d3t1eXV4end5d3hzfH15gHp4gH55end4dXd5dHl0dXp4eHF3eXV5dXh2dnh1dHV4eW52eHx3dnZ1d3l4dXZ4e3h1eXd3eHl4eXx7dXNyd3x0cHR1fnZ1eHd5gHl7d3t2cnZ6fIF8d3x8gHh7eXl8gX5+d310eX18fHt5d3q+hZClpHp8dHx6fHuCe3h4fYF2fXt5d356eXd+fHV8e3t9fXZ7eX18eX95d3t4e3t5e3x+gHx7eXyEeHx/e3x5enh6f3uEf4B5fnx/eHt+hH17fH6BfYGAe4B7f3t6gH99gIGBf398gH6Af3d8LX9/fHx+eIN9fX59fYKDgHhsfXx9enx/fH13foB+fYJ9f314e3yAfIB7fXp9fIR9gHx7fn16g3x+f4GBgX99fIGAf4J9gYF9e36AgHuAf3x8fn9/fH17fX6Cen6BfYCDgnx/g4F/gH58eX2BgH58fHt/e3x/fIV9fX5/fn2Dfnp/e4J/f4F6fXl9fH5+f3t+e32AgIN+foGChIOBgHx7enl+fYB6fX98enp/fXx1f3+CgHt9e3x8fX+Ef4B9fXp2f3t1eX57e3p8gHZ3e32BfHh+hXd5en1/fXt8fn5+f357e4F/gX97fnqBfXh4fn+Af32DfHx6f39/eoB+eHd8f4F+fHx/fHd8eN9wenh6dnJtamhrY11jY2bY2tnezdvddXl8e3d4dXR8fX57dHZ2gHl8gICAf356d3t7eHh/e356fn56gXx8e318eX94f399e3l6bn14fnt8fXx8fXhzeXZ3eoB9eHV6dnh3enuBe351e3h3d393eHt0dHp6end1eHd7e3Z1cnhzenp5fXt/e3h+e3d0cnp5eH11eXh2dnF1eG/Qy2docmxsbXFzdnR5eHx5T3Z5fnl3fHl5e3h5c3V8ent6eHp3dnl5enl1eXh5eXt2c3Z5eHl2dHd4d3l7dnhzfHh6fnd6e3R+eXx+eHp7enV5dnd1dnp9dnZ7e3V1dHH/f5h/BH5+f36NfwN+f36Nf4d+/3/VfwV+fXx8ff9//3/yfwF+j3+Hfv9/BX9/f35+3X8CAgQAgJGVlpiPkZKRjo6SlI2PipKOkZSUlZOWj5KPlIuUhpOPk5CPlpOPkpOUlpKQkY2Uj5WWlZaRkJCPi4qHiY2QkIyQkY2JjYqMjI6EhpCQjZCRi42Mi4uShJKQkJOTh4aJjJGVi6WHho2HkI6MjomNjoqKi42KiI2GiIaTjo+Ij46LgImLg4qHi4qIg4aIioXwiYiOh4OHjoeEgPqSioOGgvaEh42Mj4aNiYOLjYKAhYqJjIyMiY2Ni4ns9fGB9vHp9feGi4yKko6Ojo+Ljo6OkJKRlI6Nj4mJiI+KjYiKkYuRjIyFjI6MipCKjoaJi4uKj42OjY6IjY6HjouUkJCJhoiKgIqOiY+VjIyPiJGTjI+OjoiMj5GOj5CKjIyPkZOPj4mGj4iYipCPl5GQlZSPjIuNkJKPmJGVkZGOk42MkZKSkZOTj5aSjpCNk5WTlpWYl5WOkIuQkJSPjZOUkpSVkouKjZGSkpCWk5KWlJeUlJSTkZSRjpWVkI+RmpSUjYuTl4uQgJCTlZqWmZeSk5ePj5SSlpORk46SgOLkjJWYmJSXkZeTl5OPl5eUkZaTkZGSk5ORkJOWlpSXl5KTl5aakJmVl5qVkJOZmpWfl5yelpiYl52ZlpqVlZiYlJqXj5WVlpebmJmSl5OXlpSZkJGXlZabmZialJqalZmXk5SUk5mamJeWgJeZmZWVmp6UkZWUlZWVkJaXmZqZmZicl5ubmqCZlZqalpWamZGRlI+UmZublpuZmJiYmZeYnJ6an5eVkZWZmpqcnZiYlpmUlZeUlJmWlJSZl5GTkJeXlZeam5qam5aXl5qclZeemJubmpuWmJuXmpuWlZKYl5yXl5qSlJmblpWbgJiZmZqUlZWVnJyYlpaamJeYmZGYlpiYnZaWk5maoJuWl5OWlpSWlZuYmpOXmpOampePlJSVk5aRm5ydoZiWkpKWmZWTmpyVrJeZm5OZkZadmJGPn5yak5ibmpmgm5mYm5ufnZecop6bmpaXnZ2ZmJqZnJmbn52bmJ+ZnJuamp6cgJebmJWUkoiJ+uOTjYaRh42cgMKqloiGh4H/+/Xzg4uS/oP5sYuSj6CSkJyWmJiWmJWSmpuYn5uXmpiXl5iVmpiXmZmXmJSSl5SbmZqVl5KZlJWUlZOYkpWVm5SUlZOUj5CRkZKOkpOYlZmRlZGUlI6OlI2VmJeSlpaXl5GYk42PgJKWkIuNmpGTjZeYlZSZlZWUmJiUlZOMlpGNj42UmIyIiIH4hZ6RhYaGoJCUkpOWmpSWlJWVkZCNkY2Qjpebl5OSjZKTkpGSkpCRj5STkZCTkIyPkZCRj5KTko2Qk5KQkpCWk5WVlJCZl5qRlpGWiZKZlIyTlJiVk5KPjZSTlZKTRYeNi42DhYeHgoOJiYKEgoiDg4eKi4mKh4eHjISNfYeBiIaGjYmGiIqLjIiEhYCKhomJiYqIh4aIhoaAgYWJhISHh4SAhYSCgHx+hoWDh4SBgoOEhIh8h4OHhoZ9fYKDhYZ+k3yAhX+FhIKFf4SFgICBg4J/g31+fIeEhH2BhIB/gnuBgIN+gH2DgYGA6oOBhX58foN9fHbkhH98fXjfeYGEg4N7gYF6goJ7e32BgYOCg4CEhYOB3O/pffDv4+vqfYGFg4aEhYWHgISGh4aJiIyLiIaIgISChoCGgIKFfoeDhnuEhYGAh4OGf4KEhIKHgoOEg4GDhYGGg4qIhoN9gYWCg3+EjIWDiIKJiYODhIN+g4mKhYeIhISHhoiLh4mCf4l/jIOJho2JiIyJhIWEh4uKho2Eh4WIh4mEgoaJhoSIi4aNhoaEgYeJgIaNi42OjYWIgYeIiYWEiImIhouKg4WHh4yHh4uKh4qJi4qHiIiGjIWDiYqFhYeOjImFgYmMgoiHiouOiY2KiIiMh4eKiIyJhYuFiHbS14KKjYyJiYmLiYyLhomLiouNioeIiomGh4eIjIqJioqHioqLjoWMiYqNiYSKj5KLlI2PfJGNjIuJj46JjYuLjIyJkouFjIuLjY+NjoeNiYyMjY2HiI6Li5CPj4+JkI+Lj46Ki4yMjo+Pio2Oj46Ki4+SjIeMiYuJjIqMj5KSkI+PkI2Pj5CUjo2PkIuKkYyHhoqEioyPkY2Sjo2MjY6Mj5CQjpOMi4aLkY6RkI6OkI+Ei4CJio+Qh4qQj4iLiY2PjY6Rk5GRko6Nj5CRjY6TkJCSlJaPjpGOkpOMjImQjZCPj5CJipCQjY2PjY+Qj4yKjYySkY+KjZGOj46Oio2MkI6QjoyLkJGWkYuNiYyMio6Pko+Pio2Qho2Oj4aKioyJjoqRkZSXjY2KjIuLiYiPkIyojoCLjoiPipCVjoOAko+Pio+PjYuUj4+OkZCRko2OlJKOi4qLkJCKi5CSkY2NkI2PkJSPkI6PkZGRi5GNjIqIgYP02ouFf4mAhI1vnYp/eHl8ePPx8e2AhInteueZf4WDkoeGj4qPjo2Oi4iQkY2SkI2Qjo6MjoyRj42QkJCOjIiPjRaQkI6NkIqRjIyNjImPiY2JkYqKiouLhIeAiYOJjI6Lj4mMh4iHgoWLhoyNjIiIiYqMhoyKhoWFjIiDg4+IiIGLjY2MjYqLiY2PiYqJg4uHiIiFh4uCf3587HuLgXx6eYyDh4iHiZCJiYiIiYeFg4iJh4SMkIuKioWHi4iJhoeHhYWJiYmIiYWChYiEiImKi4yGiIyIh4iEiokdjIuJhouLjoWMiI9+h4+JhImGioyHhoWEioiKiImAeH93fHR3d3d0c3Z3b3VxcnNydHt6fHp3dXZ7cXlsdW1zdnd8dnZ4fHt7dXJ1cndxdHVyeXp1d315eXBxd3p1dXd3dXN2cnV0c25vd3R0dXJzcHV2d3lueW92dnRwcnJzdHJreW1xc3BycXFxbXV4cnFzdHNxc3B0cHl0dnFvc2+AcHNwd3J4cHdzdnVycdl3dnZycG9xbWxlxGxtbmtoxmhydXZwanJva3RwcHFwcXJ1dHJ0c3VzdMPb2nTg3M/UzmtveHp2dHN2eXh5eXx9d358eXx7cnd2eXF2c3R2bnVydnB4d3JxeHd3cHV3eHV2c3R1c3V2dnF3dXd8enZxdncueHNzeHl4eHp1eHh3d3d2b3J6fHl0d3V2d3p6enl6dXJ+cnl4eXd8enh+e3R0coR4gHlyeHV5enp2dHR8dnN2e3d7dXZzcnV6dH95enx7dXdyd3p8eXd4dnp4enp1dnh4gHh2e3l2eXd6fnh4eHZ6dnV6f3h1d319fnVweXtyd3Z6enx2d3l7e4B7enh1e3d2end2aLvEdHl4f3t5e3h3e4B3dnp6fH54eHl8d3R2eXd9gHx4e354d3Z+fXh5fHt8fHV6f399iX5+gYB8fnyCf31+e3t/fXyBenp8e3t/gX9+eoCBgH5/gH97fHx8hIF8gH6AfXuBe3x8fYGAf397fX2Bfnx7fn9+e394fHt8gIJ/gYF/gYKDfnx7g4aCgYN8fX2DfHp2d3V6fH58e4B7fX98Xn58e359f4V8e3x+f3+Afn19g359gHl/e36Eg3t9f4F9f36Af4CBf4OBgIJ+foKDgYCAg4GAf4KBfYCBfIB/foB8gH5+fX+DeXqDg35/gICBgoOBeYB7gH1/f36Be3+EfYB+gH9/gH58g32AfHx/fHl6eoB7fYGAe36CdHp8fn58fH53en1+f4uHfoF7fH98eXh+f32gfHh7e4B7hIt+dHF+e3l3fXt7doJ8f31+e36AfHZ+gIF8eXl9fHd5f4J8enl+eoB9gHx+fn1/e3x3f358fHd0eeDBfXhyenN0eVtvYIBhYmdvbuDm5t93eXrRbclzbXNxeXV5fnl9fH1/enh+fXh/fXt8e357e3yBfn6Cfnt7fXp+en99fH6AeoB6e3x8d394fHh+eXx2enp2dHR1enN3enp7end6d3h3cXV5dnt8enh3eXl6d3h5d3d0eXh0cn93dnF5fHl9enl5enl9eGx3eHB8eXl3d3R3cG5xctdmcWxsaWd0c3V6dXZ+d3d2eHd5dnR5e3l3eH12enZ1c3p4enl2dXRzfHx8eXV1cnN5dHl4fHx8dHV+enV2dHh4fHl2c3Z5e3eAd31vdHx6c3dzdXx4eHVyenV3eXn/f45/AX6KfwF+hX8Bfph/BH5+fn+Ffv9/1n+Cfv9//3/zf4J+j3+EfgZ/f39+f37/fwF+3X8CAgQAgJ6VlJeSkZKXkJOQk5KVj5CQlJCUk5Obl52cmI+YkI2Vj4+QioqPjJOVkpCWj5CTk5KPjJKWipKOk4yLj5GPkpOPk56YkI+KjJ6ch52QkYqRj5OWiouRkJSQj4+PgY2MkImHjIWIiImJhouIj4yIjIuHjI6MjI2HiYeJhoeLh4WIA4GHhoSMgIaKjYaPi4uOj4uHioeOgYqEhozkg4KF9oiIh4qGhpGHio2Mh4uOjIeLkpCRioqKjfiDg4eEhIGGi4yLk42Nj46SkI+QjpKOkpCNipOOjI2OjI2LipGLj5OKkIyShJCPj42NkY2OjYaGjoyRkI+MjIeNi5KPk4yJiYuIio+PkIuNgJGPkY2NkI+MjY2Pj5eQjYyOiJGSjZCQk46RjpSHi46Pj5KSlpSOlJGQkZOMj5GRjo6Qk46LkJOQkZKRjJSQkpKRlZSYkIyOlouOkZeRkJGSkYqRlJWOipONjJSQkJCSl42RjpSSk5CZlY6OjYiRkYycl4iTj5SSkpmXk5COk4qYgJWSkZaPkpCUmZSUkJSUk5SMkZSblJCTjZGTlZKYlJCSlpOVlZWOkZOQk42TlJGTl5KPm5OWlpWVlpmWmpuVlZmUmZWTmZ2Tk52Zm5aYlJeTmZeYlpeVnZmdm5eUmZWamJSVlZaZmJWUlJKVk5SVlZaZj5qZlpyYm5qam5iZlJOVbZaWmJmamJuXmZqYmpaVmp6WmZmXl5uglpKamJyXl5SPk5iZnpmamZuWk5qUmZSXnJmYmZaemJqZl5aVmpaWk56YkZWYlJSSmJWWlJaWlpeVmJial5qcm52XmZiXlpuYlJmanJOWmpiXmpaWlpqEl4CDmZeXmZmSlpWTlZyXl5aYm5qSk4yal4+alZaWlJiVm5WPlpmYlZeXlZmbmZibm5aTmpSVnJOTl5WTmJaTmJqTlJyWnpWYkJaalpiWlZWUm5WXlJSXmZaQmpaYm5yZn5eapJebmZWZm5adlJuZl5qanZybnJySm5uXnZmXnZ2al4CYnKGWnZednJuam5eSjpCL/9yIi4yMi4+QmKyVlYeNh4r7/v2Ei4yJqZGMj5KQn5OflJeYm5aVmJecnZqVmpWZkZadlpyYmZuWlpeWmpeXnJyWl5aTkpSTkpaVj5eYk5mUk5WTlpaXlZOQlJOQkIqTj5OXkJOXlZCXl46QkJqak4CSmZiIkZaRj5WQl5aQlI2SlpWWkZKWl5WVkZWTl5SPlJOQkYyIh4qIhYCEhoX4mYiCkpGXlpuUk5iQjo+QlZOVko+Rj5OOjZCOjo2OmJKUk5GSk5KWk4mOjpOUkJGRk5GMkI6QjY+PkY+XlpeXmZiXmZaSl5STlpKQlZKPlJWSkAiXlZiOl5eRjoCUj4uKh4WHi4WJiImGioaGg4SEioiGjoyNi4uHkIuEi4aEhX6AhoOHi4iGiYWFioiGgoKFjH+HhomDgoiJhIeIhomPiIaEgYKNi3uRiYeChoSIioCCh4SJhoWGgnmEgYeAfYN+gX9/fn6CfoaCgYaAfoOEgoCCgIJ+gH5+goB+f0x3f32AgoOFf4GFgYaCgIKFg3yAfIV9gXh5gNd7e3zqf39+gH1/iH6Cg4J/hIaGf4KIiIWAg4OE7X99gHx+fICAgoGIh4GHhIuHhoaEhIiAg4WKh4aIiYSEhIKHgYeJg4eFinyGhoWEhYiDhoV/foKChYaIhIN/hYOJhImEf4CEgIGFhYeDhomGiISFhYiDhIeGhoyHhIGFhIuIg4aJiIWJhYmEhIeIiYiHiYeFioqIiYuDhIeHh4aIjIWEh4mGhoeFg4iFhoaGiIiMhoGGjYCAhYeMhoSFhIWBhYeHhIGIhoOIhYiEhI2Ah4SHhYeFjop/goV/iISAkYp7ioiMiIeNi4iGg4uCjYqKiIiGioeJj4uKh4qJiYqFioSLiYWLhIWGi4aLh4WMioiJiYqEiY2IiYOIjIqKjYuFkIqNjIqHio6NkI6Li46Ki4mJjJGJiI0JjI+LjYiMio+MhI4KlJCSjoyJjoyRkISMgJCPjIyLjIyLio2Mi42GkJCNjoyQkI+PjpCKiIqOj5GQkJCSi42QjpGMjZCSi4uOjIuQj4uKkY+Si42KhYiMjpSNj42Mi4ePiI+KjZGLjZCLko2PjouLio+NjIeVjYaLj4qNh5GNioyNj42QjIyOkIuQlJGSi46MjYyRjouOkZGMgIySjY6OjIyNkY2Pjo56kI2PjpKKjIuJipGNj4yMj5GLjIWRjYmTi46LhpGNkY2HjpGOjI6Nio+Pi4ySkoqIj4yMkYiJioyLj4uIjZGKi5GJjomMh4uPjYyMiYiIkYuMioiOj4+JkIqMjY+Ok46Ol4yNjYuQkIuRi5OQjZCPj46RgJCQhZCRjI6OjY6Pj46OkJKKkouPj5KOkI6IhoiE8tKBg4OGgoSCg5R5fniAfoPv9fV/hIR/lISBg4eGkIeSi4yKkIuKjI2SkY+Mj4eMiY2TjJKNj5GNjo2MkI2Nk5GLjY2LiouKiY2Oh46PiZCLio2Ljo6NioyIiImHiH+JhYmMgIaJjIuHi42FhIWNkIWIkIt+iYyHhYuEjo2EiIaKi4qMiYqKjYuOhoqJjoiFiYmJhoN/gYOCf3p+gHzeinx5g4WMi4+JiIqFg4aIi4aLiIqLh4mFg4mIhYSIkIiIioeHiIeOjIGFhoqJh4eJioiEiYmJg4aEh4aLioqLiouJj4yJFYyKiIqIiIyHhoiNh4WLi42Di4mGhoCHgnh5d3N4end7eH15eXV0dHJ1eXZyenl6d3h2en10dnF0cm9wdnFzeXZ2e3Fwd3V1c3R1fHN3dHZwdXl2dXZ4dXp8c3Vwc3d8emV4d3NydXR6fHFzdXF4d3Rzcmp1bXVybHFwdHNxc3J1b3ZxcnZvcHV1c25ycHNwcXFvdXNycYBtdG1zd3R2dHN5dHx0dHB1dG1ybnJucmlkZrdsbW3VbW9sbm50eXFwdHBzeHh4cHV4dXRuc3Vv1HZzdm9xdHdwcG94e3F3dnx6d3lxeHp0eHJ4eXp7en14dnVyenN2eHN4eHxvdXV2dHh4dHd5dHFwc3Z1eXZycnR1enR8dnRwd4BzdHZ2eHd9fXd0dXd2enNzeXZzend1dXZ3eXZzdXp5eHl7eXx3eXp4d3l3eHN4enh8fHN4d3l4dHl+d3J4eXd0eHZ0eXR2d3V3cnZ7b3V7bnJ3e3ZzdnN0c3V4eHVzdXd1dHZ5dnaCc3Z1d3d6en17cHJzc3p2cX15cH14d3h7fYB4eXZxenJ8fX54eHZ5dXiAfX15fXx6e3Z7cHh5eXt2eXh8dnp3dn14dX16fXd9fnp9eXh6e3h8enaAfHt/eXd6fX5/f3h7fnt8e3p5fnx4eHh7foB7fXuBfn9/fn2Cf3+AfHl+foSCf398eYB/e357fn98fX1/fX14fX58eX6AgYCAgYKEfXp7fH2BfoF+g4KAfoKCfXuBgX95fH59gnx8fYGBg3+Ce3Z4e3p/f4F+enp2enZ8fX18dnyAe358fn5+fXx9fnl5g4B7fX55fXl/fHt9gIB9gH59fX9+f4OBgnp6fX5+gH18fX5+eXuDfH57gYGBgn+AfoBzgYCBfoR7foB9fnqEfn5/eHqCe4B5f3x9f3h+enR/f4J9dn5+fnh8fHh9fXl6fX16d3p7en56e3t7eX18eXp7eXp+end3fXd7fHx7end3eoJ4fXx5fHl/eIF5fH5+fH2Ad4J6fnp2eXd5fnqAf3p7fH59e3p5eIF/fHx5fn18fXt9e315gXx9eYCBf3+AeXZ8eN69dXh2eXRxbmpxV19lbnF12uTjd3l4bnJxcXJ3cnp0end3d4J6e358gH5/fXxzd3p4gHl+e36AfX5+ent7f4J/d3p9fXl2d3d7fnl8fXl9e3d7eXt/fHh7end6eHdudHR4eXZ5enp1eHh0dHV4f3V5gXpteH13eIB4c317c3hzenl3enh5eHx8fnh6eX13d3l6fHR2c3F0dXByc3FsuHRta25zenp9eHl6dXR4en11fnl6enl7dHR5enZ0eH52d3l5d3Z6d3t0dHl7dHd4e313dHt6eHR4d3p4e3d2fHlzdnl5enl3ent3eXx0d3Z6eHh7d3l0d3h3df9/m38Ffn9/f36YfwF+/3//f/9//3/Uf4J+j3+Dfv9/in8Bftp/AgIEAC+clYuRmpGWjY6LlJCSko6Wk5GMkZqTjo6djJSTkpiUkpSSkJKWlJORjpGTkpCOioWRgJORk5KTkImOiouUko2RlI+QkI2RlZWYj4iDiJWLho+OhoKKkI+Sj4yQi4yQkI+IhoeEgIKGh4qNk4uMjomKiYmQkZGNjZCIg4eMhI6Fh4qCiIuLiIOHiIWGk5OFi4aDj/yFio6cmYXs/4aCjoGDhYOKiI2Jh4aFh4yMiZCNg4qJgJOHifaDiouSiYuQiImPl5qTjJSQkImLkJCOkYePkIqMjYmKkIeLjo+Kjo2Ok46VjYyKjpCSjZGMi4yJkoyOjI+KlIqRj5SOh4OHiI+KjJGMjYyQjpCNjoqNj4uNiJKNjpGRlZSPj5GOjouOjZSPi4iOlZKSkpOPlpOXjI2RjJGUgJSTlJGMhJaRko6OkI2IlpOTk5SNko+Rk5OVj5GXkJOVlJORlI2TkI6SlJaMmJOVk5aXlJWTk5aQl5SYk5ORi5WQkJKXjpKUkZGZkI+VkJeWlZiUj5GYlJOWmpaWkJWXl5OVlpCVk5ORlJSWmJWSkJSTl5iVkpOSkpePkI+VlJSRgJiVlpWWlZeWlJaSlpqYm5WZmZOUnJyXnaCZkpeam5mXkZOXm5yWk5SUm5mUlJuXmZqZl5mVlpSWlJaXkpWWkpaVlpSVlpmVmJqZkpuYmZWXl5qZl5mWmZeXl5WQl5mWmZWVlZeVmZOZlIyYlZmTmZCYmpOikpuenZaYlJWUlpiYgJmYlJeTmpSUlZaXmpmYmZiUmJObkZGTkJKWlJWVkpaPlJeSkZWYm5edl5iYlpOWl5aYmJqZm5mXmJaVlpeampyalpebmZiTmJySmZeYmZmVlpiXm52WmpiYl5qXmJiTl5qVlZeVkZacl5iWk5aTl5iYlZmYlZOXmJuekZiUmZeWgJWSlpSQj5OXk5acm5eWmJmYl5aclpOalpSTl5eYmKCbk52cm52Zl5malqSempmXmpuXk5aPn56XoJ+enJWfmJmhopeamZuanpmZnZiXmZyVlZaVkYj264Lp+YOPh5OLqqOPjYeAhISNlZyPi5ORlI+VnpGfkJWWmJaPlZSXm5yWgJiblJqamJealZ2cm5mfmZygmZmamZqWl5GQl5qYlJWQko+NkoyVlJiUmZOajpSRkJCPj5WQkpCPj5WQjo2aj5WVlpOZmpOVmJSYlJeTj5GUk5WRlY+SmJSZmJaSkJGNkZKXlJSRmo+Ri42SkZGPi4iEh4yHipaRlY+PjZOSjZOTTJCPkZKMjpCRj5CVl5mTkJaYkpGSj5KXl5OTkJiVkIyOk42MipWTmZSMj5SQlJmXlZeRjpSTmJWQlpmXk5OOk5KQjo+QkJSQl5WTjZGAkoqChY6DiYaFg4qFh4aFi4iGgYaMh4OGkIKJioiMiIeKhoWHiYmJhoOJh4eFgYCGhoOHh4iHioqJhIGEgIKIiISIiISEhoSGiIqLhIJ5f4l/fISDgHuBh4SIhoKFgoSHhoWAfn9/eXt/fYCFi4F/hX+DgX+HhYaEg4SCfYGFfIVjfn6AfIGEg4N+foB/gIqLf4N7eYTzgYGBi4p83e17eIN6fHx8g3+Egn57f4KFhX+IhXqCg4h/gul+hYKKgYOJfoGFiIuHg4uIiIGDhoiHiICIi4WFhoGDiICEh4aBhYSGh4WKhYOAhoKJgoKEgIiChYSGgouDhoeKiIB9gYSKgoSJhYSCh4iKg4WChYeChYGIhIeIiouMh4OGhoWFhoaMhIOChYyIiomGhYuJjIGGiISJjYmIiYeDfo+JiIOCgoV+jImLi4aBiISGiYiLhIeLhYmMiomIh4GIh4aIi4uEjIuHi46MiIpSiIiMipGIi4eHh4CKhYSGjoWIjIiIkYeHiIWMi4qMiIWKjIqKio6Li4WKiYiJjI2LjouJiYuLioyKh4iNiouOi4eKi4mOhomHi4uJiIyKjYqIiYSKgIeKj42QiYyOioqQjoyQk5CJjpCQjImKiI+RkIyLjIqQj4yHi4qOj46Pjo6MiYyJi46Ljo2Kjo2MjIyLi4mKjY+Jko2NjoyNjZCMko6RjY2Oi4SMjouMjIqMj42RjI2NgY2Nj4yRiZGRi5eGjJKQipCKiYuMjI2MjYqIiZGKi4uMT42QjY2NjoyLiY6FiIyGh4yKio2LjImNkIuJio6QjpSOj4+OioqNjo+PjpCSj46PjouNjpKSkZGPj5SRkYuRkIqTj46NjYqNjIuPko6Pjo2EjjuNio6PjI2Oi4eNj46NjIuLiI2Ni4yRjY2LjI2PkIaMio6NjIuIioyIhoiLioyPjYmJjI2OjYyOjIiQjISLgI+PlZGNlY+PkYyKjI+NnJKLjo6NjouJioSSkIqRlJSRiZKNjpaWjZKNjY+PjI6QjI6NkY6KjIyJgevcedroe4F6g3uRjIOEf3t+foeMkoWBhYWKhouPhJCEhouNjISLio6QkYqOkIiRj4yLj42SkpGPlY6TlY2PkY+QjI2Ih4uOgI6Li4iJiIeLgIqKjouOh4+FiImJiYeJi4eLhoWHjYWDhY6GiYmNiI6PiYuNi4+JiomIiYuJiIaJgYiPipCMjIeGiYSIiYyKiIiOhYWAhIaLioiFf3p9f3yChoiKhYSFiYiFioeJiIiKg4SHhoSGiY2Pi4WMjomDh4aJjYyJioWMMIiIgoSIhIOCjIiQioWGiYWJjYqLjIiHiIeMi4WLi4uKioSKh4WHhoaHioSKiYeCiICDeHR1e3R3eXh0dXZ4eHt7eXRvdHp4dXZ8cHh9d3t5dXZ1cHV1eHl2cnR3dnh0bXd2dHl1eHh7eHRxcnRwb3J0cnJ0dXR1d3Z1eXpzcm1xeXFwc3BybXBzcHZzdnZucnZ0dXRxc3NubnFscHZ2b2h0b3Zwb3d4dnZ1cnRxdHNvdoBxb3NxeHV0dnNzc3J0eXhzdWxocuJwcW5ucmq/x2lqcmtra21vcHBwb2xxcnd4dXp2bXF3dHJ02XB4c391dHpxdXZudXl1fHh7c3N1d3V7c3h7d3l3dnd6dXl4dXN2dHJ4d3x3dXVycnVveXdyeHF5dnd2d3J3d3V5e3x1cXV2eoB0eXt4dXV2e3l2dXl1c3N1dHV1d3t7e395d3V4d3Z2dnR3d3V3e3h7enZ2eHh+dnd4dniAeHt6dnVwfnZ6d3Nxem54ent+dXJ4dHV4enxzd3p0dXt5dnR2cHh2eHx6e3V8fHZ6f3x4eHd5fHmAeXl4eXhyfHhzeHt4d3l3eYF6eAl2dnx8d3d4eXyEeit+e357fnt7fnx6fHx3eHp8e3x8eHl6fnt6gXx5enp5fXx4eXt8dnh6e356hHmAenp8e356gXd6e3x8f3x7foKBen56fYJ9fHx+f359fn95foB8dnV6f4B/gH9+fHd8enqAenx7e4CAfnt+fnx7fHt8eIN+gIJ/f318e4B8gH59fn10eoF9fH15fIGCgn59fXF8fX57hH2IgXyGeX1+fXt8fH1/e319fXx7d3uBd3aAdnh9fn19e3x/fnx8eXqAdnZ+en1+fH56fH96eXuAgn6DfX1/fH19gYJ/fXuAhH2Ag4B9fYCCgoJ9gIGDgIB8goB4hIN/fX18e3t8f4B9e3l6fHp8e4B7fYF5e4B6eH58gHx7d3p5fHx7eX19fnp6fHt7cX15gH18fHZ4fHl5dHuAfHx9fXd5enx9fX58ent6eHx+eHqDgICAfn94e3p6enl/fYp9dn18fHt8dHZ0f3h4foSDgHqAe3yCgn+CenuAgHp8e3eAgX19eXx6fHfYw2zCzW1vZW9lcW9vcnNydHN8fYF2dHJzeHZ3d3J5cXN4fX12e3l9e395fYB2gHt4d36AfICAf32AfoGBe31/fnx7fHd4ent+eXp5eHl4e211eHx6fHR9eXt8eHt0d3hyeXRzeX51d3Z7dnR0gXl7e3d5fXl8dnd6eHp4d3R2eW57fHp8enp3dnp3eHl8eHp2fHV2dHV5fHd6eHNua2pud3R5enRzdnh1dXd2end2fHh0dXNEdHV6fX58dnt8eHF4cnd3eXh4d3l6fHZ2eXR2dH54fHd1eHl3eXx6en14d3h3e3hxdHN9end0eHZ1dXl5dnd0e311cXv/f5V/AX6Gf4J+mn8Bfv9//3//f/9/1X8Ffn5/fn7/f/N/AgIEAICXlI+Ujo2Ok46Pl5STkZKTkZKZlZaVjoyTlZOUl5aLkJOTlZKTkpaUj4aTlZWYkpiOlJKPjJWSjZGTj4uGho6Tj5GQkJGKkY6Hj4eDi5KRi46QkYqJjpKNkJaRk4uPiYOLkIWKg4mP7puVi42RkY2KjIyTkY6Kj4qQi4yQkYqRiICGi42SiIaJh4uJjYuHjY2KiIiMhYOLhu+bi/HM5vWK9omGhY6Fi4iFjImNjZSOjpKIi4iLlI6OhIWHi46Ki4+OjpKmq5WPkoqQkYyRjJOPkpGNiY6Pi4uPhImQi4+Oi4iNj4+JkIyMjo6MkIaLjYyOj46MjoqJiZOQjIqIio2HjICNjoyKjZGRjI+MkIqLjoqNl4+Sj4yJjZOLjouQjJKPio6SkoqQjoyRkZGPmJCTj5CTkJKOk4+UkomPk5ePkYyOj4yTlJKQk5SPiJKUjY+Tj5SWl5aVkpeSlJGNk4iUj4qSlJaUkJWWh4+VkpWNkY+Zj5KTkJuUlpSSlJSXk5KUl4CXkJuUmZWVjZCUnJWYlY+UkpOYmZeTjpKXkZGQlJCNl5eRlJKUlJWWlZSRkp2SlZWXkZWVlo+TkJmUl5qSkZOZl5iYmJGXkpuZnpaSlJGUlpyYlJeTlZuamIGUk5Oal5iVl5qXmZeWmJuSlZiTl5eZmpeVoJSUlZWYmJaTlJeXmICUlZWVmJWXk5mZk5eXmZeelpmVm5aZm5idnpaTmZOWmJOXlZaVl5yalZiSmZeWlZaZlpqZl5iVmJibl5uUmJOZlpaZkJSUmZSQkpaMl5mYlZqYl5eTj5OXlpqcnpaXlpmbmI+llpiXmZqXlpuXlJealpqVnJuUmJiWl5iWl5iUlYCVmZibmZOcm5eZlpiZl5qWkJWXlZeYlpmYk5OXj5eUk5abkJGWlJWUl5aXlJaWlpSVkJeVmJWVmpaSl5iZmJudl5aVkpaVmJSVlZaRlJeVnZKYmZuRmJqbnpibnpyamJyemJmcm5mamZGWn52flZyZlJiam5eYmJyVm56XnZ+XlICam5iYl5KRkY2MifPah5CUl4GNoaWTnamim56clJqYj4yWi5Sdk5mdlZiVlpKbmZugmpmUmZaZk5SalJiWmZycnJuem5icl5mYl5SZlpWUmJqQk5CTkImRlpeUkZaVkYyEkJKRjpCQjpGQkZOUiomQkI2Tk46WnIyTl5KWkZCQkYCSk5SXl5aRlJaTmJiXmpSPkJGUmJqPk5KNk5CQjo+Ij4yUkIOC+IqKiI+Mjo+JipGRjZSNkYqRi4uMjY6Lk4mPkJORko+KkJCFlJWSkJSSkZGLkY2Mjo6RkZiTjo2SjJCajJWUm5OYmpOYl5KXlpiTk5KFjZOclZOTkpmNkpaSkYCMioeIhIKBioWEi4mIiIaIhYaMiomLgoWJiYWHjo2BhoqIiYiHiY+JhX+JjIuLh4yEioiGhouJhYeIhYJ8foeJgYOFiIR/h4SAiYJ7g4eGhIaFiIJ/hYiDhYmEhICGhH2BhH6Be4OF3IuJgoKGhYCCg4KHhoN/g4CHg4GHh4CGf4B9goOJgICAf4KCh4SBhIaCgICEgHyCe9aHgevE2uN/5n98f4Z+gX9+gX6DhYyHh4aAgX2BhoWGfn+AhoOCg4aFg4eUmImEiYGHiYWGhIuGiYiGhYmHgoKHf4GGhYWEhICChoaDioaDhIWEh3+Dg4KCg4OFhoODgomGhIODhIaAhICEhYGBhYiJhIiEhn+BhoCFi4eHhoSCg4qBhIOJhIiEg4WIiIKIh4WJh4aFi4iKhoSHhYmIioWMiYGIi4uFiYOFhYOHjImFh4yGgIWHgomLhYuMioiLhoeHi4eAin6JhIKHjYuJhoyKfYaLiYyEiIWQg4aLho+KjYmHh4mOi4uLjICLhI+LjIqJgYeIjoqNi4aJiomPjYuJhYmNiYmJjImFjo6MioqMiYyPjYqKipGKi4uOiYyLjoeJhY+HjJGIhoeNjY+NjIeNho2NkIqJiomKjpGOiYyJi5GPjHeKi4mOjIyLjZCOj4+OjpKHjY+MjY6MjY2Mk4yLjImPjIuKi42OkICLjoyMkI2OiY+PiY6Oj4yTjI6KkY6QlJGVlY2IjoqLjIiNi42Lj5KOio2IjIyOjIyPjo2Ni46Njo2OjZKKjoqQioqOiY2IjYmChY2EjI+MjJCOi4yJhoyOio+Tko2LjZCTkIigjY2PjpGOio+Mi5CRi5GMkJGLkJCPjY6NjZCLjICNkI+QkImPkJGNi4qNjJKMh4qKi4+PjY+JhoWNhpCMioyQiIeMiYuLjYyNio2Li4yLh46Mj4uKkI+MjY2MjpCRjImJipCNjIiJiIyJioyLj4mOj5CJjpKRlI2OkJCRj5CRjo6PkZKRj4aHjpCSipCMiIuPjouMjY+LjpCNk5KKioCQkIuMi4iHiYODgOnOgYWHind/j5KGipeUj5OTipCNhYSKgoqNhYmLipCNj4qQj5GUj4+IjY2PiIuOio+NjpKSkZCRjo2QjI+PjYqOjIyMjo+IiomJiIGJjI6Mho6KiIR9h4eJiImJhImIh4mLgoCIiIaLioWNkIKIi4uMh4iFiICLioyOjYuIiYqKjIyMjoqDhYaKj5GFi4iCi4iEhYR/hoSKh3t66X2AgYeFhYaBgYmGg4yDiYSJg4KDhYWCiIOJhYmHiISCh4Z8i4qJhoeJi4iEiYaDhIaJh42HhoOIgomRhIqJkImLjoiOjYqNiouIiId9hYiRi4mJh46FiYaGhoB6dnh3cXFxeHZ2ent8e3Z2dXR8eHl6cHd1d3V6eHt2dnl4dnJ2eoB3c3B3eHp2cnx2eXZ1dX17dXd1dnBvb3dybXJyeHVxdnpzeHNtdXZ1dHl1eHVwdnt0dXh0dm1zc3FwcXBxbXZzxXd2cXV2eG9ydHN2dXRwcXN6eXF0d3F1coBycHJ4c3JycnN0eXJ1dHl1cnR2cHFxaK5padGwx8FozGxqcHZucnFvcXR1dYF1dndxcG1ydnR2dnN0dXZzdHN6dnF5eXh0eXF1enR1d3p3e3h2enl8eHZ2dnh5dnl1eXR0d3d3fHl0cnN1dXN3eXVydnN2d3V0cXh2dXZ1dntydIB2eHJ0d3t4d3t4eXRvd3R1eXh2dHN1c3l2d3R8d3dydHd5fHR5end6d3V2eHt5eHR5d3p6eXJ+eHZ8e3hzdnh3eXV4fHl0eHt2cXN2cHV5dXl4dnR7dXR1eHdxenF3d3V4fXt6dXh6cXh9eH90eHt+dnV7dIN2eHV5dnZ9fnx6eoB8dnx7ent6cnR4en1+fXt9enuAfH16dHp9enx7f3t1gH58ent7eHuAf356fH55fHx8d3t4e3d8dn96fIR5eH59fXp5enl8eH59fXl6fXl7fn9+e315fX+BgXGAf3iAfnx9foCAf399fIF5e39+e3x6fn57fnp+fHd+fnx3fX6BgoB9f3x8f35/en+BeoF+gX6CeX97hIGAhYCIhoB8fnt+fHp8en18foKBenx7fXx8fHuAfoB+eHp+gHt5fYJ8fXt/fHl8fH53e3l4eIB1fX97e4B+e4B7eH1/e4J/gX99eYGFg3yVfXx7fYR+fHx7gIJ/eX98goB7f4CBgn9+fnx6f4B8fn5+fHuCfIB8eXd5eX95dXl4en1+fn10d3Z9d4B8fXt+dnZ6eHp2e3p8e3p6eX55d3t8gHt4fX19e31+fH9/dnl6e4J7eHd7eXp3e3p6fHh5e319fH19fnd9f3yAfn9+fX9+gIN8fnZ1eHyBdoF3eHd+fnp6fn19fnp4f353eYB/fnt/fHp3enV2dda9b3Z3dmdvdXVxcnyBfn9/d31/d3Z4eHhzcHJ0d39+gHx+f36Bf353eXp+eHp6d316en9/gHx+fHx9e359eHl5fHp7e3x3eXh3dnF5eXh6dH93d3RydHV4eHl5dXl3cnR7dHB3fHZ5eXN5fnR3eHt9c3p2eIB8e317fX13dXZ4enx5e3h0dHV5fHx2endwfH10d3Zxd3d8eWts1W5ydXt5dXhzdHxzcXpzenZ6dnd1d3RzfHZ6dnt2eW90eHVrfXh8dnd7eXl2eXZ0dXd2eHtzd3R5c3l9dnl2f3R4fXh7enp4enp2enZyeniAeXh6dnp2e3R2dud/AX6vfwN+f3+EfgJ/fv9//3//f/9/73+Cfv9/mX8Bftp/AgIEAICPlY+Wl5OXlJOTlZmRh5GOmJCWjYaOl5SKkZSWipKSlpaPkJSTl5GSkpqPkZGHiJGMjJaMlJSSjpCQkImMj4yPkpCGjI+QkpCKiI6MiJqQho2Ni5KMlI6Rk5SLj46Pj/iIgomGjYuLiJGJipCNjImLjJKPg42PkY+Dj5GJiYiKiICKj4uNiIiOi4yJiY2Gi42Ij4qIhomLjIWcgdvL4fX39fn/gYeLhpOKh5SHj4qOlJCTi4qHj42Ki4D9hY+RjZmbkZmJjKyTkJCWjZCPjoiLipCNj4uHjo6OkY2OjI+OkIyRiouMi4qRjo+MjZGNjIuRko6IjomIiZGDkYqIi4mQhYCIiImPj5CMkpOOmJGNkIiSjI6Mj4uQkYuPiIiPk5KNjZCPkJGSkJGPjZCPkJGVjZOSkZOSkpmSkpWSjpWSkY+OjJKJkJiXjpKMkpSXkIuRkpaSlJaQlJuUkI+Rk5GQiZGUkouTk5SUj5KNjpGRmI+Vk5iWj5CUlpOSkZuUkpaOlYCVjpeUmJWZkpSXk5WTkJqSj5OOmpSVj5aTkZGSlpOWkZeUk5OXkY+Tk5SNlZKUlZGSkpWVl5GRlJWVmZCUkZGVmZKPl5iWlZyWmJuam5ealJiRlpSUmJSXmJOXlZOdmJmanZmdmZWXmpqYlZaUmpiYmZmUkZeYlpialZWZmpqYlwGQhJmAlJeSl56bl5eWlpeXnJygnpiUlZqZmJSZmpiamZmalJuWmJycm5mXlZuWmZublZmZmJmVjo+dmZualZmWl5SXk5SXlZGUl5SUlJWYnZeTlpOTnJSYlpmYlpyXl5aWlZiZmJSRl5mbmJuanJWXm5aemJmWlpqZm5aWm56SkpebmZ2Am52TlZyUl5STlJSSmpqZlI+WmpiNmJiVlZORkpaZmJeZlZWVl5aWmZadnpaYl5KUnJiYl5iYm5qal5ual5SZlJyanZuZlpaZl5WYlpeXlZuanJigmpedmJeanZacmpqTiZqbnZSXmZuXnJmdnZyenZiZm52am56XmZqXlJWamZOAlZWXkouUi4L6lZqbnJOPi5CSk5OTmaGbk5aYkJKChp2poZmVl5mam6OTm5ybm5aUl5WUlpKRk5edl5ecnJucmJSVmpeZlZmbl5uWl5qcmpial5aalJiRkpeWl46VkJWNkJGTjpSLipCOjpSTkI6QmZGPj5uQkpWSlImPlJaPlJR7kJGUmZKRlJSVlJSWkpSUlpGRmJmajJGRj5GLkI6Mi4yGhpaDkZWVioiLjZCRkYuRjomOjJKQjZGOjpGPjI6HkJKWkY+SjI6QlpGUm5GRlY2XkpiTkpGUl46WlZiXmJmTk5yZlpWSmJuUlpeelI+Pk5aUk5mWkZuVkZWRN4WLho2Nh4yJiImLi4mAiYSMhYiBfIWNioGIiomDjIqJioWEiYeMh4iHjoaGhH6Ah4GCjIaKioiEhYCBgISDhYaDfIGEh4eGg4GEg36Nhn6Fg4GKgIiDhomGfoGFhoXgfHiDgYN+gX+GfYGEhH5/gYOIg3yFgoSFfIWJgoWChYGChoWDgYGFg4KCgYN8hIWBiIGAf3+Bg3aMetHF1Obk6OvwfH6Ae4qCfoiBhoSFioWJgIF9hYOAgnzygCaHh4SNkIaPf4GaioqIiIOGiIeBhISGg4eDf4eGhYiHh4WJh4mCh4SEgIKIhoaDg4aDg4SHioOBhYCBg4h9hoSCg4GHe4GAgoaGhoOJiIeNiYOGgIiEiIWHgomJhoeAf4eLiISGh4aGiouHhomEhoWIiY2GioiGioiHkYmJi4iEi4iJhoSDiH+Hi4uChoGHiY2Gg4eFiIiKjIaGkYiEhYWGhYeChYiJgIiGBIiFhYiEhYCLh4uGioeFh42MiYiFkYqJi4SKiYOLio6Lj4qLjYqNi4eOioiLhYyJi4aLjYiKiouLjouNioiIjYmJioiMhIuFi4uFhomKjIuGiouNi5GJioeIjIyGg4uLi4qQjIyQj5CNj4uPhoeJjJGNjI2JjYyHko2NkJKPkI6JjpGQjYyMh4CPjY6OkYyFi5CRjJCJiI6Sk5CLiY2OkJCKioiOko6OjpCPj42RkJOSj46NkIyPi42SjY+LjY+Nko2Nk4+Qj46MkYuOkJGLjZCMjYuHiZCLj42MkImIh46KiY+NhoqMiYqKio6SjIqNiIqUio2Oj42OkI6OjoyNjZCOjYmQkJGPk4CRko6MkoyTjpKQjpGPkY+PkZOHh4+SjJKOkIiLkIeLi4iMjIiPj4+Jh42Rj4KPj4qLiIiKjY6Njo2Ki4yOjIqNi5CRio2Nh4eRjY6NjY6NjY6NkY+Nio+Ok5CSj5CMi46Pjo6JjIuKjo+Qi5KPjJGMjY6RjJKQjImAjo+Qh4eJjICJjo2TkJCOko6Pj5CQjpKNjpCNiIiOjYmLi46JhIuFffSKjIuKhX59hYiJioqNko+KjY2Hh3h7i5WTkI2Oj46RlIiRkpKRjYuNi4qMiomJjJSOjZCPj5CNi46OkJGMkJGQlI+Nj5OSjZCLjpCMjYeJi4yMhYqIjYSFiIqHjIKCiICHho2JhYWJjYiFiJGHio2Ii3yGjYuHiouHiIiMiYiGiouIiIuIiYyNiIiMjI+FiYmHhYKGg4KChH6AiXiGjIuBgIGFh4eIgIiFgoaEiYaEiIOFioWAhIGHho2KiIiDiIeKiIqPh4aLhYqHj4qHh4mPhIqKjIuMjIeJkI2LioiNkROIiouQiIOGiYyJiYyMh5GKhYyIgHZ8dHt7c3p3dnp7e3hze3V5dHNtbXp8d250dXZyeHd4e3l0dnR6d3Z3f3Z1cnFwdnJweHZ4dnh0dXR1cW5xdXV2cmxxc3V1dXFzd3Nvd3lvdXVvenB6cnl6em9tcnZz0m1pcHR1cHBzdW9zd3Nra29zeXRvdXJxd3B3e3d5cnZ2ZXV2d3Nyc3d2c3R1dWx0d3F6cW5ucHBwXXBmu665wcHV0NJsaGxsfXVxe3Z1dXN2dXd0c3B5eHFzb91wd3R3fHp4fW5rf3h+enR0dHt8cXZ8d3R8dHZ8enh7fHdzfnt6dHV3dXd3hHiAdHh5dnZ3dXpyc3d0cnZ4bXl4d3R2dW90cHZ4dnp2eXl7e3h1dnB5d3l3fHR5enl3cXB3end0d3l7d3l9enZ6dnN3eHl9eX56dXZ8eH53fH15dnp3eHh0cXdxeHh7c3pydnd6d3V4d3p0eHt3doJ5d3V4eXV5dnl5eXV4dHZ0dXeAc3R1dnx2fHZ6dnd4ent5eXJ9dnl4dHt6en9+fXp+fXl6en59eX98eX52enh8eXx/eHt8fn1/fnx8dnl9e3p3eXt3fHV5fHZ1eXp9e3F6f399hH14dnt7fXRzeHd7eHt+fYB7f398en96d3t9hoB6e31+fnmEfnyDhIB+fHx+gIAqf39/eYF8enyCe3V7gIF6gXl5f4OEgX95g3uBgHx4eHyDfoF+f3yCfXt8hIGAfX98f4CAgoGDe3yBfoSBfYF/fIKAfoJ9fYGFfX2Bfnp9en6IfH15fX53dHd+e3p/fnd+e3t7d36CgoB6fHh8hnp6fn18eYJ+f356gYGBf355fH1+g4aBhH57gICDfoOAf36Bf3yCgIB0dX2AfH99fXd4fnV8e3d9e3l9en12d36AgHx1fX94e3h6e3x+eoF8enh4fXp5e3t9gHp8enV2fYCAfHt8enx9fH56ent/gH9+gX2BfXt8gH2Aenx5fHp4fHuAf3yAdnp7fXt7enx9dHt9fXh1eHd2enyDfn19fn5+g39/eYB6f3t+eniAgnZ5fIB8dnt8ddl4eXd0bWhodXiAenl5eXx9eX19d3Vua3R0fX59fnx5f4J5goB+e3x6eXp5eXh2eHqBfn2AfHx/fXp8fH5/eH1+gIF+foCAgnl+enyBe3p1eHh6eHR4enpydHl4eXt0cnZ7dXp4eHZ4fHZ1eHl6ent4eGt4fHd3eHt4d3Z7e3Vzenp0eHl7en59enkXe3h8dHh3d3V1d3NycXVwdXdrcn17b3KEdk54cXh4dXZveXt4enF2fnZwdnB2dHt2eXd3eHZ5d3t6dHV3dHh5fnl2d3V+dHt7e3p9eHN3e314ent8e3h6e39zc3h3end6gX57fnhze3jffwF+un+Ifpd/AX7/f/9//3//f9d/AX7/f/N/AgIEAICOjo2PkpSUk5ebmJOWj5GQkJOOlY+Qi5GSjZGTk4yUm5OTlpaVlpWSk46Ml5GSk5ePkIyPkJSUiY2Ki4uUjZKHk5OOmJCTiI2Pi4aQjo+TiZCKjZGUj4yNkoyNlZiOkIuMiIaKk4b/hIKEi42SjY6BhYyLjZmUjouEjI2JjYaKioCLjIeQioKIjYqJjI6JjI2MjY6MjIyLiobh6tfu9feA84b/hIuTiJCJhoyTiY+Rko+MjI6Qjo2JkIaJlZSHkYqQlJCSo6uikZGPjYySjo+MjY6Nj4iQi5CTjI2JjoqOio6Ji5GRjIaHkI2QjYqIiYaJjYqOjo6Njo2PjZGSjIaEi4CKiYmRlY+NkY+PkJGQjpSOkouNi46Oj4iPlY+Mi5KQkJGOj42OkZOTjoWIjZCRjpGRkZWPkpGTjZGRj4yVjpKQjJaPjoyWk5mSkI6Ok5CTlJCQkZGTlZOVlZCTl4WCk5OUkpeSk5WWlpWXk5KWjJSYk5mTkZSWlZOSjZCLlJiRloCTlJKTjZKVkpGTkJWTlpGUmJGSlJKWj5OVkJCSlZSTkZiXlpSWmJWWlpWXlZaYk5CPkpSWlJSSk5OUkouVk5WXmZaQlpWUmJmTnp6amZmVm5aboJiWk5WdmqKUk5adnpmYlZiZmJWcmpiZlpaXl5mXm5eXmZWSl5aYm5mXmZKZmoCXmJialZeSmZaboJiWmJmQlJqZmpiZmpiYl5yWkZeVnJmYlpaQkpiamJKYmZ2Wm5aWlZSWmpKbk5SXlZSZlpeal5WUnJmVmJialZGTk5WZmJmYkZWXlZWXlpaZmpiVl5qWmZiUl5+YmpmamZmclpyampmWn5GSkpaal5mbnZaWl4CekpaZl5SalpWWlJiVj5KXlJSamJCVlJSTlpSTl5eXkY6VmJOZlZuUkpaXl5mXkpabnJSVkZOWlp2Zl5qXl5SVkZiamZicl6CWm5ibmJSZmZWbmpualZaclJ+dm56boJ6Wl5uZmJOQmpiXkJKanpmcmpWYnJOblJabnZyYk5uZmICcnKKcmpSXlZWWlpOMiYGEjoqWmZeTmZeOif+OkZ2ZpZDfzJSNkpyXmZWYlJmanJ2ZlJ2cmJmZl5iampWblJiZm5yWo5SYm5OYmpaWl5mcmZSbj5OWl5WSlZSUkZWMkIyOjZKOjZOTkpCOlJWLjYiYj5KLkYqVjpKOkJKTjo6IloCVmJKMlZeVk5eXkpaQh4iJio2Kk5SWkJmSi4iVk4+Pj4yOko+SlouPkomTkp+WkZKVk46TlI2MlpCOkI6Mi42MkpOQi4+YlJOSkY2VkJWSj46Pko2WkpCUmJCXkpGWk5WYlpWZmpSZlZSVlZyYm5CUm5WRkpCWlJWTko6YkZeVkICGhIaGh4uLiYyNjoeNh4eFhoeEioaHgYaHhIeJioOKjYiIi4iIiIeIh4GEjIeHiY2EhIOGhIqIgIOAhIOHg4h/h4iCjYiKgYGFgn6Fg4WLf4R/gYaLhISDhYCBioeBhoSGgX+CiXzwfnx6hIOJg4V4fIOBgpCIhoN7g4R/gX2BgYCDhH2GgHuBg4KDhYV/g4SEhoWDhYOCgXzP3M/o7Ot65H/vfISKf4iDgIGJgIWEiId/gYWFg35/iICCi4p8iIGFiYeGkZaSh4OHhIOIh4aFhoeGh36IhIqLg4WEh4SHhIeBg4eHhYF/hYOGhYKAhH6AhIOHhYaGhYOGhYmKg4B9hYCGgoOJioSEhoWIhoiHg4mCiYeGh4eHhn+HiIOCgomFhoiChoWGiYyNh32Cg4aJhYWIio2DhoWIg4aIhoKNhoeDgYqFhYGKh4yGhYKEiYWHhoaHh4WGh4eKioaGjHt6ioiIiIuFiIeLi4mMiYiJhYmMh46Hh4iLjImHg4aCjI2LjoCLi4iKg4iNhoiJiIyKjYiOkouJiomNg4iLiYmJi4yMio2MjImLjIyMi42PjoqNioWGioqLi4uJiIqKiYKIh4mLi4mGi4uLjo6JlZOOjo+Kj4uPlIyQjJCTj5SLh4mSko+Qi4yOjIqSkY2PjY2Mi4+MkI+Pj4uHj46Oj5CQkIuTkoCPjo6Pi46Kjo2OkpCOjo2JipGQkI+MjpCOjJKMiJGPjo2Njo6GiY6RjoiMjpCOkYyNi4qOkYiTiIyOioiMjo6RjouKko+KjY+MjIqKjYuNkY6OioyNjYyMio2QkZCNjY+NkI+Mi46OkJCQjI2TjJSRkI+MloyLjI6PkJCQlIyNi4CQhYiLiYqPiouNjYuLiIuMiYePjoeLi4yKjI2Lio2KiYSMjIqOipGKio6NjI2LiY2Rj4mKiIqLi5GKjJSPjIqJiY6RjoyTjpSMkI+PjoeMjIqNjpGPjIuPi5KQjpGPk5CNjZCPioqGiYqLhoePkI6PjY2MjoqQi4ySko+MhouNioCQj5SPjYiMiY2Ni4mFgXd5gX+Mj4mIjo6HhvCBhJGMk4PNtIKChY+OkIqNjIyLkJOOjJWTjI6OjY2QkY2Qi5GRkZKLmImOkoePkI6LjY+TkIuRiomMjoqKjYqLh4uEiYKEhImHhYiLhoaDiYuEhYCMg4eEiYOJh4qGiIqKhYZ+jICPjoqCi42MiYyMh4mFf3+BgoGAiImMhYyFgYGNioeFhYGFioaJiYSDiIKJhI6IhIiMiISJjIeEioWFhYSEgoWCiImHhoWMi4yHh4OMhIqLhYeIh4WMiIeKjoSLiIeJh4qOjYqOj4yOioqKi4+Mj4eIkIqIi4iLiouGh4GLhYyJiIB1d3h1dnt6eXt4f3d/d3R0d3Z0eHR1cnB0c3h6fHV3eHZ1eHV6eXZ4d3V0e3R2d3xvbnJ2c3l1b3RyeXZ1c3dxdHZxe3h6dXNzc3F1b3N7cHNycnd8cnBzcXFxd3RvdnZ2c3Byem/UcXNrdHZ8dHVsbHVxc354eXltcnd2c29ycIBydHJ3cXB1d3NzdHhxc3Z0dXhyd3Zxcm2vvL/Y1NBrzGzPcHB3cHh0cHF4cHF0dnZ0dXhzdHJvdXR2eHZudnN2dnt1eXR9enB6dHB8e3h4en17fXF5d3t+dnp3eXd6dXRydHV5dnVxdnR5eXN2enJzdnV6d3t4d3V6eXt8dnZyeDV7dHh6eHV4eXh9enp6dXtwfHl2end1dnF1d29wdHl4entycnd2eHd5d3B1c3R3dXR3d351eIR3gHl1cnlxcnRxdnB0d3d1eXRydHd5cnR2d3l5dnh5eXp7dnZ7b3F8dnp3enh8dnp6e3l3eHt5eH12fnd1dHl9enhydnZ5eHl4eHl1dnZ5e3h5eXx7en16e4N9enp8fXd+e3l7e3l9fXl+fXl4fH97fHt7f397eXp2enx8fnx8e3l8gHd/cHF4fXp6eXV6e3t5enmGgXyBgHt+fX+AfYB+gYJ8g4F6fYeAfoSAfX57e4CAf396e319gX6Cf39/fHZ/fn9+gH+Ae4OBfn9/gH1+e317eX6Df399fHd+gH5/foCBfHuAgX+Eg4B/f4OBe31/gX95fYGDfoF+f315f4B4hnh9gIJ5d3uAf39+fHt/gXx6gYGAenuBfXyEgH96ent7e3x7fIKCfXx8fn2Bf318gXx9fn94fIV8hoB/e3qDfYCAfX5+fH+GfX59fXV5fHR2e3x3enp+fHh5fHh3fXt5fHt8en59e3p8d3l1eX55fXyAdXaBfH57eHd6fnx6e3p5fX6BgHt9gn15e3l6f399fIF7fnx+f35+d3h6fH1/fnx2e355f318e3p+fXh4fX94e3hyd3x5dHZ4eXl9e3p+e4B6f4OCfXt4eHl1fX2BfXp3end9fHt8dHNpaW9seHx4en1+e3rYanB3dXZx0btrcXN9fIJ6f32DgXyBfXuEgXx7e318gH5/f3l7gIB/e3mCen2Bd39+fnt8fH9/fYJ6d3t/e3l9ent1e3V5dXR0d3NzdHpydXN2d3RzcXZxdXh5dHV6eXJ5enp4eHF9fnt6c3x7enl3enp6d3NzcXJzcnh7fHV4dXFzfHhxdXZweHx3eXd3d3t0enJ4dnh3fnt2eX55dHl0JnV4d3hzdHN4d3d6dnl6fXd4dnt0e3t1dnp5c3h2dXl8d3d4eHt3hHwdfn56eXd6e3l9fX94d313dXl0e317d3hyenR6eXnmfwF+sX+GfgR/fn9+/3//f/9//3/8fwF+hn+Cfv9/3n8CAgQAb5CRkJeVj5OUl5SVkpqRk5mbm5mZlJKQk5GRlo6QkJ6dk46Wk5uZk4SSl5OOkYyQlJSPj5OPk5OVjZOPk5OOj5KTkpCUkpWPiJGQjIyJjoyPkJKTlJuMkZKRjY2QkY2KjY2HiZCUjYSEipGJlYyMjISIgIOMj42Mj5CPiIuMkJCIj5CQi4eMjIqRiY6JjIyPkomOioSNioiA7O2B/ISD+PuDiYuMhYmJi4qNio+OkZSLiZOOk5aPkJKQhYSHlJybnIuRh4iTl4yVkoePkZGLkI2NjJGMj42HjoyRkoeQhJeKjI+SkomDiJCPkIiNj46MiomOgIyJjY6Mj4+Nh4yFkIuJkY+LjpCQi4iHjYmKjo+Ok5GJjYyNkpSSk5KPkY+Qj46PjpOSkZGUkJSQh5GQjpKOj5aUk5GSj5OOjZCGjoyOjo6RlpiOk5aSl5CPkJCMlJSUkpGVl5aSk5iQlJKTlZKQipWYlY6Rj5SSjJKZkYyQlJCVgJWSl5KWlouVjJecjo6TmJORiZOXkZGVj5GQkpKVkZOWlJSUmJWUk5aNjpGRlpKVlZaZkZaVk5KUk5SUmZOUlZaVlJSZmJSUkY2SkpKZl5+al5uYl5STlpycl5SanJiZmJeXk5eSkZmamJqck5eRj5OVl5KXlZqWlpeZm5OempicgJeZm6Cblo6XlpSVnJaYmJOamJacnpeRkp2ZmJeempuYm5mamJybnJWenpiYlJaYlJOTlZaZmpibmZuVmJSYmZiWkZWbnJubl5qcl5WVjpaSlpKTlpaYm5WUlpibmJeWk5WVlZeamIuUmZuVl5SXmJKUlpmXmJOXmpaWlpeWl5yXWpeamJmampiZl56cmJqblZOWl5OZlJCXlJWVlpmSkpSUl5OTlpSWkpSVmZaSlpaZlJOXlZqclpWUlZmYl5aUk5WZlZmQk5iZm5GVlZmRlJiYlpydmpmam5WVlYSZgJudnZyZoJaak5efnJuan5mdnZGanZqZmZaRk5OWlpigkp6boKCekZqWpZqcnpyfnpebnpeYl5qbkJmakpSSjZqXlpmTkZiRlJOGho+Bj5CZlpSfmpyhnJuVmJSsqJGblI+dmpeXnJ6cnpqbmJyZnZ+ZmZqam5qWlZWVm5eZlZiagJWWmZaVmZqUkY6Vmo2UlJaMj4+PkoyJmJCNkpGTi4mUk4qRk4+LkYeWl56ckJGVlJOTkpmakpeUlpaWhYaBiJCHjYmMlIiVk5aPio6OjJKdkZGNj5GSkZCOiouKlZWWkpKbkYaQiZKMkoyRj46Vjo+TkY2NkpGNkY+PlJOQi5KRL5KTj5KTlpWUkJqalZiZl42VmJuZk5ObkpSVkpiYlZmNkI+VlpiVlpSNmJSVkJGMgIeEhYyJhIiGjIiKiI6JiY+PjoyRiIaGiIaGi4SHhpONiYKOh42LiHyIiYeEioKEiIiEhIiEiIeLgYmGiIqEiIqJi4SKiIyGgIeEhIZ/goKFhYeGh417g4eEgoOFhYSAhoaAf4aLhH5+goiBjIODhYF/gH51gYaEhYSLh4CBgoWGgH+Fh4SCgIKDg4iChICEgoeGfoWDfoeAgHzm5H7vfXzr7n2AgoJ8gICEgYSAhIWJjIGAh4WIi4SFi4Z9gYCKjo2Rf4J+f4aLhIqLfoOGiIWKhoSFjIeHiIOHh4mKgIl+jYGGhIiJgXqCh4eKgoWGg4SFhIiDf4OFg4WEhICEf4eFgISJiYSGh4iCgoKDhYSGhYaMi4GGhIWIi4iIhoWJh4mHhoWDhoiIh4uGiIZ9iIaFioWGjYqJiIqHiYWAhX2DgYSFhoeNjYSJiYiKhIOFhYCIjIqGh4iLi4mKjYaIh4iLhoSBjYuKgYaDiYmCh4+IgoWIhYiIh4mKi42Gi4ONlIWEgIeKhIh/h42Hh4qEioaHiYuLi42Ki4iNioqMjYSHhoeMh4yJjI+Hi4uJiouJiomOiIuMjY2KiJKPioqJhYeJh4uJkouKkI6NiYqMkY6Ri46SjI6NjY6MjIiHjZCQkJKJjoqGh4yPi46Mjo2Nj4+QiZGQkZaNj5OVjouGjo2LjJKNgIyNiI6NjZOTj4iDkY+OjpOQkY2PkJCPlJCPhY6QjZGJio+IjIuJjZCQj5CPkIyPiIyMj4yIjZKOjpKMjpGKi4qEiYiNi4mMi4uOioiKi5GOkI2Kj4+LjI6Ngo+QkYuLjJCPi4yOkI6Qi4+Rj42Ni46Pk42PkZCOkI+OjoqPko6RgI6MioyLiJGLh46NjouNjoiJiYiOiYqMio2HiI6Qi4mLjpCKiY2MkJKOjYuMjYqNjYuLi5CKkYeOj4+SiIqMkImMjoyLkZOQjo+OjYyLj46QjpCTkY+Ml42RiYySko6Nko+QkIiOk46OjYuEiYuHiYuUiJOSkpGPg42KlouQkY+QgJKLjpGNjoqMjYOOkYqMiYONjYuPiYmOh4qLfn6Ad4KCjImJlJGTlZGRjJCNq5iEjouIko2NjJGTkpSPkI6SjpKUjo6RkZSQiouMjpCPk4yNkI2PkYuMkZGLh4KIkYaLi42DhYmGiYaBi4SCioaJgYCJioSJiIWEiHyPj5ORhomLgIyJiIiNj4qMjIyLh3qAeoCIfoSAhIqAioePhoOChIOHkIeIgoWHhoaEg3+BgY2JjIeHj4R8hoOJg4mEiIWEiYaHi4eGgoqIhYeFhYuHhICIiIiLh4mJjomKhoqOiouOjoSLjJGQjYqPh4uNio6Nh4qDiIiLi4yKjoyDi4iJhIWDWXh3dXt1cXV2e3Z5d3p5d36Bend8eXR3eHBzfHR4c314e3V+dn56em56eXZvfXNyc3VydXZxeHZ6bnh3dHd2eXd0fHN5eXp0cHNydXpyb3B2dXhzdXZrcHZxhHKAdG90d3Nvdnx6dXJyeXN7dXZ2dnFwcWhxeXd5dH17dnNzendzeHV0dnR0dXB3dHZyeHN6eHF3dnB2b3Jz0NBx1nFs1N5vcXFwbG5zdXN3c3R4fHxxb3R3eHp2dnt5bnJxeXx0fnJua21td3h6enN0dHp7fHl3eYF9eHl5fHp8fHWAe3B7cnh1enhxbHZ8e394d3tzdnp1e3dzcnd1enZ0dHdzeHl1eXx2enl8dnZ2cnh1eXx4fn11e3R0dXl4d3V1dnZ6eHd1b3R3dHR7eXl3b3Z2dXt1dHx7end7d3l3cHhzdXJ6dnd4fHt2eXd8fHpyc3ZwfHp4dXZ0e3x8fX96f3qAe3p3dHN6enp0d3N5eHd4fHhzcndzdnZ2eXp5fnF3dXl6cXF4enR3dXt+e3l7eHt3eHh8fXx9fH15fnp7fnx3enh3fHZ9eHiDd318eXx/enp5f3d7eH2BeXeBfn16fHh3ent9eoWBeH56fH6AfH98gX98g3x8foKBfn17eHh9gX82f3x9e3h2fHx6gH6CfnmAf4R7fn9+hn6BfoJ8fHl/fnd7g4GBf3l8fXx8gH93cn2Af36Cf35+hIFIhX58bXt/f4Z8f4F9fX15foF+f39/gH6Ae3p9f3t6f4J8goN9foJ7fHl1fHh8f4B8e3l+dnx+foB+f35/gX98enl8dnyAgn16hH6AfHx7fYN7fICBe3l7fYGCfX+AgH97fnx9fH6Afn97fXqBf3d/dHN+fH54fHp1dXZ6fHh5eXh7dXd7enh5eoCBfHh+fYB+fX18fIF6fn16fH19eIB3f356fnV2fIB6foB8eoCBgHqBgH18e3t6g4CAgYB8eoV5gXl5fX55e357f3uAd32CeXt9end6d3F1en54gIV/fH51fX2EfHx+e35+d3uCfHp6fHtze4B5fHl1en54gXl5gXx8fHFwb2ZsbXh6eoKAgIGAgHt+gauIcnx4eIF6fn2CgoGBfH5/gX6AgXl6gH6Afnl7fH18f4B8fn56e3x7en19dnZ5goBxenZ+dHSAeXZ3d3B6d3N5dHdycHl7dXh6dXN3bX59gH90fHl6dXV4e397d3p7enFrc3F4enB1dHd3cnp2e3d6dXV0d3p2d3Z3d3N4cnJxcXF2d3l0dXhvbHZ0enR4dXp3c3N0dXp3d3N6dHN5d3N4eHdzeXp3eXB1eHt0dnZ0fnl4fIB1eXcdfYF9eX91eX15fXlzeHR3dX17fXl7e3N4d3d1dnb/f5p/CH5+f35/f35+/3//f/9//3//f/9/5X8CAgQAgJWTjJGZlZeTj5ePl5qQkpOQjJKWlZeamZCMkZeekJOVk46amJWUn5SSjYWWipWZkJKXkpSLkYmTjI+LlJWQi46SlJWUmZOSj4+Qko+MgpePj5GSj42NkIuKiZGUkpKHiomKj4qSj4iCnI+LiIiVjo6DioKCjpGMj4uLhIaOhoqRgJOMioiFiIuPjZCNj4yOiImVk4+Nj4uOhoOHioiDiYWBjIr8hImPi4qNkJCSi4yQjJSJkI6NkJSajpeUkZaQmo6GhpGXlZCUkoyOkI6MkpCOjJKKjoyOjpKOiYmNio+JhZCDjo2LjImKh42LkZCOkouRgYyIhIyOjoyNiImFi42JgIuNkY2MiJCNk5GUh4qPj5GPkJCSiYyKjpSTlI+RlZGPjI6MkoyTlY2UkZKLj5OPlpWTlJSbk46RkZOPj46RjI2RkI+OjY2Sk5GNkZWRlZONk5STlpiTlZWVlpWXiY6SjJaTkJKTkZGMkJeUkJSYlY+Zk5qUmJKXk5WUkZeVk5KRgJyWj5GQl5mWjZSXlpeWlpeXj4+QkpOUlZaSkZSTkpSZl5eYkJeYmZWUlpSXk5SVk5SQlJGUl5SUlJiSjpCPl5WYopiXlpOVkpeVlZaZmZaWm5mZlZqbl5mVmZWXl52UlpqZmJyamJialZaVlZaZmJuYmZqWnZqZmJqblpqYmZmYgJuXmJeVlpmYlpqUmZKPlpmYnJuXk5WZk5yVkZadlZeamZKdmpqdnpmbl5eclZSamJmbk5GQlJmWl5Wam52XlpqXl5mWlJaTl5ibmpiTkJiVmZCWlpKTkJaYl5SWmJOVl5OWkJOWlpOQlo+YjZiWkJaZl5eWk5OYnZeam6Cbm4uXgJuam5uamJiXkZCYkJOVlZOVlJSTl5KZk5SYlpaUkpOQkpWUkpOVlYWDmJmWkZeQl5eUl5iblJOWl5eYmJeVmZKUmZaXlpaXl52Sk5iXmpSZm5mXmZqbl5qWn5ialZmalJeUoZeWmJahkJKPlY6LjI6okqGjnJmfnZqXnJyanZ6bgKKel5qalZiYnJ2bmZOZmJmYkZOWnJyYlZePlJOWk5ibmZGZk5GYn5qYlZiTkpKgmp+Zk6CWnZ2eoJ6dkpSZmpiWo46HjpqXmJ2YmJaOlpWalJWSlpeXl5aPk46OlZOTkZOOjY2Uk5WWjZCTkouQjJKGkpOKlo+OjpSVlJKYjJiYapaSlpaXk5SVjIeMlo2Ph46NioqLiJOVkpSSkIyNjY2Iio2Rj5ORk5aPkI6Wjo6SlpWPlZqNjYuUj46Ni46Pj42OlpCGmI+QmJaWk5OUkoqMlZiXk46QlJOWl4mSlJeQlJGWk5mZl5WXmJmEkxKVlZ+XmJSWj5eOjouOk5GVkpSAioeCiI+LjYmGiYKLjIaMioaCh4mIiYyMhYKIjI+Fh4aHg42Mh4iQh4eEfYuCjJCJhIqHiIGGgYeCh4GGi4eDh4mKiIiOiYSEhIaKiYN3j4WEiYiGg4KEgoB/iYl9h3yAgX6HgoiEgXuMgoJ/fYuDhHyDeXuEhYOFgYN6fIN8gYeAiYWDgn6ChYaGiIaFg4V+gYiHh4WHg4aAfIGBgn6Ef3uCgvN+gYaEgYaIhoh+gIeEioGJhIOFiIyFi4uKioWOhH99hI6Kg4mJg4eIg4OIiIWCh4OGhoiHioeEg4WEhYF/h32JhoOHgIJ+hIWJh4aIgYh6hoF8hIaEhIaDhH+Eh4SAhYaKiIiCiYaKhoiBhImEiIeHh4qChYSHi4qJh4iLiYeEg4KGgIuKgIqIioOGi4SJiYmLi5KKhYeIi4SDg4aChImIhoWCgYWJh4GGh4eGiISHh4iLi4aIiouJi45+g4mDi4iHiImFh4KJiomGiI2IhI2HjoqNiIyJiImFi4iKiIWAkIqGiIaLjYyIkIuMjoqLjY6JiImJiIqKjoqKjY2JipGOjJCHioqNi4qMiYyLioyKi4aLiIiKiomLjoiGh4ePjI2bjoqMiIqHjYuJioyLiouSjo2Ljo6Mi4yPjI+Ol4qNkI+OkZGQjJCMjI2Pi46QkpCPkI+UkI2Nj4+KkY6Sj42Aj4qLjYqMkZCNjoePioeMj5KQjo6MipGLj46JjZONj5GRiZOQjo6Tjo+MjZCJipGNjpCIi4iNko2KipKRlIyMj5CMjomIiYqPjJGOj4eHi4mPiIyMiYqGio2OioqLiIuPio2IjI2Ni4mOhZODjouGjZCNj42MiY2Rj5OPlJCTgo1Ck4+Sj4+OjI6JiY6IiIyMjIuKioqLh46Hi46PjYqIi4iJjIqHio2PfXiLjo6GjIWOjYqMjo+LjI2KjIyOjoyPiYuShI6AjY+QiIiQi42LkZOOjY6OkYyMjZOOkImNkIeKiJSNi46KjYmHgoyFhIN+kYaVlpCMko2MiY6Mio6Rj5KPiI2PiomLj5GOjouPjI2Mh4mLjo+MiI6Ei4uMipGQj4mNiIONk46Lio2LiomTjZGNi5GNk5SSlJSTiYqPkIuKlIODhY6Ajo2SkJGPho+MkIyLiYiKjIuNh4mAgo2LiomJhYOFjYyLjYKGioqFh4OJf4eMgoqFhoaLjYuKjIKNjI2IjIyNhomIgn2Ah4aJgIaGhIKEgouLhYiJhoSChISBgYSHhomKh4qGhYCNhYSHiouFiouBhoGKh4SIhISGhIODjoiBjYQ6iY2Li4eJiYqAgYuNjomFhoeHjYyAiIyKiImHi4mOjo2MjIuNh4mIiYiHjomNiIyFi3+GgoeIiYqIi4B7cnJ4e3t7eHZ6dHp2c316dnR2dnh1fXt1dHl9fXZ3eHdxe3l3dntwdHhuenJ4gntyd3d0bnNyeHB5dm9+eXN2d3J1dIB5dHJwdXp6dWl9c3V7dXRycXJybW97eHF4bW10b3R0eHh1bXtxdXRseHJ0c3ZvcXN5d3Zwdm1xdm9zeIB4dXR2cXN3d3Z0enh0eXFyd3R3eXZ0dXJvd3V3c3RzcXVy3XRwc3R0cnR1eHFxd3l4cHp1d3h5eXV7fnd6dnd0dHRzenhudnp1eHV2d4B6dHV6d3l4dnZ6e3l3eHp4dnB3dXt4d3p0eHV3eHt3fHd1eHB4dXN4enZ2dXd4dHh4eIB6d3x8fHd8d3t2eXZ3fXl5d3l4e3R3eHt+enp6eHt3dXNzcXZwenlyeHh4c3V7cXV3eHt4gHR2eHd6dHl3c3B3eHd3eXZxdHd3cXR0d3V2dXZ4e3p3dnd3fHt9hHJ4e3R+enp7d3J6c3p7enx4fXZ3gHd7enp2enl2eHV6e3d4doCCenV5eXp5enyEfnl7d3Z8fXt5eXt4fHl+eXyBhHp+gHt6gHZ6eX1+en56fnx9fXp9dHt8eHl8eXt+fHh4eX17e5SDe356eXd8e3t/fHt8fIV8fX19fn17fn9+fHyMgH6BgX+CgIB7f3x8fn96foOEgoGAgYqBenyBgHmBgX+AgEOBfXl8en2Bg36AeX5+eIB/hX+AgX19gXt9gXp6h4KFfoF6hIB+fYV+f36BgXx+goB9f3uBe4KEgX57gYCBfn1+hH6AhHuAfHh+fX92dXx5f3p9f3qBeHV7fHZ6fXt5f3qAe319fnl4fHiDdnt7foF/fn18fHp8gICCfoF/gnh9g3uCf4B7fn96en93dXl4eHh6fHh3dnpzeXt9e3h3fHh6e3l3d32CbWd7fXx6fXR7e3d4fHt6f313e35+fH1/e3yGgn1+f36AgYB2fH97enmAg3x4fHh7enp6f3t/enp9c3Z3hIB4fnp7dnNvfXVycmVvc32BfHqAf3x6enp4e318gXx5fHt1dnl+fXh+f319enh1eXt+gH56fHF7e357gX6BfHt2dH1+enl4e31+eXx3e3x9d3mAg32BgoJ6eoCAe3p6bXVwen0xe4F/f316fXp7f3p6eHp3e3p3eXJ1fHh3eHZzcnV9enp5dHd7enZ5cnlzc3xyeXN1dIR7gHlzfXl8dnt9fXR5dnFwcHN4enZ6d3p2eXV6e3Z5eHZ6d3RydHJ4eHd4end5dHVwfXNydnl8eXZzbndze3Z2eHRzenV0dH98dXx0e3p2eHN2dXl1c3h3fnVydnZxfHlzfH98eXl2e3t4eXl5enp5dnZ4eXp2eHR6c3dzdW54c3V1BHh4e3b/f6N/AX7/f/9//3//f/9//3/jfwICBACAkJCQk5SSkouVjJaZj4+alJKYkZaQlJaZlo+XlpGSlJeSlZCVjJCMjJOUkZuflpSSkZWRlo2XmY2Mk5Wak5STk5KOko6OjpSRjIeJk5SMiZOLk46Rio6UlIuRj46VlJCSko+Qk4+PjoqYkYeKg4SNjJOTiJOOjomGjI+IiYyNj4eAjIeNjIiQiIqMiYONl5OHkpCOipCOjImNh4WNgIaMkYWGjIKQko6BjoqQjIyKj5OZkI+Ui4+PkJGNk5KSjIeQl5OFiImOjIuFj4+OjYeQjpSRjIuRjY+OkYmNjImMkY+OjIqIh4KLg4WHiYuNlImMjZKPhYiFiIyMmIqKhYyOi42Ai4yOjYyJi4+OkJGQjYyRjYyNk46MiYuLjZSSjYePkoyPkIiTloyXj42Nj5KQj5GRkY6KjZCRjo+TkZCWlpCMi5WMkJOWkJSRlpGLjpSPj5OclpOUkJaZl52WkpSSio+Rj5GPkJOVlZKTkpGVjpCUkJGVmpWPk5iTl5OVkJaQmJOAnZWSl5GUl5aQlJKUlJGZlJqRnZeN75Wak5CZmJGVlZKbkJeWlJOXjJORmJqYk5aalZiRmZOXlpOGkJKUkpOXlZOTlZmVlJCWmZuVmpebmpqdl5aamZuZmJeSmZablpSVmJaVmJSWkZeZmJmTlpiamZKbl5mYmZqVlpmWlpaUnJ2AnZeZnJeXlJqOlpWWmpeZmZKUmZaSl5qfmpqbmpiUlpaZlpiblJKZk5eWmZqXl5SUl5uXmJSUmJeamJqZm5uYnZuYmY+XnZaXk5aamJaWk5aUkpibl5ORmpeUmZCVmJKblZiXl5iYl5WTlZWUmJmUlZyVlpSMlJqZnJqUlZWTmpmAnpiTm5ePj5GTk5WUl5iYlJiTmJqam5aYlpeWmJmPk5aTk5iZk5OYlpGWk5aZlJmSlZaclpWXmZyYmZuWmJWWmJiUlZKRm5aVnJeXnJ6Zk5WWmp+Ul5ial56ZlJeYmJWdnJicl5aamJWUn5WYlJONhaGRoZecppuYmZ2amJ6SnpmAmZygmZ2YnJycnZWZlJmeoJOZlpGTnpaXlZiclpeXmpaVlJKWk5WZlZaUl5aRhZCYopeTjJSXoZeYlpyamJWUnJWblJ2cmZeXmpWalZWalpSVm5+alpOUlZiWl42WkZCUj5WRkpKPkJeYl5KXkpSPlJCTi5ecmJaUlI6WkJOZl5aAmpGYkpWTjJeKj5WRjIqFg4uNkpCJj5aXlZOPjJKTj5KNkZiSjZCTkYmMlYqWk5KRjY6Nj5WXi5CSjo6OkY2LlI+Sj46Sj5CRk5OPkpOUkJmblZCRlZKUmJCUlpaTkpWSk5OUlpOVkpWSkpSUkJCVnZeRkZCTlZiXlYuMk5GWko6AhomHiouGioKLgomMhIaNiYmMiI+FiIyMioWPjYeKiY2Hi4OHg4eDgYeGhY2SjImIhouGi4KLi4B/hoqNiIuLiIaFiIOEhoeDf3t/iYmBe4WCiIKFgIaIiIGHg4KHiYSChYWGh4WDhH+OiYCBenuCgIaGfoaDhIF/hIp/f4KEh32Ag3+Fg4OHfoGEgXqGioh8iIiDgIaHh4KGgH6Ge3+ChHx/gXaFhoV6hYKFhIJ/h4qQiIWJgISDhYWCiYeIgX+HjIl7gYGFgoN9iIaGg4CHh4uGgoWIh4mFiISEhYKHiIaGhISDgn2GfX+AgIaDjICDh4uDfYF/gYWEjICEgISIhoaAhIeJiISCgYeIh4iHhoWHhYOCi4eDgYWDhIuGg4GHiIaIiYSKi4KMhYSFhoqIh4uGh4OBhoiHhISFhoSJioODgoqEi4qKhYqFioaEh4uDhYmQi4iJhomPipKKh4mHfoaHhYeFh4qMi4aIhYaNhYaHhYeJjIqFiYyIjIiHhoiGjoeAkIyKj4eJjYuIjYqJjYiOi42JkIyD34iMh4mNjYiLioiRhYuNioeLgYiGjIyMiIyPi46GjYuPj42BhImMioiMjImJi46OioeOkJGKkIyQjo+SjYqLjZCNi4yIkI2PioiLjI6MjIyNipORjpCKioyLjIqUko6Ni46MjI2Pjo6LkZGAlI6NkIuMipGCjYqIkY2Oj4iKjoyJjo+Wjo2QjZCLjYyOioyRiYWNh4uMi46KjIuNi46NkI2MjYyRjo+Qk5COk5GPkIaNkouOh4yRjIqJiIuLi4+PiYmHj4uLjYWMi4aRi4yLi5CPjoyNjIaLkI6LipGOjo2EjJGPlJWMjo6KkZGAkY6Ok4+Hh4iJi42LiYyOiY2Ei5CPj4mMjY6OkI+IiIiKi4+Qio2Oi4eMio2Oh5CJjo6SioyNjpCOjIuNjoyNjoyLjIaHkouOko+OkZKOio6NkJOJjI+NjJGLioqLi4qRkIuPi42RjomKkYiKioaEeY+FkIuQlIuOio+KiJKGkI6Aj4yUjZCOkI+PkYqPjI+SkoqOioWIkYqPiYuRjI2Nko2Li4iLjIuOi4qJjI6JfIKJjomIgoqOk4yMjZGRjYuIkIuNiZKTjYuLj4uRjoyQjouMj5SPi4mMjZCMjIWNh4eJh42IiYiGiI2LjYeMh4uFioiJgo6RjY2Ki4aMhouOj42AkoePiIuJgIp+iImFhIJ/f4aGiIV/iIyLi4mFg4mKhIiFh42KgYiJhn+Ci4GMioqIg4WFhImMgIeIhoSDiYSBioOHhoSJhoeJh4eDiYmJho2Oi4OEjYmKi4mNjYmHhYiJjYiKiYiPiouGh4mIhIaJj46Gh4WGiI2Qj4WFh4eLiYOAend3e3x3enR5dXd7c3l6eHh5eHt2cnx6dnSAfHZ3d3x0enV5c3d2b3Zzc3t7eXh3c3l2em56eHJtdnp2eXl6dnNxdXFzdXd0cW1xe3hwa3BudW13b3h6dXJ4cnF4dW5tcnl2cnFycmt5dXBybW1wcHR1b3N0dnR0d3lzdHV2enGAdW5zdXN2a3Fycm12d3dseHd4dXh3eHNycXJ6bW9ucm9ub2R1cG9sdnN1dHZxeHp7eHV6dXRzdHVze3t2c3R3fXRqcnd4cXFudXV4dHN3fH53cXl5d3d6enh2dnh5ent4d3Z1dHJ7c3V1dXl1gHR1e350cnZ0dnh1f3J0dXZ8eHiAdnp7fHd0cXd5eHh4d3Z6dXR1fnl1dXhzeHp2dHR5eXl1eXl6eXV6c3l3dHZ1eHt1dHRweXp3c3Vzd3d2enJ1enl1fHt4enh2enh2eH54dHh+e3Z2dHd8e4J4dXl3b3Z2c3d5fH57fXh7cnR+eHh3enp4eXh0eHh3enZ1dnZ2fXaAf3t5fnt/fnp3e313fXl6eXh3fHp0zXh8eXt9e3p9e3mBdnt8eHl4dHp5fX55fHx/fYB0f4OCgX11c3t7enh5fXl6e31+fHh8foB5f32BfX2FfXt+f4R/enx6gYCBfHt8gYR7fn6CeX9+fYR/fX19fICIhHyBeXaAf3x/foF7g4KAhn9+gX1+gIJ2gH54gH+Agnx+fH58gYCFeH1+f399fnx+fX2Ee3iAe35+e4B/gYB/fH59f3p+fX2DgYCAgH+Ag4CAgXt9fn+AeX2Af3p7e3Z8en96d3x5gH56f3qAe3eBfHp5foJ/f32Ae3d3fX56eYB6e313eX1+foZ/fYF9gIKAgHt6gnx2d3p5fX56dXt8e311eX9+e3l5fXl8fHt6eHZ6eX5+e35+enl+e31+dYF9gH+Cd319fH18fH6Ce3x8fYB6f316gXx+hX+Af39/eoF9gYJ4e4N9foF9eXh8dnmAe3V/e3h+fXh7fHp7fXVwZHBvd3h7end9ent2dnt0gXiAfXyBfXp5fHp9gHt8fH6BfXp7e3J5fXeAd3eAfX58gX15e3l8e3x/enh3fYF7cXBvb3V2cXp8fnl6foB/e3t2fHl6dX9/e3V2fHh+fXt/fnx7foF9end7eH57eXd4dnd4dXl2dnN0d3p1fnV8d3l0dnd4c32Benl2eXV7dHl4fHxbgXmBe3d1bnNreXl0dnVzc3Z5dnZweHp7fXh2dHl5cnl4d3p6cndzdHNzdXB5eHt3dHV1dHV6cHd5e3Rxd3h1eHV1eHV8eXZ9eXhydnh4dnx4e3Rxd3d7ent/fIR3IXl8eXt7dnp3d3V0dnRydXl8enZ4c3V3eH97dnV3eXh1cf9//3//f5h/AX7/f/9//3//f+5/AgIEAICMl5ObmJSQlJeXlJmTkZiXmJeamJack5abm5GUmpmXk5WWl5iJi4+PjY6RjIuNipSPjpSRkJmSkZCSkJaQmJKblpGRkZSTlJCPjY2OkZCQhI+SkImOjYuSlpWRjI+Oj42RjIuHh5aEkKGUhYWMiYyIhoqB/4eJjIeMhomGjYaQhoCHipCQiIqMko2OkJKRi4iMioyMi5eRioaGh4qNho6O/oLrgImAi4iAiY2RhIqQkI6Pjo6PkpCVmoyYk4mPk4KFjJKQkISEiImHipiTjo2Ok5SLipGLjIqPjI+IjomKio2HiYuJiomMiYqQhYuSiIqLjYmKi4uIkYGEh4qKjIyLjYCNjZGPko+QlY6PiouOkZCQjYyOi4yPjpGRkY6Qi5WRlpGRho6Sj5ORj5KNjomPkZaWko2NjY6Sj46TkZeWlZKMlY+Uk5eSlZOTjouRloqUj5KUlJP+kJWZkZCUlo6SkZKYlp2TlJKRl5WOmZKKlZSXl5OUmZGXlZiWkpiKk5CUniyalpGQlJCOlJaRmZGJj5OQmZiXk5SXmZGSkZWSlpCUl5SYnZWUlZiVmpyQlYSWD5eTkZqYlJOSjpSbkZOWlISWgJSH+4yQkpaXlpWXlZ2UlJeTlZaWlp+Tl5aWl5iZkpqYmJqdmZiWmZ2XnJqZmJmdnJuWlZeXnZabnJqak5SWl5qVlZiXlZuYlpaZmpqZl5WXmpiYmpqZnJyWm5mVk5iXm5iZl5GSlZ2Zlp2WnJGSlpWNlZaZkJualJealpObl5eXCpiXl5iYlJOblJOEl4CUlpSTjJOXmJeamZSUlJKYlpuSmpiTlZydnpeal5mZmpKXlJiVl5uXmJuRl5acl5eYk5WVl5aUlpWRlZCamJOYlJeTlpSamJqalZ6ek5mSlJOUl5GPlZKSmpuYnJSTlZiSkpKXl5SRmJqbmpqUmJeXl5iZjpKUl5yZmJmWl5aWmYCelpuZmZebm5SUm5qTnZqXmpCZn5+cmJWVlZuWmpSLl5aVkZCQpZ2hnpuenpSYmKCcmJWYl5qeoJmTmpuYmZSbn5eVlZyVlpmVmJWUkpSTjpSSl5aZlJOXlZyUkY+XrJyOj42SjZaViY+Wnpaam5ydmZmYmpyamKGNkJuckpqZlQ2YlZiZnJWanJmclJachJSAmZSalJqTjJKTk5GVk5CQlpKSkpWRjZSPio+VmJeUjpKUlaGaiYWMmo2KlouQjoqRkI6MkpWWkI+Qj4+Ui46PkZORlJOOlpOWlZWWkZOZio+bnZCPmpOJjY+RkJSLjo6QlZGOlJGTlpSXj5CUlI+Tlo6Pk5CQkJKQkZWRk4+Wk5cejpiTj5SRlJSSlpSNlZCRi4+UkY+QlZeUjJaPlJKMgIiOiI2NiYWJi42Ii4eIjY2Mi4yNi4+Iio2OhYuPj4yIioaJiICChYWEhYiDgoJ/ioaBhYOFj4aHh4eEioOHhpGMiIaHh4iHg4OCgYOGhYZ7hoiGfYKDgIiKhoJ9g4ODgYaAgXx9i3yIlYl9e4GBg3x6gXnwgoKEgYZ+gnyGf4aAgICBiIqAf4aMhoOFiIeAe4SChYWDjIWDf3+Bg4eAg3/uedt4f3mAf3d/god+gIWGg4SFgYiKiImMfouGgISIfH2EiIiGgHt/gX6CjIqFhYmKioKBhISGgoaHh4CGgoaEiYKFhYGDgIOAhId9gomCg3+Ef4GDgYCHenyAhYSGhYWHgIeHiYiKiIiMh4aDhoiKh4WGg4aFhoqIiIqKhYaEjImMh4d8g4iGjYeEhoWIgYiIi4qIhIWEhYyFhYiHjIuNiYKLhIqNjYmKh4mFf4aLf4mGioiKiOiDio6IioyMhYiGhoyLlImIiIiLiISOiYGMjY2LiYqOhY2KjIuHjoCKhoqQgI+MiYiKiIWKiISQhn+FiYaPj4yLjIuMh4iIiYqNiIaLio2RjIeHi4mLkIeLjI2NjI+JiZCPjIiKho+TiIuMio+MjYqJeeOGiImMjIyHi4uRi4yMho2Mi4yShouMi4yMjoiPj5CPk5GOi5CSjI+PjIuPkZCOjIuNjJGLjpKQkYmKgI2PkYqQj46Jjo+LjZGTkpKPjIuOiY+TkYuRk4uRkI6Mjo6QjY6NiIeMlI6IkIqSiYqMi4aMjI6FkJCLjY+NiZGLjYyNi4yMj4qLj4qKio6Oj46QjIqDiY6NjpGOi4yKh4+Lk4qOi4iNk5KVj5CTkI6Nio6MjYyNj5GSkoqPjJKOgI2Oio2MjYyLjYqFjYeQj4mOjIuHiYyOjpOPipCSh4qIiouOjoeHjIiIjZCNj4mIipCHh4iNi4mIjpGPj5CNjo2NjY6OhYiLjpCPjo2Nj5GQj5OMjYuPj4+QiomRjoaRjYyMiI+Uko+Lh4qMjoeKiYCGiImEgX+Pj5KRj46OiIuMgJGMiYiMjoqQkIyHjouOkIuMkIyLiZKLi4+NkI2Lh4qKhIuDi4qNiomNjJGMiYeKmYyDhYWKhouLg4aNk4yPj5KSjo+NkZOPkJN/iZCRiZGQi46Mj4ySjJGTj5GIipKNiYqLjYmPjZGGgomLiIiLioeHiomLiYuIgoyHgoaKjo2MgIWKjIyVin99g46CgoyDh4aEiYiEhYmKjIiFhoKCiYSHiYaJh4iIhYmJjIyKiYWIjIGDj5CGhIyHf4iFhYaKgYeFiYuKhIqGiI2NjoWHiomEh4uDhYyHiImIiIuKhImGjYqMg4+KhYuJioqIiYmGjYmLgYaIh4WHjY2KgYyHiIiFgHd5dnh8eXR3enp1eXV2eX98dnZ4d394e3l4cnp7e3p2d3J2fHNzd3J1cXV2dHFvenZybmxye3R0dnV0f3RydIF6e3V1c3d8dHFwcnVzc3ZueHd1bHJycHV3cm1tcnJxcnVycm1ue3F4f3pubHJ0eG1qcWrZeHR6d3pxdnB5dHpxgHZ0enl1b3aCdnV2dXh1bnd1d3d1dXBzcHF0dndxdWvSaMRob2tubmttbXVzdHl0cnFycnx6dXR4cHpycHZ3bXB3eHhzdG5xdG9xdnl1d3t4eHJyeHV1dXl6e3N3d3p5fXd5dXB1dHRwd3lzdnl0eHN6d3Z3dXZ3bnF0dHZ4eXh2gHt8fXl7eXp8end1eHh5eHR2c3V4d3t4fHp5eHZ2f3x+eXhvdHh1enh4d3Z5dHh2d3l5dHV2dHZ1eHZ5enh8eHZ6dXl+fnd4dHh3cHR6cnp1fHl7et54e313eH58dXh3dXl3gXx7c3p4dnN+eXN+foF+eXp8dn94eHh2e3N7e3uDgH19enV6dXV8eHF/c3F0eHd/eXp8e3V4eHl6fHt+dHd4e3+Ce3h2eHl8gnh5en97ent7e36CfXl+e4GCenl6e4F9fX56aMB8enh8fYB5f32Cfnx/d319fnqAd3p7fX58f3l7f4N8gYB7eH+BfH2BfX+BfX17f3t/fYB7gYSBgn18gHt+gX5/foB7eIJ7foGChId+enp9e3+AgX6FhHp9f4F8gH1/f3+AeXl+gX50gX2De3x9enh8fH14f4B8foF+foF9fn18en1+gH19f3x5eXx7fX1/eXt6foB8fYF9enl6en98gHd5eXd8f4CBf36Ef3t6eH5+e3l6f3yCg3p+e396gHd9eXx7e3t2enl3fHR+gHh8fXh3d315fYJ5e3t+d3h6enh8fXd4fnp9fX59fnt5e4N3dnp+fHp5fYGBfoJ+f39+f4B/dniBgoKAfn57gYJ8fYR7eXqAfH2Aenh+fnN9fnt2dX2AgIB8dnZ8eHh8d3FtbnNya2t1eX17fXt6end6QXx5dHV2e3R/gH93eXZ7f3h6enp7d316eoB+gX5/d3h5dn5weXl8eHh/foF/e3d4e3R0dXl6dnh7dXV5gXx+fH5/hHyAf39/fGx5fXx4fnx6f3x8d355fn99enR4fH10eXp2d3p7e3Rvdnp1eH14d3V0eXp4enp0eXdzd3d8f31zd3p4gHNwcXd6cnZ7dnp3dXx4dHZ4dnp8dHd3cHl3eXl2eHl2enV5d3h5eXh0eXhxcnx/d3N1c3J4dXV2eXV1c3d8e3Q7enR4fHt6c3l6e3R2e3N0fnd4eXp4fXp0e3d9e3x1fnl2enp7fXV1d3R8d3lzeHh0dHd6eHVweHZ5dnPzfwF+q38Dfn9+/3+zfwF+8H8Bfv9//3//f/9/vn8CAgQAgJaSj5KPlpSPkJiYkY2ak5ycnJ6WmZGUlZqYlZGTl5SUkJuXjYiRlJSTl5KOk5iZlZaGkZONjI2OlY+UlY6MjJaRiI+PjI6Sk4qQm42FkoqPjZOUjpGLlZmNkZKQi4yWipCLiouOk5mXlYiKio6Og4iPgYL8iYmMjISGh42JiYeLgIiKjY2IiISJjY+Yj4yPjZCCjJCUioKAj4mPi46Ti4mI+4SLgoeIioiIioaHjIyMjY2NiJKNkpaRkpSMjY6QgYyJhoOPjIeHhICBiZOWlZORj5aIko6IjIiKjo+GjIqMkIiNjI2Lj46OiouRi4eUjYWHipKPkI2Sh5CPkoyLjoiRgJGPjJCLkZGRjo+Pk4uVi4yPjpCTioyOk4yPkZGMkY2RkI+Mi42Njo6SlI6OjpKSlZWPi5KOkI2LjomRkpGQkY6ViJKWlo6PkZGWlZiUkpWXkZOUmZOUlZqglpiSkpaXkpGUlJKOj5WSj5aTjZOOl5KWmpOUlZaPlZWOnYuXj5qWgJ+ZlI+UjpGWnJKXmpORkZWTl5WTlZSckZeQlpWZlJOXl5KWk5aXnpqYlJyal5ybk5eUmJWUlpSWlJWTmJCSkpOQl52Vko2QlZmYmJmbkpGVkZSUlJGXmZSYl5iZmJiVlJaXmp+dlpydlpuWlJ6am52XoZyYl5mgm56amJacmZuTgJSUmJqWlJOem5ualZialJmYl52QkpqXmJyYmJGWkZyWlpWXmpiUlpaSnJaUj5WamJqZlJaVmpqfl52ZnpiamJqYlZeWnZiamoyWlpCZmZeVkoyAlJqUlJaXmJmTlpWSkJGWlpebmp6SkpWVlpmdmJaVl5Kbm5qXmJmWmJedm5mbgJuVkZKUlZaXlZyXlZKWmZGQmZWZkZCUl5aYmZWbmJibmJKUkJOWkpSXlZianKCWk5iWmJWSm5qVlpeSlpeXmZmUmJiWmJaVlp6cl5iSlZOTl5Samp2ZnZGPkpeYlpOXmJqbmp2MlZiblJOSmZGOlJCOkJublJaPlaGfmZuam56egJ6YnZuTnZiJnJSXpJWcl5mXkpGVkJmalpSVlJWYlZKSlZSbiJWWmZOVlpCSkpGrt5SVi4mcjJeRi4mcoJ2clZ2bl5qfmpeclp+dnZ6Vk5iUmpSZl5uHiJeXkJyWkZeLlZmTkJWUjpmZlo6HkI+Rl5OPjpKSk5CTl46PjomQkJSPgJOWk5adoZqOmZGNlY6OiYiSlJSTjZGTl5aOlJKOkJORko2QkJWUkZOSlpGQmZiMkpGRl5SZlpahl46dkZCZmpCUmI+Pj4yQl5GUjoiMj5CYlpeSl4+Wl5KPj46TlJOTlZGTk5GOkJKQkZCalo2QlYqQlZKVkZGTk4yZmJGWmJaQgI+JiImFjYmDhIyMhYGOiZCPkJSKioiKi42KiYWHjIiHg4uLhH6IiouKioiDh42NiIp9hoeDgoWGi4SJi4KEgoyIgYeIgoSHhYCHjYF8h32Eg4eJhYZ9iIyChoeEgYCMf4R+gH6Eho6Min+BgoSFen2EenzwhIOEhH9+gIODgYCBgHyAhYZ/gH6ChYSOh4aFhol7hIeLgH16h4KHgoWKgH9+6nuAen9/hH6AgH6Cg4GCgoGDfYaChoiEhoaDhYaHeYWAf32GhYB+fn59f4eJiYiIiI5/h4WChoOEh4qAhISFi4CGhIWEh4WGgoSLg3yLhoCCg4mJh4SHgYiGioeEhoGILYiLg4aEioiJhoqHi4WMgoWIhouLgYWFiISHh4aDh4OEiIaDgoWGh4WHiYSDhoSKgIWCh4eHhIGHgoqJh4WHhYt+iY+KhIOFhYaMjIeHiI2EhYiOh4iJjZWLjomKi4uHhIiJi4SCiYaFioiBhIOLiIyPh4iKi4aMioSShIyFko6Wj4yGjYOGiY6FjY6KiImNio2MiI2JjYWLh4iJkImKjIyFjYuMiJWPioiSjouRkYuOgI6Oi4qMjIuKiomOhYeIiYaMkYuGf4iLjY2OkJCJiIuJiomKh4+Qi46LjY6Rj42LjIyQkpOMkZOOkY2Kk4+RkIuTkoyKjpeRk5CMjpSPkIqPjY2Oi4qKkpKRj46Pj4qPjouShIWRkJGWkZCGj4mQjIyKjZCNiYyKi5KMjYSLkI2PRY6JjouRjpSLkI+Rj42OkY2Nj4+SjZCPg4yKgomKjIuJh3uNkYmIiouQkIqMjYuIiYyOj4+MkIiJioyOkJKOjYyNiZGQkoSOgI+PkY2Lj5CMiouKi4yLi4+Mi4eMjoeGj4qNhYiJjIqNjYqQi46Qi4uNiIuLiomOi46Oj5SLiZKPjYqJkZCMi42Ij5COkI2Kj46Mj4qJjJKRj46LjIuMjYiLkJONkYSJhouNiYiPj5GOjpGEjI+RiYqJioWDiIeDg4uMhomEiJKRTo6Mh4yQj4yLjo6HkYyAjoeLlIiQjI+LhomNiY6Pi4qMjIqOjImLjYqRf4uJj4mJjomIi4iZnoWKhIORhI6IhYORlJOQiZKQjo6VkIyQjYSUgIuJj4qQjJCOjn59jI2HkYuIjoOJkImJi4qJj42KhX6FiIiNiYSGio2KhYiLhoeHgIeHi4SKjouLi5GMhoyHhYuDhYOCioqKiYSHh4uNh4iJh4WEhId+goaIiIaJhoqHhYyMg4eIhouHioqLkoiEjoWEjY2Fi46GhYWFiIyIioN+NIOIho2Ni4eMg4yKh4SEgoqLi4yKh4qIh4KFiIeJh42JgYeLgYSJhYyFhYWIg42Mh42OjIYZfXl3eHZ/dm5yfHp1b3x3f397f3h3e3Z4eIR1gHh4dHR5eHNzfXh2dnR1cXZ7eXd0aHJ2cm9xdHtzeHpwc3B4dW90d3F4eHNudndzbXdscHB0dnN2a3R3b3Z2dXJxe3F0bXBwc3N8eH50dHVzdmtsdW9y2Hh1d3hycHJ2d3NwcG9ueHZzcm50dXR7dnZzentvcnV6cW5tdXJ4cXV3gG5vb89sbm5vbXNucHFvd3R2dHFwdG93d3R7d3V7dHN4em94cHFve3ZybXV1c2tzd3h4eXl8cXh4c3p3d4B7dnZ2eX51enh5eXt1d3J4fnhveHl1eHh6fXt3eHV6eHp6enl1eHp8dHl4fHl6dX13dnl+dHZ5dnl4cnZ3eXd4eXl0gHV0dXh0dXZ2d3l2d3l0dHJ4eXZ3dXB4eHd1cnhzeXl8c3h4eXB5fnl5cXJ3dHt4dHN1fnRydHp2d3t8hYB/ent7end1enZ+dnR5d3h7eHN0c3p5fH52dnt5dnt4b355fnaBgIl8eHiCfXt7f3Z7eHl3eHl+f3x8gXl+dHl4fH2CgHl9fHt5fHx9eYJ5e3mDfXh9gnuBfHt8eHd8fHh2en15eHp8eXyCf3Zuenp7fHt/gHp2fXt+enp7fn17e3h6fIN+f3t8fICDgnt/fnuAfHyFgIGBfoODfn6BiIaCf3x+gnuAdnx8fH5+fHmEgoKAgH5/e399fIV6dHt7goaDfHl/gHyAfnx6gIR+fH12fH58e3h7goGCgnp4eYB8gH1+foCDfoKDf4CAfYF8f391e3x1enp8fXp4cnuBend5fn19e3t5e3p7e318fH99eX17e32AgH16e356gXyCgH9/eYCBgHx7fn15eXl6eXh2eYB9end8e3d4enx/d3l4eHt8e3qAgHp9f3p+f3t6eX97f3p7en2CfnuCgHx6eoKAfnx+fX+Af4CBfYF8eHx6eHuCgH18ent9eXx4en18eHhrdnV3fXZ1en59eXp8d3Z5fHZ4d3R0dXF0cm90dHB0cXJ3fHp6c3x8end4fX13fnpveXZ2e3eCfX57eHV8dnt7fHx7fXp/gHp2fYB6fnF7eYF6e357eHp5fnt0eXV5gHd8eHl1fYCAf3h+e35+hn16fXp/gIKAenl/e317fX97cnJ6fHZ9eXd6b3d+enl2eHh8eXhybHZ3eHl5cHd6e3lxdXd1d3h0dXd7c3l+fnx1eHd3eHZ2e3V4dnR4eXl5dHV2eXt4enl1Y3R4dXdscXl1eHV6dnl3dnp3dXZ5d310eHd+fHdzeHB1fnt0d3x1dHp3d3x3d3Fydnh0ent6d3xzfXd1dHJzd3h9enl6eXh2cHJ4d3h4eXl0eHpucXZzeHJ0c3Zye3p4fH17cvN/AX6sfwF+/3//f/9//3//f/9/5X8CAgQAgJKQj5CQmZeSk5WakpWYoZeXk5WWk5SPk5GWnJaXlpGNk5GbsYiilpiTko2SnJiUlpGUjpWVk5OUlJeVi46Hh4ODgYqLiYiNlZSSlJKPkZCNlZSTlI6QlJmRk4yOkoyNjoqHkJaQkIaChYKFk4yIjI6JioeLhomLhoiFhYqJi4yAgJCKkY6JkYKElJCHi4yOkYyFiIqPjY6KiYqHiouHhomGhIeEh42PhoqKko+Pj4yRiZaRm4iQko6LhZSNkpOQ/vyMk5OH+Pj+iIqLhYaFmJaUjI6FlI2Nh4qJh4uJjIaOi4iKi5CNhZCMjpGQjpKOiY+NkIqKiI+Ij5KLkI6PiY6KgIuSjZCSjY6NkI+TkpGTkYySjYmViIeQkY2VkpKRk5aUkZSPkZKNjI+NjpGWlI+Sk5GOl5KRkZOVk5GUlZWTk5SWkZeYlJSTkpWRjpWTjY6Rl5SXlZaWjY6Yl5WUk5eVlZaUjpGSjpGVkpOUlJOWlpGYkZiYkpKMl5uTkJCal5iZgJSblJCYko6YkpeZmJiPi5OYlJeTmJWWkpeVkJWOl5iYmZiUn5ydmZyXn5uWnZWTk5WMipOYmZWXmJOVmZGQlJeTl5mRlI+XmZmYmJyanpWXlpeUl5SQnZSUmpybk5uYmZybl5mXm5yZlJ2bmZqLmJCZnKGalJqaoJadlJqUlJWXgJOWmJSXlIyZl5KTkZKWlJiXm5GZl5eJlZGWl5WWlJeUk5aYmpmXmJeXlpmYlZaZl5WRl5aYmZaal5iZlpqUlpihmJiVmJmUmJaTmZeOj5mclpKXmpmVmJiUmpeSmZWWlZKampWUlJecmZWSlJaWl5yUlZKZmZSalJuXk5OYlJaZgJ6TlJOWlpiUk5qUmZGYmpial5eYkpiaj5GWm5eWmZWTlpecjZGLlpGblZOZlp2dmZWYlpSWnIuQmZiXmZCQmJeXlZSZkJuYnJ6al5malZaUmpucmJmbm5WRkZiZmJeUlZiam5qqoZidmZmXlI+TkJiSjJCRmI2Xi6ChmJmal5eSgJmUk5mclJaXkpWamJudlJuXl5GVkZKYl5mZlJSYmZSSlZWZkJmXk5eWl5OQjpqXuZeTl5Gaj5KRiYSUlZqZm5ufm5eVnZWVnZaYmpuWmZaWmpiVk47y6ZeYkZaZlI2TkZWWjJGSlo+pkJKYioicmYyOkY+RlJKVkpSUkpWXk5aSgJSboKGbkoqUlpOclZqZiIqJl5iTjpSSl5WPko6SkZKUmZSUlZCSk5WVl42Rmpqak5KXk5uWlZeQl5OYmJmXlJaVkpOPj5GTkpKSjIuIjZybmJWOi5qXnZiQj42OlZCZkZCWk5OMi4uSkpWVkpOTlo+XkJWakpKWlpSWk5SWj46PgIeGh4aFj4+FhYmNhoiLlIqQh4qJhIeFiYeKkYmOjISCioaQnnuTiouIh4OGjoyIiYeIg4uLhYeIiouKg4N/gHt8eoOEgn+EioyHiISFhoiCiIh/hYKFioyEiIKEhYOFhIB+iIeDhYF7fnt+iIKAhIN/hIGCfX+EfIB7fIB+gYB7gIaDiYl+h3t8h4N9hIWGiIF+gIKHhYWCgYCAg4N+eoB+gH58f4KDfX+BiIWFhIOEfouJkn6Fi4SBfIiEioqI8/GFiol87/L6goaEf4F+j4qJf4R8i4aGgYOBf4SChH+IhICEhYiGgYiFh4qHhoiGf4WEhYWFgoZ/h4mFiIWHg4eDgIOJgoaHg4WFiIaIiYmHiIWJhIKMgICJh4aLhYmHiIqJhomHiImFhIOEhYmLiYWIiomDjImIiYiLioiIi4yKh4aLioyMiouHhYqHg4eGg4KIj4qOiouNgYWLjIuLh4mIiYyKhYqIgYiMiYuKiIeJioWOh4yLhYSEjZCJhYmQjo6OgIqPioeOh4aPiYyOjI6GgYuNi46LkI2Lh4uKh4yGjY6LjI2KkJKQjI+LlI+Kk42LiIyDgImMjYmNjImLj4iJiYyKi46HiIiPj5GRjZKPk42NjIuHi4iIkIaJkJKPio+Mj5COjY+JkZCOh5SPjZCDj4aNkJaRjJCTmo2SiY6JjIeOgImNj4yNioWPjYiLiIyNiY2NkIiMjIqAiYWPkYuKi4uKh4uNj42LjYiNjY6NjIuPjYuHkIyMjI2SjY2OjJGLi46VkI2LjpCIj4yIj4mCho6RiYyQko6Kjo2LkY6Kjo2MioiNjouLjI6SjYqLjYyJjZKLjIqRjYqPiJKOjI6NiYmOgJKHkI2MjJCLh4uKj4mNj42SjoyMh4+Mg4aLjo2NjouJioyOgYiCioeRjomOiZCSjIyOi4qMj4SJkI+NkYSJkpCPjomOh4+OkZGMjI2Qio2LkI+PjY+Pj4yJiI2LioyKipCPjZCjlY6Qjo+PiIeKgoqGf4GAh3+FfY2QiY6NiYqDgIyFiY2NiIiMioeNjI6PiI6LjYeMhoiOjo6PjIqMj4yLi42Qh4+Mi46Nj4uJh4+FoImJjYiRiImIg3+KiZGRkJCUkI2NkYyLkYyOkJKNjYyOko6Ni4Xm04yOh4uOioKLiYqLg4iJjISYhIWMg4GSkIOEhoWJi4eMiIuKioyNiIyJgI2RlZCKhoCJioiQiI+OgoV/i4uIg4mIjYuHh4OHh4eKjYeGioWFh4qKi4SDjYuSioaKho6KhYmEiouNjIqMiImJh4uHh4eKiomLhYR9h5aUjYmFhY6MkY2EhIaHjYiPhoeNiYeBg4SGhYiJhYiHjIaMh4iMh4iJioiKiIyNhoOEgHh3dXF2fH10dXh4dnR6g3V7cnh1cXZzd3N3eXN/fHR0e3Z+gGh8dHd4d3BzeHtxcnZ2dXp7cXV6enl6dHNtcXBwb3VzdXN0eHt2d290c3hsdXdpcXR4eHlzdnF2d3N3eHNveHJwdHRxcnB0e3N1d3JwdXRzbXB5cXJvb3FudXFugHB0fHxwdm9ud3Vvdnd6enBxcXZ3eHRucXFydnd0cXFxdXBtam9xbHBydnN1dHR1bXV4f3J4fXV2b3pydXd+3eFzd3Vt3+DncHdzc3NseXZ6cndteHZ1dnd2b3h4d3R6enp8enx6dnp1e3t5eHl5dHx2dHl5dHZ1enp2eXp4dXx0WHZ5cnd7d3Z3enl5enp2dnh6dXh9cnF5eXp8dnh3eHl4cnp2e3l1dHV4dXh2dnR2dHdxfX53end4dnZ1e3x3d3V7fnx6fX91cXh1cXd2dHV7f3t7dXl7d3eEfIB1eHl6gXx1fXlteH98e3t2eHh6dH5zeXV0cnR6fnt3en98fn52e354fHZ2fnl8f3l3dnJ6fnx9e358e3R8enp9d3yAe3p+f4aDenl9d4N5e39+fXt/dGx2fX94e3l6f4B5eHiBfXl8enh1fX6DgXt/f4B8f3x7eHx8e4F9eICAgIB6gn1/gH58gH2CgX12hIF8gnd/d31/h4CBg4OMgIJ6fn5+doF5goB7fnl3f3p4fHt+f4B+foB4fX56dXh2f4GAfn15dnuAfX99eoB7gH5/e3x6fX18e396fXx7fX9+fHiCfHx+hH9/eX2BeH6Bfn57bnx8hXl+goN/eX+Ae4J+fICAf3x6eHl7fH14e358e3t/e3l7fXt7eoF9d3l2f3x8gHZ0dXp9d317gHx9d3N6en13e36Agnx8fHp/dW95e3t7fH15d3t7eW96en15fX56fnR8f3l8fXx6fn51e36BgYN0foSAfH54enmAfYB9fX1+fnx7fX5+end9fH56enl5d4B4e3l7fX18fZeEeH57e3pyd3ZudHRva2twbHBncXByeXx3eXN4c3N3dndzfHx2eHd7f3h8eXt3fHR6fH59fXt5en15fHt+f3R8enh8e358e3t8bnx5d357fnt4eHdxd3l/fn17fn98fH58fIB7fYCAfHp8fn57e3p21L96fHd6eoB5bnh1dHhydnh6cn9zc3l3dH98cnN1dHd4dXl4eXd3fHt3eXl+gIF6cXJxeHl3eXV5fXV5bXt7eHR4eH96eHV0dnV2dnd2dXpzc3d2eXt0dXt5gHp4dnd4enN5dHh6fHd6fn17e3d5eHt5eXh1eXd2cHiHhn52c3Z9en55b3N4eSR7dX1zdnt5d29ydnhzc3Vxd3d5dXd0dnp1dHV5dHl1eXx1cXD/f79/gn6Ef4N+/3//f/9//3//f5N/gn7/f6p/AgIEAICMk4+Qj5mbmZOTkpuNkaCZnZeXj5SQmJeNlpaPlI2XkZGKmJqOiZGOkYqIjo+Tl5SUj5WUmZedkpCUi4/+gveChICBiJT8+YqLi4mOkJOJjZSSk5iSjpWQk5qWkZGIiYuNkJKVkJaMiYaLhI+CiIqEg4mGh/+CjY2JkIuMioeOioCLiIiKi4mUkpiLiI+Tj4uNjYyMjpCNkIeJiI2Gi5CNjIqOioaJl4+IgY6CkIySkpONkJCQk42VhpCOk4mOi/uAgIaNj4GKhvv/iIiPkJCVlpKRkI2HjIyMkoiFjIyMiYWJi4yPjIqPj4+NkYaUkIuQiZGNioiNjYiPlY2PiYmLi4CPkZOUko+TlJGVjpGLj42OjoyJlY2Pk5ORkY6QkpOIk5CSk5SRj5OKjI+RkZOUkoyRkY6Rko+SkJSUkpGRl5SXk5OQlZaQlZWTk4+MlZWVkI6SmJWPj4+QlZSSlZOYk46Sk5STkZGTko+OkJqXl5GTnJiWmI+RkpybnpuYlZSZmoCelpqZm5qXlJyYjZOTkpeVk5OYmZeTmpGYlpeRlpycoZaUlZqYkZeZlpaVnJqWm5qekJSbm52ZlJOWlJiXkpKYmJSWmJuYl5WTlZWdlZiUlpaWnZWVl5KRk5SRlJiXlZiQl5efmJaelJ2WlZ2YkpmQlZqSnI6WmpSdnZScoZeYmoCWk5mSkpKVlpmXk5acnZaWlZaXl5STmZ2UmpeZmJiZmJKXnpqXlpmXmpianJuUmZ6am5aUmZmXmpyWl5SXnZeYlpmWlJSWk5WXmJuXmZGbmZOWl5WVmJqdl5yWlpWSl5iZkI+XmZmZmp2cmZqVlJ6cmJOdmpCSlZiWlJSTl5eTm4CZlpaZmI+WjpaZk5eYnZqYlZCbkpeXm5uWlZ2amJqZlZeam5WUlpuWk5aVlJWXlpiel52YmZOWkpuXmJiWkZSUnJabkpWakpSclJmalpGSkpSZm5iYm5qZk5ienJyUm5CZmpKXmaKVl5SZmpOTko+DkZWShp+Uk4yWkaaWnpuZn4CemZeZm5qalZORn5qamZaWnZuVkZOTo5aSl5OZmZOUl5iTk5GPk4+Pk46RkJKTj7mWk5OTl5SYkZKNmJmUmJiXnZmUl5qVlZmWl5mYlZGXlJWVlpyVkZSYnpKYmZCTl5iSh5GMjpSSmJyTkZ6JjZGTkpeQk4+SkZSPiZCSlJOWlICVl5yfoZqblZORlpCKjZGWk5aQmJOSko+VlJGblpePmJOSlpiXlpOOj5KUm5mclY+Pl5aVnJicmJiOmpualZGVlZKOjoqYiJSji4CPkY2AjZicmJCSkZWZkpGPkJOQkIqPj5CSlZCRk5ORjJeVj5GVk5CNlpOPlY6Kl5KSj4+QlICAiIeKhI2QjIqJh4yBh5SLkI6LhImDjIyCi4uDi4WLiImDi4+Cf4eDiYJ/g4eGiIiJhImFjI2OhYOHgIXofPR8fXl6gort6oKBgYGGhYiBhI2IiY6IgoeFhouJhIWBgYKEhoaKhYiBgH2DeoJ5foF7e4B/fex5goSCiYSFfH6EgoCDgICCgoCHhIyAgIeLh4aGhoSDhISEiYKDgoV+gIeEgoGEgHp+hoJ/eoR7h4CGioqHiIqFiISKfYWFiICDgex7eX+BhnqDffT2gn+Ih4OJi4aIiIV9hoaEioJ+hoaGg36EhYaIhYOHh4aDh3uIiIWJgImFgoGHh4KGjIWHg4CChICFhomMiYeKjIaLhIiEiIWHhoSCh4WFh4qHhoWFh4qBioeJiYuKi4qDgoeJhoiMiYSGh4eKiIeGhYmMi4qJi4eKioqGi4qHjIqGioWEiYuJhIWKiYmGhIaGioqGiYqOiIKKiYiJhoaHhIiEh5CNi4OHkIyKjYaHh42QlZKMi4aTj4CQi5GPjouLiJGMhImKiIyNioyOjIuIjYeOjI2JiY+Pk4yKi4+Ph4yQjY6Nj4+KjY2RioiTj5GOiomNi4yLiYiPjYeLjZCPj4yLjYqRiYyKjo2LkoyJjoqKjIqBiZCNio6Jj4uSjIyTiZGOio6Nho2GjI+FkoiOko6Sk4mNkIuNkYCMiZCKiIaKjJCNi42RkYyOjouNj4iJjZCLkI2Pj42PkIuLkI6Mi4+Mj42QlZCIjJaOkY2LkI+OkpaNjo6PkouOjZCLiYmMio+NjJKQj4WQkIuNi4yMjpCQjJKLi4yIi46Ng4KLkJCOkJGQjI6MipCPjIqSjYaIiY2OjYmLjo2KkoCQjIyOjoeMhYyPhYuNkY+Oi4SSiIyLj5CKiY+Qj4+NjIyQkIyMj5GOiYyKjIqMjY2TipOMj4yNiZONi4uNiI2Nko6OiIyQiouRio2OjIiLi4uOjY2LjpCPipCUj4+KjoSKjYeLjJWNjIeMkI6KiIZ3gIKCeoyEhoGGg46Hjo6LkICRjouMj4+QiIiGkYyPjYmKj46LhoqLk4yJjoqNjYmMi4yKiYaFioaHi4SJioyIfqKIioyNjYyQiYqIjY+MkY6MkY6JjpGOjI+Mj5CPjIqOjIqMj5SNiImRlImNj4WIjY6HfoiFho2JjI2JiY+Cg4OHh42Ji4eKiImFgIiKioqMiViLjY6NkI2Qh4iHiIaChYmMiY2HjYmJiYOLiIiOiouDjYiJioqMi4qGh4iKjoqMiYWDiYmKjouSjo+Fj5KMhoiKiIiEhIONgYqVfneGi4Z4hY+OioeHhoeNhIckioWJgoeGhYiKh4qJioaBj4yHiIqHhoSMh4SLg3+NiIuIhIWJgGx5d3V3eHx8eHh2eXR6g3p7enp1eHR8e254eHB2dHd1eHJ8fnZydnR1dnF0dHNydXdzd3Z1dnVubnVzd81s221wbmxzeNbScHBycnpwd3Bwend3eXRvdHJ0d3lxdnJyb3V2dXl2d3BzcnhscXFwdG1ucnJvzGt0cnJ3c3VqcHVzgG9ucnB0cnVyfm1ucnp3enZ1dXV4eXd7eHdwc3F0d3FudXVxa2hram1ucnJ3cXN5eHh2e3F4b3ltdHF1cHN22G5qdHZ3bXVv2tt1cXxzbnV3c3d4dnF4eHd+dHJ9eHl2c3h9fHt4eXt4d3N4cHh9en52eHd0dHh4dXd8eXd1cnd1gHR1d397eH5+eHx0d3Z7eHd3dnN2eHd6eXl1c3R2e3Z9dnl0ent+eXFyd3x2dXp2dXR3dnt0enh3dXp7fHh4eXh3eXR6enh9enZ3c3l4e3l4eH54e3d2d3d8e3Z1e4F5dnt4fXl0dnV0eXZ4fnt8dnSDg3l4dnp3en2Agnx6eYSAgH98fnp7d3p4f310e3l7f3t6fIF/enh7en57eXt8gH+BfHl+f4B4fIF9fnl+e3qAeoB5dYF+f314eX16gH57eIF/en9/gX59fnt+en58e3uAf3yAe3x9fnx5eW16gIB8f3mBfn94eYF6fn56fH14f3WBgnaDe4CAfYGBeHl8fX1+gHl6f3t0dnh+gYB9fn2CfH2Be3l+eH9+f31/fn99fIGCfH1+fH13fXx9gX6Cf3t8hn9/fn1+fHt/hH9/gIGCen9/gnx6fIB7fn57gICEeH+DfoB7fH9/fn98gHp9fnt7f3lxc3x/fnx9f35+gH57fH97en55dHZ2eoB/e3l+eHmCgH18fHt5dHx1en50d3uBe357c4N5fHqBgHp6fn58f35+foB+fYKAgH15gH18e3x9eoJ7gXl9gIJ7g4B9en98e3uEgX55en57eX54eXp4fH19gHx6f3l7fHx6fYB8end7dXl4eHp9gHh5dXd/gXx5cmdmamlpb3F1cnBtb3N9fHx9gH9+eHp6e31zdXd9dn12dnx/e3p2eXt7fHh9enh7enx7fHl4d3Z4d3p6dnp5f3dpgHZ8foB7en56enl5fnyBfHt9eXd8fH58fHt9g4F8eX17eXp8gHp5en9+dXx7c3h7fHhvd3V3e3h3dnd3fHR0cHR3fHl5eHx1dnNxdnp9enx5gH19eXZ3d351dHZ0cnF1eHt4fXh8dnl7dXx5eX55dXB7d3d7eX18eHZ3d3p3en11dXBzd3l7eIF7f3V8fXl2eHp5eHd0cXttd35ra3p8dnF2fHZ1cXRyd35yc3V0eXV7c3NucXZ3eXp8eHRrfHx4e3p3eXV8eHV4cHB7eXl2c3N1vH8Dfn9+hn+Cfq1/AX7JfwF+iH+Cfv9//3//f/9//3//f71/AgIEAICSkpWTl46SkY6Bqu+Pio2LkZWUk5CSjZeKlpiOlpWXk5iSkpCZnoCTlI2NmZORlZyUl5CLk5CakI+XlZODgYeLhIaEh4KOhYeWkI2RkZGUjJaTlJaVmpeTlJKUjpGUjY6Jk42Yko2JiIaGiYSGi46Kko2Ih4X9ioSJioqFio6Oh4CKiI+TjYuGjImMjYqKjI2OkpKQmI2Ti4WJjIyLjI2Tg4uPj4+O/OKBgI6NjI6Nio+TkY+NlZGPlJWXlpORi4KEjI74gYL7goWHhIuJgYaRq5OOlI6Nj4+NiY6KjYmMi5KPkYiOjpCNkIqMjI6QjZGPjouNj46PiYqOko+Qi4mPi1mQiJOPi5KVkJCMkY+UioqOkJGPjo2RkZCWl5GSkJGTkI2TkY+SkZeQkI2Sk5KOkZKQk5eLjpaSjJKPkY2Wj5GLj42UlZGUjpGWkY+SlI6Ul5OPmJiPkZSVkYSUgJaTk5mYl5OTlZGUl5KUlY6Ol56alJ2VlZmbmZSVjZmbl5Ocn5uUm5STm5iYlo+WlZOcmZaVkZGalpeWlpGZm5eUmpuglpmcmJWXl5yYlZSPmJWWmpWSlpaVkpGUlZKUkJGVlZqZmZeYm5yYmJuZl5WSk5aXmpyalpeWjpWUl5qZgJ2aoZOdoZ+Zl5aVlZabnJuam6Gcl5qWlZSWmJqak46RlpOanJaanJqYl5qZnZqdnZqXmpqWmZSUlpibmpaXm5uclJyel5qTi5ibm5eem52bkZiXkpKQl5ielZiWm5KWlZiWnpaYj5qYm5OYmJmMlpaWlZSXlJmcmJWXlJqblpOUgJSXk5+UkpOYnJiTmJqbmJiWmpmWlZiTmZOZm5SWlpmak5aXl5WUmJWalZeWk5iZmZeXk5SVmJmak5WbmJaZlpuWk5mUj5iYl5qYl5qTlpaWlJKSlJeWkpyZmI6Um5immJGVopKXmZqYm4+Zl5udkZiSkpOTnpWVm5eTnpWamp2agJqclpaUnJebmZKZm5aWlpSMjpGMmJ6ZlZeQhYaGhKmXmJyil5+XmJmblpqWkZqgop2dk5qamZaUkYyamJKWlZaYlpeRk5eTjY+Wk46YkJeNkqyws5SVk5aVkImMkJOVk5mUk5edmZWZl5GUlpCXl56VkZqZkpOVl5eUl5iWl5adgJWYmJySlpKWlpORlpSRkpaQk5WQlpeSlJKSkZOTlpGTl5aTm5SinZiUlZGXnpmcmZSUkZKKiYSHjZKWiYyZk5SRjJKTlpOXjo6QkJCHlZSTjaCXmZ6clpmalZCbmpiXmpePjpiTko2VloyLnJOXnp2YlpKVkJKUmpaOj5aTkZOSI4qMjIiTk4uRjpOVlpOTi5GTkpCQkI+OkomTl4qSiYyLkpWLgIeIjouPgoiFgnig3oaAg4KGi4eEgoeEjYGLjIOJjI6IjYmFg4yQeIiIgoWOh4SHjoqKhIGGho2Eg4uIh3p8fYJ+f3uAe4N7foiDg4iGg4l/jYmKi4mPi4iIhYqFhYaDhIKJgY6IgYOBfn2EfHmBhIKJhYF/fO6Be4KAgHyBhYd+gH9+h4mDgHuEgYKFf4OBhYaIiIiOg4mDf3+DhIOGhIh6f4SEf4Pu0n15g4WEhIWChYyKiISLhYeKio6KiYmDen+Fhe59f/R9f4KAhIB7foOWh4WMhYWKioeDhoGEg4SFiYaKgYWGiYaHhIeDhoaCiYiGhIWHhYaBgoWHg4eGfoWAgIZ/iIaDiouIh4aKiYqBgoeJiYiEh4eEhYiLioqHiIuIhoyKhoqLkoqIhomJhoOFiIWKjYGFioeFh4aGh42EioOIhIuNh4uFiIyGiIiOhYuMiYGJioWIjYqJiomJiYqGiI+Ni4eFiIaKjYaKioGEjZKQhY+LjI6NiYeJg4+PiIaRgJKPjJCLiZKOjIqGjImJk4+Jj4mIj4yOjoyGjo+KiI6NloyNkouJiomRjY6Lg4uKjI6KiI6OjYiFiIyJioiHi4yRjo+MjI6Rjo6Qjo6LiYuNjY+PkIyMi4SLiImPjZOPmIqTlJWNi4aJjI+NkJCRkZeSjIyNjIyMjo+PiIOGiYaPgJOLkJGOjYyMjpGPkZCNi5GRjYyMjI2PkI2Li4+PkYiTkoqPiYKPkJCLkY+SkIiJjIqMiY6PlouOjZCIjouNi5KOjYOQjo6Lj4+Oh4+Ki4+Nj4yRk46LjYyMkI2KioqLiJaLiouNk4+Jjo+RjY2Jjo+Njo+HjoqNkYqKi42Ri4yOgI6MjI6MjoqNjI6OkI+MjYmJiouQjoiJk4qKjouQjoiPjIiRjYuLio2PioyKi4mKio2MioeRj4+Hi5CLmouIjZiIjY6NjpKGjIqQj4mMioiJiZaIi5CNi5KMj5CTjo+SjomIkYuOjoyPjouKjIqEg4aBiIyJhoeDd3h5dpWKjIyQgIqQjYyOkIuNiYaSlZOPkIeQjpCMi4aEi4uIiouKjY2OiIuMioSIj4mEioeNhIabmJqIjYuMi4qAhYeJi4qRioqNkY6MkZCKjI6HjY6TjIqRj4eJjIyNi4+QjY6MkoqNj5CLjYmMjoyGjYyJiY2Ji4yIjI6KjYuIiIqKjImIioyGOY2Kk4+LiYqEjJGLkY+NjYeHgH58gIWJjX+DjomKh4KHiouKjIWEhoeJfoaEhoSSioqQj4yNjImGkISOQ4qDho6FiIWIioSFjYKEj42KiYaLh4eHi4qDg4uGhouLgYOEgouKgIWDi4uKh4iCiYiHh4aHiIeIgouMg4eBhYOJjISAdXd+fHxvdnVxa5bDdnN2dXF6dW9wd3Z6cnx7dXl7fHd5eHZ2fXlseHpxd3x0dHZ7dnJwbHF0dnBveXp3bm9vcnB0aXFqc2ttc3JyenJ0c218dnh2dX15dnV0enZ1eHR2d3JvfnlwdnBycnpzcnB0dHl1c3Fx1nFudHFvb294e2+AcHN4enJwbXB0cnVtdXJ4c3h4eXpwdnd0dHF0dHl0dW5wcG9rbcu4bG11eHF0dXZ2e3Z3dHt4fHt6fHp8e3Rwc3N233J43WxydHZ0cnBubXp1d4B2en1+e3Z4dXd0eHh6d3x3enl7fHp4fXV4dXh6fHd3d3p5e3Z0d3l1eHd1dnOAdnF7d3N7fHh1dnl6eHR0enp5e3V5d3J2eHp6eHV3eHl4d3l3en2Bend6enp1c3V3dHp9cnV3dnl3dnJ3e3R/dXhxeX93e3Z5e3R2e3t2eHp7cXZ4dXh8eX14d3d4enh8fH14eXZ4eX1/dHh6cnd7gXxyf3p7fnp2cnR3f312dICAgXx9f3p5goF+eXl8d3mDgXZ/e3t+fH1+end8f3x2fnqJfn6Ce3p5eYN7fHdweXp/fH15fH55enh5fXx5fn9/fYJ/f395eoB+f4B/fnp5e318gH6Bf3x+d31+fYF/g3uKfIeFgn19eX5+f32Afn2BiISAdoJ+fHx+e315dHR6d3uAgHl+gX+BfHx8fn+Df4B9hoF9fX9+fIKDfX6AgIGBeIaAfH54dX+BgX+Bf4B/eXl9gYB6foGGfn1+gXyAe3x7hIN+dn1/fX2Ef4F/fnp6gYOFgYOBf3x9f3t9fnp8d3l7hHp9fXuBf3x+foN+gHp9gHp7gXt9enuAeHp4fYF7eHyAf3p8e31/eXt4fn19fniAfnh3eXt8eXl/enV7eHt9eHt6d4F9eHd2fHd6fXt8eXl4fHp5eYF+gHh8gX2MfHl9hnd6goB+e3B3e3t5d3h7e3p6h3p8f3t+gHx/fH17fHx+d3R7eXt6eX18enl8enZzdHBxcHNucm1jYmBjdHV4dnqAdnp6e3p9dXh1doJ/gHp/eoJ7fXp9dnh3dnd2enZ6fn12eXx6d3h/d3Z3eHt0dH14e3h8fH58e3J2eXp4fYF8e359fHl/f316fXd+fYB6fH98d3l7fH16fn56fXt+eXd8fXp4eHh7eXF5eXl7fHt7end6fnx+end6dXp4d3V4fXOAe3d8fXh5d294fXZ9fnyAd3dwcGtxd3l8cnV6eHh1cnd8e3h9eXh2dXdvdnF0dH15dnt9e3t7eHN7fHt5enp2d3tzd3JvdnV2eG1uend3eHV7d3V2eXpzc3V0eHp6cXJ2c3d0b3d0eXt7d3ZyeHpyeXd0dnh+cHp1dXZ1eXd5enaKfwJ9ful/AX6vf4J+m38Efn9/fv9//3//f/9//3//f8B/AgIEAICHiZKYkJeMlpeIy4uHkJSNkI+Tk5qOm5iQkpqUk5aZlJeVlZKLmZuekqOkkpSXlY+Qj42OkJiRlpSajpuJiY2DhoiUkIyTl5KMgYSPk5GOk5eNmZKTlZKNl5mRk4qRkouPiZKYi4ySkIyPiouIh4yPgIOKh4j1i4iHjoSIi5CQjYCMjJGTk4yFioiNjZSMio2Nh42CioaOiYyPlJCDh4uBhoqJiYaMhYiH/ISFhZqLmJGWkY+UkJSTi5WQjZCKkouAgYiCh4X4gYSChIOKho+SnKiVjImSj42Oj4+Mi4uPiImKjY+JkZKMkI+VlI6NjYqMi4uUi5OQkZCOloyNjoiHkICRj5WQj5COkYyKio2KjpGTj4+UlI+Mj5OSj4uUlY2Jko6Oj5GHjo2Ni4+TjpSQj5GRkIyPm5KVkYuQkJGQkZOWlZOSmJGVlZWWkY2ck5WVk5OQl5OUlZSWlJaVk4+WlZial5COmJmUk5WYm5aUlZaYlZudl5KWlZiYnZKXnJiakYCYkJqckpKYlJSYlpeTlJeUjZKZk5qUlpqXl5aalZOSm5ebmJqfm5eYmpSblpidlZiTmJqak5WXlJaYl5iTl5mVk5qUlZabmZeWl5WUmZiXlZWXjZ2ZmZiWmJiblpWampyVlpybnZqbnp2ampubmJyam5mUmZmRlpSZlpKYmpaVmICTlpOVmpqVmpSZm5yalZmalJuUlJiUmpeZmpadm5uYlZqYmJWUlZqcl5eXnZuVl5iUmJuXmZWbjpWZkZaZmpmXlZGWl5eYl5mWlZSSl5SRlZaXm5aXlJyXlZiUl5WYlZSUmpeZmJiVnZaRl5eUmZuXmJ2YlJmWlZqZnJeZl5aTl4CUkpuWlZqbl5eXlZyamJucmpeVlJeXnZiXlpeZlpeWmZiYk5iXm5qelpSXk5mYkpWVl5mXm5SXlZmXkpuXm5mWmZ6YmZeUmZmWlJaZmpeXmZiTmJeVlZeWlJyZmZiWmpGRm5mZl52UlZWUmJiVkY+AjZKpo5GOnJWYjYaIl42XnX6gmJ2dmZ2XnYyVnpeWo5yXjpiWkJSbjpWYlpaalpKTm5KUlpORj4aUkJL88IKCmrC4kJaZl5mUkI2Sl5aXlpaXlJ6VmpOXnZqckJmam5uYnJWYmJmRlY+WlZ2WmZyZkpianpWTlpSIiZKOk4uRlY6Pi5CLkYuVlpWckJCSmpmEl36amJaSl5qhn5WPlJWTmZeTlI2NjY6KkpSRkJaSnpeXjpKXkIOWh5COiYGMlJagnJqYmJifoZ2bk5eZkpKTk4GDj4T2/YH8gYORlpKVk5KNk46Xl5KPkJCIi5CUkI2JjYOHlIqSkJWKkJGMjo2Mi46NkIeQjJGRi42JjI2OkouAgYGKj4aKgYqMgL6Af4eHg4WChomOg5CIhYaRiYiMjoeOjIaGgImKj4KRkoWHiIiCg4SBgoSLhY2Ki4GKfoKBen9/ioOBioyHgHd9gYiFgoiLgo+HhoeIg4mNhoyEiIaEhoCFjoKDiYd/hIKCf36Ch3R6hH9/54SBfYR5fYGHh4KAhIOGiIaBe4KAg4KLhH2DhH6Ge4F8hICFh46IfICAd4CAgYB7f3x+f+p3eYCNg4yHjImIiYSJh3+KiIWHhIiBd3uAfYKA7Xp9f4B/g36Eg4mUi4SDiYeEiIaGg4WDiYSBg4WGgYiMhYiGjIqHhIWBh4SDioOKiIiHhI2BhYV+gYaAiIWNiYiIhoeFgoGEgYiJi4aIiomFhIeLiYaBio2FgoyHhYeJf4aGhoKIioSMh4WGh4WDhI6EiIeAhoKHhoeJi42MiIyFi4uLioaEkIiLi4mJiI6HiYuIjImJi4yFiYmMi46HgY2KiYWNjo+IhoyKjYqRj4mIi4qKjZOIipGNjYeAj4iPjoeJjomJjIqMiImOjIKEjomRiIiNjYqLjoeHiIyGkIuJkI2Nio2HjYmPlY2MiIyOj4iJjIqNjo2OiI6PjIiOi4qJkY6PjYuLio6OjoyMjIKSkI+QjJCQk5CMj46QioqUk5CQkpSSkpCRj4ySkI+OiY2MhoyJkI2Jj5GNi4+AiIyKjpCMiY6Kjo+RkYqJjYuQi4eNjo+LjJCJkY6Ojo2QjYyMio2Tko2MipGVj42Pi4uNjI+MkYWLj4uOjpGPi4uGiYyOkY2MjIyKi46Kh42OjJGNjouQj4qMjI6Kj42Li42Mk5CMiZOPhI6NiI2PjYyTj4uOjIyRkJGOjo2JiIuAioqRioyRkImQjouQj46Sj46LjYqNjZCOjIyNkI6Ni46NjYqPio2PlIyKjYmQkIqNi4yOi5CLjIuPjIiRjpKOjI6Sjo+MiZGRjYiMjpCLjY6NiYyLjIqLjYyRkI6Oio+FhoqNjoqQi42Li46PioqHfYF/jIyEgo2FjIJ9fYqDhoo6kY2Pj4uOjJB+iZWQiJaNiIWMioiIjoOIioqKjoyJiJKKioqHh4V8h4SD7+B3eo6YnYWLjo2PjImEiYWNgIyLlIuRjI+RkpOHkJGTko+RjI2NkIaNhY2LkIuRko+HjI+SjYqKhYGBioaIgomMhIeCg4OJg4yPi5KIioqMjYyKjIuPjYqGi4+TkImEiomJjIqGhoWEhYaBh4qGiIyHkYiLhYqOiHqLgYmEgXiDiYmSjIiJioqSlI+QiYyOhYmKQYl5e4R97+9773x8h4qFiIiHhIqFjYuJhIaJgoWIiIaEgIR9f4Z8h4WJf4SEgYWDgoGEhoeCiYWHhoOEg4WFhYmDgHN1en52d297fXSrcXBxdXJyc3V2e3Z7c3Z0fXZ1en53fXl0cnB0c3xyeXhycHR4cnFubG9xeXR6enhwdG90b21xc3tybXZ5d21pbXB3dHF1dHB6dXVzd3d4e3Z8d3p0dnVvcXtycXh3cnd0dHFwcnltcXRxcdFxcW1zbG9we3lxgHVyd3l3dW54b3Nyd3Zoc3NzeXF0bnd4eXl+dm1zbWh0cnBybGpqbWzOamtveXN6d3p5d3p0eXVueHhzd3Z5dGpvdXZ5ddZ0cHF3cXRvcnFvgH56eXp5d316eXR4d3t0cnV2eXV6f3d6en17d3V2cHp3eH94f3x/enZ+dnh3cnR2gHd0fnx5eXZ5eXZ1dXN4eH15e3x7dXR4fHx6cnp8dXR+eXh0eXF2eHVyfXxzenh2c3Zzc3R6cnp6c3Vvdnh3eXx9fXZ8dXt2eXZ1c393eHl1e3h8dXR4d3t6eHt6eXV6f3l5dnV4en14gX+Cend9eHl8fn10e3l2d3p9enmAe3t5gH14fXx5eHx2eHx8fHl4gn50a3l2hHp7f3l3en57end7dn15eH17gH18eH12gIWBfHR8e3t4en5+gH1+fnh8f317fXp8eX9/fX54e3yAf35/f3tygoJ/gX6AgYeCen17f3p3g4KBfn6DhIN9gX98gH59gXx8gHd6dnt4en9/f3yAgHp8eX5+fXt/e317gH58e3x8gHx9gX+AfX5/enx9gIJ8gYF9gHp9hIF/fXp9hoB9gn98fXt+gYp4f4KBgH2BfXp9end7fYN+fX1/fH9/fXd+gHyCfoF+goF6e4CAfoF9enuBf4B9eHh8gHaAgHx8f3t9f3x5fn17gISFfnx+fXx7gHt6f3x8gn14f4B8e3t7gnp4e314fH5/eXt5eoB/fn57eXh4fHh9fYJ7en16gIB6fXt6fX+DfH55gX17hYGCgHx6goKCenp+f31zeH18en2Ae3d3eXp4enl2fn5/fnx6cnZ6d316eXp8e3t8fX96eHFsY2ltcXF6dXtzbWlwdG9xPHx9f353ent+bnqMfnaDe3R1eHd4eHlzdXV3eXt7eXh+end5dXZzbnR1bNbLZWt3eHt2en59fnx8d3p9f4R7gH2AeX58fYB+gXh9f4F9fH13enx8c310fnp6e4B/fXZ4fn58eXhwcnR5dnRxeHl2enJucnlzfIF7g3h7eHl5fHd3dXt7eHd5enl7eHZ6dXh3dnVxc3V4dm92e3d1eHd/eXp1eXx6cHxyeXdzanF2e396cnVzeH5/e3t1eHx0dnV2QGptc27S1G7VcHJ6d3h6d3R0dnB4eXp2d3l2dXd5dXR0dHJwem53dnlvdHJweG9zc3h3dnF5cnN2dXV1d3VzenOKfwF+6n8BfrJ/AX6cfwF+/3//f/9//3/of4J+/3+ZfwR+fn9+uH8CAgQAgIWJlZSUmIyNiIaChoSBioaJiomQkpKNkZCNjYqPi5iWlZONkZOKi5eOj5CTkpmblZOSlJiXkpWTj5KVkpaLjZGJjIqRkZKJjIeKi5KIiYqTkpKPjJGPkYuPjI2Oj42PkZuQkYyFjo+PjIyGjIqNjY6OjImLhY+Hi4uNjY+JkYaPgIWPg4+RjI2EioyJi4uKjI6OjJKOkI+Plo2LipKSkIeIjY6NhqiHgfX0gYmXhoyVkpWXlZWXk5KSiZGKiJWPjYiGh4SAgfyDgoeJjImPiImdspuUi5KKjIaFi4SQhYiBiYmMkJCRj42KhJKTjo6Ni4mQioyTjo+RlYeSjI6TjouQgJCPkI2OkpCNkY2UjpKOjJCQj5OKi5aPj5GUjJGNjJKUjpCOj5COkJOOj5iTj4aNkY+VlI6TipKUhpSUjpWSkZaOlJKOkpmPkZOSmoySmpKUl5iQmJWRkZiVkpOPi4+SlZeVl5iclpmYl5eYmJySmpSTk5ealZeXn5WNmZOWmpacgJmVlJWVlpmSl5aQmZiSmJebl4+ZlZSUlJiXkY6dj5KVl5+Yn5yXlZiYlpSYmY+UnJybl5iRmpOYl5SclpaZmJiVlJaYkZmSl5GQkpaXmZeXlZibmJmblZKWl5iampybmZecmpqSl5aVmJ2fnZaUm5mampqbkZ2WmJeZmJiWl5SWgJSYj5eYmp2VlJyXlZyWm5WcmZqXlZiZl5uYl5iblZuZmJiSkpOWlJaZm5ybm5qZlZORk5OVmJPwlKCekZWdmZialpaYmJeUkJKTl5iZnJmVmJuZmJaYk4+Wl5OVmpmcm5eXmJeWm5uelZaZlZaZlJWbl5aZmZGVlpKVmZWZk5qVgJial5aXmJqalZeYmZqYn5yVmJmRmJaalZiVlZGYmpeamJeXlpSVlpyYmJaUmZSYmpiXlpiYmZuVn5qTlpWYmpiZnJaalJmXkpOcmJaWl5WWmZ2Ul56ak5STlpaam5KWmKCZlpuZl5WUmpuWlpOVkIX9j5yfhY6JkZyQkJSOjpOqgKSenJuUn5mVmY2al52XnZuUnJiRlY2OnJCWlJqZj5KRkJaXkpmRlYyDiI2HkpiXpqaSk5WWkZWPj46Ql5aZmJOdkZmTl5eYl5STkpGVlZ6RlpiUn5ablJeblpObl5aYmJeai5CSkJOVj5CQkIyYmY6QlZGTj5WUkZWPlIuVkpGcgJSTkpeNl5aXmY6LmJKOlJiZi5eOjoqLlZKSk5abk5yVkpWRl5WPkZGNlpWWlpacm5aSlJOQk5mUmpKQjZuLjJSNjI6Lh4SKhIWLgIuVlZSVj5mYm5mTl5STlZCUjpKWk4uOlJKSkI6NjIySk5CTkpGMjpSQkY2NkIyNi4yLiouFgIGCiomJi36Fgn58fnp5hH5/gH+EhoiFhIOBgYKFg46Ji4eBh4iBf4mCg4SJh42NiIiGhouKiIyMh4WKh4eDhYiBhoWIhIl/g3uBgIl/hIWJiYaCgoaCiIGCfoODh4OFhpCGiIF6goaFgYN/g4GDg4OEhH+BfoZ9g4KFhoWAhXyFgHqEe4SIf4J9goSDhIaAhoSHgYeEhYSEjoSEg4mJhn+Ag4eDfZh6dunqd3qJeoWLh4aMjouMhYWIfIN/fomGgoKBf319fPV/fX+BhoOIf3uKm4uLg4aDgXt7g3+LfoJ6g4WFhoeJh4WDeomKhoWHgoCHg4KKhYaIiXuIhIaJh4OHgIeGhIOFioeGiISLiIiEg4mIiImChIyHiIiJhYiGhYqMhIeJiYiHh4mFiI6HhXyBh4aIioWKg4mJfIuMhIuIhomEiouGiZCDiIiLk4WJkIuMiY6GjImGh4yKhoeFgYWIjYmJjY2SjY+Li4qLjI6GkImIh4qMiIqLlImDkYmMi4eMgI6Kh4uKjI6MjoqFj42LjYuRjoSMioyJjYyJhIORh4mMiZCNkY+Ki4yJiYeLjYSGjY+Qi46JjoqOj4uSi4uQkY2MioyKiY+Jj4mGiIyNkI+PkJGRjo+Qi4qOjY6RjI6Ni46Sj4+LkI6LjJKUkYyJjYqJi4yQhpCMjY6Qj5CNjYqLgIqOhY+Nj5KKjJCOjJGMjYiQko+Mi46QjY+Oi42OipKQkI2JhoiNjoqPj5OPkI+OjIuKjIuMi4jjiZWUiYuSjJCPiIuQkI6LiIyIjYyLj42IjY+LjIuRiYOOjYiLj5CUlI+Mj46LkpGWjIyQi4yOiouPi4mPjoiMiYeMjoqRiY2KgIuSjYyMkJCOio6NjpCNkY+Mj5GJjYyQjY+MjIeLkJGQj4uNjoqKi5eNkYyJkImOkIyLio6OkJCJkY6KjIiNko+NkouQiI2MiImPjoyNjYqLj4+Gi46MiYuIi4uMjImPipWNiZGOjouLjo+LiYmNh3zmfX6CeH5xeI2EhYiBhYiWaI6Pj4yLk4mMjYaQi46Ljo6IkI2Gi4WEj4eNiY+NhIiKiYuKh42GiIB6f4aCiI2Lk5KKiIqLhomHh4WGjIyQjoqTiZCIjJCMj4qNjIiMjJCFjY6Lk42QiYqPiYmOjI6PkYyNf4eIh4mKhIeAg4+RhomNi4qHjoyIjoaKgImIhpCHiIeJgYyLiYqBgo6JhIeKin6LhIKEgoqJi4aJkYmQiomMiI+LhoeHg4qKjIiMjo+JhYmJh4iMiY2Hg4KMgYSFg4OEhIN+g35/gXmCio+KiYKMiYyMiIqJi4uHioaIi4eCho6Gh4iFhIKDjYkUhYqHhn+EiIWHg4KEgoOBgoSDhoKAdHN2d3d5bHZ1cm5ubHB2cW1va3d1enZycXVuc3R1fHZ3d3B0cnFxeG9ydXh2e3h2cnF0eHh4e3xzdHp0dHN5eXN2eHVwdnF1a3Rud3B7dnN4eHFyenV4dHNudXR2dHJxfnR3cW1xeXlzc3B0cHF0dXV3bnBwdG5zcXN6c3B3bXCAaXhxd3pvdnF0dXJvdnF3dXhvdnVzdnN/dnV0enp1cXR0d3VsfGZj1NJranNpb3p3dHx/enh0dXhweXBtenZ2d3Rwc3Jw3ndzcnJ8c3hvaG56c3Z2dXVxb3F5d39yd255e3l5eH17e3lve316d3l1dHl3dXp8eHd6anl3d3t5dHmAeXl0dHh6ent8eH94dXN0enR2enZ4fnh6eXt4eHR5fX91enx3eXd1dnV3fHd0bnN2d3p5dXd0fnhteXx3eXd0dnd9e3d4fXR3eH6CdnZ/f352eHR7dnJzeHp3enhxdHh7eHx8fH+AfHt7fHl8gHaDd3V6e3h5enR6eHV+d3x8dXWAfHl3fnt7fH1+enN7f3x6f4J+dHt+fXx+d3d5c313e311fnt9ent6fH92dnh9eHl7foF6fnmAeoCAeoeDf4GBfXt5fXl7fnl+enh4fYCBf3+EgoSAgX9+fYGCfoB+fnt9f4R/f3uCf39/gICBf31+enh5fYF6gHl6fH+Agn1+en6Ae4B4fn17gnl8fX2AgH1+d3qBfn58gIKAgH58fn58g3+Cfnl1dH1+eYCBhH2Cfn19fn17fH1+ecp6iYZ/goR9gH93e4B+gX14gHl+fnyCfnt7gHt8eoJ8fn58fH2BgoSDf4GBfnuCgIWBf4B+fn59foJ/eH97en56eoB+fH52eHeAd4F+fnqAgH95e3l9f39+eXt9fHh9fYJ7fXp4cnd/hIKBe4F+enp7h31/fXh8eX5/fXp6foKAgXt/fHqBeHuAgXuCfHx7fXh3dnp7fX19e3l+e3R6e315eXd2dXh7dHZ2gnt4gXt6d3h5eHh6eHx2bMJjXWFlaV1gd3d0dnJ5c3KAb3l7eHd7cX97eX14eXd5fHZ/fXV8dnJ5eHt4e3l2eHp6eHp0eXRzb25xd3d4eXd1dXl5eHh2eHl4dnh6e4F7e4F3f3V4f3qAfX58eHx5enV8fnt+eX55enp1eH15e31+fHlseHd0c3N3eHh4dn+BeXt8e3t2fXt4fnh7cXd4dX+AdnR4d3B3enZ2cHF7enJ4eHlvd3RxdHJ4eH10eX55e3t7f3iBdnR5eHR6enpzd3t7eXJ2eHd1e3Z3dnRxe3B1cXFydHZ1dHRxcXZtcnd9eHZ1fXh5dXl7enh3c3p0dXdzdHh/dXZ4dXRycnlwdnt2eXR3d3N6d3FzcnNzc3dzd3j/f6h/gn6cfwF+/3//f/N/AX7/f7l/AX7/f/9/kX8CAgQAgJKZm5eRloz6+Pv49oeGivqGhomPiJSNlo6Wjo6Sl4uRlpCXlZiYnJmXkpCOkZKZmJCTlJmenpqYmpiVk5mjkI+LjoiIhouPkZONjISCkpeGiY+NjYySi5KOkoyOjIqPkpKPiIyJiYyIjIiFjI2Jh/2JiIr9ioqEho6Oio+JjY+TgIyOiYqHjo2Tk5CNjYiSlJKLh46VkJWSjImRl4uMkIyKjZGIlYjk94OHmJeK/ZCQj5CNlZGLjpeNk46Lj5eQi4X09oSDhoeIioSEh4SMlIqau6GVkpGRkJGIkoqNjIyNj4+NjYuMjIeKiJGNkJWUko6Nj4+NkY6Mj5OLjpOQk5CNVZCSlJSWlIuQk5CQj5OKkpKLk5GRio6Pj5SPjo+UkJCQj46Ri4aJjZWUkZiSjJSXjZGWlJCPlJOUlJiXj5WOkJSWmI+SkZWQlI+WlZSUl5mclpKSkZSFlRaNiI2PlZiTlp6VnZSZkJeTlKCUlZqUhJeAlJuVlJeZiJeRk5iclpOXjZiOj5eUjpuZk5eXmZaOkpeSkJaflZKbmJ6QnJugmpiXlZmcn5eOlpKWj5KMlpKhmYyXk5CNl5SMlZaSlZGVk5OZnZaXl5iRk5GXmpmVmJaWmJidlZeWm5ugnpmelZqWlZSWm5WYl5mbnJmZmZCZmph+n5uWmZiZkZCXnJqbkpWWl5Walpqbl5Wal5iYmJqZmpeVl5eXnJmamJGal5eUl5mUk5OXm5iampmclZWXlZibmpeNmpuSm5uUlJGYmJaclZuVmpacmJCXm5OSlpWalZaYl5WPlJiYm5ycmKOXl5aZmJeVl5mSmZSYl5yXlpibhJWAmJiUmpuZmJWZmZSVmZGalZ2Zl5KZlZeZnp+boJaVlZmZlZeYmpuYlZeanZWUlZOVmJuRkJaXlZeZmZqZkpSXkZyXoZyVlpuXmJiYnJ6ZkpOWk5OXlJiVlZmXk5KUmpiUlJGemJiUlZOXnZagkpaSmZqcmpuVmJeRkYX9jaGIhJCAjqWUlI+RjoeUhoepp5iblpyanJifnJyhmKibl5SZlJOQm5mSlZSZlpePmpSXj4yMkpKRiouMj5Chp5CQk5WXkpeXioWQkZuZlZGWlJeVmJadk5iUl5OZj5iXmZeXmpqTlZuQlpmXlZKbjJiRmZiTmJSVlpWMj5GQlYuPjJCRjZGAkJSTkpeNlJaQiJCSk5ORkI6VmZCPjI2NjIyMjoqSi4OJkZWTh46Kho6Ll5aQkY2Nk4qOiZCRjo+YkZGUlI6VlZWWlZSJk5+Yj5GLiI+KioONi4+Vj4qLkJqHopmSl5WWkJSXlJeRkJeUj5CQkZSVmpOOk5SSkJaUjouIjpKPkI8KjZSQjoqMiISDgoCKj4+NhIqE7evs7ep8fITpf36Dh4CKgouBiIaCh4p/hYqEjIqKipGNjIaFg4GJi4yGiImKj5KRkI6Nh4iMlISDg4aBg36BhYiIgoJ9eoOJfoKEhoGBh3+Hhoh9hIF/hIiLioGDgX2Bf4GAfoOEgH7sfX2A6oKCen2DhYKFeoCHi4CBg4F/fYSEjIiGhIN+iI6IgH2DioeJiIWDio6DhYqHgoWHf4l91Ol8f4yGfO+GhoOBg4iDgYSLhIqDgoKMiIR+4Ot8fICBgoN9fYB8g4d9iaCPiYqFiYeIgYmEhYSDhIaHiIOFhIeAg4CJhoeNjYiGg4iFhomGhYmJgoaJiIqHhICGiYmKj4uBhoiDhYeJgYqMg4qJioOHh4OJioaKioeGh4aEhoN/g4WLioqPioOIi4KHioiEhoeHiIeQjIiLh4eMi4yJiomKhIyJjoqJi42RkYuLiYeKjIeIi46CfIOHjIyGi5GKj4iQh42IipaHh4yKjImKioiQjYuNkHyPiIaMjoCJi4qDjoSBj4qBjo6Ii4mNjoOHjYeBi5CLiJKRlYWSjpKNi4yLjo2TjYSLhYeFioOGhpWQhI2JiIWOiYGLjImKhoqKiIyQjI2LjIiKiIuOko6Qjo+Qj5CJi42Rj5ORjZKMk46NjYqOioyLj4+TjZCOhoyPjZSOi46NjoWGjZCSkoCMjYuMjpKKjZCLiI6Njo2LkZCPi4qMjI2TjpWNg4qLj46Oj4mLi4qSjo6PipCKi42Mj5OUjYGMj4aQkYyJho2Ni5GLkYqQio+MhI6OiIiMjJGIjIyMjYaIj4yLj5GPmo6Li4yPio6MjoiNiJCLjouLjY2HiYuMjI6KkpKRj42SjoCMi4yFj4ySjYqIj4qNjpCSjpWNjouPjouJi46PjomLjJOLioeIi4yRhoiNjY2OjZCRjoiKjYiPjJSQiouPjY6Pj5KTkIeIiYaHiYmOjYySiYmIj4+Pi4yIk42MiYuJiY+JkYGKho+Oj4yOi42Mh4d65X2JdXN5fpOEiIeHg3p/eYB+l5WOjoqQkI+MkY6Mlo6ZjouKjIuKhZGNiIqKj4yMho2IioKBgoeGhoOEg4SEkJKFh4mKi4mNkIJ/iIaPj4yIjoqPi46NkYqOjI6KkIaMjY+NjY2QiY2QhouQkIyGjoKOh4yNh5CMjI2LhIaJiouChoOIiYaJh4yLiI2Ch4iBfoCDiYeJiImGiYuFhYKFhIKBgoR/hYN9gYiKiHyDf4CFgoyMh4OCg4iBhX+HhoSIi4WDiYiFiYqJioeHgoyPioWEgH+EgIB8hIOFioiBhIeNfpSNh4uKiYSGioqNh4aOioWGh4iJipCKhYqKioiMiYWEgYSHhYWIhomEhYGFf32AfYB+fX16dHl32dTW3NNub3XNbG1yc3J+cXdyeHhyeHlqdXl0enl1eHt7eHdzcHB8enV1cnR2d39/enZ1b3V5eXRwcnRzeG9zdHd0cHBta251bnNyd29vdnF1eXlwcW1wdXZ+fnZ2cW9xbnFzcXN0cnHWbW1w1HV0aXBydHR0bHB0eYBucnRyb3d1fHR4eXNvdnx2c3F3enR2d3V0enxzd3l1dHV2b3RnwNdwcHdya85zdnRzdnVwcXd3cXdzdHF5dnRv1tNwdHl2enl0cXF0cXNqbnl6eXt2e3d5eH56end1d3p6eHl6eXxzd3V6d3t9e3d/dX10e3t4eHt9dHd6fHx4doB3fHp6f3txd3p2eXZ5cXd9c3t7fHR2d3V3fHh9e3t5eHZ4d3Ryd3h6ent9eXR7fHB3eXd0eXd0eHZ/ent6cXl7e3p6eXp6dnt2fXh3eX6BgXp6dnV6enV0e39zbHd3e3l5fYJ8fXV6dnl4e4xydnt6enh4d35/d3l7em+BeXd+fYB4d3d5fHJ1fXV4fHt7fXh9gXd0fHh6gHx7dnx/hHaFfoB+eHt5fXeCgHZ8eHh6fnd4d4SCeYB9e3eAfXd7fH17enl9eHt/fnx6fHl8e36Bg4KBgYKBgoF9f36Af4F9hYF+f31/fnp9f3x8fn2CeYGBen58fYJ8en15f3l5f4CDgoB8fHp6fYF5fH55d399gYF7f4F9fHl8fXmBfoR8c3t+fn6Af3p9fHWAfn99eIF9fXx9hIOGfnd2f3mChH+AeX17fn9+hHqCfH1/d4F8en1/fn5+eXp5f3l9gH96fH9+hn9/fn6AfoF9gXV+coB8foB+fnt8e3x8en59goGAfn+FfYB7en14gn+Ef3p2fXp9gH1+fIR9fHx/gHt5enl7gnd9gIN7eXR1e3t9enZ8f3t9fYCBfnd6fHmFe4F+fHt9en1/gISDfXl4e3d6e3p9e32Denx2e3x9fH14fXZ2ent3d3p1eW98cn18eHZ6fH16dndpxWlqXmJjZ3d1c3V3cmVkZBhvfXl6fXh/e355fXt6hIGKe3t4eHp9dX2EeYB+e3p7f3Z3cHBxeHZ3d3dzc3N1cnZ2d3d5eX1/dXJ6dXt+fnl+e354e319en97fXqBd3h8fHt8fH14fHx3eH9+e3V4cHt0cnp1fHt5fHp4eHd5fXJ1c3l6d3t3e3p1enB1c25xcnVzend9eHZ2d3RzdXVxb293cHN1cXJ2dnVpcWRvcHV1fYB7dnB2enR5cHp1dHN4dXd6d3R5eXd8cnR3eXl5b3NxcHV1dXN0dnZ6eHN3d3lte3Z0dnZ2cXN5eHh3dnx2dXR2eHd1fHl3d3R2eHx4dnl1dXl4eHh2eHR9cnZwc3V0h3+FfgR/f39+338Ffn9/f36xf4J+hX8BfpN/gn7/f/9//3//f7J/AX7/f/9/kX8CAgQAgJmfl42Vl/fmgf2CiIT6g4iFgYaJjZKOko3/hIyWl6GXlZeWkJCQlIyOmJSUmY6SkI6Vl42ak5GOlpaXkpWZp5SJhoqJjIqVkpCPj4+KgYT4/4T59I2RkI+JioyPkJKJho2Pk46EjpCUjIuGjo2GjIiIiIyEhYmEhIyFi5CMi5COgIqFjo2MjpGVjI+ThoeJjo2QipOTiYuKio+Nj4SMio+Uho+ThYCBiYuTi4SIgIGOj5GLl5COj5CTkJGPlZORj4uJg/2EhoeMjY2RlpuOmomVvp2XlJSRjoyIjo2Ki4ySjYyHioyNjoyQiYeOjIuKjI2PkYyRjouSjYyPkY+QjJKTgIyUl5OXlIyOj4qTkI6LkI+MlJKSkIiIkpGSj5GUjYyUj4+SjY6Oi5KQj5CQjImLk5GSk5OUk5OSk5KVj5WWk5yXk46UkZSYlpGSlZOTlJKOjpWZkZCTlpeQk5KWlY6Tn56TkZGTlJeXlJaMkJGYlYyVkpSVnZuYlo6UmZKbnpqXcpidl5SJmJKYjJOVlZqdnJOXkJiXmpeZmJyVlZ2WnpuYlpiTlJmcmpmVmJeUnZOVmJeYk5mUmJuXk5uUjZCXlZGQjJSYl5ibl5KYkZOUmJqWmZuUlZabnJ6Zl5qZnpqanJyZlpiWmJmWm5iVmp2Ymp6ZloSXgJOWj5iVmJiYmpaUlpmRlZyXnJegmJmRl5mckZeVm5iWm5qWlpWWl5eXmpmel5Sal5iYm5aWl52bnJqZl5ucm5ygnJWYlpOTl5aXl5yWlpaXlpebmZiYk56SlpKSmJmYkpmXmJuWlpWWm5ablpqOkpqVl5SZlZuXm5acmpuYmJmZgJualZyamJ6blZyUl5yWoJmclJabmZaZkpiZnZyXlJGeoJaYlJ6UmpefmJyek5WUnpmVlY+TlZWYkZqVlpKTlpackpyRl5mXkZSVlJedn5+ckZiRm5WVlpmXmJWWlpSVlJiUmpqVl5KZmo+WnpWZk5ScmJeamJqamZeYjoyOko+VgJecmJSWlpeNlpiJh5qRp5mgmpygnpqYnqSinJuamJaVnJmQl5GRl5aQmJyQlZ2OgZKUj4mPio6koI6MjZeZk4ePj4iIi5qYl5aUlZaVlJeUl5aVmJiUlZKYkpSYlZKZn5ugkZeYlpaOl5uVnpKNk5aWk5ecl5ORl5GUk5OUkJCRgIqMkZeQkZORkZCMjoyFjImflJaMlJuPkZKRlJqKkouLiIyGgf+C+/+B/oOPkZCLj4mOkpOLjZKOioaOi46QkpCWjpCak4+flpefnJGSk5CKjYyIjYeOi4+Wg5CilJSRl5aZlpmYlZGdnZqWlJGDjJWSlpSRk42Vk6CRko6HiIWRCo6Nk46Una2Wh5aAj5SJgYiL5d979H6Efel9fn96goKChoSHg/J6gIuHkYyKjYyEhIiJg4GLiomMg4mFhIqJg42JiISOioiEhoqThXx7g4KGg42HhYWHhoJ4fvH3fe3ogoWFhoGAgISFiYCAhISGhX6Dhoh/gX2Eg3uAfoB+gX59f3x8g3+AhIKBg4SAf3uEgYKFhoqDhol8fn+ChIOAiYl/g4ODioSDfIWDhYl/hYl7eHmCgYh+eYB6eYWFh4KLhoSIhYqFhYaKiYeHh4F6736BgIWFhYiMj4WNfYanjoyJiYaEg4GGg4CEhY2GhoCChIWEhoqBgYaEhYeFhomJg4mEg4qFhoaJh4WChog1g4mMiIyKg4WFgoqJiYOGhYSLi4qJgICIiYyGh4uCgYqGhYmFh4aDiIqIhYaCg4OJg4eLiIuFiYCGhIqOh5CQiYKNh4mMjoiLiYmIiIeFhYmNiouJiouFh4iMioOLkJGJh4eHi4yIhoqFiIaNi4WKh4WIjo6PjYOLjYaNj4+Lj5WQiXyRhomDiYiNj5CRiY6IiYqOjY2KjomKkIqTkY6MjYmJkI6OjIePioSPiYqNi42GkIyOkI+Ji4CHh4mQjIiFgo2Qjo6PjYuPiYqLj5KPkZKMjY2RkZSQj5GPlJGPk5OMjI6NjI6Lk46NkJSLjZOOiYyNjYuHjIOLioyQi5CKioyRiYuRipGLk4+PiI2PkoiNi4+NiY+RjIuPj5COjZCQko6OkI2OjJCIj42TkY6Rj46SkZGRlY+LjYCLiImNi42LkI2Ni42Mi5GLjY6Jl4uOjIaNjJCKjo+Rko6NiYuOjZGPkYeLk4yOi5GLkY6RjI+MkI+MjZCQkIySkY6RkIqRiYyRjJaPko+OkYyKkIWKjJOQjYiHk5OMjoeRiY+KkoiNkIyMjJKOiouHiYiLj4eRi42JioqKkYiNhICOjo2Gi4yMj5KUkpCIjYeOi4yRkIuKi4uPioqKj4qOj4yMiZCMhIqQiI+Hio6Li4+NjYuQiYmFfn6FfYWJiouLioiLg4iHfXuHhJWLlZCQk5KPi5GWk46PjYqKiZGOh4yFh4yLhoyOhIeShHiIi4eAhYOElIuBgYKJjYd9hoiDhYCEj4+NjIuNjI2Kj4uMi4yOjoyMio+HiI6KiI6Tk5WJjY6NjYeNjomQiISLjo6LjZKNioqNio2LiomJiIiCgoaNhoaGgoKFgoWDfISAkomKgomPhYaFhoePgISAgX+Fenjxe+34evR/hoaFgYeAhYeIgYSHhIF+hYGFhIqJiYSEjEaFhoyHjJCLhYiHiIWEhYCFgIWDiIl5hZGIiIaMioyLj42LhZCNiIyKiHmAiYaKiIiKhImJkoSHgn+AfYiGhYiCiI+ch36JgH1+dXB1eczMb+NwdnTTcHBwbnZ2cnRzeXvabW93cYJ8eXp5c3N5dnJxeXl1enZ8eHRzdnF5eHZweHp0b3Z2eXJvb3NzdHZ4d3F1eXdzaW/Y3m/d0nR4dnlycHBzcnZ1cXZ1c3ZvdXh0b3Rwc3NxcnBvbnBwcm9vcHNycnV0dG5zgHBvb29wdHl5dHh4cXFxdXZydH1+cHVyc3t4c3F3d3Z3dHV4bmtsc29waGZvbW51c3h0fnV0enh0dHd5enp4e3l0bNJyd3Z4eXh6fH91enFxgXl6eXh3d3dzeHp0d3iAenl0d3p7eHh7dnZ5dXN6eHh8enZ6dXZ8eXl8d3h2dHl7gHZ8gH1+eHV3d3d4enp0d3dzfHl6eXZ0ent/e3h5cHF7dHl6d3l2d3h7eXd2b3NzdnF0eHh8dXl5eHp2dXZ+dISDeHN7ent3gHl7eXd2d3d0c3Z3eXp3enZ2eXh9enV+gIB5en13fnt2eXt1enZ7e3d/eXx2eXx8enN1eXB6e3x5gICDf3Zsf3Z3dHt6en9+f3V7enV4fH58d35+fX96f4B+fn9/fn53eoB6fXhzfHh7enh/d319fYCAfHVzen2Cgn14d4CBf3x/fn6BfHt9gIGBgoJ/gH6BgIWBhYF+f3uBg4N8gIJ9fnx9h3x+gIZ4e4OAeXp7fHt5enZ8eHt9fIF6SXmAfnd7f3d/eoB/gHx/f4F5fHd9eHt7gH5+f36EgX5/fYF/gH99fn+DeoN+gYN7gH6AgX6CgoR/fX5/fXt9en59f39+fXt9fn6EfICHfoB9e39/fn1/gYCAfHx3fX55fn59enuIfH58g3x/gIF8f3yBe4F7gIJ+f398e39+eH51e357g32BgH6Bd3l/eXl2gnx5enqEgH9/eIN5f3t+eH+Cent8fX16e3h6eXeCdnx7gHl6dneAeXhxfX57dXt9fn6Dg4CAeX93fnl+f4B/dnZ7en14d3l+fHuBent5fHpzdXpyfXR3d3V3e3p8fHt3dXNrbG9rcnZweHd2d3Vycm9pZWtzeneCgHh/gHx5e4KAfnx6eHd4f3t3enV5e3p2dXlzdIJ1a3d5d292eHd7cG1ycnd5dnF6eHZ4d318fX18fX19eH56fHt+gH59fIB7e3R2eXZ3fX+Bhnp5fX14eXp6d3t2dXl8f31+f3t4enx7f3x7eHl5eXVycXl0dnRtb3RzdnRvdXB3dndzdHp1dnJzdX1xcXFvcXdrbdVt2+Ft4nZ4dHJxdXF3d3p1eXp0dHN3c3dzen14dnR4cXR2cnp7fHZ3d3h3cXdwcnB1dzN8emxwd3F1dn15enp/ent2endyeHZ4bXF4dHZ4e3t3end6dHhxcnFteXt5enR0eoNyb3iGfwh+fn9+f39/fot/AX61fwV+fn9+fu5/AX7/f/9//3//f/9/3H8Gfn9+fn9+4H8CAgQAgKCflpKUl4L1g4GAg4yJh4mJh4KQhYiMkqWFi52Yko+QmJOYkpmWkJGPlJWUkpCOi5SMkJqPipmVjZGRjY+al6KbjI2Ui4WJlJWTlYyA/fjngIKChIj9+oKNiYyFio2GhZGNjIeUj4uOioaIj4iLjI2IjIOGio+Nlo+GhIGAiIePgI+Ij4qDgpaTkYiOjoyLi4+Qjo2Pi5CNjYaNlJmXk46IiIaNk46LiI+eh+nv84qHkJGNj46RmJOalZuRl5eRkImOhYWHioyKhY+Qk5WXm5SawpyYk5KSipCOjo2QiYmHiZGLj46FjZCJi5CNjouKj4uPjpGRi4uLlJaRjo+Rj5ONEY+PjZOPkZCKko+UjpaRiIuRhI5KkI2Pjo+IkoqOjY2Lio2Pj42JjpCQkYyPiJCPi5aTlJGQkpSRko+Lj5eVl4yZjo6Yk5aZlo2Pl46UlZeSlpaVlZuajI+UkpGZkpKElYCUk5iXmpaZmJaWkJaSmpaOkZucnJaVmI+Qmpybn5CZmZmcjZeRk5mWkZeVlpmck5SXj5eVmpeYnpqYlJSYmJOVmpqZnJeSmJmblJaPlpealpONlJGRlJOUjpORmamYmJmXkZeTlJGTlpqYm5yZmpeWnJ2doJmZl5mYmZqanZqTl4CgnpmWl5aglZealZuZlZWampmWlJaalJWYl5iRlpaVmZiVm5WZk5mYmZuWl5iWmZyamJqelJiUl5qXmZmZlZSdoJqZl5ybmpiZm5aYlZqVmJKgmZqZl5yWl5eYkZKanJSZmZWXlpaVnJ+an5WTlpabmZ2flJWRmZmZm5WblJyZnICSlpeXmJiWlpqQk5uUlJeXmZeXl5qcopuXl5ecm5+XlZaVmJWWl5mZmJqXmZyZk5iXlZiZmJaWlZWTjpmJlpiempiVl5mTlJSVlI+RkZiSko+Tl4+PkI6Uk5mWmpWSkpialJWYmZaXloyVmZWUlZuWlJuXj5yWm5uZlpSRkJiimYCZl5iZkJiRl5ydl5WPk5aTlZialZiVjJmblZKMnZuQiJKKiZmak5Wdn42WmJaZmKKTlJKWpJKampqRo6STmI6Lk5eTlY2Yj42PoJ+OlpaNmImQkYmPj4WJkpqUlpWTmJmXmJ2XmJiXmZKUmI+YlZWWl5mWlpmUlZSXkYuZk4+SkYCRkpWNj4uOj46LkZWVkZaUjZSXlpCSj4uVk4qLj5KMk5WSjpCRiIuKmomRkY2Rh5CPjouJjYaCg4mD/YD7/4CRjI+UlIaXhYiElZOXjo2NjZKPjZOSkJmRj5ucl6GXjYyNioyLjo6RlJCT9YaM7ayVkZaalJWTl5eRjJGIgYqRjR2Wl5eQj5KSkZWUko+Yko6LjpCRjI+TlZ2epayRqoCSkoqGiId26Xp7en2CgIGDgX19i35/gYaSe4CSjYWChZCIjYaIiISFhoqIjImGgoCGgoSLhYCNiYGEgoSEj4yRjH6Eh4F+gouIh4uAevDu2nl8eYF+7e59g4CFfX6GfH6HhIJ+jIWAgn98gIV+gYKDfYB5fH+DhImEfXp5eH9+goCDgYeBe3mLhod+homCgX+DhoOFhYKJhYWChYqOkIiFgIB/g4iCg36Cj3va3+aBfIWEhIaGiY6HjYqQhYuMhIWCiIB9foGCgXyDiIeJjI6JiaeMjIqHi4OGhoaFh4KDgX+HhIeGf4SJgYOHhoWFhIqDiIiHh4KDg4qJhIaEh4SIhYCGhoWKhoeHgomDioePiYB+h4WFhoiKhYeDhICKgYOFh4SAhYiJhYGHioeEgoZ/hoWAjIiJi4eJjYmIhYKDiomLgY+FhYyIjIyNg4SLhIyOkoiLi4qLkI+BhIqGiI2EhoaLjI6LiI6Mjo2RjYuLhouIlYyEhY6PkYuMj4eHjYuQk4CHjY6KkIWOh4iMi4eQiYqKkIiHj4eJiY6IjZOPjYqHjIuHiY6NjpOKhIuMj4mKgouLjo+OhYiFiIiJi4aKho6nkZGRkoqPioyKi4yQkJKSkY+PkJOVlJaPj4yMjY6QjpKNjoyUlZGKjomUio2QiY+OjIaQj46MjI2PiIiLi4yGj4CMjI6Ni5CMkIiOjo2PioyPjY+PkI2OkImOiYuQjpKRkIuLlJSQj4yPjJCRj46Mj4+QjI6GkZGSj42SjIqNkYmJkZKLj5GMkI2OiI2UjZOOiIyMj5CSlIyPiI6QjZKKjoqTkZGIjY2OkY+Pi46IipCLiI2PjoqPjJCTlJOOjZKQkICUjYuLi5GMjo2Pjo6Qjo+SjoiQjY2Ojo2NjIqLh32Lg4yLkY+NjI2Li42MiYqFiYmOh4uIi42GiISEjYeMjJGLhoSLkIiKjo6Ljo6GjI6Lh4qSjYqPjIGRiY+PjoqGhIeQlI2Ni4uLhIqHjY6PioqEioqIiomIiIuJg4qNiYmEjYCNf3uDe32Ji4WHkJB+h4eJjoyTiIqHi5eIj46PhpWRhoqBfoiOiYuEjIeEg42MhY6LhIuAhoeAh4eAg4iRi4yMiY6Qj5CUjo2OjI+JjI2HjI2Mjo6OiI2Pi4qLi4mDkYmFh4eHio+FhoKGiIeDioyLiY2LhouMj4iJhYGHhX+BhYCIgYiLiYaGhH+CgY5/hYOBhX+Jh4OBf4B+fX6FgPR78/R5iYKEiYt+i31+fYqHi4ODhYWHg4SGiIONhoSNi4mOhoCDhIOFgoWFhYiFhuR+gNqbiIaKj4iIio6OiYSIfXZ/h4OLjIyEhYWFh4iJiIaMioKBhIWGgoWFiJCSmpmAmYB/fnlzdXJq0WtvcXFzcndzdW9yfHJvcnN+bm59fHd2c3x3fXZ5e3ZzdHp4e3ZvcnFzc3J1cG98dm1ub3J0eXh4dGxucHJwcXt3dHx1b9zex3BxaHRw0ddwdXB0bmt3bm53dHZxfHNvcnBudHRscHNya2tpbG5xdnNzcGpsam5xcYBycnl1bHB6cnlwd3t1dXJ1eHN5eHR5dnl3d3t9fnh1cXJwdHp0dm1yc2O3ws5za3J2d3Vxdnx3eXuAd3l4dHdyenlzcXR1dnB0e3l5ent5d4F6fHh2fHR3d3d5enl5dXV9e3h6dHZ7dnd5d3dzdHl2fnt6end4eHt6d3p3enZ6doB6eHh6eXl4dHlyeHiAdnNtdnV2d3l+eHlzeXV7b3V5eHFxeXZ4dnN2d3d0d3ZzdHNye3p7eXZ4fnp6d3J5eHh+c35zeHx4enV+dXd8c3t/g3d6fXV/en11dnp1ent4dnZ+fnt+eX17fXh9f3x6dHp5iXt2eHx6fXl7fHlvbXKAfoB0eoB5fXd7eXZ4enp7d3p1fXZ2fXV5fHt7f4V+fHZ4eHhzeIF9eoB5eH2AgXp4c358gIGAdXl4fHx/gH18eICfg4OEhX6Dfn19fn2BgoOEgYB/gIaGhYiBgHx6fHx9foB7e3qFg395f3iFfXyBenx7e3OBfIB7eHt+fHx7e3x7goB7fXt/e4B/gXh9gH99e319fIF/f399f3t/eoCCfn9/f3l5hoOBfnuBf4OCgH5+fYGBfHx2f4OBfX2FfX59gnx3hYWAgIN+goB+enyEfIF8e3p/g4SChX5+eX9/fYKBfX6DhoJ4eX57gIF9eX16fIB8e359fnl+e36Af39+gYJ+fICAe3x6fIB7gn58fYB+gICDhHd/en1+enyBfHt6enV1c3x6gnt+fH99enx5eHt4enl9eX15e351e3R4fHp+f4R/dXR5fnx7f3x6f353e3p2dnmBent9fHJ/eXl9end2dXR9gXp5eXV1dHh5enl6eHhwe3t2enVxd3Z2eXN6d3Z2eIB1bGpuZ2xxdHRzfXpsdXN2fXx7d3l2doF2fn1+dn54c3dwcHh8eXlzd3Z3cnVzdXt4dnh0dXdxd3h1dniBfH56e32CgH6AfHl9fXx8fnl3eH96fHp8dXp9enl5eHl2fXp0d3h3eHx3d3N6eXh1enx5enx4d3p7fXd4dHRyb29ydYB5cHZ5eXV0cXB0cnhvdW9tdHJ9d3RxcG9wcXN3dOZw3uBueXR4eXludG9xcXZ0d3NzdXd1b3Zxd3F4dHF4fXl5dXB0c3F4cnh2cHN0d89vbbt6cXF0fHd2e35+e3V7cmlyd3N8enlxdnh0dnx4dXl4enZyeHd2dHZ0eICBhH5tgId/AX7Ff4N+hX+CftN/g37/f/9//3//f/9/9X8Efn9+fqx/BH5/f36vfwICBACAlY2crYi0mIL9hIb/hI+JjJGNhYaFkJGUlpaako+Mi4qRjI+Sj4+SlZKWiYmOk5CNjI+NkJOMlY2KlZKVlo+VjIyjmYyOjo+PhoiKioaD/eb194L+9/7884WHjoSLhY2PioGIkoeJiIqKj4uHiYiLhouLj4aHi4yFioeJgYqNkIuAjYmLjI2Qk46MkZWRjJCOi4KRh46IjY6MjoqQkIiKi4yFiZCSiZONie/8h/77g4+FiYyTkYuQlIuQlJWWj5KPjouH/IGFjI6QkI+RlpWMjJfAmZ2RlIuGkYeNi42MgYWLi5CMkImPjpGSko2NjY6JjIuMkY2LjYuRjo+Njo+Pj5CAjoyYkJSPi5KPh4mMkIiMkJSOh5KKkoyQlI2Mi46PkpCPj5KOjo6Kk4uSk5aWkZCNkY+PjZOQkpSPlJWTjY2TjZCXk5uOlpCWkpWOl4+Uj5OUk5WakZCPjpiUkpGWmpCKjZCWk5mXlpafnqKRl5WSlZiXlJCTkZeOlpmXmZ2WmZWAkpWXlZKWmJeUlZmMkJuZmpaQl5aThJmXkZiYm5eYlp2dl5yNlJmTk5yakJWXlJWYmZOUkIyOlpaXl5OQko6VmJOPlpWVkZWRkJiZnZeZlpmanZmbl5qcnZGYmpqZnJmamZeal5uampaQl5SWlpealJaUmpqXnJOXm5iamZial5KAmZaZl5iWlZiWm5WYlZmRlZaWmpmYmZaZmZeZlpabm5eVnJeZmZiZmZKZlpaZnpmbnZqan5qTk5aclJmXlJWTmp2ZmJmWlJWVnJmTlJqcmpeblpSVk5SVkZuSmJKUkJmYo5GZmJKUl5WVlZeVl5qXmJWZlpeUmpqTlZiZnZafmo2AlZiUm5malpiWm5+ZnJaUlZWVk5iVm5SZlJiTmpeXkZWQlp6amZSTlY+Xl5aVlI6SnJWSlJaVkpiYkpaPk5abk5qWmI+QkpaWmpqTnJ2VlJSalZeXnJORlJWamJmXm5WbmZyXm56WlJaclpqYl5WZnZWUlJGXkY+Sm4ygkpidk5mAlYuOhouOgoqQjIqa/YaD9JaTjo6Ti5qamJWZmqSTjpaZl5WTkZaWjpKPl5mem5KVkYyUkoyVi4qLioiTkpOQmJGWlZeWjpOWl5SUkpiWlZeTlpWMk5ORlpiRkpWQlpSXmpOOlpOVkZSTk5KNkpKQko6Rl5ONlY2PjZSXm5SRmZWAk4mPi4mJgpGVlY2LjZCNhZGJio2RkYiFkYmKjIiKiYWD9YaSl5eQkpmOiI6LjI+Lj4qIkY+QlY+On5ORl5CNkY+MjJuOh4yM/Y/4hJSAnIKKmpGVkZCPh4yPl4yRlJaIjo6SjI+Mj4+Yl5KNkZGMkZGPioeMjpCWmpmdjIyJhoyAioSPnHmbhXjzf3/xeYOAgoiGen5/h4aJiYuNh4iCgYOIhIOEhYWJi4mHfoGFhoCBgoOChYmDiIKAhoiKiISLgX+OiICEhoSHfX6ChIF88ODs8Hv07/Lz6H57h3mCe3+FgXl/hn2Cf3+AhH59f36CfYOCiXx7f4N+hICFeoCGh39HhIGDhoWIiYSAiY+HhYqIg3mGf4WAgYOEiIKGhoCCgoV/fomLgomDgebsfejufYV9gYSNiYWJiYCGhYWIhYmGiISA8nt+hIaEiICNi4KCh6iNkYiJhn6KgoeEhIZ6goeGiISJhIWGiYiJhIaGhoSGhYSKhoKDhIiDiIaEiImDiIWEjYeJhYOGhXt/g4eAhISIhYGIgIiCiIiEhYCChIiHhoWLhYWGhIyDioiLkIqFhIiGh4SJh4eJhYmLh4ODiYSHiImQhI2Ji4SIg4COh4qIjIqGio6HhIaDjoiJiIuQiIOHiIuJjIyJjJOQk4ePjISLko2IgoiHioOKjIuNk4qLiIWKiomIjY2LjIyLfYaQjI2IgYqLiHeLjIeNjI6OjYuRj4ySgoaNiYeSj4aMkImJj5CGjIaEhYuMi4yKiYuFjJGMiY+OjomMiYeOkICQj5GPkY+UkZSPkpKRg4+Qj42QjI+NjpCLk5CQjIaNiYqKjZCNjIyRj46Rio6PjZCNjZCOhY+JjYyMjYyMjI+JjYyPiI2LjI+Pj5CMj4+NjIqIkIuMjJCJjpGNkJCIj4yRjpGPkJSOjpKOi4eMkoiQjoqNio6Qjo2MjIiKjJGMiICJkI+Ojo+LiI6Ji4qHjYiMh4qFjoyThYyIiIqNi4yJjYuNkI2Qio6MjYqNkYqOjo6SjJSPgomPjJCPkIqPj5CVkZSMiIuPi4qNjZOKkYqLh5GPi46Mho6XlJCMi4uFjo+QioeGiI+IiImKiYaNi4uNiImMkoqMiY6GiIaIiYyNiA+QkYuKiI6KjouPhoOHipCEjICGj5KRipGQi4eKkoqNi4yIjZWIhoiFh4WChY6CjoiLi4OHg35+eH1/dHyCfXyI6Ht34omGf4GHf42NjYmNjZWBgomNiomLhImLhYeEi4mMi4eLiIOHiYSLg4OEg4OIiYuIjYmOjY+LhomKjIqKiI2MjI2KjIqAiIeIio2GhoiEjICLjY+JhYqJi4iLiYuNh4eIh4mGh4yJhI2Eh4SIiIqJhY6Lh4CFg4J+e4aGi4R+hIWDfYZ7gYOGh359hoKCgoGDgX5974CLkIuEiY2EgoWCg4aBh4GAh4WFiImFjoWFioWChIV/gY6DgYaC8IbtfYd4j3mFjYeKh4iIgYWFi4SHjCKLfoOFiYWFgYKEiYmHgoeHgomGiISAhYeIjY6OkYWFg36AgHlxeX5jem9q33N022xxcnV6dW5zdHh0dXd1eXZ6dHB3e3dwdnN1eXN4dW9zdHVwb3RycXJ6cXhxcW9ycnRyeXJudnBud3ZydnBxdndzbtjM1t1x4dvX19BraHZpcmxtc3JpcnVwc3JvcXRtb29vdHB0dX5ubG92b3VzeW1xeXlxgHdzdHx1eHd1cXqBdHl4eHVodHN4dHV1eHl0eHt2dnN1c3B4enB4cGvMzGzJ03BzcnZ0e3V2eHpxdnh3d3V0dnp5c99vcnl6enZ4eHl6dHNzgHiAfHl5dYB6eHZ4eHF3e3l8eXp5dXl9e3t3e3p6d3h5eH17dnl5enR5d3Z5eXR4gHZ0e3d9eHV4eG5wdXhxdnJ4eXV4c3h3e3p1d3N0dXl4dXR6eHh6dn11fXh5fnt1dHR2eHR5dXd4dXl6dXV0enh7dHuAdXl8d3h7eoJ2enh9d3Z4fXh2enh/dnp2c357eXl8enp7e3l9goCCd358d36CeHl1end6cHZ7fnt9eHx7gHd3e3l8gX98e3R4cnd8eXp2c3d9eW54e3t8dnx9fHd+ent9d3N6fXiBenZ3f3p7gX14f3h4en99foF9fn12gYJ+fYF9fnp+enuBgn+Cg4OCgIWChHyFhYF3gX6Cfn9+gX+BgHuBf4F/eHx7eXl7fnl7en9/gX93fX56gH99fX93gH14fX59fXx9e315e31/dnx5eX6Af357f4B+e4B3fXx+e395fIOAgIB9f32DfoCBfoOAfoB+fXt+gXl+gnp/fIF+eoF9fXh+fIB9fHx9fH58gH17gH5+fHeBe4B7f3uCgYl4e3h6fXx6fHp5fHyAgIl6gHp4en6Bdn5+gIR+gnpzgHmCeICBf3uCgH+EgYN+eHt/fnh8fIKAg3x5dn59ent7fX+Hg3t5e3p3fX6AfHd3eXt3eXp6eXeAfX18d3uAgnt4eH53eHh6d3l8en19eHx4eHeAfHx3dHd4fHt6eXt3gYB+eH5+eXd5fXh5eXt5fH91dHlzcnRvbndycnR0cW9vgGtsbGRmaWNobGlscc9pacp3c2tvdnF8fXx3e3l/bHF3e3R3fHN5fXd3dHl1dnh2eHd1dnl2enZ1dnl4eXp7dnp5gX1+eHZ4eXx9enl7e39/fH12b3d3dXl6dnJ0cnp5e357dXt6e3t8eXp9eXh7eXl0dX97dn10dnJ3dHR4c3x3gHRudXNzam93cnl0bHVydHF4bXN1dHJucnd2eHN0dnBycNxzenx4cnZ6dHh7dXV5c3p3dXZzdXR6d3h0c3h1dXBxbW56dHN3ctR02nN0anlqeHx2eHV3dXR3dnl2eXx5bnN0fHd0cHNzeXh3c3d1c3hye3p3dXZ3eXp5fnZ4d3RziH8Efn9/fsJ/hH4Bf4V+0H8Ffn5/fn6VfwF+/3//f/9//3/NfwR+f39+/3+SfwF+pn8Dfn9+tX8CAgQAgJSTlJOPqq+LjYOPiIiIlJGKh5GMio+QjZOJjYiIkY+Qjo+ZjoyIi5CNh4WH/Pr7hIeJhomMlI6PkIeWhoqVlYyQlZ+ylo6LipGRg/3z/YKE+/fv/4eD9PeC9YCEh4iJjISFhYiDgYWEioqMhIeMjoiKg4uLjo+Ej4yIi42Mjo+QgJWNkI+UkI2Oj4+OjYeUjIyNjIqOioiOh46Ok4+MjZWWhY+Cg4yLjomKi4eCgIaMj4aQkI6Wj5KQkpOJj5KQlo6NjpOGhoCE//GOio2Si4+cvaqWiY+Iko2DjIuIiYmIjI+RjIyOk5OMiI6NjY6Mk42LkZCMjIyNko2UjY6Ui5CPgJKQl4eIj5WJkJSPjpGRk5STkJGSio2QjI6Ok42Lj4uPk5CPjImQj46TjpGSj5KQipGNlJKRjZGVj5aZmZKSlZeRlpKLkpKNkZCXi5eTko+Vlo2WjZeQj5KTlJeRl5ySkYiNjo2bl5iQl5yYl4qRlZSWkJCalZGWkY+XjZGTk5qQgJuioZGZmZeQlpGbnJicmZeVkZqfm5WhjZWei5uWmZqXlZ2anZual5aWmJeclJiSl5OXk42IkJGTkpONiYGNjImClI2QkJCYl5eYk5WUl5qZnZyenJWUmZuam52anJuYmZ2cmpmXip6YlJaWmpmSkpqYl5eUkZOYl5WampaTm5aYgJWclpmWlpiamJeal5SUjJeVlI2UmpiYm5SSmpmVlpaUm5iWmZWZmZSXm5eWmJKemJ+amJmWmZSOl5ebkJiYkZGRlJCOl5mXmpqTjZiTl5qYnpeSlo+JkZuYnZOWkpOYlJuPkJiZmp2XlpOclZefnJeYlZWYlZmcnJmYmJqhmZyUgJWUlpWYlpWjmYmZl5Cfm5CSlp2ampyTlJGXlpeWm5eUlpKbmZqWkpiTmpOSkJWXlYiYmZKMlJGVlJmOlpSQmJiUnJKQlZuVmZeYmJaYkpSWmJWVmJSampWak5KSlJuYkZWblJOhlpaZmZaYnJuZlpCQjpWTi5eVkJugl5GbmZeYgJKXlYeYmI6Rk5uPlYeHjPWKjZmZmpeRlZGYmqCmlJCWlpOSlJWRipWRj6CwopGVk4+LkIuNlIyNkoeHk4+WkpSUj5aOlJSVlpmamZWZmJaWmJeXmJKQmpaRlJiYmpqXkpGQlY6RlZGPkJSSkY2Rk4+QjYmPkZCVjo2UoJmNlY2PgIyRio6Nj4yMko+PkpaUkZKampCZj4+KkZCVkJGKj4qIgvmEj5iPjImPh4iHioqIiJGUiZGLjJCIjZiHiY6ZkpKVj42Vj4yFiIiUkZCYhKOek4qHjY2SjIyEhv6GhI+PkpOKj4uPm4+PkIyUkZCQlIyRhoWPiJWSjo2MjYmJi4qSgImHioeCk5J9hHuFgYKBiId+fomGhoiFhIqDhX6BiYaGhYSMfn9+gIaCfXuB8ejpfH59fYKFiIWEhH6LfX6KioOHioyahIJ/gIaDe+/k7Xp+7/Dn94J96et97Hp7e3+ChH17fYB7d359g4GDfXyBgn6BeYOCgoN+hoOAg4OEhYWGgIyEhYaJiYSFiISFhIGKgoWDg3+HgYCFgIaFi4iFhYqLfYV4eoKBg4GCgnx6e32ChHyGh4KKg4aHh4Z+hoeHioOCh4p/gXt+8eqHhISIg4WKppaIgoZ/iYV8g4aDgIF+g4iHhIaGiYqEgoiFhoaCioWChoeBg4SHjIeNhYSLhYmGgIqIjICAh4t+iouGhImJiIiHhYiJgYSHhYOFiYWFhIGIioeHgoCGh4WKh4eHhoqFfIeFiIWIgYaLhoiMjYiHjI2HiIiDh4eEgIaNf4yKh4eKjISMg4qFhIeLjI+Hh42GiYmGhYWQjI2Fi4+Mi4CHiouKiISIhoaJgoKMhISIhomDgIyUloeSjIqFioiOjIyNjIqIgY6UjoiWgYqRgoyOjoyJiZKOj5CSjomJi4qPi4yGjoyMi4aBhYaHiY2FhXyFhoJ6iYSHiIeRjo6Ri4yMj5GQk5OVk4uLj5COj5KOkI+PkJKQkZCOeo+Ki4uMkJCGiI6MjZCLh4iNi4iNjo2Nj42MgIuQjI+MjI6Oj4qOjYqLh46NiYGKj4+NkIeJjY2Ki4yLj4uPkYyQj4aLkI2NjYeUi5SMjI6MjIyCjI6SiIyQhoeFh4SEi4yLjo2Kg42MkpGMkY6Fj4iBhZKNk4eKiIuPiJCHhouQkJKMi4qOh42VkI2NiYiPi4+PkY+Li4+Vj5KNgIuIjYuOjYqUi36NjIiTkYeJipeQkZCJjIiMjIeKjY2LjIuPjJKMiI2Gj4iIh4yLioGLj4eEjIWKiJCFiYiHjYyLkomIjJONjY2OjouNiImLjYiJjIiNjouPi4SIiY6KhoiMhoWTiYqPh4eIjZCNi4iDgoqHfoiGgoqSiYGKiIiIUIOGg32IhoCDh4yBhn99guaChI6NjoqEiYiNj5CShYaJiYeIiIqGgIqEhJKZkYaKiIeBhIOFioSHioGCh4WOiYiLiIuGioqMjI+Qj4uOjYuNhIyAhoaPi4mLjI+OjIqJh4aJhYuNiYaFi4yIgoiMiIeFgIeIiImDg4uQi4WJhIWFhoKEg4eEhIeEhYeJhomIjo2GjoSEf4iIi4eJgIR/gX3yf4aNh4F+gn+CgH+Cf4KGioCFf4SFf4KJfoCGjISEioKDiIaFfn+AioWFinuTj4mCgIUshIiCg3x+8H5/hoCFiIOGg4eQhYOHg4mFh4WIg4Z9foaAjIaBhoSFg4OGhIqAdXR7eXN3dG91cHZ0dHR6eG5ue3h6eHRzfHd4bnJ4d3l1dnpwcW1vdXJsa3TY09BxcmpvdHZ0dXRycXptbHp8dXR0d4Jyc3BxdHJw2NPhbXDa2NXcd3Db1W/QbW1nc3Z2b2xtcnBrcG5ycHJwbm9va3JvdHZwcnN2c3R1cnN1dXaAfXd1dXd5dXV4dXZ1cHhzc3d6cnt3dnd0enp9enx7d39vdm5vcnF2cnBvamtsbXFxbnV5cndydXVzdnB3dXR4c3d8eHNzb3Hf13l6c3l2dnF9eXZ1dXJ5dnB0eXVzdXR1enx4d3p/f3V3fHl1eHR3eXV4eHd6ent/eH15dnp1eXqAeXR7cnF4e3J7fnh4ent3eHd4eXdvcHZ3eHp5e3d0cHd4dnV0cHV2dXt4enR3eHZre3Z4dHpyc3x1eX59enl7e3R0eHd1d3d2dX5yf353d3l7dXt1c3Nyen97fnt0eneOinp3eX53enZ8f3t4cHd6enp8c3F4enhwcnt3c3d1dnaAdn2Bd4N/fXV4d359e3t/eXhxeIJ+dYJxeXt0eIB9fHd1gn19gIJ7eHd4d356enl7f3p6eHd7fHp9gnp7cXh7d2t6eHl5eYSAf4J9f32ChIKEhIKEent/gICDg35+gX1+gYCCf35menp/en+AgHh6fnp6g395eXx9eX6CgHp/fn+Ae4J9f3x9fYCAe358fH56f3x4dH19fH2Adnd9g319fXuBfH+BeYWBd3qAfX59doB6hH19fHt7fnV8gIJ8fIF7eXt4eHN/gX19fn14fXt/gHt/f3eAeXR6gYKDe316gIN0fn56foKEfXh7fYF1e4CAfX5+en17f4CCf3l7g497goKAfXh6eXyAeoF5bHx/eYJ+eHp6iYGAgX18fH18d3h7f3x+eX16f3t6fHWBe3d1fHx4cHh8dHeGenx6gnt6enh7ent+eHR7gH17fn1+eHp4eHx9fHp8dn1+eXp4cnd4gXx2d3h3c3t0eIBwdXN7fnl5eHBsc3Nuc29tcHh1a25wc3OAb2xwbm9sb25xdWtwb2l00nN1fHl8eHd5enx9dnVwdndyc3d2eHpzd3F1e316eHp3d3JydnZ3eHl8d3t2dn14d3h4eHl6e318fH59eX19fH15eXx8c3R7d3h6eX59enl3dnh7eHx/eXl3fH15c3Z/enh1cnd4d3p0dHh3dXZ1cHGAdnVzdXR3dHF2dnd2dnN6dnl3dnlwdHF5eHp3e3Z2cHVy1XJ3e3ZwbXNzd3Vzd3R0cnZ1c29zc3Fyd29xc3dzcXVvbnZ1d3JubnR0dndue3l4cnB4cnNxd29z2HBvdWxydnJ3dneAenJ2dnhydnV2dHVvcHZxe3Zud3d6dnN4d3msf4N+nH8Ffn5+f3+EfgZ/f35+f37tf4J+/3//f/9//3/KfwF+/3+SfwF+uH8BfqV/AgIEAICPkoqI+N+is5uPkJCNgoiRlYiIkY6MkoyKi4mVlY2OkpOZjJSVkYyIgfXx7Pf5hPny/IGLk4uIjpCMj4mGmJOUjJaUl6eVkZKJjon5goSJ//2ChIH4h4r6+f3594CFgPiB8vKDgYSG94KH9IeIhYqCgo2CjY+RiI2Ki5mUh4eKiICNk4mVjpOMj4mMhIaLiYOUiouNjY6KgJOMkYuRiZOTkI+VlIuKioyKkJWHjYWIh4yMiYWPjZGQko+NlJGQkZmKjIuWkYiJgomHi5GKjomKp7aij4WNkI2Lh4qHiouJiY2HjJKLjZGMh4CIjpGTjo+JkI2Qj5GLi4qLj5KVkIiQlICAkJGLkIyTjoyZkpCNkZKNkpSMi4eLipGKk4iSkJOVjo6RjpGSjouRjoyTkI2Rho6PipGPk5ORkpeSkY+PkpGQjZSQl5OUkJGUjoyYj5CLjpmPlJWVkpaXjpKUjpKRioSSkZqNmJ2ZmZSXlJCOlJeUkpSXmJebl5KXkJSUnJiWm4CRl5eOkZeXlJuPk5eajpOaj5SZlJemlZiXl5ecm5uQnZeYmJeUk5ihmJ2XlJiPipCQlJGF+NHP8oOHhYSEioqJi42RkpSclYmdmpqYl5aamZmdmp6fk5eWnJ2YmJ2VmZSWmY6Uk5OSmZ2amZuVmJGYnZedlpaZlJmcm5qWmJCOmYCTlpaYlJWVlJqUm5OXlZCWlJiZmpuZlJaZl5idlp2Sl5OYkZWZmpyTlZabl5WclpmVlZyXlJyXmqCXmpaamZiUl5eZmZORpZiWkpKXl5CZj5OUmZSWkpSZkZeYlpORlZGXlZmXl5iWlZaUlpiZm5mXmZKUnpaZnpWXl5aSl5iVmoCZlpqWkJOXnZuVmJmZl5yYl5CYko/nkJuWmJSTkpaUl5mWnZ6dlJeQlJGUk5WWk5KTlZSXjpeTkYuWmJaZkpmUlJWRlZWYk5ednJeUmJaYmZ2an5aOlZKbmpSQlJuXmJeSk5Sel6GYnJuWk5iVmpebmpSUk5SKj4mXiJqbjp6GkoCblJWalZOSjYqUiJaJgYiQiJOSjY2SmJqTm5mUj5iTk5aakYyQk5CPi56hoZqUmJWOlJONkpKMkIyDkZGaj5OPmJSVjZOakJSUlZORkpiXl5ORk5iXnpmXmJePk5eYkpSTkZKWkpCWk5GRlIyXkJKLlo+Qk5SMjJGTnJGTlJWVkoCWkY2BiYiEjYqSkZSZko+TmI2QmpGVmJmNlpSdkoqIgoeEi5CUioiQlouNjY6Kg4aOkI6MjIqLi4iVkomGjJyWmKKRlo6Lh4mGl46Sp6CNiYeGiYuIiIuRjpWVho6F9oWMkJCMlo6RlZGPj5CMk5OWnIGTm5OVk4qMj4+IlZOPl4CDhYB/7dCNmYh/goSCdnqGiH+BhoiFh4WEgX2LioSEiYqOgoeKhYB/fOzn4Oz0fuzq8XyEioJ8hYaEhoF+jomKgIiFhpSJhoh/g4Hvfn5/7vB5fXruf4Pw8fnx6np9eex75OZ8eXyA7HyA5oKAfYF3d4V7hIWHfoaBgI6IfH5/foCFioGKg4mAiICCeX2DgHuJgISGhYeCeIWDiIOIgIuMiIWMiYCBg4N/g4Z9g3t+gYCEg3mEg4OEhIWBh4qGio9/hYKKiICAe4OAg4aEhH+Ak6CShn6DhoeCf4OAgoN/f4aChoyEgoaEgnyChomJhoaBiISFhoiDg4OEiIqMiICHioB5iYeFioGJh4KNi4eFioqEiYeFhYOFgIuEiYCGhomJhYeJhYWIhICJhoSHg4WJfYaGg4iDiYuJiY2IiISFh4aGgYiGjYiIg4SKhYSOiYiDgIyIi4qJiYmMhYaMhImFfHeKiJCGjJGQjYqNi4OCipCJhomMjouPjIWMiIiHjIyJjICEi4t/iYyMh4yFiYyQhImOgoOOiYiYiYuMioyTkpSJjoeNjI2JiIuXjJGNjI2EhIeJhoN88MXC54CDgH5/hYWChoaLi4uQjX2SkJCOkI2QkY+Rj5KUio6MkJGQjpGLjoqNjoOLi42KjJKOjY6IjIaMkouRjYyQjpKQkY2Li4aEj4CKiouOi4yIjY+IjYiNjIeKi4uMj42OiIuQjouQi5aKjoiNhomPjpGFiYqSj4uRi4yKiI+Oio+NjZOMkI2Sj46JjIyNj4iGm4+OjIiNioaOhYiMjoiKiouRiI6OjoqHjoqNjY2Jjo6PiYuIjY+PkpKOkIiMmIqNkoqNj4uFjoqLk4CSjpCMiIqLjo6JkIqMjJGOjoOOiYbFhZCPjoWJh4uMjo+NkY+Si4uHioeNiomKi4qJj4yMg4uJiIGLjIuPio+Ji4yIi4uPi4yQko6LjoqQkZCMkYiEiYuPjIaHi4+Ni42FioeSi5KOkIyLiIyIjomMjIuGiId/gXuGfImJfop2hICGhoaKhYSEf4CGfId+en+IgoyHgoODjIyIjYiEho2IhouOiIGDiIaGgJCPj4uIioqEi4qGi4mGiYJ+iomQhomEjouLhouRhYmIjImHiY2Ni4qHi4yLkY6Mjo2FiYyLhouKiImOioeMiIeKjIOPiImCjIaHioiCg4aIjYOGiYmKimiNhoV6fYF7hoGFhomLhoaKin+GjISIio2CiomYioCCfYF+goWFgX+Fi4SDhoSBfICFhIOBgIJ/gYGJhX5/g46Jjo6Ei4SDf4J+kIOJmJCBgX96f4V/fX6FhIuKf4h+6X6DiYeCiIGGi4SFFIKJiYqOdYSOiomHgYWHhH6LioGKgHR2cnTgvHd4cm1wcnJpc3BycXNyeXJ3d3d1bnp5dXd7e3xyd3V1cHFu2dDM1uRx39TacnJ5cm53dHd0cW58eXlwc3Zxe3d1cm9ydd10c3TX22xxa9lzdtnc593MaW9t1mzN1m5rbnDYb3PNd3RucmhpeG9xdnRvdXBufHRscnBugHd5dHh0enR5cXJsb3Z1cHl3eHl2e3hrc3l5dXhzeXl4eHt4dXV5c2tzcW5zbnJwb3J2bXZ3c3Nvc3B5eHd6fnB5dXh7dHNzeXV1d3l3cXF3fHRzdHZ2eXhzdnB2d3Jyd3h4fHhyenV2cnd6dnh4d3F4d3Z6e3d0dXd+fH55cXl5gG12c3d8dHh1cnl8dnd8e3d5eXh6dXZze3N5bnl5fXpzdXx1dXp0bHd6dXd0d3ZxdXl1enZ5enl5fnh7cnV1c3Vxd3Z6eXZ2d3l3eH19fHNueX58e3h1d3t2eHh1enhwcXpzgHl7fn95d3h5cnZ7gHR2e39/fH18d3l2eHl6dnd3gHJzeG98e3x0d3h6dn93eXx0bnl5eot6d3l8gICAg3h7eHt6eXxzfoN7gHx9gXR1dnp1b27ktbTeenp3dXR7e3d6fH99f39/bIKAgX+CgYOCgIKBg4N8fHx+gYN/gX+CfX18dn59fHx6gHx9fHeCenp9foWBfn5+gX1+e36BeHh/gHp6e3p2fnt8fHl6en55eHt9foCAen96eH9/f317i3x9eX15eoCAfXZ6e4F+e4B6fXd7gIF5fXt5gH6CgYqCgnx8fXt+e3mIfnx8enx6eH94e3t8fHx/e4F7fn+CfnyAen1/e3t7gYJ7fnx7foGDgn6AeoKIeoCAfXp+fXh/eH6CgIGBgHh4enp7gHqCeHl2f3x5dH15ecN7f356ent6eXmBgHyBe4J/fXh8d3x5enl6enl+e3p5e3p9cnp+fIF+gnt6eXh+fHp6eX5+gHt+e3yAe3l+eG92e396dnd3eHt5fXV6d3p7gH6Be3h3enl6eHd2eXV1dnJvam5qcm9nbmRrgGxwcnV1cHBrcXFobm5qb3ZyenVydHN7fHd4cXB4e3N1dnp6b3F3dndte3R1eHl4eHR7eXl6eXt6cHR4enx3enR7enx5e311eHN4eHZ2fHx4eXV4d3h/eXh8fXV3eHxze3l4eH16dXt6d3p/dH94enV8d3p8eXJ0dXV2bnN3eXR4gHp0d3Bsdm93c3R0dXN4eXd3bXV0cHZ5fnN2doZ6cHRxdXN0dnJwdXZ3dHN6d3VzdnJ0dWxvcWxzc3V1cXBxdXN3dW96dHFzc3N+c3d8e25wcGpxdHBwb3hzdndxeHLUcXV7eHJ3cnV7c3R3eXB5c3Z8ZXB5e3d3cnR5dnB7e3J5hH+CfqN/hX4Ef35+fpl/DH5/f39+fn9/f35/f4V+B39/f35/fn6EfwR+f39+/3//f8t/hH7/f95/AX7/f/9/yn8BfqF/AgIEAICRhomKgfbqj7Khi4iIkI6Xmo2RjY6LkoyOj5WSjI+QlpKNjpKQkIjrgfz7+oD9hIL9i4mA/o2KjJSUlfeQjouI+oyMjZ6Oh5aPkIv9h43844CBhYD7hYaHh4SCgfuBhfL6gYKF/f2DkIP674GAio6IgYeFioqKj4j+h4eMi46HgYCPlY+Pi5CTk4uOkpOPlJKMiI2Ij4uOlZaNjJGSkI6Mh5GFhoSLhYiJiYKDiZKIjImNio+SkI+MlIyPkYmMjoiLipGRkpKR/PeBg46Vjo+Xl7GkjouLiIuLjImHh4eKioWJjIuLi46LjIyNj4+Li4mLi4mHj5GQkpGKjo2Ok5GckYCJkY+PjJaQlI2UiIiQjo+QkI6OjZCOjJGTjI2UjYqLkJKQjZKUjo6Mi5GRkI6UlI6SjY6YjouTk5WSkZKLlJmRkI+SlZOSjZCQjZKRlIeUl5GPl5OPkI2Yi5CSipOPlpWUlpmXnpeWl5eUl5aRmpaSkZOZlZWck5aUlJKTl5WWkoCVmZyXkJmdmaKhkJuVm52blpWbnJKUmpuVl5WWlpuZkJWcoZWbkpeVl5uWmJeVlJOMi4nyv6Wr0qOa6oOBhIGJi42UjpGFlJmYlZeVnZWXm5WemJ2alZmanZiYmZWTmpuTlJidk5GalZeVnJeYlpeYm5KSlI6Sk5aYlZKUk5qZmYCbmZuYmpqWmJSYmJqXmpiakpibkpWVmZ2WlJ6alJyfmpmal5iYn5iWlp+YjpWYmJGUlZSUlJGTlZaTjpqXmZaRkpCVlpWampaXmpyYmJOYkpSSnJWUlZSYl5yYl5eVk5iWlpyVkZSXn5uNk5mTkJeVjZSTmpaLk5mTmJydk52fl4CWmJiRmZaVkJWTmJadmpiZnY2alJWVk5WUm5eWlJuYmZGTmZyUlpiTkpeYmJaVlpaWk4+SlJiYl5ebk5mWmpWWlo2SkJeLkpWYmZmWko6Zk5OcmZGVlJWZlJmYmJudlY2KlpSTl5ybmZ6ampCYmJ6fnZKRnpSNkoiJj4qQk56SjoCfkI2hmIqQjoiOioiShYKKh4iRk5KYko6RkpGPlZGVmIuMkpGOjY2Zi5qko5uUlZeXmpORmZiSk4yKlJSSlZSUjJqWlZOUlpKZkomUmpWcloyOj5KQi5KPk4+YlJeYkpaQjpOUmJKTlZOVj5KRkpGWjZSUkpKSj4qPoJ+VnpaRioCIkpCUkZWPlZWUnJCUmZOTm5yimZWVlpmRlZmPjo+RjZCXlJiSlI+NkI6Jh4+MioOUi4qWmpqZppOMm5uPjYyblpOMio2GjpqQmp2Wj4+ei4WDgYGXlpGNjIqck42Ih4uRkI6QkpuVi5CMiY6JiomOiYyPjImRko+OkI+UjouRjYCJfICAeuzeg5mMf319iIWKi4CFhYR/hoKFho2GgYWFjYmEhIaDhHvde/Xu7XnzfXz4hoJ88IOAgomKieiHgoB+6YKAgY+AfY2HhoDsgYXx2Xp5gHjofoGBgIB+eO18gOvwe3x/7vF8jH3v43t5goOAeH58f3+CiILwfn2AgYZ/eYCHjIeFg4WKiYOEiYqFh4V/e4OBgoOFh4eIgoiIiIaEgYZ+gX2Ee32Bgnp8gYaBg4GDgYWFh4WBiX+EiYKEhYKFgoeGhYaH7uV5fIKJf4OJhZyVhYWEgoV/hYWDgICDgX2DhYSGhISDgoGIiYWAgoGDgoGAhYmIiomCh4aEioiQiYCDiYOEhI2Hi4eJf4GGhYaIhoaFhIiHhYmKg4iKhIOEhoqKiIqLhoaDgoiJh4WHioaKg4WRg4SNiYuLiIeBiYyFhoWHioeHgYaHgoeHiXyFiYmHioeEhISMg4mNgIeGi4yMi4uMj4yLiYuJh4qJj4mHhIaMjIuPhoqHiIqGjIqJiICDj46QhoyOipCSgYuLkI6OjoiMjYSIj4yJjIqMjY6Nh4+Rk4qMh4qMjJKNjo2MhoeBgH3ftZaWxKKd54B+gX6Fh4iNiIt+i5CRjY+Mk4qNkYmTjJWOi4yQk46NkIuIj5CKh4ySi4uSjIuJjouNjo+Mj4iKi4eMiYuLiIWKiY+KjYCPj5GNkZKRjouNi46Pj42MhY2ViIqMj5KPiJGPipGTkIyPi42Ok4+NjpGNhYqNjomKio2JiYqJjo6KiI+Oj4+GiIaMjouVj4yLjI6KjIqPiYuKkomLjI2OjJGNjo2MiouJjJONio2OlJKGjZKLiJCPhomKlY2EiI6Nj5CSiZOUjYCNjZGHko2OiI+KkIuPjY2OkX6JiYqIiYqMi42KiY+Nj4iKjoyJio2KiY+OjouNi4yMioaIiI2Ni4yQi4yKjYqOi4aJh4qCiIqMjo+KhoSSiYeTjIaKiIyKh4+Kjo6QjYV+iYeIioyOipCPjoeLjI+PioWEj4d/g3x+g32FhIqAfoCLg3+RiX+Ag31+fX6GfXp+fX6Eh4WKg4KDhIaHjImKioKDiYeEgoKOgIuRkY6Jh4uMj4yJjo+LioWDiouHioiJgY6Li4qIioiOh4KKjYyPi4SGh4mHgoeEh4WNiouNiIyFhomKjoqLioqMhYiKioePhYmIioqHhYCCj4+Jj4iGf4B/h4WIh42Fi4yKjYWHjIqHjIyRiYmHioyFio6FhYaGg4aMiYmCiYiEhoaAfoaDf3yLgoOJi42MlIWAjImFgYCLiYN8gYd+hpCIkJCLhIKLf3x7enqOiYeDgYCPiIWBgYKGhYSChY2JgYh/gYR/goKJg4SFgICGh4eEiYWJhH+GhIB4bnBybdjFbnl0bXBseHJydG5xc3FsdHR5eIB1c3p1fXl2c3d1dG7TdeLW2HDcb23lfHNv1nJxc3l6dtd2cnBv0HJvcHlvcXt4dW/XdnbexW9ncWrUcXN2c3ZxbdJucdPib3Rz19hugG/b0W1rcXdvam9ubW5vd3Xab25zc3lwbIB2fXd2c3Z6d3d0e3x3fXZxbnRzcnVyc3d8d3l2ent2dXVscXNzaXFxcWtwb3l1dXJwcXV3fHVxeHJweHV1dnR3c3d1dnZ219Zvc3F9cnV1bnx7dHx5dnlxeXd2cXN2dm91eXl8dnV1dHJ5fHd1dXR1cnJ0d3t6enp2e3l2fXl/eIB1d3Z3dIF3e3Z5cHV3dHp5d3p1dHd2dXp7c3p2dnh3d3t7d3h3dHNzdXp4dnV0eXh+eHmBdHR6dXt6dnZyd3hvd3N2e3x6d3l3cnV9fG5zdHt5e3l1d3R8dXqAcHl3eXt+fnZ4fYF8dXp0d3p8fHZ5b3J6e3uAcnZ1dH12enl3doBvenqCeHh2cnp/dHx5fnp5e3h8eXV7enh5eXp7fXx+eoCDgnZ/dHl+fICAgH58eHd2dnHHqYF+vqGY4Hl4eHV8e3+Efn5yf4aEfoGChX6AgHuBgIV7fHt+g399hH15gIJ8dXuCfXyAenx5fn+Bfnt+f3d+fXl8d3p6enh7eX54foB+gIF9gYKBe3p7fX5+fXt5dXqFenp8foF/eYCAfX+BgnuAfoCAhYF+gIR9eHl+f319fIF7fH19hICDfoSCf4F5enl+fnyJgHl9gIJ/fH2CfH59gnx9fX1/gH+BfIB+eHx8fIKAe399goF5fH95en1+dnd6gX53eX57fH2Adn6Degx9fIJ5g4B9e4F7gn2EfoB8c3V8fHt3en99fnp6fX57eH1+eHl6gHt1fn98ent4eHx6dnZ5fHx8fYB7fHp7eH18eHZ5dW52dnd6enh2dYN5eIF7dXp4eHl1f3l8f31+dHZ/dXh6dHl5fX14dX57fnZ3c3N2cm11bW1wam9tcG1ocnBqd3Nwb3Jva2psc25pbIBtb3N0dHVtbm1wdXl6enhzcnN2eHNwb31teHh6fnd0e3t8eXd5fn13eHh4fHV6d3pygHx7eXZ5dnx3c3h4fHx4d3p5eHV2d3Z0dnp1enl2enN2dnl+d3p6eXp0en18eYJ3e3d7eHV3cXF5dnR5dnV0cHZ2dnd9dHl6eHVyc3x7dXB5dnpyeHR2eHZ5enR2c3Z3fHh5dXJ2d3d2eXRydnRycXtxc3d2eXl+c3B7dHRvbW9wamtxeG91fXh9enp1c3Nwb21vbXt3d3hzbnp1c3JycnR2dnJydnFvdm91eXR0dXl4dnRxcHJ1eHZ7dHhzbXVwhX+CfqB/Dn5/fn5+f35/f35/f39+hn8BfoR/AX6KfwV+f39+foR/AX6Hfw9+f39+fn9/f35+f39/fn6NfwF+zX+Cfv9/7H8Ifn5+fXx9fn7/f/9//3//f8l/AgIEAICJkoyGhf7++vaYtqSOk4+Hk5uekIyEhIeSkoqRkYqSlY2Ui4eInvTu+fns8oeEgISG/PHr+oWIjI2Mi5COg/z/hYSKmpSQk5mUh5GdhYKF+4DygYaIioSFh42BgouHgoH9/faChP39hIWEgIKEhY6MhIiJhoaQh4yNjIyJiomB/4CNlpCMi5iOio6Sio+Ojo+Ui4ySkYuNjIyLiJCKk4yCjJGHjYiLhpCNgIyHiYmGj5KJhY2NjomRjpaVkY2Sk5GQkZSKiY6Rk5aR8Ozh7c7kiqCnlZKHg46Jio+JiYqPiYiLjI6Ki5CEkoqMk4qHiJKGkIiOkJGMjJKTkZKMjZGRlICPj42RkY6VkI+Uk4+OkZeRjY+Qi5KJioqMkJKUkI2Njo2UlYuKjoyJj5GUjYiVjpCNkZSTjo+OkJSUkJSUipCIhZGVkZGVk5OTlIuSko6OiJCRlZWSkpWXjZmWlpaKkpKWj4uSi4yZl5iTk5mWmJSVmZOakpKVlZOamZacl5SblYCTlJqWkpaaq5SVnZmVk5aZmZqYl5KWnKCgl5mUlZmbkpePmJqQjJiWk5eYlJKUk42GhLGcvojm692E6pz1/4CHiouNkI+Ok5GTl5aYl5aTl5aXm5mcl5WZmZqXlpiZmZyhmZqUnZ6YlZOTl5aUkpaIlo+KmJGZlZiRmZKRkpSXlYCTmZqXk5SVmZiXlJiXm5mSlZiYk5aYkZWQmZWaj5OblpyQlJWZnZyVlJKSlJCWk5qYmZeal5ORkZSWmJmWlY+TlJeUkpOZlpmZmZSUl5aamJKWkpKVj5STl5WTmJeWlJWalpaXmZeYmZyZlo+QmJ6WlpaUk5mXmZmYlpCWmpmWloCWmpKUlJWUlpCUk5yWnJmQl52glY+UmJabmZOWk46TmZePko2SmJSZkpiVnpaXl5mTk42Ok5Gbk4yXjJKRmpeVm5SUk5SLlZubmJaTlJiam5ucnpmYmpeRmJSRl5afnZqUkZePm6CXkpqel4+Wn5OdlY+QmZiPkZCNnI+Mlp+akICdmo6bkYmNjIWJlfuKjPiGj5OUjoiFjpGOk5mNj5WSk6COjYaej42LkpqnnJWek46Ulo6Pk5ORjYKRk5GWlJOUk5WWlZWUmZWWmJCXlZqeko6PkJCLiI6Rk5STipKSlJOQjpGOkJSXj42TjoqSkZGPnJKWlJGVkJakn5KUkpmXkYCGj4yTmIuSipGQkJqgl42UlJiWmZ6imImdnJaTiYqNk52MlaaYlJKQkY+Ljo+Jj5udmZWRiIuCk5OSjJebo5uclaGNkpuVkY6Xl5uQi6Csl4eKiIeAiY6RkJORkZWgl5iFkY2Hj5WRlJn3g5SUkpOWjpKQh42LjoqVjI6NkouIjICAiYB9fPHy7uiJm4+ChYN/iY6Ohn94e36Hh4SHh4GGjISKfn6AkOTj8uzg54F/e4KB8Ofi63l+gYSDg4SFeuvwe3p/joeChoyGfIWNeXt97HrlfYB/gXx8gYR5eoV/fH7v6+d2evHwe3t7d3t8fYOCfICAe3uHgISDgoGAgX9484CFjYiDgo+DgoWJg4eDg4OKgoCHh4KEg4WFfoiBioV6hYp+hIKDfoiDe4F/fn99hIeBfoOGiIGHh4uNioSJiYmHh4mBgoWJiY2I4eHS4MTTe4yTi4uAfIeCgoeDgYSGgoSEh4eChIh8ioOEi4SBfYt9h3+Eh4WFhYqKiYuFh4mHh4CFh4OFh4eLh4KIioSFhouIhYmIg4mCgoOCgoqLiIOEh4OLjIGDhIN/hIiKg36IhomEiYmIg4aChImKhoiNhYiBfIOJiYeJiImKi4KGiYOGgIeIjI2HiYiJg46KioyDh4aMhYKGf4KOioyHiY6OjoiIkIqNiYqMh4eLjImOiYiQjICGhIqLh4iLm4mHj4+Lh4mPjYyJioSNk5KQio2JiI2LhpCGi46Gf46QiI2MiIWLioN6d6GQsIXi5eGK7pvx/H2BhoSGh4eEiYiLjY6Oj46JjI2Nko6RjImQkI6Li4uNjo+SjY6Lk5WQj4iKjYqHjI2AjYaDjYmOiYyFj4iJiIqMioCHjI2MiouPkJCNioqKjoyIio2Nio+NiIqEjYqNh4iOho2GiYiMkZOIi4uLiIaOiZGLkI2QjYmIh4mLjYyMjIiHio6KiYiNjo+Pj4mHjI6MjIiKiYeMhoqJi4iGjo2IiYiOiouOjo2LkJOPi4GGjpSKi4uKi46PjpKQjIWLjo+Li4CNkoqMi4eLjYeJio6Kk46EjJCSiIaJiYiPjYeKiYWJjo2GhoCIioiOiI2MkImLi42JioaGhYWPh4GMg4aIkI2Mko2LiYeDi4+Pj42HiI2TkI+NkI6Oi4mFjYqHi4qWlI+HhYeCkJGJhI2RioKIkIWNh4KIioiFhoeDjoKAhoyJfoCJh36IhH+Cgnt+iOx/fuV8hYaFgHp9gYiFio6EhoeIiJGEgnyQhYSBhYySiYmQiYSIioeIiYiIh36HiISLjIiIiouLi4yKi4iMjYiLjIyQiYWGh4eCfoSHh4eJhYiIi4qFiIqHiIyMh4WKhIGJiIeEkYWLi4mKg4mVj4eHgoyLh4B9hYKHjIOHf4iHhZGQi4OIiYyJipCSiX+Pj4qKgH6Di5KBiZSJiIiGhoiBg4N+hJCQjouHgIF4hIOEgIeMkouLhZKAhI6JiIeLi4qFf5KVhXp/hH12gIaHhoiFhoeQio18h4R8hYeChovlfIuHh4qNhoOFfYOChIOLhYeBhYGAhIBwenZwbNjY39JxenVxdHBvdXd2cmxmbW53enZ0dW93f3d4cHNxddHR3tTK03V2cnZ039PI125zb3V5dm52btLYbmxxeHJudnZ1bXV3a29z127ScnVwcW5scnRsb3hxb3Taz89lbNjYbW5va2xsb3V3bm9ybGx2cnNvb3F2d3Fr24B2fXx4dX51d3R8dXpzdXJ6dXF5enh3dnl4b3h1eXxtdXpud3h1cnZzbG5tbHJ0c3dwb3J3eHJ1eXp3eHZ8e3h1eHl1dnZ2e3t50M/EzLTBaWt2d311b3t3c3t2dHV3dXR1en17dnpvfXRzf3l1cH5ydnF4enZ2eHp7en12eHl5d4B3d3F5fHV5enR0end3eXt5dHx5dHd0c3VydH17dnN1end7enB1dXRwdnh7eG94eXx2dnt+dnZ0dnl6d3h+d3dycG91fXx6eHd4eHR2fHd3cHZ5fXp2d3V3dH15e310d3p7e3dyb3V+en18d399enR2f318d35/enp2d3V8dnZ+dwNzcHaEeICEen54fn15eoGAfXp4dXx+foJ6e3l4en57gXd8gnluf4N5enx8eX6AdWxqkYKuhPDt9Zb0k+LrdHl8ent8e3V6foKDgYB/gX1+fH6BfX56eYGBgH9/enx/fYN/f4CGh4GBenyBfXl9f3SBd3N6fH12fn2Ae3p7f318d3t9fXx6fYB/gHx6eHqBenl8e317gYB7fXl8fYB3doB9f3t8fX6BgnuAgoB5eH94g4B/fYJ/fHx6fX1+goB+eHZ9f3d6fH2BfoJ/f3l9gn1/e31+e310fHt8eXiAfXh2dnx3e359fXuCf3+AcnZ6gnx6f3h9fH1/goB6eXx9e319gIJ9eXt2fIB9e3t5fnuBe3V+foN4e3h5dXt2end5d3t8f4N4cXl9e394eXx/eHl/fXx6eXZ2dX93dXx1dXuAfH1/fHl3eXR8fn2AfXl0eIV5fXyBfnx2eXJ4enR4e4GAend2dHCCeXVxe356cXZ6d3p0cnhzcnFzdHJ7c3Juc3Fpb29nbXFvcIBxbGxz0G1qymtzdHJqZm1te3d7fXZ5eHp2eHBtanh1dG9zd3hzdnt5dnh2eXl2d3d3c3l4dHqAenh7fHh5e3h3dnl7eXl8eHp1dnJ0eXRwdXh2dXZ2dnl4eXJ5fHd3e317dnhzdXp3eXR+c3Z6dXZweoJ6cnVyeHh3bXdweHl0c3lsd3Zxfnp1cXV3eXd3eXl4dX57eXlybXR7f3B3eHV0dHd2dHN1c29yfn56eXZycGhzb3BvcHN6cm9rd25xdnJ2c3l6dndwf4BxbXB4bGZudnl4dnN0cnp2emp1d290c250dcpwfHFxeHx6cnVtcXB1dX14dnNxc251hX+Efp1/hn6Ff4R+iX+Cfo9/A35/fo5/B35+fn9/fn6YfwF+yX+Gfv9/5H8Mfn18fHt7e3x8fn5+/3//f89/BH5/f37/f9t/AX6WfwICBACAj4KKiImFhob59YW/pY2Ik4aQj5WTjYiNjJeYkZWOkpWVj4qIiJuD8oKJhf+EgISCiYqFgISDgoyDiYLu9IOOipWSiIGEjoWQjJeLjZORioeKioL+ioaKioyPiYmS/oKLgfGC+v2GjIiIk42Rhv2Kg46KiIWKkpSHkIOMjoyEiIeAioeIkJCQkYuTko2VkYmDg42OipKRioyMi4+PjZCNlpWLho2Qj4qJgICIioKCjo+WgIWJkIqQkouMh4KMjY2IiYmLkIqKj4+UmYqAhoqNjpaHjZWDiImMioyOjIeHjIaJjY+MjIyNi5CIjo2OjZCRko+IjYuPj4uFkYeLkoyPjZKAj5CRjJSMjYqPk5SRkJWPho+LjpCJi46LjZqTkJKUl42RkoyKjI+VkI2LjpKVjI6Xk4+Rjo2Mjo2QlJOHjZOQlIyPk5CQjYyTlJGTjJGTkJGFjJKTk5SYk4qMmZiYmJSXlZaNkZGYk5WVkpSbnJaZjpmSmJabmJWYl5aanZmQlpGAlpuUlZaYoZuQk5+WnJSbk5SZlpaXkZiXmKCclJWZmpaZjZKVj42TmIyVlZWQkpeKgbaM4NO3hsuvprd1z9+AhIqQjZSViI2QlZiWl5aSlZmUmpuYmZSYkY6Xm5qYlpiZlZeYmpeXmZqYmp6Tl5iVk5qVmZqTkpOZk5WQi4+QmpmAlZOUmpeTkpaXlJWXl5WalJWTkpWbl5aVlpiUlpaUmZidmZaZm5WUkpOYkI2XlZCVmZScnZqRkZeWko+Rk5mTkpKUmZiVk5mYlZGXmZiVlJOQj5KYn5eUmZiWk5SWlJeNmpycl5KdlpaXm5SZmZqYlpySl5qZm5GTlZWXmpucmpiAl5OOnZeXl5WZlZaTmpKampWam5yZmJWXlpeWmpSYlpOdqZeclZiXm5aVkZWVk5eVkJKUko2XlJCSkJGTlZOZk5iVmpmVjo+Ul5iXkpyZk5uTlZuampiclJmWk5mXjpSbkoydnZ6dl5aXk52inp+bmpaQj5eWkJeSlZiUjZiRnpaAkZiRk5aIg4eE7vvy74uGko6OkJaRh4yWkIiRk46OlI+KopaMkJKVkJmaqaCTlon+lo+LmpmQiYWHjJuTl5KTlZCYjpeQlJWXlYeGkZSXkpKMi5CXkJKUk5aWnJOWlpaZlpCPkJWYmJGUnZWVkZiTkJaaoJSUn6Sem6GQjISEkIyAio2HjYaFkJGTiIGZmpqUk5ucn5eWkZeUoZeWlpCJj46UkJaYo6ekpq+kqaSkm5iKiYeNiImPipKUnoyRmoyShYSEmZiOkpSXkpGOjpSYn7qckIaLhoeFiZWPj5OWjYyMiZKKmJyalpWWkJeNj46Vk4yNi5OOiY2Nko2JkJGFi5GAg3qAgYF9gH/v6nulj4GAiX6GgoiGhH+EhIuLhIaBh4qLhYB8gId143qAgfiAen59gYJ/ent6eX96gHzg4nmEgo6JgHd4gnyDgIp8gIeDfnyAgHvygn6Bf4GBfXyI7nuEfeR66/F+goB7hoGIf+2CeoSDgXx/h4l9hXuEgoF8fn5MgX+AhIaFhYKJioWLh393eYSGgoiIgYWHgoWFg4iFjoyAfYSIhH57enqBg3x8g4SLeH6BiISEiYCCg32CgYOBgYKEhoKBhYeLjoB6gYSAgHR8hXt+goWEhIaGgoKFgISFhoSDg4eGh4GHhYiEiIiIhoCGg4OBgXuJf4SJhIWBiYaIioSJhIWAhoeKhYaJhHuGg4iIgoKFgIKQiISGiIyCiImAgoOEi4WFgYeJioOFjIqHiIaBgYOBhoiGf4SKiIuBg4eHhYKCiYiFi4aIiYaHgHyDh4iGiY6Jg4SPi4yMhomMjIOKiY6KiomGiZCPiouDjoOLjI+HiIqKio2PjYaLhIqMiYuKjpOMhIWRiZCJjoeJjomJioePiouUkomKjo+IkISJiIaEiI+DjIqJhoiLf3qkdr23pn7GtbLLftbafX6EiYSKjICFiY2PjI6OiYqPgIqQk42Ni4yHhI6Qj4+NjIyIi42OjpCRk5CPk4qKjIuGkIiMj46LiY+Li4d/iIiPjo6Jio+NjIuOjYmLjI6Nj4mJh4eLkouLi4yMioyNi5COkY+Jio2JioiIkIeDjYqGjYyLk5GRiIiNiYqHhomPiomJiY+Ni4mOjouKjY2Ii4uKgIeAhIeRkY+RjYqLiY2NjYOOj46MiJGJiY6SiI2Oj42LlIiMjo6Th4uOiI2Ok5KPjY+KhZKMjZGLjY6Nio2JkJCKjpKQj4yIjY6Ni4yJjYmIkJ6Ik4qOjY2LjYaIiImOjYaHiIl/ioqHiIiIi4yKkYmQjY+LiYKBiImKiomSj4mRgImKkI+PjpGLjYyHjouDjJCHgZWPkI+Ih4mCj5SPlYyMiISFi4uDiYeKioaAi4ONh4KIg4SIe3d9ed3p4t19e4t/hIiMiYGDjoaCiYmEg4iGgZGJfoaHiIeNj5eTiIuA8YmEhYyLhYB/gYOOiYyKh4qHjoaMh4qHioyAfIeJi4aHgIKDi4+HhomLjI2Ri42MjJCMh4eIjI+PiIqUi4yJk4iEh42RioqQlpCMjIKAe3yGhIODf4V/f4iHjH95ioqMiYSLjZCJiYWLh5GKjI2GgoaEioiKipGVk5eblJiRj4yKf35/hYCBgX+FhI6Ah4p9gXZ4d4yMhIeIi4WCgH+MjJGiMImCgIN8f36BiIGCiIqEg4SBhoGOjoqKiY2CiIKFf4uJgoKEiIF8g4GHgn+EhnuEiIBxbXJ1dHFzddnOan1zc3B8b3Rzd3V1c3F0enlzcXJ4enhzdG9ybGbPbnJ233RudXFwc3Nwbm1scm51bMPPbHFyfnVya2ZxbHBxeWptc3Bub3F0b9hzb3Rvbm1ta3fWcXlx0G7P3XBwb2dxbXhx0HZvdXRybnB4eXBxb3dydHBwb0JybnV2dnZ4dXl7eHp3cGdsdHdzeXp0eHp1c3Z3eHh6fXBwc3tzbWZsbXJ0bnF1dXZscXB6dnZ4bnNzcnN1dnN0cXWEd4ByeHxzbXRxbWplWmFxcnF3d3l3e3l4dnh0d3t8eHd0fHt6dXx3enR5e3x4c3l3d3J0bnlvdnp4eHN3dXt7eXl4dG94d3d2eHl3cHV2e3h2dnpzdIB7c3d4fHR5eW5yd3h7eXlydnl+dHV4e3Z4d3NxdHF3enZydHp7em9ydnd0c4Bzd3p3fXd6dXR6cHN5eXd3fXh1dXt3eXpzen59dHt7e3h5eHd8fn19eXB7cnl+hnd6d3l7eoJ/dnt2d3l4e3p8e3Z5eXt2fXmBeXh7d3Z6eIB2e35+eHp8fHiBeXx5dnV6f3h7enh7d3lybopZmZqTes7ByOaH3NB0dniAeH1/doB+gYKCf4CCfHuBdn5/e3x7fHp6gH9+fHx+f3p5fIGAgoGCgYCAeXp8e3V/dnl9gnx5e317dnR6e399f3t6f31+fIJ9fHl6f3t+d3p9eXuEfXx7fXx+fHt4gH6AfHp6fHx+e4CBe3d/fnl8en6Gg4V4en99eXZ8foJ9e3t8fX17eYB6fYF5fX50fHt5d3F4e4CAgX1+eHx8fn1+dnqAfn55gHh5fIF5gH58fXiAeHl9fIJ3eX90gHuAgIB8fX15g316gX19f316enh9fXt+gH5+e3l7fnx9e3l7eXV8k3uCeX5/e319eXh5eX56eHh4eXF3eHd5eXd6e3uBeoB9fnx8c4B1fH16enx+fXt8dXmBe398fnl7enZ7d3J6fHZ0jXyDfHVveXV9fnmFeXl2c3Z4e3N2dXZ0bm53cHRxbnJvbnFpamtoxc/IxGhqfHF0eHh5dnSAdXJ4eHN0eHdwenVvc3R1dnp8e3x2eXPZdHJ2dXV0cHR2dXp4enh1enp9eXp2d4B1eHt2cnZ2eHF3dHF+fXNzeHp7fH55fH15fnV0eHl6fXx3eoJ7e3eMdnNzeHl2dHt+enVybHBvcHd0cXNydnJyd3N8cmlzc3d3cHN1enZ2cHp4eHZ4fHdzcnR4d3l4eHt7f4F5fHl2eHhvbW9zb3BuanRvdm10cGZqY2VndnVsczp0eHNycHJ6d3yBbnF0dnBzdnV4bGx2cXJzdnZ3cX17d3V5e210cXNteXtzc3V3c2x1c3Zxb29zanR2iH+Cfp1/BX5/f39+j3+CfpZ/AX6Jfwh+f39/fn9+foh/AX7/f/9/xX8Cfn2EfIR7A3x9fv9//3/Nf4R+on8Bfv9/0X8CAgQAAYiEiYCEg/39hYGAqquRh5GNk5GQi46Oi4qOg4WKjIuPjouPg4eb/f2Agv6BjoOCgIyOkYyBgIaAgoaHiY2IjfqMhYSTjYyRlZKHjpCUmZSF/4yNjo2XkYmKio6Oj42JgYKFi46GiYyPi4yFgYWIjoyVmo2HkIWBhIH58YOB+4SD+4CQj4COjIyMio2Oi4+Tj4yJkoeKipCQkoeNjpOKi4+MjpGNhZCMgoGIkYaOlo+EhIiOiOGQi4iJh4ePhoqHjYeMkIyMiZSSlI6JjpmimqCipYmJjpuRo/6AhIqIgoqMiI6Lj42RiYiKjJCNiZGIi4yKjZCQiYaRlIeMjYuQjI+PjYyLjoCGlI+SjZKRhJKRi4mPk42IjZSQlJGRk5mTkpaNjYuNjYyNj5GYjZSPl5STmY+QlZaRjpOLlJOQjY+OjZOSj4uMjpGRl5CQlI6Tl5mPipKQk5KTjZOUlZSQjZSQlJSRmZiQlZeYk5KXlZWKlZOXmpyVl5GWk5Wam5WVk5iTl5COj4CYoJOXmpuZn5qcmoyOl5SclpGUl5SQmJeWmpiblpSWlZGPlJCXkpSTnJSOhL7Eqp2LgoH7g5umyZTuhImTmJuPjJSYmJqWk5qYmpmUk5aZk5mWmJycmZOaoJ6alZGTm5SXk5aak5qTlpmSlJOSk5WVmJqX6f2TlpeVmJWXlJKVlYCTkJWPl5SOnpicl5qZmpKVm5mVnJmXm5SRmJaZmZWTlpWNmZiYlpCQlpKPkJmRlI+JlpaP8ZKYmJqSkI6VlpqVkJaXkZCYm5iSkZSPjo2WlJablpWUkJeUlpWVmZOSj5icnI+WkpqZmpmWmZiYlpSVnJGQjZeWmJmanJKRlJCXkoCZkp2dlpObmp2dmJuZlZWdlpCVm5aMmJaSlJyXlpiRl5aSmJOUjJKXkpSPl5eTk5ePko+RkpKPkZCSl5aPmpGTmpmZmqGLj5eUlZSWkJSXm4+WkJSUl5KakomXlpGXjJuTmZqQmpufn56XlpqVjpeWkJCSj5yTiJ6Ln5iVio6RioCRhoOBgO/3+IaQnpaTjY+bkZOQjJKRkJWSjpOTkJSSl5WYkZWTkaWSl42LmZOTk5iSjYqMiYiIkZGPk5OQlpKRj4uempyZkpadmpWJjZOYmJaTlpqZl5uZoJyLjpWMlZCVkpKYlJGXlZaNj4qRpZqXn6mks6KCi4eHjZKGi5WRiXv6hIucipKUlZiYkpaUl5mbmZaYjJCOlJGVjIqFkJGWkomRi42HhoGDhJKPgISVkY+Pko+KjZGI+ICJkJCUj5aro6OcmoKNlJGTlpyzppKMjZSSi4mIj5mTjYyXmZmRj5SOhYqHgpWPlZGZkJGTlYeUnJeamZGPjIiKiIqAgoSCgoF+eu7xf314lJWEfIaCiIaGfYOEg4KFfH6Ag4OGgoCHenmH6e15e/V5hHl7eoOEh4F5eXt4e35/gYR9guaEfXqJf4CCiIR8hISFi4d87IKFhoOLiICBfoKDhYV/eHh9gYN8fIGEf4B9eX1+goKMj4R8hHp6f3zp4nx66n6Afe95hoaFhISEgIOEgoOJhYWDiYGEf4eGiH2EhIuDgIODhYiDfIiEfHmBinuGiIN8e4CDgdaGgH+DgYGGgYF+hX6DhYSEgoiHhoJ/gYiPi46Pk36Bg4qDlPR7gIeBfIKGgIaEhYaKg4GDhoiHhYuBg4KFgIWGgX6HjICBhoSHgYSAhIaEgYR8jIaJhImFe4iIgICHi4SAg4qFiIaHiI2HiIqGhISGhYOAg4eOhYuGjYmHj4iJioyFhIqBh4eEgoWEhIiHhYGBgIWFjYiJiYSLi46Gf4mCg4SMgYmMjYiGg4iGiIiEi4mBi42NiIqLi4p9iIWIi42Ji4aLiouOjouKh4uAiIyJhoaNlIiLjo6Kj4yMj4OFi4iPjoqJi4qGi4mLkY2TjYuMjYuGiYeMiIaHk4eEfbexlX53cXLedZmszJbofn6JjpCFgoqPjpCOiZKPjY6KjIyPiZGMi5GRjoqPlZOOiYmKjomPiYuSio+JjI2KiImJiYuJjJGN1O+Ji42MjYyAjYmEi4yLhouIjIqGkYuOiYiNj4mLiYaJko6OkouHjoqNj42KjIuAio6OjYaIjYeIiJCKiYeBjImG4YqSjpCJiIaKi42JiY2NhIWLj4yHh4uIhoWLi42Pi4mKiomIjI6NjoqGgpCSkYOKiJGQkJCMjo6QjYqOkomIhpCMjI+QkoiAh4qDjYuTi5SQh4aQjI6Ujo2JioyRi4WNjomBj4uMjJCIiomKj42Ii4mJgYWMioqHj42JjIyFiYeHi4qIh4iLjY6FkomIjYuNj5eEiIyHiYyKh42NjoeJgoqLjoeQiH+PjIiNg4+Hj42GkZCQjo6MiIyIhoyIg4KDg4yHfpCBkIiAh32Chn+GfXl3d+Dm6X6Hj4qKhIaQh4aDgYiKh4qIhIaGhIuIi4WMh4mHhJKFi4WCj4mHhYmFhISEgX6Bh4aFh4iFi4eJhoCRkI+OhomOi4Z+hImNjYuHi4+OjpCOlJCDg4mCioeOiYiMioWNiIuFhH+ElI6Kk5uUmop6g39/hYiAgIGHhoHrfoOQgYmIiIqMhImJi42Oi4mLgoWEiIWHgIF7hoiLhX6GhIWAfHd3eYeEdnyHhISEhoN8gYV/5nd+g4WGhIiYlJOLiHSCiIeIio2ZkYGCgomHhIGCgo2Jg4OOiYmGhYSCfYN/eYaFhoaMhYSKjX6GjIqNiYKFg3+BfYKAcnZzdnRybuDYcHBndnRybXdwdXR2b3N2c3F1cW9zd3V0cHF1a2dwz9VycuFue29ra3NzdW1pa2xrbm9ycXZtb8p6bmt3bHNuc3RvdXF0d3Nt3HR3eHJ3dHFwbHJ0dnZva2tvcHNubG9xcWxuam9wcXR7hHVsdG1scXLSw25v0nAKb9pueHZ1dXZ2coR1gHp5dnZ5cnZ0d3Z3bnp3fXJvcHJ1eHZveXRwbnR3bXNzc21scHV3wHdydXR0dHV3dW12cHR2dndydnVzcmtrcHNxcnF9cHNxc2582nBzeXZyenhzeXt2d3t5d3h5fHx3eXZ5c3dzd3Z2eHt8c3F4eXlyc3d5eXZ3bnt1d3V7dm55gHd0b3R2b2xyeXV2dnd5e3l5dXd2d3Z4d3J3e4B3fHZ7dnZ+eHt4e3V1e3J6dHFydnJxc3d3cXBxdHl6e3t6dnt4e3dxeHRwc314eHp7d3dzd3h7eHJ5eXN+fH14fXl6eHF5cnl3fXt/eHt6dXp7fn10fHl7e3l2e350dnx5c3x3gHmAd3R6eX97enx4eHJ4d3yAe4N9eXt8fHd3en97e32Ie3lvq6eAYWFdXLdoqtvwoNZzc3t/gHx2gYJ/gn97g4KAgX59fX57g357gn5+fX6Ihnx9enp8eX17fIF6gHt9e3t3enl7fXl8gYC92Hd4e3yAf398dnx/fnd8eXx8eIN4gHp7d319e312cnaEgICDf3qDfH+Af3l7e250fHx9enh/ent8g3t6fXd/fHrWeoR/gXt4dXt9e3x6f312enh8fnp8fHl7eXt5en55d3p7en19f357fnh6gX9/cnx4gX5+fnt6e4B+e4B/eXp4g4J9fYCEdnZ7c318f3mCgnZ1fXt8gIh9fX14fH95d358d3V/enx8gXx7d3mAfHp7d3txeHp7e3iAe3t+eXh3eXh6eXh3e4B+f3eEenx9fXx/gXV8eHh7f3l4e359dnpxe3l+eH13bHx8eYJ3f3iAeHOBf357eHl1d3d3eXNyb250d3ZueHJ3cnRrcHVzeHBpaWnIz8tugHd4eXh0dXp4dHR1fH92dnh1dXJzeXh2cnZ3eHlze3F4d3F8enNwcnB1eHhybnN2dnN1d3N4dnp0bX5+d3t0dnp0cXB0eXl4eXZ6fnt7fnuCfXFveHV5dn53d3t5d3l2enNva3B3d3N8fXt3bWtzcXN1dXFxdXJz1nF0eXB3cXR2cntydHV4eXx1dnVydXV2d3ZxdWt2eHhzcHd2c2tqaGhrd3FncHJwcnNyb21ub2zDZ2tvc3J1eIJ/fXNwY3B4dXV4eXd3bXR0d3h2dHdxeXdycXp1d3R2c3Jwdm9nc3Rzc3x2b3d9cXR2dnh1b3Z1bnBvdYd/gn6efwV+fn9/fpR/AX6QfwF+qH8Ifn5/f35/f36yfwF+o38Bfv9/2X8Cfn2FfAF7hHyCfrl/gn6/fwF+/3/Rf4N++H8BfrZ/AX7DfwICBACAkIWD/ez3+/7y+oP26ZKflJKNiYeKiY2Kh4qIhoGMjZGRj4KIg4Wt+4T6gIGHhoqKi4OHhYKEh4mKhoeUkoSDg4SMlI2EjIqOk4+Elf6FjZGZkJaJjIqJjpKNjoiRjY+NjImPkI2Pi5GKj4OKjI6NjoWOkIuMi4T1/vL/g4OAiPKA+veBiIaEhIr2iYSHk4+QlZWPjI+Vj4uOjImTlZCNj4WRiY+TioeSipCSipCThN6NhouMj4iQhoiHioj+iIeMiYKGhYWDhYyFjbOHjJKRjJSOiYyEg4Hxl/3sgoSDioiMjI6KioqNgZCMjYuIkYuGiYmSjY6TipKKh46SjY+MjoyAiZSNi46MkY+Ljo6OjI+SkIuSk46LjI2NjpCSiomQjI+NkI+Ui5KLipCLjZCOiIyIlZKMko6OlZOUk5KLjoeNjZeSk4+OjZKJjpSKlJOGjpGPiZSSjZCOlZaUl5aTjomWkZuSkZCVkZiakpSXlo+TlJaUjZKSk5CPlZeQnp2SjJeAmpuJiZKKkZOTlJeanJeUkpaUkZiPko2UlZeRlpqXmZaUj4+aio+SlYyVlpGRk5eUj/rt3aWD1qif7duj7N3+hYmJj5CVj5WWl5mZl5SYlZqXk5aXm5OTlpaVm5KZjJafmJWXmZqVkpWYlZOXlZiUl4+OmZWVk5iSi4yVm5aYmJOAlpiTlZKVlpKOkpOVlZqXl52WlYuTmJiYl5uVlJmXkZSblZiQk5WRjpmYjJSRk5OVk5GRkJCWk5SWkZaXkpORlJSOkZmWk5qUlJaSlImQj5yUi5OVjZWdlJKTkJOXkZGYmp6UlJiKlpSWk5aUlpSXnI+VkZeXlpeSko6ZmI+OlI9glZeYi5OlmJuamZqdm5qWm5OUnJmNl5ecm5OMlJORnJuZkpWYl5SQl5OZmJqYl5GWl5eVl5iVkYeQlpWVk5GVkJeQlZHmkp2bnZOYlZaSnpmVlo+MlZKNm5aXlpWRmpCUhI6Al5WWlZuZl6Sdm5KbkJaNlo2Hi4mInZOJm5WPl4aBjICB/fXug4ONmaOgoZeVkJOUkZaPlYeFkJiWj5OPkZONkZWVm5SUmI6PmZuQkpebmJSYmI+Jivj5+qCalJOZlJmWlpWbl5ack5icmpqRhJOWnJqZl5eWmpeUk5GTkpOSlZeAmpaMl5KQjpKTjIyGgYOGm5+YnaO5oYaLhYeOlJWHh5KTh4X5+YWTlpCUlI2Skp2UlI6WlpiemJuNlI+Pj42HgYOFh4D48/2Dh4CEk4+OjYePj/GI+oKAjo6FkpCblpehj4WXgoePkJOSkpaXo7CrpKOJi4qMmZGQj46KjoaDiv4ciJOOjIeJkI2TlpGLlI+RkZCMjo6TjoqNjY6LioCHgX3x4+7y8ubse+3hgouFgoOAgIN+g4B/gX99eoWDiIqGeX97epfieet4e4F+goKDen5/enp/goJ+fomJeXt8fIOIg3uCf4GHgXOK7HqBhoyCiH6DgoGEiISAe4WAgoKCf4SGhYeChH6DeYKChICDe4WJgYSBfezt5u17fXl/5IDs6nyEf3x8hOV/fX6IhIeLi4WChomDgYSCgYqNiISIe4mAh4uFhIaFhoeAhYx90IOAg4WGgYiAgH+DgfKDfIJ/enp7fn1+gXp/nnyAhoeDiYV/g31/e9uH8eh/gH6DgYaHi4aDg4d7hoSIg4SLhoGFgoiFhot/h39+g4eDhoSFhICBi4WDhoGGh4OGhIWEhoiEgouLhoeFhIOHhYyCgYeDhYeKiYyBiYOCiIWGhoZ+gX+OjYWIg4WLiYmGh4GFfoeFiYWKhIWBhHyGjIaOi3+IhIJ7ioWBh4eNioiLi4iAfomEkYaHhYiEj5GKh4qHhYqKjYeCiIeKhoOHjIaTkYiDjICNjYCDioSFh4aHi4+QjImHi4mIjYaHhYuLi4KKk4yOjo2IiJCDhImMhIuNiIeJjoqF7dbPiXbDmonTwZXf2PB+g4OHiI6Hi4uOjY2NiY6IkI2JkI6RiIeLi4mPiZGDjZSOi46PkImIjI+HiI2JjYyPiYiRjImJj4h+hYuNiY6PiYCKjYiKiYyLi4aPjIyKjImKkouOfYmRjo2Kjo2NkI2Gio+NkIeKkYuFj42DjIqLiYuJiIeFhouKiY2GjY6JiIiKjISHjIqJj4mIjYmMf4WBkIyDjIyEi5WMhomFiY2HiIuOk4aFiHuGiIqJi4mLi42PhoyIjYuMkIyIhJCQhoOHhICNiouEip6MkJGNjZGRkI6SiYmRjoSKjJOPiYSNiYaRj42Gio2Ih4aMiI+OkI6Mho6NjYyOjIiIgIWNjo6LiY2Hj4aNhteCkZGRiZOKjIiSj4+KgX+JiIWOio2NjImRh4yEhYaFjIaJio6NjZSPjIaMgYiCi4J/f35/joZ/i4aBhYB8eIF4evDn4Xt6hY2SjpGKhoaIiYaIhYyBfoWLiYKIg4eHgoeJiY6KiIuAgouNhYeMj4uHjI2FgH/o8faRiIeGjYqOi4yMj4uHjoeOko2OiH2LjZCQkIuLio6NiIaFiYeIiIyMkY6GjIeGg4aJhIN9e3d7jY2Jj4+diXqDfn+DiYCMgH+JiYB87u18iYqFh4mDiIeOhoiEioiKkIyTgoqFhIOEgHl3e3566ubreXx5eoeChYN9hYPfeOZ4d3+AfYSGjYuLkYR+jHp+hYeJiYWGi5GalY+PfYSCg4+HhYSDfYF+eX/zgoqFhoGBg4GGjIeDjYiEhoOChYGCgH+ChISCgYB8dXDZz9bc3tHMb9XObXNycHJxcnVsc3Jxbmxva3R1eHp3aXFsa33Aa9JucXNxc3FzbXN4bm10dXR1b3Z1bGxsbnN1cmlwcG90cGZ81Gxxc3dwdG50dHRzd3RtanFvcnR0cXN3d3hzcXNzbXJvc250bXR8c3Vyb9XP29RucW9w04DR0W52cW9vdspwcm55dnp8fHVzd3h2c3dwdXx8enV7bXt1eXt2eXl4d3hvcntwu3FzdXl2cnlxcXB2ddZ3bXJuc2ttdXNtbmhqhGxtcnVzeHZzdG9yccNs2dh0dXN3dHp7fnx2d3xxeHd7dnh8eXd6cXp6en1ze3Nycnh0eHZ3doBxenh4enN1dXZ1dXZ4b3Nzd317dnh3eHV7dX51cHp0cXt7eHtxfHlzeHl1cnZwc3B+f3d1dXd6entzdnN3cHR2eHR6dHVudW12eXh/fHVzcHJtenZxdnp7dHJ2enlybndygHV5d3tyfn97dHZ3dnl2eXRxd3t7d3Z3fXx/f3Rze4B4eHB0fHZ2cnN1enx8enl6e3Z2e3d2dXx5eHJ4gX59fnp5d3t1eHx7dX6BeXl5fHd138q/cGavgXGwoJDezdt1eHl7fYN8fnx/e36AeX54gXx7g4KDe3h+e3t+e392f4aAfoF/gXl7fX52eH53fYGAenmAfnx7gHtzd3l7eX+Ad4B6enh7enx3e3d+f4J9fXt5g3h/dXyChYB9gIB/fX96fH5+f3Z7gn93f313fnp7e4B8fXd5eoB/f4N9gYR9fHx+fnd7fXp7f3d7f3t7dHlzgX52f4B1e4Z8eHh5e3t3fHx9hHx5cWdydX19fXp+fX99d39+gHt+f357dYF+fnN5d4B/dnpzdox5fYJ7fIJ8fX19eHp9eHZ5eoKDfHR+eXqBf3t4enh2dXZ8eIB7fXh5eIF+e3x+e3d8c3V7fX18ent5gXaAedR/g4GAd4B6eXmEgX98bnB3e3l+enl/fX2FeIB0dnZ3e3V4eHt6fXx7eHd3cnlveHFxb3B1d3Ntc3FubSZpbW9ratrQzm1vd3d2dXlzb3V4dnV3dXt0cXR2dXR3dXh3dHd3dYR4gG9xdXh3d3h7dnN3fHVycNPc4HVucnJ6eX54eXp8d3J3c3t8dnR2cHh8fX19eHd4eHp0eHZ5dnp5fHqAfHZ8d3VzcXh1c21sZ2l3dnZ3dnhranNydXN6fnNxeXh0b93XbnNzcXN2c3h1d3Z4dHV0dXp6e3N7dHRyc3JpZ2pvbNPPVtNsam5rdHN8c2xvcsdoy21rbG1udXV3dXl/dW92a251dnZ3c3F4gH96eHlvdXVzfnV2dnNqa25ocNh0eHR0bXBzb3F6dXZ8dXR3c3V0cGxubXB0dXBzg3+HfgN/fn6afwN+f36ifwF+qn+EfoR/g36GfwF+pX8Bfox/AX6afwR+f35+/3/Yf4V+CX19fXx8fX1+fv9//3+HfwF+wH+DfrF/g37Lf4J+n3+Dfot/A35/fqp/AX6cfwICBACAhPqHjIGCg4eC8OTr+fn9g5SMmZeLhYaBhY+KiYmHjI6Ji4uNkYyVqYSChoaJh4uGjIiSj4eSloWQjYX5hoKIiImF/4SKjY+NlY6OiYuMko2Qm42PlYqOkpGVmZuSjYqOj4aGlY6OiYyFiYyOkYOGgoaPjoWFiPiEiImFh4WChICAhJCRjY2Fh5CNiISPjpGSjoqNioSOj4qMgY2Tj4+Ik42PjIuVjI2RjpCBhYSDi4yKhPiNh4WBhoGNhoeDgYCGgf2SjIqJho+NhI7o+vqCiIyPioaEhfeBj/n5//yKiI6OjImQi4+PiouIipGLi4eMjIqSkYqLjY+RjIn/k5KOjY6Ai5KUj46Mk46Qko+Rh42YiJGSkZGNkYyPho2NkZOQjpCOj4yMkpGNj5CRkY2PkYaJkIqMkYyHkZORlZWQiYqPi5OSl5KVj5WRjY6PjY6Xko2NjoqRj5CTmY6LlY+Qi5GTlJWUj42LkZeckJGel5mZmI2Tl5WSlpScl5OVk5eMkZiAlpaKjpaTlZKakpeZm5qbm5OcmJiTkZKclJOXmZqVmZOakY6Zm5uNkJaPjZOTlpGPko2GgYT48OTW6eTz/4KGiouMiP6Fi4+RkpCPi5mWlJmXlJSVl5KTkZTylKCglJKXkJGLlpSTlpiRlpaVk5iZlJWTlJmTkpuQl5eVlZaYlpSAj5WUkZOPlJSajpOWlpiUkpSYkJmVk5ePmJyTkJSSlpGXkJqWlIqVmJeUlpSOkZOUkZSOkZOPlYyPiomLlJCTkY2PiZiTkpePkJmYjpOXkZaRlJCKk4yQn5WMnJaWmpGRlJWSlJCNjpOUlJOUmpeOlJWXlZuVk5KTlJSOk5aXmJSAj5eTl5SUlp2Wl5eVk5iXk5OVj5WWleH2kYuZk5mZkpKSlZeVlZSSlJqZlJSWmJSNnpuZl5aVkpKOlI+Ok5GVlpqRmJSVkZGXmJCVm5KXm5KPkZeRlZGOl5OYkJKUl4yRjJeMmpGZmZyamKSsnKKSlo6SkZmTl5WVmZiXlZyglZaAm5mSlJaOj4//iI6OjZSRlpmboJqYnaGmnZujn5qQk4iRipOTipOgh5WUk5qSm4mPkIiOiouQi4SIiYuHqJamk5aYj5aTlZmYlZSck5WWkYWHl5WVlpmYlpGRkpqSl5iYm5SXkJiYk5GYk5OWlImI8YeGiJCSoqClq8KZjoiKgIqAkpGNjY6UkYmGgI+QhIuQkZSOjZKdkI+Oio6Nh46WjIeDgoCIh4aMiYyHjIaGi5aMjYuQh5iMipKRh4eHjpeWmZWXk5mIjoqMk5OMi4+Wk5OXnp6drrOuspiEiYyOkYiDg5aZi4eE/oGGjpGIh4uYiZKOkpaRkY+RjZCKlYOPiIeAfu9/hHp7fHx76tfh6+3te4aBiIiEgHx4fIZ/fn9/goOAg4KEhoGIl3Z4fHx9fYWChoGJhH2IjXuDgn/uf3uAfn995np+f4ODhYKCf3+Bh36Bi4CCiH+Bg4SIio2GhIGDgnx8jYWGgYN7gIKDiXx+e32CgXt+ful8goN/f4B6e3qAfYWJh4d+gIWCfnyFhYiIhoOEgneBhIGDeIWJhoWAioWHgYGLgYWHg4R1fH58gn19fOyGf358f3uFgIB9e3t9ee+IgX6AfIWEeoHf8u58goSHgn9+fe54gvHy9vCEgIWGhoSIg4iIg4WDg4qEhYGFhoKJi4CDgoaHg4LvhoeGhIWAg4mMhYGEiYWHiIWJfoOOfYeFh4eCiIOGfYSCiomFhoiFh4OCiYmGhoaEhYKEhXx/iISGiIN+ioeFioqCf4CEgomJi4aKgYqGg4WFg4SMioGDhIOHhYaKkYSBh4CEgIaKiomIhYaBh42SiYePio6Mi4KHiYuHi4mPjYqJi4yAiYyAio2AhouJioWLho2MjZGNj4mQjo2IiYuRiIuLjZCOj4mShoaRkY2ChYuIhYqKjoiHioZ+en7r493H1tfk8XuAhISCcOGAg4WHiIaDgZCLi5CKi4uNjYiIh4vhi5qbjYqLhoWBj4uMi42Gjo6Ni42Oi46Kjo+Jh5CGio2LiYuOjIqAhImIhoqHiYuPg4uNi4yJiIqOiI6MioyFi5GHhIyJjIWJiZWMj4KMi4yKjYiFiY2HiouFiIuIjIKEhIKHjImLh4OFfZCIiIqFho+RiIqNhouJjIiDioCDlYqCkouKiYGChoiGjYaEhYqLjYuMj46FiImKiY6GiImMiYqFiYmKj4mAhYyJkY2MjZGLjJCKiYyNiYiNiIyLhtHmhoSQiIyOhoiGh4yKiomJiJCPiYuLi4mDlJKPjoqLh4iFi4eFh4uNi46FjoiKhoeOjYOKj4mOko2IiY2Ki4iGj4ePh4mJkoWJhY2CjoeOj42LjJOZj5SKkYaJiIyIjYuJj4uKiY+Qh4eAjo2FiYuGhYXxgYKDf4iHio2NkZGNkZaZj42Tj4yEhHqFfYeHf4eSf4qJh4+GjX+Dg3yEg4OHgnuBg4SCl4ORh4iJhYuIiI6Ni4iPiIiKhX2Dj46LjI+KiYeKh42Ijo+PkYuOiI6Ri4qMiIqMh35+5oJ9fYSCkpKSlqOIg4GDeoCAiYmDhYWKiYJ/eoSCfICDiIeBgoaOg4OEg4iFf4OKg354e3h7fX+CgIKAgHt8f4mCgn2Ee4qBgIWFfH18gY2LjYmLhot9hX+CiYeChIeKh4mNkY6Omp+ZnId5e4GBhIB7e4mIf3978nt/hYN9f4GIfIWCh42EiIWFhIWAinyIgX6Actdydm9vb2xv2sfL1NrVa3BvcnR1c25rbXhvb3RzdXZ0dnF2dndyemJobmtubXZzeXR6c210eWxyb3LXcm5xbm5rxWNmbXNwc3Fxb3BwdWtvdnFxeHFub3R2c3dydm9xcGxrfHd2c3JvcndxdHBvcG9ycWxtcc1vdXZzcnVvb3CAcnZ0eHdvcXh1cGx1dnt1d3Z3cWRucnJza3Z3dnRzfnh5cXB5cnR2cXBlaW9vcWtwcNh3cXFzdG9zcHNvcHBua9p2c3JzbnV2Z27J39ZyeHh5dHFxceBqatvl6eB4dnx6e3qBe3t8c3d3eH14enV6e3R8f3NzcHh2cnPXcnR2d3WAdXh+dXB2enZ5eHd9dHV+bnl4entzeHd3bXV0eHh2dXl2eHR0dnl2dXhyc3FydGtvend5enRxfHV2eHpwdHFxcnl4fHV9b3xzdXl2dXZ8enNydnV3dHR5f3Jwc3Fyc3V8e3V1dHdyeXyAeXV8en99d3J5enx4eXl7eXh4eXpyenuAdnlzeHV5d3J5dXx6en18gXeBfXl4e3yAdHx9eX56fXiCdnaBgYFxd3t9dnt6f3l1enhzc3bY09C8v8PO2XF3e3x4WLt4eHl2eXh2c397fIJ9fn+Ff3l7en3VgJGagnx6d3d1gH2Aenx3gX19fn5+fH13fXx7eX93en17e3t6e3iAenp3dXp6en+Adn9+enl8enx/e39+e4B5fISAen16fnh8e4R6g3p/e3t6f3p7foF+f4F7e359eXR6eHV6fX9+eXd6dYN6fXh7e4GCe398dHp6fHp2fHZ0gnp1gnp4dmxtdnp0fHd2dXp/f359f352eX18eoJ4ent/fn52fHl6gXiAc357gX98eoF4fXx5fHt+fXh5e35/eMHReXh+eXh8eXx4d315enR6eYGAeHh5eHp4gICAfXd4eHp3fHp3d4F8e352fXV3dnd+fHR5fHp+g395eXx6eHJ2fXh7d3t9hHN3dXt0eHF3fnd5fHx+eoF2eXh9enl6fnpzenl1eHt8cXGAeHh2enp3dHXfdXBwanV3dHl1eoF8f4GCeHl4dnpwbW52cXl3cXJ9cnl2dX1xd21xcmtzdXV2eHF2enh0d2dwcnVydHl2dnl7eHN6dnR0cG51en54eHp3dHZ6d3h1fX59gXx+en6Dent6dXl8dm5wzXRtbHFtfHp7eHxxcnZ2bXEKd3d0eXR5fnZwbYVwcXd3bnB2enFzdHd7dnFzenNva25oaWxycG91dW9sbnB4c25tc21zcXBwcnBua256eXp4dm90cHZydXl1dHV5e3Z4en55eH17eIJ1bGxvb3Fya294dnBycd5vcnNrb3FucGt2c3d8cnh2c3J1cHlxe3RyAn9+h3+Gfqx/AX6GfwF+s38Bfrp/AX6OfwF+iX+Dfoh/A35/f4R+nn8Bfv9/u3+IfoZ/AX6VfwF+/3+9f4J+8H8BfuZ/AX72fwF+mX8CAgQAgIGD/YKIhIP684WA8vaA8/CCg4mIkJyPmJCMhImJj42UgoOKkYyGi4eshICDhPyAh4uMkYuUi4X8goGGhISOioH0/ICFk4WJk4mOlpGKj4uEjpiTl5eXmZeSm5iDjpWS/4qIio6GkYqLjImEgIGBipCKjPaAh4H09P2EgYOEgoKRgIeF+PePmJCEhZGLhoWGjY2IjYaTiYWOjY2Uj5aKjpiKkJWPkIuKi5aUioaAj4iKgZGGhIOAhIqPgIGBgfH2/oOAiJ6OhI6JiIGIif2BhIWJiYSEgIWEg4aCjYCCg4iRh5KNgoCEh4qQio2Ki4uOjo+Oi4mDiIn9h4mIjI+GiI6LgI2Oko2IipGNko+SjI+MkJCPj4+Xi5SPkIqQi4uTi5GTjo+Pj5KTlIyLmJONjZKSkJSJko6Nk5STkYyWjIyPj5KRjY2RlJSSgJKViImMjYmNjI+QjJKUj5STkpeSl5eTjI+bk5CQh5CUkY+WmJKZkZSPmIyam5GUlpWWl42RjpGVgJSTkZGTkpOdj5qTmpGYm5aSl5eSkZiZlpaWl5aSj5eOiJWSlpeRmZSWiZKPkJCUj42MioKJiouGg4P+gYWHjI+QifX/kJiXmZeYl5WblZKUkJyZlpOUk46NjoyNi5GTjpCNkJKPjo+Sk5CNkoySl5GVmZiVkI6QjZiXl5KSk42TgJaPlZCWj5WWlpeVlZGQnJaSmJadl5aUkZmXmZmWlJWTmJeOkZWTi4yPjIuQkZiQmJOLjY2PjpCLiYr0jo6Ni46KhouQkpCWkI2QloWUmZeYlpSPk5OSl5iUl5SZlZeRjY+WlJWUkaCSmZWSlZSUl5ORlJCVlJSNlZaPjpWTk5GUgJqUk5OWmJeWko6XmJaalo6YmZaNnpOVmJqYmpiZl5iSlZGQkJOPkJqSkJaPlZSSk5CXm5uSl5SWjpSVlZeWlYmOlJCWmJWRlZiSk5WLlJWUjo6TjoaRjI6Nj4+Olpeam6CYmZyemJmWmZSVlqSbkpORj4+RlI6Qj5OVi5CTj42UgI6Sl4+Vj4mKiomAh5CSlZeemp2WmZyUkZumtJ+YmpqWkpGQnJOTmo6Olo6VlJaOjpOXioeDhoD9l6WThJizuKmpuKeZmpqRjIuSkpSZnJSMg4iYk5WPlZmWnJiWlpiTmZeWlJCRj4zp6YuMj470g42Hh4iTm5CdoJ+uspSOkY+HgIiLi46Pk4WEgIOMoJePjYaKlpWNlYmJkIuQlYyOjJSThYePkpOLjZWPjZKMkZSZjZiVlo6YloyalZWUkZ6PiI6IjIqKkIyUj5mTmYqAjZWRk5KSmZqdmZ+usKKIgfuEkJGNiY2Q/oGD84KZmZaWjoqPk4eTlpOXlpaPl5uOiIv/gHx99XqAfX7w5nx45Ol95uF9fH5+hpCCiYSBfICAhoGIc3qAhn18gX2ZdHZ7fet5foSDhIGIgH3qe3p9fH2EgHvu8np8iHx9h3yBi4Z9g394gomCiIiHiYmFiox4hIeE7YJ9f4N+hn+Cg357eXl6gYWBg+l7fXTk6ep8enuBfXuIgH5/7u6JjYV3fYeBf31+hYN8g36GgHuEhYWKh4mDhI2ChImFiIN/f4iDf355iIGDeoeBfnt7f4OHe3t6eOjs8H55fo5/e4eDgnyCge9+fn6Bf3t/e36Afn98gnp+foGJgoyFfHl9f4KLhYiEgoOHhoWFg4J9hIH0fn6BgoZ+foWCgISFiIN8gIeCioaIhIWDhYWIhoiLg4uJioKEgIaJgYeKhYeGhYaJiYODjYeEgYiHhYqBioaDi4qIiIGLgIKHhYiIg4KEiYeGd4mNfn2EhICFhISGgoWIhYmIh4uGjIqKhIiOhIaIfoaJh4aJi4WNiYiDjIOSjoWLjIyMioKFg4eKKoaHhIiJiYiPgpGKj4KNjYyJi4yIho2OjYuJi46FhoqFfIyHjIuEi4iLf4SHgIuFhIaBeH+Ehn98fPR7foCHiIaA1OSFjoyOjYyLiYyLiYmHk4+Ni4qIg4WHiIiFiYyIiYWIi4WGiIuNh4SKhIiMho6RjomGhYiHj42NhYiIg4iNhoqFjIaMi4uNjI+JhI+JhYyKko2Li4aNjZGRjYiKiY6OhouOjIWEiYaCh4mNXIaQiIKFgoeGh4aAhOmJiIiFhYJ+g4mKh42GgIeLdYiKiouNiYWJi4iKjYmOio2JjYmFgoyJjIqKmYaMiYmKi4yLiYWKhomLjoOLjomCi4iJhoiOiYmKjI2MjIiHhI2AjIaNjYyElYmIjIyMjYyRjI2HioeKiIqEg5GIh4yFioyJioWLjo+JjYmLhYeKjYyNjYKFi4WNjYmGjI2GiYmDiIqKg4WKg3yIgoeDh4iHi4yQkZWLjo6RjY6GjoqGh5SOhoaDhYWGh4SGg4mHf4SEg4CIgoiKg4yFgYGCgXp+hYeAiYyPjI+MjI2GhIuWnY2GjIuKh4aDj4iJjoGDioOIhoiAgIiOgYF8fnrviZSBeoiZmpSRnJOMjoyFg36GhoWMkIeDfH+PiouEjI+KkoyLio2Jj4yOjIiJh4PV1YF/g4PnfYR+fH6HjoOQkZCbmoSChYN/gIKChYeIfHt3fYOSioVyhH9/ioqCiX59hYOJioKDgoeJfH6Fh4iEho2Eh4mChoaLgYuIiIGIiYCMhoiFg4t/f4aBgoCBhoWMhJCIj396g4iIiIeFi4yMi46Xm5F/evJ9hoWEg4SE6nqA6H6LioqIg4GDhn6IiomJiouAiIp/fILugHJy221xc3bi03Ft0dR31cZubGxvd3tyeHd0bnFxeHR0ZW1vdW1ucG57YWhwcdFrbnZydXVxbXDUbmxzc3B3cnDZ1mttc3Bwc25tdHVtcm1ocHVwdHZ1dHR0c3ZndnFu0HNvbnRzdnBzdm5xbm9vdnV0ctFva2jM0tBtbG13b298gG5119R4d3JncHVxcG9ueHNsd3FzcG51d3l8dXd2d3x0cXd0dnRwcXJtbnJtdHB4b3R0c3BxdXd4bnBtatbY2nFtbHlxbnt2dHF2eOB2dHZ1cXBzcHB2dHdycHF0cXN9eX56c3BzdXaBen14dHl5enh5eXVyeXjgbm9xcnZvbHZ1gHNyeHdwdHZ0fHZ3d3ZzdXV6eHV3dXt6e3N1cXZ5dXh5dHl5dnV6eHF2fnV0c3h2dXpyeXdze3p4eXJ6cnZ7dHR2c3Rzend0aXh/cG10dnR2dHR6dXF1c3p2dHp2en19eHp9d3d7b3d3eXp3eHV6enl0d3aEfXh3eXh7dnR0dHp3gHJ1cnN4end5doB2eXR6fHt5eXt4dnx4enp6fHlzdnt2b314fH11e3h7cnl5e3d4dHV5cWx1eXl4cXPjdXd5fH14crC+c3p5f359enZ3e3h8e4KAgHx6fHZ4fH5+fIB9f314fHx4e3t8fXt3gHZ3fXZ9f356eXh6dn19fHh9fnh5gHx7fXZ8eX18en19f3t3fnp1eXuBfXp+eYGCg4aEe3l8gYN6fn98dnZ9enh3f4J4hoF4enN6eXp7cnvZfHiAe3p3c3R9fHl+eXd7dl91fXt7fHd0eXx5fX12gHl+e317eHaCeX5/gI15fnl/en5+ent7eXh8e312fIF5cX14fHN5gH56eXx+fHx+enN5fHx7fHR7eoB3hHlzeXl6fnp8eX55eXZ6eHh0cX95d3p0d3p5enN6fX56f3p8eHV6e3h5fHV5fXZ8fHd2f314e3h2d3l6dXd+eWx3c3h0dXd3e3l9fYN1d3p9fHt3fXhxc3h6dXF3cnJ1dnB0cHd1cXVyc251DXF5e3R5dnJxdXFvcXSEeIB1e3l1enVwdH17dXV5eHh2dHJ8dnh8bnB2cXRzdW1udntzdm9wcNd1d2Zpb3R1dW51eHZ5eHJ0a3Fzc3d6cXNucH58eXN5eXd+eHh3f3uAen57eHl4db+8c29vc9NydXFtbnN4cH17eX53b292dHBxcnB2eXduaWpwcnx2cnR1bm93dm92bGtzdXt6dHJydHhsb3VycnR3eXF1dXJ3cXFweHNxa3R4cXhwcm9ucm1ucW9wb3F1dXx0f3R+cXFxdnd2d3R2eXZ4enx6eXBu2HFxdHRwcnPWb3Xacnh0d3Fwc3F1cHZ2d3l6d2twc3FvdNQDf39+hH8Jfn5/f35+f35+nX8Bfol/AX6If4J+nX8BfpJ/B35/f39+fn6Jf4J+uH+Dfox/AX6qfwF+/3/EfwF+h3+CfvZ/AX7/f/1/AX6vf4J+hH8Bfu9/AX6HfwR+f39+ln8BfgICBACAgfWIkoKDhYGIhfr49vX4+4GGgIeLioCKh5Seq6OIkY2Ph4uJgZKHgYqmlIqKjouKh4WLhYyOjoaJhoyPgv6Fhf2DhoaKmZugo5qWjomKi4yLkISPlZ6clpSLifyTj4mNlZiR/IOFhIOCgIKCj4OGi5KGj4yAgvaEioaIio6Nj5SAjYmGjY6ZlJKRjIaLlIuKhYaLioqKkoyRkJCOiY+Li4iSjY+NkI2Li/n4hYqKhYyJhYaGh4GGjvb+gIKB9ICGg4SLpaiCiY2OjIeQkIuDhomKhYqRiIKKiYmIh4iJhoWPipCJi46Gi42Mjo+GkI6KiP6KiYqHhIaOhYCHj42SjIiAipGWiouRjpOTlZONjI6RkIiMjJOOjoqWlI6PkJCKjpKMk4yMkI6PiZSUjZGTiJWMjI2Sj42NjJaMkouQjIqOipGRkpGRkI6MkYyVj5KTleOOjo6Hk5CQk5iRk5KWlI+JjJCXlJSOlJWTlJGWlJOUlpeRm5idkoyXko6Rl5qXkpqAkpSWlI6QmJKYkZGUnpuPnJSWlZCKlYyLlo2VkJKUkpmTlY+Pk46UmZGQkYyUiYqNjYiIjI2HjIiGhYmJi4qTj5CUlZCPkZCQj5KRhpeYkpaPkpePlZyamJOPi4aJhYyMjIuPjJCHhI+Qj5GTlJOPkpOVlpmQlJaSjo+UlpOPmJiAkpeZnJaSlZeXk5WOmJWXmZmUkpaXmY+UjoeRko6RkJmSi5OUk5KQkpCUjf75i4qIjIyRi4uLjIaEgfuEiIeFh4aGjYuQipCNjZKTjYuUkZKQl5GTlpiRj46UkZeTmpSYkZWPjpaUlZeLjZqOkpCTjpWTk5SMkpGKh4yLk5CQi5SAkpOYlJCUkpmTjpKVkpiTlZOWnZyUlpSanJOclY6ZlJSRkJWWlZSMko+QkouXkpqSmJmTj4uSkI6QjYyUkZCTj5GUkpGQkpKTk5CPkZGQkZKRk46NjoqBh46jnZCPj5yUmpedmJibmpSRj5eXoKyVi5eNlJSTkYuGjI2Pko6Tjo6AhoiNj4aLhIGHjJSRkZWao6KsqqqSnJCUnZ2jiqGgmZaVmJWQl5iUkpCMk46VlZCHg46J/vb1+pWm2d2VnIGQmaKgkZGeo56SlJablJialIyAioaJkJWUkpOdkZSSjJCOlpOGkpGO8PuGhYuHhIaKjYSHhZWZlZafnJmnrKeNiYaAjImFkpWNjo6KiYONjY6bk4uJkJqUlImQkI2JjZKKiI2SiZCMi5mUkZWSl4+KkpCDkJWQj5uHjI2Mi4/giomHk4uMhIeQjZGXkImQjZGLjYuPj46UkJeNkpKYl5GFiIeRiYOJkY+EgYb5hoeeh4KTkIqDiICAhpONh4SOgKajhIWAeOiAiHp6fHuDfOnt7ejs8X1/eX6DfnZ/gISJlZF8hYOHf4SCdod8d36QhH5/gYCAf32DfYSEhn9/foSHeOt6eux5fX5/i4yNkomGgnuBgX9/hXiEho6Mh4R+feSGgn6CiY6L7nl5enp2eHl4hHl+g4h+hYF5eep8goB/g4WCh4mAgoGAhISOiIeHgnuAiIKDfX6DgIOCiYKGhoaCgIaDg4CIg4SDhoWAgOvmf4GDfoaEfoCAgHt/h+r5fXt76nuAfH1+kZF5gYeDhX+FgoJ+foCAeoCDf3yDhIKBgoCDfnuHg4iBhIR/goaGh4h+jIWDfeyDg4SAfX+GfXl8hYGHg4GAgomLgoOEg4qJi4iHhoWGhX+DhYqFhoOKiYaIiIiAhomDioKDhoeHf4mHg4WIfomDgIWHhIOHh4+EiH+JhIGFg4mJh4eGh4aEh3+KhoeGidCEhIR9iYWEhomGiImLioZ/g4WMh4eGiomGh4iNiomJi4yGjoqOiIGNhYGFiYuGhI+AgoeLioOIkYmMhYaMkI2HjouLjYaAi4ODjoKKhoiJh5CIioWBhH2HjYeHiYOLgoSGg39/hYZ/hIF9fIGBg4KJhIWLi4aDhIaIhImJfIqNiYuFhoqEiJCPjYiJhYGEgYeHhoSJhYiAfoeHiImJiouEiImOjJCGiY2IhYOKi4iDj46Ah4uMj4uHioyPi4qFjYiKjIuHiY6NjoeKhH2HioeGiZGIgY2PjIyIiomMhO7pg4SCg4aLhIaFh4GBffJ+g4KAgn9/iIOJg4mEhI2LhIOJhoeGjYuLi4+EgYWIiY+Ij4uNhouFgY2Ji4qDhpGFhoWJgYqMjIyFioqFhIeHjYqJiI+Aio6QjYaJiI6LhIWGhoyHiomHj5GGiIaPj4iSiYGOioyHio+NiYqCi4WHi4OMh4+IjIqGhYOLhYaIhoWMiIeLhoaNhoeEhoeIi4iHiIeHiIuKjYaGh4N7g4eQkIODiI6IkYyQjIyPj4iChI6IkZmIhIqBiImKhoN9gYGGiIKGgoSAe32Dhn+Efnt+goyHi4yPlI+XlpaDjYKJkI2OfI+NioyMiouDiYyHhYaDioOJiIR9fIaD9uvp7oePv82Eg26AiYqMgYOQkI6EhIaOh4iKioR7gn+BhouLioiRhoqKg4aHj4t9i4iG3eV7fICBe36EhXyCfoyMh4qQjY2WlZKBf32AgYOBiYqFhYWBgXuDgoWPhoGChomFiX+GhYaBgYaAgIGEfoaAf4uIhYiGiYSAiId7hYaDg4t8gIB/fobOfX2Ah4CDfoCJhoSLh4CGgYV+g4SHh4SIhIqChIaJhoR8f3uJf3uAiIJ5eX7qgICOe3eDgYN6fnd4f4eAfHd+b4+NeXqAbNJzd21tbW51b9DX1tHV2HJyb3F0cGdxcG1tdXZpdXh4cHd1aHFtZWV0b29vcXBtdXF1bnJzdnBzb3B2atJsatNrbXBscXFzdXB1cGtxcm9wbmhydHd5dHBubsl1bm5xdH581Wtpbm5pbWxrdG1xeXpycnVua9VudHRwc3Zyd3qAdXRxc3R4cXV3c2twenZzb3F5dHR1eHV2d3hydXp0dHd4cXZ3dXRxcNHLcnJ0cHl3cXJzdHBzfdTkdHFx2nF1cXBocXVwdXh0eXB2cnZydXRxbnJwcHB6e3dzdnV1c299ent4eXZzdHp6entxf3d1cdl2eXh2c3J3cm1vd3J0dHOAdXp8cnR3cXt6fXl2d3d1eHV0dXp0d3R6dnZ7enh0endzd3J3dHh2cnd1dHN5cnd4cnd5dXN7eIB4eXB5eHF1cnl6d3d3eHh2d3B6dHh3fMR2dndtenNzdXd3dnh9e3ltdHN8dnZ4fHh3e3l+fHx+fXpyenmBeHF6dHJzdHl1d3uAb3V7eXN5f3t+enh6e3p4fHt4fXhzeXJ1gXR3dnl8dnt2eXVyeHJ2fHh6fXl/dXV3dnN1eHZ2enpzcnNzd3d4dnd6eXdxc3Z5eHx5bHd+e3lzd3Vzdn5/fnl5enZ7dHt7enh/enx3dHd6fHx6en11eXh+fYF5eH96eXN5e3hzgX+AeXp7fXp3e3l/fHp1fnp8fHx4fYF+fnuAeG54e357fYV6dH6AfoJ+f3yAeNXSeHl6eXp+ent4fHd3duF1eHp7e3Z3fXd+dX12d4B7eHl8eHp4fX56eIJ1dHd8fH97f3x8dH56dIB5fH13doB1dnx9dnt/gHx4fnl3eXp7gX99fYCAeH+BfHR6fH56dHN4dn14eXp4fn93d3h7e3l/eHF/e313fXx+e3xzeXR3fnF6dX55fnZvdXh8dnp/d3d+d3Z7eXZ+d3Z1dnh3e3p3enR4d398fXt6d3ZueXp7eXFteXt1f3Z7d3l3enRwcn10fX1zc3Zwdnd5dHFub3R0enZ6cnOAa3B3enN2cW1wdH55fHp9fXR6eHVtdm91eXRyaXd3eHl6eHxzd3t2dXRze3V2c3BucXZ35dbM0nFum7FsZVlpdWhuaWp5dXVubWp3cnBzeHRudG9zdXh9e3l9dXt5c3Z2f3xue3h3vsNtcGxxbHd2dG11b3p4c3l5dXZ6d3dycW6AcXZ1dnl2dXVydG52b3R7dHJ1d3Nud3N3dXl1b3VzdG5zbnVwbXd3c3RzcnFxdXFqbmxtc3JscWxwcX7bcm9wcW1wb3F4d3Z4dm93b3VrdHV3d3N4c3V0dXR0cHJrbWx6b25seHRqa27Wc3R1aWhqbndubGlrcXVsamRoWnRxa2sCf36If4Z+rX8Efn9/fpp/AX6HfwF+kn8BfrF/gn6NfwZ+fn9/f36zfwF+3n8Bfv9/3H+Cfo1/AX7/f/l/hH4Ef39+fqt/gn7TfwF+rH8Bfpd/AgIEAICIi4aHiYaJhICHg4z2/ICAiomEiICIgYSDi+aGmZiGjpOIjo6Mkof9g/GZso6FgoyEj5qQi4b9gYuL+IWOk4qGjpWTn6Wcp4GDi4mMkJGWjI+Uj4WMl5qblI2NjIqOjIWEgYiIhYyKgo2Eg5KMiYyNhIGLjo+O/oWMj46MlI2Vj4CRjJKjm5iVi4mHi4yIiY6IkIaKiY2EiYmNj4qMj46NjoqNkJWViomQiIyDiouOhoiHgIKGh4qJgoaKg/v/goGJgpWmlaeOhY2GiIP5iIGLjY+Bj4aBgIT4hYaRioGLhI2MkIqOjIuKhIWIiIWOiomIh4CMgIeMh46IiIuQlpCNhYCPjJGOkJKMiIiTiIqPjpCVkZKLk5GMi4+Qj4+KlY+QlZGQlIuNlZGNkJKSkpGSj42Oj5KJkY2QkYyG84WRiouKkJKQj4eRj5eOk5aRkZGTkZGUkY2SlY+Pk4+XlZCUlJick5GNkJCUnJeYlpCUnZOZlpeVnJeXmZaSlYqbmJaLkYCPnJqSmpWNg4+VkJSYmZWbkZKQlImMjYyJlpeVj4+SjZCYjoqHiJiNkYeKiYeCgouCiIeHjY2Ni5CRj5CJj5SRlJeVkI6NkpOVkZOYlZWXkJShqKixtq+hl5eOlYiHjIqKi4mCh/2Cg4yGjI6SkI6Mj5GUkZOak5OVlZSZlZ+am4CWm5aXmpuVjqGRmJCSkpaZmJOUk5aYlZaTlJGSjZCUkZeTj5OQl46WlZCVkoqEh4eGhYSCgoT2/oWGk5CLhoWKhYWFg4aMj4yMjZCQkJSUlZGQlJCQkZKTkZKTlZSNmJiTmJWIm5WMmZCWkY6OjJGPkY+SkYuMlJKTko2Uj5KVlYCXl4+QmZONkI2Vk5OUnZmYkpmVl5eWjpWSlZSTmZmYlZOXmJWdmJSMk4yQlZaVmZCVmI+Xjo6SkY2OkpiMko+QlI6TlZKRlZWRjpWTkZONiIeJiomNiouLloiZnJmZn5+cnZuTlpSRlo6Oko2XqJuWkYqMkYyKiIuKhoeOiYKFkICE9/uCgIWFhY+ahIecn4qXlKSerpXQ6YiVoq+kpZmbk5aXmo2PlZaSh4mLg4qIg8fYgvbr6oWNn/iAnqiZo5m1pJiPiI2Nl5aQmJOWlIj/9/mA/4H9gIeNlZmTjI2KlYqKjpGKi4WAgP75gIGEg4eFhYiNhISMlZqSlpSOo7eTh4CJiIeHk5KMh4qMiYuMjJCHjYqPoq2Vjo2Nj4+IkI6Hgo6Rj4uJmJ2cjJCamY6Kk5KLmo2HqI+Rj5WNkoeMh4aPi5KbjJKNkJKUl5aTjouRlIuSlpOSmJGSh4WJl5SPiouJiYeGhI2PjZGNk5CEiYb/g4OOhICG9+fw8f3m/Z3+h4CBg31/fn6CfHeAeoHq73t9h4J9gHp/eHl5gdZ7h4V5g4d9hYOAiH7rfeKHmIB7eoF7g46EgH3reoOE7X+FioF8g4aDjZCKlnN2fXt/gYSJfn+JhXp+iYiJhYF/f4KDgHl4eoKBeH1+eYN7e4aAg4WEe3yBgoWC732Eg4OCi4OJhICGgISViYqIgYCBg4V/gIV/h36CgYZ9gYCDhX6DhYaEgoCDiIaHg4KGfYN9gISDf4CAfH6BgoSCfYGGe+/5e3uCeYaVg5GBe397fnvngHyCg4V5gnh1eHzsfn6IgXqBe4SDiIOHg4GDfn2BgH6Fg4KBgXuGfIKEf4eEgYOGiISEfoCIg4mFhIaCfn2HfoGFgoWJhod/h4aEg4WFh4eDjIeEi4mHioODi4mCiIyKiIiJh4aEh4iBiYaLiIR50H2GgYKBhoeFgn6Hh46EiI2HiYiHiIeJh4KKi4aDiIKNioKIhYyTiYiFiIaIj4qNioeKlYmMi42Kj4uIi4uIiX2Pi4qBh4CEjYyEio6Fe4aKh4qOjYqRhYmFhoCBgYN9i4yKg4WHiImPhIJ/e4iBiH6CgX57e4J8goJ/hoeGgoeIg4aBhYmFi4yLiIWHi4yLh4qNio2Ng4WPk4+UmZiNiYqEjYSEh4OFhYN9gvZ9fYN/h4eKi4aGhoiKh4qPiomJioqNipKNkC2MkYuLjpGMhJaIi4WGhouOjYmMio6SjYyJi4iKhouNiJGNhoqHjYOOi4eKioSEgYCAf359gPL0fX6IiYWCgoWAgoF8f4WIhoSEh4eHiYmLhYSMiYqJiYiIiIuNiIaPjIaMin2RiH2Mh4+JhoWEiYqNi4uJg4WLiYyFhY2Jh4qNjI6IhpGKhIWGjIqJiZKQjoeNh4qLjIaNi4qMi4+NiYuIiYuHkoyJgomEhYqNjZKJi4CShYyIiYuIh4SKj4KHh4eLhoeJhImLjYiFi4yKiYeAf4KFgoiGhIOKeIiHiIqOjoyOi4WGhYCDfn6EgIWVi4mHgYGDf39/hIJ9f4WBenyEe+TwfnV+fnuFjHh+kJJ9hoGQi5eBs815iJKblZWHjIeOi4x/g4mKhn2Ag3yAf3q1y4B76ObjfYCM2XWNj4SKhJmLh4J+goCJhYKKhIaFeujp7Hr0evB5goWLj4mDg3+Jg4OFhH5+eXR58ex6e3x9gn1+goV9e4GJjoeLiIKSnYV/gX9/fomHgYGBgn6Eg4OHfIWBhZObiIWFhYeGgIeFfniCg4WCgo6NjYGDi42DfoaHgU+IgXyXgoWEhoCGe4N/foOCjY6CiIWHhYeMi4eDfIaJgYmKiYWKiYV6fH2Jh4OAg4CCfnx7gYOAhoCHgnh/fe98e4R+e4Pp1uPm8dzniuKAgHJ2bG9tcHRwZnBtc9bXb3J8dHF0bnBqamtsumdvb2h2dm1xcWt1btBwyG53bm1scW50fnBubdFrcHLTbnF3cGlydm1xeHJ2YGVqbG1sc3VraXRya2t1cXNzcm9rcnNwamludXNobG9qcG9tc293dnVvcXBydW7WcXdvcHN9c3VxgHVucoBycXJvcHR0d3BzeXV6c3Z2e3BwcnV4cHd6eHNxcnV6dm9yc3xzdXFzdXVwc3N0dnh2dnR0dndw2eNxb3RqcHVmdG9ucnBycNRxb3RzdW95YWBocOF2dHdzcHRweXh8d3l4c3dzb3R0cnV1dHJ2cnpzd3dveHV1eHd2c3JvE3hzenR1dnJwbHRxcnR0d3Z1dnGFd4B0dnp2fXh0dnV3e3VzeXZ0eH17eXt8d3h3e3p0e3l8eXJyz3V1b3V3dXd3eHJ7eX9zdXx6e3Z2eXV4dnB1d3Zze3B8dm92b3uBd3d2eHZ2fXZ5d3p7gnl7eHt2fnp2eIB7eWx+fHR1e3d1eHN4f3dtc3l7eH15eIF1enZycnJ0doBufX17cHZ4fHl+d3h1bXdzeHF0cW9xcHhydnZxeHx6eHp9dHZzdnp1ent6d3Z5fHt5dnt5eXx8cHN0dmxvdnRzcXR2gHp4end8eXp1eel3cnl3fHp4gnp7enx7eXuAenl7fHt/eYB+fX+AeHp9f391hHh7dHR1eH15eX17f4h6foB4gXx+e4GGeYN8d39+fneAfnx+f3p4eXh5e3d5dXXk5HR0d3x7enl7dHt7c3N3fHl1c355eXt2e3Z3enp8eXh7e3h7fnV2gH94fn1xhHp0fHyAend2eX2BhYOAfnh5f3x9dnaBfXh/fXmAfXqBeXRyenp3enx/g4p6fXl+f392fIB9enx7eXx6enh4e3aCf3hzeHl2ent+f3x6f3N8d3t7eHh2eX1xdnh2e3Z4e3V8eoB5dXh5e3t5dHJ1dXZ9eXVzd2Rya292dXd0eHNqb29sbWprcGxudXN3dnBwcHFxcnh2cHN5dW1wdW7Y3XNocW9rc3Zkb39+am5pd3R1Y4qpaIByeX1+e250dHt4emxweH1zbm9ycW9uarvBbdHW0m1rbqtjc21nbWlyam5wb3RtcnBscm1ub2bK0NZp2m7ebXh4en53dnRrdnN2dnZua2RhatbVbW1tb3ZvcnV2cG5xdXd0dnh1d3txcHJwcnF4dXF1cnVwdnZ0dm52c3R6f3V3eGh2eXlydnduaHNzdHN1enh4bmxydnJocXRycm5pgG1zcXV1d21wbm5yc3p9dnl0dXF1fHt3cmx2enJ4eH90fXlwa2xsc3Nwb3RxdXRtcHBvbnRvdWtkbGzTbm92cXF517fLz9fCxG7AcYx/gn6MfwF+jH8Dfn9+jH8Ffn9/f367fwF+xH+Cfo5/AX6LfwF+4n8Bfv9/q38BftB/gn7/f8p/gn6Sf4J+mH8Kfn5/fn5+f39/fpV/B35+fn9+f36Tf4J+/3+GfwF+hn+HfgN/fn8CAgQAgIuIh4aGhoCHhoGFh46QjpWako2NioSXjImKg534+4ydmpmOjZOKkY2OkfmOlY+NjJSMhouLjpCIgYn6iI+MkpiOk4f7+4OKgr2nl4yPjJGLi5Gbl5KLiZGRoZGJipSD8oeO/YGKh46Lh4uJ9oCMi42BjpCUhIKBgYeIgoKIh4iCgI6Kgv3/mI2Ni4yMiomMjoOLkI+Fh42Oi42QkYyLj5GPkpSRk4iKjpGLi4iLlYeHhIGLiJKIhYiKi4z+g4aJhI6Jo7COoYX4goqHhYiHi4eJ9IGF+O6A+YOCiIiUkIqKjI2IhouNkIeJj46Ih4aKi4aFhYeOkI6FioqRkJSLiY6VgJqQkJGKjo2JjYqJio6QkJORkY+JkY2TjZGMj5CHjJGQkpCPjo+XkpaRlYyOjY+KhYiOnpORjY6VkoqFjIyRiYiRkY+Qh46RkY6NkpSQk5KOkY+Pl5WSi5STkpWTmZiYno6OlZKSkJWcmZKYlJGTj5SMk5KPmIuXnJOWj5mUlZCYgJqTk5WTmJSHk5aRlpiXkpKUlI2TkpCWj4uWj42Nh42QkIyPh5CSkYyDgs7BxNHjgfL3/f6DhIeGi4uOkZSSj5KPkpCSkY+Pj5GRj5SSi5OUko+HkZqgsMG7v66nlJmTjoSMkZCRjY+Rk4qDgoaEhouOi42RkJSVlpSWk5KUj5mcgJSSlYyWmZaVnJaZk5GUlZSTk5yXkpOUmZiRiZmXj5OVmJGJjJSRjo2QlY6Gj5WUj4yKh4CJgoiMjIiI/5CE7ZeJiYyRjYOGhomPj5GTj5GZj5GNkY+QkI+TlZOXl5SXlJGVkpqUmJOSlY+QkYqOkIyNkI+SkpOSlZWSk5GQlpGVeZOTkI2TlpiWlJCUk46TlJmUnZyRk5KOl5iJjZqclYyak5mWmZebk5CRjJSLj5KUlJOSjIyRk5eNk5OTkJKWk5eSlJialJCPh5iVj4+OiYuKh4+UkYmQlJ6SnZ+Ypqmelo2GhYL79O739P/t1df3jpGMipSSj4iGh4eEgICGjY6Ok/P1gpGNjYyUiqD8156QppuqoZmli/epnu+BprOni5KUkpeSmJmShIyHhJOOiIDq+e+DmJiKgayfmJKSsrqclYyHio+AhYz3/46ciIWPiISAg4aIgfyEi5eOkJKRlo6JkZmYjoCD/viBiISD/eyHiYmEhIeFjpOWkpSIiIC00MOOhouMiZCPjZGNiYqLjZGJioqHiJ61nZGLjIqKiI6KiYaOkpKOjZWjjZCOmp6TkpGRnY+MlpKTkpqPiIeVkZKQjIuckI+SlJGTjo6QkpaSko+OjZCLjpmZjYuMjI+Qjo2QiIuIi4aDh4+Ni4WHgo6PjYqPl4uA8fz19Pn26QOagoUBgYR+gH14fn14fH+FhISMkImDgIF5iIF/gHeN3uN+ioiKhYSIfoOBgoPmf4F/gYKIf3t/foOFf3qA7X+DgYaGfoZ65+x6fneflIh+gX+HgYOEjYmEfnyEg5GCfH+Jed59hOx5f3+Dgn6Cfuh4gIKCeoWGint4enl/gXh6gHx/eoN/ePHygIyBg4OFh4OBgoN7gYaFfn2EhIOFh4mCg4eHh4mJg4aAhIeLhIF/hIt/gHx7g4ONg32BgoKB835+gHqEfpCafot45XiAf3yAfoOBgOd6fenhd+x8e4B+hoWBgoWEf36EhouCgYeIg4F/goOAgIODhoqHgISCh4eLhYKDh4yFhoZ/gISBgIWBf4GDh4iHiIeGfoSFiIKFhIWGgIeIh4mHhIWGi4iLiI2DhYWHgn5/gI6IhoeEioh/fYOGhYJ9hoaEhYGEh4qEgoeIhoeIhYSEho+Lh4GHiIiLiI+MjJGEhoqHiYWLjo6Gi46FiYaKhYuIhY19i42Gi4SMiIeEjIuIiIaFgIuHf4aKiYuMi4WGhoaChoaCjoeBioSDgYCCh4qDh4CIiIeEfHvFv8DO3Xnp6+7zfH+CgIWDhYiLiIiIh4qJioiHiIeGhoiIhoCHi4iCe4GHh4+bl52VlIaLiId/hoqIioOHiYuFfnyBf4CEiYWFiIiLio2Ji4iJiIWMkImIiIWOgI6JiZCMjIeHioqHhIeRjYyLjY+KgoGLjYKJi5KGgICJiYWCh4mDfoWQjoiHhoF6hHuChYN7e+eFeduMfn+Dioh+gYGDhoWGi4WJjoOJhoqHioeJiouKjo6IioiHjomPiYqJh42JiImCh4WIhomHjIuMi42NioqIiI6IjImKiYaIgIuPjYyGjYmBhoqOipaTh4iJhIuMf4eOkImCkoiOi5CNkIqIiYOKgoWKioiKiIGAiIqPhImGiIaIioWMiYuMjo2FhX6PjIeIhoGDg4CFi4mCh4mNiI2FfYyOgHhzb29tzszN19ffzLe/5YKHg3+HhoWAfYKBenx7e3+EhYWE2uB5gImDg4WMgJHavIt+koeXjoaRetaUhs1zk5yQgIeIhIqJiomGfIR9fIqGf3ri8Oh6i4p8c5aJgH+Bl5mEgnx7f4N2e37g5n2Ie3uEgX98foGBfPN+g46FhouIjYSAh4uFf3d37u96gX1/8+KCg4B6foKAhoiMh4l/gJ61qIF9goOBe4ODhYiDgYKEhYZ+gX9/gI+fkIaCgoGBgoSBgXuDhYaDgYeRgYaEjIyGhoWIjoOAiIWHiI2EfnuJhIWDgoOOhIKGiYWIhIWGh4uHhYKCg4eAgIuJe3+DgYOCgoOFfn9+gnt4fYSDgnt8eYGBgX2EioB54uzn6ern1Yl0fIBzb3Btb29sb25qcXN4dHV8fnpzcHNrdXJybmN0vb5nbnF2dnR3bm1yb27Ga2tucG52bmptb3FzcG1y2HBxcnVwbnJpx85tbGN8eXRucWx5cXZxenNubGtvcXxtbnB1Z8VudNNscnJxc29ybNBtcHN0b3d1eWppbm5ydWttcWtwa4B0cGnW1nNvcXV1eHd2dHRwdnp3cm50dXZ2d3p2dnd2dnt4c3VvdHd+cm1uc3h2dHJtd3d9dXJ2dnN04HV1c3B2b3R7am5l0m1wcnFvb3V2ddRuc9C/ZtFwcXVycnN3d3d5cXJ3eoB2c3p6d3Z0dnZ1dHh3dn96cXJ1fHp7enZydIB5d3d2bnJxdHhwcXRxcnd0e3lzcHN5eXR3dnd6cXh3d3h3dnV2dnl8d3x0dXd5cnByb3Z2d3p1fXtzdHZ5dnd0fHh2dnV3eHdzcXN5d3N4dnJ0dXx5dHB2d3d9eXx7eHxzc3Z2enV6d391enx0dnZ6dXt2dXpqeHl4eXd8eHRtfIB8d3Z2d3t0cXV6dnp+eXR2d3VydHh0g35zenR3cXF0en91eXR5eXh5bnHKxcbM1HLd19/ecnZ5dnx1enx9enp4eXx6fXl4enp5eXl2eXF2gHtvbW9uY2Vpa3Fzenh6eXp2enp5fXV8fHx4dnV3dnV3fHd2eH5+f4B7ent7d3t6gIB6eXd5fX99e4B9fnp6d3p6eXaEgX99e4F7eXh7enV7gYZ5dXV5e3V5fX12dHqFgYB+fnVye3N8dnJpZ8hzaMF3a253e310eHZ2eXl6f3d6gHR7d3h7fnl6en54gIJ5fnVzfXx/e3t7fIB9eHh4f3l6fHx4fH1/fHx9fnt6d317fIB5e3p5fHh8fXl2fXhxdnmDfIeCeXh6dXt5dXl+fnh1hHqBe4B7gn97enZ2cXN5d3N5cnJzd3qBdXl4dXZ2fHV9fH19foB4dnB+fXd7eHh2dnN1enhyeHl1dnZnWmlrXFZTT1NQlpaira+3opCexnB0cHF4dXVycHd2b3Fvc3N4eIB1cLfFbnhvcHd5cHmsmXBpdW18dW1yZLJzY5xfeHpya3R0cXd3dXVxb3ZucXh4cG3Q39Zud3JlXHNsYmVocW9kZ2drb3Jpamu/wGhsZmx1dXVwcnR0bt1yc391eH15e3Vxd3ZraWpp2dlwdHBx4tB4d3JpcnN0eXh6dnlybniFg4BwbXV0cXJzeHp0cnN1dXRsdHFzcnmBenVxb3NzcnNzcWxydHRubHF2bnRxcXZvcHByeHBvcHF2dndwbmp1cXR0cHN6dnZ0d3V5cnRzdnh4eW5xc3VucHV1a21ycG9udXR2bnFvcmxrbXVxcm1pZ3BxcmxwdW5s09XR2NPNtmxhbpx/gn6MfwF+j38Bfoh/gn6afwR+f39+iH8Bfpd/gn61fwF+i38Bfol/B35/f35+f37/f9R/hX4Bf4R+/3+IfwR+f39+/3+mf4p+lH+Cfoh/gn6JfwR+f39+lX+DfpV/gn6MfwF+kH+CfoR/gn7/f4h/h36DfwICBACAiYyGhYeC/4uFh4yJjZWbk46F7ISD85KamfGkg/He5fWSiIiFoKemj5ytk/yUnJGNlI6TlYiHiYGAiIqMiYCDg4GJjY6UnLu8nIqZiomMj4OKgPGKlY6UkZGOjIiLjoqGiIWBg4KHhfmGh4mEg4uNioSHgv+EgIeRh4+Hh4qIjJCAkoSDk5GXioGGhoqIho6Oj5KPkI2Nj4/+iJKPiI2QjZSVjJWPkZCPk4yLiIuJh4iOhoeRioeFhI6C/YiIhIKHkLi0moeIj/qIhYOIgYqciYft+YSClYKDhIWJjI+UmJOEjYyMio+MhYuNjYmOio6JhIaHioqHiYmOjJKLh4eOk5uAkZaTjYqQkI2Kg4GQjI+Qk5OPiZGRkouMkpSJhYiEiI6LjJGPko+UjJaSkI6Li46NhYKWl4WKjJGSkpCQjZCPmJSPjo2Qip2NjJCTmYyPlJGSk5GOkI+NioySlZCUmJSal5GWkJaTiZGUlpiRlpKZlZmYk5Cbk5yXjJOVlZmfl5WAopKMnpeWkZKYlJKOlJKMj5mVkouLkIiJiYSMg4SLioiLiIqMg4eFgPh6ZLaomoV3Yl2hu3KMsuGBiYyPkpCMlJGSjpKTjI+RjY6SjpSRl5GSjZCPi42MlaivyL6up5OYlpmTj5CMiIuQko2NiYyGiIb+j5GNlpCMj5GVnJKTlZSAjZeYnZKTl5SZm5uUmo+UkZeWk5qTl42VmJSVoKyspqC3qquWiZWPjI6RjImZkYqNiIiJiYqMi4yE+o36jYaM4Iydlo2Qj436iouOkoyakpCRj5CMkpaSj5aWkZWXkJCTlJqSjpWQkpGRko6EjZGVk5SOlY+OjJmSkpOQiZCSlJiAlJaalp6QhpCaj5iajpaRkoiSl5iYkomRmZWZlJiPlZeOmJCbkJqTkZqUk5aVkJaTkpSNlZeVipWVmpeRkZSSlJCMiY+Wk5KQioqNhIL/jJeOjpGUqquhjYeipJeUhIbr7+rg3+fY0s3Q+bKm0eXq7YWNjImGhP75h4WNlIiGgIiAjp2lh4KLh4+BgpuYhI+WnpSWl5SVrrShjojBxtz6nvyXm5KSlYmZlYyUjoOCkYXo8IuZjb/H6fSchaGUnbqnp52QkJKNhYeKiI+MhYmD/fqHgoGJiYKB/IeQj5SGlpGRk5mViIWFhoiOjISJi4H7/YCEk46YlZiWlZKYlYD5/aWAyraPio6Mj5GRkpGKi4iC+pGdlZWUlamejpGPj42Sk5KRiYyOio6Ij5GRkI+IipSKhJmQmZScpJihlpuck5WenKGZnJWXmI2KiY+KioSCgY2Ui46ThpCLjZifm5OWj5OSioqNjo+MjpCYjYqEiYyYkIyGj5GTkIeIgf+K/e2Ih4iAfYN9fX568YF8fISBhIiRiod83Xx63YOJitmVdt7Q2uOFd3x2kZGSfouageSGh4aDiIOHh3x7f3d5gIGBgHh5e3h8goGHiKGghXmHfICAgXh+euOAioKEg4SBgH1/g39+gXx4e3d9fOh9foF8eHyDgHuBe+55d32Gf4R8fYB9goaAhnx6ioSJf3h+fYOBfIWEhYeGhH+ChX/mgIiEf4aGhIqIgoaDh4eEiIOCf4GBf4OHgH+KgYB/eYN67oKAfHd7gp+giXx5g+6Afnl/eICNe33l8n14h3l8fX+ChIOHiod9hIOFhIiFfYOHhYSJg4aDf3+AgoSAgoKIhImCfn6Gh46AhYqHg3+Fh4GEenmFgIOFhoaGfoWHhoKCiIqAfYF+gIWDgoSGiIiKgo6JhIeDhIiGe3SGjH6ChYeGhoeHhoeGjIqEhYOGfo+Dg4SIjoOFiYeJiYiFhoWCf4GFioaJjYmNiYeKhYuIgIiKi4yIi4aPjJGOioWPiJKNgIeJiI2TjYqAlYmBjouKhYiNioaEiYSDhIuGhYB+hX6BhHyEfH+FhIKGgYKBfH9+ee59bsu7q5qJcmmxxHeOsdx9goSHiIiDiYeKiIiKg4eHhIaJhImDioaIgoeEf4F6gIeNnpiRkISJiIyJhYaFhYaJi4WFg4qChYHthomGi4eBhIeLkoiKi4iAgoyPj4iJioiKjJCIkYWKh4uKh46IiYaKjYqKkZmVj4mbkZWGfouGhomLhH+NiYSKgYGAf4CChYR/7YDhf3h+x36MiIeKi4byhoWHiISSh4eGhoqIi4yGh4uKiImNhomJipKKh4yJioaGiIh9iIqNjY2HjYiHg42Ji4yGfYeIio+AiY6PiI2AfYiRg42RhIuHiH2Ki46Qh32Ii4qQi42EiYqDjYOPhZCHiY2JiY2MiIyIioyEh4qKgYqIjo+IhYmHiIiEgYeMiYiGgoSEfn/0hY+HhYeIl4+DdHGBgXh2bG/Fy8vCwMnAv7m935yWwdTZ33yCgH9/ffHtgH6JjoB7dXyAhZKTd3d/en50do2Hc36GjIeHhISIk5WEdnKoscHbh+CJj4iJiX+LiIONhoB9iH3d54SOfqqx0daEcoqAhpuLjoqCg4OAenx+fYB3dX188/GBfHyCgXx78IKHhop+iYiGhoeCen1/f4KJhX2DhHv08np8ioaPiYuKioaMiHnq55CAqpyEgIeEhoeGhYaCgoB77oeQiIyIiZiOgYaEhIOHiIeGgIKDf4N+gYWGhYJ8f4qAfI2Fj4aIk4qPiI2PhYiQjJKLj4uLjYV/gYSDg357eISHf4OHe4J+hI2Qj4WKgYWGfYKDhId/g4SKgH55f36Kg4J8g4SHh31/e/WC7dx4dnuAbXVvbW9t1nFvbXd2dnh9dnZuxmtrwWxwdrl7Y7u5x79uYWtlc3F1anN7asZxcHRycW9ycmxubmttc3BudHBsbWpqa25yb3p1aWNwa29ubWdra85yeG1tcG9ubWxvd29xdG5sbWlwbdBtcXFuaWlzcm1wb89qbG94b3BtbXBscHWAcm5te3F0cWxxbnV1b3V2d3Z2cm10dXPQdXt3cXl4dnh4dXl3enl1fHNxcnJyc3Z6dXN6c3h0bHdx3Xh0c2xwcX9+cW9hcNp1cGx0a3B3aG7Z5HRrcW1xcXN3d3B0eHZyeHN2d3x6cXZ4eHd7d3h2dHJ0dHZzd3V4dHZ0c292d36Ad3hzdW5zdHZ3bGl0b3J0cnJ4bXN2c3N2eHp2bnJxdHZ1dnN1eHl8doB5cXh3eXl1b2JtfHZ3e3d4d3Z3eX56fXt2eXZ6dIB0c3F4fXFzeXh5eXpzd3ZzcHJzdnV3e3h9e3h9dnZ2cHh5enh3eXiBfIB8eHJ6dH99c3d6ent+enaAg3tufHp4dnZ5enR3e3J0cHd2dnJveHF1eG95cnV4dnV3cnVzcXRzb+CIgu/c0sS1npH1/JOcttN0dnh6eXx1fHd6e3p7eHl6d3h7dXlydnN1cHtzb21kZGNga2xwcW90dXd5dHd7e315f3x3dX18fXbhenl5eHhxd3l/iHp7fHeAeX6Af3p6fHh6fIF3f3d5eXx5eH14enh6fnt5d3p2b2x3c3l2c353fH2Be3F6fHl/eHlzdHN6eHRz1my3aGVopGdzc3t+f3nbfHp9fXaDfHh4ent5fHt3e3t7enqAd357e4J+fX97fXl4fXtweX6BfoF6f3h8d3x5fX57dHh3eH+AeXl9dnZoa3iDdHx7cXx3eHB9fH9+dXF6e3l/fX50enx1f3N7doB2fHp4eXx7d3x2f3x2dHZ9dnl1fYJ7d3l8eHh1dXh8enp3d3p2cHLeeYN6d3V6fG1jVlNZV1BQSlCVoKWhoKWho6CjwH+FqMDExW5zc3B1dOTceHB7e25nZmyAd4B4Y2drZ2piZHhwWmdvcHJvbXFyb2xeWFWIlJ2wZbRwenZ6eHF3cnJ7dnZueG7H1HV6ZZGYsq9jWWplZ3BobnBwcHFxbWttbm1nZW1w4dl0cHJ2c3Fu23R4d3pxdnh0c2xoZ251dHN7eXJ2dm/g3m5veXh9d3h4d3V5d2vQy3KAfXl1c3dydnh4cnZ1dHNx2HV6cnl1c3t3bnV0c3J3d3ZzcW9wbXFwbHJ1dG9obHZvbnZweHBweHR3cnd6cnV6dnt3dX16e3ZvcnN0dHBwa3V0cm50a29rcnZ4d254b3J2cXRvb3Jxc3F4a21tb2h1cm5pb3R0d2xuceR12b9iZGmGfwF+i38Kfn9/fn9/f35/f4R+i38BfqZ/AX6UfwF+i38BfqN/AX6hfwF+jH8Bfol/gn7/f9d/A35/f4d+gn2Efrl/AX7Hfwd+f35/f39+h38Bfv9/hn8BfpF/kX6Gf4J+on+EfgJ/fo9/BX5+f39/hH6Wf4J+h38BfpZ/gn6Nf4J+kH8Bful/B35/fn5/f38CAgQAgIuKiIyKhYeOjYiTnKiZi4mOgfrygv3d2ffk4OX1g4GIh/eJ/eDa9+z83JSJho2JkomKh4CIjIyJjYmG+YOEiYqBioeKtaqmhtWShI+Sg4KEjIeViI6RkYiMjpKNjISPjIvH84CLjomJho6OkpSOkouAgIWDhY6LiYiLjIiIiYqLgIyRjJCSjYqLiouRkI2NjpKMkIuTjpCOjZGEjZGLlYqSloyQkY6Qj4OJg4CChYyAhIaLioaFi4iEgYWFhYGQp7aij42GhouIh4KIhoCQiuuCg438gobzhYCMjImIhYqI/of1hoiRjZGNio2Ji4mIjY6KiYaKh4eLmo+IjYuTj5SNgI6TlI2SkZGQi4iKjY6SkI6RjI6HjI+PkI+UmpKQkI2Jj5OIkJCWk5KQjYuPioaLkY6SkY6Qi4+Ri5SRi5COjI+LkpCLkI6SiI2SkpOLkY2VjpOQkYqWkI+GkpSWlIuJjo6VkpKalI+Xk5CRlJKOmZOZlJebkY+WlZWZkZOZlpSQgJaQl42Jk46QlYuPm5iVj56ZkZuajeL7h4iblIqBh4WEg4WDg4SIh/3okJqalJKB5tXQzrqKdV6T8f+Eh4qPjZKQlpaZmZeOj5SUlJKYj5GVkpCRlYqLi5GSk6S3vb+9trGlkpOgn5qUkZKRjYyQjI2D+4KJjZGMi4+Tj5ablpeTgJiVkpKYl56L/I2WmYySlJKVnJSZmJ6XlpmkmpiUmJeVnJ6fpKKin5mNjYmalYuZlJqakqChm6iYkY7/gIDQhoTj64KEi4uAlY6Jgo+MjZSLj46Ojo2SjZaQk5SQkZCRkZKKi4+Th4+QjouNkI+Yl5mXnJWanJublpCYmpKSkZaagJWZl5Wck4+Jjo2WlpiTipaTjpWUj5aPio+Rjo6TmZKUi5GYm5WSiImUkI6TkJCTj5aTlZWSlpCNkpCTkJqQiouKiouOjY+RkIOJhoKCiI2MkYqUn7urjoaIopOD/YeB/v6F+oKB9Pj46Orz5b7M6/KDhYCEgfnwgffkhYeUl4WMgIyAhZmWmJaZmpaai4qagI6GlaayuLOphu7IuLy6t9HG5+uAipCMjY6MjIWC8OKB9O6Imoz3na6JlaCnqbHUoqOQj5qKi4qLiYiSkIiJhoSEgYSCgoKIgf+Kl4aVkZCUnKCTi4qLjImDioiHh4aFhP39ioyUl4qZkpSQkZaLlZiUgKLSq5OPjo+SlZWTjYyNkIr+hYSNkpyUqamUlZSSiouGjYuOkZGLioqQlZOTkYyan5GQi5ORi5GimpqSjY2SkpOQiZKSjZONiYaJhYKIh4X4iIWKjIiVkZGIjZSVoZKYk5CFh5STjJOQgIqMjZKRh46Rk4+Ogf+JioePjor+iYeNgIGAfoJ/fX2Eg3+KkJuMgoCFevHoee7QzuPP0tnmeXh9fed/6s/O6tXnyoN4eX57iH2AfnV8goN/hIB7632CgYF3gH18nZKPdL2DdoCEd3Z5gXuJfYKAgXx9foN/gXuGgIG64nZ9gn5+fIOBhIWAhX53eXx7en6CgX6Cgnx8fn+BgIKFgISGg3+BgoOJhoKChIaEhYGGgoWFhIZ7g4aFjYKIjYOFhYKGhHyAfXx+fYJ7foCEh4F/hX59en59fXuEk5+Pg4F/fH9/f3qAfniCe9t6d4LxfX/jfHiEg4F+e35/9X7qf4OJhIiEgYWAgoKChoiEgoGDgICDjoWBhYSIhIaDgIWIjoaJh4WEgX+Cg4SEgoWGgoF9gYWFh4aIjoiJh4SAhoh8hISMh4iGg4KGgn6DhYSJiIWIfoKFg4uIgYiIhYWBiYiChoSHfoOIiYeCh4OLhIiIiIKLhYJ4h4iJh319g4OKhYWMiIaOiIWHioeFjYaNiY6PhoeLi4mMhIeMiYeCgImGjIOCiYOHiYGEioeGg46JgouKebXhd3eJhIN+gn9/fX5+fn2BgO7elquvqaaG697Vz8KZhm6W7vh+gYSJhYmIi42OkI2Gg4mLjIiNg4eJhYaGi4B/f4OCf4iTl5idl5SRhoaRkIuIiImIiIWLhoh+8nyEhYeEg4KKhYmQhouHgIyKiImNjpCA6YKKjIONjYeIkIqQio+MioyVjYeCh4aEiIeGioaJjo2Ghn6LhHuHhYmIgYiMh42HhIHteXG6ennQ13l7hYR4joiBfYmFhoyFiISHiIWLg4uDh4qDiIeFhIWBgoaIfoWEgoKGioiQio6MjYSLjpGSjoGMjYaHiIyMgIeQi4qRioSAhICNi4uHgI6LhoeLh4qCfoOHhISKjIeIgYmOj4iIfnuOiIaLhoSJiIyKjYuIjIaDh4KGh4qAfH+DhYSHhoiFhXyAf318gYSCiICIi52NdnJzhHhu2nZy5eV35Xp43uPn29Xe1bTF3ed+gHh9fO7heefUfHyKi3l8gH1xdouMjImJjImMfnuIdIF6g5GWlY6IbsepnqWlo7ux0th3gIWDhIaDg3196th45eN+jX7li5V6hIqNlJyuiYuAgIp/f359fH2EgnyBf3t9e39+fXyBffaBiXuLiIWJkI2Cf4CEhYF8hICCfn5+f/bzg4OOjn+Lg4qJiomBiIuGgJKzk4WGhoWHi4iGg4ODiYTyfHyHiJCIlZSGiomKgX98hIKGiIWCgX+GjImFhH+NlIiDgYiBfoGPh4uFgYOIiomEgISFg4iCgHx7fXyCgX3nfXt/gn2GiIV9gImIj4SIgoV5fYiIgoaFd4B+gomGfYWGhoGAduyAf36FhH7vgH2CgHRxb3RxbGx1c3J6fYJ3cW5za9XNZsirr8O1tMTHa2hqb8xx0La3zLrBqmphZmdodW5ybmZscnNsc3Ftz29zb3NobWpqfHJ0YJtrY2tvZmVobWt2bHBrbGxnbW1qbm92cnKvzWlxdHFxaXFtcnNrdXBram1vcGx0cnB0cmxqbXBxgHJzbHJ0cW1uc3N4dnJxdHdzdHR4dHV2cXRveHV6fnJ0fXN2eHd1dnByc3BzcHRvcHR4e3dxeXRzb3Nzc3BzeYJ3cXB0cG5vcW9zcnBxacdqZHHec2/Ia21zdHZuaWpv4HLScXR6dXp3cnVydHR1eXp5dXZ3dHVyend1dnV5eXR2gHV4enZ5d3F2dHFwcXN2bnd3cW9yc3Rzd3V2f3Z0dHdyeHpwdXZ3d3h2dHJ5cXF1dXd6eXN6cHZ0dXh2cXh4d3Rwent3d3R2b3N4eXVxeXZ9dXV2e3R9dnJrdXt5dWtwd3V7d3J6d3R8eHh5dXl1fHV+en57c3V7eHZ4c3N4dXdygHZ3eXJ0eHN3d3R0dnp0cXd2bXZ1YoXAZmRwbnh1eXh3dXRzdHJ1ddvTqMvMxr2F6tzKxszH3sKj3+l1d3h+d314e3yAhYF5cnl7f3h7dXZ1cHR4eXRyc3BuZGJkZWVub3J0b3B6end3eXl7e3x6d3t05XN5eHl8dnV7eXt9dXd4gHx9e3t/fX5wx3B5eXR/fHd5gXmBfIN/fnmCcm5rc3RscW9mamNqc3p4fHJwamlvcXFua25waWtvdXDbbFubZ2e2vGtve3hpfnl3c317eX19fXJ2end5dHx4ent3end2dXh4dXl5c3l3dnd6fnt/eHl6enV6fX5/e3F+e3d6fHp7MXF+eXV7d3dvdHJ7eXd1cYJ/ent6c3NybHF+eHR7eXN5dXx/fHl7bWt+eXp6d3R6enyEd4B6eXV4c3Z2c2xnanR5d3t4eXJ0cnR1cnFwdHF5cXZzcWRbWltgVVSpXmHExmfFaWrAyNLFu8G9n7TI1XF0bm9y3Mxt0btqaXh2aWpoW2FzeHZ0c3p1d3FsdWVxa2x2cWlhXE6ThX2DhYWblLG8Zm5zcnN2c3RwcdTBZsjJbXNpxoBvcmNsbW52e3pkaWxsc3BxcWtpbW1sa3Ryb3FzdnNvcHdy4nBybn17d3h4dGltb3V1dXB3cXVzcnF04dtwc35/cHVweHl4dm91dm94hnVzd3Z3eXt4dXR2c3t43WxueHd7dXh2b3p5d3Bva3N0dHVwcnBtdXx3cHBveX92bG1wbUtuaXFucXFucnd2d3Zvb3Zzd3Rzb2pvbHR0cMpuanFybnR3cmpuc3B6bXRwdGduc3dxcnVmbmxyeXRtd3ZybXFmz3NsbXJxadJwbnOSfwN+fn+IfoR/An5/h36RfwF+jH8Bfph/gn7rfwh+f39/fn9/fol/A35/fv9/tH+CfpB/gn6Gf4Z+BX19fn5+t38BfpZ/AX6xfwh+f39+f39+fv9/m38Jfn9/fn5/fn9/i36FfwV+fn9+fp5/in6Kfwl+fn9+fn9/f36ifwF+l3+Cfp9/AX6+fwF+pX8BfoZ/BH5/f38CAgQAgIuIiYyDhoeOiJiXi4OFj4uA/oLw9ufg9fH2+/KCiZGEhoaGgoP2+YP1ifWmn4SDioaGjpuNi4iHhYePj46Lh4qKlJ+0jOPY29rhg42Rn6SlkJ6JgvuNkY2H9e6Qj5GKhoOKh4yJhYmLhZCRkYmJjIqRkZCIgYSEjYmKiImE/oWIB4OHiJORkpWEjICRhYuNlpaLiY6PiY6Sk4+Mi46Wlo+VlZqKkI2C/IGF//6KgICHgYOEiY2HhoqBiIuFn7KwmY6ViY2IjYaIiYSIgIOKm4uB9YWCgoqIhoeFiIiFioGDh4OLiomNjY+GiZCKjI2KkJCEh4mLjo+Ig4eRk5OQh42Vk46RhouKj5GSiYCMjpGJj42Kko+KjouOipaKkpCLkZGPjZKSl4yTkY+Rjo6QiYyGjI6Qjo+SlY2Kjo2JkYWOmJGKjpCSk5aSlZSQj5KVj5SXkZGPiYyPk5aSkZKXjY+WmY6YkZCXlJCJj5GYj5CQlpiKmJWUlZSXl5eVlZKTlpCSmpCNl5KHhZKXo4Cilp+Zn5qgoZePj5esmvfuxoqfkIKBhPT7g4OA/+PQs7jTeXjf2N2cdZR+1Lbo/IaIiY2UlJSQkZOUkJKTko+SlpCRkZCUkZGQjpWRjoiFjI+XrLe/xMLAq6mdn5yfn6CcnZaXjIiPiYGFg42KkZWNlJSTkJWVlJKUl5aRkYyQlYCYl5eVl5eVm5iYk5aXmZahlpOPkYyQjoWNjJOVn6SjlqSopqKdoJ+bqKihmqSnnJqVk6Okk4mMkYeEjf2PkZOYjIL7jvWMlZqRkY6SkJeEjZGSko6PjoyMkoqIjI2QjJKRoqalo6qrp6WfoJWVlpqWjJeWlY+JlpSPk5SOjYmXlICXi56VkJmQkpeWmZaUmpiVkZCJlJeSk5mTk5CSkIyXlI+NjZGQmZaRl5WTk4yRkZeVmJWTiIiIkZCMjpGIiob95oOIg4yQo7ejppOIiqOPj5OVj4KNhYCHjIaJjIKGgvDv5+H5g4f/7/qIhfbn25GPgIyFmpSUmZuZmZemlJmPnYCL9ZGClai1t6unnemkj6nOva+sxrzI3N/qhIyKiYGJiID52tXb2vCF+P2crpSViJSltcmgpJ2cnYiOipKWj4uHg4aHgoCBhIGGgoP9g6WMkYuUlZ+kloiMjJKPj4+JjoaF/vr/hIeQj4qLkZGTj4+RmZqGjZWYt7OJjpOLjpWYmXWPjI+HjIuTlI6Hio6VoK+wnYuMiI6TlIqOjomOjoaCiIuSkpOPjZSZlpaUiImIjZGYlY+IhI6Ql5SRjoCEhYiOj4uFiYWEgoP3goyKm5uNmouQmZGYkI2NkZebk5KSi4aLiJSJk4uTgYOOmJSMi5GKiJKRh4mAgX59gXh9fYV9iYyBe3mEgXnweuLo3dTn5evw5HmAhnp3e3x6fOPoe+B/4JaOenqAfXuAjYB/f399f4KDhIN/g3+Jj59+zcjP0dd5goWOj5J/i3p15n+CgXvg2oKAhH18d398goB9gYF8hoOEfX6Bf4WIhX93e32FgYF+gnvnfX+Aen1/iIWHiYKCgYOIfoODj4+BgoSFgISHh4N/f4OLioOLi5B+iYN79Hp+8/CCenuDfX59gYR/f4R8goR+jpybi4SKf4KAgX6AgX2Adnp+i3t353x7fYSAfYJ9gIB+gXh6fXyFhISGhYZ8g4iDhYSBhIV9goGDg4WAen2FiIqHfoSAh4eHhn6DgoOJi4CBgYN/hIR/hoSAhYGCgoqBiIWAhYODfoaJjIGGhYSGhoKGf4J8g4OFgYSFiIODhIR+hn+IjYWBhYKIioyKiomFg4eJiYyMg4WFgn+EiYuIh4eLgoWKjYOMh4WLiod+hoiOhIWIi4x/jIuIioeLiYeJiYSGiYSAh42DgIyJf3yFhJCRiJKJi4mPjYd5doOUjOfes36Rinx7f+vzfX599tzIrrTbhYHq6Ouog6SBza7h9IGDgoaLjI2IhoqMiIiJh4SJioiGh4WHgoOFg4iGhIB7foF/jZKanp2dkJSKi4yQi4+LkYuOhoCGhHt/e4CBi46Gh4iIh4eAiYiIiIqLhomCho2OioyJiouJj4yKh4qMjIiQh4iFiIWGgneBeoB/g42MgouSj4qFh4WBjIyHgouPg4aCfpGThYGEh4B/g+yIiIuShX3vh92DjY+FiISJh455hIeJioWDhYeBh4GChoWEgYaHj5KOjIyQjIuJj4eLh42JgYuKh4aAfIqHhYiIhIV/i4qNgJKJiJKIiIyKj4yIjouJhoR/i4yJjI+Ih4iLh4GNjIiFf4aIkI2IjYqIiIWHh4qLioiMgoOCh4WChod+gH/z2Hp9eICDjpSCiHt0eox5fYaGgXd/eXV/hH6Cgn2AeePj29fsfoDs5POCft7SxIiDcX14jYaAhomLjIqIloOGfot+24Nzg4ySkoeFfsCKeJG2qJ+jubG8ztfff4aFgnmCg33x1c3Py95+6uiMlIGGeoGPl6CHjouKjX6CfoWIgH99e319eXx9f32AfH32fpaDh4OLh46PhHyFg4qIhIWCh4B88e/3foGIh4SBg4eIhIWHjYx3gIiAip+ZfoSIgYOIjI2FgoiEhYWLi4WBhIOIj5qajIKFf4SKioKEhX+DhH56foCHhYWCg4aKioiJf3t5g4WOi4WAfIWGiIWFgHh9fX+GhoR/g3x8ennte4F/jY1+i36FioOJg4GBg4qMhYSEf3x+fIqAiICGdniDiod/gIV+fYWFe4CAc3BqdmpwbHVrc3pya2hzcW3SbsXTx8DLy8/f0mtsdWpva2hrbsjRbMJsu3t1Z2xxb2tpc2tra29wbW9ycXNwcWpzdHtjqa+3wctqcnJ4dXhqcGVjyGhpbmrGvGlpc2ttZ3Fuc3Bxc3JtdXFubm9vcHZ2cHBlbHB2cnBvc2rLcnFHbW9vdHNzdHFwc3Z3cHVyg4x0dHV4cXN4eHNwcHR6e3J5fH1venVx5G5x5t52b293cXVxdXVycHhyeHpxeX6Ad3J2bnNzbm2Ec4Bpb2xyaGfPbXBvd3RwdXF3dHFxaWhrcHd4eHh5d3F2eXd3dXNzd3N5dXZxdXNvb3N0eXZudXZ5enhxdnNzeXtycm5zb3R0cXlzb3Rwb3V5c3V1cnh1dXB4dnp1dndydHZzdXB3bnd0dnF2dHp0dXRxcXZzeHp2c3Zxdnl9e3p4dYB2dnZ7f3txc3Z2bnJ3eHV4dXdvc3d8b3p4d3p3c3R7eHt0dHZ5e3J6eXR1cnh3cnd3dXV2c3R7dHR6eXFvdHB3fHd+d3pzdXVwZVtrdnXKvZ1ogX5xcHfc5nV1dOTOuJ2v5ZCD6e77vJ/ko9Gf1ON3e3d8fX5/fHp7fHt5eXZ2eoB1eXZ3dXJsb3Rydnd2dG1tbGFkYWZram1vd3JyeHxydnZ9eoJ9dXt4cHJwcnd9gHl5eXZ7enl3dXZ9fXd6dnZ7fnx/eHh+fYB9e3l7enlxdnZ1dXp4d3FqcmhmYWNrbWZsc3VrY2doYmlpa2RsdWtvamJ5eG9zd3Z0dnTMe3l8gYB1dOB723h8e3R3cnp6gG53dnl6eHRze3R4cnV8e3Z4dXN2fHBtaGtta293dnh2fntyfXt4dmx6eHR3dHZ5dHx9fXJ9c3Z8eHh1eHh4dXp5eHd0cH5/ent9eXd6fHRxfXx5c211eIB+eX56eHZ0dnR3e3dyfHR6d3d1cHR0cHNx3oDHbW5pcHNzaVxfXV1hal5ncW5va25sZ3J1cXJycHNrzM7Ky9Vxc9PU4Xdzu7CoeG5faGZ6c3R2dnl4cXxrcGpxcMdwYG1uaGFbWlaMbGR2lYuGlaSeqLjKz3R4d3VqdXh05Mm6uKzBcNLHc29nbWZobm5rZXB2cnRsc25zdW1uboBvbW1oc3J0c3dxc+JxfnJ0c3lzdXBpaXZzeHd0d3R2cW3Z299xcnN4dnFwdndzc3R9e2NvdXh9dW51eHBzd3p5dXR7d3h4enp2dXZycXZ5fHRyc29zd3dxc3VvcnJwbG5udHFubXJycnZ1enNuZXJxeHp2c21zdHd0cm1scHBwdTV2dm9xb25watttcm95dGx1anF1b3Nta3JydXVyc3FsbW5seHF1cHRlZ3N1dnJ1cGtsdnRoc5F/An5/iX6JfwZ+fn9+f36af4V+in8BfoR/gn6ifwF+qX8Ffn9/fn6lfwF+/3/If4N+hn8Ffn5/f3+GfoJ/hX4GfX19fn5+/3+NfwF+hn8Dfn9++n+Cfp5/hX4Kf39+fn5/f35+fpN/AX6Jf45+iH+GfgN/fn6hfwF+lX+DfuR/AX6sfwICBACAkJCLhYOcpK2fjZGRjouMg4aE9/WH9vuB9YGGiYuMhY2NhYeC/JGBiICR4tXrgpCPkZGLho2HiYySi4uHgo2DhIeKhefj8O7s9vqIjpCZmp2cjpKXjYHzg42XkJaRgoKDhYqJiYmHhomRlImOkJaLgYKFg4GLhouAi4KLiIyZhIqAipCOhoqQk42NiY6LgouOiYaRiY2Ql5qMjIqTkJKTj4+UkoqNi4L5/YOCh4aB//iGgYGEhYeKiIWJlquinJCTiYqJhY6PkIiOg4+PjoD9kYr6hu/1g4eNiY6Pj5KHiYWGhYGRgoqIjYuPjIiPkYmLjY+PkouRhIqJipCSmYSGkaCAjYqWkIuGkpOLhouMkYmMjpmKkJSIh5KWjomVjIuPkpSHkYqMjo+PkIyEgo6Nh4mSj42Vl42Wj5KLj5OQiYqQho2Qk5KQkJWQkpKSmpGKi5aOi5aRk5aVlJORmJGVkI2RlJSTkI6WkoyPkpeXk56YnI2Ok5SRl5ibl5eZmJqSjpmAkprr5oWKjYWRpqChoqCSnZCVoaGhpq6W84aJjdr18vyBi4T5+P/8+Ob15qapoovr6eDcgI6s0t72hIWOio6PlpGQi5CPkI+PkZGJjZCQj4WKjYqOlpKSj5GNiouQjpCZoay4u8LGw7+wqaiXmJyZjJmShoSOg4CJjpCQiZOMjZOAlJaamZSUk5GSkY+XmpWWjpOWk5COmJSWkJCQmJWSk5SSiYyJkYiGjI6Yk4SYn6ejo6Skp6ylpZuinaCbmcCtnZqWloiQlJqIlZWPko+RiImNk5SNlJCQjIyQkYuRkpqLkZGPkJCPjYmNiqGxta+loJ6UmJOhmJuXmpiRk5eVmJuAjpaWi5WTkYyUko6ckI2hmZ6QjpeOkJKPj5eUlo6LkZeTjI2UjpCPk4+PkpOVjpKWkZORjYyTlIySkZSVkIyVjY2XlJiSjI6JiIiD5PmOnZeUlqGYkKGTmZ2dlI2XlJOOiIqJjo2LiYqE+IGD6YOIhIHo34aMiY76l4OEm5yYlZqAlYiUlKGJnJqhmpGAjrK+q7e0rJ2FyZPMxcnJzqrCyc7Q3erv94OJh4iIg/vz4e/1h4n1kIiPl5Cjo5mqwdOllpifmJePmI6Ck42MioiEh4OBhIiDh4OSmJCQjJSNk6CUmZCKlZeSk5OTjouKjIP/h4yMiY6EiJCYkpSPk5OYnJ6AorK9lo+MjZeVmJKHkJqXj4uLj4+NkpqNm5KVrrGViYSIjY2MjY2Ph4yIh4L7ho6YlY+OkZCajI6bkZyHk5KXkoyKkoyPi5COjZSNi4WIjI6Kg4GMiIORjo6Ym5acm5qSlqWuq5aQg5qPkIWJlJCMj4WDjYmNjJWcnpaKjoCMmJaAgYSBfXeLkpiKgIaFhYGEe3x65uZ/6ut76XuAgoOCeoKCfH5564R2e3WFzcbbeIOChIV+e4J7fX+GgIN8eoR8fX6CfdbY5+Pj6OV/g4OKiIyLfoOGfXTie36FfoKCeHd5e35+gX96eoGKi4GDgop/d3p8e3qDe4J4gXuDfoCMen+Af4SEfH+BiISEf4KBeoSEgH2HgICEio6Cg3+GhYWKh4aLiH+FhXvv9H16foB89uqAfX9/foKBfH6CipqQjIKEf4KAfIOChn+GfIODhXnrf33rfOTtfIGGgYSEf4aCgn98e3iFe4GAhISGg4CIiYSFhoiHiIOKfISCgYaJi31+iJR4g4GLg4N8ioqCgIOCh3+BhIyAhYl+e4OJg32KhYOFh4p+hH2BhISCg4N/e4SBfn+Jh4SKjIKJhYeCg4iGgYKIfYOHioiGiIuEhoiHj4iAhIuEgIuFiY6Nh4iHjIeKhoKIiYiIhISIh4KDhouMh5OOj4GEh4mEioqNhIuAjIaDjYeOzsx+goV6fJCNjo+Pg4uAhY2Oio6UguN9f4HG2tnne4J77PD89eve7+OboJV90dDLxHKDo8XU7YF/ioSFh4yKhoKIh4eGhoiGfoSIiId9g4N+g4uGhoSEgX6BhoJ/hIeKkpaanZ6akY+RiIqOjIKOiYF/h3t7gYWIhoCAi4WIiomLjo6MjYyJiImEj5KKi4OKjYiGg42LiIeFhYiKiIaHhoKEg4R7fIB/h4FxgYWNiIiEhIeKhIV9g3+DfXydi4aHhIZ7iYuOf4uOiImFi4KDhIyMh42JioODiIiCh4qPg4iHg4eHhYeChYKPmJaUi4iGfX9+iIKFh4qLhYmAjIiLjIaMjYSNioaDioiGkYSCk5CUhYSMh4iJhISNiYyBgIiOiIKEiYODhYyGhYaKi4WJi4WGg4SEjIqCh4aJioWAjIaDj4qJhYKEfYKBftjpfoiDgIKOh3+Rg4yMjIeCioeHgXt/goSEhoCBe/B6fd58gn9909CAgn2B4YVzdYyAjIeGi4l8hYeOeoqIjoiGd4CWnYiRj4mAbaZ8r6qzt7ufu8DHyNTh4+d6g3+Eg37069zp74GB5oJ7f4aBkZGGl6KpiISIjYqKgYuBeYWDhIN/e4F+fn+DfYJ/i4yEhISKgYOJhIuFgYuRi4mIiYOEgoZ/9YGEgH6FfICHjYiKg4iAiImNkI+XoIeEhIONi42IgIePjIeCg4iGhYqOgo6EiZuciIB5foaEgoSEhn2CgYB773yBi4yEgoODjIGCi4WMe4qHiYaCfYWAhH6AgYOJg4J+goKBgnp7gn95iISDi42HjIqLhYeSm5WIgXaLf4N7gIaFgYZ8eoJ+goKIjY6JgIMEdYGLh4B0dHFxZ3N4e3Rxd3N2cXdwbm3R0nDS1m3Rb3N0d3BscHRucGzPb2RoYnSyr8FqcnFwcmpqcmprbHNxc2tocm1sbnFqtsHRxc7SynFycHdycnNrcHBpYMptaGtkaGpra25ucW9zcGdmcnh7bnJueXBnampucHVndW1zb3ZwbHdrb4BucXNtbW93dnhwcHJvdndxcXh0cHR3eXR3bnNzc3l3enp4cnZ4btrfc3JydHLm13RydXVxdXNtdnd1fHZ2c3NudHBtc3R4cXVydnN2cNBlatZv0t1vdHd0cnFqdHdzcWxpZnFvdHV4enh0dXt9eHZ4eHp3d3tudXR1dHlzcG54foBydn10d3B6fXNydHF2cXN0f3VzeHFrb3Z1bHd1dHd1enFzbHBzc3JycnNvc3JwcXl4dnd5c3R2dHJydnh2c3ltdHl4dnZ7e3R3eXl9eHh4eXNxenR7gHx2dXR5dXl2cXh3d3R6c3Ryc3Fyenx2fnt9c3R0dHN1d3l4eXV4dXV0eIB2fLu/b3R3a2N5dnh6eHF2cHN2c3N0d2vNcXR0pbS3xm9ybdjb6OLX093YjYx+aKquraJbbpCzv9x3d314eX2Afnh4enp4d3l4dXF3eXh4cXVxbXN5dXN1dnJvdXhxamdkX15hZGltbm5wd3V5enhyfHl3dXpwcHh3e3l1fnZ7eoB6eHt+f4B9enl7dXyAd3l2fH93dnV8e3N1cnF1enV1eXZ1eXl3bG5vam5mWGNkaWFjXV9hYFxcWVpbYFhZbmBobWxtaHZ6em54fnd3dXx3eHh8enh/fXtydHh4dnp8f3h7gHV2e3l5eHd0eXhuamlpZl5gZWRjaHB0cXJye3d5eYB2e352e3h2c3h5d4Byc36Af3Z3f3p6d3RxfXl9dHN4enVxdnhwbnd/eXh5fXx5eXp1d3N1cXx4c3RydnZ1cHh0dYB6d3RxcG13dXTFy2dsZWRnbG1se292dnRzcHZ4dXFscnN0dHlxcGvgb3LFcHRzc8i/c3NscbttYGV3d3BxdoB1anJ1dGZ0cXZwc2dpb3NcYl9cWUx4X4qMmqCkkK2yvr3GzszOa3ZweHp15NvQ1dVzccptaGhsbXR3anh8dmJpb3R1dm12bWlycnV1cXF1dXR0d3B3dnh1c3V1e29tbGx0dnZ8gnp7dnhxdnN5dNpzdG5pc3Bxdnl3e3F2dXJ1eIB1eHtyc3NxeXp6d3J4fHh4cXZ5c3Z4fXR3bXR9gHRwa253cnJydHdwc3RwbNdtbHZ8dnJycXVzcXh0dWl6dHFzdG50cHJrbnJ2d3VwbnJxcnZsbXJuanZzdHh2cnl4eXRzeX56cXFrdWptbHB2dm9wam52bXF0dnp6dXBya3F2dZJ/B35+f35+f36LfwF+hX+DfpZ/h36MfwF+z3+CfoV/gn6efwd+f39+f35+/3+vf4J+lH8Efn9/f4R+g3+MfoR9hn7/f/9/lH+Cfpx/BH5/f36Efwd+fn9/f35+nX+QfoZ/hX4Df39+u38Bfr1/AX7TfwICBACAjZOTnqGXhYeSk4aQkomDi4uJ/ouKkYaAgouQmpiPjYuNh4ePhvCA5/396Of1gvyHiY2QkYWLiYeKjYiCiIeJjI2Cg4WGgvb4gYONjYyTk46Rpq2ok42I9IyHh42JjJCSj4SIhYmKi4mDh5iXjo+AkYmHh4mJgIKF/YGHg4eQgoqAi5WKjI6Qj4uQioiN/4yIlJOOi5CPioyQm5CNi46NiZKPi4OC+v2B/ISBgIGE/YKC9/+BhoOPkJmtqZePjpKVko6EjoiJh4qQhYaLjIz+jfftgZGCiIGIi4eGhPb+iYeLj4yGgJSJiI+OhYWQioeJioyPi46IlI2Oj5iKkI+WmIuAj42Pi4eMjYiDgYaRg4eKi42Nj5KMj5OPj5KQkYuLj5iGj4KJj5GMhoOKjI6KhIuMjJCQlpSPkZyTjJOQlJeSiZiKiI+Vj5CMiJWMjY+TlJGNiZSUmJSLlY6NjY6Sko+Wk4qUlouRjZWRjIuZlZeWm5mWm5qMkZSclpiWk5eVl5qAlpyTiYiOmIKdoJ+dm5WXnZujpqW9pfn6kpeJgY+SkJORg4L7mIuFgvH38ubf08rGtMLOx9zo8YCFhYSLkJSMjY+Uk5STlZeUkI6NjYmGhIeLgYqFi4+QkI6OkYuLj5GLi42LipOXn6qyusi+w7KwkZSYmJmTjI6Aio6IiJCSkpKAlJOVl5eUk5KNlJSRkJWXj5aTj5CSnZiWlISJi4uMjI2MhoyPi46MkYqLlY6AiYSOkIuSkpOI0JvtnqGhpLOzta2op6CNiZCHh4+QkJKVmIaEhYWPlZGSkpiIkpKUi5OYlZGVlIqTi5GUpqepopuOjI2MkZmMhoqIjZCclouNkpWAiZGXmJedkZSOj5OWlo2Rk6CPi5GKkJKNlY2VkJSKkpGTjpiRjZGamJOSmoSSjJOOlIyTlI+UjouPmZaYlY2VkZWXo5aMiYuJiIiOh4OSkoeKio6IlpSamZGYnZudoJKRi5ORjIiKiIWMjYiGhYOHg4eE+IOW2I6VoJaQmpqamZiAm5eXm5yalJORkYuUvb6vnJaQiur7g86y5rO/xdTe09Xd6PH7/vyGhIL8+fL0iYyGhY2Fg5WWnZafqaGmu8/Ao5+rnZugmJeVkYyJi4+H//iA9fbiiZWPi5aQmZKTlpCRk5GWnZOSlYyJiIiJiYyMk46WkY6LjY2NjpOUkZSIk5eAq6y6jY2RlJGKkpuRkI6Rl42NiomJjo2blZiUmpupl5GFioqSh4yNgYaJlImJg4KMkZWOm5OXiY2TkZ2amJiLlY6Ml4uJkZqIg4+LjpGRiYeDh4aKiZGRlpKQk5aUkpuhpKSQiJWJjon8jpGSlIuFi5GHioyQmJyfk4iOjpSMi4SAgYeEkJKJen2FhniFioB7hISC7IF/hn16fIaIj4qBgYKEf32De+F42+7n1Nzleeh/f4KCg32Ag35/gX53fX9+gIN6fX1+eurnd3qCgYCKh4CBkZeTgH5723t5eX98foCGh32AfH59f396fY2Kg4V5h4B+foGAeXx88Xp/en6He4GAgYh7gISFgoOGgoCD8oF9i4eAg4KFgYSFjoSDgYKDgIeEg3588fB67356eXx+8nt68Ph9f3yFhIqWmIqHhIaIhoF7h4KCgYKEenuAg4Tuf+bcdYV4f3yDgn57e+v0gX+BhYN9eYmAf4iHf3+HgoKBgoOGg4aAiIKDg4qBh4SIiYCAh4OCgYGDhYB9e36FeH9/gIKDg4eAg4WFhoiGhYCDhIx6hnx+hIeGgnuDhIWBeoGFhIaDi4eGho+IhIiGhomGfIqCfoOJg4eDfouEhYeIiYaDf4uLjYqDjIWFhoeHh4WMhYOMjIGFhIuIg4OQjZCLj4qJkJCChoeLiYiJhIeEio2AiZCHgIGEinOJj42LjIODi4mRk5CjkOHkiYl7coOKhomJeHbhhoV+f/H07uDbzMK4qbK+udTc53eAgYCHiI+DhIWJiomIjY6KhoSEhIOAgYSGe4R/goWFhYOEiYB+gYV/gYF8en5+goqNkp2VnZSXhImLjY+Jg4p7g4WAgIeJiYuAi4eHiYeGiIuEiYqKiYqJhYqLhYWIlJCKiHqBgYKBhIKBfYKHg4SEhn2AhoB0e3eAg3l+fXtwnmmyf4GAhYyLjYaEioyBgol/goiIiImMjH19fX+FiYaIi49+iYiLgYuQi4iKiIGJgISIk46Ojot+fH+Ag4l9eXt5e4CKhnqBioqAf4WPjYqPiImDhYaIjYWIipaGhYV+h4WEin6IhoqBiYiJg42EgoWNjImHjX2HhIuGiYKKjIiMiIOFjouNioSKiYiNlIZ/foKDgYCIfXiChXp9gIJ9i4iLjoeMjouPk4aFgoiKh4ODgX2EhoKAfHp+f4J87X2Jwn+FjIaDjIuKiYiAjImHiYyKh4aCfnx/m5aGenl2csPUb7Gczp6wucrVzMzY5Ov09PKAfXvz7+ntg4aBfYR7eIaGjoeOl46UoaifjIyUjYuRiYeGhYB/gYV+8+587fTeg4yBgYuIjISBhoKDhoaMkIeJi4OBgYCDgYWHin+FhIWEiYKGg4eIhop/hoWAlpWff4KGiISBh46GhYOIjIWGg4KBhoWOh4mHjIuViYl8gYCJfoOCeYCAioCAeXiBhoqFj4WJf4SGhImKi4qBioSCioB/ipCAfIeBg4WFgH57gH+Af4iEh4WGhouFg4qQk5KDfYN7gX7pgoWHh35+g4R8gISHjY2Rh3+Dg4eBgnyAcHVvd3Zxamxwc2d1fXNuc3R00nBtdG5tcXd3fnZucHN3c25xbMRmutDKt8PNaNJwb3NvbmxudG1rc3JobG9ubnFscW5uatbOaWtucGt0cWxpdHd0ZmdmtmVkY21rbm11dW5wbm9pbnFub3l0bHVud3Bub3NubXFw2m9ybG94bXMbcXNtcHJybHV1dHJu129te3ZweXR1c3dyeHJ0hHGAeXZ3dHLc2Wvddm1tcXLjcnHg6HRzcHRzeHp8dXV0d3Z0cW55d3d0cnBrbm50eNFq0cZkc2pvcnl2b2dr0N10cnF2c21seXN1fnpydHZ0dXR0dnhzdHBxdHZ1dnF0dXd1cXV0cW9xeHVxc3Bydmlxb29yc3R1bnJydnV0d3FvdXWAd2hzbXB0eHl4bnNzc2xsc3l3eXB5dHZ0fnd3enh1d3VrdnZxcXFzeHZyfHh3eHR3dnNxfHh6enZ8dnZ4eXh2dH14dn19cXh0eXd1d4CAf3h4eHt8gHd4eHVzc3NtcnF1eXR+dW5zdnhfbHV1dnZub3Vwdnh2gXe+xnd1Z19zfHiAenxoZL5vd3N17uXj282+saKanaSjv8vUbHd1dnx6gHV3eXd7e3h8fXh2dnV1dHN1dndwenR0d3Z2dHd7dnJtc3Fvb2llY15eYF1gaGVxcHRwend4fXl1f3F3dnZ1fXt7fHt0dnhzc3h7eXp8fnl3eHV9end2eYR+d3xsc3NycnaAdnRwc3l1d3h4b3R1cWRjYW1wYmRjYFRuQndZWlldXFpbV1dhdnx2eG9yeHh5d3x5bW9xdXV2cHd7f3N7eXxxe39+fH57dnpydXV7a2t0dG5vcXB0eGhpZ2FeZ29ubXF8e3B0fXx4eXd1dHNxd392d3mCdnN0c3p7dnVwd3d5c3eAeHh1fXV0cnt4e32AbXd2e3V4c3p6eHt4cnR6d315cnV6dHt7b21wcXV4c3lyZmpvaGpqb3B4dXZ7dnV3c3t8cnV2d3t6d3Nzb3V4dHJobHJ0c2/QcHSkcG92b3R4eHV0dHV1c3R0dnVxb2ZpZm5iVVFTVFKTo1WIhKyHmai8x8OAv83W3uTf3nZxcOXc1dl1eHJuc2hlb251cXR6dH19dXRvcXZ2eHpzcXB1cG5wc3De1nTf4NJ3eW1xeHd1cWpvcW9zdnuCd3l9cXJzcXdyd3h5aXBvdHV8c3lyc3Z4eHJya3h1dmxzc3dwc3d6dnZxeXh0dnNxcnZ2fXR1c3V0dnVheWt0cHpwcHFqcnF4cXBqanJ1eHR5cXVxd3R0cnF6dm55dHN6b3J5fHFwfHV0cnRxcG9wcm9tdHN3cXV2dHFtc3V6d29uc2tvbc9uc3V2bG90c21xdHV3eHl0c3NxdG9ybZJ/AX6SfwJ+f4Z+An9+l3+Cfo9/AX6gfwF+k38Bfpd/BH5+f36FfwV+f39+fpt/BH5/fn6Kf4J+/3+6f4J+i38BfoR/j37/f4N+/3+6fwV+f39+fpx/A35+f5B+g3+EfqF/Bn5+f35+fv9/lX8Bfpd/AgIEAICOlqmei4CBiIGMkpOGhYmIi4yFjo2Li46LjZaVi4eMi5eMhpeLi5CAgPXx+oOPi5KTjIn3ho2Rhv6Kj46JiYWJgoLy+oOM/4KGiYeJjYuEiIWC+IuIoaOmlY2Kl5mVkJGJiIuJg4yHhpOLiIiJ+4SJi/+FhvuDiv+GiYiFifiHlYCMhYuEiYKHjImKioaQi4qMjouKjY+Nk5SWh5KSkY2QioyMhf+BgIWIiP6DiISGgfr9iIqFjoufq6+XipCOio+IkoKNio6IjIWGhoyPkI+IkZiC5+v1gIiMkIyFhIiO9oOVgYKMhfiDhoWJioyOiImFj42OjIaNiYGGjJCQj4WIkoCSjomJjI2JgoSKiI6KjJCXjICKlIqSmpSNi46QioqQjJGSiouNiv+Gg4yXiIWCiYeEipGPmZaSko6SkpORl5iUiI+JjZeOhoyQjImQj5GNk5SVkI2NjZKJko2QkJSNkI6LjI+OkpqUjpWXjo6Uo5yglJWXk5mVl5iZlZqfmZ2dmoCZjoqMk47ypaCkpaOipKKdqamxxbnf54qQia2JlpeXlJGak+W26ZuWhoHy6fPo5uTp7eH2/fmCh4qGiYWLkImLjJOSk42Vk5SSj42SkpaSjYmCiIOJhIOGiYyWkJOPjo2MjZGHiY6Li42SmaW5w8G0oI2flZKVl4yDhoqLkYuWmYCXmZeZmpeYk5CLmZaUkpOQk5aOkY2SlY+QjYSNh4mNi4+TkJWPio2JkYuNioiJio+LjYuKh/jr24OEiYyIlpeVi5iboZ2Zk5SIiY6Kk5SWmI+Pk42RlpaWjJGOjI+OmJWbj5mRjI+OlJOooJmOiZKNjYyJi4iEiIWJhoqLkJmSkoCXkpaXmJidmJaVjYuQkI+coZaYjo+Oj5SRjI+RkZGHhpKLl5yOjZOYk5KPjpGMkoyPk5KEiZWQkIickpOVl5qUmJyijImGjo+Qk4qG//OJjIiLkZSSjY6QkI2dm52YmJiLjZCLj4yJioaFkouJgIGK+OTk5PTn+5WYoKqaj52TjoCUo6OjnZmjnpuGicfDwKOIgOra4tLm7a631NPO1dfi5+rp+IGDg4GEg/P0iI2QlIqFiIqUlpWWm52mo52r3rmbm6SZkZWbk5SWlIiHiYuEiYiFiY6Qi4eOlJSdlpCFkJORjZCQkJaTjoyMhoeUkoyPhIeIhIT4hvuAiIiOho2Vo4ChqbmQiYKNlJKLj5ORioqRkY2MhoyPj5KWnpeRl66bjY2PjZWNj4+ShoOC8IWIhoaHiIuNjo6MjZSSi4yJjJKPjIiQlo2Pio2QmIuFiIqGkYqJkJCPk46RnJuTjZKLlJePkpSQj5CPjYmFjJSLjIqHioqLmp2XiZCTko+Nj5aJj4CCh5eRgHd4f3qDhol8en5+goN6hYSCf4OAhIuJf3yEfouEf4p8f4V5eeTi6HqCfoSFgHrke4OGf/CAgoJ+fnyBfHzn7XyE7np/gX1/gX95fX144n52jIyPhX59hoeGg4V+fYB/eYF9e4mBf3t85np+get8fe56gfJ+hIB6f+h/i4CCe398gHx9gH6BfXuFf4CChYJ/gIOChoeJe4WHiIGGgoWGf/V7e4GDhPR9gX2AfO/1goR8hICQmZuMgIaDgIZ8iXuDgoV9g3l9foCDhYp+gYh32t/re3+Ch4R/fX+E6nqHfHt/fe18gYCBg4SFgIB8hISFhX6EgXp8hIeHhH1+hoCFhYF/gYOCe32EgYSBgoWNgHeAhn6EjoiCfoSHgYCCgYaHgYODgvR/e4GJfn18f39+gIWChoaJiIWGiIeGi4uKfoaAg4mDfIOIg36GhYeEiYuLhYOGhIl+h4WHhYiCh4WAgoaDho2Kf4iKgIGIlY+OhIeOi46KjIqOjY2RioyOioCLfnyChn3NjIyQlJGQko+LkZKXpaHN2YCEfp18iY2LioeOiM+gyouNgn7v4e7i3tvh4tbq8+58f4SBg32DiYKEhouKi4aLiomJhYOHhoyJhoF9g36DgH6AgIOMhoiFhIWEg4Z+fYJ8enl8gIWSlZiVjX2Oh4eGi4V+gIWHiYKKjYCNkI2KjYuMiYeBj4uKi4mFi5CHh3+Ii4WIh36GgICGg4eJhYqEgIV/h4GEf3x8f4SAgoJ+et7Lt3BydHZvenx3b3h6g4eKh4aAgYR/iIqLkIWHi4SIjoqNgomFgoaFi4mPhIyEgIWBioWSjouAeoaCgIF7gYF6f3p8eHx7fomEhICIhYmHiIePiYqKgH6DhISNjYaIgYWEhYmFgoGEiIR5eoeCjY+CgouNh4eIh4iEiYWIio59gYqHh4GTiIuLjY+Ki42QgIB/g4eGhX597d58fn+BhISIhYSGhYCMjI+LjI6Dg4eEioR/goCAiYSDeXqC7dbW1uLU3oSIjZeJgo2BgICHko+Ri4eSjox6e6Sbl4Juasa7w7PH1Jymx8bBzNLe5Ojk8H9+fnyBgOzvg4iIjIV/gH6IiYeGjYuVkYyVupqEiJGJhYaNhoeKh4F/goOAhYR/hIeGgX6EhouLh4J8hoaFhIGFhouMiIWGfoCMiYCAen5/e3vofvF6fX6EfIKJj4CPkJ6CgXuBiYeChoeGf4GHhoWDf4aFhISFj4qDh5uMhIOFhIqEhYaHfn1743t8fH99gIGCgYGDg4SHgYB+g4eEgH2EioGEgoCGjH57e35+hoCAh4aFhoCFjI+CfoWAhoiEhYeFg4SCgn9/gYiBgoF+gH6AjZKLfoOGg4OCgod9hBVwb3t7bmttc212c3RramtscXVsdHSFcIB6d29rdHF3dG11ZWtxbGzKzM5pbW5xb29nzWpwdnPXb25xb21rdW9u0dNuc9FpbnFrcHBwbHBvaMNmXm9ucW1ramptdXB0bW5ycWlub212b21oadBvbnTSbG/Ya3LcbnZ0am7TbHdubG1rcW9sbW1yb2h0cHN1dXFvcHNydnJ2a4B1eHpwd3R4enTjc3B1dnbjcnZydHHa2nN2cndwen+Ae3R3dXR6a3hwc3R2bnZrcHJwcHZ+bG5wZMvM2XJyc3d0c3NwdNRqdG9tbmzZcHZ0cnR0d3RzcHV2dnhtcnRtb3R3eHJubXNyc3JwdHR0bW51c3Vxcnh+b2twcmxzfHJ0boB4eHNtcnR2cnR0dnLhdXJ0dm9wcnRzdHV3cW1yeHd2dnh2eH96enF4cXl4c29zeXNvdnR3dnl6e3d1eHN4b3l6eXV2cnV3b3N2b3V3dm12e3VyeoF5eXF3e3t4cnZ3e3t4fHZ2eXJzaWpzdWuob25ze3l2enlzdnV3e3uvynlzbICLbHd4fXt1eni5fpxwfXp25NPg19LQz8/D1ePdcXN1dnVxd351e3p8e397fHt5enZ2d3l+fHl1c3d0eHV1dHFyeHd2dXZ4dnN1bWtubGViZF5aYV5lbXFsdXF0dHx3dHZ6fHx1eXx6fHx6enl7dnNsf3h5fHl3fX14eG98fXN3eIBxenR0e3l2d3V+dXR3dnpzeHFsaW9zbnR4bmnCqY1bXVhbW15aVExRUmFudXNxcXJ1b3Z4eX10en12eX59fXR6d3J1dnp6f3h4d3J3dntwc3ZzbWh0cnBvbXV2b3JpbmZpZ2p2c3V2dHd3c3N7eHd9c3F4dnR6enV1cHdzc3x3eIBycnh0bmp1cn6CcXJ2eXV3eHl6dHtzd3aBcHZ8dHh0hHh7eX+DfHh3dW5wcHR5eXVxb9bDbm1vcXNscnZyeXNucnR3d3d9c3B1eH12b3JwdHt5eXBscdi+v7y+srJwcXR8dG11aWxwdnV4c253dnZpaXVoZFhOTpeVn4+jtIeStIC3r8PJ09nd2ON3c3NxdXXV3XZ4eHh2c3Jsc3FubnZxenV0d4NtaW91dXNud3N1dXV0cnR0dnh5dnd3dXNvcXF5cW9ua3JxdXdydnV4fnl2enByeHVubGxxcW9tzm7cbW1wc2xydnJ0cnhtcm5udnJydHNzcXN0dHNzcnZ2dHJweGZ0bnJ6dXZxcnF1cXN3dW9wbsxtbG50bXJwcHJxdHVvdG9ucnJ0cW1sb3RsdXVtcnhubmtqbnNvc3d0d3NvdHV3cWtxcnFzc3FydXFxbHFvdnB4c3NybnBrcXV6eGtxcnFxcm9ybHSpf4N+h38BfoR/AX6JfwV+fn9/fot/AX6afwt+f39/fn9/fn9/foV/AX6lfwF+hX8BfoV/gn6hf4N+iX8BfoZ/AX7AfwF+338Bfo5/Bn5+f39/foh/g36Ef4x+/3+Dfv9/lX+CfqJ/h36af5J+hn+Cfsp/A35/frN/AX7UfwICBACAoZmMiZCSlpWNmJSNjY+UjouPgZOIiYiSk5aEhJeGjIeMlYuG+oSC74WSh4WXlI6Ig4mDiIyKjIaJiYGIh4aEhYqDioOBgoDx54+OgYeSjJeJi/3s8YCKpIKE/JmIg46QmJyPjIqFi4+Kg+vtg4iYj4SBhIeKgYeQgYWAhY6Ri46AjoiMiYiMjZCXlJGLkI6Xlo+Vl5OIl5CQiIaKjpOFjImJ8fv+g/uHiIaFhYX794CDhYaEkpGfpaKgj4eLhY6GlZGEj4aHjImOi4iIjImThIf49IaHgoSOhIWLjYmDiv+IgIWChof8goSNh4OHj4mIhpaGhouQioiLjYyPhI6Ni5KAjoWJjJSHi4mMjI6NhI2Si5ONkI+OjZmZk5mUi4yQkY+PjY+IiIeHi4mJiYWEioyJiYqQkpekjYuQhY6akZ2TkYyRj5SMiZWNk5OMkI+Oi5COjZaNmpSTi42PkI2NlI+Ti46PipiTkZWPhZSSkoiPkJ+qloyVkJWfk5SMmZefnI2AhIqRl5PxkqKptaOlo5uYoKetyseurfvs9tyJnpCRlpqblpSTj/fG35KMhPrq6Ovx7e/4hYaDhoWJiouMjoqPj4yRkZCRjIyIjIyJl5WTkJSVkJGWkYyMiImFhZGPkpyOjZCUjY2KjpOOiYuNkJCZucKkmI6Xk5eXkIKCiIyPk5GAkpOPmpqWlJORj5GTkJGPi5GVlZeZmJGLjJGHiYiIg4aFg4qJkImHk4mLiISBiYuJjI2MgP+BhoT7+YCAiIiSiYuFgomQlIKQl5KHk5aYk5WWjImTkY6KkJOSi5ONkoqTi46HjpKUkJKboIqJioaJho+PkJCLk5GRi4yJjo2SlZaAnJuSq5COk5eWk5KHjouYkYeOkZWVlpCTmI2Qk5OJhIqGlZWXmJSRlJKYlpGJkpSPjY+NjIaMj5CNjY+Rk5ONl4+QooqSjpCSk5OHg4D+jYuLj5GRlYCGioyHjpKgl5qZmpKKjoaCjpeOio+BhpOMgvTt5fHq3+aJjJiZm56SmZaAlI+Zl56ak5aZm6bFvo3b7fv44djew+Hm5evX5ejf4ejw7f+Cg//5/+v7kI6PiomRkImTko+Uk6CXnJ6lpsK3op2blI6bk5GUjY6MgoqF/oOFlJOTkIqPlY+MipePj5WUj5SYlZGPkYyOl5iXkY6Flo6D+vaB+f/0goLu/IKDj5GAl5qxi5OJhpCMjI2Pk5OMi4uSmJaQko6Mi4qVkJyio5SQkYaLhoqBjIX6goWCiY2HhoqHgo+IhIaDgoCKhYeEiY+RhoiTjoyPkZGMio6GiI2Ui5OcmZmPjomHh46SjIyJhIqVjoyShpCRhoWKh5SRjpWSlZ6SkoyHi5KQiJCFjo+Ako1/fYaHiYmBjIiDgIGIgoGGeop+f4CHhoh6eYp6gXyBiIB65nt44n6Hf3qIg4F7e353fYB+e3t9gHmAfXp5fH94f316ennj3IeEeH2HgYh7gezY33F5kXR55IV3eISDiIyBgn97gIN/etzheXyIf3h5fX2AeXyDd3x5foSIg4WAhH2Bf4CAgoWHhoV/goSLiYOIiYh9ioWEfX6Cg4l8g4GD6fDxeup/gICBf37r7Hl9gX9/iYaRlI+QhH2CfYN9iYh8h3x9g3+Fg399gYCIenrf5YCBfn6Ee3uBg4F9f/GDd397eX3ufH6HgXyAhoB/fot+gIWGgoGEhYODfIOEgYWAhHyBg4iAgn6Cg4OCe4SHgIeEhYWHhI6NiY2JgoKGhoKEhId/gIF/g4GAgHp9gYKAgICDgoaTg4SFe4WOhYyIiIOHhIWDf4yDh4qEhYiFgomEhImBjYqIf4GIh4KEiYKGgoGDgo2Hh4eGe4iFh4CGhIyai4eOhomQg4l/jImSj4KAen2BiYPTg4+SnI2PkIeJjJGSqKeXm+fi7s1/jYGChYyNiYiIhOOxvICAfvTm5uft6ObugIKBgH+Dg4KDhoOFiYWJh4eGf4WAhYN+jIuIhYqJhIiNiIOGg4OBfoaEho+FhIqLhYN/gYSAe3t6enN3k56TjoONiI6NiH59gIaGioaAioeGkJCMjIqIh4iLhYmFf4eJi4yNjYeFg4qAhIGCfH9+e4N/hoCBiYGBfn14gICDh4aCeOt3e3rm4nR0dnh9dXVvbXB4gHGAioaAioyLioyNgoKJiIaBhYmIg4mDiYSJfX58goeLgoSLj359fnt+foSCg4OBhYaGg4F+gX6BgoOAiIh+kHx9homIhoZ9hoCNhoCGhYmLi4SKlIaHi46Ce4V/jYyNjYeGiYeOjYiBiIqHh4eFgnyDiYeFhYaKiIqDjoWHlYKKhIOGhoWAeXbpf36BgoOEhXV+gX57g4aRioyNjIeBhn57ho2EfYF4fIeAeObh2eTZ0dF+f4aHi46GioqAhYSLho2KhoeLiYielnG0xtXUw7vGrtHW19vJ2uPd3ubs4/Z/f/jz9N7vioeHg4KJiH+IhYSHhIyFiYqQjqGViIqJhYKPh4OKgYKDfIN89oGCjomIhoOFiIB/fIuGho2Lh4yLi4iGiYaHjZCQioh8h4F76+R67PPqfHje7Hp8goGAhYaZf4eBf4aCg4aGh4eEg4OJjIuFiIWEgn2HhY2SkImGiH6DfoF2gnzwe315gIV/f4J+eYR+en16enuBfX98gYSDfn+HhIKEhoWAgYN7fYOKgImSjYmDgXt8fIOGgoGBfYKJgoCGfYWGfHmDfIOHg4mHiZCFhoF8goSCfIN5hIOAd3Zrbnh1dXhzfHVwb3BzbXN1bndtbW90c3VoZ3BobmxucWtmy2ppym5zbmlxcm9nbW1pdHJrZ2ttcGxwbWpoaWtobm5oamzIwHdxaG11cHVrc9u+w2Bgbl9lwGdfZnRucnVrb29ubWtpbMPMZ2l1amlscm5wbGltZ2xscHN5cnKAcmtvcHRybm1wc3Vxb3N7eHF0eXlsdXJzbG5zcXptcXR12NnacNV0c3N1dXLV2W9xc3ByeHN7fXl6cnBzc3hzendwfm5sc292dnBtc3B1amm3x3d2dXRzb2pvc3JvbN13a3NwaGzbc3J4dnJ2enVxcXpyc3Z0c3N5enRyb3N3c3SAdG1yc3d0dXBydHZ0bnV0cnVzdXR2cXp8eXp4cXR2dXFzc3dwdXZzcnFyc2tvdHJzcG91c3F6c3R4bnd7dHd6eXR3c3J0cn1xeHp1cnd3c3t3dntyfXl3cXN6dnR0enN1dG5vdH1zdHd3a3V2d3F2cXCAenl+d3V/cXRveHV4dmyAaWpsdWuvbnNwe3BxdnJxcXJvfHx1fMnT0rVtdG1vdHl3dXZ3dsiTk2ducuTb29/k2trddnt4dnV6eXd3enZ0fXp6fHp3cXh0enhzfHx2d3x4c3h9e3h6eHd3cHZzdXt2eH1+d3VycW9uaWdjYFNQY3J5eXN9dnx9eXR1dHZ1enWAd3J1gX95d3l5d3l7dXd1dHp6fH19f3l4dHt0d3R1cXR1cXp1eXN5enR3cHJsc3N3e3h0bNJvbWrLxmRjXmNpX15YU1NcZVtodnRzeXt6e35+c3R2eHl3dXp3dnlze3mBdnVweXZ5bnB3dWtsbW1wcXpxdHJydHZ2dnFrcG1ubG2AcG5ocWVndHV1eXlxdHF8dHN3c3p6d3R7iXl4fIV2cnZwf32BfHVzeHJ8fXtzenh1eHR0cmpyd3p2cnZ6eXhzfnd1fW98dnB3cXRzbmrPcGxxbXBwdGlvcWtqc3d4c3d7eXVyc2xsdn5yanBqbXZwa9HKwsa9srNsbnFvdnV0d3ZicXFzb3V0dG91c2VrY0+FlKKnoaCpmLzCwsi8zNfV1Nnczd5zc+Tl5cjTfHh1c3R3d210b29xbnBrb3J2bnJnZm5xcHF5dG57cnF1cHNt5Hd2fXl3dnh1dW5vanh0dYB8eHqEeYB9d3d5eX16em1tcGzSzW3Z4ddvasfQbGxsaW1pd2p0cXB1cXN2dXd3dXR1eXl2c3d2dnNscXF1d3Z4dnVwc290ZXFu2GxybG51cnN0bGdycGtsbWxwd3FsbGxybm5uc3VzdnRwcXJ0bm1xdnB5fXlzb25tbW11dnB0dHBwd3BtcRpuc3ZxbHNrcHNxdnJ1fHB1bmx0bmxpbWVyb6R/BH5/f36ff4J+iX+DfoV/AX6Pf4J+tX8Ffn5+f36Gf4J+on+Cfox/AX6GfwF+/3+gfwF+kH+Efot/Bn5+fn9/f4h+/38If39+f39/fn7/f5R/AX6if4d+l3+VfoJ/hX6kfwF+o38Kfn5/fn5+f39+fq1/AX7WfwICBACAnJSWlo6Sj5WdjJOYlZSEi5CUl4yNjJugoKGUj4OTlp+TipP37oGPio6NkpOMjIqYj4qOjI+FhISBhIGGkoyNhYiEgPT2g//04YaThYf8+oCLi/z/+4GOk4XW5+T4g/X+iJOQk4mRof3+/4aRg//qg42J84OIjYuKiomGiI+QlpiAjvmJkvb659/6jJGF/o6Sk5CTjIeNlpOG+YmNj5GKh4f+74KBh4eCgv2Gh4OAhoaIgomLio+bnaijl4+MgIuIlISNi4uGiImFi4ePhIyDi4ySho6GgouSjoWL/ID9g5KL/oaGiIiBg5SSlIGAjoaRj46Lh46Ljo2QjYeLkI2PjoeAg4eHjoaQkZGGiZCRioiPhoqPjo2Mj5CclYuQjZOPlI6RioGAhYGCiZKKjY2PhIGJi4OLkpWyj4qQjZSPi4mNj46Qi5KZkZWLko6HkIuRiYyRmZKQlY6QjZGPlZCIkZOMko+PjJmMj5ORmIyPkoyIkoSomI2Ni4+NlZqSj4j6goGAj5aeksOAnZqhr7SytLGqoaKtz7q1tJ/T6ImPl5aSoJiZkpWbk5CUkoj99dCJi//08/n4+fr/gIOEhoaMkIeNio+SkY6RjIyThoqSlZmQmZWakI+VlpyWlpKNi4mUlZOWko+KlY+MjJGPj5KPjISFm7bAs5KQlZebnpb7ho+RkYyAl5OTm5OTko+VkoqXk5iThJSXk5iPkZGQiomRj5OUh4iGhIeEg4SIgYqLj4qNipGMiISG/fqEh4D2gYaC9YGCgP/8/M/yk6KFnJaKkJOVkZGSoI2OkY6RjY6OjJSQiIXyh4yPlYnvkI+WmZSDgouJhYeAiIyKjIuJjJKJkJCSmJINjpGZiIeKmJCYkIyNj4SRgIqUiZOSkY6BgYyFhIKHjZSSkJCRmI2Rk5SLkYmHj4eMiYuJi46SkpORkpGLhI2OjpWYnpCLjpOIkIqFioiAgomEg4yTm5iFgICGiYyKhvuKi4mFg5mNmZSTkY2SkILw4+7x6+vm5OaEk5mOjIaAhoiOlsO0lY6prra4svjK8tyAgPbt6+Pj1t/k2ePm3uTt8v6EiYyHhf/f+omRh4aLiJCNl5qfmpuVn5misKyiiZyUipCamZKSj5CXioaGgsrbi5mVkZuXi4eCjZSKk5CLkpqZkoyPkZiQlZeFiZeQjp6SiI6H9+32gu6H9YHy9/GNoISHjZKKhoWIlI2Oio2TlJCIb4yVj5OQlYqJiouCj52ijo2OlZOGg/+FjoOKjobz/oWGhYn5gvmFhYSCh4ePj4aBhpOGi4STi4yHipGSiYqPlqyoo46HhoWG94qMhISKioWIio2Kl4iJiomIkYONiJuflImIioyFhZaRjI2RmpKThICOh4eIg4SEiY19h4mGhHuAhYiJgISDj5STkoiEeoSHi4F8hOLedYF/gYKIhX18f4iBgYF9gnl4eXV9dnuFgYJ9gHl45+x78OjZf4d8fuzpeICD7PLqdX2BdcXV1uV43/B8hYSGeoKS6O3qeIR66tV2f3zken2DgX+Bf31/hoaKjYCF6H+F5/Ld0OZ/h3vlhYiIhYWCfYOLin7rgYKEhIB9fvbpfXqBgHt88H+Af36AgIF8gYKChI2NlpGKhX95hICKfIOCgn1+f32AgIZ7gnuBgIh7hH99hoqDfIDseO55hYL0gIGBgHx9iYiGe3aDgIiFhoOChoOHhoeEfoCFgYSEfoB+gX+Ef4eHh36AhYh/e4J7gYaBgYODgomJgoSAiIWJhIZ+eXp9en1/iIGEgoZ8eYCCeoKFhZ6DgIiDiIGAfIKEhIV+hoyDh4KIhH+Ggod+gYeRiYaJg4iGh4WLh3+GioGJh4qEkICEioaMgoSJg4CDdJOGhIaBg36GioaCf+p4eICBh4yCrnKHhIiUl5SZmZWLiY2nnJmdjMfdf4SLhIKQi4uEho+JhYqGgOzhwYCF8+/u8vTz8PN6fn2Bf4aJf4SDhYuJhYeEhYt9goeKjoWKio+EhIqMjouLi4SCgomIh4qIhoOLiISDiISDg4B+cm+AkJuZhYaMjIySjPN+hoqJg4CNiImOiYaFgouLgoyKjoR2h4iHi4SJiYeBgIqIio2AfX59gn59foF8hIKFhIOBh4J9e4Dw731+duF4enbfdHVy5OLgs9OAkXiLhn2Ch4uHhoaTgoaIhIiEhoeDi4h/ecp5f4OHft6HhIqNinh5goB4gXZ8gYF/gX6ChXuDhoWKhICAhIl2en2Gf4eAfoGCgoiJiX+JfI2LiYZ9fIN8fXx+g4yKh4WJkIaJi4yCioF+h3+EgYOAgoWMi4uHiImDe4OGiouNjoWBhYV9hoF8gH91en95e4GFi4h4d3h5enp4dOB7fX18eo6AjomJh4OFhXnk3OTi3t7b0NB3hYuCgXt2eoB7goakl4J/jo6Rk4/Io8i5btbV2s3Sx9HW0d3e2d/n6/R/hYaAgfvf8oKIgH+EgYmCjI6QjI6EjYeMkoyEcIOBfIONjIWGhIWKgX+Ae7rUhY+IhI2Mf3x6hIp/iISDiJCOi4WHiI2HkI9/gZCHhI+DfIN+6ODlfON+5nnk7OeCiRd2e4KGgH5+gIiCh4OGi4qFf4WKhImGiYR/ZXiCjpGDgYSJhn578nyEfYOHfebzfX58fel55n98e3yBgIeEfHl6hnt+e4R+g35/hIZ/foaJm5WSg398en7lgX96fYF/e3+Ag32Fe36AgX2EeIN8jpCGen6BhHx7h4SAf4KKgoh5gHlyb3RzcnJ1eGt1dHBxbW9ycnRvdHJ2eXd3dHJqb3BvbGlux8lnbWxtb3Jxa2psdG1wbmpyaGZrZW9qbHFucHBua23Oz2jOzcBud21v0s5oa3bZ4s9jYGlisLm5wmjE12xwcXFobHXHz89mbmvItWJpastrbHJzbnJwbm91c3V5gHPKcHTN3cq4yWp0cMd2eHdycG9vcnd4b9dyc3J0cHBy4dNzb3Z2bnDWcnVzc3J1dXBvcnVzd3d8end4b2x4cXpxdnZzb29xcXJzeG1xb3Bvdm10cnF7enVsbcxq1WpsbeByc3VvcXJ4dnNuaXVzeXV6d3Z2dXl6eHdxb3Jvc3ZxgHR1cHJweXh4cHN3eXBucmxxdHFvdnRsc3V0d255dXh1dm5sb3Fucm55dHZzeXBtcXNsc3NueXByfHV1bnRvd3V1c292eXN1cXZzcHVzeHFzd4F7dnh0eXl2dXx3cnN2bnd3fnmDb3Z7d3l0cnd0cnBfd3FzdnN0bm50c25sxmdsgG9xcm6RWWRiY21ranN2c2xrZ3Z3dXxxt8xvc3hwbXh4dm90e3d1d3Nzz8CscXrX29/d5Oje32xyc3VxfHt0d3d1e3t3eHZ4fHN1eHl6dHd4e3V0eHt4dnl/d3F0end2eHh2dXt5dXR7dHVyb2xdV2JibHh0eHx5eXx13HN1fHp4gHx3eXx5dHl4fHpwfHt9cmZ1d3h5dHp6eHV0fXyAgXdvcHJ1cXN1dXB5dXh2c3B3cnBtduHicG9pzm1ta8hlZmPBwreVqGVzZnNubW91eHh2cntvdHh4eXV4e3V9enFrwm9xcXVw1HdyeXh0bGt6dWl3bG9zcnJzb3Z2cXV3c3hwgG1tdGJoanNqcmxucm1xdXl6cHpxfX57eXNwdnBxcG9yf3t3dHt+d357enN7cnF5cHRxcnJzdHp6fHl3fnpucnd8e3t6dHN1c212c250c2prbGhtcnB1dGtqaWllY19cvWZpamtse3F8dnl4c3F0btHO0MjLzMazrWNyd3JybGhogGZxb3txaWlrZWBiYY52lZBWrbXAsrm1xMjF1NPQ0tzd5HV3d3N35czbc3ZybXNxeG17e3h1e25zbW9tZVtRZWlqcHd1cHV0dXZxcXRxpcZ2enBvc3dubmpzeHB5dXF6fXl4dXp5fHV+fW9vfnVzdm1pcnLLzMxuzW7Pas/Y1G9rgGFpbXBvcXJxdXJ7dnh5eXNwdnh0dnh2bWxubGlvd3N0b3R0cm9u2211cnZ4ctLgcnBwbdRsyXFubHB0b3dwbGppdWxwbHRudHFudHZwbXR2gHt5cnFubXHNcnBucW5qanJxb21xaWxycGlyanVwenhwaG1vdWxqcnBubWxybXVpo3+Cfp5/Bn5+f35+foR/CH5+f39/fn5+hH+EfgN/fn6Hfwx+fn5/f39+fn9/f36OfwN+f3+FfgR/f39+i38Bfod/gn6GfwF+sX8Hfn9+f39/fv9/nX8BfoZ/AX6Sf4J+kH8Ffn5+f3+IfsR/AX64fw1+fn9/f35/f39+f39/hX6afwF+hX8Bfvl/AX6Pf4l+lH+EfgF/kH6Ff4N+pH+CfqR/C35+fn9+f35/fn5+qH8BfoZ/gn6EfwN+f36ifwF+p38CAgQAgIyJgpCTko+IiZSUl5GNiIiEkpOfoa2jlor+j5iYjpibhOyCjImjkYuMi4WQlY+QhfKCjYmKjYeHhoqLhJGIh4mHhYOGiYmF5PaFjYaD9YWB7vCBgoCC5PDh6+j9+u32iJKDg4qJiZKUvJSaj46GloHtkJiSi6GOj4yDiYiLkIn/gP+Cgv2MhP6EhICehIuKi4+KjYyFgYWE/4iUjoeKkITuhYGCg/7/hoeN/fj2gIKEhIiLkKKlpaSjnY+EiomMhoaDiYyHhoqGhpCRhYCQiJKMkYqJiIOF+4SL85KUi/32iYOGhoiEkIiKh4eF/P6FiIyJiIONjZCMjY+NkoyPkIyMgImQhIGLlIaHkY2MjIKIiZCXjo2NkZWSi5SemImEhoSJjJCE/YiLhJONiIqTh+yEj4qIjJWyn5SPkoqLjI+OhZOPjpKQlJGEjZGLho2MiI+JlpSOj4+Rk42SjIyWk5OUj5KJhI6PjY2ZlpiFkJGJiZWblpGOjZWMiZKalvP0hYyQgI6RhO34gYORk5ufsaS8x7yssNnGvLeiy/OLkZSSn56gmpiZlpWZlpGTlZGEgPOAgYmG8/n5gIKCgIaJh4mLiIyPjomPko+TkIySjZOTk5KVk5WVkZmbk5CajoqMh5GQlYmJjZCJiJGPkoyMiI6QiYmQpbm5l52Yn5yL/YeMjZGTgJSRkYqYlpqWlpOSkZCMipCRlo+Nj4yPk4iHi4yNjouPi5KOjomQjI6C+YOKj4WJjYGD9YOSj4WFhoWChYSFgPf67ezu/KuUmaWNjpOal5KfmJmCkY6UjI+MkJSLjJiTjY6T74ySkJSTkpSNlIuGg/b1gYSJgYSGj42LkI6Pj4+LgI6NkJORk42SmYyEh42JiIyOioeJhYWQjZKK/+T8hIOE/oiPjIGJh42Qk5GSkYeLg4eKjY+JhY2KjIiLhYGGjIaJhZWXlYyLh5KWj42NioWF+vSMjImLi//9ifiEioSUiZGQkJCGlpONk5GGiIyLgoKK/ufohvrw2fKAh5GQgoaQgOmIk76psLK/uLazkL2X7uGy4OLm3/Hp5tfg0t7l74SDh4qJhf7459yPiIGMiIiBkoeakJybmKOns7anmY6GlZaPjo+HjpiSk5KOhfPNgJCVoZSPiI+VkYmIjYiTkpaRipKVjZCGioqOjJKKm5KZkYn59oODgYaFhIWEgoKA+5mGgImAgpSYmJONkpePjo6NlYqLiI2QjpWajpSHiYqPk6GRi5SQlY2Sio+EjoeCiYmChoSA/oWFkJGGiYuK9YKKi4iEgZCMhoaLlJKMkqGwrJ6Pi4n9/IeGiICAh42JiIWDgf2Nh4L9hYuIhfqJlZmLh4GFjI+Hg4+TkIqQkIWNj5ycgH+AeYOGhYB7fIeFioCAfH96hoeNjZ2Sin/rg4uPhIyKc9d2fnqOgYCCgnyHiYCCetV3gHyAgH58eYKCeoh/fn98e3h7foB70+d7gH165n133uR8eXd41eHS4Nzt5uHke4N5eYB9eoSBo4GJgYF5jHbZgYeBfZN/gn54fn6Ag4DxgPF1ee2Ce+99eXSNeYGAgoOAgYN8en6A8H6IgXx/iHvhgHx8fO/0gH6D7urrfHx+fH+AhZOUlpSUjoR7gYKFfn9+gYKAfoF+gYeHfnmGfoiChoCAgH1/6XyA5oWGgu3hgH6Af359h4GCf31+8fWAgoSAgHyDhIeGhIeEiISDhYGDgIGEe3uBhn5+ioaAgn1/fYSLhYSAhImFgIeOhX57fHt9gYV+8YCBfYeEfoSJftx7hoF+f4aekYqHiYKChIiGfoiEh4iFiYd8goWDfoODgIN+ioqCg4SIioOGgIOLiYeKhYd9fIKEhISMi4t7hoiEgYiMiIWDgoh/fISMiubkeoKFgH+DeNjkdHN9foWEj4OWoZqPkLCmnp6SweeEhImBjpCRiIiIhYeKhYCEiIl8duJ2fIOB6u/ven19fYCEgISFg4SIh4GKiIOKh4SKhoyKioiLiIqKho2MiYSPhIOGf4mGiYOBhImDgIeGhoODf4ODe3p/h5SchpKNkpCB7X+FhYaMgIuIhn6NjYyJioiHiIaCg4mJioGDhoWFiYGBhYaDg4SGhIqHhoOJhYd/7XyCh32DhHp86HuIg3t6e3l2eXp8dt/m19HP2JWBh5N/gYaNjYeRjI16iIOJfYeEiYuCgo6IgoSH2ICGioqIh4iCi4J+fu3uenx+eXp+g4GAhYKEgoaCgISEh4eFh3+Fjn94e4B9fYOIhYKDgYGHhomC79Tyf4B98YOLgnqFf4WKi4eMjIGFf4CBh4iBf4WAhH2Dfnt/hX+Ee4mJiYGDf4WIg3+CgXx7599/f32AgPLxf+B7gXqIfoWFhoR7ioSAiIZ7gIKDe3qC79vce+Lcxd50e4eEenp+gM5zeZqJkpKWkZGReJ6EzMaeysvRz+Hc3MvVydXi6n99gYKCgPf039aIgHqJg4J8hnqPhY6Mho6Rko+Gfnh0hIaBgYJ8g4uGiYaFfeXDeYaJkIOCfYOJhYGAhH6Gh4yIgIiLhYWAgoKHhoqCkIOIgHvl4nl8eoCAfXx6ent58Yh3gH96eomIioaEiIuFhIaEioGCgIeGg4mNgYh/gYSEg4+GgImGiIKEgIV/hIB7gYF8fXp58Hx7hoh9f4GA5Xl+gX58eISAe3yAh4WAiJCdlYqCgn3t7X98fnd4foF6f396eOqCfHboe359fOeCjpCBf3l9hIh/eISGg4CGhH2Cgo6MgGxwbXF1cm5paXBxdmltam9qcnV0cH50cm/Pb3d+cnR1YbtmbmZ0bmtscW50dG1va7pob2twcG1xbXNya3dwcW1qamlpa25ntcdobG1rymxqwsxua2Vpuby0zMPRxsvNZ25qaW9sZXFofmlybGxnd2e2am1qaXtsc25ob25tbnDcgNtkadNybthsaF5vaXJwdXRxb3NvbnF23HB1bmprd27SdW9xb9fkdHBz2NnfcnFzb3Jwc397fHt7eHNvdnZ4cHFxcnN0cXJxdXZ5cW1xbHhzdXJvb3J01nJvz25zctPCcHNzdG9wdnNxb2pv2d5zdHRxcnBzd3h6dnp2fHZydnNygHV0b21wdXJwfXpydHJyanR6dnVudHlycHJzcXBtbGtscHVx3HJ1cXN1cHZ6cclwdXFub3B8enNzeHdzdXp2b3Z0eHd0eXRvcXB0b3FycHNvdXZxc3R6fXN4c3R6eHd5dHlxbnN0dHR4entydHZ5cXN5c3Vzc3JwbHF3dMvGaXBzgGtvYq+5XFheX2JbX1lkcHFpZnp8d3p4s9N2cHJyeHt5c3JycXZ2dXJ0dndqaMZnb3Ry1t3fcXJ0dXR6dnZ3eXZ9enJ9e3R5eHZ5eHt8eHh9eHp4d3p3d3V9eXd4cHh2d3R0dnp4c3h4dXd2dnRvaWlqYmR1b4B9fXtu03N3eHR+gHp7eXB/fX16d3h4eHl2dXl6enF1d3h2fHZ4eHp6eHl7eH9+enh8eHx003BzdW91c25y1292cWxsbmxqbWxvasXKvK2nq3ZlbHdqcHR7fHd7dnVrenZ7cHh2eXt2cnx1cnJzuHJ4f3x5d3t0eXFzd+DccHBwbG1wcnRydnZ1cXV0gHZyeHp2c25zfW9ra2xqbHB8d3Z3d3h5enh12MDfdnly3HZ+dXN6b3Z8fHp+gXF4dHFzdnZ1dHZwc2l1cW90d291b3t5d3JycHR3c3F0dWxuysFtb21vcN7abcZrb214b3dzdXJvd3JteHZpcnF3cHF22MnHacTCqbtfanRvamlkgK9fX2tia2hkYGNiVXVjm6CDr7K6u8rM0L3CvMfV3XFudHRydeTkycR8cW14dHNwcGV0b3d0cHNyaWFbXl5gbnJwb3BrcHdzdnN1cc+3anVzdWtubXN3c3FydHBxdnx5cXV4c3RzcnR2eXp1fWx0aWnLzmtva3Bzcmxua25r3G5ggG5ubHh1dnR1dnd2d3l1eHJ0cndzcHZ2bHJydHRzbnZ2bndzc3BxcHJxcXRtdHJvbmpt2GtsdHdwbXFw0WpucG1uaHJwbGtwdnJtd3l+eXFxc27VzW1ra2Zqbm9mcnJtbNZybWjKbG1vbtJ1e3xwcGpxd3pwaXBzbm1vcWxvc3lymX8Bfod/AX6OfwF+ln+CfoR/BX5/f35+hH+JfpF/AX6Ofwh+fn9/fn9/fpB/AX6HfwF+hH8Ifn5/f39+fn6qfwl+f39+f39/fn6Mf4J+tn8Bfol/AX7Nf4J+hn+CfpJ/gn6UfwF+hH+DfsV/AX6ufwF+iH8Bfox/hn6dfwF+jH+Cfql/B35+fn9/f36vf4J+hX8Efn5/fpZ/BH5+fn+Efod/AX6LfwJ+f5B+hn+EfqN/gn6jf4J+i38BfrR/AX6IfwF+ln+Cfox/BX5/f39+hH8BfpZ/AgIEAICIk5WWjpGWnZONi4mTk5aJipyzqpmQlJiWmI6Qh/jj7JOsiYKIhtfykpWMlJmdlYmTh4yLjI+Eh4mJkIyIi4qJj46Dg4CKkIaClYaBhoqC+YKIhZD58+vm6uDvgvnc8PmDhYyRjY6LoJKXkYWIiZT75Y6A7oiGj4WLi4qM+IiPh4CLj4SFjoyF/vyA/ZOPiviLloaFg4eKj4+WhvKEgID67/Tt+/6DhYaBhP6Hh4aDiZCYpqiqppmgnI+Fh4OJiISMhYWPjZCGi4WTio2LiouGkIqHj4GJj4iDipywlaD58YL3i/ztgIeFiYmGg4aIg4+KhYqJjIiMhoqOjpGRjpCXioCLk4CKmpGKg46PiISGio+Mi4+Pjo2LkZGOiZajlYaAi5GJiomKgImAi42MiIqDhoiHhpyujYeGioyJkpCQjJGHh4mKlIuNkI+KkImOio2PjY6RjI2Wj4mKj42WjoyPjZCNk5GQk5eUlY+blJOIjpGRkpmTl5SQlZqb+e+A+oaHiIDx5cfY6/n8hIaRmJiToqazxsbS19O/rYf9/IqSmouVlp+ioZeZl5qSn52Vjf2MkJHe9ICE/Or7+YKBhYiMhoqKjI2NjpCQh4+Nj5WSkIyOko+UkYqIko+Ak5WTj4yLi4yUkY6PjY2QjY6QjZGSiJWPjYujv7yahZadjfmKjoyOkoCPlY2VkY+QlJGTkoqKiZGMjJCVh4iJjo+HhomFhfGJhoaLjouKg4qJioeJg4GGhoD7rciRk4L5goGGjYX3///t+vT48POdlpSOg42UlouMk5edipCIkY2LlY2SmZaXiZOWkI+JjYqXkpKQlImZkYqNhYHq6YCEgvGBiomQkI2MjICNjouSkI2PkI+Gio6LhP+DhIWLioqHkIuMg4iWlpGTlZCSmImEhImHjo6Ijo6QiIaFk5GGh4aLiIaMh4aEgoaKgIyOlJCJkZOMkJiRk4KPgoqC/YaIjoaOi4yKhIqJipSakJKUmpmUk46IhYSEkYKDiIP4iYiC/YD24d74iIuEiYCplY+zva+vpqWZoYTu9Ovt8O/y7Ozu5Obd4drn+YSEjY2Ti4mC/urI8omAi4qK/JCOi5iTkp24v6aWnJyOh5KUjY2Zk4+ImpORj4qKlZefnJmSjIOAjo6RkY+Mio+RlpWQg4WGk42OiYCSlJmVmJCGiIWBh4iGhYWGhYGGiJOVk4CNkJOVlpWSko2SjI2HhYmM/oDu7Pv39IOB9ICAjIqOj5OKioKLio2QjouKh4qJg4X7hon9goSKgoCOj4yKgIeNjvyGkIyIhZWmopeJiIiOg4iLi4SAgoOEg4SGh4yPkYCChpSAg46KipGRiYiLjo2Nj4qIkYmMk4mJjZWdloyMkoB+iIuKg4SGjIeBf36DhId9foyemomFiYqGioCGe9zR2oSYeXV+esLkhoV+h4mLhX6FeIB+goR8foB9hYR/gn9+goF4fHiAhXx4iX13e39343mDfITj39/f5NXheOTQ6Op7f4CCe319in6DfnN6eYLhzn903Xt5gXl9fX5/4nyEeoB+gXl8gn167e545YODguqCi3x8en5+hYeLfuJ8e3nv5Ofj7/F9f398ffOAgX98gYaIlJSaloqQjoJ9fXmBgH+FgH6Igod+fnyKgISDgoN+g4KAhnh/h4F7f4eZhJLn4njrgvHfe4F/goGAf4F/fYmCfoGBhIKHf4OFgYeGgYeKgYCCiHqCjYWBfIaFgH5/gYaCgIaGg4CAhYWCfoOQin96goaAgYWBeIJ4g4SFf4J7fYB+e4qYgn6AgoOBh4OEgYR9foCAhoKHiIWBhoGDgIWGg4KFg4SLhH9+goKHg4SFg4aCh4aEiouKi4WMhod/hIOGh4+JiIqGiYuO6uF8739/foDd1rzK2OPkd3WAhIB7hIiQnJqhqqyglnnv8IGKkYGIiJGTk4mKjImGj5CLgeWBiIbM33eA8+H18H18f4KHgIWDhYWFiIqJgIiDiI2KiIODhYOGhIR+hYJzh4uHhoGDgYOLiISIh4OHhYaIg4aHfoeCf3yLm5+MfImMge6Ch4OFioCFjYSNhoWHjIiKioKAgIWEhoaLfYGChoh/f4N+f9p/f36DhoOCfoSCgYCEfX2AgHrtjaiGhnjnenp+hH7m6u7c6N7i2dWIhYOCd4GIjIGAhomOgYh+iYKCi4KIjouNf4mMh4aAhIKMiImFiIGPjISHgHzf5Xp9fOJ6g4CGh4KDg4CEhYKEg3+CgoR7gIKFfOl3en2Cg4B9g4CAfYGPj4mLjImMlYF+fYSBh4Z/iYiGgHx9j42Cg36EfX2HgoCAe4CFeIeHiomCi4yBhYuFiHqEeoJ35Xl/iH2GgYKCe4J/foaMhoqMjoqGhoN/e3t6h3l6f3vtfn1343Tcx8XaeHxyd3ONd3GMlImIhIV6hXHP2tPZ29ve3dne2d/W3dfh9X5/h4aJhIN99+a74IB4g4J/6YSAfIeAfoWWmYR9hYeAe4aHgoKNiIaAjoaFhoSFjYuNh4WBgXh3hoeHh4SCgYeFiYiFe39/joeFgHqHh4uFh4B8gH15hX2Af318fYCIhoaEiIuNioiGiYSIg4aCf4CB6Hrm4Ozn4Hh153l4g3+ChIiCgXl/gYOFhYKBfoOBfX3rgIPueXuDe3iEhIGCeX6Cg+t8h4F8e4iVk4t8f32BeX+BgHt5eXp5eX19fICDhnV5eoV0eoR/f4eIgIGDhIOFhIKBiH6Bh38IfoOGj4mAgIWAcHV6enRwb3JzcG9tcGxuamt3fnpzc3R0cW5rcWm5t8JsemVjZ2amxXFwZWxwcnJwdGpuanBzcnFyc3V2cnFva3BtZ21qbW9pZ3NsampvadBpeWttwLzDzc2/xmnPvszIaXBwb2JkZmljamheaGZz17NoY8RnZWxqbGlua79pb2mAbGpqbm5sadPTaL9rcHLRcXxqbGxxa3F3d3DMbW9t39TTzdTZcXJ0cHDbdHVycnV3dHl9gXx2eHpxcG5scXFzeHVzeXN5dHBwem92dHN1b3N3dHdqbnZybm9qdWd7zc1o1HHWx29xcndycnJ1cXF4cm5ydXJ0eXF1eXJ3eHB5d3GAdXZuc312dXF4dXRzcXN1cnJ1dHNtb3RwdmxodXlza3JzcnN5dGlxanZ3eHR0bnFxbmxweG1ydXV1cXZydHJzbm9vbnJyd3t5dXJycnB0dXZxcnB1eXNybnBzcnN2d3Z2cnVydXl5eXx5fHZ2c3p0eHd+fHh4dnV2esrDcdt0bWmAtbairrbAvGRgZ2dhXF1eYGdmZm98enNo29Zue4Bxc3V+foB7eHp4dnl5eXHEbXZ2s8Jpdt3R5eB0cnV4fXd9eHt5eHp9fHN7dnl6eHh0dHZ1dHR1bm90cHp7dnh0dXJ1eHh1e3p3d3l5fHZ5d3F3cnJpbG55dXF3dmzZdnl3eXx9eHx2f3d3eH16fXx1cnN3d3p5eW11dXl6dHh5dHjCcnR2eHt3dnZ5dndzd3BydXFu0m+Jc29pznBwc3hyz87TyNHIx7uwa21sbWdvdHpxcXN2eHJ2b3x0dH5xeHt5fW57f3l6cXZ0fXp7d3hxfoB2e3Rt0dlycnPWb3dyeHeEdYB0dXVwb3Nwc25zcXVuyGZpbnF3cG1xcG9xd4B/eHl7eICUcHF0eHN4dmx9fXpybm+FgXR4cXZscXx0cnZtdHtud3V3eHV4eW5ydHN1bnNvdWzQb3F4c3d0cnVsc3FudXd1eXl9dXNydXJvbWx3b21wbdZqbGjAYreiorBjZ1xdZoBTUF9hW1lWWlVfVqOysbq8wsPJvcbI1MjSz9TkcnJ3d3h2dXHo1rDDb2dvcG/ObmllbGpkZm1nWV1mbWtsdHFuc3t3eXF7dHB3d3h8dHFrbW1wamx4eXR2dHJxd3J3dXRqcXB/enl1a3V3eG5xbG9zcW5tb3BvcW5wb25xdW5ydn94en52dHN5dXZydnZydHDJatDEzM7FaGbPbWxybXF1d3Zybmxvc3Z3c3Rwd3VxbdJxdtZrbXRtanNxcXVsbG9xy2x4b3Jsc3h4dmdubHFrb3BubG9ra2pscW9ubm50ZGtqcmRsdHFxdnlxdHV1cnV0dHJ3bW51b21wcXd0bXBsnX+DfoZ/gn6nfwF+hH+HfgF/hH6PfwV+fn9/foh/AX6Kfwh+fn9+f39/fot/BH5/f3+GfoV/AX6zfwd+fn9+f35+/3+Wfwd+fn9+f39/h36Rf4J+kn8Ifn9/f35+f3+EfsJ/AX6ifwF+kn8Hfn5+f39/foV/iX6tfwZ+fn9/f36WfwF+w38Bfp9/Bn5/f39+f4R+kH+Rfoh/hH6FfwF+4X8Cfn+FfgN/f36WfwR+f39+jX8Bfr5/AgIEAICDjpOSlpuSjZiclpGSjpSlsqOXkZWRi5GNh4yKjI2ChPqIiP37i5SDiY6Dh/yBjo2LjIuUlImMloyPkpSTkZOLgoiKhomD/IaSh4GTgfSFgv+ChvTk8/Xx7+3+6uDh3IayiPWEhoaWhPiKm5COhZOUm577/YGIjpCBiI+NjZqEhYCBgfbq9oKB5Oz495Wd94GGhYmBiI6LiYiB8oGAgoCAiYWA+YCCh4qCh4GE/4ycoKeopKSNioWUlIqPh4KWkoeGiIqPi4+FkJ6Mi4mXoI+WlZGHi4aIjYyHkI76xufo2vr1++6A/IKJioyJgYeHiYqNjYmMi46Lio2Tj42KkpOPi4CPko+HjYSMjoyCg4yDlIyJj5ORkJGRlI6IgoWRn4uN+oCGhYqJjZCPjImIiouHi4eSq6aTj4yMjZKQjJCQkY2KhYuKio2RkIyPlZaPjI6TjouRkJONhYiJk5aPk6ePiIiLk46VlZCJjIuOlI+SlIuNlpmQk5SUkJKA9/j9hf/m54C/2+jf5/aCgoaMkIuRjpacpqi/zc2+xcq6mv2Ino6aqJmcnoDomZ6ZmZ6SlpuKhpKQiPCC/YOD8+nm+oaLhYaEjo6Rk5OSko+Lj5SPlZaWlpSRlJONjpOOlpGTlJCRh4qPjouKj4qMjYyQlYyLmJOSk4+Qp7StoaGYiY2HlZGLk4CNkI2Mi4eHj4aIjY6Kj4yJh4SKiYCFhPz1gIH5hYb/+/qChYeFgoCDh4+PjIiLjIuSgu6Wivr//4L/goGAgvD05uDT6eyHmJuWlZORlY+Ok5majIeLkY6IkI6RjJGQjJWUlIuLj4uSkY+Oj4mOkJGZjpGMlYmJiYeC/4aCh4eLiYCIiYeJjJWTko2Hjo6IiYiG/O3/ipaZmJON7fKejo2Lk5OakpWWlImFiIuMlY6KioKIiY2GhoGAgIeFhIH+goSIhYmFh+3ii56hnpGbsZqJhIeFh4eKho6QjYiFi4mCi4OAh4WBlZ2flI+Oi46OgPqAhvr8iY6Shoj6hfvp2feFiYCvrLG2oJ6c9Nedi4H98u32g/ns4+Lp797e4O+Kj5CNjIiOiIaJhe7q6oDc3+HfgIWAk56grsi3sKCRj42SkY+MhIaHiYyQkouEhv+SoqCdlouHi46Thoz+iY2QkZeLioiTkoaKhIWMkY2MmqCQgoGOjoeQiYmEh4WBhv2CgISOnoCal5iQkJWflI+Pj5CLhfTz5vXx+vj88PeD/v/8iZCTkI6VjouBhYqDioKFg4SDhYODgoaJh4OIgPr8gomUjYOLjoqIg4qfopKFhY2WhoGEhvf3iYmBhIWAiYuIi4eJhI2TkIOJkomFj5WVjJCQlJiNk5KIioaDkZCMk4iMk4mPjIB7hIaAhYyEgouMh4WHfoGRnpKIhIh/e4ODfoF+g4F7eO99e+rif4h2fYF4eOVzgX59fn+Gin18hIOBhoWHhomBdnyAen9663uFfXmHeeZ8eO94fOPW5+nq5eDw2M/S0nmeeuV6e3qHdeJ8iYF9d4KCh47q7Xp/f4B2foN+f4p5eoB3eevf6Hh429/i4YOK4XR7fn94f4aAfoB66Hp6fHt3gH5683x8gIN5gXp57IGPkJSWlZaCgnyJh3+EfniIhYB8f4GEf4R7gY2AgoCIkIKHiYZ/gX58g4V+gn3gttfay+zr8uZ99X2ChIaCe39+gYGGhoKFhIaDgYWKiYaCiIiFgoCHiYV9gnuBg4J6fYR7ioSBhoiGg4eHiYR9dnuDin2D7nh+fIJ+hYiIhYOBgoN/gn+CmJOHhYODhYiIgoaEg4GBfoN/gISIhYSHiomFgoWKhoKHhYmEfH5+homDh5iGfX+BiIWLjYh/goODiIaHiX+AiYuCiIeGhIZ38Orxfu7Z24Cxz93T2uB0dXd9gXp9eH2AhoiZoJ+VnZ+XifB/j4KLmouMi3DRjZGMiY6FiI2Ce4OEfuB58H5/8OLX8H+Ff4B+iIWJi4qJi4eDhoyHi4yKi4uHi4qFhIeAjIeJiYiIf4OIh4aEh4OCg4KEjIKFjoeGh4SCj5aSipCGfYV8ioaBiYCFiIWFg3+Ah4CDhYWDioR+fXx/g3x/ffDue3zvfX719O96foB/fnl8gYqKhoOEhYWNe9yHf+nv9Hrue3p6e+Hn1cy/0tN5jI6Ki4iFioWEioqKgH+DiIV+hIOHg4eEgY2LiIGBhIOJiISEhX+GhoaPhYaEjYOEhH579X14fHyAgoCCg3l/goyJiYSAg4WBfn166tbnfYiKiYWB2+KQgoKAiYiTi42Mi4B7gYaHjoiFgnqEhoeAgX17fIOCgH71fX2Df4B5etTJe4+UkIaNnoZ5eX58gH6BgISEhoB9g4B3gXh0d3tzh42OiIaEhIWDdel1fevtf4GEenrac9rOutBuc4CPiYqOfX19xbCDd3Dj3NrhfOvg1trh6Nrb3OqEh4iEhoCEf3+Df+Ph3HPCzc/OdXVvgouLlqKRjoZ9gX+EhoSEfHx9gYaJiYOBgviKjIyKhHt8goSJfIPvgoaIho2DgYGKi4GEfX2FioaBiop+dXeFhX2EfYB7fXp0fO16fH6EjoCOjYyFh4qRh4SDhISAfOTl1uzo7uvv4ex78vLwgIOGhIWKhIJ7foF6gHt8eX58fH58e3+CgXt/eOrreX6Hg3uDgYB+fICPkYZ6fICDeHV5euXsfn12fnt4gIJ/g31+fIKEgnd+hH16hYmLgYaHiYqCh4V+gH96hoWCin+Ehn6EgoBxdHJpbXZwc3h4dHN2b29zfntwb3ZtanFyb3NtcHBradhsZ8i5bHJjaGxkZ8Bfa2trbm9zeGtpaXFvc3F0dHpxZ2tvaW5pyWhwbmlxbs9uaNZqbMe/zcvMysXOtba9umV9acxsbmpvYMJmbmhlYmptbnTLym1wamlkbXFra3Foa4Bna9LMzWhryM3HwmVwxmVqcm5qcHdybnFt0WpxcXJsb3Fu33Jwc3VtdnBt2XJ4enp8fH5zcWtzc29wbWZydXRtc3R0bnRtb3dudXR3enF0eXlydG9rcXJsa2K5lLG4s9DT29Jz3G9ydHZ0cHFvdHN4enR3cnV3cXZ7enhyeHp4c4B4eXhwcG5vdHRuc3dte3RydXNzb3R3dXBwaG9ucGl12mlybnVvdXl4d3RxcHRzdXFsfHd1c3F2eXl5cnV1cHB0c3Vwb3d4cnZ6d3Vzc3l6enN5cnh0bHFvb3Ftc356cXRyeXR6fXdveHVzdnV3d2xtcXlzd3V1c3Rp2dbfb827voCXsLy4wMRiZmRoaGJkYV9dXV5kZWReZmlwcddxeXR0gnZ1dlyxenx7eHhzcnd1cXN2c85t2HN16tbN4nZ8dHV1fHp3e316fHt4eHp3fXp4fnt4eXl1d3x3fXV4eXh2cHR3eXl5eHdzdnJ3fXR4e3N0eHVxcHFubnRuaXZteXt5foB5eHp6d3N0eHZ5eHp3fndzdHJydXR0duDcc3PgcnXj5eBudHt4dm9teH58enV2eXl/b8Rvbc7T3nDVbW5ucM7Sv7amsbBhd3p2d3FxeXR0eHRvb3JyenhxdnV6dXRzcXp6e3d2eXV9fXd1dnB3dXZ8d3Rze3Z5dXJz3G5qcG5vcoBydWxxcn16enZzcnRyb25u17nCaXN1cnRzwsd7dHRwdXWAeX55dm5sdHl5fn56dHB7e3lwcXBtcHl3dXHecXB0b29lZq+lYnh7fHN0fWxqam1wcnFzc3VzeHdvb3JpcGpmY2xqc3d2dnh0dnVzZdJnbtLRbm1raWe7XKyklaFUWYBmX1tcU1VUjYRgXV2/vcDBbtPOx8vS283T1Nt1dnZvd3RzcHR3c9DNvF2hrrOzY19ZZ3BudnRhYGNha2xzdHJ0bW5xdXp5eHV1duF3bnJsbmhrcXF1bHTUc3d3dHx1cXN6fXV3bW93e3hvcWtlYmh2dm1xbXJubmplbdVucXJ0c4B6e3l2d3h8dHNycnJwbNDQvtbS1tbWytZu293Wb3J2cXJ3dHJucXJscm9vanNwbXNwbHF0dWtxadXQaWxzcWtycHBzcGx2eXFpbHBuY2Npb8/UbGtpcGxrcHNvdW1sa25zbWhvdG9rdHh6c3d5eXhxd3Vwc3FpcnJweG9ycWtzdKB/BX5/f35+h38Bfpl/AX6GfwZ+f39+f3+MfgR/f39+hX8Bfol/gn6OfwV+fn5/f4R+A39/fot/AX6IfwF+iH8Bfq5/iX4Cf366fwF+2X8Efn5+f4l+lH8Bfol/AX6NfwV+f35/f4R+3H8Kfn5/f35/f35+fpF/CH5/f35+fn9+hH+HfrR/AX6Wf4N+hn+CfqF/AX6Hf4J+qH8Ffn9/fn6FfwJ+f4R+iX8Ffn5/f3+EfgF/in6LfwR+fn5/hH6cfwF+jH8BfqJ/AX6Tf4p+BH9+fn6cf4J+ln+Cfq5/AgIEAICKgIiKj46ThYSPiZSeqqOOjYyQkoyRkJOZkImGiIf8ioqGi4qHh4eBg/j1iI2MjJCUlZCPlZWPjZOVkJKIi4mOjIiKiP2Fi5SHi5CEgoOB/u706vnp/PT19/T87fHu7PaKjoCCg4mJgIT6kJ6L+eyCgfXyi5aA+/SPl4eH+OXniICE+oWGjYeRiZCIgJGE+/eGi4iHhouH+vb+g4mHhof9h4eFhIeMioOChIqNpqaYmoaLoJaNkICIloH2iJGckImCjZiPh4iSsYiJ84iYkIuPgoeCgoSG9/X/i/n06+Pp6+T0hIKDhZCL/PyIiIiHiImOko2Oi46SkpOMjouIlI6BjYCOkYiFjo6PjYGFiY2NioWKjpabk4qPkomOh4WMjoyE/4qHiJuMiYiLlIaMioGBlauklo2KjYWLkI2OjIyNiI+KiYqMkoSMiZGOl46Qk4+Qi4iGj42GjJSWjpKFh5adoJSQjZCSiouOjYuQjYqQlZSTlo6SkpSOj4uAgoqF8eni2oDL09Tc7vHyhIWNioiFkZeRmKGup7W4w8/C0b6nrJuenpuSlpGgn5uenpqZm5yZlJqRlo+LjoP4+Pvj6/v7gYaLjYyTkJOWkI+PkZGUkY+RkZGMjYyUj5GSlJmRj5CSjYmNiYiLhoiMjYyLkZiZm5aVlJKLmp+YoJ2RmJiQjoyTjoCMkIuMioqKh4mHh4eIhIH/hYX6iImJhIqNioOHhf2Hhoj8i4iF8/P8gPOEhYqHi4yJjJmKgIL/goaChIWA7fXk0qaGt+KLj5KHiYqRi46bmpCHk5CLh5aTiI2KjJGIjJCPkZKKjo2KjYyQk4+RjoyTi5CPi46Oio+cloLzh4iIhoCKio2OjpKQi4yMiI2NjoqKhvbmgJaUjZibjNv8iZeWk4uSjYyVkPiPjpmHio2MhoSM8IKCgID494GDhYyKlZGap5emoJqYjoeFk4qHmY+WoJmUj4uOhYyPkI6OjYaKkYiG+YH79/iIhpiQp4qIg4b89OeVk4eQn5mWkYqFjvDz7ROFnbOg15uh9umAi//09fDw6+zrhOKA0tz/jomFi5CNj4qIjYqA+u7aie/p7t2D8eTi86XQuJ2QnJmXlo6Oi42Oi4mIi4SLi4OA7qqklJWak4eJh4GIkJGKi4WJkI6dkouKgIuRn5X09IuPo5KHg4aGjoaKjoyFi5WPhIaDjI6Oj5ObmJSSkZOQjpSIhP75+YKA+ff5gftp9fSFhYH6iZKPj4uLjomJj4aAjo+HhoeJhoiLioyHg4qOiIKHhoWJjv2JjoWDk5aRjo+HiomUkoeBgYH9hoWGiIWGhYmMiIePj4aNkoyHiYiQjo2Tk5GTk5CPkZWOiIL7iouPiISBjJScgH56f4CDgoR3eIF/iIuWkYCAgIKEgYWChIuEf3t/e+eBgH5/gnx8eXh55ON9gH59g4WHhIKJiIGBhYiEiH2AgYR9en9/7nt/in6Dh3t8fXrx4+TY7t/t5uXq6u/f4tvd5X+Cd3t7gHx0euKAjXvl13V14uF8hnXr4oKJfHvm2dR9gHrrfX6Be4Z9gnx0gHri436Dfn9+f3zs6PN8hX98ffB9fn17gISCf3x+goKVkoiKeYCUi4GEeYCJeOt/hIyCgXmBiYN9fYGZen3efYyGgod5gHx6fn7o6u+B6OXe1tzf2Ox8fX1+h4Tz84KBf4KBf4SIg4aDhYeHioOIhH+Gg3uEgIOJfnyGhYKBe32AhYeFfoKFjJCHf4GGfoN7eX6Cfnz0gXx9jIKCgIGIfISBe3uIlpCIg4GEe4KGhYKBg4F+hoN/gIGKfYSCiISLhIWKhoWCf3+GhYCCh4eAhn1+iI2OiIWCh4aBg4eEg4WAfoOJh4iLg4WDiYSIgHh7hIHm3tvTgMXGydPg3eB3eoF9eXaDgXqCiZGGkZKYoZainY6ViY+OiIKEf42PjI+NioyNjouFjIOGgX6Geunp9Nvk9fF8f4SHhIqIioyIh4mHh4uJhoiJiYWGhY2FhYaJjIiGh4mDgoWDg4WAgYWEgICGiYmLhoaGhH6Iin2GioWLi4WChI2DgICGgYSAf4GCg4GBhYiAevOBgvCBgoN8hIiAe4B96oB+g+uEhYHm5+9454CCh4GDhoSDj4B3ffZ9fnl9gHvg5dfClnqkz4KGhXuAg4iCg42NgnyJiYN+iYd/hoKFh36DhYOFhoGFg3+CgIKHhYqCgoWAhYiDh4WCho+LfOZ7fX98gIB7hYeDhoiAg4OBg4OAfYB85NJzhYR8g4Z+w+B8hYaIgoqDf4+H4YiHjoKChoeCfYXke3x7fO3vfXt/gnuBfYqTgo6JgoJ4dnN9eXeHgYmTh4mCgIN8gIOChYKDen+Hf3zke+ri43t7iYOVfX56euvm2oeFe4KLhoN9eHR3zNHKgHCBj36sf4TOzXF949zk3uHg4+HY3N3f0Nj3h4N+g4eFhn9+gIB06+XPfd7c39F31tLR3Y6qkYJ/iImIhoKFg4SDgH6BgHuCgHx85peMf4SLhX2BgHt9h4mChH2Dh4aTiYOEfYOFkITn531/iYB7e319g3uAhIN8gYqGen18g4eEgISGjIqIh4SFhYKFfHbp6ep8ee3q6Xjr6ep+fXrtfoOChX6BhIKDhn96hIR+fX+BfX+CgoKAe4KFfnt+fH2AhuuAhn18iISEgYJ9gn2GhHp1eXnse3l7fn1/fX6Cfn6GhnuDhYJ9e3qCg4CGh4WKiYaEhoaCf3zuf4KFfXx5hYqOgG5wcXBycXBjaW1tdXJ5d21oa29xcnJtb3dycGhuasdvcG9qcmptamZmyMpsbGxrcHFwb2pzcmxscnhzd2xtc3NuanFx3WpqdG91d21vb27Yysy918nOycjP0s7HxL3FwWlsaXBrcGVhaLtjcGXIuGNgxcZlcGfQyW1zbGnGyL1ugGvSbG1qaXVvcGVeZmrKx21ybnBwbm3V2dxtdm9rbdlvb3FvdHd1eHJzc3F7d3R1Z216enBubHF0a9Vwb3JydG5tcnFtbnF4ZW3GbXh3dHZtcnFucGzM1tlwy8rGuLzAwNtvdHJvdnPa23Rzb3RzcHZ5dHdzdnR1eHR5dW5ydHB2gHV8c255enJzcXJydnp4cnJzeXx1b251cHhsaWttaG7UcnBwenJwcHF2bHRyc293enV4dHN4cHB0dnJ0c3FsdHRrb3J6bnJzeHFzcHV4dXJ0cHJ4d3JydXJucm1pcHJ1dXVxdnd1dnl1cnVxa3B2dXV2c3JyenZ6cGtudnHLwMO/TrKwuL/KwcdlanJqaGNqZmBjZmlcYV5cZF9mbWt1b3Z3cW1vaHV3eHh4dXZ2d3p0fXBubHF4a9HO4dTZ5txxcnd4dn18e3p3e3t+e3x6d4R5B3t6e3R0d3iEd4B6dXR4d3d2c3R3eXN1eHV0d3R2dXFucm1dZnBwdXZyb3d/dXF3dnd1cXF4dnZ0foB2b+V4eeB2d3dweX90cXdy0XZ2d9V3enLU195x4Xp8e3h4e3p1dWlnceFycm9zdnHR0cOthWqKs3F3cWpwdHtzdXZ2bWt5enFweHhxenR4d4BvdHV1eXl2dHRxdXFydHN6b3F2c3d2dXt4cXN+dGvNbHJxcHJrdnl0dXtydXJxdXRtbHJuy7lha2pma2tspbtqcm12cnpyc390wHZ5e3J1d3V0b3fRcW9tbc/acG9xcGZlXm1yYnBsY2JaW1lhYGFranR6bHBrb3JtbHVzd3FzbIBvd29vy3DYw8JnaHFrd2dsaWvSzsFxbmhscmtsaGNeWZ+ln1VdZFV9XWCepV1qw8HJys3QzNLIz9HVx8vjd3RydXd4d25ra3Bj09C6a8DDyb9jsq+yum55YV9mbHNycHB1dHR1cnF1dHB1cnFz0X1raGx1c3Byc3Btd3pzd3B2eIB2f3l2d3NzcHZuz89paWhnbGxxbXFpcHdzbXB3d2xvb3J3dXNydnV3dHFycW5wamjK0dBtbNTZ0mnQ0c9scG7Ubm9xc29vcnN0dG9scnJtbnNxbW50c3Fxb3V1b21ubHBwd9Bxc29udm5wb3JtcmxwcmtobGzRamhpb3F0bmxybyNucHJmbnBybmtocnFwd3V2enh2dHNyb3Fv2G9wcmxraXZ2d55/AX6Kf4J+mX8Bfop/kX6Jfw9+f39/fn5/f35+f39/fn6EfwZ+fn5/f36Lf4J+h3+DfoV/AX6afwF+j38Bfot/BH5+fn+IfoZ/gn62fwF+3H+Lfq9/h37SfwR+f39+in8Nfn9/f35/f39+fn5/fox/AX6Gf4h+tn8BfpV/gn6Hf4J+in8Bfop/AX6Ef4J+qX8Ffn9+fn6Jf4N+i3+DfoR/B35/f35+f3+Pfox/BH5+fn+EfgF/hH6XfwF+nH+CfqR/EH5+fn9/fn5+f35+fn9/f36ifwF+kn8BfqN/AX6JfwICBACAipWTjoz+g4+QhoyVpJCNjoqRipaIjomIhYuLi4SFipCIh4OBgYWIhIWRhYmJ+IyTlZORkoWRjZSVjpeQk4uIj4iKj4mKlpKHjI+Ng4LvgPr7/PCA9uz07/2A/fyC+YaZ+Ojw8/yCiY2bo4aLgYmD+4CB8YyC/eSPgvqGifSEgPyA8oCFhYmLiIqCiomajIKEg42Miv788f/8iYKGg4CDiY2GgIWJiIr/hYmdnpaSkoqJhob8hIuKnoyJi4uZi4mTiYmL5py0oISP+O6IjIGChPDv5dv6hY2UiIeGhYeBhOr08IH9hYuIh4GEh4qKiYqOhYmMj42Mh4WPkI2NlpicnJ2Am42FjIaSh4SKiIyHg4qLkYuOjouUk4OHjoGIg/aBjZSMi5GNiYWVkJCDgoOIoKyZmZiJhY+Mj4mQloiMhYeJjpOJjZOPiYaOk4yVloqBiYSTmoiMiIiNioOQmpGQiJquqpGehIeHkI6NiIuPkJSSkI+TlpeSloz3iYOLhvnk7diAwMnP2tvn8ID8g4SSkJWOj5mZqZ2coKirtLy/t7y9sru7v7Gxr6SZpqCfmo+ZoKSdnJ2Vi/T0gIKEg+/n9fuBhoWIjpCNk5OVj5OSlpWOmI2Qj5KPko2Lk5ORjJOXk4uJkYiIiYyGh4OEiIiJhI2ZipOTkvmCk5OZkpSRkIuKi5SAjYiKjISFhYKCh4KA+4OChPuOkY+SkpGOkIiJjIqRjYyTj4mG+PWDj5WMi5CCgo+Gi56Yh/2HgoGHg4GA+Pbz5u6ChI2WkJmMjJKVk5SYj46Lj5GLkY2SkIaHi4+MjYqTkpCPjI+HnY2Tj4aPkpGSl5KUlpaPhI2Wk5KbnP2BgpCAkIeUjoeGjpWRjY+OkJGPlYaEhfKEjJOPhODyj4CIiI6OlZaWk5WLk5WRjY6Tl5Sem5+Yl52WipmVl5uhoK2vsKeelZeOioOci4qMgISPjo6loKGsj4eKjZGKj5SRjIiCjYuDhIL+//H19oqurpP79+SFiIqRl5yamaGim5eRjpGAlIeMlpWJ0fCNiIOB8/f++vSA8uXr39jxg4eQj4+MkYmVl5SPjouIhfuA6/78h5KLgv6Ewq2rm5KdlJKWmZSNjIOIh5CEhISKgICXrJ+Mho+UkZCRjZCMh4uOi42I/o6JioeDi5T++u727vWIiIKD/oiXgIyOhoyCg4iFh5CLio2AkJGXkJCXkY6NiIiAg4SFgf+EhYT9hYGE7fuJ//+EkpGLk4eLiJCQlJKJj4yEiIuIjIuHjI2Jg4OFiIGIgoWNhIL7ipOTiYWLiY2KgoWC/oSHhoH7+4mJ/YH/hoSNj4+EgYifloaLhZCFjI+Ph4yQi4qJh4aIgYaIgYONh4uIhf+Af4iEf4Ptd4KEeoKGk4SBhH+Ce4d9hH6AfH5/g317fIJ9gHl3d3p8eHqFeH184ICGhIWDg3iDf4qGgYyCh4B/hn5/hH+AioZ+goGCennjeOvw7eJ47N7j4fF78vV9732L4dbm5O97gICKkHd+cnt48Hp53Xp05tOEd+J7f+J4demA5HZ7f4CAf4B4gH6LfnZ7fIOEgu7x5fbvgHt+e3p9goV+fH2Bf4LwfX6PkIaHiIGAfX3xfIKBkIB+goKKfn+If3+Bz4adi3eD6uOChHp6feXo4dnyfYWKf39/gIR8fuPr6H3xf4KBgnx/gYWCf4SHfoGDhIOEf36Hh4KEiomKjo6AjoJ9gHyHf36Af4KAfIF/hH+FhYCHh3x9gnh+eel3goiEgIWBf3uHhIZ6e3x+j5eJiot9fIZ/hoCFiX2Ce3uChYaBhYqHgX2CiYSHhoB5f3qFjH6DgH+FgXyHj4eHf4iVk4GOe4CEh4SDgYSDhIiIhYKIiIuHi4PwgHyEf+vd49KAvMbI0tHa5njqd3eDgIR8eIeDjoKBgoqKkpSZlJaZk5mXm5OTlI6Ek5CPin+JjJGNjo6JgODleXp8fu3j7vR9goCDhoeFiouNh4qLjYqFjoKFhYiEiYaCiYqGgouLh4CAiIOEhIeBhHx9gICBfIOJfYSHhuJ2hIaLiYiDiIOBgouAhn+EhX18fnx7gXt+8nx8f+6EiISIiIiFh4GBgn+HgYOJgHx54NhwfYaEhYh9fYZ/gI2JePCAe3mAe3t88/Hp3uJ6fICJg4uBg4eLiYWKgoOBhoiChoGDgoB8f4OAhICJiYuGgIV+i4GHhH6Eh4eIiIWJjomHeYWPiIaPlOt8eoSAh36Kgn18g4mGg4OChIOCiXx6e+N5f4B+ds/ggXeAgISDiIeGgoZ9hoWGhIWLj4uUkJiPipGLgY6Ki5CNjY6Rk4qHf4R/eXqId3x/c3Z9gICRkI+Ygnx+gYN9goWDgHt4hIF8fHjt8Nre5H2XmIbl6Nh8fH2Bh4qIh4qKg4N+e32AgHV6fn50tdp+end44+nv7+h66t/m3dLsf4GHhoWDin+JioWChIJ/fuZ44fPxe4J9ee56oY6RiISLhIWIi4mCgXyBgYZ9fn6BenmKlIl+fISIh4aIhYiDf4SHhIN/84N/gIB9gIPi5drk3OF6fXh48H6LeoKEfoN7e39+gIiCgIKAg4OJhIOJhIGAe3p3e359efB6fnzxfnt74/WC7+97hYWAhnx/e4WGiIh/hYJ8f4F+hYKAhYJ/e3l7f3V/fH2CfX7ug4eDfnx+foJ/dnp573x8enry6X597nnwf3+EhYV8eH+OhnuAeoR7goaCfIOHg4KBfHt9dnx+eH2Ee3+BfvCAanBua3PJZW9va3Bzd29yc25rZ3JrbGlsbWxscXBsaHFsb2ZnaGlnY2ZvZ2hpvmpycHJub2p0a3dzb3lxcWxvdG1scHBxc3Nucm1ua2rRbNPY08pq2sfMxdRw1txs121zt77TytVrbWhtcWFnXGhp2G9uwmRhxrdvZMFna8dnZM1jymVobm9vb3Fqb250bGZtb3J0cNLW0eLYcnFtaWpzdHdxcXB0cnXfcnB3d3F3dnFxb23WcHN1e3BucnJ0a3F2cm5usm16bmNszc12dW5wctPY0dbbbnd1cHFxdXhvb8zY1nLfhHKAcXV0d3VydndxcnN0c3Zycnh2cXR4dnR5d3l0cnBweHJxcXNzdW90cXVtcndxdHVvb3dsbmnVbHN4cnBwb3BtdHR3b3NxcnZ6dXN1cW91cHdzc3Vwc25qcXV0cXZ5enNucHV0cnFxb3Frb3hyeHNvdnNueXtzdW5sb3RseG1yd3eAdnZzeHRyeXV1dHp3e3Z3c9RxcXluz8nPxLG3t8LAy9VqzmhkcG5ramNvZ21kYF5fXWRfZWJgZGdqaWxqbWtxanl7dnRzd3N3fXx3dG7AxWppbXLf2t/jcnd3eHl6eH19fnt5eHt8dn1ydnZ4d315dHp4dnJ7endwbnh2eXd8dneAcHBycnNuc3prb3NzwWZxdHh8eHN6eHV0fnpyeHh2cXRyc3ZweN9vb3Pbdnh2fHl3dHp0cnVxeXJ4eXFoZLqxWWlzd3p4cnN1bmxzaV/WdXFvdnBxc+Ld18nLbm1sdXN3bnV4e3hwdG1zdHR4dXZtbXBwbGxzc3d0eXqBenV3bXGAb3Vycnh6eHh3dnt7dnZrdn54dXZ5y29tdHpyeXJwbnV4d3J1c3VwcHVsamzPZ2hjZmOzxW1nb3Jyb3BwcGlwZnJzdnN2e357fHiCfHZ5d3F8eHh7b25oZ2tnZ2RtZ2JlaltlaV9hZGtvdG9ud2psb3JxbHByc3BsaHVzb29s1NKAvLjAZnV4cMXTymttbWtzcHNubGpkZ2ZkZGZgY2FfWZe8aWdmas3N0tjXc9/U2dHE3HVxd3N1cnlveXdzbnRzbnPIac3b1mlqamzXaXRobWxxcm1xcXZ4cXBxdnN5cXJzcW5vc3JubmxydXN1end5cm92e3Zxb+F1cW5zcGxovcqAu8rJyWdpaWnWcXdtdHZtcW5sbnBxdW9vcG5sd3JydnFxbGdpaG5wbmvVbXNu2nFwbtDgdNLWb3JxbXBpbWtxdnZ2b3Rxbm9wcHZ0cXdxbm9qa3BlcHNwcXBy1XByb25ybW1ycGZtbNZra2pt2NBta89r2HRydnJzb2ltd3Btb2sZc21xcm5pbXRxcHFrZm5qb29ob3BrbXJw0oV/AX6nfwF+n38Cfn+EfgF/hX4Hf35+f35/f4V+in8Sfn9/fn9/fn5/f35/f35/f35+kn+Ffo5/AX6LfwF+j38BfoV/gn6Ff4V+in8Ffn5+f365fwF+2n8BfoR/i34Cf36rf4J+hH+EfrV/AX6YfwV+f39/fpN/gn6OfwF+h3+Ffrt/AX6WfwF+hX+Cfsp/hX6Ef4N+lX+CfoR/hX4Bf4Z+kH8Ffn9+fn6EfwF+q38Bfod/hn6EfwF+oH8Nfn9/f35/f39+fn9+fqR/AX6MfwF+hH8Hfn5/f35/fqV/AX4CAgQAgISCiZSV/IH2gISVj42Kjo6LkI6NiYuNjImLhoaBioWHjYiQlI6KgfWAiI6G+YeOkpiSk5eSiYqSi4WCjoqJhI+MgoWRmpGJkYaJjYT+hfP8gPuBgPj0gIP9gfmBjIiN/9nf6u6BgPyLqIyBoZ2JnI+E/Pr19uz7gZmMgfiRhPj0gIORkIuTlY+QkIqTnJGMiYaDiYyMiImIgvOAg4SChfiIjIyPiISEi5WdloaPkJCMj4mIj4SJj6GFh5ucnJ+dpov2gaKR9OPb2/X+h4aChoLr/YKLhYmUhP6Ch/j6hIP4hf6FiI2Bi4OKjI6Mi4eFiouMiIeOhYmKjI+OipmTjpuZgJiXmIqIioSLjY2JhYmPkIeHioqNiIiOgoyK/oCAiYqUjJGSjo2KipaDhYiInrKfjY2Oko+Jho2Ik4yEiYmRjoyVg4eOlI6LiIGFi5mEhY2PlYiEhIuOio2LipSOkI6RjKOqqZqHhoiGlI6FjZKLjI+SipqXk4f7+YGLi4aE/ufXgNHF0djY3+rw8PL/h4ONkpCQlJ6Vk5qYp5qomJuhppysrbKzsby+wcHAraaeop+jtaafnJCVjI2HkIeJ/+nv/PqFjIeSkIiTkpSVlJmTl5CQjoGSkJCUkZSPkpCQiYuPmJeNkYyLh4OJhoD3gYmE+4b93uKDk56Vmo2PhY6LjJCNgISEhIaGjIyJhfaEhIaPkIyOiI6QkIyFiY2Sk46K+oyMk5iRlpGRkN/jpY+InpOnqZ2HgIWEhIeFiYKBhILz/42Sh4WNlZ6SiY34ipiRjouPkJGLiI2Ok4mJhY2SjYuEjJKPjZOQi4qLjYySipOUlYmVj4uNkIuHjoqIk5GOj4mFgIL/jISKjIiJjo2TkIqUkIWJivzrguaKhvfqhYOLj5WQgJSNiI2Iio6DiYeMjpmLmZSZk5qUkZaXoKG4vqiclKGkmpaYk4mNl4uKgoWPhfX7iauTpKyTh4yKko6Oi4yPh4yFh4KMg+7k+vTk+oeqsJbp3IiTjpinqauooJyWnZ6cgJWJjY2EnpOKgoSIgICA//Hr5uro3eWGjIaPjIaTkY6QkqKamIuNi4mH+YCAhpqMiouNssaikZ6YlZCMi4+PmI+LiIb4g4qPhouBnZSTj5STjJCRi5CPhYiBhoGMjIKOgYaIi4iBg4mA7/L1goD4/fuOlpOekYaIi4mEhIaGi5OSgJCOkIuLlI2Hk4mMiYOKhIWAhYCCg4X7gP3/h42NjI6LkpeTi4yMjIeBh5GIhoSJjZCJh4qMg/b9hIiNjoOFhYWEgoeKhYaFhpGIiIiFgoeCgo2EhYWHg4KEi4qIgv6Ej4qQk5+ZjZGYioqGhYOLkYiIioeChouMl5aNhYuCjoeCgH58fISG63vnd3mHg4N/hIJ9hIF/e4CCgH58eXx4gHp8g32DiIGCdeJ1fIB6532Eh42Gh4iGfn2Ef3t5f3+BeIaBeHqEjoV9gnqAgnrvd+Lxee94dujmd3vue+98g4GC7c7W4uF6eel/mIF2j4t6jYF88uvk6d7seYt+d+KBduTlgHuGhYKHhoKFg3qDioWBfnt7gYWFf4GBeul6fHx6feeAgYOFgH59gomPiHyEhYaChoF/hnx+g5J6fY6Mio6MkX7sd4t93dDN0Ov1gH57f33m8XyEf4OJffh7ge3ugHzuf/R9goV7g3yAgYSFhYB9goSDf3+HfoSEg4KCgI+Igo2LgImJjIB/gXuDhoODfoKGhn5/gYCCfH+EeIKC8Xl8g4GJgoWEgYKAgId5e4F/kJuRg4GChYR/e4N+hoJ7gIKHg4CGe3+DhoN/fHh9gIx9fYSHjYJ8fIKFgYR/f4WChIWIfoqQkox+gX99iIZ8gomBhoWIgI6LjX7w73mDg4F/9OHUgM67yNLO0eHh3t/qenWAgoCAg4qAfISDkIONgYSHioGMi46RjZaZmp6fk4+HjIyNnZCLi4KJgYWBh4KD9OTq9fSAiICMiYGMh4yOi46HjYiHhXiJiIeKh4qGhoWFgICEjYmEiYWGg32BgXvpe4R+633x1c14h46Ji4ODe4WEhYeEgHp9fX5+hIOCf+9/fXuFiYGEfoSHh4J8goWLiIWC5IOCi42GioOGhMW/kYB4joCUlIh0c3t8fX5/gX18gH7o94WHfnt/iZCEfoLjgI2Hh4aHiYmDgYWFiX9/fIKIhYN8hIqHhIqHhH+Bg4GIe4eLin+LiYeGhoF9hoJ/iomFh4B9gHnng36Dg4CAgn+DhIKHg3t6fuvZd8d+ferbfXt/f4WDc4F5eH13eH11eXl6gI5/jImPhomFhYeIjIybmYaBgI2Oh4SFhn5+h3+AeHx9dNvhd5B+kZWCe4OAhIOEg4OEe4F6fHqDetvT59/S43eXnYjV136EgIqTkZGQioeCh4qJgIJ7f3x3jIN9eXx9d3l69OPh3ODi2eGBhYCHhH6HhYSEhpKJiYGDhIGB7nh4fot+fYCBm6SLgYqFhoKBfoOFjYSCgH/tfoODfYN5jIOCgIaFgYmJg4WEfn97fHmCgnqEeHx9gHx1eH533+bneHjr6uiEiYeQhX5/g4B9fX59g4eEgIOChIGAhYB6gnqAf3qBe355fXZ6fH3te/L4goODg4eAh4uGfoKCgX53fod+fn2Dg4eBfoKDe+bufoCChXt7fHx+fH+Benx8fYZ/f398eH56eYF5fX1/fH18g4F/eu97goCGh5CKgYaNf316enh+hn2Ag3x2e3+Bi4uEfYJ3hYB7gHJsa3Fwz2zPZmhwbnBqcnNrcHBvaGttb2toZ2xrbmdpbmhqcG1xYMJkamtlyGtydXlycnRzbGtxbm1tcXB1bHRwaGxyeXJsbmZub2rUZ8nbbtpoZs3Oa23Zb9Vub2xrzrzEzsVpasJoenBmdXJuf3Fv2dXMzsLOaXRuZ79oZcfNgG1zbm90cXBycmlucHJubGxvcHV2cXR0btRubWxuc9NycHNzdHFxcnZ5dmx0c3Vxc3Fxem5tb3dscXpxcXFzdm/ZZ2xjtbK1utrndXJxc27Q3G92c3d2buJydNTVc2/adeFyd3Zvd3BxdHN1eXRwc3ZycnJ1cXZ3dnBxcXl4cHh1gHN2eG5wcmx1dnVzcXZ3dm5zcnJyaWp1bnJ02GtydnJzcnVxbHJxcHNrbHNzeXh8c2xtcnJubHRvcnFtcHJzc29xa29xcHNvbGpucHRtb3N3enVub3N0b3VvcHBucXZ3bGtwbnVud3FzeXVwcnpydXV6c3Z5f3DZ2mpydXNy2szFH8S0ucK/wMzKxsPPa2VucG1paW1pZWdncWVpYmReYlyEYoBdZWdpcXZxb2pwcHB+dHJ0cHdxdXZ5d3fh1t7k5HZ7cn99cX13gX96fHV8fHd1a3p6enh2eXN1dXVvcHF3eHZ5eHt3cHR0cdRvdXHUcNzGt2p2dXV5dHNwdHZ4d3hxcnJxcHR3d3PgdW9ud3t2d3N2d3dzbXF2gHl2cst2dHx7doB2c3Zvppp1cWp2anl2Z1ddZ25ycXN2dXV3dNXjdHdwa3B4e25tdstxenZ5enl7enVwdnZ3cXFtcHV1c3R2e3lzeXl2cHF0c3dvdnp5dXt3dnh2cm52cW55enh3cWxw2XRxc3NvcHFsb3JzdW9oaG/Ux2Sfa3DeyW1tbGtrcGJnYYBiZmFdZF9hYmlrem13dXxvcG5xdHJwbXRqXF5kbHFvbW5xb2tubW5pbGNftrxgbmZ1bmtuc3JubnJydHZsb25qbXZwxb3IvbPCYnl+bbrZcG5tdnh0c3BtbGhrb29qaW9oZnJsa2ltbWtucN/N0c/V1cvXd3RzdXdzd3BxcHN7coB1cHF2c3bYaWxzdWpncHB5d2pscGpvcHFtcnR8dXV1c9lzdnJvdGtybGtsc3JweHRwcXFvcW1uanFzbHRqa2ptamhqb2vIzs9pa9XWzHN2dnlzb290b25tb21xdHBubXBxcmtsZ21qb29tc21wbm9rbm9v3XHf5nRwdHZ4bXV4c11qcnJwcGhucmxwb3ZzeXNxdnVs0dJxcnN3cW5ubXBxcHFta2xsc3BwcW1qcGlob2lwcnFvbm1zcXJs025ucHZzd3NtdHZtbWtnZ2pyb3F0bGdvbnB5eHhub2d0cW+FfwN+f36ffwF+hH8Bfp9/D35/fn5/fn9/fn5/f35/foR/hX4Df39+in+GfoR/BX5/f35+mH8BfoV/AX6hfwR+f39/hn6Ff4J+hn8Kfn9/fn5/f35/frl/AX7bf4J+hX+OfrB/hX6qfwl+f39/fn9+fn6WfwF+k38Bfol/gn6Uf4J+in8Bfrd/AX6Qfwh+fn9+f39+frR/gn6Wf4Z+hH+Cfpx/iH6TfwF+mX8BfqR/CH5+fn9/fn5+pn8Efn9+fpx/gn6lfwF+on8CAgQAgIeIg/zxh4uJgomUko+PhomMkoySjJCOjomPjJWMjYmJjIqM94iJjIiA7PuMgYCIio2elIGChYr+gIeGiImNjYiSkoyLh4uUj4b6gIGEgv2DhfL++/SChIH9hYaJmI+A6+zkh4iF/YyeoZqaj4uKjo+bnJ2PiouNjuWEgOzv+f2AgIiFioqGh42ajYeLlpGGg4CBgYiI/oWEje6DhYqRkZONj4aGiYWEhJeSi4yKjIyFhoiLjI2RlpersJ+PloKA9OSFoovr5or2+POAiYKCg/Pr54H4goSDhIL9gIiKhoeGiIyKkI2RiIWRjIOIhoeJiIWAhoSCiZCVk4qNl5STlZWUgJqdlpufoZOPlY+Li4mO/4SOk4b3/4GMgviFg/2J8ICDiY6VlpCVjIqEjJCHl6WHjI+NiouGif+Dj4iOi4qKh4uOivmNkoOWioiDhYGJhomGh4iLjI6NipKTlZWRkIuLiI6jrKuTkIeCipOLlI+PjZSMkJOcnIz5//6MhoSLjIn5gODTzdLY1Nnj7fPrgfaFhoeLi5KYmZiZlZCWmZ2pmZ6cnaihnKmtqK6xuLa2qKynpqKrnoWLmpONjo+Oi5D05PKEi4eIjYiOkYmPl5ORlJSRk5GSkJWQio+MjIuSlJqXk5OTl5uWlY+LhYWMloSHj5WIkZyYlZOTi46PkoiMjIeMgI2UlpKXkpORkY+JlI2Nk46Qi42HjZaVkoWIiZWTkZGSkY+TlpSXl5iNro6hn6KojIqJg4OBhYaH44eNiYeBiI6SkJGUko6TkpGYk46RjI7/iZSWj4uLi5CPjImOiZCEhpGNjIuRkY+UjpKOhomMi4OMh4iJjYyPjYyOlJWPmZSVgIb+h4qIjIiLjpGMj42Jio2F+/782tmAhICGi5SKkY+TlJWYlJSPkJiRhYyOiIaFp5aVmpyan6WwuLyyp6COm56Uk5iPk5SUjpKMg4uQioKFmYiNkq6Wg46Fk5SRi4qPjY2Ij4Px+oj/guHi7Pj3jLaxgvCNi5mnpZ+koaWsnIyOgIiRj42YlpSGgYmLgoL58e/26Nze94mGhYiMh4yKiI6UnZuRj5SUko2Lg+7ngIKelJalvK2hpKSRlZiRkZWNjoqIi4eIgIKFgomsn4qRnI+XkZWKhIKJgIuMh4GAgoaPkIyDhYaE+4WDhYOCiIH48ub+g4KFkIOMjYuLlpWI/4aMgJOQk5eVkYePko2PkpKRj4mBgYaNioWGh4uEgJCPlo6Lko+PkpGTl5WTkJKQko2HiI6HiY+FhoWOg4KBiJGFhIiNkf+AhYGHhf+NioWAho2Ni42QkoKFioSNkImIhoOBkZORiYONjaWOkJKLioWLhYeRj4aEh4eEgo6Tk4WAhYiCgIB9d+7feoCAeoGJhoOCfX+BiIOIg4aDg36DfouAf35+gX9+43t9fXt13euCenqBgoSQhXZ5foHsd319fX6Dg32Fhn+BfYOHhHzreHp7du16funy6+p7e3rvfnt+i3903ODXgX998IKNkI6Pg359gICKio2Cf3+AgdN8ed7i7Ox4gH17fYF9e4OLgHyBiIN+end6e4SD831+hON9fYKHhYiChXx8gX99e4qGg4OCg4F+gH+AgoOFiIeWm45/hXh14dd8k3/e2X/r6+h4gnx9fOjl4nrse399fX7xen+Bf4F9f4OBhYGDf32Hg32BgICCgX55gH19goeJh4KCioiGiIeIOo2Pi5CRkYmEjIWDgoCD8HuEiH3q73mBeut9fPCD6Hl7goaJiYOJgX55g4Z8hYx7gYOBg4F6f+l6gn2EgYB+gYB/6YOGeYqBgnx8d4B/goF/f4GChoSCiYeJiomIgoJ9gY2QkIWFfXuAhn+Bg4OBiYCDho+Ng+v09IWBgISDg/bhzsrJ0M7Q1+be1Xjfenp9f36ChoaGhYF+goWIlIeJhYOQiYONkYyNjpSWlo6ZkZOMlYt4fpCLhYSIh4CI64De5X6FgYSGf4iLgoaJh4aKioiJh4mGiYaChoKBgYeJj4+Mi4mKjYyMiIV+gIeOfn+Fin6HjoiIiIaAgoGHfYCBfoKEiouGjomIhYeGfouFhIyGh4KEfoGLiol8foCJiYSGh4SFiYqJi4qKgJp8jI2KjnR3eXh7e35+fNB/hoKBfIB+g4eHiIeDgIWJh4yIhIaFgu2CiYyJg4GAhoaFgIeAh35/iISDgIaIhYmChIJ8gYB/eoWDgX6DhIaBhISLjISQioyB7X6BgYR/hIOGgYN/en9/eerw7cbKeHp4fYKGfoKDhoSEhYCEfn6HgHN5fXdzdJKDg4SFh46TlpeZkYaGfICFiYOChH6GhIaAhIN8gIV+eXiDeXx8l4V3hX2IjIZ8foSGhH6IfuTqfup40Nbb5ON+oZhz1oB/ipGPi4+NjJSJfYF7g4GAh4WDenZ9gHx77+nl7uLY2/WDgX1+gn+Df4CFiYyNg4WHiomEgnzi3Hp6kIaJkZ2UjpKRgoiKhIGGfoCDfn+DfH96e357gJiLfIaMf4yHiYB5eYF6gYJ9enp6fYSCf3h8fHvpe3t7eXh/e+7q3Pd9e36HeoCEgoaLiX/vfH+Dg4aKh4N8gIKAgoeGhYOBd3p9hoN8fX6DfXqIiIyEgYaCgoeIhouIhoeHhIaFgX+EfoCHfn59hHp9eH+Fe0J/gYWH8np9eX998398enZ7gYKAgYWGeXuBfIaFfX+Ce3iFhYF9fIOClIGEhoB+eH99fIiGfHl+f3t5g4iIgHl/f3mAcmpl18xpcG9rbHNwc21sbnB0cHFvdHFvbHJtdmxsbWpxa2nCZ2trbWXIzm5panFucXhzaGxxddNqa2trb3Nza3B0cnBsc3N0bNFpbGpnzWpu2NnU1HBsa9huaWtxaWDEzsV0bW/Vbm93d3lva2xua3RzdXNua21tuGxqyM3T0GqAbWlrdnNqcXdubHBxcm5naGxudnXVbHF0z29ucHZzdXJ0bW5zcnBtdHR1cXJxcnB0cGtycnBxbXd7eGptaWbHwmxxZsjFb87R1G50cXFs0NXQbtlwcW5tcNdwcXByc2xuc3F1cnR0cXZzb3JzcXN0c21zb250eHl2dnJ1dnN1dHaAdnp4fXp6dXN5dXR0cHXZbXh4cdDTbW9u1G9w2XfUbmxyd3d2cndvcG5ydGpsbmpwbm9zcWp00m5wbW1ucW5wcm9vzm9zaHRxcW9vanBydnZwbnRxdnZzenV0dXl3c3FvbXJrdG1xbGttcm1qb3JydXJ1dHh2cNbe33l1c3Rxc96A0sW7s8DDwsTOxsFrwmxobG5pbW5ub2ppZmhrbXJscWZjb2tkaGxlZGNna3BueHR4c3ZuY2h8enVzdXZwdtnU1XJ5dXh4b3l+dHZ0d3R2enl5d3p2d3Z0eW9ucXV1e3x+fXl4eX58eXZxdHp/cHFye293d3V1dHdwc290bW1vcXSAdnh5d4J7eXJ1dXB6e3V+dHV2dXR0fXp3cHRzenx3dnZ1c3R5eHd1dGx9YnJzZ2NRW19lbm5xcHDGc3t5dXJtb3h2eXhsa3F1eHt3dXV2dNN0dHd6dXByeXp1cnpzd3F1eHd0cnl4dXZvcXJvc25ubndzcG9ydnZ0eHN8gHmBfnqActJvbm1zbnVxdnBva2dubWzW3NKotGhsbm10dG1ubm5qam5paWRmb2RaYGZgX191aGdoaW5ydnNsaGRfZWNma2tuamd2cXNvcnFvbnFsb2dmYmhmd2xkcXByeHRqbXR6dm56eM7UbcpqusLEyMVogHhfs2xscnFycHJxb3Zxa3CAa29va21ramlna3Fub9rW1N/W0NLddXRvcXNycWpvdnV0dWxxcnZ7dHRvzsdtbXdwc3Z3c3h5dW54dG9scmpzb3J0bnVvbW5scHpybHNzaXh1dHFoaHJtbW9rbW9tb29samdsbGzPbHFtamp0btrU0OlzcHB1am9zcXd3dW/SbW2Ab3Fwc29samlsbnF0dXNycWhrbnhyb29vdW9veXh8cm91cXBydnJ1c3J0dHJ0dHNudHFxdXBvcHdscGtwcWpzcnJ23HBva3Ft2m9qZ2dsbm9ub3Jyamlxa3hza29zaGlxcHFvb3JyfmtudHFtZ29ra3Vxa2xwbm1scHZwcWxwc2sFf39/fn6efwF+hX+Cfox/AX6RfwF+hH8Dfn9/hH4Ef39/foZ/B35+fn9/f36SfwN+f3+EfpV/BX5/f39+pX8Lfn5/f39+fn9+fn6FfwV+fn5/foV/AX61fwF+hH8Lfn5/f39+f39+f36YfwF+i38BfrN/g36Gf4x+An9+sH+Dful/AX6OfwF+ln8BfrF/AX6Pf4V+zn8Ffn5/fn+FfoR/AX6af4h+lX+Cfrl/AX6Hf4R+jH8BfsR/AX6FfwF+t38CAgQAgP7z44Pz4fSFiZOSlZyQlJGIiomJiIOLjoWKmYT8kIWQg4uBiP787/Xq8PnyhP6Hh/+BmKmbmZmaiYny9Y6Ah4mJg4+PkJGEh4uEhYeKjoyA9vqKi4D5go2SnqSV9O3y7YCFgICJmaiihoePgoKChI2YmpiNkpCOhZWIgoX9g46QgJKUi5SOiZmQi4aSj42HhYuAhomDgvuCjoqChYeDgJOQjIqAh4qEkJKHiIyPk4iIioqOlqe2qp6Zi5GYkpqIiPzxgPD8/oP8goGGhYT9gvbr8YSI+4KHhISDiZCSkYyRjo2Uj5CMjIaIgYqKjYH+gYeEh4mIh46Rk5GUm5acjJGRgI+UlpWajf7+jYmFjoj0h5mG++eFqKSKhoeIh/+AgIGGlZabjpSMh4yHhpKKm6yVkJGLh4WMiZ6WgoeFiIiJ+YeAjYmOk4eEkIiIgPaIh4aGh4mDiIqNjoqMi4qSkouJhpCOm6Oslo6Bk4ykkZWF/YmNkZmXlZ3e9fyIhoqNiouJgIT86d/Fy9HV4eLh6ff47/mAhYWIiZGJkY6al5OMn5+knpWZmKWjm5+ysKO1xMKvrqiXnKCal5iPi5KSh46K9/aBhYuUkZCSkpGNkpSWk42MjpWOk5KPjY+Kk5OVk5WPk5SPmpuYkZGRl46Tg5WRk5aSnJCPlZSRk5aOiIyG/ZSegJGXk5qWk46VlJGWlJCPkpOPl5KZk5L8nIyMipCQko+Sk4iYlpWRlJCNkuufn4zu7evu+YiIjY6Nj46LiImD/JGNkpaTkY2QkY+Sj4uMjI2Fi4uCi4iAjYqLkoiNhpCNi42VjZWNlpaLj4qPkpGPjIaVko6GipCOkImLioaFgIWKgJiVlYeEhYqFiIuFiYCIhvDlgeTV3PaEiIODiYyPlIyHlY6TkYWXkY+NiJKWhYudlY+WjZyqr7q4oqKalYmYkZaQlI+PlZiNjImOh5OOjoSOiZCQjZKagoeNjIGB/e37goaLjIX06IDyhPH99O7t/IGks92ImpyQpqanoJKJgImdgKOgn5aL/4qHg4WHgoaH/vX46+H6jIqEjY+Ik5ORjoeUnJmXjo6MkJOLi4z69/SDiYygraqUl5+al6CZkJGWj5aJiYuLiomIjp2olJWbko2QlI6OlIyMjP+DiomFjouOhYqTiIWGiYWKioeA+oGA/4CBgoyLi4mMk4aBgYeRj4eUgIaJkJCWpJuOjpSRj4+Ok4uNhIWHhYGG/IeSiIGOlY6SiJebmZWQkpSRi4iNi4yHjo6Oi4KDgoyLkY2HhIqViYmOjY6H+oSMg4SDhYiG/YiIiomIhoqLi5CLiYGNhIuRjoePoJmWmp+fk4eTkIuBhYCBgJCSi4mHhZGHjon68fT5gOvk1nvk1ud7gIeChY+Eh4F8fXx9fHqBgnt+injogXiDeYF2fOzq5ure3u3sfe6AgPR6iZaJh4qMfX7h6IV4foCBeoOEhId7fH14ent/gIF45+6Cg3nmeYSIkJKG393o4nh8eXh/i5WSfX+Ienp4doCJioh/hIOBeYd/eXvqeYOEgIaGfoaAgI2Egn2Gg4F9e4F3fn59fPF9hoB1e357eIeCgIB5gIJ5hId/fYKIiH1/gH1/hJOgk4qNf4SLho2BgPLkeufw8HjqfHt9fX3yfezk5n6D7XqBfHt9hIeIhYCFgoGJhYaCg3+BeoKAhHrzfIF+gYOBfYWIiYWHjomPgoaFgISKiYmNhPHvhIF9hH7pfop87dx6lJKAgH9/ffJ6ent+i4qPgYWAeoKAfoR9h5iHhIF9fnqAfY2GeH59f35/6n14goGChn18hn18eed9f39/foF8f4KEg3+Dg4KIhX+CfYaCiIyXh4J3hYCRgot+735/goiHh43W6/SBgIaHgYGCgID039nCx8bK29vV3OTl4uV1eXp4fIJ9hICJhoR9jYqOi4GEhpCNhIeXl4qUn6KTlJGDipKOi42GgImKf4aE6Ox9gISQiYSIh4uIiYaHiIOBhYmEi4mGhIN+h4iIiY+FiYmFj4+KgoeIjYWNd4uFiYyGkIKChoeFiI2Dfn945IWPgIWOh42IhoKJh4WKh4WEh4iFj4iKhoXbiYCBf4OEhoKFiH+NiYuEiIOChMyJinjPy9Lf5oCAhIWBhIeFfYB65IODiIiGhoSIioaHh4SDg4N9g4F7hIB6hIGBin6FfYWBgoaMgoyEi42CgX6EiYmIhH6Lh4V9gIiHhH2Cg39/en6CgIyLiX1/foN7foR9gXmCf9zUeNPG1O1+f3d0fH6Ehn97iH6BfnWAenl5dnl+cHWFfHp8doWQkpWUhYqEgXqFgIaChYGDhYmBf36BfoiFgnqEfYKCgIaKeHyBgXl57d7sen+Bgnni0XTcfOHz6OHd7HmTncF3h4uAkZCQioJ+eH+MgI+Jh3535X99eXx+e4CA9Ovx5t31hoR9hIh/iYiFgn6FiIaKgoSBhYmDgoPu6OZ4f3+MlpSFiI6KiZCMg4WLhYuAgIWCgoGBhouQhYiMhYKFiIaCh4GCgu56gYJ+hYGCfYCFfX1/gHyAgn147Ht683t7e4KDg4OFiX17e36Egn2HgHx9gYOIkYeAhIaFhIaChH6DfH1/fHp+7oCKgnuGiISGf4uOi4aChIeGf4CEgYJ/hoWEgXh8fIGBg4J/fYCIgYKDgYJ973qCeXx6eX186X+AgX5+fIKBgYaAf3aCeYGHg32Cj4qGio6PhnuHhX92eXZ5eIWGf399fIZ9hH/n6O/wgMnIwWrMwMtnbXFsbXdub2ppa2ttaGpubmtqdGfGa2hwaW5macjQytDMzNfYa8xwct1vdHtyb3R2anDO0HdtbG9ubm9zcnRtbGxpbWtsbG1oz9h2dm/RaXN3e3ZtwcXUzW5ubGhucnd4bm94b25oY2tzc3Nucm5sZ3BuaG3MaHJzgHRvanBpbnVxcmx0cm1rZ3Bqc25vbNNydGxlbHBuanFsbG9tdHRscnNwbnF4dW1xdGxtcnZ9eHN2dHR2dHtzcdnLbM7e2GXTc3BtcHDab9fT0XF11W1yamxveHd0c3Fzcm13dnd1dXJzbHJxdW7bcnRzc3VxbXVydXZ3enJ5dXZ1gHR1c3V6dNrUcnNxcW7TcHVv2ctndH1xc3NzcN5xcG9ydnR4cXVxbHJybnFsbHVwb2dqbXBybHBsaG1tcG5u0W1sbm9wbWpudm5nbNFtcHJxb3FvcHJzc290c290cm10b3dvamt2cnNrdGxzbHNx2W5xcHJxcnLD1OF1cnp3c3NzgHHcysO2urW8x8vHxcvLzMtmaWlna29qcGpybGxoc3F1cmhtbHNtaGt3d2docHdzcm1nbXh4dnt2cXh6cnd4zdVzdXaDfnR3dHp5fHZ4d3NvdHdxenl1dXFvd3p7eH92e3t1enh3cXd2fHeBb310eX1xfHFzc3Z0eIB1b25lyHF8gHR+dXl1dXZ5d3J5eHNzdnV2fXp5dW6xcG5xcXB2enJze3Z6dXlxdXRva6JqZ1egnqvByW9xdXlwdHt4cnVuxG51endzeHV4fHh1d3JzdHRxdHFyd3Fpd3Z2e3F4cnVxcnd+cntyeH50c3F4fHp5dXJ4c3VvcHd3dWtydnV0cHFygHR0cmpwbnNsbnZtcW51ccHBZ7ivwdRubmlmaWtycmxrdWlraGBlXl5cXFxaU1dgWFpbVmBpa2ZkXWVnZ2VvZmtrb2xvb3VtbWtwbnVxcGlxbHBwa3FzaG1tbWZo0cfOaWxubmjNvmWzasXXz8rCzWd3eKBhbXVud3Rzbmlwa292gHNrZ2BfxG5sam1ubnR149jh2dDleHRvdXVxd3ZycW5vb2p3cXNvdHx3cHPQzMdkbmtxdXdxc3Z3d3d1b3B4cnZwb3lydXNvdXRzb3R2cm5xc3Vxc29ucNJsb3VwdHJwb3JybW9ycm5wc25r1m1r3G9sb3Z0dXV4dm1ucG5ub2x0gG5sam9wc21ucG9xdHZvbW10bnBxcG5w03B4dW51cnN1b3h5dXJvcHRzb3NycHVxdnR1cmpvb3FwcXFybmtzdXRvb3Jt0WpyaG5raW9u0mxvdG5tanFzcnRubGp0aXBzc3BxeHJwc3V1cWl1dW1lZWZsa3N1bmtqbnVrc23M0NnYB35+fn9+fn6VfwF+h3+IfgV/fn9/fol/gn6UfwZ+fn9/f36Gf4R+nH8Bfph/AX6pfwh+fn9+fn5/foV/CH5/fn5+f39+mX8Bfph/gn6FfwZ+f39/fn6IfwF+on8Bfox/AX6ifwF+h3+Dfoh/j36uf4J+vX8Bfph/AX6TfwR+f39/hX6LfwF+0H8Dfn5/hH7Gf4N+hX8Ffn5/fn+GfgR/f39+kn8Bfoh/hn6Xf4N+qX8BfpN/BH5/f36ofwF+rH8Bfoh/AX6uf4R+AgIEAICE9Pjz/oD19O77goGOiY6Gi4qGlpCO9//68/SGgv74/f+Bh/Xv+v2FiIyLjob99Y6Lif+BhIX36vKIgIKLh4qMhfqC/Pz5goP/6vz+5+75hoiChoKFk5unno+A8vaFhICG/oOGprKbi5GOgofa+fyJmaqSpKCYlZmTkJ2SoJqJgoCDi5OIi5KWkp+Uk5iTgoqRg/OBiI6Mi46Gho2RipGJhIaPhIqSiYiNkIeGkY2HiJGVprOsnJGJjpWOnIyWiomEgISIiISKg/v1hPzo5ID59ICDhoaJkI6OkJGLj4+XkpCMjZqTiIiQkIKJhoyDhImEh4aChoWSlJSRlYiTlZKTk4COiYmJi5GGgoeF/4iMgZaptJmQlIX8gP2CgYWHh4yGjYyNh5CZi4iEl5GHhpGtu5qJjfKF9/WFgP6O/omahoKPi4qHi4uLjYmKjZOTh/aBjoyKgoeKi5GMi4mQi4SFkpGIiJCgsImimouIiYSQgIiKlIaKm5SL8vL7h4uHivGKhoCKgoeNhYaD9ObO0dzf3u3g6f/8gIaGjoeIiJORk5WanpqbkpmbpaOnqqabpK65wL+urKiam6mPioyRkJGPk+ro8oqLio2Mi5CTj4yUlImXnJiXjo6Vko+Ql5mVlY2YlI+Vi5SVlZOTjJSOj46TkY6QkpeRkoyUkY6TjpCBgYqcmYCWlZKVl5eVl5WXlZeTmI2SkIyKjYuGhoiPkJOQj5iQlJSTjpiUmZOZkZmllufm7faBgI2IiYyPiYmMjoyFh4OQkZCUjY+LhIqNhouLiIiGjpuL/4aGkJOLh46KhoyQloeNkpGNjo+NkouLlYqLjZWNj4mQkYqJiImLhIiEiZOWjYCWnpyjnJuNiY+H+/yB++769t/g6fqFgYeLh4+Ok4yOjpmSkJKTko2FlZ+RioOKhYSGjZuPlKKVi4eulpmTpo+ak5WTkZeSkIKFjY2OjpGRj4CJjI2MjIWB7+/8guGWq5PsnIqC6P36z8X2/+757fb286K70IuOoK6YnpiGiJ2utICci5SVjYqJhoaCjIaDhID39unh/IuIi4aWjZabk5CQm52UlpOLj5COi4iKg/iBhIWIlZqkppiWnpOfnZGUjYmRio+NgoOFjamxraCCjpaXnJGSiZWKjpSOhZGGjZaYjomGio6Qj4uFiIOAgIOHiIqC/oGGiIaHiIiNkIyPiYyNkYCWqqSF+JmUi5OZmZuRh42QjoSCh4mOiYf/jZKOjp2JkpKOnJeJjJCUkZGIkJOMjI6IiZOPi4iLjI+ZjYCDipCSh4uOiYyMko+Ji4+Gi4mKjIyPlIaOjYiKj46AjYaOl5eNh4WTmJmTlZmRkojz9YqOhouJh4yLipKQjYSAgoH6g4B+5u7n8Hfq5eTtd3OBfoF5fn55h4KA4vHl5OZ6eOvn5ux4euDe7PF/fIB/hHzt6oiDg/R6fHvo3umBeHuAfICDe+p77+/weXnr2ujt197vgIJ3fnp9homVj4R25Od9e3Z863l6kZyJf4aDeH286u6Aiph/j46Jhoh/foqCj4p7eYB6goV7gIeKg46EhYqGd36Ge+l7gIWEgYN+fYKFf4R/eX6EeIKJfX+Ehn57hYR/foSEk52YiYJ/hIqCjoKJgIF+en6BgX2CffLrfPLg3X3y7X18gH+BhoWHhod/h4aOh4SAgo2HgH6Hh3uDfoN6foV8fn99fn2HiIiFiX6IiYWIiICEgYB/gYN/en598X+CdoSUnomBhHrwe/N+fX+BgIZ9gYSDfIWLf315iYd9e4KTn4h8gN135eN7dueB7ICMe3mDgIR9goKBgn+BgYiFfu18hISEeXt+gYaDgHuFgn57homAfICLl3mSioOAfXWAeH+Bh31+iYiF6/Dyg4R9guKAf4CCfYCDfH165tjGxtHQzuDR2O7pdXp5gnt8e4aEhIWFi4qJgIeIj4yPk5CHj5SbnZ+RlJOIipqGgYKGhYiIh9zg4IOEgoWCgoSJh4OKiX+Mj42LhYaMiIaGi46Li4SOi4aMg4qKiIeHgoeCgoKIhYSGiI6FhoCFhYCIhIJzdHyNjYCKhIWJiomJioaJh4qHjn2EhoN+gYF8fHx+gIWEhIqEiIaHhouIioWIhI6Ugc/R2N50d4KAgYSGf3+ChYN6fniBhIGIhIeCfIWCgISAgYCAhI2C8Hx+iIiDfYaBfIOFjICFh4eDhYmEi4ODin+BhYuChH+GiICAgYGCe4F8gIWNf4CGkY6VkZGGg4iC8fV97uPu7Nnc4O99d35+fIJ/gXyBgYyFgYSBf310g49/enV3b3FzeYJ3fIR7eHSTgoiCkn+HgoiHg4aAgnl7gIGEhYSEhXd+gIKDgX164N/te9GLm4LUjHhy0eXgvLPc69/r3ezn4I6dsnx/i5OGjol+fouUl4CFe4WHgX9/fX15g398fHvw7uPc8YSAhH+Kg4eMhoiGjImFhoeCgYODgYCBeuZ4enp8hYqRkoeJjoWPjYeHgX+HgYeFe3x+hZmYlI96g4uJi4WKfomAg4iEe4V+gYeLhIB8f4SHhIF9f3p3eHt+goV983uBgX+AgH+BgoGFfoKCg4CHlY556IiEgIiJiYuEe4KEg3d6f4KFgYHyhIeFg45+h4iEj4p+gYGDgYN/homEhYR/gIeEgYGAgYKLhXh7gIOHfICCfoKDiIJ+f4N7f319gYGEiHyCgX2Ag4N4gnuCi4qCgH2HiIeChIeFhH/i5YCCen9+foOCgYeDg3t4e3nrfoBwy9bO02bMx8rPZl92bWxmbWxlcW9rxNXGyclrZ9HRwstsaMPK09Vxbm5xdmzQ0Hdzc9lraWrNydNzamhtam9xa9Fu2NTaa2bMxs/RwMrScXNnb29xcXF3c3Bryc9ubmlu0Whob3ZubnZ0aWug1dZvb3tldHZzbWxmZG9qdHJoa4BtcHBqb3J1b3dtcHJ0Z2t4bdhwb3R2cnFwb29xbnBvanB0a3J4b3BzdnBscnVvbXJvd3t6b29vc3pwenJzcXVxcHR2dnBybuDWbt/QznTj3XNvdXNydHN4dnlwdnV7dHVwcnh2cW93eW50cnZvcnZtcHFxcG52dXh0dm56dnR5d4B0cnBvb3BvbG1w2W1xZGtzfXVubmjYceN0cXJ1dXdsbnNzbHN4bm5seHluaWttdW1na71jx8hqZMhv0Wt0a21wb3FtcG9tcW9vcHdwcN9xcXRza2tucnJwbmhzcXFqcnhwa2poc2ZydHJtbGJpa21uc2xwb3V219zdeHZwc9RyboB1c3FxbG9rz8K6vL2/vMbDxNPUaWpobW1rbHRycHBudHVxaW9ydG9wc3FscnFxbnJrc3NtbYN0b3J1c3d1dMTNznh5c3ZvcG90dnR5d255enx4c3l7eHZ1eXt3eXJ8enV8c3h3c3V3cnRvbnV3cnR4en9yc3N1dG52eHNpZ2t4fYB7cXV4enh7fHd4dnp5em12dnNvc3Jtb25scnZ1dHp1cnR3dXd0b21xcXh1Yamttr5iZ3BxdXl6cm5yd3Zuc2prc212dHl0bnlzcXFvdXV0c3dx2WdxdXV0bXd0bnV0enR2dHFydHp1e3V0fHBxeHxudXB2enJzdHN1bHFzc297boBteXZ8fHx2dnl2099w2NHX08fOythsZ29sbW9ramhtbnRuanFraGlia3NoY2BhV1hZX2FXWVxYXFtvZWtteGlpanN2cW5rbWhsbG9xc3NxcGlxc3JwcG5tyczHZbB2fmeydGFZr727nZWxycnUxtLPyHN1kmZpcHJsdnVvb3JzcIBlZXFvbm5tbG1tdnNub3De2tXR2HVydG90dHR3cnd0enJvcHd2bXF0cHJya8dmampna3F1dW9zd3J3cnR0b292c3l2bW5wdH15c3ZtcnZ0c3N3cHVucHRzbHNwcHJ1cnBqbXR4cnNubmttaW9xdnhw1251cnJzc29vbXB0bXBwb4BtdHBny25ra3VycHRxaW9vbmZqcHR0cHTcc3R0cHZrdXZydnNtbWxwbG5wdnV0dnVvcHNycHFub293dGtscG9zbm9wb3Fzdm9ubXJtb21rbnBzdWtvb2twcXBob2ltdnRyb2xxcnBub29zcm/J0G5xbG1ubHJtbndubmtrb2zPcQF/hH4Bf4R+jH+FfoJ/hH6Cf4R+hn8Mfn5/f39+f39/fn5+iH8Hfn9+fn5/f4d+jH+CfoR/AX6Kf4N+on8BfrV/CX5+f35+fn9+frp/AX6KfwN+f36afwl+f35+f39+f36SfwF+qH+DfoR/AX6Jf4x+q3+Dfut/hH6ifwF+t38Dfn5/iH7Dfwx+fn5/fn9/f35/f3+NfgN/f36bf4V+mH8BfsN/AX6TfwF+k38BftN/gn6QfwJ+fwICBACA/YGCgfb9/YGE+u2C9/T49ff5g4OOjIOFiv6Bh4GD/vXq6v+Fh4yFhvyGi4yEhf/7hIP7gYKLhf6BiIGDhICAgIqQhoOIh5GF9oD/hIHw/4CDiIqZpJuTh4iEgIT3goCCi5alqJCKk5SYlYeHhPaHgP7xhLWZg4mCi/v4h5qk94GA+4iEkp6TjZGMgZWGlY2Rg/KEgIKHioeIhoSMiouEhIaJjYCBhImQjYWGgYOLlpypuqOknI+NjJGNj42Ni4X+9oGChIyIion29ez7/YKCgYeJhYeJjYyKjIuVko6Pk5iUjJGUmIuKiomBgICHhYqGioiPlpqVlIWKjY3k4ouPkYqAi5GKiYaHhYOEi4CAkZGxoKOfmIfv39/3hIiGh4mMjoOEiYeXlIiKipONkI2In5upk6jg7vaD+P6EhI2QgoaGiYSLkI6JiYeJjpiQjpaKh4eG/oKFiYaJi5KW/4SRj5GKipCOj4CHoKOUgomVi4iRlYiKhamjjIT29oSEhIGF/4GAioeFgoWIj4eLiIyXhv3j6dLu6uTo9/eEgYaDiomXjY2XlpySlZiOoJegsaqsoLCtvMGvprujjJqUjo2Oi4vX/P6MmIqUlZGSlpCPlqCZko2OlpOQlpCGjpyVmJWXj46MkpCRlZWPiZWPlJCQlJCQl5aWk5WSko6LjJCVgpmbmZWAnZOWlJ+TlpGXoZOUlZSPkY2Sko2UjY6Ki4qPkJSZjZSXjpCNj4yZmJ2jjfH48/b4/oaGhY2PjYaPio2Hhoj+gpGIh4yKhoWJnpKQh/+Eg4+HhYSLi4yKjI+Qi46Nj5CJjYmVjYmKjZONkI6PgoiIh46MjYmMiYb2+YWRhY+SlZaAk5GYmpqYlIyOhYmHgoP77oT+9vn57v2AhIGJiIuNn5SRkpWRiZOQh4uTjIyNiYqAgYyFhIqUkZ+fnJKYmpqalpOToZWFkYmGjouMiI2NhoWHiYuKiYb5/eDth5vk9MrDgYmSmJiYlOrf3dj3gf/9+vr7q6H2oZihopbnkaa4ppWAkouMhYmMjIyVj4r4/Pvz9fLo4P6KgvePmpGMkpeQjpaSl5uGj5SNi4uMlJCKiv+HgJuXn56anJqNjZGKlI+QjIyNjo2IjZqosZKYlZWThoiQiouOjY2QjJKSj4qLlo6Si5OUiYuThYmCiYGAiIeEgPiKjoWEiYeIi4qNkYKPpKWA/IKjmZKWnI+Qi5OdlYuIiYWSiImMjYmBhZSUiY+Ek56blJeXlZaYlJSUi4f7gIuSjoiGjI2Nh4mJkZmUkI6UjJCOkJWOioGGkYiPjJuenIuGhY+PgI6GiZSQj5GPiYaNoZudlIqXmaKV/I6alY+GgIiCiYuMiIyFiYbv7IODiYGA7nh6euvp7XqB9eV97ezv7evoenmDgXd6fe55fnZ67eDc4Ox5e4J5e+p5f4N7ffLufHrseXqAeu13fnh6fHZ2eIOHe3t8fYZ56HrwfHjf83p4fn6KkYmEfH19eHvleXR1fIWQk4J+g4SKh3t/e+J8eOjgeKKIdnt0etfeeIaQ4HmA6396hJGGgod/eIp7h3+Fe+V9eHl9f36AfX2CgIJ5e3yBgnV4e3+HhH1/eHl/iYyTn5CRjIeCgISChoR/g33y6np8fYSAg4Dn6uDx8319fIKCfYKEhYWAhISKhIGFiYyJgIWJjIGBgoF8e3l+fIGAg36DiY2IhnuChIPJwn6EhX+AgoeAgYB/fnt+gnh4hYObjpCOjH/o29nyf4OBg4SFhXp6gH+JhoCBfYeDhoN8iIGQgJTR3Oh66/N8eX6Ednt8gnuBhYR/fX2AgouHhYt/goB97Xl5fXx+f4aL7X2HhIWAgYWCf3R5i4+JfXyHfYKIi31+eZaSg3/v7n19fnt/9nuAgn58eHt+hH5/fn+Jf/HW2b/a29HZ3+N5eHt7f3yJf3+HhoqDh4h9i4CHmJWVjJiPmJ2Xkq2ZgYyIgoSGhIPH8+6FjoGJi4aJi4SFipOPh4OGjIiEiYR6gIiJjoyNh4eEh4eIjImEf4uHi4eHhISFio2NioyIhoKAgYaKd4qNjoqAk4WKhpCEh4iIkYOKh4WCh4CGiYSGgoJ/gX2ChYqNf4eNg4SCg3+JiIqSfdHh4ufp6X1/fIaHg32FgoR/f4LsdYV9foWBf32DnY+Gfex7eoN/fHyCgoWDhYeHg4WChIl/hn6Fg4KFhYqDh4eFdoF/foWEgoGDgnzl6X+HfIOEiouAhYWJjIyNi4WHfoKBfX3x5Xzv6+7s4vN2e3p/fH6BjoWDg4KAeoKDeX+EgH98eXl0c3l1dXqBfIeGhYGHioeJhoaEj4V3hXx9hYGBfYKBfHt/fn5/f33t89fhfYvI166pdHZ/hYmHgtXMx8Tgdu3s6+zolorWjYiQkInZhJGbioCAgX1/e3+BgoGIhIDs7evs6+3k3POBeumEhoKAhImFg4mDh4t8goaAf4GBiIJ9gPB+d4yIjo6Li4mAgIOAiYSHg4OEhYeBg4uTmICHh4aGfHyCfn6AgYCFgYaGhoKCi4SGgIaJgIKGe396fXl7goB8euyChX1/gX6AgYCCh3iCko6A4nWMiYaIjYKCfoSMhoB/fneGfYCEhIJ8f4mHgYR8iY+MiIqJhoqJhYeIgX3teoSHgn19hISEfoGAhYuIh4SHgoWEhYeDf3l9hHyDgIyNinx7e4ODdoZ8f4eEhIeGgX2BkI6Qh3+JiI+G5oOKiIF7eHt4foCCf4J8fnre3Ht9g3eA1WhrbdHG1mty3cpr1tbY0s/NaGZwbGZqa9Bsbmdv1MDEzMxna3Jqa9FobnNtbNfRbGzXamltbM5rbGlpbWVqanN1b21rb3Vn0m7RbWnJ125lbG11eHFwaW9wcmvQcGRkaWxwdGttb3F3cmlsbchtab65YIFwYmdfYLG2ZWh0wWyA13JqbXhwcnRrZnRrb2pxa85wa2tucHBxbnFycHNqa21zcGNrcG52c3Bza25wdXd0e3l4cHRub3V1d3NvdnHj3XBycHZzdXTX18nf33NycXR0cXd3c3VwdXJ0a3B1eHZ2bnF1d3FwcXNwb2xtcHZydHB0dXV2c210dXK5uW51dG+Ac3Vwc3JwcG1tcWtob2t5c3d2dG7YzM3fdnd3eXt2dm5qb29ybnJyandydG5qaGFsY3W9v81t1t9sbGhsZGhsdG1wcnNvbW1wcHd0cnVud3Jt0mtnbGpsbHJ10Gx2cXdyc3NvaWBkbHB0c2xzbXN2em5tanl8c3Tb3HJybGtx429wcnJtZ2tvc25wbmp0ctnExKe9w7rCwsVnaGxub2p0bWxxc3RtcnBmb2RqenV2b3Fqam90c5WIcXd1cHR2dXGy3NVzenJ4eXd3enN0dnx+dnFzeHdydnFpam92fXt6dXhzdXZ3eHZxb3h6fnd2bnN3doR7gHh1c3B2fH5odHd7d4Bwe3R+dHN3dHxueHhzc3Vwdnl3c3NzcnNvcnB2e291fXB0cXBtdnRzdF+gt8XSzclscW94d3Jwd3F0b3R11WR0b3B6dXJtdpiOdG7Ra2pybWxxcnR5eHh4d3Z1cHN7b3Vub3B0dnZ6dHl7dmt2dXJ4dnN2gHh1btjTc3RsdHN5eHB0cHZ4e3d1dnJzc3Fx3s9s1NPW08vXZmxscG5tbXVxb25raWdvcGVrbmxsZWJfYF1hYV9iZWBnZWZqcXRwcHBxbHZwZXFrbXNvb2xwcG1tb3BvcHBv1tvEyGxzp6qLiF5dZGtwb2yzsaemvmXL0dXUzHdoVq1wb3J2dMVwdXVnZmlrbGtub3BvdHNw1dTY3djd1MzUb2rMcGpubm51dHJ4cXRzbHFuZmpxdHRxbW7RbGd0cnN0cW5ubGtrbnNzeHJ0cnV5cm5zeHdrhHGAbWtwbW1vcG5zcHV0d3JzeXZ2cnZ2cXNya29qa21wdnNvbNRydW9ycm9ycm5xdmhudG28Ymxvb3F1b25scHNybnBtZHJrcHR0c21vdHJycGx3eHh1dXNvdXJydXRvbNdvc3RwbW1zdXZvc3BzdXR2cnJwdHJxc3Fvam1xbm9tdncydGprbnNtZnJscXRzcHR4cm5wenl6b210dHdwynF2dG9taWhpa21zcnNvbWXEyG1vdGcMfn9/f35+fn9/fn5/hn6HfwF+hH+FfoV/AX6FfwV+fn9/foR/AX6Qfwd+f35/f35+jX8BfpB/BX5/f35+h38Ifn5/f39+f36PfwF+rX+Cfod/hX6uf4J+mH+Efpp/Bn5+fn9+fpl/AX6IfwF+nH+CfoV/AX6Of4p+p3+Dfup/hn6NfwF+jX8Bfqp/gn6VfwN+fn+Gfr9/hH6Cf4R+h3+FfgF/hX4Df39+hX8BfpB/iX4Df39+l38BfsF/AX6PfwF+q38Bfrx/AX6Qf4J+hH8CAgQAgPr3g/Dz//r1+OX+hoD36v/r4/D69YSNkoiDhYL9iYSDgfGGgoKFhYSChoeKlIqNjv6LhoKEi4WBgY6GhoiamI6OjYaTlIuMlYaNlo+RipCQipmeoZCJhIP6goWOjqCdpa2TgoCLjIePkY+GhoiCg4GCgPb23OvZ+YOC/oH92euMgJSXjYeDipmWjZiYkpKFgPyEh4GMh4GMgISRjIODhoqMlIuChYiLioqPh4eAkbKxoZqXnJWWkI+TkY2Hi/bl+f2Gg46KkYL/9fmBg4WIhviEio2NjJWRjZeRlY2SkJGUmZaUjoaIi/qGhZWVlIyQmZWUk4uKi4CEipCWloqKk5CQgI2PiomEiouLj5CKgYuTqp6UlpeN7fWAgoiCgISFi4WBiouLjImLjY2Sjo2Lh4qepfSonMrig/qB+/yQjOSHgJWMjIiIjYmNjYiFi4mTlY+DgISPgoKCh4z6/oWNjIGPno2H3f6hsKWdlY+PmImDkoyEiomUpJqRhfSBh4P9/YGGgICCgoSCgoCCiJCgopCMkJmL7OLU29jq7/70gIGJhYmPlJGWoaCbm6Kdnq2ppqufoai+yMmxkJyUk5KMj4zk74KAiZONi4iQjJCTmZeTjo+UlpKTkpCQjI+hlJKKjoqNkJGSlpSTjY+OjpGemI+PjZeSlJmVlI6Yj4ONh/SelZWTgJGSk5WRl4yZk5GPlpKUl5aPj5SbmJOQlpmUlY+VkJSYjZGOl5GUj5uUlez88vb7/YSKg4+JhImIjI2MioyChv2LjYmJiY6AgvCDi/uBh4qChYiDgIaEh4mJi5GJkICHjI+GhYSAi4uJipWLjIqNlIqJjouLiYHzgIuPkY2Mj4uPgJGUkZSPj5eNkpGGhYuRkY2L9feShviKnJWJ+/2Vio6PipaXlJCWlo6IjJaGjI+Jko2MgIOAjZ6LpaOUl5GVmZWRkZCMj4yEiIWLh4mLh4WPiIKHjoP86+WDlKKAp4X/oKCgmp+YloWIr7+0y9ro9PeDh/KqnvmPn5qFkJq4tZ2TgIKNkZCJh4mMhoqPhon8gP7u7d/uhoiFioeChpGVlJCakoqXk4qVm5WQjpOUjYyGifz6iZ+nsZibj5yakYWBg4iJhoWSl5CSsLqkkpyOk5WPiYeCiouJiY+NkJeKio6Sl5SPjomPkIqFhYiHhf2Aj4SOj4mMjYSPj46QhfuSpI+lgJaTk5CMiYmilouNlZeYlJCKgo6M+5CSkoj+jIyUk5uViISclY6Ok5qSio2XmY2JiIyNkY+IlpePj6CjlJCJiZqPjo2FjZGOjI6Zo6CRhJiRjoaI/PiQjYeFipGWko+Vlo6XmpWRlo2JpqWRhYqTmIyOhIaIjIiKi4r974yQh/v1gO7pfefs9+3p7tfwgHz05vnk2eju63h8hHp6fXntgHt6edx9e3p8fX57fH6AiX+Dg/GAfXl7gH13eIB7en2OioSGg3yGg4CEjHuCioOGgYeEf4yMjn9/e3npen2DgI+KkpeCdXeBgnuDgoJ8fHx3end4eODmz93L6nZ26njnyduAgIeJgHh2f4mJgImLhYR7d+x5fHmDfXiDd3uFf3t6e4KAiIB5gYKEgICDfHpyfZicjoqJi4iIhIWIh4Z+gurb7vB/foaBhnr36/B8en2Df+p8g4SEhY2Ig4mGiH+AgoeKjImIgHt9gOh9fYyJh4GCi4iIhn9/gnp9gYKKioKCiYWEgIGDgoB8goSBhYWCd4CElZCHiYmG5+98foN/e39/hX16gX+AgH+DhIWFgYKBf3yIjtGPjL/Zeel58OmCgNh9dYaBfH1+gH2BhH97gn+IiYV8eHyEeXl6foPq8H+Fg3h+ioJ8zuiOmJGMiIeHjIF7hn59gX2CkYqEfPB6gX7v8Hl9gHh5eX15end1eX6NkoWGh4uB2tjK0s3e3u7lenh+eX+DhIOHk5CIiI6KipiUjpWKjY+doq6jhIyFg4aAhYbW5n96foqEg4GHgYOGjIuGg4WGiIODhYeHgX+NhoeBiIOEh4eHjYmGg4WFgoaTjoOEg4yKi46KioSNg3mAetOMiYeHgISHh4aEjX+Kg4OBiIeIjY2BgomPjYaGiImHiYWLiIuOhIiGioWGgo2Eh9bh3eXt7Xl9e4WAfYB+g4WDgYV5fuyAhH+BfoV4eeB6fOR0e4J7f356eX+AgYOBhImBhXd/goV7eXt3goKAgY1/gX+BiYCAhIOCf3rleIKFiIWEhYB+gIOJhoiEhIuEiYiBf4SIhoSC3umGfON4iYd95OGFeoKDfYSHhYOGhYB7fYh5fYB8hX99dnhyfox5jI6EiIGGh4SDgoGBgYJ4f3yBfH+Af32Ce3qAg3zz5t5+ho1vk3fsjouKg4qHhXV5mK6ovMjU3uR7fdiThd2AjIt9hYuel4eBgHV+goR+fX+BfYCFgIHvfPnn5trlfn98fH18foSDhIKKiH6KhHuJj4mDgYiIgoN+f+7pfY+VnIiMgoyLh396fH6Afn2JioGBmKCShI6AhIaEfHt6f4F/gYSEhIyBgYOHiYiDgoCFg4B7e4B/fvN7hXuEhYCFh36Hh4OIffGJk4CPgISEgn98fn6Qh36AhYiJh4SBdoSE7YeHiX7ug4GIiIyGfXyNhYKEhYqEf4GKjISDgYGCgoWAiomEgpKUhYOAgI2CgoJ7goaDgoKIkJCBdoN8gnx/7+OHgH17foWJhoSMjIKIiISDhX98lI6Ee4CEiX6BfX5+gX9/gH7p34KFfOfogNbPcdPT3c7T1sfScXDg0uTSyNLZ0WVmbWtqcGrVcWlpa8BtaWprbXFvbXBudG5xb9Vwb21wc3BpaW1sam52cnB0cWtxb3F1e25xdW9zcndzcHd0c2xua2zRbG9ybXdub3RqZGlvcmltbG9tbGtna2doaMPCscG3y2JjyGXArL9sgHJxbmZrbXV4b3B0c3BoZtNsbWxva292aWpybW5vbXNud3Fpc3VzcnN0bnBkaXd/dXZ2dnN1dnR4eHZwdNbJ2+J1c3hzdm3l2d1xa3B4cdZtdnZ3d3t5cHV1dGtucXh3dnh3a2ttcMlxcnp0c3BveXNydXBwc29xcXB4cnJ0eXVygHBvdHJtc3Zwc3VzZm5tdHVyc3V31uBzc3l1cHZ0d3Fwcm5wbm91dHZ0bnBvbmdqa6drcarBaMtq2cprb75pY3VyZWtscmxvbm9scnB0dHRsaW9zaWpsbW/P2W9yc2ttc3Frus10dnh3eHZ0dnJxdGltcWxpc3NzbtpydW/P1W5ugGhrbHBsbGlmZmZ1fnh5enlxvcG3v7rKytrSbWtvbG9ycXBzfHhvcW9wcHl1b3tvc3FwcIiRc3Zta3Jsc3e5z3NtbHVzd3N3bnVzfHlycnNxcmxtcnV1dWl0cHRyenRvc3Vze3l0c3N0cnV+fnF2dXx8fHl2end7dG5tabx0dXV4gHR2dnRxenR4cnBwdnd0eHlyc3V8enRyc3N2eHd8ent5cnh3eXFvb3dvaqu1uc/Z0mZrbHZ0cHRzc3V1c3ptctNvdHBza3Rra8loZL5iaXJuc29sbXJ1dXd0dHZucmlzc3VtaG5qcHNycntydHJyeXNxdXV1cXDQbHd0dXRzdnNygHZ0b3Zyc3hzenhycnZ1c3Nwyctza8VkbnFrwr1qZXJxaW5wb3BwbW1paHJlaGxnbWppZmdjZm1gb29rc2ptbGlsbGpsanBobm10bm9wcG5xampyc3Dd0slvcG5Yf2e2cW9qZG5tbmJlgJOYp660t8ZubbtzZLdobnFucXV4bmhrgGZqbXJtbnJybnByc3HZcuXY1cvPbnNtaWtwcXFtbWxudm50bmh0eXJvbXN2cXJwbtLJanZ8f3JybXRzdW5tb25xb212cmtrdnx3bndscHJzaWpsb3BscXR0cXdwcHN1dnZ0cHBybm1rbHJvb99uc2pxcnF4d3N2dHRzbtZyc2hvgGxvaWZmbWx2bmtscHR0dXNwZXVz1XR0dG7RcG90d3Vwb3B3cHBxbnNxcXJ5eHZ1cXBvbnVxc3Ryb3p6cG5tb3VucnJrcHNycG9zdHRrZGplcG9v2dB4cHBscnR2cnJ4dG1vcW5sb25sfHJuam1vdGxtb25tcG5vbmvJxXBybM3QA35+f4h+gn+Ifod/AX6EfwF+jn8Bfqd/AX6Zf4Z+B39/fn9+fn6QfwF+rH+EfoZ/g36FfwF+l38Bfq1/gn6afw1+f39+fn9+f35+f39+m3+Cfoh/gn6UfwZ+f39/fn6Tf4l+o3+Cfrx/AX6uf4Z+j38Bfoh/BH5/f36qfwF+mn8Ffn5/f36Ef4J+uX+DfoR/g36Jf4h+Bn9/fn9/fpd/An5/hX6cf4J+un8Bfo5/AX6YfwF+hH8Bfrl/gn6kfwd+fn9/f35+AgIEAICBhP/9hoKB9v7ugIXj6u7r+fn+i/+Rh/qEhIaOifP1iIyOkIH59IeOjoyKgIX9+fiPhf3/h4WRjpiZh/2BjYmGiIyOhf6Gior9hIaNjoaHjJyuqYn/jY2Un624tqKYjYqUhI+WkpuhlpuHiYH8gY+Hhv3+gPbu8ovt2+H4joeK8oCC/YuJhoaIi4yPiY2Hhv/6iI+Ig4SLi4uVlYWHhY2AhoeMkoyMjJiMgo2wv7OklZeXkZGMm5aJiIKFhf78h4iIiYyVkfTxg4aJ9PyGg4mFhoqMioyNj5STmZSSjpualZOPk4v5i4+Vk5GThvyGjIqKhZCAi4qLgIiDgoeFjZORhID+/IKHiYyNhv6AjoaKg6GlpKWhh+3sg/yDh4qIiISAg//+iIiKjIKGk4uOjYmMrI3nlZLy4/SOi4T69o3uhIOKjJiOjZOJhY6HjoyRjJGQioiOiYaHjoT9/JSM+fiRkfnq6JWkpq+Qj4+NkI2KjpiThY6JjZGdppiS/e74/4f384D48fn18YSLobCpm4vwhf6Nh4mKh+fd1+HY7PnzhYWIiYSNkJKVn56bpKanp6ipo6WjpafLs5+RjpOYj5KH4vL4iIqHiZGEjY6MjZKUl5iPm5CMmpSMi4iNmZmRjZGJiouOkIyPlZWTkZaSkJOTi/+Ojo2Njo6Kj5aGhfODoJyTm4CZkZmRj5Gbl5eRhZSWk4ySl5OTmJWRl5WRjJiMjpGMjZKSjZmQjZKMmIfu8PCAgoCD+4SNh4WEiYOEh4yFhYWCkYuL5oaHkYiK+ICChJWQgPaEgZCPloiGiYaKioeEg4mNhIqQg4mLioqQi5CRko6Jjo+Ni4vwhZKGj4mRkJKTmICTk6CmnZGQiI6JhpSWj4iHgYmRko6LiIWBhu+EhoCUkZKUk5GZlpmPk46NkI+HjYqDhoWJhICLiZWbj5ScmpOKiZGFhYKBh4eBhoiDg4KOiYOEjoX//N6KztPgmJj0jpmim5mYlouG+M7Tz7XX3Ons7/aDgrOkgeaGnZewwaSYjICJjIyKiIiEh4WEiI2P8viB9vTn6o2JhYqFhIWRjJOJh4GSj5WOiJmgkJCLk5SQjo6N/YWcipqqk57/hY2Rkfr3+pCaoIWTjOqamKCpoZecl5mPi4T5i4qPk4+PkZCLkZOXk4qLjIiIj5CHiISDhouRi42Pho6Jg4KIkIT1l56Sn4CbkICanJmal5eUi46QlJOPioyNjoqOh4+NkYeRnpSdqJiToZ6boqKYnKSThIyllYP7kqefoJuhoo/+g4qik5CMk46KkIOcpKmWhYKVr56Sh4iNkoH9go+Qj4qKkpeppKSbhoWOkJempZSWpquZkpiMk5KNjIKJhYOMkIuEgfz7/4B6eu7wf3t77PXheX7W4uTk8PDygu2Gfep6d3p+f+PlfX+Ehnvs5H6DgoOBen7z8eaEfPD4fX2FgoqHeuh2hIJ+foOEe+18gYDufH+Dgn19f4yXk3rogH6DjJahnY2Gf3yGeYKHg4qTipJ8f3TqeIN8fO3xfOrk3n3PytPigXh44YB66oB/fHl8fH2DfYF+fvDtf4V/fHyDhH+IiXp9eYF0e3yEhYKBfod/d3mYo5+TiYmKiIWAi4x9gnp8fe7sgH99f4SKh+zofX+C6/GDfYJ+gICDgoWEhYuHi4mEgI6OiYaEh4LpgoKHg4OGfOx/goGCfIV1gYCCe359fH5+hIiHfoD28Ht/gISEfu13gnp/eY+TkZSRgOnnffV9gYOBgX15fO3ugYCBgnh3h4GCgX9/loDRgoPl1t9/fnzl34LgfHp9gIuBgYV7e4R8g4CDgYeHf32Fgn2AhXzx8IeC7O2Hhe7b1YWRkZeDhIWFhoR/gIuHfYR+f4eIkYiI8N7u9n/q5oDo4+fj2nl+jpeVjn/XeuyEfoGAgN3Ty9XO6uvkfn6BfnZ/gYSGjoyIj5KPkpKUkJCNjoytl4uBg4WKg4Z91+vwg4KAgYl8h4WDgoSFiYqDjIKBioOBf3x8h4yIgYZ+gIGFhYKAhoeFhY2Eg4iHgOyDgoGChYSChox9etpyj46FjYCOhIyEg4eOiYiEeYiKg3+Gi4aDh4eFjImCgo+Fg4iAg4iKgouDg4iAiX3d3d53e3p98n2Cf355gH+BgYV+fnx6iIKE2Hx6hX193nR3eYmHeuV8dYKEjH9/gn+ChYB+fIKEf4aKeoCAgoOIgIWGhoSBhoiFgoPifIx+hHyIiYiFiYCAg5CYjoWFfoOAfIWFgn5/fIGGhoGBfXl1d9d3eHaFg4GGhoGGhYmBhX57gYN8gn14e3l7dnR+fYiKfoKIhoN9e4B2eXt6fHx5fn16fHqCfXl7g3zy8Mp9t7vEhorbfIWNiIaEgnx64LnFvqXIz9na3OJ3cZqMdNJ8jIaVnouGf4B8fn58fH95e3t8f4SF5fB76+jg3YR/e4F7eXyGgIZ/fXeFgYOCgI6SgoJ/hYqFg4OE8nuNfYmXg43qeoODh+7s7IeLkHiFftCKio+Vj4iKiYuDgHzug4GFiIaHh4iChYmKhn2BhH18hoeAf359gIWHgoSFf4SCfHt+hXzpi4yCjYCHfnSLiouJhoiFfYGBhIWDf4KDhIKHgoaEiH+DjoeNlYuKkoyMk5KIipKGfH+Rhnruh5eRko2Rk4PqeoCRhYWAhISAhXqOkpeIfHiElYyDe3yBhnnzfYSEh3+BiYqak5KKeXqCg4eRkoaJlZeIg4l9hIWCgnuBfHh+g4F8eOzr7YBtaNDUcHBt1djLam6/zNPR19TUcM1vatBsaGhobsrHaGtyd27Xz21vcHJxbHHZ2sx0btPebW50cXNyac5mcXBvbG5wa85sb2zRbXFwbW9tbnV3eGXHbmhscXd+e3JubG1rZWtub3N7cX5pb2XXaXFtbdDab9bOwGawtb3Aamlsy4Buz21qZ2dsbGpvbXBtct3XcXRubWl0dnB1dGpsbHBlbW52dHFraHFqZWh4fX10cXd5dXZwdXtwdG1tb9DWdHRucHV4ddPYdHNz1th3c3ZzdXFzdHZ1c3p2d3lzb3l3dHN0dHXTc3Bwam50a85xcnJ2bnRmcHBvbW9vbm9xdXJxb4Dh3G5vb3V1cNVocWluaHJ3dnh5cdrcc+BydXZ1dnRtbtDUc3BwcGtoeG9vbm1rdGuzZWjKwsVpa23Jw23EbmttbnRucnRrbndscW50c3V1b252c2xwcGvZ1nJv0dh0dNXDt2x2dXhwcnNzd3dxbHVvcXJrbXVseHJ41szX3nHR04DUy9DGwGlocHR4d2u5Zs9tbnJybsHDur6/39zLc3NycGVscHN0dnJvdXJvdHR6d3ZvbWuDcW5rcHJ0dXZqu9neeXNwcnhqd3RycW9wdHRxd3NzcWhrampqbnh0bnZtbnJzcm9sdHRzdH1ucXd3bs9wcXJxdHV3eH9ycMJhe3Z0fHB9b3d1cXd3eHhwa3p5cG13enNwcXZzeHhxb317dnltc3Z6cHRwc3Zucmi3wMJpb3Jz3XBucXJqc3V2d3l0cG5td3J3wWthb2xqu2FjZnV1bMtsYm5yeXJ1dHF1eXJ1cHV0dXt9b3FscXR7bnd3dndzhHaAeMhrem91bHh7eXF2cG54g3pzdXBzcm1ybXBvcXB0dnRwb2xmYmC5ZGZpcG9rcHJrb3F1b3BoZW1taW5naGppa2ZjaGdvcGdqaGZoamltZGlram1ubnRvbXBvcG5rbXNu2tuwZpyXnG9ytWJocG5ua2pnZbyksqqRtLfBur2/ZVmAdGpht2lzbnBwaGxraWxqZ2pwamtsbm90dNPccdvUzcR0bm1zbWlrcWpybmlrcm5scHB5e2xubXF4dnNwcthrdGpwem5zz2lxbXLW0NF0c3RlbWSwc3d5eXdzcnR3cW5u1XNwc3Z1dHF1b3J4d3FqcHJranR2cm5xcHB3cXF1cnCAcnVtbWpubtRzcWxza2djcW5yc3FwbmltbmxtcG1xcHBvdXR2c3Zub3dxdHh4ent1eH14cHR6dW9ueHJt0nN/enV0dndvx2psdnJ0b3Fyb3Ftd3d7cm1pa3R1b2pscHJo121ubXNscXZ2e3d5dmpscW5udHdwb3p7cHJ1bnRxbXAMbHBuaWhwbmtrz9HVDH9/fn5/f39+fn5/f4d+BX9+f39+hX+CfoV/gn6Hfwd+fn5/f35+h38Bfoh/BX5/f39+i38Bfpd/AX6Efwd+fn9+fn5/hH4Gf39/fn9+jH+Cfqt/gn6Hfwd+fn9/f35+mH8Bfod/AX6Uf4J+hn8Bfot/BH5+f36If4J+jn8Nfn9/fn5+f39/fn5/fpp/C35+f39+fn9/fn5+l3+EfgF/h36HfwN+f36Ff4h+oX+Dfq5/AX6LfwF+r3+DfoR/AX6RfwF+hX8BfoZ/AX6kfwF+pH8Bfrl/BH5+fn+Gfol/i36FfwF+lX8Dfn5/hH6dfwF+h38BfoR/g36GfwF+jH8BfqZ/AX60fwF+iH8Bfpp/AX6of4N+AgIEAICPkYuC9oH7gvj2hY/6g4OA+O/5kIn/hYKGgoODioKLhvmFi4uKkIuNhIWFgIX0gvbj/YeC+f71hJWLnKicno6LkIyW/YmRk4yJlJmNi4WBkauukoOEpcLNwKWNiZCQkqOXj4KEiISFjfiKhIeMk/yD/ouQ/fKDgvv/g4iDgeyN+4CCkI2LkIyNhI+QkI3/iIOGi4aJi4uHjo6RiYuFjZGaloOCjJGZoaeUk52tlJCSgYqNkYyOi46Jifv26OeDjISOjZCQhYKChYWDgYaPiYuJioeHiI6OiIuajpiUjomEk4+ClpGKk5atnI79hIH8hYeTi4uRjIGFgemFjZKIjpKFhICQiY+NhIOChpKOiICKj6qZpqmtjoCEgvz79oWI/YGEhYGHhf6GiIWMh4aHjouYpJeEhIOEgpGQ6vyA/oGB9YGCgoKUqZH9g4WQh4KIjY2Hi4uPiIaMj5OQj5STiI6Zh4eKk5SNjaWdhpKJj4+RkY2JkI6OmZWQko2Wk5qLkImHiYCJh4aRlpKUo6ST/o2Jho+Jj5CTlY3z7d/e3ujz/PmEiYX8iYiRmJianKaanaaqpqusnqmxtKunoJiinvnk6ISIlZaOk4aFjZCRlZSMj5GUjpWampqJgIaNoZOMjoWJkJOSlZKWkIeEopaPl5KQkuaCk5KPiY+Tj4+Mh4GYnY+RmICYiI+WiI2KkZKPkZORko+FjoiEj5KOkpKNjIqLiICBgYOLi4aKiYySoIX5gYKFgYP/gYODgYaLg4SFhIKBiIaGj4eCg9/yhv+G7/6BgJygnJKTi4qbnpOLlon7gIL8h4eJjomKiouFhYuLi4qIlYyIi4uSiYqEjYyWjpWOmJyZnICZj4eIgYmLjoyQnpaNiIr1h4+OhIeRjImGjZmP+/mRkYaWlJmZl5ONkpSPhYWHiIOIho2Lj5KIgoOKno+MjoyC+/7z6OL9hvmM+/GN/oWA9/+BgID27OvyzfeR+I2TlJCJpqWZkoWB8N71sZbJyOfm+uKH9uyYppeXprmwmpiYj4CVkIyUlJCNhvz+g4WDgf32+YH6/oqIgo2Hh4eflJCIkYyOjpuMh5iVlIyNmpWOi4+SkoSFj6GilJmSnJDwiZaTmf+TiZOYlImRmIaHj52ekoGHjZqQi4GTjY+RkJiQkZGKk5GMhY2K/IqPiZORjI2MgIaOiYGIlYOBiYqGhZSXoYCjlpD5l5L5+4yfjpGQmpqUk5mVjo6EhYiOj6CjkpObrpOMlpiOjY+loKSpn5etq6WmnaSskISDh4+Kkor+lpKKh5SVlJaVi4qZppmIh4+elIaFiZ2Ph/qFkZWno5OTj56io5qdkYz6j42Lj4uJiouNipKRk46QkYz7/5KHioOKiICCg4B65nzvfOnsf4jtfn567eXthn7vfHZ5eHx6f3qAfOd9gX99hH6Aen1+en/ofe3W7n987/Hie4l/ipKHjICAhIKJ53+HiH59i42CgXx4gZaYgXR2j6ewpI5+f4SCgpCHg3h9gHx6hOuDenh/hOh564KG8up6eenqeXx1dc986IB5hoOAh4OEfYWGhoHvfnd5gHuAgoB8hYaHgIN/g4WMint8hIaIkJSEhoqYiISEdH2EhIGDfoJ+g+/t3959gnyFhISDfnt7e317e3+JgYOAgX9+gYaFf3+OhIyJgX56h4N6ioSBhoWUiITxf3zyf32If36Eg3d8ed5+goZ+hYl7fYCHgYeDe3p7f4iFfXN/gZaIlZaZhXt+ffPz532A8Ht+f3qAfvB+f3uBfn18g32Ik4l3e3l9eIKB2el25nZ453l9eXaFkoHxfHyGgXt/goB9gX+Ffn6DhIeFhYeFfIGLfX+Di4yDgJKMe4eAiIeGhIF8hYeDjYuGhn6Gho+Dh4N/gIB/fn2FhoGGkpCB54F9fYWChoeHiYTo6dzY1ODq8ex8gXzjfnyGjIqJi5OIiJCXkZaYh5KYm5aRj4qTkeDP2n+Ci4yFiX5/hYOFiYqBhYeJg4eKiYx/e4CAjIWCgn2AgoaGiYaJgntzkoqCiYiFh9R7ioWDf4WKhYZ/enGJkIWFkYCMfYWNf4SBhomChYWGhYR5fn15hYR+hYd/f32DgXh4eX2FgXt9fYKGkXrqfHp+e33zeXx+e36CfH6Afnp4f3x/iIJ5eMnheemA2ux1b4WIiYCEfnqJjIN+iH7tenvtgYKAiIKCgIN5e4OAgYKCjoN+gYKOgoB4hYaLhoqCiImIiICFg3t+eoCBg4GDjIB+f4HrgYWEen+Hg4B9hY6E6+iEhHuLhoeKh4R+g4aCeHl+fniAfYJ+goR8dnZ7iXp9gH945eni2tPsfOV9491/6nt46/B3eHjq49/gvuqH24GBgX54kY2FgHd02szgnYi9vNfV6tV829mMl4iJkJyQhIWGgYCFgYCIiYSAeunve3t6fPft73zx9oOAen58fXyQh4V6hIGDg4yBfYeDhoCAiImCgYOFiHp7gpCThoeEkIbhf4qFi++GfYWGhnyBinl6gomJhnh/g4yEg3qHgYSGhIqDg4WBhoODfYWB7ISIgYqJg4KAdX6HgnmAiX18fn17eYaHjICNhIDhiYLm6oCNf4aEioqHiIqIg4N7f4KDg5CQgoiNmIWEioiBgoSVj5OXkImbl5SVjpOZgHh5fIF/h3/wiYSBfYiJio2Hfn6KlIt8eIOMgXt9fo+Ffu1+hYaYlIaHhJCQjoaJhILog31+gH19gIB/foKEhYGEhYDs7oZ8f3l+foBwb29rzm/Zbc/NbnjUcXJs09DUdHDWaWdoZ21rbWtwa8ttbW5qcW1taXBvbXDVb9fGz3Bv1trLbXRrcXVscGxvcm5yw250cmxsenpwcW5qa3N2amBlcnyCfHJpbXFuanNzcGhxcW1oc9VyZmVqbtJnym9y2NRobcnAZmhiZLJu0IBsc3BudXJ0bXJ0c2zPamhobmlvcWxqc3N4cnVwdHR2dG1vdXJtdXhvcnF2cHFvZm11bm9xb3Nyc9jZzcpxd3F2c3NxcHFvbm9tcHR6cnRzcnBwb3VzcW56cXt3cW1qc3Bqd3RwdHBybHbbc3Dbcm14bmpwcWtubctvcHFudHVscIB5dHh0bm9uc3Z2b2hubHZvdnZ7c3Bzc93e0G9z3XFydGxzcNtwb2pxcGxqc29xd3NlbGhwaGxqwcVlx2RmzWtuaGZpc2zZb291dG5ub25sb2x1bW9zb3BxcnJxbmt2a29zenpwbnd0a3R0eHVzcHFtdXdxeXpzcGtxcnlxeHdxclhwcW1xbmtvdnJow2ppaXFydHd1dHPV3tHLxs7W29lvdG3GbWp0eHZ0dnhxbXB4cHh6bHJ3dnRxdHR3ereuv3F1eHhzdW5vdW9wdHhwc3N3b3RzdHZwb3JthG8YbnJxd3d2cnhuZ113eHB4d3Z1wm55dnVxhHeAdWxidn10eYN8cHR7cXZzeXlvc3Z4dXBqcnNsc2xtd3dxcnJ9eG5paHB2b2lvam9zdGXObm1ub3Daam9zcXF1cXR5dW1tc25xenVpZazBZMNzx9BlWmRmbWpsaGRucm9tc27ObGrVdndxenZzcnRpbHZzcXJzfHJudHaAdXFqeHiAfHd4cXBtbGlrcGhwbXNycnNzdGVncXDYdHZ2b3J2cG5udndvzcRubW14b3JzcnBucHJvZGlubGpubG9rbXJtZmNlamFnbG1oy8fHwLvSbsJkubxsxWlo0NhjamvQzMTEqsxxs2pnamVhdGxoZmNgurPEiXerp7y3zb1mt7d1e3CAb3BzZ2Zpa2xtam1xc3BrZsvTbGtsceLd227c3nVxa25sbWx2cHFncHFwb3Nvb2tpbWxtbnVxbm1wdGtpanB2bm9ueG7GbHRwcdFuZ2xvbmluc2Zpb29vc2lvcXdxcW5yb3B1d3txcHJwcG5xbHVz1Hl5cXd3cXBzb3F6c2xwcmyAbmtra2Vtbm5ua2bAcGzMz211a3RwcnBxc3V0cHBtc3Jtb3dybnR2enJ0d29scnR6eHp7eHJ/fnl5dXp8amhqaWtsc23TdXBzbnN1eHpzbW90d3NsZG9yaWpvcXhvcNVuc298eXR0bnh1dG9tbm/Ib2pqa2tscG9sbGxwb290cm0IztJ1bG9mbnCEfxJ+f35/fn5/f35/f39+fn5/f36KfwF+jH8Kfn9+fn5/f35+fox/AX6kfwF+hX8Lfn9+f39+fn9/fn6EfwN+f36MfwF+q3+Efq1/BH5/f36KfwF+n38Gfn5+f39+hn8BfpN/B35+f35/f36HfwF+xH8Bfop/iX4Ef39/fpl/g36wfwF+u38BfoV/AX6Tfwd+fn9+f35+j38Efn9/frF/AX6Mf4J+on+Gfg5/fn9+fn9+f39+fn9/f4Z+An9+i3+LfgN/fn6Tf4J+hH8Gfn5+f35+qH8BfoR/AX6lfwF+m38Ffn9/fn60fwF+mX8Bfo9/AX6Rf4J+hn8CAgQAgI+OiYeGhIWDh/6C/4P+g4iLhpOOh4uRifSJipCOko+Lgv/xg42JhYODg4eIjISA8IDw6vT9+vKEhvzz7vn/gYWQlZ2inJ+QnpeLl42Sjo6fqaqJnrizooyRrqaWjZWljY6UiY2NiYuHi4qOku7w/oHQj4mGhYH8jIjy/OmFjIjjgIOPgYmLiYiGhoyVmpGRkYaJhYuKi42CjZCOkYyNiIuLjYeRkpmdlIaNp5qQj4SFhZOIkYyNjYTi+viHho6Ih4aIj4SCi4WDgoOKj4SE+4iFiZCOkoyRjo+WkJGIipWXjY+Ik42hoaKakI6GioaGhoWDgouElo2Igf6D/IKMlYmNgIn5hoWGg4mNi4+IjJWWoJubpKih/vmAioSE/P75g4CEgoKGhYmGj4eJj4yFiJaem4eHh4WOiJP7kJuFgYCBhYSJi4SPnIuNkIiChYeHjYmE/4OHk4qJioeIlpmbm5uIl5OGhpCVmKWSlImSlJCXkpiTjoWSjZORjo2MmJKSj4+KgIuQlZeNi5KTnoiLk4uViIyHh4mPlZOK/O/r79fs9u6DgoOEiYiGjpGTkZ2dnZyhpKCpoKqfmq2k7//t9f+DkJKIiY2Oj4uIi4yQlZCNlY6PjpKclJeNipSjl5SSnI2WjZKRmI2OjYqKkpiVjJOTj5CZkZKMjYKPjoiHnJKIkpiUgJCJjZGRkpSSkZCQioqIgoaCiP+GgfmXk5Ohq6qdmIuE9e+BhYyMj5iXg/rxgPT5/4CDiIOGhoWFgYeJhoWDg4uEjoCQg4SA9v6LlZmZlYyJh4qIjI+Li4eFioyJhPeGk4CGhYOIioyNh4WMgoiLjIePi4eMiYaLlquboaSdlpGPgImHioWGjI+NlJeagoWDhY2QjY2Tk4WJiYD+goyZmYOChJGWjoqQmJOUiY+JjIiJjIeMjY+OjoXm6YWYk//w+YiHiYiK/eyPk/fv39/f5NiHopX18eLug4KGmfqZl5Gao6Oah5P/iYX4/+PEmbG1vNbZ3NHe5oufv7Wro62jjpGigJedmJyXjYqMiJCDhYH6+/KAhIOHhICAkYSGgZeHj4ePjImHnZWPjpWUio6SjIWCg4qHhIWDhYyYlYuLiIWNm4mRiqGGlJCBh5CZi5CXmpmZlpGOiJGRjI2OmJmajpSYjIiHk5eOh4mMj42VjoeGgoeFjo+QioWPlpOSmpaSlaOagI+nl4CCopeOiZ6RkoebpI2VhoGRl46Rj4mOg/2LmJqin46QraCamqKjo6CanaajnJ2XnqKPkZWJjYyHg5Cgm5SPipGZh4L9/oKFjJCUmJOapp2B96G2npybj4WBhpGYlo+SioeVmpSJi/7rhJKYk4eNi42JiYmHjomHg4iKio6GgIGCf3x7fHp7gfR78n31fYCDfIeDfH2Ced1+fIGBhoCBee3leX9/e3p8fICBgHx64njq4+fw7eZ7ffLi3efte32Dho6Ri4yCjYR8ioGEgH+NlJV5iqKfk4CEmZCCf4SUf4OKfYGBfX55gIGChOHk8XfCg4B/gXjsg33i8N97fXvPgHeCd4CDg4F+foCJjoaHh3x8eX1+f4R5h4SGiYR+foGCg3yEhoiLhHp8j4qFhnx/e4Z8iYOHhnzV7+2CgIR+fnx+hHx9g399fHyChHt87YN9gIqDiIOHgoCKhYd/foeJgYR6g36MjpGPhISAg359fH56e4F4i4R9eu977nyCh3+DgIPtf31/fIKCgYaAg4mGjYmIj5OS8/N8hH588PDrfHt+fHt+foF9h3yAhYN9fIyOjH5/gICIfobrgot6d3V3fX6AgHd9i3+ChH95fH59gn9573h9hn+BgH59h4qLjI5/i4d8fIaIhZKEhoCGh4OIhIuHhHyIiIyGgH9/iYaGgoWBgIKFjI6DgISGkHuAh4CKgYV/gH+CioqD9Orj5NDk7OR+fH18gIB8goSJg4uIi4yPj4ySipOKg5eR1ubX4ex6goeAf4OFhYJ9goOGi4WBiYSGhIaNhYd/f4WQhoiIkIOKgoeIjYKGhoB7hIqMg4eHhIeSiIaCg3mDgnt3iot+h4qKgIV+hYiIh4uJhoOHf4GBfYB5e+h8eN2GhoCFkJGHgXp74uR+fX59foaIeerieenu9Hp8gn5+gX9+e3+Ef359fYJ6hHmGeX556/CBhYaFgnt8fH9+gIJ+f3l3fX9+eeV4hHZ7e36AgoaFfXqCeoCDhYCGhoCFg3+Ch5uKjI6Gh4SFgH5/hXx9goJ/h4iJeH57f4WHhISKiX2CgHv1fYaOiHh4eYOHgn2DiYeGf4N8gn+AgnuCgoN+gHrT1nqIhOfd54B/gIB+6tmAguHez83M18yBlYfl5dnie3p9h9WEhX+GjY2CdIHgeXfn6MqvjKesscvIyb7Q2oGJoZeQjZOMf4ORgIiMio2JgX6Af4V6fXry8uh6fnt/fHd3hHp8eIh6g3uBgn57kYiEgYaFe4GEgXl1d4B9fHx4eoCKioB/e3uCi3qDfI94hIJ3fYSJfISKjYqKiYaFfIOEgYGCj4yLg4eJf39+hYeGfoCEhoWLhX9+e4B7hIeGg3yDiYiGjYmEhpGGgICShHZ4kIeGgo+Chn2MkX+JfXqHioKGhX2BeOmAi4mOjoSFmI6JiZCNkY+Nj5SQjI6IjYx+g4l+gIB9eYSOi4mGg4eNf3vv63d7fYKHiYaKkYx35ZGij5CMg3p2eoaMiYOFfHmFiYZ9furfeoaKhHqBfoB8fX59g399eX19f4J8gG5sbW5tbGhrb9Zt13Dgb25xbXJwa2lsZbdtam9qb2x0atDMaGxtbW5ubW9vbGxuzGzSzczW1tVqaNXJvc3LaWtrbXR2c3Rqcm1odGxtbWxwdHZhbH19enBrdnBpbGx3aG9zam1uaHBob3JubMfM0WWqbmttcmbHcWnD08hnZme1gGVuZnBxdHJub291dXJycmpraGpxc3JqeXV2eHJucG90cGtucmxzcGlmbXFydGtwbHJteHJ4dG+91Nd4c3VxcG5tc211d3FwcG9zc3Bx03dxcHhxd3Jxb3B7d3hycHN1b3RndG5xdHd6c3NzdXFwanBpbG5pe3ZvbNNs0W9wcW9ygHXYdHFycHRxcXdzdXRucnBtcHR5395xdHRx19nWcG9xb3Bvb3Nudmpxb3JtbXd4dW9xc3J5bG/QbHFpaWVncG5sb2VjcGttbWxscW5tcG5r1WlwcWtybm5tc3Zzdnltd3JranNxaHVydHF1dnNycnV0cm5ydnxzbXBtd3N0b3FygHR6h4lzcHB0e21xc3N5c3dxcXFvd3Z04tzW1sjS2NJxb21vcnRtb3B4bHJvcXV0b3J1bXNsZXRysLWvvtFvbXZxc3JxcnJtcnJyd3VxdHJ2cm9zcHFpbm9zcXRzenR3cXZ2eXB3d25ncHZ9eHh2cXd8eHd1dWt1c2tpdnpueHt7gHd0eXt3dXh3dnR5cnZzcnFsbM5wbMFxc2pmcHJnaWVpx8x3cGpmZWpwZ8zIbdXX3m5xdHBtdHFycXZ3c3JycXRncWpvZm9qztFsbWtpa2hqamxrbmxpbWZjamxvastkbWZrb3Fwcnh5cGxybHF2eHN3enN2dHBveJJ0cnRrcnJ2gG9yeWpwc3BrcHJwZ29ucnd2dXV7dWxzc3Dfc3Z4bWZoZmpxcW1wcnJvb3ZrcW9xcWtwcnRram28vWhraMC7xG1ub2xsyLtoZ77Ata22vbhwf2/I0MnLb2lqa6RsaWJqaWphXmm9ZGXJxaqUfZWYnbSzrZ6wwGxtdW9pa3BqZ212gHFzdHNzbW1wcHNrcG/e3NJtcW1yb2hocGpqa3RqbmpvcXBtenJxbHFxZmtvbmhqaG1vcG1maGxvdXBrZ2pvb2NsaHRha25nbXBxaXN1e3NydXR0bHRzcHBwdnZ2cnVybHBub3J2cHBxdHV4dW9ubHNvdnVxcWxwc3RxdXVtbnJqgGlxZ2hldHB4dHVrcmx0dWt0bmpzdW5zc2tsZsZvdHBzdnJyeHZ0dXdyeXh4eHp2dXFvcm5nb3dta2xrZ3B1dHRzc3R4cG3UzmhtbHBzcnFwcXFoynp/dnd2b2lkaHF3dG1uZ2htc3Fra8rKa3J1b2htbW5rbG1sb25ra29vbG5uiX8Ffn9+f36KfwF+iH+Cfox/An5/hn6Cf4V+rn8Ffn5+f36Ffwp+f39+fn5/f39+t3+DfpN/AX6qfwN+f36GfwF+kn+CfoR/g36afwF+mH8BfsZ/iH6Zf4V+1X8Efn9/fop/gn6IfwZ+fn9+fn6Xf4J+lH8Bfrt/AX6dfwh+fn9/f35+foV/BH5+f3+HfoN/hH6Ef4J+iH8Dfn9/jn6Yf4N+/3+MfwF+qn+Cfot/AX6Vf4J+lX8CAgQAgIqQkZSSj4KHh4iGhYiVlJKTipqPiYaCio6aoqqB8pCYjpSKgfr7hoSAgoaGgoiHhPT/gIWDgPf57/SA7e/+iID7gOzn3/b1+IyVjJSDhoyokYiToZiZnY6Sk42js62SiI+PipCTkYeTjImD6ePh+/+I/viEjIOSgISKg4bs84P/gIOWh4P+iIuHmJqKhoiPioGEjI6KiYSKioKLjpKNhISQkpSdkouKj4WcnouKjYmZmJmPh4Hx9/uDgomJg4uKi42MiYaGjfSAjIOHj4WBiYmJjIaOipCPjZKOl5iQkZWOk5SHlKaYk5uQj4qKioL1goCBiYiSjKCWg+yCh4+Sg/iQgIqChYiIiY6HiIWJiJqjmpiNlK6vpoiFgIKF/oSD+fr1gPr+i4uIiZCPlYuPjJOWkIaOlIyRgoOChISm6fmXkYWAhICIhuePkpOGgfOVjIyGjIiEhIj/hYeRko2InJ2UjouGipCPj5qjnImIlY2KjYqRk5CWkJKIjZOQjZGMi5SQgJWUkomDjpSLjImOjo6RjImIh4WDiouKj4X97OXn4ubq8YD4gYX/hoeOl5ePj6Chp6Cfqp2HpqSOiPWHjoiSjo2KlI6JjJGJjpmQiZONkpGTmZuVlJKSkZial5ackJCPkpiTlJOVlpSQkJWTm4mWkYuNjo+NhYaIioeWnJiXjpCMgIiOk42Li4aGgPeFg//9+YCJj5ueopyoqJKZoqWvtqyjnIyQl46LjIWN+fXs/P39/oOFgYCFh4SDiIqBioSChYr+jIOKhICHlaSiloiJjIuHipCQiYiMhYqDhYmKiYiHgYP8+oOMjpKTj4qEh4qLjYuIi4+RjIuUm5+QkZCMjYORgI+ShYSKkpSLlZKTgYKJhouEhomIhIWG/oTvhJS9xJuCkYqUlZOVkYiJh4+FhoeHkIuEjpOKh4aG6u//gImFlYuJiY2Ejoz99JKAkZCK9/aGiuyGl/nD2fmYjpSSnKSim5KXlYX19e7Q2b2ZprO8v8bP1+/19J6/urKZjqiom5upgJOVl5CRmJqTiYiGg/yE+PqEi4mE/vSCiYD4+pGLiYWWiYuCqJGLkpGJipeRkIqEkYCMhoSLh5SIioaL+YmFjYyVhoCImKCPjpCSmZaLjZaWlZeQjZeXlJeQlZWTlZOFkomSlpCPmoyQipWNjYyMg4uKjI+WlI2Rj4+QkJSQnqazgKKUtJ6CkqaViYmelJiLlJiVnZ2dp6yGg4aFjI2JhYiGkYSKk5+UmZ2dm4+RioqLgvL2hYuRhouChJGkl/mBjomFhoyPjIyPjpKUiYqOl6CXnZyjmI2Rj42Wj5uZm5eTkZ6eqZCHmZaEl5eRkoyC/oOHioyMjo+OjIyJjI6NjImJgH+ChYaDg3p8fX98e3+HiYWHfIl/fHp4fX2LkJd14IOHgod9d+npe3p2eYB/fIOCfOPxeHx7eu/x5O554ObygnjreN3Z1uvj6YGGfol4eX2TfXeAj4mNkH6AhYGRm5aCeoKEfoSIhX2JgH9619fW8PB+7ed+gHWHeHyEfH7b43vtgHeHennvf4J9i4uAfX+GgHh6goSCfXp+gHd+goWCfXyFh4mQhn1+hXuKjoCAgn+LiYuEf3rk7PB6e4SCfIKAgoaCgH1+hed5iH1+hHt6gYGAgn2HgoaGgIaEjY2Eg4aCh4d6h5WIhYyHhoODgnjjeXh1fH2HgZWNfeJ8foSGe+uIgIN9f4CAgYR9gnyBfouSiImCgpeXlH9+enx+74B97vPsfOrwgYJ/gIWFjIOGgoeHhH6Eh4CGe3x7fXaV1+iGgnZ2e3p9e9d/g4Z9eeSKf4B7gn19fn/se32DhYV9i4yFhIB8gYaEg4yUkH9/hoCAg3yChYOIhYeAhImGgoaCgIiFgImGh3x6hIaAhIKEg3+GhYJ9f3x6gYGBiH714tja2t/g6XnneH7senuEiYZ9fouKk4qIlIt5jpGAe+R9hH6Ig4J9hYR+g4eChY+GgImDhoWHjpCKh4eIhYmLiYaNhYWEh42Ki4iKiYqEg4qFkICOhoSEhYOAfXt5f3mBjouKhIaBgHt8hYJ/gXt/eeV/ffLy6nV/goiLkoWMj3l5goOJkIyKiX2Din9+gHqD6eni7/L08H1/fHuCg356gIF5g4B9foDwgn2Be3qAiJOJgHV6gICAf4OCfH2Den92eH59fHt+eH7y4n+Gh4uKiIJ9gIOChIR/goaHhICFjI6BhIN+gn2HgISJe3qBi42Ch4SFeXyCfoN+gIGBf35+7IDlfoijoIZ1f3mGh4aHhX19fYJ7en9+hIF6g4N+fn582+Hqdn55iH9+foV+hILr24h3h4Z75+h9gNp/iuS0w+SFd4CCh42Kh4GEf2/d1dC1w6qKnq21t73FzODq6pCfmpaFfZCQh4eTgIKEiIWFjY2Ign9+eu9/7fJ+gX588ed4fnfl54J/gHqGfIB3loR/hIV+fImCgn16hXeBe3uDfYV+gH2A5H17f3+Gd3R8hY+BhIWHi4qDg4qIhoiEhIuJh4mFh4mHioh7hn6HjIaHjoGGgouEg4OEfYGChIONiIGIhIWGhYeAi5KagI6EnIx5g5GHgIGQh4qAiImHkJGQlJZ7e3x5goaDfX9+h3yBho+Hi42OjYKFgX5+d9/hdXmAen94eYKRieZ1gX58foKFgICDg4aJe32CiZCIi4yRiYCGgXyJgYyKjYqKhYyMlX96iIV2hoaDhYF553R6f4KBgIOCg4R+f4F8f358gG5scHFvcWpqa29sbG9zcW5xZ3BqamlqbGhxc3hmwW1xcHRsas3ObW1mam9vbm9wbsvWaGxvcNjWydFtx9DYdWrRab6+wdLPzm5vbXRnZ2ZwYl5kdnJ4empnamx2eHJnamxwbXBycW13bXBruMDA1s9t0c1tcGd2bGx1b23CyWrJgGNvZWnRcHNudHVvbXB1cmlwcnV1am1vcWZscnRwcW92dHN1cmpsb2hxc21xcm10dXNvcG3L1tpwbXl2bnVydnlxcG9yeNVvfXRudHJxdnZyc292c3V1b3Rye3pyb3Jyd3Fpc3l0bXV1cnB0dGrPbWxkaGt0c4B5bs9vcXJxbdN2gHNwc21ucnNtc2xvbnR2cXNtanZzenNzb3Fw1nVx2eHUcc7cdHJzcHJ0e3VzcnNwcW1yc3F2bnFubF5yvctua2BlbHBpabtqam5ubMt0aWtqcWtvcW3RbGxvdHZpcXF0dG1rc3d0b3J8eHJycm9vcG5zdnB3dHVvcXR3dnRyc3d1gHl2dm1reHVvdHR0bmlxdXJvcG9sdHJweHLj1cjDytLV2WrRcHLSa2p0cW9wZ29ud21sdXJnbnNoZMZrdHJ4cnJucHNudHdzdH12cnVwb3Fye394c3V0cnRwcW50b3F0dnt3eXN4enZzd3xyenV9c3R0cXNzcW9qb2lod3Z2cHFtgGxobnFoa2tza81wcNfc0mRwb29scWRqaVlXW1dZYGNob2drcWloamdtzNDM2Nnh2XFzb253dnFxdnJudXJxcG/VdG5vbW5ucnVnYWRvcG5zbnJwbGxwam1lZmtnZ21uZ3DZxm9ydn57eHVycXVzdXRxc3l5dGxvdnhqbm5tcnB2gHN6bWtxe3t1cW9vbHB1cnVwcnZzcG5w1XPQcHR5cGhhaGd1dnBwc2xubG5sbG9tc3BpcW1nbG9swcTCZmtnbWxqa3Jvb2/NunFncm9myM1qbLttdcaep8RoW29nZ2tqaGNpZlW1q6WXqpN5kKKrq66xtsLP0HV0bW5saG9vbnB1U21tcXFxd3Z0c3Jwbdhw295tbmxw2c1mZ2XKyWpqbmttaXBrfnJvcXFtaXNtbGprc2puaWpxa3Fucm1qy2xsaGdsaWRobXdtc3N0d3dzcnd2dnNwhHSAc3Fwc3Z2eGlvbHR5dnh5cHZyenJzcXRvbnJ0cHlzbnhxc3JycWlucnRwbXhxbG11cXFxenJzb3J0bnp6d3l5a2xrZ3B2dG5ubHVwcXN5dHh2d3ZwcHBtaGW8uGFgZ2duZ2drcm7HY25ubXBzc29vcXNydGpsb3N4cHBwc3Nucm8paHNsdnN0dHVzbHF4a2dub2Jub290b2rIYWptc3Ftbm9wcmlsbGxwbmmdfwF+hn+Cfop/gn6Ef4R+CH9+fn5/f35/hn6jf4V+A39+fol/BH5+f36EfwF+r3+Dfo5/AX6nfwF+in8BfoV/AX6bfwl+f39+fn5/fn6Yf4J+iH8BfoV/AX6JfwF+w3+IfgV/fn9/fpN/AX7PfwZ+f39+fn6af4d+kH8BfqB/gn60fwN+f36ef4N+i3+CfoV/B35+f39+f3+EfgN/f36Jf5F+l38Efn9+foR/B35+f39/fn6gfwF+9H+Cfop/AX6xfwF+kX8CAgQAgI6OjYaSiYqRnJqF+oaSj5jGm77Co4yEkoX1+pOGgPzqgIOGg4uGhoiIgoSAhPGAgvTy6/6Ag+/+hIGChoOH8vX5/Pzq7IDzgI2NmI6FhbKsjIebkZKOkZeWh4qL+YGRnKCZmJmb4OLv3bza7+b29YD87PiCiP7/g++KiYP7+OzugObw6YiCjomUl5iKiIGFi42Jioj28oOIjIqQhomMiIqQmJb2iY2Piougio2QjIyCg4iCgvqAiIuFi4mLhfqNjJCJjIyNhe+ChoKCiYmLjYmD+omJh46NkIySkZGVn5eNlJaOo5SFmKSMioyMjJGSi4uPioqQiYaJi/mBhouHhIWJgIyJh4aAg4eGh4SMkpiWkpuPm6G1saONhoKBgvHwgPzu+IWDh4eDhP/4hIeIk5efkIWFhoKDhIeDi4aOipT6ho6tlfSGgff4jZGjmPmChoeChYiIjp2Ch/qDkIyTjpCdkI6LkfqHj5CbnJmUjo+MiI+PiI6Jk4mHkYqEiIOInIiOgI+Jh4qNjIiYlYeNi5GPkJGLj4mPjYOKhI2UjYuC7uX15uT19v+DhoOFioWFjJaJiZmei4+hs5iFhf+Rk5mGipSPhoSOk5KOjY2Rjo2PjIiPkoqOlpeLj5iMlZeZl5aVipCSipGZlY6MiZaXiomSj4yNkYaLhIWOioCPrp6alJeUgI6WkoWLgv2FgoqSj4uOj5yemJqalpihnYWRj4qRkJuhoI6Ul5aTiJiWlZGFhYOIgIaChIr++4P8+f//h4mCl4yTjYeHi46WqKCQiYGBh4aEjYiK/oeIiYmHhYP7ioiSnJ6YiPyHi4qMlouKi4iJhY+KiI2Qj5iSlJmTlJGSi46KgIWIkYmLjpiempWH+ImKjo6Mj4mMh4H4+IGUrrqqlamhiIWKkouOjYuHiIqH/4eHiImNj4yMh4j88vyQhuf1gJKKhICEi4eKhoD68ufk9pf7/uWMmqKdh9jpxuuXmZKL/pH/g+vt1tG/sammv7vHytfg7ebx/abKsKSclpacpJydgJKNlo+Njo2HioyJh4SB9/qFi4OAgIKOlomWj5SZjoyJmoyGnZKDjpaCk4qHiImCi4uL/YaBh4uDh4iWl5OJn4yYlJ2Vg4yZk4qKjpOdlpOTlJibmo2blZSQkZCXj5CampaamJGTk5eJhI2NlIWFjo2OnZKMj5WdloiQjoqJlaKjgLShpbmglKGymI6VmJiTkZalkYyFhJWgjYiLkIGF+YWEjIaKhpSSk4WJj42Oi4aLi4Hwiqetq5yLgfuYpJCcpaG+kYuXkY6Rl4iFhIqFiZCQkZGQlI2Ij4uOh42WlomSjYuMipaMmp6QjIeGmJehl4yOkI+Ji4aAjJOOiouUjo+QgIKEg3uGfX+BjIp34HuEgImtiKOminp0gXrj6IV5du/bdnh4dn9+e3x7eXp0fOd5fOjk4PJ3fOXvfnx9fnl+5Ojq7+3a2nrreYSBiYB4eJuUeniMhISAhImHfIKE6HWBh46FhYmLy9Dez7LT4dPo4nPr2Op8e+Dset6Bf3fk4ODkgNvd2n96hICHiYyBfnp8goSAgYDt6Xh6goCEf32DfoGGi4jbfoOBgICRf4KFgIR4foB5eet6g4F9hH+CfumFhIeAhIKDfeF7f3x7goKEhIB654GBfYOEhIGHhIWJj4iBiIiAkYZ7iZOCgIKEg4aHgYKEgH+Hf3x+gu17foF+fX5+gIOBgH96fX9+gXuBhYqJhoyEi46dmJGCfXx7fOrne/Lq7X19gH98fvLqeH9+hImOgn5/fnp8fX98gn1+fIfleHuWieyBe+jjgIGSieh2eHt6fH59f415fu17hoCGgoKOhYaChe17g4SOjImIg4eGgIaFgYd/iH+BiIF9e3V6jX6FgIWAfICEg36JioCEgYSDgoiChYCFhX6Ce4KIhYR95+Hu393q5fF7fXp8f3l6f4h9fIaKeX2Klod6fOyEho18f4eEfHyEiYWDg4SGg4KFhH6DiIGGiYp9gIqBiYiJiYuJgIWFgIeOi4eCfYqMgX+JhoOGinyBfX2DfnV6k4uKho2LgIKKiICDeeV6eIWLhIGEfomLg4KChIWKh3N9e3Z5c3uHhXuChIOFe4mHhoSAgHuAd396fYT283/y7/PvfoB6ioGCfXt6foKGk4t/e3Z0fX58gXyB63x/gYKAfHrofXh/jJCMffB/g4WGj4SDg39/fYiDgYWIgomDhImHi4eIgYSAgHl+hoF+gIeOiYl96oCBhIWGiYGEf3vw7HmIl5mKeI6MeXl9hHyAgX17fYB+7n18foCCgoCDfX7r4u6Eds7gd4Z7d3V6gH1/fXbn39PT54vm79qCjJWQesHNsNmGgnx54n7acNDUwMGtoJubt7bDxNHW4Nrk7ZGlj46JhoeJj4mLgIJ+h4KChIN/g4SCgX998u99gXp5eXmChn2KgYWHg4N+i4B7joZ3gYh3hX17fYJ4f4CA6359f357f4GKioZ8kIKIh46Ddn2Kh4CChoSNiIeHhYuOioONh4qChYWKhoqQjYuPjYeKioyAe4SCint+hoKDkIiDhIiNiXyEg4B+ho6OgJ6QkZ2Mh5Odh4GKjIqIh4mXhoJ7d4SPgX5/hHZ77H18gn+CfIaEhnyAhIGDg36Cg3rkfJGWlYl/eOuEjoCJkI2lgYKKg4CEiHt8en17f4OBhIOAhH99hYGDfoOKi3+GgoF+eoR8iIuCgXx6iIiOhXx+goN+fntzgIaAf4GJgIOEgG9zbmpwaWxrcnNju2htaW6TbHt9bmRfa2jFyXBnZtLBZmZlZW1tamtqaWllbctrb9LMxtNpcc/Qb29xdWxvzs3S08/CumzUa3JwcGxkZ3ZxYmJ0bm1rb29xam5zzWFtbXJpbXZzqa+7t6fFx7nQymHMxNFtaMHPa8JvbWXFxM3RgMjCxXJsdW5wdXlxbm1vcXNxc27Y0mdodHFwbm91bnBzdXLAcXNub3F0bHByb3Bpbm9qbcprd3FvdnBzcdF1dHdwc3dzbdBxcnFvdHN2c3Jt1nJ0bnNycnB2cXJ0dnNwdnNtd3Bqc3VybnB2cnN2dHdzb2l1cW1rctZrbnNwb3BvgHNxcnBscHFucmxub3R2cnRzdHF5dXZwbnBvb9TUbdza1nBxcW9ucNnWZ3Bub3V3a25vbWtvcHFucmplZm/DZGBycdx0b9LFbWp3cchlZGdpbGxra3Nqb9BtdnJzcGt0cnRudNVpb3J7dXR5c3d1cHVydXZxd3FyeXNuamZmeHF0gHJta2txc3B0dXF4c3NxcHp0cHJ3d3JzbnR2d3Zy1dTY0M3UzNttbWxtcGVpbnJrZ2xtX2JrcG1oa8JscHhtbnd6bm1ydHNwdHRzcnNycmxyd3R4dHVobHRudXJwcXh0cXFwb3V8e3h0bXl4cm97cXJ2d2xycWx4b2NecXJ1dXl6gHN3dXJzatZvbXV3dnNxaGpsZWRnaWlsaVxiYFhWTlZiYV5nam1wZXNzcG5wcm1vZ29vb3nk23Ph3t3Xbm9oc2xqZmdoanBscGplY2ZkbHBucm1y0WtvdHNycGvHaGJncXR0atlvcHZ2gXZ2c3Fxb3Z1cHF3cW9vbXJwe3V2bXRqgGtwd3ZwZ3B2cHRx1XJycXJ7eXFzcXHi2Wx3d2leVWpsYmdra2hub2lqbXBx021qbHBwbGxwaW3LzNpvYqi/ZXBoZ2VrcG1tb2TIvru8xXHD0MBwdnd1Y6Cnk7NrZ2Bgu2anVq6woKSUioqLqqu5tr/DxsXHy3J0ZGtubnBwdHFwgG1qcW9wcG9wdHRzc3Nx39ltbWdqaWltbmtya3BycnFocG5qdHNobnBncGlpa3BnaW9vz29vcGxscm9ycXBrdm5ucnduY2lxcW5yc3N4c3N1cnZ1c3B2dXZvcG9zdXd7c3d6dnJ3d3pxbHRyd21vdXBueHZyc3F0dWlucG5rb3BwgHt2cnRtcHt7cWx0eHNycnF8c3BnYmp1b25rcWlt025scHFxbXFtcG5ycG5xcW1xc2vLZW5vcG1tac5naWRrcnB/am90b29ucWptbWtrcHBrbm1qbWpscW5wbHF1dGpycG5sZWtnb3JwcGtocXJ2bWlqa3Rua2ljbm9qbXB0bXFwi38BfoR/AX6Ifwd+fn9/f35+jX8Dfn9/hH4Ef39+foZ/h34Cf36VfwF+iH+Kfg1/fn5+f39+fn9+f39/h36Qf4J+jX8BfpB/AX6IfwF+iH8Bfop/AX6nfwF+on8Gfn5/fn5+hn+CfpR/AX6EfwV+f39+foR/AX6LfwF+i38Bfrl/iH6UfwF+zH8Bfq1/A35+f4R+mH8Bfod/AX6HfwF+p38Bfop/gn6UfwF+in8Hfn5+f39+fot/hX4Ef35+foV/hH6EfwR+f35/kn6Zf4J+on8Bfup/AX6TfwF+h38BfsZ/AgIEAICSjJKOjo6XlIiLitqNo7e6oZCZnI6NlPjg9OrwgJmQ/P6BjJOGiISJ/4aIhoKE+O7y+f2CgP6F/4L7gPb1iID3//3wgOTlgpGWlYuPj5+5hviIkJOPqpeamIyNkoiBhIGMi5elmoj3iIKJj5WJiJaakYqNlYn4gYKNiICDiYmPhYCOhYGOhpGNkJCJjYeLioX1g4T9h/uFhoOEjo+Oi4mJlZGKk46Tkp+IhomJk4qKiISJgIf3homEgomNjI6Ogu7wl4aKi4yE/ICCgfmDjISGgYWIkZKYkpKSj4uNmY+PmpWepYuHi5ukjoeNhY6IkZOQiYT9ho2Il5eI+viBio6AhoCAhIaFhICDiISOipmRlZiWmp+VnKOnmYf87v2CgPn0/fuB/P/6hfv8h5COnKOZlYuQlIaBjJCJjIWPkY2Omv/7jKHx+PiKhfqKmZ2ThICAhYv9joiUhYqMhZKNkImYhoqIkJCUjI6RkIuRlJeTl4+MkYyPipSOiI+Jko2FjI2OjYCRg5WOio+PlY2Nj5iKi4yIio2GiZCFhoyNi4qNkpWGiIX894D58PXy8eL3h4D395GO74yfpJGOi42OheKckY+Ki46NiIuEh46PkJGSkI+JlJKOio2QipmJko6YmpyTjZiSlJSJj5OHlJSJi4uP/o+IhpCZjoqIhImMg5OVmJCRk4CMiv6FkpCOlJCempejpZ+Vj5GWmJKQl5SNh42AgO7noqeoko6QkZCYkY2Qko+HjoT4g4aA7P35/IyQi4qNio2Og4CGj4KSraiQhoGEgYKBj4iLgo2G/4KGgoSCg4b98oOIh5KFhYKGgYmRk42Dh4yK9IuOlo2ShImKkpSOh46KlICOjoSNmJiflZCOiYuLiY6KjoqHgoP6g525vrKkn5abl6WYjY2LkImMiYqOiof//4T/+vqFiYL38oCB5dz3g4yDgY2MiYKNg/iC/oGB5/qF27fS8/OVn5uloI/mg/PtiYHnxsizs7G1ucHGwMrDzNL2gIWFio6suKaVnJ2en5eRj4COj4mOg4SHhYGA//z6+Ovv94uXh5SLkZKRlZWakZCN/ZKNlpqMh5OXiJCPmZiVi5CVio2Kk5ycnJeZn5CnoZuQk5ebkJCNkpiDk5CJmZGRjo6WmJmbkIqVko+CiJKRkImNh42NkpCQj46Sk4eHg4eRjI+YlpWVhIyLnJmTkZiPioCFjqWiopaioqKcmpeWkomBh5eJg4+bhZOGj5OJiICG9YGJiIeAiI+O/oWHiYyB/4eB9oDt0YyTjOn7kIfz7f+Ci5uipKeah4f7goiJk5aUiYGC8oaKjIyOmJeTkZCQjZGOl6eqk52OlKGgn46Hlp+MkZCHio+JhoKAhY2GlIqRlICFgoqEg4CJhnt7ecF+kqOjjH+Ki3t8g+HR39vbdIiC6ex3g4V7f3h+73x9fHh65+Lp7/V5duh68H31eObrfnno9vDieNzae4aKiHyBf4ujeOJ9hYeAloSEhoKCiH92dXV+f4iTinvigXuAhYl9e4aJgX2BioHpeXmBenZ6f3yCeYCAeniFfoWDh4aAg31/f37oe37oeuh+f3x7hISEg35/iYOBh4CFg5B9eX19hH9+gH6Ce4Hqfn99eoCFhYWEedvZiHt/g4V863p8eut7hHx9en1/hoaLhYaFhX+Di4GCi4aOkIB9f42Wg3yAfIV+iYiEf3rreoN9i4uA7+t5gYZ6foB6fX9+fXp7gHyEfoqDiIiGipCGiY6SiHvt5PB7efDr9PB68e7ofO7ugISDjZCLiYGGiX95hYZ+gn2DhYKAi+bnfJHm7OuCfOp+h4yHe3d3fYLugnuGe4GCfIWBhX6HeH16hYSFg4KEhYCEhoeGjoWDhYCGf4SEf4Z/hoF6gYKBgICDd4iEg4SFiIKAhYt+gYF+g4N/gIh9gIWBf4KAhYp+gH7y7Hvw4+jn49bmfHbn5oWC4oGOkoSDgIKEe82Ng4OAgYGBgIB6fIKEhIWKhoWAiIWBgoSGfYt9hYKKi4qDgYqHiYmAh4l+h4iBhYSH9oiBfoWOg39/eX5/coCBhYKGiICCg+1+i4aBhIOTh4eRk4yAfYKFgn2CiYN9eX1vcMq8iImPfn2BhIKNh4GGh4V+hX3ve4F95vLu74SGg3+AfHx/d3J6gnWClYt5eHZ7enl5g35/eIN99Ht9eXx5enrj23N3eoZ5e3iAe4GHioV7f4OD4oGEioCHeX5/h4qDfIOAiYCDgnyDioiLhYKDfoGCgIWChYKAfX3vfY6anJGJh4OIhZOEf4F/hH1/foGFf3/x73nq5eh6fnnr5Hd41NLue4B6d4GDf3iDfOV57nl62ul6yafH5eGFh4eSkH7NdNfUe3fLrrKjoaanrbW8uMS9yMrreH5+goOPmI+FioyLi4WCgoCAgX2Bent+fXt89vTs7eLj54CKfYd/hISFhoiMhIaE5IKCiYyBfIeJfIKDjImIgIOHgICAiI+SkIyNjX+Wjo2BhIeMgoODhop3h4N9iIOEgYSKi4yIgYCIhIR5f4aEhIGEfoaEh4aFhYWJiX9/f4CHg4eLiYaIeoKBi4qEgoqCf4B6gJGMjIWPjpKOjYeJhoB4fIh+eoOLeYZ7goJ9gXl+6Xd/f312fYSE7319foF48H555XbXunuBf97pg3vf2+t4fYmPkpOIe3rleH1+hImHf3d743t9gYGBioqIiIWEgISAjJSTg4x/hZGPjoB7iI5+hYF7gIJ+fXh4e4J7iH+Eh4BwcXpycGx0cmdmYKJlcH19a2Rwc2Nja8G8x8HBYmxs0NBncnFtcGVs0WltamhqysvM2N1uactq0nDda8rMb2zQ3tXFZ8XFbHF1dWlsaGp9Y8FrcHFpdGxoa3BtdHJqZ2dsbXB4cWXFc25xbXBsaG9wbmpueHPPamhxamRpbmptaYBraGx2cHNyd3Zxb21vbG7NbG/KatFzcm1sc3NycmtucW1xdG5ybHNraGlsc29scXB1bG/ScnRram92dXR2asG5c2xxdXdy0XFybcpqdXBwbW9xc3V3cnNydG5yd21udHJ2c2trbHR7cGtxbHNxeXh0cG7Qa3Vsdndy2NVrbXJqbYBsc3Fvbm9ucG1yanNtcnFtb3Zsa29yb2rSzNxvbdnU4d1t19LQatPRb3Fxc3Z1dHR2dHJtenhtbm1tcW5pccPBYXPQ1tBzbdBtcXNxbGhnb3PRb2dxbW5wb3Rvc2pwY2dmcHBwdXBvcG5wdHB0e3V1dW91b21yb3VweXFpbnRxcIBtZnJwdHNzdnFyd3xscnBwdXZxdH10dHpyc3Vuc3hwc3Pe1XHZy9LPzMfNamjMy3Nvx25zdG5ycXNwaax1cHJ0c2xwcnFqanFycHJ5dHFxdXJydHNza3locXF2dHBqbnR0dXhwdXZscHJwdnN033xzbXR4dXJzb3BsYGpta3J0doB0dtFzfXdwc3J6cG10cWliZGpqaWducmxnZWldW6CQZGRrYGNpb253c3B0dHFsdHDWanN02dzW2HV3dHBtaGZqZmFnbGJqcmRcYmRrbm1qc29uanVu3WxvbXJsa2bBvF9iZm5jZ2VwbHl9e3dtcHNxx3BydWtyaGttcnRvZ21tdYBzcXFydXJtb29wbnBzcXRzdnZzcXPacHVwbWRjZGZubHRnaG5scmptbW52bm/Y02jBxdBtcGvbzWdptrzXbXFra21yc2lubMhrzmxrychlq5CvyMJqbWx2eGaoW7K3aWSmkJSKiZKUmKOrqrawu7rPaG1tb29obW9sb3FvcG9tbYBsbGptamtvcG1w29jW1MzKy291bHNrbnBwbnFycHVzx2twcnBsaXBxam1udW5ybm9vbm5vc3R6eXp6dGV0cnVrbnFybnBxcHVodnFvdHFyb3R2dHZwa25ybnNtcnRvcW9xbnVxdHFzdXR3d29ycnB3cXZ3dnJ2aG5uc3JubXRubYBmanJtbG11dXp2dnBwcXFqaXJtam5xZW5qbWhsdW5w1GZrbG5obHNy1G9sbXBp1HBsyGS2omRpbMfFamO9u8VmZm5zdnZxbGfHZmtrbnR0b2ZsyGltb29tc3BzdXJvbG5td3pzbXVqbnV0dW9ncnJqcW1pb29ubWlqa3BqdWxtcot/AX6Lf4V+BX9/f35+h38BfoV/hX4Mf39+f35/fn9+fn9/hH4Df35+in8BfpV/AX6OfwF+mX8Gfn9/fn9+nn8Bfop/gn6GfwV+f39/fqd/AX6Gf4J+nX8Ffn5+f3+Efgd/fn5+f35+ln8Kfn5/f35+fn9/fol/AX7PfwN+fn+Hfgd/f35+f39+iX8BfrF/AX6UfwF+mn+CfpF/BH5/f3+Efp1/AX6Hf4J+kX8BfqR/AX6XfxB+fn9+fn5/f39+fn9/fn5+in8Ifn9+f39+fn+FfoZ/Bn5/fn5/f5B+mn+Hfo5/AX7/fwF+iH8BfoV/EX5/f35/fn5/f39+fn9/fn5+iX8Bfol/AX6tfwICBACAlJ+dm6CimZqblIL8iY+LlZ2a7ObX2fuBhpGNj4L5jomG/IKJhoyIhv6D/vfw34WMgYj+ipKE+//5+oeCg4P89feC7u/+g4mVlqGf84KPgPmSjpWdjYz79fD1h4WIhoeBhoiDgu7H7YX/i4yFiouMjYaVlpKKk46AgYKC+v6Mi4eAioqIho+JjY2OiY6Qi4eIg4iAgIKElZCF/P2QmpOLh5SQioqRm42QlpedlouMlYX8gfaGgoqFhYiNhYaRjouRl5GEhIKEg8/og/75hISMh4mVjYuPkpOTjpKdkZOTmp6uk4WHkZKbmIyMh5KKiI2Pj5WRjpCGgoaKjImGgfuGh4OAg4eC+IiGioyDjJ6alZmknJyalJ2Toq2fmY6VloDq7P7/gYCBgIiAhoeMk5aMjJWahYKKiJSJiIGHkpmYh4eLkoON//+vkfvf7YKSkZOai4P/nY32jImQiYyRjoSOi4iKkZCKioSLjpGPk5WalpePk5KakI6HhoXqkJeMhY2KlZiAlJmWkYyNlIyRkIuTlouFhImKiIeKjIaKioyKhZKOh5COj4uQk4+SjIiIkJeLj5Sbl5WYl5GMj5GLjo2Rl5CNjo2NhYb8h42LjYeIkpKVj4+GjoeLmpGOkJOZhpmKjY+RkoyQjYuJhfqVh4qLmoeQhfaIgpKKk4aIgp+SlpaIjomAioiFjJSNjYyVlZ6Onp2NiIiPkYmRj42MkIL4+evWwvKHm5yLj4uRlZGPj5SNi4+NjY6OlY6Pjo2OjY2TlY+J//qDio6OnKWKhYKDioaFif2C/IeNh4aBgYaAgIiB7OmEgoyOmKin/oCDgfyBhIeMhIWNk5uKh4qFhYiNkIWJhoaAjZCWmZuUkIiOioyJhIqNhIaDgPSAnLGssaeZmI2TkI2Xk5ePjI2Mg4SFhP6GiYaB+oaE+Or2gN3e2fuCjpaIgYePiYmJhYaFiYOIgOHXvrrZ+oadl5CWkY2QnJiUlIyGjYD79u/89PuLhYuLh4OMk4+MkYedt5SboJeZqKSOhImAhYmGhIeE/PuB9ejp6vD394uKlJulnZqUkImEjo6Ih4SUjJaRi4SH/ITx6o6Pkv6Xko+XmpeckIiJqKKShpSekoSQm52WkImQj4+QiImAj5uSj4KIpZOMiJGUiPWWmoePmJmNlJWMiY6Rio+WlZmdmp6glIyUkpSQkYyOjZGYj4yAkZaIlKWonZuUkIeTlY6HhIWLg4KGj5GTh4KH/oH/iILy6OWFi4CHgoTx7IaOjoaF/oOAg/7tmor5hPT5/fTx+u/wiIOAiIeA84SEhPuDhoWGioeJiouHiYaIgouC+YWLlZSVma66p6OYjKOajImVnaGX9viBjZOYioCC/5OTi4uAh4+NjZGTi4mIg3bofYKAiY+L1NLEwuN1foeBhHfhf31753l/fIN+fOh57eviy3qCen/tgoh87fTs7X15e3rr5Ot64Obwe4CJiJCK2nN/c+aGgoaNfHzm5uPkfXx+fX54fIF6ed634H3tgoJ9gX+FhH2Lh4Z/hoN3d3l66e2Cg36Af359fYZ/g4OCfISFf318en95eHl5iYV87OZ/jIp/eomGf4CEjXuBh4SLiXx/hnvneOV/fIJ9f36FfnyHhoOFioh8fn1+fsjgfPHqfXyAfn6JgX+Dh4mJgoaPhoaIi42ehn6AhoeNioKBfYaAf4KEhI2Hg4R7eHt+goF+ee2Agn6Ae4B55X5+g4N7gI6JhYiSjo6Lho2Bi5WOiIKGiXnl5vb0e3x6eX95fnyBhod/gYiOfnqBgIuAfHh+hImHfH2AhHeA6uKZhOvW4HiDgoSMg37xjILrg4OEfoGGhnuDgX2AgYF/f3mCg4eGhIeLiYuEiIWOhoN+f3vRgYh/eoR9h4mAh4yIiIR/h4CFh4GHioJ6eoF+foCBhHyCf4R/e4eCfoWBhoGGhoKFg4F/hYyBhYeOiYeLioSAhYd/f4KFjIaDhISBfH/rfoOCh4F/hoeJhIN9hHuDkYeDhYaKd4h+f4GDhYKDhIKBe+eKfoKCjH2Feed/d4R9h3p/dIuFiol6gHqAgIB8gop/gYKMhol9j4qAe3mAhH2AgYR/gnTe49W9p9Fzg4h8gX6Fi4eCgYiDgISCgIOAiIKFhIODg4GDhH995+Z4fH+AipF9eXZ4gHx8fu177HyFfn97eX54eoF629h4dH59h5eV6nt/evF7e32GfH2EiY99e356enuAhHyBfHyAgoOLiY2Ggn2DgoR/fIGCeX18eep7j5mTl46GiH+Dg3+Gg4iCf4GBeHp7euZ7fnt36H174tnmd9DWzvF7hYqBe32EgYKCf4F/gHuCdM7Is7DF5n2Lh4GIhYKGk42IhoB8gnbr6uju6fCBe4KAfnqCiYKBiHyNm4GLjoSGko+AdnuAen58eX178PF77+De3eTr54J/houVjImGg396gIJ/gHmHgouHg3t95nnh14GBhe2JhoOHioaMhH99ko6De4aMg3qFjo6Hg36CfoCIgH51hI2Ig3l+loaCfYeKgeqLiX6IjY2Bh4qCgYWFgYaJio2Pj5KShX+GhYiFh4GEgYSKg4CAgod9hZGSjY6JhXyGiIJ+fHuBe3l+goSGe3h77Hfuf3vm3NZ7gHV+eXzi4n6ChIB/8Hp4fO3biXrqd9ri7Orl6tbaenZzd3p24Hp8eu59fnp5f359gIJ+gH6AdoB55XyBiImKi5qjkZCHf5GFfXuGi5CH4ul5goOMf3h47oeDe36Ac3l3dXd8dXBta2HBY2lsdHVwsrOlocBlcXRrc2m+amppy2ptbXJsa85szs3JtGhubnLUdHht0t3S021sb2zNytZtxtDVbW9zcXNxu2BkX8ZxbW5yZmW9xcbKaGpta2tpbHBpa8Oey3HRcHFsb211c2t0cXFubm1lZ2pryMtvcW2AbG1tbnNvcXJva3RybmtrbXBtamtrdnRqzsJoc3NqanNzbGxwcWVscW5xbmdsbmrLa89vbW9qb3BybGh0dXNvc3lwc3Fwcb7TcdfUcG9wcXJ4cG5ydXd3cHJ5dHJzdHF+dHBycnJ0dHBva3NvcG9wcnl1c3JsaGlrcnNvbdZydHOAcHNmxG5ucXNubHNyb293dXZ0cXJoanBycG9ydW7X097ebXFrbXBucWxwcXFqaXN8cWtub3hxa2tvb25ta21va2Vu0bt1a8vFymZtampycXTWcmzNc3Vzb3BycW1zcG5wa25vbWtwcHd0cXFzd3hxd3R6d3Jwb2iraHZta3ZqdHSAcnVxcnJtc3F3eHB0dnRubnJtb3Nyc3B2cnVubHVycG9udnB0c2tzcnFucnlxcnF2cm5ydG5sdHNvbnFxdnNvdHJvbG3Tb3RxeXVucHVzb3BxdGt1fXZyb25wZnBna29ucXBucXNvasdybnVscWtvaNJuZnFsdWpuYnJyeHhscGl2cHRsc3pwcXV7cG5jdW1qZWNrcWhsanFqbGLAy8GgiaNXZWljamtwdnNtb3VxbXBwbXBtdHBycXFycm9sbWptzM1qbG1rb3FlZWJkbmpubtdw1G95cnRwbm9sbnVtxLtlYmlla3RywnB1btpva252a2xxdnZoZ4RpgGxvbHJoa25wdXB0cm5rcnJ1cG90b2dscG3UcHh5cHBoa3BpbWxscG10cGxubWpsaGjAaGxtasxubcW/z2S3xL3cbnN0bGtscXB0cnJ1b3Bsc2OwsqKgqMRucHBwdnFvbnl3cXBmZm1kyszLysrTbmpxbGxrcHNucHVpdHNmcnBmEWpydG5kZmdtbGhubNfbbtnIhMaAyG9pb3V7dHF0bm1qbGxvcGdzb3Zvb2xtwGfDvm1na8xzcm9wcm5xcW9qcW9ubXFybWp1dnJsbWxvam51b21kb3JybWhse3NybnN3cdV1cGx5endwc3VzcnJybnJ0cnh1d3l4cG5wcXVzdm90a3BzcWxqbmlwdnNxcnNxa25ybm9xbWtubWlvbnBzampozmnYc23Nxr1sbmdwbG3M0W9vc3Jy12trb9K8b2PLYbG5y9LOzLS4ZGJhZGhoxmlras9tb2tmaWxrcHFsb3FuZm1sxm1wcnd3c3t8cHNxanlsa2hxcnlwxM9pcG91bWpq1W1saGyLfwF+hn+FfoZ/BX5/f39+hn8Cfn+EfoR/BH5/f3+EfoR/B35+fn9+fn6GfwV+f39/foZ/hH6KfwV+fn5/fpJ/gn6bf4J+lX8Dfn9+lH8Ffn5/fn6xfwF+hn8Bfpl/hH6hfwd+fn9/fn5+h38Efn9/fqN/AX7MfwF+on8Bfoh/AX6pf4Z+n3+Cfo5/A35/fot/gn6HfwV+f39/fqh/AX6XfwF+hH8Hfn9/fn5+f4R+kX+GfpB/hn6efwN+fn+Hfpd/CH5/fn5/f39+rH8Bfr9/CH5/fn9/fn5+hn+CfoV/Cn5/f39+fn9/fn+IfoZ/BX5/f39+kH8BfpR/gn6HfwF+hH8CAgQAgJSOjY6OiYyRiYOCjfn5gPqO8sXb5fyFiYSDjo72+P6BhIuEiv6Kh4SIhYGBgfj9+4iJi4KGiIaGiYiIgPuBjIyB+4CLlJKqq6annYeah5CXl5WH/f6OipSWkJmRj4mKjP+JjOjX6oT6iY+DgpGGi4yCg4eVh5WJgfGHlZmRhIaHgJCSh42Rh4qXmJiJiICCi4SJhIOFioaNlpeeloiH/IyTj4+Xm5eRjISIjZeRhe3U7+j26/qAhoaGhYeHiIGKkJiSi4iNiYWJ/4GGk4b5i5OWi4qKj4mQk5WanZuNkpSUl4+Nl52MlaCOjouI8viHjpaQiIqKgYWPk4uBgfTv/YKIgImBg4eIjI+K/pWVlJWPjJOToZOchpispqGNgY348viBhPbv+oL9iZaMjIqTjYCbl4mRgYiKhoSEh5qYnfGMioCFhOqLovzs/fqMj42Klo2LgIP/h4eUko+Kj4yOi4aMh4eOlJKLjpCWjYeMk5qZmJaLhoiNjZSLi4qRj4aAjJCbgI+LnZ6Mg4iQjY6Ug4mJloeNipGNj46Nj4uMk5eOiIiMj5KVmZyUlZKHjpKYjpGTko6RlJSQlo6LlJudmZeclYyZjJWTkYaHg4KEi4mHgYWNk5SKiZCOgIqLjIuOkYuLl4GQhpCBiJaaj4OFh4qGo+yDiY6OjI6OiJmZioeXlIuMgJWTkouQioqNlJeTj5ubjI6Sm5aTlZCMjoqE5YKF5oiQioWSkI6Ll5SKlJOWjI+Mio+LjYqJkJiQk5KSkoiH/I2JjYqVkaWWkYaCgf39h4eFhISFgoWF//bq8oj59tzt4/6IgOqGi4Dwg/H7gYKEi4GGiImXioGTjYqJjYKGjomMgI+TlJCWjZGNhoWFiIaDkIeG+++AnKqtq5yTjIaaloyOjpGRjI2KhouPhf6HhPXv7oD67oH91rfZ+5OTjYmIjIuMi4+LkYqFgOro2drX9ISNkZmcoJSQko6PjZSJk4+Zl5ecoKWnr6mZloyPi4aAjo+Bh4mDmbWYg5ikoJ6TjIOCgIiB+fT2/fj17Ozv7vOBh4ehp6OOiZKVi4uHg4L9ho+EiYuOj4aGg4WTgPvd4N6CpJ6Kk/r8kY6KlaCtmoyKmJqXj46QlI6TkouOmoyMhYyWlJSRk5Wak4yGnZODkpKJioSMlY6LipeQi5CdlY+HkJiXm5ePkZSQj5KUj4iFiY+UgJiRj5KTlp6jlZWKjo6TkpSCgvOLkY6N/4eKiIb7+orb1O7rgIjy7YeKgfH+g4mIh4qAiIiC9vaDgIiFloja6+jp9/Xm4urrg4SGg4OIj4WDioWIj4CGhIaBjImFh/z3+YSHkJKVmqKqoZqPlpKJhoyNmamim4b+hqKjnJyck46OgIaCgYOCf4GFfnp4hOrreemC2LHJzed5f3x5gIPg5/B4eYB6fex/eHl9end2eevw7n97f3p9gH99gX59dut0gIB36nmBiIWamZGTiHiLeYSMiox+5uWDf4eHgoqEgXx/gep9gtjQ5X7kf4R3e4Z9gIF5enyJfYp+eeV/iouEeHt7gIOGfoSEeX6IiIp/fnd4gnp/e319gnyBiIaSiX6A54SIg4GHiImFgHZ/g4p+eeTL697s3+l2gIB7eoF/f3qBhYuHgH6DgX2C83t+iX3ngIWIgIF+g4GGhoaLjomBiIiEjImFkpB/hpCFhYJ+4Ox9g4mDgH+BenyFiYF7fO7n7XyAgIF4e31/g4eC64SGhoh+fIaHkISKeoWVkI5/doTs5el7fOrj73vufomAgoGIg3WGh4CHeoKCgHt8fYmIjd5+f3l8eNB8kejh8u6ChIN8hoGBeXrwgX2HhoB/hIKFgHyDe3t/h4Z/g4SJhH6Ch4yIiYh/e36Cg4iCfX+HhX54gX+LgIJ8i41+eoCHgoSJen9+in6DgIiHh4SEhICCiI+Ef4GDhIeJi42JiYV9goiKgIWJiISGiIeGioF+hYyPjIuNiYKOgIiIiX1/fXt8g4J/eXyAhIR/f4aHd4KAgHt+hoB+ineDeoR1eYGLhXt+fn1zkdl7g4OBfoOAfIeJgHyJh4B/gISFhX+GgYKCiomFgouOfn+Ci4iGhYCAhH94zXJ0xXeAfXmDgYB/i4d7hoaLgYKAfIB+gX59g4qEhoaDhXp45oJ/gn6Hg5CEgnt5efLxgH9+fX18eXx98ujf437q6tHczumAddFzeXTee+Duenh8f3d+gYCLfnWGgIF/g3t9hoCCgIKDhICIhIiDfHx9gH18hX198ON4jZSVkYiFfXqMiH+CgYWDf4KBfoOEe+99e+Tk4nTi3Hjz0LDR7oeIgn59goSFgoSCh4OBed7cy8rI7YKFh4uMjoWCh4SHg4l/ioOOjYiIi5GOlZSGgn2DgXt2gYF3f4F6ipuEc4SQjIuEf3h3gH967uro8Ozq4eDf3eR7fXuRlI+DgIKHf4J+eXjqfoR3gICEg3x9eXyIdOnM0tB2jod5g+XrhYN+hY6YioB/hoyIgICBhYGHhYGCjYCCfIKJiImFhoiMhYN9kYp9iImAgHh8h4OCfomDgIaQiIJ9hYqGjImEhIqDgYSHhHx7gISGgIeCg4aFhIyThol/goGHh4h2eeCBh4KA6HyBfnvu7oTLyuPjeH/l5H59eOHxfH+Af4F4f3545uR5d313hnjL3dze5N/U0+DdfHt6eHd9hX16gH5/hnh9fn96gYF8f+rr63x9hIiGiI+TjYqAhIJ7eoGDipWMjH3ufZCPjIyLhoF+gHBwbG9va2tuamhnb8rKZsRqr5KytMhnbW1mbHLHzdZpZ21sbc5wZ2ltaWdmbNfW125nbWxvcXFscG9rZ9Rmb25o0W1vc215eXR2b2JvZ250cnVqwMFva3VvbXVvaWtucMZobbjA1HbIbnFqanRubm5oamp0a3ZrbM1xeHRuZGtogG50bHFvaG1ycnRwcWpqcWhubW9wcm5vcnF6b29zxnJ1b2lwcHBxcWNrbnFoZsW92MzTxs5rc3JtcHVvbmpwcXRxbnB0cW943W9xeXDQcXF0b3Fscm90dHJ0dXFteHRwdHl1gnttcHV1dnNuyNRucXRxcW9xbW9zeG9scNvT1G5wgHJqbW5ycXRy0XJxcXJmaHF0c2xvaWtzcnBpZXXX0NFtb9TL2m3RbXZub29xbWBpcXN2bXRzc2tua29vcsNsbWlsaK1kd8HG2dJvcG9pb21wbGrScm5ycm5vc290cmxxbGprcnFub292dG5xdXhzc3VxcW9ycnZxZ291dG9ocWlwgG9kbXRpa291cnV5bG1veHFzbnR3enRydnB1d356cHFwcnZ5eXp0d3BubnRya3J0cm5zdXJ0dG1sa3J5dXZ3d3J6cXV1d2xxcG5uc3Z2bW5sbm9sbXV0aG9va2ZocGxodGluaHJkZml0cm5xbGlbc7tucHRwbXJua3F2b211dW5sgGtrcG15cXN0dm5vanFwZWdrcHFwcG5wc3BmsmJdoWFnZmdsaW5rdHNqcnV4bXBtZ2lqbWprb3RxcnJwcmllxm1sbmtxanBlZmdobNvXcnJycW1tamxv3s/GyW7S0bnBsclsY6pYW1u7aL/Qa2pvbmptb291bmZxaG5vcmxrdG9yUm9rbGpzc3lzaWlsc25tcGxv18hrfHdzcG1uaWdxbmtub3FubG5ubXJxadFqZMDMy2PCx27cv6O91XJzc21scHR0cHNydnBzaMLEr7Gx03Z1dHWEb4B2cHlwdmtycXl3c3JwcWpscWppaXBtamVta2dvcWt1eGpgZ29vb21sZ2dtac3LzNHQ0MbCwLzGaW1sd3lzbm9tcG9wbGlozm5zZXFucG9rbWlrdWDLtbi4YWxoYW3EznJybW1xeG5rcHN2cnBrbG5tc3JzcnVub21vcnF1cnN1eIB0cm12c21zdG1pZWdvcG5tdnBvcnlwbWxwcW5zdHFwdnFtbnJwam1ucnJxbnFzcGpwem9zbGtscXJ0ZGjAb3dybshpbG1q1ddzsbjO0WpvzNNxa2vM2G9xcnFua29uaMfEaGdoYGhhtMLFxMa8uL3NzGtsaGhjZ3Nva25vb3Rraipsb21uc2pvzNLLbGlxdnFsb3RybmlrbWhqb3J1eXF0a9NvdXNydHNxb2mMfwV+fn9+f4V+hn+DfoV/AX6If4N+jH8BfoR/AX6Rf4J+i38Ifn9/fn5+f36QfwF+pH8Bfo9/h36TfwF+hH8Bfp5/gn6Of4N+in8BfpN/Cn5+fn9/fn5+f36WfwF+hX8Dfn9/hH6JfwF+/3+dfwF+qn8Efn9/fqB/AX6Mf4J+iX+EfgF/hn4Kf39+f39/fn9+fqZ/gn6Xfwp+f39+fn5/fn5/hX6Pf4Z+tH+Lfo9/AX6Nf4R+hX+Cft1/AX6EfwF+hH8Dfn5/hH4Jf39+fn9/f35+iX+CfoZ/in6Wf4N+ln8Bfol/AgIEAID6g4mNgvL/hYSF/fHr+ezxj5f55uX9gIuYg4eMhoqDg//8h4n6hIWA/YSNhv6QiomPh/2Dg4WFjIiJg/6C9vyHiIaIk623p4uGgfiAjKWjopeRjZKD/oSFhYXwgv6JiY78/t7V+/XtgYiEiYuMiYiGiJGNk5GFhJOVh4CIkpSLiICPl5+bopCTjImJjoKFhIn47YaNiomKhIiF/IiJkJaXl5mXiI2FjZORj46UitzR3/769oCFhoWEgIeRhYGJjY2Pj4qPkpX57v2AgYuUjIqHjZiQi5CPmJqVioyPjYiZl5KJjY+Rm5mPkI6SkYeIh5GQi5KJiJL+hYOJhob8g/r+/4CFgoKJiYuQipGSkIyNlpybmJOJjfyPkZuboIegpZiF9o+Jk/iPgIj+g4T9+4aLo52KmImCiIWHi4L8ioaPhYuS9vOuiOnw94aJj/uCipSQk5mWk4yWh4qDkZOKhIyMiY+MiJGSjI+WlJKFio6XlJipl4SBio6hiIiFi5OSg4aGiID9kIullJCEj4qIioqUhoWPi4CIjoyUjoWOi4yJiY6OkY2KkYqLjpeSjo6LiZOPj5CSjpeUko6UmZKVmZeWl5OKj5OWjo2MjIqNi4uFjZSJjYuJiZSIi4mHhYKGifmL/Iucj+2D/4KOs5eW9v+Dj5Oa9IuMi5KPjIiYpqiknKyfnoCrrKqvsailkpaKipGVmZGHkoeIj4+RioHz5Mfou5WHioWHm5eSkpaMhI6OkoyNkY2QjIOQkpKbl5OLhP7+gI2TjZGTlZiljIWBhIOEkIaEgYeFg4D++u7r/Pf+3tTGv9+G//32+/r059nRlIWXoIeJj46SlY+Ji4qGjIWJk5GTjoCQkYiHiYqEhPOLhP2BiZKFg/fwgqKup5eUlZSblJmajoyHjIiFgYOHiIf/goH+ivXu+P7p6oaFjISPhIaG/ob+hIPm59jFwd650PGJjoqgpa6VkpCJlpqdnLSHlp2PlJSRo7qxqJyQhIeGhYqMjISDhoWCgP/7m66ej4T+/Pjx2hfs7+ji6+v09/eFkI2OoZaRn5+NiIeFh4SRgI2IhoOHh4mLhISCguCNge3h6IagtK6GkJCOiImAio6XlpaWgpeZiY6Glo6PkpD/i46K/u+EjZmUloaMkZ6fmZCKoZOQiI6OoaahlI6PiZSMl5mTnoyJoJ6cmJmUjYeUk4yNh4eGlJiTl52Ni5CjmpGRjZCIm4SHh4yJiY75+vyDZYPs5fXh1u/0+PDw/fvvgfn3+uCAj4GDh4iPko6I+uX0/YL6hfvygez9koaDgYyIhu7/gYeOh4r+h/+C+PD27fHo/YWGiILx8oeNnIWDio+ThICGjY2Ng/iAlp6XkZ2biv2AiYWAgOJ5foJ56PN8e3zr39zm3OKEiefY0el0f4x3e4F4f3t77ep9gOh8fHfwfYJ554SAgYZ77Xl4e3yCfX558Hzj7X5/fH6GmaCSfnl46niAk5CPioiDhHbyfH58etR67H5/gujs0M705Nt4f3p/gYSAf3t+h4CIiH15hYV7d32FhX59gIKIkIuQgoh+fX+Fenl5ge/kgoOCfoF3fXnof4GEiIuIi4h8hX6EiIKFgYaAzsbT7fLteXx9f314gYh7eYF/gIWFfoOEiOXg8Hp6g4mDgX+DjISChYOMjYh+gYB+fIuKhX6Dg4KMi4WHhoqHfYF8hISBh4B/iO5+eYB+ffB87PDygH18fIKChIiAhYWDgYGHjI2IhHx+4ICBiYaPeIuRiXzog3+I54Z4f/F7ffDrfn+MjH+LgX2DfX+BffOAfIN4foXl1pd72+bpgIOH7Xp/h4SFi4mHgot+gHmEiIB8hIOChoN+hIaDgouIh3yBg4mGiZWHe3qAg5J/fnuCi4d7fXt9gOuDeo+FhHt/gH9/gYp+eIWCeYCFhIuEe4OBgoB/hIWGgYCHfoKFi4aBg4OCioKDhoeEi4eGg4eMiIiJiIaJh3+EhomFhIKCfYOChH6Ch3+Eg3yAinyFgoB9fX2A6IDlgJGE33nqdHmbh4bg7XeAfojefYCEioWHfISPkpWKloqHgJKRkZWWjo+EiX+AhYKMhXqCenqBgoeAd+LYt9CngXR7eXaLioSEiYF5hIOGgIOHgoJ/doKFhYyIhn556+11fYR/hYeJiJF/e3l8fHuEfX17gXx8evDu5eHy6e3NxLWwz33w8eXj4+DRx8GFdYSOeHyDgYOKg4CAgH2FfoCIh4eBgIKFf3+Bgnt95n966nmBhnl56uF3jpeOgYKGhYuHiImAgH+BfHt5e32AgO96eu2A5d3p8NnbfnuAeIJ1fH3pf+13d9LTwbW1z63G6IGGf5CQlYKBg3uDiYuNp3uKiX2Dg32Nm5CLhX51e317gYGBeHd8fXp58eqKloh8dubj5dzHgN7h2NPi3eXo536EgYORhYGJiX59fXh8hoWFgYJ+fXp8en2AfHp3ediEd9rS23uKlZB2gIKBfXt0e4CHhoWEdYiHeoJ6iICCg4PugYOB8d96g42JinyEh5GSjoZ+kYeGfYSDlJWPhoKGfol/i4yDjYB8joyNiYuHgnuGhoGCfX58gIaKhYmLf3+EkoqEhIGFfY16fX+Bfn5/3uboeHnc1uLRyOLq7t/g8e3ed+rn6tJ2hHp8fH2DhoF+6Njk4Xjpferiddnnhnx6eoSAf97teH6DfoLqf+167+Hq5OfZ73x+gHvn6H6AjXZ2fYGBdnR7gH1+eeV1iYuHg42Jfud2gX12TsNpa21r0dZsa27QyMTAvb9rbcXBtcZkanZjaHBlb25u08tsctBubWjSa3Fnx3Rtb3Nq02pna2xubHBr13HK1W5uaWtve313bGdnymxtd4RzgGtnX9VtcG1sxWzJaW5yyc60utrMzGlra3BxdXJtbG90bHRzbGlxcGZnam9ubGtucnVxc3F2ampwdGpmZ3DVz3Ryc21vaWplz25ydXR1cXNvbXNvcnNqcW5sZ7a/xdXd3W1tbHFraXN0aWx0bG1wcmxva3PKytdvcHd6dHJwcXdygHFycnd4dXBxa2pseHZycHV1bnR2dXZ3eHVucmlscXF1cm510nNrbm5v3G3R0tdwb25zc3J1cHN0b2ttcnNyc21nar9qaW1pcWNtd3Ru0m9rcslzanHZbnDa02toanNud3FydmtwcnLYbWptZmdwzLJ0ZcDR0m9wddJrb3Nub3VzbnV0dW9vanN4b3F1cHJ0dG5yb3FsdnJwbXBucnFyd3BxbXBxd29ybnR5dW5tam7Tb2JxcHFrcHBvb3B5dW16cWltdHh5eHBycHVybnR1dHBwdm9ucXZ0a291cXlvcXV0cnZwcnByeXRycXFwc3NrhHKAc3Fvb3NzdW5wc21zcmtvdmx6dHFtbm5uy2vEa3lwxmnPZV94bWzCzmZqZm7BaG9zd3N1am5yeHltdGlmcWxpb25rdW9yaW1vanFvZnJtaW5udG5qycWiq4lkW2NjY3N1cm91bmd0cHRucXNsamtmbXJydXFwaGfN0GVnb2xvcnNXb25oaGhubW1wb3BvdGxvbdXVzsvWy8qzq56ctWrR1cK4ubWkp6VrX2hvX2pwbW9yb2xtb213bHN3c3Bwb3Btb29ybm/MbGvQa3BwZ2nQymZzdWlmbHBshHCAaWtubWlqa2xscHLSZ2rQbsXAydDExW1qa2dsZGpsym7PZGSxsaSZnLSarctvdGxyb29kbm5nb3BxdpVqc21kam1nbHNkZmVmYmhubXFubWdna25rbNnTdHRnYV+5vsO+rb/Et7G8tsLEw2tubXJ3b29xcW1tbWVsdHFxam9ubG2AbGpwbmlqbGnDcWS/tL9laWpkX2pqbGtrYmZpb2lraWRybmlxaXRvcHJw0G5ucdzGbG95cXNtdHR6d3VxanNvcGhycnp5c3BxcWtya3Z2bHNua3Nzc3F1cm9ncXFtb2pvbnN2cHRybG5ydm1vcGpxanRqbG5vbW9uzMbDZGjAu8FhurfG0dPHyNfUx2fRz86+aHNsbGtrb3Brbcm8xr1pz2/NyGPCxHFtbGtybW3Gz2hub25wy27La9fKzs/Rv9FrbW9uzc5ua3BgYWlqaGNka2xpamfKaXJta210cGnIZHBuZwF+hH8Ffn5/f3+GfoJ/hH6Kfw1+fn9/fn9/f35/f39+hX8Bfoh/BH5/fn6LfwF+in8BfoR/Bn5/fn9/f4d+qH+Cfoh/AX6Sf4Z+k3+Dfq1/AX6FfwV+f35+fpR/AX6Kfw1+f39/fn9/f35/f35+jX8BfoZ/C35+f39+fn5/f39+tX8Bftt/CX5/fn9/f35/foV/gn6EfwF+p3+Ffp5/gn6Xf4x+AX+Jfp5/BH5/f36Ff4J+l38Ffn9/fn+Gfoh/BX5/fn9/iX6pf4J+hX+Ofp5/Bn5/f35+fpx/Bn5/f39+fsh/BX5+fn9/jX4Bf4R+in+Efgh/fn9+fn9+fod/gn6FfwR+f35/h36Ef4J+j38Bfoh/AX6EfwICBACAgoH1g/aAg/qFg4L2/ff05fechoz+iIaCi46JioD8ivz29fyFiYmEiISChouXloqMg4WIg4aJjoeDjYWGi4eEgYGDnbauhP+Pk5SYqaukn4aGiYCA94WJj4eYjoyPi4LgzOPzgYCF+4mHnIiHgISRj+r9gI2GjoaIm5aUkpaenp+Ar6Caj42PlP6QkZKQkuj5gIPp+YqEhf6BhImQl6Gwj4aQj4eMjJKikoqNmPTj5er58fP+gIGCh4iIgYORhoWFgImHjY2E/f6CgIGLlIqSjpWUiI6Ni5GMjYmMjpeQlY+Um5KTl52Mho+IiI+TjoeOiYyKjIyBgYCGh4aDiYWB+YCAg4eJjY2MjoaJjY2JkZWYgoSTjI+Pjp2msYmI64uZhfqG84L+gfL9g4SFhYuPjJ6OjYv9gYD/iYP7gYuGjZCLi/GJiu305vCMkZORkoyKg42QmK+uhoGEgv6Ei4iLiYWHiJOXjpCPh46MkImKjpORm56Uh/+Oh4aKlI6JiIiNkJCAk42XqZecj5iFk5CKlJeQhfiGiJeMhYSOj46Bk5CNj4qUj5KOl42SmoWMkZOQi5GGi4qMj4+WlZGUmZWKk5GalZ2Rk5mOio6JjZOMiomNjpSMi4SJh4SPiJCKnJyPo6KbkZ2gnf6OgIqmh4uOjJCWkJiPi5STkoKQpaKhkZKZpKiAnZ+pqLWvtK60nImMjJKBiYyQj4iIhYv6hJyF4fCYoJiPi5KKjImSj4iGhpGKiY38jJCRjI+PlJCB/4WHhIKWkpWYlKebjouOh4T2iYqDgoLx+/r89feCjPnf++fB0tv8iYualpeYi9/L6YKW8YuKiYKMhoKMio6IhoqBhouLjYOAgP6Gg4WIgIeEkfuGiZiFiIjz9pKxs6eNmpqRkZSUlJGIiImD/oD2hfvw7O/47uze7+7k5eX75+Xt6v7i2dS80MnSyvPq8oH6/oGWnZimqv6MkpmwpZ+XlJiSiY2WkI+SlKqtpZiQlY2FgpKPioyJkIiEhPbs7+6NpZj95PPt5+eA/YWFjZGNkp+XmJaPnpSHj4mZiYWQjpCOkI2Mi4mIgoeNjYX8goP24ZOJgfvu8fGG7/mEnY6IhouEjYOYmZKIk5KUjZCSioaGjY2Eg4iE+YP4iomEjYido7abjpquh4+Bkebs9ZmWiYPxiZSQlqemmZGPipWXkZKQkJCKi4+Pi4qAg4SPj6Oak5WQnZORi4ONkJWMho+Znoru1NPRz87K7Pzj5tne7feGhIKA+ff1+Oz/h4yiiJqMkoeJg4SHjZCEhPP8+oOGkob/iI6GiYT25vH13uT//fH1+Ozi8vLp7oWD/YCHjYn7j5aSi5aOh47/h46Gh42Qh/f78f7/gvL++v2Aennmfep4fOx8e3rk7Ofh1+KMd37nf316gYJ+fXXngOvn5+98gYB4fnt5fX+Jh3+De3x/e31+g315hHt8gn57d3l5jqCZeO+DhIiJmJeSjXh9gHZ6635/hXeMg4OGf3vVvtjieXp96n52jHx9cXiGhd3tdX97hHp4i4iEf4KMjouAmIuKgoOChuWDhoWFhtnreHva6oB8fOt6fXuAiJCbgnqDgXqCgoWRhIKGi+PZ4OPq5OTweXh7fX2CeXmFfH57d4J/hIN77u16eXqBiH+Gg4iFe4GDgYWBgn+ChIqBiIKFi4WIiY+BfYB9f4WKg3yEf4SBhIN6eXl/f315f3157HqAen2ChISEhnt9gIF+g4qIdHKDf4GAfoyPmHt81XmGeO5/6HvufOfzfHt9f4CBfIuCgoPwenjzf3rwe4KAhoiAf957ftvn2+WEhoaEiIOBeoOEiZebf3t/ffN9gn6CgX5/foaIgoeGfoOBhH5/hIeFi42HfuqCfX6BioF8foCChIWAiYOFk4WIhIp5hIN/h4uFeuZ9fYiBenqEhYZ4hoSChYGKgYmEjICEjnuBh4iGgYV9g4CDhYOMjIeHjIZ+hoSNiY2Eg4qEgIN9goeCgICBhImDhHyDf3+HgIiBjY2AjY+FfIWLieqBdHaRe4GEg4WIgY6FgIqJhXV/kJGRfn2HkJCAhIaOi5OKko+XjnyAf4B5fH+Fgn59e4HveIt3yNCEj4mCfYaAgH2Hhn98fIV9foXogIKDfoOBhYJ37Hp8eXeGgYSFgpKKgH+Ef3rlgIB6fHzn8PHx6+t8hevR7t21xczqe32JhoiJgM2103aG0np4enR+e3eAf4J+fYN6fYKChH2AeO5+e36DeX54guV8fYx8fX7k6YKXl418iYl/f4SGhIF9fn968HbpfO7m4OPt4N7P4NzW29bp09TZzefPysOrwr/FvOjf63/09XqIjIiOjdt7foSWjYx9goiDfn+EgH+DgpCPioaBh4B9eoWBf4F/gn16f+ji5OGEloThzdzY0s+A43h4foKBhpGKi4mEjYB6hH6JfHiDgoN/gn9/gn9+en2Af3zvfHzr0oZ9duba29R00uR5h3t7eX92fnaKh4R8hoOJf4eFf3x6goR9eoJ86n3uhYB6g4CTkaGLgomaeIN2htPa34iEfnvkf4iDiJOUi4OBeoaHgoOCgoJ/gISDf36AenuDgZCHgoaEjYSCf3uBgYaBfIKKi3nWvsC9wsK63ezY283T3+19eHx56err69rzgYGTeop+gnx/eHl7gIF2eOPp6Hh7h33tfYJ6f3vm1t/o0dfv5+Lo7uTZ6O7k4X146nZ+gX7tgISEfoaBfIHofIB6fYB/fePu4O3weubw5+mAbGrObsxpbs1tbmrK0cW/uchwZWvIcW1qbW5tbGXKbs/OztVucnJobmlrcGx0d25wampwb25tcW5tc21ucm1nZWpsc3p4ZtBudHNxeHZzdGRsbWVnyW5vc2h5bXB2bGy+qr7Gamtry29eeGtpXWd0cMjTZGppc2lkdW9sZWVvdm2AdW5xcHBsbsJvc3BwbrnQam3CzXNsbNNub2lsc3d7bmlvb2htcW51bnRycsDDz8fKzczVaWhrbWx0a2p1bnJrZ3BvcnBrztZrbm1xdG50cnZyZ25ycXRwbW5xdHdvcnJwc3J1cnhxbW5wbnB3cWlvb3Nxc29samxxbm1pbW5s1Gxsa3FycW9wc2pqa29tanRwZmVvamtoZ3FwcWNmslxrZtVy0GzRbs3acW9xc3BrZ3FtbXTPamjWbGvZcHNydXduabpkZ7fGwcx1dXBtd3N0bG9ucXV9c25ybttub2xwcG9ycHN0cXZ3b3Nsbm1uhHGAdXFszXBucm95cGxtcXJ1cHZyaXZwcXFzaG9ucnF4c23Lam5zcWhqd3J3anV0cXZwc3B5c3dqbHhucnN0eG5ubnRxdXBvdnpxcnRtaHBveXFzb212b3FzbXBybW5xbW91cnNvdnZ/fXJ4b3BxaGxtZGBlbWvJb2JdbmVycHFzcXGAd3ZydnVzYmdydHFiYmdvamBiaWdlX2llcXJqbnBsaWdscG5sbGt02WVyYp+oaHFwbGZwcG1sdXVuaGtuaW5zymtsbGlvbGxoY81oamlmbmhta2lwa2hqcG9oynFybW9w0dvd2djYb3fRtczAnau3y2Via2prcGqwp7NfZ6NeXmOAXmhrZWpscW5xdmtscXJzbmvSbmtudW1wZmjCa2p1a2xuy9JqbWtnYmtsamdub3Fpam1va9Vjx23WzsXLz8XBs8nAv8a7xrGvsaXCra+klKamrKjNx9d129hrbm5tbGqtYWBnc2xwYmtwb2xpbGtsbGdrZ2psbXNubmx0b21vbHCAamhv09DPxm52Y66jta+qpbNhY2RobHN5b3RzbnZsaHNvdGhkbm1ubG9qanBta2xsb3Btzmxu1sBvaWXDuK6pWqi9ZmtjaGpqYGdhcW1ra3JvdG1zbm1qZ3B0b2twbc9v1HNvZ3Bud3V/c21wdWRrY3Gzu75ubGxqxWxybnJ4eniAbWpmbGxqbWpucG5tcW9tbGxrcm51bmtubnRvbW1qbWtua2pvcnRksKCkoausq8HOwcG2usTRbWhvacrM0tbC1G1seGVwam1rbmVkZWptZmbIyshmaXBqy2pvaW5rzb7D0bi/2srPztTOyM/WzsJpZcdla2xoz2htb2pua2ptw2oQbWRoa2lqwtTM0dBsytPKyQt/f35/fn9/fn9/f4Z+BH9/f36IfwJ+f4R+o38Bfo1/AX6Kf4R+BH9/f36Jf4J+lX8BfoV/Cn5+f39+fn9/f36Uf4h+kn+Cfrd/AX6cfwx+f39/fn9+f35/fn6Lfwd+f39+f39+h38Dfn9/hH6RfwF+mn8Bfpx/AX7TfwF+sn8Gfn9/f35+kn8Bfol/AX6QfwF+hX+GfoJ/iH6HfwZ+fn5/f36UfwF+iH8BfoZ/gn6RfwR+f35/nn4Df35+hn8BfqN/hH6Df4d+on8Ifn9/fn5/f3+EfgN/fn6cfwN+f36Qf4N+hH8Bfq5/j36Ef4Z+kH+DfoR/AX6Ff5F+A39/foR/AX6IfwF+h3+FfgF/hH4CAgQAgI2Jjob2ipH0hIOB/4OJgf70/e6Ego+D7oSFk5KC8YWFj42LiIL4gIqKjJKPiYyHg4qKkpCHgoeHh4+QjoiFgYWSp7arjIqLkpistquJ/4H7iIaNiY6SioX+gYH449rN1vT2/v79hfqWk/OBiJSMhJOImo+UjYaNkJSLgfaXqZOUgJDzgYSVm5WOkvrd1faA9/f9hYqDgYODh5GWnJiUi4qKioKDiPaGkYyEgI+A4OPm9YKDho2JiIyEh4mGkI6IioWKi5GLioiMio2EgouEiY+Qk5GLjIGDjpSXj5eTjoyJiZCSmZGSkY2Sh42LjJCTjYuGhYODgYD6hoSGjfiBhISDgISGi42Cg4SFjJWYjoj1g4n+jJ2ZlJicnamomvXy9fXq/IOEh/+A1uiC/oGHjJaHiYeNi46OiYOAho2KhIWHioyLjpOOjf2HhYqTl5SNhIqQj42ktoyFhfn8+ISLgIaFg4eGi4+Tj4iKjY6OjIqUmZqenoqSkISLiYqM/oWGjpeagJSNmqWYlpGNjpCdjoSZiYOEjYWRkJSL/oeIjoaNjZePjYWOkZWPio6OjIqDkJCPkIaGj4mKjpeQlJOTkJCXnZWRkpWYnJePmYmMiImNhIKGio+Ii5COhpSTlZOWlpKHjY+HjJiIjI6UjpKTkIeKl42Uk5CTkZapr6Ocm5uYmqGZgJ6clJWUl5qhs6yNjpiRjIeAk4iLjIyCgJad58H0lZWTi4SNioqMjpCHjYWLj4CJgYyJkI2Hk5Tv+fnz+oONjIiVoKqjk4iEj4WF+oCFgYGAgfr58oCPi5KK84n/6+De1oqLg5OSkZ2QgJ2srYuFhf3/iIWHhf+Mj4GQnYT1iZaBgP2Qh5aRgpKHhImXio+Oi4CO/o6eqZ6akI2SlZKKjoqFg4SPivv5gISC8+vp5ers6O+ChouPlKKNiJeZlZmShZKIi4OJjIuOhpiSoYqGhImGmq+koZyRg42MlpOQl5WQlaqooJKLiIiEhJSSjIyJko+Kh4P6+/TzhpCYlpCTmpqpgKeYjZecmIyEhIaOlZKSk42NiYmOjYqNi4uQlJGNiIeE/v+K//qDiv2KgoSMkYHy+ouFpKH/gPyJhZSinY+Mj4f8iZaRioqXj4yOjoL8hoOF8fiEhYH1l6i5o6KioafagPzlgv6R0deQipu1nJKRlYiCkIiNjImJjp2Ui4SGjYeEgIWSh4SEkpOgkpmWnpSRkZSdm4Wanv3e0crZ0uTR0fDq6NrY6N7Z4vaB+IONiImEhIOkmo2Um5+RkYiIhYuNjI+KhIKGh5KK/4SLg//5g/H08fT/hf74hIv+9v3r8PPv4vyLiPOR/v/+loqKjIuJj4aE+//8h4GTkYuGhYiIhIeJgIN+hHzmg4ThfXt68Hl/d93f6tV4d4N52H17h4V54Ht7hIKBf3vtdX5/gYaFgYF+eIB+hYN+eX58fYKDg397eHeCk52Zf3+AhYWUnph56Hrqf3yCgIWHfXrqdnnl0s7CyuLl7ezse+eHgtp3fIV/eYZ7jYKIhHyAg4V+eOKGlYOAgIDfeXmGi4aChufRyut57e3xf4J8eXl6foSIi4eIfn5+gHp5fuV6hYF5eop419rb7nt7foN/f4B4fIF/hoF6gHyCgYmDgH6BgIR7eYJ8gIKBhYOBgnh5goWMhYuGgoGAfISEi4OGgICFfoOAgoeJg4B8fn18e3nrf35+gud6fH16gHl+gYN6e3t7gIWIgn7leYHqfo2KhIeLiZGVjOTk5eHY9X19f/R7zd1673x9foh+gn+Eg4eDf3t5f4WDe3x/g4aBg4iEg+2AfX6HjImCfYGEgX6Qn4SAf+/z6nuBd359fIB7f4SGg36BgYCDgn+IiouOj3+Ggn2DgoGC63x+hYqPgIyBhJGIhoSEgoKMgniEfHp+hX6HhIZ/7XqAg3+Dg4yEg3iChoqBfYKDf4J5hIaEgnt+hoGChIyEiomJhIKHj4qEh4eKkYqEjYCEf4CDfHt/gIeCg4aFeomHh4aJjIV4foV+go5+g4WMhISFhn6Bj4KJiIeKhoaQl46MioeGh4+HgImHfoF+fIOGkZV+f4aEgHpziH1/f392cYKOz6rVhYWFf3d/foCBf4J+g3t+gXR+eIB8gX95hIXZ6+zn7Hl+fHmGjZONhHx7hH196nh8eHl3ee/u5XmEfYZ/3H7s2dLTw3l1bXx+fop8boSLkHV2eebsgX5/fOyChXiFkn3mgIl4gO6Fe4eDd4Z5d32JfoGEgneB7YKFj4iHf3+EhoF5fHp3eHmFg+zse3575N7f3eHl4OJ6foWFipOCf4yMi5CLfouDhX2DhYKBeoeAjHV1d3t7iZOJhoSBdoODioaCiYOAhpGPiYF/e3x5e4eGf4F/hYJ+fXvt8enmfIOGhX+CiouUgJCFfoOJiH57e3qChoWGh4OFfHx9fH6BgYCDhoWAfn998+5/7uV6f+iAeHp/g3ji4Hx2jYfkd+uBf4OOjX98gH3qfIiDf36IgoKFhXrvfnt/6Ox8fnvmiZaejJCNi5PEdurXeOaFwMaAfIqbiYWDgnp4hH6AgX19gYuFf3t9gn16gHyGfnt4hISOhYqFjYeFhYWMi3uJidrDu7vLxNnEw+Hd4NPP4dnS1uh56nqEf4F7eXqSh32FjI6Cg3t9eH9+fIR+eXp9fYaA6XqCeerqfOTp4+byf/HwfYHs6/Lg4+fi1e6Cf+CE6OvoiHx+f4B9gXp46PPzgHeGg318e3x/e36AgHJucWvRdnLKbGtr1GhqZr/ExbxsaXJrym9rcm1rx2prcG1vbGzYZGxscXV1cnJvZm1uc3Fva21tbW5ycWxqZ2Ztdnp6cG5tb2xyd3VlyWrHamtwbnJya2fOZmvKubexsMjJ0NHKaMdyZrdoa25qa25ndHB2cWlpbm5qZr9oc21mgGvBa2hudnFucsm6ttdq09LVcnVwbmxucXBtbmtvbWxsbGlpbtBncG9naXhrx8jJ2mxsb3Fub21obHFydGxmbm1tbXZybmpvb3VsanFtcXFvcW5wcmpqbWt4dndzcXJxbHVxdW9vaGpwb3JucHZ0cHFtcG9wcGzScG5rb8hsbm5sgGhtb3Fra2xqcG5ycW/GZnPYaXFwamhubG9zcsXMzsTE4nJxcd9rt8ps2HBsZ3Bsc3BxcXRubGtsc3h0a2twcXVsbnNxcNhycG1xdXZucHBwbWxxfm9xctjc1m9vaW9wb3FrbnVxbm1xb29ycGxzdHRydWlzbW91c29t13Bwc3d8gHltaXZxbm9vbmtvcWlrbGhtdnV2cnBu1G9xcXJycnZ0cmlxdXlubHJyb3Fob3R0b2twc3FycXZwdnV0cnBwdnRucnFxd3VxeXF0cG5wbGxwcHZ1dnR0aXl0cHJ0eXBqa3NucXdtcnV7bnFydXBye291cXN3dW9vc3Bxb2hpbW9tgG1qYGBhX2RfZG1oZmpsbGdkdm1rbm5kXGdup42wamtubGRoam1vam1scWprbWJqZm1paWhjaGq3xtLN1GpoZGZxcXBqa2hrcW1ty2hraWpnadXWzm10aXJrvGnGubu5o1tSTVtdYmpjV15cYlRaY7/Mcm1vbtBzdGlveG3MbXNqgNJvZ2xoaG5lZGpxbGpvcGZuz2xpa2ttaGdsbmdiZGNhZWpvb87RbXJv1c3Ixs3RzMlobnNyc3dua3Z0d396bnhyc21ycXFsZmpibVpeZmpocHBoZmdqZW5ydm5qcmxrcHNvbGhqa21qbXJ0bW5vcW1tbm/b187Na2toa2RjbG1yfHJyb2xvcmxpaWdscnNycm91a2lnaWxubW1sbW9paW5u2dNvzchpb8VsaWpsaWXEvGZgbmnBZ8xtbW1vcGhobGrQbHNvbm5ybm9zcmvOb29x1tFobG3KcXh8bm1uaXCoZ8q+Z7ttoqxmZXF1bW5ua2Vob2xrbGtubnBubmuFbH92cG1nbW5ybXVvd29xcWxtb2twZ6yhmqm2ssaxscnJy7y7zce+uMdnzGpwa21pamp4amRrc3Zua2hqYmtpZHJvZ2ptanFtxmhvZMXLa8rNyM7TbdXZbnHR0tnLzszEvdNxbcBux8nKcGtsa2xqamhmydXccWVvb2tvbGpubmxvhH8Lfn9/fn9/f35/f3+EfoR/AX6FfwF+h38Bfqd/A35/foh/A35/f4p+BX9+f39+kX8BfoV/AX6Hf4R+BH9+fn6TfwF+h3+EfsZ/AX6EfwF+kX8Efn9/fop/hn4Jf39/fn9+fn9+m38BfpF/g36gfwF+nH8Bfv9/Bn9/f35+fpp/hX6OfwF+hn+DfoV/An5/hX6Pf4J+hH8BfoZ/BX5/f39+kH8BfpJ/BX5+f39/iH7Cf4R+qX8Ifn5/fn5/f36Gf4J+hH8Dfn9+iX8Bfot/Cn5/f39+fn9/f36Ifwl+f35+f35/fn6uf5N+An9+nX8Hfn9/f35+f4V+BX9+fn9/iX4Hf39+f35+fol/g36MfwICBACAjIeGhImFh4CLjYyLgv3v74+Si4aQjY2QlpWMipCShof8hI2VioeC84GLmZSSjYqQmpOJh46MhISIgYWPio2LorOnl5ejnaCkopaXhvTe8OHf7vrg5uPl6e7e5/LX8uz+h4eFgoaGjJfi+YKGgoWMi4CDmYyPmJeQjomMhPr47YeAhqaip6+SjYnv0+fk7O79gPuBjIyAiYiJkJuP6OP5+IaH9/z+gISWh4T7iJDm5e7p+f2FhoyJkI2GioiHh46Pgo6cm42FiYr6g4yRiI+LkIyMlIuCho+RlYmWl4+Jj5KYnJ2WkZGGiIqHkI6LjJGHjIyNiIqCg4WIi4r8gICHhIWAh4CGioqFjYeDgO+EmpqTi4qPnpOUlJyqrKqi8/SOmOnG7pWEgoP/8IHu+YqHhIeMiJOHhoiJh4j+h4KAiIqQjImNiYqNjIWPlpWLiZGMiYmRlKaxhoX8hIeIh4eAhISIiYmJjY6WiY6Li46Cjo+Mj52impqTkZGSjIaDgoqNkJGAj5SglJORm42WjJOMi4iKlIiNk4+FiZGOkJCIm5CNj5OGjJSLlo6NjpKXi5eHioeOjpaIhYmMjI+UjI+Ml5SRkpWPi5WUlpGJjYuIgYyOkpKTi5eRkY6UiJOVlpeSlouNjZaTjpWYl5mXj5OOkIqRjo6Sj5aZk4ycp6SgkYuUlpKAjJGQl5ilo6Gmrp+RjJeQif+Fi5GOi4j/nY+Nl5aZpJmSkIuM/f+IlI3+goePjIiE/IaMjpSKg8z7gfr6gIiRm52aloyHiIqLiIeIi4eLioaDhYT6iZLyg4eSkIOHg4WIitb1kJiyq5b935Py8faEg4iRl46NkYuUhY6JhKmMj/qA+5WX/o+WjYWAi4eRj4aNiY6UmpqRlo+Oj4/6h4Xz+oKC+er7+P/s6e3r7e3sgYyRmYqKl43+lYKEkIiSk4yKjoyMjY/4jf777u+J5vWOmJmvpKKbnZSLkZaRlZCYopefop6WkIiLhIeTjpCQipGKjoqHg4D99faNk5CPkIqHjJmAkY2QiY2RlIyJlpiUkI2hlY+Ph42VjpCUjIqLiY2I/oH76/6HiISLiouIiICEioP5+ZjAnoP1/ISFlpyM9YyWkYjziZmJjo2NkZCGgu6IioKFhIH7gO3T7/yDnrWak4yJjvzrxtLO1tuNq7inkIiNhoCAiJCQj4aJjpCOhIf+g4OA+IGBg4ySnZyRk4+Qi5KRj4+dpI+BzrnY2Obd2OHm8ujz8/Pk5eHa2OyAhoCA+v6DgI2ZiZGPnJCdjpqTioj/hPuC+YiKjpCOi4uF64OAgPL4+4T85+iBgISD+oP4+Pv7gNvw//fr/4KHj4STlY2OkpKSl5mRkIiCi46JiYeIg4uAgHx7eYB+fnmEhYOAd/Hl4YSGfnqFf3+DiYl/e4CEe37rfIKLgX565XiAjIiGgoCFjYZ9fIWBent/dHqDfX97jZyWiIuTjY2TkIWJfeXR5dbW4uvS2dTW3uTS3ObM5eXufoB7eH18f4nH4nV6eHqAfnR6jH6BjImEgX6Be+Df2nqAdpKQkph9fH/hydzd5uTxevJ5hIV2f4GAgoyD1dft5X5/6vDweXiLf3zpf4nb1eTi7/B+gIWBhYF6gYGAen6EeYKOjYN7goHneYKIf4aAgoKAhn94eoODhH+JioN9hIeKiIiFg4R7f4B8g4OAgol+g4KBfYJ5eoGAgoLweXl/fX2Af3qAgIF+g4B8edx2iIuGf4CAioKFhYiTlZCP3+B+idGr24d7env05Hrh7IB/foGEfoh/f3+Afn/ugHx7f4CFgYCDfX6BgnyEioyDgYWAfn6EhpObfn/wf4CBf354enp9gIB+gYSMgIWBgIN4gIWAgo6Qi4uHh4WHg3x7e4SEhoeAgoWRh4eCjIWOgoeBgX5/iX+DhIF8foaAhIaBkYOBg4h+g4mAioOBhYeLgIt+gHuBgop+fYOCgISKgYN8ioaDhoiCgIaGiYeCgoKAeoOFhoaLgo6HhIWLgIeKi4qFioKAgIyKg4iLjYuKhIWGh3+Kg4KLhIiNg3iFkIuLgnuAgoKAeoB/hIaPi42PlY6EgIyGf+h6foOAgHreg3h5gn+DjomFg4CC7e5+h4Pud3qCgn165Xl+f4V7ebrndunseX6DiYiFhIB8foCAf31/gH2BgH58fnznf4bWdXaCf3d+ent7e7vPen2Wk4HZvX3O1uB3d3mBioCChX2Fdn18dpl7feSA5oOE4H2FgHx2fnmDgHuCfYGJjomCh4KDg4Hgenjc6Hl76eDu6+/b4OPl5OHjfISHi35/iX3hhXd5gHeAh4KAgXx+gITkgevn29l+2OaBiIeVjI+MjIeAh4uFh4WJj4WKiYqFhH2AfH+HgoWFf4N8gX59enbs5ud+hIODg4F9f4yAgYCDgYKCg4F/hYqHhIKUioaDen6Ff4KJgH5/gIR98Hnu3eZ7fHiAf4F+fXZ5gnzt54egh3bl8Hl8iIZ+44GGhoHpf4yAhIKCh4SBet6Bgnl/fnzqet7J3up1i5d/gX59hOraucTEys2ClpqMfnt+eHZ2gIWDgnx+gYSBenzpenyA6Xl5e4SGjIqCg4ODfoaEgH2LjHputafLyNbRzNTa5+Hr6ezb3trV0+N2fXd37PB8d36FeYKCjIGOgIiBfnzpeuF35nx/goKAf4F72Ht3eObu9Hzp1dl5eHt67Xvq6urves7l7+rg73d7gHiEiIOChYOFi4uFg3x4fIJ9fHx9eoCAb25qam1tbWhzcm9tZtXKxnNxa2pxa21uc3VpY2huaXDPam95cm9szWltdXZzcG91e3JsbXRwZ2pwZWhvbGtlcXp5cHF4cnR2cmtxase9y7u9xsy3u7i7w8q9yMm3ztTZbnBtZ2lpbHOowmJkZmpta2Rqc2ptd3FraWpuab24uGaAYHJ0c3dlY2vDuMjO0snQbNZqcnZpb3FwbnZusMHLxm9sx9TTaWFya2rHa3S7vc7M19lxcnZwcm5mcXJsZ2txam90dG1ocXPLbHF6b3RtaXBtcW1qaXBvbm5zdHBudHdzbmpucHBob29tcnRwb3lydXFvbHJqbnRyc3PZbW1xb22AcW5yb3Fwbm1varxha3Bva21rbWttbGtydW5vwsNmb62PxHFrbG7VzG7L0nBwcXF0bHBwcXNvb3Hbb25tbW5wcHJya21vcWxydHl1cG5tbG1ucXN4bXDWcHBzcm9qbGprb3Bxb3J3bnFwbm9nbXNwb3NwcXJydnN2c2prbHdyc3WAbm95dnVsdXV8cnRxcW1teHJycGxra3RycXJxf3Rtb3Nxdnhvd3JudHZ3bnZrcWlqbXNvbXNwb3B0b29ndG5ucHFvbm9xc3V1cXJxbHFycHN3cH92c3N2b3Vzd3dzdXBqbXt4b293eXR2cW9ydXB7cHR6dHF0amBnbWlvbGhqaGqAZ2Rma2pua21pb3FvbnNwbMdoamxtbmOrXlhcZ2Rlbm9vbWtvy9Fsc3HKZmRtbmhmxGVoaGtmZJ2+YsjTampubmlla2xsbG9vbmtqbWtwcG5sb23LbHCxW15nY2RqZ2loYpKgWltra1+mkFubq7lhZGNnbWZvb2ltYGVlXnViZsCAxGhns2VqbGpmaGJpaGdva2t1d29scW1wcWq6ZGS7ymhsz8vQ0NjFzM/TzsXKbnR0dGlscGS2aGRma2hpcW9wbWdsbW2+a8fNwrdqwtNsbGpyaHR2dHBsdnZwdHN0dHFybG1scGpvbnBzcXNzbm9pbGptbWnQ0dJvb3Bxb29ubHOAcG9xc3NtcG5scHJycnF6cnNxZ2pya292b2trbW5q02rRxcBoZ2RpaGxubWZlbmrNy215Z2LN1GVqb2Vpxm9tcXLOb3Rxc3Fucmtyaspvb2pwcm3KaMO0xMVhbWxfZWdpcMzAp7CxtLNvdXFoZ2hsY2Rlb3JtbWxsbXBtaWrHam2Ay2xpa3BwcHVta21vaXRxa2hxa1tUlpGzrLu7ucLD087Z2d3RzcTBvstmamVmzc5qY2hmZGxsb2l0bHFsamnKa8Znx2dqbW1sbnJqu2xma8fR22vFwsJta2pq0GvNzMrQa7bJ0s7GymdpamZydXFwbmtxc3BubmlnanFsa25va3GNf4N+kH8BfoZ/AX6kf5R+iH+CfpJ/g36Jf4d+An9+in+EfgV/f35+foV/A35/f4Z+lX8BfrJ/AX6PfwF+kH8Hfn5/f35+foR/BX5+f35+jX8Bfp1/AX7/f7p/AX6GfwF+jH8Gfn5/f39+hn8BfoZ/BX5+f35+l38Efn9/fop/gn6FfwZ+fn9+fn6RfwV+fn9/fpZ/B35/f35+f3+Mfoh/AX6OfwJ+f4R+A39+fqd/g36nfwV+f35+fox/gn6Ef4J+hX8BfoR/AX6KfwF+hn8Cfn+Efoh/h36VfwR+f39+lH+UfoR/gn6PfwV+f35/foh/C35/f39+fn5/fn5+hH8Cfn+EfgF/hn6ZfwICBACAiIGKhoiAiIOIgfX++v+G9IOGlp+Egob59veDgPiJkpOMjoWFiZiTjYyJh46Uk4uMjIaJi5aKhIaOiIWFmbKtoZKLk4+JjJWMhov98unx7u3og4X6hZ6Yg+3vgu/zioOGiIb9/oSCgvWGgvKC9YaLlIeTl5eO9pKFgJCMhoSIgoOAhor6+6OcjeHS1+Dq7/T7+4P3gv3zhpKM+/rt/Pj8+vLzhIqFkPqRlJGRiYP+7O/t64GLiIGEhIeMio6UkIyLiYCEh4KJkIiMioyMjoDwg4iJjov5ipSPjY+Qi4eJg5abmpaYkZGOi5OMlYuIiJCYh4qG9IaBhoaHj4qEhY6Sj4uAjIWVhfP7/f2NkJiLnoSdm4mOi4eGk6GTrJKRnvWC+vP8o7yC2PmHhtiBhP+B/4eJiIuVkY2Fi4uGg/+MhomQk5acjomGlZGRlpORko+OjIqUn6SVkYeHhIaGh4CGh4SBh4uIi5iUlomRkJKSio6Pj4+hnIyCio2GhIeMjYuJiouAn5WXkJ6hpI6Mi6CXj4mOi4qOjo2LjJCKh5KRh4uPlpePjYmNjJWLjJKTjZSVkJCJiY2Pi4qMj5GXlZCLio2LjJeXl4mNjpCNjYiRipGHk4+Qj4+LhoqQj4+Uj5CVkJKVk5SOi46JjpaaoKCYlZKTio6NmJCHlJmUl5mbm5GYlqGAlZKSl5eVm5ybmJ6Ui5SUg46LiIeQjIOOj4fh9oeA6OX1goaSjYqMjIiEhomGiYL//IqNg4KEiorx+oP8hqKblIyPjo6LjouHioiKiYeIg4OEgfyBhYaDifzo5PiLkZyqmKeqprT/l5D6iPnw3/ft+/CMhYKEiI+RhoCIjYaYioiAhoOGjYmEhIaIk42JgoWRlIyaoJCOj4yBjJWOhfyChezpgIL+7+j5/YaOhJOdoZWCiY2Ug5WFko+I7fiXiYGEk4+Lj4mJiYb/g+L9ipGGqLuspZOUiY6JmZOVlJGbnZ2lnI+Qj46IhZKOjoqIkI6LiIuKhf2AgoqUj4yHh4qJjI6AlJWSjICNmI2KiZKVlI+KjpeMh4eGjZCAhIaBgoD+9feBg4+UgYaKj4OFiYGA9v+B5oSxuJL34vaAhpibioGKo5GD9IaPi4mEioqGho2Nl4eLg4L25/f5//fviaOgnJuBiIyD9Onuk6ShwMelkoyKj5CD+/2WkY2IgIiQlpONg4KAhPnt+IagoJaQmJKMi42NkYqQobSF58jg6N/o5unx5uHx6ujg3uTg3uPjhPCA942Uio6ZkZyRnI+QlI+PlZmMiIKEhYeGgIH+j4yHjIiLi4b35/qMhIeGhI6F/vrj8+jphIOIhIH6+f+B/YeMh5WRkYeHlIeKjoP/hoiOh/yDhYSAfnmBfn53fnqBeebp5el85Hp6hIx1dXzu7Ol6eOp/hoR+g318fImFgYJ+e4KFhH5/gn1/gop7eXuCfHt4hp+XkISAh4N+fYaBe4Ht6+Hk4uHcfn7seY2LeOHgeuDlf3h9f37w7nl7e+h/fOB543x/h3qDiIeB34N5dYJ+fHp9e3uAfH7k35KMgs/DzNTi6Ovv8Hzleu7ge4qD5+vf6+716uTje4N/hd2BiIaGf3vw4OTe3niCf3Z5en2BgISDgn+Cf3d6fXp+hH6Cf4GAgnfien+AgoHrgYqEgoSFgXx/eIaKiYeKg4WDgoiBiYN9gIiRfoOA7IF7fYB/hoF9foaKhoKAgn2MfePv7+2Eg4p8kXiNjH+Afn17gI6AlYCAjOJ549rhjqN5yud9e8x3ffF79YCBfH+IhYN5g4J+fOyEf36Ch4iNgX57iYSCh4aGiIWCf36EjJOIhoB/fn9/f3l/f316gIF+gIqIiYGHhIiGgIGAgn+QjIJ5gIN9eHyDhYR/fn2AjYSHgIqMkoB/gJCIg4GGg4KFg35/gYZ/fIeEfoODiYqDhICCfYuChIaIg4mKg4SAgIKCgYGAhIOKh4R9f4F/gYqKjYCBgoR/gH6HgoZ8ioWFhoWCfH6ChYOLhYWKhYiGhYmGf4J8g4qMkZCKiYWJgoWDjoN8homCg4eLiX+Gho+AgoKEh4aFhoaDgoiFfIaGe4aCf3mAfHV5e3S+0HNvzc/edniDgoGBgX56fX17fnfq6H2CeXh4fnvd53fjeI6IhH+Af4KCg4B8g4CBgH5/e3x7eOl3eHl1etjOydx8gIaOfIeJiY7Sg3/he97YyuPb6t6BfXl8gIOEe3V9gHiJfoCAfHt+hYB7e3x8goF9d3uEiYGLjoCBgX55goeDfOt6ftvcennw5dzz9n6Ee4eLjod3fH2Ed4V6h4F30uOJfnl3iH+Ag317f4DwfNTufYB2kZ+RjoSEfIZ/ioSKiYWNjIiPioGEhIF/fISDg4B+hIKBfoODfe55en6FgIOAfYCAgYOAhoiHg3eCiIKAgIN/g4B9got/e3x8hIN0fn94enjp6e15eH6GdHp+g3h8fnZ35et32XmZm4Hh0OV0fIeJfnqAlYZ86HuEgH98gH9+g4WFjn6AeXno3fDu8urffI2Mi4x0foN76OHkiJSOo6WKgH18gYR56uiFgoB+d3+Eh4aBe3qAfO7l63yQjIR/iYR/fn9+gXt8iJpzy7fR3tPc19vk39nn3+Hc2t/Z09zafN935oWHfoGGgoqAi4SCg36AhIh9fHZ5e357dHfrgX5+gHx/gX/q3O+Ben17fIV77/DV6tzefXt+enrn4/B464CAeImEg3d8iXt+gHjre3+Dfup4fH6AbGpybWxmbGtybM7MxtBux2tnaW9jYWzV1sxqadBucXBscW1ubHRvb3JzbHFwcWxuc29ubnZpaWxwa21nanZydXBsc25pZ21tam7M1sjN0NTKb23QZ3J3ZcnHZ8XLamdvbm3Y02pubtFxcsRlxmttcWhpbmxuwW5nZ2xra25ra22AaGi/sXN0brCruMHT1dHR0mzBadPGbHlxydLFydTYycvEaXJvbrptdHJubWjQzdHKx2pycGVnaG1vbnRsaW5wcGlsaGltcWxtam1rbmjJaW1sbW3UcXdybnFzb25vaHFybnN1bXN0c3ZwdnVtcnh7b3Vx3HdvcXVxc29scXZ5dnCAcW97bsXP0c5zb3dod2ZxcmxqaGloZG5kcWVjbb9nwrO2bHtmsNFtabZpcNxt2nN1bGx4dXFodHFqbct0cW9wc3J1bm5teHBub3FzdnNvbGxtc3l0cm5tb3NycW9ycm9rbm9wb3Nzb3B1c3NycHBubWp3dHFrb3BtZm1zd3lxb2svcm9zanNxeGlqbHVycXB2dHB0bmtsb3JsbXZybHJxcnNtdG9uaHpzdHR0cXZ3cHKEb4Bxb250cnRxb2lvb2xuc3F1b3BsbmtsbXZzdGx2cXJ1dnJrbWtucHlxcXV1d3BzdXRvcGxzeXx5dXNycHh1c3B0aGZubmhpbHBsZWxtdGhpcG9vbWppYGBoa2VucGxxbmtob2xjYF5Zj5xXWKesvmZkbW5xbW5tbGxqaWtnxsNnbIBlZWVpY7a/Yb5ib2ppaGpqbnFxb2pwb25ub21rbGhmyGhjZF9fo6GmtmNlZWZXXF9jY5dmYbdhramivLfKv2puamtzc3RoYmdoY3JscWhqbnFubG9qamdoZmRsb3Ntc3Frbmhma3Bxb2zNbG/CxW1q1c6+191wdGpubG1vZ2dkaoBlamhwbWjFznBrbWpybHBta2Zsb89qvdJpaGBvdW5wamtncm5xbXVzcXRwbG5va3FxbnJwcHFzbm9xb29tc3Zv1mxvbXJsc3Btc3JwcnNxcHFpcHNrbnBsZm1sam51bGlqbXBqYWhybG1mxMzPa2hrb2Fma21qbGxmZsnMZr5pdIBvZsC0w2RpbW1qaW99c3HVa29vcW9ramxxdXV7bm5sa8fA29fXz8Roa3JzdGJscWnSzMdudW95dWdoa2drcmrTzGxtbmxob3Fwcm9sbGvXzc5reG9sanFuamtrampkYWZ5Wqaeuci/xb7Cy9DI0szRzszPyL3Ewmi8YslvbmlsbE1obmpxcGxrZmpqcGtrZGlqb2piY8lsbHBua2xub8zE1W9na2tvc23X173UyMZtaG1nacnB1GrLa2pkc29vY2t2Z2psZ8ZqcnBsz2ptcIp/hH4Cf36HfwZ+fn5/f36tf4d+A39/foR/BX5+f35+hX8Lfn5/f39+f39+f36IfwF+jH8Ffn5/f3+Jfgh/fn9+fn9/f4l+hH8BfoZ/hX6cfwF+hX8Bfp5/AX6Rf4R+lH8Sfn9+fn5/f39+fn9/fn9/fn9+jH8Bfv9/4H8Hfn5/f35+fo5/gn6HfwR+fn9+ln8BfoV/hH6IfwZ+fn9/fn+Hfqt/B35/f35+f3+FfpF/gn6MfwR+f35+qH8Bfql/g36NfwR+fn9+hH+Dfop/AX6Qf4d+iX+Dfox/gn6Nf4N+j38Cfn+VfgR/fn9+mX8Bfoh/g36Hf4Z+hX8Ffn5+f36NfwF+hH8Efn9/fwICBACAiISGj5GLg4SMhYGAg4WD//yB/fT/9/6KhYaFh4mFh4eSlpiZkpGTkZiYiIuOjZmYlJCSjIuKlJ2rr7G2mf3p7YmNk5iWj4CHiIL2+oaChI2KkJWkoI3u8oLxiI6Njf2Gg4iDhI2Ig4qOjIf8hP+MkYWKgIWEgIWFhIWUipaFgYKA/vLu3Iv/ge3t2tvx7frx+oKD/oj6/YiDhImHh4aCh4qWjIGJiYWOjISAgYSSkveA9fqHhIOLj46JjI6Ki42Ti4qJhZORiYmIioyInIqJiYSIkJKVlZCHgoGBjJaZo5SRjZaenIWGjYiFj42SkY6QjIqDg4uGhImFhoCCj4uH/v2AgZCOhYiLjY6Fhfvqg4eYiZmckPCImZymrbWdjICEiIuLioOA8PD25IyQ+feFioT1g4eKjYeKjIuHgYmTi4mLiY2WmYqLi4mNi46copuaiJWdopGMg42Jjof+gYWLi4iB/4mJjJCSlZqSlpGNkZGVl5OfoJWEh4qHjYqQg4OLkIyAmpCMkZSZkZySmpKTipKRiI+LhIWMkIyGiIiMkon4ho2Ul4SPkp6Qi5GRi4OJkZiRiIiFi46KiYmLi5CMjoqFiYqQkp2HlZeLi4yIi4uRjo+MkoyMlpWLjJOPjpWUkZKTjpGQkZWUjo6Xk5KOjZWRkpOgkP+FhoWEj5CKjJuilpGAlpeTlpSglJKTlJSOlZuZ/IyOhYqPhorw+d7l5eL3h5OI7OeBg4mCiI2LgoX/iPz9+fiGiZD86fv1iYqJj5eQjpSSj4iNjoiDh4aHiY2TiIKMhIb5iPOLgOr7joijq7is65D/z7jN5eWGmJGKg+no3+Dk8/2HlJqQiIGSi4SHi4yAjoqQjpCOiY6OiYiIiY6Pkpmdho6M+oiDh4+Pi4iMkYqGhv73goSFlZiKhIP9gpGJjYqOjJeXoJ6FiPySkYKUkY6JjZKHioaB9+zxlKG1wbygkZmRjIuMkJKVm5WYmJ+mmZCLjIqHkImMh4qSiIiIhoL44/iDhZSViYGBhYD4hYqAiZGOh4mDjI+NhJKZmo6KioCSifvv8ID/+/T1+veNhoyNjYyKgYeHjYSFhYeB+fz6/PuIqqiW+fTx7/aJmvzejaKQiIGOj5mQipCJjIPziIuKiYaHhYKB/4WE8vyJkJOUjYCIj4KA+93nltO9louOjouJgfqEjJiJhoWEj5WNgoOA/evm29mCkZmJkJSKhoWjm4iW/ejh9uvi7+rl5urr5OXo4OXZ4uju2dvi842LlJu3lY2OjZKipIupoI/0+oSMmo+Li4yDjYf9lJH7goSE/oWKhv6A+YiEgISAgIHj8f7t94OFgYGIgYT6gYCEhYuQiYGEhZKVkoKJiIqPi4qGiYyAf3p8hIaAeHmCe3d3eXx67u535dzs6OmAfX99f4J9fXuHiouJhoaGgouKfYKBgIeKhoKHgoJ+g4uUmZadiezZ3n6Ah4iIg3Z/fnnm7X14fIaBg4SQi4Dh4HzhgYaFg+5+e393eIB9eYKFgX3rfOyChXp/d3p6dXh6d3iHe4h4d3eA7ejjyYDreNve0c3i3u3k7Hp77YHt7IJ7fIB8e355f36Ig3l+f3l/gXp4e3qFiOh55+p+enp/gIB9gYN/g4WJgYB/e4eEfn5+f4B9kH99fnt8hYWIiYR+enl3fouMkYaGgIWMind6hIB9g4GEhoWIhIB8fIJ8fIF+gXp6hX9+9/SAe4aGfYCCgIV8evHeenuJfIqOht5/hoqSlp6MgHZ8fH9+e3V04uLnzoKK8/B/g33men5/fnx+gX9/eoCGgICAfICJjYCBf3+BfoCMkYiIeIeKjoGBeoN+hoDwe4CDhH958oCBgYOGh42FiIWBh4iJioWOjYh8f396gX6IfHyBg36Ai4B+g4KFg4qIjISHgYeGfYeBfHl+hIN9f3x8hH/keIKIi32Fho+FgoaGgXuBh4yGgH97foSDgIGCgoSAhn97gH+Fh5B8hYh+gIF/goKHhIWBiYB/iYh/gImEg4yHhYeJgoeFhIuIhIaLiIeFgYeChoiYh+5+fHt2hIJ8eoeNhYCAhYaAhIGQhYSDg4WAho2O7ICAeXt/d3vU28LJy83fe4d729N0d393fIKBenvqfero5uV9f4Tk1ebhfHp8gIWAgYeEhH6Eg315fX1+foGGfniDe3zketV7cdDZe3eOjpaNuXTRr56xy896jYZ9e9ze1dTT3ex+h4yCfXWHgHuAgoOAhICHhoiGgYODf359gIWBhIqOfIOB5oB9fIKFgn+ChoJ/f+zpe319h4h7d3blc4F6g3+FgYqIjop5fOaCg3eBgoN8hIaAhX976eDiiI2copyJe4eCfYGDgoOJjYeJhYyTi4R/goB9g36Cf4GEfn9/fHrr2e57foqLgHp7f3zwgIKAf4aEfYF8gH+DfISIjIJ/gXiEfeze4nbs7OXl6uaAeoGDgX99dX1+gnp7fHp15OTk7Ot6jo6A4uPf3uN8ie7RgJSFgHmEhI+KgoaEhXvhgIaCgX2AfX1584B85OyAg4aGgnh/hXp66MjOg7CbgXuAf317d+19f4l+fXt5gIWAenqA8dzZ1M53g4h7fIJ9eXiPg3R/2cjC2tXR4Nzb3t7i3t3l3N7V3+Pl0NLa5YJ+iIydgHyCgIGNkXuTi4Hd5nV7iH19fn92gn3khIPodnt87Hl9fPB25X97eHx4d3fU4+/i7Ht7dnl+d3rleHd6fIKGgHh7e4aGgnV+e4KEgIF9gIOAbmhrb3JuampwaWllaW1q0M9kwbjKzcxtbnFsa3Nubmx1d3Z1c3V3c3t3bHBuam9zcm9xb29raHB1d3Z/c9DBwGpqcXBxcWdsbWzNzGtnbnhwbGpxa2fSy3DKcXZ0cs9tbHBqZ21raXB0cG3Ha9RwcGdrZmlqYmdrZGRxaHBlZmWAzdTKq2nFaMLCu7jPxtXT1mptzHLX0HJubm9qZmxqbW1zbWZpaGRqb2pqbWhtdMlrz81sa21ta21pbG5tdnR2bm9vanVuamxqbW1rem5sbGpqcW9zdXBtamtnaHV0dnJ0bWxwcGVpc3JwcXBxdHB2cnBtbnFvb3NxdG9udWpq3d2Ab3N0b3FwbnFqadbDZmlxZ290csJva252dHlvamdwaGpnYV1gxcfJsW955Npucm/Ma25vaWxsbG9wcXFxb3JyaWtyd3B1cXNvamt1dnFvaXJwc3Bya3JtdW/Xb3R2c29p2HFxdHBvb3hxcnNydnV1cm52cnFscG5qbWh3a21sb22AcmxsbWtsanR3cGttbnRxbHhua2dscG9scGpncG/IaXNzdW9wcXZ0c3Rzb21ydndzcG5oam9zcnJxb29ueG9qbW1xcndpbG9qbGxsb3BzdXNudm1udXZub3Vyc3dycHN0bnRybXd2dXp7c3N0b3BucnR+b8tqZ2hibGxoa3BsaGiAbW5pbGp0bGxrZ2toa3N0yWtqY2dpX2Glqpemqqi0Z3BkurVhY2xkaG1tamrFa8rCwb9pamy6r7+8ZmBlZWlpanJxc3BzcWxrbGxsbW5za2ZxamnBZKtjWqOgXFtnZWdhfFGahn2Qqq9oeHFoZsDCub60tcxtdHVva2NwbGpvc3SAc3B0c3Z1cXFubmpucXNsb3J0a29twnFwbG9ybm5wdXNxc9LWb25ubmxkZWS/XmtocW9ycXRwc3FlZcdqa2Rjam1qdHRzdWxsysXEc251d29oYm5rZ3Bwb210eHR0b291cnFscHBvcm1ycHBxa29ua2rQxM5sc3Z0bmptc3LZdnSAbnBzbnFtbWpvbG1vdG1rcWhta9G/wGDEysbI085tZWlycXBtZmlpbGhra2llycbBz85pbWphvMTExshmbsfCcXdxcGhwcHh1b3NvcWrGb3hyc21wcXBs3HJqzMttb3Bwbmxsb2lpvaOmaXtuZWVrampnaNVua3Zvbm1qbW5ubGuA08PExLhmb29mX2hnZ2dwY1thqKKct7a8ysrMzM7PzsnXz8vE0NDRvr+8vWlpcW90YGNvbWdydmJza2i+yGJibGRmaWdibm3CcHHNZmlrzWdra9dkxm5ra2xqZ2a9x8jG0GtrZWprZ2vFZ2hpamtxb2Zra3FxbWZuaHRxbHFvcHKPfwN+fn+FfqZ/g36Kf4J+in8Efn5/foR/AX6MfwN+f36Sf4R+A39+f4l+Bn9/fn9+fph/BH5/fn7Mf4J+in+Cfod/AX6Qf4R+CH9/fn5/f39+qX8BfoZ/AX68fwF+1X8Bfpt/AX6Hf4d+BX9/f35+iX8Cfn+EfoN/hH6afwd+f35/f35+hn8Cfn+GfoV/h36hfwF+jH+Cfoh/AX6NfwF+jX+DfqZ/g36JfwF+lX8Efn5+f4Z+kH+FfoR/hX4Ef39+fo5/AX6JfwV+f39+fop/g36KfwF+jH+Ffo1/mX6Qf4J+in8Ofn9/fn9/f35/f39+f36Hf4V+h38Bfpd/AgIEAICKhYSEjo6QhISMioeEkY+Wh4aD9oH7+IaAi5WOhoSMi4iEjIiMkIWDkZill4aNjo6NlJmXl6mwnoeC7YeYmJCMkIuCl4uF9vj8iIyKj5Ggq8C1l4H++4eKi4OBioqKhIf+/I6LjIyTmZKNhY6Lh46SiYiDjI2Jh42bmY2HjZGFhYCOg4GBh4uIhOfe7P367fnuhYmGhv+CgYCF84mChI2Tgfn5+/+I/4aFiYiQjYuPkYj6/YGLg4mQhISEi4aCh4yJkIuHipGR//WEkJGOlPuGg5CXh4yLjJSdkY+UnZqci5OXl52NhI6Hi5GDl5WMk4uP+IOEhP/y/4WEjIqEj++Gh4CNk4uEiY+Ylo6Ji5GekYWDgoafkaGWjautp5yQlvKTlInv7YWRh/uVjYmK6vL5/IKEhY+Pk46HiIn4gYWBhI2Gm5KXiZKLi4qQio2OkpidnJ2XkJKQjoaHhoqEiYaD9YSKho2Jk5aPjY2Nl5iUkYqSlZibro2Lh4SGg4qNjZKQj4CdipidnJmaoZ2OjZOTipuWjIiMi/6Lh46WjoqNkIiJkI+Vi5GFjpCWjYyKkImHho+RioOGiYmLi4iNhoKJipCOjo+Wi4+Gh46LjI2LhIeLjJGbi5KSkJOTjJKPhoSSk4mQmpKTkZSJmJCNkoOKjZKOkI+Hh4H0ge/9iICEi4qMkoCTjI2SlJeTlpGbjpKSlZGHiIaF/vqF3vSA2ZGkkJ+UhouShoWK5+Po8YL9iYSDjYT8gvuD8teOio2fn42CgoSFiIuHjIyIiIKGjo6Lj4yFgYLuhIL0/uuHjYKOoa+mmN+hzsvUyt768YCGlYqKkYz96e3294SE84uBhJKOioiIjoCKj4SLjJKPiIeGj4aVlpCLj5GKkZCIiZGJiIeHio2FhoaCgu77+ZSIiYGG8IGYk/WXjpGVk5mikY2Mh5CEhJSCi4aV/YX8gfTz+IOJocjLsJ6QlpOai5mXl5adkpiZoZGTkY+JiIuJi4aKlJSOiYWA+feIiI+OkYeFhoKF8e+FjYCJg4GMjoyEk42Ch4WDif33+IaG/oWCg42NhoqQiISJiI6Hhf7/iIeB9oWFioKIgoeC8oCGiaGbiP368O6ChYb9hO2B+u/3g4L7/IOAgYGGhf2Lj4yOhIX/i4KHhPjp9OPy+PLx5uuHjqGjutaYko2IiIiMhYH8iJKOjIaDi5KHg4D06+fW2t+Kio2PiomGiazerbihz4mM3dHa2ebf6e7g2cnW49jb0tLJ0NTniYSMn6acnaWZlZOWnqSWk46DjI6Kgob8hZCTj4qglf6ChYOJjIOGhoiJi4KH+P2FifWKhYuLh/T0gfiQhoqJlo3/gJaE+vyJjIuVio2PioqFiYuMjYCAe3l5g4OCd3qCgH16hIKHfn154Hft6H53gIqDe3qDgX14gXuAhHt5hYqTiHmBgIGBhoqHhpWYi3h3232Ihn98gX96joF75+bsgYGAhIaOlaedgnXx8X6Bg3l2gIJ/eHrq5n99fX2FjYaBfISBfYWIfn56g4J9en6LiX9+gYV2e4CGeHh2fYJ+fNfT4e3q3ufifoB+fO17eHV95H95eoOHeezv7PGA6H17f32Fg35/hYHt7Xh/eoGHe3t3gH56foSChoF9fYWG7N94hIWDiOh9eYWKeoB/gIWQg4KEjImMgIiJiY+BeoV+gYh1h4eCiH+G4Xp7e+3i8H98g4J5hOF/f4CFiIJ8gYWLioJ+goeQhXlzcnuPgpKHfZaXk4t/it2Eg3vZ1XSDeuWHg4OE4Oju63p6fImHhH9+foPten14eoF7jIeJfIWCgH6EfoF/goeNjY2JgoSFhH6AfYJ7gn9+6n2Df4aCiomEgYKEjIyJhoCHi42JmYKEgH5/eoKEg4eCf4CRgIuQjIqKio6FhIeIf4uHgX+DhO2BfIOIhn+CgXyAhYCHfoh8gIWOhIF+hX9/foWHgXx/gH+CgoCFfHp/gYaEhIWKg4V7eoGAgoSDen2AgoeRgYaFhYiKgYqFfXmFiH6DioWKiYx8jYeFjX+BgYOBhoeAgHvme97lgXZ2fnl8g4CGfH6CgoWBhIGPg4SCh4N6e3l45d13w9lxw4CSgo2FdnqDeHd80tLY3Hjwgnt4gXzseu564ceCfn6LiXx4eXt8f4F7f4B+f3t9hYKChIJ7eXnge3re5853fG94hpOLgsCJtbXAuczn23Z8i4CCioTx3+Ls8H1614J5e4iCgIB/g4B/hHmBg4iFfHx6g32JhoF/g4aChoV+f4d/f36Ag4V/gHx7euHw7Yl8fnd833WHgteGgImLiIqQgICAfIN5eoZ1gHuP73/vfOnp7Ht7i6apk4V8g4SLgIqFh4eOgYeKj4SGhYSAf4F+gn6Ah4iDgH9+8Od/foaHhH9/gHqA5uV9gYB/fHh+gIN8gn93eXh5gfDp5Hx88Xt3eIF/e3+Hfnh7fH96eensenx35np8fnd+eHx533Z4d4mIeejp4uJ6fXzpeth26dzpe3zv8Hx7fXl9f/GFhYKGe33yg3l9e+nf6tPg5+Tk2uB9gY2KmKx/fX16fHuCfHnsf4aCf3t6f4V9e4Do3tnM1NZ/fYCAe3x+epq9lJ2Ksnl9xsHNztzX3eLZ18TP3NTa0cvGzM7ggnqEjpKFiJKJg4OFjY6Bgn94fn54dnzpfIeHg36OhuR2fnqBgHl9f36AgHp+5+t7gOOAen+AfuTkeOGCfIGAiYLqd4t44+iAgn6DfICAfX97foCAhIBwZ2Vsb29tZWpubWtpb2xvamlowGXS0G5mbnVvamhxbGhmb2htcm1qc3F7cGZvbm5scnNxb3Zyb2Nlv21wbmlla2xpd2xnzsvPcW5scXBydX92aGPR1W9xc2hla29uamrMzG5qaWdxd3VvbHFua3F2bG5tcW5oZmlydG1ub3JjaYB0Z2dka3BubLq8yM/NwsnPcG5tatBsaGlwxmtrbm5tZ9XUy89txW1pbmxxcmpqbm/P0mhranJ2aWpnb25sb3Jydm9ra3Ny0MNncnJudMtuaHB3am1ubmt4b29scW5xb3Jvb3ZsbHRvcnZhcnRwdm530WxtbNjQ2HJvc3RpccdxcYB4dW9tc3V1dm9vcXF3cWdhYWh1a3duZnZ2dnJlb8NqamW5s15rZ8NycnR0yM/OzWprb396cmtvb3PUam1qa25nc3FvaHFzcmpwa2tpanF1dnR1b291c29wbXFtdnRy1XF2dXl2eXJybnJyeXd3dnJzd3lveG91cHFxbHR1cnRzaYB5bnN4cnBsanJycHB0a3FycXBwcstvaWtxdW1taWhwcWdvaHVtbnJ6b3BscXBub3Jxbmtxbmxyb290bGtucHFxcnN1bnBoZmprb3Bwa29ubnJ8cHNub3J3cXl0a2dwc2txc3N3d3lsenV0fHBxcXBwcHJxb2vOb8XEb2RiamNlbIBtaGppam5mbWh3cGxrbGxoaGdjvLhgn7BcpGd5a3NvXmJpYF9msrC3uGbScWpobWrOa89pv61saWdsZGRmaGprb25pbG5sbGpsdG9ub29qaGfEaWi5up5dX1BYY2tkXo9ulJuln6/HwWhsdm9zdXLTyMvb3m9qwXJqbHRsbm5vb4BtcmhucHRuaGhlb251dHFvb3Fuc3RucnZrbm1zdHNycGpwbtDb2XVoamZtxGJrZa9ubHN2dHFwaWxtam9oZ2tha2uC0m/XbMrQz2lkanVzbWZlbm91cXFucnN3b3NzdXFycXBwcG9tcm5wcHRzc3Bv08Bsa3N0bWxucG51085xc4BvbmtubnJpa2ZiZGRmbdnXx2tpy2tpaGttbG54b2loaGxqaMfQZWppx2hrbWltZWlmy2lpZ2lrYsTFxcdoamnDZ7doz8HNa2vQ0mxubWhqbNVzcXBya23WdGprbc3Kz77GzMzJwcRpaW5qbXViYWZnbGpwbGnScHRxbWtsbXBsa4DQzce4wr1oZmpqY2dtZnSdcHNoi2FkpK2yucXHztHIyLO+yMPKw7m1vLzHbGRycHFoaXNtaWtrcG5maGllamlhZGnFanNxbm1xcMhncGpybmptbm1wb2ttx8pqbcRsZWttacvJasdua3FwdW7JaXdm0NFvbmhpZ21sam9qbWxsc5N/BH5/fn6jfwF+i3+Dfot/gn6Kf4J+pn+IfoR/AX6EfwF+hn+EfgJ/fop/gn6Uf4J+hX8BfqJ/B35/f39+fn6GfwF+n38Kfn9/f35+f39/foR/hH6KfwF+pH8BfrV/AX7gfwR+f35+mn8Hfn5/fn5/fot/hH4Cf36FfwZ+f35/fn6bfwZ+f39+fn6If4l+h3+FfgN/f36sf4N+hX8Ffn9/f36Tfwd+f35/fn5+pn+Cfop/gn6QfwZ+fn5/f36PfwZ+fn9/f36IfwF+hn+Efg5/f39+f35/fn5+f39+foZ/AX6GfwF+hH+Kfo9/AX6Kf4Z+iX8Hfn9/f35/f5V+l38Bfod/AX6NfwV+fn9/foV/BH5+f36GfwZ+f39/fn6OfwICBACAioOBiZKOlIqBjIOLhYH09vn1iI6Tif6Eh4eHioWFg4qIgoKJiY6GhoOAipGWkYyVkqCzyryrp7u7taOYo5iJhYL4gf/3goqQkZuYnqa4wcSh/+/4gvuLjI2KjYX5gYWLiIKDhIaUkJOKhYmJh4+QioqEiouLiYeQhPv5g4iUk56AkI2Oj4P6/oiA+uPz/IiKhfr2/f+OhoWEi4aHjYGMiICLhoKKjYuF94aHjo6XlJaRkPXy8veFiIiSkIWVjZmRi4yMj4iPkI2Tm5qNhoqXkYWMh4mPmJSTq46NjIaCko+HiI2Lj4mLhIiMj5SGh5GHjIaChIuNi/yChIj/jIKCiouAgpCFiJGNgIaNgI+Vk4OnnYyTjaeNjYePpZufm4+NjYeI9+eWoITnioOKi4n07/X1/oaMiJSXhOv9g4qRiYOLkZuVkY+OjZKJiJWWlI2Qn6+fkZ2aj4+PjoWOioWAh4z/hIaDjZSRjo+MiIyQlJWUjY2RjZ2Yi4uKhfmA9oaNko+AkpSOkpCckZyinJKOk42Qj5udjoiFkZGUlIiGh4yYjpD+j4eLko+HkImOioeQh4eFh5WMjYWMkZGSjpaPjouEio6MlJGPjYyNjZCIjY2Ki4uSkY2Ri5COkIyUko6LiIuIiI6RlZSPkJOOjZeLj5iPkYyQjoT79/Lm5eb0+/yCgY2AjI6NkYiLiIaNjZCSjpOBiYqE8+Dzj/npkYqsoZOTh/OO+un09oSI+YaFh4OJio6CjoWIiISHi4afl5GJjIuLi5CPjomFg4eGhYWGioyIgIiIjZKLgoiE+KaTiKOOgLa2yNzx6fiG7/f8/I2Tj5SOgoqWioOC84KKjIWIhomQjJGAlJGOnYyMh4iNjYuNjoKMlp6Nj4eEgIeUi4qKgpeMiYiHhYby/IeHiISRkoiI/fSHiZOMk5Gek46NhoDqk4yOk4uFhYmHgISC+/ySkZu3saSWjoyOkJCXk5yXmJiZl5STjY2JjY2MiYeIjISIhoL6/YiKlJWJjoaHg4eEhf3+i5GAj4uLgZOci4qTgYPx6IOBi4+DhYiGhISIhYqKjoKKg4GE+oOHh4GQi/3+hYeHiP6GgID9g4yLiJWZj42EgPf4gPvo/4CQg4SBiPuKg4CB+oWThYePi4CIiYqLiY6Fi4mD8O/78erg4+zug6nR2p2VkIuJj4uFhPaFjI+SiouCjIaAg/T27PHj54WKhJGLhIOPg6ey7KjP19ve1+jq2+Li6uTe3+nX1dXT0NHZ5IWDhJuro5GfpKaYiamOkZGlkZaLiIuJjZSTi4SEo6GKiI39hI2E94SGhYqKjYOIhIqGgPGA+ISBg4iHhIaDgYL5gYeKgI3/hYmPhISFhIWC/IWFhpCAgXt5fYeGin54gXqBfHjh6e3ogIGGfet7e316fnp5eIB9e3p/f4N7fnh3f4SHhH6Gg4+draCVkaCdmYyEjYl8fXnhduzlen6AgYyJjpKfo6mM4tzseeyChoaBg3rid3yCfnp4eneDgYaAfICAfoWEf313fX19fnqDeuvneH6HhJCAgoCBhHzr7oB669Pj632Cfe3n6+2DfXt7gn19hXqCf3iCfnuAgn545Xt9gn+KhYaFherl4up8f4CHhHqIgYqEf4OCgn6DhIGFi4yBfn+FhXuEfX+DjYeDl4CBgHx4hIJ+fIOCgn6Een1/gYh9f4Z8gn55fIOFhfB8foHugnt9goOAe4h8fYWAcnmEeYKIhnSUjn2GgJaCgXuAkomOjoN+gnt949GEjXbNgX2ChYHt6+vm9H6BfYWJet/0fYGFf3uChYqJgYCAgomBf4mIiICBjZuOgo2NgoKGhHyFgHx6gYT2f316g4qHhoaBfoKGh4aEgYKEgIyMgoOCe+p57n6Bg4OAhYiEhYGMgoyTjYaBhoODg4yOgn57hoWIiXt6e3+KgYPmhX5+hoh8hYGEgHyFgIB/f4yEg3yCh4eHg4mEgoF6gIN/hoeFgICBgoV+g4OAgYGJh4KGgYaDhICLiIaDfoF7fn+Ch4iFhYuDg4uAgouEhoOIiIH07uzd29np7ul4dYGAfoB/hnt/fHd/f4GDfYV0enp34snVgNzLfniXjoODetyD5t7m5Ht/6Xx7gHl/gIN3g3l+gH1/g3uOhYJ7f36AgoeFhX59fH9+fHx9f4J+dn58gYaBeHx43JJ8c4p5b6Ojt8zi3OyA5Ofq7IWNiI2FeoKLg31+5nuDg3p9eXuEf4OAhIN/jH19eHqCgICAg3V/hY+AhX17d32IgX+De4qCf4B/fXve7X19fneGhH5+7ed9eoWAhIKMhISCfHjYhYKDhoF9fYF/eH1+7+yGgoeak42DgIKEhYaKh4+IiYqLiYeGgIJ9goKBf31/gXt9fnrq7H1/hYV/hn+BfH99ffb0gYSAhH5/doaQf3t/dHnk3Hdze4J4en2Ae3h5en5+gnp/enV463l8enSCfeLnen19feZ6dnXqeH59e4aHgYB4dePld+ze9nqFenx4g/CEe3d774CHf3+Gg3l+gYKCf4V7gX144OLv5uHY2N3cd5CssoSCf358hIB7e+d8gYKGf4B3f30jfers4OjZ13t/doJ+enaBco6VzI+0wMnQydvg0tnV397Y2eCEz1nOzNTcfnx7jZSLfYmOkoh7kXp/fpKCh3x6f36Eh4V9dnaQjHx8hOl6g3zme3x7gYOFfH12f3l353vmend5fXx5fHp5duB5fH50gOl9foR5eHl4d3fqe3p8h4Bxa21vdHN3amdta3JraMfR08dubnZv0GllampuaGhnbmxqa21tcGlsaWhvcXFwa25rdHuBenRxe3N2bWludmpsa8Jm0sJpbm9udnR2dHd1fWy3vtNp13N9em9vaMlobXFubWhqZWtpb29vcW9rb3FtaGRraWVoaG1qz8tpbXJwdoBrbG9vbdLPbWnMvcfMbnJv2cvRz29ua29za292aG5vam1ta21sa2PJaGhsbHRwcHFw1NHDz2tvcHZvaXJtdHJvc25tbnFwbG5wc3BvbHN2a3Nsa216dWZzanFwamdtaWxqbmtqa3Nta2trdG1wdGtxb21vcXR10W9vb85xbG90dYBsd25udG9kanFsbnNxX3J3anRuem5tbWxzbXJybGlsaGvGtGhuYKJtcHJ1b9PW0Mnab3JsbnFnyd5wc3Jua3FvcXNtbW5weHFtdHJybm9yfXZudnRwc3Vwb3Rwbm90dd5zb29zdnV3dnJucXNydG9vcHFucXBwcnRw1Gzbb3JubYBwcHJxbHJqdXl2cm5ycW9udHRua2pxcHF2amhnaHNsbcRwbGpzeWtycHJua3JycW9vdnN0b29zcnFwcnFwcWdrbWZqbmxpam1vcmxycm1vbnVzcHVvcXJxbndyc29sb2pwa25xcXFvenJzdXBweHJ0dXR0b9fV1cO+wc/SzGhibYBpbWlyZWxlYmtqbW1pbFxiYmTAp61mtqRiXnRuZ2tmu27My83Eam7MbWlwanBucmZtZ2xubnFyZ25nZ2NpamxxdXJybW5tbGtqam1ub2xmamptb2tnZGO3b1xVaFhTg4qet8vJz2/Pzs/Tc3h2eXRucnZzb3LNbG9xZmlkZ25ra1Rrb2twZmlmZWxob21uZ3Fuc21wamxobnRwbHJtdXFycHBvZ8LRaW1rZ3NtamvMzmtia2ltbnBub29ra79vcG9ucG9pcG9rbnLV0m9rbXBobGpscnSEcoB6dHR0dXJwcW1xbHFwb25ub2xrb29qy89rbHFvbXJtb2ptbW3e2XFxb29wZ2x5cHBuYWrLxGdlaHBlaGdsa2dkZ2ppa2psZmBkyWpua2J1b8rIZ2tvasBmZWbMaWdnaG9va2plZsDBZc/E2mtxaWtncdV0a2lt0G9zcHBycGpscIBtbm52bG1oasjO1s/OxL/CvmRve31iZ2dranFvamvMbW5vc25sZWlscdjWxM7DuWpqY2tqa2ZrX2ltm3GOoqy5tcPKwMzLzcvHyM2+vcLCvbi/wmpoZnFvamNqbXVsZGxfZGN1bWtkZmxrc3JybWVkcHBsanHNa3JuyGtqbnNzdShvamhvZ2fQbMlpZGZramZqbGxmvmlpa2Jpw2xscWZpaWhnaM9raGtzjn+EfoR/AX6qfwR+f35+jH8Ffn5+f36GfwF+nH+Cfop/BH5+f3+EfoN/hH6TfwF+iX+EfsF/BX5/f39+pn8Gfn5/f39+hX+FfoZ/gn6mfwF+mX8Dfn9+pH8BftN/iX6VfwZ+fn5/fn6HfwJ+f4R+A39/fq9/AX6Gf4d+AX+Efot/AX6tf4J+iH+Cfox/AX6Mf4J+pH+Cfox/gn6Nf4J+lH8BfoZ/gn6EfwV+f39/fop/Bn5+f35+foZ/AX6EfwF+kX+Jfo1/AX6Kf4Z+i3+ZfqJ/BX5/f39+jH8Dfn9+in8BfoV/AX6JfwF+hH8CAgQAgIKKho6IhYiLhoWHhYOLhfqAhIH7i4iE+/6B9/GJj4uOjY6FgYiSh4GBh4SHko6eq6y3pY6KmpOiko2Fge/+/4qVrqqhoaKfmIiTqL+/upDj1dn08e6GhoiCgomMhYaGkJCKioiAgv+Cg/qDj5aOiYmDhYuHiYiHhP+LhIOTiY+OgIiFh4SFgoWBiYWD+/r4+/z6+vOHjIeGg46QkoeJhYiJjYqNnJqG/YuRlJGNlZCXlpCJh4GChoiKjZKSkZOKjouRi4+PjZWXjIGLiYeEj4/9iouNj4+coZOGjZKKkI2JgY+dlZaTi4udmIuPhYiPiZSRjYWKkIKBgoiPiY+EhoSNgImPhf+Cg46Mk4WIi4eGj7O0hIOLi4aAj4ukmJKDkOKOjPvtlvDkooaJjISKj4uD9vmCh4+Qi//t+faIjIiOjIqNl4uMiZORjoiMjJGQi6msqJSQjImUjYaPj4eAhoSGiJCSi4CLjYePlY6Ojo2XioWKk4qFgYyMiP2BhouPk5CPgIqTkZiRjI+MkJyOipSZlIyKk5GWi5CIiY+PjoyRiY+YhoOIk4+Ih46TiImJjZyOkomIjYiSkIyLjJGTjJCVkIuGgYKJkJaOlJONjIyLiYyIi4yQkYyPkJSOjI6NhoOHgYeGiomJkZaSjI2TjYqPjYuKjo6YjYb8/NLJ7uXk5fn7gIH9gIOJjYqIjImJjZD6+P769fGFjor0zpOiooXfyfqGhff6/oSPiIGAioqD/4SMh4mJjIP0iZ2lpoCLiIiGh42KhY6FhI2LhYSKiYaFgfmAh4H+8YP9iJuTiJKP0MrQ1OPr6/aBhomFhIGCkZKHk5KMiI2OmZagtaKQiqKkjpWOgJeOhpGPk4qJiYyGkpKMgoyQkI+Lh4uSjomKhI2GjImAgvv5+YiCgJGOjvr3iYCFhIqKkIyQh/qnjYuQjoqNgoWGj5iVioaDhfmAmKGfvKqhnpaPlI+cnpqUkJaZlpKPkoyJjYuJioqMkZKNifn/ho6Uj4iDjouK+/SA7ID184OLgIb0gIyXmIaB4vnohYmG94SDgoKJiYiMiYWCgIqEjIOGhoSJiYSCgYWAgIaEgoSBgYTw+4aNgYLwjoOKhoiJgoKC7/uCh4mMnI6I/4OL/Ij4hYaMkIeH+PyGioWMkI6Ji4OCioL8+ebb6O//hZq6uJmFi46J/4WBgYGJkYmAgouPgIWDgfHl3tzejYqMjoWE8YCbrYnf5urs3ubo5eXp2OLg5unf3dHb1t/v5/aC+fCDnbKnr62hp6qrmIrfueKNiJKWjJSKgY+LhZaomo2Kh46UkIaB/o+GjIyI/IGIhoP1gP+BjYqA+YiLi4WGjIWIgYP9h4+ekJSPi5CPgoeKh4qLgHqBfIZ/fX5/fHt+fXp/fep5fnrqgX156+555+J/hYKAgIF4d4CGf3p5fnx6gn6Ol5WfkYF7jIeTf396eN3t63+FmJSOj4+Mh3mBk6Ohn4DQyNDp3959e355eYCAent9hYV/gXxwd+16euV6hIuCf312eH56e3l9eu6BeHeHfYODgH18enl6eHx5fXh35ubp7+7t7eF/g359eoOGhn+CfXx9f3+Djox97YCEh4J+hoOJioZ9fnh5fXyAgoOChod+gn2FgIKEgImLgHeBfnx6gYLugIKDhX+IjYR6f4Z+gYB8d4GPg4WEgn6NiH6AeHyDf4mGgnqAh3x6fX+Cf4V+fnyDgICFfep5e4WBinx8fn16fpqeeHh9gXt0g3yQh4J3g82AgOnUhtfMkH2CgHyDiIR95+Z6gYaIg+7g8Oh/gX6Cf3+BioCAfYSDgX6GgoODfJOTk4eFgX+HgHuBhX15fHp+foOIh3qDhH6GiIOGhICIfXyAh4J9eoWDf/F8f4OEiIOCgH+IhY2BgIN+fouBf4eKhYGAhoSJf4R9f4KDgYGGfYGJfHh8iIWAfoSHgIB/g5CEiYCAg3yFhYJ/gYSGf4OGg398d3l/hYqEiYaAgYCBgISAg4SHiIOFhoeBgYOCfnyAen99gn99homGgoOIgn6DhICAiImSh4L5+My75d7a0urrgHnrd3p8f4B8gX1+g4Ph3+nl2NR1fHnXr3yIh3DBseJ9e+bv8n2Ffnt6gYF78H6EfoB/gnndfIyRk3OBgIB/foV/e4d8e4SCe3iAgHx9d+x3fXfu5Hvte4eBe4OBv7zGx9zl3uh6f4B+fXp5hYh+iYiAf4ODjIiLm4p6d4yMeoF8gId9c35+hH59fYJ3hoiFeYOFiIiEfoCGhH1/eoB9goF7fO7u7YB4doWAg+nmf3Z6eYF/gn6CeduUfX+HgHyBd3x9hYuHg4R/f+p2jJCJnY6LjIV+h4WOjoyGhImKh4OChoF9gYKAgH+BhIWBf+ntfISIhH95hIGB7ed323nn5n6EgIDld4CHiXx41u/dfX585np3d3d8f3uAfXp6dn99gHl6enl8e3Z1d3x3eHt4dXh1eHra5H2CcnbXf3d8d32Benp54+x6gYGEjoJ/83yD63/pfoCFh39/6PF+gXyBhYN8f3p6gnvt6tvO2uTweYWcloV3fYJ+6Xd3eXh9hYB3en+CgHx6eubc1tLQhIGBgHl62HKFknXAyNDbztXb2dre0d7c3uDW2M/X0t/x4el67+Z5iZiOlJSNkpKOgXq9lr17eYSJgId6c4F8e4aPhIJ/eIGIg3156YV+goGA8HZ+e3jeePd4hIB15nt8f316fnl/eXrkeoGMgYJ9f4KBdXuBfn+BgG5ybXRua25sZmltbGlvbMtqcGfHcGtozdNpzspucG5nam1mY29zbGtpbm1mbGl0eXV+dWtocXB6a29sasnUxmxveXhzc3Vva2NocHh2dmiysr/Ux8Zvam1sa29vaWpscnFucm1kacptcMxtdHlzcWxmaXFsamZoac5ua2t2aG1wgGtsamZnZmlmaGRmy8vX2djY18xxcW5nanFycm9xbG1samxsd3JszGtwcWxrb2xuc3Fpbmhpa2pwcXJrcXJrbm5zbG9xa3FyamZwbWxqb27ObXBxcWducHBqbHRra2poaWx4bGlvcG91cmtwZmpub3V2cGxwdXFtbW5vbXJwb3FygHN0b8hob3dwdWtqaGpoZXV5Z2docWtkbGRwbGplbrZoacexbLGrcmtzcG5yd3RuytFwc3R4c9DK1s1wb25xbG5udG5raG1vbW90bmxxaXNzdXNycW5xamxxc2xtbGxvbnB0d291dm91dnN2dW5ybW1tcXBubXVyb9hwbGtvd3JugGxvcHVpa2xqZm5saW1wb3Bvb25za25pbW9wbGxtam5waWZpcnJtbm5wb3Bvc3dwdm5ucGt0cnJxcHFxamxvbW1pZ2dpb3VvdHJwcW9wbnFscXV3dnFxcXNwcHFwb29waW1scXBsc3FwbHF4cmxvcm9ueHaAdnLi37enz8jLwtLQgGjSaGlnam5sbGhrbmu8vMrEsq9fZGCriV1kYVKbk8Nubc3T0m1ubW1scXNs1XF3bXBucWe8aG1sb15qbnJwbXRuanNranRuZ2hub2dpY8xobGXKyW3RZmdiYmlpnqWxtMvU0NRucG5tbWtpcHNtdnRqcG9vcm9tcmhgXmxsXWNhgG1mXGRkaWtnamxjcXd0anBucnhyb21wbmxvanBwcnBrb9TVzm9kaXJrb9HLa2FmZ29sbmhqZLV0Z250b2ptZ29wdXR0dHdyccxmdnNtdGlpbmtpcG91eHVwcnFwb29ucm9rbnFtbW1vcXFvbtLVa3N0b21pdG9w0s1ouWnIwmxxgG7NaW5wdXBswtLBbG1ryWdmaGlqbWhramlrZ29wbWdpaGZmaGhiZ2xoaGllZGVmaW7Cz21sYWe5amdnZGxtaGlnxc5scnJwdG1u1WxxymzMbXFycXBwydNrbW5ucW5na2prcmvS0MW6vsbPaGlwa2djaG5pyGdtbGptc3BqbWtugG5qa83KxbmvbW5vbGhpul9na1mYo6/BvMPDw8nLwc/Ly8fAyb7DwdTiyMZp0cVkaHFsbXJtbnBpY2GSdZNiY2pwa3BoY2tnaGtvam9sZmxycG5qy3Fubmxw1GltZGe/a+Bnb2tlzGxobXBnaGRramm8ZWdwa2pqbW5uZmpubWtwj38Nfn9/f35/f39+fn9+fqB/g36Qf4Z+kX8Efn9/fo5/AX6Sf4h+k38Bfqh/AX6zfwF+mn8Ifn9/fn5/fn6Jf4J+hX+Efr5/AX79f4p+An9+i3+GfgV/f39+foR/CH5+fn9/fn5+iH8Bfod/AX6Zfwh+f39/fn5/foZ/iH69f4N+hn+Cfop/AX6RfwF+o3+Cfol/C35+f35/fn5/f39+hn8Hfn5+f39/fqJ/gn6EfwF+iX+Cfod/Bn5/f35/foZ/gn6Mf4d+iX8Bfo5/hX6GfwF+hH+YfgN/fn6Mf4N+ln8BfoV/AX6EfwN+f36EfwF+in8Bfo9/AgIEAICOkJGPk4yJiI2O/46MhoWKi4CDhoOJh4mJjIyNio+QjYqJi4aLiIX5kY+MkYqH+J6mjIyK+u7r7/jv9YaLnp+jpJOSmaefkJGYtbCg5czp7e3s7PuFgIuChY2Jh4WGiIuIh4GBiYeB/PyDkIaFhoCFiY6Kioj3hIyLioqDhYWMjICGiYaOmJCIhoaNjYWChoSDhIiFioKEiIyDhJmO6eiBh4+WkPbwgoWVlJSNmpyaqqOm9oCMh4T7io2Ml4yPnJqOk5GPi46HioCGkIqIj5SIh4mRnqKWoZ6Ug4aGiIuWkJSJnY6QjoWLjZL76YGGkIyPg4KBiIiBh4OGhYeE/oCGhICMkomJiYiKiI+NjI6HkJGnuouWiPv6hICGmJSkqoT6++WGl4mJkZeEgIaKk5KOlYXx4fiLjoz66IP++IOKhY2TiJaHhY6LkJqJlIaNl5mkoKeZiIX4hI+ZjqmBgYiPlIqJhIWHi5WRlo6ViYiPkIaFiouNgYaIjYqBguiDh5OVk4COjpOYmJiLlJShko6Kj5WXlJKYkqGQoYqEioaSgf6DkY6HiYuKh4mKjJGQloyNipKRjZCEiYyNk4yLi4yQj4yOiYWFio+Li4+RlI6OjouJiIuMjZCNj46OkI+Iio6RiYiEh4mCiIeGjo6Ujo+LiZWPj5KQko6LhoOG9dbF1t7d5IDq7PLn/PHn8/P7g4WG8OLg1s7gie/SxfSo04aPkIWbg/yAgYOB+YWC/YKFhoqE+4CBiIqB7oyUnpCH/4GFhouJjYmHiI6HhIWBg4WDhIODh4WEhoaKhI/1/vHs48vQ4+z/+YKHg4WJgoqCg4yVkJSVlJOTk5mssJyXkouLmJuLjICNjYWChYeJjYKNk42Jh4+UjJCOi4eTk4qTk4qPhYWGgoL9iZSIlImRiomKiIqF2PWLiImSjIywkYmSjfyJ+IyTjoqNjoiJi5GO+IGNs8qglYyTl5mclpOZlY6Tk46QkIqCiIuNiYuVk5CQh4D5iouGiIaKg4H8+e/pi4WI/vL+gYCKkJGFio2C5fSJiYKFh4SHiYSDhIODhoKB/YGGgvf+/vz8gfz3gfr8gf+FhYKBh4KF/4TyhouH6fT5/u7t7YCJjI2D/vaEgYH7iaajh4P9gIOCiI+EiIaAi4H+gPr1iIaBjPuH8ufs9Pfw9O7ni7fDm4uLi4iD/4b6+5GNg4KFgICG+fnz7vfy3t3x9PuBjuCB6pCJ6NXm0N3Y5dXX6+Tg3tXY6tDV1crP19jmjISJjaKTlJOlq56kn4vqv4P654yTkJGRk5GRk42TorCViZCRj4WDgvqIhoeIiIGQkoyNjoCF/YGGhIOCjIiOjIaUjoyPi5CNjpCLkZGPjomOlJaTjYCEhYWCh4CAfoSD64GCfXp/gHd6fXp+e318gIGEgIOGg35+hHuBfHvohYSAhn5/7JKVf35/5+Hd4ubf6XyAi46Qk4SEiZWOgYGHn5qQ0Lzf5d/e4Ox+eoR5fIGAgH5/gIB8e3Z3gX135+N3gnx8fnd7foJ/fnzkfYKAf4F7e3l+gIB9f3uCioR+fHuBgXp5fXx8fX94fHd7fIF5eoyD19V7fYSJhOPifHeChYeBjY6KmJKV4nR/e3rpgIKAjIGDjYyEhoGBgoR/gnZ9hoB9hIV5fICEjZGGj5CHdnx7fX2Gg4h9j4GEhn6AgYjv2nh6hoSGenx6foF5gn19fH9+83d+eYCChX+AgH6Cf4aDgoR9hISToXyIfunrfHZ4h4GQlHjr79V5h3h6gIl6dXyBiYqEiIHo1uiChYPu4Xzx43h+fH+EeYZ7eYN+g4x/jH2BiYeQjJWNfXvoe4OKfpd4eoGDioOAfH1+f4qEioKLgICEg3x7g4OFeoGChoJ6fdx8gIqJhYCEhIqMiYx9hoaOg4OAgYSHhISJgZWEkoF4fnqFduh4goN9fYJ+fICCgYSCi4SGgYiDgoZ9gICCh4GAgIGGhIGCf3t7gYSAgoeHiIKCg4GAf4KBgoWEhYOChIV/f4KGf396fX97gH5+goKJgoGBgIqDhYmHi4eCfn2C8NK/1NnV2IDh4N7Y7uHU3+DmeXx83tPTxrfEe9K8sd2Ns3J9gXiOffJ5en966Xt57Hp9gIJ973l5fn9y1HqAi4J87Hl9fIGBhYGAgIR/e315e3x6fHp5e3l5eXt+eoPe5tja0cHG2eD08HyBfX+BeX14eYSGgYaLh4mIiI2YlYKBf3p5hot7gYCBgnp3eXt9f3iCh4SBfoWJg4WCgXyDhn+HiIGGfHx9fH3wf4t/iH6EfoCBfoF90uuDe3+GgHqbgnuEgOl/5oGEgX6BhICCgoWD53d/mKiJhH6Hh4yPiYOIh3+Eg4CEhIB6foGCfoGHhoSDfHrrf399foKEennv6+fegXh98uXzeoB9goR8gIF53ueCfnZ3fHd8enh6e3t6fXh46nZ5dt/m5+Pkct3ieeXoeO95eXZ2enh77HnjfH171+To5t3d4XV9gYF47+p9e3nogJWQfH3vd3x7gIZ6f3p1gnjvd+3lfH11ful+39nh6Ovj5uDYfZuig3p6fXt47X7q64iCd3h7eGJ96uvh2+ns2NLg3+Zzf8BwxXh30b/Xws7N3MnK4NvZ1szQ4srQ0MXKz8zXhH2AgZGDg4KPlIiNiHrQqGfAw32BgYOChISDhYCFjZeCeoKEgHZ5eOd+f4F/f3mFhYF/gXV+64R5GneBf4F7eImAe4N+gIB8gn2Bg4GBfoGFiYaBgHJ0cW1wa3Nuc3PQbm9ram5uampsaWxpampvcXJrbXNvaGtzam5paMZycW1yaW3Nd3ZrbGrLy8zIwcHNa2txc3V1b25vdnNtZ216dHGzpMvRycfKzmtsdGpucW5tbm9xcGptaGhybGfRy2hva2pqaGttcGxqacVsbmtscm5samxugG5tam11cWtpaGxraWlwb29xcWptamlrbmhqc3LQxG1pbW9vwcNqZnBubnB1dnJ5d3vCY2lpbM1xcG10b3F3dnJxa21wb21vZWx0a2xtbmdrbm9ydG10d3JkbGpoaWxuc2pyam1ybXBtcdTHaGhyc3JqcG5vcmx1cGtrbXDYZm9ogHBwb29wb3BwdHNzc2txbnR6aHJrystsaWNqaHJ2Z8zSt2NrYWFmcWtlbG93eXN3c9bH1XN2cdbPcNjHZWdqa21jcGhkcGxudHB5a3B1cHNxeHZpac9sbnVoeGhwc291cHBrb3FwdnF1b3VvbG1ramdydXNsdXNzb2xwx2tweHVugHBwc3Vwd2hwcnFobW1ubHNtbm5qd2t2bmVpY2pkyWZpbWxqbW9tcXJvb3B3c3Rtcm9wc29vbHF2c3NycnJwbm9saWtxc29yd3d4cnN0cW9tb3BxdHN0cnFycm1ucXVtbGprbGtycG1wbnVubnBydG9zeXN2dXNvc3fbwLHGy7zEgMjEvsHRv7q/wMRoa2bBvb2ulaFiqp6WuXWHV2JoZXhv22xpcG3LamrOa25zcW7YbGpsa2GrXWFpZmfJa2xsbnJzcXFwb21rbWlpaWhpZmdoZmZiZWhmabe+r7q0tLvDx97abnRxcXBpaGprcnBscHpyc3J0dXRpXWNmYWFrcGRvgG5vaWZmaGlramxzd3RscnJucXBua2tva3F1b3BoaWxtcNRsc29xbG5qb25sbmzG1HFobm5rZXhraW9sy2zLb3BvbG5ycHFyc3HIZWxzd2ppZ3Buc3VybXJxaW1sbW9wbmpubW5rbXFxcXBsbc1sbW1wdG5oa9XSyr1sYWfRy9ptgG5xcGtub23GznFramVqZGtoZmhqamlqZ2bIaGtowcHFxsVlyMdpx8ho0mtpZWZpaGvQbchqaGe5xMvExMbDYWdlaGXMzmtraspwfnNrb85mampucGdtZ2RwadZnzsBla2RozG7AwsfLzsfHxr5nc3JjY2RoaGfRbsvUdG9maGtpgG3R1MfE1NXFucC5v15lmFiVWV6wqsGrtb7Kt7nLyMfJvb/Nt7+9tLS2r7dva2toc2xqZ291bW9rYrOJSYmcZWZoa2lrbG5waWluc2VkaHBtZGprzGtvcHBwbXJvbWhpZm7JZ2ZmaWVubG1kaHVsaW9nZ2llaWVrb2ttbGpudHJuin8Bfpx/AX6GfwF+hX+HfpF/iH6Tf4J+jH8BfqZ/gn6Ff4J+jH8BfoR/AX6yf4J+kX8Bfpd/gn6If4N+j38Lfn5+f39/fn5/fn6ZfwF+pX8BfqJ/AX7bf5F+g3+GfgF/hn6GfwF+hH8Efn9/foV/AX6FfwF+hX8Bfpx/i36/fwF+jH+Cfot/A35/fot/AX6ifwF+iH+EfgZ/f39+fn6If4J+kH8Efn9/f4V+CH9+fn9+fn9+h38Gfn9+f39/h36FfwZ+fn9/f36FfwF+i38Efn9+foR/An5/iX6JfwR+f35+h3+Lfgd/f35/fn9/mH6OfwV+fn59fpV/AX6NfwF+nn8CAgQAgJOIkZGGjJOSmpGJhYGDg4WHhouB+P+Bj4mMkYqGiYqKifuGiIyFjYGHj5ORi4ydn4z38/eAgYHygoOAgJShmpWDh4j9/uyGlZWL5uH27+z++4WFh4WHhYiGiImHhYeMioaFiIyNiIiGkJCEgIKAh42MjI6Hio2LjISEg/yIi4eOgIOLlpWShouDg4mIlJaOhISHi4qMgYqFg4GDgv746djm+IKDhIuOlJOIjZGNk5aVoZr9gIeOjIOAipKOjo2UkYiEg4aIhpOMjI6FioqSlYiFiImEg4aOj5SSjIqQmo2FhImRmJ6AhI6Oh4yEiYqViomIhIWMg/HwhoiBiYSAj4uEVYaLhouHg4SFiI2Qj4+UlKKRho2OgIWC8YWbqpSMkfD8gYHpiq+/8dP5iY6PhYH8/oaM+PuLj4bx6f38iYWFjo6WlY2Ik5GVlIyLg5CZmZCRipOZmYCEhoCao/r7jYaIloiCjI+Qi4WOjoOGiIuMkIuGhYiIio2IioyFgYWIiIaSmJaTk4+PjZSQlJaKjo2SlZeIiY+UlJWIkoqQiYyLh5SLh4OKko2Gk5ORkY2Oh4WBiI6SkY+NkJGQioiIioqQk46GhIeGjIeDiY2Ti46Oj5OPjYqGjYuPjHOMkZCPiY+Qi4aFgYKLiYOGjYiOk5GVjZGSkIyQjISFkI2P/uTLvsHY3dnk4djBtdHm+IWB9Onu8ffU/P+GhunkzpKNmpeakY6C8/6FhoiH7+6DhICBjIKFjN3lkZuhjfqHgfuIiImFh4KCiYqOhoODhoCEhIWAgvuEg4D/k4716+7/gP+LjYWGgoGK/viDho2TjpWPkpWSlI2Mn6+jipCOiIWIjo+Vj4uKg4WIj5WSg4SIlKWdlo2Oi4iUkI6JjYyKkIyOh/f0ioX/iZCNhIiEhP7+ho+VkIqWk5Ggm4mHjoeEiZCLj42FgoeBio+NjYmHlbGtn5OAl5Weo6mnoJiTj42UkZKJg4iKhoeOk5OSj46IhISQj4uMhYrj7+fx/IeEh4iFiPj1jZOD/vbv/ImKhYqFh46DhoaJ/oj8hYCEg4GCg4SGgPaDhIeA9Pvz+ICD+YD88YSBgYSE+oSEhYX5+P/36NXb+4KFgqiihfjy9POEoLKUg+mAgoiJgYqOk4aFgoOHhvzuh4aB7fXygYGHiYP77+v4gZXCxaGPjYiFgoCE+IiB+oD6hYiI/fzz7uPezMr9ioeu4qWLm4jwz8XU2ODo5evt4+Hm4t7S2dfe3Nrj/Yfk3uGNnaSI9eSIkY+M/fqCh42Tjo+SkJSNiJOSi5Cgr5mQkIwwjY6Oi5CQipaUio2Oh4iMiYyChYiIiYaF+oGDjY6NjYuLjI2SkJGLho+RjJCWkpCWgIl+hoZ9gIiEi4J8fHl7enx9fIF46PB3gn6BhoB7f4GAf+Z8fYF4gXZ+g4WDf4KQkYLm5eV3enzkeHl3doaNh4V1eoDs7t59iIeC3Nfo4uPz7H5+fnx+fYJ+foGAfoCCfHl5f4KBenl4gYJ6enx4fYSBgoN/goKAgXx8eux+fnqBgHh+iIeFe4B5fIB/hoiBfHp+f4CCeIF6eXV3d+jd0sLV5nl6eH6EhIZ+g4aAgYeFkI3rdX2EgHp3f4aDg4GFhX56eXx/eoiEgYN6fn6GiH15foF6e36FhIWFgHyCjoF4enyCh412fIKGfYF6f36HgIKAe3uAeujlf4F7gH14hIN8gHyAfoCAfH19foKChIWKiJGBeX2Adn1513qJk4N+h+Lye3bHdpWl2cDigoaIfXfl7oCE5+mChn7j3/Hsg3x7hYOJiYV9hoOFh4KBdYCLjIKDe4CEi3l9fX14hpDo7IN9f4d/eoGChIB6goN4foCCg4eCfHx+foCDgIKDe3d9gH99gIiKioiIg4SBhoKGiH6CgYWHi319goiFhnuFfoJ+f398h399en6DhHuHh4aGhIeAgXuAhIaGhIKEhIOAfn1/foOGhH9+f4CEfnh9gIWAg4WFhoOCgX6EgoWCg4iHhX6Dg398fXx+g4F7foV/goeDioKHh4mChIB5fIqIiffdxri8gNHW09rW0K+gwNfnfnfm2+Df5cTd3nh70s60fXR/gYeEhH3p735/goHl4Ht8eHiDeXuDzs59iZCA54B78IGAgn+CfHuAgYV8e3x9eX19fXx9eup8fXrwh4Df3uT3fPKChH1+e3Z/8Od7foOIg4eDh4mIhoGDjJSMeIB+enp9gYSKgIaCgHt8f4SIiHZ4f4eUjYmCgYJ+h4SBf4SAf4aBhIDt6oJ763+Dg3l/fHvv636HioeBi4OCjox+foF9e36DgIR/eXh+eYCEhIWAfYWVk4iCiIeQk5SSjYeEg4CFhYh+fIGBfX2ChoeGgYB8enuEhICDe4Ha5Nrf6nx4e399feroFoCEevHt5Ot6enp/eX2CeXx8f+9/736EeoB2dXp8duV6fn536vDl6np75nXn2Xd3eXx75np7eXnr6u/l2MfN6nh7dpWOeeXi5uR7j5mDd9h5fYB6gIKFfHt4e35+6t59eXXe4uB5en19eefh3up4gqGjiIB/fHp3d3rifnfjduZ8fX7p6eTg1dPFvuZ7d5jEj3aGet2/t8XN2Dbc3OHj1tje2tnM0s/c1tXb8oDU09SAio533Mt5gH6A6+d2eX6FgX2Af4OBe4OEgISOlYaDgn+EgiyHhX6IiH+Cg3x9goCBeX18ent9eud2d4OAgX56fXx+hYGEe3eChn6Di4eFjIB1a3FxbWt3cHdvaWlqbGpqamtvZcbRaGxqa3JvZ2xvbmzIbWluZ3Bka25rbmpudXhrwsfDZWpvxGZnZmhsbGtsYmZuy8zEbHBrb8fEz8rP281sb21sbm1wbmxtbGxvcWtra29wcGhoanFwampqZGtzb29wbW9ubm9qamvTbGtma4Bna3Bvb2htaGxtbnBxb2xnbW5vbmtxamplaWbEv8PG0tRoaWltbGxxam1vbGpubHJzz2Vqc3Ftam50bnBtbm9vbGhrbWh3dXBxZ2tscHJtZ21waWlucG9wcWpmbXhrZ2ZmaGtzam1tcm5wa3Ntc2xvcW1rbWzXz3JwbW5uaXJybYBrbXBrcG9vbm5vcXF0d3J2amZlaWRtarpqb3RsaXDG12tlplxterSnwXN4eW5pztlwc8nKcnVwys/f1nZraHRwcnJ1bXFubm9sbWNocnZrbWhmanFpbWtsZ2lzy81ta2tub21wcHFvaXBuaW5vb29zc2pnbGxubm9ycWpob3FubIB1dXNycW1wbW9qbXRsam1xcHVtbmxyb2xmbmxtcG9tbHFramttbnBsdXR1dXN2cHFsb3J2d3JvcXN0cm9tcG5ydXJtb3Bwc25rb3J2cnR0dHVzcnFtcXFzcnJzc3Jtb3Jua2xtcXNxamxzbXFvbXRrcXN2bm9sZ2t5d3rdxrKttYDBwL3Gw72UiLC/zG1ozcXFwcCisrNlZbGukl9UXWFubXNw09RvbnJzzclrbGhnb2hobq6oXmluZMRtbdZxb3FwdG5tbW5xaGlpaWhta2tqamnMbW5szm9lusTK3G/bcXRua2tobdHIamxwdHJybHZ3dnBucXNval1mZWZnam5ydYB1cW1tbm9vc3ZnZHBzd3Vzb25xb3FxbmtybW1ybG5vztFvactrbnFobWxr0MtscXFwbXRqbW9ya25zbGlscG5xa2Zobmpxc3N1cG1ub25qanBxd3Z2c29ub3BvcHBzbG1wb2hobHBxcm9taGlna25sbWh1x8W7vMtoYmVpaGnPz2VydGnX1sfLZGZpbWhqbmdraW7LcNNtampubmtmamtnyWxubWrQ083TZ23NZs3CaGhrb2u/ZmloZ8jMzcK8tLPBYWZidG5lwr/KwWl0dWpnvmZsb2xwbWxkZ2Vsam3MvGdjYMLHxIRsgGfDwr3HZ2dzcmVlaWdmaGdpymxox2fJbW5u0c/RyL27sKjGY116nm1ZamO6paW3vsfGxMzLvcfLw8a6v7bJw729zmqxsLBobnBgtqpiZmVsysRhZWpramhmZ25vaHBtcHF0cWtubGlvbXF0d3FscXFucG9qbnNtbGdsZ2Zob2rGF2dmbmxtaGZpZWluanFmZm9yaXB2dHJ4lH+Cfot/AX6Pfwd+fn5/f39+i3+DfoR/h36qfwF+n3+GfpB/AX7Df4J+oH8BfoZ/C35+f39+f39/fn5+hX8Jfn5/f35+f39/hH6gf4J+/3+ff5B+gn+IfgV/f35+foh/gn6Ef4J+iH+CfoR/BH5/f36Vfwd+f39/fn9/hH4Cf36Hf4J+uH8Ffn5/f36Hf4J+xn+FfoZ/BX5+f39/hH6LfwN+f36KfwF+hH+EfgZ/f35/fn6FfwF+hH+IfoZ/hH6FfwF+jX8Ifn5/f39+fn6Ff4R+jH8Jfn9/fn9+f39/iX4Ef39+foR/l34Ef35+foR/gn6Ef4J+rX8Bfpd/AgIEAICRl5OMkIiPk5GOh4qDgIiEgYSDjYSLiIGA/IeGjImCi46H+YiOjY+LhomIiImNqo2Cgfvq7oiRi4yWmpSLi5eDh4yJgomFg42MgYH7gvfz+PqAgoKFhICFhYiHiYqJjouOhoiNjomIhYmGiIiLjIudnJWPjI+B8fP+iIyJi4eFioCHjY+Jh4GLg4CLiI2JlpKTlISDj4COio2HipCPkYz85IGIhIWPjZKJkYqPlJqHiYmGi/j+g4CIhPj8942SjI2PjpOTl46QhY+PkI2Lj4SMjIiIhYaHkJmgmoWLh5SMj5eTjZ6S9YePhoyZiYSYgf6G+YWIgfbz/f+AhYqOkIeMh4CKioKFhoCNiYqLiYSNmpuYmJKUiY2G/4qQpoqko5OKh4OAjtSprO7m4vqEhfzp/4b8jIiA/feLku/n9YCKh5COlJOfioaJkI2IiIyKj5OYmpiZlpGgg4mNoomTgoKFhZGGmY2DjJiJj4aLhIeEhoWMgouLhIyHi4iAhIOBi4iMjEmJkpKNkomGgJGWlZKLiISOk5WSiZSSlpaVk4mOkIKE/ZCMjYH+gpOSj4qTkIqLioyHhYGFjZOQj4+Nj4yRj4uPkoyRj4yMjIqLhI6Ak5WKkpeUjY2OjYuHhomJkJCPkIuQjoeEg/v8hYOJioeHjo+RkomIiomGiJCNj46PjJGHhILhy8fBz8LX2vLu8uTwhObv/vP0mY/YvMD6+7CFmJeKi46D+ICMkIT65eCRi5KWl5CjpIGMmfmG8oSGi4aMhIeIgoeDhoGDgoP8gYCAg4KKh4aHhoOJ993qgIOG/vj1iY+Fgo+MiouKlo+QmpWTkJSYk4uToKWiioiBhYCGg4CIj4WNj46QiYmJgYGImJGOjIuKiJeUjI+IiIyOh/709O2Aj4uFgIiGhIH5+Pv/hZSbjo2Nh4aUjo+QiZGRjoiGiYqHh4OFhImLjY+TqKeAoJubn6SkpJmYkY6KjJGRjpCKhYaHiIaPko+OkY6Jg4WHgoOB8O3q5e/7hoL/i4qJgOny84Lw9f+Fgob+hY2JgoKBg4H+iID+94OG//79gPj+gfnq8enk+Pjy94KE/oOKg/+BgYWC/v2Dgvv6+fPq9NnF2f74gIWwvKSF6uz5hKmAu4Xn/IOGiP+AgYyIgoTvgPqHjo2Gh4aD+vn3iIGFhIH/+ObmntfFmoiJ/Pr7//OIkoKB/fn3gYL7/+zr+ufw+erG7eXt/4b+6e3n3vLx7fP94Onu9/Ha2NDZ09HT1vf09f3+io+A+/OQjoqPm5aKioCMlJGRmJibj5SRjY+Lm6gzk4aGhoyFiIeNi42PkoyTio+Tk42Fi5CalZeMi4uNjI6AjNS4qoGjlIuSi5GIj5OYiomLgIOKh4KFfYOGhIJ+f3hzfHx6fXmBeIJ/dnfrfHqAf3iBg3/lf4SBgoF/gYB+foCZfnh55tPbfYaAf4iKhn98hXV9g354f315foF6d+t56efs7nh6enx7eH1+gYCBgYGEf4J8fICCfnx6gH1+fn+AgY6MhoKBhnvk5vKAgX5/fHt/gHp/gn1+eYJ4eIJ/hX+IhoaIe3mEdISAgHyAhIaGg+nOdoB7d36CiX2Ef4OJjXp+gH2C6fB8eH566unihYmDgYOBgoSMhIR9hISGgn+Fd4OCf398fXuCi4+OfIB8h36AhIF9j4jifYN7f4yAeY138H3ke3935er19Xl+gYaIg4Z/gIF/eX1/eoSBgYB/e4ONj4uFhYh9gX3uen+Qdo6OhH1/e3Z9tZGV19bU73187NrwgOyBf3js532F3d/qeIGAhX+EhY+AfH2Egn5/g3+DhoiMiYiIgJJ4fH2SfIV4eXp8hnqLgHqAi4CDe4F8fXt/fIF3gYF9hH2Cf3h9fXqEfX+EgICGh4GFfnt1hImFhH59e4SGiIV+iIOJh4SGfYCDeX3phH+CePF6hYSCfoaFgIB/gH58eHqCiIeFhIGDf4OCf4OFgYWFhIWEgYGEg4ODh4mBhoqIg4SGhYSCf4KBiYmHiIOGhH98fezwf3t/gH1+hoOFhn9+goB8foiDg4SGg4iBgH593cjCvcy+z9Dr6urb23jJzODW1YaAv6eq2NWMbn2FfoOGfO55gYN65dDPiIGGhoN+jYtvd4fmgOh7foF/hn2AgHp9fH97fnx+8Ht5fHuBfn5/gHuA3sbZd3l98ujgfIR9eoSCgH99iIKHkYiIhomOiICGjo2MfXx2e3Z8eXd/gIN7gYaFhX18f3d3fImDgoGBgX6LiICCfH58f3rv5evkeYWAfHZ9enl58e/w7nuHjYKCgX18hoGDhX6DhIN/fH1+fHt5e3x+goOEg5KSj4yKjY6PkIeHgoB9f4SFgYSCf359f3yDhYOEhoSAenx+e3575OPb2uTtfXbqgIB/ed7ogOV34eDtfXl97Ht/fXd2eHp37H158eh6fe/w8Xfl73rq3ujh0+ji4+p7eux2fHfodnZ7eerpeXnp8PLk2+fLvM7u5nd6maGMedjc6XmSnnnZ7Hl7fOx4doF/eHveded8goJ6e3x45+rkfHV7eXft5NjRhrKngnZ84+fo7ul9g3R1gOjn6Hh45+nZ3O3X297VstPP2Od56Njf18/m4+bm7dTh5ejl0NHK1MzGy8bq6Ort7Xt+cuTig4B4fYqGfX91gISBgYqIj4GFhICEfYmTg3d8e4F6f32AfoKChoCIgYWIh4J4foONhoaBgoGAgIJ0gsmum3aPgHyEfod8goWLfXx/Jm5ycmxybXBxbm5rbGdlamprcGluZ3BtaGjKamhqa2Zsb2/OcHNthG6AbWlrbHpqbGvFt8BocWtqb29ua2htYWpxcGdsbmdla29pzmrUztTWam1rb21pbm5vbm9tbm9tc29tbnBubW5zcHBvb21teXh1c3FyaMbM1mxsaGtnZGZlaWxqbWpvbnFwbXFpcG9udGxnbmVycW1pbXBycnHMv2pva2Nkb3Vsb2mAbXNzZG1vanHR1m9pbmzTyr5wdHFsb21qbHVxbmx0dHRwcXVpcG1ubmlobG9yc3Nsb2hxaGdoaGd2dMdvcWxtcW5odGXWbsxsbWbH0NrWbW9udHZxdXFvbGlvc29wcXFsa2xxdXpzaXFxam1t0WRoc2Bvcmxqb25lZpRrcK+0ttGAa23Tw9RvzXBtacvHaXHEy9Rrb3FwaG5tdW5pam1sam5yam1ucHVvbW5ldmdoZXVnamVlZ2txZ3Fqam1vb21mbGpva21pamVxcG9xaW5wa25ua3BqanVvcXFwb2ppZ25ybW1saW5ybXRzb3Rscm1qcmtqb2pux3JvcWvXbHBwb2yAdHRycHBwb25sbHJ4d3RzcHBvdXRydHVxdHNyc3RwcXRzdXV3enN3eXRwdXd2dHBtcXJ3dnRzcnVwb3Bvy9ZybW9ta25wbXF0bmxwbGtud3BvcHN2d3Fvb8e4ubW6tMC52NnVxbpioaCwr6ptZ5yMj6mdZVBdaWpweW7Uam1uZsGAtbZ0bXBrZV9paFdcZ7xsymlsamtxbnJza2xscGxva27VbWtubHBxb29uZ2qyor5oZWzWzcFob25rb29ubGhybXV6cW9ydnh0bHFyaGlnZWRqZ21nZmpva290dHRsaG9nZmlybW5ub2xtc3RramVpZm1m0cbWzGpybmxoa2lnaNkr1tHNZ3FybW9ram1tbW9xa25vbm1raGpqamhsbWxvcXNwd3Rzc3FzcG5wbIVtgHFzbnBwbm5sbmxubW1sbm1saWxsbHBrytC9vsTEaWPFa25rZsbTz2bIzddtZ2nJaW1tZ2doaWjRbmvXyWpr1tXaasvWbcvCz87F08rM02xpzGlvactpZmdoyMVmZsrP08jBxrGkrMS9Y2FzdGtmvMPQZ3B0ZLzJY2tt0WllbmxngGfAZcppamxmaGhmys6/Z2RpZ2fMvrmyZHl0YF9nwMfI0NNsa2JmysrLa2rMysHEzbW4ua2XsK63wmXKvsW7tc/P0srUwsXJz8y+wLnCtauxqcbLzsbCZGZcvMFvamBjb25rbmdqaWhsc294b3BtbHVsbXFrYmtocGhub21sb2pwJmxyb3B1c21mam1zbXNvcG5tbm5kcryznGdxaGpuaHFobnFyaWptmX8Bfoh/AX6Pf4N+ln8Cfn+EfqV/g36lf4J+kn+CfoR/g36pfwF+iX8Gfn9+f39/hH6efwF+jH8Dfn9/hH4Rf39+fn5/fn9/f35+f39+fn7kfwF+hH8BfsF/gn6af41+AX+FfoJ/hn6HfwF+hH+Dfot/A35/fpB/AX6Lfwl+fn5/f39+fn69f4R+iX+EfsR/hn4Df39+hH8Lfn5+f35+fn9/f36Ifw5+f39+fn9/fn5+f35+f4l+B39/fn9/f36EfwR+fn9/i36Gf4N+hH8Gfn5/f39+hn8Dfn9+h3+DfoV/hH6Gf4V+hH8Ffn5+f3+OfgF/nH4Ff39/fn66f4N+jn8CAgQAgI6JjY2LkI+RiY2KiJCSi4eKi4iKkIWOi4yOjIaDi5KMiIWIi4mPmJSTkoyRj5KLmYb9hIWLifqDhI6UhIr+9vzy/f2DjJCXkoT4hfuD+fX6/oKFhoGJiYSDgf+KiYaGhICEipOSj46KjYyMio2IhvyChoWJkI6MjJaZkIyKh5SYgI2JgIDt5oeBgvWBiZGM+oCIjZqUk46IkIOShoSIj4mLjZOThoGFjIeOiYyMioyLjY+XiuXv/oWJh4eKjI+HiYiQiYL+i46QjZKPho2JiYiKho2RhYWVlpGWh4iKiPz0+JCbj4aKjoyIgoeaj5CHhoqI+oT9/oT7gvSCiI+IgoeHgIWOk4P/g4qIjoeIh4uYnaCblI/ohIKKi6+YlJuG94aLgd6O44egitbk1YORj4qGg5eJgYaHg/CHguLh/YKNi5GRi56Ph4mNiJmNk5KSjYiOipCYkoOChYyMop6Egf+BjpiJiomWm42GgY2PiYiHiImQkIOAiI6Mh4qIhYSPi42NgIeKjZOWlZeZ7IqTkZ6Hho+Jjp2WjYWPjJKRjJWWjIPygIOMkISDiomLh4mJjpWPi4uJiouEhI2PhYWKhYaGjpKSkpCQjoySkYmJkJaPioqRkJKTkY6OioqGhoaJh4mJg4mJi4yJjImGhImD9v+BhIeJjouLkJGMjpCJjI+GkZOWgJCQjo2KgvHc6oH48OXr4uzf0/H0w9CDl+fHvc73h/u3p4aJnJyclJHx6fSSioaB+v3i6IuVmIqDk5KKioP4goSDi4X8g4KCj4v6joOAgoSEg/73/YmL3Nny84CA+vb5gP6JhoiJioyGi5GMko2Ok5KQm5qZmqyZj4SHhIWF/YWGgIWLhYmOkYSJioaJjpCKkpCQi5CUkpOPiIeVjIeLiID5/YaLhfqB8fX7hYmDgoeQj4qMkYuNj46IgomejYyNhoeIhYOHjY6MmZuYnJuXmZqno5uXlpeUko6RkI+RkIyIhYeFio6UjZeNjouB/IL9/Prz7Pv5/4eLh/Xq8/WC/Pf8gIOKjo2GjYWJhoOCh4OLhoGFg4T+8YP+gfX77YCD/u/n5e/1/YWC7/+D+e79/v7//vv58/b08fzs8erSzdP6/t/6hviMm7nQp+3p9YOKmaONhIaAg//8i4qIjJSIiIKJgYqIgIONh4mFgoeDgYSLjfz29Z/L1badhv+J+fXq/fWEgImHgYGD/YPy6unegISIgf2NioaI/Nf38urm9PT05/P18+Tv4OHay8TJzr/X+fmEifjN64+GkZOYhoqalpaTlZWQjYOHgYyCk5SH8pCqpZCOlJ6B7oOTk46PjIeJmI+Uio+KjYT/go+JkpKRl5qGtcP6ioKZmoyQjo2QmIyOkJCOgIB+gn5+hYOGfYKAfYWIgX6AgX9+hHuDf36CgHx6goOAfnt/gH2EioWEhIGEgYd+iXnpeXqAfex6doCDdn/s5Ovm9O15goaNiXvnfet65+Ls831+fXd/f3t5duyBgH59fXt9f4aFg4OBhYSCgIOAf+55fX1/g4B9fIOHgoCAe4ODgH58d3fg3oB4euR6gYd/5XR+foqEhYN9iHmHd3p9hn1/f4aIend5gX2EfoCCgYKAgoOLgtvk831+fnt+goN7f32GfnnogYaDgoWAd4F/gIF/eoKIe3iGiISKfH5+e+nd3n6HgX2Bg4GAeX2Ng4eAfoGB63/x9H/ufON5e4R/fICAgH6DhXz0fYF+hoCAfn+Ji5CJgoHaeXV+e5iEgYh65Xt/ec+Ax3WQgcrdzXuHhoOAfIl9e318eeB/fN3Y9HyEgYOGf4uDf36DfYl/h4aFgX2BgISJhnh3fIJ7jox6eOp3hIx/gICHi4B8eIGDf4F+f3+Cg3l4gYSBfYJ+e3yFgIOEgICCg4iJiIyO3H+DgpF+fod/g42GgnyGgoOCgoiIgnzieHl/hn16gYKDfoCBg4mEgH9+f396eoGDfn2AfXx7gIWFhoWFhIGFg31+hYyGg4KGhIaJiIaFhIN+fn6CgIKAe4GBgYKBgoB+en955vF8fH9+hX6BhIV/gYeAgIJ6houNgIeHhoeEf+fY43rw6NnfzNfJt9PSprBwhs20qrfWdNGcjXR3i42MhIHS1NV/e3Vy3N3AwnV/hXZ0h4eBfnrpfH58gn3wfXt8hoPtgHt6fH58fPPv7oGCz9Dq63h57enseeuAfoB/gIR7f4aDh4KFiIeCjY6Lh5WFgXd7eXp76np9gH2Ben6FhnqAgX5/hIR9hoN/foSFhIaCenmJgH+Cf3nt7H2Be+V65ufwfoJ7fICEhIGDh4GFhIWBfICSgoGDfX1/fHt+goN/iImGiIiFh4eSjoeFhoiGhYOEg4OFhIJ+foB+goOHgYqChIN89n3x8uzk3Ojl9YGCft3b5eR15+fsgHh+gYB7g3x/eXl8f3mAfHd8e3vy6nzseOzq6Hl57eDZ1+Dk6Xh34vB549jt7evo6ujp5+fj4urd5+TOw8bl687oe+F/hZqrj9PT4HZ6h41+en12fO/rf399gYl9fnZ8d4B8dnqCfoB7eX16d3p/gOro6I2psJyHd+R64uLX5915gHp6d3V55nff19fLdXd6dOeCfnp75cTo5tvb5+rm2unq5tro2dvTxb3Dx7bM7+17fN691oN6goKHe3+Jg4WHiISBgHp9d4F2hIV72oCTkYGCho503nqFhYKBf35+i4KIf4J9gXrtdoB8hYSFhol6qbXqfHWFhnuCgYOEiH+AhIR/gGxqb2lobXBxaW1ua290bmxtbm5rcWlvamltb2pucm9sa2psamtzdnBtb2xvbm9ocGjLZWhva8xoZGxqZmvOyNHT18tobXB5dGrKas1szcXQ2HBxcGtxcGxqac9vbmtqaWhrbXN0cnNxdHN0c3Nwb9FudnFsa2loam5wbGtrZ2trgGhoZmW9vWtla8hsb3Fqw2JsanFvb29qdWVuYmhtdWhsaW1ya2dnbGlsaHBxbGprbm5zcsfP2m1ubWptcHFscG5yb2nHc3Rvb3NrZG5ucXJrZ25yaWZwcG1xZmpsa8u9tWNsbW1ycXBxbW5zbnRycG900XPZ3nPVbctpanBxb3FugGxxcXDkcm9vdW9ubm9zb3JwZ2i+Z2RrZHRmY2hkvmdpaLRlollzbLTJuGp1cXFxbXNqbGxsashvbsvG3W90bnB0bW9yb2xwbHFtcnNxa2pta2xtbWVkbHBjcXFpZsBmcnRxcXJzcWtuaWxubnNtb21tcGprbXBuaW5ta2tvbnJ0gHJycXJwcXN1wW9wbHpwbnZtbnNwbmpxb2tpb3NxbWzNa2VtcWxqcXFvbXJzc3d1cm9tbWtpam9ybW5xaW1wd3p5dXBycm9zcmxscHZzcXB2dXV1dHJyb3Bsa2tvbG5va3FwcXRxcnFua25qzNVubG1udGxwcnJrbHVtbW5sd3Z3gHVzcXZ1b87CzGvTz8HCrrWmk66of4dVaaiYkZmqWaFza19jcHJwZ2SpsqtjXl5aqKqQj1hfY1pcbnFrZ2bHbXBrbWrSbmxrcXDMaWptcGxtcNfT0W9stbnS0Wlpz9LTac1wbW9va3FmaXJxcW9xdHNtdXZ2cXBlZ2NnZWdpx2dtgHBva3BzcGpycXFvcnFqb25qaG1sbG1qZWRybWtwbWvR0GttbMlqztLWbW9ubnFzc29vc290cHJxbnB5bW5ta2pta21ucG9scXFubG5sbm1xbWlsb3NycnJzcG5ydHBvcHJtcG5yb3hwcG9r12vR1M/Iw8vC1WxsaL2/zcpmyMrHgGRnbWlqcmpsZmdubmhuaWhwcG3Yzm/LasvJy2hny8bBwcjT2G5szNRrycTV1M/Ey8nIys/LyM/Fzsq1rKq5t6O6YbRmZ29za6uuvmRhamxpamhnbdnKa2ltbXFpbGRmZ25ra21vb29raGppZWhuatDMzW96eXFnZL5kwsS6wL1rgGhoaGRkw2W+t7uuY2NnYstsa2powq3Lz8HF0dDOxdXX1MvSxMe/sqerq5qv08xoZrKcr2pmbGprZWpwa21zcmtpZ2hqaXBkamxovWt0cWltcHVjxWdxb2xqbWxqdmxxbG5tb2fQZmtlcG1ubHBjqbTWZmFpaWZtbXJxcGpqb29ssX8BfoR/AX6Gf4Z+hn8Efn9+f4R+iX8BfpR/AX6UfwZ+fn9/f36EfwF+pH+Dfo1/AX6Zf4N+kX8Ifn9+fn9+f36LfwF+jn8Bfol/DX5/f39+f35/f39+fn6MfwZ+f39+fn6hfwF+qn8BfpZ/AX7Lf4J+mX8Efn5+f4x+gn+FfgJ/fol/g36Ef4R+in8BfoV/AX6FfwF+h38Ffn5+f3+Efgd/f35+fn9+nH8BfqF/Cn5+f39/fn9+fn7CfwJ+f4h+g3+EfgR/fn5+k38Kfn5/fn9+fn5/f4d+BX9/fn5/mH4Cf36Ff4N+iX+Cfpl/g36GfwJ+f4V+hn8Cfn+EfoR/AX6Ef5p+BX9/fn5+l38Bfoh/AX6QfwF+iX+Dfo9/AgIEAICVl4iRjI2UlJCMjI2Si5KQjImA+4KEhoqOi4eJhYD1gIWJhIyQiIaKhY+Ojo6Lh4KLgYqRhoWOi42XlPvv6IaVlo2FjoqIl46JjIWIg4L2gYGFhIWIhYSAgoODhIeGhP+AiYyIh4eNkJKOjomIhIWGhIaFiYOBjpeYjISKiYyTiID//P77/Pbv842Mh4iPjoiNiYePjpiVmJmOjImNiYyPjpCSipCLioyMkZWFhYuNj5yThIGJ7vSCi46AgIOKi4+MiJCLjpaXl5STiIiJjIX+h4mHjo6SkpGLi5mWgI6Vi5Gisf2Oio+Kg4SIio2ChJCPi+3/+IeB+YmGj5KGhIuJh4CNi4iB/4GEhIyHiJKTmJqDnZ+Tj5iVie/6nfWCiv3g9OHyoJCIiIWAh+OHhYuGi42G9oaHhouLmIeG/f2HjpONkZCTkI2PkouVioiSkpCGjYSSk5CRio+Gh5OL9fmHh5iUlYyOi5WhhoeNjpCFiIuHgI2DgIiSko2GhYKFhI2Vk4CBhoqHhYmNjJSTmZGNj4mHjImVj5OLiYOTkZ+kiY+ckIaJhIGIi4aLi4WMiYmLjY+QhoqKi46Rj4+PjY+Sj4WGjI+Qko6NkI6RkI6Lj46PhYqQj4yOk5GNkI2NiYmHkIyJi4uMj46OioqLjIqNi4T+gYSIhouTjpCKi5CQjYuLhoCCioaJhI6ZjIL409/i4tLbycu5sqe4tcfx/oOH14j8naaqtq6nqInNjP/MhNOQp+3H64CKjISEi5KAhIjzhYD6hv2Eg4SDioWKhPv5gIqLh4qGj5eSiaKPmo/3/IT//ouHlZaMlJSOmZGanI6SkpGQlJmWlpyin5mHhIaFhfv3ioCEjI6PjJCWlJOTkZSOjZKOhY+Ok5KKiYGI/YKAh4D15/KH/v78+PSA9vj+goaKkpORjIaJhomFgJCBlI6MlZOTjouUl5iXlJScnJ2dnJ+hnJiVkJCRk46OkY6Oi4mPj4uIg5CPjI+Jh4aD7/rv+ID4hIKGhoKSiIaHg4KAiI+RiICMhYeBhYWDhfuD+/uAgPqCgYH89O/4hOv2+PaMg/vn4fH6/YSB+fqDgff0/vyDgPeA9/j7gPP17uHj1crf5PTy38Ld6e2Lt+O8he//8/+tkoOA+Pv1j5SMgPX/kJaIiIf9+YL9iIeLhYyFgIGChZSPloSMirrUu5uJhIWC+f6AiICJk5OKiImNgP2B9IL+iYfzgY2BgdTc/N306/728+7p4uz37ezt5uHOz9Ha0v/9+oGGgoKTgYOBiZCPhYaMlpaQjpCYkI6G/fiC9vmPpKSbkJKTgo2LioqFj5CMgImEioqLg4KCiouFjY6LiYDrgZeljo6JpaiRkpWcl5uUkJSSj4CHiHqGhIOIiIaBgoKFgIaGg35163Z4fH2Afnt8eHfld3t8eoKGgH+AfISDgoWCfnmBdH6FfHmBfoGHhd3g13uGhn17goB+ioKAhH+Bennoend7e3p8e3t2eHh4e39+fvl+hIR/fX6GjY6Fgn5+eXt9fHt1eHV0fYWFfXh9e32EfIDu7/Dr6+Pc44OAe3uBgn2BfXqAf4iFh4yDgX+Cf4KCgIOGfoaCfoB+gop4eoGBg4yGfHiA4uV8gYN4eH2AgIOEfIV/gYaFioeHfn+BgHvte359hIOFgoCAf4yIdYGFfIOMmeKBgIZ/e3p9goJ5e4aEg+Pz6oB46YB8g4R7fIGBfoCDgH579Xp6e4R9gIeGiYt1i4yHg4mEfd/hjNt2f+7W69Xdj4N8f395gNV9foJ6gIKB539+fYCAi3198PB9foiBgIOHhYSEhn+FfXyFh4V+gHmDhYKGfoV7eIF94eJ7fIaFiYKGgIWSf36Cg4V9f4R/d4F7eoGIh4N/fnp9fISKioB5f4F+e3+AgIaIjYOBhoB9hH6IgYd/gHqGgYqUfoGPhn6Benl+gH2Cg3uBgIGDhIKEe36AgYOGhYSCgISJhn59gIOFhoF/goKFg4KBhYODen6ChoiIioiFh4aGg4J/hoR/gYKDhIODgYSHh4OEg37veHt/e32IgoV/gIaJhICBfoB6gX2AeoaNhn3yytfY2crQvryonpOkoKzQ2HJ3vXXVf4SJlI6FinKseeS1e71/ldGuz3N4e3NyeoFvd3vffHfsful8fX59g32Cf/PteYOEf4F7gYJ+d5B/ioLj6nvu7H95h4t/homEi4WMjoGEhYiGiYuKiI+Ri4N3eHl6fevmgYB7g4aFgYSKiIaFg4R/fYSDeYKDhoR9fnR853h6f3jm3eR/7PD07+t96On0en6ChYeGf3yAfYF+eYV3iYWCiYaGgoCGiYqIhIWKioqLio2NiYeFgoKFhoGBhYSDgoCFhoF/eoSEgYSAfX+A3+/l6XnsfXl+f3mGfn16ent7gIGFf4CBeX53e3t3euh58Ot3eOt7ennr49/net7r5eGAe+7d2OTr7Xl15O18eeXg7Oh5deR24+jqduDk4tXYz7/M0Nzaxq/J1dV8l7eacdLn3OKSgHl46+3kfoOAeeTtg4h8fn3o4Xnqfn1+e396eXd4e4aBin1/dJ2vmoN5eHp35ep1fIB8hod/e3yAdOh44Xbrf3ridoN3d8DK783r3u7o6+Pe2eLt4uLn39nHycbNw/Lu6Hd7d3eHc3p4fYWEeHt/hoeDgoCGhYJ66eV56OuCkJKKgoeHdoB/gIF8hYaBeXx5gHx9eHh4gX97fYB/fnbYc4eRgH54kJGAgoaLh42Hg4WCgYBwcWd0dHJwbXBucG5tbHNzcGxkzmRmaWZqa2tsZ2fLaGlnZ25zcm9uaXJwbXRvbmhxY2duamVpaG1wbrnGxWdubmNnbW1pdW9ucm5xa2jNbWlsbGhqbG1pa2xrbGxsa8tnbnFuamlxdnd0c2xraGlrbGtiXVldaW9waGdsaGZsZ4DN0M7LysK6vW5rZ2dpa2ltaWdra29ubXRvbm9ybnJua2xsanBsaW1nbXppbG9sb3FubGxvzs1vcW9rbG5ub3F1b3JwbnFuc3F0bm1ybWnPamxpcHJza2hua3FvZGpuZ2xvdMdtbHVraWpsbW9obHFvc83c0m9ozm5pbW1pcHFyb4BvbG9w32pqbXRrb3R0cXNfbG5vbW9tasK7brZha8u+0b23cmtlbW9rbbxqbG1nbG1yyWtsbm5ucW1t1tltaXJuaG5zcXRycGlsaGtucXBvb2lub2twbXJqY2dnyMloaW1ud3N4cG92b21xcXFtb3JuZWxvbXN2cnJxcGptanB2eYBscHBtaW9tbXFzdm9udnJucGtzbnNsbmlvam53a214cmtsaWtsb29xcWtwcHF1dHFybXFubHB1dnRzdHd6eXFvcHR1dHJvcnN1cW5sc3R0bG9zdXh5eXZzd3Z1c3JscnBtb29wcnBxcnV0c3BycW7Sa25xa2xybHJvbnN3c25xbYBvcmxva3Z2cWzWs8O7wri5qqWRiH6MgYigpFpfmVyhWlhdZGFaXlGEYLiWZppjcaWMqVxdXVlXXmRYXGO9amTJashqa2trcWpyc93PanBzbW5paWdhXG5ibmvGz2zQy2xnb3Nqb3Rycm91dGtqb3Zwc3JycHlyZ2JeYmNoa8vMcFZqdXV0cXF0dHNxbmxoZm1xam1xa2hmamJmxWdqbmjJx8xty9bY1NJwzM3Xam5wcXN0a2tvbW9ua3Blb21ucXFybm9zdXZ1cG9wb25xcHFvcG9xcHBwdIRwgHFwbHFzcG9qb25uc3Fub3LK1snIZ8hsbHBsaHBvbWxrbWxvbG1tcGRoZWptaGrHZ8/MaGjKamtsycLBvmW2xLq2amfRx8LHztVvasXJbmnJx9fRbWXKZcjR1mzIy8i8xb2oq6qxqJ2SpquuYmh2alaqwrzAbWZqZ9DQxGdpa2rNgMxrb2lsbMvGZ8pqa2xrbGtpZmNobm52bWZbcnhsY2JlZ2TIzGRnaXN1bWdpbWTGZsJnzGtlx2duaGais9K208PSz9PNy8fN0szK08rArbGtrKHRzcJkZ2VmbmFpZmdtbGZpa25ubm1pa25rZ8XEZ8fTbXJzcWlxcWVta25ubHN0JnFpaWVqZmlmZ2lxbmdnbmxsYrdfbHNpZ15wbWhtcnVuc3BvcWxrk38Bfop/AX6cf4N+kH8BfpB/AX6gf4h+rH+Cfph/AX6TfwF+jn8Gfn5+f39+jX8BfpJ/Bn5+f35/f4V+h38Bfod/AX6If4J+n3+Cfv9/k38Bfpl/kX4Ff39+f36Ifwt+f35+f35/f35+fop/Bn5/f35/foh/gn6OfwV+fn9+fp5/gn6afwF+hH8Efn5+f4V+BH9+fn6/f4R+An9+mH8Kfn9+fn9/fn9/f4R+AX+EfoJ/hn4Gf39+fn9/hH4If39+f35+fn+QfoV/hH6Ef4N+hH+CfoV/BH5+f36Yf4J+in8Ifn9+f35/f36Ef5t+l38Ffn5/fn6hfwF+k38CAgQAgJCHkJSHhI2LhpKPko6KiYSE+IyPiI2QkZSDhI2KiYaGhYeNjY+RlpWSipGYlIiPioTz+4SBjYaGjYOB8f+PjJODiYqPjJaUiIWHhI2EhIaHh4aKjIqGh4eIjpCNjYuFiIqNj5CMiImNiImNjY+LhYeIh4eQjYD9hYqKi4mJkIqEgIeJjIGGjYiIkYyAhIOIlouFhvP/i5KUjpWdmJWRhYWJhPaPlpKSkYWHkZCHkJGNhPn/ipiLiIaRhfCCjoiOlpGUk5SIgvfz/YGHiZKHiYeNjZGMi4yPgoaFkKCRm5GWnKmI+YODhouGh4n6hYSRi4/x+P2C9/2GiYqShoeJiYuMgIuVg4H+iYuIiYiQkJa2tpSXiPqNi/ndgImDg4GDioD69IuLhfP/++j9g4SKj4mLiIOCh4rxhYmQioCJ7PCAi4yPkIyUh4qPj5CVjIqNjYaJhYyMm5iOh4OJi5mRi5CPlJKSkZeOkI2Fi4uQipCRg4iEg4+G84OE+v/7gYKBhYmHgJSMgoqPmJGZkY2Hkf+HkIKSkJSLjpaQi5GToayonoOHiISHh4yMiI6Nj5CMjJOLi4uOiYiJjImKjI6KiYWFiIqNh4yOjI2Mh4qKioiIjo+KhYGBg4WHiouPkI2Mi4yKiY2LhYiKi42NiomLiIiLiouFg4aB/YuLjo+QjomIjI6IgIqBlZqPk5iKhPzP4OTg49vg1NjDwdjf88bQ64PtxNbZ+o6XkqTvwPeZjpmkp6PLzuD+jo3UzeWHgdzsj+7UgYCC//eEgouGgPSEgYOIlI6DhIuO9IOH+5Ge9vrx7vWAkZWgnJaXlpeclZKalJGblZKWmZ2QmJ6cmf/z9ev7+Pv/gIGIiYqQjpGSkZOQi4uEi4yWkIWDgoiJh5GLg4SA/fbw8ff194D8//rz+IGHhIiGmJaMiYOFhomJk4qMmZaTlI2Oj4+YlpqYmZqak5GWmZmUjpOUkZGQkoyIioiPj4yJhISJiIv+/ouPjIaD+euAkY2DjIWIkZKIh4iMioGMkYWFgIuH+/f0gYD98O36++bs9PDx9ejp9/mB7/LvgYD8gebl9uzq9ujy9P/05oSD9feAgvv9/4H+8/Tr6dnPucng3eDZsOH8/IOt19+3/eHq84yrkoGA/ID7h4yFhoiLipKWjoiMhPeDg4OEg4qKhIiEkI+cjKGjnK6TtpqMjIX+gIeIgI+Ij4+QgP2D/fqJh4iEhoL2hIDy4Nbf6e3x/Pf14vL27Pby8evl3by8vs7G7/b6gYeYkoGQjIn4go+TiJKUkpWMjpaZio6PmY6YmZSYqZuKion/hZKQl4+PiYyNkpeQgoWJiJOLlpKYnJaKh/j09e71iY6WmpCSmJmTkJGKjIuMgIN4g4d7eYOAeYaEhoGAgH195oKGfoKGhoh1eIF+fnt+fX+DhIOFiomGfoSIhXuCfnri63p2f3p8fXh33emEfoZ3fX+HgYqHfnyAfYN5e39/fnx/gYB8fX1/hISBgoJ/fn+ChIWAfX+CfX6BgoN/eHp9fXmAf3XrfH18fX1+hYF7gH+Agnl9g318hIF4enl9iX93eNvof4eHgIaKhYeDd3t/eOGDioaGhXt7hYN8goSDeufsgIx7e32Ge+N8hn1/h4SHhYl+deri7Xh8fYV6enqBgYR/gYCEeHl6g5CBiYCFi5d753p8fH59f4HofHmGg4fn7PB76PJ9fICFeXx+f4GAP4KQfXrsgIJ7fICEgYScn4aKe+GAf+bOeH15fHh4fXjt5n99feLv8dftfHt/gn2CgX54fH/ienyCfXZ/3uR4f4SAgIZ/f4KEg4h+gIKDfX56f32KioR8eH19jIOAhIOGhYeDi4ODgn6BgYV8hYZ6gX18hn/je3vs7e98e3h7fn6Hg3uChYyFjIeDfYPrfod7h4SIgYKHhIGDhIyVkY55fX97fnyAgX+Eg4WGg4WKg4OBg3+AgYSAgYKCfn97fICChH6AgIKCgoB8hIWEgYCDg398enx+f4CEh4mIhoWDgX5+goJ9gIGChIOCg4WCgIOChH58f3jsgoCDg4WFfX6Cg3p+dYePh4mPgXvwx9ba1tvW18fItLDFxtuytcpy0q68uNF1fHaHxZnJf3R/iIyMrrTG5IGAwrvQe3jJ1IPcxXd1eOzogHx6g4B65Xx4e32Ig3h4fX/VdHrggI7e6ePh6HaFhY6Mh4eGiYuFhYyHhIyIhYiNkIOKkYuH5dvf2Onm7O93f3+AhYOEhoSDg4KDe4GAhoB5dHJ5enqCf3l5dOvo5uXp5ut67/X16et6fnt/fY2JgoF8foCCgYh+f4mIh4mBgoSDgIqHioiJiYqDgYSFh4V/hoiFhYSGgX+Af4aGg4B8fIF+gOfpgYSAe3zt4XeDgniAf4GFhHt+foODeYGIfHV+e+Tn7Hp48OTg6+rZ3enn5uXc4+7vfOjq5Xx68Hzj5+/h2d3R4Onv5dt5eePod3bm6Op37OHl397Vy7O+0snIu6DSgOfmdpGssZjfz9jbepWBd3fldOJ4gXx9fn97hIqHfoF633h8fXt5f356e3qEf41+i4l+jnWYhHp9duV0e3l+e4KBgXTreujmfHt+enx54nt239XN093l5Orq79rp6OLp5ebg2tSxtrvHuePq63p9iIByg4B/4XWBhH2DhYWHgIGIPYl7hYaMgIqMiImUin5+gOd5hoOIgoR/gYGHh4B3d318iH+IhIiMhn184t/h2uB+f4OIgIWKi4V/hX6AfX6AcGZudGxqcGhjbnFyb3BwbmvHbnJrb3BvcGNnbWxsam1vbnBxcHJzc3FqbXBtZmxsacXFZmRpZWZnZGO9yG9ob2Nqa3NvcXBtbG5rbmlsc3Nva25vb2tqam5xb2xub21ramttb2xpa2xrbnFwcG1oam1rZmloYclsbWxsbW5vbGmAa21tZWlsZmVtbWhqZmZxbWVnxM1vdnBqcGxtcm9kamxkvm1yb3FtaWtubmhtbm5mydFvcmhnbHFoznByaWdtbXBwdGtjzcrTZ2Rpbmdoam9tbWttbnBoZGdvdWhqZmtxeGnMa21qbW5ucMtsaXFvddTY4XDT3m5ubnFoampscG6Acnxua9Jycmdqb3BvbXl7cXNnu2hoxLRmaGhtaGZnadfOaWVqxtLWxdVtbG5wb3FwbmZqbspqaGxraG/DzGltb21tbXBybnBvbnJtcW9tbW9tbWZucHBqZmtpdG9vbm5wcXRvd29vcG9wbXJpbnNrc29sdHHQbW7T0dhvb2lsbG6AcnFrb3J3dXl3cWptzG10bnZvcnFvbm5ubW9uc3FzaWpua2xobnBxdXR1dnV1d3N2dHVwbm5zcXBzdXJyb3BydHZvc3Ntbm5tdXZzb29ycXBwb29vcHF1d3h2dXRxb2xsc3RwcnFxcnByc3ZzcnVzcm9tcGnOcm5xcHN1bnFzb2iAbWZwfHd5e2ll0LLFyMPIxsS2r5mWqqi1kZCdW6mOko6cV1lUX4xrlFtSYGdlZH+MnLdpZ6ShsGNjpatquKdiYGXIyGtpcG5oxGtnamtxbGZkZmWsXmGzY2+9zMjGymNuanFyb21ucm9rbHRycHVycXF2e3BxdG1muba5vMjE0NKAaHBtcHBwbnFua21wc2tubG9mZF1bYmFiaWhmZGDJzc/LysfRbNXY3c3Ram1qbm53cnBybG9ycXF0aWhvb3B0cHFvb3FvcHFxcXJraGprbm5rcnRzcnJxbmttbHBycHJubG5sbdHScXJta2zSx2lta2VubnBycm1sbnFuaW5ybGOAaWjGzNJsadHHxM/OvsHNycTIusHN02fFxbdnas5py9LQwLi6tcjQ2M2+a2zGx2hpycnPa9XIy8XCwr6oqa6gm5uDr8G+ZHJ3dWuxsLe7ZHRrZWbBYb5jbGxqamxkbXV0bmxowGptbmlmbGpobmluanJmbGNZY1RsZGRkY8FiaWaAZ2hubGpgzWnJy2xqbWlrasVoZr/AuLzOzs3Nz9bH0MzGz83OyMi8oKenq5jByslpamtkXmtqbLpia25raG5xb2pscG9lcnJ1bHB0dHFzb2hsccJlcnFvcHFscW5xb2hnZ2hocmtybHBya2lqv8PFvL5pZ2trZmtydmxpb2lwbmqRfwF+n3+Cfoh/gn65fwF+m3+Cfo1/AX6Of4J+h38Bfot/g36afwF+h38BfoV/Bn5+fn9+fo5/AX6NfwV+f39+foh/BX5+f39/hX6LfwF+hn+Cfrd/Bn5/f35+fpJ/AX7nfwF+lH+SfgF/hX6Ef4N+hn+EfhF/f35+fn9/fn5/fn5/f39+foV/AX6KfwZ+f39+f3+Ffpp/iH6df4d+AX+Ffrp/gn6Ff4J+lX8Ffn5+f3+Pfgh/fn5+f39+f4x+Cn9/fn5/f35+fn+RfoV/hH6FfwN+f36NfwF+mH8Bfol/BH5/fn6GfwN+f3+cfoh/AX6afwF+mX+Ffo9/AgIEAICOi5ONi4uIhIqOio2Lg4OAhImGjZSQiIuG9veDjIuLjpOQjIaChYqQj4+Sk42Ih4CLk5aCi5WM84KBh46PjIf9gemDiIuUkIuJiYmGhYWGh4SEh4iOj4mHjI6LipCQl5eOiIuKhYqLioiPlZSOjoyHgIeQjoqMiIiGhoiIkZSJiICLkYfx6/6HhIaFgYCBhoX59PT8+oGGhYKHjZOLgIyQjY2Qm42JgfuAhZORk4P9hIqIipONkYX8gIDv/oyGkIiFhoyOhoiOjZGTkpaGhZKIiI+QiYmPk5OTmY6JmJuGmIiRm4KC94iPi4OQiIGOhoWEh/f2+YmIjZSUjYmJi4aFiID8gYaEioeFiYaOh4+vwqSCho+CgZuVgfCJlIP7/IqE+Pz89P6EhYeAg4+TjoGIiYyKhoWIhYeOhIWLjo71+42Nj5GQkYaFkZaRjoKIiZyVjISPi42UkpSNj5KQlYiQlZCOipqRhfqNlpuIi4+Im5SLjY2OmI6Gj46alouTkZSZmoCYkoOU/4WNj4OGgomHh4uLhYmCh4yUi5KSjJ2hmYyAgImGi4qPjI+LiomMkI6PiYaGioaHhIWHiY+Rjo2Eh4iGiImFhomNh4OEh4iGiImIiIiEhYiKhoaFh4SJi5CJhYaKiYSDhYiMj46QjYeKi4iHi4uEgfv9houHioWLkY2HiYCH/omekYeJj4SC/urm29rg4OTaw7bd1NjSvsyGjp+UksPm1NCA/8fO0eP2hejj2dfpnpLo04OL4u7i75OLgYyQiZKSjYmGhYGEgfXm95CSiISVkYTx8YuLjYuGgoWMjo2MkZKSkpCPkJ+emYmRlpSZmpaMkZqRkJ2K8/L6gYOEh4CA6YSIjIuPk5OTkImPk5CNj5CNhYaDgPmGg4Tz7/n6/vPv7/H96/X///uC9IOFi4KGiIyMjoyLiYiLiYmKioqSjpGRk5ucn5mbmZqenpyYlZCMj5iVjJCQiY2OkY6Iho2Hj4uEgIiFi4eB+Prr8ISDjZGOiYuKgYGB+/z7/IH/i4CGgID+//r3+vX1+e/l5fv17YCAgfjy/fj+gYPy9PP4+Pz69ICFhe7a9feBgvTv8/Pw9PiF8unh7e7Nyca4y9bogIPdyeTr+5u7z6/15/Xti5KIjPvugYCRlpWEhomI/YmFh4eJhoD18fOAh4KOjYuQk5eUm7G0qraghYWKiIeIgYCMm46FiYeAiYKAhoeGh4OC+5GJ5/KB/PPr6+r08urx6+no4+Xh4uLQvcrV0vzx8uv7mYr08PuNhfuD+oaSjoyJh4abnI2KhIaPhYGHmaqVho6OkY+PlJWRlYaQj4mIhYmJhYOKhof+/4yRg4KHjJKQkJWKi5OLjY6Xm5iXj42PkICCf4aAf4B9eX+FgoKAeXl4fH95goeDfoF74eV4gYB+f4WEgXx7fICEhIGFhoB+fXV/hYZ2gIiA33d1eoCCgHvndtZ5foCIhIOBfn58fHt8fnx7fn+FhH59gYF+foSChoR+eoCBfYGAfXuChoWBgoB7dXqCgX+BfXx7fH59hId+fYB9g3zf3PB+eXx8eXd3e3ro4N3n6nd8e3d9god/dYGCfX+Ci4N+dud2e4eEgHrqe35/gYmAh3zud3nh7IF8hIB8e4CEfH6Fg4OGhot5fIl/f4WEfoCEhoaDi4J7h458iXqAind76YCEgXyGfneEfn18f+jt5317goSFgX+Bg358f4DseX97gn56fXyFfH2Vp5F4fIJ2dIiHeN9/iXru64B86e7w5up5eH56fYSJhHd/goSDf31/fHuCeXuBgIbr7oOBg4WDhXx8hoqGgnp/f4yGgnyEgIGFhYiBhIeGinuEiYKDf4qCfeuDipCCgIV9j4mChISFjIN9hYONioGHg4aMjICLhXiJ6XuBhXl+eX59e3+Be4B6f4KFgIaIfomOiYJ2eYF8gYCFgYOAgYGDhoSGgH2Ag35/fX5/gomJh4d9f396foB+f4GEf3t8gYKAf4B+gIB8fn+AfoB+gX6ChIiAe3uAgH5+f3+BhIKFhX+DhIB+goJ9eertfIB9fnl+hIB8gYB/63eNhX2Bh3167uPe0s/Y2N3VuqbNvMG9p7N2eId/f63Fsq5t2amysL3PcsTFwcDMi4PWxXqA0d7T14J5cXyCe4OCgH1+f3t+e+fV3oCDeXKBfXDLzXp8gH98en2DhoR/g4WEg4KDg5COh3mEiYiKioZ9fomBgI1929njeHd5fYB22nyAg3+BhYaGhoGEiYSBg4SAeXh2dON6d3vj3uns8ufh4OPw5e319/B7531+gXh+f4KEhoSFgn+Cf39/gH+EgYODg4qKjYiJh4iMjImHh4SAg4uHgIOFgIKChoN9fIR/hIF8eYB7gYB79O/m7H57gYaEf4B/dnh68/Tz83zxgYB+eXfq7uzl6efl593W2e3p5Hd5fenj6uPteHjX2uDm5ebp4XN3eubU5eN3eenh3+Hi5OZ84dnQ3eDIyb+uw8nbc3PMvNfb5oiXpo/Uzdzae4B7f+fYdnKDh4h6fIJ95318fXp+fXjk4Ol4fnuDfoGDgoR/gpORiZSHdHd8e3p8doB+iX93ent3fnl3e3t6fXp76od+1OR48Ofd5eLo6OPn39/Y2N/Z2tnLusnTy/Dn5N3sjX3i4OaAeOd0432FgoB9enuJi4CBeXyCeXV7hpOFeoGDhYKBioiEin2Eg319en6BfHl/e33q7oOEeHZ8foOBgoZ8fYF9gH2IiYiIf36Cg4BvbnNtbW1oZm5ycm5ta2prbW1nb3Bubm1ovsNkbm1raW9xcGxrbm1wcG1wcWxtbWdscGtjbXBnvmVjaGxpamXFZLxrbm12cHJuaWprbGtsbW1pa2xycGtqcHJram9ucW9oZWtvbnBua2ltcG5sbmxoZ25wcG9xbWtsbW5rbXBvb4BtcGi+ustramxra2hkZmfLw8DOzmZramlsb3BrZmtsamxudHBsaMhmbnRxa2rFa25ucXRsb23VamvI029mb21qanFzbW11cG9wc3NnZnNwb3JwbXBycnBsc21pbnRpbmJmb2Rs0XBwb2x3bWZwbW5scNHZzGhpcm5tamtydm1qb4DPbHBmcG9ram1zaWVxfnZpaWtjYm1tZcNtdmnQzm5s0dfaz81nYmxub3V1b2Zvc3BybmxtaWdtZ2tub3PS1G5tcnZzb2pvdHNxbmtxb3Jxc3J1bW9rbnJtbnBxdGhudG5vbG5tbtFvc31wbXFteXZydHR1eXBucHB4dHBybm90coB0bmN3y2xwdGpwam5sb2xuanFrcHBucHFxam9tcXBoa29sbm1wb3BwcnFzd3R1cXJ0dG9ua2ttbXN1dHZwdHNucXFsbG5xbmlpcHFtbXBtcXNtbW9xcG9vcnF0dHdwa2xxdXNwcHBydHBycW1vcG9ucnFtbNTQaWprbWdsbmtrcIBux2J0cW50cmZmzcnKu7vDw83EqJKxnp6eiJNbWWVhYomYhYBSo4KIg4qdVZKYnJ2kamezrGZnscG3rWRfWV5mYGdpaWlrbGtta8u4uGZoXlllYFWXm19obGxta25wc25namxta3F0cXl0bmZucnJxbmxoanBrZ29js7LBZWNnbIBmxW5wcmlucHFxcnNxc3BucG9qY2FfX7pjYmXAvsjM08fCw8bT0tHY29Jpym5wcGtvcHBydXJycG9xbGxsa2xvbW1sa25ucXBvcG9xcXFvcnJucHRxb3Fzb29tcHBtbXJwcnBuam5pbm5s1tLO0WlocXJtaWxqYmdr1NXY1W7VboBubWjKztHJzs7LzcS7uMXIw2NrcMfEzMjOZGa3ucXMzdXTwF5pb8+3zMhpa83K0M/ExspvzMW3v8K3t7ahqqKtXl+pori9wmlrbWiqrLq8ZmVnacO9Z2Jvb29nbW5ow2hubWprbWjIxtBrbm1xa25tamdgYGZjXGZmXmFlZmdpZoBpcGdlZmVoamlpbGtoamls0XJpuMxn187I087P0c3Tx8TCwsjExsS1q7q7rcnGwb3Kdme+wMZuZsljw21vbG1taWpxc2xvamluZWRqc3VraG9vbmpsc3BtdmpubWppZmpva2hpZmbHznBuZmZoZWtoZmpoaWppbWlxcHByZmZtcZl/gn6cfwF+h38Dfn9+wX+Dfol/hX6SfwF+hn8Bfoh/BX5/f35+qX8Bfox/g36MfwF+ln8Ifn9/f35+f3+Ffph/gn6nfwF+nn8Bfu9/gn6LfwF+iH+RfoV/hH4Bf4Z+AX+FfgZ/f35+f3+Efo9/g36Hf4J+o3+DfoV/AX6VfwR+f39/j34Cf36+f4R+i3+EfgJ/foR/jn6Df4V+gn+IfoN/hH6Cf4d+AX+MfoJ/hX6Ef4R+hH+Cfol/AX6Hf4N+p38Gfn9/fn5/m34Kf39+fn5/f35/fqt/gn6YfwICBACAkpCSlY+IjpGPjYmEjY+MkIeIjoj7gYeCioeHiomI+oaOlpGSl5ONjoqilIb8hY+FhoKNmJP8gYCHiY+QjI2AkIuGiIiJkouHhouLjIqHiYiGhICAhI2Uk5KPiIOJjpOTk4yLi4yWm6GmpJuZjoaCg4SLj4+KiYeEhIeGiImNiIWAhIGHhfX094WLiYeJhYGHiPj5gIGFkI6Li5COkImMlpKRioKOi4mEipCMi42QjJCPm5mRjYSBiIqK7YH++oOTl5KQiYWShob4gIGBgYuKh4yRipSXkJCMiIuMkYqDjJ2H9pX4hP2Bi4P/hYCKjI6E7/eEhIaBg4ahhID+h4qKgfSAgYCFhISDkImLiqjBpIqTkIyPkJeJ+I/85+z475KMiIj7+v7/h4OFiIiJjI6NjoyGiIf3gYWIkYSRk56e+PGDh42VlZaJho2JhoOLiY+Pl5OMjYuNkpiOiYiEiJmZjYmInJaYlf/3jY6OkP6IipyZjYySlIuG84WKj4uHiJmLkJ6ApYyOhY2QjYSGiYuLjoaMjIKHi4yNjYGCg/mXnpaKhYKEhIuHiYiMi4iIhIqJiY2HgoSGiYuGiYmKhoiIhYSCg4yIhoH/hYuIhYaIhIqJgoCFiomGgoWE/YGBgPyDhoeJiYuMi4CFj42Pk4uHjYmJioiEi4uIhoP++ICGjIiEjI+AjIiIj5iZmJqNiIb85tnP0uvn8u3E1tisqaDa+N6YuNnts73l2IbrucP8iYaBh4b9/Pj+goOE+YD5/Orc8oSGhvb6+oSLjY2YmZycnZb68omF/Ir38Ovqh4+NjIuXmZSSlJGZjYiXmp2YkZOMiJSdmISNj5KSkoyOlIT1gP70+vqAg/+Ei5OSioyKhoeMkIqJi5KQj4mE/4ODhoH564D97PX08e/v3u7z//j8/IeBgfXy/4OIj4qDgoOCh4uTnZmXj4uRlZOUmZiUlZWXnpuXjoqPlZmSkI6LiIqJjo6MjP2JkI2Cgv+ChoT9gP6A7vuMjJWKgYiCg4SE/vnxgP6KhYWA+IOBgfX5gPjt9/H26uz45fP5gYT++/70gf2IifHp+vaIgP+Mh4CDhID66vPw+fz49fHn7u7o6ebo28TJtaOYxd7f0Ovu9IDxnMLTr/75/IiflpGDloj5goWGiYmIj4iG84uLg4eF/ID+6e7+i5uUn6WnoK+wsNHPrJSUjIqGiYqAg4+Sj42Oj42MjYyOioSIhoeFhu//9fb04+r6/+3z6+7k6PHn3eLp4OPS1eLu5erq7IOooIKCiJCDjISDjJqKg4KEgYybiIiFgIuEiZGAgI6Uj4iRkZCMkI6OlJWOi5iViYWBgoqKh4qNiYGB+f3q9PmDjI6ajZeUjo2OkpiZkpaAhYSEhYN8goeFgoB7g4OAhH5+gX7pd3t2gH17fXx85nuAiIODh4R+fnyRhW/PeYN6e3eEiITleHV9fIKFfH9zhoF6fX18iIF8fH+AgX98gIB8enZ4fIKHh4eCeXR7gYWFhX9/fn6FiI2QjYiIf3p4eHmAhYN+fnx4eXt6fX6BfXuAend8e+Xk43p/fnx+e3Z6fOXldnh6g4B9fYKAg31+hYSGf3mDgIF4fIKCf4CFgIF9i4yHgnhze3+B23jp6XmFioWDfHqHfH3nd3Z5dIGBfYOGgImNhYSCfoGDhH92gI9714Tee+R2gXzwfHmAgIJ74ud7fXx5enqOeHbyf4GCeOaAeHh8fHx5hH5+fZCljXyEhIKGhouA5oPs2dTh2oSCf4Dy8fDxf3p+fn2AgoOCgYJ9gH/seH5+hXqHhY6U8u18f4KIhoh+foR9eniBgYmGiIeAgYCChoqGf4B7fIuMg4B/jomMiu7phIKChOp+f46KgICGiYF/6X6Agnx7fIt/g4+AloOGfISEg3x9gIODhXp/g3x9g4aDf3h7e+GFjIiDfnl9fYR/gX6Dg4GAfIF/gIWAfH2AgoR+gIGCf4CBfn58fYR/fXjsfIF/fH6AfoOBe3d9gIB+en5/8np6evJ+fnx9foCBgXl9hYKBhoF8gn9/gX56f399fnrx7Xl8gH57gIKAgHx6eoSIipCGgH7w39bHy+Hh6eK3wcibnI3D3caEor7Vn6fOunbQpavee3hyeXro6+LoeHl86Xnm7NzQ43l4ed7j4HZ7fX+Jh4eHioXd03hz3XvY087KdIGAf3+KioWChYOLgHuJjY6Kg4d/eoWJg3N9gIGDhn+Ainzjd/Dj7umAfvF7f4eGfoKCfn6AhX5+f4WDgXx36Hd0eHTo23Xs3eXn6OTk0eHk7Oru7H15eubo9n2AhoF7e3x6gIGHkIqHgX6Eh4SEh4eFhISFjImGgH2Ch4qFhoWAfX9/hIKDg+9+g4F7ffR6fXvyee953uZ/foZ9dXt4eX9+9e7hefGDf3yA5Hl7euTpdufd5eDk2dni0uLpfYPu5Onjdup+f+Lc7OR5du19eXR5eXbh2+jl5eft7ufa4t/X3Nnd07/Fr5SGsM7PxOPl5nfZh6Cqj9XY4XmNhoJ3hXzhdnh5fH1/g35+439/eX587Hbw4OPxgIuEjZKSipWUk6uoj4CDf315e32Ad4CDgH5/g4F/gX9/fnl8e318e9306evl0+Dt8ePs5OXf3ebe2Nzj2N7N0Nvl3uHi4XuXjnZ1eoR4f3h4fIl/dnd2d36Je355dYF6fYR3dYGIhX+FhIN/hIOAhYaBf4uHf3x3eH9/e36CfHZ45eXV4eR3gIGLfomGfHp/g4mKhYmAc3FubW9rbnRyb25ocXBscW9uamnIaGhia2hnamtoxWdqc25ub29naGh4bVKaZW9raWRxb2rBZWRqaWxtY2Zic21pa2xqdnFtbmxubmxrbm1qaGZpbHN1c3JtZmRrbm9tbmlpaGdrbW9xcG5vamdnaWhtcW5sbWtoZ2hnaWlsa2kJamdqaMTExmdphGqAZmhqycVlaGluamZma2xsaGdob3FraW9tb2ZncHFrbHFpbmlxcnJuZmFrbnC/as7OaW9yb25pZ3Bta8pmY2hna25rcHJtdXhubmxscG5rZl9sc2avarpnwGRwbdJta3BrbWrIy2ppaWpoaHNmZddxdHVpzGhmaGxpaXFramtuem2AZ25zcnFzd2/McNTFtLy7bWxtcdzd2dVwaGtram1vb21tcW1sbNNpbW5vaXRvcXje4HBxcXVzcWdvc2poaXJ0eHNzc2tsbHBzdXRtb2trcnFycW12cnVxzM9xb25wzm5udnBsb3J3c2/SbWxqY2ZocWxtdXtwc2xzcXBqa29zc3KAaWtva2xxdG9na3BuxWtvb3Bwbm9vdHFyb3V1dHRxc3Bwc25ucHJzdWxrb3BscHJtbm5tcm9uZchtcXBub3Buc29pZ21xc3NucXPXbW1v3XJxbW1ucXNyaWt0cXB1cGtxb29xbmpubHBuatXWamtvbGxtbGtnZWJpb3V6cGtrz8uAxbm6zMvSx6OorYeHfKa0m2V/mbGEjKmZX6qLkbNkZF5jZsbHvcVmaGzMasbPwbe+Y11gsri1X2JlaHBuaWdqZq2kW1qsYaynpJ5baWtrbXRza2lrbHBpZG90dHBuc2xnbGpkXmdqaGluaWx0a8Rky8jQ0W/UbHB0cmpucW9vbm86a2tscHBrZWC+XVhcXL+3YMW/xsnJx8u6xMfMz9DNbGlrzM3Wa25zcWxrbmxta2tyb21qam9xbWxuboRtgHBtbGtsb3J1cHJxbGtsa29ucHHYbnBwbW/ZbGxr2GzXa8XEampyZmFoZ2pxcdvSwm7XdG9qw2hqa8vNaMrFzsfQxr29tszSa2zHzNTFZcpra8LBzsZmZM9tamdnaWvPw83KzM/T1M/Az8fAvrzLwrK3mHhmiaevp768vV+ybHFvgGOerr1lbWpnY21pwGdkYmdqbGtoa8NqaWlua9Bm0svM0WxxbnJ0cWpubGt4c2hlaGhnZGVoZWpuamZrcm5rcG9wb2hpaWxraLvT0dHLv8zW1svXz8jJyM/JxcTLw8m9uL7Gv7/Gxml6cGNlZ3FpbGVmZHJrYl9jaWhvaGloZ21oNWttaGdudHJtb25wa3NxbG9xbm10cm1sZWVsa2dpcWlkZsG8ssHDY2hqdGlxcWViam1wcG1zlH8Bfol/AX6NfwF+iH8Bfs5/g36Jf4J+p38Efn9+fop/AX6Yfwl+f35/fn9/f36Gf4J+iX8BfoR/AX6VfwJ+f4V+hH+Efo5/AX6Jf4J+pn+CfoR/AX6KfwF+o38Bfqp/AX6SfwV+f39/fpt/gn6Sf5p+AX+EfoV/hH4Ff39/fn+FfgZ/f39+fn6KfwZ+fn9/fn+EfqN/An5/hH4Cf36TfwF+hH8Dfn5/jn4Gf39/fn5+rX8BfoV/Cn5/f39+f35/fn6Kfw9+fn5/fn9/f35/f39+fn+LfoJ/hH4Ef35/f4R+A39/foZ/nX4Cf36Ef4N+h38Bfol/AX6FfwJ+f4R+p3+efrt/hX6PfwICBACAj5KOkIqAiYuLg4H3hIiHjJGI/4WCgoKGiouJk5aUioeFhI2TlpqWkYfsiIOAh4WJiICHh4aCkJSJjY6Tg/2C/YWMjYKEiYeD9YSEhIOEhIeKiYmGhIeNjpGSi4uJhouRko+NhoWJiIWCgYCEio6LjYyKiY+Pj4yMi42Ii4uNjIuAjIqMio2Kg4OChoqFgvuFj4iDhYaLi4uPjYyCg4WA/4aOjoKMjJCUkYXsipOOhoeF7oH+h4GEjIj99P6Ij4+KkoT79oaRh4mGkIuR/4yTkYeUkYiHjIWPjIiTnY+Tn5GMiKCHgfL0i4WDif6KhYD8iIGB8+yAgoCBg/z/hoeJg4SAg4WKh4KEh4mVpbOjip6am4yIhoCA+/r+/Pr6lI7s7u+D/YD8jIyKj4mDgoGRjYqTiv+FkZKNg4mHk56ogPyIkZOLjYmOhv2CgpiMjIKJio2Il4qNk46Qj4iIipGVlY6Tl4qSlIaJiYuTn5ucoImAjo2LlZOGg4SHhZOFm4mIlJ6Aq5X5/IWB+oGLiYP7g4eJh++ChYz7h4OPjImTlZCI/ISFhYmIjIuIhIaHhouIiomJgoSBgoaAhoOFg4uHhYmH/oOHhYSEhfmBiomFiICGhoWHhIKCgoSD+/WBgIaIiIOIjpCSjoqNkJGOkJOMjY6IjI6JjImMjYH/hoaHipCOiIeAhoeAh4qboZmTh/+B7szfhIT039Lg48zFrcbgzavQ2uzltJjjvMraqt3yi4GAjImLh4D8gIuH/fv6gPyBiYKEhIOMivTP6fDr8t+IioKHif715PyE68/KzeLn7Y6UmpKRmpiWkpCXlpeYmp6Znaqhk5CalYT/g4yMkI+Sj4aD+IGA+YCIjYiKiImJiYqLioiHiY6MiIODgoKF/vTv7+/t9ePk4+TY3+T4gvj89v6AgYH/8u7y9viAhY2OmZKMipCSk5CPkJWZl5WSk5aYnJKQkJKSkIuIhomPjJCJiY6LkIuMi4OFhIGEiPn6+v3wgImEhouCiIWC+u/y8fiC+/X9//uA9/Tt8/Hz8fTz7/j9/YGB7vv+gIGF+P35/oT+hPnw9o+B9PLxhIX/hPr18vX7hYX09ezd6OPn7urs69ixsrC9ytb1zNLj5oLbg5m+08eY+oH6gIePjYOBhI2Fhob/8PmE9++B/YWFhPL18f3xgYGVmJqvtb24tsHJqJSPkYyFiZCAkZKPkJCOjoKKh4eIgoGGgoWE/urz6P/p7PDx8ffm9/ru7O/p3ubo6+bQ1Ofs8O7t8e3uh4aEgoWCgISHg5iRgoqBiYCbmYOZivaIi46Ki4mVj5WWk5CSkpGOjI6HjomKmo+LhoOC9YWEj42QiZCPiYuDgYSPi5aNjo2LiYSChIN2g4WDhoF3f4OBdnfje319gYh/73x6enp8f4F/hIWEfn14eIGGh4uIg3rXfHZ3fHt+fnh/fH11gYp9f4CCdud15XZ8fXR6gH143HZ2d3h5eHt9fH16eXyCg4WFf39+e4CFhIB/eHd7fHp3d3Z7f4J+gIB/foKCgoR/gHp9fX9+fn99f32AfXh3dXp+fHjpfIN+e3x7f31+gYGAeHp7dOx7g4N4gYCDhYJ61HyFfnp8edh46n53d3997uXkfIGBf4d97eV7hX19e4SAhup/g4J7hYZ+fYN7hoN8hIyChpCCf3eQfXvm5n16e3rjfXt37oB4eOPfenp3eXnngOp7fn97fHt8gn13eXx8g4+akHyPjo2AgH54d+zs6/Lm4IOC4OPkffF67oSDf4V9eHp2h4OAh4Lte4eJhXl/fYOIlXjwf4aGgIJ/gn/xe3mMgIN7gH+DgIqAg4qEg4V9fn+Eh4mEiYyBhop9f4GCh46IipF/dYKAgYaGe3l+f3mHgHeLfHuEiZSJ5+19eOx6g4N7736AgYDbeX2E7oB6hH95g4aFgfF+fn2BgISEgX1/gYGEf4GAgXt9e3x+eoB+fnqCf3+Cfu96f3x8e33reoCAf4J7gIF+f3p7fHl8fPDre3p/gH96fYKEiYiFhomLh4eHgICAeX+Cf4WAg4R47nx+gH+AhIOBf3t9dnl2hIyMiH/1euTE03t87djL1dvDvaC2076bxMjY16aG0aezw5bG1XxwcHt8f3136XN+fe7r7Xrye4B7fHt5gH7dts3TydTBeHpxdnfc18XdddK7s7fKysp8g4mCg4qGhoaDi4mKioyMhYiUjoWGj4t76XiChIaDgIiHfnrmeOp5foN8fnx9fn+BgoF/fn+Eg4B5dnV0duPf3N/f2ubV2dna0djc7Xrl6+rxent68+Xk5+jqeX2FgomEgX+ChYeEgYKFiIaGhYaHiY2Fg4OFhYOAf3+BhYOGf32BfYN/goN7fHl5fX/q7O3t3Hd/enuAdn59e+zk6ejrgHnr5e7x6efs4enm6Obr6+Xp7OZzdNrq7Hh7fuXn6e587Hrs09uAct7f4Xp84HXs4+Do6nl56Ozcztza2uHf6unPp6Oir7nL57/G1dF6yniInqqihNdy4HJ7gn54eXx+dnl+7uLpfOrfeOx6eXvm6uPy5Hp6h4mIlpignJqfpIp7WXx+fHd8gYKDf4CCgIJ3gH1+e3R1eXd8e+vb49nz3OPm6ebu3vH06OTm5trh4eXfyczd5Onj3+He3Hp9fHh9e3Z5fnmJhnh9d4J2jIx4ioHmfn+Bfn99h4CHhIYphYWDgIJ7hYB+joaAfXp76Hx5gYGDfYKDgIB3dnqCfIiChIGBgXt3enqAcXBxcm9nb3RwZWbFaW1scHRs0mxraWxra25ra2xva2xjY21xcnRuamSyZ2Jnamhpa2tua2hjaXFpaGdjYMdiwWJmaGNqb21qw2lqaWlqa2xtbG1oZWlwcnNyb21tbGxubWtoYWJoaGdnZmZrbG5qamtram5tbWpoaGhmaGttbG2AbWpraWtrZWNiZ21raMprb2tsa2htamtubm1nampkzWpxbWlvbG5vb2q9a3BmaGtnuWjIaWZjaGrPyr9pbmprc27TxmpxbWppcG9xy2tpaWdwcGhrbWtxcWttb25ydWlkX3Nrb83HZmdrZ8Fqa2fRcWhnyMZta2hpZcDBaW1tb2+AbW50b2ZpaWpscndwZ3Z4dm9xbmVl0tHMzL27aWrFy9Rw223Zcm5uc2hnbWpzb3Bzb9FqcndyZ25wb2x5a9pvc3BubnBxbdRqaXVvdGxxb3JydXB0dm1sb2pub29zcXBzdm9vdmxucXNyc3ByeG1mbWtvb3BpaXFyZW9kcWhnbGuAdHTIz2xo0G90c23Xb21wb8xrb3HNb2tya2dtb3Jw13BycnRydXRzb3F0dnhzdHFxa2xrbm5pcXNzbXNubG9u1G1wbGxqbs9qb3Bxcm90cnBva21sZ2tt2dRubGxtb2tucG1yc3JydXl2dHRubnBqcHRxdW9xc2zSbG9xbW5xcm+Aa2xlY15mcHpwZ9Bnxa6+aWrVx7zIx7SukKK2n4OoqbCxiHOwjZike6SoYFdYZmhsbWjIYWprz9DPa9RpbGtqZmVqZ7SUpqebqJpfYFdbXaqqmqxcqJmXl6GdnGJqbWhscGptcG11cnF0cnBobHBtbXB6dGrNam9zc3BzcGxoxWaA1Ghtb2lrZ2psbm5vcG9sbXNzbmRfW1pasLS2ubeyvrW9vsG/xcTQacfKzNNra2rTyMnKy9BsbnBsb2trbG1wcm9tamxvbnBwcG5ucW1tbXBwb2tqbG5wbnBubXFvc21xcm1ta21rb9HS0dHJZmxoaW1ibHBu1s7Q0M5p0s/a18uAy9PE0crPzM/RztDOzWVlv8vRamlpztDNzGrRb9DGym1jysfGbm62XMvIx87MbWzP08S0vbi9ytDd5MCRi46Rjqq8pa68uGGkYGhucG5hrFyzXmZpZmVraWZhZ2zOwcdqzsVnzmhlaMzOys/Ja2lwbW10dHZzcnFwYVthYmViZWqAa21qaWxrbmVsa25vamhqaWtrzcXHwNvFy87QzdbM2dvRz9DUys7Lz8i2tb3Cx7+9wLy7ZW1raG1qaGhrZnBsZmhqcmV1eWp1cc9sampsa25waG5ucHFwb3JvbG9rc3BveXJubWtuz2lpbmxva2xvbW1mZGhtZnZ0dGxvcGpkZmyLfwF+hn8BfpZ/AX6TfwN+f36IfwF+xH8BfpB/AX6KfwF+hn8Dfn9+hX+DfoZ/gn6IfwF+mH+CfoR/Cn5/f39+f39/fn6Ff4J+mn+Gfgl/f35+fn9+f36NfwF+i38Bfoh/AX67fwV+fn9/foR/AX6EfwV+f39/fol/AX6gfwF+hn8BfpB/gn6efwF+kn8Hfn9+fn5/f5l+iH8Jfn9/f35+fn9+iH+HfoV/hH4Bf4d+mX8Bfol/A35/fpd/j34Bf4R+g3+GfrJ/hX6Jf4V+AX+Sfgh/f35+fn9/f4R+D39+f35+fn9/fn5+f39+f4V+gn+XfgJ/foZ/A35/fot/C35+fn9+fn9+f39/hX6mf6F+ln8Bfpx/AX6ZfwICBACEjYCVlo2MiYyMko6IhIP/hoeRj4uJhIaFgYmMj4yHlZWQkIqRiYSFgIaQlI6HiI6HjIaNio6KipiQkI+Pk5GKioiEiIuDgPTrgIOCg4WHi42MioyKiImHh4eJkI+LjYiJjpOKhIWFhomHhIiLjI2Mj42NiIWIhoSHjpaXlZGPjZCTkA2LiIiFgfWAhIT99PWBhICAgoODhYSNjIqIhJCYi4SIjYSMg4CJi5KN/4KIiIiJiY2bi/j6gIiKhouMkIKIhIqUiZGGj5SFioqEiIKHhouK+omUj6aVloOalY2NjpSIi/mIioSDiouCgv+EiID5hIKFhYOChJCEh4WBiYOPiYeOhJWbpJWeh4iehJCMl4eBiYKAgoOA/vP784Do8PD7i42HjJSag4KEhIyKk42FhYmKiIqLhY6Nkaf96/uCjo2NkYyQjIKBjJCYl4aHj5SVjoeFjouOj42NkYuJlZyPhIaQko6XkZ2ch4CNi4WIjIaHmY3/gYf8i42Ul4GOl6OfmZCJgoCFg4iO7YODh4r5i/f1gfuAhIH59IONi4n7/oD6hIOHiYOGhYWIh4WKh4qJhYaBhIP1hYD9gYaEgIX56fz+gvj/9YHy9IaH+4L29/Py8fiA9/GIioSNjYuIi4mNj5CTjoeBjI2Hio+NkIqOjIyNi4eLjpGJiYiC/oSNjoeDgIaG8++Fn6Kbhejk84WQ+9Hb4OCA2te+vcjM6tO+xL+3sdrOysnnzrCov9/IyuP9g//k8fHl3Ovh6u7xg4WE/IXwvrbq8/vo8PmD8ujg2Njh6NzO4Nbj5uXAvvCLjYyVlJCRj5qPh/uQhpWcm5ydmpSflpGOjoyIjvmAiZCG8vXzgYaIjYaEhoSMi4ODiIiHjJCXlZCAjIiEgID39Pj47vDr5+nf3Ov3+fb++fPp7vb/gP/8+Pb0hZSZlIiChY6VlZKWmpyYj4qNj5eUlYqQj46OjIqH/4WGioaLgYH9hoL+9Pv/+/uAgvrygoGE+oaEhYaC+/bv6+ru+v2BhP7N/vv88+/k9vft7vz19fz7+/iFiP/5gPCA9o37hISBiYrt8IeK6vPz//Pth//1/oeC+/TZ+e3v7OXt5tzKzbSyzc/O5+G60Nrf5vGBiYD0pMLTxJP2hJCCiIiIhYmIh4iFg4OD+PiG9O7p7IP/gPv09vn5ioqZlqe9zsTLo5CLlpORjZGKioiLkpGPhoeGho2JhoSCgYDy+Pxr9Pbx8vHm6fLq7e3s4+/i5erw8eXk5evr09Ha5uTu/IuK/e6Ai4aGg4uH/YaF/oHVkJKKlJmKgoaEh4SDiYGLkZWMko6Ok4uOkISGkZORkpeQkJOTjoeHhIKImJuRjpGMjomQmp2PkZCKi5OAgoODhIuLgIB+f36DhH57e/J/fYKBgH14fHl1fYGCfXuHhYKBfoR8eXt2e4KHf3x+gXyAeYJ/gXx+iIF/gYOGhHt8e3d+gnl32c51eHd4enx/goF+gYB/gH9+fH2Eg4CBfX6BhH14eHp8f356fX9/gH+Bfn97eHt5d3l+hIaFgoGAf4KDgX58e3l24XV5e+ri4nd2eHh2d3l4eXl/f4B8d4OLgXt9gnmFfHR8foGB6nV7enx7fn+Lgejnc3p8en6BiHmAd36HfoN5g4l7gX94fHh9e31/5XyIgpSGh3WKh4J/f4d+geV9e3t6gYB5evB7g3jnfHp9gH56eYd7fXt6gnqAhX9+hHaDjJGDjnt5jnl/fYl9eX94e3t48+Xu5Hfe4+Psg4OAg4iLenx+eYB9hoN/foGDfoCBe4N/f5bq4PJ5gn+Eh4GHhHt6g4KJjH19hYmLhoB8hIKEgoKCh4J/iJCGfn+EhX+HgIyOfXWCgX6BhIB/ioDrd4DsgIGGiHSAg42AioiCfnh4fXx9hN17fYCE64Lr53vufHvs43iAf37s8XrrfXt/gXx+fHx+fn+Cf4KBfH56fXrmfnn0en59eH3q3PDxfOzy5nrj5HuA73zr7eXl5O166eKBg3yDgoF/gn+EhoeKhX92gIJ+gYSChX+Bf3+Af32BhIV/gYB673uFhH2Aenh+feDUcoaIi33c2eJ+hvDI0dvc1tKzrbW62sizs6+so8y8u7vYv6KZrMu0tszhc+PK19bMydbK09nZdXd44nzfrKPX4OLP0tlz1MrDxcPL08i8zsXU1tOrp9R8fXyEhYCDgol/dtuBeYWMjYyNiYSOhoWDhYJ/heh3gImA5+iA4nl8fYJ8ent6goF6en+AfYCBiYmEf3p2dHXj3+bj297a193T0d7p6OTx7une4erwevTy7+3lfImNhn14eoKIh4GFiYqGgoCBgYiEhXyDg4KEg4OC9317fnt+dnbmfHrx5O3w7et4euzleXd56H5+fn977efk4+Ll7O93euey6++A7+vl3evs4uDt6enp6ObjeXzq5Xbd4oDkenl3gYHf436A1+Dg8u3ff/Dl6Xl35uLG5t7e2trf3dTDy6+qxMfG19OyytTV1+B0eW/Qh5qpoX/Zcn10eXd6eX18e318enp55+Z85eHb4Hvveerm5+zogH6Ig5GgrKGmiHt4goGBfoGAe3t6fIODgnp/fXyAfHt6eXh24ejs5+zi6OXd4uni5ejm2OXY3OXq6N7c3uPjzcrR3djf7IGB6dh1gX18eX186Ht76nrDfoJ7h45/dXx4fXx4f3Z/hIh/hH+BhH6EhXh6gomHiouFhoiIgXt+fHd8jIuBgIN/gHqCi46ChoN9f4iAbXFyc3h2a2xraGttcWxtbtttamxqbGtoa2hkaWxsZ2Zubmtua25rZ2pmaGpuaGlraWluZm5qa2Nnbmlla3Bwa2ZnZ2VtcWpqycFoaWlrbW5wcm9sb3F0dHJxbWpubW1wbGttb2lnamprbWpna2xqbGxsaWhkYWVkYWFlbnFxbm2Aa2xtbGppaGdlwWRoacrDv2VmaWloaGhlZ2dsbGxlY210cGxra2tzbGVpaGlsxmNnaWppbGtxa8vIZGdmaWhtdGtyaWx0bW9pcnducmxobGhuaWhsvGVtbntxcGFubG1kZmxqbshqamtqbW5tbNFsc2jHbGttcG9sZ29qa2lsc2qAcG5wcmNrc3Jsc2ZmbmdqaG1raGxobWtp2MzNx2jHzMrScXRvcnFzaW1tam5rb3BxcHBxa21ua3JoZnrIyNZob2xydW91cW5rcW1ye29sc3Z4dHBrcW5wbnBvdXJtcXZzcHFxc2pwaXN2a2VpbW5wcm5tcmzIZ3HPbG5ucWRqZ22Aa21sbmlrb25rb8pxcHBz3HPTz23Rb27SyWdsbWvS2GvTcW9xc3BwbW5wcXFycHVzbGtobWrJcGvYa21uam3Mw9TVb9rgzG3Ny2py2G/V1s3OztdszsZxcG1zcm1rbmtwc3V3c29pcXJwcXFucm9xbGxubGlvc3Nsc3Nt0W10cm6AaWpubcOuWGRjbGi/vMFtb8qzvcnKxsGdlZymw7GfmZSWjbCbo6a+pY6Hkq2Zmam5X7ymrq2kpbKnq7CyX2JiuGa4j4u2ubWlp6xZpJ2XoqKotKmcqaKxtLGLiK1iYWVvb2hsa25kXbJpZG50dnR1b2x0cXR3c25tcMVmbXJrx8ZQw2Znam9saWlpcXBoZ2xvbW1ucm9tZmFdXV64tr68tLa2tcC3uMHIxsnSz8rExcvRbdzZ1dHJanBxbGlnZ2xxcmpqZ2ppa2hrbHFtbGdsbGyEboDRaWVpaGtmZs5ua9HDy9TTy2dq085sa2/MbW1vbmrLzMnNzM3V1GlqyprS2tfSycLJ0sXFz8nFxsfLzWxuzsxmvcpzymhoZm1vyMlsbr++v9PLw2zU0dVsacrIq8e/wr26x8XCu8KgjKSqq7Wzkaqvtbq6XF5XnGBpbmterVhgXoBeXGNjZWdra2poZWPHyGnIy8LFactoyMrK0cdrZ21ocHZ4b3BhXV1maGpnaGJkZmdtbWxobW5rbWttbGtraMnNzMzWy87Nx83Tz9PV0sDLxMrT08zGxcXFxrSxt8K8v8dtbMW4Y2pqamhnZc1maMhppmRraXF3aWJpZmtrZ2xkbCxwcmxuaWtqa3JxZWZpcnV1c290cnNpZ25rZmd1cGlubmpsZW12eGxxbWhqc5B/AX63f4J+v38Hfn9/f35+fp1/AX6Jf4J+m38Bfo9/AX6IfwV+f39/fqZ/hH4Bf4R+mn+DfrZ/BH5/f36SfwF+hH8Kfn9+fn9+f39+foR/BH5+f36UfwR+f39+hX+Efgt/fn5+f35+f39+f4Z+A39+fqV/AX6If4J+hX8Ffn5+f3+gfgF/i34Ff39/fn+JfgF/kX6LfwF+kX8BfoR/g36Zf5Z+AX+Ffp5/AX6HfwN+f3+Gfgh/f35+f39/foV/iH6Cf5N+CX9/fn5/fn5/foV/BH5+f3+GfgZ/fn5+f3+afgR/f39+hX8Bfo9/A35+f4R+A39+f4V+o3+ifgR/f35+h38Gfn9/fn9+u38CAgQAgI2LiISMhYWMjomMkJWPjo+Jh4aDiIqOi4iCjY2LjIySmJSLlJiQmpSVj5WQmpSNiZqNkYuSkIyUoJOHg5aVlZCMgfn++4GHhIGFhYiEiIiHiouMjI+RjIaKjo6RjYiKjYiKj4+F+vmDhoeGgIWNjY6RjomFg/+DgYKOj5SakomEFYaHhoWGiYOCh4OCgYeQjoyNhYiLg4aHgIiCi4uPkYGHkIiKjIeMlIuSifuCi4uOiY6Ri4v7gYSHgYqTj42LhYiFioiJh4OJho+NiYuIiouLg4iUkoKN9or++4X2koufoIH/iIGKgYeHhIuBgP2Ig4v26/+FiYKHhYz/gISFhv2BjpugjouQjIuJgIGMq5nw/urp6fb4hoOFgPz09Yjv/YqSh5aPg4X/iYiMioWCjIOMlomEi4uOlZ+V//KAhoqNlZCIlYmMhYiNmZSQjoqTjZKJhoGBipCWhICEg4iVkPmDiJKir6WE8vL8goOCjYiCj4aGjIyPhIOSkZmIm6CEl6qbq52DgP+Qhv6LhoKJi4mOgPaBjYKLhon1gPDz/PPq8/n6goH9iIKDh4ODgYKLiJCVkouGiIGIiISJiIL59/Hr8YH79+729PHu+e346N/w2/Txg4uQioKNj5GIkIuIio+KioWIiIyNjIeJjY6Ki4uGiIqJhomPio2MiIKIh4SFgYOFhoL97feJlp6Ize78g4+A2MjE0dTCxb/GgNXX0rmekremrqann7/Py7TX7oDYxOPk6OvOy8/FvKe+tbC2o6zS9OjP4ezbxq6tw8HDvrqru7HOyrm2w8uxsL7Q7IyWlZaflpSZkpSPjpWcn5iVlYiVj4+Yl5uOjaGhh5SWoZ2Sg4GHhoWKgoqLiYiIiYqGhIWIjY+YmZ+XkYyGgIOAhP6AgPX37ebx7u/78/X7hoT78+fm9ouNiYyWkpainIyGh5KbnpeVlY2Ll5KNhoqNio6FhYD++fr/9f339oSDjf3k2YSD7PfygPb+8fP4gYT+8+rh8ob8gICAh4eKh4mA8Pr6+e7n8ejt7vD56fTw8+/v+Pv2/IWC/oeCgoiGgIeEge79hYL4+/3g5fj994SI/ujx9oDs6vLl1NrXvr/Lydji1srb6t3S1uL3/ICU2ZOtucrEvqGdlIOIg4WBhIaChYH1hYXz9vDw8ObX5IWPi4L59fiCho+am7rAw7GclpKTk46YkYyIhY6Qko2Kh5CMgIGIhYGAgfaB9vP27ujrZe/v8e7u4+vv8uzp7+7o6Oju8OLa3ufp6PP284iEiIaMko2Nk4+PjoaOhpiU84OOhof7iJiXi5aYk5STlI+PjZORjJGUko+Nj42OhYyIjIqWlY+Ii42Mi4SEhf2Fg4eHgYCGj5COgIGBfnuDfXyCgn2BhYmEgoR/fn16foCAf312goN+fH2Dh4R9iIqBioSEgIWCjIaAfIt+fnyEhXuDjYF5dYSEiYSAeeju63Z6eHd7fH56fX18foB/gISHgXt/gYKGg35/g4B/gYF44+J4e35+eHyCgYKEgn17eep4dnZ/f4OIgnt4gHl6e3p6fHh3e3Z1dHqBgICBen2AeX18fH18en14foCCiHh9hX2ChX19hn2Cfud5gYCCfH6Cf4PreHyAeH6Fgn5/en54fH18fHl+fYSBfX58fn+CeX6Eh3eD5oHk6XrigXyMj3TkfnmCdn9/eH55e+2BfYPi3fR8gXl+e4LrdHx7gHvwd4GKjYF9hICBf3Z3fpOL4/Xf4N/m7Hx5fOzm54Hm84KIeomGfHvmf32CfXd3gXmCioJ7gIGBhY2J7ud4foGEi4V8iXuDgICBiYeHhYCKg4mCf3l5goSHe3l+fHuGgOV6f4WQmpN56O3xe3t5hIF8h31/g4CGe3mHhIp4hYxygH+RhZSMe3jthnvugHx8gIOAhXjqeYN5gXuA5+Lk8enh6Ozuenrvf3p8f3t9e3uDf4OIhoJ9gHqBgn+AfXjm6ufj5njv7+Tk5e7o7uDx39Pk0ejkfIODfXV/g4V9hIB+gISCgn2Bf4KBgX2Ag4SAgYB8fX+AfH2AfYCAfHl+f3t+gHl7gHt3693bdoGMfb/d5HeDec6/usnPvbSvt8PGwayViq+eppybkbLBwKfG2XfJtNPW2du/vcO7spu0qKOqlZ3B49zB0drItJyZr62wqqibqqTCvKqotL2jorC70nyFhIaNgoKIhYiAgIWLj4mHhnmFfn2JiYyBf46ReoaJkYyCgHd1e3t5f3eBgYB+fn6AfHt7foCBiYuNh4F9eHZyeOh1dOHl39vl4eDs5+rwf3zt6N/h64KCfoGJg4aPjIF5eoSJi4aCgX5/iIF9eH2AfYF6e3jx7Ozr4Ojj4nh2furbzHt83+fjeery6Orqdnfs6una4HrqdXd5fn6Afn943OntgOje3urd3uLj6tvj1trW193m4uZ6d+Z5dHh/f4B4dtzoenbk5ebR1uTq6Ht649Xe43Xe3OPcz9PJtbbBvsrTxrvJ08nEzNPi53WBun+Sl6KenYiFgXR5d3d0d3t3enfle3nf5uLm5NvL23uCgHjq5uh3eYCHhJ2eopeEf31/g32HgIF+fXqBgoR/f3yCfnV3gHx4d3rleefl6ePf5Ojn6eTl2+Tk6eTj5+fi4ODn6t7W2eDh3uns6IB8f32BhYGBhYOBf3qEfIqH03eBenzmfYqMf4iKhoiIhoB9fYODf4SHhIGAgH9/eoF8gHyHh4F9gISAgnx7fPB9eXx9eHl8goSBgG5wbWtzbGpvbmtrb3Nwbm5rbWlna2xqa21nb29oZmhra2tncXBncW5uamxrb21qaXVmZmpxa2NnbmtlYmpscm1ta87V1WlpaWpsb3FsbW1sb3FxcHF1cWpsbm5yb2tsbmtqa2tnx8ZnZ2hmY2ZqZ2dpaGVjY8NlYl9lZGhubWlogGlqaGdnaGVlZ2RjYmVrbGxuaWxrZmlra2toZWlkaGxvd2ltc2xxc2tscWpnaMNsbmprZ2hra2/LZ21waWxsa2hvbW5nam1oamZtcHJtaWhpbG1vaWtucGNvzGy5x2fCZmZscmK9amhuZ3BwZmtqbMtxbW3Bw9pucWhrZm7DZG1qgGzZZW5ucG1nb2xvbGBkaXRwydbDysjKzWlma8zK1XTW1XNza3V2bmvKb2xuaWVnb2pyd3JqbG9xcHRz09FrbWxydnFqc2pycXNuc3Jzcm93dHZzbmpscXFybGtvbWlwa8dqbG90e3po0djZaWtqc3Nwd21wcmxybGpzb3JkZ2tYgGBuaHVwbmzUcWvTcW5vcnRxcmrQa3Bobmhsy8bDzs3Jz9PYcXLccW5vb2pubmtybnB2c3BvcWpwdHJwbGzOzdTTz2zZ2M7CxdPV28jYyr/Pw9PMbXFuaWNtcnRscG9ubW9ubm1wbnBvcXBvcHNxcG9tbW5taGhraWxtbGpub21ugGlwcGpmybuwW2NvZ6zEwWZuaLesqbi8qZyXoKino5ODfJ2QlIqFgZmpppWwumeum7O6v8KopamknoyjlI6Tgoyowrigq7GklX98joyOjYuAjoigl4qKmaSOjJWerGJoZ2lqYWVrbXRvbW9wc3BwcWpuZmlzcnFsanNxY25ucGxmgGJiaGVnbWdvbW9vbm5vbmpqbGxsc3V3cmxpZGBdYr9gYcHCuri+vL7LyMvTcWzRyMLIz3BvbG5yam11cWllZGtvb2hmZmZobmppZWlmZmxpbW3VyMfCury7xGdjbMnGuW1ov8zJbNHb0dLNZmXGxMi/xmvNZmdqcXBwcHJqus3UgM/Axc3FyczAzcDDuL6/ubzN09Bra9FrZmhvcG5obMXBZmjFwr25w8bFx2tpxbzGymnFxcrCuLutn6CoqKq0ppuhq6OhprC6wWBlj2Vsa29qbGVkZF5iYWFeYGdlaGbHaGfCxsPIxr6zwWdubGjPxclmY2ZpZnFubmljY2JjaGVtgGhoaWhub29qbmxtaWZpcW1qamvKacvLy8rHz9LT09DQyMzP1M/S1tLIxcfO0cnCwMXEvsjExGlmam5ua2ptbG5tZmV0a3JwrWVraGa9anJ2aXBzcHNzb2lmZ2xvbHBta29tbGhoZW5rb2xvcmtpa3FucWxubNZsaWprZ2lqbW9rwn+DfqB/gn6OfwF+t38Bfol/AX6hfwZ+f35+f36FfwF+in8Hfn9/f35+foZ/AX6EfwF+j3+Hfgl/f39+fn5/fn6HfwF+kn+CfqN/AX6Hf4N+nH8Efn9/foh/AX6Gf4l+A39/fpd/hX4Bf5B+sn+DfoR/Bn5+fn9/f5t+AX+vfsF/A35/f4t+gn+Ffp9/iH4Mf39/fn5+f39+fn5/hX6Cf4V+An9+iX+WfgN/f36IfwR+fn9/iH6Cf4R+AX+XfgN/f36TfwN+f3+IfoR/g36jfwJ+f6F+kX8BfoR/AX6ofwF+in8CAgQAY4iJiYWLioiHiZCLhoeDi5KSkY+OioL4gpGXlZaTlJOVlY+EiYmKjJOViICIiYqQiomOkYyLjZCLjpWHgYSMgPb4g4WJhIaOgoKAiJCKi4mCiY6Ih4uMh4eHhIeGiYn8/4KBgYSHgIOA+fj6+fz8+f/18Pf07erj5vWAg4uOiIaIiYWGhYSEiYSChIWIiIqJi4iEhoOEiYqLioiKg4iPkYiMjY+Kh5KQg/T8gYqNiY2Giv+EgfrxhIL1hYf9g/+FiYeEgoWDjIyNjIuI+/qFi4WLkYCEgYGDg4mMn4WQgIqXl4b+hIX9gPmGhISB84CF+4mD7f+BhIaDgIKGhoeEiYOBhJmejoKCg4H3hYWG9ombmID47/j9g4OOg+nzgIaAipCIj42Bh4qOhImTkISFi4mHi42MgYaSkJmU/oCAho2KhJCakJOQhIKBhoielJOHiYyTl4b+gYmElZWOiIWHkJqippmOjYyKgIT7gIGBgIyNhoaGkIeEi4OCgYuNkZWAjZaiop+fn5qMhYroh4+DjYaBgoGCg4iQjo6MgoSE/eni4+Pm6/uBgoKHh4KBhvKDgP2Ok5CSjIeB/oqJiYiTjPzq7NTS09vk4+Ps3+Th7vP4/oKEhP2AiISQjomKjIKMj4qNjI2NiYeIgIuKiomLjIqMiYiJjYeEi42MjY6RkYmIhYOFgIiEgvn/gP+Ih4qIj5WQjZmQ/trH0sfBvKOcqqijraaks9nh1dC2laqqtLTHurW3wszQ5ejhz+DdwNm1tMDGvN+AkpeNgfDj3Mi7qaq3t6arkZCTrLWopsbyipCKjJOVlZmdnqKigJqZmY+SlJOTjZOalI2JkouNkI6IjZmmoJuQi4OBgfyGhIuEhIOChIiKi4P8hoSKio2SkpKWnpuQifn05tLl697Y1uro6vD8iv37ipCRkY+QlaSXjJGaoaKbnJ2anpaHgI+KhvL9/e/s8efg4+iOlJKBiYyFjf/zg4GEjoX8hPnzgO3x4d3j2NHz9oL/jYSIgYGGg4HzgoH47O/19Ozu8vf49Ovy//vz5vyD+/n7+fPx+/SD9f6CgPrr+YGC+fb96oKA+IGAgYOC+fr+9OPi4N7ayMOyuuDO0d/Q3dmy39be5Nz4kIOJl5qenbi6vLGjloyAgYaFhoeC9+zv+4Pz7ebngO/t5+74gYWEhYeSlpmam7K4p6SbkpKXkpyYkY+Hjo+KkIqChoiBhIiD+vT++vb07vL39/L59fXx9vT09PLv7+vp7Pbu7u7s5t3Z197d3vz+iYaDgJyRhIKMgfz8gIOFoIeHjoiGgfz/k4qH9vWDjpWUi4yDiI6MhoSKgIWOjZGHGYOIg4OOi4iEg4GEh5CRjoyQjYyQi46PjI2AfX99fIB/f39+g398fXiBh4aHhYN/eup5hYiHhoOEhIWFgXl8fH19goV6dX59fYN9e39+foGChX9+hnl0d4B24Nx3eH13eIB2dnV+hoGBfnl/hH19gYB7e3x6fX2Bf+nueHZ3fXx7e3p46ejq5+vr6Ozk4ebj3djR0950dn6BfXuAfHx5e3p6eX15eHp7fn5/fn97eHt5e3+Ag4B6fHV6gYV/gX6CfXyEgXjf53Z/g3+Cen/reXfq4Ht34nuB7XroeH58enV6dn6AgoKAgOrgeoF6foV2eHd5eXl9gJB6gXN7hIJ56nx76+d9fXt543p96n554PJ7fH56eHl7fX58gX2AeHaGjoJ5eXp45Xx6fOJ8i4t37OPx8Hx9gXrf5np9eYCFe4SEeICDg3p9hIZ7fIJ9foCAgXh6hIKJiPB5en+CgHiDjIWHhnx7en59joaGfYCCiYp/8nqBe4iHgn58fYKJjJSMhISFgn3weXt7eYODfHx8hX97gnp7eYCAg4d2f4SAiouJiIiIf3yB2HuCdn13d3l1dnd8hIGCg3p6e+/d2NnY3d7seXt7gYF8en3jfnnwhIaEhoJ/eu+BgH99g4Lu4+LIx8fO297f49Tb1+Pn6vN6fXrsdn58hoV+f4F4goSAgoCCgH59fYGBgYCAgoKEgX5/g3x6gYF/gIKGhICBfX2AfXmAfHnq7XXleXl9e4GDgH+Hg+rNu8jAubOYkJ+elaCcmKfJ1svIr42io6imuaqnqba8wNnc1cPU0LTNqqq2t6vLdISJgHTVzcm1q5qZqauaoIeFh6CroJy3232BenuAhIWJjIyPjYaIiIKGh4aGgYaMiIJ+g3yBgYB7gIaQjIeAf314dnjsfnuDfXt7ent9f4B56X58gICBhYiJiYyLgn3m4dfH2NvNysrc2drg64Hv64GGhYWDhYeQhn1/iIyMiYmLiYqDenSCfHri7+3h3eDb2NnYgoSBcXuCfH/r4nh0doJ66n7y6uXn2dTWy8Tg4njugnp+fHp9e3rmennn4eKA5eTj5OPp6+fd4u3o3NHoevDq6Obg3+jfd9zmd3bk0d14eero7N54d+15dnd1dOTp7uTWzc7Sz7u2q6zSwcTRxc7JptDKztXG2oB0eYKChoecmZyVjYR+c3N3eHh7eunX3ep54uLa3OLi3N7kdHh4eXuAg4aDg5WajYmDfn+EgImAhoKCe4GCfIF6dHp9dnp+eOnj8e7p6OTo7u7p8Ozs6Ozs6+fm4+jj4ebw5+fn5N7W0c3T09Tu7oJ9e3iRiHx5gHXj6XZ6epB5eYJ8fHXl7IZ6fuTkeYSLiH+AeH2AgHp4f3V7goCDeXN8eHiBfn16eHh8fICDhIOFgoCFgoSCgIKAbG9sbG9wcG5rb2xqamZscW9xcnJtaMpnb3FwbW1ua21tbWlqamlob29nZ3BrZ25paWhkbG5vc2pobGRhZW1mxMNoaWtlaG9paGpxdnFxbmpwdG5tb2xnZ2pqbW5wbcbLZmNka2xpZ2dmxcPExMjJxcfCwcXCu7OrqrBdYGZsa2oIa2xpa2ppaGqEZoBpaWpra2hlZmhpbGxubWdoZGlucHFvaW1ra2xraMHHY2hvbm9machkY8vIbGXDa2/TaMZmbGxqZWpkam1ubW9v0MdqbWhpcmhoZWllaGtsdmhsYWVpZ2bFamfJx2xsaWjGbG/La2fJ1Wxrbmppa2pqbWxwb2hhbnRtampqaMpoZYBowmZycWbMy9jWbm5rZ7/Qa2xrcnJpcnFpcnFsaGtvc2hocGttbm5uZWtwb3F002xubW1tZ251cXJ0b29tcG12c3FtcHJ4d3DZbHJqdXNsbGxubW1tdnJxcXVwas5qbGxocHFra2xxbmpvbG1sbW1vcWZqa2pramlobWtrb8RrbYBkZmVoaWdmZ2pubnFvamtt1MS/wMLJzNZrbm1xcm1sb8hwbttycG51c25q1XBybGprbtHQyq61vb/PzMrOwcfI0MjJzWlsacNnbmt1dGtqbGZucW1xbXFwbG1tbnJybG5xc3Vxbm91bmlub25vb3Jwb3NtbWxvdGxmzNBlw2NiaIBnbWtramxrybGnsKmjnYZ6i4iCjoeJla+9tbOaf46OkJGilJGUn6Kmub25rLaynq6OkpyckKlgbG1mWqakoJGIfYCPjoKJcm9xhpKMiZ21ZWhkY2dpam5xcXN0cG5tam5vcHFub3Jwbmxuam9sbGhrbHBrZ2NjXl5kym1rcGxsbCZqamxucGrLa2ptbnBzcnRzc3JsaMC9tKi2uLCurr28v8TJbcXCbIRvgHBxdGxoam5xcXFycGtraF9ea2ppw87JvL7Jx8G7tWZkY1hkbWlty8JoZGVsZslt0s7K0sS8u7CtwsVo0XJrbmtsbWlpxGpnzMbFycLIyMbR0ce/xtPNvbfJaefdz8S0xc/BZsHJaGjTv75las/NzsZtZ8xkY2ZoZ8rP0s3AuLq9gLSlo5mTtaSrs6OpoIKop6quoK1iWWBkZGVgaWlta2hkY11fYmZlaGnGtsHOZ8PDvL6+wb69vWFlZGZlZWZnY2JrbmVjYWBkaWdubGpraG1uam1mYmduaWttaczI1tLRzszR1tbR19bU0dbW0M/OzM/MyM3Zz9DMx8S+u7W4tLXKSstubGpoeXJraWxgwcpnamt4ZmZuaGhfxc5sZW3GxmlxdnJta2VpbG1mZm1naGpnamReZ2RiZmRmZmZobGtsb3RzdG9scW9yb21uln8Bfqh/gn6df4J+iX+Rfq9/gn6Hfw1+f39+fn9/fn9/fn9+jX+CfpV/BX5/f35+hH8Ifn9/fn9/fn6VfwV+f39/foR/hH6Ef4J+nn8Bfpl/AX6TfwF+oH8BfpJ/iH6IfwR+f39+h38BfoZ/kn4Ef39/frF/BH5+f36Kf69+hX+Ufqp/AX6MfwF+jX+OfgN/fn6Zf4p+iH+CfoV/An5/i34Cf36IfwN+f3+SfgF/iH4Kf35+f39+fn5/f4R+A39/foV/m36Vf4R+AX+JfqR/p36Kf4J+in8Hfn5/f39+fqx/AgIEAICDioaFiI+LiYyNjpSVj4uGioqWnJiWjomPjoSEjJSTkZeYmZeYkoyRjI2Sj42Hho+SjJOXnJCMkZCOlIuNjIiFhoeGgoOKiouEgoeIhoOCgIeHiYuIhYOCgoKAiYKB/P+DhoeKgvyBg4aEjY+NjImC/PmBg4SBgPru2dbsg4SB+YDy9vr2+4CBgf6BhYWBhYOGjpCMjIqMioiIhYeDgYOAgYCEgIGCi4eHgfT3h4qNiIWHhoaFgoGHg4H+gISDiImIjomFhoaNiYiJi5OEgoaIhIKPi4qbkoSGgYmMkI2Sl46Ki4SEgYWChoGDh4T4+4iJhfuB+4KAgoP9g4OGgf2HgYCGnJyB9fLwgoD+gIKJhoyfnIaIg//88IP5gv+B+/WChYyVjYWKi4P4jI6Pjo2DhYWGjpWPj5edm4Pw+4aGio6PkZOVjIeQjY2LiomPlJOUjpGLlZSMl5GSnKCjo6OinpqTh4H18vj0/Pb3+P6Cg4aFhICHjY2PkYiKioaLjpSXoICnk4yjmImbp5ufm5yppaOXmJ6krKWem5yQhoKA9fzu3tjV1d7viZGMjoqHhIeKjvyMkYOHjJGGi46Mj4aKkY2Pj5KBhoOEiIqGgo2Lg4iL9/+MjJGM/YGIj4b7gf38iYuGgIH99/iEi4qGioyIiouJioaJiIiFiI+KioqJiI2NjoCIhvf07PWA//6AgYP39ISYkJKRlJ2QgeDX0cDBtqqns6mVjJ2yn5ihpKK6x7/Iy7S7tKnE6NvJuqyxvsXV7O3Y5f2AhPT83NfIvLCjsbGupp2isaOnp6Gr3IuLj4uLiYqPlpWYlpSUkZCSk5COj5CVj46MlY6IiYySipOUlKCgpoCdjP/t9oCGgIOFi4+Ni4uMiIqJh4WEiYT99/b/gYeOk5aSiYX48urm+YaIiJCXoaCXmpOOi4ySko+SnqiplI+QoJuVkYuIgYWC8/2DgoqA5oD88o2agof18/f7+YORkJH36u3s49ji2uP0/IiRhYqahP+EhYCAg/779/X/6fr69IDy8fHq9PH2+/n8/4T89u7/9fP/8YGCg/eD+e6AhP3y+YHx94GBj4f67/H8gID26+vs9/bi3ePWxcy7udDL1d3QsI6xrbfM696Gop2Wj5+HjZynp6mppqSgko2JiI2L//Hn5ebn6ezw5+3n3t/b74ualoKKmZCMpbuzoZiin5+Zk4CalJCMjImSkI2Nio2I/fb8+PeAg4H58vDz8+7n9P307+rn3ujt5uv39PXs7+js593e5dvU4e+E9IOIiYOI/vmNk4qDhYuJlIuDhI2JhoqMjI+IkIqIhoWJjImHi4GMj42OkJiSiJCNioyIjIiIi4iDgYKGjIyDg4SJioiHh4eIglZ4f3t7fIN+foGBgYWHg396f4CKjoqHgn6CgXh4fYSFg4eIioqKgn2Df4CFgoB7eYCEfoSDiH16f4OAhX+CgH57e317d3iBgIB6eH6Afnp4d359fn5+e4R5gHiAenns7Xp9fH947Hl6e3h/f319enbn5XZ2d3Z15NfEwNd4eHfo5erv6e14eHjseHp6d3x5fIKDgIGAgX58fHp8eHd6d3l4e3Z4eYF8fXbh5Xp9gn16e3p8enN3fHl46Hd7eX6Af4N8eXp6gH59fn+HeXl9gHx5gn56iYZ5eXN9gH+DgISEfHl5d3x5fnx8eHh9e+3rfX556nnsenZ5eut6eX567355e4uMeOfm43x57Hh2fnx/j497fHnv6uF76HrwePLqfH2FiYN+goN953+ChIGBen18fIKJhIKHjY175fJ+fH+Dg4OEiIN8hIODg35/g4WGhoGGhImJg4yGhY2OgJCRlJGMiYV+fOrm6eXv7ezt8nx8fn59eH6Dg4SHgYKEf4CChoeOk4N7jYR4hY6FjIiIkouOhYWKkZSOiYqNg3x4eOPs4M/KzM3Y5oGKg4SBf3yAgoXug4d6fYGHfoGBgIJ5eoOBgX6AdXx5eXx+fnmHhXx/gefsgIKHgu54foR7gOV75eV+gn14evDs6nyBgHx/gn+BgH+AfH9+fHp+hH+BgoGAg4OFf37q6N/neezseXd659x0h3+Af4GKg3nUy8m2tKqgmqifioGPpJGKl52as8C5wcSssqudtdTFsKGUmaewwNbZw8vlcnXX4sPAtaulm6ekoZ2Wmaean56YnMJ9gHx/e3x8fICGhoqIhYSCgoWGhYOCgoiEg4CJg39+gIR9hISBiouQi3zf0N93fXh7fYCCf39/gX5/f358fIB99e7p8Xp+g4iMi4J96eDZ1OV4eXl/hpCRio2IhIKDh4WDg4uPkIB8fYmGgYB8fHh9e+bod3Z7ddp56dx8h3B01NjigOfidYGGh+zh5OTc093V3e7ygIR1eot66np8eXp87url4evS4+rn4OHn3uPj6/Ds6+l55+fh8efj7dx1eXnked/TdHnp3Oh64uZ2d3987Ofm6nZ03dfe3ujp1dLYy7a7q6jDvMrPwKR+paOpudXIdIqHf3qDcnmEjYyPj42NioF9gHt7f37q4dfW2dra3uLa4dnR09Dcf4iEdHqGf3mKm5WEfoqLjYV/hoKBfn58hYJ+f3yAferk7enpe3x67ufk6ejm3+3z6efg4dfe4Nzm8ezw6Ovj6eHU1NvUz9vkfeR6f393eebkgIR7dXp8eYR8eHiCf3t+gH+Be4R/f3x7fYB9JnyAdX2BgYGCjIR8gH17gH6Cf36AfXl3d3p/f3d4eYCCgH+Afn14gGdsbGpqcWtsbW9tbW5tamVpbHJ0cG1raGtrZWVpcXBwcHF0cnFqZ29tbXFxbWVmbHBrbWxtZmRnamxtaWxvcG5ramloa3JwbmlobW9ua2hmbW9xb25qZmZpa2luaGnRz2dqamtmzGhoZ2JnamttaWXHyWpraGRivbKjoK5hZWfOgM3P0tDSamlpzmlqa2hraGhsbGptbGxqZ2hnaWdmaGZmaGdlZmduaWpmxcdoamppaGdnamZfZ2xqa8pnamltb25tZmRpamxpZ2xvcmhpam9saG9rZWxvZ2RfZ2psaGtoZWNkY2xsb21pZWNoZszJaWxoz23TZWJmac5qbG1qzmtqgGxzcWbFyMprachpYmdoaHRwZmZm1tHIaspqz2fX1XBvcnBwcW91cMltcXNtbWxubG1vdHFucXNzas/fa2trb29ub3Rxa3Fyc3NsbnBvcXBwdXV3dHF1c3B1c3R0d3RzbGlqbM/Ix8XN0dHR025tcHJvam1xcnB0c3Jzbm5vcXBygHRpY2xmYGZrZW1rbnNma2dpbHJua2ltcm1qaGrM0seztLm6x81tc3BybmpscnNy0nN1bG9xc21tbW9zbWZqbG1qaWFnYmFqZWhndndqaGzKyWttdG/Qa3BybMBtw8Vqb2tpa87Lz21xcGttcG5xcG9xbW9ubGptcm1xcm9vcG9xgGxtz8zEymjJzWlpbcq5Y3BoZWRmbmpluLGzoJqRioaTh3hueot7dYOMiqOuqK2vm6CZi5yyo5CCd32KkJ6ssaGkrlZap7Kdn5mRjIOPjYuHgISTiI6OhoalZ2ZpZmdmZmltbG9va2tpbHBwbmxrbHNycnB2c3Jwbm9rcHBscXBygGtfrKS0Y2lmZ2pucW1rbG1rbWxra2tvbtnRzdRsbW9xdXZva8jEurG6YmVlbG90dHBycHJxcnVzcXF0c3BiZGdsaWlpZ2dnbGvDw2RhYl+5Z7yuZWlTWaq0vsS5Ym91d9LM0M/Ivr+4w83Qa29kZ3Nrz2tuaWdqy8O+v9S9z9HPgMTFycPIys7X0crGaszQz9TNy8/CZ2Vnx2jFwWdpyL/Hbs7JamtzcM/IyMxmZsDDxsjTyru6vK+dqqGYppunq56JXoKHhJOqnltkY2JfZVhdZGppaWlnaWlkYmRmaWvHv7q7v8HExcS5v7m0s6+4Z2tmXGBpYVtmb2phX2lscGpmgGtpa2pranBtamxqbWvOy9PQ0WxtbdfPzM7R0MvW2tHPyc2/xMXAzNPR18/Rxs7FurrBv7vDx2bDaGxrZGS/wWxtZmFpaGZtZGdpcGxnbGxpaWVubGtoZmhraGluZWptbWxudW1oaGZka2xwa2tsaWdpaGptbGZnZm5ycG5xcW9o3n+CfoV/AX6Kf4J+hX+FfoN/hn4Ef39/fqB/gn6OfwF+sn8Ifn5/f39+f36EfwF+hH8BfoZ/Bn5+fn9/fop/Cn5+fn9+f35/fn6JfwF+kX+Cfqh/iX6wf4l+in8Bfp9/gn6EfwF+hH8Efn9+foV/g36cf4R+CH9+fn9/f35+iX+rfoJ/lX6pf4N+k3+Efoh/hX6gf4J+hH8Efn9+foR/hX6Ef4t+hn8BfoV/lH4Bf4h+D39/f35/fn5/f35+fn9+foR/hH6Cf5t+ln+Qfp9/hX6Df6F+An9+hX+CfsF/AgIEAICDhYeH/YSGgomNjYiJkZWVioSEhoiNioyKjIiKjYyIjY6Rk4mFhIiRjYmChPiIkI2SjZD85vmJio+PkIiMiIqKiIeHhoSJjY2PkI6NiYGEgoSFhIKDg/n+if6B+PDj8YGAgvyEg4GB+/n5/YGChIOD//uA/4KDg/707OLj74OKhgv8+4GDhIKDgPWChoSBgIKIi4qLjo+OiYeEhYeEh4aChYKFhYWMiYyFhY2NiYGBhPiFi4WAgv/5hYaJjYeEiYOEhIaEgIKGgYiEhYuKiYSJi4yFj5KSgIGCi4yRi5CHhIiQi4iGhIKHhIuFiIOEhI2CgoCAgoWCgYOEiIGFiYWHmJX+goH7hIH59YOHho2XgI6F//37+Pj5gIKB/omAgIWEhI2JiYP3g4qVlJSSi4qGi4mLkI2fq52FhoKAhIuMkpKLg4eSk4+Ih4uKj5GKioiKjY+QjImMlZmhpqqimpaSj4X44u7w8vT/+v+JhoWDiIGBgYWPjo2OiYOBio6MkZKbj4+nn5CJk42OqomNm6aQgImFjIqYn52jsKGPgZKKiIaHh4Ds1+T5hYyUmZSVlJGfloyQioqHjIqPh5CIiI6boaGWhomMi5eRjJGIg4SEjJD6i42JlZeaiIb/+IqHhoqJjJmWlpGKiIqHion49PiFhomHh4uRjImGhYeGhouEg4aIjouLjICA+/Lp6fX47vL1Zvn7+fuHiIuZmouE683Bx8rHv7u7v7OovMfa2OHq0+fh6uPHsb/CvMjTycnEw9Li49axnaCXlaevo5uYi4yan66wtbfAyt/zhJOVjomJh4iIiYmLjJGSlY2Ml5WVj5ORjY6JioqLkYSQgIaGgoCAjZeRl5uaiIOCgoaBgICBgo6LioaGhYSBh4WFjIuLioqRjo6OkJqiqKiqsK2koKChmpWUkY6OiY6Vl4qMk6ClopyiopSMiYaGi4qAgYigqpb094WWlaWWjIHo/ZKKhvvx8OvU0c/Q2+Ts+PuFiouOi46LiomD/4b29OfogIOGgoOA9fP68+Lr+fOAgf6AioX5gP/++IGGhYSC9/v6goKC+4GCgoH9gYPw+v/5+fr+5/X/9vvr4N/f3N/i28u+qMOtydrTwtbj4szAxPeglp6hnpyVmpuclpGHh42B7oWLioyLiO/l5uzk497h4Of+/f3z6N7d4fuIh4qotMG2gL2jmJuhnJiTkY+Pj46Mi4yKiouCgoD/+4H9/vry9+zv7e/w7e3y7erk5u3u7+zw9fj49fPp8vLk5ujj3tjc7YacnJibk4eToJWUkJGQi4uGg4WEhIWIiYmEi4aCiYCAhP/8/oKMj4OCg4aBgv6Bg4eIh4iKioqJiYmHiYSOiouMB4iGh4aHh4SAeXx+ful6e3h+gYF9fIOGh354enx/gn6Afn98foB+e4GChod/e3l7hH59d3rie4KAhH+D6dPlfoCEg4R9gXx+f318fXx5fIGBg4SCgX52eXh8fXx6enzq7YDteenh1+R6eHrufXp4eObj4uR0dHZ2dujodel1dnjr5NnMzdV0fXuA6+t4enx6eXbgdnp1dnV0dXx+foCDhIF+fHp5fHp8enh7eXp5eH9+gHl4fn18dXh43Hl+enZ57OZ7e36Benl9eXp7fHl2eXp3fXh4gH+AfH99f3l/goV0eHp+f4N7gXp5fIN+fn57eX58fnl8e3p5gnt6eHh5fHh3e3x/eHyBfX6AiojwennneHbl4nl8e36BfHrq7Oji4+p7fHnpf3d4fn17hICAe+V6f4eHiIR/gH2BfoCDf4yZjXp/e3l8gICGhoF7fIWIhn58gYCFh4CAfn6BhIWDgYKHh42SlJCMh4SCe+rY5uXj5/Lw9oF+fnuBent7fYOBgoSBfHmAgoKGhI2Agn+RioB+g3x5knd5gYx9d3J4dYGGg4eVjYJ1hX+BgX58dt3M2ux8goaKiImHg4+If4N+gH2AgIV7g3p7gYuQkYt9gH9+i4iEiIF7fHiBhOuEhH2Hhoh7ee3of3x9gn1/i4qKhX5+f31+f+Xe5Ht8f359gISAf3x6fHh7gHl3fH6Agn99gXd26uDd4Obo4+Tm6uvl53x8fIiLf3javrO7vLiyrK2zqJ6yvNDN0NbA1tXf2LqjrK+ptsK7vLe1v8zQx6KOkYuLnaSWj5CEhZKYpqesrbW9z+F4g4R+e3p5e3x8fH6AhIWJgYCJiIiDhYWCg39/gIGFhYSFg3t9eXh2gIiAg4mNjn16eXd5dXR1d3iEg4J+fXx8e398e4KAf31+hYODhIaLjpSUk5iVjYmLjouHh4OCg36CiYuBgoaOkpGKj4+Cfnx5eoB/dHV4jJaH2dp3iYeTgnx01eeIgXzo4+vny8vIyNXa4+zue35+gX+Bg4J/evB/5OHY2Xp8eXp35ueA7OfZ3Oflennvd3x76nny7eZ6fHp4dtrj43V3duJ3e3x563Z55+3x7ezv7dzo6+Tr39PT0s/U1s26rZi2nrrHwrLDz9K4p6fZioGIi4mEfYSGh4OAd3Z8c9N1eXt9fHvg1tfh2dTN09DW6+zr4tnNzM/hd3N2kZmhlpyKhIeNiocBg4SBe4B8fH19foB6e3nz8Hvw8u/p7uHl4+Tm5uPm5uTg4OTj5N/l7PHy7ezi6+zd4ePc1tLV4XyNjImNhXqEj4aFgYOBfn97eHt6eXp8fX55f3x3fXh5e+3r7HiBg3h4en12eOx2eHx8e31/f358e3p5e3eAfX+Afn19fHx8eYBrbW5vymtqZ2tvcGppbGxvamdnZmdoaWxpamZobWxpb3F0dWxpZmdvaWtmacVobGxvbHDOvMpsbHFvcGpva2ttaWpsbmtvcXBwcW5ubWdpaG1ub2xsbs7Obs9pysrFzWlnatFtamhszMbDxWVlaGlnyMpmx2JjY8C+uKymr2Nra2rNzWlsbmxrZ8NkZ2RlZmNhZmhoa25vbGlqaWhpZWdnZmhmaWZlbGxqZGVpaWljaGa4ZmloZ2nKxWloamtnaWxnaGlsaWhqaWVrZWRtbW5sbWppZWZsb2FmaGppbGNnZmRnbmxubGpobmtqhWWAcGxra2lobGdmamxqZWtuamp0cdJracRjZMXHZWdoZWRpacjNxsLDzm1sZsVuZ2lvbG51bW5pymtsc3V0bmxtbG9ubW1qcnh0aHBpaWxua3Bwbmtob3JwbGxybnFzbW1ta21wcW9wbnVvbnBydXVybm1oxr3LxcHI09LVcGtsa3CAbWxtbG9ucXFvampsb3BybnNraHFpZmxrY11qXF9fZWJeV1hXYmhjZG1va2JubHBxbWtnxbnC0GxvcXRxcW5tdXJtb2txbnRscGpvamhpcHN5dGhsa2d3enRzcm5rZ21ty3BwbXNtcGdkyslqaWtvaGlycnVwampram1w09DQa2uAbWxtcHRwbWpoaGRpbmtlaGtubGptZWLLxMPGytHNxMnQ0MjIaGZja21mYbWil56gnpmUk5eQi52ntrGvs6W3usO6nouVk4uZo6GlnpukrK6ohnF0cXSJj4N/f3NygIqanZ6boKa2xWdqaWNhY2RlZmdmaGpsbG9paHFxcW9wcXCAcm9ucXBzc29wbWhpaGZlbG9tc3N0Z2doaWtmZ2hnZmxucG9taWlpbmxpb25ubW92dXZ2d3d4eXZ2eHVzb29ycW9wb3F1cHJ1dm5ub3Bxb2lsbWdoamlpbG1lZGNsc2y2vmRvam9hYF+yxW9oZ8XFzMextre4vr3ByM9pZmdoZ22Ac25qatNv0cm+umdpZ2ppzsvPzMXGzctsb9dlbWvLatbP0WlpaGpmzNTOaWhnyWZoamzabXDU2t/d1NXVydXWz9DDurq2s7u6tKiZiZt9kKCglKCvsJ2Gg6hqZGltaGRfY2dpaGNfXmFbrF9hZWhmZLiys765tauurLXIyMnDt6t8rK+0W1ZaamxvZWxmZWpycG1ramprbW1qamtrbW1oa2rX1GvP0c3M0sfJxsrNy8vOzcrFxcnGxcHL09jW0M3I0dLExsfDvbm2wWdycXF0cGltc29vbW9qZ2hoaGloY2NpbGtna2tobWprbM3HxWVtcGdoamxmadFlaGxraoRrEWloaWlrZm5sbG5ubm9ubW1qhH8Bfqd/AX6Gf4N+oH8Ffn5/fn+EfgR/f39+hH+EfoV/B35+f35/f3+GfgV/f39+foZ/AX6nfwF+hX+Cfsp/CH5/f35/f35+h3+GfgR/f39+in8Bfrt/iX64f4R+qX8Bfoh/gn6Qf4N+mX+Nfod/vH74f4J+h38Ffn5/f3+Nfop/An5/hH6Ff4h+C39/fn9/f35/fn5+hX8Hfn5+f39/foR/A35/f6R+kH8BfoZ/k36dfwN+fn+mfqF/g36JfwF+mn8CAgQAgI2LiYiJiIiGhYL+gYSJioeJhYOCh4yKgImOiouMho2NjpOPjouIioeGi4yHj5eRk4+WjYqNj42MlpKQioqQjoiKhouJiI6OkIb9gYiD/YOEhoaCiP6EhoaCgYPr8fn6/vXx9oKAhof/g4SEioqGgoOKi4iGiIOA/oGB/Pn27/eBgPb0gYaNkY+Mh4SChIT8+IOLi4iIioyLi4iLjYyKjYuIh4aG/YKIi4L9gvbt8/T//YWMiYSA/4KIk4mHhYGEiIWHhIeBgoODgoqIiYmRhoWFi42PkY6Li/2Eg4iNjo+JkpCIh4aIiouMjYuEg/36+vuEgPX/hImMioqEgv2FhoeFgIiC/PmChYaIhoKDhYuUl4eHjP73+YGDhoWIg4KIiYf+kIyOjoyNiIaOjJCMioyMj5GZo6uji4WAhfH0iouPjYeMgomQmZOChYeSjo6LhYeLi5SSj4+Oj5WWlp+iqZaMi5aR+O/u8e/6+/eA9fX/iIiCgfeBg42GgIWFi4uJjpKRgJSYl5+NlZ+VmZaJnpuinYqTk5iaoZ2hsaWYgYyVkYyEgoaMh4n73s/3mYyFg4eTmYialIyjmpeSlICIiIOJp56OgYWJkoySn52YlYKFkZiOhfP/iouEiYmHiIT38IeCh4aRgoqQjIiMifDu/4aJiISBg4GHjoOChIiIhIOKjIWIgIiJiI+VkZaXkYaFg/Xz8ero4ebx8On6hYaMoaOdkYL0+Pbw7NzX3NHLy8O7usnk7undz9nu7vby4un2/+78hPrgzsm8srm2u8LHyszP193j7P2Tp6+vrJ2Vk5GNiIP8/YKCh4uOkI6Hh4b+hoiGioyNkZCMkIaFjJGJjoiAgIKIgIiFiIuWnZOKipCNh42PkY2Mi4uC/IGMj5CRlJuQiYyEgoCAjYuTlJSTkIuIjZGRkY+KkpKbpJqSjpCQl6Ggm56dlJKTlZaUlJmHh5KP+++HmaGnn4z+h4D///zm6uLj9Ovm0cno/YqLjYmNh4mJh4P1+4CE/fj35fP6gYT68e35gPz48/uB8fb4/YWGg4GBg4L9g/+Fh/f/gYDu+Pvw8fb06YOHgYL2/+/n9P2FiIL9gP3y6unf3tK2wLOrz8vaycjW2NzW3vimpp2il5ePlpyaj4uGj5GEh4iG/4L8+Pf8+d3n4fD/+fnv7vzq5N/U8YKC/oKEip6plq/Nua6jnJqbgJaSlZeUkY+MjouOjIeC/4D98/f5gPjs9vr46/L0/PPx+Pjz8vHs5PWB+ff/+oH99vDm5enm6ezy/o2UlI2RiYqZjIqJjZyOi4aEhIeGiIiFiIaLhoSNj4WGhoWEiYeLjImEgoSHgP+Hh4mMi4+NjIaRjoiIjYyLj5GOiouQjo6OgICAgH9/fX5+fXrweHt/f35/e3p4gISAdX2Df39/eX+AgoaBgoB+fnt6fHt5fYaBhIKJf3yAgH59hoSDfH2BgXx+e39+fIKChXrreX146Xp7f313f+p6fn15en3e4+vu8ePf5Hp3e33reHh4fXx5dnd8fXt5eXVz6HV14+Pg2uJ2gODgdnqBg4B9eHZ0d3bj4Hd+fnt8fX9+fn1/gIB9gH18fHt76Hl8fXjpeOLY3N/n33d+fHl26Hd8hn58eXh6fXp9eXt3eHl5eX99fn2Ee3h5f4GBhIB9feZ6eXx+f4F9hYR9fHp9fX+Af315eerq6+96d+Hren2AgIJ9fO1+fn96gHx67eZ6fXx7fXp3enyFhnZ6gu7o6nh6fHyAenh9gIDrh4KEhYKCfnyCgYSAfX+BgYOIkJiVgHx5gObngH+DgXuCeH2DjIZ6e32IhIOBfH+BgImFhIODg4aHh4yOlYaAfoiI8Ozp7unx7ut87ejuf4B8fOt7eYF9d3x8gIGAg4aDgIeKiI5+hIuCiIR4iIWMin2Fg4aGi4WGlZCHc32HhYN8eX2Ef37n0cXmi4F6eHuDhnaHhX+SjoqFiHR7fHh6lIuBeXd7hoGGkoyHhnp6g4qDfN7sfoB7gH58f3vq5IF9gH2HeX2CgH+BgeLb6nx+fnt4eXV7g3p5fIB/enp/gnp8gHl7d3uGgoSEgnp6ed/f4N3c09bh3dnleHd5i4qHf3Td4eDa2cvHzcS/wberqrfP2tnMvsfZ1t3bz9rj6trnd+TNvrmxqK+utbm/wcXIzdLW3OiCkZWWlYeDhIJ/fHru73p6fX+Bg4F7e3zoe317foGCg4N/g3t6gYeBhH95eXuAgIF/gICHjIV9foSCfIOFh4OBgH968HmDg4WGiI2GgIF7eXl5hICEhIKCg4B9gIOEgoB8hYaNlYyCgIWGiIyJhoyMhYSFiIiCgod4eYSC5tl6h46TjH7pfHTs7+/c5N3f7+XcyL/b7oGAgn1/enl6eHbc43Z87OLl1eXteXzt4d3mgOzt6u965uzu7319d3V1d3freOh3dtjgdHLX4+jX2+bh0XZ6dXXg7eTY4Od6fHfnduvg2NjP0septaihxL7MtbTGycrHzNqQjICIgYF9gYaHfXt4gIF1enx763bn5ebo5c3UzNrp5Onf3eva1tDC23Z15nV3eoWLe42nmpWOiIiIgIR/g4SCgX99gX+BgH168Xnz6envfPHl7vLr3uTl6+Xl7ezn5ubg1+V45uXx63rx7ufd2uHh4OHm74KGhYCDeHeIfX59gIyCfnh2eHt8fnx6f3uAfXqAgXp+fXx6fnx+gH56eHl8d+x8e3yAgIODgnuDgX19gYB/goOAfX6Dg4KCgHBub29ubG9ubmvTaGhoamxvamlob3FuZWxua2prZ21sbXBtbWtoaWdoaWdna3BucW9xamdsamlnb3Buam1wbmpqa29taW5vcWjNa29pzm5wcm9qcM1qbGppbHHFyNDT1MG+v2dnaGjNaWlnbGtqaWhra2loZ2NkzWVhu76/ubxhgMHGaWxwcG5rZmNgYWC6t2FnaGhqa2tpaWlsbGtpa2loaWdmxmhoZmPDZ8K4urrAuWBnaGllx2dobGhoZ2hnaWZpZWlmaGdnZmtrbGtvZ2dmbG1rbWpnaMBnZ2hpaGpnbGxnZ2hpaW5ua2hkZL/Fz9ZqacDFZWdqbnJsbNFta2tngGdmysZoa2prbGVgZWZtamBobcrOzGlqamxva2hqcG/Sd3NycXBxbGpubG9tbG1vbHBycXd2a2prcM3PcW1xb2xyamxqcm5qa2xzcW9vbW5ubHVwb29wb29wbG5vdm1qZ3Bxz8/M1M7SystszszRbm5sbdFraWxraWxsbG5tbm1pgG5wb3Fma2xma2dgamZra2RrZGlqbGZlb3BwYWhxcnBqaW91cXHOubLGdXNsZmhta11ub2l4dnFwcmRra2dmfHFramVmbWtxdHFvb2xobXFsaLjEaWtpaWllamnMyW1rb21yZ2ptbGlscdLV2G9wcW5qaGNocmppamxsamlra2VogGZnY2RtamloaWVnZ8THy8nKv8DEvsDGZF9gbm5sY1u0vLy5tqilrqafo5uUlqGxubeupKq5tbq7t7/Gy7rCY7ypn52UjpSRl56lqKusrbC0u8BmbG9zdGxmZWZnZmXGyGZlZ2dpa2pnaGrKamppa3Bubm9scGtqbnRub2xpa21vJ3BubGlxdW9rbHFvaW5wcW9vbm5s1GpxcnN0dXx4dXJtbGtrc25vboRrgGhuc3FvbGpwcnp/dnFwdXZya2RjaWxrbW1vb25vbWNncG3DwWlvcXRuZL5lYMPHzcPKxMPUzsOro7bKbmxtaWhlZ2ZjZLvCaW7Rxcy8y85maM7FwcfMy8vTa87V1NBsamRhZGZly2jJaGvC0G1pxMvJwMDDvLhsbmtt0dvZxcfLgGtvbNJrzsHAuLK5speln5aqoaySlaWlqKiqrGhfWmdiZ2JjZmdhY2JmaGFlZmfJaMm7tr2/qaifqbq+w7m6xrazq5qtY2TBYWFiaWtYYHFrbm9tbG9ta3BxbGtram5tcG9ratJr1c7KzmrUy9TY0srOy8/JycvKyMfHwrzMa8rKVdPQa9PQzMK8w8bCv8bKamxtbXBlZnRrbW1tc2xpZWRkZmhqaGltaW1tam5va3Bva2hraWlramlqbHFpzm1sam5vcXFvaW1raWpubWxub21rbHFycXGKfwF+vn8Ffn9/f36GfwF+hn+IfoR/AX6PfwN+f3+FfgN/fn6Lf4J+lH8BfoR/An5/hn6FfwF+oX8BfpR/hH4Ef39+fod/AX6Gf4J+jn+Dfop/AX6Zf4J+p3+IfgR/fn5+hH8BfrJ/hH6of4J+iH+Cfox/g36gf4t+iH+ffgF/k36Mf4J+in8Bfql/AX64f4J+hn8Dfn9/jn6KfwR+fn9/hn6Cf4h+AX+Efod/CX5/fn9/fn5/f4h+hH+GfgV/f39+f5Z+k38Cfn+UfgN/f36cfwJ+f4R+AX+TfgF/hH4Bf4t+rX8Bfpl/AgIEAICPj42LiYeLjoyMi4iGg4OIio+Oj4uKj4+Sj4WHiIqHiZGPi4uQlZmUlJCSk5KXkZWUlpOQkI2JhYeJioiJioiLi4yNiISFiomDgPyHiIaKgfTb8YKHiYaLjIaBgICC/ffn/oqPh4H6+/r0goeKhICHjIuIjIqMiYuSlI6Ni4uKioCIhoeHh4WGhYSMjYODh4qLlJiUlJGMhYGFh4eGhYWEgvn8+//5g4SEh4WGg4WJjYyGh4uLjYuJiI6Tko2Kh4eChIWEhIOBgIWKioiFiIqIhoqMi4uFiouJiIiRioiFiouD+vaCiIuI/fyDgYWFhYSIh4L8gYyQj4WChYiHh4aIi4CHgPaAhoKC/4SJhIuUk5KNh/z3+vWAhImLhYyRhIaDh5CPjYeIh4yNiIWOlYuPk52isaOJhvj8gIKDgImKiouMiouMjpSUg4iHjI2NjYuGiIiCiI6RioyPkZOUlamqopeRkYf79+ns7/qB/fyCgICFgv3z9oWFhoaGhIeKjYmJjoCTlZaXlZeSnqeknJWdn6Chm5ScmqWioKGgk4SMjpCSjIuKh4eFhYaFlZCLkZ6Oho+WgoGIn4+CjoiZnZmVo534haCQhZCKh4aHkJCLgIWFgYCA9v6Lg4aRkYyHgv33+4P4hoyOg4WIhYnf6oyEhIaEgoWGiImHg4WEh4uGgoiEhYCEhoeEh4uWnJybnpedmZWMjo2PjIiJhoWJhZGhnZucl5WRkJGKh4qGiYmMi4qKiIaGhYaKh4WHiYiJmqGflpugm6GloZylp6anpaWnqKeqs7a3tKqdk5CPjpOUjo+PjoyOi4SBhYyJh4aNjImJjo2Li4uMjo+MhoOEhIiLiYmOjYCIhoeIiI6XlY2Riv+CiY6NhoWF94WOkIuEgfz+/ICAgPjz+o+Tl5OTlIaLioyPjYqGh42KjZqUi4uSkpKZoqWhm5iR/4mQmZGFiYaEh42ToJaTj4aBhYSF/4D928zFzdLZ7/iChPyEk42Wj4iDiIqB+4eE/Pf77vj+7vmA/Pf394D89evy8/b19f+D9vOC/YH3gIKDh4aAhIH6gID0gYD/+/z6/oOGg4D49vSGiIWC//Pr9eDo7efVyMa1tszV1dfVn8Tj1eiJr8G7pqamjY6emZKL9e/v7vyK/4OD+/zt3eHYwJbC5u/3hv70497sj5+TioH3h52lqLK4wL++p5uYmYCVmZOSj5CQjIWNjY2GgYODhoWFgoKF+O328fby9/378+vj4d/c4Pb/+ff56urzgP2B/PPs7N3m5+r29o6fmZKRm5OZnJSVnJmXkYmEi4uRkIuJhoSLjIuLg/6HjIT+h5CHgouNjIaFg4OHh4uNjo2LhYqFhoWEhIWNjIuKjYuMj36Dg4KBf32Ag4KBf317eHd9foF+fnt7goKEgXh5eXx6fYOBfn2ChoqEhICChISIgoWFiISBgoB7d3h7e3t9fnx/f3+AfXp7f352c+N5fHyAd+DH4Hl9f3yCg355eHh36ubW63+Efnrw7erle359end8fn16fXt9en2Bgn19e32GfBd7dXRzdX1+eHl8fX2Cg4GCgH15dnp7eoV5gObr6uzidXh5fHp7d3p+gX95en18fn19e4CDgn9/fHt4enp4d3h3eH2AgH56fH16en6Bfn96fn5+e3yEf3p4f4B55+R4fX554+J2dnl6fHp/fXrrdn+BgXp4e3x7fX1/gH146Xh8d3jtfYJ7f4aFhIF76eTo53h7f4F7gIZ6fXl+gISEgXt+fYCCfXuCiX2BgomJmJJ/gO7ueXt+eH+Af3+CgIGAgYeHeH99gYKBgIF9f4B4fYKFgYGCg4WFhJOVj4iFhYDv7+Xo6/B78fR+enl+fPLp6n58fXx8e35/gn5+gIWJiYqIhoGJkJGKgYiKi4yMg4eGj4uIiIyEdn2ChIaCgIKBfn99e3t5hX+AhZGCeoCHeXiBk4J4hX2LjYmGkovVeJSDeYmCf399hoaCdnt7eXl56/KEfH2IiH98evDp63vnfoKCenyAfYLP1YB5eXx7eXt8fX59e359gIJ+fIJ+fXt8fnl6fIOGhYKGg4qJhX2Af4F9fH16eXt1foqFhIeEgISEg4F8eXt4ent/f359fHh3eHl9enh6ent7iI2KgoeKhoyRj4yTlJWWlJGPjo2QlpiXkoyGgH59foOCfH6AgH+CgXx5e4F9e3qBgX5+hIJ/gYKDg4SDfnx+fYCBf4CDg398fX9/g4mIgoaA8nt/goJ9fn7se4KGg3178/r+f3t5f+ji5YKEiISEiH1+fHyAfnt7fIOCg46JgYKGhYSHjJCOioaC6HuAh4J5fnx6fYGDi4SCf3dzeHt86Hbw3dDI0djZ5+16e+N3iH+Fgn14fX966H185uHm3uvx4ux67uro6e7r4eXl6+fo9H3o5Hbicd10cnV3dXF2duZ1duV3duqE5YB2end26OXieXt5d+vi4OrW2+HczsC9rKvBx8PBwI+21MLVepeknIqMi3t8iYaCfNvZ3Njjfel6eeXn29DZza6Fs9TV2njq4NDGz3yKgHly3nmKjo2SmKCgoY+EgoOBhoOBfoCCgHuBgYF6eH19fnx6eHl+6+Hu6+vl6fHw6OLa2l3Vz9ft9uzo69za5Hvye/Hp5ura393f6ON/jIaBgIuEiouFhouJiIN9eoCAg4J+fHp5f39+f3fqfIB553yDe3mBgoF7e3h2ent/gIGBgHl/fH19enp7gYCAfoGAgIKAcXJwb2xrb3Nycm9ramhmamtta2tqbHBvbmtjZmdpZmhsa2lmam9yampqbXBwcm9vbHBsamtoZWNkaGpqa21ra2xsbmtlZmxtZmXDaGlqbWXFtcZsbm9vcnJwbGxqZMnIuchrbmtp0tPMxmptamdlaGpqZ2loaWlscHBtbGpqaWmAamtqaGZkZGRiZWNdX2NmaG1ubXBva2ZlaGloZ2hoZmfHycbIv19jYmdnZmNkZmZnZGZra2xqbGhqa2xpaGdmZmdlZGRlZWZqbG1rZ2loZ2lrb2tqZmloaWdma2djYWlpZcXDZmhnZMbFY2FjY2VnbGtpxWBlamtramptbGxra22Aa2fMaGpoZ9BtcmtqbWpubGjJxcfGZmlpb2pscmpua21wcm5pbWxubWtsbnNqa2pubXh3bHHU1W5sbWhtb29ub29wcG5wbmdva25vbW1ubG5uZmttb2xrbm9wbmlxcnJwcHBuztHNz9PVa9HTbGlna2rQys5tamtpa2tsbm9paWiAa3BycXFuaW1ycGtia21sbG5naGltaGVlb21jam5xc3BycnFycm1pZm5oam96cGtucWdnbHpua3NqcHFwb3Zvt2d8b2V4cW5vbnFxb2lwa2pra87Rc2xsc3BramvX0slqzG1xcGtub2x0y8dtaWpsamlqampramlsamxxbGltaWosaGlqZmVkaWxqZGZla2tmYmJjZmVnZ2ZlZV5iaWdmaGZoam1rZ2ZpY2NkZmWEZIBnZ2hraWdoaWdkbG5rY2RnZmpta2pxdHNycG5tbGhobGxraGRhX19gYmZoZGZoaWpsbGlnaWxpaWdra2tscXBubm5xdHRzbm9wbm5ua2xvb21sbW9ubnNyb3FszmlrbG1qa2rHam1ycm1u2t7fcG5v1svIbnByb25wZ2llaG1raA1qbHBvb3Zyb3FzcXFuhG2AcHDRaWlta2RpamhpbW93b2pnYV9kaGrTbtrKvbK5v7/Kz2toxmRqZnJuaGVub2fKcG7IwMjE09bGzGnS0cvKy8zFzMzV1NPXbc3DZM9nxWViZ2xnZWxoxWVoxWVlysTKzcVobm1pzdDNbW1ra9PNzM29xMzLuKipnZyqp6Kgn3aAlaqdql1tc3FmZmhfYGtoZmS4tbe2u2XBZWTCxritta+PZounpaddtK+il5xbYl9eWrFhbG5sa21vbnFwbGpqa3BubWpqbGtpcHBvamdsbG1sa2pscdHI0tHSzM7V1tDJwLy0rbTM2NPO0MDAyGrRatLOysy9vb7Cxr1odHBqa3E/bHJybW9ybm1uamZubm9ua2lpanJxbWxnzGtuashobmhob3BvamxraGhna21vbWtna2lsbWlqa3Bubm1wcHFxyn8BfoV/g36Lf4R+hH+EfrZ/hX68f4J+hH+Cfol/AX6PfwF+hH8Bfol/hH6gf4J+rH+GfgN/fn6Ff4N+y38BfpJ/gn6IfwV+fn5/foh/gn7/f6F/AX6HfwF+hn8Jfn5+f39/fn5+oH8BfpR/An5/iX4Df39+in8Dfn9/iH4Bf41+B39+fn9+f36IfwZ+f39+f3+FfoR/g36Ef5d+jX+FfgR/fn9/jH4Bf4V+hX8BfqN/mH4Df35/in6efwV+f39/fqJ/AgIEAICNjo+Mi4eHhYiNjo6PjYqFgYOFhIKHjpGOjoqKio+Jh42Mh4uMi4yVkpaXlZOTjIiNh4+RjYmEgoCGhIOGiouNhYSKhIKBgvv26e/u9P3/goX7hoaCh4WHgYCEg/WI//uKjY6GiPz6gP3+gfWChoKGh4qJh42QkZKSk46LjoyIh4CFgYSHi4mIhoySj4WCgYSSmI6HioSDhYeLjoyHhYKEg4SGhomNi4mEjIuJhoaBgPmAi5ebm5eLiICGh4OChISB/oWA+4OEgoOEhYKJjo6PjomLiYyMiomHjYKAgoGGhoKCgoaHg/2AgYaIiIuMiI2Kh4WBgoOD/oWBhIWEg4iF/oCAgIWDgYOFhYCHkY2Eh4OQhoKE+/z4hoePj4qBgoH9iI2NjoyFh4uIgIKQk4+XmKKpoo36+fTs74CDg4iMjIeGhoyNkY6Uh4KIiIuOj5OOiYuJg4eLkJWNkZSZl5uTkaKnlIH39vTs8fT7goH+gP/zgv/++IGAgv6BgYD9houNjICOjZKMkpeenJqenJKWnqqsnZiToaKoraidl5KNl4+OiomKh4WHjYeLhoiMl5iSkJuUjZKKhZCOiJGWjZKRipeUkZ+bmpOSkouFhpCKjo+Agf2DiIX75ff8hoSEipORjoyFhoaIiYSHjImJj4OEg/b6hYqGgoSEgYWKhIKEhYSHh4CJhYyJhoeKh4mLkZCXm5OQmJOMgYWJiIWKhomHiYeChpCWmZuZmp6inJWRkI6NjZKWkpGSjYaCgYKGgYCIkIyIgoH37oWWoKCnrq2yt7e1sKWYkIaCh5CNh4iFgomLhoqMi4uMiYSGgvuEhYWEiImKiIqMiIyQi4T9hpGTlpWTi4CKjIiE/P6BhZKampWUmpWMj4aC/oKAgIqCg4OGhoD/goH7hoyKiIKCho6Sl5WRj4+PhIOIlpmLjo2LkZSgpaaknZeUmZqUkYyJg/yEgoGEhIL9g/qBgPv98Obb5+fz6uTe2eWFgvqCgfuDhIqE+/6A/oWIgvvwg4OA9f/7+/j27oDr/fL68fmBgOuG/4WD+/yFhIGCgoWGhYaD9oOEhIDz7/yCgvfm6ICOgPuDgf+C+O3y6e3l3NfSy9zRw7e0xOTd1tjo+YGUprixopaekIyLkZOQiPjo+O+DgP7469nezcvCmOWp5OTfgImTgYCTjJWWk5KxwKyhsr27trzApqGanIChnI+LioyNiIGMi4WJhYuKh4WGgIOEg///goGGhYWGgO/k1tTR2v6Fgfb5+vX6+/qB+vP19u/07uzs7IaenpeNkY6MlJaUkZKPh4aGhYeIh4SC/4CKjYqNj4WAh4SJkJOSj4qHhIONiYiIhIWLi4mKiYiHhomJiIiJiouQkpCOjoCBgoOAgHx8en2Cg4SEgX55d3l7eHd7gIKAgH1+f4J9fIF9dnh6en6HhIaGhIWGgHyBfISEgn94dnN5eHl9gH+CfHl+enl3eero2+Dg5fDze3vre3t4fHt+eXl9eeB/8Op9gYJ5furnd+joeOR4fHl7e35+e4GCgYB/gX18f316eoB5d3p7fn17eX6EgXh1c3R/g3t4fHp5ent+f397eXZ3d3l7ent/f355f359enp0dN5yeYSIiIV7fHR5enl4eHl363p253p8enx8fnp/goKDg3+AfH6Afn16gXl3eHd6e3d1d3x9e+13eHl8fH+AfIB+fHt4eHZ36Hl4end4eH5764B4eH16eXt8fHd7hIF7fniDe3l75urre32GhYB4eHfnfIGBgn95fYF8dXiEhoGHhYqOiXzq7ujh3Xh6e4CBgX58fYKAhIKIe3d/fn9/gYSAfoOAen6BhYqChIWJiIyCgI6ViHrt7e7o7urwennze/fsffP28Xx7fO54eXnsfYGBgYCDg4SAhYeLiYaJioGCiZCRiIiCjY2Pk4+Ih4R/ioSCfn2Afn+Chn+Ae3t/iIiFgImFf4aAeIODgIaIfoOEfouIgo2HiIaEhoJ7eIN+gYN3fPB9gH3r2OjqfHt7f4eHg4B6fn6Agnt+g39/hHt7eeXpfH99en19e3+EfXp8fHp/f2l+e4F/fX5/ent9f32ChX98h4N8dHh8e3d8eXt5e3lzd3+Bg4SCgoOFg4CBgoGBgoeKh4aFgHt4d3Z4c3F4fnt3cnDW0HaGjoyPlZSXnJyZkYiAfXh2eoOAenx6d3x/fICCgYGBfnl6d+mEez9/gH9+goSBhIeDfe59h4mLioeAf4F+fOzueXyGi4mEhImHgYWBfvR7eXh+ent8f39683x66nyCgX95eX2BgoWEh4CGfX2AiYt/gYF/g4SMjo+MiIWDh4mGg4B+eup6eXh6enjmdd90dOPo4t3Z4t/n3NjX0tp8dud7dt93e4B66/J67n2BfOrfe3145/Hu7evr4t3x5+3l63h33n7wfXvm4nd3dHR1d3h3e3rieHt5dNzW4Xh54szOeY197Ht25HXe1YDe2+DZ08/KxdbLurCotdLMw8XV4nWCjZqViIOIfXt5foKCfebZ6t95du3o3MzPwcK3iseb0srAb3V/b218d399fX2TnIuGlp+dmp6hjYuFhomHfnp5enp4c4CAe397gH96eHp4fH588PJ7en9/gIB65dzOyMHI8YB65+/w6+/v61B46OPn6OLn4+Db2neNj4l/goCAhIWEg4WCfHt7e31+fHl46XZ+gH+BhHp3enV6f4OCgH9+e3qBfHt6eHp/gH58fHx9e3+Af3+AgICEh4WCgoBwb29tbGlmZWlvcXJzbmplZGZpaWlsbm5oZmZpbG9raWxpY2RmZ2pvbWxsam1vbGdqaXBwbmxmZWFnZmhubmxwamltamloaM7KwcG/xc/UamjLa2Zma2luamxuaMBuz8prbXBma8zIaMvFactnaWhpZ2lmZWtrbG1sb2xpamloaB9nZWhnaWhnZGhtamJgX2FqbmdjZmZmaGlqa2hoZmNkhGZfZWdoaWRoaGdkZF9ftFxfa25wb2hpZGhoZWZmZ2fMaGTGZmhobGxsaGttbGttbGtoaGpra2dsZWVkY2RjYmNjZ2lozWhpaGloZ2RjamloZ2ZlYmXIaGhpZ2dmaWjJZWaEaYBsamNkaGlpa2VtaGlrwsfMZ2VxcG1qamfKam1vb2pma21nZGVvbmpraW1ubmfI1dHN0G1paW1sbGpra3Bsb25wZmVubmttbm9ta3FuaGtsb3Fsb29xbXFsZ29zcGvS0tTO1dTabWrVbNvVcNLS0GxsbM9lZmXFaWxsaW1sbmpvboBxcGprbWZobm9vbG5qcG9ub29rbW9rd3NybGxxb3J1dmxqZWZobHFvam5saG1ua3V0b3V2bHBwa3Nva29ucnNzcnBoZ29rbW5ma9JrbWrJus7PbGpsbW1vcW5pa2xwdWxscG5rcGtta8zLamtramxuamprZ2dqaWdta2hmbG9tbYBsaGlpaGZramBcZGNfXGFkY19hX2JjZWJeYmZnZ2dlYV5gYGFiYmJjY2pwbm1ubGhlZGJiXVpdX1tYWFuzrF5na2psb25sbG5uZ19aW1teZGpmYmVmZGdpZmlqaWlramZmZ8trbGxrbm5ubW5vbnF0cW3QbHN0dnh2cG9xbm7V2IBtbXFzcm5vcnFucW1s1GxpZ21scG5xcm/gcW7Tb3JvbmdkaG1sbHBzdHJxaGdqcXJoa2xqbm1xdHNxcXBtbXBubG1rZ8tqampoaGjFYbZgY8PKzcfBysfIwsLGwL9pZMdlY79oanJuzM9rzWtybM3HbG1nytXS1NjXysnez83N0oBoZ8Jr0WtrzcVnZWJoamtra2xqymZjZWPAxtRqaM29uW2Gd9hvaMxoyMXFwcG+vL28ucy/raianbmslpuss1heaHRxaWFnY2BgYmdnZcC0v7dkYsbBvre2qquWa5F8qaOYVVRUSk5aU1VWVVdsc2Rjb3V1cXN2a2xrb3VvZ2ZlZnpkZGJub25vaW5uamhqaW9wbdXTa2dsbW9wbMm8raefp8tsZ8bQ0snKxsBkx8bN0crMxcO7sltrcG1namprbGttcHFvbW5samtsa2loy2ZsbWxtcGlnZ2FmbXBuampsa2luaWhoZmtxcGtoaGlramxtbW5xcnBzdnVycsd/iH4Df39+in8Efn9+foV/B35+f35+f37DfwF+kH8Efn9/fqF/AX6QfwF+iH8BfpN/g36IfwF+lH+Ffqt/h34Sf39+f35+f35+fn9/f35/f39+1H8Efn9/f4R+ln+CftJ/gn6kfwF+j38Bfot/gn6NfwF+in8Efn9/fqh/AX6GfwV+f35/f41+Bn9/fn9/foR/DH5+f35/f39+fn9/f41+CX9/fn9+f39+fop/AX6EfxB+fn5/f35+fn9/f35/f35/ln6Pf4R+gn+JfgF9hH6wf4J+h3+HfoJ/h34Bf4p+l38Bfq1/AgIEAICTkJGTlJSRjI6NjpKRjIyMi4qSkYyMiYuKiY+QjIyNkpSXkYyMjY6TmJSVk5CNjouPj46LjZKUkIqHhoiMiYuJioqKi4uA+P387f74/Ovy7vfv6oiDgu2DioyJiYOGgoqGhIH4goOFioqQi42Rj4yGiY2Lio6TkoyHhYyKhoWEiICLiYaEh4SHio2Sk46NjZGSiYSBgID//PuBgf+AhIaEg4eIi4uLjIiLiYyMh4KDhIGEi5CMl5OSioeHiIiEg4OCgoSFgYGAhoSDh4SDiI2Oi4uOjI2JiIiOkI2MjJGQjIWAhoWD/IKEiIqGiISDhIKA/IGEgoD+ioSDiYiB/PSAhICFgoiDg4aAh4yNkZGE+YOCgoOHhoGFiImGhYWCiIiJi4+MhoSHio6MiYiTmZaalKKbkoeA+/327PWCh4mIhoaJhYiKkI6OkISFhYiOk5KTkIqPi4qJh4yPi5CNlZuflY+Uk4fv6uTp7PHv+/X47vj79/n4+fr6+/aBhoSDgoWDh4CFiZGXjYyRpaGon5mcnY2en6GkpKeztJ2dp5iPkI+MiIuIiIqOjIyJjomMioqSjo6JkZiYl5aM/vqNi42Sk5KFkIn9jJ2Zk6CalJSBg46SioaGgff39PH6+4OAgImM/YaNgPuEjYuHiI+QiIeFioqKh4D9g4KGiYWDhIKFh4eFioCKi4iMiY2Ni4eJkIqJi46Li4eIh4KGiIqGhoqHgIKKiYiGgPn6gYmHhYiKhoCBgPn8hoiGiI6Tk5CVkIeRj4KEjJGI/YCHjI+RlJSSkZOQjYuMjo2J/e/8hYaEgv+BhYeDiIX6gIiKhoT9/IOFhoqLgIOJkZGSkI+Qko2Hg4aGiICKhYiLjpaVkouOkJKPkI6JiYiIiob+6OTr8PDy8OLu/oaGh4aDhoiDiY2MjpaMjIyJhoeTi42Li4aD/+2AhomNjo2EgYSEgoCKkP6EhICEgfnxi5WJ+P6A+eHcycrfgfiBhYaM+4H9kYuHg4SJgPn2+YaD+fqD+vKA9fmAgfn49oDw9ff8+fX7+vmBgPaFg4eGhP/4gPqGiYOG/PqDjYv37vf3+oDv8Ph/jY78gYLw5eLl8PLv5NXn29TRycazq7W4uN6qq7+Rpp+SoaGSjI6Hi52Oi4rv+/f58/j17N7YzsSvs93l4+7f1OaCko6WkYWUssG5q6y9wLilqrSslpCXlYCUj5GUlI+TjYuIg/aAgoGCiIuJhISA6u38/oKFhYWI/+zv6uDb5/36//317fCA+ef1gfT6gfjx9+3o5OuMoZOPlKGYmqCfnJSOkZGUkY+Vh4CHiYqFhomQkIuMiYqMjYyLkJCNjYuNj4qHiIWChoyPjo2NjIiIiYiIjZCPj5OVloCDgYSIiYiEf4GAgoaDfnx7fHyEhIB/fH59fYKBfXx7f4GEfnh8f4CEiYWGhYOCg3+BgIB+gIOGgX18fH+CgIKAf359f3515u7t2+/r79/p5urj34F6d9l5gYF9f3t/e4J9eXbjeHl6fn+EfYCEhIN9foJ/fYCFhYB6eH99enp5fIB9e3h3e3h7fYCEhH58fYCBe3h3d3ft6ed3d+p1eHt6en5/f399f31/fH9/e3Z4eXV4fH99hoODfXp7e3x4eXp5eXt7eHh2e3p5fXp3e4GCfn+CgYF9fX2CgoB/f4KBgHt2fHx663h7fn56e3d3eXd15HZ5eHfsfnl4fHx25+F3eoB6eH14eXx4fH5+g4V55Xh4d3h8e3d9f4F+e3x3fHx/foOBfHt9fYJ/e3mEiYaIgYyIgXp36/Du3uZ5foB/fHx+en6AhIKChXt8fH6ChYSFhH+EgX+Af4GEf4J+ho2Ng36Fh3/m5d/j5enn8urt4uzw7fDw8fTy8u98f3x6eXt5fYB6fIKIf31/j4uPiYWHh3mJi46PjY6Ul4eHj4J8gIKBfH99foCFgYOBhH+DgoOHhIJ9h4yNi4d/5uCAf4OGiIR7iX7nfpCMhpGLhIV0eYKDfXx9eOfo5ubx8n94doCD6HyDeu18gX99foaHfXx7fn9+e3brenh8gH19fnx/f4B8f4B9fXuAfYCBf3t9g39+f4N/f3t5eHR4en18fH57dnh/f3x5deLjdXt6eXt+e3d4eOnpent6fICDg4GGgnh/gHV4gIN643N6f4OEh4eEgoOAfHp6fn985tjleXp7eu13ent3fXzpdnx+e3np6np7fH+AeHp/hoiIh4aGiIN9eHp7foCBfX+AgYiJhoGFh4WBgoKBg4OCgn7w3Nzl6urr6t3j7H19fn16e3l0fIGAgYiAgYF+fX2Hf4GBgn578OR5gIGCgoF5dnp8eneBh+h6e3d6d+jefYN85eh259PTw8HPeOd3eHh95XblgHx6eHl+eu/p6Xl14+R46ON65ON1eOrp6YDl6Ovv7Ovw7O57eul9eHl4eOvod+d7fnl75eB4f37l2ebq8Xzl4vCCi4fveXXW1dLR4OPf2Mzaz8jFvr+spKisqseTk6V+kIZ+jot8e311eIh/fn3d6Onx5OXl5NjQxrmfnsvX0tvSy9Vyf3l+enB6j52aj46bnZmMkJiTg3yBgICAfYCFhX+Bfn14deJ3enh6fX99ent54uHs7nl9fX5/79/k4NjV2Obk7O7p4eN67djneePpeOjj7OPc19d9joSChZCIiY+OjIN8f3+CgYGJfXV6e3t3eX2EhIGAfn+AgYGAg4OBgH+Agn18fnx6fYGDgYCBgX5+fnx7f4OCgYSGhxZvb3J1dXNuamxtcHJwbGppaGdtbmxshGmAbm1nZmVlY2hoZmhqa21wbW5ubW1saWtramptcnVwamlpbnFubm5sa2traWTK0Mm/0MzRwtDIycvMcm5ju2lwb2tubW9rcW5qZ8hpamdqam5maG1vcGhpbm5sbW5vbGZkamhmZmVoa2poZGZkZ2hqa2pmZmdpamZkZmhozsnGZWYezGVmZ2hoamtsa2hqamlmZ2RhX2JmYmBhY2Fqa25shGuAaWpramlpamhqaGxsamxpZGVpamhqbm1taWhpbm5sa2pqa2lpZWpqaMtoZ2lsaWpiYGNmZMZlZ2Vly2poaGppZcrGZ2ppZmppaWpoa2hnbG9oxWloZWdqaGZrbWxpZGdnamlra21ta2pta21qZ2VudG5saXFuamdm0NTUx8tpa2yAbGlqamlrbG5ubW9pa2xtbnFwb25qcG1oaWtsbWttaW9zc2pqcG5pw8vKzNDU0djR0cfO0dHV09LS0dHOaWpoZ2hpaGppaGtuZ2Vkbmtta2trbWVtb3JvbGtyc2tqcGpoam5vaGxsbG1xb3Fvc21zc3J0cm5ncXV0cm9qy8Zvb3SAc3dwaHVtxmZ3cm52c3JzYWlxbWtra2fLyMbL1dVtZWJvcctsb2rQbm5sZ2pzcGdpa25vbmxny2hmaWxqamxrbG1uamxqamtva2xramhobmpnaGtoZ2NkZWFiZGZlZWhmYmVtb21qZMLDZWppZ2hraWZnZ8jGZ2loaGlsbmpsaWE4ZmdgYmZoYrheZGltbnBwbWtqZmJfXmRmY7mzxGdoaGbHYmNkY2lqy2ZqbGpp0NRubWptb2pqbXOEdIB2eHVvaWprbm9qa2trb3Bwbm9vbmttb25xcW5sas3Fys/S0tTUyc3PbW5vbWpqZWRsbWdma2hpaWhoaXBpamxua2bLx2dqbGtra2dkaW1oZWtww2ZqZ2hn0MRnaWfEx2XGu8G3sbpqy2loZWfAZchvaGRmaGxmwcPMaWbEzG3NyoBqyclpa9TU0dHVzs7S0czN2G5syGtjYGZq0NJpyWtuZmvTzWxxcM7T0svcdNjV3X6Mg99vbMfDv7rDyMO+ssXAvrqxtKCZlpOGlmZoc1xraF9nZ2FgYl9jamNmasPRzM7BwMDBvrOoln5/rLu3wLmys1tbVFhVTFBeam1oaHN2c4Bsb3Nxamhra25sbG5uaGpqaWRgwWlpZmdqbGppa2i/wMfNa2xtbW7MvMPCurOyvsHMzcrCw2jMuMNmvsVp0M3Tx724sWRxbGpsc21tcHBvbGltbW1sbXZuZ2tqaGVnbHBvbnBvbm1vb25vbWttbW5uamlsamhsbm5sbG1sZ2lrawhrb3FxcHJycsZ/jX4Ef39/fox/AX6xfwZ+fn5/f37JfwF+i38BfoR/AX6Gf4J+j38BfqZ/hX6qf5V+v3+Cfol/AX6Qf4Z+hX8Ffn9/f36PfwF+sH+Cfop/gn6SfwF+kX+DfoR/AX6GfwF+hX+Cfqp/i36af4J+jn8BfoV/CH5+f39/fn5/hn4Cf36EfwN+f36Hfw9+fn5/f35+f35+f35+f3+MfgN/f36FfwR+fn9+hH8Ffn5/f3+Ffgp/fn5+f39/fn9/mH6Pf5V+on8Bfop/hH6Ff45+CH9+fn5/fn5/h37EfwICBACAkI6PkY+RkY+Ojo2KiImHg4WGhIWDhIeLjI2QkZGQioeHj5KNiIuMj5CPj5CTkI+NiIiKiYuNkJCPjo2LiYiEgYSLiISM/vj59oGAiIiPi4qFgYaKh/+EjYWGhYWEiY+Ni4eJiIuFh4iPlZKVif+Ci5GRjIuMjYKEhIWIiImNjpGAlZKRj5GUlpGIi5CTlpCNiIWDhIOEhIKBgYGCgoSFhYqH/4OFhIeJjo6IgPrz6+7o1uLq+fyBiIqF/4D28vTw9Pb69vXx9ISF/vv9/PKBiY2Mh4uRjo2JhYqJjY2Ih/aBh4WBgoD+hYaJh/+GiYWHgICHh4uMiIWOjYaGgfuCh4OAhYWLiYuJh4yTlpCEg4OCgYWMiYaGiIaGiYSBhImHhYyNjI6EhIaOiYSEkJaWm5yelJKMgfT+/fb+goeLjIuFiYmKipKWjo2GgIOIiI2Uk5OLj5KNjIqMjI2QjoyWoKSRgIGD9uHj3+Hr8+zz//f7gYGC/oCAgIH1goiDgoKEgICAhIKLlZWPkZCYo6y2tqqboqakp6GpoqCinZqcnZ6alpCPlJCVj4uNi4iBgYOL/4WMhoqQkI6PlpCXlJKTmpWMjY2Qi46OkI+dmI+XoKGoppeWi4Pw5evm8Oz3hIOHi4nz+ICIhoeGg/KCgoKFh4eJioyKhYKIiYaIiIuJiIaCgYkah4eJhoKCi42GhY2Oi4SLjIiEgYGFh4mMhoOEgYD8/YCDiI+MgvuA+vDv+oOGhIODhoD39oCIhoKJiYWNlI6Nj4qLhYaEgoaGhoiIiIuNk5KOiYL7gYqB8PeC8+Lh7/2Bgfr5+P6Gh4iJjYmDgoSChYmGh4WAgoeIhYaC+oKHhoqNjoyKiIWGiIqPk5WXlY+OjYiGh4ODhIKBgIKEi4CLioyKiYyKiYWFiJSNiIyLiIqNkJKYloyUj4GAgoSIh/6KkYyKiIqLjIuFhIOIgPby+ICJhP/9+f/9+fOB+vb4gPru8eL1gf6JhvyBh4SEifyEgP6C/oiGgIOD9feA/fH1/fz5+/mA9Ovr7oGD/4iI9oGLgvuCg4WHh4j/hoXy6IDv6/uB/4T+/vr0+ff97/709fL49PPx6uDezMnNyszAtrioguPLpvqQlZSakJ6dlIuIiIeMiYeLhvr07v377OHIp7rN39jR09Xe3djEtLTZ6dmFrsXO2M3IvranpKmsnZWPi4yBgoeCgYaEhYiLh4yGgoeGgPnV3fmEhIaEgYT6+mLy8Orn6Ofm29rxjYiEgPL//fDy9fyDhvnu6eHo7PWOk4+QiZGTl5aTlpCOi4uSiYKMj4SDh4WDiImQj42Oj4+I/4GCh4iHiIyOjo+JiYqJiYuNj5KPioWEhYmKjpKSlpeUk4CBgIKDgYKCgYKEhIF9fXx5ent5enl6fH5/gYSGhoV+e3yCg316f4CBgoGBgoWDgYB7e31+f4CDg4KBgoB+fnp3eIB9eYDn5ObmeHV9fISCgnx3fH188n2Fe35+fXyBh4WAfYJ+hHx+foOJhYd97neBiIaAf3+CeXp4eXx9foB/gICEgoB+gISGgnp9gYSHgH58enp8enp5d3h4eXh4enx9gH3sent5fH6Bgn536eTb39jF0NTi5HV9gHzud+jn7Obp6+3n5d/geHno5OTj3HV8fn96foF+f3x4fnx/fnt74nV8end4deh5enx65Xl8e3t1dnx8fn97eIGAenp253d8eQx6eH1+gn98gIeJg3aEd4B5gn9+fX58fH56dXh7fHp/gICDe3x8gXt3d4GFhYiJjIWDgXnm7/Lr7nl8gIGBe35+gIGEhoKDfnl6fn2ChoWHgISHgYCBgYGAgoB9h46QgnZ4eufW29bW4efh5e3m63p7ffJ6e3l6636EfHl4eXV3enZ9hYWBgYGGi5CXmJCCh4CLj5SOlI+KioeEhYaHhYWDhIiEiIOAg4J/eHp9g/B9gHyBh4eDgoqGjIeFhYuJf4KDhICIhIWCjo2CiJCPlJGJin964NfZ2OTc6Xl4fX9+5ex4fnt+fXjge3p6fX5+f35/fnl2foB/f36BgoF/eXh/fXt9e3h1fH54dn6Dgnp8fTl7eHV1eXp8gHx4dHV2eOvrdnl8gX514nTj2trleX16d3Z6d+fjdnx5dXt9eICGgH6AfHx3eXh3e3yEfYCAgIWFgXx25nZ9ddrieN7NzdnidHXn5+ftfX5/f4KAfHt9fH+CgIB+eXyAf3t7d+R3fHuBg4SDgX98fH6AhYqMjYqDhIN/foB8fX18e3t9foOEgoKBf4F+fnx8fIeAfIGAfH2BgYKFg32EgXZ1eHl+fe9+gX5+en19f4F9fX2BeIDq5+58hHzt7uvv7enmffPm5Xbq4eHQ23jrfXrndHl5eHvhdHXpd+t9fXd6euPmeO7f5fDu6u3ue+3k4+N5e+99e+J4gHjqeXt7foB/5Xh64dri3up38nvz7+rn6unv7fLh4t/p5uLe2tTVxcPBvcG3ra2WdcqukeWAgn+EfIeJg4B9enh4fHp4fXnm5+Hp6t7Vv5yuvs/PzM7L1NfTuqOgvsq5bYmbp7Coo5qVjYuQkoeBfHp+d3l9eHh5d3h8fnyBe3d8e3TgvsbmfHx+end66uvk5eTg39rYzcrdgn57eOHu7eTn6Ox5fu/o4tfc3+F/hIOFfYKFiYmHioSBf4GGfjN4gYR3dHh4eH5+hIOAf4CBfOp4e39/fn6AgYKDgIGDgoCBg4WHhH56ent+fYKGhoeFg4OAbW9yc29vb25vcXJwbWxpZWdpaWtpZ2dpa25xcnJvZ2NlbG9oZGhpa2xra2psamlpaGptbW1ub25tbG1ramxpaGhubWhswb/Gy2lka2pxb25tamtpatZvc2lsbnFxcnhya2lvbXVvcG1vdXBwbdFqcHZybGlqb2hmY2Rqa2xubWwbbmxsamxub21oamxsbmhpa2tsb2xpaGZmZ2ZnhGiAbGnGZmdkZWVmZmVgwb+9wLShqKizt15la2nOac/N0c/R0tLPzMXDZmfGw8W/tF9maGlpbG5qamlnbGpsa2hqxmhrZ2ZnZsVlZmprxF9mZ2llZGdpamtoZm1saGlozmttaWlnaWxua2lra21pY2VmaGhnbWpqaWtpaWpmZWhnZ2aAa2ttbWZoaWtlY2NscW9vcHNwcG9t0dPSz9Bpa29vbGhobHBvcHJtbmxqam1scXFtcWtucWtqa2xtbG5sZnB0dG9mZ2rOw8zFxs3Qzs/Ty85qa2vTaWtpa9Rvc21paWhnaWtnam5ubGtnZ2hqb3BsZGtxcHFubmloZmVkZmlta2yAa2x1c3Nvb3B0c2tubXLSbm1pcHV3c251c3Vzcm9ycmhsdHNweHNvbHd5cG1ydXp4cnJpacS8srfFvs1saWxub8rRam9sb25pwWdoaGxvcHJyc3JrZmtua2trbW5taWRjamtqbGplYWdrZ2RscnFpaWpmZGNkZmZobGplYmJjZ9Ap02tsbG5sZsZmycLCymlqZmJgY2K8uWJoZ2NmZWBkaGRjZWNkYGRlZGiEaoBsbm5xbmtqZcJhZV+0v2W4qKizv2Vnx8LAxWhpa21xcW1rbGtucW9wb2ttcHFtamXDZWpqbnByb2xsa21tb3Bzc3Vyb3BwbWxuamtsa2trbm5ycXBxb25wb21qZ2NqZWZqaWVmampqb29nbW1kY2ltbWzPaGZlZmRmZGhvbW5vcIBr0s7UbnNpytDR1NHOx2zZx8Nkyb/Hub1mx2hlwGRqa2dmu2Njy2XKa2tjZWfDzmvRxcvT1drY2HPa0tTPbGzLbGi+aHBr0mpoa3B0ccVoa8/M0cXUadhu1tTV09XT3NTXzs3Izs3Iw7y7vamrr66wo5uXf1+Mb22tYF9eZF1laIBmYGFiYGNjYmloycrEzcvBu6aCiJqxtrOztLq6tJuAc42XhE1eZ3F4dXVzcm5tbnBqZ2ZnaWZoa2djYl9eZGlobGVhaGhlupGgxGhmZ2ZlZ8bN0NnOwMLDv6mgs2xsaWO6zc/Dw8XGZmvOyMS+v77Ba25ra2Rrb3BtbnJubGppbjRpZnBxZGJnaGdrbHBwcG9vcGvDY2Zra2tqa2xtcG5vcG9tbGpsbm5rZmZobG1vcXBxcG1uxX+Efox/AX6XfwF+s38Bfol/in6EfwJ+f4t+gn+FfpF/AX6GfwF+hH8BfpF/AX63f4V+qn+MfgR/f39+hH8BfrV/AX6lf4d+hX+CfoZ/AX62f4J+hn8Cfn+Efod/gn6ffwd+f39/fn5/hX6Cf4R+ln8BfsF/AX6OfwZ+fn5/f3+HfgV/fn5+f4V+BX9+f39+hX8Gfn9/fn9+hX8Dfn5/iH4Bf4R+Cn9/fn9/fn9/f36GfwN+f3+FfgN/fn+dfgR9fX5+kX+ZfqN/hH6Gf4x+hH+HfoJ/h36ifwF+oX8CAgQAgJKPjI6Sk46Mj42MkJKNioSBhIWKiYSAgYCBhIaGhoSDh4mOiYWGgYWNkpSOj4yMioWMkIyPjIuMiI2PjI+PkI2Kh4SJkIuSh/uEjpGJiIb8goCHjYmDh4b2/v+JhYOLkIf7iImGhoSMj4+Li5CKjJKRk5qXhoOMjYWLioiMiYyMgImOjYqGjIqHhYaDhoSGhIaHh4aDhIWGi4uD/fj4/IOA//bx9IWGgfr5+O7w9/jz7/bw79zN0tjxgImIg4D5/4SKjIuQjoqGgYCJjYaDj4ySl5iYlJCFgIOKjY6Kg4CCh4eB+omFiYmMj4yFh4iHhomHg4WKi4OHgoSC/fyEhIeAgISGh4eFh4mLioqOhYWNhYKGh4aCh4aGioeHhomMi4eJiYaJioyKj4+PkJOSlKSooZSM+eLr+Pbz/ICChYeIh4eIio+QkI6RjoSFhIiHjZOSioaLjYyPk5WOi4mNkZ6glon69fHn593d2+Hw8/v57/fy+P7+gICBgYODhoeGiYqFgIOEgICHjZWZj4uYprC2saeooaKoqaGXlJebnZmWm6GfnJiUkY2DhIqJ/o2Yk5uYjYWAgImGgYSBkYiFhIqJjpKJiZKLgP2GkI6EkJaWkJyWm5qamaOnl4f3hoL+h4eChYL1gYCDhIuL/PyDhoSBhYaFhIaEhIWIjYmChoqKiomJSYqKjIyKiIyNjIqLi4qIh4qNkY+Kgf+HiYiMjYyGg4aFgfX7gIGGh4uXnZuZhfr+goWHh4uOjpCUlJORkZOUjYPv+Pvm9Pz/gIOEhYCChYeGjJWSkYuCgYGOpKqhloyPlIyHipCJ/fj5hoiFhoH89Pf3gYaOk5GDhIiIgISJhIeKioyNi4WLj46IiI2PjZCQi4uJhoiChYeMi4qJiIaIiIqIh4GAh42Ph4ualY+MjYyVlZOTj4iOioqJiYT7gIODj5ONiISEgoCBgoqM/ID5gIuEgoKDgP3++vj79YH+/ery9vn+/uyDiISEio6DhYL4gvzx94SA7/T7/v30+fn57vb5gIH49/fu6vn+9fXs9/zyg4P8+fP3goj2/ISHg/b05fb4gPX/+IP6/vTw8ffq8vHs8O/r5OHa09zZ2NnCvbusvLXD8oGC/52jkZqWk4CKi4yJhYWB9vfx39HR4uvIsZaf5ufW0tfO19rXvdvg3f+RjJOstcTCt6ejnJajsqGhnZymoJSPhufxjpiSj4WG7uaKj4iJ+eD/gISFh4L2gPz5+v3u6ejc0sTM/oiG/fzy7O/u84GIhYiE++rlgIiKkZCRlpydnJqamJWLiIaOjTOJjYWDgYWDgYiIiYSCgIGDg4SHhoeOko6Oh4WIiIaDh5COiYSEg4GIjo2Qk5GTkY6PkpOAgoB9f4KCfn2Afn6Bg4B/fHl7fYB9d3N2dnh7fX18eXZ4e4F/e3t2eH+DhICBfn9+e4KGgoSCgoN/goSBg4ODgn98eHyDgIV86XuDhH57fOx5d32CfXl9fOTv7YB7eIGHfuuBgX5+fIKDg4CBhYCBhoeHjIl6eYF/eH5+e358gH+Ae4B/fHl+fHp6eXZ5eXt5fH5+fnt8fHx/f3jn4uLpe3n07ObnfHx36ujm4OLl49zZ4dnYyLm9xdtyeXp6eOzueoCCf4KAfHhzc3p+eniCf4KEg4SAgHp2eH6AgH54dnh7e3fnfnp8fYCDgXt9fXx7fnx6e35+eH14ennt63t6fHaAent7fHp8fn9/fH94eIF7eX19fXl9fHx+e3p5en5/e3x7en5+f3yAgIGBhIOFjY6MhX/t2t/q6+jtdnh6fXx6e31/hIOCgYWEfX17f36Bh4eDgISFgoWHiYOAfoCDjY+If+7p4trZ0dPS1uLn7evh6eXq7+94eXl6fHx/f319f3uAeXx4dnyBhIZ/fISMkpeYkZKMjpWWjYOAhIeIhYSHiouMioeGhHl4fX7lgo2Gjo6DfXh4gH15e3mHf3x8gn2AhX9+iIF47nyGg3uGi4mAiYSIioqIkpWKfeN8eO1/fnp/e+d4eHt6fn7p6nt9e3l7fX59f359fn6Df3t9gIB/f35Ifn+BgYB9gIF/f4CBgHx5fYCEg4F57X5/foGDgXx5fHx54+d2d3t6e4WLi4197fF5e3x9gYOCgoSEg4KDhYZ/d9nl6dbl7fF5hXuAeHl6eHyDgX97dHR0f5CUjYZ9foN8d3l+e+ro6nx/fH157+fo53l9g4aGent+fXh7gHx9gYCCg4J9g4eEfn+Dg4OFhYKBf31/enx9goODgX5+f4CBf315eH+Af3d9joiAfX5/hYWEg394f319gIN97Hp7eYKFgHt4eXd1dnd9f+iA53Z/eXd5e3jq7u3p7up57ujZ4+bp7uvaeXt3eYGFfH576Hjt4uZ5dt/i6evs6PDu6tzj6nl46Onr5ODr7OTm3+ns5Hl66+nj6nqC5eN5gH7r49bo73nm7N934+rk4+Xp2+Ph3OPj3NXU0sfNy8rLtrWypLOotNlzdeiNjX2Fg4KAfn9/fHh4duTm5NTGwdHauKKFjNHYzs3Rx9PRybTMzMnkgHt8j5WgnZKHhYKBipeKjYmIkYyDf3nW44OKhIB1eNjVf4V+fdrH6Xd7fX986nnr6Ofq2dHSz8a1uul8e+vr5N/g3uF3fnt/fO3e1Xd+foSCg4iOjo6Mi4uJgoB+hIEzf4R8eHV5eXd8fH15dnN0eXp6fHt7goeEg358f4GAfoGIhoJ+fXx5f4SChIeFh4SAgIODgHBvbm9wcGxqa2tsb3FubGhmaW1ycGhkaGlqampoZ2NkZ2hsa2lqZWhucG5qa2hoaGdwdHJycHBvbG9wbHBwbm9uaWZpc3Fxbs9rcXFrZ2rLaWhsb25rbW7J0c5ta2huc2zPcW9vcGtvcXBtb3BrbnJwb3RxaWlrZmRtbGlqaGtrgGhqamhobGtpaGZiZmZoZmlsbW5ramlmaGplxcLCxmdkx7+8vWRlY8TExMDDysnAt7myr6SbnaCuXmhramjNz2txc3Bxb2plYWBjZWBeaGhsbW5wcG5oY2drbGxqaGhpaGZny21maGpucnBpamtpaGtnZmhoaWdsZmZmzs1oZ2pnOmlqZmdoamtqZ2lrZWVraGZpaWdlaWhpamZmZWVoaWdnaWloaWxpa2praWpranF0dG5sz8HHzczN0miEaoBmZ2ptcG5ra29uaW1sbWxucW9tamlnZmpucW9taGptcm9ra9DMw7/Dvr27v83R09HJ0M3Q0MxoaWlpbW1ubm5ra2dpbmhlZ2tub2ZhZWZlaW1tb2trcHNuaWVmaGllZWltb3FxcXJya2ltbsZxeW9ydnJuamt0bm1ua3Vta210a4Bna2trcm5q1253dW90eHhscWtrb25renxya8lqas5xcGxvbs1oaWloaWvLzWpubmtscHFycW5ramxva2ZnamlpaWpsbG1tbGtub25tbW9wbmhna3R3c2nJa2xqa2ppZ2htbWnFxmVobWxqb25ra2LByGVmZmZpamlpbW5tampra4BnZLq/xLbDzdBnZ2hoaWlmaGpoaWxpZ2NdXV9pdHVtZF9jaWNfYGZkwsjPcHFtbmzY0c/Ma25xc3RpaWtrZ2twbGtubm9wb2ptb29tb3N0c3R2dHJwbW5rbm5xcnNxb29wcXJvbGhoamxpX2NybmhoaGZrbGlqaWVqZ2pucW3Pa4BraWxrZ2VlZ2hnZGVsbsvLZm9sbmxrZsbS09HR1GzKxry/xMjOy8JoZ2Rkc3psbmzOac3LyWtpxsPIzM3O1dHKvsnQa23R09HU09LTzc/Iz9rRa2zPz83Qb3bHvGZvb9bMxNbRa8rKuWTBzsrLzdDDzMe/w8O+uL/ArrOxsbWelICblJ2MiqpaXLlkYlpkYmRiZGVlY2VkxcfDt7Kps7ifi3FyqbCoqa6otK+ulqqpnrBiXVxpbnBubGdpaGdrc21zcW9yb2pmYK2/a29tamJlt69oa2ZmtKG/YmVlZmbJaMfCxsu6sLS4r5iYumFhwMO7vMHAwWNnaG1qzcK8aG9tbkJramxwcnJvbnBxbWtpbmtqbmlnZWhnZmppamdnZWVmZWZoaGhvc29tamtvcG9sbnFtbG1ubGltcG5vcnFycG1tcHDIfwF+hn8Bfoh/g36GfwF+uH+EfoJ/hH6Df5F+hX+CfqN/AX6Xf4J+tn+Hfqh/k361fwF+m38BfpJ/BH5/f36FfwF+hn+Cfqt/AX6Lf4J+in+CfpF/h36ff4N+hX+Efsp/AX6Pf4J+h3+GfgF/iX6Jfwd+f35+fn9/jH6Cf41+gn+Efgd/f35+f39/hX4Ff35+fn+dfgN/f36Nf5p+l3+CfoZ/gn6Ef4N+hX8Cfn+MfoJ/h36Ff4N+xn8CAgQAgImOjI+Xk4+LiYyTko6NjYmMk5CUkoyIg4GEg4CDhImSlI+Ki42MjIuHiYqNlZGMh4aIio+Pi4mGh4mIjIuNh4eOjZKQjISHgYuNjIqC8/z/g4GCiIiEj4iGiISOiPiA+4P5h4mLi4+Tj4uSlI+OkIuCiob/hoiMh4yPlIeDioqLgIqFiIqJhoaKiYmMjIqJjIqB/v+ChYWFiIqJiIX//vz1+Pb+hYiFgIWE//n5+4GChID+/vXs5N3h4Oft9oGE+/n9/oSHgoCDg4uOiPX18++Fm56OhYSFgP/w64CC/Pb8hIWCipSD9/nx7fr7goWHhYGAhoWJiImGjYmChIaBgIL9gICChYuMjI2KioOFi4KEioeJi4mFgoWJhomMjYuLiIiKhYiOjoiLk5KTnJuWkJOZmY+J/eXu/ff2/vz9hIqNhoeIjJKSk5ialIyIhouKiYmTjoeFiY2WmJaPio6XnpuXkJGH+vLo39/k5u7v7PTz9O35+Pn/gP/7/P+Ch4CEiIiHgIKHgoGCiIOOlpKSlJuhrLChoqSkpJybm5ucl5mbn5eRkJCalImLiI2LiYuWjoqJiI2Ch4iLi4mRkIaJgf6MlZaZjZKaoJSRjoyAgYCCiYaEg4uTjIeJjICIjZWQiPf6hfmBhoiIg/ns7PL+94CCgoaHg4L394CGiIqNkZCNjIyLgIiIi4yPkpKOjYuLjo2NiIeHi46LhoSGgfqCio6G/Pb7/fb7hYWA+vf1+4CAh4qMj4uBgIOBgoWEh4uMkJWRjZaWl52dj4iEgoOEgYOIiYeFhoqUjIaG//2DhoH8hoyIhYeGh4mGg4yTk42HhoSGh4aFgf789v2AgIGChIiPjI+LgIeJh4aIgoSFhIKIiIqLjpKPio6MhoyOjoyKgvb6gIWFiY6OhoiJho6cmpaXmZ6ZkY2F6Nr3h4uOkIiH9vD99ICPi4SLiIiEhIWFgoGFgIT7/u/z9fz6hoKA+YGK/PqHgvTx+oOHjIOGh4P/hPXv/4aDg4SA+fSDiILs74H859TkgPmB8vL3gfL5/OyBgYCA7vaEhv7/+/Tz8oDy84WD/4H/goL/j4P2/PWC/O7r8tre7Pfl6+/u6+LT3dHV2tPEsLe4wcDHyNb8/6CXgOqQhYiDgvnp8unu7OvT2trQwafezMHX1+Dm697c6f6C9/H+nKKmxMW1tLewqKOfqrOik5aSgJaalZCPl5SXoZGOhomLg4SBhIuIhoiGhIKIhvDs+f2Ag4H07vHs4NTZ8YmPkY6LiISLh4uSjYKEhYGCjZiioZiTkJedm5CNioaDgoeHhYiIh4eGiYiUn5OOjYyGgISGi5CNkJWXnZ6cmpCB+IeVm52dlpCIipSWmJWRkI6PkZGJgH2AfYCHg357en2BgH5/gH5+g4GEgn57d3d5eHd7fX+EhIB/goKAgH57fn+BiIWBfHx+gIODgH99fYB+goCCe3uCgISCfnh9dn5+fn934uTre3p7gH56hoB9fnmEf+l45nrmfoGEgoOFgn+HiIKAhIF4fnvse32BfX+BhHl3f3+AgH96fH58enp+fn+DgoB+gH556+t6fn17e319fHno6+3n6uftent5dXh36uTk5XV1eHXr6eTc0cjKytHY4Hd45OPo6Xl8dnByc3t/e+Hh3dV2hod7dXd6ePDj33d46ubpenx5gId45ezr7PTveXp7end2e3t+fn97gX55e356eXvsgHh6fYGCgoJ+f3l8fnZ4fnx+gYB8e3t+fHx+fn19fHt6d3qAf3p9goKCi4qHgYOHiYB769vh6ufp7uzsen+AeHh9gIaEg4aJhoKAfoOCgIGJhoB/g4aKiomFgoOJjouIgoSA8ung2NXY2eHh4eno5+Hq6evzeu/s7u96f3d6fH19gHmAe3p7fnd/hICCg4eIkpmOjpKQkImIiYmKhYWGi4aBf4CJh3+CgIR/fn+LhICAfIB4e3yCgoCFg3uAeeuCiYyMfIKIkIWHhYN3d3Z1fXx6eYGHgX1+gniBhImGgOnofOp6fH5/eubZ2d/q5Xh6fICBfXzs7Xp9f4CChIOAf359gHt8f4CChYWCgYCAgoGAe3l4fIGAfXt8eOp7gYR+7+zz9/Dxfn956+fl6XZ2fn5/goF5eXx6ent8f4KDhYiDfoWFhoyMfnh2dnd5d3l9fnx6eHmBe3d35uR2d3LfeH56eHt7fH58eX+Dg315eHh7fH19eu/r4+t5ent9foKHhYaCgH6AgYCCfX6Afnt/f3+AgYOBfoOBfYKFhoSDfOvsd3t7foCCfYB/en+NioODg4iGgn52z8Dden2BhH565+b063mCenJ8enl4en1+enl8dXfk6eLs8fPse3d15HV96uh9eeHe7Ht/gXh7fXzvf/De6H16eXh26ON7gHjd3nru18HUCup64+Dhdd/m69yEeYDh6Xx/9PTt49/ieeHdeHjxeO16ee6DeuXq5Hru3tfiytDf6Nnd4N3d08TKvsfNw7WjrK68tru3vt3fjYRtyYJ7fXd14Nji2d3d3MbMx72wl8Wyr8TEztfYy8fT7Hrl2N2Ijo6hnJKUlo+KhoSOlouAhYOFh4SBgIiFh46Af3Z5fXJ4enp8gXx6fnx5eH594eDt7Xd4duLg6eXTwcbgfoKEg4KAen98foOAeHp6d3mCio+NhoKAhYmKgoKAfXp7fn16fn9/fnp8eoWPhH9+f3x3ent/gn+BhIaMjYyMhHXgeoaJioqFgHp9hoeJhoOBf3+CgXuAamxsb3RvaWZmaW1pZmdqamtta29vaWZkZWlqZ2dobHFxbGhqa2poaWZpamxxbmtqa2xtb29tbGprbG1xb29pZ3BrbW5sZGtla21ubWjHydNtbGxwb2t1b21tZ3BvzWzKa8lrb3Vvb25vbXBwa2pvb2dsa8xpamtmaG1tY2NrbG+AbWdsbWlpam1tbW9ta2pra2nPymZoZ2RlaGlqacrKysPGwsNkZ2dmZ2THxsjPa2hlY8bCubezqaamsLjAYmTEydHRampkYGFhZWRfs7a0sWNwcWdfYGZmz8bAZWbNyMZnZ2ZtdGfFyc7Nz8xoaGppY2JpamxsbGdsamdqamhoacuAZmhma2xvcGppYmRnY2lsaGhqaWlnZmlmZmlqaWhnaGhlZ2tram1ubGpta2xoaGxvaWjPw8rRzMzY1NRsbW9qamxvc3JubXFvbG1scG9rZ29ubGlqbG9vb2xqbHB0cGpmbW3Ox7+9vb/ByMzMz8/PytXT0dJnyMrM0GtuZmlqa2mAZmpmZmhrZmxva2toZWFseHJxcnFxa2xubm5oZ2dta2ZjZXBxbnJtc25ub3h0cHFubmhoa3F0b3N1bXRt0nJ1eHdlaG51b3BvcWpraWVrbG5pbHBvcnFyaXJ0d3Vxzcdr1m9tbG1ox7/CxMzMbHBydXVwbc3LZ2ttb3Fybmtra2qAaWhqbG90dHBubm1ubW1qaGhscXFua21nxGVqbmrLw8bMyMZnaWbKx8TIZmVoaGpsa2VmaWZmZ2dqbW5xc25pbGxucm5hXV5hZGZkaG1wbmxqam1nZGTExWVkX7hiZWNiZmhpamdjaGtpZWdqa21tbm1p0M7Kz2lqa29ydHZxcW6AanBycG9qbXBvbG1ra2xwcnFwc3FrbnJycXFv1dNpbWtrbGxoa2plZW1rZWVobGhnZFyel7VmaW1xbGzMzNjQaW1jXGZlZmhpa29tamplZ8HHydrf281pZGTEZm7MxmlmwsHQcW5pYWRoas9w1sTFaWhoZ2bKyWhrY8LGace0oLiAzWnGxsFkw8rPxG5ua23T2nJy1tXSzcnGZsLCa2nOZsFma9l6bcvOyG7SwL3Mu73H08LAwcDBt6uvmaW1r6GWmJqompubl7CraGZWm2ZhZWNkv7e/u726u6uzrKWNcop7h5yeqLO0rKixzGrDtLNoaWNtbF9ibGxraWltcWtob2yAb3BtaWZqamxxZ2dhZGhnamppamdna2llYmhpvLzHxmNmZL28xsGxoKG2am1wcG9taG1paW9uZGZoZWZscHRzbWdiZWpua21samhpbW1scG9tbGlqaG50bmxrbGlmaWpsb2xtb3Bzc3JxbWXCZ21ub3BvbGdpcXJzcG5ta2xwcWzNf4N+jX8Ffn9+f36RfwF+nX+Cfol/h36Gf4R+hH+LfoJ/hH6Jf4R+iH8Ifn5+f39+fn6Gf4Z+lH8BfrJ/iX6nf5J+AX+EfsN/AX6gfwR+fn9+hX+Gfod/gn6jfwF+hH+GfoN/hH6sfwZ+fn9/f36Wf4R+pX+CfpV/g36Gf4R+kH+Hfg1/f39+f39+fn9/fn5+h38Ffn9+fn6Ffwh+fn9/f35+f4V+BX9+fn5/hH6EfwR+fn9/hn4Rf35+f39+f35/f35/f35+fn+ffgR/f39+hX+NfoJ9in4Ef35+fq1/hH6Df4h+wX8BfpR/AgIEAICQk5OQmpmSk5CRmpydmpWSk5aWlZSSko6Li4P17/mKjoSFh4SDgYGDh4qIiouJhoGGkZSQjIyLjI6OhoqLgv6KjYqIgISFhoaB/46TnpWRg4iFi4iMhICBiZGQj5KF/4KKi4yNjIyPh4OPj5KQhpCXkYuLkZOMh4mTnI6LhYOKi4CMlZaKiJGUkIiKj5CKh4WFgoKEgYD7+4H9+IOHhYmG+4KIhYWKiYqOhoSC+Pf6gISGh4X79v/+g4WEgoKDhYOGiImKipGRkI+Gj5GQjZGZk5SVh4H/+Pb4/4aHgv6Bg4SFg4GEhID//P+BgIOEgv/+h4mIhoaIioeNjIWJgICEg4CBg4SIhoyLiImIiIiFiIqFhYmRh4KHh4mIi4qFgPP0goqPjYuNjoyIlZ+dk4yTl5ePkoXr5vaA/fyEgoCIj4iGiY6SjZCYl5CJjIyPjYeJiZGRjIOGkJWVlYuUmJiYlpGQkIqB/fjm5+3l6fb/hIKBgoD4+ICDgoWFhImFhISHi4CKiIqE/YKGg4ONj4+PjpyvrqSkn5mWlJSTnaGam5qSiYqPmJqZko2Kjo6MjYeDhIGJj4v+ipGKh4iMi4eMjomLjYP7+PWWppyOiY6GhYL6gIGBhouB/fiG+vaSkIHw9oaE9/iCh4aCg4SFiImLioSB//+DhIKBhIOEhoeGhIWGhICCgoODhYODiYyOkIn+gYWBhYOIj42Jg4WIioiHiomIjY6HgIL++feAh4aB/e/f4vSFi4mEhIOBgP7//f+B/4KC+oGDg4SDg4WKiYqIiIuIhoD/hYHy7evu+4GDgoSGh4T7/4mLjo2Hg4GAgoSEhoeHgoaJiIH0+oGFjpSSkIuMkYCQi4aChYWHiouOkI2Kg/z+gouSkZSVko6Ki4yKiYeCgYuRjIuLjI+gppiFkIqFjpSYnIyNlYyDgfPk4djT1PqDl5aTi4aGgYCC/4CF/f+Eh42PhoCBgYyG/vbz7vH5h4f5+4SRkIqE9oCEhIKDhICBhIT17vLw/oOC/Pj66vXv7oD59u7y8ent8Pbg7vn49OqDi4eCg4T//YGB9v2HhIGJ6PPz9IWE+e7n/v7r9/7u9/Dy8eXp7fDj5eje3OHd4tLKz7i/z9nkh4+ajYfYl4+A/YSGhoaD/+zgz7q5x8vLytzW0tTn2eHp+4CHg4SD44T214CR+pW8v8C3qqWwtq6gooCknpmP++uAiZGXkpeD5f2OkY+LiYSMjIyNj4eEgYKA+ezv7dzj7ejazt6BiI6Mh4WCgoOHioqJhoWJh4SHhYqOi46XoK+rlJOTjoqLioaFg4D13uX8j5CKjYiChICEhYiLjZWZlI6LjImRmZaKhYeKh4WHi5OUlZWUkZKWmZSTlICGh4aDi4mCg3+AiIqLiIWDg4WGhoSCg4F/fnfe2OSAg3p7fHt6dXR1en19f4B+enZ8hYeFgYF/gIKBeX1/eOd+gn9+dXp6e3t15oGDjoeBc3p4gH6Be3Zze4SEg4J36HaAgH+DhYKCfnmDgIaDeYKJhH6ChoeCfn6EjoOBenh+f4B/iIl/foaIhH2AhIV/e3p7eXl6d3bo6Hju6Xl8fIF+63h9eXd5d3l7dnZ04+PmdXd3dnbk4unmdnd2dXV3eHd7fn9+foGAgH54gYODf3+FgIKCeHTq6Orv8Xx8evF6fH19e3l8e3n09fV6eHt7evP3gH9/fX1+fnyAgn6AeXp8e4B5e3x+fYOCfX9+fn16fH17fYCGfnp+fn98fnx3dujmd3yBf31/gH56hYyKg32DiIeAg3rc2+l47u16d3V7gHp2e4GFgoWNiYJ/goOJh3+DgoiIg3t9hoiHiH+HiomJiISCg4F89O/c19rZ4u3xeXl6e3nr63d6eX19e4B8fHt+goCAf4F87np9e3h+goKBf4qWlpCRjIiHh4WCiYmEhYeDfX+CiImMh4J/hIWEhX96e3iBhoTuf4J8fYGDgHuDhH+BhHrr5NyIlo1/en94e3rod3l5fYJ27eV+5+KKhXfg5n585+h4f356e3x8gICBgX189fR8fHp5e3p7fH19fHx8e4B6ent8fnt4fICChX/reHx3eHh9hYSAe3x+f359gH9+gYJ+eHrt6eh2fX136d7S2el8gH15eXp7evP28/J68Hl56Xl7e3t6eXp9fX1+gIJ+fHfse3jg3d7h6nh4dnd6e3nn6nx9gIF8e3p6fX99fX9/en2BgHrm63l8gIGAgX+BiYCJhH97fX1+gIGCg4B/eefte4OHhIeHhoWDhYWCgX97eX+EgYCAg4OOkIBxfnx5f4GDh3x9hoB5e+vc2NHLzO55hYN/eHV3dXR373l/8fB6fYSIgHh5eIB55+Hk4uXrfXri5XqHhnx12nR5eXh6eXR2eXrh2+Dg6nh57Ojq3ebh4YDv6+Hk4Nra2t7S4Obg2M93gX58e3ro5XV45up+fXt/0+Tt7Xx66t3X7u/W3+3g5t3e39fZ3ODW3N7OydLP1Mi/wKixvMTLdH2JfXW4gIBz5Hp8eXl37N/TxbCosbi5tsjGwsDQxMzL2XF4dXd3ynPZuGd1x3mbnpqUkYyUmZKFiICKh4WB59d1fYOGg4l40eeBhIF+e3iEhYKDhX17eHl35tri4tHV4NjHvc12e4GAe3p4eXt+gICAfn1/e3h7eHyAfYCHj5uWgoSHg4B/f3x8enjkzdPngoJ7fXl1eHV5eHt9fYSHhH9+f3yCiYd+enx8enh7f4aHiIiGg4WIi4eHiYBwcXFwd3VvcW1rb29vbWtsbXBwcG9sbGppa2W6tsFscGlqa2poZWNkaGxsbWxqaWZsd3l0bm9tbXBvZ2xvacVrb21sZWhlaWpmx25tdXBtZGlnbW5taWdjaG1vbm1lzGRubm10dnFwb2pvanFvZWpxcG1ydXRwbmxudG5uZ2ZrbIBrc3VvbHJzcWptcXFrZ2ZoZ2hpZWPBv2HExWhqa29rxmVoYmFlZWhta2llxMbHZGVmY2C/x8vAYWRlZGNjZWVqbWxqZ2psbWtiaW1sZ2dtaWpoYWDM0NPX0mhoaM5lZ2tpZ2NjZmTLz9BoZ2pratLPa2xramlpa2ltbWpsZ2dnZoBlZmlraWxsaWlmaGlnaGljYmRpZmdsbmxqbGpmZMXFZGlubGttbWpmaWtrZ2RrbW1mZmC8wtBpztJwbmhrb2ppa25wbW92c2tlaGlzd29rZ2xubWdpbm9ycmlvdHN0cmtoamlmzMm9ub7CzNbVamptcXHW0GhrbG9vb3JubWprbYBsa2tmw2Zsa2hqamlnZGx2eHNyb25wcHBscXFrbG9rZGVobnFybmtqcHNzdnJtbWtxc3HRbm9sbnFxcGlydHBwcWzNwrVxgHdpYmtnZ2zVamxpbnVs0sVuzsx4dGnGxm1qyMhka2loaGptcG9wcG1r1dRrbGtrbWtsbGtra2xta4RqgGxqZmltcHJuz2prZGZnam5ubWtqaWlmZWtramxrZmFgubSvXGRnYsC7s7fBaG1saWlqa2rPzsvKZsxoatJpZmNkZWZoampsbG1vbm1r1GplwL3Bxs1naGZnaWlmwcJqbW5ta2xsbXBxcG9wb2lscXBpw8ZmaGtvb3BtbXN0cm9rgGxsbW9vb3Bvb2jM13F0dXJ0dHRzcHJzcG5saWhsb2xpaWppb21fVmJgYGlqZ2liY21raGnHvsPCwL7RZm1mYVtaYF9fYtBpbMzJZ2lxdXFqa2ltZ8vGys/QzWxrxsNlbGhhXbJhZWlqamVkZmZov7i+u71kZ8zKyLnFw8bLy8rOgMS5t7a+vNPazsC5b3dybWtnzc9oacTNb2xpbbvM1Ndva9HJyN3Tu8LRytTNzce+wMbMwMfKtbO5s7ayrq2Rm6m0tGJmbWJelmdkXr9jZWZnaNLFwLqil5KQkZKhnZyisqyxrb5gZWRlYqVis4lHT4NOZ2ptcHFscHJuZmptamtofMC2Y2lra2luYrC+aGppaWdhaGdnam5nZmdpaMq+xMOzt8C5pZilYGNnaGZmZGZpbm9tbGppbGxqbGlqa2hpb3N6eW5wcm5sbW1rbm5qyLK1xG5va21pZGZjYmFlaWlucW9sbG1qbXNya2lsbWppa2xxcnJybmpqb3NycnSbf4N+oH8Bfop/AX6UfwF+tn8Ffn5/fn6FfwF+i3+DfoV/hH6df4V+BH9/f36Jf4N+hX+Cfq1/gn6UfwZ+fn5/fn6rf4l+hX+CfpB/AX6ufwF+jn+Dfol/AX6Gfw5+fn9+fn9/f35+f39+fo1/gn6afwF+l3+DfoR/hX6If4R+BX9+f39+kH8Dfn9/hX6Hf4J+k3+Cfpd/gn6of4d+in8Ffn9/fn6Kf4Z+BH9/fn6FfwF+in+FfoJ/ln6GfwZ+fn9/fn6Ef4R+gn+hfoV/BX5/f39+hX+TfoV/B35/fn5/f36Qf4J+h3+CfpB/i36nf4R+q38CAgQAgJSTlZGQk5ORjY6TlJSRjYyQlJSTlJaXmZybmpSOjYqHjZGSkpKNiIGCgYCCgoOFhouRkpCLiImGhoSDhYiFho6J/IOAh4mKjZORkY6MkZWdnaWknJGQjo2Sj5GRj5WRj4eNjY6IhYeSh4L07YmTjoKEjpGTjYuNkIaHkZWUkI2SgI2LjIuJhoOKhoyQjoWGiYD5gIKCh4aFhISFh4f/+fLv+IL99f/4+IKFhISGiI2NiYaKioeGiZCYko+SjI+OiIiGgYGCgIKBgIGNjZGYlYeHjpOTjoKAgf+EiIuKioqHioaFhYqGgPn2g4WFhIODhYmGgfj8+fH5+fyAhIiFg4aEgIOB+P2Eh4H/g4WEhYeEg4D/goODhISGg4SIh4L+hIaHjpKSj42IhYeSk5GRjouWnZiTjOrb4+zx+PPzgYqZj4OChoeLj5KPk5OPiY6NkJSSlIeCg4eJioyPl5SOjYySn5ePjI6AgoP89vz9//qCgfTx8YD48PD6gYWEhIiGg4SHgIeIhoeHhIWGhoaKjYyPmKS4nI+cpqehlpSaoJSSmJiWl5eXnZ2elZejo5aVj42KiomNjIeB7oGAhoiQiYiKg4CCh4CEk5aOlo38jo6KiIOEg4aHioT6+4iMi4OHioKC/IKG/4H5+f39goOCiYaChYWEgoCBg4KFhYGBgoSHioiDgIaGgoaG/veAhomGiIuJhYSJh4D694WGhY2MiIqPkpWVkYyIhPXe1drp8/b+hob14+Dj5un9hIaFhYeIiIP5gIKEiIyJh4aGjZSRjouHhIWJjZGHgPbg2N7l5+DZ6PqGg/Ty8eXl7PX39vb06N7rgYOCioyOjoWDiIqGhYiLjY6PgI+PlJGIg4SFiIqIiouLhY2Ri4aEhYmGg4SEiI+bkY+TlpmalqScjYf3hY+Fg4qIh4SN+PHu4d33gfz+94WShuv0jZKLiIOFjoj46OTn7fXi193q8fnt9YKBguuB+IGIj5WNgIuMk4r69++BipGTioKC+f35/P307/Hq6/f59Pf6gIL8+/vy9O/9iIKOiIP9g4iHioyHgPGFiYH/hoeHhYqG//vy/oD8+Pft9v32+Pn/gPHp6Onr4d715dLT5Obo6tve4+Hh3tfX0+6E6+qOmYmRiPnt7+vp28/IwMjGuOX+6vT4hPXs5/WHkIWE7vmLleS/3fyKk5ahsMfCqKSwtrGkgJ6gm5SRkJaG/YeQlo//+oiQm5OEhIqOkJGO/fz59/iA+OfY2Ov97+bZ3vWEjpSMjI+GhIyNjo+OlpaTjY+ZnpaMi4+RmaaspJyanZ6bnZ2Zkob4gIGHkY+Tk5CPi4uHhIuVlZKSlZiclIaJmZydn5uYi/3+ipGQjY6Mi5KTkpWVIYmHh4KBhIaFgoOIiYiFgYCDhoaEhYeIiouKiYSAgX98foSBgH57dnZ0cnR2eHt8foOFhoOAgXx8eXh6e3h5gn3menZ6enyBiYWGg3+BgoqKkpCJfYCDgYWBgoGDiYGAe4KCg398fIZ8eebefISCenyFhoaAgYKEen2HiYWAgIaBf4GCf3x4fXqBhYJ6e3547Hl7en18enl5eHt98e3m4uR1493kgN3ddHZ2d3l6fn56eHp4dnZ6f4N/fYB7fn15enp2dnd1dnZ2d39/goeFeXl+gIF/eHp88nt/gYGCg4CCf35+gXx26Oh7fX57ent+gX998vPp4Obk6Hh7f3x4eXh5eOnte3546nh7ent8eXh16XZ4d3d3eXZ3fHt263p6e4CDg4GBgH56eoKCgIB+fYiPiYR/39fa4OLm5OJ2e4V/eHh9f4OEhoOFhoN+hYSGiYeJfHd5fX5/gIKJh4SEgoOMiIF+gHh7fe/o7fH28np55+fnee3k4ep4fHp7f317fH9/f31/f4B/fn9+f39+gIaOnoh8h5GVkIaEiIuAf4SHiImKi5GPgI+Hh5GTiYmFg4GCf4OBfHfYdHV8fIOBgIF8e3qBenqIioWJgeaDg399eXp6fn6BfeztgYSEen6Ce3rnen7ud+rs7el4eHh+e3d7enh3eHl6eXt8eHd4en2Af3p9fnt+fu3kdn6Cfn+BgYB9gX536eZ9f32Egn+AhIaHiIN+e3jiEdHKz93m6e17fOvg3d7d3e19hYCAgXvnd3l6foODgYB+hIqHhIF+fH1/gYR9d+TTztXa2tLL3Ox8e+jn593b4eru7e3p3tXhfHx6f3+Agnp5fHx4dXh8f4KFhYSKioJ9fXx+f319fn13f4SAfnx8f358fn+BhY2FgYODhYeDjoqAfOR5gnt4fXp3dn3d2NnQ0e199PGA5nmFfd7lgYR7dnJzeXjl3t3f3ubYztLb5e7i53l5e+F56nh8f4F9dYOGioDk4t12e3+Ae3h66efi6/Dm4OHb2ubq6ezufO/q5d7k3uZ6dX55eOt4e3l8f3144Xp/evB/f359gXvt8ezxeOzm5dvj6t7j4ul239fa2tvT1fHixsOA1Nnf4NDT2NTPzcnMy91419J/inyBe+Xc39vYzMK8tLm0o8zk1d7ectvf3+d8gnp64+uAiNKwzud7fn+Fj6Kgj42VmZWMiYqHhIOBhnzvfIKHge3nfIKJgnV0eoSGhH/m6uro5nTby8LG2erc08fN4HeBhX9/gXl4gIGCgYGIiIZFgYOLkId/foGCiZGVkIyKi4uIiouJhXzldnZ7gn6BgoCAfn97eH2FhIKChYiKhHl+jJCQkYyIfOTlfYWFgYF9fIKFhomKEXRxcGtpbXBxbm9xcG9tamhrhW4Tb3FycnRzcHFxcXN0cnFva2lmaIRqgGxub3J1dHNxcG9ra2lnamtnaW9rym5oZGNobXNucG5ubmhsbnZzcGVqcG9taGpnbnNtamdubW9uamhzamfKxGhsbmtrcHFwbnBwcGdpcXNsaW9ybm9wcW9sZmllaXFwamlqZcdoa2psbGtpZ2Voa9HRzcjLaMe7x8fIZ2loaWppgGxraWdpZmVmaW1xb25ybG1raGloZGVnZWNhYmJoaGpta2RjZmdrbWpvc9hrbW1rbW9sbmpnaG1rZ9DLaWpramlqa21ras7Qybq7v8hoaWpoZmZnaWrNz2trZsdnamprbWpnY8RkZmZnamtkZmxradFsbGpucXJxb2hnaWZlZ2djgGRrb21lX7u9wMTGy8vKaG52bWdmaGhra2xoaWlpZmxwc3BsbmVkaW1vbm1ucnFtbm1vdm9lZmpiZ2fGx87P1tZqZ9LU1W7TzMrLaW5ubm9taWhqampoaWtsbW5vbm1tbGxvcHlpYm10d3VvbW5wamtwc3JubnBzb29pa3R5dHRyQ3FydXJ1cGtltWBmcW9ydHJzb21rcmtpdHh1eHDHcG1rbmpqaW1xdG/W2nR0cmpscGpmwWhry2fO1NLGZGRma2lmaGeFaIBnamxoZ2Zoam9sZWhqaWtszcRkaGppbG9wbGhqamS9umZoZ21taWpucXJxa2ZjYbuzsrjBx8TCY2bHwcDExMHMa25ubm9vbmjBY2RlaGxqaGdqc3p2cm9ta2tsb3NrZsa5tLi5uLWwvMhpZL3Ey8bAw8zQzs7Lv7fDa2xqbWxtboBnZmlraGZpbG9ydHJwdHNtaWtsbnBubm1qZ25wbGxrbXFwcHFwcXV9dHBvbW5sZm5tZ2K2ZG5pZ2toZWBir7K2u7/fe+jZy2hwab/FaWddV1ZZYF+5s7rAvsW9tLi+xczHz21pa8lqzGppZ2hkXmhqbWi9wcJlaGdlZ2hqzcHAy4DRxsPAvr/HysnP0WvT09jMx77MbmlxbmvRa29sb3Nuasxuc2zRcnBram5qz9jS123TzMu+x8jBxcjNaMfBurrBwc/w0a+wv8LHyLq8wMC6ubm4sL5lurVpcWlvasS2vL68sKqvo6KPe6K3pa+wXbW1ucJmbGhqxspqbbKassNmZIBhYWR0eG1rdHd1cnJwbm5ta29nyGZpbWa5t2Nob2leXmRqa2tpvsfLxMBgtq2rsL7HubOoqLhjam1nZ2pmZ21sa21ucnFvbG9zcW5pZ2dnbXN1cnBxc3NwcXFxbWW7YmRobWtucG5ta2tnZGhubWtsbm5xa2FlcXFwcnN1bcbFagtwbmtraGdtcnR3d8F/AX6of4J+pH8Bfot/hX4Bf4V+sH8Bfo5/gn6Kf4d+iX8Gfn5/f39+iH8Bfot/AX6Wf4h+rH+GfgZ/f35+fn+Efr5/AX6TfwF+i3+Cfoh/BX5/f35/hH6df4J+jH+Cfo9/iH6Cf4d+iH8BfpZ/in6Cf45+un8Bfol/hn4Jf35+fn9/f35+iH+OfgZ/f39+f36Kf4N+h3+PfgF/h36FfwF+h38Ffn9/f36Gf4R+AX+KfgF/mX4Df35+hX+RfgF/hH6EfwR+fn9/hH6VfwF+hH+Cfot/hX4Bf4t+p38Bfp9/gn6MfwICBACAkY2Pk5OSkI+PkJWfoJuUk5GOjIyMjZCOioaAgPjm9oqHiY+KhoWHjYqCg4qIh4iJiouPjIyLioeGjIuLjYyDnJiJh5GOhIKIiIeSiIuMiYGKjZCPl6Czo7OilJadn5OYmpD7hoeFk5WamZWE+4aRkI2LiZWdmZqSipGTj42LjpCAi4mKiYiMjomQj4qHg4H/gIGEhP/6+v6BiIiCg4OA+feAiYOE/fWCiYqJh4aHjJCPj4+Ih4yNj5GRkY6MjIeFhIOChoqGhIiNlJmWj4yQlI2UkYf59PDv9Pz9hIWLkIuFgoaDhYWJiIiFhYWDhYGEioeBgoODgYaNjI6MiYOJhoOAhYWHhYSChoWDhIT/+vL4gYOHiYqKioyEgoeJi4uKho2NjIyL/fiDiIaLmJGKiIiIh5Shiubv7u3w/oCD/ImFgYODhYmQmJiXlI+Vjouao5iWkoXz9IH5/oOKjpOKh4uQjo6NiYWQkIyH9u/x7+/w5unr+vj+8+/9gIGCgoOBhIeAhoaGi4b96fDy8/yCgoOFh5y0sqyfmqSpqq2qpqGfoJ6dnJeOiI6Sl5mipqaelZOMiI2OgfX4+oOIiIb/gvDX9ImJkYqDiYuJhYmFhIKHkZOFgO/x9IaKhfHy9/v/gIH6/oKA+4H/+fn9hIeFiYqKh4aJi4qKhf3/g4aGhYH9/oCAgYSHg4OHiIuJh4mHgoOHiYaGiYqFgISLiYiNkIuJiouJhYKChIyViPHYxsHK5fr38/Hq5+z1/f+AgYSIhoSDgICDhoeGhIKBgPyDg4CEh4eMjoqC//Lo7ur8gunl7OPa2uL1gPLl5tvT4vn9+fb9gICDhIGJlZGEhoqLiYmMiIqAiIWGhIeKjY2OjIuKjouFhYaGgoKEh4qMjZCTk4uGhYiLi4qHkY6NjPX+gICA//r5693tgPn4+vL7gYGFgoD8i5OWl5eSi4H/+Obe4OL7+/6A+ung6eXb1+z89vuBkZqY49Lh9IL1+/eAjoaLiffs9/js/YH+9/Ts+v778/X/+/yAgPX3/IGC/4CB/ICBgv38/oyEhYj79PyD+4eJg/35gPX1gPGA9fj08fT27vr48fX48/Hu8PDu7+zt3ezl6uze2d7Q2eXX1NvT5+Pa3N/gz9Hg4Ovz+/Pb3uDezMfP5vno28fJ1OWGjYuMj5GVk4z85/CDgPqFjaK+yqafn52apaGAnpuXiPeAh5CE9PWAhImZoKGO9fmHjZeampSOg/Pu7vX06vT48oD+7t/e84OFjpOOi4SFjY6OhY+OjpGZlo6E+f2AkJiUkqCqoZaTlZCOi4WE+enb54aZmJSOiouLiYTvhpSMhoiGhYH8iZWak46RjoqGh4yTk42MjJGUkpOVmpqAhYCBhYWEg4GDg4eQkYuGh4aDgoGBgYOBfnt2deLR3359gIN+e3x+gn51dnx8fH5+f3+DgYKAfnt5f35+fnx0i4h7d4F/dHd/gX6GeXx+e3V9f4KChImci5uKgIKJjIGIi4Hhenx6h4iLi4d443qFhYF/fYePjI6FfIaIg4B/goSAgH5/f32Bgn6Dgn9+e3nxent8fO7m4+d2fXx3eXp46eR0e3d35eB1e3x8e3t8f4KAgIB7en19fX5+fn18fHl5eXh3eXt3dnl+g4iGf32Ag36Fgnvp6+jp7PLzfXyAhIB9en18fXyAgH98fn57fXp9goB8fX18e32CgYOBfnh+e3l4eXl8e3p4e3p5envv5t3meHp9fn5+fX12dXp8fXx8eX5/fX595+V4e3l+h4J8eHh6eoWQftrh4N/g73h66316dnl5e3+DiYiGhYGHg4GMk4mHhHzo6Hff5Hd9gYaAfn5/f4GBfHiAgYF96N/j5ufo4eTj7u7y5+HshHeAeHd4fX19foN/8+Lj5Ofxenh3dnWGmZeUioeQlJWXlZKRj46Li4yIgXyAg4aIkZSVj4mIgoCGhHjk5uZ6fHx663bezuuCfoN+eICBgH2CfXx9gYaIe3ng4uJ8gn/k5ent8np67vN6eu547efp7nx9eXx/gX19gIKBgXzt73p7enmAdubndXZ5fHt9goOFg4GDgXx7fn56enx+end8gn9/g4R/fX6Af3x4d3d7g3reyru4v9bp6+jo4d3e5/D0eXp8f318e3h4en1+fXt6ennwfH58f4B/g4WBeu/k2uDa6Xnc2N/a09Xd7Xvp293Rx9Tq8O3q8Xt7fX17gYuGeXp9f36Af4N/f317fHp+goSEhIKAf4OCfoCDhYB9fH1/gYOFhoWAe3p8gIOCfISCgX/h63h4d+zn6eHV5Hvx7ezk7Xp5eXV16X+Fh4aFgX1z5eLV09fX7e7wd+TZ1d/e2Nbk6uDld4OIhcjB1uJ23ujgdH91d3ni2+vq3ed48Orm3ens6eOA5e/q53bk4eBzdup1duh1eHrt7e1/enx96ufseel9f3rr5HXd3Xbgdubn4N3c3dri4Nzg5uPg4uno5OPe4dPj2Nvd0czRxszYy8fSydrY0dPX18TI09Pf6PHqzdDZ2MbAw9bk1ci6uL3QfoKAgYKFiYmE7tnie3fmeHyKm6OLh4mAiYePiYiIh3rhcnZ8deDhdnp8iIyMe9TZd3uFiYuDfHXk4uTo5Nrh5eB26dzPz+F5en+FgoB6eoGAfnV+f4KEiYaCe+TodYOIg4CKkoyEhIaBfnx4eeTVytZ7iYeCfXx+f3122HyKgnx9e3x684ONj4aAhYSBfnyBh4WAf3+ChIMEhIeNjQZua25xcHGEcoB0eHl2cnJxcG9ubm1vb29ubGzWzNl1b3B1cGlobXR1bm1ycXFyc3Nycm9vbWxpaG9vbmxpYnFtZmFoZ11jbXBtc2lwbWZkamtpaWlrem51aGRnbXBnbnJrv2hpZXByc3NwZsRncHFsbGtzeHN1bWdzdW9tbmxsbmxsbWtubmtvbYBqaWZn0GtsbGvMycrNZmpqZ2loZMXJZmxqbdXEYmdqbGtpaGtubGtpY2NmZ2ptb29raGVhZGVjY2ltamZnaWxvbWlqbm9maGhmx8vMztTa1mpoa29taWdoZmhrcHBua21taWpnaWxqaGlpaGRmbGtqa2tpbmxrbGxvbGpmaGVobIBs1tDExmdpbG9vbW5vZ2dra21raWpubm5wb87La21pZ25qZWRlZWNpbmW6w8TFx9JobNNtZ2ZpamppaGlnaWxpbWlqc3RtbW5t0s9nxctnamxuZ2Rma21taGVgY2VoacjCxcjJy8jJx9DN08/Jz2lpa2xraGltbGprcW3Pvby+xIDOamlpZl9jbGxsamtwc3N0cnFxcnJwb29tZmBlam5vdXl7d3Fybm51cmjJzMxpampmzWa/vtx3b3Fua29xcW10b25xc3N0a2vDxcJucnPLycvO1Gtq0NRqadZu183LzWlpZmtubmpoamxrbWvMy2hsbGtpzctmZ2praWhqbG5saS5oZWFiZWdlZmlraGZpbWpoamtqa21ubWpmZGBjbGnGt6ypq73Ny8rLxsLBx9HWhGyAaWZmZWVmaWpoZ2hsbNNqaWZqbm90dHBpzsjCwrzMarqzubGrsLnIaci9wLSos8fMysjPamtsa2lveHRpa21tbG5wa2tpaWpqbXFycXFvbWxwcnBydHVycHBwcXJzdnl4cWxpa3B0cGlzb2ppu8BkZ2jNx8rAssJq0M7IwNd1cGqAZGLBaW1vbmtmYlqyqqeus7TH0NRnxbm2w8fDwMe/srNdaWtmoqW3wGG6x8JjaV9jZcbGzcW5v2fSz8y/y8zFxcDNysxpzMXDZGfVbGvMZWhr0M3RcW5wb8/O2XDUcXVyz8NnwbxoymvKzsvFxsm+wL67wcrFxMfO0cnLzs2/0MRaxsm9ur61wcm3usGxwcLAxMfGuLzGwcHG2Mqztr/Dsaqksr6xqZyZm6hlamttcHN3d3TSu79nZcRjYWl2fm9tbm1sb2trbW5lwF9hZ2LAwGJkZGttb2Oss2FjhGpmaWXFyMnPyLa9wbtjw7enp7liYWZsbGxpaXBuaWBpaWlqbm5uacTCYWxxbGZudHBqam5raGZlZ8S4s8Bqc3BsaGhsb25qvmhxamlsbW1rz2t0eHJsbm5ubW1xdXNubW1xc3FwcXV1nH+DfsN/AX6JfwF+oX8BfoR/hH6Hf4J+hH+Cfq1/h36xf4R+lX+Cfo5/hn4Df39+ln8Ffn5/fn6Rf49+jX+Gfql/g36EfwV+f35+fpJ/Bn5+fn9/f4V+CH9/fn5/f35/hH6Nf4J+hX+Cfql/kH6RfwF+in+GfgF/iH4Bf4t+uX8Ffn5/f3+GfgF/hX6FfwF+iH+JfgF/i36Ef4R+BH9+fn6Ff4Z+AX+MfhB/fn5+f39+f39+f39/fn5+hH8Qfn5+f35/f39+fn9+fn9+f8F+iX8Gfn5+f39+kH8BfoR/gn6Hf4J+iH+JfgF/hX6Uf4J+kH+Efop/AX6IfwF+l38CAgQAgJSTioOEho+SjoyQkJCSkY+OjIqHhY+Uh4OCgYiKh4eMiIOBgoSDgoeJj46SmI2FgYOLkY6Ig4KEhomLiYmHh4yLhoyC/oiGioqJlpKPkYmFj4OGjJKHj5+Nko+Tlp+UpZiXnqCcnZ2WnJySioWMkYX6+Y2Yj4aPlY2Mj46TlZCOgI6Kio+MipGRj4aEhIOFiouLiYSBg4OChIeHgoD9gYD49/T4/YKCgICBhIWIjIqIiYyNj5GQj5CPk5SOjY+OiYWIjpGSkZedl46NjI2JhISChoiHgoCBgPn8h4uKiYaFgYKD/vuKj5CPiomIiIaIipGVj42Ji4eLjo+SjoaChIaDgIWHiYmBgoKChIH/goH8hImIh4mKjYuLg4aHh4eGiIWGj5CGg/369oWNiY+Vi4OIh4aIlpqJ7+XWydjp8On39u/5gPSAh5Cao5+LiouOlp+hnpiD9oCKi4eKjIuRkouIiY+Mio6Ni4mKmIv27fPz9fP29/Ln8PyA+PXy7vj//4CCgIOCgf34+O3ugoKBhYiHiYqSlpals7GuqqqprLCztri4sqqblZSTlJWVmpyXl5KSkZCZkoyLg+/X9/799+rg4+r1hoaChYH2gvuIiIOBhf72++no94OJgfPpg4eAgfn29u70/4aHgP6AgIGEgf6Ei4r+/YWHhoWDhISB+e//iYeHgIOAgf2AhIWEgYOKh4WGh4iKjIuKioqJiImJi46NiIaGi5WNhYiNkJmfl4qIhoKEhoWA/oKGiYiGhYaIjIuJhfz+///++PuBgv76/ID9/oaOjYuKhoWMkYiCg4SJhYOKi4b+/erY4oCIiomA9PT9+/X08vT8hIaEgIGAgoWHiIaEgImPkpKPjYaFgvz0/IGGio2OjY2Rk5OQi4uMjYuJh4WFjZCMjIuEjZSQi4P/g4yJhYOBhImHg4KAiY6KgIKA7/L8+ICMkJmWoY725OLl2NLV09TW2tfe7YCFgoD1gJeMi/jeg4OD9fuTjPT08/r0+/2Ag/b5gYDw84CAgvv8gIKIgIDsgYiB9YCB+oWHgIL46PP+9/b8hIT39oeDhYSFg4OAgfv8gvqA/PKDg/j78ufu8ICB8+7v+evo9fj48/D48erhzdvg6uzl7uzo2+Tk4+Ll5N7Y1NTP0tPTztPUzNDh7ujr7YSRl5yXkZaSjIyDhYyRnbCnoqCYmp+jnJ6hno+JgJagp66hlJOamI7/goCGiICJhon/gJWSjZSOioeIg/z+8ens3tnt+YCBgP6B/PmAhoOLmZiXlYyCiZqYiYqDg4aJ/4mPi/6BlqKnq7CrqKmfk4uKiIKDioiEgICDgf2EhISOlJmbk46OkY2MjpSUlZaVjISJj46GgPz49P2EiJGUgIiHf3h3d32Af4CDhIWGhYODgoF+fYiNf3p5eX+BgICDf3t6enl4eH1/g4KGi4F7eHmAhIF9d3Z5en1/fXx5en9+eX1z4319gYKAjIR/gnl3gnV4foZ6gpB/hoOFh42BkYeFi4uJi4yEioyGf3qBhnzl4YCLgHmDiYB/gYKIiIGBNISBf4N+e4KDgnx8e3t9gYKCf3p3eXh3eHt7dXTndnfo5+ny9Hp4dnZ3eXl7f358foGCgoKEgICDhYB/gYB7eHp9foB/hIiBent7fHp2dXF2fH16d3Z15ux9gH9+fXx6e3zw7IKGhoWBgH5+f3+BhYiFhoKCf4GCg4aCe3h6fXl5eHt9d3l6eXp37Xl46nt/fXx+f4KAf3d6enl6eXp4eYGCeXfo5+R7gX2Bhn51dnZ5e4iMfuLYxICyw97m4Onl4el233Z7goiNjH59f4GGjY2KiX3veoGBfH+BgISEf319goB/goJ+e3yIf+fg5ebq6vDv6OHp8Xnq5eXh6ezrd3p8fHzx7ezm6318fICDgYB8gIKBjJiXlZKQj5GWmJqdnpmVioaGhoeIiIyNiYqFg4OEi4R/gHnaxIDk8fPv4tja3eN7d3d8euh87X5/fHuA9ezz393rfIB35t9+gXp86+jp4ubvfH1363d2dnl363l8e+foe35+fnx9fXjm3ex+fHt4dnjueX18fHt+hYB8e3p8fn9+fn5/fXx+fHx+f3p4eH6Hgnp9gYGHjIh/fXx4enx8d+p3en1+fAN7fHyEf4Du7vDw7+jse3zz8PJ79fN+hoSAgH17f4N8d3h4fXt5goR+6+jZy9V3f4GBeebm7+3q6uvs8Hx8eXV3dnd7fX59fH+DhYaEgnt6evHp8XyAgoSEgYCEhoaFg4ODhYF9e3t8g4aDgoF6goqHgnvxe4F+enh2eoCAfnx4foB5cXd44oDk7eZ2f4KGgYp+39LOz8TDyMfJzc7I0N94e3l34XOEfX3n03l6eubngn7k5OPq4uzveHnj5HR14+h4dnjn5nR1fXTZdXx35HZ25Xp8eHro2ubu5+Tqe3nj4Hp5e3l6eXdzdOHidOV46Np2eOPl3NXb23Z55+Tj7NzZ6vHu5+Xm34Df1sXPztnd1N/f3dLb2dnd3tzUz8/S0tHLxsLGxcDE0+DU1dl6g4eKiIOGgnx9dnh8fYOQi4qKg4OFi4iKjIp/eYGJkJeMgH2FhHzgcnF3eXF5eX3hcIF+fIF9fX5/euru5Nvcz8zf6HZ3ee125N1yeXd+i4mHg3t0e4qJfHx1dUF4fOh8gX/ndoiRlpmblI+PiYSAgH14eoF/fHd3enjreXt8hYmMjYeEhYeCgIKIiImJiYB5foOBfHjt5+LoeX6GiYBvbmpnZ2drbWtscnR0dXNxcG9ubGtzdm1qaWhtbmxudHFsaWhnZWVoam5vc3dtZ2RnbnNwa2dmaWpsbmtqZ2ZoaWVlYMJsa25ub3lvaW9kY25jZGpvZWp0anJxcm90aXJqa25ubG5yam5xb2tmbXRvxrlpcGpmbHFra29xdXJrbhZzcGtsaWZrcHJrbGtrbG9xcXBsaWlphGiAZGK/YWPEwsHI0WllYmNlZ2doamlnZ2hmZ2psbGxtbmxnZmlqZ2Nma2xtbG1va2ZqbG5rY19cYGZnZWZoZ87SbW9samhoZ2hpyshvcHBwb3BtbGprbnJzcG9tbWlqbGtsa2lnaWtqa2pqa2ZnaGdoaNFra85rbGlqbXBxcG9oaGmAa2xoZ2hqbXBradDOyWlrZmluZWBkY2BfZmxmv72yqK61u7vLysjQacVmZGRoa2xkZ2lpbG5ub3Bu2mpucG9xcW5vbGZmbHFsam5tbGdlb2zLxMbEyMjLzMvIz9hszcvLx8/U0Whsa2pr1dLRyclra2lscG9vbWtmYGNnZ2dkZWiAbHF1dnd4dnZxcnNydXZ1dXNtbmtrbG10bmtuZrqmwc7U2NLKyMnKbGZobW3ObdJwcm1scdjP3MvK1W1wacvEbW9rbMnGzMjM1HBxbNNpZ2dqaMxobGzIyGhpaGhpamtr0srTb21raGdpzWVnZ2hoam9ua2ZiYWZqa2lpampoaGeAZmhqZ2VkaXJuaWtram91dG1raGVlZWRfv2Nna2xqaWpsbm9ubMzQ0s7Iw8loaM7O0GrS0G11dXNybWpra2JcYWZsaWhubWjCwLawt2RnaWtnxsbNzs7Qz8/RampoZWVkZ2loZmZma3Bxc3VzamlqzsPMbHBycnFvbm9wcW9wc3SAdHNwcG5tc3VzcnFqb3Nwa2XFZm9wbWhlZ2lpbG1oamllYmVjtb/MyGVvcnFnbGGupqSno6u1r6yurqqxvmhsaGbGZG9naMa0ZWZnxclzbsfEx8rD1tdoZ8fIZ2jGymhkY8LIZGVsY7xlaGbHam3RamtqbdHGztTR0tdubMjEbGuAbWppaWhjYsC+Y8hpzsFmZsnPxsDFxWlryMfN0764xtPS0NPVysjIu8PBzdHEwsPFvNHV1dHW1cm/vMG/vrWysbS0rq61vLW3uGRqbHBxb3Jwbm9naWdiZG1oa2tkZGlvbm9xbWViZmhqbGReYWlnY7pgW11gWl9eYrleamdkamdmaGtuacrMxMHAqqG0u19fYcVkwb1jaGJlcXFwbmhjZ3JxZmZhY2VmwmhsasNjcnd3eHp2c3d2cGtramhpb29saGdpac5qa2tydXZ0cG9wcm9tbnN1d3h1bmlvcm9radTRy85oaG1wxH8Bfqt/gn6qfwN+f3+FfrN/gn6Jf4J+pn8Efn9/fpZ/g36Of4x+An9+kH8BfpZ/jH4Bf4d+hX+Ffq5/i36FfwN+f36Ff4Z+BX9/f35+hH+GfgR/f39+hX8Gfn9/f35+iH+DfoZ/AX6ufwF+jH+Hfgh/f35+fn9+fpN/hX6Ff4l+lX+Dfp9/AX6Sf4R+h3+OfoR/AX6Efwl+fn9/f35+f3+Hfg1/f35+f39+fn9/f35+hH8Ifn9/f35/f36Ef4d+BH9/fn6Jfwl+fn9+f35+f3+GfoJ/sX6nfwF+iH8Bfop/iX4Hf39/fn9+fpN/BX5/f39+l38Bfpp/hH6EfwICBACAkpWVl5eUkJOVlZWTjIqPj4eGiYqMj5GMiYeEhYeGgoGBg4WKjZCSkI6Ig4GAg4uLiYuKiYaGi4yKioyDhISIhIiSk5OJh4iIj42DjpKXnZKQk5SUh42IjpiMiIiVkpCSlY/96o2bn6ChoaCfmZOUkI2OjIiLkY+MhYyTl5GHho6Ajo2Ki42JhoeGhIWEhomLh4KAg4qMiYX/goOBgYSKkZGRlZKKioqF/oKKjYWIjYuGiImIiYWDh4aD9YGIiIeIh46Oi4eHjpGXl5OPj42Pjo6SlpSG/vny+YSCg4aGg4ODgoOFhoiKkoqEhIKFiI2Qko+Qj42KhIKDhYmKh4iLhYJe/oCEgefs7/SAiIqGgfWEiIeHhIKKjIeDhIeIjIaFhYaHg4SB9O/7gYWJjJOSh4OChYeAgJOTiYL36/T5/vvzgoaDhouMlJqeoZqPkJOYoaOimoz68P79+vmAgoWEhISPgI6MioyRkY2YkIaDgIHz7f/+8Ojt8ur1gIL19Pr3+YODgoaE//T7+/mBgPqC+PiEjoT2h4uXp6+yq7O5r6mksLaysKeako+GgomIjpCSlJSOiYCFkIz95uvi/4OC/PXxg4r/9f2AhYmHho+JgYGOifj4jIqA8veAgf359eno+IaEgPbu8/v79PmCh4yKiIyLh4WEhIL/gIKDg4OFg4OHh4SEiIiEhYiHhIKBhIiKi4mHh4aIh4T/gIKDh4iFgPf7homFg4SHiIuOiYiPkI+UlYiDhYSDgIH68/mAgYKAgICChIKB/oKEgYGCgYGDhY2LgoCAhIaFgoCDhISFhYaIi46PgI2Oj5STjImCgYH9+oCCgYCHkZKLhouOko+Gh4WCgYKEhYaJj5GSkIuHiY2UlZCJhISA/oGEhomMjoyHh4yNiIWBhIiIjpSOioL+hIeKkI2G/vX47vP++u/2/+PV5Pj6+oD8kZOFi5GYmJGMjY+NkZiPh4H+8vL29/6FjIaIj4qMgImJhf3/h+7p9/H5iIP9/Pfy7/GB+oSC9PCC/oD2+Pf57PuAgIP+4uz7gIP89fT8gO/2/Pjz/oSD9/SBgvTu//f79fz49PD2hIaF9PXwgYD88NX3/ffn6+3y9fn38v/48PP2+PTo4+Lh4+Dg4uLg6ebg09HN1dbQ1dbFs73BxtPagNvQ4t/l7efn3POJh42J+pq6wKWpq6aUmp6gnZ2XkYiHgZGnl5qbk4yOjI+OjoqHmJWIhPL2la6VhIWE9/eB/vv17/L7/Prz+4uIgIOQm5iXnJqeoJuRi5KYk42JiYeMiIeJiImJhomGh46aoJyVkI6KhoOAh5KLgfqBg4WKh4eKHoiQj4eLj46Pj46NkJKQkZSNho2TlpmZl5aXl5aSjYCDhoaJiYeDhoiIiYiBfoKDfXx/f4CDhoJ+fHh2dnRycnR4fH+ChIaFg398enl6gH9+f39+e3t/gH1+gHd5eHx5fIOCgnp5fHyCgXiDhYmOg4KFhYZ5gHp/iH58eoiFhYeJgOTUfomMjY6Pjo2JhYWDgoOAe4CIhYF8gIKHhX58g4CCgn1/gH5+f357e3t9f4GAfHl6gYN/eul4eXZ3e4CEg4OGg31/f3rpd3x9d3yBgX5+fXt7eHZ7e3rqfIB9ent5fX58ent/f4SFgX5+fH59foWLiXzq5ODpe3l5e3t5ent6e3t9gIOJg4B/fH+BhIeIhoiIhoJ7eXh5fX99fYB8eIDmdHp53eHj5Xh9fnt25Hp7e317eH6Aend5e3t+eXh5enp3eHfk4Op4eXyAhoV6dHR5fHh5h4Z+eebc5+3w7eJ5fHh5fX+GjI6PiYCAgYSNkI6JgOzk7u3q6nl6fHt6g4SDg4KAfoCEg3+IgHh2dHbh4fTx5OHn6uLqeHrt7u3m6IB8fn6AfO7m8fXyfHrteubgdoB33Hd4gIuTlI2SmZONipSbmZiTi4aEfHl/foOChYeHgXx0eYWB6NXa0u59e+7p5nyD9evyen6BfXyFgHh5hoPu7YWCeOTpenvy8O3g3ex9e+jg5O3t5ed3en59e359enp7fX34e3t6eHl7enl9fT15eHx9enx/f3x6eXl7fn9+fHx8gIB873h7e35/enTe43p/fHl5e3p8fn18goOBhIZ8eXt7eXd35+Psenp6hHmAenl57nt9e3t8e3p7fIKAeHV2e318eXd6e3t8fHx+goWGg4SEh4eAfnl5ee7seHl4eH2Cgnx6foKGg3t7eXZ3eHp8foGFhYOBfXl7gIiLh4J9fXnueX1/gYODgn18gYJ+fHl7f3+Ch4F9deZ4en2CgXvu5uvg4+zp4OXpzcLV7PKA8nrsgX91fYKEgnx4e4GChYqEf3ru4+Tn5ul5gX5/goCCgH9+9fuE4dru7ex9d+rs7uve3Hbpfnrj33jtd+bp5ePV5Xd7gPLT3et3euzm5ex54ujs6ufteHnq6Hh34Nfp4ebh5+Xf2eR6e3nf29R0dergx+bu5NTZ29zj6efi7eqA6Ojn5ePY1dTV29bS1dXU3tvWy8zN1dfS19a8qba7vsbMyL7PyM7Y0dPJ4Xx3fHnXf5mdiJCWk4OJjY+LjIeDfnx3g5KEh4eBfoF+fnx9eneGgnZx0dWCloF2enzp6nfq6OTe4ebo6unxhIB4eIOLiYeKiIqKh4J9gYSBf318en8/fXt8enx8eXx5eH2Ii4iEgoKCgH16gYuEeut5enyBfn1+fYSDfYGFhYWEg4KGh4SEhYB6gIWHiYiGhYWEgX59JXBwbm9vbGpucHBxcW1scHBram1tbG5wbmxoY2FhYGFkZ2hoamyEbYBraWlqbXFvbG1ubWtsb21rbW5maGZqaWtta2xlZmhpbmxkb3Fxc21sbW1vZGpoa3JrbGlvcnR2dmvBt2xxcXJxcXBxbmpsbW9vbGhudnJuaWxsbW9ra29ubmtra2hpa2tqaWlsb3Jxbmxsb29uaspnaWVlam5wbGpub21vbmjEZB5paWRnamlmZmZnbW1oZ2ZjvGRnZGNmZmtsaWhpbG2EcQ5ybm9sa25xcWjNzs/XcYRuhGuAaWhqbW5xbW9xbm5vcnR1cG9vbmtoaGdlaWxqaWlobd5tbWvDxcbGZWtsaWXEaGlobG1qbnFsZGRpamtmZGVoamhpaMzIzmVkZGNsbWRiY2drY1xpbmxow8DO0c7Kw2dqamlra25vbmxpZmdoam1vb21oycvT0NDQa2psbWpvcnSAc3JwbGxvbGZpY19fYWTBwM/RysrR1tDUbW/SzNDN0W5tbXBt0crU19ZubdRtz8pobGa/YVtbXl9hY252cm9scXh7fHt3dHNtam5pampsb3Bta2Rlb23Et761y21s09TNb3PX1NhucnJta3JwaWl0c9DRc25mxsppatbY2M3F0nCAbtDFxs/RyspoamtoZmlqaWloaWjOZ2lqamtubGptbWpscHBtbW9wbmtqamxtbGpoaWpub2vKZmloamtoY77BaG1sa2tpZGNnaGpta2ltb2hmaWlnZGbOzdJqaWlpaGdnaGdlxWdra2pqamtsbXNyamZnamxsa2lra2xtbGxtbm+AcG9ubG9uampnaGjPzWhpZ2Zpbm9saWxwdHJraWZmaWttbnBwcnJ1dXBra25yc3VzcXJt1m5ycnFzdXRwbXByb25qbG5scHVwa2G/ZGVkaGlpysXNxcTKy8bIyrCouc7V2GrNbWVaYGZubWZjZ21raWtlZGPKxMnOyctrcWxpaWwxc29ub9LYdcnE19XUdG3OzszIwMBmyGpqzcdoy2fHxsPAs8NkZW3Wv8jPaGzUz9DXbITNd8nLaGvMwmdpwLfLxsi/ycvLx8pnZ2fCwL1oatDAtNfWyLW4v8TKztDR2dXS2s/Ky8XEwsXM0s7IxMfLz8i6urzIx8HEv6eYqa6tr7GnmqufqLS0vLfFamhrZatkc3BhbHJyam9xdXJwbGtoaWNmbWVoaWRjZ2RjhGBva2pjYLGvaHZlXmdrx8Zjvr+/ubi9wsbCxmxrZmZweHVzdXJ0dXRwa21ycW5qZ2VqZ2dpZmZnaGxpaGtxc3Jwbm5tbGtobHNuZsxscHF0dHV2cXJuaGtubW5tbW1ycm1sb2xobnJycm9tbW9vbm1t4n+CfrN/AX6PfwF+kX8Bfpp/hH6ofwR+f39/hH6FfwF+ln+DfpF/h36Uf4Z+ln+KfoJ/hX6Ff4V+Cn9/fn9+fn9/f36jf4V+Cn9/fn5+f39+fn6Lfwl+fn9/f35+f3+GfoJ/h36MfwF+oH8Bfod/gn6Xf4N+in8Bfqd/gn6mfwF+ln8BfoZ/kH4Cf36Rf4Z+in8Dfn5/hX6Cf4Z+CX9+f39+fn9+f4Z+g3+EfoJ/hH4Bf4Z+Bn9/fn5/f4t+CH9/f35+fn9/u36EfwF+pH+CfoZ/A35+f4p+sH8BfqV/AgIEAAqRjYeJjY+SlJKPhY2Ai4mIio2MjIqDgf77/oCChYeJi4yNjIiIjIyLi4uJiIaFiIiHi5GSkpKWlpSNio2UlJWbj5KNj5OTjoWMj5SUi46SjouGjJqXlJaXkZWPk5SSk/yB+e/6/oiQkpWYnKOnqKGM/PeEj42MiJCXh4KBgoOLjIyKiYeEgoWFgoKHhP2A+fX0+YCEg4OCgYWLjouDgIWG9/T7/4WKiYyOl5iTkpKPkYyOjIiChYSA+vmHj5KRj5KQi4yRkpOYl5GRjIiHiYmGgoOFiIeFg4mHgYKIh4iGg4GBiISHiISEhYWLj42Mj4uLkIuFgYOEhYeKiouLh/Xl8IGCg4mJhoGDhIWBgYaAhoGDhYmOjouHhIH8gYKIiIL8+IOBhYeGhomFi5KNiYCDhYeEhpKTpKGN59LU7O/6gv3++vyDgomPnJ6MipKVnJydmpSVhvn7+4CEioWGioeHjI2SjoyOjoj1gouMhYHt4PSDgoWG/Ozj5+/59PH5/IGGiYuG/vDw+oCAgoOA/v6A9u76/vL4g4WHjpeblZOqu7+8wLSik4qLkZOMi4mFjJGMhYSChISFgPHc39ze6ff0gIT47vj89fj9hYeHg/n8gv7y7+2BiY2MjpCNh4T//v6CgIOKjYWAgIGCgYCAgYSHiIaD/4OGhYeJhYOHiIOAgIKEhoT++f+BgYaJh/v7goOAhYqOiv7w9v2Eh4eFh4WB+4GIiYiIh4iGhYqMiIiJjZKPiIaJiYaHiYqLi4WEiYaCgYSJiIOEgPyAgPv6/4GFjIyEgYD/gYL9+/Py+YOGhYaHh4eEhIqKhoeHiIyPh4KGgu7t9v2AgICEgPXr7fiCgYCFioyPjYODg4eIiIiBg4OAgYSJjI2RkI2LiouMh4SEhYqIgYKEh4uUlY+MiYqLh4WG++vk1Ly63Oft+4aMiPTv9vb3gYqMg4GEiI6IhoH6+YaZkIWJh4iHiYuNkJGRjYiKiIiE/uzt/fr6hpGHhIHo+vSE/fmA/e6Ah4mAgYCEgfn27+XO6/b18e/r8fX08IKAgP3w//Pz9vzz9/T+gYOA8+z4/Prv7ur67+LpgIGAgvyE9/j4hoby7+74/ev6iP71+Oz6/PHl5+vj4ejr7u/t5tja0+Df5+DS1dXSybnGy7+7vb+4rsHHsNLi5NPh9tbH8Pv8nb2klJWWmZagl5WNiYuF/oD8lp6fmpOMj4nt84dxk5iUkpSRgoqTlZaOjIiDhoKCg//78P+Fi4yIhoaE/PqJkpiTlJONiY6YmI6OjZGRjI2JiIiDgIWGh4yOk5ydmpqRhf78gPr4+/v+gIGC+fHv8Pb+ioz9goOChoWFhIiMkZSTj4qJh4eGhoSFj5mVkJGAhYN+f4GChIWEgn9+fn+AgH+AgYKBgH56efDt73d4enx+gYKDgn5+gYKAgH9+fnt6fHx8foGDhIOGhoR+fH6Eg4SLgYR+f4KDgHp/goaFfYCBfXp3fYmKiYqKg4aChYSDhOJ36eLt736FhomKjpCSkI1/6uV6g4GAfIGJfHl4eXmAf4KDg4KCfXp8enh5f3vt7u3u8nx/e3l4eHyChIJ8en573tzm7Xt+fX6Ah4iEhISDg35/f3x4enp35+V7gYOAf4KCf4CEg4OHhoGAfXt7fXt3dHZ4e3t7en99eXp+fX18end3gHx+gH1/gICEhoWFh4SDhYF9ent7fH6CgoCAfOGA1eF4eHh8fHt3eXp6dnZ7e3d4en6CgX58enjqdnh9fHnr53t6e319fX98gIOAfnZ5fH9/gYaGl5eI4c7L3+Hseenq6+p2dn6BjIx9fYOFioqLiYWHferv7Xl7f3t7f319gYKGg4GBgXvbdHx+e3ng0+V7eXp77+Td3+Dn5Obw8XuAgIWGgvXl5O15eXp6d+vs5dvk6N3idnV2e4KFfXiLnaKgopiNhYGCiImDgYB8goV/fH16fHx9eubOy8jL2Onpen7r4+7w6+3vfn9/fvDveuzh3dx4gISFh4mFfnvx9fN5dXh/gnx3eHl6eHd2d3h6e3p36nl+f4CBfHl8fXt3d3mAent67OjueXl9f3vm53l8foCCfefb4Od5fHt7fXt36Hd+gH5+fn9+fH+Cf39/goeEf31/f319fXx9fXl4fXt4eHp+fXl5deZ3ee7u8np+hYV9eHfqd3jr6eHg5Hh8fH5/f398e3+AfH5+f4GEfXd5dtve5+x4d3Z5d+jg4+x7enmAfH+Ag4J7fH2AgX9+eHt8en2AgYKGhYKBgoOEgX9/f4KBenx9f4KJioN/e3x9eHd449jazrOpxdXb5Hh9et7c6OrseoCBeXh6fIB7enbk5nuMhX6BfoCBg4GBgoODgHt+fX567eDi8+7rfoV+e3jb6OR/9el06OF5gYF3eHp9e+qA6eLUvdzs8Ovf2d3g4uR5eO/h9ejl6u7m7OryeXh25d3o7ufY19jm2s3SdHV0deR55OXlfn7i29jh6dbhe+3n5t7v8+rd3d/X1d3f39zb1s7Ry9zf59vN19nPwrbDyLq5vLy1qbu+pMLPzsTW5ci53ubji6ONf4GCg4GNhYR/fH2Aeel044aMjYmDfoF71dl4gomHhYN/cnmChYV8e3p2e3h4fPPu4+54e398enl34uB7hImGhoWAfIGLiYGCgISFf398fHt1cnd5eXx9gYqKiIiCfPT2ffb09vL0e3t77OXk5uvxgIHqeHl4e3p5eHyAhYeFgn5+fX1+fHp5gYqHg4OAbmtqbnJzdHRzcWxqaWptcHFzdnZxbGpqbd/c2Wttb25ramlqa2pqbG1sbGxtbmtpbG1vcXJvbm1wcnBraGdqaWpybG5oZ2lsaGNnam1rZ2hmZGRjaXB2d3R0cXJtbmxqar1lxsXR0Wxwc3Vzc3Z1dXFqy8lqb25vamxwaGlqa2iAa29wb25tamlsaWZobWzR09XV1GxwbWxpZ2ltcW9qZWRmwL7Eymlsamlpb3BubGtqbGlsa2llZ2dlxb1jaGtqa21tamtvcHBycnBwbGtqaWlnZGVobG5ubHBuamlta2tqaGZmbWloamxwc3N0c3BvcW5vcWpnaGprbHBzcW1tbc2Awclqa2xwb21oaWlpZmZpaWVqbG1wcG5ta2nMZ2VlY2PKyGlnaWlpa2xoa29tbWlsbm5tamllb3RwwbW2wsHRa8vHwsRlZWppbm5lZmpqbmtqaWdsaMvR0WtucXBwcW1sb29xcW5sa2e/Z21rZmjJvMlrbG1s0svGx8vOysrR0WqAb3N1ctfPz9VsbG9ua9XYzsTMzcTFY2BfX2FgWVJebHR4f314c25vdHZycm9pbG1qaWpoaWhqZ8Kuq6qsuM3Sb3DRytHS1NXScHBucN3bbc/FwMFnbXBzdnZybGnP1dhua2tvcGlkZmlraWlqaWloZ2ZjxWZpa29ybmxvcG5qaWiAaWpozMvTbGxvb27R02xqaGhrasW5u75kZmZnampozGhtbmxra21ram5wbGhlZ2xsaGZpa2pqa2ttbmllaGhlZGZqa2hoY8VobdrZ221vcnFraGfQbGvPzMXBxGhsbG5wcHFubG1qZ2tra21vaGJiX7W6xdFrZ2RmZszJztZwb26Ab29ub21maWxubG5vbG9wb3FxcXN1dHFxcXN2c29tbXJybGxtbm90dXFva2tpZmVmwrq6r5OGn7jCymhoaMjJ0s7IZWtnX2Bla25rambFwGRxbmlsam5wcG1tbm1tamdrbG5mvr7E1tbPa3Rua2q/xb1qy8NkzcRrb25oa2tsasyAysW6qMDJ0tHGx8XGyMpubNHK2MjDys7M2NTVaWhozL/M0c65ucHQyL23ZGZkZsxtzcrGb3PMxcHJ28rOcNvS08rd3dPJxsfCw8rM0dPU0MfIwtTU1sy+ztHDrZiyva2qq62mkJ2ijKWvr6Syv6OVtsHCc35uam5vcGpwa25raGhFYr1gtmlsa2hnZ2tmtLdianBwbWtpXmFkZGRhZmdjZWJiZMbHwMZhY2lnZmVkwL9pcHZ0dXRva3F6d3FzcW5qZmhpaWpohGIoaGtvdXVyc25ozM9ozs7T1NVqamrJw8THzM5sa8NoaWVnaGlpbG1tb4RwC29ubmxqanB1cm5umX+DfsV/An5/hH6Lf4J+mn+Ffo5/hH6Uf4J+xH+Dfph/AX6Ff4J+l3+GfgF/hH6Rf4N+kH8BfoV/g36Ef4p+hX+EfoV/iH6if4h+gn+HfoR/A35+f4R+iX+DfpN/AX6Qf4N+hX+CfoZ/hH6HfwF+p38Gfn9/fn5+h38Dfn9/hX6Vf4R+hX+EfrN/in6Df4V+i3+CfpR/hn6Ffwl+fn5/fn5/fn6If49+gn+LfoN/jH6Efwd+f35+fn9/h34Bf7V+j38Dfn9+iH+CfpR/hH6Hf4J+o38Dfn5/hX6Df4Z+A39/fpp/AgIEACKOjIqJhoeLkZqempKIh42KiYmJh4aIjJKXk4+KhYWFiIeJhIuAiIiKiouKhYWKkZWTjYmOk5GRlJWdnZqVjIqKi4eC6OLLrqm+7oWIi5CSh4aPmpeckZyOj4+DgIqDiYGC+vmBjJaUkZOTioGF//GEjIyGgYCBh4uEgYOB/YKGhoOFhYD49vv/g4T//YKFh4mJiIyMjYqGiYmFhoaDgoGBg4qOjIKAhYiDgomQlZKPkJSSi4eJjI6Qk5CMiIyQlJGLkJOMiIOBg4aJjYuHgf7/g4OIj4+D///9gPrx/oOCgYKDhYSBhYGChouPiYOEgf2BiYOBgYKGg4KCgoWGiImEgfv+gf6EiYiGh4eGhYGCgYKIkJSOioeD//b/goKBgoaDg4X+8/SA/4OBgYqOj4iFgoaJh4iJiIWHhP/x94CEi4eFgv+UlpKOkIyIg4aH+YKNlpaSkpyiju7w+4KAgICB9fiEkI6QkoWOk4iLkoX17OTc84CDiP3o397n8vX0hYmJh4qSkPrn7f2B+PqA+PiIhoSDgoGBgfqA/f3z7ufwg5aluMS9p5OAh4iLhYGFgfmCgPT+7/z49/LlzMTS3Obg3+Tn64CG/fmBgICCiYiGiIaFgoSDgoD/goSHiYeDhYOFhoWChYaBgP/49PyAgIGC+/qChIWGhoaKi4yMi4WBgYCBg4WHhYL89/Px8vHz+f7+/fn39/Hq8oSKhoH26O33goaGg4GBgYSAhoWDhYSBg4eFgfj5goaIhoSJkI6Hg4KEhIODhYOBgIGCgoKB//P4iI6KiouJiImMjImIhoeSlI6MjI2Jh4qMh4CEiIiJhYOFiYmI/uzu+v/3+Pv19P2BhIeIio2KiYmKjIiHi4mKjoyIj5WPiIyQjYmIhYmD+Pv7g4eKjYiHiYiAioeCgf6Dh4eG/frkuqOjv93p7fT+9e/1gYX3+oaMkYuDhomHhoeNhYOPgv6BgoGBgIGJh4iOgvuA/oT/gIaKg/6EiITyhIqC/4aC2/X18+75hPXr5f6ChID49ffx7P74gPX09/358PSB+Pf59fr+8vX5+Ob4/fX08/399v7z+4GA+4Lz4YD67uPb7ejx7/f6gPzo9vnr+PyA9+3vgvn65erv7Pr37u/q3/vq5+vd5+jk1+Xd2cnS28a6rK6zo6Gam6G3ws3c2Mi42YSZn6q4sZiVkJCNjpCKi46Nj4eIjZSdnZKMlJiTh4aKjZOZn5OKiYaAiZeYi4Du94H174GI/uVa/YmNkpKMj5mYlJmeoJaPi5CYl5OQkYmJkpSQjYqPlY2Hg4CFiI+QjI6Yk4uGgIGFh4qOjID5g46OhPnx7veFiYaChImJi4qJhoaMkJCRm6GamJuWkJOUjoSHgIB/f399fYCEi42KhX19gn99fX17eXl9goiGhIF+e3h4eH2BgYF/e3t9fn9/fHx/hYeFf3x/hIKDhoeNjYuGfXx9f3pzx8Crj46j0HV5e4GDeXt9iIWKf46Cg4N4d4F5fXZ16Oh3gImIhISFfHR66N15gH98eHd4fH55eHp46nh8gHx5fX167Oru8n+A9/N8f4GCgH+Bf357d3x/fn9/fHp5d3mBhYR7foB7d3p+hIF/gIODf3x+gYGAg4GAf4GChYN/hIaBfnt4enx9gYF9eO3qdnd8f3947u7tee3j7Xh3dnh7fn15fHt9gIOEf3x8e/R6fnd2d3l9enp7fX99f397gHns7Xjten19e3x8e3l2eHd4foiMg356eO/q8nx9fH2AfHx+9Ojo8Ht5eH+AgH18eX+Eg4SCfXt+gPPd43d6f3p5efGKioWChIF9eHt/73l/hIWAgYqSheTi63p4eHl65uV5goCBhHuEin+AhHrk3djS4nd5fu3f2Nfe5efmfYGBgH+DjIvz5u34e+vrd+rrf357eHd3eHjndOTk3NjN0XB/i5ynppeJgIKEf3x/e+t7eufu4u/r7OrgxrnDzNrZ2N3g43uA7eZ4eHh5gH99gH9/e3t6e3jwe36BgX57e3p7e3p4enx3duzn5O57fX586ed4ent9fn+Cg4SFg316enl6gHx+f3157Ojm5Obl5eru7+7p5ubk3uR6f3166dve5nh7e3h3d3h6fHx6fH16fH99d+boen+Bfnt/hoaBfnx+fHx7e3l3d3h5eXp79OjpfoR/fn16e3x/f3x6enyGiIOAgIF+fYCCf3l8fn6AfXp7fXx87uLm7u7i4ejn6O95e35/VICCf39+f4B8e359foKAfoKHg3+EiIWAf3yBfvDy8Ht9gIF+fX9/gX55ee56fHt44+DKnH99m8Ha4+r08ezufYDx736Dh4F6fX98enyCe3iGfPF7e4R3gH16en115nTqeeRxen967Hl8eNx2fXfqfHjJ4N3Y1OR759vY8Hp8eOfk5t/a6+Z33trk6Obe5Hvt6u7q7vLn5unq1ufv49vg7Off6d3jeO154Mx06t/Vzd/Y3Nvk5XXn0eDp3efseOjg5Hzt7Nfd39rm5t7b1M/w4t3j2eTm4NTkgN3YwsrTvbWorbGdlY2RmK+2u8jCrp/BeYmMl6SfioeDgXt8gHx+gICDfHp9hIuMg4CHh4R+fX+AhIqRhX59eHR8iouAeN3jduLdeH/r0OZ9gIKBfIGKiYSIjI6Ggn+Di4mDgH94eoSIhIKAg4Z/eXd1enyAf3x+hoJ/fXx9f3+AKIOBd+h5goJ55NzZ4Xh9fXx+gH9/fX16en+CgYGKj4mIjIeDhYaAeXuAbGtrbW1sbnN4eXNsZmhydXV1cmxoaGxyeXZ0cnBtaGVkaG5wcW9qaGtsbGxpaGtvdHRvamxubG5ydHl3dXFqamttaGKnnIZvboKlX2NlaGpkaGhsa3BoeG9rbmhqcmloZGTHx2RqdHNubWtmZWnJw2tsampoaGlramdqa2vQaWuAamlra2jJzNXYbm/Y1m1vcG5qam5vbmllaW5wcG9saWhpaG1vbmZqbmtlZ21xb2tqa2loaGtsbm9wbWpnaGpsa2htcW5saGZmZ2ZoaGhnzsxmZGlvcGrQ0dBs1s/UamhmZmhsbmloam5wc3dxbG1q1W10bGVlaW9ubm9tbW5zdXGAbdTVbNdubWpqa2tqamhoZmZscnVvbWxq0cvOaWhmaGtpam3QxsXRa2prbm5vbnBvcHFubmtjXGJqzcDOa2ltamlo0HJyb25wa2ZiZWjDZWlnYmBgZmxrx9DbcW9rZ2fBwmZsam1xa3N3bWxwaMjIwbnFZWVpxr29vsPIx8hucnF8b3J5edjN1eBv0NBrzcxwb21qaWtsaslkvraqo5mdVFxibHd7dnJvcnJubnJs0G9u09nJz8nLy76hlJ6mtba6wcbKbXHSy2loZ2dsbG1ycnFramhqZ85rb3Fxb2pnZWhra2hqbGpr1tDO1WppaWjMzGpqaGlqbHBxc3RzbYRqgGttbm1qz83Nzc/NzNDT0tDMztDKwcZqbmtoyL7Bx2ZoaGdnaGprbWxqa2lmZmloZL68Ymhramlsb25ramlqamxucXBtbGtsbnBx4tfUb29pZ2dnaWhoZ2doaGx3eXJubW5sbHFzbWZoamtta2hpbG5x1szO0Mq7uMDEytNsb3FxgHN1c3Fvbm9pZ2lqbnFvbG90cGxwdHJucG5va87PzGpsbW5qam1ucW1ra9NqamlmvrWhdFdVcZ3Cz9nf29XQamzNxmZrcG1pbW9sbm9yamhzbNVqamdpaWpuaWVoYsRn0WzHYmtwbdtvcGi3Y2tkwmlkrsW8tbXMbsu9vNZsamfHgMjMx7/Qz2zJw8fBw8TIacvNy8fJx8HGy8q6ytTHxMbLycjTyMhp1GjCwm3MwLmyv73FxcnLadC9ytbGytBr083OcNPQvcXGvMjIv7Ouu+TOyMu+0tLOw87IxrW8xbGspKamjn9ydXyMjZmonIt+nWJxcHJ2dGxzcnJram9rampogGtlYWBlamllZWtpZmRkZmdpbXNrZWhpZmx0c2plvcNitLBhacm0wGhra2pmanR2dHV4eHJvb3N5dnBsbWhpb3BvcXBxc2xoZ2drbnNybWxybWloaGtubWxubGbFZWtsacrEw8pscW9sbW9ubm1samltb21tdXh1dXh1c3Z3cGdpxH+Hfpd/gn6Kf4J+jX8Bfod/hH4Ef39+fsF/gn6Gfwd+fn5/fn5+kn8BfpF/BH5+f36Tf4N+iH+EfpJ/g36GfwF+in8Bfol/g36Ff4J+jH+FfoN/iH6Hf4R+Bn9+fn9+foh/An5/hn6PfwN+f3+SfgR/f35+j38BfpB/hH6Ef4J+lX+RfoR/hH6Sf4J+mH+DfqR/i36ff4N+jH8BfoR/j34Ef39+fo9/AX6LfwV+f35/foR/C35/f39+f39/fn9/hn4Bf4R+g3+HfgF/h34Bf5Z+Bn9+f35+f4p+AX+HfgV/fn5+f61+rn8Kfn5/fn5/f35+frR/AX6Ef4R+nH8CAgQAgIiEhYqNi4mKjpOYlYyJiYaGiIWB9/uEh4iKiYaChIiIiYiE/oKJiISAgYWLjYuFgoSDhIWHhYWIh4mOkJKSkpGNjYqJhejSurSw1PyJjIqRiImOjpGQjY6Ph42GiJSVj4mUlJCJiYmHg/yBh4yE/ICEh4OBgYODh4T/gIL//oGBgID8/Pr6gYiGg4H/+fb2+YCIjIaA8eL0g4OEhoaEh4iHg4GDjIj69fqBhIiE/4SNk5aTjYeLioWFhID8+4KWnJSSl5ePjI6QjYuNj5CSk5SVlZOUl46IhYOB7uHg8vmAgYKA//rx7O78i5WKgoOCgPX1+oKA/oGJh4WKjYSChIH/gISHhYCBg4aFg4iKjouKi5CSjIeEgYKDhIf+/ICA/P+Gi4qCgYSAgoT//4WCh4iLiIX3g/35/oCEiIaC/YCI//2BgICEipCNiIuQkJGQkpWXmJaVoLS4pIn27ff8/oSFgoOKjZSMhoeNioL6g4uB8+3n6vqBjYDd2NXQ4PeDh4uJe4iFjJKUjYeCgID39fr+gof8/IiE+/by5eHj7ff3+4CCgIGKmaSyuJ6Iio2GgP/49fDr6d7Jwbmnp6u1vszS0tTc6/Tz9fr/+f+GjoqHiY+Lj5SYkIiDhYSAgIH//Pb3/4WHg4KEhYWC/Pz8+fuA+/Dz9ff7/IGDgYGEhISBgIKCgYKFiIT9/YKA/IKFgoH9/oWCgoP8/4KFhoWHiYH5gYH+goaFgoH+/IGDgYSJiYeHiYiHhoD06+j7hoT79/6BgYGEgfv9gIKCg4SGi4yMioSCgoGChoiFhoSCh4mEhouKiIWDg4WKjYeFhYSEhIaMj5GNh4KBhYT+/YGA+/yBgIWBgYeHg4OEhIeJiYiJiIaGiIyJg4OKiIiLiomOk5GMiIT/goP8hYyOiIaKi4qJjYn48YD99vbp6Obj2+Hj7veIjImIjY6OkY2FhY6ShoL1gouGiP2Ih/79i4iBh4KAhIeLg4KH8eL0gf70/YyG9IH0gICAgYKE8vqE/e72hIWDgIb8+oCA/PXh7oD+/YSEgIT+7/j2/vb69+318f3384H+/IH/+fnz6+iBhvH78f/49urx+/fy6u35gYmC8OyDg/n6gPuBhfLyg/v7/f7+9fj45/D06vPp7tfU0tri4ujby7m9v7qnl7HX19fR0tbshp2jqqy0o5aSkpaWlY+Tj4uIgPf29OTd84SVpZuWj4mBgIeOnLCnn5OSj4qHg4KB/PH2gYWEi5KZkIWJk5aVkpKNjpOdnZyenZmYkYuTmJWSjYuLioaDg4uVk4aChoiFhISJkZiWkIaDgoOGh4qMiIOBgoD27PKBio2DgYCEioqKjIiGhoyQjZGVjIiFho2KhoeNAY6AfHl5fYB/foCEiIqIgH59ent7eXTe4Xh7e318eXV2eXp7enbld319eXd4e3+Bf3l2d3d3ent6en18fYKEhYSEgn+Afn13zbefnZy94Xt9fYV+f4J/gYB/goJ5gnt8hoaDfoeGg31+gH9563V9g3vpd3p8e3p6e3l9fPB3eOzreHeAdenr6+x6f39+fvnz7+/we4GEf3rj1ON5eHp8fn1/f398eXuEge7r7Xl8gHzve4GHiYWAen17eHd3duzpdoaLhYWJiYOBg4SBfYCDg4WGh4yPioWEf317eXff19Xh4XF0dnbq49zc4Ot+hX15fHt55+Xnd3Xpd359fH+DfXt7evGAfYB+eXl6e3t7fn+CgH9/g4aEgn15eXp8f+zodHTm7X6Cf3h4e3Z4euvse3l8fIF/fu598u3sd3t9e3bmdn7w6XZ2dnh8goB8gIWDg4KEh4iIh4WOmZyRf+ng6+/vfHx4eX+AhH97fYKAeOR4f3Xf3t/e53Z+d9TU08/b7nt9f32AfXqAhoqGgn98eurp7vF7f/Lvfnrs6une1tXb4d/ic3NwcHeCjpqfjH2Ag3979fDs6eTe0bu0rpydoqmxwMnN0tnh5uju9Pbt8HyBfnt9gX6AhImEf3x+fnt6e/Pw6uvvfH16eHp7e3np6uzr7njp2drg5+/zfH16eXt7enp6eXqAe3p8f4F98O97evB7fnt67+97eXp87u55e317fHx15Xd37np+fHl57+14eHZ5gIKBgoKAf3523NLM2HN14eTve319f3zy9Hp6ent7fIGDg4F+fHx7e36AfX19fH+BfH2CgX59fHt7gIN+e3t6enp7fn+Bfnp5eXt77Ox5eOzseHuAeXp/fnl6enl7fH18fHt4d3yBf3p7gX9+gIKBhouKhIF+9Hx87H2DhX58f4KCgIN/5+F37ubq5OXh29Xc2+bugIJ/foGBgIOCfXuDhHx34niAf4Dpenvo5n9+e4J7d3l7fHZ2eNXO4Xfs5u2AedZy4HZ3eXt9fNvfd+bd6X1/fHyA7el3d+fezdh37Op4eHd+9OLr6fDm7Ovg6ePt6OF78+136eHi3NXUdnzh593p4+ba3+fe29TW43iCfOHYeXrq6nbnd3rd2nnq6e7y8ebp59no7OLp4eTNyMnS2t/k18m4ubu2noqm0NHJvsHF1XWIjZOVnIyCgIOKi4iDhoJ+e+CA3tzS0eR5hpSLhoB7dXR6foiblYt/gH98e3l3deXZ23J1dXyEi4R7f4eJhYGBfYGHj4+NjY2Jh4B7goaFhIGBgX97eHiAiYZ4c3d6enp7f4WIhoF7e3t8fn5/gn97eXl35t7keYCBenx9f4KBgIB8enp+goCEhn15eHqAf3l5f4CAa2hoa25tbW5xdHVyamhqamppZWPCyGhpaWpqamhrbWxramjLanBwbWtsbm9vbGZkZ2lpaWppbHFzdHZ3dnNxb2xraWlkrJqFg4Oat2dpanBrbGxlaGhnbm5jaGVmbG1saW5tbWhsb29qyGVrcWrGaWxta2ttbWptbNZsa9HOaGmAadHU1NRtcW9tbdvZ19bYb3V2bmrJvsttb3JycnFxcG9sa2pva8nJzmlrb3Dcb3Bwb21saW1vbGpmYLi2XGhubW9zc29sbWxqZmdoanB1dXNxb3Fxbm5samrOycbMy2RjZGTNysHCx8trc25qa2xrz9DQaGTIaHBua2tsbG9wbteAbnFwbWxsb21rbW1wb29ubW5vcG1sbWxvcM/JZWbO1G5vbmtra2hqas3TbWlra29vb9Jqx8fLY2VqaWTKaGrIzWdjY2VobGxpbG1pamprbW9vamdtd3hwZszM0tTQaWppaW1ucWpmaG1tZ75iamXDwLm4x2ZrY7GvsbC5ym1ydHCAbGdtcnRxcnFwcd3a3d9xcs3IbGnMz8/FwL/Bw8HDYmBaVldfZnJ8c2pucG9u3drY0cfAtKCUjH56eYOPpLS7vcDDx83W3NnNz2tvbGprbmlpbHFua2psbm1tbtjUz87OaWlmZmlsbm3U0c/MzmnMw8nQ08/KZmlnZWhqamtqaWqAaWhqbXBtz85ra9Nsb21ry8JjZGpt0MplZmdmZ2hivWJky2tvb2xr09BpaGVobG1sbG1sa2tnw7WptmNmycvTbG1ucW/a3G5tamhoa29vbm9ub3BubG1vbW5ubG5va21xcG9vbmtrcHJta2tpaGlqb3JzcGxqaGppycxqaM7PaGqAaWltbWloaGdpbGxrbm1oZm1xcGtrcG1rbG1rbW9wb29v2W1szm1zdG5qbXBvbW9rxcRo0M7W09HMxLnBwcfPbGtqamxqa29wa2tydG1oyGpzdHPGZ2zTx2lpa3Juamhpa2dmaLy7zGrNxMttZrpjxGdnaWpsabrEas7L12tpZmqAzs1mZ8rHt8Js1sxoaGVv18PDw8q/yMnDzsfP0Mxs08xkw7/FxsPCaWrG0MbKwsS9wsfIxsLDzm13bru3amrKy2fMbHDHx3DQy9PX08bLzL7K0czWyM66uLi8ydPZxrSssbOlhnKNu72toauyu2RydXh5fXNqam5zdXVxc21paLtVuruysMNnb3ZxcWxoZmVnZ2t0cGtlaWpoaGZlYr+2tV1hYWdscW1pcHd1bmlqaWtudXZzc3V0dW9pcHRycW5vcXFubGxxd3VqZGZnZWVmaW9ycG1qbIRtJ3Bxb2xsbmvNxstrcXJrbG1wdHNxcW9vb3BwbXFyaWRma3FuaGhub5R/gn6NfwF+oX+Hfp1/AX6EfwF+in8Ifn9/fn5/f3+EfoV/hX6Ff4N+jn+DfoR/AX6Nf4J+nX+FfoR/hn6HfwZ+fn5/f36KfwF+mX8Gfn5/f35+iX+Cfod/BX5/fn5+hX8Ffn9/fn6Yf4V+jX8Efn9/f4V+g3+Gfo5/hH4Gf39+fn9/in6Pf5x+kn+Ffoh/hX4Bf4d+kX8Ffn5/f36Ef4J+hH+Cfod/BH5/f36Ff4J+jX+EfgV/f35+foV/gn6yfwZ+fn9/fn6jfwR+f39+i38Dfn5/jH6PfwF+hH8Ffn9/fn6Mfwx+fn5/fn5+f39+f36GfwZ+fn9+fn6EfwR+fn9/hH4Df35+hH+OfgR/fn5/hn6Cf45+EH9/f35+f39+fn9+f39+fn+mfpJ/hn6Xf4N+wX+Dfp1/AgIEACiNioiC+4GMlJGJhIaLkZKQioOBgICDiY+SkI6OiYWIjpGSi4eJiYiIhIoThYD8/4WKjYyJio2OiYaIjYyOkISSgI6LhoKA//qBgoWE+fSGjIiChpaSk4yLg4qPkImFiZGVkZOXk4uMlZSLioeFhYmKi4f/+oOIi4mHg/6AhYT++/v28vr9+/Px9/+Dgv+Ah4b/8uvl8oSIioaBgoeJhIGBgv7y6e75+vyDh4iHhoWIjIj46PKChoeHg4ODiY+LiIqNgIX/8O+AgoGAh4+PkpWNh4OEhIGChoaChIaGg/f+hIyNioyFgoeNioeIh4X+9viEhYD9hIyKgouNgPOA//2Cg4KChIOE/f+DhoiIhoiLhYaBgoCB/YCDg4KFi4WBg4WD/P+B/oOJhe7l7YGOjIyB5Pf9/euCho6HhYeJgvHr5/SBgISAkY6SkZWYl42Ph/z/gOj+mKm0ovzvhIH9gf/+/YOGiImIhIOCgICEiILf1dvn8v6A9+zg1dzxg4iRk4aDgoODg/vj7PXw6e/z/P/t8fv/hIOGhYWE//n28Ofq7eTo7/qCiJy3wsW5rqiTgO7r6d/O1N7azbGwwMnT3uHZ3/D3gP6B//nz9vyDgYKHiouNi4qPlJGQk5OOioeEhoaC/4GC//6AgoOB//r5//77goWGgfz7/YCDhPbu8OXq+YGC//yAgYOEhIWFg//3+fv8/oGFiImFhYiMjI2Pj4yJhYOCgP3/gYKBgoOGiIH7/4GBgv/39fPu5ubz/YCBgP37+vyAgID9/oGFhoiMiIeJioyQj4uJiIWDhIqOkIyMkI6KiYiHhv+BhoODhYmJiYyMiomIhoWFh4qGgYKEg4iIh4iHhYmMi4iCho2LhoeMi4iJiIiHhoaIiYuJhYGCiIeKhoP9/IOHg4aMiIaEh4eC+/j48fDp2uPs7fuIi42Fg4mJiYuHgIaPkoyKkZSL+f+Fi4+Mi4uSkYaCgIWIhYKIg////ISHgP6EioqEhYiCgfjy+YmIgID48erl7PCBhYOD+v+FgoL7gICE+/Dv6feBhYHx/oT49faHkYT2hPPy+Pbs9//w6/Lw8/T73Nr37vD/8vH6+vKB+f+GhoX58OrwiITs/4SEgP7x/oXt6d3e+ej1gO/b4PH04uro3vDp5ubl4NTEw8XCubfc6u7m5eL/gICWqqmtv6qdj5qSkJOTkI+KhIT57PLp24CPnZuVk5eUko6Ii5idmJ2hlpCPhPb97tjl+YmRlZeWk42OiYuSl5mcl56mnJqbl5GSlZWYmpePiIeLiIaNPZGIhomOlJWSkpGFgIOFhoiLkJaXkY2PkZKSkIuGg4aJhP38g4mNh//6gIOKkpKOiYSFiIqIiImNkJCSkJAkgH17d+h3gIaDfHh7f4aHhYB5eHd3eX2Bg4F/f3t4en+DhIB9hH+AgIGBgX567u16fYGAf3+Bgn98f4OBgoSGhYWFgX55dXbs53Z3envp4nyAfHZ6ioaHfn13foSFfXl8hImFhomEfYCHhn+AfHl5f3+Bf/Lvf4OEgX577nd7eero5+Hf6/Hw6Obv+X9883l/fe7i29XheXt9enZ4f4J+e3t78OLY2+WA6e17foB/fn1/gX3m2eJ4enx+eXh3fYN/e36Ce+/i4Xh4dnZ7gYGGioJ7eHp7eHh7e3d7gYJ95ux6fn17fnl5foJ/fn9+fvHp6Xp7duh5gH55g4V77H318Xx8enl7e3709n1/gIB9fYB6e3p7envye359fX2Aend7fXvs7nfrd3yAet/b4niBgYJ84e3v7th2fYN7ent+e+Xc2+R1d3SCgYaHiouIf4J96+x21uuKkpmO6+Z+eu158vDuen1/f39+fn14dnmAf9rQ1Nvj8Xrs5d/X3ep6e4GDeXd2d3d359Pb49/Z4ury8+Dj6+x6eX19fXvt5+Te1NbYzs7R13B3hpden6KeoKOTfufh3tTGzdfRwaaktb7J1dfR1uTo8Hrz7ujq7Xl4eX6BgIF/foKHhYWJiYWAfHp8fXrveXrx8np8fHrv6urz8+97foB+9/X2e35+5tze1Nzrenzy7nh5e4R8gHvw6unp6u96foCBfXx/goKCg4KBgH18fHvz9nx7eXp6fH546/F6e3vz7+/s49fW4eh2eHjt7O3wennu7Xh8fH2AfXx/f4GDg4B/f358fICCg4GBhYSCgX99fe94fHl5enx8fX+BgH9+fHt7fX16dnd5eX18e3t6eX6CgX11eH9+gHp7f399fnx7ent6fX6Afnt2eH5/g4B98e97fnp7f3x6eX1+eezs8Onm3dHc5N/ofX+AeXh/f3+AfHl/g4GEi4uB5uh6gISBgX+Cgnp2dXyBfnt/eOjs7Ht+d+x6foB8e355eeri6oCAeHjn5N7W2+B4fHt65+h6enrrdXV66t/YgNDjeHx64et76+Tjfoh+6Xrk6e7r4Onw5OHm5N/e5s7S6tra6+Lh5+Xadufre31959na4X983vB7ffHi7Xzf29LY8+Xue+XS1+ft3OTk3u/n4+Tf08e8v8G9sK7Y5+rh39rvd3aFk5OYppODd4WCgYOEgYB+eXnn3ubdz3iDjomEdoOGhIJ/enuEiYSHjIN/fnbb4dTAzuN9hIWGhoR+f3t+goaIjIiOlY+Oj4qGhoWEh4mJhH59gn58goR9e32Ch4iGiYuBfn9+foCAgoaGgX+Bg4OEhIF8e32AfPLzfoOFgPHseXyBhYWDgHx+gIF+fH2ChYWGhYSAbWpnY8FlcHd2cGxtbnFzc29qaWlqbHBzdG9raWdkZmpucnBvb25ubnFyc3JwbtjbcXR1c3Bwc3Nva21xb29xc3JxcG1qZ2VnzcdmZmhpzMhsbmljZnRxcmtrZmlvcGlnaW1xbm9ycGttcm9qbmtoaG9ucnTh3XN2d3Nva9BnaWiA0djb1M3Q0tTOy9DYbm3Xam1pw7q5u8ltb3BvbW5yc21oZWPBuba8xMTJbHBuaWZlZ2ppyMTNamlmZWFhYmhua2doamK+uLtlZmNjaGxsbm9pamtubmtscHFwcXRzb9LccHVzbnFtamxvbW1vbmzSzsxqamjPaWtpZm1vasxt2tSAa2tramxsb9nbcHFycm9vcGlqbW9vbdVtb3Bvb3Fta21ubNDQZsdmamrDvsJla2hraMfPzM/CY2RoZmhqa2fCvLrCZWlkbWxuc3h1cGVnZbu6Xqiza3R5b7zEbm3Wa9bV0Wlrb25nYmJiZGhtc223r7a/xtFpzcrFubzHZ2pzc2aAYWNmZWO4prjJyMLO1uPp19XX1m5ub25ubNTU0Mi9vsG5trKxWFxlcHV5eHp9dmrGw7+zoKm3sp+Bgpaksb2+trrJz9dt2dbU1dZsaWhsb29vaWlscG5tb3BubGhnam5s02lny85oa2toyMLG0NDLaGtubdLLyGZqa8K8wb7G0mxwbNHMaWxubmxramnMx8nIyMplZ2hpZWVna2xucHFvbmpoaGjR02tramtrbW9r0dNra2zW1NXUzsK8w8tpa2vW1tfYbWvR0m1wcHBycXFycHBycW9vcXJwbXBzdHFxdHNwbm1sbNFpa2lnaWxtb3N0cYRwgG1tbWpnaGpoaWhoampnam5ua2RmbG1rbXFwbGtqa2xsbG1sbm5rZmVrbW9ubdHQamtoaWtqamlqaWjR0tbQzcG4xMnI1nBvbmdpbWxsbWtnamxscHd4b8THa3BxcG5rbm9pZ2ZqbGtpamTJ1NNtcGfIamxua2tva2rKx8hnZ2VogM3KzcnKy2ttaWjJymhpatBoaWzQw721xWptab3JbMu/w294bchnu8jMxsDBxsfLzcrBvcizs8zAtsG9wcvIvWfMzmtvcdHFxc11ccrVbG7ZzNVzycC8w+DO1m7Nv8PR18zR0M/i2tfX0cW2r7Kzr52Vu9Lc0cfF2WtpcXl2dn1vZWlibWloampqaGVjZsC3wLuwZW51cm5sb25vb2hlaWtnam5rZ2Zfs7qrj5KhX2ptb3BwbW9tbnFzdHd0eH14eHl2c3JxcHN2dXJvb3JvbXF0bmxsbG5vbm9xa2prbG1wcXFzcm1rhGwjbWtpaG1yb9fWbnFzbtLNaW5zdnRwa2lrb3Fwbm9zdXV0cnGEfwF+qX+Cfph/gn6Ef4J+pH+CfoZ/BH5/f3+MfgZ/f35/f3+Ffox/h36Jf4N+jn+Dfpd/gn6Ofwd+fn5/f39+h38Efn9+fod/gn6NfwF+i38Kfn5/fn9/f35+foV/hX6If4R+jX8Ffn5/fn6Efwl+fn9/fn9+fn6Nf4Z+AX+Gfop/jn6Gf4t+i3+VfgF/hX6WfwV+f39+foR/hn6EfwZ+fn5/f3+GfgR/f35+iH+GfpJ/gn6IfwV+fn9/f4l+g3+EfgR/f35+nn8Bfrx/gn6Lf4t+kn+CfpF/B35+fn9/f36If4N+hH+GfoR/CX5+f39/fn9/f4V+Dn9/f35+f35+fn9/f35/mX4Gf35+f39/hH4Kf39+fn9/fn5+f4d+AX+dfpR/hX6Vf4Z+xH+CfoR/gn6UfwICBACAhYSEi5CLiYuLiYmKi42NiYmKi4mD/4L37YGGiIiGgoGB/f2Fh4X99PyFh4WEgoKEhYOHjYqHhoeIiYuMi4+TlJKPjI6Oi4iKi4iIh4yTjpKTlJaNiIuOg4eOjpKWk46Li42HhoOChYmEhYKB/4GFi4iGhv3ygIiGg4GAhIeIhICA/P6CgoGCgoD+/Pj99+7w8vH3/vDt9f/7+4OHhoiFgvTp6u35hIaIi4qLkJCPj5CUlo6JiYeFhYeJhoKAhY6Si4eHi4eFh4OIkIyGh4mRmZiWj4iDgoKDg4OBhYL78uzt9IOFhoaCgID5+oiJ+/yEgoGGiouJhID5+4L89PyBgf8GgoGBgP35hICA/feAhYaDgoH/gIKCgP79/v2AgYGGhYSF//qHi5Kch/H/gYD48/3/8+r7+ISDiI6Oi/6Cg/+Ag4WDiYKCgYOGhYWJkpKMhfj7hIeSmZuqpIPn2u73gYSCg4iIhI2Tl4mBgPXx393l4vKFj4Ty6uPi29fygYSDgPD48PuCiIuQiv6A+YH9/f/9/P6Bg4SHifv9gPv36+bm7O/r7fL49fP8h4+aq7GgkIP98uvl0sjU2s+3ssXZ6Ofn5+329vHm6vT++Ofr+4H69Pj++vf/h4yNjpCQkpSSkpGKhYiGhIWEgoKBgICBhIaIhoSDgYGBhomC/ID/9/mEiISChYmMkZSQi4iAiImMhvny+vr7gIOFg/z2+f3/+vL3gISC/oGCgPuEi4WCioyJiIeIiIeGhIOHi4yMjY6Mi4mIhYWHgoOIi4aA/oD9gIKCho2Og/Tp7ff19vqAg4SChISBhYmKh4eHiYiHiYqIhoeIh4WFiYuLkJKNiYaGhICGi4mNi4qJjJOUioOAh4mLiYeEgoSEhYmMh4OAg4SHhYOEhYiGgvv2hoT+gISFiIeA/YOEhIKChIyNgoOIiouKjYuKj5GGgIGPjYX7hIWHioSChIeJgfuEgPXy8vSDiYH394Dv+oyL+YCCgYOAgIKHhfnu/YOB//7v6PH7goWC7ef2goSGgf7/goSBgoKA9vjx/4Dx8fHl/IeIh/z36Ofl6/7u9/n3g/qA6+b67vOG/vrz+4KEgvyB+/eAg4P6gIKB/YHy7ICLiPmCgvDh/f3++fb1guTn/fv16ubo8On48+Hi3trS183NyMDU5u3r8PfzhaGmrbXByLWpp56Zn5eSj46KiouFhYH59oSNl5l2l5OPhY2UjZCanpOXk4yFjov///36h4+PnK+6sqCfnpmbmZGTlZqmraKcm5iWlpiXlZKOj4qDgoP8hZGSj46RjoX/iYuEg4H2h5eRjYiGiIuPjoqLjouCgYKGiImMi4uMh4OB//v69+3n8/6BhYT89O3g7/7/g4B5dnd/hYF9fn16ent9gH96eXl5d3LidN3VdXp7e3p4eHns7Ht+fO7m7Hx9enl4d3l6eXyBf318fHt9gYOChYiIhoOAgIB9e31+e3t6foWAhYaGh395fYN4e4GChImHgX+Bg318enl6fXl6eXnueX2BgH5+8uh6gXx3dXV5fX56dWHn6nl7fH18evPw6u7s6Oro4+fs4eDn8Orre358fXx65dvZ2+Z5en2Afn6CgoKBgoWHgX6Af319f4B9eHd7gYN+e3t+e3t8en2Dfnl4eoGGhoR/e3d3d3l4eHh/fvLq5OXphHmAdnV15eh/gOrpenl5fYCBgHx36Ox78Ojwe3v2fXt7efT6gn99eu3ld31+e3h373l7e3v19Pb0e3l5fX19fvPtfH6BiHrl9Hp57/D49eji8u59eX6Dgn/seHnrdHd5dnt2d3d6fHl4e4GBf33q6np9hoqIj4x43dXl6Xl7eXp+fXmAgISIgHt56OXa2+Hc53yEfurj4OLe2Ot4eXl44+ng6nqAgoaB7eh68/j8+/f0enp6fH7q7nnu6t3X1tnXzs/W29jV3XZ8g5GYjoR55t3Z1cO7y9HDrKq+0uHh4N/k7Ovm2t7m7ebY2+t68Ort8u/s8X6CgoKEhYeIhYSDf3t+fXyAfXx6e3t7fH5/f399fX18fHp+f3nqdung5HuBfXp7fn+ChYJ9enl6fXjg2uLm6nd6fX307u/w8Ozn7HyAfvR6fHnte4B8eoCCgH9/gYGCgoB9fn+AgYOFhIOCgX5+f3t7foF+evR78Xl6en2DhHzr5er08vP2fX9+fH19en2BgX+AfoCDg4GDg4F9fX17eXl8f3+EhYJ/fHx4c3h9fH99fHt+hIR8eHx+f39/enh5eXd7f3x5eH1/gX98e3yAfnrp3nh343R8fX+Be/R/gH99enl/gXd3fX5/fYF/foKEe3Z5iIiA7Xt+gYV9eXp9fnbkd3bl5OXofIN86uV33+aAgOWAdnl5e3h4eXx76d3pdnTr7uPY4u16fHnb0+J3eX156+l3end5eNvW1Od23tzj4PR/f37w6NXV1tno2+Xo6oD0et3a7OLrgfLr5ut6e3bjdujoeHp67Hp9ffZ74dt5gn3le3zm1+vn7Oro6HvZ2+/y7+Ld4+rj8O3b2dbVzNDMy8CAscDV397n7ut8jpGXnaaolYuPi4mNh4KAgH5+f31+eu7leH6GhoWBfnV8g36Ah4h8f3x5dnt329rY13V9foiVnpmJiImGh4aBgoSJlJqQioeEhYSFhYWEgoN/e3p55niBgoCAg4F66n+BfXx653yIgX99e31/goJ/gYSCe3p5fH4bgIF/gIOAfnvy7Orm29fk7nl+fuvc08vb6ux5gHBtbHBybWlqa2ttbm1ubm1qZ2VkZNBu0sJmZ2hoaGdpbdjXb3Fv1c3SbW1ra2prbW9tcHZ1dHNxb3Byc3J1d3Z0cnBvbWpoampnaGdrcGxwcnBwaWNnbmRma2tscXFta21wbW1ta2hnZ2loZ8toa3BwcHHc1W5xbWhmZ2xvbmppgNLWcHJycnFw4uHZ18/GyM3M0NXMytHY0tBucnBtaWS7tbi9xmdmZ2tsa21sa2prbG5ramtrampra2lmZGdub2pnZ2hlZGZlZ2xpZGNnbnBubm1raWlpa2tqanFx4NzU0M9tbG1taWZlxclwc9bVcG9scXV2dW9pyctpzMbOa2zUN2tqamnS1m9tbW7d2G5ubWtrbt5vcG1q2Nvd221ubnFxb2/Z0mxtb3Zty9JsbM7K0NPNyNPPbGmEaoDDY2G9YmRkZWxpa2trbGtpaGlnY2G3u2NlaW1scW9ivbvLzmtta2tub2pram1nY2TDycK9vLjHbHBmv8PFysS8zWhmZWW9xMPLZ2ttcnDV1G7X3OXp5d9vcG9wc9fWac7Mwru5vb+8vb7Auq+xXV9ha3FrY125tbGtm4+eqaiXl4CswM3My8fHzs/Lwcra6OTW1+Bw2NPV2tbNzWlrbGxramtsa21ubWtvbW1ta2prbGtqa2xtbm1sa2ttbXBzbtds08rMbHBsaWpra2xta2djYWJmZL23vcDEZGhrbNXR0dHSz8vRa21q0Wprac1rcGtmaWtqa21vcXFxb21ub29vcYBzcXBvbmxucGxsbnFua9Zs1mtqZ2lvc2/Y0dHY2tzcb3BxcG9tam1yc3JwcHNyb3BvbWprbGpoaW5ycnZ2c29sa2diZ2llaGhnZmpvcGlnamttbWxnZmhpaGtubGppbG5wb2xsa25saMW+aGfDZWxramxo029xcW5pZWhpZGhrbIBwb25qaW9ybGlsdHJrz2ttcXVwamdpa2bHaGfHxcjHaW5qzMtpxMhvbcVmaWpraGhoa2rIwc5kY8zRz8rNzWpubMXAxmVla2nIxmRoZ2lpwcPF1GrIxdTO3XNyb9DHubm3tcfCxcnLbM5qzMjWy9J029TK025vas1my85rb3Heb4Btbc9oxL5mbGnHbXDRw9zY09HS1nC/wM7S0cvHz9fS4d/MycjGuLi5v7Khs8fRzdTa02x0cHJxdnxxbnRxbnFsampsa2pram1qzchoa3BwbmpoYmhtaGltbGJjYV5cY2GtqqamXWhqcXuBfXFxdnd4dG5vcHN8f3l0c3FxcXJzcUVvbGxqaGloyGhxcW9wc3FqznFyamdjvWhzb2xramtsbWxpa25taWhna21xcm9vcG5ubtrW1dHBuMXSbHJz2si5rb7R2G+VfwR+f35+iH8Ifn5/f39+fn7DfwF+hn+Cfot/gn6Gf5F+hn+Ffrh/hX6HfwZ+fn9/fn6Jfwl+fn9+fn5/f36Ef4J+hH+CfoZ/AX6Ef4R+h3+CfoV/BH5+f3+IfoZ/BH5/f36Rf4J+iH+Efo1/h36Df4d+hH+EfoV/A35+f4Z+hX8Dfn5/jn6If51+AX+HfqR/BX5/fn5+kH+FfoR/iH4If39/fn9/f36ifwN+f36Hf4d+yX8Ffn5/f36GfwF+mX8Bfop/A35/f4R+C39/f35+f35+f39+iX8Ffn5+f3+GfgZ/f39+fn6Ef4J+hX+EfgF/hX6Df4t+A39+f4V+AX+Efhh/f39+f35+f39/fn9/f35/fn5/f39+f3+IfgF/nX6Xf4J+lX+EfqN/AX6IfwF+hX8Bfpt/iH6Df4d+AX8CAgQAgIyLiISBgYSHiYuLhoaLi4mKioiKjYuLioiHh4mOj4qB+f6AgoaHiImIhoSEhYWGhoOC//X3hY2NjIuGgP+EiYyNjImIhIGDhIaJioiLioqPi4aJhYKA/4GCjI+Ih4iMjYmIh4aIg/yAg/73/f37gIP+9Pn38veDhYDy/YeGgYGCgP3z/IL/8u38gYWGgf6Hi4yKhP7z8O7xgouI//X27+vp8fLv6+vx+PXs4/OFiISEhPzs4e34/oL/+vf9g4ySmJaPi42PiIeOjIWGiIGEjYmIkJORk5SOhYSGjIuHhoaHiI+Tk5KQl5eI//aAgv+DhYKFh4OC/viBhYaCgoSEhoT+Ev37gPn5+vz9gPX5goOEgoGAgoSBgISEhYSCiYSEiI2KhYSKmqCdkubp8Pnu4OTt+4GAhPHs84GChoSBiJKWlI2Bg4WCgPuBh4GDkJqXlZSJgoKEiIuNmqehhvv6go2IhoT/9veDhoeFiID4hYDm5er8gYP+9Ori3d3f/P718PL6h46PkYaBiIyHgfzy8PHz/IKCgISFgIH69fqBgPvv6/WCiYqIgvbx+v77gYOImrS0oYj06eDKw9Piz7O51tvd4urq5OPo94D++fv8+e/j3Nvt/oH88+31/oGDhIWGg4SIjI+Rivv8g4WHiYWGiIWBgYKB/Pn48Onp7/Ds7PSAhIeGhID/gYeJh4qPi4L39/v29YGHhoD7gICDg4OCgoOFg4CCg+nc84KBgv717Onw9vX3/oGBgICBgf349/f3/f/7/IGGi4qFgv/6/P39/vz9gIOB+/2DiIaHiIaFh4mGg4iQkImDhYqIh4uLhYKFiYuNjIuKh4SFh4mD+/j5/PqAh4qIhob/+oSIh4iD+fT8g4mHgoKHj5GLgIqPjYiKhuzj9YKGgPr+g4eIjI6NioiE///4/IKKlp+TgPj3+P+Dh4aAgP+CgoCAhPz6/4KB8fSDgIGFhoeJgvn8/Pv3/vn5/oT57+iFhIOEgfX+gv7+hYP+g/2FioLv+/ne3O73+vX89oCDgoD8+PmChPz0/YGGgf6GhIOFgP/6gPz78PuA7uL5+PyCiPnT6eLP+/Drgv/+h4WHg/SAgP309O3w9oL87vLv4O6BgO/Z/P/6goPr4/D9+fLm7Pb57/D5+fHY3ff44OLh29/m5uLX1dXZ4+nx+oKPkaSyxtzDppecmJCXjZOhlYeA+oGE+9zY7vmJkYaCkZmRg/L1gPfgatnugf+Ah4qVoa+Yio6apJ+VkJCPi4+UlpebmpeRiomOkIyKiIuLj5aVjob88vWBjIuNko6SlIqIjImChIuOjJGOhYSEh4qJioyPj46RlJOWl5iZm5SA7YOG/Pf/ho2Qj5COiYaHi4+Qj4yAgX98enl4eXp7fn56eX5+fHx7ent8enl5eXp9gIOEgHjo7nh6fH1+fn17eXp6e3t6eHfr4eJ6gYB/fXp17Xt+f4B/fHt3dHd4en1/fH59fYJ+e357eHXqd3eAg318foKDgH99fX567Hh77OXt7/B5e+zj5uXi5nh5c9nlfHx4d3iA6uTxffTn4e56foB88X5/f3546uLf2Nhze3vr6O3p5ODk4t3a2eHp6OLa5nt8eHh56t3V3ujuee/s7O55f4OHhX99gYJ9fIJ+dnd5dXl/fHt/gX+Cg354eHuCgX18fHx/hIeFgoCGhnzr5HZ363l7enx+fHru6Xl9fnt7fX+Bf/YR8/F77/b9/fd76+57fH17enqEe4B8fX5+fXuCfXx+goB9fH+IioiD2d7k7ubf5uvweXl+6uTneHd3dHJ3gIWDf3Z4e3t563h+dneDjIqJiX94eHl7fH6Ik4977O16gn17evHq7Xt8fHp9duN5d9rY2+p3ee7p4t3b29rx8urk5vCCh4aIgH2FiYN76+Pi4+j1fn16e4B7eOnk6Hl67eLd5Hd7enh03dfc4NxydHeCmJqMeeDg38m/z93IqrHP19jb4uTe3eDte/Xv7+7r4djT0+P0ffz68vP2e3t7fHx6e3+Ch4iC7ex7fX+Bf4CCgHt7eXfq6eni29ng4uDh6Xp/gX99evJ6fX57fYF/eerr7ujmeH19dx7od3p7enp7fX9+e36A5tnufHl68Onj4ebs7O71fHyEewHwhO2A8vTw8Hp9gYF8eu7q7O3v8e/tdXZ25+1+hoWEhoWEhYaCfoGHhYB7fYSDgoaGf3x+gYOEg4F/fHh5fH577+7t7+x5fn98ennn5Hl9fX166ufvfICAfXyBh4mEhYmHgIGA4trsfX925ep5fX2BhISCgn719fD2f4OJjoN05eLf53mAf395eOx6fHt6fO7o63l66ex9eHd5eHd5c+Lp7O3t8uvp7Hni1dJ8f39/fO73ffPue3rteeh5gHvn8u3S1OTr6uXr5Hp/fHfn4eF4e+3n63Z7eu99fn+AevDr7vDj53bVyOHk6nqG9MXYy7nr6eN89PODgX964HR17OTo49/id+yA4+vm1OJ7ed7L7fLufHzg1+Lx7ufb4Ors5OTq6OLT3uzn0dnb1Nfi493QzcnO2eLq8XyDgZCcqrilkISIhH+GfoSPhn157Hl76szH2uN8g3l4ho2EeN3hdufTy9p15XF2eIGMl4Z7gIqSjoaEh4aChIeIio+Nh4J8fYKDgH58gIBEg4mHgHjf19tzfHt+g3+Bgnt8goB6en5/fYKAeXh4fIGChYaHhYWKjYuKiYeHhYB03nx/7OTnd32AgYOEgoCBg4SEgoGAcnJwbWtpaWtsb3Fvb3Fva2ppZWVnZ2lqamprbG9xbmjJzmhrbnBwcG5qZ2hqbXBwbWrRz9RxdHJwcG1r23J1dHFuampnZWdmZWZqam1samxqaGpoZWTKaWxydGppa29wb29tbG1qzmhpysrU1ddsbdTMz8/KzW1vbNLbcnBtbG2A1c/YceDV0d1xcnJu021vcG9r08zJwLpgZWXHyM/MyMXP0cvEvbzDx8a+w2dqaWps0s3K0NHOZ8rLz9FnaWlpZmJhZmpoaW5rZmZnYmVsa2lra2pvcG5oZGJnbW9wb25tc3d1dXV3dm7Qy2xv2nByb29zcnHe121vb21tcHJzcNiA2dVr0dTV0tRs0dhwb21rbG1ubm9wcHBubm9wdHBvcXVzcHByeHl2ccbT1NrQxsjL1W1tb8jK1W5saGBcYmhrbGpjZmhpac5obGdobW5ramtlYWJkZWZmbHNyZcnKZWloZ2O+vsVqbGxpa2GzX1+vs77KZWbEv76/wcG9ztPX2tiA2XN2dHRtaW1wbmvV0M7M0d9ycG1vcG3Ry81ra9HHw8VmamtqZr+3trWzXFtbYnFyamCzsbGhmaq+spuguL29vcTHwsHG023U0dje3tbLwbzJ1m3c2NHS1mxramppZ2hqbG5vab2+ZWlrbGprbGtmZmhp0tDOyMG+wMHCxMtpbG6Abmxq1Gpra2lqbmtnyMvQycVma2tnzGhqamhmZGZpamlrbMO4y2tqbNXMxcPKz8/T2m9vbWxsbNHLycfJ0dbU02tvcnFsac3IycrM0c/NZGZnztJsb21ucXJwcXFua21xb2tobHR1c3V1bmlpbG9xcW9vbWxtbm5rzsvIycdmaWuAaWZlwsFna2xta8vHzGlubmxrb3R1cXN3dW5wb8O8ymptZsnPa29xdHR1dHFt19jT23FubG9oW7a5wc1pbG5qa9dramlpbNDR1mtrz9JuampraGdnY8vOycfK0czN0GvLwr9tbnBwbcrTa83ObGrQZsBncG/W2sWywM3QyMPIx22Acm9oxL3Da23VzstjbGrRcXFydG7Qx8vQwsVmvrXFw85weMimyMCw18/Das7ObWpsbcZkZ9TQ1svFwWPJxcnEs8Bpasiyz9bVcHDHusPN0tbOztLPxMPLzci1u83Nvs3RyMfS1Mu6vsPFzM7R1GtsaXd+hZCFeGxubmtxam52cWt8atBrbtO8s77BZmljY292b2fHyWfFsKezXrJYXFxhZ3JrZmx2fHhxcHJ0c3R2dnZ5eHVybm1wcW5ta2xrb3Rxa2W8srVfZWRmaWdqa2Vob2tjY2pubXJzbWlnaWttcHJ0c3J2eXh5eHd1cWpfumxwzsbIZ2ttbnJ0dHJycYRwoH+CfpB/g36HfwF+mX8Bfo9/A35/f4V+gn+GfgV/f39+foV/BH5+fn+EfoR/AX6Ff4V+g3+RfoV/hn4Bf4R+rX8Ffn5/f36Hf4J+iX8Efn5+f4V+A39+fp1/iX4Gf39/fn5+j38BfpR/gn6Ff4N+hn8Dfn9/hH6Cf41+in+GfoZ/BX5+fn9/hH6Ff4V+iH+UfgF/i34Bf4V+jH+Cfox/i36GfwF+iH+FfoR/AX6MfwZ+fn5/f3+JfoZ/iX6Gf4h+BX9/f35+pX+FfoZ/gn6Ff4N+j38Ifn5+f39/fn6Jf4R+hn+EfoV/AX6Ffwd+fn5/f35+iH+JfgR/fn5+hX8Nfn5/fn5/f35/fn9/f4t+hH8Mfn5+f39+fn5/f39+hX+GfgF/hX6Cf4h+A39+foR/A35/f4Z+AX+GfoJ/hX6Cf6N+lH8Dfn9/hX6IfwN+fn+EfgJ/fqd/g36ofwZ+f39+fn6OfwICBACAiISJkpSRjIeFhYKAg4eFg4WGg/7/hIyQi4aB/f6Bg4WJi4uLiYeIh4D8hIiHhISJiYWGiouKhoSCgoSEhIWHiIiIiYiHiISCg4eHhYqKhoL8/f/+hIWCg4WDg4SCgIGBgYaB/oCCg4aEgoH/+PPz/4D++/37+4OF//Xt6/aBgoCA/Pr19/2FiIWEgfz+/oKGgoCDg/v1/4WE+/X29fuAgIKEhIKAgYWIhYSMj4mChYeGg/z9/fvx7vb5/PyAgYKLjImGh4Ty4+7s8v+GhID8hpKXkYqIg4CCh5CKgf2EhoOIioqMiYyOgoWEgvzz+ICBgoOFhoD6+P2BgPf19vuA//+AgoKDgoSIhoOB8+jo6euBiYiIhISFgoWEhYWFioqLkJKRj5CTkYmAhYb38u3m5ujv9YGA/ezygoH+houHg4qMh4aKjImNj5CMkJKTk4+OioySlJyOiIeDgYyZmY2Bh4WB/4D89/2AhIeIiYqKiIaDgIGAgoL77enm5OPxgoeD9/CA8fuB7+bu7P6HjZKJgPv1/oD5/IKEhYSA/v6A7uHXzuf+9/qAg4aDgoH79v2Dg4SOoqmdhuvd2dLc5+LQztfY3ent5ujr6+7y9Prz7vT6+/79/YCCg4OHiIeFhIeGg/z6/ff4gomOjoqKiYeHh4WEhYmJhIGD/fj18fuCgPv28vKA+IGCgoKDhYiJjY+OjoyJhYCBhIaIiIaCg4OA+Pn+gP718/+CgoOFhoWFhoSA/P+EhoSDhIWHhoH/gYKEhoiC+viAhYP68fH19/j8+fHt7u7x8u/t7/L1+4CBgoODipCNjIuKioiEg4iJh4iDgomPj4mFg4OGiImMjIeD/ffz+feA8/f17/L3/IKBgISIhICBg4WC/P/++fX3g4SGhYWFg4P7/YmLi46RjoiHgvj2+vz6+Pn/gYKGhoWKhPP3+f2CgfPo84OEgYKFioyCgIGAgfn1goT9+ISFh4WJiof394GB/oeI8IKIg4WA//6ChP6A/fyGgoCFg/zm/IaHhvT29vyA+/Xx+YOEgf6C+P2Hgvfv9vaA7vjx9/rw9PLy4/mD9vaLh4T3z/CHhvjwg4OB9/aJiYP//ISCgPvzgoP0/P3x6/SDgPrv7efxgO7x7/KA//bw6/b3/Pj4+vv17/D14+Xv2+Pr7u/u3tXk6N7h5+L59/WJk4iPkouGgoODgYCFg4aAiIX+9/j19uji8PqBjZSRjPvn4dvVyriyu9DihKWvpp+ZjIyTnKGRiomNjpCQi4+Uk4uPkJCHgoaKjIiCgoP45vSD/vmAhImOj4qLjouHhYmIhIOHh4aFhIiIhYP5+YWFg4SGio2MiIWGiYqOmKGbjImLiIeJiIeHiYmKjImEh4uAfHqAh4eEf3p5eHZ2en19e318eevseoGCfXp36eh2d3l8fX5+fXt7enPjd3t7eXh9fXt7fX17eXh2d3l5enx8fHp5enp6fXp4eHx7eX19eXbl5unre3x6fH16eHl3d3h4eH1573l7e3x6eHjw6ubl7nfr6Oro6Xt87OHZ2eZ5eniA7Ozn5+x8f318eezs7Hl+end5eOPb43h46+rt7vR8e3x9fXp4d3t9enl/gXx3e319eefm5ujj4+rq6ed1dnZ+gH99fXrj2ODX3Ol6eHXpeoGEf3x8d3R2eoJ+eOt6fHqAg4KCfn+BeXx7eOfh53l6e31/fnfp5u16eOjo6vB79vdofXx8fH+DgH176OHg3915gIB/fHx8en59fX18gYCChoeHhISFg3t1e33n5OXg3uDn6np57uHlenjte397d35/e3yBhYSIiYWBhYeIioeGgoaNi4+BfHx4dX+Li4B1enl37Xjt6/B4en2EfoB9fXp3eHd5eOng3dzb3+5+gn7u5+fwe+Xd5eX1gYeKgXfn5O146+x6fH17dujpddzSycDV6eLmd3p8eXd25uDldXR1foyRh3XXz8/K093ZyMbNztHd4tzf4+To7e3x6ubv9vb28vB5e3x+gYKBfnx+fnzw8fb09X6Cg4J+fn9+fhZ+fXx9gYF7eHnr5uPe5XZ26ebk4+l5hHqAfH5/goSDhISBfnp6fH1/gH56enp57O70e/bx8Pd9fHx9fnt6e3p36u57fXx9f4CBgHvyent9f4F88O56f37w5eTo7O/08url5eXo6ejl5ufp7nl8fX9/hYmGhYOCgYB8fIGDgYJ9fIKIh4J/fXx+f36BgH178Orm6+jl6Onm6u2A8Ht6eHt+end4eXt57fLz8u3re3x+fX5/f33t7H5/f4GEgn18eejn7fHw8PDzenp6eXd6dt/r8vd+eeLa4Xh5d3h6f4J6eHp5eebhdnfl3nd7f36Af33m5Xd2532B5Hh9eXx47et1d+d16OZ8fHp+fe/Y63t8fOPk5Orp4NznenuAd+x56Oh6dNvU3uR54Ojj6vHo5t7bzuN34OSCgX3hs9J8furhfHx56OqDhHzu6359eu3jdXTV4Ovi2994duPf4Nnofenq6Ol69e3m4urt8e3t8fLy8Orp2d/q1Nzn7OvizsjX1MvW3tfq5eN/hnp9f319enl5d3Z7ent8eu3n6up67N7V3+V1gIeFgObV09PQxbCmrLzJdJCXj4qEenl9hIp/e3uAgYF/e4CFhH2Cg4J6d32DhIB6envo1uF36eV1eX6Cgnx8fnt4dnl4dnZ6fHx8e39/fXvt7399ent+gYOCfXt8f4GDi5GLfHh6eHp+gH+AgoKDhIB5eX2AaGZrcnJvamVlZmdpbW9ua2tqZ8fGaHF0cWxmxcdmZ2hqa21ubm1ta2XHaWxsaWdqa2prbW9tamlpamxtbm9wcG5tbWxsb21sbXFxbW5taWTDxcrObG1rbG5qaGdnaGpqam1q0WlrbG1sa2rW1tXV3G3SztDO0G9w2dHLx89sbWqAzdDW3N9ydHJxbtbSzGhsa2lpZ8S+xWhpzc7R0tVqamtra2dkZWlraWdpa2poa21tbdjX0MnEydHOyshkY2BkZmdlZ2nKwsi9u8NmZWXNam5wbWxsaGVkZmxsaMxqa2pvdHNyb29ybW1sbNXR1W5vcHFycm3X1tpvbdPU19pu2NiAbWtrbXJ3cm5tzcXBu7dncHBwbW5vampsbm9vcnFxdHZ1dXh5dWtjaHHc2dXMx8jO0W1t1cXHa23WbW5qZWpsampsbm1vbWtqbnBwcWxqZmdqZ2hkZWZkYWhwb2pkamhnzmjLys9maG1vbmxraGpoZWloaGXCvMTL0dTXbW5s1NGAz9dv0MfHwM5rb3JsaM/M02zY33Fxb2xoztFqycGzqLzKw8hpbW5qaGfEurldXFxha29nWJ+dpKu5wLalqre7vsbHwcbNz9DQ0NjX19zb1dPR1m9vbm5wb25sbG9wb9fZ3tzccHN0c3BvbWtrbGtrbGxqZWRlxsTBvsRmZs3MzMww0GtsbGtrbnFzdXRxcHBvbGhoamxsaWdlaGtq0dPYbdfKydVtbGtsa2ZmaGlnzM9rhG+Abm9va9VramprbWrNzWlubdPLyc7R1dnX0M7R09XW0czJyMjLZ2hoamxzeHd0cW5ubmtqb3FubWlrcXd3c3FwbWtpaW1ubGnLxsTLy8nMzsrKycxoaWhqbGlnaGpsac/V2+Hl6HRua2hpbG1t0Mxub3BzdXBoa2rMzNTZ19ja2myAaWZkYGJiwcnOz2Zhuri8Y2RkZmptb2lnaGlnxcFlZsXDaGpsbnFxbs7Ra2rRb3DCZGdjZmLHy2lszmTAy3Nyb3Ft0MLZcXBtx8bFw7Szv8ZmbGzUbNDNaGS5t8fKacXTzsrPzMi9v7fJZr7QfHhvxZ2+cHHUzG1ubNDTeHpw19SAcG5t0sRkYrPDzMHCzmxnxL+/vcltzM7Lz23TyMvS19TSycbJ0drd0s3By9jDy9bX0cq3s7+7s8HIwNDPzHN5a2lra25wb2pmZGhnZ2lpzMnS09jOxMjIZW50cW7Kvry9uqiQhoqTmFhuc29vb2hoa25za2prb29wbmpvdXJoa29Mb2lnbHFvamNkacq5xWrNx2ZpaWpnY2VqaWZjZGRjZGptb3Bwc3Jta9LZdHJtampsbm5sbG1wcnV7f3doZGVkZ21wb29wb29wbWhoapN/gn6Gf4J+jH8BfqZ/hH6PfwF+h3+FfgF/hX6Cf4V+g3+FfoV/g36GfwV+fn5/f4V+lH+Kfol/hn4Ef39/fo1/AX6Of4N+h38Ffn5+f3+EfgN/fn6Jf4V+m3+Ifgh/f35+fn9/fqd/BX5/fn5+j3+HfoN/hH4Bf4V+hX8Gfn5+f35+hX8Dfn5/iH6Gf4N+iH+efox/hX6Sf4V+gn+Ffpp/BH5+fn+Efop/gn6JfwF+hn8Ffn5/f3+UfqN/jH6Lf4Z+iH+Cfol/iH6Hf4R+BX9/fn5+jH8Gfn5/f35+h38Ifn5/f35/f36Ffwh+fn9/fn9+foV/Bn5+fn9/f4h+CX9/f35/fn5/f4R+AX+Lfh5/fn5/f39+fn5/f35+f39/fn5/f39+fn9/f35+f3+GfoJ/hX4Bf4R+AX+jfpF/iX6Ff4t+o38Gfn5+f35+mH+CfqJ/AgIEAID9g4mOioeMkIf78fODi4qFgfz7g4iNjYmFhYeKioyLh4SIiIX87PKCgoKGhoiLjY2Lh4SEh4yMi4qFgYSIh4aGh4iIhP/++fH1gYOAgYOBgoOFiYuGhYyKgv/7+/6BhISDhoL18/X3+ffx9oCDgICHjImE/vb3gYGA/ICBg4qTkICLiYeHiIT26/mHhYD7+vfz+oH48uru/P+A/vXv9fn4/oSHgfn19Ozl5+zq7fT5gPnu7/Dq8oaIiYWDgfj9hIGDiI+SjYOGkY6KhYeJhYSDhpObm5eWkYyHhIT99ICFhIaFhIKC/4GDhYaFhIH+9PX7goqH/PXw94D+9/2ChIL+gYCFiYiEgoGAgYL7+v+Bg4SEgP7+gYT++PuCi46SmI+LiomHiYqKiIaHioWFhISBg4L79ufw/4eJhYT07vDx+oOCg4iTmZSOk5iTmI+JkJGPkIuMl5uQgoD/9YaXnIz98/SA9/T7gYDx2uP3+IOJgvDp8v799vn6goPq0uz28Pn4gWqBhYP28/XxgoiDgoiLioT49/6Ag4WDhIaEg4WFhIL4+IDw84GGgoCCgoSJi4eEgPyAhp2zqYfj1ePs3svGytTj7Orn6u7z+vr29O/l3Nrb5fn+/IKEhIL++PX2/4OEhYaD+OXc5veDhoaHhIkjiouKhoGAg4P97/CAh4qIhIKA+/yBg4H/gPr2+vz47uj4hYWEgICBgoWHgPT5+/by7+7x9vLq5uXm6PKDiIiEgfv5/YCB//v5+vz//fr7/Pr5+fn7/oD56d7e4+zy8PH29u/q+omF8uyCkYuEhIeJi4qHhoaJiYaDhYeHh4Dz9/v6/IGFiIeFhYWGiI2M//L4gYaDgf7//PDp6Ov0+4GChomIhYSEhYCHiImMjYiFio6Jgv//+fWCioaAiYyFhoWBgP39//+BipOI+oHv3f6I+Ob6h4X57+fr89vW3+Lo6ubxgfjy9ejw+PuDgIaH8ev2/PuGhP2BgIGGgPr6gIeHhISChIWChfrn6vqGhf6C9en08v/97oiG/v+A9oGJh4L6+9nL6/CCgID17/X8g4aA/4P15fH08vX6+oCFhYCB/v3w7vje7O398/Ty/4KEgf7x9/j7+vTz8eLb7fmAg4Hh2u6Bg4H/gP/4/P2C/Pfs7vTo5+zr9Pfu7PHq5t7X9fDr4un0/P+GhoPs5+/3/4OEkJOMjY+J79bd8v3+/Pv49Pf59vPt69vJwmu/wMnP4IOYrbKQhYqF/oOcrqyelJKUlpSUmqCXhfr9/YCEhoaFgv3x7urt7vLv5NPJzeiEiY6OhPv1gIaHiIqMjI2KiYiD/YCEg4H+9PaGkZWTjYqMi4uOjIySko6Mh4OJkI+Qk5aYk4iB+oDkd32Cfnx/gnzm3eF6goF9eevqeXyBgH56enx9fn9+enl9fHjh0tdzc3R3eXt/goKBfXl3en+AgH97eHp9fHt6e3t8eevt6ePmeHh1dnd0dXh7gIOAf4WDfPPv7/F6e3t6fHrr7vHz8+7m6Xl7eXh+gYB87+nre3t57Hd4e4GIhYCAf35/gHzo4fB/fHfq6ufk7Hno4tzg7O947+ji6Ovs8Xt8duTh4t7c3+Pe3uTod+XY19jT2Xh7fnx7eenreHV3e4GDgHl7goB9eXp8eXh2eYWKiYaFgn57envv5XV5en59enh47Xl8f4B/fXrz7/T3fYF+69/b5nnx6/J8f332eyl+gH98fHp6envw8PN6e319eezodnjn6Ot2eXuBiISEhYWCgYCAgH1/gYR9gHp8fPPt4OXwfoB9fOjm6ebtfHx+go2VkIiJjIaLgn2EhoSHg4aOj4R5duzheoiMfebj53jt6vB7eebR2u7ten555d3l8e7m6ut6feTN4+zp8e18fH9+7Orq5nyAfHyAgX976+zyeXt9e3x8e3p8fHp44+N23eB4fnp5e3t8fn56gHZz5XZ6jJ2Ves/F09zQvri8yd3p6eXp7PD29PDu6+Td3N3k9fj0fH19ffXw7u/2fn5/f3zq19Db6Xp9fX6Af39/gICAfXp5fHvr3Nx0e359fHx78e96e3nveOzo6+vo39fmfX56enp8fX+Cgnrp7/f28u/q6+7o39rb3+TufoB/gHt36OnteXnw7O3w8fPv6+zu8PHx8PL1e/Hg1dTY4ujn6e7t4tnle3jd1XN9eHJzd3p9fHt6fH5+fHp7fX1/eu3z9fHweXt+fXt7fHx9gYHw5ux7gHx58PPx5+Df4efvfH1/gH57enp8f4CAgoF9eoCFgXvw8u/sfYR/eH6Ae3x7gHh47+/x9HyCiHzkddrJ5n3m1ed+et7TzMzUw8PN1Nzc1N135d3bz9be5Xl5foHo4Ojr53t66HZ1dXdy3990e3p3eXd7fXl979va6Xl25Xbh1d/g7enfgH/u7XfneIB9eO3sysPk43d15eTr7nh6dep44M7a3uDi5ON0e3x4eO3xgOvm8N3k4O/i5Orxenp15d3j4OTg2NbW0NHg6nh6e+Xb5Xt+fPR57uXk5Xny7+Pl7OPe4ODs8ujm6+Tcz8bm6Oje4+jv8X9+e93V2eDod3iDhH1/gHvZxcvc5Ofo6+vq7O7q6OXl2Mi/ube+wcx3ipubfXR4deN0ipeVi4SDhYeFU4aMk4p65enodHh6fHp26ODg3uDg4d7Vxr7E332AgoN66OJ1eXl5fYGAgX9+fXfldHd2dePY2XeCh4aDgYOCgoSBgoeHhIJ/fYGGhIWIioyIf3biNdFscHRwbnF0bcrEyW10cm1q0NBrbnJycG1rbW5ucHFubW9taca7v2ZlZWdpamxtbGxqaGhrhG8rbGtsbm1ub3BwcG7a3NjQ0WxtamhpZmdqbnN3dHR3c2zU0dHTaWhmZWtt1ITWgNPMz2xvbm1xc3Ft1tPXcG9t1mtrbXB1c3JzcnN0ctjQ2XFuatPW08zLaMnIxcfQ0mrV0c3R1dXYbGtlw8DDwb29xMbKzs9qyrmwrKOvZ2tsaWhmx8toZGRna21qZWdwb2pkY2dnZmJha3FwbW9vbWtsbNDKaW1rbWxqaGnRa25xNHNzcm7U0NXacHJvzsfGzWrPydFtb23XbW9xcG9wb25ub9zd3m9wcG9t2NVrbM/O1WtraGuFdit1dnZ1c25ucG5vb21sbm/b18zV2m5uamnLzdDLzmtqam54gHlta3Bvc2xnhGuAZ2lubWVfYcW6Y25yasnM2HPZ0tZsasa0wtjTamtoysPEzMzFxMFlase809PEy85ub3BuzszS0G1vamdpbW5t09TWa25ycXJybmtqamtqzM5px81tcW1qbG1tb25nY2C8X2Ftd3Bbm5eqt6yYkp60y9fW0tLS1NjY2Nrb2NPLw8aA1dnYcHFubNXRz83Qamtsb2/WycTL0mxub3Bxc3V2d3Z0b2pqbm7Qv7xiaG1ub25s09NsbGvTa9bW19bPwrrHa2toaWpramlpa2jM0tbV1dPOzM3LxcPEx8jMa21ubGfIyM1patDOz9LV1c7IysvIxsjL0dRq0MbAv8LIysnM0dKAycDJamS0qltjYFxfZGhsbWxqbG5tbGprbW5va8zT2dbTaWpramlqamprb2/Nw8ppbGpoztDPycfGxsrPam1wcW9tbGxtcHBwcXBtam5yb2vSz83RcXhyam9uaW5vbGvV2NfTa29yaL1itKK7Z8S2wmhirqigpq2aobO5wMK+ymqAysfMxszR0Gtrb3DOzMzJxmppx2NhYmRhu75laWZkZmdtcG1wzr6+zWxqyWa/tsTEwsK+bGzV223Mam9pZ8zOtKC+xWhlydDb2m1tZ8dmvrK7uru/xMVkamtlZMXIws3dzs7G0cbN09ptbGbEu8PEwb7AwL++wMXHYWJpxcLObGuAadNpz8vLzG3a29HR18zIzM3X29HLycPFv7XP09TNztPb2G5vbsnFy8vMZmJoaWVobGq+q6+5u7/HzczQ2d3a19XYyrewp56dnq5lcXx9ZmBlY8Jjcnx+eHFwcnV1dHh8dmvMz81nam1vbmvSysnIzM3PzMS2q63Ba2xub2jIx2Ywamttb3BwcW9ubmvOaGpoZMK6v2p0eHh1dHV0c3Rxcnd2c3Jwb3R3cm9vcXNybGjLAX6If4N+hX+CfpF/g36df4V+kH+EfoZ/iH6Ifwd+fn5/f39+jH8Gfn5+f39/hX4Bf4Z+AX+HfoN/i34Bf4Z+hn+Cfp1/gn6IfwF+h3+EfoN/hH4If35+fn9/f36Kf4N+hX8Hfn5/f35+fph/hX6Ef4V+mX+CfoR/CX5+fn9+fn5/f4V+g3+IfoJ/h36Ef4R+iH+Dfox/BX5+f35+jH8BfoZ/nX6Ef4V+hX+FfpB/g36Hfwd+fn9/f35/iH6Lf5B+hX8Ffn5+f3+QfgF/jn4Ef39+fpV/hX6Lf4N+hH+JfpR/hH6Lf4R+hH8Lfn9+fn5/fn5+f3+NfgF/h36Ef4V+A39/foV/gn6Kf4R+BH9/fn+HfgZ/f35+f36Ef4Z+gn+EfgV/f39+f4h+hX+NfoN/jX4Lf39/fn5+f39/fn+EfgF/mn6Df4V+iH+Yfoh/AX6Pf4N+hn+NfoV/gn6MfwF+hH+Dfpx/AX4CAgQAIPD0/IOD/vaBiYb894KKiYSGiIH39ff4/oWJhv3p3OmChIaAhYCAg4SA/4OEgoD/+vb6gYSGh4eIiYyNjImHiYmHhoeIiIiGhoiJh4eHhYSCgYCCgIOIioeEgICDhISDg4H+gIOBhIeE/vPv9YGGhYOCgYGCg4KCgP/8/ICChYiEgYGDgoODhoyMg///9Ojo8/+GhICAgISKh/zx8vn5+4GA7+eA7Ovr8f6Fh4L4+vrx6vDw+IaJi4qGiIOGj5OMgv+BgIGGh4aIg/Xm3vGBhYuN+N7ugoX++vv9+oOIhYaJjYqJg/bz8u3q9/z8gID6/P39/fn6+vv/h4yE+/r6gIeLiIOChoeJhYOB/YH7gIOKioqHkJGTkIiDho2OhIKDhIiKiomAhoSCg/fxgP7w5OPc39/p9fH3gYqKgIWKjIiHgfWAhoOCg46Tlpmal5qXi4+Tj5GI9fL5/IGBlKGbivPq4drh7e7m5dnN4+7ygYaB/vv18/X8+Pn2/f73gID+/PeGkoj09vPv9vf48d/rgIaIhYD79PuAhIaEiouFhY2Uk4iCgIGAg4eHgv2CiY6QjoiEgff5hZiprJv+5+HQyMrO09rr9vn69/f6+/yAgoaGhIWD//mCh4WAg4WDgYOFgf38gIGCg4OFiIyMhoKEiIuIgPqBhIKBgoSE//T0+v3z6fL+gYGBg4D5/YOGiYaA/4OHiYmGg4GChIWFh4SBgYOGi4yJhoULhYOB+fLv9YGFgv+GgID9/oOJhvrw8PTy8PPz8vf+go6Ym5ycmpufpquppqShl4yFhYaCg4mNi4iIiomJjI2LiYmIio6PjIyOjYmHh4OBhIeHiYmGgf+GjITx6t/d6PLy9v6CiYyJiIeJhoH//v/98O3x7ePi5vGEi4T28fH6goP9+/jzgIL//vz29/f5+oD2/4P9/PPt/IaD+veChIqSjPv+g4eNkI6BhYqA+YGFgv35+PyDgoSC+/r6iIaBgfzw9IOFgoOE/ujj7fSCg+nl9vb37/T98vHr8Pf9iYby5vaE/ufn/oaF/fjz9oT/5ub22vqGgf/+8fDg2O7y+/WFjISCgvvy+of64+/x7e7w+YD5+vPm6eTw9vWBhoD28e72+fv9g4D+/PaAgvyA9faHhImIh4X08vP+gPLu5/uB//uA+vn3+vna1vXy6+LmgPzo9IL6gIf23OaMnJePjIiKh46VlZKD9vH0+fHy+f716+Hg4ebs9PmCg4KCiJOblZCMk5SLiYyTkI2RkZOdoZmXmUuVjYqLj5COjIyG/vX2+vn07/+GiIiIi4mFhIWDgoqQjYaHiYWCgYGCg4OB/v7+gIWIhP+Ch4aEiJGZnpuVkI6Li4qKiomIh4SA+PGA3d7ld3fk3XV/ferleH58eHp8duHg4+ToeXx44M3CznV5eHd2dXJzd3Zz53d5eHjw6+fqeHl6e31+gYODgX59f357eXl7fX5+fn9/fX18enl3dnZ5d3l+f357eHh8fHt6ennyent4e39+9+/p7nyAf358fHt7fHx8e/f49Xp7fX+AeXZ2eXp5eHl9fXfs7+bd3ebyf315eHd6f3zo4eLo6Op6eOPg5ePi5e16e3fm6eng2tzb5X2AgH58fXl7gYN+dul3dnd6e3t9eN/RydZyeH5/38nXdXno6e/x7Xt+fX5/goGAfOrn6OTh7O7wfH718vDu7ujn6fD3f4J87u3teX+Ag4F9fH+AgX18ee567Xh8gX98d4CDhoR7c3R7f3p5e3+IiYWBfXp7fe3oe/To4OLd4t/l7ujse4KDen6Cg39/fPF7fnx7eoKEh4qMioyKf4KGhYZ94t7l6nZ0go2Kf+nm4Nrg6Ofi49XH3Orue3568O7n5efu6uzr9Pfve3z07eeAfomA6Orp6vLt6+LT33h8fHp48Orsd3t8e4CAfHuDiYh/enh5e35+ee16gISGhYB8eejmeIeTkoXh0tDEwMXKzdTl8fX39fPz8/R7fYF/fHp57+x6fnx5fYB+e31/fPf3fX5+fn19gISDfXl7gISEffJ8f3x6e3x88Ofo7vHn3eQv8nx7e3t57fF9gIJ/ee97foCBgH17fH5/fn56eHl8f4ODgX9+fn166eLf43h9e/KEeYB6evLzfYN/8Ojo7ezr7+/t7/J6hI2PjouHhoqRlpSRj4yDe3Z4enZ2eXt7enx+fX6Bg4J/fn19gYKAgIODf319e3l8fX1+fn1673uBe+Tf1dTf6ens8nuAgn9+fn9+ffz49fDk3+Pg2drg6X6Ffevl4+h2d+jn6OR4e/Hy8e3t6oDo5+XvfO3p39vrfXrq5nd2fIR/4+N1eH+Cf3N3fHXkdnp25eTn6nl4eXjo5+h9fnp57eLpfn55enrs29ff5nl629bm5+bd3uLZ3dfa5Od7euHX5Hnt29vzgH/x6+Tsf/PVzN3P7n157Ovg39TI2ePt53t/eHZ14tvkferW4+Xg3YDe6Onk2cnQ1eTm5Hd5ctvW1eDl5ut8eOvr6np76Xbk6oJ9enZ1dt/h5/h949rV63rz7njs6+jq7M7L6ujh19t569fkeut3febKzXmIiIOBfHx5foODgnfk5uvx6+ru8eje1tXW29/l5nZ2d3h8hIuGgX2EhX9+goeDgIWGh42Oh02DhIF8fH+CgoF/f3nl3uHo6OPe7H2DhYODgHx7fHh2fIJ/eXt+e3l4eXx8e3fr6+t2e3167Xl+f3x/hYqOi4aDgH5+fX+AgIB/fXno3zPS0tRsasq/ZGxszcpqbmtnaW1pyMXHy9JtcGzKt6mtXmBhYmNjYGBjZGHEZmloZ8/MzNCEawtsbXB0dnRxb3JycIRtgGxra2xubWxramloZ2VmZ2xzdXNwbGpramhoamvZbW1rbG5s1dLR13F0cW1sbW5vb25vbt7f22xtb29pZGVqbG1tbnFxbNTX08zKztJsamhmZWhubc3GyNDS02xqycbLy8rM1m9wbM7P0Ma6ube/am9wb2xrZ2lvb2pjwGBhY2lqgGdnYrmyr7dcW15isJ2rYGTEzdbY1G1wb3BwcG1raMfJzc7R3uDbbWzQ0dTU1dLS0dLSa25q0tXWbXF0cnBwcnJyb29u02vUbG90cnBrcXJ0dHBpaG5yb29yc3d3dXVyb2xrysls29DCvcHMzdfg2NFqb25na25va2xs0GdpZ2dlgGpsbnFycHFvZmhsbG1mube8wWRibHl6dNXRz8fEz9LR1Mm8ztfbc3Vw1s/IxcbOysjFztXTbGvU0cxwe3XW2NTT1tDOybi9ZWdpaWnSz9RrbW1rbm1oaXB1dWxnZmpuc3Jry2dscHNzb2lju75jbHJwZaykpZybpbC6xtTb29rYgNjZ2NVqa2xrampqz8poamhkZmlrbG9ycd/fcXJxb21rbHBxbGlscXRzbNFsbm1sbG5v18/P09XNxtDZbm5ub23T1G1xdHJs1W5wcXFvbWtqaWdnaWloam1vcnNycXBvbGjFv7/Gam9t1Glpa21ubtnXb3Nvy8C/xsjHzNDS1tdrgG9ycW9saGdqbnJwbm5uamViY2RhYWVnaGlqamprbW5samloam5ubG1vbmxra2lpa2xra2xraM1scW3Kxb28xcjCxctna29ubWxubm3a2djVz83QzMXFydFxdnDSzMzSbGzT0s7La2zT09PS0MnIysnRbM3HvLnJa2fIw2FgZmxkgKysXGJnZmJXXWRjxGRmY8PExsloZ2ZkwbzCa2xrbNPK1XBsZmZoz8e9vr5kaLuzw8nOyMvMwMO/xcfFamjAusdq1MTAz2pqz9DMymzYvbLJwdtxa9HOwcTBusnM185qbGRmZ8G4v2rPvMXSzsbEyc7Gt7C6u8LIzmloYcLDwcvPgM7SbGbH191vbc5oyclsaGhjZ2vMz9LfcNDEvM9v39ds1tjb2dK1utzg3tTTcdnCzW3RaWvArLBncW1paWZnZGhtbnBpyMfO2NTU2NvRx8LCwsXIzsxmZWdobXJ3dHJvdHRwbm5yb21vbm5zdnJydXNtbHB2d3VydHHVysnR1dDHOtJwdHd1c25qa21qaW90cWxsbmxrbG5wcG5q0NDSa3BxbNBpbGxqa25wcG5sa2trbG1ubm9wcnNy3tYMfn5+f39+fn9/f35+h3+FfoN/hH6LfwF+hH+Efq9/AX6Gf4R+jH+Dfo9/h36If4Z+gn+HfoN/iH6MfwF+iH+EfoR/BX5+fn9/hX6Jf4h+gn+KfgZ/f39+fn6MfwN+f36bfwN+fn+Lfop/AX6Tf4R+hn+OfoN/jH4If39+fn5/f3+KfoV/g36TfwF+iH+CfoV/kn6Hf4J+i3+CfpB/AX6Hf4l+hX+CfoV/AX6Zf4R+BH9/f36GfwV+fn9/f4t+t38Efn9/f4l+iX+MfoN/hH6Cf4R+gn+KfgF/hX4Ef39+foV/gn6JfwR+f39/hH6Ef4N+hH+DfoV/hX6Cf45+Bn9/fn5+f4R+gn+EfgF/hn6Cf4p+hX8Efn5+f5F+g3+Hfgt/f35+fn9/fn9+foZ/hH4Bf4R+BH9+fn+Mfgt/fn5+f35/f35+fo1/kX6kf4h+mX+DfoR/AX6Wf4J+AgIEAICGi46Qk5OI/4CBhYySkImFhomJiYyPkIqF/fTv6eHk9P+BgYKEh4aDg4SIjpGNiISEhoeHh4iIh4WGiIqKiYeEhYeGhIWHhoH9gIKCgYKEgffy8e3u+oGDg4eLhfnt9ICC+u3t+YOEgYWIgvjw9v7//fv9gYSFhYWEgvz2+/z59YD6hYiHhISFgoGBgYOEgYD99ff78eHh6vqDhoOChIH69/Xs7vf48+bf+oqE7+/8hoeIiYmHg4eSiYGA/faBiIqGiYuNjI6Tj5KTi/z4g/f1goL6+vz+g4aJh4SC+/WBh4eEhoWA+f2Cg4SCgP7+/P6A+fj79/b18fb9gPv6+YCHi4CQifPl6e74gIWGiIyVjIeKjZKSkJGRjoT7go6QjYuIjpCC7ent6ufw7t/o6+Lr9/fl5ezv6vT++4KIgoKFhZCIgoKEgYSC/IOVlI2LhouOlZaQiYuQkJWbmZaXn5SGiYLu6fLs6Ovq6+vq+IOCgPXn49zY5/Dr7uXogfXl6eby9YDs9/n29Pj16u/y9/v8goOIioWGioqEg4WIiYuKiomIiZOVkZKVkIiDg4GGj5CLj5GPioSCgIOSpqqYhffo5evz+/bt8PXz8/X09PX5/YD/g4WEhIWEgYOJjI2NjYuKioiGhISDgoCChIaFgvnz+4H/9/b+g4aIh4WHiIeEgYGEhYCDhYiLi4mGhYWD/vqAhIaEgf7+/fr68+nu/oaHhomJ/e3v9PPz8uzs/YSC+fHx9/v+gYKA/vv8gICA/Pf6gID48vX49/uDi4qGipCVlpONhYKChIaHiIqNjYqGg4GHkpSOlJ+ShISHhoeEhIqNjYyGg4WGgYKHiIeDgPv8//+A/oD79/L16uLo5eXj19TRztbl6+vl3fCBhoP8/vr3gIH/8+/y7+ne2ez39uvo7Ovk4+Tm7O7v6ezu8vH5+O34gPns7vT9hI6RivzugoLz9fOC9ez1/v/37fHu7O+Jkob98/Lw9fP2//aAgIGChIKA/YSAgoeF9//t8PTp7/rz6/Ts4oDz7vT/goP9+IaLh4Hx9/bx7e/4/f30/YKA+/T86d3r7ujz6O35iIT/goaEhoj/ho6E9vbt8vP89unr7vP19+TmiIuChIH6/YGBgf//gID29fPk4vqC+/SB//n2+fTv8PTs8f6GhIKIiIiE+d3Y6+jz7+3v7YCBgoDmy9Ds7O38iYCEgYOEho2J//2Ghf/4+v/15uz5gP/39PX2+/6CgPr/hYiJhoWVlYP6hIyIg4ODiY+NkJSRjImJiYiIh4SDg4Dx3tTe3Nvy+vWAhYaHiIqMioWBgIiLhomLiYSA//bu/YeE//z17/P2+ICCg4H8gIqLhoSLkZOSjoqKiYaCgISFhEp6f4CAgH945nZ4e4KHhH55ent6fH+DhIB76N3Y1M/T4ux3d3d5e3p3d3d5fX99enh3eXp6e31+f359f3+AgX99fn9+e3t7enbodoR4YHl23dbV0tbleHt9goR959vhdnfm2Nnmenx5fYB77OXr8/Xz8/R7fX19fHt67ez19u3l5nt9e3l4eHZ2d3d5eXh37ujs8OTV1d/se358e3x56+nm3d/m5t/Ry+R8d9vc5oR4gHp6en6GfnZ049x1fX97fH5/fYCFg4aGfufleefpfHnm5OPldnt+e3h15OJ4fn99f3967fF8fHx7evP19PN67Ozs6Ono4+fuee/u73l9gIN+3c3V3el7f39/g4qBfICChYSChoiEeuZ3g4WBf3yChn7q5uro5u3p3OTh1t/q7eLhgObo4+z283yAenl7eoN9eXl7eHl57XmGhoOBenx+hYaCfYCFhIqPjYuOlIp9f3ro5/Dq6Orq6eXj8H59e+zh3dbS3+bg4NbZeOba4uPt7OLr7Ojm7Orh5efp6+x6en1+enqAgXx7fYGBgYB/f36AiYuGhYaCfXl6eX2FhoGDhIN+gHl3dneCkJKEdd7U09jg6enj5unm5ufn6Ovv83rxe3x8fX59eHh8f4GDg4KBgH58fH5/f35/gYKAfOvj6Hjt5+jwfYCCgH+AgH9+fHx+f31+gIGCgYCAgYH69Xx/gH98+Pn69/bt4ePvfHx7fX7u4OTp6Ojn4N7te3rr4uDk5+p4gHp58e/veXp68ezve3zy7e7u6ux6gH96fIGFh4WAeXZ1d3h6enx/gH58enh8hYiFh4+EeHd5eXp5eoCDg4J8eHp8eXp+f357eOvr7+959PPv6ere1NrZ2NbMysvK0+Hn6eXb631/euno5uZ5e/fv7Ovm4dfQ3ubm3trf4t7e3NrdgN/e1tfb4eHo6eLte/Lm5ejxfoaGfujcd3XS0tN03dTe6eri2+Xt6uN/iHzo3t3e5uXl7el5eHZ3eXd37HpxcHt96uzZ3uDU2Obi2+Dc09zV4Ox4d+fje4B/fO/09O7m6fHy8OnseXrx5+zazt3h3uri5Ot+e+55enp9fup5gHnjgN7W3uHo49jZ3OPq6dDRfH10dXHd5HZ3d+zyenfm5+nd1+d25uJ47+bh5OLf4OPd4ex9e3p9fX145c3H2trm5ubo43l6e3fXv8Te29jke3Z0dnl8gn7q6Xx98+zs7uXX3u158Oro6urx9X588fN8fXx4eYiHdd91fXt4enp+goGEToiFgX5/gICAfnp5eXfi0snU0tDi5uN3fHx+f4CCgXx4d31+eXt/fnt47efg7Hx67+/q5ufo6Xl7fHrweICBfHt/g4OCf3x9fXx4dXd4dz9rcHJycm9oyGhscHV3cWhjZGZnaWxvcGtnxb26t7O6y9Vra2tsb25ramlqbW5rZmNkZ2lpamtsbW1vcXR1dHKEb4BtbGxoYr9iZmhoaGpoxr+9u8HPbW9wcnRtyr/EZ2nLwsTQbW1qbW9rzsvT29nW1dZsbW1tbm5t1dbj6N/Pxmlub21sa2lqa2ttbm1s1M7Q08m8vsbQa2toaGppzczLyMvR0MvAvNVzb8vN1W5ta2ppZ2ZsdnBpZ87DYmhqZmdqbIBsbnFtcnd12tFpwbpiZL+8vb1hZ29uZmC/wmZpa2prbGnP1nFzdHFv2NLQ1W7a29zX08/LzM9p0tbbbm5vcm7Hu8LDx2hsbWxtdHBtb3BycW5uc3dz2nB3dnRzb3Fybdba2dPQ2NXHy8e8xc7TzsrKy8jL0c5qbWlqaWRua2lmZIBjZ2jLaHNxbGlhZGZpamdlZ2hmaW1sbHB1bGFnZ8zV4NfS2Nna19Tbb29w2MvGwbvEysPCurxnxru8ucfIvcPKztLa29PT0tTU0GhnaWtoaGxtamlqbGxtbW5tbG51dnJydXRwbWtpa3JxbXBxb2pmZWRkaHBxZlutpaWuucXJyDfKy8fIzdHT1djcbtlucG9ubWplZGhrbXBxcXBwbm1tbm9vbm5vb21rzszXb93V09hwc3Rxb29uhW2AbGtvcnV1dHJzdHPg3HBxcG9s1dbW19fQxcfRbm5tcXPazc3Pz9DQycjUbWrMx8jMztNtcG/c2dhsa2rT0NNsbdfT0s/LzWpubWlqbW9vbWhjYWJlZ2hpa21ta2djYGdvcGxudW1kY2RmaGdna25vbmlnaWtoaGttbWtpz9DT0GeAzMnHw8K4t8G/vby2tbKutsfR1dHGzmtsaczQ0dBrbtvS0NHNyL64x8zHvb3J0M7OycfMzcvBvrq8wMzPyNFt2tHOzc1nZ2Zmxr5lYKelpVqxtMHEwb6+y9LMvGRpYrq1v8XNysvNympmZGNkZGXMZlxbaGzIx7zFwq+80M3EycaAwLmswdZsbNPQcHRybtHU0c7LzNHa39jWa2nKxs2yrMjUz9jJx85ta9ZrbGpsbtBpbmrO0c3Qz9POx8nJ0NHTxsVuamFlZszUbGts2uBva87O0MjBxmTGxWnQzMjNysLCxsLF0HBxb3Nzcm3Nsq/Dw8/NzdDPcXN0cMmytMrIwMSAaGVmaGdpb23PzW1tzsXL0cm+xdFr2dbU0s7R13Bv19VucHFvbnl4aMlrcm5qa2tucG9xc3FubW1tamhpaWxwbtLDucC5tcbLx2hsbm9wcHJxbWppcHNvcnNwbGjOx8HObmzR0c7LzMvJaGtsa9VrcHBra3Bzc3RycXNzcW1qbGsBaYd/AX6Rf4h+p38Bfod/hn6GfwV+fn5/f4R+hn+Ifod/h36Of4l+hn+LfgV/f35+fox/gn6Ofwd+fn9+fn9/hH6Gf4J+h3+CfoV/hH4Bf4l+BH9+fn6Ff4V+kX8Bfol/ln6OfwF+mX+LfoN/i34Bf5N+rn+SfgJ/fpx/BH5+fn+Efpd/gn6Ff4l+hX+KfoJ/hn4Of39/fn5+f39/fn5+f3+GfrV/hH4Bf5Z+g3+EfoJ/n34Bf4V+hH8Ifn5/f35+fn+LfoN/iX6HfwF+hX+RfgR/f35+hH+LfoJ/jH4Df39+hX8Efn9/f49+hX8Jfn5/f39+fn9/hn4Ef35+f4t+h3+KfoR/h36IfwR+fn9/iH4Bf4d+BH9/fn6IfwF+l3+JfpN/hH6Cf4d+hH8BfpN/AgIEACiFhomNkZSVk4iA9vHy8eDT6P+EhIiNjIiGhP77/Pv8gP749/yChoeGh4WAhoiHh46Pi4T//YOGhYL99vb/goOCgf739PyCgoCAhIL4+4aHhoeIh4SDgPTt8vr58vDw7vP9hoqOjIT8+v+DgoGDg4H9/oGDg4OC//f5g4mLiYSDhIL9/v349/+Dg4GIi4eA9Ofd4vH09f6A+Onj6/Dn4OPk5uzv6vL48fX6/fSA6OLyg4aC+/eCi4Dw8vSBg4H36u78g/rm5eLZ0NDR2eL0hYqKjo+NjI6QjoWFiImHhYSF/vj38evr8fr59vLs8O/19fLs7PD3/IGFhIOIj4mLlJCF//qGiIWFjpyhloyLioWEg4P/+vz99IOQjpCSk5iPgfbx7OLn9PDt7fX+gvyA7vj39fPx8/75/fr5gP2DmI6Mj4+OkpeJ8O3x8PLxgJKWj4yHkp6gl5SWkIXt2rTK9Pvq5eTX2+no4OHk5vj8/vPp3Nnq8vmA9OTo7ent7fL5+vf4+vnx6+nw/YL+6+Xr+IqIgICJh4eGg4iKi4uOkZOQjIaCiJGRkZKJgYD/gYdFjZGSlJOPhv6Hl5iG/oCDh4eA9vDt9oOIiIWDgoD38fX3+vz/gYGAgYKBg4aGh4aC/f+DhoWDgoGA//6AgoKCg4ODgoGAhIGAgoWH/Ors8e7u94D//oGDhIL66ufr7fH4/f37/Pr08/b29vv/gYOEhIL9+Pv/gYOEh4eC+/f7g4WE/fP0+/7+/fyAgoSGio+Sk5KRkpaanpuVkJGSk5GQkZOWlpSSkZCRlZSNi4qNj5COjIT6+/f3+4CChIWDgf+EjZCLhIOEg4KAhYiFgP+Cg4ODgv/17ufdzsfU4uHj7e7m4t7c5N7W1NPa39zf4eHh4N7l6/j9+YCB+vPy7eDU1uH0gIGA9vTw7eba0970+/Tx8P376uft6/Dx8ebW75OOgIiTkYqC9fWC//yCgf35+YOA/O3s+4WIhoD98/Dn9oSMhP75/Obo/YCA9vOJnZyP+/L4/4P///+Bgv2B+PD7gICEhYmQiPzy9/qCgYH6/fD/+fr2ydr6goaC/ff+g4eCgIWEg/Tq7Prz7vb9/oH/+fT89vCCkY327viCg/js9OXo/vfi6viFg+34+ICTiv388Oz1+fDw/vzo7/Lt9vHf6fPy7/b4+fPp7vJUg4mGgYGFge/rg4X67ff89Ov3gof++vyAhISGgPqA//n3+v6Dhf6Ag4aNi4iLhoP9+IeI//qFhYKChYyUlZaZmpaWmJSNjpSQh4P/+Pr/hIeMkY6JhIYwh4iKioaCgoaHgff2/oKEh4mIhYaEgoWIh4aJjYmA9v2BgP6BhIeFgICDhYWEg4aIKHp7fX5/f399d3Pi4ODcyLnO5HZ3en9+fHt67u3u7ex26ODf5XZ5enqFeYB6eXp8fH2DhYJ+9PJ9f3x67ebm73p8fHz49PP4fn16eXt55up9f39/goKBgH3u5+nu7Obj4+Dk7X6Bg4J76+juenl4ent57O15ent7e/Hq6Xh8fnx5eXl35+np5OXue3t5f4F+eOni2tzn6Onyeenb2N7g1tDU1dbd39vj597g5YDn3tTP3nh6d+XfdHty2d3ieHl14Nng7Xrm1NXV0cnEwsrS4Xl+fX+BgH+AgoB5enyAgH9/f/Pt7Orl4ubv7+/r5ufm6+ro4uHj5+x6fXt6f4N9foaCeerqfH17e4OQlYp/fn57eXh57Ofp6eJ3f3x+gIGFf3Xj4d3X3+rm5OPo7oB46t/o5+Xk4+bx7vPv6HXneIqBgYODg4eMgurl5uPi3XSFi4R+d4STlouHiIN73cyouuDt5eTl2Nvo5+De3N3t8vTq4dTQ3+TqeOPU2N/g5ufr8PHt7Ozr5+Tg4+567dvW3emBf3h5goF/fXl+gYKAgoSGhYN/e4CHiIiIgXt58UF6foKFhoaEgHjmeoeJeeZ0d3x8duXh4Op8gIB8enp46efq7O7w8nl6eXx8e32AgYKCfvX2fX59fHp6e/Tze3x8fIR9gHx7fH19fXx8fOnd4ufl4+p58vJ8f4B/9+rp6+rs8fX19PXz7ezu7Onr7nh5eXl46+nt8np9fX9+eenm63t+fPDo7PL09fTze319fX6Cg4SEg4SHiYyJhYCAgIKBgYOEhoiIh4aFhYiGfnt7fX+AgH955+jk5OZ1d3h4dnTqe4OFgIF7ent6eXyAfnjteXp8fXzy6uXi3M/H0dvb3efo4NzW0dnVz83N09jW2d3e3+Dg6e708OV1dubi4t3PxMjR4nZ4eOnm5OLd083X6vHr6+ju6tjT3d/l4t3PwdaFgHB0fXp1cuHoe/HseHXm6Ot9eu7f4fJ/gX547ejo3up7f3frgOzw2trtd+PnhZSTie/e3+Z26ujldnjoduzs+X58f4GChIDx4uTqe3p67/fr+PHx67nE63t/ffTr63p+eXZ7enri2Nrl4N/o6uVz5eLc4+Dad4SB4NPYcnXf1+HT2vLs193oe3jc6e56hn3q7OXh5evh3Onr2uDh2+Thz9zn5ubxgPLu5t3g33d6eHZ2enfh33p76N3p7+bd6Xl96urwenx7fXjqd/Du7/L0foH3enx+hIJ/gHl139x6fO7qe3l0c3Z9hYiIi4qGh4iFgIOJhX978e3u73p7f4SBfXt7e3p5eXt8e3l5e3t03d7peHp9fn18fX17foB/fX+Cfnbk6nd2Dup3enx7d3d6fHt5eXt8JW5ub29vbWllXVq2vcjOwrXE1W5tb3Fwbmxox8G/wslq1tLNzGiEayxqamloaGdoaWhnampmYsDEaGtqZ8vIytBpaWlq1dPT23BvbWxtasnKbG5uboRvgG3UztDU0czLycXFzm5ydHJrzc3VbGppa2xr09Zubm5ta9LP0Gptb25sa2tqz9DQztDZcHBtcHFuatHNyMnPzMvNZsW7ucHHw729vLvAxcbM0c7T19fRxLrBZ2ppzsplaWTFzdBraGO7trzHZ8e9xMbEvrqxsLO7ZGhqbm5rbG5tgGpmaGlqbG9vb9PN0dTU0NDU09LS0tfX2dTPzdDR0tNsb29ucnZwbHBtZ8bHamlnZmlxdXFtb29ra2pp0tXc3tdtcW1wcG1taGHDwL26xtXSzs3P0WrSytTVzszKx83K0M7LaMtndGxsbm5ucnl129THvsC/ZXF1cGtiZ29vZ2RngGNaoZZ4i7XKyczMv8XY3NPNycfR1NjPw7e2w8bLZ8e7uba3xcrM0NDNysrMzs7JzNVt2MrGy9JxbmhnbWlpaWhra21ucXR3d3ZxbW5ycnN1cW5t12xucXNzc3JvaMdpcnNmw2JkZ2RetLO5xmtvcG9vb27X1dna293fb29ucXFwgG9vbm1tbNXVa2ppZ2ZmZ8/Qamxtb3JzdHRycXFwb21sa2vLwMTKycnQbNfVbG1vcN3Tzs7O0NTY19ja2dTPzsnFxshlZmhpaM7KztRscHFycGrNzNJvcXHd2Nzh4N3a2G5wcG9ubm9ubWxsbnF0cm1oaGhqa25xc3R0cXFwcHBxgG9pZ2hrbW9vcW/X1svHx2RkZGVmZspob3Nybmxsa2prbGhlzWlqbG1u2tXV1My6rLG7ubjBw8DBwL7Ewb6+ur7DwMDDyM7NyM3S19DHZmjNy8i/s6+0ucNmZ2jNy8fFw7y4xdfZ09HLysO4usXGxsbDuamqYVpUXWRgYGLGzGvQgMlmYr69wWZnz8nK021saGXLyce7xmZoZc/V17m60GjN2X6LiX3Yx8jLZsTCxmZlymfLytBoZWdrbG9w2MC7wmdoac/XztzSz8ylrMtrcG3QwcJobWxqbGxt0MnGz83P1NDIZczMx8i7tWZzccq/v2BgwsPLv8bd3cnG0G9rws/PgGZxbdHSwr7Hx7mzvcO5wsS9xcO2wcvKzdnd3NXKzM1vc3Bram1mt7NlaMjBy8/KxtRwctHKz2tubW1ozGjS0tTZ3XJz3W1tbnV3d3dwasjCamzMyGtsamlnaGpsb3Jwa2lpaGdsc3JubNXR0NNsbXBzcW5sbGxqZmRmamxsbW1sJ2fGxs9qaWloaWtvb25ydHRzdXVxatTdcW7Vamxubmxtb3FwbWxtb4p/iH6If4V+AX+EfpN/gn6Ef4R+hH+EfoZ/gn6Jf4t+hX+DfoZ/gn6Ff4N+iH+Gfod/iH4Bf5d+Dn9/f35+f39/fn5+f39/hH4Bf4t+kn+Wfot/gn6Pf4V+iX+LfgF/jX4Cf36Kf4Z+jn+bfgF/k34Bf4V+nH8Bfol/AX6EfwF+hX+Efod/h36Mf4J+h3+CfpF/h34Df35+hH+TfoV/hH6GfwZ+fn5/f3+Ifqp/hX6GfwF+jX8BfoV/pH6Cf4l+g3+Zfoh/DH5+f35+f39+fn5/f4R+hH+FfoN/hn4Df35+hH+Efgt/fn5+f39+f35+fod/hH6Df4p+Bn9/f35+fod/iX4Bf4Z+CH9/f35+fn9/in4If39+fn5/f3+cfod/BH5+f3+HfgV/f35+foV/An5/hX4Df39+iX8Gfn5/f35+lX+EfpR/g36RfwV+fn9/fo1/AgIEAHqLi4eJi4iB+oOKi4iEgv76/YOGhYWEg//+hY+TkYuGgYCAgoGBg4iNjoqIhoOChomIiIyMhfv2+/76+IGIiYaEhYmLioiGg4GAgYWHh4aGhYSDgYCDiIeC+fn5+4KGgYGIjoyIhIODhIH89/b7goaGg4OA+feBh4mIhoSEgIL9+/n9goaIh4WDgv/z6+np5eT2hoaCgoOB+OXf5Ovt8/f08/iDgvvu6PH5+fmAhIaGhP/3/IKChIeGh4uIg4OC/Pf3/v3+9+/z7u34hYuIhISJi4mOkI+Qj4+QkImDgPyFiISDhYX89vf18vL0+fb09/ju6Ov4gYKAgYWHhoOGgIeJioWDgPPxg4mFlJ+Tio+Lhf728enq9vT5hoeHho+hnZSQkY+MioaB8PDv6ezx//Pt9O739ubrhf7k4vH6+vTy8PzbxNX0i4+Fh4+MkJCPh4CChY6dnY2DgOfQvK2yvLjN/IXmztfY1er87eP8iYb98/jz6/X9/P3+/Pnw6uPeKeH1gYKA/4CA+vyA8vT+/fr8goaD/YKGi4uKiYuPjYWCh4yRkoyFhouQhZJakIiFhoD2+/3+/4CEiYmCgoaE+fPu5e39gYD/g4mMiYT/9PmBhoyMiIeJiouLiIWD/Pj19Pf/gYOGioyMhPb3g4iGg4ODhIKBg4aFg4SGiIiFgPv36er+g4CBhYKAgPj1+oGCgYKCgPyAho2QioaB9Ovt+ISHhoOCgYD16OPj6/Hz8/b7/f+AgYD69vqCiYyMi4qJh4aFhIGAgIWMkI6MioiFhIOCgICEh4eD//v49fj9hImKiYeJjY+H/ICCgv7+hImIiIqKiYeFhYaB/oKDhYSFiYyPh/6CgoKHiIKA/e3d09LRztXg4NfX5/Pn0MXCwMO/v8TEw7y/yszIyMnNzM/Oy8zMzNPc2t/SztbZysTN6oGCgfrf4ejm8/SAi6G1rp6QiYLi1OLq8/vx+ID4/P6BgP38+4GAgYGEh4L+9f6GgPr5/oODhvjs7+3whImFgYGChIeJif/w8vXv8O6A8fzxg46I+oSLi4eF9frz9eji5+76gob76PD4hfX3+/D9gf35/+/y+IKOhfH3+fyB//jt7vHt3OmHg4T749bm69jJ1NvW2NPO0cvJ5ffl4N3b3ur38/fy/oL6/P3w3+P27PWDhIL8/P30gomFiYaE/v6EhoeFh4n794L89fDg4+919YSHhID/homH//X39/mChPv3/YGIioqPj46OiIL+gYOGiYmLiYSA+fj49YGKlZuThv6Cg4L9+/fx8vn8g4qPkY2FgYOIiouLhv+Agfz6/oWHhYaHhoWGioqJhoaHh4SBhJGXk5CNi4uIgv//gICAgoWFhYaJIYODfn5+eXHXcHd5eHd15+Tnd3p4d3Z04uJ4g4iHgn54doR1gHh9g4SAfnx4eHt+fHyAgHvv7/b59fJ9gH99e31/gH9+fXx7enyAgoB+fHx7enl4fIB/e+3p5+d4e3d2fIF/fHl5en189O7p7Hl9fXt7eObjd31/fnt5eXt7ee7t6/B6foCAf3199Onj393Y2u+Dg359fXvt3NbZ3t7k5+Lg43h4gOre2uTs7e55ent7eOje43Z1eHp4eXx7eHh36urq7Onn39vh4OHnen57d3d9gH6ChIOEhISFhH56efSAgnx8fn7y8PHs5+fq7ern6ezk4OHqeHh3eH1+e3h6ent8eHZ24N53fHmJlId+gn555N3d2tjf3OB2c3Bxe4yJgX6AfXt9gH14397d2d7k7+Tg5eDk49ngf/Tc2OPo6OXl5vPUvMjdfH95e4B9f318eXR0dn6Oj4F5dtK9rJ+lrqm55n3cx9LU0ef25tvxgoDz7fT17/b59PLy8O7o5N/d4/J9fXryeHfo6nbi5e/t6+97gH3veX6DhISChIaEfHp/hYiIgHl6gH6EhoaHh4iFfnt8eOzx8fLzeXyAf3t6fnvt6uff5vN7evN8gYWCffDk53d7gYN/fHx9f4B+f3/69vHu7/V8fn6AgoJ85eJ3e3t5enp8enl5e3p5en1/gH157evh5Pd/fH1+fn5/fXzx7e95eXp7e3jsd32Eh4F9eODV1N53fX59gHx8eufX0NHa5Onr7e7u73d3durn7HuChISDgX9+fHt6eHZ2eH6Bf39+fXt8fX17e31+fnru6+jn6ux5fX9+fH2AgnvndXd26ut6fn5/gYGAgH59fXnvent9fH1/gYR97Xl6e39/eOjbzcjKy8rP19TIx9bj3Mi/vbq+u7zBwL63gLrDxsK/v8PBxsbExsjIzNDN08bBys2+uMPeeXh159PS19Pf3nN6ip+ain96dMm/0N3p8+fse+3u8Ht79PLxe3l4eH2BfPHn7X547O70fXx+59zh4OJ7gH16enh4enp77uDe3dja2uLx6H+Hg+t7hIR+e9/h3t/X19zg63p86d3ofe586evu4Op36Obw5ebod4F64OXl6Hbm3Nbf497L1X16e+zUx9LVwbPCzcrKxsLCuLHL49vb2NXV4fLw8urteOzx8+nX1+fb5Hp8e/Du7eB1eXV7e3nr63l6e3t/ge/qeeni3MzM2OB5fXx58X6Af/Pt7u3ufH7x8Pd8gH57hH5aenbreXx+f35+fXp46ejr6Hl/iIyGe+p4eXfs7uzm5+3uen+DhYJ9e36BgoB/e+x4eOXc3XR5eX6BgX59gIGAfn18fHl2eoaLh4OBgIB9d+npdXV2en5+fn+BInR0cG9vbGbDZWhoZ2ZlxsLFaG5wcHBv2ddvdHRxbWtpaGiEaYBscHJwb21qamxvbm5wb2nIxczS0tNvcnJua2pqaWdmaGlqbG5xdHJvbGtpaWhna29wbdPT0s9naGVlam5ta2lpaWpq1NPR02tubmxsa9LQbHFycW5sa2ttbNLPzdBrbm5tbGppz8vHxsXDxdZ0c29ubWvQxcHDxsXIy8fEx2hlw4C8usDHzNFrbG5vbtfPzWdmZmdnam9va2hkwcHCxMG+ta67x9DUa25rZmNmaWhqbW1ubW5wb2tpa9pxcW1saWjN0NXT0NTZ3tzd4N7TztHYbWtqbXFycG1tamZmY2RmxcJmaWRtdGxoa2pox8PDwsXLys1taWRjanZxaWVlZWRjZYBkvb+9vMfQ3tjS0svJxr3AbNC8vcvR0c7MxNC/qqexY2dkaGxmaWtqZGBhYmhyc2pgXaWTgXJ0fnqLuWi+r73EwtPczsPQb27Tzc/JwsrPzMvLx8TAv77DydVubmzVaWbFx2fGx83MzNBqbmzQaWxwcnJxcnV2cW9wcnRzbWdpbYBwb29wcXJyb25vas7W1tTSaWxxcGpobGrNysa9wMtpatZvdnt6ddvJyGdrcnNwbm9xdXh3dXHX0MvIytFqamttbWtlvLphZWZobG9wb2xsbGtqbG5ubWllysrBwtBpZmhqa2xtbm3U0tRsbGtsbmzXbG5zdXFsZr2ysbxma21tbYBubMy/vMLO1tnY1dLP0Gpra9LN0WxydHVzcW9samhmY2FiZWxubm5vbm1tbmxoZ2pucHDb2NbSz81pbG1ta2xucGvGZWhpz85qbm5ucXJyc3FwcGzTaWdpamttbW5q0Gtra3BwaMOypqmyuLi8vraoqbrJxLKpqKiqo6KnqKmlqYCzs66rrbO0trCsr7W1tre3uqytubytqK2+ZGFfu6apsLG8u2Fmcnt0amJgXaeirrnK2M7OZsPH0G5u3drWa2dnaWxuatTN0Gxmx8jNZ2JixcXNzcxwdG9sbmtqamdqz8PCwry8v8bPx2xzbLtdYmVjYLjBvcG/vrvAymhtzMHLz4Bv09PSxctmx8fUzMvRbHBmwsjIymjIwsLLzcm8w29qa9bKu8K9qJyqsKyxr6yvqaS2xb6/vb/Bytrb3tXQZcbR19C8ucnFz3BxbtLR0MZrcW1wa2jS3XJwbmxucNLPbNLMyLm5w8lqbWxr1G5vb9fU19nZbm7R09xvcnFvc3Z1clpsaM9rb3BycXBtaWfKx8XCZWlvc3Br0mtqZ8rLycnO19pvc3Z4dG1qa29xb21r0Gps1dPSbG1ucXRzcXFycnBsaWhnZmVocnVzcnFxcnBr0M9oamttb25ucHOHfwF+hn+DfoZ/gn6cf4Z+nX+Efo1/hH6Gf4J+in+Efod/iH6Gf4t+gn+HfoV/g36Lf4x+k38BfoZ/kH6Pf4J+in+Ifo9/j34Bf45+k3+JfgF/in6Cf5J+CX9/f35/f35+f4Z+BH9/f36ef4V+iH+GfgN/f36Ff4N+jX+Gfod/gn6Tf4V+iX+DfoZ/AX6Hf4R+h3+MfgZ/f39+fn6ff4Z+iX8Gfn9/f35+jH8Bfol/AX6Gf7R+g3+Hfol/iH4Jf35+fn9/fn5+h38Lfn5+f39+fn5/f3+Ffop/in4Ef39/foV/iX6Cf4R+AX+FfgF/hn6Df4R+AX+IfoN/nX4Bf4l+g3+EfoZ/gn6GfwN+fn+HfoR/BH5/f3+FfgV/f35+fop/AX6Jf4R+hn8Efn9/f4d+jX8Gfn9/fn5+m3+Cfol/AgIEAFGIhoaIiYX/9/z++Pb7/Pn6/4H+/oSLjomB+vf8goaHiYyMjYuMiomHhIH/gICAgYOFiIqKhfvizcPZ8ICEh4eIh4WBgYOFhYSCgYGB/PXz9f2EgoCBgP79//77/f76+fiAgoODgoODgP//gICHjIyKiIaHiIiGgv3y7fH5goWEgoCDh4mIiImE+Ozl6fP07ejp6uvy/YGB/oD89+/p7fqBgoGCiIyIhYaGg4aJiomFho2SjYqJiouLjomDhoqKiIaIj46JhoqLioiB9e3zgoiHhYWKkYCVkYqHiY2SjoOCiYuKjIf77+zp6fX89vf07e7z8/WAh4aNkYuKhISEgIGIjYeBg4WMmZqH8uXPu7ezvtTVycjS5eLm6eDa2trQ2+rg2ePygIOAhYeB/fDv/Pvy8fLz9e3i6N/Y3OLjz8bc39zg4+/r84SIg/vs9pimmYiJhu7f64DZwse8sbGzyNne84qH8dnc3uDf4eLX4oWPjIqE/ujh4uXn6+71++jUy9Hd5vPx8Pn79fWDjIuB+4CDgoGCgYSEhYWHiYmJh4qRl5WOiouJgoGFhoWGh46TlJKNhoKBgIKGhoSDgfv3+f6A/Pv6+/3///jv6fKAgP79+YCGiYmIiICLjpGTjoWB//6FiIeEgv/06u3z+v6ChoeE//n18PP7gIGEh4aFg4KB/oCDgoKBgYKEg4KBgYKBgPz7gIeFhIaJiIeKjY2LioWB/Pjz7Ozz+4CCgffm1M3U3+Th4Or9hoaB9ezq8v2DhoaIiIeGh4mJhYD9/4GDgvv4+fX5gYH7/4CFjY6JgYCBg4WB9/eEioWBgIH++/n9gP6Bh4qJhIGBgv/7/IGDg4GEiYuKhoSHiYiGiYyPkI6Li42Jg4KDgoOFgPj+gP727ero5unx7ufs7evj6PDt8/325+n0hZaWlpeXjPvn+YmQl6CbmJ+empWPjIqKhPHn7vHq8fr2/IOIhYCGhoD9+/n8g4SA9oKNjIP484SMif/++f6C//Pr8PT2hIX+9/j6/YCC/YH//f38gPvw/f/++fuA7+/79fL+i5GKhoSB/Onl8YKFh4qJh46D8O78g/iAhPv7/P6Dgvr5g/Pz/Pj0+oH//4X/+uz2gIDy7eXf0NHZx62ygfWUnbjOiICw9cPd5Ofm6e307/f57+SA+/Lz8vf99/uC/ff7+e708/+C//j19fv8hYeAgYD2gouPh/vv6ubs7eP6g/34+oKD/vyDhYGDh4mJioeEg4OEhYaFhoT+/PX3gID9+/+Ehf329fyFkJGGgYOAgIWLiIP//f6AgoOFh4eGgoKB+vqAgg6DhYWGh4eFhYiKh4WGiYSKGYmLj42Gg4OFh4aDgPfu9P6ChYODhYiHiYlje3h5e3t449nc3tvd5unl5ul15+d3fX96c9/d4nV4eXt/f4CAgX9+fHp48Hh4d3h6fH5/f3zs18a80OF2eXt7fHt6d3d5ent5eHd3d+ni4eLodnV1dHNz5+jq6ebo6efm5nZ4hXqAd+ztd3d8f359fHt8fX17eObd3OLre317e3p9gYKAgIB86+Da3ufo49/i4+Pn7nl47Xbo5N3a3uh3d3Z3fH56eHt7en1+fn16eX1+e3l6fH+AhIF7fX9/fn19goJ9e319e3l14dvednp5d3Z6f4KBfXt9gIJ9d3h/gYCDf/Dp6OaA6vb88+/s5ufr6+x4fXt/gn5+enp4dHR6fnp0dnd6hYd749vGsKejqrm5srG7zczS08vGw7+0v8zHw8zYcXV0en147uTk7+7l4+Li5eHZ4NnU19rax77P0dHZ29/U2XR4dNzQ1H6Hf3Z5eNvP2MevtK2mqKu/0NXpg4PqztDU1dKA0dHK24GKhYJ99OTf3uHi5+zw9OLPxs/d5ezm5Ozs5eN4f39463l+gICAfoB/f4CCg4KCgYOIjIqDgIB/d3d6e3p8fYOGh4aDfnx8fH+CgYB/fvXv7/J69PT09fX29vDo4+l7evPx7np9f39+fX2Ag4aEfnz29H5/fXp58ejf4eYY6+55fX145+bn5ujueHh7fX19fHt58Xp+hX2Af39+fX19e3jq53V8e3p9f359gISEg4F6dODe29ja4+x5e3vu4M7H0N3i3tvh8H9/eunf3OLtfICAgYB/fn6Af3x36+t2d3jr6uvp63p67+96fX16dXZ3eXl14eN5f3t4d3fr6Obpdep3fICAfXp5eOfg4HN3enp9gIKBfnx+f36AfH6AgYODgYCBf3p4eHZ4enfn7Xjx6N3Z2dfY3tvV2NnX0dXc19rk39LS23iIh4aFhXvfz914foOLiIaNjImEf3x6enfb0Nfc2uPu5+Z2enl6fHjt7e7yfoF+8X6HhXzs6XyCgPHy7/F78uri4eLjeHzz6enq7Xl89Hvx7ezue/KA6vLw8PHwddXY6+np9ISGf319e/Hj3uR5fX+BgH2DfOrr833venrl4+Poenrp5Hfg4ezp5Od36eZ25+bY4HV14+Tg3dHN076kpnPPeoKWpGyJwaK9zdfb4OTq5uvu5dh47ebo5+jq4eV25ubt7eTo5fB67ebj5OrsfX52dHThdn17f3nl39rT2NjN5Hns6ex7fO/se316fH+Af356d3d3eHp6eXl46url5XV16OfpeHnq5ebtfYaGfHd5eHl+gX978O/veHp6e319fHl6eOTgcnV3eXl6enp5eXx/fnx8fn+AgIB+foGAe3l6fH19enbh2t/qeHp4eHx/fn9+gG5pZ2dnZcK9w8O+vMLIys3PZ8zMam5wa2TCwchobGxtcG9wcHFvb21rac9oaWhnaGpsbm1px7aoorPBZWdoamppaGZmaWxsa2tsbm7X0M7N0WhlZGNhYLu4ury/xcjFxslqbW9tbGxradDPZ2dscHBvcHBwcXFwbtjU1dnecnJvPW1qa21tamlqac3IxMbLzMrIys3LzM9nZslkxcK/vL7FZWVlZmpqZWRnaGdpa2xsaWhra2hoam5xc3Rybm6EbxVtbGlmZ21ubWpkvri7ZGpra2pqbGuEaIBpaWRhZW1ua2hly8/R0Nnp8Obg3drc397fcHJvc3ZycGxtbGdmam5saGlnaHBxZ8LCua6qn5+traOlrbWyt7y1r6ymmaOwo5qouWJiYGZqaNPPztTPycbFyNDNwsW+uby/vqqju8C+w8HCtLVfYV63qalmbmZfY2Kxq7anj5GLiICPlqeztb5oZr2yusLJx8K+tsFscGtpZ83Cv7/BwcXIyci5q6WvvcjU1dHRzsXDaG1taNJtcG9tbm1vcHFydHV0cm5wd3t5dHJycWpqbm1qa2twc3Nyb2ppaWhqbW5vcXLe1tLRadTW19fX1tbSzMjQbm/d189mamxta2tucnh8eoB0cN3ab3BtamrUzMXGyMrLZ2praMzN0NDT1mtqbG5ubWxratNrbWtqaGlqbW1tbGtpZ2XIy2luamlsb29ucHJxb21pZsnKycXFys9oaWnNw7WxusfNysfI0Gtsas3EwcbOa21ubm5samlqamdkx8hlZmXEwMHBx2lqz85pbW1qZCljZWdpZsXFanBtamlq09LP02vSam5xcm5qaWnQ0NNtb29tbnFxcGtoaYVqgGtsbGxrbW1raWdlZWZjwMVkxL23ury5t7q3sre4sqemqaSotrixs7pkbm1sa2tktaClWV5jamlqbW1sbGtnZWdlurG5vLrI1M/NaGdjZ2xt29fS1W9yctptcGxlxsRobW7X2tjbbtTLxcTHymhs29nUz9VsbdVr1djW0WrQxs7SgNrh32rAxNbX1dRmY2VpamzVxsLFZ2ltcXF0fnfY1dlsy2lqw7y/x2ps0s9tzMvU0cvQbdDHZcfOw8JiZcrOzs/HyMmukoxXh09OUGhNYYd6mqy2vMTJz8/V1cu/adHP0MvKzcrSbdTR1dPEwb/JZcfEwcHHzW1vamtsz2pvcWvMeMnIx87Pws9s0M3RbG3Y125vamptb25vbGpsbW9wcW9tatDQztJtbNbT0WpqzMfJz2twb2hmaWhmaW1saM3Q0WdnZWdqamlnZ2bCvmJlZ2hpamtramptcG5sbW9vb25ubGxta2dlaGpra2poyMDAx2dra2xxdXV1coZ/i34Df35+hX+Dfo5/AX6Kf4Z+kX+FfoZ/in6If4J+jX+Ffox/jX4Ef39+f4Z+q3+DfpZ/j36Wf5t+hn+cfgZ/f39+fn6Gf45+gn+KfoV/l36EfwF+rX+EfgF/i34Ff39+fn6Nf4J+hX+HfoR/hn6JfwF+j3+Cfo9/h36Df4t+g3+Ffox/BX5+f39/hX4Ef39+fop/gn6Gf4R+An9+iH+Dfp5/A35+f5d+h3+Dfo9/iX6Gf4R+BH9/f36EfwV+fn9/f4R+AX+GfoJ/hX4Ef39+f4R+AX+HfgF/hn6Gf4R+iH8Hfn5+f35/f4R+BX9/fn5/hn4Ef35+f4R+gn+LfoV8g32NfgF/iH4Bf4h+AX+GfoV/AX6Ef4h+CH9+fn5/f35+kn+Efgd/f35+fn9/hH6Mf4N+in+CfqB/hH6JfwICBAA7gPz49vX4+/z27efl5uzx+YKKjpGSj4iCgP799e7m393e4OXm7PiB+vL0/4KChIeIhoP58fD6gIKDhISEh4CIiIaEhIWFh4qJgvb2/oGDhIaGhIH9+PmAg4H37Ons8fT5gYODgoD6/IGEhISIjIiA7+bx+oGChf3j3++Ch4aIi4yJhID89fP9hYeHhYODg4SDgoGDhIH48e3q7e3y+v6AgoWEhIiPi4Hw7fqA/f3/gYOGiYuKio2LhYOC/vyAgoCChYeLjIyLiYqPlo2EhYP59PP4iZmbmZmTi4mJiIP9gISHiIH9/v3v3+P7gf3+9+rj4uT2hIT9//3//fT7gYGEg4OJjoeFkZSF++fMwLS+yMnoiJCXn56Sg+jc9oWBg4PugIiB7fqDiJOThYD7/f76+vfo4ujn493Y3PCCgPT2gE3+9O3r7e7t4+Dh1t75kaKZ/dbOxdbbzdb4hIOC9PCIlYjw2s7S2uns2NT0jZeI9feBg4L9/ObZ4+vw9Pn19eze5/Hx6ubw/IWFg4D8gYSCgID29/38+/6Ag4iQk5OOh4WPm6KjoJmVi4H49/yEio2Qk5OWmJWUjoL5/f38goqHgoCBgoGA+PqChYOA/fz3+fz/+/b7gYD89vX7g4aGhYaGh4iIhID7/IKGiIeHhoWC/PX0+YGDg4OEhoiIhPv18e/t8fT4/oSGg4D++/yBgv+AgISIiImJiYeHh4SCgYSKj5CPjY2PjoyFgICChIaHhYOFio+Oi4SBgoOEhIeNk5OQjYqEgoOFhoeJiYeFgv76+fmAg4OCgoD37OLZ2OLt8e3q8v3+9Oru/IOEgoGAgPr2+f7/+/+ChYeLjIuLiYuOiYSBgIGAgIH+7+f4i5CLhPz8gIOC/4WIhYaIhoWGiIaJj5GQkZCPjo6SmZ+goaCflpCQjo2Mh4iNkY+Qj42MiIGB9eLp+YCDgYKEgYSBhIiJgfH5goH5+/vv7/qE/e7y+fr6gYWG/PH3/u/ngIaBgfuD/vL3/oOEg4OJhPT7iJKNhYD07/j06fqE8/P1+u7y+u/xgPH9goaC/Pv8+/z9/YSIhYD8/4OGgfPr6vDr6PaA+vyGg4WC/IOEiITx7oCCgPf3+f/++e/r7uzq5+nu5ePgnNDl+IurhYaigouw+vLa3O/9/vyEiYeIioSE++/47+Dm6/Dt6fSBhYSGgPuEhISCg4D7/YD98+be8vLh09rp8Pf7cPb2+YKIhfv3gISEhYiHg/r3hYOAgISDgICDiY2KhoL7+v+EiIaGiYeDgID89/yBgoH/gYWDhIeJhYD4+oePj4uHhYSEhIKBgYKCgP6AhIeFgv7+/PXx9ff4/oaKi4uLioiIg/fs7fP3+PmCh4eEgYGAdOXh4N/i5ujk3tva3N/h5XV6f4KDgXt5ePHs5eDc2NfX1tfX2uR149zd53Z3eXx9fHno4eDndnl6fH1+fXx9fX16eHd3d3l+fnjj4+t4eXt9fnt56+bod3d13dXT2eHl63p8e3l35+Z2eXp5fH56c9XN2eV3enzr1NHfeHx6e3+Af316ee/s7PR/gYF+e3p7fHt7ent7eOjj3trb2Nrg5HN2eXh2eX15ctrc63jq6Ol2d3p9fXt7f399fn7z7nd4dnd5fYCAf318f4V/eXp45eDd33mFh4eIhYB9fXx46HV6foB69fn47N/l+n73+fLl4N3b5Xh46ezr7e3j6HZ2eXiAeX6Ce3iBgnjn28i+srnBvdV5fIKIh35z0cngeXZ5eNp0e3TS3HV9iYt9eO/y8ezq5tbQ19fX09DU43d04+l36uPd3N/e283Hw7e+1HuIgNvCvrnL1MjK4Hd4eufkg4+B48/Gyc/e4cvB2X2JfunreXp57e3Zztrk6/L38vDl2OCA6ejg3OTufX57duh1d3d4eXnt8ff08fF6fYKJjIuHf3uBio6PjIaDfXfr7PB9gYOFh4eJiouOiX7y9fX0e4B/e3p8fX598/N9f3157+/s7fDy7+rteXjt6Ofsenx8ent8fX5+e3js7Hl9fn18e3l15eHj63t8e3p6fH5/fOvl4+SA5ezw8fR9f3168+/we3vwd3l8fX5/f319fnx6ent/g4ODgoOFhYJ8dnV4eXt8e3p7f4OCf3t4eXp7e32BhoeGhIF7eHh6e3x8enl4eOvp6ut4e3x7e3rs4dfOzNPb3trZ4u7w597h7Xp6eXh4d+nl6u/x7vJ7fH2AgIB/fn+CgHyAeXh6eXh47d/X4Xp9enTi5np78Hx/fX6Afn19fnt9gYKAgH9+fXyAhouMjo6NhoGAfn18eHd8f3+Af31+fXl56NXb6HV3dXV2dXd0dnp8duDpenrs6+rh4u197+Dh6Onod3l55d7m7+Pbd3x4eOh69Orq63h6enh9d9zmfoqEfHiA5ODr6uDvfOTk4tzL0uTf3t7se3557O7v7e3r6Hd8enbk5Xd9e+ri4efi3OZ37el6eHl26nl6f33q5Xp6duPn6e3t6eHe4eHh3d7g083IirPM5nBkOkFHTnOg6uHJzeHw8u57gH+Bg39+7d/m28rP2eDe2+Z6f35/eOp6eXl3eHaA5uh26eDSyt7g0cTL3Obx9O/t8oCGgu7mdnp5eXt7eertgYF7en18eHd6foF/fHfm5ex7f3t6e3l2dHbo4+d3ennveX18f4KDfnnr7oCGhYJ+e3p5eXd3eHp6d+l0d3l4durr6+bj5ufo7XyAgYGCgYB/fOrg4OTl4+R3e3x6d3aAY8TBvbq6vL69urq8wMTGyGZqbnJ0cm1pZsvMyMbEwsTGx8jGxclmxsHF0WtqamxsamfGwMDHZmdoaWtsbG1ub3BubGtramtubmrLy9Jqa2pra2lozMbGZ2try7+7wMjO1G1vb25rzctnaGhnamxqZLyzu8Vnam3SxMXUcnRxb26AbGpnZsvJytRubm1qaGlrbW1ta2tracrEvrq+xMjHxGJlamtqa25pYbW0wWbR0c9nZmdpamprbm9sa2vOx2NlZWdlZWVmaGdmaXBqZWhpzcjGxGhvcG9xcnFvbWxq0WlrbG5r1djXz8XL227Y2dfU08/Ky2hmxcjIycnI02tqbWuAZ2puaGNqbWXHw7i3sri7s79pa21vbmplvbfJaWZoZrdgaWS3u19ia25mZ9bc29PNx7q4wcbIxcLCzGpnx81pzMbCv769vLOxs6OVmFpoY6iaoaK0vbe6ymZjYry7anRrvaujqrfLz7msumdvaMTEYmJjxMa4sLzFytDUzse/usiA0tHLxsvPa21ubNRqaWhpaWnO09nVzs1oa3F6fXpyaWZtdXl5dnJxbWjMztJtcHBycnByc3Jzb2fL0tXVa25ubW5wcG9s0dNub2xp0tbX3N/f3NfabmvQysvQbW9ta2xtbW5ua2fHxWVoamtra2pozczO021tbGprbG5ua83Ly8yAy8rIys9sb29u2dfVa2rKZGdpamxvcXBwcG5ramxwdHV0cnJycW5nYmBhYmRlZWVnaWtpaGZmaWtpaGlscXNzcGxlYmRmZ2doaGdnZsrKzMxoamtqamjMxLuzsbnCxcC6vcXHwru/yWlqaWhnZ8zKzNLU0tVrbG5yc3JxcHFyb2yAa2ttbnBx3tDHzWxrZmPFxmVmy2praGlraWhoaWdobGxsbWxsa2tsbW1ucHBwa2hpamppYV9iZWRjYmNoaWhr0b+9wGBiYGRmZGNiZWlraMjSa2fHz9jU0ddw1sbFysrLZmZpycLFyMC/bHJwb9Nu3dfU1WtpaGluZrnDbndzb22Az8rQy8HKas7U1MmzvszL0dHTZ2Zm0tfY1tPPy2VoaGjN0nF3cdXMyM3Nyspiur9oZmdp1W1rbm7S03Fwbc7JzdjZz8fJy8jIys3Tx7WrdpWpyWVVMUJgXWuY3NS9uMLR1NBtcW9vb2pqysLKw7i6vsG+vcpscG5wbNFsbG1tb2uA0NJq0szDu8fGurO8yMvQ1NDP0Wxxbs/HZWhoaWtsa9LZd3VvbnBva2lrb3JwbmzS0NNrbGloa2ppaGnPys1rbm/fcXNvbm9va2fKzm50dHJwbmxramloaWpqaM1maGlpadLT0ce+vsLHzm1xcnJzc3Jybs/Cv8DAwMNma2toZWQBf49+iX+NfgF/hH6Hf4R+lH+Dfod/Bn5+fn9/f4d+hX+Cfoh/hH6Df4R+iX+Efo5/iX6Jfwd+fn5/fn5+jH+CfpF/hH6LfwF+hX+HfgF/iH6Cf4d+jH+Jfod/g36EfwZ+f39/fn6Gf49+BX9/fn5/jX6Df4l+CH9/f35+f39/in4If39/fn5/f3+UfoR/AX6Gf4Z+kn+Dfox/hH6Jf4J+hH+JfoJ/hH6Lf4J+iH+Efol/iX6EfwZ+fn5/f369f4R+hn+RfoZ/h36Sf4R+hH8Ffn5/f36sf4R+jH8Efn5/f4Z+AX+GfoN/hn6EfwJ+f4R+hn+CfoV/hn4Bf4t+g3+HfoR/BX5+f39/h34Df35+hH8BfoR/BX5+f39/kn4EfHt7fIR7Anx9iX6Hf4t+hX8BfoZ/A35+f5B+BX9/f35+h3+Cfo5/g36Jfwd+fn5/f39+iH+Cfo9/AX6Ff4l+iX+HfoZ/AgIEAID9/Pr19vv06ev2/P+Ag4WGgoD++vn8//z08v2DhPXs7YKFhPv5/Pz59PPz8vDu8/r+gYKFhP7z5+v4g4SB9/L6hIiJiYmHhYWHhID7/4H/9vL5goGAgoaIhYWCgPv39PTz8PT/hIGBg4aGhYOCgoCBg4SB/vuAgoKEhISDgf36/xL++/n17ejn4+Tr+IGBg4WIhYGFgICCgYGFiYmLjYiCgYWLj4+Pjo2MhoKDiYyHgv3+hYuLiP7w9oCCgID+/f+B/4WPjoqF+Ozj4O727er4jZ+jpqOXjIqGg4OFiYyMhv78gv/y+IOFgPLx+/n07/Dw9Pfx+oGA/f3++fn9/v7y3+Tt5Nnm+YDu2N7m6OHg94f8gZuhk4CH6NbQ0NXshIOE+uvujJmcioGAgfn0+vL5gfv4+/jt8fj57fKDh/zly8TX5u3r9P+BiIyLh4DozsGxr77J0NbS4P2KkIX27ufY0NbW1+fy7/uCgYKGiYuC+oGGgvnr4Ojfy8jT1tPc5/Hw8Pr68u/t6uny+vf09/2AgoSLjYqFgoCCgf7j6vPn4+bp9YORpqyoqKmkmo2C+vqAh4uRmpyRiIL4+4KGio2MhPv4/f/79/P1/f+AgYKEhoiHhoDv9YGGiomGgPz7/v+B//bu7vHw8/Hz/IOEg4GAgIGCgYGChoaDgISKjIiCgID89PPy9Pj/gYGA/4CBgv+Aho2QkIuHhYCFhISFh4mJiIaCgIOGhYH27urp5ePn8PuEiIqJhoSFh4P6+4KEhYeJiIeHhoSEhP/57urq7PP/hIWFhoiHg/+BhYeEgfvz7+3u9f6Fh4SCgoOEhIaFg4KEhISFh4mMjoyHhYeF//bw9fyAgP75+Pbv6uz4goODg4H79vuBg4H19ID+hIKBh4uGhYeJioaHhYOEh4OEhoiGgfv+hISBgIWPkYmBgPz7/fr2+f+IjIqFgYGEh4L++uzrg4+M/enl9vLr+//r8oKEgPuDh4qMiYeD9eT9ioGBgfzy8Pj8hfTX1uqEhYL/hYT+gID+/oL84Nbo+fb2/oPs2vX5+oKG/PL8gYCA7Orz/4KGi4qPkouE9v+Cgvfu7oCDgYOHg+rvhoH3/O/k8v2C7ePh1fL+g4L5+IH++YKCgPf48d3c8OjJtsjm7uTw+ene2/eHhf/r4uDt/Pn3/P749/CAiYX++Pn///77/P/+gIKBgIKA/Pj1+4GDhYT97eWBjo+NiIP5+oWIgFzt3+Ln7/3/+vPu7e7y8fSAgP3/gYOGh4SGioiDgYKEhoaCgYP98PmB/v6DhIL+9+zy/fn0+oGB+v2EhYSDhISEh4eEgYGEhYH79/uBhYaEg4OB/ICEhIODgoGAgYSECYL/+PHq8IGEgoDl5uTh4+jf0tPe5Od1eX1+enjs6Obm6Obg4Ol4eeLb3Hd5d+Pi5efl4+Pk4uHf5Ovxenx+fvLm2t7rfH177ejufH5+fn17eXh6d3Pj6Hbq4uDneHh3eXt8eXh1dOXk4+Tj4eXve3l4en19e3l4d3Z3eXp47Ot3eHl7fX1+fPTx9RL08vDr497b19ff7Ht7fX6BfXmEdw54eXd3eXx7fH15dHN3fIR/gH5+enh5foB7duXleHx8eODW3XNzcnLk4+Nz43d/fnt23NDJydjf2Nbjf4yOkI6Ff357eXp8gIOBeufpeezi6Xx9eefn8O3o5OLh4+bh63l57+7s5ePm5+nh09jd0sXS6Hnhztbg4dfS4XfecoeLgXnRwb2/yOB8envo2dh9iIt+gHh3d+Tl7ujue/Dt8O3j5e/z6O2AgvDZwLjH1d3d5vB5foF/eXHNurOlpbO+xMrH0+x/hXvm4d3OxMrM0OHr5/F8fHx+gIN87np/eujc1d/azMrT1tDU2+Lf3ufp4+Lh39/p8Ozo5+t2d3mAgoB7eXl5893g49XT1tjjeYORlJCRgJKPiH526e14fYGEi4yDfHfm63p/g4aGf/Hv9Pf07+rs9fp+fn9/gIB+fXjk6Hp/hISAe/Lw8vN68Off3+Lh4+Hj6Xh5eHh5e3x9fn19gIB9eXyCg396eXnw6Ojn6u7yeXh37nh5evF4fYCCgX98e3t6e3t+gH99end2e3+BgfvzgO7p5OLl7feAg4KCf3x9f3zu7Xp6en1/fn5+fX19fvTu5eLh4ufwe3x9foB/e+53eXp4defi4N3e5O59f357enp7fH59e3p6eXh5enx/goB7eXx66uHY3OJzderm5ufi3dzjdnV1d3ju6e16fHzs6vR+fHl+gXx7fX5+enp5d3d4gHV3eHp5deTkd3d1dXmChH12dufl5uXi4+h8gX96d3h6fHfr7eLge4SC7+Dd7u7q9/rn6np7d+p6foKDgX566NrsfHJydevm5erre+HAvtJ4eXbsfX3wd3jv73nnzMHS4uHj6nni0eLi5Hl+6+Hrenvm4ev4fX6ChImNhX7u+X58gOrh4Xh7eXp8d9baenfl5djS4Op42tHUyuHue3no5nbn4HZ3duTi2M3S4NCrlKrM2NTh6dvPyeJ+ffPk3tjg7u3s8fDm5+N5gHvs5ejw8vLv7u/veHt6eXp57uvm7Hh6e3rs3dR4hIaGg4Dz83+BeeHU1dne6enm3tra3+bm53h4WO/xent+fnt8f356eXp7fXx4dnjp3+t78/N8fXrq4tfe6ebh5nh56u18fn59fX18fn56dnV3eHTh3d9ydXZ1dHV053Z6enh4eHZ2eHt8fH177eTd2N92eHaAzMXAvcDIxby9xsrMZmhrbGpp0tDPztDMwsDGZWO2sLNkZ2XExcnLycXExcbHyM3S1mxtbWvLwLS4xGhqaMfFzW1xcXBvbGtsbmxoycpmyMC8w2dnaGtvcnBuaWfMysrMzszM0mxramtubWpnZWNhYmRoa9nYbGxrbW1ub23W09aA1tTT0c3MzMfDwshoaWttb21raWhnZmVmZmdrbWxsbGlmZ2pvcnFvbWxsaWhobXBtacnFZWdnY7ewt2BhYGC+vb1etl5lZ2Vhure4vMnMvbS5ZnJ0dnZzcHBubm5vcXJuZsDBZMC3wGlqZsfL1dTRzcvIyMzK0m5u1dLRzMrNzceAuairs6qeqL1mw7nDysi/u8NjrlRmbGZis6yqqay8ZV5bsKyuYGZqZWNkZ8vQ2M/Tb9nW2dnV1tjWy9NxcdC9pp+zv8K9wsdkZ2lnYl2voJKEjaWwr62lq71nbWe+ubq1sba1ucrTz9VsamhnZ2tp0mppYbivrr28r6uxs7C3vsWAxMLLzsnLzM3N1trUzMnJZWVnbG5ubGxsa9K9wsi9urq3vGFoc3VydHVzb2pmz9Vtbmxrb29mYV20u2Noa25va9LT2NnW0s/Q2NttbnBydXd3d3DPz2tvcnJwbNrb399v2tLIxsjIycXFy2tsamhnaGttbWxsbm5raWxydHJubWwW1tDQzc3O0mlpaM5nZ2jPaGxwcXBua4VqI21ubGtpaGlucXJw1s3FwLm1uMDLa25ubWtpamxqzcpmZmdphGuAaWlpatDNx8bEwsXNaWloaGlpaMxnaWlnY8G5tbO1usFlZ2ZlZ2hqamtsa2lpaGhqbXBydHFta2xs0snAxctoatTU1dHIwL/FZmZobG7Z0tRsbWnBvMZqa2pucGpoaWlpZ2hoZ2hoZWdoamlnx8hnZmJhY2ttaGNm0NXZ2NXR0G6AcW9ra2xvcW3Z2MzJbXJsx769z9PR3dzJxWVnZMtqa25wbm5t0cPRbmdqbNjT0tfddM+ztMFqaWXNbm/ZbW3Y1GvOt667xsvT2m/NvdDP0G1vz8nWcHTd3eHjbm1vcnVzcG3R225s1dfUbW9vbm5qvMFvbc7JvLnH0mzFv7+vws6AamjIymnQy2tras7IvbC0xr+hj6LE0MXK18/EvMxratLNy8XK1NLS1dDGx8drb2nHwcfU2drY19jWa2tpaWtr09DO02xtbGrHuLVncHFycHDY2nJ0bsq8uLi5x87Py8nKz9PMyGho0NJrbG1taWpubGloaWptbWtrbtnS3HHd12wxbGrPyL3F0dLR1Gtpy85tcHBvb25tbm1raGhra2nR1NpvcXFta2lmxWRmZmRkZWVnaYRrCWjHv7m3w2tubIx+hn+Jfgh/f35+fn9/f45+hH+FfgZ/f39+fn6LfwN+fn+Efop/iH6Pf4J+iH+OfqZ/gn6Ef4N+hH8Ffn5+f36Ff4l+kH8Jfn5/fn5+f39/jH6Cf5B+AX+IfgJ/foV/hn4Gf39/fn5+h3+FfgF/in6Cf4p+hn+MfoN/jH6HfwR+f39/nH6Kf4l+i3+Cfol/gn6Gf4p+iX+CfoZ/hH4Bf4p+ln+Hfgh/f39+f39/fpd/iX6Jf4J+jH+Ifod/AX6Ff4d+mX+FfoJ/iH6Ffwl+fn5/f39+fn6Wf4J+in+Hfol/hH6Df4p+BH9/f36Hf4N+hH+FfgF/hH4Mf39/fn9/fn9/fn5/iH4Bf4V+B39/fn5+f3+Efoh/B35+f39+fn6GfwR+fn9/hn4Bf4Z+Cn9/fn5/fn5/f3+TfoJ/jX6Df4p+hn+EfoR/g36GfwV+fn9/f49+BH9/fn6Rfwl+fn5/fn5/f3+IfgR/f35+j3+Dfod/AX6Of4V+g38CAgQAgIaFhIH58/P1+vj29PHw8vT29PP2+fn18vT6gYH//IGGiIaEgPz/gYOCgP38/f+Bg4SEg4GCh4yNjIuMjIqHhYWDgoGCgoOCg4OEhYSDgf/9+vTz+YKFhP/19fv58Ojq9vn6/oGDgv/8/YCCgoKBgfz5+/+A/4GDhIKChIaDgoODgIGA+vPz+//58vLx9vz+/Pn4/4OGhYH9/ICA/v+EiYqEgoiKh4mLi4qJi4yMjIuPkpGRjo6PjYyLkZSQjY2Pj42LjY+Qk5iZkouFgfr3+YOOm6WkmpORjIaJiomIhISC/PTy9Pf7gIKF/+7q9v/7+Pj27tnI0OXy+P348u/z/YH9gOPQ1Or4gIeG/u3s9P+OmInw94aOioL34Nrl18XJ2NnA1P6Nm6mhj4qLgvv46uDe84GCg4T//oH484CBhoqA//bt8f+BgIuRivr09/Dd4evw9Pr8goT5+4KFgfft7u7o7PHx7uvm6fmDhYSA/oD//v+AgYGBhoeA8Ojl7ezl6vDqgOn1+PDu9v6Dg/718e/z+vn8g4SA+/yAg/3p3OX6hYqOhoGGioSA+vL1/YKIlpiF9e/n4en19/iAhYiIiYyOiYGBh4uOjYuJiYiLlZqSioOChIaHjJGUjoyJjY2Jg4GBgf758+7t7+7s6uvu8/qBg4H9+ff39vj4+/v49PP8g4aHgImJh4WD//P0+oCBgYKEh4eHiIeKjpCQjIqIiYiIhoWFh46Yl5GNi4iGhYeIh4L78uzv+YGEhoWEg4WHhoOBgIGDhISFhID9/oOFhYSGiIiFgv/9+PHr597b297ZzsrP2uj8houKh4aGhYSHio6Oi4eEhYaFgPz7+vby8vf8g4qLgIuLiIiLkJKQjouFg4iGhYOBgYSIioqHgvjy9fr8/v6AgoODhYOA/4CAgoaJh4KBh4yNh4T98ezm8IGGhoSHhYH8gIGA//7/gPz1+Pz59O+AhYGB/vn29/79/oeMiP38+vn/g4WFgPDe4vWCh4Hn09fs8O/xgIGC/fb38vPz8/T3gPjx/oHeuqzY7fLu+YCCg/nx68+yyuzzgH+EiIDx84OOgPiBgOjm/oT+/ICAgf/z9PLv9Ozy/vjq9Pf1hIf74+735ev4/vfw8O/05+v/gvz99v6KhvLz/vTt8PD2gYaFhIX++f79goWA8PSDjYfw5uv3gIOHiYmFg4WJioWFiYaDJIGDg4H8/vfu/4SB7+z8g4GDiIeB/Pf19vr/gvvv9v3//4H8/oSDWIKBhYiHgvz+goSFhoiIhIKEhoSAgYSFgfj6hIT+9vn5/oiOhfTn7fX8/v2BhIOA/fr3+Pfz6+jy/ICAgIKGiYuLiIL69oCBgoOFgv318fDw7/D3g4qLh4U4eXh4dubj5Ojs6+fk4eHk5+rn5OPj4d3c4OV2dufjdXl8e3h26ex4eHd26urs7nl7fHt6d3d6fX6EfQZ7eHd4dnWEdIBzdHR2d3d3duvr6+jp8H2Af/Tq6e3q4NfY5Ojq73p7ee3p6XV3d3Z2d+vq6+9373l6e3h5e317enx8e3vy7Ozx8+3n5uPm6uzs6unwe359ee3td3br7Hh8fHZ1e318fn9+fX1/f39+fYCBgIB+gIF/fXyAhIF/f4CBf319f4CCh4CIgn13ct3a4HeAiY+OhoKDgHx/gH9/fHt45t/e3+DidHl98ODe6fHt6uvo38m6w9jn7vHs5N/i7Xnv2MbI2eZ3fX3v39zi7YOLfdjhe4N+ddzIxM7Eub3Hxam63nyJlIx+fYF77Ofc19foent7fO/we/Htenl8fnbr5Nzc6HZ3gICGf+jj5NzJztje4Ofsenrn6Xl8eOXb3d/a3uPj4N7c3/GAgoB89nz29fV7fHx8gIB66OPj6Obf5Org3eTl3dvm835+8ufj4eTn5OZ3eHTi4nN25djS3O59goeBe4CEf3vu5+nteHyGiHrl4dnS2eXn53Z6fX1/goR/eHmAhIiHhYCEhISHkZePhn9+fn9/hIiKhYOAgYF8d3Z3efDt5uDd3t3c3N/j6e97fHrv7ezr6+vq6unl4N7neX+ChYWDgH305+bqeHl5ent9fHx9fH+ChISBf31+fX17e3p7f4eFgX59fX18fn9+eu7o4+bue31+fHt6e318eXd1dXZ3eHt6doDo53Z5eXl7fX59ee7q49vW0szLzM/Mw8DFzdnrfYOBf39/fn6Ag4WGg398e3x8eu7r6ebi4+Xnd3t8fX57eXt/goGBgX18gH9/fXp6fICDgn566ePm6unq6XV4ent9enXndHV2e359eHZ7gYN+ffPr5d3jeH18eX18d+d1eHfw8IDuduzp6Obg3Nh0fHt79PDs6/Hw8X6Be+Pi5OjyfYCBe+jX1+V4fXfXys/g4eDjeHp66d7i4ufo5+vw8urwetq3pcvd4d3qfYB/7uXiyKe32uV9gIOFfenne4V35nh43drwfe/reHl3597g3dvg2d/r5drj5eN6fvDc5uvY3efq44Df4+Lj09Poeevt6fCCfuLj7ujk5+XoeHt6eXrt6/Hxe3543+N7hIDl3OPteXp+f397e3+Dg318f318e3t6eOrr5N7ufHnj4fB7eHp/fnvx6+jo7PJ66tzi6uztd+rqeHl5eXd4fX9+eu7went7fX9/enh6fn56e35/euzufX7z6jru8PaDh3zi193j5+jod3p5d+vq5+jn5NzY4Oh1dHJzdnl8fXt35uR2d3d3eHbn4N3d3Nvc4nh+fnp5OWpoaGbHxcjM0tHPzs3P0dXV0czIxsO+vsLJaGfJwmRnaWdlY8LEZGZlZMjIycxoamtqaGZlZ2lpZ4RmgGVlZWRjYWFhY2RmZ2hpaGhmy8jHxMbObG9u1M7P09DHv8DJzM3QaWlnysfIZmlqaWhnysrP02rTaWpqaGhrbWxtb29ubtrW2N7h29DJxcfKysnGxMlobG1qz81nZsrJZ2lpY19jaGpsbGpoaGtub29vcXJycG5tbGpqa3BzcnFwgG9ua2lpa2xtcXJubGpmx8PAYWdweXx1cXJycnRzcnFubGjHwsPCwMJlaWzTx8XN09DP0NDOwbK0ws/X2NPLxcbOaM25ra+9yGdracO7wszHaW5ksbVdXV1cs6WnuLOnq7GoiJKxYGdwbmdrb2vQzsbAwNJubm9v2dlu0s1qaWttgGO/vb3BxF9cZWpnxMbMxLO1vcPJ09hua8XFZWhmxcLEw7zBxMG6tLTB1XJ0cm7abNbW12xsamptbmfBu7m+v7zCyMC7wsW/v8bPa2rPx8TDxsnFwmRlYsHFZ2rRw7e6yGltcW5sb3BqZb+0srRcYm1vY76/vbi8w8G/YWRmZWZpB2toY2Vrb3GEcIBvb3V6eHVycXBvbnBzdXFycXR1cm5ub3Dd1s3GxMbIx8XExcbKZ2lp09XX2NfX1dbX087M021xc3RzcGxqz8jKz2lqaWlrbW9wcHByc3RzcG1ra2tsbGtqbHB2dXFubGppam1ubmrNw7y9w2VnaWhoaGlqaWViYWFjZWVmZGC+v4BiZGNiZGdpZ2TEwsG/v721s7W7u7Ovr7W/z21vbWprbGxsbnJ1dHFta2ttbm3W09DOzc7OzmpucXJzcG5vcG9ubm1pam9wcnFtbG5wcnNycdrV1NXV1NBmZ2hpbWtnymRjZGhra2dmaGtubGvPx8G5vmZqamlsambLaGpr29/fcIDd0cnIx8bBZ21sbNTS0dLX19dvcWzGyMvQ229wcW3Mu73KZ2llu7a/z87FxWpsaL+2vsLJysrN09LGymvEo5K6ycrI0G1wcNTNyK+XrMvUcXJ0dGzJxmx0Zsdqa8rN4HLW02xsa82/u7rDysDD0cy/yMzLb3PWwsjJtrzGysXDyoDMybm9z2rPzsXOcG3Kz9nSzM/P1G1xb25u0c3W2nBybcnIZ2xqxsfP0WhnamxubGxvcW9ra29vb2xsamjN0c/K2G9sycjXbGdnbGxq0c7P0NPSaMW5wtDX2W3Z3XBta2loaGtubmrNz2lqa2xsamZkZ2xsa21vcWzNzm1u1tPW1zjac3VtxLi+yNDSz2dmZWLCwcLIzdDPz9XabWxrbG5vcG9saMjGZ2lpaWpozMfEw8PBwMJma2xraoR/ln4Ef39+foZ/gn6Ef4R+oH+GfoN/jH4Gf39/fn5+hn+EfgJ/fo1/kH6EfwZ+fn9/fn6vf4N+kX+GfoN/ln4Bf4Z+g3+FfgV/f39+foR/jH6If4Z+hH8Ffn5/fn6Ff4V+hX+Lfgd/f35+f39/jX6EfwV+f35+fod/kH6Cf4h+B39/f35+f3+Ffol/hH6Ff4h+qX+NfoN/jX6If4R+pX+FfpN/gn6Jf5F+k3+Ifpt/h36HfwF+jX+Ffod/CH5/f39+fn5/h36Ef4d+g3+FfoR/hH6Df4d+g3+MfgF/iH6Df4h+hX8Rfn5/f39+f39+fn5/fn5/f3+OfoJ/kH4Bf4R+gn+IfoV/hH4If39/fn5/f3+EfpN/hX4Ff39+fn6Gf4Z+AX+GfgN/fn6Kf4J+kH8Efn5/f4V+g3+HfoR/in6Kf4J+hn+IfoV/AgIEABqDgf758urp7PHx9v6DgYCA+vTy+fn06O3y+IT6Jf6Bg4OCg4WFg/739fLy7Obg4en1/v+BhIiIiIaFhIKA/4GEhoWEg4CFh4iJiYmHhYWFhIWHiYiFgv779fDy+oCBgP+Bg4WD//38gYWIiIaCgICCgYCAgPrt4+v2gIGA+PDj2dfZ2dfT1uLs7vL3+Pn6/P7++/b19fr+/v2AhImMjIiDgISKjY2Ji5CRkIyLjIuNjo2Ni4iIi4+Qj5CVlJOWmJqYlI+LiYCIio2PjIuSn6ahmo+C/4GEhoeIhoP8/oD/gYKA+/+DhYDy5en7//jz9Pb09vj39/j5+fPr8vn29fPw8vuChoP58/eJl5aNhoL8gYiOjIX9+/P49+zs9/uBgo2anJCSj4T8/vbxgYL79/376ez7+PT38vWChYiNioiE/f//8+bt/oCC+vj6gICBgPrv7v6A+vT7/oD+g4L26/P6/Pv8+/iAhIiJgYCCg/34/Pz27+vl3d3b1eHz/PDh4+nn4+jg1dbf7vH08fD3+oCBgYWKkJSXlpGKgoGDg4GAgID7+oCCg4aOkIaDgYGEh4iE/P+GhYGBhIaMj4uGgvXn5e75gIOEhICHiYmHg/z7g4iJiomIiYyNi4qMjY6NioT6+4D//vn38+3s+IOFgoCA+vT1+fTy+Pz9/4CA+/b4/4OEg4OEhoeGg4KDhoaFhIWFg/z29Pb8goaJiomHhoWEg4SJjZCOioiGhYOCgoGBg4WGhYL/gIGB+e3o6Obj4+Tq7/2FhoHx6IDn6+fk5evw8e/r5uTi5+zx9/v8+PHp39zk8f6DiY6NhPv29PmBg4aIjIyKiYeFhIaHhoWGiIPx7/2Fh4eGhYWFgoD8/oOHioiEgoOGiIeC+/z8+vyAgYOFhoWB/Pj59/X6//76/4KDhomHg4L++vyAgoD7+/+BgYOB/Pjz+YH9+oD18PiGiIL7gIH48/P1/4GChIWC9e7v/4iLiYmD+/f08vqHioeBgYD76+Tt+P+EiI2OiYT89/Le3Ofs9oOF/vr7g4SGg4GKjIX794CDhoCEiYf48PWA+IONkImHhIOB9e3t8/Hv8f+Ghfn5/f6EhIGDhYiMgPWDh4iC/Pry6e33/4D0+4iA7viCgfqBjY2IiP/03tfm8Ons7/OChf71gIL5+Pn29PqFiIeD8/yBgYL78fH6+faDjJCLhID7hIf77O3+//iBjY6Ig4D8+/Hr7u74/vr194GD/fqHioX89ezo7PeAgPf3gISDhYaGhoWCgYOIioHm4fKEiIaA/v348feBhDmFhoaEgfz19Pn6+fr9/4D++ff49vj9goOCgP/9/v/++/v59/n7/Pj19vb08fP6goaHhYqOlJKOiYaAeHbs6eTd3uHl4+bseXh4eu/q6Orm3tPW2uDj4+Tk6HZ4d3d3eXl35+Hf3NrUzsrM09/m5nN2enp6eHd2dXPpd3t9fHt6ent8f4CBgYF/fHx7e3x+gH99eu/r5N7g6Hd4efN8fX988O7uen6AgH56eHd3dXNzc+HWztXgdHV15eGA2NDOzs3Kx8vV3d7i5+fo6evu7enl4+Xq7Orpdnl8f358d3Z5foGAfH+EhYN+fX9/gYF/f3x6en1/gH+AhISEh4iIh4N/fHp6e36Afn6BiY2Khn916XZ5fH5/fnvt7XjveXl25u18f3vp2t3w9O3p6uro6urn6Ovt7+jd4uro6OaA4uTreX177+fme4aGf3p25HV+hIF65uTe4uLZ2OLkdHN7h4mBhIJ55+fg3XZ47Oru69vf6+jm6ujre3x6fHp6eOfn5tzU3Ot35uXodnd5eOre3Ot459/j53XreHfk3ePo6ebo6el6f4OEfXt9ffLu9PTu6ebe19fUztnp8Obb3uOA3tjc08bFzuDk5uHf6O15eXd5e3+ChIWEfnd2eXp5eXp68fJ9gIGCiIh/e3l5enx8euvvfXx6e31+goSBfXrn2tjh63l8fn+BgoF/e+7ufIGCg4KBgoWGg4KDhIWEgn7x9H79+vPu6eTk736Afn199fHy9fDs7/Ht63Z15uPo8X2Afn19fX+Af3x8foGBgH19fHnm3tvc4XV6foB+fHp5eHd6fYGEg4B+fXx7enp5eHl8fn168Xl5eOjb1tfW1dja4OPufH165NzZ3NjV19zg4d/b19bV2d7h5Ofn5d/Z0M3T3+58goaFfe/q6Ox5e31/gYB+fXt5eHl7enl6fHfe3uxQfH59fXx9fnx67+95fH59eXd4e319eu3s6ubodnd6fH5+e/Dq6ujk6O3u6/F7ent+fXp56+Xnd3l36+30enh4debl5Ot57eji3uV6e3XjdXiE8ID0e3t9fHfYy8veeX9+fnno4+Lf5Hp+e3h5ee7i2+Lr8nx+gYJ/e+zq59XU3uHtf4L48e96e359fYaGfuvneX1/en2CgvHm5nfmeIKGgHt5eXjn5Ofn3Nba63t54t/j5Hd2dHd7f4J45Xl9gH759ebZ3Ofu5Op8ddvjd3boeYWFf4B+6NrDvtHd2dvd4Xl77OV4euvq6ubg43d7e3jg6nl6e+3i4uzr5XmAgHt3d+18fune4PDw5niBgnx4d+vq4dzg4evy7+zue3vq5nx+eurm39rd5nd24+J1eHd5enp6eXh4fICEfN7a64CFgnz18+zj5nh7fH18enfo4+Tq7Ovv9Cz2evHp5OLe4OV2dnVz5uLi4uHg4+Li5Ofo5+Xm5uPi4+l4e3t7foGFg4F9eoBjYsTCwL3Bx87R1Ndta2pq0MzLzs3IvcHEyMnGw8DCY2VlZWRlZWO/urm4uri0sbG2v8PCYGJlZ2hoaWloZcpmaWpqaWlpamtsbW5vcG9tbW5vcHNzc3Bt087GwcLHZWZnzmlqbGvT0dBqbW9wbmtpaGlmY2JjxL66wcpoaGfIwoC6s7GztLSytsPMzMvLxsTDxsvOzsvIxsfHxcNiZGVmZmNhX2NpbnFucHN0c25ra2xvcG9ubGttcHJxb25wb29xcnFvbGxtbW9ubW1raWpxd3VybWXMaGpsbXBwbM7MZ9FrbGrR1W9ybtPHx9bc2djb3drX087O0NPTzsXKz8zMyoDJz9ZucG3Qxb9lb25mY2K+X2FjYmLDwr3CxsTGzsxkYWhzdm9ycGjIxr+7ZmnU1NnXyMrU0tDV09Fsbm9ycG9t09PQwbS5yGbGxcpoaWtr1srEzmfDusDLadJqacO6wsrLyMbGy2ptcXRvbnBw19Tc4+La0ce9vbqxucnTyr/Fy0XFvLy0q6yyvcDHyMjP0WtramxvcnV5eHVuZ2RlZGNiZGXHx2Voam10dWtmZWVpbGxoxcdpamlqamptbmxsbtfNyMvPaGuEbV1sa2jLzGtwcXNycXJ0dHFwcG9vbm1r09x06+fh29bQz9RtbGppa9fZ3N3Vz8zIyMxpatTS1NpwcG5tbW9xcG5ub3JycW5ubGrNyMXExmZpbG1saWdmZWVnamxtbGuEbANrammEZ0BlY8RjZWbFvLm5urq9vsHDzGpracnEwsG6tLO4vsC/vbu6uLq/w8jNz87KxLu5v8fKZmpwcWzPycTFZGVoam1thWyAa2llZWhubtDP1m5vbm9ubm1qZ8rKaGtta2ZlZ2psbmzU1NfY12tqa21tbGjKxsbEwcfP0tHVa2hpbGxsa9HNzmlrac7N0WhmZWPDxcvVbtjW1M/QbGtp0Glq1NPS0NNqbG9uab60tMRtc3NxbNDNx7i+bHFuaGps2s/GyMvVb3CAcXBubNHPzsHBycrQbXDX09Nsam1sbXR1cdbPam1uaGhvctfJyGfFZm5xbWxramjOzc7Nxb7C0Gxpwr/ExmdpZ2ttb3FpyGptcG7Y08i9wMvRyc5tZLvFZ2fOa3Rzbm3Ow7Cww87Jys3Oa23Tz25w2tnW0MrGZWZoaMXNaWlq0s2AzNDOyWptbGloa9tzc9TDxNfc2W93d29qZsnIxMLIytXb2tjZb3DX03J2ctzWzcfI0Gxs0dBra2hnZmZnaGhpbHBybMfH2Xd7d2/X0MfAwmVoaWtramjMxsXJzM7P0dJp0MvHx8PEx2ZnZ2jS0tPSz8vLysjJzM3LycnIxcPEyWgKa2xrbW5wbmtoZYJ/in6Ef49+iH+Nfop/AX6Zf4Z+BH9/f36Ef4N+jX+FfoN/nX65fwF+h38Mfn5/fn9/f35+f39/m34Gf39/fn5+hn8BfoV/iX6Jf4R+gn+Mfod/h34Ef35+foR/hH4Bf4R+BH9+f3+Jfoh/oX6Tf4J+jn+Cfot/hX6Jf4J+kX8Dfn5/iH6Ff4p+gn+EfpJ/hX6dfwR+f39/i36Df51+hX+EfpJ/g36Jf4J+i3+Ffod/in6Hfwl+fn5/f39+fn6Ef4R+AX+FfgZ/f39+f3+FfoV/hH6Ff4V+hn+GfoZ/iH4Ff39+fn6If4J+h38Ffn5+f36If4h+gn+Efoh/AX6Ef4l+B39/fn5/f36Ff4p+Bn9/fn5/f4Z+hH8Ffn5/f3+GfoZ/A35/f4Z+hn+Lfgd/f35+f39/hn4Ef39+fo5/g36Ef4V+h3+JfgF/h36Ef5R+i38CAgQAdvnu5OLj5OHe297m8f6AgYKDg4H++/f39vj6/oGB//r07+zr7e/09PiAhIaEgf/66+Lf7Pf6+oCBg4OAgP+DhIaFhoSA+/3/gf74+f2Bg4SEgYGA//38+/v4+fn6+fr8gIOGhoSCgPr28/uChYSEg4D48vb6/oGEgoCEiIiFgPj3/YGB+vDt7vHx8/j89+/o6Ovs7PH3/4CBhIiLkZSRkJSWlJCNjI2QlJaXmJiUkZCSk5KSjouJiIuPjoyKiYiHiIuLjI+PjYyQmKCoqaOclpCTlpOTkpGMhoH4/YKB+v+EgoD+gIKB+fDx9vbx8e/u9vv++O32gYOEgYD9+vX3/oGBgYKCgoH5+4iTkIiGhf3z/YOEiIiB9fmBgYOE+vqGjJqjmY6Ki4b67eHrg4j+8vyEhYH9/P+DgIGHiYmNjYyKiIaDgf+BhICDjJCG/oD+8u3x/ISIhPr2gP/z/YOA/YD58/n5+oCNk43/6uXj19LRx8TFztLS2N/f4YDu/Pbp7PP09Pf5+4D9+YGGgoCEhoP98uvw9/yBgf/7/oGDhoOAhouLio2QioeGhoSEgoOEhYiKhoH/goWHi4uIhYWFgvrw7evp9IGHio2PkJCRkY+KiYuJhoSHiYqKi4uLiYSBgf338vHu8fb7+/fs5uz0+ICFhPzu8PX3+v6CgyCCgIGDhYWEgoGAgPrz8PL2+Pr9gISGhoODhIiLjIuIhoSFgISEhYSCgYGDhYiKi4yPj42Ig/vz7ur0/oSEgPj08fLx8O7s6ePd2NjZ1s/KzdHRz9DV1M7Fv8DBu7GtrK+2xtrq8/yB/PXz9vf49PP7g4SEhYeIiIiHh4aEg4GA+vTu8PqBg4OC+vLw7/H3gISD/vr+hIiJhYH+gIKCg4SGhoWCgPr3/YKCgICAgYGChIeE+vuCh4uLiIT//Prx6+zs6/X+g4WEgYD47+31/oH8+v778+/y8vDz/oeLjY6LiYT129XngJGTh/v0+4qI/PP4//bzgIaCgYWKiYiC/fuA/u7n6e/17eiAk4rl5/b8goaGh4mDhYby39XR6PmBhob55fGCgIqHgf/59vLt9P6AgoOAgYaNh/XugIiNh/7//PT7/vqCifvZ4vj5+/Pu8fX2+/+Bhobz7vz55u75+/+B9vH49/2GjpGJ8uj09uvo6oP51tXd6+zv9vv//YD45fCGh4SEhP/y7veBgoOBgYT++YKC9eje3O329vPj4PH28/6IhISHYoeGgPr9/4GB+PT7+/j/hYX86ebp7fH1/oWIiIaC+/yBgPjz9v2BgoSC/Pj/gvvt6/T4+vv6/YCA/4D/+/j7hY2Pi4mJiIaEgf6Ag4WEgfn18OTRztbj5uf29vbx9/2Bg4OAgOfd1NXX2tfU0dXc5fB5eXp6enjr6eXk4eLl6Xd36uTe2tjY2Nrd3eF1eHp5d+7q3dTP2eHk5nV3eHh2dut4eXp6e3l25unsd+zo6u14eXl3c3Jx4uTn6uvr7O3u7u/xe3+BgYB9eu7p6PB9gH9+fHnp4+fq7Xl5enp6fIGBfnnpgOjteXjp4d7g4+Tm6u3p4tzb3dzb4OXsdnZ4e3+Fh4WDhYWCfn1+gISIiYqLjYuIhoaFhIOBfn18foGBfn19fXp7fHx9gIB/fX+Fio+Qi4aDgIKFg4OCg4B9eervennr8n59efJ7fX3x6uzw8Ozq5+br7u/p4ep7fn577uzp6vF8AXyEe4B66+p9hoJ+fXzq3uh5en5+eObreXh3eObpfH+Jj4aCg4J74M3Ay3R549vmeHh15+jreHZ4fH18fXx7enl4dnXodXh0d4CDeeV06ODf4+t6fXnj3nPm3ON2del25uDk4uN3hoyH9+Tg3dLNzcbCxM3R0NLW1tnl8ezh4unq6u7w8lN79fF8fnt6foF97+Ld5O3yennu6el1dnl3dXyEh4eLjYaCgH57eXZ3d3l8fnx37np9f4GCf319fXvu5+bn5/B9gIGCg4OCg4SDgH+BgH18fn+AgISBPX17e/Tv7Ovq7fL3+fbr4+bt8HuAf/Tp6/Dx8/V9fn17fH6Cg4KAf35++PT1+Pr6+Pd7fX59eXh4fH+Af36EfYR8gHt6enx+gIKEhYeJioeCfO/p5ODp835+eevo5ufn6Ofn5uPd2djY1MzGyMvLyMrPz8nAuLi6tq6sq66yvs3a4ux47efk5uXm4uDneXl4eHl4eHd3d3V0dHRz497Z3OV2eHl56eDd2tngdXp67+3yfoGBfXrweHh3eHh7e3t56eftgHp7eXl6fHx9fXx11tdxdnp7enjo5+TXzMnKzdjjdXl6eHjr5eTt83rq5+3v6+zx8u/v9YCDhYaCf3neycXUdoOGfenj64GA8Ofp7ePfd318fH+Cf3x36Od38OPd4OTm2tF0hYDY2ubqeXx8fX55e3vh0cbC2Ot7gYHu2eJ5gX94gOzm5ufk6vJ5eXh0dXp+ed/ZdXyAfOjo6OXt7ud3fOTFzuLl6uTg4OLi6Ox3e3vg3Onn09zl5+p35eDo6Ox7gIJ72dHd4drZ3XroxsTN2tzg5urs6nbh0dx6fHp7fO7g2+R4enp2dnnp6Xp75tjLydrk5eHSz97j4ed5dnd6enl1X+br7nh46ebs6+rxfX3q1c/NzdTd63x+fn166ut5eezp6/F6ent56uXrduLU0d3j6e3t8Hh48Hny7+vsfIGBfnx7enl4deh1eHp5duPd1cm6ucXU2t/s7erj5up3enp3gMK8trvBxsjHxcjLztJoZWRkZGPEw7+/vb/BxGRkxMK9uri2tre4t7phZWdmZcjDurW1wszOzWdnaGhmZsxoaGpoaGVjwsTHZsvJy9Fqa2toZGJiw8bMz9HR0c/Pz9DTbHByc3Jwb9rW09dvcW9vbWvPyczP0mtsbW1tbnJzcGvMgMjKZ2fKxcXIzMzNz9HOyMPCxcXCwcLEYmJkZmdqamZlaGppZ2ZnaWtvcXFxcG5ub3BvbGtrbG1tbG1tb3BwcXBubm1ucHBvbW1xdXl6dnNwbGxtampqbG1ta83QamrU3XJvbdltbW3W0dPV1NDQz87Qz83GvslrbW5s1NLMzdRtgGxsbW1ta9DOam9taWlpyb2+X2Bma2rPzWdjYGC8x2tsc3p1cXJ0b87Bs7dobdDIzWlpZcXDyWlpa3BxcXR1dHJvbm1t12xsaGdrbWnLZsrEw8XKZ2lmwL5iysvWbmnMZsa+v7q8ZXF2ctTGxcO4s7KsrLPAxcLExcPE0uDcz87SVdPV2NrXa87HZmtsbHBxbtTMx8nMzmlpzsjIZGZpaGZpbGxrbnBsa2poZGJgYmRna25sac9oamtucG1qampoz9LY2dTVbG5wcXFxb25ubWxucnJwb2+GcEBuamhozcvMz9DV2t7c1ci8vMLNbnh45tnY29zc3nBwb2xqaWhnZWRjZGXO0tnf4uPg3W5ubmxpaGhrbW5ubWxth25PbW5vcHFyc3V2ent5dG7UzcfDy9JsbGnLx8TExcbGyMfFwb2+u7Wtqq2xs7S4vby2raWnqqehn56go625wsbLZsnEwsTCwby7wWVlZWZnaYRrgGlnZ2hp0c3Jys5oZ2Vkwr28urq/ZGlpzs3RbXBxbmvTaGloaWpra2xqzMfJZ2hnZ2ltb29uamO0tmJpbW1racvLyL63uLu8wMJgYWNmatXR0NPVasnFzdLPzc7NysrSbnBzdHFtZ8K1srtkbW1ox8XMb2/U0NPVy8ttdHNuaWprgG5rysRp3dnV1tPNwLpodnHDxc7PamxsbW5qbGzAsKqpv9Jvc3LNtr9pcXBpzsjKztDY325taGRma29rxL5mbHFuztHSysvIw2Zuy660xMbP0c/NzszP1GxxcM7M2tnJzdLOzGjHx9LS0Gtvb2i4scHIxMTIbtGzsbnFyczNzs7NgGjKvMlvbmxsbdXMydNvcXJvbW7X13BuzcS8usbKycS3tsXLytFvbW5wcXBs1trecXHZ09bT0NRta8ayq6uutb7La25ta2jIy2lpzMrN0mpqaWfIxs1oyr65wcTGyMjKZmbNZ9HQztBtc3RycXJycnFv2Wxtbmxox8G5rJyapbS8C8LQ1NTP0dJqaWdkjX6Gf4h+gn+LfoV/iX6GfwF+h38Efn5+f4R+h3+Mfod/hH6Gf4V+in8Ffn5+f3+TfsN/DX5+f39+fn9/f35/f3+PfoR/hX6Hf4J+hn+DfoV/gn6Ef4J+iX+Efgt/f35+fn9/f35+fo5/AX6HfwJ+f4V+DX9/f35+f35+fn9/fn+FfoR/nH4Df35+h3+GfgV/f35+fpl/AX6Kf4Z+m3+PfoN/h36Nf4h+o3+GfoN/qH4Bf4l+j3+FfoR/hn4Gf39/fn5+hX8Bfol/g36Lf4J+hn+KfoV/hX4Bf4t+h3+EfoR/BX5+fn9/hn6JfwN+fn+IfoN/hH6If4Z+Bn9/f35+foR/h36If4J+hH+HfoJ/jX6Df4l+AX+FfoR/h34Bf4t+BH9+fn6Ff4R+hn8Efn5/f45+h38Ffn5+f3+GfoJ/iH6FfwR+fn9/hH6EfwR+fn5/iX4Ef39+f4R+in8BfoV/kH6EfwICBABw+fr7/Pn29Pf7/YCBgYGDhYiKjI2Ni4eBgIGDhISFhP7w5+z1gIGBgIGDhYaHh4WEgYCAgIGCgoKAgP369/r8+vv68vH1/YKDg4KA/PuBg4SEg4SEhIaHhYH98/D0/YKDgoKGiYeEg4ODgfv8gIKEg4WCXoOFiIiEgoWGgOvf4Oft7+7z/YOGhoOCgoH58u7r6+vt6+zm2N73g4SEiJGWmZyem5aPh4D9gISGjJefmY2HhoeKiYaIi4iD/v+Fi46Mh4WDh5KgrLCvqJqKgYKJjIiEh4CEgPv9/Pf3goOBgP6A/f+Cg4H+//z6+v6CgoSFiIyNiID38vP49viChIOBgYGAgoiOj5GSkpGLhYOEhYeGgoCAgP77goiHho+Zm5iWjYH6/YGA+/6BgYKFiIWA//v6+ff9goGEhf+Cg/309fPw7+rr8vr2/YWC/fvx5N3Z7IKC94D1+fjVzfGDh4uJi4yMjY+Hg/7gy8zWzsHCytHS0dve3OTx9/n8/f7+/fv26+bp7u7r7fP4+4CCg4WIiYiIiIeGh4eFhYmD6dLO1eLy/oSIjIyHh4uOkI+NkZOM/Onh3+n8hImKhYOFh4iKi4iCgYOLlJyblI6Li4yOkI6Kh4eGhICBgYWJiIaDgoWKjo2KhoL78+rr7/T4/IKFiYL38Onr7/2EiIaC+fPx8PL3hImOjYuHgv319fqBgf/y8fX9goSB//n09vqA//37+PXy8/X2+f3+gIGHjJCPj46OjYmEgoH67eDY1+Hp9/7/9eje3d7g4+Pj4ebxgouQkY+Lhfz06IDe1tDU2N7o9/+A/oCDhoaEgoD++vn7gIKEhoWEg4H48O3u+ICCgoOFh4mE+/Dr6vD6gYKBgICDh4eEg4SDgP769u/v+f/88/aAgoCAgYKCgf+AhYqMjY2Jgfbz9u/p7vqA/Pj+/4KIiIDt4NTN1+Lh1+Ht8/f39PTz8/777O/6goCKk4iA+e/r+fby7enj5O73gYL78+rtgYeFgYGC//z07/b7/PT2+/Ts+YaLipCG8vqGhYWJhoaEg4KB8vSDgfry+YiTj4T6/v7+gYSG//qHh/P1hIaBgIOA9/L3+fT2/oP/9enwgPHf4+To7PX4gYDy6u/y9P+A+P3/g4mGgIGA+oD6/oCC+/+HiIWKh/v6gIKC+fb2+P6CgoH6+oGGh4WEhoT+9e/u6OXr9PX2gISIh//y6Or+h4SBgP+Bgfzz8PX3+/35goiHhYOFhoH8gPz38/Ds6+vs6OTn+4SDgPn2+fv99/eBhIODgf77+/6AgP38+/z8+vv9/Pj1+P/+9vLx8Sn2+/749fP8goSHioiGh4iFgfz7gIaJhoDy6eXx/4WA9urq7fP19vj6+oTmFuPh4eTo7Hd5eXl6fH1/gYKCgH54dneEeVR35NnS2OJ2d3d2d3l7fX5+fXx6eHh3d3h4dnV16OXj5OTg4N7X1tnhdHZ4enjt63h6eXl3eHh5ent6d+vi4OPseXp5eX2Afnx8fH167+95e3x8fHuEfIB/goOAf4GBet3R1Nzh4+Pp8n6Bgn98e3rr5uLf3dva19fRxczkeXh4e4GFhomNi4iCe3TndXl8gouRi4B8ent9fHp8fnt25+t7gIOCfXp3eoGJkZWVkYZ6dHV6fHh3eHh5eXXo6+3p7Hx+e3ryevT4fn599vfz8fDzfHx9fX+AgD18duTi4uPj53l8fHt6eXh7f4SFh4eJh4J8eXh4enh1dHV37ut5fX19hIqKiIaAeOnreXjs73h3eHt9eXTmhOFZ6Xd3eXjpdnjo4uXk39zY19vh3uZ6eezp39fPx9l5eebh5OHCutZ0eH1+gYGBg4aBffTax8jQyby9xcvMzNXY1Nni5efs8vT09PLv5eHi5OLd3uHj53d6e32EfoB/f4CAf3x7fnjWxMXP3OjxfH+CgHt6fH+CgX+ChYHu4dza4/N/g4J9ent9fX6Afnp5e4CGi4uFgX9/gYKEg4F/f356dnV6gIF+fHx/hIiHhIF98+3l5unt8fiAg4V97ujj5ur1foB+e+3p6Ofo7H2BhYSCf3vw6Ontenfn2djd54B4fHvz7efn6nft6+nl4uLl6e3y9fV6e3+ChYSCgYCAfnt6eevg083N1t7r8vLq39fV1dbW1dXU2OJ5gYaHhoJ+7+fc08zIzM/S2eXrdup2en18eXd17Ovr7nl6fH18enl2493a2+N1eHl7foCCfvDm4N3f53d3dnV2eX19fHt9fYB78u3o4uHq8O7m6Hl7eHd4eXl363V7f4GCgXx03Nnc2NTZ4XPi4OjreH18dt3UzMnS29bN1+Hn7O/u7+7q8Ora3ep7gYd8deTf3ezq5eDe2dvk6Hd46uTZ1nJ4eXd2d+zp5OLo7vHq6Ozk2+V6fn2Ce+Dnenl4e3l6eXh3duTmeYB47unugYqFfO/y8Ox2eHvv7H184OF7fnt4eXXj3uHh3ODseuzh2OJ549PX2d/l6ux6eufe4ufq9Hvu8vF6fXp1dXTm5+p2eOfpenp3enjh33J2duTk6OvveXh57Ot4fHx6en177OPb2NXW3eTi4nZ4fHvr4t3i+IN/eXfrd3jr4Gzc3+Dk5uJ1eXl4eHp6deFy4+Hh4d7e3Nva2+P2fnp14+Hn7O/q63p9fX178ezs7Xd26ejo6enp6+3r6eXp7+/m4d7b3ODk4d7d5nd5en17ent7eXXl5XV6e3hx1s/N2OZ4dePZ2tzj5ujp6eiAxsXDw8LCxMjLzmdnZmZlZmdnaGdmZGFeYGNoamtracm9ucDKaWtraWlqbW5vcG9tamloZ2dnZmRiYsfJy8/Qzs3Iv72/xWVnaWtq0tFrbW1saWhoaWpqaGbKxcfP2W9vbWttcHBvb3BxcNrZbGxsampqa21ub3Fzc3Bub29rx7+AvsTLzMvR23N2dnNwbWvOycbBvLi2tLa1rrfNa2hmZWhqaWxvcG9saGTIZmpscHV5c2llY2Zpamdpa2llx81tcnNxbGhkZWlvdnp5dnBpZWdsa2ZlZmZnZmTIztLR13JzcW/idOTicnFv293b2dfXbGtrbG5ta2Ziwb+/wsTJam2AbGtrbGtrbXBwcXJycm5qaGhnaWlpamtr089pa2prcXd4dnRwaMbHaGnR1WtqamxsZV67vsXGw8VlaGtrzmls087S1dXUzsrJzMbMbGrQzsi/t7HAa23QycnFq6S+Z2lsbG5ubW9xbWrPtKGksq+lqbO3tbbGzsrKzs3N0NXY2tiA1tHHwL/Bvrm5u8DIaWtrampsbG1tbm9ua2ZlaGS3q663wcrPamtubmppa21vb25xc2/LvrWvs79kZ2hjYmVnaGpraWVjZGlvdXZzcG9wcnR2d3VzcnJvbWtucG9saWlrbnBvbm1s1NDLy8vO0NVucnZx18/Iys3ZcHBua9DOzc6A0NNvc3Z3dXJu2NHR02xpybWzuMRmamrT0M3Oz2jNysfFwsHEx8rNz8xlZmtvc3R1dHRzcG1sbNTLwbu6wMPM0NHKwLm2tLCwr7G0vchrcHJxb2xpyMK7trOxtLS0uMHGYsNiZGZmZGVn09PP0GlrbG1tbGpoyMG+v8VkZWVmZ2mAa2nKwr68v8RkZGRmZ2ptbGlnaWpq1tbVz83T2dTIwmJjZGVmZmZjxGJobG9wb2xkv77Cv77DzWnPy87Qa3Bwasi+tLG8xL6zub/CxcrOz8zJzsq/w8lobnRqZMfDwMfCwMHBvcDJz2tt2dPHw2drbWtqaMvKxsHCyNDOztTPx8uAaGpqb2m/xWdkYmRlaWhnaGvS0Gxq0MrNbXNwa9DU089oa2vQ0G9txMhucm9tbWrLw8K9tsDPbNTMw8pszb7CwcPI0NFsbM7HzNDU4XPf39tsbGdiY2G4t7xiaM7Tbm1scG3DvmJmZ8fHy87Sa2xpzM1pa2tqbG5s0cvEv7m5wsx6zM1qbG9v1s7HzON4c21q0mdkvrW0ur7DxcFlampqaWpqZsZlzc/R0c3LyMXAu77Namhjv7zDyc3LzWpsbGtq0M3Oz2hoztDS1NTS0dHPzMfIycW9uLW0uL3AwMLG1G9wb3BubGtraGXFw2NnZ2NdsKusuMNlYr+5vsOEyQLKyYp+lX+FfpZ/jH6Ff4J+jH+Ffox/gn6Sf4l+h3+Nfo5/AX6Sf4J+m3+FfoR/B35/fn5/f3+Gfol/hn6af4J+i38Gfn5/f35+h3+GfoR/A35/f4x+gn+HfoJ/h36Lf6R+kX+Hfo5/hn6uf4h+hH+GfoR/hn6Hf4R+gn+FfoN/hX4Bf4x+jn+Wfod/jH4Cf36Hf4R+iH+Ffoh/hn6Nf4p+iH8Bfoh/h34Bf4R+hH+WfoV/jH6Cf4R+hn+NfoV/gn6Kfwd+fn9/fn5+hH+Efgl/f39+fn9/fn6Gf4d+AX+EfgF/iH6Cf4Z+BH9+fn6Gfwd+fn5/f35+hX8Ffn5/f3+FfgV/f39+fod/in6Ef4V+hH8Dfn9/iH6IfwJ+f4x+g3+HfoV/hH6Cf5l+in+CfoV/hX6Cf4p+AgIEAICC9+zh5Ofr8PLw7erq6+/w7+3y/oWJhoL++/6A//z6+fn59/Tz9/v9/f+Ch4qLiYWDgYCB/4D7+vXz9fj9gYOFhYaHhoaDgP6AgP78+/z//Pv5+fuAgIKDg4H89/j6+PX4/YKGiIiGhIOEhomJiYiHhYSFiY2OioWDgoSGiIyKh4CC9+7l3tzd3dvf6vT/g4WEhIKBgIOIjZKVlpeVmJqYkYqFg4D69vPv7fiFiYqLioqJiIeIjpSRh4KBg4aMkJCQkpSTkpaVlJeWjYD1+oGBgoaJh4iLjpCLg/Tr8P2Dg4D/gIH57Ov+iI+Og/Hu9PqChYSB+P2GhP6AgoWFg4OEghCA//z5+//9/4CAgoaHhIKDhYWAg/3t3+n69+/y/IGBg4aFgvzz9vr39/v+/vX3hIPx7P2B+viAgPj6gP728+3s8PaC//qChoH/+vf7/Pv6+/ru39vf7v2Cg4iPlZqam5iThfDj3ODg2M/P7oSEgPTk2tzl5uju+4WJjoz35ejt7e709/b2/oqPjpWalo+Jio+SkZAujIiGhIKEiI6Oi4qNjYqE//+EhoWDhIWGhIKChYeD+vXt6O78g4WD/Pf8goSFiIaKgIyPjoqHgoCBhIWEgv/8/f2BhIiKiYeC/f+Ch4yLhYKFi42IgPXy8vPw7u/u6+3z+/7//fv6+vn7/4SFgvr3+fn5+Pv9+/n06ujr+P6A+fX2+fv38+zx9Pf6/oKEhYSCgf6AgoWHh4eIi4yNjY2KiIeIiIeFgoKEiYyMiIH07e/xgOvl4+La0MjDwLu+z+r/hYeFgoGDhYaEg4KCgv/79e/z/ISIiYeB+vT1+v7/gIKB//+ChYWEg4WFgf36/4WHhIKFjZGPi4b//4WIhoSB/PTv6+rz+fz9/f6Bg4OA+PDp7viBgfv38u7z+Pfx8fXcvMff7e7u+oOKiIDx5+bs7fD1gPbx8PX37/H3/ISJgvD3kp6Vk5CJhouPjImD/Pj28fD3hImKi4+SiPz259jk+Prv/oiC9vXy9f399/f8+PmAhYmMi4aFhIH38vLy9Pf2+f2BipKKg4uQhoGEg/ns8Prn3u/79/P3hYmB+P338fX+gICEjZOMhYD8gYaBgISDgoSDgIODio2Ih4DwhIiDhoqKhoKGiIOCh4qFgIKCg4WEgv6BhYL/gvzv5+Pn8PP5goSHhYKEgvnw9P3+8uPh8oOFhYT+9/r//Pbu+YKCgYCBhoaB+vj59e7s6/H8//r49vPx+YKA+fX09ff2+YCBgP/+/4GChISDgYGDiIuG/PLx9PqCN4aEgoCAgYGAgICBgf/+/f326ubu9vr8/f+AgoODgoCA//r29PT19/f09vz58uvz+vny7/b+g4SAdNvRyc3Q1dvg39zY19fY19bX3u18gH167uvtdunm5OTl5OPh4efr7u7ven6AgH98enl4eO536ejj4uTm6XV2d3h5enp6d3TpdXbq6efo6+nn5ufqd3h5eXh14tvb39/g5u57foB/fXt6e31/gYGAf3x7e36Cg4B8enl5enx+fXqAd+bg2tXS09LR1N/q8nx8enl4dXR2e36ChISEg4WHhX97eXh36ujl4uDrfoGCgX9+fHt6en6Cf3hzc3R4f4SFhYeIh4WGhIKEhYB25ud1c3N1d3Z4en2Afnno5erzfHp153V36d3d73+FhHvk4ubtfH99eObrfXzveHp7e3p7fHsOevTw7fDy7/B4eHl9fnyEeYB6enl459vQ2unn4uXsdnV3eXh149/m7Ovs7/Hy6Oh7ed/Y5nbk43d24+V26+Pg29zg5njq5HV3dOjm5efo5+Xn49bJyM/f7Hl5fIGEiIiJiYZ74tnT09DIv77ZeHp35dnQ0trb2tzkeHp/gOja3N/c3+fr6urxgYSChoqGgXx+g4CGhIN/fHt6eXp+hYWDgoWFgXvv7Xl6eHZ3eHl5dnZ5fHrr6eTh5vN9fnzu6e16fH2AgYGBf35+f4KCgH57eXp8fn189fLz8np8f4CAfnvz9X6Bh4eDgIOHiIJ44t3b2tbV1tbV2uHr7/Hw7ezq6Ojrent56efq7vDz9/n18Onb2IDb6O957+vr7Ozn4tzh5ens73p8fXx7evJ5enx+fX19gIGCgoOCgICAgYF/fHx/g4WFgnzq5OXm4t7d3NXLxL+8trfE2+18fn17ent9fXx6eXl47Onk3uPsfICBf3vt5ubr7u54eXfq6HV2dnV2eHl47Onte3x4dnh/g4J+eunpeoB9fXx78Ojh29je4ePk5ul2d3d15uDY2+N0dePe2dba4OHd3N7HqbLH1NXX43h+fnjn397k5+zv7eXi6Ovj4uXld3x44eSFkIeEgnx6foOBfnns6+rl4eR3eXh6gIWA8Onax9Lm593rgH3r6eTj6+vm5+zn5XZ6foGBe3l4duTj5IDl5+nr7/B5gIV+eoCDfHh7euja2ODSxtjo5+TpfYB56+/p5OnxeXh6f4J+enjufIN+e356eHl3dnV4enh7eON7fXh7fn56dnl8d3V6fnx1dXV0d3Z05HV5eOx46t/a19vk6e16e3x6d3p459/i6ejby8rad3l6e+3k5ujj29Tgdj53d3d4fHt36ejp49vY2uHs7+ro5eDe53l46ufm6Ono6nd4d+/u8Hl7fX17ent9gIN/7uTh5Op6fn17enp7e4V6F/Du7Ovk2tjh6Ovs6+x3eHl4d3V16OTghN4Q3dze5OHa09jd3djX3uZ2dmxgurWxt7q+wsXCvri1tbe4t7a7x2hsamjMzM9ozcrFw8LDw8TFys7Q0NJrbnBxcG1sa2ts1WvS09HS1NbZbGtraWhoaGloaNBpadDOzc/S0dDOzc9paWpqamfHv72+vsDGzWpsbm9ubm9wcHGEcIBubm9xc3RxbWtqa2xtbmxpZsS+uba3u7y7vsfP1GpoZWNhYGFla3Bzc3FtaWhpZ2NgX2BhxcbFw8LJa25wcXFxb21ram1xcGpnZmZmaGttb3FzcG1ubm9zdXFqzc5nY19hZWRnaW1ubGnMzdTecnJw3W9t0cS+yGdoaWfFwsHCZIBpamrNzGtqzWZnaWtsbW1satPQzs7LxcFhYWNnaGhoaWlnZmZpa9TLwcbRzcTEymVjZGZmZcjFyczL0uHr6drQa2nCv81pzcpoZ8nJZsvKzsvKz9Zv08hnamfLx8TIzc3Mzci6r7TAzdRqaWlucnZ2dnV0a8W7sq6nnpmhwm5ua4DMwLi7xMXDxMlpam5txbi4vb7Bx8jGxcpsbWptcnFva2xwcW9saGVjYmFhZGlqamtvb25py8tpa2tpampqaGZmaGpmxMPAwMrXb25szsfHZmdoa21ubmxramttbWtoZWRobG9wcODg39xtbnFzdHNv2ttvcnRybWpsb3BtZsO/vWi7uLe6u77CydDU1dTT1NTV1tlvb2zR0NPX2t7i5OLb0L23uMfPadDOz9LU0czFx8fIyctoamtramnPZ2hpbGxtbnBwcXFxcG9vb3BwbmtrbG5wcG1ow729v729v8C6sqyppqCeprfEZoRpBGtsbGuEaoDSz8rFydBtcHJwbdLNy8zMy2ZoZsfFYmNiYmFkaGnT0tRsbWtqbnR3dXBqysxscHBvbtfPycO/wcC9vL7EZWdnZcXDw8zVbGvNycbHztLOyMXFsJmnusG+vslqb29s0szLzs3Mxru1vMnPysrMzWtvacTNd4B3dHBraGpramtr14DZ2dDLzmpram11enTV0L6quM/TyNV3ddXFu7/HxsDEysfHZ2tsbm5pZmVkx8vP0NHU09POZmxzcG1ydm9rbGrGusTPtaO4ysrJ0XBxaszRz87Q121tb3N2cm9s02xxbWlnZmdra2tqa2xqbWnGamtlaGppZWJmZ2JhZmpoZmlsayFqZWG/ZGhmy2jNxcDAx87P021wcW5sbWvNxcfQ0ca4t8WEakHS0NPUzsbAymloZmVmaWhlx8fIxsPDw8fP0c7MysfH0W5t19PR0dPU1mxradHQ0mprbGtqampsb3Jv1M7Nzc9qbYVsAmpphGcrzM3Oz8vDwszU2drZ2WxramlnZ2fNysbEwL++vLu9wcC8uL3CwLq2ur9iYgF/k36EfwR+fn5/jn6KfwJ+f4d+in8Dfn9/in6Gf4h+n3+Mfpd/hn6hf4J+jH+EfgZ/f39+f3+EfoR/hH6EfwV+fn9/fol/h36Of4l+hn+Lfg1/f35+fn9+fn9/fn5/h34Gf35+f39/j36Lf4l+g3+JfoR/i36cf4J+jX+GfgZ/f39+fn6Wf4R+h3+Cfot/lX6Df5B+AX+NfoZ/AX6bf5J+jX+GfoV/hn4Ff39/fn6If4N+in+CfoV/i36Ef4V+gn+SfoR/kH4Ff39/fn6Mf4Z+h3+JfoJ/i36Jf4l+i3+LfoN/hn6IfwF+kH8BfpZ/Bn5/f39+f4h+h3+JfoR/iH6If5B+gn+HfgZ/f39+fn6Lf4V+jX+Nfod/lX6CfwICBACAgYWKjpKTlJGPjYuIhYKCgYD8+vr7+fj4/ICEhYT+9evo7PDx7+/t7uvn4eDg6O709PDt7O/x8/T19/f38/Hx9vr9+vj6gYWFg4GAgoSFg4H9/P2AgIGDhYeHhIH+/ICDhYeHg/nw8/6GioiC+ezi2tni8vr8+/2Bh4yOjo6Mi4mAiouLjo6LhP319/+A+e3l5/CAipSboKGelYmCgP78+fn8//7/goaLjo+Oi4iHhoeMkZCMjI+OiomLjY+SlJWWl5eTjYX89fb39/bx59jQ0uDr8oCHh4WFhYOBgIGDgffy9//8+oCBgP79+vqChoL68OXj5ujx+ICCgPv09PmAg4eAhoH++/n7gIOGjImBgIH++4H+8vT49fX69/Px7/T28fD3+vX0/YD9/v728fqCgvn2/ID/gP79gYD28fj7+O7r7fD7gvP0hIT+/oOIiYT69vn29fuCgvXm6/yBhYaGiZGTi4Lq2Nbc2tfX1tHM0Nba5PDp5unn5efzgYmLgu/i3dkv3u/8/f3++4CEhIKBh42Phv7+goKB/Pj8gISJjI2Oj4+RlpSMh4SB/PTw8vb8gIGEgoSDDfzr5uvw9Pb6/YCBhIWGhh+B9O/8hIaGh4eHhoiLjY6LhYH+goaGgvz7goeJh4WEhYU4hIKA/vv38/Ly7+7r7/j8/fr7/4KFhoaFg4CAgIGBgf77+vv8/fz5+Pn9/fr39vuAhIaGgoD+//+FgAiDhIP//v+AgoSEhoJcgPz5+f2AgICChIWDgf77/P+BgoOEhoaFhYSEg4KCgYGB//bw7PL19fT5gISEgoCAgPv49fb5+/+BhIaGiYyNi4eCgoKB/fj39/j+goD69O3r8Pj39fb4+vv59PKE84D3+/Tq6/L7goSB/4GEg4KB/Pj18+/n5evt7/L3+fPw9fby8O3s6ejk1uHz8evv9Pf4/oKEg4H++YD+/4SKjYyG/vr9gPv4/vz18fj+8Oz6gPvv5/D1+P2A/OzegIyH/fLp2c/Y4OXw/fz4+oSJh4iGgfr5+PXy/oD6+fDdz83P6YCEhfPj+IH9/e7l+oaJhfr39/6Bg4SB+/X4/P6BgoWEg4qIgYGDhIWB/YOFhIH//oKNkY+MhoSD/vf0/4L//ICA+vj7+4D77ujv8O7ygIL28vDk4On7hYH9/4GDgvr6/ezj84GBgISFh4mGhIKAgIGB/vbt94KC/PPz9/bp08zc72P28/L2/YOGhoOBg4aGhoP+9O70gID///3/goKA+/v//fr7gIOEgvry7enq8PX6/P399/Lu8Pb7/f6AgYGA+e/q7vT9gID68/P5/oGBgYCBgYKBgYGCg4GA/4CCgoD6+Pr7/f4MdXh7foCAgH59fHx7hHmAeO3r6uro5ubreHx9e+7k2tjc4OLh4+Dh3tvX2Nng5enp5eLh4uPj4uLk4+Lf3t/k5+ro6Ox6fXx6d3Z4enp4deTi4nNzdHZ4enp4dOXkdHd6fHx449rc53p/gHzy6eDa197q8PHu7Xd6fX18fHt6eXt8foCBf3rq5OXrdeXY0dCA2HN8hImLi4iBeXR16unn5+nq6et5fYGEhYSBf35+foGFg4B/goJ+fX+Bg4aIiYqMi4eDffDs7e3q5uLazMbI1N3gdXt8e3t6eXd4eXp34+Dk6+jkdnh47uzr7n2AfPDl29fZ3enye3t35+Pn7nx/g4B88u7q63d7foKAeXl67+oIeOvh4ubl5uuE6IDs7Ofl6Ojh4Ol37fDw6eXuenru7fV+/X3x63d24dvh5OPb2Nrb5Hbi5Hp45eR3fn976ebp5uTpeHfh09fndnp7fH6EhX101MfHzMvJyMjEwMTKzdbg2NTV09LU3HR7fXfg2djW2+ny8vHy8Hl7e3h2e4CBeufnd3d149/jdHl+gU2CgoOEhomGf3t4duji3+Dk6HZ3eHh4ent7fH3z5uTr7/Dv8PB4en2AgYGAgH9+eufj731+fn9/gH+Bg4WEgHp153Z7fHvx8XyAgn99fIV9SXt4duro5eTn6uno5unw9PTv7e95ent7e3p4eXl7e3v08vDx8fLw7evs7+/t6+rteHt+fnx68vHweHh4d3Z5eXjt7e95enx9fX2GfBZ67ejm6nV2dXd5enl47evs7nh4eXp7iHqAe3x9+PDo4+bo6ejreXx7eXh5ee7q5eXm6Ox4e319gIKCgH15eXt79PDt6+rvenjt6uXj5+/v8PL19/bz7Onq6uno6ezl3N3i63l6eOx3eXh2dOHd29nX0M/T1dTT1tvc3+ju7e3r59/a08XO4N/a3uTp6/B5enh36uNz5OZ4fYCAgHzw6+x46ebt6uLb3uLV0uB28+vg5urr7Xjq3dB2gX7y597Rx8zS1+Lu7enrfIF/f3146u3w7urxeezq4tXKx8baeHnh1ul58PDi2++Ag4Hx6unwe31+e+7o6u7xeXp7enl/fHV2eHl7duh3enp69vV6gIKAf3p5eevj4Ol15eKAc3Lf3uDict7Ry9PY2uR7fezj39TQ2ep8ee7went67Orr2tHednV0eHl6enh3dXR0dXXo49zleHnp4eLo6NzHv8ze4+Df4+l4e3t5dnd6ent67ufj6nt79vf29317d+fm6uno6nh6e3ns5+Tg3+Ll6Onp6OTh3d7i5+nqdXd3ducl4Nze4+l1c+DZ2d/md3h5eXt8fXx7e3x9e3r0e3x7eenm5+fo5wlkZmlrbm5ta2qGa4BqaMzJyMfEwcDEZGlqas3Gvbu+wsPDxMPExMTDxMXLz9PSzMjHyMrLy83Pz9DMycnMz9LRz9Fsb29tamhnaGloZsrLzWhqa2xub25ta9PRaGlpaWhkvre5xGdqaWTBvbq5u8TQ1tfU1GtucXJxcW9ubW5vcXNzcm7W09ffcNvNwoC8vGFlam5wcXBqZGFix8XCwcLExMdna3BzdHRzcnNycXJ1dnR1eHl3dHNxb25vcHFyc3JycNzb3NrUzcfAtrCvsrW2X2RnaGpramprbW9u0czQ2drab29t1tPPzGlsaMa/ur/GytDTamlnzMrN0mxvcm9qzMS5t15hZmxsaGhs1YDPaMzCw8fIzNTSz87R1tfPycrIwsHKaNHU1M7K1G9x3djab91rzspnaMjFycvIwLu7vcZozNFubMrGZmxva8vHx8XGzGhnwLK3x2hra2hob3JuZ7epqrS3uLm1rquvtbW7wrq3uLi3uL9mbXBrysK/u77K0tHPz8tmZ2dlZWpvcSFryshlZWPCwcRjZWZoZ2dnaGtvcGtpaGfMycjM0NRrbG2HbAzVzszOzsvKzNBqa22Ebmxvb29szMbPa2xtbm9vb3FzdXVybGjNa29wbdPRam5wcXFyc3Jwb25saWjPzs/R1tra2tbV1dTRy8vOaGpra2tqaWprbW1t19XU1dfX1NDNzdLT0tHR1Wxub29satXW12xsbGtsb3Bv2NXUaWqEaxhpaGhnZ2ZlxMC/wmJjY2VoaGdmysrMz2iFaYBoaGdmZmdoamtt2NHMx8nKyMbIZmhpaGlrbNTQzMzP0tdtb3BvcXNzcnBtbW5t2NPR0NLYbm7a2NPS1dnZ2Nrb3drY1dXX1tLOzc3Hvr7CyGhpaNFsb3BwbdXT09LOxL67s7K4wsfDxM/W1dLPy8S+uK20wL67wcfKyc5pa2xr1IDPatPTa21vb2vLwL9iydLe3dTMyse2r7xn2NXP09LNz2nPwrdocW/Ov7Snn6KptMHMy8nJaGtqamhlycrHwsbQaMvKxr22s7C8Z2nFvtJv2NXLx9NucW/Rzc3UbW9va9DMztHSamtsamhvbWdmZ2doZMptcnR17utwb29tbWppaIDGvrzHZcTAYWC6vsjNaMvBvcHDxM1ubtDMy8G8xdRvbNDPaGts0c/Rw7nEaGdoa21vb2xraGVkZmjOycHJamrKvr3AwbmoprjL0tLS1tpvb21qZ2ltb3Bw29XQ1W9v3NvX2G1raMzLysbFympvcXDa1tPS0tTU1NPS0s7LyMjLzQLNzYRnJsnBvsHGzGdnycLBxctoaWprbG1ubm1sbGxqadJpampnx8TDxMXFkX+IfoR/qH6Lf4N+iX+CfoZ/hH6Ef4t+kH+EfgF/hX6Lf4h+oH+Ofox/hn6Df4R+g3+IfoN/hH6Ff4R+iH8Dfn5/lH4Bf4Z+DH9/fn5+f35/fn5/f4p+B39+fn9/fn6Ef4Z+gn+Efol/ln6Ef4t+iX8Ifn5/f39+fn6Pf4Z+in+Jfot/g36OfwF+hH+Cfo5/kH6Mf5B+hn+Dfoh/g36Nf4R+iH+EfpB/iX6Hf4d+jX+GfoJ/mn4Ef39/foV/on6EfwV+fn9+foV/BH5+fn+LfgF/h34Hf35+fn9/f41+hn+GfgF/iH4Gf39+fn5/hX6Df4R+hH+Ffo1/AX6Ef4J+iH+EfgV/fn5/f4R+AX+HfoJ/h34Hf39+fn9/f4Z+jn+EfoJ/j36Kf4R+gn+EfoN/hn6Ef5N+hH+GfoJ/hX6OfwF+hH+GfgICBABl9Pr/goWJi4mHhIOBgP+AgIH+9u/o6uzt7/Dx8/Hx8fT8gYWGh4aE/vv8gIGBgICBgYD8+vuAhYmIh4L77ufp7vT29PHv6u3u8/j6/v+AgIGDhIaIiYyMjI+QkZCPjImGh4mKiIaFhYCEhIODgoOFhYeFg4KB//To3Nbf84GGiImKi4iC/PX09/v//vjy9YCEiZCWmZual5SRjIaCgYSJjpCOjIiEgYKFioyNioiHiYuMjpKTkIuGhISHjJGSj4qE/v2DhoL78Onn5OHWysze7/b4+fPw8Ovn7PP3+Pz/goOCg4D69vPy/4CIioT77+rq6uzu7O/w8fP5+v+Dgfv69fL6hYyPioWHioWAgIKFh4aFh4aGiYuIhoDy9P6Cg4KCgfz4+//56eL0goaGhIKDhoP18vmBhIL//v6Agfzw9YCFiIWDh4eEgv/9//+BgoGAgYGBhIWCgoOA/4KDgf715d3g5+rr5eHp7XXs7/b/gvzt4d3l7ers9/788vL4/fns3tfX3+Td2eDt94CDhIuWl4v36Oz1+f+AgIKEhYeKioeEgoSHiIT67+zz+v6BhIWFhIaJjJCQjoqIhID/gYODgoGCg4SDgP//gIKEhYSDgoH++fr+/4GCg4WGhoeHh4iEiYCMjo6IgoCAgoSHh4aFhIeE/fXy8vDu6efl5uzx9Pf8/v79/f3+/oCDg4KDg4WEgv/8/f+AgYKB//j2+P2AgYD9/Pz/gYKDg4KBgP+A//6AgYKB/vz5+fv9///+//+AgYKA+vTz9fj7/Pr4/IGFh4mJiYeFhIWHiIiGgv/+gPv49YD29/j6/YCBgP7/gYD/gYSHiYmHhISGh4SBgPz5+v3+/ffy8e/t7/Dr6uzw7Ojo7PL6gIKBgYKFh4WEgYCAg4aHiIeGh4WDgoODgfz9gYKBgYKBgYGA/v78+/fy8/mEjZKTjYiFgfj09PDr6err6+Xd4PD5gYL68/Lw7u3v9/337oDp3d/2goDu5efu+oKD/vn9/4GFg4KEgf/48PeCgvXq5ODh2s7e9P2GiYeIhoWC/vTo8fr69vaCh4L57ufk7ff/gPjm6fqBgP78+fyA9OfwgYeJ/vOChIKGhv/8++Xd8P/55+Xy/oCDh4SA/f399+/t7vP7/oGC/PT09/yA/ICHiICFhIiJgfHu9ff09Pj6+vr79vHu7fX+goGAgoODg4H+/f+ChYSC+PL6gPfw+4KDgoH9/4D79fX5gIH/9u3m7v6BgYWHgvv+goSB/vr9gIH++vbs4ubzgIWGg4D++vn7/v/88uvp7vP5gISEgvz4+fz69O3p7PP5/Pv5+ff4+4CCgh2A/fr6+/6BhISD//r39PPx8O7r6Ovt7e3s6ebo7Fjm7PJ7fX+Af317enl48Hl6e/Hp49zf4OLj5OXp5+fm5+55fH19fHvv7e95e3t6eXl4d+jk43N2eXh4dOPa1djc4uPh397a3Nzh5ufq63Z2d3l6fH1+f39/hICAf3x6eHl7e3p4d3d4ent8fHt7enp6eXp6eHl58eri2dXb6np8fX19fnt36eXl6Ovs6ePf4nZ6foSJi42MioeEgHt3d3l+gYKBf3t4dnZ5fH5/fXx8foCBg4aGgn55dnd6foKEgn156+x6fHfj2tXT0tDIvL7M2d/i5ODf4NvY3eSA5+fq7Hl6eHh25uPj5vKAgnzt4dva2drc293g4OLo7fR+fPHv6uftfYOGg36AgXx4eXt8fn19fn18fn9+e3fj5u96enl5eOvr8PPr3NTjeHx8eXl7fnvn4+Z2d3bp5+ZzdePb33V5fHp5fHx7ee/w8vJ6enl4eXh5e3x5eXp373pwfHnv5tbO0NbZ2NPS3OHg4uXpdubb0c3V3Nrd5/Du5eTp7ure0cvM1tvTzdLd5nZ3eH6IioDl2N3l6Ot2dnh5eXp8fHp4eHp9fnvp3t3i5ul2eXt7e31+gIOEgn99enfweXt7eHZ2d3d3de3veHl7fIR7CPTx8/b3fHx8hn0sfH19fH2Ag4N/fHp6e31+f359fH577OXj5eXl4d/d3eLl5+ns7u7t7u/x8HmFe4B9fXvy7u7ud3h5efDr6urueHh46+rs8Hp7fH18ennxePDveHl6efDu7O3w8/b19PPzent8eu7p6Ojr7e7s6ux4e3x9fXx6eHh4enx9fHrx8nrx7uvq6uvt8Hp6ee/vd3fsdnl8fn58e3t8fXx6ee/s7e7x8vHz9vXw7+zm4t/g3YDb3ODm7nt+fXx8fn58enh3d3p9fX18e3x7eXp6e3jq63h5eXl4dXJwb97h4uLg3uLpe4CEhoJ9e3jr6uzp5eLh4N/YztDf6Hp76+Pj4uPi5Ovx6+DXycfXcG3KwcXQ3XR14+Ho7Xh6eHd6efHs4+p9fezc0czNx7rF2eR5fXt9fIB9e+3h1Nzn6unrfH9559/b2eLt9nzv3NvqeXfs6+jteOXY4Xh9f+3le359gH/x7ezXzt3q6NvW3eVydXp3dOPi4dnS09bb4uV1dufh4eTqd+x4fn55d3x/eebm7e7q6e3v8O7t6Obk5Ovye3p4eXp6enjt6+t3eXl45d7mdePd53F3d3Z05ed15uLi5nZ36eHY0djodXZ6e3fn6nh5debi5nV36ubl39jd6Xp+fnp47urp6uvq5+Da2d3i5nZ5enfn4+Xn5+Le3N/l6u3t6unn5ud1dnV05ubp7PJ8f4F/+PHs5+Th393b2Nve39/e3Nja3izKz9JqbG1ubWxqaWlo0GdnZsnBvbm7u7y9vb/Bw8XHy9Bqa2xsa2rOzc9paoRpImhmyMfHZWdpaGhlx8G/w8jNzMnDv7i5ub7DxsrMZ2dpa2yFbYBsbnBxcXFua2lpampoZWRkZGNiYmNlaGlrbm5wb25tbNfSzcjGyNJra2ppamtpZsfExcnMzcrCurpgZGhscHJzc3Jwb21pZmZpbnN0c3FubGttcHJxbmtpam1wcXJ0dXNwbWtrbnF0dXRyb9jXb3Fu0sjBvr28s6Wjr7q/wcTExoDJxsLFyszP1t1zdHJxb9va2NXbcW9pxL2+w8XIycTCxsjLzczPamjMy8jGyWZpa2lpbG1qaGlrbGpnZ2loZmhqa2tmwb/GZWRlZ2fMys3OyL66xGdrbWxucnZy1MjGZGZly8/SaWnLwcNmam1ram5ubWzV1dXTamppaWpramtqZlxmZ2TKaGlny8K3tbi8vsG/vcPHx8rMzmbGurCut8HBw8rPzcbHz9jZ0sS6tbm7s62yvMNkZWdtd3p008nN0tPUaGdnZ2ZlZmViYWFlaGlnwbm5wcrQam1vb21saoVoIGloZ89pa2poZmZnaGhn0dRrbG5vbm5tas/Kys7RamtshW6AbW1tbGxucnV2c3Bubm5vcHBubm1vbdXS0tPT08/MyMTHyMjKzM3My8vLzMxnaWprbG1ubWvQzMvMZ2lsbdvY2drdbm1rzczM0Wttbm9wb27ebtzYamlpaM7MycfFxMTDxMjKZmdoZ8rHyczP0tHOychlZ2lsbGxrampqbW5wcW+A3d1v2NTPzMvMztJrbGzX1mtq0mlsb3FxcG9vcXJwb27Y1NLS0s/LysvMysvLyMbExMG/v8LGzGhramtrbW1ramlpamtsaWhmZ2pqamxsbGvS1GtsbG1tamZgWaywuMDExcfIZWhrbm1ra2rT1NTQysbEwL24srPBzWxryMPGzdCAz9Ha4t7Y0L63wWJcqaSnrrlhZcbDx81pa2dlZ2fOysPLbnDUxr25ubGlsMLEY2RmbG1tacvCvcfNysXHaWpnyMPAvcPM1m3VxcLOa2rT0tDSacq9v2RpbM7Ka2xqbGzQzsy6tsnVzr+7xdFpbXFva9DOzMO6trW4vL1iZcrHys0e0mrTam5uampucGrJytTW09LT09LS1dPR0dDR02trhGqAaWjOzdFsb29sy8TLaMnDx2RiYGHEyGXHw8THZ2nPycS/xNJrbHBxbNHSa2toysbJZ2jNycW+trrFaGtraGfLx8bHy87OycbGy8/Sa2xqZ8fExsnKx8PCxcvP0c7JxcLAwmNkZWTHx8vO0Wttbm3Uz8zJyMbFw7+8v8LExMTBvb8Bw4N+in8Efn9/f5B+hn+Dfoh/g36Gf5J+qn+Hfoh/in6yfwV+fn9/f5l+hX+FfoN/j36Cf4V+l3+DfoV/iH6Ifw5+fn5/f39+fn5/f35+fol/hH6NfwR+f39/kH4Bf5t+h3+Gfo9/hn6PfwF+in+Cfoh/hX6ef5Z+iX+EfoR/hX6Df4R+h38Efn9+foR/i36Ef4p+j38Dfn5/iH4If39/fn5/f36Nf5d+mX+Cfol/iH6If45+gn+PfoJ/hX6Cf4R+hn+EfoJ/in6Hf4h+g3+HfgF/hH6Cf4R+CX9+fn5/f39+foV/jH6Ff4p+gn+FfgJ/foh/kX6If4N+hH8Hfn5+f35+foR/A35+f4R+gn+GfoV/Cn5+f39/fn5+f3+HfoV/jX6Ef5J+hH+FfoR/k34CAgQAgPP1+Pn6+Pf39/X3+vv7+Pb19fTz7evs6+zw8/j5+fj4+fb08O7u7vL09PX29/f18/f6/oGB//r39vX28e3r6uvr7Ovp7PWAgYGCg4SDgYGDhIWCgPz08vn9goODgoGBgYCA//6BhIaHhoaDgYCAgYGAgIKGiImIhYOEhYWFhIODgIOEhIaIioqKiIWC/vr3+YGGiIqKh4WC+One3OLp8vr+gP329fn/gYWJkJaYlZGOk5qio52WkY2Mi4aB+/r/goWGhYaFhYSEhYqOkI+Mh4OBgYOFhID8+fbw7Ozu8fX8gP36+/3++vb09v3///79gPn1/4SGhP38hImIhomLi4yOgIiBgIKB9OzzgYeE//v/gPj2/v75/YCA+vf6/oCEh4Hy9P398+3o3tjh6feFio2NkZSPhPfv8Pf28PP7//rz8/Xy8/j8gYGHkpuUioL8/4KA+fT29/f/iIqIhPzy8/Ty69/SyMvNy8nJ0djW0tbi8/367ez09/uEiYeA8+3q5ebrgPDw6N/a19bc5/H08faGkpWNh4SDgYKDhoiKioiFhIKDhYaGhIKAgIGDhISC//r9gYWGg4OFiouMi4iGgv/79/X4/ICCg4SEhYaFg4GBgP79gIKFhoWGhYWFhIOEhoeGg//6/YCDhoaDg4OGiY6RkZCKh4iKi4mIiImJiISB/vr2HvLu8vX39/b08fDw8vT09vn+hYeHhoSCgICA///9/oaABIGBgoKFhGGFhYSCgYD/gIGAgID//vz9gP/7+PHr6uzr7fL19/n8/4GBg4WFhoOA/Pr7/f+A//z5+vz+/vv5+/z7+/j3+Pr8/YCBg4SFhIODg4GAgP6A/vn18/Pz7ejh19rq+f+AgoKBhP6A/Pz6+Pv++/Pw+PyA/ff2+oCChIWB/Pr+goOEhoaFg/vy8fX+gfz18+7l6vHx8fmAhYaA6t3b29/n7fP4gYiQlI6Hgfjy8vHy/oKB/f/99vP2+4ibpaCVhvv6gYqTkIX9/Pv7+vv9+Pn+/vv49vP4/4KC//Hr9fyEiomEgoSB+P6Ag/32+YGEhIOGhf736+Hh8IH98YSRkI6IgYGFiITz3ePx8e7u9vv6gImRi/nt7P2Ghfv2+/r8gYKC+Ov6iIqGhoWDhIWBgPzz7enr+4T+7ez8gPn4+fHn4+Pg4+z3goP88e3v7unr8Pr/+/z+gYOC/fXw8Ovg3ur6/v/++fmBhINugYOFgv749/f3/IKDgv728/b8gYKCgYGFiImIhP/x6efk39za3+Ti5Ozz+P6Dh4iEgf749fb+g4WGhYWDgYD+//317urwgIaIhYD7+v+AgPrw6ePm7PT19fPx7/L1+vr38eXj4+vz9ff6/f/99/UE3d/j5oXngObo6uvr6uno6enn4t/f3d7h5enr6urs7Oro5OHh4eXm5+fp6urn5unr7nl47Ojl5OLi3drZ2Nna29nY2eF1dnd4eXt6d3d4eHh0cdzSz9bbcHJycnFyc3R05+Z0dnl6enp5eHd3eXp5en2BhIWEgoCBgoGAf3x7ent7foCCgYB+OXx46uXg4HN3eHl6eHd139DGxMrS2uLkcuLb2t7jc3V4foKDgX59gIeOkIyHgn18e3hz4d/ic3Z3eIR5gHh5fICBgX97d3Z2eHp5dunm493Z2dze4eh37+7w8vPv7Orp7O3t7O136unzfX598/J9gH59f4CAgYN/enl7eebh53l+fPDv9Hrt7PP07/B4d+bj5+t4fYB76env7uXi3dbP193me35+fX+DgHjm4N/l5N/h6Ozo4+Pl5OTn63h4gHuCiYV/fPH0fHrr5ujr7fJ9fnx56ODk5+bf08S5ury8vcDK0s/JytPe49/U09vg53qAgHvs6OXh4+nt6+HX09DP1ODq7uvuf4iJg316eXh4eXx+f4B+e3h3eHl7fHp5d3Z3eXp7e/Hs73p9f3x8foGCgoF+fHnv7erp7O94enp6WHl6e3t5eHh48PB5e31+fn59fX59fH1/gH578OvseHt9fHp7e36BhYiIh4KAgIGBgH5+f39+e3js6OTg3eHl5+jp6OTj5OXn5+js8H1+f359e3p5efPy8vSEe4J8hH2Afn19fXx8e3p4dnXpdXd4eHr09PHyefPw7Obg3+Hh5Ojs7e/w8Xl4eXp6e3l36+rr7e937+3q6urt7evp6+3u7u3s7e7v73h5ent8e3p6eXh3d+136+bj4+Xn5OLc0tPf7PF4eXh25+Tj4uDj5OXp6+ff3ufsd+vk4+Z1d3l6d+qA6e96e3t9fX177ufm6O967+nn4tri6+3v9Xx/f3ri2NfW193i5Od2e4GFgX156+Tk5OPseHfp6unj4eLmfYyUkIZ54d9zfYaGfOnn5ufm5+jh4OTm5ufo5unqdXbr4tzj6Hh8e3h3eXns73rr5el5e3t5e3nq5dzU1uV77+J7hoSAgnx1dHh7d9vEzN/k4+Pr7+16goiC6d/c63x65uLn5eZ1dXTc0eF8f3x8e3l7e3h36+Ld29/te+7k5vV88O/v59zV09LX4Oh4d+LY1trc3ODm7/Pu7e54enno3tjX1M3O2+rt7u3o6Hd5d3V2eHfo5OXl5Ol3dnTi2tjd5HV2dXVYdXh8fXt459vU08/LyMfM0tLU3OPo7nx/f3p25uDc3ud5fH+AgH98e/Py7ubf3OF4fX98eOvq7nh3597Y1Nje5+fn5eLi5uvy9O3l1tPU3ebp6+7w7+zj4IDFw8TIy87P0NDP0NHRz83Ly8zMzMjGxcPDxcnO0dHS1NXU1NLQzs7R0dLT1dbU0c/Q0NBoZ8rGwsC+vrq3tra4ur27ubnAZGVlZ2lrbGtsbW1taGXFvb7HzWptbW1sbGtratLPZ2lqa2traWlqam1ubm5vcXN0dHJxcG9ubGtpaIBoZ2doamtramlnZMTAvLxhZWVmZ2VlZMO6s7K3vMLHy2fNx8XHzGdpa21vb25sam1xdXZyb2tpaWppZsvM0WpsbW1samloZmZpa2xsa2loaGlrbWxp0M/MyMTFydDZ43Pj3Nze4NzZ1tTW19jX2G3V09pxcnHc2W1ta2lrbW1tboBrZmRlY7ezwGhsa9HT2m7VzczKxslnaM/Nzc1mZ2dhub/KzcbDwLq2vL/FZ2lrbHB1dG/V0c7KwrvAyc3KxMPExcnP0GhpbHN5dnJs0NBpZ8fDw8bIzWpsaWbDvb/BxcO5raWnq6+ytLm6tK2ssbi9t62uuMLLa25rZcHDxsfK0YDZ2dDGwL28v8fOzsrKa3JzbmppaGlpa21ubmxoZWRlZ2lra2hmZGRlZ2hoaMvFyGdqa2hmZWdoaGloaWjR0M3Ky85oamxub3BycnFvbm3a2W5vcXFwcG5ubWxsbnBycW7W0dBpa21ta2trbW5xc3Nybm1tb3FxcHFycnBtaczHwgu/vcHFx8jGxMC/voS/B8DDZmhqa2uEagXW19fYbYRsBW1tbW5uhm8Hbm1ramjPaIRpgNHPy8plysnIx8bGyMTDxcfIycrKZWZoamtta2nR0NLU1mvW1M/Oz9LU1NbZ2tbTzsrLztHTa2xub3Bvb29ubW1t2GvPxr+8vb67urSssL7Kz2doZ2XHwr66uLy9vsHExMHCy9Fr1dDOzGVlZmhnzMzPaWlqbG5ubtXNysvPac3HgMXBusHM0dXbb3FxbcvCwcDBxcnLymVnbHFycm/Vzs/Q0dhta9DOzMfFx8trdXl3cmvLyWZscXFqyMfL0NDOy8PCxMfJztPS09VqatDIx8zKZGZmZmdraszPatHMzGhqamlsaszJwrezwWrWzW52dHBqZGNmaGbCtLvEw8LEzNHPbGhuc3DKwL/Pb23Mx87P0mtsa8m7xm1wbWxqaGtrZmbPzMnDw9Bt0cTE1G3X2drUy8fJyMrO021szcK+xMfHys3U19LR0mpras7IxcTBurzJ2NvZ1dHUbW5rZ2hpaM3LzMzLz2loZca/vcHHZoRnV2lsbm5s0snEw8G9uba2trKyt77Eympub2xpzsfBwMZnaWtramhmZMjKy8nFxMlqbm9tas/O0Wlpy8O8uLvAx8jKycfGxsfJysnHwMHDytHU1tnb29jQy7N+gn+Rfo5/hX6Jf4J+p3+Efoh/iX4Bf4V+lX+Dfpd/in4Bf45+CX9+fn5/f39+fo5/Cn5+fn9/f35+fn+GfoJ/hH6Ef4x+iH+Rfoh/BH5+f3+GfoR/nH6Ef5N+n3+Dfo1/hn6Mf4J+kH+Dfpp/lH6Jf4R+lX8BfoV/hH4Bf49+iH+FfgF/k36MfwJ+f45+hH+PfgF/hH6Ff4N+h3+FfgF/in6Ef4l+h3+GfoJ/h36Gf4J+hX+RfoJ/hX6HfwZ+fn9+fn6Gf4Z+A39+fop/in6Ef4R+gn+FfgZ/f39+fn6Kf4Z+AX+EfgF/i36Cf41+g3+Ofod/hn6Df4V+in+QfoV/hX6If4d+hX8Ffn5+f3+ffgICBAAe9fDs5OLi4+bo6/H6/oH++PTx9Pf6/P6AgYKBgP79hPwN/f+A/PLo4uPp7/X3+ob7Fv2AgYODgoD8+fr9/v39/v+BgYGDg4KEgYCA/fz9+u/m4ur9hISEhYeJiYeDgPz28e7v8fj+gIGBgICBg4SFhYSCgPz6+Pn59/X08/X+hISDgoODgPz38/b/ho2Qj42MjpCOi4eFhYaJioiHiYmJjpWamZWPi4iFh4uOjYmGhIWEgPj6gYaIiomIiYuMiYaFhIaKjIyGgYD+gQGDhYKAg4KB/fr8gYOEgfz39PDq6e3u7eXh5eru7ePg8fv79Ovs9oGEg//6+/vz59/f5+/2/oKEg4SEgfTj3OLu+f6ChoeGhID38O/2/Pbz7evn5/L58ePf4ufn6PSAiIyJhoaD9/Dx8/f26unq7/f59fHr6O/8hIqKhYH/gIOFgfnx59+A5PSB/vfx7vL+/Ozg3uDj5OHc2t/j29LS2eHm6e3r5ubp7e7t6d/a3+bw8uvl4+bs9v358u/w9PuAgICBgoOA/Pv8+/z/goOEhYOCgoKEhomMjYyKiIP79/Tx8PiAhIWFg4KCg4aJi4uKiYeFgoGA/4CChIaGhIKChIWEgoCAgoKEgR2CgoD/gP/+/v37+fn5+/n7+//+/oCAgYGBgICAgYSCPoGBgID+/vz79/T29vb07vDz+oCA/vv4+fyAgYCA+vXq5uru8/X09vf4/f6AgP359PLw8fP19fb3+v+A/fn1hPIU8/X4/P+Agf/+/fr5+fv+///8+veE9RT2/ICDg/328Ozt7/T8gIGB//39/oSAgIGBgf769vTz9Pb6/oKEhYSDgoH99Ozs9P6BgYD37enq5dnU3/KAgf318vL5gYODg4H9+/Ps7/X5+PTz+P2AgoaMjYmC+vn7/Pny7ezx8u/s6uno6/H18+rh4er4gIGAgISJjImEhIeLioWC/vv8+PTu6/H49vX39/f7/4D7/oOHgIaB9ezx+vT1/YSJioWDgfz07+3u7u72goSAgoP/+P2BgPr2+P6BgPz1+fz8+vf28O/2/IGDgfv18veAhYmB8vH1+fr7/YGB9er0goqMhf7+/fz494SSlYn88/qAgoKBgoWIhYOA+/f7g4WC/P+BgYGA/Pnx6uru9vn68eLe5fH6gPv9/Pf7goSHiYiFgPn18POBiomHh4iIhYD9+vLv9fr7gIKDgf/6+Pv9+/Xw9fyAgYKDgoD59vf3+Pfy7Ozv8fL2/YGCgf317+vs9Pj17OXj5/D8goKBgP//gYGA/Pn6/fz49fT18Ofe2Njh6+/w7/P4/P3/gICB//v39PT2+Pn5FPj19/b29fP0+Pz///78+/j18/P1gN7Y1M/Nz9DU19vh6e547OXh3uDj6Ozvent9fXz18/Lx8fDw8Xjs4dfS09je5unt7/Hz8/Ly83p7e3t6eOvo6Orq6enr7Hd3eHl5eHd2dXRy4d3d2tHJxs/id3h4enx+fXt4deXf2NXV19/mdXd4eXt+gIKCgX98ee3o5ubm5OHhT+Ln83+Afn19e3nq5N/h53l+gIB+f4GBfnp3dnd5fX5+fX5/f4OLjo2JhIF+e32AgoF+e3l5eXbo6HZ4eXp7fH+DhYJ+fHp7foB/e3d27HiEeYB4d3d3dujl53Z5enju7Ozo4Nze397Z1NbX19bOzuDo59/Z3OZ5fXvx7O3u6N3V1Nvi5+x5e3t8fHnm2dPX3+Xndnh5eXd15N/h6vPy8u/p4d/o8Ore2dvf3Nrjdnx+e3p8euji4eHk4dXX2+Lq6ubf2tbb5Xd8fXp37Xh7fXrp34DW0dnpe/Tu6OTp9PPm3tzd3t3a1tXb39jQ0NPU09HT0tDS2N/i4+DX09nh6+/o4N7h5u3x6uHc297ldXV2d3l6eO7u8PDy9Hx8e3x7ent7foCDhYaFgn965d/b19bdcnd5enl5enx/goSEg4J/fXp4dup0dnh6eXl3d3h5eXd2dh95eXh4eXl6e3rzevX29/f18/Hw8O7v8fb29nt7fHt6hHmEem55eXh37Ozq6Obk6Onp5+Hh4+p4eO3r6OvveXt6ee3o3dnb3eLk5Ofq7PL0e3v08Ovo5uXl5uPj4+Tnc+bl5eXm6Onr7O/y9Xp68/Lx7u7u8PLz8u/s6Obm5+jp7Xh6euzn4t3e4OTqd3l58O7v8IV4gHl47uzq6enp6u3xenx8e3p5eO3l3t3j63d3deDV0tPQxsHK23R26OHd3eV3e31+ffbx5t3f5Ono5ubp7Hd5e3+Bf3ru7vL08uzm5uvs6Obm5+nt8fLt4dXS2eJzdHNzdnp8end3en5+enju7u3p6OTi5Ofj4eLi4+bodOXnd3p6gHjl3d/l3+DneX19enp57Obi4ODe3uR4eXZ3eezl6HZ15eHj53Z26eTo7e7s5+Te3ebwfH987ebj5nV4fHbj4+bo5+bndnfj2uZ8hIaB+PXy8OvoeoSHfu/p73p7eXd3eXp5eHbp5+p5fHnt73p6e3nu6+Te3uHn6+3m2dXa4+jnLejo4+Z2eHp8fHp26Obi5XqBgH5+f397d+jl4N7l6ut3eXl37Ofm6Ono5OHn7YR4YnZy3t3g4uXl4d3d3+Dh5Op3d3ft5+Pg4Obq5t3Y2N3n8n19e3ry8Xp6ee/s7fHy7+7u7ufe1M7N1Nzf3drc3uHj5HJ0dejm5OPj5ujo6Obk5uXm5eXm6u/y8vHu7enl4uDgNs/Kx7+8uru/wsbK0NFoy8O+ubzAxcrNaWttbW3Z2djW09DPz2jNxr+8v8bN1dja2djW1NHP0IVogGfMzM7R09LR0dFpaWlqaWhnZmVkYsHAwcK8t7bA029wcHFzdHJva2fJw768vb/Fy2ZoaWlrbnFzdHNycG3X09HR0M3KxsPCyWlqampra2nNx8LDympwcnNxb3BvbGlmZmdpbG5ubG1tbG91eXp3dXNycHFyc3FvbGtqaWbHymhrgGxsbW1wc3NxbmxrbW9ycm9raM9qbW5ubWxsbm5s1NLWbnFzcd7a2tfT0dTU0crGx8fIxb69yczJw7y8wmZpaMzIyszHvbi4wMbKzWhpaWloZsS3sba+xMVlaGloZ2bMy8zR083MycjDwcvU0MbEyMvFvsBjaGtoZWVkxsG+u7u6Mbi+w8jR1tjW0cvN1W1xb2tmy2ZoamfHwru3vcxt29jSztDX187GxMXGyMfEwsbLxLuEt1u4ure0t8DK0dPOwrq9xtHUzsfFyc7V2tXNx8TDxWRkZmlrbW3Z2tzb295wb21samprbG5vb3FycW9tZ8C3sKywvGVqbGxramprbW5wcXFxcG5sa2nQaGlrbW1shWoeaWhqbG5ubW1tbm5t2m7e39/e2tfU09TS0tPW19hthG6EbYBub3Bvbm1qZ8jFwsHBwcXHyMbCw8bLaGfNy8nLzWhqamrU0svJy8zP0NDR0tPW1Wpq0tDNy8vMzc7NzczO0mnS0NDP0NHT1djb3uBwcN3a2NLQzczLy8zJyMbExMTGydFrbm7VzsW9u7m7wGFjY8bHysxnaGhoamtt2tjV0c7MzIDO0Glra2ppaGbIvrSxtLpeX2C+u7u/vLOssbxiY764t7i+Y2VnaGfIxLu2usXO0tHR09Vqa25ydHJu08/Ozs/Ny8vOzcnIyc3Q09PSzsa9ur7EY2RkZWdqbGppa3B0dHFx5OTh2dPNyM3U09PU0s/Oz2jP1W9ycW/SxsPDur3LblRzdHFubNLMx8XGxcTHaWpoamvRystmY8C8vcRkZcjCx8nIx8O/ubrG0WxubNHNy81nam1ox8fJycfHyGZnx77IbHN1b9bV1NHMyGp1eXTi4uVzcnCEbYBra2rRzdFtbmvR1G1tbWvQzcjFxsjMz9LOxMLJ09jY2dnU1GxsbXFycW7Uz8rKanBvbm5vb21q0MzFwcbLz2ptbWzUz83P0c/IwcTMaGlpamlozsvKyMfHxMHBw8PAwMRkZWfPz87NzdHSzMTAwsfP2G9vbm3a2m1sas/LzM7PzTLMzdDMxr+6uL7Fx8fFx8nKyclkZGTHxsbGyMzPz87Lx8bExMPCxMrO0tTT0tLPzs3Nz41+AX+JfoV/iH4Bf5F+hn+Jfot/iX6Kf4h+jX+Lfod/hX6of4J+lH8Bfop/g36Ef5h+g3+MfoZ/h36Gf5V+h3+SfoV/AX6Ef4Z+AX+1fod/hn6Rf4Z+k38Bfpd/An5/j36Rf45+gn+FfoR/jn6Cf41+AX+MfoJ/k36Df4h+g3+Efod/iX6Hf4Z+g3+JfoJ/hX6Ff4x+h3+Yfo9/kH4Df35+hH+HfoZ/iH6FfwV+fn5/f4R+gn+MfoN/hH6Ef4d+BX9/fn5+hH+GfoR/g36Kfwh+fn5/f39+foR/lH6Hf4R+iX+HfoR/in6Gf45+g3+OfoR/BX5+f39/mH6Df51+AgIEACPy8Orl5+z0/Pr29PT3/oCCgv/8+PTu7Ojn5ebw9vn+/4CCg4SEEYWFhIOCgYGB///7+fn2+f3+hICD/oT7Evz8/oCCg4OCgf37+vr7/oCBg4SFgISEhYaHhoaGiIuOjYyKh4aIiouKhoL9+/3/gYGA+/f3+Pf3+Pf29fT2+fv+gIGBgoODg4L99vf9gID++vTw7/L29vPz+4KFhoWDgoL++PX1+Pv/gYKDhISCgYGCgoOCgICBg4L++fn6/P+A/v+DhYL47urq7/T08ezo5+np6OnugPX9goH16ePj6u3t7+7s6+7x9Pj7gICDiIqJhoODg4D79vX49Onj6O/u6OXs9fn8goWC/Pn08/Lv7vH19O/s7/Tw7fH6/4D68fiBgv3t5+bn7PT6/f+BgoGAgYOHiYuKh4X42cfK0dPQ2en1+PXu5+Li8oGEhIOB/vz69feAgoL/Gvf08Ozr7O3r6ezv7Ons7/Du6+bh4+Xj5/H5hIFN9OPc3eTl4ODm8PX18e7s8Pn/+vT2+vyAho6Vk4j99vj7+/z+//38/oGEhYWCgYD+/4D9+Pb4/YCChIiKioeEgf39gIOGiImIhoaHhoaEhTyEhISDgoGA/vyAgYKEhoaFgoGA//37/YCBgfvz6unt8vX19PX3+4CDhYWEhYaIiYeGhIKA/Pfw7e32/4CFgSOAgP///fr49/j7/f7+/Pr59fLz8vf7/fz49fTy8/b19vv/gISBgID9+Pb29Pb19fPw7e7v7vHw8PHx8vLu7u3u7/H19/n8/P36+PXw8PP3/IGBgYD9/f+AgoOFiIqIhoKA/v+A//38/oCDhISEhYaHiIeHhYSDgYD+/Pn19PX2+Pr8/f+AgoOC/PLu8PT29/j39fHu6ujo6urk4+fr7fD3gIL/9vLvgOzr6+zx9fuBg4KBgIGDg4OB+vf3/Pv7goWFhoT//IKFiIqJh4WA8Obm6vL8/vyChYP87unt8vT3/oGC//f18/qBg4OGi4+Qi4P88+/r7PWBhIaGhoH1+f7//Pv8/f2AgoD49/r49O7p6uvs8fv99/6EgfP09/f9goWDg4SIh/70gPXz6ebo6+2AjIiDgoKCg4SFiY6Lg4D//Pv7+vb0+fyAgoH27O7z9/2Ag4L98ujs8+/n39bY3uXv+P389e7u8/n6/IKHiIaDgP7/+/f3+Pfz8/+Fgvnx8fDs6Onp6/D5gYODhIaGg/z0+Pz58e3w8/Hv8Pj+gYOB/Pb18uzn6vP8UP789Ond09PY4+ns6+vs7e/v7O3w8/X4/f/99/Px9Pr+gPz5+fr9+/jw5+Lh5u3z9vf39fTy8/X5/oGBgoH//Pn39PHt6+rr7u3q5+Tm6e/zOODe2tba3+bv7Ofl4+Xrd3h47erm4t7c2dnY2ePp7fHyeXt8fHt7enp5eHd2dnZ16Onm5eXj5+vshHc97Ozs6erp6erp6nZ3d3d2dOTi4+Tm6nd5e318e3t6enp7e3p5enx+gX9+fHp5e31+fXp36Ofo6nd3defk5YTmMOXj4uHj5+rueHl5eXp6enns5+fseHfp5d/c3eHm5+Tm73x+f316eXjq5eHh4uTndIR1gHRyc3R0dXRyc3V3d+vo5+jp7Hbs63l5duLc3N7j5uXj3trZ2NjY2d3k7Hl45tzY2uHj4uPj4uLk5+rt7nh4en1/f317e3x67unp6+ba1Nje3dfU2+Po7nt+fPDs5uLd19LS09HQztTc3Nzi7PN78urve3zx5Nza2t7l6uzueXl4gHh5en1+f316d97Et73ExcHI2eXo5NzU0NHfd3x8fHvx7+zo63l8fffw7Ojm5+jq5+Tm5+Tj5urs6+ji3N7h4eTp7Hl5e3vn1c/R19jV1+Ds8vLv7Onr8fLq4t/g4nN4gYmJgO/p6+7u7/Dy8fDyfH+CgoB/fff0eOvj4ePmdHZ4ent8fXt5d+zsd3p9fn9+fX19fHx7e3t6enl5eXh3d+zrd3d4ent7enh4d+3s6+14enrw6+Tj5+ns6+np6+95ent6eXp7fH18e3p5eO3p4+Df5e14eXl5enp5efLz8e7t6+3v8PDw7u3u6+rp5+vs7ezp5+fm5unp6u7yhHpFeXfq5OHh4OLj5OXk4uXm5+np6urr7Ovn5ePi4uLl5+ru8PHw7erm5ejs83x9fHvz8vN6e3x+gIKAfnt58fJ58/Hw8np8hH0BfoR/gH18e3l47ern4t7d2tvg5OfpdXd4d+fe2trd3+Hk5ufk39vY2Nrb1dPY3N3f5Xd57ufk4d3d3+Dj5+t4eXl4eHl7fHx56eTj6OrqeXp5eXfk4XN3eXp4d3Zz29TV2eDo6uh3ennu497h4+Pi53d59PDu6+97e3p8f4KCfnjq5OHfgODqen5/fnx44+ju7+zq6+zrd3h26Ofs7Ojh2trb3eTw8+3xfHnl6ezq7Hl6ent9gH/u4uLh3N3i5+h7g4B7enl4eHp7gIWBenjy8O/u7ezr8vd9f3zo29nd5O15e3ru5Nvd497Y0MrN09nh6u/u6ODf5Orr63h8fHp3dObn5OPmgOnq5+jxfXrq4+Lj4N/g4N/h53Z4eHl7fHrs5+zw7ufj5OTe2trg53Z5eO3q6url4uXu9vby6d3RxsXL1t3i4+bq7e/u6Ojp6+zu8/Tx6+fl5+vudurn5ubo5+Te1tDP0tne4ePj4eDe3+Hm7Hh5ennw7uvp5eLg3Nzd39/c2dbYA9ve4SHMy8bCxcnQ19XS0M3Nz2hpac/Oy8rJycnLzM7U1tXV0miFaYRoT2dnZmdmy8vIx8jGys7QaWpqatHR0MzMy8zOztBpaWloZWPBvr/BxcpnaWtsa2ppaGhqa21samloaGloaGZlZWhqbG1rac3LzM5pamnRz9CE0oDQzcvIyMvP02pramloaGhp0M7P1Gxs1tPPy8rLzs3JytFtcXNycG9t1c/Ly8zO0WprbW5ubm1sbGxramlqa21s1dLT1trecN7fc3Vz29HMy9HX19XQzMzOzcnGydDZcG/Sx8TH0NTT1NLPzc3P0tbYbWtqaWlpZ2ZoaWnU1NfY0IDDvcLIx8K/xcvO0WtsatHQysjJyMfIx8O+ur7FxcTIztJpzsnPbG3SxsG/xM3W2NjWa2tqZ2dnaWpraWdmxLWvtbq4tLvK1NfV0s7IwcRkZmhpa9XU0s/Ra21t1tLRz8/R0tLMxcbJysvOz8/OzszLzc7Ky9DTbGxtbdHEwMPKzIDJyc7U19XSzcnL0dPNxsTFxWNnbHJxasXCxMbIycvMztDUbW9xcW9ubNXUatDLyszOaGhqbG5ubmxoy8llZ2psbm9vcXJzcnJxcG9ubGxra2lozs1naGpsbW5samhmy8nJzmpsbdfTz8/T1dfV0tHQ0mprbGxrbG1vcG9vbm1s1gfRy8fFyc5ohGlrampr1tbT0M7MztHS0dHPztDOz9HS19rb2tbT0MzKysjGyMlkZWVmZ2fNy8rLy83O0dPV1NbW1dXU09TV1dPPy8fEwsHFx8nNztDP0M/NzdDS1Wxsa2rS0dJpamtsbm9ubGpp0dNq0tDOzmiFaQVqa2xtbYRsCGvU0MvHxMTDhMKAxWRmaGfKwr/Aw8fJycjGwby6ubzCxMHBxcjJys1pas/Jx8bDw8PEyMzQaWppa21vcXBubNDLy83NzGhpZ2dmxcJkZ2lqaWloZsS/wcXN1tjXbnBv2NDP1dbS0dhwdOfi3dbWbGxrbG5wcW9t19HNysrSbnBvbWtmw8rR09DPz82Ay2dqaczM0dHMxsLCwcHH0dPO0Wxqys/S0NBoaWttcHFuzcfJysXK0tfUbnNuaGhpaWlqamxwb2tr2NfW1NLQ0dnfcXNvzsG+wcfOamtqz8fAxMvIwru0t77DyM7R0s7JyMzS09RtcHFxb2vQy8O/v8LFxcnWcG7UzMzNy8bEwsJTxctoamtsbGxqzMbJzs/My87Rz8/R2Nxvb23U0NHRzcnL09rb2NLJvbS0uMHExMHAwcLExcTGyMvMzdHR0M3Ly83S1WrSz87NzcrFvrWvr7O6wMKEwxzBwsLGyWZoaWnQz83Kx8XDwcDBw8LAvry+wMbLjn6Df49+j3+JfoR/in6Gf4Z+nX+EfoN/j36If4R+gn+Lfod/h36Rf4Z+Bn9+fn9/f5J+gn+Qfot/kH6Df5N+Bn9+fn5/f4p+jH+RfoV/hX6Df5t+hH+XfoZ/i36HfwN+fn+Ffol/gn6Wf4J+in+EfoN/jH6Of4d+iH+gfoZ/qX6Ef4N+in8Dfn5/hH6Qf4x+hH+YfoJ/i36Kf4Z+hX+Cfoh/iH6Df4h+gn+Ffol/hn6Gf4l+g3+PfoJ/hX6Hf4l+j3+JfoN/hn6Df5d+hn+KfoJ/i36Hf45+g3+pfgF/mH6Ef5N+AgIEAICA/vn29PP4/P7///7+/oCCg4ODgYD+/v39/fr5+Pf39fb39/r7/f79/v+ChYeHg//7+Pr8+/z9+vj59vX4+vv6+vv8/oCA/4CBgYD//v39/4KFhoeGhIODg4H89/Lw7/H3+Pf6/YCB/fj4+Pn8+/v9/v37+fb19/2ChYWDgYD++zL8/f7/gID//fr39/f4/P/++/bw6+zz/IOHiIaFg4L/+/bv6+nt9Pf18u3p6ezw9vz//YT7RP6BhoqJhID8+PPv8vmAg4SB+fHp5ufs8/bx7O71+PX09PPw6+jo5ePo8PXv4tve6/j/gYKEh4mMkZCMh4H/gYWIiouIhIUPg4KDgYD/gYSGiIeD9unkhOCA4urx8e3q8Pn++/fy7vX6/4D8+PT3+fby7u/19/j9//348e32/oCBgoKDh4mFgfz8gomI/+zp9YKHhoHz6enw9v6Dg4OFiYqIhYKDhoL38O3r6Orv8O3n4+Tn6ezz+PHo5Of0hIeD/PXu5+Xk4dzY2N7l7PDz9ff6/vz9goWFhYQNg4KB+/b09/yAgYGBgISBToD/+/v+/vz+gYODgfvy7u3w9fr/gIGDhIaIi4yMioeEgoKDhIOCgYGBgoODg4GA/Pv+goWIiYmHh4WC/fj09Pf3+fn5+vv8+vjy7/L3/YSBef/9+vXy7/D09ff5+fr7/P6AgICBgYOGh4eFgf339fb29fX29vPu7Ono6Ort8Pb4+fn39vX1+P6Bg4SFhIKA/f3+gIGCg4SDgfz39Pb6/4KDg4H38u7r6eTf3+Dj6vL2+vr6+PX2+4CDg4KA//6AgYKCgPn18/H0+fyFgG789+/s7/aAg4WGhYaGhYSDgPXq6fH7gP7z6+v0/4OGhoWCgf/8/Pz48+7p4t7e4efu8fDt6eHg5uzv8fHt6Ofr8fb6/4OEhIOBgICBgoKEhIL26OPo6unt+oD36+Xl6e7y8/T3/IGA/YGEhoeGg4WCgIH79PL1+fv6/4SLkJSYn5yWjYWA/fXk09Hc6vDu6ejo6fD07+zu8vPv7fHq6/aAgoOB//z6+PT0/P/8/IOGgfHp5+n1gYSEgYCDhoeHhIKCg//y9PXy9fqBhoX15/GAhoeB9fL18erwgIaC797d6YCNko6Li4mFgoL88OXd5Pf8gO/j3dnZ5PX9+/3//Pv8/v+Agf+AgPfn3uLr7e7y9/r5+Pf19ff17uvu7+3v9fv/goOA/PyA/vj29PHx8vLz9/f19Pj9gIKGiIiGgv329fj7/P+BgoH79vX0+Pr5+/6AgYD9+vf08e/x9vv9/Pnz8/j9/4CAgIGCgYD69fP0+Pr6Dvj29vb3+Pn49fPy8/X3hfgI+vn5+vz9/4BHevPu6ufm6Ozs6+vq6+t3eXp6enl47u7t7e3q6ejn6OXm5+bo6Onp5ufodnl7e3jr5+Xn6ejp6ujn5+Tk5ufn5ubm6Ol1deuEdwrs6ujo6nd5e3t6hHmAd+jh3NnY2+Ll5+vueHjs5+bl5efm5ejr7Ozr6ejp7Xh6end0cuLg4+fr7nh58O7s6urq6+/x7+rk3dfX3eZ4fH19e3p57ejk39zc4ebn5eLe29vd4OPo6OXj4eHh5HV6fX16dujk39vd5HV4eXjr5uHf4ebt8Orl5uzu7Orp6OIl3dvb2tvg6e3k1s7R3ejveHh6fH1/hIJ/e3jteX2AgYF+e3p6eoR4gHbteHt9fX155tzZ19bU0dDV2NbRz9jk6+nk4Nvg5Ol16ufm6urm4uDi5OLg5ejn493a4+t2d3l4eHp9enfr63qBgvfm4ep6fn565+Dj7fP4fn59foGBf3x6e3166uXl5OLj5+jl4N3e4eHh5+rk3drb5Xt/fPHr5N7a2NTPysvQYdjf4+Xo6u/z8vR9gIGBgH18eu3n5OfseHp7e3t8fHx9ffj08/T08vR7fX188ejj4+Xo7fB5eXt8fX+BgYF+fHp4eXp6enh3dnZ3eHh5eXjw7/F6fX5/f35+fXvv6+jq7vCF9ID19PHt6+3w9Xx8e3rx7+vm5OLi5ujr7u/v8PHzent7e3x9gIGCgX328O7u7erp6Ofk4N7c29rc3+Hn6evs6unp6e3ze35/f358evHv8Hl6e3x8e3nv6+jq7fF7fHx56OHd2dfSzs3O0tng5efo5+bk5ux5e3x7evLxeXl6e3nq5nfj4uTp7Xh5enp69O/n4+Tod3l6enh4d3Z1dnXj3uDo8Hrv4tnX3uh4enp5dnXn5eTl4+Dd2dTQ0NLY3+Pk4t3V09bd4eTk39nX2+Hm6u96fHt6eHd2dnZ1dXVz3NDN0tbX2+h35drQzNDW3N/k5+t4duZzdXZ3d4Z2gHXl4OHk6Orq73yEh4eJjYqFfnh16OPYy8rQ2d7e3d/g4efq5OHj5ufi3NrS1OB2enx68e/u7ejm7e7p6nt/e+jf29vleHt8eXh7fX1+fn5/gPru7+/s7e95fXzj1t92e3156+vu6eHid3x659ra43mDh4OAgH98ennq4dfS2u3xHuTY08/P2urw7e/w7uzt7u93d+pzc93Qyc/W2Nvg6ITsXejo6ebf3eDg3d7j5+x4eXfq63ft6ejn5eXl5OXn5+Tj5el1d3t+f3588uzr7vHy9Hx8euzm4+Hj5eTn63d5efDu6+nn5ufr7+/s5uDf4+fpdXV1dnd3dubg3d3g4oTjGOLk5Obk4uDe4OPl5+fo6Onr7O3w8/T3ewlo0M3KyMbJzc6EzxbQaWpra2ppZ8zMy83Ny8vKycnHyMjHhMiAxsfHZWhqamjNzMvNz87OzsvKysjJzM7Pzs3NzcxmZsxmZ2do0dHR0tVtcHJycW9tbGpnx7+3srCyuLu9wsZlZsjFxcXGx8XFyMzQ09XU09PWbG1ta2lnzcrLztDSaWnRz87P0tXZ3N3b19LMxsTGy2ltbm9ubm3Y1tPOzM3S2NqA2tjV0tLT1dbY19PQzs/P0Wtwc3Jva9TT0M7Q1W5wcnHf2tXT1Nnh5ODb29/h397d2dHJw8G+vsXP1c/Evb/J09hsa21tbG1wbm1qaM9qbnBxcW9sa2tramlpaGfQaWttbWxpycLAvry8uLW4urewrLK/yMnIxsDCxsxo0dDQ09OAzMW/vsfO09XTzsS+vcfRamtqZ2Zoa2lo0NNuc3LUwr3CZGdoZb+3trzDz21ubm9wb2tnZmlsa8/N0NLS09bX1tPQzszJyM3RysK9v8psb2zSzsvIx8bEwb/Aw8bIycrMzM7Qzs9qbW5tbGlnZMC7u77EZGZoaGlrbG1vcN3Z19Yx09HSa25vb9rV0M7P0NPUamprbG1vcXFycW9ubm9wcHBubWtqaWhnZmRjxMTGZWhqa4RsUGrR0NHU2dvd29va293c29jV1tjabW1satHPzMrJycnNzc/Qz87Nzc5naGhoaWttbm5sac3GwsG/vr29vr69vr/BxMbJzM/Q0dDPzs3MztJrhG0xa2nQ0NJqa2xtbW1s19PR0tPWbG1ubNHOzMjGwr69vb/Fy8/S09PS0NLXbnBwb27b2YRsCWnMx8bGytHUbIRtgNfSysbGyGVnZ2dmZWVkZGVkx8bK0tds0cW8u8HJZ2pra2pr2NfW087Iw723tba6wMXHxcG8tra8xMjLysXBwMPJz9Xab3BwbmxqaGdmZWRiX7Oopauws7rDYrisqauyub/DyM3QamjMZ2lpaWhnaGpsbm9v3drd4OLh3dxucXFxgHJ1cW5oZWPJyMG1sbS7vLm6wMfL0NLMxcXGx8fHy8TFzmpsbGvTz8vIydLb2dLQbW9rycPBws1sbm9samxtbm5sbnN25tjTzsnN021xbsW3wGhucW7TzszFv8Jna2jFvsHKa3BxbmtramdmZ8vEvLnD1trPw7y4tr7M0c7O0NDRZ9XW1mppzWZlv7CqsLrAxczU2NnZ19LR0MvDvsHDw8bKztJra2nP0GnTz87Ny8vMy8vNzMnIys9qbXF0dXRx3NfV1tbV1WppZ8jEw8PHyMjKzWhpaM7MysjHxsjM0NDPzMjIzM/RaGiEZxZmycXExsnMzMrIx8TDwcHCwsPDxMfJhcoIy8vKzM3O0GgBf41+h3+VfoV/lX4Df39+hH+Ffop/i36Cf5F+hn+GfoJ/kX6Hf5l+hn+GfoR/oX6LfwF+j38BfoZ/l34Bf5R+iX8Ffn5/f3+EfoR/hn6Mf5Z+g3+Vfoh/hX6Kf4d+hH+Ifpt/g36Jf5N+hH+Qfot/nH6Hf4N+h3+GfoR/lH6Ff4J+hX+HfoV/hn6Lf4V+AX+GfoZ/oX6Nf4h+AX+LfgN/f36Mf4h+i3+afoR/in6Df4V+jX+HfgZ/f39+fn6Ef4Z+g3+Efop/l34Ff39+f3+afgZ/f39+fn+Pfod/h36Df4l+g3+Rfod/oX4BfwICBAAG9vj6/YCBhIIBgYeCN4OCgYD9/ICA/fv59vb39/Xz8Ozs7O3y8/b6+vbz8fP19/r59/Xz8O/w8fP2+v3+//78/Pz+gIKEg06CgoGBgYD+gIKDhoeGhYWFhomLi4yLiYeGhIKB/vr49vPz9vn5+Pb09PX29vb3+oCCgoGA//+AgIGA/vv49/Xy8fDv7Ofi4ur1gIKDg4OEgiCB/vny6+fl3tLGvr7L4/6KlJqcnJiVk5KQjIeFhISGhoSFgIOA/oCBgPz18fL3+/v39vj39/b29PP19vTw7/P8goKA/v6A/v3+/4GEh4qKh4L89/j+gID++fT09fT1+oGEhoWB+/b29fDt7/Pz8feFiYqJiYeJiYiGgoD+gIOGiYuLioiGgvz+gYKDgvn19Pb19PHt7fD2+/2AgoaJiYWB/fv+gID+gIOHiYuNjob59PiAg4WHiIeIhoOA+e3i4eTp7/Dw8fqBgoD++/6A8+jq9f3/+vPt5+Tl7Pb9/vz8/fz38O3x9ff4+4CDhISGiYyMiIP/+ff28/P5/fz59/uCiY+QjoyIhISEhYWGh4aC+u/o6vH5gIOGh4T98u7z/IGCgf/8TPn6+/39/Pn08vT1+Pr7+/n3+P+Eh4mLjIuIhoWEg/338+/u7u7y9vn8///6+Pf5/4KCgYD9+/r39vPx8/b5/f7+/vz7/Pz7+vv8/YCEgUGDhIaIiYqLi4yMi4mHhYOBgIGBgoKDgoH//v77+vn3+Pn7/oCA/vv48/Dv8fL1+Pf4+fn7/YCCgYGAgID+/4KFh4WIJoeFgv379+/u7+7y9fn59vDq5+fq7/P29/Tw7u3w8/b39fT09/v+hICAgoOFhYSEhIOBgP/9+fXz9PXz7+vs8PL19vf6/P6BhYWB9ezk3t/j5OLb08/U3NzRxsbV4+vu6+/+iY+NhfXl4ODe29ne4eTm5env9vz89enf4OXl1r2onqGsvdLh6Ozk1czO1+Lr8fb5gIGBhIaKi4eB/YGFhYL69vT28ujk5uyA7/P3/YKDhIH88ejk7/6DhYOBgfvy7/P8hImHgoGCg4L87OXs+4WHhYSGiIiGh4mLiIOBgf338vD2hY+OgvaAiY+RjIP79vH1+vn094GC+e3s8/2EiYuKg/n2+oKIh4H8+fHj3N7h397g5+7y9ff38+7r6OLd2djc5u7x9Pj8/oBdg4eKioeEgPTq4t/m8fuAgYD+/Pz/goOB+/Lq6Ojo6ezv8vf8gID++vuAg4aHh4eE/vPo39zg6PX8//78+vn7/4KDhIOB/vz8/f/+/f38+/v8/fv49PHy9vyAgoOEhYKEgQ2Cgf/8+PXz8PH3/P7/hYAF/fv5+PcE5ubn6YV1gHR0dHV1dnZ2d3h4d3bs7Hd47uzq5+bn5+Xl4uHi4+Tn6ert7Ojk4uPk5ebn5OPi4OHi4+Xn6uzs7ezp6Ojqdnh5enp6eXl5eHh37Hd5en1+fXx7e3x9fn5+fXt5eHd1dejo6Onp6u7w8O7r6Ofn5+jo6et3eXl5ePDxeXp7evLwD+7t6+nn5uXh29XW3OV3eYR6I3t8fXz28evk4N3Vyb61sr3R53yDh4mIhYKAgIB9e3l5ent7hHqAd3Xqdnd36uTh4uXp6uno6ero5+Xj4ePl4+Hi6PB6enjs7Hbo5eTkc3Z6fX16d+nn6vF7e/Tu6enp6OfqeHt7enbn4uHe2tna3uDf43l9fXx8e318e3l2del2eXx+f359fHt46+14enp45N7b3+Hi4uLl6e7x8nl6foCAfHjs6uuAdu14e36AgYSDfObh5nd7fYCBgIB9enju5t7d4OPn5uXm7Hl5eO/t8Hjm297o8fPx7Obh3dzh6fDy8fLz8uzm4uXp6urrd3l6enx/goOBfvfz8fDv7/P29PHt73yBhoaFg4B+fX5/gIGCgH3w5t/i6fB8foCBf/Tq5urzfHx89fFj7u7u7+7s6uXj5OXm6Ofm5eXm7nt9gIKDgn9+fXx78Ovo5OTj4+bp7fDy8e7s7O3ze3t7evHw7+3s6efp7O/z8/Lx7+7v7+7u7u/weXl5eHh5en1+f4GCg4OEg4GAfXt6eHl5hHoqee/u7uzr6+np6+zueHjt6ubi4ODh4uXm5ufo5+nsd3h4eXh4eO/wen1/hICAgYB/ffPw6+Pg397h5ejp5+Pe29rd4ubp6ubh3tvc3+Dh39/g4+fqdnZ2d3l7fHx7enp5d3Xp5uHe3N3f3dvY2Nvc3t7e4eLkdXl6d+Xd1tDP0tPSzMbFy9TWzcLCztrf4Nvc6HyCgXzl19LS1NTW29zd29ja4Obr6+Xb0tLY2s+AuKGTkpmmuMHGysa7tbnDz9fc3t5xcXFydXl7eXXmdXh4deTi4ubj3drc4eTn6ex5enp58Ork4On0fX59fX316+bm7XyAfXl3eHl35tjU2+p9f319f4GAf4CCg4F8e3vz7unm6XyEgnjoeH+DhIB46ejm6u7t5+h4d+PZ193neX6AgYF76+ntfIKBffXx6NvV2+Hj4+Xq7u/u7+7p4t3Y0cvGxsvT19jZ3eLldHh8fn58enfp4t7d4+rveHh27Ovp6nd3deTc2Nna293g4+bq7nh47ejndXd6enp7eOjf1s3LzdPe5ejo5uXl5+x5e3x8evHu7ezs6ujm5ePi4uPi4N0N293h53Z4enp5eXl6e4V6DXnu6+bh3tvd4+jq63aEdwXs6+rp5xLNzs/Rampra2pqaWhoZ2ZlZWWEZGfIyWdo0NDR0NDS0tDPzMnJycrMzc7S0c/My8zMzc7Oy8nIxcXHyMvN0dPT09HNy8nKZmdoaWtra2xsbG1t2W1vcHBwbmxra2pra2ppaWdmZmVlZs7O0NLS09bZ2djW1NLRz87Ozs9phGoW1NVrbG1t2NbV1NPS0dHR0MvFxMfNaoZrXmxsa9POycXGyMa/tq6stcXXcnd5eXh1cnFxcnBubW1tb3BwcG9ubGrWbnBw3tnW19zh4uHf4OHf3dnTz87NysfGzdVtbWrR0GjNy8nFYWJkZmhoZ8vLztNrbdzZ1teE2IBtb25saczIyMfCwsTKzczPbnBubWtpamppZ2VkyGVoa21vb3BwcG3W1mxtbm3U0tDQ0M7Ozc/U2dzcbWxsa2tqaMzLzWbLZWZoZ2doaWS8ub5jZ2xwcnR1c3Bu2dDIx8bGxsTBwMZnaWnS0dRry8DAyM7PzcvKyMbHy9HU1NHPz03OycTAwsXGxshmaGprbXBydHFu19LQz87P0tTSzcnJZ2tubm5tbGtsbW5vb3BvbdTNx8jLz2psb3Fx3dbS1dpub27b2NfY2dvb2NXQzoTPDM3LysjK0Gttbm5ubYVsIdTQzcnHx8fKztLU19bT0tTX3nFycnLh39zY1tPR0tTV2ITZCNjY19XR0M7OhWcSaGlra2xsbW5vcG9ubWtpaGdnhWgfZ8vJycjHx8fIy87RamvV0c7KxsXGx8rNztDS0tTXbYVvBXDd3XBxhHJFc3R0dHLf3NbMyMbExsrNz87MyMfIys7Q0tLOysfExcfIyMbFxsjKy2ZlZmdpa2xsa2traWhmy8rIx8jKzMrHw8LDwsPDhMKAYmVmZMPBv72+wsTDwLy+x87Ow7axt7y9vbq9y251dXDMu7GtrrCzubu8ube6v8THxr+2r7K7wsCzpZudpK+9wsXJyMC8v8fN0M7JwmBfX2FkZ2lnZMdmaWlnxsPDyMa+urq/xMrO1G1ubmzUzcfDytVvcXBvbtXNys3VcHRxbWuAbGxrzsPBytZxc3Jxc3Z1c3N1dXNwbm/b1dDKy2xyb2O3XmdtbmliwMLEyszIwcFiYbarqbC4YWZpamXExs1scnFu2dXOw8DHzs/Pz9LU0c7OzcvGxMG7saignqOqsLa7wcZmam5wcXBvbNLLxMHEy9Jra2vW1dXXbW1rz8fAvr4Ov8HExsjLz2lp0s/OaGqEbBZqzcS9tbK1usPIzMzLysrMz2psbW1thNgY19XS0M7MzMzOzcvIx8jL0GlqampoaGhphmoNac3Lx8PCwcPKz9LUaoRrBdPS0M/OhH6SfwR+fn9/rX6MfwF+lX+TfoV/gn6Ef49+in+Ofpd/BH5/f3+XfgZ/f39+fn+Efod/hH6Cf4h+hX+Lfox/AX6Kf4J+hH+Nfod/BX5+fn9+iH+Dfop/i34Hf39/fn5+f5x+in+MfpB/hn6Ff4V+g3+Vfot/kn6Ef5d+nX+LfoJ/kH6Hf4J+i3+jfo5/k36Ef5h+hH+sfol/AX6Ef41+hH+GfoV/hX6If4V+j3+FfoR/AX6Gf4h+gn+FfoV/g36Ef6B+iH+HfoN/hH6Df4x+BX9/fn5+h3+QfoV/lH6Pf4t+hX+FfgICBAAPgP77+fj39vXy7/Hy9Pj6hPhX+fr8/fz69/b19PLx8O/w8vPz8fDv7/L2+PuAgYKCgf77+Pf4+Pr5+Pf19PX2+Pn7/Pz9/v3/gICBgf/9+vb08/Lv7Orp7PL6gIGDhYaGh4iIiYqJh4SChYAB/oT7gP6BgYKDgf/7+Pby7Ojn6e3y9vj6/P6AgP/9+vXv6ufp7vb9gYODgoD+/4GFh4iIh4eHiImKjI6OjYyKiIaEg4H/+/r7/f+A/////v7+gIKB//r17u3v8O7u8fT4+/z8+/n28/L09vj39PT3+v2AgIGCgoOFhoWCgP78+Pj6+/j2gPXx7e7y9PX1+Pn/hIiJiIWDhISEgoKCgfrw6+vt7e7v8PLv7Ozu9PyChIWIjIuLioqIhoWGhYP9+Pby7efm5ubr9v2Bg4WHiIaEhIOCgoSEgf/69/uAgoWJjI2KhID7/P2Ci5eempCG+eHRz9ff4+Tl5uXm5ebo7e/t6+zu7+vpgOvt7+/w9P2ChYWEg4OB//36+vyAhYuPkJCSmaWurJ+L+ezs8vXz8PHy9Pb4+Pj5+Pb18u3q6uvw+oKEgoD8+/j39vb4+/z9/f359PLy8/Hv7u70/ICBgoOEhYWDgoD+/Pr6/P6AgYOFhoiIiYmIh4WEg4OCgoGA/fr5+/r6+fTuD+nn5ubs8/b49/Xy8vPy74TtE+7v7+/u7Ovq6Oru8fmAgIGBgYCFgoCBgYKDg4KCg4KAgIGChIaHhoWCgYGDhIWEhIH9/P39/fv59vT1+f+Bg4SEg4WFhoeHhYSC+/Tz9PX39/f4+vv9//35+fr+gYKB/Pf2+f6Bgf/7+f2ChYaHhoOCg4SFhYSC/ffw6OHd3uLm5+Pb1NHT2OPt8/X3/YCBgYKCgYGDhoCGhYOBgP+AgP359/n7+fTt49bOy8/c5ebg19TZ4+z09fHx8+/o4Nvc4uXl5env9Pj7+vTy9vXt49vUzcW1p5+dnJqWlZyvzuf3hIiHhoL89fHz/YePkY2D+fr/goWFgPf3gIWHhoKA/fr19vv/gYWGhP3t5N7a1tPW4fD8gYOFhoCGhIL/+vX1+oOFg4GChYaEgP339fj8/fv38evq7vPz9fj39vf7gIGBgf3u4Nne5+jr8fTu5ebx+4CChISCgYD++vb0+oGDgoD9+vXu6env8vL0+vz59fmAgoOC/fn8g4iKi4mGhIeNkJGQjIiC+/f6gIKDhIWGhYWDgoOB+/X5/1eBgPr7/4GA/vv6+fr8/4OFhoaHh4aEgPn18vP4/oD9+PPx8fP3/oOHioyMioiGhIWFhISC/vj09v2ChoiJiYeC//n08/Py8/T2+Pn6/Pz8+/bz8O/0+f6EgAb9/P7+gICAee/s6efl4uHd293f4efq6enq6uzu7/Hw7uzr6uro5+Xj5OTl5ePi4ODk5+nteHl6enjs6ebl5ufo6ejo5ubn6Orr7Ozr7O3s7Xd2d3ft6ufk4uHf3NnX1tjc4nN0dXZ3d3h5eXp7enl4d3Z2d3h37evs7/H1fH1/gH/69vPw7OcF4+Hi5emE7CHtd3fu7Onk3tjV1dnf5nZ4eXp58/Z9gIGAfnx7enp6e3yEfoB9fHt7enry8O/x8/R68/Ly8fHyent78+7q5OPk4+Hh5Obp6+vr6efk4eDh5Obn5ufq7O13eHh4eXl6e3t5d+3s6urt7ern5eHd3+Pl5+fq6/B7fn18eHV2d3h4eXp46uHd3d/e3t/f3djV09Xa43d6fH6BgH9/f358fH19e/Dt7IDo4t3d39/j7PB6e3x+fXt5eXd2dnh5eO/s6et3eHp9f4B+eXXn5+l4fYSIhoB669vPztbd3t3c29ra29ze4+Xi39/g4d/g5u3y9PT2+4CBgH9/fn349fPy8nl9gYOCgYGFjZSTiXvh2dvi5+fm5+nq6+zs6+zt7Ovp5ODh4+n1fzaCgH749fLw7u3u8PHy8vLu6ujn5uTi4ODk6nZ4eHl5e3t7ennw7uzr6+x3eHl7fH1+fn59fHuGeYB48e/x8/P08+/p5eTj5Ovy9vj48+7t6+nk4eDe3+Dh4eDf3NvY1tbY2uFzdXZ4eXp8fXx8fHt7fHx8e3l6eXh3eHl7fn5+fXx7fH1+f39+fPLx8fDv7ern5ebp7Xh5eXh4eHl5enl4eXjq5ubn6evs7O3u7/Hz8e7u7/F6enns5oDj5el2dunj4uZ2ent8e3l4eXt7e3p36OLc1tHOz9PX1s/Fu7e5v8vY3+Hj6HV1dHR0c3J0dnZ1c3Fx4XJz5+bm6evq5+LYzMO+wc3V19LLyM7X3+Xm4+Xp6ebg2tnc3dvb3uTp7O7t5+To5+HWy8O6r6GUjoqIhH9+g5Kwydx3e4B6eHXi3Nrd5nqAg4F56OnteXx8d+bkdnh6eXZ05uXh4+rxe3+BgPbn4t7c2tnZ4Onwenx+f399evDr6Onsenx7eXp9fnt36uXj5+vr6eXi3t3h5eTl6Ojp6u14eXl68ufZ09nh4uXq7Obc3efxe31/f317eu/q5OLneHt6ee/s52Pi3d3i5OPj6Onk3uF0dnh47ersenx9fn16eHp+gIGAfnp35uLldXZ3d3h4dnRycnNz4+Lp8nt67u3weHfr6Ofo6evteHp6eXl5eHZz4uDe3+Tqdenl4N/e3+TqeX1/gYGAfnyEey96eOnj4OHoeXx+gIB+eu7q5eXl5OTl5+nq7O3t7Orl4uDg5erveXl4eO7t8PF5eg5q0c/MysnIx8XDxsjJzobRBdLS0tHQhM4bzMzKycrLzM3MzMvKzM3Oz2hpaWhny8jGxMTFhscgycrLzc7Pz9DQz89oZ2dnzMvKycnKy8rJycnM0NVsbGyJazFqamlpamtra9PRz8/P0WprbG1t2NbV1NHOysnLz9TY2dna3G5t2dbRy8W+urm8wMVlhGcTzs9pbW9vb21sbGtsbW9xcXBvb4RugG/e3uDj5eZz5OPj4uHhcXJx4N/e3Nra2dbV19nb3Nva2NXS0M/Q0tTT0tPU1NRqamtramlqbGxratLQzMrLzMvKyMXDxcnLy8vOztJsbm1qZ2VnaGpra2xs08vIyMnIxsTDw7+7urzCyWhqa21wb25tbWxrbG9wb9vc29jPxLy3S7nC0NlvcHBxcGxpaGZlZWhpaM/Mys5qbW5vcG9tamfNz9BqbnR4d3Jt0L+zsbnCxcXGxcPCwsPGzM/Lx8XDw8HDyM7T1dXW12xsa4RpgM/MycfIZWdqbGxqaWxyeHhxZ8G+xMzQ0M7MycTAv7/AwsTFyc3OztHS1txxcnBt1dLOysfFxsfKzdHU1NTV19jW09DO0tdsbW1sbG1tbGtq0tLS09TVa2trbGxtbGxsa2ppaGhpaWprbNnY2drb3Nza1tTU1Nbb4OPk493Y1dLPFMrGxcPDxcbIysnIxsTCwsPEyWZmhGc0aGloaGdmZ2doaGdmZ2dmZmhpa21vbm1sa2tsbW1sbGnP0dPU1dTT0M7O0NNqa2xsbG1ub4RxgG/Y0tHQ0NDPzs7P0NLU1NLS09RrbGrQzMrMz2hozsvKzWlqa2tramprbG1sa2nMx8C5sq+vs7a3s62npqqvt72/vr/DY2RlZWVjY2NkZWRjY2TKZmjR0tXc4+Xk4dnNwbWxt7y9urW0uLzAxMXFyM7Py8XAv8PFxMbKzs7Oy8a/gL3Eys7R1dPNxLarp6eno5yXmaW9zdFsa2hlYby6u77EZ21wcGrP0ddtb25ox8JjZWdnaGnU1dLR1Ndsbm5t18/MysbBuri+yNBrbXBycnBv29bPyspqbGxqbG9xcGzTy8fGx8bFw8C+v8LGxMPDwcPFyWZnZ2bFurKxuL/AwsTEgLyysLa+ZGhsbWtqaM7JxMDBY2RkZMnKysfDxcvPzc3R0c7N0mxtbm3W0dJsbm5ta2loam1vcG9ua2jLyMtnZ2dmZGFeXFpaXF25u8TOaWnQ0dVsbNbU09PU1dZsbGtqamppZ2TEwcHDyM5ozsrFw8LCxcloa25wcXBubWxsa2trJGrRzMnJzmpsbW1tbGnOycbFxcXGyMrLzc3Ozs3LyMXCwcTHyoRmBs3O0dNqagF/rH6Ff5d+hH+OfpR/hn6Ff5B+gn+LfoV/gn6Wf4Z+AX+GfoN/nX6Lf5N+jX+Qfo9/jH6Of4R+iX+Dfod/n36Hf4V+jX+ZfoR/l36Kf4Z+k3+pfqZ/jH6Nf5J+g3+FfoJ/hH6Nf5Z+jn8Dfn9/wX6Ff4V+hX+DfoR/gn6Gf4Z+hH+Lfod/hX6Jf5R+hH+Pfod/hX6Ef49+hH+Dfo9/g36Mf4R+B39/fn5+f3+Hfol/hn4Bf4h+jn+Ffod/l36Ef4R+gn8CAgQABPv9/4CMgRKCgoGBgP/+//+AgID//fz7+/qE+wL8/oT/DYCAgIKDg4SFhoWDg4OHgkSBgYCBgoOEhYSEg4D8+vn5+Pj39vb4+fn49/Ty8vLz9fb19vb09PPx7/Dx9Pj7/4GCgoKBgYD//vv38/Hy9Pf5+/2AgYSAgP///vr4+Pn8/4KFiIiGg4GBgYD9/ICEiIeEgfny7u7v8vT18eri3uHq9fv+gIGBgP/+/4CBgYCBg4WEgfvy6eTk5+/3+ff2+f+Bgf/48uvk4+jt8O/s6+/09/n59PHy9fn/gYSC/vj4+vn3+fyAgYGBgoOFhoiJiIeEg4OChIWFSoOA/f6Ag4OB/Pj4+/7//fr5/P+Bg4SGiYqIhoaHiImGg4OFiIeB+/j49e3r84GHhoH58u/u8fLy9Pn59/b18Orp7veAg4OEhoWChP+AgYWJi4uHg4H78Ons8/n9/fjx7Orq6+vl4N/g4uTl5ujp6uzs6+ro5+Xj5Orz+Pr49/2Eio+SkpCOj5GVlY+F/fj6/fz48ujh3d3h6O/3/IGDg4H+/Pv8gIOGhoT98uzs8vb5+/z69PDr5+Xm5ebl5Oft9Pr/gP348/H0+4KGiosajIuKiYiGhIOCgoGCgYGAgYKDhYaFhYSEhYiEimeIhYL99PHw8fP4+vr7+ff28e3t7e/1+/2Agf/7+/z9gIKEhIWEg4OBgYGAgYKFhoiJiYeFgoD//v6ChoiLjYuJhYD7+/v8/fv5+Pb19ff7/4KEhoaEgoGAgID+/fv39ff4+Pv59vb2hPQO9vf7/f39+/fy7/Dz+P2GgYSAg4GEgICBgoL/+/j4+4CEh4eGhIOEhIH68u7u7/Hz9/r8/oCA/fXu6ePh4ubu9Pn7+vPl1crI0Nzn7O/z9PLs6+/09vf3+ff19fX3+f+DhoiJiYiHhYL88/H5goaIiIWDg4SHiYmJi42OjIuIg/2AhIaFgPbt7PiCiIuLh4OAgP///v///ID37evy+vz48+vk4+fp7fL07+Pc4vGDiYLy4+Lj4t/k6+/x8/X29vHp5+jr8fr+/fns5ebr8vX19e/i083V6Pb9/v+A/fbx9v+Cg4SCgP/++vj5+fv/gICA/4CBgYKBgICB/////fv9/fnx6efn6u7x8/eBhIH78eji5PaJk5CIgEL6+v369vHv8fX4+vv68+rl7P2HjYyHhISDgf75+Pf08/T4/YGCg4OB/PXt6Obk4NvZ2dvh6O7x9PT09fb3+Pz//fmE+Dz5/P358+zl4eHi4d7Y1NPV2+Hm6evt8fX5/oGCg4SEg4KA/fv6+Pr9gIGCgoKBgP/+/f3+/v39+/j4+PkF6evsd3eHeBR5eXl6e3t7enry8/P0enp68/Lx8IbvAvDxhPIGeXh4eXp6hHsFeXl5eHiFeRB4eHd4eXp7fHx7enjr6OfmhOQH5efp6urq6ITnAemE6oDo6Ofl4+Pj5efq7Hd4eHh3d3bt7evo5eTl6ezv8fR7fHt6e3rz9PPv7Ozs7vB6fH5+fHl3dnVz4+Jzdnl5d3Tk39zb3d/h4t/a1NLV3efs7nh5enrz8vN7fHt7fH5/fnvy7OXh4ePo7ezq6uzxe3vz6+Xf2Nfd4uTh3dzf5Ofq6YDk4uTp7vV9f3zx6ujp6Ofp7Xh5eHh5e3x+f4B/fXp5eHd3eHl4duvsd3p7ee7s7fDy8e7q6OvveXt9foCBf318fX5/fXt6fH9/evDt7uzm5Op7f3556uXj4uPi4OLm5ePh4d7c3uTreXt7e318ee7s7Ot3fICDg4F+fPLo4uTp7Xnw7+vm4+Li4+Pd2NfY2drZ2Nnc3+Hj5OXj4uHg4ebu8/Lv7fB8gIOEhIKAgIGEhIB45eDi5efn5eHf3d/h5enu83x+fnz19PT2fYGFhoP67ufl6e3u7/Hv6+bj397f3+Dh4OPo7PHzee7o5OLk63p/goODgoGAf3x6hHlpenp5eXp7fH5/fn58fH1/gYKDg4F+fPLp5ubo6u/w7+7t6+nk4eDg4eXq7Hd47+vq7O13ent8fX18e3t7enp5enx9fn58enh1c+fn6Hd7fYCCgoB9eOvq6uvr6efm5OPj5Obqdnh6eXd2hHQs6Ojn5OPm6Ont7Onp6unq6+zu8PL08vDt5t/Z19nc4XJzc3R1dXV2dXV2dnaFdYB2dunm5ebreHx/gH99e3t5duXe29vd3+Hl6OnqdXXn4dzY1dTV193i5Obl3tLEuba9ydLY3OHj4t3a297g4OHk5ufq7O7w9Hx/gYGBgH59e/Dr6vB8f4B/fXp6fH6AgIGChYWDgX546HR4enh14NbU33Z7f4B+enh26OTi5eno44Db2N7m6efk39ra3d7h5ebi2NLX5XyBe+jc293c2Nvg5Obp6uzs5+Lf4eTp8PPx7N/Z2t3j5eXl4NfKxs/i8Pj4+Hz17ejt9n1+fn179PHu7O3u8PR7ennweHh5enl3dnXm5OHd297g3tnV1dbZ3uHj6Hl8eezk29XV4nyDgXp04mHj5uXj4eDh4+Tm5+fg2NPa6n2CgX58fX178ezo5uPi5erxe319fHnr4tnV1NPRzs3O0NXa3+Lk5OTl5ufn6uzo5OLg4OHi5efl4NvV0tPT0c7JxcXJ0Nfc3uDi5Obp7Hd4hHkKeHbr6urp7O54eYR6Dnnx8fDw8fHw7+3p6ejnCszP0WprbG1tbW6FbRVubm5tbWzW1dXVampq1NTT1NTV1taF1QTU0tFoh2cHaGhnaGlpaYRqDmlpaGhoaWprbGxramjNhcwGy8rLzM7PhdAl0dHT1NTV1dTT09LR0tLT1NXWa2xsa2ppaNDPzcvIyMrN0NPV2IRuLm9v3t/g3tza2dnYbGxtbWtpaWpqac/OaGttbWpnycXFxsnMz8/NyMK/wsnS19qEboDb29xvcG9ubm9ua2jKx8XFys/W3d3Z1tXVa2vT0M3Jw8DEyc3MyMXGycvMy8fFx8zT2nByb9jS0dLQzMvLZmdnaWttb3Fyc3NwbGlnZWVmZmVjxcdma25v3NjV19jY2NbW2d5xc3R0dnVzcXBxcnRzc3N1d3Zx29LNx7+/yW10dBNw2tbU0tDMx8fKy8rLzMnFxcjNhGmAampozczLymZpbXBxb25u2dLMztPY3NzY0czKy87PysTCwsPCv7y6u77Dx8vOzcvJxcTFycvJxsXJaW5xc3Nxb21tbmxnYby8wsjLzcvGwr67vL3Aw8hnaWpp09TV2G5wc3Ry3NLLyczQ0tTW1dLPzs7Q0tLQzcnJzM3P0mnRzsxLzM/Wb3N2eHd2dHJwbmxqamppamppaWpqa2tramloZ2hqbG1ub25ta9HKyMjIys7Q0NDPzMrGwsHBw8nQ0mpr0s3MzMxnaGprbGxsh2tSbG1ubmxpZmRhwsPHZ2xvcXNxbmtmx8bHyMjHxsbExMbKztJqbG1tbGtqaWpq09XV09PW19ja2dfW1tXU1NTV1dbX1tTRzMfDwcLDxmRlZWdoaIRpgGppaWdmZGNiYWC9u7zByGhtcXJzcXFxcGzQysjJysvLzMzLy2VlxsC6tLCvsrnDzdXb3dnPwbaxs7i9vsHGycnFwsG/u7q9xMnN0NLU1tpwc3V2dnRycG7Vz87Wb3J0c3Fvb29xcnFxcnRzcW5qZcJiZWZmZMC3tsBlam5vbGpogGfLxsPFxsXBube+xcfGxsXFyMrJyszNy8TBx9Jwc23PxsbHxcDCxsnLzdDS087Jx8jJy8/Py8a7uLvByMrJyMK2qKawwtHa29ls1c7K0dxxcW5pZMfJycvMzc7RaWlnyWNiY2VmZWRjwb+/vb2/v768u77AwcTGx8xqbGrOx7+6Z7jDanBuZ2G6uby9vsLIztLS0c7Mxr+7wtFwdHNwbm1sas/JxcPBwsbM02xtbWxqz8fAurm5uLa1tbe7v8PGx8jHyMnKysvMyMTCwMDAwcTGxMG9ubi6vLu5tLGxtLvCx8nKysnJycuEZh5nZmVkyMfIx8nMZmdnZmZkYsTCwcPExsjJyMjIycqDfpJ/hH6Df5B+oH+jfod/jH6Gf4l+in+CfoZ/kX6Ef4N+iX+NfoJ/l36Df4h+lX+CfoR/i36Tf4d+hH+Sfod/hH6If6p+jX+QfoR/hH6Ff5l+AX+GfqV/lX6Cf4V+l3+Dfol/jn6Kf59+lH+Ffop/i36Cf6l+iX+EfpN/AX6Ff4R+iH+bfoN/qn4Bf4V+hX+IfgR/f39+iH+RfoN/hn6Ff5J+iH+JfoV/uH6If4Z+h3+NfgICBAABg4SEJ4OCg4SFhISDgoGBgoKCgYGAgP/9/P39/oGCg4OCgf76+Pj3+/+AgYSCJ4GA//7+////gP///v7/gIGChIODg4KCg4SEhIaHh4iJiYiHhoSCgYSABv/9/fz6+YT6V/v6+fj4+fn5+vv7/YCCgoGA/vz7+vn39vX29/b18u/r6ejo6u/3/oKDhISDgfv08fP6gYSGh4iIh4WDgfzz7ezx+4SJjpGQjImFg4D7+fr8/Pv5+Pn8/4WBgID+/fz9/v/8+PTy8vP19vqAgYD8+fj5+PHp5eXp7/X6/f369/Xz8e/q5+bn6eno6Ojp6efr8PX4+/z+gICCg4OCgoOFg4L/+vv/gYODgPz4+PyAgoKDg4WIiYmHhIODhIWFgff0/ISFhID57ubk6PD2+Pf18/T5/f379vT09/v+Of+BhIiJiISB/vz9/fr39vXx6+ru94GJjYuJiIaD/PHq6Ozw8fP3+vz+gYOEhoWEg4OCgP/+/fz9/oT9Kvz48+3q7vqEi5KWlZGLh4OBgIGCg4KBgYGA///+/fz69vHt7e7x9vn7/4SBLYD++/n6+vj39/f5+/v7+vf18/Lw7u7u7evp5uTl5uXj4N3b29zh5enu9v+ChISFEoSEhYWGhoWDgYD+/Pr49vb3+IT6C/j3+Pn5+/39///9hvwR+/r59/b19PPy8fDv7+/u7vGE9R/29PL09/f4+vr49fT08vX6/Pz9/v7/gICBgYGA////hIA6/4CBgoSGiIqMjYyKh4SA/v+AgoSEg4OBgP7+//78+/n29fb29/j5+/+Bg4WFhISFhoaGhYKBgoKDhoSJgIaDgf/6+PqAhIiMj4+PjoyJhoSCgYGDhoiJh4WEg4H+/YCDhYWDgPv18fL2/ICBgoH/+/j29fT09/v//vr2+P6EiIiHhYSDgoOEhomLiYiHh4WDgP37/oGBgPv5/ICBgP76+v2BhIaGhIH48fH28/Dw8vj9goWD/fb18u3o4NzfgOv2gISEgPv39vr+gYH//Pn17ePg5e/4/4SLkI2D8Obj4+Ti4+br7+/r6e/09/qAhIL56d7b4+zz+YCEhIOA/YGCgYD+gYOFhIKA+O3o5ujr8Pr++/j8gID27efs+YSHhoOA/fz/gYOHi46Oi4eFhYWDgPnz7urq7vT39/X09fX0VfmBhoqLioiHhIKCgoGA//v07uro6vD19vPx7/H09vb08+/q5ePk6Onp6Ofo7vb8/fry6ePh5Ofp6+vp5ubn5uLe3Nzf4+bo6/D4/4OGiIqLioqHg4GEgC3//fr08PDx8fL08vHx8fL3/YGCgoH//4CBgoSEg4ODgoGBgICAgYKCg4OCgoIKfH18e3t6eXp6e4R6LHl6e3t7enp5efDt7Ozr7Hh5enp5ee/r6urq7fF5eXp6eXl4eO/v7/Dx8nnyhPEFeXp7fHyFe4B8e3x9fXx9fn59fXx6enl4d3d37evr6efm5+jo5+fn5uXm5+fn6Onp63d5eXd26ujn5uXl5OTk5ePh3tvY1tbW193l7Hl6enp4duTc19ngdHh7fX5/fnx5d+ng29rf53h9gIKBfnt5d3bp5+jp6Obj4ePo7Xh6e3t7evPx8PHz84Dx7uvq6+zt7/J8fXz28/P08+vi29rd5Ovx9fTx7ern5eLe29rb3d7e3dzc2tjc4ufs8PP1e3t8fHx6eXl6eXfr5+nueXt8ee7r7PB7fX5/gIGDg4KAfXx8fX1+eenm7Hx8e3fp39jW2N/k5ubj4ePo7fDw7e3t7/T2+H1/goKAfSZ67uvt7uzp6enl4N/i6HmAg4KAgH987+fi4ePl5eXn6ersd3l7fYR8Znt69fX08vPz8fDx8vHv6ubi5O58goeKioaCfnx7e31/gH99e3p58O7t7Ozq6OPh4uXp7e/w8nl5eXp58O7t7e7t6+vs7vDw8O/s6ujn5eXm6Ojn5eHg4N/e2tfU0dHS1tre4+v0fYiAIIGBgX99fHv08vHw7u7w8fHz9PTz8vLy8fHy8fHw7ezshess6unp6Ofn5uXl5OTj4+Li5ejp6erq6ejq7O3t7u3p5uTi3+Hl5+jq6+zteHiEeRrx8vN6enp58nl5eXp7fH1/gIB+e3h15+h0doR4FHd26urq6efn5ePj5OXm5+nr73l7hnyAfX17enl6e3x/goKCgX16eOrk4uNzd3p9gIGCgoF/fXx6eXl6fH5+fHp5eHbo53V4e3x7ee/o4+Hj6HZ3eHfr5uLh4OHi5evw8O3q6/J9gIGAf359fH19foGDgoKBgH98evPy9Ht6d+nn6HZ2duvp6e15e319e3jn4ODk4d7f4+uA8Xx/fvbw8O3n4dnT09vidXd4dejk4uXpdnfs6efk3NPQ09vj6Xh+goB44dnZ29zY19nc3+Hf3+Tp6et4e3vw5NzZ4Ofp6nd5enl37Hh7enr3fYCAf3x35dvW1Nba4ejq5eLldHLb08/U4Xd6enh26unseHp8gISEgn58e3x7eOsW5+Le3eDk5+Tg3Nzd3eF0eHp7enl4doR1ZnTn497Z1tXY3+Tm5OHg4eTm5uXk4dzY1tfa2trY1tbb4ebo5uDZ1dTX2tze3tza29zb2NTR0dTY293g5evxe31+f39+fXt4d3d3eHnw7+3p5ubn5+jo5uPj4uPm63d5eXfr6nZ3eYd8hnuGfIVrA2xsbYRugm2FbC5ra2pp0M3MzMzOaWxtbm9u29jX1tbX2m1tbWxramlp0dHR0tPTatPT0tLSaWprhmwCbW6EbSVsa2tqaWhnZmZlZWZnaNHR09LQz87OzczMzMvKysvLy8zMzM1ohGqA1NPS0M/NzMvMzc3LyMXBv769vsPK0WtsbW1ta9POy8vQa21ub29vbmxratLOzM7U3XN3eXp5dnNxcG7Z19fY1tPQzczNz2hoZ2dmZ8/T19ve3tzY1dTV1tjZ3HBxcN3Z2NjX1NLPz8/Q0tPU09HQz87OzcnHxcXHyMjIycvIxMWAyczR1dfZbW5vcG5raGdnZWPDwMHGZmlsbNfW1tpvcXJzdHZ4eXl3dHBwcHFwbM7N1XFzc2/XzcbDxcnKyMXExcrS2NrZ1dLQ0NPW121vcXFua2fJyMvMy8nJycbDwsXLam9zcnFxcW/Y0s/Q1NfX19jZ2dltbm5tbGpqamlo0dFJz87P0NDQ0dLS0MzIxcbObHJ3enp3c3BtbGxub29ua2loZsrJyMjIx8XBvry7u7y9vsBhYWFiYsXGx8rNzs7P0dPU1dTU0tDOzITLFsrIxsTExsnJyMXCwL/Aw8bJztXccHKFcyZyc3NzcnFvbWzU0M7My8rLzc3Oz8/Pzs7NzMzLysnIxcTDw8LCw4nCQsHBwsLDxMXIzM3Oz8/Ozc/R0dLT09HPzs3LzM7Pzs7Nzc5oaWpqa2rU1dVqampp02pqa2xsbW5vcG9ta2hkxsViY4ZlG8rLzMzLysjFxMTExcjLztJqbG1tbGxsbW1tbIVqgGttbWxraWdmyMXFyGdrb3J1dnV0cm5raWdmZmdqbW5tbGtpZ8rKZmlsbWtp0M3LztLWa2ppaMzIxcLAv7/BxsvNzMzO0mxubm1raWhnaGtwd3t9fHt6eXh26+jmcW1oxsDBYWJiwsHCxmZoamlnZMK+v8TCv8DCxcZmamvRysfBgLiyrKqstr9kZ2dkxcTHz9ZucOLh393Xz8nIz9TXbXFzcmvKxMPDxMC+v8DAvru6wcfLz2tubNDCuba9w8TEYmRlZmjSbG9vb+Fyc3JuaWO+t7S0t7q/yM7Oy8xmZMG5s7bBZmloZ2XHxshmaGptcG9tamhpamlnysfEwsPGy8/Pes7OzszJyWZoamppZ2dmZmdoaGjOycK7trS2u8DBwb++wcTHyMjHxMC7ubq8vr6+vb7Dyc3Ny8W/u7u+wcPFxcPAwMC/vLq4ubu+wMHDxcrPaWttbm5ubWpnZWNjY2LDwsG+vsHExsjKycfHxsbJzGdnZmXHxmRlZmhphWgGZ2dnaGlqhGuCapd/hn6Gf4d+iH+GfgF/hX6df5Z+hX+WfoZ/hX6Kf4Z+in+LfoZ/j36Df6h+i3+EfoR/hH6Rf4N+hH+Xfod/jX6If4x+in+RfpN/kH6Ff6p+kH/IfoZ/g36EfwF+jn+Cfoh/kH6Yf4R+mH+CfoZ/hn6Ef49+lH8Mfn5+f39/fn5+f39/hH6Gf4p+g3+LfoR/hX6Cf4t+hX+RfoN/iH6FfwF+hH8BfoZ/jH6Cf4V+hX+Dfo1/j36Nf71+jn+RfoR/gn6WfwICBACAgoGA//37+vv9/oCAgP/9/f+BgYKDg4GAgP/9/f+AgYKDg4OCgf/9/Pr5+vr7/f79+/n39vb3+Pn7/Pv6+PXy8PHy9Pb4+fj4+fr7+/z8+/r5+vz/gIKEhYaHh4aEgoH++fb08vL09fb3+fz/gYKDhIWEgoD//fz7+/yAgoWIiYsjjI6PkI+NioeD/fbx7u3t6+fj39vd5O31/YKFh4iHhYOBgYGFggGBhIAGgYKBgP//hICA//37+vz+///79e/s7e/v6+fl5efr8PT3+Pv9/fv28u7v9oGGh4WCgPv28u3s7vL19PP09/z///+BgoKCgYCBg4WFhIOEg4KAgYOFh4mIhYKAgIKDgf307+30/oOFh4SBgYCBhIaGg/35+PuAgoKBgYKDhIWFg4GBgoSEgvv18e4L8fX4+fj39PL1/IGEhX+GhoSA/oCA/vbu5uDe4u37hYyPjYmEgoGA+/X0+Pz9/f3+gIKEhoeHhYOChIaHhYH89e/t7Oro6Ort7/Dw7ejo7/mBhYeHhID69fDr6ers7/T4+/v59vX19PLv7/T8gYOEg4KBgP+AgP//gP/89u/s6+rr7u/v8PHy9PX2+Pv+hIAM/4D/+vTx7u3u8PP1hPcN9vb39vXz8e7u8fb6/4SAGvv28evj3dzb2Nje4+Xp7/L2+fv/gYGAgP/9hPyA+/r5+v+Cg4eKi4mHhoSCgYGBgoSIi46QkZCPjIiEgPbt6enr7vL3+v3+/f3+/f3+/fv49vLw8fDv7u3s6+rq6uno6Ons8Pf/hIiLjIyMioeEgf/7+PTz9ff4+Pbz8O/w8/b9gYOEhIKA/Pz9/Pr38/L19vX08vDx9v2ChIWFhIINgP39/f7+/v//gICAgYSCgIOEhIOCgoKEhYeHh4WD/vbu6efp7O/w7+3q6+/0+fv7/4SIi4yMioiIh4aEgoGCg4OA/fr49fPx8fT5/oKCgPv6/YCDhYeJio2PjYf+9vX4/oOHiIiJiomE/vHi2Nfg7/mBg4KA/v37/YGCgoGAgICChIWDgYGDhoWD/vv6/v78Tvbx7ujf1NDR3er2g4eJiIaEgffr4N7i6fH4+vn49/f5/4KCgoODhYeHhoWFhIH++v2BgoKCgf36+vyAgYGCgYGA/fj19vn8/4GCg4OCgYSAG4GBgID+/oGDhYSBgP78+PLt7vX+goOCgYCAgYaCOoOFh4iHhYKA+vb09fb19vf39/j6/Pv5+Pr+//769fDt6eXh3t/i5+/5gIKFhoaGg//26+Ha1dDNzMyEywPO0NOE1SDW2Nzd3eDk6vL6goWHiouJiIaC/vj09Pf8gISHiYmKiYWICoqLjIyMi4qJh4QTenl47uzq6err7HZ3d+zr6+13eIR5MHh48e/w8Xl5ent7enl48O7t7Ozs7e7w8PDu7ezs7O7v8PHx8fDu7Orp6uvt7/Dx8YTwVe/u7uzq6ers73h6fHx9fX18eXd26eXi4eDg4eLj4+Xn6nZ4eXp7enl47u3s6+vsd3l7fH1+gIGCg4KAfXp25N3Z2NfW1dLNycbI0Nri6Xh7fX59e3qEeYB6enp5eXh4d3d4eHd05OBubWxs2drb3N/k5+rp5+bo7O/t6ePg3+Dk6e3w8vT39/Xw7Orr8X6CgoB+fffz7unn6ezu7Orq7fHz8/N6enp5eHh5e35/fn5+fXp5eXp8foB/fXp5eXp7ee3l4eDm7nx9fnx6eXl6e3x8euzo5+p3eYB5d3Z2d3h5e3l4d3h5eHXh3NjV2uDl6enq6enr8Ht+fn18fHt4dOVzdObh3NXR0NTe63yBg4F+e3l5eOzn5+ru8O/t7Hd4eXl6enl4eHp8fn168ezn5uXj4eDg4uLj4uDd3ePse3+BgH167unm4+Lj5OXn6ern5OPi4+Lh3dzg6DJ3eXp6eXd27HV16el06OTe19TU1Nfc3uHk5ujq7O7w9Pd9fn5+/H358uvm4+Hj5+rt8IfygPHv7Onp7PD1+H19fHvy6+bh2dPT0tDS2d3g4+fp7e/w83p6ennx7+7t7ezr6efn6nZ3eXt7eXh3dnV0dXV2eHx+gYOFhYOBfnl249rW1tfa3eHl5+jo6evt7vDx8O/t6ujo5+Xk4+Lh4N/d29nX19nb4OV2eXx9fn18e3h26unnOOXl6e3v7+7s6urt7/L4f4GBgX989fPy8O3o4+Hi4uDe3dzd4+t5fH19fHp47ezs7O3t7vB4eXl6h3uAeXh4eXp7fH18e3jr49zX1NXY2trZ2NbZ3+Xq7O3xfYCDhYSCgH9+fXp4dnZ2dXTn5+fm4+Lh5Onwe3x68O/weXt9f3+Bg4OCfOrk5ejufICDhIWFg3/z6NzV197o73l6eXbq6OTldHZ3dnZ2d3h5eXh2dnh7e3nt6uns6+jj39sY1s7CwcXU4ex7fn59e3h14tfPztPa4efohOcw6e97fH6AgoWHh4WDgn977+nrdnd3dnXl4+Tmdnl6fHx8eu7p5ufq7vB6e3t7enl4hXcZdnfu7nl7fHt3debh3dfU1t3oeHp6eXh4eYR6Z3l5ent8fHt5d3Xl4eDh4eDh4uHh4+bo6Obl5unq6OTh3tvZ1tPR0tTZ4Oh3enx+fn588eng19HNycfGx8bGx8fIysvMzMvMzdDT1NTW2d/n7nt+gIGDgYB/e/Ht6ens8Xp9gIGCgoGFgIKBhIIEgH9+e4NphNIZ09XXbGxs19XV12xtbW5ubWxr1tPT1GprbIRtBWzX1tXUhNJ109PS0M7Nzc7Q0dPW19fW1dTS0tPV19rb3Nzb29va2djX1dLQ0NLUa21wcXN0dXRycG3Vz8rHxMPExMTFxsfKZmhpamtsa2rV1dbX2dtvcHFxcXBvb29wcG9ta2jLxcLAwcPExMK/u7vAxsvQa21vcHBwb29vhHADb29uhG0hbm5ta9PPZWNiYcTFxsnN0NPV1NHNzdDU1dTRz8/Q09fZhNpy2dfTz8zO1W9zc3Fua9PQzs3N0NTY2djY2tzd3t9xc3NycW9ub29ubGxtbWtqamtsbnBvbWtpaWlqZ8rEwsPL1W9xcnBubWtrbnBwbtPOy85oamppaWpsbW1ubGtrbG5ubNHMyMbL0NPV1NTRzszMZ2lphGh1ZmPHZWjS09TSzcjGytFtcnR0cnBvcHDc1tTW2drb295xc3V2dnRxbm1vcXJxb9vX1NPT0c/Oz9HS0tDNycjL0Wtub25raMzIw7+9vb/BxcrNz87Nzs3LycbFyM1pamtqaGZlymVly8tmy8nEwL/AwcTJzM3Ohc+A0NLUa2xsbNhs2tbRzcvKzNDU1dbW1NPR0M7MycXCwcLHzdTZbW5ubtjU0czFv769urq9wMHEx8nMzs/Ta2xsbNjY2NfW1NPPzMvLZWZoaWloZ2dmZWRlZWVnaWttbm9vbm1raGbIw8LDxcjM0NPW19fZ2tvb3d3c2tnX1tfX1tZW1dPS0c/MyMXCwcLDx81pa21ub25ubGpoz87Oz9HV2Nrb2tjW19re5ex5enl3dHDb1tLNycO9urq5t7a2uLzDymhqampoZmPBvr28u7y/xGRmZ2doZ2WEZBNjYmNlZ2lrbW1ta9HJwr25uLi6hLsrvcDEx8rLz2tucHFycXFyc3JycG5sa2lnzMzMycbCv7/CxGNkYsHBxWRnaYVqgGhkvbi7wchoa2tqamtratDMxcHDx83RamlnZMXDwMRlZ2hnZmZnaGpqaGZmaWtrac7NzdHS0c7LysbAuLSyuL/GZmhnZGFfX7u3tLa8w8vR0c/Nzc/S121sbGxtbm9ubGppaGbJx8pnaGlqatHPz9Nrbm9vbm1r0MnGxsjLzmhqCGppaWhoaWlqhGwW19dtbm9tamjMyMO+vL/Gz2pramloaIZpLGhpa2xtbGppaMzIx8bGxMTFxcXExMLAvbu9wMPDwsG/vr28urm5vMDGzGhqhGsKas/Jw724tbKwr4SuKq2vsLGysbCwr7G0tba4u8HHzWpsbW9wb25ta9PPzc3Q1GtucHFxcXBvboRvhHAFb25tbGqDf4d+g3+Efoh/hH6If69+i3+Nfoh/hn6Pf5B+mH+CfoR/on6Gf5B+nX+Gfox/hH6Rf45+iX8Dfn9/iX6Jf4l+jn+SfoZ/ln6HfwZ+f39+fn+UfoR/An5/m36Ef5R+hH+Lfpp/qX6Kf5F+hn+Rfod/iH6Wf5N+kX+KfgZ/f39+fn6Kf4V+iH+IfoR/hH6Rf5F+h3+Pfo1/g36Ff4R+h3+Hfo5/gn6Gf4h+lX+hfod/n36Jf4Z+ln8CAgQADY6Pj4+Ojo2Kh4aEg4KEg4iCWYGA//79+/r49vTy7+zu8fX3+v39/fv5+Pf3+f2AgoKBgPvz6ubk4eDk6/P4+/6AgP/8+fj4+Pf4+v2AgoWHiYiIh4aEg4KBgYKDhYWGhoaFhIKA/fv8/oGDhoYUh4mLjIyMioeFg4GAgIGCg4WGhYSFgwWCgYCAgYSChYEzgP/9/P2AgoSGiImJiYeFg4H9+PTw7+/w8/f5+/3/gIGCgoD89/Ht6ebk5Ojv+IGEg4H9hPsH/X/++vXv7IbqgOzs6+no5+Xl5uvy+f6AgP78/YGEiIqKh4eGhYSFhYSCgP+AgoH//Pn4+fr8/PyAgYD99/X0+YCEh4WC/Pr8/oD//f3/goeLjIyHgf6AhIaGgvv4+/+CgYCBgoOEhYWEhYaKjY6LiYeIiYmIh4aFhIKA/f2AgYGA/v3/gYKB/fbvI+vt9Pn8/f7+/f7///79/4GCg4ODhISC//fx7/L2/YKFhoaGhIU8hIOCgP78+/v8/f379e3o6O71+/+A//359fLw7u3t7Ozs7e7t7Orp6+7y9vj49/b08fHz9fj8/f7+/fz7hPyA/f37+vf08evl4uPl5ujs8PP19vf39fLw8fL09vn6+/z8/Pn49/j6/YGDg4KBgP7+gIKGiYuLi4qHg4GA/v3+gICBgoSEhIWGhYSEg4OCgoODhISFg4D8+PLq6Onp6ert7Ovs7/D0+vz8+/v7+PX08u7q6evu8PP2+fv7/Pv6+PaF9Aj1+Pv8+vj4+YT4gPTw7Obh3+Ln6ezy+f6BgYGAgID+///9/Pv59vTz8fP2+v6Bg4SGhoaFhYaHioyNjo6OjYuHhIOCgYD+/Pz8+vr6+/6Bg4WGhoSB+/Xx7u7v8fHw7evs8Pb8gYKBgYKEhYeIiouLjIyLiYeFg4GAgYKCgPnw6OPi5ufp6+zt7u/ygPmAg4P/+PX1+PyCh4qLiomHhYSDhIT87ufn7PP7gIGA+PLs7PL4/ICBgoKA/f2AgYKDhYaC+u3q6fD1+fv8+ff2/ICCgoOEhIOA+/b29/f4+f389vDt6/D2+/z49fj8gID7+v6AgoODgP6AgoODgoGBg4aGhP/7+fj29/j8gIGARP79/v///v79/oCDh4uNi4iEgf77/ICChIaGh4eHiImKiomHhIH9+fXy7uvr7/f/g4SDgPn18/T2+Pr8/v+AgoSHioqJhYdXhoWFhIOB//39/Pr59vLs5+Pj5uzy+Pv69/Py8fDw7uzq6u3v7+7s6OXk4+Pj4eHk5+rw9fb3+fr6/Pv38+/s6ujk4eDg4eTq8Pb9gYSFhoiKjIyNjo6OhI8BjhaCg4SDg4OCgH59fXx8fH19fXx7e3p6hHlDeO/v7u3s6ebl4t/c3uHj5uns7e3t7Ozr7O7xenp6eHbm3dXQzszLz9fg5+vveXnx7uvq6ejn5+jrd3h7fn9/f35+fYR7E3x+gIGBgH9+fHp47Ono6HV2d3iEdxN4enx9fn59e3p4dnV1dnd5ent6hXkDeHd3hHYBd4Z2gHV16Obm53V3eXt9f39/fnx5d+nk39za2tzg4+bp7O95ent7ee3m4NvX09HR1d3neX1+ffj4+vz9/4D/+/Xv6+jl4+Hf3t7c2tjX2Nja3eTs8/Z8e/Ty83t+gIKBgH59fHt7fHt6efF4ennw7erp6uzt7u14eXju6efn7Hl9f317VvDu8fN68u/v73l9gIGBfnnweXx+fnrs6evveXl3eHl6e3p6eXl6fYCAfnt6ent8e3t6enl4duzueXt8fPXz8nl6eOrh2NTV2+Lm6evs7O3v7+3t73l6hXsKeu/o4d/h5e16fYR+hH0efHp57u3t7e7v7+3n39nZ3ePq73nz8/Lw7evo5uTjhOJp4d/c2trc3uDh4eDe3Nzd4eXp7fDx8vHv7u7v7+/u7+7s6+rn4t7c3eDi5Ofq7O3u7u3q5uTj5efp6+zt7e3s6ujn5+jqd3l5eHh37e13eXt+gIB/fn17ennw7/B5enp6e3t7fHt7enl5hHdPeHh5eXh26OXf2dja29vc397d3uDh5Ons6+vs7ezq6ujl4eDh4uTm6Ovs7ezs6+nm5OPi4eDh5Obm5eTl5ubm5+bj3tnTzcrO09bZ4OfteIV5I/Ly8vDv7ern5eLg3+Lk53V2d3h4eHd3eHp8fX9/gIB/fXt6hHlY8fDv7+3s7O3wenx+f359eu7o4+Dg4OHg3tvZ2d3i6Hd4d3d4e3x8fX59fXx8e3l4d3Z1dXZ4eHfn3tXQztHS1NbZ293f4ul4e3z28O7t7/N9goWGhIOCgIR9a/Ln4eLm7fJ7e3rr5d7d4+rvent7enfr6nZ2dnd6enbi1tLQ19zh5ujm5OTqd3h5eXp6eHTf2tja3N7g5OTf2dbU2eDn6uno6u14eOvr7nl8fX179Ht9fn18e3t9f3577+rn5OHg4eV1dnbrhemA6uvseHt+goOBfXp36ufndXh6fH1+fn+AgYKCgX98ee7r5+Th397h5+56e3p36eTi4eLk5ujq63d4en2AgH9+fX5+fn18fHt5eO3s6+vq6Obi3NfT0tTZ3uPm5eLg4OHj5OTj4uPm6Ojo5+Ti4eHh4N/g4uTn6+7u7e7u7u/v6+cT4+De29jU09TU19zi6O15e3x9foZ/BoCAgYKCggpzcnJxcG9ubGpqhGmEaoRpdGhoaGdnzc3My8vKycnIxsbIy87R09TV1NLRz87OztBoaWhmZMO6s6+trK20vcfN0tVrbNfT0dDPzs3Nz9Fqa25wcXFwcG9ubGtqamtrbGxsa2tqaWdlycjJy2dpa2tramlpaWpsbW1ubWxramloZ2doaWtshmsZamlnZmVlZWRlZWVmZmdnZ8zKystnaGlrbIRugG1sa9TRzszMztHV2t7h4uRzdHV1c+Ha1M/MycbExcrQamtqZ8rJysrMz2nU1NPR0M/Q0NDPz9DPzs3Oz87P0tfe5Od0dOTh4XF0d3l4dnV0c3Jyc3JyceNyc3Lg3tzb2tvc3d5wcXDe2tjY3HBydHFu08/P0WrT09PUbG9xcnFuDWrRaWttbGjLy83Sa2uFaQ5qaWlpam1vcG9tbW1ubYZsIWvU1mxub2/d3N1wcG/Z0snExMrP09fa3Nze4OHg399wcYVyCnHd19LR09bbcHKEc4VyPXFw3NnX1tbV09DJwb2+wcbM0GnU1NPQzszKycjHx8fJysrJyMbFxcbFxMLBwMDAw8fL0NXY2dnX1dLQzs2FzBvLysnGw8LDxsfJyszOzs3My8jEwsLDxcfJysuEzCDLzM7Q02xubm1sbNXUamtucHFycnFwb25u29vcb29wcIVxBXBvbm1sh2pMaGbIxcC7ubu8vL7AwcHEx8rO1NfX1tbV0s/MycbCwcLExsjLzc7Pz87Ny8jHx8bGxcbJy8vJx8fIxsXEw7+8uLWysra8vsDEyc1naIRnFM7Q0dDPzszIxsPCwsTHymdpa2xthGwCbW6Fby1ubWxramppaM3KycnIyMrMz2lsbm9vbmzTz8vIyMfGxcG+vL3BxcpoamprbG6Eb4BubWxramhnZmVlZWdpaWfHvrWura+ytbi7vr/Bw8hmaWnOy8rKzdJtcnV1c3FtamdmaGrQysjKztLUampoy8bAv8PIzGhqamlnycdlZmhqbm9szcC9vcTJ0NTX1tXV2W5ubWxraWdjv7u8v8DDxsrJw725trrAx8vLzNDTa2rPzlPRamxtbWvUamtramhmZmdpamnOzc/R0dDPz2dmZcjJy87NzMrIx2RmaWxubWtoZ8rIyWZnaWpqa2trbG1ub29tbGnRz8/PzczMztLXbGxqZ8jDwIS/CcDAwWFiZGdpaoRphmokaWfLycnHxsTCwL26t7a4vMHGycnHxcbHycvNzs7Oz87NzMrIhMcwxsTDxMXHycrJx8bHx8jHxcTDwsPDwsDBwsLFys/T121vb29wcXFxcnJzcnN0dHRzm3+afoV/jX6Cf4p+mX+EfrB/hH6Mf41+hX+LfoR/hn4Bf5h+BX9/fn5+j38Efn9/f4l+g3+FfoV/hH4Bf4R+h38BfoV/hH6cf4J+hH8Gfn5+f39/kn6If4d+jX+QfgF/036Gf4J+jH+Dfpd/yH6Gf49+mH+Jfod/j36Zf49+g3+Gfox/h36Df4d+hX+Cfod/jX6If5V+BX9/fn5+hX8Bfot/iH6Df4l+iX+DfpB/in6Ef4p+kn/FfpF/AgIEAEOOjo2LiYiGhIGB//z6+/v8+/r5+Pf39/b18/Lz9PX3+Pr7/f7+/v+AgP/7+Pf19PP1+PyAgYKDhISEg4KBgP/+/4GChYQeg4KCgYD++vj49vX19vf29fT08/Lx8PDx8/f7/f+AioGEggSBgP//hYAo/v7/gICAgYD+/Pr6+fj6/oGCg4WGh4eGhYWFhoeHiIiJiYmIiIiHhoSFhIaAhYWGh4iIhoSCgYKCgoGBgoSHiouKh4WDgf/+gIOFh4eGhIL/+fTx7u3s7e7w8fP19/f29ff3+fv8/Pz7+/r7/oCBgIGBgoSEgoGBgP38/4CBgYD++fX2+YCGh4SA/Pf4+fj29PPx8PHy9Pb8gYODgf769/Tz9PT4/YOIi4qJhoMOgPr3+oCGi4yLh4OA/v6EgIWBC4SIioqIiImLjIyLhIkeiIiIiouKh4SB//7/gP78/P3+/f78+fXy8PDz+P2BhIIug4WHiImJiYqKiYeGhIOEhYeHh4aEhISFhYSCgPz17ebg29nb4uz09/j6/f+AgYSCB4GAgP79/v+EgFH//v37+PXy8e/u7e3s7Ozu8PP19ff4+fn7/f7///78+ff29PTz9fb29fX29fT19fb4+Pn7/f39/Pz8/f7/gYKDg4OEg4L//Pr38ezt8PLz9vuFgBmBg4SFhoiJiYiHh4SB/fnz7+3u8fX5/YCBhYIagYD+/Pv7/f+AgYKDg4SFhYaGh4iHh4aFhIOGggiBgf/8+vr5+IX5hfoF+/z9/v6E/Q38/P39/Pz9/Pv6+fn4hPcQ9vf4+vz+gIGCg4SFhIOA/4T+LP///fj19ff4+v6BgoODg4L/+O/n4eDh5Orw9v2Bg4aJioqIhYKA/fv5+fj4hPcW9vTz9Pb7gICA//38+/n28/Hy9Pj+gYWCUIGA+/b09ff4+fr8/4GCgoGAgYKDhIODgoKB/fbz8/f6/P79+/r5+vv7+ff29/j5+PT08vP3+Pv9gYaHhIH7+fj5+vv9gIKFh4mIhoL9/v+BhYJRhISEhYWGhYOB/PLp6O76gYWJi4qJh4WEg4GCg4OB/fr4+fyAg4SEgoD9+PLr4t7h6vX9goaLjY6NjYyLiYT99vT2+4CCgoODgoGChIWGhoeGhIUrhISEg4SDhISEg4OCgYGA//+AgYKB/vn29PT19fXz7+rn5ebp7O7v8fX7gIaChINMhIWGhoSB+/Tx8vX5/YCAgYOFh4eGhYKA+/fz8e7r5uLf3t3f4uXn5+jn5uTj4+Xq8vmAgoSFhYSDgoH/+PLs5uPl7PP3/YGDhomMjYSPRoaGhoSDgYB+fHv19PPz9PT08/Hv7uzq6Obi4eHh4uPl6Orr7Ozt7nd47uvp6Ofm5efr73l6e3t8fHt5eHd26+rrdnh5enqEeyF6enrx7+7u7ezt7/Hx8PDv7+7t6+rq7O7x8vN6ent6enqIeQV4eHfu7oV3Xezr7HZ2dXV15+Ti4N7d3+JydHV2eHl5eHd3eHl6e3x9fX5+fn19fXx7e3t8fX1+fn9/gIKDgoF/fXx9fXx8fH1/goWGhYJ/fHnv73h7fX9/f3589vLv7u7t7O3t7oTtCezr6erq6+3t7oTtW/D1fX5+fX5+f399fHt68vDyenp5eO7q6OnseX5/fHjs6Ojo5+bl5eTk5ebn6e96e3t57uro5+jq6+70fYGCgn99enfp5ud1eXx8fHl3durqdXZ1dHV1dXR1eX2FgAWBgoF/foV9Dn6AgoOBf3359/d79PHvhPAR7+3p5uXl5+zxent7e3p7fX6EfzKAgH59fHt6ent8fX18enp6e3x8e3nv6uXg29fW2N7m7e/v7/HyeXp7e3t6enl47+7v74R4Cu/u7evp5+bl5OOE5ETl5+rt7u/w8fHy8/T29vb18u/s6unp6ers7Ovr7Ozr6uvr7O3u7/Dx8fDw8fLz9Ht8fHx7e3t57eno5N/a29zd3uHldIR1BnZ3eXl7fIR9D3x6d+rm4d3c3d/h5Od1dYV2EnV05+bm5+nsd3h5ent7fHx8fYR+A318e4d6Dnl57+3s6+rp6erq6enpheoE6+zs7ITqD+np6urq6+zt7e3u7u3t7oTvFvDy8/R6e3t8fH18e3nw7+/u7u/v7emF5j3odXZ2dnV05d/Y0czMztLY3uTqd3l7fX59fHl3dejn5ubm5+jp7O3t7Ozs7vJ7e3ry8O7t6ubj4N/h5Od0hHWAdnZ26efn6evu8PL0931/f359fn+AgYGAgIB/+fPw7/L19vj38/Hw8O/v6+fk4uPj5OHi4ePn6u7xe4CCgX3z7uvr6uvseHp8fX5+fHjp5+Z0dXR0c3N0dXZ3eHl5d3bn39jY3+h4e36Af358e3p5d3Z3d3bq6urr7nh6fHx6eOwb5+HYz8vO1uDnd3p+gIGAf39+fHjn4d/h53Z4hXkEenx9foR/AX6Efy9+fn59fHx7eXh3dnV05+h1dnd36uXi39/f3t7d29fV1dfb3+Hi5OfseHl5eHh3d4R4UHl6e3t6d+jj4OHj5+p2dnd4enx8fHt6eO7r6efm4t3Z1tPR0tTX2NnZ2djX1tbY3uXseXt9fn59fXx89O7p493b3uTq7vJ7fH6Bg4SFhoaGDnZ1dXNxb25samnQzs3OhNBWz87Ny8nIxcPCwsLDxMXGyMnKy8zOaGjQzszMzMvKy87QaWpqamtqaWhnZmXIxsZjZWZnaGlra2xsbG3Z19fY2dnb3Nzc29rZ19bT0c7Mzc7Pz89oaGiHaTBqamtramlp0dNqa2xtbtzb3G1tbGtpzcjFwr+9vr9gYWJiZGRlZWRlZmdnaGlpamuFbAVra2pqaoZrP2xtbm5ubWxtbnBwcXJ0d3t+gIB+fXt46eNvb29ubWtpZ8vIxcXFyMvQ1Njb3uDi4d/c29ra29vb2trZ2dvhcoVzGHR0cnJxcN3b221tbGrU0dHU129zdHFt1oTSA9HQ0ITPNdDS121ubmzV0tDP0NHT19xxdHV0cW5qZ8jFx2dsb3BvbGlnzc5nZ2dmZmZlZWVnamtramlqhGs6amtrbGxsbnFzdHR0c+Tj43Hg3tzb29rZ2NXQzMjHyc3RamxsbGtsbnBxcXFyc3NycXBubm5vcHFwb4VtFGxrac/MyMXCwMDCxszP0M/Pz9FphWoLaGdmy8rKy2ZnZ2eFzi/Nzc3Mzc3O0NHT19rd3+Hi4+Pj4uLh4N/c2dbS0M7My8vMy8rJysnJycrLzM3Oz4XRBtLT1NRqaoRrLWpp0M3Ny8bDw8TFxcjLZ2dnaGhoamtsbW9wcHBvb21qz8zHxMPDxsnMz2lqaoRrGWpp0tHR0tXYbW5vcHBxcXJycnNzc3JxcG+IbgZt2NbU1NSF0iDR0tPT1NXV1tfY19bV1dTT0tPU1NXX2NjZ2trZ2NjY14TWC9fYbG1ubm9vbm1shNYF1dbW1NCEzSjO0GlqamtratLNxr+6ubm8v8PGymdpa21vcG9tamjPzc3Oz9DS1NbXhNhW2dtubm3Z1tXU0c7Kx8XGx8hlZWZmZ2hoaNDOzc7Pz87P0NNsbm9vcHJ0dXV0cnBvbNLKx8bIy8zOzcvLycfFw7+7ubi4ubi2t7i7wcbL0WtvcW9t1dSE0xPUamtsbW1saWbIys1oaWhnZmZmhGUyZmdnZsvGwMDEzWptcXJxb21ramloaGlqatLRz83OaGlqaWdlxsG8t7Kxtb3FzGlscHOEdBZzcGvMxcLEyWZoaGloaGdoamttbm5vhW5ZbW1sbGpqamloZ2dnZmbMzWhpa2vU0c7LycfFw8LAvbu7vcHExcbHycxnaGloaGdnZ2ZmZWVmZ2dmZMTAvr/CxchlZWZnaWtra2ppaM3LycjIxsPBv76+v8GEwyPBwL68vL3CyM5pa21tbWxramnPy8bBvby+xczQ1W1vcXN1doR3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+fn49/f5+vz+gID//Pj18Orl4+Xo7e/x9fr+/v37+vn28e2F6gjr7e/x8/T09Yb2Dvf39/j4+fv9/4CAgYODhIQPhYSDgYGA//37+/r49fPyhPEN8vP2+Pn6+fj39fTy8ITuKe/v8PHy8/Tz8vDv7u3s6+rp6Obl5OPj4+Tm6u/1+v6AgoOFhoeIiYmJhYhMh4aGhYWEhISFh4iJiIiHhoWEg4KBgYCA//37+vj5+vz/gIKDhIaHh4iIh4SB/Pf08/P1+P2Ag4WHiImKi4qJhoOB//6AgYKCgoGA/oX9Ufv38+/s7PD2/YGDhIL/+ff4+fyAg4WFhYSDgoGA//39/P3///36+Pf3+vz7+vj18vHz9ff6/4GDhoaFhIL/+/f08e3u8fb7/Pv59/b19PT29oX3Nvj49/by7+3t7/Lz8e3o5eXq8fj+gYGA/PXu6ejv9/z/gYGB//ny7e3y9vuAg4WFhIKBgoODgoSADv/9+ff39/b18/Ly9fj7hP4Q/////v38+/r5+fv8/f+AgISBCoKCg4OEg4OBgP+E/iH9/Pv6+vz/gYKDhISEg4OCgoOEhoeJioqKiYmIiYmKioqEiRuIiIeGhIOBgP369/X09PT19/j7/f6AgIGCg4OEhCqFhoaGhYSDgYD9+/r49vX09PT19/n8gIKEhYeIiYmKi4uLiomIh4eGhYSDe4R8iH0Vfn5/gIKDhIOCgIB/fnx7enl4d3d3hXiEeQV6e3t8foZ/C359fHt6eXh3d3d2hnWEdAd1dnd4eXp6hXsVenl47+7t6+jn5ubn5+jp6+zueHh4hXkmenp7e3x9fn5/f4CAf39+fHt5d+zq6Ofn5uXk5ebo63d4eXp6e3yGfYV8HXt7enl4d3d2d3d4eHl5eHd2dOfl4uDg4OLk5+vuhHgz8O/s6OTi4N/c2tjY2t7j6O55e31+f39+fHnu6ebl5ujq7e/x8vPy8fLy8fHy8np7fH1+hH+Afn59fXvy7+zn5OPj5Obn6evs7Xd5ent8fHx7enl5eHh4d3d2dXV0dXZ2dXNy4uLj5OPj4d7c29vc3+Dh4eDe3NrX1NTV19nc3+Lj5HJyc3NzdHV3en1/gIKFh4eGg35559/Z19jc4ujteHh48O7r6eXi3drY2dvd3t7d3d3c29gN19jb4ed3e3x8e3nt6YTnJObm5ujpdXZ27Oro5ePi4eLj5ejq7O7veHd3d/B4eHh5ent8fYR+hH8qfn59fX5+fn+AgYB++PLs5+Pg3t3c3N3f4+bp6+3weXl6enl58vHw7+/vhe4q7/Dy83p58u7r6OPd19XW2dvd3+Ln6urq6ejo5eHd29ra29vc3d/h4eLihuMR5OTl5ufo6evt73h5eXt7fHyEfR98e3t69PPy8fDv7evq6urr6+zt8PLz9PPy8fDu7OrohOcl6Onq6+zs6+ro5+bl5OPj4uHg397e3d3d3uDk5+rsdnd4eXp6e4R8BHt7fHyGewR6e3x9hH4FfXx7enqFeWPy8vDv7e3t7/B5eXp7e3x8fH18enjr5uPg3+Di5XR2eHl6e3x8fHt5d3Xp6XV3eHh4d3bs6+vq6ejm497b2drf5u56fHx78Orn5+jreHt+f39+fXx6eO3p5uTj5OTi4N/f4eWE6CLm5eXm5+fp7Xd5e3x7enjs6OTh39zd4OPp6ujm4+He29rahdk32tvc3NrY1dPU1tjZ2NXS0NDU2d7icnJx39rV09Tb5OnueHl57+rj3dzg5el3enx9fHp5eXp6eYR3Du7q5uTi4eDf3t3e4eToh+sM6urp5+fn6Onr7e94hnmEehd5eHd16Obm5uXl5OLh4uTndXZ4eXp6eoR5EHp7fX5+f39+fX19fn5+fX2GfEZ7enl5eO7t7Ovq6unp6uvs7e53eHl6e3t7fHx8fX1+fn19fHt58fDu7evq6eno6ers7nh6e3x9fX5+f3+AgH9/fn19fHx7Cmpqa2trbGxtbW2EbBNrampqa2xtbW1sbGxramloaGdmiGWEZhNnZ2hpamtsbGxtbWxrampqaWlohGmCaIhpBGpqa2uGbBRramlp0dHRz87Nzc3Oz8/Q0dLTaYRqhmkTampqa2xtbW5ubm1ta2ppaM7My4TKDcvN0NPXbW5vcHFxcnKEcwtycnFxcHBvbm1sa4VqSGtsbGxramlny8jFw8LDxcjLz9Jqamtr1tTS0M3My8vKysnJy8/T2N1wcXJ0dXV0cnDc2NTT1NbX2dna2trY2NnZ2dra221uboVvH25vb3BvbtnX1dHOzMzLysrLztDQaWpqa2tqaWdlZGOIZAhlZmZmZWRiwYS/IMHBwL++vb6+vr29vb6/vry7u7y+wMPGycvNZ2doaGhnhWU5ZmhqbW5ubWpnx8K/v8HEyc3RaWlp0M7MysjFw8LDxcjKy8zMzM3MysfFw8THymdqa2tqaM3LysrLhcwTzWdnZ8zJxsPAvr28vcDDxsnLzIRmC81mZ2doaWprbW5vhnCHbxhwcHFwbtfQy8bCvry7u72/wcXJzM/R02qEawRq1dXUhNOE1CvW1tbXbGvV0c7KxL24tba5u72+wMLExMPCwcC+ure1tba3t7i5u72+vr+/hcARwcLDxsfJy87R1GtsbW9wcHCEcStwb29u3NrY19bU0tDPzs7Oz8/Q09XX2NjY19bW1NLQz83My8rJyMjIx8bFhcQdxcXGx8jIyMnJyMnKzM7R1NVra2xtbm9wcHFwcHCFcQtwcG9vbm1ubm9vb4RwAW+Fbg1t2dfU0tDPzs7PaGhohmmAaGZkw7+7uru9wMVlZ2lrbG1ub29ubGtp0dBoaWlqaWlo0NDR0dLT0c7Kx8TDxMjLZ2hpaMvIx8jKzWhqbGxsa2pqamnPzczKycjIxsTFx8vR1NTU0s3IxMHAv8DEZGZqa2pqaMvHwr+8ubq7v8PEwsC+vbu4uLm6u7y9vr7AwcEtwb+9vLy+wMC/u7azsbS3urxeXl23s7Cvs73J0dlwcnPn5N7Z2Nnb3W9wcHBuhGwma2pnZmVkxcG8ubi5u72+v8LFyc3Pz87My8rJx8bEw8PDxcfIy8yIZ4RoA2dmZIfHB8bGxsfJZWaEZwhmZmZnZ2lqbIVtCWxra2tsa2tqaoRpCWhnZmZlZcnIyIfHDsjIyWVlZmdnaGhoaWlphGolaWhmZMbFw8C+vLu6urq8vsBiZGZnaWprbGxtbm5ubW1tbGxratx/j36bf4x+pH+LfoR/kX6Jf5R+jn+Ofpp/n36Uf4l+g3+ZfoZ/i36Df49+hH8Bfpx/kn6Gf49+gn+0fo9/vX6nf4l+jH+Ifo1/gn6Hf49+hH+Gfop/mX6Hf61+g3+JfoN/iH6Pf6B+j3+MfqZ/jX6Tf41+lH8CAgQAJICBgoKDhIWGh4iJioqLjI6QkZKTlJWVlZOQjImIh4WEg4ODgoWDBoSEhIWFhYiEBIODhISFhQWGhoeHh4SGhocLiIiIh4aFhYWEhISEg4eCBoODg4SEhIeDhoSKhYKEhYMIhIWGhoaFhYWEhjKFhIOCgYGAgIGCgoSEhYWFhISDgoD9+vf18/Ly8/b4/P+BgoKDg4SGh4iIh4aFhIKA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bdjW09HPzs3OztDS1WtsbW1ub3BxhHIEcXBvboXcStva2NbT0M7Nzs/R0dNrbG5ubdbSzsvKyszP0tXXbW9xc3R1dnZ2dXRzcnJzdHV2d3Z0cnBubNjY19bT0dDQ0NHT1NbX1dTU1dVrhGxLa2tqaWdmZmdnaWprbGxsbWxramlnZWVkxsTBvbm2tri9Y2htcXR2d3d3dXRzcnFwb29ubm1ramhmY8LAwMHEyc7T2N7i4+Le2tbThNI20c/Ozc3P0WpsbW5vbm5t19TR0M7My8nJyMnKy8zNzc3LyMXCwcHCxMbHx8bFxMPCwsTHymZohGkXaGhnZsrJycpmaGprbG1tbGxramppaGeEZhFnZ2doZ2ZlZGPGxcXEw8TFxoTHhsYIx8nKzM7P0dKH0zXS0dDNy8nIx8bDwcDBwcHAwMHDxMXEwb27urm4trOxsbK0t7m6vL7CxMbHyMrLzM7Qamtra4VsDGvU0c/NysjFw8G/v4XAM8LDxMTFxcfIysvNzs7Pzs7NzMvKyMfGxcXFxMPCwsG/vbq3tbW2t7m6vcDEx8nKzM7P0ITRhWgEZ8/Pz4lnA2ZmZYRkg2OEZIdjBGVmaGmFaohpgmqLazlqaWdmZWRkZGVmZ2hpamtra2rS0M/Ozs/P0NDR0dLS0tPU1thtbm9vbm7a2NfW1dPSaGhoZ2ZlZGSEYy7Ix8fIycrLzc/R09TU1WtramnPzMrHw8C+vby9vsDCw8XGx8fHyMnKzM3QamxthG51bWtq0M7OzMvIxcK/vby8ure0sK6vsra8wsfKzGZnaGlqamtsbW5vb+Dj5efp6ujl4t7a19XT0tDQaGdny8nGxMPExMXFxMPBwL69vb7AxGRnam1ubWxraWhoaWprbGxramloZ8zKyMbGxsjMZ2prbG1sa2pphGgbZ8/Ozs/Samttbm9wb29vbm5ubW1tbGtqaWhni2YPzc3NzMzLy8vMzM3Oz2hohGkpaGhnZ2ZnZ2lrbW5vb29ubm1samlnZszLysjHyMnJyMfIyMjJycvMzc6Fz4PO/3+rf4x+kH+SfoV/i36Zf5N+nX+Jfpl/nH6If6V+in+Efpx/0n6Kf8d+hn+Dfs5/kn6Gf4d+jH+OfoR/mX6Kf5l+jH+RfoN/k36Vf4h+jn+FfqB/jX6cf5t+AgIEAAz5+/z+gIKDhYWGhoaEhxSGhYSEg4KBgP/+/v+AgICBgYKDg4aEhIWDhoaFhoSFhYiGi4WChoWHhIaGhQWEhISDg4WCCYGBgP/9/Pv6+oT5FPr6/P3+/v+AgIGCgoODhISEhYWFhoYJhYWDgoH//fv6hPgY+fv8/v7//v7+/4CBgoSFh4iJioqKiYiHhoaAh4iJiYqLjIyNjIyMi4qJiIaEg4KCgoODgoKA//z49fLw8vb7gIKEhYaHh4eGhYSEhIODhIWFhYSDgoGA//79/Pv7/P39/f6AgYKCg4aHiImJiIaFhIKAgP79/Pv7/ICBgoKCgf/79/Pv7e3w9PmAgoOC//r29vb3+fz9/fz69/Ui8/Lw7+/v7u3s7O3w8/b6/YCBgYD/+/bz8e/u7+/u7Oro5oTlKOfq7fDy9fj6/P39/fv49O/r6efn6Onq7O/x9Pb4+/6AgYKDg4SGiImEigiJh4aFhIOCgoSDLIKCgYH//fz6+fn4+Pf4+Pf39vb3+Pr6+vj28/Hv7ezt8PT5/oGChIWGh4iIhYkciIeHhoSDg4KDhISFhoaHh4iIiIeGhYSDgYD//YT8BP3+/v+HgBL//Pr49vX09PPz8vT3+vz9/f2F/gH/hICCgYWCBIGBgICEgQiAgIGBgoKCgYSAiIEQgoKBgP77+ff29fX09PT19oT3OPb29fX08/Pz9Pb3+fr8/4CBgYGA/vz59vPw7+7u7u/v8PHz9vj6/P3+/4CBgoKDg4SEhYWGh4eHhIgsh4eGhYSDgoGAgID+/fz7+fj29fTz8/T2+Pr8/v+AgP77+fb09PT3+v6AgYGEgoiBU4CA//7+gICBgoODgoH//fr59/b39/b19PTz8/T19/n9gIKDhYWGhoaFhIKA/Pr4+Pr8/f6AgYKDg4OCgYGA/vr39PLx8PH09vf5+v3/gP/+/fz9hP5P/Pr59/b19/n7/oGDgoKDg4H9+vj08vP2+fr7+/n28vDu7O3u8PHy8vLz9fj7/f+A//79/f38+vf08O7t7e3u8PL09vj7/4GDhYaHhoWEgoSALIGCg4WHiYqKiYeGhYSEhIWGh4iJiYiHhoSDgoKBgID+/Pv6+Pb08/Ly8fHwhe8T8PH09vj6/P7+/v38+/v7/P7/gIWBF4D+/Pr5+Pf4+fv9gIGDhIaHiImKi4yMhY0JjIuJh4aGhYSEhIMOgoGA//37+fb08vHw7+6E7QTu7vDyhPSF9QL29wf3+fv8f4CBhIIjgYGAgH9+fXx7enl4d+zr6+t1dnZ2d3d4eHl5eXp6e3t7fHyJfYZ8hn0Kfn5+fX19fHx7e4R6hXmVeoZ5CXh47+3r6unp6ITnEOjp6uvs7nd4eXp6e3t8fHyIfRR8fHt6eXju7Orp6Ojo6ers7e7v74TuDnd4eXp8fX5/gIB/f359hnwGfX19fn9/hIAHf39/fn18e4V6G3t7e3p58O3q5+Tj5OjteXt9fX5/f39+fX18fIZ7Fnp5eHd27Ozr6unp6uvr6+x3eHl4eXqFewx6eXh3dnXp6ejo5+iEdCFzcuHd2tbS0M/R1NlwcnNz4+De39/g4ePj4uHf3dva2diE14TVPNfa3uHjc3V2du3r6unp6evs7ezq6OXj4eDf3+Dh4+Xn6ers7e7u7uzq5+Xj4eDh4eLj5ebo6ers7u94eYR6Ent9fn5/fn59fHt6eXh4eHl5eYV6DvPx8O/u7e3s7Ozr6+nohecV5uPg3dvZ19bX2t7i5nV2eHl7fH19hn4EfX18e4R6F3t7fH19fn5/f39+fn18e3p58O/u7u7vhPCEeBx3d3ft6ufk4+Lh4N/f3+Di5efo6enp6urq6+x2hHcBeIV5B3h4d3h4eXmEeAZ5eXp5eXiJd4V4BXft6+nnheYD5+fph+oM6ejn5+fo6err7O7whHkIeO/s6ebj4d+G3gzg4uTm6Orr7O53eHiFeYV6gnuEeix5eHd2dXR0c3Jy4+Lh4N/e3dzb29vc3d7g4uTmdHTn5ePh39/g4uXodXZ4eId5hHpZeXnx8fJ6e3x9fn5+ffj29fPy8fDw7+3r6Obk4+Pj5OZ0dXZ2d3h5enp5eHfs6unp6+7v8Hl6e3x8fXx7e3ry7uvo5+Xl5efp6urs7e947+3s6+vr6uno5+WE5Fjm6OrseHl4d3d3debk4t/e4OTn6erq6OXi4N/e3uDi5OXm5ujp7O7x8nny8fDw7+/s6ubj4uHh4eLk5ufo6uzwenx9fn59fHp5d3Z1dXZ2eHl7fX5+fXx7hHoCe3yFfRd8e3p5eHd2dXTn5ePh4N7c29ra2dnZ2ITZB9rc3+Hj5uiE6oTpBOrs7XeEeBh3d+3r6ujn5+fo6ux3eHl6fHx9fX5/f4CFgQOAf36EfIR7I3x8e3p58vDu6+nn5uXl5OPi4uPj4+Tm6Ors7Ozt7vDx8fL0B9PW2Nltb3CIcg1xcHBvbm1sa2rT0tHRh2iEaQdqamtra2xsiW0FbGxra2uHbIRtBmxsbGtra4hqjWuGaoVphmgHz83My8vKyoTJEsrLzMzNz2hpaWprbG1tbm9vb4ZwCm9vbm1sa9bV09KF0QHShNMS0tHR0GhoaWprbG5vb3BwcG9vhm4Gb29wcXFxhHKAcXFxcHBvbm1sbGtrbGtra2rS0M7LycjKzdFrbW9wcHFxcXBvb25tbWxtbWxsa2ppaGfPzs/Q0NLT1dbX2G1ub29wcXJycnNycG5ta2ppz83Ny8rKZWZmZWRjxMC9ure1tLW3u2BiY2PDwL/AwsPFyMrLzMvJyMbFxMPDw8LAwMALwcLFyMrNZ2lqataE1TXX2dzd3dzb2dfU0tHPzs/Q0NHS0tPU1NXU09HPzMnHxsXFxsfIysvMzs/R0mpqa2tsbG1ub4RwE29ubWtqaWloaWhoaGdnZmXJx8aGxSDGx8fHxsbGx8jIx8TBvry5uLe3ubzAxWRlZ2hpamtsbIRtBmxsa2tqaYZoGmlpampqa2pqamloaGdnzc3MzM3O0NHR0mlqhWsP19XU0tHQ0M/Ozc3O0NLThNQG09PT1NVqhGuGbBBra2pqa2tqamppampqa2pqhWkBaoVpDWpqamloz83KycfGxcWExDHFxcTDw8LCwcC/v8DAwsPExsfJZWVmZmXKyMXEw8LDxMXGx8fIycrLy8vKycnIZGRkiGWIZoJliGQByYXKAcmGyBfJy8zNzWZmy8nGxMPDxMbKzWhpamtsbIRthW45bW3Z2NltbW9wcXFwb93b2NbU0c/Ny8nHxcTCwcDAwcJiYmNkZWZnaWpqamnQz83Nzs/P0Ghqa2xthG5AbdnW09DOzcvLzM3Ozs/Q0WjQzszLysrJyMjGxcTExsfKzM/RamtqaWlpaMzMy8nIyczOz9DQz83LysrLzc/R0oXTR9TV1dVq09LR0NDQz83LycjIyMnKzM3Ozs/Q0mpsbW5ubWxraWhmZmVmZmdpa21ub25tbGtqamtrbG1tbm5tbGtramlpaGhohM8Dzs3MhMuCyoXJEsrMzs/Q0tPU1NPS0tHQ0NHR0oRpGGhnZsvIx8XEw8TExcZjZGVmZ2doaGlqaoZrBGppaGeEZoVlIWRjY8XEwsC9u7q5uLe2tra3uLm6vL/Cw8TFx8nL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6+7weHl5eYd6hnkUeHh4d3d2dnXqdXV2dnd3d3h5ensHamtrbGxtbYRugm+dcAxvb25ubW1sbGtqaWmHaIRpBGpqa2uNbINrhWqDaYdqC2lpaWhnzszLysnIhMeFyILJhMqEywfMzM3OzmdnjmiEaRJqamtrbGxtbm5vb3BwcXJyc3OGdIVzCnJycXFwb25tbGyEaxFsbG1ub29wb29ubGtramlpaYRqDtRqa2tsbG1tbW5ubm9vhG6DbYhuhG2Ebhpvb3BwcXN0dXZ3eHh4d3Z0c3Jwb25tbGtraoZpE2pqa2xra2pozcfCv76/xMrQa2yEbRnX1dLPzMvKysvMzc/Q0tTX2W1tbW5ub29whHEHcG9ubWzX1oTVCtbV1NPS0M7MysmEyAXJysvMzYXOIszLyMbDwL27ubi4ubu9v8LFyMvOaGlqamtrbGxtbWxra2qEaSZoaGhnZsvIxcG/vby8vb6/wcLExWNkZWZnaGhpaWlqamtra2xsbIRtgm6Gbwxubm5tbWxra2tqaWmEaAppaWpqa2tsbG1tiG6DbYduB29wcHBxcXKEcx1ycW/b2NbV09DMyMbEw8LBwL+/wMHDxcfJZWVmZolnhmaLZYRkAcmFygjLzM5naGlpaYZqDGlpaNHQ0NDPz87NzYtmhWWEZIxlhGSHxwnGxsXEw8LBwMCGvxTAwcLExshkZWVmZ2doaWpqa2xsbIVtAW6GbStsbGzZ2NjX1dTSz83LycjHxsbFxMPCwcC/vr6+v8HCw8TGx8jJy2ZnZ8/Qh88O0NHS0tPU1dXX2Nptbm+FcIZxNHBvbWtqaWdlyMbDwMDBYWJjZWdoaWpra2pqa2xtb3BxcXBubGtpZ8zKycnIyMnKzM9oaWqEawHVhdSE1QzU1dXVamtra2pq0tGF0BHR0dLT09LRz87MzMzO0GlqaoVrFWpqamlpaGdnzMvLysvLzM3OaGhpaoVrEGpqaWhozs3NzWZnZ2hoaWmMaoRrBGpqaWmEaAbR0dHS09OE1AbT0tHQz82EzBbNzc7Pz87OzczLy8rJyMfHyMnKy83PiGiDZ4VmFWVlZWRkZGNjYsViYmNkZGVlZmdoaul/nn7UfwF+zH+JfoZ/kX6Rf7J+l3+Pfth/ln6if4l+jn+JfqR/nH6df6N+g3+UfpZ/hn6Zf4p+h3+OfoZ/lX6Qf4l+jn+Efp9/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","name":"blouberg_sunrise_2_1k.hdr","id":153,"type":"FileEditor"},"154":{"outputLength":1,"height":null,"title":"File","id":154,"type":"TitleElement"},"156":{"value":"blouberg_sunrise_2_1k.hdr","id":156,"type":"StringInput"},"157":{"inputs":[156],"height":null,"id":157,"type":"Element"},"161":{"x":-1117,"y":910,"elements":[162,164],"autoResize":true,"source":"// Addition Node Example\r\n// THREE and TSL (Three.js Shading Language) namespaces are available!\r\n// Enjoy! :)\r\n\r\n// layout must be the first variable.\r\n\r\nlayout = {\r\n\tname: \"RGBE Loader\",\r\n\twidth: 300,\r\n\toutputType: 'Texture',\r\n\telements: [\r\n\t\t{ name: 'File', inputType: 'URL' }\r\n\t]\r\n};\r\n\r\nfunction loadFile() {\r\n\r\n\tconst url = parameters.get( 'File' );\r\n\r\n\tasync function load() {\r\n\r\n\t\tconst { RGBELoader } = await import( 'three/addons/loaders/RGBELoader.js' );\r\n\r\n\t\tconst loader = new RGBELoader();\r\n\t\tconst hdrTexture = await loader.loadAsync( url );\r\n\r\n\t\thdrTexture.mapping = THREE.EquirectangularReflectionMapping;\r\n\r\n\t\tlocal.set( url, hdrTexture );\r\n\r\n\t\trefresh();\r\n\r\n\t}\r\n\r\n\tload();\r\n\r\n\treturn null;\r\n\r\n}\r\n\r\nfunction main() {\r\n\r\n\tconst url = parameters.get( 'File' );\r\n\tconst result = url ? local.get( url, loadFile ) : null;\r\n\r\n\treturn result;\r\n\r\n}\r\n","id":161,"type":"NodePrototypeEditor"},"162":{"outputLength":1,"height":null,"title":"Node Prototype","icon":"ti ti-ti ti-components","id":162,"type":"TitleElement"},"164":{"height":691,"source":"// Addition Node Example\r\n// THREE and TSL (Three.js Shading Language) namespaces are available!\r\n// Enjoy! :)\r\n\r\n// layout must be the first variable.\r\n\r\nlayout = {\r\n\tname: \"RGBE Loader\",\r\n\twidth: 300,\r\n\toutputType: 'Texture',\r\n\telements: [\r\n\t\t{ name: 'File', inputType: 'URL' }\r\n\t]\r\n};\r\n\r\nfunction loadFile() {\r\n\r\n\tconst url = parameters.get( 'File' );\r\n\r\n\tasync function load() {\r\n\r\n\t\tconst { RGBELoader } = await import( 'three/addons/loaders/RGBELoader.js' );\r\n\r\n\t\tconst loader = new RGBELoader();\r\n\t\tconst hdrTexture = await loader.loadAsync( url );\r\n\r\n\t\thdrTexture.mapping = THREE.EquirectangularReflectionMapping;\r\n\r\n\t\tlocal.set( url, hdrTexture );\r\n\r\n\t\trefresh();\r\n\r\n\t}\r\n\r\n\tload();\r\n\r\n\treturn null;\r\n\r\n}\r\n\r\nfunction main() {\r\n\r\n\tconst url = parameters.get( 'File' );\r\n\tconst result = url ? local.get( url, loadFile ) : null;\r\n\r\n\treturn result;\r\n\r\n}\r\n","id":164,"type":"CodeEditorElement"},"167":{"x":-390,"y":912,"elements":[168,170],"autoResize":true,"source":"\r\nlayout = {\r\n\tname: \"Environment\",\r\n\twidth: 300,\r\n\telements: [\r\n\t\t{ name: 'Environment', inputType: 'Texture' },\r\n\t\t{ name: 'Background', inputType: 'Texture' },\r\n\t\t{ name: 'B. Blurriness', inputType: 'Number' },\r\n\t\t{ name: 'B. Intensity', inputType: 'Number' }\r\n\t]\r\n};\r\n\r\nfunction main() {\r\n\r\n\tconst environment = parameters.get( 'Environment' );\r\n\tconst background = parameters.get( 'Background' );\r\n\tconst backgroundBlurriness = parameters.get( 'B. Blurriness' );\r\n\tconst backgroundIntensity = parameters.get( 'B. Intensity' );\r\n\r\n\tconst scene = global.get( 'scene' );\r\n\r\n\tif ( scene ) {\r\n\r\n\t\tscene.environment = environment;\r\n\t\tscene.background = background;\r\n\t\tscene.backgroundBlurriness = backgroundBlurriness;\r\n\t\tscene.backgroundIntensity = backgroundIntensity;\r\n\r\n\t}\r\n\r\n}\r\n","id":167,"type":"NodePrototypeEditor"},"168":{"outputLength":1,"height":null,"title":"Node Prototype","icon":"ti ti-ti ti-components","id":168,"type":"TitleElement"},"170":{"height":679,"source":"\r\nlayout = {\r\n\tname: \"Environment\",\r\n\twidth: 300,\r\n\telements: [\r\n\t\t{ name: 'Environment', inputType: 'Texture' },\r\n\t\t{ name: 'Background', inputType: 'Texture' },\r\n\t\t{ name: 'B. Blurriness', inputType: 'Number' },\r\n\t\t{ name: 'B. Intensity', inputType: 'Number' }\r\n\t]\r\n};\r\n\r\nfunction main() {\r\n\r\n\tconst environment = parameters.get( 'Environment' );\r\n\tconst background = parameters.get( 'Background' );\r\n\tconst backgroundBlurriness = parameters.get( 'B. Blurriness' );\r\n\tconst backgroundIntensity = parameters.get( 'B. Intensity' );\r\n\r\n\tconst scene = global.get( 'scene' );\r\n\r\n\tif ( scene ) {\r\n\r\n\t\tscene.environment = environment;\r\n\t\tscene.background = background;\r\n\t\tscene.backgroundBlurriness = backgroundBlurriness;\r\n\t\tscene.backgroundIntensity = backgroundIntensity;\r\n\r\n\t}\r\n\r\n}\r\n","id":170,"type":"CodeEditorElement"},"173":{"x":418,"y":904,"elements":[174,176],"autoResize":true,"source":"\r\nlayout = {\r\n\tname: \"Ground Projected Skybox\",\r\n\twidth: 300,\r\n\telements: [\r\n\t\t{ name: 'Texture', inputType: 'Texture' }\r\n\t]\r\n};\r\n\r\nfunction getGroundProjectedSkybox() {\r\n\r\n\tasync function load() {\r\n\r\n\t\tconst { GroundProjectedSkybox } = await import( 'three/addons/objects/GroundProjectedSkybox.js' );\r\n\r\n\t\tlocal.set( 'GroundProjectedSkybox', GroundProjectedSkybox );\r\n\r\n\t\trefresh();\r\n\r\n\t}\r\n\r\n\tload();\r\n\r\n\treturn null;\r\n\r\n}\r\n\r\nfunction main() {\r\n\r\n\tconst GroundProjectedSkybox = local.get( 'GroundProjectedSkybox', getGroundProjectedSkybox );\r\n\tconst texture = parameters.get( 'Texture' );\r\n\r\n\tif ( GroundProjectedSkybox !== null && texture !== null ) {\r\n\r\n\t\tconst groundProjected = new GroundProjectedSkybox( texture );\r\n\t\tgroundProjected.scale.setScalar( 30 );\r\n\r\n\t\treturn groundProjected;\r\n\r\n\t}\r\n\r\n}\r\n","id":173,"type":"NodePrototypeEditor"},"174":{"outputLength":1,"height":null,"title":"Node Prototype","icon":"ti ti-ti ti-components","id":174,"type":"TitleElement"},"176":{"height":711,"source":"\r\nlayout = {\r\n\tname: \"Ground Projected Skybox\",\r\n\twidth: 300,\r\n\telements: [\r\n\t\t{ name: 'Texture', inputType: 'Texture' }\r\n\t]\r\n};\r\n\r\nfunction getGroundProjectedSkybox() {\r\n\r\n\tasync function load() {\r\n\r\n\t\tconst { GroundProjectedSkybox } = await import( 'three/addons/objects/GroundProjectedSkybox.js' );\r\n\r\n\t\tlocal.set( 'GroundProjectedSkybox', GroundProjectedSkybox );\r\n\r\n\t\trefresh();\r\n\r\n\t}\r\n\r\n\tload();\r\n\r\n\treturn null;\r\n\r\n}\r\n\r\nfunction main() {\r\n\r\n\tconst GroundProjectedSkybox = local.get( 'GroundProjectedSkybox', getGroundProjectedSkybox );\r\n\tconst texture = parameters.get( 'Texture' );\r\n\r\n\tif ( GroundProjectedSkybox !== null && texture !== null ) {\r\n\r\n\t\tconst groundProjected = new GroundProjectedSkybox( texture );\r\n\t\tgroundProjected.scale.setScalar( 30 );\r\n\r\n\t\treturn groundProjected;\r\n\r\n\t}\r\n\r\n}\r\n","id":176,"type":"CodeEditorElement"},"179":{"x":-2547,"y":913,"elements":[180,182],"autoResize":true,"source":"// Addition Node Example\r\n// THREE and TSL (Three.js Shading Language) namespaces are available!\r\n// Enjoy! :)\r\n\r\n// layout must be the first variable.\r\n\r\nlayout = {\r\n\tname: \"GLTF Loader\",\r\n\twidth: 300,\r\n\toutputType: 'Object3D',\r\n\telements: [\r\n\t\t{ name: 'File', inputType: 'URL' }\r\n\t]\r\n};\r\n\r\nfunction loadFile() {\r\n\r\n\tconst url = parameters.get( 'File' );\r\n\r\n\tasync function load() {\r\n\r\n\t\tconst { DRACOLoader } = await import( 'three/addons/loaders/DRACOLoader.js' );\r\n\t\tconst { GLTFLoader } = await import( 'three/addons/loaders/GLTFLoader.js' );\r\n\r\n\t\tconst dracoLoader = new DRACOLoader();\r\n\t\tdracoLoader.setDecoderPath( '../examples/jsm/libs/draco/gltf/' );\r\n\r\n\t\tconst loader = new GLTFLoader();\r\n\t\tloader.setDRACOLoader( dracoLoader );\r\n\r\n\t\tconst model = await loader.loadAsync( url );\r\n\r\n\t\tlocal.set( url, model.scene );\r\n\r\n\t\trefresh();\r\n\r\n\t}\r\n\r\n\tload();\r\n\r\n\treturn null;\r\n\r\n}\r\n\r\nfunction main() {\r\n\r\n\tconst url = parameters.get( 'File' );\r\n\tconst result = url ? local.get( url, loadFile ) : null;\r\n\r\n\tif ( result ) result.scale.setScalar( 3 );\r\n\r\n\treturn result;\r\n\r\n}\r\n","id":179,"type":"NodePrototypeEditor"},"180":{"outputLength":1,"height":null,"title":"Node Prototype","icon":"ti ti-ti ti-components","id":180,"type":"TitleElement"},"182":{"height":686,"source":"// Addition Node Example\r\n// THREE and TSL (Three.js Shading Language) namespaces are available!\r\n// Enjoy! :)\r\n\r\n// layout must be the first variable.\r\n\r\nlayout = {\r\n\tname: \"GLTF Loader\",\r\n\twidth: 300,\r\n\toutputType: 'Object3D',\r\n\telements: [\r\n\t\t{ name: 'File', inputType: 'URL' }\r\n\t]\r\n};\r\n\r\nfunction loadFile() {\r\n\r\n\tconst url = parameters.get( 'File' );\r\n\r\n\tasync function load() {\r\n\r\n\t\tconst { DRACOLoader } = await import( 'three/addons/loaders/DRACOLoader.js' );\r\n\t\tconst { GLTFLoader } = await import( 'three/addons/loaders/GLTFLoader.js' );\r\n\r\n\t\tconst dracoLoader = new DRACOLoader();\r\n\t\tdracoLoader.setDecoderPath( '../examples/jsm/libs/draco/gltf/' );\r\n\r\n\t\tconst loader = new GLTFLoader();\r\n\t\tloader.setDRACOLoader( dracoLoader );\r\n\r\n\t\tconst model = await loader.loadAsync( url );\r\n\r\n\t\tlocal.set( url, model.scene );\r\n\r\n\t\trefresh();\r\n\r\n\t}\r\n\r\n\tload();\r\n\r\n\treturn null;\r\n\r\n}\r\n\r\nfunction main() {\r\n\r\n\tconst url = parameters.get( 'File' );\r\n\tconst result = url ? local.get( url, loadFile ) : null;\r\n\r\n\tif ( result ) result.scale.setScalar( 3 );\r\n\r\n\treturn result;\r\n\r\n}\r\n","id":182,"type":"CodeEditorElement"},"185":{"inputs":[186],"height":null,"id":185,"type":"Element"},"186":{"value":"../examples/models/gltf/ferrari.glb","id":186,"type":"StringInput"},"187":{"x":-2028,"y":-312,"elements":[188,185],"autoResize":false,"id":187,"type":"StringEditor"},"188":{"outputLength":1,"height":null,"title":"String","icon":"ti ti-ti ti-forms","id":188,"type":"TitleElement"},"193":{"x":-1838,"y":913,"elements":[194,196],"autoResize":true,"source":"\r\nlayout = {\r\n\tname: \"Replace Material By Name\",\r\n\twidth: 300,\r\n\telements: [\r\n\t\t{ name: 'Source', inputType: 'Object3D' },\r\n\t\t{ name: 'Name', inputType: 'String' },\r\n\t\t{ name: 'Material', inputType: 'Material' }\r\n\t]\r\n};\r\n\r\nconst origins = local.get( 'origins', () => [] );\r\n\r\nfunction restore() {\r\n\r\n\tfor ( const { mesh, material } of origins ) {\r\n\r\n\t\tmesh.material = material;\r\n\r\n\t}\r\n\r\n\torigins.length = 0;\r\n\r\n}\r\n\r\nfunction main() {\r\n\r\n\tconst source = parameters.get( 'Source' );\r\n\tconst name = parameters.get( 'Name' );\r\n\tconst material = parameters.get( 'Material' );\r\n\r\n\trestore();\r\n\r\n\tif ( source === null ) return;\r\n\r\n\tconst setMaterial = ( mesh, material ) => {\r\n\r\n\t\torigins.push( { mesh, material: mesh.material } );\r\n\r\n\t\tmesh.material = material;\r\n\r\n\t};\r\n\r\n\tif ( source.material && source.material.name === name ) {\r\n\r\n\t\tsetMaterial( source, material );\r\n\r\n\t}\r\n\r\n\tsource.traverse( ( obj ) => {\r\n\r\n\t\tif ( obj.material && obj.material.name === name ) {\r\n\r\n\t\t\tsetMaterial( obj, material );\r\n\r\n\t\t}\r\n\r\n\t} );\r\n\r\n}\r\n","id":193,"type":"NodePrototypeEditor"},"194":{"outputLength":1,"height":null,"title":"Node Prototype","icon":"ti ti-ti ti-components","id":194,"type":"TitleElement"},"196":{"height":669,"source":"\r\nlayout = {\r\n\tname: \"Replace Material By Name\",\r\n\twidth: 300,\r\n\telements: [\r\n\t\t{ name: 'Source', inputType: 'Object3D' },\r\n\t\t{ name: 'Name', inputType: 'String' },\r\n\t\t{ name: 'Material', inputType: 'Material' }\r\n\t]\r\n};\r\n\r\nconst origins = local.get( 'origins', () => [] );\r\n\r\nfunction restore() {\r\n\r\n\tfor ( const { mesh, material } of origins ) {\r\n\r\n\t\tmesh.material = material;\r\n\r\n\t}\r\n\r\n\torigins.length = 0;\r\n\r\n}\r\n\r\nfunction main() {\r\n\r\n\tconst source = parameters.get( 'Source' );\r\n\tconst name = parameters.get( 'Name' );\r\n\tconst material = parameters.get( 'Material' );\r\n\r\n\trestore();\r\n\r\n\tif ( source === null ) return;\r\n\r\n\tconst setMaterial = ( mesh, material ) => {\r\n\r\n\t\torigins.push( { mesh, material: mesh.material } );\r\n\r\n\t\tmesh.material = material;\r\n\r\n\t};\r\n\r\n\tif ( source.material && source.material.name === name ) {\r\n\r\n\t\tsetMaterial( source, material );\r\n\r\n\t}\r\n\r\n\tsource.traverse( ( obj ) => {\r\n\r\n\t\tif ( obj.material && obj.material.name === name ) {\r\n\r\n\t\t\tsetMaterial( obj, material );\r\n\r\n\t\t}\r\n\r\n\t} );\r\n\r\n}\r\n","id":196,"type":"CodeEditorElement"},"199":{"x":-1516,"y":-89,"elements":[200,202,203,204,205,206,207,208],"autoResize":false,"id":199,"type":"StandardMaterialEditor"},"200":{"outputLength":1,"height":null,"title":"Standard Material","icon":"ti ti-ti ti-inner-shadow-top-left","id":200,"type":"TitleElement"},"202":{"inputLength":3,"inputs":[209],"links":[367],"height":null,"id":202,"type":"LabelElement"},"203":{"inputLength":1,"inputs":[210],"height":null,"id":203,"type":"LabelElement"},"204":{"inputLength":1,"inputs":[212],"height":null,"id":204,"type":"LabelElement"},"205":{"inputLength":1,"inputs":[214],"height":null,"id":205,"type":"LabelElement"},"206":{"inputLength":3,"height":null,"id":206,"type":"LabelElement"},"207":{"inputLength":3,"height":null,"id":207,"type":"LabelElement"},"208":{"inputLength":3,"height":null,"id":208,"type":"LabelElement"},"209":{"value":10682488,"id":209,"type":"ColorInput"},"210":{"min":0,"max":1,"value":1,"id":210,"type":"SliderInput"},"212":{"min":0,"max":1,"value":1,"id":212,"type":"SliderInput"},"214":{"min":0,"max":1,"value":1,"id":214,"type":"SliderInput"},"231":{"x":-1520,"y":210,"elements":[232,234,235,236,237,238,239,240],"autoResize":false,"id":231,"type":"StandardMaterialEditor"},"232":{"outputLength":1,"height":null,"title":"Standard Material","icon":"ti ti-ti ti-inner-shadow-top-left","id":232,"type":"TitleElement"},"234":{"inputLength":3,"inputs":[241],"height":null,"id":234,"type":"LabelElement"},"235":{"inputLength":1,"inputs":[242],"height":null,"id":235,"type":"LabelElement"},"236":{"inputLength":1,"inputs":[244],"height":null,"id":236,"type":"LabelElement"},"237":{"inputLength":1,"inputs":[246],"height":null,"id":237,"type":"LabelElement"},"238":{"inputLength":3,"height":null,"id":238,"type":"LabelElement"},"239":{"inputLength":3,"height":null,"id":239,"type":"LabelElement"},"240":{"inputLength":3,"height":null,"id":240,"type":"LabelElement"},"241":{"value":657930,"id":241,"type":"ColorInput"},"242":{"min":0,"max":1,"value":0.43,"id":242,"type":"SliderInput"},"244":{"min":0,"max":1,"value":0.603,"id":244,"type":"SliderInput"},"246":{"min":0,"max":1,"value":0.178,"id":246,"type":"SliderInput"},"263":{"x":-499,"y":-96,"elements":[264,267],"autoResize":false,"buffer":"Iz9SQURJQU5DRQojIE1hZGUgd2l0aCBBZG9iZSBQaG90b3Nob3AKR0FNTUE9MQpQUklNQVJJRVM9MCAwIDAgMCAwIDAgMCAwCkZPUk1BVD0zMi1iaXRfcmxlX3JnYmUKCi1ZIDUxMiArWCAxMDI0CgICBACRzYLMhs0BzM/NAcySzazM3c2LzIbNusy9y4bMBMvMy8uJzJHL0cqmyQTKycnKlskDysrJksq6yYnKhMmCyobJBcrKycnJn8qfy4jKAcuSypfLgsz0y5fMj8uEzILLwswBzYKPjo6Cj4aO/4+pj4KOto//jv+Oh46WjZOOno22jtKNkY7RjQGOhI0BjpCNAY6kjZCOho3GjoyPrY6Cj5lzqHSFc450iHPWdIR1rnSHdal0inWSdLZ1iXT/dfB1jHSHdd90h3WrdJFzlHStc4NywHODcqpzl3Slc/9//3//f/9//3//f/9//3+IfwICBAAB0KLRgtK10YTSAdGk0ojRgtCV0cvSidGW0JHPpM6LzQfMzM3NzMzMhM2ezJLLg8qIy4TKkMsDysrLjsqEyYXKAcmGyoPJhMqVyY7IiceJxgHHhsaFxYnGhceGxoLHjsYBx53Gh8cBxozHB8bHx8fGxsaUx4PIk8eCyI/HksiFx4rIj8ecyIPJhMifyY/IjMmGyqjJlsqay5DMqc2HzpXPhNCL0QXQ0NDR0YXQj5KJk42SnZOClJiThZSKk4qUi5ODlMuTjZKFk4SShJOPkpaRg5CPkQOQkJGlkJiPio4Dj4+Oko+TjgKNjsiNAYyGjYqMAY2PjIOLooyCjZuMlIugjISL/4yXjIONmoyfjQSOjo6Nho6FjYWOAY2IjoSNiI4Fj46Ojo+EjoKPiI6Hj4iOtY+FkI6RAZCQkZSSA3V1dIR1h3SFdQN0dHWFdMh1g3aFdY92iHWIdoV1oHajd5x4hXeEeJR3j3aGd712i3XKdol1qnaCdYV2pnWudop1iXbLdYV0knWLdIR1A3R1dbF0jXOGdANzc3SKc4RyoHOKcoZzonKLc4RygnOHcrlzhHKGc6R0k3X/f/9//3//f/9//3//f/9/iH8CAgQAg9SG1Y7WhtcB1ojXgtiJ14PYhdeC1ovXhdgB2Y7YhNmF2o7ZgtiE2ZbYhdeE2ITZhNiR2YPYhNcC2NeG2IPXhNaE1YLWitUK1NXV1dTU1NPT05jSjdGD0IbPAc6GzwHQhc+EzpDNh8yGy4vKhcmDyJ3HhcaFx5HGg8eKxgHHicYBxYfGh8eExgPHxsaFx4TGi8eHyILHiMiCx4bIAceSyIXHi8aMxYTGkMWNxIXFicSFwwbExMTDw8OJxI3Fg8SkxZTGjseDxovHg8aIx43GhccBxpHHBcbGx8fGiceCxpjHjciCx4XIBsfIyMfHx5LIAceVyIzJg8qIy4bKjMsKzMzLy8vMy8zMy43MgsuEzIPLkMyWzYfOh8+P0IbRhNKG0wHSitOK1IaWjpeGmKKZAZqImYWagpmHmoKZi5oBmYaamJmGmIqZgpiYmYSYg5mbmIqXA5aXl4iWmpWLlIqTCJKSk5OSkpOThJKOkYyQAY+EkJOPhI4Bj4WOgo+NjoiNAYyNjQGMkY23jAGLo4yLi5CKjImFioeJiIqJiY6Kg4mFioWJhYqHiZ+KhYuNipmLhIyDi4SMgouIjIKLjoyLi4OMhYuCjJGLAYygi8GMj42WjpiPhpCEjwaQkJCPj4+NkIaRAZCVkYWSg5GIkoaThJKGk5SUiZWClph1iXaFd412AXeHdop3lXiXeYJ4i3mCeol5m3oGe3p7e3t6mHuEfIZ7AXyIe4V6hXuCeox7A3p6e6p6j3kFeHh5eXmFeJx3CHZ2dnd3dnd3mXaIdwF4jHepeIl3jXgDeXl4h3kEeHl5eY94inkEeHh4eYZ4AXeNeJF3kXaFdZp2hXWCdoh1iXYFdXZ2dnWNdot1AXajdYN2oHWZdJlzuXKHc55yAXOHco5zhnKic45yA3NycptzkHSXdQF0h3UDdHV1h3SNdf9//3//f/9//3//f/9//3+IfwICBACF2YbagtuE3IfdBt7e3t/f34jgheGI4ofjhOQE5eXl5IzlBObl5eWG5oTliOSG44fiB+Hi4uLh4eGP4obhguCR4YfgA9/f3oXdg9yE24vajtkB2oTZitiI14TWh9WI1IfTg9KK04rShdGF0oXRBdDQ0dHRiNCFz4XOhM2EzITLisqJyYLIhseGyIjHhcaHxQjExcXExMTDw4nEAsPEkMMLxMTEw8PDwsLCw8KVwwTCwsPDiMKEwwfCwsLDw8LCjsOFxInDg8SOxZTEAcOExITDhcKGwYrCiMMBxITDhMIBw4TCBcHBwcLCh8OCwoXDAcKJw4PChMOLxIXDjMSCxYfGpMeCxonHhcaDx4bGgseexgLHxobHisaHx5PGi8eTyIbJgsiHyYPKicuHzI3NhMyCzYjMA83Nzo7Nhs6Iz4XQjNGX0obTAdSE04fUCNXU1NTV1dTUjtWO1oTXhdiHmoqbi5yPnY+eip+EoIWfjaAIn5+foKCfoKCnn4+ghqGFoIShBaCgoJ+giZ+NnpmdiZyFm4Sag5uFmoaZjJiElwSWlpWVhJaClY+UjpOFkgSRkpKSh5GQkIqPA46Oj4SOgo+EkIWPiY6SjYSMho2GjIKNhowHjY2NjIyMi4qMmoucioKLh4oFiYmKiomLioaJjYiQiQGIhImDiI6JBIqKiYmEioyJhIiciYqKAYuNioKJhoqZiwGKiYuFjAiLi4yMi4uLjImLAYqKiwOKi4uYigSLi4qKiYuMiomLBoyMjIuLi5SMnY2MjoyPkJCFkYOQkZGLkouTBpSTk5SUk6CUjJWCloSVjZaHlwWYmJiXl4qYhZmDmgJ0dYd0lHWIdgF3iHYBd4p2hXeLeJZ5jXqOe4x8i32MfoV9iX6FfZt+hn2CfqF9h3wEe3t8fIV7h3yGewF8knuIeoJ7h3qQeYd4hXmNeAF3hniDeaJ4g3eMeIJ5h3iEeZd4hnkFenp6eXqJeZZ6hHuXepV5jXiPdwF2incBeIV3gnaFd4d2g3eGdgJ3dox3BnZ2d3d2d4d2hHeJdo51gnasdQh0dHR1dXV0dY90mnOIdItzAXKPcwFyiHOCcohzAXKFc4JyhHOVcgFziHKCc4RypXMBdIRzhHSHc4N0h3MNdHRzdHNzc3R0dHNzc4V0jnMBcpVzg3SZc4h0AnN0i3OEdP9//3//f/9//3//f/9//3+IfwICBAAI5OTk5eTl5eWM5oTlDebn5+jo6enq6+vr7OyE7Yfug++F8IfxhPKC8YXwgu+F8BPx8fHw8PDv7+7u7e3t7Ozs6+vqj+mG6oLpiuiI54LmhucF5ubl5eWK5Afj4+Pi4uLjiOIH4eHg4ODh4YTiguGG4ITfBuDg4N/g4Izfit6C3YvchNuD2o3ZCdrZ2djY2NfX14XWgtWE1oTVCtTU09PT0tLS0dKL0YPQiM+GzgrNzc3MzczMzczNhcyNywvKysrJycrKysnJyYjKhsmEyIjHgsaFxYTGicWFxobFh8aFxQbExMTDw8OGxAPDw8KEwYTAAsHAhMEEwsLBwYTChMEGwsLDwsLChMGKwoLBhMKFwYXABcHBwsLBlcIBwYXCAcORwoTBBMLCwcGFwITBjcCNwYLAicGCwInBBcDAwcHBhcKCw4XEBMXExMSOxYbGAcWIxoPHi8YGx8fHxsbHicaFx4LGh8WCxInDg8SGxZjEhMWDxozHjsiCyYbKAsvKhMuJzIPLksyCzYzOg8+HzgTPz9DQhs+I0IjRiNKM0wXU09TU1IbVgtSE1YfWhdeJ2IfZBdra2tnZhNiH2YrahNuC3Irdgt6E34jgAeGH4oLjhOSEnoWfiqCDn4igg6GHogGjiKIEo6OjooSjiqSFo4aijaOHooWhhKCEoYOiiqOCooijBKSkpKWJpIKjhaQBpYakjqWFpoelAaaHpZ2khaOGooWhhKCHn4yehZ2DnISbiZqEmYKYhpeGloKVhJaDlYSUipOEkgGRhZIBk4SShZGJkIePipCKj4SOh4+EjoiPho6Lj4qQgo+OjoaNiIwGjY2NjI2NiIyGjYWMiYuFioWJhoiSiY6IioeIiAGHkIiDh5yIhImEiIWJA4iIiYSIi4mGigGJh4oGiYqKiomJjYoBi4iKgouOioKJh4qEiQGKjImDiI+JAYiIiY+Ig4mEigWJiYmKioqLh4wCjYyNjYiOAY2EjoSPBY6Pj4+Oho+HkI+RCJCQkZGSkZGRi5KHkwGUh5OFlImVCJSUlJWVlZaWiJWCloSViJYDl5aXiZaEl4SYhZeHmIqZh5qDm4Wag5uLnISdAZyGnYSekHSPc4x0jHWOdgF1iXaKd5B4B3l4eHh5eXmEeoN7hXqEe4Z8g32Pfgl/f3+AgH+AgICKgZeCAYGNgpGBioCIf5R+hH+Tfod9BXx8fX19h3wEfX18fIx7B3p7e3t6enqEeYl6gnmaegF7inoHeXl4eHl5eYZ6mXkDeHl5iHqMeYZ6inmDeoV7hXoHe3t7fHx9fY58iXuFeoV5hXqFe4t6iXmIeIV3jXiFd5B2incBeIx3AXiVd4J2iXeQdgF1hXagdY90hXOHcglxcnFxcXJycnGFcgZxcXJycXGIcgpxcnJxcXFycnJxhXKGc6xymnGbcoxzg3SLc5J0hXWHdAF1mXSGdZB0iHOFdI9zAXSHc4N0h3OEdP9//3//f/9//3//f/9//3+IfwICBAAE7u7v74rwBfHx8vLyiPEa8PDv7+7t7Ozr6+vs7O3t7e7v8PHx8vLz8/OF9Bn19vb3+fn7+/z9/f79/Pz7+vr5+fj4+Pn4hPkE+vr5+oT5gviI+Qf6+vv7+/z9jvwH+/r6+/z8/IX7Cfr5+fj39vb19oX1hPQH8/Ly8fDw74buBO3s6+uH6oLpiugB54TmheWG5AXj4uLh4YXghOGH4oPjheSC44TiCeHh4eDf4ODg4YXgh9+D3ofdhNwE29va2YbYhNeE1oPVhdSC04fSgtGJ0IbPhM6FzYTMg82KzATNzczLjMwLy8vMzMzLy8rJycmGygXLysrJyYbIgseEyAfHx8fIyMfHhMYDxcXGlMWIxAXDxMXExITFhMaCxYTEhMWFxAPFxcSLwwHCkMGDwITBhMCEwYXAgr+EwIPBiMCFwQPAwcGMwIK/hMCDv4i+Ab+Nvgi9vb28vb29voe9kb6HvwTAv7+/hcCOwYbCAcOFwoLDlcSGwwPCwsOGwgTDw8LBhsKCw4TCAcGFwgPDwsKIwwHCicMFxMTFxcWGxgXHx8fIyIXJCsjIycnKycnKysqJywPKysuHzITNDszMzM3NzczMzM3NzczMic2DzIXNAs7Njs6Dz4XQhNEF0tLT0tKE0wrU1NXV1dbX19fYhteD2IvZjNoF29vb3NyE3Qfc3Nvc3d3ehN+D3offguCJ4Qbi4uLj4+OH5AHlheaE54noiOkI6urq6+vr7OyH7YfuB+3u7u/v7u6CpIaliKSCo4iiCKOioqGhoaCghp+CoIShhKIDo6SkhKMKpKSkpaanqKipqYyqDKurq6ysrK2urq6vr4iuEq+vr7CwsLGxsrKys7O0s7O0tIWzkbSKs42yh7GJsISxE7CwsK+vrq6tra6tra2srKyrq6uEqoKphKgFp6empqaKpYymh6WJpAajo6Oio6OEooahiaAEn5+enoadg5yEm4iag5mGmISXBJaXl5eEloWVhJQIk5OTlJOTkpKIk4mSgpGFkoWRBZKRkZKShJGFkIiRhJCFj4qQhI+KjoWPkY4BjYmOAY+EjoWNAY6HjYKOhI2GjIKNkIyIi4WKA4uKioWJAYiViYiIhYeIhoSFAYSMhYSEgoWEhIKDiISVhY6GBoeHh4aGho2Hj4iFh4uIEYeHiIiIh4eHiIiIh4eIiIeHhoYEhYaGhoyFCYaGhoWGhoWFhYiGAYWMhoSHAYiGiQmKiouLioqLi4uRjIWNg4yPjYWOBo2NjY6OjpKPhJAJkZGQkJCRkJCQipGCkoSTBZSUlJWVhZSKlYeWA5eWlomXi5iQmQGaipsSnJubm5ycnJ2cnJ2dnZ6enZ2dhJ6LnwygoKChoaGioqKhoqKFo42khaUDdHNzhXSLcwVycnJzcoRzhXKIcQpycnNzdHRzdHNzh3SEdQd2dnZ3d3h4inmDeoV7g3yOfYJ+hX8TgICAgYGBgoKCg4ODhIWGhoeHh4WIDYmJiomKioqLi4yNjY2FjoSPBZCQkJGRhJKDk4SSBJOSkpKKkYSQD4+Pj46OjY6Njo2NjYyMjISLg4qEiYOIjoeFhoeFiISJg4aCiYGEgId/iH6KfYZ8hHuEfAR7fHx8j3uDeoZ7j3oDeXl6jXkOeHl5eHh4eXl5enp6eXmEeAR5eXh4inkBeI55j3qFe4N6hXuIfIV7B3x8fHt7fHuIfIN7j3yEfYl8g3uEfIV7hnqFewR6e3p6h3uEeop5g3iEeYN4jneIdoV1hXaJdwV2dnZ3d6B2hXUBdox1jnSCc4l0BHN0dHSGc4ZyjHGKcIZxAXCecQFyhXGCco1xg3CKcYVwgnGKcIZxhXCXcYdygnGScoVziHKCc4VyiHOGco9zhHSMcwR0dHR1lHSCdZd0lHWKdP9//3//f/9//3//f/9//3+IfwICBACG/TP8/P38/Pv6+vn39vTz8vHw7+/v8PDx8fHy8/T09fb39/j4+Pn5+vv8/P3+/4CAgYGCgoKEgwmCgoKDg4OEhISEhYOEiIMEhISFhYqECIWFhYaHh4iIj4mCioWLhYoai4uKiYiHh4qPSUwnJygpKhYXGBoaGhsNDQ2FDoYPhg4FDw4OHBuGGjYZGBgYFxcuLS4uLCpSUU6WkYuHg4H//vz6+fj39/b19PPy8fDv7evq6ejm5OPi4uHg4OHg4N+E3gTd3dzcjtuL2gjZ2NjY19bV1ITTBNLS0dGI0IrPic6FzYPMhMsEzMzMzYzMhcsHzMzMzc3MzITNh8wIy8zMzMvLzMyKyw7KysrJycnIx8jJycnIyIfHhMiDx4XIBcfHxsfHhciFx4bIiMcLyMjIx8fGxcXFxsaIxQTGxsfHh8YFxcXGx8aExRXEw8LCwcHCwsLBwMC/v7/Av7/AwMCOv4a+Ab+Gvoe/Bb6+v7+/h76FvYS8hbuDuoS5AbqFuQa4uLi3uLiEt4S4Bre3t7i4uIW3iLiRuQK6u4e8A728vIe9g76Lv4LAh7+IwAi/v76+vr29vZO8h70Bvoa/Ab6HvwHAhMEGwMHBwcLCh8OFxITFDsbGx8fHyMnJysrKycnJicqCy4TKhcuEygPLy8qNywrKysvLysvLzMvLhMwGy8zMzc3Nhs4Ez8/OzoTPhNAO0dLS0tHS0tPU1NTV1NSE1YTWA9fY2YbaiNsB2orbBdzc29vcht0F3t7d3d2F3offjOAB4YjiA+Pk5ITlheYK5+fn6Ojp6enq6oTrCuzs7e3u7+/v8PCE8Qny8vPz9PT19vaH9w74+fn5+vr7/P38/P39/YT+Af0DrK2thqwLq6urqqmpqainpqWIpA6lpaamp6ipqaqqq6usrIatBq6uV1dYWI1Zg1qGW4daBFtbXFyFXYZcCV1dXl5fX2BgYIVhhmCJYQFih2F3YmNjZWhtdodNWzU6P0NIKCwwNTk7QCEjJCYoKCkpKSgnJyYlJCMjIyIiICAfPTw7Ojc1MzEvLy0rKCZJRUA7NC9ZU0uHe3NvbWvTz8zKycfGxcPCwL68vLq5uLe2tLOysa+trKupqKenpqSjoqKhoaCfnp2dnZyMnQacnJ2cnJyEnYOchJsFmpqamZmLmAGXiJaElQSUlJWViZSGk4KUkJMBko2TBJKSk5OEkgGThpIJkZGSkpKRkZKShZEDkJCPhJCFj4OOho+IjoKNjY6Fjw2Ojo6Pj46Pj4+QkI+PhY4Ij4+Ojo+Pjo6EjwaQkI+QkJCEj4KQhI+DjoSNg46MjYaMhYsBjISLhooEiYmJiIeJhoiHh4aGEIWFhYSEhYWEhIODg4KCg4OFgoaBi4AFgYGBgICEgQSCgoGBhoKCg4SEhoWFhpCHloiIh4KGh4WGhIaDg4KEgYSCAYGLgoSDCoKDg4SEg4ODhISFhYWGAYWGhoOHhYgDiYmKhYmJigaLi4uMjIuEjAGNh4yHjQGMhI0OjIyMjY2MjI2MjIyNjIyEjYKMhI2GjIqNBo6Ojo2OjouPBpCQkJGRkoWTjpSLlYaWhpeHmIKZhJiImYSaiJuDnIqdBZ6enp+fhKAQoaGhoqGioqOjo6SkpKWmpoSnhagDqaqqiKuErIitAayErYhyhHGEcARvb25uh20Gbm5ub29whHGNcoI5jjqRO4Q8ij2EPoM/iECDQYRChUMBRIZFekZGR0hKTVJZZXeX05PKhaK91fKMna2/z93vgoyWn6WnqaqpqKelop6al5aUko2Hg4H99/Hn2c7IwLmyqJqOhv/u17udh+/NpP7DnoyAeOXc1M3JxcG+u7i1srCuq6mnpKKgnpybmZeVlJKRkZCQjo2Mi4qJiIeGhYSEiIOFgomBhYCCf4V+j30BfIV9hXwBe4V8iHsEent6eox7inqCe416CXl5eXp6eXl6eo95Bnh5eHh4eYd4BXl5eXp6hHmEegN5enqHeYV6A3l6eoV7gnqFe4Z8g3uEfAF7hXyIfYJ+hH0Ufn59fX1+fn59fX59fn5/f4B/f36GfwaAf35/f3+HfgR/fn59hH6FfYR8hX2PfAF7hXyEewF8hHsEenp6e5B6A3t7fIR7hHwEfX19fo1/jIAFgYGBgoKJgQOAf3+HfoR9hHwHe3t6enl5eYZ4hHcDdnZ1hHQDc3NyhHGGcIRvCnBwcG9wcG9vbm6Hb5NuhW+FbopvhHCEcYhwgm+GcMhvg3CFcYNwiHEEcnFxco9xBnBwcHFxcZVwkHGJcgJzcopzhnKFcwZ0c3N0c3OIdIt1iHaOdYJ0hHMBcrJ/3YCCgYWCh4OXhI6DhoKDgYaA/3//f/9//3//f/9/tX8CAgQAhYYBh4SGhYUFhISEg4OJgoSBhICEgYaChYMNhISEhYaHh4iIiYiIiISHA4iIiYaKAYmEiIKJj4oEi4uLjIqNBY6Oj5CQi5EOkJCRkZGSkpKTk5SVlZWIlgGXhJYYl5eYTExMJicpFRYXFwwMDQ4ODxAQCAgIjgkBCogJCwoKCgkJCAkJCRIShRE0EBAQDw8ODQ4cGxoZGBctLCpNSYqCfPHt6+rp6ejn5uTj4uHg3t3c29rY19XU09PS0dDPzoTNBM7O0NCIz4XOhs2JzIfLBcrKysnJhMiGxwHGhceJxojHBcjHx8jIhMkByoTJg8iEyYPKhMkByofJgsqEyQHKhMmEyIjHh8YIx8bFxcXGxsWFxoTFBcbGxcXFhsYExcbGx4TGhcUGxMXFxsbGhMWGxIbFEsTEw8PExMXFxMPDw8LCwsHCwoXBh8AFvr6+vb2FvIO7iryCu4W6Brm5ubq6uoe5AbiFuYK6h7uDvIy9hL4fv7+/wMDBwsLExMTGxsjIycrM0mtrbGxtb3M7Ozw8PIU+Bz9AQD9AQUKJQwtERENEQ0VGRURDRIVDAUSEQ4VBJkA/fHt7eXVxbm5v2tTPzs3LysnIyMjHx8bHxsXFxMTEw8LCwcDAhMEDwMC/hb4Evb6+vYW+jL0Tvr6+v76+vr/AwcLDw8LCwsPDw4XEgsaEx4TIiMmEyoTLBcrLy8vMhMuDzIXLAcqNy4LMic0HzM3Nzs/Pz4TOAc+E0ITRBdLS0tHSitMT1NPT1NXV1tbX19jY2NnZ2tvc3ITdiN6C34TeBt/g39/f3oXfBODg4N+K4IThguKM44LkhOUE5ubn54bohekQ6urp6urq6+zs6+zt7u7u74TwGvHx8vP09PX29/j5+vv8/P7/gICAgYGBgoKChYMHhISEhYWGhoRbg1yGW4laglmEWolZhlqIW4Vcgl2IXgZdXV1eXl+FYIJfhl6DX4Rgh18EYGBhYYZihGGCYoZjg2KEYTZiYmJjY2RkZWVmZ2doaWlqa2tsbG1tbW5ubm9xdHqCjExUYjlDUC0zOkAiJiksMDM2OR4fICGWIkshISEgIB8eHh05NzY0MjEvLSspKCYlIyA8NzQwLCdEPjZbSXtuZsO8uLazsrCurKqop6ako6Gfnp2bmZiWlZWUk5KRkZCPj5CQkZKLk4OUiZMBkoWTiZIGkZGRkJCQio8Djo+PhI4Dj46PjY4Nj4+Pjo6Pj4+Ojo6NjYWOB4+Pj46Ojo+EjoePCI6Ojo+Pjo+Oh42FjoSNgoyEjQGMjI0BjoSNhI4Fj4+Ojo6Gj4WOAo2Oh4+HjgSPj4+QiI+CkISPAY6Fj4mOBI2Ojo2FjAWLi4uKioqJiIiDh4eGh4cJhoaGh4eIiIeHhIaHh4aIh4mFiiaLjIyNjo+RlJaanqZYXF5hZ251PDs8PUBDREVHR0lLS0tNTk9QUIRRNVJTVFRUU1RTUlJRUVBQUE9OTEtLSkpIRkRDQkFAPTx0b21oZGBdWlamoZyZlZKRkI+Pjo2MhIsLioqJiYiHh4eGhoaFhQWEhISDg4SCA4GCgoiBhIIEgYKCgoSDBYKCg4SEhoUDhoWFhYYGh4iIiYmIhImIiomLjIyVi4OMhIsEjIuLi4SMhIuEjImNhY4Ej4+OjoWPhJCCkYSSBpOTk5SUlISVipaPl4mYiZkBmoSbipyCnYWehp8EoKChoYeihqOFpIKlhKYSp6ioqamqq6usra6ur6+wsVhYh1mJWoNbjDuIOoI7hDqCO4k6BDs7OzqGO4U6hzuFPIc7hzyJOwU8PDs7PIc7hDyCPYY8hT2EPoY/OUBAQEFCQkJDQ0RERUVGR0hJSktMTU5PUVJUVlldZXCFpdKGrOKUwPmbutn3ipqqucra6/uEipCUl4SYhZmEmk2bm5ydnZ6enZuYlZSRjYmFgv748OXa083GvLKooJeNg/DczLmii+7NpO6g1Z6B5tTIwbu3s7CsqKWin5yal5WSkI6MioiHhoSDgoB/foV9h36MfQd8fX18fXx8hH2HfIR7hnqJeYh4iXeHdgh3d3Z3d3Z2doV1h3YEdXZ2doZ1iHaDd4t2DXd3dnZ3eHh3eHh4d3eEeIh5hXqEewV8fHx7fI19AX6Nf4eAhX+DgIZ/ioCHgYWChIEFgICAgYGEgAKBgoaBhYILg4KDg4OEhIWGhoaEhwOIiImIig6LiouLjIyNjY6Pj5CQkISRf5KSlJaXmZyeoKGjpquxuMHN3PWNmqWzw9nxgIWNlaCoqrC2u8HIy87R09fd3d7g4OHm5+Xm5eTl5eLf3NfX1tPR0MvEw724ta6ppZyVkIqD+OnayLannZSG8NnCubConpiRkI+MioeFhIKCgYGAfn18fHt6enl4eHh3dXV1dHSEc4ZyBHFxcHCEbwdubm9vbm5vhm6Db4RulW+PbgFvjW4FbW1tbm6PbQRubm5tim6DbYVuAW+MbgFviW6Nbwhwb29wb29wcIRvhXADb3Bwi28KcHBwb3BwcG9wcIhvBXBwcXBwi3EDcnJxjHKDc4xyh3OGdIV1hXYFdXV1OzqEOwE6jjv/gKGABoGBgYKCgoSDiISjhY+EhoMIgoKCgYGAgID/f/9/8H+HgLaBiYD/f/9/x3+VgAICBACCjISNBY6PkJCQipEEkJCQkYuQiI+EjgGPhZAWkZGRkpOUlZaXmJiXlpaWlZaWl5eYmISZhZiOmQiam5ucnJ2dnYWeEp2dnJycm5qZmZmYmZmam5ucnIadBpybmpqZmYSYBZeXl5aWhZWKlAaTk5KSkZGEkBORkZJJSSUnFBUWFwwMDg8PEBEShQkBCqQJhQotFBMTExIREBAPDhsaGRgXLCooSol97ebl4+Df3NvZ2NfX1tTS0M/OzMvKycjHhMgCycqEzBLNzMzMzczLysvLysrKycnIyMiGx4TGhMUMxMTFxMTFxMTDw8PChsMBxIXDBsLCw8PDwofDhMSDxYTECsPExMTDw8TDw8OFxInDhMSJw4PEhMMExMTFxYTEFcPExMPDxMTDwsLDw8TExMPDw8LBwYbCAsPEhMOExITDhMIBwYjCAcGEwoPBhMKHwRjAwL+/vr+/v76+vr28u7u7urm5uLi5ubmFuBC5ubm6u7u8vLy9vb6/v8DAhMEhwsLCw8bHyWdrNzk7PB4fICAhIiQlJCUTExMUFBQVFhYVhRYHFxcYFxgYGIcZghqJDYwaDRsbGxobGhsbGhsbGxqLGwUcGxscG5MchR2GHoUdgxyFG4QaHjQzMzIzMjAvLi0tKysqKU5MSUhIRUKAgHvj1MzJxojEhMMDwsLBhMIKwcHAwL+/wL+/wIfBDcPExcXHyMjJycnKysqEywTMzMzLiMwFzc3MzM2HzIrNh86LzxHQ0NDR0dLS0tPS0tLT0tLR0YjTh9QN1dbW1tfX19jY2Nra2oTbCtzc3N3e3d3e3t6E34TgB9/f4N/f3t6F3wvg4OHi4uLh4uLi4YTiAuPkh+WC5oTlA+bn54boBunq6+vq6obrguyI7SPu7+/w8PHw8PDy8vLz9Pb3+Pj5+vz9/oCAgYKCg4SEhIWEhISFCIaHh4eIiYqLhGEGYmJiY2Njh2SPZYtkhWOGZIVlA2ZmZ4ZoB2dnZ2hoaGmFaoVphGqCa4VqhGsBbJBtgmyHawNsbW2FbgFvhW6EbYpsBm1tbW5uboRvhHCGbxVwcnd/i05bNkIoMDc/JCgsLzI2ODuFHowfjSCCIYwgNh8fHjk3NTMwLSooJiNAPDcwKks/MUt3Z761sa2rqaimpaOhn52bmZeUk5KQj46NjIuLjIyNjoSQA5GQkIiRhJCFjwSOj4+Oho2FjAmLi4yMi4uLioqKi4eKiImLigWJiYqKioWJAYqKiYaKgouFigSLi4uKiIsHjIyMi4yMi4SMAo2Oi42FjISNgo6EjYSOjY0BjoSNhI4gjY2Njo6Pjo6Oj46Oj46Ojo2NjI2OjY2NjIyLioqLioqEiQGKhYkEioqJioSLBYyMjI2OhI86kJCQkZGSk5SXm6VdaztBSFMsLjEzNzo9PkFEJCUmJygpKissLS8wMDEyMzU2Nzg4OTk6Ozs8PT4+Pokfgj+HPgE9hT4BPYk+hz2GPI07BDo7OzuHOj85OTk4ODc2NjU1NDMzMjEwLy4uLSsqKSgoJ0tJSEVEQT88OTg2NDIwLVdSTEhEQDtrY1ummJCNi4qKiYmJiIiEh4OGhIUDhoWFh4SIhQWGhoaHh4iIhYmCiIWJjYgDh4iIiomGioKLhIoJi4uLioqKi4uLh4wIi4yMi4uMjIyHjYSOBI+PkJCIkYKShJMMlJSUk5SUlZWVlpaWhJcGmJeXmJiYiJcElpeXmI2ZAZqIm4SchJ2GngefoKGhoKCgh6EIoqKjo6Kio6OEpIKlhKcKqKmpqqqrq6usrYSuB1hYWFlZWVqEW4NchV0FXl5fYGABPI09jD6MPwQ+Pj8/hz6NP4NAiT+GQIc/jUCLQYdCiUGUQjdDQ0NERUVHSElKS0tMTU5PUFJTVVZYWVpcXmBlcIas5pzVksiHrdL2jqG0xtfm8/+DhIWGhoeHhIgLiYmJioqLi4yMjI2GjoOPhJA5j46Oj46NjY2MioaB9+zi1sq+sqaajPzn0K+S8r6Ep82O6NHEurKsp6OfnJmWlJGPjIqIhoOBf318hXsLfH19fH19fXx9fX2JfIR7iXqCeYl4hHcCdneEdoJ3hHYFdXV2dnaFdYd0hnMBdIVzhnIBc4dyB3NzcnJzc3OJdIh1hXaGdwl4eXh4eXp6enmLegN7fHuEfAR9fHx8h32Efgx/fn5/gICBgYGCgoKHg4WEhIWIhoKHh4gLiYmJi4uMjY6Ojo+EkEuRkpOUlJWWlpaXmJiYmZmam5yeoaWpr7rQ/aTYiKTD6IKPnq2+y9bg7vuEiY6UmqClqa+zucDDx8vQ1Nfa3uLl6e3u8fP4+/3/gICFgSKAgP/+/f38/Pv6+fj49/f39vT08/T08vHx8fDw7u7t7OzqhegH5uTk4uPi4ITfAt3chNtV2djX19bV1NPS0tDNysjGxMC9uba0sa2qpqOgnZmVkIyIg/737+Xc08rBt6+mnJOKgvTkzrysmYfqyqP8w6SXjYWBf359fHt6enl4eHd2dXV0dHV0c4Vyg3GEcAFvhG6EbYZsAWuRbAFtnmwBa4RsiGuEbItrBGxsbGuEbAFriGwBa4hsBWtrbGxriGyEbYZuim2CbolvhW6Cb4xuhG+FcIRxA3BwcYZwgnGJcINxhXKEc4l0hzqKOwM8OzuEPP+AtIAEgYGCgoSDiISvhYqEhYMGgoKCgYCA/3//f6l/goCEgYqCn4OJhNiDj4KHgYOA/3//f5N/mIACAgQAJo+QkZGSk5OUlJWWlpeXmJmZmpqbnJ2en5+goKGhoaKio6SlpqeohKkRqqqpqaiop6empqenp6ipqamEqoerDqysra2ur7CxsrKys7OyhrGDsoSza7S0tba3ubq6ubi3trSzsrKzs7S2t7i6u7u8vb29vLy8u7q5t7Wzsa+tq6mopqSioaCfn5+enZ2cm5ubmpmYmJeXlpaVlZSUlJOTk5KSkZGQkJCPjo2NjI2MjIuKi46QR0clKBUXGA0ODw8RhRKEEwESmRMEFBMTFIsTihQqExMSERAPHBsZLytORXzp4t7d29rY19fV0tDNysjGxcTEw8LDw8TFxsfHhsiJxwrGxsbFxcXExMPEhMMFwsLBwcGGwAO/wL+EwIe/h8CCwYTAg8GJwIa/hL6Kvwm+vr++v7+/wL+Fvga/v7++v8CGwYbAAcGNwBLBwcDAwMHAwcHAwMDBwMHBwMCFv4S+A7+/voS/A76/voS9hb4FvLy7urmFuIS6JLu7urq8vb2+v8DBwcLDw8PExMPCwWJoNzk9ISIiIyISEhMTFIQVBBYWFhePCwQKCgoLhAqCC4sWBRUWFhYXhhaGF4QWCRcWFhcXFhcXFoUXDRYWFxcWFxYXFxYXFxaIFwQYGBgXjRiFGYMYihkLGhoZGRoaGhsbGxqEGwEahxuFHAIdHIUdiR6EHxweHh46ODg3NjQzMS8uLVlWUU1KRoR9dtrPy8nHhMYBxYTEBcXEw8LCiMMBxITFB8bGx8jIycmFyoTLCMzMzMvLzM3NhMwLzc3Mzc3Nzs7Nzc2EzofPB9DR0dDQ0NGK0gjT09LS09PU1IXVBNbW1dWN1gTX19fYhNkH2tvc3Nzb24TchN2E3ATd3NzbhNwO29vc3N3d3t7e397f4OCE4RLi4uHh4eLi4+Pj5OXl5ubl5uaE54Pohekr6urr7Ozt7e3u7/Dw8fHy8/T19vb3+Pj5+vr7/P3+/v+AgIGBgoKDhISFhYSGDIeHiImJiouLjY2OjylkZGVmZmdnaGhpampra2tsbG1tbm9wcHFxcXJzc3R0dHV1dnZ3d3h4eIR5Anp5jHiEeQZ6ent7fHyFewp8fH1+f39/gH9+hH2CfoZ/D35/f4CBgoKDgoGBgH9+fYR8AX2HfhV9fX18fHx7e3p6enl5eHh4d3d2dnaEdRR0dHRzc3JycnFwcG9ubWxra2pqapRpE2tudoVMWDRAKTM8IycrLzM2ODiJOQI6OYQ6Ajs6ljsBPIw7MTw7OTc0MS4rJyQhOjQuSjtdRWu8saypp6akoqGfnZqYlpOQj42Mi4qKiYuLjIyNjY2FjgyNjY2Ojo6NjI2NjI2EjIaLBYqKiomKhYkEiIiHh4iIBYeHiIiHhIgDh4iIhIcBiIeHB4iIh4eGh4eThgKHhoWHhYYMh4eHhoeIiYmJiIiJhIiFiYWKB4uLiouLi4yEi4SMg4uFjASLi4uKiYuHjIaLB4yMjIuLioqGiQSKi4qLhI2EjiePj5CRkZKSlJaYmJqgWWs/R1MvMjY9QSIkJykrLS8xMzM2ODkdHh6IH4geAh0ehB2EOoQ5gjqGOYg4hjeQNok1ijSQM44yhzEFMDAxMDGNMAYvLy8uLy+FLo8tJSwsLCsqKSdMSUZDQD07ODYzL1lUTUhCPGxiV5qPjIuLioqKiYiEhxWIh4aGhYWFhoWFhYSDhISEg4ODhISIhYaGBIeHh4aLh4qIg4mFiISJA4iIiYWKiYsFjIyNjY2KjoqPhJCFkQGShpOHlASVlJSUhZUBloWVg5aGl4WYhpmDmoebhJwDnZ6ehJ+CoIShBKKio6OEpAmlpaamp6eoqKmEqoOrhKwGra6uV1dXhVgTWVpaW1tcXV1eX19fYGBhYmJjY4Q+hz+HQAVBQUFCQoRDgkSERYJGhUeURoRHi0gER0dISIZJBUhISUlJiUqGS4JKhkmOSI1HhUYBRYVGhEWCRIlDQ0RERUZGR0hISUpLTE5PUVJTVFZYWl1kcZHOlNOSyIm04YifssPU4uru7/Dx8vLy8fL09PT29/j5+Pn6+vv8/P39/fyE/g79/f78/P7+/f7+/v39/IT9AfyE+yj48+nay7+wnZCB3b6f97XziZ/rzL61raijnpyYlZKPjImFg4F/fXt6hXmJeoZ5h3iId4V2hHUCdHWHdIlzB3Jyc3NycnOIcgFzkXKEcwR0c3NziHSCdYV0h3UCdneIdoJ3h3gFeXl5eHmEegR5enp6hnuKfIV9IH5/f3+AgYGAgYKCg4OEhYWGh4eHiYmKiouLjI2PkpSVhJYsl5mbnJ6foKChoqOlqKyyuMbon+aaxfuVqcLg+IaQnKiyusTN1t7o8vyBhIeEiQSHhoaGhYU3hIODgoKBgYCA//79/fz6+ff29PTz8/Lz8/Lw7+7t7Ozq6uno6Obl5OPi4eHg397d3Nzc29ra2YTYPtfX1tXV1NPS0dHQz87NzczLysnJyMjHxsfGxcXExcTCwcDBwL69vb28u7m5uLi4tra1tbW0s7OysrGwsK+vhK1ErKqpqKenp6alpKOioZ+enZycnJubm5qamZmYmJeWlZSSjoqGgPPo39TJu7GnnJCF9du9rZyD16qExJuIf3t4d3Z0c3KEcQNwb2+FbgttbWxra2tqamlpaYdqCWlpamppamlpaodpB2hoaGlpaGiHaQFohWkEaGlpaItpB2hoaWloaGiIZ4doA2dnaIRnAWiJZ4Zok2mFaoZrhWyFbYhsgmuFbIJthmyKbQFshG0BboRth26Db4hwhXEBcoc5gzqEO4g8BT09Pj4+/4DBgAeBgYKCg4ODx4QIg4ODgoKBgYD/f/9/BYCAgYGBhYKNg5mE/4Oag4uChoGDgP9/9X+bgAICBAAxjY2Oj4+PkJCSk5SUlJaYmZyeoaKlp6mqrK6xs7a4ubq8vsDCw8XHyMnKy8zOz9HS0oXTCdTT0tHQz87NzofNAszNhM4J0NHU2Nra2dfVhNNB1NbY2dvd3t/h4+Xm6Ojn5uXl5OXn5+jp6uvs7e7v8O/u7ezq5+Ti39zZ1tTRzsjDv7y4trS0s7OysbCvrq2srKyFqzmqqampqKiop6empqWko6KhoJ+enJuamJeWlZSTkpGQkJCPjo2MjIuKiYmLRkclKBYXDA0ODxARERGtEogTKxQUExQUExQUExMSEREfHRoXK02Fdt/b2dfV09LRz83Kx8TCwL+9vb6/wMKEwwTCwsHChMEHwsLBwMHCwonBh8ADv8DAhL+FvgG/hb6JvYW8Crq6u7y8vb28vLyIu4K8hLuEvIK9hLwBvYe8gr2EvIK7iLwGvb6/v76+hL8Evr29vYW+BL29vbyEvYK8iL0zvLy7vLy8vby7u7q6ubi4t7a2trW2tri4ubq6u729v8DAwsTFxsjIyclnODoeICAiERIShRMDFAoLhQoECwoKC4kKgxWFFAUVFBQUFYwUghWIFAEVqhSIFQEWihUDFhUVkxaEFwEWiReLGAEZhRiMGYkaiBsHHBwbHBwcOIQ5hDqHOxU8PTw7OTc1MzIxW1dSTUmLgnbYz8uEyYnIg8eGxgTFxMTGhMgEycrJyYTKA8vKyoTJhsoIycnKy8vMzMyGzYXOhc8B0IbRhdIO09LS0dHR09PT1NTU1dWF1AfV1dTU1NXUhdWG1gnX1tfX19jX19eE2AHXhNgT2dna2tvb29zc29zd3t/f3+Dg4YTiAuPkhuUS5ufn5+jo6ejp6Ojo6erq6uvrheyE7irw8PHy8vP09PT29/j5+fr6+/v7/P7/gICAgYGBgoODg4SEhYaGhoeIiYqGiwOMjY0zZWVmZ2dnaGlqa2xsbW5vcHJzdHV3eHl6fH5/gYOEhYeIioyOj5GTlZaYmZucnqChoqSlhKYDp6eoh6d1qKipqqurqqmpqqusra2trKqopaOhoaGio6WmpqenpqanqKmqq6qopqOhnp2cm5ubnJycm5ubmpmZmJaVlJOSkY+OjYuKiYiIiIeHh4aGhoWFhISDgoKBgYCAf39+fn19fHt6eXl4d3Z2dHNycW9ubWxramlphGgEaWlqaoVrGGxsbGttcoFNXDZEKzYgJSouMDIyMjMzMoYzhzQCNTSGNQE2iDUINjU1NTY2NTWINjE1NTY1NTY1NTY1NjUyMCsoJUA5MSg/W39jsKikop+enJual5SRjoyKiIeGhoeIioqKjYsEiouLi4SKhYmJiIOHj4YDhYWEhoWEhAaDhISFhoaFhQSEhYWGhIUBhISFAYSGhYuEAYWEhIKDiYQBhYmGBIWGhoaKhwSIiIeHhYiCiYSIhYmEiAWGhoaFhYSGJYeJiouMjY6PkJGSkpOUlZaYmZ2qaEFKLDM4QCQmKS0wMjQ3OR2IHoUdhhyEOQE4hDcBNoU3hTaFNQQ0NDU1hDSDM4cyiDGHMI0vji4GLS4uLi0ujC0HLCwsLSwsLYwsiisBKoQrBSoqKysrhyoEKSoqKoUpASiHKYwohyeETwVOT05PT4dOHE1NSUZDQD05NjNfWFBJQnZpXJ6PiYiHh4aFhYWEhIWDCoKDg4KCgoGBgYKFgwSEhYWEiYWJhgaHh4aHh4aHh4aIAomIiYkBioeLCIyMi4uMjY2MhY0Kjo6OjY2Ojo+Pj4eQg5GJkoiThpSFlQmWlpaXl5eYl5eFmAGZh5oRm5ycnJ2dnp+fnp6foKChoaGFoi6jo6OkpKSlpqamp6eoqKmqq6ysra6urq+wsVlZWVpaWltcXF1dXl9fYGBgYWJihGMFZGRkZWWEQIRBhEI8Q0RFRUZGR0dISElJSktMTU5PUFFRUlNUVFVWV1laW1xdXl9gYGFiYmJjY2RjY2NkZGVmZ2hpampqa2tshGsUamloaGdnZWRiYV9dXFtbW1paWViHVwxWVVRTUlJRUFBQT0+GTgRNTUxMhUsHSktLS0pJSYRKAUmESopJhEiDR4VGB0VGRUVFRESFQ1BERUVGR0hJSktNTk9QUVJUWFpeZXq1kNyf45/RgZirvcrS1NXX19fY19jZ2dna29va293e3t7g4eDh4uLi4eDh4N/g4OHi4eDf4ODf4OHh4YTgK+Hg4OLh4eLh4uLg493Sx7KgkfrYtIrF8v2P28O3r6ikoJyYlI+KhoJ/e3eEdYp3AXaEdwN2dneKeIR3A3Z2d4V2g3WLdI5zgnKIc4Ryh3OCcpNziHSFdYR2AXeFeIN5h3iHeQh6ent6ent7fIZ9QX5+fn9/gICAgYOEhYaHh4mLjI6QkpSVlpiZmpucnZ6doKOlq7O8yv7Ln9uWutT2ipaktsPP3+/6goeIiIiHhoaFhYRog4OCgYCA/vz7+vn49/b19PTz8vHx8fDw7+7s6+rp6Ojm5ubk4+Li4N/e3t3c3NrZ2NfV1dTT0tLR0NDPzs3MzMvKycjHxsbGxMPDwsHAv7+/vr29vLy6urq5uLe4t7e1tbWzs7GxsLCErhWtrKuqqqqpqamoqKinpqWlpaSko6KEoAWfnp2dnYScGZuamZmYmJeXlpWUlJOTkpKRkJCPjo2Mi4uEjEGLioqKiYmIh4aGhoWFhIOCgoGBgP/+/v37+/n4+Pf29vTz8fDv6uDYzb6wpZmK+ebMrpf1uYnFlH93dHNxb29uboVtDWxramloaGhnZmVlZGWEZoNnh2aEZYdmCGVlZmZmZ2ZmhGcEZmdnaIZniGYCZWaIZQVmZmZlZodnjmiIZwFmj2eEaIppgmqHaQFoh2mCaIdpAWqGaYRqBGtramuFbINthm6Cb4RwB3FxcXJyczmEOgg7Ozs8PD09PYU+hj+FQP+Ay4AGgYGCgoODyoSEgwSCgYCA/3/ifwOAgYGEgomDlIT/g7qDmoKFgYOA/3/if52AAgIEAE+bm5ydnqChoqSlp6mrra+xs7a6vcHGyc3S193h5uvw8/b6/f+BgoKDhYaHh4iJioyMjY6PkZOUlpaXmJiYl5WTkI+OjYyKiIaFhYSEhYeIhIkLh4SDgoOEhYaHiIiEiXqIh4aFhIOEhYaHiYqLjIyMi4uLioiHhoSDgYCAgP77+fbz7+3s6ufl5eTh39zZ19XU0c7My8rJysnIxsXDwb++vLm3tLKwr6ypp6WjoaCem5eVlJOSkI6OjYyLioqJiIaEg4KCgoGA//+AgP78+vqAQCEgEhUXDQ4PEIURCBAQEBERERAQhBGKEAQREBEQjhEIEhEREhIREhGEEgERiBKEEyISER8cGi0nRHPU0NDQz87My8jFwr+9u7i2tbi6vb6/wMDAhsEDwMHBhcCHv4e+Br29vby8u4a8Dbu6u7u6ubq5ubq5ubmPuAW3t7i3toa3hLaEtwy4t7e2tra1tbW0tbaGtQW0tLW1tYi2Cre3uLe2tra3t7aHtwO4uLeEuIO5hbgsubi4t7a1tra2t7e5ubm6u7y8vL2+vsDAw8TFY2g3HiAlFBMUFBUXFwsMCwyHCwkKCwoVFRQUFBOHFAgTFBQUExMUFJEThRKwEwIUE5EUCRUVFBUVFBUUFYUUhxUBFpAVkhYJFxcXFhcXFxYWiRcFLy8vMDCEMYQyBTMzMjMzhDQFNTU2NTWENgU3NjY3NoU3hzgDNzc4hDcWODg4NzY0MjEuLFRQSYd8bs7Ix8fGxoXFhMYLxcTExMPDxMTGxsaFxw/GxsfGxsfGx8fIyMfHyMiHyQjKy8vKy8zNzYTOAc+Fzg7Nzs3Nzc7Pz9DQ0NLS0YXSDNHS0tLT09PU1dXV1oXXBtjX19jY14fWENfX1tbX19fY2dnZ2tva2tuE3Afd3t3e3+DhheIK4+Tk5OXl5ufn6ITpBujp6erq6oTrIezt7u/v8PHy8/P09PX29/j5+vr8/Pz9/f7+gICBgYKCgoWDG4SEhYWGh4iIiYqLjY+QkZKTlJWWl5eYmJmamkFvcHFyc3R1dnh5ent8fH5/gIGDhYiKjZGUmJ2ipqqvs7e7v8RkZmhpa21vcXN0dnd5e3x+gIKDhYeIiouLi4qJiIWHEoaFhIOCgYCAf39/fnt3c3BubYRuBG1sammEaAhnZmVjYmFhYYRiGWFhYF9eXVxbWllYWFdWVquqqaimpKOjoaCFn06enp2cnJuamZiXlpSSkI+OjIuJiIaEgoB+fXx7eXh2dXNycW9tamlpaGdlZGNiYWFgX19fYGBgYWJjY8XGY2PIyMnPeEovPCcyPSMoKy2LLoovDTAwLzAvLy8wMDAvMDCFL4gwCS8wMC8wLzAwL4UwKzEwMTExMjEwLiomQTkvSjhKZ7CmoZ6cmZiWlJKPi4mGhIKChIWGh4eIiIiMiQmIiIiJiYiIiImHiISHAoaFhoaChYaEB4OEg4ODgoKNg4aChYGFgAGBhYCFf4N+h38Ffn5/fn6Hf4uAhYEJgoKDg4KDg4SDiIQphYSEg4KBgoKCg4SFhoeJioyOj5CRkZKSlZeZVWpHLjQ+JCUqLTA2OR2EHoUdCxwcHRw5OTg4ODc3hjaFNYQ0hTOIMoMxhTADLzAwhC+FLocthywBK4QsiisBKoQrkSoCKSqPKQgoKSkpKCgpKYgokieOJgQlJiUlhiYHJSYmS0tLTIZLiUqCSYRKBElJSkqFSYlIBEdIR0eERodFEkRBPTo2Mi9VTkZ8bFuZi4aEg4iCDoOCgYGBgICAf4CBgoKChIMEgoOCgoWDAYSHgwuEhISDg4SFhYSFhYSGjYcKiImJiYqKi4uLjIWLhYwGjY6Ojo+PhJCVkYSSA5OTkoSTDpSTlJOTlJSVlZaWlZaWhZczmJmZmZqam5ubnJydnp6en5+foKChoqKjo6WmpqeoqaqqqqurrK2urq+wsbGyWVlaWltbhFwdXV1eXl9gYGFhYWJjZGRlZmdnaGlqamprbGxtbm8/QUFCQkNDRERFRkdISUlKS0xMTU5QUlNVV1haXF5gYWNlZ2lrNjg5Ojs8PT9AQUJDREVGR0hKS0xNTk9QUVJShVOGUhdQT05NS0pJSEVCPz06ODc3NzY1NDMyMoQxBDAvLi2JLIIrhCoBKYYoilCFT4ZOA01MTIdNBUxMS0tLhkoMSUhHRkZFRUVERENDhkKEQTZCQkNFRkdJSkxMmptOUKauv/G/ooG+ib72k6a0vsLCwsHCwsLDxMTExcbGyMjIx8fKysvLysqFyUPIyMjHx8fIyMjKycrKysnKy8nKysnMycvMys3Ly83MzszOzszQzci9qZb/1Kv8pKml38S2rqehnZmTjomFgX14dXR0hXUMdnZ3d3d4eHh3eHh5hXgBeYd4iHcFdnZ2d3eFdgN1dnaEdQN0dXWFdANzc3SEcw90dHRzc3Nyc3Jyc3JycnOFcgZzc3N0dHSGcwZycnN0c3OEdBlzc3Rzc3R1dXV0dXV2d3Z3d3Z2dnd3dnd3iHiAeXl6eXp6ent8fX5/gH+AgYGChYeJi46QkZKUlZeZmpyeoKOosLzVlf3Sk7bmiJuzxNXo94GFhoaFhYSEg4OCgYGA//36+fj4+Pf39fLx7+3s7Ozr6+vq6Obk4+Pi4eHh4N/e3dzb2tnY2NbV09HQ0M/Ozs7NzczLycjHx8bFxMMIwcHAv76+vLuFujK5uLi4tra1tLSzsrKxsK+ura2srKuqqqmpqKinp6alpKSko6Kjo6GgoKCenZ2dnJuam4SZFZiXlpeWlZaVlJOTkpKRkZCPjo6PjoaNT4yKioqJiIiHh4aGhoWEg4OCgoKBgoKBgYGAgID//f7+/v38/Pv6+Pj39/bz8/Py8fDw7+7u7e3s7Ovq6+no5+Xk5OLh4N/f3d7d3NvZ2dmE1xjW1NTRxbitnI2A3cKk/8GFr4J0b2xra2uEag9paWloaGdmZmVlZGVlZWSEZQhkZGNkZGVkZI9lBGZlZGSGZYRmiGWGZgZnZ2ZnZ2eJZgFnjmYIZWVkZGRlZGSIZYlmBGdnZmeFZoZnAWiHZ4JoiGcEaGdoaIhphmoOa2trbGxsbW5ub29vcHCFOIU5Bzo6Ozs8PT2IPoQ/hkCEQaSA24HKgAR/f4CAhH8HgIGCgoODg8yEB4ODg4KCgYD/f8t/BoCAgYKCgoeDjoT/g7mDwYIGgYGBgICA/3/Lf6eAAgIEAGaztbi5uru8v8HDxcjKzdDT1tnb3uHl6u7z9vj5+/2AgoSGiIuNkJOWmZyfpKiqrbCztrm8vsHDxcfKzc/P0dLS09LQzcnGxMK/vLaysrS0tbe2tLCwsbO0tre3ubu8u7q7ubaxrKiEp4elCaSko6GgnpybmoWZSpqZmZmYlZOQjouJh4WC/vn08Ozn4+Hf29jW09HPzMrHxcPBvru4tbOxr6yppqOhnpuYlpOSkI+NjIuKiYiIh4aFhIKCgYD///35hPgV+fn39vb19fPx7/J+QiITFhgNDg8PhRABD5IQgg+MEAEPhxADERARhBAEERAREIwRhBIaEQ8cGCslPWzQz87OzMvJyMbCvru7ubm7wMGFwAvBwcDAv76+v7++voS9Bb6+vb28hL2Gu4q6hrkKuLi3t7i4trW2t4W2hbUPtLS1tLS1tLS1tbW0tLW2hbUFtLW1tLSFtYS2C7W0s7K0s7KzsrO0hbWFtAuzs7O0tLO0tLOys4SyMLGysrGwsLCxsrO0tbW4ury9vr+/wMDBwsFmOh8jJRMUFRcXDAsMDAsMCwwLDAsLF4QWgxeGFgQVFRQVhBSNEwYSExMSExOZEo4ThhKwEwMUExOaFIQVAisVhisCLCuILIQtBywsLC0sLCyELQMuLi2FLgIvLoovATCEMYcyhzOINIYzJDIyMC8tKiknJkdDQDs4a2fKycjHx8fGxsbFxMLAvr28urq5uIS3HLi5u7y8vL29vb/AwcPExMTFxsjHxsfHx8bGx8eFyCrHx8fIysrJysrLzc3MzMzNzs3Nzs7P0NHPz9DS0tLT09TV1dXU1dbW19iE14PYh9aF1QXW1tXV1YTWItfX19jY2dnY2dna2tvc3Nzd3d3e3d7e39/f4OHi4+Tk5eaE5wTo6enphOpF6+vt7ezt7ezs7O3s6+vs7e7v8PLz9Pb4+vv9/4CAgYOEhYaHiImKi4yNjo+QkZGTk5SVl5ianJ6goqOkpaeqq62ur7Cya3x/goOFhoiKjI+RlJaZnaCjpqmtsba6vsPHys7R1WxucXR3en2BhIiMkZaanqGkp6qsr7K2ur3AwsbLz9LX2t3f4eLh4N7b19LMw7q1sbCvrqqknpiTkZGTlJOTkY+NjIuJiIaEg4KBgH9+hH9Xfn17enl4eHZ2dXRzc3Jwb25tbGtoZ2VjYWBfXl1ctrKwraupp6WjoJ2cmpeUkpCOjIqHhYKBfnx6eHZ1dHJycG9ubGtqaWhnZmVkY2NhYF9eXVxbW1qyhLEdsrW3ubu9vr6+v7/Aws9/UzYlMj4kJykpKSoqKimEKp8rASqHKwcsKyssKywrhSwCLSyFLSguLS4uLSsnIjovSDM/W6einZqYl5WUko6LiIaDgoOGh4aHiIiIiYmJhIgGiYmIiIeHiIgFh4iIh4eJhguFhYWGhYWFhISEg4WCCIGAgICBgYCAhoEDgIB/hIAFf3+Af3+Efg59fX5+fXx9fHx8fXx8fIZ9BHx7e3yFe458gn2FfjZ/f3+AgICBgYCAf4CAgYKCg4SFh4iJiouMjo+Slp5hQCk2QCYqLzU2HR0eHh0eHR0cHRwcODiEN4M2hDWDNIYzDTIxMTEwMTAxMDAvLy+LLoQthCwBK4QshyuGKokphyiPJ5UmAyUmJpglkCQESCRHR4RIBUdHR0ZGhEeERgpHRkdGRkZHRkdHhkYGRUVGRkZFhUaTRQNERUSERQlERURFRERDQ0OGQiJBPzw4NDFaU0xEPm5hrZqKhYSEg4OCgoGAf359fHt7e3p5hXoLe3t7fHx8fX1+fn+EgAGBh4IHg4ODhISEhYSEhoUGhoaHhoaHhogJiYqKiYmKi4yMhY0FjIyNjY2SjgSPjo6OhI8CkI+EkISRgpCFkYWSg5OFlAeVlZaWl5eYhZkJmpqam5ubnJychJ0Gnp6en5+hhKA2oaKipKWnp6iqq6yur1hYWVpbW1xdXV5fX2BhYWJiY2RkZWVmZ2hpamttbW5vcHFyc3V2d3l7fEFBQkNDREVGR0lKS01OUFJTVlhaXF9iZGdpa21vcjo8PT9BQkNFR0lLTU9SU1VWWFlbXV9hY2VnaGptbm9xcnNzdHV1dnZ2dXNwbWlmZGFeW1hUUUxHRENDQ0JBQD89Ozo5OTk4NzU1NDU2Njc2NTU0MzMzMjIyMTExMDCJLyAwMC8vLy4uLlxbWVlYV1ZVVFNSUlFRUFBPTk1NTEtKSoRJCEdHRkZGRUREhUOEQoVBPkJCQUBAP35/f4CCg4WIi4+TlpianqOrvP3m062GwvuUpKyvr7CysbCwsbOys7S0tbW2tba2tbW2t7a2t7e3hbZIt7a2tre2tbe1t7a2uLa5t7m4ubm5u7m7ury9vb+9wL/BwsLEw8XEwbWfieCv9pSEh9O/sqminZmUkIqDfnp1c3R2d3d2d3d3h3gKeXp5eXl4eXl6eoR5hHiJdwl2dnV1dnZ1dXWFdIRzDHJycnNycnJzcnJyc5ByBXNycnNzhnIFc3NzdHSGdYJ0h3WMdgR3d3h4hHldent8fX1+f4CBgoOEhoiLjY+PkZOUlZeYmZyhqrfix7eSyfSWq8Td8IOFh4aGhoSEgoKAgP77+vn49/X08vDv7u/u7uzr6+jl4+Lg4ODf3dza2NfW1NTU09HPzc3MhMtDysnHx8XFxMTDwb+/vr69vby7uri3tra2tbW0sbCvr6+urq6sq6qpqainp6akpKOioqKhoaCgn56dnZ2cm5qZmZiYmISWhJWElAuSkpKRkJCPjo2NjYSMhIsSioqJiomIiIeGhYWFhIKDg4KChYE7gP+A//38/Pz7+vn39vf39fPz8vHy8/Ty8fDv7+/u7+/u7ezr6urp6eno6ejo5+fm5uXk5OPj4uLh4N+E3kzc3Nra2dnZ2NjX2NfW1tXV1NPS0dDPzs3Ny8nFvLGklofz1buehdKk/beCcW5ta2tqaWdmZGNhX11dXFpaWVlZWltdXl9fYGBgYWJiiWOHZARlZGVkhGMLZGRkY2RkZWZmZWWHZgdnZ2dmZWVmh2WFZIxlBGZlZWaFZYJmh2WHZgRlZmVljWaDZZFmh2cBaIRnBWhoZ2dnhGiEaQxqamprazY1NjY2NzeEOIQ5hDoGOzs7PDw7hTwLPT09Pj4+Pz8/QECegO+BuICTfwaAgYKDg4PPhAaDg4KCgYD/f7l/BYCBgoKChYOMhP+DqIMCgoPdgoWBgoD/f8p/qYACAgQAgLi6vb/BxMfKzc/R09fa3eDk5+zv8/n9gIGDhIaIi42Qk5aYmp2hpqqvs7a6vcDFy9LX3eR2eHt+gYSGh4iIiYuMj5CRkZSXmpucmZWVlJKQjoqGhISGiIuNj4+PkI+OjYuHhYH79vT09vn48url4N7g4+bn5uXi3t3c2dbSzcrHZcO/u7ezr6unpKGcl5KOioeEgf/69fDr5N3Y1NLQzcrHxMG+u7i0sa6rqaakoqCenZybmJaTkZCQjYyLiomIh4aFhIOCgYCA//78+vj29fTy8/Hw7u3s6+vr6ujn6HlBIhMVCwwNjA4BD6MOBA8ODw6HDwYQDxAPEA+JECoREBEQDxoXKERy2NTQz83LysjFwr+7ubm6vL6/wMHBwL+/v769vb6+vb2HvAK9vIS7B7q7ubm6ubiGuQ24uLm4uLi2tre3tra2hbUEtrW1tYW0jLOEsguzs7O0srKzsrOys4W0B7OzsrKxsbKGsYWwAbGIsi6xsrKxsbCvrq2trq+wsrO0tre4uru8vr68XzYeIhITFBYZDQ0MDQwNCwwWFxcYhBcHFhYWFRYVFoQVhBQBE4UUhxMHEhISExITE4QSghOEEgsTExITEhMTEhMTE4gSBRMTEhMThBIJExISEhMTExIThxKCE4sSBBMSEhOJEgETiBICExKWE4snhCgFJygnKCiSKYUqASuIKoUrCCorKywsLCsrhCyGLYUuii8BLogvPi4uLi0sKigmJEM/PDhsZsbFxsbHx8fGxcPCwL+8urm4trSzsbCxsbKzs7S1tre4uLi5urq7vb6+v8HCwcLChMMGxMTFxcbHhcYHx8fGxsbFxoTHBsbGx8bGxoXIL8rMzc7P0NHS09TU09PV1dbW1tXW1tbX19bW1dXU1dTV1NPU1NPU09TT09PS0tLThNKG0xTU1NXX19jY2dnb29vc3dzc3dzc3IbdhN6F34TeA93b2ofYQdnZ29zc3d/g4uXo6err7O3u8PHz9Pb3+Pj5+fn7/P3/gIKCg4SFhoaHiImKi42QkpSWl5mbnZ6go6Wnqq2vsrW3Q31/goSGiYuOkZWZnaKnrK+ztrrAxszSa25xc3Z6foOIjZKWmZ2iqa60uLzBxcnO1d3l7vmChoqOkpaZm56foKKkpqeEqICpqailoJuVkY2Hg315dnNxcXBwcG9ubWxramhmYsC/wMLExcLAvbu4t7WzsrGxr66sqqajoJyZlpOQjIiEgX16d3NxbmtoZmRiYF23s7CsqaekoZ6al5WSkI2LiIeFg4KAf317eXh3dXRzcnBubWtqaWhnZ2ZmZWRjYmJgX11cWx62tLKwrqyrqaqsrrCytbe4uLq7vL3AbUo0JjQgJCWKJqQnBignKCcoJ4UoDCkoKikqKSopKyorKogrISciOC0/UGOooZ2bmJaUko+LiISDg4SGh4iIiYmJiIiJiYmIhokCiImEiISHFIaGhoeGhoaFhYaFhYWDgoODgoKBhIABf4SAAX+JfoN9hH6FfQ1+fn59fX59fn19fX5+hH2EfAF9hnyFewV8fHx7e4R8BHt8fHyGey98fn+BgoOEhYeIiYqMjpFUPSo4ISguNDoeHx4fHR4cHTg5ODk4ODc3NjY2NTU0NYQ0BDMzMzKFMQYwMDAvLy+ELgItLocthCyGK4YqiSmFKIYnAyYmJ4wmhiUDJCQlkSSeIwMiI0WGRgRFRkVGk0UIRERDQ0RDRESPQ4JCiUMHQkJCQ0NDQopDBEJDQ0OHQkFBQkJBQkFBQEBAPz88OTUwWVFIQHNiq5aKiIeHhYWDgoB/fnx7enl4d3d2dXV1dnZ3d3h5ent7e3x9fX1+fn5/gIR/AYCEfweAgX+AgYCAhoEOgIGAgYGCgYKBgYGCgoKGgwSEhYSFhYYTh4aGhoeIh4iHh4iIiYqKiYqKioSLhIwEjY6OjYWOCo+Oj4+PkI+Qj4+GkIaRhpIFkZKSkpGHkgeTk5OSkpKRhJIEkY+Pj4SQQpGRkZKTk5SWl5iZm5ycnJ2dnqCfoKCgoaGio6Smp6ipq1ZXV1hYWVpbXF1eX2BhYmNkZWVnaGlqa21ub3FzdHZ3eltAQUNERUZGR0lLTU9SVFdZW15hZGhscDo8Pj9BREhKTU9RU1VWWVxeYGJjZWdpa29ydXh7P0FCREVGR0hJSkpLTExMS0tKSklISEdEQj88Ojc1MzAuLCoqKSgmhCUUJCQjIiFCQD8+Pj9AQUE/Pj07OzuGPAw7Ojo5OTg5ODc2NTaENSs0MzIxMTEwLy5bWVhXVlVUUlFQUE9OTU1NTExLSklISEdGRkVFREREQ0NDhEKFQYdAKT8/P35+fn18e3p6fYKFiIuQk5SWmJyjsNGnrKGIzISaoqOkpKWlpqWlhKcCqaeIqAWpqKioqYWoBqeop6moqYSoPampqqiqqqyprKusq6ysrK6tr66xsLKytbW3t7i5ubq6u7u1ooncos/Mo93AsqmhnJiTjoaAenZ1dHR1dnaEdwF4hHkrenp6eXl6enl6enp5eXh4eHd4d3d2d3Z1dnZ3dnZ2dXV2dXZ1dHN0dHNzc4VyB3NzcnNycnGPciBxcnJyc3N0c3N0c3R0dXV2dnZ3d3Z2dnd2d3d3eHd3eIV3BXh5eHl5hXqAe3x8fn+Bg4WIiouNjo6Oj5CSk5WZoKi+jZiGzoOjvdnxgoWEhYOEgYH///v79vX08vLx8e7u7Ovp6Ofm4+Pj4uDe3NrY19XU09LRz87KysrLysnIxsTEw8LCwL++vbu7u7q5uLe1s7KysrGxr6+uraysrKqpqKempqWkpKOjoqIxoaCgn5+fnp6dm5ubmZmYl5eVlpaVlJSTk5KSkZCQkI+Pj46Ojo2MjIuKiYqJiIiJiISHSoaHhoaGhYSDhIOCgoKBgIH////+/v79/P38+vn5+Pf29vT08/Hy8e/u7+zt7ezr6uno5+fm5uXl5OXl5eTi4uHh4ODf393c3dzbhdwX29rb2tvb2tra2dnY2dnY19bW1NXV1dSE0y7S0dDQ0M7PzcvKysnHwrqrm4ryz62K3KXxr4l8eHVzcW5raWdkYF5cWlhXVlVUhFMSVFRVVldYWVpbXF1dXl9fYGFhiGAFYWJhYmKIYQlgYWFhYmJiY2KFY4VkAWWFZoVlAWSGZYJkhWWEZIVlBmRlZWVmZodlAWSMZQNmZWWGZgpnZmZmZWRkZWVljGQBZYRkgmOEYodhB2JiYmNjY2SEZQdkZGVlZGVkhWOCYoRjATGHMoQzCTQ0NTU2Nzc4OIQ5CDo7PD09Pj9Al4CdgbCCroG1gJZ/BYCBgoOD0oQFg4OCgYD/f6p/BICBgoKFg4iE/4Odg+yChIGCgP9/33+hgAICBACAsra4u7/Dx8vP0tfc4OXs8vb5/YCDh4qLjY+Pj5CSk5WXm56hpKeoq6+zt7zBw8jOaWxwc3h8gYaPl5+lqa61vMHExsrQ1dnb293f5u/2/ID+9ezn6Ovv8O7r6ejo6eji39/g4uPk5eXi4N7c2dTRz8rDvLawqKGdmpeSjIeEgf5R+/jv5t/X0czGv7mzrKahnpqWko6MiYeFg4H99/Ht6ufk4d7b19PQzMnGwr67ube1s7GurKqnpaOioJ6dnJqYmJeWlJKRj4+OjYyLioiIh4aFhIMSgoGBgYCA/Pn49fTw8D8iExULkA0DDAwNjQwCDQyFDQ8MDQwNDA0MDQwNDA0NDQyHDYwOhw8aEA8OGBUjO9PR0M7LysjGw8C9u7m4ur2/v7+EwAm/v7++vr69vr6GvQy8vLu7u7q7urq7u7uNuRK4ubi5t7e3tra2t7W1tLSzs7OKsoSxBbKxsbGwjLEEsrGxsoexhLApr6+vrq+vr66vr66trKurrKysrq+wsbG0tbe6vL28YDUfJBMWFgwMDAuFDIYXBRYWFRYWhRUHFBUUFRQTFIYTARSLEwMSExKNEwMSExKJE5USAROIEoMRhBKCEaASAiUShiUBJooljyYGJycnJicmhycDKCgnhSgGJycnKCcniiiMKQUqKSoqKoQrBioqKysrKoQrCSwrKyssKywrLIQrLioqKSgmIyE/Ozg1ZWTIyMrKysnHxMC+vry7urm4trSzs7a3t7i4uLm7u7y8vb2FvgHAhMEJwMDBwcHCwsLDhMKCwYfDA8XGxonFA8bFxYjGHMfIycrKysvNztDR0NDQ0dHR0tLT1NTV1NTT09SF04bSAdGF0j/R0dLR0dLR0dHS0dLT09LS09LT1NXU1dXW19fX2Nra2dnZ2NfX1tXV1dPT0tLS0M7My8rJysvMzs7P0NLU1tiE2UHa2dra2tvc29zd3+Dg4uXn6Ors7/Dz8/X29/n6+/3+gICBgYGCg4OEhIWGhoiJiouMjpCRk5WXmZudn6Gkp6qtr4BydHZ4e32BhIiLj5SZn6atsra7YWRpbXF1eHt/g4mOkpWZnqOpsLa8xM3U2+Lp8vqAhImOk5iepKuxtr3Cx8zQ1NbY2t3f4ODg4uPl5+jpc97UzMS/vr28uba1trW0saypp6WmpqioqKWjoqCem5iUkIuHg4B9eXRwbGllY2FetWeuqaKdmpaRjYqEgX57eHVzcW9tbGpoZmRiYb+7t7OwrKqnpaOgnZuYlZOQjoyLioiGhIOBf318enl3dnRzcnFwbm5ta2ppaWdmZWNiYWFgX11cW1paWltdX2Bhw8TFxcbI00QyJzYghCMMJCMkJCMkIyQjJCMkjiMHJCMjIyQjJIUjCCQjJCMkIyQjhCQEJSQlJIQlhCaEJ4QohCkaKioqJyI3KTZAsqOcmJSUk5GPjIeEgoKDhIWFhhCHhoeGhoaHhoeHh4aGhYaGhYUHhIWEhIWFhYWEBIODhISEgw2CgoGBf3+Af35+f31+hn2HfAV9fX18fYd8g32FfAJ9fIV9hHyJe4J6hXsEfHt7e4R6I3t8fX5/f4GCg4WIipBWRTJCJzA3Hx8fHh8eHh0dOTk4ODc3hDYGNTU0MzMzhDIEMTAwMIYvgy6GLQQsLCsshiuGKocphiiLJ4kmhiWFJAIjJIwjkiICISKFIQQiIkMihESGQ4dCAkNChkMLQkNDQkFCQUJBQUGJQgJBQoVBA0BBQIlBhEAPQUFAQUFAQUBBQUBBQUBAhkECQEGGQAdBQUFCQkFAhUE4QEBAPz8/Pj06NjFYTkU8ZVeYjouKiYeGg4B+fXx7enl4d3Z2dnh5eXp6e3t8fHx9fX1+fX19fn6PfwGAhH8BgIV/hICFf4J+hH8DgIB/hoANgYGCgYGCg4SEhIOCgoSDHoSEhYWGhYaGh4eGhoeIiIiJiomKi4yMjY2OjY6Oj4WOhY8EkI+PkIaPho4Hj4+Ojo6NjYSMCYuLi4qKiomHh4SGT4eHiImKi4yMjY6PkJCRkZCQj4+Pjo6Oj4+QkJGSk5SVlpiYmpqcnJ2foaGjpFNUVVZWV1dYWVpbXF1eXl9fYGFiY2NkZWVmZ2hpamxub3GAPj9AQUJDREVGSElMTlBTVVdaXTAyNDY4Ojw+QURHSk1PU1ZYW15hY2Voa29zdXh7P0FCREZHSUtNTlBSVFVXWFlaWltcXV1bWVlZWllZWCtVUk1IREFAPjw5NzY2NTU0MzAuLSwtLCsqKysrKikoKCkpKSgnJiUlJSQjIiEgHz0FOzo5NzaENYQ0MDMyMTEwMC8uLi0tLCxYV1ZWVVVTU1JRUE5OTU1MTExLS0pJSEhIR0dGRkZFRURERIZDg0KFQYNAhz8hQEJERkhKl5ueo6y9/ouWjNiHlZiZmZubm5ycnJ2dnJ2eiZ0InJydnJycnZyFnUqbnZydnJ2cnp2fnp+eoJ+goKGgoqGioqOkpqenqKmqq6ysra6vsLGysrO0saKHz5GiiPvMuaqgnJiUjod+eHRzdHV2d3d4eHl5eYR6Cnt6enp7enp5enmFeAd3eHd3d3h4hncDdnZ3hXYedXV2dHR1dXR0dXR1dXV0dHV0dHR1dXR0dHV1dXR0hnWHdgJ3doR3Bnh3eHd3d4V4AXmFehB7fX1+fn5/f4GCg4WIjJCShZN7lJeaoq/HpL2o96LG5oCGh4WEg4KBgP/9+/r49vTy8O3s6ejm5uXj4uHf3NnZ19bV0tDOzMzMy8rKycfFw8HCv8G+vry7ubq4t7S0s7GxsK+urauqqamop6alpKSko6KjoqKhoaGgn5+enJ2cm5uampqZmJeWlZSUk5KShJEOkI+Pjo2MjIuKiYmIiYiFhwyGhoaFhIODgoKBgYGEgF3/gP/+/v77+vr59/j39fXz8/Tz8/Hz8/Hw8PDu7e7s6+7r6Orp5+nn5ufn5ubm5OPk4uDg397d3Nvc3Nra29rb29rZ2NfW19bV1dTT1NPV1dTV1dXU1tXU1dXU09OF0gHRhtJA0dHQ0NDPzc3LysjGwryxoIzyyqOAuoOyjIF8eHRxbGhlY2BdWlhWVFJSUlNUVVZXV1hZWlpbXFxdXl1eXl9gX49gBF9gYGGFYIxhiWIKYWFhYmJjYmJjY4VkBGNkY2OJZIJjh2IDY2Jih2OJYohjBGJjY2SIYwhkZGRjY2JiYolhgmCGXwReX19fhF4mX19gYF9fXl5dXVxcW1pbW1paWVlaWlpbXFxdXV5eXl9gX2BgMDCGMRAyMjIzMzM0NDQ1NTY2Nzg4hDkGOjs8PT0+k4CdgZ+CAYOvgpuBwYCHfwSBgoOD1IQEg4OCgf9/nn8HgIGCgoODg4mE/4ONgwKCg/iChIGCgP9/6H+igAICBACArrG1ur/Cx83T2Nvg5ejr8PX4+v6ChIaHiImKi42Qk5aZnKCkqK6yuL7FzdTc5vD0fYKGio6Tmp2iqbG4wMzc8YCJkJeepauxuL7Eztvo8/yCh4+YoqmsqaalpKKbko6QkZORj42Kg/rs287Fvbeup5+Yk46Igvjq4drTyL+3r6hxoJmVkIyJhIH47OLZ0szFwLqyq6ahnpyal5KOi4mGhIH99vDq5eHd2tbRzcvIxcK/vLq3tbSyr6yqp6WjoJ6dm5mXlJOTkpGQj46NjY2LioqJh4aFg4KBgP/7+/v6+/z9/fr5+fV+QyUVFgsLDAsMCwyOCxQKCwoKCwoLCwoLCgsKCwoLCgsKC4QKAgsKhgsGDAsMCwwLiQyJDYgOFxgUIW3Pzs3KxsTCwr+6t7S0t7q7urq7h7wCu7qFuwi6urq5t7i5uIS3graItwW4uLe3toe1B7SzsrKxsbGMsASxsbCwhrEDsLGxhbAEsbGxsoSxhLAPsa+vsLCvr6+urq2trayqhKkVq6uur7Gztbi5urdhHCETFhcNDAwMhgsGFhcWFxYWhRUNExUUFRMUExQUFBMTFJQTARKJEw8SExMTEhMSExITEhITEhOOEooRihKMEQUSERESEYUSAxESEocjlSQFJSQlJCSGJQEkjCUGJiUlJiUlhiaCJ40mDCcmJyYmJicmJiYnJ40ojikBKocpJygnJiQiHzs4NWjNy8nKysvKyMXCwL27uba2tre4uLq6u7y9v8DBwYTCEsPExMTDxMPExMXGxcXFxsbFxYTEC8PCw8PCw8LCw8LDhMIGxMPCwsPBhMImw8PDxMbHxsfHx8bGxsfHx8bGx8bGx8bHyMnKy8zMzc/Ozc7Nzs+E0APS09OF0gvU1NPT1NTT1dTU04XUhNM61NPT0tLT0tHR0tLT0tLR0dDP0NHQ0NDPz87My8rJyMXEwsPDw8TFx8rLzc/R0tLT09PS0tHR0dLT04TUP9bY2Nnb3N3e4eLj5OXo6uzu8vX3+Pn5+fv9//+AgIGCgoODhISGhoeIiYmLjI6QkpOTlJWWl5ianJ6ho6aoq4B1d3l7fX6Bg4eKjZKWmqCmq7C2vWJlaGptb3J1eHyAhIeLkJWboqmyusTN1d3l7feBh4yQlJqjq7K4vcPI0dzpen+EiI6UmZ2ipqqutcDJ0WprbW9zeHt5dnNycWxmYmFhYGBfXlxYp52Vj4qEf3l0b2tmYVxZraagmpSNiIN9dmxxbGlmYl5aVqafmpWQjIiFgX16eHVycG5samhmZGJgXrm0sK2qpqOhnpyZlpORj42LiYeFg4F/fnx6eXd2dHNycXBvbm1tbGtqaWhnZ2ZlZWRiYV9eXVxbtLO2ub3AwcHCw8TGxnFTQzI8ICCSIYQgDiEhICAhICAgISAhICAfhCCEIQEghCEEIiEiIYkihCMHJCQkJSUmJoQnhCgYJyI3KDVwqJ+bl5STkY+LhoOAgIKEhYSEhIWEhIqDDIKBgoODgoKBgoGBgoiBA4CAf4h+hH2GfIh7hXoEe3t7fIp7BXx8fH19i3wBfYR8LH19fHx7enp7fH1/foCAgYKDhIeKjmEpPSYvNh8gICAfHx4eHR45Ojg5ODg2hDUNNDUzMzEyMDEwMC8vL4Qugi2FLIUrhioGKSkoKCkphCiGJwcmJyYmJicniCYDJSYmhSUJJCQkIyQjJCMkhSOSIgQhISIihyGHQgZBQkJCQUKQQQJAQYRACUFAQEBBQUBBQYZADD9AQD9AQD9AQD9AQIs/BD4+Pj+HPgE9hD4BPYk+hT+CPoY/AT6IP4c+JTw5NC9TRzxmqJSNi4uJiIaDgX98enl4dnd4eHl6e3x8fX5+f3+EgIKBhYACgYCEgYWACH9/f4B+fn9/h34FfX59fX6EfQ58fX19fn5+f4CBgYCAgIV/AoCBhYAigYCAgYGCgYKCgoODgoODg4SEg4SFhYaGhYaGh4eHiIiJioSLDY2MjIyNjYyLjIyMjY2EjBCLi4uMjIuLioqKiYiJiIiIhIcahoWDgoF/fn19fn9/gIKDhYaHiImJiYqJiYmFiAGHhIg/iYuLjIyOjo+QkZKSk5SVlpeZmpydnp6foKKjpFJTVFVWVldXWVpaW1xeXl9gYmNkZWVmZ2hpamtsbW5vcHJ0gD9AQUJDRERFRkZHSUpLTE1PUFFSKSorLC4wMjQ2OTs9P0FERklMTlBTVlhaW15hZDM1Nzo8PkFDRkhKTU9RUlYsLi4vLzAyMzQ2OTs/RUlMJiQiICAfHx8eHh4dGxoaGRgXFxgYGBctKygnJSMjIiEgHx4dGxs1NDIxLywrKScnOCYlJCIiISEgPz08Ojg3NjU1NDMyMTExMDAvLi4tLCwsWFhYV1ZVVFNRUU9OTk1MS0pJSUhIR0dGhEcJRkVFRUREQ0NDiEIEQUFAQIY/Gz59foGFi5KTlJmdpbHMrtPkwf2Nj5GRkpKTkoSTBJSTlJSIk4OUhJMLkpOSk5KTkpOSk5KFkz2Uk5STlZSWlZeXmZiampucnJ6enp+hoqOkpaanqKmqqqyurq2ljtiQmdvlwrGmn5uXk4qAd3JydHZ3dnZ3iHgBd4l4C3d3eHh3d3Z3dnZ3hHYBdYV2B3V0dHR1dHSGdQx0dHV1dHR0dXV1dHSHdYV2iXeCeIR5Cnp6ent7e3x8fH2EfgF/hYGAgoWKkJOUlZeXmZueo6q44e2C25jH8YeLiomHhoWEgoH+/vn59/Xy8O/s6+jq5eXh4d3c2dfU0tLP0M3NysnGxsPDwsHAv727urq4trS0sbGvsK+vrKypqqinpaWjo6KjoqGgn5+enZ2dnJubm5qamJiWlpWUk5KSkZGQkI+Pjo4Jjo2Mi4uKiYmJhIiEhzWGhYWEhIODg4KCgYGAgID+/v38+/z7+vv6+fj39fT08vLx8PDx7+7u7e3v7evr6ujp6Ofo54XlCePk5OPi4uDf3oTdENzd3Nzb2trZ2dnX19bV1dSE0wHUhNMJ0tHQz8/OzczNhc4Bz4XODM3Ozs3Nzs3NzM3My4jMN8vLy8rIyMS+sZ2G3a+EvPuohX98eHRvaWVhXFhVU1FRUlNTVFZXWFlaXFxdXl9fX2BgYWFhYmKEY4tihGGFYAZhYWFiYWGFYgFhiWIIYWFhYGBfX16EXwFeh18BYIRhBWJiYWFihGGEYAphYWBgYGFgYGFgh2EBYIVhhmAHX15fX19eXoRfiV4BX4ReBF1dXVyFW4VcHl1dXVxcXVxcW1paWFdXVlZVVlZVVVZXV1hYWVlaWoRbhFyDXYReDV9fYF8wMDAxMTEyMjKEM4Q0EjU1NjY2Nzg4OTk6Ozw8PT0+PpSAnIGQgpCDl4SPg5KCmIG4gI1/BYCBgoOD1YQEg4OCgP9/k38GgIKCg4ODioT/gwGD/4KFggSBgYGA/3/xf6OAAgIEAICtsbW5vsTK0Nbb4ebq7/L4/ICChIaIiYuNkJOWmZ+jqKyxt77EytHY4ev3gYWKj5OZoKiyu8fU4e/9ho6Yo623xtvwgo6gts7j+IaTqsTX5/2QqcTroOKTuuX58MmJxJuE3K+R+NW+r56O/NvFtqqajIHv3cq7rKCVioL36t3Pw1a4rqWfmZOOiIL17OTc1c/KxL65s66oo56al5SQjIiEgfn07uXd1c/JxL64tbKwraupp6SjoZ+enZuamZiWlZORkI6OjYuJh4aFhISDgoKCgPz7+fj4+IX3Ffb08vT19Pb29/f49u9+RiYUCgsKC5EKCwkKCQoJCQoJCgkKhgkDCgkKjQmNCoULhQyHDR0ODg4ZFiM4ysnIxsTBv765tLCwtLe5urq5ubm4t4S2hLUPtLOzs7Kzs7Oys7Oys7KyhLSCs4WyhrOEsoWxg7CErxCurq+vrq+urq2sra2trq2thq6Dr4awha8csK+ur6+vrq6rqaioqqusrK6vsbS2uFsyHhEVF4QMhAsECgoKFIQVBRQVExQThBQHExQTFBMTFIQThRKTEwUSEhMSE4USghOHEgYTEhISERKHEYYShhEBEo4RARKEEYUjASSII4IihSMBJIUjAyQjI5MkASORJAQlJCQkhCUKJCUlJSYmJSYmJoUlAyYmJYwmAycnJoYnASaHJwIoJ44oKycnJiUjIR44NWVhxMbHx8fGxcLAvbq5t7e4ury9vr/AwMHCw8TFxcXGxseEyAfHx8jIyMnJh8gPx8fHxsbFxsXHx8bHxsTFh8QBw4bEAsLDhsQBxYfGA8XGxYTGEsXFxcPExcTExcXFxsfHycrKy4TNCs7R0NDR0dLS0tOE1AfT1NTU1dXVhNMx0tPS0c/Pz87NzczLysrJyMjIx8fGxcTDwsHBwMC+vbq6ubq9vsDEx8jJysrKy8vKyoTJUMrLysrKzM3Oz9DR0dLT1NTV1tfX1tfY2dnZ2tvc39/e3+Hi5efp7O3u7u/y8vT1+fr9/v+AgYGChISFhoiJi42Oj4+QkZKTlJaYmpyeoqWpgHp9f4KEhoqNkZWYm6Cmq7C1XF5fYWJjZWdpa25xdXh9gIWLkZiepayyu8Rmam9ydnyCipGZpKy2wMtrcXiAiZKerr1mb3mHl6e3Yml4iZWfrGBufJRmk2F4kZ2Wfld7YVSNcV2hi3xxZlyllIl+dGphWqaZjYJ4cWpkX7WropqSbYqEfHZwamZhXrSuqKGalZCMh4J9eXRwbWpoZWFeWlhWqaainZqXlJGPjIuJh4WDgH58eXh3dXVzcnBvbmxramloZ2VkY2NiYWFgYF9gYGBfvr++u7i3t7Sysa+trKqusre5u7y8vb6/bVdHMx2UH5MeCR8eHx4eHx4fHoQfCB4fHyAfICAghyGFIgUjIyMkJIUlGSYmJyU/Lz0+qZ+ZlpSSkY6HgX5/gYOFhYWEhIWDhYKDgYaAh3+EfoZ9AXyEfYR8Dnt7fHt7fHt7enp7e3p6hHsEent6eo97j3wYfX19fn19fH1+gICBgoOEiIuPTjs0JTA5hCAhHx8eHh4dHTk5ODk3Nzc1NjQ1NDMzMTEwMC8vLy4uLS4thCwJKywrKyoqKikqhCkGKCkpKCgohCcBKIQnAiYnhCaKJYskhSONIoohgkKEQQFCh0GFQIU/BEBAP0CEP5M+Bz0+Pj09Pj2HPog9iD6PPQI8PY08Dz09PD09Pj49PT49Pj0+PYc+hz0lPDs4NC1OQm9aloyLiomIhYKBfnt5dnZ2eHl7e3x8fX5+f4CAgYWCgoOJggaBgYCBgYGHgAx/f4B/f39+fn59fn6JfQV+fX1+f4x+CH1/fn9+f359hX6FfwOAgIGEghSEhIOEhYaEhYWEhYWGhoaHh4eGhoWHAYaEhwiIh4eGh4eGhoaFhYQdg4OCgoGBgYB/fXt4d3Z2eHl5fH6AgYKCg4ODgoKFgQiCgYKCg4SFhoSHQ4iJiYmKi4yMjI6Pj5CQkZGTlJSUlZaXmJiZm5ydnp+goaKkpaeoqVVWWFhaW1xdXl9gYWNkZWZoaWpsbm9wcnN1d3gNQEJDQ0NEREVFRUZISYRKgCUlJicoKSoqKy0uLy8xMzQ1Nzk6PD5AQkRGJCUmJykqLS8wMjM2OTw/ICIkJyswNz5DIyQlJyovMxscHiEkJysYGx4iFh8TFRcVFRIOFRIRHRcVJyMeHBkXKiYkIiEeGxkuLCknJCIhHxw3NTQyMS8tKyoqKCYlJEZEQj48Ozk4Jjc2NDMxLy4uLCwrLCsrLFZVVFNSUVBPTk5OTUxLSklIRkVFREREhEOEQgVBQUFAQYdAIj8/QEA/f3+Af35+fn17e3p5eHh9g4qQk5aanqfDqtv1zoCEiQWKiouLioqLBIqJiomKik2JiomJiomLiouLioyLjIuMjI2NjY6Nj46Pj5CRkZOTlZWWl5eXmJmanJ2eoKGipKSlp6mpnPepuoLvyLOnnpiTjYR6dHFyc3R1dXZ2d4R4DXd4d3d4eHh3d3d2dnaGdYV0CHNzc3JzcnJyinMFcnJzc3OFcgNzc3KEc4R0GnN0dHR1dXZ2dnd4eHl6enp7fH19fn5+f35/hIBggoKEhYeJjJCTlZaXl5ianqStwYm0w5TJ+o2RkI+NjImHhoOC/v75+fb19PDv6ejm5OTf4Nzc2NbU0NDMzcjIxsXEwcK/vry6uba1srKysa+vrKuqqainpqWlpKSjo6GghJ9Enp2bm5yamZaVlZWUk5KRkpKSkZGPj46OjIyLi4qKiYmIiIeGhoaFhYSEg4ODgoGAgP/+/v39/fz7+/r6+Pj29PTy8vGG8Arv7u7s6+vp5+fmheUO5OXk4+Ti4uHf3+Dd3t2E2x3c29va2tjY19bV1dPT1NTU1dXV09LT0tDQz8/OzofNDs7OzMzMy8vKysnJysrJhcoCyciEyQLIyYbIOsnIyMfIyMjHxsfGxcTCvrKehdWd3I+yiYB7d3RvaWVfWVVQTk5RU1VWV1dYWlpbXF1eX2BhYWJjY2OFZINli2QUY2RjY2RjY2NiYmJhYmJiYWFiYmKEYQFghGGCYIVfDV5eXl1dXl5fXl9eXV6FXQZcXVxdXV2FXgpfX15eXl9eXV1ciF2CXIRdAlxbi1yEWwZaWlpZWVqGWRBYWFhZWFdXVVVVVldXV1hZhloGWVlYV1ZVhFQFU1NUVFSEVQZWVldXWFiEWYZagluFXIVdg16EXwdgYGFhYjEyhDMWNDQ1NTU2NjY3Nzg5OTo7PDw9Pj8/P5GAmoGPgomDh4SHhYSGgoeHiAaHh4eGhoaGhYiEiYOOgpeBsICYfwSAgYKD14QEg4OCgf9/iX8GgIGCg4ODi4Txg/+CkoIEgYGAgP9//3+cgAICBACAsbW4vMLHzdLX3eLl6/H2+4CChYiLjY+RlJaZnaCkqK20usHI0dvl7viBh42Um6SvusTP3u+AipWisMDT6oGPn7LO+ZOqyvef3oq7l5OKjbiFpZKBk+mu7/T9j5+/hIzJ1pDXo++i98m1ofjUu6eQ8tG3o5KC6tbEtKaZjoLw3MpwvK2gl4+JhP/06eHX0cvGwbizrqqkn5uXk4+MiYSB//rz7efh2tPPy8nFw7+6tK+rpp+al5WTk5OSkZCQkI+Ojo2Mi4uKiYeGhIODgoKBgYGA//78+fbz8vLz8vHw8O7u7u3u7/Dx8fHw7uyHKRYVCqMJAwgJCIQJAQiLCYIIiQmICoYLgwyFDRAYFSJnxsbDwL+9vLixr7C0hLYDt7a2hLWEtAOzs7KEs4SygrGEsgSxsLCwhLGDsoWxCbCwsbCwr6+wr4WuCq2tra6tra6sra2Erguvrq6trq6tra6uroStgq6FrRasrK2rqaqqqqytsLGys7a3ubgwHRIVhgyCC4YKDhQVFBUUFBQTFBMTExIThxIBE4YSDBMSExMSEhMSExITEoUTARKEE5USjhGDEo0RAyMiI4QiAiMiiiMLIiMiIyMiIiMiIiKGIwQiIyMinSOCJIQjASSFIwMkIyOEJAQjJCQkiiUBJoklhCaDJZAmBScmJicmiScKJiUkIR44M2HJyITKI8bBvrq4uLe5u76/wMLCw8XHx8nJycrLysrKy8vJycnIyMfIhskKysrLysnJycjJyITHEcjIx8bFxsXFxMXFxsbFxcXEhsUOxsbGxcbGxcTFxMPDxMWEwwnExMTDxMTDxMSFwwfCwsPFxcXGhsciyMjJyszMztDOzs7Pzs7Nz9DOzs7MzMzLy8rJx8fIx8fHxoTFH8TFxcTFxMPBwcDAvru4t7W1tbi7vsLDxMXGxsXEwsKFwRnCw8TGxsjJysrLzMzMzc3Nzs7Q0dLS09TUhdU41tfX19jY2drb3N3f4eLi4uXm6Ors7/H09/r9gIGChIWGh4mKi4yNjo+QkZKTlZWWl5ianJ6jqa2AfICChYiLjpCSlZibn6Omq1daXF5gY2Voa29zdnl8gISKj5SYnqWttLxiZ2twdn2EjpefqrRfZW13gIyZqV1ncX2Oq2RyhZ1jjVh4ZGBUWXlYbGBNYbKGuqe0doOSX1p9iFuJaZtrpoh7baWOfXBho418cGZcqJuOgnhuZ1+ypJdIjYaAeXNsZ8a+t7Gpo5yWkIqFgX14dG9rZ2NfXVpXqKKfm5eUko+NjIyLiYiGg4GAf316eXd2dXRzcXBvbWxramloZmZlZWRjhWGEYB6/v769u7m4tbKwrauqqKWjpauvsLKytLW3vH44LTiVHQEchB0CHB2EHAEdhhyEHQEcjx2EHgYfHx8gICCEIQciIiMjIyQkhCUfJicmJj4sNGajmpaUko6Khn99foGDhISEhYSFhIOEg4WCA4GAgYSAC39/fn19fn59fHx6jnuFegN5enqGeQF4hnkBeIZ5CXp5enl6enl6eol7QXx7e3x8fH18enp6fH6AgYGCg4WHiZAxLSUyHiEhICAgHx8eHR0cHRw4ODc3NjU1NDQzMjMxMTAwLy4uLS0tLC0shysFKioqKSmEKAMnJyiEJ4smBSUlJCQliiSHI4ciBCEiIiKHIQggIUJCQkFBQoZBiUCNPwE+jT2JPAE9hDwBPZE8hDsCPDuRPIM7iDyHO5Q8AT2EPDA7PDw7Ozk0LEo9YZmLiomJiYaBfXp4dnV2eHp7fH19fn+AgIGBgYKCg4ODhISDg4OEgoiDA4KDgoWBhYAGf359fX1+hH0Gfn19fn59hn4Gf39/fn5/hX4GfX5+fXx9hHwBe4R8AX2HfCJ9fn5/f3+AgICBgIGAgYGBgIGCgYGCgoKBgYKDgoODgYGBhICEf4SAhH8BgIV/GH59fXx7eHVycHFydXd5fH19fn9+fn18fIV7Fnx9fn+AgIGBgoODg4SFhYaHiIiIiYmEijyLjIyMjY2Ojo+PkZKSk5SVlpeYmZucnZ6goqOlpqmrVldYWVpbXV5fYGFiY2VmZ2hqbG1ucHJzdHV3eXqAP0BAQUFBQkNEREVGSEpLSyYmJygpKiorLC4vMDEyMzQ2ODo7PT5AQkQiIyQlJygpKy0vMTUcHR8hJCgsLxkbHiEkKhgaHSEUHhIXDxIOCxENEREOGTQkKic7KysnFhEWGxIaFB8ZJiAdGigkIB0ZKiYiIB0aLywoJiMgHx03NTMpMS8tKyopKE5KRkNBPzw6ODc1NDIxLy4tLCoqKikpUlFPTEtLS0pKSUmESA1HRkZGR0dHRkZFRENDhkKKQYdAH4CAgYB/f39+fHt6eXh4d3d5f4eOk5aboK3Z9auu8IGFgoqDB4KDgoOCgoOJggGDhIICg4KEg0KChISEhYSGhoaHh4iIiYiJioqLi4yNjY+PkJCQkpOUlZaYmZqbnZ6eoKGhopnynpW217qqoJqTjIR4cnBxcnNzc3WEdgd3d3Z2d3d3h3aDdYV0BXNzcXJyjXMCcnOEcgNzcnKEcwRyc3R0hXUJdnd3d3h5eHl5hHple3x8fX19fn5/f4CBgYKDhYeHh4mNkZaXmJmbnaSsuuiDsZPPg5KTkZCPjYqJh4WDgoD9+/f28u/t6efk4eDa2tfW1tHQzMvLx8jEwsK/v7u7ure3tbOxra2rq6qpqaemp6empKKEoA6fnp2bmpmYlpeVlZWUlISTD5KQj46Ojo2NjIuLiYmHh4SGS4WFhISDgoGAgP///v39//79/Pv5+Pb19fT09PPz8/Lx8O/u7e3q6+vq6unm5uXl5OPk4+Li4eHh397d29va2trZ2tvZ2drZ2djX1oTTAdKE0S3Q0c/Qz87PzM3Ozc3MzMzNzMvMzMzLysnIycnIyMnJycjJyMjIx8fGxcXExMWIxkHFxsbGxcXFxsXGxcbHx8fGxcbExMPBvbWegMOIrcSFfHh3dm9nYVtWUk5NTlFTVFVWWFlaW1xdXl9gYWNjZGVlZotlhGYFZWVkZGWFZANlZGSFYwViYmJjY4RiBGFiYWGMYAZfX19gXl6LXYVciFsNXFxbXFxcW1xbW1taW4VahFkDWFlZhViDV4dWjFUuVFRUU1JSUVFRUlNUVldXWFhYV1ZUVFNTUlFRUVJSU1NTVFRUVVVVVldXV1hYWIVZiVoFW1xbXFyGXQ1cXV5eX19gYGFiY2NkhTIYMzM0NDQ1NTU2Njc3ODg5OTk6Ojs7PD4+kICZgYyCiIOGhISFH4aGh4eIiYqLi4uKiouNjo+OjY6QkI+OjIqJiYiIh4eEhoWFhoSIg4qCl4GzgJp/BICCg4PYhASDg4KA/38Ff4GCg4OOhOWD/4KbggOBgYD/f/9/hn+dgAICBACArK6ytbm+wsfN09jf5evy+oCDhomMj5KVmZ2ip62ytrzEyc/V3OTw+YKIjZScpK23w87b6/+Ikp2qucvmgouZsdL/prbVgamJpeHF2tSM1JfZp/as76mKhvCz6b3C8bugzPeQopap3v/HpsGG4sOxit29pJGA4sy4qp6Shvjj0cNEtamelo+Jg/ru4tjQysW9uLKuqKGcmJWRjYqGgv769vHq493X0svFv726trOtqKWkoqCdmZaUkY6NjYuKiomKioqLiomEiB2Hh4aFhIOCgYKBgP/+/fr5+Pf29vX09PTy8e7t7oTwCPHx8vGHLBcVhxOGEpcRhBAGERARERARhRCEEYsIigmFCoILhAwaDQ0NFSNsxcC/vru6ubWvrrCxs7O0tLS1tLSEtQe0s7SzsrKxhbAbr6+wr66vrq6vr6+wr6+trq+wr7Cvrq+ur66tha4Ira2trq2urayFrQquraytra2srq6uhq0Drq2thKwarayrqaemp6msrrCxs7S0t7syIBQWDQ0MDAyEC4QKEBUVFRQVFBQUExMUEhMTEhOSEgETpRKbEYYiASOJIgQjIiMjrCKEIwciIyMiIyIjhiKSI4wkBCUlJSSEJQEkjCUJJiUlJSYlJiYlhiYmJyYmJSUlIiA5NWTGxsfIyMbFwr+7ubi3t7m7vcDDxMXHx8jJysqEy4TMAcuIygrJycjIycrJycrKhckCyMeEyITHDcbFxsXGx8jJx8jIx8eExgTHx8jIhsaFxQzExMTGxsTFxMHCwsKEwRnAwcLAwcHDwsPCw8LCwsTFxcXGxcbGxcXEhMUHxMPDw8LCwonBgsCGwX3CwcDAwsC/wb68ubWzs7W2uLu+wMC/vr++u7m4ubm5vL2/wMDAwcLDxMXFx8jKy8vLzMzNzM3Nzs/Q0dHR09TT09XV1tfY19jY2drb3N7e39/h4eLj5OTn6ert7/L09vf5/P+AgYKCg4SFh4iJi4yOkJKUlZianJ2goqSmqYB1eHt9gISGiYyPkpaZnaKnVlhaXmFkZ2pucnV5fYOIjJKYnqWrsbrBZWltcnh+hIyVn6m0vmRrc32HkaVdZG9+k7B1f5BWb11wloaXeWCmg7mHp3W9jXVvs320lp/FkmmYz3qFd22RrIl0iF+fh3pfl4FxZFqlmIt+cmlgsaOXjHGCe3NtaWRgt6+noJqUj4qFgHx4dHFua2dkYF1asKqkoJuXko6LiIWBfnx6enh3eHl5eHZ1dHNycXFxb25ubWxra2tqaWhnZ2ZlZWRjY2JiYWBgYL29vLu6ubi2s7GvrauqpqSjp6uusLGxs7W5czcvN4Q4hDmHOIY3CDg3Nzc2Njc2hzcONjc3Njc2Njc2Nzc3ODeFOIcchh2DHoUfHiAgISEiIyMkJCQlJiYjNUF5pJmUko6NiYN+fX6AgouBD4B/gX9/fn5+fX18fXx8fIR7CXp6e3t6enp5eYV6B3l6enp5eXmLeg15eXp5eXl6enl5eXp6iXuEfAF7hXwae3l5enx+gIGChIaIjZU3NCk2ICEgIB8fHh6EHS8cODc3NjY1NDQzMjIwMDAvLy4uLi0tLSwtLCwsKyoqKSopKCkoKCgnKCcnJyYnJ4YmgiWEJAEjhSQDIyMihyOHIo8hgyCHQQNCQUGEQIg/gz6OPYU8ATuFPAM7PDyQO4o6BDs6OjuGOgE5lDqGO4s6izs5PDs8PDs7Ozw7PDw7Ozs6ODJUQ2qljYiHh4aEgX56eHd2dnh5e31+fn+AgICBgYKDhIODhIWFhYSEjoODgoeBhICFfwN+f36Kf4J+hX8Efn59fYZ+CX19fXx9fXx8fIR7Cnp7fHt7e317e3yIfYJ+hn0DfHx7hXwEe3t8e4Z6hXuCeoV7FHp7e3p5e3l5eXd0cW9tbnBxdHZ5hnogeHh3dnZ3eHl6e3t8fH1+fn9/gICBgoKCg4SFhYaGh4eEiISJPIqLi4yNjY6Oj4+QkpOTlJWWl5eZmpqcnZ6foKGipKWmp6lVVVZXV1lZWltcXl5fYGJjZGZoaWtsbnBxc4A+Pj4/P0FBQkNERUdISUpMJicnKCkqKywtLi8xMTIzNDU2ODo7PUBBISIkJSYoKSorLS8yNhwdHyElKS4ZGx0hJCkcHiASFxQWGhYaGxw4KzIkHxo2LSgjNDJWSUZPMxs1UiskHxgkKR4dJBcmHx4YKCMfHBkvKyglIh8dNjMxMCkuLSooJyYkRkRCQD07OTc2NTQ0MzEwLy4tLCwrVFNSUU9NS0lIR0ZFRIRDBURERUVFhUSERYVEjEOHQh6EhYaFhISDgoB/fn17end0cniCjZOXm6CqxMKis+6F9wr4+Pj5+fr6+fj3hPZf9fX39/f49/f39vf29fb09vf3+Pj3+Pj5+vn6+vr8+vv9/P7/gICBgoODhIWGhoeJiYqLjI2Njo+QkpOVlpeYmpucnp+ioY3Gz/Hds6OclpGIe3Jubm9wb3BwcXN0dHWEdgV1d3V1dYR0hHMDdHNyhXMHdHRzc3Jzc4h0hXMWdHRzc3N0dHV1dXZ2dnd3eHh4eXp6eoR8gH19fX5/f4CAgIGBgoWGh4qMjpGUl5mZmp2hqLrqntKt7I+TkpCPjYuJh4SDgoD9+vn08u/s6+bj4dvZ1tLTz83OysrHwsPCv8C8u7q2tbOwsK6traqrqqmpp6Wko6KioaGenJuampiXlpeWlpWUk5OSkY+Qj4+PjYyLioqJh4eIPYiHh4aGhYSCgYGBgICBgICA/v37+vn4+fn39vXz8vHv7+7u7u3s7Ovr6efm5uXl5OXk5ePk4+Lh4N7f3d2F3Dbb29va2NfW1tTT09LS09PT0tHS0NDPzs7My8vLzMvMzMvMysnJycjIxsXFxsbGyMbHx8XGxMSEwxjEw8PExMXExMPDw8LCwcDBwcLCwsPExMOExIfDL8TEw8TDw8PBwL+9sJXmo9DnkX55d3RvZ2FZUk5NTE5QUlRWV1haWlxeYGFjZGVli2eJZohlhmQIY2NkZGNjZGOEZAVjZGRjY4hiAWGEYhBhYWBgX19eXl1eXl1dXVtchlsDWlpbh1qEWYVaBllZWVhYWIVXhFYFVVVUVVWEVARTU1JShFOEUhFRUlNSUVJRUU9OTE1PT1FRU4VUA1NRUIRPDFBRUlJTU1RUVFVWVoZXgliKWQRaWlpZhloLW1tcW1tbXV1dXl6EXw9eX19gYGFiYmNiYmNjMTGGMgUzMzU1NYQ2CTc4OTk6Ozw9PZCAmIGNgoeDhoQqhYWFhoaHh4eIiYuOj5CPjoyOj5CQj42Oj5CQj46Njo+Qj42KiIeHh4aGhIWFhIeDi4KVgbeAmn8CgIK5g6OEA4OCgPl/BIGCg4ONhOOD/4KcggOBgYD/f/9/j3+agAICBACArbCztbi6vsHGzNHY3uXr8/qBhIiLj5KVl5ueoKKlqaywtLvCy9Pd5+/4gYaMkpiepq+5xNDg84OQm6i4zuuAjJimvuSDkqW84v2Ipd6Zwu+f5sG+s8T8jpOHmZXb4vbEwtrXqMqas43qwaKPgenIrZqK/OnYx7enmY+G+ObQv7JMpp2UjIT78ujd1M3IwLqxq6ehmpWRi4aC//jx6uXg29XRzcnCvbm2s7CtqqeloZ+enJqWlJKQj46NjYyLi4iFhIOEhIWGh4eIiIeHh4SGF4WEhISDgoGAgP79/vz7+ff18/Lx7uzshe0H7u7zky4VEocRhRABEYYQAQ+HEIQPBBAQDw+IEIQPARCED4kQhBGICIcJhAqDC4QMFRYjZcC9u7m4tbKrqKuwsrKxsbCwsISvDq6tra6urq2trq2trqyth6wBq4Wsha0Or6+urq6vrq6ur66trq+Ergevr66trq+uiK8DsLGxha8bsK6ur66urKmlpqisr7G0tri6ulsdEhYNDAwMhAuECgMVFBSMEwYSEhIRERKEEYYSBBMTEhONEocRhhIDERESnBEJISEiISEiIiEhhiIBIYkiASGQIgEhiSIBI4QiASGXIgQjIiIjiCIBI4kihSOCIogjjyQEJSUlJJElASaEJS4kIiA7N2TOy8vJyMfEwL26vL29vb7AwcPExsbHycrJycnLzM3Mzc/Ozs3NzMvKhcsByoTJBMjJycqEyYTIGcfHxsXGx8jHx8bGxsfHycrKycnKysnJyMiFxxTGx8jHyMjHx8fGxsbExcXExMXFw4XCDcHCwcPDwsPDwsHBwsKHwAzCwMDAwcDAwL++vr6EvQK8u4a8PL2+vLy8vby8vb28u7u6ubm5uLayrqysr7K1tre3uLi5t7WysbGxs7e5uru9vr/AwsLExMXFxsbFxcfGyITJhMoLycrLzM3NztDQ0dGE0wTU1dXWhNgw2dra29zd3t/g4OLj5OXm6evt7u/x9Pf5/f+Ag4SGh4qMjpGUlpianJ6goaOlp6mrgHN2eHp8f4KFiIuPk5ebn6SqV1lcX2JmaWxwc3d6fYGFiYyQlZufpaqwtl5hZWpvdHqBiJGbprJfZ3B6hpaqXWZtdoaiXWl2hKC3YXWebo2qbp2Df26T0XZ3aWhxn6GnhoeamneKa4JrtZJ5al6njXtvZrusn5GFe3JpYbOlmIyBNXhwaWRftq6km5WPioN8d3RxbWlmY2BcWrGtqKOfm5eTjouJhoSAfnt4dnNwbmxqaGdnZ2hqhGuEbAlrbGtqaWloaGiEZ4RmJWVlZGRjYmJhYF9fvry7ubi1s7GvrauppqissLCvsLGzuH1EMzWFNoY1BTQ0NDU1iTQEMzM0M4U0CTM0NDQ1NDM0NIQzhTQNNTU1NjY2Nzc2Nzc4OIYchR00Hh4fHyAgISEiIiIjJCQiND1qnZOOi4mGgnt4eXx+fX19fn59fn5/f358fH19fnx8fXx8fId7iXoDeXl6h3kGeHl5enl5hHoIe3p7enl5eXqFeQ56enp7e3x9e3t7fHx8e4R8QXt5eHp8foCBg4SHi49cLCU2HyAgHx8eHh0dHBwcNzY3NjU1NDMzMjExMC8vLi4uLS0tLCwsKywrKioqKSkpKCkohicCJieEJoUlhSQGIyMjIiMjjSKKIQggISEhIEFCQoRBjECGP4k+hz2KPAI7PIg7Ajo7iDoBOYo6iTkBOoQ5ATqEOQE6nzmIOgE5iDoFOzo6OjuFOg45NVlHbaaRjouJhoN/fYV7CHx9fX+AgYGChYSChYWGgoWFhBGDhISEg4OEhIODg4SDg4KCgoaBDYB/gICBgICAf3+AgIKEgQSCgoGBh4ANfn9+fn1+fnx8e3x8eoZ7hHoGe3t8e3p8hHsHent7fHt7fIZ7hHqCeYl4hncOdnd3dnZ1dXZ1dXZ2dXWEdA1zcXBtaWhobG9ydXZ2hHcTdXV0dHV2eHl5enp7e3x8fX19foR/BICAgIGEgoSDR4SFhYaGh4eIiIiJioqKi4yNjY2Ojo+PkJCRkZKTlJaXmJqam52eoKGjpKWmp6mqq6xWV1hZWVtbXV5fYWNkZWdoaWtsb3BygDw9PT4+P0BAQUJCQ0RFRkdHJCUmJycoKSoqKyssLS0uLi8xMjQ1Njg5Oh4fICEiIyUmKCosLjAZGx0fISYsGBodICQqFxkcHygwGBskGSEnGSIaGxouTiYfHCo+TDcoHyQuLiEjHSUfNCkgHBkuKSMfHjcyLSspJiIfHDUyLywrLykoJiQjRUJAPjo3NTQzMjIxMC8vLy4sK1VTUlJQT05NTEtJSEhHRkZFRENCQD8+hD0LP0FCQ0RDQ0NERESIRYNEhkOERIZFG4uKiomJh4WEgX9+e3d5hZGWmZyjsM/l2dDp6oTrLOzt7Ozt6+vq6urr6urq6erq6evp6Ojo5+nr6urp6Onp6evq6uzs7e/v7u/vhPBM8fHy9Pb4+Pj6/P7/gIGDhIWGh4iJi4yNj5CRkpSWl5mbnZ+goZLMw8PPsqOalI6Ddm5tbm9vb3BxcnJzc3V1dHNzdHR0c3N0c3N0copzBHR0dHOIdIRzG3RzdHNzc3R0dHV1dnZ1dXZ4eHl6enp7e3x9fYR/gICBgYKCg4WHiIqMjpOXmpucnqGpttTXsKbqj5OTkY6LiYiFg4GA//z79/Px7enn49/e2tbV0dDPycjJxcTDvr67t7i2srOxrq+ura2qp6empKWkoqCenJyamZmYmZmamZeVlJOUk5KRkZCPjYyLioqJiYmIiIeGhoSEg4ODgoGAEYCA/v37+/z6+vn39vT08/PxhPAS7+7t6+vq6eno6eno5+bm5eTjhOIS4ODf3t3c3Nza2tna2djX1tbVhNQt09TT09LQ0M/Nzs3MzM3NzM3Ly8rIycfIx8bHxsjHx8bFxcTDw8HDwsPCw8PDhMIQwMHAv8G/v7/Av7/AwMHAwIS/AsC/h8AOv8DAwb/AwcHCwcHBwMCEvyW+tqH3q8rZiX96d3NtZV1VUlBQUVJUVFZXWVpcXmBgYWNlZmdoiGkCaGmFaIVnBGZnZmaIZRFkZGNjY2RjZGRjZGRkZWVkZIRjAWSEYxZiY2JhYmJhYGBfX15dXl5cXVxcXV1dhVwFW1tbWluEWg1ZWFhZWFhZWFhYWVlZhFiCV4VWBFVVVlWEVIpThFKCUYRQKU9OTk1LS0xNT1BRUVFSUlJRUE9OTU1OUFFRUlJTU1RVVVVWVldXV1hYiFmCWohZCFpZWVlaWllZhFoIW1tcXFxdXV2FXhJfX19gYGBhYmNiYmNjZWRlZTKEMxE0NDU1NTY2Njc3ODg5OTs7PJGAmYGNgoeDhoSGhRSGhoaHh4eIiImKjI6PkI+NjIyLioaJBIiHhoaFhYWEiYOKgpOBwYCWfwKAgsODmoQDg4KA8n8EgIKDg4yE3IP/gqOCA4GBgP9//3+Yf5aAAgIEAICqrK6ws7a4u77BxsvQ1dnd4ebt9Pj7/oGDhIaIio6Sl5ygp62yucLL0trh6fL7goeMkpmiqbK+yNTh8P+IkpunuMra6fuHlKW2zOT9i5aqwOCEhIvQlpuF+9q4moj30bmsmoz649PEt6eS+N/Kuaqfl42E/O/f0MO3rKWgmpWRjDiGgfz17eLY0MjDvbaxq6einJaQjYmGgv/69e3m4NrTy8S9uLWxq6eko6GfnpyamZiWk5COjYyKioaIEIeFg4D8+fn6/oCBgoODg4SFgyGCgoGBgIGA//79/Pv5+Pb18/Hx8O7t7+7u7/Dy9IZYGBOGEYcQig8BDqQPAxAQD4cQBhERCAgJCIQJhgobCwsLDAwNDBU9vL6/u7m1squkpquura2trq6uhK0CrK2GrImthK4Er7Cwr4euhK0KrK2tra6sra6ur4SuBq+vr66uroavILCwsbGwsK+ur66traysqaanq6+xtLa4t7syHxUMDQwMhAuECoYUhROHEosRChIRERISERESERGFEpMRARCLEQcQEREREBAQhhECIiGLIoIhhCIBIYsiCiEiIiEhISIhISGGIgQhIiIihCEEIiIiIYsiASGvIgIjIokjAiQjhSSHJQEkjyUoJCQkJSUjIDo2aNDQ0dDOysXCvry+wsTHycnJysvNz9DQzs7Oz87Oz4TQhc8czc3OzczNzM3NzczLy8zLy8rKy8rLy8rJysnIyIXJEsrJyMnJysrKy8nKycjIycfIyITHEcjHxsfFxcbFxcTEw8TDwsPDhMUTxMTFxMPExMTFxcTDw8TDw8PCwoTBC7+/v76/v76+v7+/isA/wcC+v7+/vr2+vry6u7q4uLi2tLGuq6mqrbK3ubm5uri2s7Cvr6+xtbe5uru7vL2+vr+/v8DAwMLDwsPDw8TEhMVBxsfHx8jKycrLy8zNzc7P0NDQ0dHS09PU1dXV1tbX2Nna29zc3uHj5efo6u3w8vb4+vz+gIGCg4WFh4iKjI6QkZSElgmYmZqcnqCjpaiAdXd5e36AgoWHiIqMjpCTlpmcn6Onq69ZW11gY2VpbG9ydXl8f4GFi5GWm6KpsFtfYmdsdHuBiJGao6u3YWlweomWoq68ZG57iJalt2VvfYujYWFll25tW6uahnFluZ6LgHNovbGqoJSEcsGrmYyBeXJqY7mtopiPiIB7eHJua2Y6Yl60rKainJeSjYeAfHdzbWlmY2FfXFqvq6einpmVkY2JhYF+e3h2dHJwbm1ramloZ2ViYV9eXl9fYYZjCWJjxcTDwsNhYolhhWAfX19fvby7urq5t7Wysa+uq6qutrq7u72+wWZeLTQ0NIkzAjIzijIGMTIyMTEyizEHMjIxMTIxMIQxhDIEMzM0NIQ1hTYENzg4OYUcKB0dHh4fHx8gICEhIiIjJCEuVayYkY2KhoJ7dXZ5enp7e3x8fHt8fHyHe4J8hHsDenp7iHoDeXl6h3kBeoV5Bnp5eXp6e4R6hHuCeoR7A3x7e4Z8L3t7e3p8e3x5eXp9gYOFh4qMljg4LR0fHx8eHR0dHBwcGzc2NTU0MzIyMjExMC8uhi0ELCwrK4QqDCkpKSgpKCcoKCcnJ4UmhyWEJIUjhyICISKQIQUgIEFAQY1ABD8/Pz6EP4c+Az0+PoQ9iTyDO4Q6hDuHOgE5hTqSOQc4OTk5ODk4hDkCODmGOAI5OI05ATiIOYU4hjmKOgc7Ozs6Ojo5hDoTOTNUQGGZkY+OjIqEgH17fH5/gYWCBoSGh4iIh4SIhYkEiIeHh4uGB4WFhYSFhISFgwaCgoKBgYKGgQ2AgICBgYKCg4KDgYCBhICGfwx+fn19fHx8e3t8fHuHeoR5hXoKe3p6ent7fHt7e4R6BHl5eHiPeQR4eHh3hHaEdSl0c3NzcnJycXBubGlnaGptcnV2dnd2dnVzdHR0dXZ3eHh5eXl6enp7e4R8AX2Hfhh/f4CAgIGCgYKDg4SFhYaHiImJioqKi4uEjDaNjo2Pj4+RkZOUlZaYmpudn6ChpKWnqqusrq9YWFlZWltbW1xbXFxdXl9gYWJkZWZpa2xucXMFOzw8PT6EP4BAQUJCQ0RFRkZHSUlJSiUmJiYnJygpKSorLC0uLzAxMjM1NjY4HR8gIiMkJScpKy0vMjUcHR4hJiouMjUcHiEkJiouGBofISkZGRklGxoWKykkHBozLSgmIyE9ODQxLyokPjcvKiclIyEfOzg1MzEuLSwrKScmJSQiRENCQT49PB46ODU0MzIwLy4tLCsrKlJSUE9OTUtKSkpJSEZFRESEQxBCQT8/Pj08PDw7Ojo7Oz0/hEAKQUFCh4iIh4dDQ4RCBkNCQkNDQ4REHkVFRo2Ojo2OjYyJh4aEgn18gZGdoaSorryB26bc4IThMODf3+Dg397f3t3d3Nzd3d7f3tzc3Nvc3Nzb3t7c3t3c3d7e3+Df3+Dg4ePj4+Xm54ToPunp6uzs7O7x8vP19/r8/4GBg4WGiIiKi4yPkZKTlpiZmpybhaPf9r2pnZaNg3Rqam1vb3Byc3R1dHV1dXR1hHSIdQp0dXR0dHV1dXZ2inQOdXR0dXR1dXR1dXV3dnaEd4R4eXl6eXp6ent8fX5+gIGBgoKEh4iKio2TmZyeoaasvOeY68mIk5KQjoyKiYaDgYD/+/j39PDt7Onl4NzZ19LPzszJyMXDwb+8urq4tbSxr6+uqqmrqKeopaKioZ+fnpybm5qamJaWlZSUk5OSkZCOjYyLi4qLiomJh4aEhSeDg4OCgYGBgID+/f38+vf39vX19PT08/Pz8PDu7u7t7Orp6Ofo5+aE5QTj4eDghd4b3d3d3Nvb2NfV1dTU1dTV09TT0tLR0c/Qz8/QhM4Wzc3My8vKysnJycjJx8fGxsbFxsTEw4TED8LCwcHAwMC/wL/AwMDBwYTAC7+/vr6+v72+v76/hL4Nvby9vb69vr6+v76+voW/AcGEwirBwcC/v8DBuJvkj5mihH16d3JnXVZRT1FTVVdYWVpaXV9iZGZnaWprbGyEbYRsgmuEagxpaWpramppaWloaGiFZ4dmBWVlZWRlhmQCY2SEYwVkZGNiYoZhB2BgX19gX1+EXoddA1xcW4RchVsDWlpbhFoFWVlYWFmPV4dWAVWEVARTUlNThFIkUVFSUU9OTEtJSktNT1BPUFBPT01MTEtLTE5PUFFRUVJTU1NUhFUEVlZXVoVXhVgKWVhYWVlYWFlZWYhahVsIXFxcXV1dXl2FXgZfX19gX1+FYAVhYWExMYQyhDOENIU1CDY2Nzc4OTo6l4CXgY6CiYOHhIWFhIaDh4WGhoWHhImDj4KVgauAhX+TgJZ/AoCByoOVhAKDge1/A4GCg4yE2IP/gqeCA4GBgP9//3+Xf5uAAgIEAICWl5iZm5ubnZ6fpKuvsrS4vcPIy83R19zf5erv9PyBhIiMkJSYn6Spr7a8wcfO1dvg5evy+oKGjJKbo6uyusXP2OPu+IKJkJuos7vF09/q8vuChYiMjYuJio+Rj46Mi4Tx49vTzczLyb+tnIz/7tzKu6+km5OMhYD58+zm4NrTzlXIwLy3s66rqaajoJ2bmJSQjouIhYOB//rx6eTe2dPMxsC8t7Owq6ejoaCenZqXlJGQjo2LioiHhoSCgYKDg4OCgYD//vr07uvs8PL19/f4+fr5+fj3hPaE9Qf08/Py8fDvhO4X6+nt8PDw8vT1hFoZEhAQERAQERAPEBCJDwcODw4PDg4OiA+GDoIPhg6FD4gOiQ+FEAMRERKKCSAKCgsLCwwWIGfIxL+8uLSrpKarraurrK2urq6tra2uroathq4Fr6+urrCFrwKurYauFK2trayrrKytra6ura2trq+vr7Cwh68FsLCvr6+EriCtrKqopqaorbCxtLe5vDIhFg0NDQwMCwsKCgoVFBUUFIcThBIBEYQSIBESEhEREhERERISERISEhESEhEREhERERIRERISEREShREEEBEQEYUQkBEFECEiIiGQIs0hASKHIQIiIYgiASGOIgIjIoQjhyQCJSSHJQEmiyUsJCQlJCQjITw2adPV1NTTz8vHw8LCxMnN0NHS09TU1dbW19bU1NTS09PV09OF0gXT09PU1IbTB9LS0tHR0NCFzwrOzczNzc3MzMzLhMwIzczMysvLysuGyAnHx8bHxsbFxMWExIXDDcLExMPExMbFxcTExMWExAXFxMTFxYTCB8PDw8LCwsCEv1q+v8DAwMLDwsLCwcLCwcHCwsLBwcC/v76+vb2+vLu6ubi2sayoqauyt7m5ubq5uLW0srO0tri6ubq7u7y9vr+9vb69vr6/wL++v7/AwcLDxMXFxsfIycrLy8uEzCfNzcvMzc3Nzs/R0dLS09XW1tna3N/i5ebp6+7w8PHy8/T19fb3+PmE+Bb6+/z+gIGDhYeIiouMjpCTlpiZmJiYhJeAaWlqbG1ubnBxcXN1dnh6fH6AgoSGio2Slpuhpqy0XmJkZmltb3N3en2AgoWJjpOZoKattLphZWtwdXp/hYyTm6GosbhgZGtzfoaNlZ6nsLe+YmVpa2xrampramloZmRer6ijn5qYlo+EeXJqxLOhk4iBenNtaWVhvLSsp6GcmJRQkY2JhYB7eHVzcW9samhlY2FfXVtaWbCsqaWhm5eSjoqGgn98eXd1c3FvbWtqaGZkY2JhX15dXFtaWVhYWVtdXl5evLy9vbm2tLS1t7e2t7aFtQm0tLW1tLKysrGEsASvrq2thK8KtLu8vb/AwmRSK4cyhDGEMAovLzAvLzAwLy8whC8BLocvBC4uLy6EL4YwhS8JMDAwMTExMjMzhDQENTU2NoQ3Ajg5hB0eHh4fHyAgISEiIyMiNjhcnJeSjYqGe3R1eXp5eXt7h3yFe4J6iXsCenuFegd5eXh4eXp6iHkKenp7e3p6ent7e4l8BHt8fHuIfCZ7e3p6fYGDhIeJjJU2OjAfHx8eHh0dHBwcNzY2NjU0NDMyMTAvL4QuDi0tLCwrKysqKiopKSgphCiEJ4QmAiUmhCWFJIcjhiKKIYcgB0FBQUBAQD+EQIc/AUCFP4c+gj2FPIU9Bjw8Ozw8PIk7Azo7O4U6Azk6Ooc5hjiCOYU4Azc4N4o4ATeSOAE5jDgHOTg4OTk5OIY5BDo6OjmOOgE5hDojNlpDYpyYlZOSj4qEgoGBg4WIiYqJiYmIiYuMjY2MjY2MjY6EjwWOjo2MjISLE4qLioqKiYmJiIeIh4eGhoWFhYSFg4SCGIGCgYGCgYKBgYKBgoGAgIGAgICBgIB/f4V+hn2DfIh7Bnp7enp7eoZ7Bnp5enp6eYR4Dnd3eHh5eHl6enp7e3t6hXmEeAR3dnZ2hnUmdHNzcnJwbWlnaGpwc3V1dXZ2dnV0c3R1dXZ3d3d4d3h4eXp5eXqEe4R8hH0Zfn5/f4CBgYKCg4OEhIWFhoaHh4eIiYmKi4SMMI2Ojo+QkZKUl5iZm52foaGio6SlpqanqKmrrKusra6vsLFZWltcXV5fYGBiY2VmaIVpg2qFOYA6Ojs8PD0+PkBAQUJDREVGR0hISElKTE1OJiYnKSorLCwtLS4uLzAwMjM2ODo7PUAhIiMkJScoKSwtLzEyNDYcHB4gIyYnKCsvMTQ1Gx0fISIjIyMiIiAfHh4cNjU1MzAtKiooJSMgOjc0MzAuLSspJyYkR0RDQUA+PDo5OTg2NUo0MzIyMjEwLy4tLSsrKiopKVFRUE5NTEtKSEhHRkZFRUVERENDQkJBQUBAPz8+Pj08Ozo5NzY3ODo8Pj4/fn6Bg4WHh4eGhoWEhIeDH4SEhYWFhoeGhoeHh4aFg4GAfHl4hZqipauwu3Som9KE1IXVGNPS0tLR0dLS0dDR0M7Oz87P0dHPz9HPzoTPUNDS0dLS0dLT09PU19bX2dra3N/f4ODh4eLi4+Tl5ujp6+7v8fP3+fv+gIGDhYaHiYuNj5GTlJaXkc2ll8KsnZSNhXVqam1ubW1wcXN0dHV1h3YDdXV2iHUCdHaKdYZ2BnV1dnZ3d4V4gnmEegF7hXwBfYR+e4CAgYKDhYaIiYuOlJeZm6GotdmN7NCLkZCOjIqJhoSC//z6+PXx7uvo5eHe2tjV1NLQy8nGxMLBvry5t7Sysa+tra2rqKempKOjoZ+fnpybm5mWlpeWlZSTkZGQj46Njo2LiomIh4eGhYSFhISDg4KBgoGA//79/Pr5+IT5J/j49vX08/Ly8O7t7evr6enp6Ofm5OLi4uHf3+Dg4N/f3t3b3Nvb2oTZGNfX1tbV1dTT0tHR0NDPz87Ozc7MzMvKyoXJCcrIyMfGx8TFw4bEBsLDwcLAwYXAG7/Av8C/v72+vb69vr69vr2+vL28vLu7vLy8u4S9ELy7vLy8vb2+vr69vr6+vb6EvwHAhMEuwMHBwbyn+5eTl4N/fHlzaF9YUlJUV1peX2BhYmNlZ2psbW5vb29wcHFwcG9vb4RuBW1tbWxthWyEa4JqhWmFaBNpaGhoZ2dnZmdnZmZlZmVlZmVlhGQEYmJhYYdgBF9fX16GXwFehl0FXF1dXF2GXAFbilqFWYhYAVeHVhtXVlVVVVRUVFVUVFVUU1JSUVBOS0pLTE9QUFCEURJPTkxMTU1PUE9RUlJTVFVWVVWLVoZXB1hXWFhYWViHWYJYhFkBWoVbCFxcXV1eXl9giV+GYAFhhWIEY2NjZIQyEjMzMzQ0NTU1NjY3Nzc4ODg5OJ6Al4GPgo2Dj4SMg4yCnoGsgK9/AoCB0IOQhAODgoDnfwOBgoOKhNSD/4KsggOBgYD/f/9/oH+WgAICBACAiomJiYqLjI2Oj5GTlJaYnKCkqa60ub/EzNLZ3+Ll6O3x9fyBhoqOkZKVmJugpaiqrbC1ur7DytHZ4Ojx+YCEhomNkpeboKevtbzAxMnU4Onw+oOHioyMkJaSi4WBgYD79ezg2tnZ1M/Lw7myrKSXjIL17Obe2NPPy8a/t7KuqqV2oJuYlZKOioaCgPz59PHu7Ozs6ufi3NjV0c3Kx8XEwb+9ubSwraqmop+dm5iVkpCOjYuJiIaGhIKBgICA//78+/r6+/r6+fbz6+Xi4uTm6evt7O3t7evs6+zs6+rr6+ro6Ojn5uXk5OPk4+Dj6urq6+3xg1gXEYcQAg8Qig+GDoIPig6CD44Ogg+GDoINiA6ED4MQhBGCCIQJGAgJCQkKCgoVImTIxcC8ubWqpqmsrK2trYWuD6+vrq+urq6vrq6ur66ur4uuha2ErAGrhqwura6trq+ur6+wr6+vrq6ur6+ura2trq6trKyrqKenqq2vsbS3umEfFAwNDQwLC4QKAhQVhBSFE4YShxEEEhEREYoSBBEREhKZEYQQhBGEEIIhjyIGISIiISEhhSKVIQUgICEgIYUgAyEhILMhmCKGIwokIyMjJCQlJSQkhCUCJiWEJgMlJiaGJTAkJCMgPXHe3t7c3NfW1NLQ0NDS1Nfa3N3e4ODh4ODe3dzd3Nvb3Nzd3Nvb2NjZ2NiG2QzY2dnY2NfX1tXV1dSE0wfS0dLS0tHRhdAg0dHPz87Nzs7LzMrLysrIyMfHxsbFxcXDxMXExsXExcSExQTExMXEhMURxMXExMTCwsHAwcHBwMPExseEyBLHyMfHyMjIxcDDxsjIxsTDwsOEwiPBwsLCwMDAv7+9vr69vby5trOtrbK4u72+vry6t7SxsbS2uIW6V727u7y9vb69vr69vb++vr+/wMDBwMHDxcXFxsbFxsbIyMnIyMrIyMnKy8vNzM3Oz9DS0tTW2dve4OLl6Onr7e3v7u3u7u/u7vDx8fLz9ff4+v2AgYKEhYaGB4WFhYaGhoeFhgSIiYqKgF1dXl5fYGFiY2RlZmZnaGttb3J1eX2Ch4yRlZqeoaOmqq6zW15hY2VmaWttcHN2eHt+goeLjpKXnKKnrbNdYWRnaWxwdHh8gomPkZOXoKisr7JcYGNmZ2lqaWReW1pYraumn5qZl5GMiomHhH1yaGBbsKyooZyYlI+Kg356dnRxTm9samdkYl9dWlmuq6inpqWko6Khn5yZlpOPjYuKiIaEg4F/fHl3dHFubWtpaGZlZGNiYV9eXlxbW1lYV6upp6irr7Ozs7KxsK6sqqmqqoSrKampqqmpqqqqqamoqKinp6empaOjoqOko6KlrrG0tri9YVMrLzAwLy8whS8ELi4uL4wugi2ELJAtAS6GL4UuhC8vMDExMTIyMjMzNDU2Njc4OTk6HR0dHh4fHyAgICEhIjg7YJuVkY2Jg3hzdnl5enqFewh8fHx7fHx7e4l8hXuFegV5eXp6eop5hXoIe3t8fHx7e3uHfAN7fH2FfCZ6enx/goSFh4qQWTItHh8fHh4dHRwcHDc2NjUzMzIxMTAwLy4tLYQsDysrKyoqKikpKSgoJycoJ4UmBiUmJSQkJYYkhyOIIoYhByAgISEhICCJQII/hUCFPwc+Pj4/Pz4+jD2FPAQ7Ozs8hzuGOgU5OTo6OoU5ATiEOQM4OTmKOAE5iTgBN4Q4Cjc3ODg4Nzg4ODeUOAE3iDgFOTk4ODiHOYs6ATuFOiA7OzRQc6yenJqZlpGMiYiJiYuMj5KTk5KSk5OTlJOTk4SUDJaXmJeXlpWUlZSUk4WSFpCQkI6Ojo2NjIuLiomIiIiHhoaFhYWIhA2Dg4OCgoKBgYCBgYGAhIEEgH9/f4V+AX2EfAZ7e3x7enuFegd5eHd4d3Z2hHUCdHOEdBN1dXV2dXZ3eHp6enl3en1+fn17hXo1eXl5eHh4d3d3dnZ1dnV1dHRxcG1paWxwc3V2d3Z2dXRzcnN0dXd2dnZ3d3Z2d3h4eXl5enqGe4R8A31/f4SABYGBgIGBhIIbg4OEhYWGh4iIiYqKi4uNjo+QkpOUlpeYmpuchJ2EnhagoaKipKWmqKuuWFlaW1xdXl1dXFxci1sEXFxdXYA2NTY3ODg5OTk6Ozw8PT4/Pz9AQEFCQ0RERUZHSElKS0tMTSYmJycpKSoqKywtLi4vLzAxMjM0NjY4Oj0/ICAhIiMkJSYnKSosLi8wMTQ1ODo+ISEiIiEgICAfHh0dHDg4NTMxMC8uLS4tKignJSQjIkJAQEA/PTs5ODY0MjEwLxwwLy8uLi0sKysqVFVUU1JSUlBPTk1LSkpKSUlJhUgKR0ZFRUVEQ0JCQYRAhD8fPj09PDs6OTg3bGlmZmpwd3t9fn6AhImJiIeGhYODg4WCAYGFgoWDFIKAfXt5dXRxbXaOm6KnrblvrJ/JhsqIyAXHxsbGxYTEBcLCw8PEhsVSxMPExcXFxsfHx8jJycjJysrKzM3Oz9LT09TV1tfY2tvc3uDh4uTm6Ovt7/Dy9vn+gIGDhYeKi42PkpSVk+C7m8OrnpSNgnFqbG9vcHFzdXV1doh3AXiFdwN2d3eGdgJ3dod3gnaIdRB3eHh4eXp6e3x9fH19fn5+hYBcgoOEhIaIiouNkpeanJ+kr8Srs7+HkJCOi4mHhYKB//359fHv6+fk4d3Z1tPQzcvJyMbEwb68ure1s7Kxr66tq6inp6akoqKgnZubmZiYmJeVk5KTkpCQj46NjIqEiQ6IiIiHhoSDg4OCgYCA/4T8LPr6+/r4+fn49vb29fPy8e/u7+/v7u3r6ejn5+Xl5OPk5OPi4eDg4N/e3dzchdoI2dnY19XU09KE0wjS0dDPzs7MzITLJMrKycnIyMjHx8fIx8jHx8bHxcXFxMTExcTEw8PBw8HBv8DAwIS+A72+vYa8Cb28vby8u7u7uoS7Fby7u7u8vLu6uru8vLy+vr2+vr2/voS/iMAtwbmaz9XAi4WBf3pwZlxXV1haXF9iZGVnaGpsbnBxcnJzc3R0dXV2dXR0c3FyhXEDcHBvhm6EbYVsBWtrampqhGsUamppamlpaGhoZ2doZ2dmZmVkY2OEYgJhYoZhh2CCX4Zehl0QXFtbWltaWlpbW1tcXV1eXoRdEFxcWldTVllaWllYV1ZXV1eGVoVVhlQUUlFPTUxOUVNUVFRTUU9NTEtMTU6ETwxQUVFRUlRUVVVVVlaFV4NWhFcFWFhXWFeJWINXh1gIWVlaWltcXFyIXYVcFF1dXV5fX2BgYWFiY2NkMjIzMzQ0jTMINDMzNDQ0NTWjgJqBlYKNg5KCmYGygLZ/AoCB1IONhAODgoDifwOAgoOKhM+D/4KxggKBgP9//3+ff5uAAgIEAICAgIGCgoOEhYeIiYqMjpKWm6Clqa2wtLa5u73AwsTJztLU2Nve4OHk6e/0+v+Bg4WGiIuOkZSYnKOrs7rBxsrO0tXX3OHo7vPz8vX/ho6Sk5OVmJyen6KloZmRjoyMjY2OkJKOh4SCgPny7ezp4NHAtK+sqqelpKGem5aTkI6NinaGgfv17+vk4d7b19PR0M7MyMbDwcC/v7++vLq3tLKvrKmno6GfnZybmZiXlpSSkI+Ni4mIhoSDgoGA/vv6+Pb19PPx8PHy8fDv7uvl3Nnc3+Lk5OTj4+Li4eDh39/d3t7f3+Dg4N7b2tfX19XX39/g4eTohF0UhQ8BEIUPARCGDwoQDxAPDxAQDw8Ohw+gDoMNiQ6FD4MQhQhLCQkJCgoVH2DFw7+8u7OlpKuvr66tra2srK2srKytra2urq2urq+vraysrKusrKusraysrK2trq6trK2trK6trK2srK2tra6ura2thq4hr6+vrq6vrq2srKqpp6itsLKztbe7NhIYDQ0MCwsLCgoKhRSCE4sSghGQEqQRhxCHIYciAyEhIpkhgyCFIZQgCCEgICAhICAhnCCQIQQiIiEhiiKGI4okhyUDJiYlhSY0JSUmJiUlJiYiPXHj5+jp5+Tf3dnc3uDg4uPk5+nr6+zs7e3t6eno5ufo6erp6Ofk5OTj44TiAuHihuEe4N/f3t/e3t3c2trZ2NnY2NfY19fW1dXU1NPU0tHRhM8Lzc7NzczKycjHx8eExgjFxcbFxsbHx4XGJ8fIycjHyMnIx8bHyMnIyMnKysvLzM3NzMzLyMfHxsXFxsXDvLq7voXABL/AwcGEwITBgMLBwcLBwsK/vLi0sbW7v8DAv7+9ube2t7m5ubi4uru7uru6u7q7u7y9vr29vr+/wMHBw8TFw8PExMPDw8LDwsLDwsTExcbGx8jJy8zNzc7O0NHS1NTV1dja29zd3+Hi5OTl5Obm5+jp6ers7vHy9fb4+fr8//+AgID//v7///38EPr49/X09fPy9fX3+Pv9/f6AVFVXWFlZW1xdXl9hY2RmaWxvcnV4eXx+f4GChYiKjI+TlZeZmpudn6Onqq2wWlxeYGNlaGtucXR5fYCEiIuOkZWZnKClq7K3ury+xGZscXN1dnd5enl5eHVvaGZmZ2hnZ2hoZF9dWlitqKiopJuSioWBfXp3dXRyb21raGZjYWBcX120rqmmop6ZlZKPjYuJiIaFhISDg4KCgoF/fnx6eHZ1dHJxb25sa2pqaWhnZmVkY2JhYF5dXVxbWrKwrqupp6ajoqSmqqurqqqpp6OhoqOjpKSjoqKhoaCgoJ+EnoSfAp6dhJsNmpiYoqWmqKyvXGUsLYQuiS0HLC0sLC0tLYUsBysrLCwrKyqFK4csBCssKyuELIstEi4uLi8vMDAwMTEyMjIzNDQ1NoQ3HjgcHB0dHh8fHyAgNjVYmZKMioZ+c3N4eXp6e3x8fId9AXyHfYR8hHsNfHx7enp7enp6eXh5eoR5BHh5enqGe4h8Bn18fHx9fYR8Int7fYCChIaIjJdAJDkgHx8eHR0cHBw3NjU0MzMyMTAvLy6ELQEshCsEKiopKYQohieCJoQlBSQlJCQkhSMDIiMjiiKIIYcgBUFBQEBAiD8PPj4+Pz8+Pj8+Pj49Pj4+ij2EPAY7PDs8OzuPOgU5OTg4OIg5iziHNwE4hjcBNos3AzY3NoQ3Cjg3ODc3Njc3ODeFOAE3hziEOQE4iTmJOiQ7Ozo7Ozo6Ozs0UG+poaGhn5uWkY2PkpSUlpiZm5yenp2cnZ2EnhKdnp+hoqOioZ6cm5qamZmYl5eElhqVlZWUk5OSkpGRkJCOjo2Li4qJiIiIh4eGhoSFA4SEg4SCAoGChIGCgIR/BH5/fn2FfBB7e3t5eXh2dnV1dHR1dXV3hHYZd3h6ent9fX5/gIGAgH5+fX18fH18fHd1d4Z5DHh5eHh4d3d2d3d3doR3C3Z2dnVyb21sb3N1hHYNdXR0c3N1dXZ1dXZ2d4Z2D3d4eXp5eXp7fHx8fX1+f4d+AX+Ffk5/gIGBgYKCg4SFhoaHh4eIiYmKi4yNjo+QkJGSk5SVlpaXmJmZmpucnp+ho6WoqqqsrK2sVlVVqaioqKempaSjoqGhoJ+hoqOjpKWmpqiCMoQzEDQ0NTU2Nzg5Ojs8PDw9PT6EPwVAQEFCQ4REgkWERjZHSUpLJiYnKCgpKiorKywsLS4vMDEzNDY4OTs9P0FERUVHSSUmJigpKisrKywtLi4sKikpJyWEJAUiICAfHoQ8KTo5OTc2NTQ0MzIxMTEwMC8vLi0sKypTU1JRUVBPTkxMS0pJSUlHRkZGhkWCRoRFAkRDhkKEQTFAQD8/Pz49PT08PDs6cnFwbmxpZmBeYmp0enx8fX6DiYuKiYeFg4KBgYB/gICAf39/hIAYf359fHl3cW1qZ2yGlJico690+63AwcDBhMAEv76+vYW8Zbu6urq5ubu8u7m4ubq5ubm7u7u6urq7u7u8vb6+v8C/v8DAwMHBwsTFxsfHycrMzs/Q0dTV19jb3uHi4+bn6u7y9ff7/f+ChIeJi42PkJOS3KeKuqWYkIl6amhtcHFyc3V2dnd4hXeEeIR5AXiHdwV5eXh3d4R4BXd2d3h3hXh7eXp6ent7e3x8fX19fn5/gICBgoOEhYWGiIqMjpOYmZygprTbqJD2kZCOjIqHhYKB//z49PHt6ebi3trX09DOzcrHxcLAvru5t7Wzsa+tq6qpqKilo6Ggnpybm5qZl5WVlJSTkpGQj42Mi4uLioqKiIeGhoWFhISDgoGBhIAF//78/PqE+Cn29PTz8vLx8fDw7+3r7Onp6+rp6ejn5ubl5OTj4uDf397d3d3c3Nza2YfWgtSF0wHRhs8ezs7OzczKysrJycjIyMfGxsXEwsPDxMPEw8PBwsDAhb8LwL+/vr69vbu8u7yGuwW5ubi7uYe6A7m6uYW6Bby7u7y6hLs1vb6+vr2+v7/AwMHBwsHDwcHCwsHCxLyawLeoioiGgnltZF1eX2FiZGVmaWxucHFyc3R1dXaEd4V4DHd3dnV1dHRzc3JycoRxBXBwb29uh2+Fbg5tbW1sa2tqamlpamhoaYRoBWZnZWVlhWSFY4hiL2FhYWBfYGBgX15fYGBhYWJiY2JjZGRmZ2hrbGpqaWhhX15cW1tbWllUUVJUVVVViVYEVVZWVohVF1RSUVBPUVRVVVRUUlFOTUtMTU1OTU5PhFAPUVFRUlJTVFVUVFVVVlZWk1cEVldWVoZXClhYWFlZWlpaWVmEWgFbhFwHXVxdXV5fX4RgBWFhY2RlhWQQMjIyZWVmZmVlZWRkZGNjY4RiBmFhYmJiY62An4GcgpqBuIC5fwKAgdiDioQDg4KA3n8DgYODiYTPg/+CsYICgYD/f/9/o3+DgJd/AgIEAICA/vz8/f+Bg4SHiYuNj5GSk5SVl5manaCio6WkpaSlpaenqKuvs7a5vMDGzNDV2d/m6Ort8fT3+4GGi46Slpmcnp+ipamsrrG0uL3FztbZ2drf5uvs7u/s4NXS0dDLxsfIycnJxsS+uLWzrquopJ+Xj4qIiIaDgYD9/Pv49PDu6kHk4eDg29bSy8S/u7i1s7GvrKuqqKWko6KgoJ+cm5qZmZiXl5aUk5KPjY2Mi4qKiYmJiIiGhYOCgYD//fv5+PTx74TuEOvp6Onp6Ojm4trU1Njc3t6E3R3c2trY1tbV1tXW1dTS0dDPz9DQz9HY2drb3uaQK4YQkQ+FEAkPDw8ODg4PDw+SDoMPjw4DDQ4Nhg4DDw8OhA+DB4QIEgkJCRM6w8TBvbiypJulrK2uroWtB6ytraytrayLrQKsq4itDq6urq+urq2rq6ytrK2thq8Drq6vh64cra2srKqpp6err7K1t7nBHxYNDg0NDAsLCgoVFYQUDRMTExIREhESERISEhGOEgIREoQRghKUEQEQhBECEBGIEIYhBCIhIiKPIQEgiCEEICAgIYQgBCEhISCEH5UgAR+TIIYfjSCVIYkihSOFJIslASSFJQMmJSWEJjEnJyYlJUN7+f78+vj39PHv7+70+Pv9/oCBgYKCgYCBgP/7+fj5/P39/Pv5+Pf08fHuhe0Q7Ovq6eno6ejo5+bm5eTk44XiEOHg3t7e3Nvb2dnX1tbV1NSE00TR0dDP0M7Pzs3MysjKycjJyMjJyMrKyMjJy8zO0M7Oz8/Py8vM0NVtNjIxGBgWFRUKCQgGBgYHLbrHx8bGxsS/tbi8vYS+hb8CwMGEwoLAhcEzwL++ubi7vb+/wL+/vru5ubu9vLu7urq7urq7u7u8u7u8vLy9vr/AwsPDw8TDwsLDw8LChMEvwMHBwsLBw8PExMbGycrKy8zMzs/P0NDQz8/Q0tLT09PU1dbY2t3f4ePl6Ovt7/GE8grw7+7s7Orp5+XkhOIP4+Tn6urs7u/w8/X4+vz+hP8BgBxSpaWmqKpWWFpcXV9hY2RlZWZmZ2hpamtsbnBwhHF+cnR2eHt/gYSGiIuOkZSXm6CjpamsrrG1XV9hY2Vnamxtb3Bzdnl7fYCFiY+Vm5+goKKnqqytrKmfl5ORjomFhISFh4mJhoJ+fHt4dnV0cGxoZmRjYF1dXLa0srCtq6ilop+bmZiWkIuHg4B9eXd1dHFwb25tbGxramppaWhohWk1aGhnZmVkY2JhYWFgYGBfXl1cW1taWrOxr62sqaeko6GgnpydoKOkpKSioJ+cm5yen56enZ2EnBqampmZmZiYl5eWlpWUk5KSj5OanJ6go6hqSIgsAissiCuGKg4rKikpKSoqKSoqKikpKZEqgyuLLCwtLS4uLi8vLzAwMTEyMzM0NTY2NxscHB0dHh4fICAxT56RjYiEgHdxdnl6e4R8BH18fX2FfAF9iXyHewV6ent6eoR7Cnp6eXl5enl6enqHewR8fXx8hX0wfH18fHt7foGDhYaIjZ8qLR8gHx8eHR0cHDc2NTU0MzIxMC8vLi0tLSwrKysqKiophCiFJ4QmhSWCJIcjiyKIIYcggkGGQIg/BD4+Pj2GPgI9PoY9ijyEO4w6BDk5OTiIOQk4OTg5OTk4ODiJNwE4hDePNgI3Noc3ATaXN4Q4Bjk4ODg5OIc5hTqHOzo8Ozs7PD04V3q6sK2qqaafmpucnKCjpaapVVVVVlZVVVVUqamoqamqq6yrq6mnpqKhoaCfnp6enZychZsempqZmpmXlpaWlZSTkZCPjYyMjIuKiomJiYiHh4aGhIVGhISEg4OCgoCAgH9+fn59fHt5eXh3d3d4eHh5enp6fH+AgIGChIiVXDY+RSYqLzM3HR8hIyUmI0SLgX9+fXx7eXR1eHl4eIR3CnZ3dnZ3d3Z2dnWFdgl3dXVzcG9ydneEeAp2dHNzdHV1dHRzhHSGdoR3CHh4eXt7fHx9hHwJfXx9fXx8fHt8hH0Qfn5/gICBgoODhISFhYaGhoSHNIiKiouLjI2Nj5CRk5SVlpianJyen6CgoJ+enp2cmpqZmJaVlJOTlJSUlpeYmZqbnJ2foaOEpAOlpVINMGBfYGBgMDAxMTIzM4Y0ATWENoI3ijhGOTo6Ozs8PD0+Pj9AQUJDRERFRkckJSYmJicoKCkpKistLi4tLS4vMTEzNTg6Ozw9PT08Ozk3NjY0MzIxMjMzMjIyMC8vL4Qugy2ELIMrhFUWVFRTU1JQT1BOS0xLSklIR0ZFRURERIRDhUIEQUFBQohBhEAEPz8/PoQ9hDwkOzt1dXRzcm9saGdkYF5bYGp0d3h6e4CHjoqHhoWEg4KCgYB/hoAWfn58e3l3dXJvbGZhaYeTlpmfs6PltIa3dLa2tbW0tLSzsrKys7KxsLCwsbCwsbGxsK+vsLCxsLCxsLGxsLGysrS1tra2t7e2tre3t7m6u7y9v8DCxMbGyMvOz9LU1tfb3uHj5uns7/X5+/2AgoOGiImLjo+Lu8/ksaKXj4VwZWptbXBzdnd4eHh5eXl4hnkBeIV5BHh3d3aEd3J2d3d2d3d4eHh3d3Z2d3h3eHl6e3t8fHx9fX5/f4CBgoODhIWGh4qNkZaZmpufprb6hLeKkZCNioiFg4H/+/f08O3p5eHd2tfU0c/MysfFw8G9ure1s7GvrqyqqKalo6Kgnp2dnJuZl5aWlpSSkJCPjo2EjAyLiomIiIeGhYSDg4KFgQ2A//7+/fz8+/n39vXzhPIM8O/t7O3u7Ovs6unqhOgG5+Xk4+LihOAW397c2tva2tjZ2djW1dXV1NTS0dHR0ITRBtDPz83NzYTMFMvLycnIxsXFxcTExMXEw8PBwsDBhcA9v769vry9vL28vby8u7y6urm6urm5urq6ubm5t7i3uLe5uLi3uLi4ubi5urq7uru6u7q7u7y8vr6/v76/v4TAJsHCwsPDwsPDxcSm0cazkYyJhX1xZ2NkZGZoaWtuODk6Ozs8PDw9hnwLfX18fHt6enl4eHiFdwN2dnWEdIRzAXKJcQZwb25ubm2EbAFqh2kEampoaIVnPGZlZmRjZGRjZGNiY2NkZGVlZWZnaWppamxub3ByeIbAqIG25oynwNryhZGdq7i/mMCzbmdkYmFcVlBSVIRVhFYVV1ZWV1dWVlZVVVRVVFVVVVRTUVFShVMGUlBOTExNhk+DUIdRElJRUlJTVFVVVlZXV1dWVldWV4RWAVWIVgFViFaCV4VYGldYWFdYWFhZWVpaW1tcXF1dXV5dXl9fYGBghF8KXl5fXl5fXl9fX4dghF8BYIZhAmAwAYCFf7KAwIHBgLt/AoCC2YOKhAKDgdt/AoKDiYTMg/+CtIICgYCQf4mA5n8EgIGBgYWCh4MBgf9/rn8BgAICBAAO8vX2+fyAgoOEhIWFhoaEiAWJiYmIiIWHYIiKjZCUlpiZmZudn6KlqKyxtLa5ur2/wcTJztLV2drd4ubr7/L4/YKDhIaKjZGYoaiqqaiqra+wsbGvqKGcmpiRi4eGh4eKjZCRkIyJhYGA+/bz8ezl393d3NnV09HPzYTMd8nFwr67ubm2s7GrpJ+cm5mXlZOSj46OjYyMi4uKiIaGhYWEg4OEhIWFhoaGhYWEg4OBgID//fz7+vj39fHv7+7v7u3r6Ofn5OLh4+Pi4+Le18/N09jb29rY2NbV1NPU09LS0c/PzcvLysrJyMXBytHS0tPW7VUSmw+aDgQNDg0NnQ6FDwEHhwgRCRFkvr25trKnnaOoq6yrq6uFrAatrK2srKyErQGshK0JrK2trq6tra6vha4Hr66ura2srISth66GrSCsrKyrqqmnp6qusLK0uGEhFw4ODg0MDAsLCxYWFRQUFIQTBxIRERESEhGUEpoRhRCCEYQQlyGDIIkhiSAIHyAfHyAfHyCHH4cghh8BIIofASCVHwQgIB8fhiCTIYgihSODJIklByYlJSQlJSWFJoUnhCgJI0OFioyNi4mHhIVEhoeJi4yNjo+Pj46MjY6Oj4+NiomKiouJh4aEhIKAgIGCgoGAgP/+/fv49/b08/Px8PDw7+7s7Orq6ejp6ejm5OPj4d+E3hHd3Nvb2tjY19bU1dTT0tPQzoTNG9DR0dHPz8/NzcvS125vODcbGhoYCwsLCgkICIQHhAYBBYQCFwMUWcPFxcPCwLm0uby8vr6+v76+wL+/hMAgwcDBwcC/vr69u7e2ur/Ew8TEwr+7uLm7u7u6uru6urqEuRC6u7y9vb/AwMHCwcDAwcDAhL8TwMDAv76+v7/AwMDBwsLDxMXGxobHhMmGyjvLy8vNzc3R0tPV19rd3+Hg39/e3N3c2tjY19fY19fZ29ze4OHi4uTj5Obo6uvt7/Hy8vHw8fDv7u/v8ICdn6GkqFZYWFhZWVlaWltbXFxdXV1eXV1dXFxcXV9hY2VnaGlpa2ttbnBydXh6e31/goSGiIyQlZaXmJqcnp+hpKisWFpcXV5hY2hucnV0cnJ0dnZ1c29qZmRjYmBdXF1eXmBiY2VlY2BdXFy2s7Gvq6ejoJ6bmJSSkJCRkZCPjgWNioeFgoSADX16dnFsamhlZWNiYWCEXwZeXl5dXV2EXIldTVxcXFtaWVmxsbCvrq2sqainpqWlpKKfnZuamJeZnZ6enp2cmZaUl5iamZmYmZmYmJiXl5aWlZSUk5KRkI6Ni4uIjZSVlpieqFspKiorhiqPKZ4ogimHKocrKiwsLC0tLS4uLi8vMDExMjMzNDU1NhwcHR0eHh8fHyVmlYyIhYF4cXR4eox8iHsBfId7A3x7eoV7AXqEewR6enl5hHqDe4V8hX2Efix9fXx8gYSFh4qQUjE0ISEgHx4eHRwcODc2NTQzMjEwLy4uLS0sKysrKiopKYQogyeFJoIlhySDI4sihiGLIIRAiT8DPj4/hz4CPT6EPYU8AT2GPAI7PIk7izqDOYg4BTc3ODg4iTeLNgI1NoQ1BTY2NjU2hDWENgE3hzYLNzY2NzY3Njc3NjaENwg2Nzc3Njc3N4Q4iDmHOiM7Ozs8Ozw8PDs8PTw0SGZfXl5dWlZVVFVXV1hYW1xeXmBgYIZfFV5dW1tcXV1dXFtZWFdVVVZWVVVUVISnbqampqWlpKOioaGfnp2cm5mYl5aVlJOSkZGQj4+Ojo6NjIuLiYiIh4aFhIKBgIB9fXx7eXl7e3x9fX5/gYKDhIxWaj5IKS4zOB8gJCYpKysrLCwtLi4uLy8XGBgYLzRShH99e3p5dXN1dnZ2d3Z3hnYadXZ2dnd3d3Z2dXV0cnBvcXZ6ent6eHZ0cnONdAh1dXV2dnd3eIV5B3p6eXl6enqEewR6e3t7hHwKfX59fn5/f4CBgYSCiIMYhIWFhoiIiIqLjI2Oj5CRk5OTlJSTkpKRh5AFkZKSk5SFlROWl5iZmZmampqZmJiamJiYmpubB1pbW11dLi6EL4UwhjGEMC4vLy8wMDAxMjIzMzQzNDU1Njc3ODk5Ojs7PDw9PkBAQUJDRERFRkZHRyQlJiYmhCUFJigpKCiQKYQohykMUlFRUlFQTkxMTEtKhEsBTIVNCUxLSUhISEdHRYRCAUGEQAQ/P0BAhj+OPoQ9BTw8PDt3hXYgdXRzcnFvb21raGVgXVhWXGp0dnh5foeSkIyIhoWEgoKEgSiAgIB/fn19e3dzcGxmYl9caoiPkZahzOSkr7CwsK+vr66trKurqquqhqmCqISnhagDp6amiKcHpqaoqamsrYSuQq2urq+wsLGytLW3uLu9vr/Bw8fKzc/S1NXZ29/j5+ns7/P29/uAgYSHiYuNj4SGxL2kmpOLd2lqbG5wcnR1dXZ3d4Z4BHd4d3iGd4R2Bnd3dnd4d4d4b3d3dnZ3eHh5enp7fH19fn6AgIGCg4SFhomKjZGWm5ybnqW3gpfNjpGPjIqHhYKA/fjz8Ozp5eHe2tfV0c/NysfFwb66uLWzsrGvraqop6WkoqCfnZybmZmZmJaVk5KQj46OjYyMi4qKiYiHh4aFhISDE4KBgYCAgP/+/Pv5+fj39/b19POE8SXv7u7t7Ovq6+rp6efl5OTl5OTk4+Ph4N/e3dzb2tra2dnY19bViNMF0tHQ0M+EzgzNzcvLysnJyMfGxseExgjFxMPDw8LDwoTBAr++hb0GvL27vLq6hLkLurq6ubi3t7a3t7iFtxu1tbS0tba2tbe2t7a2tba2uLm5uri5urq6u7uFvSa+vr+/wMHBwsLCwcDAw8W5hodsTUpJR0I7NTM0NDU2Nzc5Oz0+P4ZAAUGFQIRBMEA/Pz49PT0+PT0+Pnt7e3p5eXl4eHd3d3Z2dXV1dHRzc3JycnFxcG9vb25ubm1tbYVsR2tramlpaGhnaGZmZmdnaGloaWpqbW5vcnZ+m4z4sOeRrcvqhpSjssLP19vg5ejr7/P4/ICBgoPtwZSBbmhkY2BWUFJUVVZWhFcDWFhXiFgWV1dWVlZUUlFSU1VVVVRSUU9NTU9QT4VQh1GGUoVThFSGVQlUVFRVVVRVVFSXVYRWhFcEWFhaWodbBFpbW1uEWiBbWlpbXFxdXV5eXV1cXFtbWllYV1ZXV1ZWV1ZWVlhYWYV/voCpgciAwX8BgdyDiYQCg4DWfwOAgoOJhMqD/4K2ggGBsIDEfwSAgIGBhIKQg4SEA4OCgP9/r38CAgQAAeaE6IDp6Onq6+3u7+/v7e3s7Ozr6+zv8fL1+f6BgoOEhYaHiImLjpGUlpeYmZqam5udn6GhoqWnqqutsbS1trrCxsjKztLV2ODs8/X19ff7gIKDgf337ufh39vY1tPT0tHR0dLW2t3d29fT09bV0c/Nx8S+vLq1sK2rq6uqqaqrra+vrSCpp6Wko6WjoJyYk5CMiYeGhYSEhIOBgYGA/fz7+fr494T2NPj4+fr6+/v8/Pv6+Pb18/Hw7u7s6+rn5eXk4+Pj4d7c3NvZ3Nzb2tnWz8jJzNHS0tHQz9CFzxXOzc3MycnHxcLAvr28xsnKy83TixOLD4oOAQ+kDgcNDg4NDg4OhA+RDgQPDw8QhA+DB4QIIBI4uLm3s7CroKOpqqurqqqrqqurrK2srKusra2tq6yrhayFrQ+urausrKqrrKyrrKyrrKuErIOtha4fra6trKuqqqmnpqmtsbS2uMMfFg0ODQ0NDAsLCxYVFYUUAROIEgQTExITixIBEYcShREBEpERhxACIiGHIpEhkCABH4YgAx8fIIQfASCcH4IejR8BHoQfAR6JH4ggjiGFIoQjiSSDJYgmASWEJoQnhSgwKSkpJ0eKkJWYm5ycnZ2en6CfoaGioqSnqamno5+en6GioJ2bmpucnJuZlJGQjYyMhYtHioqJiYiIh4aGhIOCgYGA//79+/n39/b18/Lx8O3t7ern5ubm5OLi4uHf3Nvb2trZ1dTU09PS0NDP0NTacHE4OBscGxsODQyFC4IKhAmFCIQHggaEAx0CAgcstr++vr67s661ube3uLq6vL28vr6+vb/Av4bADr25tLW5v8LEw8K+ubi5h7ocubm4ubq6ubq6ury+vb6/v76+vLy9vr+/vr29vYS8Cr29vr+/wMHCw8KEw4TEA8PDwoTDhcQ9xcXHx8jKzMzNzc3MysnJx8fHyMrMztDS09TV1tbV1tfY2NjZ2tva3N7g4uPj5eXj4uHf39/c3NvZ2dvf4oCXmpucnJydnp6en6ChoqGgoJ+goJ+goaOlpaesr1hZWVlaW1tdXV9hYmNlZmdnaGlqa2xub3BwcnN0dXZ4enp6fYGFhoiLjZCSl56jpqako6NTU1JRoZ+bmJeampiVk5OSkI+PkJKVmJmZmJWVlpORj46Lh4SCgX58eXd1dHR0dh52d3h4d3Z1dHJycW9tamZiX11cW1paWVhYV1dXVlaErQOsrKuFqlqrrK2tra6urq2trKuqqKelpKWko6KhoJ+fn56cmpmWlJKTlZaXl5iXk4+OkJKUlZSVlJSTkpOSkZGQj46NjYyKiYeFgoGLjo+SlZpxJSkoKSkoKCkoKCgnJyiKJ4QmgieIJgEniyYHJyYmJicnJ4UohCmGKoUrKCwsLS0uLi8vMDEyMzM0NTY3NxwcHR0eHx80VJyPioaDfHFzd3h5e3uJfAR7e3t8hnsZent6enp7ent7enl6enl6ent6e3t6e3p6eoV7BHx8fXyHfSR+fX19gIOEhYiOoTA2IiIhIB8eHRwcNzY1NDMyMTAwLy4tLSyEKwoqKikpKCgoJycnhCaCJYUkhSOJIoghiSCKQIU/iz6IPYc8izuEOgY7Ojk5OTqEOQY4ODg3ODiNNwg2Nzc2Nzc2N4s2iDUBNoo1iTaRN4c4Ajk4hDmEOgU7Ozo6Ooc7MTw7PD09OlyAZWVlZmVjYWFiY2VlZmVmaGpsbm9vbWxrbGxta2hnZmdoaGdlY2JhYF+FXoJdhFxqW1taWllYWFdXVquqqaimpKOhn52cm5uamZmXlpWUk5GQj46MioiGhIKAf359fX5+f4CBgoOFilVrQEoqMDY7ICMmJycoKCkpKSoqKisrLCwtLi4vLzAwMBgZGRobGic0gn98enh2cm9ydIZzAnRzhHSIdg91c29sbHF2eXt6eXdzc3OFdAFzhXQJdXV0dXV1dnZ2hXcQdnd3eHl5eXh5eXh5eXl6eoR7gnyGfYZ+hX+EgIKBhIIJhIWGh4eHhoWDhYIJhIWHiYqMjY2OiI0Zjo6Oj5GSlJSTk5KRkI6OjY6NjY6Oj5CTlIRShlETUlJTVFNTU1JSUlBRUFFSUlNUVYUrEywsLC0tLi4uLy8wMDExMTIyMzOENCc1NjY2Nzc4OTo7Ozw9PTw8PT4/P0BAPz8eHx8gQUNDQkJDQ0JBQUGEQAVBQkRFRoRHDkhIR0dISEdGRkVFREREhkUSRkdGRUREQkFCQ0JCQUFBQD8+hz2EPD94eXl4eXh3d3h4d3d4eHh3d3h3d3Z2dnV1dXRzdHV0dHJxcG9vb21oZGBcV1NYaXJ0dXd8iZGPioaEgoGBgICGfyp+fXp3dnRvamVeV1Z2iI2RmKvShqioqamop6empaWkpKOioqKhoaChn5+EoA+fn5+en5+fnp6dnp+enp+EoAmfoKChoqOjpaeGpj2nqKmrrK6wsrO1uby+wMLDxsnMztHU2Nrd3+Pn6u7y9fj8gYWIi46Rkt/13bCglY6AbWxvb3FzdXZ3d3h4hHkHeHh5eXh3eIV3g3aFdwd1d3d1d3d3hniAeXl6ent8fH1/f3+AgYKChIWHiYyPlZucnJ6mt/aS0pKUko+MiIWCgPv38+/r6OPf29jV09DNycbDwL26t7W0sa+trKqopqWjoJ+dnJybmpmXlZSTkpGPjo2Mi4yLioqJiIeGhoWEhIODgoGBgYD//v38/Pv6+fj49vX09PXz8vEK8fDv7+/u7evp6YToFefl5eXk5OPh4eDe3dzc3Nvb2tna2ITXEtXU0tLRz8/Ozs3NzczNzczLyofJD8fFxMTDxcTFxMTEw8LBwYTAEMHAv769vb28vLy7vLq7uruIuQW4uLe3t4S4Cre3tra2tbW2treEthq1trW2t7a4uLi5ubm6urq7u7u8vb6/v7/AwITBG8DAwMPCqdSuU0xLSkY+ODc4OTk6Ozw8PT9CQ4dFEUZGRUVEREVFRkZEQ0NCQkFBhECJP4Y+DXt6eXl4eHh3dnV0c3OFcgxxcG9vbm1tbWxramqEaUZqa2trbGxucHN4kYPur+uUs9Hvh5emsLS3ur7BxMbJy87S2Nzg4+br8PX5/oCChIWHh7CBl3NoZGFeVVFUVVRUVFVVVlZWhFcFWFhXWFiEVw9WVFJSUlRUVFNSUExLTE2ETgFPhFCIUYJShVMGUlJTVFRVhFQFU1RTU1OIVAFVhlSPUwdUVFRVVVdWhFcWVlZWVVZVVVVWVldXV1hYWFdWVVVVVIRThFKHUwNSUlGEUAVRUVFSUp1/tICEgcmA138BgN+Dh4QCg4HUfwKCg4mExoP/grmCAYG/gK1/BICAgYGEgpmDhoQCg4H/f7B/AgIEAIDh4uHh4+Pj4N/d2tnZ2tza2drc3uDj5Obq7u7u7/Dw8vT19/uAg4SGiIqLi42NjY6PkJCQjo6PkpKUl5qam56ipaepra+wsbS5wcbHx8bEwsC+vLq3tLOvrK2urKysq6ysq6moqKmrr7O3urm2trW0sa2qqqejoqWnoZybm5qWlRuVlJSWmpubm5qXlpSTk5KQjoqGgoD//fn29/iE91n29fPy8fDw8O/u7u7t7Orq6+3t7e7u8PHw7u7s6ujn5OTi4N/d3NvZ19fZ2NbU09TT0tDOy8S+wsfIyMnJy8zNzMvLycjHxsXEwsC8urm5t73FxcbGy3smD4sOAQ+FDgEPmQ4FDQ4NDQ2FDoINkg6DD4oOiA+GBw0ICSFfube0sayio6iphaoEqamqqoSrAayEqwGqhaseqqurq6ytraytraysrKuqqqurrKyrrKyrq6ysrK2shK0drKyrqqimp6uvsrS3vDkVDg4NDQ0MCwsLFhYVFRWEFAgTExMSEhITE4oSBBESEhGEEoQRhBKTEQIQEYQQhiEBIpEhgiCEIYogAx8gH4ogBB8fHyCGH4IeiR8BHp4fAR6FH4Ieih+KIIYhhSIGIyMjJCQkhSUEJiYlJYYmhCcKKCcnKCgoKSkpKIUpcSonSpOZn6SorK6wtLi8v8HCxcXExMbKzczIwru4ur28ubS0tbi5trKrpaKioaCfnpyZmJeWlZSTkpKRkI6OjYyKiYmHhoWEg4KBgP/9/fz7+fb08/Hu6+ro5eTj4t/d3dzb29jW1dfccXM4Oh0cHRwOhg2DDIkLggqECQoICAgJCAgIBwMEhAM0AhavvL28u7qxrbO2tLW2trW3t7i4ubq6vLy8vb6+vL25tbCvtbu9vr69ubS0tre3ubi4uIS5Dbq6u728vL29vby9vb2GvIa7Cby8u7u8vL29voS/AsDBhMCEvwzAwL+/v8DAv8DBwcGEwjrBwL68u7u+vr/CxcjKycnIx8nKysvLzMvNzc/Q0M/Q0dDOz83MysnHxsTCvr6+v8HCxsjN0dXY3N/gX5KTlZaYmZiXl5aUk5OTlJOSkpSVmJqbnaCio6OjpKWmqaqqq1ZXWFhaW1xcXV5fX2BhYmJhYWJjZGVmaGhoam1wcnN2d3d3en6ChIWEgX58enl5eHd3dnRzc3V1dnZ3hnaAeHp8fX5+fXt5eXl4dnRzcnFwcXFua2loZmNiYWFhYmVoaWlnZWNjYmFhYF5cWVdVp6alo6OjpKWlpKSko6KioqOioqKhoaChoaGioaKio6OjpKSjo6KhoJ+dnp6dnJuanZiWlJOSkY6OkZKSkZCOiYeIi42Pj4+Qj4+OjY2NjIwZi4qJiIeEg4GAfYKLi4yNklpDJycnKCcnJ4smjSUBJoslASSEJQgmJiYlJSUmJYUmBicnJygoKIcphColKyssLCwtLi8wMDEyMzQ1NTYbHBwdHR4fHkJYk4uIhX90dHd4eoV7AXqJe4h6Anl4j3kCenuHeoR7KXx8fH18fX19fHx/g4SFiIyYTDIjIiIhHx4dHBw3NjU0MjIxMC8uLS0shCsGKikpKCgohCeDJoQlByQkJCMkIyOJIoYhAiAhiSACHz+EQIQ/gz6GP4Y+BD0+Pj6IPYQ8hzsPOjo7Ozs6Ojk6OTk5ODg5hjgINzg3Nzc4NziJNwY2NzY2NjeFNgE3hDYMNTU2NTY2NTY1NTU0jjWINgI3NoY3BDY3NzeHOIY5hzqCO4o8eD08PT09PztZemxtb3Bwbm9xdHZ5fH5+fn19f4KGh4WCf36AgH99enl6fHx6dXFvbWxsbGtra2ppZ2ZlZGNiYmFhYF9fXl1dXFtaWVlXV1ZVqaempaSjoJ+dm5mWlZKOi4mHhYOEg4OEhYaHiI5WbUNPLTQ6QCIkJIQlCiYmJycnKCgoKSmEKh0rLCwtLS4uLy8xMjIZGRoaGxsaL4h+end1dG9sb4txGXJyc3Nyc3NzcXJwbGlpbnR2d3d2dHBwcXKJc4J0jHWEdod3DXh3eHh4eXl6ent7e3qEewV6e3p6eoR7hnyCfYR+DX19fHp5eXh6e31+gIGEggKAgYSCBIGBgoOEhByFhoeGhoWEg4OCgYB/fHx8fX5/gYOGiYuMjpGRD01NTk5PT1BPT1BQT1BQUYZQBlFRUlNUVIVTDlRUVVUrKywsLS0tLi4uhC+HMBAxMjMzMzQ1NjY2Nzc3NjU2hjcENjU1NYc0DjU1NjY3ODg5Ojs7PD09hT6CP4U+hD8BQIg/Bj4/Pz4/QIg/Dj4+PTw7OnV1dnZ0dXV1hHaHdQl0dHR1dHR0c3OFdCV1dXR1dHNzc3JzcnJxcG5tamdkXFhTTldpcnNzdHuMjYmGhIGAhX8zfn58fHt6eXd1cGpmX1hTYIGIjJKhi9mho6KhoaGgoJ+enZ2cnJuamZmampmZmJeXmJiYhJcKlpeXl5aWl5aWl4SZSpiYl5iYmpudnp6fn5+goKGhoqOkpqeprK6wsbO2uLq+wMPGycrN0dTX29/j5+vw9fuAhIeKjY+RhueStaCXkINvbW9vcHJ1eHl4hXkFeHh4d3eGdgt3dnV1dnZ2d3d2eId3aXh4eXl6ent7fH19foCAgYGCg4SGh4qNkZidnJ6ksdfJwpWYlZGOioaDgf349PHs6OPe29fU0s3Kx8TBvrq2tLKxr62rqqelpKKgnp6dm5qYl5eVlJKQj46NjYyLiomJiYiHhoWFg4OCgoSBP4D///79/Pv5+ff39/b29fTz8/Lx8O/u7ezs6+zr6urq6Ofm5eXj4uHg39/e3t3c29va2tvb2djW1NTS0c7OzoXNhMwJy8vLysrKycjIhMclyMfGxcXFxMPDw8LDxMPCwsHAv8C/wMC+v768u7q7uru6u7q6uoS4Fbm4ubm4uLa3tra3tre3t7a1trS1tYa2Bre1tre2t4W4CLe4ubq7vL2+hL9BwcLCwsHBwcDCxKS1i1JPTk1JQj08PT5AQUNEREZISkxNTUxLSkxNTk1MS0tMTUxMS0pKSUhIR0ZGRUREQ0REQ0OEQoRBEkBAQD8/Pj49PT08eXh3dnV1c4RyUnFxcG5ubm1tbGxsbW9xcnV4j4r/wf+evt7/j5icnqCjpqirr7CxtLe6vr/DxsnN0dXY3N/k6e3y+Pz+gYOFiIqLgqjDemplYl9WUVNUU1NUVFOEVINVhlYGVVZTUU9PhVALT01JSUtMTE5OT0+EUINRhFICU1KKUwtSU1JSU1JTU1NSUo1ThVIDUVJRhlKEUwdUVFRTU1NShFMyVFRVVVVUU1JQUFBPT05NTU1OT09QUFBRUE9OTExLSkpJSEhGRUVFRERERUZHSElLTEykf/GA4H8CgILfg4iEAoKA0H8CgYOJhMSD/4K7ggGByICdfwSAgIGBhIKhg4eEAYL/f7F/AgIEAAjd3Nza2NXT0YbQgNLT1dja3N7f4N/f4ODi4+Xn5+nr7vP2+4CBgoOEhoaGhYWEhIOEhYWGhoiNj5CQk5aXl5mam5qanaKnqaurqqein56fnJqamZiXmJmYmZubmpiYlpWTkpCRkpOUl5iZmZiYmpual5WTko+PkJCOjY2PkJCQj42LioiHioyNjIqIFYeHh4aEg4D7+PPw7uzr6uzr7Ozq64XqC+vq6efm5ePj5OTkh+M+5OXl5OHe3dvb2tnY2dbU0tDOzczKys3My8nGvbG0vMPFx8fGxsXEw8PCw8PCwL27ube1tLKxvsDDxsroJw/FDgEPlQ4FDw8PEBCGCBAJEWS1s7KwrJ+ep6mqqqmphaqHqQSqqqqrhKoGqaqpqamqhKkHqKmpqKmpq4SqBKuqqqqJqxaqqqinqq6xs7W6aBMNDg0NDQwLCwsWhBWDFIgTARKGE4cShREEEhEREpIRAxAREYUQiSKKIQEgiyGJIIUfiSCLH4IelR+FHoYfAR6EHwUeHx4fHokfAR6LH4ggAiEghyEOIiIiIyMkJCQlJSUmJiaGJwgoKCgpKSoqKoUriCxqKk2dpayyt73Ey9DX3eHl5+vv8vT09fj8/ffw6+vw8e3p5+bl497X0czGw8PBvbq4t7WxraqmpaSjn56bmJaUkpGQjo6Mi4qHhYOCgYD//fz49vXx7evn5ebm5ePi4eDf4HR0Ox4eHx4ODo0NiQyFC4cKhQmHBA8HXbm8u7u5tq+xtLS0treEuCi3uLi4ubq6uri4uLeyrKyyuru5ube0r660tLW2tra3uLe4ubm5u7y8hrsHurq5uLm5uIa5Aru8hrsEvLy8u4i8N729vLy9vLy8u7y9vL29vLu6t7W1tba4vL/Cw8PCwL++vb2+v8DDxMfIyMfHx8bEwsC/vby7urmEuhW5urm5uru8vsHFyMvR09XZ2drb3d6EjxGOjYuMi4uMi4uLjI2PkZKUlYSWFZiYmpqbnZydnp+hoqRTVFRUVVdXWIRXEFZXV1hYWFpcXl5eYmVmZmiFaUZtcHJycG9taWhoaGZkY2NiYmRkZGVnZ2ZlZWNjYmFgYWFiZGZnZ2doaGpqaWhmZGNiYmNjYF5dXl1dXFtZV1ZWV1laW1pahVkTV1ZUpaGfnZycm5ubnJubnJydnIadB5ycnJubmpmFmiWbmpucnZycm5qYl5iXlpWVlJKQj4yJiIaJjo6NjIqIgoOHi4yMh4oUiYiIiIeFhIKAfnx6eoeIiImNpDuEJgclJSYlJSQkhSWTJIIjhCSEI4gkCCMjIyQkJSUlhSaFJ4QohCkoKiorKywsLS4uLzAxMjM0NDQ1GxwcHR4fHy1tlI2Jhn9zcnd4eXp6eoV7hXqFeQF6hHkJeHl4eHh5eXh5hXgFeXl6enmFeoV7gnyHfR+BhIWHjJJnKCIjIiEfHh0cHDc2NTQzMjAvLi4tLSwrhCoHKSkoKCgnJ4UmgyWFJIMjiSKDIYwgAUCFPwU+Pj8/QIY/hT4CPT6JPYk8BDs7OjqGO4Q6hjmNOAs3ODg4Nzc4ODc3OIo3hjYBN4U2ATWFNoo1gjaENQI2NYc2hzcENjc3NoQ3hjiDOYQ6gjuEPIQ9Bj4+Pj8/P4RAbUE+XIJ6fX+CgoGChouQlJeYm5+hoaChpKmppaCdn6OjoJuZmZqbmZSPi4eFg4KBgH58enh2dHJwb25samloZmVjYmBfXl1cW1lZWFdWVaimo6GfnJmVkpCNjYuLioqLi4uQV3JGKS82PCEiIiKEI4MkhCUbJiYnJycoKCgpKSoqKisrLCwtLS4uLzAxMjMZhBoRGxsuZ395dnRzb2pub29wcHCKcSBwcG9wcG9qZWVqcHFwcHFvbG1wcXFxcnJzc3JycnNzc4R0h3UDdHV0hHWEdoZ3iniEdwJ4d4Z4F3l6enl5eXh3dXR0dHV3eXt9fn18e3p6hXkVent8fXx8fXx7enh4d3Z3d3d2d3h4hXkRenp7e31/gIGFhoiKiYqLjY8TTU1NTk1NTE1NTU5OTU1NTk5PUIZRCFJRUlJSU1JThFQBVYQrASyNLQEuhDAVMjMzMzQ0MzMzMjM0MzQ0MzIyMTIyhDGGMAEyijEQMjM0NTU2Nzg6Ozs7Ojo5OYc4EDk6Ozs7Ojo5OTk6Ozs8PDyGOwM6c3KFcQ9ycnNzcnFycnFycnJzcnOFcgZxcnFxcnKGcwR0c3NyhHA/b25ua2lkYV1ZUExabXFydHmGjIqGgoGAf35+fX18e3p6enl3c29pY19cVVV4hoyRnei8mJmam5qZmJiXl5aVhJQBk4WSAZGGkBKRkI+PkJGRkZCQj46PkJGSkpKFkQiSk5SWlpeXmISZN5qbnZ6goqSmqaqsr7Gztbi8vsHEx8rO0NXZ3uPo7vT6/oGEiIuNkI6n2b2il4+Db2tvcXJ0dXeFeIV3BnZ1dnZ1dol1anZ1dXZ1dnZ3d3d4enp6e3x8fH1+f4CAgYOEhYaIiYyQlZyenqKsxMiSj5qXk5CMiIaD//n18e7o497a1tLQzMjFwr67t7WzsrCtq6mnpaShn52dm5qamJeWlJOSkZCPj46Mi4uKiYiHhYWEgwuCgoGAgf///fz7+ob4Lff29vX18/Lw8O/v7+7u7ezr6+jp6Ofm5uXl5OPh4N7d3Nzd3Nzc29va2djW1ITSBtDQ0M/OzYbOBs3NzMzLy4TKBcjIyMbGhcUExMTDw4fCBsHAv7++v4W+Dby7vLq6uru7urq6ubiEtwW4uLe4t4W2Cre3tbe1tba2trWHthW1t7e4uLi3uLm5ubq7vL29vr6/wMCEwoDBwsPDpKyCWlhWU0xFQ0RGSUpLTE5QUlNVVlhbW1pYV1haWlpYV1ZXWFdWVFJSUVBQT05NTUxLS0pJSUlIR0dGRURDQ0JCQkFBQEA/Pj09PTx5eHd0c3JycXBwb3BvcHBxdHd6iYD/w4WlxuiDjZCQkpSWmJucnp+hpKaoqq2vsiW1t7y/wMTHy8/S19rd4uXp7vT3+vyAg4WHiYuM1fmQcWhjYVlRhlMGVFRTU1JThlQQU1NTUlBOTk9RUVBPTktISYRMBU1OT1BPhVCEUQFSilEGUlJRUlJRiVIBU4VSiVEBUodRCFBQUFFRUFBQhFEJUlNUVFJQT01NhEwaTU5PT09OTk1LSkhHRURDQkJBQ0RERUVGRkWFRgxHR0dJSkpLS0pKTEymf++A4X8BguGDh4QCg4DNfwKAg4mEwYP/gr6CAYHLgJR/A4CAgYSCqYOHhAKDgP9/sX8CAgQABNDNzMuFyn3JysrLzc/R09XV2Nna29vd3uHh393f4OLm6e7y9fX19/r7+fj39/b19PX09fb5/ICDhIeKjYyNjo2NjY6OkZeZmZqZlpKQkJCNiomJiIeHiYmKi46OjYyMi4mIh4WEhISDhYeGhYWDgoODhYaFhYaHh4iJiIOAgIGCgYGBgISBF4CAgP7+/fz7/f77+Pfy7+vm5ebm5eTkhOUP5OTk5uXk4+Tj4uHg3t3ahdkr2NbW2NfY2dnX19bV09LQ0M7Ny8jDwsC/xMbHx8W+sbG7v8HCwL++vr6/v4S9D7u4trOwsK6uuLy/wsXjI4sOgw3IDoINhg6FD4QIFQkJFDezs7KxrZ+fpqenqaipqamqqoepAaiEqQKoqYSqhKgUp6ampqenp6ioqampqKmqq6usrKyFqyGqqqmop6yvsrW3wh8YDg0NDAwLCwoKFBQTExQTExITEhOEEggTEhITExMSE4USBhERERISEosRCRARERARERAQEYkQAiEghSEBIpohhiCGH4MgkB+DHrkfASCEHwMgHx+JIIQhiiKAIyIiIyMkJCUlJiYmJycoKSorLCwtLi8vMDExMjIyMzM0MV29xcbM0NPY4unt8viAhYeIiIqMkJSYmZiVlZaZm5mUkJGUk5GLh4OB/vz58erj3dfT0MzHwr23sa2qpqOhnZqYlpSSj42KiIWDgf/9+vXy8PDv7Ovo5uXj4OR1PT0IHh8fHg4ODg2EDg0NDQ0ODg0ODQ4NDg0Ohw2EDIcLiAoBC4YFIwQLr72+u7m3sbK0tLS1tbW0tLS2trW1tbS0tbO0sq+ooqexhLUPsauqr6+xsrKys7S1tbe3hLkOurm4ubm4ubm4ubi4uLeEuAG6iLkEurm6uYS4Cbe3uLe4t7i5uIS3Ebi3tbKvra6tsbS5u72/v76+iL0LvsDAvry6uba1s7KEtBe2tra3uLi4ubm6uru6ury7vL7Dx8rKzYTQBtLT09LS0AGGhIWHhliHiIqLjI2Mjo+PkJCRkpOTk5KTlJWXmZqbnJycnqOko6KioqGgn6CgoKGjp1VWVllbXV5fYF9eX2BgYWVmZmZlYl9eXV5cWllZWVhYWlpaW11dXFxcW1pZhVgNWVlaWVlYWFhZWlxcW4RaLltbWlZUVVZVVVVWVVRTU1JRUVGio6SkpKOkpKOinpuZl5aXlpWVlpaXmJiXmJiGmQ6XlZaVlZSUlJOTkpOTk4SVHJSUlJOSkZGQj46Ni4mHhIGBiIuLi4qHgH6EhoaEiB2Hh4aGhoWEhIOAfn17enh1foSFiIqbOSUlJCQlJYQklCOCIokjAyIjI4QiAiMiiyOCJIQlhSaFJykoKSkqKisrLCwsLS4uLzAxMjM0NDU3HBwdHh8fM0SVjomFgHNyd3h5eoV7CHp6enl6eXl5h3gEeXl6eoV5DHh4eHl5eHp6e3p6eoR7gnyFfSR+fX5+fn+EhYeJjp8yPCMiISAfHRwcHDY1NDMxMC8vLi0tKyuEKhApKSgoJycnJiYmJSUlJCQkiCMJIiIiISIiISEhhCABIYggBkBAQD8/QIo/Az4+P4Y+iz2EPIo7Cjo6Ojk6OTk5OjqIOQg6OTk5ODg4OYY4ATeLOAM3NziINwE2hDeHNgM1NjaPNQY2NTY2NTWJNgQ1NjY2hjeCOIQ5hDp5Ozw9Pj4+P0BBQkNERUdISElKTEp3tKanqKimp6iqrbG1XV9iZGZoamtsb3Bwb21ucHFwbmtpamtoZGFdW7Guq6iln5uXk5CNioeCfnp4dXNxbmxqaGVjYV9dW1tZWFeqp6OfnJqXlJKTk5STk5SXXDtIKzI5QCEhIYYiAiMihCOEJAUlJSYmJoQnAigphSoQKyssLS4uLi8wMTEyMxoaGoQbDySTfHd0c3Ftbm9vb3Bwb4ZwFm9wb25vbm9ua2ZiZm5wcHBxbmtrb2+EcAFxhXKGcwV0dHV0dYh0hnWFdoR3iHYFdXV1dnaIdQ5ycG9vcXN0eHp6eXh4eIR2NHd3eHl5enl3dXRycXBvbm5ub29xcXJzdHR1dnZ3d3h3d3h4eHp8foCAgYOEhISGhoeGh4aFTIRNhUwDTU5OhE8aUFBPUFBSUlFQUVJTVFVVVVZWVVZXV1ZWV1eEVhRXV1ZXWCwtLS8wMDAxMTAvLzAvL4YwAS+ILgEthC6HL4Quhi+FMAcxMTIyMzQ0hTUEMzIzM4U1gjaENyU4cW9vcHBycnJxcW9ub25ub25ubW5tbm5vcHFwcXBwcHFxcXBxhHBccXFxcnJxcHBxcXFwcHFwcG9vbWxqaGVhWlBKTmZxc3V1foqKhoKAgH99fHt7ent5eHZ1cm9pY11aUlBvhYmPmtetk5WVlJOTk5KRj4+Pjo6NjYyNjIuLi4yMi4qEiwyKiYqJioqLjIuMi4qFiwGMhYtOjIyNj5CRkZKSkpOTlJWWl5mbnqCipKaoqqyvsbS3ur7Bw8bL0NTZ3+Tr8PT4/oGEh4uOkNCdxKWXj4Vua29vcXR1dnZ2d3d2dnZ3dnZ2hXWCdIR1gHR1dXV0dHR1dXZ2eHl6eXp6fHx9fn+AgYGDg4WHh4mNkZednKCmtOqZ+Z6bl5OOioeEgf349e/r5eDc2NXRzMnEwb27uLWzsK6sqqilo6CenZ2cm5mXlpSTkpGRj46NjYyLiomHhoWFhYSEg4KBgYGAgP7+/fz8+/v6+ff49vb0Q/Tz8/Hy8vHw7+/u7e3s6+no6Ofn5eXl4uLh4N/f3d3c3d3c3NnY19jX19XV1NPS0dDR0tHR0tHR0dDQz8/Qz8/OzcyGygTJyMjHhMYsxcXFxMTEw8LBwcLBwcDAv76+vb6/vr29vbu7urq7u7q5ubm4tra2t7e2t7eEtQS0trW1hbSAtbS2tbe2tbW1tLS2tre2t7e2tri5u7y9vr/Bw8XHx8jJyMbHy7LJqXx0cGpgWVdYWllZLS4wMTI0NDU2Nzg4ODc3Nzg2NTQ0NDU1NDIvL15dXVtaWVhXVlVVU1FPTk1MS0lIR0ZFRURDQ0JCQEA/Pz57e3p5d3V0c3N0dHZ3eHtIjYaF0Ius0POFiImKi42PkJCRkpOVmJucnqCipaaoqauusrW3ur3AwsXIzNDT19vg5ent8vb4+/+ChYeJi4yLkemBcGdiXFNTiFSEUwxSU1JRUlFSUU9NSkyFTw1NSklMTU5PTk9PUFBQhVEFUlJRUVGGUglTU1JSU1NTUlOGUolRh1CCUYRPhFABT4VOAU+EURdPTUtKSUpJSktMTE1OTUxKSEdFRENCQYZCB0NDQ0RERUWERg1HR0dISElJSEpLS0tKhku4f9WA6X8BguODhoQCg4HLfwKCg4mEv4P/gr+CAYGMgJyBooCQfwOAgYGEgrCDh4QBg/9/sn8CAgQAK8XHxsXGxcXHycjKysrMzc3P0NLT1NbY2dfW1tfV2Nvd3+Hj5OTm6Ovt6ueF5oDl4+Lg4+bo6+7x+P6AgYOBgICCg4ODiIyNjY2Lh4SFhYOA/Pz7+vn5/P7/gISFg4KCgP37+fb19vb3+Pn5+vj29PX09Pj59e/u7Ozw9vf18/Py8u/s6urq6ert7/Dv8O/u7u/v8PHw8e/s6Ofl4+Pi4uPi4ODf3+Df3+Df4d/e3Svb2NbV1dXU09LQ0NDNzM7Ozs/Rz83OzczKysjHxsTCwb+9v8LBwsG7srK5hL0YvL28vLu6ubi5uLe1srCwr621vb6/wuAkhQ6HDccOgw+LDhcPDxARCAgJCQkTObWzsrCrnqCnqKenpoanC6anp6eoqKipqKiohqkMqqqpqamqqamqqaqohKqEqxisrKytrKyrq6uqp6etsbO2umsUDQ4NDAuFCg0TExMSEhESEREREhIRhBIBE4QSARGEEgUTExISEo4RBBARERCFEQoQEBARISEhIiEilSEFICEhISCFIYMghB+GIJ8fASCGH6QgAR+GIIkfAyAfH4YghCGHIgYjIyIjIySFJXQmJicoKSorLC0uLzAwMjQ1Njc5Ozw9PHn9goOFhomLjpKXmpucoaWsr66zvcrX2tXMzdnh4NnV1drd1crAu7OvrKWfmZKOi4mE//Tq39PKw723sayno5+cmZaTkY6Kh4SCgPz49fPy8Ozq6evtfD9BICEgHo4OCg0ODg4NDg4ODQ6PDYQMAgsMiQuGBTEEGLK+u7m3s7Cys7OysrOys7Kys7OzsrOysbCwsKykoauys7OysKqprrCwsbGysbO1hLYEt7a3tom3Bra3t7e2tYS2Z7e3uLe3t7i3t7e2tra1trS1trSzsrGwsLKzsq+qqKelqa+ztrm5uLm7urm4ury+vLy+u7i3s7CtrKusra6usLCwsbKys7W2uLm5uLi5u7q6vL6/wMLDxMbIyMnKy8vMzMvKysjHx8cqgIGBgYKBgoOEhIWFhYaFhYaHiIiIiYuLiomJiomMjpCRkpSUlZaXmZqZhJgBl4WYE5qcn6GipairVldZWFdXWFlZWVuEXQlbWFZWVlVTpaWEpA2nqalVV1dWVVZVqKelhaQzpqalpqSioaKio6anpaSioJ+ipKWkoqGioqGhoJ+dnZ2cnJ2amZeWk5GSlJaZmpmXlZSThJIEk5OSlIWTDJSVlJOSkpKRkJCPj4SNA4yLjYSOEo2Njo2OjY2MjIqIh4SCgH99goSGIIR9e4CDhIWFhIWEg4SDgYGAf358eXh4dnN6goSHipw5hCSEI44igiGWIgEhjiKEI4QkgyWEJjInJycoKCkqKiorLCwtLS4vMDAxMjM1Njg5HR4fICA6UZiNiYaBcXF3eHl6e3t7fHt7e4V6A3l5eoV5EHp5eXl6eXp6eXp6ent6e3qEe4R8AX2Ifil/foCFh4iLkmYqIiMiIB8eHR0cGzY1NDMxMC8uLSwsKysqKikpKCcnJ4QmASWHJIQjhyIIISEhICEgICGHIANAPz+FQIU/Dz4/Pj8/Pz4/Pj4+PT49PoQ9Azw9PYc8hTuCOoQ7hzoBOYQ6Azk6Ook5gjiLOQU4ODk5OYo4hTcBOIU3ijaJNQQ0NDU0hDWENIY1hDSDNYY2hDd1ODk6OTo8PT4/QEJERUhKTE5RVFZYWlxRgfJ5eXp5eHh6fX6Afnt7gIaMjY2NlZ6lpJ6eo6aopJ6alpSPiYaCf3t3cm5pZmViX1uxqaCYk46JhH97eHRxbmtpZmNhX11aWFZUpqKfnp2cnJ2cnaVpQ1IxOD9ChyGHIoQjhyQtJSUmJiYnJycoKCkpKioqKyssLS4uLi8vMDEyMzMaGhsbHBwcNIR4dXNyb2xuhm8ZcG9wcHBvcG9vb25ubWpjYmpvcHFxcGxscIVxAnBxh3KEc4p0AXWIdIt1h3RKc3NzcnNxcXBtbGtrbW9zdnd2dnV0c3R0dHV3dnZ3dnRyb21rampra21tbW5ubm9wcHFyc3R0c3R1dnV1dnd5eXp7fH1+fn5/gICFgQSAf3+AAktMi0sVTEtLS0xNTU1OTk9OTk1OTU9QUFFShFOEVAFThVI0UVFSUVJTVFVVVlhZLS0uLi0sLS0sLC0tLi0uLS0sLS0sLFdYWFdXV1hYWCwsLSwsLC1ZWoRZBFpaW1uFXIRdAVyEXRJfYmNiYmRmZmVlZWRjZGRlZmiIah5rbWxta2trampqa2trampqbG1vb25ub25vcHBxcXCGb1Vub3BwcG9wcHFwcG9vcXBwbm1rZmRfWlJISF9wcnR3fomGhIOBfn17fHt6enh2dXV0cW5oXlhTUGaEiI2V3baPkJCQjoyLi4qKiYmIiIiHhoeHhoeHhoYMhYWGhoWFhYSFhYWEiIVNhoWFhIWGh4iJiYmKioyMjY2OkJCRk5SWmJudn6KkpaaprK+ytLi8v8HEyc7V3OLn7PD0+v+ChYmNj+jQyqSXjoRtbHBwcXN0dnZ3eHiEdwV4d3Z2doV1hHaAd3d3eHh5eHl6eXp5ent7fH19fn+AgYKDhIaHiYuNkJWcnaGqwcSfmqCbl5KOioeFgfz28u7n4d7b19LOysfDvrq2tLOxr6yqp6SioJ+enJqZmJaVk5KRkI+Ojo2MiomIhoaFhYSEg4KCgYGA//79/f37+vn5+vn49/b19PT08vIL8fHw7+7u7ezr6umE6CLl5eTl4+Lh4N/f3t3d3N3c3Nva2NfX19XV1dbV1dTT1NTShdEq0NDQztDQz8/Pzs7Nzc3My8vLysrKycjHx8bFxsXFxMTDwcHAwcHBwMC/hL4Kvb2+vbu7urm4uYS4FLe1tLSztLWztLSys7Kys7OxsLGvhLBvsbGysbCwr7CxsrKysbK0srS2uLq8vsDCxMjLztDR0tTU1cmFea1UU1FLRkNAQEFBQD89PD5AQkNDRUhJSkhGRkdGRENBPTs6OTw8Ojo5NzU0MzIxMC9dW1lXVVRTUU9NTEtJSEZFRUNDQkFBQEA/hX1SfHt7fH+cqqDtncTm/oGChIWGh4iIiYqMjY+QkZOVlpiZmZuen6Klp6irrbCztbe6vL7CxsrO0dLX3N7j6O3w9Pf6/oGEh4qLjIi4rnxtZWBZVIRVBFRVVFSGU4RSF1FRT0xMT1FRUFBPSUhMTU5OTk9PT1FRhFIBUYhShlMBVIlThFIGUVFRUFBQh08NTk5NTk9QTk1MTExNTYROCE1MSklJR0dIhEkXSklHRUNBPj08PT4/P0BAQUFCQkNERESERYVGEEdISEhJSUlKSUpKS0tLTEyGS7x/loCJf4eA/3+VfwGC5YOFhAKDgch/AoCDioS7g/+Cw4ICgYCwgZmAi38DgIGBhIK2g4eEAYL/f7J/AgIEAITEKMbGxcbIycjIyMnKycrKy8vLysvMzMzNztDS1dbX2NnZ297e3Nzb29uE3CLa2dnb3eHi5ebq7e3w9PPz8vT5+/r9g4WGhYSB//79+vTvhO6A7e7v8fLz9/n29PXz7uzr6Obm5ujp6enq6ujn5+fl5enr6ubl4+Lg4N3a2dve4eTn5+jn5OLe3N3i5enp6ejn5OXm6enq6+jk4N/g4OHf3tva29za2NjX1tbW1dbV1dPT0dDOzczLycjIycnKyMjJycjJyMfGxcXDwb67u7m+wcAivr63ra63vL29vLu7urm5ubi4uLe1sa2tq6iwt7m8w+sjDokNAQ6IDZUOhB2FDgQcHR0djw6CD4gOgg+PDhUPDw8QEBARCAkJCQoUPLSzsK+om6GHpoSnhaYFp6ioqKeEqCypqqmoqampqKmoqKmpqaqqqqusrKytra2urayrq6qppqetsbK1vT0WDQ0MC4UKBBMTExKKEYoSBhESEhESEocRARKSEQMQIiGEIoQhAyIhIoQhASKNIYggASGOIIQfhR6FHwEghB8GIB8fIB8fhSABH50gDR8gICAfICAgHx8fICCZHwIgIYYghCGEIgEjhCR2JSYmJigpKisrKy0uLzAyMzU3Ojw/QUOOlZyfpKuytbq/xMjS2eDm7/f9g4mPl6GoqqaprrKyraimp6Wdk4mD++7k3NLIvLKrpJ2UjIT98+nd0srBubOspqCbl5OPjImGg4KA/vz6+fb3gEFDIiMiIB8fHh4PD5MOBg0ODg0NDYUOAw0NDogNhwyCC4QMhgYSCjO5vry4ta+ytrW0tLWzs7KxhbKEsR6uqaCgq7GysbCsqa2ysrS0tLO0tLW1tLS0tba2tbWEtgS1tra1hLYDt7a2hLUMtra1t7a2t7e2tLOyhbMOsrGwrqyrqKSjpaWrr7OEsjGztLO1t7i5ubi1s7Gtqqmpqqqrq6ysra2urq+wsbKytLa1tre3t7m4uLm6u72/wsbFhMcJxsXGx8jHxcTEhMOCxAR+fn1+hIAFgYKBgYGFgoWDhIQShYaIiYqLjI2NjpCQj5CPj5CQhJEikJGSk5WXmZuenp+ipaSjo6WnpqWnVVZWVlVTo6KhoJ2amoSbDpydn5+go6WjoqOhnp2chJolm5ybm5qamZiYmJeYnJ2cm5uamJmZmJWTkpSWmJqampiVlJORkIWRC42LiIiLjZCQkI+OhI0Hjo6Pjo+Pj4SOAY2EjkaNjYyMi4qKioiHiYmJioiJiYmKioqLiomIh4aEg4B+fXqAhIaFhIF7e3+Cg4OCgYGAgH9/fn59fHt5eHZ0cXqChIaKpz0jjCKEIYIilSEEQ0NCQ4UhhEOIIYUigiOFJC4lJSUmJiYnJycoKSkqKiwsLC0uLi8wMDEzNTY5Oh0eHyAiP1qajYmGfm1xdnh6hHsEfHx8e4p6CHt6e3p6e3t6iXuFfAd9fX1+fn5/hH4uf39+gYaHiY6ZUTokIyIgHx4dHBw3NjUzMjEvLi4tLCsqKikpKSgoKCcnJiYmJYckgyOIIoMhiiCCQJE/hz6GPQQ8PTw9iDyGO4I8hTuIOoQ5ATqNOQE6hTmCOos5iTiLN4g2iDWDNIUzkDKCM4U0eDU1NTY3Nzc4OTo8PkBCREZKTVBUV1tgZGlva7Wqr7K0t7e6u7u8wMPKysfExctoa2xxeH2Bf36ChYR+enp7dm1kX1yxqaKblI6HgHpzbmljXrOonpaPioSAfHh0cG1qZmNgXVpXVlSnqKiop6dfQFEwOD9BQkNDQokhAyIiIYQihSOGJAQlJSYmhCcEKCgpKYQqHyssLC0tLi8wMTIyMzUaGhscHBw3SX94dHFwbG5wb2+EcIVvhW4PbWxoY2Rrb3Bwb21rbnFxhHIEcXJyc4VyCXNzc3JzcnNzc4Z0UHV1dHV1dHR1dnV1dXR1dHRzcnJyc3JycnFxcXBtbWpoZ2hpbXByc3JxcG9vb3FycnN1dHBubWpnZmZnaGlqa2trbGxtbm5vb29xcnFyc3NyhHMTdHR1dnh6enx8fH18fH19fn18fIR7A3x9fQNLS0qJSwFKhUsETExMS4VMAU2EToRPBFBRUlGNUBJRUVNTVVVWV1lYV1dXWFZVViqFKwdVVlVVVVNTiVQFVVVWVleFVhBVVVZXWFhYWVlaWVpbWlpahVszWlpbWlpcXV5eX2JiYWBgYWFhYmRlZmdoZ2ZkZGZmaGhpaWhoaWlqaWpqa2xtbWxtbW5vhHCFb01ubm9vbm9vb3Bwb29wcHFwcG9ubGppZmFcUUlGXnBzc3SAjIiEgX9+fHt7enl5eXh3dHFtY1xXUUtigIWKlP/KiYqJiYmIh4aGhYWEg4SCBIGBgoKFgQWCgYCAgISBhYAE//7//oWACf/+/v6AgYODhIWGOIeHh4iKjI2Oj5CSlZeam52foaOnqauwsra6vcDHy8/W3OPp7fH1+4CEiIuO8+fLpJaNfmtucnJ1hHaDd4Z2B3d3dnZ1dnaHd2B4eHh5eHh5enp7e3x9fn+AgYKDhYaHiIqMjpCVnJ2jr9Lb8KGempWQi4iFgv/59O/p4t7b2NTOysXBvLq3tbOxrauppqOhn56dnJqZmJWTkpGRkI+OjYuLiYiHhYWEhIOEggeA/v/+/f78hPtK+vn49vb09fTz8vDx8PHv7u3s6+rq6enn6Ofm5eTk4+Hh3+De3+Dh4ODf39zc29rZ2djX2NbW1dPS09PT0tPS0tHS0tHR0tLS0dGEzxzOz87OzMzMy8rLy8rJyMfGxcXExMPDw8LBwb7AhL8Vvr28u7u8u7q6ubi3t7e2t7W1tLKyhLBQr66trK2sq6urqamop6moqKmqqquqqqirrKysrq+wr6+ytLe7vcDDxcjN0dXZ293d4eW2vnx3dnVybmtpaGhoZ2RjYV1ZVywsLC0wMzMyMTGFMIAuKygmJihPSkdFREFAPTs4NjMyMF1aWFZUVFJQTk1LSUhGRUNDQkJCQ0OFhYSDhZF9idqVv+T3+Pr6/4CAgIKDhYaGh4iKi4yNjo+Rk5WWl5manJ6hoqSlqKqtrrK0tbe5u77Dx8rN0NPW2t/k6e7y9vn8gIKFh4iJ+cuVdmtlXwNUVVaEVYJUhVMVUlNSUlJRUE1JSU9SUlFPTElLTk5OhE+CUIZRhFIEU1JSUodThVSEUwlSUlJRUlJSUVGEUDdPT05OTUxMS0pJSUlKTE5OTElIR0ZFRUZGR0hJSEZDQT06OTo6PDw8PT4+P0BAQUJCQ0NERUVFiEYHR0dHSElJSoRLBUpKSktLiEoBS8Z/hoD/f6t/AYKog4SChYOEgrGDhYQCg4HGfwKBg4mEuYP/gsWCk4GVgo6BloCGfwOAgYGIgriDhoQCg4H/f7J/AgIEAAbCwsPDw8SExSTGxcXGx8fIxsTExMPExcXGyMvNzc3Ozs/Q0tPT1NTU1tfW1dWE1CPT1tze3+Lk5OXl5ubn5+jr7e7u9Pr8//r08O7u7ejj4OLi5ITjbuXn6evu6+rq6OTh4d/d3N7e3d7f39/e3dzb2NfY297e3tzc2tjY19TS0dHQz9LV3+jo5ODa1dLR0dbc4uHj5OPh3+Hk5OLg397e3drZ2NfV1NTT0dHOzc7Pz9DPzs3My8vLyMfGxcXFxMTEw8LChMMuwsHAvry6ubWyur28vbuxqq63urq6u7q5uLi2tbW1tLKuq6ytq7G4ubq+eiANDocNBhsbGw0bG4QcARuGHAMdHRyGHYQeBB0dHRyOHYoOhw+EDoQPhA6HDwEOhA8CEBGEEg4JCQkKFTq2sK6rppqjp4SmAqeohKeFpgqnp6eoqKinp6iohKkEqqmqq4WqHaurq6ysra2ura2srKuqqKanr7K1uMUiCwwLCwoKhAmCE4UShRGJEpERARKLEYUQgiKOIQMgISCGIQQiISIihCECICGNIIIhhyAFHx8fHh6UH5sggx+HIAEfhiCVHwEeiB8IICAgHx8gICGFInEjIyMkJCQlJicnJygpKSorLCwuMDEyNDY4Oz9ARpWboKWtu8bQb3h/ipSfrLnK2+b4iZafpaalqbTAvLWsp6WimIn25NTFua6hlIb14NHCuK2jmJCIgffr4NTKwbuyq6WgmpWRkI2KiYeGhoxGSCMjI4chhCADHw8PhA4IDw4ODw4ODg+GDgcNDg4NDg4NhA6FDYMOhQ2EDAQNDQwMhA0zBgYGBw1jvbq5t7Ottba3tbWzs7OysbKysbKxsbGwraigoqyysrGwqaivsbGytLW1t7a1hLQesrKztLOztLS1trW1tbS1trW1trW1tba2trW0s7O1hLQts7O0s7Gwr66tqqahn6CkrLGzs7Gwr7CxsrS0t7ezsK+sp6SlpqenqKipqqusha0Jr7CwsLGztLW2hLcLuLm5urq9vcDBwsKGwwjCwcC+vr6/wIbBAnx9hH6Ffwd+fn9/gIB/hH4Pf4CAgIGChISEhYaGh4mJhIiIiQqKi42PkJGUlZWWhJdhmJmam5ybnqCgoaCdm5qZmZWSkJGSk5KTk5SVlpiam5qampmXlZWUk5KTk5SUlZWUlJOTkpCQkZOVlZWTlZORk5GNjIqJiIaHi4+Rj46MiomIiIeHiYyLi4iFg4KFiY2Mi4SJIIqLi4qJiYqKiouKiouKi4qKi4qKiYmJh4eGh4aGhYaFhIYZhYaFhISDg4J/fHl3foKDg4J9eXuAgYGAf4R+En19fXx8enh2dXRweYGChIhnQoQihSEHQkJBIUJCQ4RCgkGEQgJBQodBhEKEQQFChEEPQEFBQUJCQkFBICAgISEhhSKHIy4kJCQlJiYmJycoKSkqKyssLC0uLi8wMTM1Njg7PB4fISJBW5eNioZ8bXR2eHp7h3wDe3t8jXsIfHt7e3x8fXyFfYR+gn+GgC1/f36BhoiLkKM7ISMiISAfHh0cGzc1MzMxMDAvLS0sKyoqKSkoKCcnJiYlJSWEJIYjhyKIIYUggkCOP4Q+gj+EPgM9Pj6HPQw8PD08PDw7PDw8OzuFPIY7hjoEOTk5OpE5BTo6Ojk6ijmEOAU5ODg4N4Q4jTcGNjY3NzY2iTUHNDQzNDQzNIQzATKIM4U0hTWENm43Nzg6Ojw+QURGSU1QVVldYWZsc3lvx83V3uXr8v2ChoqOlJmfpKiutL1kbHJ1dXd6gYaEfXd2dnJrYrKjl46FfHJnX7Klm4+GfXRtZ2FcrqWclI2Hgnx3c29raGViYF9dXFtcbEdYNDxBQkJCQYRCBEFCQkKKIYMihSOGJAolJSUmJiYnJycohClEKiorKywtLS0uLi8wMTI0NTY2GxwcHDJlfXh1cm5qbnBwcG9vb3Bwbm9vb3Bvb29ua2diZGxwcHBva2xwcXFxcnFxcnKHcQZycnFycnKGdIJ1hnSHdQF0hHOEcjxxb29ubWpmY2NmaW5wcHBubW1tbnFycnNxb21qZ2RiY2RkZGVnaGhpaWprbGxsbm5vb3BxcXFycnJzcnKEcwZ0dXd4eXmEehB7e3p6eXh4eHl5enp6e3t8g0uESohLB0xMS0pKS0qFSwNMTUyFTYJOhE2CTolPHlBQUVJSUlNTU1RTUlJTU1JRUlRVVlVUU1NTUlJSUIhRAlJRhFIDU1NShVMHVFVWVldYWIRZBFhZWFiEWTtaWVhZWVhYV1ZWVlhZXWBgYWBeXV1eXV9iZGRlZmVkZGVnaGhpamtra2pqa2xsbW1ub29vbm5vbm5vcIRvA3Bvb4VuIG9ub29vbm1tbGxsaWdiW1FJR11wcnR2ho2GgoB/fn18hHseeHZ0cWxlXVVRS2SDiY2ZwOeFhYaFhIOCgYD+/v+AhP8C/vyF+mP5+vv6+Pf3+fn49/b29/f4+Pn29vX29/n4+Pf2+Pj5+fyAgIGCgoKBgoODhYaHiYqLjY+QkpSWmJqdnp+ipaitsLS4u8DGzdLY3uPo6/D5/oKGio3z4syjlYx9aXBydHV3d3iEdwh2dnd2dnZ1dYV2bHd3eHl4eHh5eXl6enp7e3x8fX5+f4GBgoOEhoeJi42Qlpqco7Pywo+gnJiUjomGg4D59O/o4t7b2NHNycTBvLi2tLKwraqopKKgnp6cmpmXlpSTkZGPj46OjYuKiIeGhYWFg4OCgoGA///9+4T8Ffr7+/n5+Pf28/Tz9PTy8fDv7uzr64TqB+jn5uXl5OOE4YLihuEI4N/e3Nva2tiG1jbU1dXV09PT0tLR0tLS0dHQ0NHS0dHR0NDPzs7Ozc3NzMvKysnJycfGxsXExMTDxMPDwsHAv7+EwB2/vr28vb28urq5uLe4t7e4t7a1tLKztLOzs7WzsoWxNLCws7GwsrKxsK+traysr7Cvr7CwsLO1uLzAw8bJzdLX2t7j5urv4IyZjY6RkIuKi0ZHR0eFRjNFREUkJCUoKysqKy0tLCkoJicmIj86NjMxLy0rKE1IREE9ODY0MjEvXVlXVlVTUE5OTEuESmVJSEZGR02QmOugzevw8PPz9vb3+Pr8/f2AgYKDhISGh4mKi4yNj5GTlJaXmJmbnZ+goaKjpqmsr7Gztbe6u8HEx8rLz9LU19rg5uvv8/f5/ICChYXU2IZvZ2NbVFZXVlZWVVVVVIVTE1JSUlFQT0xNUFJSUlBKS05QT0+GUAZRUVJSUVGGUohUhlODVIVTiFI3UVFQT05NTEpJSUpKS0xLR0ZEQkNFRkZISUZCPzs4Njc4ODk6Ozs8PT4/P0BAQEJCQ0NERUVGRoVHhEgISUlKSktKS0uGSoRJhUoDS0pK/3/2fwKAgomDBIKCgoOsgq+DhIQCg4HEfwGCioS3g/+Cx4KIgYyCkYOJgouBloCCgY+CuIOEhAKDgP9/sn8CAgQAEcDAwcHCw8LBwMC/wMDAv7/AhL9fwMDAw8XHxcfHx8jJysrMzc3NztDRz8/Ozc7Pzc3R0tTX297d3t7e3d7d3eDj5Obn6ezw7+nm5OXj39vW1tfY2dja29vd3+Di4+Ph4N7d29vY1dTU1NXX19bY19bW1dOE0QXV19bV1oTULdHOzc3KycnHyMzP09nc1tDLxsXDx9DZ3d/e3NrX1djb29fX2NXU0tHQ0M7NzIbLF8rJycjIyMfHxsXDwcHAwL+/vr++vr/AhL8Pvbq4tLGwuLm4t7KtqK+1hLgYt7a0tLOzsrCtq6qoqKayt7i6w0EeHA4NhRsEHBsbG4ociB0BHoQdARyNHYIchh2FHIYdiw4CDw6PDyUQEBARERISExMJCgoLFzu1sa+spJ2mqKinpqenqKioqaipqamqhqkBqISpCKqqqquqqqurhKwFra2trq2FrhCtrKqopKausbW5ZRMMDAsLhQoCFBOIEgERjBKVEQ0QEREQEBEQEREQECEQhCKEIQEgiCEDIiEijSGTII8fgh6NH4Yghx8GICAfHyAgix+FIAQfIB8fiCABH4QgAh8ghh8FHh4fHh6FHwUeHx4fH4YggiGFIm4jJCQkJScnKCkqKSosLC0uLzAyMzU3OTs9PoWSmKCmV19la3OBk6W6z+2MqcvxleKr1NvGws/t//DWyMm/lM6ljfPWuZ2H68qzoJGG9tzKvLKmnpaLgfXn2c3GvrexrKikop6cmZpPTicnJiUkJIYjhSIEISAgIIkPBQ4ODw4PkQ4FDQ0NDg2FDogNAQyFDRkODgYGC1q/v7y4sLC1tbOysbKzs7KzsrKyhLGAr6mhpq+xsbCtqayurrGysrGys7KysbKysbGxsK+02raRuuzXw7etsbGytLW3uLe4t7a2tLOzs7KztLSzs7GxsbCuqqKgoaWrsLOwrKutrrCytLS2s6+sp6SkpaampaWlpqenqKmpqqyurq6trq+wsbGwsbKzs7S2tre4ubu9vr8awL/AwMHBwMDAv768u7u6u7y9v8DAv7/AwMCEfIZ9hHwBe4Z8En18fX5+f39/gICBgoODg4SEhIuFX4iJiYuNjo6PkI6Oj46OkZKSk5WWl5qZlZOSk5KPjYqJi4yMi4yNjY6PkJGSkpCQj4+OjoyLi4yNjY+PkJGQkI+PjYyNjY6QkZCOj46MjIuJhoaEgoF/f36AgoOFh4WDhIIdhIaIh4eGg3+AgoaHiIaGhoeHiIaGh4eIiIeIiImGiAaHhoWFhISEg4SCG4OCg4KCgoGBgX99e3h2dX2BgYF/e3d7fn9/gIR/F359fHt7enh2dHNveH+ChYpHQ0MhIUJChEEGQEFBQEBAhkGPQIJBiECCP4tABEFBQkKEQwFEhiKEIywkJCQlJiYmJygpKSorLC0tLS4vMDIzNTY3OjseHyAiP1CWjIiEd252eHp7fIV9BHx8fH2EfAF7iHyFfQN8fH2HfoJ/h4Avf39+gYaIio9YJiIjISAfHh0cHDY1MzMxMC8uLSwsKyoqKSgoJycmJiYlJiUlJCSFIwIiI4UiiCGGIAMfPx+LPwNAPz+KPgI9Poc9kjyDO446Azk6OoU5ATqUOQQ4OTg5hDiDOYo4Bzc3Nzg3ODiHN4Q2BzU1NjY1NTWHNAkzMzM0MzQ0NDOENHE1NTU2NTY1NjY3Nzc4OTo7PD0/QERITE5QVVpgZGhtdnra1N7q84CGi5GYoa22wtXrgpGiuGyUaYOGenaAk52RgXp8d16NdWSwmoNwYaqThHdsYbChlYqBd29oYVuso5uUj4iCfXl1c3Bta2lxSFs1PoRChUGEQgZBQUJCQ0KHIYcihCOGJIIlhSYFJygoKSmEKhsrKywtLi4uLzAwMTI0NTU2NxwcLU56dXJwa2uFbolvGW5vb2xnYGVtb3BwbmptcHBwcXBvb3BwcG+GcBBtaotyXWVgYWprbnBxcnN0iHWEdDtzc3JycnFxcG9taWVjZGdsb3BubW1sbW9wcnJzcGxoZWJiY2NjZGRlZWVmZ2hpaWpra2tsbW5ub29vcIRxDXJyc3N0dHV1dnZ2d3eGeBB3d3Z2dnd3eHl6eXl6e3t7iUuFTAVLS0pKSoVJAUqKSwVMS0tMTYtOD09QT1BQUFFRUFFRUFBQUYVQA1FQT4VOB01MTE1NTk2ETghPT1BQUVBQT4VQDFFRUVJTVFRWVldXWIVXAlhZhVg+V1dXVVVUU1JRUVJTVVhcXl1cXFpZWVteYmRlZmVjYmRnaWtqampra2tsbG1tbW5ubm9wb3Bwb29vbm5ub26GbWVubm5tbWxtbm5tbGtnYFpRSERkcXN1e4yJhoSCf358e3p7enh2c29qY1tUUU5qg4iOo6/1/4CA//38+vr5+fj39/b29fX29vb18/Tz8fLx8fLy8vHx8vHw8O/w8fHw8PDv7/Dw8YTwRfHw8vX4+Pr6+/v8/P3+/4CChIWIiYqLjI6QkpOWmJqbnqGkp6uvs7i8wsfN09fc4efs9PuBhYmM8MDBopSNeWxxcnN1doR3B3Z2d3d4d3iFd4R4fXl4eXp6e3p6e3t8fX1+fn9/gIGCg4SFhoeHio2QlZiaobaPjJmemZWQi4eEgfv18Orj3dnV0czJxcG7t7SysK6sqaajoaCfnZuZmJaVkpCQj46OjYyLiYiHhoaFhIODgoKBgID/gP/+/Pz6+fr59vf3+Pb29PP08vHx7+/uhO0K7Ovq6Onn5uTj4oTjPOHi4ePh4uHi4ODe3dra2djX19bX1dbW1tTU1NPU1NPS0dHS0tHS0tHS0dDPz9DQ0M/Pzs7NzM3Ny8zLy4XJBMjHx8aExS3ExMTDwcDAwsHCwcG/vr++vb29u7q5uLi5uLe3trW2tbe1tre1s7O0srO0s7OEtAK2tYS0gLKytLW1tba4ubq6vcHEyMzO0dXa3uPl6O3xw82RkpWYTU1OUFFSVVhcY2g2OT9HJy8dIiQiIiUqLSkkIiQkHi0lIjw0LCUfOTYxLisoS0ZBPDk2NDIwMF5cWlhWVVVVVlRRT0xMT3SE3JvH4ubp6+vr7O7w8fL09vf5+fj8/4CBRYKCg4WHiYmKi4yNj5GSk5SUlpibnZ+foKKkp6qsrrGytbe6vsHExsrN0NPU19zg5ert8PT2+v6AgK+EfmxnYVdVV1dWVoRVFlRUU1NTUlJSU1FPTE5RUVFQTUhKTU2ETwFQhFGGUg9RVWlTTG53cXhcU1RUU1SEVQFWhFUQVFRUU1NUU1NTUlFQUE9OTYRMEE1NSkdDQ0JER0hISUZCPzyEOSM6Ojs7Ozw8PT4/Pj9AQUFBQkNDQ0VFRUZGR0dISEhJSUhJSIVJh0qESQNKS0uHTAFL/3/2fwWBgoKDg8GCpYOEhAKDgcF/AoCDiYS3gwKCg/+CxYKFgYuChIOChI6Fg4SFg4aCioGQgIKBlYK5gwSEhIOA/3+yfwICBAAFvb6/v7+EvjK9v7+9vby8vL28vLy9vr/AwcHBwsLDxMXFxsjHycvKy8vMycnJyMjKzc7P0dbW2NjX1oTVedbZ2tra3N3e4N/a2trY19bRzszOzc7Oz9DR0tTV1tfY2NfW1NPR0M/Nz9DOztDR0NDQ0dDQz87NzM7R09PS09PQztDNycTExcLCwcDBvbq6wcjKysXDvb29wsvS19jV0c7Nz9LT0tLOzc3NzMvLysvLycnJyMjHxsWExgrFw8K/vr28vLu7hboLu7y7u7q5t7Syr66EtiCxpaqztre1tLOzs7Szs7OysKypqKimsLW2u8sgHBwcHYUchB2HHAMdHRyHHYMciB2HHogdhhwGHR0dHh4ehB2MDowPBRAPDxAQhBEXEhIJCgoLFje0sK2qnKCmp6emp6eoqKmEqjupqquqqaipqqmqqqurq6qqq6uqq6urrKusrK2sraytrKyrqqmop6WnrbG0uWoUDQwMCwsKCgoUFBQTE4QSDREREhITExISEhESEhKXEQMQERGFEAshIREiIiEiISIiIpAhAiAhnCCHH4cgix8IHh8fIB8fHyCJHwUgHx8gH4ogAR+hIIUfhSCEH3ogICAhICAhISEiISIiIyMjJCUmJicoKCkqKywuLzAxMzU2Nzg6PTyBjZWdp6+7ZW17i6G61/6Yvv2vg9WGxqfZyqKD04r0svGx1Lvnv6C7gMCX9cqqj/bSuKaWhvXfzb+zppqOg/bo29PKxb+4tLGxWCwqKScnJiYlJYUkgiOFIoIhhSCEEIcPBQ4PDw4Pjw4BDYUOig2KDk8Nsr68uresr7KxsrOzs7Kys7Sys7SzsrGwqqCjsLOzs7Crr7Kys7Szs7OysbGwsK+wr6yM39zr2dSrs9Dz/6mmnY6ttLS1tra1tLW1tLOzhLIusbGwrqukn5+iqq+wrqytrq6vsLGzsq+rqKWko6SkpKWmpqWlpaaoqaqrq6ysroSvKbCxsbGys7O0tLS1tbe2uLm7u7y7u7u8vby7u7q6ubi4uLm5urq7vL29hL4BvQZ5enp7e3uEeoJ7iHoCeXqFewt8fH1+fn9/gIGAgYWDhYI8g4SFhYaJiImKiYmJiomJio2Njo6Pj5CRj4uLi4mJiYaEhIaFhYWGhoaHiIiIiYmKiYmIiIeHh4aHiIiJhYuEii6JiIiIiouLiomLiIWGhIKAgIB+fXx7fHl3d3x+gIKCgX5+f4CDhYeIhoF+fX+ChYSHhRCGhISFhIaGhoWEg4ODgoGChYGEgBZ/f4B/f35+fn17eHVzdHx+fXx6dXd8iH4QfX18e3l3dXNyb3uAgYSSMoRBBkBBQEA/QIU/BD4/P0COPw9APz9AQEA/Pj4/Pz8+Pz+EPoQ/Cj4+Pz8/QEBBQUGEQoJDhiKEIygkJCUmJycoKCkqKissLC0uLzEyMzU2Nzg6Hh8gITpClYyIg3JyeHl8h30KfH18fH19fXx7fI19hX6Cf4iALH9/f31/hYeKkF8sIiIhIB8dHBw3NjQyMTAvLi4sLCsqKSkoKCgnJiYmJSUlhCSCI4oihyGFIA1AQCBAQEA/QD9AP0A/hUCEP4U+ij2SPIQ7Bzo6Ozo6OjmIOoQ5gjqVOQE4jjmFOAE5izgDNzc4hjcINjc3Njc2NjaONYQ0hjVnNjY2Nzg4ODk5Ojs8PT9BQ0dKTE5SV1xgZWtltL/O2eTw/oaNl6O0xtr6karLf1iGUIZ6opVyVZxkqnecg5Z8lHhrgFuKbbCQemixmYN1aV6rnpKGe3JqY16xqKCZko2Ig399jVg0PYVDBkJBQUFCQohBh0KHIYcigyOFJIQlLSYmJicnKCgpKSoqKysrLCwtLi4vMDExMTI0NTY2Nzcni3l2c3BpbG5tbW1ubYduFW1ubWtoYWRucHBxbmpucHFwcXBwb4duVW1rVYeFhoqPeHyOm55wdF9dcHR0dHV1dXR1dHR0c3Jyc3JxcXFwbWhlZWdrbm9ta2ppa2xucHBwbWllYmFhYmJjY2VmZmdnZ2hoaGlqa2trbG1tbW6Eb4RwCHFxcnJyc3R0hXWEdod1A3Z2d4R4Bnl5enp6eYdMBUtLS0xMhEuCSoRJiUqGSwRMTExNiU4MT09PUE9PT1BPT1BQhU+ETgNPTkyFSwZJSUhKSUmFSgRLTExMh00PTk5NT1BPUFFSUlNTU1RUhVUsVlZWVVVWVVNTU1JRUlJQUFBPT01NTlJWWVtZWVZVVldcYWRlZWRkZmlqamqEawhsa2xtbW5vb4RwDm9ub25ubm9ub29ubm5th2w9bm5tbGpoZV9XTEVLa3FydYKMhoSDgoF/fn59fHp5dXFtZ11WUU51hoqSvpP2+Pf39/b09PPx8fHw7+/v8IXvBO7v7+6E7IXrAuzrhepS6+rp6Ojq6urp6Onq6+zu7/Dw8PHx8fLz9fb4+fz+gIKEhoiJiouNjpCSlJeZmp6go6aqr7W4vcPIzdHX3uPp8fmAhIiL4I+3n5OIb25ycnR1doR3D3h4eXh4eXh4eHd4eHh5eYV6hHuAfHx8fX19fn6AgIKDhIWGh4iLjJGYm6O3paacnJiTjYiEgf338Ork3tjU0MvHxL+7t7Wzr6yqp6Win52cm5qYl5WTkpGQkI6NjIqJiIaFhIODgoKCgYCA//+A//z7+vn4+fj39/b39vX19PPy8fDv7+/u7ezr7Ovo5+fn5uXk5OQt4+Ti4+Hj4uPi4uHf3t3c29ra2tja2NjX2NfX19jW1tXV1NPT1NTT0tLS0dHShNEK0NDPz9DPz9DOzojNG8zLy8vKy8nJx8fGxsXFxMPDwsHBwsHCwcC/voS/Cr6+vLy9vLy9vLuEuYC7vbu7u7q6urm6ubi3uLi4ubm4tra3t7e5ubi4uLm6vL2+wMPFyMrO0NTY3N/i3JORf4KGiYmMSEpLTVNZYW4+REwuHS8YJStHRS4aNS5SMTArRDo7IiAoHS0kOzEoITozMCwqJklEPzw6ODUzM2RjYmNhXlpXVlmfp4Oy1t/h4nXh4+Xn5+nr7O3t7e/y8/X29vf5/f7/gICCg4WGh4eIiYqNjo+RkpOWl5qbnZ+goqSmqKusr7K0t7m8v8PFyMzO0dPW297h5ejs7/L19/n5jcZ7a2VeU1VWVVZVVVRUU1RUU1RTUlJSUU5LTFFSUlFNR0tOT0+KUBxRUU8+Y2VzZ1hLUVx8sFxRRkdSVVVVVlZVVVZWhFWCVIRTBVJQT09PhE4rSkhFQ0RFRkdIRkM/PDk5OTo5Ojo7Ozw8Pj8/QEBAQUFBQEFCQkNDRERERYRGhkeGSAdJSUpKSklKh0kFSkpKS0uGTP9/9n/JgqODhIQCg4G/fwKAg4iEtYMDgoKD/4LHgoeBiIIcg4ODhIWFh4mMjY2Mi4yOjYyKjI2Ni4iGhYWEhISDhoKJgYuAAYGcgrqDs3+LfvR/AgIEAFC9vL28vLu7u7y9u7q6ubm6uru6ubq6vLu7vb2+vb6/v8HDw8PHyMfHx8bFxcXExMXGx8nMzM3Nzs/Ozs7P0tTW2drW1NPV1tTPzc/OzczJx4fGVMfHyMnKy8zMzM3My8rKycnHyMjIycrLy8vMzMvLzczLysrKzM3OzMvMy8nLysK/v768u7q5t7i1sK+vs7nAxsK/vL29wMbP1NPQzMnIy8vLysnKy4TJA8jHx4TGhsUOw8K/v768u7q5ubq6ubmEuBW3uLm4tbKwrbO2tbazqamwtLOzsrOEtA+ysbGvrKmop6Wwsra5cCCGHQ4cHR0cHB0dHRwdHRwcHIUdghybHYIekh2MDokPARCED4QQEREREhITCgoLFWOvrKuonKOohKYWp6ioqaqqqamoqampqqqpqqqqq6urqoarhawfq6yrraysrKupqKempauvs7htFw4NDAsLCwoKFBQVFIQThhIEExMSEogRgxKTEQUQESEhIYwihSEBIochBCAgISGKIIIhmSCWH4Ighx+eIAMhICCEIQUiIiEhIoUhFCAhISAgIB8gICAfHx8gIB8fICAfhyCFIXwiIyMkJSUmJygpKiwuLzAxNDY3ODs9P0GCipKfp7K+zN7yhZi00vSSq9SPxJH485/xobePt/bkz5XFmYLbl8DXgvuo+baN4sCljPLQtaOViPbcy7usnpGIgfju5NzX02cyMS4sKyopKCcnJiYlJSQkJCMjIiIjIiIiISEhhSABH4kPCg4PDw4PDw4PDw+FDgQPDg4OhA0BDoQNhg6EDYUOJg8PDhy3u7m4tKqys7OzsbGxsrSzs7OysrKxsKuko7K0tbSwrbKzhLSEswGyhLETr6+n7L+/rsurqLqVw/uF/tbtqYS0QbWzs7Szs7OxsLCwr66rpqGhqK6wr66qq62wsLKzs66opaOjoqOkpaSlpaWmqKanqKioqqqrrK2ur66ur7CxsrKxhbMLtLW0tbW1tre3treEuAS3t7e2hLUPtre4t7a3uLq6u7y8vL28hHkBeIR5Anp5iXgReXl5eHd4eHp6ent8fH5+fn+LgB+BgYGDhIWFhoaFhoeGh4iIiYqJiImJi4mGhYaFhIOChoGEgAOBgYKGg0mCgoGBgYCAgYGBg4WFhoaHhoaGhISDg4OEhIWEg4SDgoGAfn19fHt6eHh3eHZzc3R2eX6Afn18fX6Ag4WGhYN/enp+gYKDg4OEiYMYhISDg4KDgoGBgH+AgH9/f35+fn1+fX5+hX0nfHl2dHB2e3p7enV0eXx9fn5/f39+fn18e3l3dnNxb36AgodePEBAhD+JPgE/hD4BPYo+AT+EPgk9Pj4+PT49Pj6EPYc+DD09Pj4/Pj8/P0BAQIRBBUJBICEhhCI2IyMkJCUlJiYnJygpKiorLCwtLjAxMjQ1Njg6Ox4fIDRnkouGgG90eXl7fH19fH18fXx8fX18in0Efn19fYR+hH+EgCeBgICBgYB/f36AhYiLkWIwIyIhIB8dHRw2NTQyMDAuLi0sKysqKSiFJwYmJiUlJCWGIwUiIiIhIoUhASCEIQwgICBBQUFAQD9AQD+IQAs/QD8/Pj8/Pj8+PoY9DTw8PD08PT08PT08PT2EPAQ7Ozw8iDuQOow5hjqMOYI6izkIODg4OTk5ODmEOIk5Azg3OIQ3hjaNNQs0NTQ1NTU2NjU2NoQ3YTg4OTk6Ozw8PkBDRkhLTVFVWV6jkJuqtsLQ3e3+jp6wxt18j61vjmGfmmOkeIxtgqiuoG+JaWStdI6LXbV3r39joohzYqWNe25jW6eYjIF3b2hjXrWspZ6Zp2M6QkVFRESEQwZCQkFCQkKFQQFAjEGCIIQhhyKDI4YkMyUlJiYmJycnKCgpKSorKyssLCwtLi8wMTEyMzM0NDU2NkeJe3VzcGhtbm5tbW5tbW5uboRtDGtrZ2FibW9vcG1rboZvQG5vb25vbm5ubWtljXiCeo5zdHpjjbJcqIabb3RzdHR1dHR0c3NzcnFxcHBwbGlmZmpucG5tamlqbGxubm9sZ2OFYRliY2RkZWZnZ2hpampqa2pqa2xsbG1tbm5uhW8GcHBwcXFxhnICc3KGcwF0hHMBdIR1A3Z2d4V4gnmETYJMhU2CTIVLCUpJSkpKSUlKSYZKg0uETIVNhE4ET1BQUYRQBU9PUE9PhU6ETQtMS0pKSUlIR0dGRodHA0hISYRKBEtKSkqES4RMFE1OTk9QUFBRU1JRUlJSU1NTUlFShFEqUFBQT05OTk1LS0pJSkpNUFZZWFdWVlZZXWNmZmZoamxsbG1tbG1ubW5uhm9ecG9vbm5vbm9vb25vbm1sbGxtbW1ubm5tbGtpaGZjXFBGQVtwcXN2i4qGhYODgYB/fn18end0bmVcVFBPfomNl6bS8fHx8O/v7Orr6+rq6unn6enp5+fo6Ojn5+fo54TmiORV4+Tk4+Pi4+Tk5uTk4+Tl5+fn6enp5+fn6Ont8PL19/j8/oCBg4SFhoiKi4yOkZOWmJqdoKOnrLG2ur/DyM7U2uLo8Pf+g4eKv7StmY+Ba3Bzc3V2d4R4AXmGeAh3eHh3d3h5eYZ6AXuEfHJ9fX5+f4CCgoSFhoaHio2RmJyis620m5qVkIuHg4D48uzn4NrV0s3JxcC7trSxrqypp6OhnpycmpiXlZORkZCQjo2Mi4qIhoWEhIODgoGAgP/+/fz7+vr5+Pj39/j39/b19PTy8vDw8O/v7+7r6+rp6OaE5wPl5OOF5Cvl5OPi4ODd3t7d3Nzc29vb2tna2dnY2NfX1tXU1NTV1dXU09LR09LS09PShNMM1NTT09HR0dDR0NDPhM4Ez87NzYbMFMvKycfGxsfGxsXEw8LCxMXExMXEhMMIwsHAwb+/wL+Evg69vr+9v76/vr6/vr6/voS8gLq8u7m6urq5ubi5uLa3uLi4ubu+v8LCxcjM0tbYwcpjYmdraWlvd3o/Q0ZNUi40PycuHS4qGSgiMystMkBELzUeIkYyNy4bOSQ2KyE1LCUfNjAsKignR0M+Ozs6OTk4bGZhX2Onq4e21drb293f39/g4uPl5ebo6enq6+3w8vPyS/L09vf4+Pr9gIGCg4SFhoiKjI2PkJKTlZeam52foKKjpaeprK+ws7W4u73AwsXIys/S1dnb3uHk6Ont8PLz8/modmliW1FWVlVVVIRThlIVUVFPTEtQUVBPTElNTk9PT1BQUFFRhVAdT09IX09MQUs9TE03SlctWVl3UlZWVldXVlZWVVaEVTJUU1JQT05OUFFQTkxHRURGRkdISUVAPDg4Nzg5Ojo7Ozw8PTw9Pj9BQUJCQ0NERENERIVFiUYDR0ZHhEiESQFIjkkISkpLS0xMTU3/f/V/AYDLgqODBYSEhIOAvX8CgIOIhLKD/4LMgoqBhYIfg4ODhISFhYaIio2OjoyLjY6OjIyOjo6MiYeFhYSEhISDhoKJgYaAAYGigreDAYKyf4t+BH9+fn7xfwICBABQuru7uru8urq5t7i3uLi2tbO1tba2tba3uLm5ury8vL6/wMHDxcXFxMLCwsHBwsLCw8THyMjJyMfIyMfIy83O0dPT0dDPzMzKyMnIx8XEwb+EwAnBwL/AwMHCxMWExoDFxcTDw8LBwMDAwcLDxMXFxsfGxsbFxcTFxMXEx8TExsbFw7+9vby7uri0s7Oyr6+urauprba/wb66uLe6wsnQ0M7MxcTGx8jIyMXFxcbGxMTFxMPEw8PDwsHAv7++vr67urm6ubq5uLe1tbW2tbS0s7Kwraystra2taqmqq+wsh+ytLW2trW0sa+sqKanpqe1t7rCPx4cHR0dHBwcGxwbhhwJHR0dHB0dHBwdhByHHQMcHRyNHYMejx0BHIcdig6MDxgQDxAQEBESEhMTCgoKE7etrKqhm6eop6aEpwaoqampqKiEqQ2qqqqrqqqqqauqq6urhawjq6usq6ytra2sqqqpp6WrsLa7bBcODg0MCwsLFRUVFBQTEhOHEggTEhISERESEpERChAREBAQERARISGMIgMhIiGIIoghAyAhIIkhByAhISAgISGGIIUfCyAgIB8gICAfIB8ghh8KICAgHx8gIB8gIJIfhCCGH4sgiiGFIoghAiAhhSCPHwogICAfHyAgICEihCNtJCQlJycpKywuMjU3ODg6PT9AhpOYn6awvMfW6PqKnr7lhqC56JnQoqCQybfUrd34waLM0YKXvcW8iKbG2I3OnvjFppH+276nlon64tHFuKeZkYmC9+10cTc0MjAvLi0sKyopKCcnJyYlJCQjI4QiiCEFICAfICCND4kOAQ+ODgcNDg4NDQ4NhQ6EDwsOHbS9urayr7W1s4WyM7GxsLCvrq2sqKCfrrS0tbGqsbW1tLKysbCxsrGysLCvr7Cvrq2hkof78f2AgN/bgpGlsISxAbOFsSuwr66tq6minqGnrbCysKyprrCxs7Sxq6OenZ+hoqKio6Wmp6iop6mqq6qqhasBrYSuBa+vsLGxhbILsbKxsbKzs7S0s7SEswa0s7OztLKHswy0tba2tre4ubm6u7sEd3h4d4V4Dnd3dnd3dnZ1dnZ3d3Z2hncreHh6e3t8fX5+fX18fX1+fn5/f3+AgIGCg4SDgoOEg4SFhYeHh4aFhIODgYSAGX5+fHt8fXx8fXx7e3x8fH1+f35+fn19fXyEe0J8fH19fn5/gIGBgYCAfn5/f3+AgIB+fn5/fnt6enl5eHh3dnZ2dXV0c3NycXR4fX59e3x9f4GEhoWEgHt6e36AgIGFgoWDB4KBgYKCgICFfwp9fXx8e3x7e3t6hnsRenl3dHJyenp7e3V0dnp8fHyFfRB8fHx6dnRycHF+gIOJSz4/iD4EPT09Pok9EDw9PTw8PT09PDw9PTw8PD2EPIQ9gzyKPYQ+AT+EPoM/hEA3QUFCISEiIiIjIyMkJCUmJicoKSoqKywsLS4vMDIzNDY3OToeHyAqqpCKhnlud3h6e319fHx9fIR9Cn59fX1+fX5+fn2IfoJ/hIA3gYCAgYKCgYGAf39/homLkGAxIyMhIB4dHDg3NTQyMS8uLSwrKyopKSgoJycnJiYmJSQkJCMjI4YihCEEICAhIYQgiUCCP4VAAz8/QIQ/Az4/P4Y+hz0DPD09hDwFPT08PD2HPIQ7BDo7OzuMOg05OTk6Ojo5Ojk5OTo5iTqPOYU6ATmIOgE5hDqDOYQ6hzkSODg4Nzc3ODc4NzY2Njc3NjY2hjUFNjU0NTWJNnE3Nzc4ODk7Ojw9P0FCQ0ZISkxPUIF2e4SNl6Sywc/fe4qftGZ3iq1xkWZjWX+ApYmqsYx7mZpZb5GWiFZnjqBlk26vkXtps5eCc2dfr6CWjIB2b2liXLCxZ3dDR0dGRkVFRURERENDQkJCQUFBQEBBQYVADUE/P0BAQUFBQkEgICCEIYUigyOFJIQlAyYmJ4QoEykpKSorKywsLS0uLi8wMDEyMzOENAtAg3p1cm5rcG9ubYVsGm1sbGtramlmYF9rbm5vbGltb3Bvb25ubm9viG4YbWxmXFainKRTU46QVV1qcXJyc3JzcnNyhHEPcG9ua2dkZWpubm9tamlrhG0Ya2ljX15fYGFiY2NkZGRlZ2dpaWpqamtrhGyEbQtubm9ubm9ub25vboRvBHBwcHGEcIRxBHBxcXCEcQ9yc3JzdHV1dXZ2d3d3eHiFTRpOTU1NTE1NTE1LS0pLS0tKSUlKSkpJSUpKSYRKAUuGTAdNTExNTk5PhFADUVFQhk+ETgxMTEtKS0pKSklISEeIRgdFRUVGRUZGhkcBSIRHC0hJSUpLS0xNTU1OhU02Tk5NTk5PT05PT09OTk1NTUxMS0pKSUhHSEhIR0dLUFZZWFZWVlhcX2VoamtucnJwb29wcHBxhHADb3BwhG+DboRtbmxsbG1tbG1ubW1sa2tramloZmJXSUJHa29wc4qOh4aDgoCAf35+fXx6d3FqXlRQW4eMkaTB5uvp6Onm5eXk4+Pk4+Pi4uHj4+Ph4uPi4eDh4eHg4eDg4N/f3+Df3N3e3t/d3N3d3t/f397f39/hh+I94+Pl6Ort8PL09vf3+vz/gYKEhYaIioyOkZOVl5qdoKSor7W4vsHGzNPa4efu9PyChomR9KSUi3hrcnN0doV4Bnl5eHl4eYZ4gHl5enp6e3p7e3t8fH19fX5/gICBgoSEhYaHiYyRl5mgsqW0m5mUj4mFgv338Ork3dfTz8vGwbu2srGvq6iloqCfnZyZmJaVk5CPjo2MjIqJh4aFhISDgoKBgP/+/Pr7+/n5+Pb39/f29fX09PTy8vLx8PDv7u3t7Ovp5+bl5eblhOYT5eXk5OXk5OPj4uHh39/e3dzb3ITaHdnZ2NfX2NjY19fW1tXU1NXW1tXU1dTU1NXU1dXVhNMv1NTT0tHR0NHR0NHQ0M/Pz9DQzs/Pzs3Oz8/OzczLysvLysrKycjGyMjJycfHxcWExoDEwsPDxMPDxMPBwMHCxMPCw8HBwsHBwsHAv8C+v769vLq5uLi4t7e2tre4urq7vL3AwMLDw8fKzKiJR0ZJSkhLUlthZjU5Oz8jJy04JC0cHhojITc1RTgoLUE+GyQ4PTccGS00Hi4kPDMqIjo1MCwqKU1JRkI/Pj05NDNniZX2qnXM0tPV19fZ293d3N3h4eDf4eTl5ebp6+3u7u3s7fH09fb2+P3+gIGChISFh4qMjY+QkZOUlZiZnJ6hoqSmqKqsrq+ytbe5u7/BxMfJy87Q1Njd4eLl5ujq7O7s2J1zaWJZUlZVVFRTVFRTU1NSUlFRUE9OS0yEUANOSU6IUB1RUVFQUVFPT05NTEQ8N2VgYDExWGI7QkxTVFRVVYRWTlVWVlZVVFNSUE5OUFFQUE1IRkZHSEhJR0Q+Ojk6Ojk6Ozs8PDw+Pz4/QEBAQUJCQ0NDREVGRUVFRkZFRkZGR0ZHR0dGRkZHR0dIR0hHR4lIEklJSEhIR0dISEhJSUpKSktMTf9/9X8Bgc+CoIMEhISEg7x/AoCDh4Swg/+Cz4KLgYSChIMbhISFhoeIi42OjYuMjo6NjI2Ojo2LiIaFhYSEhIOGgoqBBICAgYGmgraDAYK2fwd+fn5/f35+9X8CAgQAMrW2t7i5ubq5ubi2tba1s7GysrK0tLW1trW2tri4uLu9vb/BwsDBwsC/vr2+vr/AwMDChMMnwsPEwsLDxcjM0M7KycnHx8XDw8TCwMC+vbu8vby9vLu7vLu8vb7AhMEFwL++vr2EvAe7vLy9vr/AhMGAwMHBwcC/vr6/wcHBwsC/vbu5uLi3trKxr7Cvra6urqupqKars7u8uri4urvCyszMycW+v8LEw8PCw8PCwsHCwsLAv7++vr69vLu8urq7urq5uLe1tLSztLOzs7KysbGwrqu1tra1q6aor7KxsbO0tbWzsbKxraqqqaivtra7aiACHR2EHIIbhxwDHRwcih0BHIQdghyTHQMeHR6GHYQeCB0dHRwdHBwchx2LDoMPhA6GDxwQERERExMJCgohs62rqZ2gp6anp6eop6ipqaqpjaoCq6qHqyCsq6ytra2ur66tqqelo6qxtLhoFw8ODQ0MCwsWFRQUE4kSARGGEpARBBAQERCEEYQhgiKFIQQiISIhhCKHIYMihSGCIJEhhCCGHwogHx8fIB8fHyAghh8JIB8fICAgHyAfhiCQHwIgH4UghB+FIIYhgiCKIYMiiyGIIIQfdx4fHx4fICAfIB8gHyAgICEhIiIjJCUlJScoKiosLzE0Njc5Ojw+PvuIjpGXoa25xdTl9ISUqsnuiqPF+qLly6KppoG8ibXViJ+jnIiD1oCisMnytIrWrJH61reaiv/s1sS1qaKdl5CLh346ODUzMjEwLi0sLCsqhCkMJycmJiUlJCMjIyIiiCGEIAEfjQ+EDoMPkg4FDQ0NDg2LDg4eubq4tq+utLWzs7GxsISuLK2trKypopuqsrKzr6mvsrGxsbCvr66urq2vr7CwsK+urq6trK2trKyura2uhK8FsbCwsK+EsTuvraqnoZuaoqqtraypqayusLGyrqikoKCfnqGhoqKioaKjpqipqqmoqauqqqusrayrrK+ura+vr7CwsYSwBbGxsLGwhbEOsLCxsbGwsbCwsLGwsLCFsQiysrKxsrOztYS2BHV2dnaGdwZ2dXV2dXSFdQJ2dYZ2OXd4eXl5fH17e3x7fHt7fH19fX5+f4CBgYKBgICBgICCg4SGhYKCgoGAf3x8fHt6eXl5eHl5eHl5eYR4CXl5enp7ent6eYh4D3l5ent6e3x8e3t6e3t7eoR7M3x7enp5eXh3d3d2dnZ1dXN0dHN0c3RzcnBwdHl8fX18fX5+gYOEhIB8eXt9f4CBgYGCg4WCg4CFfwh+fn59fXx7eoR5GXh4eXl5eHd3dnZ0cW52eXl4dHJ0eXp7e3uGfBB7eXd1cm94goSGUzc+Pj0+hD2HPII7hDwBO4U8iTsBPIg7ATyFO4k8BD09PT6FPQU+Pj4/P4RABUFCQkIhhCIrIyQkJCUmJicoKSkqKyssLi8wMTIzNTc5Oh0fHzuZjYiDcHJ4eXx9fX5+fYx+in+GgCmBgYKCg4ODgoGBgICGiYuRWy8kIyIgHx0dODY0MzEwLy4tLCsqKikpKIQnAyYlJYQkgyOFIoUhhSABQYVABUFAQD9AhT+CQIc/iT6JPYc8AT2GPII7hDyFOwU6Ojs6O486hjmFOoQ5ATqGOQQ6OTo5ijoCOzqGO4g6gjmFOoY5ijiFN4w2BDc2NjeENmE3Nzc4OTk7PDw+P0BBQkNERUZISUbNXmJobXR/i5agrLplcoOXrWV2ja5vlnxsdHhroW2AqHmKgHBydrFgcIKer31fm4RxvJuEc2i9r6CUiYB6dG5nYWZ0QkdHRkVFREREhkOFQgpBQUBBQUFAPz8+hT+EQAZBQUAgICCHIQUiIiIjI4UkhSUBJoQnFCgpKSkqKisrLCwtLi4uLy8wMDEyhDMOO4B5dXJtbG9vbm5tbWyEaw5qamppZ2BcaW5ub2xobIZuB21tbW5tbm6Eb4RuDW1ubm5tbm5ub29wcXGGci1xcXFwb25saGRkaW1ubmxpaGtsbW5ua2dhX2BgX2FhYmJjZGRlZ2doaGlra2uEbAZrbGttbW2EbgFvi26IbxRubm5vb25vbm9vb3BwcHFycnJzc4R0BHV1dnYSS0xMS0xNTUxNTUxLTExKSUpKhEuFSYRKA0tKSoRLAkxLhUwCTU6GTwdOT09OT01OhE8RTkxLS0pKSkhHSEdGRkZFRUWGRolFAUaERYZGC0dHSEhJSkpLSkpJhUoDSUpLhkwCSkuGSitJSElIR0hISEZGRkdKUFZZWFhXV1hdZWpsbXJ1dHNyc3NycXJxcHBxcHJxhG8Ibm1tbGxrbGyEawNqamuGahFpZ2JXTEJBYW5wc4aRjIaEgoSBHICAf315cmhfWVRrjJKZgrXk5OPi4uLg3t7f3t6F3RXb3Nzb2t3e3dzd3d3c29vb2trb2tmJ2Ava2tnZ2NjZ2tvc3ITdSd7g4uLl5ufp6uvt7fDw8fP09/v+gYOEhoeKjY+RlJaYm5+kqa60trrAxcrS2uHl7PT9goaEs8Scj4dwbnJydXd4eXh4eXp6eXqHeYV6Xnt7fHx9fX1+f3+AgIGCgoSFhYWGiIuPlpifsJGnmZeTjomEgfz27+jh29fUzsnEv7q0sa+sqqakoZ2cmpmXlZORj42OjYyKiIeHhYSDg4KBgP/9/Pv8+/v6+fj4+PaE9RX08/Ly8PDv7+7u7e3t7Ovq6ejn5eaG5Rzk4+Pi5OTl5OPi4N/f3t7d3d3c29rZ2NnX2djZhNgI19bW1tXW1teE1gjU1dXV1NTT04XUA9PT1IXTCdHT0tPT09LS0YTSEdHQ0dDR09PS0tHOzM3NzszMhssNysnKyMfJyMfGxcXGxoTHhMYYx8bFxcTExMPExcTCw8PDwcDAvby7urm5hLqAvL2/v7/AwsLDxMbFxcWWxzk5Ojk1Njo/RExTLC4xNDggJCkyIiwjJCgjIDYoJi4lMC8fJCxGIyEnNDUnHzUuJT86NTAtU09JRkVEQTw2MzZmypfAysvMzc7Q0M3Mz9LV1tnb3N3e3+Hi4+Pk5+jp5+fp6uvt8fLz9Pf6/YCBgoNfhIWIiIqLjY6RkZOVl5mcnaCio6Wnqauur7K0tri7vb/CxcjLzs/R09fa3uDj5efo6ue/mHBlYVdTV1ZUVFNTU1FRUlFRUVBQT0xJT1BQUU1HTU9PT1BQUVBQUFFQUVCEUQVPT05NTIVNDE5OT1BQUVFSUlNTVIVVGVRTU1BOTlBRUVBMR0ZHSElJSUZDPjs7PDuFPAk9Pj4/QEFCQkKEQwVEREVERIRFiUYDR0dIkEcBRodHh0iDSYRKAUv/f/R/AYDTgp6DBISEhIK6fwKAg4eErYP/gtKCAYCLgYWChIMchISFh4iKjIyMiouNjYyLjY6NjImHhYSEhIODg4WCjYGqgraDAYL/f7N/AgIEACqztLW2t7e4uLi2tba1tbWztLW0tLa3tra1tLe4t7q6ury/wL6+vr28vb2EvC69vb7Av8DAvbu7vb6/wsXKy8zIxcTCwMC9vL2+vry6urm4ubq5urm4t7i4ubq6hb0EvLu7uoa5Bri4ubm6u4W8hL2CvIS7Eby8vL28uri3tbOzsLCwr6+uhK1mrKuopKOiqre9u7y4trnAxsXHxsG7vcDBwMDAwcHAwMHAwL69vby7urq5uru8u7u6uLe2tbW1tLWzs7SysbCtqqiprLK0squipKyvr7CwsbGxsrGxr6uoqayotLm7xEAfHR0cHBwdiByVHYMclR0BHowdARyFHYQegh2NDggPDw4ODw4ODoQPDhAREhMJCRM2r62rpp2lhKcHqKipqqqrq4WqDaurq6qrrKyrrKysq6yGqx+sra6vr6+urammo6iwtbjMFw8ODQwLCwsWFhUUExMTjBKREYUQghGHIgchISEgICEhhyKEIYUiByEiISEgICGJIIIhiSCKH4YghB+EIAIfIJofkSCQIQMiIiGGIoYhhCAGHx8gICAfhyCJH38gICAhISIiIyMkJCUmJicoKSosLS8xMzQ1Njc22ujy/ISMmKKqsrvE0eHyg5Glv+CEnbbXgJ674JWtt8DP4+7r7v+Jl4WJsorjuZiA28GrlIHn08OzqaKZkImFgn88ODUzMTAvLi0sLCsrKikoKCcnJiUlJCUkJCMjIyIiIiEihCGDIIUfjw+HDgEPiw6JDYkOLQ0ct7y5tq2wtrOxsLCvsa+ur6+urayrpJqir7GxsKirrq+urq2ura2ura6vr4SwAa+ErgOwr7CJrz6urq+vsK6vr7CvraaemaCqra6sqaeprK6vr6qloZ6gn6CioqOipKKkpaWlpqaoqaurqquqq6usq62trq2usYavBrCwsK+vsIWvA66vroSvhK4Nra+vr7CwsK6ur7GxsYayBbSytLKxCnR0dHV1dXZ1dnWKdBR1dXV0dXR2dnV3d3h5e3t6e3t6eoV7Inx8fH1/fn5/f319fn59f4CCgoOBgH9+fX57eXh4eHd1dnaEdQF2hHWEdoJ4hneCdoZ1C3Z2d3d3eHd3eHh4i3cEeHh4doV1hXQNc3JzdHR0c3JxcXBzeIZ8D36BgoOCe3Z5fX+AgIGCgoSBgoCEfgx9fn19fXx7enl4eHiFdyt2dnd2dXVzcW5scHZ3d3Vyc3d5enp6e3t8fX18e3l4dnRxf4SFikc7PD09hDyEOwE6jTsBOoU7ijoKOTk5Ojo6OTo6Ooc7BTw8PT09hTyEPQw+Pj8/QEBAQUFCQ0OFIiUjIyQlJSYnJygoKSorLC0vLzAxMzU2ODkdHjtLkouHfm52eHt9hH6GfwF+hX8GgH+AgIB/hYAIf4CAgIGBgoKEgyuCgYGAhImMkKorJSQiIB8eHTg3NDMyMC8uLSwrKikpKCgnJyYmJSUlJCQkhCOGIoUhBiAgQEFBQYhAAz9AQIc/BD4+Pj+IPgM9PT6HPZA8jTuCOoY7iDoKOTk5Ojk5OTo6OoY5iToBOYk6hzsFPDw7OzuEOgU7Ojo6OYc6hjmHOIg3CTY2Njc2Njc2Noc3Zjg4OTk6Ojo7PD0+Pz9AQUJCQkNERUCqoKy2XmJncHmCi5KcqLhkb3uNpV9vgJZZa4Chb4CFi5WiqqmqvGZxY2J/Yp6BbV+nj31uY7OhlYyEenFqZWFse0VIR0ZFREREQ0JCQUFAQIU/gkCEPwRAQD49hD4DPz8+hD8IQEFAP0AgICCGIYQihSMfJCUlJSYmJicnJygoKSkpKioqKysrLC0tLi4vLzAwMYQyEDmAeHNxamxwb21tbWxtbGuEagxpaWJcYmxtbWxmam2FbgRtbW5tiG5QbW1tbm9ubW1ubm9vb3BvcG9wcHBxcHFwcG9ta2ZiZ2xubmxqaWltbm5uamVjYGFgYWFhY2NkZGVlZmZoaGlpamlqa2xsbW1sbW1ubW5ubm+IbgRtbm1uhm0BboltCm5tbW1ubm5vcHCEcQhycnJzcnNzcwlLS0tMTExNTU2ETAJLTIZLDkxMS0tKS0tLTExLS0xMhEuFTDBNTU5PTk9OT09OTk5PT05OTk9PT05MS0pJSUhHRkZGRUVEQ0NERERFRERDQ0NEQ0OQRAdFRkZGR0dHiEiFSQhKSklKSkhISYRKBUlISUlJhEgqR0dHRkZGTVZbWVlZV1hdZmxvcHZ8e3h2dHNycnJxcXJycXBwcG9ubW1rhmoDaWpqhGk9ampramhmYlxRRT1Sb3N1g5ONh4aFhIOEg4ODgn98dWpgWlmFk5eqrdfe3t3d3Nzb2dnZ2NbX19jY2djY2YTYLNfX2NjX1dbX1tbV1dTU1NLU1NPS0tLT1NbX1dbV1tbX19nZ2djZ2tze3+LjheQ55ufq6+3t8fT3+/6BgoSHiIuOkZSVl5qeoqessba6v8TM09vh5u72/4SH87axlo1+aXBydHZ3eHh5iXqJe3t8fHx9fn5/f4CAgYKDhIWFhoaIi46UmJ6s95KYl5ONiISB+fTu5t7Y1M/Kx8O8tbKvrauopaGenJuZlpWTkZCOjYyLiomHhIOCgoKBgP/+/Pv6+fj49/f28/Lz9PTz8/Px8fDv7u7u7ezs6+rr6unm5+fl5uXk4+Pj4uGH4g7h4d/e397d3Nvb2trZ2YXaCdnZ2drZ29ra2YXYD9fX1tTV1NXV1dbU1tTS04fUhtUD1NXUhNUg1NPT09TT09PU1NXW1dTSz87Pzs3Ozc3Mzc7NzMvKyciEyRPIyMXHyMnKycbHyMnJyMjIxsXHhMZjwsPEw8PCwL68vb/Avr6+vb6/wMHCwsLDxMXGxsXAiq9ua2oxLi8yNDc8QEZKUCosLTI4HyMoLxwhJzEiJyksLzQ2NDM3HyUhHiYfMisnIz03My8sU09NSkhDPjg1O4PyrcnLhcwFy8vLycmEy2DN0dPV19rc3t7e3+Hh4uPk4uXo6+7u8PDy9vf6/oCBgoSFhYeJioyOj5GTlZeYmp2goaOlp6msrbCztbe5ury/wsTHyc3O0dLT19ve4ePm5ubktJBuY15VVVhUU1JSUVKEURRQUVBQTUlMUlJRUElKTE1OT09PUIVRhFIHUVFQT09QUYRQKlFRUVBQUVFRUlFSU1JTVFVVVFFOTE9RUVBNSUdHSktKSkZBPjw9PD0+PoQ/hEAKQUFCQ0RERURFRYRGhEWIRoZHAUiERwtGRkdHR0ZHRkZGRYRGA0dHRoVHhEgHSUlLSktKSv9/9H8BgdWCnYMEhISDgbl/AYOHhKuD/4LUgoSAi4GFgoSDhISKhQaGhoaFhISEg4WCjIGugrWDAYL/f7N/AgIEAAWys7S0tIS1R7S1tLO0tLOzsrS0tLW1srO0tbW3ubq6vL29vLy7u7q5ubq6ubq6vL++v8C/vLu6vL2/w8TGx8bCwsC/vLm4ubm7uri4t7e3hbaDtYS2Bri5ubi4t4q2UbW1tra4ubq6ubm4t7i4t7a3uLm3t7i5uri4tra0s7KxsK+vsK+vraytrKqnpqWioJ6hrbm5tbS0trzDw8XCvLe7v7/Av8DBv8DAv7++vby9vIe6MLm3t7e1tLO1s7S0srGxsK2sqKalsbKyq6Cnra+vsK+wsLGxsbCwrauqqKu4urtrH4UdhBwGHR0cHR0dhRyKHQccHR0dHBwciB0EHB0dHYQcjB0BHIsdBh4dHh0eHoQdiA4BD4gOFA8ODg8ODw8QEREJCQkRXLCtqaCihKgDqamqhauGqoarA6ysrYWshK2Frhatq6mlpa60t8MtDw8ODQwMCxYVFBMTixKKEYIQhhETEBAQEREQESIiISEhIiEiIiEhIYYihiGGIgQhISIihiGFIAohICEhISAgIB8ghB8BHokfASCNHwEghh+FHokfhSABH5QghyEIIiIhICAhISKKIQEghSGGIIUfAiAfhCCCH4ggBCEhISKEIwskJCUlJycoKiosLYUuXC2utbzCw9Ll+ISNlp6msb/N3fGDj5yrvdPogI2apKyyv8TEw8jP09LBs6iflYfv07mnlIf23Mm7rqKblY6Ig3w7NzUzMTAvLi0tLCsrKikpKCgnJyYlJSQkJCMjhCKEIQMgICGFIIYfjg+YDoQNiQ4ODRy1uri2rbC0sbGxsLCErxqtrayqpZqZrK+wrqemqqutra2srayurq+vsISvB7CwsK+vsLCFr4KuiK02rq6rq6qjnJ2mrq6urKqrra6vsKyln52foJ+goaKjpKSjoqOjpaemqKepqqytraytrq6uraysha4Dra6uhK+Ergqvrq6trKytra2vh64Era2uroavhbAEr6+xsYSyBHN0dHSIcwRyc3NyhXMLdHRzdHV2dnd3d3iFegt5enp5enp6e3x8fIR+GH9+fX19fHx9fn+BgH9+fXx7eXh3dnd1dIhzBHRzc3OFdAl1dXR1dHN0c3SIc4R0hHYBdYd0CXV1d3Z2d3V2dYV0J3Nzc3R0dHNycnJxcG9vbm1rbXN6eXl6enp8gYGCf3l4e35/gICBgYSAgn+EfQl8fHt6enl4eHiEdyx2dnV2dXV0dHNyb2tqa3V1dnVzdHh6ent6e3x9fn5+fXt5eHZ6hoeKUzU8PIU7BTo7Ozs6hDuNOgE5hzqHOQM6OTqFOYY6iTuDPIQ9DT4+Pj8/P0BAQUFCQ0OFIiQjIyQlJicnJygpKissLS4vMDIzNTY4HB0eMl6OioZ0cnh6fX6Kf4uABX+Af4CAhYGCgoWDKIKBgIKIio+gSSYlIyEfHh05NjQzMS8uLiwrKykpKSgnJyYmJiUlJCSEI4cihSEFIEFBQEGHQAQ/QD9AhT8BPoU/iD4GPT4+PT4+iD2FPAU7PDs7PIU7ATqGO4I6jTuSOgE5ijqJOwg6Ozo6Ozs7PIQ7DDw8PDs7Ojo6Ozs6O4o6hDmFOIc3gjaKNwU4ODc3N4Q4Djk5Ojs7PD09PT9AQEA/hEBOOY1+hIuRl6CqWmJqcnqCi5eks2Bncn+NmqdbZW5zdnyGjY2NkJOXk4d7dW1kWqGUhndqYrSlmpGHfnZtZ2NvgUhIRkVERENDQkJCQUFAhT8FPj49Pj6EPQk+PTw9PTw9Pj2FPgE/h0AGICAgISEhhyIPIyMjJCQkJSUmJiYnJygohSkfKioqKyssLC0tLi4uLzAxMTIxMTh/dnNxam1vbW1tbIVrIWpramlmXl5qbGxsZmhrbW5tbWxtbG1tbW5ubm1ubm5tbodthG6Cb4ZuF29ubW1taGRla29wb2xqamxtb3BtZ2Jgh2IJY2NlZWZnZ2hphGqFawdsbG1tbm1siG4EbW1tbIZth2yEaw1sbG1tbG1sbWxtbm9uhG+EcAVxcXFycgZLS0tMTE2MTIJLh0wLS0xNTUxMTE1MTEyGTQpOTk9PUE9QUFBPhk4PTU5PTkxKSklHR0ZFRUVEikOIQgNDQkKEQ4JEhkMGRERERUVFhUYLRUZHR0ZHR0hISEmESAlHSElJSElJSEiESQJISYVIHUdGRkpSXFxaWFdXXmlvcXR8gnx5d3Z0dHNycnFwhW8KbmxramlqamlpaYRoKmlqamtramhmY11TRj5GbnN3g5qTi4mHh4aGhYWFhIB7cmdeWmmSmaCHr4TYJdfW1dXV1NTU09PV1dTU1dTV1tPS1NPT1NTT09PS0tLQ0NHPzc6EzxXNz9DS0tHR0tPT09TU09TV2Nrb2tqE3ELd3t3e4eDj5ufq6u3w8vb6/YGCg4aKjI+SlJabnqKnrbK0ub/Fzdbe4+jx+oGFicGcppOIcW1zdHd4eXp6ent6enqGe4Z8UX18fXx9fX1+gIGCgoOEhYaGh4iKjZKZnarc45ialY6IhID68uvk3dbRzMfCvri0sa+sqaajn52bmZaVk5GPjYyLioiHhoOCgoKBgP/9+/j49oT1N/Tz8/Lx8fHw8PDu7u3t7u3s6+vq6Orq6Ofo6Obm5uXl5OLj4+Hh4eDg393c3dvb2trZ2dnY2diE2gPZ29mF2iXb29va29vc29jX19jX1tfX2NjY19bX19fY19bV1tfW19fX1tbXhtkL2NjY19fV1dXW1daE1SnW19fV0tHQ0dDP0NDPz87Ozc3My8nKzMrKycnIycvMysrIyMfKysnIyYTIgMfIx8fIx8TCwsDAwMHCwb/AwcHBwsPDxcPBxMTHyMjDiJpkY2FWU1VaLzE0Nzo9QURJTigpKy4zNjcdHyIjJScqLy8tLS4vLiooJiMgHTk3NDEtK1NRT09NSEI8OT+L/LHGycrKysvLzMzMzc3Ly8zMzM3O0dHU1dfZ2tnZ19ncVN7e3+Lj5unr7O7v7/L19vn7/oCBgoSFhoiJioyPkJKVl5mbnZ6goqanqautrrGztrm8vsDCw8bHyc3Q0tTW2dze4OLj5OCxjmxlX1RUVlRTU1NSUoRRDlBQT05KSlFRUVBISExNhE4ET09QUIRRClBQUFFQUVBQUFGGUIZRglKEUxhUU1BNTVBSUlFMSUlLS0xMSUQ+PT4+Pj+JQBFBQkJCQ0NFRkZGR0ZHR0hHR4dGBUdGR0dHhEgJR0dISEdHSEhIhkeJRhFHR0hISEdHR0hISElJSkpLS/9/838BgNiCm4MFhISEg4C3fwGCh4Spg/+C1oKIgIqBh4KUg4aCjIGygrSDAYL/f7N/AgIEAAe1tLSzs7OyhLEYsK+vsLGxsbKys7S0s7OztLa3t7m7u7y7hbosubm4uLe3uby9vb2+vLu6u7u8v8LDw8TDwL68u7m1tLW1tbKytLOysrO0s7OHsgKztIS1A7S0s4eyX7OztLOztLW2tra1tbazsrOzs7S2tbWztbe2tLW3tLKxsbKvsK6ur6+ura2tqqmnpKKhnpqdq7S3tLS0trzBwsG+ubi9vb6+v7/Bvr6+vL29vLu7urm5ubi3t7a0tLOzhLImsbGxsK+tqaelprGxsa6kp66vsLGxsbKxsbGysa6rq6e0u7vFPh6GHQ0cHB0cHR0cHB0cHR0dhhwEHRwcHYQcAx0cHIUdAhwdhxwHHR0dHB0dHJ0dBB4eHg+GDoMPhw4ZDw4ODg8QEBERCAgIHrCvramdp6ioqKmpqYSqAauEqjOrrKurqqqrq6ytrKysra2ur66tra6vr6+trKunpa62u8MkDw8ODQwMDBYWFRMTEhIREhGIEgQREREQhBENEBAREBAQEREQERAREYQiCCEhIiIiISEhhSKRIQQgICAhhCCKIYcgox+CHoUfkCCOIQQgISAghCEBIIQhhCADISEghiEBIoQhhSABH4Uggh+IIAEhhiAGISEgICEhhCJoIyQkJCUmJiYnKCgpKSgmlJqiqaiuu8rZ5vKAh46Yo6+6xNHi8PmCh42UnKGgo6qxsK62wLmvraminJWRi4L46NvTybmspJyWj4mGfzw4NjQzMTAuLS0sKysqKiopKSgnJiYlJSUkJCOGIgUhISEgIIQhAyAhIYggBBAQDxCFDwEQhg+WDoUNhw48DRu2u7i0rLKysrGwsK+vr66urayqp5+Xpq6uraekqqqrrK2srK2tra6vrq+vrq+vr66vrq6urayrrK6uiawgrauqp6GXmqitrqyopqqsrq+vqaSfnZ2foKChoqKkpKWEpiCnqKqrqqytrK2ur66ur6+urayurq6vrq6ura2ur6+uroSth6wCq6yGqxCsrKyrqqyvr6+ura2ur6+whK8EsLCyswp0c3R0dHNyc3NziHIQc3NzdHNzdHV2d3d4eXp6eoR5BHh5eHmEegZ7e319fX6FfR18fH1+f39+fXx6enh2dHV0dHJxcnFxcXJycnFwcIdxB3JzcnNycnKJcYRyB3Nzc3R0c3OGcoZzCHRzdHRycXJyhHMgdHRzcnFycnJwb25ubmxqa3N5enp6eXp+gIB/fnh4fX+HgA9/fX59fn18e3p5eHh3d3aEdSx0dXV0dHRzc3Jxb2xpZ3N1d3d0dnl6e3t8fX5+f39/fXt5eHaEiYuQTDs8PIQ7hDoCOzmJOpM5gjiEOYQ4hjmEOgU5OTo6OoY7hTwEPT0+PoQ/BUBBQkNEhSIiIyMkJSYnKCgoKissLS4vMDIzNTY4HB4fQ5iMiIFweHp8foSABH9/f4CEf4SAAX+IgISBA4KCg4SEKIODgoGCiIqNmDYmJiMiIB4dOTc1MzEwLi0sKyopKSkoJycnJiUlJSSFI4YihCEHIEJCQUFAQIRBC0BAQD8/P0A/Pz9AhD8IPj4+PT4+PT6PPYU8hDsBOqI7jDqGO4I6hTsDPDw9ijyFOwE6hTsBOoY7ETo6Ozo6Ojs7Ojo6OTk6Ojk5hziJN4k4BDk4ODiEOYQ6CDs7PDs7Ozo7hDxLN39vc3d5gYuSmKCrW2Bla3N9hY6Xoq21XmNnbHJ2dXZ9hIF+hIqJg355dHBtaGFcr6ejoJiPhoF6c2xncIJISEZFRENDQkFBQD9AhT8HPj4+PT08PYU8Ez08PDw7PDw8Pj09Pj09Pj8/QECFQYQggyGEIoUjASSEJQgmJiYnJygoKIQpHSoqKysrLC0tLi4vLy8wMDAxMDt/dXNwaW5ubm1thGyEaxVqaGJcZ2xtbWlna2xsbW5tbW1ubm6JbQ1sbG1ubW1sbG5ubW5thW6EbRdrZ2FkbG9wb2tqbGxubm5pY2JhYmNjZIVlFGZnaGhpaWpqa2tsbGtsbG1sbGxthGwFa2xtbW2EbgJtboRtAm5thmwHbWtsa2xsbIRtCGxsa2ttbm5thG4Kb25vb3BwcXBxcgRMTExLhEyCS4pMBEtMS0qETIVNhE4ETU1OTYdOFk9QUFBPT09OT09OTU1OTUxLSkhHRkWEQwVBQUJBQYVCAUGRQoRDiEQIRURFRUVGRkaERwFIhEcTRkVFRkdIR0ZHSEhJSUhJSUlISYRKYkhHR0ZGSlRaW1tZV1dhbnJ0fIiAe3h3dXRzcnFxcG5vbm1sbGtpaWloZ2dmZWVnZ2hqa2tramloY19VRkBEbnN2gJ+YkIuKiomJiYiHhIB5b2RcWYOanrTF0dXU1NPS0tHQhtGF0gvR0NDPztDQzs/Pz4TOFMzMzszLzMvLzc3Ozc7Pzs3Ozc7PhdBI0tPV19fY19fW19fY2tvd3d7h4+Xn6evs7vL19/r+gISGiIuOkpWXmp6jqK2ytbi/x8/W3uTr9f6CiIngyZuPgmtydHZ4enp7hHqGe4d8U319fX5+f4CAgYKChIWHiImJioyOkpqeqMqYlJuVkIqGg/z06uPb1tHKxcC8trKwraqopaGem5mYlZSSj42Mi4mHhoSDgoGAgP/+/Pr49vb18/LyhPEa8O/u7u3s7Ovs6+vr6ujq6Ojp6Obm5ufn5uaE5Qzk4+Pi4uDe3tza2tqE2Q/a29rb3Nzc2tva2trZ2tmE2g3Z2dna2dvb2trb29nZhdiE1wXY2dnZ2ITXB9nZ2tna29yE3hnd3Nzd3d3c29nY19fW1dbT1NPT09DR0dDRhNAMz8/Q0dDPzs7LzM7Nhcwny83NzMvLy83LysvKy8zMzc/Ny8vJycjGxcLDxsbGxMPDxMbFxMPDhsSAwouTWlpYUU1PU1ZaXTE0Nzk7PT9DRUhLTCUlJScoKSsqLC0uLzAxMS8sLSwrKykoKE9PTk5OTU1LRkA+P37xrsPFxcbHx8jIycnJysrLzM3Ozs3Oz9DQ0tLU1tXU1dPW2dnb3d3f4eTp6Ort7e7x8vb4+f3+gIKDhIWHiIqLjY83kZOWmJmcnaCipKeoq66wsrS1t7q9v8HDxcfKzc/S1NXX2tze4eHi4LuKbGVeU1ZVVVRUVFNTU4VSEFBNSlBSUlFMSUxNTU5PT0+EUAFRjFAJT05NTlBRUFBQhFGEUhtTUk9NTlFRUU9KR0lLTE1MRkA+PT0/Pz9BQkKHQwpERUVFREZGRUZGh0cDRkdGh0cCRkeOSIRHhEYBRYVGhkcMSEhKSUlJSklKSUpK/3/zfwGB2oKagwSEhISCtn8BgoeEp4P/gtiCi4CMgZiCjoG2grODAYL/f7N/AgIEABeztLSzsrGxsbCwsK6urq+urq6wsbGxs4S0C7O0tba2uLm6urm4hLkzuLe3t7m9vby9vby6u7q8vL7CwcDAv767uri1tLS1tLCvrq6vr6+wsLGxsLCxsbCwsLGyhbOEsgqxsbGwsLGxsbKxhLMJtLOztLSzsrKyhLFysrOzsrOzsrOysbCxr62srKusra6sqqyqqKakoqCdm5map7SzsrS0uL6/v7+0tLi6vL2+vby8vL28u7y8u7u5ubm4t7a3tbWzsrGxsbCwr6+urqypqqikrLCxrqSor7CvsbGys7KysrGwra6rrLm7v3AghR0DHB0dhByHHZEchh2NHI4dgh6QHYQegg+GDokPFw4ODw8PEBARCAgJETSwrauhpKipqKmphaqFqYOqiKsirKytrK2tra6urq+vrq2sqKWpsre9QA4PDg0MCwsWFRQUEoYRARKHEQMQERCIEYcQCCIiIiEiIiIjhSKCIYUiiSECICGIIAQfICAgiiGLIKUfjyASISAgISAgICEhISAgICEgISAgiiELICEhICEgICEhISCJIYgghCGGIAQfHyAghx+FIAgfIB8gISEiIYQibSMkJSYmJiOGjJKWlpuiqrO9xM/Y4Ov6hIuVnqass77EyczT2tzT0dzf19La4NTGxsrFwb69tq2loZ6bl5STkIqJh0I+Ojc1MzEwLi0sKyopKCgoJycnJiYlJSUkJCQjIyIiIiEiIiEhISAgICGEIIIhhCCFIYgQAQ+EEAUPDw8OD44Oiw2FDiANDRq0uLayqrCxsLCvrq6ura2srKuqpZmcrK2sqaKqrIWtB6ytra2urq+FrkutrK6ura2sq6yrrK2traysq6urqqurqKCYnairq6qmpqqsrq2pop6cnJ2eoKCgoaKjo6OipKanqKmoqaqrrK6vsK+urK2trK6trK2EroWtha4PrK2trKyrrKysraysrKuqhKkYq6qrq6ytra2srKyrrK2tra6ura6urq+xgnKFc4JyhHGFchBzcnNzc3R0dXZ3d3h4d3h4iHkLenl6ent8fXx9fX2GfIV9C3x6eXh3dHRzc3Fwhm8OcG9vbm5vb25ub29vcXGOcBRxcXBxcXFyc3JycnFycXBxcXBwcYZyBHFycXGEcgJxcoZxGXJycW9ubW1raWdpcXl5eHl5e3+AgYB7eHyFfw+AgH5+fn19e3t7eXl4d3aEdCxzdXV0dXR0dHNycXFvbGhoc3Z2dXZ3fHx8fXx9f3+AgH99e3l3eoeJjWM3O4U6Cjk5Ojk5Ojk5OTqIOQg4ODk4OTg4OYY4Azc4N4k4BDk5OTiEOYI6hDkEOjo7O4c6ETs7PDw8PT09Pj4/QEFBQkMhhCIhIyMkJSUmJycoKSorLC0uMDEzNDYcHR46RJGLh3Z1e31/hICEfwGAin88gICAgYGAgYGCgoKDg4SEhYSEg4KCiIuNlUkiJiQiIB4dOTc0MjAvLi0rKyopKCgnJycmJiUlJSQkIyMjhiKEIYNChEEBQIVBh0ALPz8/Pj4+PT49PT6GPYQ8Aj08hT0QPD09PTw8PDs7Ozo7Ozs8PIY7ATyGOwE8kjuCPIY7gjqKOwM8PDuLPAY9PDw7OjqFOwU6Ozs8PI07hDqCO4U6iDmEOAE3iDgBOYQ4Azk5OIQ5ATiGOVA4OTo7Ozs2emVnaWhtdHuDi5GXnaWttGBnbHJ3e4CEiIyQl56blZegopqXoqack5GPjYmHhIB9eXh2dHJxcGtma39JS0pIRkRDQkFAPz4+Poc9hjwGOzs7Ojs6hjsTPDw9PTw9PT0+P0BBQUJBQkJCQ4chhCKDI4QkBCUlJSaEJyMoKCgpKSkqKiorKyssLCwtLS4uLi8wMDA9fnVyb2ltbm5ubYRshGsLamdgYWttbWtla2yFbYRsBW1sbWxthGwBa4RsB21sbW1ubm6JbRBrZ2NmbW9vbWpqbW5vb2tkhGEYY2RkZWVmZmVmZmdoaWpqa2tsbG1tbGxshG0BbIRthmyLbZBsBW1sa2trhWyCbYdvBXBwcXFygkuFTAFNhEwFS0tMTEyES4VMCE1NTE1MTE1OhE+CToVPAlBPhFADUVFPhU4QTU1MTEtKSEhGRENDQ0JBQYdABEFBQECFQYtChUMERERERYREA0VGRYhGGUVFRUZFRURERUVFRkdISEdIR0hGSElIR0iFSYRIJ0dKU1xdWldVWGhzdXqMi397enh2dHRycHBwbm1tbGtpamloZ2ZnZ4RoAWmEaj5pZWNhVkg/QGpzdoGfmJGNjIyLi4yMioaAem9jXWmXoKmyuM/Pz83NzcvMy8zOzM7Ozc/Ozc7Nzc7OzszMzYbLDszLycvKycnIysvKy8rLhcyCzYTOA8/R0YfSQNPU1dfY2trd3uDh4+Xn6evt7vH1+Pr9gIGFiIqPkpSWmZ2hpq2xtLrAx9DX3+fw+ICFividrpiMc29zdXl6enuGeoV7Y3x7e3t8fH19fn5+f3+AgYKEhYeIiYqKjI6Tm52juqCInpeRi4eD/vbr4dnTzsnEwLq1sa+sqaWinpybmZaUkpCOjYuJh4aEgoGAgP7++/n49/b09PLx8PDu7u/u7uzs6+vr6YToA+fm54TlIeTj4uPi4eLi4uPj5OPj4+Lh4d/e3NnZ2Njb3Nzd3Nzd24TcCdvc29rc3Nzb24TaCNvb3Nzc29vchd1I2trb29rZ2tnZ2NjZ2NjY19jZ2tra29vc3dzb3Nzd3d7e3drY2NbX2dnW1dTV1dbU09TS1NTT1NHT09TU09HR0tHR0tLR0dDOhNAFz9HQ0NCEzhfPz87Pzs3Lzc3LysvLycjIyMfHxsfHyITHHsbGxcTFxJSgVVVTSUdLTlFTVlxgZGhsNjg5Ozw8PIQ+KEBCQD4+QEBAQkVGREFAQkNAP0BAPz4+QEJDQj47O1zMoL3AwMHCw8OExAzFxsfHyMnKy8zNzc+E0F7R09LRz9DT1dna2tzc3d/h5Obp6+vu7/H19vr8/f+AgoSFhoiKjI2PkZOVlpicnqCipKWpq62vsbO1t7m7vb/CxMbIy83P0dTW2Nnb3uDg38eNbmVbUlVWVVVUU1NUhlMNUE1OU1NST0lOT09QT4ZQClFQUVBRUFBPT0+FUARPUE9QhVGFUC9RUE1LTlFRUU5KSUxMTk1KQz8+Pj4/QEBBQkJEREVERUVFRkdHSEhHSEhJSUhHR4VIAUeESIhHh0iCR4dIB0dGRkVGRkeERgtISEhHRkdHSElJSYVKA0lJSv9/8n8BgNyCmYMFhISEg4G0fwGBh4Slg/+C2oKQgKuBuoKygwGC/3+zfwICBAAzsK+vsLGvsK+ur66vraytra2usLGxsrGzs7Kzs7W2tre3tre2t7a1tre1tLW2t7q7vLy8hLseurq7wMDAwb++u7q3tLSysrOxsK6ur7Cura6vsLCxhbAHr6+wsbGxs4ixTbCwsLGwsbGwsbGysrGysbGwsbCwsK+ur7GwsbGysbKxsbGwra2sq6qqq6yrqqqpqqmopKKgnpyZmJedrLSzsrO1ub6+v7mztLq7vLu6hLs6urq7u7q4uLe1tbW0s7OzsbGvr7CwsrGwraupqKWirq+vq6Wora6ur7Gys7Szs7Gura2ptru9yCEeHogdgxyEHYccBR0cHB0chh0FHBwdHR2MHAEdhRyHHQIeHYUeCB0dHR4dHh0ehx2FHgcdDw8PDg4OjA+EEAwRERIJCQkRs6+uqJ6EqYSqBKmpqKmEqjCpqaipqqqrq6qrq6usrK2trq+vsLCwr62rp6extrttGQ4ODQwLCwoUExMSEhERERCHEZIQAyEhIoQhhSIBIYgiAyEiIochhSAEHyAfH4YggiGGIA0hICAhISEgICAfIB8gix+DIIsfCB4eHx8gHyAghB+JIIQhkyAJISAgISEhICAghCEEICAgIYYgiiEFICEhISCEIYUghh+KHgEfhB6CH4UgWyEiIiIjIyQhd/r/goOJjpOXnqWstb7Hz9vn8PuAhYuQk5aYnaSln56lqKOcnZ6ak46LjIuGgf/47+fh3+Lo7PJ9PTw6OTc2NDIxLy4sKyopKCgoJycmJSUkJCSEI4UihSGIIAcfHyAfICAghiGOEIcPjg4GDQ4NDg0NiA4lDR2ztbKuqK6vra6urq2srKuqrKupoJemrK2tpaasra2trq2trYSuQKysrK6trq+urq2trKysrq2trq2trKysqqqpqKagmJynrK2ppaaqq6usp6CcnZ2en5+enp6fnqCio6SlpKanqKiGqhKtra6ura2urKyqqqysra2sq6yGrYSsAauErIWrGqqrq6mqqqqpqamqrKyqrKysq6ysq6ysrKuuhK0Br4RwGHFxcXBwcXBxcXBxcnJyc3N0dHR1dXV2doV4DXd4eXt6enp5eHl5eXqMfBB9fX5+fXx5eHZ2dHJxcXBviW4EbW1sbIRtCG5ub29vcHBwjG8NcHBwcXFxcHFxcXBwb4VwhXEDcHFxi3Adb29wcXFwb25tbGtraWdnaXR5eHh4eXyAgYF9eXuEfgJ9foR9Onx7e3p5eXh2dXNzc3R0dHV0dHR1dHNzcXFvamdmdHZ3dnV3ent8fX5+f4CAgH98enl3g4iLmC46OjqMOYk4ATmIOAQ3Nzg4iDcDODc3ijiEOQQ6Ojo7hjoDOTo6hDuEPD89PT4+Pj8/QEFCISEhIiIiIyMkJSYmJycpKisrLC4wMTI1NxwdHiuijoqCc3t8fn+AgH9/gIB/f4B/f4B/gH+GgIOBhIIvg4OEhIWFhYSDgoSLjpNhOiYlIiAeHRw3NTIwLi4sKyoqKSkoKCcmJiYlJSQkJCOGIoQhhkIGQUBAQEFBiECEP4Q+Az0+PoU9iTwBO4g8Aj07jzwEOzs7PIU7hjyIOwg8PDs8PT08PIY7Bzw7PDs8PDyEOwQ8Ozs8ijuEPAE7hTwQOzs8Ojo7Ozw7Ojs7PDw7PIg7ATyFOwU6Ozs6O4Y6hTkYODg4Nzc4ODg3NzY3ODg3Nzc4ODk5OTg4hDlMOjZ7ubtfXV9jaW1xc3h+hIuUnaWwtV1gY2ZnaGpucnBqa3J0cW9xcnBsZ2ZmZmJeuLSxramoqaywtGxCSUlHRkVEQkFAPz49PTw8PIg7Azo6O4w6Bzs7PDs7PDyFPQw/P0BBQUJCQ0MiISGHIoMjhSQJJSUlJiYmJycnhCgEKSoqKoYrEiwtLS4uLi8uLj5+dnNvaW5vbYVsDm1sa2xramReaW1ubmhphG0Ibm1tbWxsbG2EbApra2xra2tsa2trhm0DbGxthGwYa2diZW1wb21oam1ubm5pY2BhYWNjZGRkhmUUZmdoaWpra2xsbW1ubm5tbW5ubm2SbAZtbG1sbG2GbBZra2xsa2tsbWxramtsa2psbG1tbm9uhG8GcHBvb3BwDkpLS0tMTE1NTExNTU1MhE2ETAZNTU5NTU2ETgRPTk5NhE4BT4pQBk9QUE9OToRNC0xKSEdIRUNDQkFBh0ABP4dAhUEEQkJDQohDhEQBRYZEhkUBRIRFDUREREVERENDQ0RFRUaFR1FGRkZHR0ZHR0hISEpJSEdHRkVGSlhfXVhVVF9yd3qPk4WAe3d1dHJxcXBubWxramlpaGhoZ2dmaGdnaGdnaGlqaGhlX1VLQEFudXiHoZiQj42EjBaLiIWAd2tiX4uepMmDyszMzMvKysnJhMofy8vLysrJycrKysnJyMrKyMnKysrIycjIx8fHxcfIyYTIC8nJycrMzc3MzMzOhNBIz87Pzc7R0tTU1NbX2Nna2tze4OLk5+rq6/Dz9vn9gYKDhYmNkJOVmJygpayxs7nCydDY4+ny+4KHiqLgn5KCbHN0dnl7e3p6jXtafHx9fX5+f4CBgYKDhYaHiIqLjI+TmZ2gr5vVoZuUjomFgPft4trUzsfBvbmzs7CrqKSgnZuZl5WTkI2Li4qIhoSCgID//fz6+PXz8vHx8O/u7e3t7Ovq6unohOcl5ubl5eXj4+Pi4uDi4OHh4N/e3t3f39/g4eDg3+De39/g39/g34TeD93d3N3c3Nzd3dvb3Nvc3ITdAdyE24XcGN3f4ODf3tva2drb2tva3Nvb3Nzb29rZ2oXZA9jZ2YXaNNva29rb2dra19bZ19bW1dTU0tTT09bW19TT1NfY2NjV1NTW2NjW19TX1tfY1tXW19fW1dWG1BPS0NHR0c/QzszMztDNysnLzc3LhMgzycjGw5/Ip6NQRENERklNUVVYW11eYGRnaDMzNDU1NDMzNDMxMjM0NTU2ODc2NTU0NDQzhGYLbW5pYWFwjYW2vL2EvBG+vr+/wMDBwsTFxsbIycnLzITNWs7P0M/PztHS1NbW2Nna29zg4OTl5ujp7e/w9PX49/z9gYKDhYaHiYqNjZCSk5aXmpyfoaOkp6mrra6xtLe3urq+wcTGyMrNz9DR09XX2drb3NvVkG1jWlFWVodVhFQNUk5MUlNSUUpKTk9PUIVRBlBRUE9PT4RQEE9PT1BPUFBRUVFSUVJSUVKEURRQTkxOUVJRTUlKTE1NTUhCPz8/QIdBDUNDREVFRUZGRkdISEmFShBJSEhISklIR0dHSEhIR0ZHikgDR0hIhEcJSEhIR0hIR0dHhEYER0hHR4RICUdISElKSklLS4RK/3/yf96CmYMEhISEg7N/AoCDh4Sig/+C3IIDgH9/kYCagYqAAYG9grKDAYL/f7N/AgIEAAKurYSsAa2ErEytra6usK+usLCwsrSzs7KxsrS0tbW3t7a2trW0tLS1s7S1tre4urq5ubm4ubi4u7u9v769uri2tLOysbCvrq2uraysq6urrK2ur66uhK0Jrq6xsbCxsLCxhLCCr4iwDK+ur6+vsK+ura2trIStNq+wsbGysrKxsK6tq6ysq6mqqaqpqqmoqKeloqCfnZuamJWTnq6wsbKzuL6/vrSttbm7vby7u4W6ELi4trW1tLKysbGxr6+wsLCEsSGwrauppqWkrq6vqaWpra6wsrO0tLSzsrCurKqwvL3EPR6RHRIcHR0cHBwdHB0dHRwdHRwcHR2UHIQdBRwcHB0dmh6EHYUOiA+FEBMPEBAQERESCQkJN7Gvq6GlqamohKmFqgGrhKoCq6qFqx+qq6ysra2trrCwsLGwrqyppa20ucQWDg4NDAwLChQThBGEEIURkhAFISEhIiKEIQIiIYQiASOJIoUhjSABH4YghR8EICAhIYsgAh8ghR8DIB8gjB+FHgMfHh6EH4QgBx8fICAhISGGIAMfICGGIAEhhCACISCFIQQgICAhhyAJISEhICEhICEgkCGGIIIfhB6FHQQcHR0chh2FHgQfHx8ghSE6Hzfp8PHp7/qChoqQl52kqrK8xcvP1drd3+Tl5ev2/vb1gIP/+PyA+e7q493b19PNxsG9ure1uMJnNIQzEzIyMC8uLSwqKSkoKCgnJyYlJCSFIwoiIiEhISAgISAhhyACHyCEHwsgHyAgICEhICEhIYUQARGJEAUPDxAPD44OAw0ODYQOgg2HDgoetbOvrKaur62uhK1GrKyqqqmlmJiqrK2poqysq6usrKytrKytra6trKurq6ytrKytq6ytrq2urq+trq2trKuqqKKZnKeqrKmkpqqqq6uknZqcnoSfAaCEnh+foKGjpaampqipq6ytra2rqquqq6ytrK2qqaqqq6yshKuErAurq6usrK2rrKysq4WqDKurqaqpqqqqq6usrIWrDqyrrKyrrKurrKutra2uAXCFbwFwhnEFcnFyc3OEdAZ2dXd2dnaHdwF4hHkHeHh3eHl6eop7FXx9fX59e3l4dnV0c3Jxb25tbW1sbIRtg2yGa4JthG8Ibm5vbm9vbm+GbgZvb29wcHCMb4NwhnGEcIhvX3Bwb29vbm1sa2tqaGZlZGt2eHh4eXp+gIB+ent8fH19fH19fHt7enp5eHd2dXR0c3NycnN0c3R0dHNzcnJxb2pnZnJ2dnZ1d3p6fH1+f3+Af39+fHp3fIeJjkM5Ojo6ijmEOAo3OTg4Nzg3Nzg4iDcBNok3gjaENwM4ODeHOIs5hDqDO4U8MT0+Pj4/Pz9AQEAgICEhIiIiIyMkJiYnKCkqKywuLzAyMzU3HB0eWJWNiXd3e31/f3+EgAF/iIABf4eAK4GCgoKDg4SEhYWFhIODg4mMkaIpJyUjIB4dHDc1MjAvLi0sKyopKSgnJyaEJYIkhCODIoQhBkJCQkNBQYZABD8/P0CGP4c+Bj09PTw8PYU8Cjs7PDs8PDw7PDyFOwI8O4c8CD09PTw8PTw9jTyCO4U8BDs7OzyGO4U8ATuUPAg7PDw8Ozw8PIc7Bzw8PDs8OzuGPAU9PTw8PZA8Cz08PDw7PDw7Ojo6hjmOOIY5QTg4ODk6OEKsra2orLRdX2NmaW1xdXqBiI+TlpqeoJ+foKKnqaSiVFesqrNar6iloZ6cm5iTkI+NiYWCg5VlP0NDhEIGQEA/Pz49hDwBO4s6hTkEOjk5OYU6hDsZPDs8Ozw8PT4+Pz9AQUJCQiEhIiIiIyIiIoQjhSSEJQ4mJiYnJycoKCkpKSoqKoUrBCwsLS2FLghEf3ZybmhuboRthmwLa2liY2xtbmtmbGyFbQFuhW0EbG1sbIRrDWpqa2xsbWxsbm5tbm2EbCdqZ2JkbG5vbWlrbm5vbmdiYGFhYmNlZWZlZmZlZmdmZ2hoampra2yFbRVubm1tbG1sbGtra2xra2xrbGxsa2yFa4lsBWtrbGtshGsDbGtshmsFbGxtbW2EbgNvb3CEbwFwhksTTE1NTUxMTU1NTk5OTUxMTE5OT4VOE09OTk5NTk5OT05OT09PUFBQUVGFUBFPTk5NTEtLSUhHRkVDQkFAQIU/AT6FP4ZABUFBQUJChEMBQoRDBEREREOGRIZDhEQBQ45EAUWERgtHR0ZGRkVGRkdHSIVJKEdHR0VEQ0NMWl1ZVFJZcXd5kJmHfXt4dXJwb25tbWtpaWhoZ2dmZ2aEZ4RoJ2lpaWhkXlZJQERwd3mPpZWPjo6NjI2Mi4iEfG9hXW2aobCWwcjIyYTIAsnIhMmEyBDHyMfHxsbHxsbGx8THx8fFhsYNxMXGxsbFxsfHxsbGyIXJTcvLy8rKy8vNzs7P0NDR0dHS0tLT1NXX2drb3uDj4+fp7PD0+v6AgYOFh4uOkZSWmp+kqq+0u8PL09vk6vP8g4eD67CXjXNxdXZ4enp6hnsBeoZ7XHx8fX5+f3+AgYGBgoSFhoeJioyOkpWdoqzZh6KdlpCKhoH57+bd19DKxcC6s7Ctqqain5uamZaUko6MjIqIh4WDgYD//f369/Ty8fHw7+7t7Ovs6unp6Ojn5eXlhOQj4+Pi4eDg4N/f3t7e3d7c3tzd3N3e3dza29vd3d7d3dzc3d6G3wfe39/e3d3dhNyI3Rfc3dzb29zb29zc3Nvb3Nzb3Nza29vc3InbgNna29vc3Nzb3Nzc2tvb2tzb3Nvc2trb3Nza2djY2dna19jY2dnY29rb2tva2tvc3Nzd3Nze3t/e3t7f3t7f39zc29nY2trZ2NfX19bU09PT0dLU1NPS0dLS0tDOz83Ny8nIsYGlop2EfYJERklMT1BSVVZXWFlZW1taWltbXFxbH1tZWCwtW1tcLlxZWlpZWllYWFldYF9YU1aTvpy0t7eEuXW6urq7u7y+v8DAwcLCxMXFx8nKycrKy83Ny8vMz9DT1dbY1tjX2tre3uHj5ejp6+vv8fT1+Pr8gICDhIaIiouMjpCSk5WXmJqcnqGjpaepq6yws7W3uLu8v8LExsfJy83P0tTV19fY2NfrlXFmXlRYWFdXVlaGVQ1UUk5PU1NSTkhOT09PhVABT4VQhE8KUE9OTk9PUFBRUYRSHFFSUlFSUlBMTVFSUk9JSktMTUxHQj8/QEBBQUGGQxpERUVGRUZHR0dISUlJSkpKS0pKSUlJSkhISIVHCUZGR0dHSEhHR4ZIEkdHR0ZFRkdHSEhHSEdIR0dHRoVHCkhISEdISUlKSUmFSgFL/3/xfwGB34KZgwSEhISBsn8Bg4eEoYP/gt2CAYGGf5mABoGBgICAgZGAAYG/grKDAYL/f7N/AgIEAAGsh60frKysq6ytrq+wsLGvsLKysLGxsrKztLWzsrOztbSzsoSxKLCwsrO1trW2trW2tre2t7i5urm4tbKxsLCvr66tq6usq6qpqamoqqqFqxSqq6utrK2urrCvr6+ur6+urq6trYSuCa+vra2ura6urYSsN6urrK2sra6wsK+vr6ytrayrq6usq6mqq6uqqKalpaWjop+fnJualpSSl6mvr66yt727vLattLiGuUK4ubi3t7W1tbOysLCvsLGwr7GwsbCxr6+urKqno6Wtr6+opKmusbGysrS2s7KxsK+tq7e6vm4fHR4dHh0eHR0eHR6EHQQeHR0dhRwLHRwdHBwcHRwcHB2EHAEbjxwCHRyHHYIehR2HHgEfjR6FHYMOiA+JEAwRERIJCQkStbGtqKCFqAGph6oNq6uqq6qqq6uqqqurq4SsAq6vhbAUramlp7K3vUQNDgwLCgoJEhIRERGbEAUhISAgIIghCCIiISIhIiIihCEBIoQhiCABIYQgBh8gHyAgIIofhCABIZEgjh8KHh4eHx8fHh4fH4QgBB8gIB+KIAYhISAhICCEIYMgiCEWICAhISEgIB8hISAgISAgICEhICAhIIwhhSAIHx8eHh4dHR2JHAUdHB0dHogdAx4fH4QgOR82297e19vj7/j9gomQlJmfo6etsbW5trS2s7CyuMC/ur/Ix8HEzcq+vcC/uLCtq6ajo6iop65aLoUtFC4tLSwrKyoqKSgoJycmJiUkJCQjhCKIIYIghB+FIIUfAiAfhyCDIZAQhQ+PDogNhQ4mELewraqkra2trKysq6qrqqqqp6CVoKiqqZ+mrKyrq6usrKytra6ErSyrrKqqq6yrrKysq6yrq6uqqqurqqqqppmWpaytq6WlqaysqqWcnZ6fn6GiooSgCKGio6Kjo6SnhKgOqaytrKysq6qpqamqqqmFqhurq6qqq6qrq6usq6ysrKurq6yrrKupqqqqqamIqherqqmqq6qrqqmqq6ytra6ura2sq6usrANvcG+FcAxxcXJxcnFycnNzdHSEdQR2d3d3hXaGd4Z2Bnh4eXp6eYR6GHt6fHx9fXp5d3V0cnJwb25tbGxrbGtraoRrhGoKaWlpamtsbm5ub4ZuhG0BbIRtiG4LbW1ubW1ubm1ubm+FcCtxcHBxcG9vb25vbm5vb29wcG9ubm1tbGtoaGdkY2Jmcnd2dXZ5fn9/fXp7hXwTe3p5eXh4dnZ1dHRzc3NycnNxcYVzIXJzcnBtaWZpc3V2d3d4enx9fn9/gIB/fnt4dnSEiY1eNoU5iTgBOYU4hzcENjY2N4U2Azc2N4o2hjcBOIQ3iDiEOYY6iDsuPDw9PT4+Pj9AQEEgISEiIiIjIyQlJiYnKSorLC4vMDI0NhwdHjCqkIuCdHt8foyAA3+Af4aAhIEsgoKDhISEhYWFhIOChouPl1glJiMhHx0cNzUyMC4tLCsqKSkoJycmJiUkJCSEI4UiCCEhIEFCQUJBhkAFPz8+Pz6EPwQ+Pj8/hT4DPTw9iDwFOzs8OzyEOwc6Ojs7Ojs6hDuaPAM7PDyMOwc8PDs8PDs7izyDPYY8AT2FPIQ9hzwEOzw8O4Q8AT2FPIQ9Ejw8PT09Pj09Pj8/Pj4+PT0+PoQ9Azw9PIU7Ajw7hjqIOYM6iDk8OEujoaGbm6GprbFbX2Nna25xdHd6fX5+f4GAf4CDiIaEhoqKiIyRjYWFhYSAfX17d3Z2d3Rzk2Y8PT09hj4DPTw8hDuDOoY5hzgDOTg5hDiDOYQ6hjsVPDw8PT0+Pj4/P0BBQiEhISIiIiMihCMXJCQkJSQkJCUlJSYmJycnKCgoKSkpKiqEKwUsLC0tLoUtBiSDd3NvZ4RuE21tbWxsbGtramVeZ21vbWVpbG2MbDZra2xra2xsamtrbGxsbW1tbGxtbW1sbGpkZGpubmxoaW1vb25oYWFiYmJjZGVlZmdnaGhoZ2iEaQZqa2xsbW2HbIRrh2oEa2praohrg2qHawNsa2qKa4RqhmwLbW1ub29wcHBvcG8BS4ZMB01NTk5NTk2FThRNTU5OTlBPUFBQT09OTU5OT09PUIRPGk5OT09QUE9PT1BPT09OTUxKSkhGRURDQUA/hT4MPT0+Pz4+Pj8+Pz8/hUCCQYZCAkNCiEMOREREQ0NCQkNCQkNDQ0SFQxVEQ0NERENDQ0RERUZGRUZGR0hHR0eERgVHR0lISIRGT0VCQUBEVVxYUlBUbHZ7kpmFfXh1cW9ta2tramloaWhnZWZmZ2dnaGdoamlqamppZ2NeV0U8SXB2epqhlZGQjo2NjYyIg3xwZF5fjJyln6yExgfFxsbFxsbFhMYUx8bFxsXGxcXExcPDwsXDxcPDwsWEwxzBw8TEw8LDxMPDw8TGxsfFx8jJycjHx8fJy8zMhM1Dzs7Oz8/Q0tTV1tjZ2tvc3uDj5Ofo6/D1+f6AgIOGh4uPkpWZn6SqsLW9xMzU3ubu94CFibb3oJKCbXN0d3l6e3t7fIR7BXx7e3t8hH2Afn+Af4CAgYGChIWHiImLjJCUm6Clu9uYopqSjIeD/fPp4NjRzMa/ubOvq6eioJyamJaTko+Mi4qIhoWDgYD//fv59vPx8fDv7+3r6eno6urp6Obm5uXj4uHg3+Df4ODe3t/d3d3c3Nzd293Z3Nrb2dnZ2tjZ2NnY2tra2drb3N0I3d3e3t7f3t+E3gTd3t3dhNyE3QTb3Nzchdsc3Nvc3Nvb3Nzc3dvb2tvc3d3d3Nzd3t3c3dzc3YXeGuDg4eDh4d/f4OHf4N7h4N3d3tzc3d7e4N3dhOAT4eDf4OLi4+Tk5ujq6enm5uXn54TmgOfm5ufj4uXk4+Tg4N/h39zc29ra3Nvb19fZ29rY1NPT0c/OwbCsnpiFfH+EiYxJTE1NTk9QUVFRUlNSU1JSUVJTVFFPT1FQT09RUExMTk5NTU9QU1pZUE5SyOupsbKztLS1tre4uLi5ubq7u7y9vr/Bw8TExcbFxcbGyMrKx8rLW8/Q0tLS09PT1tbZ2dze4OLl6Ojr7O3v8vT2+Pv9gYKDhYiJi4yOkJKTlZeYmpyen6KjpqiprK+xtLW4u7y9v8HExsjKzM/R0tPU1NTT0oChc2hfVVlZWFhXV1aEVQ9WVVFOUVNTUUhMT1BPUFCITwRQUVBQhE+DToVPAVCHURtQTk1QUlJOSUhMTk9OSEFBQkJCQ0NCQkNEREWERoJHhkgBSYRKAUuFSgZJSUhJSEiERwNGR0aHRwFIhEeCSIZHBEZHR0eGSIdHhEgFSUpLSkmESoJL/3/wfwGA4oKXgwSEhISDsX8BgYeEoIP/gt6CAYGJf6yAAYHCgrKD/3+zfwICBAACrKuFrCutrKyrq6qsra6trK2ur7Cvr66wsrKzsbKysbCxsbCwrayrrK2srq6vsLGyhbMitLSztLS1tLKvra2sq6usq6qqq6urqaqpqqusrKqqqaqpqoSrHqytra2ura2sra2srKyrrKytraytra6tra6urqyrqoSpdKurrKurq62trK2trK2srK2srKuqqqmqqaimpKWkpKKhn56cmZeVk4+Qpa+urrO3uru7sK2ytba3uLm3uLe2tbW1tLKzsq+sr7CxsLCxsLCxsLCtrKurp6Onrq6so6atsLGysrOysrGysa2qp7G5vc0fHh0ehB0CHh2EHgIdHoUdhRwMHRwcHRwcHBscHBsbixwQGxwcGxwcHB0cHBwdHR4dHoYdkB6EHQQeHR4ehh0BDoQPjRAREREREgkJCTixr6uiqKmoqKiEqQKqq4iqDaurrKurrKysra2ur6+EsBStrKakr7e7bRcNCwoKCQkSERAQEZEQgw+FEAMgICGIIIYhgiKMIQQgICAhiyAIHyAfIB8gICCMH4cghSGFIIofBR4eHh8ehR8KHh8fIB8fICAgH4UgBR8fICAgkyEBIoQhCiAhISAhIiEhISKJIQUgISEhIIYhgiCEHw0gHx8eHx4dHR0cHBwbhhwBG4QcAR2EHAIdHIQdhB5RHTTIzc7N0Nvi6u/1+oGGioyOjpGVl5WVlZSSlJaan6Ccm6Cin6GmqqKenZ6empaSkJCRkZekKysqKikpKiopKSoqKSkpKCgnJyYmJSQkIyMjhCKCIYgghB8BHoUfASCIHwIgH4QgBCEgICCPEIQPiQ6MDYgOGg+6sq+qo6ysq6qqqaqqqaqpqaeblqqtrKijhKsMqaqrrK2sq6utraythaw6q6yrrKytrK2sq6qpqKmopp6Wn6msqqWlqqusrKWfnp6dnqChoKKkpKOjo6Wmp6ioqKmqqqurraysq4SqhakLqKioqquqq6qqq6qFq4asEaqrqqqpqaqqqaioqaioqKenhKgWqaqqqqusqqmoqqytra6ur6+tra2srAlwb29wb3BwcXCEcQhycnNzcnNzc4R1LnZ4d3d2dnZ3dnd2dnZ0dXRzdHN0dXZ3eHh5eXl4eXp6eXp6eHd3dHNxcG5ubWyHa4JqhWsLamlpampqa2xsbW2EbgZtbW5tbW2IbBRtbm5ubW5ubW1sbG1sbGttbG1uboRvgnCJbzNwb29vbm5tbWxramloZmRiYmBgbXV1dHV2e3x9fXp7e3t6enl4eHh3dnV0dHNyc3Rzc3OFcgxzdHN0cnJvbWlma3WEdx55enx9fX5+f39+fHl2c3yGiZguODk4OTg4OTg4ODmHOBo3NjY3Nzc2NzY2NTY1NjU2NTU1NjU2NjY1NYk2BDc3NzaINwc4ODg5OTg4hTmIOoQ7MTw8PT0+Pj8/QEBBICEhIiIjIyQlJicoKSorLC4vMTM1NxwdH16Ui4d0eXx9f4CBgYCFgYiANYGAgIGAgYGCgoODhIWFhIODgYKJjJJdPCYkISAeHTg1MzAvLi0sKyopKCcnJyYlJSQkIyMjhSIDISFChkEGQEA/QD8/jD6HPYU8ATuFPBA7PDs7Ozo7Ojs7Ozo6Ojs6hjsDPDs7hTyFPYQ8AT2EPAM7OzyNO4U8DTs8PDw9PTw8PD09PTyHPQU+PT4+P4c+iT0FPDw9Pj6EPYs+Bj8+Pj8+Pog/Aj4/hT6HPQM8OzyEO4U6gjmLOjc5WKOWl5CPlpugpKuyXF9gYmRlZmhoaGpqamhoaWxwcW9vcnNxcXV2cGxrbG1ramlpaGZlaZM0hDqHOw06Ozs7Ojo5Ojo5ODg5hjiEN4g4Dzk5Ojk6OTo5Ojo6Ozs8PIQ9CD4+Pz9AQEFAhCGEIoQjhSQNJSUlJiYnJycoKCgpKYUqBCsrLCyJLR8nj3dzbmhub25ubm1tbGxsa2tqY2BsbW1qZmxtbGxrhmwBa4RsAWuLbCltbm5tbWxsbGtmYmhub21paGxub29oYWBhYmJiZGNkZmdnaGhpampraoVrBGxubm6FbQRsbGxrhGoKaWhpampqa2praolrAmprhWqFawVqa2xraoZrAWqGawRtbW5uhm8DcG9vBE1MTU6FTYVOCE9PT05OT09QhVEBUIRRDFBPUFBRUVBQT09PTolPFE5PTk1LSklHRURCQkJAQD8/Pz49hj4CPz6FP4RAA0FCQodDAUKXQwdCQ0JDQ0REhEMzRERFRUZHR0dGRkdHSEdHRkdGRUZHR0ZGRUVFREJAPjw+TllWUU1QbHh7l5KBeXVxbmxrhGo1aWhnZmZnZ2VmZ2dnaGlpamxrbGllXlNDO1F0eH+nnJGOjYyMjIuKhoB1aF1Ze5uj0IXDxMKLxAHFhMQSxcXExcTDxMLDw8PCw8HBwMG/iMESwMHBv8DBw8PDxMXGx8bFxsfHhslIysvLzc3Nz9HT09PS1NbY2Nra3N/g4+Tm5+rs8fX6/oCChIeLjpGTmZ+lqrG2vcPM1d7l7/mBhoX/s5iMcHN1eHt8fX19fHx9hHyFfWV+fn5/gICBgoKDg4WFh4iKi4+TmJ+isJHfopuUjYiD/PLo39fTysW+ubKuqaWhnpqYlpSSkIyKiYiGhYOB///++/j08+/v7O3q6ujn5ubl5eXk5eTi4eDg397d3N3c3N3d3t3c24TcV93c2tvZ2tfZ2NnX2dnZ19nY19fZ2Nrb3N3d3d7e4ODh4eDf397d39/e3t3c3Nzd3Nzc29va293e3dzc3Nvb3Nzc29zc3t/e397e397d3t7e4OHh4eLi44Tld+fm5ubn6Ofm5ufm5OTj5efo6Onp5+jo5ufp6efn6Ojq6enp6urr7O7v8e7w8fHy8vLx8/Hx8/Dw8e7u8O7s7ezr7Ovm5eTm5eTj4+Lh3t/f397d29jW1tD2v5iUfHJ3e4GGjZFJSktMTExNTU5NTExLS0pKS0tKhUkESElJSIRHEUhJTlVRSUhR64GtsLCwsbKzhbR2tra3t7i5ury9vr7AwMLDxMTExcXGxcTGysvOz9DQ0M/Q0NPU1tjZ293h4+bm6ens6+/v8vP2+Pr8gIGDhYeJi42PkJOUlpeanJ6ho6SnqayusLK0t7m8vsHCw8XIy83P0NDQ0dHS0tHPkLt4bGBWW1pZWFhXV4RWDlVUUE5UVVROSE1OTk9OhU+CToVPBFBPT1CHT4RQhFEWUE1KT1FRT0lHS0xOT0hCQUJDQkNDQ4RECUVGRkZHSEhJSYZKgkuFSgdJSkpJSUhIhEcERkZHR4lGgkeGRohHA0hHR4ZIgkeESA5JSkpKS0xMS0tKSktLTP9/8H/lgpaDBISEhIGvfwKAg4aEnoP/guCCAYGLf6mAxoKxg/9/s38CAgQAAqushKsMrKysqqqqq6mqqqiohKcyqKemqamnp6anpaWmqainpKGhn6Gho6Skpaemp6iqqaqrrK6vsK+trKyoqaipqaqqqKiGp4SpAaqLqwWsrKytrIWrAayEqgOrq6yIqxCqqaiop6iqqquqqqusqqqqhawpq6utra2rqqmoqKejo6SkoqGgn56cm5mXlpOQkKKvsLGws7W2t6ussraEtRK0tbW0s7SysrGwrq+ur7CwsbCErw+tra2rqqelqq2trKOprbCGsQyys7CrqKu0uME+Hx6IHQYeHR4eHR6IHQIcHYgcBRsbGxwbhRyEG40cBB0dHRyEHYkeBB8eHh+VHogPAhAPhhAOERARERIJCRC1rq2opKiEqQOqqquEqoKrhKqEq4SsGa2trq6ur6+wr66rpKezuMImDAwLCgkJEhGUEIMPhBAEIB8gIIYhgyCTIQUgICAhIYwgjh8BHogfiyADHx8gkx8EICAfH44gASGEIIwhBSIhISEihyEDICEgiSEFICEhISCHIQQgISEhhxCCD4UOhRwGGxscHBschhuEHAMdHB2EHDkdHBpfxMTAwcvV3OTr7vT+goOCg4KBgYCB//z9/4GDh4qKiIuOjIqNj42Ig4OEhYH9gYOEhZAoKCiFJwQmJycoiCcIJiUlJCMjIyKGIQcgICEhICAghB8GHh8fHx4ejR+GIAMfHw+MEIUPig4LDQ4ODQ0ODQ4NDQ2JDh4QwrOuqKGqqqmpqqqqqamop6eil5yqrKuhp6urq6qFqQWqq6qqqoarO6ytra2srKurra2tq6qop6OYl6aqqqWip62vraaenJ2dnp6eoKCgoaKkpaamp6eoqaqqq6usrK2vr62rhKo2qKipqaipqKmpqqmop6enqaqrrKuqrKysqquqqqipqqqqq6ipqKmop6enpqenqKeoqKepq6qqhKuJrQOsq6yFb4NwhG8GcG9wcHBvhXAycXFycnFvcHBwb29xcHBubm5tbWxtb29wcnNzcnNzdHV0dXV2dXRzc3Fwb29ubm1ra2uFaodrhGoFa2trbGyEbYZsCG1sbGxrbGxthGwTbWxtbGxtbGxra2tqamtrbG1ucIRvhG6Gb1xub25tbW1sampqaWlnZ2VkYmFfX2t2dnV1dnl8fHp2eHl4d3d2dnV0dHR1dHRzdHNzdHNzcnJyc3NzdHR0c3FtamZvdXZ2d3d4enp6fHx9fn59enVydoSHjUs4OY04hzcHNjc2NjU1NYU2ijUENjY1NYU2EjU2NjY3Nzc2NzY3Nzg4ODc4N4Q4Cjk5OTo6Ozs6OjqGOy08PD09Pj4/P0BAQUEhISIiIyMkJSYnKSkqLC0uMDI0NjgdHjCnjoh+d3x9gICKgYiAhYEEgoKCg4SEJIOBfoWKjppIJiQiIB8dOTYzMjAuLS0sKikoKCcnJiUlJCMjI4QiAyEhIIZBgkCFP4g+hj2JPIY7Bzw7Ozs8OzuMOgM7OzqEO4M8hz0GPDw9PD09hjyFOwQ8Ozw7hTyCO4Q8BTs8PTw8hz2GPoc/EEA/Pz4/P0BAQD9AP0A/Pz6EPwM+Pz6HP4RAED9AQEBBQUFAQUFCISAgICGJIAZAPz9APz+FPgg9PDw8PTw7O4Q8Pzs7Ozw8PDozWpKRioeMkpacoaasr1hYWVpbW1taW7a2tbRbXWBiYWBgYmNiYmNhXVtbXV1cu11dXF6OMzk6OYc4CDk4ODg5ODk5hjgCNziFNwE2hDcMODc3ODc4Nzg4ODk4hDkLOjo7Ojs7PD08Pj2HPwFAhCAHISEhIiIjI4UkgyWEJgUnJycoKIQpByoqKysrLCyGLQsuLi0ooXZyb2lvb4RuhG0NbGxoX2NsbGtkaGtrbIhrAWqEawFthmwFbW1tbGyEbUJubGxpY2Jtb29raGtub29qY2FiYWFiY2VlZmdoaGlqamtqa2xsbG1tbW5vb29ubW1ubW1sa2tramppamloaWppaWmEaoRrh2oFa2pqammIaoVrDWxra2xra2tsa2xtbW2EbgVvb25vb4pOGE9PT05PT05OT09PUFFRUFFRUFBPT09QUYRPAU6ETxdOTk1NTUxNTUxLSkpJSEhIRkVDQUFCQYRAAT+EPoI9hD6DP4VAgkGJQoxDAUKHQwREQ0NDhEIDQ0JDhEQHQ0VERUVHR4RGC0dHR0ZGRkVFRkZGhEVfQ0NCQD48OzpKWFVMSEpsdnqdkH12cm5sa2pqamloaGZnZ2dmZ2dnaGhpamtsa2tqaGVdUUE4XnR3hqeYkY2Mi4yMioaAeGxgWmaSna+5v8HDw8PCwsHCwcHCwsTDw8KFwxPCw8HCwsHCwsPCw8DAwL/Av7+/hMAcwcLAwMDBwcHAwcPExMPExcbGxsXGx8bHyMnKy4XMPs3Oz8/Q0tPU1dfY2Nvf4eXm5+jq7vL5/v+Bg4aKjZCTmZ+lq7C2vMPM1d7l7/iBhrXqnpJ9cHV4e319fn5+hn2Iflt/gICAgoOEhYaHiYqMjpGUnJ6myuOgnZaNiYT98+nf19HLxr+4sayppaKdmpiXk5COi4qJhoSDgf/9/Pr59/Tx8PDt6+nn5eTk4+Pi4uHh39/d3t3c29vb2trahdsB2YTaI9nZ2NjW19fV1dXX1tbV1dXW19fY19nZ2dvc3d7g3t/g397fhd4G39/e3N3bhdwE29rc3ITdgNzd3d3f3t/d39/e3t/h4uHg4ePk4+Pl5ebm5uXn5+nq7O3s7Ozw8e/w8PLx7+7u7O7s7vHw8fHz8/Hz8fPz9fX19PL2+Pv7+vv9//+BgYCBgIKBgoGBgYCAgf/+//3+/vr4+Pn19fTy8e/x7+vp5+np5+bk4+Pg25+NlpF+bHB0Cnp+hIeLjkhHR0eESAlHjo2LikRDRUWFRBhDQ0JCQUFBQkVJn09HRU7yiK6wr6+vsbKHs4C1tLW3t7i5uru9vb/Av8DAwMHCwsPDxcjKy8zMy83M0NHQ09TX19rb39/h5OXo6ezu8fL09vb5+/6AgYKFhomLjY+SlJeZm52eoaOlp6mrra+xs7e5u72/wcTGx8fJzc/Q0dHS09PT0s+g6XdqYFZbWllYWFhXV1ZWVlVTTk9UVAdSSEtNTU5NhU4HT09PUE9PToVPB1BPT05PT0+EUBRRUVFPS0tQUVFNSUxPUE9JQ0JCQoRDA0RERYRGJUdIR0hJSUlKSkpLS0tMS0tLSkpKSUlJSEhIR0dGRkVGRUZGRkeSRgRHR0hIhEcMSEhJSElJSEhJSUlKhkuHTAJNTv9/738BgeeClYMDhISDr38BgoaEnYP/grWCjoOfggGAjH+JgIR/k4ABf4WAyIKxg/9/s38CAgQAR6empqWlpaOhn5+enJydnZ2cnJiZmZqZmJqam5uZmpiXlZeZlpWVk5GQkZGTk5SUlZOTk5KTlpaYmZyfnZ+dm56goqSkpaOlhKYapKWmpqenqKinqaurqqupqKmqqqysrK2sq6uEqgasqqqpqamEqgirq6qrqqqqqYSoX6mpq6urqqqpqaqsq6qqqamqqqmpqKenpaWjoaKjoqGfn56dmpmYl5aUk5Olsa+vs7S5uraoq7GztLSzs7KysbKysrGwsLCura2vr66trq2srK6uraqrqKSqrq6npauvhLEVsLGwsK6rqaiytrt3Hx4dHh0eHR4dhh4LHR4dHR0cHRwdHR2EHAMbHByFG5cchB2DHoYdhx6HHwIeH48eig8CEA+FEBgRERITCQk2sK2roqioqamqqamqq6qrrKyEq4ashq0BroSvGa6sqKSutbo8CwsKCgkJEhEQDw8PEA8QEA+FEAYPEBAPDxCEDwgQECAgHx8gIIUhBiAgISEhIIUhAiAhkSABH4Qgih8BHogfAR6IH4ogAx8fIIgfDSAgIB8gIB8fHx4fIB+MIAshICAhISAgICEgIIshAyIhIIchCSIhISEgECEQEIURlRCDD4YOjg2JGzkcHBscHBsZWb69vLfCy9HX2+Hm6+3v7u3r5+bm5OPi4uPp7fD2/Pz8gP/7/4D78+zn5ujm6O/y9o2FJoUliCSCJYQkASOFIoghhCAHHx8fHh4fH4UeiB8BIIkfASCFEAIPEIsPAg4PjQ6HDYkOYBBls6+ooaqpqKeoqaqqqainpZ6So6yuqaSrq6uqqaqpqaioqampqqqrqquqq6usq6ytrayrq6uqqquqp5+Un6urqqOkqKqsqZ6cnZycnZ6foKKioqOkpaWmp6ioqKmpq4SsKK2tq6urqaqqqqmoqampqKepqainp6amp6ipp6mpqaqqqaioqaipqqiEpw6op6anp6inpqenqKeoqImpCKusra2rq6qphKgDbGtrhWoCaWqEaQJoZ4VogmmFaAhnZ2doZ2dmZoRlhGQRZWVmZ2doaGhpampsbW5ubW2FbA5tbGxra2tpaWpoaWpqa4tqB2trbG1sbG2EbARra2xskGsEamtra4RqGmttbW5ub29ub25tbW1vb25vbm5ubW1sa2tqhWkraGZlZGNiYV9fbHd1c3NzeHp7eXZ3d3Z2dXV0dXV1dHRzc3JycnNzcnJxcodzHXFtZ2VxdHV3dnd5eXl6e3x+fnx5dnRxf4SJdTc3iDgBN4c4hTeJNgE1hDaPNYo2BDc3NziGNwQ4Nzg4hTmCOoY7MDw7PDw8PT0+Pj4/P0BAQUIhIiIjIyQlJiYnKSorLC0vMTI0NzkeH1aSioV0e31/gImBh4CGgSyCgoKDhISFhIOCf3+Ii5FAIyUiIR8eOTc0MjAvLSwrKikoJycmJSUlJCMjI4QigiGEQYNAhT+IPoQ9Ajw9hTwGOzw7PDw8izuIOgE5hjqGO5E8AT2LPAQ9PDw7hjwGOzw8PD08hT0FPj49Pj6HP4ZAiUEBQoVBB0BBQEFAIEKHIAMhICCOIQEikSGFIIQfRj4+PTw9Pj09PTw8PTw8Ozd0jYyIgYaLj5OXnJ6hoqOkpqalpKSio6Sjo6aprLCwr61XsK+wV6uppqKhpKipqaquiDI4ODiJNwQ2Nzc2hDeDOIY3BDY2NzaFNwM2NjeLOBg5OTo6Ojs6Ozw9PT0+Pj8+Pj4/Px8gICCEIYMihCOEJIIlhSYJJycoKCkpKSoqhCuCLIgtCSpbdXJuaG9vboRthGwka2ZeaGxtaWRramtqamtqa2pra2pramprbGxsbWxtbGxsbWxshm0ba2dhZ25vb2lrbm9vbGNhYmJiY2NkZGZnaGhqhGsBbIRtDm5ubm9ub29ubW1tbG1shmsEampqaYZoDmlpampra2pqamlqaWpqhWmEagRpampqhGsPbGtsbGtra2xsa2tsbG1uhW2DbIpOA01MTYVOB01NTU5OTk+EThRPTU1MTk5NTk5OTUxLS0tKSklISIRHF0ZFQ0NCQUJBQD9APz4+Pj8+Pj49Pj49hz6FPwFAhEEFQkJCQ0SNQ4NChEOERAhDQkJCQUJCQ4ZEhEUFRkZGR0aIRSZGRkVERERDREJBQT8+PTs5OUpYUkhDSG5yfaCJe3NwbmxqamtqaYRnG2ZnaGhoaWlpamtqaWlpaGFYTT48a3V5nKKSjYSMSImGgHltYFlbh5qn9bbBwcLCwsHCwcHAwsPDxMPExMTFw8PBwcLBwsHBwsHCwcG/wMC/wL++v76/vr/AwcDBwsHBwcLDwsLBw4XEBcXGyMnJhcgHycrKyszNzoTQNtHS0tTV1tna3eLk5ubq7fH1+/2AgoWIjZCVmp+lq7C3vsXO197o8fqBgd2qlYpwdXd6fH19fYR+iX+EgFKBgYKDhIWGiIqLjZCUmp6gsYKKnpiQiYT+8+rf19LMxb63sK2opKGempmWkpCNi4qIhYSC//v6+Pf18/Hv7+3r6ejn5eXl5OPg397e3Nzb2trZhNgG2dnZ2NjYhtcF1dbV1dWF1BXT1NPV1dXW1tfX2NnZ2tzc3d3d3NyE3TPc3d3e3t3e3dvb3Nze3dzb3Nzc3d3e3N3d397f397f4eHh4ODh4uTj4+Xn5+bo6Orp6uyE7yHw8vP09fb4+Pj59/n4+f39/vr5+P2A/4GAgYCBgIGAgoKEgQeCg4SFhYaFhodhiImIiYiJiomIiIeIiYiGh4WDhIOBgICA/fv6+vn18fLx8O/u7OnnwfeTjX9na29zd32DhYeJiYmKiYiHiIeFhYaFhYWEgYCAgEB/f4BAfn5+f4CIlZWHg5P2h6yvsLGwsISyELGxsbKzsrS0tra3ubq6uruFvVS+wMDAwsPExcfIysrKy8vNztDT0tTW1trb3d/g5OTn6O3w8fT09/r7/oCBg4SHiIuMjpCTlZiYm5yeoKGkpqmqra6wsrS3uby+wcTGyMrKys7Q0dGE0gvR0K6TdWhfVVtaWIRXhVYHUk5SU1NOSIRNB05OTk1NTk6ETwZQT09OTk6ETwNQT0+HUBBNSU5RUU9JSUxOTUtDQkNDhEQFRUVFRkeFSAFJiUoOS0pKS0xLSkpKSUlJSEeHRolFiEYGR0ZGRkVFhUcJSEhISUlKSUpJhEqCS4ZMgk2ETgNNTU7/f+5/AYDpgpWDA4SEga1/AYGGhJyD/4KeggKDgrODkIIBgKB/BYB/f3+Ai38BgMmCsoMBgP9/sn8CAgQAUZqZlpSSkpCRkZGTk5WXmp6ho6Soqa2vsbO0tra2t7a3t7i4uLSztbSysbGwra2rqaemo6Ggn5qZlZORj4yKioqMj5KUlZiZm56fn5+io6SlpoSoCaqqqamqqamqqoSsZ6uqqqqrq6qqqaqqqqmoqKipqaqqqamoqaqsqqqpqKipqquqqqqpqqqrqaiop6emqKanpqWko6OioaGhn56enZuamJiXlZWUkpSnsK6xs7S4trCnrrKys7GxsbCwsbCwsK+vrq2trq+EriGtrq+ur66rqKalr6+upqmsrq+wsLGwr7CwrKmmsLe6bh+FHoMdhx6MHYIciRuRHAYdHR0cHRyFHQEciR2HHgsfHx8eHh8eHh4dHYUeAR2HHoUPAQ6JDxAQEBEREgkRr62ro6apqqqqhquHrB+trKysra2trq6vrq+wsLCvramkp7G4xBQKCgkJCREQhQ8FDg8PEBCHD4IQhw+LH4YgASGKIIMfhyCDH4Qghh8BHoUfAR6SHwcgIB8fHyAghx8IIB8fIB8gHyCJHwEghB8JICAhICEhICAghiEDICEiiiEBIIkQghGEEAQREBAQiRECEBGEEAERixCED4MOhA0BDokNBAwNDQyFDQMMDBmFGjsYLba7ubO5wcbKzdHY293b3drY2NfW1NPS0tLT1tvg4+Lg5eTj5ejk3dza1c7P2t/mhicmJiYlJSQkJIYjiCIBI4YigyGFIAMhICCFHw8eHh8fHx4eHR4eHh8eHx6EHwseHx8eHx8fICAPEIkPhA6LD5gOKBBttK+qoqqoqKenqKeopqalpJyUqKutpaesrquqqqmpqaqqqaqqq6qHqzKsrKusq6urqqqpqKeclqWrq6ehqKutq6CbnZ2fnp+fnp+goqSlpqanp6irq6qqq6qsrIStDKurq6qpqauqqqmpqoSoA6mnqISnhKYBp4aoAaeFqAenp6inp6anhaYap6anp6amp6eop6WlpqenpqempaOgoJ6dnZ0aZGNiYWFiYmFiYWJkZmdoamtub3BydHV2d3eEeCZ5eXp6eXp4d3h3dnZ2dXR0dHNzc3Fwb21sbGtqaWdmZWRjZGRmZ4VmBGdnZ2iEaYdqC2lqampra2xsbG1thWyFawtqa2tqa2trampra4RqAWuEajdra2xsbm9ub25ubW1sbG1tbWxtbGxsa2tqaWppaGhoZ2ZmZGRjYmFfXl5tdXNwb3F4eXl1dnZ2hXQxc3R0c3JxcnJxcnJzcnJyc3JzcnJycXBrZ2lzdHR1dnd4eXp7fH19fHp2c296g4ZaNYw4Bjk4ODg3OIY3iDaRNQU2NjU1NYQ2BDU2NjeFNoY3CDg4ODc4OTk5hDo0Ozo6Ozs7PDs8PT09Pj4+Pz9AQUJDQyIjIyMkJSYnKCkqKywuMDEzNTc6HiydjIh5en1/gJeBA4KCg4SEI4KAfoSKjZ8wJiQhHx46ODUyMC8uLCsqKCgnJyYmJSQkIyMjhCIJISBAQUFAPz8/hj4DPT0+hD2HPIw7jDqFOQI6OYU6Azs7Oog7hzyCPYs8ATuEPII7hjwCOzyEPQQ8PT09hj4GPz8/QEFChUEBQoRBgkKKIYIijiGFIgMjIiKHIw4kIyQkIyMjJCQjIyMkJIYjCCIiIiEiISEhhCCFHzs+Pj09Pj08T42IhnqAhYmNkZWXmJmZm5qamZmXlZSVlpaXmpygoqKhoZ+fn6CdmpqYlpacmZmeezE3OIU3iDYBNYU2Czc3NjY2Nzc2NjU1iTaENxw4ODk4Nzg3ODg5OTo6Ozs6PDw8PTw8PT0+Pj4/hyCDIYUigiOEJIIlhCYFJycnKCiEKQgqKisrKywsLIYtCitwdnJuaG9ubm2EbBFramloY15rbG1naGtsa2tqaoZrDmpsa2xra2xsbW1tbm5uh20fbGVga29va2hsbnBuZmFhYWNjZGRlZmdnaWlqa2tsbYZuh28Obm5sbWxsa2tramtra2qEaYRoBWdnZ2hohWkBaohphmoFaWlqa2yGawFqhGsObG1sa2xramloaGdnZmUUTU1NTExLSktLS0xLS0tMTVBPT1KFU4RUJ1VUVFNSU1NSU1NRUFFQT09OTUxMTEtJSEhGRENBPz48Ozs8PDs7O4U8Dj08PD0+PT4+Pz8+Pj8/hEABQYRCD0NDRERERUVFRERDRERDQ4ZCh0OEQoVDhEQKRUZFRERERUVFRIhFH0RDQ0JCQkFBPz49Ozo5NzdLVVBFP09vcoqagXdybmuFaQNoZ2eEZihnaWpqaWlqaWpoaGZgWUk7RnJ3gKiYjYyLi4qIhoB4b2FYVXSWo6CkhcAjv8DBwcHCwsTDxMTFxcTDw8LCwsHBwsHCwsHAwL/Av7+/wL+Evhe/wL+/v8DBwcLCwcHBwsLDwsLBxMXFxoTFRMbHyMnKy8vMy8zMzM7P0NDQ0tPT1dXX2dvf4uTm6Ovw9Pn8/4KFiY6RlZqfpqyyuL/I0Nnh6vP9gpzOnJF2dHd6fH5+hX+HgICBgYCBgIGBgoKDhISGh4iLjZGWnp2m1qKdmJCJhP7z6eDX0srDu7OurKekoZ6amJWTkI2KiIeEg4H9+vr29PLv6+rp6Ofl4+Li4+Lh397d3Nva2tnY2NfW1dbW19bW1dXV1tbV1dTU1NPR0NLR0tLQ0dLS1NPU1tbV1tbZ2dna21Tb29rb293d3Nzb3N3b3N3d3t/e3dzc3drb3Nzb3N3e3t3d3t/e4ODg4uPi5ePj5eXl5Ofp6+zs7vDx8vP09fb29vT39/n9gIGAgIKEg4SEhIWGhYaFhRKGhoaIiYqKiYiLi42Mi4yNkJCEkmaUlZWUlJSVlZaWlZeWlZaVlpeWlJSSkJCQjYyKiYeHhoSCgoGA/fr7+vfz5tieioFmZWpuc3p/g4SEhIWEg4GAf35+fX19fH19fHx7enp5eHh4d3V4en+MmYh+iMuFr7Gxs7KzsrOEsmmztLSzs7W1tba3uLi5uru7urm6uru9vsDBwsLDxMXGyMrLy8/Pz9LS1NbW2dva3t/g5eXp7O3v8PT2+fz+gIKDhYeIiYqLjo+Rk5WXmZudoKKjpqirrK6vsrS3u7y/wcPExsjKzMvN0NGE0gzR0L7Yc2VeVltaWViFVxNWVVVRTlNTU0pJTE1NTU5OTU1NhE4CT06ITwNQUFGEUIRRFE1MUFJRS0hLTU1NRkJDQ0RERUVEhEUIRkdISUlKSkqFS4JKhUkDSktLhEoKSUlIRkdGRkdGRoRFh0SFRYJGh0cISEdISElJSkmISoRLDkxNTUxNTU1PTk1MTExN/3/tfwGA7IKUgwKEg61/AYOFhJyD/4KOgs6Dh4IBga9/AYDKgrODAYD/f7J/AgIEAFeUlpqdoKOoq7K1tbi4uLe7vLy9vL7AwsTExMXHx87V3+Xq7/bx7eni1tDHxMPBvry7uri3t7a1s7OwsbCuqKOgm5iVkY2KiYmNj5KVmZyfoKCho6Wmp6eEqAupqqmrq6qrrKurqoSrI6qqqqmqqqmoqKqrqqmoqampqquqqaioqaqqqaqoqKeop6eohadipaWlpKOioqKhoJ6enp2cm5mYmJeWlJOSkJOtr6+vsrS2tauosLCvrq6ur7Cvr66wr66ur6+usLGwr66ur66trqyqpqKqsK+rpqmsra2wsLCxsK+rpqSns7nRIB4eHh8eHh2HHgQdHh0ehx0KHBwcGxwcHBsbHIUbjxyJHYIcjR2DHoYfhx6CHYUeAR+EHgEdiA4WDw4ODw8PDg4PEBERExIzsKyoo6mpqoerEKysrK2srK2trKysra2trq6ErxOurq2qpqOts7lACgoJCQgQEBAPhA6KD4IQhw8DHx8ghh+CHoQfiCABH44gjB8BHpMfAR6SH4ogAx8fIIofiCAEHyAhIIwhkBAFEREQERCHEQQQEREQjRGGEIYPiA6HDYoMAQuEDDcLDBgXF1i4uLCwuL/Ex8rLzc7Qz9DOz83Ky8nJycrLzM/R1dTQ0tbV1tbUzsvGxMPL1Nd8SycmhSUFJCQjIyOEIpEhCCAgHyAfICAghR+KHgEdhB6CH4gehB+TD4MOhQ8GDg8PDg8PjQ6EDx8QOrWvqqKrq6upqainpqSkpaKWmKaoqaKorK2trKurhKw1rayrqqqrq6qrq6uqq6qqqampq6upqKSYmaesrKalqaytqJ+cm52dn6CgoaKhoqOmpqeoqquGrCurqqytrK2rq6uqqqmpqaioqaipqaeoqaioqaiop6ampqWlpaSkpaWlp6emhaeCpoSnAqalhKSEpRWkpKalpKKfoJ+enZuamZeWlJOQkJIWYmNmaWxtbnBydHZ3eHl4eXl6enp8fYR+E39/goWIi5GUlZaTkY6Lh4N/fXyEe4J6hXkeenl5eHZycG1ramdlY2FfX2BhYmJkZWZmZ2hoaWlqhWkJamtrbGxsbW1thmwJa2tramtqa2pqhWsBaoZpEGpqamtrbGxsbm5tbWxtbGyEaydsbGtqamppaWhoaGdnZmZlZGNiYmBfXlxdcXRwbm10eHh3dXZ1dXSEcwpycnJxcnFycXFxhHIhcXJycnFycXBqZW50dXZ2d3h5ent7fHx7eXVwbXKBhJowhjgGOTk4ODg3hTgCNziFNwQ2NjY1hDYHNTU2NjU1NIs1ATaGNYk2BTc2Njc2hjcBOIU5hDozOzo7Ojs7Ozw8PD09PT4+Pz9AQUJDIiIjIyQlJiYnKSkqLC0uMDEzNTc6OkGOiH91fH2AiIEBgIqBhoItg4SEhIOBgICIi5FTJSUiIB47OTYzMS8uLCspKCgnJyYlJSQkIyMiIiIhISBBhEABP4U+hz2DPIc7hDoBO4w6Bzk6OTk5ODiLOYQ6Ajs6iTuEPIM7hTyDPY08hD0BPIc9Aj4/hECDQYVCC0NDQ0IhISEiIiIhhiIEIyMiIogjASKGI4ckhyUHJiUmJiUmJYkmhSWFJIIjhCKCIYQghR8WPz4zUISDeHh/g4aKj5GSkpSUlJKSkIWNGI6PkZKUl5iXl5iWlZWSjo2MjJOSkJBnWYQ3ATiENwo2NjU1NTY1NTY1iDaFNYI2hTUGNjY2Nzc2iDcJODg4OTk5Ojs7hDwLPTw8PT4+Pz8/Hx+EIIQhhSIJIyMjJCQkJSUlhCaCJ4QoASmEKgQrLCsrhCwfLS0sLEd1cW5obm5ubW1tbGxrampnX2Fra2xmamtsbIZrhGyCa4Vsgm2Ebixtbm5ubW1rY2Vub29qam5vbmpjYmJjY2NkZWZmZ2doaWprbGxtbm5ub29vboVvFm5vb2xsa2xra2tqaWpramlpaGlpaGiFZwtmZmVmZ2doaGlpaYZqCWtqamtqamlqaYZqA2lqaoVpDGdmZWVkYmFfYGBfYAhLS0xNTk9QUYRTBFJRUlSEVYRXCVZVVlVUVFRVVYRUBFVST0+EUBlRUU9OTk1MTEtKSUdHRkVEQ0JCQD8+PDs5hDoBO4Q8Dz09Pj0+Pz8/QEBAQUFCQoRDBERDRESERYNEhEOGQoJDhEIBQYRCAkNChUMGRERFRURDjUQfQ0JCQEBAPz49PDs5NzY0N1BTS0A/YHF2lot6cWxqaYRohGYJZWZmZ2lrampqhGkqZ2VfVEU8YHd5j6mWjYuKiYeFgnxwY1hUZY+a5I28vr6/v7/AwcHCwcLDhsQNxcXExcXExMPDwsLBwoTBDMDBwL+/v76/wL+/v4TADcHBv8DAwsLBwMHDxMOEwkvDxMXGx8bFxsbIx8jIx8jJysvMzM3Oz9HU1tTX293f4eLn6u7z9/qAgoaKjpGWm6GnrrS6wcnS2+Ps9f7uhqaTgXF4eX1+f3+AgH+EgAOBgICHgVGCg4SEhYWHiIqLjZKYnJ+0wJCXkYmE/vXt4dfPyMG6s62qp6ShnZmXlZKPjYqJhoSB//z7+PXz8Ovo5uXk4+Hf3+Dg3tzb2tnY2NbU1dXU1NSI04XSINHR0M/Pzs3Ozc/P0NHR0tTT09TU1tjY19fX2Nra2tnZhdoQ29va3Nzc3dzd3t7e39/e3YXehN+A4OHh4+Tk5OPl5+bo6u3u7+7y8vP09fb4+vz9/v+BgYKCgoOEhoaHiImKioqLjIqMjY+Rj4+OkJCRkZKTlJOVlZWWl5iam5ydnZ6dnp+hoKCfoqGkoqKjo6SjoqKhoqSjoaCenJybmJWVlJGPjYyLioiHhoSEgv/7sIGMhGlhZms1b3R8f4GBgoGAfn58e3p6eXl4d3Z2d3l5d3Z2dXR0c3N3e4GRiHx+m+yus7S0tbW2t7a2tbWGtoC1tre4ubi5ubm4t7e4ubq7vL2+vr/BwsTGx8jJzM7Pz9HT09bY2dnb29zg4uXn5uru8PP39/j6+v6CgoSFhoiJioyNj5GTlZeZm52goaSmqKqsrrCytbe5vL2/wsPExsjLzM3NztDQ0dHR0MelcmVeVVlZWVhYV1ZWVFRUUk1PUgRSUEVKhU2CToRPClBPTk5PT1BQUVGGUBxRUVJRUVBMTVJSUUpITE5OSkNDQkRERUVERUVFhEYKSElKSktLSkpLS4ZKhUkKSkpJSUlIR0dGRYRGBURDREREhUMLQkNDRERFRkVGRkaER4NIiEkHSktLS0pLS4VMB01MS0xMTEuESQFK/3/tf+2CloMBgat/AYGFhJuD/4KJgtmDBIKCgoCtfwKAgcuCs4MBgf9/sn8CAgQAgLGztbS2uru9wsuCst2Fm6y/0eHygIeMkZecn6SorK2ur7CxsbKzsrKzs7KxsKyppZ+akoqA69a/pIjZn+C8tbCwrq2trauppKCalI2KiYuQlpmbnp+jpKWmp6anp6iqq6usq6yrrKuqqqqrqqqqqaqqqqmqqqqpqamqqqmoqaqpE6inpqempqenpaSlpKSlpaamp6WEpCmjo6Ggnp2dnp2bm5qZmJeVlZOTkI6arqysrq6ztrGnra2trq+ur6+uroavJ66vsLCvra6trq6tq6uqpKKsrqujpqutrq+wsK+vrKqmpKSytMRCH4geAR2FHgQdHh0ehh2JHAEbjBwJGxwbGxwcHB0dhByFHQEchx0EHB0dHZMegx2FHgcdHh4eHR0dhw6DD4UOCg8QERITHq+tq6CEqQSqqquqhKwMrayrrK2srKytrK2tha4Xr62tqqiipbC3xBUKCQkICBAPDg4NDQ2HDoQPhA6EDwMeHx6EHwEehx8BIIUfiiAGHx8gHx8gih+IHosfgx6PHwEghx+FIAEhhSAEISAgIIYfjyABIY0QChEQERAQERAREBGGEIwRARKLEYQQBg8QDxAPD4UOhQ0KDAwMCwsMCwwLDIgLAQqECxAKChYstbayrbO6wMXGx8fHhcYDxcTFhMYZxcfKzM3NysrMzczPzcW+vLm9xsrjSScmJoQlCCQkJCMjIiIihCEIICEhICAhICGJIIcfih4BH5Yelg8GDg4PDw8OiA+IDogPGB61sKukq6yrqqqpqKWjoaGcj5ujpqWhqoSthKwLra2tq6urrKuqrKyFqxOqrKuqqampopaeqaupo6itrqyjhJ4Sn6CgoKGipKWlpqeoqKmrrKyshKsSrKusraysrKuqqqqpqainp6ephqg9paanp6ampaWkoqKio6KioKGjpaampaWlpqenp6alpKSlpaOjo6WkoqGhn56amJeVko+PkJSYm6GkqKqusBx1dXV2d3l5eXuJUV1qOj9DR0tQUyssLi8wMDExhzKDM4Y0PjMyMjAvLiwrKU1IQjw3X1GFeXd2dXV0c3Nyb2tpZmNhXl5fYWNkZmZmZ2doaGlpaWpqbGxtbWxtbW1sbGtshGsEamtrbIZrCGpqaWppaWpphGoLa2xrbW1sbGxra2uFagJraoRpJGhnZ2ZmZmVkZGNiYmFgX11bWWFzcG5sbnZ3d3R1dHNzcnFycodxGnJxcnNycnFycXJzcnFwbWhnc3Z1dnZ3eHp6hHsJendybm59gI1XijiEOYU4hjeENgE3hzYDNTU2jjWSNog3BDg3ODiEOYY6Ljs8Ozw8PD0+Pj4/QEFBQkNEIiMjJCUmJicpKSssLS8xMjQ3OTpHlouFdXx9f4CHgYaAhYGFgoSDKoKBf3yBiY2hNCUjIB8eOTc0MjAvLSsqKSgnJiYlJSQjIyMiIiIhISBBQIQ/AT6FPYc8hTsDOjo7ijoNOTo6OTk5ODg5OTg5OYQ4izmEOoQ7ATqKOwE8hDuHPIc9gj6GPYU+Dj8/P0A/QEFBQkJCQ0MhiyIGIyMiIyMjhCSKJYIkiCWEJocnhCgCKSiLKRMoKCgnKCgnJyYmJSUkJCMjIyIihCGEIBcfPTuFgnt1e3+DhomLjI2MjY2MioqJiIWJFoqMjpGRjo6Pjo2PjomFhYmMiourUTaKNwI2N4Q2DDU2NTY2NTU2NTU1NI01BDY1NTWENoM3hTgUOTg5OTs7Ozw8PD08PT4+Pj8/Hx+GIIQhgyKFIwgkJCUlJSYmJoQnhCgEKSkpKoUrhyxXLXZyb2hub25tbW1sa2poZ2NbY2hpaGVqbGxsa2xra2xsa21sbGtsbWtrbGxtbW5vb29wb29ubmlhaHFxbmhrb3FuZ2NjZGVlZmZmZWZmZ2hpa2xsbW1thW4abW5ubm9ub29vbm1tbGxsamtqamprampqaWmFaANnZ2aFZQZmZWVmZ2iEaQpoaWprampqaWpqhWkWaGdnZ2ZlZGJhYF9fX2FjZGhqbW5yc4RTEFJTVFRVVisrKhQUFBMSEhKFCAMHBwiRB4gIhRIRJSRIRENCQkNCQkJAQD8+PDuEOgc7PD09PT4/iEAHQkJDQ0RERIZFB0REQ0JDQkKEQw1CQ0NDQkJCQUJCQkFBhEIDQ0NChEMLQkJDRERFRENEREOEQiFBQD8+PT08PDw5ODY1MzI7U0xEP0lrcH+agHdvbGlnZ2iEZkFnZ2dpamxsa2tqa2pqamhjXE49P3F4faWdj4qJh4WEgX1yZVlQV4mUteu8v7+/vr6/wMHBw8TFxcXExcXGxcbGxoTFCMTExMPCw8LChMMBwobABr++vr69vofAUb/AwcLBv7+/wMHCwsLDwcLDxMXGxcbGxsfIycnKysvLy8zN0NHS1NbY2tze4OTo7PH1+P6ChYmOkZaboaiutLzEzNbd5u74/uq2l4pvdnh8foaAAoGAh4FSgIGBgoKDhISFhoeIiYqOkpqcp+O3l5KLhYH47+Ta0crBurWuqqikoJyZl5SQjoyJiIaEgv76+Pbz8e3p5+bk4+Lg3t7d3dvZ2NfX1dTU1NPT0oTRCdDR0dDQ0M/PzoTNhsw/zc3Pzs7R0NDR0tPV1NXV1dbV19jY19jY19bX2NjZ2tvb2drb2tvb3N3f3+Hf4OHj4uPn5+bl5uXl5ufo6evthO+A8fLz9Pb2+fv+gIGCgoSFhYaIh4iIiYmKjI+QkZGSlJSVl5eYmJmamZuam5ufnp6foaKjoaKjp6anp6iqrK2sra2vsLCwsbGztLW3tre2tbe0tbWysbOuq6uqqKOioJ6cmpeWlZSRjYyMi4jqjJCFcl5iZ2twdXp8fX59fHx8e3kleHd3dnV0c3JydHRyc3Rzc3R1dXd8iYx7ecvLq7W0t7a4ubm6uoS5hrqEu4O6hbmAurq6vLy9v7/Bw8TFxsjJy87O0NLT1NXX2drb3N3f4uLl5+vv8fLz9Pb3+/2AgYKDhIeIiYqLjY+Rk5WXmJqcn6KjpaiqrK6vs7W2uLq7vsDCxMbHyszOzs3P0dHT0tHNgHVlXlVZWVlYWFhWVVNSUU5LTk9PS0ZNTk5OTU1NTk4DT09Qhk8BUIRRiFIlU1BMTlFRTklMT09NRkJDRERERUVFRkVFRkdHSEhJSUhISktLS4RKFElJSUpJSUlISEhJSUhIR0dGRUREhEUHREVERENDQ4hCD0NFRkdHR0ZGRkdHR0hISYVKBEtLSkqFSwJMS4RKCUtKSkxPUFBRUop/g4CHgaGChYGCgP9/sH8Bge+ClIMBgqt/AYOFhJqD/4KCguKDAoKBrX8BgcyCtIMBgv9/sn8CAgQAb6fumrnU8YWRnKeopqKempaSjYuMkZacoKKkpqanqKmpq6qsq6ysrquvsLO1t7m6vLu4s6+rqqutsLW7wMC3qZmG5r2Ow+i4tLGwrq2tqqOemJKPkZSanaChoqSlpqipqqusrKyrqqurqqurqqurrIWrGKqsrKqrqqqrqainqKinp6WlpaSmpqelpYSkM6OkpaSjo6GhoJ+enZycnZuampqZl5aVlJSTkY6Np6uoqaeysrOoqqytrq+tra2ura6troSvFbCvr66tra2sq6usqqihpq2tqKOnq4StDK6tq6qno6Ctsrw+H4Yehx+GHoUdkRwBG4ocAx0cHYUcCh0cHRwdHRwdHR2FHAMdHRyFHYseAR2RHgEPjg4aDxAREhFfsKylqaqpqKmqqqurq6ysrKusrKyJrRaurq+vrauopaGptLpACgoJCAgQEA4OhQ2GDgEPhw4FDw8PHx+IHgQfHx4eih8KIB8fHyAgHx8fHoQfhB4IHx4fHx4eHR2IHggfHx4fHx4eHpofiCABIYwgASGEIAchICAhICAgkhCPEQEQhBEEEBEREowRBBARERGFEIQPgw6FDQYMCwsLCguECpIJLxWvtbKrsrnAwsPEw8TDw8LCwL28v8DBwcHAwcTGxsXExMXHxsbHwbWxs7m/zUMnhiWDJIUjgiKGIYUgjB+EHgkdHR4eHh0eHh2QHgEdiB6CD4UOBg8PDw4PDo0PAQ6JDwUODw4OD4UOJA8PDw4ODyC2r6ylq6yrqqmop6WjoqGbkaClpp6hqaytrq2srYSsDK2trKusq6yqrK2uroatNqyro5agrK2qpKqtramfnZ+foKChoqKgo6Okp6ipqaioqKqqrKupqaqrrayrq6qqqqipqKmoqYmoh6c4pqajoqOioqGhoaCfnqGkp6alpaanp6ako6SjpKKjoaCfnJeTkY+Oj5KWmp6mrK+ysrW3ubq7vt0bXG0+REtRLC4wMS8uLCsqKSgnKCosLi8wMC8whS+FMAMxMDGEMjYzNDU1NTMyMC8uLy8xMjM1NzYzMCxPRDhYgnVzcnFwcXBua2dkYV5fYGJkZWZnaGlqamprbGyEbYVsBGtsa2uFbAZrbGxrbGyEagFphmoJa2trbGxrampqhGktamlpaGhoZ2hoZ2ZlZWVkY2NiYWFgX15cW1hXanFta2hydXZ0dHNycXJxcXJyhXEDcnJzhnKEcwxycGtmbXZ3dnV1eHmEegp5eXZxbWt5f4ZNhzgFOTg4OTmGOAE5hTgDNzg4hDcENjc3N4Q2BTU1NjY2hDWCNos1hTaCNYk2Ajc2hDeEOIQ5hjotOzs7PDw9PT0+Pz9AQUJDQyIjIyQlJicoKSorLC4vMTM1Nzk1XY2IeHp8f4CAhoEJgIGAgYGBgICAhYE2goKDg4OCgYB9fIWKkFckIyIfHjo4NTIwLy0sKikpJycmJSQkIyMiIiIhISAgQD8/Pz4+PT09hzyEO4c6jDkCODmPOAM5OTiHOY46hDsFPDs8OzuIPIM9iz6FP4RAB0FBQkJDIiGFIoUjhCSFJQImJYQmhScBJognAygoJ4YogymFKgQrKywrhSwILSwtLS0sLCyEKwEqhCkKKCcnJyYmJSQkI4YiDyEgHyyMg35zd3x/goSFhoWHAYaIhRWGhoaHiYiIiImIiIqHgYGFhIWOQjSGN4c4CDc3NzY2Njc3hDaRNYc2hDeEOIU5hDsGPDs8PT09hT6EH4UghSGCIoQjFiQkJCUlJSYmJicnJygpKSkqKSoqKiuHLCYtLSw3eHFuaG5vbm5ubGxqaWhnY1tlaGhjZGlrbGxsa2xsbG1tboltA25vb4VwOm9vamJocHFuam5wcW5mZGRkZmZmZ2dnaGhpampra2tsbG5ub25tbm5ubW5ub29vbm1ubW1sbGxramuJaoVpAmdmhmUFZGRkZWaEZyVoaGhpaWlqaWppaWhmZWNhYWBfXl5fYmVpbG9yc3N1dXZ3eHqRCywrFBQTEwkICAcHiAaFBwMGBwaKBw4GBwcGBwYGBgcGBgYHB4cGAwUGBoQHFRARESNFQkJBQkFCQUFAPz08Ozs9P4ZABkFBQkNDRIRFBkZGRkVFRYtEF0NDQkJDQ0NCQkFBQkFCQ0NCQkNCQ0NDhEKGQ2BCQUBBQUA/Pj09PDs6OTg2NTQzMTBITUQ8OV9qcZmIeXBta2lnZmdmZ2dnaWprbW1ta2tqa2traWdgVkU5V3d5jqKQioiGhYSCfXVoWU9PfJOmxbu9vr6/v8DBwcLCxMSExRDGxMXGx8fGxcfGxsbFxcTEhcUJxMLDw8HBwcC/hcAQv7++v7++v7+/wL6+vr+/voS9R7/AwcLCw8HBwMDAwcPDxMXFxcfHyMjLzM3Oz9DS1NbZ3d7j5+vv8vb7gYWIjI+UmqGorbO7xM3X4Orz+9STnY92dHd8foCAhIEBgIaBUICAgYGCgoOEhIWGh4iIiouQlZufsdCPlI6Hgvzx6d3TzMO8tq+rp6Ogm5eVk5GNioiGhYOB+/j38/Du6+jm5OPi397c3Nva2dfV1dTT09LRhdAGz87Pzs3NhMsFzMrLyciEyQLKy4TMgMvNzM3O0NHR0dLT0tPU1NXW1dbW1tfX2NnY2dnZ2Nna2dra3Nzc3d/f4ePl5OXm6Ojq7O3t7e7w7/Dx8/P29vj4+v7/gYKDhIWGiIiIiYyNjo+PkJKUlZWXmZubnJ6goKGjoqOkpqmqqqmrq6+xsbKysbK1tre5ubq7vLy9vL2/YcDCwcPExMXHysnKycrKycjGx8fHw8LAv7+8trKzsa2ppaSjn5uXlpWUkoqKqId8XmBkaG1ydXd6e3t6enp4dnV0c3NzcnJxcXBwcG9wcXFxcnN2fot+doyPn7e5urm6vLyGvYS+gL+/wcLBwcDAvby7vLu7u7y+vr+/v8HAwcHDxMbHyMjJyszO0dLT1NbZ29zf4ePk5Ofs7u/x8/P1+vv8/YCBgoOFhoaJiouMjpCRk5WXmZueoKOkp6qrrrCytba3ubu8vsHDxcfJy8zP0NDQ09XW1dK0eWRfVFhZWVhYV1ZUUlFRC05KT09NREZLTE1OhE2DToRPglCEUSxSUlNTU1RTU1RUUU1QU1JNSU1OTk1FREVGR0ZGRkVGR0hISElKSUlKSklISYRKA0tLSodJhUgSR0dGRUVGRUVEREVFREVERUVEhEMIQkJCQUFBQ0SERoVHBUhJSUpKhEsXSklISUlHR0hJSk1QUlNTUlJTU1RUVFeCgISBu4IEgYGBgP9/pn8BgfGClIMBgKl/AYGFhJqD/YLngwGCrH8Bgc6CtIMBgv9/sn8CAgQAZaijnZaPiouVnKCfnp+dnp6blpCG9d3IsqKXj4f/9Ori2tTNz8zMz9TZ4un5hI2Yp73X8YeTm6OqrrGzt7iyqqWpsLe9xMOzm4LLj6LHtrSzsrGsp6GYkZGTmJyfoqWoqquqqqurhKxyq6yrq6usq6usq6uqq6qqq6urqqqpqKiopqSlpqalpaWmpaWjpKOioaCioqCfoJ+fn52dnJybmpqamZmXlpaUlJORjo2Wq6ilo6yvsamorK2trq2traytra6ur7CvsK+wsK+vraysra2rq6ikrK6ro6WqhKsMra6tqqWmo6qutz0ghR8BHocfAyAfH4Yeix0GHBwcHR0dkhyGHQEchR2FHIsdmh6EHYYOAw8OD4QOEQ8QERESG7Csp6Wqqqmqq6uqhKyFqyCsra2trq6tra2vr6+wrqunoqGxt78UCgkIBw8PDg4NDIQNjw4BHYUegh2JHgMfHx6NH4oeCB8eHx8eHh4dhR4BH4wehx+CHoQfAR6GHwYgHyAgHyGSIAYhIB8gICCHEAEPjhCPEQkQEREREBAREBCFEYUQBRERERIShBENEhERERIREREQEBEQD4QOBQ0NDAsLhQoBCYgIhgcMCAgIE1Oysayutbm/hMEJwMDAvbq5ury9hLwTvb7BwsHDwMDCwsDAvbKprra7dYUmgyWEJAUjIyIjIoUhhiCGH4MehB+EHoIdhh4IHR4eHh0dHh6EHYUeEx0eHh4dHh4fHh4ODg8ODg8PDg6RDwEOiQ+FDgMPDg+GDhsPDiC5sKylrK2rq6qqqqilpKKakaGkpZ6jqKuErQKsrYauhK0BroSwXK+wsK+urq2llqOqrKenr7CxqJ6goKKioqGhoqOko6Smp6ipqqmpqqmqqquqqqmqq6usq6uqqqmoqKiqqqqop6mqqainp6amp6anpqenp6WkpKOioKCenp+goaKjhKQko6SlpKOin52ZlpWSkpCQk5igpq2ytbe2trq+z6SCrdf/kJ+qTi4sKignJyotLi0tLSwtLCwrKickQTs2MzAvLSxWVFJQUE9OTU1NTk9QUlVWLS4xNTlASCcqLC4wMTEyMzQzMC4vMTQ2ODk1MCpGN012c4RyEW9sZ2JfXl9iZGZoaWprbGxrhW0IbGxtbWxsbGuMbAFqhGsFamtra2qEawtqamppaWhoaGdoaIVnImZmZWRkY2JiYWJhYF9eXVxaWFZccG1qZmx1dnVzc3JxcXCEcQhyc3JycnNzc4RyDnNzdHR0c25pZnR3d3Z2hHgMeXp6eHZybGl1foREhziEOQE6ijmJOIU3BDY3NzeJNog1ATaJNYI2hTWDNok3gziFOQU6OTo6OoQ7KTw8PT09Pz9AQUJDREUjIyQlJicoKSosLS4wMTQ2OTozkYl/d3x+gICBhIADgYCAhYE6gICAgYGCgYKDgoKDgoB+fH6Hi5wzJSMgHjo4NTIxLy0sKykoJycmJSQkIyMiISEhICBAQD8/Pz49PYQ8hjuHOoU5BDg4OTmIOAQ3Nzc4ijeJOIU5Czo6Ojk6Ojo7Ojo6iDuHPAI9PIQ9gz6EP4ZABEFBQkKEIYMihiMGJCQkJSUlhSaFJ4MohSkBKoUpASqFKRUqKSorKyssKyssLC0tLS4uLi8wMDCFMRoyMTMyMjIxMTAwMC8vLy4tLSwrKyopKCgnJoQlECQjIiIhOl6FgnZ2e3+Bg4SEhQeEg4OCgoODhYIUgYKDg4WDg4SEg4SDfoCCgIJjMTeGOAM5ODmKOIQ3hjYDNTU2hTWENoY1gzaENxs4ODg5OTk6Ojs8PDw7Ozs8PT0+Pj0+Pj4fHx+GIIQhhSIJIyMkJCQlJSYmhCcIKCgoKSkpKiqFKw0sLSwtLCwtLUF8cW9nhm4ObWtpaGdiXGdoaGFlaGuGbBBtbm5ub25tbm5tbm9wb29vhnAXbGRscnJtbHBxcmxkZWVmZmZnaGhoaWqEawlsbWxtbWxubm+LboJthWwBa4RqBGlpaWiIaQdoZ2ZmZWRlhGMBZIRlDWZnZ2hoaGloZmViYWCEXRVgZWpscHJ0dXV1dniGWzhCS1QuMDABCIYHMAgHBwcICAcHBwgICAcQEBEREhMTEyYnKCgnKCkoKCcnJyYmJSYSEhEREBAQCAcHB4QGAQWEBgEFhAYxBwcICBERIkRDQ0RERENCQD8+Pj4/QEBCQkNERERDRUVGRkZFRURERUVGRkVFRUZFRYREBkNDREREQ4ZCAUOEQjtDQ0JCQUFAQEFBQUJBQUBAQD8/Pj49PDs6OTg2NjUzMjAtNk5GPTdPaGyOjHtzb2xqaWhnaGlpamtra4RsK2tsbW5tbGheUkA+cXp/oJmOioeFhIJ9dGlaTkxtjJyft77AwMHBwMLCw8WFxgbHx8jHyMiEyh3LycrJycnIyMfHxsfGxcTEw8PDwsHDwcDCwr+/wIS/C769vby9vb68vLu7ibwIvb2+v7+/wMCEwTXCwsPExcbHyMrMzM3Q09bZ2+Dk6Ozx9Pn/g4eLj5OZnqSrtLvFz9jk7vj+k6yVgHF2en6AgIWBBIKBgYGFgk2DhIWFhYaHiIiJiYqMk5qcpNuulY+Jg/716+DVzcW/uK+qp6Ofm5eVko+MiYeFgoD9+PXz8e/q5uPj4uDf3dzb2dnX1tXU09LS0dDOz4TOAcyEywTKycjHicaAx8jJycnKycnJy8vMzc7Oz8/Q0NLS09TV1NXW19jY2NfX19nZ2djZ2dna29zd39/h4uLj4+Xm5+vq7vDw8vP29vj6+/6AgIKDhISFhoeIioyMjZCSk5OVl5iZm5ydn6KkpaSmqaqsrrCztLS2ubm6ubu+v7/Dv8LDxcjIysnLzc5HztDP0NPU1dbY2t3c3uDi4ePj6Obm5uXn5uXm4+Li3djX1tHLycXCv7e0sq+qpKCfnJjnuYh/ZVthZWlvdHV2dXZ2dXV1c3OEcj1xcnFxcXBwb29wcW9wcnaEhXZ4oIm6vb/AwcHCwsHCwsHDxMTExcbHyMfGxcXFxMPBwcLCwcLBwMDCwsPEhMNcxMXGyMjKy83MztDS0tPX2dzg4+Xm5+jr7e7v8PP19/n5/P+AgYKDhIWHiImKjI6PkZKTlpibnJ+ipKaprK6wsrS2uLq7vb7Bw8XHycrN0NHT1dbX2NjV7oZlXlOGVx9WVVNSUE1KTk5MQ0ZJS0xMTE1NTU5OT09QUE9QUVFRiFMdVFVWU0xRU1NNS05PUExFRkdHR0hIR0dHSEhJSUmEShxLS0tKSUpKS0pKS0tLSUlISUlISEdISEdGR0ZGhEWERIZFAUSFQw5CQkNDREVFRkVFRUZISIRJhEgZR0ZGRUdKTE5QUFFSUlJUVFYsFRQUEggICJSCiIGQgIeBloIDgYGA/3+efwGB9IKSgwGCqX8Bg4SEmYP3gu6DAoKAqn8BgNCCtIMBgv9/qn8BgISBg4ICAgQALKOioKGel4Tdt5yI7Mupj/HVxL+9u7u9u7q6ube3t7i2tbW0tLa0tra3tbW3hLUxtLS1tbO1usfpkrrmk8D2lai0u766rJ+irbfAxrqd9qu7yrq4tbOxq6Kak5SXnKGlqISpEKqqq6qsraysq6ysrausq6uEqhOsq6qrq6mop6elpqenpqSlpKOjhKKCoIWfIZ6enJucmpqZmZiXl5eWlpWUlJORjYyNpquopqatsKqpqoasCq2ura2vsLGwr66Erx6traytrayqo6itrKakqqyrq6utra2sp6Wfp66yOyGEHwEehh8BHoYfhB6LHQYcHB0dHRyHHYkcix2HHAYdHR0cHByJHZgeBR0dHh4dhA4FDw8ODg6FDw0QEREPrq6qpKipqqqqiKsGqqusra2tha4dr6+wsK+uqqWfprO4PAsJCQgHDw4NDQ0MDQ0NDg2GDgENhQ6JHYIehR0KHh4fHh4eHx4eHocfhh6CHYgeAR2OHgEdhh6PHwggICAfHyAfH4wggiGHIIgQBA8QDw+HEAgREREQEBAREIwRhRABD4QQAhEQiRECEBGEEoUTCxQUFBUUFBMTExIThQiEB4QGBQUKCQkIhAeDBYQGRQcGBwcHJ66yrrC1uby/wcHBwMC+vbu6urm4urq6u7u8vb/BwsG/v8C8ubewqrC0ykcpJycnJiUlJCUlJCQkIyMjIiIhIoQhAyAhIIUfCB4eHx4eHh8fhh4DHR4djB4MHR0dHh4eHR0eHh4fhh4CHRyEDgQPDw8Ojg8BEIcPCRAPDw4ODw4OD40OUhC+r6ykq62sraurq6qop6Sal6WmpZ2mq62urq6vra6wr6+vsK+ur6+usLGxsbKysbKwrqynmqesraWnrLCxpp+hoqOjo6SjpaWlpqamqKusqquGqlqrqqmpqqurq6qqqaqpqqmpqaqpp6aoqKenpqalpaampaSlpqamp6empKSioaCgoaKioaGioqKgn52amJaTj4+RlZqiqK2xsrS3vMGB5andhJqqraeelY6Qm6IQLi0tLCsoIjgyLitRS0VAe4R6BXl6eXp5hHgRd3d2dXZ3eHd3dnZ3d3Z2dXWEdjB1dXN1eHmBRk1VMTxLLTE0Njc3Mi0tMTQ3OTcwUD5Td3Rzc3JxbmhjX19hZGdpamuFbIZtCWxtbG1tbWxsbYVsAmtshGsJbGxsbWxramtrhGkBaIRnA2ZnaIRmLmVkZGRjY2JhYWBgYF5eXVxaWFZUamxqaGhzdXJxcnJycXFxcHFycnJzc3Rzc3SEcx50dHV2dHJtZ3B5eXh3eHl4eHl6enl2cm1ncHuBPDeGOQI6OYQ6jjkEODk5OYQ4hzcENjY2N4Q2BjU1NTY1Nok1ETY2NTU1NjY2NTU1NjU2Njc2hDeHOIc5Kjo6Ozo7Ozw9PT0/QEBCQkNERSMkJSYnKCkqKy0uMDI0Njg7LZyLhXZ8f4WBQoCAgIGBgYCBgYGAgIGBgYKCg4ODgoKBf317gYeOSiMjIR8dOTYzMS8uLCsqKSgnJiUkJCMiIiEhICBAQD8/Pz49PYQ8hDuEOoU5iziMN4Q2hDcBNoU3hjiEOQQ6Ojk5hjoCOzqHO4Q8hj0HPj4+Pz4/P4ZAAUKEIYYiASOEJIMlhCYBJ4QogimGKoYrgiyFLQcuLi4tLi0vhy4ULy8vMDAwMTIzMzM1NDU2Nzc4ODmIHIQbHxoZMjIxLy8uLCsqKiknJyYlJCQkIiFPiYN6dHuAgoOEhQiEg4OCgYGBgISBGICAgIGCg4OBgoOAf4B+fn19jk04Ojo5OYU6Czk5OTo5Ojo5Ojo5hjiFNwY2NjY3NzeQNgs3Nzg4ODk5Ojs6O4c8hT2EPoQfhCCGIYQiFSMjIyQlJSUmJicnJygoKCkpKSopKoQrBCwtLS6FLQcljnFuZmxthG4cbW1ra2hhX2loZmBmaWtsbGtsa2ttbW5ub25ub4RwAnFwhnEwcGxlbnNzbW5ycnJrZmZnaGhnaGhpaWprbGxtbm5tbm5vbm5ubW5ubm9vb25ubW1shW0HbGtsamlqaYRoAWeLaAFnhGYEZWRlZoVlJWZmZmVjYWBeXVxcX2RobXBzdHR1dnlNakFOKzAyMC0rKCcqLi6CB4QIDAcQERITJygpKVRVV4VWhVWFVAJTUYRSHVFRT09PTk1MS0pJR0ZEQ0REQyEhHw4ODgYHBwYHhgYHBwcIBxESJIVGCUVEQ0FAQEBBQoVDhEQGRUVGRUVFhkYLRUVERURFRERERUSHQwhCQkNCQUJCQYVAhEEkQEA/Pz49PTw8Ozs6OTg4NjUzMjEuLCtHST01QWZrg5d/c29th2o5a2pramprbG1ub3Bwbm1nXEg7WXh8lqCWjIiGg4F9dWlaTUdmiJSBtcDBwsLCxMXFxsfIycrJysrLhMoIzM3Nzc7NzM2EzBPLysrJyMjHx8bFxsXEw8LCwsG/hL4ev768u7y9vLu5urq6ubq5uLi5u7y7urq8vr2+vb28hL0Dvr2/hMAwwcLDxcfIyMrN0NPW2d7i6e7x+f+Dh4qOk5ifpKu0vsfR3Ofy/aHKmYpxdnp/gICAhYGDgoaDVYSEhYWHiIiJiouMj5Wdn6+si5KLhYD37eLZz8jAubKsqKKfmpaUkI2Kh4WDgf749PHv7uzo4+Hg393c2trZ2NfV1NPR0dDOzs3MzMzLy8rJycnIx8aFxR/Dw8TEw8TFxcbGx8bGx8fIycvKycrMzc7P0NDS0tLThNSA1dTU1tfX19jZ2tnZ2trb3N7f4ODi4uTn6Orq7e7w8/T1+f6AgYKDg4SFhoeJi46PkJGTlZiYmpueoKGio6aoq62vsLKztLe4ury9v8HEx8vN0NDS1NfZ2dvd3N3f4OHh4uTk5ubl5+fn6ers7O/y9ff69/n7gICBgoOEg4KCgoMhg4GA/vv48+zn5ODY0MvIw7y2saypo5jxl4NxW2BkZ2txh3MJcnJxcHFxcXJyhHEScnFvb3FxcXN6hXt0l7i4w8XHhMgIycnJysvNzc+EzgPNy8qGywPKycqEyWfIycjIyMfHyMnIx8fIyMnKzM3Q0tLU1dfZ3eHk5+fn6Ont7+/w8PP19vj6/v+AgYKDhIaHiImKjI2OkJKTlZeam56hpKapq62wsbS1t7m6u73Aw8TGyMvO0NHT1dbX2NnYmKxmXVJVhlY9VVRTUU1LT09KQUhKTExMS01NTk5PUFBRUVBRUVFSU1NUVFRVVlVUU1FNU1VTTUxPUFBJRUZHSEhISUhISIRJBkpKS0pKSoRLhkoRSUlJSklJSklJSEhIR0ZGRkeERYhEOEVFREVFRENERERFREVGRUVFRkdHRkdHRkZFRENCQ0ZISk1OUFFQUlNVKyoVEwkICAcHBgYHBwcIh4KEgYSAqn8GgICAgYGBj4IDgYGA/3+YfwGB9oKSg6h/AYGFhJeD84LSg46ElIMBgap/AYHRgrWD/3+jfwSAgIGBi4ICAgQACY/rv5PgxL+9u4S6Xrm5tq6nn5uOiYX87+fcybGhqLnQ452usrO4vL+7qKu4t6+qpqjYybKOt7Kwsa+vsLGys7vUkMuV3Jasuru1n5uptsLKvJbU69u+uba1s6qgl5WZnaGkpqepqqqsrK2MrAGrhKwPqqqqqaqqqaenqKimpKOjhaFZoKCdnp+fn56cm5qcmpqZmJiXl5aXlpaUk5KPjYuJnqupqaerrq2lrq6traytrK2urq2usbGwsK+ura6vr66sra2rp6Straynq62trq6ura2uqaKdo6+ydCCFH4wggh+JHhMdHR0cHRwdHRwdHB0dHRwcHR0diByHHY8cjB2FHgYdHR4eHR2FHgEdhx4BHYQehg6IDwgQERERZLGspYWphqogq6qsrKutra6trq+ur7CwsLGwraijna62uhMKCQgHDg6EDAELhg2EDgoNDQ4ODhwdHR0chx0BHoYdiB6DH5IehB0DHh0ehR0HHh0eHR4eHokfASCFHwUgHyAgH4cgAR+EIAEfjSABIYcQAQ+KEIcRghCGEQgQEREQEBEREoURBRISERERiRIDExIThAmECoQLhAwHCwsLDAsKC4QKEAkJCQgICAcGBgUFBAQDAwKHBBsDBAUGBhFWuLWvtrm9vr/Bw8LAv7+9vLu6ubmGuhO9v7/Av729u7azr66xuXspKCgohScTJiUlJSQlJCMjJCMjIiEhISAgIIgfAx4fH48eAR2FHgEfih4HHR4eHR0dHoUdgg6LDwEOiQ8PEBAQDw8QDw8QDw8ODg4Pjg4fD2SwrKSpq6urrK2tq6qpp5qeq6yno6utr6+wsLGxr4SuhLAsr7CxsbKysbGwr6+uqJups7Onqa6wrqOgoaOjpKOkpaWnqKioqaqrq6qrrKyEqzCpqqqqqaqrq6mrqaioqqqpqKeoqKanp6ampKampaWlpqWlpaanpqamp6alo6KhoKCEnyedm5mUj42NjI+ZoamusbO1t7zTwZ/diqSyrqWbk5Ofo6Kgn5yK2a5kLFBHQHt8e3p6ent7enl4c21oY19YUk+Xj4l/dGVfYW99i1piZWZra25qYGJramRhYGFvQD0zUnBxc3R1dnd4eHl9RlIzRi0zNjc1LiwwNDc7OC9JYX52dXNycWxmYF5hY2VnaoRrBWxsbW1thG4Hb29vbm5vboVtAW6EbQpubWxtbGtqa2tqhGgFZ2dmZWaEZR9kZGRjY2JhYGBhYF9fX15eXFpYVlNhbGpoZXJzc3JxhnIBcYRyCXN0c3R0dHV0dYR2CHVybGp4enp5hXoMe3p5dnJtZ216fm43hzoHOzs7Ojs7O4o6BDk6OTqGOQc4ODg3Nzc4hzeINoo1BjY1NTY2NoU1BTY2Njc2hzcDODg3hDgFOTk5ODmFOiU8PD09Pj4/QUJDREUjJCUmJygpKissLzAzNDc5Ozx5kYl5fH6AjIEBgIWBgoKFgy+CgH18fIWJmC0jIh8dOTc0MS8uLCspKCcnJiQjIyMiIiEgIEBAPz4+Pj08PDs7O4Q6Ajk6hDmGOIs3jTYBN4k2FDc4Nzc4OTk5Ojo6OTk5Ojk5OTo6hjsGPDs7Ozw8hj2CPoc/CEBBQUJCISEhhCIHIyMjJCUlJYQmCicnKCgpKSoqKiuELIQtDi4uLy8vMDAxMjIyMzMzhjQHMzMzNDMzMocaDhsbGxwcHR0eHh8fICAghiGFICgfHx4eHRwbGhoZGDAvLSspKSgoJyYkIzlUh4R3fICCg4WGhoaFhISChIEYgoGBgYCAgIGCgYODgoODgH+AgXt+ZDQ7hDwBPYk8hDuDOos5Ajg5hzgINzc2NjY3NzaGNws4ODg5OTo6Ojs7O4U8hT2EPgQ/Hx8fhSCGIQwiIiIjIyMkJCUmJiaEJ4MohSkJKioqKysrLC0thC4lLSpYcW5mam1tbW5ubm1ubWtjZW5uaWVrbG1tbWxtbWxsbW1ub4RwL3FycXFycnJxcnFwbWVvc3Rub3N0cmpnaGlpa2pramlqamtsbW1ubW5vcG9wcHBvhG4Bb4RuEGxtbG1tbWxsa2pqamloaGiGZwNoZ2iFZwVoaGhnZ4RmK2VlZGRjYmBeW1lYWFtgZmxvcXJzdHaCXj9NLDIyMC0qJyouLSwsLCkjODFIEigpKlRXV1dWVldXVlVRTEdDQTs2My9VT0pFPDUyNDo6OCAiIiAiISIhIyIkJScoKSssDg4OHjg7P0FDQ0JCQUJBIB8ODgcHhAYBBYQGBAcIESSERwtIR0VDQUBBQkJCQ4lEgkWJRoRFAkRFhkQPQ0NDQkNDQ0JCQUFAQEA/hUAlPz4+PTw8PDo6Ojk4ODc2NDMxMC4rKTxHPDQ4ZGt3moF1cG1ra4VqNWlqampra21ucHFycG1rYVJCRXZ+iqOWj4uKh4N9d2pZS0RbiZPZs8LDxMXGyMjHycnMzc3OhM0Czs2Gzz7Qz9DPz9DQzszNzMzLysrIyMfHx8XDw8PCwsHAv8C+vby9vbu5urm6ubm4uLm6ubq5ubm6urq7u7y8vb29vIS9Nb6+vb6/wMDAwcPExsfIycvQ1Nba3ePo7PL5gIOGio+UmqCnrrjBzNbg7fnp16GQdXZ5fX+AhoGCgoSDhIRRhYWGh4iJioqMj5Kbn6POnZWQiYL88ufc0svDvLSrp6KdmZOSj4yJhYKC//z38e7s6ujl4d7d3NrY19fX1dTS0dDPzs3MysrKycnIx8fGxcTEhsMDwsLBhMJ+w8LBwsPExMXGxsfGx8nKzM7Q0dPS09LR0tHR0dLT1NXV1tbY19ra2dvc297f3+Lh5Obn6ert7u7w8vX4+/+BgoSEhYeIiYuOkpKVlpmbnJ+foaWnqaussLK1tri6u7y/wcLFx8rMzc7U19jd4OTm6ezv8fH08/X5+v7+/v+AhoI7g4OCg4SFhYeIiYmLjI2NkJKUlJOVlJWWlZWVk5KPjYuKh4OA/vbt5NvUzca9tbKr5Y+GfF1eYWVobnGGcoVxHXJyc3JxcHFycnJxcHJ0dXeBiHZ4n5fFys7O0dLThNaE14DY2dfX1tTU1NXV1NXT09PS09LR0NHR0NDQz87OzMvLysrLzM3Mzc7O0NHT1dnd3uDj5efo6Onr7e/w8PH09/f6/P3/gYKDhIWHh4iKi4yNjo+SlJWWmJudoKSmqKqsrrCytLW3ury+wMDCxcjLzs7R09XW2NfX2biMZ11SVFVVVRRWVlVUVFNSTU5QUElCSUtMTE1NTYVOCU9QUFBRUVJTU4VUFVVVVVJMUlVVTU1PUU9HRUZHSElISYVKh0sCTEuETApLSkpLSklJSUhJhUiDR4RGBUVFRURFhEQFQ0NDRESGRQZGRkVFRUaGRR1EQ0FBQUBBREdJTE1OT1BRUSoUEwkIBwcGBgYHBoQHBAgIEBEEgYCAgJN/i36RfwSBgYGAi38EgICBgY2CAoGA/3+TfwGA94KSgwGAp38Bg4SEloPygr+Dq4SMgwKCgKh/AYDTgrWDAYD/f51/A4CBgY+CgoECAgQAcLm4ubuzp5eKgOrc69zOwru1s7GbiI+TkoDTztHd7o7Mj5SUm6GmsLK3ubm7wMTGwLWwwprBrojPra+vsLWpo62ur62usLO1vuy6m/eesLWkmqe3xM7BlLicw7y5trOroJiYm5+ipqirq62traytra2ErAStrKyshK1MrK2sq6qrqainqaiop6SlpaOioaCenp2dnp+fnp2bmpqamZiYmJmXl5eWlZOSkI+Ni4mVqqmpqKywrqWsrq6srKyura2urq+vsLCwr4auDK+vrKmlrLCwpqmtrYSvFa6tqqWgoa6xdCIhICAhICAgISAhIYcggh+JHogdghyKHYUchh2UHKQdBB4eHh2FDoYPFhAQEBEREh2xraeoqqqqqampqqqqq6uFrAatra6ur6+EsBCxr6ymn6GxtzcJCQcHBw4NhAsFDAwMDQyIDYIOixwEHR0cHIsdlR6MHYUekR+FII0fAyAgIYQgBiEgICEhIY8QARGHEAEPhxCGEYUSBBMTFBOFFIcKBQsLCgoLhgoLCwsLDAwMDQ0NDg6HDwQODg4NhQwQCwsKCQkIBwcGBQQDAwIBAYUCGAMEBAUnvby2tLy/wcLDw8LBv7++vby8vIW9Fby9vb7AwsG/vr26tK60uMVLKyoqKoQpAygoJ4UmCCUkJCMjISEhhiCLHwIeH4geBR0dHh4fjB4BHYYeAh0ehR2EDgkPDg8PDw4PDg6MDwcQEBAPDw8QiQ+NDk0PNrKtpKaqq6usrK2srKqnnaWsraanrq6vsbCxsbGvr66urq+xsrKztLOzsrKxsLCvrqmaqLGyqaiys6+ko6WlpqWlp6anp6ioqqusq4atEayrqqurqqusq6qrq6moqKanhagKpqemp6empqenpoSlhqY1paOjoaChoqKhoZ6cmJOPjYyKjpedo6yxtbe5x7ml65SxurOrpKGqpqOinYbIl+u0hM7Bv72AeXl6eHFoXlVOkY6bjoN0cm1qaV5TV1hWTYaHj5KaW3xSU1RYWl1hZWlpaGhrbGxqYV1fKjcxKmhaXF1gZWNmbnF1d3d3dnZ4gE41TjAzNC8rMDQ4PDouQkt3dXNzcmxlX19hZGZoaWpqamtsbW5ub29wcHBvb29wb29vbm5vbm4Yb29tbWxrbGtramtraWhoZ2ZlZmVlZGRkhWMGYmFhYGBghF8QXl1cWlhVU1ptamdkcXNycYdyBXFyc3NzhHQkdXV1dnd2d3h4dXBqdHp7e3x9fX18fHx7eHRuaWt4fWU4Ojo6hTuEPIU7ATyGOwI6O4U6Ajk6hDmFOAM3NziGNwM2NjeFNgM1NjaHNQM0NTSINQQ2Njc2jDeGOC45OTk6Ojo7PD09Pj9AQUJDREYjJCUmJygqKy0uMDI0Nzo8Pj+Wi357foCBgYGAh4EBgIWBg4KFgy6CgH17gIeMOyEiIB0cNzUyMC4sKikoJiYlJCMjIiEhICAgQD8+Pj09PDw7Ozo6iDmEOIc3lDYCNTaENYY2Bzc4ODk5OTqFOQU6OTo5OYc6BTs7Ozw8hj2DPoQ/CEBAQUFCQ0MhhCIVIyMjJCQlJSUmJicnJygoKSkqKisrhCwVLS4uLy8wMTIyMzM0NTU2Nzc3OTk6hx0BHo0dSB4eHh8fICEiIyMkJSUlJicnKCgnKCcnJyUmJSQjIiEgHx4dHBsaGRcuLCopKCcmJCJFjoqAe4KEhYeIiIeGhYWEg4OEg4SEhISDg4SFhQ6EgYOEf4ZJOj0+Pj0+Pok/BT4/Pj09hjyGOwM6OzuFOoI5hjgDNzc2hzcBOIQ5gjqFO4Q8gz2EPoYfhSCEIQYiISIjIyOEJAslJSYnJycoKCkoKIQpBioqKysrLIktBj1ybWZpbYduDGxqY2lubWdnbW1ub4VuCW1tbm5vcHFxcohzIXJybWVuc3RtbnNzcWdnaWpramttbGxsbWxtbm9vcXFxcIRxAXCGbwVubm5tboRtBWxsa2tqhGmDaIdnAWiEZwNoZ2eGZihlZGNhXlpYV1ZYXmNobG1vcnV6WT5OLjMyMC4rLS8tLSwqIjMtT0U+hHgkVlVWVE5GPjgxV1RbSzouLSwrKiUjJCMjHjo7P0JJKTAbGxoahR0+HBwaGxscHBsaGQQGBQUaGhsdICUoLTA3PEBBQkJCQ0EgDw8HBgYGBQYGBwcICRMmSUhISEdFQ0FBQkJCQ0OERCFDQ0RFRUZGRkdHSEhIR0dGRkZHRkZGRUVERERDQ0JCQ0OEQgNAQD+EPgY/Pz09PTuFOlY5ODc2NjQzMS8tKygyRT01M2RtdpaAdXBsa2pqaWlqamtqamtsbW5wcXJxcW9nWUdAaX+DqqOUj42KhYF7cV1MRFSFkr+txMXGx8fJzM3OztDR0dHS1IXTAdSE0jnT09LT1NTT0tDQ0M/Ozs3LysjJycfHxsXFxcPCw8PAwMDBvr28vbq6ubm6uru6ubm6uru7uru7vLyHvQi+vr6/vr6+v4TAL8LDxMfHyMvO0dTX29/k6e70+oCDh4yRlpyiqbG8xc7Z5PL7sa+WfnZ6fX+AgYGBhYJkg4KDg4SEhIWGh4iIiYqKi4yPlJ+frIGGk46Ggfnv49jOxb61q6ahnJeSj42JhoOBgP338+3r6ejl4d7b29nW1NTU09LQz87OzczKyMfHx8bGxsXEw8HBwMDAv7+/wMDAv7++v4TAgMHBw8PExcXHyMnLzc/P0M/Pz9HR1NLS0dDQz9HS09PV1tfZ29zc3dze3+Hj5efq6+zt8PHz9/v8/oGDhIWHiYqNkJKUlpmcnaCjpKissLKytbm7vcDCxMfKy8zP0dXW2Nrd3uDk5unt8PT3/IGDhoeIiouLjI2PkJCSlZWUlJWUgJOTkpKTkpWWlpeZm56fn6CipKanqKipqamnpqqopqOin5yal5KOjIeA9e3i2M3FurOdwY+BbFtiZGdscHFxcnFxcXBwcXFycnN0dHNzdHR1dnV0dXZ4fIuIeYSkvNDV2dzf4OLk5eXm6erq6ejo5ufl5ePh4eHg4eLh397e3Nvca9zb29vZ19jV1NPT0tHQ0NDP0NDQz9DR09jb3eDj5eXn5+fp7O/w8fL09vf6/f6AgYKDhIWGh4eIiYqLjI2PkZOWlpiZnJ6hpKaoqqyusLK0t7i5vcDCxMbIy83O0NPU1tfY2dnOgmdcUlFUh1UaVFJNUVJRSEdLTExNTU1OTk5NTk5OT1BQUVKEUxtUVFNTU1RSTFJUVE1MUFBNRkVGR0lJSktLTEyESx5MS01OTk1NTUxNTExMS0tLSklJSUhISEZGR0ZHR0aFRQJEQ4REBkVEQ0REQ4VEIUVEREVGR0dHRkVEQ0E/PTw+QkVIS0xNTU5OKRMSCAgHB4YGDAcHBxERJicoUlZWVol/kH6FfYJ+k3+EgpJ/A4CBgYuCAoGA/3+OfwGA+YKRgwGCpn8BgYWElYPwgrODvYSJgwGBqH8BgdKCt4MBgf9/mH8DgIGBjYIFgYGAgICEfwICBABs9eW1i+/o9omVn7a2uLi3tbKppZ6K+NvSwMzq9KXigI6QkZGSlpqdoKKoqa2zub2+wcPN1tbKw6+zpaWoq7O3trGysri9urOtq7Gzs7nv2tCar6+aobLBztGkw5XDvLe3tKedm56hpKeqrK2uhK1Urq6srq2vsK+wsK6ur66tq6qpqKmpqKmnpaSkpaWkoqCfnZ2cnZ2enZ2dnJuZmZiYmZqYl5eXlpSSkY+Ni4mOqaanqK+xsKOqrKytra6vr66vr66uhK8irq2tr6+wsrCvp6ewsKyqra+wsK6urqupp6GhrbNxIiEiIowhhCAHHx4eHx4fH4QejB0EHh0dHYscgh2HHAUbHBwcG5IcAx0dHIsdghyEHYIchx0EHh4dHoUOhg8PEBAREhMStq6sp6yrqqqqhKuErIStBK6ur6+GsA6tqKSeqrS5EQgHBwYMDIULhgyGDQQbHBwbjRwFHRwdHByMHQEehh2JHoUdAx4dHYQeiB8BHoUfBiAgHx8gIIQfASCHH5AgASGJEIURhBCCEYUQAg8OhQ8HEBARERESEoQTCBQVFRUYDAsLhgwDDQwMiQ2EDIINhg6EHQ0cHBsZGRkYGRgWFRUVhBRPExMSEhEQEA4NCgoIBwYFBQUEBQUEBA+2wr63wcTGxsbHxsXExMLBwcHCwcHBwsHBwL/BwsLDwsHAv7ayuL7sLy4uLi0sKioqKSgoKCcnJ4QmCiUkIyIhISAgHyCLH5wehB2JHoIdhA4BD4YOiw8GEBAQDw8PhxCFD4gODQ8ODg4PDg87sq+mpqyErQ+vraytqZuprq6kqrCwsbOEtR20srGwsbKys7S1tLSztLOzsbGyrZ6nr6+qrLOzsYmmHaeoqaqqrK6wr6+ur7Cxr66rq6qrqqurqqqrqamphKgOp6iop6emp6ampqeoqKeFpjqlpaampaOjoqKioaCenZuUjouIipOdo6irrrC35/XHiau2sKehoK2vqaCIwo7SkM+9u7u5ubixoZCBd46GZVGZl6Jga216dnRxb21tZmFfU5mPh4aQoqRshkpPUFFRUlNWWFlbXl5hZGZnaGloaj4/OTlRXFlZWVteYGBcXF9lbG5ucHJzdHV3glVDLjMzLC4zNzs+M0VIdnRzcnBpYF9gY2VnaWpqa25vb29wcXFycnFyhHGDcIRvFW5tbW1sbGtraWppaGhnZmZlZWVkZIVjBmJiYWFgYIVfFF5dXFpYVlNWbGtnY29zc3JycnNzhHIsc3NzdHN0dXV2dnd4d3h4eXh1bHB7fXx8fX5/fn5+fXp2cGlreX1mODs8PDyFPQQ+Pj0+hT2FPIY7hjoCOTqEOYY4hjcSNjY2NTU2NjY1NjY2NTQ0NDU0izWFNgs3NzY2Nzc2Nzc4N4U4Kzk5OTo6Ojs7PD0+PkBBQUNERUckJSYnKCkrLC4wMTM2OTs+MJ2Ng3h9f4CLgQeAgYGBgoGChoMfgX58eoSJkyQiIB4cNzQyLy4sKiknJiUkIyIiIiEgIIQ/Cz49PDw7Ozo6Ojk5hziFN4g2hjUFNjY1NjaINQQ0NTU1hTYBN4g4iTmGOoI7hDyFPQM+PT6EPwdAQEFCQ0MhhCIwIyMjJCQlJSYmJycoKSkpKioqKywsLC0tLi8wMTExMzM0NDU3Nzg5Ojs8PR8fICAhiCIIIyMjIiIjIyKHIxgiISAgHz08Ozo5ODc4NjU1MzMzMTEwMDCELwQwLi8vhC4fLSwrKikoJyclJSQjN6WPjH+Eh4mJi4uJiIiGhYWGhoWHCIiHh4iHh4mJhIgPhoeHh6MxPz9AQUFCQkNDiEIUQUJBQUBAPz8/Pj4+PT0+PT09PDyEOwQ6Ozo6hDkKODk5OTg4ODc3OIQ5hDqEO4Q8hD2EPoYfhiCFIYIihCMIJCMkJSUmJiaEJwUoKCgpKYQqBSsrKywshS0ILFVzbmdobW2Hbj1rYmptbWVqb25vcHJxcXFwcHBvb3BxcnN0dXV0dXR0c3Nyb2Ztc3NtbnN0cmdoaWpqa2xtbW1vb3BwcXFyhHMGcnJxcXFwiG+GbQRsa2tqh2mFaIdnhGgqZ2dnZmZkY2FeWllYWFxiaGtrbW5whWdFKjExLi0rLDAvLiojNCxKP3d3hXYEb2JXTWJRPzApU1ddNTk6OzUyLywtLSsoJiJCQT0/QUZGKjAZGxsbGhkaHB0dHBwcGxobHBwcGxsHBQUFEBoYGRobGxobGxsdIikuNTxBQ0NDREIfDwcHBwYGBgcHCAkTJklJR0dGRIVBEkJDQ0NEREVFRUZGR0dISElJSYVICkdGRkVFRUREREOFQoVBAT+FPSQ+Pj08Ozo5Ojo5ODg3NjY1MzIwLSooLUU9NjJkbnaWf3VwbWuIajRsbG1ub3BxcnJybF9QQlV9gZ2rmpKNi4iEfXBhUUVRg5S+q8XIycrNz9DR09XX2NnZ2drZhNgE2tnZ2ITXPNjY19bW1dXU09LS0c/Pzc3Ny8rJyMfFw8TDwr/Dw8C/wL69vb28vLq6ubq6u7q6ubm6u7q8vLy9vby8u4a8OL2+vr2+vr/AwcLExsfHycrO0dPW2t3j6ezx+oCFiIySl52jq7S9x9Dc6PKjyZqHc3h9gIGCgoODhIKAg4KCg4ODhISFh4eIiIqLjI2QmKChwYGVkImD/fLn3NLJv7aup6Kcl5KPjIeEgv/8+fXw6ufm5OHd2tnX1tTT0tDPzs3My8rJycjGxcTEw8LBwMC/v7++vb28vb29vLy8u7u7vLy+v7/Bw8PFxcbIysrKy8vLzM7Pz8/Qz87Q0c+A0NHS0tLV1dfa3Nzd3t7h4+Tm5+np6u3w8vb7/P+BgoOFiIqMjpGTlZmcnqGkpqersLS2ub3AwsXJy87T1tja3d/i4+bn5+vt7vD1+f2AgoWHiouPkJOWmJqbnZ6foaSnqq2trrCxr6qjnJWOh4H27Obf19HQycS+urS2tLKvraonrK+vrrK0ubq9vsTExcbIx8fFw8C8trKuqNngg3tfX2JkZ2xubm9vhHALb3BxcnN1dXV2d3eEeIB5eXyDjYJ8y4LT2+Hn7e/y9vf6/P39//3+/f39/vz7+fj29vPy8vPw7u7r6unn5uTl4+Hh397c29nX2NjZ2NXU0tHS1djZ29zd4OLk5efp6uvt7/Hy9fb3+v3+/4CBgoOEhYaGh4iIiYuLjY6QkpSVl5mbnJ6hoqWmqKqtr7K1tiq5u72/wcTGyMrMz8/R09XV19jU82tcUlFTVFVVVVZVVFVTTFJUU0dKTU2FTodPA1BSUoRTIFRUVVRUVFFNUlVTTExPUU9IR0dISElKS0xMTE1OTU1MhE2CToVNBkxLSktKSoRJC0hHRkZGRUZHRkZFi0QBQ4REB0VDQ0NEREWERhRFQ0A+PT5AQ0RGR0lKSk0nEggHB4gGEAcPESYnUVJSU1JSUEg/Ni6EfoN9jn6HfYJ+lX8FgYKCgoCWfwKAgYqCAoGA/3+KfwGA+4KRg6Z/AYOEhJOD74Kwg6GEq4MBgqh/1IK3gwGB/3+UfwKAgYyCBIGBgICLfwICBABw9vrv8Iqerri4u72/wL67q5uYmYvyia7DxNHg9ICEh4qKi4qKiYuOkpaanqCgpKqtr7K7+NvU2Y3GvrWnn56en6GhpKaus7m6t7GvrKyvs7W8gYaBqq+apLfG09GTgNC+uLa0rJ+eoaOlqKusra2uroiwSbGxsK+urq2rqamoqKimpqWko6SkpaOhoKCcm5qamJibm5uampiYl5eXlpaWl5eWlJKRkI6Mio6oqamkr7Csoqirq62ur66vsLGErwKwroSwIrGys7Gtqaavsq6prLCysrGwr66rpaCgrrF1JCMjIiIjIiGGIhUhISAgHyAfHx8eHx4fHh8eHh4dHh6HHYYcgx2JHAEdhRwFGxscHByEG5Qcix2HHIgdBB4eHx+IDxMQEBARERMUFGWxrqWrqqmpqqqqhauCrIStHK+vsLCxsrKxsKyooqCwt2IQBwYGCwoKCQkKCgqFC4YMghqFG4YcARuJHIQdghyIHQQcHR0diR4BHYgegh+GHgQfHh4eih8BII0fiyACISCIEAMRERCHESEQEREREBAQDxAQDxAQEA8PEBEQERITExMUFQsLCwwMDA2EDocPhBATDw4cGhkXFhQTJSMhIB46Ojk4N4Q2GjQ1NWlpamlramlnZ2loaGlpaWtqamptNjc1hDYEODY1NoQ3OjQsx8S9wcvLy8nHx8fFxsXGxcbExMXGxsXFxMPDxcbHx8bFw7u5wcqZNTMzMjIxFxcWFhYVFBQUExOFEgURERAQEIQPBxAPHx4eHh2EHgkdHh0eHh4dHh2GHoIfjh6DHYQeCB0eHh0ODg4PiA6HDwUQEBAPD4gQBQ8QDw8QhQ+DDosPXiC3sqqmrq6vr66ur66tq56rsLClrLKztLe3ube4trW1t7e4ubi3trW1tLS0tbOzsKGms7Wvr7e6taakpqenp6ioqKqprK2ur62tsbKzsrGwsK+trayrqqqrq6usqqqGqU2op6mnqKmop6emp6alpqWlpqamp6empaSjo6Ohop6ZlI+NjJCYo62trq+15f3Tj6yuppyTm6OhnIS2+qfZwb6/vry3ppKD7d3ChM7S3x2UmZuiYmt6gHh2d3R1c3JnXl5dV55ggIh/goqUTIVNUExMTE1PUVRWWVlaWlxeXl5hUUA+QDpmZWFZVFNTU1RUVFZZXF9gYWNna3J1dnh5QzAoMjMsLjQ5PT4vM3l0c3JxamFgYmRmaWttbm9wcXJyh3MocnJycXFxcG9ubm5tbGxrampqaWloZ2dnZWRkY2NjZGNiYmJhYGFgYIVfFF5eXVtaWFZTVGxraGNvcnNzcnN0hnMmdHV1dXR1dXZ2eHd4eHp5dXBsfH9+fn5/gICAf358eXJsbHl+ZzqEPo0/BT4/Pj4+hz2CPIQ7hjqFOYI4hTcENjY3N4Q2AzU2NoU1hTQGNTQ0NDU0hDWHNg83NzY3Nzc4ODg3ODg5OTmEOiQ7PDw9Pj4/QEBCQ0RFRyQmJicpKiwtLzAzNTc6Pj10kYl4fn+FgQGCiIECgIGFgiKDgoKCgH17fIaLWjgfHRw1MzEuLCsqKCcmJSQjIiEhICAfhD4UPTw7Ozo6OTk5ODg4Nzg3ODc2NzeENgI1Noc1gjSLNQY0NTU1NDWHNoU3hTgGOTg4OTg4hjkIOjo6Ozs7PDyEPQs+Pj8/P0BBQUJDQ4QigCMjIyQkJSUmJicoKCgpKiorKywtLS0uLi4vMDIyMzU1NjY3ODk6OzweHyAhISIiIyMlJSYmJygoKCkqKiglIyA9ODUvLCgkQz03MCtST0xKR0VCQD49OzpycGxramtqaWloaGlpamtsbW5wcTo6PD4/QEJDRUdJSk1PUVQ6k5GICoSKi4yMjIuLiomFiIOJhIobi4qLjI2NjIyMioyLjXE7QENERUYjIyMkIyMkhiOHIoQhByAgIEBAPz+EPgc9PTw8Ozs7hDqEOQM6OTiFOQs6Ojs6Ojs8Ozw8PIU9BD49PT6IH4QghCGGIoQjDCQkJCUlJSYmJycoKIQphCoGKysrLC0shC0GOHVvaWZthG4cb29ubW1jbXBwZ2tvcHFyc3R0dXNzcnJyc3N0dId1JHRzcWlsc3ZvbnR2c2lnaGlqa2ttbm9vcXJyc3N0dXR1dHRzcoZxEHBwcG9wbm5tbWxsbGtra2qHaYJohGcCaGeEaIRnJGhmZmNhXVpYWFthZ2trbGxvgWhFKzAuLCkmKisrKSEyUkN4d4R2C3JlWE2MfGZKfH2EHkFKVFo1OT9ANzUyMTEwMCsoKSklRS8/PzMyNDQaGoYZCxgYGBkaHBwbGxobhBpDDAYFBQkbGxsaGRgYGBkYGRgZGhwcHSQuN0BDRUdHIhAHBwcGBgcHBwgJE0pIR0ZHRUFAQUFBQkNFRUVGRkdISElJSYZKIkhISEdGRkZFRURFRENDQ0JCQUBAPz4+PDw7Oz09PTw8OzqEOYA4ODg2NTQzMS8tKicqRz02M2Rtd5x/dW9sampra2tsa2pra25ucHBxcnJxb2VVRkR1gJGwnpKOi4qGf3NiUkhSgJPBscrMzdHU19jZ3N7g4uPi4+Tl5ePi4uHh397f4ODf3t3c3NzZ2dnY19fX1NXU0tDQzsvKy8nJxcHFxsTExQHEhMBYv729vby6uri4uLq4uLi5urq5ubq6ubq7u7q7u7y8vb29vr69vr/AwcDCw8TGxsnLztHT1trf4+js8/qAg4iNkpedpK22wMjT3ujcwqGPc3l7gIGCg4KDg4WCU4ODgoSGhoeHiImKi4yNj5OboKm185KMhf/16d7RyL+3rqeinJiTjouHhIH9+fXx7Ofk4t/c2NbV1NPS0NDPzczKycfHxsTDw8LBwcC/vr69vLy8hrsFurq5ubmEuoC8vsDCw8TDxMTExsbIycrKy8vLzMzMzc3Nzs/Pz9DR09XW2Nra2tzf4uTm6Ors7PDy9fj8/YCDg4WIioyOkpWWmpyeoaSoq6+0trq+wsbJz9HV2t/i5ers7vH09vf6/f7/gIKDhIeHiYuNkJOWmp2fpKerrKWclIyC9N/Kt6aUgoDiwaeRg/ft3tDEua6mnZOMhPvw6N/V0c7Nys7SztLW2t/j6fT5gIWLkpidpq60vcbP2eTx/IaHf2xcYWRmam1vcG9vcHFwcXBxcnNzdHV2d3h6enp7e32Bi5GCg7aq3ebu9vuBhYaJiYqKi42Oj42Ni4mJiIiIh4eGhYWEg4H//ID29fXy8vHv7ern5uXk5eLg397e3dzc3Nvc29rc3d/g4ePl5+rr7e3v8fP19vj5+/39gICBgoKDhISGhoiIiYqLjo+Qk5SUlZaXmJqdnqGjpqirrbCytbe5vL/Bw8XIyszP0dLT19jZ2tjEc19VT1NUVVVVVlVUU1JMUVJSSE1PToRPP05PTk9PUFBQUVFTVFVVVFRVVVRVVExOVFVPTU9RT0dGR0hJSklKTE1MTUxNTk5OT05PT05OTk9OTk1NS0lKSoZJB0dGRkZFRkaERQRERENEh0MfRENDQ0RERUVERUZEQ0A/Pj5AQ0dKSklISUslEggHB4cGFQcPJCZPUlNTUlJLQDYsQzIgGC0uNYR9kH4BfYd+l38FgIKCgoGZfwKAgYqCAYH/f4d/AYD9gpCDAYCkfwWAg4SEhJOD7IKsg5iEh4OFgoyBlICRgaZ/AYCGgpuDs4K4gwGC/3+QfwKAgYuCA4GAgIp/hH6DfQICBAAc8Y2muLC6vsHBvri9vry6sa+hkpWryP78g4OEh4SIcYeJh4iLiouMjY+RlJWWmZqdoaqLzNHWvsG8u7y1r6OamJmZmpudn6autLa2sq+tq66xtbrwiIyvo52wwM/aqpbVv7u5tqmcn6Kkp6mtr7CxsrKysbGys7KxsLCvrqyrq6mpqKempqSkoqOioqKhoJ+ehZorl5mZm5mZmJiZmZeWlZaWlpWVlJORjoyIjKmopaKwr6qlqaqtr6+urq6vsISvJa6ur7CvsbKxsa+rpK+zsKeqra+wsLCvr62lnp+xs3UnJSUkJCSGIwciIyMiISAghR+IHgMfHh6GHYYcBx0cHB0dHBuNHIIbjhwBG4scjR2GHAIdHIQdhh4EHw8PD4QQEhERERISExUVH7awp6qqqamqq4isB62trq+ur6+GsBGtpqGlsbkaBgUECQkJCAgJCYUKhgsCDBmEGgMbGxqJG4UcghuGHAIdHI0dAh4dhB4FHR4eHx+JHgEfhx6QHwIgH4UgASGJIIsQAhEQkBGFEgMRCAiECYAKCgsLDAwNDg4PDxARERIRESAeHRoYFSgkHzo5Nzc2NGhmZWVlx8O+vLu6ur28vLy9vbq6vb2/v7/AwcHCwsLDwcHAwMHAwsPExsfHxsbIyMnKy83PzMvPz87OysnByMzOzczKycrJyMjJx8bHyMjHx8jIyMnIyMrKycbEvcPL2R9fPDo7OhwcGxoZGRgXFhUVFBMTExISEhEREBAPDxAQig+CDoQdBB4eHR2THgEdhx4PHR4eHR0eHh4ODg8PDw4Ojg+MEAMPEA+EEAcPDw8ODw4OhQ8LEA8PDxC9sqykr6+Gri+vqZ6ws7Krsba5urq9v8HAvru7vL69vby7urm4t7a2tbSzqqS2uLSvt7q2pqOlp4SoJ6mrra+wsbCxsbCxs7KysLCurq2urq2sq6usq6qqqaqqp6iop6inp4SpCqinp6amp6empaWFpjelpKOhnZmUjoyOlaGpra6wtcPGvYqsq6CUiZWcmo3Rhq3Xt7S1tbbPldaHxeTY0dP2k6CmlO3acqVheIV8g316d3VydHNybmppYFxkdoahl0xMTU5OTkxMSktKSkxMTU1OTlBSUlNTU1RVWjs6Pj9GY2RkZWJdVlFQUFBRUVFSVltfYmNhY2dudHZ3eIIwKzQvLDI3PEA1OHlzcnFvZ2BiZGVoa25wcHJzc4V0HHV0dHNzcnJxcXBubm5tbGtramppaGhnZ2ZmZWWFYwpiYmFiYWBhYWBghF8RXl5eXFpZVlJUbGllYXJzcnOEdA11dXV2dXV1dnV1dnZ2hXgaeXdxanZ8fn1+gIGAgIB+fHp0bG16gWg8QECEQYhChEEIQEBAP0BAPz+EPgU9PT08PIU7hDqEOYM4hTcBOIQ3hjaFNYU0AzU1NIg1hTaHN4Q4hDkpOjo6Ozs8PT0+Pj9BQkNERUZJJSYmKCkqLC0vMjQ2Oj1AO5WMfH1/gYGEggOBgoKIgYSCK4ODgoGAfHp/iI8yHRwaMzEvLSspKScmJSQjIiIhICAfHz4+PTw7Ozo6OjmEOIU3hjaFNZc0BTU1NjY2hDWCNoc3hziHOYU6Yjs7PT09Pj4+P0BAQEJCQkMiISIjIyQkJSUmJicnKCgpKisrKywtLS0uLzAxMjIzNDU1NhscHB0eHx8gICEhIiMkJCUmJygqKikmR0I7NS8pRTgtU05JREA8cGpjXVejmI6HhYUOhISFhYWEhoiIiIeJiImGioWJGIuMjYyMjI6PkZKTk5SUlpaXl5eVlJGEiYWOgo2EjASLi4yLhIwWjYyOjY2QkJCPj46Pj5JIP0NGSSUmJosnBCYmJSWGJIIjhCKCIYQgCB8fPz4+PT09hDyDO4Q6gjuGOoM7hjyFPYQ+iB+EIAYhISEiIiGGIoIjhCQFJSUmJieFKAspKSkqKisqKywsLIUtCCJ+b2tkbW9vhnA5bWZxc3FpcHJzdHV3eHl6enh2dnd3eHd3dnd2dnd3dnVzbWl1dnNwdXd0aWhpamtrbG5ucHFxcnR0hHUGdnV0dHRzhHKEcQdwb25ubW1thGsKampqaWloaGloaIZnPWhoaGdnZ2hnZ2ZkYV1ZV1ldZGlrbW1vcldAKi8tKiYkKSkoJTcqRHV0c3N0dYlDTi0zUHR1eYxRWV1VkI8fWTQ+Q0BDOzQzMS4xMC8tLCwpKTA5OkM5HBwbGxsaGYUYCRkYGBcXGBkZGoUZDxoMBgUFChscGxsbGRgXF4YYSxkaGxwdHycxO0RHR0dFDwcHBgYGBwcICRNIR0dGRkI/QUFAQUJERUZHR0hKSUpLS0tNS0tKSklJSEhHRkZFREVFQ0JCQkFBQD8+PYo8hDomOTk4Nzc2NTMyMC8sKSYqRz40MmdsfZh7c29sa2trbGxramtsbW+EcGdxcG5nWEg8Z3yIsJ6SjYuIhH9zZFNHTX6SvrTR09bY3N/j5ujr6+zv8PDx8fHw8O7v7Ozt7Ozq5+Xm5uTi4d/e3t3d3Nra19fV1NXR0M7KyMvJycjHyMfGxcXFwsG/wL29u7q6uri5hrgLubi6ubm5urq8vLyFvQG+hb8BwITBKMLExsfIy9DR1Nnb3+Pn7fP6gIOIjZKYnaaut8DJ097lkayWd3d5foGKglODg4SFhoeHiIiJiYmKi46Wnp23rJCNh//16d3Rxr62rqehnJeTjouHg4D69/Pu6OXi39vY1NPS0dHOzczMy8nJxsXEw8LAwcC/vr29vLq5uru7uYW4hrdguLi7vLy8vb29vsDBxMXFxsfHx8nKycrJysvMzc7Oz9DR0tTV19fY3N/h5OTo6Ons7PD2+vz9gIKEh4mMj5KUl5qcnqKnqa6xtbi9wMTIz9LW2+Dl6e/09/6Bg4SHiYyNhI5Mj5CQkZSVl5qdm5KG+eXLtJiBzaKJ9+HKt6OQ/9/BpIrctJqLhoOAfXx8e3p6eHh5d3Z3d3Z3d3d2d3h5eHl4eXl6eXx9fX+AgIGBg4SEhIU9hoJ/eWBiZmdpbW9wcG9vcXFwcHJyc3N1dXV2d3l5e3x+gISRkoaLhsHf6/mBhoyRlpqanJ2foqOjoqKgnYScfpmWlZORjo2LiomGhYOBgYCA/fv69vLv7evr6uro5+Ti4uLk5OTj4uLh4ePk5ujp6uzu8vT29/j4+vr8/oCAgYKCg4OEhYaHiYqLjI6Pj5CQkZKUlZaYm56hoaWoqqyvsbS3ury/wcXGyMvO0dPV19na29zciYhfVU1SU1RUVIRVDFNNUlJQRkxOUFFRUYRSCVFRUlJSU1NUVIRVGFZWVVRQTlRUUExRU1BIR0hJSktLTExNToRPBVBPT09QhE+EThNNTEtJSUhISUlJSElHRkZFRUVEhUUDRERDhUKGQzdERUVFREJAPj0+QURHSUlJS0smEggHBwYGBQUFBgYOESRNUVJTU1BPFBIHBxUqKisvGhwdHD9GAX2Xfpt/BYCCgoKBnX8BgYmCAYH/f4R/AYD/go+DAYKkfwSChISEk4PqgqODl4SGg4OChoGFgNt/AYGEgqaDq4K5g/9/jH8CgIGKggOBgYCHfwWBgYKBfoR9hH6CfQICBABvpra0raq3ur/Bv7W1tbe7t6WCqe76+oeMjo+PjI6MiIeHh4WEhYSGh4aIh4eKi42QkZOXvbDLzdurpKGiqa6vqaKXkI6NjY+Sm6Knqqyyt7Otq6yytbjAoNKknJywwM/cpuDFu7m2sJ+fpKWnq6+xhbOFsgWxsK+trIWqCKelpKWko6KhhKBJnp6bm5qampmYmpqbmZmamZiYmJeXl5aWlZOTkY6MiI6qp6Ghra6pp6utr7GxsLGxsbKxsLGwrq+wr7Cxr7CxrKSqsrGpq6ytroSvFKynnp2utXwqKCgoJyYlJSUkIyQkhBELEBAQICAgHx4fHx+GHgUfHh0dHIodAxwcHZQciBuCHIQbixyFHYIchh2DHIgdCB4eHh8fHyAghBCEERUSExQUFhcTsq+pqKqpqaqrraysrKuErBWura2trrCvr7Cwr66rpJ6rtLYLBASGCIMJiQoDFhgYhBmHGoobBBobHByEG4gcBB0dHRyIHYUegx2PHo4fCSAgIB8fICAfH4cgixACERCMEYMShBMBFIUKhAuDDIQNZw8PDhwbGRcqJiA7OTg2NWpnZcrAu7u4t7i4uLm3ubm7uLm7u7y8vr6+v7+/vr2+v8HBwMHDxMXFxsfGxcXGyMfHyMrJycjJysvMzc7Oz9LPzs7P0dLS0dDQ0NLS0M/OzczLysrJycmFyoTLIczNzc3LyMDH0OtzQ0NERCEgHx8eHBsZGBcWFRQTEhIREYUQhA8HEA8PDw4ODoUPhA6DHYYeBB8eHx+EHoIfjh4KHx4eHh0eDw8PDo8PAhAPixCCD4UQhQ8FDg4PDw+FEAgPEBHDs66jroaxOLCwrKCytrauub2+wMHEycjKzMnFxcTDwcG+vb29vLu4uLaypra7uK61uLSnpaSmqKipq6yur7CxiLMmsrGwsK+ur6+vsK+ur62trKuqqqqpqaioqaqoqampqKinp6inp6WEpDulpaWko56ZlI+NjJSjrLGxsbfri+uao52UiY+amYzB7IrAs7GwsKqa+baHnpzU5desuO/Nz/eIhIT5hHN4g3x4dXl4eHh1b29vcHFsY1V5oqOZT1JTU1JOT05MS0tKSUpKSUpKSUtKSUpKTE5PT1BcND09TVpVVVZbXV1cV09MS0pKS0xSV1tcXWBkY2Fob3R1dndJQjAtLDI3PEE0XHRxcG9tY2FlZ2hrb3Bxc3R1hHYrdXZ1c3NzcnJwb25ubGxra2pqamloZ2dnZWVlZGNjY2JiY2JiYmFhYWBgYIRfEF5eXFtaWFZTWG9qZGJzdHSGdYZ2BXV3dnZ3h3gIcmpye3x+fn+EgBZ+fXl0bG17gGs9QkNERERFRkVERUVFhSIOISFDQ0NCQkJBP0BAQD+EPgU9PT08PIQ7gjqFOYU4hTeHNoU1hjSHNQM2NTWFNoY3hjiFOSc6Ojs7PD09Pj5AQUJDREVGSCUmJygpKywuMDI0Nzs+Lp2PgXt+gIGFgoaBhYKEgxqCgoF/fHmDiZYoGhkxLy4rKigoJiUjIyIiIYQgDD49PTw7Ozo5OTk4OIU3hDaHNYU0jjMENDMzM4U0iDWHNoY3hjgBOYQ6ZDs7Ozw8PT0+Pj8/QEBBQkJEIiIiIyMkJCUlJiYnKCgpKissLCwtLi8wMTEzMzU1Njc4HB0dHh8gISIiIyQlJiYnJygmI0E7NC1MPTBWT0hDPnJoXqiWioaFhIKBgYCAgYB/gICEghWDhISFhoaGh4iJiYqKi4yLi4yNjI2EjBiLjI2Oj5CPj5CRkZKSk5SUk5SUlZWTkpKEkQqSkZCQj4+OjY2NhI4djY6Ojo+Pjo+RkpOTko+QkplXQUVISycpKissLCyILQ4rKysqKSgnJycmJSUkJIQjBSIiIiEhhCAHHz8+Pj49PYs8Azs7PIU7hzyGPQE+hx+FIIghhSIaIyMkJCQlJSYmJicnKCgoKSkpKikqKyssLSyFLUkonHFtY21vcHFxcXJxcG5nc3VzbHR2dnh5fH9/f4CAfn59fX1+fHp5eXp5eHh4dGt2eXZwdHd0aWlqa2ttbW9wcXJzc3V2dnd3hHYcdXV0c3NzcnJycXFycG9vbW1tbGtsa2pqamlpaYRoiGc8ZmZmZ2dnZWFdWldYXWRqa2ttb4I3SisrKSckJykoJDROPXJxcnJxal2SaEdUQUVEPy0se3d6jE5PUqNbFkFBOzc0NTMyMTAuLi4tLS0rKDxLRDqEHSkcGhoZGBgZGBgYFxYXFxcYFhYXFxcYGBgZGQUFBQsbGxkZGhoZGRgXF4QWShcZGxwcHR8gHyIvPUVFRkYhDwcGBgcGBggJJUdGRUREQEBBQEBBREVGSEpKS0pLS0xLTEtLSUlISEdHR0ZFQ0REREJBQkFAQD8+hj2EPIA7Ojk7Ojo5ODg3NjUzMjAuLCkmLEU8MzVpbYmUfXRwbWxtbGxtbWxtbm9wcHBvb29sZVZGO1N4gaSgko2LiIZ/c2NTR017j8i71tzf4+rs8fb4+fz/gIGCgoKBgP////z9+/n59/b18u/u7Ovp5+bl4uHf3t3b2trY19bT0tHPzxnOy8vLyMjHxcbExMLAvsC/vr27ubm4t7i3h7gCubqGuwa8vL29vr6EvzTAwMDBwcLDxMbHyMrM0dLW2t7j5+zz+4CDiIyRlp2lrbe/ydPdhLyagXZ5fYGCgoODg4KChIOEhASFh4eHhIlCi5GXn6PiuIuG/fLl2s7DvLOspZ+alZCMiYaD//j08e3o4+De2tbT0dDNzMrJycfGw8PDwsLBwL++vry8u7m4uLe3hLaJtQO2t7eEuIC5uru9vr/AwcHCw8PExcXGxsfJy8vMzM7Q0dHT1dbX2t7h4+Tm5+vs7/L19/z/gYKFiImMj5OVmJufo6isrrS3ur7BxcrR1tvg5eru9fj9goaJjI+Slpicn6GjpaakpqGSgebIq5DstI/84suxl/7PpO+rj4uIhIB/e3p5dnV1cwNzcnGHcwp0c3R1dnZ1dXV2hHUPdnd2dnd4eXl4ent8fH1+hX8Wfn9/fn18enh2dXJwbWtsbW5wcHBxcYRwgHFyc3V2dnZ4eXt8fX+BhZWQh5O5w9fp/oePmKKqrrO2ury+wL+/v768ubi2trKtqKWinJmWlZSTkZGPjo2MiYeGhIH//v369/X08u/t7e/v7ezr6Orq6Ojo5+fp6+zu8PH09vj5+fr7/v6AgIGCgoSFhoaHiIiJiYqLjIyOj4+QKZKUlZianJ+ho6aprK6xtbi7vsDBw8bKys7Q1NbZ2tzd3t6v1GFYS1FTh1QRUk5TU09ITlBRUlNUVVRVVlaEVSVUVVRVVVZWVlVWVVJOVFVSTFBRT0lISElKS0tMTE5PUFBRUlFShFEDUFBPhU4KTU1MS0pJSElISIRHhEYEREVFRYdDhEIaQ0NDRERFRENBPjw8P0VISUlISUoREQcGBgaEBQQGDSIlhE4WT0k7VTgcFwkHBgYEBikoLD0gISZaNpZ+nn8EgoKCgZ9/AoCBiIIBgP9/An+AjYKHg+2Cj4OkfwODhISSg+mCoYOThISDg4KFgYOA6H8BgYSCq4OngrmD/3+Jf4KBiYIDgYCAh3+EfgGBhIIBgIR9BX5+fn1+AgIEABvQzcS5qcfMwb+6tLi1p5ibscmEiI+QjY6Pj5KEkWCSjo2MioeFhISEhYWEg4SFh4eIjaGaycvvnI6NjI2Vm56ioJmRj4uJiYyQmKGkpq+9xMK5q6uxs7a9hcadk6S4xtba3Ny+u7m3p6Kmp6irr7Gys7S0s7KxsbCwrq2tq6uEqRGnpqSlo6OjoqGgoqGfn56cm4SaVZubnJyZmZqZmZmYmJeXl5aUk5COjIeUq6aipbCvp6isr7GxsbKzs7KzsrOxsbGysbCwr7CxrqWir6+urK2ur6+wsLGsp56erraGLiwrKyopKhQTExOEEoIRhhCJDwEdhB6MHZMcjhuFHIQbjxyKHQMcHR2HHgYfHyAhIiKEERMTExMUFBYXGBdesKynq6mpqaqrhawFra2trq2ErhOvr6+wrquloJyvty0GAwYGBwcGhAiECQwKCQoKChYXFxcYGBiHGYUahxsBGogbjByFHYUehB0EHh4eHYoehR8BHogfiCCCH4QgiRCHEYcQhBIGExMUFAoKhAuCDIQNIg4NDQwLFRQSIx8dODc2NWZkxLy4uLi3tra1tbW2ubq7u7uFugK9vobACsHAwMDBwcDBwcKEww/ExsfIyMjJysrLysvMzMyEyxfNzc/R0tPT0tLU1NPT0tPT09TV1dTT0YTPhc4BzYTOJ83NztDR0NDPycHM0NhGLDI6QkomJiUjIR8eHBsZFxYVFBISEREQEIQPAw4OD4kOhQ+HDoIPiR6EH4seAR2EHoQdkw8KEA8QDw8QEA8QD4oQiQ+IEEBpt7KksLOzs7Szs7WzraO0uLayv8THysrN1dfY2drU0M7My8jGxsTCwcC/vrywub26sLS2t6ekpaeoqqusrq+zhbUBtIS1EbOxsbGwsK6ur7Cwr6+ur66uhaxQqqmpqKmop6ipqKinp6empqSjoaKjpKSinZeRjIyUoqywr7G2gpz/p6edj4eWmJLT/Yy6sq+ur56B3L2orqHXs7PIu5fu3c7Asfr7i5act80WgYF8dW9/fnh1cG5xcGpkboKKU1NUVYRTDFJQUFBRUU9QTkxLSoVJaEhHSEhJSUlKUC48O05PSkpKS1BUVllXU05NS0lIS05TWFlbYWltbWhkanFzdHVBPy4pLjQ4PkFKenJwcG5lYWZnaGxvcXJ0dXZ3d3Z2dXV0cnJxcG9vbWxrbGtramppaWdmZmVlZmVkhGMCYmOEYoRhAWCEXxNeXl1cWlhWU1tvamVmdHV2dXV1incSeHd3d3h5eXh5eHRra3t7fn9/hH4QfXx5dGtreoF0QUZISUlJSoYlCSQlJSUkJCMjI4YiCCEhISBBQUA/hD4DPT08hDsQOjo5OTo6OTo4ODc4ODg3N4U2BDU2NjaHNQQ0NDU0hjUGNjU1NjY2iDeGOIQ5hDoiPDw9PT4/P0FBQ0RFRklKJicoKSosLjEzNTg8OmGPhnp+gYeChYGGgjCDg4KDgoB8eniGizUwGC4tKyopJyclJSMiIiEgIB8fHz08PDw7Ojo5OTg3Nzc2NjaFNYg0hDOQMoMziTSHNYU2hTeEOIQ5DTo6Ozs7PDw9PT4/P0GEQmYhIiIjIyQkJSUmJicoKSkqKywtLS4vMDEzNDU2Nzk6HR4eHx8gISIjJCUmKCgpJSE7MytDMixQSUI7al6lkoqGhYWEhIODg4KDhISEg4ODgoOCg4SDhIWEhYWFhoaHiIiIioqLjIyEjQKMjYWODY2Njo+PkJCRkZGSk5SElReWlZaWmJeXlpaVlZWUk5KSkZGQkJCPkIiRP5KRkZOUlZaUkZOUm0MwNjxETSwuMDIyMzQ1NTY2NTU0MzIxLy8uLSwrKiopKCcnJiYlJSUkJCMjIiIhISEgIIUfBT4+Pj8/hD6CPY48hD2EPoYfiCCGIYIihCMHJCQkJSUmJoQnDCgoKSkpKiorKyssLIQtCy4uLW9yb2VtcHFxhXI1b2l1eXZxeXx/gIGCiYqLi46Jh4aFhYOBgH59fXx7enlvdXl3cHN1dWlnaGpsbW5wcXJ0dXeEeAh5eXh3d3V1dYR0hXIEcXFvb4VuCGxra2ppaWpphmiCZ4dmOWVjX1xZWF1kamxsbW5EO1IxLiomJCgoJjlRPm9vbm5tYE6Ba1teVHVjYmpeN0lAOS1QlZ1aaXF8giQ6OTUxLzY0MjIwLzAvLS84QUAiIB4eHRwcHBsbGxoaGxkZGRiHF4IWhBcNFhcXBQUFChgXFhcXGYQaghiEF0oYGRscHR0eHyEhISk3Q0RERSEOBgUGBgYHCBJGRURERUE/QUFBQkRGR0lKSkpLS0xMS0tLSklHR0ZFRUVEQ0NCQkJBQkJBQD9APoY9ATyEO4A6Ozs6Ojk4NzY0MzIxLiwpJzFFOzI+bXGUjntzcG5ubWxubm5vb3Bvb25ubWxqZVhHOUhze6Gsk46Lh4R+c2JSRkx6kODD4Ofs8fb/goOGiYqLjI2Ojo2NjIyKiYiHh4eEhIOCgf37+ff28u/u6+ro5ePh39/d29nW1tXV1M7MyhLKy8vKycnFxcTEwcC/v768u7mEuAS5urm6hbkJurq7uru8u7y9hL4zv7+/wMDAwcLDwsTFxsXGyMnMztLU2N3h5uzy9/2Ch4qQlZujq7W+ydK4hZ+Lc3h9gIGBh4KFg0yEhIWHiIiJioqMk5ufqpP3gvjr4NXLwLexqqSdmZSQi4eFgv/48+/r5+Pf3NjV0s/NzMvIx8fFxMLCwsDAv729vLu6t7e3trW0s7KzhbKAsbKys7S0tbW2trW2tra4uru7ury8vsC/wMDAwcPFxsfIyMnLzc7Q0tPU19re4OTk5urt7/L2+Pv/gIOGiYyPkpOWmZugp6qvtLi7v8THzNPY3OLn6/H3/IGGioyPlJibnqKmq6+xq5iF4LiO0ZyG68qqjeGm7KiTj4qGg4B/e3kEeHh3d4R2Cnd2dnZ3d3Z1dnaHdRx2dnV2dXR1dXZ1dXV2dnR1dnZ3eHh5ent8e3x+hH8OgICAgYGAf317eXd2dHKEcQJwcYVydXFycnN0dnZ2d3l8fHx+f4aSjIWUmJCgttf6k6exvMbN09je4eLi5eXi393c2NbRzsjCvbaxr6upp6WjoJ6alZWUko+NioiGg4OCgYD9+fj5+vr39vTx8fDv7u3r7O3s7PDy9Pb3+fn7/P///v+BgYKDhIWFhYWGTIeIioyNjo+RkpSVl5qcnqKkpairr7K2uLu+wMLFyMrNz9LV19rc3N7f39HeZVpNUVNTVFVUVFVUUU1TU1BKUFFSVFRVV1dYWlpYWFiMVhZPVFZTS05QUEhHSElKTExNTU1PUVJSiFMDUVBPhE4XTU5NTUxLS0pKSUlJR0hHRkZFRkVFRUSFQ4ZCPENDRERCQD48Oz5DSElISksmEhAHBgYFBQUGBg0gJExMTEtLPzBCKxoaFyQgICIRBgcFBQUfTFEvNTk9PJJ+on8EgoKCgaF/AoCBh4IBgf5/AYCHgpmD5IKNgwGAon8DgYOEkoPngp+DkYQGg4ODgoKChIGCgPF/AYGFgrKDoYK4gwGA/3+EfwOAgYGIggOBgICHf4V+hH0Bf4SCBIF+fX2FfgICBACA09PS0Lm6r6WpurCWmavZ64KIjZKSk5OTkpWWl5eYlpWWlJGQjImGg4KEgoGA/fuAgoKFlo3HyPCUiYWDhYiLjZSYnKOjopOJhouSmqCkqKeinJuanJSisbW4va+FlJ+0wtLjlva/vLu5rKOlp6isrrCys7SysrGxr6+urq2tq6k9qainp6ampaOio6OhoKChoJ+dnZubmpucm5ucm5qZmpmampmYmJeXlpSSkY+Khpurp6KqsbKprrGxsrGxs4S0JLWzsrK0s7KysbGysKijrrCurayur6+wr66tqZ+drLVIMTAwLoQWBBUUFBOEEoIRhRCEDwMODg+HDgQdHR4eih2FHIMdiRyQG4IcjxuHHIYdhB4lHR0eHx8eHh8fICEhISIiIyUSEhMTFBUWFxgZGTuvq6WqqamqqoerBKysrK2Erhewr6+wr6ykn6CxtxEDBAUFBQYHBwgICIcJBgoVFhYWF4QYiBkJGhoaGRoaGxobhxqJG4ccBB0dHR6OHYUeCB8fHx4fHh4eiR+EIIUfBCAgEA+IEIYRAxAREYQQDBERERITExQUCgoLC4QMNg0NDg4ODBcVEiEdHTY1M2Rivba2tbO0tba3t7i5uru6u7y8u7q9vL69vb28vL29vcDAwcHCwYXCFcPCwsTFxsbHxcbGyMnJyMnLy8zNzITNVs7Nzs7Pz9DR0tHR09TV1NXT1NPU1NXV1NXT09LQ0M/Pzs/P0NDPz9HRz8/Q0dHS0c7Jys7R1tlucTg6PEEoMDYdISIQDw0MDAsKCgoJExISEBAPDg4Ohg2HDoIPhA4FDw8PDg+GDgYeHh8fHh+RHoIdlQ+FEAEPjxCHDwIQD4cQgB22sqivs7S0tbW1trexp7m7uLbHztPY3N3l7e3x8u7j4uHd1tPR0c/Pz87IurW+vbK0uLmrpqanqaytr7Cxs7e4ubm6uLi3uLW1s7GwsbGxsLCwsbCurq6vr6+urKytrKuqqaenp6alp6inp6empaalpKOioZ+blZCOkp2orLC0MLn0nIKqppmLiJWWgp60yrSwqqqeguiy15qw0Y2wqYGysrnRlpvR0cSApM3g1M/Q2xR+g4OBdHRramtwa2Nug5iVT1FTVoVVaVZVVFRUVVRUU1JRT01MSklJR0ZGiYlGR0ZHTCs7O01MSEdGR0lLTVFUV1tbWlJKSExRVVhbXl1bV1dXWlplb3FxckknKiwyNz1CL4BxcHBwaGNnaWptcHN0dHV1dnV1c3NycnFwb25ubYVrBmpqaWhnZYRmA2VkZIRjhGIHY2JiYWJhYYRgF19eXVtYVlNicGtla3Z4eHd3eHl5eXh5hXoSeXp6eXp7enp5dGxpeHx+gH5+hH0PfHl0amp7gUFHTU5QKCgphCgJKSkpKCcnJyYmhCUHJCQjIyIiIoQhDCAgQD8/Pj49PTw8PIQ7gjqFOQk4Nzc4ODc2NjWENgk1NjY2NTU1NDWENIc1hzaGN4Q4hjmFOiE7PDw9Pj9AQUJCREVHSUtNJygpKy0uMTI1ODtck4p6f4GEgwWCgoKBgYSChYMvhIODgoJ/fHl8h44zLi0rKigoJyYlIyMiISEgIB8fHz08PDw7Ojk5ODg3Njc2NjaFNQg0NDQzNDMzM4UyiTGEMgkxMjIzMzIzMzKEM4c0hjUJNjY3Nzc2Nzc4hDliOjo7Ozw8PT0+P0BBQUEhISIiIyMkJCUmJicoKSkqKywsLS4vMDEyMzU2ODo7Hh8gISEiIyQlJicoJyM9NCtBLytNRD1sXKCMh4aGhISDg4SEhYWGhYaGhoWEhYaGhYWEhISEhYeGAYeEiAWJioqLjIeOC42Oj4+Oj5CPkZGRhJIDk5WVhJaElwSYl5iXhJYJlZWTk5STk5OShJMflJSSk5SUlJOUlZaXl5STlJactGRvP0VNVjM+Si01PIUgJx8fHh0cODc1NTMyMS8uLSsrKiopKCcnJiYlJCQkIyMiIiEhICAgH4QgBx8+Pj8/Pj2KPAk7PDw9PT0+Pj6NH4MghSGDIoQjByQkJCUlJSaEJwgoKSkpKioqK4UsAS2FLm0tdXFmbXBxcnN0c3R0cWt4enh3gISHi42OlJydn6Kfl5SSkY2KiIiGhYSEgHVyenlxcnV1a2dpbGxub3Bxc3V3eHl6e3p7enp5eXh3d3Z1dHV1dHNzcnJxcXBwcG9vb25sbGtqa2pqaWhoaGdohGc/ZWZlZWRhXVpYWmJqbW1ub4Q6KTEuKiYmKSgiLkVyb25ubGBOhV94WmN3TF9bRmZka3ZTP0A7NyA+hZWOiIGBGTU3NzYwMTIvMDEwLzlHUEQgHx8fHh4dHByEHSIcHBsbGhobGhkYFxcYFxcXLSwXFxYWFwUGBQoYGBcWFxgZhBobGxoaGBcXGhwdHh4fHh4dHh0eJzlCRERFIAcFhAYIBwlGRUVFRkSEQh9ERkhJSUpKS0xMS0tKSklJR0ZGRkVFRENCQUFCQkJBhUCAPz49Pj09PDw8Ozs7Ojo6ODg4NzU1MzIwLy0qJzpFOzBOcHWeiHxzcG9wb29wb29vcG9vb25ubGljWUk8PW55mLWYjomFgn11YlBFS3WOgczo8/uChoyOkZWXmZyen6GfnZ2bmpqZlpSSkI+OjIuKh4SDgoD++/n38/Lu7Onn5uQe4uHf3Nva2NfVzs7Mzc3MycbFxcXDw8LAv76/vLu7iLoBuYS6Fbu7u7y9vb6+v7+/wMDBwcLCwcLDwoXBI8LExcfKzM7R1dna4ebs8PX9g4iMkZefp6+4wcW1o5BxdnyAiYFPgoKCg4ODhISGhoeIiYmMlJuet9r17eDXzcS8tK+oopyXko6KhoWC/vjz8Ozn49/d2dXS0MzLy8nHx8XDwsDAv769vbq5uLaztLSzs7KwsYawDq+vsLCxsrKzsrKys7O1hLZxt7i4ubm6u7y9wMLDxMXFyMnMzc7P0dTW2Nzf4OPn6u7z9/r+gIKFh4mMj5KUmJyfpamssbS4vcPHzNPY3ODm6vD3/YGGi42SlpqepKissaWP8MKTzZWD3reU4qHRmo+LiIN/fXx7fH5+fXx9fHx8e3uGeYJ4hHcednV0dHV0c3R0dXR0dXV1dHNycnFzdHNyc3N0dHV3hHYYeHh5e3t8fHx+fn1+fn59fXt6eHZ1dHNziHJ1cXBwcXJzdHV1dnZ5ent8fn+Ehn59huiq6JK01/iWwviZveCAg4SGh4eEhIKA/fn08evm4d3X0czIwb26t7Ovq6ijn5qZl5WSkY6LiYeFhIWGhIKB//38+vj29PPx8fHw8fDy8vP3+fn6+/v+/4CBgYGCg4SEhIOAhIWGh4mKjI2OkZKTlpeYm5yfoaOmqayvsrW4u73BxMbJy87S1dfZ3N7f4eLh34VrXU9QU1NTVFRUVVVSTlVUUU1TVVhZWVlaXFtcXl1eXltaWVhXWFhYWVlYVFJVVUtOUVJKSEhJSktMTk9QUVNTU1RWVlZVVVRVU1JSUVBPTU4KTU5NS0xLS0pJSYRIBkdGRUVFRIdDgkKEQz1CQ0JAPjw7PEFGSEhJSkoSCAcGBgUFBAUGDyNLTExKST4vPR4hHSEjFhoYFCIkKzAfCgYFBQQYQkpCOzY2kH6df4J+hX8EgoKCgaN/AYCIgvx/AYGEgqGD4oKLgwGBon8BgpOD5IKfg46EC4ODg4KCgoGBgYCA+n+CgISBBoKCgoODg4qEp4OZgrqDAYL/fwN/f4GJggKBgId/gn6EfYR+hH0CfoGEggF/hn4CAgQAdtjV0sy3vsLBrYuHqcD7+oKIjI2PkpKTlJaaoKGgnZ6fn5yal5CNi4iFg4D+/vv6/P//gpGIwsTfi4GBgoKAgoKFiIuSnKirqpuVlpaUkIyIiomF/NnAt4Ovtri738GNnbDB0OWW4r68u7iopKenqayurrGzsrGEsB2vr62traqnpqSkpaSko6SjoqGgoaCfnp2cnZubmoWbB5qYmpmZmpqEmSGXlZORjomGpKmmo7C0sKyytba1tbW3t7m4uLi2tLOzsrOEsiOon6yysqysrbCvsLCvrqqhnayzTzY1NRkYFxcXFhYVFRMTE4URhhCFD4oOAQ+EHoYdhByCHYkchxsHHBwcGxsbGpQbhRyGHYQeBR8fHh8ehB8iICEhISIiIiMjJCUTExMUFBYXFxkaHCavrqWpqamqq6urqoWrBqysrK2uroWvDa6po5qms6wGAwMFBQaEB4IIhAkECgoKFIQWhBcBGIcZCBoaGRkaGhobkBqFG4Ucih0HHh4dHR0eHYUeAR+HHoYfgiCIHwQgEA8PjBCCEYYQhBExEhMUCgsLDAwMDQ4ODw4NGBUSIBw3NDNjYLmys7S0s7O0tLW3uLm5uru8vLu8vL29voS9Bb6+v76+hb8jwcHCwsPCwsPDxMTFw8TGxsfHyMnIyMjJycrJycrMzs7Nzc2EzwTOz9HRhNIC09aE1RzX1tXV1NTU09PT0tHR0tPS0dHQ0NHR0tHRz8/QhNIF09HRz86EzCjNzM/Xbjc4Oj0jKhgaHA4NDAsLCgoKCQgIDw8ODg0NDAwMDQ0NDg0NiA6ED4IOhw8EHh4eH4keCR0eHR4eHh0dHoUOjA+JEAQPEBAPjBCHD4kQTiC4s6ersbKztba2uLq4ssPHw8TV3OXr9P6GjpGTlZSMiIX79Ork5Obk4NnPvMG/trO6u6+mqKqqqqyws7a5uru8v769vby7urq3tbSysYSyXrGwsK6vsLCvsK+vraysq6qqqqmmpqWmp6inpqSlpKOioZ2Xko6PmKavsLG1z4L4qaeZi4iXle+GjLqzr6ypj8f3s7zjhZj8q6/E0d3k6N7h7faJ+YqimP343tjY1tpmh4J/em51eHhtXGB6gpuVTVBSUlNUU1RVVlhZWllYWVpZWFZUUVBOTEpJRoyMioiJiotGSSo7OklIREVGRkZHR0hLTlFXXl9gV1RTVFNRTkxOT06YiHlzUGtub3B3PSgsMTY8Qi97hHAIaGVoaWtucXOFdAxzc3JycXBvbm5tbGuEag9paGhnZ2ZnZmVlZWRkY2OHYgRhYmFihWEeYGBeXVtZVVNscWpkcnl5enp6eXt8fHx9fHx9fXx7hnwLe3ZvaHZ7f4J+fn6EfRB6c2preYFJTFFVLCwtLS0uhC0wLCwsKysqKikoKCcmJiYlJSQjIyMiIiIhICAgHx8+Pz09PD08PDs7Ojo6OTo5OTg4hTcCNjeFNgY1NjY1NTSNNQc2NjY1NjY2hDeEOIk5Ljo6Ozs8PDw+Pz9AQUJDREVGSEpMJygpKiwtLzEzNTdIl4x9f4GCg4OEg4KDgoKHgwGEhYMlgX57d4CIliMqKikoJiYlJCMjIiEhICAfHx89PTw7Ozo6OTg4N4Q2hTWENIMzhTKGMQkwMDExMTAxMTGGMgMzMjKKMwI0M4Q0ATWENoM3hDhOOTk6Ojs7PD09PkBAQEEgISIiIiMjJCUlJicoKSoqKywtLi8wMjI0NTU3OToeHyAhIiQlJigpKSQ/NCk8LVJIPm5dm4eFhIKBgYKChIWFhYYFh4aGh4eJhoSFhYYCh4aEhxqIiIiJiYqLi4yMjo6Oj4+Ojo+PkJCQkpGRkYSSB5OTlZWVlpaIlweYl5eWlZWViJQFk5OTlJOElISVP5aXmJiXlZWTkpGRkpCRkpu2aDpCSlM1Riw2QiUlJSMiISAfHh0cNzUzMjEvLi0sLCoqKSgoJycmJSUkIyIiIochBCAgHx+FPoU9hTwHPTw8PT0+PokfAiAfhCCEIYIihSMbJCQkJSQlJSYmJycnKCgpKSoqKysrLCwsLS0thS6APnpzamxwcXJzdHV2dnRwfYF8fYeMkZaepVZdX2JiYl1ZV6WfmZaWl5WSjod5gIB5dnl4b2lqbG5vcXN0dXh6e3t9fX59fnx8e3t5eXd2dXV0dHRzc3JxcXFvcHBvb29ubW1ramtqamppaGhoZ2dmZmZlZWNfW1hZXmZrbG5vdjMwTzEvKyYmKShAKj5wbm1taVd4il9ogk5Xj2dxfH+Hi5SKiZCTUnkqLCxEVoSBgYCBHT03NTQxMjIxLy41QEBEPB0eHx4eHx4dHBwdHh4ehB0NHBwcGhkZGRgYGC8uLoQtBhcXBQYFCoUXAxgZGYQaGxsdHR0bHR0eHRwcHB0eIEI9OzoqPkJDQ0IOBYUGHAlFREVFRUJBQkJDRUZISUlKSUlKS0pJSUhIR0aERQpEQ0JBQkFCQUBAhD+EPoA9PTw8PDs6Ojo4ODg3NjY1NDMyMS8uKylBRDowXnKBmYN5c3FycnJzcnFwcXFwcG9ubmpjWUo8N2Z3kriYjYiEgn5zYk9DT3eNldXy/4aLk5ugpKeqsLO2tba2tbKvrayqp6Win52al5WTkI6MioeGhYOBgP349vTz7+3q6OXi4DHe3t3b2NbPz8zNzMrJyMnHxcTCw8LAv76/vry9vLq5ube5urm7u7q7vLu8vL2/v8DAhME0wsHBv7/Av7+/wMDBw8XExsjJzNDT1tjc4efs8fmAhYiMkZecoqmvtLCplHV2en6AgYGBgIWBVYKCg4OEhISFhYaHh4mOlpqf+rHe1czGvrevq6WgmpSRjoqFhIL/+vTx7erl4N3a19PRzs7MysjHxcTCwcHAvry7uri3tbOzsrKxsK+ura6vrq6trq6Er4SwerGysrOztLS0tbS2tre3t7i6vb/BwcPFx8jKy83R1NXY293f4+fs8vX5/YCDhYmLjpCSlZmdo6mssLa4vMLIzNLX3eDm6fD1+YCEiIyRlZqfpqqlkfTBjbuP+cqh7KTEj4aCf3x7fHt8fn+Af35/gH9+f39+fn18fHt6hHkIeHh4d3d2dXWGc4NyhnOEchhxcXJycXFyc3J0dHN0c3R1dnh4eHl6e3yEewt6enl4d3V0c3JycYhwB25ub3Byc3SEdYB4eHp7fX19e3l1cnFyc3JzeZLzuoKnzvWb4ZrG9JCamZiVko+MiYWC//jz7unj3dfQycPAu7e0sa2no6KdnJmWk5COjY2LioiHhYKA/f3+/vr49vf29fT19PX29vj6/P7//4CBgoODhIKCg4OEhYaIiYqMjY6QkpSWl5iZm52eoHGho6aprK6xtbi8v8HGyczN0NPW2Nrd3uDi4+Ti5XpjVFJTU1RUVVVVVlRQWFhSUVdbXFxeYDEzMjM2NDEyMWFhYF9fYF9eXVtYXVpUUFRUTUlJSktLTU9QU1NUV1hZWVlaW1paWVhXVlRTUlFQT09OToRLDElJSUhJSEhGRUVFRIdDF0JCQ0NCQkJBPjw6Oz9FSEhJSUkSEAcGhAUpBg4QI0tMS0pEMj43ICMqFxgsKjM8Q0NFSERESUsqHQUFBQkaNTMzMzWPfpx/h34Gf3+CgoKBm3+EfoZ/AYGHgvp/BIGCgoKng96Ci4MBgqJ/k4Pigp6DjIQKg4ODgoKBgYGAgP9/iX8BgISBBYKCg4ODi4Skg5aCuoMBgph/iYDdf4KBh4IDgYGAhn8BfoR9gn6MfQd+f4KCgoF/hX4CAgQAG8jAwMm5u7Kbksvug4iMjY+QkpSVlpiYmpmbn4WgVqKjoZmUj4yJhYOBgP/++fr6/IGRh7vBw4b49vb29PLx+oCGkJabnJ6cl5WSh4KD+Obev5mctcTL7Km1t7vPuY2itcba7dvLvbq5tKWmp6iqra6vsLGxhLAjrqyqq6uqqKWmpKSlpaWkpaOioqKgoKCenJycm5qamZqam5uJmiqZl5STkY6IjrGsp6e1trCwtbm6ubm6vL28vLy6t7a1tLS1s7Gxqp+ttLSEriCvr66sqamjo6+ygTE4Hh4cGxsaGRgXFhUUFBERERAQEIcPAw4PD4kOAQ+EDokdjRwFGxscGxyJGwQaGxsajhuGHIYdgx6HH4QghCEBEIQRhBIRExQUFRUXGRscHRe0samqqaqEqwqsq6qqqqusrK2thK4Yr6+tq6ihmKy1JwQEBAUGBwcHCAgICQkJhAoEFBUWFoYXhRiEGYwaARuLGgUbGxobG4UcAx0dHIUdAR6EHQMeHh2JHosfCiAfIB8fICAgEA+SEIYROBISCQoKCwwNDQ4ODw0YFCIcGzQzYl+5tLSytLOys7a3t7i5ubq6u7y8vL28vb6+vr++v7+/vr6/hMAHvr7AwMDBwYbChcMIxMTGxsfIyciExyTJycnKyszPzc7MzM/Oz87Oz9DR0tPT09XU1NXV1tfW1tXU1dSE1QTU1tTUhNM31NPT1NPT0tLS09XW2NfW1dXS0dDPzs3MyMbGxcbHx85pNjY4HSUWFxoNDQwLCgkICAgHBw4PDoUNCw4ODQ4ODg0ODg4Qhg+KDgEdhh6KHYYOjA+cEIMPiBBRERESwbSqqrGzs7W2uLe5uLbK0c3W5fD5hI+WorTFztLbxrqmnJOMhf3z5t/VyMjJxbu/wLmrqq2usbW3ubq9vr/AwL+/vr29vLq5trWztLOyhLFdsLCvrq+urq6vrq6sqquqq6upp6ampaWmpqWkpKWjn5iSjpGeqrC0truszaCtoI+Gl5XwhYK2sq+tm+6r/4fgu7Oyv8nKysvJxsvU4oXw4IKUs8uJmunp2Irn4tvSEXZzdXltcXFmbJGbTU9RUVJShFQZVVZXV1dZWVpZWltaW1tXVFFOTElIR0aMjISKQUZJKDc6Q0aIhoaHiIaFikdLUVZYWFlaV1VTTEpLk4uIfGZjbnZ7imRrbG1xOyctMzg/RUpzcG9wb2RnaGlrbnBxhHIMc3JycXBvbm1tbGxrhGoHaGhnZ2ZnZoRlB2RjY2JhYmGGYodhEWBfXlxbWFVXdHJraHl8fX19hX4IgIGBgIGAfn+Efh59fXhwaHV8gISBf35+fX58enZtb3uAdEBPLS8xMzOFNCgzMjEyMTAvLi0tKyoqKSgoJyYmJiUkIyMiIiEhICAfHx8/Pz4+PT09hDuCOoQ5hDiGN4c2jzUDNjY1hDaCN4Y4Nzk5OTo6Ojs7PDw9PkBBQkNERkdIJSUmJygoKSorLC0uLzAxMjM0NS6ijoB/gIKEhIODhISDhIOHhAGDhIQVgoB7eISKOicoJyYlJSQjIyMiISAghB8MPT08PDs6OTk4Nzc3hDYFNTY1NTWENIQzhzKEMYYwhDGKMgMzMzKIMwU0NTU2NYQ2BTc4ODk5hDpGPDw9Pj9AQEEhISIiIyMjJCUmJicoKSorLCwtLzAyMzU2ODk7Hh8fICIjJScoKCM8L0cwKkpAbFqShIKAf35+gIGDg4WFhpGHCYaGh4eGhoeHh4eGhIcWiIiIiYmJioqLjIyNjY2Oj46Ojo2Pj4aQFZGQkZKSlJSVlJaVlZaXl5eYl5eWloyVAZSElUaWlZWWlZWWlpeYl5eYl5aWlpSUk5KRj42Li4qKjJGtZTtFTyw+KjZAJSYlIyIgHx0dHBs1MzIxMC8uLCsqKikpKCcmJiUkhSMDIiEhhSCDH4Y+hT0GPDw8PT09hx+GIIQhhSKFI4MkhCWAJiYnJycoKCkpKiorKyssLCwtLS0uLi8vLiaIdW5uc3R1dXV2dnd1c4GHhYuWnKRXXmRse4aKkJaEfHBqZGBbr6ikop2QkI+KgYKBem1sbm9yc3V3eXt9fX5/f39+fX18e3t6eXh2dnV0c3NzcnJxcHBvbm5vbm9ubW1sa2pqamkCaWmEaENnZ2dlY19aV1piaWxubm9ORC4vKyclKipCKj1vbm1sYZFkkEyCbGlueH1+foGAf4SHkVSUjFVhdn9NM0NFPS+FhIJ8GjExMTMuMzMxOkhJIiAeHR4eHh0eHh4dHRwchx4YIB8cHBsaGRgYGBcvLy4uLi8YGAUFBQkYhi8bMDMaHB0eHh4dHR4eHh0bHT9BQj0yLS4uL0A6hEACDQWEBg8HEkRDREVFQkJBQkRFRkeESBBJSUlHR0ZHRkZFQ0RDQ0JChEEBQIQ/AkA+hD0lPD08PDs7OTk5ODg4NzY2NTUzMjAvLSsuSUM5PGxzlJKAenZzc4R0dHNzcnFxcXBua2VaSTw0XnSNu56OiISDgn5zVkRSeozkst2BjZahqLG4vMHIzNDR1NPPzMnHw8K9uLSxrKimo6CdmZeUkY6MioiGhYOB/vz6+PPz7ezo5eHg393a2tnV1dPQ0MzMycfIxsXGw8PBwb+/vr+9hbwPu7u5t7i3uLi5urq7vL29hbw8vb2+v8DBw8XFx8nN1Nre4ufv9PqAg4WIjI+SlZebn6CipKeprK6wgbyVe3Z5fYCBgYGCgYGBgIGBgoKChIRFhYeHiIqQmJymtcXGwLy2r6umoZ2YlJCNiYaEgv/79vLu6+bh39rX09LRz83KysjIxsTCwsHAvry7uri3tbW0s7Gwr6+thKyDq4WtAa6Fr2qwsbKxsrKys7OytLW3ubu8vr7Aw8bIyMrN0NbX2Nze4uXs8fX5/YGEh4mNkJKWmZ6kp6uwtbzCyM7T2d3i5+3x+P+ChYiMj5OYnaKdhdqm4peC1KTvnLCJgHx5d3Z4eXt8fX5/f4CAgYGAhH8Kfn5+fHx7enl5eYR4hHYFdXR0c3OFcoRxhHIpcXBwcXFwcHBxcXBxcHFycnNycnNzdHZ2dnV4eHh5eXl4dnZ1dHNxcnGKcAZvb29wcXKFdAd2d3h5enp7hHpLdnRzcG5ta2lmaGttcn/cuYWu3oW8jcLzk6OinpmUkIyKhoL99vHq493X0svGw724tLKtqaagnpqbmJWTkI+MiYiHhYSCgP////z7hPhJ9/n4+Pr8//+AgYODhISEhYWGhoeIiYqMjY6QkJKTlZeZmpydnqGjpaanqquusbS3ur7BxMjMztHU1trc3uDi5Obo56GcaFtVVodVVFNSWFpVVlxeYDIzNjc8Pz49QDw7ODg2NjVramlqZ2NoaGJaW1tWTUxMTE5PUFNUV1hYWVpaWltbXFtaWldWVVRTUVBPT05NTUpKSklJSEhHSEdGRoRFS0RDQkJCQ0NDQkNDQkE/PDo8QEVISUpLJBEHBgYFBAUFDQ8kS0tLSkBbNTwcNzMxNDs9P0NGRUNFR0ooSUstMzw6GwkLCwoNNDU0M4t+oX+Gfgd/f4KCgoF/iH6Of4p+hX8BgYaCAYH4fwOAgoKrg9SClIOhfwGBkoPhgpyDi4QJg4OCgoKBgYCA/3+VfwmAgYGBgoKDg4OLhKODkYK8g5V/kIDWfwKAgYeCA4GBgIV/BH5+fX6OfQN+fX2EfgF/hIEBgIR+AgIEAHKDk6W6yNfi5+jp49zVzMK4rJ6O/Niwi9Kyq6impKinp6ajoZyVj4uIhoWDgYD19fz6+4CQj7fDn4b08e7t8/j+gYSEhYWE/4CChoXz4Nm5ooyKi5awucTIr8+ptLi9+/CSqrvN5ea6wLm4tamho6Woq62Erw6ura6urKuqqamop6empYakLaWko6Kfn56fnZycm5uampucm5qampubmpqbmpqZl5SSkIyHn7WvqbK7vLW7vIS+FL/BwcPCv768u7m4uLe2ta2hqrS0hq4irayrqqemrK+0Zzc9IywyHR8dHBsaGRcVFRMSEREQDw8OD4UOgg+IDgEPiA4HHBwdHBwcHYocjxuFGokbhRyFHYUeCB8gICAfHyAPjQ6CD4UQDxEREhMUFBYXGRwdGmSyrISpg6qGq4Ksha2Erg+rpZyZr7QPAwQEBQYGBgeECIIJhAqEC4QMBg0NGhkZGoYZiRqCG4UaBhsbGxobG4UagxuFHIgdhx4DHx4ejR8CIB+EIAUfHxAQD4UQEg8PDxAPDw8QEBAPEBEREhITE4UKIAsLDA0ODRgUIx03NDNgvbe2tLOysrO1t7m5uLi5ubq6hLsEvL29vYS+A7+/voW/hcABv4fAPr+/wL6/v769vbq4trKyrq6srainpqSko6OkpKWmpqanpqeop6uvsLC0trq+wcPGycrN0NDR0dHP0NPU1dXVhdQM1dPU1NTV1dTU1NPVhNct2NnW19bW1tXU0dLRzcvKyMbEwsLExMLCxWhpNjgdJBUXGg0LCgkICAgHBwcPhg4XDQ4ODQ4ODg8PDw4ODw4ODw8PDg4ODw+GDgMdDh6GHQMcHB2FDokPBBAPDw+aEF4PDw8QDw8PEBAQERARERETbLeuqrO0tba5uru+vr3P083Y8oGPn7LI5pK734uuqtKV5cipl4r/7uHXy8vKw8PFw7Gtra2vsbO1t7m7vby9u7u6ubm5uLi2tLKysbGxhLJdsbCxsK6vrq2srauqq6mqqqqnpqanpqampaSlpKCZk5CVpK6ytLbNkIeupZaFj5aFlY65s66tmt6RxL/Fx8rHyc3b6PD98+33gYuLiJScobPN1trwkOTBwLS+uc7ogC80OkBESExPT05NTElHREE9OTZiV0xCbWBeXFxbXV1eXlxaWFNPTEtKSUhHR4iHi4mIRUkqNTo3R4mIh4aHipBJSkpLSkqRSElMT5WLhXZsX1pZX21yeHttf2Npa2x5SiowNTtCRVJycHFwamZnaGpsbnBxcXJxcXFwb29ubWxrCWtrampqaWloZ4RmCmVlZWRkY2NjYmKFYYJih2ERYGBeXVtaWFVkdnFsc3+AgoKEgwmCgoWFhoaFg4KEgVl/f3xzanV9gIaDgH9/f35+fn16d3x+gGRBUjE+UDE6PT4+PTw7Ozk5ODY2NTMxLy8tLCsqKiknJyYlJSQjIiMiIiEhICAgHx8/Pz09PTw8Ozs6Ojo5OTg5OYQ4Bjc2Nzc2N442Bzc3Njc2NjeENoI3hDgPOTk7PT4/QUJDRCMjJCUlhCYlJycoKSoqKywtLjAxMjQ2Nzk8PkBBQ0VDiZGEfYCDhISEhYWGhouFDIaFhYSAe3uIkDQrLIQtHSwsLCsrKikpKCgnJyYlJSQjIiEgHx89Ozo5ODc2hDWDNIQzhzKGMYgwhzGIMoQzWTIzNDQ0NTU2Njc3Nzg4OTo6Ojs7PT0+P0BAICEiIiIjIyQlJicnKCkpKiwsLi8wMTM0NjgcHh8gICEjJCYnIzsvRS9TSD1mo4iEgX9/f4GAgYKCg4SEhYaGjYcBiIaHhoYDh4eGhYUChIWFhDSDgoB9e3p5eHd0dHJxcXBwbm9wcHFxcXJzc3N0eHh5fH5+gYOFh4mNkZKTlJOTkpKUlJWUhpUGlpaVlZaVhJYJl5aXl5eYl5iZhZc/lpaUk5OQjoyKiYiHh4aIiouZXW9BTCo5KTU/JCQiISAfHh0cGzY1NDIwLi4tLCsqKCgnJyYmJiUkIyMiIiEhhCCGHwM+Hz6GPQM+PT2FH4gghSGGIoIjhCSEJQQmJicnhChOKSkqKyssLCwtLS0uLi8vLzAtVnp0b3Z2d3h5eXp7eXWFioePn1dfbHmInGSDm2B4dIlhnIt5bmjFurWupKGajouKhndxcHBxcXN0dnh5hHsMfHx8enp4d3Z2dXV0hXMIcnJxcHBvbm6GbQhsbGtqamppaYRoRGdnZmNfWlhbZGtubm91NCgwLCgkJykkLT9vb21sXolXdHJ2fH18fYGJkZqlnJiaU1pXV1tgZ3V8en6DTVc8PTdGSU9WCQkKCwsLDA4NDYQMEAsLCwoKChQTFBIiHyAeHh+FIFQfHRwbGhoZGRgYGDAvMTAxGBgFBgUJFy8wLy8yMzUbHR0dHBw3GxwdH0BBPzk1MS0oKCkpKisqNzlAQD88CwUGBgUGCCREQ0RFQ0FBQUJERUZHR0iFRw5GRUVFRENCQ0NDQkFAQYk/Cz0+PTw8PDo7Ojo5hDgYNzY2NjU0MzIwLiwqOElAN1ByfaWLf3p4hHZ6dXV1dHN0c3JxbWdcTD40VnOGu6GPiYWDgoKBgHtxf4aNtJfWiK/mlLnK09zi6Ovu8fDu6ufk4t3X0s7Iw725tK+rpqShnZqXk5GPjoyKiIWDgf/69/Pw7Onm5OPg3d3a2NbV09DQz8zMysjHxsbFxMPBwcG/v7/BwcCEv0W8urm4uLm5urq8vL29vr/ByNHb4Obt9PqAgoWJi4yOj5GTlpicn6Klqa2yt7zBxsrP09fb3+Pn6NXQnIN2e36AgYODgoOIgkyDg4OEhYaIiYySmJu26efq6Onn5uLf2tTOysbAvLizrqqmoZ2Yk46JhYD58Ori29LOy8rHx8XEwsG/vry6ube2trW0s7KxsK+urayrhKp/q6usraytraysrq2urq+vsLGwsLGxtbi5ubu9v8DCw8bKzM/S1dbb3uTo7PD2+oCDhYiLjZGTmJ2ip6uvtLm/x87X3ePp7vX7goSIi4+Slpidm4XUodiT/cmVyeOPg355dnV1dXZ3eXp7fH1+f4B/f35+f39/fn59fHt7e3p5eIV3CXZ2dnR0c3NzcoVxNXBwbm1samlnZWNgYF1cW1tZWFhXWFdXWFdYWVlaWVtaXV5fYWNkZWZpbG1sa25wbm5ub25uhW9pcHBwb3FwcXJxcnNzc3R1d3h6enl4eHh3eHd2dXNxcG9ua2ppaGhnZ2hrbG5zlpHro9SCq4rD+JeinpuYko6KhoH79e/q493X0czIxL+6tbKurKijoJ2ZlZKQjouKiYiFhIOCgoD/gP/9hfxy/f7/gIKDhISFhoaGh4iIiYqLjI2OkJGSk5SWl5ibnJ6hoqSmp6utr7O2uLu+wMTGys7S1NfZ3d/i5Ofq6+zPh25fVVhXV1ZWVlVVU1FZWVFXYDE1Nzs7OSAmKRcbGyIdNzw9PTx4eHZyb25sY2BgXVJPhE4mUFFSVFVWV1hXV1dYWFdWV1ZVU1JRUE9PTk5NTEtLSkpJSEhGRkaERQVERENDQ4VCGUNDQ0E+Ojk7Q0dISElKEQcGBgUEBQUGDySESydAWDI/P0FCREVGRUVKUlRST1EoKiorLC8uMjQ0MTEaFg0NDBQTExOTgYSAln+Ffgd/f4KCgoF/h36GfwF+hH+PfoV/AYGGggGA938GgIGBgoKCq4PEgqGDAYCgfwGCnIPUgpqDi4QIg4OCgoGBgYD/f6B/CYCAgYGCgoODg4qEpIMCgoOKgryDAYCSf4aACIGBgYKCgoGBhYDTfwGBiIICgYCFf4J+j32MfgV/gIGBgYSAAgIEAICiqrzP3+zx9ff5+4GEiI+VnaOrtL3Eys/NxLakkoHeuZTbjbidlY+NioeGhIL3+Pn2+YCUiai+14Dw8fX3/IGCgPz07O3u6+HTx7y8oYmMjJacqbC0trrBwb36srS6w5mIobPG3fvjxbm2ta6foqOlqautrq6trKqqqquqqqinqAWoqKampIejCKKhn56dnZ2chZsjnJucm5ucnJubm5ybm5mXlpKPioqwuLWyxMTBwMXIycjIyMuEzDrGw8G9vby8u7iypq62t7Kwrq+vsLCwrq6tq6ysrKusrbG9Zzg+IywaHh8ODQsKCgkJCBAQDw4ODg0Nhg6ID4kOBR0dHBwdhByHGwEclhuDHIcdgh6JHwQeHh0chQ6GDQIODYYOQw8PEA8QERMVFxkbHxETFhclsK2lqKmop6iqrKuqqaurrKytra2sra6urq2rpJmer1YDAAABAQECAgIGBwcHCAgICQqEC4QMhg0HDg0ODg0bG4QcghuFGocbARqFGwEahxuFHIodBR4eHx4eiR8BHoQfhiCMEIUPCg4PDxEREhMJCgqFCykMDRgUJR4cNDNivbe1s7OztbW2t7i5ubq7vLy7vLy8u7y8vb28vLy9voS/gMC/v76+v7+/vr69vLq6t7Cpo5yWko2HhYD27uji3tzb3uLj4+Pk5Obj4+Tm6uzu8fPw8PDx8fDx8vHy9vX39fT08vHy9Pb6/4SIjJCVmp6iq7G3usLHz9LR09PU1NXU09XW2NrZ2dnY19bX2NfY2NbW1dTT0NDNysfFxcbGxcK/EL6+v77ExmdoNjUcIxMVCwqECREICAgHEBAQDw8ODQ4ODQ4ODoQPCA4PDw8ODw8PjQ4DHBwdhw6OD4YQAQ+EEIIPixAEDw8QD4cQhhFOEkW+tKazt7i6urzAwsK+09rZ7ISPmrDRkpzqq9iLn9KN1JKg2b+Rhf/349vLx77Ew7mvrq2rrK2vsbK0tri4uLm5ube3tbSzs7GxsrGyhLEKsrKwr6+ur6+urYSrBqmpqKmnp4WmRaWkoJqTjpSjrrGzuPm1n66fjoWXkb66xLOysaPzofX36uj2hYyIhIyZsMjLw8nS5pGE1JTC7Yyerri1qZb/1OPTxqmRl3MuMDU8QUNERUZHSSUmKCkrLTAyNDY4Oz09OjYxLChGPTNQO1tUUE5NTEtKSEeIiYmGhkRJKTE3T0aIiIuMj0lJSJCNiYmIiIaDgnx3a1tbXF9gZmlsbnF1d3WTZ2hqbDUnLjM4P0lQdXFxcW5lZ2hpbG1vhXALb25ubW1sa2trammEaARnZmZmhGUHZGRjY2NiYohhh2AdX19dXFtZVlVyeHRwgYaGiIqLiouLiouOjYyMiYeEhgyEg4B5bneBgomFg4GEgIJ/hX4tfX6Ag5dmQlMzRzE+SCQjIiEhIB8eOzk3NTMxMC4tLCsqKSgnJiUkJCMjIyIihCERICAfPj49PT08PDw7Ojo5OTmIOAQ3Nzc2izeHOBU3ODg4OTk7PD0/QEFBQkNDRERERSKGIyokJSUmJicoKCkqLCwtLzEzNDY4Oz9DR0woKy4vTJSJfYKEhYaGhoeGhoWEhhGFhoaGh4eHhoaEgHuAjE0pGYQaKRkZGTAvLi0sKysqKSkoJyYmJSQkIyMiIiIhISEgIB8+PDs6OTc1NDMzhzKHMYgwjTGEMgYzMzQ0NDWENkg3Nzk5OTo7Ozw9Pj9AQEEgISEiIiMjJCUmJycpKSorLS4wMTI0NTccHR4fICEjJCUkQDNMMitKPmaiiISCgYCAgIGDhIWFhYSFhYKGhYcEiIeGhoiHgoaEhR2Eg4OCf3p1cGtnYl9cWVeno52Zl5aWl5eYmpqbmoWcLqCfoaOkpKSlpKWlpaeoqaqoqKamp6eoqKeprbBbXmBjZ2ttc3Z6foSIj5KUlJSElSKWlpaXmJiZmZqZmpmZmJeXl5aWlJSUkpGPjYyMi4qJiYeHhIUth5NdcENPLD8rNyElIiEgHx8eHRw3NDMyMTAtLCsqKSkpKCcmJSQkIyMiIiEhhSCIHwY+PT4fHx6EH4gghiGGIhYjIyMkJCQlJSUmJiYnJygoKCkpKisrhCxJLS0uLi8vLzAwMEZ9eG52eHh5eXt9f397io+PmlVfbHyRanCvaJpbaa9diWJ2oo5xaszFurWjl4yOjH91c3Fvbm5wcXN1dnd3d4R5CHh3dnV1dHRzhXIEcXFxcIRvAW6FbVFsbGtqamppaWhoaGdnZ2RfWldbZGprbW+BPi0vKiYjKCc2R3Fubm5lm2SbmpaUnldbWFVbYW95fHp7eYBNOlI0QU4rMDU2My0mQDlDQD0wKiwOBwgJCgoLCgoJCQoEBQSFBYsGXQoKCRIRHR0bGxsaGRkZGDAxMC8wGRkFBQUQGTAxMzQ1HBwcOjk4NzY3PD9AQT82LSspKSgoJyYmKCoqKUQ+Pj8/DQQGBgUGBhNEQ0RFRUBBQkJDREVFRkZGRUVFRoRFBkRDQkJCQYRAgj+EPoY9Bzw7Ojo5OTqEOYA4NzY2NTU0MzEvLy0rRkc+OWl3lJuGf3x6enl4eXh6eHd4dnRycGtfTkE2VnaEtqKQiYWEhIODg4KFh4mKi42Okby7mNqOzZbM/oaHiIiHhoSC//r28enh29XPysXCu7axq6ilop+bmJaTkY6MiYaDgf779fLv7Orm5ODe3drY2BDV1NLPzs3LzMvJyMfFxcTDhsJRw8TGxMbEvr28vr6/xMvT2+To6u3x9/f3+fz/gYKCg4SGhoiKjY+Rk5aZnqKnrLK3vMLHzdTc4ujv+YCEh4SUmoZzen+Bg4OEhIOEg4SDg4KChINOhIWGh4eKj5WcgN6QkJCOioeEgPnx6uLa0szFv7q2sayopKCdm5eVkpCOjIqJhoL99fDm3NPKwr66uLe3t7a1tLS0srGwr66srKuqq6qqhauCqoerYqytrq+ytbe3uLq9v7+/w8XL0NDS1tnf5Ojt8vX7gYOGiIuNkpWZoKSnrbO4wMXL09ni6PH6goWJjpGWmJ+jmfK6/KCG05vP4o+Efnl1dXV3d3h4eXp5eXp6e3t8fH59fn19hXxde3t6eXh4eHd2dnV1dXRycnBuaGNeWVZTUExKR4mDfnp4d3d4eXp6ent8fHp6en2AgIGCg4GBgoODgoOEhYaGhIWFhIWFhIWCgICDQ0VISkxPUVRYW19hZWlucG9whHIWc3N1dnl6eXp6eXh2dXR0c3NxcG5tbIVrgGprbGppZ2dnaGpvdYyL8qvjisWd2IefoJmUkYyIhYD89e7p5N/a087JxcK/urOtqKOgnpyZlZKQjYuJiIeGhIODg4KBgP7+/4CAgYGCg4SFhoeHiIiIiYmKi4yNjo+QkpOUlZaYmpucoKOkpqiqra+ytrm7vsLFyMvO0NTX2t3gDePm6Ons7um4emlXWFmGWENWU11dWFwwMzc5MyIfLSAiFRs1FB8aIjlCQD9+fXZ2b2lhY2JYUE5NTE1NTk9QUlNUVVVWVldWVlZVVFNSUVFQUE9NhEwQS0tKSUhIR0dGRkZFRUVERIRDGkJCQ0NDQT47OTtCR0hJSkkSCAYGBQQFBg4ihEoqQ185Tk5LSU0oKiooKiwtLTAxLjAxGA8RCAkJBQUFBgUFBQsLDg4OCwkIi4GTggWBgYGAgIp/hX4Hf3+CgoKAf4V+g3+afoR/AYGGggGB+38IgIGBgoKDg4OIhJ+DwIKfg4SEAYKffwKAg4iEnIPKgpiDioQIg4OCgoKBgYC8f69+vn8IgICBgYKCg4OKhKaDg4K/gwGBkX+FgAyBgoOHhoeIioaDgoGEgNB/AYGHggKBgIV/gn6FfY1+Bn+AgIGBgYeCiIECAgQAcs/+hIqPkpaWlpeXl5manqGkp62tsLvM65PKh67U+pKlucvW0bqeg9Kd0eKTh4D39fP1/KfqjaPtgv79gPbv6+Tf3MPOx7ujm5SNgISUoqanq62qqKqgheXRve6ns7i9uoKdr8DX+KHOvbi1sJ6hoaOnq4SsAaqEqYSoFKeoqaimpqOko6OjoqKioJ+enp6dhJxnm5ucnJ2dnJydnJucnJubmZiVkY6KnLm4tr7P0cvO1dnb3t/f49/d3NnPysbDwb6/v7mpsbi4t7ezsrKztLW0srGwsLCvrq6traysra+zv2k4PSQWGQ0NDAsKCQkICAcODg4NDQ0ODYUOhQ+EDgEPhg6GHAYbHBwcGxyKGwQaGxsbiBwHHR0cHR0cHYQeBh8fHx4fH4Qegh2HHBobHBscHBwbHBscHBwdHBwdHR0eHh4fHx4eIIQhLSAeHDCtraiop6apq6uqqaqrrKysraytra2ur66uramhl6exWBMICAcHBwgHCIQJBgoKCgsLC4QMiw2GDgIcHYQehB0BHJIbhRwEGxwcHIYdhR4CHx6HHwEghB+EIIIhhhCCD4YQhQ8mEBEREgkJCgsLDA0NDRgVIRw3NGNfuLa1tLOztba3uLm6uru8vLyHvYO8hb0BvoW9aby8u7u3r6aclYyD9+rg1tHQ0NLT09XW19bW2NjZ29zc3d/g4N/g4OHj4eTm5efq6u3w7+3v8vLw8fL19vX08vDx8vDw9ff39/r59vn4+Pbz8fb4+Pj5+PuDipOYn6iyucPN0tHT09bX2YTYGtnY2dnY19XU09PSzszKycjJyMbFxMTDwsDAhL8KxGc0NhwgEhQWC4QKhAkFEA8PDg+FDo4PlQ6MD6MQiBFWKL62p7C0t7m6u73AwLrR3uL0hpSit+n0mYfL6o+vpY7q1YikjaOPiIP8287ExcXAsrCxsK6trq2wsrW2tra3trm5t7azs7WxsLCwr6+xsbKzsrGwsLCErlWtq6qrq6qoqKenp6alpqWloZuTjpKirq+yuIjIpaubiYyVgIf5t7SyrqGH083o8v762+Dk6tzv/peS+rbzl7fW5NzItaSQ98eSxPWi+oOH4JSI7Y3JBZC1VlNWhFiAV1ZVVldZXFxdYGBhYWJnOEQrNkJMLDI4PUA/ODAoQjNLX0xIRoiFg4SHTEgqMWZJkY9HioeFhH59d4F+dmxmYFxTVF1laGdqamhmZmJUlIqBkV1naGpLJSwxNj5HNnhzc3JwZmdoaWttbm5vb29ubm5tbWxsbGtramppaGdnZ2YBZoRlCWRlZGNjYmJhYIRhg2CIXzBdXFtaWFVgenhzfI2RlJaWmZqbmZqempqZlpGOjIuJh4aFfXJ5hYaKh4WEhIODgoKFgUeAgYCAf3+AgYKEmWpFWDgrOiQnJiUkIiAfHh04NjUzMS8tLCsqKSgoJyYmJSQkIyIiIiEhISAgID4+PT08PDw7Ojo5OTk4OYU4ATeJOAI5OIQ5ETo6Ozo6Ojw9P0FCRENCQUFBiUCHQQhCQkFCQkNDQ4RFhUYWR0dIR0hHR0VBOzRGj4t/goWGhoeIh4WIF4eHh4iIh4iIh4eHg395hYxXNR8hIyUliCYIJSUkJCQjIyOEIoUhhCCFHxA/Pz8+Pj07Ojg2NDIxMTEwhjGHMIUxATCFMVYwMTExMjIzMzQ0NTY2Njc3ODg5Ojo6PD09Pj9AQCEhISIiIyQlJiYnJygpKywtLzAyMzU4HR4eICEiJCYkPC9CLlJEcFmOh4OBgICCg4OEhIWFhYaGhYSGAYWIhoSHhIYlhYSDgoB5dG1nYFuso5qSj46Oj4+PkZGSk5OUlJSWl5eXmJmZmYaaGpucnZ+hoqOkpKOkpKamp6qqqamop6enpqephaprq6uqrKurqqmpqaqqqqtZXmRpb3V6goiNkpSVlpiYmZqZmZmYl5aWlZaUk5OSkZCPj46NjY2MiomIh4eFg4OEhYecYz1JKjUnNEElJCMhIR8eHRw3NjMxMC4uLSsqKSgoJyYlJSQjIiIhISGGIJAfhiCHIYUihCMXJCQkJSUmJiYnJygoKCkpKisrLCwtLS2ELlMvMDAwLzZ/eWx0dnd4eXp8fn57ipGUnlhkcIGntnFrprtvdYJflZJid2l4cGxozKqfkI6Ohnh0cG5tbW5ucHF0dnZ3d3d4eHd2dXV1c3NycnJxcYRyFHFxcG9vb25tbW5ubGtramppaGhohGdFZWBbWVtkbG1tb0NCMC8qJCUoIyp4b25ua2JRfnuJkZeRgIKFiHyIjFA+Wj1MLzg+QDw3MSsmPzMkMk1AfUhLWiomQC57dkZbJyQjIiMiIiIhICAfHyAgICEhISAgHg4OBwgJCgUGBQYGBgUFBQkIDxoZGBkxMDAwMhkKBQUaGjU1GzY2NTU2OjpCPjs6NS8sJycnKSknKCknJiYkJU5LTU03PT4/HQUGBgYFBglERUVGRUBBQEFCQ0RERUaFRYRED0NDQkJCQUBAPz8/Pj0+PoQ9hDwCOzqIOYA4Nzc2NTU0MzIwLyw2TEU9T3V/pJCEgH9/fnt9fHx9fnt6eHVzbmNSQjdTd4azoI6JhoSEg4SDgoSFhoWGh4iIiYuNkJTDxqPpoIPCgZeYlpSRjoqGgv748+zk3tjSzcbAu7Wwq6imop+bmJWSj4yIhoKA/fb28e3r5uXi397c2BHY1tPT0s/PzszKysjIycjHxYTGM8nJycvMysvL0d3l7PX8+fTx7uro5+bm5ujq6+vr7Ovt7Ozt7e7v7e7w8vT08/Pz8PHw74TsGOnn4NfLvKqTmpKKcnmAgoSGhoWEhYWGhoWETIOEhISFhoaIi5CTrtOAkp6mra+xsK+vrqyqqaekoZ+dmpiWlZORj42MiomIhoWFhIOBgYD//fv69+/m3dPMwbe0s7GxsrGwr6+urayJqwGqhalQqKipq6yur7GztLe5u7y+wMPIy87Q1Nvh5env8vb7gYOHiYyQk5ecn6SorbS6wcnQ2N/o7/qChouPlZuipZbxr9iT9Lb7kJyJgX13dXZ4eXqEe4R6hHmGeht7e3x7enp6eHh4d3d1dHJsY15aVE6TjIV8enmEeoV8A319foR8JX6Af4CAgH+Afn+Cg4WGh4iIh4eHi4uKi4uMi4mIh4aHiIeHhoWEhAeCgX+Af359hH4XfHx9QkVJTVNXXWJob3Jzdnd5eXl3dnSEcgRxb25siWsBaoRrgGlpaGdmaGpvdaisjcmApIjE/pialpKOi4iEgPz18evm4dzW0MjAvbm1r6uno5+bmJOQj42MjIqJiYeGhYSCgoGBgoKDg4OEhYeHiIiJioqKi4yMjo+PkJGTlJWXmJqbnaGipKWoqayvsrW4u73AxcnN0NPW2Nrd3+Hi5Obp6+rEA31pVohZQldUXl5aXjM1NTM3OSEfSDsiKy8fISQdHyY+Pz49e3JuZGNiXlFPTUtKS0xNTk9RUlJSVFRVVVVUVFNSUFBPTk1NTYRMC0tLSklISEdISEdGhEUERERDRIRDL0JBPjo4OkBGRkdJJBIHBgYEBAUGEEhLSkpFNCIuLS0wMjAsLy4tKy8xFw4PCAkEhAUTBAQDAwYFBAkSEyoYGBIGBgoONYJ+ln+CgISBiYIDgYGAhH+Ffgl/gYKCf39+fn+bfoR9hH8BgIeC/38GgIGBgoODioSdg+GCAYGffwKAgqeDxYKXg4mECIODgoKBgYCAsX/NfrR/CICBgYKCg4ODiYTkgwGCkX+FgA6Bg4WKiouLi4qFg4KBgYSAy38CgIGHggGBh3+NfgV/gICBgYmCD4GBgYB/f35/f4CCgoGAfgICBAB1i4+Yk46JkpGSlJWTk5ebnpyboKOhoKGgoZ2ZmZaWlZSVoMqjhb3zlLDG0rqS15K3vYaFz8bg3ZiG/+zZuc/W2NCsnJmF5/GLlYSQm5ybmZWViPDV4+rt5+v5g5m0t7qQgJ6uv9b3u9e8uLavn5+ipqmqqqushKsDqqqphKgQp6iop6WlpaSjo6KhoaCfn4SeCJ2dnJ2cnJ2dhJyGmzqZl5SRjpK0uri5zNba5O/6goWHh4iHhID56t/Wz8nGxcW/sLW8ure4t7e3uLe4trSzsrGxsLCxsbGwhq4gsLG2xmw6ICcYGg0LCgoJCAgHBwcODw8PDg4ODw8PEBCID4cOhxyCG4QaCRsbGhobGhsbG4QciR2EHgIPHokdhhwBG5YcBhsaNTMyMIQvDl1aWFdWrayrq6qrqqqqhasfrKytrK6urq2trq6vr6+rppyusbFYV1gsLS0vLzAxGIgZBhoZGRkaGYcahRuFHIMdhh4KHx4eHRwbGxobGoYbhhwCGxyJHYMehB+OIIIhhhABD4oQSA8PEBESEwkKCgsMDQ0YFCIdNzNiu7a1s7S0tba3uLi5ubq7u7y8vb69vb2+vr2+vby8u7y9vLy8u7q4sKackomA7t3QzMzMzoXPZNHR0dLS09TV1dXT09TY2NfX2tra29zb29vc3Nvf4ePl5err6+vp6+zu7e7x8PHw7+/w7/Du8PH09fX08/Tz8/Tx8PL09fb29vn7/Pv8/Pz7/f35+IGIkpumsL3IztPV1tjZ2diF1i3T0c/NzMzMysnIyMfFxcTDw8LBwL69vb6/ZjM1OB4TFgwMDAsKCQkJCAgHDw+JDoUPAg4Plw6IDwQQDw8PlRABEYUQBBEQEBCEEWMSEhITEhISFcO1qKyytLa4ubu+vrbO1tfj/oqXrbTco8rL24+Alo/ni6eTk6OVkIeC4tPIyce2r6ytra2vr6+wsbK0tLW2tre4uLW2tbW0srGxsK+xsbKzsq+wsLCtrq6vrayEqk+pqKenp6anpqSelY+RoK6xtbqR0qinmYeOldrKxLOzsZ6RgoWPiv7+io+cnLG7oemaw97YvaSGz5Clv4P08Ovr9vLL49PP2NTd4qjKs5jxd1NXW1lWU1hWV1lZV1ZYW1xbW1xdXl5eXV5bWFZWVVJQUVJbPS0/TzA4PkE5LUQwQFRHR1pASEdLSY+EgG12en19cGlnWpeaV11UWl9gX19eXFaei42WlpSYmk9TZmhpQCUtMTc+ST17dHNzcWhnaGpsbG1ub29vhG4DbWxshGsaampoaGdnZmZmZWVlZGRkY2NiYWFgYWFgYGCEX4ReMV1cXFpZWFZYdXx5eZSepa2xtFxeXl1dXlxZsqSalpOQjYuJgnd8iYqOjImJiIeGhoWEgwSCgYKDhII7g4KBgoKDhIelckwwRDNFKCclJCEgHx4dHDY0MjAuLSwrKikoKCcmJiUkIyIiIiEhICAfHz49PT08PDuEOoM5hTiGOYU6Fzs8PDw9QUJDRCJEQ0JBQD8+PT08Ozs7hDorOTg4ODc3NzY2NTU1NDMzMzIyMTEwMC4sVFFNSkZCPztwaWJcV6WZi4uEiImJBYiJiImIhokciImHhH6Ji5teZGw6PkJGS09TLC4vLzAxMTIyM4Q0gjWENg83ODg4OTk5Ojo6Ozs8PDyEPQk8PDw7ODY0MjGHMIMxhDAEMTEwMYUwTjExMjIyMzM0NDU2Njc4ODk5Ojs8PT4/P0BBISIiIiMjJCUlJiYnKCkrLC0vMDI0NzkdHyAiJCYlPTBALU9BaqSKhoSCgIGChISFhoaHh4SGhocFhoWGhoaIhQ+DfXZvZl9Yo5ePjo+PkJGEkAORkJCGkYSShJRWlpaXlpeYl5iYmJmZmpqcnqChoqGio6Slpaanp6mqqKinpaalpqeoqaepqKmqqampqqqpqausq62trq6vr66trq2rq1hdZGx0fYWMk5eWlZaVlpWVlZSEkjmRkI+Pjo2Mi4uKiYmIh4eGhIOBgYKEjFs6R1QzKDciJiQjIiAfHhwbGjMyMS8uLCsqKSgnJiYkIyOEIoUhhiCKH4cghiGGIhwjIyMkJCQlJSYmJicnJygpKSkqKisrLC0tLi4thC5OLzAwJIJ5b3J2eHh5eXt9fXiJjpCYqFtmeoGrdpaXq3F3ZIGSXHVnaXJraGJgopeOjYp4cm9sampsbW5wcXJ0dXV2dnd3dnR0dHN0c3NyhXERcnJxcXBvbm9ubm1tbWxqammHaEdmYl1ZWmJrbW5wRUUwLyolJyg9S3Fvbm5hVktMUk+Rk01SWVZYSTdJLzpBPDQsJDgnMUlBgoB/g4iFdYB7fYCBhoVCOTEmYkslIyQjIyQjIiEiIiAfICEhICAhIiEiISIiISAfHx8dHBwaGQ0HCAkEBQYGBgUKCA8aGhocDAoKGRs2NjczNjg7PTk7OTFMRyUmIyaEJxIlJSRMS0dERERDOhwtPD09HAWFBgYJRERFRkWFQQNCQkOFRYZEC0NCQkJBQEBAPz4+hj0DPDw9hDwBO4U6Gjk5ODg3NjY2NTQzMjEvMEhMRkRufZWej4lDhkIYQYN/fn16dnFnVUY6UXqJt6ORjIiGhISChIFVgoODhISFhoaIioyNkJKUmufpuYPHqvWbop6ZlI+KhoOA+fTt5t/W0czHw7y3sqyppaGdl5SRjouIhYGA/Pjz7uvq5uPg3Nza19bV0tLS0M/Ozs3NzYTMTs3NztDT2eTy+fyA/Pfy7urk39rW09HPzMvKycfGw8C/vru4trSxr6yppqajoJ6bmZeRioHv3My+rp6Pg+fQtZ+L6buNi4CHiIeIiIeHhoSHg4aGhTSGhoaIioqK1LjQ842fssXY7f+Ik5uhpaisr7O2uby+wcPExsnMzdHT1dfa3N/g4+Xm6Ovshe4U7evn39fNwravrayqq6qqrK2srayEq0+qqampqKenp6mpqq2usLO1t7m9v8LGycvO09rf4+js8vj9gYWIio6Sl5ueoqeqsLe9w8rR2+Tr9v+FipCWnKSa9a7Tj+mq2+COhX56d3h6hXyFfQZ8e3t7enqEeR14eHh5enp6eXh2dHBpYltUTZCGf318fHx9fn+AgYeCC4GChISDg4SFhYWGhIgxiouKi4yLjI6Qko+Qk5ORkJKUlZWVlJeWkY6Nj4+Qjo2LiYmIiIWEg4KCgH+AgIGBgYWAA4KBgYR/FH5BSE9WXGFobHBxcXFvb25tbm5timuAbGxra2pqamlpaGdlZmpqcYSNgb79noHCgJqcmJSQjoqGg4D48u3m39bQysTAvLWuqqSenJqXlZORkI+PjYuKiIaFhYSDgoOEg4SEhYeIiYmJioqLjI2Njo+QkJKTlZWXmJudn6Gjo6aprK+xtLe6vcDDx8vP0tba3N3d3uDj5ukI6umZjmpYV1mFWkJbWVNeXlpeZTU2NzA5IzEuWSQvHTQkGCEeJjg5OTY3aGNdXlxQTEpJSEhKSktNTk9QUVFSUlNTU1JSUVBQT05OTEuETAVLSkpJSYRIA0dHRoRFAUSGQ0dBPjs5OkBGRkdIIxEHBgYFBQYOIklKSko+KhkXGBguLRcYGRkZDwgIBQUGBQQDAwUFCRITKikpJiYmIywpKy4vMDAKBgYFKKN/BICBgYGGggOBgYCEfwWBgYF/f4x+gn2Lfoh9AX6EfwGAh4LPf4iArH8GgIGCgoODioSbg6eCAYOpgoiBhYCif4OAh4HhgpeDh4QHg4OCgoGBgK1/4H6ufweAgYGBgoODi4Tgg5J/hIAOgYKDhYqJioiKhIOCgYGFgMh/AoCBhoICgYCKf4J+hX8DgIGBh4IFgYGAf3+OfgWBgoKCfwICBAB/yqeim5SOkZKSl5mZl5menZ+bmpyenZ+hoZ2bm5eUkpCNjIqJh4WDg4mh/fbA/pamovuth7jAjYPq3tzc1djQuI+PgvK7w4COjI2PjvTSwbHCy9Tjh5OP+N67x5WzuL+fh6Cwwtv9rtC8t7SsoKGlp6ioqqqrq6uqq6uqqamop4WmCqWlpKKioaGhoKCEnwSdnZychZ2EnC+bnJucnJuamJaSj42jvby8ydrk9Iqkt8jKxcC/tK2kmIf9693Y1NLMvr7Hw8C8uoS5Jri2trSzsrGxsrKxsrKysLCwr6+ura2srK2vsmQ4PSQXGQ0MCwoJhQiJEIkPhw6DHIQbixoFGxscHByFHQoeHx8eDw8PHh0diByGGwQaGxsaiRsZGhozMTAuLVpZWVapqKqsra2srK+vr66trYSsEq2trKusrKyrrKysq6usrK2urYSuCq+wsK+vrrCxsrOEsoS1ELa3tre0s7NaXFsvLzAwMDGMGYQaChsaGhscHBwdHR2KHgMdHRyFG4cchB0HHBwdHR0eHoUfhiABIYQiBCEgISCHEAERhRABD4QQgBEREhIJCgoLDA0MFSMdNjRkvrm1tLS1t7i4ubq7vL2+vby7vL29vr++vr6/vr29vLy7urq4rJ+Uiv7n2M/P0dXU0s/Nzs3Nzs/Oz9DQ0M/Ozs7Pz8/R09PS0dHS0tLU1dXW1tna29ra3d3d4eHh5Obn5uXm6enq6+rr6ujs7OrqB+np7evr7u6E7Sjs7O/v7vDx8fDx8vX3+Pr7+fv6+vn29vf5+Pf39/n7gImTn6q3xs/ShNQw09HQz8/Ozc3LysnIx8bGx8bExMPDw767uri4ur9kMzccHxMXDQwLCgkJCQgICA8PiA6GDwMODg+TDogPlxCFEQEQiRGCEoYTQxbkuqyrs7S2uLm7vb21yc/Q1+b4hpShspeHuIua/8i7lvWjhqKRh4H+4NPFxse+rKmpqqytrrGztbOzs7S2t7i3t7aFtWezs7KxsbGysrKxsbCwsK+vraytrKurq6qpp6eop6emopqSkZ2rsLS4gc2pp5eFkJO7oLmvr62ciNzy/oyXnJvR6MWNvOLdvJbbhoach/Hi6PP7gOjY0cO73NXU2OLS3dzExJr74MmRd2ZiYFxZVldYWFtcW1paXV1eXFxdX19gYWBeXFxZV1VTUE5MS0lHRkZIT2lbQlMwMzBKNi09Py9Egn18fXp/fXdhYlWcfIBOV1daWVaXiYB5fYOIj1FbWp6MdnVSZWdpRCcuMzhBSzp5c3NzcGhoamtra2xtbm5uhW0bbGxra2tqamppaGdmZmVlZWRjY2JjY2JiYmFhhWA8X2BfX15dXVxbWllXVlRUZ358e5ClssBmcn2IioiEg356c2pesKaempeSjIGEkZCUkY6Mi4qJiIeGhISDhIJJg4KDhISDg4OCgoKBgICBgoleQVY6Lj8mJyUjIiAfHRwbNDMyMS8tLCsqKSkoJiYlJCMjIiEhISAgHz8+PT08Ozw7OTk6OTk5OoQ5hDo6Ozs8PD09QEJFIyMiQ0JBPz49PDo6ODc3NjU0NDMzMjIyMTAwLy8uLi0tLCpQS0ZCPXBnX1eekpCSkoWThZQQk5KSkI+OjYyLioqKi4uLioSLBIqKi4uFig+Li42Ojo+QkZKSk5OTlJSEkzSXpFpiajk9QkZKTykrKyssLC0tLS4uLi8vMDAxMjIzMzQ2Njg5OTo7Ozw7Ojs7OTc1MjAvhDCHMU8wMDEwMDEwMTEyMzM0NDU1Njc3ODk6Ozs8PT4/QEEhISIiIyMkJSUlJycpKiosLjAxMjU4HR4gIiQmITRHLlFDa6OLh4SCgoOEhYaGh4eHiogEh4iHh4SGGoWEg4B5cWhgs6SXkpKTlJOSkJCQj5CQkZCRhJCCkYSSgJOUlJOUlJWWlpaXl5eYmZmam5ucnZ6enqGipKSkpaanpqanqKmpqaqqqailpaampaSkpaWkpKWmqKmoqqmpqqqrrKusrq2sra2srKyrqqytrKysrq5YXmZud36GjpCRkZGSkpGSkZCPjo2MjI2Mi4qKiYmIh4WEhIF/fn5/gYdaIjpIKzgrPCUmJCMhHx0cHBozMTAuLCsqKScnJiUkJCQjIyOEIoMhhiAFHyAfHx+IIIchiCIHIyMjJCQlJYQmRScoKCkqKiorKywsLS0uLy8vMC8wMDEvoX10c3l5ent6enx7doaLiY+aplplcXtuY4NjarCLhGivdFxrYFlVqpSMf4KBeYVqGGtrbW5vcHFydHR0dXV2dnR0c3JzcnNzc4ZyDnFxcW9ubm5tbWxsa2tphmhJZ2RfW1pgam1ub0BCMTAqJSgpN0Rxb25rXk17ipBOUlRTZFhBLDpEPzQqPSYtS0iCfH+DiEZ/d3Jsant4en6EfIKBeH9gXz84JQEhiyI8ISEiIyMjIiIjIyQkJCIhISAfHh0cGxoZGRgXFxgXGQ8KCwYGBgoKCg4MCho1NjY4Nzc5OzIyKUw3OCIkhCMVSUxNT0tGQ0EfISA6NC0uLzw9PRwFhQYGCUREREVEhEAZQUFCQ0NERURFRENDQ0RDQkJCQUA/Pj49PY88hDscOjk5OTg3NzY2NTQzMzIyQFJNSF56h5xJSUlKSYVHGEhHRIN/e3RpWkk/Tn2Mv6qUjYmHhoSCgYV/U4CBgoKDhISEhoiJiouNj5OWmqmkmOakk+WWo5+ZlI+Lh4OA+PPs5t/Z1M7Gv7m1sKmmoZyYlZGOi4eEgf369fDu7Ojm5N/c29nW1dTU0tLT0tLShNNL1dvr9/+Dg4D68+3m4NnSzMfCvbm2tLCtq6qop6Shn52amJaUkY+LgevUvqaQ99GriNOxpKOipKSlpqSjoqSioJ+dnJiXk4+NjIuJiYiGh4CIh4aHh4mLjZCRlJiXmZmZmpuamZmcrdeNsduDmK7H3faHkZWXmZucnqCjpKWmqayvsrW5vMDDyM3R1Nne4ePk5OLi39bLv7Orqamrq6usrKurq6qqqaiop6ioqKqpqq6wsbW3ur7CxcnK0tjc4ubq8ff8g4aIi4+UmZ6ip6mwtiG7w8jN1d3n8PqCh42TnJ2Ave2U8qvZ15OFf3p4eXt9fn6EfwV+fn19fYR8Mnt8e3p5eHh3dnZ2dW1mXlefkoiFhIWEgoSDhYaFhoaGh4mKiouMjYyMjZCQj5GTlZeYhJkxm5ydn5+goKCioqOipKapqqmsrKuoqqioqqqoqqinp6ejn52bmZeUkY6MiomIiYiGhISFH4aGhoWFhoWFg4KCgoGDhYeHh4aHh4RCRktRVVtjbG2Ga4hsgGtqamtra2ppaWhoZ2ZkZGZpbn2Fg8eEqpPZjZ+dmpeSj4qFgfn07uff1tHKxL23sKumpaOgnpuYlZORkI2MiomIiIeFhoWFhoaHhoiIioqLi4yNjY2Ojo+QkZKTlJaYmZuen6Kkpqmrq66ytbm8wMLEyMzR09jb3uDi4+Tm6evtWdDlcF9aXVxcXFtbXFpTW11aXGFmNDQ3NSshLR8fMyklHzcoJDUzMTBiWlhRU1ZPRkZFRUVGR0hKS0tMTU5PUFFRUlJRUVBQT05NTUxLS0tMS0pKSklJSEhIhEaERSlDQ0RDQ0JAPTk5PkRGR0gjEQcGBQUEBg4hSUlJRjQfKy0qFhcXGBkQCYQFIQQDBgQIFBQoJycoKRQmJickIiQjJyswMC4vLjklDQYGBat/BoCBgYKCgoaBAX+LfoN9hn6IfYN+hH2EfwGAh4LMf42ArX8GgIGBgoODioSZg6CCg4OggoWBhIDBf4OAhoHSgpaDh4QGg4KCgYGAqH/xfqp/B4CBgYKCg4OKhN2Dk3+EgAWBgoKDg4SCg4GEgMh/AoCBhoICgYCGf4N+hX8CgIGGggWBgYB/f4V+AX+PfgV/gYKCggICBAAVybKno56alpaXmJmampmcnp2fnZ2ehKRbo6Oko52al5SSkY+KhIH/gP7+gIODg4vf25PMz8GvhsrX4LSwqZHi2en9gPPQ4N/Osq/N0sbe7oGCgfPYvbKVqr30obO4veiRprXJ5IDRxLi0sqagoaSlpaaoqoWpEqqpqaimpqWkpqWmpqSjoqGgoIifBp6fnp6en4meOJ2cm5uYlJGNmbrDxMvY5PWPr96Tt9zZ49e8p570rJiNhPrs4dDIz8rBwL++u7q4uLi2trSysrKxhLAGsbGwsLCvhK4RraupqKaoqK3ANTsiFhgNCwuECgcJCRMTEhIRhRAFDw8QDw+HDoMNjxoDGxwchB0LHh8QEA8PDx8eHR2EHIMbhBqGGYUaJzQyMC8tWldVpqiqqKeoqKmqq6mpqqqrq6qrq6ysq6qrq6ytrKuqqoSrDayrra2sraysra2trq6HrwSur66vhbAFsbKztLWEtha3t7a2trW0tLW2tbSxsVlbWi4uLi8YiRkEGhkaGoQbGBwdHR0eHR0dHh4eHx8eHRwcGxscHBwdHIkdBR4eHx8fhCCCIYUiBCEiIiGEEAERjRAWERITFAoKCwwMFhIeHDVmYr27ube3uYS6Cbu8vb6/v76+vYm+gL28u7i0ppiL+uXSzM/Oz9DPz8/Q0tTU0dDQ0M/Ozs3Oz8/Ozs3Nz9DQzs3P0dLT1dXU1dXW2djZ29zc3d/e4+Pk5ebo6ujr7Ozt7Ozs7uzt7Ovs7Onq5ufs7Orp6ejn5efq6+vs7u3s7e7v7/Dy9PTz9fPz8/Hy8/Pz9fn4+Pv7Gvn29fX2+Pb5hZKerLrHysrOzs7NzMzLy8zKhMgtx8bFxMPEwb69vLu6ubu9v2c1Nx4lFRcLCwoKCQkICAcODw4ODg0NDQ4ODg8PlA6KDwUQDxAQD5AQjREIEhIREhISExOEFFkVR72zqbS2t7i5uby7ssPKwcfW4e+AkJ6mrLbN/IGMk4a1mZGH/vrq6djJvsC/qaWkpKSlp6qtsbW2tLO2uLi4ubm2t7W0tLW0srKzs7Kys7Oysa+urq2urYWsAquqhKlLqKaelpOaqrK0uNewpqybiY6Rr464tLCqnpmN9/qFlqKohrno2bGDno6eiODe38PL9PjY0NLZ4Ovu0r22sa63vri71YGMlaPbqNvNCjRjYF5dW1laWluEXAFdhV8XYGRlZmRkZWVjYF5cWFVTUE1JR41Ij4+ESEFJXksuPkE9NipCV35vbmddlI6Rn1Gbg4uKhHp5homBiZNOTk2UiHt3Zm95jVhlZ2lUKjA0OkQnTXVxcXFrZ2dpa4VqgmuEbIRrCmppamhpaGdmZWWFYwliYWJhYmJjYmKEYUNgYF9fX15dXFtaWVdVU1FafIOBiai2zm9+lmF+lpOZlYN0b614amFcsKmcj4yXlpeTj46MjIuKiIeGhYSDg4KCgoGBhYI+gYGCgYKBgH9+fX+AhKM8UTQpOyQmJSMhHx4dHDc1NDIwLy0sKiopKCcmJSQjIiIhISAgHyA+PT48Ozs7OjmGOj87Ozs8PD09P0IiIyMjIkRDQT89PDo4NzY0MzIxMDAvLi4tLS0sKysqUkxGQDtrYledlpWUk5KRkZCRkpKRkZGJkAOPj46EjQKMi4SKBIuLi4yEi4WMhosMjIuMjY2Ojo6PkJGRhJIHk5SUlZaWloSXJpiXl59YYGo6P0RIJigoKSkpKioqKyssLS0uLzAxMjQ1NTY4OTk5hDoDNzUyhTCGMYIwhDFgMjIzMzQ1NTY3ODg5OTs9Pj9AQEIhISIiIyQlJSYmJygpKiwsLS8yMzU4HR8hIyM5KzQrSHJYkYyIhIOEhYWHh4iJiYiIiIeIiImIiYmIiIiHh4aGhYN/dmxgsJ6RjpCRhJKFkwSSk5KShZFGkpKTlJWVl5aXmJmZmZqbnJyeoKGhoaKio6Olpqepq6ytr7Cztba0s7SztLW1tra1tbS0s7SysLCtraunpaSkoaKjpKWlpoWnh6gBpoSnBKapqayFrhCvrq2trKypqltjbHV/iYyNho4DjY2MhIspiomJiIeGhIOBgH9/fn2AhZFiP1AtRDNCJSQiIB8eHBsZMjAuLCsqKCeEJgklJSQkIyMiIiKEIZAgiSGHIl0jIyQkJCUlJiYnJycoKSkqKiorKywsLS4vLy8wMDExMTJKgXpyent7e3p6e3tzgIR/hY+Wn1VdZm11eoquWWBoX3hlX1ekoZaYiX5zdnZmY2NkZWZnaWpsbnBycXOHdQdzc3N0c3Nzh3JdcXBwb25ubW1sa2tramppaWloaGdiXVpeaG1ub3c7MDErJicoNUFvb25rXlhPiotJT0M5KzpEPjMmLy9MSnp4fXJ3jI14dXZ3e4GDdWtnY2Bqbm12ilVaXGB8Oj45BgkjIiIhIYciCCMjJCQmJSUlhSYRJSUkIyIhIB8eHRsaGTIaNTaEGz8ZGg8JCwwMCwkQHzs5OjUySUBAQyFAOTo7PUBDQkJDQEEfHh04NTAtKCkrOjQ9PT0bBQUGBgYDEkJDQ0RBPz6EPwRAQUFChkOCQoRBEEBAPz49PDw7Ojo6Ozw8PDuGPAE9hDw4Ozs6Ojk4NzY2NTMzMztUVlFWdoCVSEhGJS8wLi8wKycoTUhFQz91Y1FETn6PwayUjImHhoSCf36HfUd+f4CBgYKDhIWHiImLjZCTlpuhqPSB0paE2pSinpiTjoqFgv/48uzl3dbQy8O+t7CrpqKdmZORjomHhIH++fPv7evm4+Hf3YTaVNnY2NnZ2dzk9IGGiISB+fHq4tnQyMC5tK+rpqOgnZuZlpSSkI6Miob538Gli+SxgbujoaGhoqGho6Gko5+enpuYl5iZmpiWlpWTko+OjY2NjIqJioaJBoiJiIiIh4aID4eGhoWGh4iJiouMjo6Oj4SQQpGRkI+OjY2NjIuKio2wgbLljanF44CKjY6QkZOVlpianZ6hpamssba7wMTK0NTa3N3d39zQwrOrqKmpqqqrqamoqYaoQKurrrCxtbi+wsXIztPY3uLo8Pb+goWJjZKYnKClqK+2vMLIzdXb4ev0/ISJkZmN25KpgsL9gaKTiYB9fn9+f3+GgAl/f35+fX19fHyEe3d6eXdyaWBYn5CFgISEhoeHiIiJiouMjY+PkJCRkZKXm5yfnp6go6Olqq2vsbS2vL6/vsLHyMzPz9XW2Nne3+To7Ojq6+vs8PDq6unn5uHl5uDb3tnUzcfAurStpJyYlpORkI+OjIqKiouMjIyNjIuKioiHiIiJiYaLGImIiIN/f316eHhARU1UXGRoaGpqa2xsbIVrhmqAaWhoZ2VlZGRjZWt1kaSW2o7Ss/+foZ2Zk5CKh4L79O7o3tXOyMC6uLWwqqekoJyalpSSkZCOjIqLiomIiIeIiImJiYqKi4uLjIyNjY6PkJGTk5SVlZeZnJ+hoqWoqqyur7O3ur7BxMfLz9PX2t3f4uTn6Onq7Oq9emZZXl5dXVxFW1tZUlhaVFdcX2IyNDU2Njk7RCIkJyc5NDMyYV1XWlRNSUxMQkBAQEFCQ0RGSEpLTExOT1BQUVBQUVBPTk1NTUxLTEtLhEoMSUpJR0dHRkZGRUVFhkRKQj87OjxDRkdIRxEIBwYFBQYOIUhJSEQwHRcrKRUUDAkFBQYFBAQFCBQVJicnIyQoKCUjJCQkJSUkIiIhHyMkKDA1ICYnJCQHBgYBgaZ/BH5/fn6GfwGAhoECgH+FfoR9AX6MfYN+iH2EfwGAhYICg4HLf4OAiYGFgK5/BYGBgoODiYSZg5iChYOagoWBg4DXf4OAhIHHgpaDhYQHg4OCgoGAgKR//34BfqZ/B4CBgYKCg4OJhNqDAYGTf4iAhIGEgMp/AYGGggKBgId/Bn5+f3+AgYaCBIGAf3+YfoV/g4ICAgQACMHJr6aioJ2chJqAnJucoKGkpaepr7G2t7u+v7u1sKukoJmUj4qFgYCCg4Lu4fr58vSA0u7Zy/O18PLB/seQ+uzm8+y+mp6nosPv/v/78tO6q5eXm7XIz8rAtrC0uMLDm6y+1fTWnbq0sayfoKOlpaWmp6inp6ioqampqKinpqWlpKSko6ShoaGgoJ9Onp6dnp6en56dnJ2dnZ6fn6Cgn5+enp6cnJmVkY6Wt83S0tzi7oSs35DMseX41uK8oeOxtIuvmYqA6drTzsfAvr69vbu6uba1tbSzsrKwhK8urq6ur66vr6+urayqqqimpaSlpqmtvGw7IiwaDg0NDQwLCwsKChQTEhIREA8PD4cOHg0NDQwNDQ0aGRkaGhoZGhoaGxsbHBwcHR8PEBAPD4QeCR0cHBwbGxoaGokZCTMwLiwrVFOhoIShBaKjpKWnhaYZqKenqKmqqqmpqqqqq6qrq6usrayrqquqqoSrBaysq6ythq4Cr66Er4qwE7Gys7S1tra2tbW2uLe1trW1tbaFtRC0tLSztLW1sa9YWC0tLjAYihkHGhoaGxsbHIQdDh4eHh8gHx4dHBwdHBwchB2DHoQfBSAgISEhhiKDI4gRAhARhRCADxAQERISCgoLCxgUIx03NGTCv767uru8vb29u7u6u7y9vr6/vr+/v769vL29vLu3q52M/ODMycvLzMzLzM7P0dHQzs3Nz83Oy83PzsvNzczOzs3O0NHR1tfW09bW2G1ub3BwcXBxcnJxcXJycnd2eHd3eHp8fXt6enp7enl5enh1eHl5enl3eXl4eHl36+rk4OHg4uPi5OXn5unr6+3v7/Hw7u7t7Ovs7ezu7O7v8vT29vX29PL09vb18u7t6+nr/YqZqLnGycrLy8vKysrLysjHxcXFw8C/vry8u7u8vb2+w8nObTgdIBMWDAsKCgkJCAcHDw8Pmg6LDwMQEA+QEAMREBCMEYQSgxOGFIArwLSnsba3uLi3trWsur+4wM3a5fDz94GDiY+YnJeUjoL57ubf2dPY0sLBwbWko6Kjo6Wlp6mrrrGys7W3tra3t7a1tbS1trW0srKztLSysbGwsK+trq2urq2trayqqqmpqaiknJWWpbO2ucSDm7KikpaSso+6tLKrlfSGjpSMpS6LyeXGlLuonoj+59jd+eTLw8POucXTxa6Vpbu2oYiBg5Wru8yNo6CirLPDq9DTCTRCYmBfX11cXIRdZVxdYWJkZGZobG1xc3Z5enhzcGxnYl1aVlNPTEpLTEuKgI2MiIhFQkhAREszREY6UEhNko6OmJR6aWpub3yWnp2dmId7cWdoZXJ9hYaAY2BkZ2o9LTI3P0pDSXBubm1mZmhqaWlohGkBaodrYWppaGdnZ2ZmZGRjYmFiYmJhYWFgYWFiYmJhYWBgYF9gX19eXVxaWlhVUlBUdYqNkLLA13J/mWGMeZ+9lKCGb6N9gWN8a2BYm5SYmJqUkY+NjIuKiIeHhoWEgoKCgICBgYCKgTOAf39+fX19f4GFm3NPM1E6JSYkIiAgHh0cGzQzMS8uLCsqKScmJSQjIyIhISEgIB8fPT2EPD07PDs7Ozw8PD09P0IiJCQjI0RDQT89Ozk3NTMyMTAvLi4tLCsrKilRS0U/OWdcp56enp2dnJybmJaTkZCNhIwCjY6IjwqOjo6Pjo2NjIyMhIuCioSMC42MjIyNjY6OjY2NiIwIi4yMjY6PkJCLkTaSk5KTlJSVlpeYmJqZmJeXl6BZYzc8QkgmJycoKCgpKSoqKywtLS8wMjM0NTc4OTo6Ojs5NjOEMIcxATKEMzY0NTY3Nzg5Ojs8PT4/QEIhIiIjJCUlJiYnKCkqKywtLy8yNDYcHR8hPzJELU9AZZyUjoqIiImGiAaJiYqJiYmEiAGJhIhFh4aDem5isJyOjIyNjI2OjpCQkZKSkpOTlJSVlZWWlZWVlpaXl5mbnJ6foKOlpqiprVhZWVtdXl9fYGFjZWZnaGhqbG1uhHBRcXFycW9ucG9vcG5ubWtra2lpZ2ZjYLetp6Cfnp6fn6Cio6Kjo6OkpqeppqWlpKSjo6SmqKioqayrrKqqrKyrraypqaShoJ2bnahfaHSAiYuLhIw5jYyMjIuKiYiHhoSEg4KBgICBgoOFiI6rc0ksNis7IyMhHx0cGhkYMC4tKyopKCgoJycmJiQkIyMjhCKFIY4ghyGFIoUjWiQkJSYmJicnKCkpKSoqKissLS0uLi8wMTIyMjMzO4N9cXd5enl5eHh3bnh7dnuFjJObnaBSVFhcZGVkYl9VpJ2Yko2FiIJ3dnZtYmFgYWJkZWZoam1ub3Fyc4Z0hnOGcg9xcnFwb25ubW1ta2tqamqEaUxoZmBcXWVtbm9yLy0yLikpKDhCb21saFeKSk9POjgtPkM5Kzg6TEiLgnt9jX9zbXB3a3J5b2FUXmlrYFBOUFpmb3tXYWBgZGpyXUM8BgURJCMjI4UiViMjJCQkJicoKCkqKisrKywtLSsqKSgnJSMhIR8eHh4dHTk2Ojo3NxsLCwoODgcICAgNEiJEQT5CR0NCQTxBOzw+Pz47NjMtKSoqKi4vLCoyOjw9PgwGhAUbCCBAQEBBPT0+Pz8/Pj4/P0BBQkRERENDQkFAhD9RPj48Ozs6OTk6Ojo7Ozs8PD08PD08PDw9PT08PDo5OTg3NjQzMzlOV1ZXcoORSUZAIyoiKz84JiAaJyElKElANitJT3qOvquUjIeGhIOBfn17hnqAe3t9fX1+foCBg4SFhoeJjI6Qkpaeo6qx2efJkvzTkaOel5OPi4eDgPnx6+Tc1s/Jwbixq6ainZiUkI2Kh4SA/vny7urq5+Pi4d/g3t7e3+T3hIqLh4P99ezh183CuLCppKCdm5iUkY+NioiF+96+noDEjryamZudnqCgn52dnJkslpSTk5KSlJWWlZSSk5KTk5OSkZKRj42Mi4yLi4uKiomKiomKioqLiomJiYqGiQOIh4eEhgqHiIiJiYqKiomJhIgCh4aGhTWDg4OCgoKBgoOEg4e4kMqEpMTogoiJi4uMjY6QkpWYnKGmq7G3v8bO1Njb3N7e08W2qKWmp4eoPqmqra6vsbS5vsHCyM3U2N3k6fH7gYaLj5Sbn6WqsLW8w8nP193l7PH5gIWMkP665pDoobjCppWKg4KDg4KChIGFgIV/gH59fXx7eXZuZFmfjYB/gIKDhYeJiIuOjo6QlZWYnJ6fnp+gpq6xs7q+wcLIyczW2+Tn7Pf/gYuPk5ecoqmxt7m9wsbM0djb3uLk5efp6ejm5+jk4uLf2trTzsrKw8C3r6WajfPYvaugmpWSkZCPj4+QkZGRkpKUkpCQjo6PkJCSZpWTkpKSkI+OjIyIh4aCfnx3c3JxcXJ6RE1VXWZpamtra2xsa2tramlqamppZ2dmZWVmZ2hrbXN9iMzeuIKolueXpKCalJCKhoL+9+/n3tXOy8TAubGtqKWhn5uZlpaTkZCOjYyLi4aKhYuEjEaOj5CRkpOTlZWXmp2enZ+jpairra+ztbm9wMPHys7T1trc3+Lj5+nq6+zrz4RsWl1dW1tZWVlXT1VVTlBWWlxdXl8xMTIyhDQeMzFgXVlYVU9SUUlJSkY/Pj4+P0FCQkRGSEpLS01OhU8ITk5OTU1NTEyES2BKSUpJSUhISEdHR0VFRURDQ0RERENAPTo7QUZHR0cRCAcGBgUGDiFGRkVALDAWFxcNCAUGBwUEBwoTFCkoJycpJiMjIyQhIiMhHx4eHx4dHBsdISUnKx4gICIiIyUUBwYCgoCqf4Z+Bn+BgYGAgISBA4B/fpt9AX6EfwGBhoIBgMp/EICAgIGBgoOGiIWFhYOCgYGEgK9/BYCBgoKDioSXg5KChYOWgoWBgoDpf4KAhIG9gpWDhIQHg4OCgoGBgKJ/q36uf7R+o38GgIGCgoODiYTXgwGCln+KgMt/AYGGggKBgIV/Bn5/f3+AgYWCBIGAf3+bfod/A4CCggICBACAyZHFpKOhn5+dnJuanJ+kpquwtLrCzNrj8Pn/gPXw5NTCt6iakYuJi4qKhoH07+7q6+/YnpWAktvC9KiojcCn39zbi+PBteHk1NrOv7WtqZefoq3EysnFuK7firS2u7WQpLXK6PrGw7SwraKfo6WkpKSmp6Wlpaamp6ioqKempaUVpKOioqKhoaChoJ+fnp2cnJ6cnJ2dhJyEnUKen56dnJyZl5SQioqlvsDDytHY64ms3pmMusH4lvfp87rLhKGPmY2AhOPVycbAv769u7q5t7W0s7KysbGwr66ur66ErSivrq6urayrqqmoqKempqSjpKistmo7IhcdEQ8PDg0MDAsKChMREBAPhw6EDYYMhhkMGhobGxwdDw8QDw8OhR0DHBwbhBqHGRMwLixVUp2WlpWWmJmanZ+hoqKjhaQMpaWkpaampaepqamohamGqgOpq6uEqgmrqqqqq6ysra2Ergatra6ur6+LsAixs7S1tra2t4W2C7e3uLa1tba1tbS0hLUWtre2tbW3trKzt7i3tFxdLy8wMRkZGYQYgxmEGhIbGxwdHh8fHyAgIB4dHBwdHR6FHwEghSECIiOFJA0lJCQREhIREhIREBERhBA/Dw8REhIJCQoLFhMhHDYzYsG+vby9v8DAv7+/vr28vLy+vr+/wL+/v729vbq3p5SE6c/FxsjJyMnJysrKy8zMhc1DyszMy8zOzs3P0NHS0dFpaWlsbG1tcHBwcm9xdDk6Ozk5Ojo6OTs7PDs8PDw9Pj8/P0BBQUA/P0BAPj8/Pj0+Pj09PoQ9cjw8PHh3ctnX2Nvc2trb3Nzf4OXn5+jp5+no5eXm5+jq6ezs7e3y8vHv7u/u8PDv7+7t6uno5uPk5ebj54STo7PBxMTGx8jIx8bFxcTCwMC/v76/wMHCxMXFxsbLzW44PCATFgwLCgkJCQgICA8PDw4ODYUOAQ+ODgIPDocPmBCEEYUSgxGEEoMThRRBFRfFt6qxtba2tbOysqu2vra6y9PV19ve4eTj6fP/+Pfy5N7a08/IwsfCtL2/qaSkpKKjpKWnqKqsr7K0tbW2tbSFtYS0DbK0tba1s7KwsLCvrq6ErVSsq6uqqaqqqKGalpyvt7nAoYC0qJmTlcSevLi4r/fDxoq6waHQv4+g5Ib59PrcvLexq6aaioKMhoL1i7v999f0iImFkKamlo2OkJ6uv8jGtrvJq9oDOCRohF5rX2BhYF9gX2FjaG1xd32CjJKboqRSoZ6WjYB4b2RdWVdXVVRQTI+LioqKi1cvKyVIg3h3ODMpODFCRWRUlH11iYqHjYV8dXFtYmdnbHx/gH10cIlNZGVnRyowNDxFTUVxbG1tZ2RnaWloaGmEaINphGoMaWhoaGdmZmZlY2NihGFOYGBgYWBgYWFhYGBgX2BgX19fXl1cW1lYVFFNTWJ9fX6ZssrYcX6hamSBh9R2x7G5eZNgcmluY1laoJqblpKQjo2LiYiHhoWEg4GBgYCAhH+EgAKBf4SAe39/f35+fn1+fn6Bg4eack8zKTwmJiUjIiEfHRwbNDIwLi0rKSgnJiUkIyMiISEgIB8fHz8+PT0+PT09Pj4+QCIjJSQjIkRCQD47ODY0MjEwLy4tLCsqKSlNRj9xY7Cdm56fnp+hoJ6bmJeWlZOSkpGQj42MjIuJi42OjoWQDI+Ojo2Ojo6NjY2MjISLhIyEjQiOjo+Ojo+Pj4WOCY2NjYyMjI2Oj4SQh5EBkIWRBZCQkZGRhJIIk5OUlpeYmJeEmCKcWGM3PURKJicnJygoKCkpKistLjAxMzY3ODk7Ozs8OTUxhDA6MTEyNDM0NDU2Njc3ODk6Ozw9PkBBQiIiIyQlJicoKCkrKy0uLzEzNDUbHR4gOSw8LEo8W5mTjoqKi4SMBYuKiomJhYoPiYmIiIiHh4aDd2lco5CKhYwTi4yLjI2OkJCRkpSWlpaXmJqbnYSfLKChpFJVV1hbXV9hZGZoa21vOTk7PT5AQEFCREVHR0hJSktMTU5OT09PUFBQhE+ATk5OTUtLSklIR0ZEQ0A8b2RZopyZmpuampqbm5ycnp+goqOioqOioaGho6eoqqytrKysqainqKipp6imo6GenJuamZubm5qeWWRve4aJiouLi4yLioiIh4aFhYOCgYKEhoaGh4iKi4+aZkNVMSc2ICAeHRwbGRgXLy4sLCwqKSgQKCcmJiUkJSQjIyIiIiEhIYwgiiGFIoQjFyQkJSUmJicnJygpKioqKyssLS4uLzAxhDJOMzMpiX1vdHd2dXNycnNrdHl0d4CGhoiKjI2QkpedpaGhnpOQjomHgHp8d290dmZiYWFgYWJjZWdpbG1vcXJycXJycnNyc3NycnJzcnJyhXFdcHBubm1tbGtramppaWhpaWhjXlxga29vcUcmMi8qKCk9Rm9tbWiUb29DQz4yQTorMVVFiIeMe2ppZmJgXFRQVE5NklFylIx5h0lJSVBfXlVPUFJaY292dGxuc0o/gAUFIiEjIyIjJCQkIyUlJygpKywuLzI2OTo8Ph89PDk2MzEuKiclJSUkIyEgPTw8Pj8+HQYFBR5EPC8MBgQFBAcMKS1IOTM6OjY1MzEwLyspKCcoLC4tLC0rPis7PD0bBQUGBQUGDz4+Pj89PD0/Pz8+Pj49Pj8/QEFCQkJBQEA/CT8+PTw8Ozo6OoY5BDo7OjqNPDg7Ojo3NjQyMjNBVFFNYXWCikM9Qh4cJCJGLko8QyIjGBsqPTAnKniJt6eRi4aEgoB+e3l4eHd3d4R4AXmEeoB7fH+AgYKDhYeJi42Qk5Wbn6KorbLT6tCWg9+apJ6ZlJCMiISB+/Xt5NvSy8G5s6yloJyYlJCNioeDgP349fDs6urp5+Xm9YaOko6JhP3x5tnLvrSrpJ6bmJSRjouIhoHnwp/2tOqXiYmMjZCTk5OSkpKRk5OSkpGRkY2MjIuKjhiRkZKTk5KRkI+QkI+QkI+Ojo2LioqKiYmFiomLAYqFiwSKiYiIhYcEiIiJiYSIhIcMhoWEg4ODgYGBf359hXyAe3x8fn9/gIKDhp6CxYSozO6Bg4SFh4iKjI6SlpyiqrK5wcrS1tnc3tzQwKujpKampqeqra+vsLO2uLq+w8jN0dbc4+32/YKHjZWboaeutrvDzNLZ4ujw+P+Dh4uP7KbBiM+IjLaklYyKiYmIhoaEg4GAgIGBgYKBgH9/f319fXxqeW5gU5SCfn9/gIGCgoOFiIuPkpaamp6hpKarsLO4vcfJ0dnd6PSBh42TmqCttMDK1N/v+oOLkZifpK2yu8DEyMzR2d3h5Ofp7e3u8PHw8vPx8Ozs6uXh3tnVzsrFv7asnozfrITNsaOemISWNZWVlJWWmJiZmpmZlpiam5+io6WmpaOgn52cmZaQjYuHhYF9eXd2dXRyc3NzdEFKVF1oaWlphWo+aWlpaGdnZmdnaWttb3BxcXJ4gJ2sofCahNqUpKCcl5KNiIT/9u7n4djQx8K7trCtqaWjn52Zl5STkI+NjYyEi3aKi4uLjIyMjY2Oj4+PkJGSk5SVl5iZmp2foKSnqq2wsrW4vL/BxcrO0dXX293g4+Tl5ujq6qSWbVtYWllYVlVTU01RU01PVFdXWFlYWFtcXl9hYF9eXFlWVFNQTk9MRElKQT8/Pz4/Pz9BQkRHSEpLTE1OTk5NhE4MTU5OTUxMS0tLSkpJhEgFR0VGRUSHQ01CPzs6PUVISEkkCAcGBgUGDiJGRkZBTickEw4KBgYGBAYREygnKCcjISIhISAeHR4eHDkeICwrKCkVFRUXGhsaGRkaHR8iJCYkJCcLBoKCmX8BgJB/hn4Rf4KCgn9+fn6AgYKCgoGAfn6XfYR/AYCGggGBy38RgICAgYKDhYiLioqKhoOCgYGEgLF/BYCBgoODioSWg4yChoOTggWBgYGAgPh/goCEgbWCk4OEhAeDg4KCgYGAoH+ifo5/roCDf7Z+oX8GgIGBgoODiYTVg+l/AYCGggKBgIR/Bn5+fn+AgYSCA4F/f49+AX2GfpJ/AoGCAgIEAIDJwo+knZ2dmpmZm56hpqqtsrvF2fGHnLLL1tjUyLahi/XZvKmjnpeRiIOA+ebdw62wrZC8u+Oa8ufc0c/lo5SNx8qJjoDDwa2gl42epq2wsbKzt7W3tbGps92stbns7J+uwt39ooS0r62mnaKlpKOkpaWkpKOjpKWkpqWlpKSlpBmjoqKgn5+foJ+dnJ2dnZycm5ubnJucnJ2dhJxAm5ycnJuamJWQiISUsbKttMDFytv5kK31lNXhp72/76aZo5+VnqWbrvzYzsfDwsC/vbq5uLe2tbOysbGwr6+uroSthKwora2tq6qqqqioqKelp6akpKKhpKm8azwlHCMSEA8ODAwKCQkIDw8PDoQNCAwNDAwMCwsLhAwBDYYaAw4OD4UOPh0dHBwbGxoaGhkZGRoaGTEuLFVRm5iXl5WTlJSVl5manZ6foaKjpKSjo6SkpKOinJOMjpukpaWmpqenp6iohqkGqqqrqqmohKkOqqurq6ytra6ura2trq6GrwGwhLEIsLKzs7S1traGtxG4uLi3t7e2tra3t7a2tbW2toa3E7W0tra3uLu9vr29vLxfXy8wMTGEGIUZFhobHR4eHx8gHyAhIB8eHR0eHh4fICGEIgMjIySGJQgmExMTEhISEYYQWhESEgkJCgoKFBIfHDVmx8XBvry9v7+/wMHBwMDAv7++vby7vLy9vb27uKqYhufIwsPFxsXHycjKycjIycnKysvKzczNzc/R0c/S02tqamttbG9tbG1uNzc4OYQ6Bjw8PR8fIIQhhCKDI4YkhiULJCQjIyIiIiEiIiKEIYUgCR8+PHNu1NPU04TSLNbY2drd4ODh4uLh4d/h4N/i5enr8PPz8PHu6unn7Orr6+nm5uPg3t7g4+TihOM54eP+j6C0wsTExcXExMLDw8LBv8HBwsTFx8bGxsfGxsnKbDk7IBIUCwoKCwoJCQgIEA8ODg4NDQ4NhA6ED4cOAg8OhQ8EEA8PD4UQBQ8PEBAPjBCDEYYSgBESEhITExQUExMUFBQVFRh5u62ssrOztLOzta64v7e5xMfJy8rM0NTV1Nnh39/e1tHOyse6vb+zsriwoqOjoqSjo6OmqKmsr7Kzs7W0tLO0tLO0tbOytLSztLS2trOysrGxsK+uraysrKuqqqmpqaaelpamtrm91rWwrpqSnOzNPcS7u8nRgafIn/eP9aD8j4eE7fmAhoqK/+/n+Iufqa+kiPKPmqa8uZWMjJGWnKi7u5iOjpCbrsDHxrS20JJNODUqYFxcXV1dXmFjZWltcHR7g5GhXG19j5eZlo+DcmKpk4BzbWhiXldSUJqTinxvbWkrNjZGW5uSi4aAiT4wKjo6KCoxcnVuaGNdZmyEbzdyd3l6eXhya3hhZWZySS4yOUNMM0Bra2toY2ZoaGdnaGdnZ2ZnaGhnaGhpaGhoZ2ZlZWRkY2JihGELYGBgX19gX2BfYF+FYFFfXl9eXVtaWFVRTUpUcG9qepurur3Ka4Ooa5egjouUy29zdXZqeHVsfbaen5mUkZCOjIqIh4WEg4KBgYCAf35+fn19fn5+f39/fn5/f35+fX2Efi5/gH+AgIKEh5x4VDcvRSooJiQiIB4cGxozMS4sKikoJyYlJCMiISEhICAgHyA/hT5IISMlJiQjIyJDQDw5NjQyMC8uLSwrKilPRj9uYKecnp6ho6Gem5eWlZWVlpWVlJOTk5KRkZCPjod6cnKAiIuMjo6PkJCPj4+OiI0NjIuMjY2Mi4yNjY6OjoWPCpCPj5CQkI+Pjo+EjQGOhY8HkJCRkZGSkoaRBpCQj5CQkIeRbpKSkpOSkpOUlpiZmpqamZimXms8Q0pNJycnKCgoKSosLTAyMzY4OTo7PD06NjMwMTIyMzQ1NTY2Nzg4OTo7PD0/QUJDIiMkJSYnKSorLC0uMDEzNRsbHR4gOSs4Kkdwq5eSjo2Njo2Njo2NjIuLh4oTiYmIh4aEeWpcnoqHiImKiouMi4SMPI2NjpCRk5aZm52foKGjpadVWFlbXV9gZGdqbTg6PD5AQ0ZISk1OKCkrLC0uLzAxMjMzNDU1Njc3ODk4OIg5VTg4ODc3NjY1NTQ0MzIwLy0qSTpiVp+amJeXlpWVlpiYmZudnp6fn6ChoKChpKqvtLW2tLazsK6sqaenpqakoZ6dm5iZmZmampqbm52bmZuwYm97hoeEiDeHhoaFhISDhIWFh4eIiIqLi4qLjpNgQVIvJDMfHx4dHBsaGRgxMC8uLCsqKSkoJyYlJSQjIyIihyGFIIghiiKEI10kJCUmJicnJygpKSoqKyssLS0vLy8xMTIyMzQzMmF6b21zc3JxcXJzbHR5c3R7fH1/f4GDhYeHio+Pj46IhIJ/fXV5eW9tcmtfYGFgYWFhYmNmaGpsbm9wcHBxcnGHcoVxhHBdb25ubm1sa2tqaWhoZ2dnZWBcXGVub3B2OjEwKicqRlBwbm9wTSo/TjRJKEcxXUlKTIeMSUtNTZWKgotOW2FlXk+MUVdeamhRTE1PUVZfampVUFBSWWZxdHNqanYyewUFCSAhIiIiIyMlJigrLS4vMjU6QCUtNDo+Pz05NC0mRD01MTAuKyonJSRHSklCPDk0BwYGCjJTTkhBOToPBQQFBQQFDS0uKScpJygoKiorKysuLSwsLCswQjs9PDoLBgYFBgYHHz49Pj06PD0+Pj09PTw8PT09Pj9AQIQ/Bj49PDw7OoQ5gziIOQU6Ozs9PYU8Nz09PDs6ODYzMTE5TUxGUGJuc3F1ODszIi8rOCYsVCweHB0YKjYrMHiErqSPiISBgH17enh3d3aEdYR2gHd3d3h4enx+f4CBg4SFh4mKjY+RlJicoqaprLDe/NuimPqmqKKemZSPi4aC/vLp3dTMwbmyq6Wfm5eTj4yIh4OB/fr39PT0go6YmJOOiYL56NbEtquinZeTj4uHhIDrw5zqn7eKiYmIioiGhoSFhIaIiouMjI2Ojo+Pj46Ni31qBmVpeIeMjoSRF5CPj4+Ojo6NjY2Li4qJiImKjI2Li4yLhIwrjY2MjY6Njo2NjYyMi4uJiYiIiImIiIeIh4eIh4eGh4eHhYOCgYGAf4B+fYR8AXuIeoB8fX+DhomLjZGY3LD5osnt/oGChIaHiY6Tmp+nsLvFzdPZ3eDh18WxpKSnqauusLO2t7q+wsfL0NTd5ev0/YOLkZigpa+4wcrS2uPs9fyCh4qQk/KouobE9PGuoJWPjo+Ni4uKiYeFg4OCgYCAgYGAgH9+fXtxYVWTfnt8foCBgkWEhIaHiIqMj5OYn6SrsLO8w8zQ3uj1gIiPmaKtucjb6/yHkJulsL7I09zn9oGHjZOZnqSprrK1uby/wcTFxsnKysvLzc2EzoDNzczLycjHxMK+uri0r6mglYXQgrWC0ruxq6Wjn56cm5ucn56foKSlpKSlqrK7wsjX2NbYzMO6ta6moqCclJKRjIeGhIJ+e3t5d3Z1dHNxc4FKVF9mZ2hoaWlnZ2ZmZmdnaWprbW5ubm9vbm93foyKkuiXgdqaq6SclpKMh4L89Rzt5NzUzcfCu7WxrKmlop+al5STkY+Ojo6Ni4uMhY2Gj1iQkZKTlJWWmJmbm56ho6aoq66xtLe7vsHFyMzQ0tfa3d/i5OXn5+nr2aVtW1ZXVlVVU1JSTFBSTUxRUlJUVFVVVldYWVtaWVlWVFJRT0tOT0dFSEU/Pz8+hD8LQEJDRUhJSktMTEyFTYROAk1MhEs2SUlJR0dGRkVFQ0NDQkNDQkFBQD05OkFHR0dJEQcGBQQFDiJIR0ZEHQoQEgoLBQkHExUWGCoshBcmLSwqLBkbHR4dGCwZGhofHhgXFxgZGhweHRkZGRocICIkJSMkJQYDgoKBkn+LgIt/h34FgYKCgX6GfQJ/gYSCAoF/lH0BfoR/AYGGggGAzH8TgICAgoOFioiKiomFg4KBgYCAgLV/BYCBgoODioSVg4aCiIOPggWBgYGAgP9/hn+CgISBrYKQg4WEBoODgoKBgJ9/nn6Lf4uAr4EEgIB/f7t+nn8GgIGBgoODiYTSgwGA538BgYWCAoGAhH8JgIGAgIGBgoGBhH+CfoR/hH6GfwF+mn8BggICBABr0sWY2Kmfm5qZl5ibn6SquMXT5ISm35XQl8bVuI2+jdWlg97EsKObkIXz6r6Zherh95yIxNS+6NHN0L+xtMXwgr/fgsbTjo/jzLShmJSWnqGbnaiqqKWlnbeos7jdy5qovNb5xMG1sK2nnqGEowmko6OjpKOhoqKIoQygoKCfnp+enZ2dnJyFm02ampubm5ycnZycm5qampyamJeUkIqEkamxsLK6vb3C0uWAl6+IhNSduf+D4Yyewaq3q8+L2tHJxMLBv768ure3t7WzsrKxsLCvrqysrISrC6yrq6uqqqqpqKiohqcbpqajoKCho6aqXzg9Kh8SEA4NDAsKCQgHBw0NhgwGCwsLCgoLhQwHDQ0ODg8ODoQNBBwcGxuEGjYZGhozMS4sU5yWlZaVlZaXlZaXmJiXmJiZm56fnp+hoqOjoqGioqCaxojLl4WHiJ+hoqOkpKeJqAapqKmpqaiFqQyqq6urrK2vrq2ur66FrxawsbGxsLCwsrKztLW1trW2tre3t7m5hbgkt7i2tra4uLe2tra3uLi4t7i4t7i5u7u7vb/AwsHBwsLCv19ghDCFGAoZGhscHR4eHyAhhCIUIB8eHh8gISIiIyMkJCYmJicmJyiEFB4TExMSERESEhIJCQoKCgsKEh4bNWPAvru6u76/wcGEwgfBwcC/vr69hbwqurShjvLOv8DFxMXFxcTFyMjHx8fIx8jIycrMzMzR09Jqa2xsbG5vb2w3hDgOOTs8PB4fHyEiIiIjJCSEJYsmgiWEJhUlJCMiIiEfHx8eHh0dHB0cHRwdHR2GHk09OnJu08/Qzs7Nzc/P0tPT1Nfb29zc3tzc3N7db3R4eHh7fXp2eHju6ern5eXm5N/c29ra293e39/f4N/f4ePf3dvmg5aqu7/BwcLCwoTAB7/Cw8XGxseEyBTHyMzQ2Dk7HhIXDQwMCwsKCQgID4QOhA0FDg4PDw+KDooPBhAQEA8PD44QhBECEhGJEoQTTBQUFBUVFie7sKexs7OztLW3r7fAtrG6vsLDwsTHyMrLy9DQ0NHPysfGwbO/u6qxtaWfoKCgoaKipKWoqaqtr7CxsbKysrOysrKztLOEtAyzs7OysrGwsK+urq2ErFOrqqqopZyWmay3usK+lLqsnJ+Tjt+/vr+/qKfU2eDQwKinusrS0c7XxK6srbnEzqymqKu3xMrEpqGgn6W6t6aXkZedpKmxuqOQjY6XorDAvbS0zGs+Nyd3Y15cXV1dX2RnbHR9iJWjYHuja5NoiJKAYYdlnnpfnIh5bmhgWaObgGlal42TUCc5PkSHfnyEe3NyfptTXkUoOz8qKV15bWJeXmJnaGZncXNzc3JoZ11lZWw/LTE2QEs9T2tqaWhjZIVmHGdnZmVlZmdmZWZlZWZmZmVkY2NjYmJhYGBgX2CMX0JgX19fXl1dXVtZWFZTTkhPa3Bob5Odr6yvuWNugV9gm3CGt2CmY2+Me4F2lGWfoJiSkI+Ni4qIhoWEgoGAgIB+fn6KfYV+hX0Ff31+fn+EgCB/fn+BhVRBW0E6KCooJSMhHx0bGhgwLSsqKCcmJSQjIoQhTCAhICAgHyElJycmJSQjIUA8ODUzMTAuLSwqU01EO2ewnZ6goaKgnpyamZiWlZOSk5KTkpKTk5OSkpKQkI+NirFuwY51eG2Hi4yMjY2FjgSNjIyNhowmi4uMjIyNjY2OjY2OkI+PkJGQkZGRkJCQj46OjY6Oj4+Oj4+PkJCFkQGShZEFkJCQj4+NkISRgJOVlZaWl5iYl5aWlJVUYjhASEwmJicnKCkqLC4xMzY5Ojw8PT48ODUyMjMzNDU2Nzg4OTo7PT9AQUMiIyUmJygqLC0vMDIzGxwdHR8hHi05KkZtpZSOi4uNjY6PkI+Pjo2MjIuKiYmIiYmJioiEdWOojoWGiIeIiYuKiouLi4yNLo6PkJGSk5ebnaCmqlZYW19hZWhqbzk8PkBDRkhKTigqLS8xMzQ2Nzk6PDw9PT2FPgI/Poc/Cz49PDs5ODc2NTQzhTIFMTAxMTGEMgwxMS5NPGdYoZqYl5eElXKUlJaYmp2dnp+hoqSlqK5cY2hra2tpZmRhXrezr6yqp6OhnpybmpqZmpmam5uamZmZmJaWlp1aZ3aChYaFhYWEhISFhYWGh4eJiYqLioqKiYiMkbk/UTAlNiEhHx4dHBwbGjMyMC4sKyopKCcmJiUkJCOEIoUhASCJIYsihCMhJCQlJiYmJycoKSkqKissLS0uLi4vMDEyMjMzNDMue3JqhHI6c3JybHF3cG10d3l5eXt9fn9/gIOEhIWCfXt6eHB3dmhtb2NeXl9fYGFhY2NlZ2hqbG5vb3BwcXFxcoZxAXCEb2Bubm5tbGxra2pqaWloZ2dnZmNfW15pbnBzTSo0LyorKTF4cXBwZDwxPT9CO0FQWGRxdnNzeW1iYWFpcXZiXV5gaHF1cVxZWVpdaWhcVFBSVVpfZGhcUlBPVl5ncXBqZ2ReBwYFJyQjIiMkJSYpKy4yNTk/Ris4SC08JjEyLSIzKEA0KUE5NTIvLCtTUUg9NFBFSCIFBgYNPj49RTw0NUFULCQLBAUFBAUVKSglIyEiJigoKy8wLywsLjk3Pj89DIUGCAcdPj09PDo7hDwVPT08PD08PDw9PT08PDw9PDs6Ozo5hTiCN4Y4CTc4ODk6Ozw9PIY9Mjw6OTc0MS81SEtESV1kbW1tbjk5OiEeLRwgKxgrGRslKTYtNj1/q5+MhIF+fHt5d3Z1hXSEc2pycnN0dXV2eHp8fX6AgYKDhYWGiYuNj5KVmJugo6eqr7eFl/zAwJq2sqqim5eQioWA9end08m/t7CopJ6ZlZKOjIuIhoSEjJijopyXkIiB7tjCs6igmZKMh4L83LGHu96TjIuMi4mGgoKDhIEggIGAg4WHiIqKi4uMi4uMi4aiU65zeWZlio2PkI+Njo6EjRmMjIyLiomKiYqKioyNjYyMi4yNjY2Ojo6PhJAOj46Ojo2MioqKiYmIiIiGhxCGhoaHhoWDg4KBgYF/fn19hHyHemd7e3t8f4GEhomMjpGUlZaWo4LOkLrj/IGChIaKjZOZoKq2w8/W3OHj5t/MtqqqrK6xtLe9v8PIzNLZ3+nx+4SJkpmhq7XBzNbh6vWAhIiNkpiFtMeGxe3dqJyRjY6PkI+PjYuKiIaEhIJOgYGBgIB+emtdnYJ6e35/gICEhYeKi42QkpaZnqGnrra/ytbp9YONl6a1xNfn/4qVo7K+zdzp+oaOl6GpsrrAx83R19vc4ODh4uTm5uXmhOh35+jn4t3Y087KxcG+u7m3tbSzsrKzsrOzs7W1tbSsnfGUypHrzLy2tbCtqqejoqanqKqsr7Czt77G2/eMo7vFw7qvp5aHgvDf0cS4sKminZORjYuHhoOCgH18eHd3eHVycXZETVhiZWVlZmZlZmdnaGhqa2xsbW6FbyxzfIf/jeqai+idpZ6Yk46KhoD88uni2dHLxcC5tK6qpqGem5iVk5KRj5CPj4SOg4+FkFWRkZOUlJWWmJmbnZ+ho6Woq66wsrW4vL/CxcjN0NPX297g4eTn6Orr6Y10YFVYV1VUU1JSTE9RS0lNT1BQUFFSUlRUVFZVVFNSUE9PTUpOTENGSD89hD4OPz9AQEJDQ0VHSElKS0uFTBRNTU1MTEtLS0pJSUhHRkZFRENDQ4RCUkFAQD87OTpDR0ZHIwgHBgYFBhBHSEdHOBEJCwwMCgwVGh4hIiMlJSEeHh0eICMdHR4eISIkIh4eHRwcHx8cGhkaGxwdHR0bGhoaHB4gJCQlJBWDgpB/BYCAgIGBhYIFgYGAgICHf4V+CH19fX6CgoKAiX0Dfn6AhIICgX6QfQF+hH8BgYaCAYDNfw+AgICBgoKDg4OEg4OCgYGEgLd/BYCBgYKDi4Seg4uChIEBgKB/Bn5+fX19fup/goCEgaeCjYOHhAWDgoKBgJ1/nH6Jf4mAuoEEgIB/f5h+i3+cfp5/BYGBgoODiYTPgwGC5X8BgIaCAYGFfwGAhYEBgLJ/AYACAgQAeI7OwezSs6GenJqZmp2jq7rL5oSp4ryg5uyqu8Ka9Lak3qqI48evnYbas5OB4NfV2+3GvcmoqMi3usLfzNnHop6koZOB2IjT0YLM46CPioaKj42VnZ6cqbWjtbjgxZWkudHwxuO3rqupoaGhoqGhoqGhoaOko6KhoYWgEp6fn5+dnp6cnJucnJuampucm4SagpmFmkGbm5qZmJiXlZOOiIGMpK+tq7e4tLe+x9Pqgo+er77ppMPCl4+RvKKdwYra08vFwr+9u7q4trW1tLOysrGwr66urISrC6qqqamqqqmqqamph6Ygp6alpaSjo6OioJ+foapkOSAYDw4NCwoKCQgHBgYNDAuIChELCwwMDQ0ODw4ODg0NDRsbG4QaOxkZGTEuLFOem5qZmZmYlZeZmJeYmJeXmJmZmpubnJ6en5+goqOioaCfnpyY1KaAvZiq0J6hoqOjo6WlhKYCp6aEp4SoDqmpqKeoqaqrq6usrK6thK4Zr7CwsLGxsK6ur7CwsrKzs7S0tba3uLi4t4S4ULm6urm4uLe4ubq5ube3ubq6uLe6ubq7u72+v8DBwsLBwcLDw8PExcXDwF8wMDAxGBgZGRoaGx0eICEiIiIjIyIhICAgISIjJCQlJiYnJygphBQEFRUVFIQTLQoKCgsMDBUiGzRkv7y5t7m6vL6/wMHCw8PBwL+9vby7uru7urOgievIw8LCwITBMcLEx8XHx8jIycjIycjL0NBoaWttbG1sNzc5ODk6Oz0eICEhISIkJCUlJiYlJicnJiaGJ18mIyAfHBsYLiopJyMiIENDQ0VHPj8+e3tGS05PSThBUFBNSz9pbGs3NS80fHZ2dnR15ejzhY6bpbG9xMvS19nY2tra3d5wdn9EQ0JAQT8/Pz08e3d7eXZ26urk497d3ITaQNnc29rb3OHf3d7d3d7d3f2Rpre9v76+v7/BwsLDwsPFxsfJycjHxsXDwsFkNDceFBkODg0MCwoJCBAPDg4ODQ2EDgEPhg6RDwIQD5AQgxGLEoQThhSAFr+0pbG0tbW0tLaxsriwrLe7vL6/wMDCwcPDxsfHyMnFwb+4tby1q7OwnJycnqCgoaGjpaeoqqyurrCwsLGxsrKys7O0tLO0s7OysLCwr7Cwr62srK2trKqrq6qnoZaUnrK5wNS4uberpafan8TE0YSIsZvS9tL/s5ib0cnV3qsto9XBr7CxvcvOwKurq6y5ws/Ds6CcnaKvvr2kk5KUnKGvtZmOjo+Unqu9yLC9ajE6NlR6amBhYmNjZmpweoeUqmuItIptrM2HoI91wnx3pYFmn4Z2bWCfhGlYkomHiIxCNzs0UHNtcXyRjJGGa2ZoZVo7Rio/PiY/cl9bWldaXV9pbm9sbmZbZmZuPiwwNT5JPFZramloZGSEZYJmiGUIZGVkZWVkZGOEYgRhYWBghV8CXl2MXjxdXVxcXFpZV1RPSk9lcGpoiJSlpKSnrblianV9hKJ4jotqZWeDcGuKY5+imJGOjIuJiIaFhIOBgYB/fn6GfAF7hXwVfX5+fX59fHx8fX19fn5+f3+AgIB/hH4bgIOIZEsyKyIrKSYkIR8dGxoYLywqKScmJSQjhSJIISEiJSgoJyYlIyJBPDc1MjAuLSsqT0U7Zqydn6CgoZ+dm5qYl5eWlJSTk5KRkZGSkZGSkpOSkpGQj46Ni4a6to3QorG5ioyMhI0BjoeNBIyMi4qEi4WMCIuNjY2Ojo+PhJCFkQaQj46Pj5CFj4OQhpGCkoWRhJABkYmQDpGQkJCRkZGTlJSUlZSUhJOAlJSTk5GRn183QElMJicoKSorLTA0Nzo7PT9AQD86NTQ0NTY3Nzg6Ojw9P0FDIiMkJigpLC4wMjQ3HB0fICEhMkIrSG6jkYyIiIqLjI2Ojo+OjYyLi4qJiYiHiIeHgnNipYyHh4eGiImJiYqKjIuNj5CRk5OWl5qcn6NUWFxgZWkcbTk7PkJFR0xQKisuMTM1Njg6Ozw9Pj9AQEFBQYRCakE9OjYyLisnRj86NTEvKE1JSEZGPDo4amlGTU9PS0FBTU1LRDZcYGU1OTU2aWFaV1ZPoqKtXWVudXqCjJSZmpydn6GkqLJfZ3ZFSktKR0dCPz47b2tnZGJds62opaSin56enJyamZiYl5eEmA6WlpWUlatjcX2AgoOEhYSELoaGh4iIiYqJiIeHhYOChVg/VDIpOiMjIiEgHx0cNTMxLy0rKikoKCcmJSUkIyOFIochhCIBIYgiAiMihCMjJCQlJSYmJicnKCkpKiorKyssLS4vLy8wMTIzMzMkfXRocHKFczRub3NuanBzdnd2d3h5enp7fX59fXx4d3dycHVvZ21sXV1dXl5fYGBiY2RlZ2lrbW5ub29viXAQb29ubW1tbGtramppaWhoaIRmUWVgW1lgam5wdjs1NDAtLkNIc3J1LycwKTtTTEoyKS5panV9X1t3bWRiYmpzdm5gYGFja3B4bWNZV1hbZG1sXFFQUVZZZGhYUFBRVFtjcHZmam0HBgYWLSgmJygpKy0uMTY8Q00zQU84I1RYSklDOUAsLEA1LUY8NjQyWUs6L0xFQD4+EAYGBRw2NDY9TElUTDMwMC0nEQgEBQUECSQjIiIiIyYnKzAwMDc7NT09PQ0GBgYFBgcdPD08PDk5Ozs6hTuCPIY7Djo6Ojk6OTk4ODg3Nzc4hjcMNjY3Nzg5OTo7Ozs8hT2APDo5NzUyLzJETUVCWmFpaGlsbnE6Ojs7NzsoLSsiIiY7Lyg1PoGomIaAfXt5eHd2dHNycnFxcXJxcHFxcHBxcXJzdHV3enx9fn+AgYOEhIaJi4yNj5KUl5mcn6Kmq663s7qRiYa3u7KpoZmSjYaA8ubaz8W8s62moZ2alpOSkJYkpLCwqqKakoj538a2qJ2VjYaB4bSFssqVk5OTkYuGhYSCgYKBh4AGgoKDhIaHhIkcioqKiYiCvtyctrvJwoqNjY6OjY2Mi4uMi4yLi4SKAYuEig2LioqKi42NjY6Oj4+QhJEkkI+QkI6Ni4qJiYmIiIiHiIeHh4iIh4eGhIOEg4ODgoKCgX5+hX1gfHx8e3t6ent8fX6AgoSGiImLjI2PkZSXl5man/HIkL7r/oKEiIuQlJ2otsXT3eXr7/Lq1LiwsbS3ur3AxsvS2eHp84CHjpWirLnF0uDs+oOIjZSZktTois734KKUi4qNhI5NjY6Mi4mHhIOCgoKBgoB/em1dmoB8fX5/gYODhYiKj5CUmJ2ip6uxucLN3e+EkqGzxtnzhZOktMfZ6PmHkpymsbjDy9LY3ODk5+ns7e6F8IDs3c3AtKaWh/LXxLCfo4Hp1M3Gv5aPg/frt9HZ18zWwNDOx7qW4+38kJuqp/3Ns5+Rgu3t8H2DjJGYoamtr7O3ur3F0N78l7v2r8zTy8O0qJqMgevTvqeYifTj2MO7sq6kmpeVkI+MhoaHgnx5eXZ0c3FwgUpTYGFjZWZmZmdnZgNnaWqFbIBra2poZ2t2gJj5pJj3oKGdmZWQi4T+9u3m3tbOyMK8ta+rp6Kdm5iXlpSTkZGRkJGRkpKRkZGQkZGSkpOUlZaXmJmbnJ6go6WnqKutr7K1uLy+wsTJzNDS19re4OPl5+fp6IaEZ1VWVlVUUlJRTU5QS0hMTk5OT09PUE9RUVJSUSFSUE5OTktLTUlDSEc8PDw9Pj4+P0BBQUJDREZGSEhJSkqJSwtKSklIR0dGRUVEQ4VCVUBAQUA+Ojc2O0RGRkYSBwcGBgYOIkhHRxAGBQQJEhAJBAQIICIkJRwdJCEeHh4gJCQiHh8fICIjJSIgHRwbGx0hIR0aGRkbGxweGxoaGhwfIiUpJicEgoKCgI5/EYCAgIGDiYmKiYmKhYKBgICAhX+EfoV9BX+CgoJ+jX0CfoCEggGAjX0BfoR/AYGGggGAz3+GgIaBhYC7fwSAgYKDi4SZg4qCBIGBgYClfwd+fX18fX1+738BgISBoYKMg4aEBYOCgoGAnH+afod/iICfgYeAhH+EfoJ9i38Ifn19fX5+f3+GfoN9kX6Df4qAhn+bfpt/BoCBgYKDg4iEzoPkfwGBhYIQgYB/f3+BgoKCgYCAgYKCgbJ/AgIEAHj20sTr0cXEtKKenJ6gpq69zuuLqOPVgqXls/6n1Kya+uzEl/Krj4Hq566F7eLg5OmHscTQm+bAxNO7sqaYk4bdvbChqeu12Zmq6bu+lp6Li4iEjqKWys2itruH2ZSjuNHwt924sa2ooaCgoKGhoqKhoKGioqGgn5+EoAGehJ2DnISaDZiZmZmbm5qbmpubmpqHmDSXlpWUk5GOiP+Inq+oo6uvq6y0vcTJzdXi8vyFjY6Tk5CPjImBmfbUzsrGw769u7q5t7WzhLEHr66traysq4SqBKmoqKiGqQ2npqampaWkpaSkpqWkhKMcoqKhnZyeorE0OiYZDQsKCQgHBgYFCgkICAkJCYQKBwsLDA0ODg6EDUobGxsaGhoZMi4sUpyamJaWlpeYmJqbmZiYmZmZmpiYmJmam5ydnJ2en5+goKGjo6KhoaOioqCP9vGAkp+hoaGioqOkpKSlpKOkpIWlgqaFpyWpqKqrq6usra2trKytr6+vrq6vr66vr7CwsbGysrK0s7S1t7e3hbgUubu6u7u6urm5urq6ubi4ubu6urmEuzy8vcDAwcLBwcHCw8PCxMTExcXGxcPFxWJiMTEyGBkaGhobHR8hIiMkJSUlJCMiIyQlJSUmJygpKSsVFRWFFhcYFwsLDAwNGBIcMzFfu7Wztba4uru8voW/Eb69vLu7urm3s6GJ58bCw8LChcEhwMPDw8bHx8fGx8rNz2lqa2ttbm42Njc4OToeHyAhIiMkhCWEJocngCQhHhsYLSgjQkBBS2hjuau2LjQ4ODA4Tzg3NTV5kJ6in7EwNTc3MjlUOTo5N4GAjYqInjo7bXFucHNvaGxkuIyWq7qCg3x6e/T8iJ2tvWl3P0cmJSYmJSQkIyMhQEI+Pj48eHV1c3Fwct7d1tfZ29rZ2Nnc39/e3dze3tzZ2NXUK/aMobW7vsDBwL+/wMHExcbExMPDwr+6trS2tmI0HCEXDw8NDAoJCQgIDw6EDQcODg4PDw8OjQ8BDoUPBRAQEA8PjRCEEQYSEhIREhGEEoUThRQcF9u4p7C2t7e4ubiwqbKspbS6urm7vr6/v7/AwITDgL+9vK+2vK2staiam5ycnZ+hoaGjpqmrra6tr6+vsLCxsbKzsrOysrOysbCwsK+wr6+urKurrKupqqmpp5+WlKK0usGekMG3rrCWhdbFydy0woDq99fIxtChyuqogczF1+bYuq2rrbLDytG6qaejpqu8yLmknJmdo67BxKuSj5OXDZ2ntKWYkJCWn6S2w7trZj03SHl3d29mZGZqbnV/jJm3coq5n113upPKiK2YbbarkXG0fGdeqqh8XaWYkY+OSzU7PjaAdHyOg3xsY2FbmYp9d3uVaVcyNEY3NzJdW1xbXGR0aHh0XWZnPUMsMDU+SDZTamhoZ2NjZGSIZQdkZWVlZGRkhGODYoVhBWBgX19fiF4BXYVeQF1dXFxcW1pYVlNPlE1fcmpngpCeoJ6hpKWnq7K6w2Zoa21raWVgXFZssJiemZKPjYqIh4aEgoGAf399fX18e3uHeoR7CX19fX5+fX18fIR9hH4if4CAf39+fn5/gIGEmz5aQDooKCUjIR4cGhgtKykoJyYlJIQjMiIlKCopKCclJCE+OTUyLy0rU0pAbLGen6Giop6cm5ubmZiYlpWVlZOTkpKSkZGRkJCQhJEVkpGRkJCQj46Ni4Dc1HB+iouMi4yMho0GjI2MjIuLhIqFiwuMi4yMjI2Njo+Pj4SQBZGRkJGQhI+CkIaPBpCQkI+QkIaRDZCRkZCRkJCRkJCQkZGHkAmRkJCRkpOUk5OEkkyRkZGQkJGQkJGRkJFUZTtGTCcoKCorLjE1ODw/QUJDQj86NjY3ODk6Ozw+QEJEIyQmKCosLzE1OR0eICIjPCcvTTtVj4eFhoeIioqLhYwSi4qJiIiHiIiHg3RhoIqIiYmJhIiAiYqMi42QkZKTlJibn6NTV1pfZGluOj5BRUhNKSsuMTM1ODo8PT4+P0BAQEFBQkJCQT03Mi0mRjovUUdHRV1Of3F6Ljg8PTVCVzs6ODVcXmFjZHExNjk4NT9TOTo4M19RU1ZVaTw4YGJeX2BaVFZPaU5XZGxYYlhWV6yuY3OCl1gjaT1NLjAyMjIxLy4sKk9LR0M/PHFtZ2ReW1esp6Shn52bm5qEmESXl5aWlZSTlJKSpl9vfYCAgYKCg4SEhIWGh4aGhYSDgHt4dnh/XUMrOjIkKSckIyAeHBozMS8tKyopKCgnJiUlJCMjI5kihCMLJCQlJSYmJycnKCmEKkwrKywtLS4vMDAxMjMzMJZ3am9zdHNzdHNubHJtaXBzc3N0dXV2d3d4eHp6eXl3dXVucXJpaW5kW1xdXV5eYGFhYmRlZ2hqa21tbG5uh29nbm5tbW1sbGtramlpaGhnZ2ZmZWVlY19aWGJrbW9FKjc0MTErMHl0dFc2OilfkIB0cXM5PEEuKWprdn54aGBgYWVwdHhpYGBfYWRtdWlbV1VYXWRvcmJST1FUWWFoYFdRVFheYGx1bWMWBwYLLSwtKysrLTAxMzg/RlY2P089HzNdP189UkwnQj86MFM9NTFaXUIvUk1IR0MgCAYGCD07Q1JKQTkyLitOQj89OzsnFQUFBwUGCycnKCcnLzoxQkA3PT4dDAYGBgUFBx2EPBk5OTo5Ojo7PDw7Ozs6Ojk5Ojs6OTk5ODg4hDcJNjc2NzY2Njc3hDYJNzc4OTg4OTo6hDs1Ojk4NzUzYDM/TURAWGFnaGZnaGprbXBxczo7Ozw8OjcwKSQtdn+ilYR/e3l3dnV0c3JxcXCGb2xubm9vb3BxcnN0dnh6fH19foCBgoOFh4iJiYuNj5GTlJaYnJ+ipqqv3IXywtOrvretpJyUi4T98ODUysG4sKqlo6Cgrr7DvbSropWG6863qJ2Siv3Nl83qm5eYl5WRjYqIh4WEgoKBgYKBgYGEggyDhISEhYWGiImKioqEiweJiH/n4XWAhIoKjIyNjIuLjIuLi4eKDYmIiYmKiYqKjIyNjo+EkIWRhI8IjYuKi4qKiYmJiAeHhoSFg4SEhYNmgYCAfn5+fXx8fHt6enp7e3x+f4KDhYaIiYmKjIuMjY6Rk5eYmZuapYvlpNX7goWJjpObqLrO4Ov1/P7+7dK6uLm+wsTIzdXd6fSAh4+bqLTE1OX0gouRmJrzlJXmjoKej4eGh4mLhYwGi4qIhoOChIGAf3ttXZmCgIGBgYKEhoeJi5GUmJ6hp661v8zY6ICMnbHF4PuQo7bI3PCCjpyntL3F0djc4OTo6Ovt8PHx8vLr2sWznoX1w5n51dCuzoywhKeDoausm+X/paGbkKxYXV1egoCWmpqU1+yUlpGHzkxTVlR3pZ/S1snIyrilqZ6fP0mAX3N/sJ2Uifzyhqa75ZPGjdKOn6msqaOclIqB7dbEsJ6M/dzGrp2QhPbe0MC2sKefnJqRioeEf318d3VycG9rekdUXmJkZGVlZWZnaGlpaWhoZ2ZlYl9dX2Z3n7GKwLiQqaOdl5KNiIL99Orh2M/IwruzrqmloZ6bmZeXlpaUk5NIk5KRkpGRkZKTkpKTk5WVlpeYmpucnqGjpKanqqyvsrS4u7/CxcjN0NPY293f4eLj5ebDxmlXVlZVVFNSUU1LT0pFS05NTE1OhU8jUFBPT09OTU1KS0xEREhCPT49PT0+Pj9AQEFCQ0RFRkdISEmHSmhJSUlISEdGRkVFRENDQkFBQEA/Pz8+PTo2NTxDRUUjCAcHBgYGEEdHSCQHBQYYNi8pJiMIBQUECCEhIyYkHh0dHR8iIyUiHx8eICEiJCEeHRwdHR8jIx4aGRoaHB0fHh0cHR4hIiUpKQSAgoKBjn8IgICAgYSIiomEigWFgYCAgIR/hH6FfQV+gYKCgYp9hnwIfX6BgoKCgX+JfQZ+f39/gIGGggGAwX8BfpJ/i4DAfwSBgYKDiYSWg4eCBIGBgYCsf4J+9n8FgICBgYGdgoqDhYQGg4OCgYGAmn+Yfod/hoCbgQyAgIB/f39+fX18fHyFgIJ/hIABfYV8hYCCf4SAAX2FfIt/hXwBfYR+gn2EfgR/f4CAioGGgId/mH6afwWAgYKCg4mEy4PifwGAhoIJgX9/f4CCgoKAhX8FgYKCgoGwfwICBACA4q7Iqey2vcKymZeYm5+iqbTP8460h4eOm9fNqL+XoJq7n/65+M29wsKngdzV3++L3aPItoDru6KQjIf7zramlpSWk5Wey4mZs4eF4Nvw86aKh5e+h/mxub+8+JemvdnulLq1r62ooqKioaCgoaKioqGhoKCenp+goKCfn56cm5wYnJ2bmpuamZmamZmampuZmZmamZqZmJeXhpY7lJKQjYj6hZirpqKqrqytsre3u77Axs3P1drh4uXk5Obi1uzYy8jEwsC/vby6ube1tbKwr7Cwr66sq6uEqoOphaiEpymmp6ampaSkpKOjpKOjoqOio6OjoZ+dnJqcnqJfNyAWDAoJCAcGBQQDB4QIHQkJCQoLDAwNDAwNDQ0bGxsaGTIuK1CWlJSTlZaWhJcRmJqcnpubmpuamZmam5ybm52GnxWgoaGhoqOioqGio6OioaChoaGioaGEooajC6SlpqWkpaWlpqamhKc7qaqqqqmqqampqq2trq+ura6vrq+wsbCwsLGysrKztLS0tbW2tra3uLi4ubq6u7u6urq5ubu6urq7vLyHvgK/wYXChMMexMXFxcbFxcTExcXExMXGx2MxMjMZGhobHB4hIyUmhBM/JSUkJScoKSorLS0tFxgYGBkaGhsNDQ4NFR8aMWC6uLSztba3ubu7vLy9vL28vb28u7q6t6iO7Me9v8HDwsXChMAkwsTFxMXFyM7Q0Wdoam1tbzc4ODk6Oh4gISIjJCUlJSYmJyYmjCeAMRwzP0NITDdAMjQyNFT5iZCOnC82ODkxNk44NzY2RkREQ0InMTU3OTQ5Vjw8OztLQ0ZCfFNJM2Z1cnR3cXBvcbrt+ISFvHZ3eHp3aV9cWE2F71pCKjMeISUoJyYnJyYmJCQhIUE+PDs4ODhycXJwbmzd2tfb3NnX1tnZ2NjX2NYt1tPQzs3U/5Wrubq6vL6/wcHBwsLAv767ube1s7CxtLVkNh0mGQ0NCwsJCQgHiA6ID4MQhw8BDoYPhxABD4UQihEHEhIRERISEoQThRREFUa7q6y2uLi4t7ezqbSvoa+2uru6ury9vr28vL/AwMG/vbust7emr7WdnJucnJ2en6Gho6Woqqysrq6vsK+vsLCxsLKFsQGwha9esK+urKysq6qpp6imnZOUqLS3xIO0wbq4tOehyMTC3ImO4Ory+u7j2MrK8ung24aay9Lc17etp6qxx9nYyqqjpaWsvM3CrJqXm6Kqr7eilJKTlJWisLWhkZGVmaK5w3BxNTgxdG1ydWxeYGNma3N6hp3Fc5FmaGhjuaifl4CAcIl4w4yvk4aLjnpcm5OaoVtlMDs5PpV2Z1tbXKeTg3pwcnNxcnaKVlxiLSlFQUZRZ19gaXRNiGFnaElLLDA2P0YsSWhnZ2RiY2RkY2RkZGVlhGQUY2RkY2NjYmJjYmFiYWFgYGFfX1+GXoZdRVxcXV5cXFxbWlpZV1VTT5NNW29nYn+RnJ+cm5mZmpudoKKmp6mpqaaknpeNo5yUmZOOi4qJiYeGhIKBgH9+fXx7e3t6eoR5Cnp6eXp6ent7e3yEfRF8fHx9fX5+fn9/f4CAgH9/f4SAR3+ChYxnTjUvJCckIh8cGhgWKyknJiUlJCQkJyorKikoJiMgOjQxLSxTSD1kpJ+goqKfnZyampmZmpmZl5aWlpWTk5KSkJCQhJGDkIaRCJCRkJCPjo6NhoyCjYSMgo2EjIaLAYyFix+Mi4uMjI2Nj4+QkJGQj5GRkpKSkZCQj4+PkJCQj46Oho8EkJCQkYWQAZGFkgSRkZCQhJENkJCQkZGQkZGRkpKSk4SSg5GFkIaPO5CQkJtfOkRMJygqKy4yNzxAQyIjIyJAOjg5Ojs8PkBCRUclJyksLzM3Ox8hIyIzOilCX5GKhYSGh4eIhIlAiomJiIiIh4eGhoR5ZqqKhIeIiYmKiYiJiYqMjo+Qk5WXmJyiVFddYmdtOj1CR0tRKy4wMzY5Oz0+P0BAQUJCQolDgEJIJDxESE1SRUk0NzY3QrhjZmRzMTo9PjVBVzs5NzY6PEJGRygxNjg5NT5WOjs5NkVRRz9tUUgwWmZiY2VfXFpbdJCXTlCNYGBhY2JXTkxKRWmiSEMxQCctNDg5OTg4NzUzMC4sUk1HQz88OGllYV1aWKqmoaGem5iYmJeWlpWTEJOSj42KipCpZHN8f3+BgoKFgyeCgH17enl3dnR1eYlpSi1OQSkoJCIgHRwaMjEvLSsqKSkoJyYlJCSGI5UiSSMjIyQkJCUlJicnJygoKSkqKisrLCwtLi4vMDAxMjIyS3lsbHFzdHRycm9qcG1mbnFyc3NzdHV1dHR1dnd3d3V0c2txcGVqbV6EXSheXl9hYmNkZWZnaGlqa2tsbG1ubm1ubm1tbWxra2pqaWloaGhnZ2ZmhGVTZF5ZWmRrbG8uMzc0MzNISXRzc3Y0SJCYoKWZkIN0cn5RQjwlPm5zfHllYF1gZXB6enVhX2BhY2x1bmFWVFddY2ZsXVVSUlNVXmdrXlVVV1pfbnBmJQcGBiYnKSspJicpKy4wNDlFVzE8JyUmLlNFQjcwOio1NFY+UEJBSU1BMFJMUlg0LwYGBhdUQDQuLixVSkQ+Pj04ODg6QSUjIwYDBQYHDy0qLDM/Jkw5PT8cDAYGBQUGBx09PTw7hjkEOjo6O4Q6Ajk4hjkGODc2Nzc3hDaEN4Q2CzU1NjY2NzY3Nzg4hTkQODc2NTJdMDxMR0FTYGZnZIRlH2ZnaGlqa2trbGljWUs/U3F+m5CBfHl3dnV0c3JxcHCEb4RuYm1tbm5ub3BxcnN0dnd5eXt8fn+Ag4SFhoaHiYqLjI2Oj5GVlpeZnZ6ip7C5y5+qmsG5sKack4qC9eXYzMS8tbGzxtfUzMO5qpWA2LynmY/9w4ijrZaZmZiVkI2KiYiHhoaGhYUGhIOCgYKChYQOhoWFh4iJioqLi4yMjYyEiwGMhIsGioqKi4uLhIwBi4SMIYuLiYmIioyMjY6OjY6PkpKTkpKTlJSVlZOTkpORj46NjIWLC4qJioqKiYmIiIiHhoVohISEg4OCgYCAf399fXx8fXx8fX19fn+Bg4SFhoiJiYqKiouMjY6Oj5CRk5WWl5ea18eXzfyEiY2Unq3E2/H+g4aGg+nJwMLFyMzS2+Tw/IiRnKu8zeDzgYqWjb7DgKywpJGGg4aIiYmEioCJh4eGhYOBgH9/fnRhnoJ8gIKDg4aJioyPkZabn6autL7K2u+Glae92veNn7PK4/2Mm6e0v8vU2uDj5ujr7vDx8/P19fb3+Pf39vmExtLb7vz165+joZ6IyG1yebWNpq2vnN3/pKCak4aPrsnhgYSVmJqTz+mVlpCItt+5m/C1uICIvdbLzM28trC1zoWFQkX0uLW3urupm5uVi8L6l7mU0IObscPGx8XEwbmvpZmM/ebQvaeThOfMsqCOgO3czb2ypqGZj42NiIR9eHVybGhnan9MV19hYmJjZGZmZmVkY2FgX15eXl1cYWmS3deX/uqjqKOblpCJhP/27OPYz8XAuRSzraikoZ2cmpqamJaVlJOTk5KSk4WSPJOTlJWVlpiZm52eoKKjpaeqrK+xtLi7v8HDyMvN0dXW2Nve4OHi4LVwXVlYVVNTUlFOSk9MRUlLTU1NTodPD05OT05NS0ZLSkJFRj09PYU+Dz9AQEFBQkNDRUZHSEdISIRJaUhIR0dHRkVFRENDQ0JCQUFAPz4+PT08OTQ1PUJDRREIBwYGBg8iR0dGPg0ZPkNERDw3LyclIgkFBAQRIiMmJR8dHB0fIiYlJCAgISEiIyYjHx0dHR4gICEeHBsbHBweICIfHR0eICImJwR/goKCj38RgICBg4WIioiJhoaEgYCAf3+HfoR9Bn5+goKCf4Z9i3wJfX19gIKCgoF/hX0Hfn5/f3+AgYaCAYDBfwF+4H8EgIGCg4mEkoOFggSBgYGA/3+ufwSAgYGBioKEg4yCiIOEhAWDgoKBgJl/l36Gf4aAmoGCgId/hIACfnuEfIWAgn+EgAF+hH0BfoWAgn+EgAZ+fX19fH2LfwZ8e3t8fH2KfwZ9fH5/gICOgYeAhn+Wfpl/BYCBgoKDiITJgwGB4H8BgYWCAoGAhH8CgYCKfwWBgoKCgK5/AgIEAIC/4s/C1Kamrre3o5KSkpSerLvP8oyt8qS84syriPzYs5v1vvvLqpyGi4KH/oOI8PzwyNS3262Xh+C6mIOCh4mOko6PjoeNrdnp/ZLfvMDL3/fWjdybtrzKjoqbrMbo2NOOtK+rpaKjo6OhoaCio6KioqGhoqGgoJ+enZ2cnJ2dmwebm5qZmpqahpkCmJmFmFqXl5eWlpWVlZSTkI6KhfeEl6umn6isqKmrra+ws7W6wcHBwMHEycrLzci6xMTCw7+9vLy6ube2trW0s7GwsK+urq2srKuqqqmop6ipqaipqKampaKenZ6foaGGooShIaKioaKgn56dnZuXlZidWDQdEwoJBwYFBAMCBQUGBwgJCoULUQwNGxwcGxouK0+UkpOUk5OUlpeYmJiXlpiZm52en52cm5qZmJqbnZ2dn6ChoKCgn6CgoqGhoqKioaGjo6OhoKCho6OjoqGgoqOjoaGhoKGiooSjFqKioqOjpKOjo6Slpqemp6emp6ipqqyErQasrKurrK2ErBqura+wsbGxsrSztre4t7e3uLm6ubq6u7u8vIS9Db6+vsDAwMHAv8DBwsOHxAPFxMWFxoTFOsbFxsjGxsXFZDIyGRobHB4gJCYTExQUFSkoKCkqKywtLjAYGhsbHB0eHxAbExw0Yr26trS1tLW1t7mEugO5urqEuTG4sp2C07y+vr+8uby/wMHBwsPDw8XIycvNaGlqa2tsNjg5OjsfICAiIyQkJCUlJSYmhicEJiIfHognISYsND0tLSsrN0AxMzIzZiQeFhohMjU3ODA2Tzc2NTYfE4QUgDs3Nzk1OFY9PTw6JBMTExIeXWVefHd5ent2ennRo4HHpXR/fn2Ag3t1enh3R4eSlZHCXldMRksrNB4iJiYmJSUlJCMiICAfPTs7OzlwcHFxcG3X1dTW19PS09PV1dPRz87My8rL4IWftLi6u729vr6+vbq4uLi3trSzsa+xtL0zDRsfFQwMCwoJCAcHDw+GDgEPhxCOD4UQAQ+IEIoRBhISEhEREYUSShMTExQUFCq/r6i3uLe2tbW0rLe0o7C1uLm6urm5ury7vLu9vb27ubast7Kjsa+Zm5ycnZ2fn6CipKanqaqrrK2trq+wsLCvsLCwhq9irq6ur66traytrayqqKeknJKYrbS4zcXAvrzEqZvlysbCwcTO09ve5vbY2PLNxsTPpo/Tt53W1t/UsKSkqK/M1dTMoqCfoqe4w76nmZWWnKayt6SUkI+Qk5upsJuOjJCUnatmaUg7OFdiYmdtbmNZWVtfaHB7k7Rpg7R7lLGbgGnEoYNytYuzjXZuX2NdYbpfZKqZRTo/P39tYliYhXNqZ2lrbW5tbG1qboCUmp9VVTw7PEBQekVwUmRnajAoLTI5REBCQGdmZmNihmOGZANjZGSEY4RigmGFYIJfiF6DXYRcRF1dXFtbWlpZV1ZUUU2OS1drZl97j5udlpSTkpOSlJaWl5eYmJeUkImDeYeOj5WPi4mHhoWFhYSDgYGAfn18fHt6eXl5hXiEeRN6ent6eXd1c3N1eXt8fX5+f39/hoCDgYWCMoOGildFLycgJSEeGxkXFiopJyYlJyotLSwrKSYiPDUwLStJPWKhoKGjo6Cdm5qZmZiZh5celpWVlJSTk5KRkZKSkZCRkJGRkJCRkJCRkJGRkI+PhY4ajY6Pj46OjY2Ojo6PkJGQkJCRkZKSk5OSk5OGlAWVlZaXl4SWAZiFlwyYlpaVlJOTk5KTkpKFkQWQkZKRkISRBJCRkpKFkYSShZEDkpKRipIIkZGQkI+QkJCEj4WOhJAyml86RScoKiwwNTtBIiQlJSVFPTo7PT4/QkVIJScpLjI1O0AiPCgtSnCfjoeEhYWGhoeGiIWHP4aFgHBck4SFhYaFhoiIiYuLjY6PkZOXmZygUldcYWdtOj5DSU4qLjI1ODo7PT4/QEFBQkJCQ0NDQjszMUNEQ4VEgENAQUYyMjExRUk1NjY3WDAuIy04Njk8PDQ+Vjk4ODczIiQlJiY7ODg5NjtUOjo6ODwiIyIhMFVUUmdmaGVkYGJhnYBqoYNhaWhmaGplYGVoa0NWWl1Yd1JORkRWNkUqMjg7Ojk4ODY0MS4sKEpFQTw5bWljX1tXqaagnZycmZeUQJKQjo2Li4mJiYqWWmx5fH5+gICAf39+e3p6enl5eHh2dnh7oj4qNzMkJSIgHhwbGjIwLy0sKikpKCcmJSUkJCSFIwIiI40ihiMwJCQkJSUmJicnJygoKSkqKisqKywsLi8vMDAwMjI9fHNrc3V1c3Jwb2lyb2NtcHFyhXMEdHN0dIR1CXJvanJtZGxqW4VdDl5fYGJjZGRlZmdoaWpqh2sQbGxramppaWhoZ2doZ2dmZoVlVGNdWFtlamxxPjc2NTgwNXp1dXV6f4uOk5SZoYWKm3pxb3JNLjwyL3J4fnljXF1gY3R8fHdfXVxdYGpvbF1VU1VaYGhrXlVSUlJVW2NoWlNSVFdcYlkkCwYGEyQkJigoJSIjJCUnKzA7TC01QzI+STw1LVM6My9SP05BNjQwMzE3bjg9ZFINBgYJOzMvLE9KQjs6OTk5Ojg5ODc6P0FDSCYVBgQFBxFBIDkuPD8/DoYGBw4dOzw8OjiIOQE6hDsDOjk4hTkEODg4N4c2BTc3NzY2iDWGNjI3Nzc2NjU0MjFdMTlKRj1QXWRmY2NiYWJiY2NkZGVlZmZjXlJGO1BufpaFfHh2dXRycoRxgm+FboRtWmxtbGxtb3BxcXFycG1oZGNnbXZ8gYSFhYWGh4eIiYqLjI6PkJKTlJaYmp6kgqeQiYq7tqugloyD9OXXzMbM4vDs49jJr4/kvqiYh8qIlpyTlJWTj4yLiYeHhoSHhoYKhYSDhIWDhIWFhoSFDIeIiImKi4uMjI2NjYaMRY2Njo6NjI6NjpGUlZeZm52foaKjoqKkpaWoqaqpqaqrqqurrKqqq62rq6uqqqmqpqSioJ+dnJqZl5WUk5GQjYuKiomIh4SGA4eGhISDgIKCgYGAf35+fX5+fX5/gIGBgoOEhYaHiIiJiYmLi4yLjIyNjY6Oj5CSk5SVlpjGwJjSgYiOlqG31PKEi46Qjv/Zx8nL0Nff6fiFkJ2tvNHl+obojZDZ986WioKEhoiJioqJiYiIhoWFhISCf315aliNfH6AgYOEiYyQlJWZnqKogLC4w9PogZGlu9j+lKnA2fKJmai2w87W29/j5+rt8fPy8/Pz69O2sfb49/j5+vr6+eT255uamJfz7J2hoJ7Zq76PuuGio6ipl9b6oJ6bmLyKkJWXmMKYmJqTx+GTkpCL1IWHhoPG7eem1c3Ny8W6vLrztJPa0tTHw8DFy8TB0tjeSJJHSUtOu7m+uLv/rueUrcXT09DNycO8raGRgunPs5+O89i7oo2E6dPAtKyopJyQh4N8d3NuamlnZW1DT1xeYWFiYmJhYF9dXYZeKF1eZG/tnoa3u5espp+YkoyF/vTo3dXOx7+5sa2ppqOhn5ybmZeXlZSGk4SSgJOSk5SVlpeYmpyen6CjpaeprK6xs7a6vL/CxcjKzdDT1djb3d7d1nphVFZWVVNSUU5IT01ESUpKS0xNTUxMTk1OTU1MTUxLSUdLSEFGRTo8PT4+PT4+Pj9AQEFBQkNDRUZGRkdHSEhIR0ZGRUVERERDQ0JCQ0FBQEA/Pz4+PTs3UjM2PkFDRBAHBwYFBhBHSEhFPzxAQUJCQ0IyNzwqJSIiEAQEBAgjJCUjHhwdHh8lJygmICAgISIiJCIfHRwdHh8hIR4cGxscHB0fIR0cHB4gIiMFf4GCgoCPfw+AgICDhYSDg4OBgICAf3+Ifgl9fn59fYCCgoGEfZJ8Cn1+gYKCgX99fn6EfwGBhoICgYDBfwF+438EgIGCg4iEjoOFggOBgYD/f7Z/A4CBgYiChYOKgoiDBoSDg4KBgJl/lX6Gf4WAoIEDgH9/hICCf4SAA35+f4iAgn+EgAGBhYKFgIJ/hIABgYSCA4F/fol/Bn19fXx8fop/AX6EfAF9hX+CgI6BhYCGf5R+mX8EgYKCg4iEx4MBgt9/AYGFggGBk38FgIKCgoGtfwICBABqqPfQxreinqavt7yskZOcpKyzvcvigZiru8TBt6+gkv/XqYfuuZKOmaOL8MW5uMaQksq786yH36uPiYWHhoWDgoGB+fr+gYOux8va7orh8I2Wmey/uL6MzZOit9Tro4bWtK6rpKKioqGiooahBaKjoqGghJ4SnZycnp6dm5uamZqbmpqZmZmYhZdkmJiXl5aWl5aTkpKRkZCNiYP3g5SppqGoqqSipaeoq6yvsba3tbW0tbm9vb24rbW6vby6uri3t7e1tLOysrKxsbCwr62srKyrq6uqqamoqaiop6aelJeXmZucnJqXl5manaGhoYigMp+enp2dm5qYlpWWmaIwHCAQBgQDAQEBBAUGCAQEBAoLDBwcGxoxK1GTkZGQkJOUlZaWiZgrm5qcnp+enJycmpmam52eoaCgoqGhoZ+goaGioaKhoaChoqOioJ+goZ+goYSgAZ+EoASfn5+ghqEKoKGhoKGioqSkpYamFKepqqusra2srKytrKuqqqmoqamphaouqamoqKmrq62usrS1t7e7vbu8vLy9vr69vr+/wMDBwsLCw8PDxcXGxsXGxcXGxoTHB8bGxsfIx8iExmbIycnKycdlMzQbGx0gIycVFhYXFxYrKywuMDI0NhwdHiAhIRkiNjFiv7u4t7e2tLW1tba3t7i3t7a2trW0qpHuwrq6u72+v728vr7Aw8XGxsbIys1na2xsazY2Nzk8Hh8hIyQlJSWLJhsjHhkoIj05MjsdJicmJyYmJSUrMkAsKywrNkGEMg0uFBYWFxc4NTc3LzRNhDQCIxSEFYA5ODk6NTZWPj4+PCUTExQUITVeXX97fn+Efn+A4uno4WFYgoGAgoZ/enx9eEV55eH7jD49O2xtcmxbTDcxJTEdISUlJSQkJCMiISAePTk6ODhwb3BxbdrX1tbV1NHPysnLysrJysrIyc/6lq63urq5ube4uLi3t7a1tbSzsbCwsg+0YjYdEgsLCgoJCAgIEA+FDoIPhBADDxAQjA+QEIcRBBISERGIEoUTQRQWyrSos7e2tbO0s6qzsKKutbi5urm4uLm4ubm7vLy8u7mxsbarp7GlmpydnJydoKGhoqOlp6ipqaqqq62urq6whK8Irq+vrq6tra2ErFyrq6ysq6qppp6Wm660uvP7u7GuvIvRzsnIxsXEytTX1trk6Pb3587Kx8rN55G4x4CP19vPqKalp7DGz9C1oZ2bn6q7wb6pk5KVmqGqvLOTjo6QlJmps6KQh4mOmGhdTzw4RF5cY2lvcWZUVV1manB5hp5dbn6OlJGIgHVqt5RzW5+BZ2RpcWKkg3h2eUMpOztwalyghHRubG5tbGxqaGbJy81oan6MkpinXHVQKScrXmFmaD1AKy80PUUxLm5mZWViYWJiYoRjCWRkZWRkZGNjY4diAWGFYAlfYGBfX15eXl+EXh1dXVxcXFtbXFtbWllXVlRSUEyKSlZrZl93jJiYkYaPLJGQj4+Pjo6LhX11bH2Ij5GKiIaFhYSCgoGBgICAf359fHt7enl4eHh3eHh5hXgUcGpqbXBzdXRycXJzdnp9f39/gICFgTKCg4ODhISFhoaHiYydPixCNiAdGhgXFiopKi0YFxcuKyY/NS8tTkBmo6CjpaSenZybmoeYEpmYmJaXmJiWlpWUlJSTkpKRk4SQg5GEkISRC5CRkI+QkZGSkpOUhJWFlgGVhJSFkwKSkYaSBZOTk5SVhZQPlZaWlpeXlpeWlpWUk5OSh5OElASVlJSUhJMCkpGEkoKRh5IBkYSSCJOSk5OTkpKThZIBkYSQBY+Pj46PhI43jY2Njo+QkJCPnWM9SigqLTE4QCQmJygnJEA8PT5AQ0VJJyouMzc7MT1SQl+SioWEhYWFhoaGh4iGL4WDe2ioiIODhIWFhYaGiIqLjY+Qk5aYnaNUWF1jazk/RElPKy8zNzo8Pj9AQEBBh0IKOzIpPS5GQ0RHMIRDDkJCQUE/P0cxMDExQUg1hDYMJiorKytAODo6MjtRhDZ5OSYnKCgmPTc4OjU6UTs6Ojk+IyQlJDUuUE1raGpnaWRlaKu0tK9YV2xraGpuaGVqbGpMZLemrl04NzVjZGZeU0k5OC9DKzM6PDs7Ojg2NDAsKU1HQj47bmZhW1iqpKKfnJiUkI2Li4uKiYmIhoaKpWR1e3x8fHt7e4d6JHl5eHZ3eoZoTC8pHyQiHx4cGhkzMS8tLCsqKSgoJyYmJSQkJIYjAyIiI4kihyMGJCQlJSUmhCcmKCgpKSoqKisrLC0tLi8wMDAqhndqcnV0c3Fwb2lwb2NscHFycnOGcg50dXR0c3JsbHBoZmxjW4RdDl5eX2BhYmNkZWVmZ2doiWkGamlpaGhohWcDZmdmhWVVY11XWmdqbHdMNjMxNipTdnZ3eYCDiJGSkZSZm6WhkHt2cnJyfTg2NSM+eH54Xl5eX2RzeXppXl1cX2VucW9hU1JUWV1jbGdVUlJTVlliaF5TUFBSV1giEQcGCiMjJSgpKCYfHyIlJisxNkIpLjM4Ozk2NDEtT0AwJUE3Ly43PzlbRTw5ORwFBQYeMS9aTkdEQkNBQDw6OTZmaGg5Oj1CREZIKSkKAwQHJTY+QB4NhAYKBwcOPT48Ozg4OIc5Bjo7Ojs7Ooc5CDg4ODc3Njc3hDYENzc2NoQ1AzQ0NYY2hDUwNDU0MzEuVy44SUM9Tl1kY2BgX19fYGBhYGFhYWJiYFpPQzlRa36KfHd0c3JxcG9whG+FbgFthWxka2tramxtbW1oWEtNVFtjaGhmY2NncX6ChIWFhoaHiIiJiYqLjI2Njo6QkpOVmsmOiOz5r6ugloyD8uTn/oaFgvfYqfrGqpfom6ihkpSUkoyKiYeFhYWGhoWFhoeHiIeHiIiHh4SGbIeGhoaIhYaHh4eIiYqKi4yNjo6Pjo+Ojo+Qk5icn6OlqqyvsLCwrq6urayrrKyqqaioqaenpqalpqSlpqenpqamp6eop6enpqampKKhoaGgn56cm5qZmZiXlZSTkpCPjoyLiYmHiIeGhIODgoWBGIB/fn5+f4B/gIGBgoODhIaHiIiJiYmKioeLgIyMjI2Ojo2Oj5GUlpWV29qq6oePmKnI7omSlpiXiOTJy9LX4u37h5OissfWrcb/ramikIWEhoeIiYiIh4eGhoWEgoKBgH94ZKB/fH6AgYOIi42Rk5ecoaaxvMfW7oWbs8/yjaO92feMn7C+y9Xb4OTo6uvt7vHx8vPv1LSO0ZPcgOT587P39vb49/b089zr4ZWVl5bq6p6goaC/lqOlp6TFoaSjk8zxmJiXltCVmJqbmb6Tk5eQvdmRkI6K1oiKjIzZgcia08vMx8m9vMHp7u7epO7LxsTK0cnG093gsJ//yeSwgISC9vj77tLApKmY45e2097d29fSyLmqmon12r6iKYvsz7OWgufVy8Cyp5uNiIN8c3FqZ2ZjY2R7S1hcXl5eXV1cXF5eX15fhV5lYWmH29+ckoatq6KakYmC+fPp39XNx8G7tbCsqaWhnpybmZiXlpWUk5STk5KSkpOTlJOUlZaXl5manJ2foaOlpqqsr7G0t7m8vsDDxsrNz9LU1tfYpZVnVVdXVVNSUlBKTkxDR0qGSxJMS0xMTU1MTU1MSEhLREJGQTyEPYI+hD8LQEFBQUJDQ0NFRUaERwdGRkVFRENDhUIBQYRAOj8/Pz08ODQ2PkBBQQ8HBgYGByFJSEdCPjw/QkNCQ0RERUE2KyclJCMkCAQDBBAkJSMeHh4fISUmJiOEIRsiIyQjHxwcHB0eHyEfHBsbGxwdICIgHR0eHyAFf4CCgoGQf4qAhH+HfoV9B36CgoJ+fX2MfIN7h3wGfX2AgoKBhH8CgIGGggGBwn8Bfud/BIGCgoOGhISDBoSEhIODg4SCA4GBgP9/vn8DgIGBhoKGg4iCh4MEgoGBgJd/lX6Ff4WAloGCgIR/iYEDgH9/hICCf4WAhYKFgIJ/hIABgYWChYCCf4SAAYGEggOBgH6Jf4R+jH8Jfnx7e3t+gICAiH+CgI2BhYCFf5R+l38EgIGCg4iExoPefwGBhYIBgJZ/BYGCgoKAq38CAgQAb6eFwsaCsaSrr7KxsKygmpiYnKOpqqisxdjf5t/Yxbmgjf/hwLCch/Po19O1l5atzcjSoI/KsKSYiP2Bh42Jhfrx6/Hu6+fmhKO3xc3g/ZLusYe8ucLzg5qsyerM0aq2sK2ppaSjo6GgoaGioaGgoIShAaCFngWdnJydnISbdZqZmpuZmZmYmZmYl5eWlpeXl5aVlZSTk5KQjoqHgO+DkaainKKmoqGipaamqKqsr7Cvr62tr7Gzsayjr7a6tbW2trWzs7KysrGxsLCxsK+vr66sq6qqqaqqqqmoqKinnZGan4jgw6ylqLbO8pGfm5Wanp+foISfgp6EnRScm5qYlpSSk5ecWxsbCgIBAAABA4QERQoNHB0bLlOYjo2NjpCTk5OUlZWVl5iYmZiYmZqZmZqbnqChn56dnZybm5ycn5+foKCfn6GioaKhoaGfn56fnp6enZ2en4SehJ+EngWfnp6en4ShhKIToaKjpKSkpaWmqKiqq6qqrK2ur4WwDq2sq6ysqqurrKuqqamohKcipqalpqampaalp6aoqKCUnqmyur29v8DAwcHCw8TExcXFxofHhcgJx8jIx8fGx8fHhcYmx8nJycrKyWQzNRscHiMoFhgZGRkYLy8xNDc6PSAiITEfNTLDv7mEtwG2ibUqtLWzsqiL4rm3uLi5urm6vb69vsDDxMfIyspmZ2prNTc4ODkeHyEjJSUlhiYsJycnJiYlIRwtIz1CTS0zMTAnMjklISQlJSUkJCMoMT0sLC0pNEAxMjI0MRSEFgw3NDU1LjNMNTU0NCKFFAc6Nzg7NzRShD+AJBMUFBQhOGBfgoGEhYuEg4jueHjraS+HhYWGiIJ/gIKARCAaEyiifHU6b3E7PDs3R46Ok5mKQyQqGh8kJSQkJCUkIiEgHjs5ODk4b3Fxb23Y0s3Ky8rIycfHyMjIx8jJy++QqbG0tre4uLm3trW3trW0s7Kxr7CzXzQcIhULCwoGCQkJCBAQkA8BEIcPkRCGEYYSBhMSExMTEoYTRxSBuqqwt7a3tbW0rLO2oay0t7m5uLm5uLi5ubq7urq6t6yytaerr5yYmpqcnJ2eoKGio6Snp6ipqaqrra2sr66ur66tra2uhK2CrISrW6ytrKuqqJ+WnLG3vJePsKKWp96TzMrKx8O8u7zBx8/e4OPr9+vc0c3NztLgn4/RsPDY3sGnpqevus/UxK2dm52gqbfAsZmRk5aZo665r5eIipKco7S0n4uMk5tkYDc1OC1oYWdpa2pqZl1bXV5jaW5zd3iKl6CinJOGemZcqJWBeGxfqJyRi3ZgX2Q3OT06UIJ3dHBq0m91eXh12tPOzsfAwsRqeoaRmqq7Y308NmNmaFYnLDA4QzxBRmhnZWJgYYViD2NjY2RkY2RjY2NiYWFiYoRhCmBgYF9gYGBfX2CGXyNeXl1dXFxcW1taWVlZWFdVU1BNSohKVmplXniMlpSOjY2MjIaLFomIh4R9dGtmeoSNi4eFhIOCgYGAf3+Efgl9fHx7e3p6eXiHdxd4d29ocXZmq5SLhomSpr9xe3l2e35/gISBCYKDg4OEhYWGh4WII4qNl3IpOi0bGRcWFhkaGhkYKiI2LytHc7CgpaimoZ6amZmZhpgXmZiYmZiYmJeXmJmXlpaWlJSTk5OSkZCGkRiQkJGRkpSWmJqbm5ucmpmZmZiYl5aWlZWElAaTlJSTkpKEkSWQkZGQkJCRkZGSkpOTlJWVlZSWlpiYmJeXl5aVlJSTkpGRkZKSiZEGkpGRkpKShZMOkIFveIGJj5GRkpKSk5KGkwWUlJOTk4SSCJGRkJCQj4+PhI6JjWKOkJGRklY3RSgrLzQ/JCgpKiglQj0+QUNHSygsLkwwTDumj4mGhYaFhoWFhYaGhoWFhYSEg4N6ZKGDgYGCg4OEhIaHiImNj5KYmp6jVVpfZTY7QEZNKi4yNTk7PT9AQUFCQoVDEkE4LkUxSEhRMDc3NixAQSs5PoQ/gD49Oj1EMTAyLj9GNDY2NzolKisrKj83NzcwOU81NjY1OCUmJyclPDg4OjY5UDs6Ojk9IyMkJDUwTE5ua25scGpqbLJfXrdgMW5tbG1wa2lrb3BDMSwiLXZtazVlZjQ3NzVMUFRVZW1ILDwpMzo+PTw6OTc0MS0pTEdAPDhrZF1YHFShnJmWkY2Mi4iIh4aEg4KDhJ5gcnd5enp5eXqFeyV6enl4d3Z3fFdELEI4IiEfHRwbGjMxLy4sKyopKCcnJiUlJCQkhSMEIiMjI4kihyMEJCQkJYQmFicnKCgoKSkqKisrKy0tLS4uLnJ5a26Ecw1xb2pub2Jpb3BycXFxhXIgc3RycnJwam5wZGhrXltcXV1dXl5eX2FiY2RkZWVlZmeMaARnZ2dohmcBZoRlVmNdWFtoamxBKjMvKzBHRnd4fIKCfHx9gYSMlpaZnqWThHt2dXR1e00sOjBRdoBvXl5gZWt4fHRoXFxdX2NrcGZYUlNWWWBlamVYTk9UWl5oaV1SUVVaZSMRBgYFJSQnJygoKCcjIyMlKCsuMTQzOT4/QT88NTArJkZBPD47M1tPRUE5MjEuCAYFCSdDQD9AQX1CR0tJRoB3dHJtbGpsOD1BR0tTXTQ2DhQ6Pz8dBgYGBQYHDh09PT07ODg4hzkCOjmFOoM5hDgDOTg4hDcCODeLNoQ1IDY2Nzc2NTU0NDMyMC8tVS84R0M8TVxiYl5dXl1dXl5fhGATX19eWExBO1dqf4J2c3Fwb29ubYZuBW1sbW1timslbGxqWElca2GijX13eoSVtGx5dnd+goSEhYWGhoeHiIiIiYqKioSLIYyOla34gdrinpyRh4aQl5iUiNGRzbGQyOXGkpOUkY+MiISGAoWGhIVFhISFhoeIiYqLi4mIh4eIiIeIh4eGhYeJiYqKi4uMjY6QlJqeoaerr7Gys7OysbCvr6+urauqqqmpp6emp6ako6SlpKSjhKIqoaChoqKio6Sko6KjoqGgoaCgnp6enZ2cm5iXlpaVlJSTkpCPjoyLi4mIhIcchYOBgYBzY2tzen+Af3+AgIGBgoKDg4SEhoaIiYWKhIuAjIyMi4uLjI2MjIuLjIyLjI2QlJaYoJOKzYSQnbXgiJifn5uL48nM09rm8oOSmfaY6YzomIqEhYeIiYiIh4aFhYWEg4KCgH94YZx9e3t8gIOGiI2Slpqgp6+6xtf1i5+52oGZs87tiJyuv8zW3OHm6evt8PLz9PT06seh7qTk3vKAi6Ompo/s2ZLX6uzu7Ozs6NPi3ZOTmZHf45ugoaHJlaKmpqHCnp6djcTolZaVksyTlZeXlbyTkZKMs9CPjImI0YWHiYrUgbeSz83Ny8rBvsP5gID2uYvQyMbM0s7N1OHkur2piJD4/P+A/PyCiIuL105IS4rzyIzMj7bX5+Xh3dcnzMCvnorv0LWagty4pI+B4tK8q5qTjoh/dW5saGdjYWFyRVVbXFxchV2EXilfX15eX2ZyjrWR6u2npp2VjoiD+/Lp3tbOyMO8trCsqKShn52cm5mXlYSUgpOEklWTk5OUlJWWl5iZm52eoKKkpqmsr7Gztbi5vL7Aw8fKzM7P0MrnallWWFZVU1FQSk5OQkVJSktKSktLS0xMS0pLTExMS0ZJSkJDRj08Pj49PT49Pj8/hEAXQUJCQ0NEREVGRURFRUREQ0NCQUFCQUGFQEU/Pz8+PDc0N0BCQiEIBwYFBg4iSUpKRD46NzY3OT1DQUJERTcvKygoJyUmEQQDBBIlJSIfHx8hIiUmJSMhISIiIyMkIR6EHREfICEgHRoaHR8gIyMfHR4gIQV/gIKCgpx/hn6IfQWAgoKBfYV8AXuFfIh7h3wIfX2AgH9/f4CGggKBgMJ/AX7Ff4h+nX8EgIKCg4qEB4ODgoKCgYD/f8V/A4CBgYWChoOHggeDg4OCgoGBl3+UfoR/hYCVgQWAgH9/f4WAA39/gIiBA4B/f4SAgn+FgIWChYCCf4SAAYGFgoWAgn+EgAGBhIIDgYB+iX8Gfn9/fn+Ain8LgIGBgX9+f3+Af3+EgAF/hHwEfX+AgI2BhYCFf5J+ln8FgIGCgoOHhMSDAYDbfwGAhYICgYCYfwWAgoKCgKp/AgIEAGmgu6G9mLielo+Jiof/9/n+9PGAg4SJjpGYm52XjYf78e7fzbqd5ci7x8Cviubz4pTLqIrHsZyNgfXv/YH49/yB+/DQxcrV4eyHtPefrZHp/rC1toLCkqS93d+J+NizrqqlpKWkoqGhoKCFoRifoKGgn56dnp6fnp2bm5qZmpubm5qbmpqEmQmamJmYl5eXlpWElDGRkI+Oi4iD+OiDkaSimqCioJ6go6Skpaeoq6urqqqqrKytq6OfrLK2s7Kxs7OysrOyhLENsK+vsK+urq2trayqqYSqF6mpqJ2Woomrk5WTjYmHhomLlMuVnJKZhJ4Cn56EnYacVpuZl5aUkpGTl1kZGwYAAgMEBAQLGx0aLlGUjoyOjo+Sk5GTk5STk5SVl5mam5qZm5ybnJydn6KkoaCfoJ+fnZ2en6GhoaCfn52bm5qXlpaYmpqcnJydhZ4MnZ2enp6foJ+enp6fhKAGoaKio6OihKMqpKSlpqaoqqusra6trq+xs7O0tLSzsa+vra2urq2trKuqqqmpp6inqKemhaUjo6Kjo6GYsKa0x9ni9IWSnKi0vsLExMTFxcbGx8jJyMjIycmGygHIh8c0xsfHx8jIx8fHyczNzc3OaTUcHiEoFxocHBwbNDQ2OkA7Kx01YsG/uri4uLa2tbW0tbW0s4SyC7Gojt+3tLSztLa4hLwWv8DBwcPFyWVoa202Nzc5Ox8gIiMkJIQliCYXIhwsH0lwTi0yMTIyNjMuLyUyNz4nICKEI3AiJzA9LSwsKjRBMjMzMzETFRYWFTgzMzMsMk02NjY1IhMUFBMTOjc5PDc1VEBBQkAjExQUEyBpXWqHhomLkIqKkHh7e+9qL4uLiIiJhoCFhYQqFRYTIVh9d3VwcDs9OzlFauTh7cc6OTYtT0AkKxsghCVGJCQjISEfHTs6OThwbGxt0c3QzcvKyMbExMXFxsTDwcLljKixtLW1tra3t7e2tra1tLOxr7G0ujIcIBQLDAsKCgkJERERD4YOjw+SEIYRBRISExMThBIDExMShRMTJ7+tprS2t7a0tK+xtqSssrW2uIW5griEuYC4t661taWvrpeampubnZ+foaOjpKWnp6eoqamqrKytrKysra2sq6usraytrqysrKurrKytrKypnZWdsrm/tp2pl4mfr+zNzMK+ydDS1Nfd3eTl5+Lt/YHt1c7Oz9XY5PPFy8a35uC6pqissrzL1LKlnJyep7C4u8Kdj5GYm6q6wQ20koyVnqOts62Yk5ebaV1hJzYybl1aV1VXVqahoaSgn1RUVVlcYGVmZWFcV6CcnJGIfmqeiYKIgHNemZFbJzk0Pm1oYVxYr7bRbt7f4nTn3cS2tbzGzm+DrHd9Z4yEXGNkOjwqLjQ+QChWcGdmZWFgYWJiYmFiYoVjhGICYWKGYYhgAV+FYAVfX15eXoRdQlxbW1paWVlYVlVTUEyPhEpXamRdeYuUkoyMjIuKioiIh4iHhoSDfnZtZGN7g4yIg4KBgYB/f39+fX19fHx8e3t6eoR5gniHdyVvbHhnhXV2cm9tbW1ucXihdn16fH+AgIGBgoOEg4SFhYaHiImJhIoei4yQlmsoNicXGRscHRknOS8pQmeio6appKCdm5qZhJg7l5eXmJiXmJiZmZeYmJeYmpuZl5aWlZWUlJSTk5KTkpKUl5udoaSlpaSjoqGfnpybmZmYl5aWlpWUk5SFk4KShJEukJCPkJCQj4+Qj5CQkZKTlJSWl5eYmJiZmJmZmJmYl5eUk5KSkZGRkJCQj4+Oj4SOBI+Pj46FjxCNhn9wfImUn7Bha3V+iJGShJMGlJOUlJSThpIDkZGQhI8Hjo6OjY2NjISNK4yMjI6PkZOUlJ5nQigsMTokKSsrKSVBPT9BREU5Kkhrm4+IhoeHhoaFhYOFhC6Dg4KBemSdgYCAgIGCg4OEhYeKjI+SlZmgVFleZDY7QEZOKi8zNzk7PT4/QEFChUOAQjovRipTa0MxNzY3ODs6NTYrPj5BOjY7PDw8PTs5PEMwMDEvPkc0NTY3OiUpKiopPzY1Ni83TzY2NjU2JCUmJiU7Nzg6NzhOOzs7OTwjJCQjM2BKVXJxcXB1bm1yXWJivmMxcXFvb29uam5xcDUiIyEsSW9samZnNjc2NU9bnJQ5mns4OTcsU0gsPSs1PT8+Pj07ODQvKyhJQj04aWFaVaGal5OQjYqJiIaEgoGCgX+Al11xd3h5eXp6hXssenp5eHd1dnqXPCo5MSEhIB4dHBs0MjEuLSsqKSkoJyYmJSUkJCMkIyMiIyOMIoYjLCQkJCUlJiYnJycoKCgpKSkqKisrKywsLDJ8bmlwcXJxcHBrbW9jaG5wcXFwhHEtcnFycnJxcXBpb29iaWpaW1tcXF5eX19gYWJjZGRjZGVlZWZnZ2hoZ2doZ2hohWcFZmdnZ2aFZVdjXVhcaGtsRy0xLCcuPYR8fnuAjZGQkJOVlJmam5afq1aXgXx7fH1+g4ZDOTY2foJqX2FiZ213fWlgXFxdY2drbHBbUlNYW2Rsc2tUUVdcX2ZqZ1pXWFplIiAFBgYkJSYnJycoT0lHSUlGJCIhIiMkJCYmJCIiQ0ZLSENANFFEQkA+OS1JSCIEBQYSNzQwLi9ibHtBhYeKRod+cGxucHF1PkhsWVZES0EwOzsdDQYGBgUHBhw9PT07Ojk5ODiJOQE4hTmJOIc3QTY2NTU1NjY2NzY2NTU1NjY2NzY1NDMzMTAuV1MvOEc+OExYX15cXF1cW1xcXV1eXl5dXlxYTkFAXGp8eHFvbm5thWyCbYlsKmtra2xsa2tsbGxqV1JrZXpmZWRfXl1dYWRqkXB9hIKDg4OEhYWFhoaHh4SIUIeHiIiHiIiIi53ng9vTkZ6prqqEsuC1h6yrnpeZlpOOi4qJiIeHhoaFhoaHh4eGhoiIiImJioyNjYuKiYqKi4mJiomHh4mLjY+Sl5qfpKmshK4Hra+urKurq4WqCqmnqKempqWkoqKEo4KihaEGoJ+enp6ghaEKoqGgoKCfnp2dnYScNJmYl5eWlZSTkpKPj42Mi4qJiIeGhYWEg4KBgH53eGpxe4OLnFNdZW53f4KDhIWFhoaHiImGigOLioqEiwWKioqLioaJgIqKiouNkJWZm5zT37iBkqTLgpihn5eF18LFzdfWt4bO4raTh4OGiIiJiImJiIeGhYSDgoGAeWWbf39/gIKEiIuNj5Sepay3wtLtiJ643IObtdT1jaK0xNDY3uPm6uzu8PHz9PTvz6fuivD5m4+mqKqrsKuio4zjzcjdz9/j4+XmgOPK3dOPkZSQ2eCanZ2exZOfoqKewJqXl4m+5JSVlJDFjpGTk5G4kI+RiqvFiYeGhM6DiIiI1PysndTRz8zQxsLLgYSD/sSP0cnFyMzPzNjl6LKBhYOUm/////r8hYmLi9yHw5Wrz5mlnoDq2IrVmcLk7ezp5NvQvauVgd+/oorqUMaojvjcw7alm5KHhIB2cmtjYmBdcEVTWVpbW1tcXV1eXl9fX2BfXl5ibb6WiMfQnaWclI6Ggfjv6ODXz8jBu7Wwrammo6CenJqYlpOUlJKShZGEkjqTlJSVlpiZm5udn6Gjpaisra+xsrS2t7q9wMHCxMbFonBbVVhXVVNRUE1OTkNFSUpKSklKSktLS0pKhUsRRUlJQEVFOjw8Pj4+PT0+Pz+FQIRBAUKHQ4NChUEGQEBAQUFAhD84PTg0OEFCQyEIBwYFBhBLTU5LRkhIRUNCQUFDQT8/Q0ciOi8tKyoqKiknCQQECSUlIR8fICEjJCWFIRojJCQjJB8dHR8fISIiIB0cHyAiJSQiICAgIQV/f4KCgod/hn6Mf4d+h30HfHx9goKCfoV8CHt7e3x7e3t8iHuDfIR9Bn5/f3+AgYaCAYDCf4J+xH+Mfp1/BICCgoOGhAaDgoKCgYD/f5J/h36zfwKAgYSChoOIggKBgJZ/k36Ef4WAlIEFgIB+fX6KgAR/f3+Ah4EDgH9/hICCf4WAhYKFgIJ/hIABgYWChYCCf4SAAYGEggOBf36MfwN+f4CKfwWBgoKCgIZ/hIAGf3x7e3t9hIAEf3+AgI2BhICEf5J+ln8EgYKCg4eEwoMBgtp/AYCFggGBkn8BgIl/BIGCgoGpfwICBABnjo+/loal9tS6z9SoorLE0u34g4SA/4GDgoH/9Ovf09HLw8C2nNWho7S5sJTxhPfGztPjsZuK+ezu5ebf19nZ6url5NjM0dDA8Zmqk7WqibC3wvmAma/N5KefjrOwraajo6SjoaGgoYigFKGgn56dnZ6enJycm5qZm5qamZmZhpoBmYaYOZeVlJOTlZKQjo2Kh4H07IWWpKGZnZ+enZ2foaKjpaaop6ampqiprKynnp2rr7KwsbGxsrGwsbCxsYSwJ7Gxr66trKytrKyqqqqrrKqpqaaXpJidl5OTlJeZlo+KiJGdwJ2ak4Sdgp6HnISbPJqYl5eUkY+PlJlWGhkIAwkaHDQsT5KQkZGTk5KRk5WUlJSTk5SUlJWVl5mbnJybnZ6fn5+hpaiko6KhooShMp6amZWUkpKTlJSUlZeYmpqbnJ2dnp6enZ6enZ6en5+goKCfn5+hoaCgoqKhoaOioqOjhKSApaaoqq2vsbKys7S2t7m5uru5uLe1tbOzsq+urayqqaiop6ampqWlpKSlpKSko6OioqDsmo6A38jM7Yuetc7c5PuKl6SxvsTFxsjIyMnKysrLy8vKysnJyMnIyMfHyMfIx8XGx8nJysrLzc7P0NDR0Go3Oh8mMR4iIiA8ODMmODQ5ZMTAvby7uri3s7OysrKzsrGwr6+rkea4tba0s7S0tbi7vr++v8HCwsZkZ2lqNzg4Oh8gISIjJCUliSYcJB8ZJUFCNomcn6FUMDAvMTE0MCwtIzI3PR4aIIUiHyYvPy8tLSs0QzMzMjQwExUWFhU3MzMyLDFNNjY1NSKEE3sSOjg6PDk0U0JCQ0MlExMUFCFiYnGOjI+SlpCOlH+BgHxvMY+OjI2Mj4SHiYgpFRUTJltAPjw6cDs9PDo+GRITcXg6OTk2OTc1Myg8JyEYHiUnJiYlJSQjISAeODc3N21obGrRzs/MyMfGxcLDw8LDw8LC6Y+rsbW2traGtxa1tbSzsbK0uGU4HxYODQwLCgkJERAOhA2FDosPkxCHEYsShBM0Fcezo7G2ubi3trGwtKaosbW1t7i5ubm4uLe3uLm5ubastrKksKmXmZqcnJyen6Cio6SkpoSnFqmqq6yrqqurrKyqqqusrK2urqysq6uFrViroJefsrm/zKSmk4edjtTLy8e/w8nY5/iBiZSanJSOiIWE8dvRz83OzdLo8YXAzO/83rGpqrG8xdLWzKWcn6evtr/Iu5iZoqepsLSyopWTmJuWmZ2S8JSTZFRULisrapqJfISJdGx2fYOSmlBRTpxQVFRTo5+ZlI6Kg315c2KScnF2d3FgjUU+NzxMe2hdVaCan6axr6+0vsvQ1NbMwL7Bn8R3h3JyYktjZ2lXJiwxOkEwM0BoZmZhYGFiYWGEYoNjhmKHYY1ghF+EXhxdXVxcXFtaWVlYV1ZVU1FMjYRKWWhiXHyLko+LhIoliYiHhoaGhIOBe3BnXWZ8g4uFgoCAf39+fn19fXx7e3p7enp5eYR4Hnd3d3Z2d3d4d3Vqd3F6dnJwcHJ0c29sa3Z+lXyAfoSAMYGCgoOEhYWGh4eIiYmKiouMi4yMjI6Vaic2LRorOy9PPl+gpKeno56dm5qZmJiXlpaFlwSYmJmYh5kHmpyem5mZmIWXJJifo6errq+urKuopaOioqGgn52amZmZmJeWlpWUlJOTkpKSk4SRB5CQj46PkJCFjyGQj5CRkZKUlpiZm5ydnp6fn6Cgnp6bmZeVlZSTkZKRj46PjB2Li4qIumtnW6GNkadhcYCQmKC2ZG96hY6Sk5KTk4eSA5GRkIWPMo6NjYyMjYyLi4yMjY+PkJGSk5SVlptiQFEuNUInKSgmRDw5MFFDY5eOh4eIiIeGhISDhoKAgYB8aaOBf39/gIKDhIOEhomLjpGVmqJWXGJqOkBGTisuMzY4Ojw9Pj9AQEBBQUJCQDUoN0tIM1hiY2NJNTU0Nzc5NjM0KTw9PyoqNzo7PDw6NztHMzExLjxHNTY1NjgkKCoqKD02NTUuNk42NjU0NSMkJSQiOzY4Ozg2Tjw8PDtxPCMjJSQzWktieXV2d3t0cnhiZ2ZjZjJ0c3FycnVtb3V1NyIjIS9NOjk2NWk2ODg2QiwfImZSODs8OT07OTUrOisuJjI9QUFAPz47NjArJ0dBOjZmX1hTnZeUj4+MiIaEg4KAf359fplgc3d4eXl6enqEeyN6eXh3dnZ4iXFSNjEjJCIgHh0cNTMxLy0sKiooKCcmJiUkJIQjAyIjI40ihyMEJCQlJYQmhCcFKCgoKSmEKhIjf3Rmb3FxcXBwbWxvY2VtcHCGcQFyhXEKcG9ocG1ja2RZW4RcB15eX19gYWKEYwZkZGVlZmeEZgtnZ2doZ2ZmZ2ZmZ4RmW2VlZmReWF1oa25MLzArJi40fX6ChomQl5+qtV5jaWttZ2JfXFmgj4eFgoB+f4iMNDc4Q4N8ZGBiaG1zen53YVxdYmZrcHVtWltgYmNmaWlgWVdaXFpcXlWMV1VkISAIBQUjREQ+PDw7Nzc3Njs8HR0cOx4fHx9AQUFCRUI8MzEvKkY9Ozs4MipEIQcFBg00NDArU1NXWWJlbHJ0fHx9goB6e3ZsiVppWkQ2KDg9PhwGBgYFBgYNHT8+PTo5Ojo5OIk5Bjg4OTg4N4k4Azc3OIY3hjYBN4o2FDU1MzIxMC1VUy87R0A4TVhgXFpahVsTXFxcXVxcW1pTS0BGXmp4cWxra4hqjmuDbIRrGmhQaWxvZGBgYGNkZGBeXWl1h3mEj4KCgoOEhIaLhyCFg4KCgoaW6Yfm+5TS77H8l4OamJWTkY+Ni4uJiIiIhoeHBoiIiImJioSLA4yNjoSMFo2Mi4yOj5GXm5uipKWmp6inp6eoqaiEpwmlpKSmpqenp6aEpIKjh6GDoISfAaCEngmfoKGjoqKioaGEoj+goJ+enZybmpeXl5aVlZSSj46NjIuKiYiHhoWEgoGBgH9+fXqnX11UmoeDllVfbHmAhppWYm14goWHiImKioqOiS6Ih4eHiIeHh4iJiYuNjo+SlpiZnLXAq/eTsOSNl5GI6L6tmf+/vaWOg4OGiYmJhoeAhoWEg4J+aaSAfYCChIaHi4+Sl52jrLfH3P6VsdP7lrLS9ZCjtsXO193h5efq7O3u8PHx5byOuOPYhV5gY2aioaShpaaoopyghtrIvpqo1dvf4+ThxtjlmZaUj9PbmpybnbqOnKCgm7qXlZOEtd+SkY+Lv4uNjo2KsouMjoqkwIqAiIeF0IOFiYjP5KOq2tXS0dPJxM6IiYiCzJPSycbLz9fQ1unwvISHhJurgYOBgPyEioyKuK2Bj/eqlqippa+urKOGs4Kbhbfh9/fz7+fcyLGaguK/oYbdupyF5c2/qZ6al46Bd3BoY2FeXXFEVFhaW1xcXV1eXl9gYGBfXl1faI4k+/6zwJqkmpKMhoH78+vi2M/Iwbq1sayopKCdm5mYl5WUk5GQhJEBkoWRLpKSkpSVlpeYmZudoKGjpaiqqqqtrq+ytba3uLq6h4NhUVhYVlNRUExLTUdGSUuJShlJSktLS0hESEdBRUI6Ozs9PT08PD0+Pz8/iECEQYRCh0EFQEFAQEGEQFg+OTU4QEJCIQgHBgUGEE5RU1BMTU1OU1ktLTAwLy0sKikmQTo2MzAuLSspKQgEBAomJSEfHyEkJSYnJSIiIyQlJSYmJCAhJCQiISEfHiAgISIhISEfOyAgBn9/gYKCf4x+BH9/f36Ef4t+h30GfH2AgoKAhHyRe4J8hH0BfoR/AYCGggKBgMJ/gn7Ef45+nn8LgIKCg4SDgoKBgYD/f5R/hH6EfYd+r38GgIGBgoKChIOEggOBgYCVf5N+hH+EgJSBBIB/f3+EfAF+ioAEf39/gIeBA4B/f4SAgn+FgIWChYCCf4SAAYGFgoWAgn+EgAGBhIIDgX9+jn8BgIp/BoGCgoKAf4SAAX+FgAWBgYB9fomAA35/gI2BhICEf5F+lX8EgIGCg4eEwYPZfwGAhYIBgYp/ioCKfwSBgoKBpX8Dfn9/AgIEAIDXy7TOwpy1qa6+ysng+O/lgf719fT29Onc5ODm5dfLwq+hi4T2uJCdwcG1po2ypMmEositkoP/9OjV2N3W0tHZ4vSImaLNg6Sump+O4qK0uZzJj6PB3rW8sLywrKilpaSioqKhoaCfoKCgn56enp2fn56enpycmpubnJqam5uamj+ZmJmampuamZiZmJeXl5aWlZWUlJORkI2LiYeC8vKIlqKel56emZqcnJ6goqOjpaSlpKSlpqmppZyjrbCvrq6HsBSxsbGysLGwsK+vr62sq6yrq6qqqoWrHqmco5ahm5qio6aopp6SjJCSrZyak56cnJ2cm5ycnISbhZoRmZiXlZWTkY+Pk5VUGBowK5qEkUaTlJOTkpKRkZOUlZWWlZWTlZaXmZyfoJ+fn6ChoqSlqK6uq6ainpqamZSQj4+PkJKVlpeYlpibnZ2bnJydn56dnZ6enp+eiJ+EoIShBKKhoaKEoyOkpqeoqauusbO3u7zAw8XIyc7My8XCvru5tbCvraupp6ampYSjMqKhoJ+goKChoKGgn5K9zZ35/vnr0baci4KBla6wtb/J1/OImKa4xcnKy8zNzczMzMvMhMuFyn+wk9OL7KfLzs/Q0NDR0dLS09PTbTk7ISw8S0AnNjNjycS/vru5t7WzsrKwsbGxsLCwrJjxvbCys7S0tre5u7y+v7/Bw8VkZWZmNDc6Ox8gISIiIyMjJCQlJiUmJiUlIx4XIz1HKTM2NEykeodCSy4uMDQyNDIvMSUvNDodGB4hhCIfJi0xJy0sKzJCMzMzNDETFRYVFDczMzMtMEw0NTQ1IYQTfRI8OTs9OTVVQ0RFRCUTFBQUIFRrgY+PkZWclpKXh42KhXcxlJKSkpCUiYeNjSkUFRMqYEJAPjt0Ozw7OEsWFR07kTo5Ojc5ODY2Ml+NmJCFSCUYHyUTEyYmJCMiIR0dNzU3amxsbNDOzsrHxMPEx8XDw8K/vMP2ma+0tre2h7cUtrW1s7Gxt2Q4IBcPDgwLCggHDw6FDYMOjA+EEAEPjhCJESESEhIRERESEhMSEhIUfrukrbS1uLa3tLC1qKSxtLa3t7eGuIS5Ibeut62psqCZmpqbm52dn6Gio6Wmp6elp6epqqqpqautrYWrAayErWGurayuraysrKuimZ+1usDRpqKQiJrqzcnHwL/AxdLi7Ii18aPY6baHw5bz29bQzszGura2rq/AvNaMgLuxq6mqrrm8taWcnaGjpaGgppuXnJiSmp6blY2KiYKA/PPz+PPtY4d+Mj09XHFscXl+eIORjodNmJKTkZSTj4uWlpmRiH93a2NZVZt8Z2x6eG9jTTgtOStMa2BUT5+dnJihpqOjo6u4y3F/hqVngYZ5alaBWWdpQj4pLzdBNTpHamdmZGJiYmFiYYtihmEGYGFhYWBghV8uYGBgXl9eX19eXl9eXl1dXVxcXFtaWVhXVVRST0uJhktcaWFef4yUjomIiIiHh4SGEYSCgX13bGJZbH+HiIOBgH9+hH0vfHx7e3p6eXl5eHl4eHd3eHd3d3Z2dnh4eHZtd3B9dnV6e35/fnhvbXN0hnyBgYCEgRKCg4SFhYaGh4eIiImKiouMjI2EjFOPlWclKEc4rJ6ipKKgnJuamZqZmZiXl5eYlpaWl5eXmJmam5ubmpubm5ydn6GenZydoqiusrW2tbOxr62sqqmopaOjoaCenpybmZiYmJeWlZSUlISTBJKSkZGEkAGPho4qjY6Pj4+RkpOVl5manJ+hoqSnqaytr6uqp6SfnZqWlJORj46Ni4uKiImJhIgkh4eGh4iIiIeGd4mcdLm/v7SdhXNlX1tod3l/iJGbrWNveoeQiJGCkISPhI4vjY19ZJJgoHOMjo+RkpOUlZWXmJiZXz5PLjdCR0IyTkJelo6KiomIh4aDgoKBgYGEgIB9bqyGfn+AgIGEhISFhoiLjZGVm1JYXmY4PkRMKi8yNjc5Ojs9Pj4/P0BAQD89MSUwQ0orNzs5SmtbajpPMzM1OTc5ODU2Kzo7PSknNTk7Ozw6Nzo9LDAvLjtFNDU1NjckKCkpKD01NDQtNUs1NTQ0NSMkJCQiOjU4Ozg2Tj09PoA8PCIjJCQzTVVse3h6en54dXtobm1obDN4dnZ1dXhycnp8OCMkIzNQOzo4NWc1Nzc1TSUlPDx1OTs7OTw7Ojo5VldZXGdKMCUzPyIiQkE+OzUvKiVFPjlrZVxVoZqUkI6KiIWDgoCAfnx6fp9ldHh4eXp6ent7enp5eXh4dnV3ghpqUTUzJiYkIiAeHTc0MS8tKyopKCcmJiUkJIUjkyKGI4IkhSWDJoQnOigoKCdkdmducnJycHBubHBmY2xvcHBwcXJycXJycXFycXFvanBoZWteWVpaXFxcXV5fX2BhYmJjYmOEZBNlZWVmZmVmZ2ZmZmdmZ2dnZmZmhWVYXlldaGttTTAwKiYtXnx8gISMkpilsrhkg692mKJ/XIhprZ2YkIuGgXh1dGtpVDg7Jj9rZmNiY2ZrbmtiXl1fYWJgYGNeXF9bV1teW1lUUlFOTpuWlJaTkVJAPQ4LCxwvLTI3NjUzNjMxHDYzNTY4ODg7R0pGPzgzLywrKChLOzQ0Nzg0LyMOBAUFHjMxKylWV1hUW2FjZmhsc4BIVFp3UGtwXkMxSTQ8Ph4NhQYKDR0+Pj48ODk5OoU5Bjg4OTk6OYg4hTcEODg3NoQ3iDaGN4U4Kjc2NjQzMS8sVFUvO0U9OU9YX1xZWVlaW1paWllbW1paV1RMRT9OYWtvaoRohWcEaGhpaYdqCGlqamlpamtrhWwdaldqam1nZGlqb3FuaWNiaWxvd4eahYODhISEhYWEhiKHh4eGh4aGhoWEg4GAgIGGl+qBltuC25mZm5iRj46Ni4qKhIgBiYSITImJiYqKioyLjIyNjo2Ojo6QkJGRk5WYm6CioaChoaCgoqGio6Kio6OkpKSjo6OioaGjoqSkpaWko6KioaCgoaGioqGfoKCfn56dnZ6Fn1GgoqSlpKSmp6Wmp6inpqako6Cdm5mYl5eWlZORj4+PjYuLiYeGhYOBgH9+fn18e3psdopko6monoZtXlhQTVdiZ2lxeoOUV2VxfoeJiYiIiImGiIWJgIiIiYdUiVSPYnyEiIuNj5GUmJucoK2vpvmWteD44qL3sKSfjYSFiIqLioiHhoWFhYSDg4J/cLGKgYOEhYiLjJCWmqGos77O5oSgwOuPrc7wjaK1w83U2t/j5ujp6+zt7ezftIOhzdmAo7Cs0bWPyIjVnZ+jqaSppZ6hiNPAtZWbgMzc4eTk4MLQ0oiTkIzQ05ibmZu1jpmenpi5lZKRgbHWjIyLisCKjIyLiK+HiouFo7uGh4eEzYCEh4fLy6rA2NTT09bKxMyMj42H1ZXTzc7Q1NvV2u/6woaKiKWyhIWDgf2DiImH2ZGS/an5laSlpKyrqqqq91NbZc7kooS764CAKP/38N3Hr5aA2baa/NCrkfnVxrSsmZCGfnVwZmBdW1xxSVZaW1tcXF2HXiddXF1kftnwrbmXo52Vj4qF//br4dfNx7+4sq6ppaGenJqYl5aUk5OEkYKQho8rkJCQkZKTlJWXmZqcnqGio6Okpaipq6ytra6upr5nU1RVU1JQT01LTUZFSYRKE0lJSkpLS0pKS0tLSUVIREJFPjuHPAg9Pj4/QEBAP4dAAkFAiEGFQEk/Pz9AQD8+OTU4QEJDIgcHBgUHIU5QUk9MTFBWW1sxOUgvOjwwJDoxWExCPTg1MC0qKiknEgUFBREjISEgIiIkJSQjIyMkJCMjhCISIyMiIiAfHyAgISIhQj8/QUBBBn5+gIGBf4p+AX+Tfol9BX6CgoJ9hHyMe4R8hX0Hfn5/f3+AgYWCAoGAw3+CfsV/jn6gfwWAgoKBgf9/mH8Cfn2Tfpt/A35/fo5/A4CBgYaCA4GBgJR/kn6Ef4SAlIEDgH9/hIAGf3x8fH1+ioAEf39/gIeBA4B/f4SAgn+FgIWChYCCf4SAAYGFgoWAgn+EgAGBhIIDgX9+jn8BgIp/BoGCgoKAf4SAAX+FgAWCgoF/fomADH18fHx9f4CBgYGCgoiBg4CEf5F+lH8EgIGCg4eEv4MBgNd/AYCFggGAin+DgIWBgoCMfwWAgoKCgKB/hn4CAgQAY4/v5pmTvLfK2+fn06+BqP+LhP3v4dbN183O0M3Hw7Scg4aFioXVmavAtqylrrrKw7fPqpOJhffe09Ti2czP447G8Y2UpLa4qJr8xpCyuOGahZy428PWycWxrqmjpKSkoqCiooWgDp+fn56dnp2fn5+enZychpsHmpqamJmamoSZH5iYl5aXlZWWlZWUlZSTkI6LiYeA7PiIl56Yk56empyEniefoaKjoqKkpKWnp6qim6WsraysrK2trqytrq6usLGwr7GwsK+ur66ErS6sq6uqqqysq6yrqqWgpISno6WlpqekoZ6Xl66NnJKcnp2cnZ2bm5qbm5uamZmYhJc/lpaWlZWVk5KOjY+Sl4+OjZCRkJGRk5OTkpKRkpKUlZWVlpeVlZeZm52eoKKioqOlpaWmqK2rppyXlpeam5iVhJMJlJWYm5ydnp6dhJyEnQycnZ2enp+fnp6fn5+EoIShNKCgoKGio6Kio6Smp6iqrK6wtLi9xMrU4Ozx9vj17ebbz8W+t7GvrKmmo6KioqCfnp6cmZeEloSXfJPAtrrR3uTp5t/WzMnIqpPJo+6F2dD0jqW5zuCBlai7ys3NzMzLysvLy8zMy8vMyV232d3o9sfO0NHR0tLS09TV1dTT09VtNzgdNTJkzsrGw8C9urWysbGwsK+vrq2soP7Hsa+usbK1t7i5ubm7vb3AxWhqa2s1NzoeICGFIoQjhSRIIx8YHUU8KzQ0MzI0NTRQSRcWISUwMjAxMjMzMDImLzQ4HBgfISIiIiElLSwuRC0rMkEzNDMzMhMUFRQUODUzNC0vTTY2NjcihROAPTw8Pjs0VkVFRkUmFBUVFSBHj4mQkZKXoZiYm5WYlZF/MpaXlpSVmIyLkkopFRUTLWJCQD88Oz09OzhJFhYPQE07Ojk2OTg1NTMsduyN0jg1MipIJhkhJhMTEiUlJCEeHTg3NTZraWtpzc7JxMPCw8G+vL29vLzOhaWytLW2t7ghuLe4uLe2tbSzsLK5ZDggFw4NCwoJCAcHDQwMDA0NDg8Qiw8DEBAPjxAIERAQEREQERCIEYYSGSS7q621t7i4trWvtaqgsLa2ubm5urm5t7iFuSG3s7qpqrCYl5qbm5ucnaCfoaOlpqenpaanqKinqKqsrayErWeur66tra6ura6uraysq6SYoLe7wMqjo46HlczPy8m6u7/Ez9/tkL6j3oe8wqaThs2N5bmqmpWP+u+BhYePj8PgjriF8t3U1NTi9fP/iID23/L26+3+/Y6SiZDSms6BmK7D0eHq482uYSpKSTIwRDw/RElKRz8zTIBKSY+Ign57iIGBgHt4eHJiUlJQU1KFaXF5cWhiWDA5O0dsXlZSUJqTj5KlpZ+irWuXtWhseoWIf2qcdFJkaHEyJy00PzpCTW5oZ2RiYmNjYmGGYgFjhGKFYYZghF8BXoVfAl5fhl4iXV1dXFxdXFxbW1pYV1ZTUU5JhYtNYWhfYYCLk4yJh4eHhoSFHYSDgH57cmdeXHWBi4eCgIB/fXt6e3p5e3p6eXl6hXiMdxZ5eHl1dHhjf31/fn5+fHp4dXmKbn+GhYEjgoKDhIWFhoaHh4iJiYqKi4yOjYyNjY6NjY+UoJqgoaCcmZmFmAWXl5iXl4SYOpmXl5eYmJqbnJ6enp2dnZ+gprC2u76+vby7uLSyr66sq6upqKinpKKhn56cm5qamJeYlpaVlZSUk5OEkmmRkZCQj4+Pjo6NjY2OjY2Njo2PkpSXm52foaSmqq+1vsPDyMjHwLqzqqSemZWSj46Ni4qJiIaFhISDgYB+f319fn19fXuWhYufrK+tpqCak5GRe2qSd65enJSnZXOEj51baXmDjZGQkJCJjyyQjk2Impilo4eOkJGSk5OUlZaWl5iZmpxfPUsnSz5dl5CMjIuKiIWEg4GAgIR/b351u417fHx/f4GDhIaHiY2PkZaaUllgaDtCSSktMjU4OTo6Ozs9PT4+Pz8/PjMmLk8+Lzo6Nzg6OzlQSSQsPkA2NjU1Nzg4NTYqOTk8KSc0OTo7Ozo1ODM4TDAuOkQ0NjY2OCMnKCgoPTY0NC40TIU1gCMkJCQiPDg5Ozg1Tj0+Pz09IyQlJDQ+cHV8e3t9gnp5fHJ5dnF0NXx8enh5e3N0fUA4IyQjNlQ7OTg1NTc4NzVNJicfQz44Ojs5PDs5OTk1XqNfizo6NSxONSg4QyIhIUA9ODMtKEpDPTdmXldRmpSQjYmGhIKAf358enqFVmx2K3h5eXp6e3p7enl5eHh2dHZ+Z1A2NykpJiMhHhwbMzAuLCsqKCcmJiUlJCSGI4oiASGKIoIjiSQaJSUlJiYmJSYteGppcXJycnBvam9pY2xwcXKLcRZwbmxvZWZqWVlaWltcXF1fX2BgYmJihGOEZIRlhGYFZWZmZmeFZl1lZmZlYVlcaWtsTC8vKiYtVnt9gX6Ik5qjsbZtlX+kbZOdhm5al2mqkIJ1bme2rlhWVllCOz4mWk6QhoGChI6Zl55TT5mPl5ePkZebUlZNTWA+TC0zOT5CRUdEPDJcCBETDAwTDw4ODw4ODw8ZLhsaNDMyNTg+OTUxLy8vLislJSYmJEE0NTgzLysmBQUFDzItKykoTlJSVF9fXWFqQmiKVFdea21kSV5HMDw+PA0GBgYFBg0cPT4+PDqFOYI6iTkHODg5ODc4OIY3gjiENgQ1NjY2hTUGNjY2NzY3hjgVNjU1MzIxLVJYMD5COjtRWFxZV1dXhVgfWVlZWFhXUktCQFZiamdkZGRlZWRlZGRlZmZnaGlpaIRpCmhoaWlpaGlqa2yFbh1nY21ccW1wcHFxcG9tbG94Z4OmiYWEg4OEhIWFhYWGhYUvhISEg4KCgoODhIiVspKTlZSPjo6NjIyMi4qJiYiIiYiIiImIiYqLi4uMjI6Oj4+EkAyUnJ+ipaenpaOhoaCFn4Cgn6ChoaKhoaGioqKhoKChoaGio6OjoqGhoqGgn6CgoqOioKCfnp6enZycnZ6en6CgoKaqq6qrrK2tr7CpqKejpKWloaCdmpmWlZSSkY+OjYyKiIeGg4B+fHt6eXl3dnZygXB1jJmakoZ/enV2d2NgeWWFTIR7kFVibnuGUV5tfAOGiIiEh4SIgImIiIiHgaihmraTdYKHiYuMjpGSlJeanaCisrCe44HwppWaioSGiIqMi4qJhoWEhIOBgH92v5WAgIKHiIuOkJWboam1xNbvjKjN/Zu54YedssHL0dbZ3uHk5ufo6urq5LuHluWsi66vqKiusK7jzIis8vGnpqKkpqiknJ6FzLmxgJSYy9zg4+Lfv8unwPCQi8fLlZiYl7GLmJubmLeVkZCDrtWMi4uKv4qLi4uIromIiYOeuIaGhYLNgIOFhsioyMzW09LT2crKzpSWlI7hldnU1NPZ4djc9YHHio2Ip7iBgoKBgYWJh4bdlJeDsYuXoqSiqaimp6inm9aR7KawnYDzMKeSzv6Fg4D47NrBqI30zayM6LydguLErqWWjYN2bmdjXltZXz9PWFpbW1xcXF1eXoZdKGR5y++vwKGpopuUjYWA9uvf1cvDu7Wvq6ekoJ2bmJaVlJOSkZCPjo2IjA2NjY6QkJGTlJWWl5qbhJwbn6ChoqKjoKCdh2pXUlFRUVBOTUpOSENJS0tLi0oNSUdGSEFDRjo7PD08PIQ9Bz4/Pz9AQD+EQIZBCEBAQEFBQUBAhz9cQD89OTU3QUJDIQcHBgYHJE5QUktJTVJYXV83QzNBNUdROi0gPTNbTkA7NjFbWCopJyURBgUFIB88Ojo4PDk+QUMiIkJAQkE+PT5BIiIfHR4PEQkJCgoKCwoKCgkFgYCAgYGJgAR/fn9/k36IfQSBgoJ/hXyJe4N8iH0BfoR/AYGFggKBgMR/gn7Hf4x+/3+9f45+hH0Efn19fYV+lX8Ffn19fn6QfweAgYGCgYGAk3+SfoR/g4CUgQOAfn+IgAZ/foCBgYGKgAR/f3+Ah4EBgIR/BICAf3+FgIWChYCCf4SAAYGFgoWAgn+EgAGBhIIDgX9+jn8BgIl/B4CBgoKCgH+KgAWCgoJ/f4mABX58e3x9hIAIf4CBgYGCgoKGgYSAhH+PfpR/BICBgoOIhLyDAYLWfwGAhYIBgIp/DICAgYSJiImIh4KAgIZ/gn6EfwaAgoKCf3+JfoJ/iH6Ff4KAioECAgQAZOm+u6ykzs6BreWSr8XV4uPbzLGU8LiBlZHWxrqtnKGWk5GJiITYm6+6ta6t7r3XnuS1p5GI+Ovi4+/6+JfK/5qjnJCpt7CZ67mErLTA7fKatdrL9OHXr6yppKKjo6OioqGgoaCFn4aeD5+gn52dnJycm5qampmZmImZJJiXlpaWlZWUk5OTlJKPjIqHgvTqgYufnpeXnJqZnJ+goJ+eoIehCKSnqJ+dqKmrhKo0qKKem5+kqqurra2urq+vsbGwrq2trayrq6ysq6ysraurq6qmo6Scgtm7rK2srbfQgZqbmIacK5ubm5qamZqZmJiYl5eWlZSUk5SVlpSSko+Ojo+Pj46Pjo+QkZCRkZGSkpOElTOWl5iZmpqam5uboKOkpKKioaGhpKyrpqajq6mkn56dmpqZl5abn5+gnp6enZycnZ2dnp6FnXafnpyWlJOWmZqeoKGhoqGgoKChoaKipKWmp6ipqqqusba7wMbN0+mGnsHS1cKni/Lj1MS6tLCsqaWjop+dnJ2alZOSkY+NiomIhoWCzJalsrOqtbm3sKempaiqoYSf2eDk5tC3oIjb4JO+3e/5i5urv8rLzMzMhM0Pzs3AkISKnsDNz9DR0tLTh9Q009TW19RpZtHMyMbDwb66trKvra2trKysqJHcsq+tra+wsbO1uby+vb7Aw2NmajY3ODsfIYQihCOAIiMjIyQkJCAZJSxjqa+vUTQ0MzIzNTU1LCAaFRcUMTIvLzMzMjAyJjI0OR4XHiEiIiIhJC48JTFEKTFANDUzNDITFBUVFTc1NTYvLk84Nzg5IhMUFBMTPz49Pzw1WUVGR0UnFRYWFSB7Y5CUlZqeop2aoaewqaGLNJaZl5iZnJN9jJNLKBYWFS9khkBAeTw+Pzw4SBYWEEZOOjk5Njo4NjM4JhoVSXs3NTc2NTUuSDEmHSQTFBMTJiQiHx0cOTU1bW5qaMvFxMXCwL++vcC/v7/A5pOvs7a2tre4t7m5uLe2tbOws7hnOSAWDQwLCQgHBw0NDA0ODg8PEBAPDxCID4UQAQ+VEIcRCRIRERInyLOjtIS4Cbaut7GdrbW3uYW6gLm4ubm6ubm2s7ekq66UlpibnJ2dnp+goaOkpaWmpqaoqKmqq6usra6urq2trq6tra6usK6vrq2sraaZnLW8wbmhoo+GkbvNysW8tre8xtfc95na1cG4+KW01tenhPbNs6CVlZmblI6OjoqCxdHk67euwe/FrLbJ3tzKz+PTxpbLE4GcucvY3trPwraropmQioPpwJNmQzs6MzFEPCMtPCcwNzs/Pz06MytIOio5Q3NvbGdeYFhVVFJTUYdmb3JsZWE/Mz02dmVeVlGYlI2PpbW2bpC2bnZvZHiEgGiQbk1jaGtVRyszPjxLUXJpaGdjY2RkY2NjYmJjYmJjiGITYWFgYGBhYGBfX19eX19fXl9fX4RehV2CXIVbN1pZWFZVVFFMjoRJUGZmW2iDjZKKh4eGhoWFhISEg4GAfXhtYllie4WNiIOBf315dHJ0dXd3eHmFeCV3d3d2d3d2dXZ2d3Z2d3d4eHp7e3l3eXRio46DhIOCiqJme4KFhYEegoKDg4SFhYaHh4iJiYmKiouMjI2Nj46QkI+QkpqahJiElwyWl5aXl5iYl5eYmJiFmUKanJ2foKKjpKqzusLIzczIxL+7urm2srCtrKqpqaipqaejoaCenZubmpqZmZiXlpWUlJOPh4SDg4aHi46Oj4+Pjo6EjV6MjY2NjpGTlJifoqWorrW8vsdufpKhoZSBb8K4raOdmJSQj4yKiYeFg4OBfXp3dXNycXFxcG9tqHF7h4t/foB+eXRzcXN2bVpulJudn4+BcV+ho2eFnKetYW15hY6OhY8TkJCRkIVkXV5shI6QkZKSkpOUlYSWgJiYmZqbnF1amJGOj46MioeEgoCAf39+fn56Zpt7e3p7fX6AgoWHiY2Pk5ifVFphNjxDTCsvMzU3ODk6Ojs7Ozw9PT4+OCo4NV9uaGlSOjs5Nzc5OjkuKSsqLCQ4NzQ0Nzg2NDUpNzc6KSYzODo7Ojo0N0QrOEouOUI0NjU2OSQogCkpKDw2NTYvM042NjY1NiMkJSQjPjo5Ozk1Tz0+Pj49IyQlJTNlUHp9fYCAgn17gIOJhX5+NXt+e3x9fnhzfkE6JCUkOlZ1OTlsNjg5NzROJygfRT83OTs4PTs5Njk+MilHXTo6PDw5OzJSPzowPiIiISA/PDcxKyZFQDlpXlZSFZyVj4uHhYOBfn58enp5k2Bzd3l5eYR6AXmEeB13dXeAaFE6PiwpJSIfHRszMS8sKyopKCcmJSUkJIUjhSKOIYYiiiOFJBA+fm9kb3JxcXFwaXBsYWtvhHElcnJycXFycXJxcW5tb2JmZ1hZWltbXFxdXl9gYWFiYmNjYmNkZIZli2aGZVphWVpnamxILi8qJitQeHl+f4OJkZqnqrt1sbaJmsCFkKydfGS2moZ5cWxsbGVeXFhVOzs4PHxybniNeW12gImIe3uBXUoyQSgxOD0/Pz07NzQxLy0qKCZENyldDAwOCwoPCwUFBgMEBAUFBQYFBgUKCggNFSgrKyooKSQiIiMkI0EzNTUxLSoKBQUHNzQvKidLT1RYYGhrRmOGV19aUWFoZUtTPyw7Pj4cCwUFBQYOHD4/Pj06OTo6hTkBOog5DTg4ODk4OTk4ODc3ODeGNok1AzQ1NYU2FTc4ODc2NTQzMTBbUy4zQ0A1P1JYW4RXhlYbV1dXVlRQRz5EWmFpZWJiYmBcWFpfYWJjZGRmhmgxaWloaGloaWlpamttbm9wcHFxb25wblqVfnV5end9kl98laCJhIODg4SEhYWGhoeHh4aGHYWEhIODg4SDhIaGh4mQkI2Li4qKjIyLioqJiYiJiIoqi4yMjo2LjY+QkZOZnqSoq6qpqKalo6Oin5+fnp+enZ6en6ChoaCgoaGhhqJ2oaGioqOimo2Hg4OIi5SfoaGhoJ+enZ2cm5qbnZ2en6KfnaeqrKyusbGknk5SWFxaVVBLlJednJmVk5KQjo2MiomIiIV+eXZ0cnJwcG9vb2yfYGh5e2hfXl1ZVVVVV1hVQlBweX6CdWlfU4+MV22HlptXY218h4mIgId9X1xdZXuGiYqLjIyNjpCSk5aXmJuhpaWzq4mYioSGiYuLjIqJhoWEg4GBgH5qoYGAgYGFiIyRl5yjq7XC1vmPq9KCosjykai6xc3T19vc3t/h4+Xm5+bOm8Gs75lzd+awsayoqKutrYqIpqClhqiknp+mpqGanYPGtaqTlMjZgN/j4t+9x9OBv+mHxMSSl5WVsYyanJyYtJGPj4Ko04yLioi9iYuMjIisiYaGgZy4goOBgdSChIaFwvCF0dTR1NTVzMfRoaWhmuuW1dfT1tni3dv0hM6OkI2zv/6Agf2AhYmHhNuWmoO2iZOfoqCrqaaip+HRta3Bo7Cysq+ym//bLsuw64aGhID26dS4nobju5v7zaSG3cKzpZmKf3hyZmJeXFtrRVVYWlpbXFxbXF2GXClids3zt9KqrqWbkoyF/vLl2s/Hv7awq6ejoJyamJaVk5KRj46NjIuKioSJK4qJioqLjI2Njo+QkpOUlZWWlZWWl5eXmJeXl+J9XE9RUlBQT01ITUtBR0qGS4RKDElKSEhGRkhAQ0Q5O4Q8BD09Pj6FP4ZABkFBQUBBQYhAAz8/QIQ/XD49OjU2QUJDIQcGBgUHIkxNTkhGSE1UWVphOUtHOU5XRUFPNy8pYFNAODMyMjEsKickIhAGBQkyMzI3PDU0Njo9Ojg4NR0QCAkFBQYGBgUFBAUEBQQEBQQECAcGCoCAgIGBgICBgYGKggWBgYGAf4x+h30Ef4KCgYV8h3uDfIl9AX6EfwKAgYSCAoGAxH+Cfst/iH7/f5l/iICdf5p+gn2Ffqh/goCTf5F+g3+EgJOBB4B/fXx8fH+KgASBgoKCioAEf39/gIeBCYB/f4B/f4B/f4WAhYKFgIJ/hIABgYWChYCCf4SAAYGEggKBfo9/AYCJfwuAgYKCgoB/f4CAf4aABYKCgn9/iYAFgYGAfn6HgAV/f4CBgYSChoGDgIR/j36TfwSAgYKDh4S7gwGC1X8BgIWCAYCLfwyAgIKGioiKiYmEgYCNfwSAgoKBjn4Ef4CBgZCCg4ECAgQAZNLZt/fpjtrXz8TEydPP1pbUpIvKhKG82+jfv5LOgvLOqpeMi4XGp8m2tbH6gMmRxbuknJCB8PP1jr7ylaWqrba7u765mNas9qW1vMjdmLTZ0IL27rGtqqSjpaSjoaGhoKChoaCEnxWenp6dnZ6enp2dnZydnJubmpqZmJeGmAWZmJeYloSVHZSUk5KTko+Ni4mF/enwho+gnJOYm5mZnJ2dn5+ehp8hoaKip6aaoKamp6ioqailmpCQqKynpaCfnqSoq62vsLGyhq+ErYWsGKurqqmnpaOhoZ6YmJmbnZycnp6dnJubm4SaA5mYmIWZB5iWlZWVlpOEkoSRg5CGjw2Ojo+PkJKUlJOUlpeYhJolm5ydnp6fnp2enp6hpKmxuL62tLjBwbGoo6Cgnp6foKGgnp+fnoidFp6dnJ2dl5GTlJaQjI6Rl5manZ+hoaKEo2+kpKWnp6ipqqepr7S5v8rJy/Gd/rOD9fewiOGX68zKwLiyrKmlo6Cem5mXko2KiYeFg4OC/vry2IKEiYHW6eXq6+3t+qKBwqSpt8PM1dzk8PyA/tTPwrnX39/g3NnjhJqxxcvNzs7Q0dHR0tLT0tKE0zrU1dbV1tbV1dTU1dXW1NPRzszJxcG8ubSwraurqaqpoPvAq6urrKyur7Oysra6vsHFZWhoNjc5PiAihCMGIiMjJCMjhSKAHRYfPU0uQvX3g4FONDQyMjI1NTUsIxoWFxQwMC4wMjIxMTNLMjU4HhgfISIiIiEjLUEwTS86LkA1NjQ1NBMUFRUUNzY3OC8uUDk5OTsjExQUExM/Pz4/ezdbjo+RjikWFxcWHn2Gl5mYoKKloJykyNbGuJo1lpiYmJmelZCYTCkxFhYVM2eGgX57ej4/OzhIFxYRSVA6ODg3OTg1NDcaGRhCTDc1NTY2NjUzLpanPhwZIoUTOCYjIh4bNjU3Nm1qZ8zKxcPCv7/AwcDAv77MhaSytLW2t7i5ubm4uLa1tLG1t2g6IRYNDAoJCAcHhg6MD4sQgw+IEIIPiRCFEQcSE4G7nq21hLcrrLW1n621uLm5urq5ubm6ubm5urq0tLejr6mUlpiam5ydnZ6foqOkpKamqISpB6qqqqusrq2FrmWtra2ur66urKysp5uasLm+npykkYePtMXEv6msr7CzucTL3oaw37yP4e3upsnd++jBppeIiImMjo6LhYD+8aSYjN/Hu7m8yKHDp/ekydfTvKOL3aTbhbD1zsSxo7q+vr7Bxs7Lylx6fUdCQUB4d3Nubm10cWtCTzMoOiQrMjo/PzYqPipcaFxUUVRRfGmDcWpmaSI4LllkXVlUUJqdoWCCoGRydnt+gIKEgGmFaI9faGpLQCoxPDwnV3hraWdkY2RkZIhjB2JiYmNiYmKGYYZghF8FXl5fXl6EXURcXF1cXFtbW1paWllZWFdVVFJPloeJTFVpZFxyhpGPiIaEhISDg4OCgoKAf3tzaF9YbYGJjIaCgX96c2ppeHt4dXNxc4V1h3YDdXZ2hHeAeHh5e3t8fXx8fH19fXp3eH2Bg4OFg4GCgYKBgoKDg4SFhYaGh4iIiYqKioyMjYyNjo+PkJCRkpKTlJOUlZaWlpWVlZaWl5eXmJiZmZmampybnJydnqCmsLnEyMfHx8jKz9DKxsPBwLu2tbKwrKqqq6uopqSjoqCfnpybmpmYmJgTl5aVkIWHh4qGg4OFiISAgYmOj4SOeI2MjI2Njo+QkZCRmqKmrLm8udSDw3VjvcJ5Yqt3wKSjoJmVkY6LiYeFg4B7dHBtaWhnaGpr1NHNumNjaWefo6CkoaOnrnFbiW5we4WNlZmeqLFbt5eQh4KXnZ2dmpeeXGt8jJCQkJGRkpGQkZGSkpGSkpOTk5SVlYSWOJeXmJiYl5eWlJKRj4uIhoOAf359fHx8dLeHeXt7fHx+gIOGiIqNkZaeVFtkNz1FTywxNDY3ODk5hDqFO0kyJCo/TTFBoqFdZ085OTk3Nzk6Oi8sLCssJDg2MzQ3NzU0NVE2NjopJjM4OTo6OTQ3QzJRNkQ3QjU3NjY5JCgpKSc8Njc3LzNNhDeANiQlJSQjPjo5O3E1UXx+f30/JSYmJS1ibn+Af4SEhYB9g5qmnZGLNnx+fX19f3h2f0I8JSYlPVl1cXBtbDg4NjRPKCcgSD82ODk3Ozo4NzgoLjJHQDo6Ozs8PTw7NFl4QikpOiIiIiEhPzs0LSlKQTo1YFhSm5SQjYqGgoF+fXsJe3uEVW12d3h4hXkjeHl4d3Z0d4BsVUFDKicjIB4bGjIvLSsqKignJiYlJCQjIyOEIochBCAhICGGIIYhhiIBIYYiGiMiaHRjbnJycXBwanBvYWpwcXFxcnFxcnJyhXEbbW5vYWdjV1laW1tcXV1eXl9gYWFiY2NjZGRkhGUIZmZmZ2dmZmWEZmFlZWVkZWRhWlhlaWtCLTAqJStOcnJya3N5f4SMlJWeXoPJkHy2zMWNoqCupIl5bWBdXV5eXVlVUJtuLScngXlycHF0Tkg3TDE7Pjs1LiY+Lz4oQm5s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g4SVioL4gITu94SLiZ2cpqaiVFpkQTc5XGloY1tYUV1fWlxbWFdSU01PVl1eXFheYGI+RUIxQEU1PDpaejA3RDdQZFlZVlFSd11ncHN5fYKChHpjeV6EW2ptSD0qMTw8KC1BbWtqZ4RlBGRlZGSFY4ZiBWFiYmFhhWAPX2BgX19fXl9fXl5dXFxchlshWlpZWVlYWFdWVlVTUE2RhYVKUWZnXGeDjZCKhoWEhIOChYFAf3t0aV5WaoCLjYeEgn95cGhpe2AzP0BBQkNERDswSF97c3Fwb3Fyc3N0dXZ3d3d4eXp7e3t8fX1+f3+Af4CBgISBKIKCgoOCgoSEhIWFhYaHiIiJiYqKiouLjI2Ojo+Pj5CPkZKSkpOTk5WElICVk5WVlZaXl5eYmpueoq64wcfGxcbGx8jIzNLX1tjc5Hjn39vbz8rFv7u8uLOwrammpKOjoqCfnp2cmpiYmJeWiY6TxKeopqWil5COkZW1iomJjY6Ni4qJiIaEgX92ob+Dipyosa6ov3h3b4Kfv4y2rqaNz7GbpZ+ZlI6GgHlysUWB0MDFb4uqtcTIy8rKtKCbhZLRd2dgXFy1koh1ra2CtYmZnaFbZ3efr6+woX6Gh390WpxycmaukYqjYGhvkV53jZCRkpKEk4OUjZUulJKPi4eDfnt6eXh1ZJJxcXJ1dnh8foKFi4+TmlJYYGs8RU4sMTQ2Njc3ODk4OIU5gDguPTNhVDM0MzQ2NzVPNTAmKSs1NTc2Nzk5OS4rKykqIjQyMTE0NjU0NU8zNTcoJjM4Ojs7OTEzRDAxMEY0QzQ2NjU7IycoJyc8Njg5YTFTODg4OTgkJSYkIz85dHZxNVV9gIJ9QicoKERCfImFh4eLjIqIhpaPjnppWziCg4CBgIB/end+fz0nJydGZ3VxbGxoaGZjYk0pKSJORTc4ODU4ODY2OCkvNUtFOjs9Pjw9PztCIUVQWTc5PTRQKSY7JSUlJCMhQDkxKiZDO2thWaedlZGMiISBgYB9fXyNX3R2d3d4eXl4eXp5eXh3dHeQQC0pJScjIB0bNDEvLCsqKSgnDCYmJiUkIyIiIiEhIYUgiB8BIIQfjCBGISEiIiNBhW1ib3JycXBua3BlaG9ycnJzc3NycnJxcHBvb2tvbGFoXFZZWltbXFxeXl5gYGFiYmJjY2NkZGVlZGVmZmVlZYZmYmVlY2Nj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o6a3urm6vLe6tKGvur2/vr29vL28u7u5uLmxuK+ir5KSlZeYmJmcnJ2foaKkpYSmdKeoqKmqqqurqqqsrK6tra2urq2rqqqompeyu8CUt6eanZTDv4P1opSUlJWXmJmPjIyOlZqipqWln53c65ONioaDgYKHiIK3pfLpqrn97cXQgLnpzayboZ2Xoqqrq6atqJyhoKahp6afnJiSkZObnp2fq7KvVGRqKj1Acl9bWVNQSkqTSk9QT05PU1ZaWFRQTpSTlpSPjIuTkZSWkIZHMEEzNzgrKDtWUE9beItseHp7c1p3XoZbam1IPioyPTtMLUNwbm1raGdnZoVlBmRkY2JiYolhGWBfX19eX15fXl5eX19eXl1eXVxdXFxbW1uEWmZZWVhYV1ZVVFNST0uLfIVKT2NnXWaAio6HhYSDg4KCgYGAgIB/fHVpXVZpgIuNh4WCf3ZqYmh9cDU8PT09Pj8/QEBAQUFCQyEiIyMkPjBCkXVxcHBydHZ5ent7fH1+fn5/gIGAgIGEgoSDHYSEhIWEhYaGh4iIiYmKioqLjIyOj4+PkZCQkZGRhJIBk4SUD5WVlZaVlpaXmZ6os77Avoa/PsDCxMjN09rh6e98iJShs7Cjmo+EgHpz2s3AuLKxrqyqp6akoqCgnp2cmpmZmZePkpG8oZ2XkYyGhoaKjrCKhIuGinGMjo2NiX9vk5mcoKWooKW2Y3COc8F6Z7mom5+gm5eUkY2Lh3/MZXKRw3Fmh9DZ29rY2MJoZbuYjaium4h5e3x7gYSDlqRaXl9jY2BgYXKPjXN7gHZnna+NioN+eHJtZmFgYVqRVl6jVHCHp2uAkZSUlISVhJSEkyaSkY6Jg314dnRwsn1qbG1wdXl8gYSJkJafV19oOUBJKS4xMzQ1NYQ2hjeAMkVKOUpCp4+oezEzMzI1Nzg2VEQ1JigsOTc2NDU2NjUsKSopKyI3MjExMzQ1NTVOMzM3KCc0OTo8PDoxM0UxLy8wTDNSNTUzPyQoKSgoOjQ1Nl0wUTc2Nzg7JSYmJiRCdnV5czdkf4KBf0cqK05KSniQjY+Nj5KTmLKCd75+tohHPomGhIWGgHl2fn9AKiopVoB2dG9tZ2hsaWdQKywkXFA2ODg2Nzc1NTcpMDdLRTg5Ozo7PD88RDA2PkE0Nz0+PDk5TSskKCmEKB8nJiUkQjowKkpBOmVbVJ2V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pqetsa5nYW8iPy1aU1JQT1BQUE5NSIyLjY5MUlGcnJmVkomHiI6NjYqFgYI0L0IveG9aVHJNREhOQSk0IzVBTWJvcE1CLTdBNj5VQG9vbWtpaWhoaWloaGdnZ2ZmZWRiYWFgYGFgYGBhYF9fX4ReAV2GXkddXl1dXVxbW1taWllZWVhXVlZVVVVUUlBNk4V7iktPYmlgYXqJi4iGhYSDgoGBgICAf317dmtfWGd/iY2JhoR/dmlgaH9xM4U5hTqCO4Q8Iz09Pj4/P0FBQiEiIiIjIyMkJSUiNkmffXd2eXx+gIKDhISEhYURhoWGhoeGh4iIiImKiouMjY6EjwWRkJGRkYSSWZWUlZWVlJWVl5iktcLFx8bGx8fGxsbFxcTFyMrLzM3O0dbf6fWEk6BegGaVp5yQUW5RmGeonJSI9eXXzca8s66pqKakoZ+enJqZl5aWlZSTlJORkI+OjY2MhIuFioCLi4uMjIyLjHV3jo+QkZOUlZaXmZqcnZ6dnZybmpqYl5WSkI6NjIqJiIeEgoGBf357eXd1ctuze5u/srCPdoyzgZeblIWBfX+EhoBxrZemnqd1anWBfmiVrmlntZWevHB1a2FmZWJWimOJbm15aF2EnlWZdllxn1haZnmLj4+PjoCNjImBc2mndmprcHd8gIOHi4+TlZeaoFpvRi02Ojw8Ozo4NzY1NDM0NDMyKCs6eW8tLjEzMzMxUTcmOSI1MC8xMjQ2NU9BMyUnKjY2MzIyMzMzVlApKSsiNjAxLzAyMjExSDAxOCkoNz0+Pj49MTFCL1xcLzJaMkYzZEsnKisrKoA6ZWZkVTBLZmZpbkIoKSkoJkR5eX5xM1yDhoCEVTFPfbxre5OPkpKWnp6fsZxvcsKLy0KOjYmHh316eYSITTU1NG61gXZtbGhnamlpUzAwKk+DZzQ1MzQ0MzIzKjM8Vkw0Njc2NjY6O0MyHkBAMjU4OTg4OEgpQiYpKR8uMUUnJkImIisrKiopJyVDOS4oRDplWqKVjomEgX98e3t6gVdxdXZ3eHh5eHd3dnV1dXxfUUBAJSEfHDYxLisqKCYkIyIhICCEHwkePT09ICIjJCSHI4QihCGGIDVAQD87LHdmZnFzc3N0cHNmZXF0dXZ1dHR1dHNycXFwa29jY2VSVlhZWlpbXF1eXl9fYGFgYYVihWMIZGVkZGRjZGSEY2Bi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lpSXmaCem5eWlEkxPStEYGlua2NQV1N4d2NLZW5xVCQyOz4sNU12bGxqaWhnZ2doaIVnFmhnZ2dmZWRjYmJhYF9fXl9fX15dXl6IXS1cXV1cXFxbW1paWVhYV1ZVVFRTUlFQTk2ShHmES0xYaGZeZ3uDhoOAgICBgYGEfxt+fXt0aV5abYOLi4iEgXxwYV1vk0w4Ojo5OTiJOYw6DDs8Ozs8PT09Pj4+P4RAhCAfISEhIjwra4iAfH+DhYeIiYmKiYqLjIyNjY2Ojo+QkIWRBJKTk5OElF+Vlpeisbu7urm4uLa2trS0tLOztLWzsrKztbSysbi4ury9wMbP2eTzfY9acXJYZH2PZG9hpJGPiYHz5NbLwbmyrqqmoqCem5mXlpSTkY+Ni4mIh4aEhIOCgYGAgH59foR9hnwNe7B8fX17ent7fX6AgYaCcoOEg4KDgoOEg4ODgoKCg4ODhISFhYWEhIOCgXy6uF1eXr/FvH5zi4uSmZ2OiIR8e3h3dnuCiXqxXYZ1oHCfhLui0baWbru5qqKci4qzwmRlZWRihZd7ZHNXW6OdoKCdlYqBfn18e3l4dV5wen2BgoWIi4SPgJCRkpOVl5qcolRaY3BCTSouMTIzMy43M0FdMDFVOkIvMjExMjEwMjBNNUc3ITIuLS4xMjMzTT0zJigrNDMyMTMzMjJUUCwrLCQ1Mi9ZLzAwLzFFLzE4KSgzNzk8PDsvMUFYW1xcZGNMMlRkVSktLS0sO25valczUm9wcXJELCwsgCspRnp4fm0zWYaFgoM3OjxCXDuBlJCVkZSenJaZnJSZhndmPIqKiYaLhoB/hYUkMzQzR2Z2dWxsZmVoZmpVNzc5aJNwamdmZGQxMTQvNyN5ZDM0NTU1NDc4IzYhRUMwMzQ0MzQ1Ryg6HydBQExNPSs2ODs8PTk2QjQpLS0sKyknXCRANSxLPmpbnpSMhIB+fnt5eHWQZ3N0dnd3dnd2dXZ3eHqGPC4wHxwZFysoJiQjIiEgHyEkJycmJiYkIyIhIB87OTc1NDMyMC8uLCwqSkA3Xk6BeXZ1dXRzc3N0hnUydHN0XWp0dXZ1dXR0dHNzcnFxbnBhZWBTV1hZWltcXF1eXl9fYGBhYWFiYmNjZGRkY2SHY2ViYmJjXlZfZ2k0LylFRHxqaXOMlJhUU6JQkJNLkDcjPzJ9RD4+Nz8wMEp7e316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z8/Ozs3MzM2EzwTOzcvKhcmAwrKbiPTYwrKjoa/WwMTDwqqhuMLEw8PBwL+/v76+u7umpp+MkJOUlZaXmZucnZ6foKChoaOkpaWlpqemp6mqq6qrq6uqqaOXrLSBop+ah/i91vqZtsm6tbO5ubCrq6ytqaGhnp2cn6i0wYLqnIezq6iZkZKTlZaO5cOdgvPn9fAq7LeikZyZlqqg0IPJ1dORub+a2JWbqub7hIeQlJmXlJqprbSzsrO2wrixNjqBV1VUV1ldYGFhYzdFODImMjw9Oiw4YIFsZ2hyOCU2QmpncV9FXm1xWy1CJDY7VX1tamloaIVnA2ZlZodlCWRjY2NiYmNiYYRgDV9fXl5dXV1cXFtbW1qEWYVYG1dXVlZWVVRTU1JRT5mRg3mElU9QWGJmYWh2foR/BX5+fn19hHwqe3t6dWxgYnGAhISCf314bmJhcq9ZQSIhIUNCQkFBQUBAPz8+Pj49PTw8hDuIOgE5hDiFN4s2Bjc3Nzg5O4Q/gB8fPDg0L0lZo6OenZybmpubmpmZm5ydnZ6dnp6krrW1tbS0tLOzsrCvrq+wsLGxsbKztLS1tbe4uLi5urq5ubq6ubi4uLe4uLaxgbCtqqilpaGdmpOPjIuJioWBf358eXh2dHNxcG9ubW1samhnZmZlZmdnaGlqbGxra2tqaWhoG2dmZmVlZWZoamxvcXR3eXm/fYCBg4WHiYqMjIeOMY+OjY2Mi4qIh4WDgH15dnJua2djYV++vbu4tbKvrKmlop+dm5eVlJKRj42Li4uIh4WFg4CFhYeGjpafU1hZWVlYWFenTlNVVlhaXmBgW19bV6CJhpGdk4WAfXt6eHh3d3Z1dHNxcW5ta2t4bmheqKi3Zm92foSOm1ReaHF7go6EkIV/TkpPVFJRLSs9ZlVRUVZWUksrNCM6WlpYWlhVVUYwQjkhWVBOUFhbW1ZBNyMoKiswNYA2LzJQVVdISzAxMic0XFlLNEZhXVlCNzRzLCw7P0BAID8zOURZT05NOkVeYF9eSx8gICAfQWhnZEw7UGltbXItIiIjIiJSdnh8aDc3cXh+g0wuRE+LRYGOkJCQk5iVk5SWl1RWVzRAi46EhIR+d32HjjEjJCM5OnNxbGljZGdmaT1WICA6R3JgY2VkX2NfWl81IS6skl5gYGJfXF1iJyE3hGxaXDI0MzQzRSk3ISk+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QTxnYlHs8Ojy/YWv1MTLv7mumYSHtNj2yZctfJSbk5xdhmqSuoa7moKBfFqBX3N3dWdcJ0lka25RLiYnMTZLbWloZmVkY2NjhmSEYwdkY2NjYmJihGGFYINehF0BXIRaAVmEWF9XVlVUU1RTU1FQTpeUjoN1dICTT1BVYmlkZ3WEjIuNmKtqbn+Ai5iQtriUiH92bGdsd3x7e3l4d3NsZW6WNC0bGjQzMTAwLy4uLS0sLCsqKikpKCcnJyYmJSUkJCQjI4UiOyFDQkJBQkBAQD8+Pj09PDs8Ojs7OTo5OTo4OTo5P0A+OzUwLispQ1/Fw7m1s7GwrqyqqqmpqKeoqKqvhFhZWVpcXmBhx8rKyMfGw7+8uLSvmsa57aOzt7m5urm5ubi3trWyrqqnoJqSiYJ9eHRwb2xraWdlZWJRWFxdXl5hZGZqbXN7hI2TmJ2goqOlp6ipqqqpqqqrqqqHqSOnp6ampaSjoqGgoJ+dnZyamZiZmZiZmYmGmJiXl5aWlZWVmYWcC52enp2dnJubm5eXhZiCmYeYhZcGlpaWl5eXjJYGlZaWlpeXhJY9l5eYmJSHeGdZnZmXk5CLio1hhGWApFJxlppcqG18nG9vcG1qZ2VkZGJgXl5dXVtaWVdWrKtbmZmcUFBPT4RNgEx9e1NnPkFAQHZhRSw6RkNHSklMV1Y8ZEpHSVBQP1U2RktsTlFCOkRISTk/KTUkVkpINTBVVlFCOzw3OjFdWlQ9PFdWUk4rICUnHTJVU0gnRl1XUkUhOmdOKTg9Pj4+PCwxPlFLSEQqPFhWVVZJJCQkIDs6aGtsUWFgd3Z4eTVCgERFQz46aXJ3bEycgIB+dVxOoG6EMnmKjo6QkZONjZCPjU1BMSZGh4mLh4uDgICDijA/PTwsP21wZ15fXF9bXnQ6Ozc3VklBZko5c0lVcTUmOVtEUE1MP0JKVFowJ0VJhU9QWDFBVVI5PSg2Rz05REczJDw9NSM8RSU9MD5dW19bgFgtM1E9WC5cWFkrN2NVVjk9QCMiIiEgHzkyKkRopJCJh4WBe3VxbWlonWZte4SHh4iIiYqKi4yLiHNZjnFscGVbTYyBend1e39OXGuElWhxaW51YZdOT1FSVVZUUZeSkI2Fgmd5jFh1dn9ufImSnFRbXVlTnZaPg3tmaVNXWVlaBVpbW1xchF6EXwVgYGFhYYVia2Nj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XFtZWFlaWlxfYmhuc/Dv6OPZ0MfCvry5tpCH+qC0uLm5uLa2tLKvrKikn5mSi4R8eHd1cW5samlnZmNgXl5dlmBhY2Rnam98iZSbnZubnJ6goqOkpqepqqqrq6yrrKurqqqqq6qqqKenpqalpKKgnpuYk4+Mh4OAf3x8fn1qD4GDhIWGiIqMi4uTm5ycnISdIJybmpmWj4iIh4iHhYWFhISFhYmKjI6QlJWUlZaVlZaWhJULlpWVlpWVlpWVlZaHlYCWlpiZm52en5+gmIh4Zambl5eabl6Rq19vaKhuXp6UbWZiX15gYWFhX15dXVxbWllZV1dVU6NarVZWVVVTU1JQT5dnVHWFYkBDQkJ4YUQrOUVGTExOTlYqO2RJRTxZR1loSTBUaU5QQjRCR0k7VD8lJ1RKRTQxU1NPQj4/HR80WoBZVEA7VlVRTSwlKisgYFNPRyhGW1dRPjIxYUooNzs9PT07S0U9UEhEQkY7VlRSVTY8QUI+NmFmaGpJTVh1c3NuRE1PTkc9Y2pwd22MlYB/eHp4hIJ/b0aMbrt+jY+RjIuMjIRKNzkxP4aKi4mIgoCAgod+RIZ/SD9sbWZfW1pbV4BaYGtXPVROh2J8jHJuUlBZSyY1SEBSTUs8OEhRWSgjNGt9TU9aXj5UUGs4KjlCOTVERzAhUFY+SllZMzQrLDZAT1NVWDNRPFZZW1RSNkw2VFhVQEUyPzw5NTEsTUBoV5uPioeGhYSAe3h0bmeGY3GAhoaGh4eHiYBomoKGkohwWkuPeW9nLzlwaXSFfGVyUmB6V29waG9oWVtVVFRRmI+BfHuAalxlXYmEZ2xse4GJmKhijquFe31ioZGIe2ZrU1ZZWVlaWltcXV1dXl6EXwFghGGFYmtjYmJiYVtZZ2lFLihFPWZdQDUqX1lbWVpYU09QUVNXWFlVT0uSj49KSkxOT09OUVMsQSpAJjw5QUlhWlZRjYhfZVlbWk5MTYx/d3pFQ0hbW2RlXk+AXMqflpyiVGueiI1+XpKTm1RGQCtMYClXSEVESj81PDFLOT49Ris0PT82RD80TD0qNTocBwMDBgsaOTo5ODc2Noo3gjaFN4Q2gzeGNgM1NjaGNYU0gDMzMzIxMTAwMC8uLVdSTklES1UvMDE3PT8+Q0tQUFFSUlRYXEEzKj9PMypBZ1tPTExTWFpaW1xcXVtYYH+9oISX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U0pEj2dkYWNrR0hGRENwenx6UZJigoWEg5CXSktHgX2FjZSJvseio56beYaYmZdsgO+Wr4z4q7GsrbKcnpqXijGqsre1taqoqqiqlJKTjEE6i4qFf3p1wpNqpJvGc2pUanZ4bbCRaYBncmRsWmuKZWFfmpFVYGUfFDRjiVpfaG5AZWCDLSAoNz8+WVxeLlReR0Fmah8uUXtZTlkyMjUtUUJcXV1aVC1NOlRaWFRiLyQcHBw4OXNw3NjPzM3PzM7LycfFyMW5rb2UvsnKy8OplYP29Yie/+GMwr27sq+ynZ2WmoifmOih10uThteft7SgpOfmw6igr7fDytzj6fHv9ejalPqYp7fk6v+AvJeTjJWzuYbp6suosI2QlJSUlZaWl5iampqbnJ2en5+hoaKjoqKio6SEpWekl5+yzpWS+LK7sq2ri9iGj5qUlZORjIaFhY2Tk5GLiIiGhoqIiYmOj5OjwPDg/sK736acl5SQi4Lfk+yRjpCR+PCG7+Xp/73oj1JGQT01XlaJ1Onx8vL+oZiRjODDveqA0Lyqhv6fJF5cXG55iWxZdIGQkoh4WWt/eWdli21MY2xTMiotOztObmhmZYRjgmKJY4ZihGGAYGBgX2BfX15eXl1dXFtaWVpaWVhYV1ZVVVRUU1NTUVBOl5GGeWxwgJGXTVRgZl9kbnl8fHx9f4CAhIqVpL50wWZue2Rzca2EfHt6eXh3cmtpdXcqISgnJiQkIyMiISAfHh0cODc2NDQzMjEwMC8uLS0sKyoqKSkoKCcnJiYmJSWAJSQkJCMjIyIhQ0FAQUE+Pz89PD49Ozw9Oz1DIT87NS8sK0xoxMjAvLm3trW0s7Kxr62srKy0wcBdXFpaW1xdYGRrd4ibrK6gj4Do18zGwb+/v72pjLi3t7a0sa6qp6OfmZGLhH96eXZ0cm9ta2lnZWJhwcLDxGPBqGZre46fp6cTpaKgnp2cnJ6foaKjpKeoqqqrq4SsVqurqqqsrKmknJSNhH12cnN2e3+EiZCgXmw9Q0pRVUQzNzxBIycsMjmclp6dnZ2enp6dnZubm5acJSYhHjgzLionR0E7NVxRk42Lh4WDg4KDh4qOkZOVhZSHlRCUlJSVlZSWl5mampuSmaCghKGAn5KAbFykZ4xxpWF3eH2TaXJuaWZhXFhWV1lbW1taWVhXWFdWVVRSUlteXVxbWlhWVlSSVEtlbkxsQUZFRXtlRis7R0pQUFBRWCs8UFREP05MSkNIUTU+TlBCX0RGRzstQywrU0lENDBRUE1DPz8fITZZV1NFOldWUk0sJi4yJmCAVU8/OkVWVVA1Q1BcRyc1Ojs8OzpBOTtOR0ZBOnNXVVJUYVVPTUhDWmNlZENqUWtra2hxgUREQHFiaXB1a4mUfn55d15ufXt4Zmi1ZnFpvIOJgn+Je4WJhIE2hoqMiYmBf4GChnd8eXRDO2xqZWBbWJZtSGl4oGBoTVJbX1d8blOAUVxQZlhgdVFNTHhoRU9VKSNCX2tLT1daOVdRaTEoN0I6OkNFWj1MUzxFW14zNUtlUEVWMy82MlI4VFZXVU44TzVQVlJOUzM+MS4qST9sXqeak42MioeHhIOBe318dm14YHuDg4N7a15RlpVUaJ2JUnVvcG1saThMVltQX1mIXX46VU1+YG9tYmSKiXhqaXB3f4iSlpufnJyQiWCiYmp2mJurWIt3d3N3eoJamJiBam5UV1lZWVpbW1xcXYRehF8GYGBgYWFhh2IfYFldZ2wtK0g3aWZjXCk9JTxdXF1aV1NPTk5UV1hVUoZQQlJQUFBRVUFEPUU1PGZfXltaVlVQiFaMWFRUVZiRUZKHiIo9RSxqY2JbUIxvn6ScmJyepGRkYVySfHiTTmI2MUOYZig9NzQ9QEY0LDg9QkE6OCw2QD45OE08KTU7HAYDAwYLGjk5ODg3NjY2izcHNjc3Njc3N4Q2hjeHNgc1NDU0MzMyhDOAMjEwMC8vLi5YVlFJQENMV1wvMzk+PD9FTU9PT1BRUlJUVlpeYzNSKjQ8NDc5ZltYWVlaW1xdWl968ZqMuLWwrKikn5yXko6KhYD79vHr5+Lf3NfUz8vGw8C7ubWzsK6qqKWin52Zl5SSkY2LiYWFg/359urn7OLc4eHV19/X1uCA2vaH/+i/pJuU/L2enpmYmJiZnJ+hpKmsr7K1uMnt/4CBgYKCgoOChIaHiIOBgX13dODZ1c/Iwb63rpd+npmWko6JhIB8d3NubGllYWFfXVxYV1VUU1JQT1Cmq62xXLWoZWl2iZmos7m9wcPGxcO/urWup6Gcl5SRj42MjIuLjI5ajpCSlJeampyYlo6LiYmLjJKcp7vtqeaOrcro+NCjuc7ogZGjtKbKtsPFxcPCwcDAwL/AwcfTi7Oei+zOtKGL8sunhcuOyqmak4iDfn6EipOZn6WqrKyur6+vhK6Hr4WuJ6+uoq+2tra3t7ayoodzXah3gWSDTGFnZm5SbmpmZGFeWllZWFdXV4VYgFdVVVRSUm6SkpOWmJiWlZH9np7f5I3ThIqKjPbr0ImNmaKtrbC0+ISpka+IhKarpp7T+YiDs7ef85+qrpuJ6b7B57yvsYvKycKwwvWKlsbo4Nr/ntva0MWHrN/xsPzYyr7IpdDPxZzkxa3NktDg5ujm4tKliK6lpJuo+bq0sLDUgPTe0MewvMLBvobOiL69u7S09Y+Rg+itpq2vouLJtbWopoGXqKWi2Kzji5aG6qGnoJqtqcDXyvaht7W4tLWrqq6yu7S8uq2ila6pop2Xlfq9g7a58pjyrp6ssJ7f2KimuZDa2L7gp6ek9uCYrLyBg9XhzKSywtSTxrzxoIS+y4ONgIyV/9zCzZHk3uPFoMzzxqn4oImvq/+X2uDm4NG7+ZHY4tvS14fkuqqS7K3zrf7Jp5mNgHNwaWRfWVlWTkdRQlJWVlZTRzwxWFYyO1tHLT07Pj8/QSIrMTIrMy9CKz0wLEo4QD84OUtNPzs+S1JWX2hqb3NzdGhhP2E+QkhYV1snGiovKjArJDAuU1NPQ0U5PD4/Pj8/Pj4+Pz4+hT+CPoU9bjw8PT08PDs4MzhAQAcGCgs/Pjw3BgQEH0BAQUA+OzY1Njw/Pz07ODk6Oz48Ozk6Ozs5HA8LCwcOO0JCQEFAPjpeO3NBPTs8bmo5YVtYWAcGEKegoJSD47LvkmpZU1NVMygoM3FpZHlBQQUFLmRKhn0Bfoh/AX6FfQt+f39/gIKDg4KBgL5/iX6UfwSBgYKBhICMfwKAg4+EpoOSggGDhoICgYCUf5KAqH+EfgN/fn61f4KAhoGEgoSDAYKQfwSCg4ODhYKEgYKAtH8Ffn9/f36Gf6J+B319fn58fH2Ef4N+iH8Efn9/f4R+BX9/f35+hX8BfYZ/goKKfwSAg4OBiX8FgIKDg4OKfwR+fn5/h4ECf36Ff4J+hX+FgIV/AX2Hf4OAhn8CfX6LfwV+fn1+fop/AYCOf4KAhn8Gfn5+fX5+h38CfX6Gfxx+fn9/f359f39/gYKAfn5/f39+f39/fn+BgYB/hH6HfweBf35+f39+hH8Bfod/AX6Ffwp+foCBgYGAgH9/kX6JfwN+fn+EfoZ9h34GfX19fn59hX6TfYZ/CYCBh4iJh4OAgKp/DIKCgYF/f359gYKCf5p+Bn+AgYGBgIh+gn2FfAN7e3yEewOBgoGFfgN9fXyGewR8fX5+hH0Hfn6Cgn58fQICBAAk6IX7lKasutX2pr2+qoXy3bWT3LyGsca+n5Ol4tvq3bexraqnhaZ0pKSjpKOjoqKhoaCgnp2dnJybmpqYl5eXlpWWlZWVk5STk5KQj46OjIyLi4uKiYiGhIH98uLRwcHT8Pn7/4iOjYuOmJubnJydn6ChoqSpr7W/yNPg7ITCxoWuqKajoqCemJKZYxYPEA4NDQwMDA0MDA0NDQyEC4QJChISEhESEREREA+IDoQNig6FD34eICEgICIiICAiIiAQEA8eHBoZGSqKh4eJiYmIiIiHhoaGh4eHhoWAfX1+gYWHRUdKTldidprw6Ii5w6i2ecmzpJyVkIyIhoF3hoOChYWGiIiJjI6QkpGSk5SVlJOSjoWA/Pv4+fv+/ID9/eJxbGtucHJzdXZ3d3h5eXh4dnWEdEdzcnFycnV4en2Ah4iOkIyRj4+JjkkmJSYTExQUFBUVFBQVFRYLExYKCgoLCwwNDxmSlJaWmJiZmZqbnp2cmo2cGwYMCwwNDYQMBhgYGBkZGYQYEzAwMF1cra6vsLO0sre5u7y7vLyFu4C9vr/AwsXJukbFuovQ0NLS0tPT0tHQxLObh++PlYvgx7itp6GdmpOPiYaDgIOHiYqLioqJh4eFg/fvfXt7eXd1cG2qeXeUin04ZVNWVVOWekwnQVRYXVxeXV9FUKhLT2tqWlRMR0FGc1pcS3tKTVFGMDUQEVhRSiowVldUSDwUCSAJHVlZVBk+XV5bUFENBQUMYVlSQUtNXllRMj9ifUQbIoQkgCM2QVdjX11aQIZmY2FiaINFRkNAbXd6eE6OYIGAgYKMkpSVj4F4h4mQh8bHnaWcmPG9n7rmX4qxsqLiuKiX+qiwlpqZl4Ysp6+ys7SqqKqorJGQkIc8NoqAb6dymLGDvXR7b7VaU213d26zj2lsdn6Faz99ZWhln5ZXX2M8JzdFgHVeYGVqfmtmlU4/KDaCPlZaWi5TXUU7ZWsySU56WFU0SFNQKENUOUpXW1cuTzpSWlVSYt2HNTQ5c3Fs09HNzMrLzM7QzsvJx8TIx8O5tZenjubM0eHv8/H+jPzBw8iCnIbO4u3dg4uSnZzxzdfsjOf8rLitr5ng6On46tHNvKiUgIigrteHhOGi45+p7P6C7YODtODAuJ6X6erVq66Pj5OTlJWVlpeYmJmZm5ucnp+eoKGhoqKjo6SkpaSkpJ+Rp7W0oYji8ranntCBxKy4lZ+WkpSWkYyJiImRko2Li4yMiICDkIeVnryJkrKl75ubjY6Fg4qK+4H67cCdtJCJ+IHwI+Tflobnvng6ODMzMVtKy+np6u6JvYq+x87Y4+/+xdnI0M/sIptXpF1qcn2PpWt0cWVTnZJ2X4JrSmFrTTErL0BAUnBoZmWEYwdiYmNiYmJjiGKDYYdgE19fX15eXl1dXFtbWVlZWFhXVlaEVRdUU1JRT5mRh3xsbHqPlZieWmNhXmZzeIR5RXp7fHx7foKGjZOZoqZcjJJkgX16eXd2dG5qc18rJjIxLy4tKysrKiopKCcmJCMhIB8eHh05OTg3NzU1MzIxMC4tLCwrKoQpgCgoJycnJiYlJCQkIyIiIiFCQkE+P0A/PT4/Pj0iIyE+NC4sK0XFzce/vLu5uLi2trWzsK+vr7THy8XAvbu5XV5gYmdtd4m9nlFseHedgPHi1s/JxsO/u6OXtLKvqqahm5eRi4R9eXh2dHNwbm1raGZkyMfMzc3Nz2fX/vKWtri3ebOvq6ajoZ+enZ2eoKGio6WnqaqrrK2srKuqpJiMgXdsbHF3go2oaT5HTysvMjY6PUBBQkNDRCM3QSYnKSsuMTY8TKCXn5+enp6gn5+fnp+el583GCwqJyUjISAfHjw7Ojg3NDEtKSZGQDdiU4+JhoWEhIeKjZGTlJWJlAuWl5ibklOEhWSaoISigKGhoaCVg29hrmRnXpODe3Rwa2djYF1YVlRRUlRWVldWVlZVVVRTnKBhX19dXVxZV4ZYYHdrXjNdREhGRX1nRys8Sk1RUVJQUj5JhT5FV1VOSz89QkZmT1BCX0FFRzwuRi0yVklDMzBPT01CQSQjJThXV1MjOlVWU0lWLxweLV1QgEw9S0ZWUUwwNkpYRSY0ODo6Ojk7MkZRUE9KM2pWVFJRUnhCQ0E+WGFkYUBjTWdlZmZsfYSEfG5haWtybIKQeIB5dLaSgpi+V3CGhnqqdGpxv4GGdn5+fHkygoaIiYqAfoCBhXR1c3A+NWtkVXtPXXVdiFZlXJpXTFFcXFV5bFRXgF9mbFs4aE9RT3xnRExSRDo+QWBNT1NUZ1pUdU5JND90O0JEVjdKUjw+WF1AQkVpT00vREtRLUpPOUtSUlE4UTNLUUxIT4drP0E/bWFXoJeRjYqIh4aGg4OCf3t+fnx1dGJqWY56fYqVl5SfWZ51dnlOXE97hoqIT1NVW1qNfH+KMFWIlGdwamtcg4uQmZGDgXhqX1xreI9XUohlk2VsnqtZpGRljbGcfnVpmJiHa25VVoRZD1pbW1xdXl5eX15fX19gYIdha2JiYmFfV2FoSjEpQlhoYF+FTEAwMzFgXVpbXFdSUFBPVVdVVVVUU09ISEs0KywyIiYyLVFYWldZUU1RU5lOl451YG1UUZdOjYV+SypEOodRVE5PTYtioJmXmptZd1d5goeNkpecQD47eIecI1kxWzU8PT9ETDM4OTQrUUs9M0k7KTY6HQcDAwULGjk7OTg3hjYJNzc3ODc3NjY3hjaDNYU2ATeGNgU0NTU0NIQzhjKAMTAvLy5bWFNORENIVVhZXDQ6OjpASE1OTk5PUFBQT1BRU1VXWVtbXS5BRjtaWVhYWVpaW153o5ag3eLd2dTQy8nEvrixqqSfmZONiomFgf/69PPs6eTf29jUz8zIxMC/vbq3s6+ppqKenZmWlZKMi4qEgIH+8Ozx6t3h5uPb4+yAgYuB6MWpl5DR7J2al5aXlpaYm6Clp6qusbO61+3y9Pf5/ICBgYKDg4B2fU0bIi9EdHDn5NvUzMS8sqqShpqUj4mDfHhzbmllY2BeWlpZVlRSUVJPTp6epKmvs7hgy+DbgJifp6+zub3BxcjKycfDv7exq6WfmpaSj46MjIyLi4iAhH98enqAipCg5b6IsdmAkKKzxNXj6+7x9fmAxPSPl6Crusnd7v3RuMfIx8bFxMTExcTFx8/f9oLq0bytoZmQioP99O3n3My6qJOA3rGJzpG9m5CJhoWKj5yjp6yur7Cvr6+ur6+wr6+vsbWshJZuqrW4uLi3trSysaSOd2XEYGKAVol+d3Jua2hmY19cWlVRUVJTUlNSUlJRUVBPl8+koqSjo6OdmvGlx/vy/ZjZhYyLivzx0oqPm6axsLGw26W97oWWw7etpJOgyau8sbWa35qmqpuN7sTi6rmrsYnBwr2wyImlsNvh3tiCl9XWzbj624iOyPjMvqv6ps/HvoClqaSAxIzK2t/i4d26iqK3s7axh961sa2nlfaX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iGCFYWhgXVVkaDguJ0BAZoxscoCVPUU7R1pWWFtbXVxcWVtZV1dWT05RNUQ0ODE3RFZSNzs5VVZYWVKUj2NzkoN4k4VnlVyUlIaAeEwxPDJ7d3WUdHVHd6GYsYyjwm53foeNj5GUmDg7K5ZvdSFLWVNZMTM3OTxAQENGTEw6PjYoNTwdBwMDBgwaOjs5ODeGNoU3hTYHNzY2NjU1NYY2CDc2NjY1NjY1hDSIMyAyMjIxMC4tV1RNRUJIUlhaLTE3OTU6RUpKS01NTk5PT4VOgE9OTkxJTl83WVhXVlZWVVVYbNmOtYiQioWCgID/gP338uvk29LJv7Wtp6KbmpaSkY2LioaE/vjz8Oro4+Db2dfUzsrDvbi1r6yqpKGfm5WTko6Gg4SB8+7x9evi5vCFk4j5yaKYjZ6pm5STkpKTlJaYm56ipqqutMXY3+Tp7O/0Kvr+gIGDhYFudiE4LSswK0hx4Ovh19HJvbSqooCHjoZ/d3BtZ2RgXVpXVYRTgFBNmp2hpKassLO5znaEkZWaeqCnrbO5vsLGyczOz87Lxr63r6mjnpiSi4R9eHR1eYKV79Se3IOasb/J2t/g4eHj5OXm6uzw8/b7gIOujZOapbHC0ub4md26ysvJyMfHxcfHyMnK0emdg+vQvq+km5GLhYH58+zn4+Dd19DLwbizIa+nnpeJ47mPuNegmJGNj5ecpKurrbCxsbGytK3Mf7WauYS7gLi2tbOzs7KwrZ+Fa6iMhXt1cnBuamZiXltYVFNSUE5OTU9QVEx0WYKRurazsK6tp9fd7oCFgKrXgYeMjIiB8M+KjZybqqHlkYTmqJmZlr6ypZ6S2ZrSwa6xlteYp6eXie/C9/G0pbCKwL+5qb6bvcjy2NjT54vQz8yu2rqJmtbegLCZ/6i0qZONjIuan7uHxNPa3NzWq/m57uLdrerZrq2ppoLWiJCKgKq0ubH7zfmxsKyrmMrr69/PpqWkpZy8uKClm5l9hpGDx72coqKdlbSbv8GksX6bmZnB7qekqq6vpqSmrLOalpOL57i7vYfTnduPm5mTi4OQzZOZp6SZz8CfgKe1npKXqYSZo6OCxY6jqLHBsJL4pKqso9G7u/2k0KCj4oeBismvrrGBs8jVxIOp8L65gbq9yJC/7qX6oZW3sf2FsL+3rcGCnIyMoZ3/2sSxopyUh4J5cmxlYF5bVVRK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p5FeaEZreXVzcF5PWYVAQkBBOD1JNERISUV3XkQqWkRPY3JRTUY6Qn5FQFVNSEQ+UTNXak5OQFhBRkM5LUM4J1NGQDBbTU5KOjooLTAhUlNJRlJTQkBBVTo8OSpRgE9OT01VVlFMWl1DU0EjMTU2NzY1MVxvSURpVldlUlFOUZFsfEI+dVZfY2NGPZBlZWZkZWKId2hVV3qeYmV0iXNxbG1qe314bV5tf4B/eYGdfn2Cgm9ti519SXh9gIGAe3Vwe3aOmVyRYEtdXmFbrYZWWldasbSwVD1PWVdPfGZQgFFWY2pbTkVNUU+FWEZNTWWNZEB0SE1PTlZUUnBcOCtkaG15QkNARUxtL01SLTlcZ0xKWT5KTFQ9YklFKkZHMkc+SEZjPjVGPklFP0qXhoiHhoSDgn99eXNoVoRwdnRnWVR4ZGdrbW5wc3aBkJKRdGuMm41vc3RejGpbY3eGkpScPFRXe3lkdIB3TmSWbntjtrphYmVlcKWQYI9nZY1TY2h/bn2Eh4yNjIiDgHx6eG9kYVBVV1hYWVlaW1xcXYVeiF+FYGhfWVplay0rRzNnYYV9jWJ5j1BdTjcsbWdhZmVlYjE+MTgyPjFjS5BMUlFSUVBRWCk7OVVTUlJUbmSPUpJwcFKCZoiPloNydi80PXZheGNob36SllBdg2x+iY5KTlNZX1lJPTQsU1BQQx5OTUssMjc4O0JLODQxKUQvOx0OBgMFCww6PDo5ODeHNgE3jTYWNTU1NjY3OTk3Nzc2NjY1NTU0MzMyMYYyHjExMS9bV1JLRUZOVlhZLzQ4ODU5QURERUdISkpMTYRObU1LSkZCPj1FTlFSUlJRUE1ISFHhiqCIkJOUlJWWl5ymtcTU4u76g4uSk5KRjIT48O7x8uzn4dnY0Mi/ubKtpKGbmpiTko6Mh4L89Ovi3NfSz8jBvby6squkop2Xj4mGh4qJi6WeidyrlvSamZWGj3iQkZOUlpqepbfGyczP09fc4OXq7/X6/4KGg+JhJTJFMzZRYGzc7+bZzsO2q6CVi4G5cm1mYFxXVFJQT01Mmp+ipamrrq/Bb3+KjI6Pk5WYnJ+kqI+pt7zAxcvQ1NbX1su2mYF8h5zqidqUttPp7/Hs4+Pf3efi5OWF4kbn6urq7O7v8PH2+Pz/goS/hJGYn6m1xtbrgM73vMvNy8rJyMnHyMrMztn/34Hp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x6ihqK2wtLi6vcHP6OzpS57M1tiwtbmX1bju1s3agZKF55a+pd3c5eCr36TdhfH3/IiMiYyRmPGS58uC5/ajprmss8fMzs3My8jFw8G9uaKmhoyQkpOUlJaYmYWaBZubnJ2dhp9soKCfoJ+UoLGVtpyE67KJ47ml1qDRgISpooC9rrj5rKfBoL+L1JuVk4qDgPXsgIGB9/SGrty7nY+OiouPtpnM2vq22KKImeXl3s7agaev47To0f+6oJSQ1f6FjJGUlZaYnc7Zt4G8+NLB66LGIjMqKSclPionSnFpSXVSZ3NDRS1BQCt1aGZkY2JiYmNiY2OEYglhYWBgYF9fX2CFXwRgX19fhGAlX19eXVxcW1pZV1dWVVRUU1JSUU9MlIt9b3F/kZaXTlhhYVteZYRufG1ub3BxcnNzc3JycW9rYVphbXZ4eXh4dnNqZGZvdlBCUS0wMjM1Njc4Ojs8PT09PDw7Ozo8RCctNDtCJCguNDk8HyAhQ0NBPzs4NDAtKSclJCMhISEgHx47Ozg3NzU0MjEvLiwtLSsqKCYmJyYnLCklPTMtSMfNxcHBwL+EwES/vb6+vc3m5NzX0s/MycbCwL6/wMLGZWhscvGMsn6Wb5x+5+HVycG5sKqhl4+GfnS1cG1qaGdmZGPHzNLV2N3f6Iqiu4TGLcfGxMTEw8TBwL66jLSysK2qpp6UjImLlWpTND4jJiYlJSQjIiFCQ0JAQUFAQYpAIUFBQkJCQ0NDRCIiIzEkJicnKSsuMTU6NlmUpaempqalpoSlH6OjWTIZLSooJiQiIiAgHz09PDs7Ojk5ODg3NzY2NziENwQ2Nzk5hTeAODo8Ph8fHzszK0hnnpmXl52jpqWlpKWlo6Ggnp2alIp6loSRlI2De3NtZ2NfXFlZWlxplV1xfYOCb3aBTXR9e3daX5OEgT9BQkdzPko0RUdLR31cNjVXQUh3dVBMR0U0W0Q/Uk5HRD5OMldqTk4/WEVDPjhZQCo5UEQ/LVdMTEqAOTUmKSMoVENMWmpASUZsWVVERTtQUE1KS1RTT0xZWkJRP0YwNDU1NTQuW3FKQ0BHVWZSUU5Qi24+Qj51Vl9jYEdSkmNkY2GqjJV7dmmFaJJpbGRlvHBraWuCgH15YWuBfn54fJp7dn2BbHR0b5uAaGF8f393caphYWG0wqZMRF+AYmNcVYRWWldXYWO9VUVQWFVPemRQUlRkbF5MRExST4quUFBPa5dnPnNHSk1PU09Qb1o0UF9ocHdCQGZDS3FMSE0rOV5mS0lYO0lKLkhiSENOREVKP1c9al52PEJEREVBeWdte4SDfnx4cmhZl4N0amFVmo6Ih2phY2ZpbG5vcnVSf5CSkGGChot/h41ogH2YhnuATFVOi1tzaIOCiIVmhWSXY660u2VlY2VnbKRaiYdQjZdlZ3Nqbnx/gIB/fnx5eHZ0cGBkTlRWV1hZWVpbW1xdXYReAl9gjF9nV11mQTMsJFVmT6uUhqBuiU9QYTYkPmRkVzQxODA8MGlcWllSTkyTkk5OTZKOSzc/Pl1XVlFQU21ceX5/bIFhUVyQkoZ3dywvL3JkhnF/TC0pKT9LJykqKSkqLC48PjQiMkY5MT4uN4UEFwgGCh8xLSc+Kzg6DgYDBgoNODo7Ojg4hjeINgE3hDYRNTU2NTU2NjY3ODk6OTk4NzaENYA0MzMzMjEyMDAwLy8vXFZORUdPWlpaLjM4ODU4PENFRURERkdISktMTk9OTUtJRkE7P0dOUVBQUE9NR0RIUFuDsfuVpK64vcHFzNHY3OHj49/b2tjW2vaPqMfn/46lv9Tr+ICBgP/28Obi1cvEvrq1sayno52Wko2E//f07enl3YDX1NLMxrqvqaWfm5eSjo2vrJXur5fX3ZeSjo2NjY6OjY6Pj5GUmKO2u77CxcnO0tfc4OXr8PX7gIKFgfBuZj1KPG9w5ezo3tLGuq6flouAeG2oZF5YVFJQTUyYnKGmqa2wtWRzgYqMjY+SlJWXmZufo6itsY66vsTJzMe2n4qEloDA2/W864mTk42HhIGAgPvy7Ozp6u7p6ejo6efm5+jr7O3v8PDz9vj7/4GDhbWNkpmfqrTF1ev/44C+y83My8rKycjJycvO2pD/gOnQv66kmZGJhoL/+vTx7enk4NnV0MvKytPa2trZ2d3b2d7l6u3w9PX4+vuAh4ntyqHY18qyqoCnrrm/v768vLu3trSxsK2poI+4nJ6WkId/eXNtZmNhXlxbXGJuQk9UVlJIXmlar87LyZ26v4HuhIeJjv+16YWNjpaM/9yRksqCk/TxpJ63zpymj4ivoJiSjOKe1MOpqozHlpmUiv3Gj9/FpJuY5LCyroqUmOG0rOOtt9Hwlqyp9YDR2bO2lb+/uLKywcG6t+Hjg4uk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DA4PIDPNysfBvLexq6eKrKWvvczziIDs3c+9qaCZko2NmcPQhJKanJqT++mIsb6zelpZT01Ec1BVV1pJSEdqaJ9+U05yOzdzUU6Wj19ZXlE2bFdSaWVVUEhaLVR3WlpIe1RUT4JeNHV9UHJNhl5/Tk91UT08gDldUlhcWlZWWFKSWE1BQ3lZWllWVVtbV1BjeVvXQDIfICAgHyBZQoOJQWmEPnZeXFpgYCUTJyQkam50dVpRtndrb2wTFz8kSHdyg4iKjo6VmJmbj5GhnvKob5H2n6WTudCmn6Si8//1qWF4nqOdmKmmgpCYm4mC9NpUkoWNiPH2gM/we+nrkI/6V01tcXTVwoZpb3Ov8KZNSmNpab7FdGxo9dXOQoVcXmNxa2FkkoBaQV5+i6FaqH2aVI2hWFs0O3F4VFFgeVZPL1R2WKCNpFUsSmObjaCVazhFm5WRunpjZpWlxOXZyb+1raafmZmboKy6w+CQlp6ipqqusre6wMjbVvD39LKSqJTdnKSvraKcnPX6/f/8gICA//Xr39Wzrpar8/X1+oGrvrGnpKSlpqnQvL2/oKm+wcLBwL69vLq4t6Ook4mPkZGRk5WWl5mam52dnZ6fn6CfhZ5snZ2dnJqOqraCuqfkvLGAh7aDmqD/84G3qqal9sCHrqyoo5+Ylo2IhIeJiIOA9O74jI2P0cf9rpaXmpufpJqTh5uxnabD76ijuperj4SJtO+V2t2lgJWfo6inmbO1xKWeutTo8oLw1rWSg/2DFVeeZlyPZVlKY2kwPzInJCs7Z2ZkY4diBGFiYmKEYQ1gX19eXl5dXV1eXl1dhFw/XV5eX19eXl1dXVxaWVhWU1JQTpeQhndrcoKTlkxRW2FdXWJrbG1tbGxsbWxrbGxtbW1sbGxoYFxhanFzc3JzjHIJcXFxcHBwb29vhW4CbW6Eb4Bubm5vb3BwcG9vb3BxcnJ0dnh7gISVWGc6QUlQVywtLzUiLz0lLTM1NDIwLSwrKSgnJiUlJCNEPzw4NTIxNzQqOS1zw8S+vb6+vb/CwsHBwcLE3e3p5N/a1tPQzMjFxMLBwL68op66vb7AwcHCw8PBvbm0rqWdlYqBenRwa2dlY0RhXl6ZZm5ydnyPqcLJysvMzc3NzM3MzczLy8nIx8bEwsC9u7m3r5awr6ypXS8mLiciHjYxMC0qKCcmJiUkIyMjRkVD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LmVlfFBSVlhaXWFkZmltcXR4gIGIkKBSWFJXUo6Hb2RhaGZmZ2hpampsbm5vb3CEcTVvb25tbW5vb3Bxc3R0V3V0c19rdHV1dHNzcnBvbWleYEtRVFVVVldZWltcXl5fXl5fYF9fXoVda1xcXFlVY2kyMSwwaE5WUk1+oGF3eaKoX2OCaEBBNWxuaGNaVlNVW1lZWFRUVlpbW1xbYig2aWJkY2Nncnd+SUA/Lkw9Wk2BdU18Qzk8JYiMf6FcY2htbmlch2RjbG9wZ3OBjUxFPkFkamhbE1tcUEEvOx0HAwMFDBw8PDo3NzeFOAE5iTiCN4U2BTU2NjY1hzRMNTY3OTk5ODg3NzUzMjBdV1BIRUtVLS0uMzc3NTlAQkNBQUA9QUFDQkNERURDQkJAPDs/RElMTk9OTk5NTU1MTExLTE1NTE1PUFBSUoVThFQQVVZXWFlZWVpaW1tcXF1dXIRdhl5AX2FiY2Voamxtbm9vbm1ubm9xer6j86LN9ISJmu6/irbZ4tzSx7qol4f139yE94CTpIyGgX96nqB4lYaGh4iJkISaNZucn6Kmqq6zuL3BxszQ1dne4ubr7vDw7uzn49zTyb+zp5mQhXt2cGtpaGlucnZ8hoqOs7GwhK07qqmqqqmpqqqrrK6ws7W3ur3BxMbJzMzN0NK9193i6PHk2q2xoI+F9eDSxbisn5mSjYqIhYD/gP38+feF8x7x7/D1+Pr7/P3/gIKCv/+KjpSZoKexvtLogY+mzsyEzoDNzMvMzM3T7p2L9dvGs6iel5CMiISBgPv49fPx7uzm2NXp6+zs6+3r7/Pz9Pj8gYOGiY2Wm5+ltbzO3vSDk5+60d2tydK2t7ayrauopISqrbXC0OPxfnlya2K0p5uQh310b3BGOkFCQ0NAOzN+S5yi6p6kn4+vx4aYm5yhyceygoCPgseKgdu3+pmRi+T+pprN6JmnlI+topSOg8+FqLKanoCsk5KDqdCQjLiSjY+RnJ2amLmxwsrftKWprKukpaWg35ekg4joqqiopaOnqaak766N1Yvhoq+0tbawirGpnJWH9Zqxh4WBhZ61zeaEiYWC9+aS8eLEk3qW3LHt49XDr4CMjpOYjpyruLy1h6fqiYnap4fUgoGVmbF2gYpweXfVjdeDfn19rnFvgY6Udm9vaqanioWJ7c6j8/vs8mxiw5iE+vqA77CD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OTg5ijgBN4Q2hjWJNDk1Nzc2NjY1NDMwXVhPSk1ULi8uMTY3Njg9P0BBQkJBa2BIWUBDQ0RDQ0I/Ozo/RUlKSktNTk5NTk6FTQ1MTExNTU5PUFFTVFRUhVWEVgtXV1dYWltbW1xcXIRdiF6AXV1fYGBiY2VmZ2lqamppaGlpamtqamprbHGE8suRwOyEiaOJ3pe4xLejjfbc/I6VkYqIg4B/fXxhc5Bwg4OFh5SbmJeXmJmbnqGkpJ+kqa+8xcrP0tba3uHk5ubk497X0sq+taqfkod+eXZxbm9zd3yFjJ6xvr+btr27ubi3t7U6sbGvsLCwsrOztri7vsHEx8rN0NHS0tPU183W4eXq7/uVkr6znpCG9ePWwrCimJOMioeGhYOAgf/9+IX2I/Tz8vb3+vv8/f6AgYK3h4uOlJmfprG7zuOAj87PytDQ0M/OhMyA0dr4uIbv1cGxp56XkIuHhIH/+vj18/Du7OHQ3+3u7ezt7/Hz8/f+gIOGiY2PkpeiqK6+xdnogYuesMrky+zgtbe1sq6sqaaEqrPB0eb+la6hh350a2S4p5iOg3l1eEo9REVEQ0E7NEBNtq7woaahk7HEhZuchIqRi8v9kYPHjIGA27z5mJCM24apmcbhkK6VkratlJCBxoGksJqb+qPPgrPeleyrs5KMjImSmJeVwv3319+xpaWkpp2ipp7dmrGNkO+ooqafn6Kin57qspLFi96frLCxsayFqpfj1N68v9mTge3Zga/N8uXQ6WRdWUlddmNyeZuxlJWPh/XAiIeBho6AiYKEj5Wnz7Gdr+zbcF9zfGCGaHjEr2t5eXWT0n96d3ipdm2AfottaWhmur6Fg4TgyZny++juZmC+lIP09frjtIDfzrqfiYOH1sm5qZRzi5GLmGbh0rmpwOOfo63ElvD/yM2U4tPs0ezC2LCd4Ori6La34tLriNLw573E6NShwNRF+ci4u6iRpcLRrL6lnqWYgqarxZB7nnOUj4iBfnx3cm1iW1NYMzQ1Njk6PD4/QD8/QUNEYLHvlJ2xn7GT1q16X1Q+QURFhUYfSEpOUFJVWFpZWFZUU1VWWVlXV1tZV2FHRkhBQUdJSIRGFURDQkM6PzE1Nzg4ODk5Ojs8PT4+PYQ8BDo6OTiEN2o2NTMzPDwHBgUdPVM+Pzs+a1NdSl1/QEJFPU8LBSZKRENBQEFCPzg4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eWxxck9tUmecdGGZZWRgeG9htVuhmXeqrKmfoodjhV9HX4qgbHZ9hYWlX59wWYWKhoqOjJCIg4FwWFBve5WVjIyLZYdsVXZVamp2bYd+VlJSa2x7XU5ReG59hGlyW0tSeW5WZnFYREljYFNhWUU9a2NVX1c8TGBqVpFYi3NchIpki4yIgn15gpeZTk9QUVRVVldYWVhYWG1wb2diZWBhUYqBdHJ2d3h7fX18e3t8fX5/gYmYVFxjaXB2ent6eXl4eHZ2dnV1dHV0YWdydHNycXBwb25sYmBTS1FTVVVWV1lZWlxeXYZecV1eXVxcXF1dXFhXZFA5LSRtZniptnORjVZdWo1VfWlldl9rPjxbb2NbVlRZXFdYWFdZWFtcXWBeYmIyKiYxNziEenZWeFheWaaYlZWBb2xvcEUoMWySVVpbtZhurml+goF4YW9tf09WV05LPipUWUBYCj0QCAMFCxs8PDqPOAM3ODiJNwY4NzY1NDOFNIIzhjKAMTAwL1tUT05VMDMzNDc5Nzc7QEFBQEBAQkJDQkFCQkNDREI/Ozo+RUhKS0xMTUtJSljAV1RRUE9OTk1NTUxIP1GkgYiQlp2nrLK0t7ezsK2ooJmSioLz3825oojeptCNc2pmZWNiYF9fXl9fYGBhY2RjZGZmaGhpaWpqa2xqaWdtZ2ZlZWVmZ2hpa2ttb3WVlfqz64eStoCB/s2Fe3l5enp8fX+BgYKDi5mZl5WUlZWXmJqViIPAuLG1wMqNyc3Mz9LS09HMx8K7saulnZaKg317e3p9f4WLmLHO4uXk4t7b2dnOttTSz83KxLy/wIS/e8HDxsrN0NTX297h4+Tj4uLh4+Te3ezx9fr/kfOO0rSij4Pv18O3sKWgmJOMh4OAgP+AgP/8+/r5+Pb29vf4+Pj7/f+AxPiIi46Sl5yirbjI2vWIkt7T1tfV1NTS0dHS1eSI6oLqz76upJyVkIuHhIH/+/j18/Dv7NnO64TvgPLz9vj5/YCDhomMj5SbpKi2w87i7oSSpb7eoJiVsrKzsrCurKmGr7bI3faQuIjCgLGimo6DeGvIsZ6Rh4CAU0JGRkZFQj00QVF9zYGpq6qZuMXJiJWpnYHolu+RgceK/9K18JmSjNeDpJe61JHKkpK/tpGQgLXflYD74s6TkoOCgLfG8J/0jISBhYqVkZHAh4be67GfoJ2dl5ib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hJylW2l6gn55bHGNVlhSi4ZLNkU3XGdaFQcDBgsbPD07OTg4Nzg5OTk4ODg3N4U4CTc3NzY2NzY2N4U2gDU0NDQ1NDQ0MzIyMjExMTAuWVROSE9bMjM0ODw9Oz1CQkJBQkJBQUFCQ0NCQUJDQ0A8PEBGSEhKS0xMTEtTlbOygvFmWlJRT09OTk5NTEhAToLk5Ofr8PX5/oCBgYD//fv39fT29vf5+/v8/v+Agfzt3868qZWA1Kbb3H9uaWZlfWRkZmZnaGlpamprbGxraWhnZmVkZGVmZmZnaGlpamtsb3J99eGo27+vfnh1dnd4eHl8f4CBhI6Yl5WUlJWVlpaXmY2Vz7ixray2xc270MjKzMnFv7qxrKWel5GIgX58eXuBhoqPn8Ln9/b28/Hw7evo5+fF1N3YyKqeqLDGhMmAysvO09bZ3ODj5unr6+rq6unq7O3Y9PT3+4CDrJixzbScjIDl1MS5q6Kck5CMiYWDg4GBgP78/Pn49/X29/j6+vz+gICzhIiKjZCVmqOsuMfa84al49fZ2tnW1dTU1dfb7JmEgurQva6impSPi4eDgf359vTx8PDv4t/t7+/w8vOA9ff6/4CChIaKjZSZn6eyu8zc8YORobjbyLK/tLCys7GvraqItrrI4fqWx5jfmsvusqeakIR3a8KvnY6GhVJCR0ZGRUI9NEJXhdmFrLCrnaj4wpnKr6CEk8SAkv3Kh4HTr+WZkovY/5ySsM2P0ZKPi9+PjvyilY/R6omUmo/89qyAs6KbrYyB/ISIkpCNt4aQ5/aynp2cmpSYnp3Qov6NkNGekIuKguS+lt1thl+F99H34LyY45l3hHlCWVtgbEBMWVhTSEAmNHtnUVRTTkJRS0lMWGJgX15fY2NiXVpZXF5fW15iaWtub29ta2xqYFxfYF1XWFVUWVpWV15ZWFhdW1uAXGhubG96sWNpvqR1iKSxuJTTp+B+gF6A+NXH4+3d7+Tg4eCQvLSTnN3X3N3l6OXcvnXjjZLG1fHz6erwscqrgqCC1sDRv8/bvYK+s/qqyqymzLzU1KzI1Lazxa+Vorveu6yhmn+SqMOhraOQm7OoppawfoizgXa4d6OkpaWjoZwulI2FZGJgYWJjMjMyMTEzNTc4O1V8hnRXUl1apol0e21zdXVzbWdjZzg7PkRIS4lNH05OTU1MS0pKSklJSEdDPkZI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trS0tr9qHBkuKigmJCMiISAgHx8+PYY8gjuEPIA9PT0+P0AfHyAgICIjIyQmKCksLzMdIikwNUY4mZaWl5aWlZV3k5SWnKNYZTxILTk8NFyrnY1+cGGokoBxZ2prY3F0eHd0bVxeaGCCQ1NUUWxcPzlLY1dQOzlEOkZ6bUM+Wj1RWElGd39MSEZKacxLR4dsSYVXZ0JTf0hHRkhEfIB5V21QZG5BPXI+P0ZEQl1JQkFUZEtLS0hHSUtKbV1NKCo9cVmYgm1xcXBqWoyNTFVUbXJzdHNyXZaRT2Boao6HVWVkWV5wUYtudpJdW1ZSfk5fVltvbGtoZWVnamVhYGRnZ2Zoa3Bydnl3dHBva2VhaGZiZGVhXmFkYmRpYWdmZ4BhZmZpaGppaGVtdadZhm5pYlx/VXJXV6+xmZJzl5+ekp6TjY2MoF2dVVOCfoKEhoWEf41ZgklMdnuMi4aHhmZ/XJhupGdhb2qBYXBsZFF3fGJjQXFsfoRkbFlKT3NoWWNwW0dHYl6XXVVCO2JkWWhWO01kclNTXUZaWWdnmZqipSylpKCeoI6Ojo+Oj4+Slk1QU1ZZXWFoa5aXk25IlF5kiI2MklNfaXN8hIOCgYSAEX9+fn19fXx8fHt6eXl4eHd3hHUhc3JeaHFxcXBwb21sampbXkhNUVRVVldXWVpbXF1dXl1eiF1rXFxbV1hkOT44MGxggIqdWmyVtY11c152i3duXnN1MDBvW1laYGNcVVNSVV1nQkUoKytFc0MwLWlub3d6elFuqamppZeXlYl2cHBucTI2QHKfj3dfXV5ja3yBfXlye05VlE9RkXJ4YGhIOjQHBgsbPD07OYU4FTc4ODg3Nzg4ODc3Njc2Njc4ODg3N4c2IjU1NDU1NDQzMzIyMC8vLVVPSEpWMDIzNjs9PD1DRUVFREOEQmZDQkJDQ0JAPTo7P0VHSEpLTExNTVaP+N2YoJuWjr1bUlFRT05NTk1MSUJP197c3d7g5Ofq7O3t7ezr6ebm5eXn5+nr7e7x8/T2+fn7/oCChIiLkJOVjoHjwZ70qsGmd3Jua2xsbWyEa4BpaGZkZGRlZWRkZWVmZmhoamtsbW1vdnx3dXJzc3V2eHl7fYCEkZaVlJOTlJWWlpeZnJyUkc63uLi7ypXCw8HBv7q0rqafmJCIgXx6enp8gYaMkKDR/IOEhISDgoD++/n2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aGZmZGVmaGRlZmlrdGVdW2RzWoyYVlyQmp2RnpWOjo6mYKBlW4V+gIKAfoB/i1iGUFCAdHqJh4KChGWBWJFuomRgcGiDZHRwamJfdFJCU1pmZIBlaFVITnFlWGNwWUVHY1+WXlRCO2RmVmlUPEljcVRXN0daVlN/daKmqqysrq6thIOEh4mNk5lOUVRXWl1iZZJriJJjcl9gZolhdIGLi4uKiYiFgnt/gIB/f35+fX19fHyAe3p6enl5eHh3dnZ2dXRzcmZhbXBwcG9ubWxraV1gSkxRVFVVVldZWVtbXF1eXl5dXV5dXV1cXFxbW1dYZEA/OzFrZJOpZmBkZbDTY79qaX14c6ZxbVlCM2FeWlpdYGBoSUUnKDsyO15YV1ZiJi55c29vbW9/Zaijop6XnJB+dG8icXV0QDY4UFJeVV9jY2NmeYOFfHWSU5ZKTl9WRF8+MTMrRwYaOj08OjmEOIg3BTY2Nzc3hTYBNYc2Bjc3NjU1NYQ0WjMyMjAvLldQR0RPLjEzNjo9PT1BRUdHRkZGRUNEQ0NDQkJBPz06Oj9FR0hKTE5OTE5h7Zv9oKGemZOOioXcYFRRUE9OTk5NTUtES6XW09TV1dfY2tvd3dzc3ITbUdzd3t/h4uPm6Onq7O7v8vX4+4CChouOkpSWmJiZmpqXiOjBltTxwX1ybWtraGhnZWVmZWVkZGNkZGVmZ2doaWlpamprbG5wcXFzdXd5e36CkIaTgJWVl5iYmpyfoaKkmI2Pm7O8u7u6uK+nopuUjoV8dnd1dHp/hIeNpNuGi4uLjI2Mi4qIh4aEgvvWt8GokoGvu8HL/rvT4eLh4ODh5ejr7fDy9fb39/j09PT19vji/f+Bg4aJj5iUyrWfjYTx3s/BtKijmJSNioaFg4KBgP78+vf2gPPz8/b2+Pn7/NPfhIeJjJCUmaCptsTZ8YfW/Nzj4+Th3tvd3uHl9aiUguXLuKyhmpKOioaC//r38u/t6+nj3+vt7vDx8/T3+v6AgoSGio2TmaGntMDR4/WHl6/M6autwK+ysa+urKuOvsHQ6IOa1qn9tur2+INpY7mtoZCAccq6gKKUjlBESUhIR0Q+NUZbkuqRv6yzxIKrhaTbuqeCnMCbkv/UjIHKpuGjkIG88pCOpbmQj46IiL215ZGBv5W+jYyOkYeB77Dlx6u7iIT5goOOjYywh+Pdt+Of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WWhFWEg+LiwjJTlWeFdwTy4rKy49Az07Ook4hzeINgQ1NTQ0hzUENDQ0NYQ0gDIxL1tUTERKVS8wMjg9Pz9DSUpHRkZFRUVGRkVFRENCQT45OT1DRkhISUpKSU57pLOIoqCcmJOPjImFgv2mYldSUE9OTk1NTUtGR+/Qzs7Pzs/Q09TU1dTU1dbW1tfZ29vd3uDg4eHi5Obn6Ort7/P1+v+ChYeJi42Oj5CSlJWXGZmbm5yYg9Wi4YTRdGtlZGVlZmVlZWRkZWaFZ4BoaWtrbG5ub3FzdHh6fIKNjo+PkJCRk5SVl5iZmpyeoqWnqaywsbS0sq6qpJ2XkYuEenVvcW9zeYCEiafpjI+QkpSVlJWVlZORj4z4ysWW38/gi6KqsbnCjezL2unp6Ofp6+3w8fT3+fv8/fz5+Pr7/f/86YKEhYeKjZbP0MGrnICOgvLdx7qvpp6WkYuIhoWCgoD++/j29fPz8/X19vf5+7H/g4aJjJCUmaGqtsTb8Ybph9/o5+fn5ODh5Ofr/IaQ++DItqmdl5CLhoOA/Pj08O7r6eXh4+rr7O/x8/b4+v2Ag4WIjJGXnKOuucbb8IKRpbzh9OHJrbGxsK6trJS+x4DV74Sa26yFvvT/gP+GdW1ku66ejHzcw62WklVGSUhGRkM+NUZcme2TkvOboYDg7qXguKqEm72LlYHRjobIoNeRj/et9pSPl6z8soj1aqu5gdvvnMvmiYqLkIb/8a7hyay6hID49YCMjImph7qs/baemo76yqHVcXS/z5OPZVdeZIBjZGNra2VheHVGSlNJUVJTU1NQP3pTUFtcVX1hWVRPS0spKSU2L1RJOzM3RlBITl5cXFlZW1lcXFpYXF5dXV1hYGRkZWNiZGdjXVZZVFJRUU5QUU9QU1JRU1ZZUlBQUVNTU1JVVVhYVFFTVVNUVVZWVWWLX3Cce3ikcI3D08vQ1ICwTabVtdPM09bPxMnGvk23sZq/xNrSyNDWm7HsuYbwxbHGsK+XuLaqop+boYDrr4iudY6At6KbrpqRla3JsJ+ZheOFl7STnI+GkaagnI6vcpeYlHqBcZWIk8bS3HNybGBWWV5jaG90fUNGSU5QUFFQUEgzQ0BYUEMwVk9SU1JTUxBTUlFJNVc8SkxMTU1NT09PhFAnUVFRUlFQUFFQTUtKSEZEO0BERENCQUFAPz47OjItMzU0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IiEgIB8+PTw8PDuIOh47PDw9PT4+HyAgISEjJCYnKSotLzMcICYtJCmqlpiEmYCXh6Wdn6RVXGg/JzM+Hx89PTpwamGvnIx6aFiWgnNvdWZydXd4dnBcYWxmhlFyQVFJNzk8T2ldUjo5QTdHPWxEQlY8TUlIQFxBS0dAP3d8WKpuSIN+Z19gbGFFRUZJQ3d3U21QaXA/enh0QERCQVmVQkg9NmxRdmJkamJZXVFabm5xcZ5laGxybnV1bmdUiUtsWWJydXZ1dXN7SmhXaWVlTmhgXlpcYVdUU1BHZ3tsg1VZYlhWbWpmZmJkZGZjY2NnaWdoam9oa2tqbGppbmpiYWRhYGBhXV1gXWBgYmNkY2NkYmJeYGBfXF5cX19dYIRjgGJiX2BiYmRnan2RgHd0YXGWXXKgX6uCa4SDhoqHfoCAiVmOYFRwd4SAdXl6YHqZgmmfY2FuZ3ZWa2xjXWFcZ29AcWhlYaKAYlmBamJZYHFXQ0JkXJZZVkI5YGBUY1M6SGN1VVk3RVdYU31fWI1bXmBkaVpgY06YmppQU1daXWFlVWhqb3hVipKJeWqFkYqNjo2NjIyLioiFW4JdfH5+fn18fX19fn18fHp5eXh5eXmGkmtpc3JxcGJgbG1ubWxsampoZ1tZR09TVFRVVlhZWVpaWltbWluEXG5dXFxcW1tXVmI8OzJEaGdlc2ptgraLmnyjbnBwbYFbcHF/PDdGNys2PStmVVRVU1JWWFdVVlRTVlxKLnxbc25qqKqUkpWYi4uHeXBucnJIfIAxNTM8JSovPjw8PDo1MkcyIik0VGlBKjIyNkJmZAE6hDmHOIM3hDYDNzc2hzWENIIzhTSAMzMzMjIxL1lTSEdQLi8vMzo+PkRNUU9MS0pHRkVFRkZGRENDQDs7P0RGRkdISUlIUKbMzpSkn5uXk5CMiYWCgPv179WQXFNQT09PTk5NTUhGldXOzs7Nzs/T09TU1dbX2Nna293e3uDh4+Pk5OXl5ebn6evt7/Dy9vj7/oCBg4QyhYeIiYqMjo6Pj5CPjYuJhv/Yqv2ppoFtZ2RjZGZlZWZmZmdmZ2lra2ttbm9xc3R2eYGEi1iNjo6PkJGTlpaVlJeaoKWoqaytr62qpqCZk4+Hf3hybWtrb3R4f4Wn7Y6RlJaZmZucnZ6enZqT8c32o6b3iY6R05uhqK+2vqvZ49nu7/Dx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g32AgIdakGRacHSBf3V1eF57jX1nmmNgamF0UWVpZF5gcERsf25lYmJdYkw8SFeDVVtvVERCYV5SXVdAN2BgWWNUOUdjd1hbOkVWVlFdTlQgUqNiZmpyb1l5Xm1VUlNVWl1hZWlscXZ9d2d/iIqLjIyEjkSNjY2Mi4qJhYOCgYGAfn18e3t7fIRVNkEmLDI4HiEiIZpkcHJxb2xcZ21tbGtramloZ1xdR0xSVFVWVldYWVlaWltbXIRdhVxqW1pYVWE1Oy89aFRyrV9plWqJf5VsXWVscE1ETDcqNiM4Nl1aXmBYVVNUU1RUVldWV1dcYWRROS5qnIWFiX6Hi42LgoeEfXuERzwqKSQxIjs1KjY5Vn6MW4l7Z09MTGZoM0o0QT1OaWVhYII4hDmEOIM3iDaFNYU0CDMzMjIyMzIyhDEZL1pUS0hNWS8wMTY7Oz5GUi81OTFVUEtIRoRFgERBPDtAR0lISEhJSElR4/LanKShnJmVkY6LiIaDgPz38u3p4eRaUlFQUE9PTk5OSkWr2tfU09LS0tXW19ja29ze4ePk5ebo6uzt7u7v8O/v7+7w8O/w8vL19ff3+fr7/P3+/4CBgoODhISEg4KA/ffz8O7t7OzbsoezqYBuZ2VmbWRmaGhpamtra21ucHJ0dXeBiIiJiYuNjo+QkY6Gem2WuZKdpKaoqamrq6OdlZCLhX92bWtqaWlwdnp/pO+NkZOXm52foKKlpqWjkefo0IDT/oGEh4uPiOmaoKarsrm6rYHa8Pf39/j6/P+AgIGHgieDhISF9YiJi42Pk5nbwLm+qpWI9t7Lva+knJWRi4iEgoGA/vz49vSE84D09Pb46MGAgoWIi4+Sl52ntcPY8IaHpuHz9vb19Pb1+P6EkuqF7dG9q6CXj4uHg4D89/Pv7erp5+Tk5+nq6uzu8PP2+f2ChYiMkpefp7G9zN7whpSou6utpaiqrq2rqqibxL3K6Iae366Hy/mDhIP9+4WIgXhvzbakknzbvKOZV4BHS0dGRUM/NEZepIGTy4+2p6W0kbXtybODmrODloLZj4zAl8yKjIOl/ZmOipKPhaz0g4by+dLDj4amhoiHkIfx6qDPuL3BgPv18f+GhYCU7JqbgJ9uYV1ZXmNhVlJIOWJ6gn1wW2BoZGZoa15ZSXpEUzpQVFZWVlQ7PUBCVVhaW4BETFJQSkspKSgo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ODg3ODg3N4o2gjWENIIziDKAMTAwLy5YT0VKVS8vMDM4OTo+RUtOKR04RSs1U0pHRkZGQz49QEVISkpKSUlLVIqF7qClo6CdmZWSjouIhYOA+/fy7urm4tzjXFJRUVFQT09QTktFc8vb2dbV1dXX2dvd3+Di5Obn6uvu7/Hz9fb29ff5+fr6+Pf3+Pn6+vr5+vmG+E73+Pj5+fj39/Xz7+vo5ePh4eLk6Ozy9/X17Lfgg2xkZmtramxsbW5vcXN0d4OHiIeHiIyOkJKWlJWWlo2IsIShpaemp6ajoZiPiIN+dWuEZzprcHN4oO2MkJSYm56go6aoqqqlieeOtK3u+fz/gYSGiIuO34qZnaGnrba96qPc8fv8/P6AgYGCg4SEhYVEhoaIifmLjI2PkZSYn5+bx7aij4Lu2MK2qaGZlIyIhYKB//36+Pb08/Lx8fLz9Pa48P+Bg4aKjpGXnaayv9LrhInN6vuE/YD/gYSIj56NgunNuamdlo2JhYL++fTw7evq6Ofl5ufp6uvt7vHy9/r/goaKjZWbpK26x9zthI6itsaA9KqlrK2rqqedxb3J5oik47CHzPyDhYSD/vWDjYZ+ddrFs52H6MiwolVHSkhHRkM/NUZgqKek1ZS1q6a5lLjtzraDlbCDm4CC2Y6Lu47FjY2Dn/6YiuKyt57xgYWJ/oTe3ZmGp4SGhYuD7+mey7W+xfr46en137Hjk7zOl3phb2NYVVleY1VPTllTlpiWf1RbY2VlbGVoXWt3Q0RgUFZYWVlYTD4+PFBWWHA/R1BQST0lKSkpKlFfM0VFR05IRldSUllXXFpaWIBWV1hbWVpZXFxcX19dXFtcV1NTUk9OTEtNTExKS0pQUVFRT05NTE1PT01MS01LTU5OTk9QTk1JSEtNTlJRUVNSUFJTU1ZYXXZXcJDBpazGibi8tL+9r0yytKGsr8TFt7Suh6ipi3DXurHBsKbymaCdlpmdqIKAp5iRkIyVj+zii0umW5CKtaWUjYR1g5Cog5OEfoaUjY+Ir3eZkYl0em385YKct2ZtbG1dTTg6a2ROSE5VW19iYl9fX2JEQTlDSEtOT1FSUlRVVVdZWluEXDJeY5Wym96Or83sgoaJjJCTlZicn6GjulhHSUZERD49QkJAQEA/Pj07OjgvLTEzNDU1NYY2Ozc4Nzc2NTU1NDQzMjEvLjcdBgYL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Hx8gICIjJSYoKiwvGR4jKzM9RJuXmpuampmUuJygqFddaUAoNkAhISEgHz48PX12b2a3pJB9arSZhX5uZnJ4enp3cV1jZWUxS1tGTkw9QT5UbWNUOzY/N0g8cUVAUTZGRkZBVEN6b002YV+CRkVJRUZyb0NNZ0REQ0aDgHlzUW5Tb3J/dluVeGhmamlHcmdramtqYV5fY2dgW1tKXKOalmlnaW52b3N3b2xOiFNfc256fHt6enVrkEpeZmWNgFRgYl1Zf1BOSoV6aVdXVVlgWU1oZWJkYGRiY2FeX2NoZ2hoaGlmaWhmZWdmYWJgX19dXVxdW1taXV5gYWFggGJjYF9fXmFgX15fX11gXWFjY2JfW1pdXmFiX2JnZGJiZGNkZWRkZWdodYl1YGFphFZtdYBZkmRccHN/fXRwcVp3f3ZilV9dZmB0lmJkZWJkclBCQWxkXl9dXWNofGZfXU1kZT5AX19zZVk+NWBgYmJSOUZje1tgOENVVlBDbVtzMYNYYGdpgJ2QkIJue3RcX2Nob3R5gIiTeGd9anF9hYqNjpCQjYqIiImOXj5MLDE3PD2HHy8gICEhIiIjIyQkJSU5Zm5wbmxqXWhra2pqaWhnZWRbVUZPU1RVVVdYWVlaW1tcXIRda1xcW1tbWllYUmBGOyw5ZFVMPbaTi1Q4SUY6OUJRcGujbm1na3IwKmRbV56eo1VYVlZUWVxfX11fXGBldjAliJCFnXx7d0EwMSUmPio2Sn52cJBrQCYyP3Bpap91f2NbRF9CMjQrJi07Z2NghF+EN4Q4BDc3NjWHNgE1hDSDM4QyhjFLMC5YUklHTi4vLzI4OTc6QENER0pRJzI2NTwzMktIRUE9QEVHSEhJSkpKV7KR/6WnpKGem5eTkI6LiYaDgP359fHt6OXh3NWiYVZRhlCAT0xEYbne3Nra2dnb3d/h4uTm6Ons7u/x8/b29/n5+Pv9/4CA//v7/P39/Pv7/Pz7+vr6+Pf19PTz8fDu7ern5eHe29jY2dve4+ft8oSdqLfCmPaGcGxra21tbm9xc3V4gYWFhYaHiYqNkH96RS4uSoqTbneipKOioJ6YkYuCfHOAaWRjYWNobXF1mOSHi5CUmZ2hpKarrK2igPXOgtL2+Pr8/f+Bg4WGiYqLzJOWmp+kqrC4j/bf7/6AgIGCg4SEhoaHh4eIiYmKi4yMgI+R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ZWZqVV5cXWNleWdfnGZccHSAfnJvdFx1eHNgk1xVY15yk15iY2Vmb1BDQ2xgW19aX0BmeGVeYWJiS2N2ZmFsaVk/NmNjZGFTOERheFhgN0RXVjNRRWpcc1FQXWFmfFpWWlWXdqljY2dudXyDjJWff21mjm1xeIGGi4yIlCY6ISMlJCMhICCJHy4gICAhIiIjIyMkJSUmIHlmb25ra19jamtqaWhnZmVkXV2MTFJUVVZXWFhZWltbhVxqXVxcW1tbWlpZUV9xOS07ZF04OD54noLCZaVca2JmY2uga25ubWxXQyxaUk9QnKGnqKKhVVlbXFpZWmVra1U6SXhgT180NSkpJUVWm5qXi4V7cm9paSo6PT57ToNYUXtYdCkxMi4wP2ZkYYVfAV6DNoc3DTY2NTY1NjY2NTQ0MzOHMoAxMDAxLy0sVUxDSVQtLjA1Ojk5PUJCQ0RFR0lPVkMuPEc4UEdEQkRHSEhJSUlKWcegiKqppqOfnZmWkpCOi4mHhYOA/Pf08Ozm49/b1tDEyFpSUFFSUlJRT0xGW6Li4uDe3N7g4uPk5+nr7O/x8vT29/n7+/z9/oCAgIGBgP/+/oD/gP/+/f38/Pr6+vj39PLw7+3r6ufk4d7b2dXT0dLU2t7j5fmYmqKghbm2dW5tbW1ub3JzdHV5gIKChIWGiImLjXhqU0ouMzhAfJydgJ6fmpaRioN8cmlhX15dYmdrb47ag4iMkJWZnaGmqaysmvOGl6Tt+vr6/P7/gICBg4SFhoCIiffpkZOXnKGlq7ShsuzugIKDhISGhoeIiImJi4uMjY6PkISSlJaXmZygpMOfqLykkoTx2sW4qqCYkImFgYD9+vjz8fDv7ezt7e/w7q30+f6BhIiMkZedprC9z+aAi+WCjY6E27e1oKuNora/gOLFsaOYkYmFgfz38vDt6efm5IDh4uXl5ebo6+3x9fj7gISIjJKZoq68zOL2ipu01aaK1qKlp6elpaPds7/fhJ3VrYbI+oOFhYWD//jxgpaOhXx22MOwnYTlw7hYR01LSUhFQDVHYbWlptPcuq6YrZG+9s61goWjgJ393YqIrPiwhZGKpfTl2r3bp4SEg4aLhITX2oCShqX/hPmFgfDaj76vucLdd2FXWltaXWBFVXlcXVNgXFRTXF1bUk1HU2qEiIJ2XGNnaGtubGRXeXQ9NkRWW1xcXFtI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SiwtLTE3OTc7QUJDQ0RERkdHSEpKTlBST1VWUUhISElKSUtc+K6Mr66rp6OgnZmWlJCPjYuJh4SCgP759PHs5+Pf29jTz8qIWlJSUlFQUVBPTkZUjufm4+Hg4OPl5+nr7u/x8/T29vj7/P7+/oCBgYKFgYCAgIGBgYCA//38+vr6+ffz8e7s6ufl4+Dd29fU0tDP0NTa3uDjjpGPiOq87Xdxbm9wcHByc3V3eX+BgYKDg4SGiIqNdGw7QUQyQURzlaCcfI+TjIR9dWpfWVlYW2Jna3/JgISIjJCVmZ6ipqmokeeW0MT6/P7//4CAgYGBgoKDhICEhYeIicmJj5KVmZyiqLCq7fnwgISFhoeIiImKiouLjY+QkZKSlIaXmJqcnqGkpoaEwLGfj4Hq1L+xopmQjIWB//z59vLv7e3s6+rr7OzMy/T3+4CDh4uQlZylr77O5oCLm4mXmaGGsqr8jaa5xtH22cCuoZWOh4OA+vTv7ern5YDj4N7g4uTl5efq7e/09/yAhImPlJ2mtMHX64ORp8jUzbCioqSjoqGgdq660vmU06WCw/iDhYWEg4GA/fWFmZGJgHfdy7ahivPSwl1ITUtJSEZBNkdms7qk143AsZamlL73zrCDgZ6AnfXSioun66WEkMakz5CHr5/JzIiBhomAhoDVzIqGpv2EgYWA7tf7tLO4sINnZFhZYV5dYVpPbF1haFxfU1NXXF1UTU9SgYOJiX5aYGVmZ2xlW1tQbzpRO1ddXFxcW2RxUkhYWFJzXU9QT0lXm515NFZBSERCR09KfFZTT05RU1JWUVBQUVRUVVZWVVVaVlZZWFhSUk5NTktKSYVKEE1MS0xMT0tKS01NTU5OTEyETQlOT0xLSUdISkyEToBPTExNTUxKTEtNT01NS0pAMz9BREdIRy9NWaWUnKKxtamipIGcgXFjwaSVpaCR1I6SlJWTkJyE7ZiGgoV7kJj/55CBhYiImP6gViQshoeV9JWHhIKNgYSBomuThoR4gWqH7YajcGJ1iGloNi4yMjBTV2RpcnqDiImKi0ZnVkhAWkc/QURIS05Rh92UhenOtqaakY2Kh4aHh4aFhYeJi42PkpSXmZygo6arrIROSEZDQUA7Pz8/Pj09PDo4ODcuLDEzMzM0NDU0NIQ1BTY1NTQ0hDNqMTAwLDQ3BgYKNkNuFAlLZFNzXEdUNT48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YrGdiHZkqZSMfGh0eHl6eHJdYWlmPUlXMlJMPD49UzZgTztqPDZGbnNEQ01hQHo/ikJJS0JbVlJTRYBFSIOARW1rQE5mhUOBgmaUemxYf3F/bnFsZ2Bnamdobk12qnB3ZGllXGJmaFxaWk14jJSSamlsbW9wb3BrY4CIVmdLeYGCgYGAWIpbXGVnY1BvYmBgXaWqrWxzVllaVFZaYVyOY2FeWlpfW2BcWmFhZGdmZ2ZlZGdjY2RkZV9hX1xfW1qAWlpbWVtbXl9gYGBlYGBgYl5dX15aXFxhXV1gYmBdXFxbXV9fYGFkY2BgYmFhYmNjZGVjZWNpUFhbWV5iY1yEU2R3hmJ+WnR1bm5bd3BvXIpXUl9gbI5hZGVnZm5OgnppXVxdWmBJPnxjWmFkZHZjZFleo5RcPGZjZmtiU2tCX3w6UF02RVxiU0NlXHlXXYhxYKFrZWxqZFekbmxyfIiTnatdY418dXBrXHF0d32BhItnJx0zLSkmJCMiIYogLSEhISIiIyMjJCUlJiOPY2xraWdeY2lpaGhnZmVjZFtXjk5TVVVWV1hZWVtbW4lcYltbW1lTW2QwL0JgY56YNzujymmxZ3GooLKyYKBdYmZkY2FhKy5YUaCUk5OaUlJTVVhcYWNiWo2SRis/Qy85MmBcXVpfe2R2U5BSf1OLfHBpaodDLUs+TWQ0JjdANUZoZGJgh1+CXgs2Nzc3NjY2NzY2NoQ1gDQ0NTU0NDMzMjIyMTEwLi1WT0ZAR1ItLS8yNzY4PkBCQkNERUVFRkZHR0hIREFETE1LS0pKSUpZ5amOsLCtqqekoJ2amJaTkY6Mi4mHhYOBgP359fLt5+Pe2tbQy8bgWlJHQUtFMDBEQ0RR7+vq6Ofk5ejp6+3v8PL09vf4+vn9DP7/gICAgYGCgYKCgoWBboCAgP/9/fz7+vn28/Hu7Onn5OHe2tfT0c/OztHV2t7chYuC7dS4lnlycHBxcXFyc3V4e4CAf4CAgYKChYeJjIt4dlg5TFtseYicl5J2eX92a2BZVlRVWmBmcrX3goP1rqOFlZ6lp6aK5rSD4oODhYIFg4SEg4OFhICFhoeIhcuNj5CSlZqfpKqtl4LwgIWHiIiJiouLjI2PkJGTlJaXhpmbnZ6goqSprs7fzbOejPngybennZSNh4L++ff08O3q6ujn5+bo6anq8vT5/YGFiY6T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XX1Xel1daaJvZ29uaV5bcnB4g5Cer19ocKCIf3VycWd0d3h7f4KNPzI2LysoJiQjIiGLICohISIiIyMkJSUmJi5naGpoZWNdZ2hnZ2ZlZWRkXl9IS1JUVVZWWFlZWluIXGZbXFtbWllWV2EqMSRoXMXPYDxakG1sl2KVnamrsnuOmVWmU1VXazc8UKOenJudoVNSoVVZYWJhNTJENCg+PC1eVnNdYWSAepaeiXKCXHqVfnBrfHFrPTdlUD43KDxLa2RhYF9gX1+EXoNdAjY1hzaGNYU0fDMzMzIxMDAuLFRMQkBKKywtMTc4Njo/QUFCQ0NEREVFRkZGRURAP0NISktLTEpMWuirjrCwrquopqOhnp2al5aTkZCNiomIhoWDgoGA+/fz7unk4NvW0czHoGBMcWRYN0FINW5zTcPx8vDv7u/x8vP19vf4+/z9/f//gICEgQaCgoOCgoOFgnOBgYH///79/fv5+Pbz8e/r5+bk4d3Z1dLPzs3P0tbb2v2J99zIsrl7dHFxcHFxc3V2d3p/f35+fn+AgICChIeJjJCQjIqLkJmZlpONh4BrYmRZVFNSVltgZ53o/IGFiICizeybo6OF6+Gkg4yMioiHhoWFiIaEhYCGhoiI2vuLjY+RlJeboKetvY30goeJiYmKi42Oj5GSk5WXmJmajJ6foaOlqKqsu5ewyqyWhOzUva6gmY+Jgv739PDt6ujm5eTj4+Plpert8fb7gIOGi5GXoKu8yd/6iu2fsLO0t7i5vL3A2OPt5cy3pZqQiIL89e/q5+Xi4N7b2IDZ3uDh4+Tn6+3x9Pn8g4eNk5ymtMHa8oiXqMCA7racoKGgn52KqK6/3YW9l/W98YCEhIODhISCgYD7iJmTi4V65tO7p5L+381gR05LSUlHQjhIbLzQntepxq6RpJfAqtuugPuYgZ7555OSpdjz1tCKkpGXhrTPrfuD+YSG+4HHsoD9gaLxueV7W1dmajxXiXt7c2NcWFJjX11eU0tNZGt9hV5XWVpdV09KRU5mjpGJc19hXFRaXVtXRm86OktRXV9fYWBVOzlEVV1cWEZMUVNNcJVnYVtkQkZDRUNOS3VRTk5JSU9PVU5MTlFSVFVTU1FSU1NUVVFTTU5NTEtGREZERYBDRklISklLTE5MTUxMSUtLTUpMS05KTE5MSklHSklKTE5NTlFMS0tKS0xN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UVhaZ16GY2RjYl5eW2FbWV9fYmNjYl9fYWJgYWNfYFxdXV9cWllaWFtaW1xcYGBgYl1eYGBeXmBiW1teXV1dYGFghF6AX2BhYmFiYV9eYmFfX2BhY2JgX2BdTFRRVVpdYGaHik6GWl1fUVNYVFhaV1teZleEWFJkYm6CWWNjZmZsTHtyX1xZW1ZfR3JwYltfYmVJaGBZYGBjUmRJWmhUYVBkel5zkFNlgmBkV0VlXn9ZimljfV+Ba3JybmRoenqHlqpgbXgjhI4/Y6KLgHxykXZ6fHx9f4FeIhwzLyooJiQiISEhIiIhISGGIBIhIiIjJCQlJSYzaGZoZmNiX2eGaBBmZl1cj0xSVVZWV1hZWltbhFxiXV1cW1taWllZV1JfNDopSV9vXltvNjWLfrWChaGsrmt/gHRpb29fW1ZTRTxCdG5ygpM1KTU7P0Q7hnp8VYOFf4hHLStLa21slZNzbrLBaVNan2OTjHFQa00wLicrdGRgXl6GX4ReBF1dXFsINTQ0NDU1NTaFNYM0hDN5MjIwLlhQRUBKKy0tMTY2NjpBQkFCQkJDQ0NERUZGRkNBQ0hKSktLSkpY2qSLrq6traqopqWjop+enJuZmZiWlJSTkpGOjYyMi4qHhoaEgoD89/Tw6+bh3dfRlVpHfH8/PjlxQUlL54aIiYqLjY6PkJGRkJGRkpKRkYWQgI6OjY6Njo6NjIqJiIiIh4eGhYSDgYD9+vj38+/t6uTj39zX09DNyszP1NfdhvvPuar7pXlycnFycnN0dXdwY212dnh3dXRzdXd4en6AhIeLjY6PjoyIg3x1bF9WUUyLU1hcbLHh6vP6gYWJjpOXl/rvqIPCwrmxqqSempWRjoyKA4qJiYaKhImAiIiIifDnjI2NjpCSlZido6v6v4GDiIqKi42PkJKVlpmbnZ6goJOmp6mqrK2usLPK/NGulILr1b+wn5WLhPzz7urm4+Lg397e3d64yOTo7O/z+f+ChoqRmKOwvtXtg5m40NDNy8nIxsPG3sT438SvnZGH//bs5uLe3drY1dDOz9OA2Nrd4OPm6e7z+P6Cho2UnKe2x9/3iqHA0pD6n5qdm5qZlLirtcjwp4Xcr+39gYKBgYGDgoGA//v2iJ6Xj4Z86NO9qpSA5NBgSFBOS0pHRDh9x8H3od7HwaqHlpG5ouCx/t6HgZ398pX0qZ+Kj5iS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S2Q6LC8rLj1kYV9eXl+GXghdXV1cXFxbW4o1hzQZMzIxMC5VTEJEUS0tLjQ4NTY9QkJCQ0JDQ4REgEZGREFCSEtMTU1LS1XCoIqsrq2rqaiopqSioaGgnp2cm5qZmZiXlpaUlJKRkI+OjYuLi4qJiIeGhIKA/ffw6uCCXVBNTEhISktMSkqWjpSWmpufoqOlp6ako6OjpKOioKCgnp6dm5mZmZqZmJeVkpGRkZCPjo+Ni4eFhYWB//z4GPTt5+Pg29PQzcrKy87T0/aD2rinh+R7dYRyTHN0dHVvjl1SUFlhZ2lqa25xc3V4fYGEhoiJiYeEf3dvY1dRT01PU5teldPi5uz0+oGFio6P8efDqPj2693RyLyxq6SdmZSRj42Mi4uKjICLi4uKyYuMjY6PkJGUl5uhqofkg4SJioyNj5GTlZeanZ6hoqSSqausrq+wsbKyvJ2zvKCM++DJtKSXjoX78Orm4uDe3dva29vbnt7i5urt8PT5/4OIjZSeqbjO54Cpxd3d19HIsqGtvNaG+9/Cq5iMgvjt5eDc2NbV1M/KyM3U1oDZ297i5urt8/mAhIqQmaKuvdTshZSx1IPaqpeam5mXlsmjscXwmPfOpuf6gIKB/PqAgYGA//37+IqfmZGHfnXVvKeQgOTUY0lRTktJR0Q5R3W8j6XizMWrgY6UvpHgrv3Xg/2cg/C84NSa6YeYj5CPkoCvxKP+gfP95ZGYbGSDlIB8a25cXFpdVldoW0Q7Ul9iaF5VUVpiW1pdUD9ag4pqW1FNUFdbTU9GS2qLfXZoWFhPTk9VV1FtazpBNltiZGRlZmdmYFFaXUtsPVBOSFs+c5Sbh0pHQEZIUkduUFBPT05OS0xIR01MTlBPTUxLS01LS01LS0dFR0hGRUVFQ0NERhlIRkpKSUlGR0lKR0hISUZIR0hFR0hJSEhIhEmASkpLSklGSEtKSEdHR0ZEQ0RGMjQzNzs5OCwyLyduQ0NBPDU7Ozo7PDxMRVdUrpiLpaKLqn+HhIaBfYzozYB3dXhygo7mzYd4eoKGh9/AeHp9gXK8pH2yVb7/1+KBn8CB4e2Eh3uB14qvgIDWm6yRNTM4OTg3QoeSqL5odX6FjJQ726TH6JJtWUsySElKSkxOYbHjjoDkybOjlo6Lj5KRj42NjYuJiouNj5GVmZ2hpaippvVGRUI/Pjk7PT2EPAk6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Pj0+PkKMhHxyZ7qljnpoWJqQgGl1enx8enNeYXBrKUhZP1RIazY/UjVkTW9dam5GOFFSREKDS2FJQUNER35gYVdOeFyIbWddY1dsW2RseGpmY2dpXGt1TGtoX3p5ampcY29qaXBTa5GRkF9kbWhkY2ZpX2BhfYyYlJJ5Z2tgYF1lZGBQh0tfgIGNj5COjV6IbmhvcGOGS2VgWltXgpeYiltVT1VYZlyGYmZgYWBgXF1ZWl5gYmRiYl5eX2BcYGNfXlxdXF9YWlpaW1hYWFxbYIRhgF9eYF5hX15bXVtbWlxfYF1eXWFgXmBiYWJgX11hYV9fYWJjYV1dYGJNU0pRVFZXYIiGS35WWV1SVVZVVVhZZFNTT4NoVVFfVl57WV1iZF1mTHhvX1lYW1ZeTXFpZFpdZmdNa2BXXmNmUFVPV19gb1ZedF53kFZhfGBiV0RiW4FcIkhpaYVxZmx0dHBnboSFmLFmdoaTn6k0KTxgnYuBenhlfn+EfhSRLB8dMy4pJiUjIiIjIiIhISAhIYUgJSEiIiMjJCQlO2pjZ2VjZGJtcXV4e3txZ2VeWIxNU1ZWVldYWVqEW4RcXFtbWlpaWVdTWmU3LzlecXRzbHBHOD6CcX2VnFaKWmFgYWBfXVtqPCQnMDckKVFUUFNnVGBqeYSNnqitWFpaXlhgRDJJcHF1WlZZWlyCal9HX1YkMTUxQWVhXl1diF6CXYRcA1taWo80GjMzMTBbVElBR1YtLjE2NzY9Q0JCQkNDREREhEUaREJDR0pMTk9PTVOQhPSjqKmpqKempaWlpKOEoQ+gn56dnJybm5qYmJiXl5eHloSXY5aWlJKQi4eB9L1nU05MTExLTExKScGVqKywsrm+wcLDwr+9urm4tra0srGwrq6sqqmpqainpqWioKCgn56dnZqWlJSQjIuKhoOC+PHs5N7X0czKyMnN0tqI7b2nkaZ7dXNycoRzSHRsjl+Rhn9zV3ytd6jL021vc3h8gIKDhIOAe3NoXFJPTExPU1h2m87c4efs8vmAhIXo1OHfq6edlIn97d7TxbmwqaKcl5ORj4aOiI+AjoyC2IyMjY6Oj5GTlpqfp46EhYaKjI6QkZSWmZqdn6KkpqObra+wsrO0tLSyhJO/qZiG7tS8qpmNg/nq5ODe3Nva2dna29ye4ePn6e3v8/f9gYWJkJqkssXe/LfR5ebf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dHBnboeMo2FvgZCfrFs3Nyc8X5mIgX6DcX+AgH9/gF4mIRwxLCgmJCMiIyIiIiGJICYhIiIjIyQlJkRhZmVkZWl1fpRgeGGKdGphYEmSUFRVVVZXWVpbW4VcWVtbW1pZWVhYVVZhLDUkYIxpY2RfYi4pO3WQlqBXVGVmazVKJywtLDcyQHFGNSNRX5GVW55eYWhwcoGQm5aQl4Z2UoY2O0FuclaOTENiVIJaRDwwOTRGZ2JfhF0BXoddCVxbXFtbW1pZWYc0iDMUMS9aUkVFTy0uLzM3Njc8QEFCQ0OFRBVFRURCQUVLTExOT0xRs9TUlZydoKKHoy+kpKSjo6Oko6OioaGgn6Cfnp+foaGipaWlpqaoq6ytrq6vr66sqKKdlYr79VdNTIRLJkxLSYeav8fP2OLo6enq5+Pd2NTS0M3KyMbEwsG/vb28vLu6ube1hLR6s7Oyr6yqpqGfnJaUkYyJgvzz6d/X0MzLys3O/4PPqpbvfnh0c3Jyc3N0dG2LWHyFVlJDf4uqdLuD0Z3Ka3B1en1/fnp1bmNXUE1LTFBTYJzGz7DO4OXr8ff42bSoxMTz7dzIs6GRhPXn2c3Ct66noZuXlJORkJCQkZGGkgaRkI+O0IeEjDWNjo+RlJidpJOWiYiMjo+RlJeZnZ6hpKapq5ewsbK0tri3trW12PW4oo/94Matm4yC8+jg3IXaMdvd4Nyn5efq7PDy9vn9gYSHjZWfrL3V9cLc6O3ix6/kwcTJ3c/uyquVh/vt5uLe2teE1oDT0cnS1tfZ293h5uru8/qBh42YobC+1eiAkanEmOqYlZaWlJJ+m6S/3InYsZHa9/+AgP749oCA//37+vn59IKemJGKgXbYwKuVgOTPZElQTkpJR0M4RnnFqqnpysum7ISXw4HpsfPF6IGS9eagnIT0vYyckIyMkviaraLtiGdgYIBkXVlcakhwdm56XVhZX1ZVY1hDOVVza2NbWE5aW1hXUUVjWFxbdltSVFNWW1JQOlN6fX9TVFZVUlJWWVZSP2U2OlNmZ2loaVpsP1FYX108W1NOPWemoaCak1ZAPUJESD1kTlNPUE9PS0xHRktNTU5NTUtKSUhGSEtHSURDRkhDR4BFRERDQkJDSE1KRUZHR0dJR0dHRkZGRERFRkZGRUdJSklIS0tKR0dGR0tKSEZHRUVFQkNFNi8wNTw8OS46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fG6ak3dvbGdrZVpiRl+UoWGVZmNjbGFda2lpXHuDXnBoYlpobWVkXmRXYWVWbWtjYWBpaWJkf4OOl5ZnZWiAaWppcnBniIxSaVyPlpaVlpNbildrbGx1X2SDiUKjlZaVjHxWUlxbYlmIXWdiZGRkYFxdX2BgYGRfYmJfX2JgYmJgYF1dXGBZXlxaV1ZYVlhdYmNgYGJiYWJjYWJdXVxbW1tfXl1fXWJhX2BiYl9fX1xhYF9fYGBiYV5bYFlRVFeAWlxYdI5cd3xHWmBaWl1bVllYXlBcWFd9dlRve4RKTEdFS1NXbFyXcm1cVlRaU1mCbmJhW15aYktmXFdfYWNTallVYGBim05dfoaTWF10YmNeR2BfjmVNa3BefGhtdHVyaXOPmVxwgpWoXGNqPT8+N0w5WZWIhoRVgoODgoGAky8THhsyLSgmIyIjIiIhISAgIB8fH4UgJCEiIiMjJCYxY2NkZGeFfJCKsZexgW9lXVxHkFBUVVVWWFlaWolbVFpaWVdXU1s8QDIxXV1FJi0lKSIzIyEjLTNhUkxQU1lbXmFpalORY1ZTNy0kQUxnV2R9kpePiH10cW5uc4CPb4xRLy08LS9AXGgpKkI6TmxjX15dXYpcg1uEWgVZWVlYVwIzNIYzajIyMC5aVUxIUDAxMDQ6OTc8P0FCQUFCQ0NDREREQT4+QklKSktKSlqCn/iPjoyMjpCSlZianqGjpKWmpqeqrKytrrCxsbKztbi6vL2/wcTIzNDY3+Hk5erv8vLz9vj7+/v48eXVwaigXE+ES4BMTU5NapPziJafpqutrKqmoJmRjIiFg4KA/fn08e/w8fHx8O7q5eLk6u3w8/X29vXv6OHa0s/IxLy1raWZkIT16N7W1NKIjdCi1Y55dHNzcnJyc3Rul4BqgXtZmKdYgHFdhGfGj3ebq5KHTFloamJWT0xJSk5RYZq6vcLGys7TuzTM4efu9oCGjL/MoM+I94bu0bummY2E+ejYy7+0raahnZmXlpWWl5iXmJiXlpSTkY/bg4qJhYiAiYuOkpegmLGPjo+SlZeanZ+jpqirr7Gbt7m7vL28ure1s5SrvKGJ6MOokoP06eLe3d7g6f6GjZOX3ZeWmpaWl5mbnZ+ipqemo6Okq7zVz+ve7+fGwc3Jx8rZ1q+Xh/j18O3v8vf38/Pz8O3s6+rr7uvr5erq5+vv9vyD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c2NkXUZgX4pnTWhuXoFnb3Z2cmp1lqFgd4mcsWFpcD5AQD80SHFXk4mHhWSDhISDgoBhJR4aLiknJCMjIyIhICAghh+EICMhIiIiIyUmNV9jY2OadHdrmWqVd2ljYFxSiExSVVZWWFhZWohbWlpaWVlYV1VWXzM7JWBETis8QTVebkoyN01alk1SU09TXGd5UFJ2cWZPjItpPi8xbFFSmFNSVJ2Hc2xsbnFoWz4xKzcuKTc2ZkcpIjtPbGFeXl1eXl1cW1tbXIZbC1pZWllZWFhYV1ZWgjOFMhAxMC5YT0VHVTAxNDk5OTo/hkFVQkJDQ0NBPz0/REdHSEZDS4SLiLCtj42Mi4qLjpKVmJygo6apqqytrrC0tri6vsLDxcrP09jc3d7h5env9v+EiIyOj5GRkpKTlZeXlpSRi4HmwPBmUoVMgE1NTmaOiqK/0N3h393a1dDAr6ejn56amZeTj4uIiIqJiYmGhIL37oOIjJCTk5OSj4uGgvz37+nh2tHHuquah/no3tvlm4Kxgc57dnR0c3JycnNtjXJtcWSDc3VeQXFhe5Z6lYeEmYqDf3w8OnZDSUlGSU1Reqy0uLu+wcXJztG+I8vg6O/6goeNk7/LoMuB6oLx2sSwn5CE8+DQxbqyrKain5uahpmAmJeVlJKPjM2KiIeFhYSEhIaIio6UnZa4kZCRk5aZnJ+ipaerr7Kisr2+vbybgKSztLX5jqSG3LaaiPru5+Lh9IiUnJuYlpSSzo6OjYuMjIyOkZSZn6WssrrDxsbJvvvV7uHMx83HxcjTvJGUm6GfmZOPiIH58+/t6ebk5OXn6u6A8/j/goWB+vTzgoqTnavA1vaKl5+Q9pCNkZCPjGeUn7TgqZHvvO/7gID/+Pb59vDD4r2V785JyoaPoJmSioF42sSqloXn0l9HUVBLSkdDN0Z5q5Wu5sXPpc7kj8SB8LXu4LqArq+sp6OLn/CwmObcbmFXV1lLd9WBa2RdXGBVUFCARUh4bLFgV1ZXWU9WXDU6aFdVTmBXS01aWlRSVl5QWV5LWk5OTVNcT083VIVMS1VOU1hXV11hWEk4OTlhZG5wcnNxa204U1lcez9EPEk9gaauqaeURUZKSUpKPklTUE9OUk9LSkpKS0tLR0pJSElJSElJR0lFRkdJRklGQUJAQECAQkZMRUBISUdJSElISEZGRURFRUNE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PodBCkJCQ0NCPzw9QkaER4BFRLKH7o2erLGpjIyMi4uPk5eboKaqrbC0tri7wMXKztLY3uLm6/P4/oKEhoeIiYyRmqGprrG2uLq3tLa8v7+9uLmwoozYm8ZYUU9OTk5PUFFf+Iqoztrh5dva19HVyby7vL7DwsTCv723sbW5tbazrqyno5mbpKuyubm4tLCqo4Cdl5SSjoiD//HjzLOchvTm34qd2ZSoe3Z0c3NzcnJyboRXgmdbdJVVRkpBZ3VykoKhp5eDf3x6PG1aWF9thJBMXZKprbG0tri7vcC+xtbEzeLp8fqBh4yUm8TOo86IgY+G7suumYj45tfLwrqzraijoJ2cm5uampqYlpWTkY7j/YCIhoWDgoGBgYKEh4qPlpK6lJOTlZicnqGlqaywtbidvsDBu5bRsr+8udzekfC9m4j87en8j52fnZuXkYyJhoG6+PPw7+/x9PqAhIqRmaOtusfU4Obdj83s0tHPysXBxc/is7m5tKyjl4+Igvns4dnSzsvKzc/T2uXv+oGFiY2RkoCOj5KdrMDd/4+b5tqaiI2OjYp1kpiszpWD4rHn+v7//vv08/LB0oGv/rX7zUjEgJCgmZGKgXfbw6uXhOnVY0lRUExLSEM3R3W937HnutSeyueWzYLotbeLyIOxr6ymnoWY7rTObGRfZFtYRUVxuNKQaWBaW1dOTTtAXWJ0UlpYVoBZUFFcUTtAUFWCZVlJQlBaU1FBOWpbWXlXUE1NUl5STEdMUWRgUk9QVVhWXFpaVEQ5N0pgcDo7dXVLOV1QVFhEd1E5NENqsbmyrFVIQ0dLS05BR1FPUE9RUUxLSkxOTUxJSktHR0hISkpISURGR0hHSUdDRUJAP0JJTkJDSElISYBISUhFRkdEQUBCQkREQ0VGR0ZGSEdISEVFSEVERURFQ0A/PzwtMzI5PDlEU1dDYzo+Qj88Pjw5Ozs9JygnJzpMhV1nUTkuLS8vMTIwMTKJi5KBt2txaHHozq93bnJrb/fQpXJzdntz0at+iIaHeqG0jI13cpjNlZKT/rii06yIuTitwLpNNjo8PD9Lnrp2j6CxXmZvdsXz9ffprs3q95RxW1JFT1BRUVJixvuM9NW8qJyXlZCMiIWDgYWAJIKDhIaKj4+MlJuli55EQz88Sz9AQD07Ojg2NTQ0VFUvMTIyMoYzgzKFMVMwMC4tKzIL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ZW9fZWBiZF9aaV5jZKNWg3ZngF5ZZ2lhYUlqeZWId3FnY2Fob2NfTW2LkYt9ZGdxbHB1dnFNTlV2a5tQUVJRl1tZZWVqYk5gWF1kaKqckousYFxZW11gUVlramRjZGVhYWBiYWBhXmFiX2FiYGFhX2JgY2dkYGBjX2NfXFxeY2NfXWFhYmNiYGJcXV1cXl1bWVpegF1gXmBfYWBdYF5eYGBfYWJgYF9gYGBMU1FUVlVaXnBYd4tbYllXWlhZXl1gXF1dXGhZk2JeenFHRkRISU5Sj1RPbktVWVxYW2qTgm9lW1ZaXFuTZ1tQW1lcUGRXVWZkYVVdaWJoa2RLcYxkW41bZI5pT2ZrUYFgcXh4dGt6p1tvXIecs2Rudn4+P0BBQkA7Kz9joJCLiluEhYSDgn9pKxwzLSgmJCMiISAfHz09PDs8Ozw8PT0+PyA+PyAiIyU4W2BfXV5cYWBfX15eXFpYWUmGTVJUVVZXWFlZWlpZhVpKWVlZWFZVVFw/SC1tTlR1cK1baXB8eGQqSVVJlUtMX5NfgF+CS3VPhHp2eolTeFI4NkwzOzU9QlNcgmiSVE58Vi9HJD9Obl9dW1uIXAdbW1paWllZhFgMV1ZWVVVVVFRUU1JRcy8uWVJKQURRLS0xNjg4PUYlGBkZKUlFQ0JCPzw6Oz9COSo/RkZHSElJSEZFqfq93PqZr7qZkZKVmZ+mr7e/x8/U2uLr8vuCh42RlZqgqbK9zNrg4uXl5ebh4fj59/Lj49fBpofw482/qZWA07GalZKTYFqFWIBZVlb2/oGB/vv7+/z8gIGDhoqMkJOVmJibnZ+ipKaqtdP+lKzB0uH1iJiwvsfNztfZ1uXr6+3n4NfHtJn2wJn/4/ii1MqDdnJqTlhub29uSl5/Vk5+ZWl8k6OHn8Nov2u00LZ+ezxzYFlbYGp1glOEmZyfoaSnqaqpl4TRuYeDrDiL8+Dk5uz0/IOKk5ymsN/ksPS3l4/vyKyYjIDy4tfLwrqzrqmloqCenZqZl5WTkI3n9YWDgP3594T2gPn9gYWMiK6Vjov7psb1p6ywtbm3osPGyMrJyMS0tb6/j/vp4oSfqayqpqCYkIf7693SypG7ure1s7OysrS2ur/Gz9ro/YyfveSB77zS2tHMxL+8wceLtLGjkoLu287FvrmzsK2rqKOjpaeutsDJ1OT0g4yWn6i0vsXJ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X15gXV9fXl5dXmBgYF5iV1FQVFVXV2SCYWJ0UV1iWFtaWlxhX0dhXGFKdJRYZIJQSEZERklMTHRyUGN7UldaV1ZaWoCCb2FaVlpeYJRoXFFaV1tRY1VQZWNhU1xnY2tsZUVeRIaKXI1cYZBnUGVsT4FccHh4dGx8VV90i6C4Z3F6gT4+P0BAQT44KDxcmY2Li22GhYSCf4VNOBswLCclIyIhIB8+PTw7hTomOzs8PT48PD4gIiMhhFxeXVtZXF5eXl1cW1lZWVKFjU9TVVVWV1iFWYRaSllYWVdWVVJZOT49OVtgc2OjZHJ+WI2VTjIxME2Qk1BaYWJfk31OiH11c3mLYzk2J0ElSnJPiWGTcFBVTXhTLiMlP01tX1taWltbhFyEW4JahFkFWFhYV1aEVQhUVFNSUlBOS4BXT0Y9RlQtLjE3ODg9QUREPTQrIyZFRUI/ODk+QDNFPUFCNjRHR0hKS0tKSUhz2bnX/Z+2upWWmaGqtsPR3+nw9/yEiY+ZoaqxucHK0+DzhJKYmpOG+tvPsZeJ9t3CpITKrKahnZmUi/3kzrmmkoDdh19dW1pbW1xdW1tjaWprbgNtbm+GboBwc3iEmLnihZ251vSKnK7C1+2CjpqhpKeqrLnok7HH4fKKpr/Q1+bx8+W0hsmW8daJiIjBenNvWnlfam9uTV5lVV9WbnOXqZupdaOnoZqEhLWMPTxwXlpdY2lzgmSNlJeZm52foaKbhOaB5PaAhYuO29eL+err8PqCiZGbpbG69oD5uY/Ai4Djya6ai4Dx39bNwry1r6qmop+dmpmXlZGOisqFg4D8+PXy8PDw8fP3/IGFgZ+RkP6MsoCmqq+zt7yfw8bJysvKycbCu76Js+KLnaerq6WckILw39LIwbu1gaunpKOhoaGgoqSmqa6zt77FzNXg6vO2tNDXzsnCvbi+wICz087IxL66trOuq6ilpKKhoJ+fn6Cjpqqvtb/L1uqElqi7zt7p6eHRwv+fgYeIh4TCjJiw5sO1md3v+fv6+O2dmryqj4qFhNWrqPO7RLvviZ6XkYmBeNjCrZiE59RnSFJPSklHQjdHboqhveiq0pi805Cqv/2uzeWZhrWPkW92fYBBVJznxmJZV1dcWFs6Okq7vG5eV1BRTkxOWEGCdGFZUlJTTkpOUl4/bVZZfF1QSk9XUElIPHuLfVlXUU1OT15V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WUuAlFBTVVZXV1iGWUhaWVlYWFdXVlVUXDElMTpQcFaucXeIeZSTjYpJLj9LSUqHY1xYVmmFf0QsOS02OTRNWFJVOiZyTnZTWk96UzIjJEBPbF1aWVqIW4JahlkTWFdXVlZVVVRTU1NSUlFPTZOHdndCQExYLC4zNTU5P0BAQUNGKzslHjpFPz88PDJGKTVAIThDNzowR0lKS0tNTUxLWLC22Iatxa6aoK290ej8hoyRl5mcpLDD0+b1gIaLjIuCgoHjtoz0yZ7puLGsqKGSgeC/oILOnNuRcGhmZWRkY2FfXl1dXFtbXIRdD15gYWNjZGVlZGRjY2NiYotjgGRkZWZnaGpufK6Ct+yQrMrngpKdoaOlt/qex+b/jpDpsYbby4axsIB3c3Bwbm9wb1FePklLYmJHTI96a4aWeHJ6hodjl+4/a1xcYGVqco12jY+Rk5aXmJiXh42qwd7l7PT8goeNk/bDtoLw8PiBiI+Zo6+7yIqKw6XTk4fryq+ZN4j87eHVzca+tK6po5+dmpeWko+N6vCEgf348u/t7e3u7/H09/v+yvqJlJaanaKnrbG3u7GwxcmEykXIxcG+vM3ajJmgnpWH9ePWzMS7trCspvCgnpybm5uamZmZmpqbnp+fn56cmZOHmana08zGvrm0ubbYgYySlpmbm5qZmJiEl4CYmJiZmpudnqGkqa+3wc/jg5/D5oCGhPHUrIOFh4aGcoWRrNevrZjj+f+B//rQsKajzaiPjIaC0rCl/cBHt+KFnpaPiIB32sOtl4Tn02hJUU9LSkdCN0ZshZu64afNlrXOi8eNrq7JgK3ejHlxYmFyclpKoLKUXVlXXF1XWE05Y4DXh2JWTk5MTU9GPUVseGJQTlFQSUZTRjxqa2l5Y1FKS1dSUE1URYeCcYJUSk1KV1hNUE18cXBmbE5VW1lkZmZfRTc1X24/QEFBQWY7P1BPUU1CUkpSP2d5cF9+R0lCRUxNPkA+Mi9iYV8uLS1bT1QtLSssMDU4Njk7Nzg7P0ZFR4BJRkFFRERERUREPUVHR0hJR0ZFQ0JEQUFBQkFERUdISkVFQ0NEQ0JAP0FCPz9BQD9BRCwwNDk6OzspK1FlcTw/Ozs8PT5CQz4mJigmLz5+VURITC0rKy0wLz4yM1RaPDU7MjQ6OjdZr4SApGhxd3zxxJlxdXR2cb2UdIqKh4CsrXOR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ent7hHyEfQF+hX+EgBuCgYKDhpVXZTlASE8rLS4vMz1CPz0/PUKopaKFoCGef11+romoe2hna2+xgY2mZ2h6iJBtbpKah4aHjpOaZo+ElUeUlJOTkIlkNEFCQ0RFRkdIJSYmJ0hEN3pivL5gYmRnam1wdHhOPUtMKC0oST01LihJQz05NTIwLiwrKiopKCcnJiY7JSRIR4hGgEdBQEElRTVISElKS0xNT1FSU0VWV1laXF5gY2dpa3AiJy0xLSsqKCgnKCgoKSkqJ0MpUU9OTElHRUNBPjw5NzU0MjEwL11aV09UTEdCQURERUNCQUA/Pj4+QURJT1MsLjI2Oj9FSUwnKCkpKiorLC0vMTM3OyEnFxkmPYGBhISFgH6ViIqUVzspPCAhISA9JSxLNlFDMiw7KU5FRTJPSVKFbmyNh350al+pk39uXaKYjG17f4GBf3dgZGNPRlJdS1hDWGJDOU9MSl9PXlB6b3RtYWl6XmZoXGttZ2JiaGNkV19xmrWIa2JZWVtXX39ifnZgZVteY11VYmJ/bWtyXYFrgFtXZWpjXlJkaI58kmxjZGBjbWRlTZyMjItZZWpzbnl/eHNyTVNZZGdYlHZXZZZIYYhWVmRkXmRWbnp/doNVU0tJUFFJR0eEgHB8cHB+f4hXinyAQouOgJlxUHx0g2pte4CMRqCamlZJU1VZW1tgZGJjZWNiYWBhX19fXl9cYF9dgF1dWlxfX2BcXF9gX15dYF9bX2BaTVJTV1lXS2J2jHRPWV1XWltaX2RfSmFeYUZqkVphd1hJRYVIS05RT3BSYUVWXFVRVVZXinVueFFQX45cYJRsWVFYWltTZVOcYmFeVVZdZGhqaFBnb2WCUVNcjWRRXm6Tf1JweHh0bIBaYniRLKdfa3V9hD49PT4+P0A+OjJGaKKPjItVhIODgX9/OzQdMisnJCMhHz48Ozo4hzcmODg5ODY4Ojw/ISNLWlxaWFhZXFtbWllZV1dXVYqGTVFTVVZXWFiIWYRYP1dVVFdmKCNHXX2JanqEYYCGiYuHhU8yMkBHdlVnPCQ8JywtJzQxUE1XZJeRf2Y8LjlCV31VPiYkPExsXVpYWYpahFkXWFhYV1dWVVVUVFRTUlFRUE1Lj4FuXmdoUVotLjM0NDo/P0BAQUJDREhIR0RMKEczSzZHNTgkJSEwPTcsQS8/SktMTU5OTkxS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R0hKS0xOT1FSR1JWV1lbXV9iZWhrbD4mJSEjJyYlJEdGRENBPz1fdIQ7Uzo6OTk5ODg2NjU0MzIwMF5cWVJTTjhfVlVbPEBDQkA/Pjw7OnFwb25sa2tsb3eur16ocnI+RE0sMjg/RicrLS8xMzYdK459goODhK+FipFTNyY8hCKAOCwrOmJILEoxKz0oJ0RGM0pJUX5tbY2GfnVpX6qTf25dpJySb3yAgYB+d2BjXplFT1pNVkJcN2tOUk5CjEl8hFhsbXNlYWt7RV2WdrhpZWNla2Nlg1yMvotsZV9XWlxVV2BPbnNoXFtgYlZYZVRjZWhncXRiWWBraWJhameHfG+AbWdgYGBwZ19ZVYCJhZJnanVsd3x6doJLj21+homOj46Pj29LTW5MSkhISUZfbHZ2ekZ7cWxzd0FCgIWJcXZlZW18fYFKgYB/gWPApHdiYGtHdH14fUNGhniAh5BDQoSGRUVHR0xVV1teYGJhYmJjYmBgYV5fW1pbXF5gXl9fYF99XVtgXV1eYU9RUVVXWFpoU4xrgVdaWlpcW11jZmhgXWFZUqFPYEt/Q0iBRkhPTlh1bmFzUldZTVNTU3SUa2tHTlRZUlySkmxaT1lbW1NgUptiYV1TWFxkaGhoVmdvZ2V7jlyOYlJca5GAmnF5eHRsgFplepKpYGx2f4Y+PDyFPUs8Oi5AYpqNioxlg4KCgH2MMh8aLSgkJCEgPjs6OTc2NTQ0NDU1Njc4NzU2ODo8PkEiMVlbWVZXWltaWVlZWFVWWE2Bjk9SU1VWV1iKWT9XV1dWVVRaPzVDMFdhin6bhIqJhIWIhEI6PzBDPjw4JzlGaIl9d3N3go5QlG6UdFtOWkhcXCUmIjhLa1xZWFmIWoVZhFgFV1dWVVSEUw1SUlBOTJOIeWdecIlHLC0wMzQ3Oz0/P0BBQUJDQ0I/Oz5FPSwsRTVcQEhANiMoIzIoOi4rMzNKTk5OhE8pUuOt47DcxKm+5Y6nvM/j7/uB7dvQzbSN7LaAu7StoYnfrf2jq3BpZGGEYAFehF0WXFxcW1lWU09NSUZCPzw4bmNdWFVSUoRTgFRVVlhaXF1dXVxbW1pZWVpaWltdYGNpbXF2fUJFSUxQVVpfYWNkZWZoa2xtbm9wcHFzdXusk9mRtdr+i4/Pjn96d3NxcXFwWj9AeWRgYGVhmFx2g5JYX4myanJtoJZe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OSlMQEgzTktRe25tjYZ/dWlfqZWAbV+nnJhve4GBgX93gGBjWZVGTFdSVEM4Vj1PRlp3dFNmbIuMaGxuYWd1cGZchISBaWZmaWdiXV9hh7dUaWFZWlxZW39aeHBiX1hgZlpUZWN0d1dnWH1lU1tmaWRgSIR+lJdfbGFkXWxoYWF4VoN8ZWhncW91enxzXUeHcGp+gIWHh4RVYl1ShYaCg4hGgEZnz3Vyeop9eHBwdEGCf3t1bnx5bnp/eoZMgoB8X2RqeWqafWl9ent5eIGJe3t9XWF2eD9zdG1xdoV/h3ukVIlGTltmYmJhYF1aWVxcW1xdXV5gX11eXl9eXF9fRVFTWltbU1SPV3BKV11aXmFdYWZiTGBfX35gj1tceWJHRUVFUU9QW21fb2JOVVtPUlRSfm+AXXhPUVlTUlNXdGtcT1pbVE9hU5deXFpTV1tiZ2ZmWWZsZWSNdYOOYVJaa5KAlXJ6eXRsgFtle5OsYWx4gIg+PIQ7Uzw8Ozs4KTtak4qKkXiCgoB/fGMpGi4oJSMiHz06ODc1NDIxMjIzNDQ1NjY0NTY4OTw/Qj6IWFlXVlZbWVhZWFhXVFVXSYGUT1JUVlZXWFlYWVlZhlhJV1dWVVNZSz9AR1JgI0AxMkM8Ojg5Pz1KUTEsMUWLWm19cGxqaHN+hlZZXotSjW5VODIlOjRIaFxZWFhYWVlaWlpZWVhZWVhYWIRXF1ZVVVRTU1JRUVFPTkuPhHRhYXeMSExWBDQ0OD2EP3lAQUJCQUA7ODs/NywjISEeMDNYTjpPOCcyQSUiPyMsLFBMT09PPzxPUlK1qPjV5Ky76ZrB6fLn28mg/ciZ1LGsoYjWn9bseWlnZmVjYmFfXVxcW1lXUEpIQz46bGRbV1VVVllaWVhYWVpaWFZUU1JRUVJUVVdaXV9hiGWAY2JjZGJiY2JgX2BfX15eXVxbWlpbXWFpcn6JSlBYXmRmaWtsbG1ub3Bxc3V3fribwH97eXZzcXFxX2hkZmptXUVOmpWRf3G/cKFwW6GTg29GWFphZ3FJdYSGiIiJi4yLhIGLtNDT1tnb3uHj5+nr7vT8goaJ7PuWlsuakoaCho5kmKKwwdTngOfCh7mKgu3RuKWTh/ro18i/ta+mnpiTj4yKh8eFhYaPmqGhhf2Gl5ianJ6ipaqR+5qXmp6jqK2yuLqhxMbIycvLycfEwb7D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SEdHRkVGSYB0ZnRogod+dXJxeICCfnt5kHRkiHJ6Qn9RhnxyXHeFZmRMhYJ7d3l1eIKFf3h8l3Z5eXdza21vdIN7dGF4dIaBQkN/foRDipeTTlVaW11bXV1fYF1eYF9dXWFUS1JYXVldgVmGbXpXXV1bYmFhZWZrX11iUklRU2GPhkVIRUVIUVBPg3yFYIRVW1RUWlZYd1hdZU5RWFhMTVN7eqJWglhcWk9jU5JdW1lTV1tiZ2ZnWGdtY2KFfGVgYlBZaJh+jHJ6eXVtglxnfJSsYm55gYo9O4U6Hzs7Ozk1Smylj4mLo4GCgYB+fkkyMCsmIiIfOzg0MzGFMCgxMjQ0Njg2Njc5Ojw8P0I2ZVhYVlVVWVhYV1dWVVRWUoiDTFBSVFVWhFiEWYJYhFdAVlVTVFsqP0BEWEI7WDxPjomAhoyPYVdQWik5MGhwf29lYWFugFN5g2lTc0wpOyQuMEVjW1lYWFlZWFlZWVpZWYRYhFcaVlZVVVRUUlJSUFBPTZOKfW1eZn6KjU9YWVyAOj0+Pz9AQEBBQkI8Nzo+Pi9CODlBJB0sQzxIO1pgTycyQTI1JzAwISo8SVFPSTRGVVRWi6KN372ozIGdn4PYn9Crp5fzuoGVjW1pZWNiYWFgYF9dWlNMRT5wZVpUVFVXV1hWV1laXV5gY2ZpbG1tbGtpYV1ZV1ZVV1lZV19ocXWAeHx/gYSEgX+DgX56d3d3dXRzcXN3d3VycG5ub25ubGtpZ2ViYWBiYmJqdoSRT1ZeZGhoaWprbW5ydnd3d3Z0cXFmal9eXV1fW0lRoWN2iqKjjXhPe3lyaVlTWGFocFZ+g4WHiImJiod9g47W2dvc3d7f4eTn6evt7u3r6/iDhuNr+pGVltKklIiEipOgrsDU6/+XjdWv45mQ+9CwmYr86NrMwrasopuVkI2KieWBnqmnk/74jY6Mi4yNj5KWm6Gnh9KZl5qeo6ius7edwMPGyMnKysfFwr+9uLKtqaqxusHL1d7n7vX7x4aIioyGjYCMi4qJiIeFgoD79vDk2NjNs5eLjp2go6GblZGMhoL89e7m3tfRy8amxFYsTk1w/6GdmpaUkpGPjYuMiouNj5CLhIaEg3uOiaC7h4WG0Obx7ZClw6KMtZaci5ubg/X/8KyzpPPBRaLihJuUjoZ+ddjCrJaC5tRySlFPTElGQTREyYDYvazR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kJGOiomPkpGGhI6Qjo6RieDwhImMj5CSk4SUBJWWlZWGlGCSj5OshduOgsH0urbd6NrU1ufYgOz3iaGsoNbumevMx/WOrZb2rui2gODm4YucrJyZmpmam5uamZmamZmYl5eWlZaVlJSTkpKRkY+OjYyJh4P97NG1o8Xv+YGQlpejs7ZGbG5vcHFxcnJrYF9rdGpSem9vbWlqeXZ4bn1knIRxjIqFUGV1T0t0YX9zcG+KioiJiYmHhqUtHiAcHRw1Li0rSz5knH99foV/gH5+e3NqYFecjYODg4SEhIWGiIiJiYiIiomFhYaHio6QmVloPEJGRp+Ff3p4dnZ0b3kpMDI0NTg6Ozs7Ojk5ODg5ODY0Mi8tKk1EPjVaSoF6dnV0c3R3eHx+f31/gH+Af4F/fn5+gY6jtGRveoGFiZCZn6KioZiOcG9vcXJzcXV8JKqZrLuUka3ue5aikoR/e4aQoH6YmZeWlpaUk5GOSEFGRkVERIhDQERFRUVERkglJkFIJygpKT5AWVdZW15hZGhsODhGO0srJychNywjKiIjKzEvLSopKCcmJigrIyskRSQmJUhHRUSEQyxERiQlJjlMRkZISUpMTlBMSlVWV1hbXWBjZmltc3h8f39/fXVqcHl6enk8Y4U9Nzw7Ozs6OTg4NzY3ODs8PkFGQEJJTU1LSUdFREVYQjkwU0hAPHJubGpoZmZmaWhmZWxnZWZjYF+EXoBfYGJkaW9zdnp9fn6AgKSAg4eNTmNFJywrMEo2MjYtUFYvL1AwVGxTSUBIMj5KUnlsbYyGfnRpX6iVgW1fo5qZb3yBgoKAeF9kYJJTS1NQMkuIjk6PeW9xcW1lbF1rZ25qbV1caHGEV1eOrGhqbm5nYGNpWWe0YmhhW1tcWFeEV4B+eWFfWmFkWFVqZXFOXWaWe2ljYGppZWN6l42Qg29nZF5gbV5aSop0j5FWfkJDhHZpYVBYSm1tZXyAg4aHdk9TSEtMTUiKR0iEsMlnbn1/enNven+HfXVgrrCQb3NDRHlZRH5yen6vZ5p5hn93dHdweoePfoJ2Tnh6P3ZybWxyfoB7dHeSTkZDREZFSEtLRG9or3R/hEhLUFZaX15dXl9eXl9fSVBUXF9cVlpeW3RIVlxbX2NgZWllSlxdX3dcmF9bemxKRkVHUFB+bIFjblVYXFVbXFZmYG1fRVBXXE5JT0dpZnBNSFtKV1JkUopaWVhRWFpjaWZrWGJqYmGKilaBW22jWGRRe4Zyenl1boVdaH2Vr2NveoKJPDs6OTk4ODk4OTk3MEJkm4uJi2KDgoF/fFEfGi8pIx87NjMyMTAvLzEyNDU3Nzc5Ozo7PT8gISMkJCQvWllXVVRXWFZQT1VVU1RWTH+JTVBSVFVXV1hYhFk/WFhYV1dWVlZVU1VdKjwmKV56Z2l/i397foeAT5ebVGNcNDxDK3ZsboxXbFqNZIBXJD0/QCs+XllXV1dYWFhZhFglWVlYWFhXWFdXVlZVVFNTU1JRUE9OS5GHeWZdboSJSFFXWWBqa1U+Pz9AQUFCQj87Oz5BPjRMPT8+NzY5Li5ANTBjbXaAREAlLzMmJDk0OjM6Q1JSU1VVVlVV45yTwI+Pidino5TrruTrcWdjY2NiYWJgXVtUTUU+cGJah1mAXF5hYmZsbXZ+g4SNmKa76rH3nr7b5/ZyZWBdW1xeX3WApaqwt8LM0dPU1NDMxcC9urWvqKCVh/XYuZXinM+qm5aLh4aBfoF9enp2dXJxbWxpZGFfX2x5iEtUXWNmZ2pucnNycGhrW1tZWlpcWV9liHyMn3htiLtcam5iWllXX2mAeWV+gIGEhoiJioSHs9Lp5ubn5ufn6Orr7Ozt7/Dx8Ojr+YGD2vaMj5GQyZuXjImQnay/1O2DkL2t6I2Xn4jZr4ykhoywzcG0p5+Xk4+NlaaBo4b3gYiD/vfx7evr7PD1+oCDg7eolpaZnaKorrKsqb/CxMbIyMfFw8C+ubSxsbSAvMbMzNfn7/f/gtKLjY+Pjo2Mi4uKioqJiY2Tm6a0wdPp4dqvy8O8tK2opqz01K6L37iciv7x59zUzcbCv6uPfYWOl6WfmpiWk5GPjYuLioiKi4qIh4WDg4KnhpCkvoX05JWnqsT/uqyZiPGwk4uglP3s9uOst6fTw0Sj5YSZlI6AhX1118KsloTp0nJJUU5MSUZANENk19epys6BlNjxjZh1aGpsaFtQRpCiamFjWFdkaWA6X83bW1peYFpRUkY6b9NpWU5JSEpHRFo1dXlfTkdMTkZDV0xBNllikWlXUExZV09MT4KIi31bUlBNUFpKRzVCNVFTMj4fIDw1MC0kKCaAOjUzPz5AQ0U/LyklJicoIkAiJEFMVy4yQD07OTc7Ozw5Ni1cT0gxNCAgOigdODU1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tYBtbm9vcGxiYGdxdXV2aZ5+d3h1b2trkl5Mi4eLk1JxdopbUk9YV1p9NlWJdnGMjIyLi4uIhaAqLywqTEBpo4B6eXl6e3t8fHpzZ1yjjn97e3p8fH5/gICBgIF/foCEi5ZgQVIxOD5CIiQmJygpJyckIiBDiHl0cG5ubGp6Ljo6Ogw8PkBBQkNERERFRSOHJIIlhCZaJSNDPzs2MStLPzJKgnt5eHl/g4KDhIWEhISDgoGBiJirv2t6jJmWmGhoaGlsbm9zi429zXyCe+PIwviLh4SFgoOOXJKcmX1nmJOUlI9fOyZMSklIR0ZFREREhUNARERFRkZHSEklPkgmJicnJzo8V1ZYWl1hZGk2ODpUQSs0JDw8N0YvRy48PiwrKiknKCwtKTxBJiZJRkVCQD49PIc7gDo4SUdGR0hJS01OT0RSVVZXWVteYWRoa3B1eHt8fHx7eXBlbnU6bjxJTlQtLzI0Nzk7Pj9BQ0VFIyMlJigqLCpLTkxJSEZFREY4PTQuKiYkRD88ODYyLywqTkdAOTJTQmthXVpZWVtdX2FkaG1ydXh7fX5+f2WBgYSHjJVSXTQ0gC1ETkxRPUpPYy8wTzFUblJKP0dGLkxOdXBujIZ9dGpfqZaBbl6lnJpwfYGCg4F4XmRfjlZKUkhYX4hvgHKEc2tvaWhVYF6ToGhqZlNfbmhgVqiLcWxpbWtiYFFXiKSocGVdWVteVVhik2p5YlpfYVpVYG9NZnZiYZNwZGBpbWlmgFBtY26tbGZgWlNSQ3mAV35ZhZ1vfkJCfVRYZndnUXddcH2ChoaEV2tHTE9RSodIR0hbZ2fMdn57cnN5fUJ/bamunGVtfoVDbFxCd258golsY32FfnZ2c3B8i4GAhrtle318fG9vbXWCf2i4e3ZERklIR0tNSXe2qn5saURDQn9GgEdETEpVXmBgX1dVUlZaXF5JVU9rd1daXF5hYGJmaGpcWF9ThZhSYYlMSElGRE5QUZJrSWFMWF9VWFpXTmRqXXVRVlxRR0pLamBchkpFSlFnk2VTiVhZV1JUVmFpZ2pYYmpgYZCVWmxUrFZjX4GAcnp5dW6IX2p+la9jb3qDiTs6Azk4OIU3Hjg3NSs7XJKKiop1g4KAfXw6MTMqIzw4NDIxMTM1NoY3Kzg5Ojs9PkAgIiMlJygqLFhZV1RTV1VVXoZVVFJUVkl6jk1RUlNVV1hYWVmGWDZXV1ZWVlVTVFpMMCwmWYBNU2Jze3+EcJWMnFN4WVaDSDA0JUxdh1x3TC0rMygwSW5aV1ZWV1eIWCZXV1hYV1dXVlZWVVRTU1NSUVBPTUqNgnRiY3eJikpVV1pjaWprbC0/QEFBQkA8Oz1DRERDQG9WS1FPRTs1RDAqUlNTVTtlPkUrKSYqKSxBGihBPEGEVRJWVlVTvYqsn5LxtfL9a11bW1uEXIBbVU1FemlbVlhaXFxhZGVlaW1xdnyFl6zu2rT6ocXk/YeOlJqdnZqWko6HxJh2a2VhYWRrl5rR0tTZ4uzx9/v+///+/oGDhIWGh4eHiYuMjo+Pi4T25dO/qJP0vIGRup6OiISFhH96d3NtaWZhXVtaYHB+j1BbZmxodFVVVlZWV4BXXI+QsOCHiHjQsaO8ZF5bXFtdZUV0f35mV5KFiIiApbWA//77+fj4+PX08vDw7/Dw8fLz9Pf6/P6BzvOFhYaGhbeNnZGQm6u+1u+Glqb4zpDKlvnmsc6IzpDX8bCsopuVl6y1n9jmjIf/8ubc1M3IxsbGx8fHxcG7sNeWk5abn4Clqq6ym7q/wcPFxcXEw8G/vLm4usHL1N3i3tfr/IT6l8DZ8IONlqCpsbrH0drk7/uDi5OerrbEtL7Iv7iwqqakscjz0LKcjIDu3tDCtqqglIj03cCjh9WVy6WalJGOjo+OjYyLiouKiIeFg4OEaYeMmKnH+KHVi5qNv9vM+7vr4YDUj4mbkvXj79ypzeGHx0Ge24OalY+GfXTVwquWhOnVb0hRT0tJRkA0RGXL3qbGy7Ofp3G2qoVwZmpnZE1JSdvcYGFhSVdjWkI518NxXFhcW1JQQDRsvsZmUktISk1FRjdqaXpeR0pKRkNNWjdAbGBfgVxSTldbV1Q8RV5jl15RTIBFPDUmPjoqMyhKVTU6Hh43Ji4xOzUnPSw7P0BDRUc0OSMmKSkkPyIiJCcuLlo4PDs4OTw9HzsxUVFOODU8Px85Kx44NDY9OSouNjg4NTQyMDU4Li0wSy4yNDQ1MTEvMzY5K1Q+PCEhIiAfIiIgM0tHPTkvHBwcOSMpJCgpNDw/Q3hEPS8yNzs9Pi0qJ1loPTs8PUA/QUZHPCUkJSJRfERXWy0rLSopLS8vVjs2Tjc2OTU2NzUrPUxGXTU1ODApLCw/UVB2LSouM2G3tZLSe3t5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QEBBQkNEhEUGIiIiIyMjhCSDJYYmYSUlIz83LyhBNleRi4aFh4uOj5GSkpGSkI+OlKaQYmJjZWZpa2xwqKXfh5qdnJmb432BgYiLiY5toaSin5l4jpaTk1kqLCspJyYlJSQjIkVERENDQ0REREVFRUZGR0hJO0eESmNLSzZwV1ZYWl5hZTQ3OT8yRjdLKkM0fsbAgkFCPCopLC8oJSYnPCVHREJAPjw7OTk4NzY1NTIrRjZRRUVHSElKS01PRU9TVVdYWl1gYmVpbG9vc0BNLDA1ODs7NDw1REVFQkCFRCZFRUZFRkZGIyQlJigjMixMTUtJR0VEQ0ZBPTQuKSYkRkNCQEA/PoQ9gD4+P0BAQD05NTEtKUg9MkVtbG1vdXx9fn92h4KEhoqOSk5TR1NFRE9UUTVLUGMvMU8vUWhUSEBPNjJKTXJubo2GfnVqX6mUf25eppybcH2Bg4OBeV9kYo5TSEJwkYJgZmNgZn9waW9qZoVZbVmFZ2xmWmNqTlZpg2doZ2RlZ19hgHZXmY5faWBbW2BcWERaf3lkZl9iYFZXbGRrbV1ieXVnYmBubGpkS4mSoo5NQnt4d3d1kIeUdJKphnqBQnlcS2p4YEhrZWV3fYOHh2pRc01QUktFR0ZLYmFqbGiCfXV1eHxEQ2qTqlyrdH1EQ2pfQXNSY7B2cmmAgnt2eHZ3fot7gICDj3hAQUB5cmtzg310do9URURHSkhMTk2EZaRualJHREhLSYJbWGuSZXZbglOCYUtZXl1eaV5YcUVYW19gZWJjamVJWlxcbVaTXVR+d0tGRUlRUG92iV96WVtZU1lXW5ZjXGRSVlxWSktNdWJXb1ZISFFSUl9iU4dYW1dUU1RfK2tpalhgamJgkJ1ZbmuHk2V9i3pxenl1bodgan+XsGRveoSKOjo6ODc2NjaFNYAyS2uoj4uInoODgoB9jDQ0KSE5NDIzNjg3NzY1NDQ0NTU2Njc5Ojw/QCEjJSgqLS1HWFdUUlJXVU9UVFRTU1RTjHmQTlFSVFZXV1dYWFhZWVhYV1dWVlZVVVNTWEA6SSUuTHZUb5dQWVSGfIWNanNek5JoSkpDg0xVSS4pM1A8XgtXVVVWVldXWFhYWYRYJ1dWVldWV1ZVVVRUVFNSUVFQTkuRiXtrX2l/i0dPWFhcZWhoamtra4BAQUE9PD5DREVFRkZFRkU6WFVWVEo7QEdPSkRJQzVMJz1APjw5LCk2QytHQ0c4VFVVVVZVU1OWgaePWlZVVVVWVVRTTUQ+bF5XWFlbXmJjZmxyeoWWqNLMypvQgpivwM3RzMzP09LPxr+3rqijnZiSjYaegKOIfG9scYHPh83P0UDV2+Hl6ezv8PHy9fj6/f7/gIGChIWGh4iJiouLjI2Pj5CQkI+NhOzNrY3cncvvtJyRgnpzbmtnZGJgXl1cX2xnhVGAU1VXXNTI9a7KxLOnp+hnZVtfY2NoUn+DgoF+ZX6GhZD8kpyXkY6Mi4qIhIL++vj29fTz8/P09PT29vj5+sPu+vn5+Pj2pP+kl5qpvtbyiJmqw6DlrfqtwYzM+/fsp9XhpJ+wv52MjpTPgvTn2c7Fv7u3s7KxsK+tooTJj7yOkJOAmJyhpqqun7G6vb/BwsPEw8LAvby4xITVj6a/ztfXsdOy7u7r3tPg397e3+Hl6ezv9f6EjJagrpfnwczBu7Osp6KhsOfvza+bi4H27ujj39zZ19XV19nc3+Lj4dfJvKyaivLEi5OmlYqDhIODgoN7j42YpLbXgZSql8i1tNfT3IqAzN3Th4Sajeze8NTB3p2Xu0Ca24KZlI2FfHPUwayVhejXcEdQT0xJRkA0QmXI3aGduZ16XFhTlop9b2dqZWF1RGSMoWFnYFJdYEI4T6+EW1dVVVdOT0wzpKhoVk1IR0pJRy82dXpjS0pPTUVGWk5BV1tgdGBVUVBdXFdPKDtRXmCALCA3NjY1NDk6RDhBVkc6PB85MiMwOS8mMy4yPkJGSEg7LTkmJyklISMkJy8sMDEuPDk5Ojw+IB8vQUsvTTc8IB8zKx02KjFLMC4vODc3NDMyNDc5Li8xRDIaGxozMS0wNjMvMUAqISEiIiAiIyI7MEU7OSoeGh0fHjctKjBYREOANE81TzwxOz89PjYuOmQ6Ozs9PkJAQ0lGIyQkJTRCfE9FRUYtKyosLy84Q19NXTg4ODQ2NjdSPktONzU4MikrLUFKQ1g6KywyMTFOr47HeX14eZGJjJuZmpmkpJaT4v+OqJw7aph7pkg2PD09PlBqepq3zHF4gYiPo8nJyMXFyMpOysrIx8K07vz4iGlbXUtMTE1OocD0uIfXvbC+1d/c19PQzczKycnMzdDX3+j0/IOLlaCsuLnnPTc0MjEyMS0wMTEvLzAvVElVLjAyMzQ1hDY0NTQ0MzIxMTAwLy4tLCwvGQoFAw0vTzlMazo+OWFaXmNARDldXkAVESVNKxgMBgUJFhcwMIUuhC+KMAYxMTAwLy+EMBgvLy8tV1JLQztCT1YsMDM1Njo7PDw9Pz+Qf4h+h30Bfol9hH6KfwKBgI1/kX4Ef4CBgZeCAoF/iH6UgZaChIEDgIB/nn4BfYZ+AX2Hfot/AYGLg5qCiICEgQ6CgoOCgYB/fHt7fH6AgoiDAoKDkIKCgZqAgoGagoiDiYABgoaDl4IEgYGBgIp/hn6EfwV+f39/foR/EX2AgH2Af31/f39+gIB/f319h4CFfwN+foGIgwaCgX1+f3+EfQR8fH5+hn0EfHx8foh9BHx8fn6HfQV8fH1+foh9BHx+f3+IfQV8fX9/foh9B3x7fX18fX2HfAp7e3p6e3x8fXx7hHyLewF8iH2LfAN9fXyEewt8fH19e3x9fHx7e498BXt8fX19iHwCe3yIfQZ8fHt7e3yFfQx8e3x8enp7fHx9fHuFfYR8in2Efgd9fHt8fHt8hn0Ffn99fHyHfQR+fnx8h32EfIp9hH6CfYR+C319fn5+fXx+fn6AhH4BgIWDAYKFf4WAjoECgH+FfoZ/BIKDg4OXgoeDAYGPf4N+mX8NgIGCg4F/fn59fH19fYR8En19fXx8fX+Af35/gIGCgoGAgKN/iH6MfwICBABbqJyeq7m6uL/Av77AwsbHyMKa4tPOw8a4kYaMif2ctY3Ok+TX96nOhYqq0s3LyIjY4N7c2tjX1s/DuLOyr6ycivHWurW4ubq3t7i4tq+usLo3QykvGx0gIiIjI4UiHiAfHx4eHh0bGhkZGRgXFhUUEiBiXra9w7+6vF87IYQihiOCJIcljBKCE4USSxMSEhEREB4dHB0eHx8gPDnUw9rc2t7f3dvXodjy9fn8/4KFiIzEzqW6Y7uwqqmOjKvBz9bgsN3e3dva19PPyHMSERAPEBEQEA8PD4UOgxyEGwEahhkEGBQWFoQVJRQVFEZCQUFCQkIhICAkFA4LEwsODQ8QERQNBgYECAYGBgcHDhCFETwSEhITExIhHTIuW1otLSwrKykpKCckIiAcIB8fHh0cHBsaGhkZGhcJBw0LCgkJCQoTERUYGBcNEAwRFhiFGgIZGIULDwoLDDc6Ozo6OTk5OhUNDIQLBxgYGBcYGBeHGAEXhBiADAsMDAwNDQ8SFCNUoKeoqajeq6uqq6tUVFNEiD1JRk9kUDlSUItaLHcrSZZNMlxPMCtWk8uhnMi/saOVhOrPt5qD6eTgpLnAxcfGv5qakflVgNzj7eval7a+o4DTubW7tqrKqPh6vrfEs6+2s9udhp3es6yyt7CrnKK0n8bWwrSAsLKzqKiz/5ejj6yyt6abrcGHx8yL+NrGw7HDu6iYzeTf7Yj5iPLl2OH43uPTzJzchKvw96OIlMrV5siWupOgn6israSKhJGUmY2Hg4uMxfL7g/fo6eHd6oGGhNOKn+SF0/WHgKu++KKvgpyhlbSAg+vj5uPp88KiqJ6O+omPhuiA1Nnu25WO8b/rhouNioySmomIy42j3PuEi5WTjoi6qo62wYe2y/iD9MWtsLWSh5a3z6Svsa21s7S8ysmnpq6O1/2Np+2akZKGjqapuoLkvLCerrakqKqpz5DFq4iktraknaeNxZarnZqUnaamqOrS+86KlJCGeYGXp6SggZOroZiA3H2Nqavt/J2DxrWxvLqxpcmRmbTQ8YuerrvDYERQNSAlJCQjIyQlJicmPXHX09LRlsrIxcK6YRsjOzEtLS0qJyUjIiEfHh0cGxsaGhobGhsaGhgLCgkIBgUnjY+LiouSj46Ojo2Li5CK8dP4hYiMj5GSkpOUlJOUlJWWlZSTlJMokY+Pj5S+5tXlrpCM4cCgh+nGu7/C1PSOp9aVzqiV1oburfWbtZiXlYSWDJeZmpmXmJiXlpWVlYSUIpOTkpGQjo2MiomGgPfo0LCtzvX+hJGYmJ+rrKytrq+wsLEyZl1faG9wb3V2dHR1dXZ3eHVdioJ9cnJuVE5RUI9Xa1F4Vo6Ek2J4TFFieHRycE+GjYuEiiuIg314dXRzcWZZnId2dHV0dXV3eHl3dXR7hzFLM0MoLjQ4OTk7PDs6OTg4hDcvNjUzMTAuLCspKCYjITRpT5GMi4mJilFRNzk6Ojs8PT4/P0BAQUJDRERFREUiIiKHI4UkhyNNIiEgPz07OjczMC9NPKOToqCgoJ+enZlzpLq8wcTHZWZnaa6/nKZbtbS1uJeMiYSOkpuDpqenpqakop6XcCs3NTIvLSsqKCYlJCMjIiKFRANDRESERYBGRkY3REZGRkdISUgyZ1dWWFteYjQ2Ol49MTc8Mo+mmpWXnVo2OSw4JisqKCY6Q0NAPjw7Ojk3NjQvTTxbRoaGQ0NERUZHSEpLTE5ORVJTVlhZW15gY2Zpa2s3IBwxLSooJyUkPyxBRUVLMz4sNjtAQ0VFRURFRSMjJSYnIjAtSBZMSkhGRUNDRkg6MiwoJiRGRENBQD8/hD6APz9AQEFBQ0UjJCUnKi0wMzQzRVJ1fX5+faKBhYiLkE1PUUNrP0pGUVhRNlZQY2AwTy9RaVQ1TkY2MEVKcnJwjIV+dWpfqZOBbl6lnqBwfYKEg4F5YGRjj1BujIGHhXlMYHCDUXtraGxnYm5Yq11uZm1gXmdmc1ZjgItjYWVnYVuAUVtlb4x4aGBdXl5XWGCXanRfZWRkWVJibEduiGGsfWpnXmlnXVBphYquZZNFf3lxdYF9enB/a7JqYXt/VkhMaG97alJyXXF+g4aKflBIS01QSkdGS0lxvMFkxH99eXZ4QUNEdWhjpmp4fUVCY2V/U2BkeHlxakNDfXp7dnqDh3yAgXhSfkJEQnhucnyBc3G3enpFSEtJSk1RSWGaaWqEhEVIS0tKR2NYWIKHWmtrgUR7a11dX05UWWxyU1pcXGNhYWRrallZX099m1FeiFJMTUVHUFFdTIVsZVFXXFFUVVR7V21bSFVdXU9OVUdoVV1QTUlRVFJTfYWYdVNZV1JRUl8qbGpoV11pZWCRTVdrbJypZE97cHJ7enZth19pf5exZHB7hIplVUhBLTY1hDQhMzMyLUNjn46KimOGhIKAe2QkJDo0NTs8Ozk3NjQzMjAvhS4nMDI0Njo+QSMlKSwvLThWVVNSU1dVVVVUVFJTVlCGepFNUFNVVVZXiVgwV1ZWVlVUU1JVY1E/QjMwT4l3Y1OUgHl4e4OTVWF4S108LT4lQzNQPV9XVlVUVVZWhFeGWAFXhFYjVVVUVFNTUlJRUE5NSo+FdWNgcYiNSVVYWV5lZmdoaGpqa2uAPTs9QERFR01LSklIRkZEQ0A3WlhVRENDPDQ1MVUtRSY1K0pGUDRALjAxQT8+PS1PU1NUVFNTVFVTUE5NTUxGPmlZTU1QVVldYWNpbneOpuGA97n6nbvW5enq7/T19fPu5+He4OLg3NPKwbivqKKblI6FzPuFwqibmaC7kvHCy85+1Nrd4eXp6+zv8fP1+Pr8/P6AgYKCg4SFhYaHiIiIiYmIh4eHiIiHhoWC/vTr5NS+qZnhkOd4dm1pZmVkY19Kfp2dnJye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hISCemFmZYWfcFNIkIl/cXddqoGMcmRqZ2ZYWlZ7omlmgGtfZGZcW1i1fGdiXmFjW099VW6KZm1lXlxgW1dEXnlvWmNhZFpPWWxcXmFupVpnVk2Hg3l1c2GAnImoXUR7c3V2enF7X3hmandxa1ZGVV9pdmBJZ2BqeX+DiIddXkVMUU1Hi0xJgrZrZ3F8fnl1d4FDRHVxjsdxfXxEQFtjUGhrgHJtcm5vg4Z8enpzfIWGg4NcaYFERX94b3NDfXJrg1ZFRUdJSUtQTmF9f2ZycEJGSU5OTEV1Unl5mGRoekR+d2hHTlZcfJldfFtcXV5fXWNpakVaXFtpVZxaU3yETkRESlJSdm5RYURWXFVTVlaCb4Vbd1RbX1JQUlVwWFhGTUlNgFRVV3VmZklnapBVU1BQXmxqalhaamZekZ5TaGuVfIGNdWpye3t2bYZfaX+XsGRwe4SJWYROQkIpJzM0NDM0NDMxKj5fmI+NkHiHhoJ/gUc2NTxAID47OTUzMC4sKikoKCkqKisrLC4wMjQ3O0AiJSglTlVUUlFTVlVUVVRTUlNXD06Fe5BNUFNUVVZWV1dYWYRYLFdWVlZVVVRUU1NVZk01RDIpP1pPk4uKiYqNTF1FLjlFRTguSTpcVlVUVFVVhFYDV1ZWhVcqVlZVVVVUVVRTU1JSUVFQTktIiH5sX2d8jUhQWFpbYWVkZWZnZ2hpZ19cgD5DRUdGTaKXclhOSkdGRURDQkA1VkpFS0c6LSsvPzpESzI4Nj0xRjZFPT08OTc1R0pSUlFQT0xKSEM6ZVVJRkdJTVFUWmFqeZj0opjflrrX5ezz+f2AgYKDgoWGhoWE//rz7u7t6ubc08rBuK+poJmSh+XV35l9d3V5i8jkt8fOFdXc4OTp7O/y9vj5/P3/gICCgoOEhISFAYaFhwGGhIWAhIODgf/89/Lu7O3ug4D12P2ggHRtaWdkY1ZseZOUl5iZmp1PT3Fctae/nouMloivhmFogm+AgYOEhYWHipnTgYyKgvHg08zDvbSqopuUj4qFgv/7+Pb29vX08/Dv7erkqeDj4uHg3+Di14DTtqu40vCzmrba0LWK2NOpmpORl6WAuevxlLqmkoX14KHIvrewqqGK05a9hfz28/Lz9/2ChoqOk5eanaGlqZ2wtLe6vL2/v7/Bw8fKxfeW+dbDtqWXi4S1w9uxlqCau/OL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e0VFRkVIS0+AfnGtbVxBhUdJS0lIgY54QVBWgGJ3gnt8blVbdV2yanSLR1lbXWBdX2RqaFBVW0lxmVJgglZNSkJFUVGEe05kalVYWVNWV11Uhl1mT1dfVk5RXIBmVXhWTExTVVlFZVp0ZmplXWeOUVBea2tpWFlpZF6QnlBnapePh2JyY3N8e3dvil9pgJixZHB7hYpWe01FUIJKViklMzQ1NTU0Mik7WpaQkaaHiIeDf1gtPSEiIT45NDAtKScmJiUmJicnJygpKSorLCwuLy8wMDAvT2pUU1FRVFZUVFVUVFJTVkyFeI1MT1JTU1VWV1dXhFgqV1dXVlZWVVVVVFRTVFo6SiwyNTIwMDAxMzY6PTs1LUs8YFdUVFNSU1RUhlUGVlZVVVZWhFUoVFRTU1JRUVFQUE5MSo2DdWNcbYVGTFlgY2RmZGRlZWRkZWZiWlxlalZFRUe52s6GhM+oblZLR0ZFQ0NDRD4sTldSSz82MDM1QzxTQDmDcz5GYjw4NjQyMC84Qk5NRjwyVEc+QEJFSU5TWWRuj6fHpeydw9jj7PT5/4CChIaIiYSKPIuMjYyJhYOA/Pn28+7k3NLIv7evp56Wi/CWr/RmYV5ezobirMLO1+Dn7fT5/YCCg4SFhoaGh4iIiImJiYSKgIuLiomJiIiIh4WEg4KA/vr28/Ly8/6A6tKsv5iAdnBraGVfeFh4mZugoJ6alpOSSWCJ0u62majVrfOAZkV1f4GBgoODg3+k+PXq1sOwnY2D++3l28u/sqedl5CKhYH++/j39vXy8O3p5uPbp9zc29nY1tXW2MfdxrnYoq+LxM/Pe7SFwMSejIeIi5DIqIu+nI3/69vNl7ixm/a28repn5SNhoH++/r8gIOGiYqNlJeanaGkpaOusbS3uLq9v8HFytHk2qaK5cy/rJmJ/L/6v9fqvKyW4vqypsLWz8/zocv+kZyDutWVqrKwp6Cbl5emmta+qJiOhf/07ejj4ITdgN7h4+fq7vP3/YSKk6GzyOKAksX8f4CCgXuZpL7t1tW1tp3im5+Zxaus6tGC1dDFr+2O9dHx1Y+Us5OE6kCf54OVkYqBenHRvKyXg+fZd0hPTkpHRD8yQWiNvF1OR0eHf3daSkPKs3hjXmNcXDg7TvGqYF1dUF5dODRzqmtRTFFXgFBHRTw8cJtxU01MTE5FQjg6aHpQT1BN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rZeUk5KRkZCRkpOSkpKTlJWTlJSUk4WUK5OSkY+Ojo6Mi4qIhYL+89a9oa/U7/iGlqPEg43du66qqamppJqYnqmsqqBNazs2JSYlJCQkIzs+fXt3d3d4eHl8e2WPgX11bGZhVXlZT2ecZFp0YYRfWVVRTUiKhpKYjXd1d3l6fH17dW93V0k1QycuMDM1ODo9P0CSICofHx8+PT07Ojk4NzUzMjAuKykmRDtxha51gWydeXA7PD5AQkVGJCUmJyeSKGUnJycmJiUlJSQjIiIhICA/PT0+PkI+ODVMuqOcm5ycmpK7cWmcymlsbW1samdiXmhjdG9kbX5Nicx9ZqSqqqusrKuroWg0Mik/IiQ/NzAqJSAdGjEuLCsqKCcmJCMjIiJDQ0NCQoRBBkA9Mz8/P4VAgEFBOSAege7MLT8eOTBPk3Vpy8jJZmtzflg8RUNCQD02U1ZMRkB2bWVfW1ZST0xKSEVESDFEKzQvSEtLTE1ORlBSU1VXWVtdX2BiYmJAOyQ5Mi0pJyY/QCNFNDlKPUpcME48MydAOCknKDZAKiIpLihJSEdFQ0JBQUQwODAqJyUjgERDQUA+Pj49PT0+Pz9AQEJDRSMkJigrLzQ7JCsxgnx+fn2whY6cYkY5QjhDPD5CPU9ZNFhMNlVOXWBYTGBpilQ0Ok40XWxNcHRzi4V8dGlep5N+bVyjnqxyfoKEhYJ6YmdidFpuYXxOS4uGdVNcXGlcemRmamdobVWlYXVna2NdgGVkZlODfW1fXV9kWFJFV1yHbGViX1xfXViFYXxviF5cX1VPTEV8WH95vqeAR0NBfX9/gYFSeXBzioSEeXl6f3V3cWOWm2lUbn9xVGJocVtKY1xodnuCgn9PgkdLTE1GRkZJXtHJzHJ8dHF3f0JsYWPHwsV3d4R3UId7fXx2d3TFgIKHf3p/eHd9iXuAgFd/Q0VBeWtwgH5rzIJWQ0RHRERGTUh+jZNrR30/Q0VFRkeFWnWQVEtWcnt4fHJwiHZQn15kfWqFSE1UXVddZmZbgE5TWFinWk+BRkxERkpTU1Fge2JJVVxTVFZXbmWFXUVPW1lNUVR7hFViVExLUFRcUWlVV2JbamdhXFl9U1BbamxrV1dmYl2RTqBlZ5WWYYphW3N8fHdvi2BpgJixZHB8hYpWdE1FTE9UZDUsKTQ1NjY2NEtvqJKRkWmLiIWCfz45QD85My4qKCcmJoUlXktLTExLTEtLSUdFQkE/PDk1MUlSUlJQUVNVVFRVVVVTVFZMh3WIS09RU1NUVVZWV1hYWFdXWFdXV1ZVVVVUVFNTU1RVV2FsOz9BQ0NAO2teV1VUU1JTU1NUVFRVVVSMVStUU1RTUlFRUFBPTkxLkIh9bFxhdoaIS1dhd1JZiXBoZWRkZGFcWl9maGdiWmKas4SLiYeFgoDImF9OSUZERENCQkM5ZVxXUD41MS1ANjZNh2pfVV2TOTEvLCwpUk9XW1BBPkBCREdIS1FciMPjtemMo6y2wcza6fX8gIOEhoeIiImKioqLjISNLoqHhoL//Pn18eji3NLKwrmwppyO+bfzs5xPUUuRp+qmv8/b5/P8g4aIi42PkJCFkQWSk5SUlYSUgJWUkpGPj46NjIuJh4WDgoD7+Pf29v3p0bPG5piDenNvaWOEUEx6o1NVVVNQTUxLSVma2temqs+XzKxcTXh9f4CBgoKCeqWRl4PWgoz62cCvopiOhfnl0cG2q6GYkYuGgoD8+PXx7uzp5+Pf0arX1tXT0dDOztDStomPw9XawqDWgICl3dGYgvb3+4GFj6qTx/Hk2c23k8zCoZCA6NK/saSbk4yJh4WCg5eM7a7dhZOXm56go52prK+ytLe5vcHFy9LbtO2a+tnBqZqV6vOD/ZamyKrK+Iffua6I8+CmmJrI7KWYwuG3s62spZ2YlZWjsdu8pZeNhf7z7Obh3t3c3d3fgOLk5+zw9f2Ch4+YprjO8I6dl55/goF/tpi+57/EvOPI17uuraPS6YvqxIDXzLzo6ozr5f7ZjJK1kf6tPpjhgpOOiYF5cNC6ppWB49d6SE5NSUZEPjJCZHxTc09zSUaCfGxHRUWldHNeXmFfWlA7xJJvXF5WUltXRDOcp2tPTE9UgEhDNDFUlnRSUE1LTktHWzl1dIxLS09EOS4jOyw8NmxeQiAeHjs9Pj9AJjIyV3FCQDw8PkM9PTYxTk85KzU9NyctMjYtJTUwNUBESEZELEcjJScoJiUkJC1dW106Qjs3Oj0fNTEsV1RVNjY6NShFOzw4MTAtUTw7ODU5NTY7Ny8wgDAnOBweHDM0NDgyKlJAKR4dHx0fHyEgQVRWOiY3HB0fHx8eOStXXCghKDE1MjQ0LTw2JGM4N0M3OyIqMjs5PENCMTkgIipChE1ATSovKiotMzEeIEFPNTQ3NDQ1NDkyZE40MDY1LDEzUWVFVDctLTEzNS9NQlI9QT44NTJyjYGEa5ibmZSRm5WP44L2m57l3YikezU1PD09PVBne5q4z3F6gYiMWIY4oa2E0O6EkZPDyc7PzL7y++N/X1U5TUxMTlWyyOrs2L2omZGMiIaEg4GBgP7///38/Pjx6eDXzsW8saSXiKc1MzIwLzEyhDEKMDAxMS5US1YuMIYyAzM0NIUzHTIyMTAwLy8uLi0tLS4vMDEyGRgZGRgYGDEwLy4thyyELQYuLy4uLzCFLwEwhi8BLoQtICwsVVFNRDs9SFJVLDE1PSQoRj48Ojs7PTw7Oj1AQ0RFA3+BgoeDAoKBi3+IfoV9A35+fYh+g32NfgR/gIGBioKVg5CCC4GBgICAgYGBgICAh4GmgomBAYCIfoV9in6GfwSAf35+in8HgIKDhISFhYiEjYOYggSDgoGBhIIIgH97e3t6enqEewJ8gYaChYGOgAWBgYKCgZSAA4GDhIaDBoKCg4KCg4SBBYKBgYGCh4EBgoSDiYCHg5KCiIMDhISChX+Efg5/gIGBgX9/gH+Af3+AfoR/EX1/f31/fnx/gH9+gH9+f319h4CFfwN+foGIgwmCgX19fXx9fn6EfQR8fH5+hn0EfHx8fod9BHx8fX6IfQR8fX5+h30FfHx+f36HfQZ8fHt7fX6EfYZ8hHuJfAl7enp7e3x8fHuEfIt7iH0EfHt7e4Z8B318e3x7e3uEfIJ7hnwBe458g32GfAN7e3yIfQZ7enp7fHyGfQZ8fHp6e3uEfAx7e3x7e3x6e3t7fHyKfQh+fn18e3x8e4d9BH+AfXyHfQR+f3x8h30Efn18fId9g3yJfYR+EH19fn5+fX19fn59fH59foCFgwGChX+FgAp/fX9/f31/fn+Ah4ECgH+EfoZ/AYGQgpKBAYCQf4N+oX+HgKl/iX6Ef4KAj38CAgQAASKFEIQRQxARITjEyMbGl/Sq0dXOl9nOwbmxmr/W7viVq57drtSSi4X98urg1My6jtLZ3N3d2tjNcSMrGRweHx8gISIiIyQjIyKFEQYSEhIRERGEEoURIiIhICAgHx8eHh0dHBsaGRcXFzZI3LnCiK2q0I84MC0sLRaHFwEYhhcEFhYXF4kWaxUVFBQUExMSEhEQEA8OHh4eHRwbHHHZz87S1dbQkqu7weaBg4aHiIiHhoeHjGlwcW9qazZeob6Dl7THztLU1tbY0WMwGRoPFQ4UDQwUEQ4MCQgHDw8PEBAQDw8PDg4cGxsaGRgYGBcXFhMViBRPEAgbaZdkLRYheXAjEgwMHB4iJSkWFxsUEQwHAgAAAQABAQEFDhMVFxkaGyArGxASFAoKCQoUMSwtLCwrIikpKCYmJSQmKCgoKiwvFQwJDYQMJxUODRYWGB4UEx8yMi8yHx4WGxocHRkYDA0MDz89PDw7Ojo7Ow0ODYQMiRkBGIcZhAyACwsKCw0KE1Ojp6ioka+wsV0tGR4aJzEqNEIzTVIwWk46Uk2IVVd3SkyBVDFBYDJYq5rQpaHIv7KllITrzLKZgubg9aa4wcfLysKcnY3Qr9eZxOf++enjyYiovqHlyqGxtLSmrZ+688C4wKyruZyip4X4saiqsK6Zn8Gvj8rgt7OAr7C5o52UvMS2jIr23+L7gvPlpNHD18XFh4aGg4aEgvaCvKm7kID17ejt8uHjm4qNh7Ti+P2K8M7C07mRqIaVnKWqqJeS2YGGj/31goCw6/rnn+rf0tzn7MygjPb6+r3e7oSD5vzvxpKUipLy/u/t8+rw+qKuupDIgY2N/uvX9fqAjIboteaDgoT5goeQuqWsmMPn+fyC/4CA+rXAuZ6olLbZ1Ojp19unhMjtjMe9gIWF9tjU/5OqoufEjdifhoaZ4a2UioWMo6qW+syz3aWtq6GmqdvUnaTZpbC3qqmpm+KqqpaZmJ6qsLyClqWgy8/DvK+iwcmH+qWnqoWPpZ2R4X9N/Z6j74CZrOeRtMC+tqjTk5u21fSLna69xorXllNai2B7SlEuKSQpKi0wL0J42tjX3sDPy8fCvUclIR4eHRsaMjAtLCopUE9QW7CRi4eEgzOEhIWFhYaHhoeIiIiHhoaKjo6NjY2Oi4ySh/jO4PyGiouNj46QkpOUk5OUlJWVlpWTk5KFkQGQhZELkpKRkJCQkZKSk5KFkQOUlJOEkjaTkpOTlJSUk5KTkpGQj46Ni4qJh4SB9uXNrqPB5fT8ipKYoLikxr/N1LasqJyVmKOnp5+dYR4HRCYmJSUlJIUjSDs7fHp4eFmLZX1/fV2IgHNrZVdseYeQX3ZvhHiZUU1KjYaAfHhzZ056fX1+gICAe1orQiksMDIwMDI0Njo8Pj8/Hx8gHx8fIIYfhCAsHx8fPT08PDw6OTk4NzU0MjAuKyglRkiziIRbeHylhkBAQURHJSYoKSoqKyuGLIArKyssLCwrKywsKysrKioqKSgoJyYlJCMiICAfPz4/Pjg0LHS1n5iZmpmTa4eSk7NocHFxcXBva2hlZ2JucGxtf0dcboVhdYydoaOkpaWmwn1PLzYtQzE6Hx01LygjHxwaMS8sKyknJiUjIiJEQ0JBQUFAQD8/OTQ9PT0+Pz8/QIA/KhVd28e6LyITLzdWYHBiube6usBlbn9qUUA+PHBpTVpUTkhCfHNsZmBbVVNlQSkyOx8fICE6U0pMTU5PR1BRU1VXWFpbXF5fYGFiNiYfNC4sKSc6IyM6VVJVSEFKW11TUkE6Ki4uNDYwLiUpJzFLRkZEQkFAQEQzNi4pJiQiQwRBQD8+hj2APj9AQEFDIiMlJyotMjhBJzVfe319fW2EiphaPzNCPD08Mj9CPlJZNF9YNllQW1hZS0lQWVU1OVA0XWpOcXNzi4V8dGleppJ+a1yhm65xfIGDhYN8YmhfcVFmV2uJlo6EgG1DV215iHBbZ2ppW1tYjrJqZmxfX2dUV19hqGFcXF+AX1dZaFtfkYJgXlpcYlVTU3OIiExIgXV2gEJ9d1t6d6mce0RDQ0NFQ0N9S3RviGBFgnt4e31zelVXZFF2d36ATHxpZ3BkU2Zdb3Z+g4BgVHZFSE6KhkhGabvIuHF7dW51e3hvXGa/wsFvdHxDQ3aAgnlyd25lgYqBfn15foN+iI6Aa21CR0d/dm59jHBss3l6RENFgEJGSmN1gWV3cnuARIZERIZlj4diZFBacXB7cG5zWUl/mlpxZUJERIJzcYdNWlmaiF6BXlhUW31fT0dCRlBVU5V2Y3VUWFhTVVZ3gldcdVJXXVRSU1GDXFtNTUxQVlhfQVNXUGZnYF5YV292SpJgaGpuVlRjYFqPT6BlaJZPYW+DVnN8fHdvjGBqf5exZHF8hYpXdExDSFZSZT5INDQvNTY3NzJDZJuSkJV/iYeEgZ5MKyooJyYlJEZBPDgzL1hTTlGWd2leVlNTUlJRUVFShVEGUlJSUFBThlUgU1RXTo5zf5JNUFJTVFRVVldXWFhYV1hXVldWVlZVVVWEVIdTDVRTU1NUU1RUU1RUVFOFVDhVVFVVVVRUVVVVVFRTU1JRUVFPT05MS0mLgXNhW2p/hotPVFheb2+emJmFa2Zj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rnRLLjImPTAdHhwyLCcjHx01MCwqKCcmJCMiIiJDQkFAQD8+Pjc2Pj0+Pj9APzEiMquytMImECV5YC1SPmxguK6tq7K9Z3GDQmd6d3FtXVVbVVBLR4R7eEhZNTw+PTw8PT4fICAiM05MTk9PT0hSU1VWV1laW1xdXV9gPTkkHDEuKig6JCMpU1pTVVVUPVBHWkcoPTwvOTI+RyMpJBpTREVCPz4+P0Q2NC0pJiMiQkA+Poc9gD4+P0BCQyIjJigrLzU+JShVfXx9fXqSiJRVOSxCQShLOy9BQD5RWTRhLTRcUFovWk1bL1FWNDlQNF5qTXF3dYuFfXNpXqWQfWtboJqtcXyBg4SCfGJmYHFRUGV+jY2RiYF3aWpSXFqBaFxnZ2tIV2hihWZnZV5kaHpVU2N8YVlagGFYWEtaVpF7YGBcW19cU2yDdHqLcH14dnV9fnp4Wnd1h3dQRUdHRUeDf3OJc3isbYV9enl8fnF5d1lVhGF/Q35XXmVZUklpXGhzen9/c09UgUREgn1HhnSuw8Fkf3Ztb3NNi4RrvLejXXJGhHx9gIB9c3J0aYKDfYN9doGJi4uKgKtESE9NQnp4Q4RxcYxWQkJEgX9CR0GcZFVsYXx8gIGDhIBskodgZlJKc3CAcHNzb1ZPjI9paYVDQn1vb3l6SlmTfXlwaWx4lEd5R0xERktSVHhLfWNQVVhVWFlcXGFeYk5VXVlSUW9Kilh/U05QVlhgV2NTgGRnYF9Yg4lbblNcXHdda1dSZGBZkJ+dZGebUGB4aUxyfHx3b4xgaoCYsmVxfIWLWHhMQUZXT2ZzPFo4L0oyNDQyUDeqk5CQWIyJhoR+YTYzXFFGd2NWUVFQUFBOe35ub157T1FQUVFRhlCFUQhSUVBQUlZVVYRWEVhZUkp6eo5MUFFSVFRVVVZWh1cKWFdWVlVVVFVUVYZUA1VVVIdVClRUU1NUU1RUVFOEVIRTL1RUU1JRUFBPTk1MSo6FemhYYXWEhUhQVFVaX2Nrn3K5kpFxa15fY2NfXZFPPiUlUYeFhISDg4OEhYiKjJCU24dXTUg7R0hISElEMUY+OThURD5HgMPKUaHkLFNPTUtJRkJAOjEqQUVJTlRYYZ761/2Poq6xq662wcvU4Ojw9Pf4+ob7NPr6/P+AgID8+PTv7e3u7evo5eDc1dDIw7munovlpoB5Ti9NbMv2o8Lb8YGKkpifoqSoqqyErQSvrq2vhLCAsbGysrGwrq2rqaimo6Gem5iUkIyHg//34cKdvcb4rHx7fXNeg2dwX4tZXl9eXVtZV1RQUNN9TlNFY96mVWZzcmt3TF1udnh6fH6ExOK/hpuE6NWLmo3/2bmhkoX15NG/sqaclI2Hg4D69fDs5+Pf3L6109HOzs7PyL3i5bTwjYN93dKv/Zu/lZ2A6uPk4Oz5h5WphtDy7eTavay3qp+Tif3m7K72osnb4Obt9PyCh42V0J+UmpyeoJelqKyws7a6v8TIzdTcqvWlgNrNuKjgkIyYtbu1ydHPjsOv8uCD1uS/2MXb6KPHrobxoqSalI+Nj6DU17mjk4iA9ezl4N2E24Dd3+Hk6O70/IOKkpyrv9XwiY7+j4GDgn6dnMqNiIzf347evJS+sK/d+Jj/gIL+3bOB6o7zgp7ZiYu5jPOfQJTjhJOOiIB3bsq4opB93Mx+R05NSkdEQDJBXXBFQ3KDeIOIf3ZrYk46dIB5W1NeXF85OVuNjlhaWFBSVFc1T358UIBMTVNIRjY4RaSIT05LSk1LREU+NTZENzo3Nzk8Ojc2KjQ1SUIrIiIiISFCQz9AME2hUEpGQ0NFRTo9OS8vPTI+IUEuLC8sLCk3Lzk9QUdGPCsrQSMjRUIkQjhNUkwrPzw4NzcmRTstU1JKLTUjQTs8PDo4NTMzMz09Ojw5Njk9MoAyNEoeHR8eGjI2HTUuL0AmHBweODcdIB9ZOS41LTU5Ojg4ODcxWFw0NCgjMSw/MjEyMTEqQkI2MDccHDgzMDEzJDlmTElDOTdQgz1FKy4qKiwxMS4cW1Q7NDY0ODg3LzJJTjYyODMuLkIuZUNmNC8vMjM7NlBAYTw+ODYwSFhQaVo9N2t6lZGGlpCI4f/nk5rpdI2qhys2PD09PVFpfZ+7znF6g4qNXYU2kpiLtdr+p9SSiPu4vLqt/4P+imhVMExLTEtN6peJ576RxXVJQD47Ojo4TDIqLSRLNjeENiE1NTU2NTQzNDMzMjIxLy8wMTExMjIxMTIzMCxNS1QuMDGHMoIzhjIHMTEyMTAwMIQvhi6GL4YuBC8uLi+ELgUvLy8wMIQvLC4uLS4uLSwsKlNQSkM6O0VNTyovMTE0Njc6QDJVO0k/Pjs8QEJDRvrs1YiIjoMCgoGLf4R+AX2Kfop9iX4DgIGBm4KDg5WChIEGgoOCgYCAhIGsgoWBAoB/h36FfYx+Bn9/fn9/f4d+in8KgIGCgoODhIWFhYaEjIOUgoSBCYKCgoGBf357e4Z6BHt7e36MgQWAgICBgYiChIMBgpSABIGDhISEgwaCg4OCgoGEgISBA4KDgoSBBoCAg4ODhImAh4ORgoiDA4SEgYV/KH5+fn+AgYGBgIB/f4B/gH9/gH6Af39/fYB/fX9/fX+Af36Af35/fX2HgIV/A35+gYiDAoKBhH2CfIZ9BHx8fX6HfQN8fH6HfQR8fH1+iH0EfH1+fod9AXyEe4p8BHt7fX6GfYN8hHuJfIR7CHx8fXx7fHx8i3sMfHx9fXx8fXx8e3t7h3wKe3x8e3t7fHx8e5R8AXuFfRR8fH18fHx7fH19fXx8fX19enp7e4l8Cnp6e3t8fHt8e3uEfAh7enp7fHx9fYZ8iXsCfHuHfQR/gHx8h30Ef398fId9BH98fHyGfQR+fHx8h30XfHx9fX1+fn19fn5+fXx9fn59fX5+foCFgwGChX+FgA5/fX9/f31/fn5/fn9/gISBgoCEfoZ/BoCBgYCAgIl/Bn6BhoaCfqF/g37Jf4l+iH8EgIWFgop/BIGCg4MCAgQAARGQEDI6z9LU1NXX2dvareXKreiajpSwZXLMm7n16uTc1dDNysWevrnFxtbY29rZ1uZEJi0ZHoYgiCGFIoYjhCIEISAgIIUfGh4eHh0cHBoZGRo6SdGd7ZL2oc2ONzAvMBgahhuEHIIbiRoHGRoaGRkZGIQXgBYWFRQUEhEQDw4OHRsbGjZuZ8Cxt7am+cS4qojGhIiHh4uOjo+NipKH6/3O2/zQkKvT3+Hh3M7Fyu6Tr8THxsXFxcRgLxgZDwsPCQkKFBEOCwoIDw8ODxAPDg4ODQ0ZGRgXFhUUExMUFBQVFxgJFC1QZ6ulXEcvLnM7HA8OIB4gCCQrKSwZHh8BhQAIAQAAAAYPCw6EDxYQERESExQUFRUKCgoSMC8vLi0iLCwqhCs6LC0uLzAxMxUMCRANDQ0XDg4TFx4fICQlLx80QS8uHhAYHBUYJwsMCwYjPTw3OF1wOjsPDg4NDQ0aGooZBxgXGBgYDAuEDIALDAcJEFKkqKen+7CytV4tHR0hOjUuJTI7MUonQFklNyVHhyhVdSpAJFIxQGExW6yWv6Wlx72yo5WE7M6wmYPn3/ekt8HGyMnCnJuL3bSsiMyjnvP45eDOq6ybkejeqKa7tLLImengurC5rLG6p6+hw4SzpqWtq5alx66NzsS2q4CklouFgM30wr/Cufn49eT/hPnqs9G+g6jyjZCOjI3289vRruWK0Ozp4OLp4tGYjp+vy9r9gancnaCouJmkjpmeoqSlg8Lq/fuA4oD17dfi+vjb3sbPt63d/I/10Oiyi+zu/fz05r6Tl5Sq+4Hq+e3n/OCvtbGhmLvo2qqKhoWlj4D0u9f+g4L29IaLiIOro4bf6vL09vPt24uYlaiGz8if1Nni6N6StZL3pNL6gv/z3Nfk2tn/oa+lkoaYtMnm1LKXkYuMn6bewNqt+KmwrK6sqabgqqWGp7Syp6K1g4OawqOgnqeltredlMS+ysXItsSUorGWr7e0seyCgqGVjdz78VWYn/eBl7i59bPAvreo0pKbttP2i56vvcSJ0ZZUWI5bgWg/ZENbPCcjJCNEO2vQ1dTUqtDLxsK7t7Cd9+fvhISCgYCAgf/K7uPJlc6Ag4SEhYSEhYWFhIYihYaHiImIh4aIjo+QkpigqqmfkoPlxeL9hoqMjI6Oj4+PkYSSBJOUlJOElBiTkpKSk5OSkY+QkZKTk5KTlJOTk5KRkpGIkDCPkJGPkI+NjIuJh4aEgv/y4cisrMTq8/uKjpGSm5ydoKSrts/j8YXaqKOhmaY3ICGEEIYjRiQlJSYnKCosLjAlOoKCgICBg4SFhGeEdGSIYFRadU5dil5ziYF9eXd0cnJvWnRxcXR+gIKChYmoVDlDJy0wMDAxMzU2NziJOYU6Bzs7Ozo5OTiGOYQ4IDc2NDMxLywpT0yyd6tmrXSdejk7P0YnKi4wMTIzNDU1hDYBNYU0hTOANDQzMzMyMTAvLi0sKykoJiQiIR89NzAoQWpZn42Oin3EnpWFbaZydXV2d3h2dHJubV+qr5OOrJhldpOnp6ekmpSYt3SLnJ2cnJucpm5JLTMnHi4cHhw1LykjHRowLCopJyYlJCMiIkJBPz49PT01OT8/QEFFSiIYM1dGxsoTETGATZVXOHBfrqGfoKiuvWZ0iWxzdHFtaVBiXlhXYjhARkZGREJAPj08PD0+Px8hIitNTlBRUkdSU1RVVldYWVpaW1xeYDQoIDMvKyg7JSM0R0dLTlxubEVGV0JFPi1FPS0pRiQoJBowQkQ/Plx0PkQ2MSsoJSJDQD89PTw7Ozs8PD2APT0+P0EhIiMmKCwwOSMsM1h7fX19v4aPoWdLPkREQ0w6LUE/PlIuSVstMy5NWS9aTDFJLFM0OVE0Xm1Ob3d3i4R8c2hepJB8aluemLBwfYKDhIN8YmZhb09KPHpcW4yOgX5xWVlUdKF9Xl1qZmRoVJmmbGVoX2FtX1pYil1iXFqAXl5TWGpcYZZzY1tWTklDQGaDdXV+aYV/enJ+QHx3Xn10W3WCRklJRkd+fXGDb6NzeYB/d3l9dmxTT3GPl3aDQ1h0VVZbaVVlX3B2e318UnR7hYJCd0SEgpy2x8J6d2lsYmB0hGjEpapkSX16gYCAfX1ydnJng0J7gXx4g4WIin6AXkxccWxUQ0JHfG+3c3KDQ0SBgEVHSGKAaktzeHx+gYB+dllzX2ZKcGpQbm5zdnJahliUYm6AQoB5bWx1cnaZbnpvYVdgbX6Vdl1OSkZHT1F6doBig1dZVlhaWWGJYFxDU1tZUk5YS01XalNQTlZWXmVYU2leZGFfWGRUWF1MV1thWluNVlFiXVqOnZVhY5pQYHVzjHJ8e3dvjWBqgZmyZXF8hotbdU1ARVhLZVQ3TDtFNi4qKyhLOlaVkI6PcoqHhYJ8eHJlnIyTVFRSUVFQT5x7rayab31PUFBQUVFQUFFRUoRRglCGURpVVlZYXWNoZV9WSoF0h5RPUFJSU1NUVVVWVohXhFYFVVZVVVSFVYJUh1UDVFRVh1MFVFRTUlKFUSxQT05NS0qRiYBxX11qfoSJTVJTVVpbXV9hZm6BkJtYjmZiYV16RThHJCQjI1WBgICAgYOGiIyRlpugp7O5gYBdUUxKSUlIRkQ2ST9AaGJJUZCAo6Q3XlFLSEZGRUVDQjZEQ0RBSFBaXWmE8f7M8ImdqamnqrS9xczS19ze3+Hi4uPihOMJ5Ons7evm5OHhhOIs4d/e3NnUzcrHvrCikvGu/mxtOWdXqcqGpsXjgY+dpq2ytLi7vb/BwsTGwb+HwYDDxMTDwsC+u7q3tLGsqKWgm5aOh4Lz06+BociCxJyQhHGojIJyW4tbYWJiYmBdXFlTUj9mdmxaXnBSWm+AhIWCeG9wjFlte35/gIOGqcy6iKGSgN2LkYDhybKfjoDn1MG0qJ6XkIuGgfrz7Ofk4d65wtfX19Tf8IzCuIaXmJ3W14C8r//Dh6OC693a1N/r/4qY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V1dUjKFlYmNcZGVJVoCLWWKAW1lcU05LTmNUTkl5b3V5fEBBckh3b4RfQ0F9dYFEgXhlfnKZtVxFSUhGRYB9aHl2pXZ9gHh5eX1lYIlve5RbfIh/X1FScFlKZ1xodHd7fWJRcISEf3uAg4ils7bCdHRrZ3VTgIRivW6OU2V/goODf351a3Blb0OBf4J5eoaEgn+AanRXbUZfdVyRkYJ3i1OCQ0WEgoNIcmhcYHVtcXp3eHx8eXZgsGePcXNxe1lsc21YgGVXUGxAQIJ6bW1zZWFTcnJnWZ5ibnmMc319SkRIS1FTYHFraVNXWldcW2NvglxsUFdbU0xOTpFZW0xQTVNVWE5QUllTYF9fWldYYFdCWFpNXFlUYZlOX1lXipiTX16WT1xzcX5yfHx3b45faoGYsWZyfIWMXW1MeURZRmo5aEw5RjpCPjs8aluZj46OjZ2GiIWEf3lyZJF+kVFVU1GEUDSgm56dT1NSUVNZY3GBSFFbMzxIN1FQUFBRUVJRUFFVWFtqklB+fWBXSoVtdo1MT1BRUlNUhVWOVgJVVoVVAVaFVYVUA1NRUYVSLlNSUVFQUE9OTUxLSZCLg3RkXGV5g4RJT1JTV1laW1tcXl9fXmFrbWRiXWk8MkSHRlX8/oCBhIiMkpmjrba+xM7b8P6m6VhNSkhGRERDPSpLRZZTWZKQwopsUVBLSUpJSUhFRjg0PUBRRFJbX2mDuruCw+mHlpubnaOqsLi+wsXHycrNzc7NhM6Az9LV1tbRzc7Pz9DR0dHQybWgiebBof7EjcGj4cO5w5GNwt7V14iz/oyrof3C+Ze54YSYrcTU1tPS09LR0dDR0tTV19jY19XSz8zIw764sqymnpaOg+vHnNuSxYnRsaCIcLannH/KmmBjY2JhYF9eXFdVUmxfXz5sTExgfZSfqKmAqqeNgn58i1htgIWEhYeJocm/jqmZhM+FkYfrxaWQge3Yx7uxpp6VjYeC+fLs6OXjtdLjgYf/+OHAkru7naCgwteom/uuhZDw3drZ3un8hb6i6/nf4eHd0ZrG4fHr5N/b1s/Kz9Ta4efs8Pb7gIWLkeuSmJyelZujqK2ytbi6vL+Aw8fN1aDml/HhzLL4mIbkgvfvi77v75mOypvV+M3EhJuRwa+i1obJlp2VvOHqgavkyKuYiPzw6uTf3Nva2tnX2N3i5erw+IGJkZus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ZnVjXGRfb3V7fXNOWIiHgXt8hYRcrcTAcnJuU1Z0hIRommmiYGpFQ4SEeHtua2mxdUN6e0F2eoaAeHt7rYRqe3lWemWZjIFteHFCRUWFgkVFX1uYaWJ1fn95ent5TaaaZYJxdXVyWV5qaFJ+YF56U3hBgHlra3RWUVp2bV6hoV1mdIBacmyBg3+FSlF/XV1ih1RXWFtdX4Rlbl5KU1tUUE1iW4JWglNPTVVVbGpWUkdcX1taVUeBU3FqWFtcWlZsb2tKgFJVjJmSXluSTlltdHNyfHx4b5BgaoCas2ZxfIaMX2VId0NZRVVeZklqgVtGUGqHmJGNioyOj49ki4iEgHt0alWlplVcdEpXMTc9REpOKSorLC4wMTM1Nzk9QiQpMCZVUYRQGlFRU1FSWWJ8sbtwa2FWVEyMe2p8jktOUFFShFOFVIRVBFRVVVaEVQFUh1WCVIRTg1KFUYRQKk9PT01MS0mPioJ2Z1thcX19hUtQUVFVVlhZWVpaW1lWV1xfYVxiZy0/RIVFhEaA/YCDh4ySmqSxv87d5/D7h5memNWpZUlGRENCQT48MlToaWCc/b2mYygpTk1OTUxJR0c6UjFAT11KWV1hcayKh4a84v+JjZCVm6KprrK0trWxsbO5u7q6urm6vcC/vry8vLqnjOKt8ZaIg5S6ubLRn8K8wbCep5qboOGes6bH4v1mp5629q3QtdO+oK2/xtik29m8lK/M2dnb3d/h5ens6+zr6OXg2tPMxLuxqJyRhOS6iMKEu4vauaeMemm7rpiHfXqYrL5hYWBbV1JUUUlEf3Z3hVhwkLfP47C9yOezp5mKh4eLV22AhIiAiaTUyZKzq52AlI3uxqeUiPrp2Mi6rqKYkImC+/Tv7OvBkpKF9ey/wKqG4fCJ/7bxwbeShbeT+OLZ1+ni9oSQn7eGgOiTpqjwkoj98+rh2NDLy8vN0tvi6e3w9fyBhYuKpJGbnZyDnqOrsbO1t7i2/IW7xtKykvvq16GXmtHNgaqA5O7u6tSR5d+j8Yzgl6+B3ISwluaJg5CXjoeFgYTt6b6gjfzr5Obq6+ro6efi1tTY5uzw9v2EjJelt9Hviqitl4SHhoO0mbmHiI7pt/OJ4umzjcOtuuG41vuE+oXXr4vqiY/Jq4CBhcOL/+49jNiGkYuFfXZrxrKei3nVyn5IT05xTEhHQDI7TV5CPDIx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cXx+d4CCf3l1VoVfj1xpdlibgXKXVYNFR0SGRUhabXdkSnZ+gXt7e3xgZYJkdWt1dXh3cFlbT3JeXIt0YWuAdmtqbkhOYnVogLWdWF1hdmlNTHOCh4B+gX93UnGBWVBVWVpfXX5xiFl+UlhVTU5YlYRVaVRNS1JUWWyEUnNdX1paVXVYUGJWW15cWGR9WHVWU16KipaQX1uSTVZncWhyfHt4b5NjbIGbs2VyfIaMYWFIckNag1NdZYZZRkhQQ2NZbJKRko+Pj5CVKHyJhoN8hT4nJSQkJEVBPTo3NDMzNDMyMTAvMDAyMzU4O0EkKC4oV1GETyFQmVJUUlRve7GBZXVcVVFSUEmNdmx+i0tOT1FRUlJTU1OFVApTU1NUVVVUVFRThVQHU1NUU1NSUoVRO1BPT09OTk1OTUxKSY6GfXFkXGZ0g4OAhYqNSlJUVVVVV1dYV1NUV1pcWV5aUjtBQkJCQ0NERUZGRkdHgIOHjpahq7XBzuD3h5SiqJaBr8z8v49FQkA+OzcuRzhHyoZejPCfmj0qK1NTU1FRT0tJPlUyPUxZcU5bY3af8MatjLfX7PeAhoyTmZ+ipKOalZSVnKKjpKOjoaOjoqSiwcXMn+3srcjJuY+y2MeAzNTDuOKxsq2amem5pLGglKy7gJycobCH4rfs/KHpydbMlszI7MKrxKiS35e93eXo6+/w8vLv6+Tf2dDHu6+hkoPZqvex97GG4MOvmox9cc+8qs9iYKVvcH2HjZaZTU9LRX90eI9eelD4nI/J+/vSlKucyayjnJePkVVpfYmHiIqMte7am83FsvOF+tu+ppaI++XRgMGzp5uRiYT/+5Kh3Ib34dnahbaBzZKto7+NidC3w5HbhOvc2Nzh8YCOoL6Kr4n23MOFmYuB8+Tg08jCwMG/xdPn7+ri7fuFkYKu6aKr7tmvnsawkrCys7Ov2fSzvMSZxIP34ZGomoyQ2qP40ujP1qW155jzxM6i352CqquX4IiggImVkIaCgIOi16yRhf/79/Tz8e3s6unp6u30/4D+gIKKkp6wxuGAlJDQjoiHhW+Uq+PQ2tbTjv2M4Oaxh8Cot4Tn2/+F+ITi347jjpTJiYSAhMSL+7M+jNmHkYqEfHRqxbOeiXjTyUBIT09LSkc/MWSHV0A5LzFXoXBmanZvamNRgD01fL1sVFdeVlBENoGFVk9RTkxT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pbm4a3JUalxsh4VNc4d+cH9GREOFgIh0bWtdg3ltdXV0fX14dWZpR1JmdGxUS5GAgGV6cUNISEdFSIKVdlZtcHh9fXt9enJjlWNsaHt8fnt7dYRgX1ZZQ3iEflhtZWpock5mbWOynVpeYnVYWl95h4iIhYd+XHBobUd8UVdYXV1gn01cZVBUWVFOU2VZXFxRUUZRU1VcflFjU1pZW1ZjfFFWUmFjXltiSVNjVFRWVVNjgJqUXlaUUFVkb11xfHt3b5RjbYGas2VxfIWMX2NJbIZbhIVZrFZHhE1PRHFqakFja2VVl5OUWIqIhICCOzw0LysnJEM+NzMyMjI1NTQyMjExMTI0Njg7PkQmLSlYUVBPUE9PmS1QU1RUYWNmYVpWVFFQT1FOSo97aniKkkxPUFBRDFFSU1RUVFNTU1JTU4RUhlOGUoVQPE9PTk1MS0pKkY+KgndqW19peoiIiklMSY2PkkpRVFRVVVVTUlRXWVZXUks3PT4+Pz9AQUJDRUZHSCQlJoCOlaCxwtPj7viHmrS0n9L4h9uG2kpCPzw4L0k0Mzs9T4g2Py4vL1IsLS4sVVRUVFNMTENkO0BKWGtCVGR4k8yY9b2IrMbU3uv5g4eKi4uJgvz29fmBgoKBgOOy+L2B+53O2N+Kh+m9vaf9rsm8kNbata/dm9OumpfluqCvkp21uICgk5qX5Nak5+OR2LbUsoKzray0vNHe3drAx4iJ66TR4eXl4t3Z2NHLxLqtno31yJnjpOatiOfNv62glYuCddF9XWh4YG9spn1pYmR3TEeCd32daIle1sjK7/iBgoWG/caHiIXCpZyYkpBRZnyJioqKi9iM85/w5tqOkv3Xtp+OgYDw28a0ppuQjamrm4e93Mi8vtCml67slqK9ocSCtJ3mqJT77Ojl7v6Cipy37efqkf7isoyUhPLq5NDGrpCVyfmFi/3Sx4OVpvzgtJKHiOOLvvD0rfK4oLCvsbOft7rAyauIg+GLpJaIqpiExOfXvs+fto6ml8/K4+uHq++Wxt6BsoCEmYqE//yBxb6vqJuMgfTq5+Pe3Nvb3N7i5+73gIePk5ulutPwiaSbiIiJh4Kim8GerbLp2uH4kd/kq4W9pba77t/1gO/52qyJ6ZKXx5Ddtt2/iPr/QIvghZCKg3tzasSzn4h208ZAR05OS0lGPjA8lVM+ODAwU0ddSkhz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T3NveHdoYaJ3fIB7bmhsTaZzYHJ3fHx3UleHiH56fn5/l7O6sm5oUm1kgYJKnoBjuXB4RYdERIWFa2xtYIl+cHV4en99dnifdUZLTEiDQ4+Gh6VRf0VHRkVHSUl/l2Vcd3p7fIJ+dYnDZG9gfYODgoB9dYySj1OMeYR9b4ZlamJgT2lpvqWjX1xlaUuEcnyAg4hFgnGMaWp0WoBCRUpSV1l3f1hbRE9XU0tRa3FZWoCGW01NVFJqZVJWSldUVldWTlRSSWRnYFpcVVJaTFZWVFFPeY6GXFaVT1NlbVVwfHt2bpVkboKasWVxfIWMYmRMaYZYZmFUWEpLg05PRHQ5UlJLNjI/XZmTlXKKhYF/YzQ5MiwoJEM/OTUzMjI1NjUzMjIyMTIzNTc6P0QkKitZUSxPTk9PT01DRkRbVFNUVldVVFJSUVBPT1BNSkmCcW1+i5RMTU9QUVJTU1JTU4RSCVNTU1JSUlNTUoRRQ1BQUE9PTkxMS0qSjoqHfnZsXlxodoaJio5KTE9PUE5KYIuNU1ZWU1JTVlhXWVNIMzo6Ojw8PD0+P0FDREZHJSYnKCqApbG/1fCGlaKrrqKDp675p5tVRUE/PTRQOjlCSU9UU19hLj88OSZGLC8xL1xZVlRUTUxOPURKUVxvQk5ccISu8LOCvICbrLW/yM7T2Nra2NLLx8LFybKCn5qDpPyFxsTww8vO36+9r7DB166/spmElaakzb2kp5iTyKqgq4qIkJGAmIPLjMnBh9T6opue0pfhnZaRmrfLgoyB2vqC8aKcstPHlsLKyMbAu7OqnpCA3LOM0ZjdqYz12tDEuq6pnpSHn2FqPENUZE5FYD2Elo5KiHuJtnahb/PO6fT0+oKFh4iKi4rppcOSx6ugmI+QUWh/iomJiYyRqYerqKWGloXavaeAl4jy1L2qosK8p5T8x5ywp8GQr6OkP12ysKfKltC/e96AlIX5+oCDhpW1zu+wn7CWgdyIno+C9+vQmsCQ78HZ3q2G2/yZnfHz7a6Z+r6Cqfje09CDlbWMra2vqaq2usCYvufD+ammhZTO1ZbzvsXUn7ycxdLZvZrtks+UiuL8is+AjY2FgPr2gIjl1b2olob26NzRyMPAv8DFytPc5vSCjZmotcPbgJqXvY6LiYZnl6/9ho7qgLHr8p3Z3aeEuZyiiPHb+/uY2ZG4+OKYnsWa3Z2b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W1WJsl5eYFJJhHRYgHxrZ3labXZBQ0B+fW9FX4qAX2doaXR3d3t1TnZ1kGlDRkdOYXVuWFeLp1+FSUpJSEd9bm9zent8eW58X21pi16IoGR7gH10a21pW2hfanR6e3tacX6EenR5g31QtLSPVWhnfFh+fWBromVzdkWFiEWAf2ZqZWSLh4JEgH2AcGprgJV9REZCeXaGhH5xd3CEiEhFhUl8opZjinN6fX2ERYBgj2tpV39ERERFhX5UZU2YiX2Gd3Vfd2lbVFRtZa+ZUl5aZ19OV3p8en2ETZJca2lvSnNBREVHR0ZGRlJygWNRVFZQTFhYfFltXFdNU1JUhWZSeVVTT1ZWRXtRdWJmZV5ZgGRUVUdXVlRPUmplR1h3UZRPU2RtS297e3ZulGRugpuyZnF8hYxkZU1nhGlfWkJ2kEyBTlFHOztWa1s6Oi06rJSUoIWGgoCBPDw0LSonI0E8ODQzNDY2NTQzMjIxMjM1Njk+RCQqLltQTU1NTk9ORUY7LVBTU1FQUVNSUVFRUFBPF09PTkxLkX1vanaAipFMTlBRUVFSUVFRhFJNUVFRUFFRUE9PTk1NTUxLSZCMh352cGdbWmZ0hY2NjklMTU9PT1FSU1NSTIVQUlNTVlZWW2ItQCNCOzk5OTo6Ozw9QEJERkklJyosLzKAzNnqhp6708ymxu+J0OCFSUVDQTgsQT1EUmp/pqWGdXJqWVFKPVZWLzIzMV9aVlJGOWNOSE1ZMTdASE9WZnmUwIK4gKvb/IiQl52ho6WlpqajmeaSl+K7ooPBvuyjjp/Isri8vaWXpKa6samwp9LhoJqbtcurmo2RqY2cpIi3tcOAlfPtp4K67r6owNWRzIrIgoHwirbFxJGgzu2B+qLL5tmY6pXXx5CopZ+WjP3iwqD8wZHbrY7/7ubdbGppZl+zz2RuQEd4SEtXVU0/WEtLjIGUvYFbgsfy/YGCgISGh4eJi4yOj5CJzfvG2b+9o5aTVGmAiomJipvOzZTe6M2Bhe2AyKuS+dG939e8nfjQuZeMm5BeidKYHzCDc7/Oi5ipk4nHyJ+Ig4ugpbDE2u6Ty464kOqisqCRhdOV5omm++/Zy9HPopmK4aH5i6WKu+mZft3Hwru+xZHPg6qrq6Wws7jBoqObib+LtKaE/8yOyYb6qbKzloeM9tung6HCjuqLoYqAi4aB+PHv+76C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S1QzSktKTzZYX1xwSFl7fYiBeXBlW6GOfGhZnpdecHuBhYeEfmNlVVtIRTs4XE1KR6BefG1xZ2txVlVYqYJrYm1gYUpQV4ZRTIRxbnBzgHVrS3JrZoZhcXxFRYJ+d1NOQnBfXl5bb3R1eXxaenaPZEdXe1d6YnBcV4pxRIdISUpIRXlhZWtynYh8a35lgkdRdpiicoGBf3Fta45VbL5wdnx7aFBne29scoF7WquuZ2dLZ4hOe3dPdL5hb3hEhYmIgXxobGRphX2ERX9/fmhmgK5afUGBdWh0g3p2pU1/g0WHgoKOha5dbHB8g4NDRoV4uoBolHpFR0dGRYZbXWdTgXiFdnNKTW5STVxtvp6OmldaY0xkanp4dnt+gXqeamx9ZIFGSUdHRUOAW2hhcUuEUVlUTlRgcGJfU1xSUlNUd4pPZ1FST1NYclpOYV1mZmFae2VnU39cWlFJUGxZeVVTW2iBTk9nbkVue3t2bpZlboKas2VxfIaMZmNMa4F6ZV87Q0dMgk9UTTw9VjdYRDI4TGeglJNmh4KAfWEzNTItKCRCPTo3NjY1NDQzMjIxMTEyMzU4PUMjKC9nUE1MTE1OnEVDODBSOnVSUU9OUIRRGlBQT05PT09MSktJgnNqZ256hIqRSktMTU5OhU+ETkdNTEtKkpCLhn53b2ZdVlpkcoKNSUxPT05PTk1PUVFRUlFSU1JRUFFUVVNeNi8gIyMjIiIjQz05Ojs7PD5BREdLJykrLzQ5P4CDi5e11cuUraz7m+5NSEZEPzJKPkBRbJPYrb6P84zKkn5nU4NgT1YwMzFfUEA2Z2g/PkZYNDxDSlBUVlljcoaj2Yy46pKrwdDd5+/2+Prko7uWy8qvta+Vqa2t1I3lg6amqKqajIuXl56HlKScopKZk5ShrsKKgoqQepefhvubmYCR7LLGtbPcrOzns4jE+7b5793/sLrdtpi82uCrjsXg2MSWvbaaiaz/zP7038iqjeKwitavl4uBfH1+fXx7dW1+aHFETZReNDw7ODNIZJCMip/RlmyV8YmMjIyLi4qKiomKiouMjY+QkY3Xgef4vaabkIxVb4SJiYqM2Y72oJ2e9oCG3KyG3Pv926f0y7OimNahs07peGFKNNK0ydSdgmBtgp6cvrGrsLW4w9np+KrXsNuwi+SovqW/hv/wreWD5Laqys+wrqKJxL7f0Zzl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hYV0lJarPDInLFJAN0xPNEdCNVc1O1U/S0hZT1RXLlsxT1Q0SElNRFdNW1pvSFx9gIeAeG9lW6COe2iznZdfcHuBhYaGfmRlVVhIQzo2W0tJR0FbilNwbml2b31Ug7lwZ2dpYVxdfXVwVHB1cWxrboBydmWCamBTgmh0g4WAdGBTTG2KjI10WW51eX9/ZYV8kYlVepCdW5d3i5SCdl9xSEpKSIRycW2aY2WCfFRtUnB4fn2TqoGHhH1wbGNRaVprdn6Bdp1IcGZna317aZigrV1lZkdHfHBhrbvEbXiHg4eFf3tqaLxzgXaFRXqCeGdolYBre4J7bmd1eW1heGx/g4R/e4RpxLRnXH2Fh4hHRVGxlXCCc4VGRkZFQ1mnZ2ZWaX1vak1mWkmMWmOvmIWSUVpeR0l1e3dzeH1yU2doblJ2REZIS0pEg2BuX2R7YXuERUpLU15keluFYlhRVFJhhVJXTllRTVhgjVBXVGNmZVxienxSbF5fWVNOdVNpVFFcXV9PUE1ncIFue3t3bpdlbYKZsmVwfIaMaGBLQ3Y6ZzdfREdNfk5TTjxAVzlWMC41M0FclpKXfoN+fX08PTgwKSQhPjo3NjU1NDQzMjIxMTEyMzU3O0IjJy9FT5qYTE1NmUdANCtLREBdaFBQTk1QhVE5UE9QT09PTk5MS0tKiH51cGxscHN3fYCDhYaHh4iIiIeFgn53bmZeV1ZcZW98iIuLjExWY3xtWlVShVEpUlFPT05RU1JSazYsICIhIEFAQEFDQyIjQzw7PD1AQ0gmKCsvMjlCJSuAu83JqOSCj87Pb01KSEU3VEBAREpkzsfWoL+/urmnl3aVdFyOZFBVL1tFPk5sVndIMC5NPE8uNTY3NjRmaXB/lbLdiKfM7YWUoquxpefe2/6di7qLq66jbnOknsHwt9iJn5+fh4WCi42OeY+lmZPxio2QmPLtiYSFgW+TnoPago2AjOqpnpmt0KPAu6eAt+Cn5t7N9Ke2yOPBt8zL+eW72dP+hL68toyShfCPsY6mlfrJnv/QsZyQiYqHiIeIhYJ9imtyRlOyYzpEQ0I8WHWMfYSncql5poeYm5ubmZeVkpGQjo2Ni4uLjIyLiIT7xfPT0KqSiouQW3aKjY2OlrS+j+GA77em+4GQ/qzvv6WSyc6sjOY7y/atxGfendHJipZ4yp6bnN3dzs3R1pXw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Nzk6Oz0/QUNGSUwnKSorLC4vMTQ4PScZQIKEhIKnkptlJyhBOSo8NktLM0ZAMVIyPVE7SUw1L1JULFsvL1MzR45MO1dVRlhqRViCgIeAeG9lWqGMeWiynJdicHyBhYeHf2RkU1xHQDg1WUpJR35XnlFoeIB4eHdlY1V1m2xeZVdGdGNIdmptUWxxbmxrb3d0VWdLS3pUZGBncIKIiYqMjUhIhF9qeYB+gHGMgJqWfmVvZKltV3uHiMHtVEdIR4KFV0poeGdgboJggXl5Y3aBjGmEhoBxaWqCWWPCdJexirdobGZnbXZ8coWXoIBzZFB8elV1wYDAxG58hIGEgYB5ZL+xeYB4g4V2g2xnZZF1f39zZ2l1bWuejnt+h4N+fX2AZ2OEfoVFh0ZJibm8nnNrfYaHRESDmrKHaE11Z2hkU2VneUZdXaSSh09RWlRSXnt8dHN2cWF6ZGt/aoVIR0hMSYhqdVdgdFt5REVEQ0OCgXN3g1xfWWpSU1Zag1pSQ1teV1xYUV5SRWFnZV1gS1FgV2FaVFFAUFxUVVhfYERrTYFfcHhtfHt3b5llbYKasWRxfIWMamBLR21cZl1ZRIxPgE9RTTtAVjhWVy8tNi44o5GPXIJ+e3lgOj0xKiUiPzs4hDYFNDQ1NTSFNSE3Oj8jJi5dUJuaTE2aoEc5LUtEQD8+PjKAUE9PUFBQUVGHUBNPT05OT01NTU5PTY+Gf3h0b2xphGdJZmdnZ2xucnqDi42MjY2OSUtNUFVpq8Kbd1pVUlJRTk5OTU9QUE9PXDQrMTQ1NTY1NTU2ODo9QURHR0A8PUBFTSovMzg+SSw1PoDesb28gqLtUExMSkAxR0NJVmSp1OioxcrQ0czBvLSeolqHZZluUlZdbDU2N0pZhyMwMDQuPCMnKSZIQT07O3+LobnWgJSqwdXbqMXxqq2lmJSQ34Wjo5R/l5+Ys8+Pqd6WmZf4cfP+hYJvjKWSimuEjI6P1dWGg4fwaY2Vgch3h4CF156PkanRl8DHp4CkoJvSzrzuobbC1Le4v7/jybLOyt7evb2ywtakqayg5r7srrSS78qzo5mTkJCPkJCOjYmWbjpKV8I4PUdHRUMxeotvdKt8vYpfl6mpp6aloZ+dnJmXlJKQjYyLioiEgfvz7OS13q+ymo6Ljk5ngZGRkZPbjoD5qI7Ajoig0KuBj9WRlJqEsDRu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sK2lopiMh4xQX0xOS09QUlQzSC0/Z3NkWDtcLCUlMTE7KkE2MTMtTz8+PTw6O3aAdXRzc3JxcG9vb25sbGxramppaGhnZ2ZmaG57Rk5YMjhARk0oTI+BgoJqj5KmOTJAMS9NOjNFSTJFPl5NTk9POEk8Ui9QUlhbLjNHLUWMSzlYRWNWaURYh4GGgHhuZFqgjHhmsJyVYnB8goWHhn9lZKJWRUA5a1hJR0Y+P01qdXCAgYF4b1JTUKdaTXdoamptb1yGcmltU2VnbG1vd3tseWlgWniFiIqIioyNj4yLjo+MaWp9Q0OEfFCKpFd9T0hjUktGeH2MkGlDRoNJXYRugGuRjolse4CDdXuQdoWfdoF8eGlpXUxqtGqCl4ZuUmd0bW5yfHeGfZugeGdWbnhuXcOAtrtsd31/gHp8aryznnx5eYF7dIJiYbBTd357bGhrb8dkfWeCgESAeHxghm9rcICFgkNJjJdtgWtheHt9goB/jZOGZU54fU5hVGJgZpVbuZ2MilFVZFNsaHp7cnVyZExnZW5UfohJSEtLRXdIW1xsUnRDSEhFQ4N5WnNkd0xFWFKAVFZRcFpSaVxkYFxaTFBRb15kZF9dX1NXTF5aV1NLUVNKVldgYUpcc3B5flh0bX18eG+bZWyBmLFkcXyGjW1hTEo6V29hW0OKT31PT0w4P1M3VlEyL0A3TGKVjpF1fnt5e0QfMywoJEI9Ojo7PDs7Ozo4ODg5OTs8PT9AIiUqOFA1TUxMTUypO1pJR0pNT09MSEU7UGBPT09OT09QUVFQUVFQUFBPT1BRUlRaX1tVUU1MTEtLSkqESSxIkZCQj4+RSktLTExNTU5QU1mKuZt1i1tUUE5MTk9PUFFRUVBQT1JxXUQrL4UwFjEyNDg+QkdLRTw/RSgvOD8jKDA5OSuApqPj7YJOTUtGOFNHTFRhk7LdscrN0NPW2dPIuaukmKxnmnGlb1JYW1M8SSVDQWwpNjFJPyYuMzMvLCdIQkBAhJCfrMTg+uObzsTp3Oav04qGgMjzlpaHaIKSjqW15IS3ipGN3b3h8vnzxoafjoXJ64WIi2bUgf+JfGWAjYHDgYc6/c2en+ekzqOz8tSDjruRwr2r7qCvkJytsLm9i9ymxsXb1ry+s8LQnqSrmd6G9KWimc/Gr6WemJeXl4SYgJWhcTtLWGs7P0pKSEYzgY1wcaqF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fX2nxsjDvrq3sqqdkoxlJjYeODQvbeaslv/JmL67ZmPh5aWaqJWJj2mnqbC8zN71iJuBSRwaJ0A8LCUtJyY3QUks5b2C0EBCLC8yNUhcpIt82rOklZKal4+JlIV3YXRLSEtNToJOO2M3VzNET0NGQzA6O04rQzoyJTw1Ozo3OYRzgHJxcXBvbk9raGtramppaGdmYFKTpGJkY2RlZmhtcnZ3eXt9fX18fb2FiI6YVFdTNkE6MVI8N0E8M01DP1JGNjZQUVZJTlJdV0w/R0eHRjRWUkI4aT9TRYKFf3dtZLOei3dlrZqUYnF9g4aHhn9kZZ5Mh350aFiZdmliraFgX09pgHd7c1d1W0h0b2xLXV1YVFVbYWRVgHBshGd7eXRvdIOMjImKi4yMjIuMkZKPjIqNj5GFYnSBQ4SEXIeOcJNKRYGDgYRsaWy1i2xGWFyKe3loW6eMfXl4iXx7VHGDnHeFf3BnaFtMcbpuf4WJZ3d2d3h5eHlTrmWqdmpiZUtRdL2zgLZne3t6fHd0raeoWn15fn9ydmuxrol7gX50amlur698YoOCioN7faN2W3d5gX58gkZNvWZoj3N1c3ZzcnWzc2RPfH5zbUleVVekX62fm6BUX2FVWHF7dnBycVNpZG5WfH54eIKFg2Z3ZGtLbINJSEWJhXlYa19oR25FRUNAgH18gEJSfUlCXmNfW1hUVVNKXGJfWmd3U3FZXVhWU1lIbVpXXWJXUVRVcG9ua3iddX59eHGdY2t/l69kcHuFjHBaSkpIXURaV0KHTX1MTUhsPFNqU04xSkREMypsmYmNbX16d3omITQqJiUjIT87NjIuKyopKistMDQ5PUIjJykqIVCbHZiYTJdhLzQyLywpTUhDPzw5Nzc4ODkzVUBWT09QhE8NUFBSUlNXY7KAvp1aU4RRAVCET4JQik+FUDdRU1VYXFhUUlNNTk5OT09QTVw1dFFQUFBPT1BQUFaLOEorLC0uMDQ7RU4pSTpFMUovLjg+UzyQgFBNS0c4UUVLVGKF6NSKl6WyvsfO0dLPyb2uoJeOiYivhWWRpGhCRzYzOjs7NFarLTgyMyYqLzMzMzAsKUtFQT8+cZvDaJG/vKmWy7fBVGL67uAtcP3/5C9z//qElZ61hIGFgGErztnnci53iYJ8VtPx+YZfw4L49oVx/4KCbH6AgOLBk8ion8Silo+6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ezY7PT4+PT08Ozs5OTg4NzUzMS8tKygmI0RCQD8/QUVGRkRDQyM/K23Bzml+b3mcwMzIw8C9rGAxLDoxKSQfOizcqJR8LkdgYlOewrisgbSLZnGGbsHO3/OEk6O6gyw3NVaTLzslKx0gUkA2O1SI1JgyUywsUk1FZKyG8b+toYaPhpOMi4iBe4RwR0lKS0qBOjs2OjlSMzozP0M4QzVILUgxNiInOjk2N3BwhHEHcG9ubm1rY4RqgGloZ2ZmYlCAnF5jZGVlZ2twc3V2d3l6eXl5cYOAhYqQlGFTQVU5X0c1NTgyRTtnP09BM0NLUFBHSk1bVUlMNEeFRTRTVD8tbj9YiX2EfndtY7KcindkrpiRYnF9g4aGhn9jZZheRT1qallGTFldXLClmJNrZVV6enhtYFdHcGppIEhbWlNUXWdqa1iAd3JOc3t3cG15io2KiImKjo6RlJWThZGAk4pjboJGRIZigHxdhEWGfoB/g2NcYHZtREaQXodbU22wcX97eX2Gd3h8dIBbgoB0bWtuamW/ZneEiHlYbXh2dnR5X5R1pmtoY2dfaXOwrq1qfX97e3Z3qaGXXnt8gn9wdl+lnlB/gnxvamxmrp59e4eKi4F9mGZnbmJ7fnl7iIaAgYKNfnB9c3Rxa3KAfWORfH9yamFTUlCkvaOPkVJTXVxqZHN0bW1uZIZgaoVqfXx3gIN7b4llaYJohEVFh4aEeVhoXWlHbUNEhX58gIBsSmhxgF5YYV5eVlOHU3xaX15cW45OX1FbV1JPZoBKb2VkZWVUTVdvdHhDeIttdH99eHE9nmNqfpawZHB7hYxxWklHSEo/WVZBgUt7SktIaDxQaFBLMUg7Pz43RV2OjJ17end1azsxLiwoJCE7NC8rKIVNPCcpKy80OyInLDAuUJpMTEyWcjI1Mi4qTEQ/Ojc1MzIyMzQ1OT5GJ01FOUpYUU9RUVBRUlVgi7KHZVhUUoVRBFBRUFGEUINPhE4OTU1PUFFRVGI3Oi40PF2ETipPT1tNOzo5MEVaXlBPUFCTjH50b2BcQiosLzM4RFBOPzRINzE3TGd/dHaATEQySUpXYXiFoe+Ah46aprO+w8bHxL+2q5+SjIWA/qyTc5mkXDlCPTc/QkA4LsUrPDQxJCcrLS8vLCknSUU/ZYKymIUumrCyrCjDqrNMWu/ez2KD8OvYOozm1fKKiZp17fX2VD7J1d9rQtP89XoVM+TxgWBg//ffcFqAg4CokIyA07yLj4mXvJq925fi8uPvn5yE35egwJyVnau/qOCcrbiKgrGzrdyGnZmiwNGN+oGbroT06eCm2rGXqrO8wsnP3IJGUXOzQkNOTk5KNYpWhn7EqY/OiKjByc3OysfGwsC+vbu5tbCspp+ZlIyFgPfu5+Tj5/H19/L1+oPjhLKko1GAUlh1k56dnJuakaOcuvfOrJmL+ItpHiJtqLOUgo/Sd5agcGdGpMHZicrP191yeHt/h4eZprXhs8KQwKiL+o+HoImWoHaS+YWE8NOzypmD+PHo18/ei93Rxb2tnKHpgo+Mi4z4gZKQkZHQgJ+kxsGp99Dihu6xuqeNnpGUhPvx6eOA3tjSzcnEwb2strazr6yqqKWinYDF04WSk5KQkJKRkZGPjo2Lh4OAd46NmK7G4a7EndaQ7rSIio+DwZH+qLyxjrPOx+GeydWs5cGwha3drIG4vqyDuDBz/4SHgnp0bMa3qJZ/38q7QUVLTEtHRT4vOnhHLixQT0M+TWZxePDgsYyAX085Pzs6MyspITQwLSApKSYmKS4wMCw/OzgnODw7ODc+R0hEREdHS0tOTUxKTU5NTk5OSjQ5SSgmTDVARD1QJ0tISklMNTc/OjkrK10zTjsyPlo5TkxMSk9ISkI6QjVLS0dBPT87O2E9UWNnUy89RERISEY2UD1RQ0M7OTE6QEqASUY0QkM/QUJBTUdCMEA/Q0I3OixIRyg/QT01MjUtR0Q+PUJERj88TS8xOjI8PTg5Pj5pc2BEOjo2NzUzO11JMUc4OjIuMCgnK2J8bVlRLSs5NjUpLzEqLTAuRTEwPi0yMjEzNTAsPC4zPiwyGxs0MjAsIy8oLCAxHBw1MzEzNCuAIDU9TzwxNjU0MDRgPFY2NzY2NmtBTjQ0MS4uSGk8T0A9PkQ4ND1GRkcmQEo3NTw9PTpWa3yWssZtdH2EiHJpM6HFs4ytoYLrntWfkqDoicTtzKWHuZG3xbzHpopfXUlHSEvz1bm7taKTgt67oZGC+fn5+/yBh5KfttiDosv62DgiYzAwMGC6jpaUjILu2sm6r6iinZ6hpa270vGE/eSvq1c8MoUwCDE0P0Q1MzEwhi8BLo4vDjExMjQ5PUSBwP7Owb90hDEqMjNs8M/Lw53T4XM7ODc2ZFgpNTGR3L2A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Q0NNUCglSk1MPyZGS05wdER/g3R+RYaDeFd7aaGUkaqpp6OnUV+HWopwkFJSnIpgoFyLdJ+IfGB/Mjc5Ozs8PDw7PDw7Ozo5ODc0Mi8tKiclR0VDQkJEQ0JAPTw8Oz0+Oky7x3/N1G90i7PPzMjFclAzKyg3NCwlIGW1z10VGVV+Yp3jrYVYfMD7T2qEgvOFlKa5zoB5p09SYTs9RiQgRkFKXU4wT0hXQl9WTI+Xn1JSXpDhycOpiYiDgH52hIWBeKBNSEdJhERFNzVFWThgVDs1PC1DRihIPjwhFCQ1MzNtbW5uhG+Abm5tbWxra2pqamloZ2dnZWRhYmFjZGVnam5xc3R1dnd4dXV0p3Z8goaMfkU5RVJnYkNjYzJdMVlPOEs1bkFITktFSUlaUkeHREWERGJPUT8wcUFuUFl+fnZtY7GciXdkrJiTY3F9g4WGhn9kZJNPQHNmZlhGS0g+dHFxbXZhemCAdXRyd3htXldJcWpojFlcXGNscHN0XoZ8eVR0dnBqbHyHh4eIi42OjpGSkZGRkJBJSpFreoRHRoloe26nhUaGf398Y3RqpqJ6h2VtfYZ3jW6va317d4V3c2Vvbn52hXxwbWxUVGrEdoeMiVVYd3Byc3lqTYCfZmtjcFpzcqulo2mAe396fXd2nZiOZn1/hXtveqCah2CAfnlra26spX9de32DgXuJaYiNkX6BfXuDQ2N1p25pf3l7fHRxcI1fjX2Ccm1hVkxJp7OgioqhV15PhW51cGlpaVVpZnJWcnuBgYOEe1Rqb3tggEZGhIODe1ZyZm9McIGCf318gH1hgGZrg15sd3yCS1ZXcH5RYVZcYF9eRYFMen9qVlNKg0SCRVFLXGRLbmNNd1VkPppudX9+eXGfYWh+la9kcHuFjHNZSFE+WXJZVH5/SndISUZkO01mS0NdRkU6SCovPKKKjGZ7d3V7RD05MCdBNi8rKCdKhUg8SUsnKCsuMzoiKhlPmZlMS5RyUVJOSEM/PDk3NTMzMjEyMjM2OT4kLTojKzEmclFQUFBRUlVZYFtWU1FQiE8DUFBPhU4bT09OTk9SeTEnOSkcJikjOVdNTk1OTk1HMjo5hDghNzUrPUhTT3+XYmptTk1iV0IrLzU/Rz4yMitFWnZ0dnZjgFpIUFtu2sK56fP9hYyUoKm0vL69urWupZySiIL27OWlo4CkoE8zQkA7QkVDOS+/MEI3Lj8iJigqKikoJUY0PJdwjI+FNXqLk5E3tp2p5ovf18BYTOTczGeHybjZ/HeEy8La35SgwM3XZTHJ+vHldbDf6u8rLvjx7GlS9v6BpGZ3gNe8iYWDlbWSorKQ1JLb4ZeV9tGRnLmYkZutw67llKqykIijpqXgipaWm7fIjPX6pMGF+O6Vh922/KihwtPh6/dHXo9i2ERFJyhPSjWOXpON2LOaaoydsbq+wMC/wL/CwsHBwb26tq+oopuSi4T+9O3q6uzr6OXg3dzd4ubQxKqlBqWmVFdoiYSggKjMoZ2h5Nq4nYXwUU5ymr2n16bJlYqap41e0o+lx4zgd4GNm6trn+uCo5WJw/eSjvzayfHlgq+XloTgtITW4/iFh49/6t/a1tTRyMnRw760o6DTl4GPhvKAqYuSs/CW9/rMo7iI7P+D6MvRooiEkY2E9u3m4NvWz8vHxcK/vLq4gLWzsK2rqKWjoJySj4yRlJORkpKRkZGQjoyJgX58tYONm6/CvZKIpMb57qLy/YDlgcOji6+K7au9tsuTw8Oo2LjxsKfWqvmuuq+QvzFxT1yBgHpza8W2ppSB4MW4QUVLTUpIRj0vOXU5LFJMTkM5PDgzY15dZ3dFQy04NjQ3OTQsgCohLi8tPioqJyoxNDc5LT48Pyw7Ozo4N0FGRkdJTExLS0tMTU5PT04nJ007REwpKE86PT1kSihOS0tHNkw+X2hLUzxITlhRUUBcOVFQS1VNSzw7PU1DTk1EQUE1MT1zVW50cDIvQ0RISEk9M0NPPkU+RDJDQUlIRzVBQkJEQUFHgEhBNkJDSEI4QUlDQTRBPjs2NTdIQT0wPUBEQz1CLj1HSj4/PDo9HktteT43QDk6OjU5YXAyRzc3MC4qLiQpbHpjU05UKzgxPSwyMCwrKyk4NDQkMDM0NDU1MSQxMzYoNxsbMjIzMCMwLy4gLDMzMzIyMzIqOzA1QSwzLzMkLi9CgFo5RTIzNTU0LGpAV05EOjIwXC1BIywpMjwvRzYoQS00IVM2Nj0+PTpXbHyWssVsdHyDiHNtMcmb6d2ol/vgmNGci5nVg7bewI35r6uWxYygmNZrVjpHRkdl3eDhyaL7w6GRh4H27/Hx8vP2/IOIkZ2y2JHZiTdjYTAwYMXr8OndKdDHvrWvqKWinp2foqexwNmBpuOZy+qwyzIw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dYhpdXVZb2hye3JKgJmQhHxri5KaLyqkm6QyN6qlVWtcqoCHbVJvZ1BbSGZwRl1MYksxMVlZQUpIMjJFT1k2JEJKTisnSEtKRCdDSU1xd0R/hHh+RYmEe2l9eYuNf5CQqVRWw2VuR4V0kFNTnophnmKSeqaPgWKDMDQ1Njc3ODg4OTo6Ojs6OTg3NTIwLSonJUdFRERCQD06ODY1NDQsO5ydrYC7xc3W2W5+p83RzstyTjMpKTg0KzosimYTCSZBYdOI83ZYaK+RdVduk7Oov9jxifGhvOxVVjhATShRSjpHODxCPm2LxN56g4yUm6BSkX/w3N2pgIiNh4B5eIF+gYBOfUZIdEU6PTw9Pjs7STtFRyo8NEknKCwXISsuWmJkZmdpagRqamtrh2qAaWhoZ2dmZmVlZWZlZWVmaWtucHFydHV1dHFxZXhyen6Ch2RmOkNQWF0+XUVkWzhYSmdHWlQ+RUpHQUdFWFBFU0ZDg0FiTlE9YlV8U0tLanp1bGKwmop2YqqVkWJwfIOFhoV/Y2SSTjxuYGNXS05CO3Jzc2NdV2NQX1BtcnR1dWyAYlxMdmxliV1kaG54g0hIaJJ9f1tyc2xocoKHiYmKjY2Nj5BJSUmQgWpYZHp+Q0pHiG14Z6J0SER8VVB+XWJnp06Nf0hJf254eLZ0fnp9fmp6jm1wVYCBeXJvbFpqYXKNlpZmgXFxam93dF+PnWRwZH13dHKkoqdpe394fnd0i42AiHB6foR3b3GPjYF0g35zam1nnZZ0cm5udHV3dnyibnOCg3x/QomxXmRcd3p/iIN/Y4lbh3qAc21iV0SBqrKbiIhQWV1MUHFzbGhnZYhfZ4FqeoCEhYWEaHptdVN6R0iGhUN+WXp0fFJvfn15dnR2dVJoZWtHYnl7fn1zbGxyiHRkTFBYXV1XU02FU2xcWUxMSkZEWHOEZ3uJYGVbaV2CYkqTbXWAf3lyoWFnfZWuY3B7hYx0W0pNb1RvWFB8eEh4RkVDYHRKY0ZCWUNERDpYOk9kjYaRd3Z1dDxDMyM3MCsoJ0xJR4ZGW0hJS00oKSssLDNPTk5OTJRmPD9APz48Ozk4NzY1NDMzMjM0Njg8Qyg1JSAnXFFQUFFMiYaQU1RSUlBRTlJSUFBOTk5PTk9SW2w/S1o3OD9NMTskKzQfJiY3WFCFTQacODg5ODiENww4ODg5OTQoNmpVUlKETxFRY1pFLTIvUD5QdHZ4dFuCbYBTZouDmdHf5vH7g4qSmqSpr7a3trGpoJaNhfvt3sy+mFiLq09MM0NCPURFRDoxsTMjOVU6PSEkJScmJDc6saWwcrO62cevi8indMKDtU5XjqmwsKjf18Rggs+31utmcrXC1NGMlbXE0rdSyPnr1LSk2uLsGxTu5e0ZFff6gatU2oDet4F6gZCsipSbhbyazNiQkOLFjZ67nJWZrMfLgpOhrKKVnaGg9YyPkpi5yoXz+63ChPjsnani0+rJobfB/YmQ0WlnOtlFRigoUEk1lGqjnOrGoG6Ok6Ooq6+xsLGytrm6u7+/v764sKqjmY6GgPfx7u3n39fQysO/ureQjsikqICprbGysFdhgp+lp6uUvpuSpejVre+um5OjrcSByMxVsbDywn5Jr4eJvaSGm7TOd5Ftf9eT46S734e4mLXgpdHMg+h/0f2VrcTY6/2F7anp3VvKxcvGwcO6q6CUkpaj3oqC0ISZopOhv7Kk8MfM1YXcseWHjLaBvYGB9+Lb1M/Mx4DDv7y5t7a0sbCvrquqqKajoZ+dnJqXlJWUk5KTk5KTkpKQjId/fG+EgpGbscCw54aautPhktyg9dyLu6D8otrHn7GxvIm0t6bPrZewo86j8aW3qe9wVzovN299eXJqxLSllIDhxbdBRUtNSkdEPi84dTUqT0dLQjw/NjJjW1xRToBJSzQxKDc4NzU2My4rIzQxLT0qKiwzOjsfITBBPUMxOzw4NjpDRkdHSEhLSktLJygoT0c7MT1JSycrKVA8OzVaQyknRS0vTDU4RmkvYFYwL1ZLRkJcRFFOU1ZIT089RDVOT0tERUQ3QzxTeoiHTEVFSERKTEU2Sk44SD5LREVDS4BERjZFR0RGQ0I5Pz88QURIQT0/QkE/QEJAOjc6Mj89OT44Nz09Pjc3TTk5PT47PB9Nj0o1Mzw6OT88O01yMkE4OjMxKygnRmVuXk9HJy81LSYuLy0qKio9LDA6KzAzNjU1Myw5LzQlLxobMzMaNCk1MTYiLTIwLSstLy8iLi4yIIAoLzIxLi0sKzNMSTcwMTQ0MTI5XzQ+MTMpKzIwKzJFSTdIXEM5M0A0SDgpUDY1PD09O1lpepWwxGtze4KHc3EysvPk0qSO79ORz5eDkM/5qtGrgeejo6mZ/cT4xIpaV0NERUiI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MS8tLCxWVVFNSEJ0X4aiU15Pbnt2i5OSf16PZERwfWRwgI6WTXdZfoRlWVdohplLnphzW3Z2YWdSTn+EY2xfj11waS5Bdmx2d2RqgXJ6cVFxaXB6bX17mpKIMm+TlJspaqKfozk1V6WjepukgIprnV60T1hNaHCFUklaSjAwVldASk45OUZQXT4pQUZNMStGRUYlJUFHS3J6Q36CdnxFh4R9Z318j3VreZCTTVlYXGxLiHWQU1SejWKcZ5x/q5OCZIYuMTIyMzQ0NDU1NjY2Nzc4ODc2MzEvLCkmSUdFQj87ODY0Mi9RPltNkpCWgJ2paEvR09nfd53I1NHPcEwyKSg3NCoaIwgIFDIucaShWBwclNGbamKOtYT0iJq3gprx3F82PEOklY92PTAnJ0RNdarY5HmCipGWnKBQZonCq5aHiIqGgH55dIB/gplqi0VYWkZQUT8/VTYtLElJOic7LlE6Jy0yVE5WWV1fYWNlB2ZnZ2hoaGeEaApnZ2ZmZmVlZGVlhWaAZ2hpaWprbW5xcG9sinJzeHt+hHdvOUFLSVk7TmlcXzddS2BEOFM7QklDP0RCWExDWGJCf0BhS1BdVWdYYkVCSn5za2Gum4l3ZKuWkmJwfYOFhoV+ZGSPSzpqYGFVS0g/PHVzcWJgW2lrcnVYhmRybWZoZmJdUHdpZYheanNFS34NS4dWfXCFZHFtamx9iYSMgI6MhYKCg4FxiG50V3uEhkpKRnZ2Wn9ei1pYX36HU19feneGREZFd2Fuhqx6gHd3cGttb2jXcYF9dHBwlFpqbZOioYRecHFqbHh2bY+cXXRkhYl0ca6jn2d+gXyAdGuFg3t1ent/cHNigoOOgIJ6bmpzWJh/TXRwc3h0eWeXoleDgIWCfYd+mnNmTnp3fIiEglp5W3x3f3VtYV9EfKmvmYdSTVZcVWB0cmpoaV9vW2hQc4CEiY6MfZVodUxuQkWFg4eDXXl9SmtHR0Fza2pza0hTTF9IYWpqa2ZdXV5QcFBXf3BVTXhZY4yNXFRQU05UV4x2XEJgVESEkmFsX2FXVj5KgHBed4GAe3KjYmZ8la1jb3qEjHVbSWxiRXBWS3V1RXdFh0Jbb0VcQn9TP0E/NEY4Mjibg4JadXNzelIvLSspTkpGQTw4NDIwLlpXVFFQTk1LSkhDZplOZWpPmKNsc3h8foBBQURHTlleVVFSVlovMTU6PUJHJEtSUE9PT4ZcWV2HhFEsTy0fMidAOTUzMzQ3PEMlJyouMzc1MDM9KTUiKyMoRU5MTEtMTF8nODc3NzaENyE4ODk6PD0/QkQ6KVJUU1JSj4WOW3ZfNpN0dnlwVXlubGiA0cvC94CFgPj8goeNlJuiqK2ysa+onpKIgfLfy7imlIxelLlSTDZFQz1ERkQ6Mqg2IztPMzc7P0QjODVXrI2FiYrI5oDM6LOQxpahqIuEvd+itnvXpcm5EzPQwtTbXWilwtPFg4yyvMuxp8T0588soN7g4A155eTpMBR08vKzktGA2673ad2Noo6Slfimj7bQiYrXuoadx6mjlarR+J6PmKe/r5iWlYqIiY6St8mD7fmqv4Lw6qKp3NDvhcHR+OmKpEpNWjrURUUoKFBLNph2van4z6ZwkImVmpyfoaKipKerrK60tba5trKtp6CUioH38u7m2s/FvLKomfqgtoPWx8eAxMivtOy/wLxefJ+usLSFs5uOmt7X4YB3gOXznKGFcnmMwZuvXHKveHLHa6xne5Y9VHG6o4yus/D8n8D297T756H6t+f6j6W6z+Lx/YChjMrAvLzAwbSxsaqflpKXtr3yiKC6vs3OtrLhn4mK9+bIgL6S5suQv8Xo5sjCvLq4trKAsbCuraurqKelpKShn52cmpeWk5KRj46OjYyLi4mIiImKiYeEf3mfgoiUnrLA9v2Ck6ymyoiy993UhL+Y7JiLupanqLKDqqmiw6Wi65zCnOqdsM5fcEtCKyc2hHdwaMOzo5F+2sK4QUVLTEpHQz0uOHY0J0lDRUI8OzUyX1tdUU6ATVlZXKBgRi4zMzIyMCwqJDcyMj4pLzUiJT4rTitDO0c0Ojk2N0BGR0dHSk1NSUdHRUVAU0FIOExRUiwsKUU+Mk0rRi0xNVBYNDlDWlZeLzIxVURDTWJUVlJUTUpIPzyERVFQSUdHZjtBTo+joXY3REtLTlFMQURINE5CVVJGRk2AQj40SEtGR0U9Njg9QEJDRUBCNEBAUERDQDo3PCg9OStBPT4/PD4zRlUvQD49Oz04aU8zKDw7PT87OjdaMkQ1OjUxLCogQ2psWUUwKjI1KycvLywrLCk0LzUjMDQ1Nzs5ND4wMyAwHBw1NTg2KjU0ISwbGhcsKScpKR0iIywjKSmAKCsqKCYnITElL1NHMC5KN0JeYjsu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eoSLd1hKZEpZbFeNbXJBdUKGflVoQVY+fk07cz45ZjlDOZ2Be31scG9tikqOgXBfU1CbmZaPeGppdo+RkI52b36TlJWWjYORsY+KS0xMTU2KjE1PUVNvblCGWFRRhFAQVFtlbnJUUE9PUE9PTJGXT4RQUU8sMShFPjo3NjU1Njk8QSMoLjM4PUA7OyY5Kh89bk2Yl5eXlkkwNzY2NTU1Njc3Nzg6Oz0/QUQkJysvMzAjJmFThlFnbXF0dnlrT3Nta2hSU4DE1Nvo9oGEh4WGiY6VnKKmq62ppJqOgvPfyLekkoLoiGSeZFhSN0lFPkVHRjwzZVYxIEwwMzY4Mjh2sBJdi3a6kMF0inN1q4xyF5mfipfOhfGL1vzxvsMSXcO80dJWYJ29ysCAFaq4x7N1z+7gy6+g2dnosH/k5eqYfOnu5Lh5yoDXrO5lz4idiZSR6qOJp8WEgcKv/5nazsyTpNmw15GVqv/lm5KOlfSFioi2yoDr7qe+geznp67b0O6XkOPRsbm6S01ZOdFFQycnUEs2l3/NtIHTqXOUhY+RkpWWlpaYmZudoKOkpaenpqKfnJSIgPPt49fIvbKml4TWpoPez8/S6IDIu4iUgZPLxr29vXadtbm8/K6cjKLf3JtrbNyvnfV+lbKBgKiLT21KO37RlW2EVTyCUnyai6eThZiF7vXmi52zrJyE+YCNoLPF1+Xw9viTx7Owtbq2pKutqJ6TlZWd+KHhgZKoyMme1fSCjIiCsYK634qJjviUxt2vopiWkYuIg4CBgIH++/v5/fyBgoOEhYmMjo+RkJCOjYyKiYaDgoD69O7q5dC0y/J+hZCcrZbc/P2MoIfEgo72lM/7s43g2J6wiZugpPifnp+5nqyViMOV45TlgVRfaEc4IyY/m25owbKhj3zdw7ZAREtMSkZEPS83ejMmR0JDPz06NmBiXVpSUIBOWVtpufa0gj8rMzAtLCsrKydAMjAkUTA7MkYxbT9JOzkpNzo5OTxCQ0I2LFU0OD9CRktILj1BX05WLDAvLU1BNzYeLl46OlA9SEZPdFpdYmRhVzk+UGlXVlFWUVQ1O39pVFZSTU1KOkhEk62olDlAT05PVFRHRUZZUUczLkpHT4A+PTlMS0pKRTg0NTxGRERHPkVWPjkyI0Q8Ozs5Pjw3OkJDRUE+PT1WRT9CQD4/PT1QPkQ8PDk9Ozcy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YUtaYYqHQ1xpgV9jeVtucFiFgVljR15bZYBdd3tmS0ZcdnBdcIVEWW9wUHuCh2FzT15gWklHcG9qWFNYXT9Ji212goB7c6ZgZXyTq2JueYSLeVVHX0VVaFWIaW18cYCCeU9felI6e0VuanVAU2lgjXhxcnCYbGtjk2NtoDBYllKPlpWVjWtZVmGNkI6MgnF+j5CSk5OLY45PlpmZmZqZlldNT1FUVZdsj1NTUVCHTwFQhk9aUFBQT1BPT09NeT1CQD06OTc1NTU3ODk8P0MmLDU8ISNAPCwoMV2Zl5eWl5c+PD09Ozg2NTU2Njc4Ojs+QEIjJiktMzwjKjAtPEZCaXF1d3hnk3Fua2VZUjU3gLi8w8zb7PqCh4mMj5WcoKOnpJ2ShvTeyLWklYfx17yAaaZpXVY5SkU+REdGPTRJKDhZTC4wLjdoqp7dEFt/2DQnvHWEc3KfiTpKk5qHodKF6pKGhomakVdOs82g0ExYk77RwyMTqbbHxSxY4tzGu5/R2ee7fuF48ptp5unhw2nEgM6t7WXIg5uHhpHft4idtPr1sKr5kYCXpJGe5Yihl5awuaSfmJee24CGhLzXgPLtrcSC5OhgpNfU85aQ5NnG1NNMTFw6zkNDJydPSjWViNzBhNetdZaAiYqLjY2NjI2OjY6QkZKTlJWUko2KhPvw5t3PwrOmmoz45uHNzdXc75vQgP2FiYmGiYW8ntPGxMJxnLvBxfitmo3zi21dgKyfhM2Hqomait3G8nhLPk1uY41ddYZeNpCHjWiwh5a/rbqAisDKo5OHh5Gfr7/P3ubr7cm4tra5u7qztresqJyenKfNjs7UhIyZv7KagJyL6JeVm/frl7uxiP28r5WTlJSTk5STgJOSkpCRjomHgf/16t3Nxbepm46Fgfn08vr4goSLkpulsL/K1r3Y6/SAh5WVg8Dt8oSSgrzujOnMseKkhaWrkaWBi5yc9JiUn7CWq63Lw42rrGiIf5ZfV0w1HyRTqWa+saKPfNzCt0JES0xJR0Q9Lzd4NExGP0A8PDs2YF1YWFFRgE9YW2K5g5CO0o45Jy4wLS0rKy0pPDg+NEY7J0FPSD0yQzk/MDs6Njg7OTRHP0UzPkJKTU9LOUI9U01bMDUyLkk6QwsbNy43Kj5dM0VXQmFoampjWFtDTERYVlVXVFRDeoNNWFZUT1NdQjlgcW9nQy9W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X3p0d3t4ZmZdaGt4bWtmZUpXZbKgmldQXVF1d2lqZ2tgbF9rWG1xdHl8elNiZYRicHRsY1mPdFtbeVxxgFGQZU1OgXRnRWJ0bVB+foFgbHBQUE1ufG5RR1RtalBpU25KY1BkR0xmUYiHS2JbZWZySnttcmxgXlRXWj5GjW52goB7cqZfZXySq2FueYSLe1JGXoxaZFSMaGp4bnx9c01cdE5oc3tmZWtyR19Vh4uVYWdoXGdjmHWCU1hVnZ2ZlpSRj42LjI6NjoSNDI+QkJOUlk1NTZmZmISZEk1OTk9TXGVkWlRRT05PTk5NTYZOhU8BUIhPT1ZmdkJKUVsyNjg4OTk7PUBEJzA6REM4SFSlmJSUlpZgLR8+Pj4/QT45NjY3ODo8PkBFJSgrLzdBKjg+LiwoN4h1eHlljXVxbmtnZI8+KzCAoamwuMLR4PKAiIyOlJmcnZqTiPffy7elloj03cexm31sq3BjXDxPRT5ERkY+NEY8JxxNWj1ZooK1muN7qH1xYEi2coIgb5l+b0aRmIWp0n30lJSKgIyVM3h7hpTLSFCNus7DPxOjsMOwir/U1b1PTsvY4htA3nl2mHV0697EY8aAzqfmYMH8l4Hlf8bKh56t9PGkp/OMh6W0jJPho8CWlrLw3KCem+GB9IP6vdj76++twvPU41yo1df3jo3p5Mjq7EpKWTjIREEmJ05INZWN58qI3K13l/WDhYaGhIGBgYD//f39/v/+/Pj18fDm3dTNw7ernI+E9/D/h46ht8vjgIOAh4eJiYiIiYqL2+GM0tHNdJq9w8X0qpeVj3OZ8Z/5wNiW25CV+IGq7l89UGRuwnOBT18zqHZXkdmY4vey6ufYlZ6zoJWTmaGssre5ubi1tLjDtMHFvrG4u7SwpaS2v9SBwsmWgtGdtbaE2PqPzaTEiui77++hu/3k3NXNzMvIyMaAxsfEw8LBvru5trOxrq2qp6Whnpyal5SOiYP669/Qu6eR5sq7oOiFjqfK7fOiuuXp9YSMtt2g4YHt06KDr/eLnvOGlJfmkIugqJCltqW2xollT0uKc4FhUVYxOkx+sb6woo993cW6QkNKS0lHRD0vN4AyJEQ9Pzo6NzNZWVZVUE+AS1ZXZruDkYnISDw6OyMtMTAsLS4uKUVBJUQiQUNAP0E+Kz44STc6ODg5OjgsQURYPURKTlFSSU4/SUYy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eHJ2cl9cYGFnT0h0b4JiZVd3e3Z5bopfYlB1cW1naVJhYo6Bg3pzdmFVU4BqfoKJRoh5T4JWbHd5c3BzW5FXSnt5en57amhdY2V2bGZlZUpda7WSql1NWlFTh1JoampQYmGCZ25tb3BvWl5db1VjVZGXTViSaWl4VmtqTUp2U2RUVIpSaHRqS3+AjmtITUxKgmBXV35gh0Rya0xKYmx+Q0VIRFB0TJF/al9kZH1FTWVpcnJnbk5VVlo8RIlud4KAe3OmX2R7k6thbnmDin1QRl2KWmFSiWpmc252dnBLWm5KX2xtX2FlbVhbVHuIo2FkY3xgYWBaWlpZVlJOmJWSkI6NjIuMjIyNjY6NjY+QkpKTlZaWlpeXl5iZmZpNTU5OUFJSUU9PTk5NTYRMBU1MTE1Nh08FTk9OTk6GT0tQUVRgTnuvTlQvNz1AREgoLzJDS5uVlJWUlU87IDw6OTk6PD07PDo5Oj1BQyQmKS41PSQtNjQ3KziJdXd4YYl3eHd3cWtZmlxxSDeA1YWUnqu5yNrtgYiLjpGSjIT44s65qJqM+eHLt6WSfnpvsXRoXj5WRT5ESEY9NEI8KB9NVmKTqhyzm+RztoN0vouyb4EUZ416akyMk4FXM378khdC7YWYMnbqh6zLkaF6mMvB74Ceq76dkKbMzrsWlcPQ3q47a3JysCdzctDKXcGAyKblu7v0lPXaz6rVg5ev8O2jmumI1OWAgYnKgKORj6fVxpeal+yd7oH1udHt5eupufjm7q6uztv1horp48qBgkpOWTrBREEmJ05JNJCU8s+K2q55l+6A//788tq+o4jhtJDQzLr1u6KaqpnExN2KoKq7y+L5hZOnwNj7iIqRlJGAkZCQiYiIioiJh4f2kLbY1NLfl7zAwPOl8aOYtJynqs711fCA1a/0ltVYMWe4bmhKUVhhXVVchumDoJb96uKF0M65p5mQjIqMkpmiqKyws6GatrbDyMTEvr7IzcXG3YW0tvn/r6OUo7DllYrShN/whZHW5b2Dgf306eHe29fU09GAz83LysnGxMLAvbq1sa+tq6ako6KgnpyamJSQi4mE/vXw5Jfq1ZLt6vL++ZCZtd3c6faUr9Ok2YCXyJ+A1vaDmO+AjY7YiYKeoYmgsaagYmt9Z0JKTG13ZkkrLTJJYLOun4182sO3QkJJS0lGRD0vOH8xR0I8PDY2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kn9gaL+jZEmIiYqBfGtpeYuEgoN7TJFpTYWFh3+AZ5pciX+Ce25yWm9ckEuSglOIW1NOT2dYVlt1fHFtRUaAfohQV1dXVFCBcmp7R4uGhIpnV16JXmJhZYBsTXhyWHp9g4WCUl5RS3Z6dW1yVHR2bHaDfIB/bWVYXGF3bWZeYkZcdbuRmVVTWldbZmlha2N/WmVOa2tpamZkfIeUY4GDZ2djdF92Z3VUbW5ShmB4blCEjl11emJHgn5NeUpLS0tJSEdGiW5YSlN5V3dQY2dnaWhpamNWRHRgZ4BhY2VTT1ZecXNtQ0pHVFZYOUOIbnaBgHtzqF9lepKqYW15g4qBU0hbklxgT4RmZW9rcXNtkFhmjV1ubVheYGZbV1KQlF9eYV9coFxdXFpYVVRRnZeTkY6Ni4qJi4uLjI2MjI2OjY+QkZKTlZWVlJaWmJiYmZlMTU1NTk5NTUxNTQdMTExNTUxMhE0ETk5NTYpOTk9OTk9RVF1ihJlVU09OrnJHVzU8OkJOlpWTlJM1Hzw2NDIwMDEzNjg6PUFBQkgnKi0xOSMtMywzMDqMcnV1W4J2dndaKTcsPE5CRUpQYYCFrtj7jJurvtDo+oCBgPnr3c6+qpqM/efQvauZiHlpeXG4dmxlQlhGPUVIRjw0Qx4nIUlehZHAaLiY39eqfXCxhazR9zOyhnfPlYCYf5a+eX2NjH/YgpQXMt2Ho9STnX7Zp4nmepWnuCgKncXMuKSUvsPXp3NobG0vE3Fw0MdorYC3pd+qseiS8tTDmN39kbLs4pCY34SFuL33hJSk7Pfo6Kavu7W4nJmThJDhg5OPnvaR5O3wqKPP1OyEhezgy4yOTU9WOLdFQiYnTkg0kJj3043esHmX5eGNyIDHrOixleqHxq2Dzo+a69abk8WslY7Ilpylidq35uTbi6uys7GooIChoZmPjYyKiYqJiIiF+KPx59HM1o60vLvaY412/5G41M6M7u/iyKOHxrBHRElNZ6dITVNQWXzB87B48vuV/NfEw66glpGOjo2F6Mm6s7G42MDqvM7O09nbgOvg3fKHodWb6LLjs4eXg+n78PWYu7GTwYDJuu7/8Oje2NTRzsrJyIDFxMPAv727uLa0sK2qqKWjoZ+dnJuamJaUko+LiIWB/fnko/Tqr4aGjpeFxpq80s3k/ZiixZjP6eu4k/K67vyQ4/SFhs6C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Vn+KhGJIWW1gY2ZiYVR9cHxabHV5dXRuUXRxkmt1dnl8fWN7bYdsgYlHTYaAcm5aYVFwZ21OX3m2k1BIiYtHf2mEUX2Ge4GCdm1PZHCAfn1za2ZiaU9RTJRXbHh/VFhblYh0eX6EjZacUlRWWltdXl5fYWNhhGCAX2BgX19dXFtYVVBampGNiYaEgnhnVZN6ZlRLSltbWVxocmtpZWRUT2R/fHx/dGVnTlR0b2VeX4ZVdW6UoFFXXGVhZHeGa11oW3FfbmlsaGVSXIxTfFBWcE9vZ4dkbk5pcFd7WXlohXGAXnl5XIB0eViESEhJSUlHR0hISElKSIZuW3xabGxsa2hnXUt7bHhKW0FWXWRdV1NUc3RyVUtARlVUWTVEhm51gIB7cKpfZHqTqmFteYKJg09IWaRfXlB+YmVtamxvaYlWYolcaWhVXF1jVlNQSpBbWVpZVG1UVVdUUlBQnZqUkI2MioqJiYmFihqLjIyMjo+PkZKSkpOTk5SVlZaWlpeXS5iZmIdMgk2ETANNTEyKTYdOS09QU1yeuJBaUU1nZZeXlpmbVGRcTpORkTQwMC4sKyopKSkrLTI3PEIkJyguNj4jKS8wPTQ8jm9ycVl6cHFxMy06PD0+PTguS0NETICzu8/O2vKJmazAztTU0Mm/tKebjoHu1L2tmov+4GKzeXS7eW1nRlpHPURGRTw0Ox0nHkJdg5G+HlyY2zxUeW4tJpjB6r3AgnTHiHmRe09ccemJiHjSf5BEY9eEn9yWnoCZuOaHcI+dsZkWUcTIt6GRub/XekdjaGxhU25t0MJ7joCrodupq+OU+L63i9fthbbs3oOW1vmW84SuoKaCsrWI2Yfd8LCAgtXPndb20smSvNClktKN08SivuOJ/vDTwJaXSktXN7RGQyYnTkk0k53915DjsnqXt5nk75bNyYXYpvWG0ZmY4vyu6tHUioLFspDrsJiRmsT9xIiy+befgazIwIC2s6WZlZOQjoyKi46OkZGU17LVxcHIiaysY2aFpcOYv9rYiu3XxdiR0rahQktOcm+FRUxSU4z724JbWKvqiozcybGN3bKckZK685SntcPK0tjW29fp6O73/IOFjZeq0ZSzgLyzx92CifGN1OKmsLXPzYWr86Dy5N3VzcvKx8TCwnrCwL69uri2sq+tqqmno56dnJmYl5WUk5GPjImHhID79/Ttq/uAx4eHkJX8xprAz8nfgJaasoDGwLWvjOmk5vCL1uaBgL/655aW/n3YdWBXflRZf2VkP09ZfGV1KUNHWKibinrVv7ZAQkpLSkdEPS44gDFLQzw5NS8qTYRCgDs5OENDPjU7TE45PkBBQUBAPFhVUTUmLTI0NTM2MEs+RTE5PkFBPzouRUJZODw/QkZIPE1HYU1faDc7XlpZWztGQFQ5NkFKS2fESUGDhEJtOlA5fY+DhYR0SSs+YnRwal9VOTdDNTMza0BTZlw8QURscWVkZ210dn5BQUNHRkdHBUZGRElHhEZ1RUVFRENDQkA+OzdHZmBaU1FPSkU8MlFDOCwjIS8uLCsvNzY1MTMvKDI+Ozs7NjM/Kiw2Mi8rKkI0XVhOVC4zNzMpLDs9LikyKzouNDEwLi0lME0uOiMqOSEwLz4wNyQuLSdBKjMuQTY+KzMyKD43NSQ3HBwchBuFGngZMCE0JCgpKCcmJSMeODg7IysgLjM3NDo9MTo6Oi4rGx4nKTEeKE01Mzo7OzhZY3WQqr1ncHd9gn9eMVf8h5mAxaOYt5jAprLmkqfdpZ24lKCfqpGPiIHmWEo/OTE+LjEyMjIwMGBgXVpZWVhYWFlYV1ZVVldWV1iEWRRbXF1dXl9fYF9fX11dXFstWltcLocthiyILVYsLS4uLy8vLi4uLy8vMTNKWEExMS8/QWFhX2FnPzk1MFtccaW5ta2jm5SQj5GVnqy60OyEioWgxvWUr8C04bSt30hHRDVQWWCGi5rT29/i382g8ca3tIaAj4GGgA1/f4B/gIB/f359fX2BhYMGgn59fHx+hH0Vf359fX5+fX5/f319fXx9fX59fX59hH4RfX59fn1+fn9/fX59fX5+fX6EfYR+BoB/fn5+fYV+iH+Sfg6AgIB/fn5+f39/fn5+f4R+BoB/fn5/f4x+AX2GfgF/hH4pgIB8fHx9foKDhISDg4N/fn19fn5/gICBf35+f4F+f3+Bfn+AgH9+f36Gfw9+f39+f39+f3+AgH9/gIGUggKBgIR+CH9/f4CCgoKDhYSEg4OEhIIDgX+AhYIIg4OCgoKBgYGFf4d+h38BfYd5hnoWe3yDhIJ8goWFhISDg4OCgoKEhYSDgKl/hn6Cf4R+AX2GfgF/hn4GfX59fH59hX4Hf31+fn1/foR9GXx7fX18fXx8fX18fXx+fXx8fn9/f35+foKIgwOCgH2HfI17Anx9iH4EfXx7e4h8g3uIfIN7h3wge3t7fHx8fX18e3t7fX59fHx9fXx7fX59fXx8fXx7enuGfIN7hnwOe3t7fHx8e3x8fH1+fn6JfYl+AX2Rfot9hnyDe4Z8g3uGfAN7enuGfIR7gnqEewl8fHt7fHx7e3uHfA17ent7fHx7fHx8e3t7hHwHe3x8fHt7e4R8BXt7e3x8jX0DfHx7iXwFe3t7fHyFfYJ8hX2GfAR9fYCChYMBgoV/hYAWf31/fn19fn59fn1+fX59fn1+fn1+fIl+AX2Gfod/pn4Ef35+fqV/BoCCgX9/f4d+hH8Efn5+gY+ChoOEhAODgoGFf4R+AYCIgYSAAgIEAIAyha+je11ORSAgHx4eHx4fHh4ePT9BQ0ZLU7XL1uXos6bsmq7ftPfNu97z9daaiI7GlpqXxtXLlITb/JaCqoifuM3z94mSsoqq5I21iKPl9/CC6I7agobt3/GQtbzw/Kuhy5/H9rWZwN3rxeHl1LHS6PqKwamLiYqLkIyG6/+B9IC5m9eE9NOG0f6GeJycgUdSUFNcYl9sfG9zVZNas4VTjXZrj2GRam1+XIBmaYFbeGVfhFmKnG5tgl6VZX58cCAil6XAl8i854WG+tmQxaV8yHvSuY+5IJllaDsXZDBJEmUsHyE6Lho0Oz4hUS02SjBALzdKI0FUIRsgSjE9SDEXG3oUGRkYFxcVFBIREREQDg0NDAwXJYB9e4Jdd9XKQCMLBAIGCA0dJBQRCw0zvszDtOQlE4XS0WAIBAAAARUYGjMtRlJ3SlpjampmY2FhYmJiY2NNPT4/PRweHB0cHRoWCAcEGxxJFxUXFywXMCIlGBgaHCElJHFvbGxra4RtgG9wcXJydHR0c3Nyc3JycXBwb21sa2ppZ2RjYV9dW1mwrq6uo3NUVGZcXWCkYmdPYHBvf4hUXl9bd11iYm3GTph6WWx7oXyVfXuKSHvP+LWqpaXOjIH2rs2hmoyFrfv41d/y6p6H59DPkaS3wsrO0MqjmuOj/t7LyLehm4nX5ebtgNG1mtDfzKOrv9vg/N6RkpKUlZWO3v32wouyubi5sayQzrnbo8G5usfRvvu+vpvP19ng5NP+t6et+viiiZOH2Zr4gq6GlJKPqKGLkIqE/YSFzY2a8OTVzNC8h46ZxZvJx5Gowtbq/4iRmZyam56fnaHw16Chn6KlpaOhoaGlp6emgKam2aWgoaSjoqSjpaSkpKOjpKWD26GioqS1y/T32Limo6Cdlo+n9drOtp+M7anOi7Waq7nU0oCV74vRwLmsrfiRzLmahoqajte3qcCXqYiVl+K7wbe6vqjcjI/nqKn64Lm9/52q/rTGo9qc2sDkrNCp6N+p6u6wqIKLi4mHiImHgIeIi42Jh4uPkI3/4eDWza6M1brZkrTM0c/OzYOhoY6F3uTgtZuZuueIkZSsycytu8jFvK7/kpWwz/GKm669xsSHjJB8m4/1xI+hlqGYqZHKfYfHfrONd3+BieR3cNdw8PXz+N2CoaC/9PPy8vDt6OXh393e3tvb2t3b29zd39/gIeHg4OTn6erq5enu7O3u8PHy9Pb29/r7+/z/gID//4CBgYWAA4KCgYSAhIFLgoGBgICAgoSGjZOckoqE/vj1+PX1+PKohpTM7e/vPiImKBUVFBQTEhITFBYYGw8QExwkFBQRCRgqUKarqojCr6yfLhwdHh8fHyAhhCMCJSqAOYWflHllWVcsLS8wMTEvLSwqKVBMSEVEREaTmZ2enXtzpmlue2GHh3uQmpuHYUM5UEBNTmp0eFNLe5Y7Rl5OOD5ujZMsS2NTTlFRZFBlhZCWU5lZhU9WjYeMVmZ9hY5fYXxlg6ZtXndxWXiPkYhnhJmjWEU/WVlZX19cWp6rU5iAfmueWKSZX6K4X1R3eFlMXVpNSFBOWXdhZUuBWp5zSIBzZ4RaiGxqdVV4am17VnRrYX9VgJZnZHFTl2F4c2c2NlhcZ0mRfZNVVqOQYZB/YJRgoY1tjTVna3FGRmo6XDpuN0JZRDtMQk9URGY5XFY7Vz1DWzZPZjUrK1hKTVJDMDmAKjMxLSwsKyooKCkqKCcnJicoP0nLztDdlbhQM2CVSE9AFA4LFhQJDhUWJTIzPEaMnqpVXnH/RVizrqrpuoA2MFNdfE9daXR5fH1+gICBg4SFZX2coKNVWmZocoGJl1VvRTxHm0U0NTNfNFw+TysxNTlAS0BhXVtcXV1eYGJkZWaAaGhpaWpqamloaWdmZWRjYmFgXlxbWllXVVRST06ZlpWUjWhOUGtRU1aKU1pEVV5eanZARUlHX0RJTkiBPGlnQFthe2xlaGFaQWZwj2xjX198UkyOZGxWV1FIY4N1ZG+Dp3BeopCMZW98goaIh4FkY5BHbl9YV05ERDxlbG1uYVeATWRrZlZZYGhykYZYWllaWlpYhpuXfVBlampoY2BTenKGXmtpaXByZ5Jzel54e3x+f3SXcnFliYhbTVJMhmfFYmpPV1RRYmJbV05KjUlKdFBYh4N7dXZpSlFfdV14fFlod4SRoVdcYmNjY2RkY2SUimVmZWZoaGZlZWRlZmVkZGQCgGaEZHhlZWRmZWRlZWVjYk6CYWJjaHGAnaKIcGVhX15bVmWUhXprXFCGYW9PYlVfZHBvR1SISm9pZV1dilWEgGZUVF5Sc2VfbVVcTVlbhGdpZWVkV3pSVoVeXouAY2eQXWSLZGxXelBzZoJjd1t1clmEhWxeSE5OTUxNTk2ETIBKSUtNTUuFcW9pY1VHc2hxS1tnZ2ZmZkFQVUxBbnJzYVRNW3BESU9dcn9tdoCAe3KpX2R6kalhbXiCiIJNSE9TYF2dfGBjamdsbGWGVF2BWWdlVFlbYJlQTpRNoqiloItSYWB1l5eVlZORjoyJh4WEhYSEhIOAgoKDhISEhYaGhwGIhIoVi42Njo+QkZGRk5WVlZaWlpdLS5iYhEsETExLS4tMh00eTlBUWV1YU06YlpSWlZaWkG5scnuNkJ5CLztHJycnhCYoKCovNkEmLTI1RzA6Nx86QEptbmtVdm1tbzkxOjw9PT5AQUFBPjs3N4CJ+fHl2tnd8IWSnqOmpKCZkYmB9NzHsp6QgObRuqaeeXW9fHJpSF5IPkRHRTwzOB4nH0BbgJLCW1ud4jpRdmgYI5TA6Btgg3JlRXKNeY+pzdiDl3bBdoxRaM2Cm9yYo32StZezwpmFq5ZRnMC/tWGVvMZtJyhlZmlgUmhqxbxFv4Cokc1VsueW98lehszRf7bs5aSGnJaj0am+kfeP7uGU77ie7aj5o5XPmtWfltCWyZyH2Y7c3qKlu4rcqs7JsYmKSkpTNbhHRSgoT0k0laWD2Y/mtHmXkYfs9Zvh24LVp+6DsvyekNCSv8/A8YnlxpDSl6jeiMX+r4CC3drj0dKj0ICavrChnp2alo+NkZGMiomJjI3Kosiwqa97mm59u/iFpL7nzpDV5omm28D6a0FLUqN7YD1JVYLSrWZHTpqV45KC19DuipScpay1v8nT4Ojw+Pu/rfH4/4eJj5ylrb7dj9vLoMb5zI+Ri/eG/7zVhKGunbzgruzUyMC8uba1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V4tRYl5eXEhTX4duVmVLrF2ejYtkb3yChoiIgWVjjkhuYVhVUEFBOWVsbm9lXFNqbGRUWFplZWxsYHRUVldYWFlXjVdiYohUa3BwbWVfT3dwkGJoanB6e2GBdpRwf4CCg4Jeam6VUI5vfl9TioOIeFRTWVdUZE9bXkeJfHRtiHCMcZFYbX+PUVliZmeQYGxzb2lnZWZmZ2hoa2toaWedZmlrbW1taWhnhWgMZWSCaWVmZmdnZ2lohGmAaGdkbVhhYmVqYobGlJ92amNhYGFWgmBeXl1dXl5dWldQjn1qWYlUZ09IUlNWXImIgYJpfmxsVoNmVWxLdXpQXU9pZWNbSG1WWm1TX1FxWGtSZWB8WmpceE1wZoNiclZoV1ZecH5eZklRUVBSVltdWVZTUVFQUE5MRYB2a1lHcGeAdUxgbm1qZ2ZiW1BFe4NhY25wZV5adotSZXSKqF98bXaBgHtzVV9keZGpYG13goeETUZKV15cnnxdYGpkampkglNafVhnYU9VWF2Fm5qXS12YjIF5bE5zXGJycm5qYV1WUZeLg3t1bmlcVlKgnJiVj46LjY2PkpOXoahYXWNmbXMZeYCLlJxTWV1kam94foaKjI2QkpKUlJWWloSXB5iXS0uYmJiEmQtNTk5PUE9NmJeWlYWTOo64jIaRjo6HeXqOqT5TNUBISksnKS00Qy4+Sk82KUdEUGlraVNzaGdwPDM6Ojs8PT9AQkVHJEpKS0yA2cGum4v81bahlY6F79jKvq2vrqyztLrBxca7q6CQjHl3wH91akdgSD1FR0Y7MjEdKSBFWnuYvVtbm99voHNln3+ZwuVhYoFtZBNvinYqL8LCf4J3ud+ILGXDgJbdn6l7haWTxz1/f76NWZu7vbtGU7xha09UZmdlYlRma7+zTGCAoY/NUa/gkva3ooHAvum27+mu5Juu+qDn0JaBne7jk+uwmuuk96OWz5XIm5vOlMefjdeO14vHzvjbhqKpzfyWkklHUjW4SEcpKVBJNZWohtuQ57N5l42J7/ud9duA1avq/tW2mo7xnoeIgYqE/MWHuZ681ou77qP/qNDSreq19PaAq/yqhaGhnp2UjomF+vXu5efl4NqhiqKVi5RucJ3zipuUuM3NzLK00N7Hu6+gsnnMpYlQYVpXv5BaREpJXJ/p5NrNwri2trzFzdTc5Ovw8/j6+pyVj4+U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cXFwcIRxhHCAb29ubWtpZ2TDu6t4W1w6Z2aweFlXQk1XWmlfgENMTFpDSUVHQEVIZXtUXHtoYGRZfFZkYVBOTWNfW01OXX9XXFZmYoZcUUZhR2FhpJyLimVvfIOHiIiCZWOSSGxgVlVRPkBwaWptcWdiYT5AcldZWGNhZ2hnZGB9U1NVVlZXV5CAXGJ0UG1zc21hW055d1Fpa3B9g3dUdnlcfYSHh4NOeWt4iGFvS4d+ZVRLb1JTWUyGjoyPbVFoeqJaaHBva2ltb2xscXR0bnF7d4aCdm9sa2prbXB1c29ycGJraGptb29taWZoampqaGRjZmxkY2RmZmZnZ2dmZmVkYmNbZGFkZmiAY6W8bZa5VmNiYmJrYGBfXl9eX19fXl5dXFxbW5N/W1STfmdQfHB+hXpvUY1eTmVKc2BmfIxgWGVjU4tNgFlkSV9cgUtoWYFlc1ZpYIVHbmeHYG6MT1ZmfIB9XHJigmpLUlVUUExLUlZXWVVSUE5LR3ZWgHF7UF9rbG1oZmFXSHSAaXZKWGdsRV5ndXc/RUFDTk9baD55bXaCgHtzVl9keZGoYG13gYd7UUZJW19bnndaXGZlaGlggKBafldlXJhRVVt+kpRPTF5nWY9WcVJ0cnmMkJisX2iDolZZYWZobHN5fYWKjpOYTE9TVFVVVFRSUU6YkY2HgHhwaFxRintrYWEqX1qpnZiraYKeXm+BkVJcZ3J+iYuNj5CRkZOUlZWWlpaXmJmal5eUk5KShJA7kZKTkpCOi4p9bn+JjIp9cHGYP1g4RicrMT4yWUxKSVdpaWdTc2ZldUQ8REI+PDw9P0EiIyQmKCosLS6Ai4GZxM/kmqqc2u7y/7GL+ob+9XOLg4i8qZZ3aohmeXh4wn97cEliST1FRkY7Mi4dJx9JXX2cvlywktI5UnRnmn2TveLDYn1pMyBvgnBiW7S6fiQ7vHGKTmXAf5DkqK15gJ+PcEuErYiqWJ25uV0lVLq6Z01SaGhnX1RkaMO9T3uAjpLJpK3VjO6tq/y4j6m89eu0kLGwhLbv2JP7toPglO2tkfGl9qGRz5PDmpvJk8agjNOO0IbGz/jm2d3lj8egmUhNUza5REgpKlJKNJWujd+V6LJ6l4eK/P+ejd3/z7Pn+siplomDq5CxhYbzhKqj/MS30I6x2Zr/hM6GjOunkumA7J3b25q3j52VioDz4tTOw8TCvKTq9rKVioJ+gqDWwJqAmbrZ0s7U3+Hd5pGu3+3A7O77aWaF35FdSUZJnYWD9+fYy8G8u8DGzdPb4efr6fDx8cOKpJ6jrrvJ4fuRtPWOjpuqt6W+4O3Xy7fenIjLwoGP1s+7tq6opaSkpqSjo6SEpYCjo6Sfw5Ognp2cm5mZmZiWlZWUk5GPjYuIhYH++fKzk6WCi4v86bq6iqK4u9C4+oiSlrOGkY2DgJSM2fO5y/Hmqt7Eo15wbk9AUnBsYkhLYntUUz5aX4BWTjY+KUFgzMi2sT9BSUpIRkU9Ljd4MUY+NjY1KypHQT8/PTg4Nx4dPIAxNTIxLjU4PT9AWTk3ODg4OTlgOj5ILz08PT04OC9KTi82OD1CRUI1U15EUVZZXV86eVhhhmZmM19mVi8ya1pfY1eSmcu/gmiMsMtvfoZ+bmt1e3Z2gIiKgYaTmcK3kHhrZWVna3yNiH+DfnF1a290enltZmRlaGtsZmBefWdWU4BTVVdYW1tYVlVUVE9MT0pFRkdPUWRwQltnQD8/QEBKQD8/Pj06OTg5ODg2NTU0NVVMNjFVSDosQTo9Pzw6KVk8JCwiNykwRUMuJysqJEAmPC4zIygnNB8rJzcsNyMnKT8iLS8/MDpIISQtNTU1KDcuPS4eICEgHhwbHiAgIR8dHYAeHhwwIjItMx4kKCgmJCUjHxw1MjUgJyotIC80QUYkJCElLDA5SC5KNDI5Ojs5LWNyjqi5ZW11e311Wi5CiICI7LCOhKCKp5eVwvuPxYyIlPaEipHD6O+FgJ95W4NFZD49Pk5gY2x0QUlVYzY7PkNDRkhKUFNYWVpfMDEzNDQ1Njk2MzIwYVtaWVZQSkM6NWJXSU9NR0eGfXiCR1dpPEJPVzE3PkNJT1NTVVZXVlZWWVpbXFxcXV1dXFuEWgFZhFo6W1xbWllXVk1OZ1lcXFJKTr2J66begY2i0abyxtfixlJCPzJISVbA1Nj48unk6Onw+ICDh4qNkZSWkw+CgoGAf35+fH59fH19fX6EfRV8fn57fX18fX5+f3+AgH9/fn19fYGFgwaCfn18fH6IfQR+fn1+hX0efH59fn9/fn1+fn59fX5/f31+fn9/fX59fX5+fX59hH4DfX59hH4Ff4B/fn6KfwN+fn+Lfg19fn59gICAf35+fn9/hH4ff39+fn5/f35+fn9/fn5+f39+fn5/f35+fn9+fn1+f4R+M4CAfHx8fX6Cg4SEg4ODf35+fX5+f4CAgX5+fn+Cfn5/gX5+gIB/f4F+f3+Af36Afn9/foV/CIB+fn+Af3+AhX8DfoCBhYKJgQKAf4Z+BICDg4OMhAqDgoKDg4OCgIGDhoIDf4CAlH8Be4h6CXt7e32DhIGAgoSECoODg4CAgYKDgoGVfwF+ln+Efgd/f4B+fn19iH4BfYd+Dn19fn1+fX5+fX59fn58hH0BfIR9Cnx8fXx9fXx8fXyFfQJ8fYR+AYKIgwOCgH2HfIh7gnyKewJ8fYd+BH19fHuIfIJ7iHyCe4d8DXt7e3x9fX59fXx7e3yEfQV8e3t7fIR9j34BfZB+AX2QfgF9kH4BfYZ+BYOFhoR/hX4BfZB+BH19fn6EfYJ8hX0Le3t8fHx7fHt6enuEfBZ7fHt7e3x8fHt8fHx7e3t8fHx7fHx8hHuHfAR7e3t8kX0FfHx7e3uKfIN7hHwFfX19fHuIfAN9gIKGg4V/hYAnf31/fn59fn19fn1+fX59fn19fn1+fH59fn5+fX19fn59fX18fH19h3uEfI59i36KfYd8hHuDfIR9s34EgICBgYSCBoODg4KBgIR/hH4BgImBiYICAgQAgEM+P1mGm5mGkHycjb6QrtO62ur9toHqlYWZg4bitbqyqfOet+i9hM253fL115aAi7iYkZXF7tX6gND1yrenidLdzPbsjYOvhce4i6j/pcvV14OkmeSLl76m45Omx+jzpp7HoIDRot+9jbPk0+Dti/34/oSGjpqYlImSkJCDgIbjgIGm3O/l1IfQ0OyCmIG0RFJQU2Z5dVqDWYpXln1hiVmTdWqaYo5saoJbcml4f2t+amqDXX/KpoBTlXWQkF+JIyOXq8Gd3sLthoX92o2xrITKgdi5krwfnDNjPRFlYkcdY1giFzovFEoyNhUzUiNGPiYmP0okPEgbMl9GRkNROic+gD00RCo6STMqHyIfGxgWFRMTIx84ZmJlYmBcWVVXuEsPCwQDBAcKCwsKCgcKEjwpCggDBAUDAAQFAAAAAS0rLFhVU1NRUFBSVVdZXGNobXN3fICEYC8mKCkrKxkaFRcJCB0bHhEYGyEbIRkXFCQlIyMkIkGGg4KAf31+fn5/f3+AgICAgYKCg4OCgYOEhYWFhoiJiYmHh4iIi4qJiIaFgnzv3s2MY182gYCaWWNkUV1rc39vXVxuY2NeW4zQwltneqpodaF3lni4+6KKpKaCi4ufm434mp/Xo5Sh5LPgpfGUq+i1wM3CxJWktsLKz9DLoprgm+zcxtHDk4zhgPH3/+7hgOGlspnFtKm9wsnHw7G/2vfyhIeJjY6PjvSbj7GNu7u6rqWg/8rWmsHI1OPkw/XP6cTfuIPNiqeRxr3Uqd+pjpWxhY3ZpdSClZ6rvMK99aS1wcTDsqq1ubK2vr+9ta6ah2fMvbOvq6ysr7S+ta60sPT1p6isr66oqKWmqKqoo6CcK6KmoaOlpKWnqaiop6alop+fnKSjoaWprfSipKOhqKWioIbrnpuenqCcm5yEnYCbnZ6NuZ2amZiYl5aWlpqdoJ7yiLf+0Mav9v/Oz7CqhoCzq6qUzpiujd+sq+GgypvEwYHcsrfmmqKygKfg7uXXm7+xgtCEio2SloCy5I6RkZOanqGficb8w/3Tjvrd0MvFuJnuvbjjiJ+rsb3JxcCts+HJ34GHkrGuzuiGv669ykzGva6Ck5Svze+Jmqy5javxiYePmI74wYydkKWbpo3C5IbIfbCH6fd9hb/h13TUjNbUwY2rk4aTmYOjtr3Gyc3Q0NHR0tPW1NXW2Nrchd5A4uPj5ePl5ubo6urs6+vs7+/t7e/o3dbNv7KmnIz63b2khc2jjPjkybe+h8yMs9mBmLHH3Ojp7vLz8/b08vPz74jsJOvq5+Xn6eTk5Obgtp2evuXX1up9RSYtGyY8U52in4C2pZqYMYQeECAgHx4fIBASExUXGRkpHiiASldDRVd7dlRpWGZlg2B1i3aOl5FvVn5bUVVLT39aaHh1q2xyf2ZJiXqPm5yIYTw5UUFKT2h9e1VGdY44T1xNbW9vlI5QS2BLdWlPXJdseXp9Tyw9g1JaRV2DV2CEipJbX3ZiQFZfhHdCVIiFjZ5cpKCjV1xcYWFgXF5eX1RWV5KAUXCdnZaVXZ6UrFxsZYRNYFxRUGBdTX5Pdk2He1l5TYJwYIxciWlld1VsaHN4YHZuZX5YeryPe02OiYaJWH08OlhgZ0mdgZdWVqSQYIWFZpxjppBtjTJnOXBHMWttWkJrZUs9QTltUjxJgD1nPVFKQTVMWDtHVi1RaFRXXVxINUuAUkFROUdWT0s/SEM/OjYyMi9UQGCppKepqqqtr7N8oWmXYFozHx8YGBgWFxoiWEguKxYYK6KNou/ApqryXEpHiYR/fHp6e3x9fXx9fn9/fn9/gIBha2RpcH6JTFlic00vRz9BND1PSldRNjsuSjVPNVo8QmtpaWhoaGlpamtsbW0Kbm9wcHBxcXFvcI1ygHRycXBta2hlwrWsel5gPWppek5WVEJLWVtmV0VBUElJSUZrh4BGTWV3U115aF9heIhYU2FjR0pSY19Ui1xheV1SUX1lel+GSlN1Yn2Qh4Vnb3yEh4mIgmVklUZoYlZWUUE9ZzltcnRqbHJLUkRiXFhgX2NiYmBlcJWVUFJUVVZXgFeVYFlkUWxubGdfW5Z/hltub3V/gG+UiZ5zfmZNeE5gWX9te2OAY1NScVlYhmuQV2BmcH2FfqRveIaKhXVwe356eYCCgXp1a2FNmIV6dHJvcHR7hX12enemqXFxdnl4cW1qbXFzcmtnZXJvZmZoaGlrbWtqaWlpZmNjYGJjYmVoEW7VjIqJamhlY2JTkGBgYF9fhV6AXV1bXFxTcF1cXFxbWlpbWlxdX1yNUGqRdWpgjY5mcWBeSEdiYl1UdFZdT4VjYYNjjGBqaEiBZWWFWF5jRV98gn54VmxoSnJHS05RU0hngFBRUVBUVlhWSmqLbYxxSHxsaGVhW018Zmd4RU9VV1xhX15XXHRUXTg6PktLWGM9dW2AdoGAe3NXXmR5kahgbHaAYV95RkhcXFicdVpeYmZnZ199m1h9VmZbl59UWniRj1KWYpqWil13Vj9KWUxgbXR5enx9fn5+f359fX5+f3+BgoKDhIOEhYaGhYeHh4iKioyNjY6PjY6PjouFfndybGRdVJeGdWZUg2ler5+Rg4tiiFkacIpQX2x7houMjpCRk5OSkZGQj46Pjo2MjYyEizWKiIeGh4iJhWpdX3GHfoGVaFA5TDA5U19rZmVQb2NgcUE1PD9AQUJBP0BBISQmJyktLEcyPIDM+ayRkvnrlNKkqr3jmdDlqfT5nb6JgIKBcVR2tUtpdnnFgHtvTTNLPUVHRjswKx0jH0lde568OFeSzhJSeGqbfZC53mVhe2W6fWx83JGSpq17ETa+dIgcT7d7iOqttXqGoo1DOYGtnkVLrKevv0yluLpmS1BnZ2ZfU2VpY19OnoBmksemqMuI47vIi7Kk5MT88b6TsbKK14Xdl/vYlNuS7rGT+aXxn5HQlbyYnseYxaKJzpHK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QS+MWYhvcXhVZnGEdVh1bmR+WXlgkHdOjIOCiFl/QDxZYGpLm4SZV1enkV+EimicZaaQbI5WY1pkfDpjXElPYllBOT9mfU46REV0Nzk/OT81SFQuRVIqUjVOWFdXUFhERk49Rzo7QFZAXjdeVzI8N2heUpOOioWHiYuRkpebn6qXrI3E3GEzMigaGyQkJiMxOmBmRy0uJhg0y9/QX1FOS5KMh4J+fHuEfIJ6hXmAenp7aHRudYJKVGBwRkg1QTdDS3UxQjE4NXkrNUVCOENIWFWVWllbn1tfYr2ru7e1m6ysp5WlpaKckZ2cmYmTkY+DhISCeojqvKS2j693gaKdb1pePrOCb1JPToBKWFlfV4p+XIF5RVNNgohIXWBtVFZ4XE1kYmNUZlhXXVtuUlmAXl+JenBXemJ+UVNafFxneWNPfHtcaHB9g4aIiIJmZZtCZGBXVUxKPTw9bjs8PERQdT5fdmlhZV5fWl1ZYGlscUx6l05QU1ZYWlpbUGdfcUxfYWNjYlmIcYNkbHFwUlWPj359g4dSdZNpqF9gjWyapJ6Jh1RZVk6MkVBUUZGWZFViWVWPi1dbUY1NUU+Whk1RbV1TlIuEgYOJlVVNhZCPiHKHiJFPUIl7eoGJkIt5cnGSd29vcnZ1e4R9d3R2dW1ndGJmaGhoaWpqbG1raGVkZZxPYF9eXlxZV1dZW1pZWltbbFiFWQxaWlpZWlpbW1tYi1eFVoBVT4hwWYJSVImCUol5WFFbUWtUZlZ3VGtLVptkVXd9gXtsSmJvXIFHSEhJiF50aU53TlZZXF1LZnVtVYVST0pWmHqhjWVsQUpPS0tNTEtCb19+Oz45QDxQVlw2OD5KSVZjPHdtdYGAfHRXX2N5j6dfa2KZUVhehWZwSVecdVlbXX9hY2JadpxUfVVoWZaeU12dloyBlWR2pVp8gmRKTkh+nZqBc72+aGJugYNjnqexYG58iJZOVl9iY2ZqaWppamhmY2JcWFKUinlvamhqZWNfXltgaHqPpV9xgZRSXGZwfIWJio2Njo6IfXFkVpJ3WXdkr5mQZpdpg1BjcnyBgoKEhIU0hIWFg4SEg4SFh4iLjZOYnFFTV1pcXJtmVVdXOzM0MjAuLi8xMzc6PiEjJTpBNDcwPE5mdoC7+syJvNjQqLyTt7T6wqHXcZXXZ9S9enF0dVluo0w+dXrHhXt6UzlN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dmOVfmCGeVNhYoqKZICVW05Nd1qCdXWsbnWGbVKNe5CbnIZeeDdTO01VZnd3WoltiWiEX1FvWWeBjiZPUYuRWkhWiVVQZWiAU219VVo2PHhQWYmWmlxaeWaJmF6FcZOjdleGbp2YUldiWFtlX1xYVlpiW5yVooCYcaSyqI+tk3NyflN2R1IwXlRQWmNFMy93UUQzQXdQeDg9iVWFa21yU2tqcXJUcmm6fVd3Y595mYaEhohYekI9WV5sSp2Fm1dYp5FggYponWWmj26OU2BUYXotYF5IS2BUPGY8YzxKOT8qcjcoUzgsWkVOLUNVKk81TTVcVVVQQ01LOkQ4S0JQPFxnYD9fRUVVTo2GgoF+f3+AgYGAgomVp/d7cFlaJz9AHxolJyopN0lBaJu00isZLzo4gk9YVKCYkYqEf3x7e3z28/Du64V1gHHQpF54gopOVmB4Wi9SVF1ARzdtb0M6Oy1IST9MN0GzoaCNqaionKe0t7SfraeijJmXlYWSkY+GhYiIhXl9e3fd5ODVycK3p5O8naWBlpmKYUhNPMRcfk5MTXRGVFhdTYR7bF6AeG1RfIhHXFxsU1Zda2N3Y2JeWV1eWltbYldcgF1feYBuTINea1RdUItYWl9ZTk5maHB9g4eJiIJmZZpDZWRYVVBLQHJBcD1BQkldRERleHFhZmFkXGBWXmhtb4BTgpKbUFNWWVtcXl1RZ2N5UWNkZWRkUFVuhklSUG9lTZx5U2yfY292c21lop9/YV+emF9kYlxUUVdZWFRaXntkQWJQT15iWk9VXltWlVRndlpbUpyVkZKXUl5XjE5PlGJ4lJ1XWZWChpGXT06AeXmhenV2en98iZeMfHl+e3NrZWtnhGgcZ2hoa2lnZmVmdmNgX15dW5uHTpFYWFlZW3pTWIVXgFhYWVhZWVpaVYxVVVVUVVZXV1dYWFdWVVCETk9oh21RiItgWXqPZVFfoV9ObnmAfW1IY3dehEZISkp7TmR5ZEdTWV1fVneJaFCFXWloYVaHYUt/iEpSU1FNS0hCaGRTak1ciz5AOjw7T1NdNzg9SElYZT13bXWCgHxzWGBjd5CngF9hY09UXUhka0aAk5x0WltbX2FkW3eioH1WaFmYnVVhmJSEe5Zlj053cIpoUVZSkG52ipdTW2Bmam5xdHd6foKEhYWHh4iJiYuLi4yLjIuMjI2Oj5CSk5OSkY+MioWCe3p4cmplXlhSkn9uX5FuW6SsaYlWaoCbW2h0gIiKiYiHSH5yY1WHYYpmXZ6QXJBlglFhc3p8fn9+f4CBgYKDhomNkpibTlFUV1iZZU1NT1FSWko5Rk0nKCkrLjE1OjonNz82VzVrQ1FrcICP4qz6rsy/obiJt6rtgp7QmIaVbM/JarPgc1drn0qAc3vJg319VThMPENHRjsvSBskGlNleY20aKWEyHSidGtxUoSm1gtZZ7bPWF9uyF5VhZHKNGmsdI0fPaZwefrL0Xt9o42fhnmkj7iSimWZiJ2ZVVtwT1JiX15lVVxkYMK1soCuis3cyb/1xqShu4e8hN6F/86Upb/SvZ7Zo4Klydub1rSI6ZfmrLDBjbegqsGLwJb/yozDkv3D/dnu9+Cazb+rQ0VMMp5LSSkqVU43h7uY75notnmWz3ibwfeAvbiSuse5hfGC3IWegZ2I+oOFsIOG7529ipzKjPmBt4z/ytDGmIDBjp6SrJrGjNTn3YjMkY6qkvHOtqygl46FfHNlWVFMQJ785ICVs9Pyw8LV//n42rya7baL8oXC84mKyYWPiP7t2se3ppiLgXrx8fDy9n6BhIiLhvjFmd3o+ZKtxPbPj97j47KsgPHwmKeAo+rQy8+xitCspIyXl5mMj5WVkoSPj4COe4mHhnaCgX93eHx4dGZqaWSxtLGplIiHk3yCZotjesvPnYiektyh9pecnOaKqKysj/rmv6Tv2caZxO+NsL3ErbKhhG1vXWtlW1JWXV1dUkxjY2ZvYmVJellUQFVJfVJJPjgsKjU+QUpLSkhFPS84kDVEQzo2MzAoSCVCISAhJ4AwHBwuRUI0NjEyMDQyOT9FR083WmBrOTw/QEFCQ0M5SkNRMTg5Ozk/MzA5Ti01OUlFO5B/YIfmmKaqoJeN4OzSpp738bLAu7CblaOqpp2tuuvHv46Nwc+1lKCvqZ79n93jtaeG8tXL0OaKuqnkiYfnnL3j/Jmk9r3B2emFh76opTf2qYuNmaqlzPjPnZWfmoZ1cWddXV1bW1lYVlhYVlhZWGdPSkdFRUuRTyhTODk5ODdLMTU1NDU1hDYNNTU3NzYzUTIwLy0vL4UugC0tLSpEKig0PjIjQT0oJjY+KyIsUy4iLzM1Mi4hLDYsPSAgISE2JDE6LSAkJigrJjVAMCM7KCwsJyM3JyE2Nh4g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8/P29/j5/P78+/r8/v79+vz4ZPn4+fj5+fj4+Pfu4tPCsKCP/NKj6Jb/lcyEsd6In7nO3NzczrWehMyUvojyz8+lh7rulbC/xsnO1djf5uvw94GFhOejgf/8/oH/+fr38YFHTyosLhYYFiA0R1g8ZV1Qok9sjI2AjWFOemNrY2ljTHJhlKxeg4BXiHB1hoB5gVtQT31YfXZ1rG51iW1Sj3uQm5yGXnY4TjlPWGp7d1iNcYhGSV5Qlllmf4owQ4JZRWZdVmNJS3iDbJJ3e29lTnlTg1JImJxcXHxoiZVcgnCYo4NbjZN9hJVbZVZZZF1aopFTXVCvZ1iAkmG5dWaNVpJ2c39VdEhSMC9WUVxjSzk1dFJFOkp2Tm5OboJSgm1tbVByaG1tUnNjsXpUd2tReZqEMjuJWHdDPllcaUmmh5pXWKeRX36LaqFmp5Bujk9hgVRYM0RHPWNdVXRodV5fQ2c+LW9tLlU5Olw6Si5DUihOM0wzWlNcTECASTlBNT9lR09oREpXUHVeV46Gf358eXl3duvl28S0pr2VIitHVy4yOSIZHSIkIyxFRD1LnbrBOS48U0s+jlWijmqKhYB+e3p58OjQtp+H3rWKvoeMjr9LhkVMVWRFOjpNTDs3PmZXRlAzL0xJNUBFVHmel5aMlqCinpWprquUop2AmYOMiYh4hIJ/dXl6eeXa39nSxMW9t62spaKQxaauiJ2jmmVJTTuUaEZJSkxsQlJWW4iAc3JlgnuDV3mLRVdYaVRObVNmYnBnYmFcVmhaW1dXYV9bXWyEaItJWV1fYX1wYF9UTktraHB9g4eJiYJmZZtEZmRXVVBKTEg/PD5ERE+AYUREYXhxZGZkZFxlWF5ma3CAR4qKlJ9TVlhaXF1dXl1Ta2iDVWVsb2B8SVeKj3SAXHF4g4mRpXx4hml5eGNdb09mX1xscXFwODdramw5OzprTXBmNjo6N2xsbWpnXWFram1jXVpWVFNWXGZlTVhZU0pXUFZcXFCUklBTV1uLgYMVWX57gIePhE5aUoSBiY54a3hpaGdnhGYbZWZnZWZmm1hiYV5eXVyQc29WWlpZWopMV1dXhVaAV1dXWFdYUI9VVVRVVFVWVldWWFhZWVlOilZUVFKPcVR3iFRIS1yUVI5te3p0YoVhfGWIj0lLSm6EZZB5U1dcYWFJW2uIe11vdnd0Y4FJaoJUYGFZVlFLd114dWOIT1JXXpBDQDs7Ok1RXmw4PklNW2g8e211goB8dFlhY3iPpls8ekhLWU1maEZqXF1kdVpcW2BiY1p1oqB4VmNWmZtWVpSOdm+ZZ1FcX6eUblpwgEuMnp6enZ2blpSSj4yLhooFiYqLi4uFjQyPj4+QkJKTlJSWl5aElwmYmJeWlZWTlZSElV6WlpWVj4d+c2thVpZ9ZJNmqGKFWHCKVGFxfYODg3trXlB+XXpjspaZd1Z3lVtsdHd6fYCEh4uQlJlPUFCOYkqSkJFKlZSTl6lqSV83P0YnKicyPT1EOFZOQnJBT2lsgPjPnuiiwrGssYCuo+aPmMSai8+Cs8OBk7hxVmylR4J1e8iFf3tbOks7Q0dGPC5IGyUdWGd6l7FHp4zJK1BxadFafaHWG06gb0pGc2iCP0KVo5h/iZaLkkePb7JxgdTZgYCrjaOGdaCLxpiUZ523eJCbXHJTU2BbW8+gVF5X13pegJ9y5Jd8uHnIzdC5j7OQ4YeB1qCoxPDjudOohsPs3JjHx9bgj9y0uL2JwaKqvIjAjfjIhsCzjsj+1pGp4p/LxK1DRUw1nEtKKitWTjWHvZftmOa1eJXAdOGeqoWGjoD5vrT66fnP5JDmmpvw/5u0iLH+i7KNmcCF+YCyh/rB7riSgLiJlIiQ5qOu8ZapwK/0wqT02r+wpp2WjILx1reUbFBOM4P/qO2oxeDevLjf7frmu5KNiZuCusPywuGtlvuG/NqhxLKgj4F1bdPLtqGQd8WcbYNMUlrEjvyFlqvPm4O267+Fp5L8xq/KsJ7b5KWhn9PYqpOKf4CGiIN3g4eIdn98gH5venZ3bHRycGdsb23QwsXAtaWknZiDenaLgYttmW2Czd+oi6KRoc2KlpeY1ISipaf17NDEse3a8qLI9Iims72sj3Jbc2tpYWloYEZhXV9ZSF9oX11cYV6IRlNDSVt1Vj85LSosOT5ASUxKSEU9LziTOEVDOjYzLi4qJCMhIiEqgDEcHSxDPjg3Nzc0ODY6P0ZKUixcXWdxPUBCQ0RFR0dHPkpFWjY8PkE2Sy03X3FrkVVtfpSmvvmLMzJkzr2al86Vuams3u3y+oGA8+74h42D66Xx24CPkYb97+ri1LfJ8OHQvqadjoiHj6fW4IesrZGBk4OYq62N+OqHj6C08cvGMoy5oKi/2MKDqpG8rr7P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janHiNiA89bBrZPfs4eFyfyHh4SDgoL1ro+SnKGiwMrd/vySqazF4ILArrzMxr2vhJaRq8rlzvqAh6qczM2Su7G2oce5hpiHopKoirt/ecJ+rXvl6YDz597Xt3Ccc3ydjGKst+T/lfiHiIeEg4H69fLv7Orp6Ofn6Onq6+zq6err7u/w7/Dw8vP09vf5+/39/oCA/f3+/f38/Pz+/f37+/v6+vv7+vn49/n7+vj49fXy38ezn4bdq+lGmOyq/qnYiaTAzdPNuZ6EzpG0ivrMz62RxYSqws3Q1dq5u9DC4+nx7/P19fDm4+Ta3nZ+QkdOJTd7haRGU2tec1avT26Ii4CHX1J+X2dhY2NMal+Qh1uBdVmLXF+CSU9PWVBSgld7dnasbneKbVKQfJCcnYdePzZPOk9bbHx9Q4p1h1hLXlBBTltVVltlbImCYUyFS3NrT1aWi2RaWptzWVtKSEmcnVtfgGuMlFyFcJqhiF2PmqCbX45onKNjWlxkYlxkVm91dIBsimaTbY5XkUVKgVhxTlIwLlZVWWQtJyJxVkhHLHNOZUJvglB8bnBsTnRhZW1Pdl+vd1Fzdld5T4YwRotedkQ+XGF4XLSHmldXppFee4tromepkW6OTGRRh4JHU4B7U3p3Z2dyXFtDYzpDbGo3UzlBMmU7M0JPTU1kSV9VT0tKPoBGOT4zcEJbWTlGS2VRVkpieF90d3VwcG7Ux768x+aBkqOuNZSBTSkwJR4eIiMiH2F4cMXCsMGPJiovLllKVZqelI6EcLqZeLb7m3nl3dfMxWdoamhtakVVSU5XaW4/TUk6Z2tnW00/PysvPEFNTlJMmYqPj4+FlZqdka5fsZCYk4COfH9+fG53d3TS3t/ezcvGwLizsaynoJ2cn5FnrbeRqbGjX0hMPH5pf0RGSWpCUVRYSH5ZbmmBTYJZcYdET1VpemZZXFZlX2FsZmhYT3JbWVZObV9bV1+JY3lUVIlQW2BbUlNYZ0dpcH2Dh4mJgmdkm0dmYllWUklMRzw9QEZFUoBeQDxZdGxlZ2VjXGdYX2VqcIWVWX2WoVRWWFpcXF5eX2BgVIB8ilCQald7UWFmboBycImZqVMqXn6Cql2JgIB/QmBtPUBDRUdJS01OT05NS0pCNkZOUlFOSUVDQT8/REVDXWJxbmlmZWVpbzpfZGRfXkpeX2JaWWJSVl5ga1SQjDVfg4WKk1KQWGZjj4ybUn1brGxpZmRiX2BhYWNjZGVnfWhjYGBfYF5dl1pbXFxcTo5YV1dVVoVVgFZVVlaWklVVVFVVVVZVVlVWV1hYVoxOWFdYWVhYWFpeU4trTF1bh19scmd5XH9qj0tOUEtpdGxajVZbYGRZeH91bldxeX5/dFBebk19RoJvZFxOeGRPTnaTTk5NTEtKjWg+PT0+O0tTXWxvQE1PWmc8fG11g4F8dFlhY3iPoop/Xz9VTmhlSl5YW1NlbFhaXGBiZFhyU1N8V2NWmJpXoZuWlHdQalRUbWlPc2R/lVWcV1dWVVNRnpqWk5CPjo2MjYyLjIyMjY6OjYyOjo+RkJGSk5SVlpeYmJlMTJiZmpmYhJdjlpaWmJeYl5eXmJiXl5iXlpaWlZSRhXhtYVKEaZNkqHCibIVTYnN6fXtvXU5+Xnpks5yXc1p9UGd1en2AgW1senOAhImIioqKiIeHipCgYG5AS1wwP2VxeTpGX0hcRHc/TWZpgOm/nuqZuqugr4Gcn9yujbmRiM9diLRKZGhxWW6oR310e8mGgX1YO0w7REhHPS8mHCcbXmp8l7MwqYzIN1JyZzkzb2h2QW1/qqdaYKtrb2lnbup1anZ382Nqd2JchdzggISojaWIc6KJ0p2XaZzJnZhjkHevoF1XWoNvWmNZhIp3gG2UecKJsnvLjrPAn7Gu6ImC262mxqaqg82ykPmT0pS4jtnhjNK8wbqDwqCsvIPAhv3HhL3UpciD46vP6KrMy69IUndgq0xKKitWTjaDwJv0mOq0eZa3dIv9/IKX9euO7vja5vPE0YzZjOfi+suxi9SH65uhlbP87P2m9u21uqmNgLCHioL0ldq/g5em5K7Gn8bToKmlnpeRh/fdw7Gqq1RQTIys+qrXiLvl7tXa3t7EspSO276UiP/Vyq+L1sav+O3WxKuGx45dcGocDBYaIig1JCkuMTxEicKTn7bi+PTUyY2RoZ6nvtG1g4Ojr93Gv5uvhYN7dWt3eXdmf0WHcHJxgHNnbW5tYGhnZbrHyMe9v763ppyZkoN0cG6NiUx1pHSJ1e+lkaeWlsD3jJGUzIKen6CE6JfBuvCK9KS96oaNqb2/dl9OYHFoWGdqalY+bF5dVz9sZF5ZTWJZbFBMWzk8OzcwLjA8Jz9ASUtJR0U+LziPOUlFPDc2Ly4pISMjJCQr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z295wMyrpqOkoJyZmJmZmpqfo/eWpZ+enZ2en5uZmJeYmY3VkpCEjiCJiImJiYqLi+38ioyLi4uMjYyMjY2PkZGB8ZKQkZOTlISVgJeVlJaXis7WqfaapaCj2/aJjYGgr9O2ip6krq+ElbudisXb4ePWnb3HkZfb9s2e5KL06b6h3oCCgYD+98yT1IGZgYyVm5yeu8TX/vqUqK/C1/7BrbvNx76vhpWPrMfm4ueWmsbPlqmotqmtkb3BlImikaSFun56x3+0fN96gersKuZ3t2ybdKKXjWivwu+OpoaSkpCMiYaC/PXz8e3q6Ofp6urr6+zu7ei1yIXwI+TU9fj3+Pr9/Pv9//+AgP7+/f38/f79+/n7/f79/fv6+fr6hPxR+vn59/X08u/r6+rm0bmjit2i2OmT45rNgZ+5w8GvmPnDh6eI69KX96/bg//6v6TE2drX3d/b3NzY3Nvf4XFzdll8dFqCuGSPdoZ8V66da4WGgIVZUYBaYl9gZJxoX457WHtvV4dll2NIUE2ET0+BWIB3da1vd4pvU457kJydh10+N085Ul9ven9EhnOHRUlceXmGakV7a0qISFh+TIRKdm1UWJ2KYFRWV5JkZF1aTJ2gUHFeWYqRXYdxo6GGXI2rnZ2VSamnXGNbXnFuX2NVloWqgIyBZpZyjleYIDCIWnNNUzAuV01KTiQhOkxOaUQqUHlJYmF7TXtxdW9QeGFndVh3YFl2UHV8XXpRiTlMjmV6Rj67ZHlKq4iaWFekj114j2ykaaqSbY5LYVCGgWxbQktXWHdQb3FbWkNla0poYSdSOSlEbThVQE1LS2BFWlJoR08/gEVuQTRBc2JaOERIZFBdSllCSVdabGlnZc3Nz9bf7ICLnrCsl3ZnMBcUHCUyMjApKFZXk/Gxq6YqJCQmLltfTVl8lqeEenRw0ca0ppmYl5eVkp+mqqWtp1dCRUZfT100SlphU0ZOSD1UPl1bRi5BUayLeId+in6RkZaVboWynpSMgId3d3Vz0dzG2cfNzcy+uLezqKminpWXk5GalGmxvJm1wYxfSUo8k1NBREZGaIRQUlNQe1JcZ2VWg1pviUVNT195YWRQWFhjXFl4aWZOSH9cWVJIfWBfU1ZPa2xMcVpKdmJ3TFxrRGpwfIKGiIiBZmOYR2ZiWlVSTEtAPTw/RkNQgFdxaE9vbGVpZ2djaVpjaGluhU1dWpmjU1dYWVtcXl9gXVdQTI+Mg1FYW19jZ2x0f42djFppfU6HhWjLXVZTVFNUVEI+UlkvMTQ3OTs7Ojc1MzJPVzs+Pjo2MVxXU1NXWFVRT0VCQD8+Pj4/QUNGRkI8QmJub2ZpOWVJZGg3N1xOUm2KkJpRWk5bSHFQm1VgkZN1cGxqZWJfXmFiY2NlaKBiamZlZGNlZmVjYF5eXlaDWFhXVlVVVFRTU1RVVVaPmFRUU1NTVVVUVVVVVldXTpFXVliFWoBbXF5aWVpcVYCCZ5Zda2FdgI5NT0hcZ3toTVlcYWJKV2xWTnB+goR9W21wVFh/jXVVflmGhnBfhUxNTEuTj3lWdkJIOjs8Pz0+S1RcaW5BTFBYYnV+bnWEgXxzWmFhdoydcmxJT2NnTVpWXVRaSGFsVl5eY2VYclNUglpnV5hRVjScoJxWfFFtV3BqblF4aIdTXlNdXl1bWlZSn5mWk5KRkI+Oj46Njo+PkIxpdI+RkpGRioGVhJYKmJmamZqbTU2ZmYWYBZeXmJiXhphXmZiYl5eXmJiXlpSTkpCOjoyJfnBiVIRniJtikWSAT2FweHVpWpZ4Vm9dqp5qpXCCR3RuamRvfH19gYB+f4CCiJCWoVZeYUhaV0Bnf0lmW2heQ3d6S19jgOayneWMrKKWqvqVm9WkiLOLgMRn1ohJZGOcWGylR4N3fMqIg4FYPUw6REhIPC8kHCUaZm6AmLUyo47KNVJsnJt/glWzWlioXm1vYaRpd2xsc/p7aXB1j3l1hXx4jOfqa5J6dqqJcKGI26OWaZnfq5+UTc7NXlxXW5yDW2FXspaxgJuNgfqosX3bkNfjoa+x74qD37KjsYaO1aKp4e2MnuyEy8bWh9LE072ExKK2x43BjIbIgsDrvsiN79bw7LrXzq64W2YunUtKKytWTTaAw5z4muy0eZe4dIX28seqgIuHqfWMouzCzY7c9dPY4ZOuipfV94v2kKzq4+ac493yuLuKgKT+jIGL/+6/gJGd3KXOm7qNjp6Bl5KNg/rz59rOxF5WU52Ff4m1/qS9uOT06dWwgfqdmuSWjYyNqZ6Rlp6Yw6bR8viQWjATCgMBAQIECA0VHyszQklYiMW5yb701P2FtMT23rfStLr2tOLAnoup6tqPbnl4c19qam1kMj5XbWhngGlbYGBfr7+rtqy3ubu1wbitmJCL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8I18h1dkcz8/eHBqZGJgXltYTVZaXjI1Oz0+PTs4NDIxMjY+Q0NAOjRfWVhXXC5ZUkKIkpSZT1FTVFcvMzMwWklgS0tIR0hFdYSHRkeAdoClXmttb3NodX19zMfOcMSzyLWtrqOfm5mdoqOjpK/Vt6qlo6GjqKqin5uYl5TOlJCOjoyMioeGh4iKi4rcgYqIiImJiYqKi4uLjI6L44aUkZCPkJKVlpeXmZuamYnM7JGRl6WwldWWv42vgIzt2pSZoq6az9rn363V3+jksNXejoCY8ZqemYGbo4zqv/mGhYP/+e+/9PSV/5yajP6Un6ahor/G24D/maarutH5v6+8zci9r4aUj6n62b6EmrrMpaemsqKlgr+zz5aBnZSjgbN4d8aCtXzlf4bOeOyOprqZc7KgnWi504GWsI+ampiUko7O5vr28+/r5+bn5+fp5+jo6CHs0dHx6+3u7efM8PT09vf6/P/9gP3+//3+/vz68fL2+PqE/Vv+/fz6+fr5+vz7+fj49/bz8u/r6erp6OXk4+Pj39GzmoXTnMTyofCy6pSuurukid6Ztd6dper9w6Wesrq/wsLBwsTM0NDS292mhWmvdMF+uHCJoYl+raycan+BgH1TTXpXXFxhY5VnXop0VnhvU4NkkY1limFIc059VYB4dq5vd4xxVI57kJudh11BOE08VV9se31zgnCDY11We3hQR0qJhUpISlt9S4FKeG1UW1KMX1BRWpFhYVhXTqKmYGZlTY1eV4l3rp6FXo6roU+RiH+KYmNeYHd3Y5JciHVegIp+YUhIkFeTKS+GVVQ8VDAuVlFfdDYrKGqHTEgra2xdSnVSipqMU2ZSd2tydlh3YVp1TnNDZ3ZUikArkWmERz9UWnNIV4qcWFekj1x2kG+oa6mSbo1KXk+EgXNagEdgd0xTfHFaVmBGVTtkWTpNZi5MaT9bXUlIR11CSTphR1U/gEFoP2VEXmNXbEJHY01aSVVzTkZjmGRpaWpp1Njj7/6Jm7aulc5LHg4QFiMzOTQ3Njw4Vc3AoJ5lJSEhUX2Hg1I0Y39tYruvnYl7c2xpaWtsbHJ5f4OIkpVoWFd9Tjo4RkJcV1xWaFZeQz1ANV5dmHx4cIKagnqMj5ORs5S+nY2IgIZ0dHFwzdW7xLy+wL60r62roKCclI2NjIyXlWy0waO2q4ViS0s3XGZKQURDZYJNUU5SeGJPcJFVgl1yhkRNWmJ9cWBeSFlaYFRShWhmSUaTX1hKiY1fXVOPTox6Z2tDUVtWS4d9YWpvfIKFh4eBZWKVRWVjWVZQU0Q9PDxAR0VOB1BhYUxoaWuEaYBxY2pubHKHTl5alZ+jVVdZWlhRTJWVUFFLUVRWWFtdYWVrcniFl65rY3NNW2w8O3BrZmFiYFtXVEZOU1kwNjxAQkI+OTQxMDE4Q0tKQzozW1RSV14vWU87c3l8gENGSUxSLjMzMVhEV0REQUBBPWNxcTs9bF6BSlFSVVpPXWVplDiMl1aPhJCAdXRpY2BfY2Vpa2xzknhva2pnaG1wamdiYF9dflpYVlVUVFNTU1JSVFVViU1UU1NUVIVTgFRUVlSJT1hWVldYWVtbXFxdXV5fVX2RWVldZm5dhVxyUmFJTIh8VFdbYld1eoF+YXqAhoVmf39OV49eYFxLWl9SkXKSUE5MlZCMcIl7Q3NGRD9vPkBEQUFQV142ckRNUVhhdX5udoSCfHRaYGJ1qXBZP0xeZVRXU1pTWUNiYHBOY1hfZGRZcVNSgFxrWJ1XXIZToGpsiGtagnOAWH9tSmZqWmJkYmBfXIKVoJuYlZORkZCQj4+PkZGPkHt6kZCUlZWQfpWXmJmanJydnU+cnJ2bmpeXl5KUlZWXl5iYmJmZmpmZmISZUZiZmZiWlJOSkI6NjY2Mi4mIiYeAb19Sh2ODn2qebI1Ya3BvYlGDXHOfZ0lkbXJoWWRqb3Fvcnd5fIGHjZOVdF1OgFaGYn5TX3tmXoF0dUlZXoDSp5DTgaGamKTokJjLl4Gthni5adDEd7R/Wo5qpEaOd3zJiISDWz9MOkNHRzwuJh4lHG1ufZO5ZKCNxF1rZJ2dVVpf1XBbW2B0bmCdaXlubXaFfmlrbZV9coJ2dJL0+ISGgmGsYG2jiO+nlGiZ7c1TkIWPpmdcV1uhjV2RZrOcYYCaj32LqLJ9zIqqz5iSjfONh+eioMe4uozA6ZL8kLq2nYvYh9jz9JixiMe1x8mPwZOWxoO/jePDmfL8lfHG5NKsPT1NLVJOSisrVUw1fsWg+53wtXmXt3OC7+/cqPWGnPGXiajov7/Vl8Sb0sPaovC6/vGZ99Sh4tbTk66U1q/KhUeb6oX4jMnrtvaIlNKcwZWy/JeLseSPlZGLhPrt38+/WFe0lFmg0OKVssO4u9/p0ryw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WZK4pI/yyamYkJCOkpqgUVtjZWhweX1zOUA7STpBREE9OzpbPkJgSl9BXnZ5gIF5foSIfYaNjIanZK6Nh4aAhHFucG/EyMvHsrW3uKylop+XmZaOiIqIh5OZbrLEqJOyjGRLTE1cckNBQ0JhgkxOh1NxfIlnc1iAXXGHfExkT2RseWJhgl5ZXFNNkGlig0hPW1x/RlFhXYBbSGxmdn9ERYNEU2pIa257gYWHh4BlYY9FZmJbVkxTSjs+PT9JRUwFSllXSGiEbYBucHVsdnVzdYpMXWGboFNSl4+MikpNmk1NT1CUTVRXWlxeYmZpbXR/jVFcbERQWF1cWVZVVltYVFBNTExTLDA3P0I8QEM8NTEvMDVFKCglPjQtU1ArMDAsTD5tZmVmanRCTjA7REU+Mk5wTVBRUlNRTkpJSk1LQlhZY2JkbFxtPTdBV1dfOVVYUk6AgmpeWlxkbHd0dVp2g3NramdmbG5pZ2RhX4RaWVdWVVNTUlNTUlNUU4FQU1NThlIHU1RUU5aHVYRWgFdYWVlaW1xcW1R+iVhZWVpcqq2VdGlkWYxle1lsSFeSU2VeVXN9goVxSkuNVZVkam5igp14Yo9XWVVRTpeQc4hySUlEPmpCREI+SElMSkhVXWM5O0RKUVhgdn1vdYSCfHNaYWCsgE9vTFlmV1dRXlJae2VmbntbZFNiYllvUFOAgFxsWlFcYppVmW+BiW9gT0FEYodybU9XX2lqZT8vKU1JSEdJSk1PUystLzI2PSQ5mpKRk1YpQjgzLy0qUU9NS0pJSEdJJywyWkyWjIWCk4ZTlJmZnZ2cm5qbmpqbm5mZmpqal5WTkZCOjo2NjIuLiomIiIeGhoWDgn9wYU5zlFRmL51sik5wSWhRXWo7e3CWoGaATFdeZ25ydn2EiWlReVFDUn9edVR0dV9be2xvjFZZgMCYiMh/mo+fm9uNjr+Ve6qCcrNpxb5qkH1dk2SbRpJ3e8iJhIBbP0o6Q0dHPC4yLj8neW16kbhom2NpfJlaamtlsV3aeFlaXnhwYaBrfW1sdIV/aWRjln1weW1wloCDg4OLkJFZhnF/8p2NZZHydlaNhGB7jlpWWpmCaVZYf59kgJaUeeCZqXi68O+7iJuB+I+K6ayq0MbJlr/iiOaBuriqz7vX0c/20e/R6PqqwY+6mqTEgrejq8Wggd3g7sjj1q07PEwt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RV9Pf4JCWXRyfVVVV5RvVUh9SHlfWFBlPlFSSWBsXHJzNTZNUDo+Zz89doBrW3NXgUhVTGR9QluBVkRQaEiBYH1NTJpsbm7k7fiDjqO4r5fFz9w1KigmMSMoKFek+rGtr6uxe11ZcHR/jpCSobHTeo7MhsyUcGNcXGBjaHF6fkhxUFVgaY9DJSgsLzAxMS0oJCA0Kyu0Z3FwcG+AgIJ6iouGfYuNhY6alIaDh4CDc3Bua8HBycSrrbCvoJuYkY6MiIOEhoWAlZpvscuc1duSZk1OWVI+QT9CQWSCTU6dU2+Bk1aNVX9dcHRPU21lUGNjhGVkd2ZaXlBKUWddcU5UXV9vh4Z2d3VxbWhcTn9kcEZRXn1sbnqBhIeHgWRghkZlZVxVTE5Fd0A+QkhFSIBFVE1EaW1xc3Q6PD16hIF8eIlJV1+Kg4GCjI+OkJKUlEtMTE5PSZNPUFFSUlNWW2JqeY9YOEZOUjQ7Nmk3SFJUNTFfOUsyNDs9MzQxNUA2QDk4L0ktNTkmNTk0L0w/RCgtU1d0c21tcj1HUDA9JiU/MU9FRUtRVVpfMWAxNDQzMUFFPkVAPUA8QURGYmlwQ150WlmRkHVkXWFoeId+gKaMinNqZmNiZWVjYWBgllhdWVdWVlRSUlJTVFVUflJUU1RTUoRRgFBSUlKDl1VVVVRVVVZXWFhYV1dSfolYWlxcXVh8r7egcZJra21wbGdWfI5qjVJ2gYh/V12BkYljcHZpkl6bhmBycmtjXFVPlHFkUEtIR0dBOWlISE1NWlpYUVBYYGU6OUZJU1diPXZvdYOCfHNaYqZQRGJMUWNbWEpdVmNxZGl8f21bXHJJVWNcclBUgV5vWlJcb1pbnW9TSIJqWDtiPW5ZYSw7Vl5wbzojNi0mRT89Ojk5Ojs8PkBEJi4mn5WTlTs0Kkc/Ozg2NDY3Nzk6PEAjKDA5W5qWZ45WTX9vmJpIKyZCOTFUR3VZnJmbnJyOcZmVkpCPf3SIi4yLi4mIiYiEhziFhYaGh4iHhHFdjXqPRVJdW2s7PId4hWxeY2p1h55YX2htWF1uVYBTg1J+XXNZfG9ZVHRoaoVVVoC3j/64eJaHnZTSjIO4kHehfm6sZbu6h4d2V2m8i0Sdd3rLiYWFXEBMO0RHSDwvIhkmJohsd4+yUGaCr2RUW29zZ6Fa1XpYWV19cWGgbIFtZ3GKgmljYZx8a3BlbJ+Hi4Z/gYy8hW9+V4yxjmGLfJBeiYJwkpCHVVvngVN5W4zvkYCamIaj16brq4WEtIehg/mPi+moqcqE1qe72eCa3re/tL2p19TMpqK7xMHFlsvBtMTTxoW53Ym/nYDa3+7D4dWvPD1ILF5NSSorVE00gMqj/Z7tsnaTp22D4u2SpODb96CljNnesZX9j/resaW8hrPJps3v6/H3mpeesISN14OC5EbnwOLK/JC7ltb3iL75s4Oi5IX7yuSZgM+NiYHz3MddV13buHmId26A2ZuJsbzFvvawv6Cpu3mBsNr79MiOe3R4hHqd1atVjACAAQQIDhUfKZrhhZentLe/wraom47uwZTeYWNaWU5WVVROV1lYUVdWUVVYVVJWW1xQT1BQjpWdnpGYoqagoqWklZaMf3x2b2SenlV3uoPq7+G/obPBnYCBgISBsu2NjeWKstbok+2Q4qCuvmJYZmFRZleGbm1uYGVnTz5PZ1xnQlSAX2FibntSTEtHRUE4Lkg0OygvNEJAPUZHRUM/OSszeTxLTkY/OzcwUiclJyQlJyYrJyI8QEJBRyYtL2JkZmJbVyw2RGNZU09TVlpcXV5gMTExMzMxYDY4OTw/Q0lTYXif3aWAxu/+nKKG9YCixdqKgvLA46Cr0eW8vKu65r/hv7lul/SVydeJwNGvk93M1IGK8tLr28jI3oiu35/gi4falMiambXJ1OP5gvuDjpWalcOep5CMmomZp7LN2PKpydetqejpo1g8N1ePtqSt79fSlnpvampvbmlmY2CRUlJIQDo3NTMzMzQ1NTVWNzQzMzGEMAgvLy8uLUhULIUrgCwuLi0tLCwqRkouLS0tLC1MbmxgRU0xMTM1MzEqQEgyPyQ1Oz44KCw3QD8uNTcyRy9SSDI7PDg0MS4qTzs0IR4aGRoYFi0cHCAkLCoqKSowO0kvN0lRXmdwRnk2MDk7OzgvYbI+Kz8yKzQwMys1MTtENjpFQTM1Rjh7j5qzio/TgKOWn4qWu5KT/cqLh/rbuYPWgsHM8YbCzzc/RLSB0KOG5Mm7tbSyv8jP2ej+lLiPf2xrb7vOnPvZx7quqrm/wMjS4PqQsdTo3mllP1c2MlBJZWnGqozluZTrseNziX15eXdrV29rZWJgUEpdX19fYGBfYF9eXV1eXFtaWVlYVlNHLzhTRmMkKzNBQSkonJ5xWVZha3mRpWBocoSCps+s853tmNOlyZvVxpuSyK3A3JKcEnx9fX59fn59fn19fn19fn19foR9AXyGfQt+fYCAf39+fX1+gYWDBoJ+fXx8foR9BH59fX2GfgN9fn2HfgF9j34FfX5/f32Nfg9/f39+fn9/fn5/f35+f36Efwp+fn+Af359foCAhH4ZgIGBf35+foCBgH5+foGAfn5+f39+fn5/f4V+Kn19fn9/fn5+gYJ+fn+DgX5+foCAfHx8fX+Cg4SEg4ODf35+fX5+f4CAgIR+AYCGfgF/hH4EfX9+fYV+D39+fn5/fn5/fn1+f35+f4l+Cn9+fn9+fn5/fn2Gfgp9fn5+fX19fn5+hn8KgYKDg4OEhISDgpGBBX98fHt7jHoCe3qFewF+i38Efn59e5t8lHsGfH1/f35/hH4Ef39+fYV+Bn19fn59foV9H359fnx8fX18fH19fHx9fXx8fX19fH19fXx8fX19fHuJfIN7hHwBgoiDA4KAfYd8AXuKfIV7g3yGe4J8jH2GfgF9jH4Bf4SADYGBgYCBgH+AgYF+f4CEgQWAfXx8f4WBEH58fHx+gIGBgYB/fn9/fn6FfwmAgICBgYKCgYGIgAKBgIWBioAIf39/gH9+f3+LfgF9jX4BfY5+AX2OfoJ9jn6CfYZ+BYCEhYSAiH4EfXx7e4Z9BHx7fH2EfgR9fXx9iH4DfX18h3sBeox7h3wDfYCChoMCf36HfRd8fX19fHx9fXx8fX18fX59fn1+fn1+fIZ+BX1+fn59hH4Gf35+foCAhH8FgoSDg4OMgoODhH4DgYODjYKEgxWAfn5+fX5+fX5+foCCgoGBgYCAf3+lfgN9fHqFewR8fH1+iHyHfQp+fX59fn1+fX59hH4FfX59fn4CAgQAgNNpxJaQhfyejeFditK8icm6htq69vmC+qaUz9C7roCzq/qkxPzTmdy64PP31pCChbyZl622sPb87ffTvYKFjpOt2vbo14aFhJDHjeyFta6MlYTMm4uVkrKYl4yZdXl5oaSlt8bRqMvMpqeKkOaCl47s3pJFU83WsD9Pl9SXjkaPgNu8Zy4p0vzAGxm4c3diQSUkS2yBllZBWYWXjz5hfYmIeGuRjodaTHuFhGOleYNfP0aAV3kdLnhVixcxjWmLJyaKqOyZhc/yion92Iecuo1rid29krozj1+AhBNfdFARW1I0RHA/LUtDczFjd1ZjdWA9WFooaWUmJE9Rcz1mWHpFXEVYd053UERFVW5xTn1Dd0RHdF9EZDlYN12JioyPkJBJSUg+LD07OCRaRiojEw4QHDc6LDAUFxkeKCgdFyEdGgsNPkE+LGGCY+zj7oSMlae2w83a8PmEjZObGAEBiwCADKmLi5ifpJq0t7akvsS8osHGv7PNxba6uLKWk4eB5enk0ru9u6mikIh/fXNw2tPW4eWQsYrY/oHEgqdpRkJZVpJNTVBWgZhiaLRptbl5qM1ytHutlbnxkqzEiKmq+6qjwL+Wof+Fla2fwZSNnpCkieLE54iYt8bJztDKtZyPrKCAo7S/yc7Qy6CUv5Tr7c/IqqyRhJWSl6CMl4G4qpLU7/2Bg4iShPyNi/LX8oCZjNjc1tfk5+vw9PHy8vPz7/L2+YKHjI+RlJidpa3A2oFINSkxODBKVVxQSzNHRjkfMkA+QTMiHCMdIz00QkI6KxgqKRkjODs4LCYUIh0WK0lJi4mASE1YLBwtJTQoMEpQR0dCVSssLFZZLzY5OjUvX1dRUVNTUlFISEhNbnKBd2ptyp6Dqr3B2cqQsWpuu6qnpKSlpJ+gnYf7lZKQjYqJiIeIiYuMiMaHiIeFhISEg4SDgoOF+NCGiomKiYqKi4uNjY+RjtfSi4+Qk5OXm6O04b6NybaAuL+2rbO+xsnMwZvjjOS+p8PMoLvEt4K6mpTO+4L46dbArJqH3tjIqJqbnp+elI+FtLzCxuvo48rK1N3ojISbnLK52YKsr7vNyL2viIGr9LGWlcS0sIe6q8zOv9D4va+00O60qpCKuXx5x4S9gHiDkYiCh5d6Y1c7MFIxQ+DZKRyAJifirqtVEhEODh4eHR0cGxkYFhUUEggGCf3y8fQgCwwaGhkYGBcWFhUVFRQJCAkLDIL98fXq+diA8fryFBUWFRUWFhYXGBgZGRcWFhgYJuXq6tG45ujn5uXi4N/f39vb3N7g4uXp7e/x7OONhqTg2dyq4oaT1LWDiYHz4NjGzd0Z8PTVasSHd71+tnW4hKl5vZh9d6SglMqBgoB1SX9kUViiZFmJTVR8blJya016bY6QRpJXUHd0aFhEenevcniPdlaTe5CbnYhbPDVLQGFbYmCRjYaJgXZGS1ZeZnmGjINJSUldeFCATXNoUVhYhF5RWGBuVVdPVlVXWGBhYm2Lh2B4eH9/T1KGXnpejIJmSVR1hGtBVGB7XXNJdoCKe2I/NoyhhC0qiFNVRlQwLlJWZHhOSlZvgX1FYWhzcnRmgX98Wk9uenlenGlwU0VMdU9vM09wUYE4UIdlgUhAVl1xSF2Nm1lYpI9bc5RxVmmqkm2JQ15QfIIwWHFbKFdVV51rQFBESHc5XXRoWXF9PlpiOWxuMjZIU3U/Y1yAR4BKWm9Td0tMRl94gFaEUoVQbIp0Yn5ad1qM0Njd5fD9h5m3sZfDytasdEw2MigeJluzqYvcva+qlHhqbHHn4vmFh+6unrm73ovgkHU4Oz5ARU1TXWpzP0VMWVI3PygqLzAwMC0qKCQgNo1cXGVscGt8gIJ4jZGNgI6PjYWWkYWGh4CGdXNua8LEwrilqqqhoJCNgYOAfPL6//n6kp1wrdFqpHGYak1NWEx8QEBCQWF5TU2BTn9/V3WOU31dbF1yj1Vjc1BjXJZnYmtuWGCSS1ZkW2ZVWGBUXEx8bXhHT1tkaGtuaV5TS2FsbXl/g4aFgGRfgEVmZlpWS0tCP0RBREhDRYA/UktDaHB0PTw9RUaNTEmEfYpJWFSChX+BiIqMjpCNjIyLjY6Oj5JLTlFTVllcYGZvf5llQDQrNzg0Q01ZTEg8TEk5JDVEQUY3JiEpIihBOEdIPjAfMzIfKTw/Oi4uHDEqHzZDP3JwO0NTKyE1Lz8zN0hLQklDVy0uLFNXMDg+QD45MVxRSkxPUlFOQ0FCSmJkamFSV4hcSWF8jqCPaIJNU4RzbmppbWxoZ2ZZpGFfW1ZVVFNSU1JUVlR7VFNSUoVRhFCAloBRUlJTUlJSVFZWVlhYVoODVVhZW1tdX2ZxvLF1gHJydnBqbnR4e313X4lTgW1cb3lfcXZyUXReXYOhU5+ThXdpXVGEfmdWTElFRkZAPTdNUVhecWplV1daYWU8O0ZJU1dlPnJvdIOCe3NbVGd5VUpMYllbQ1xWa2tia4RkWl0xcXhfXVJfclVVg2B0X1NddWllb3hiW1NAOFE/RZpvMDBJMpp0clNCOi8nRT87Ojw9P4RANEEkKyehlZSXRDUqRj48Ojk4OTg5Ozw/IicsMiZXmZCTi5OBTZOanjA4NTMxMDAwLy8uLSyEKw8vNZWRkX1sjI2NjYyKiYqGiTeLjI6SlJWVk49VRVBsam1lbj0/ioNOUk+cko2Kj5mmopFO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jUxQbkaIXmheSnt3sHJ8SXdWk3uQm5yHWz41TUBjTlBpamCIjIN4RklTXmJygot8SUlKVmlPglBlXE5WWoJeUlpljV1aWVpXWVlfYmBtYG1hdXZ9koCIY0ZGUoyAejcmWlJUJzliflx3SV2AW2ZGJl6JVJM3MYlaVEZTLy1OUWF6XDk9b4B8Njxpc3F1Znx9eUREbXh5WVhufnhQd4VwZ0ZobU15NVeEXXtHQVRebkhejJtZWKSOW3CTcKZoqpFtiUJbUnuAIVhXfDhZVmMyXWVZRkR1PF12WD9uYmFSXldqY14zSWF0QF1oREaATVZsUHNeQYFedHlUgU6ET2VGfGCAX3pQn8q7ys3V3emGs7GStbvFnqyytTdCJx1HU6u6keG3oqXV1auDmb6VkZKwtaz29Y3Ww7VGQEBIJiwzO0NQWWM1PlsvNkIlKzI4Nzk4ODcuJXymXGJganBxb36Ee4hpnn+PkpeLmpiOjJmAYHN0btTAzsSyqKqknKGKgnx+fHzz8+7g5Iqeb6lqz8d2nW1OTE5Of4B/QkJgeUxMf5mLZJdxkFB7UmRTkHWPU1p+Tl9ZUGVfYHdYYIRRW2ZcVVZ8cnV2b2daTHxfZWpmXFRWaoJWbGx4fYGDhH9jXHVEZGNYWExMPkNEQ0VJQkaAfE5JRGg6Oz8/Q05QVlxRjYKKSlhXgYKBeXZ9fn6ChomNj5KWl5eXTVBSVFZaXGFodIlbQFZOIB8jRGBpPGZLSC5bRS5HKyguSzE6PTouRiooLCY4OjM6Jyw7JEM2NkFEQTA1U0R1cz5LMTgcNiQvJzRVL1k8OEwzNyxEMjhAQz5JOjBORUdOWWQ0MVhTUVI0Ukw+a2dTZFNlk1BdUWGaWGlNhYV7eoiGe3dsnWRjYV1ZVlVUU1NUVVF3U1FRUVBQT1BQUVFRUIaLUoZRgFJTVFVWVot9U1dXWFhaW15iZ211fn9/e3JscXR4foSIi4+TmJ6VcYpVgGBTbmJvVnF5enp3cGVXkXBUcU9sbltLR0NERkFAOVNYZHREPnBjW2FmaT0+RURTVmQ+cG90goJ8c1x5YEZHR2JXYXpdV3ZfYGpIW1VbdWxbWF9QmHRZgFaGY3hhVF0/c21Bem5lVWhTTVo5S245Lkk3kmV0dSlBME1FRENBQD4/QEJEIyMkKCejlpWXTjMnRUNAPTw7Ozs9QEMiJCs3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amRWh1twel9SintjTEdERUg/QDxYXGo9S0Q8al9la2o8PD9BVFdkPW9tc4OCfXNdjXZ6hGJabG1cV4JVWmVNVVNaemVZVllJX0pqW1WGZXxiVls6dnBQfHs0UmVXP1pOQW04OzQ6m6F2dUA9KigmRkA8ODUzNDc7QSMnKyeApZeWmEYvKUpCPDcyMC8xNTwhJCktNT14npiWlYuBVplPcBwdODY0MzM0NDIyMjg9IiocISlblZKTaCcuLi0uLisnRjwzUW2WiqNTVFZYWFaPV1xMZ0dGSIZMSkqbkG1pZ2JiZ29um5VwaUpyXlJ8W3RNdZ1vil1qVJNoXl98UVKAks7Co2yD732CvaR6qIxmj4JjoTqwsllirpqMan9VaHt5yIqHiGRCTDtER0Y6LigZIx+pXJ+gxWWhn9N3qlprhmaUVe54W1tdjHVgnHeTcmVsnFZ8ZXCxmXJ1eHaplJGGeXKGp5xuf4DK+rOedoa+qpGSgoa6VH9cpOpdelSUr5GApVzPsoSiiNyWjM6Wko/6jobShJbAsaGHt8zX7riovLa9nsnDvISeur3EhYvCyLrg7LSk+7HOtIXAjM3Nm7/FsD47RCpkTksrLFZMNH/Oo/uZ5LBzj6BmotPqmZ6ZhO2prKnZofvqkfzkq7PmyoXn6ruAmrDTwdaA/b7oh6uAkYRJt5rDp+K27IGzztie4pbqkcKA8rzYutK4sOOznp+HdGh7hOymr5+XZW1COrWTdqmBt6Ccsny4eodPVWZqWVpc8bWh3qqmpeuH640AgAHFj7jZg6TF3/SGjpmV8pbDgn48SklMUlRRUlVdbkdhZ2VjZWVnaGppa2jNY1+4v6uyrq+vvcOvpamwraalopWEgpqPq1d1xHKYhYPUqrGsuvnygIKBtOGF/c3kxI3Exu2EtnBmX0Z/hpJXRolSXUZXa2JQhGBlblBiZ0NINkxEgElTLzc5Nz5LTU9QT05GQCc9OUBAPz06NCdGWTpRUElGPDkyMSsmIyQiJUEnJCI9JCMqLjhDVF5hV0VqXi00N1FWWVlbXF5gY2JiY2NiY2VnZjU3OTxARk5abIe+pJukkcvr7qmWgp7on43zufDgi6mgrbSC8Or2k7zij6iP3/Grc8zWg7CL8rrSmMOx/4+Fr4L5mN+23pOlmurGg4yh2fKR2K3y08/Q4ayh3d/v0a+q2+XmwaGe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TzlFIzgnITZBNTdPMUAgMkoqKScpHk8zRyUtOkxFQzY1MVdkTVk2Q0ZFPTZhW1ZUTURAP1ZyazpIaVRXTFU6YFVCTHyAi4N0YFtYV1ZWn3hSUFBPT5+foFBRUVGRhVFQhE+AUFFRUVNVUXyRV1hZWlpcXl9hY2drcHRzbW5vcXV7gouWpFljb3h5aWJcbXeBjJqpXGZwfo6dqa6nj2iATmxbjXpeXmtMR0hJTUREQltgbXVIRD84YGdrazw8ckVRWGU8aGxzg4F8c11Ne39YV3VgW1dGT1ViT1BRWoReWlldQ2KASmZacVWGZ4FiVmJYcz1IfEM4VGVXSFxMVGo1NzM6XpZreYE/LStNQTgxLCopKisuMz0lLi+ql5eZUTUrRDcwKygnJygrMDkiKjM3N1Kce5JPf4uDTp4sHjcxLSoqKy0wNDc6OT0oGhwnuJeUk0YsMS8uLi4vMDM3OyAjKCsuNCs1PlpeWXtWp21hgFhbWlZeVU+hmIeLi4iLkYV2ZVZKb0lzWUx8Wm+ZdlFrnqFnoo5jXF58nJ2AjcW7nWb56XF6toN1n4ZmjX5imniorlpdrFeNXWlUcXx6x4mGhWJDTjtCRkU6LykXIhy0cFOk33GioNiFplpqonykWv2mXlxfopVenHayhGhqo0xAZ2y/cn11dHKslZGJeXSI/YxwfX+vr61RTu+XgZOXvd+VUXxb0otabFqCpJuAnVTz0teXjuC4sNKYl5H6ioLOgJe10cagtcnTxIOkubStnr+6uJWuwL/Hj6fDxreGr765us76k47OzuTSo77Erj48QSpdTEorLFRMNX7OpPqZ5K1yjqNous/kpKC4hZCor6yWuvKIkefas63gyITi+4PHu/i6ssD5+bHSzdTrjoRPvoKwod2tz4Glws+Z0pDWib378LTOtMm5terf93dwXlJnivi0vbClblslM26iqHhinarJ0lJngmtkZXx2c3FSWoDNrdq0q/WKmd+DHQADAYcAgL3Kp9mBo9KAmKGxwbzd/bemVFdaY2VlaWhpaGlpZGxpamtraYliaWto09fTztHPy7fAwb7BzterlZyhnm6Wlo3/75eTrVlxsHmajIrUrrTGy4H0+/z7r92C7dmrq7P21ue+an93a1N2c42QVUSMVFZIXGhdR5RlZ1ZQXk1PTkpHgDwyKUU1Tk5NTk5RUFJVLTg7Nzo8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U2RMblxNgHd5hUxWXWdwd3l8fHt/gYSKSl9nY2lvc3V2clhSZT9mXFhYS00/SENHRktAQoA6SkhIOkFETktZbEGBgmpUkJJLV1h8f3+AgYKEhomKjIyOj5CPkZJNUFNYXWNseIifYX2BSk8uOyQxQ0dLRkM6QTxLLUorKEEwWTQlLDIfIycpSz4sOSItIy5FPDM4Pzw5KTBbRXNtPUw3RCQ7IzUsJzY4Tz0qLlg7OCsnJi8xMDkpLURQVDI6UEpHPVMxSzozOzpfVFRbX2FdUz9ESEJCTUhnXFVkQzRPVmWMTlhTimZfW1lYUH9UUVCFT4BQUFBRh5FSUVCenp5PT1BRUlOGhlJVVVhaW1xfYWJkZ2tvbGhrbXF0eoKNmqxea3R+d19cbHqFkJ6tXml2hpuuX2ZnXppun39tV358fIibWHJNTk1QTkhHRF5ibGxEQz85YmlvbD4/P1JOVmQ9ZmtzgoF8c1xhSnRjV1FXWUxNUYBfUk1NWEVWWFpeeWBMX1lUa4mBaEhgVWFVaUR7P0dbUGJXS1hSY2UvMTlDeqRZfX1LKEc5MSwqUVBPTygpKy4zPiWrmZiZQUc5Mi0qTkpKSksmKCsxOycwMJ+aV1CSgl5OTzAgNSwnJCQjIyUoLjY6IEEkNia4lpaVKy8vLS4vMkI0MzE0OiAlKzQiIiMycmKbZ2hNMTdQYERDQD06ZVNUUVNbXFxeTmRKeFVRcElvV0t+inWac1RooGVgl4diWVt7mpyAiLywj2F4427arH5ulItji4K6lbSptF9cqFh5bJtQcnx5xYeDQ19ETTlDR0U6LykWIh2/bqGl85ejpdq7V1pux5lXX4bJZGNguLVgm3nLjmhqrIRLYWjLvHd2dW6okY+OfHyH3IZxcnCAgLJ6cYeu25xYubPoVXpV3YGIUU7esb6AVVbXw+V6f2TN02ORo5K41NO5pMzvlYrxk6i0gfCctK/EwbezsLDSwri6ps/GxsDDrca5wYCfpYrJiZTPq8jDqT8+RSlbS0kqLFRMNX/NpvuY5K1xjZ9otsvR0J+1iMaprMi6veqJjtfVv6bezf/a8IXF0/6EiKzu8qzNktbsjIGAqLelldCnl++ertGTyYjJgq3q5KW/q7OsrKXnnrShVEhjj/6/zL+zeGJRXGKCwK1TYZBpiZKVr9yUhHJwdWxca3ODkue3/YWRmKGm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VVKIpqqsgvewq6GvyvyN+9i/hf+yzt/+hP6IrMbCtbOpwXF0YisvNDc4Mi82RzlQ+KpioIZaQ1A0QTI5NjQqTDRgxnmfgZOXmpyXkZydoaTTzJ2DfXuGjI6N03fShrKAkeXZx8i4sqvIhYGKh5eQk6GvtrvDurZ02amwwZ10mMWHXXNTTkVpgYB8fn1XeUaEf4NbdWpbXJiAX1ZtaFORe4eOikpYklWPW1xhUJNbXWJjZWNhXVNJgHJ2gkRHUmFocnyGjX9tYV5mam1vb2tSnlw/aGFZWUtQPUtESUdLQ0SAO01JTDxHSlRVYndIjYh0WpRKSllXe35+f4CBhIWHiIuLjo+Oj5OWUFNXXWJsdICPVGaCmE08OVErK0tZWLc8OD85ZE44Qj5JIy0gNS4iLD49PzoxRyorNzIpNTIqQSI/ICVHcXlqZ3FGMj0fNj42MUQtNU0uOVsuPS8qTDAgPDCAKUdJU1c4PzM0Ok01Qik/Nic7UV5gMjJeT0pET1ZXWFsuUDxuOUc7Uj47U2Y3VIttZWBeU49ZVlRTU1FQT1BQUE6En1NVVVNRUE9PT1BSkoJOUlNVV1hbXV5gY2VobGlnaGlvdXuDjptXY3J7f29oZnmEkp6vYWt6i5+0YmlpX5h4cVlTVVSRY4FgV1VUU1t3UlNRV1JPS0ZjYGlpQUE8OmNrcG1AQUhPUFZiPWVpcoGAe3Nch3J7Q0lIS0lLSltUUEpYS05XW2hyXVBXVlRcUl5KaExgU5FZYH55SWdYSltXR1dPa2BPKjJBTbShd4CcU1VPSkVBPzw8hTmAOjs3o5ubmkA7PTs4NzY0Oj02OTs/Q0lNRlxPhZFSiJONT6IkKickIkJBQUBBIiQpMD1EOD22mJeXOTQsKCUlJigtMzg6PCQtOSQ2KElmXHxnQ0YtLTg9SDw1LixSP1RWUzpDQ0JgWF1daV9Qckt0WkxijWaNdFlnn2pekYphWlgDd5mZgIGuo4FhdNpu2qp+aJWQYYaIs42tsbSvhapilW5fTnV7ecaHgYRfRE06Q0ZFOy8pFiIcw2eYmoW9qq/j511edorUWmKQoW1mY7qbZZx7h6pqbLbjg2Bl1Ml0aGNdxZOCR2haWSElQkA/dX5LRlBlPFZIS106YE1JSktIRkRISk1RgE9NUU9NNVdEkExLXD1MQkZDPkBRUEuqTE1KWYVRUl1ueGSZqLPyl4aLl7qAv8PEqrzPtcXDg76muvjz0a3NwatAQEcpXUlJKitUSzN8zaX9meGscoyaYpu/ytSIp4b3p6qX4rjl+IzWy72j28761eiCvsqlvp+J2ealys6J6orxgL7ohIzGrOq0l6rKjLz/xPWk38uct6O0pJ6gl4L23KGMYpaEydrMw4PTnpaZpr3VbrW7u4CzZ5jD5ov6fYGSpru5xu+GqNuOo6arpqqlp+HAzd/akOr0vpq7kuarxby3kf6ktdGFpImewrm0vKuorb613L1zaX1maGdrarditWZ8gGm7rKDLl4+QzoeKkoqmqKyvu8DAy727b9GuvLCma4imnJzmtrabyv76+vv1oN6A5tXRk7iih2eukW1hcXZehml+k49KTJFbi1NialN0S0RGREJBPjs1LU1EREcmLTQ7P0NKVFZJOTczNzY0MjAsIU5OPFdST01EQTk0KyomJyQkgCIkISckKi48RFZtS62qkGF9MzA2QmRkYmFgX19fYGBiYWBgYWRmZzc6PEBFS1VhcUddidevsaTxmK+rhZD7pYiejdfMqtPP74fcov7Rk67RzcivnuqPje/nj66bg/GN64OIs/v/ycLwuJ7Lg6P+zarWhaD2lsn8sbycjPfThtSngITKyO736Leuptz9msOb/6qFw+v4/4qM/LiYiqq/xtfxhdWN84TEmrWon6Lhg6rtf2JVTUKaSEE+PTs5NzU0MzEvVGQyMjEvLSwrKioqKUpHLCsrLS4uLy8wMDIzMzU0MTM1Njk8QklSLjU+REU9OjlESlBaYzY9RE5YYjY6ODJPgDovLjAvVD1ZSERDQ0NBMyUlJCclJSYnMC41OSIjIiE/R1JcOD9OYWZvfkplNTE6Ojo4MEMrLRgZGiIoLyoxLjEoLSgyMDM9SjYsPTMzPTE5U7qQvJT5mKjy3cjhwLLYwJrNtfah44ajw8jXckpS2+z05dXIvrWtqqinqKqutLmlgJd5dnaBpKmloJybmp6hpK23wtTn+uFiNlhZNVZhXjWOkayYjIP/+/j5+oGKnbnr/8nP8nd0e8vVrZeKiY+VqMPa3eCY0v6l7pGWRjtXcZX3rrXc1OzNrYyJ7pBxjpSRsrKt4p+op72lksKHpZWFmdmX2qiFlOqXjdjOkICFqt/jBnx8fX59foV9BH59fX6EfQV+fX19foV9C35+gIB/f359fX6BhYMOgn59fHx+fX19fn59fX2EfgF/hH4Kf35+fn+Afn1+f41+BH19fn6EfYKAiX0BfoR9AX6SfQt8fX5+fXx9fH5+foR9QX5+fn19fX9/fX19f399fX1+f35+fn+Afn5+g4N+fn6Dg35+foKAfn5+gIB8fHx9f4KDhISDg4N/fn59fn5/gICAhH4MgX5+f4J+foB/fn6Ah34BfYl+BX18fn59hX4BfYR+AX2GfgV9fn19fYp+B318fHx9gICFfwF+hn8GgH9/f4CAhIECgoGIgoODiIQNg4J9ent8e3t8fX1+f4WABH59e3uPfIh9Bnx9fH19fpZ8Bn18fH1+foV/BH5/f36IfQF+hX0Zfn19fHx9fXx9fXt7e3x8fXx8fXx8fX19e4p8hHuLfAGCiIMDgYB9mXwBfYR8BHt8fHyTfYl+hX8EfYCAf4SCB4B+goKCf4CEgQeCgoKBgoOChYEEgIKCgoWBBoCAf39+fIV/GYCBgYN8gIGBgIGBgYKBfYJ+goKBg3+AgYGEgBKCfIKCg3yBgYOBf35+gICAgYGJgA6BgIB/gICAf4CAf3+Af4Z+AX2MfoJ9i36CfZh+jX+GgISBhoCCf4Z+AX2Ne4R8hHuHfAN9gIKGgwN9fHyFfRt8fX19fH19fXx9fXx8fX18fX18fX19fn1+fn2EfgF/iH4DfXx+hIAFfn5/f3+RgYR+AYCRgQp/f359fn19fn9+hYKFgYeCAYGEfgGBjIISg4ODhIOCgH9/fn5/gIGBgH59hH4EfX18foV/AX6FfQZ+fX58fX6FfQ1+fX19fn19fn1+fX19AgIEAICvrLaNh4D2loHSqP7Cyv64xffFevj/VIf0oViMqquItKr6o8f91Zrfu+H3+NWOi4rVpZyg9u53d4GE41CPjZM/Q4ONfiOgkogmHJbxgHrRkIn+5Yfq6ur1lMmzhqGBkryqpbzS8qTfxMW9urm+vsHDw8HCwb++vb2/tLW7vby6uIC0sbGwsY/QrZLfxeaotZKYkeqfv7qL4eqnpYjY1KGlpdb/oKmWwZ+rsJGZmq++tSEkzNVxERp3eYkPFmFejCoqlrzoo5LW9IqJ+9SDkbyO3ojdv5K3MYRjfGEQWlNsFF5RFghsfiNMcWg0YHxQknR0hGBpSWRTPUabVGx0XklDiIBiW2BFeWhDZU9mbkZpgGl9RHZjQF49VTlHMkNDb05VWzoqJThMTEs0YVBUXH+m46LOj1EfGQ8ICgYDBggNBgIBBwgHDAwOBgcICQkJBwcIBwwOCgg4PygqLSkoIh8cI0VJgNPyhI7F6e2zr7GwvtTtlaDVt6OCmq/E0NXbkem7sICnkIf+0dXJxaee9ZXrkrWzsr69u725xc3QjMyriI+3859odVdMQ4OVmJpUoXaWYZy205WJ6vrwoubanIS8rYTp0Oju5eWe75jeo5+S99e5mYLgzdLrhpautdyX6e/jyc7niqnD04yMh5KYnaCgm/Hkg4Hr6tPLpMCQsJ2voLGZm4CEwa6/ibK/09b3hJT/gNymgIGBnYy+wsfHy9HX2tvh5OXq6uz1+YCKkpmiqrTE2O+Jo8PqiDEgKB08+LOmnjpDTF53PEsuMTxBOz8jIjY5NC5aVEpIMDszNUtGTkUvIyUiOUNvfHd3fkdTLiYgEyIdGUUwNCczQhwwJyNGHRU9K4ApREdUVi43LTAwIjZGJRwwMxwtNDI8KEE3U1RZWV9pOz89NFdQU0FHUUZ1RURouq2hnInzko2LioiHhYKAgIDk1v+BjKCmmIqEgICCgdfngoSHio+Sk5WYnJ+ioaKfn6Spsr3J1+6Fl6m8r5GOqb7O3/WIl6zE3feJkpGE2KaJiICQkoDDhq6clZCLhO+lqOquucbVx8K5ouLU4dKBiIqC5vj57pKZo52ruND/l6W4y8S7rImLvc+5npKVpPWwoZaHpZCPo7nZ1bebpKKirJixxpywxnzGY3+JWpBDe2lMZmVVZF6Et0k5QEho2+mSwL2yopGF/vfz2LCI5fHnxs70f4D4/v39gIiHhIODhICBuvKFhYWHhoeEgf6ehIDtsYOEhUFCQyEhISAjISIjJCYUFh8u+/bz7yAPDw8fHh8gIhIWHCIUMhsLCyaem+SckSsYFhUUHRwnHx88KTSHlX5oIycnOHandJmynG+mZcCJeZzcpdy9iqr9qozm4Judi8jz9oBgbWtXTE+UVU16YJNvdo9lc4tqXoaMQGWFVkhLYFNUfXewcXqQdFiUe5KdnYZbQjhTQ3Rbi4RXZElLk1JSTldHREtTXTJlVU41KVGATlyHTkyao052cnuoV2BUQFNDTWJTUFl2jFNrZGtnZmVoaWttbG1vb21tbGtsaGhoZ2lqaoBpamxqalFxYmOoaXVbYWBkX4ddb2thsZdiYlmflWFjZpGlY2hamHlsa1t1dm59fzI/nqVcKjppZnwjI1pdfUhDVGBuRmONnVlYo4xZapRzqGiojmuHO1RPblorTk1uKVNPKy9je05Cb2hFVndYd29+eGBoT19QRVWEUmx1WElCgIBfUVFDbWBFYVBkbkpqg2qFVIhzUm5OaFFYSFtrnISFv4RUUIahpKZ86qWgm67K/qnTo62Pq7u094GLhZrFamp1/vBZSVJpODI1Pjk5NzU5LEVJNz9rZEhQT05XXE5HUE5NY5mtWWyaqKyPkpeepLnVhYeZdG9tgY2apae3jsealoCNfXXXt7ysqZGN7Y7ZhrOzucLBwMS+y6a7hKqLbXWXxYpidVtUQ2l6fnw/fFd0R219lmZen5GSYYqDXE1zaVKMe4uThoNfjluAYV5Ujn1rWUh+dXOBRk1ZXHtpmIB4cnB9Slhja0hdWF5hY2RjXpCPUj1lY1lYS1BBSUNLRktCRIA7UUlQPUtQWl5sP0ySSHteS0pJV1Z6fH5+gIGEhYWIiouOj4+SlExSV1xhZm94hJZYaoOlbDckKyBBqXZyc0pXVFp6Q0UsLDc+N0IpJzk6NCxXUElGMkA5O0Q8Rj4yKSwoPj9ZY1tZYjtPLS4pGS4nH0YxPDI9TCY6MChOJRtELYAqQURRVzlBOj1BKT1PMSA9QCMvNTNGKUY1TEtTU1tpPEJANFNJTTs/SjxgPTxRiXdtZ1mjXltZV1VUUlFRUE+NhaFTXWxuYFZRTk5QT4WPUFFTVlhaW11gYmRnZmRkZmlscXmCjp1ZZnaEd2NgdYKPnq9hbXqMobRjamhdkWtYV4BcXlJ+VW9jXltZU5JhXntYWVdgWVdSSmNdZWQ9Pz05ZmpzbkFESUtSV2F3YGhxgn96clxQZWxgU0xQVn9cVVBEWFBNU15zbFxQWVVXW01baVBsiWONSV1iRnI7YVdCVVZFVU1lXkdJUlVpraNjgX53b2FZqqSgjYtlmqOei5CvXICenp2dWmlkYmJhYFtqk6RZWFhYWVhTUZ5gUEuMak9QWURNUystLzAzNDg7P0MjJio5tJmYlzMtJSFAP0BBQyMqNUAjRTIeHzJzaZhodDovMzMwPigvKSVAMjBqaWBWKzAvOlF2TWd8aU9wSXBbTmOPY450WWehZ1qPiWJaVXeXl4B6opx/YnPSc2ymgMiRk7t/jrCIra6zjLCobaxffUp/fXjDh4KCYkRMOUJFRTovLxcjHdJunZu881la/v59YX3jzl1kxtLAZmTRqmmbd4mmVk/boUVeVmJtNDUvJTIVFyktLDEkJCU9NEA+PT0/QUNERkZGRUVFRENCPj49PT0+QIBBQ0dFRDROOzp+Q1I8TkNFQmNCT0xUqXBFRUeNeENERXaCP0Y+jmtJRz1ubldtgabj292Go/+ootKXj6ywyr6sPz9IK15ISCorU0kyfc+m+5jhq3GLkV+EuKadj5XnvZyhnuG54daHz8StoNrK8tTf97jBk7WXjMnumtD1nYyD3oC0koWEtp+DmpCovYSx9rzrmt/KmrybrZ+Qioqx4seOdGObidLk29GM4Kigoay92YCTf9ru4nd37nyAg8fwkp6N//mMkaHSg5OvurfAvMDCnffazrbIy521tbrx9eDU8caSkaiSRFKWmJiNlpiWlJytZX9QLjJQPUJHTlBnhnJOUYBMQz94e2lgXm100HPHiN/c4/Lv6und25ekhLiXXWuGopuX5Lq3mdDy//qA7ZvWgLrN8qCS2LCpdJqPalZ2d1l/a4yYjX9TmF5+XWNRd19LPTJUTElOKzE2Oi8cMFRJQEBMKzU9Pic1LjIyMC0rJjtISjxYV1FNRUc4NiwtJyUjJRofJSAmIysxPUpnQlnOZKZpQDYzN0FiZGRiYYRfgGBiYmJhYmZoNTg7PUJHTFVgcUZcgsD89avTjPfFbWpzoqernNGIrICIq8yt5ZOEqb6nhfXPoZ2aza23oJW2l5yXqJrYrpyynJ2+h96Lr82D6LSQwY+7oqbci+KYhP+Zg+qgjcC/7fb6vtjR/YW56rmD3PWKiZ+V+4PZi6acs7POgPuas6+O17bMnJzYo8mXn6Xonn1oU7pRSUZDQT47OTc3NltdaTQ0OTYxLSspKSooSE4qKiorLS8wMTAwMTMzMjIyNDg6O0JKUy44P0hCNTRBSVBYYDY8RE5YYzc5ODFKODAxNTUwTDlSTEtIRT9qRTQ4KigpKy4tKSkxLTQ4HyEjgCRFS1daN0RSYGdvfpdiNDE6Ozo3MCUoKiUfHSAlQDEuMykuKi8vNUZENzA3MzI4MDhIM3nmxfCCo6KE0YbJsZDEuJLBq96XwN353dLhiUJRVF1fWVSgnZ2DUkKjtLu4yfiEj316em6nlI2JhX10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2KPIse6+iKeAr+vr4Jidjcrx8oBda2xXTE6QVZd3YpBveIpjeItuZleNQ2V+V1iLalRWfXavcXpHdVeTe5KenYdbQDlSQnZejYU6L01QTilcUVghPUlOXR5ejHo4WmxjZZFXZGtnkYJjaWdyWGJUTG98UEpPTlhLh0trZmlkY2RmaGprbmxtbG1tbWxsZ2doaGloaIBnaWtqaVNQUnF0ZnJZYV9iW3Rha2h1woRdYXmhcV1jlpaCY2VesWFqZ1uXvGpuX3KQW2tsfIpleY4yQZ57V2loVV9tR2WNnFlYo4tYapVzqGioj2yGOFOaa0o8UE1pP1BKNChkemBAbmdCV3dYenJ4dl9mR0xNQ1CBUm50VGhveIBcUGB7aVlHWU9jaIxof2l+T4V5VHFNY1FcT2NQnJZ/i/dTToSdoKF53J2YnLHL/qjP2K/KnaVpotp3dnSUc3FiL1tZZGNsNzIuMTY0NTgoU0U3HikkMSglHhUjLDBBXXdia05aoWPAkqWFiIuNlaXDdJd8nHFwgpSmpJOCg5epmICNgnhv0Me3uJ6ReKPxjLi3v8zHyMjB0fipiLiAg3OawotkeGBXRHJ2fH5CflZyhXZ4WWCKmGmAS2F/i1tKfGyThXqUlYl6Zo1gdFVOS1FWXF9dW1lSSYFzbouZgVBVWVpVWE+GhmFVn1RVVlZTTXV8lT1kY1tZSk5BSkdMR0xERIA+Uk1RQlBWYGU4Qk1JS35gSktKWFZ4e3x9f4CEhIWIiouOj5KUl05VWl9ja3N9ip1aa4SZQE41QDg2Z4qDa4FZcqh6qX4+QlBaMjY3NjJWSEA9PUBFSlBSSTxlaG5BT1ROQWGhnZybVGB0QEg5SysrOEFIQUgfPj0uMDRERDQeS4AuUllNTiArNiIfSz9RMTspSio3Oy8sORwsSkhJQGE4Q0hGQjs2UVxTUEQ7R0ZcS4d7ZVplYF1aWFZVUlFQnoaQmo+6rHupznRXmZuHiE9PUFFUV1pcX2JkZ2RfXmJma3R7hZKnYG58iHFob3+MmqpfanmJnrRjaWZZiGdaXWJgnoB4on1zamVgV5p/W1B7SH9eXWBnYGFUT2daZFxzOztwamxxcEBES1BTVmB5XmdwgH95cFyZZWZGbEdJRnx3T3lXUU9PX39lW1FVVFhdTV1nSVNbV2BZiF1cfV9hX1VqZl1GUkxfV0YoLjcrSrKheH1RKCI6NDIxMTEyMzQ1NzshKYBWUVBRMycfOTY0NTU2Njc4Oh8jKy5sUI2NUYtQlVFyPDQuUUd8bHdre1NWW2ZyP0Gwm5mXQD9CQT89Ozw8PT9DJzQlJD4lSXFgfGhYJystLzAxICkyKihDLjhSX1xaNz5HQ2BsY2NRak5sh2tdi2x+bZdyVWZQaZWQil1aVXeUlIByl5p8ZXHMddKigsCQlrKAmK6JN1ytkqagd9ezhEmIfXjDhYJBYURNOkNFRTouMhckHNl1nJyZnV9hjKqtZIKL+15gxoWmmYad4lpCQVIzNDg8VUc3OjwyJjUsKUonGRsqKi8aIhs9Njw7Ozw9PkBBQ0VFQkNDQUFCPz48Ozw9QYBDRUZEQjMzN1NoP1I6TkJEQFhES0lrvV0+Q2WPWEBDa3xkQkVDqVFIRkCeqU1PRnR8QlFTi41OZI+jrbzFiP30Oz1EK2FKSSorU0gyfM+m+ZfjqXCJimD9r4+vjY7E0JOPy6y32b2AyL6dnte99tXX87O9iI6PgLDtmM/ynMPI0YCqjZXqq4+CkIulsvOs57LXjNrSmcGUqJeclpqP1uanstqejNvu5NyX7LCnqrXC2oCSmYnM9v+Fj9aKjKrYiY+pgN3I0tz3iJGDn66vsb+KvOLagqmUuqiXjYeYvLeLo5Y7OEI3SzTWj5aRlZqUlZ6lXWpJQzM5PURNSkdFRE1TUoBPRkE+c25kZV5wanfGiNnc4uzr6+bZ2OyNh8yMZm2Kop2a5Lu4meTx9/yC75zM8crBkY3IrHiXWXWOlWpUg3qmc2SLnJJ5YJlmcUo/PD1CQEJCPjw1MVNDPzIxLDE2Njk1MCtLSjMxVC0sKiclIDNCkEBZV1RRSEQ5Ni4sKCciIoAgJCMoJSo1RlM4S2VzdrVuQzYzNkJiY2JhYWBeX19gYGFiYmVoaDU5PD9ESVBYYnRGWoHEpu+y2audWFVNP66gtrqN+u6NpMv7laOmpJf4xJ6Mg4qevNfgvoLF0uWb2OfSm7H05uLrjLT+nMG5+ZmlxZuuqv2Jv7SfubHa1+SK9YCc1ffc4Y+P2oCA4sDyuuaI9rKiuKSx5IDCuqrLoPiVuszIuKKS1/rk4raLwsjGhs2idnplWFBKRUNBPTs6cGBpZl6ATSw6TzUuT1BHSyopKSkqLC4wMTIyMjEvLzAzNjo/RUxWMTtF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XF5hZWNfYGRobnV/i5pYZ3iGfHBnfIiXqV1pdoeasWNpZ1mGZFddYl+ZcZ2NgIN6cmdfWlVRlFh0dEpJfmFmaHBpa1pRaV5jWW46Om5sbnJwQkVLUlBXYHlaZW9/fXhvWmNmZlOCiXJMXmp3SVFPSVyIX15XU1BYXExdaEdUXItbYYuMXm11YmBqZkFxaUU0UmdVTjA8JT4/OqFgeX8oLSUgOTUzMjIyMTI1OSApIzVTUlM0JyA4NDIwMjM0NjgeIikyIVZRaFGQkHZRUik3NTMxhDBZLzAyNx4iJT+bnZ2eXZh0c55gb1azVSwxOD8kKShefXSgb4dAKCcnKCksLiAnNEdDQFI+VWBfXD0qUHJVWGVVUmtOa0ZtXnJzYXBZbFJiTm6Vj4NZW1h3j5OAcpaVeWZuynTOoYC9kZWsfJiohzlYqJ5rpX62bXZKjH14w4SCQl5FTTpDRUY7Ly8YJh3lcJ6crbNdYIrcm1FZ7LRYSEogNTAvR0I2MDM0OzI7NiI2Mjw4KTY2LC07IRkiJysvIyIkPDc5Ozo7PD9AQUJEREJDQkFBREE9PDw8O0CAQ0NDREM0OUySgj5RN1VDRT5IRUZHdLZMPUSBim0+QlV9QEJEYbBzSERapWxKS1aASkFLYHJASFGFgEhbWl1xRjg9QihdRUoqK1JIMX3Pp/eW36dwh4Bf85iql5CQvu6K9oK3r9CM/sq5oZnUwvrW0/itu4LWjuuk5pCK55SwmJKAo4S3o66O/IvRpKjsq9yny4PRzZK4iqKTlpOQloL4mNGmoI3j+evkn/u3rrG7x99/kJeLdcTkwfTXlf+GiIabkPaQi5edkouXpq6wrcCjm4Gqwb69iYKSzbyM/eO0Ih8pMTk8QEhp3pOZjZeXmIySmLhpRT8+O0RMVSs+eVkwX1OATEtDPztwaGlhdWx80IbX3un48ezs39Z5kI+Uj2dwjaCemua4u57h5PX9hPefzcjjnf+AqZdgfYZddYeqeZ+OfIRwYpeimmhkb25eVEtJPjRYUEtITlcuLzI0MSxMRkBEUC43NjxBWUhHREE/PDYrOYeBXlhUUkdCOjQtLCQmJCMcICQkKCQuOUcwQFVtfYLJdEY0MTVCYWNjYmJgX4RhgGJkZGhpazY8PkFFTFJaZHNEVnqY/9KDheisMy9OQ1PgbX1ko319f4SFfoF5dWdcWVJQT1NXX2Vubmljb3yXv9HPtpJ6eH+Mqs+M0LWYxtS89YvT9cLngOqy17qPtdWzpta6k77Y5ezq9vekwOW93dCNi9u5iYLj35GH3cPipczdgM/1lJPk3oCWl4nk54KfvJHMtp16Z1pQSUZDPz05ZG9pZFs/SjpCT2InSEVRUykpKSorKy4wMTIwLzAyNDg7PkVOLjVBSEY/OUZNVF81OkFLVWA1OTYuRDMwNTk3XUtyaGJdVUk+NC4rUTI8NSEhOy8wMTY3OC8tNzI4OD8hI0VJgE5aXjlHV2Rmb3ePXDIvNzg4Ni4uJSYiNDktHCMoLyEuLykwSjg0LzYuMjksNEEtLzloRWzj7KC6yqql2O+p/uaMisz5hfCv6Y7t64awQE54rNOhgNnBubi7wcDK3v+ayrNCQEPjr4DYxLqutcLGzuCAnML0lEU6QzZaWlE3PZjXYMi+t7S1t7exs7/dg5qjlHl3d5WNlWlrxaHvpvHvjaTC5YGQivRqTGxc69WXlZuhqrfEhZa7+d3T8NHid5+w5ZWS8bCsvragtpC5g46fuKiLoYaadoZqldLKu35+f6PHywZ8fH1+fX6NfRWBfn19fn19f319fn6AgH9/fn59foGFgzmCfn18fH59fX1/gX5+foJ+fn6AgH19fYB+fX19f319fX5/fX19f399fX1/f319fXx+f359fX1+gH+hfR98fX19fH18fn5+fH19fX5+fX19fn99fX1+f359fX1+hH0BfoR9AX6EfQF+hH0ufn19fXx9fXx8fH1/goOEhIODg39+fn1+fn+AgIB+fX5+gX5+fn9+fX9/fn5/fYZ+En1+fn1+fn59fn19fH5+fX5+fYR+D31+fn1+fX5+fX59fn19fYt+hH2CgIV/AX6Gf4WACX9/gICAgYGCgoSDjYQBg4aEhYMDgYB9iXsBeYp6BHt8fX6EfQV+fXx9fod9hHwFe3t7enuJegV7e3x+foZ/BH5/f36EfQF+iH0RfHx9fXx9fXx8fXx8fXx7e3uMfIZ7hnyFe4V8AYGIggKBf5d8hn2GfJJ9in6Ffw6AgYGAgIKCgYGAfn9/f6Z+A39/gISBB4CAf3+AgIGEgCKBgYGDg4KBgICAgoF/gYF+gYGDgYB/fICBgoN+gYKBgIJ8hIEFgIGBgICEgQaAgIGAf3+NfoV9B4GDhISDgH6EfZR+i3+GgISBhoCCf4p+Bn19e3t8fI57gnyFe4h8AoCChoMHfnx8fHt7e4R8GH19fH19fHx9fXx9fXx9fXx9fXx7fH19fYt+D39+fXx+gICBgYGAfn9/f4SDioIJg4OBf39/goODiYKFgwl/f35+fX1+f3+NggSDg4OAhH4Bf4R+BX9/gICAhIEKgoKCgH9/fn5+gIiBAX+Efgh9fn18fn+AgIZ9Bn59fn1+fIV9BX59fn1+hH0Gfn1+fX19AgIEAICpqrOMiIH3lPPGovjAxva5x+rA6evsm4bzqJexrK2MtKv6pciA2J7bu+L3/NiNgo3Qq52n9vOGheXx1vPv5taXntnNt7faw77B8eDDzdiRnvPis5Dj5Nvihdy5ub2Un/iApK7HwYPWybW4t7a1uLy9u7vBury3trS4t6+yt7O2soCvq6yxr5Wmjpi9wN2fwpGWhZyorLa8572bqf7OsJ+c1c23r6zpy6ipmti+lKuj0JGInq7T6IefroPpr8/Jyd2imKbrnJPY+IqI+9OAiryQ2ovevpK1ZYS8g3AbXV52KGBRf1ttfEBMemw6XHlRk3tyfWRsQYRNeEagmnpaVWuIqTlOqo6CgmmUc3dtb4pmd2VxQHNoQ184UDdFNks3SVhbcGQsJFMrU1M6Q1lfcYar5JvF0LGC1cqZVBCEAIUBgAIBAgMEBQYFBgYFBAUFBQQDAQEAAQIE55K386mLtY2yo434hfy7w46VlZ6uvtPtpJLA55zB8JCqq+uFr5v3trejlY6G9v7dr4ep9K3W6PWHiIf/7viNyZ2cyKCZvdmgbHtaUEuJj5hQVaN4l4O9f4r5q6TOjq/Dip7AirXs+rCZgOHMkfznx7KNgvz51YKn0OTv7+vWwra5vtf9jZeblojrzM3oqcmlsbm1p5uM4I/A6OLMxsaksJ6lprWnt6ybkubX367d/pGXnJ2ijYv+rImMipWGtsDExcjM09jd3+br7fD3/4KGk5ujqK6xtLq5tre1oZmanJ2eoaanpqWempiYF5eWl5iZmJeVlpeYmJeXlpWVlpaVlpSUhZOAlJSTk5KVk5KSkpOVlpaYobC/xL69aXWAhYtLTkpNWTMzMi8tVUpPUy0fHTJGNDseJiMjSTs0Mi0ZKikuXDhTQEAiLB82OjVjOjhoXVhcL1pKdmBqva+knpeTkZCQhOuB/Pf4+oaiw8uihdvn+Pv9/oCDh4yRmJ6dnqKkrLO6ydeA94eaq72kl6W70Oj8jZ+20fWHkY/+x56OlJmK253w49jOvq6imJaUkY6KgubT8YiE9LrL4YH38Mu8gub0zPiBgoD6gYKBlZiorKW1xvOHnbHGwLWnhtS+s5zHlJSCj5eMkJfwr4Wis6ifjqWvir3Nh6my3JjH4Iy9jq2HgIiTeS5FQYJePTCGrU8jExQeIB1s5aqxFwwMDhwcGxoaGRkYFxUKCiqKh4gYECAeHBsaGRoZGBcKCgsIK4n/g4nxlIGISxcXFxgYhBdZGBkMCwkHE4D//v0rGBgXFywqKCkzSEJ794KUo623lcenWRgWFBMTEhIRERoxSDs5N0U+b8aHax15lViEipWNV4ldom3AgsS8zsOLmviq/7bkutmTm5HL6u2AXmxrVExNkVaTdV6NbXSIZHWEZoqEg15Kg1tuYl5WV3x1rnJ5R3ZZlnuTnp+HWUA4U0V3WomDYV94fXCMf21jTlhlYVpmeFtaWot4XWFpUlFzbF9Rbm5oeEpoWFpgTFKBQFFUYm5KaGFhZWRjZWZnaWtrbmxtbGtpaWloZ2ZoamiAaGhrbWpXX1RmhmpzV3hfYVZZY2RpkLZwWGOxlGleXpWSc2lnpJ9qZFyXmGJqZo5yV2Bnj7FVYWhdrWt9e4ObYFNXbUVljJxYWKOLVmOWcqZnqJBqhWpTjXZmNVJVb01USIV2YXhPQXRmSFFzWn50cnNdZ0V7SnxLe4xyVkxie5GAR5d2cHNbiWVsZGaIY3thdEyBcU1oRl5LVUxaUmF+hKCcUEWNTZeXcXKUlKKyzvyewMivhNzSrq6IZYzQdldNgV5oaUBBOTU6Ojk2NjUwMycpMSsjIScyMCrIUmOGYlBtWmBaUa9k5KawkZeXmJqmvM2Ibn2Uf5i5b4KCumiHesuAlZiJfnZx0de5nYCl8JvM3+9+gYH26fWLtpeIpH59nbaKZ39cV0p0eH9CQ39Xc1yIWFyVaGN6VWl0WGJ2VG6IkGtchHpboY18a1JMmpuJTmB3iY+NhXtyZGRocX1ESUtMRn10cX1Yh2xydnRpYVmNWHx0ZFtZWExNRUlJT0lNSERWQFlUWUZZZTk7QUdPTU2FYktMTFdVdnt+foCBg4aHh4qMj5GUlk1QWV9jZmxtcHJzdXZ1Z2FhYWJjZWlqamllY2JiYmFhYGBgYWBiYmFhYGBgX19eYGCOX4BeXl9hYGFgYGNsfYyOiYlQZHFydUJHQUVUMzMzMCtOP0ZQNiYkOUY1QCgtLyxRQTU0Nx82Ky5cQldHSS0xKD46NV86N2NWUFYuVUBgTk+HeW5nY2BcWllSlFKgnp2hWW6IjG9XipCbnJ2dUFJUV1tfYl9iZWdscXiCjaFdaHOEcoBmcYKRorVlc4SasGJpZq+CZVpfY1mPaaCXj4d7bmVeXFtaWFVPh3aGTUmBZGptO3VxYFg4Y2lcbTk6OGo5OjlCRkxRUVZedlRjbn17dm1ZfmZhVG1RUkdKTklLS3paRVVcWlhMV2FGYW9EVV55VW50VIRrh2JaY3hmMDlpRzYtZYBVTzUfJTxALFqdcXcqNSghOTY2Nzc4NjY2Nx0lPFVUVTckOTc2NTU5OTo5OR4jLRw7Vp5OUY9YT1NTNjIxMC4tLS8wMzkgJzQjM2KgoKpIMjArJ0hCPDk8V0NcrlhtiIp+ZYNvXyYnJiQkJScoKjpAUT89O0M+UlxfXShbZj9ZXRxsYURiSnJPbVp9d396W16aYJptjnKDWVhZd4qRgHGdk3Vka8Z0x5qAvI2Vqn2XpYOhoqKGXqJ7w3RwS419d8SEgEJhRU47Q0VGOy8zGSYg2G+Wjs/JZl5VQTs7NigmNTMzKjEyMDQ4NC8yNyInPzorITc8OjgeNS8vNhwZKiIsLjUgFjo3Nzo6OTo8P0BBQkFAQkFAQENBPDs8PD0/gEJCQ0RCNzo1Ym9BUjZaQkQ8QENER4WsSD1FmYJFQkB7gUtERJShSEY/hpdBSkmBdD9HToGyO0VMWq5LWFZihUg3OEIpYkpJKitSSDF3zab5k9umb4X7XebmsbeRksT1jIDn+azOh4HMuqiVy8n50Mj1q7qN3YTYouHyxqODq+LygIH6ssC0ifOcrKCk5KHVn8OH0MaNroWbkIuSjpSSsa3SuqKN8YDx6qWJv7a7ws3hfoySiHXQyK73xIPE+Y2BhdODot+Uo5WSorO3wcK2uMGVocqph4iw28mNpBweLSIhLSQmIyNYO+aVoYmXmZiUmpmoYkI3PT9HVDA3OV0uOTVggFBORT8+O2dqY3ZqfNeL4errgX589uzieY2VrZpnco2inp7rvL6f3uX0hIXwndCg6ImBtHpziWF9f2d1fmGCmaJ7YXNkWaudgWtHPYOGe0NNWWNm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YmBOkIVeX5SHalxcUZNjWlxXtnJeXXptW2ZejatQXF6chlRgZWxlaHFZWnyQm6FieVBwk51YWFKPVrmUbaVmoo5oVnNPiYV6U5BjroRgZ1yUYWacVGuMeXedWXdjcXZXXE6WhnJGf19sZmZic3hzjK1qgHNmcl+eXm+FYXZcbYxuYYthgVOHno1WSaqdlmiISz91R0ZHaFyNlZ2sxOGCk5eP9dPIzdbZzq6CgZmqn3JycrSIeo+AW4iBPUA4WD82MmJcX0dXeZOnbHGrZZWv1mebmo2ThqKjma6spKZwkH9ojnOEs3GSu3iTprCghGm4lN3GgL2xpKmUidyFv3pua291cXV5epaco6exv3uNrrGWeJJlWU54gYJDR4Vff2qReoyClI1kaIRbYnFpYHdsbFZ4V3CAfpBXYmdPWHN8jI3XqERDQC9EPjYiJQ4eHyAmPjM2QDcsT1KBXWx+qmFhbVOEZVlhWlNYUEtLSkpOUVVTUl9LgEtOWmlOjJVSR3VnUEhLT11+r6+vrq+xsK+ro5+Xe5GgvW5sbW9ta2tqaWloaWlqamtpam5wcXFxcnFvb21na2xpampqaGlqaq9ib3HBnZOXbHh8YnmHmrNfXZ+Ccmasa2dlZWZnZ2hpn2JnYWFiZZP0ipunp5n37oTV9bvAqo+XBp+hoqJRUYRShFNpVFVWXIRVWldYYXNGLTExJzMuPCkkKChLKS0iMEtThoKHlaCkppqFcW9qZWBeW09SVlRTUlCcmZmajJGeUVVbZHGFm1hjWlxuhZioYXSKpmFqaKh3XmBgonqrm5aNfG1jYmNjYmJjY2RkhWUBZoRngGhpbG9dTk5Ih31ncj5ERktFPFBGSzw+QUI/PkRFQUZHR05JVVprcJVSXl1aVpJnR0lMSX1tZF5aWlhaXmVqa1hGb1pPf3RLhYBKZkaLZmZoamlpeJRvc2tjV2d9aEBeVWNcMDU3PzdfSYdzj1ZZoZabg5J5dnR7U19fXl5dW1pZA1lZU4RYdFdXVlZVVVRRbVdQe1JVVlphanF4QENHS1BQLjIzQV1cXzghPz08PDw+QSQsQS9ISX9igXA3KysqLC4xNTk3IB89OTYzMS44PSs2NTNGNkk/Vj4/aGNCaWJlY0ZmfGtmYl6BdIJnU3luVI5hVJaDg1hUbIGDboiskma2xa5vsot5nX+KpXd+o4GBpJ1li2pIOjooKCBfcr2CfUNjR1I8RUdFOSpQFyAWKSMtKSckKSgjJiArJS0tJS0tLC0hICwrNDEoLTAqHzM4Nh8sOzguIDovLTQeGSIoLTEtJDY3LDc3Nzg3hDYMODk6Ozw8PT4+PDs6hDyAPj4/Nj5QVWgyRS5NP0IrRkA+PIJvPEBxf1Y9P0KJVEA/SrZVQ0JqckFMRYauN0NFnoA6REp7VktQPlGFdH+BXHJBX0hFKSooSTDWzJ7wj9KfbDxBPWdzdz5zUKBoTVhVfFZdjU5ijZSj5YXVo6/MkZaN+tmui8+WprydmcOyq9hC6JyiirSF7I6hzpC8kKzkpp3snOiD+Pj9g4H//NmRuqKK8IWCgLSQ1c/Q0dfedHZ4c9O/s62qppqHbG+BgXU8IhIHkgCAAxImOjA9Xj1ne5RMfIVgUoygqai1tLS2hqOmZHVGRWxAS1w2PUNHRkg6XE+FdnRsYWRbdtl5wHOCgoKFgYOEg5CPkKrWxXWEmZ+lq/C/wp7r+v+GjPuVuIKwkZaPpqBod5dpdIJ5bn98fmBySHmpzvirzdWUg8a1OTdlYDEzJxJZERINDigWJSIcFRkyLiEWFT9Qrm1gjN+Om7+gsnBhZF1VUkQ8OC8oJygmJSUwJi9FcsKo+bxwYIhhQjEuLjplj42Oj46MjImFendxWWByiEpHRkZFRkZEQ0OFQlpBP0BDRUVGR0hHR0ZBOjw/Pz8+PT0+P0BoO0JDeGFXXUBGRzZBRUlLJyZIQDw6Zjw7Ojk5Ojo7PF85Ozk8Ozlv95yxwL2o8uOHroGtzadbXGJjZWUzNDQ0NTaGN147UtWpr5iPoNefgZTcoKGNv5aaoaK+g6WTw7uBimdfYGFfX1ZIOzo4NjQ0MissLi4sKypSUVBPSUlPKCksMjlGUS8zMDM/S1dhN0FOXTY7OVk9MzY6ZlF8dnFmVEI3hDQCMzSGNYQ2gDU1NDQ0NTUuIiIiQTc1Ox8iJCYiICUhJCIgIiUnKS0yOEFHUl9caG54ZUshJygpKEs3GRoaGzEnJCEeHx0fJCMmKCAdLzAwTEY1S0sqOiZYPz4/QUJCSZi4xcKpq8ft2Y+6eO/8hIyMpInmh6dnXjg7bWdsXWdWVFRYPEREREE/Cj4/Pz4/Oj9AP0CGP249Ok0+N1Q4OzxNbpK33YCTqb/Y84uboYFDQmLKgvns5OHh5O+Cnu2i8e+zRl1on6OpqrXF0ej18omF/u3d0si848+BpqOevKS/PYuAiNy5gcSsuqyEpNeqsXedv6LCm3mclm/WeXW6tPh1cIustQV8fH1+fJV9Dn59fX5+f4B/f35+fX6BhYM6gn19fHx7fX59fX1+f319fX99fX1/f319fYB+fX1+f319fX9/fX19gH59fX6AfX19fH5/fn19fX6AfqF9HX59fXx9fH19fXx9fX5/fn19fX9+fX1+f359fX5+hH0Bfo19AX6GfSV7e3t8fH1/goOEhISDg35+fn1+fn9/fX19fH1+fXx9fXx9fX58iX0Hfn1+fn1+foV9CHx+fn1+fn1+hH0Yfn59fn1+fX1+fX59fH1+fX59fn19fX5+hX0GgIB/gICAiH+EgIp/Bn59fXx8fIZ7BXp6e3t7jHoMe3t7fHt7e3x7e3x9iHuFfAZ9fn1+fn6FfwR+f35+h30FfHt8e3yLewN8fn6Gfwt+f39+fX19fn59fYl8EX18fX18fX18fX19fHt7fX5+hH8Hfnx7fHx7e4l8AX2KfAt9f359fHx8fX19fpJ8hn0EfHx9fYd8jX0EfoCAfKd9B3x9fX1+g4OJfYJ+hH0BfIl9AXyIfYV+BX19fn18iX2PfoZ/E4CBgYKCgIGBgoOCg3yBgoSBf3+Xfod9h36If4SAg4GFgIJ/nn4EfXx8fIR7mnwCf4GFgwKCfoR9jnwBe4V8Bn19fXx9fIh9DX5+fn1+fn1+fn18fX6Ffwd+f35+fn9/iX6Yf4R+iH+GgAmBgYGAf39/gYKHgQuCgoKDgoF/f35+gImBgoKGgQGAhX4KfX59fH5/fn19foR9AX6EfQF8hX0Hfn1+fX19foR9BH59fX0CAgQAgLiyuIGA/OeW8Lyi3brD9bS+68Cq4tmMi9DQo//clqaYjvWlwYDanuO74/j404Lwisqe3+DXmszjo9mU97Sb6/eMjrXJ0Lval8W1zIvdusOS38DQwPyf0Nvak7vHstWJqJv2rri0we224qKMlI6olZKlp6eY95qwpJHtmKaH5v2fgJnR/OqFnbCY14er773VnITutaKmo8PempuOkoeZlqqCk6Ga2POTpZrI65qplt26iqCY55ePqb2IjLK6yq7c8vqFkrPzoOD2i4uA3f/0vovVhtu16a+ImfnEkKPejtWZnYDsk4zxx5+X672OmKH3scNzs6qJrpp9Rqttfnp3bomdgNaO233Oimtzrm6GiWqCZXSMZ1d9XHOTbIpvTHCJdnVaYy0jQy0tLkE8Ym54iqXJ74qUjfbTyNDe5ODCk5WttLqLye7rsajVxInrjZKWjunLubKUgYrCw+f8jqKw9Y3K2+Tau7K0yIq27+LJosCLsIfKh9W5r8bqtqGkq8Db8NeWgJvRgraShvzy2qqXm4uY6bW8wZDatp24r8W10u6euNPZr4SYX1FXmpybVVaWk9eQxumjmcin8L6m0JWawa6h+sbD3daFpF1hZmxuW3x5VkdXW4CmwdvS5IyR+oWTmomA6d+hnsfj4+B4WFJ2qYl/e3xa6MWr1te5x7S0ur7BqcfVgNrbyaCxwIyrzMSpZYWQsJWCg4SNtPPt6uPWt6mclZWSjY6TlIyKiYuKi46PjIeHio6Qk5SRlJyTiY+MiIeJjI6SkIyOkI6SlJORj46Qg/mZqaCSqKOioauQs9Po8uzGpZSL+PCNiYiJjI2LjIvHiYaSs7+iiI2Q3Gt7fXhxZVxMgGVrj5GVorK/zNjm7vb09fb5+vv9/4D9gIGChJCozYBUW1YzMFZILUQnO0VDNCEqxb+2sbS+0ev/iYv51761opKXk4+MiYaEgf7k4YKDhYmQm7XWhI2GiqfJ34GVssz2jJKEyp6Xmv+6gOPXyrmkmpqanJqbnZ6gn6KkoqKjpKSlgKWmqq61wszLyKmHh/727sfoiaWv0cKj78vOnJmmq6Sbn5eQra2NoJC1wt6m0eGC//DkwZ/p19Pi7fXu3MuzrKmfn5qpvcrZ0a601PXulbmM/7Wys7i0s7zH6PaKkoxjg56EXoGwfmVdYDw5amZIfYSPraaL/rO3odb564nM5O7xQ/Hw8e7t7Ovq7/P3+PHo4t3X1M233/7qvNTd5Onx+ICDh4WEhoeHjI6QlJiZl5WCf4FESUxMISUoKy9SlN+ZUS4VFBOEEjkTEA0cHBwfJBccGCA8LD4+PVZDbIp3TKSNksF+ioaAZ4KriZCwk9G93bm8xK+i+aCG+djLmYq05OOAbHNpTUaShFWHbGCAaHGHYW2EZWBza0VLYWJXgmlHT09hrW96RnRZmX2Tnp6FU3g6Uj9YXHhMZHFUfEt9V1h/dEJMZl5kXHhRX1doTWhZXk53XWRejVdlamxTYVtSaE1gWXlTVlpvfll0V0xQSldNTlZUVU1/T1lUS31PVUR5iVKATXCFgEdRWk5sRFqDbXtaT45pW1tVcIBXWVNTTFdYal9aW1eNt1VgWoiqXWJYooVOW1qsYlFgcWRVZml6d628t1VRVW1rkpxYWFKQq7GVbqdmooifXktYl4NcX4xhkV1gW51aWK2AZWKyfGFndJ9+pFuYmniYjHRHf1xqZGJac3+ArnKnaqZtW2CWXnV9XXdZbJBpXIhfgaSIm4pVkKGXlHSGSz51RkZGZ12MkZupv9n3ipCI8dHDxMvOxaeCgpuoo3ecsK2IfJeHWY1HRUg8Z1RMSk0+SG9/p8NwgYvGda7HzLCfn5qahZ+5maaMhlFvY6JfinJ3iI13c3mHlqazoGeAYZVom35x0tDCkYaKfX7Nr52YgcGViaScrKW1x4CXrbWYepdkWlV8g4JGQmpjkl99lGRffWmSdGaBX2F5cWibeX6Zn2uLU1pkbG1TYnuGkqeDbEw/PjdYIiIvFA8QEBEnQjg3QD4sLXJKQ1+LeG5sc1CCZFllYlRXTk1NTE1FUFVkVlZTPD5EN0ZmgYJTY1lfTUdJTFh/sa+tqaCOhXtzb2xpaG1taGZlZmVkZGZlY2NkZ2hoaWlqbWZiZmVkZWZmZ2poZGNlZGZlZGRjY2RasG11bWZ5cnJ1emWCmqqwq5N7bGW0qYRggGJjY2NklGRgZ3uDdWNlaatqlaGruLqtkeOIYWJmbnR7hIyUmZ2en6Chn6CholKkUlJSVF51lmlMVE8yLVFGLkUuQEZNQi42koF2cXJ6iZurWluhhnRxZFpeW1lXVVRSUJyLjFBSU1ZbY3KNWWBaX3OJn1xrf5axZWlciGZhZap+FVedlYh1aF9fX2BgYWFiYmNjZGRkZYRmgGdqbXB4gX9/ZU1Nj4V9aHI/RUpTTENaTFFCQURGRUFERD9GSkNKSVhaZ2KBiE+fmJF/YntzcXl/gX52aV5bWVNRUVdgaW9sW2FxiZtNYkqGY2VmaGZlam59lWFsXE9ka2hHVlNVWk9VODZlWD9eV1lsalmgb3FjiJySVH+Lj5KUf5WVk5GQkJCSlZeYlI+KhoJ/em6Kno5ygYWIjZOYT1FRUVJSU1NXWFtcXF5fZGBpd0JLU1kuNT5HTF9nkmZMTSsnJSQlJysuKyA8MzEzPiYuLDU9Kzc2M0Y2SUBXPndpYIdXXF1SRlRzWl1j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gYGI8sqjn4TWlZjY+pmumPaQxLGkp5fSjdnCo4tyiKlpeXd0aYaTgNOIrYfV3H+ir2yvfWqDZ3CDZVV5WHGUZ4VrmG+DeHN1VC8jRC0uLkI9ZXF5iqTC5oSKh+/SxcjT2tO+lZarqq+V77WfpqnhzP2oq7LB0M3HyMyajI/L0oSat+CU+7D5x+ygh+THvbFeb3+HenN1e4CGkJedpK2ytLSxrq6rpZ+bgJmVk5GQkJGRkZSWmZ2hpamstbu5t7StpJqRmJ6xs9HTrYtPbmZjl5ufV5mf+5DH9tDsuJ696qnWosmenebWl6Tfap5bXmVqbG9wXYU+YTxZZOurzIHh/5Cglo+WqJqNgvnAp9D28+56lJqIYpCDf31b7LqptMq+ybewusPHvdPcgOTf3qa6z5fC8OPUvJ60tpjX/vyLmsS/tK2L8ff79ezt6vL0/fj59/v5/v/9/oGDgoKBgoOEh4yPjIT+/f3+/4OHiIeIhYeNi4qGhIKChIXsjpSTkpOXmJynksHh49a8m4+FgNjsgICCg4OEhIfY8YaGh4iHh4iJsoOLioqHhoeIa4e6hYmLj5OTk5SUzZ6rucrc6vH09fT29/j49vb4/P2AmV93gYZ4eVcyM0Y9KBodvMG0r62qq7DF6YOOlI7zvbywpZuUk5GP/OmCg4WHjJSfssbS64qty+eJoMHmhpCJ3aiTmofIhuTbxq6ahJSAlpeYmZmbnZ6foKCfoaSnqKqptsPHy7+wrq2Xh4mC9vLN8pSvvOfYtIXn36mptb2foqCYlKy1oISgtL/Skq+ku7SurqGi6M7Iuru9wNPW0sO3ppL82M2rkI2UptbzkbCM5a6vsLWwrbe7vcHc14WMWYOahFl4sH9obVs6aWxkUIOAoKXgyaeV9o+AkYuIyI+Rk5SUlJaTkIuJ+IuPk5SQiYSB/PTtxtvxhNXj5uru+ISRmpaH9u3p6Obn6uzs7Ovapfv57tOorYqY0Ory7pKHnFcrKCcoKCgiGSQ6W2JeW1RWUlRhLD9dPT09V0JqiHiUinSOvo+6pJiDnXWlnq6TtsIR3MTjmZSljZqE8NXX+YKt5+aAanpmkEdKhVqDan2BZ2t0WFZgW3+EZVd2Z2VMfW5cbm9srHB4R3ZZmnyTnp2EVIKPZEltYYdYXGhmbU5SRG1qU0M/b0dlYn2DYVxRdmZbX39PVmFefXtXYlFOQlZPenxfiUtLVU5JTXxqRYNpUk9MX05JTkpRdFhTSlg/U1NEYHqASnp3VD9PUkRtRVxWcZJZd45GdHqMTUhQUopPSk5OY1CKk49UnopNSJGdUFRNYHJOWlW+k1Nfd4FZZ2RhlGKWRUlMVWtplJxZWVOPqrWWbadkoLlSX4OMYoNNTlOrgGFiYIRaYJ6bYHRuo2OSdmhsbo1fopB+dGeHfFhmZV1TcniAqm2FcqqzaoWTW5RqXXVba5BkV4NcfqCGmYSrjpyXj450Sz11RkZGZ12OkpiludLrgoaC6c2/vcHCvKSEhZuloIHJlX+Ag6yYum5nYWNmcmdnaVtOVIGUZXOJqm64mNiOqHF4waqrqlxmbXJxbXB1eoCHj5ecoaWmpqSioZ6bl5SAkY2KiIeHhoeHiYqNj5OXmpufoaKgm5aPhn6Dh5aYsrSZg1FybFp7gIBFZmSaXoKfgZNvYXaTZ4NnemVjkIVdZqNUhFJZY2ltcXNXaUGGjbyY9GFDID5mJiceExIQERIUQEM7P0csLm59gHBQgHZyc0+BZFteYFdWTktOTk5KUliAWVdWO0FIOUZUUk5TWnuIdImSklJvkIyIhGzBycfCu7i3ure5u7u3ubi6uLm8Xl5dXV1fXl5gYmRkX7m+vb28X2JiYF9dXmJgX1xbXF1dXaJlamppaWpucXZnjqWmmodyZ2BeoapbW1xeX2BfYZ2wYWFiY2JjZGOCYGVkY2JkYmAUXoVdX2FiYmJjZGOQbHR9iJGWmpyFnoCdnp+eoKFSa0pgaGpcXk0yNT5ELyUoioB3cm9sa2+Al1ddYl6ddHRtZ2JeXFpYmYtOUVNWWV1jb4CKnV56jqNic4qnYGhglm1fZViFWp2XhnJiXFxdXV5eX2BgYGFhYmNjY2VlZ2doaHJ6fX92bGtpWEtLR4V+anVCSk5YVEkyVoBWR0dKTkZFRURDSU1JQk1WWF9UbGRzcW5uamJ8bmhmZmZnbnFwaWBYTYNtYFZKR0tYb4tQYEuAY2NlZ2RjZ2lrbXx+XllIY2lnRExSVV1jUTZgY1ZJam5nkIF5Z6BcUmFrXX9aW1xcXF5eXFpYVZtXWlxdWlZTUJyWj3WIl0+AiWeLjJCXUFpgXFOZko6NjY6Qj4+QkIlom56ahWlsWWKHm6CfX1hnXDQ1Nzg6PTgnLzVbYVhTS0xOUWhAPFY1NDFGNEg+V3RiV1yFZ35xYlthS2xqYF91d4dzil9gYFlgUol/gJtOYoaGgIfEi7dcX6eAqIXVn3p5dUw/NjBHUDUuSzUyJEo1Kj46XLuBgEJgSVM8RklHOix1VD0rPDU6IiovMisfJSM1LCYgHSsfMS8xMjAuKi8sLS87HisyMTY1LC8oIB4oIzdBMD8lHSEfICAvKBo3JhobGyUbGh0dIiwhIh8qGiIjHy86gCA5OisbISEbKBskIS4zHCg6GSosMxwbICM0Hh4gICkdO0REJUBLKytRRzI3MT5DNUM9smI6R22FPUtJQ6ODdTAtLDA5U0hFKSooSV3V05zujc3OQzZqZ0+DOj1FqmNQUV1qTFSPiFdtaphbkXZiZXONXKyroKGQ8r2Im6+Ngb6oRfWcsK3l556x04DNmYazi5/bmIzbkNv06Ovx/fno9d7Rm6GL74WDgbiX3NPQy9LV2nBxbcy5q6WhnJKCa3SEem87OBsIAY0AgAUDCRcmKDhPck6Mc7B5kltg0N3j8YORm56fp66zusHJ0dfg4+Tl6Ojl5ODd29fV09HPzs7NzMvNzczMzczMzcvKyMK+t6+noKaZpaq6xsPJkN/fr/Lx84Oqh9F4ocWcqIBwgJ5xkXiMdnirkWFiy4TZm67R4/H4+KepgtxHPztvgDk6GBUVCAgIDB0WExAMGDM7LhsWE8D1+dqGyb27v5Kna2RiYVVRRjozLywlKSclIyYVGSAoNkRNUF5zko5ZXVJPLkthXFdWS4iLjIiFhX99d3l6eXZ3c3V1dnc6Oz08PD5APj9BQkA8dn58e3k8PTw7Ojc3OTg4Njc2Njg3ZDs+hT9BQEM2QEZFQjw6ODU0Wl8zMzQ0NTY1N19sPT4+P0BAQkJVPUJCQUBAPz08VTc4ODk6Ozs8O1hBSFBXXGJlZmVlZ2iEZ1Roa20+h4nV49aapsagqpbVp6iuxX9eUEhDQUFIVDAzNTJROTk3NTIvLy4uUUYnKCkoKi0yOD5EUTNEUF44QVBeNjo1UDo1OjVWQXNuXko4MTEwLzCEMYQygDMyMjMzNDQ0MDQ4Ojk0Ly8uKSIiIDk5NjohIyMnIyITJCUkJCUoJiguNTlBSE9UXGZobE45LTMyMTM2NS4qJyclJiUrKSYlIh8cMCkkIBoeICw+Tyw2Kkk9PDw+QEBBQ0RGTW+Zi4a/sMyIjHK07fu7gujuyKzAd01eWpF/ekE+gGSSdW5CQ0VGRkNEQkRDQXVCREVEQkE/PnZuZlReZzldZWhpaWs5PT49OW5qZWNiYWBiY2NhW0ltcG5dRkk/RF1rcmw/P1/Kjpmns8HVzY2RlO324Mu8vLzI/Oy9/KGelrqXuDyJ4cuqovq94rqdn5KEprlxoMCrwZ/DhYd2eJRvCKuqtvNpe626Bnx8fX19fo19AXyMfQh/f39+fn1+gYWDJIJ9fHx8e3t9fn19fX9/fX19f319fX9+fX1+f319fX9+fX19f4h9DHx9fX18e3t7fH19fYR8B317fH19fXyEfQZ8fH19fXyEfQZ8fH18fHyEfQp8fX19fHx9fHt9hHwIfX19fHx9fX2GfBB7fH19e3t9fX18fH19fXx8h30EfH1+e4V8FX+Cg4SEhIOCfn5+fX5+fn19fHx9foR9I3x9fX58fX19fH19fXx9fXx9fX18fXx8fX19fHx+fn1+fn1+iX0Mfn19fn1+fXx9fn1+jH0GgIB/gICAiX+DgIp/BH59fXyHe4p6BXt7e3p6hHsIfHx9fX5+f3+Efr5/D359fX1+fX18fXx7fHx8fYR8C318fX18fHx7e35+iH8Mfn17fHx8e3x8fXx8iX2HfAF9hH6EfQF+knyKfQF8hXsBfIZ9k3yNfYV8kH0BfJN9gnyIfYJ8iH0BfIl9AXyJfQF8k32CfoZ/CICBgYCBgoKCin6Ef4p+gn2LfoR/hICDgYWAgn+jfgR9fHx8hHuGfAF9k3wCf4GGggF+jnyJewV8fXx9fIx9En59fn59fn59fH1+fn5/fn5+f4h+gn+Efot/AX6If4R+gn2HfoV/jX6EfYt+AX+IgAF/in4Mf359fn5+fX59fH5+iH0Gfn1+fX18h30Nfn1+fX59fX18fn19fQICBACAs7au8IGC46fftKjBsf22tfPfvKe91siAi8vKwIvbmYeP1aHBgdqc57zk9/jQhbHkmYiE2Yrmk8noh/W61JSatYi39quLu5GgwMKkmsOurouS4rCjgojVnJ/4gO2doMjEps6XkozH0cLOgoOpm6Gh3IKKm6HPxLSn2LzdqZ6TyIuAidL8jb2FpILFjqSeqYuOipT50JWcj4Ga8/vRjY7L6OmC7rHlyNrFsY3C0byw1Njh4puI64yNmpmar4+ZuHf7/YiTq+ah4/iLioHc/fbBitqG1oPn+o/PpLec05eRlqOKiYaIgcSZjsq5iZee8a63tYnd+Ozz6PbKkqnGcnp0ho+A17jhcoKLjtig2KvA0YRqb39nV31VaZhqfmmZboF4cYBTLyVGLi4uQ0BncnuNobzb+YOB6M/Cw8rRzrmWmaujoI/456qHk+7fi660vteMhtWMzoCYrsLj/4yTn6itqKOhnZmWko+MioqJiomKjI+Rk5meoaarrrGztLOwrqunop+Am5eUkpGQkJKTlJicn6Wqrq+1ubu8u7i1sKqjrb/F29q8mVg9N22Qo6ORlvKZ4Y618dHJ26Cj3erro/L4zoL3ko5VXGRobG5wOTlfi0dpcFhlapvRg/KQkK6rkpu0p5aOht6x1oWD/X6GhX5ilYyIi2LgvbGnu6/Cq6i1w9We1OiA6unxrLzTndTp5tPJre+cgaGl4oCWxcK+tsucivWotbu9xMbN2tzf4+Pl5ePh6O33+vfw6/P2+Pbv7u/s6PT3/Pz68dzj6IaDgvv39PX8gYfl+ouKiYqQlZujmcjex6uViIHy6r/p8PH2/v/+gYS5/oKBhISFg4LpwYGDhIWEhIQagsGDgoKHioyNjYe6jIuNk5uw0PvTgPru7uyF7kzt7O7w8/uSsW+Dg5BQKzxj38O4sq6rqaeorbvdg5Gbn4faxretp5eFiIqGh4uRmKKqvuSOtNPxjqvP9oyN6ayRlorOh+bdxqiTjY+RhJCAkZSTlJeYmJyen6GnrrK4vb2/tayopqWko6KQh4X78/zS/Zi8v4P0yJqIgs3DxMysqaSgmLKzrJymssPS8Y3c+oaLhvaGpJ+Yge/byq6QjZKlv8nJscb6g4+dmpGboIrMrbCvs62qs7e4vcHB+qb1uIOohaR3qoI3QDo7aTdnVE+Accf0htVeJxkvLywvMTEyMhgYmpqZFRUVJycoKSorLBcXF5GMt+rVq4uNJ0tJSZWWlJeWioaTjYuMi4mFg/3s2IvQo7CtsPHIiZSA2/CAoquuW2x4RW1lX1ZoXGNeVlVWVlfcJD9ePz06Uz9jiXqPYmqZoZ/DsJ2BonOnrK6OyZ0Rx8LmvNuklJzi4tbajcer7eaAZ3tihEpLgnB/YIFiV4RZVndxW1FmbGFAS19haEdnR0FJlXB5SHdZmnyUnp6CVF5fQDk5b02CSGFuSYpbaEhYVEBYjV9EWktaWVxPUGVUU0VLb1JOP0dtTE5+RnhOUGdvYX9QTEZneGxtQUJeUFBQcEJCSE1lYFpSbV5sUUxJa0WAQmZ+SlxCUkBgSFRQWFJUVFeEdFJeUkVSiZt4TEx0lYZIjGyNdH1zcFZye3FpgoGEg11Vj1BQYGJaZ1F4n2GXjEhKUmhvlZ1YWVOQqrGYbKRhnFuMilZ6aXxchWJmXWNeYFFTXX9fWpBzXWd3m3N/eV2Wt5yjnsqaYn6eXl9ccHCAq5CpX2ZseaqGs46dtXJdaIhlWIFYeJ2AkoSsj56Ui5dxSj1zRUZGZl2MkpmmtMnh9n9848u9urq7taKGiJ6hnoHXx5V4eb2wcI2AdYVacLF9rWl9k6zC23uGkJidnJiXlZSRkI+NjIyLi4uMjI6PkpOYm5+jpqeoqaimo6GemZaAk5GPjIuKiouMjI+RlJeanaCjpKWmo6GemZOPlaKnvbymjllAPGJ3h4FiZ51jlF53moaAhmRlh4yVaZmaflGic3RNVmFnbG9yOjtZcEOG/8uqhXFQIEc5KyolFBUQERMUHks/QSoYMWxycGpOgnx4elSAY19bXVZXT0pMTFM/UlgwWlpePkJKOUhNUUxIQGVXVHt+jExrkZCMhZNVbL6FkpSUmZmdo6amp6iqp6eqq6uwhK88sbCysq6srKeosLKytLavoKKiXFtar66wr7JcYaG0ZWRlZWlrc3lvkaGQfW9jXLOvj6uur7G0trddXom8hF+AYWFfqJBhYmFiYl9cW4lbW11fX19eX12FYGFkaG97krCQV6SZl5eamZeYmJmZmJqdoF58U2dldEkyQ1SlgXhzcG5raGlseJFXYmhrWot8c21pX1NVV1RUVVxhZm13mGF7kqxme5i0ZWOecVxjXIxcnZSEb19ZWVlaWltbXF1dXl6AXl9gYWJlaGttcXR1eG9oZmZlZGRjVExLioJ/a3lDTE8xW042MDBQTk5USUhGR0RLTExIUFNWXY9WiZpTWFWhU1xYUkaAfnFdSUNDTlxmZFRhfERJUVFNVFVLcV9iYmZjYWVnaGpsa4lknIVle2aBTVReOUM4OF80XE9HdoKkWpqATkgtTUZCRkpFSVY0L2JiYjUzK01KSEZFRkgmLTd5V3GThGpXZi1PR0F5cGhkYVlVXVhWVlZVU1Kel4lZjn15bW2WflhdUIabVW15g0xfckBlWFROV1hf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fG52V1htVlhCUFVQTGhYeYBiV2RpYEp5allaYFRueUl2W5t8lJ+dhVVcYUE3M1haR3hZZFFPVWFYT3x7TFc/eF9sXUBbdmtDRD9GRmBaS0pCTGBOVEVFcktLYm1lfE1kfV52hU5PXYZTUElaa0ZNQVJPoJhuVE9KS2V9gE5Ia0d3ZXdiVk5STVxUkIx+UUaLVIFnflBPSUl+hXZjV32Qb3x7UJOKj4qAaniCd1dvkEyKVHdgb56Koaa7k4mFRU5ncZWcWFlUj6ewmW2cXoRXRniRTYONUJF5dmJhZntaX6mVZX1dUVyTcmxvZpCmZm1knnOioY5Zi4pwb7i4gKOScGJhnGxwZq6QcoxyX2aEZVmBV3WhfIuEp4uckpGaiUk+c0VFRGRdjJOZpbTF2+fv7NrGura1tbCji5Cmm5uJd56Guu6NprG+ztx1enx8foGDh42RlZqfoqWko6GfnZyZl5WTkY+Pj46Pj5CRk5OVmp6ipKipqqurqaain5uYgJWSkI6MjIyOjo+SlJaanaCjp6inp6WkoJ2Xk5eiq727ppFcQj5ggo1sWKNhml+Om2eYglyRVHOVVZ2PbWOFYkhUYWhtbnE6Ozw8WnN+QsnY0KZ9ZiEkPiwsJRoXEBESFBtUQUUuHBprbWlmn4yGhINchWVfZWBVVk9KSVNWRlBbNllYMUFDSzxIT09MSEBiUFVLhJt9a5GSj4qdWEdnO5+OwGhxe4WKkJKTlZiYlpiYmJueoaGcnoShOaKjpqWgqa2umamprrSxr6+rqKirsLddraNiYWJkaG94g3eCh3lsYbOsp6OCpqaoq66ytLeanbW6vIS9Arx3hV8HXVxakltZW4RcLV2cmVtdYWdvfJZbb2FflYF/g4mOkpOSkZGSkZKSk5SYV3deR0humYN7dnJvbYRpgGtyiVhqcnZnnIFsaWliXlpbYGdpbpFheZKybIWjX2W0hGFcW5FonZeGclxUVFVWV1dYWVpbW1tcXV5fYmhsb3R3dndxa2hlY2FgYF5cXFxQSkiIgEBvP0dPUzIxVTw3NllXO3hQbWZna3iJUF1nb3d4eHtkeIaJgHKLaWd6SVZegGZkWU9KRXh4SVJQSEhPTIR0dXZVVl9kY2RiZGdmaWlpgFBdbm5lgmFBVVNqRCxFNjY4N1RENqqicLRaQyZBOzo6Ozs8QSUmXl1hLykiPjs6OTo8PSErK2JTk4uUlFZBMjEvLywtLjQeIxcwRVxdS0Y/PzdsbF9NQ219bm5qdGZTRoxWdlJfX15iWGBlYFFKTlZcUlBIR1BLkSk3TzIxWT9WSlRddUlbZXd1gHRlVF5PYEFmZo90nGyKc1FSUleFeICEV3dUhoWAgseLrZaAXFQxMUAwLSMuLCsuOC9KTDMtPTcxJk40KDMxTIB/Q2VKUz1FR0U4K1EsGxYTISUgPSouKB8oLywjPDgmLB04Li0mGiQxKRwbFhcXJSYdGxggJxoeGx8rHBspMzI8ICkuJDItGRsiLRsaGSInGRsZIiBJRy0lIB4gMzyAIB4yJDQuNCgeHB0bIBkrMSkaFzciKykyJB0cHTQzLC8qNDAtMjEhQj4/OzkvOzo2JCpBIz4kMicxRqft9/xfVEwmKC5WSkcpKilJXNnTmOCDpEssWW9AgmhBenNWUFFjYU1RnnpWcFRLVY5qZGZmiJxibV+VdaKeg098hHGG//mA7tONjIDGmJWO8MelwqKNmMmZi9eIwvbS0OLw7Oru4t66oYzshoSBuJfe1dXR09LU0tHNwbOon52YkYd3f5F0cUkuPkp4nl9vfo2cr2Nvd32EjJSdpa23vsbN0tfc3+Hk5+ns7vHw7Ozu8PHw7+7v7evw9Pj6/v7+/fz6+Pb08++A6+nn5OTk4uHi4+Li4eDg397d2tjX1NHPzMfAucHP3uPq7aSFhLzt/qdxz3rAdKi0c6iRaJ9geapnwKJzXcaZhaXN5fT4/4KEhYOww++NuEZINzo6GwkKCAoGBhceFRMPDSxLRhANDqDRzcP8zsPDza25b2lqX1NPRTw3MzAmJyiAJiQSFhohJjJAQkE4LkQzLilRZEtEXl1bWWIrJTkkZlZuP0dSWF1iY2FjZmZkYWFkZ2hmZGhqaWlramtsbWlfam5sX2pqamtoZmZlYmNma3E4Yl84OTo6Ojw9Pzc4OzgyMGJdW1xMXF1iZWRmaG1bYHF2d3h6enh4TDs7Ozw+PDoGN1o4NjY2hDeAXV03ODtAREpPKToqK1VSUlZYXGBgX2BhYWJhYGBiZ0WIoZ2Zu6xwWk9HQ0A8Ozo5OjxILzk+QDdPPjQ0MzAuKy0vMDAzSjVEVGU+S1k0NltDMjIzWURwbl5JNS0sLCwtLC0tLi8wMC8wMTEzMzU2Njc4NjIwLy0sLCsrKikpKSOAIx86Ohw1HyAiJBISIxQUFCcnHDwuTlFecpDOiKi62uPfnUAqMzs+OzlGLSosGyMkJSUgHhsZLSIaHB0bGiAcNiwwMSksNjo6PT0/QT9BQ0BPMkFnsbzpvYGZcfzJitKHgZWNzqCm7oBL+6fri+HFwMHGwsLVgYlMRlS+p4by5eF24eTt+I3Hw208aWRqa0Hl0cm+wLS0vdiDroL/kkNEjLmhooL4+926iKqAU3qYi0M4X0iYhaW7xda62effu7miztjDvqmiuLG4iZzUjIr/k9yybozTi62z2sffvpyMjoOljHbw0KDmusuQb2hwabqbqapoo3Squgt8fH19fH19fH19fIl9AXyMfQd/f35+fX6BhYMCgn2EfAh7e358fX19f4R9A3x8fYx8FX19fXx8fX19fHx9fX18fH19fHt7e4R8G3t8fX18fH19fXx8fX19fH2DgHx8fX19fHx9fYd8Cn19fXx9fHt8fX2HfA19fXx7fIOCfHt8fHx9jHwyfXx8fH19fH1+fn17e3t8fHx/goOEhISDgn5+fn1+fn19fHx9fnx9fH18fX1+fH19fXyGfR18fX19fHx9fH18fHt7fH18fH19fH19fX59fn19foZ9B359fH1+fX6MfQaAgH+AgICWfwl+fX18fHx9fX2Gftd/A4CAfoR9FXx9fH18fHx7fH18fXx8fXx8fHt8fod/hIAKf358fHt8fH18fI19g3yEfYN+hX0BfpF8i32EfAN7enuFfQh8enp5enp7e698A318fI59mHyIfQF8iH2CfId9BH59fn6SfQZ+fn+AgH+OfoV/jn6EfwWAgICBgYWAgn+pfgd9fHx7e3x7hHwLfX18fX19fHx9fX2GfIZ9An6AhoEEfHt7e4l8gnuHfIR7AXyMfQ58fXx8fX59fn59fH1/gId/CIF+fn9+f4KDiIIIg4N/f3+Dg4OHgguDg4N/f319fX5/gYiCCIODhIOAf3+AhIGFgIN/hX6DfYt+AX2IfgZ7f359fn6EfQR8fn5+h30Gfn1+fX58h30Dfn1+in0CAgQAgK2P6LT/gcaQ+bWa4bi23Kay8cPCq6LezIb5v8OThdKMz/qRv/zdn+m95vn41Ialzo+D/tzbtN39mrLtx6vIqtrLpMil8oGe6erQi8u+yv6btpDm8fuUhaOblaaO+sqSlKaytcLOwfeohpWcj56apKTSjI2hoqDuu6P4kZCTkL/TgJiVgo7Pzv6YiqKphMWUvoiTlp2Juu7a3e2zr8z2gu+LrtKz3NaXnp2fn5mdhMiv9OL22KyHhKiYwaaeqaq/6O/xhJnOq+j2ioqA2vb1xYe/lp/swIfKyuGR2rHFi5z/l4eY9sKdn9KxjZyWgq63oIze7/ngz4O3ioz6i4yYlYmggKeQztyo2MKGqseqlIt5a3KMaVh+U2uVa39ql3CLf3mCbC8kRy4vL0RBbHd/jqG40eXv7t7HvrzBxMK6paqOiaKzx9XW0MnBtquhnJiUk5SUlJaZnaGorbCzt7e1sa2opKCcmJWUkY6Ojo2Njo+Rlpibo6RUV1laW1taWlispqGegJyZlpSSkZOWlpeaoKSmqq6zubu9vry7uLWwq7DAx9nVuZ1cPzlxo5+Ei4ftle2Sxr2j8IOO5uy86Lr+ma+bW2Zsb3E5Ojo7PDw9Y5alUnJJYHByvYyTk5mvurOosb2qmZKIuteOk5WTgn98arSrpKRs5cK3zNGzwLe4wubaxeXqgPLoi8bG4K3Z6uXVxq31rsCW99zOkM7LyMTdp5WKi+zX+4XK+P+FkpqmrbK0tbe1srXBwcbMysfS0r/EzdDR2tTX2dPM1ODs7fXo5+fk5Orw8vH00PaDg4SLm6q2lpSfjv3n29LKxqfX3ePt6+nw/Kjr7/Dy9vf8+pv28vX5+Pf0N/ap9PeBg4SEh4my+IH/gomSo7+ypOvbtqGTkJCC/qSywM3X2dra2NnW1dfa4oemuMfDvrevrKiEpkipqrHSi6i4vqbtw8m3pZman67ViaXD8JfA7IeJ46qQk/Kq6NfDpIn+//z9gIKEhYeHiYuNjpCToq63ubu9vbWqpqSjoZyYlJSElYCWlYeFg/rugdiGq8r1lZb4l4aA+oaCgZDx6vv7i0hTXGNnZ2dgj+TpmJqOwJbq19PQxr22ucPJxretr7K2uLe0urzAwLy7srXlycKxsLSytLvB9KCen871dm92YICwRz0mQF43OjZbSRlAhKiefAUHExYWFRUXGRwQJo2LigQHCQEThBR0FwwOFBuIyeXSpoVNGxgUExISEhEHBggHJI6NUBsZGRgXFhQKChAOFStiV8rAj/PWQSsyLCcyd352cFpWWFphXVFSV1tQ8SJCYXo8PFZ8Z5J8l12Ilr+vxqqdeJV3i2CzQ+nMibbivIaZ2paYudfkktiiydaAYFl+WX5CYUiAWEtzWVZxVVZ5Y1tSWW9lQoVaW05DY0JihmJ3kHhbm32Unp6GVlhcPjZkWFtmgH5JV4BsXWpbb2lZaVV3QFV4d2hHZV1mh1FzXHl1fElCVFNKU0iPZ0lJVmJteWtjf1pQTU9JVFBQUGpIRExNTnJbUHxLRUhJaHOASkhASWhogExKVFhFb1ZpT0xPUUJngnl1jmhlc41PiExijGuAfllfXl9fXmBQd2eWhqaRa1VPZ15+bGp4fIqFf3tBSmFylZxYWVOPprWcaY1yd5drUXSCmVSDdYVVXKhmUl2ue2JjlXJaZ29RcoJrWpmuopqTaY1iXKhmW2VtXGuAfmSbrXygnWaJn5B4dmVdZ4ZlVn9TdZx4ioCjg5uRkpiNST50RUZFZV6MkZekscLV4enl18K4tLO0r6eVm4OElKCxu7izraeelpGNiYaEg4OFiIuQk5idoaWoqqqopqKhn52bmZeWlJOTkpOTlJWWlpieoVJTVVVWVlVVU6ShnZqAmJWTkY+Oj5CQkpSWmZyfoaSnqaqpqaeioJuVl6Squ7qnlFxCPmaFclZeV5hhlFx9eWicVVuVmXuceqB2koVTYmtxczk6PDw9PT1bdng8nODUqYZ5JiRFLi8pHxcQEBIUGS5ESTAgH3Fvb2pVlpGNjGKHZF9kYVRWUU9QVlZNV1qAW1kzRUZNPUtST0lGP2JQV0qHfo5qmJaTjqRXS0FAfWyfWZS3w2hxdnyDhoqKiouLjI+RkpKSkZeYjZGZm5yfmpmhoJmanqinqqSlp6KiqLCzurmatV1hZGdyfohvb3Zqvq+moJqVf6Cho6qurrG2f6+3t7m5uLq3eLe0tre3tbEkr3murVhZWllaWnusWLNeZW14jIJ3rKGGd3Bta2C3c3mAh46OhYuAiouPlFtzfYeFfXdyb21qaWhoaWpvh1tyfYFvnHyBc2phYWVxjl1yjq5uiqtjYptuXGCfcJ2Uhm9ao6ajpFNTVFVWV1dYWVlaXGRsc3N0dHNvaWdlY2FeXFpaWllZWVpaT0lFhH9Ab0BKUlw1N2pAPkORUVNNV5qWorFpP0tXXGOAaGdUW4OMXmBXelODeHJuamZlY2doZF5YWlxfYF9cXmBjYmBgWl59b2pkZGZmZmhnf1JVVnCHVU9aR1RRN0oxUFw5OjZXRzNAXnJtbCUnRTsyLi4wN0EmRlpYXjAsIzw0Ly8xNx8lLjlWe46CZVRDNjMuKyoqLzQdIjAeP1lXTThXPD08PUBHKjRCMEA4V0uRd1yfnj88Qz45NGxtaGFPS0hTWFNGSEpNQospM0tgMzFCXEJBWHRDX2aHdH9zZFBdT1lIYTWTfFRsiXhTWYlWW3CCg1aAXXuBgGdgcDZJJDQmRjErQzEwPS0uQTwyLTQ9MSFNLC0uJSogOEhqfIBkSVM8RUZEOCtULh8YJyEnSl46HyY0Kys2LzguJywjNRYeLCcjFyQjJTMgICg6Ny0YFiIeGR0aPigYGiUxNTorKDAkIBscGSIaGhspHhgaHCAsIB8yIhwdHTIzgB4dGh8vLjQdHR4eGCMZIhsbGx4XIS4vMTkpKDBAIDcYIjUqNDEjJyopKSgpIjkxQj9IQTQhISsoOTpYfIufT0E8HyIrVUpHKSopSFvY0pTAkXBaRzxfcpJCbW99RVGlYEVOm2lRVIxnVGFqSmZ8ZlSMrJeOh2t9XVOVUFJZW1FhgHFpq9SZyN2Etc+/q5qWiJXKk4XPgMDlzdXa6uDo6uDawp6L7YaEgLeY4NbW0tPR1dPRyr+xqaKcmJOOhY11doONmZ2alI2Hg316e32AhImQl5+nr7a/x87U2t/j5+ru8PP19/n9/fv8/Pz9/fv7+vb1+f6AgYKDhIODgoH//fz4gPXz8u/v7u7s7O3s7Orn5ubm5ePh39zY1tTPxr3J0uLn7O+qh4jG86hxdG6/drNuj4p1qmZto7eVxJKlneXpn8fn+P+ChIeHiIiFssnLgtRKQj48PBoICAYICAYOIxgWEQ8UU0wTEBSh2t/Pg9XPzNTBzHFramBSUEc8NTAsJSUmgCgmFBUaISYwODs4MSk8LSwlRUVSQ2RhX11nLSkmKDo1TTNYcHdBSlJUVlNWWFlYWlpZWltbXl5jZFxdY2RiZGFhYGBeYWRnY2NeX2BgYWVqa2lnWG05Ojk6PDw7MjI3M2BdWVhWVUhXWlxfX2BpbUprcHJ2ent8eE55c3R3eHZvBGlKa2eENHw1NU9mNW47QEVJTUJDVVRSTkxLSD99TlJVWFpbW1xbW1laWltiVHJqY1lQSEI/PDs6OTg4ODpHMD5GSDxPPEA5Mi8vMTZKNEJPZj9PYjc2UjcxNFpGbGpcSTRXV1VVKyssLSwrLC0tLi8wNDQ3NjY2ODMvLy4tLCooKCkphSiAJSEeOTkcOB4gIycUFzkiJi50QjgyO3aKueu3hKjO3+/u8rZpODopKylAIjApKSsoIyIkJCUkJCAgICEfIiAjJSEgJSUjJjo2PTs8Pz49P0BSMTc8SW6Sla+Rl2uH25zv4oqYjdavoIVeSmStkJb6x6SSj5iz24PiS0RV0rmP47x3rau624Wfv9dBWWVcSz6U3NG8raquwt6Anu2V40hEorPDysfL1fqbwvmw3IiggqJZQYy5g6jBsqyG9/nr2rKvi8DNwaWlqq+VqYOPxv2FgJ71mT+By4C0rOvF3LqZg4eAoZp1hsyjcIzJnm5pyHBxpKysaKl1pagSfHx8fXx9fX18fX18fX18fX18i30BfoR9Bn9+fX1+gYWDGIJ9fHx8e3t7fX18fX18fH19fXx8fX19e4V8Gn18fHx7fH+DfHt8fX18fH19fXt8fX18e3t7hXwafX19fH19fXx8fX19fHx9fXx8fX19fHx9fX2GfAd9fX18fXx8hH2EfA17fHx8e3x8fn17fHx8iH0tfHx7fIOAfHx9fX18fX5+fn17e3t8fHx/goOEhISDgn5+fn19fHt8fXx9fX18iX0FfH19fXyGfQJ8fYZ8Ent8fXx8fX18fX18fX18fX18foZ9B359fH1+fX6MfQaAgH+AgIDMf4mAp38DgIB+hX0KfH18fXx8fHt9fYV8BHt7fX6Ff4eABX9+fHx7hXyOfYJ8hH2EfoR9AX6RfIt9hHwDe3t6hX0BfIR6g3mEe7B8i32kfIZ9BHx8fXyPfQF8j32SfoV/in6EfwWAgICBgYSAgn+FfoR9o34HfXx8e3t8e4R8B319fH19fXyEfYR8AX2Jfgd/gIGBgYB9nHyJfQJ8fYR8hH4HfXx+f4B/foV/AoGAhH+Cg4iCCIOCf39/g4ODhoILg4ODgn9+fX1+f4CIggiDg4OEgX9/gIeCDoODg4SDgX9/fn5+fX1/hIABf4Z+AX2Ifg57f359fX5+fX19fH5+fod9En59fn1+fH59fX59fX1+fX19foh9AgIEAEWL6/aStL2muMqaqOGbupOLrKTs07OGz862oMzGm53Xt5Oeyrrz1qDnveb5/NWGptiN/fLb3reykJr25eaL16vFmbPYobmExYDGxsGHj925+/eA1YWn5t6r74ujjoW5j42OsL3IxaOQgLqYoN3qpaGQgtuYpNGpq5yLh/mPjsS5lJL7mNihk6Kcj5bM3YORzJaQrMqFkoWnq867ptChorLCpOPkkp3j6t+/7+/Nl/nUk6mcuJm6gfOFsKeB4tDY8vqGl8yu6PeKioCA2fT4w8iigrmHptrVw52Ou5S6npTxvpicz+efuIiDir+mo62C4NyfpYzikrPO7o6t25rOvqH6oZXwp/Pc46KbiqyVsdRocotornqjcZlsf2uTb4N+foRzMCRJLzAwRUNueYKOobTK3ejq4M3Bv8TIx8GztsHS1dvd3NfRycG2rB6moJqXlpaWmJmcoaWqr7O2uLq4tbGtp6JPTExLSUiFR1VISUtMnFBSVVdYWVpbW1pZVlRSUE+al5WUlJOXmJian6KmrLC1uby8vr27uLWwqa++xtfQt6BcPztzl/6YhZKJ7JzqlbvgooWJi9Wo+t1sgVdlbzk7hDyAPT4/Pz5jlqeXm0dRc3Cvk6Ggq67Lw7W0zLyqnpLS15adnaGDQ4BuvLezr3TpzMG1z7W9vK3A1t3o7/3284PS0N+33Ofr0r6r8LO6jvLty5TV09DO8KeSgYX9goTvtpeMzuDyhoyTmZ6lp6Slq66tsLS6u7i5uLzHxMXJz8q9vsSAxtHQ09fb0sjM3eXs6vP40OX59P6KmaWigvH+69XLxMLAr6/V3uje3N/qz8Hp7u3s6+7tqdbp6+3t6ujqpebm8v6AgIGBp/X4+oCFjJmq9MTOv6iYjouI04iIiIaFkKGquMrR2NXR0NLW2eDjg5mtt7CqpqWlpKSlpaeuzIyzxcyAsfzNvKKevemGpNGJt+yF9biRkoS/9tXEqIXt7vDy9Pb4/P+AgYSFiIuVpLbAvr27tq2opZ6alpWRkZCQlJGPkpGRk5OE/ILt64Dfi6GfqZSUoeKKobnsg6W53d3JxMOisa29xMPNo5L9m8X5oIL98NzRzMXExMvHwMPAurS0sbZDsLrFxby6vLezs7rEucm6sK61vfOgrobH0fmk0X5efKxFPCU8XDU7Z1pKK0uKhLCwBAYTEyQlJicUFxwoh4SDAgcTE4QSdBQWDhpOgoz07eSDHRkVEhEQERERBgMEJIWFUR0ZFhUWFhQHBAUKEU6MYF2H46WQqiYcIyMnOUB/dnNfV6NaZF2OUlddUuxCQVpBf3dYhWyZ9pegjYjSrsOrmnic5KNhtIzwzozHkJn8oIGkmdK034G7nfPdgEp/g0VVX1JaZE1UcUxZSUtTUHtnVkJuZltWaF1KVWRWRU59dY51XJp9lJ6ghVVaXT1mYFdcZmZSUH95eUd0X2pSXG9WX2VnZmZnZ2VFRWpYi4ZBaUFVcmxYfEVRR0hiR0ZMZXF2ZlJKSWtNUG5/UE5HQ2lIUGlRVU1FR3pHR21pgElIfUttUktSUkpPbYNLU3JNS0xeSFBQd2J3bm2FXmJshWGAgVhgjIuFcI2Nel2ZflVeW3BeclGcU25kTIVzcXd5QEhddZWcWFhTjqS3m510VHBQYop+f2RRenJ1YFuudVxflIxjfWNTW4lqZm9ilI9ualqfc5KXoV97j2eQhG2sgHhkpXyroax6fW6RfJiyWGiGYaV4oXeddo19oIWSkpOZkEk9c0VFRWVejpKYoq+/0Nvi4tnIu7e5ubewpamuu73AwcC8t7KrpJ2Xko2MiomKi42RlZmdoqerra+vrqunpqJQT05NTEtLSktKS0tLTEybUFJTVVZWVldWVVRTUlBORk2XlJOSkZGSk5WWmJueoaOmqKqqqqmopaOdlJmkrLu4qJZdQz9pbKtlVl5YmGKNXXSOaVVZWYxyqZlWbk5hbzo8PD09PT6EP4Bcd3hcktrqupOKMyhJNDQtJR0WEhQWGTNKTzIpI3tzPHBYmpmVkGSEZmBbYVNTUk1UVFVYWV9eXDBIR01DS05QSkM9YE9UR4SFe2yen5uXrlNLRUiJPj17YWZsoK27YWhvdHt9f31+gIKAgIKFhoaKiouNkZOWl4+Li42PmZmZnYCimpiaoamsrbS0maa5vsRocnd2X7rCr6OZlpWTiIWcnaKlpqeslpGus7S1tba1haOusLKzsaypeKmlp6tVV1hXdqyssF1ka3R/t46YkIB0bWlmnGhnZGFeY2xzeYWJjIqHiIqKiouPVmZ0eXNtamloaGdnaGpvhF56hox3pIF0ZTlkfJtdc5hkhqtfq3tdXleBp5KEbleamJmbnZ6en6FRUlRVVlddZnF3dnRycmtmYl9dWllXVVVVWVeEVoBYWU2LRoN9P3BFUE5WXF1qkFJZYm49VGBhWFFUUktRTVVZWlx1fqlhdpRiToqEenBwbm1qaGhmZ2JhYGFgX19haGZiXWBeXltaamZwaWNiZmiBVF1Kbm2F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0NTYoJvqsanu75aLia2IvpK7gpGZqIPG6piVjr2knLfKh6GEp+2UjdWwkYui0YCJituahMOKr6SOksGMiaeCjoPUuYuRiebv+rmyrKKgxfOQvfTCsbaczaTEw46Q64nile2kpezhwc3FkaTWvcz3hpOb0LHq+IqKgNfzu/O+quSPo6mH95CX8NmF4oXiioCYq7OVk7O3kJ+K/qmzooeB04PZt/mtlZD9mJaKiJObiGiEkIaFgtDvrJeL2Iik2a9vj2umdp5ym22Bb5RsfX59im0wJEkvMDBHRXF7hJKjt8ra5Obg0cbAvcLCv7O6xtTX2dvb1s/JwrmwqKOfnZuZmZmbnqKorK+zuLu7vLq0VlRRT05MS0pJSIRHgEhLTE1PUVRWV1hZWltZWVhWVFJQTk1LlZOSlJaXmZueoqissLO5vL29vby5trGssr3F1NC3oF4/O3WEkPeWh4uL9Z3i2tjZlYKvpO7MZU5ebTk7PD09Pj4+Pz9AQEBnl/WrqkZFYW2Mi6K3p6rLx7uz0cayqaHr0Zmmp7JERUHYgMC8ubd67dPJq7muxb6+vs7f2eaA9v//zt7gu9zz6dW/p/2zvpXx6N2i4Nza24CdioCKi4z18OrausbPqN3n6uz0/oiPmqGjoJ6gpqmlp6moqa2wsbS4taywscC7xMXIxraxxs/R1t7i6drN8fqBiI6MiNvV3cm+u7y5uKK+1NjXMNfY3N+f3OTn5ebp59yn5eXj4OLh35vZ2+Tu9fTz753s7fP5g4qajI24uauckouGyYSEgIOBgv3PhoeArIHDyMrLys7Qzc/U44eZpqWlpKKhoqSnqK7JkL7Kw466op6cpKq64cG+t5yIh9eP1sSjhejl4eXm6uvs7vP3+vj8gYuarLS2ury4sKmgmZiXk4+Qj5GPjo2Mi4yOj46RkoH9/vDc3J+y/qL4qcrt+ufEq9aumsTagNvKxr+ltKTCwcHRol9/rM2GboGBt8XGxszFxL+zsayoqamjp6eqtsD8t7Cqqa6xub64mcy4rrj8mrP5ws/ykYGXdWiEqYc6IzlXVzlrWkgntUuEobsoJyUkJCcxJCUmKCqDgoIkJiYlJEUsJSYnKiuF3/nhpoOMKyclIyMjIiIRYBEMHYL+SxsYKygoFBUUBgACFp6AqzYzRYnE33QwIyYlX3hBQ3Z0ZFRfWWZZTFFUTojrSEFggoN4WopwnXqboZSIcbLAopvrn92isLGV9NCKx464nqKBj9m1kpOrg5jAhIB1hHRTU0doVkZoUUtnXVlwW1ZCXltVR3FgQmJVXFxOYD89X1FecVyafJOfn4RUWl0+NjRcYoRNYGJQZF6LgU5SiWJdbG1WY2VQS1pZRFtyb1dHamltUVF2WVZ8d0tGS11FZE5rTU9OVkRxeE1KS2FPS11tQ05CXXlLR3RWRkRVcYBERHFdQ2NFYVdKTmZVUFxHTEZ9ZElOW5aIknSramJhhpRUapd1bG5efmJ1dVNTilGCWIxgY5SMeoN8Wlt0YWh3PkNGXXSWnFdYU46jd6mEZolZZmJXlVhcn5lTiFCLYk1dcnZdXIV4XWljnW1+allVlFWTgM6Ka2Gua2NfY2NqZmdcYG1eWqqleG9jrmqJsZVkgl6fd5tzmnqLfZ6EipCPoJNJPHNFRUVmXo6UmqWxvszX3t3XzMG5tbO0saittMDBwsPCv7m1sKmjnJiTkY+Oj5CTlpqfo6irr7KzsrGuVVRSUlFQT05Oh01UTk5PUFJUVVZWV1dXVlVUU1BPTkxLlJSSkpWWlpeanZ+hpKapq6qqqqmmpKCZmqStu7inl19DQHZaYaNlVllYl2GOi4WMY1tya5+tVUZYazo7PT4+hT+AQEBAXnmIWW6u9MCYiT4mSDUzKycfGRIUFxo0VFMzMSSFPkQ8qpudmpZpiGZiWlpSV1NRUFNWVVcwXmBgRUtLQkpSTUlCO2JPVkiEg4BzpKShnltYSDtDQ0aHgHR7bGqPfqeqrra8xGhudnd5eXh4eHt7fHx8foGCg4SIhHx+go6Aio6QkpWJhpOcn6Onq7Chm7W8YmdpZ2Smn6OXkIuLiop4ipygoKGgo6Z2q7GxsLW3tKl+ra2sqailoHCin6Ompqmqp3Cqqq62YGZyamyKjYV5cGllmGNiYF5cW1uuj1pbWpBngIWFhISEhYaHipRWYmxraWdmZWVmaGhrgGCBi4Y8XnVmZWVqcHuXg396ZFZXjl+QhW5VkpORkpGTlZeYmpuenqFSWWFqb3FzdXJtaGJeW1lWVVVVV1dWVFNThFSAVldLjomDfndmhK1giE1MUFBLQjtHPDhNVFdUV1RLUExYWVheelRjeIdfU0pMZ2pqb3NubGhiXl1bWltaWFdaZXyeYVpXWFhaXWNkVHJoZGmG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kqSTg3K1wJST4aPbmqq1oPfOhsWGu5LxoqOxp6Wlpa20hsWAdWl8RmJNYz9Pa0JOfUdbT0NYVHNwXURyW1J3TVo/dE9Ma1F3YU9NmnyToJ+EVFlaPTs5M2BUYW6EZW9hgWiEilx8ZnhaY3ZWVHFTfktCT2dGcXlHd21igmxpbnpQdnmDR2B2S35laUtOW2ZDR3RyQUZ4SnNLT2eBVUd/fFRVSX+AWEdSg1tSV4GKVk9mUU5mX1xNW05SkZZxiIBwXIB7i5RwYodmTY2KYU9gZHdhTVRjY11tUXGYanJzZ2VXamJsQklGR2B2l51YWFOOooidVmFnVlS6RmCMjl12e1iHUWVjVktnS4SjUV9iaWRrllWUaGyjnG+IlKZka1drfaBqipFtXoaIX3yBonCmX8eqpY+ynH9dmXaac553hHmdhZqTkJSRSjt4SEdHaWKQl52msb7K0tfY08m/uLa2sqynr7jExMXEwsC8uLOsp6Gcl5WTkpKTlpmdoqaqrbG0WlpaWFZVVFNSUVFQT05PTk9OToRPTlBSU1JSUlFSop+cmpeSj46MiYiHiIeJiYqKjZCRk5aZnZ+hoqKjoqKfm5mjq7i2ppdfREE5fFljoWOuVl2YYnygi6ORXJ5/fFZncTs9PoQ/gEA/QEBCQkFge4A6V5v8z6iNRipHNzQrIiQZEBQXGjEwVzI2JI9BSD5XrLOvq22FZGJYW1JaU1FTVVVdLzBeLzFFUUxCSFVMSEQ7Y09WRYGBe3anpaSiXUEgAg4NHUWDeIGBiX1qUVyOoqWqrrRhaGxvbm5vb3FxcXJydXd4e3x5gHFwbnZ4enVydHh/hYiSmJ+jpaajlLK7YmZoZKyNhpGLhoODhIdujZWWk5aboJCLra6us7azsHqrq6ytqqCZbpecnqGipKOccqGjo7Ria3OifIGCfHNsZqmvwsC5sKqloIWoqKaocmmjn2Jve4CAgYGCgoOEiJ9cZmdlZWRlZWZnNWp/Y35vmp9sY15bYXCPbHyUbZWIcluOiYiJiouLjY6QkpOUlpaXnFNYWl1iZ2lqaGNfWlZVhVOAVFRTUFBPUlldXl1ZioqAaGyCTV5eXkNFJycsT0M7SDs4UFdbVVdRSk9KV1hYXndcQ1h8d2FjcXptVVxfZ1BUUVRSVVhaW1xZWV9aW1hWXGFiYl9fXWJ9aGuHTV6ObnF+T0NL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80OJYoF+dliFcaD1nK6T6nGzv6WQ2aDSlKm9nO/J75u8mrO3raeio6Gio6OUgqp8andLdk9rZk9KT1pOTGFWWXZTVkVHY1VWekt0XFtwbkhZeGhgWEd6lXuVoJ+DU1dZPTpFQjRUVGZITISUTpBheEpUaIFicVpmgFtTXlN/RV5Ab3dGbXZ8RHRRWWNab3F5cXKIjVJgW1JWSmNna21vbm1Qa2poaGdmZUpkYoRhgGKKXWNiY2NkZFtUZGdpa2poYptmYH57ipCI1IyDem1nfn1ebmxkWFlWVXp4d31SWV9naHqIXXyZpYFkZm1pdUZOSUdfeJecWFiljaFMg21UTFiDaYpka2xyeoF+dnFkTFlWTElOWlZcVH2mgGVcpmxUkXTEjL1hlH5qWHRqXWtlgFxvY6R+mphjXol6pXSne6lus3JnjXCkcpRrjHSRjI+VnEk8eUpJSWxkkJeep6+7xc3S087GvbWzs7Owpqu3xMXExMLAvbm0r6mjn5uZl5eWl5mboKOnq6ysq6qmoJqVko+Li4qIhoOBgIB+fnx7fHp4enx6eHZ3dnd3eHh2dnZ1RnV2dXZ2dXZ2dXV1dHNzcnJydHNzcnFxcG1ohYmSnKmmmYtbQD47PXlYZaRiWFVkmqKIjHSOZWBRYXA7PD0/P0FBQUBAQEGEQoBifkVscdyB4bSXjzZIOTYtIyYdEhQYGy41WjE+KaJDRjxdYWJgXG2EZmNVXFFbVVNSVFNcMTEyXTJETElERypKR0M7YVBURYF9dXanp6elYTgdAA8TDT9APjx1enh+eHVkcGyTmJ6nsbS6YmZnZ2dmZmZoaWVkZGK2fauFcLJbVnSmmKqBaH98h46Tlpqip5Cbp6uso5eOfneAe3l4en6EcpSRjpCXnKB7pKmrr7OyrnmlpaVzjZiYcZKWm5+eoJ+Cip6cnLdkaWKucnd5dW9pwZi9u7ayqqKcfZqcm5qWlpJ/hYuKjaNgbXp9fX1+fn6BhZlXYoRkgGVnamuAWJ2UUVRYXWJsh1dQgXVekIOCgoODhYWFhomJiYJoZXJ3eX2HlJtRVltfXlxYVFNRUVBPUFBPT05MTEyXTFFUUU9Pe1Reko18SFpiMUdLKiksT0U7ST85U1dcVVVOS05IVlZaXnJVRluBfGNdf3dsklJfY2RdTEd6R09ZgFGGTU9SVlBUWl1cXFpZV1VX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S0BIYVRTWFF+hF5CbXhNlHuVn56CU1VaPz1MSTpZWWFeVHFcZFugh1VRVFFLZGJtWVhzg3x6iJCRS1BaYGNiYGVdXWVlZGRjY1pZY2JhYWNmX2BvcXFycW9gXGtpaGdmZlZaYoVggI5aYmJhY2NkT2BhZGdpaWlNZ2dkVqucu/iEgLmG329TlE5UVVZZW2BYWGRvf4uJVWeMr3CUumGTWZ5ycGx6RUpJSl96lpxYWKSMnoV1a2ugilRzf2JbdHpxcXt2aVKOXk19SX+HUlOVk5tXVZGmYIqNaXOkkVpnXmpvZHBxrGlzgFxibWifwpKWemF2kZi8pItkiWaou35lk3WYgZ19goOaSTx4SklJbWWRmJ6mrbjAyMzOy8S7tLCwsKyorrbExMPCwcC9urWvp56YkYyGg4KAgH58e3p5eHh5ent8fn+AgYKEg4WFhIOEhISFg4OEg4KCg4SDgoGBgYCAgYB/gH9/gH9+fn1+fn9+fn5/gIB/f3+Af39/fnx6eHSKi5GbpqSWilo+PT5KOnZeZqdkW7Fkonuhc32EWG05PD0+Pz9AQUFAQEBBQ0RDRERkg5NCcJHz7MOmkUBMOjouISggFxIWGic6WzJGLatDRTteaGplYXGCZWRUXFFcUlZQWFVUXzExgFoyRExGRUJTSEZAOl1OU0SAenJ4qaqppmM3IAESDAhHfkB5dGtxdW+Ad3h1jZBzkZyipKersra3ubiztrOuqqiZd5ppdLZNUVBIRD9Qa2p4c3iEiY+UmJqNjZqZlo6CenRudHJxcHiCgn6MiouRlpqTh6Smq66wrZONoa21hJ2RN26Lk5WXmpuabpeVlpu1xcmNyWpucG5py5O6t7OvqqOcfJKTlZSTj4p0f4F9fXx+h4GLp2FyeXmEeiB8gJVYYWNlZmdoaGJfg01QU1hdY4lnXJJ7fHx7e3p7fIR+F3RXa1mDg493YY1yh4+TTFJVWFZUUk9OhEyATU1NTEuVk5SUlZWXTZptcIZFRD9HXjM0SE4rLC0oSjtLQjpYWV9XWU9MUUlUWFpZbE5GYYOAXYmHenNwbVt2fFpPTU5OSoqNSklFREWBhElKR0hGgGxnbHyFSEh8Z2V6RniESoaFg2lUSlw8STpHTlI3VkM7kDc3aq1tJiE0KCOAQD9DJjEgQlRlHCwhOzc4OkMqOFFTck5TkmY9NixRTyku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Y2JiYmZoWmhxcnNxb2pPamhmZWRkY0tiYF5eX4BfS1VfX2BgYWJLX2BjZmhpZ05nZ2hm2uqAl5qV8M26U5JOU1VXWFpenVZhaHiKoXGIomWBoq6FU4iBVHJ3cj1CRkhNXH2WnFhXpIubRYNiWGNzYV5gbWFQUFxhT5BHUEmDV1BHgHx7eXFadsmdobWNwbmjdryFbplnjJl3p4Npn0+QaIycbHOSjLa4rNCie4tpt42/xJBulWXFwnh6mo6KkaBJd3hKSkpsZZKYnaWstL3DyMnHwbmyrq2tqqWtqq6qoZyWkYyGgH17fHt8f4CCh4MrhISFhYWGh4aGhYaHh4aHh4aGiIeHhoWFhYSDhYSEg4KBgoKDg4KBgoKCgYeAB4F/gICBgoKFgDF/f39+fHqNjJKbpaOXjFk/Pj9TSDd8XmpYZV2ke3WMcE9ibjo7PT4+Pz9AQEBBQUJDhUSAZYdKkD+WvPTTs5hLVjw9MSMpKB8SFBclP2I1SjS0REY7XGZpY19vf2BiVl5UXFZaVFhVW1swMVkzQUtERT9QSEU9Ol9LU0J+dWp6q6ysqWZIQRYPH0FBfYB7dHB2dnR/e3h+b21pl4ecmpucn6CioZ6Zm5qXmJmVb3hiZqhQTlCASkN8TZXKanN0cnyBh4uQkoyBkZGJfnZ0bmZwbm5zgYl9fYWHjJOXm3ygoqmsrayneZ+iop6Vim+IkpWXlpaWbJWTj5iyvLOLv8TOadDIn662r6ypoJh5kI2MkZGPg3V6eHRvbW1tcnt6dGtkcptldHV0dnV1dnyXWmVnZ2dfUWYvj0tOUVZeXlBVXWRkZWhpaGtpbGCNmIp9SIOFf3x4eoNubnyJlU9QUFBPTUxNTk2FTICVkZCPj5GTlZeYTXt8gkFCP0hhNjdJKCwvLSlIPExCOldYMFdZT05MSlJTVFhoT0Nkg3xZjYN7d3VzcXFpj3GIRUKIj4uAfX18dGxiWFhZXFpVVFliam5yd05fdEV3fUJ7fHJelV1rPDdCNUFIR2FUOmBxZC5CpV9LI0A6NzY1NoA3PD9vWWE2NTEwLy8wMjU3RVBTlYVSXFFMSkhISkwpMC54VT1DOzc1Njk/KhstM2mIVCQkJCYrMDM1cKGWUzszOFxXZ3FqZHBTREpJb3NvbnFzbpxLXkxYWFdGWYJDpXhrY1mJYnGEW49bZ0pEWYZORnVb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hYSEgoKDgoSFhIOEhIWFhoWHiIpGi4yNjpBJkpKRkJCQj4+OjIyLi4yNjo6PkJKSk5SVlpWUk5KRkZKRjKCipbK5tqaXVDw4NVNKQmuViJWJhKawU0xbZTU3ODo7Ozw9PT0+P0A/QEFCQ0NEQ2ympYCA74ZzaUd/v5r599PW4+3mxNnRybrW+PvO5vdHQ3rde3525nrivMe1zLXRydnO1tf194mJ/Ja42s7HvuHTxqma8LTHk/favK3p7u3sj6u05sWIh/Dt+/zv4NzdzeLNzujS1tDMjpSuytfTz8jBt7azsa+usLaOzLrV3KCioZaW/IeAjZKBg4yYlpefoKeopKiToqOZkYyJiPyMi5KmtbSSpauutLe2s6PGxcjIxcGSwcO9vbOuk7HJzM/R0dGSx8G7t87XndLc2+Pq6ta74dfRzMbEnru+sqS+xLeos8uBnI+E7sns3tbh8O/094Cb4ZCfp6mrqa2x04SdoZj+qu2BhIiAjI+KxbCqp67BycvNqPCPu92JgunEvbrB0efl74GptIDc7PqCjcCLiYaFhIOA/v769e3r7fLz9fv9/oDR3viDh4arhJmb5IaVnJOH7bf23a36gI725M+6pqHAs7zCjF9ajbCZZu3Yy8G8t7Wxq7KSxJeqtMHP0eTg6u/e8uzsgIyAj46CgY+dn6OrrrHj3PHy3uDMkMq39L97TzkwUVSVZmF2aGdwxGOxsdfEu8q7mJ2p4PL6+fn49vj9g4mF+u3l39WDxIDa4+ry+Pz8+fTienv5/YCCPTk5Oj49PiMjTO2wVSYkJSYnKhcYG6Xx11BeWFpXWm+DgXV/XVRehp6TkY4ykpVI90WCY4WCeVmI6Zf/n5SFl+ahvLOJoOuFzN7U+ZCr1ImupqCYoJqWnJ6bm5ydo5CAT2dCSIZceVRbXlhibXJmYmVpaGRDjolnYGBrQXhlZnFzbG53RUNTkHmQnJt9m1NcQD9EPzozNTp3T4l5WWV7naaPdV+iinVzc2FUmH5wdnBoY19fW19nWWhqbGZZYWRiYF5dWFVhYWJkZ2pXb3Jycm5oXVVlZWNhYWJJX15dXF2AXU5PXFxdXV9hSGBlZmhmZlBhZXttaGRhS2FfXFlTU4+TUFFTVVpfk1deaYGVWnSOr3CVuKxxlFdtV4NvfXV5QENHTlmBlptYV6KLmGKFcmdYfIZ5UVZqXE1Q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RERspqvbe/SdflVj4ob4gu3l3/jy1Nfu1ca9jPjKivuFfnbd8vns5XvatsuywrDTzMnHgNTQ7PGJivqYs9rMx7vb1L+snOaswpP45b6w7vTx75SxmZ6Tgvjl4feB19LV09vxzczj19jNzJCQgvOUt8nBtKytr7GtrK2xj8rE2tebnLCfkYOK/+v39/6NlZaQnaWioaKjlJWRjImFgv70iI6xv8Gukamssba1tpjAw8XJx7+cgLi8uLiuqpijwsjQ0NDHnsS5srXEx6DOy8zV4N6q2NPNy8O+obm3r4icsqSytqTznoPRvva70dTW5O3q6vL09fWAoeeXoaCioqOksOuL/6fo9oGDhYaDy7CqpJqM+9rGjY6aqciGhoDy59TOyLu1w8nJ6Ymw0+34mrrvg//8/vn3gPf8+/Tz8/Pv7/L1+v79xNPy+IeIsoWfnN+Aj5GTiuq1gOOq//qO/uXbvJuaxLPCwYdcs4+yml3j08G2s7Gtq6e3wpGT8pKdjZaanZ+jn5iPjJCSkZiisfmAqKSkqamq0d/Q1cf+uaKu4ZTbZlBeZV2dZl9mZsGOez9dvIWdmVFIgJV2a05QUJ6ZmFZRT1VaWlJPUkj31a6k+IODhImNkIz+hP37+fn47d6d4rWksNeOnfqgp8jPZnRIVTEvLjOs+rRvXlpcVUhxhIJ1f19VXZCZkY2PkZZH80d+YoSBeluF45j6oqOOmsv4ouCZyJLJ/cbfrODMqsScraCVmpuXl5ubBZqcnJqfgFdfRGFUQW9XVmxFV2RjZ2NjZWViZm1pYl1cXmBdXl5nZmhrfX1tWpF4jpmafZpSWkFBQzw6NjpCc3dhcJmCVlJlkq+ef2asknp0cWJVlntzdHFpZWNeWWZpe5tkZFpeZGJeXFxYVWFiZGZoalpxcXBsZmNLY2NiYF9hSl9fXlxcgF1RS1lZWltcXUleYWJjZmdNZmK1xXdmT2NiYFxXUo6VTk5SV11enVZecIiaXnOJp2R2eVeEcY9rVE54Y4J7fD9BRkxbgZSaVlafiJSBbWdmZmhxeHxxVY55bZt+fFtrjF6QYn1GS0ZDZG6S76uDWWxsamJbXLGgmXm0jLVxgoh3SoJ/trdqq4ZqsZGoX66dkaN6cJW+ZZhyj2qhbG262rPbWkk7dklJSmtkkZacoqivtbq/vr26sqqlo6Gfm6qOg4qNjo6N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e36IfQR8e3t9hHwHfXx9fH18fYV8gnuOfAF+AgIEAIDygYL9+/rvjtqdwuuBhYiIiomIh4WGiY2Ki4yLjpCTlZSTkI2Tg6y9mLXHy7LKkMSUlqGUoay/yPHgxcfIycnLysrL0eT8l+Obpd+F7rqW98utloLs0sbTx765sqCpraijoZSVnZydn5uXs73CxsietaahnJv9oYuWip+CjJOSkICPjNuRkpOQkNaSk5WYpIenpKCbm4aTrbGppZGAhv/q8/yDi5eryJHKgq3vjuubxuSmg4CB//r5+ff1vbGK94CDlJWjv6ebnayroIiRtKaelpOOhP716+T16+PWuq7Q9Y+ciLrK1MTHppavhWKKf3CDg4B/f35+fX16e3l3eHZ1d4B0cnJwb21qaWZmZGBcrZp+v4Ogqb+is+fi2uTC8JJyRFE3UVc4OTpRUnyAhJKxyODVyMTAv7myrKioqay0sMrS0c7LyMTCv7y5tbCurKqoqamqq6+xs7a5uV1dXVxbWVdVU1JRT05OT05NTk5PUFFSU1RVVVZWWFlYWFdWVVVUUwJSUYZPOFBRUqiqrK6xtLW1tLW3ubi4tLCptayzrKBKXUQ9PDEwLi1WVVYsMDQ0NTY3OTs9PT8/QEBAQUFChEOAREVFRkZFcFVieOnwlF9ch8nfrYSbjPmFj4fPgPjaza31ma71fG1r0N/c1NLjyrvHrcCz1tTbyuHN7/6FgdiOvNS9tZDHs6OajOirv4748MfurLvGx/3DlZ2Qg4H5/fPr09PY183W1evz5+Xq25iUi4OHiomDtYjpl4+Qmq20kcKAwP/7sKmuqI7+/YaBgYeLkZOTlJmaoJ+fkpWakY2Ph/mB/vqEvsrhvKSio4+uqqajnYenqqinoJKbpsPWl5mLvL3Cx8mgyMK+sLOEvcDIzcaqwsvNzcKwwdDSwbimkJeYo8yMhZi6pY3XsbT9jpOJhIGCgf3+g4ODhIiChZGWlpaAl5uipKiut8jT2eLj6t3QxrqyqJmNhKaTjZKn/fny6PPv6OHf5unvlrzb8OTLsZ+MgPDh3tza2NXXwazEy8vKy8TBw9HfsLvZ8IqEwpCmnOKEhoOI9eeq8Oyr6oqaguHIsOm6xq62r4mppoWnpL+mp6SioKOin52bm5uYmJeVkpGAk4+I/drHvbKQkJGN9+/2gYym8IC+hYuVlpCnrL7M3fOY0jpDNIOiZWadfURcyUZLQVEnhq0ZEBglISARDw6JaiscFhQTFAgDQ86QW3USEyUlFBkmGxuDRBQUFBceKRoXWpfZNRYUU7QdGSs2aUNtdXlzX7Cy/tBdWFtaV5BnZ4M8eoNgVmaqrqyHn6Kqm/dDeFJ8gXexh86K0LzFn7iM37mDxIXEibiOvcuhpffr1cnM5vr08P2IiIKEh4SAgHtAQoF/fHdHb1VqgUhLTUxNTUxMSkpLTU1OTk1PUFJTU1NSUXhlYH1jcnt8bH5JUT08QTs/Q0tQbn90dXZ3eHl4eHl9i55fkGVtmF2lgWWnhXBfT497cnl1cWtmW2FkYF5cU1ZaWVpbWFVlbG5wcVloX1pYV4xcXXJfWklOVFVTe1JQf1JSU1FSeFJTVVdeTWFfXFtbTlRkZmNhVk1Rmo+XnFBXY3OCX4pbeqlmpWaCkmpVUVCgnpycnJpzWEJ9QD9EQ0lTbGJfaWpkV2FyamZgXFlVppyWjYiCf3lvYFBJGyktMR8dKThEL5veq31xbIOFhIODgoOCgoGCgIV+Z318e3h3dHJxbW5sYFyrlnuugZq/xZKkz8270bDYnYxXaUt4eElJSWhkkpOSnrLG2c/Ev7q3saqloJ6foa6qvcTDv7y7uLazsa+sqKakoqGhoaKjpaeqra+wWFhYV1ZVVFNSUVFRUFCETk5PUFBQUVNUVFNSU1VVVFRUU1NSUVBPTk5NTU1MTExNTZ2foaKjo6SkpaanqKeoo6CVnpykn5lJXUdCRTg3NDJfXWA0ODo6Ozw8Pj9AQkOERIhFhEaARWhHUmDbwn+Pip3Jgns1HBxMExUbVA4bIDM+cD1TrmZcUZGhoqCgrmFSVktQTFdSVVBUTlhbMC9UM0BEPz0yQDw4NTFVR09AdnFionqIkJO6YEJCOzRAeGVca2lqcHhxdGtqc3d3fJRrZ2ZkYmBfXYNdjm5udYGOjW5SQFq7W1OAVlRIgH1kamtxdXl8enp/gYWEg31/fHBubWjIZcrNbZWgqpKDg4h2kpSXko59lpaXlY5/hpOkrHx9aoyQk5WUeJKPjYqWdaKmqaqplayxsrCplqGtr6KVgXJ1dHyaZGV1cV5LZ1FKiV5iWlhWVVOlplRVV1dYU1RcXl5gX2FjZmuAb3J6fn+EhIZ7dnFqZmBYU1NGOjpAU4SIioeKg32BhIaBj1hug4uCdGVZT0iEfnt5d3d2d294hIeJiYiDgoOIkGlaZW4/PE81OTVDJycnKktGN0tJOFMvNC9UUEdaTFBMT1BcfXJZfX6PZWhoZ2VlY2JiX15dW1xbWlhXWVlTk4KAeHJrVkBBP0lGTioqPWU6RC4xNTo4R0pSU1pqQ4Y7RzZIZlVUfGs9SoZBSj9KNWJ6Myo0UElKKzsnVm5FNSwpKzMjLV6EXVdcNC1MSCYuQSkzVkcqJSMlLjokMVhpqUMiJEmIMSkvNF0+YWpvaFJygbiRTEpMSkZvUVFpXGRLQ1M3goWBZnJxfXePPFdAWltJe01tOVpKUFdfR29cP2FAYkRjSGBkTU96cWljZXSJfHyBRkNCQkNEQQwrFhYsLC8lGi8iKTGEGwocHBscHBsbHBobhBwdHR0cHBscPS4iQjA1NjUsQEAwIx8cFxkaGhwkNzmFOFA2Njc5RFAwSDM2TjFYRDRTQTEnIDcsKy8rKSckHyIjIyEhHR8hICEgHx4lJScpKB8kISAfHzQgIikhIBodHyAgHh0sHR4eHR0sHh4eHyIcJIQigBoeJCUjJSEeIUFARUcjJy0zOi9LMEFZNFEvPUY4MjExYmVmZmVkQScbMhgXHBshKkYwKS4xMCs1OzQyLy8uK1RRUE52d3RtZEkYEgoQJxQXHw8ODxtBSFzAus/8/fz49vT08/Lu7e3q6ejn5uTk493Z0c3HuMG+mYz3z6nWmKavV6KGksW3scas19/1pNWc/OeGhYKxoO/r4d3g5e3k1tHLxb+3sq2rq6y+v97h4N7f4eHi4+Li5OXl5ufn6evv8fP09/n7/YCAgYGCgoKBgYOFh4eIhoaHiYWKBoyNjYuJiYSKDImIiIeGhoaFhIODg4SBgID////+/Pv6+vn4+Pn5+vfuz9PW6vL6gayKjaiPjomB7ubsjpeSkJGSk5SUlpmbnp+goKGgoaGfnZybm5qYl5TPgIiF189uIyInPAUGAAABAAABAQsKHRwdN7VZmNbBu4ukvcC+vv9rUVFJS0hNQDsyMCclJhISIBETFRUYFxcUgBIREB0aGxcsKyBfSk5TVGwpFhcUDxQ1ODc7NjEzNjs6QFFPSEE6RC8sLCwrLCwqPjhSOjo1P0RDODAvQ2UvKiUiIVhNMjIzODs9Ozs+Q0RFQ0E9Pz4zMjIwWC1aWzJGSExEQkVLSmFjY2FdUmRlaGVeVV1lZ2tQSz1NS01OTUBNgE5PVmdNcHZ6dXVneH1/fnlpbHh1bF9PPD49QE0tMT81LiM0LCc3LzIwLSwtLVtZLS0sLjIwLjEzNDQyMzY2Njc2Oj4/P0BANTAuKikpJyUjIR4dHyMtMDQzOzItMTE0NjMfKC4xLSUhHRkYLSsqKikoKCkmKCwuMC4tLS0xMjUpQyAlKRkWGQ0OEREICQkKFhcVFxQVJhUYGjc+QVNOVVZbWIrEnm66yO05NDMyLy8uLCkoKCgmJicoKCkqKiZIPjY0MiuED4ANDAUJERoVGhASFR4iLzI7NTRkS+adxY05kKao3OGLgpWWtZuGiFNfoI6j9uXuktuORfbdrYqAh6iCwO1oTayKt5b884Ol/KTDS8CtkoaNteie0MVM/euEko+TxZ+KgNmQ3fj86LalitxpoZ6dmp3hpqPApr6OjMH9//e91tvt4y+fhriDsbyR+JK3Kj8lJj86KDwvJDQkMiUwJS8xIiM0MisjKCkzMTMxGBoYFxgZFQN7fHyEewV8fH19fZh+BICBfoGFgwKBfYp8AX2Mfgl/f4CAgIGAgICFf6F+BX1+f4B/iH4BfYV+AX2UfoR9hX4Jf3+AgICBgIB/hX6HfQN8fHuGfAJ/goWDAYKHfol9g3yKfYR8AX6ffwh+fn59fXx7e4h8Cn1+f3+Af3+AgICyf7KAln8CgH+HgIN/nYAWf399fHt7fH19fHx9fX5+fn1+fn59foR9B3x9fn1+fn6GfY58A319fIt9hHwEe3t7fYR+AX2GepB5AXqIewR6enl6hnuDeYZ6A3l5e5R8BHt8e3uXfAJ9fqZ8An18hHuEeod8gnuhfJB6AXuKfYl8i30HfHt7e3x8fIR9hH6GfQR8fX19iXyHfZV+hn0Ge3t7enp6hHsBfIx7B3x8f39/fX2EfoZ/D4CBf3+Dg4KBgYGCgoJ/gIaCEoODgH5+f3+CgoGBgoKCg4J/gIaCEIODgH9+gIGBgICBgX9/fn+GfgOAf4GFfgF9h36JfQJ7foV9CHx9fHx7e3t9hHwHfXx9fH18fYR8inuHfAICBACAgqzjkK3R7YOMmp6gm5GFhIOCg4eJjIyMioWEhoqMjJGSkZKUnaGnovmToKCKrobAk5eknKi/2fGH17O7vsPGx8fIxsfIy9Tti8mDmc2D+8ma+suvlYHoycDIwbauoJ+qoZyalZGcm56gnpu3vMHDt5qjmpeWkv+fqKaSkeSPkI2Ajo3Zi42NiojSjpCQlJ2CoZ6YlZL/pK+3tJ6HjoeBgYL2h5GozqHYjMP67pzShrWC8uzp6uXj5ezx87aoh/mFiJKSp7aMxrjg6duwyqWfnJaSjYb+9vDtgv37783B5IafpZvU6OTOuqOHhmxajIJyhIOAgH9/fn19fHx7enh5eXhxdnV1dHNycG9tbWxsa2lnZGFdWFSPbZnLi4WR2HxqQktXN1JZOTo7VFJ9gYeNkpWWvNv389PDtq6rqqqrtbPM09POy8fEwr+9ubWyr62sqqqqq62xs7S3uV1eXl1cW1lYVlRTUlFQUE9OTk5PUFFRUlOEVQ5XWFlZWFhWVlVVVFJRUIVPOVBQUlNVVq+wsrS2tbW2t7i4srGpqLOyq55LWkM+OzMxMC8tWVouMzU1Njc4OTw+Pj9AQUBAQUFCQoRDAUWERoBwVWV6gvKGaV2zsN/S45+bgI6Sh+T1g+TSwPuT+fl3Z87E3tnV0+HFvrqpwLnQzNjE39PrgYSDzoW3xbCshsKzo5WH5aPAkIDz0o3Q5vP1n+Olm5OHhP755fLY7ffn49jS2/L05OXWlJGQioeGhoK0kI2I8e2XqK2OvcP5/7G0u2+wnoP+/u2B94WLkpSXlJSa67aVlY2MiIiIhPj7+YW86cK6pZ+hoY6cmpqTk4GioqCgm4ij3+KSloSztbm5tp64tLCpoZG0u7/DubDEz9DNubPL8PGyo5mcl5KVsb+LoeTMuYTUyomSlomCg4iGg4OEh4CIi4KVmpqcoKOoqq2vuMnPz9ff3eHUy8S1qpuOiqSQjpKe8PP29/rl5OTz8+zzmcDohYXqyK+aiIHx6uvl3t3Y2JbW4ePi6e7v/sijur/Z6IeHxIunmteChP+C8NOj6+ut7pCmguLAv+HOzrK2sIWsoYGmpsPw0bKjmZOSkI+UglT+/P6A//j19fPw49PFtaiQlJWRhYeKq8OCtrSTtLO4rqGttsDM3POTyThAZf+G/2h/iEddykhLPqtH/5k1LicjISEjIkWPTi0nJiUlJCUhgJeXb0CEI2skJS05kZATEiUlKBYgLlL4bBsWFBVn7BkeWzlnQ2l2enJls7T+1F1XVlxXSWhrcHlsX6e4rLCpoaCioIaMmYmyeXpyqITSi+TFtMSQnYbRrevB2Ma8qKSmrNCqmZGMjZGO9fbo4uPRy9/phD9EV3tPXnOCR01UWFlWUElJSUhJS05PTkxMSklJTE5PUVJSU1NaXV5ooVxiYVNsRFA+Oz89QktVXTh5cnZ5d3aEdUB2dnZ/kliBVGKJWrKLa6qGbl1OiXRwdm9nYlpaYFxaWVVQWVdZXFtYaWtvb2lXXVhVVFGPWV9eUlGBU1VTUU96hE9sTXRQUFFVW0xdXFpYVZReZWlpXlFWUk9QUZZTW2qHa5RiiLClZopXc1KbmZaWlJKRkpmgb1ZDfUJBQkFHUFmBdYyUjXSIaGdkX1xXVKOfmJNKkYuEe2dRKB4uOTYlIy03QjCa4Ix0dG+EhYWEiYNTgYGCgoKBgH9/fXx9ent5dXNxb2xlYVtXjHCe1Y2DlMuDfFFfbkt4ekpKSmhkkpSVmZ2dnbbN5ObJuayloJ+eoK2qv8TCvry7uLa0sq+sqailpKOEpBmmqauusFhZWFhYV1ZVVFNSUlJRUU9PT1BQhFEGVFVVVFNUhFUaVFNSUVFQT05OTU1MTExNTk5PUFCio6SlpaWEpheoo6KWlaOinZdKWkZDRjo5NzUzY2U3O4Q8Bj0/QEJCRIhFBEZFRUWERoBFaEdXYWTdbZmW25aGg2weGSYSExxXJA0fL0RsPFuvYVSdip6foJ2kYFVRSVFQWFhYTlJPWC8wMFIwQEI8PDBAPTg1MVRFUEA+dGZkmaiyt3RtSEA6MT55aF9wYm9qcHJycHN6en+AmWxpaWViYGFdg19cXJG4eYWIblU/YrJbWW1aUko/fcDGbNJsc3Z4fHp9gNuogHxzbmtsa2fAxsdnk7OSjoB/goN5j42KhYh1jY2Kg4RyicS4eXlnh4mNjYt4jYqJh4V4mKOmpZqUrLCxrZ+XqM/JmoR4enRucISQanh4a19Ea1pJX2RbWFhYhFaAV1hZWldRXV9fY2RkaGxucHN4fH1/hYWAd3JtaGNbVVJJPD9EUYaFgYSMgoB9hIODkFxxik1NiHRiV0xHhoF/e3l6eHhpk5WYm6CjpLajdnZdaW4+PEwxODNBJihLJ0pDNEpJOlcyNy9WT09aUlFMT05Yem5ZfHuOn4x2a2ReXFyAXF1PmpqaTZqXlZSSkYqBd29mVERFQycrLzY9MEZGN0JBQkNES1BZWV5rQ3s4QmSIR6BTY25ASohERzmHW8NmQ1BEPTo6PUBbWkhEPj07PEBHR1VrX1xIQj06OTtAS0VibiEgPj1AIy4uUapnKCcnKFu3LSlaN2A+XGlsZlx1gblGkktJSUxIPVFSWV1RRnuHgYaBc3VzbFFPYlx6SUhEdklrOF5KQU1MU0prWHVgaGJfVE9QVXFUS0dGR0pKhIJ4d3JtanN9QoAXIDAeJS00HB4hISEgHRobGhkbHR4dHBsbGhsbHBsbHBwcHR0gHyA6TisrKSI6QDEiHyAZGRwcHRM0QEVEPTg3NjY3NjU2PEgsQiowRTBeSTdVPzAlHjQqKCwqJSQgHyMiIB8eHB8fICAfHSMlJyYjHSAeHh4dMx8hIh0dLx4gIIAgHSsbHB0cGyoeHh0eHxoiISAeHTIhJCUlIh0iIiIjJEEmKzE+OE40SFpRLz4qPjBeXl1eXV5dXV5XOCYdNBoaHR4iJz1HNz9EQTlRNjIxLy8uLVZUUlI/hoJ6clAaCAoTLBYbIQ0ODhc8RF2dwND+/vv69/Tz8/T19fTz8vT19Fzz8/Lx8Onk5N7i4NPMyMG4pZyLhMGbv/mGfI/brteWveSZ/OeHhoO0oO/r5eHe2srT2OHgzsK6tK6sq6y/wuDj4uHi4uLj5OTj5ujo6Ors7e3w8/X3+/3+gIGCgoaDEoSHiImKh4iJi4yMjYyMjY6NjISKD4uKiomJiIeHh4aFhISDg4SBTYCAgP/+/v38+vn5+fr89vDQz+Tr8/uAo4iMqJaVj4mB+P6WnZaUlJSVlpeanZ6goqGhoaCgn5+dnZybmZiWk8+DgYVsy20mIjsjBAMBhgCACA4OGxstr1ZEyrCi+ZO1t7ay5mFMS0dMR0c8OjAuJiQSEREfERMUFBcWFxUSEA4cGhkXFSUiOVheY2VBKxQQDhYVKj4vMyktKSQuMjhDSDg9REg0MC0sLisrK0AzMTZlWT1BQDgsLTVpLCYgHyIgRV1iNGg0ODk8Pz4/QmlUPjyAODEtLSwtWltdMkZRRkM/QEZKSFxbXFlaTGJjY1xZSlyHiE9DOEdISUhGPUdHSE9SS2VqbGxmYnN7fXpuYmyWjmdOQUA6ODpDQy88NC0nHzcwHjA0MS0sLi4vLi8uLS4yMy8zNTQzNTY3NjY1Nz0+PkFFPzYxLy4rKikmJCAcHx6AHi8tMzU9MDAwNTQ6NiAqMhwcLyYgGxkYLy0sKykqKCkkMTU3ODo5O0hpOjAcIyMXFhoNDg8QCAgSChUWFRYUFCMVGRo2OUNNTlRVWlmCuJJpsb7jVkQ9NC8rKikoKSFBPkEiQ0NERkZFQjw2MzEqFBINBQUHBgoKExcRFxofJSiAMDI/MzBgRtGSsPpoNs6grNuIgZaXqYnE5OBHpN+3qKSjssLgRoG8taumrLjU30ZcRpCh0Lq2tr/S9dFeuoOA6+rwhq+sr4P3mZ2irbHEuZLggtuK0Pjy4NKyi91nnZ6cpKGApJ6gn5OC8ff5/PHe3c+mX0OPwOqKh4DdiagpSCMiGSI2LCM6MUAuMS0oIx0dIEIdHBwYGRscLzAqLS0nIikpFoN8hH2gfgKBgoSDAoF9iXyCfY5+CX9/gICAgYCAgIV/oH4BfYV+AX2FfgF9hX4BfYt+AX2LfgF9hH6Cf4WABH9/fn6LfQN8fHuGfAJ/gYWCAYGHfoR9AX6EfYJ8i32EfAF+pn8Dfn59hXwLfX5/f3+Af3+AgICxf7aAk38CgH+IgIJ/nYAOf399fHx7fH19fHx9fX2Gfgx9fX59fX18fX19fn6HfY18BH19fXyLfYV8gnuGfoZ6kHkBeoh7hHoCeXqEe4N5h3oFeXp7fHuJfAF+iHyDe5h8AX2dfAF/inyFewN6enuqfJB6Bnt9fX1+foZ9iHyJfQmAfnx7e3t8fHyEfQR+fn1+hn0EfH19fYl8h32LfgR9fX1+jH2Ie4R8i3sLfHx/f358fXx+fn6HfwOAfn+IgQOAf4CIgQV/fn5/gIiBDX9/goKBgYGCgoKAfn+EgQiAgIGAfn9+f4Z+A4B/gYx+An18iH0LfHx9fH19fXx9fHyEewF9jXwBf4d8iXsBfAICBACA84CC+oOA+P6GlpucmY6Hgvz6+vr7/IGDiIuLiouIh4mHiIuQkpSe8arDw7+h+v69kpSgoa7I/46SxZioqbK5vsDDwMLFxMPEyN2Dq/eYufSAzZz4xKeS9de/wb+vpZyWmZuM7JGFmZ6eoqKfuMDCu6KRlpORkfeUlo+LjNyKiYiAhobXiIeGhuT0i4mLjvqZmpSxj/2Yo661q4+akIuG++uCl7uDwYK0/Y3M7JLCgOTi39nS0tPR3ePGrpCoiP2JiZSSpKze8rvU3dm69p6dm5aTjYX+9fLuh4WGg+PN8pCop5XK3s/HrJ7la1CjloJ0QkKDgYGAgH99fn59enp5eXl1eHZ1dXZ0c3FvcHFvbm5sa2loZmdmYl1aVZBmWXFBSFFbN1JYOjs8U1J9goeMkJWboKKcrdDj48WzramqtLTM1NPOycjEwsC9ureysK2sq6qqq66xsrW4XV5eXl1cW1pYV1VTUlFRUE9PTk5PT1FSUlRVVVVXhVhGV1ZWVlVUU1FRUE9QUU9RUlNUVVZXsLKztLS0tra3trKrn6mxsaiflmFEPTw0MjEwLy4uLzQ2NjY3OTs8Pj9AQUJCQUJCQoRDgERFRkZHRnBWaYSG7ItoY1ucs/m9m52HkZWM+OyG7trG+4ng9XRhv8Td2dfU3MHEsq3RuczW0cHYzOb8gPnQ+a64squFqKudkYnap8uUif7Pn+2CiIq18bakmYSC8+jf7+Dy8+3u4uPp/uTV1tyalpGOi4iIg8KUjY/927SaqoywgLTo8LGpr7KZjv/w0OLt9vX+iY6SkIySm5OSivj+hoWD/+TvgrfrvJujmpmdlY2Zk4yO+JealJGS6pWTjYuM9KWsr7KXpa2uqaiMp6yxtbmVr7e7uKynsqyIrpubnJWUoaS21ZOsgebPk+rZkZabjYqLjoiJh4iLiYmNkf6WmaCkgKOmq62xtb3By9DY2d3e1tDFua6gkIeejo2Oqe3s7fbz3Nnh8fPk7JnE5YuUh+rDp5GI/fLr5+Tg4+X52Ofq7vuFiY+fvs/I2eyOi8H3ppS0//37gejCnuXnp++Ko4Dpx9vX0c+wt7aBpqD7p6u+4beajIn21eP1jsGmnI6D9+DbgMa5rZmL+vrtyZSal4iXocTkjrWxmdDRycKkrLzF19f2ibk2PWWE25+reY9HXslGSnSRfsW525Doxf6NhYqIgYGE/oiUjIOE8Lbrq+r7hI2ZkYWB/v3++ey4nKfYXWzgooxTTygnKXuCLGVkb2hDZnR5dWGqsv7VW1lnbGFcbWp1PHllW6NYo7Ghl5eC4uv6qIq1fPnlp4LUh+nXv6TS7eqEj5ujzcWntKurrImXkZOSlZqfl5qblZfG9Jyv50KFRkaMSEeNjkpSWFhUTkpIjIqKjI2NR0hJTE1PT01MTExNT1FTVFmebXl4dWSdhU4+PD4+RExiNjxwYWpwdXZ0cXOFdIB1eIpTbZ1feKdakGymfmhaloJwcHBnYFlUV1lorFNMV1hZXV1bam5ubFxRU1JSUYpS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07yn/IL7+PTv3tzq6+rY19P17vHz55uZlo2Ji4mDvZaTkOywgMCluZLX0IP1p6/Aup+Qj+jI09fX1tvo8OXr+PPz6vLr1dLOwb6/wsPJktzgup6BjY2MjO+Ig4SE4oSFgID05YD8+freio+SkPWYmpaP4IiMj5GJhIyPlI+GmpWTlISMjY6OhY6hqNi58KaX9OmqjvKVnKWUj4yJiYuKk4+QkJOZgIOcoKKnqaewr7aytbzL0NPX2+Ti19DGuqackaWJh4ea8enf9Pfl3+b2+fHym72SnYGJvcfWqJaKgfvx5u3y79zMhoiHg4GA+YGDxfWAiJ6Yy4Ghh6fa7Oni17mS0t2i5ZGfhvDvh/jMy7G6uPeZk/2kqbq0jvvczr+6tK7hhK6vgK+rsZ6pmaaokJGGiImJmqKelKWz1vKk2c2e39/W2bPUytXU1/aNvWc9X5iK5fDqzpBmzElJdLfRTEKsihMnKS8rMTSFIRUpKTMtNDDt0J6RMi0qLysrE0KGKygoJSISDktmeHAxWS9COnVwj4BlZGFodIphcnZzbKS0+85na2dnQWKjc4Bzx7y52MuigoqG9ef6ge7ggrvy99ylgdOL9/LZucSot8DfrITirpSNi4SQidT0ktOJpbKrqru/wMfK09fMgG5eYHVzdXqAjUxQUE2Ng4KFhISFhIaJi4uKi7mCm0pJSUtOT1BPT1NSTYZkh2VfcXRwXVNQVztGSGhQVlhZW1dWWFhYXWRsb29vbm9vc4ZUdpp4nrSTaZxvWFCFb2BhWldTU1RQj1BNU1pfY2NebGlgVYpPTk5NhUxMSkqOiUxLb0pJdUlISEl1SkpJSoVPUlFRS49RVV1hVWVhXFiYnFdqkHarf19nlFmVV4B8eHJwbWtyoHllWpmTk46DS4tPU09JdEFNgl9kXFypkWBYX11bWFZTop6al1hXVVKWeFczJDMxGRMiJjVCebDNkIF/d4RGhkUGRIlEiIhEhEMEhkOFhoSFZYSEg4SEhIODhIWEg4OCQD94amtxeT9LeHlLSkpnZZOUlpeZm52foaOlpaOhm5SNkqGuqb3Av7y5t7a1s7Gwr62qqaiop6enqKqsrlhYWFlZWVhYV1ZVVVRUVFNUU1FRUlJTU1NUi1UFVFNSUVGEUDlPT09OT09QUFFRUqSlpaWmpqalpaWhk5CYnZiFmLRtRkNHPz49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fn9+hn0Nfn9/fn9+fn9/gH9/g4aCAX+Hgg2Bfn1+gYKBgYGCg4F/hIERgoOCf3+AgH+AgH9+fn5/gH+MfgOAf4GHfoZ9BXx8fH19hnwBfYh8iXuDfIx7AXyLfQICBACAT0Fa1I+Yl5u35/aA8tKuqa2ztrbR09HS19re5Obo6+zp7Pj//oCA60p63OTg9de3qp2tpN6G/v/FkPLs7u/w8fDw+Pby9vyPoaWjoPqmlI2Mka6Q9q/qsuneovOwgM2unZuXkIyFjo+OjYuhqK6qmp2MiP/9hoSD4Pz6+YDZgv98/P/J8vDw8Mn8goHh44KFg+n+gIOJ+5WbnpuHnKbFnvCyhIeiqKTUgrqqtPeLtqmgg4Hu0LKlr9S8nLqnnZmhmJWBjaWq4vz28OC+5YOOjIiGg4D6+PT2paWjnY6Cl7DQiqHGzt/X0NCvWH7jj457RUVEQ0NCQ0JCQkA/Pog/AT6FPV88OjpycnNycnFxcG9uNjU0MTBbXjAxMzhWWTo7O1FU68vJuJ6G3rmwrKempqOdmpeWlpilx83NzcrGxcTDwb++u7m1srOxsbGys7W3ubi4ubi4tbOxr6uqpqKhnpmYk4SQgI+OjY6Oj46NjIuLi4qMi4aIh4WCgIKCgoOBgoSEg4aHiYqLi46OjZGSkpGQioeSob3a5o+umUI9PTg2NjU0MzMzNzk6Ojo7PD9AQUJDQkJDQ0NFRURJRkVKSEZHS3BW2Hx7i5ycam2F24PPwZWfo5mevJq9p5CK1LTo7tCln7HVgNfT29HDvLrnxNbe4Ma+0cbb9ezexduMkpyV6/6gkIP22qrZop6P/92QlJmazYPdzLuLjILv2OvKyOHi29/PxuTv8vbUoZ2WkZKMj43FjKCmlfzXhJv8z8L7/7WwzMevlI7ausPBy87S2NDKzdDW09Ld1auhpKqoop+tyv61rbCjgJCI+IaEhPzt+/z73PX16O3W8/P2+Nz+gIWA5IGBgOX3gPeB6On19/flg4SB+O6A/YD5g42Qm5yptJvZmpPh3J+T+JGep5mRj46Mjo6QkZiXmqCdjqmqp6utsLG5vLzDxcrN1tro4dnRyLimlYiikY6VrOTd5Ofs5Oro8/Pmgub7gPPAgJL1rdnEupeNhYH89vj59uO/u7e7vb67t7O1lZqfrKbXiZGGvL/a08zAsovD2JTaiJiC0uPIv9fUr7S36oyO8aGgoaFgBgsNFHqSk5DLr7Gxqq+bqJqnp5KWlI+Sj52hqae3xO+c5aGR0oHv3drE5Mzf3ej/i7VfbFvZ5uWMgPqYoabUlo52r9JOmXPhcnBtVmaAhYB9eXyLiYF/0puK4IRESUdCQ4WCikE0Mjk1PNGaJyQo3j5ERXBjdJCHt11dT3R1gl5vb2xpqbL5wG5qaGeymv6myMPBwsKY0aTM8+3+5+LS34Gz9fnhpfjKh/Xy48few+vw7LPGuYXNsI6DE6aeq6+jsrXGsqehn6GlqKWjoIiA0pSsyFlbXF5th5BLjnppZGVnaWp5fH58fX6BhoiIiIeGiY+Uk0lIjGFyf4SBnXBKQz1DP1MwUlRLVpaTk5SVk46NlpmXmJxXYWNiXpdlWFJRVWhZnnCVd6WdcqVxTnlmW1lWU1FMUFFRUk9cYGVhWFpOS42NS0pKgZKSlk2ETpd3k5FxioqLi3STSkuChEtLS4eUS0xQlVpfYF5SYGZ5aqN9Xl9ub2uLUnxxfq1afnRwYlqhk4F6fZR/bXBVTEtNSEY7P0hymqqnpJqOp1NbWldVVFKhn56gZ2dkXVZELjgtMS8ZHC8sOlyWxJ+kj4V6RkdHR0ZGR0aFR4lGgkWGRICIh4eGh4aHh4iIREREQ0B2eT5AQ0p3eUpKSWZk6rq4qI55x6impqOgoJ2ZlZOQkJCcuL28uri2tbW0sq+urKyrqainp6mpqqqqrK6vr66ura2rqKWkop6enJqXkZCQjo6OjI2Ni4iDhISDgX99fHt6enl3dnV0c3JzcnJydHR2dyF3d3h5enp6fX+AgX96eoKJmrS5dJCJRkFEQUBAPz06Oj6FQQZCQkNDQ0SGRXVHR0VJR0ZJSEVFSGhKpGRiXGnIoqp5urxqSB4gDxAVMjQkGh47VVRkqJR2anWTmZiflFdQT11SVVRVT01RTVFZV1VPUjIyNDJTVzcwLVlSRltJSUR9pG1wc3SZRGVaVzo8OXd3hGtxfoB7fHx+hYJ9hKJ1bmuEaIBmj2VucGKBmmZ3ulZZeLxfXGZbUUdKp5ObnKaoqayopKyxrquss6yKgoqQi4yDkafRjYaIenBqx2xsasrCz83LssHBuMOtxMTCwKXBYWRjr2JjZLS/ZstlsbvHx8e2ZWZlxLlly2fGZGxuc3J+g3SqakRlaE9JeUplamNgXVxbW4Bd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4+Xn29fh+omRmZyh9aKYgIaQpPew3afppuPkp/Ch0baioaifloiN7qmSjJylp56Li4eF8IOGh4flh4yO7oiEeoGB0PXz9dvl+oGC0faerPXzi4yJ8oyXoJ+ZsLvsvo7iktnWwtDw/dfFwYuC9pDx487Cs6idk5eepqe3pZ2hoZmVgIupoNzv6ebXutv7jYuIhYP+9/Xz+K6sqaOYjKK70oOv0dP25e7QzVp25puSe0VFRENDQkJCQUFBhkCFPwk+PT08PTw8OjqEOYBzcnJxNzY2NTEwXTAxMjQ5VVk6OjtRVc/Dys/X2eDSsZqBzrqtoJmUkpKSncLIycjEwL+9uLSuqqWdmpWRj46KioyJhoD38Onn39TPwbavqKWpqqywsa6uq6qltLjCvrC8wMaxvcTExqvCv72spZSC8+L/iIiB5ePa3+7s2dLV4YDn4eDg6+nrv76Q9paKiqeuh3NtODk3NzY2NDM0ODo6Ojs8PT4+Om96QUBBQ0NHRUNKR0VJSUVGSnJXbnVygpScZ23qhIf5yoKfppycs5G7oIuC0qLK8cSfnrjZ3dfi1cmy0+fZxdfaxsLTxdzt697D5YWMm43o7o2Qh/vWpuKyq4CRheeRkZOTwoXn2MmMjYLt4+fHxNrm4d/az+Pm7Prdo5+YlJGPjY3JjKKsmv+x74zhu76AiLSxzceoiYvmu73CyMvK1N3c2dbh8ePOvvmig/qA/4mMjNGBhoaIg4SEhYeKhoKHhoeEgoTMgIKDgoGDh4qMjpCIiIqKiYqJiImFwoCDhP6Bg4L8+4GA+e7t9Pj27OeWxJa6jIHHz52N8IqdpJmWl5SRjpCTmJ2dnaGhjKepq62ysbO8u7zBx8zP0N3o5eHVyLutmYmgi4uXrODb3eXo4ent+vPqqruukdCDj/vIj66npo6Hhf+A+/P42Y6Zn6Ggm5eUkqycoqnBstuFk4CIs7LSz8u2tIfC1pbWipGC2deer9/ZrLa47IqJ56GiodbdeN7T0tDR0tPCtLGuqbGerJmkpZeXko+RkZ6mqayywvetgLOmgJb87OLD5drr5+iCjrJYYaDIyp+Foaa0wtiWgndZzE6JSfnnHSQfIica6BwQICIlLjSdrasqJiIhIW4OHfcpJSMaGg+fulFxKCVKhG12hG9iboyGt1dvUmt8elxrbWppq6/3wdfHrKWnn/qCpMXCwcG8kMaWxK6UnpihyYG47/Xmoe/Bge7y6tfr0O/17sHy0IvbwZWJop+rrKWztMKn8UxieZvE0dLIuYCNw/ySc21qbnaFjZaQgHp/fXduZ2VoamttbnR3dnd5enx8f4KAo6GDTWB1eXeRZ0pGPUVDVjMrLlBXiIqIi4yJiIaJjIeFiJRPVVpdXnxYVT1CSFmSaYhplW2boHOjaIFrYF5jX1dNUJxqU05ZXl9ZT05LSodKS0xMhFBVWJNRTHBKSXeOjI1+h5NLS3mOZW+VkFNUUpFUWV9fXGt0mYFjnmeWjH6GlpJ3YltcPG1msZ2SiHp2cWlna29vcFZNTk1LRz5BS2mToaCekoygolpZV1ZVqKOhnqFtbGljXUowOS8yLhopMTQ7aa+Hj7mWh3tGhUeHSIdHhUaHRQdERESIiYmJhESAQ0F5Pj9CRUp2eElJSWZk0bW5usTEybyfiHC1pqGZko+OjIuUsbe2s7Curayoo5qVj4yHg4B8eXh2dnNwbdDJwbqyrKWclI2Jh4qMkZKSjpCMioiPkJWSh46NjoOHjIuHdoaDgXVxamK8ssRlZF++2NjO8Mqnpaetqq+vrbSysIuAonbMfXFxi5J2dG9AQkJAQD48PD9CQkFBQUJCQkE7bnxEQ0REQ0dGRUpHRUhIRUVHaUtaWlhabbaOq8Ziua1KHx4OEBMsLyUWGjVOSVakh2xoepWdmqeXV0xVXFhSU1ROTVFNU1lYVExWLzE1MFNUMDAuWVJHXU1OR0O4cW5vcJOARGteXEJBOXV5ind2fHx+fnyCiYF9g590b2xpaWppaJJmcG5lnnWtbKtbXT9hZF9qXFNJT7CZmZ2lpqOqsLSzsLO5sKOb44hs0mvXb3F0qWhnZWpoaGZnam1raWlpaGdmZpdlZ2hoaWloaGhqbGpsa2poaWhoaGSYZWXKZGJixcIsYGC+vLi5t7SusXKUb45iPl9hSUVtRWNrZGFeXV1dYGFgYWFkZ2hebW5vbnCEcYB0d3l7fX2EiIJ9dnJtZ1xUSjg4Qk5zcnF0fXZ6e4iHg2xwa12LWmOqg1plXF1RTEqQRoyKjX1jcXd3enl0c3NlRUVHUUhUMDMtOTZAPj86Oi89QjJMMDMuUVNBSVVSSE1Nkl1alWxubJWzcrGTkY+PjItcSkpMRElDST5HRkBBPYA8P0BKT05HSlBgPSs4NC05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rdv6jYqIhYH99/b2/LW0rqebkKnE2fq43dSD84rI65eg8aCQe0VFRENDQ4RCAUGIQAw/Pz8+Pj09PDw7OzuEOoA5Ojo5ODg3NjMwLzAyMzQ4Vlk6OztRVK6Di5Gdpr7M2uPs5cGggcipmYqFftDz5uLZy728t7Ksp6GXkI2Kgu7g4aDKubG0pqOesLm1m5qgn56orbS7wcbRvKvO6ouUlYuJnaOhg6aopYydpaWT6NKroJG2xMGsmJGGgH+W2tHnwR2Rk6ipoJOMk+Si/4WChJKQ7m1nNjk4ODg2NTU1OIQ7gDw8O2+JiZBhPj9CP0RDQElGQ0lJRkhKc1hpm36Bh6ldcqCBq5Cs6J+rm5iyirmkjP3WnsLpvaKfvN3j4PXTy7jt9t7Q3d7KwtLH6Pnh1r/Z/YCS/+HshIuB8d6o6Lq7nJP/kI2Njb2C79nGmoPa5uyB283l+u/47uXz8ens5J6ZgJeYl5WWkc2Wm6WjpaS02sjKx4+GrsHOwbmGj+/O1+Hk4+Pp9fz6gJKar9e01LqklZWWlb/C2tLQxsO/uLCyu5i7pqCZlJGKh+3fhYaHhImLkJKgkpWMjo6Mi42Mh9j8iYbvh46Mj5WIqbCzusO9tt2BhIyDl5fkt8WXiu6FnaqbgJyXlJOTlpqbnpyanKGLpquvsLG1trq6vb/Gz87S2+zv6N3Qw7Gci6CKjpWl3OHa4+rb5ur+gPLr6ciUyOyB9s6c2YWDnI+HgoGA+vj1mpPz/Y2VnKKhi6etrsq84YiVgrqxzMbDsrSGxNOS1omShebGt7DJ27G3veiDhuefpZ62gK+wtcTN0NPV18O0samstJ6imqOhlpmOjZGNnLK/3u3ykMeZ+r23krqeoqm2sb2mjMOWioxWjueahtDZzIOTyIJ9dq+wmt6JpMaMq5OCyeXm1G9vdHF2b76ekW53b3I/PdZ/T0pKJiVmrraqjjhXnY6jaIJsYll6h7dccFRmfHReTW1raWenrfzXwLq7wrOwn9/9qMrCsZjuwc6krqOhjYGq+azn8eug47uB6Pv01uPR+ISM9KaGnezNp46kpLCyq7Wv/vOEMylDaPH7/v/5gJuahnJckV5MmnmWX1CQbU6BX3ybW2JnZVxVXmFXWmFlaGpsbW1wcXU/pW90cYlhR0U+RkNZNSwvTlJ8fHl3dnV0dXNufHh5f4aOk0xNdVdXPUNFTZlgYWeAXYhlipNnh1Fya2JdYF1bVllZWVNXW1pUS1FQT4xQUVGSVKWkWJRQcE5NgUxMTH9MTU1LjE5RUZNUVldPV15kZWuAjF+XfGFeXqWcaWOMgXVhY1w5X6eRe2lnZ16usV1pdI+AVlBQTEpIfD1HZpGcm52NgZygWVlYVVOnpKKhpXd1cGpiTDE6M2QvGjUZOx93w8+92JyJe0aFR4ZIhEcFRkdISEeHRoZFg0SFRYBEQ0E+P0BDRkp1d0lJSWVisXl/hZCYrrnGzNHLq4tvr5eNhH11tMu+tKmflJCJhIB+fHVvamditKur+da1m5Z5dXJ7gX1vb3RvbXd+hoiJjJCGfpKiX2RjWlpnaWZVZ2loV2NoaWKflYJ4bYGNiHqBrqmT/ZCrosCUf4GGgnVtZhV3xYrYcm1te3fTaWY+Q0JBQD89PUCEQoBBQkI+b3BrcWFAQEJARUVCSUZDSEhFRUdqS1RiaGxkj4GkZGXjq0k/HA0REicsJRYXYU9FT6KEbWiBm6Kgs5RXT19eVlRUU09NUE1VWlRSSVBaLjNcUVQuMCxXVEVdTVNLSOpzbGpqj0JsXF1NRXN7dkB1bX+FeIR9goaFgoGlcYBxb2tpamxpkmdvdHd1eYSdjGhhSGVpZ29hWURQs6Onq7Czur3Bx8lodHmKpZCfc15aVlRWdX2loJ+amJKPj42RgIyCfnlzcW5swrhtbG1qbm5vcX54eXJxcHFtbm5trMpsaLppbnBzdWuDiYuPlZOLpWBjZV1uZXRaXUhBbUZja4BkYl9eXl1gY2JkZGZoaVxtb3JycXN0dHV4eXp+f3+CiomDenVxaV5VSTs9Qklsc3J4eXF6fohHg4aNe2CGolqoimaBTUtaT0xJSEePj4pkY6SvYmlxeXxLR0pKU0tSLzQsOzdAPT44OS4/QjBOLzIvVUtGR05SSk1Pk1tZlWltaYB/e31+g4aKi4uPX0pKS0NFQkxARkVAREJCRENSZHeMiItNZUt8ZmVNZl1hZ25yeWlViXVqa0FmglRKc3dpV2R0ZGdgjG+BuXh3kHOJc2WmsqGjWVljaG5cgWxpaHZte0hEn2pYXFo2PG6OkYl/P12SaYRabFtUS1ZiikpfRFNjXE5KWVlUU2d8tJN2Z2xtY2FWe45gc3FmVoNqdVVjXFxWS2CHaHh4c15yVzRgaGNWW1Be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dlBSm9bj7fT/hYqPl6CqrbGtmouG35Xll87S2Oj7hMythIaD/oKGhIOEh4iWS6WMg4HW8Pb5t/f+4+Lu94L594WSnqegpLnTyu749+L5kPuJ9ZSUNZPlu9eB06iF9t2IoZ2F3XNftjrckpmJ9Ojq6tPLx9GReKLN5enq++vTaGY1Ojk4OTc2NTU5hDuAPDxynJiWnJ1lQEI+QkJBSEZER0pHSElzWI+YjZGOp7lwlpPUj/jHnq6elKb9rZiH7MqOoNK7pKTG4uXd+tfJzLmi4tLe376/ycnT6drbvdWEh4vv4vWDiYLq16npv8Ggkpq2iYqKvP3r3NCjgLrq/qG/wun89IHl4ezv/f/yoZSAiISKlJaY0Zafpaino7HEq9PUk5bGz9a8uZCU88ru9PX9gIiQl5iCuMfjg4SfsNO8oY/yhojSr7bZ4piUh/rn4arOqqSWjYeDg8iBhdbBzYeMibGri4aFioeIiITu7oaHi4qQk5WJ3srb8vr/mK6mnKi+m7qhm9Suu5uJ44GfqJ+An5iXk5SXm56bmZ2jpoqlqaqmoJyTnLfBxcrN0tPa6+3t5dXDsJ+HmYiXoq3g4+Tq8erq6oH+84OI046r0u7Zt5fnqcnYm42HhoSB/v+Xo5uPjY6JiNXjrbm70L3YhJaGsbPQyMGtnf2/y43ZgI6D5a/Rxr7Ysrm854GH6JyhlpGAk4+Kh4SA+fPr3by9yc7b1d7b1tro8u7x8dmsu77B2umgx4TAq7mqkIn80PTq4vL0hpK3foSDjYiqrrSHiPS8q2l7rrCU71fBmZ6SiPex0d3S0MTUzb2yofLcqK2rr7Ctoay5uq+znvyC79iWamismV5VfGtSU3mEslxzm3V4bq5NbGrCtYC6/5P80bbFv6aaibfP1JictL3NqrnY1tfU04Lxrujw7KPowoHriPrc89D2jp2n0auu/duui6Wmtq+wu7WEvaWQ+NqvkdeMm/CAo62pT1aTRlSHdoxXj2dDNENGVn+GmWyUYIGmYGlxd4OLlZ1SV1ldZ22SZ2xrgGFHQD5FRV04LC5PQ1tdX19gYl9bXFxaVJRibWxrbHI3U1VAQkFGjVxdXV5fcph0m2xycHd5hIeAgHx5dXh1dXBwb21nampoZGVlZ2hqiYZtZGNwYGBcXl5fW2FiYV1kZmhoZ25wc3B4gYmSVF94q35fmHxhbmRg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qJ+mzMWytc70iY6MiZvtlZzLzcjo/ojn9omM4YCNh8eY1sCt1oT5y6mihI05n97SgIDVnqmVk5qZjJF5nrbKz9HYzcFlYTU7Ojk4ODc1Njo8PDs7PD3An6SstrKie0U/Q0RDSElHSEpJSUh0WaDGnaGgoMhtl4eJgp6pl6qelp/urJ6M7MqOmsy8mqDi+fjwg9m50ayvzdPd37vBwsjG9N/Vv9OCgoTf2YSCg/Do1qTpgL3Vspe01YiGhLHt7NHGq/j6i4mHq4f7hYSSs7b37IKA/qKcl5GSjoyV0o2jraekprfKse7snp/L3OvGxoiOz5ypt8mC+POAhJKgloai8OuVs8q1q7OPhZfut5K2o4jrzbikk4fn0NHIsaWhnqKmopqjo6SioaGmpaShnqGnpaOfgKenpa22v7/R/ouTnqytxM/52N7e1vLKm4vBq6iagt2FnqaemZSVlJaWlYqE8+LRxKqjkoLn5+nM0rvFyc/Q0NXZ8+/w49PEr52Jl4eVn6DV4d3e2eDe5Pz79ZWUzoOcwNvMspvjrvufv5KLh4eEg4TKq5qTlI2DgOHsrcXM1b3hgImXhbO4zcW+pJHywb+K0v6LguO1zdO52b29vOeAhN6XpJTpqYiCiIOCg4Wxjru7urioq7GrmZ6Xn5+mn5mqs7O70uqkw4K1jKmrmY3614Ht6fD2hI6sh4eE0sfbh4nyiKGhhY18pq+XcIynx4CTkIW1y9nX28LCybyupZO/hqqwcKGKq5uhqamfj42TkJ2EVnBnfYddUXFlm6bwha1bcp+AbW2To5STmqDumZOC4PzlqezGpqbRqpOc1cnKp6zUkJav1LT6sOv08annyIftjYTqgeCHn7nA2MDGieu7jKeou7W1vLeF3Z2bjZCXkIOGhYmAVFlVUZKen0iOdopShWM9a0JqcUNEamZkUnVWUEWAbWdlUVxlamR6imGHZWppfGBHQj5IRzA4LCxJX3Fio3lcTHt/iY1LS19kh5JKTFNqTlM/QEFFY41LUFRUVFNbdGdkUW17gH97d3RxbWtoaGVkYmFfXl5dXmFlbnuTWF+lhnAIZF1ZWFVUVFWEVGlVV1ZXWlxdYGZscX6QV3OabYSghGVeW1aWZ3xuXWZmPluLqsCk3N5ygXaNUlZaW1JOS0hEQzs8R1+er6uhinKboVlXV1dUpaKipVSDhIB6clkz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nKi5vL+/ubVkYTQ8Ozo5OTg3Nzs8PDw7O3iurLzD0v/wc0hBRUdHTUqESYBISHRXfJCpjqyz2WqnoYqRiPCMppyanOasxarxzY2YybKVmeuA/PqA0Ky7tZzC493Zv8TWzrjq38650Pn1/tnbgveE6ePPpfPJ5MOiuKOKhYKt7uzLzK0mCBgnFBw6seHJhrbEtbKDgPCcnpyTl5eTl9GXpaWkq6y+2siTlLKw4oD79srBkpu01+bc3uqBlaGqus/d846fmZu9zbatwJ+OmebJnqCN8Na+qZiOhOvdzrqwqKKnp6mppqOlqa+wq6yuqaqrqqimp6urq6ersbm9w+CDkZaeqrvN3+vz8eP1vqCNtqqbjIPkhpunoJrPtaWM4sXHxMTL0NPWzcO6t6GY/oDRu8PM1dDW2uLz8Ovi2cixnomslaCfndfb293Z3Nzi9YCCo5jE6I6vybuwm+uzjcyIqo+LiYeIhv64oZqbj4SC6vG0yNDbv9+JlvyrscO5tZyJ6620h9n+i4Ldw9Xku9e3ubTg/oDYk6Oe/afDgYXq2I3+wOO0raOmlZ6qp5mbi4CcmZqRlKmxt7vd+63qhqaFmamkjf3b/+zq8u6Di6OEiOugmqnglvWImfmH6JiduJ9ui5+oZ0CHeq1hbGpkiJljWlFNjm5dR3RabqeTmKGhl5qXjVFepYtxZ3+IW6BqZ5mo84amYHCZ083IhLDziY+bqoOUgq2ng9fR4pagsO/b5Dju+6XOxMu3q6am8IOi8Ib9qOPNk/eVl5Corb/T3ZSbl9OkisuRsa+5ubbAv/Gypq+0ppHvmpDkqIByV2xdeIuSWY52ilKFYj5qbJF1f0B3WVaIjFdVRoBrZWWHXW1tR2GHpnxrcW5+XEU8P0lJMjcqKkdONj1dSD5ERUlHSVRKh1Fsd3x8iGhNUzxBPkWGWGJrbl1YV1ZWWU5ufYSGio2HiktMT1FWaWFST0yWkpGRkJJMU1nPhIbng3WLjIqLi4mLjI2Nj5F+XZSTkZOTiJOUl5lkT1dfaZxfVJuTfIucs3VzYGloOlPGxqa+q7W/k8R1llNYU05NREZAQjo8R2VicW5kmmenn1hYV1ano6OkplWHiIZ+d1o0Ozw3IBgvHikul+69v562jHxHSEhHR0mJSAFHhUYHRUREQ0NERIdFAUSFRYBEREJCdDpXQkZHbm+HhoVc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fYt+hH05fHx8gn98e3x9gnx8fHt7fYCBgXx8fH18e3t8fHx9fHx8fXx8fH19fXx7e3p6enx8e3p6enl6enh7i3wEe3t8e4l8iXsMfX57e3x8e317ent7AgIEAICt0bO50L3Yv4XXhKiAroKViaTK3eOCkt/e8KeCo/al+q+u25+a98SpvK+su73PtKelvL/K+PienI+Niejg9vfR0piX/oOWlpfN+4KFktSOpISG3ufUjYyKi42NifmNlaiEqaWwyOqHpM6LtvOXrri0jdDC07ytoZqamJOPjY26pYCBgYOA/oKGk7b2kau2mK6di9Cy+omAnKeyqYeSvdX5s7PIrYmI2r3H0MqgmSoKutyNW6WyZUPq1+bynK6upq6kpIWMmriJoKepm+an64CAgYD69/X2+oJ6eXh2btmBjKntqr6LhpTnnub17OWdoYxOT09QUFFRUlJTVFRTU1NUgYBpXVNSeJurNQIECAMdnvteXXp5d3V1Ozw8PDo5Od3dPT1yU1tubm9MTXZ4XJrvlMGO/aqugIWIkJucmJeyz7T8rqCfk7Czsvis4IiavOWLjJP+oJ+s4rmVppqTlqbZ2ljdlOrj903fkA8HMYia+cmE5Pucp7bunJmeoerOzOD36ELZt532iKYwDB1Y0ZOl4e745+XTupuTgMvUwnuYioqNkXPYfGdmOEFAQD8+Pj48P0FAQIuLlZTom+jIwKmwpvRLSEiGR4CMi3FWm4jygIaI8+buypf2+qytl6O118SsqKSX2IiGgvbbyIKanJKYh4mTk5SUlJaan5OF8e/w2+PJztjV77PD4/r57OvTspWU2prN24qojYK4/s2+ysc5CQsNCw0MDhGdkIiu94ry96murqmin6enpoZze4J/kE6tof31jPqVgoDknOLSqbCJzp9LS0yIfF9hXGn9jsb+z4ntup+N3IPH5FNin4Oo97E4JigrJVIqKiorKkQhJyclH8PRupq0vLmqk++4leTFXR4jJiqR8JWULCopOiQlJCMyw5KzgalAObCC++Hcx8q27pSiqqWdxwMHBQMCBgMHCBEdJB8aMy8gHCn81NLa2+jp6Of/+/315tS+qpGxqKaptJ3a4/rt/P6HmZeCRiUAFi0dBoYAgEyhuYf57+DRx83ihKSippOV9Im8ytvm08nj/NmH9KWSnPbfzJiG5avlg4DWx+mvwefI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t56B05OUlvyS8IPphb3yho+KhaudsbayrL2ly8zI7nIJERIQDYb17P6JnqO9h6atr8fvgp+OnoSXzYaFiYiGhor3i5C61oKj5b6gxLqAv7CUts3b3LrdyN3OvrOrop2alpKPk5R1i5Cbp7K/y9jk9YKHh4HKgNqQgvq7lMaQoIDz646fpsevtoKURbfEzNTPtYRUIoC22DEZDwsT3MTV04usrZ+pnqSQm6CIjbLLybbMrOLz9fTz7e7v8vWCi4mJhn7tjo+n/6q07ZehxpLO/LPan6GNTk9QUVFShFSEVYBXlOzR2+eNlzM3GhgzMCIZIZWdmnd8fHt6ez4/Pjw+QEBAP0BzVF5vcHNRnnqD1bnQtLOBkt2ah42Rl5+joKrA2NT1282uwumB7JH8hZqftczOzt/bhIv0lMiGoZCOiKOLy9uhq7+S6tmA0LCVs5+dsY/0kK6gjpmN2bLQ8rqjjICEqo3O5omxs8uFjYyr88WXvOHqyL+5uZyil4iLtIGmlZCRlW/ud3RzOkRDQ0NCQkI/QEJCQorjz7jrlKmjnoGAjN1QS0pKSEhIR4+PjnJWn4by6eGCkcbauo/a1q6fmIiKibe2s6CZ0YiKipT2aKeqpaHv5JXSuaWnh5Wptcaz0oC1xsTA7N/f1c3a/eC72PrcteOAi4XUlamuh+riou7DuLa9neGplZWOq+jal4jhg8yB1+eXsre4tq+vsbCdiY9LTXmxtZfr777w1Pv60tO+orSKzaS2w87V1naAiGZVwdDcV52rtNvU0YTW54Oqr5Ofm1ArLzAzLnUyMjEwMFwmKoAqKyo4mIrE1dvb0s3CsJz8lTUlKCkrUHliNTY3MV0qKSopJzGjg7u0ytyBod7V3Ly4qu2susG1mJDduqSpqaelm7umpW1ramZ/2dy4vMLKz87X3df9goP27NvGtJS7qpucrdnI8MvJ2+y0/KONoIykq7Dwu+qb5rWr1vavvImB/ID7/fTO/Y2bpeKpla/9p7bgx+KCytPvgo+H3tawgufPmbzE1Na/trKryLmgxpajkunp1IWSgJDgommTR0Y9fHOztai5tLikqqK1taicrp6Toomksdbf6IL86oTqmuWIjYnjhu/n+vyB+42lsp+EipqeioX/kZiioajZ2djs2s2C+4CquL7Q0oCK8OrIlZHmrqefnIa35tjDsLq0urat+erS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s7HDzby5qL7n1daB+fig8oelpbGxw8XW24eY5JaUkLKs59CKmO3ahZarWD5RLTVGs3Rsl5HzhrGoq6nn9tnDzqfXn6S6z7WA+cb/j5/Kx4CModTaprDIx7Oxsq+blpeUkbOHsaugoqVyunF6bXZGRXaitkJFQ0JEQD+Gs/aH7sujn4yHiuaDTEpKSklJSJGRkY9yVrGlofODwJbDt4bArJfr46D1/9mOqKiai9iUn4SD92eek4+T2daYwLCqnpCHnKKzr8KtwdCAvOXI1b++0OvVp77zxbLb5vngs4Z+t9PAv5DipISGjqKwpp6qlZyioZ+em6muqqOmibm7t7Grs7azt5uIiOPFvcK9sgQkQxw9jJX4vo63jNilrbXCyrqm3LuIhcWghYSGh7+igceI39a1to6ho42fZj9pZF6kOkFMQzpVZnI9RECAK4jSl6Smpqiqp6WZ+Jcq1LfFi8rlv2+3MzdmR0lMTUVeqKvRl9KPnprew9C1sYvfnp+foaOqrK2tsbe+vLCp7u7p8L+UdoTYkai6wsjPz8/WhoWE+/DiyrSYuqqjpbS4i/SKvZWjgdTu1KPphJert7/Cyc7Gwr2xpZySioWFhoeAiY6TmJmZ1qyJ/qmxyra/6qC42Nb5z+SwkuS0tJOrrLC5s6qsl7mvoq+al5Ty8eCFj4zgub7E7YChk73EjsS+rarW5/PWv+HUy9C2pbDX7Pnu8O/y3vL83Y3ihIuS9Yv58/T5/4iVpKeS6eaCjYeN2e2vpaqKi5jPv/+tgIOa1O2ArYX928/DwsHOuLG3v8/dhcKK84WsyYSMzorGsO79i5jE5eiOgP/oyM+EioaNuc7PnP6JzM+lt6O7zs7TiY+J/vjOo9WKmIeBp5ORlqf7/LWjpayQ0Z+pq/aun4buhYHrjvORi/mKgoH59u3JsrKzqbyvqry3h8aluqb+ypGw2ueAoZCHgpZ/iiedlWaUaIg+XWtFg3p+BgABAQMCCAUJCm+3ZaW1rmNtjHNraGZidk5AQUI+PDw0PUA9a4w6YDUyTcOMgo+RnH5UaXx/gEg1P2E7TTQ8eFVRTktJSEiLT1dphFqTc6vKmMmVX1l2iUpOT4ZURExJRkRCQD89PDw8S1d4dGVDQkRHTFNXXWJkY3CEem9fU3FRSFZKTIA4K0djXmJhXpCAh6lNVWd+c0BCYp+oa1paN2dp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WYRYE1d5fZKbhni3xcSxSZKquIxwinyEgoCDhENDQ0JDRkZFRERzVF5xdHVSn3+Li9OolYrEvuuTqqSorbS5tcfPt66duunq2v74/6aBiquksbbDwtDUhZX5fKHFxbLw2Y3NiPerpYfEpp+nrZytypPK48aR6rr7vtPvhI2WmJuWkZCP05jCk6yv5KKXlqzv4qe51M2wtq+vqYCcloaLsoi2s6WmqpCpiWprckZGREJERUVEQ0VFRIaarc++uZaUhYWD1pRMS0tKSUmSkpGRj3JX4YLN++ePn8CMkb6eieP72f7/4vqio5qK25ab+oqAZZ2fnJbb05XAs7WhmIidobGvvqu/z7/jyci8uMbq16e+87+py+Ht2a+IfYCzwbm5jNqehIaToKSXlqaXmp+YlJaWmaCgmqWEtLeysa6xtLS8k9H546yrgIPmHBskLkiLmZrRi9+KuXuBh5Odp4vA3rnXt7vivOjNirj39ofUy52O5o2S+9qUqZKLj4hXX19Ph3iQoKRUVF6AyJGgoaGjpqimnPmZaHuK5qmk7oDBms7LzbJlYmRmZ4iVuq36vYycnN/CzLWr9OOZko6WlZeanZ2ho7Cso5yDjoehhahnjcWIn7zBw8zP0NaFiIX78ubOsJq2oqWxs7mL+/vNuZ+P3O/npeeGlqWxub/JysbEwLSmnZONioiIiYuPk5abl4rO+Iatuce4uOGhsNLQ8oDY4K2U36y1gqeqqqiupKiUsaWopJqZkfTu3oeUt4CxpK67x//0kdvhtbjU2trI8Nvm3tfM1cGos9Xx9ubi7vXl8PPljeeFiZPxlYTz+YGBhZarrJPh7YeQiZPU04KIgfiFk9ipi5SWhKKKr7XSpaWFxuPGzMBtud/zxYC3xtPL32uvofjNicyau7WTm6Pm44CAiueww4qrl4mBh4Lst6iclJKTiIbVj+2OjYDv7MmZ2qSY2YGbpI2lj4WD84aGqpHGmqen7KGdhfCGgeKK+5CM7Ibx/vb/98iztq+msrGUtKexxsXKnruguN+hy4CujnOFdpe6gWiKYoFddVxvdqWEe1Q0IwwPIyQfJCJsf3Cso4KJjF2genZ7e3KFTz9EQj09PDY8Qj5t+UJgaWJWvIh/j5KUjVVqfX9/Rzc/Ym9JNj5+VlRQTEpJSYxQV2Z7U4p6j1hui5yHXW59iEhKgVNCSkhGQ0BA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5+2An56gn52Umbfh+JKS/YK5zpmteOCIgayWVVVVVldYWIhZgFihy5mYinRzn6/GuaqbgllwYoOGhoeGhodERUVDRUhISEZGdFVecXN1UqCAj5GC1Zf25rfIqLGprrO7wL/Q1LKolrXq5N7r7fKggIKgnay4wMHMyfif/oaezt2/hfegndm5i9/tpaSG77mLicqLxcHLjYmKmqjP29/h4ePm2dfYgOHgrPyFtcTvj6PQoPHRsLvQzLaxs6+lnpeFiLOJureqq6+jnapranBGRkZFRkZGRURFRURE9ZeivZiViYCC7sSZTExLSklJk5KRkpBzV/nCgoCFkKe0iaGnlHbmy/mCgu7roaOYht2bov6Yj69+lXWKxduaw7e8ppGHop+trr+pgLzFxejM28G1xOjVtrf0vKfJ3+TTrob3q7ays4rYrI+Lm6iln4+XjJWWlpOMio6SmZuYgLe6tq+srrCCnvnP14iEsKOh8y0ZH0cs2KOHt9+R/JnQgoWfppjVjXa7cq7I4rXGq6akqKbs0sSP4Lvth+q9WDdbV1WVQUNDQFB+WWZrgD45oO26hZidn56foKKX8JaSMTs4YJyGgl+NOHKbPT4/QjfM6aeSza2PnJnWytW+qITalJCHiIqOjY+QlZmen5OHlIabmOl7yYzLgZq+v8XM1NHPhoaC9/PjzrWZqpmeqrC3m4q2w7eczP2X+Z7mgJOfqba8xcjCwruspJyVjouHgIaHjZGUlZaVlMaMj7S9zcC13qOw2c/x49asltqvtYGfq62nsaeoka2dpqyukpL88uCJm9+xkLnRhZzrs9yP7MC/3dfZy+3f5PONhJGUhb3V4eHh0vz/9fbu3oznjJCR/JOA8YCA/oiVrrOagqnX17Wpz8Li8fCBgJLxt9HH/4GkgM7bo9u4x5SGj4yBmsi5jOyp65+L7t73w8/0yYq2tfTC6o6lvLGkgYXnsM6W/ZykgYX927uglJ6in5iGorOHxd7pooTnqaOOgrqMxPO3vZ/S4pOMu4aQv5umpuuknoXwhYDjjvqQjO+A7ff9/f3GtLW1r7Szm9Wmw7KuxvqxpbmXArijgJ6BdIFrqJNdb4xjfllloqyroZ/kcVAoBjU8PTEnWmVecIFybJiubWx5cG5sZnlSQkdCPz07NkVBPGOdT0BQU1C2tq1s0G50VGl5fX1FPH5hQE4/QX5Z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1tjbqZisj+G1+5ybp5+nra6uscSeoJaboKWZnaDGlrS3waWTstLq7u3s6+bl5e6BoKKiop+YirXv9oyRgoHKknykdL3V+KyVVVVVVldYhFmAW1paWllYVomumZV6ZLz/kY3xxlJfin2HiYiIiIeIREZFREZJSEhHR3VVYHFzdFOhgpOYmoTSk9ilorG7sra8wsfG19muoI6y7uTeg4H/p4uOpKq2wMrB0cyDmYycooeV8aSRvY/syKnE55akifrol/G3jMLykKq7vsTP3Onp4t0v29zY2dzk98Cqwevtl67Bm7uK3tDJ0MWvrqyroKGdi4m1jb28sLC1rKWweHRyR0aFR4BGREZFRUP09KCk+Y/1g4DfqpmXTExLSkqUlJOSkXRXx9L4l+SFq5fxiquAZeWihIKB7+Gio5SA4amviqCTpoGirrG84pzEu7ygjomgn7SsvK64wL/o0d7Kusjh1LG39bykxtzfzamHj6Stq66H2KeFkZyemJDyjOb0iYyPh4OBhICRioXsqa+uq6yvr8L7w6uUjaC2obPYYCtcZFzWy7WFkfXUhqXU6ZWZncNkbLPhoLnbr72Wk5Wbq+rU1vexmdD4vY/IVVNUU4c+PD49S39TYWAzc8XRpfKMjpCWlpmYj+OWz2AvZl+emVRZhjZvjTs7OWDvprmg76WYipOR69nj34CMks2LhvuAgoOEiYmMjpKVjIaPwdrjyJLnj8f8krW+wczKyMiFhYX68eXIrpuynaCrsrqfr4z54sPks6zpour+jJafrre4vr+9sKSinpaPi4mEhomPlJOTlZOghYiwt8S2rdaestHL6NTdrovas7WEnKWjn6ymo5KyoK2vppOR+IDr1oyb0b6lxpac5c6ZnYLWwbzs+YKEsPGducSNrp/Ux9rb6uvO/YL69OjpheeJkZSElYD8hIiEjZKys5Lu+Y6Lh43bwuLu8Y39kInOgNiqhpuS98yQoZfkxMXTtrTX0dnJiIvIxq2il7GOzL6PzoDSna7Gxax32o78xs60t4GbplOBg/rbs5aW4sinh+6T2on9gYbw18mljoyvtKjnkpyY19K3mc7unZDBlKWn66mcgfOJhu2OgpuW+PuCkqWn/fKHhubIgsaq4KKoi57GmuWm+qDQt4CwbaB5fI6OYHaRZn9bblejxpaqs45KLgYTYFQhNU5iUnFpa3dijoe/gWtjY2BuUUJGRUA9OTZHPjhapamYq7Gtq6+zqGRpfF11dHJyYUlcXFthWW+LWVhYVFNUU09aYmh3W6dQoqd+bqN0TWFteYGHdlQ/RUVDQUE/Pj09PD9UVHk+QEBDRUpPVVZZW1tWWWhiWHpNUDVPMU12jTQ1GmZfoLaiZmmIUVloa3l2RSNWwHmhZ15tam50dXd5eTw9Nzk8Pzo8PXd6lJefh2mOiZmbnZyamZmcoFhxcG9ubkUcJlBXDRYTCCBqoJuhsIKglIVMTU5PUFBRUVJShFSAUlJPbmZJS0o/c3s7N2tvNTxddYOFhoeHh4tHSElKTlBOTUxLd1lhcHFzUpp2gYOCXXNGaGqHlp2ZnKGtt7W+voh6bIejmKRfXL6MZ3GFe4COnp6srHCIfqmuh5H9mX+ef9XMpanVfYJn46d2poVulqNhbnN0dnuBgn97eHZ2dnmAfYSOfJCWsbpwen90mnKtq6CzqpiOiYV7e3pqap2CuLesrKubk55sbnpNTk5OT09QT0xKSEdFv10jNH9XnVlZpIuUkklKSUhIkI6MiodpTHVocV+0VpW3eGfQrYqyFwgIDSskFhkQJEdCR15nX09DcFJAcpxGT0dKQzw4QkJIQkaAQURJR09LUUpCSVNLQkZbSkJPWV9dUUTCjYWAgGN0VEFHSk1MSIpRk6RZXV9cXmBiaGpwunt+fX1/gYKPtoh3ZGh/gXB9nHtLkJONzZR6U1+6s3id2PyxqZzkfJGr7Iy+vJ+MTlFZXGeGZnTVgG2TlbB/4ot5cXnCgHx+f3GoiqmAq2Kbq5VuqGBjZGRlZmZhmk++iWG4fnzOdpRrXKKmb3d1pe2WrVmncWlncG2KeXx5XmWBXVqpVFZXVlhYXF1hY2FsstHg5tC8uWyJomJ1e3yCgoOBUFBRmJKIfG5jX0tHT1RcU1tQlpB6gVhScFuKlVJaXmRqa25ubmdgXltVUlCAUE1PUFNWV1ZWU1tdQUlMUU1HUzxDS0ZPSk1AOFFBRTM8QEA/REFBPEVARUZCO1Ouo5JfZ3t1aZd0e8KUc3tfZ1ZedoNFRmufan+MbHZqfldmZ25wcG41Y2djZTVZNTY4MzszZTM2NDg7XFtMam4+PDpFblRkaGlEcT5PnWCOaFmAaVuNZ0lLSnxjYnJbWWVfZmVFR1xcXVFJVE9ycleQbqt/jqKkkWGjRJt2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+fb5g/OHjvfel4SUnaTsgOSwjLXE0eXoyqnUtca+wMa/uLvCtKytpHeXmqGmq7K+z9HT2tzSnKygirv2qYvHusDQuZWqvqezmIyXsa/1js7j3uSjnbDApK+Yi62PqOSYipmShpqhmp2fpJGZoNCmxsfRs7DG1Ovs7/Dt6ufo8IKhoqSkpJr4svnwiI2E/NPOZpFyxWNurJhVVlhZWFhZWoZbgFpaWZ51nJCQeMSnn5+anrtycYWLjIuKi4qLRkdHRUhLSklIR3VUX3F0dlWjhZqdoaOU54Spt8HIvsDEzNHQ3t+smoe8+O3qio+OtZOaqqygsMW+0M2PjZ2svMPQpNqqyJnsusH0kMbuj5G9wvSlndaw4YKHkZijrLW2tbK0tLSwKrK1t4Coh6WPjauWl9ik6uTJzsCvsrOinaGgkpK0j8PEubi6saq2gn92SYZIgEZFRkZFRH/Vy+b/+e+BgIH2mZdMTEtLlpWVlJOTdFWx5c5s3IibkuvqqG7Dn9GB/P/51aGgj/fYtK6Nqpepg5aflKntoMG5tqSRiqOnvLOsrr3FwObEyMCqwdzOqLnyvJ7C1dnKooP1q6amqILLlYCOjYyQiNPIrpyWjP6rh4bygPv7hOKnpqinqKuwpqOQ6qrhkaSWpqTy8u/z+/nZh8Kg18LUs5qn69ikyKnFsOSSpZrW7KDS1N2C4sTAxPTO/+zI5+jd+Pjb4Ht5goG/0fHpt7XPrJLa9+6B+fKEgvT/geTDPkNCgqW9lOS7clN1k27Xx9vWx+L0v7aGlIjw/reNgPuUkvvs2+bk6e7u5uj2gIeHsuF3enl5obOErtuErLzAy8rJx4GEg/ns3civmKyUl6Wru7i8tbOvn4uG5Lih5vyGkZunrbK3ubOupKCdlpGMi4eGiY6QkpGSkOH8iLCsw7Kt15So0cbdxtKphtC0uIWVoaKcqaSpla6Zo6mfjYv+gOfQiJeriozWsa+SlZaBlofD0/iEiouUkpakrPypo9bIz9rn6tb/gYal7YCJ9IqRk/iZgfuFhviRj7S6mOTch//5kuC63uPmr6269aOagqeAnKuYmZSFkKDEwaHZ+pvIucKSmsbD+pqW5NLrvZCK9b6eo6yqoaaPccy3pJSIubG6U4mG8diylpa7wbiOuK2QivL/heza1qWRiq/L9Jv92fmzmoaQm6WwrMyXpaDfmJuM6qKflaOatbOXg5CYqKKd3d+WpsDshv2G84qJeICObdLOzJe+gJdoj4JlXYFrdZVpgV1rUKN9gZiEclcuCSQ0QxkUJ3NcgrWDkJBZra6MbF5fXGxUQ0FFQT47NEY9OGBmW52aqKyxsa2qZHddVW1xdW9Xb09KRk1AbnxQT06Qj45IhU1RmZRnW1pwdJpTonNUbHR4iIdxU2hqWlhdWlZXVVVRUGFbWEFBQkRITVFUVVhaWFRVYFtRbYlXPWWIenFYS3NpUFpdXmNsapNGYHBpdHY+JYiFd2VTLVpsjWdYZVhAPT45Ojw+ODo8fIaioq+SgqKJmJqcm5iZmZyiWXKEcYBGOSZUVg4VFRMjYo6YuJlOWJWHTU5PUFFRUlNTU1RVVFRTUlKPVVpESEd8amtoaWttQWJ9hoiJiIqKjUhJSkxPUU9OTUt3WmFvcHJRmXiHiouIcqBjjJmfpqOmq7XAv8bEhXdoiaylrmJiZItmcYSFgYydm6SkcI2Nr8G9wZ24hoCghMmnr8F7uLRve4KOr3V5qnWRU1ldY2ltbXBubGxtbmxvcnNVjGd5bmyDfX22isK4n62knJOOgXd+fW1snIS9vLKxsaGbpXZ1fU5OT09QUFFRTUtJSEZwgWRzlJ+eWVteyZWUSkpJSZGRj46MiGpLampnUphQhMR31Mqo3mMUB4ATGisoFxcNTEVISGhrZHZhbXJrfqZGTktKRD05Q0VKRkJBRklHT0dIRT5IUUpBR1hKPk9XW1pOQuuLf319Ym1NQkdGR0E/bnpsbmVZoXROVLm0s2CmfX17fH2Ag3x2Z6R/pnJ5aHN4tru2tru5oVlvXJeWsZWCn/PPjbeswIyyc4CBfqmsZYuHiVWHcHigtaLJtKC8vrTb5MzNi4ePkqiz6eOvl6aKbpyxrV+1ql1drrVbotRja2x6eJxqyJSJeH3HiMGsubKgrrGJhmNtYZW7hGW3a22uqJqcnqSioKKprlleYJL4h5SbmauWaX+XW256en1+gH5OT06WkYl+bmJWSIBCSFBhXF1dW1JNR0FkWlyGk09VWmFkaWtraGNeXVxVU1FQTk5RVFVXVFNSf64/S0dPS0dUOkBOR05JS0A1UENGNTo+QD5CQEM7RD5CREM7UrCijV9ma1helI+DdXRwZXZwfYWbVFheZmVob3W7dG+BV2Blbm1wbTY4TmY3N2A3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hJyYjtf03ePj+4OLjPr8lsOTk7vJx7Kj5YSEkJ2Rm8OE/v38gISC8oeM0tX4iIbFjNCcl5HhkoScr7SNvorkkIqJkZuPkJiduNm8d7/H08/d5PWGg4SPj4afqJXbgOKQjoaB6+DPtbSvoY/+84XEzILK2u/v4qGVqdCd1+KJ3qGPiNqmoLvknqGYmZ2hkZug1rfW4fLSz9bX6evs7ejp6O7zg6SmpqiooIavhOyIjoP72o9rjnfRxNqsmVdYWFlZWlpbiFyAW1aUwpmDhnvMubfB0nRrio6RkI+OjYyOSElJR0tOTUtJSXRVYHBzdlSiiKKnqqyssLi5xc/Wy87S2N3g6OWql4TB+vLohID/n4qepKyaqruxxcaFnZ6p5fSh57Wcs42RhrfSgKqG2vfc9obriYav0O2qr7bF2uPn6OXi3uTi29yA3qWZzoWLysjdvpHGkNrDyMW2ra6Zj6ifiomzkmRlwsDBubS3dHN0SUhIR0hJSUdGR0dGRorV4//52fH4/v6LmJdMTUxMTJeWlJSTdVayv3n9+uaOjOLIrmLX3uuCgPmCzqKcie7NnaOZxb7Imayxqrj9oMW8sp6QiqilwMWssb+AwrzkybfAqsLbyaaw77KivczNwZz92aOjo6SBzJWEkYWCkYPD0Kb4foPF6ZSG2Nvf/NuopKOmpKasp/qass6uhvvI2bX4lIuB3Knl37eUqr6BkJDryqO8n7/Ek42U8NaJh87Cz9nA5Mu2ov6WmbKTkqCLjpSXlZiolJ6lkJ7P6KOAnKaTobeqsK+5r7CynbOwpKxeZaOS1OHj17iUwqXviv3tkIH3lpmbmvjV6Zj9trSUrv/jnaL/7eb1xJ2alZSj0tvsgciU1I7/96LSo8DGyMfKzIOBgPvw4M60lqmQkqOnnuPn1MWqkvWAjJ+c3fiGkJigpKmzrqqppJ6blpGLiYeAhoiOjZCSkZGG6Iy1p8W2rOCQqM3F283Ho4PJqKyIkp2foqOko5Krm6OnkpWJhvbYi5iW2PnTjJOWk/7276/19oiXmpqhp7C+xoqpntyZgvqKo5Wro5qVgoeXi5ubmYeii4WSjYCbob3JotbUg+/6j+m61efet4CMyYzw4tqEm6CApvHz0s7MkZbZnM3Tpaio19SdoqT5hdqK1sWOkILCw5eqqJ2dnJiVkYp/8OLU4YiK9c+jj5i5i+2C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o82BzoG86reWgoaFlrmro7Cvn5X4mpyPuoeF7Of3jJSRgoeOnZSNtr68sZ/kgoiSm4uTv4D5+fyAhYDshYu3iYG9iZLq2aH0jIf5w5KorYu9huuMh4aPko2Lko+Hs4l1iZCQmKCxsrS0urq07IeJ79jx6uS7uqjpi/ngyLmlmPne2rf5scXd7vTYmoaazY6PnY+So/qrie6u0KqYn5Gdn5uTnZ/WxfL+g+id7tfs7ezt6ebj6e6Cpaeoq6ujobWT64iQgPnim7BxecXcbrGaWFhZWltbhFyAXV1dXl5cXFqgkdqXg4KB6+nki2+LjpOVk5KSkZGUS0tLSU1QTkxLSnRTYnN0daWijKuwtbq7wMbM1+Ts3t/j53V5eu2mlIG8gvbyiYX6qImYpKmnqbGpwcCAp6x9oamur+6LsKLq3c6RrMaFg83c9Oe3nL+BlO2lrrfE0tni6OuA5ODj6N+vlvWIgIjwyYGC9KHji/XAwsu8sqegkKCZjpO5lmdpZ2XKw8DBeXh3SkhISUtLS0lISUhGRkXj0d/K2eH69/OUm5pNTk1NTZqYl5aVdlf5g+Hr6+WFksmin1iYieyD+/6F3aGYhe7GlKWvgoj0rcHIwcmOncG3rZuThrCAqLu+tLa9wbnhz6mourjdzKaw/bSlt8O+vZz61Z2enqH8u4uCioeFg++rxqaSzPCC49CX6trR4dmrqqmmp6uup+Op88edhoXSvKKqjuSntqOwz7mSi43GtuqKlpyMjJOS04Cnx9GGi87BwL+vmc6/k++Kmpvcr4qBmu2Q443NgtaAiY+UmKqI3u+Pt8nqu5ulh6WVlc2ghKmsevfSnoiz3Z2Plp/nkN3h/IiFgoyOgeqD3bS+o82NiKOE8Pbyx8OVpmhtgXiJkMb8pPaY7srdzb+Q0L3ExMLGy4GFgfjq3MuzmK+Tkpqooubo29DAtYuEo7Gd2fWEjZiaoKStq6amo56AmJeRiomIhoeKi46NjY2I7Yi9rr27rduUqMbK18jCpIrKqqqSjZWepaigqJiyoZ+gjZ6XjYHhlJ+lo7nwluiXvIWXw6je4fqJkpCDioWNjNHy9Lygq6Cyr46cp7vCsKnPhI2G+fnz8PbVu7r77L7GotTXgO/2jfrRkpKQkYOOtvmA3dDM542CjvH+htCLgID16oWbmYPUqavs6M7JtrqxwLuGk4/2vMTp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fHx8e4Z8CHt8e3x8e3t8kXsIfHt6eXl6eXoCAgQAgLaE3aftyNqppduew4qNgfbV+YuuyrO6hISUlpCanavA1oSpx93k18G9moeIg5m7qLGrsqSe8MeW9rOh+NOgqK2nvrqlcsT97pinqayg7pCPkZqKiLaC/fv4+f767YmMnE6k67/Z9+TVxoKTx5TyoaSGv4b1ioWCipGJhomEhcOMdYWOkJKktbC1rLa7t+WDgdrd+cbVnJnurOHDtqqdlJKRkpHXvN+eoqOgiLLJgM6K846doqWiraauqpXDmZ2PnqOfoa6g5d+Lk5f/oozY6+vo6efl4uvwhKirrbGxp7K7pOyOlf+C67iOdUTJ3XGznVlaW1tcXYVehF8UXl1cWZ6Xg8Sdg3qj04iQkpacmpiEl4BNTU5OTVBTUU5NS3VUY3J0daOikbrAxMnN1dzm8oCC+/v4+36DgvmkkPnCiPTxjYuFrJafrq64za2yyMWBp7m3saikj5mdw7iZmunygNzGu7rkkZGw5IWhi7e7xs7d7veAhYH7gYOApY+T98iDyYS0nJT5qd/als7Q0MPDqKSVmyqfjYS6mGxvb2xs0s/MfXp3S0pLS0xMTEpISUhISEaCwsDI2/H89/yVm5yFToCbmZeWlHdXpH10Z9aBf5fOrHpj0Jzt//eAheejkoPtwZW3xs3pq8nd4+LopJ7Fvb2jl4O3rsG1q7W/v7fl466YwbXX1KOy9rClt8HDu5yE3pqam5v0vZiWloaIifLOv77qgIOKkLGP08rV4dapqqejpaeooOCdwqqShoDOvLSqkYDolaOaoci7hYuWmZvl/Iyano2B7NzW2d37pcjwlqCdoevU4Pbt5+LngIKKpIHi8vyCg4OHoaCKm994x/L9g/n4/LejkPr82tjt4/Pc0eOym8/n9IeQkZK9yM2hmazo3t3Z1pRcZJyfwbKV7ZvDnKGu9qiivpm1kKGHq5P96drdjoDyqPugyPW1vcPCxc+AgoT66NvHsZiolZqjrbKEgvzv1byOhbPJmdXvg42Vlp2fp6mjpZ6bl5OQi4mFhIWFh4iIh4iIvoq6q7yxptiTo8LEz8LBpIzMr7mIhJGjqqiep560pqGCl5GVnJbxmKXBnNSI7ozGgJueu6HLyN/1gfbh9oD7mp/lya6si+3g5v/dgvDl7t3kxYORhvGIioWI+YOE/JCzxqPKzPnt7ozpv/GCiYuCk6vhgdi634vlhZDzjIay39Tn5IzEjqndwcepu6qzu6yaubqL3piGhPiW3+Db/ISGhoP37+LWyImA2oSBmafFmIq1oLz2iIGDg/by7bOamzvZqOWig5zLqJvAqPDQ96TWoKSZ2KihjviLivWN+4uH3piZ5+jy+Ne6v7CqrJW6pqi4hfDEuoqW2e+JnYCRXp14sZuoZnaabIFcWz6+wu9liI9aPDs7RkpUWmx3hpdbdIyQlJ+IiWtdX15rU0ZHRkdBQWVkUn5fbrOTf315e4B6d5n6tY5ZYWNjWHFUTUxPPmhvTZaVk5GUkolNT2ZvxKNVc8ukpqVXbalqnGBeTFE60D86OkFEPTxERUN6Snk6P0BCSVVUU09PUFN1SUmCno1mZ0hHgIZ1aGBcV1JQVFZWhn+naV5cX1d8cUGLYaxja29wbXVudXVjdTw+OT0+PDxAPYqqcHZ5yndwi5mYmpqYl5ecnll1dHV3eE0uKS9RFA0/DzA6V6OAw8NfnYtQUFJTU1RVVVVWhFeAVlZVU1CNg2aJXUpHZp51hYiNk5KTk5OUlkxOT1BRVlhWVFFPdldib3BxoJeDpKirsLS3vsPLam3V2t3fc3Vz24BuvoJboaVfYWSJYWd/f4mfj4+bnmmKoZ2kqaSOfXiUmnR4wbpjs5OQrqN0c4GiXXBcd3l7f4eTmE1QTplPT0+AZlpdtMRojmSIeXDEhryxcqOlqqOci4pyfXhodZuLZ2pqaGfBurZwbntQUlJTVFVWVFBPTUtJR3uHhYqdr7m/zY6Wl0xMTEtLlZKRj4xtTWBNVWK7fMDsebGhhF0uHhEVDRs5GQ0QWUA5Rox2imiQoKenrW1HTUhMQT43R0JLR0KARUZGQ05QPjhGQk5MQERXRUNKUFNUSD2fcXJzcbFdREJDNjk0blybnshrb3omQEyKkpSGlHp7fX1/gYF6ppBYW2tyY6GTiZFsnXdubHBxZElOd8/Jz+p+eVRVRoaCiJClxXmVvXV6bmZ3YHLr6+jn9YGDeJav/PT4goOEfXyTbY6Az5K09e99/v75pnxt5uz09Ox6gX+Rl2VLZnaDSExQUZnDzOnAlOHe3uDjsnN0a3p7eXWUW0JBRVePd3idiZ9kb3OCccvArZh0q3KzdK6ndHd7enl8Tk9OlI6GfmxiWUBARktUPT9zbltL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enuGfAd6fHx9fHt7hHwEenp6e4R9BH5+fXyFfoR/hH4FfHx8e3uEegZ7fnx8fHuEfIZ7An1+i30VfHx7fHx8e3x8fHt8fHt8e3x8e3x8insBfIl7BXl5eXt7AgIEAICVjM7WhLTmq6HXnMSKi8/Xm9nrxLbWxuLcyrK+n5Wl4qa1387l+v/tv56NkI2fvKijp6ymmPjPuNyLoK6mnp2SoJ+WlqJxopyoqKWj9aDzoOO7oZe6hYL29Pj6++qHirt7zZ6Av4mCl9qg8MGKzo2fg7iB/IuEhIuNhIiIioXIl4CCio6WpbOwvL+5uLHc/Pqfyt6wjNnEjJbHnITm0djVwLKVlIuT7+317cCAk4CW3e75mdu4irTT/5Kbtp2klJ+mn8GwpvHtmqKhjq6S2uno5+ro5+Tr8oOtr7O4uKy4xrTmk52HkPrds55H29Z3X6BZW1xcXl9fYGFhYWBhYWBfX4BeXVWgn5yTjY2WmZqbm6KloqGfn56eUFFRUlFUV1VST052VGJyc3Wlo5zN0tvl6fH8g4iRlY+QjoqKkI2DpIvwyob08I+Wg5yRlqax5bmsrr2/+5jOftLB6+apn67mwcGw84Da4bzdlq28nKyAvdXd5fn/g4WIhoPw7+Pdu9/hyYChx5GQgvPt9PGCjZPtx8zJubR9i4mUmYmFtZxwdHh0c+jkz3VvdUxLTExNTk9MSktKSUpJjNXG0ueF9fOHm5+eT09OT0+dm5mXlXhXmXBhxNt5j4i8lWCBhrPm9vmCheejkYDzvZTH48fcpuL+hYSGvaDDvb+jloS3p8O1o7W6u4Cz4OK/scSw0sucq+ymn7u7v7eZgP6VlpaV67edm573g/7gur2C9/HGvdS5kMzCx9HKpamqpaGoq6LVhNfGjn733LjAv47El56WmMW4+/qEgKn5g4+apIDz9ODItoXZp7Ou4eDi3tO8krPw3eX1/X/qUkd88vJ8e3yJn4iZq+9tuYDu7XuChIe2i7j/4+j1gPPx3szIuJjZ7/yGio+PuszLd+mV4ubl83p2lZPJmJWsxOqF1Y3jpIfK0tW1xYW5s+bl5Ofu94GIjpSjwo2muMDAv8SBgYHx59rEsJuglJemqa6HqZiB0rOPpMPXnNTn+YaPk5qdoqWkop2ak5GQjYqIiICHhIWFhoODhO6bt6PBsLHkk5zCw9TByZ+Er6K3hIaPpZWroaOYs7Cfo5yUm7WzhZmk0v7K2byuqrH7z8yC1tXr9oWI1dygkMuPg4Tjydzq4fnH+/Tu6uX//viJhur7ioKH9YaF+YuywqTQ0//u+JDks8zpgoT5kKyFg/Ci4IDNmYDw2oWKzp2Mj/GB+pOCkMeAg4aCu5Kp+aO0kpPn09aQnradvt3x9/Ts493LvruPg9+IgaO+gryko5vl1IyAg4n8gu64pKixgOWXoLS4k5OW9JiY2KTeqKSW5KmoloCJi4GW+JSB2cCM4YDw6s69v56XsKPFvsaXuGeZ0O2/5uvBhIB3dp6bY5PdXHeca4FcXGqend6zrpyNIyIqLys0IimCrGd7m5Cer7DFkHFkaGZxUEJDRkNAPWNtcYZXbGtmaGZjZ2doZoh8gWhqamZolmqean5nTnp0Tk2Uk5GRkIlLTnXO5nNXgGdRdaFtpoZloVRbS1A45EA5O0BBOz9KTUZ5T3k7P0FDSFFPUlVTVld3j45lmo5hRmplUoBrUUeCfXd0YVdXU0VYfHCAg19RNjpqqY+cbKaEYX2SsGRqcD9AOj5APkY+PY60eYKBbYFzjpmZmpqXmJiboFt3d3h6fU8wLC5QFwwkFjtDc6OC97liUI1RUlNVVVZWVldXhViAV1dWVU6Rjol9d3qCioyOkJaamJmYmZqcT1FTVFVZXFlVU1B0WGFubm+el4m1ur3Dyc3Yb3R6fnt9fXx9gX1yf2q4h16vqV1gXoFlZXZ/uKmMi5Sa0ISqacm91uaGdo25i4+htmGxqpK4bZeQa3RVe4mOkZ6fUVNUUlCQlIyMdo6AkYR7u35pYrS+yslpb4y6naajlZV3g3N8eWhtnI5qcHNwbtbMu2toelFSVFVWV1hWUlBOTEtJirSUk6djtr56lJiaTU1MTEuWlJKQjW5NfltSnaGB0etqlJh2LywgExgNHjoXCxBfPDpLoHyJa6m8YWFkfUdMR0xEPzhGQEtGQEWARkdETVBHQUlBTkw/QlNDQUpMUVNJQLZub29uqltBPkNlNm9tYJhszsSroBo+THN+dXKUd3p9e36Cg3ufdWJYXWq4oJSYo3CIgGVoaWxhkJpqc53ueoF9WFWXiHRzZU17Ul5UbHFzenJbb63k2tzu94LuhnOB8PKCgYSIlH9ykcuAo7/394J9foGwfqb/7Pj9eIeJi3x5cUdjg4hPUVBMjcjO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h/jn6XqKiOfKjYmAhPaAkpG+h8Pey+f2/fH+hYXi/rmLefmDi4KGgfjv+Pj3gISQlIXfrujin6HlhIF/jY5/ebqSkZCU9rnt2d3IzcHAjdTh5Lyy7unj4ej6hY+AlqKsv/uTmbG6ur3BgoSB8eHTxbCUrZOXlp3Ly4HEsp2HytDw7J7Q3/GEiYuVoJudnp2bmJWSkI2JiYeGg4KEgvn7+vTSvKy5s5esg4GcpbOyqbOVm6iTgfmMpK2pqaeat7SPpoaantjlm6Ohx4a9gviy0cHtonSzcenOuqmKvK6AqbqH2oaK4c7j6uSO75X4+Ift3ZCZ/IDe+4SDh/6KiIGLqq+l39yHh/2P2LLG4feU9Yin3saChL4+WENn76SCuOqXv4qjgKXniqygyYH404azhoughuvTqtPQg5ea7K+KgICGqLXCnaGOkf6b97D3lZmu1biQ0pbP49ecju6GhvQ76c+tlev8mfLC8uqpuYOc16CkmuqwrZOIjIf4k/yF96Op+tmH8/DNwbu1tbr00srYraxDPly7gf6JzsSA1Il7pGV7gWp1mGdyll9WX5CXe3pybXwpKCUnR0i7dIlZWIZrgadssJSEiI2Fi1FGQ0VFQTljbkpDaHxXW4yKj4JQmY+RjXhfcl1VVWRwTFVfbW1rdZiXV2JwipekYnSBio2Sl52hlpCgraJ3VXpnXItYc4ZAO3Jva2pwe0p3f155VFE+QEFFSExMS0pHbYN3Z5RbYXBlTkmWRHNqZGFcVVBOkEtQU4p1f4N0UTVbcYGBvKNffaqvc4xpYnJDQT44QkNDl5BdfpObn4SpmpGYm52ZlpWXmqFbeXt/goZWMjEvTx0LIBk6HWetwpzCalSUVVZXWFlZWltbXIZdB1xcXFtYUlCEoAOioqSGVYBWVldZW11fYGRmY11YU3NWYW1tbZiWn3V5fYKFi5OYnqerq6ymlpehnIF7ZrF7urm2xLyzZbKusKqos6ess65tjoWl1XSVc5Cy2ZumnYuGpMaBbH5oUnqampulWllaVlhXWVhbXF5fYWBeiV+egbuhc3pwdnlxbGOlmICjoY6UiiyMcHVvb3ablnWEi4eEhoLEX150VFZXWFpcXVtXVFJQTkxJhW+hprxmf42anIZOgE2XlZOQb055alOWpLb375CQhUwsJykVGRElShUQFzU+RC5dgXu0Ymxwc3h+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i4yM3q+Vi5XlgO/Ngpjx6eC5pNOp9Km2s8Hgt9an2evRwamUkOa/nrKm9PfogLDGq573/raK0MO+v77F/bCb6eu9jY/0ztrv6t7T4fDk5IDSs/jSl4HxgISH38m1mY5+/P2KnpnRydnO1f77g/b/goXQobaNioKBhoOIgvXs9fPkgIGUkIfsut3WpKz2hpKTs4GN/42J5faH5aeHiYDdmIHRqLywrLGek+ns6uTxgIeMlaGszIKMl6W5uLe6hoWC7ubYyLKXqJGfkJS9roemgfO+yc2tgpfL2uyBhoyRmJiYmpmYlpKPioyKiYaGgoGB/vj29vHPtpGEg4+MtJOfq66sqbWUmKOQ//mLoqOnqKWYtrWMlICcnr3BmaWouqOtsuO0i3dk6LiBhd/c6Pr2gOqE6u3ex4eC183k7fWQ5pL4+pjV5+W7/IDe8YiBifqLhfyGoqmj95PHyqOazrDJ3PuY+4yhya3uhLI5VEBc0MLfse6Y1KylztuO3cevjuLk6LGcg4yP/sWaltqslNTb4JGRjIKE9MXErI6Ij4DirJ/rjfvfopP+zoaPkIuA/+SAPYaRk5SVl5uXh/Lo496Ch8Og2qKil9yqpJOKkI3+lP6C8L6ogtiC8+zEv7qHz9vYzMfOya9oPFWam5jJobCAu4WnnXp5fmtyl2WgVVtSY7m4nXS6pqmhlYbXrXeDqXlneUiRVFhWeaKbpK2hnE9IQ0ZGPTw8gFBGj3txY6GWlIuXjJHatHhOVFlTVV9GYVRPW0d2h4WKjIeEiIuFl1CWdH5dYF9eaGx+katniKSvtLu5uW2urq6XZF5qUEeEd05/RWdBQkRGR0tPTUlHb4p0Y1BJZmxoYlaqTIN4a2ViXVhRlFVaXUxBSEmEVz9icoiMhHCoirhhboVrY3xMRkQ9SkNBmYuka3t+fLeSd4WFhoeJiouMkpdjZmltc3lVNTUvKiEMHBwbO2Wxv7Fec1mYV1haW1xcXV5gYGFhYWJiYYRgA19eWIRWBFdZW1uEXIBdXV5eYWRmaGlsbWpjW1VzVmBsbG2Ylq6JjZOaoKizvcfU3dTJuaGisJbSc2WwoZSMn7i0oc+Nh86su4bE09a9criRboOLk6OKl6V/i52zrbh3l23Zklu5aW12enx9gIKFjYyNkZefnKCfn5polXRkp4BteERDQD53bYRrgIJwwIC5oX17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8/yJjY2om46Nt8SY0rSwjNHg5OiCn66/xqinl/qlore5t6y7wLjZzODmubrUxaew36eZubjApLCD+4eHiIjZp5SKj+/y4ceSlPmC07KWh8em6Z/2q+iPnJ2bmZaSkI2IgevRuo3Qt6GAm5WNovu5l//hgI3/+ZGP75vemo6LivjUy9zm4tzn8OL4gNqt0umhh/6ChIPJyd2Phnp8fIqapMzv76/ShoD39/p35MW+tI+B+YOIg4OA9fGDgOWBhY2MjenJ7uTIoux3tPOh5c6IkcXdgJSqgZKameWsmpmJqK2uoNqA0bqnkImAiY6apbPc84STn7Wws72Dhobz5drGsJmrkJyTl8zIxLmmlIbwxKHFk8TW7v2Eh42VmZmbl5eUko2JiomKh4OCg4KB9/PosY64nrKqqI67lJippqijrJWVo4+C+4uhmJ6mnpO0saeHh6CosbGcqq65waCfnerIvKeciLG4yuuBh46A3sKmwt/3lYDf2O33hIvtlZGGncPfw76XhOL5iYiNhI2GgIOfpJ7k6oOLhIzY0JCYppT6jqa5oN6Tn3BIOmKzvODZ3JTawL+M/67Ql5qy2rm2ha+NkZv966aQvq3H47mHv6SUjJOR94CC6sTXvbiUz+3YgpGjorC2/I+QiYn82So9KCoyLWFQoKCgkpWRjIyLyZ3epKec7rOsm4WUmIaS+oHs1puT1oH27sjAxL+H0sHJv4mJg5trs5ejiea094DAm36UrYB9W5iYYW+fXGdni6Sdinh9p6Ole3Z6kN2EgZZtWodPeYSCWFlmaGBYhHqGgGBaYYB4S0h3XGBjXFpdXl5erliktItdY15idEhlVElXQXyClo6PioaEgnmKh31gkpZhbW2Af46RjWVYYGZpYV1irH+boaKBtGlrbGtoYXm8sZ10bXVYUU5NTUd2To1yi3JMhnhva7hTf3VjXFVKhYJyUFRcWjlAg3RQNFhvfoOJbaSsarx6e2xkk1BOSkRNRkuqk6FmcHNysI5zhYWHhoODhYeLj3htcXd+bjxBMTYwJhkiJSNJabnxt1x6X51aXF5fYGFhY2RliWYLZWVlZGJhX2FiZGOEZIBlZmdoaWxvcXN0dnVzal9Yc1Vfa2tslZfIqLC6xdN2f4KIgXvJpIHLnHp1c2xlsK2ho6WurrS4vr/JyWb4Z4pvomyyw2Zpb3Z3bm5zanF8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4uGpgNnm2fKFh4SPqJaOiKzKnM60qIi/zNDW65e8xsGuqJyCpau8t7Ouv8DA1dTVzbS00IDEqrLhrpq7ub+rpoLxhYWEhNCZi4uV/ITdxoiE87yMofiMzIOdn6CjrbSyrqmmpaGjn5yioKDSnfrazKuZg7+w3K6cmZ6bmI2egMmxs6CYiZCC6eXV3+ba4ebV4/H7ksOs5sy8zMS6na+M9fnk9PB5g9rH9YvHifLh5dnd1sWWmICU7PHu9/r8iYb99PSA+4WAgoH+4o6A2aqqqsPN0Nue8a6YudbxsPaVuKnI47e2wpGoqqGh7oLw9fTXrf+noK7B6fiFj5WlrbS8g4KE/O7eyayWsJyhoqjCh/rv0qagjpyi1pbD2OuBhIeLk5eXmpaTkpCNiImIh4OBgYGD/OO5toDCkpq6ta6zj7mUkqOhrKaoi5Gdk4OCiqGklZydk66rsIuWp6y2r5y0tbiL/tXDoJWM+qC1u8HV+PD+gOrLqMTu94Ge6dPrhYiP4pemlZO84LezrPXciYaLkf+KhYOJn6Wh09iBj4KM0cHxhIOXp8O0s431+/2WnpDPl7Xcu5bVr3ic/+CCyv7d/Izous7Er4iNmPeKrIOlqMDCpqa6hPHSgLmExK3Zi4+Ai/6Qj5Grk4GmxeX7lJSOifHfKjMaQjJAQEtGOTpZMlZQU0ag1p+jn/62spuMkpaFlPr+8dWTp9qA9efCtra2kLqwv7H/k+HdtaeuwreyyZyAg3N0xauWg3h6kF5voG56hF6zfq6rX4FyaGHnfYGdbJaNXVGXp1pfrV5mcG9nXXVoXlpJNjVyb0tKeW5tZKahm5J1WmBwcKaqXVyglWhGaVpIVELHf46MjYuHgX55fGyHdoNYgIuhnqFcXGajWmd1h39Ub22yaGRRWGRmZWBkbn6Aj6DAiMRrbWttbW27sKdmgpBbpo5olr/LknZhpE9LhFZHS3upaV53fn92nDRfcn2OoX3AvoRdeWtdY0pSUFhhZm+EcK9YZ3BxcKeRcoWFh4eFhIWGjJB7cXR7hHY7RC88MSARGB4gK2vplM2XoayKm6OvXmNlZ2lqamtra2xtbWwIbGxtbW5tbGyFbWNub3BwcXJ0dnd5fH+BgoOBfXVkWnNVYGtqbJWWfnR5eYJ6dM6qiWykiIaFhIF+endyb7hwbGhnamtnaWxtam1zf4GEjpKXmJ+Kg4SOpKKbmZiMk6OyYWRjbcs/cHyEfnV5g46EiUuapJ+ekpKDgo6Gi4B0ZLSgm5ypuruYiH59dMm7u6qnq6mfkJ2XppXBscCfkbHAeF1ddFlbXmJmZ2hnYmBcWlZUUZSPjZCPkJeiUVGEUoBRUVCdnJmVc1B7c3G51omE1+qsXEktITobDhktHA0QGzFBSzeQeHFdiJSXmalnS0tKRkZDOUJCSUZDQkdHR01OT05FRE1IQkJRQj1KSE1KTEGqYGFhYJlIPTU/bDlmYExnwpt9i9BLl2F0dXV4f4B8fHp1c3Fzdnl8eXSZcrSajoB5almDdXZPSEZKSkxFRUB5dGllWVJORXx8eoCFeYGDd3+LjWSWk864rbuytY54Wuv35u74gIPXsNptn3DZ4+rr6NvXp2l76+jj5emyQ0OGhYtFfkhGTk2WgGdZoGdriKWfmol7qVhXa2qGWqZziIOqu5mQmnKFhYB+tWTCwLykh4DBeHJ7iaKrWl1hbXBzd09QUZSMh31xY2BMSktRaD1tX1NDODA1OVFXd4CISkxPUFRVVlhXVVRRUFBPT05MTExLSo+Db35zUFNTSkhIOkw8O0JAQkFDOj1CSjUzN0FBOz8/PUVFSTk6QmN3dWVzdHZYpId3ZmNcqmeFmlldb250O4B8WEhZg55dYmhgakZNQXlZbE9PVWlOeGVgVzUxNjdiNjY1OFRUTmFfNDk2RXhjikQ9UFJianpco8fRzoNM63N5m4l1imcvTD8wXjFkv2GUYYKBdlhZXJthe1doRk1IPkJmRn5mR043Si1+PkE5PnZJYV50Y1BvfKmcQUA9OnqmMD09IVA+S0ZMUjxFYTdSTVFDTHdFRkR/S0g5NDk8NjxnY15+S05WN2hfU01MUFB1TndVo2WkoYN9hZOSlIx3gGGIiuG3n39XeJ1FOVxAPl6HVTFGSSUyKSgz/o6SwYamSygUPkMoS41OVF9dV09wWU9DLB8ZVj8YFiciIyA4OT45LycpMDJGSywtUUopGzo6Kywj9U1WWFZUU1RUUVRGSj9CM0tNWFhRMDA0TDI4OjQyJTZCfkQnIiQsMTIzQF2Bdp+955nMb3BwampgrJWCSkhLLlNTO1dwml5HPHs9NlpGKyg8SS0nQlBXWYwrU2lzf4pnqaVpP1NJNjIfIyUtMDZBU0yjUWp0dXangHF2eH19e31+foOIjZOVmZ6OPRARIjoQEgwFBgwgdERxu83wxePt+4aPkZOFlASVlZaWhJWAlJSTk5KSk5OUk5OSk5SWl5eZnaKorrbAx8XBuK2f0aCgqqep5dB1Wl1cY19aoYp1YaCMioeEf3lybmdin0A5OTk4Ozw9Pz87PERLTVJXXWhsdF9cXGZ0fH1zallZX2c4OTlWopCVQ0dBPDxARUJDQkFKUE5NQkE5OUJDS0VCQoOAgZKZobO5gm5fWVacmJWQhnxyZ2ZnZaacvqK1p56ho4WWn9+or7Kzt7m7vbCjm5aRjYf37ufo6e3z/4CAg4SEg4KBgf/89u64gpmQgMvSKiY/TREJBgQCBAMBAQMDCwwMFWlqE3NlYE9ye3+Dj1QmJCUjIiIeIR8fHR0cHBsaGxqAGxkYFRgWGBYVFBIUExYWGRiDR0RDQ2kWFBIVJBcwMzZkp4h2d7VQYDpDQ0VHSEdGQ0A+PTw8RUxPTk1xS2tZVEc+NFRMTiwjISchIB4mIVpEPzYvJiclTEo+SFFGUVVNPUE4LkNMb2NdcHVsWlhGd398moVBSoNrUy5TMnh7fWh4bGBVTTBHhH96bGlbLSBDSE0lPyopKiRPQzFBZEtwYm5mY1pdlnJyY1pPOGM7YFh4dlpVXFFlZmJij0OChYd4XpNpW2BodnpAREZKTE5SQEFCfoB+cV5QQC8wNTg8KERBQjAyKyknPTRBSk4nJycoKSkpKikoKCcnhCaFJYBHQDVGOCUmHxcWFxUVFBQVFRYTFhUWGSgVFxgbGx8dHh0jISMhISZOaGJRXF9jMllPRj07OG03YHcVFh8lIxEvFhIaPWFRaSgiIh4mGjk8VCYmHykfTj8kJhgVFxYlExYWGj04NCwrHB8aK1VKajEqNz1DW3hHW5CcfjUr91dIcnx1amxNMzsmFDJiU7tVbE1LRHFOR0lxQVFDSx0dFg8RMTNmPyIdETYdTR0gGxoyLWtqhGlGUV5/ZiAgISFz5qTjg/3U4tTa0prm+6CvqLyvL1IkIyJQJCAZFBYXGBgqJiNDIhkYEyIbGxkXFio+EzcfXDlhZWFkbX2GlH5zg3yEew55eX1+fn18e3t9e3x7e4V8Dn1+fn5/fnt8fnt7fHx8hn2LfAR9fn5+iX0HfHx9fXx8fIZ7jHyDeoZ8BH19fXyHfQF+j32CfoZ/hH4RfXx8enx7enp8enp6eXp6eXmEeoJ7hnoBeYl7BHp6e3uJfIZ9gnyLfQF+hX+Lfgd9fH18fH1+hH+vgIZ/A35+gIaBhICLfwF+onyEfQJ7f5p9jHyLewN9fn+GgIR/j4CIf4mAhn8KfHx8e3t9fXx7fIZ9iH4GfX1+fXx8h32jfAF9hH4BfYR6DXl6eXl5ent7enp4eHyVfoZ9Anx7inqDeIV6jHmDeod5AXiGeQh6enl5eXp5epB5B3p6eXl5enmEeoR5A3h4d4V4gnmFeAR5eXt8hn2FfoJ9hXyHe4d8g32GfId6iXkEfH19fZZ+B319fXx8fX2nfAF9hnwCe3yHewF8hnsJfHx7e3t8fHx7hHwHe3t7fHx7e4R8AXuHfIJ7hHyDe4Z8C3t6eXp7f3+Af3t7hXoJeHh4enl3eXt7hXoHfH19fHt6e4d8CX18fH18fHl5e4R8A3t8fYR+BH18e3uFfAR+gYGCiIECgIGEgAZ8e3x8fHuIfAh7e3t8fHx7fIZ7B3x8e3t7fH2KfAICBACAt+jw4tPj7JqXtICY3q+i0Ku3hP280t/c3v2m0Oubkszz68GHucKf6Y+TmJuUjo6ykJqe//XZvqSos7CupKiiq7asmZ2qoIyVnIv14MGGurePna+myuPm17anqam+18WnqqvQuKW+vNHT0v6D+PTr/PPav7TRsK6lqbKqrp2doa+Au8a7tJ6P57qvsb/Q3YfN/4mK9/Hq1aiz69DtwMnVupiUqNXumKmB8/rs+YXx+uv+reaDhLvE6KW4u5bCta6ysMDYgPaKnLKqpffbrcTGycjGxMPFy9Spio+aofuyzdvZkcDJwLeZhvPaP6HUhoDCysrFvre6vcHBxWTFaG51fX+AgoKCgYGCgoODhISEhYaHiYmKioyMjI+Rk5SSjomHgWdac1JjcXJzoqKN786siNqtmZ2goaKjpaWlpqmT5Juxqs7ziZOXlpmfqqSdrL/PxLCysbCelouFjID6gfrgzMzb/YePl5+Yiy/zpavBz8vQ2tzT2NbZ2tPX5ejs8uvSxbA+m4qE+vzt8O7p7fvy2dCwkvbVu6+0raScpr+gvqGrwq2ox4lgXmtTVVlfZGVlZGRjYF5cW1pSm5iZm52oWFiEWYBYV1ZWVaqppINbg9bu1HtPQ33ZnpOa5ujV9IaK8pellI3+pNOk1LWk4K3h49zcksLKu7mvlIGqpLu5qKK8wrnM0tGop7bMxam44q6esbO4qqb76ICEg4HNo5qDg/CIgOWF06HznJySoMaQ9I+TmJqVlouIhPzlzrOU+dq+rJKC4oDBqpeEv73TtKaV0b+nk6CN+ZvanpqNjIbs59HZ3uDo9+Lv8peAy5nI6MulkJXXrYCkxt+D4NnmyNKJqaS+pPXi+5aYpojMi56dsNTugoL28dXV5+j8hIb99PHslYDy8Zfrm56j6pOdhsS2/aeWmLHGuNDJxtDvxomJ9ZqI+pGJsoDA7qCXtdbw94SKkZyvs72EgoL759fFqpSymp2eq8CC++rkvLGUmqLGlMLU4fD9houRk5GSk5GOjoqIiIaDgoGEg/+vg7eXmZ2Wtaqqto3BkZOoo6qlnoWRnZmFhI6apJOalpmxrK6Vnaestq+btbmzlpCRlPDx+OmWybDT2YP5hYCAiM+z1Ij5h7Lt2fCRho7rlqurh8D4rbXR692NjIuN+oaGhIicpKHQ1PKIgIrGrcvi55SoqZHHzYXdkWVDSXfUk5m4kcSfxPKQ6JKy2riM2MKVrp/7gIrUufSavsuH/c+lk6ymmbbFkIja4IqOhZCKi+yPs42Il5Kd+ZSRi4TT4z54SEUzQ1E3QkktJjk1TS80LKHRoKSd97avoIaQkIST/PXlw46cyurs48CwpKeVtZ7JsJzPq4Oxjv3y64m9gICItsK1r7i9aVuEV2aaeG6ecodXq32LkpCNn3ubr4F1iJ2TdlR7hGmxYWJkZmBcREs+QUBuaItsTkxudHZtb2pwfHViZ3JpW2BoXZ6Ue0Nrb01QWYWHmZmNeW9ucYKPgnB0eIdzaHR1f32BolKRj4mWk4Rvi5doZWBiamVpXmBgZ4BxenNtYFeRc3F4h5upbaXMa23Evbepi6KpjpWDiZR+UlhmiaptZz17hX2ZNleXkq6GvWlqh5C0dYuNTFxUTlBTYnp/yFx0h4SBt596h4eIiIeHh4iMkXZfY2ltqEhQPUIzJhMXIiQtXt6k9m5vbq21t7StqKysrrKzW7NfY2tydAN1dnaFd4B4eHl6e3x9fX6AgoSGh4qNkZSTkI2LhGtcclVha2tsl5d/1ruXd7qTgIKFh4iKiYiHh4l3tXqIhJSrX2VmZGZrcWxmcnp+e21xcXFnYl1cYl25X7Ogl5aiu2RpbXFvb0q2dHiMl5WSlJaRlY+Sk5KX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t765zs7nrrC5zL2brN+unrGws6Gh9+j5//38ypuQgoH3hfba14KRx4CMoLfK0bDyna+xqaagloXx3sitm//Wq4WCiY+Yk4CgprTHza+lnN/d5pKmpYeWnKunjoSA8dvU1t/i2uDd7cPgk5CQ9JqLhPrWk4uGpKXPk7iAqY7Ok6qwptmYjY/lvrPBgsODtt+ZqNb+8uXO1d7bgYWHiPaAzcztso2Cp9mHnu3Ui4GTk83ziIr+xNGKj5CYjJaXvc/ehJukvtXn/ICImbXW6/OAh5CVqrC6hoOB9evXx6yWtKCcpK3IhImA9tXHmZacwpHC0+Hu+IGJjY6RkY+NjYuIh4aFgoGA//js2uGFmZaXj6qgo62SvYuYrKOpqpODjpuhhIGJm6ybmZaaqayrwr2crrq2obm7uYuFlJzx5ufaiszSidT6+4SC4YDt2YKJ8oaTj9H3k/+L7Im2tIa+/7Ky6vXUlIqRk/qKiIaPnq+f1Nr1hYCOx6/M5OqThqGGvNXGo5d8VEp6h57h1Y7ii6PXh4mRrpyvnY+1442Cz4a0gNKtl4SqxsWwns+5qKHFwJDtgeeOlYyamJaB3JPXi4myov2ako2Hn5vbfjyMjYyRk5teVFRYR2FALjKmzpuil/axrZmIkpKDnoL637eYjcno8u3FvNnWsNPXk8uVkZGO9NjFybz1tsiAb4aio6Wmqpy2jV9tYqpYXo1ygoxTYHGTWmh5fo+aV1pcV15hXWqBgV9dW1xaWkRJOzw/aEaWYFBUjZifmoF+jpyZin2EhYqOeXN3cV5CZG5JVI16qVePgHp7kldVWmVraX1wnVFSVV1dXGFfU09TUJmQlYlVgX59gHx7enp7fIKAkVOZUl9xbGpiZ11Sl4RqmXdqbHeJlp95bnd7c2zPyqSvs2qjjXKEh42AmXpgnalXjIGKioGbd795XlFiWFFQV2Fj0mVfgJeTkWmvh42OjY6NjY6QiIqoa3WChpNCVBhOSTUsLzg8QkCPqpVzdHe5YWRmaGlrbW5ucHBvcG1mw5SAeYGCg4SEhIWHh4iIiYyOj5GSlpibnaGnqqqpopqcmnhdcVVgamtrlpt+fXZ4e31/goSFiIqPk33Bho1pgmKBmpKJc3R1e313bW1eWmBoaV9fWp2UgnyCf4J1cnNwdGtlaWpnaWppb0lrkImJjpiaoaCYj4uSn52no6Si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u7Cqur2AudC92Ja7vNC1o63ctZW2rLOZmfXo+vn39cSYiP2A4+fjyZ6qraavubW9s7q+xMjt4amuo4PaxKyhlob07OPV7fX4mpq0tbC8yrOpld3E7Z6kiPuFhsymiof38t3Cztfi19Xgk9/WzK3ppLCJiZCNjM+9j4yJmqW0kPHIjqGUiIaA49javrqtr5TO16Crt8SNgOrf2tvZ1fPtg4f3iaeqlP3ghLS6qdyarIiGpp+LkIKCi8i93/aS+e/g5oiQqo3WxN3z+4iWn7vQ5O/8gouQnaiwgID+8ufWw6iVtpiPl63Jiob65s6/94avyJK+z9zo8fmBjI2Qj42Li4mHhIWEgf6A9/TRwdqGg5yQlIuvo6eulLaOl62lrbKYhYib9IqAj6GYoZmWl6yqn72pn7XGvaPAwsPu3pmH29fgzYbcz5bv+YSGjNqdjZGL+4eUodeDlYKK44O3uYPHicOu8YvblomUmo2QioSRrr6k1crrgIyRxq3K1+SFgaaThba10IxZPXeAZJvK8d3HjNKOpsi2xsDi5pqPxKeS09nGtdzm2/eIu8e+raSQvqugx7mNr4CGlZ2Pq6uo9bzIwuG/qI2DnpySiZKM2d3ugIWHjo+Mi4qNtnyobrHOp4SWmvS6s5qLm5qRrZf71LWpnPmLi4rBs+vYtNr8q4mNusje2N7S4c/1rtiAb19nZWJkb3xXcolYZXJ+hHxjgU5VVlSKhIR/YolTXFhbaH6Ik3mab11YU1VUU0FHODo9Ypl9YFFYdXB4enNudX53a212anR8c2xYf2Y9WltzT69hmIuEiVdqdHZ1go6IXVFcW1hfYl5cXWluWllkZ11OjGB/kIx7d32HgH97kpt5UlpVWWJxbGhneXtuc32BeH1yaFiThmGxgnecrnKZsHCuanN4cmy+p251c5NGh25yb6e6sHKHcm+BiWV3bGNlaGhk2mhtka2rpYB0lJGRlJWWkomHkJhcdX6OkVEoLx0rRi8bGysyPFqHzrJvnXGZo6ifoHV4enx8fYR+gH+Avq+PkZKTlJSVl5ianJ2go6aqrrG2vMPP1MrBvrGnrK6DV29TYGtsbpmccXV3eHp8f4KIini6g4lqfWuGpltaYGBcZHijW1WVh4B4dmhfWVKcjXdgWVddXWNkaGhiXqSak5SZrLNcoGtLhmFgZmhfYV9fZGdmX19bWa2q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49zO0sOmnLS5trO4sLW3v9rYpamkoqOIu6KYkIqB7cnsh46jrr2+tbfLsKWIuqeWn6OnprGz/aeIgvbv49PY4Ofl5N+szbqrnYHdxKeilpmgk+rn7fX19vyJibCvx4Dg25mTg/fb6tzU94Xx8/OLmIbs4eDi6vPe6dT9heG5udfQ2eL4iYj/j43T05Olw7G4y8/PsZKgkJmE5ufwhuzzlrXA2ICS6Zehvs3d7oD/h5CarLH+/Pnt4NTCqpOwl5OYs8n2+/zu1c60ts/Qlb7L2OXs8oKJi4iOiIeIhYSDhICB//i5oa2g59HxlouSiaOYobCRs42Tq+fF9o7Mh42T/oKLn5+XlJSbp6itq5Wgq8a+qcLFx+XmoZDp5uTOiN7dvK3piIyesr+vmZiGl5ydgIeWgI3kgrvEgMKUz6/siN2WiZSaj5CLhpe/0LrOx+2BgZDFqsje2YWAmZaDwNzY9YDG3X+H1a2InYz1rubW7/bP4KWL+9zEzKvsm8izzuX0r63b087Ht7zMtqPJroXnvs2hqZ2yt7WW55W/ltWVioaep56SnfXff4mKh4SA+fPw74C6s5fBr9OC8pSc5LDDv4u3trm+qfXFupLjwq2qtJCyqpi+1oetjZH2iKOSkYqOhwOYuqKAhJFecohVX2Z6SFFWVk2IYVZ9T1hVVFxhaHNtY19iY19bVaGUe3uPoWFVnU9PT0BENTY7XJByaFBYYmNqaWNbWFpcV1OSdXKBgF+DgnRPXViNbbitkVZkeX2Afn58j15OU1ppZ1haXlxYZmxtZVRYWl9Xj2WNTpKSjlKXjZqUl5eAl1BXYl5gYWdtaWppcm1jZWxrX11ZlX/KlJdpWG5uZl1gYWBqcYaYuZXJbW9y1MWweJCki3V8cnB4imtBdm1qaWln52t9n7m5so58opqcn5yXlaGqr7BpgpKkp2I5PSc4LiQUEjRATDGg3tKeyKDRbGyd7oCFh4mJi4uMjI2Pj9aAwaCjpKaoqqyvsrO2ub7HzNPh6Hh47e3YyradfGVTjHBpbFBib3Bxm5hucnV4fnvSpnF8bnnFfaVkZ2VqcHZ5nGFtcW5qbHBubGdnZWFiXlqjnZeSiXxtVpmMhnd7fGllYmNtc3dlcE5bbn+DfXt2d36Dg3l6cWpoa3SDipGXkpI5j4KBdXFxcG98g32EhoqQg351X1xgXWNoaMx7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h6C4nZefv52gnImyhPrt7+TX4ODl39fcy8bLwLOrqqq4trWztJ6QjpqEjIiFhYWQnKufopV5j4aF/IT6h42Lg/6CiZb238/W5en65+fRhYKC3rPL3dXP0tvTytSZvq2Hlp+Xrri0s6eSuLOtj+i1yofhjZuw9qe8vr+Kn7fH2Ont/YSQl6mv+vn36dnPwqiRsJ2OlajBiIL8+t7T5ZnL6Zi+ydPe5uj+hIWGioeGhYSBgP739LWtmsuBxu6ShY6EnJajr53Vtd3s6IKyjOOLj6z2+4WdnY2QlZigmb2chJ2xx72vycrKgu2ekOro5cju5Ozdj6GVkpeE7sayv4mBjZiWho35j/GGw9KNxorOs9yC1JKGjpaNkIyEm97w1dbN+YaEj9TKoqynysjimoGyp8+ogKnFucaE4+aE9fK3w/mE+biQhpXa2qOe1cyZsLjS5Yurvs+RkPCrmrWM55ChnpyrtNf+3IP8w4vaicDzv52Ll8ns+Ki1qdDk+oSLlZaSjOnqgZOrg/fE3oLylJflr6+aiaOgjaGRjdqth8z24MCk3r6ficbdvsDMyq+y2OqGl6SlA4HX4oBzWGNnaW94f45RVl5fWVCNaYKMVl2Fb1VdZGReWVdcZ3B0bWNbc5lZZVSXmpZNQUNmZzlfhWttUFmAeYaTm5mRgXBmbnFkloJ4aV1nYGmHd2GeirtTkoqGiIOChFGIXWNaWlBPWF9eWVFLT1VaYF1Qj4tvqIyUj5ZRp7aapYB/eICDj5GQY6xhW1tWU1RTVFVUUFFSkYiAdc+2lU9Kimqgdm94dX+Qn1xiYmFgY3CCnMOdZ3l/fnZrun+wb00+dHBsamfnbn6iXl63inegm5aQlJabo660tW6KqF5fc05JL0E2MRoXHCZbObeBjHF1uXZ4ebH2jZOVl5iZm5ydnqGjeYDatru9wMTHzNLccnJzfHh3eejczbeWeV6ZgG5ra2ppaGdqUGBtb3CWjXFrtYm3dG18oXOHr65ia3B3gZJaanZzcnZ2d3dzbmtkXVaShIFzYKmeoJydolpgY2Nuc32PoaOWiIqNkHBrS6hXeHmEh4mAdW1grpejo5+mpaldcoeQmoCjnqCnsK6mqKixr6qsp6ypWq+ZZm1sanNye26oebZxbGxtoYZ7g19meoiNkJCVlpGKhH13c3JvbWxra2ppaGdlZGJhYF9dXFxa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xMbPzdDIyLu6rbKwraidpqKnpKeopaipgKiVlIyVkpqYlJqYmJCN4O3iyd32/9XU0/P3+NSUqLbIz9DS4NfxpdXAj5+tt9fMwr6zqa63tKC4gJSN8Y3wtv6es7XBi5ipvNDV4Oz6iJCcp/nz8uLXyrOhj6mUkJSkuJahtd7whIaXmpeTtsjQ3efr+4OBhIeFgoOBgf/+/fXwgLy1oMyB4/eOhor9opbBue+KndaohO+Hj6uflpTl7ImcopCVlJidl7WkhaK2xbSmytTUm5KslPDYyLerpvzGyrG9kJCLyqrYvKGeraatj4mQqZef0OGh0JXUssyA246FkZmTmIuFqPqW5OKXytjBqM/BgYiC6o6Yr4a2rcjQ5cK/gKXM4L3qmo2rptzu1KHI3IPv0dXm45G3o/uu0+Da0Mrb082njOW9o7qPusq5rp2lm6yxpuO5ycq7wLWQgoKIj4OHgJjE54KLiIWEhYGBiZLEtYmEk6KRrLX2wLKchq2mlLGcmP2OhMyChomOlJKC24jnl5n2m6e1xbezpJ+h0+mNgHxPVmBobHiMTFJTT09OTW9xg4RHSFpgXl5ZU1ReZWBYXGRpaFNYWFplUpWYlZ5GRWRkN16OY3RRW4FwY7m5XauXjX2AhoCHjIBuZWuLf5iMYJGOcVlseHtxYnB1VVdMT0xTU1JZWZRKklJVT01RZmVUTm6vS05QTE7BqHd8fnl5gIyNfHyRl5l7X1lWVlJRUFlbWFdVk4l4pYBmYlxjY3WIfHBma2qVXXKEgnVjeJRZZ2txe36Mo7pwr3KHkY2IesKGWKR+hqliYV6Qe5uUoaWfm5ulr7S1dZVkb22MbC4cJUM+HxodKzJCXIy3moTRg4WHxoCepKepq62wsrW4u76MgILb4XN0dXmAfHp7d+DRvqCGaVSMeHNycG5samhoZ2VlZWdPXWttYXOZiWFsZpqrcoefpbFeaHJ2iq5kbXJ8fHp2bGRiXKGQdWNYWlxWVl1hb4ajWWBgYVxcX2ZjYmBYkpSRjIx5i3h+wXZgZoaChZObl6ZdWlikl4JwXltZWqmogFyEna1iY2NlZGdoam5sZmdiYKqqlI2Hfo+eoJOxeZhybnCdcGt+YGmKmpycm6GnopuUjIN9e3l2dXNycW9ta2pnZWRjYWBfXVxahltzalpb+rfdzprIXjc1PiUUGyYSDxIYMUxX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ysGMobLLydPK0NO8sLvBr8KWjO+Dit25kKe0srmHlq7G0dje6POGjZOh8fTu3NTLtZ+OpI+MlKT4h4WDjIqIjaCcl5Kywsra5OX0gIKDgoWEgv7/+Pf19O2AubGg3unu6YTn6OSYu8mR1KOAiZCPnKWbksCjl+PagZOhipaSnZ6SoJCUm7HFsJ7K1NWlpeq6oZeVlZC/3M/pm6qqs+bas9+Gyayduq+7noqmvMHy9cCS2cPNy6Xe8/z5gObY0oGGo7uW3cnwg4mY0bnsgICBipH47u3j3cygnueAgqS0q63T35PyjaqbhobbjoHb1enozM219fLj38eq+Oju47W91rmlsojktNm2oaKSqLKu5rnc+ILTyaSNhYiShImFstnl6e30gIWHhoiHg/z27Kz90aG2vva8s52FrK2duqKhjub61YWIjZKYmpycgJW8+4L+8d3a2tbRx8aE5qSAjmVlXlpdand9goR1cmxqZ1ddc41MU1FOTFJcZ2pmWVZhaXJzn29gYGKagoaHlklFXmNqYp1belFboJiapJ2Qb616b3qJfoaPi4hsXFZZZm+IeVpYkJ2ek4uJiE5VZWxyaGljZGh+iYSAhY9TUY5KkHxqoWWGUFZVdmlYXFlZWlSAWltRU1Faf2teYmpwa2ZnZW9tWVdSWZWsXXBTlJ1YmmGKg3pyamJodXBoomW0p2xejGaIqGN0fYWBe3+HnrdyqHGIj5egm493p6Weq6eqq6usqK6zXF56pIaJfllJPikzMiwYEyQ1PShenNrElHaUmZveibS5vcHGy9DW3eLm56mAk3+FhX56dNzHqottWpV+enp6eHZ0cm9ubGlmZWRkZGVkV4Fopb2ch3WCuMt5fYSRlouPnKlyn2Ntb21pZ2ZappV2qJeVk5uprV1zh5ShW19gY2ZiYGJfXGBlY2RmWJ2clYiGeXmJhHeziXGdoaamqaebVVtdXmlsbGlmqJJvamiAZWFhXl97q2Zqampsc3JnZmRiY5dhXJSQk6y0sZ7ggHFsbptxbHddaay5rqimsLi3sKykmJCJhYF+enl3dXNyb21saWdlYmFfXlyLXYVhTln02PbNmWBdNzs7EhYbIw4QFBw2VS1AKVBoXJNoiZGQi2lRSVFZUkg7REZNR0dFSkyASUxAP0lGTEpCPkFJQztERkQ9PnWdq6qmo4c9al1bTU9PTE9D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BH18fHyFfQ97e3t8fHx7e3t8fH18fH2Hfoh9gn6Ff4N+hn2HfoV8AX2GfAN/fn2HfAd+fn19eHh7h3yGfod/hH4Ge3t8fHx7i3wDe3x+iX8EfX5+f4p+Anx+AgIEAID7yMWzsLe2vMrPz7+4u6ub94bC7oGNk5KbpqmioJyis8TGxrnVgqyXoOe5tsDpnaji8erSgaXqp7CC6OrGjc3Ap6OvusW9v6iRj4iLirLDn6KBpJbl29+j7/j6+OfSz9erq66/saSdoJ6hnJqFhYeamcKX+oLxipmVhev3g4yKjYCVi5idkI+bprGtp6KjqK2ro6Ceh4WDlpvhs6azxbyul43ivq6SgYeWlvbAppaWg4ONsdTe4Nuu76bWnsDK29zGuKWmyPeU4Iue5OHZwp+ApYD5+PyChb6DxfCIgIKZv/Pk5fDtsYnztZTZTmi5lbe6vYeR3+bvgYH8/oGBgYD7sICC7tm3lHdiooyGiIeHhoOBgH17eXZzcG1ra2lrYFF+qNSupaS96oiKtPOCgoeOlqrO8J/O9ffx7vH39Myb3cDD09zd4OiJrsjl/4OCg4OBgICFkZCKiImIjJWPgtzUzc7b2N/HwpaOenqNz9nY397h5f2Ml5qYnJyYmpqVmJaEwYCVko2A6emDlceHnZyXk5CRk5SXlqj78+TNyePu1reIlcxwqmlmboBRbaGwopugw8fM49mH47OhmpaVk4+Kh4OAeXd1dHFwbWlnZZ9woKGiqXwuTJnIv4GC5PubpauZ3tTNyr/8lO3l9fnZ3Z/I09LQmMa+1vnMrYihstO6u7vLz4DB2LCjwcXLxbOesMaxnbW2qJCR9Nfk4uLhu5aA5OnV2966qaujobW6u8CtuMXMwMfNoKWmoJ+dv6ycmaewl42klZy5tLCsrb7EuqurqKesr7Pc+IaHnpfm2rfB1ODu6OX4g4n8yemBj6Sos73LzLy1q7Crrq2lqZ6jnJuxsKWnp4CXjIiDi4Pn5eDtysS0uMePjY+YmqqllI6KjaSI3O3n+YeTl5Skx5rk8Jq/w9Xe38nk4b6yxNWos5mLjP+Avp2fnLy4s4OMp73Iz9LY8IKFjJvo9uvX0sW2n42tm5OUqPmGi4aNjo2UoZ6Yla66yNfc7PiBg4WGgoKBgoP86NrPt4CYs7rByMK59O+/0JzkpZGSl4uWpJqfop6AkLez9tSAnqCMlpWdnJSeiJaQssmwoczd3MvRi9TQ5vO/28TC0ebekaS21tTBq+Cnz8ePqvyO+ZOYmY308+yCltK34fvciPWCiIuJibrhgs3ZxfOGiZ/jvufy+4SLi4OOk5qkpLTBuICrqKumqry+v9rv8fPt6uDAzL+6uLm0qqScjP7079ity+TfxJHPuauwiLuMjsyeoJCptLbfvsmLiKPny6SFi5WChZGvv6f4hoWFgvzu4un09fnxr5HdpcHG+MK9mYqtrZvFra6d9bvbg4iMlJmdn5yWvoSPqa6zsqSShu3Ar53hn4CSb25iX2VnbHR3dmtnaV9iilFuiUpRVVJXYGRfW1dcand5e3KDSmtkYYxucXaKSENeY2hkVFZ9UFlRnaWPaZuKdG50foR8fWpdWVRVVGt1aWhga2WPhotdiI+Tk4x8en9hZWV1bGRfYGFkYmJST09fXoFkx0+WVF5pZY+TTlRVWIBcWF9hW1heaXRwamhpaW9uaGpmUE9NYHqObWJsenlwYluXg3lmWF9oZ7WTeGdmWF1ffaKtq6aGr3mVY3aCi5CGfHacpL11sXaCwsG4oH5jfF21tbhfYYdekbBmYmFtfZKUfVZFKSNSODmvhoiih6musX+Vz9Tdd3ns7nh5eHfqpoCE2seohmtZk319fn1/fXx6eHZ0cnFvbWtoaGhpXk95orSRl56Zu2lrjsJoYWdrcICasHSVrq+opqmvrZRvmYKGlZ6goKFge4ierllZWllZWVhbZGNdXFxcYGhjV5SRioeYmZ6OmHx4ZWBij5aVmpaXoK5fZmppbGxqa2tlZ2ZciYBramVaoKRdbI1fcW9nZWJhYWJmZnW0r6GQkKWumoaUgadkmGNjb3lYeKi2qqWnxMTH18t3zambkIqGg4B9enl1cW9ta2lnZWJfXZBhel1Nes+U986cYy0eQjYVGRsZDhEWIztWLkQmlaiRkWiGjYqGX1JMU15ORztERk1GR0ZNTYBJT0NBSEhMSUQ9QkpDPERHQjk9dZqnpqOghUIyWFNPSU1KTERBRlFUV2FaXGNpb3yQdHh6eHh3aV5PT1VbV09eVVJkY2BbWFphXFpdXlpWVlmvymtqeWh1dHJ0fImemJy3ZXHUsL1lcX2CjJWcmI6LhYuHhYeEg3uAfHyIh4SHhIB8c21obWjBv7nBraybl6Z3eHuDhYqFend5e4hv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h5KcrLGyr6SkqbK7tK+nwZ20gaXOkpCayJuj4+va1oug9qmwxpHh0aquvsHAtKSml4j/h42Qqrazta+18rWO8YzslpSbo6OSjZCfmpKjsquip6GTlpi2wbeioo6Qts6Nhfr8kqSD//yNjIaAiIDv/f7/g4ygnpmOj46gn5SRkYyUkJOa2L26r7K1rJaL8oS/u6KfmJaRgc29uqyplpek0viH1PX/gs2B/fr+jp2jpuff2aytzNqCp+uayfb99ejCofGgkZKWmq/hm8jwi6Crvt/t6b2e/73Rzq3X4Oiknvj5/Pv29Onez7acgaGAYJaHhYuNjYyLiYeEhIKAf315dnRzc3JqV4xnfaWelpShy4CbyOB3foKJjZijuOSDm6+1vcbUy56I7tPHwcTDvbnFgqHH2tvd3NbVztDf5PD0+fL09fT2gYuOif39/fTu5+jds7yeiICe0dza4O7x9P+Mj42OjI2NlZyakZGPlpWA8Zv17Pv958/Q0Jnwi5CPhYODhYf4ge3g1NPQwL28roqklotmYGh0dkdjcX6Bh6LH1tXm6v6Q97yto5+ak5CNiYSAfXl2dHFua2ekdMWnvYRBJ2ud4t+Ehf+JqK+zrOnf19K4hbCB8dzw0N6bv8jHxJbGvNDht7WNqrnKube91MaAx9SsrcHC0sa0nbPNrZm2uqeTk/bQ3t7Z2LmT9+/g1OLjyaSruq3LybO4pa28v7m7y52en5ufpMewmZeut5+Tq5aYtrmvrbPDysC1ramvq6Cr5+vz5JfDlI+EhIaEhPjv8PP8hI6Yt8/n9faCkpSZmqCfpK2joJ6Zn5Ofp6uopKOAmY6OkJeC++/d69rRzMHUrZ6gp6+tpoH48/2Xs9ft7fKAhpSYkLKV4OOmwNzk+eKry9y9y8bcpq2gg/P/8fignrLCv7mAjqKywszS4PL+hYmV5ujm18+/sZ6IpJSOi6T7i4yPl5aQmaCkopWxvcrU4vCBhIKB+eTdzsOwo6Cem5CAke6+jcbTh8XAisCvo6uS6OyHmbOysKik+5CSrIDT8JiajJmXmJqYmISclLHErJ3Q5OyLjKGqsrmlhK+b/YvU/5CUkbOYgNWDtvfvuK/siYDmyNOh67WesJ3Zpev43Yj1hY6RiYjcoL+V39Lxh4Gm8bPf4/OJioyhsLGxs7fI1dmA193v7PCAhIWPmZmYnZ6YiJCNiIOCgeXZ4M/J0t/i8YCEhoGenoDh/pX7mpeMnaCPqbm95pq1s/TwhMuIi4+Z+4WXkv70+7zWpNWCh4KCgvzv6q6s8qzBxv/Ow6COqqiWvqqvp4ei5oOJjZWanp+fnaDaj6uztba1tre0s6TugouAYGtyZVteZnN+gYB4bWlgZIhScYqRTFRbZmtqaF9eYmhxbGhhcllyW2aBW1pfdkhCW19ldFdPiFFXfWSiknyDi4iIgHBtYVaeU1lcanRydHF4oHFflVWLVlRaYmNUT1FdV1NdbGhiZmBSV1lxeW9hYFZhcpFWUJKNWGhOmphWVlCAUk2UnZ+bT1VkZGBXWFllZVtZWFZcWmV1g3FwZ21waVpWlVaCfmxqZmdkWo6AgXp3aGp6mbhjqr7CYnBAf3p+S1dhk7GTnXd8mq1phMOCqM7RzrydfLh0aGlrbnqXXnacYXF5f46OjWpove3ws5zI0dqdn+bp7uvo5dzRw6qRd5aAaoR4e36BgoGBgX57e3p5eHVzcXFvcHFsWYtjcI+Gf36LsHCGqLdiaGlwdXqBja5faXN6g4+akHJhqp2WlJKTjYiRXW6HmZuamI6NiI2boKuusKipqaipWWNlYq6vr6mkoaSafIZzZF1tipKSnqmqqrNiY2FjYGJiaW9sY2RjaGeApm6vq7u9qZGVkWmlYWRjWVdXWFmmXrCimp2diYaDenuPcntiYGpub0pte4qOlKnG0c7a3ed837GhmJGMh4WBf3t4c3FubGlmY2CWZXRWT3ucnPzPpl0rIEkZFxwcEA8TGCk5LTIlJoybiZRnf4WEgl5QSlNaS0s7RkpMRkdJTkeATFBCRElITUhDPUNKQTtHSUA6O3mapKKgnYNBZlpUUUpNS1BNTEVNVVhhWFtlZGl1jnJ2fXp6fG9fUk5UXFdNVlFVZWhhWVlcYFtdYWBdWlVXu7e9vnyPdnp1c3Jvc9fIyM/UcIOFmKq1xs5qcXR5eYCAgoaAf352e3h+goaFgYKAf3Z0cnlrzL+3vLKzsqivjIGEipGIgWbGy892jbO9v8ZnaXJ0c4tCOzpLXXp+dW5yZmZkaFNgRzAiIDUvKJElR1xmY3tcanaBjJKWmqCoV1tikpOSiYd/dWdbTkRBQVCfU1FPVFVVXWBiYFhnbnd/h45LTk5Pm4+NiH90bGtra2OAZc+2aH2GV4Z0VYVzXllS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iIj0+fODgJGNhI6QjpOdm6WjlJKWhoaHlJ6Zg/2enNPCvbqvnpSIg4Luj7i0rqKlm4Dszb20tq6sl5GVrtv3+YTF+frw6du/psnKjtCIgPfq5+fXzs3d9Y+y+aPY/IKB8NW15KGbmp7B/J+74YSSnJ+lubbO5OXhm4XWy7aghXC9oYeBgoZkgKWSkpGOjIqKiYaFgoB+fn9+fnVkTnKPuKqdmZWhw3B+mKi+YmttwLvN34a795mqraejpJeC58DExMq+tK+lpM6JqKqlpaqvsLnFw8jDvcLGwMLL1Nfb5u308evu7/f7gImJhv+C8ero6fz6+4GC/vL49Pb594SGh4yOiouKiIuIgIaGmNfp5ePl3tbZ3K3z9e3y9PmEg8GcnqCmqLLQzryqjqbrrWJgaW5yelZrcn+Nmrra5ufz8oySh8S1qqGcmJKOiISAfXp3dHBsrHvB24WINiREmpP1jICMl7S8uszz59vatpXRi4DQ9tfml7W9u7iQxMHS08C8kbLAz6a9t9bDgMLQsLnFvNjGuZ+3y6qZtLOnj5Dy0tvZ1dG2mPjv2cvK4cKinaOsxNK5q5OfuLits7iWoZ+aoabJqZmbrbquk6qbnri7tbS9ycrGtbS0sq+osJKZm5OQuo6MhZKUlZGH6+no7IOGkZSTsszR5uTg94uTjaGim6WVlpWQpq2tqKSngJqRlpedoon29PPx2djR0uTP0dvd38eNj6Dc5vWBg4eKhoiLiIqukb/Bk8bW6/751+e4rKmv2sasioP78YaapZ+pqKik54messLI2OLr+4OJkeDk59Wh5K2eiJOBgYeY+5CQj5iUjpmgpqiZuMvY2tvZ2Mi4ubuzr6+spZ+gp8fjgNjQvdP0zM+u0bXBxcPW0LXBn5+hm5y00d7kq8zcr5Cpube2qKSgmZCIn5a2xaSUy97+/4WQpqmhr/Ku8eXTv4yn0d+b7M+/3d3cm6K04bGt5LvflO2XzL2H04T15N+H/oKPj4KAguOM0t7P84SFvoel1uLulMSB18fL0NPl84GGgIaGk5GKlqKcn7GxrKmnoZ+UlJaPjYn58PHv3NHc5PWEiYaFp6m9rKmQgpaeosXnzYWYjY6PqrmEr5iYqZCRmIaElZCA98Lw3O2DyfyQ6aLH4/e8sfqpvsGAvcCciKaklbimq6aStfeDiY2VnJ+hoqH325Svtbe3t7m4trWxo6CLgGRoZl9VmIdvdYCMiIiIgHSKWXWFS1lkZmRdWldeanR0c29vdXhIYJJdc5KQmGNIQVleYnlYT4pQU5yAhYB6iZKNfmesm5aQjlVkY1xeb3R8clJjqXRWXVxcXVlbXmFpb15NUFNaXGFkY15jb3RraWR/pH2HYFtaYl9SUlGPlJBSgFpYUldZVlphYWdmW1lbUlBRXWRhUp1weIJ4d3JqXldQUE6SWn16d3NxaFqjjoiFhH16bWhrgKG2wWVvfXp0bmZYUK2Sbp5fWqyen52Vk5SkxHCPzISvz2tqxqeMrnNwcHGEoWF3mmJxf4ebqaK71dfWlorIv6mUfGixk3t3eHlcgLuEhYaDgoB/f359eXl5eHZ3eXRjT3SInpCMhoKPq2d1fpGoWl9do52mrWSFuG99fnZydGherZGZmZqUjId/eY1gdnhvb3N4dn6NjZGKgoaIf4GIkZKUoamuqqShoqirW2RjYrVbpKCeoa+sr1tcs6irqKmrrFxdX2NjX15eXF5cgF1ea5upqamsq6Oen3mipJyhp6pdXoxsbm16eoWenY+DhYvCqGNmZmlteFx0fYqWoLbT3Nvm5X5+ebWpnpaQjIeCfnp4dHFua2hknGpwUkunxaSAzllTLSUkGxsdGgwSFRgyODI8JRKCnY2YZHp/fn1dUU5TVE9MO0VLTUJLSlFLgExQRklJRk5GQjxGST87RkRBOjx4l6CdmpaAPWBgWldTWk9MUFFOTlZWX1tYYGBhbYdtdHx6fH96W1RQVllQRVhRUWFjXl1gYmFeXGZjXltZX3N2dHNwkG5tbHZ3d3dyyMPGy290fn9+kaOouba4ymx1c3x9eoB3d3Z0goWGhIKHgH95eXh8fG7KysXAuLqxq7itrq2xsqB0cYKyucJkZ2ptbG1ubnCKRUJLR2l/fXdwcXVlaXBgXEkxHx1DJhlbPEhYZF5rqWl2goyPkpedplRZX5GQkYdxp3RnWlJEPEBPoFVUU1hZWV9gY2JabHeCioyNjIZ+fXx4d3d0cG1scYmcgJifusexjpCMqoeJeG50bWNrZmlycmt3gYiLeXx2WkRNV1NPSEZEQjw6QT1qhXJjgouap1hgcHNteah3nqeLfFpqiZdknIx5jKy1aWt3m3hvkX2MXpdkhXtRgz5jXVw1ZjM4NzMzPohXgmFZazo5ZERCXmRr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iISEhYeLjY6LiopnIKSRkY6NiYeGhoaEhIV8a1Z+o8i6qpaOoct5h5ijtmFphGiAcXaBpNmGjo6TmJ2gmvm3ipGYoKKhoqOlvvSOkY+KiYiHkJGOlp2an6GlpqiqsrzEvLrAx8TJy9PW28vNzs7P1t7k5d7KyMnP0dPv8PHq6uno7PHu9vbn4eLi6OTn8e33xsS9pp+bm5+bgdfb5eflzcvL7aamqLCurLCpoZmJ+taAbV1jZWlvc0dncoCOmajI5eHq7IKXtoXMvK+oopqUj4qGgn16dnK3g4O9gEwuLVOcwoSUhZGyw8rJ54P26+m7o/mTicD15/KVrbGsr5LDutnStriUtcTSvca/09bO1rLBt7vZysGiusCokbCtpJCI78jW0s/Kro7q7drEwuDItrCApaG9sZuXmZ2lrKy6t5Cep6CP8LWqp5mprJmHn5mcube1s7vHw72pq7C1s6+9uKqZo5SOgeDOv7mfpbCynJaMipGUk5STncDDytH6lpWOlaGfm5KcnJusp6yuq6uoo66usbyimJGOkIOBiYWEhoaPi4eG8/Lu94WQkZGYmp6Sj5aAlb+Zzdecwtnb/IHs18aln7C2mbXyycCv1rHOrpOToKnjjaC0wsrS3Ofw/IeJ3eHn1ZK0qZf/ioSDhZLpjJuRlZmTnZ+fnpioq7i/xcbDvbu6ubW40Of+jJ6ooaGRjLGy4Im+gMukyq+utaqdvKSgpbDLw8XY/YKmnpej8bqHmZyAkpKZm5ebjezSo5PP3ZGjqr7c7PHT0fHfj8CNmY6OzobUu6XF49+K6MSB19TFpoX767HIxYi924D744Tvg4iJhICLks2J49HthomGvuW0s7GYiubTxc7Hydbe5Pbx9YWGh5i0lZG03PTEn5xeVsXJ1Pe7rpG+ic/J0tTg8oiLjahUrL+tqZyDk5SCqsG1ytfc1ufFl+6eo++Wm5OdjZCej4KA2OLP3eXG3/n5+pWcWpa5ha3AxYXFvJqHoJ6SuaOsopfC/4SIjZWbnqGjpJqhm7K2ubq4hLcEtKyGoIBZaHZ6fX19fHBjp5OHgYSQcVuCUFJUWVtYWV1lZ2psbm1oam1zc2+PWGx+f4paSUJaXGBIWU6JTVFaj5WRi4l4bVypnpmYUmJ0Zl9Uk05Wa2hYX1pOVlhZWVBNUVxpbGlmY11QUE9nb2tXVllfa2poaVdYilhSW1xVlJCMm5ianIBRUZdRVJVOUlpjZF1eXVthX1hWVp90soOBdmhhY15ZXFmWjIdmgnZ0dnZrWllboI6MiIOAbmx4hoB4SVZTT0xSaHmSarSZXV2zfJ93ZHWHmqWflpGPlK3Jfqd7omt4eG6/pX+djImLnLpwiJ/dhop9bYqfiXh2eHt9gYKCgYF/X4C1g4SDgoB+fXx9fX2BemdSeZa2qJuMg4y1bXmGk6FZXVxbXF5hZG2GqmdqamtwcnNvs4ReZ3F8f35/goOSr2RnZWJiYWNtbWtzenNydXh2eX2CiY6Be4GHh4qQmZyfjoyQjpCWoaOjnYqHiYyQkKmpq6iop6aprKuxr5+ZmJuhoICms668lZSNdnRxcHBvXpebo6enj4yMrHp6gIyLhoWAfHqAyaRsZWFjZ21wSm55iZWcqMHY1Nved4iZdLuuo5uUjoiDf3x4dHFtaaVwOUpGlNSphNJfJDApJx0cHhcICBMZPzU1QCQTd5+WoGBzenZ4YE9LVlNKTD1GSk9JTUlNToBNUEVLRkdOR0U8RklCO0VDQDk6d5CbmJaTfDpYX1pTTFZKR1NIRlNRVVxRVmFeXF+Ea3N8enC/bVhVT1ZZUElXTk1gY1tbYGViXV1kYlxfX26OhHR8c2xiq6aVlYSJkY+Bf3p0foeEhIKKrKysscx2dnV6f35+dHx9foqDhomGiICHgYeJjJF/d3RwcW1qbGtsbWxsa2tpt7e9wGhvcHN5eHpyb3Vzj0lNS1F7hYp8N1xcZmpxUU8/JBspMiEvb3VKWl1Wcaxoc3+KjZGXnKGnV1mNjpCFbJdzZ7NTQT0/UZtTVlZdX1pgYmNiZHV3foSHhoSCgoB9enuNnq9ibnJrbIBjZ6aunliJYoFpe1RPUUxGTUVESVNpanSHpFl4YV1wrHhSXmJfYGtpZWJfl4pvYYKKXGhreoqUnYuKl45peVdhXF6EVYV1aHqnymCggVaKhHhpWKqbdYaCVHJVMmNcNWA0Njc0NEZXfVRhV2k6O0ttaWFfXVE6g4Z+iIOGkqGqtnaqoFVUVGB9bXSAppJ4hnBwX3d3faGQfXN9WHx6fXmAjU5NTmFkUklKQD9aXFFQW1JZXlxdmHhYhmqPolpBPkE5PklBNjdpaWqEmpJ5iZuwYWh7nFlGUlhZRFZQPTRBQTxIQURCQHGeVVhdYmhqbW5u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uLu/tLy+ur69wMjKycrLyMrNztfb2IDMy8aXop6Zn5+emZucjNuqrri6u8C9sJL8gY2Mi5yflpSZi5y/VVpgYmhtcHpddoCPmaK02u7j5fWCl5f82cK1q6GblY+KhYF8eMOL5bmKNS1HVMrqjJ2WnsrW3taGiYSBg8S8laOUvIaEgpivsK2wm7204NC5tJW8wsy9x8DU3IDX4LDDtcPNwLaaub+uj7GwqpXo5sXU083JrJTr49rWxNzHvq2noL6xr6GltbSurbmxmZT7vqOip6Kjm5+tnYygn5iyur22us3Kxqytrq6zr9iZmZSGi/v8hfv08ePg1NTFuLG1p6ixq6m6zPaCgJKWnZ+moKShpq2rvLy5wcjHy4DBxcfEx9HCs7Gmo6CeqqSiqaStvb3Avp6ioJeok5ikqqqzr7m+55vE4Krg59Xt9OPbuq+dmKKVuZHp7cXurJKXiZGquY+jsr3MzdbX3+n2go3f3drTm4unlYGMgPn5i/OEjoePlI6VlJWZjpyfoqzC1u2DiZieoaumoZmbnp2ntICjqLC6tpyygKO4xq2qr6eduqemsKetoqKLkKSgr4yfy4m66dS4oZiYl5CI68mjkM3ditfo8J2c9Lu4zt/0+YOsi4OVkIGEmpjV1qKB2Izf6ZSpofyIzcjIgv/vhYTjhfCAioqEgoeR3oriz/CGiYO5ttPog/+M4MKnqbrCydvq54Dr9/6DiI/npfGf78Xy9LrNx6j2hZeFpensntbWyMrS2OP3hqOQybq2q9ib9NWam5ayxeHZnI3ArPCCvIPtgvm2wIO3joDI5Nza7r+XqMfrhO9Hb8WHrsPKh828lYadlI+nmamchorvhImOlpufo6SmoJCntbi6urm6urm5trDao4CmaXd7fn9/f35+fXt4dGaBWnR9foZ/dn2Eh09aX2FcV1hcZGltTVp1XmlzeYBTS0NYW2GIWEuASk9ZioqEemliXliooV5xcXF2b2ljXpyiXGBem2qSSlBaZmpscXp5Zl9fX2xxb2ZpaHiFhHt4dXB1hXdhl1NNUE9RWFaZjomIVYBVWY+VllpobmZpcGVfXl5eXVxWW1eRbomegHp0ZGBZYmhiWllQg3pbWXF8cmtoZVyajouGgIGUmpaWS1NTW2NdSUuAdHWNp6qgeZlhgoeLi5Vge4yoXFqtp52XlKHFeJFnjb15fHp4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n5eSjYeB0palhZMxOF9VmIuanaq13+fw95WVj5CS0eaytaTCmJeNm62vq66mt73b1riumbjDybzMx+bagNDeqcG2x9DFuZnDyreXs7KKkeKyydTQy8Wpk+HU19DEzr+tp6ynrqeUkoahpqmyuK/voJuclJqpn6ShoKqXhpiRkKiysLTD3NLLt7SwtLe21uze5/D4g+v09ejn9NzZ6enn4s2yrrzU2dnX/IeRo6SotKuxqKWxt7/P0NLV293ggM/U1tjh5NLZ1crWxLu4vsTL097X09jhxr3Dur7MxsnV4t7dy53MpuXyoejy4vP44tzJw7yjxKWfgt3ciKLEnpiMkabEm6m7y9fc4uXt8YCHk+Pj0szErqaXgpT47eeJ7IWLiYuOgomMhfmfn6mtssqHoaqxrKOYoKqxs7vJvL+fgNOwrKa9xPm39fXrqayurpu5rqOvraqhpo+ZqLS+pPb2+4qWlZmXlI+JoaXDu6OMx976/oeJopqHg4KNjouOl/OEtqyr+eSM+pby3KDjg/rB+ert+83Sx8bxw/qDg+OL9/uJiIWB+dua2N/U9IaJ46ywxt6C3Jezq6SpuL/N0trkgOn4gPuFjvCOsYudnezUoYD1hr/jo5v6i6rx0cnKuqyxvbSz8Pzh0YCskb7Gi5SYk7PE58uK9NqerbyuqJifn4qQmJGE/dvMtLXApKaki8zK8ENo2oy00M2K18OZjJSSjKGMopj0iJG13P+Sm6Cjpqejp7K3ubm7u7q6urm3s5qygFVseHx+fn9/f359fHp2cWVXWmRjZWVcopF4eJOTj4uRTE1Ii1ZBiGhaZ291e1BMQVhUWXxUUHtGSqR0bGenmqN6XmdnZGRjY2JiY2VmZoSAl2NTcFlca3l7cXJ5fH6BgWddYmZydHR+k5SJg4aFbV5dYXuyaWdlYWRjY1pYWFZigGJmWF5gaXp+cmdub3mBgXd5d3J6Wl1eflOeV6KUkYmBcWBaU1lYmqF8e352dHRmt6yYj4eFhoOEkqlRfW9RSUdHRdSIe4CVlotth6Z+b2VpdICDio+ha4alX2tqY15XopqQl7BrhbeHrnqHi4yEcqtzoYB1eXhxfo93eXl6eXpcgLCLiXlloHWPz8G/wrOksMiEnahcYmRlXFtlbnNzdXmOmqlhanBvcHF1eXh3aZp8dG9ycnNxcW9virtla3V0c2y9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h+OkraSdoZaNopWWqa+pr7La0s28uLi3ta/ht7jG0t7d7Pnm6NTExsjLydDIy87Ev9LDztbpiZiRprGyrsCqrbO6wc3O3t3U2eKA2t3f5Ovt3uzp5dz53eHq5ff97vaA7eXj0NvM2NXx7P+Fgvj/wsqs9pKn1u/p8Nb05b7I0MrFt8LirLrppMSClIuFjdalrr7O29za5vH4h42S2dnMw8C5rZeCkuvu64bb9YD89ebNy9Hg+ZTBt62114SdoaamnpumtuiLo4vKh4WAiLWhsZ2NgLyBg/asoKuwnbezoaqxp6Kqm5Wry8/K6/yRgo6bkJWWjoujosvDq4jF2fKIkpeelIL8iZudlqCjoYC6qbOTzKmWq7nNqOTOgYz/14L8ldLDuqXZ+YiF64+A74uOh4Hkre6l+bDl9NKT6KvD4YS2ipa464KGipqcoq94t6yyvsvP1J3lt/uDhvb28u3VtLOZ0qa8v82Qjo+D8/uD/fj/ifqCnJa0l7mEmJiUvc3w07vBhp3Pt4+BlJmch5CZkYSA5snCsr2roY+h9dnyTHPbidHczI3bw5OMkYuEnpGck4KdyM7Oy8nlhJalqKSrtre5urm6hLsEuLasowRZcXt+hX+Afn18end0jlSAYGRtdHZ3d3h1aWVYmYR3bHaIZj1pYE5Zam50T01BWlFYdpxXcEFIeapZrIaQb1h9YFRaWmZnYmx4fI55UFWNZlJzYnF7e2diZWt/i46Dh4eDg4aEb292dXFzd3+EWlh/drVyfHlqZmVkbHNwanBxcGtxcHiJiYSAcHR0eYqJgnh6endvXLaIYWBfYFylpJeMZVpZWWBaUGeLgnt/f3tjpa5bsp2doKNhd3JQSkVHRkZF8X2DdIiKgHB8qaSUlJiQjYN1cnBydXp/j2uRsmZtaWVfV5+Ng3p/l7JxkWea0YfSqpaVg219WHR5f4F8apTTYIFzb9zd08yAzXmVq7KXhapiaW91enl4dXduf527b3p/gX18cnBycnNzcaZmZGxzalxdYWdtdLF5fnt5dWW5r6qosGBsaWdhop2foZmeql+2sLBgYsHAbGxscXZ4bcDAwrzBYmx6enxsZmRhY2JkbnV4dHV0dHZ3d36IiIFubm9nZWSy0czFubaAsrTHvb/Co2xqb2xrdXp4p294c77AwGRmYmGEeZDDgIRiYmZrb3R9ZoiNmqSu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8viLkpKMmYv8kriAo8bogYGF6fH84sjRx9qkrPf18dGI+5aCh4eM8NCCttnzgfn29/yIjpXX2tbLy66EvoDw3Nvrhsu+xr7C2dzg3tvei8Pa8oGGjZaduuGFlaS3wsyXzfudm+DApq37z/XWroDQxa+kqq6fu6qZqa2mpaueh5jR69+o9/CDkJCUkpKPjpuly8CqjrzU6Y2boby8oZmnubaoubfHgombk5XNgJKhyuL93LCOioPP1InS0cCwtd/6hoTukYH1iY+HgcyPxIPW5IOL+8bYqr7ohJCCg4eNmJSgtcfO0NHZ4Ozu/9uGjWyv6+rf6eH25bes9auV37T698fBurayn52jnZWWn5eYoKOxmY2anpnA0/fdxeHywo/2l46VnZ+KjpmViP7Iq8ajsbeYia+06/1UXtWB0dvomeDGkYqXjv+dm7q6vsnh9ff28+7p5eX8i6C1t7iFuga7u7m3s5cGZXd9fn+AhH+AfXx5d3VgWWteZW9zd3h5eHh3dnVzcnFrXlp7X4mBklJkam+YS0FcU1p6nV9oQEh6oaeEjKVYXnhzc3Bna293gINrl1FSTHpnlHFldXh5fHx6gYSCY1xgb4ySk350c2+Gjo6JlZKZXnZfr4CFg3J3dnB9fXt4fXt6Z2tob4uMjISAhIV9f4aJbmtsYHl5bo1pamRPkYqPUZmLenl0alNLkm6Yjo6Li4qCgoKBdmR6gXJWSElKRUdGRUx3ZYBsgHxuZXKcgXeCmalZV1dVmYd5ZVtfYWNmaHpehaXGamO3rJ2PiX93cnWHnqObjl+MXXeTkIqEZZdjZb2fh3uLmKW1tbWAsLxrcGpoYbS2dJCSjZq6dIqNi4yOlJKSjoqEenZ1dF9+cGdkXluqVWBoZWpebGZmc3p5dnFwb2xpb3BpbWprZmNjYF5ma2hmZWVlrKikqaq1XWdsbGpoamtmZWZnaGxqa21ycsS+wWKpra/Aampqd39/c2tqabOsqoC9ubmvqqSAo6aopKaMYGG5vrpja2pnfXV0bXF0c6iVmJKOfltra15jY2ZscXZ3UISTmqavuMPWdHV16ed0f39yybuwqJ+Wj3GUZ0OUpIfEo0IfNk06HBgRDwYJDRIgNykvSB8efHWBb3F8fHh5dktMVFZUQ0FITU1TT05TTlJTSUpHTVJPUkqATYdpbXuEajdlbo2VlJGOez5cZF9bVVhLS1pNT1BFT1RUeVVnbHBwc4K9dYOJZk5XUU1ZVUtPTE5YYlZaXWxlZ2hiaWdqZKGPnaWjq6yttMS3sqyssa/AvLy2wMTFz8LAZW16enmCioWLkYyYl42aqJ6urqentayvrK+yv7a8wWGAtMC8sa6ztWJjZGZkvGO5sbaztbdla2pmbmW5b28+O12OSDxFf3ZvaWRmU1hBLBwqUUsceEpDRUhCk4xhgJepV6iprrJaWl+JiYeBgndZe018Y2NhQHFtbXFxgoOHh4OFYIGRolVZXWJmeJBVX2l1fINghax6cJeEc4S7m8+ibHmAck5GR0hDTUlCSElGRU1NR1B2lZBkaVo0Ojo7Ozs8PT5BcH1rWXeDj1pjZnl6Z2RteXd0eHV/VVdhXV2CT1dtiJOgjntiYFqLi1iIin5tZVtjMzRdOTRjNTYzMmBRck9WXzo9cXZ/PEleNVdRUlZaYV5qeoeNkpCOj5aTnoVpeI5rsJF/hnqMgmZgjGFWhXeimnx4dXJvYmFkYVlZYFhZXV5nWVA+QkNnX2hda4STfHC7alVBQ0I7PUNAOGlpamJDTUU9MDaFk7WOdGVFa32CVGhXODM6OmlDR1ZaYGiQoKGgnp2alJGhWmx7fX2Efgd/f359fHllBWByeX1/hIGAgoB9eXZzXERmXGRwdnp9fn9/f316eHZ0bF1SYzJdY18yODs+WyggMCUmRXQ3LhkeTHB2VV1NIzBNRkVDOzs7PkFAMEIjIx9FQzguKzAyMjIxMDI0NSYgIytARUY4Li0tPEBBP0NBT0c0IVI4OjowLy4sNTUzMjU0MyUnJzBBQkGAPz4/OzxARDIvLzBxXTdfMDAuIDc4PidLRT09OzUpJko7Z2NgXWFiW2BhYltNXWVVOiUjJSAhHyIve2uKd46MfmV6j2liaH2STEpJR3dpWUhCQEJDSEteV4m54Xtwv554XFVQUltqiq6yisWNx4SetLuzqIS2gIzw96yMnrXO6OmA8Ov6jJSNjYf5/qTL08bS+piwtK+xtsC/wr62rJ6YlpN5rJyPh3t12G5/jo6RgJGLiqW0sa2npqWhn6uuoaagopuXlpGOoKeelpWVlPDi3OPk+IGWn5+bmZycjI2PjYyTkpicoJ797O15yszM6IaFhpqkoo9/e33Lvr2Z7u728OlM3Nvk4tvkuX1/7fLtgYqJirCl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raKEhtTN8Nb494GNko2Qkpiak5vGv7CPu86A55CbqtPOsaq8zsq6x8DsgoKjz4nJ7pedh5v04ciOi4XY1IHm1MS+vOD1goTxkIP2goyI98LhpNvR5YeNgsiyoqvchZqGgYKOlZimqLK9xMa7wNPg37vBnYzk2P2ghrW2s6eepJ+x8fL62tLCubu2sayfnaGZkJOTl5mZyNPx8YZPpbaKpNTG5oeW26ecmKKOjpqXjv25gK2R8NnyxMzR+qWDl8+I1t/SnO7ptI+atsDCsbq2j6CL0uj/iIuNioWA8+rtiZuvuLi6uru7urm3toB1fH5/gIB/gH9+fXx6eXZukmZcZnF1d3l5eXh4d3V0cnFua2ZXjoV5T1Zlam+TTEBcU1ZBoWdeQ4pulZSWfVthWmFVV2FscnmHXXeDj09ZXYljVnxlZGRkZ3iAf39rZmRvjZOTgoSEhIWNho2NkJJ3ZJZyZ4SGfniChHpvb294g4B/d3+Fh32EhINpY2pqdoWNg4yKXWNkapVibGZfVVVSoaObjXx8dmxWU1CMXJ+clIyMio+dWqp0Y2NeSElLRUdFRk++lWStampjWWKlW1ZdYmRmaW5ycWxtdHh1Zq2He3BoZ2huiWyZYHWDhHVlqVyglZvPjYR5epGzcJpgg1RnXIC3kpeZg3KJk4R6bGW6p2F9jZOatGx+k5yeoZyPhn5+fn2DjoyJiYWDb394e3t7eXl1cm9sa2J9f4GBgHZsZmRjYmZmbG1ramRoaWpmV6ikpKGmrV9mZmJgaGtpZ2tua2prbWpiYV6cn6CdXWRlaXBwbmtxcHBtb3Fvc3R2cnFvbYBtcGBfqqqrnpSTkImGhY9acXNxcXJwrbCxqW9mYmNnZmdXh4F7cWGau7R0XGVpb3J2eH9slJiks7rCzOF26Onq5OZ1f3vSv7Wron6gTk/ms5DTszkiQ1JCIBoODgkLEBUuOzE3KBIkfXFybnN5eXZ2gkpPVFVWREFKTlJQUVVUVYBaZDpBc2xmWo2Ck41DQD1BaDdncZGWlJGLekRjZllYVlRPS11SVVVUbJvAY2ZnbXGRzoOJmJWXZ05XUU1VVUhPT01bYlheX2tqbGZjbGZkZ2GvpJ+krKSqtLCqr7u5zMLDx2bFvmnIymlubnh/foqGi42Wjo2Mlpicpaeqq7C1r4Cqn5uiqbe0trfDt7i0raiqr7Fj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hH6Gf4N+jX8CAgQAArK5hbqAubm3t7a1tLGq2KaNnKiusLCura2urKqpqKekn5uN39yghI+qvcCAq6LxzMHsi+WcwaSPuaCF1Y2SoaWUobO/zaLZ6+rvipSZjuWfgruTpquov8zNzNXd3cPL0tLP1trX0eLlidzi5tWFrJ/V/b/Cr8fS07vbqYLRtIq/o/DPuaGAo6Kiur28usnQ08nOr4vFjK2LnaOdlJqViODW0L/D087BrbGrnK798+nj8enZysrJuJaSiImSj5actJnU6YPchYf27IL9naOrs6u1u7jBys3M3O3dxrjK0L+qkfnt3MbHxbWy76W5qJhxqI+Ry+6Jjo6D7tm6m5W1qqqLtIezdXCAeXjghWi1sb3zmLLHzc3MyszO0tLMxLuzsLCztL/Cu7Sfn5men5ycnJ6Xj5CSsYOEiYyXlZGPgnl2cm5xdnd4eXp4cGhqbGxnbXBwcXJzb3Bzc3JlZmfOzsnJZ3Jxc3FwcG9tcnN0dX1+fYGCgX11eXvt6ufidn+AfYRzarpoaWyAZmdmY2FiZHx3eXl3c3HgcICDhWxybGprZ2JdXGVjXpGAeJx4o0hoZ2xxdnp9Uoyep7rEy9Ti+YSEgf+A/4KIiPjay6HLaj4vRU6vk7/94oSfvcK20oD+//+B1ceGm5qIyaSdoqmop6Ox4Z3S3/T9gYaVnqChqLnQ2u7Fn+jtx6KAo6egtrOmkperg4fv38zQzMjHrpHv+OHX1eTOu8qtp9H05+rr9oKZyoqdmaaftraplZ6il5GUj5Cik46mxLnBu9rVxbqrubOztJ6C9OPO2+nW1cvJ29nph/2HhIX+ioj7iYmKjZSVpKqjnamzr720w7vSx9LX2cbD2cHTzt3A49KA2evr39713/f19uaA+P+Oj5KNmZWKkZaRo62prJuF1bKGx76D9dHexK7E48K9uNiXmOjzncuSgO6S+v3tkqOjpaWmr7vpi4yEntaunpqR5auS69T4iPrngJiptLPZk8CDz/6Z05GnsLS/0+z094CDh4iJxdKRrbv8/9jZkqq56MCAiMTQ0q+ToamesaSVqKqqsbOigofqroWDn932jY6PjpGUoomXyMazj7nI573O0OfZurrO5eXK5tLZg4Oq0KGFiaKP87Pq4fCOioXl/9Pj1cO7s9Xx94HujoDx9YKC8rKr4ZbP3oSNhbCdw6PJ1rGLjpSRjJapqKuyusfHxdPcz+V55tuAnl9RT6fk07+0mGqBnL6Mkufh3d7SydDKubC0sqmmrKmZlsOpsae00uvjzIrajunVqNGflqWRk5yVj4rE45itzda8xbXSibTOtt2b6ujbn+/Z/v+UlYGijZqdhpGe9vrq5PmKl5mXk42HgoD0+4iar7u7urq5uwV5foCAgYR/gH59fXx7eHKMaF5pcnd4eXl5eHh2dXNycW9sZ16Gj2tOV2VzeE5PQmZUUnNanGuHalaGhmKJW19jY1Zebnl/Y32EhIhUWl5ViV9IaVhhZGF2gH+Ai5CPfH6DhISFiIaCkpZlkpSWi1VqZYi8eHptfoWHdZB9XIuOcIF3ooh3YGJggGR1d3d4g4eJhIl9YYFdqVhlaGZgY15Vh315cHaBfHRnZmJabamgmZalpZmKioh7ZV1HQ0VDR0dUY7OUX6RhZLSpXaxka3R0bnV7e4GEhoeUoJSDfYiPh3dnsKqekIyFfYG2ipuMhGKBcuqXqFtcW1SckIFzc5SUxnGbhaBtYGhmO7VxWZSMlL52hpWZmpqanaGkopmRi39+f4KFk5aQi3x/e3t8enV1dWtnbHCFXWBnbXl6eXhtYF1ZV1xlhGeAaF9UV1pbV11eYGNlaGRkZmVlWFZVpaKgoFVgYGNeXF1cWlxdX2NsbWtvcXFsX2JjtbKtqmFraGZxY12jXl9lYWBgX1tYWmxiZWRhXlqzWmlsbVtjX2BfXVlVU1pYU4BzbIdlmE9ua25xdXl6UIiZoK+5wMjT5Hl4dut16XV3dtqAxbiOrmZgpMeb7FozKU8vJCUeDw0GDxQaIj06IyoVFnpvbGttbm1sc5BJV1phaTpDSExOUVlkbneHeWGSjoNtZFlNR0RCPD5DNThncJGWlZGMekJoZl9aUmFaT19SX4isxcG6t19zo3GBgImHk5OSclFVUUxTTkhQSEhXYVlfW2qAaGFkY25jX2N2Z72zqKy4r66noqy1w23JaWxuz21rxW1tbnJ5c4CGgXyFjYmZkpuTopedpaqgnKqWppqnmK2ip7S2qqe3r7q5ua1ft7tpaWlmb2plZ2tpdn58fHJmpYptnpRIhn2BfndzdGNdVFVHNDAoEhMqWa4+aXV8X3hvdXuAfouduWVia4CkeXJsZq2AZZqCe0B9dkVLVFxhhGWSXYujYYBYa2xvdISU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uuG8oqLGxfuLi42VmpqdoP2YhOCmuKuzusDCzdTVwKy7ucHY19fBvMK39YGChuHVsbLHpJmcpLW9v6zbyJvw8YCh5aSExbu6vr+3zMrJuK6xtrXGyI+LpcaxqKCcl4aI/e7l+/Lp5+bd19DVz8fw4vjLrurn4tjU1s2i4oaVmJqgneXO54Ha/vnm3vKJsamn6HaeoaWYkOzl7vLy2Nvh6fD08u3TwbSrkPfe08i645KLiemKh4qPjLHU6oeXm5Sa4IDZqYL0zIGfsMCOtdeRjcPb29DEvL7DxcO/vr++tq+ytq6tub7FyMO5eYGLmZmmo5uWj4ycf6ahnpuZko6JhYZ9eXZybWxxcWxubWtjYmVjZGRwcnBtbW9xb3NzdHV0dnVwcHF0acfLzGhrbWxyeXt+fYB/fn2BgIF9f4F+e3t7d4BvcL69cG1taWhlZWKiqW+BgYKDfoGBgoiMioq2hIN/Zr6uobFpaWBVUZNfeH9oZ2xucHd7f4Vum6KwwcnR2+v+hIOAgP38gY2NgrvfY0Q9X1qHwuSMjZnJ29PghpaUlZWa6/amuLiiyKudoZyaoJ2hvNfz/ISD/OzgwKGXo7fK12ThwaaU07aDlKukxbKrj5augoX05MzSzsjFrpPs+PDW1dvMyoqMhoL+g4D3nuiOj4yOj5KYm5SM+5WWlZWclY+dk/+hzL3DudLRv7uutrizrqfVxreqt9DCw83L2/f+goqKk5CghJOAmpmis62xrrjAv66rs73DzuHk19T05e/gyMXQ1+Pa8+324vf5g/f29N/q9IaNjpSZnqqbmJKTloybo4P4+//bzsyqveyXw6yyv+e8qLO+lNPfj/XVvY/3jOPf34yR9ePmxdiCjpmdnqi4kof+8MWws7vI2LCVhYLAjrrsgYP7/IWAuYK+7f2JjYyTnKi3xs/V0J2ds7jNvuT608XO2qi0mLzotLnZkZ+foLOjlay0uLS8pPWO+YaSk+vb5f39gYaRkIuIlMzh05q1ye7R393u7szD1OznupOBno2P3pWR+a79i4nH8I+LjoqF85rq5dbDjYvY+/+J+o6B+/mAgfGmkK2A/tfqh5CNoouziryAupeWnaKlsry4tcTc4+ruzoah0fSEuoZPSE+WlZaJlsJug5yOhInsqt76+e7j39bNtq60uKeepanHp7KssM/l5JrZ9vnKkIGRo5ulmZ+loJaU3Maa2dS3sp/Yt5PL5Mv1o//+7qmI4/iAj5CAp42aku/83IkYj+GIiIL8gouZnpmYlpGL4771/I6drru/BXx/gYGAhH8Nfn5+fXt5dml+ZGx0eIV5gHd2dXRycm9taGKPkpmHlGh4glZcXEpWcI9adpSqm2J/YYiPYV13i3diXnt4mFNTVVpdXWBim11Oi2VzZ2tvdHeAhIR0ZG1rcYWFh3VvdW60U1Vbk4Ztc311XF1lcnZ3bI+MbqbRgqOBWIF7eXx8eYeDg3ZvcHNzgI1iWnHVcWlkgGJdUVKTh4WUjoqKiYJ9d3l2c4yKqJiKmJaWk5COh2eAQkdISkxeprmWXJ63uamYsFpwbW+qZHt1b2lgnJaaoqaWkZeeoqelopGHgXpltKOclo2vdHFwy+Jua25rgZWmYGlpYmKd8JqExZdjcHqLbYKad3uVo6OZi4OIio+QjIuJgIqEf4SGgH2Jj5iamJFgZGt1doB9dGtlY3VfgH57e3x4dXNwcWhkY2BcXmVlX2NfXFdTVFNUV2hoZV9gYmRiZmZoaGVnaWBgY2danqKjVFZYV2JvbW5tb25tbXFwcmlqamdiZGFgXmGgoWllZmNiYWFbkJNcaWlpbGdqaWpwcnFygJt1cW5Wl42Anl5eV1FPjVZtc3RvbW5xdnp9gGqUmqe3vsXM2Op5eHZ25+V1enpypsF4gdfhqIBRNhkpNysqHxgFCAkNES5FTjArGhx3cWloZmZram56j5+mV1iupJJ4ZF1kb3h8f3FkT2RNNzxEP0pDQjs8QTM4aXOQlpWSjH1EgGhvbV9ba2diSltiZMNgW72Atnl4dXd9gHyEfnbFUVROTU5JQ0tLklhlXF9ZZmVfZWZoYmBjgbGjm5WZoZyepqi1x9BpbW10c391dHR0d3h9joaJiJKYmIuKj5eYnaqrpaO3srqqmpianqqjtbC5qbu9Y7m4tqmvtWNlZGpvc3tvgG9ra21mcndhu7zBpZuXfI2sd4xyb3F5Y15dYkeEjU9wHiRLyz9seWlic9G5t6KvZXJvb3qLmGxjvLCZi4aIoaV7W1BNcVqCrWBjvrheeFBvipVRVVNYXWZzfIKGgWBpd4ulcoqZgpCVnYt9a4S9mKCMQkVEQ0xGQEtPVlhbUXxHgIU/W1yNV1hkaTY5PT47Oz9vjYJlc32Si5KPnp6DfIeYk4Bg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mciwrY6HhIKIjo2JjJONh+rZ2NXU6ufXgvq0tY2Ok42C9/LfgNX6j5ensebV4/bM5eHPstmNs+PSxuWCjYiQ3+LVvZ7Ah4KB/eft7/Xy49LKuaCRh/fzpctparBQ3Pb77uf3goaHio+ckYBllYyLnd3RoOKqwKz0YHa72tbNxb2/vcPR3dbPxs7f49jSzc/Qyraml5OHiaCvr6ejoJuZ4ZCMiomGhoR8foB+e29jY2dtZri0sbe2tbdhb3BrbGJiY2JkY2LBbHR1c25saWhiZ2ZnZ3J1dXd4eHp1b+Hi5evpd3eJi42OkJCNY4B8dnRxbWppaWlqamllyYyMio6OjJCRkZF/goF/mIGFhHhzbmeztLCnlIV+vel6jmJvb3R5fYGCpIyjqbvIzdXb6f6DgoGA/oSFhdD2alswcnzE+JOavcaEh/6OtLq4tre2i5jN2dnA37utr6SutLC1t9jb6O/+g/3XwrCd89S9uICxmZjAvcOKmaGpxLeukJWrh4P18NHV0cnErJn1gvPf64KTo56ek4iIl9yLn5ian6SaoqiY84Hv4Zegn5WelI6WjYepxrW1utbVxbeut7apqIewr6CmrKCfp8La9u+BjZSblI6tma2sq5upoqi3xLy7vc2/vbvGxdbn2uzs9eDs5YDE0tjW0OTy4/P//+z1hfnu5vP7h4X6jpeWjJOWjp+Zn5GSj5KKg/LhyeTVy7qa4ZKik9rU0L2gt4D79+jw9qGK4ffhy4+PlZvG7drCwLWqudKOj4iA+vj059fcgJi+0OHk94n6+/vt4N/tiNKt8JSenaKrsbe7uaLLyM91kLPL14DAiraan5D0iJfVybutjp2kr56btcS/uLWj6Zf98IKmnYXf9YKFkJibk4SV1vjlpbXH98fY1NrUu8LMyYnSuKHKlZmIxuD3jpP83+fvh5GRjYf96O3k1b7rjd79gZiEnIv//v2EgKX7jebc84iUkJyIqYiI/cKgn6O1xMPM3/T6hICE0ueg9seH+eGkeFBKWWmJY4WttcvLf86nwt6Dmum88oDv4NnLxsi9sLGzrM+wuauwx+HoqqjM0s/KpfWsq62cqa60pZXtnvChgObFjpCGyeqC8IaphoyEuZX1+v2Lh4Ogh4+M2+7UxK/C4IPVkJaQhoOIl6Kin4jnj42Hgvr1iIBicn+BgICAf39+fn19fHt6d2hscHZ5enl4eXd3dnV0c3FvbGlkcZKMlZZnbYBViG1UclBJVFaKgJF0eJRnW2FXf25kWZ6OgGtVXGNugE9bZFlcU5FiXllVV19zd3h4g4WGe4GCgn58e3x5koOOh41obGt/eWhmaGtoZFiuXmBbroCkWp6Nd3R/f4GAdXd2b2FmbmxaaWdeZMptalFMS0tRVVRQVFlXU4h0eHV2h4eAUaKFj1pcYF1Yp6CQTZCbT05WfKXSlKySpKeZgJhcb5CHhKNgcnl3srChhm6EW1dXqpuhp6qlm5GSiHVsZbq9hadZXqWDssHEvLPAZ2xub3GHlIB7bmBfaZWLaZt8jnqiVW2OoZ2Xj4iHhoqZpaCakZuytquinqOimYuGfHZsbYCIhYF/fHl1qGhnZ2hnZmhmamxsa19SUVddVY6Oj5iWkJNTZWhjYldSUVJUU1GfYWtqamVfXVpWXFhXV2dqa21ubW1lV7Kwr7azXV92d3h4ent4VoBvamhnXmBeX2NmZmBYonJwcHNzdHd4dnRgYmFhemtvbmVhXFqblpSJfnFonMFllGtxcHV5fH5+nYSanrG/w8jO2Ol5eHZ16Xh4eLvWks2J/72KUSIkMkEdGx4QCw4PExo8LTJCLyMjhHlzcW11eXd5dpaam6WoU5Z/bF1Qel9QSoBJQ0JMSE44PT5BSUREOzxDNjhteJGVlJGPfUhsOnFucEZdcHJuZmZoa6ZwhH96h4mCiYp61WvMtFRVTkhQSUVKSEVYY1lcW2lpZGFia2VdXmWQkYOLj36GiZ+40MZocnZ5dXKFd4SGgnqIg4WPlpWTkp+Rk5OfmKSwqri3uam0sYCXn6Cjoa65qrm/w7a/Y7eysLq3ZGO3aW5uZGtwaHJvdGZpamplYbapmKmflo1zpm1rU4FvaWpSj2rk6s/D24hJZ3BjhW52eoKqyLKemJKLl6tra2lgu7myqaKnYHSQnKmvuWC0t7mtnZqrXohvnGBlY2NpbG5ycWaHiI9jWm58g4B2a4Bwh2q3Ym2Tq6WBREVITkdEUlpbWVhPcU2HbUxnYD9SXzU3Oz0+PTc+b5SJbHV9lXyGg4mGdXqEhVqTdmWBYGNahZyyaGermZGZW2RiX1yrm56YinaMNVRjMTgxODVkX14xMEaIVnlZYTc9PVZLMQULmHllY2RygHuClKWmWIBYipNikXxPi35ibEpHTV1z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lphwede1mdDe3ur69/P07dbNyMe80n2Gi3NX9YGBhIeKjIuMmIZyvoC4oqqwiMXHuKCFtbOlr4bf1drXzL/CxMvT2N7e3drIurO+xcbEyK2xs7iwqJ6fp6ypmpOWs4+OmJyUkYyFhIB6eHVxamlsamhibXNxcnByc3RxdHRzcnBzc3Jwb2xpa3Bub21wcW9sbW5tbm5zd3d/gYOCcHZzc3R3eHp+j3Ogg4B4dXVyuLm5vLuwqrC7gYSHiIaFiIiZmpqYnJ+gioqGhXx1cG9vbG5saWZlX5aSi2JVeHZ4fYCChIVpnqevwMjK0dzrgISD//+A/9yKT15QUcKOoL3D4ImipqOx4+Lc2tzQpsKTpqyPic+/usXr8uTZtrK5qYKUv8uxvLW9s8nPuoCxlKO+usWInai5w7alkZOqiIDv7dLV0szFrp37gYGAp6OdoaChp8OSsbO+v7q+wcLKwKKFgoKA55SYlZSYnJKWjpizxbq4udHQw7CvtLGmqoeYo56SmJWfra3ji4eWl6KjpLWvobKqrsHKtcnZ0ca9xcXA1tDSyvDj2u2C9uPb6IDV5+n54/X38fqLg4DyiIWBgYX8h4qJj6Gclo2dnoyIkY6SmJ2YkYuI7/3m28bFx8LGzabhiZyA88PGv8zMyrKj+MzF3qedu4KMivHz8IabwL3Z6efKn5Sbn52Ul5WawPCB/pKTjvXZ1+HsibnkgYm58papsLS4tLfMiprMorfHzoC9leHQrZ+I+rWakYmfnfO4waioxsi/s7eY4qGE5cWlpMfY6/+Jkp2WqIWW0P7uprbB+sff8oL9zJPl/uXgzs7jo6Sqg5ClmY72huD515KRjYmF/PXqz8aAjfGGjZiJoZKFiP6OnbnojOPW8YaQi77ejqGAgtWjscfa3uj7h5GWhoCTyLKDqLOMzceTanVfb1lQj3mXuIDWq6uMppyrjvSJrIjN7u7j3trRxcLAutGtxbipwdnuiqa31dXJqMSHkYPGz4PGkpTwnLSvoIaBnbnkltyFpKDJk5+axKP/gf2Hh/uYgYuC0+XVyK24s5qKi9vJj5qXj4iLkpyaoaCdmJKNhwlVnptVYW57gH+EfoB9fHt7dnN0dnl6eXh4d3d2dXRzcW9sZ2OhV5VTTm59i1JVRUA3TUVZp5hlarBrWnd+blZ6b3BvcHFyc3FkXKWRgmVfj1tmZpBWYmJbXmVibYaMkoVzeHx3iYyCjICDf3R4fmRtW3RwaFtNUFdZYX6ZYFpchWdmenVzVltaWmt4doB2b3mBhKFhfWhfvHNwZlpdXl1gYWBeWl5fXY+Ah5qkm5B8XWegd3SChoB6Xo9TkZmjtVxiqtuWqI+enJGgd4dxYoKbemVml2V7pGV+hlpiqYpqkY2VoK6sp62tm5iVk4yiZG1yY5G9X2BmZ2xub3adlnCFfnF1e2aalYt0XnyMeICSfq+fpaKWiYqNlJ6mrq2sqpeFfoqUl5OTgo+Slo+IfXt/g4F5dneJaGl5f3p2cG5ubGdoZ2RdW1tXVFFgaGRkZWlrbGlsbGtpaGxva2dmZGFkamhpaGtraWFeXV1gYmZqa3N0d3ZdWllbW15eX2d7ZIBsZmJjYJmYlJiZk4aHmIBnZmhoZ2Zoa3p5eHZ6e31tdHNya2ZiXmBcXVxaV1dQfXx2X197d3h7fX+AgGWUnaa3vcLIz9p1eXjq6nbqw3hrlq+Z4EcyMTRAJiYjEBYSFRkdKUw/TkI6PzJQhX14gJWYkY1zTU5LPEJNTklNR0lKUFFLSEBES0hONz5ARUlFQoA7OkE3Nml7lpiXk5CATnQ+Q09sdnN2fn9/k2+Wk52bmZqeoZ6XhnJubm6/U1FLSE1LSVBJR1RjWVpcZmViYGNpY1heaX6Af3h6gIeKj75wbXd1fXyBjoV/iYGDlKCPmaagmpGZnZWloqKcuqqos1+zqqmvnbCyvK63uba9YmFgs4BlZGBiYLhlZmVrdnRwaXJyZWVraGxvcm9pZmSzvKujmJKWlJidfqJkbkXApaqlrrKviz5ra3Coh32Vam9quLbBbH2UkqKqrJZ7cXJycG9wb3KOrmDDZmVktJuQk6RegJhUW3eZYGpsbXFueIpge5Bhb3l8dG2iiZFyX7ODZmduloCMj1hZUE9eXVpYWUptVUlpcGdkdFBYZTc7Pz1EOD9xl4xrd3yYfo6eVKSAXZaolpqJhpFma3Rea3dqYapZjpuVZWNgXVuopJqIgVA/aTg4PTk/PDU3ZDpEVI9JdVZiODw6bHEZCQ9PhWJsfY2Okp9YYGRYX4B3TmpxWndyWGFqXHNqXkZ9cYSYcrSUeFBoZ3BakVZrVIGVlpOOi4B7e354gm5XTUlTXm1RZm6DhXtmdEhMRGRqSGdAPotfcG1kbWd+i7Bcg1Vf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5ul8fn6BiZZ/nZuUgIKFhoZ64+Dfb21tcHR9fX98cGttbW9rbm5sb3Zzb2peXmC4XV9jZGJwcHFxam1ubWtucHN1gIOEg4aJioqIkpWVe59/fHqAf4GCfHVzdnp8fo1+nJubnpifoKChmpyem52lpqbwjo6Kgnp/h398gn9+eG9pra+etEd7e31+gYOFhZmFpKi0wcbM1NntgoSA/P/snHuwbHCSuMzhipzagoudl7W/ubSwnJCWmon7yZb25tDW+YL28r+zuJ2po7rVuMK2vLvJ1b2AtJ+etcHMh56gv8Gzn46SqImB9fzX2tjVz7axnLC5ubanqLS19rH/9N/f0dzP8Nrawquch4H+9O2ampOIjZeQlomPqMq4s7rd0r6pq6qlnqv5l6WgpKO4usns8YaSkJ+dqrCara+turzJ1tPVz9rh7L+9vcLS2OaCg/qF/IaBhfmA7N/g6fKAkIuTjo6OkY+DhoD3+YuQppugk6GZppaRiJGZjZykoI+Pk5D/gYHp3OjkzcbVusjHwq+N+oLwxJjKwMCLtsrfgpyin6Kjkpmsq6zG4ezr7/PkxrKtp56io6yux/CPlY+Sl5yQi6i93vOBiI6SpuKTsY/G3dtx98jHy8yAt/usxrK7pIzPp62pspOXwsC6qsXFw7GwjeOl/euNoaKL3ev3gpGbg5yImMn05Z+yvfvd+/zprMm2usK70czZwqWy5Z2hoZ+R/IHbtI+Sj4uGiPz13Nrb5YqXnbS00NbZ4bKeqMvigY7g1uCGjoqpkuDPpOHNt9DW2uuDjZKR3+iAz6aLxNTUtqemg3JTREFml3SEnqdi5Jik4Imwu8C9lPiQrIja5t7TwK+hmYeA74bv+Pn/4IeWn6yxtZGKsquSi5Own5Knp+WB2877oqCWxeeQ7sbS/8HHudm2ioP1g4H2kvuNg9Przsi9tKyYiZaKjsOmqoKenJWLi4+Yo6SjoJyAZ2NgW1afl1FbZ3R9fn18fH16dXV3enp5eXl3dnZ0c3Fwb2xmY11uUZaSdIKWV1VHRT1YRGivvsS0a3FddoNtd5hvcXBvcXJzc3NycnBwbmB+Xp51aW9yYWh6jFJvhpKEdHp9f4WMg4GaVltXh4V4ZWxviXaHamZhbXWRVV2ZjKKAj35Yhn57Y2ZkZW2CgoF5foSGfWBfhHK2c3FxaXt8eXBwcW9paG5rYlxshYtyaF5rhGpjtpWWqaKHZ51Sl05QdrKw36upj6huhnRgdF1IYFiGgXpqV35fXXxoe5pogkpTYbaRapJbW122rqanoq3AZ2qvqay6ZWlqeZSUgZ2IjIyAgn2Chbtqc3uCjYholoVXh5+yppuOfnl6gJCera+vrJyTj4h9d3qCi4mIhm+xqa5fYGJka3hkfn12YmRpbXFgpaSlU1RYXGNwcHFxYlNWWFhZXV1cY2ppZ2RSUVCYTlFSUVZqaWlrX19eW1teX2FkdHR2c3V1dXZ0f4CBao5zcmuAaWlsaGBkaGxtb3togIGAgHt8fHx/eHV2dHZ+gYK+c3Vzb2xucmpma2pnY11Xj499jkqAent9fn+BgZF8m56qub3Cyc7dd3l15+nRh4782bSDTkVNJDFMNDYzLDM7PUFNWFZcWkmVk1ubkoOHm1CZm3hMTkVHRkxSSE9KS0pNU02ASUJBSEpNNj8+R0lFQTo7Qjc3aoKam5mZlolYR2iEhYJ6fIKMxY3NxbnGq7Klu7Grno6Fc2vd0cFQUUlESUlBSUZMVGpcWl1na2RiYmVgUlnFfYWCj4uSj57CxWhvb3l8gYZ6gIeFioyXpaOinqmqsZmVj5ijp7JjYr5kvWZiY7qAsKitsLdgbWhsZmdna2lhY2C6vmZqenN0a3VweG9qZWluaG93c2dobGm5XV2tn6mnmZaci5OQi4Ntx2m8lXefZHhrjZq0anp8e3t3a252eH+Vqayts7WlkoOAenJ1dXt+i6Vja2ltdHNpZnKElKNWWl9jbY1YbFmAmJpcmnl3enyAc7eAjJKBcWGSbHFxhIiMhl9dVV5dXFdUQ2tYiWhFYWJVVFZhNTo+OT84Pm6VjGhzd5WQqKuccoR4e4F8koqRfGhyqXVycW9krVaKc2NkYl9cXqukk5OTkElQVF5kcneAhWhXYHWFRkp3VFw5PDtYIQUKMYl8b4eJh49TW11bjpOAgW5TfoyIdGxpU2JQTVJkiGt4f45hwYByilZweHt3W5VXZ1WIko6Hem5mYFhOmE6IkJSSilNcYmxwcVhMS0lAPj9MRT1HXYVLgXaTaHt2kHhRinBphWBiW29bQjNbMjFcN1syL05aT01HRUNCPDg2PWdydVtubGVcWl9mcHJwbWqAa2djXlWTej5LW257fHx8fX57eXl7fX59fXx7e3t4dXNybmpkYFhU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qamqp6uur7CbmJmanZukpaPIkpGRj5SQjIWHjYyFf3962uOsr2Z5f36AgoKEh4ZgmKaqtL7DyNDY8oWEgf7MoLa0w9f8/4mXi5eK9oD7/vju6+Xa0MrBvLSalZSP69f3gPLyz7e/msimvde0wLLAwczVwIC3m5q7wMqFmae+vq2dkJWghYP2g9/r7fT+5/HLwszGubPzy4ifi56omIiKj/fMw6ujl6Wpmov+8p2YlJGPk4yNi5ms1MK4u9bMvaunqZ+XooqblrCtxMDe+4WTjY2JgIeakJuoqqa1xci+1OHkz+Htz8DM2ODsgIiCg4SJhYaF7IDf8oDvhIqRi5iWlJCElon7ifmDhJWdmJyqq5qSnaKMoJujjJOTj42Lk46A/oDy4drhzcvTydDQw8fEwanct7WpmZSLubqzvMS7rrCvtcXi8u7zgPnSwLi+zNC3op+cmqizv8HO8oeSm6mzvMTW84WJjZCTnKKm5cOtpO2ltMHFy4Ct7OblvOfassycoZrjiqSPnu+gur+7tKiM3ajw7+2Oo6CI3uv/i5iIl4aTyPToo7i9gcXGkNrMwr22xtLf0NSIr8+PpKWlpJqDh9vHlZKPioiL8ujv8cnj4fOEheaNjI+Yh4j8+6CFl+mCqdz23Iyhm8q3ybmAjY+jvNDi47S3sYCNhsra3N7n1JmqwaKC5PCNoa7DsLj3odSnxN3f4uTn0I/3qra/rMnSzMG5qqyjn42PnNyMmJK2q8LRi/u1pZeJlbSjobTkrKaFmu7UmIqx043agP+V8/Xd/9OXhP7/+uuD6IL7zObPzcu3sZiFkoSLkpWbp6OjgZ6inpaNjJKbpENwb21qZmJeWlail5ZTXmt3fHt3dnd6enp5eHd2dXRycXBua2djXnRfgY14gphcWUlGRGpQfMT+62BucV18epKjpm9whHGAcnNzc3JxcGtnm2CObm5pY2Cwo3yAgIOKn15rdWp7fXR5naFSn3ZzYmpqbY91j5aVmJqUUldbV1FXUo+alJeUjJqeno+Zm5qVdHRylWB/i3ivZGNkZ3eEfHpzd3h2bFlVUE5sgYN+a4uXuZuJZrBhZqCMhoZ9rF91erVZ6rJ0jH2AYGedfFNfUnxcoqtbaX6Ji4FvXIppX3dcd5tlkVJjaV2UcpGwmYibYWpruqtlcXp5dWidl5aVk5KRjpCPp4B4d4CKVk94b0ymkalugIuaqKigl52gnZ2cnaCmp6KmqaKdXYmJiYuBZKKmqKdge32CaX53dG1mZmFbVVhlaGRhX3OAd3t9fn18eGKkUlOgm1laXWNoZ2dlYmpqaWdqa2poaWxubGlvcHNuZ2Zpa2Rtb3F2fHZdh3d1eXlzc3N2c5CNkZKXg3yGfo2NiIiLjo6Ndm1ub3Fudnh4nXl3d3Z4dW9mbnRzcGtkYKmygYNlfoB9fX1+gIF/Wo6bn6y1ucDGzd6AeXl24bGDydHIp6umXlBMUEyFRo6ao6qvr6qhmI+Fe2ZeXluRhZtPmZyAS1BFVUdMU0dNR0xLTlNNSkBAS0tONz9ASUdDQTw8QTg4a0SgpKextqGak5CSkIuIv5lpem19g3tvb3LHrKaOjYGJjoB118xVTkhISUhESkhPVGZeWVmAa2xpX1piW1Fad4V+lZSknLDNbndxbW1lbHx0fYWFg4yYlZCeqKihq7WklZ2eqrZhZmBnZWZlZmW3qrFds2Nob2VtbGxoZW5luGW4YmFscm9yfX5za3J1Z3JudWVpamVoZWhkXLpesKKhpJiYmpiZlZOQjo1/poiHgnlxa4mPh4+AlZOGgH2Ai6Cvr7NbtJyOhYqZmYRzcHFxeX2BhZavYmpweYGFipOlWVtfY2NlaG2chXh4pmNrcnd9aqe1pJihl32UaGljjl6Hg36QU1tdW1dPQGpbhGpmVGBhRVNcaTo+Nz44PW2Yj2h1dUp4e2CTiIB8eYWPnpCUV3CDY3N0c3KAalhZjIhnZWJfXWCjnKGkhW1VWzAyWjUzNzsyM19kRkZOfUFad4F5TyEEEFhyc1BYWGR1h5WSc3VzVE6DjpGPlYpibX5tWKC/b3mMlIB7o2aLa3uOkJSVlYNYk2RxdWZ4hYJ6cmdtaWVWVmGLXmBac259h1iJTUlCO0BPSERMgmM5Xk9bi3paW4J3T35HglB8e3CAaUczXmFjVzJTL1tMV05NTEREQTs4NDY8QEVOam9YbnBsZl1eYmpxgHd0cW9raGRfWJiCd0NPY3R8e3h5e35+fXx7e3t5d3NycGxoZF9Zaj1FSj5BTDIrIyIgLCRUh5KZRVNXSlhXZJCgc3RzcnR1dnV0c3Jwb2tnj0l9cHFmWlWZglZXUVNZYTc+Qzc9PDc9WlovWDk1LUxOPEtIUFNTVVhVLzMzMS4ygC9TW1ddV1Nd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scLYu8C2vcbMz8aAvZ2gvcPMiaGrvbWmoY+Rn4iF8YqA4rH7lZfnycXR3tWSnrW7trOhjoKDguHOraSqoK+mk5OMgPSlgd/yh42LioaWrNXEvLjMzL+moa2llJ6UhYeWl53SkouDgY+N+IeTjIueqK+xwL27zNDLxcjE1MLq6/rr+vn/84GJiP/3hfaA+I+LjoKSi46UlZGUj4yPhp6WnY+UnpeSmpydk5KlnpKVj4uHk5mPlI6LiYKJ4dzl5tTMy8Lq08bGruvCv8HArqDdmqOllob+7/ns2/KAhY6HioyJ/+v5g4SG+eHSs6Ousbm4vcPH1ISarbvK2Of8hIOHj5OaiNP2iomD3p3BytCAoN3Ai9mKiYLA+7e065qf4YaNqp27ubKoidex7O/r8KGdxtXhgYiVh5uFkMX98KKxtYLgzcvR0cG6udrz8Yv329CHnaaoqaeijI7CmJmWkoyLge78gd3LztvzgoHehYWLmYeO/oOnjKLz2fH0k5PPv+6x6YKKlaW0srbH0o2hnfKA6rvh94CCjJCA4Mi9vreak6GwzM/Ozsm/ydXw9PT18/Llq+6An77EgPbb0by4vLOjo7SMkJOhteWEgZPjraidkpa4sbPG/7u+xMCJmoWIoMCwsZSVpJKOgYrnn4SD//Pk/t/y6bzWvL68t7CZ/ImBhY2UnKXtgpfmx8GnqqaflYyAX2Rscm9tamdkYV5aVqSZj5dTXmh1ent6eXh3dnV0cnFxbmtmY16ghYpLhYGQWVpHRUaE1n+hrrtrdXZcaKi7YqVvcHFwcHFzc3JzcnFwa2hbbnBjZGVscHV5iH93bWKgkZSIh4h8i15wgZKHeXtwZ2lTb2d/mk6bk318jkpQW12AWph3f4KAjVJTo5acpqJShYB1aGGOhW+taW9vcnSEg3l3aWRgXFNcW1RXeXdyfXyEs2t9gVNii0hLSIZIRUZxfWp4l4xrZpFxTl1WgV+wZ2NbVJuYm6tbYnGBikaFeWGPalp5Y4hack9dcnhtoHOSa3NnnmetjpWdnJmcoJydmJ+AnZ2id2FLa0RIRjEva6Z+qnaKm6mqr2Vyg5CgqK2pqKOXhHdzdXt/eoZ/hIWCd25nbGpkXGJzhFhrcGxxdnh3c3FxcGZkZWhqaGhsa25sZmVkWZqYl5GaZGlqbW9tbm5rc3R0cnB0dHRtZmprbmBXWlxeZmhraFxTe3VxfX14eXaAdXVzcXJ2dYticnh6dnRvbmp4j4+PjpKXmJqMkZOTiYJmYmBienh5dXiJhn95cGZgcIiHfm+DfHt7fX94gIZwlpmgs72/wsXL4Hp2cLfDw8DCx8G6sqmgm5KKg3p0bWZjYlpSloRwXlp7elWElZiQlYNOU0VaR0xWSktIS0xNUU2ASUBCTE1POEJBSUZBQjo6QTk6bkheo4O9cnuxm56isqRzeo2Sjo+Dc2prasCml4+RiZOMgXt7cNR6UXZ9R0hFSUhMUmRdW1lnaWVeXWBaTFl1dHN+fn6rcmxqa3Zzym1ycWx5gIeKko6KmJuZmJqZo5mys7qzu8K8uWNqZby3ZbmAvWpma2NuaGpsbGtqZ2hqY3RvdGtscm1rcXJzbGt6dGprZ2Vka29na2dlYl5kpZ6lppyZlo6qm5OQfbGTkZCNiICnc3h7b2PEu7irn61bXmViZGVku6mvXGFjsp2TfHR5eoB/hYmMmV1teYGJk5yrWVldYGFlWpmvYG1RhV10e4CAY5uSbqthXVqLrnd0k1thn3WAd1NcWlVOQGlhgWxoiWFecU9WNDk8OD44PW2fmGlzdkyQh4aJiH16eo2cqF2zjYFPb3Z2dnVwXl2AaWpnY19fWKOvWZWEYE9XLzBUMTI1Oi8yXjJHS1SDWmVrPj1hYHJhj1FVXGpzbXB8h1lmZZiAi3mUoFRWXF9TkYB5d3JhXWdxhIKFhIF7g4maoJ+hoaCSao9NX3R7UJqJgXR0eXJjZXBXXFpkcZNUT1x4SklFQEBRT0tUmnFxdnRSXlFUYnVnZVNPXk9MREt0SzMxY2JVY1BaVkhSSklFRERCeDgzNjs/SFWISVqNjIl1d3RuZV2AXGJtd3VzcW1qaGRgW6ONdnJDUWJ0fX19fHt7enh2dHFwa2hkXliZaEInR0hQMisjIyJImVZvc3VQWlxKT4mnXqBxc3Nyc3R0dHNzcXBuaWZUVFhhZGFjZ21ygntwY1N8ZmFXU1NLUDdCS1ZKOz05TE4pNDxEVixXVD07RCMsNjeANls6QURDUjM0aGBjamk1Uk5FU09XSj18PkJER0pYV09NQj46ODE4NzQ+gHVpiI+lzo2gtoew/4aNgviEhI3fzJ2kcGZWZG8/HyMkR0/Bi5ubjuzOsrxhXVZPRh44NjJYTFR3aZqLx4ayzL2h9qPi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gpfV9YDf2snI6P+IgouGkJiam53Gv73KydjG2OnZzt7d5+b7h4CBkY2UgoWJ+++F/veFmZyQmJedjo2fk42JkJONl56voqidoqCsq5GakY2Og4L8k4eJg4uHiYiB7M3ex9vX5dOeg9jX1cy4s9yLiZShpqimmpKRioiNnJyVm5+bmp2gnICZkpWWk5KRgtu5uMC7zc3S4ueCnrzW7Off2MvmhZjRj4Dod/nx8pfvmojFos/xkJWe1t6sw4GZmJqMo5bD5aOsoYTXvOrx7sacnITU1PaLkoiVh5PAhoSmrreI0J6J6v/grK3Q74mF3e3m1LGxsrKwrqGd+6GemJOO//SCh+vcpYDw1/H3+N+C8oaSgof5hqKQov3S3e2LkLmHsaPgjJqjrbnH1d/JopyAxoi/3Ojy+YGK/uTc1tnXzMzR1OHh3t3P0M7U3vPy/fz39N26/eORoPja4dbEvsO0q6/GzMGpzdiojJCl1LOpoJmcwL+/1pLa6+nTxcSavKie65Cgm7emmymLmvusjfz47tvy1e3nutm8ube1qpL6jIeNk5aaooqjn47ch6pw7aO9rIBybGJbXWJrb21qZ2ZjYV5bsaabk46aVmJsdnd2dXVzcXFuamZkYFSQUFWMhZBbVkWCp9rKY2pptnd9bpmoaHFgp29wcXFxcnJzc3JycXBsamaQa49hZ21ydnd6fH19fIeIfHVtsouPiIGGfoRRY21oaFV4bYtPUlOSiIqLiFBaWoBYm4SLi4iLnZmXlJlSVVaeU4hnY5yAcLJ0jJCLinpybGVXVVdZVl1ucHmAd3p6e3idfph6sHtTWmFeTUtDO2Guqnhgj3BLXFaKY15pa3h3cWhrYrOZj46NoKqopFlsfUVIeGtUe19YgHOTYD9XQZKUprKlknuAloR7mJpulXNzqYBdaJh4b1pAS2BpY2jAoMZtaX+GkZOXnZmXmZ9ZaXaDkaGbinZxb25tdpGDhoWGe32Cg4mNj46RkZWwaWVmeoN9enhybmxoYF5fYF1ZZm1ubm5hX1xZWllWVVNmcnJ1dXN2d3VwdXh6e2lhYGFfXmNmZnCEcVeBc3d1dXV4eXd5eICAgIF9fpJzkJaZmp6fb2hpa29zdnJweJCPjpCYoKWkhrqiqKhfZF5csX2MiYR3aGW2bnp3mVaFe3p8a4txgYCfgpaXorK4u8DDxcqzp5eNh4B5cW5sa2dfV52OfW1dn4JrZWRjZWVjUoaKXIGJi4eLi05QSlxETVVKTUZISk1QS4BJQEJNSk46QUFHR0FCOjtBPDtzhIV+fHVxbbqpfL6ZmqKnl6KIgna1raWboYSKk5OLkYyLkH9+fLu7t5p8aJd7jUtQYmBbVWVlZF9aW1N7ZKu/yW57qsa8sqOlv9JuZmlocHV1dX2Vk4+emaWXpLWooaqprK3BZl9eaGluYmNnwIC5Yb3DZnF1bG9tcGVkc2xmY2ttaW9zfXZ6cnZ0enxobmpmZ2FeuWhjYl5kYGNhW6qXopOinqWadWKjoaCajYejZGRsc3d2dW9samZkZnFvamxvcHFzc3BuaW1saGhnWpWAg4qHj5Sbn6RZb4SRnpyWkYaXWGePZluiW5+cmWCXYoBYiHWtvWVlapeZcn9SW1ldXIV+pIhVU0o+aWh+bWptXl1OUFJiODs4Pjo9a1VTanJ1UI9zbZqijnBwh6JlZKOVjYd8fH18endtZqtvbWhlYbCqWl6fl2ZXSFVbXFAwWzM4Ky9eNEZPV4pZYGk5OldPZl6JV2NmbHJ7h4+BZmNOdYBVe46WoaVWXamVjYqJioSEiomTj4+OhYaHi5KjoKmppKGRd5mHVmGXiIyFend+c2lteX56a4SKalVUX3VLSERCQ1NUUVpahZWTfnR5YHZmYZJXXlZpWlZPVn1QNmNjXlRhUldVRVNJRURDQkB6Q0lTWFlVT05cY1iDW3WC+4yLd4B1cGNaXGBrdnRxb29raGVht6mVgG51Rlhodnp5dnVzcXBrZ2NeWk1zJSpMSU8yLCRMeJyJS0xDbFxkWYeMXG9bn3Fyc3N0dHR1dHNxb2xoZmBqVYhdYGNob3N3e3x9foWDdGhZjWdkWFJWT1EwOj5MTi05QVAtLzBTRERDQC02NYA0WkRISUhRYV9eW2E0ODlqOF1WVmNMQoNHXWBdXlNNR0I3MzQ1NDpLUWmAdnJ2eHKTtsuE0baWtczau7iegY2KfWRmdkQfISJKT4XDyu7gwa2sjfCunpubuM2+sFNOSiIeMTIuTUlWjYvApIC0gebL0Ma0oZSQjIeOoGpLcmtWbIBAWaWmtqmW1PTjmIf8tumRj6+9y8/W3tnZ3+yFmKWzw9jQtI+MiYeHm86/xcPDs7K5vMTO09fe3+H8lI6Xw9nPy8fAt7WunZqdn5yb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7e7t5d7QyeaZpqSmpKSpo6yqopqbp+W5qM+XjoeBjaGkpKilm4uLjo+RjILnz8/K0/KDiY+RoMXbz7m5vb7DiMnp0eeUlJWYn6SAmqGNrtDBorLo34q8jKyYk5ehwpLnnuyNgeDW/ezxmpSbnN/N64iTh5CHk8OorK6st6bTq5v87s+prv7xjayd6vG2vsDAwLy5tpOmpaGalYH7hIj065ui3Mvl7/HegvSJj/+D+YGhn6aC19/qiYqm66f435CZo6q3wcXOvoL7tMCAoLTDzdjj7PDi29HP0MzU3uPl29zb3dXQz8vY5u/u9u/r2Ma199WFkLLDzMC7wcG4uMLRr723vuLcm7zSs6yfoKbHwsrimOnr8+XR1efXl+CL+o2cq7allqb6sIyAg/HS9svq26jJtreyp6ON4IqC/42Sn6GG7efojc2giYi11o9JbWx1cm5nX1lcYWhubGlnZGJgXlpYqaKZj4mUU1xmbnFwbmtoZGFXk5FTlIqQYWmwe76Xdl1Wj2dcWKSSbHJrX6hucHFxcXJzc4RygHBtbWdrZ3xfZ25zdnh6e3x9fHx9fX5+iI2Ae2+ke3VlZF9qZWNbWHiKUVKcUlNVU1RdXV1UUVJQUpeamZyahpKZj4lUeGlkYZyHx5GOkoqBb1pWVVVUYWFlZm14hYqFcWtva5d8l4WAbFNOUE1BX6eaemmPeFVkWYtmZnBGSUM7gDx/gIJ0YauGeHp9goSQnqSoqaWkWWhweX54ak9urqxjYE1ed4lSnXGeWWd4s6CelKdvn5F2bmh6cmmDaoO5r2p+m52Yj4V1e4mTl4+HipGRk5GXn6xcurtiZWptbK+Ujo6MgGWvt2Rrf4yIioWNYYWWk4h3d3h6e2dYWFteX2JoPmlpd3t6endweHh3dnh8e315d3l6fHZ2eHd7c3JzdHVxfH5vXph6eoF8f3+EfWWkpa6zqqqopnZzk5eak6OjhKGAoKKkdHBtbG5xcnZ5fqanpKVvaW13fXh6en1+aqycj4R4cnVwfneFc4J9e2mNYX+ChIFgjJKUmJeRi4N407KlnI17bLqhh3dzcG1tbG1tbWxsa2pqaGloY5aEiFyBhIaChpNOUE1YR05VTUxJTEZLUU1LQkNOTE45QkNHR0FDPDqAPjs9d4WBgYV/eJRijp6tab+unX2Ldbu+maWjnqWlo6metaKVmZWUjIzNvcLL09DMvJl9Z1NPV1ZiYWBZT2J7j59sdXqfu7ajsrGtrKezbGfBaG1vd32BjpOfk6GeqrOXopycq6SqrWdiYmJqZmtovWS6XWfBaGl7bW9saWtrdGiAbGVoZmNpaHt1cnhvenJzdGphYV68ul9nt7RjZWGvr6SxmqavoJVrs7GxrqidlqlsdnZ5eXh7c3t9d3FzdZVyaIVmYl5XZnN1dHV1b2hlZWZnZFyllJWVm6lbX2VocIOTjn14eoKIZI6kkKZkZGVmZ2tnc2eEoYhtdqWaXXpZZFqAWVtgm3W/eYhDPmh7jGtqUVpeXl9TYDg8OD07PWxlaHBwcWKJdmunm4hzdbCvaHxqkpR8iIqIh4SCfmd0c29qZlivXmCpomI7UUNRWVxOL1k0NlYuXjVHVllGW2BjOThPlGiXiVpiaGtzd3yDeVShb3VpdYCIkZmfpJiSi4uKhopxkJWYlJORkouLiIWQmqGdpqCfj4N5mn9TW297gHd0fX13c3aBbHVwdYyIXG5xS0hEQ0dWVllhXJCSmo6BgpKGWoZRklRdamhaUlt+UzYuMV1QXVBZVEBPRURDQD8/fkY7bTo7REhMhpWRVn2BjpaXnm5gOWN5d3NsYFdZXmhzc3FvbWtnZGJguKeTe21zQ1Bda29ta2diXltRdkQpTUhINDh4ZZFpYE9IbD9MTJCEZHBpWZ9wcXJzc3N0dHNycW9sZ2dhVk5uWF9lam90d3p8fX58hHuAgYNzb2CBXFA9OjdNTUE4O0hQLzBcMDAxMTQ6Ojo1MjMwMl5gX2JgUlZZVFI5U1pWQ2pdmWVkZV9ZTDg0NDQ2QEJFRU9ljJ+kk46He6Co06Gek6WesbeQwsB5anKJTiMiIUNMgMKRr6aFh/7g0bCK5JuDh4WIjqCxuMHFuqtRSTqAMzQ0MylJlsWLtJCUiX1CZ0lfLzZQlJ+irbJuq76FlXdwX2ucfJLWr2aO0NbQyLqhq8DT287DyNPR19bg7vqA/vN+gYCGjPrf09HNuZL0+YqXutLLz87Xks3q6tm6vMTN1LCNjpWcn6e0ubze6efk3dDa2trb3unl5dzW1dXaztCA09DSxMK/uLWuu7+oj+O5use9u7nDt4zY3u/r4uHb252dzM/Sx93d2NXR0c/R0o6Df36AhYWJj5vU1dHSjpCYpbCsrq2ztJXq2MWznZCRipKGlbfk3dqxpKne4N/WjsHGxMO8s6idkPvXwqybi3rbwamal5ORj42PjYuIhoWCfnuAe3lxjGduS211fHyAdikpJiwkJiUjJCEgHR4iHx8cGhwaGxUXFhYWEBAODhERFCg4PDo6ODpIL0dJVzdcTls/QzReXVNgXlNhZGJmXmhbXl9WWlhWhICGi5OUmY10YEg8ODo9PjUyKSw0MEtXPD9GVVtYVFZaUVFMVDExVi0xLzWAMzk6OT4zPT1DRjY5OztAPj5EKCMkICQhJypBI0QkJUgmJS8pKCMhIyotJSUnJSclKCYyLiYtJy4sLy8pJiQkRk4mLEtIKSglRkJGSTxKVElCK0ZHRUZFQ01fQEZJSklKTUlRUU1KS09XNzNLQDw4N0NPUE5OS0lFQkRHR0M8Y1mAWFleZjU4OzxBT1ZPSUVDQ0hWcX2v0jk3NTU3OjlDPltxXD1Aa2g+PisuJiQkKD82VEE5GRcpOjwsKis2NjkzJi0aHRsdHh9HOThBTVM6SUdLVU9FOj1pimV3Z3VzboGEgoF+e3ZicW9raGVcvWZptKhZEhkVGx4gFg8fEA8aDx+AERwvMCchJysaFSR7ZI+EWF1dWmFjZW1sTJJGP0JKU1ZZXmFjWVhQUFBOVFdcWlpbVlpTVVFPW11kX2diYFdPS1hHPkhVXmFZWGNlXldXXUxTTEtYTjE6OhscFxUTFBQSESA0NTgxKSkxKxwpFisgOD85LCgtOiUTDxIdGR0ZHh4cFx4dGRoYGCZaIRYkFBQWFw8cKCYgPSMwPi0jJQGAlH+Gfox/FX18fHt7e3x8e3x7fH5/f359fn9+foR/AX6QfwN+fX6Vf4Z+Cn19fn9+f3+AgH+NgIp/CIB/fX1/f399mH8Bfop/AX6FfQR+f39/hH6Ff4V+jn2IfgV9fHx9foR/B4CAgYGDg4KFgQSCgYCAhH8BfoV9ln4Df35+hX+HfoJ9iH62f4p+iH0Bfp1/in6IfQR8fHx+hH8Be4V/ioCHf5V+BH1+fn6GfaF8iHuEfAF9hnyTe5h6jXsDfHx7lnyIfQZ8fXx9fXyffQl8fH19fHx9fX2KfIh7j3yCfZN8hnuOfAR9fn18h32DfIR9BXx8fX19iHwQe3t8fHt8fHt7fH19fXt7e4d8CX19fHx8fX5/gYd+Bn9/fnx8fo5/Bn5/f35+fYd7D3x7fHx7fHt8fH19fXt7e4R8A319foh/BX5+fX19pH4FfX1+fn6Lf4R+A3+Af4p8ioAPf4B/f359fn5+fX19fHx8jnsEent7eoR7DH59fn59fYGCgYGAgQICBACA09jYjNyZpaGdlo2Ih4uVnZ6ZlJCOjYmGg//37uTg3OHtgImTlpOH5daH8/a6mZen9ZL5woyM5ITq7/H4mpmRhvWioqSlqamoqampq6uloqOg46bElKOtsrS2uLi4ubq7vLy9vbu6u7q5ysWvoo20xKHMoK3d19+FmL/fhIqKko+AjoSIiomI+f+AgP7n+/zw6vCjnZuz7NX75c7Iura0kZGSlZeisK+sq6ivrq2mjImIvXedn5+ikVWB+uS1k7ixi66r/6n7eX92cHZ6gHdzOjtpcHt99e/k1MvT09nv+oCEhoDxq6TdlbXvi4fu1qWCkIyIcGhxiJ/1pdq5qpuTl5uAm6Cmu7Gmxe2Flq+ji/XtgY2FgH59e36EiZuhqpiEfYWToJ+Xo6SptMl7ln/Kxry+saKZmKq0r6mop5+ak757hpSRj4yKh4J/fISBfXx5g4eGhoeGhoaFfnJ0dXp7eX+Ch4aOkJKUkoGEgXrYspNx03GOj4iUjY6Qj5SQkZCQkJ2ApfGryrS5u7u4v76+vrzV1dTTztDS0tXM3uDk4+Pi4ODdhMPCwLq2ura0rYiDioiCiJiTffC7u6FyhnezZoSFiIuKaNZ45NDExMG7u7i0tba1s7CsqaakoJ2al5mXlpaWlJSTkpGQitzZ3pXKxsfCzoHBwcHZqM7awr67tam/zcCAspmey8W/hp2purukpIyKlIuKgf/nnKjWxoSHgoyLhIHOrpaXmpCB8sff1t3k4d3fvse5xt3F2uXPjfPz6ODU0d/sgYSC6cS5o6G33+Tc1NuJv+yB3dLaw8fHwMHg/oOVj52QlqSjm6zHxs/M3OHRx9jb6PCIi/2Ak5WOkJChkoyAjYuOhYqamISLlZiZkJ2im4aUgZCIkZeMkpubkYiDgIz9h5CLjYKHg/bv+oWC9ODx9NK3he7p7+ro1Iu2pbG7wb+usbu9ramgpKG8rM3V2d211JegpKquqqm2uqmkpaGcnKOonIvz94OWnZiZn6GoudTr8ODFnY3ZuvWhoqKgoaiAoKWJvpeIv7GV9O6cg7Oqm5qilZDEkpya1NfV5O3t9o+VmJjL1YWRh5iMjcTP5L2otta208Krq6v08PWEjq6Q4Pib3t/c2dHMxrmwq6aYgoGJivvuhp+lzMHe8fHV/O6JioOC/oeroq2H3eTmhYekvPiOm6CcoKa4vsOO3PDIoNWAlKKvtbvG1dfUzsvI0dXY2dfb2tXPysjGzcbF0+bx5ejg38e2sfbP+IyZq9DGua+qvLq0uKjfz8zjubDXtq6dnqnLx+D1l93m8PPn3uj07uzRvovC2aOCgJjrq4f7+ebP47vfz6G0qrG0s7aV9ISBhJCUoKaEoLSSwLeGqND18IlHsXyBYpprc3BtaF9YWV5lbGxqZ2RiYF5cWa2noJWNhImXUlpgZGFYkHtPl5dyY2yL1F6Kd2Bgm2mnp6yubWxmXqltb3FwcXKFcxByb21uao5jgmBocHR2eHp6hnyAfX18e3t7eoeFfHRleXplfmB0ko6SVWN7jFNYWl9eXlZZWllZoaFQUZ+QnJmOiJlqZ2V2mI3DkYaBd3V1WVNTWFxrdHVzc3SHhoR/ZF1ek2yUiYiLjGJ6vJd7Z4ZqS1xclWm2aX98doGCgnF2Q0Ntamdjtq+hkoeLio6hq1dZWleApHBxll9pgUxJf3hgT2ZnaVhUX252p3eqkIB1cHF0dXV1f3huh6FaY3NzZrqyYGtjYF1eXmJpb3uBiX1sZmt4hYeAipCUlp5kemeko5qXjn11eIyYko+Pj4mEe6BoZ3Fta2ppaWVkZ3NxbW1rdXt9fX9+fH16cWJkZGhpaHF0eHiAgIKDg39kZWNanoh0W6ZcfoB4hH59fn2AeXZ4eXh9g76FoISHhIeEjIqKi4yppqSjn6CjpqafsrCysK6urKynZ6Wnop2TlpWTjWplaWpocXx3ZbyWlZJ0hXGIX36AgoOBXKxhs6Kal5eUkpCOjo2Mi4iEgX56eHZzcXBubW1ta2qAaWlqaGOPhIhcgoKDf4lQUE9PVkVQVU1NTExFS09MSEFBT0tKOkBESEhAQjo5PDw+PZKzeISjnGdlZGtqZGCggXuAfHdnx6Wur7e/tbe8oq6krLuossCodcvV1Me9ucHGaWpkrI54W2yKqKyonqZwkrJnsayro6OloJ2uwGd4cnuAc3Z+e3WImpmbk5+roZudo6uvY2e3XmxuaWlodGxmZWVnZGdzcWNnbnBxanN2cmJtXGhma29jZ3BxaWNeXmi4ZGdmZ15eX7awt2Bd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paOmpJW5x5amrrK0tra2t7i7u7q7u7y7u7q5uLm5ube50qCow66jxry+utLV2+HqlLPSgO/7homJh/WAgYCD8/z8+/XpjqGgveOmxby8u763u7arrbGzq6euqqqrpKqoqKqWndF8lpiR5azkuIq3roSrpfy3lFtNkJOQjEJAPj0+ODY3bWtm09rqgoWDgYH9+IGHiImOjba98oPfltGZgeiWo6yypI7EloeF+O+Ejp/H3O6BgIeKjJaU/N3Avbi3uKubkouJiI+js6eYgX6Yq7SomZOLgXx3dXSGnaOoraqin6eqrKiXq7rBw7issK6tq6CanZekaHmOlZqXkp2enJuWlZKPjYSGh4aHiH17enl7gH+Eg4GYpKWek4J0Y6OXg4aEhISOl5eWmIyLkJmfoKOmsNyagMnU19rK0M/Ny7W7u769yMrIycXS2djZ19zd39/bvMPCwbjJrKenrKykoJ2alaOjoJqbjoaAj7i021qIZ3GChIeIirONbGtqamlnZMjFwr65tbOyrqqnpKKgnZiZmJaVlZaWlJOSkojS0NOQvbu6t8mIur7G1afJ1b3Au7mmvMXDgLGgnsa/uYahqbazoamKipSJiYaRheyAg4X8m4OfspKV9re0lYWN/Oj+6u/2jvv+9Ovy9sfp9o+WjIuOqoDMx9Hq+4WFjZOWmJL29fDdz4G9hZSsmpCD6uvq2N6Aio6Ti56an7Wqnqm9ybjDyNDN0/v7h/v5koSKjY2RjYaB+oiEgJqen42RpIqPi4yJjpSZmZCImpGPh4KXlI+NlpmcjYuSkIqHgIuCgIqOiYiKg4ToxoyDgv/p7+bzlsHK0czH0sa8tq2us7m4sbSihZaT/ufs7e7Lkt/h07+1wMXBwb6mnrTGzdDGwKuUio+ZoKuusr3Gz8nm0r2zzLiBqa2pqaijgJ6noLCKvIHQt5j5gdm2ubakpJmZmomtjI+OuMXn6+WJkJPDx9OCjIWijInI8/vOqa6Cna6uq/KAh6GN9+/r0NinhJGRi4Tu4NjDubOckOGLioLw4qKipsm3zu/pzvTohIaFg/eJra7BrsTr5YOIpd6UlZWaqKywt7m4s9bZoZXsgImWoqy1xsjMx8LDxMbQ7o6dq5TRxcnHv8PPzdnOvsKh8+DUxLm8ieeEk5Wiwb+/tLKxv7vJt8a0xNumiYCI/t33lqGG9pzf4+bv6+Hh5+v0hom+t+CliePurJiAgO/f1+rX4smqpp+ZmYmJgfiAgIKLjpiot5eDoLPFve3gg/eVXsB9U5RhVId8oWxycGxmXldYXGFqbGpnZWNhXltYq6Wel4+Fe3V+f3WDWVB8bHmDmExlhXJfYJvKZWZopmxqZmCwbW9wcXJycnN0c3Jybm5vbWFugWJrcnV2eHl6e3uIfIB7e3p6e3l3eJBlaHx5c4V4eHeJi4+RlGJ1iZ6oWFtbWZ9SUlFSmp6bmpWUXmpqfpRphXZ1d3p1eHZycnR2cnB0c3R5gYmGh4Vze7FtioN8vI2ae2d6ZUhaV4p5eoJOlI6KjklLS1BMRD89c2xcraGnWl1bWVeurVpdXF1hYX2FroBgl1+ahEmFVVpeYltQcVtUVqKdVFlkfIiRT1RWWV5dppeFioySlIZ4cWpmaHCCk4h5YmF3iZGJf3duZWNdWltrhIuQlJCFgoiKiod4iJmipJmQlpWVl4yEh4OMV2Nzd3t5d4uMiYiCgX9+fXZ7fHl6e2xnZmNiamtubWuAjo+KfoByYVaOhHFwbG9veIKEgoN7e4GIiIaKjZGueZumpqmWmZiXlH+BgIKBkZOTlI+jqaeopKmrrq6qg4aDgHyej4qKjo6Ign57domKh4KDeXFrdpOOuVqIYWx8f4CCg51vV1dWVlVTUaCdmZWSjouIhYJ/e3l2dHJxb25ubm1ramppaYBiiXp/WHx+fnyFUlJNUVdFTlNLS0pMQ01PTEhCQlBMSjlAQkdGP0I5OT48Pj5SYrRfYmW6cGZ4g21tsIqTfG5yzbjOv73DctzX0cPTz6vF1HR2bnBxk26yqKy/w2Zpb3J2eXXCwb2xpmCIZG+BdWxksrS3s7dia2hya3h4fYp/eoCFkpuUlZmen5y6t2S5tmpeYmdmaWpkX79jYXR2dWpqeGZpaGVja2tucWtldWpoYl5ua2dlbHByY2RqaWViXGRdX2VnY2RlXl6lkGpfX7yxrqexbIySlpWQkouJhnx9f4GAe31sV15cmoaLjI+BXpWTkImFiIeEhYR1c4KJjpCLhYB6bl5iam92eHuBh4mHnZOOfo+CWW9xcXFvaWV3dIZuh1uTfGOgVYtubmpgXlZWV1mIfIBtcmtqamdSV1lwTlQ1OTdBOjptkpV/bG9PZHFwbaFbYnVhqKKphYpmXGdoZF+topuNhoFu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OjdpY7ewzPaGiYaFhYiSlpePif+TvIOX3+fdj6CLlcXrl77q+/3v387Fy9bT1Nbdz4C+tuzhuZ+Yl5KSmKWws7CxqaSzvMTCuKysq7G0sLW5wL+5rqOmoqCiqqmnq66qp5J76ebleYmPk5OeoaOpmJmbkYyTlJmkwHF8hY6FjpOXlpaUlZeZl52anZ2WjI6GgHljqo92Z3OLjI+RkZSTk5iTlpmboJOEg4uNjLDkk7W/1oDa4OLh3d3g3d2/w8TIyMXMyMfKzt7d3+Pe5ubk5tnZ29vdwbqsqKejfXv6hoqgpqeip5vizcXji5Gne3l/f4KFhoivjGpramloZsjGw8C8ubazsayop6WioZyZmZqZmJiYlpWTk5OKyMjIha6zsK2+i7m3w9+mv8+4vby1scTFwICsqJzExLmEoKqysqGmiYmmjoqImri3wZ+gr7KroqiVku7Br6Wll/328oeA+/r08ef484ySmaCR//jFvLXO6eff1bWfjZWMlKOfgoHxt4K32OvGsJiO9oOGkJWGlJielbGtp6vIubnMw+Dp2uz19eX18PPqiZuViqScoKGPkpmckICXgJGRkqKkio2oqKqaoq2fj4GMiYORkqmmpaWcqKapqZSDh4qAi4KG9YmE6caPi46JgoDz8JzS18fPyMG4s8beyrm/rMji666DmKSlpqehi5KVlpeCo+Lh1s3Mup6ZjIyYu+bx7vT18uTSx7q9w9jg5e/16KvautfOjrK+vsXMwICevc6y4ZPA6Lq9g4Hvu7uzsrael5mhnPKUgYCSuuje/ouP4sLH/4eDp4+KwfPTxaWriZyo2o2LtcWzl/vg5cvWh7jGvKWSgezfzsCc/cyHiIbsvaqnqabCtdTk5cz36IOGhoDyjrvD25zZvfeMi6bhl6KoqKeVmaXAt/zi2JaPgYCKlqqxu8bDz9C/wb/FyYGBgoHr1s/MxcDAsbuA4uq3oMDHi4mFjYCciIaLmrTOxL+8tbi3wtXWjNLXkfSmx7q/wMX07fyIy9Pb6fj34dvb4un1yKjFlP/098CCn/jh3MzPp62d/o768PXy9e/u+Pf3h4qYofHG07HF3dqtuNLeiGd4bZJPXlOQYHSAYHChaXJwbGdgWFVZX2VraWZjYF5cWVaknZeOhHdrZZtag3lqUkWCUWBRi4pykWpmYahtaGZhWm5vcHFycXNzdHNzcm9vcG9qZIBlb3N1dnh5ent7e3x7e3x9fHt7hHqAeXd2c3RnbHKIdXG0dKKQkJSFjYqNiYJbcYKTlammVVacV1hVVaOcc5WIiXihbHZziIeJiHt2eHh5b3V5d3+GhYJ/hIZ9rma2c5Z5Z4BmjFZUhGZsSWk1W21nXllXWlxST01KR0U+cVuTfo+sXV1ZWFhcZGhpZGCuZIhbcKmsp2aAm25zfZhbcYaOkYqEfHp/hYWHho6FenmlooVyb3BvcnZ+h4uHiYN/i5aenpOKjImOkpCTmJmXkId/g4KBhY2LiYuMiYR1X7Kwr1xpbnR5goWHkIGChXt4gIGEi6Ffa3R6bm5vcnZ3d36ChYGIh4yLhX1/d3BoVI12X1dje3l6fH2AgICBhoKDh4qMfG5scnJyjrV0i5GqrbGysKimqaWmg4WEhYWGjYuKjZ2sqqyvrbKysLGin6KiopKdk46NiWJfumJkgYyMiIyCsJuPp2twg3Z7fHx9foCBmm9WV1ZVU1KjoJ2ZlpKOi4iFgX97eXd1c3Jxb25ubm1sa2tpYoN3eVSAc3d2dn9SUUxPWkVNUkpLTElGT09NRUVBT01LOEBERkVAQjc4Qz1AQ1eIipB2eIKBgnl+b2mtkIqEf3bOxMJvZ9TPxsPCzsxydXl/cc7QpZyWnbS3qpyCc2hzb3R+eWNku4JeipujjoBvbb9iZGpvZnB1e3GCfnuAlo6KmpSlq6WAsbKtpLKrs7BibmtldnN1eWtrcW5mb19ra214eWhoeXl6cHR9cmphZWVea2p6eXZ3bnh4eHprXWFlXGReYK9kYauPaWRkY15crrRylJSJk5CJhoSOm5GFhXeFk5puUF9paWpqYlZXWlpcTmqWl5WPjoFwaGJhaIKfpqemqamhkIWAfoCBjpGWnKGZdZ+HmpFccnx+goV7aYWVhLZqh5+Ae1VTmnNva2tqWlZXWleffX16bGdpZJlUV4pMU2Y3N0M7O22SgXxqbFJja5BkZIOLfWaplZaBiVh8hIB1al6toJWKb66JX2Fgoms5NTk4SEBMUlRJWlQxMzIvXDhUa3JrhjaAZTk4TIZcZmxqaFpcZ394o5GNX1tUXGRxd36FhIyMgoSBh35QTU1LjI2JiIJ+fnBrS4CGbnOVs3RsZ2ljcFVTVV91hHx4dG9wbHeDg1WDh16ZbmpPUFBUaWpyVoKFjJai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1ojN1Njf4d/e3+DfxdDP0dLR0M/Q0sXl5ebl5OTk5ebf1+Di4eHa2NLR05q3u7a+urm8ucLBtLatqKOBuqOZ+oyMnoluh4KDhIeqjWpqamloZmXIxL+8urm0r66qqaako5+bm5yenJuampmAmZiYjbzKzIWpr6+vu4rCtMfWo7zOurzCtrOvw7qtp5zM0LWEqbW1uKmhjY6jj4+LocGquMC4rMqlubqNiOO/xMy9u7G0uZ2Pn4qQmJiQqaOqyLqukvHx5p2iloSanLSgi4uDzaShvpHI6tzHuLiEhKeZjZKTnp6ioaOsqaKyw8mA29vl2er194Pi8IT78on1io+fqZ6gkKugr7OllZijjpqWnKGxppWbjq6rr6yOlYmI/JacmJ6xl5ukjZ2Wmfb+h+z0gfPg5MmQiImJiomNjJu69PX49+TY0trVzcOqjIGRlp+pmaupp6asuri1uLOnrbCvrpbKhvz39N/OsLa0srOAxP+WlJmhpKORhfHs9fP7hIKw5qb614OXqLvDytGv4vmk7dqN5+rVsYr5xLu5ucivo6Kln5aMv//x7P+l7YiI9sHC9oSBm5GJvdmluZ6l/7eI/qXExcXEoITg7cnm/LO7tqOQgOzgx5WAhomHjeGeqaqrqKHBt9ng48r77oGC9YOA+pDKz8CkrLf8m6rMhqScl6Kvtbu8u5v+hO2Tk4qNnqq0uMXHwr21wo3E37K7jIDTw9rk5N7pyMDJx4OKjrDZ7rG0pcDulfqA94+9zsLExMC6vs/g5snwi/Ogwbe7vbzm6vaCxcXU3t3Z1d/dyb+/kY6k69rQ4sKE8Onf0au6m5odjNj21c3X0dLh7/Xp74SKlpmN0qnd4O2SjdarlL5GgZOTUl1UjVlvdn9jboVrYZRlcXBtamRdVVZaX2VmY19cWFWmn5emiVuiX3N7SIFwbnqGbW6CbIxpZGGmbGZmYV1ubm9wcYRyEXNzcnBwcXBtUn1scnV2d3h5hHoFe3x7fHyEe4B6enp5eXZ0kGhmo2xfZXuZh4V2p6yLhoeCe5ibqa2jV3aHmaekV1dVnGmNXpuXe1psdneGmZmUjHV6ent2bnp7f4N8enV3b65qk3dpiGlKV1JNiXNpMjRBVFWBToRHR09XY2xhU0ZEg3NyXJ2JiIiRplqxoZGTW2prbF2lkGibfoB1YZBxoHR7VnBydXV0dnN7k6xfYGJ1d3x0ncO9rY5ra25vbnF5j6KYk5SRm6anopqXk5KOjY+Qlp6imYmGhYKMjIyNjYqIhYmGfnJyaWlkX2KCpFRUUpCEgoGIhWejm5OMWH+Dg39+f4yNlJeamEuMiIJwcG5zdnRsZWNlYnt9foB+fYSDgoeHhZKUlJx1n6ZyoWmjqauvtLSzsrKyjI+Oj5GPj4yPko+sq6ywr6+vsLCrpaytrKiioZqam3OXmpWZk5COiI6Rk5WOiYFig3Ruvm1te3twhn19gIGVcFZWVlVTUlGgnJmWko+LiYaDgH57eXZ0c3Jxb3Bvb25ubW1kgIB5eVFxdHR0elJSTFJWRE1STExKSElJT0tGRkBRUEo5QkZERUA+ODlCPEFFXY5/hoyKgJZ3godnX6WRm5+QkYiIj3xvhXNydHx5iIKAlIuAcMC8t3Z2c2VycHZwYWRfk3x4hWaIpJ2Hf3liY3Vxa3NscnZ5cnd+gHuGjZOfoqWepoCyrmCsql61r2GzZGZzdnJ0Znx2gIJ2bW11anVucHaCdWxwaH98fXlnbWVltmxwa3F+bm90ZHBsbra7YK2yXrKgpZVrZGVkZmRkZm6Cqa2uqJ+bmZyYlY14YFliZGlrX2ppaGhrc3N0d3JnaGlpaFp+WKilpJuPdXl4d3mKsWJjZ4BvcWpiWZyWnJqfVVN4pnmymFpjbnqAhIlznbR4tZtipKSJcVqjd25ucHloYF1bVlFNiOvp4cZhi1JSnEtNZTY2Pzs6aYRnd2hqm3VhunmMi4uLbliWnH6OrXuAfnVoXq6ij2hZXWFgZJ1QODg3OjVFQExQU0xaUi8xVC1eNlpwaIBdU0BuQ0xoVWhgWWFtcXd4eGOkVZxdXFldZ3B5fIWHg4GLm26ts4yEVE2AdH+GhoCHcmR1cUtRaJC7yZaajI+7cJxOl1x2hnt5eHRvcHyIinuRWJdoaE1PUFFla3BQfXyHkZCHh5GOfXZ2W0xceG5pemA9YV1ZU0RMOz48XW1gWhhhYGVvdXBlZjg8QENSfmyOkpVVUH1lW3dENTw6HSIiOCUhHiEmO1RUXZdod3V0cWphVlRXXGRpZ2NfXFipn5eNRitPLTpSL1hNSFFdW2J1VlheXliYaWNiXFducHCIcRFvbW5tamZMbmFrbnBzdHV3eIV6gHl5eHh3dnV1dHRxbmt7TUubaFRWZoF2dmaP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pK+2uL27sqKsmJeT++K008bDztvSz9TVzeDD0er4ifzPwbCckYWUnLG6uqyvtLXGy9DBzbG8t8fNtauem5KllpubpJejp6uowLu5xMHa29yA0+n9gP/6hZOKhej6g4GCkJ6YlZyflqabn6STlYiQiJKNkJWdoKWqoKmgn6ytmoqPk5aKnbK3mpCZoJ2K9Y2RmYD8uZiOjo+OjYyDgKKtiPTt8unz8/yAgPH04NPGq46tq4qUlqm0tLi5tq+loZudo7jFxsbLtvWRiIqOk4iCi5CAk6Gwt8PHysjHvrWuopKOlJS61paj+4GOjZuuw9SzkpnSs4Lyqf7n07GOhL2+vtKuoqm4sJ6U8dT2guPMn7L9gczD8YGAk4qLtrSLtp2j0s+TwL+/wcHFt4nmgMqihIuQkYyC8uLKioOLkIePzfGko6Wpp6DCs9Pk4Mf43oD/74OA/pDO1sWRkanM3deK/5+usLO2usC9uZL9guuXmoqTnqayvMfJz8uY/Mrl8ND29br/0YeFgv6CgffusaS5hYuFw+uYgeH0rIeBlovSy7u8ys3Jy9fm7uXl+fWhwrS+v73b3PGBwcXEt62inInnwMvisOWFw7Oqw6n29+rcy5ypiY8d+L3Lu7m9yfmcnZHw8oKNlZqY3KCp2ICo+KeO5vxejlJPV2ZWiFVtcXlmdIaqUZWOrIyjX29wbmpkXamjp1ZcX11ZWniGiFuNl5ekgXRpaXKEgXFcp4ldXV+lbWNmYmFtb3BwcXJycnNzc3JwcXNycGdidHV3d3h4eXp6eop7gHp6eXl4dnRXamR+al9nb3l/gYynj4drjaSPpKKmr7SdpqWfn6Vrh5uUjotboaGeZ46HjIaWl5WRinp/fnt7b3h8gYJmpG6Sc2uRbktTUVa2cHdmZkB0PmNoa5NUUlNRVaJdXl9go46DeW6ynI6NkI+QiIF+gYOJjaFeqqmaroOkgHKdXIp3dnN+enR7d11oZmlkY2Flja1lfHlssJl9cGpnY2NogJatsK6ompagoqeno5yamZiWkZ6wqamonn5vfISOkpCSm5eUkI+Ok5GPl5KEiZOUlpWXmpmXlZmblIV8c2uMkZCQjYyIg3h3Z4mToldbV1pbXGNxenp7eXZ3gICAgHx6i4+RlJCOjo2Us2iNp6+vqrOytLSsr620tbWrq66vqpyjo6mqq7GxsLKyoKapqaqrr6+srJ+Fg4CEicGbnZqZnZiblpKJaJqblpSRjIN9d454f6pWi4KAgZpwVlZVVVRSUZ+dm5eTkY2LiIWBgH17eHd3d3Z1dnd4eXx/hHiBgHN0TnB4d3R5UFNMU1ZETVJMTE1JSU1PTklEQ1FPSjlFSERFPz01OkE/Q0hggYSFh4eEdn1rcGy1pouik5Gep6Kgn56aq5KcsMFltJaNiXJtZm5yfYOCent9foyOmYqNfoeDi5OCenVyaHZtcHB3bXJ1e36Ki4mOj6KjoZ6stFzAgLFdaGVhrbxfXl5mc3FtcHdueHF4eWxsYGdmbGhrbXRzdnp0e3Z1fIBxZ2tsbGJzfoJwaW9ybWSuY2luXreHbWRmaGdmZmBedH1fqaWppquor1pYp6iak414YHJpU1pZZm5vcnJwbGRgXFxfbnl6e31xm2BcX2ZoXVVZYGRtcniEgIeKhoR8eHNoXVtdXH2eaXOxWlxZYG59inRkbZWCWah1saGKc1xVcnJzfGdfYWdjWFCIgbN84tqBc51QU01kNjQ8Ojplb1Z1ZmeBkmuKh4eJiox/XZZUgnBeY2doZF2ypI9gW2FlXmaObzc3Njg7OEQ/S1RUSV1TL19UL2E5YXhvgEdKX3B0h1WZX2xvb3J1e3dzW59TmV5fXGBobnh9hYeLinnIn8XDk8qVcIt7T01KkUtJhYdiXWtOWG3Dy35trMqmU09cVoV+cHN+gHt6g42SiYeSl2loTVBRUl9kb1B6fX1ybWZnWZV4gpNudkljWlZpU3NeWlVPPkQzOGtSX1ZVGFlhhV1hV3xoNz0/QFV/YmiMUIDEfFKHkF88IiAhJSI5IR8dICc5TE8cXm/s+vBmdHVzcGpjrZyeUVZaW1lPR0RDKkRaa29YUkhCTllcV0p2WU5SVJVpX2BdXWxvb3BxcHFycnFxcG9vb25sZV1ub3Bxc3R1d3h5eoR5gHh4d3d2dXV0c3Fua01NSWxfUldfa3V5g5Z+eWJ+jm6CgYSNknuBf317eE1kdXNta0Z/f35SbGRoaXp7e3x5ZWloZWZganWJiWqTXHdnbXA+Hh8kOb2XsYKIgfGAyt7a9pGQk5CK+JaVjo/528mxneaukZGTkpKNhIGGg4mOqGGsgKyQoYCpWWY/kYaGgV97eIKAZ3Vzd3JvbnKk13qEgH/cwZqKhIB6fIKkx+bs593IyNXY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tLjBwsHHyLarr7a7uLzS5ezx9IDdmbutq7S8vcTOz8nDwMrR2+DY0cfC19G6psa02bCDh5uio6GepqaxqYDh14aIspbpybSk2sPC1b+8vsjLvrOK7PCPnOfJy4WdysHsg/2Riouyn4W0nJ2sp7W3uru8vsPEofSK5rXq8fT39e3ivoSDi4+Ika6Imaaip6uom7qq0oDs68bz4oD67vuDkdiyiprf8f6Ux5Kau7Kvt8bEwMLMpIiJgKShk5uitsHK2NDW5b3S6qLXqLyIuu3z9/by4MaautmWnZ+BzY6Ow8XTkJrBluCEi/qT0s7SztDS3NTV4uyTg6HHsbvBw9jN5KHJwMnM2dKqjYmasbmLudqomJKukybb9e7Twpic6I6ztrOi8dHDSBAIDsHuhpGXlqiB0sjlj9jdtKvZgFROYV5dalmJpmludmdcm3mlimY3KyVulKGMX2xuamdiW6iam1unXIdzoVldVlyEeWxqg1NlXYtnmGCmm5hoYGdiYmRtb29xcnNycnNzcXFyc3RxbXOEdwt4eHl5enp7e3x8fIV7gHp6eXh5d3RqeWSZZWFpcXt/gYKDhIWTqJOMbadjWqiVmp2fnXFyeHqRh4ejl5qdZpKRi4t4dm9pZGF9gH9/e1yomGOMbG9Ndk9TgZtlgJSioaORha52eXh+gaFXRYVDSX1MWV2zpqGUenO4qJ6alZSTlZWRkJOam5aYomGYaHh8VXV9d352dW50VXp+fF+VkYN3aWViZYKofXuSZ8W5sJ6Md2poZ2yAo62mpqepp6eoqKuusa2sp6OkoqKLZLerpKWqYGp2iZilsK+inJ2dpaiopp6YlpWEkICgVVOcmI6QUJycloh6eXNvcXJiW1VUoYqMho6TVIiLiImDdXt7fICGiY6Kjp+jtmN6j5KYnp+gn6q0s7a5r7u9wcG+ur28vbmtq62traymsKyxsKqtr66tsq61trSxrbGztLauuqSqpaGamZuVjIloa29yfH2EeG5wbpGOkW+Gh4CBm3FWV1ZWVVNSo6CdmpeVkY+OioiHh4aFhoqMjYqEfHJjr5V2Y39ub5drcXFvc05TTFFVQk5TTU1PSkdNT09IRENQT0k7Q0hDRkBANzo+PUJIWmhriX6CbmRnYLlkXZ2Sq2RcYV65aGWsrbudXl9bX2OUg392joeMfoyFgYGEhICaloaXm6xWXpCCd4WBh4GQg4qJeH6MhouIppChopKep5qjsqeyXF6xXmC6sWm7Xl6rZmVgt2Z0c3Jka21waWRqYnFndn58b2tyeXlybnBzc2JzcXFvYGlnXV1ksV21aKyWj3NramhoZmhcY3ijrFpubV1ZWKukpFlZXFyOamxwn4CaYGVrbW1ubnN1dXl5amZoa25vcoKPk5aXi2F5dnl/gYGGjoyHgoWKjZGSjYqFf42JeWiIiJp/WV1gZGVkYmhod3ZfnotYZH1qn4VwaINzdH1vbW5zcGdiT4mWWXjWzdJ2allMYTZqOzk5ZGBPcmRlZnWAgYODhYeJh2yaWYx/pICpr7GuqKKHXFxjZWBpfTMxODQ4OD05Qj9NVVVLWlUxYFZZMTpieVZTYGdsQ3xZYHVubHF9end5gGlUV1NmYmBlaXqBiJOLkJqTn7N7qoCqammCmZGOjIJwWWh+VFZYUoZ1W5+XrHmFp1+KUVOWW357fnt+gYeCgIePWk9paU5RUz9UXl9rY4F4g4iQiGxXU19wclNcdlFJRlxFZWBaUEg8PmBAYXd9druor2hCKDpwbDs/QUBcSXp2j1R4gnJlfUmAICYkIyUjOEEdHCAlJkIvRFhF08bKxHmKyJF3c29qZV+rlns3VjBCOVwyLjI6V1JIQ1xBUFKGV2xWlH99YVtiXV1daW1ucHBwcXFycnBwcXFxcGxxdHJycnR1dnd3eHl5eXh4eHd2dnZ0dHNycm9qXlNIeFlSWWFsdnp8fX1+h5iAgX9lm1ZKh3Z4ent6T09VV2toaH94e35SdHNsbV1YU1BMS2twbnBuTqelV3RscDk7Hx4zclqHsuX5+8So5a681/Dt94yA+4OIy4CKhP3q4da6r+e+oZuVkZOZlZOTmJ+fmpulZJ9tf4R1bnuKgYN6Y0SBhoRqramXiHZwbXCbu4KAf69/9OPZxKuNfn16hKTS3dPQ1NfU1tja5u717e3m4OHf4r6G8OHd2t1/j5u2ytvx89zY29zs8vDt5NnZ29bW2Nn/h4T69ujpg/z69eHAv7eysrSViICA78PHvcfRgOHn3t/UuL/Aw8jU1tzR1ern+H6Ys7jAyczKyNvq5+ns3utY7/b27ubp5ubg087P0NPRx9HM09HL0tDNytDQ297b1tDY29rg1vXd7OHW0dDW0L63jZKXmqWotaabm5Cxqaqv7eze9aaE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3eHh3tzSrICAhIuPiZSjx7SXn6OorrGgtaXO7ufH8eT///j1/YzO947G4/ODlNCYj62uwsjSyM7X472WmpWyrYqnq7S8yM3Py8ffpeyGuKj507TTkZvi8dLVvLTQi46WtpSDntmJq8j80Lvn556Yo+nr5Ofn2NPX5PDuje+lxay8wsbUwNnGiY84l5uTjJ6rqZupuaOZw4yGgqKJzIDtxrqNitB/Hx4VHRAaDRUJCgW/94SLnKC5kIGFnKqX0OeszYJWVGRjYm9aiJ9la3RoV5yBV4p631RC1HxeKhEVG0RVYmimcGqaraeUc7GZlVJbeo52eV1+c1WSaoNer6WQWa1oY16JfY2gWmVwc3Rzc3FxcnR0cm53eHeEeAF5hHoEe3t8fIR7gHx7enl5eXd0b2Zli15kb3Z8f4GDg4SEhYaHh5ypkIGhureupp95b3FxcIWElptdcluFioqIfGRfVlNaZoJ8YI+pgWl2V39RUlKDmYTDj5OdVVOel3SEaWt4cXSVXVRDdXt6REdOWFKop5qEwK6qsFpWVllYWVejWGZudHd2enx9gHx9goV7bl5eYHqAf4CDiqipqqSZnLClg31/umK7sre6u7mfdYCnuru7t7Swsbe3ubeytGVpZWCzq6u1uF61oI6BcGNkYGucurivpqKprLCvqamIW15sdX2FlFVZV1SHYFtbZGxpZGhudHOAf3x8eHSTfXx6fXuGjZtZWWqEgYeIaIaHnmV5jJF0eH2EhIaJkZCLhqG2s7Cxtre3trO5sLm2ub66tbKxsa+ht7m6vLm2tbGrq6KsqJ6UkI6OiIF5aKtcYWRipKKgn5CRkI+Lg4J8bmSzomR2dHl7b42bdVhYWFlYWFdXVVWphKqAqKOUh3xst52Ja1aNgHx2dXZ8g5V6bm6WaXFycXNLVElTVEROU05OT0hGTkpRSUVCUVJJPkRKRUVBPjY6PT9CSFqdp7itdaxfYFm1nbWYpq9hZ2BibG5vZmJzdW1mdlyvlYeMk4SKd3ibmIejomlwa6JVVl+ol5KMf5eYlpuFkpOAlKuiqp2Zn6aYm6GltVqrXF9hZ7aoXmFgp2JcX2evtmZfYGRnbGhpZmFsc2p1dn6Ba293Y2RucHV0cWhxcHJqeHZmdbdea1uzXWlfoYB0cXNycGlmYGBwho2vZ2xwfX1lYWZdWKieqldcsZ9nXHaA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kLCx2qrHloH8kZHv6oXjlrvBid2ahZL63uSB+Y+MhPWuxO/q5uHtgImUo6aprKuqprK1tbW0tbW2tre4t7i5hLqAu7q5ubm4ubi4tK35q9yRobW5vsDCwsHCxMbIyMfBvLj/qLyv7OX1yb7AwLfLxNvCyuOxye6Nn7mooZSRhdCr0qqOxbKBmJKOj5qk53K/f5Z1lcxdr6nxum73r4dYXaiYk4p6fn+BjputsbCtraKE2Or17faA+vb7/IOChpSmsa6AsbSxs7i2t728s53P9MDEw+PR29zf5+flrMLC6Y+JhYb++emxnp2goKO7zd3s6Orv9fDo5uzq7OLLqZmhsMHT4nZ2d3nt6N50duZzdXiDiIZ+enbPpZSHg4KA/frx7YChnZWKgICEg/jj0NN+mJudn6OckoqBenR0bdLb2G+Tn6CAp6mbmJWZnqKmrLGwsrWnwcTG0dHtgYCCgoSIiImIinmBf4CDgtnc1cvDtLCspJqNp8vGxsLAxMC7vb271NHUztLP0tPTw7y40MfCvbqP9YL+/O/m3ba4k5eFkLNNW6t+f4CAfHZrxrKgkYLZuJbyzMbHwby5urW6xsLHxbm0tbaAxNittrfsmaSkpaqAx6zMxqjF173GyLK3xL3Cpp2fx9CslJq2raunoYGPmZKTlZvN0tqIo4+LhYmYju7v+YOPj46bnai2orOVo6Ol9+fn6+P83M3j6tna+YmIlaKTiJ6AhYz0gOHv2ebr5NLd4eyEifPu9PrV7u312P+Yio2Ti5eAjIaPk4X38oOBgPeMlKWMhpqan6mZjKCuqJqdnZOVjaSzqJearKqpraClvLGwkZOQkoGD9YDBo5GVmZ2lpZ6hqbnY1NLPys7Om6ynsLOtoaCRjouBiYuc35GfssjS1dbVz9ni6vLx6veChIaJgoGanp2enpmVnaKosqr6jubl4eCA6f/5+Pj8/Pbl29DP0d3in47CtMKztMjZ2s7MzsbNj46yncj8pIKH28nVhNjXwMXK09TCvKKfm52FkqvgnIywrrWEy4v/h7SX9bqZmZWXqq2vsLO1ur+9sePK2NnY09DHmf2FjY2LlJXDnJSjsLyyuLenvKrP6+HM9eL3/+/u/I6AvMf3peTx/pnFlJS6uLO1vdji7e/LlJKWx72CtMK/xdHa19z16smyqoOR3YqKqJ6WzebS57ynwfv9idzDme+FnJW/o4H69eCRoqCi+vXx+Pjx6e3f24eM4+/lgJ3L7rq6u/SKlJ+r09/m6er3gs+Ervvw8Jvyu4ThwamE/5wQFxYbHRAQFQwKCwYEwv+Hj6Gnz5yPl7Ld8MT+sOCFGl5oaGhzXIibXGlzalifgViGh62yw+Z3WD0chBM4IX1wl2t/gZZyfF9Tol1YenJIdFRrbVWaa3dssZ2hW7FnZF2jc4GclpGNlVNcZW5xcnNzcW93eHiFeQl6enp7e3t8fHyGe4B6enp5dXCdZohdZ3V5foGCg4SEhIWGh4aBf36zfIyIu7C2ioGEhHyHg5GJj6aAkqhjb4FuZFxaUYB2kXVjfmRFUVBXaX2FwWCBZXxle7JZoom4lmjBl35mbI6Be3Fpb3V1eX+Nl5eVk4Fjl56lnp9So6KhoFJRVmZ2fXx/gHx9hICBgomKg3GCn4OEgp6gqKmtsrSyf4SEpnBsZ2XAwrSFcHBxc3SImai3tLm8wb+4tbu8vraef3N7h5inuV9hYmG+t7BfX7ZeX2Fpbm1mY2CheWthXFpatLStrmGEf3dtY2JhXq2elqFngYWFiI+EgHtyaWBeWa6xrlyBiYiOkYiHhoCLjIyPkZSVlpWJnaOkrqm/aWlra2txcXFwcWRoamtsaqaop5uTh4eAe3VuhKagoqGgpJ2VmJeZrqauqrGtsK+woZeQpZ+ZmZhwuF+4t7Kto4uLbXJnbY5NVotoaWtpZF5XoZKCcmSrjWyrjIKAfnl4eHh7fn+Bf3hzdHd+kXdtbYCSaHFycnNLU0lUUEZNVExRUUpMUExPR0JGUlRJP0JJR0VBQTc7QEBDSVuYm59jcWRlXmJsZa6us1pjZWRsb3Z/cX1ncHJ2rpyfnqColY6aoJGUo1pbYWpfW2ZUXF2kWJalnKCfnZCTmJxYW6Srrq6UpqarnLltYGFkYWlmY2ZnYIC1rl1eXLBlanVkZHFuc3dvZnB7eG9vcGhuaHJ+eW9senl6fHJ0iH1/aGpnZ19ftFuQdmtwcXN2dnRyeIWWjImIhYmManZ0en14bm9jX19aXV5miVRcbX+FhIWFgYePlZqZlJ5TU1RWUlNjZmZoaGJcYmZrcW6kWpidnJqeqaWnp4Cpq6mai39/gIeJZHaKg4eGcHuDhH98g4SLZ2V1Y4CvclxekYKKUIJ+c3R0eHVtaV5dXltOVGiWc4HExrZZYzpuOWJXlHZjY1dndnh6e31+gYSBdpSIk5SUkpGKZ7RhZ2ZjamcxJi9PU0xISEVASkNLWFVNXlZiY1pZYThRbXNVYYBobUR5WF93dG5uc4mRl5qDX11fenJTdoF/gomTkJKbq5GLZHBqjmtPYW5phpCDkXhocY+NTH50cb9pXGmWgGamlIhXYF5lmZOUnp6alJaKhFBak4RuP01tgmdtcpVWYGZxj5aZm5yjVYY/W3NvblFqTzJWS0E0bGc3S0xhNTVDORYrPi4tdnI7PENGcFdQVGmEgmSMZoJMXyEmJSQnJTc7GBsfIxw1LhxGTXBVVWxDHurho6iwwf11R1Y6UXKNVUA5PHtBKEJMMEU0TFJJlFlTXpuRl1SdZF9XqHuDl4t9b3RCUF9sb3BxcXFvdnZ0dHN0dHV2dnd3hXiAd3Z1dXV0cnJxb2ljg0duUldja3J3ent8fHx9fX18dnJxn2p5fbGpo25laWliY2NvbnOGbnqMUFllVEhBQDhaWm5oZWU5HBwjO2qZqeuIbYChgYbehvOt2Z6d593Jr4bGu7KnqMDHtLChsrrDxsOUbJGboZiaTpmUlZpOTVNmd36AfYCCfoGFgoWNjYZ1bYmFiIeorrq9wcvLypyIiK2Dfnl34evcl4WEhYeNpbnJ4ePo6vDx6Obw8/nuz6SVn7TJ3vaAgoOD//bugIL7gYWGkZiXjouF3KGMf3dzcuXq4+KEv7mtnJCNjIPx2svhl8XLztXfz8a8rZ6Ph4D1+/mFvcqAzNXazMvN1Nfa29nY19XTwN3f4Ovi/4mMkI+Nl5iWlZWFiYuMkIzNzcq9tKWinZeOg6ve2NjV1d3Px8nJx+LY6enw6e3u8dnOwuLZ0s/MlfN66Ovf2NGxtIqBb3WogYzInJ6fmZWLgObNtJyC0J5wpHtyb2pnZWRhYGBfYl9WU1OAVFlwdFxdf2Bsbm1tPS8qLCgmJygkJSIgHyEhIh0eHB0cGhkYGBUUFBIQExQWFhcjSEBPMzsxKSwsNytUVVgrMSYtMzQ9Pzg9NDw8PGZbaWZaa1lQWFVDP00tLCwxIiQvICMlRilESkZHQ0Q3Pjs/JiVCO0tKOEFERzdKLysnJyaAKiUlKiwnTUYiJiVHKywxKSgtKjAwKyspMy8tKy4pKysvNTEwLjM0MzUvMD83OSwrLCopKlMpOy0oKisrLi8tLzhATU5LSUhKTD9HRUhLSUNFPjw8ODw8PEUlJzdDRkVERUNGSkxQT01QKSkpKiYoMjQ0MzItKSstLzU0TyxLTE6ATlFXVFVVWVxaTkE4Ozk8PjOJcllzszY2Ojo5OT0+RTQzOC4/hl9FRmFSVSlCQjw6Nzo0LiwqKy8rJyo6X0hUipCFOjofNhw+MVZEPD4vZHZ4eXl6eXl3cmiPdH2AgYKCfmfAaG5ua3ZvHgUIHiQfJB8bGBsSHCIdFiIhJiMYFx6AFSFfTS4tMDclYU1SZmNZV1tuc3h6aVVVWE45LUNLSUtNV1FUXXFaXytOV1BHIi1GPklUTVM/ODlGRSU8O0V7PiY9bVhCYEZDQEdGSWdiZHBwb21tY105RGxWOBodJS0lKDBFKCwwNUVJSklISSM3GikxLi0kLyISIB8hHUBjjMUcyviKi6q1jMeZpV8+HBgWFCYeGxwiHRQMHRIcGYV8FX58fHx9fXx8e3t7enp5e3t6eXx9f4SABH99fX2EfoR9Dnx9fXx8fXx9fX1+fn58hH4Ff35/f3+Ifqd/A359fZJ/BH5/f3+Ifgl9fX1+fn1+fn6If4J+hH0Bfod/A35/fYR/C36Af39+fX59f39/kn6FfQF+hH2TfoV9iH6DfYR/oH6EfwZ+fn5/f36Jf4d+hH2JfoR9jn6DfZl+kH+pfgJ9foh9hHwDf39+h3+FfoN9lHwDfX5+hn0BfqZ8iH2DfI59jXyKfQJ8fYp8gn2KfIt9Bnx8fX19fKh9Anx9k3yQfZB+kn+CfpR9BHx9f36LfQh+fn59fH1+foR9i3wBfYZ8C3t6enp7e3x7fHx9hHwBfYt/AX6IfwF+hX8Efnp6epR7C3x8fX19e3t7fHx9i38Ffn5+fX2Kfgp/gIGBgoeDhH59iHsUfHx8fX1/gYGCgoKDgoF+fX1+fn6MfwR9fHx8hn2Cfop/A4B/fYh8hHsQenuAgYGBgoKBgYCBgX96eoR7AXyEfQd/f4B/fXt8AgIEAF+svby6y5vf5a29y62P+M+Mz4n6rA0Dn5m5jo0QCES47ZowBybgrrDBr521u8CbgM7fmrzKheCc+ITlp+7x+I2J/IGKzYiIgv/I25Ooo/iFkJ2op7O2tba0tra2t7e4uIa6BLy7u7uFuoC3sKXP3ZSmt7q/wMLCw8PEw8C+vMHBvoKav8Dg0bKazvXw3dTD17XK5am7vL65w9PSyIa5oKWH3In9gpOhs623u7Xj1r2Ej922bcmztLzGzJ62uKHDp56oWltZZGVhYF5eXl1crZ+TgNLh2+Li4+38gIGCgZSrwMHHvsC5trnAwIDFydzD0MvJ0NCB/f730ryBysr14oaOkpmYmpWH58KmpaOop6fE9f78/vn19n+D89S+tK6sp6GYpLbL7n+Bf4CAeubb8ICDiYuHhoSBfnnMu7GpoI6Isb65tK6mtLu6saebmaOkoqagpqOlpqWkoaWpqaOe08PQ1+XodXh1fYONsoCusrWxp6+4t7u8uMrI0M3LytDOysrQvcG9vra8mZWNiI2PhomIh4KNub22s7Sztrezt7Szvri2trS6uLaxr7O3x8O+uriSlp6WlJD5up6YlauDka6T4fTsu5uLhfv15uHc1NLSzszKycjEvbq4uMDKysvLyb++0PiLrLOz4ZKcnYCdpPnJp8jKq7/Zu8bQsbzQuLaqpKPE0rCUoLW1uaqihIaMkpKOl7bV1JCGkJaUjuaG+fqR9+iDhpeXoImbtLGxs5aH7YGUh5OFnYyYoIH5lIWSho+bnpuVg+Ljztu5zIP48Ibnho2agsnV6fHx5tqAh4SFjYv4kISMj+72joHw5YCBh/aGoJyWk4+Up5mjqJyMkZKUnYiOkae1qaS0oq+0rrOyn5eNk97rzKmSj5WamqSnrrvp/5CXn6iZh5KSmp6VqbC1t7nCoI2Li5btl7OysbGptuHo6fHy6eLq5+Ta0fGOlZaXmpqhrrC0ubixt77DxsnElbWOj5KWk42OiISHjICUlIX49/vJ9b2dj/HG68/N3ej27u6ynL+zmqaWzJCB+ei6ivLPzszU18O6m5ONmoSNt/H38J7+q7Cw5rOBspLvwZaUh5Ckpqirr7G1ubq9t8bX0tPNu4aBiI2Ji5Dx3bKzsZ+pq72+yM3T9/yZpLS6xtbcp5a3gIjDzaPr9YGWuICB/Ka6y9LR0szN4eGE+IHXxOW4yNHW2uzd3J3jm8Komoit5KGWkZW9zb3Hspmw1+LrhPCImZSnnNms1c6X84uvrcT22su2raOV8cac34/Bu7m214CMm56SlJmIj5Cb1p6yv8bAjuai4N7jmdyngte5pPzxmhAWFx4SERcOCxEFBBLMhoqPna2Tr6Cuv4DXirq7ioxUZm5ubXdei5pjaHBoWJ2DWItux9SNhnZabFRpIRpXsNWCQR88nHp7f3FndXZ3TjxrcVVpb1GeauFhnXWnqa5mZLBVVIZZWVeshpBndmecVl5pcG94h3mCeoR7hXwDe3x8hHuAenZyan2HXWp3e36BgoOEhISDgICAhISBWmuCg56ahnihvLOgjYWQgJGpeYWHiISJkomAV3pqblJ7SYlPbX+KfJSqo8avfml1sI5gspSVkoyPcpWhlNSJhJhWWVxnaGVlZGZkYFaSemtXiJKLj5CRlp9RUlNTYXWHio+Hi4iFh41jkJSXqHWLiYeLlmC7vLuWgluKia6hZW10enl6d2uyiXZ0cnRzdZTCycfKycbEZWW3oIyCfn18dnJ7jJ/BZGdoaGZhtq69ZmlvcGtsbGllYaCNhXxyaGeNm5aSjYmcop2VjYKBhI6AhouLjpCPjYWMkpSNiKiXnKewtVxdXWRsdZmUl5mUjJKcmZ6gmaekrq6rq7CuqqmumJ2ZnJaadW9oZmdqZmhoZ2VwlZuVkpCNjo+Mj4yNmJSQkJCRjYyLiYuSpZ6ZlZJucnhycHC8fm1paH1jb4F4r7mrh3FmYbCnnZuUj42NioqAiYaCgHp5eXl+g4OHioV8fpCkXXhqaYtkbG5tcJZSSFJSSk1TS1FSS09TSkxIRUVQVEtAREpJSUJBODs9QEJGWoKgmWZfY2xnZKhgtq9jqaZdXmtrcGBofXlyfWhanlZjW15XaV1malipZFZgWl5naWllWZyhjpOCkFisp1ufXl+AZlmSmaano6SfXGFdXmJfsGVfZGeqr2NbrKReXq5fcW9raWZndGx2fXBlaWdpb2VoanWFenKBdn6BfH5+cGZla5+pl3pta21xcHZ4eoKjrWFma3RrW2FjZ2pmd3p9gIGEb2JgX2abX2pnZWNdaYiNj5aXkY6TkI6DfJRbX19fYmKAanN0dnl5c3d9f4CCfmF5YmJkZ2RgYFxaXV9jZVeal5mAz451ZrCDkXt6hYeYo6aAbX1xYG1limNZqJRyU497eHR4d21oWFVYWU9UcaGlo3bhusbCuGE4ZFaTeGBhUGNxc3R2eHl8fX18eoaQjY6Lf15aYGZjYmmmRhgaIEVRTliAXWRxdoWLVl1nbXR9hF9SZkxLWm1WZmo3RHNOm2d2gYaGg3p6jI1Sn1KHdo15hIyPkKCUkmGab5aDkG55n2JVaWh9hXuC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v7e+1PKHg/36+oKGi4iEgX+CioeEgYKB8/Ho4N3U1nBwbm3TxcPIwL68s7KwtLKvrrG7sqmrroCqrKmprayUf+3s5e7t5s/R0+GRxLi6vrWtt7q9wLu9u7/Cw7qxycbIysm9zsPBxMjHxcbCvcC/3MrLw8CvtLW2sa+xsa6yr6mytbGqp6WmuLa6vbu3uLa2s62hqKOhn5eI3NrXcZWutb30j5aVkIyI/v/z6ube3N3c2dnRxsC5soCtuN32iJysydXQv7Kjpq2q2YeMjo+Y8cSmxsqxvse4w8q7utK9vK6io8TTrY2iv7K9s6iDiJKUj4qh5Nv0tsfXr62dtJWC7/Hv4Ozig+yJ2IvxiYr/0fvvk5eWkIiFioCJhYeCjvrnieT1+9LX2dDQ3vPS3+j43fP4i/P4hunw/oDrhomO94eTiIGJ8YWLmof9gfj894CHipGgk4eakI+XnKWwk4KOloyWk5eLobupq66fpKKaqaL+6siurrSvqZ+dm6m024afuc/CxMy+xcmipqWkoZ+ftLi3tsG8pOSt5fX1w7O2uLrDz/H8/4GCgePV1NbY4OyQoaGkqayst8G/wYC/s6u+09XW19Ou6p+ipKWhm5KPj5iZm5+dm4js/pvQmLmpqZT22/yEhJCgxsKt6a6q1o/x77uWn4XmxL3JwrmRjIagj4+79vf204z+oK21rcbP8PDJmJL8jJ+goaSoqq2vr622w8rLyKv8gYuQiJCOvN7Ira2qk5SUpqGfsZ+8qoC/0oGDiM3R4OaBlcO3yJzq8v6Tk8aTubG1wtXu7O31+uSTmY3Tzq/X5ODh7Njeo+6Zl6aeks7O+72robnR3//vo6HLzNaOpoKGgtKu5ouhzLuA7qKhobPcgJWYoq25ydHQ1sy4wMDU28fBz+r2zpSFh5Whu/+gr6iHkp3Rz9CRzSaehNGwovjwlBQZDBEeExYKERQGBcqQkqiuxrT++9HD4OOKqsyrmVhud3RwdF+SmGVob2dTm4Nahpi2gLPyaVdwYH+MnqiupyggJiCwbniMgIV3dndeTUN2iGBvd1uy2ZlwdJWcrGNiqmJXWF9hXliEbm16p4mpopiYU15pdXl4hHkBeoR7BHx8fX2FfIR7gHh1cnyKYG95fH+BgoKAf3+AgIKDg3+AW2+Dg4KBgourjIW9mIeSfqOzeIyGf2tehmxjgmVGUFBaaJVtpZaThI+jpnB5eHtie1pvh7BeiK66p5qdp9zCkpCCgYKKWVtaU1FKTJ6NfXV2cmeQiJKSko6cn5ifUlRVZH+QlZqdnpaYLJ+fs5+dkY+SW2BgYmNhapGTksNmcXRwcXh5e3Z3c3FkrpanvWlqa2rLZmpxhHmAdGu5ppSKg4aewWtnx8nFZmltamZlZGVsZ2RiZmS7t7Ktq6OqXV5cXLGnpKukoaCZmZaZl5WRlJmSjJCPjpKPjZGQeWO6uKyxrqmdnaCndKifnqCUjJKXnJyam5aZmpuVjqOhpKanmqienZ6goZ6dnpycnrmpqaOdiIyLioeGiYeAg4iGgoyNiIN/f4aUkpeXlpKRj42JhXyEfn19eWyiqK9dd4mMjrlrb2xqZWK2sqmhm5iWlJGQj4mBfXdyc3ySn1loc4SMh3xxbHJmY4VdYWJkapBUSVNTTE9RS05RTUxTTE5LRkZQU0lARk5HSENBODs+QURFYaWfqoGGjXl0an2AZ1yjp62iqKBcp16ZXqReXKuJpqZhZGVjWVZcV11aW1ddpJ9cnqinkJObk5KXppCepbKcqK1eoKFao6uzol1hZ65dZlxXX6thYm5gsluvtbdcXmFncWlfbWVnbHF1fmhea21ka2pvZXOFeHR9cnJzbndzt6OQe3t/fXlycm93f5yAXG18i4SJjIOKjXFvb29ta2x5fn59g4JylnCUnp14aWhnaW96lZyfUVBPiHl4e3x/iltoZ2hqbG93gYCAe29oeIiKjIyJcJhubm9xa2pjX2BnZ2hraGVat8lzlmiFbWhcl4GgXFxkbX16bJNvc5FkqKF1WVxPhG5pcGxoVFFUXFaAVXaop6eMTJV7v9DPxJOVkXtjX5hebW5vcXR1dnd2dHqDiIiGc65bYWVhZWR6RjIWHB8yMzU3NThBOUo/SFEmJDdZV1laMjlSYWpQZWtvRWCDYHdsbneImZSTmp6PXGFaf35ui5aUk5yPkmWcZV5mY2iOiJVxaWx7h4yjnW1bc3NielFgTVFPooSsZ4uGdUqNYV9ibolOXFlfZnKBh4iKhHR3dIOJe3R7k5+FYVZWYmx8pWdxaVVNVGBbW0hbQjJSRT9eYWxFUDA1YTVDM0o1Mi2XVk1MSVWEuZ56bpO1bm16YlaAJiopJygjNz0fGx4hHDcyH0VRYEVadzwYHiFEbYKAenuKwuKM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gIKEi4+Hg4Gyu728t7Grm5KLif6BqqykpZ6fgJyMf4XJv8Hrh5CQjYqIgoL56OXc1tXn9oGPn5ulqq6/wc/h7Pf/gffcvaKUsKrPgYOFiJDrwqjGy6vD0bzBy723wsDDsq6pzNO0nqzKsry4soaNm46Piqv2gqTay9bQ6Mm4yIPzhfPq7OzN9f7i3+3L4ofhysnb09O7wtXC3s/bgMjIrs3FzrK50urk6Njz+IGF/NzUgN2L++yRi4iJ3vOE/vL+7/KRiuzt8oCMhP+C//yM9IyRn4GHnZ6cl56Ml6aXm4yWkJKCkKS/uKWn1ZqNitGqmZacpqelsrCsueGqydXL0MzQ2OfCzsPHysCtt7S5trWyw8O+46jZ5fyAgIGBgOLn8fDq7fX5h4yPkpWH6u/y8vTziae2vcC9u7eopaGoq6quw9va3N/dxYqzsbGmnZP0hpeTlpmamrvE+PLgrpuioq2tkf2w2JrX0quO7KWqtZHl2aOxwKmCzdHMu5aWgaSXlcD19PvbjZ+olMbLq7HEuZGQlPWGmJqbnqCipKipgK20vcC6lPSBi46IkYiFg9vJr6qrk5ajus3Bxa/autnowsyGy9Xg5oCYva282oT+jaa8y6rM4t7qhI+jsb659ZeemtPFh9rq8OTp2NDBwqurzcre08uA7uet3aKji63Bi7m52YTn8Pnjtf/zur/Kx/vzoaehkImHh4CD/fHZ0srNRNfU09DX3+Pj5eX2iYrfk+3yjJup45z57Z7Ny8uLupaH6bSa79WQEhQPHR4UEgoHBAYG35mZt7zUlImgh7/i0sPFsd7hXoCJfXN0X5GXZ2dtZFKWm5iomWRel8WclXqRkIuYppyUjI2STm9wjH55fXp7e2FXTUuIjGN8qqjeZ6lbZ36uY7WwZWJiZ2lnhWh6p2iXimBeW1lXU5+aU1tnc3l5e3uEfAN9fX6EfYV8gHl3dWVRZnV7fX1+fn+Cg4KCeXyCgnqAW2yAgYCAgH9+fHuXcn6FZ6uwYWiYdGlrTXFLUEuDdXB2oI2rgrBxoI+Zn22FeJmliG+KZqijZ7aOZ1lfYWRXnXdyc3R4fGtcVVROjnt8hYWKk6OceZuNlI2JkpCZUlRWWVpohJ2lqqqngKGft65nmpmbm5NfYGFeq5uamcJtcXh6enx/end5en2Bg399enR1bmxvdHZ3c3V4dXFuZ2VlZmZnbW5uachlbXN0cnZ2bWpqaGtpZ2doZ2RhYbdiZWNgXWFeXFtcW1qsXV9dXFqtqa6sqKaglZmZlpeciIWAhYSLiqOhmJCPl6SkgJ2ipaKRn6CepKaOi4yJg36JnpORk5WOoZmaoaGcoZ6al5OTpaGhnpmKkJGPiIx/trS0tVpbXmVlXWBgi5WVk4+Lg3FoYmG0X4yMh4V/gYByaG6jl5KwZmxsamdkX1yuoJqWlJGdp1ldaGRpcHR9gImUmqClVKOQeGdlZ2WCWVxdgF9ki1RKVlNJUFNNTlFNTVBNTkpJSFRVS0NHUUdIRkU6PUI+Q0RhrlxxlImRipeEeoZapVqkpaqikKezopmni5pdm42OlJGSgoCOhJSNkomLd46Li36CjaOdppalp1hcsJqVV55hqKFiX15gnq5bsquxq6piYqWmrFtkXbZbsrFkgLNkaXJdYG9ubGtzY2p4bXJlamZnYGt4iYR1dZltY2GTfHJucXd2dn97eYWleoqOhoeHjJKdio+KjY2EdXp6fX16eIODgJZtiZChUVJTVJGQlZOLi5OZU1ZZXF9TiImKjI6PU2p0eXp6dnNkYF1gYWFofo+NjpCPf1h5eHhxZ16ZgFllY2VmZmeJn7ivnoFmaGZtbWCvdo9niYRrV5RvdHpkn49oZW5iSnBzcWhUVVJeWVh9q6epjk1TVlWQ4tbe6Z9jXV2UW2lrbG1tb3BycnV6gIB9ZaxcYmRgaF9XJjkwGRYfMjU6P0VFSj5WRVJVPkE1VVhRUjA7UlpgYT+ETV1xgHpug5CLjVBWaXN8dZZeYmKDd1OOm6GYm46IeXNra3h7q6CTTY6JaYtrbVt1g1JqZ3hQiZGWj4yruHqVg3+VkGJkX1lUUlNNUp6UhX95fYqHgn6EjI+NioyYVliQXZSbXGZxl2egcF5mWVlGUj80WEQ8WlhrSEAhVl42PDMsL0YvEoJGRE5NWWJdcF59iYaan2h+gYAsLywnKSQ7OyIcHyEcNEJgblk3ME5iVkxJdX5sdXhhUVZlvZRKTGRMTFZUVlQ+NywhU2RLY52Gp1+SSVdvnVumrWVlZWhpaYlVWa19uIRlYl9ZVU6MekBLWmtzdHV1dnd2dXZ2dnV2dXR0dHJxcW9raFk/VmVucHBwcXN2d3d1a4BudHRsclBacXJzc3Jxb2tpflJXX1qqqVdYfGFjXjI0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iZmnl66slZuXpYTU/IHhhIPu8fSC3IC8vN3OxdXC0MbuyLC6vuC3yt6B6euDg930jYGGh46F+pGJg+GJgeaCgeWJjv77+ZGMjIqPhfmYgIeBkZial5OPi5WC9oSQl5qckJyUoKCEmaqRoaWPjpWS2dWyoKKlnJuen6ywj7OUndLo9vP499nd3t7d4cfe2d3UxL+yw8rS0NHamcjl2svD3ISGiYuN//qCgIKGiIWQlJWYoZ6IiYuGi5KcscXJz9DSzbitrrC0trO42Ozu7evkgNiipePS3OPt9YWVm56lo5jYuK3y36KmqKaZ4e/osuDYzLGYtpeVr47Z8p2rtcrDof7Fmpz6q6Cdz/j4/dWJoqupsqzniMnP15eD6ICSlZeYmZyfpKSnq66lg/qFjI2JlvrA3uzcyaeoopWWqLbIu8Kw27DV5OTL89PS3eH7j7qvgMWio5mlv+GA+uvq8/Hv9e7g29rXjpWc/8nRqr7KyMO3vYPc5oLHttDLhPnMvLjCstnBoOL4oqrHzdjo6eb0942PtLTJ9vOdqqvdi4mFgP336urcztPXysvN1NjS2OX/hoWCiI6Av6empKKP8dOdwdLEjb+Uhey3k+TQkxcaLBoZGRwXDw4FCRDppaW9wdeI/oWKkJC+x46A8olfj0uDeHRglJRvaG9uZF2YrHNgoGJwjb6BgoeWjmd2qqGlsllhaXGIh4KDgIOCdnBai1CObXhYgn1eZ2lbX6lfpreUnVZgaWiPn3aAX4ClZmZlZGNgXVpYVaOcUVpmc3uFfYJ+hH0OfH19fHp4eHVXbXV5e3+FgYCCg4B5e4B5gFpvgH9+fX5+fX19e4KDfl2pgG1sh2KGUFCUenNycXFxcHd6Y3qLeHV+fX+5bnF8hYaQp1imnFywlXesVVVhUlVTR0B8fH9+eV9WU099bWR7m6Skq7SqelVKTE1RVlldYWJnaGprfJ2to6isabJnnpyenV2ljY6jqYCamJmzZ6qya3qDhIB4eXiBgYWIh4R/gH13dnh6fnuEgYF9eHh3eHl2dnJ1dG5ucnJyc3Z4dW53c3NtZWxra2ttb2VtZ2JjYrR8d4CEjpSYo69cYGJiZF9eXl9eia6ytmJtdpWanJucmpedoJ+hpqShpqOipqCgpKSfo6pnY2VpaYBqbG1sa2xsgKSko6CilaminpmfoZ+jqKWlmZyqoZaUjWOwWl5gY2Z5e3x+f4aPioqJiYSvj4eNj426kI+Kfnt2eoZ5drizuWRobXBra3Fxe35/g5CXmqGmWV6zpJueoqanpaKgU1RUVaWJY2tpf1heX2BjhlJIVVNLUlVOTlNJToBNT1BKTEhWWEtFR1JGR0VEODtCPEBAUWRoc6KqXmduaHRxaGhlbVuUsFefXV+jqadXllWBgpOKhJWJi4uki3x+gph/jplXnJ1WW5+nW1dcXGBZqGJfXJtdWJtbW6dcYa+tsGdiX1xhW6trYFtla21pamVhaFmsXmRpam5lbmhyb4Bca3dmdHRmZGllmpZ/dnNzcHBvcnl8b4xoao+fqKSmopGXmJmWmYibmZuThoN4goSLiYiMXHiKgndwiFRWV1lbo55SUFBSVVNaXF1fZWJRUFJPUlhfb36BhISGg3FlZGVoaWhvipycnZiTjmprlISIiY2SVGJnam9ugZ2Je6WVaIBra2pmlpuZd5CKgXFiemVmd2OVoFljaXNuWottV1maYFxch6+trYpNVllXWFiWgOvz9I5jllhlaGhoaWttcHBydXhxWa1cYmNga7FyQUA2LRQVGzMyOj9IPklAVkRVV1hQYVZVU09iOlBaXz1UWGBxi0+glJOZl5SXlYmGh4NYXIBionp9bn2Eg395fVJ2gkhwa46PYaR+c3J3cKSTeqKYYGJwdnyKi4yZnFxgd3J/k5JhZWaIV1RSUKCcj4yGfYKHeXt8hIV8f4mfVVNQU1lTfnFvbm1dlV5OW3BfRVE9M1hCOVVVaFFOUVRRUjo7QDYxNIVMSFNQWlqsW15iYn+BWgNPklCALhgsJyckOToiHR8pJilEqTsxYj1FXJp3cl9jUDI5Wl5qcTY6P0dXT1BYWlxWWFVCXStKS1tMZ1xRUlE8TZlVlrSEi09caGmScmOceIaja2tqaGZjYFxXUJKAP0hXaXV2dXV2dXV1dnV0dHNycXJvbWxoS19kZ2twdHV1dHV1dnKAbG5ya3BOXXFvb25ubmxqaGVfWlZIj35raWEzNRsfUlRZXV9dXl9uXlt0Z2ueh3BxqIqOcnp+pOSC6tiKy/2t/YyJvIaFiIKC/OrX39G9p5WFuolndZehpKy3q3NKPT5AQ0lMUVVWWl1gX3CUqJ6lrIPOQJWUlpRcp4yMp7GSkJCApW+zuXKEjY6LgoOIkZCRlJKRi4+MhoeI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+e7l7+nt6uHUxLyuo52X1euAwYGV/O/q6OTWzuiOkZGQkImEjZCSk4+PmaOpr7C3o6m3uLu+xL3B0c7Fwb+7gLS3xcW8vsHE5/T18vTy5q+/8vmSnquzraGh9fh+oeShmYqirayhj6PtrdLe3sOQuZ+GjK2P1c6Zs87b4tKi3MWLt6Wg4IOB/tCQpK+suru3uM37fJnkwPTWiJKTlZeboqClppP0/YiOjIqW5ILy3fDWwZunqpaWrry1v8Wx4rnigPH52PTX2tzb+I22ttyDh4mNh4qVoa2tqrHA1/Dp7/D8oJeXl9zG3b/At7arrPvPgIWNhZKTxuDPyc/OnMGzqYyL0pnNrp7J0dPAo82ts7bE7+eZt62shoaEgISA++3g2MPExMvZ1uHe7PqD/4CBhI6Tm4jCpqitgtyiw83NiMCbJYnus5LWuZoQHEUlHxUgOxMQJUGEq7DGxNfq3/CAiIqF7ceKuKOAVVKLfHlglJeAamlcUJKDrZpylI3a/dTZi69game6jmmXZ1hXb3SXm42Rj46HbFxEdlRLfW2Kl4KzaaFepYqprF5WqaSbk4F8bWNcr5Rgb25ta2loZ2ZkYV5cWldUoFJdaHV9fn5+fX5+fXx9fn16d3V0cnV8fX5+f4GCgYKAf34Henh6enyvaoaAgH98clpmcWpmeVyBTFdQmrqRiH5yeHV2c3Fhf3pzbGFxeoutiYCngISOprNcWotesY94qaCdU5+lpFOXf319eX50ZFOcioZ4eoWUrKyvq62keVxeXF1eZGdpa3J4foKFgZGnsc6fl1WZnJuRcYOHeJ2am5aRpKmjp6quX2V6gYOGgIWCg4iIgIWGgX1wXrNeZWx3cnN3fH16fX5/enh8eXV2dmmqqaixYWpta2tsbGppYmdsbmlnZruDbWlobW9wbGZmbGx2oqy0uV5fpGliWltdYpGpqquppqCanpuZlpWUnZ6doaOVoaanpqaahoeFh4SEhY6Ni4+LiJydnJ6hlaShgJ+foJ19f394c29+oJuZmJWampual5aTmqOkop+alJKSkoeHhWlvdXd/iImRkoyDfnVzfHZuvKuporS+wWdlaWtnYl5gW1pcW1qyWVpaWltaV6ejqVZWVqmfkGJnY3erXV5fY4ZRSVZTSlFVT09TTE9QT1JLSUdTU0xGSE1HSUZGgDk9QjxAdUl/enJkXrSyZWqLs6WunJ+npKRZll+bpqOjk5lXmJKin52Km5KomqSKpIecmZORjVutqlhdYaNZXKhZYZ9cl6FYoVmio1lkamlgV2pjaGiun1tmYFxcaFtaZbOqY11ctGBpZWdyaGJgb2ZnbWCxpol1cXBwcW5ua3B2gHd7fplnjoqXoKaWpFahoKadmqKcnpyUjoh+cmtmYouWUHtSX5yLi4qFenaOWltcW1tWVFhaXF1bW2JobG5vdGVqdHR1dnp1eoaCendybGdqdHRucHF3lKCenJ+dk3J5j5dZYW52cmxso8ljdaNsblpobm5qX2ubboCLj4BdempdgGJ6Y5GFWmd2fYB0WnxyVGZfXpdaWbCETlZZWF1dXGWR0ImXx5Gok15kZWdpbHBucHJlqLFeYmJibaRKSUFMNzAYHyE0MDxBPUJLQVZIU1ZdVmFVV1VQYztQXGo5UFNXUFVdZm9uaWt0hpyTlpeiaFxfYIx5iH59dnpzc51uRUVGc0pZZIqOfHl9e12NiINqapZ5pn9dc3l7cWOAdnRzfo6KXm9oZ1VTUU9UUqSSh4Buc3R9jYmQjJSdUZ9QUVFYWmJXfm1vdVZiUVNYZE9VQjZcQzhVUW5AT4JUVDU7cDUrVEpHTkxUUlucmaRXXF1YmoFaeGFjGBgtKCgkOjsuISEiHDY1j1lBVWvG4LS9eGQwODVeSTlXOjIwPEVgWFNaYWJWSjkoPDMrQFNpdGBnN2U3eHmUpV5Vp6GQg3RtTF5o6n9gcnFwbm5ta2hmZGFcV1NMhUBKWWl0hXUfdHRzcnJycGxpZ2Rma2xucHJzdHR0cnFva2pramyWWIVwgG9taF9JUmlsZl0zNhoiKnOthHt0Y2dgYWBeU2VdUkE9SGd9m3ei23V7jc34gYDFjNLtper485/09f2F+ujMxsjPx6WG98OkenN/jKanraepn21MTUtNT1ZaXF9lam90d3WHorLznlFJjZGPhWt+g3OUj4+IfKmxrLCvs2Npg4uPgJSSj4+TloyRko2KfGbDaG95iIKFiY2NjZGRlZKPlJGMjo99wsG+yW59gYGBhIOCgXl9hYqFg4DsnYF7eYGEgn55eYGBktbl9P2Bgt2FenBudHzH7uzt6+fe1NrX1M3Ky9bX1tzhxtHY29vby6mqqaikpaa0rqyvqqrGx8fJzL7UgMzHycrGmJqbkouEnsvFwL25wL+/wcC+usra29TPycO+vbusq6mDiJSZoayvvsC3rKabmKSai+za29Pm9PZ/enx+d3BqZ2BeX11asllaW1pZVlKalJtNTk6Zj4RjWVhsql9gYGFxLCovKyYoKignKCUlJCIjHx8eIx4cGhoaGBgXgBYSFRYVFysbMzEwKylFSSorPE1HRkBDS0NJIz8rPzlEQjs+IzUxN0NDPz84Pz5JPT06REI+QDkkSjwlJilBISdFJidDJjtDJj8mREMoLi4uKCQvKC4rSkgjLCgkJywnJCxHQigqJEMmLCIkLCkiJy8jKC8oR0QzKCgnJycoKigogCgqLDFBMUJDSU1RSlMtVFNZU1BWUVRUUE1NRz45NTNJTio/KS9JPTw8OTQyRi8wLy8vLCosLC0uLS0xMzQ1NTcuMTY1NTU2NTc8OjMwLCYiJSgoJSYoMENMSUZIR0M1OD5EKjE7Pj06OlW7gFtpXHkvMzc2NS0wRi8yOj46MUI5gEhdaEhtSictMjQzLiMzLSkxLzFeOjlzUSYrLy8wLi42U4hra3pNc4BdZmdoaW1zcHFxZLG/Z25raneyQRoQExQSAwQHExAUGBceIRMhHCMjISIjIyAgHSMVITI8EzY8Qj5CUVpjXlNUWmx9cnJ0f1dVVllnPUhJR0JIQD5XNh0VdBQcJzxjf3BvcXBVbGprWVhnd6BnSFxcWlBDVE5HPEFBQkNUTEk5NzY1PDx5Z1tVRktMVmJdYVhaXC5WKSgkKCovKkE4ODorKCMiJT8zKBkUJCQjPTpulrThsvSAktWLg7GqLyAdHRYZUE5RKSsrKEQ3IygZEX19fHx8fnx8fH19fHx7e3t6h3sIfH1/f39+fn6QfRJ8fX18fHx9fHt9fX59fn5+f3+FfgZ9f39+fX2QfwF+p38Bfot/AX6EfQV+fn9/f4x+h32Dfod/BoCAf359foh/AYCJf6J+An1+hH2EfoV9hn6SfwF+lX+EfpB/kn6Cf+t+h32NfgF9h34GfX19fn5+hH0Efn59fIV9oXwBe4Z8gnuLfAN9fH2GfAF9k3wVfXx8fX19fH19fH19fH18fH18fXx8in2CfIl9Bnx8fX19fI19kXyIfQF+k30Efn5/f4h+sn8Dfn19h34HfXt9fn9+fYZ+AX+HfoV9AX6JfQN8fH2EfIJ9i3yEewN8fX6Lf4J+hX8CfnyIepR7BXx8fX1+k38Gfn5+fX19hn4JfX2AgYGCgYGAhn+Ge4N8hn+FfoJ9hH6GgI5/AoB/h4CFf4l8Ent7e3p6e4CBf4B/gH1+f4B/eoZ7A31+foR/BX5+fn19AgIEAICq7JGCiZfw6u26vZ2ZkNql0JzQ3o7ZqaCvv6fjiKWtraCM9Mqqk9Po4vr8+/2xq4nTiojpgKLKhufQyum5g9b9+J/Vg4mG95qgssmhg42hpKWkoJ+gnpybmpiXlZKPi4mGgoeVpbi/v7++vru2tK+xtbWzsLq4ubq5trW0tLW3t4CzrK2ytviVvL2+u62N1o2uosSP3I6VmpaZqtDLofzH1bOeoa2oqo3O3JSQk5qWo7P112ze/KnLzNLSnWfAfJXR1tN0s8K9vMO4iG1saWzf4MK/xMvIys3M2+np9fbu8MSnoqWqsLS6wMvRz9HR19nU7tWAj/r49uuW3trb+oCC34CZgvzy5eXZzs/P84+nt76/vb6+t7is++To5ufm6Or7jpyqr62rr7Syq6Wjn5f739zb2NnX1N3s+YOJjY2Rk5KSkZOSkZKQh/3hzcvCvLGsreHs73fq5d7Z1NPPysHB1MvLzMe+t7e7vb/AvG7UzMzOy7/O1d7f397d19PT083G1oDZ19few8vKzMfIys7P0c/NzIiEh4qLkYzAwcLDwcTKyczIxMK9ycrLz8vLucbNzsXGv8K/u7++u7OuqJmNg+rf1L+ytL2+4IWKh4mMio+E+/r2+/r09vuDgfzx8O6B+/n5/IGBgoDbjZ6ZsuL19fSA3L6t0s61vdG8wMrBxL+/z4CxsK7Fw6ijq6+zu7atiJWnjo3xgdfEuKaUhfXk0NDT0tPii/yxqb63hJiHgcD0uZGB3M3U1L3f8vHt4Pi91+jM7ZGS9+z/+dbhhP+B7ZKI+PqB/oeAj4uShYqelZCSl4iamI+OkYv6/oOEhP+ChJGhmY2JnJSPkt7P1Me5rJ2jpICgnJuZlJ2hprLHquyFlbnp+IWLhP+Cg/DHyMK/tq+69Pr8+4PxopeRk53HsIOjoJ2Vkub49fDw5OH+m5uZmpmSh4+Pi4qHgZSyubvCw7yxwcXJzNHTu7C0ubi/wbmxztng6/H48u/4/ff48e7Kirm5vcHAwLeu8677xvfQkJW1r4Ct4Lvus9nw6Zjw7ey8+Y6zlbbCnarB2d/Ks7Xckd/I/IiGgciGlrCvyMe8xYyR7ZeG24mf77XqipKYnJudhvH+h4yJi5jNxNnp3PDNt5mdrJuasbirycu13cHl7YLjhurh6eT4ira48IiG9fyChpWUmqOwu83U1dTW4+fuqoiLhYDBs+ykoaOen9bEpI+MqPH1vPOGkp6kpsuUwL3Unamsr66g2cequMnqju+m0dOKnKKkh4yKh4Xu593a5unb2NfSwsrT5fD3+v6FhoqRlpmT/rapguWiztDTj7e2sO6H7beflBIbTyQdIDY6OXdJo5GztdbM5obrpN72goWC+O2C6YByl1dMUV6TjINmZ1lYVIuOz7Ho6qDtlW1yf3WdX3J4d21hq4hzaY6Xk6OjpaJjXUZwS02KTWSNhbCDb5J/W5q6sG2SW15apmVzgolvWGJxcnJyb21ubGtqaWdmZGFfXFlWU1Rfa3d+fn59fXt4d3R2eXl4dn19fn5+fHp6e3x9fYB6dnZ5fKxngICAf3dgk2B4bn1SeUpPWFtjcpWWftepr4Zycnl3e2WOkmhgXGVodIC5xGinuYWss728kGGjg3+zuryJk5qWlpqOc2ViX124tZqVlpaSk5WToKmorrOvsIZpZWhrcHZ6gIqPjo+OlJiYs6SYWZ6goJlkmpWRoVJUpYCiXLKroaGWiomIp2h7ipGSkZSUjY6Bs52hnqCfop6vZXB8goCAho6LhX19enOyl5STj5CPjZalsV1laWtsbm5wb3BwbnBvZ8GqmZiOhXt2erLBwWG9uLSxrqyqpp2ZpqChpaGUioiPlZiWmV+soqWpqp6ts7e3t7W0r62uraykroCvq6mvm6OhpaSio6erq6ijoWRfX2JiaGiam52dnKCkoaKdnJ6XoqSmrKSik5+lppialpmVj5STkYuIhHdqYayhlYJ3eYKHp2ZpZmZnZWhfs7Grsa+nq7RcW7SnnZdUpaOio1VVVVOSZF9bcKKztLZehU9LV1RLT1dOUFNOT05OVIBKS0pTUUlHSElJS0dHOkBFPkJ4TJ2MgndoXa+lnKCempqjZLl/d4F8WGVcW4mrf2BYl4ePkIWZoqKimqeCk6GKn15hqKOuq5SVWLBWqmNdq65aqVdSX1xfWl5pYV9gZVxnZmRiZWG0uVtdXLFZXGd0bGNhcGpkZJ2Xm5KFfHN1dYByb25ta29wcniKiaFda4OdqFlcV6hXV5+ChH19eHJ8oKKoqFejaWFeXmN+clVoZGNcWYaRjouLg4GcYmNgYGBbVFdYVVJPS1pwdXh8e3ZveXt9gISFdGZlZ2drbWlleYONlpuin5qhpp6ioJ6EWnp5fH9+gXpyvI6xj6yZX2J2coBzmHuccYOZl2SenJ1+rmF7aX+AXl9oeHpuY2aCUH1xrF1bV3tHTVdYZGVea1pcnnNvq2VyoX6hYGZqbGpqW6azX2RgYm+SYj5DPUY2LhoeKjkwPEI5SEpEVUhWWTBVMltZVlNhOVFcd1BRi5NMUVxcYmpyeoWKioOEjI+Ya1hXVoB2a5FpaGtoaoVmVkpXYKaWdJVQWGFjZYRhoaG2gJGNjmhgfnFvlY6WU5Bqfn9VX2JhWFpYV1WSjYSFkZSLi4uFd3yCkJiYnZ1WVlZcYWNdpHdxVmpTWVhaSV1fXHpFg3BVcDlIfktbV3hrf0RBDU5QTFtVXFKdcJmoWFtXo5xXnWYtMRcVGiY+PCseHiAfIEFzmK7h44vMejc4TEZVND5BQT41XUk+O1pZVWRnaGU/OSc6LzRpP0psYHhWMVJTSoq3rGaNWVxYmUlbjI5rRV1ydHR0c3NycG5tbGhnZmJeWlVNREJMWmyEdIByb2pqZ2ltbWtobm5vb29tbWtrbG1ta2ZmamyVVXBwcW9mUoFWeHVmMTMZHCw3R1l7f2zSsLOCZWVpZmhadnlYUj9GW2Zxm/2YnKyL0vb9/siOwtai5v3/7MTEx8fHtpmQjYOB7du0n5mRjI+Qi5afnqSrqat5WVRWWWBma3F8gYB+f4GIjIy1mFlFkI+PiVyPjX2RSk2RiV20rqSlloeFh6tsg5GZnZyiopudjcWsrqyurrCsvG15ho6OkpqnpZyRj4yEyaSgoJycm5igucpsdn6Bg4aGiIiKjIqMjIPx0Lq7rZ+Pi5Hm/f2A+vDs6+Le3djKxt3U2d/czLO0vMLIyIDOhOjV2OHj0uLr9PDs6+vi3d3e39Xi4dnV28fRztPR0NDY3dzY0c58bm1wcnh3xcTDxcTIzMrRzs7Pxdfb2t7X2cHP2NnFxsDEvLa9vLu0sauXhHXMvayShYqcpMl/gnl4eHh2acfAub25rK62Xl25pZiQTpaSk5VNT0xKhGdTT4BmpLu7uVxwKiguLCcoKyYoKScoJCEjIB8eIB8eHRsYFhgYFxQWGBgZLSFgOzQxLCZHRUA+QEBARSxPOTM6OSktJiYwSC0pITo3ODI5Qj5FQj5MMz9GO0YoKElLRUJDPCVIJEksJkU4KEojHysrKCUmLikoJSggLCsrLDErUVIoJIAnRSUqLy8wKSgwKy8pQUE5NzIsKCsrKicoJiUoKCgsNDZIKTNBVFctLipPKSpNPUA+Pzw5PFJTWVouVDYxLy8yPzgpMi8uKic5Pz07OzY1SjIyMDEwLSkpKiYjIR4oNTc5Ozk2Mzg4ODk5OTAlIiIhJCQjIi40OkFGSUpHSUxHS4BLTD4sPkBCRENEPzqQwIpeiLtYOTw5O0k4Ri81QkM1XFNQR5hbaUpcRS0mKC4vKiUnOyE5N20+PTtKISQsLDEwLjowMFdCQVYwNVFWjl9nbnFubl6zw2hsaWx1mk8bFQ4PERADAAQUERYZExwiHiYZJCUSIhMlICAdIhMhNko8OoBiaTlCUVBXXWJpcnVyaWdsb3tjU1NSSzlSPz1CPT5ILyAXKihbaWOFSlNaXWB8XZ6ps32ShXZNSltXcOGHXSlJOTw+PURHRj0+Pj0+ZV9ZWmhsYmJhWktNUVpbVlVSLi0tMDEzK089OSsqJSUkJSM5PjdRMWhiSG2Cm66O8sj1xBb9sJQyOCYeHRkZIUc1SVAqKilMRyhEAX6EfQx+fHx8fX18fHx7e3qFewZ8fXx8fX2Gfo59CXx9fXx9fX18fIR9hn4Kf39/fX5/fn19frh/AX6Hfwd+fn19fX5+iX+KfoJ9h34FfX5/fn6GfwN+fX6EfwF+i3+kfgJ8foR9hH4GfX5+fX1/iX6Lf4l+jn+Lfo9/jH4Bf5d+AX/Sfol9iH6IfYJ+hH0BfoR9hH4FfX1+fn2EfIJ9oXwBe4d8iHsCfHuEfIR9BXx8fH19kHyCfYZ8Cn18fXx9fXx8fXyTfQZ8fH19fXyLfZN8An18hX0Gfn5+fX5+jH0Bfod9AX6Gf4h+tH+Ifgd7fH1/fn19hX4Bf4Z+hH0EfH19fox9Bnx8fH19fYl8gn2EfAV9fX1+fod/gn6FfwJ+e4l6jHsDfHt8hXsIfHx9fX5/fn6Qf4R+g32Ffgp9fYCBgYKBfH5+hX8FfX16enqEew19fn18e3p7fX9+fn19hH6FgJJ/h4CEf4l8Ent7enp6e4CBf4B/f35+f39/fIZ7DH19fn5+f39/fn5/fgICBACAgfTZs4SayvGL1tjDkKmQ1MuRu8XenKCtxrnu3dfhiMaAlJbytKjT1try8fL5sKaM04GAmbOT33eK3uLcmMTThNWgi/z9iZalnurQs5X594KLlp+lqKmopaOhoJ+fnZuYlJGOjYmEipios7O0uLm3tLW1s6+3trO0tri0sbK0ubqAsaiusrXwj6mK1ZC0pM6d85ScgseMs7Sjp623vb/UzpyCut7MpqSlnaSsraekpKOrgcOgmpuXte2NrICsqryX09fXybW8yMvR2dy769LOzNHY1MfPztXa4NnP3/v5gYKA+uGxsLe7vb/H0dnh3dfX2eW1TI+GhIWDhp6UrIuOjoiAvK+8u6OS8NLThKXBzMS/wsnJwry7uLmvnYro4d/q4+3w7f6erre5t7Grq6yuraKVi4Hw3+Pp3N3a5I+amJudnZ6clpqem5iMgIWC//z/9+zu8fb5+PmBe317e3ng69vR09DMpaiprrCwsa+op62uubTOzMzN193X4uHk5ODS1N2A2+Xn4MTJy8rHxcjM1d3c2dXEvsbOz9PPvL69wsK/utje2dDM2NLQ0MzM0c+7xcfAvre6uMHDwrq2o+fZ3dzV0Mm4trjE2+qCkpGTlJCPhfX3+fry5dza1Mm3sLa5w+X4/Pf0597dzYeem67U6Ozt9Na8sdPUt8PSxsLNwMjDwNCAtLSuzcOnp6q5sr63rI6VpI+ZkbKwmonFibCDhePf3dbPxsXJycjJy8jcgJG9xsPF88bI7IXm3+D9i/T3gOrw1oHN8+vc+ZiD1ovThYeZi/LUzvvu+/T2iIOVhZKPl/WTlouUneyCg+uKhpiK+ebphOHQyaudoqOenqejoZuhqaSAq6yuvNSBmcH4zpyB7O72+4SChYmPi4mIxLrAu7iuo+eBgYWGhOCp4tSdx8mgm5aTkI6N9YH6+vSBhZCmpKGlqKaF9ICGhYSEkbe6tLGspZ3A1tbZ3OPZwLe6ur/DxMjZ9/7//v//9+rn8PDz9PrmptnAwr2/x5KLqNiCm5Cfj9mAv5K8gbn4/cjp5ZekgZGClLaMm8Ghw+zmyrbCgcu9vs7GuKnpprvKyNPTzdWQlonkw9vHuNCorM244pCN8vOBiI2HjpmqlOfh693p1sansr6onLvJssnL4OXq4+z75Ifu3+rk84y8/uTp3PX5gpaclJORnbS5v8DDytzi77WWlZuA/rGv+56el4jAzO/jk9PztLiMi6zxm7uguKWa/qDbnaKipZqPjpu9j5eni7u2gZaap/uGjYfv4t/j7OTSzMrK1szQ2unp5uzi7vLxhYiXnpmL3fD4rOHh0JvJt4PorYjQ66gaM1SGQzg+OkvBkqSZtLbLxuGh76Tjlb3n/oWFhISAWKeWelpjf5tSfH98ZI2f6OSl1bC/gXdxfnmcj4uWW4NWZWendm6Pi46dnZ+lZFpJaUlLXmxXnIZtlY2BY4qYYJdYQpyvYGVpa6SSeWOmolddZm1ydHV0cXBtbWxramhmY2FeW1hUV2FudnZ4fHx6eXl5eHV7e3p5e317eXl6fn6AeXN2eXumY3RekGR5cIFbhk9SRXBTd3ttcXZ8gYSXmnpsnryjfXx8eHh7end2dXV8Z5J3cHFzj7Jofl14dIFumZuamn99hoqSmZ2HsJ+bmJyino6Vk5qgpJ6Tn7SyXF9ftp10cHV5fX+Fj5admJKUl5+EgV1WVVVTVGxodlpcW2WAwXuGhHNloYqLWG+Ij4qHjpmblI2NjIyDdGSgmpiblZ6hoK5yg4uPjId/f3+Df3ZuYlqklJWclJSSm2hzcnV3eXp3cnV5d3VsY2llx8XHwLy8vsPFxcRoZGVkY2O1vKqmp6eieHFvcnV2dnVwb3d4g4anp6mnsLWwu7i/vrqvrbOAsbW3tZ2ipKain5+iqKyqpqSTjZOZm6GhlpmZnqGhnLS5trCrua+urqqora+boqKYlY6OkZybm5SQgaCSkI+Mi4h8e32KnalgbGtrbGhlXrGyra2pnJSWkIRzb3RzfJSgo6CglpGSi2FeWm+aq6+usIBQS1dWSk9UUVBTTlBPUFOASUpIVFFLSkdMSUxJRz1CSERJR2VwZ16FYIFbXKmjoJiWkZGQj4+QkZGhXmuGh4mCo4eHpFqYl5OoXqOsXKWgkVaOp6GVrWRYkFuSWFxpXaqUlKyjp6urXF5lW2JiZ6VjZl9lbataXq5dYGxirZykW6GXk35ydXdzcndzcXBydnOAdnd4hKFZaX+tmGZTmp+mqlpXVltfX1xZgHd4c3JuaZZTVFhYWZNskYpngoNkXVlXVVNUkEuQj41LT1lraGZpa2pRi0hMSkhIVXV0b2xnYVx5iIeJi4+JdWppaGpvcHWGn6SmpaamoZaQlJabnaCUbIx9f31+gF50fJ1dcGhkX4+Af2F8WHicpISd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vLu7vLy9voCzqKqihqXsrq/cpeqQmqaqrLOm5tn3r5+mrrS5vLy1scXXronE6dWgmJukrLW9vsH2wJiZmJ+arLCwgqqpt5zZ3tvj1+bl9PX29vPtpNjCsaKSh6DN4ufs5+bk6oSGg4OGhoj1yL3FxsnM09HS1trZ4rlOnY2Nj42I7/yNlZaStYDWwsnMyMTAusHPzsnT2NTLwLu0trq9ubm6tbWyoI3z5PSbtrOzra2vsq2tsLKyrq6qtcC9rJ2Q/oaipKWkpamhn5qalJKSj46LiIaCgYD69ff69O7whoiHiIWH6aqxtLW5u7Gor7OrpaensKuorausvtPX2MvOztR57+d3eO3s8IDu8ff3ysrOy8rIzNr39fTz7fbw8Onh593GxsbEyMa/tczGvr25trXBvry3trK0t7a2s7GrqrCvqaGjmefi7vyBgIKFk56cm56Ylo+Mi4mIgoTatqyyu8LFwr3Cus3Rz+fp5OTs6N7G/pqbrdDg5Ojv1L2y0OG1yNPGwsu20cW5xICtsq/Sy6qjsMq8vbmxjqKxmaSatJOOkZqgqaWG7cqW8uDl4uPj2c/Pzs3Kw7u4vMjMx9Hf6YyqmbWTpdWy0N3q4P/i/+/4gPyQ+ezyhOjV+vLV0ufp/ZOTlZKdiIaEh47f8/XhvM/Mq7ego6GoqqaeoqOmoqKqqquorK+3tuD8kYCgveeYpZiYmJeUm5OIg4KHj46Ik5mamJiBwsfKzMe/z4SFhYq2jKTR1tbW1c6dnp6el5SWkLC6u7u5taavsK6ytLGS+YOGjI6Pi46amqKjqKOx2NjZ4OTr4ci/vb7Fy8/I5/yBg4SEgv7w7/T1+f6B/MuEv73Dkt7qkvOF7JSg/IC9uc2HyIn3h9iGrbWnzIuBkbP7oMityO324uaQy7WA6ujm4aWSoKSPi4aFiLfEtpP+nt+Bu++gr4jE+NWs+YeMhZCXhPTz5tnx4/bcwpGov6SfvMO10MuPpZzi9frmhOXq7+j/kL7w7sWAg/KAhoWan6aioaaqssXG2Nzq74aUmoCW2aKj9Y2IgKjhncvk8/6vpXdzcW1xf/3Zx6SDpKOIk4GlmZCRnbOS+YC/o6SLkZO28PLygoDl2tPU2tfFvbi2y9Ph6O/q5uvuhouNj5OUk5eg7oK2+Pvmptu8iPGrkNvGpxo2XVoRDmuxWZ68pKe4ucjG3eXIm9iD29ab0PX16ICWl5GNhlpthYtTbZHx+8m2nLGgp6l6hHR4cYWIg4aLnaGpZW11fqmKl1VZqaNjUlJPVXNciINroHqcp011d3tgekhsZ0WVVYZcooVWdGNiYF1YpVZdZ3N2d3Z1c3Fvbm5sa2hcX11bWVVYYm15e3l6eXd3c3iDhICAgYB+gIGBgIB6c3RvW3OkeXeIXX5MUF1nb3hojYieeGtwdXuAgoJ+e4yihXOqybB4cnB3fYWOjY7CkHBycnd0goSHYnZ1enOgpKKto6yos7W3trOueKKPf3BhV2yUqKuqqKakqF9gXl5hYmWwhHqCg4aJkI6OkJKUm4WHbFpaW1lWoLBgX2BeioDPiY6Rj4uIhIePiYWPlJWRjImGiIuOjIyNiIiEdGOkl6hzgoGDfoCAhIF/goSEgoJ/iZCMfXBnsl55e3x6fYF6eXNzbGtsbGtpaGdkY2PDwMHBvru+bG5ubmtst36ChYWNkYV1eHp2c3Z1fHZydnR1kKisqqChoq9lxsRkY8TCxIDCwcfHpKquqaWfo67Iwby9u8PAv7q4uq6gp6Wlqqegl6qmo52flZObl5WRiYOEh4eHg4B9hYmJhYCBd6CYpa5cXmBibnV0c3VwbmdnZGJfWl2Wd25xdHp9fHZ4dH6Fg5WXlJOamZWHvF5dbZeqra2tf05KVFpKUVRRUVRLU1FOUYBIS0lWU01KS1JMTEpIPkZMRU9NYVpZWl1faGlXnYVqsJ2hoJ+gmpSSkZGOh4aHiI+Rl5eeqmJ0aH1kc5Z/jpSimKmar6WjVKxeqqanW6CWr6yWlaOhsWZkZmFqXFtZWmGdpqidhpKSfX5xdHN1eHVyc3R2dHV4dnl3enyCg6OzY4BxhZ5wcmdmZGVkaWJbWVhaXmFeZGZnZWRUeXl/gnx6hlVWVVd1W2eEiYqNjIVhYF9eWVdXVnB3eXl2c2lycnBycnFajkpKT09PTlBYV1xeY19tiIeIjJCUi3dubW9ydnl2kqFTVVdXVaSYlJmZnJ9RoYJUeXd5Xsa6a7FfrWJkpIB9eYdchlmgW49XbXBohlxYZXynaYNrgJaYiIVWdmNImJaTj19QVVRIRUE/R293cFqgYZlbhKpygWWTspZ7sGFlXWdtW1JGQjpCP0QuLhkgJjk5REY/TU9RVltXXWFdNV5fXFhiOkpXeHZPUI5MUE9hZGpn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wcC8v8zQxLq+uKussbKvqa20ta62xsm9s7OsrK+opqWioKShn52dl5yVl5WQlJSUl5uXkpCNhviEj5OVkYmHqp2dn5+fmpqcnJudm5bqgnvr5eHc3uzm5+Tk4fX+/Pv8/Pb6f4D6+H5/1MTAvL3BvcDZ1dLAt7PD4+Dg39vKm5aSjYWChIL07vj87e6xv8C6vbu0tK+uq6agmJeWlo+A7eeQp6qqoJqakZ+flJKOkY+DhID5/oOE2auwsL/H0OXp8ffo7PL16/D18/PZhID/mM/f5+vsz7eqx9e2zdTDwNW11cPIxICuravXyLKruMS8v72xlKe3lKSZt5mdoaeeqKWrqqqqweTGqp6jifrX4d7a0s/MwsDAvcPAvLrBxMbEwsfU1tzq6/b//feMi4eh+6Wrt5C0lKfqnKn+gJ2im5STifiEhoWCjJCXkJSPlJ+ipqaqqaain5+gqKm+4IKgtMv5jqCHjYCIipSNnKSbnpmVmpKSj4uToZiOmJ+hnJyWzsvV4+jbzZn0qtXm6ubPzMK7s66nnKGioqOcl6nQzMnHxL+ssK2noZybi4qSj5SYnJmTnqWqr66yocbf5+3v8PPhx87V4ez3/vL1/oOEhIKC+/T1+f2Cg4eH6qrc0pirvOeBu5+ppIDPyc2A3YKZxNuw2cm8n7WE/o2I3Z2uzs3q+oiYlZD67eDZzZyltLqinJKVlcfJwZyUsPKTk5L+rK6mhu7hpNeth4/B0+Ps4dr37ILtzqOqta2ox8TI09aSmrjn9v3qiuvt8O3/kaun7P2BhI2Sl42Qlpupt7fCvb6zsL3K2urC/oD98sSdn/n/75zD9M+Yyd2dlnFtbWpwfvXX0M6Nnpui2ICdjYiLnrPX0tGOkK+MiYP8hoKB++HY2uLy7czEwcXY3trj5vjr8fuGjYOAgIeYn5mS4Yi8gYb+r+3Fif2ul+LLszpzeXmEP1dQQqM1FLXFwMXJ5pDTpqOigKyB5oOw03iXko2Jf6dntpn4hu2jupVteHJ5YaNpamNwfJ6gmpmUoaGdnlZqc6iLlqumraRiW11kck2GYF90i4GnXFJOcK2tWTtgZIh0f4RfpW1oW3ZqnmRnZWJeWlRZYGt2d3l5dm5sa2uYbGtpZmRjYF1bYGdyent9eH1+f3+FfoB9eGxYhl1/gpFmf0dPXGdwd3x/fnuZj4iSa2lxdnuAg4J9enx8fIWkiHW5z8SAf4WFh5NqkHl2dnN2d4aGrHV2enOjcIawp76+r7u+X11fXpF/WlxeYGdxjaeqrK+1tF1dXV9jZWNftp+NhIaKi4qPkpWgooyPkFxeXl1afZ9hZIBjY7Z5hYSGkpaYmJmYko6PjY6TlpiRkZKNhoSIio2Nj46Ok5yfkoqNhnx9goSBfH6FiYGIk5SLhYWAgYN9fHl2dXl4dXNybXJtcG5scnJydHh1bm1sZ71ncnZ3dGlodWxqamlqaGhra2tsaWe+amW/t7KurLezs7KzscLMzMvJx4DFyWbHyWdnqaGXkZKZlpqxrqqbko+dtra3ta+ieXhvaWFgYmG1sLa7sbiJkZWPj42Ih4SDgX14cW9ua2NaqqRug3+BeHN0a3V2bWpobG1gXluxt19elGpua3V6hJCXnqWdnZ+imZ+inp6OXE+aYZiqsbKugk9IUFRJUFRQT1dLVIBPUk5HSEhYUk1MT1BOTkxJQUhPRU9MZV5fYmNeZGJjYWJjdY97a2dxX7GaoJ+blpOOi4mMi4uLh4SJjZKOkJSbmpuiqbG2sK9hYF9tr29zfWh5Z3Wsbni8XG1vbmhoZblfX2BeY2dsaGxrb3J0d3h9e3p5dXVzd3iEol1yfoyrYIBtXF5aXGVfaW1oaWZkZ2FdXF1jbGdjZmpsZ2dlgoCGjo+Lg2KcbIiVmJSHgHZybGhjX2FfX2BaWWqHhYOAfXpsbmpkYFxbUVBUUVNVWVlUWl9iaGdqX3iJj5OUlZiKdXqBi5ScopmaoVRVVFNTnpmYmZtQUVVWlWqMhnSMjaleg4B3amWIgoVVk1Jig5Bxi39zYnZYrWJbkmJpenuQnVdmZmOjmZCKgllZX2BRTkhHTnd4cV5bbaRlZmayfH+AbMO0eJ18YWeDQ0JEQjpHOiA5MBkkLzg7RkZFS1BSXmpYXmFaNV5fXVhjPEJAeY1QU1peYlhZXmFteXd/enpwbHV+i4CYeqOjn4NfYaOpnWVvhW9afJV1iWtrbXB0c5J7d3VTW1leh1Z1cXR0doF9cIBXWGtSUU2ZVVJTn4h/gYqal3x2dHmIjImLjpyQlJ1XXFJMTFJiaGFYiTxcODhrVWRSNmFBPFlSenqjp8TBbpaMTp1iMGZbWlxZZmSTcnFwVGpQlwNae5SAT0pHRD5GNnuO9oDdjYdvXnFqYkRpSUIs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hf77797W2IefoaKenZyVkIyPjYuMh/r//Pv3+P364vHy6u7y/fXu+fPt18Csqsnl4fytpevM3ubr8NW3qsPNuMzUw8DVu8rSz8OAsa6w1sW8qbjEw8e8tZattZSjoJ2nqKqkn6WgnJ+zyuTr6drY2tqsjITygfHEpY3s0dPbysXIxsnHysjX2Njc3+ro7/j2+Pjo59zt5eLg7+v55+r39vD89fj9g4eCgYGGiZSUlZmcoaKwwry55YiEjKbI2Ov9gY2kpqOmqaqil5WAk5Gbkp+oqrCrqKWenJ6ZlZ2gmaWlqKegn4TygIu/i7LJ+fn39vLw07i4sKurqKWjqKmrr6yrvdbQzs3Qza+fpKSgoKOfkp+jrbe6wrilrLWzuLWxu+Hw8fL29vLo7vr//4GBhP32gIWHiYiIhoD8gICFjZGSidmGh3qfhaiLlZ6Anp7E++KvicCE0+nTxMGCuIHt7v7VtLP5wJ7G5sPyluTX0siYw+v16uDZ17bBysSikbH2lZaVlZKBzITcuaCZj9SkzsPO2Nje+++E6MKeqr6spsO9wcTJuu3O7Pf36Yju5/Xu/5XBx8iO+fqDkJaXnJybnqGsuLO3pai1ytnp2LKAgP/31JiLuOzlodr8i4r0lJFnZWlra3J72OmCktfdoZSai4qSlZymjrDp/YTUiIuQr+fn8ID55tvT0ubm3N/l6djU3eHy+u/v8YCEiY+Tl5iao+OOuYaJhbHvyYb7tJfm071rfoJ8ayk5bi0oFxPK1cbPz/yY1//TrJWgkvvFnvyAYnR9tmnLhIx8p5WBWp6VXFVoZHVxa61pj32Oi42RlKpXVlVfdnVkoKVrZ66rcXmGk0+AVppqdY3MmFugYJKCfWdqYnGValyJZaOFY35kiFZjbmqdZ2lmY19ZVFpmcXV6ent5oXJxcHBvbWpnZGJfXF5oc3mBgoGAfnp1ZaV8qoWAhJdohUlJfYF1gICAf3h8gYB/bE+QmWtrdHp9gIB/fHp8e3t4eHiCo4duu9Nng3t+xYt+gH+Ag4WNpGF1eHt9lJuamrVugJeis2BjZGRiuGpqbHiFjY2OnFxdX2JkZFytYGNjZmdnaV6bjpKYnJ6eoqyaklppY2NhYWV6aWtqbWmA8IuSk4qLjZyfoJyXl5eVlZSXnKCeoJyclpCPh4ydqqyop6iqnpWLhoeLhYuLioqLioaLiYaBgoOCgH9+fX1+ent1c3Fwe312dXp3enx7dnZ2d3RqbnNqaWlsuKSflZKIgHdvaGttb26Lc3JwcW9zZrq9usLCvcPGvLfAxMKxxsmAwrxvc65naWpxa1++Y2FkYmNlg8O6ubm5toC5XrhjYLZgYmFhuru6cZOTkpCPj4KunZKdpKhcXbSwqp6WnWR3eHx5eXhybmpubWpsZ7e4tLKys7Wxn5+gmpyeqKKepqWdjHdqaH+TkrRwZpyTpayvroBPR05RSU9STlBXTlJTU06ASEhLV1FQSk5QT09LS0FJUEVNTVxkY2ZjYGNeWlxre46QjIqJh4VpUkyRUJmFe2OlmJqYkY+PkJWTkpKZmp2hoqeprLKztrKnq6erpaSnrKuwq62rr6uwrbW6X2JiXltgYmlpam1xdnd/iYGBnGJdYnKIjpijVV5vcXBzcnFraGWAY2NqYmlwc3h0cG5pZGZkY2tuaGxucG5qaleeUlh4WG9+nJucnp2biG5qZGFhYGFhYmNlaWpqeYqFhISFgmxbXlxaW15cVFxeZm9yeHBeY2tpbmtmb4uTk5WYl5WQlp+ioFFSVKGcUFJUVVVWVE6cTk9TWFxbVYZTdWR0YHVhZGKAY2WBp5VwW4FYiJaFenhTd1apoqCEbW2TaGaIn4qhYZGHg3xgepWdlY+LinFxdXJgWm2mZ2hoaGZaj1+qm5COhLF6hFpTUlFJSDwiNjMdIyk4M0JCSEhQSFpOWF5gWjVeXl9XYz1VZW5MlZhQW2BgY2JgY2Zvdm9yYmJtfYqWiXOAVKaklV1Vd5mXXXSBSU2WaYZpaWttcHZ7gYdLUYZ/U1GAdmtqcYF+UmGSnlGAUFJUaImKllKikIaAgZSRjZCTlISBiYyao5qWllFUWV1gYmJjaJE/Wzg4NldqWDdjRDxcV4Kzv83Is1FLekw6MzFvZFxgXnVtlrWVdmZwaLCEaaeALzlAZUO2jJWEoYVbNlNuOS1GVmprY5xOU0dTVFdaXm84Nzc5QUE9WGA8O211XGtvdT9hQW5DUWSZbkN9OkNbcV0fIyyKWUuofb1mPjwjNSRAZGecZWhmYltTSEtbZ21ycnNzk29tbGtqZ2NeWlVPSEdRXWhrbG1ubGpnWpZ4uJKAgG00NBsfPFVhbGtraWJmaWZkUy5VZVNQW2FmamxsamhoZ2dlZGVukHhow9BkeXR1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pM3Hs6Kak4Px/YP49IOFkKSPgvny+PLw8/jz7+/3/4CDhIeOjY6ZlZebp5jO8uz7qceSvIeeu5yH+IOTmqClmJiO1uTn9f+B8picgJiXnpagp6mpqKSlqqmxsa+vraaoqKiio6LOh7rl9vLx7+P8/P////zHwL66trWxqNPf4uDh4dTJ3NnY2djYvKmtq6mjqKemxM7X19XSxq+0tbrG0dvi5fb29vj5+PLt9IGCg4aHh4CCiYmLi46Oj4aIjZCRlpiXuYTAutSbyLqVgJ2shNWPi4WCy/fw29HXqPy1+YLkz9rQq9CUgJTmyIXs39W5m8Pl9eTZzdK6yM/Jp4259ZiZmJOA/oWLjNX/vfbn5Nz92NTi5Ozo79vjlqi/q6W/vbvDyrzpyejz7NyA5enz7f2WwfDjj+32h5KVmJ+rppublZior73E0N7l6t7QgLuNiInel4Cigdq+4ICC/pCXZ2RkaGpzy8TfzsC6rrau9IqHjtVsk4Odvv6bhZOXx4GJgoD48ODb19/hzcK7usrT2Ojm59rO1vGFhvz8goWSmJrlibeEhoas8dOI+K+W8NjJeYGC3WY+NhYoXhkT2uTX29iQ/KK3yczHw5P/54O+gHuK3I+Th7eWgLCek5STw8OsjYOMU1JoXWxzcY9vno2SjpSjY35/aVmubWZjXG+IgVxNcnF0lpObeGChXmldb4+DbmyTiGt7nnl7Wlh/VnOQSDluiZuHf1xjYmNhXFhcY2xzcptycnBubGpoZ2ZkYl9cWVVTXmdqWYxnjX6IXnVBgElVXml1rXyOfJ1mcXl0fIGBgH15l6Nta3Z4eX1/fn58fXx7enp5eXh4gKOKbMNzc26HdXt+hJGUkohYc3h8gqellpSsrKugo156maybhm1en3R9jIqLh4mIo6OelIaEhpCrZGhnZWVvdnFfVVNTU1hfpplrlmpmaGdre25wbn9qgIKSlJSVkI+JjpKbnJucn56Ui5OTmqCemp2Zk5KSmp+hqq+vraejoqKhn5eAiI6RkJKQjIeBjYeDhIaAd32Cf3t4dHRwf4GDgoSDgXx8eHZ1dXJoc3d5eXJvZGttbW9ra2ttbWhmYruru25tam9vbbzAxL/Bv8HCwr6/wsHBrmlmgGjJbHW3aWtwa2Vga2xoZ2xxbW7JwcHCw7CYdXZ1dn2Ag4OPio+Tl5SQi46Nj46Pb5iTmZOdqrGqsmBhY2tscIGCd3NxcG1qbWtrZWZjYGBfX1xcta+qoJudnZiXi39wbmBjXmJgZXawUpiKk6qus7V+T0pLS0dQU09OU09PVFBRgEpIS1hPUERMVVFOTUxBR05DS0lRVlxZXmBZWWZ6hIOFhIuNjWtNUJZKkYqOb5eRi5yMa1SFdJCOhHtxbF2ssVyxrlxeZW9jW6+vsaessbKrqLC2s1pcXV5jY2VrbXNyd1+Urq22dIVojGZ3h2lYpVRcYWZoX19bjpOWnqZSnWprOmZma2Vrb3BvbmxucnF2dXV2dnFxcXBpaWuFVneRnJeVloyen6SmpKJ8cGxqZ2ZkYYWOkpCPkIiCj4yEi4B2YmNiYFxgYGV7g4mJh4V7Z2Zobnh/hIyNl5eWmJqbmJedUlNUVlZWUFFVVlhZWlpaU1VYWVpeYF9yZqORoHCNjGBja1WRY1taWoWgm4qDhmuhdqdZkYGFf2aMZ1tklYRUlYyDblhvjJSNhnx+anV4dWFUdadpampmWLJeYmKWuICU4+Dj2+KmdmVZXV1cU14hJzM4M0FEQkVORVhKWF1eVjJaXVhVYjtXqXlMjZVUXV5hZm9rYF9ZWWdrd3uKlZmbi355X1paoGNQaVeLaXdDRYdlgmZpaGtvdphwg3h1cWlwaJ1taGqfWnVJVXmjYlFYWnhTWVVToJaJh4aOkH11cEJteoWIlpSUhnl9klRWnZlNUF1iYpI+XDk4OFZpWzdjQz5dWoiEjoe3ZFJuNlw4MTB4bHB7f1G1dYWPkY+MZq6aXYiARWbTkZeayI9kbVheZVvMqG5YUE0rLUpNX2pnd1JgVlxbY3JBSkk/L2NAP0NBZHBlRzdLR0VcbHdmUINRUzBBfmMcJYGES3jmo5BGKy0aLT8XESI8WHRzV15cW1hQSk5WYmxtjW5ubGppZ2VlYl5aV1NMRD5HU1tQg2iTgG84MxgjHi0+UmGNVmlneUhoZV9maWdmYlFfc1RQXWBiZ2lpaWhpZmWEZIBjY2qPfWvCbGtbcWdra214fXxyRFNYWm2Vkoh+mZublZhVbIWTh3ZiVYhbYXBubWdnZ4aEenBjYGNviFFUU1JSW2JfTUA/QEBFTIZgXnJZVVdXWmldXlxsYXaMj5CQi4qEiY6YmJOTlpWLhJCTm6Ohm56Zk5GTnaWptby8ubStqjerqqefhI6UmJib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qcnb2dva4N/D4vz//oCBgezp9/Xx+f789vH5hIeHiYyLhIOMjY6RkpKOjZGWmpygk8KOeJXm0p+Woqmu7POS+J73g/Px6eOyoemx/PCKk8zpgJPt1Nyv/vHl16KOnrCAtKSdlpuj1d3NpI+4/5qbj/2BiI+RioyXhNHRltH619zQucHU7tjhl4Szqqa4vrjBzMPcwOLu7tjx2+np5/iSssKIkoOOkZmUmqKlo6utqqSgp7q+ztnLztDc3+OXl5f+nPvZqo/H5vL0iZd+UmdubWhufYSLhpSnsYl/4Liy0b1ag+b1uoH+m6SbvYGA+PuB8+nk3tTi29bdy83U2ODk3dXh4Ovx/fX7gouPjofRh7j4iIKy79qN/bOf++TWLzI3ODk5NTEuKBcU6YGbtrnOrO+3yNPMtfSn4Lu3gI6Wg8in2G+wiaCHd3WP2nFbXF+sp4N8aXRMX7B2d2piXWFWXWdtaViqbXBtZqRyV4pxd3x+l52WYIBaa3h7gqFvaVWhkKh4iWtyanxqV2uCQVphfWVqxpxvaJVeYV9dWldVXZV0eHh2dHJva2ppZWNfWpt+YpV5h1lxg0VSXG5WDXVvbHZ2p2J7qYSFcXaEeoB4dld/i2RodHl7f4F/gH59fHx8e3t7enp5eHqGqoxuibvAhHFlaGFZWo1zent3pqSek7G4tqqiq7Otubavpq60spWKmqCuXWFlW5eKgomYl5SVrGt1f31+hoyIc15ZWVugo15Ub2tra25vcHJwj2PeiZeXlpiXlJeWjpGUmpeXl4CUl5SVl5aJj5ibnZqVmaCbl6GpsaunpaGIbWpwcHh+io2GiomGhYKGgF6glJ1aYGJoZHJ9fICBgoGAf4B/eoB9ZmlqbG9yb2ZhZGhqbW1wdHZ1dnR0cmRqZ2lqaW5pZmxqaGVjZWVmZ2Jlxr6wa25vaGTEra6ytbGqrLCrp7C0uoC/v52kpaqlnp+Qm5SUlJiWjZqYlZGJi5KTko+Qg4F/saGgrKewqKV2fXt2dnV0bHt6enVtbGtkbWtoamVgYVyzsq6zsa2rnYNza25waW9ta2dld5FTo6+YjYOXr7W2YXtOSkZLRU5RT01SUEtRUU9KRUpVTklCTFJPTk5MQUhOQYBJcFpUWGFkYGB4h4SDg4iFh5VSSX5uWk1NW4ybm5eXnZ+TYJeamU9RVG6YlppjU4yHnJhhY1adkYGEYXZuc3KLYKZmXFxTW2x2gYiNlZthg5ehVldiX15XXV9dXFlZZHBqa2hlaFOHjIZ+hYePaGhqaGppbm5uamtsaXFyeoSBeoB8gHFyimN/mFOSVFKfnZ+Ug1BRU1RRT5lzc3Nsa2xpcJqbl5WTkIaGlJCOkpKOfmVmZGFmZGdmfYmJioqOjnmQo6WiUlJTk5CZlpWdoJ+dm6BUVlZYWVhTU1laWVtbW1dYWl5gYmVcfX5gbqqacGtlaGylq2KnaqJUnJyWkXFmlIBzpJdXXo+pX2ygio1yn5aOh11IUltdVVBLTFV+gnlgVXCuamxjsFpfZGdgYmxeiY12w+LW3smff2dPNkkkLjU7OEJEP0VNR1FEVVxdVWBXXVdVYjtNfkdRU1tdYl9jaWtpb3BrZmFldHeHj3+AgoyRlmRjY7djnox8UWt7gYNgfm9wSWptb25xcnSAe3+GjXBxs5KAlZBmgod5U55gZV52UVGdoVSak42KhpSOio97eoKHjZGNg46NkpahmJxSWVtZUYA/XW45N1doXDpoRUFhXZBQWWtsbGFdUD40Ni6US19lY2t3qIKPlpJ+pnaqqOKAmaiU2aHAWHhJT0hLQlnVWTg4OHBsU0MsMSQ7j2pvZFpIQDc4PUE9MWJETVBNgFg+YkZDRUZbcnYuPEpiaVZCekcoMKh4hZTGmWZFNx0SIjUYKjdIMzJnkG5kkFpbWVdTTUdPeGhwcW9tbGppaGRhXltVkXZloYd5Ojc2Gyk3P0GAX1pYYWCHQVyLUYhiYGRkY2JgWz9JWU1PW2BjZ2pqamlnZmZmZWRjY2RjYWJwln9ng6eubltQUkk/P2BSWVpgjYqHeJuio5qUm6SbpKCakpqbmHlufYGOTFFVS3NkXWJxcW9vhVZeZ2VlbHJwX0tFRUiAZlBBWFhaWFpcXF1beFZ1vYGRk5GSko+RkIiMj5SSkI6LkI6PkJOIi42QkpOSmqOemqixu7Swr6qNa2dubXN6i5OMlJKPjoyQimKilZ9cY2VsaniEhIqOkJCPjJKPiY+OdHl7fYCBf3Zucnh8gICDiIuMjomKiXV/fX+AfYR8eYF/fHl2hHiAdHnv5NOCh4Z+eOnLzdTZ183PzsnK0tfh6eu9x8jPzcTEr760trfAv7C9u7iwpKexsbCvsJ+gm9nDw9HN1MjEkJmYk5KSkIaamJiShYODfYN/fH12cnFnxsfCxcO9u6yIdm1pZ19kZGFbWG+ITpm+nYuBobzEyGdsJiQiJSQoKCaAJCUlJCUlJCEeICUhHRkcHx4cGhsWGx4cHTsqLi8xMzQ0Q05NTE1OTE5XLilGOy0oKDBSXF5aWV9hWzdTVVQtLjJGa3KMSk18fpF6QTg1amphXEhdWlhcYT57Qzo3MzU9QUVKUVplQU9gWy4tNTMzLTEyMDAtLjQ6NTYzMTIpQkRxQDs/P0U2ODo4NzU3Njc0NDY1OTo9RURAQEE4N0MuOUQmQyclSEZGQDokJSYoJiVILSkpJyYmJC5JSkhIR0VAQUhFREVFRDsoJyYkJycpKzpBPz8/QkQ5RlFQTCcnKEU/QkFCRUlJRkdIJycnKCkoJiaGKYAmJykrKyssKUFxgGFwZXR8SEhLdnNAbT1RKEtLRkM8OFdIdl80OldtUV+MdnFOZWFcUy4eIygpJSIfHyY+PTUpJjeTYGBaplhdYmRdYGlcZ1VMhJmYnpBsTTUgDhsECQoSEBUdGhkbGR0ZIiMgHiIiJR0bJBcgZjI6R1BTWVVZXoBiXmNlXlZOUmRmd39ta212fY9nZmbVS09IRSw4QURFZcHZgdHb4eHg4+Ps7fHQy7XJ98iZscFxSEE0J2xFSkZRNzhscz5yamZjYHBraW1UVFxfZWdgVF1aWllhWFowNDMvJj4iLzMZGCotJhcqHh81PHmssNHn+eflwp2IlYRaKhA4OTMoKFAuMjY1LjsuT3DxCHx8fHt8fH18hX2CfIR9hHwFfX1+fn6EfwF+hX0Dfn59hH6DfYZ8Fn18fX19fn5+fXx7eXp8fn9/fn59fn2Gegl7e3x8e3t9fn6IfwF+jn8Nfn5+fX19fn5+f39/foZ/hH6DfYh/gn2af4p+hH2EfgF9lH6Ef4l+jX8Efnx/f4p+An99un+Dfr1/g36Ff61+iH2YfpV9An59iHyCfaF8Ant8j32Cfo99g3yHfYJ+hH0Df3+AhH0BfoV9AX6NfQR+fn19lH6HfZh+B39/f4B/gICFf4aArH+DgIt/loAHfXx9fn9/foR9B3x9fn1+fn+GfgR9fXx9hX4Bf4R+lH0Ffn9/f36IfwN8e3uOepR7BXx8fX5+mH+EfgV9fXx8e4V8BHt6enuOehR7enx8e3t8fHx9fX5+fn+AgH9/gJh/hYAGf318fH19hHwHe3t7enp7gIqBAn97hXyCfoV/BX5+fXx7AgIEAICMxbyCj83G0O3wtNnd9puUnI2E4MuyzpHO0+/5tfj+qImZjI7L1qfe6by3rLuok+jM6/6Az4LmkrOH5LOBpZOI0fzwiq7ddLSy5NGLrIfc+4WK7Ye+wrfa1drkreDDhpydnJ3JlJWeq7eyqqOah+O1h83QhrTl/Y2Rj4edrrXEjYCS88WXrPLhw626x8C6u7u7ubm2spX4n6e0tbe6uri5uLi3tre4t7W0tbS1tbe5tceR6Oz6kdeilYyF4KuwtJzVz8amzPH77vDr9oeF9PXn8YSKjY+PlJWVlJmXmoz05u/t5+zu8pKzv8DBwcDEw6+Zk/htmZ2/qKytsLKytLL1i4CEtrS3wcXS1NjYzsbGzMzO0Nje4NzDqJqdr8bR49vV1tXX0crHxs3Oqp+ttK6vusLDxtDKwb67va2G/Pzu4unm3dbZ09Lpgougr6+sr6+usJWbmZeal5eSx7e3uLW9z9Xe5+//gYeG9YaHi5CUk4WMi4qJiomGhIGCgISGhO6GhICIh4GE3+Tl4OHh4uTq8e3q5er00+Lf283TzbrH09HJvru5v8G9uLi4vKWB69nh9YT/9+7o5+/r2OyZo5+dm5iWjY+VlJaPlIuGh4WGjImIhoaEiYWCgIH/8Ma2tLa5v87W45Weop2L8oH1x7DGzc37xr+3qryoxse8u7++qMW5vYComKvPw62Ssby3urWulKywh5zooZulq6GRkpWWrsPd5v2EiIaChIiKheH9g4iDgP/5zYD0+vHu8ISx6Ozx5/Dx5O/v9vvx/ILt5/Hu7fHzg/zCqJ2gnpaUvIeGhoCFiIrQwcXCsbKz2I+PkZSWmpabo6Cpoq2sioeGhoeHhIiiq4CqqqOqqZ2ZpKGbm5yns7W92o6jufmGhoSDguL/g4H8+YD+5oOCg4WIhuXJzMjCwsDD0Pfz7+nu69zZ7evq6ejr1rXEv7u8vsLBzNvi5e3q6dzjgYKBhYWEgOT/iYmIiYqMg/WEh4eIjI2QioaNj5CSj4yPlJiZoIXW/buhxauD/4CTpKnr4oWl7v+E+/vz67mhjMqq1IvM26Gk2OHnvoOB+ejYi46crLGnoJ2tuOvk06GMs/+XhYCFi5CQiZOdhYiak5WM45fMvsLRxa2ryautut7B19PMycjK0sbj9vPn++uC9/Oj3ufEjZX7/4WJlJiPj46Tpbm1uLm9u8fJy83S14Dz/pGTiYbro/SR1oe92t3+jHCHrquvsmdujZijyshudniAj4adluC1893CjIyPgYGB/vP39vPe2MzT2tvX29vY39rg3Nzb1dPY0NXg6YWKi4uMyfem64aDsIPdi4Gyovbp0xcYO0OQkol2bFMXN8rXs7Sz1beI092jvZ3OgHpyn4DDwpReY5GKjp6eepyfsIKAcGZgqZmEkWCHip2jcpSebmRxaWiSiGuNpoF/eIVqVH9wiZJLfk6ilZBej3Vfc1lepoKuYnulYIuGnXZZWk6GoldcoV+HiYvIydPakKaJXWpramqMYmJqdH58d3JrXp6AYI6KT2R5g0xUVVNndXl6XSZhnJNpdKmTdG9xdIKAgH+Af357eF+ga3B6e31/gH6AgICBgH9/gIZ+gH+Af5FzucHTcphoYVlUkHd5e2yVkIiCjq+5rbCttmNhsbOor2BkZ2hmaGlpZ21qbmGbjZiWk5eZnWN8hYiJiIeLjHpoYq2xZ2d0aWxsbnFyc3ChYnZ9fH+Jj5ucn56YkZGXlpaXnJ6gnYhyZ2p4iI6bmJqenaCcmJWTmpt6a3R5gHV1eH2EjpqWjIuIintbqaidlZyYkouMiYqeWmJyf4F/gYB+gmxxcXBxcHFti36BgX+KlZigqbC3XmRkuWRkaW1vbmNqamloaWllY2JkYmVmZbloZmlmY2Srs7OvsbCxsLe8u7i1ucCht7avn6Wjk56npJ2Wl5OQko6Jh4uSgF+rgJmhsF+2sKioqrCqmqhyenh1dHRzbnB0c3RsbmdlZWJjZGJjX15dYmBdW1u0pIFwbnFydHuEj19namVZrFOagYWUmZy7ek5IQkpETU9NTU9PR1FMT0lFS1VQRj1KTkxNTUxDSk1DZIpbXmNmYVZWWltqdIGIl01PT05RVFRRhplRgFRQTZyffkyWnJiXlk9qjI2RjZCRipCTl5mVm1CPjJKQkpeYUp53ZmJhX11edVRTVFFSVViAdnV0a29yjV9eYGVlZ2Zma2hva3JxW1lXV1ZWVFludHRybXBuZmRqa2VjZW93e4CPW2p6o1dWVFVUkaRUUZ6dUaCSVFFRU1VTj3d2gHNvbm1xg6KfmpWYlo6KlpWWlZSVhW12cnFydHl5gIqOkJeUk4uSU1NTVlVTUI2iWFdVVldaVp1UVlZYWlpcWFVZWFlbWFdbX2FgZFSExZl+j3pes2NnapugXHKkpFWhop2XdmZWfGuEWY6ieXiVm5x5U1KekYhKSVFZWlVRTlhkgI6HfGBVba1pXFldYWZmYGhxX1RUT1BQjnW5vtTW0bOLd0g2OGdXZmRkY2JcX1hnbG1nbmM3YFZLc3drSVKZnlNWYWNWVlZaanp2d3Z6d4KFgICEiKCqX19aV5tqnGGTTGh1eLh7XF+OhoaSb2t9b4WZklZqbGVuX3N3rGmbj3dVVFZZUVBQn5WanZuIg32EjY6Mjo2Kjo2RkI+MgHt/dnuFjFZaWlhYf3xcbTo4WDdaOjRGQ2RllTY2YEqKeFdJSVc0V3x0XFxfcH5knJ94kIHqjo+HzYDZsm07NlBPUFRRQmNdYm9bODMzYl9SUCxFUF5eO0xUTlVmYF53WD1QZVVaV11UQlJASVApSS91bmA/RlNZVi86dCeoYlyfmMi1fjgnFh07VS40WjA5OEaqsrnDgpZ7VGFiYWByUk9YZXNybmtmWpqCaJ+KPzgzMyMtMzVMXGJVSB9MZHxbYIpdSVRJQmVnaGdmZGNfWz1rUFRfYGJkZGRmhWdEaGdmZmdmZmZnaGd4Y6a7zGl/TkdAOlxVWFpVe3ZuaHGVoZuhnaZaVpmclJlTV1pbVldYV1RbWFlLb2BpaGdrbnBKYWmEa4BwcWJRTYxoVFJSVldXWVtcXVuGUmh1dXmCiJWXmpqRi4yUk5KQlpeYlIBqYWRygYWQk5ucnaKfm5iXn6B6aG50cnBwc4CSoJ2Qjo6PgFupqp2XnZmPiYyHiaFdZ3yHiYmOjY2Pdn1+foB/f3uViYuMjJmmq7a8y9dsdHXTdXd8gYCCgHF6enp5e3t1c3FxcHZ4dtx9e317dnXDz9DM0M/Pztng4d7Z3ufF4d3UwsnFsMDOy8G4vLausK2mo6ezmHDKt7rScdvVzMvR1cu2yIuXlZOPkJGJiZCPjYSJgHl4dXVzcXJtaWdubWpnZMeyhG5pZmRpcXqDXGVmYVe/U5WEioCgqKvMayQjJCQjJyYlJCUlISUjIiAgISIgHRocIB8dGxoXHSAfNEQrMTQ2NTAvMDE6QUlOWissLCsuMTEuT1wxMzEvX2BPKlFVUE5OKTpOT1JNUlROVVVYWlNWLFFPVFRTUVMvWUEyLjIyMC8+LS0uKy0xM0I6OTkzNDVKNTY2N4A3OTk1ODc7OD07MC4tLS0sKi46Pj09ODk2MDE1NDEyMjk+QERKLTQ8UCkoJygoREwnJkdGJEhFJyUlJicmQS0rKikpKSw/T05LSEtKRUNHR0ZFRUc+LTEvMDI0Nzo+QUNDSEZGQ0cqKSgpKScmQUwpJyYnKCspSScoKCkqKisoJ4AoKCgpKCkqLS0rLSU9jpqidkhK2V1HSXBsPk9dTylNTkpGPTYtR0JIMWCGcm+RjodpOjNjXlMhHiInJyYkIyYtSD83KSU2lF5VVFtgY2NbZWtYQCooKCxVTYeJl56TfmRNIAwKNzM8Pjs2OTU1NDg1NjAwMBonHyhBR0wyO36ESIBLVlhKSEhMX3BoZ2draXR0aGhrbYeRVWFaVp1XTC9SJzU9P8K7j3vUwLnh19/3j8/Qz4TZ14iQfp/D0TxOREo7PkA5NjVsZWxycmFeWmFqbGxua2dpaGpqZ2JSSUpARkpNMzU0MC5ESzszGhgpGSkYFB4gOD+EhIHamvqsibm93RSI0llHKSomJisuPTwtPErckJKQ5AV7fH1+foZ9g3yFfYR8AX2EfAR9fX1+hH8Ffn1+fX2EfoJ9hHwgfXx9fXx9fn19fn5+fXt5e35/f39+fX1+fHp7e3t8fHuEfIR7A3x9foV/AX6KfwV+fn59fYR+h38Kfn9/fn5/f359fYR+iH8Cfn2bfwR+fn5/hX6EfYR+AX2HfoJ/hH6Nf4h+jH8Efnx/f4p+An9+uX+MfpJ/jH4Ef39/fpV/AX6Gf6d+hH0Bfol9nn6LfYV+Anx9h3wBfaB8A3t7fI19iH6CfYR+hH2FfI99AX6HfQF+iX2Hfoh9qX6Ef4WACX9/gIB/f4B/f4aAqX+HgIJ/h4ABf5WAB357fH1+gH+EfQJ8fYR+AX+Hfgh9fX1+fn1+f4Z+k30Bfot/AX2EfIJ7i3qQewd8e3t8fHx9hH6Yfwh+fn59fXx8eoR8Inp6e317fHx7enp6fXt8fHx6enx7e3p6enx7fH1+fn5/gICcf4WAFH98fHx9fXx9fHx8e3t6enuBgoGBhoACgX6GfAF+hH8Hfn17fHx8ewICBACAo6Keoa6tmI6A1seZkY+SmMK7wL6mnIrwxcrf4tzh5IOj7ISY4o6Ri+vP7dCB9sTJ8I+Tob+B54zBnpOFtemdrpa5h4zvjMOdmJOzypiQnYyEgP+U39/H1eiCipOcm5mMnP+hxJqTifrc08SX39D2r+KFjpicm6Cfm5eUkYuKj8SAoeup6Lj81pu+vbu9vr+/v769urmUi6eyuLm6vb69vr6/vr67u7m5uLi4ubu8vL2974HUz5zlh5jIqoCxssGFrLG2qsG56YD7/ISMjYeEiIuZmZido5qXop+mqrC6s5f3+Pf+gYSGhai9xcbHyc/NzLyIdJys266vr7O0tLayio2A37nAyMXDwcbGycfW19zY3dXO0cWoqKmsrammnJ6ou8nV2dfZ1tPOy9DRycK3uL7O1NPX09HSz83Jv7GplomC+PLk3Nve3N+FwsG9ubKxr6imoqGeoZ2Z+N/Pz9bS09HOzM7PzczJw4WIh4iGgoXyhPr+gYP/gIGA+fr6gP7qhYBNg4L+geDc39nX0M/U0tbQzc/Kx7ra3t3Z0c7Kwdrf4dvWz9TQyMq8trqygdzM2+iAgYCD/PKGhouUoKqvq6amoZ6UlJSTmJaSj4qHiZCEjoCTi4mFhIP/+Pjm097j8vTs8PWRpqWM3pKDuqKrsLXhv8Crp7qiw8Wyt8LEq8G2ua6bq83DuZewsayzsaqQqJqagqKp682vn6urqKGfsOeNioqSjoyDgob58fLw8u/x9r2I9PL8/oOK0fT5+fbs8+ng9PDy8PGD8uyA/vDx6vL7ioCOkor/gvnWhIaDhIyOke7G0c2+trK8iJGam6mtuZuYmJygoqOktb/Cw8/W1LbCvbvBxsa00d/v4eTtiOWlx+2GhYHuiYqHiIiFgOf8/fv7/f/lg4OEhoaIhd749/v/gP/y4vn29O/x7uPh9fTy8fHu4MXe5d7f4N7Zyt/i7PHw7YDgy/uEhISFhIT4/IqJh4mJjIyAgYyOkZGPkZOJhoSNkJqhop6Or7ytuIvggZeVybufp+LFh/6e8PL+hYD3vaGMhtD60d+ChuP52L/Q2tT46cGQmqexsa6pq7e87e/Ypo6/7oCDiY+SkYqapOKYopianpqPlYreo+akttTfyJ+ogICQtr3BurfbzuH3gfeRl4y4loil4I7oi5WGhoyOlZmen6eooaWksrrDvbm3tr/k6/P7+b2Z+O7Xs+yf3822xN2Bb2dxkGdsa2hzeoZWeHJramxu43iArpHqxuyGjL78h4OCgID29e/ZycG+xdXUu7CtsMTb19TZ3e/s8fT2gP/v7C/zsICzxvKDsfzai4Kyof7u4Bk8pJiMPjMoIRwWLamwtrrF2aOfxYZ+kZF5i4SggoBzbWtvenlqYlaPh2pkY3V4h4SIhXZsXqGDiZqcmZmcWGyYVWGjbGxqs5Cki1SRcHCJVFdgdVGmj5p0alR5qmlzcYdkYLFymX1qW2ePZWBoXVhWrGeenpGxv211e4SIiG1wsG6LaWRdqJKPiWuejpxlekZNV19la2xoZmNgXFpWfB1igGWafK2RaoKAfoGDg4KCgYB+fGFZcHd9fn+BgoaDgIKCgYB/gIGBgoODhISxYKCjfsNvd41uU318hFt0d3qCg36oXry7YmdpZWBlZ3BwcHN4b2x0b3R5fIN9Z6Gjo6dVV1dYdYaMjY6RlpSXi2S3anKEa2xscHJydHBZXsZ8f4SCgYSLkJSToaClo6WcmJiKb25wcXNxb2lsdIWSnKCfgJ+hnpmZnJyUi4F9gZObnKGdm5uamJSMgnppX1ain5iQjo6MklqKioaEfn19e3p2dnR3dXOtkYiJkJCUjouMjoyMjo2PZWlmZ2hkZbZkw8JhYLxfYWC8wMJiw7VlYGVjw2GopaqlpZ6fo6OgnZ2dmZWKqKmmpaSin5mtrq6qpqOmgKObnY2IjYdemo6bollaWFqyrGJkZm16g4mHg4N/fHR0cW9zcm9saGVlZmdoaWltZ2ViYF61ra6ZhoSLk4+Plpthbm9gnVxUfXWAg4KkdktFQklCTU5KS09PRk1LTklESlNOTENKTEpLS0pATEtgSFNfi3tpY2dmZWBeZYlUVFNWgFVUTk9SmpaWk5STlZl0UZKXm5tPUnuRlZaUjZCQipaVlZWUUJaQTZqQk5CWmlRbXlmjT5WHU1VTUllaW5J0e3tzbW1zXWNoZ3R4gGplY2Zpa25ve4KDhI2Rj3mAfHqBhYZ4hYqRjIuRVZRpfZhXVVKbW1xZWlhVUZSgn5ucoKGRgFRSU1RUVVOJnJ2ipFOmnZKhnZuYmpiSjZuZmZiWk4p7jpSPj5CPjH6Mj5aamZeKfaJWVVRUU1OdoFhVVFZWWltSU1pcXV1cXV5XU1FYXGNmZ2Vbb29lcXS5XnFsj4RlZ5eQXaxtlZWjWFSdeWVVUoKjjqdjYZ6ul4SOjoaclHVLgFFXW1pYVlZcZJCOf2RZdKBYW19kZmdhbHSWXVhTVFRSS05Li33V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iqmxtsnZ18bkgoSJmJmampKjtbKzrrCwq6q8vsC9u7y7up6Ejpyps7u8vL3F1Nvc3tnOqHa3uYOzr6+ysrO1spONcNCvsbG4ta+pqrXH19/f5+nq+YaG6si0q7e6urq4ubKxqa2yxdbd4N/c3dvX0tjj4uHc2NbW19nY2NjU1c++ucG0oZaG9euUzMnHxMG+v8DAt7y7ubKtmZSSjoWA/Pne3NTT29fV1+WAhYuNi4qAh4mEiICKiYqJhoOFgYDmhIKCgPr54rCqpquvr62vt6unqaOjnt/s7Ofe4OXP6evl5t3e3dHIy8HBubqy8/SEi5abnaC0wMC9vLm3tLKxsK6vqqWflpiYmZ2fmZqbm5mZmZial4uIhYaIh46TlIHz8vuDhoiHgYrovJmQxqOkp6nQtrato4Cvnr+/rrXCxrOttLepmK/MusGgqrKvra6kjtSigaC/iI6QkvvjybW36pSjmZmYlI6OioSDhISChYD28f79+/3lwqSZ8IKC/PiBgOvk7fH3goKEg/WEiYT5//jzho6PjYqPlJL6gomLjISJgMLM1ci7tbbnn5mcnqmxxZOcoJ+qsICuqdne08/X28e1xc3IyszKuNqMzZa944j1i4iKi4mFgYGKioiKiIXj+PX08fbx5emEg4KCg4T+9IWDgoODgvfz/f7y5+PeyuH69vf09fPp0uzm5ejo6OLOzs3PycW/wL3og4WHiIeGg+WAhYiLjI2PjoeHkZOQkpKUlo2JkpqbkoDF+vv96MKStZK689Wmm6zluq2LivDyhIWB5aaLhoeMjI+MxsbLyuLepuDjx9KlsLyzubWzuq2zuvL76LOayNmDipCSjoucrfmIjaeloqymlpiNkoqLhdWk0KrbgIXds9KEla/Xw93o58n54N+I9feMoufujpaHjI2Bh46Vm6eopICjp6m4xcHKx8jP5uXX7OXq0vKOhfvPkLeX8vapbWBbVVpXWFlYaKqxsr3YZVpZW9r1way3kM75hLjxhvTr6unt9e/o593c19PNz9HLz8XGvq6yuMvi8fPm5en++vv+toG26+XZovPbkoS3oPzq7RozNjAqKCYhIh0WK+DSsolhSwxxiYt6jmqRi8HZ2MWAgl+Af5t+cWlmXaaeiXR3cGp4kZCPi3xmnImXoJ+cmKquWFpkiGBrcp1zcGWGk3hGUWBlWnZSoYWaiXVtV5J6g5eioZ6lXI5zkXF9XmZeaFxUo1NinK6tcNPU0tTc3tTA0ZqqlIZ/Y52XVml6REpSY215eXVzcXNzcm5qaGRZlF0MgoaabniuY4KCgoGChIMOgYF/f32ceHt/f35/gICMgYCAgYKDgoSxYnt5eqKXdMt6o4mJnV5zeHyVkY6EoGBiZXBxcnJrd4OCgX1/fnt5iImLiIaHhoVrVl1ocnuChYOGjJmhoqKgmYC3gXtPbWxsb29wcGxjZLl0dnd7enZyc36Poamora+uumRkqIp6cHl8fX18fXl4cXV9jp+kp6impEijnpqcoqKhnp+enp+io6OkoaGckIiHf3BmWaCXZJOQjo2LiouLioaKiYeBfXJwbmliXLavlJSOj5WVkpGqYGNoa2poYGhraGiEZ4BoZWNiZGRjr2VjY2TCvKd9eHZ6fHt7fYB7e3x1dXCqtLazr6+yo7a3sbCnq6ykmZmUkoqKgbCuYGZtbnN1jpual5WRko+Mi4mIiYV/eXFycXJ1eHNxcXBvb25vcG9nZGNkZGFnampVmJecUlZXVVFbmoZjXoFxd3l9l3BHRUJFP4BLTUlLTk1HSEtMRkRLUUtOQ0dKSUlJRUR9cElPYkxWV1maiXhpbIlXXlhaWFZVU1FPTlBRUVFMl5acnKKkk3llW5FNTJaXTU2Mi5GVmU9QUlGiVFVQl52Zk1JYWltaXF1dpFZZWVpXWVJzenx0b2ttl25mZ2hyeYhjaWlqcXZ1c4CWmpGNkpWEeoSIh4iHiH2JWIBeeJJanVhWWFhYVlRVW1xaW1hUkZmTkpGYlo6SUlBPUFFToJ5XVVRUVFWhm6GhmJGPjHqNn52dmZiWkYeblJOUk5WSgn58e3VxbW5vlFRWV1hWVlONUVRWV1lbXFxXV11eW11eX2BaVlpfX1t8nYCfoZJ2aZNyjLSYcm1rmYJ0XFuVlVZXUYxmVVJUWF9oaIiJjYugnXWVl4CFX15jXF5dW11XWGGQlYptYXiTWmBmZ2VibnmbUFJbWlhaWFFTSkpGRkmKf9nC7YOK87GiTEZET0dTV1hMYlldN1tVNUFqfElUVllYTlFVW2JtbmhmaoBpdoB9gH6AhJSThZGMjYCXWVOcfVl4bKaVhmReWlVXWF1fXVqMipOZsklRVFCNpYh/gFd5kE1xnFaSjI2Pl56alpOMjYuIhISFgoR8fXdn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jIqIhoiKjImKiYqJiIXxgoKFgYH64aqus7Syq6mwraehoJ6amM318Ozn39/X4Orl39jXgNPEwcjAvsK/v8HFwcPBvru8qr7Kyr+6tbyxs7GvraWjoJyUnaWnoZ+goqKin5iVmZePjYqHhoyTlY2LhoGGg4GA+YCD1LObk82xvLy/1qS3qpy0nbW2rbbAwLCrrrakj7S/s7mXqLCoo6uCoeKZmbHEmYuPjJCMipSVpqSfoaCdgJuYkYSFiYyNh4uEgfiG+vmEh4LZqqi+xdrzgYP4gIODhoqJg4mZmZaYl4+MipadnJ6WmJmEiYeRi4yMis3P3t7AuLa8l6Kmnqevvq+SnKSnqKyvsM/U19bl6ty9zNPY9o6jsOyHi4mKi4vv8/Ds5+nh4OiGiImLh4X92Ojh7O7ugOPXhYmGgoODgu2DhoSEg4OE8tbUzcK+wsXH6P36+vv49O3S8/Du7u3r6dTCysXBvru7vs/5+/Pm1tTZ1OSBiY6QkJKQhP2HkpCSkpaZmo2D0Ova5dri6eni+c+87b6RhJnqnrb85dzph4fr06CGg4WSoaX3z77CwL7N0Z2v+KTigKm/tsG3treqsbz7g+23n83miY+Ri4+fvJWVjYegk5+orJ6llJeTjI6Uk4z51LXox46Fj4PC24OvzeLI/t3ihu3yk6bv7IiSg42SmJSAhYqQoLa6vLq4uri5v8bJ3N3j7Nzi7/TI3Yr54bjs1du/uIh4VEpFRUlKU11OT29ihJLCXtvS74Lzn8ve8cbN84SBg/v28uDMyMjE1dTHsaqmqcHDvKmyvbzP1tPJ0NTg5uTu97CCt+r0+afZ3p2Gs5rRt5Y2TjwrLSolNCA/HjtgSXaYnIxQV9i+h+aIh/3fiM6AXlqYf3FdhG9eoZ5ZYLKJcXNydIqUmZB7aaWoq6SblaJVqqZjk113a4SEXlRxl5STaoRpT32blfOwmnNkYKSUfmFpi2d3mmFllYlsZ2tgaWNdXWByrVmsc3TZvZiCi3iydIyeolhmdn6EUVxla2tlYWdwenx9eHh2dnVzcm5gWFYLVlhXVWGfXn5+fXyEfQl8fHx6e3pqd3uEfAZ+f3+AgICEgYSCgIGBg4ODgrBnhIOAfn+Mtp65WldcZpiUjLOQjoWEq25yenx6e3x3fYWGhIKAfYB+foiJiI2NioyHgYSKiIiEhYmEjJqcnJyWfsSNmW1taGhpamtoZ2Vftm9zaW5xdnV3foKBiJihumNiYWK8sq2omY6Ce3l8fX18d32SlpSZoaitR6+foaOhpKOcnZeSnJ+am5ufnJKEhomNi4mDhH9+goiIi42TlpGPj4yKh4h8f3+HhH6CgnGXjIyXk5OUjaeptV1iY2VgamlnhGSAaGdpZ2dnaGe4YmJmYmO9p3d7gIF/eXh8eXZ0c3JwbZm8t7i2sK+mq7KxrKikopaTlZGNj42Nj5KQk5KOjIuCl6KhlZOOlIqKh4SBe3h2c21zeHp0c3d4eHd1cHBwcmxraGRgYWZrZGVgV1lWVlapV1eLemNeh3yHiYyVY0hEQEuAQEdIRklLSUZHSUtFP01OSUpBRUdGRkxDZYVKR1RtWVFXVVZTUVRWYWFeXl5cWVhVT1BTVVdUV1JQmFKZm1NWUoZjYm91gpROTpFLTU9SVFJQV2NjYmJiXlxXXWBhZ2FgYFRaWV5ZWlpafHmDgXJubG9kbnBqb3aBdWBnbXBvcnaAd4+RkY2Yn5WBjJCSn1hla5pXWVhZWlqYkI+OiYmCgZFXWVlaV1WhfoSBioyRjIFVWFRQT1BPlFVXVlRUVVaeg356bmtucXeUop6dn52ZlYigm5iYmJeWhnN0b21tbGxufZugmIyBgIJ9jFJYXV9fX15WpFheXF1eYGNiWVSBk4uAkIeMkI6Mzq2SsIhnXWagbnOfjISQV1iSfmBRT1FbaHqpjoKGhIOPlmh0pGV/WmRgZF5dXFVXYpRNjG1hep1hZWZhZHCDYVxVUFRPVlhYU1ZOTUlFRklKTJ+Zkubdj46OgsaxUFZVWUtiVVs1V1U4Qmt6R1RUWl1gXkxOUlhneHqAfHl3eHV3eX6Bj5CVm4yOmZ+CjFaXiHCWlqCOi2pkU09RT1BTWFtIR2RSXHaTnZ6nWqRgdIGOdnuaVlNVpJ6djn16e3uJiH5uaWZneH12ZGl0dIaKhXl8gIuTk5uhcDpbamxsWWpgPjVFQWRdZWmKb1lvaVdrS29DV15giqStp3ELc8qUaqVfX7ScYJKAIyE4LCgmPTUtUlEtMFtLOzc2N0BDRDwvK1tiZ2RZVmI0am5DbD5OQ09KNDxnk5eOWlU/LEdiaq1uXUlDR11xSEtybF+9141bdEkjIzIuMS4sLC80QyRVWFuwpZKGj37FiKGsm0U6My0vLkJTXl5V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zuOA+v2ChY2Pj4eVkZyZmpmTkKOpo5ibmZ6OipOUlZKQjOvN3N7QyLyy67Grray0s7uio626trW6w8LT1d/g8oCQnY6zxdTd3+rnh5COi4mQivbu6ezt7e/r1omJi4uLh4bZ6+To++3p5OWFhYOA//fz5IyMiIWGh4Tm09ba2cq/0c7k9u3k1Mi+u8Hx9u3q7O3u4MHBvry5ubi3sLnIx8zNydDR0OaGh4mHh4yPiIGJlJSWl5Hvmv/r1+Di5N/gzIDKpoWUn5vDzO+J0d7e3OuDgdrWpIeHhZelnofo2r67ubey3rOLnJuImbS8urizo7S/+4LotZ7P95CQipShz5iXkYuKlZGUp6agrJ2imJWXl4uAgISOsLWZ6ZHUobbHyIPW77S40vvk8JOn5t+AjfH1+4iNkpWWmpido6+0ubqirICyub/X19ja3uTs+PDnsMbz3L+b2fyD4IiOjYiB9vyGi5ehoYr08ICdw5njj6bOg6Hl19nf3ujs6ezp5efc2de/uaelqry3u8vNyru0sanA1NPRzcbR6+mzgK7r7u2q59XPi5OGj5bZIEoyKCEqLGk4WIWhoJxdUqj+iq37mquImAWu1IeQiIBcsaSViH5znFtfbWZiWp57dnVzcHSGl4iRjHNgqJuUn5+mYHuJgZNwWG+Dcmlbb4N9tKt/nI6V7mCoZYpwmpaPfmtkmluOtJxwemxgYlxrZ2RkYGSDds/pg3iwcoqdVWBreXRoWFNnjKF5da5tcm5saWVgZ3B4fn17d3JwbmViYAljYl9Vo3mcbHuEf4CAf39/gH5+fnJ8enx+fn+AgYKCg4WFhoaHhoaGhYSDhISDgrFmhoaFhYOEgoFmWlZYYYh3oLmRiYaDh55sc3ZvaGNna3CAgYGAgIGBgXyHkZGNi42SlZGGho6Sk5mgo6aelZKRftKQlGxzZGRlZWdkZGxglGxweXt3eX6Ag4aJi4CHgYiZpbm8uru4rrWtqJ+VgXh6hZWXl52foZ6dioaLlJ6go52blJuipqmwpJqWlY2QkJGOioeEg4mMhoeJioWDk42Oko6NjIWCgIGGiI2HhayfmJaSjIyMjoyQlJifqKRsc2ppaGhqaHB4eXNucXBtcXBwcnRwarefo6WclpWcl4C0qq+zsa2ssrexvLq7vL+cnZujo56hnYydn6CYmpeUkJOQk5iVjIBxd3JptZ6Le4xvdXJyc3RycXF6eHh5dHFvam9wd3Nua2ViZmhoY2VgWaWhqFpbW6+wpouNYFyKlKSmopxTRUE8TEZEREVFSEtCQEZHQT1LTEhKPkRKTkhtfoCGQEpNU1hPU1VTVVZWWV5gZ2hnZGRbWVZWWVVWVlVXVU6WUE+Xl5h3aGZnaWxwdoRMl5hPUVZYWFNdW2ViYmRiXmhqZmFkYWRbWWFhYl9dW5Z5goF5dnFqlXdydHN5eoBtbHF7eXh8g4WQkJWUnl9nWWxzfYGDjIpYXVxaWF1anICLiIqKioyIg1lYWlpZV1aFi4aIlI6LiItRUlFMl5WTj1xbWFZWV1aTfoCFgHRsd3qRnJSNgHVranWeoJiWl5iZj3Jta2tqaWlpZ2x0cnd5dHh4fJJXWFpYWFtcWFNZYGBhYV2ZY6iYiY2MjYqIfYyGanJ3cIiMqll9hYeBkVRThIB+YlFRUF9sbFuck4GAf35+k3hfYVtHUWBjX15cVVpmlk2JbWF9qmVlYGdzj2NgWlZTTkxOVVNSVlFSTEpKSUVCRUlQXm1muInlm6y5wYK/r19UXGhSVDdDZ3RDUJWXnVVcX2BdXVteZXFzdnhja3Fzeo6NjY6Rl56nno9rd5WEc3NfiqpdmmJqbWRgx9Btb3R3c2Sysl1simWKVGJ0TmiRfH+FhZCVlJqZlZeQjIp2cGRjaHdxd4aDgHdua2d6iYZ8eHN7l5drOFhqamhUaW5sRktLVlyWQZJxWEtVX6Fzdo+gqKV6cKzXaH6uYm5fbXyUXmVggCJDPjgzMCxAKy82MzIvVUc9ODc2Nj1BNTpCOjhnXFpkaHZKXWpmWkYwN089NCk0Um2uqXZdWWykNWJiwJ7+TlmEVljJj9Xee05CIB0nKTExMDAvLzMtb+GIgMOHnqtQRjcwKCAfIjFAQjFKhl5jYF5aVU5RW2ZtbWtoZWRiWldVgFdVUUZ8V2tNXGFgYF9gX19eXlxcXFdaWVpcXFxfX2BiZGVmaGlqamppaWpqamlpaoxOaGlpaGdnZWNJNTU3PnpjcIlrZGFhZXlXW1xVTUpMTlNlZmVjZGRkZmJpcXJvbW5ydnNmZG1wcnl/g4Z/dG9uX4Z4e09STk1OT1FOTFtSgH1gZW5vbG90dnp9gYN9eH6Qn7a6t7e2q7Coo5uPeG9yfo+TlJmcnZmZhH+Ei5KWmZGQipOWmZ+poZqWk4eJi4yIhoKBgIiLhYiKi4aIm5iWmJSTkoyIiouRlZyWlKiYk5OPiImMkIuRmaKpr6x2fnFwcnN1c3+KioJ8gIB9g4KCgISHgn7Vtrq+s66tta/RxMzQzsnK0dbO3dnf4+q8t7W/xcDAvam4vL2ytbSuq6+sr7e2rJyJkop92LqfjKGEiYWEhYaEhYSMi42Nh4F+fIOGkIyHgndydnx4cnRtYrKlq19gX7SvqIuXYV2Gore6saVPJyQgIyQkJCIhIiIgHiAhgB4dHx4dHRkaHCAjQ0g8GR8gJC8sLjAvMDEwMTU0ODo7Ozw1MzMzNzQ1NTM1NC5ZMDBbW19DODc2Nzo9QU0wXVsxMjU2NTI6OkM/P0FAPEFDPzo9Oz02Njw8Ozg4NFI4Ojo5ODUyUUhDRENIR0tAPkFHRUVHSkxVU1VUWDM0KC0uazAyMzk4Ki4tLCsvLEs4Nzc2NTc2OSsrLS0tKik8Nzc3Ozs6OT8kJSMfP0A/QC4uLCsrLCxINTY5Ni4rMTNHTEhDOzMsKzhVVU5NTk5QSjIpKCkoKCorKy4xLzEyLjEzNkYsLS4tLC0uKykrhC+ALEszWE5FSEZFREM/UHKIcVZEc6yUMD1AQzZAKSk9ODEqKSczPVpOiIR9f39/iHRfUDgyJicsLSopKCUnLkwmQDIuQJlgYF1ka2goJCIhISIiIiYnKCknKCUkIiIhICUoKzU/PXpsx4iXrZ94tYo4LSYmGRcRGDJRMDt7foRJUlaAVk5OS0xXZ2lsbVVcYGJqfn18fH2AhYt/bVFbb11KOVFpO29ITFJUVLjNbW5waVtJgoA9QU87Ti84RTFJZE5SWFtob255eXd6cm9vWlRHRktcU1lnZWJYTUdFWGNdTUZBR2FkRRwuMzIxKztFPSYxMjg/cIHs64OEg53PyMLR3vQR/OHe+PFJTEw0NicrLjgjJSMBgIZ/h36OfYZ8hH2EfoZ9hH4TfHx9e35+f35+fn19fX+Af39+fIV9inwIe3p7fHx9fX2IfoR9gn6Yf4N+qX8Bfop/hH4Ef39+fYZ+qX8EfH9/f4h+A39/fdx/kH6Xf7R+hX2gfgZ9fX1+fn6EfQN8fX2GfAF9mnwHe3t7fHx8fZx+A31+fox9A359fZ1+iX2Vfop/h4CJf4eAiX+EgIR/h4Ctf4+Agn+Jfgl8fH1+f4B+fXyGfoJ/iH6Cf4d+kH0BfoR9AX6Gf4SAAX+VfAN7e3qFe4J6hnsEfHx8fYV+nX8BfoR9BHx7e3qFe4J6hnsLenp7e3t8fH19fX6ofwJ+fYl8AXuEfAmBgICBgYCAfn6JfIV+hH+DgAICBACA6uDa1ci0opWAwJOHhYLVvfXywqiloZmn0e3v59etj//3jJijvfTO8fejnLONpKyG67jXzuid2LyRxp/q3c26mIT19nbFyZ7aiPrBmaKNiOC6j+GswoCatdqIprnFwLzCz4iN98rOr6Cdsez2/6brmKmmpKCblZejrbOxsKympKIdo56aj4m72I+Pm6zBxcbExMfGxMPDt8XBwsLDw8WFxIDFxMTFxsjGxMXEwsLBv/mOwsHBwL6/vryik4WGjNuBtaOF7vn5gIaIi5KanJeip6OkrrO3t7q6vLiuuMzU1tXY2djQvcng5ubn5eXk6+HWpouFionUqayqrLCprKiEw6qwuLSyusnK0NLOz8bDwMbJyNHg+/rv6+vp5+PX1NDT1IDW3t/b18y9uMGytLrH087O5/Du+Pf07/Lq3tvQ0c/R0dHg3trZ09DMzL7KyMbGyMC6vLm8vb6/vbevn5aPi4aA9/n99vf59f39+/OfoJ2dnJmWh6aempiUlpaZlZeUkZGPhvT5/4aFgPr85oqFh4WC+/337fLy5+/2/OXQ1NHY1IDM0L7F09TMw8i9w6ydkomC89jSzcS/vLi7tbC+mZ2gm5qckYX8k46NhYaJiIuOkZGPiYmHgOHkhoyKiYeDgYCBhomG/NLxj43EyeLj4+PorpyNn66kpp6gobaokqWqnIClwLWvj6eOyfqRjIeOqomUjJOUkPuDkZmgpaGgp7K7x4DGt6mekYmZrJeVj4iLiIeHhvHKwbfH2NPK44KD/vz5gYiLkY2WlZ6fkY+Qoq+mnZuamZWLkpOinpiWkIiJj5COj42CnK+poK21r7yjsLC0vdj3hqC+1ej+hYP+0dzi4uPq9O+EjpCOj5CShe3s7vDr7OjWiYuJi4qLif3h7PXz7YD07O3k/PXz8e7r6fWMiYmIhIKC5tnX2NrNyMa7s7vAvLW5ure04+3p7Onn6+nNxMK/vb28trO1xs7NzNDU1dPS+oqKi42Qj5KNhIeD4JL79/yA/vDq5ujg6JSAzZj32rqRjbPk3dLe+IT8zMygmpuWoaiTloXt37q1sqWPt6en04CvmI6ivMDDsLjA/YPts5/M/I6KmaPKjYuHguzmmJGMmJ2arqSnmJyblZCMioSHs7PQzdDVrY/K4cO7jKHN0MyLrPOe39j4jYT64vKFjpKdn5OUkpihtbm7vbnByczBtLe6vdHx8vHt58bv5rOoh/jQnYLmvLS3tcDFzt6EjJqn0F7/lqOfsdbQtNrj5ubm6enX0cvEwtfX09DLy8itqLS4vMPAtaqyt8rPzcqztsDe38GvgLDr8fGv7Orr+rb1vLmmMzdOMSM+qKenjFNbqPqqhruajpzg26nR9YWGi4n+gKGblpKJe21hU4FmX2Bdmoupp4d1c3BrdpSmqqace2e0smh1eYarg5OeYV1qV1xdSIJqiY+ocp++b49yp6OSfXJasrtnm5xvk1mYfmhpWlqQemKgfo1dcoKZVWNlamRseo1lX7N4gGVycn2kqaxwoGZzcG1pZmFka3V7enh0cG9vDG9rZ2BcgI9bWWFvfYSBHIKBgICBeIJ/gIGBgYODgoKDg4SEhYaGh4eGhYaEhYC0ZYSEg4KAgYB+aVlRUlajZo14W5ynrFhfYF9jZ2lnbG9sbnh+gYCChIOBeIGTl5iYm5yblIONoKSnqaampaqglXfQWVlYfmZoZ2hrZmlwWZBvdnx7eX6JiY6Sjo6HhIGEh4aTpLy9s66sqaqlnJuampmapKahnpOBfIF3eX6Gjw+NjaSrp6+zrKeppZ2bk5WEk4Cem5iYkpKRlIyXmJmYmpWPk4qNjpCRj42GdWdiXVdSoKGlnJ2ioquvr6t4enh3d3RzaIF6d3VydHR3dXdyb3BtZ7y/xmloZcXItGxmZ2Rhur66sLu3sLnCyrSboqGqpp2hlJWfnZaOkomLeWxiXFSaj4mHgn97eH58doVwcHJtb4B1al+2a2dlYmNiYGJpb3BtaGZlX6CdXl9bWVdXVVVVWV1bq5OxV1aGmK6tqaOVRD05QkZBQ0FCQktGPUNEQDlFTElKQ1VOg5lMPz5BS0lWTlFTUoxKUVZaXlxcYmlwe3pwZl9WUFxnWlpWUFRTU1FPjXdwanF6eHSFT1GbmZdPVIBWW1deXmZnXVxcZ25rZWRkYl9aX19nZmNhXVhaW1tZV1ZRZXNvanJ5d4FrdXR4fI+nV2l8ipWmVlKee3+DhIWJkY5VW1xbXFxfVY2Ki4uHiod/WVpV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q6n3lKDi5e7s9Onnld6SqaakoJqVmJ+stbSzr4Cqp6Whm83WlJKPjY+ftMPHyMrIxsS4x8HCw8XFxsTFxMPExMbGxMXGxsbFxMPCwsP5jsDAwMG+v727spmFg4fX6dGWgcDRmJScnZ2UlI+NmqOrq7Cyvr6/u7i0u7a2wdXPzMfDxsjO1N7s6PDy6ufn6b97louL5rG0tbS3srib+oDEpq6xuL7BxMTGyMnMxsnDx8O9wsbK2ejo7fPr1ujr6eXm49vX1sKxtq2vr7K3sbKurqyzwc7b8fP27Ofj393h08/R3+Xa08/QzMDG0M3QysjDwbnMyszGyMvKxL64uLSxrKmPm6CfnJ2TkIiNo6efnpmgoZKnoqSmpaChn5WTjICMioWJ/oD7hYeChIL5/oKDioiGg4Pw7e/n6ubg2rzQ0tHR0s3Hx8XBxcPEwLux+dbEyNDa2c3KysbDwL2xrsKB9ufjxMnJzteDioWFhYyNioj+6NLFpp6WlbeHkYuKgoCFg/7/9Orc8O/nsMTX3+Pi56KalJ2gn5+bo6e5q6ajpYCbhZ7V3bP0k5uompGYjJvVmJGWk+nGxLWy0YSgq7bP3drWzsPCwb/zwOLi0bafkfvUxKKWj46KiPnj94iIioiB+oGHmJmUm5icoo2NrKiooJ2dn5qKkJSWmKGjmo2Wmpqdpa2pnqipscblgpOTqLvP1+6Ejfjt7PDv9vT02t/q6lXk6PL/8oWQkJGTko6K4vP0+Pb08er1jZCTk46PkNnm/vb2+Pj52fj47uzv7uXegoOEhYSBgvvd2tXWzcvExLa0vsG+u764saze9vj3gID/+de8v8K9hMWAwMHNz9TV2NvY1eKEjY2PkpSVlYqi5Obu+P2Ag4OF+ebr+OWx+Mq1lZGMiLPe9OvU/4LxzdKgoaijqa6QoKGV9+C+sJ+I6sCW4q+WtZOYqb+4srf9hfe1os7rkJ2k4ZWVjIqD+fSWlJKUm5msn6ikqp6gn5yTjoqyssfPzcW3yPGA6bW2vo+E64eVrJeksraSxOuBgYWKio2Ulp6fjpSSkJOtt7a3t7u8wsLI19nd2+vv7vHcv4iwypuPi5uRhf2K+q+HqOalkpagrsLG0qGJwNDb3+Hi5unezcjFw8/g3c7Ju7eyyMS+xMPBwLOwvsDIzc/Nw87Rz869kbiBr/L8ku8nlYjE+9xiVylOjGphXlQ3UH7K4/eYnYqpoZnLvr+RuomPjZCK/+nagJeXkYmGf2lhYlyZbl6pn42mloaEcmNcqLN2jJaWjpOhl4dwfHiqaISGZXhya3dlWoh/jmRfpICzoW28l3isgIuldnmde6mcaVd7hpJljndprnePpVdfaGZjWVKfX5Gqf3Szrq1qbYpTbpqcoZ6knJhlmGNycG5qZWFhaXR6enl1G3Jxb2tni45gXltZWGRzf4KDhIOCgHeCfX6AgoSDAYKEgw6EhISFhoWEhIWEg4OwZYSCQYGBf311WlFPUo6toHZlm6VwbHFxcGZjYWBobXR0d3uHhoWCf3yDf3+ImJWTjYmJio6Tn6qnr7GqpaWljLxnW1qHhG2Ab2xyZaaNanJ2foOEhoWGiIiLh4mBhoF8gH+Akaaqr7Ouna2vrKysqKCfm4l4e3V2dnd6dnZ0c3N6hZGerKyxpqKgn52imJSZpqqfmJOVj4eTnpyemJaWl4iXlZiUlZiYlZKOj4qIg31kcnVzcG9pamBleX15eXN5e25+e3x9fnuAfHlwbWdpaWZqxmTEaWpmaGbBxWNiaGdlZWSzs7iztbezrYycoKGenJmTkY6LjImJhX96qY2ChI2Wlo2JiISCgH94doZcraCbhYuNkJhfZWNhYGZlYWLCp5GAZ2FaWHRcZF5cWFdaWa+0p52Yt56Ye5Omqqeij0I9PEBCQD8/Q0OATERDQ0VBOUNda2CbXFdTQj9CQUhyVVJUUH5paGNjdU5eZWyBjYmEf3p5eniObYKDfGteVpZ+dV5XVlZTUZKGlVNUVVNPmU9TX2BaYmBkaVxWbGhqZmVmaGRZXV9gYWpsZFxgZGJhZ2tnYmpscn+QVF9daXWAgZFRWZyOjJCQlZI0kYKBhYaEiI6XjlRbXF9gXltXhY+OkZCQjYecWVteXlpZWoCDkYyMjpGVg5GNhoWJi4eFVIRVgFNToIiAfHpycGptaGlwcGlobmtmZY6goqJUU6ekimlobmtxc3RzcHF4fH98f4OCf41XXFxdX2BfYVlkk5edoqVUVVVWopSXo5Fw1amQb2tjW3yHmZaFpFOXeHphZmtoa3BibGxkpJZ+dW5WlHBYg2FTYUxOWGJdWmOXT5JuZn6fgGVwdJhhYFlYUZOQT05LS1BPV1RWVVROUFFST09NV1VfZWRkYGqNlm6JkoeCy259iHeE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5OXu59rZ4Njb19XV0s7T0svKxsO+vbHCxcfGx8nEwbu6uLm5uLqsoq+tqqanp62vrbe1sbCsp6qmpaKfop2cn46RkpiZlpOL94OGiIuLhISEgYH09e7z7Oi9raiwqau2r5/Z2dPU1NDNy8/O0MzLy4DJyaLi3uPf1N76hebCxMDIzb+9yMbCxM7ItrOswYSHjvzr4cGpnJuYkIqJh4yRmN2OjY6KkIyJhIWK5fHY1qe2zNTT6u2el5StoZuamaCnrq6rqcnR3tGkwb6rio+coJWf7YKjnJL5ycfFwrq4t8Pks+Hj29bUzMjD6MDs8Ozo6YDl5ufd3Ofi3NHJuayRl42D8eXWwbijn6qoqK2emZaZq62lp6epoaWJi5SVoKuzu7DH4fGEk5umorza6fb7/4SD7oODgoOCio329vf06vLi5M/o9PXz9v2B+4STlJOVlpOP4fX78/Lr7uvkj5KSlpOQjfLugIGA+/vz7N76+ezv9YDr6eGNkZKSk46OjNnX1dHV0djHub3J0Me+vbu3sdqAgP39/v7647rHxc3U2ejr58vM1tzb1NPX2dTwiY+Qk5H0h9vW0eHz+YGChIiKi4X68MTIjeKJkpfX7Nj58tvrgvbj7LCtsKWutJGho6SX8eXHoPuVmLGGjb7busOUkrGxvYCAivq4p9OGnZaq54P06ejVztedlZqdmJOpoaylsKenoZyWk5OwsdHLz8WzyaC3kIeliP7fyqGwvrTZ6YB6U1eh9arU4YCEjpOgo52goaCjmKCen6O5v8HFurCyusLP4N3b2M3DlNWb2qWYlpidp6q0trO/vcf4v4a/2dDZ3eDk2VTh397bzs/P1dvX2c3DxLq1r6GepqSiu8LAsaqmpbHO0crKw8HIr/KH6ZeU44+IZ014a3+CdXaJPkd72Mqv/qePhNGKp67mgMzKvfzdp62/+YHl292AoZ6RhYB1bmRkYVqUa6uik6mhoJqWi26vWmBkcIGPmaCVmLuTYZtefo5udXpqcW1in3iNWXFdjbyFWXZ6aXZqgm2hWJVqh4VcV4Z9cXeMp19nZl1USWuNeZ91oKubeo9+ZWxwpneIiY6LjIaPjZGNlI2JhFSIXnBvbGpkYF9ncXcdd3RwbGmTlmRiXlxaWFdYZnaAgYB+doJ+fX6BgIGKgoCDg4KCg4SFhIOvZICAgH9+f39/fWhPTU9dc3+KsYmqeaSkh4h9a2NgYGdpbXd6en2Bf4KAgHx0aWZ9ioiIi4yOkpaXmqqwrqmqqpHBpqeElnBxcXJycHtwpn9lanmEhoSCg4KDhIOEiYiFh4qJhYWHi4uIiZexs6+sqKmwrKSoqYCfl4Z8dndzcGtwbmxvbGpibJ6qo6Oho6ukm6Cmn6GbmZiXlJmak5KSkYyNgYyOkJGSlZGQi4yIiYiFhn52hoSAfH9/g4aAh4eCgX98fHp6dXN1cXF2aWpscXJvbmm8ZGZoaGljZWVhYLGxrrGpoYV8dX95e4N9c6Gfm5uYk5CPkoCRkpCPjYuPcpuUmZiPlqpcm4OFgIeLgoCKh4SGjol+fHiKYGRptaabf25iX1pUUVBOUFFXlWJhYWBlY15XWV2bsJWVd4ubnZmklEE9O0NAPj4/QEJGRENHXG12e2l6cWJCP0RGQkd4SVlWUoZqaWpqZmRka35vkpONiIV+fXqOboCEg4J+goGEhoGCiYaDfnduZldaVVCRioJ3dGViaGdobmRhX2BqbGNmaGxnaldXXF1lanN6cH2PlVJcYWhldIaRmZmaU1OWUlJQUE1WWpWOkI+JiX6Bd4eNjY+Sl0yVVFxeXmBfXFmFjpGNjYyOhoxaXV1gX1xZlotKTEqRlY+LhICOjoSHjYqJiVxgX15fW1xbg3x2cnZ1e21lbnR2bmtqaWhnjFNUpKOmp6OQbnFyen+HkZOOfHp+gYF+fH+Eg5lZXV5fX59YkY2KlaKlVVVXWVtbV6Saeox/tmltbJOdkZ+ajplTmomObm1uaXByY21tbmWhl4RvoFdRX0tRZXhpaYBMSllaY0xSlXFoglxwam2RU5eOjoB6fFRNTk9NTFZSVVRXUlVTU1JRUVpbY2FkZF9oXW1QTl1SnJaMd5Sqka7DcGtTU4a6eZGOTlJaX2tuZ2dnZmhdYV9gZnd9foF1Z2pwdoSTj42KgnpaflmCZV1cXF9nam9wb3V0d5R3U3mJhFmKj5OSiZCQj4yCg4SJjYuOgnl7c29oXFpiYl52e3lsZmBaaICEgoR+fIFukUCBU1WDZ2pjZolgcXRubIhYV26llX60eWpkhUtZXH1JhpOKrZJue4avV5eOk4A+Pzk1NDAtKSknJDwyW1pTWldWVFNMOVMtMzVATFVmcm9vgmc+YigyNCcqLiw2NzNeRFQ4Sj9sg0k7YGdW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5uXX4+Xk4+Pj7ezX3uDX0cnAuLedn5uVmZiWn6Gr1t/m7fH1+OrT4eXj6O7k48b7/4CCgYeF/oKHh4SGh4WS7fv//fn6+/PW5/H7gPr+gIaDg5KSk5KTj5Ts9//49v7z7NaSlJSWlZOTkO6MkJCSlJicgfT89PP7/PHw9pOVk5KQjI2GzNTc4OXq8vzqwMvOxsG7urWty/+A+vf7/fTzztTi6ebq8PDv5tTX4Nvc5PP6/Pn7iYOyycfOzMrS3+fv9YKFiIiIhojspPulgIKkooTE0Ir57fTxgY6xurKwpKWojZ2ipJ+W9d7Bq9GSpKnIoe3Hl8SbiJe7gI6AwaTMh//bwa6x0ICdoqy/qJykpaObqKSqqq6rpqGYk4+at7zHy8jAssbF0NjZytrd6OaIr7Wzq4hOP2mEl6CfglxQiMuUzv+VqKeqqqaqqLC2gL2/t7vAvbrEysTCxdHPz9TLurqzr6ahlomIhIWI84KWq7rV29S/t7q+xtrc1tTKyMLU2tnb2Nfb18rHw8a9sLe9y9HJu6qjobfOzcCvpqGTitbkolNXnvKIkY39vz88aZrEmLWW4IKTjo6MiJWRqKGt7a6Ux7bp4Ov/+q6EjLrfgKCOhIVyaWZmYlSLhomOaoKOmZualYV0cnBhZnaLlZ2aXl2PeXJuWo5PbXRzZ3pyZFeGgJJwdVzRZX5qnFyidmKuZZ6eaJlhVaFxZV9YU0ZldFNUZI6EjG9yYH9dXWBefHx3eKuAfoB7gIaGhI2NhIKEjouHfH2Xgl5vbmtoYl1cJmVvdHJulJlnZ2VhXltbWVZVWmh5fHaAfXx9fn6AgYCAgIGBgIGAiIGAf4CsYX9+f4CBgYCAf4pRTk9RYH1xcG2fso25la6Jcm9pamZmaWpvd3t9eXJlXWJoZ2VjbYKJi4mKiIuKj5WmtLSuqpTApbKqXnh2dXV1dIZur5KAl5yKgX+BgX+DgIJ/gX6DiYaCgIWLkoyFdndyhpilq6ulp7Syrq2ytLOjkI+AiHpwcHJta2yFsKywr62xsK2moZ6dnqChnJiUlpqYl5mbl5OJkpmWlJKRkpGDgn5+fH2EgXaIlI+IiYmIh3qNjYuHiIOBg4SCf4B+enx9aG9qdG91bWS7YWxsZbq6tKu4sq2mpaOXlIZucHRze3h1c4ijoaGYlpWRlJOTlZSXmJiAmn5/hIiIkJKRi5iJj46Ki5OWkoiEhH93g5isZZVya2ZhX1tYVlRSU1RTVmeHqGFmZ2RhW1tgZGanp42Qd3+QkI+amEJCOENAPT9HV2h3f4KAdZyNfXdIR3RAQkhMekZdYFpLhX57eHl6fneEZIuNj4aFgoGCg2iMjYyIhoaFhX+AgYqLjYuKiIiIfoaHiYmHh4+RgoSGgn93cXBtXFlWUFVTUlhaZoOKjpOVlpqTf4aLiIyRiIp7mp1QUE9UUpxRVVVSUlFPXI6UlpWTjoyKeoSKk5SVTE9NUltbXFtcV1ySkZWQkJmPiYBdYGBiYF1dXJNXWlxfYGNmUo2RjIuSk41IjJpgYV9eW1dXVXh4gIOJjpWglXN1dnJuaWpoYn6lUqGfpKeen4OEjZGNkpiYmJGCg4mGh4mSmpuaollVdoWChISDiJCYoKJXhFmAWFiWZcqNaoB2XoOOXKCZn5xSW3Z4cnBoaGpfa21uamOhlYZse0pUWGxahGlQaU9DTGNNVEtzZXxdrIp3a2l9TFxfZXFbVFdYV1RZVFdVV1VTU1JSUFRdXl5hYWFgZGNmaGlka2xwckdnc3V0aUxCXXKDjJCFZ156m2qTrmRvbW6AbGZpaXF2fn50dXh1cH6Ef31+hYSFioJycG1tamdiWVdVVliWUV1rdYmNh3NubnB5iIuGg3t4c4eLjI2LjI+Mfnt5enNpbHWGiYN2Zl5Za4CAd2tkYFlXjJyJaXKg0nJ4dNS2SVFqhpttdWCRWG1paGVjaVZZWGaWcGWOfJuVorUGsndaX32VgDw3NTcvKysqKCE1NTc+NEdPVVVVUko+PT0zOEthZnZuQDpQOjcxIDkfJiwtLktHQTdJRVJCTUSbLD9TfVOtVlXGpPfwZHVBPHVKTDEiHRcjKiEhJy4sWT43LTgmJTYoNDEsKkQyLi8sLzIxMDU2My8tMDAtKStLWElcW1lVTUVDMkpUWVlXdHRQUE1KR0RBPjk1OEZVWlZbWllZW1xeYGFhYWJjYmNkZWVlZ2ZnZ2dmZ4NMhWWAZGJiYVQvLzAxTGRZWlqLn3+njJpyWFRQUU5OT09UWl1eWFFGPUBEQ0JAS19maWdoZ2hnbXCCjY2IhnRyjJiOP1lZWVhYVmRWjHpwhYl4cG9yc3J1c3Nxc3F0enZycHR4fHhzZWhheZCfpqedmqekoJ2kpqaXhIN8b2ZlZ2NiYXeAn5ygn52jop+ZlZWYnJ6dm5aRkpeXl5qcl5OKj5WTko+PkpKGg319fH+HhXmRn5iQkZOSkYCUk5KNjYmHioqIhoiFg4aHbXl2fnqBeXDObHp6cMvKw7rKxsG5uLakno95fYODi4eEgZu6trOnp6iipaWlpqapra+1lZadoKGrrq5opKuXn52amqOnpJqWmJSJmLDHeKV1bGpkX11aVlJOTVBQUmmPvW10dXBuZmZtcnKwwLC2kJGhp6ewliIiHyEfHyAjNkFHT09TTF9dTEMgHzMaGhwePSg0NDEnRUA/QEJCRUJNO1laW1SEUnRQO0xMSkhFQ0NDQkZNTlBQT05QUEpOT1BQUFFYV09NT0xLRkFAQDMsKiUoJiYrLTlLT1JUU1NWU0NHSkhITUZHQU9SKSooKypRKisrKCgnJS0+Ojs7OjU2NjI5O0BBQSAiIicrKispLCgsQzs8PD1BPzs4MIQzWzEwMEUqLC0vMDM0KTg5NjY5Ozk5SzIzMS8tKysrNTQ4PENGTVZQNjQzMjAuLy4sQ10vW1peYFtdS0dNTkxQVFRTTUNBQ0FERElNTU9WLyxAS0pNTk5QVlpeYDSENYAyM1M0fHaOfE05aJ5EXFpgWSwxR0ZCQj08QEtWV1hVUImBgFpsNj1ASzNNRSs1JiAiLSYpJDgwPlOOQzMtLTUeJSUnMSolKCkoJiopKywuKyssKisrLS8vMDQ2ODY3ODg3OTc6Oj0/KT1GTF9zi4KDkLzL3NfCvs3Te52wYWhlZIBiWl1faG50dGdoaGRecnhuaGdrZ2hrY1JQTExOUEtERURDRW87Rk5XaGpjTkhKTltoa2ViXVlVbHNyc3JzdXFhXlpcV0xQWm1vaFlFPTlMYF9VSkRBPD1kgZqdrcrVbnF29vyLs8fV1F9NOlc4UU9PTEpNMC0tM09AOzswNDI5QwZELyQlLTeKf4V+kX2CfoR9An59hX6EfRJ8fHx9fX57fX18fH19f4B/f36FfQJ8fYV+AX2GfAR/f35+iX0BfJN9AX6Nf4J+qX8Bfop/hX6IfwF+qX8BfIR/h34Df359/3+EfwF+hH+vfpF9AX6SfYp+AX2ffAF7hXyFfol9in7Cf4WAAX+IgI5/i4CJf4iAAX+IgIl/iICUfwGAm38DgIB/in6Hfwp+fnt8fX5/gH59hX6Df4Z+hn8Ffn5+fX2EfAN9fHyGfQd+fn59fX1/hn6EfwF+o3ySfYN+p38BfrN/An59hXwFfX19fHyFfQV+fn59fYZ8hX2Cfox/AgIEAICStK6nn5mVlpKD6Ofr+e6I/PP37/n24eDl49TdzLvO+tu7vq2nq6uDhb7EwJjF4cKagrjPi5jUfpzCl4aRp/mAgcmM9tuMhbOepJP3i4GC+vP1gZXNzNj05oD39/+Ilpecnqrqt72ztb+8u7zIxb7CxtXUz9DDuLq688GGoqCdmSuSio6Xodnknp2amJeWk4+MioeFhpKeuru7u768vr+/vr29vbu7u72+vby6hbmA6IO0trq7urm5urmGhfv6/NSwn6Gep7PA/tCLi8/lqKGurqmmnqalqamjqKCXmp6ho6GbqsbMzsfGyMvP2NjmgIHTgdDo7abNysjFxMLgqpOKgP727/Lb0MPDxMTHxcfEysrMyMbJzNjd1by2sbW2t6+40uL07/L0/fnv5ern7e6A6tvQva3ngIH9/Pj5/IGAgfv18O3k4+DV3dvc19XUzMzAz9jS0dDR0dPAzdXUzMvFx8C0wbm0tra6t7Otvr/CwMDBwbu3tLO2trGwromXl5eVmZmLhIGIiYP49f75+Pvz7uzp7fPtpKyxsrmuqKSk6+fh4dvY1tvO6Ojp5+Pj2tiAz8fAur2/wcKu4MzEzMvS2O6BiJmhrKeprrGgxqCYkpWTkpOSmr7p/pGQkpmVj4aIj5GRj5WL2srEv5yXqbCx1fjo7PeJjYqN/czP+/uFhoOU//mho6aAk6b4jLu4poeAgvbr3uTh+oSaz+Ti2tfZ18vGmvSGgoKB/v399u3z/feA+PTz8+rm3Obr6+bl5uz31uXv8ers4eLqxrS5trKvr6+yt+br7/P5+v791evv7/Dz8vPk7ISDgoeGiIeAkJCNjJKVl5Ln/4GA/YKB9uiB/oeHhIOFgISVk5OVl5WWgI+QlI+Qj42Hh5eVlZaXlo/9np6fn52fn5vl9Pv8gYH9+euAhZOVkZKRkI+EhIyGhYKCgoDuwsjMycbAuLW5zPr8+vn59/r35dvr9vb19PH0+dvZ+P6AgICBgYLN37i+ws7Jzc3O1M7f8/Pr5ejp5u3h6PO0o7nF8LfDluHr5fCo97KysKCZm4SXoqWdlZH61LLri5Omob2Bm+HLla+aiuaOgsWApsbjydPP0NLCzc3KxeCtmZmcnay4vsXJ3d3Dp5yUjpO6wsfCwr+0y73R29nK29bf4uff7IaPhJWVrZLum1ZJd5mrrsesglBl4ZCqo6ekpqaus7CwrbG1x8rExcS/vcHO0MvPysnItbe5vbzZ39zc2tfZ0s7FtLzC3OHg2ce9vMdQ4+jt7N/j3s6+sa6tx8/MytLV1c22trShnJHardGKkoK4gJCUkJn+d1ue/q+oz8PNq4afmJCKiYeIhpnh4cHKvLjpsbyqndjp8Pzv6enonIiAYHFta2djYGJgVZKSmaSiYKmnqqWtqZaTmZmUm5GBk7CXfn1vbW5fSVBpbWxZgI57YFJ5hFRimquCi2defXa4ZHCebbeZXld8amhfnVROUZyXmVJfhIWOopZTpKawXGNjamlzr3yAeHiCf3t8hYV/gIWQjYiKgX57e5+BXG5saWYeYVtbZG2bnmlnZWRkZGFdWllXVVVdZ3p8e3x+fn9/hIASf4CAf3+AgIB/f359fX6pXnx9hH+Afn59Vk+Vl5mLeG1ua3N7hsKjbHCisHpweHl0cm10dHd2amplYGFkZWZmYnKLj5CKiYqMkJaXpV1encSUo6dqf3x5eHh2jXJlaVuyrqusl4qBgIKChYOFgYaGhoSCgoOKiYV2eGpzeHhzfpGeqqmtr7OxqaOnpKiopZiOf3ShWlqAsa+rrK9ZWVqvqqejn56clp+foJ2anJaVipKYlpWUlpaXiJSamJORjI6Kg4yGg4aHi4eGf4yNjoyLi4yJh4aFiIiDg4Blb29vbnJzZmFgZmdgtrS5tbK0sKqlnaCnpXV7f36Ee3d0cKWinqGcm56ilq2uraqmpqGcl5GNiYqNjpCAgJqLh4yLj5GhV2FvdoGBfoGGeIdkX1xfXlxYVVt2mrFoaWluZ2RdX2JnZ2VpX4+Qh4FxfoSFipyfc3qGTFBVVJ+Dh5ybTUtGR3FyRENHO0RLdk1qaFxJRUeHgnx+fItKXX+MiYSEhIF7el2OTUlJSI2MjYiGj5WRk5COkYuIgocvi42IiIqPloCFjZGNjoODjXRiZ2VhXl9eYmiIjZKVmZmdnYGLj4+PkJCRiZBQUFCEU4BQXFtaWl5gY1+GlEtLk0pIi4hPnFNTUFBRUFReXFpbX1xeUVpaXltaWlpUVWBfX2BgYFqgZWZnaGZoZmSLi4+SS0qTkotWYGJeXVtaWlNVWVRUUlJSUJVzdnd1cm5pZ2yBoaOio6OgpKKXipKcnJ2dmJyfjImank5OT05PTn6Oemd9fYSBhYSFiYWQnZuVkJGWk5qOjdGUg4qQrX6FYo+Ylptxp3NxcWVdXVVka25pY2CljXKKQ0pVU2BHVnVqUlxMSotUTXVmd5R+hISGhnqAf4F+h1lPTk9PWWVpbW13eGhXT1BPU11ehF+AXWRhZmlqZGxqbG5vbHM/RUpbXnBktIlnYnqQl5mrnX5bZqdib2VnZWdpcnZxcW9yc4KEf319eHZ7h4iGh4OBgG1vb3Fwj5ORkpKMjYV9dmZudY2PjYh5bGp2kZWcnZKUkYByZ2FjfYSCfYCEiIJsbW5kYl+UdZRokYOra3R4eIMq6oZ9pNiGhJmKg2xSaW5taWVjZWRulY5ydnN1k3GBc2qQnaWwpqGhnmlcgCMnJykoJycpKCI4OkJISTBYWVlXWlhQUFVaXmduYGh0YkE9My0oIR0fKy4vLVlnU0EyPkUuO2qNRklOTZtZnGfC7avWezs8al5SJTIeHh88OTofJS0uMjo4IDw8QCMoJikmKEUwMCsrLy8tLDEyMC8vMzEtLy0tLS5OV0VXV1RPCUhAQEhTdndRUIROgEtIRUI+OzhASVlaWlpcXF5gYWJiY2RjY2RlZWVmZWVlZmVmgkpjZGVjYmJhYWA7L1xdXGRfVVhZXmRvrZVjZJKcYlRYWVdYV11dX1lKRkI+P0FBQkJBTmZrbGZkZWlrcXN/Skp7cHiDhkdcW1lZWVhqWE9ZT5yZlZWBeHBxc3N2gHV2cnZ2eHRwcnR0cXFkaFllbGxocoCKmJmcnaGhmpabl5yemYqBc2iRUVGfnpudnlFSU6OemJWUk5OPm5udnJudmJiJjpaTkpKVlpiIlZqclJOPko2FkYiGioySjYyFkZKSkZCSko6NjIyQj4uMiGt2eHZ1fX1vaGpxcmvFwcfCgMHFv7eypqmysX+HjIyTiYaCfri2srSurq+4q8rOz8rCwLu3tK6noqSnqaqXqpmTmpqeo7RgboKKl5SSlp2MkmViXV9eW1dSV32ty3d1d4B1cGhqb3V1dHlqnaeqq5CdqbCwsKBdZXZETVNZn3pxfHM0LyojOjYcGx4YGx03LT47gDEnJCVJRkNFRU8rN1BZWFJRU1JNSjZOKigoJ01NS0dJUVRSVlRTVVJST1BVVlJRUldfTk5TVlJSSEhSQi8yMCwqKywuNk5RVFZZWFhYRkhNTUxMS0tHTSoqKisqLCsqMTAuLjEzNDI3PB4eORwcNz0nSicnJiUlJigtLCkqLSwugCkuLTEvLC0uKywyMjEyMzEtUDMzNTU0NjU1PzY5Oh4ePDw6LDM0MTAvLS4rLTAtLSsrLCxQNzU3NTUyLzA1SVxdXmBgXmFgW09QV1hYWFJUWExIUFMpKSkoKChDUUhKR01LS0xNTktVX11XUlRVVllPSZSKy4BbbmOYSFZdWVhIgG5DQkI4MjNCUFZZVU9MiHxcci4zOTdCKzNJPigtJyVHKCQ5MDhhODs6Oz45PTw/QEUnISAiJi81Njo7OjszLSkpKS0wMC8wMjY1NjY5Ojw4Ozg7PD06OyElN0ZHXFrPz9bNxsrQ0NTOwLK5s11pXF1cXWBsb2doZmhndnxxamdfc1xhb25qaWVkZE1OTk9OdXt4d3dycWdcVENMVG9wbmhZTEtZdXqBgXh4c2NURkJDXWZlYWRpaWJMTFFPUEx2WmtWjpixaHB0dHTYtLbV7ZGQkm5XQys7UlFNTUtLSE5jWzg4ODtMPVEzKzI5PEI/PD8/KiaKf4Z+ln2HfoV9gnyJfQp/f4B/fn58fX5+hH2EfIN7h3wEfXx8fIZ9AXyWfQF+in+Cfql/AX6LfwV+fX19fq5/BICAf3yEf4d+BH9/foC0f4KAhX+DgMl/sH6IfYp+jX2OfgF9jXyEfYV8Bn19fXx7e4d8h36GfYt+gn+EgLx/kIALf3+AgH+AgH9/gH+fgAF/iICEfwWAgH9/f5GAoX+GgAF/lX4JfXt8fX5/f359hX4Bf4d+h38Efn5+fYV8BH19fHyFfQV+fn19fYt+AX2mfIJ9hX6OfdF/B35+fX18fHyFfYV8AX2GfgF9inyFfYJ+jH8CAgQAgLH28KO5tqmgmZCIi4uH9rvttYr/8uPh8PqG7t/R8NmflJ2aqMDSrIfxsq6M/Nvg3LaL5emiqsBx7aePjozqk5Vus/Gv6ITk4NnFwtyK+4L37fL2j7vB2/bp8f2A/IOLmZqlrditssC/xsPCwMbDzcfGxsjJu723t7y8uL7jvoaeH56blpCHs92eo6GemZeXlpGOi4mHhoOEla67vLq7vbyEu4C5uLi6u7u4tLO0tba154S5uLm4ubi4t7iig/P08omemp+fq7W5urnc/Mznse7jqrG5tbKrqaOloKKpqJeRlZecmKC61tbLys7M0tre4OK7hdPn47DVzcrCwr+F8raMgIOFh4P9/O3byMbHyMHIxsfIw8jLy9LY0cC5tLSrrbSwsYCvucfZ7Pnq7u7n5vb38PT59vbx8/j6gIOFhYWDg4aCgv/789/T5NzV0tPP0MjM4uLc29jU0c2/2NfQzszMyMHCvby6trm4tau3u7q5tbSxrZaBgIKCg4SGhNv7/IDy8YL45/qKkIuLi4WD/IKA9/Xz8/754eXn4+Tl9ffv3vnx4IDd3NzczePy6OTc1srIw7u6vcvJzsa2pY6aprbH0MvMwa6yqKijoqKro+Gts7esucXQ1eyDi4uSlIuGjo6WkIqCgvHp4I7e8ufBrKyx1YKfr7q1ppWOhvbWlvKQnZeFg/ago6WMuJrV2N64loSChoSC8e+Opbe6v8zP1t/g1onmhoCHhYODg4KD++//gID//v38/Orr+vr++Orq6OfY6uz29fTt6OPQvb+5ubWsrrCq3Onr6/Hw9fzb7/H1+Pb3+//dhIaHh4WGioiDkpKUlJWYnYaBg4GA/oL37vyZmJWZmZSSi4acl5qXlpiWhJqZlpmblZeU95aUlpSVk5GJlKSlooChoqKdifDx+4WIi5CRiYyQkZGTkZKRgoCJiIeGhoWE98LS3OPk7vv9/uDzgP35+P3+/vjb5+3x9vr59/j23fKAgYCB/7HDvbG+2eLn4dvT0dTi5NjT3Pz88PP22YuFwcDB/taus4u+w9/uhLyzsZ+ZnYmZo6Wel5WR+8qXtYuSlICvo9CijbPIp6/l6eW+rczHzcO2sbW8sKm1scSqnZanp662rJ+toq60paSLiI25w8i/ubizu7nO4tjFxsrY3OXc7oeR6v2D6veFkov82bOOxoFPa6fQluOgrK+pqanC15S5tMjgj6m5wMW6ubXBxL29vsLBvL67oZ+goqTLzMnU1l3KzszX3ubp2LzFytHY7vDo4tvX1MzBycbFt6SP7dm0lfC0eqB5h+Wbl5iPhNmOX3bTntOTiIrY5oHjpcfV/oLu8oOKgfyb1NempOSTpK7C4MmA7+PP293c3eLf18iAdaWhanp5b2ljXFZaW1ilgp10W6qjmJahsV+onJOllm5haGNrfYtfS5RjZFaehZKSd1uVk2Vth5K2a1x0ZKtxhVqFsHqUT4qNi3+HjVSYUJqTlZpcfYGQn5Wdp1aqWFxmZ2tzpHZ4gH2CgYF/hIKIg4OAg4R7fHp5e318gZqAWmshamdjXld7l2psa2dlZGVkYF5cWlhWVFVhc3x+fX1+fn9/iH6AfXp6e3p7e6ZdfH5/f39+fXx7ak2QkZBUbGhsa3R8foCAn8Kiso+9q3l/hYN/endyc2xoamleWVxdYV9mf5WWjYuOjJCYmp2ghsOVoZ9zgn15dnZ0VqR9aFlaW11bsa+ik4SBg4N+hIKDg3+DhIOFhoB2dGxwb3R6dnVze4eWo62ApKmooqGwsq2tsqysq6qvsFlbXV1dW1teXF20sKqcl6WemJaWlZiSkKOhm5mYlpWRhpybkpCQj42GiYaDgX5/fHpzhIaEg4J/fXxqV1haW1tbXV6asrRbrKhcraK1ZmtmZmZiYbdeXbKtsLC4uaerrq6srbvAuKnAuaqko6aikqqAuKymoaCXlJCIhoqUkpWNg3VgaHB5hY+Ni4SAhn+AfHt6gHqabG9tam51fX6KVmFiaWxiXWJiaWRgWFWblZpwnaumlYaEi5pXa3V8e29fVEyIaUFpPUE+OThtREVIQmFXe3x/ZVNISEpKSYiKUF9sbnF5f4SIiYJShk1NS0lJSkmASo6Ll0xMl5aXlpaLi5aWmpaJjIyLf4iKlJKTjoqIemdoZWdlXVxcXoKLjZCTkZSbhYyNkZWTlJeZh1BSU1NSUlZUU15eYGBhY2ZXSkxLTJVLioOaYmBfYWFeXFZVY1xeWlpfYFRfX15iY19fXJpfXV9dX11aVWFra2ppaWlmWIiAiZZPUVVZWldbXF1dYF5eXVNRV1dVVFVUVJt0fIKKjpaho6OQoFSnpKKnqamljZSXm6CiopyenoqXT1FPT5xuf3txfZGRk46JhoWKlJSIhYuipJ2eoYdce5yXjrmadoBad3qPnlx4c3JkXV9ZZm1uaWRjYaOAV1tESU1WVWpYTl2AaVtiioqJdWx6gIN8c3B0d29sdXN4WlJPV1laXVhUWFJXXlhYTU1OWl5gXVxcW2BgZ3BtZWNiaGxwa3I/RoKOS5ObVGBcqpSBa6l9XG+WpnynZ3J0bm51qatlfYCIlGR4gIOEeHZ0fX94eHh7e3R3dVpYWVpegIF/iYp9gYGJjZRXlYhsc3qBiJucl42IhoV9d319gHhqW5+Pdmiyi22Sd3i1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/OeDg/b57Onp5Mfl3+Db08m8sajEzNLT09rh3tnZ1tnZz8zMzsarqqmnoZ+dnJ+I5enygoP79/Xs+oaFh4qSjImB/vjx5e/uz6a1sbbFi8W8wPGinZ2ijpOckoiLkvaLmpd+iYb9u7iNutPTzNPc5OfcvZmF/YWiub28wcPDvr3H8s2Ji42IiIWHh4X0hIaBgf78gID+6vP3gICB/ff68tz9+ff79vbz89XDwb22sbOrs6/a7O7t5u3u7t7b8PT7+//7/+3vgISEhomKioCQkpWXl5iYmv6BgoKDhYH5/4GWhJeAlpqXiJubnJ2bnJyGnqCfnZmXl5X5lJaYmJaSkI6Cp6empKKioZ+Gnp+fnZ+jop+Fk5OSkpOWlZSAg4mJiYiJhoP23oaEg4D9/P2B6+r6/P/+79/VxLre7/T19vn39f/n5P6A6oW6ubbGxMDT5ODp7ezr6Ojn2tnw+/qAh+nDsZOA1Wa/namen4i73ey3rKeom5yijZukpqGamJSO9J7v1qCHoaOou/mY8K7ohY3Vl5nNvMLCwLu0p4bw2teRr6Cmq6awubWgppybmpeYh/j8r66+wcK7rcS8wtnPrcPDzNbW2/KEjeGAgoGIi46Ij4+Qj46TjvrI5b3dlKWpqvqgm89+/qKsqui9hZ1kZHuazJeP0oC6w9fk8IL7g4eFh4mKi7G0qp2YlJCMuKyioJaOifPUwayXg+rF5dLdjL3wkKexrKyrqKa6jGPWoqv/qver0PeGjdDR0eWIy5nYstORw4OI9/eAhKfMxYuOkpCPr66xxtStg7qznrXb0uTm4trYgIeEgF2VXW1qX1deX1pWnXqKf39fg5SlXl2dk2FikWdeV1RVZ36aZZKJVmyYj5iSn5JsWmZtcomDXmRugqB4mWNTc2uVT3KFbltUmIFlV1VSTlBaZoGLnqBUplRUqFhdYWFmcKdrbXV7hIOCf4N5e3+HgIiCe3h4enhzcnVwcnCXH35bamlmiYxWWWJpamhnZWJiYWFeW1lWVKVZaXd7fHyFfYB7eXl6enl4eXx/gH6rXXt8fXx9fX18fHlejpCOi1Vkamp0fX+Af4GDhr66jbShv6N+g4qLfXZ2cmlgY19WWFddX2NphZSOjo2NjZKanojAkqeqfoR4dXJyb16nfGhaXVxcr61bYWNjXKSOgIKIhoGAh4V+f4J8e3V2cHh7fIF7eoB2eIB/gI6apqmusa2srLOztbGxra2uq6OOnKlaXVxbsa+fmaSknpyYm52bjqKfk5WVl5OPgpCXmZWOkZGIiYaDgn9+fXx8eIiHhoaHhYJ9sZKNioeDfnd2d4F/fnd3cnBqXmVpbHBqaG1hbG9saGdlZGJlZWdpamlnwcGuZGW9wIC2sbCqlKuhoaCfl4iBeIuPkZOQlJiVk5ORk5ORkZCRjX+CgYB4d3JwcVuRlJRMTZOLhoWiXFtfYGViXlikoZeSmZiMf42Ei51unZmTnklDPUI+PT49Ojk9aj1BQDs8c1VhT21+e3BzeICDe2pWSY1KXGtubXFycW9vd5J4T1BTTm5OS01NTY1MTUxMlpZMTJeKj5JNTE6YkZaRg5eVk5eUlJOSfGxoZGFeX1pgXoCOjY2Hjo2MhYGOkpaTl5WXjZFOUFNTVVZUT11eYGFiZGRkn0tMTU1MSomMT2BgYF9fXmFfVWBeYGFgY2VVY2NkY4RggKBaXWFhX11bWFNtbm5ra2poZ1VmZGRkZmlnZVVhYF9fX2JhYVJTV1hXVlZUU5uNVVNTUqKfoVOamaWmqamdkIh6cYyXnJ2fop+aoo+NnlCTVX17eYGCfoyUj5ebl5eWlJOKi5ujo1NXkXZ7hKZRj3J5anNXdY2UfmhnaF5cYVxngG5va2ZlYl2aXntuTkJPUVRfiFN/WopWVX9cXn14fXx8eHNqUZGEglZfVVpbWlxcXFVWUU5MSk1IjZZaVl1fYF1XYWBia2JWX11iZ2ZodD5DfEZKVltcXVldXFxdXWFerI7UoqldaWxwoYyDp8mWn5i/n3igdXB4i695bZZZgoiSapigVqBUVlBSUVJUeHpxYl9bV1V2cW1rZWBdo4qAc2Vbq5OliLiKsdF3hIyHhYWEhaeBYs2xl7h2qXKJoFdahJSVo1+BWXZpc1l3UmK2tl5jd4t+VlhcXFJiYWN1j3hZdHd3g5uNmJeTjo2ANDQ0KEAlKionIiYnJSVFNzw2OTBJWGlAQ3lwRUZnPDMpIyEpMjopRzopOVhZYFxwZEQ0MzU7XGFDT4yJhW3pl4KzZHEkL0ZDKCosKCIfHx4cHSQoKy41Nhw7HyA/ISMlIyIoQSkmKSsvLi0tMS0uLjAsLy0qKy0sKygqKScpKUYhUkNQUFBpaUBBSlJUU1NSUFBPTkxKR0M/cz5LWV1f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8u/v7/Px9dbz9PP7gYD7+eODhYeHhIWGiPeQkpSYm5qdm/P+gIKEgYOFgoOVlZiAmJeSlJWDlZiYmp2ano2VnZubmZmYmIb0+vqA7vCAg+2dpqalpKKjn5aXo6KgoKOinpyGmJiVko+Lh4PkhomJiIaEg4L85oeJhIKEhoKE++Hby7/EwsfFu7TG6O3w7vT4+/Lt2LfWw8K8tbW7vLq/0Ofi6+vk5ODh49ra64Hy9eKAnYGb8nK+mZKWlpzc4dOjnJmUnaeRnqaopJ2amJKH07GGgbuXlZ6xr8uQl8mX4IaugKSMn52SkI+KjpGVh6Owpaaspq64uK6ymaGblZeDgv+/xurw5+rf3drd4+Ha6PHs5N/a/aus4f6DiYaKjo6PioGEhoiIjZKV8+GNk5WVk9SA9fX9kpzNZITXk6esray1sKD0gZaOiYj99OHYzL60ppyZlZCSkpOXoamk3Le2wsvL0dXRysO6ubv11samjMWQb2+T0429973b7Y6hpb3S0cewl6GYlIC2lrffxrG87+nbkZOEhc/2zYuA9vH5kajV07Hvy5iroujI+qGTvezU19mAioN4cW2Bhn5PU15gXVWdf4uPjoh7bmNcoVtpV4V0X1pZV2FsdoRglV5lbVVXWZ6QoqWKZFZdfZBXdGmAe6lrVaVgm3qGbH6doZaYhGdubmllZWZmhJGTnZqhnJynVlhWXWJlqGZoa3R5d3l3cHRzeIF+fX+AdXNxdX56eXh2amU1a3ChhVyRlGNfWFNbY2lpZmJiY2JhYFxYVaWio19veXp5eHd3dnV2d3l6e3x+fn18qbV5eXmFe4B6eZSPi4qFqWJrbHV9fn9/f4GBgoKTZ7VwbLNolHuEkJORjHplX1ZcZWFbXmBphZKSkZGPjI2Sg72YpZ+JgXRzcG9tbGKZdra0rLexWltbX2FjZWVepYuEg359e32BhYB5cHh8d3R5gX9/foKIh4aBgIOBi5SeoaWorl5fsqyvr4CeaWNhYWZueIuanJaepp+mqJ6fn5WknZuam5yfn5uZl5KTlIuLhH+GhoB+gIGBh4KJlY6FhYmGgoKWkJKXkY2FfXh6e4B1cW9obW2zZWlta2dqaGJncW9uaWlsdnNvbGlpZmdraLipuWHBwq6dko2Cd2xrZFlUVVN6np6hnpaYk4CQkJKOkZCJipGTgX57dnx7fH19f3ChlZedUk5PT01NYl1bnZ2ZnqCclpKUlYiEio6RpKCdcqKgOTo5QTg5PTw7Oj05Q0NPVIJWcH6EeXh7f4CEg4OBfHdrbW9sbXR1cnN2dYBvTk5MTU1ST1BPS0lPT1BPT05OTI+KS0pLTUxOTWmXkI6STZmVTJiYlYqMi4F1aF9dY2qAkZCMjIyRj5J/kpOSlk1Mk5OFTk9RU1FRUVSfXV5fYWRlZmWWmktNT0xLTEpRXFxeX19bW11RXWBdX2JiZVZeYl9hX19gYFSMkJNJh4pISYhnbGuEaYBmX2FoZmVmamdkY1djZGJfW1dUUIpXWVhXVFJSUp+RVldUUlRVVFajkol7cXZ1d3RtaXuUl5mYnZ+fmpWHco2AgH55d3x7d3yJl5GXlo+Rj46PiYqVUZqei19ohr1Yim9nYmlkjY2CYl5cWV9kXmlvcW1oZmRgV4NbRkJdSUlMVYBWZE9XcWKMVW5TZVlmY1lYWVZXWV5VXl1XWFtbXl1dWFlQT0tJS0dHjmVofYF/gXt8eHx5dW96enhzcWyDX199i0lbWFpdXl5YUlNVVldaXmLqrldcXWJinrjC3XWHznOJyIKQko6KkY+Btl96dXBqw7WooZeOiIF/cHBtbXBxdVl2fHOhjI6PlZmanJ2cmZCKjLihnIFytJF+fJS2c424hpOZWmZsfI6OhXJhb2tpU3NdbpJ4a36Kh4BbaWBfjaiLXlenpatnY36Deq6IZmlolX6fZ2uMqY+OjoA1My4rKjc6Mh0gKSsqJko8QERFSEhLSEmCR00+V0g2LiUiJSsxOi5SMTI7MzY3Ylltalo/LjNVY0KIfG9t6KyH/md8PjUsOToxKi0oIiYnJSMkJicsMTAyMjs6O0EhIiEjJSZBKiYmKSsoKysoKyssLispKy0qKyorLiwtKiklJIAmKVBYR3F1UUxEPkROV1lWUVFTUlNQTUlFgXp3RFReX19gYGFgX19gYWFjZGVlZGWEkGFhYWJhYF5fX15pVVJRTYNNV1lgZWZnaGpsbW9wgF2oZl6gW3ZeZWlrbGpcRkA5PURAODo7RF9qamlpZ2RlamBqe4V9ZVlTUU9QT1ZSgIBlnpqRnphNTk5TVlhaXFORenJwa2tpbG9va2dfam5qaW91cXJzeHx6dnFvcnB7h5CUl5ujWVqmn6KhkFxVUlJXXmh7iY2OmaKaoaWbnJ2O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gNmbh5ajs7/Cnt2zmp3ExLehu9Ld5eXn6/T7pLCoorCotbe0qLWenZyQlImDg6+rwcTIxLjPx87e6ejv//Ts6d/rjo7r+vPl7PL2i5WUkpKSl5eZmdCbiaGoppCIjpmer7T964bZw6ybi/HXyLGK97+AXJB42+T4kqi208/e7erpXerg6uje2uvoj3/q5XiEnbr+mrDB2ZmPqsnogZOgrLzE4/P68fPS2ICSk8KPk4KWgP/Pqf2mqP7Ovtqzvr3IgJOc1pyYo+Oj5Jq61rSJldWUjMig3+uwsr2KuYu+3YCDjoN4e6RrX4ltjFpWoIdndHuLm5iKeWttX5N+a1pgc3yCfnR2kmhzeYh9V1hUV1aeXIppZXCBlXKBeHy9eVprsJCcXF9pbZWSmZWbkIh8eHRygIuVoJ+eoFOenpmhoKdbWVqiZGtmaWx0cHF4d3h8fn+Cf4B4eXJ/enp7dHNqazVrcXR2dHV4YGhkYFxWVF1mZ2ZlY2NkZGFcWFaqp6BXZnJ1dnd4eHh5enp7fHx7e3qksXl6eoR8FXt6eWmTioSBqWJqbXV8fn99f4GAgYSCgKtnpp2Kx26gk5mfnJt8Yl5ZXW5+jZ6hopqamZWXlJF/yIufo4iIe3p5enhxYZJ2ZmZhs6iusl9hZWVma2xra2CojHuAf359d3N7gnl8fH98goSBhYqJiYGIiIeKiIWGiZCdql2wtrGSZWVkY2VjXVxkYl5hZXF8jJqcpJKinJedgJyenqGVmpaWlZGUkpaSfpaXlpGPkI2Ie4aHhYWDhoSAd46ZmpyTioaBgYCFgHd0d3N2dbhwb21va2prabltbmtqcmttcGhtZWZlZGTFvo2DgHNzbndxbmdjXFVZVaCSkJKFkJCTkZKOjoyHiYWMg4WCeX59gYKBg4eKioeNhllSTZqcU52Ti397klhWWlxVVVSemJ+mk46Mj5OdV6Smo5s7OjxANztAQU1GV5FUbYKGgYOBf3x6dHh3e3x7d3J0gYeHj4N5d3R6fYJoS1FQhFEPT06TSk9RUlJQUFBPTZVMhE6ATE5QTZGVTpdOT0xOmZ2QUFZUUlFSm5ONe5CTi4+TjpCRfY6QlZWUTExLjk1RUlRVV1hYVVhdX2BgYmRoZVReXF5dW11iXlRdXlxeXWJhYFRjYWBcYGZoY1ZiYWBeX2BeXYONR0pKjIhHS1FnaWlmZmlmaltqamhpaWdmYV6XlpWAkUtJkpKOhVlZV1VUU1JSUpRXWFhXVFVUVVR9cWtxcnBvb2tscYqMlZmcoaKYWmhpcXyAfX18dHZyc3uLjo6IhY2OjZGjooaNlJB/ltCKaFuSdmJdaHCNfGtgXFpgbmBpb3BsZ2VkYVuGgGpJhmxNQklQVl5lW45tYWmBenBldYOAiY2NkJOZoWFeWlZcWWBiYFpdU1JPSUpHRkdVVF5fY2RfZmFka29xeX99fXlyc0VJf4qJi5GTl1tkY2BfX2NkZWSJiWlqbm1gYVtjZ3aGxLJin5CAdGm6pqGRdbaWe2Kee8vG03aHk6GotLe6t7m2uLitrKqzf3bYx213hJ7OdoRKip1uYHWFm1VdZGt1e5SfpaCeiJBXaWiIY2peaFKgiHKqcGqYd26Obnl9iF1qbZtzc3ehd55pgZV+YV+GXVp6aIqTcHF1V3ZnjaCAOjk1LzFFLCc7MD0oJ0xDMjpATGBlYFJTUUVlT0AzMDc6OjU0N0s6P0NIQzI2Mjc3ZEBXOjQ8V2x/jGxw8smKpMp3XCkqLDIvLjEuMy4uLCsnKC4vMDQ1NDgfPD08QD9BIR8fPikqJCMmKSkpKissLisrLiwsKywrMCwtLSspJSYsJyopJyY5Uk1UUU5KREJKVVdWVVRVVlVTT0pHi4J1P05bX19gYGFhYWJjZGOEZAaAjGFiYmKEYYBiYFBZU05MgkxYW2FmZ2hnaWxtb3BxcnKaYJyMe6tbem1xcW9zWEJAPUBNWWd2ent0cW9sbmppXG5xg4ZqXFlXVVhYWVF6ZVpbVZyQlZlRVVpaW2BgYGJXlHZnbGxra2NianVrbm9xcHN1cnd7fHtzeHl4enl4eXqAj5tWn6qlhIBYWFdUV1RQTVJRT1FWZHGBkpWgjJ6XkZWTk5abjpSSkpOPkZGUkICbm5qWlJKOiHmGh4eIhIiFg3iJl5eVjIWDfHp7gHtwbnFrbm/Ee3t5e3Zyc3PLenx4doB5e352eXF1c3R3592ilI5+fnmGf314cGZcYV2qm5ealJyboKKloHejnJealJ6UlpSJj42SkpGWmpydl6CZXlKWmVGViYB4dIlUVVtbVldWoqKts7C6vMrO3oH17cueIR8fIR0eHyEyMz5lPExaW1dYVlZVUElMS09OTEpGSFVaW2FXT0xITE5QPCksKy0tLy8tLVMrLjAxMjExMjEvW4UtgCwtLixVWS5aLy8tL1xgWTM3NTMyNGJcVkdWV01SVk9RUkhQT1FRUSkpJ0spKywtLS0uLi4xMzU1NDU3OjgtMjEzMi8xNDMqLS8tLi0xLzArMzIxLTA0NzQtNDIwLzExMDA3Oh4fHjcyGRwlNTc3NTU3NDoxODg2Nzg3NzQyTEhECz8gHj9CQkMxMTAuhS2AUjAyMTEvMDEyM0U4Mzk7OTg3NTU9UlRWXWFjYlo1PUBKUVNSUVBLTUlESFVYVlFRWFlXWV9dTlNYVkhMlICVg3FBSFV+S1JGPDQyMjRBTFRYWVVPTUxJRWA+NCRCMiYmLjIzNz81UUI3OEBFPTA2PkJDQ0ZKUFUxLywqLy40MzKAMTUvKigkIh8gISkqLi8yNjQ1NjY4P0NHTE1FRj8+IShbZWZwdnp+T1pZV1ZXXF1eXXNWU2RoZ09IT1ZaYkpnbUd0bWNaU5GEf3hsi3uWiOWYwqy5YnJ/j6Stl5uxtq6sqaOjdbOUh+/okqS1vtVta3B0SDlVZ3dAQ0VJT1Nsc3c6dHRhZD5JSWVNXFFOM2ViXodWRmRAO1FASEpVPENHcVVUWm5HXTlHXVpIMz8oJi4pMTQqKiskRkljYIV/CH5/f35+fn9/jX6MfYp+An1+hX0QfH1+f4B/f39+fH19fn5+fZJ8AX2GfAR9fX18on0BfpR/g36Sf4J+i3+EfQF+kX8GgH9+f3+AmX8BfIR/hn4Hf4B/foCAgIR/ioCjfwGAvX+Tfoh/AX6Pf5F+hH2ffgN9fX6GfYd+in0Dfn19i3yDfZ5+AX+JgAF/ioABf4mABH9/gH+EgIN/hoCSfwSAgIB/toAHf3+AgIB/f5WAhH+CgIR/iYABf4mAlH+dfgp9e3x9fn6Af359iX6KfwR+fX19iXyTfaZ8hX2Efop/A359foR/AX6Ef4SAhX+Hfgd9fXx8e3t7j3wGfXx8fHt7hXyEfQV+f35+fo1/g4CGf4N+jn2FfIJ9h36KfwR+f39/AgIEAICg0d3L2Y6sm5qd6LDe+8yiz9zhyLe/5eKx1aurra21tdDx8O6DpqXA0+POiYuPkf/co8HJ0Oe5h83lmPuPdqPxs+iVp6qFnOjp7uPl6O7u4dvh4tzu+Pj/gvv28e7w/IKGi5DSjJadpaurrbaxv7/Bub3DwL69v7W3tbm5r6qlq06rrK60vfnDxJ3qkpWRi4T6goqUlZSUk5GRjIeDgP759faLoK2xsrCvsrKzsrGxsrHh+a+xsbKxtLKztLOwvOnUz/2QnaKqtLW1tra5uLqFvC6+w4SCytPhlpfm3+j15bi6v8rJ2N3l6Ozz5ODl4t+2mMXp6dP43t/e392xicqdhIqAi4eB/oWIjY+Qk5KVk5GQi/vYuq6xsbK6uNDDw83JxsfMycnLy8fLys3Jxs/LzM3RxMvV0ZOTk5eTkpqbmJiXk5aYmJWSiY6l3+Tn4d/X1dTR0NDV1M/Sz9TPwMbMzc7KxMC8vrHAwsLAvb25trm4uK60sKimoouLmJiPkI6Qi4qAg4yPioqGhYH/8fbw7/v+/oD35oWBhIaIg4aE3aqys7uqpp6ZmZSYlYn++Onk5PrFyMPBwbzDtaugxMjCxMC7sbm9xsrExsK8wMW7q4Lw3tTO2uXY2ODoh42UlJKIiImOkfTt+viD79nPz6mT9Zuis8CVssPR08TIx9LT18zHv8cizc7Dw727v8HI0+Xf2tjb4uTj09iqgYuNkJKOj42NjYKGjoaNgIyGgYmJioiIiIeJiejb4uvx+4SDhYP0hIqLi4qIh4OH3vf3+fLu9fH56OLr8PP3+4CDhO6IiYqMio+Okv2VlJWWmZudnoqSlZWTlZWZlY6NlZiampqcn52Nn5ygn56goqCKoKGgn52bmZXvhYWGhISGiYj0oKWooqCYmpqTjaKkgKKipKOfmoPv/v+A/4OA+vjjkY2JioaA+Ovb1oiJiIeGh4ODgdnFxMTFw8nGx8zAzfj6+vjT0qqnp6WmrbOvqamzsbS7tLjN4Pvx5Nzh7eHq6+7cytGx/qiY+O6pg4qXqL60qJmco8yUn6amopyZmJOK3ebh0ZLy5r2VjZ66p7G6gIbXwbXFhq6QiI6Zqbq+xMmLr6inrauqu7essqWlnZeWi4SIsK63wb/FuNDO2OPj0NneztvQz+n6++D1+PyAhIOGiY2Vl5WdmJaYzKCLpKWg+4iYoqGusa+ur6aprbi4o5+joqursrSzubOsp6CRkI6FgfeBgf/1/YKHipyvsLy+U7nDxMXO3ufm2MvT2Ojt+oD56urr6+71gIDD3+Lp9Y+uxImAt4qMg/nj5bTIjbSrmvDK1Nb3prmAmqe4z+X68biKuZCwoaSd1a+xt4e39/7xvPmvgHmYmYOMXWthY2eddo+fiW6IkJeJeH6cmneOcXFxcHR1hZmdnlhvdXuFkIBWWFteqZFpgY2MmoNrlqJ20YFegrB/m1VbYExek5WalJKVmZmSjI+NjpmgoKRSoqGgnJ2mVlpcXp5cYGlucHNyeHV9fIB7foF/fXp9dXl2eXhwbGlvgHBwcXR5uYp8aaBlZmNeWaZWXWVmZWZlY2NgXVlXqqahn1tueHp6eXl6ent7enl6eqKveHl5e3t9e3t7eXZzj4J+rGFscHV8fX5+foCAgYKCgoODhIlkZ56ftXRxoJujr6F8gIaPjpmepKirtKSdnpyagc+PqKeSk4aHh4mIe2SVRXtmZWVmZmNcuGBhZmZoamlsa2poYqqNdnB1dHV5eIqCg4iFhoeIg4WIhYSDg4eFgYiFhoaHf4aQjVtaWl1aWl9fXl1cW4RdgFtWWm+ho6SgnpmYlpOUk5aYlJaSlZWOlpmYmpmTkY6QgYuMi4mIiYaEhYWHfYN+d3d1Y2dydGxsaWtoZWBnaGRkYmFfvbS6tbK9wMBgt6xmZWdobGhpZ6t4e3qBdHBsZ2lmaGVZpZ+UkZGig4R/f4OFin95dZWWjo+OiX6FiZWbX5OUkI2Vl499VJaKg36Fi4F/hItTV1xaWVRTU1hcnJuqrVyvp6CggVl4UVdjb1ltd399cnR4goKEfXt1eHyAendybW1udYCOiYaDhYmKiYCBYUhPUVNUUlNSUlNLTFFQhFJHVFNPTFFPUE9PT05QT4V+go2PlE9OUE+WUlZXV1ZUVFFUiZWUlY2KkIySiYeMjZCWmExOT45RUlNVVFdXW55gYGFhYmRoaVuEXoBfYGJgW1heYGBiYmRmY1diX2VlZWhpZ1hkZWRhYWFfWopKSktKSUtLSohmaWtnZWBjZF9baWloaGloZWJSi5ORSJJLSZGQiVxZV1dVUJmPg4FVVlVUVFVUVFSHdHRzdHN1c3R4c4SlpKOgh4NpZ2ZlanB0cWtrcXByeHR2g5Cgm4CTjZCXjZaXl4yBfWz0lHq5qn1eW2hsc2hjWlxhfWBqb29rZmRjYFqHfHtzTn1zXEZETltUYHVVh4J6gldvXFZYXml1eX2BVF5bWFhZW2NhWl1WVVBNTElISFNTWF5dX11lY2ZpaGJlZV5kYF1mb3Z3hJCgUVNTV1lcYmNiaGVjZYCHjGpqa2ikY2JpaHZ4d3NzbW90fn1tZmlpcHF3enp7eHRwa11cW1dUoVdVpJ2gVFdYZ3Z0fYB6f4KEiJOalop+homWlqNUoo+Qk5OWoFRVgpKSk55ngZRXTXhlZl+1oJ18f1h6bmqYhJCPp3eCWW90gI+grKyIYnVVaGNjZYRwcAl3UXKdopdzmHOAV2hRNDYlLCcpL008QktMSFJUW1lPVGZlTFNFQjs2ODo/R1NdND0+Q0hLQy8zNDhoUzJPUlFcXHqWg2/45JHG6G5oIygwLzIxMDMvLi4sLzEuLy8xMjM0Nx09Pz09P0MhISIjPiQkJSYnKiorKTEvLisqKy4qKS0pKigpKiclJCgqJyUmJCRHPyo+eFJVU09JhUVNVldXWFhXWFNPTEqOhnt0RVZgYmNiYWJihGOAZGSCi2FjY2NiYmFiYmBdUFJJRoJLV1xiaGhpaWlsbW9xcnJyc3N4XF+UiJlgW3l0d31zVV5ocG15e31/g4t8c3Rycl54dYyKdGFgYmJjY2FUfWdZWVpbW1dQolRWWlpcX19iYWBeWJZ4YVtkZGNqa3pzdHt4e3t7dXd6eHh3dniAdXN6dnd3d291gH5MSktPS0pRUU5NTk1MTU5OTElNYpmgnZiWkpGOjY+OkJOQlI+TlI2bnp2gnpiWkpSBjI2LiYiKiIaHh4mBhoF5enloc3x+dXV0d3RxZ29ybW5qaWfMwc3JyNTW2GzRwnd0eHh7eX19zYSHh4+Dfnp2eHR3cWOAsqmdmZmxjY6HiZKUoZOMhq2yoqSknpCXnaqzq66moKyupY9Wk4V9d36Ee3d8hFNXW1paVFNUWF6tssvXeOfl3tigX1hERUhTRlZdYV1QTVReYF5YWFJUWVhUU01JSUZKVGFeWlhaXV1cVFM5KCstLi8uMDAvMCwtMC8xMjIzNDMGMS4wLi8thC4yLUpESlNVWjAuLy9eNDc3NzY1NTI1U1lYWFFOUk5STk5RUlVXVyssK04rKywuLC8vMVaFNgM3OzuGM1AyNTMwLC8xMjIyNDQ0LTEuMzQ1Nzc2LTQ1NDIxMTAtQB4fHx4dHR0cNzU3ODU1MzY2NDI5OTg6Ozk3NSs/Pz0ePSEgQEFFMzEvMC4rU0xBQ4UvgDIxMjNRPjw9Pj0/PDw/PE1lZWRjU1JBPjw9RElPTUdFSEdGSUdJU15oZFxYVVhWW1xcVE5HONCKvvN+Q0ZUekhBOjczMzVRTFVYWFROTExJRWE8OzgnNjErJScsNTI8Ry9LUUpGLDgvLCssNDs7PkAoLi4tLS4vNTQxMzAvKiYjgCEhJCopKSwvNDM0NDY2NjMzMS0uKyosMDtTYG2IRkhHTE9TWFlZYVxZW3FWT2RmYn9KVl9dbXBuaWpkZGl1dGBZWFZZV11eXmBdWldTRUNEQD96QT93cHE7OzxKT1JaWExNVFdeZ2xsY1dbXWRodDtxX19iZGhxPD1haV1fbl9qM3Y4JUE/Pz13bWpYWz5NQD5ZTVVXXkRMN0BCRkhOWHBiPjcgKSorKjQvMzQjLjw+OixASop/j36LfYt+iX0Kfn+Af39/fnx9fYR+AX2RfAF9hnyEfQF8oX0FfH19fn6FfwF+jX+Efo9/gn6LfwV+fX19fpN/B4CAf35/gICWfwF8hH+GfgR/gH9+h4ABf4yA+H+IfgN/fn6If45+hn2efop9in6EfQF+hn0BfIR9o34Bf5+Ahn+EgAF/iYCQfwSAgIB/iIABf62AAX+IgAF/k4AKf39/gH+AgH9/f4aAhH+JgJF/oH4KfXt8fX1+gH9+fYh+in8BfoR9iXwHfX59fHx9fot9qHwEfX19fo1/CH59fn9/f35+oH8Gfn9/fn5+l38BgId/A4CAf4R+hn+DgIR/hX6HfYt+j3+CfgICBACAv/2L3f2ZxK+qnaCtmcWZ4L+m+cmA9de8rqX3qrS5uLK+45WwvMGnuMzgyI+SnYa4oqmpmriil731m4qUdsuDodzQgYCAt/Dx8N/w4trn4t/Z4N7X4ez16e/79YCBgPz9g4uF0o6ZnaCkq7msrbCzzMrMzMTAt7m5x7/JyLq/s7KAsry5v8mHwcW9ve26/pGRjIf+84CNlJWUkY+MioiGgP358ufylKmurbCwsbCysbCt3fSurq6xsLGwsbKxro3x083/jZ6jqrKztbS2uLe5uru8u7y/wcLB1IvsjeyBoJLo5eji4eDd1NXV3ez3+PDf39+4n8Ld3c2C7vDv8e6uhMmAoIuJiouMi4yE9oCFjI+WlpKPjY6KhYT948i2ubi9ysPM1tLPz83DwcHEx8jIx87O1dLSz83Mx7/Cvrmto6GdmJORi4qHhY6NjY6SnOHp9O7u5fDz+djJw8O9w8XGysexzMnLyNDNy8PAtLy4u769t7SysqywsK+vtruzlKSkp6WAnZqYnJ2EkI+KiYaKhYWA+vny8fX59fr2gYSGhoiAh4qAt7Sqsbeop6efn6CajouKjpmgrLXJw8S8uMO9urCvx9HW2MfJy9fS1MbDrJ2L/efk5ure3PPs8trc7+7tgouQjo6WmZSP8oeKiIaLlKKqsLS2usW5rba7vLrExcnQxcSAvbe6vr+5uLq2vdTZ1dna3dzc4dve4uOn/46QkZKTkJCOjYaCjY6OjoyOkZCPg+WBjIyKiomMiYru5+jl4uPZzeDm6IWKkI6Oj4+Niu37/v338PH3+v7jgYD/gYD9+YDphouKjYyQkJGS/5GUlJibnqGhh5OUlJaVmJibhY6Vl5mAmJucmpmPo6Cfn6GioaCKn6Chnp6bmZr7hYeEhP6BiIuHhqSgnaKfn6Gjj5meoaKlpKSfn/qCgYCBg4OD+/bi+Onj4+Df29fazoOHhoWEg4KHhuzGzMrHxsXGzMvKxOb2rKahoqaopaqio7Kysa2ur7OzsrG209jO3/Xd1OT26u2AyqnOg/20o/yBnP6RnI+srqKnrfaZo6ipo5mUkov3kOXr69GxiIL4rI6jnKySudKz7/TM8e2yk4uIkpGMheKtrZ6alKartaqvo6ijnJyEhIaxrbu4tcS02s3V3+jc3d3Y4NHO3/aB1uv9j5ORgYKDh4uMlZqQkZHJnPipsa6IiJqAp6+7u6ijqKinoru7trGzqq6ooqKgpqmov8rN1dfI0M7RzbuxvMfR1OTt7vP46Ofm3efz/IKEg4OC+/74/YaHhvrZvaGF27aruNCGhfKVyoKewsS3q6OOrenizNS5uNGkrMXS4ufn5/f32ZeJpsPsoLuYm5/pr5v/24L06d/g6O6AeJhenrNlfXBuZm57bottoIp6t4pZqJB6cGmdb3F2dm93mWd6goV2eIeQf1lcZFR6Y21ybH9tcomtenKIYpxgdZaDU1NUiJqanZSYj46Pj5CKjo2MkJiim52hoFNUVKeoWFtYoF9laWlrcXdvcXV4hIOFh4F/d3p6h3+Cf3d8dHWAc3p6e39hhXx4epx+rmRjX1qon1ZhZmdmZWJgX11aVqqno5mhY3N3eHp5enp7enp5oqx4eXl6ent6enp5dl2SgHyrYGxvdHp7fX1+f3+BgYKEhISFh4aGmmy5brple22koqSen6Cfl5iYoK23uLGampyH2JChoZFOlZeWmJZ5YZUDe2dlhGaAZ1+qWl9kZmxraGZmZ2NfXrOchnd4eX2HgoiOi4uLiYGCgIOEgoODiYiMjIyJh4WCe3x5dW1mY2FfWldUUlJQVVVWWFxkoKevrKykrK2tl42IiYSKjIyQkIGal5mXn52clpGEioaGiIWBf4GBfIB/f4CChYFtgX1/fXd1dXl7YGqAaGRlZGhkY1+8uLO2ubq3uLNhZ2hoa2RpbWWDeXN4fXNxcmtubWdeXVxgbHaBhZWRlZCNkYuJhIWXnaCfkZSXoZygmZV+b2Kpk4+SjouMlo+Tg4KPjIxOVVhWVlpbW1mXVlpaWl9kaGxwdHJ1fXNob3V1cXZ1eH54eXRxdHZ3cXEdcnBzg4eChISGhYaJhIWGhGGNTlFSU1RTU1FRTUqEUIBRU1dWVU2IS1FRUFBQUk9SjIWGgICHfnaAhItSVlpZWFpZWVaRk5WWko2Mj5KVh0xLlExMmJdQkk9SUlVUWVlaW55dYGFhZGhrbFhcXV5fXmFhY1NXXGBfXmJjYWFbZ2RjZWlpaGZWY2JjYGBfXl6WSkxKSY5JS01KVGhlZGZkZIBnaVtiZWdnaWlpZmSaTEpISktKS5CPiJiLiIiBf3x5fHlRVFRUU1JTWViZeHh4dXNxcnd3dXaVoW9nZGRoaWZqZWpxc3FubW1xcXBvc4aJgYqaj4WNl42VgWx5VvyVhbldbbNha1hkZ2FmaptkbG9wbWRgX1qhVHl8fW9cRUN+VYBGUE5aWnB/d5+chJ6ddFxXWF1dWVSHXFtUUk9aXWJcXVdYU09QSElKVFVbW1tgW2lkaGxuaWlnZGZgXWRvO26BmF5hX1BOT1VZWmJmX19dhI29bnNyXGVocHR9fG5pa2ppZn99enZ4cXJuaGdkampsfoSFiYqBiIaGgXRsdHuChViUm52hoJaXlZGVnqdWV1ZXVqKjnqVaWlqnkHppVox6cX6NW1SVXn1RYYJ/cWxmW3alpaCejISHbnaJlKGioJ+prZ5tWGd8lmN6X19mlHFZo4lSl4+IiZGXgC1AO2RlLTQxMTE+TVBpWIJxZpBlPWxYQzo0SzY3ODc1PFg8R01PQkZKTUAwNDcuPCYvNjhJRn2JiHCB85r2hWdwPBoaHi40MTIvMzAsMC8xMDIxMjIzNzc6QEAhHyBCPyIjID0lJSQkJSYrKCgoJy4tLi8uKicrKSwr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oqKmo6CFjIiIh4iHhYqF7oWIhYOAh4uL/Kimpqemp6anqIyfoaempaalpJn2hIOCgYOFgvjvze7p5ubr4trZ18eBiIiHhP349/Hmws3Ky8jGzcvHx7/2n6OgoZ+krrG5tamqs7Otsa2us7i+w8HN5NXX29rq4NeAxaKnxsWCv6+Dj5iAj5SAqKy3w5ymp6ipo5GMiveBzvDv7Nbi252FgNKiiJePtJ7Licu7kqbohe+mhfyBj9qlm56clZeppKirnqmxq6GOh4ysqru/v8G6ys7T4eLX4+nT4tfT2fX91PDt+omI+4CCi46QoqimpaTOpp24uqDjhouAkpO2uL68v66xtLOupaupqKqsmpKVoqux0Nja3uHc3tzZ4+Pj5+Xb4eHp8Pn+7d7p5eXuhIeJh/Xw6tnBsKiH3rrF6enk4+L8lKett7GV0tvi29jR1OHVyceLmKqGl9bNqsnX2M2V+MCRpLy70viXlpLwy5W6oZ+58/2B9PiGiISAUJuHZZZ3p5aEnJ+gqFuOWnZXXa+Gc156fG9paVSMkZtZbXR+iIdwZ2+Eg1hTn3xqeYSDj2tuhKl9ckJmXG9plpyknZOUipCTkpCOjIyRjYqKiJSVlaWgoqGmp6SoqatYVVpbnWFobG52eH16eHd9iYiGi42BgX98hoaFhoZ/fHwrgH2Gg31kfYJ9fYKLiVqMW2FfW1aholpjZGNiX15cW1iopJRgcpVcbnV2d4R4B3aeq3Z2d3iEeYB4d3JrdX96rF1rbHF4eXt7fH6AgYKChISEhoaFhoWEhLBoqph7bYFhoJydqaupnI6Oj4+SnZyXhuCNoJ6TU6Kinp6bYZOIgGRmaGxtYl1hY11YrllfZmdpYV9hZGNhX2FcnIt6f4aLiYSHjIqDhIiCgH99goKChYmFg4GDgoKBeIB0eXd3enhtaWFeW1dWWFtcY5artayyqqSyXbGTnpubnJiOiY2NjJObnJ6goJ2ehoSGh4eIiIeFhYaAf32Bg4B+f26CgYJ+hH1xdH1nb2xrbGllaGJjYF9eYL5fYmBjuWxrbGlqx8fEoHx9eoGKkZaTlZuWnKepq7GxqquVmZSOj4COj4x/fo+gpKWvnpqLgW9cm5COkJSTkZmaUJqcmU6Vj46RkoqRV2FfZV1eW1hgZGllZnF3fH5zcnV5dnJsaGZoaG9zam1rbXVzbmxqa2l2eHx5foWFg4eKh4KEXYxQUVFSVVNSUlBPk09RUVBST1FUV1SNfYGAf4qPTFFSUIaNiYCKhYWJiYV+flJWWVpZWVpaWZhKTVFSUE1MS0+KUE5LTE5NT1JWl09OU1hXWFlbnVNRUlJVWFtcXFNiYGBhY2NkZlNbW1xZXF1fYFlYZ2dmZmdraWZRUU9OSUxMSU5NhUpMSklHS1BPkmtpamtqampra1lmZmlpaWppaGKUTEpKSYBKTEqQiXeJh4aEhoF8e3p0UVdXVVOgoJ+cknZ6dnh0cHRzbm9unWdnZGRhY21tc3Jqa3Jzbm5tbnJ3eXt6gpCIioyIjYZ/e2xrdoh4mJFgZm1ZYGdPZGhxeWdvbnBxbF9bWqFObYCAf290clNEQGlPRU5ZbF6GXYZ9ZnSlXKNuWIClUlmCXFVXVE5RW1pdXlVYXFhTTEpLVVVcX15fXGJjZ25ua29wZ2xlYGRvdHCGoJ9XVpVKT1dZXm90c3FujZJ4e3xokWFWWlt4eoB/gW9wc3NybHJwb29wX1ZXYmtxiY+PkZKPkI+OlZWVlpSLj5CWnKKklo6XlZSaV1laWJaRjkaEeG1vWZB0f5mYmZWOmlppcHtvWoSHioeDf4eWjYaDXWRvXWqelXuVoaOZa7KLZGRwc4KfYF1nnYRgeWhidJegUJWaVFVTgBw2MSdOR2tcVmlra3I2VkNWPUaMbllGUEY1LC4lQUdZND5CR01NQTtAS0guLE80KjE2QFVEfoeEcYKBoo+gYndQNjUzNDIwLjEyMjAvMjIwMC40NTM6OTs5Pz88Ozw/ISAgIDslJCIjJyosKSgmKDAvLC0wKCosKS0sKSssKCgoNionLCclIi8oJSUnKiosZU1UU09LhYRMV1hXVVNSUE9MkY56Slt0SFhgYGFhYWJjYn+HYGBgYYRggGFgXFdNRUGESlhbYGZmaGhqbG9xcnN0dXR1dnd4dnZ2oGKdg2ZcaU17eHh+goR+dHV3dnd9eHVphX2Pj4A3dHV0dHJNf3VuWFtdYWJVTVNTTEiTTFJZWlxVUlZZWFZUVFCIeWtudHp3cXV8e3R0d3NxcG51dnV3eXVzcnRzcnFpgGdsbGpuaV1cU09LSEhJTU9Wj6Osp66inK9aq4+dmpqak4qEhYWHk56hpKamoqOIhYeJiYiJh4SFhoGCgIOFg4GDc42NjYeNh3p/im12dHR1cGxxaWpnaGZnz2tuam/Wfn5/eHjo6OW7i4uIk5+lqqesta+3wsjR2dbPzbC1r6iogKWlopKPpLm/vsu2r56OemSgjYyRlJWRlpdPmJuYTI+HhomHfodRXVtdVldYWmdqbmpqdn6CgHRwcXFpZF5XUVNQVFhTVlNVWlhPTU1OTVZYWVhZX11aXmBdWVo6USwtLi4yMC8vLi5ZLy8wMDMwMjQ2NVZFR0lKUFItMDAvSUtITEpFRkpKRkBGMzY3OTg4OTk4XissMDEwLSwsLlMvLCsrLS0vMTRWKiktMS8vMDFVMC8sLS4wNDM0LjUzMzQ1NTY4LS8uMC4wMTQzLi2ENCU2Ojc0KiclIiAiISAkIzcdHx4eHB8hIEI5ODc4Nzc3ODkwODg4hDmAODVHISAgHh8gIEA+N0A/Pj4+PTs7OzwuMTIyMV9iZGFcREI+QD07PDk3ODpkQEI/QD0+QkNIR0RGTUxIR0ZHSk1OUVFTVVFTU09QSEFHREJAS16X73NHPkVcfzM2OkNMUltaWVlVSEVFeTA3QUE+Njg3Jx8dMCYmLjI7MlUzTEmAOj5QL1E5LVIoKz0pJSUiJSguLzEyMDIzMC0oJCUsKi4xMDIyNDY1ODo6Ojs1NzMtLDI9TWK7jktIeTxAS01VanBvbGl1Wl12eGFzSExPUG5udXV4ZGNoaGRfZmNiYWJOQ0ROV1x1eXp3d3Nzc3J4d3d4dWlsa3BzeHprZWxoaG5NPz9AP2RgXFZQSU8+Z1Vienh2cmJZMz9LUUYzTlNUUk5NV2dlYl9APD41PllUR1VgXFY9c2ZHMzY3QU0tNkhOPCozLiswOz0eNTYeHx0FgH9/f36IfwaAf39/gICEf4Z+g32Nfol9B35/gICAf3+EfZ58hH0BfKl9gn6Ff4J+in+Gfop/gn6MfwR+fX1+mX8HgH9+f4CAgJB/AXyFf4V+BH9/f36LgAF/joC4fwGAv38BfoR/AX6Ff6x+iX0Ffn19fX6HfbZ+gn+KgAF/ioCHf4SAi3+JgAF/iYABf4mAAX+IgAF/r4ABf4iAAX+TgAF/h4CNf4WAkH+lfgp8fHx9fn+Af359hX6Jf4J+in2EfAR9fX18hn0Hfn19fXx9fal8B319fX5/f36Kfwh+fX5/f39+frR/hICIf4l+nn+IfgR/fn5+iH8GgH9/gICAAgIEAIDt6+rsmJSF+7vd2oSZmpmT8ZHlkoqB59avl4GptLSdr7Chr726r46PlKOxgNnPzdDegYGrlrz8n5FLgH2rit+Rj+zg5tm249nY0tza2+fW1dLc5ebi8vj39PnygIKCiYqJj5PXm6aqtLe8xLvOycPNxs/T0s7Gx8rZytnd09jHyFLN09TR3pOs0cnO19jU2dLXn+KLi4eC8uv4h4yJiYWA//r0qt6D3uTh/paoqaqsq6jZ7qmpq6yrq6mqqqmimpL8y4CNmpmeqqyvsbK0trm6ubu7hLqAu7q5urq5vYKEwq+/k6n60NHX3+He29bU0dLWvKu31tG+h/v28vHwkbuH9df8h4iGiYuLio2Rkoz+/4CFj5KVk42LjY6MiIWA8+DOx7/HysjIysvGy8O+wMPEzNHNycbNw8a/s7a7vLq9trCsqqinqKKioJe48v3/gPn56ez4ydqA3NvX2dLP1Nfh4tza09zb39bQvLy+ura2s6+spKKfoaKfoqWlqJOtsK6jqaqknpqOoZmcmpCRkZSSjYqGhoeB+f739IL28vXx8er18dH4gYCC/ezr6uvl3Nrh4drY3NzTw73Evbu/wLy0qrO1saKQgv2CgISEg4eGhIeFgISHhICA/Pr8gIH6/YGChZSfm52gmZKimqSrrLa7u7y5trm4yce/uqqsqamwtrGvp6Wmq62wtsLIw8C8xtre5t/k5IvajI+Pjo+Qjo6NjYiPk46Mi46SkJGQiuXm6uHn6e7l59/h5PPy8fLu8fDq5eOJio6QjYyQkJH/1eLr8/r5/4KG8YEOhoeHiIuOi4r6jY6Li4+Ek4DxhoeIiYyMjY+KiZuZlpaanpyfg56cnZ2bnJqcnoejpKWjpKaqp4uHiIiJioyIior1i4uFhYCGi46KkqqorK6rrq+uoJ6pqqmpqqeko5P9g4KAgoOH//Xv2PPn4+3k3d7Z1cLygoGAg4OA/ID/ycjNyM/U08/A3biknqSmpaqrrYCvs7Krqbavp6Wrsre9w9PpwrjR1tLZyr65n5mwu6aByF6DgKHzl5PxrL7Wp6Slp6ijkouC2PHM9PHq297i4byG/O+zmYWtmrelgtzkrYyZturu2sCb05yenJqSkp2fm6Wgq66to5OXjqar2M7Mx7jSz8/g3tbg7+bh4eHc+Irqg4CCipWQj4uLl5qYm5iTl5rPr4upsK6GkKSrrrCxrbS1t7e6r7Kur7SupKCgoqS51tjX3eHa39zg5t7g4dHK1tXU3eX3g4Dq0cfCxNbk3sqwjufKz+398/P07tzE3YmduuyEiIWB/fz79O7z9O3u8eLEq9Kts7PR0L+LyOze6s+dxhizy+XYs7O25d6arpXPrdPP0s/e/oODhIKAlZWYnV9iU62OpZhZYWBfW5pfjVlUUZOPeWtfcXFvaXp5b3qDgXhhXmJrdlCDfn+Fk11feHKJsnt1R2pjhWSUW1yflJiQh5mOjoqPjYyWi4qGjJWVkZ2hoJ+kn1NVVlxaW15ho2ZrbXR5e316hYOBiIOJiYqHgoKDjYSOkImMgYBThIqIh49reIeAgoaJhoyGiWqaX19dWamdqF1iYF9cWK2qpWyGT5GXlKlmc3R1d3Z0na11c3R2dnd1dXZ1cGpil3pVXmlpbXZ3ent8fX+BgoKEhIOEhICDgoKCg4hlaJmAmXSEuJeWmqKinpyXl5aWmYrjiJ2XhVOgnpubmmGCXrWWtmJiX2BhYGBiZWZgr7JZXWNmamllYWJiYV9fWqyaioR9gYSDhYeHg4WAfX6BgYiLh4OBhn5/e3Z2eHl8e3Z2dHRycXFsamZfgK+ztFyysKirt5CcoYChnJuUj5STmJmWlZGWlpuUj4eJi4iFg4KAfnh0cnR2d3p6entrh4uFfH6AfHh0anhwcW9qampramhlYmNkYbq7t7Niury9t7Kstq+au2RmZ8KwsKy0rqensLKsqKmspZOPlZCOjo6NhHmDg39vXlSjVFFTVFRVVFJUUE1QUlJPm4CZmUtMkJRKSkxUXl5dXVxaYl1reHaCiIV/eHR3eYWBd3RrbmpscnVycGppamtrbXB6fHZ1dHqHhIuGiolRfk5PT09RUlNSUVBOU1VQUE9QVFNUVVCBf4J9g4WJgoV/g4WHhIOHhYiIhX2FVVVYWFdXWlpanHV7hIyRkZdNT5FNTzhQTlBSVVVVmlNVUlJYW1paW5VTVFJSVFRVWFZaZGJfYGJlYmVUZmRlZGJjYmJlVWVmZ2ZmaG5sV4RLgExNS01OiU1OSklES09RTlxta21ubG9wbmZmbGtra2xqaGddkEtLSkpLT5WOinyKhYKJg4B/enlxmVNSUlRVUaFTpXx2eXh+f356b4l4amVlZ2hra2xtb2xoaHVvaGhtcnV3eoSReXWEhoKEeXF2aWVucXN1nk1fXHOvZGiVaHeIgG9tbm9wa19ZU4aLbYKBfXNydndhRYJ5V0tQY1x5b1STm3hiaXycn457ZYFZWlZTT05UWFZaV11eX1lRU0tVWWpnZWNcZGNlbG5rcHZwbGppZ3FDg0xUWl9aWVVWYGNiZWRfYWKImGprcG5Wam1zc3R0c3l6e3p8c3VzdXdyZWFgbWBedI+OjZGTi42LkZaSkpOFeYGBgoqToVVSkn94dnWKlJKDclqSgoWdrammpaOSeoRUXnGcVlpYVaOio52SmZ2en6GVfGuDbXJ9kI+AWoSema2XZX2BlKKWdnR0jYdkbmCEcIWDg4OMolJQUVFePkhOViwuMHhqdWc3MzExL1g8UTAvMmVsW1BFQjcyN0ZFPUVLSEE2NTc/QSY3MjQ4RjY8T32Th3SFjqmbxmh7MyU1MzUwMDMuMjQ2MzE2MjEtLzM0MTY5OTo+Oh4eH4UiUz4nIyIkJScrJywsKC0qKi4tLCosLDArLCsqLCkoKSwpJyslKCgkJCgpKCkoL0B5U1RST5GDjlFWVVVTT5uXklBQM3l9dYNQXmBhY2NigYZfX2FhhmAZXFhNV0FDTVtbX2VmaGlqbnByc3R1dnV2dod3gHteYYtrg2JtlXp5en5+f39+fn18fW2Gd4yHcTZzcXByck1wUpGBoFdUT1BSUVBSVFVQkJdMT1RXXl5ZVVRVVFNTUJiId3NtcXNwcnV1cnRwbW1xcXd4dnNwdG1vbGloamxtbmpsa2xsamhhXFdSeKanqVeqpaKqtIuan6Cal5CMgI6Lj5CRlI6Uk5eRjYeJjIqFgoGBf3hzdHp6en1+fX5xkpeRg4eKhYB7c353eHZwcXJ1dHFta2tsasnKyMNuzM7Yz8a+z8Wx23d7e+XNzcbSzcTE1dfPyczLwq+pr6impaOelIaRkot5Y1amVVNWV1VXVlNUT0xOUE5LlJGSSEiIQIdCQ0ZQWFVVV1lZZV9wfXqMkoyBe3V0dXx2bWVdX1xdXl9dWVNSU1RSVFZcXVhWVVlkXmBeYmI1TCwtLS0uMDCEL3AyNDExMjE1MzQ1M0xFR0NHSUxITEdKSkVBQEZERkVDPUs1NDc3NjY5OTlhPD9GTlNTWC0vVi0uLi0vMDMzM1suLy0uMDEwMTJULy4rKy0tLi4uMjg2NDQ1NjU4Ljg2NjY0NDQ1NisyMzU0NTg8OS4ghB+AICAiIz4gIR8fHB8hISAuODc5Ojc5Ojk1Nzo4OTo7Ojk5M0IhISAgISNDQD86QD8+QkA/PTw9PFkyMzM1NTNlNWlKQUI/REZGQzxYVUlFQD9AQ0FDREVDPkJLSkZER0tOT09UWU1JUVJNTUE/RkNERDxDWqKBbD9Bh19+aDtIWlttWVhYWVVIRD9sVTQ/QDw2Njc2LB44NigjLDcxSUI0VVhFOT0/TU1JQDE8JigoJycnKi0vNTM0MzUzLisnLC44NjU1NTc4OTs6Ojo9Ozg1NDE0JmE8WVNUTUtHSlVXVVlYVFZXcFxMY2hnR05gZ4Roe29ydHN0aWpnaWtmVFBOS0lfeXh1d3hvcG5yeHNzc2VXXFtbYWl0PjtlVE5KSl5oZl1QQWpgY3qLhIJ/fW5VWTc8Rmk6PDs3Z2ZnY1hbYmlqbWZRQ0U8P0hSUUcwSFVfeW47PEZVWE46ODdFPC4xKjkxOTU0MTQ5HBscHod/BH5/f3+FgAZ/f3+AgICFf5F+hX0Qfn59fX5/gICAf399fX1+fZp8iH0BfKx9gn6Ef4N+hn+EfgF9hX6Hf4J+jX+CfZx/B4CAf35/gICOfwF8hX+FfgZ/f399f3+LgIJ/joCwfwGAxn+EfgF/in6Df59+AX2Pfgd9fX1+fn19tn6Cf5aAln+JgIh/A4CAf4mAAX+JgAF/r4ABf52AAX+GgI9/hoACf4CKf6d+Dnx8fH5+f4B+fn19fn5+iX8Bfot9hHwFfX19fHyMfah8BH19fn6OfwN+fX6EfwF+rH+CgIt/jH6Ef4SAjX8Bfod/h36Hf4J+i3+EgAICBACAi/Tv5oTSrJbD4LTR3IaUlZKFr4LvjYyKh/rWzoqC1ujHsMDGxbyRlJOYgdXZ7IKG7/Kam8uAppZLhozLhsqh083p8OHWr9je2eDY2trW3eLW7d/m7feE7/Dx9oWHio6UkY6OzJuwrbi6w9C9xMjV0M/Sy9XJztPL1s3T083Pz9aA1tPM2NWOqtDR2tLRzdDSyNHG9rr5iIeD+ujj8fz9/f77+OSwt97k4+Dd6YqhpaWk0uahoaOlpaWjpKWknpia9NP8jJSVmKSprK6ur7G0tbW1trS0tLW2tri5ubm6u7vUj4Kkx/+rqevZ4uXt7+rh2da3qrjNv7CC7vHy8PCNto2A8rfDyfyLioeEhIeLjIyL6sC/2P+Pj4yKjIyOhIGIiYiB9ObZxsDEw8XGw7+7wMfGzsbIxcTExce+vbq5wLe5sLuxq6eorq+qoZ6nq7zJz97c4+3S3ebj1NnQ0dPW1+DZ19rb3d7j5M/Z2NXU0s/NxceZ8/L69fb08e3t8O/w7vEQ6OHk4t2Hpaagop6Sj4yE7YSEgIWDhYKDgP359ff26Ojo0P6AhImH/Pft6PPt6Nzl39zh5e7Yw7y3q6KbkI2LkpOOjoeC/v2Ag4eGhYyFhIKHiYiGhYH8goOGjIyFhYeFjZeenZ+hrLS7uLeorLa4ub6+tLe7xs3UyrSqrayur6ikpK6wtb3GwMnGx9fi4N7nkdHxgOPm9oKOj46MjYmKlpiWlZaYkoyMivLd5uPq5ebv7uvs2+r29/ru9fXy9OzljpCTlZKSkZCShd7r6Onr7evz9enyi4qJi4ySlJSMg5KPkZKUlZWWi4OIiI6RkZSVlP6YnJqWmZuen6GFop6foaOhpKGhja2qrKuppaWrnP+LioqPgJOQj5D6io2MhoiIiYaIg7Gws7W1t7a3tpGrrrCxsK+uqKGH/IKBgIWDhoD68Nfk5eHp293a2NXL94SDgYGAgoCA+dy8y83Iw6LEusXJybGoqqeoqq2vr7CuloijpqetrrHA39bNy7S2wbavrK+c/qq7royF0mH+gqGCnKDnxvSogKSkpqijlIDe2vfF6uzj2d7l6ejPmITe7JWnlbCr79msg5Ly4fCq3dSasJ+qta6bnJuZl6KfqrCtrqHDlrO72dbU1cjRytzn8+Hq9eru+u7sio7Y/aKWmJagoqGhoIKHh4qPjtOuqLy9o/2KkJeau7/AwsC7wcG/vLu7x8fFyMXKbMzR1dvQ0dXe6uTh7+7s9fP2+vn3gPbj1Mie8M25nJGs/4P9+YKBguHP14mow9zq+oL9goiKiIT9gPWCiIiAgf/+gvbKj4LN1MTZ29mw7b6fwvyiuq2Vg5upo6uww7KmmOnK9/Pt7NvW3eL3gIBcnp6VUYhtXnuci5+QUl1dWlJuUoxQUFBRnIqPZV6Sooh5houJgWJiYWNVhIOTWGGxrW52l1uBekpvbJNdjmGIkpiYkYyAj5OMkI2QjouPj4WSj5SWnVWbm52fVllcX2NfXl6bZnJxeHp8hXt/hIuHioyHioCIioKLh4uKhYmHi0+KiYKLjGp1h4iMhoWGh4eAh4GhfqxfXlurnZylr7Kysa+snHJ5l5uZl5SbXXBzcnObp3FwcHJycnFxcnJtaGmfgKhgZmZpdHd5ent8fX5/iICAgYKDg4OEhYWbcGd/lcuGgqycn6GoqqahmZiH5oqWintPmZqamptefmOqeYCJrmFgXVxcXmJjYWCggoOWsmdnZGBhYWRdW2JkYlyqnZKBeHx/goOBfXl9goCEf4GBgYCAgHt7eHZ8eHl0fXVycG9zdHJranBzf4iQmpulrZicpaeAlpyVlpSVk5mUk5aamZecnY+fo56dnJiWj5FsnJuioKKgnpqdnp+hoqKenZ6fm192eHJ1cmdkYVmrX11dXV9eX1tdXbi4trmzpqammMVkZ2tpv722tb25t6uwr62xs7yslI2GfHJqYV5cYGFbWlZSoKBRUlZWU1VSUU5RUlBRUU6Alk1LTE9RTkxNSk9XXFxiaXF4f4CCdXqAgH19e3N0eYGIi4N1bXBwcG5qZ2drbHF2fHh7eXuDi4iEiVJ0f3l9iEpQUVFPUE1PWFhWVlZXVlJTUYt8gX+DgoKJi4qKf4aMioyCjo+Ji4WGV1lcXVtbWlpcUnyBf32AgYCKjIaPVFSAUlNUWVxcWE9XVlhYWltaXFVSU1JWWVhaWlubYWRiX2BiZGVnVmlmZmhpZ2hmZlZraGhpamZmbmOMTUxMT1BPT1KOTlBOSkpNTkpMTXBwcnR0dXR0dF1tbnBxcnJvamVUkUtKSE1NTkqQin2EhYOIfX56enl4oFZWU1JRVFJRnYuAc319eXNie3uDg4Vza2xpamtub29taFlUZWhqbWxtdo2Gfn5xc3hwb2tvZ6R4dGpmcqhOuV9zWGZxl3ygb21tbW9rX1GMiI1nf353cnR5e3hpTUJweVlgWG9tl453X2qsoKhykYtjcWZqa2ZUUlFVVFpbYWJeX1yBV1xgbGpnZ2KAaGVqbHFscnhxcHVxcD9EfqFzaGhkbG1tbWtPUlNWWVqNnH16emahY1ldYXyAf4J/fIKCfnt6e4OFgoKAhYaJi46FgoWMlpOTnqCeo6KipaKhVKKUioFjkntyXlZpp1eqp1ZXWJWBhlZsfo+MmlCdVFpbWFWlU51WWlpUVaemVp8pgVdViZKIlpeUd6iFanmZZHdwXlRsfXl8foh8bWOUgJiVj4+JjJOVpFWANlxaUyZDOTJDbWx5VSwxLy0nNStBJCUmKV1fbVBDWF9MRE5PSUU4OTc2KTMyPjI9aGdGiJtGe5Cer6jWaXY7OzY0NzMxMjI0MzMxNDMxNDUvNDM1NTceODk6Oh8fICEjIyEgOicoJigmKi8oKi0uLi0tLS4pKy8pLSsrKykpKyw2KignKCUmJicnKCYoJyUoJCgoTl2UVlVRl4WAkZ+ioaCdmoNTW4SJg314fEtdYmJjfoBfYGBhhF+AYF9dWVh7SoBSWlxfZWdoaGptb3FzdHV1dHV2d3h4eXp5eXp8e49pYHJ7rXBsi35+gIKCgoF9fW+Kf4x8aTRvcHBwcUtwVopnbHKRUVBPTExOUVJRT4Nvbn2XWVpXU1NTVVJRWVpZU5mKfm1laGxxcnBtaWxvbXBsb29xb25uamqAaGhvaWtoc2xoZmdqa2lkYWlpcn2Gj5CeqpCWoKGRl5KUjo+Kko6PkpWVlJiai6Cmo6Ogm5iSlGuTkpmYmJWWlJmdn6KipqCgoKKgYXt+d3h2aWZjWrRkY2JhZWFmYmNiw8XKzMa6u7an5XZ6f3zf3dXW4tzbys/PztXY48qvpJmAjH51aGVgZGVhYVtWpqVUVlpaVltTUU9SUE1NS0mMR0ZISktHREVCR09TU2Ftd3+Hh4d3gYqGf3x4bGxud3p8d2xmYmFhXlZTUldXWVxgW19eXWFkYV1gN0VHQkhQKy8vLy4uLjA1NzU0NDU2NDM0VkVHREdHSE1PUVBHSUtHSECAS01JSUVLNzc5Ozk5ODk6M0RDQD4/QEBKSkZUMTIvMDE0ODk1LjEwMjIxMTAyLi8uKy8xLzExMlU3NzY0NDU2NjguOTY2ODg3ODY4LDY0NTY4NTQ6NTkfHx8gIiIjJUEhIyIeHiAgHh4kOTk6PDw9PD09MTo7PT4/Pz06OC1BISAMICMjJCJDQTxAQkJEhT2AQGQ2NTQzMzU0M2FXRERERUE2UVlfYWBQR0VCREVISkhDPzY2REVFR0dITlhWUlJKSUhCQD5DQXBQRjY6V7CFzUI+RWGJckxwWllYV1hUSD5zc14yPj47Nzc5OjgvIRwxNisuLERCV1hQOz9vXVw7R0MzTDo1NTIpJycrLjUzMzWANTYzRzE3Njk4ODk5ODY3PT86Oj06Ojo2NiEqYp9+ZWNdYmJiY2FERkZIS0txW19wcFt7RkpNU3BzcHNxb3Rzb2tqa3NzbnFucXBzdXVrZ2hveHVzfn58f3t6eXZ3PnZrZV5EZlRRRkNYhUaMh0ZGRm1eWztJVl9WXTFiNTw9Ozg1aDJcNDc3NDRpaDVkSSstUFZQV1VURHFoTD5GLDc4MCs8S0pLSktGNixANDw5Nzg5QEpSXDIBgIV/B4CAf35/f3+FgIN/hICFf41+BX19fX5+hH0BfoSAB39/fX19fn2WfAF9hHyIfQF8r30Ffn5/f3+TfoV/gn6NfwN+fX6efweAgH9+f4CAi38BfIV/hX4Ef39/fYR/ioCFf42A0X+Tfop/AX6Kf4p+hH+efoJ9j34Bfbl+hn+SgJd/ioCLf5yAAX+dgAF/iIABf56AAX+HgI5/iICIf6V+EX19fn58fHx+fX+Af359fX5+iH+Cfo19BXx8fX19hXwFfX18fHyEfad8hH0Bfo5/CH59fn9/f35+qX8BgIV/h34Jf35+f39/fn5+hn8CgH+FgAN/gH+FgAN/f4CLf4V+mX8BgAICBACAjPzDsIfuubyymKCqsene6vf9hvqIjYeWiYWKhvHe1rmY0NDbx72Ulv2B8/mAiZCLjIyZ3YKlk02OmHWGyZyc+M7u3NrItd3f29rh29zU19/c3Ofl5env7/P9gYiHjZSdm5KY0K27uLjD0MPAyNHX3Nnc3d7Rzs7a4dzV2tfcz9pd1eHh496XsuHOy87cztnRzdLJw8fEi8j+gv3v5Nrn+4KCgPnk3ubo6Obk5OLk/5Wj1emfnZ2en5+bn6CgmZSVlMiHlZeYnKitsbK0tbS1tra5ubm7vL2+wMDAv7++hLyAvfWV7eaIj7ig+/Ht8vj8+9GtwNXNuvzo6u3u7ou1hvrBxr23wuuEjIiIg//burW0rLOwutr8hoyJhYWNioqKiYaGhoT14s++v77DvLi7wc3O0MfIzcTCu7e9vr2xs6+yuLq2tq6trLS0tK6wp6KZl5+urLvByMfN0uHf1dne4dOA0dDY3+nl0ODV18/H0tLPzpWGgIH9hPX3gICA+vbz6e7x7uHfiKKhn56YmIyPifKNhoeJhoaHhoD19eXe5OTc2Njf7oqNioH/goGCgO7Z5OPXw7u5qJD3jZKQjYyLioyPk5aOjY2UkI6IhoaDhoWLjIuNi4qIh4T/9vj1gP6EgoSAkKu0xr7AuLKytLWxq62wsa60v8rQzMrIzNTQvrOwra2vt73EwMHBvsHAyNni5pPA7fHz6+7y9e75g4iHlJmam5mYmZiYmJHb5ufl6+ft9fby9OHt8Pr7gPOAgfbv5YKEh4yVk5WUk4ng7+/o6vX58/v16oyPjY+MjZCXmO/z8vUS+4GFh4qP+pKNjpCUl5iZmISbhJ2AnKKgn4Wjo6KjpaepqamRsbSyra6vrq+v9o6Njo+SkpOTjfuQkJKUk5mZmZijt7e3trW2trSvlK2urayqraymooH9/YKEhYOD/fr63ejj5+Xh3dvW1M/ogYD//ICA///877S51K3Nw8fPzc/YwKGipqOpnZL94tTV44yorrPB2MuAxr+2srCppqOco6LsspTVn/mD4mmD/byFo7Ln+qeioaGjnYHj5uD9vdfg29DR2u7m1tGy+/jA5rCmm+O1lYWYkLbp9bKP6J24gO+UuMrd1cSvqra3t72tqJ/Dydzd2tvM2tLm7O7f8/Hp8er69ouHruqq3+Pu8vabnZ6doJyfn56A076ntralhZOepay3vby4u7e5ucPKycfMzbWwsbKzstPl9fj59Pzw3dDFsKaglIP5zrCepMP19d/MzdXSxNTN3oylus/s+omKh5KWl5eVkZKSkIH89/73/IOB/4KD8+/04a2Tyc20xLekwtSFrrmlo7Ozr6+ytrGhj4L1lpC/1OEJ6+7m/IiMoZ6VgFqTg3FXmXZ5cmFhdIquk42TmFGVVVRMVU9OVFagmJaBbZOSl4mCY2CfUpynXWJjXVpfeqFcgnlMdXRYYIphYLSRno+NhoCOkI+OkY+MioqOj42QkZKYnp+fpFNZW15hZmReY6FweHh7gYeBf4WLkZSRj5GSiIiHj5SPi46Kj4SLS4iVkpCPbnqShYGFkIiNioWKg39/gF+MtFuwppmTna5bW1mun5meoJ+cmZiVlq1mcZynbm5ub29ubG5ub2plZmV/WmZoaW12eXx9foR/gICCg4OEhYaHiIiJiIeHhoaHh4e6dr+taHGQeLWtrLK3t7Sb7JCclIOalZaXl5hcfV6ufoN+eoKkXGJfX1uxlXt5eXJ4dn+Xr11iX15iaWVlZGRiYWBeqJiLfX58gHx6en+FhYWChYiAf3p4e3t7c3Z1eHx9ent2dXR5eXh2dm9rgGRmaXNyf4WLipCWpaOWl5yekpKPlJminpWmnp6Zk5mZlphnWFJUqFedoVRVVqWgoZyeoJ+TmmB1cW5uaWtfZFypY19gY2BjZWJbsK+inJqZlpWSmrRsbW1lyGdnZ2S3pbGwp5aMh3ZhoFxgXl5cW1tcXl5gW1paXVtZVlVWVFZWgFhYVldVVFBPTZWQko9KkUtHSFRqdIWBhX97e31/enJwdXl0dn2Hh4SBfoKGhXp1cW1tbXF0e3h4eHZ3eXyCiIhUaYCBgnt9gYaDjEpMTVdZW1tZWFlYWFlWeIKAgISBh4yPjpGDh4iOi0mFSUuOiodNTlJVXFtdXVxWgYWBfn+HUIuHjYqIU1dWV1RVWV9gkYyMjJNNUFFSV5hbWFhYWl5eX15SYmRlZGNiZ2dmVGhpaGdnaGtra1ltb2xna29ubm+ITU1NTk9OUVNRlFVUVlhZhF2AaHV1dXR0dHNycV5tbW1ubm9uaWZPko5KTExLS5GTl4SGg4WDgH99eXp4lFNSoKBSUqOioJpwanhlhoKIjImJi35nZmhma2RdmoZ9foNUaWxud4aAfndxcHBqaGZhaGuahWeCXsNusFdft4ZdbHuXoG9ra2pqZ1OQko6WZXN2cm6Ab3V+dm5qWn+BcIFlamSMdG9pfW2IubWBZqNzflimYm6BhXdzYV9lZGJmY2heZGNsbGtqZWxobnFybXd2b3Fvd3VAQWSmb46Ol5mbamtraWlmaGhnjKR+dXVoVmxmaW95f355e3Z5eICGhYSHiXNtbm9vbomVoKGhoK2llY2FeG9da2JVoIVwY2qBramYhYuPjoWHfolZaHeFmKFXV1ReYWJiYFxeXl5Qm56koaVVUqFVVpuaoJZuX4SGd4B6dYqIUmx2Z2ZwcG9ydXd0Z1lTnmplhZKUmp2XpVlaZmVggDNIVj4sTDk7ODE0X3KBWUVAQSNBJyQeIiEjKjFnanFjT2JbXExHOTZLJkdWNjw3LSk+jaJKfpCht62BbHU7Kz41ODIyMDEzNTQ0NjQyMDI1MzU2MjU3OTk7QB8fHyEjJSQfIjknKSckKSwnJisvMTIvMDM0Li4tLzMvKzAtLy0sMisvLiwqJSYtKCYnKygoJyYpJyYqKTdvoFOglod7h5pSUlCZkIyPjoyJh4R6d4xXYH6Chl4jXV5dXlxaWldgSllcXWBoamtsbnBydHR1dnh4eXt7fH19fn2GfoB/rm6zmVlgdl+QjIyOkJCQfYaCkopzZ2xtbW1uSG5TkGpsa2hvik1UUE9Mk3tpaWhiZ2ZtgZZQU1FSWV9cXVxbWVhXVZWGeW1ubnJsamptcnN2c3Z4cG5qaW1ubWVpamxwcnBxbW1rcXJybW5nZF1cYGtpd36Eg4qSpKCPj5eajICMiY6TnZmSqKCinJacmpiaZFNMUKNTlZlRU1SjoaCdoaSglJtheHRxcmpsXmRcsGljZGdna25sZL+7qaCgoZ+gnKPNfoOCd+57e3x32cHQ0cWuoJmAZaZkaWZjYmFhZGVkZl5eXmFgXlpXWFVZWFpaV1dTUEpJR4mFhoBEhUM/PoBKZnSIio6HgoSGhn10cnZ7c3J3fnx4dHB2entua2VfXVteYGRgX19eYV5fYmRjOD5JSkpDRUhMTVQsLS43ODk5NzY3NzY3NERJRkZKR0xQVFRWS0hIS0cmQicqT0pMLzAzNTs5Ojo5NkhGQj9ARklESkdPMTMzMzEyNDo7U0tKSB1OKiorLC5YNTExMDAzNDUzLjc3ODc2NTg4OC83NoQ3Jjk5Oi03ODUyNzo5Ojs6HyAfHyEgIiQkQSgnKSwrLjAvMTc9PD09hTyAMjo7PDw9Pj06OSlAPyEiIyIiRUlRRkNAQkE+Pz48PD1bNDNlZDM0Z2ZlYkM5PzdcWmFfXmBiWUVDRkRIQjpeT0ZGTTZGSUtQVlRPSkdJSEM+PDhARGZZT1AwcVO9mWyBRktnnH1yXFpXVVVRQnh5dmIwOTs5NTU6PjkzMSg4ODaAOys6OlFOY2N2VmeSiFY5VUJKOmY7Pz0+OT03Nzk5ODc3Pjc7Nzg5OTs5OTk6Ojw6PDw5PDo4OiEmTc5ofnqBg4hiY2FfXlpaWVhyYFlrbFxETlhcYWxxb2ppZWhnbnNxb3NyXlhZWVpZcHqDgoKCjoR2cWpeVlVNRYJsXlVcc5VTlIJ0dnd0amVcXz5IUVllaTc1Mz0/Pz8+Ozs8PDFcXGBdYDMxXzM0W1lgWTs2SkhAREZRXUQqNDk0LjU1Njk+Pzw0KihPPzRJS0dQWFxiMzIzNTYGgH5+f4B/hIACf36GfwSAf39/hoCFf4d+BH1+fX2FfgN9fX6EgAd/f359fX5+m3yJfQF8sX0Efn5+f4Z+g3+MfgR/f35+jn8BfqJ/B4B/fn+AgICIfwF8hH+GfgR/f399hn+FgIt/joDQfwd+f35+f39/iX6KfwF+iX+LfoR/AX6Ef4p+AX2gfoR9An59sX6Lf46AkH8HgH+AgH9/f4qAi3+JgIV/hYABf6eAAX+JgAF/noCCf4WAj38GgIB/f4CAiH+PfoV9kn4RfX1+fn57fHx+fn6Af359fX6Hf4N+jX0FfHx9fX2EfA57e3t8fHt7fH19fXx8e6N8hH2Gfol/A359foR/AX6nf5F+hn+NgIV/BYCAf4CAjH+Cfo9/AX6Jf4WAAgIEAIC33LOMmOSnr6OP/rets429t97w+Pnc2YGOkZWUk5SRiPzev6GO4JmVgIiKgYClxrK+o/iFoo1Kj6iOicSW0qGBzN7o4tW16d/Y2N/U09Tc3eni5uLZ6vH6+vWBh4qQkZycoKjWurzIzczj1NHU2ujm4+br4NLV3djv6ubr7dbb1V/i3Ovl2pW04c7LycvX1NLV2snFwr3FyeOn5Pz48ere4fCA/vXu8vT49/X28O3q5vW16qGio6OjoJ2eo6agnZ6fnZajoJ6hrLO1trW0tba3t7i5ubq7u7q9vr++vr6/voS8gL7EionXwruhxqKC9vH60KnR7uPK9+Lk5OTmhr2M+L7Aw8fGw8Xd9YTevsC8vLW4ur3AwcrF2eyFiY+QiY2Rjo+QioaHiIX24MfAwcPLzdHLwsTEv8PGuru9qbO0tbOzrrKyu761srKztLW5ta+0uLCrpKexrKajoqaps7e8x83MgNHT1tPa3Nrc0s7LxsS9gYSA8fLy8vH4/f72g/jz6PTy9+77nJySlY6Kh4iH8o6RiI+OjI6G9/Dw7ujq6/Du5+zihIOA/OfVx7igmZSJ/YD6g4X6/feFlZCWlJGMkZWPlJKUk5iVkpCPjIn6hI2OkIyIhoWHjYaD+YCBhp6vydPegODVt7q4t7eztLOvr6uIk8TGxszM0czGxMnY1L20t8LExcnFyMvOx7+/x5DM6ufv7uzs7vz/goGBjJaZmpycnZqYmJqF3OTl5Ofx7vnz7e7q9vL9/4CCgYKB/vKNh4aHi4SGh4aG4PHv8vX8/P74/+mOkI+Pj4uNkZeJ5P35+fn6gID8//39kZSUlpiWmZaW+4yQlJeSlpacm4WmpaapqKqtsLCPpqOloqCfnp6e/JCOko+UlJSVk4+wrq+usbKysqyWubi4tLGxtLKyn6SopaitrKimpaP3/4KLlJeUl5eTidvn5ePm497e3+DS24SA//6AgID/gPjGmMbHxsnGztTdgOXTrJOD5NrV1dXa2+Pr7pLE49K4ubW4vLe1r5mNnZjqx9LY0pH4iOxs//y0jbbGyY2ioqGhkfXv8uiMvtrh29DW4ejn08nEuZfNgdzlr9qT+4aHirCZ1pecrJP5qZTn0r/H1eqMjo2KiPm/srDG19rh3dfM5N3v+PDm+fHs7vj+gPWKgZW/0s7O2Oj/lJ2uxdXS5vX9t8uRioyN9fD1goiOl5CDhYmNjJCemJeWk/bn2dPOxNHl5trOwbuxrrzM2cjM3+bg1d/Zz7a7uczV+o6rwuWGjZCWmI6Gh4iFh4yWlZOSlJOVlo+Mh4uB/4aG//Dq6+7w5Kmcvb+c/LjcjsvLHbiTlbC6v728wsC5p6OllKqf0/qPkJGRjYuku7+9gHCGbllhlGpwZVWcdGl3bY57hI+TlIOLTFNWXFxcXV1Zq5yFdWmiZmBRYWBYVmx+cHx6s16Bd0t1gGhih11+cV2TkpKRi3yTj46OkYqKjJGQl5SXlo+ZnaKlolVaW15eZWRnbqF4eoSIh5KHiImOmZaUkpeSiIyPj52YlpqZiYyIW5GMl5CLbXuQhISChY2JiIqNgoGAe4GEmXOgrqunoJSVpFmyraSoqquqp6WgnpqZp4Snb3BxcHBubW1xdG9rbGxqZXBubXB4fX9/fn1+f3+BgoODhIWFhYaHh4eEiGmHiIeHiI5sbauNkX+Xd1+0rrSZ65mro5CVkZOTkpNZgWGwfH+ChoSEhJSpXZd/gH5/eXp6fX+BiIOUq2NmampkZmpoaWlkYGBgXaqXhICBgYeKjIaAg4J9gIJ4d3pvdnd6eHl1d3d+f3iFdoB5dnF1enVxbG13c29ubnN0e3+Ci5COj5GTkJ6ioKCblpOQkIxWWFWjop+fnaKmqaFVqKSdp6Sopa9qaWBkXltYWlqqZWliamZlaGCyrKqmnZmam5+bn6FoaGTDtqKUhnRoYlaYT5pRU5eWk1NiYGViXltdX1peXF1dYWFgXlxYVoCfUlhYW1hTUE9PUk1Mj0lJTmFviJCVmY56fn1+f3t9e3dzb1Vef4GAg4OEgn17fYaFdnN1eXl5fXp+gYSAdXJ2U3B9e4CAfnt8iYxGRklTWFpbXF1dW1hYWk1zeYCBgYyHkIyKjYmNiZGQSElISkmSkFVNS0xRTlBSU1OAhoOGhweJio6JjoZVhFaAU1VaXlWCkIuLjI5JkJKRoFxdXl5fXWBdXJlVWFtdWVxeYmFWa2pra2lrbXBwV2RjY2BgYWFgX45NTE9NUFBQU1NXbmttbHFycXFuYHh2dnNwcHJwcWVmaWZobG1ramlolJFMVFtdXF9gXFWAhYSEhYOAgICBeYpVUqOgUVFSolOAn3pbgIODhYGKjZSbjW9bT4eBfX9+fXx+godbf5KEc3Vwdnhzcm5hWGdkm5aZmX9XtXO8WbeygmV4holgbGtramCinJ2XVGNye3Zvcnh5dm1nY19Qd0h9jW2FYsVoaXCJd5tqfoVrrW5gjn55gYeRVFRTU06LaWVlZ2pqa21uaG+Aa3B0c21zcnBwcnd1Pz9Yj5GLipGZqWRseIeSkJ2kqX2rcFtcXKK5o1VZXmReU1VZXFtfamdnZWSikYuGgXuJlZiMhX15cW19jJiKkZ+inJabmY93eXiChJ9YZnOHUFddYmNbUVJST1FXYWJhXV5eYWJdW1tfVaVYV5yTj5GXnJYobmZ+f2uvfoxdiYl6Zl9wdHZ2eX9/eWhmbmZ5apOoX15eXltaZXF0coAxQjgsMUs1ODApTzw0UlVtRTk8Pz84PSAjJiwuLzE0NXFvZVhNcDs0Kzw/NiotMi5TlbRNiZeiuMCUc3Q8NyghNTY5NDQvOTU0MzYyMjI0MzY2ODcyNzk9Pj0gISAhISQjIyU9KykqKysyLSwsLzMxLzA1MisrMi00LiwwMCsuK1wtKS8qKCMnKyYmJCYrJykoKyYmKCUoKUBUjqGblIx8f5BRoZuWmJiXlpOSj4h/eYFjgV5fX2BgXl1cXmFgXl9eXFdiYWBiaW1tbm5wcnR1dnh5eHl6e3t8fX5/gIaBgICAhmZln3h7aXpdSY+LkHiGgJKOfGZqa2tpakVwVY1naGxycG9we4hLfmxubG5naGlsbW5zcX+YWl5hYVtcYWBhYVxYV1dTmYl1b3Jyd3l8d3B0c21ucGhobGNqam1rbGlraXF2bmtsbG1tcGxma3FtaWNkbmtnZ2ZtbnR4eoWLgIqHio2KnaOgoJuXlZGUkVRUUpycmJeWm6GinFeoo5yopauor2toXWZdWldZWrJtcGh1b250ab+5tayfnZ6hpqGksHp5duHRuaiVe2xkV5hOm1FUmJWRV2lkamlkYWNkXmJfYGFkY2ViYV1Zm1FZWVpXUUxKSUxHRoJEREhdboiUgJmeloSHhoeJg4N+enVtUFx+fXp5d3h2cG1udnRqZmNmZWRnZGZoaGRcWVo3R0pIS0lHRUVPUigoLDU4Ojo7Ojs4NjY4Lj1ASUlJUk5WU1FTT05LUE0mJiUnJ1FWNC4rLDEwMTI0NEhGREVGRUZLR0lIMjIzNDQxMjY6NENLREJDgEQiQ0VGXTU2NTQ0MjUzMlMtLjAyMDEyNjQtOTg5OTg4OT09LTEyMS8wMTEwMD8eHiAfISEhIyQsOTc4OT0+PT48M0E/QDw6Ojw7PDY3ODc4Ozw7Ojo6S0clKzEzMjU2NTFDQkJCQ0E/P0BBP1U3NWhlNDQ0ZzRmSjRaX2BgWF9igGpsZE8+MlFKR0pLSklKTVE6U1xUSktIS0xLSUU7NkFAZ2qHi0otaE/VntN+Rk9son9LWVdWVU6HgoJ8OS84Ojg0NTs7ODMwLSwlPCA3RjhKRLtmZmZ9V2xZd3ZOXDcvTEVCP0RMLi8wLStNOjs6Nzo6PTo5ODo5OTo5Ojw8Ojo8gDw+IyZRuZ2LiY+UomFpcoGIho+UlGNmUFVTU36GkU1RVFlUSEhMUVJUXVpaWlmNe3h0bmlyg4WAenRwamh4ho+DipiYj4qOiX1lY1xiX3E8PkRRMTU+QUI5Li4tLDA4QEA+Ojs8Pj87OTg6MmI1M1dQTFFVW1g7NkNDQXdURy5HHkxFPTE3Nzg4PEREPzIxOzpOPV1lODY2NTIwLC0vLwaAf3+AgH+EgAR/f39+iH8BfomAhX+LfgN9fX6EgAl/f359fX5+fX2afIl9AXy0fYl+AX+QfrN/BYCAf35/hICEfwF8hH+GfgR/f399iX8BgI9/j4DLf4l+AX+Ifol/AX6If4x+g3+Jfgh9fn1+fn19fZV+AX2MfgF9sn6Lf4+AkH+FgIJ/ioCLf4qAhn8BgIR/iYABf52AAX+ogIJ/iYCMfwyAgH9/gICAf4B/f3+Nfop9kH6CfYR+C3t8fH59foB/fn19hn+Ffo99An59hHwBeoR7CXx8fHt7fHx9fYZ8hX2YfIp9in4IfX5/f39+fX6Qf6F+hH+ZgAN/gICMf4N+ln+KgAICBACAyrahm5HTmZHXiMGogPawmuTC1f6B/LWHjpWUlJOQkZOUjYP/7NXYyqSLjMDR2J6evZGHp4lJkLmVj9eS9fikhtPs+PTetOXp3tbby8rP1OXm6e3u8fiAgfyPiYmOk5SdnKWp0LG7wszN39TV2dzf2Obh2t7T197j5u356Ojn3dqA2+Xr4+yfuujSztbl5ufr5ubj293d3NPZ2t2a3YOEgPfr5e6CgoOHhYWDgvv29fHsmM+KnqanpqKfoaWnoJ2dnp+eopycn6eqra2wsLCxsrS2t7i5uru6u7u9vb2/vr68vb6/wcPDwtyZibGw4a7InIPOp7rc59z24uPj5OL8qIGA6sLLyMrS1dzf/ofjvb65v8XNwMHFx8XJx8TI4PWIi4mIjYiJiYmKiYqKiomB8eHAxsO/v8C/xMTHvLqkrqaoq6Sns7iup7Kvqaytsq6zs6qvsbmvra2qo6Sdm6CamJCQkpWRk4iNhpGapKyts7G6u6yHhf399/Lx8P6H/4KC/fyA8v/9+/z+oZ+bm5mcmJeciJuWlZSPj4mFhYDz7uvn4ODZ2c+2t56SkYyQjIuHjIyNkomJjI+KhoOIjJOZn56XlZidl5SRjp6hnJWSlJOWiI2Pj4+IkIWDhYKHmqa/ztHQ2Nra49bFvre6ube2ppf5zdPY96PEx9Hc29vZzLeW38mAgqnBv7u/wsq7u8uRz+nw8PLx7evv9PyB+YSVmJiYmpqYmZmakezm2uTl6ur19fHs5IWLhf74goOFhIOAgpCPiIaJjI2NjInd6/H39vuDgoH66oSNkZKPjImLjpPe/fyA/ICBgf+A55SSlJmclpSVlI7k8YKFg/79+/+Ci6imqauAqKuxsrGBio2Sl5eXmJeX/pGNio6RlZWUk4awsK6vrausrq+lo7e0tLOysbKwsJOrrquwsa2pqKSbkqSkpaCbmpaVlIvW4+Pl5ODd5OHRztyFgoL+/IH7/P60j7bKwcbMxcrSzNTRvPjU0NPe3N/b09jj44qgtLW3ubu+vriulomAoIHtxYaK+dyM6oHwaoDytJ+/28mMoKGWh/32+fSQvtjo29Td2d7m1M3Mt7mCj9uF/4ySztbyk5eDmfaalZ7O666n4bOcj4Hy+vSBjZX11rzYzN7X3N3R5d74goHh/PLr+fn794mAs8L7/fr8+PGFiImQiZKdpKqYyMSGhYKfxJpBoqOiqKSlsrq8sbm5sbuyrbq5srG/vsPY7PP5//3l2dS9tq2axOjzh5entcWCjo+MjImOkJCepKWkop6Ul5uZoJyEmDubnJqXmJaRmZmclIj/goWEhIDmm52a0aiAlM24y9CDo+/FwsXK1NW/wMPF1/f3uo+vr6edm6Gkq7TBxoB+cmRhW41gWIZVdmRKjmherY+IlUyUdVJWW1paWllbXFxYU6ehmJqSdVtXdYCFYmeHZ2KJdUt1jGxpkV6Rp3ZelZqcm5N7lJWQjZCFhYmJlZaanqCfoVNUpV1aXF5gY2tnbG+ednl/hoaPiYyPkJGOlpCOkYqLi5OWmZ6Wl5iMi1qLlJSSmXWAl4eGi5GQkZaTkZGLjoqMh42Mi2eZW1tXp52Yo1tbWl1cXFpZrKeloZxqjl5tcnNycG5vc3Rua2tsbG1va2xvdXh6eXp7e3x+f4CBgoOEhYWFhoeEiUWIh4mKiouNjIqheG6Me66HmXBemeOLoKicl5GTk5KSpnRaqX+FhYeNkJOVrl6cfoB7foGIf4CEhICEhYSInK5kZmNjZWGFYoBjY2JiW6mcgISDgIGDgoOChHp6a3FrbXBqa3N4c214dW9wb3Rxd3dwc3R9dXRzcm9uaWdqZGNgX2FkYWJaXlZfaHB2en+Ah4l9W1qpp6Kfn6CoWKdXV6yroqytrLCta2poaGhraGlvYXBtbW1oZ2RgXlqopaKemJOPj4l3eWNYVoBVWFVWVVhXWFtVVFVaVlRSVVpdY2lpZWFgZF9eXVtlZ2RdWVxbXVZXV1pZUlhQTE5NT1xrfouOjZSXlpyOhX56gYF/f3Bim32AhZVngYKIjoyMin9tW39xUGl4eXZ4eX50dXtTc4GGhoOAfn2AhItHi01XWltbXFxZWVpbVod8coB9gIODjIyMh4ROUk6VkUtMTEtKSU1WVE1LT1NTVFRTgIKEiIaIR0hIjINPVFdYV1RQUldchpGQSY9JSkeRSIhfXF1hY1xaWllUhIhJS0mMi4mOSlhsa25ua21yc3FLTU9SV1dWVlRVkE9NS0xNUFJRU09vbm5ubWtucHFqZ3NxcYBxb29wcHBcam1rcHJwbGtpY19ramllYWBeXV1VfIKFhoKAf4F/d3iNVVJRn6BSoKKjcVV4iIOBhYGFjYmNi3+afHh7gn+Afnt+gIJRYnJ1dnZ4eXdxal9WZ1OcmWNls4FUs264U16vhm59m5Ffa2tjWaehpKNYZHJ6c3B4dnZ4boBqaV9iTFB7T51WX52jv250anvCd2t8rcl7bo5xYlxSmJ6dUldenHtqdGtyb3FvaXVzeDw8b3ZybnJzd3ZAP3CMubi1r6ywYGJlaWJpcnZ6a6qqYl5bb5xudHNzeXN2gYuJgIaFgYaCfoWIg4OJg4WZpqqts6+flZF/dm9kgJaWU1hbZG14VFxbW11ZWV1eaW5vbmxoXmBiY2lnZGRkY2ZoZmNkYmRoZ2hkW6ZVV1dYVJVkam2UbVJgin+JjFNlmHx5fYKLkIOIi46WqqR8XG9vamVkaGVnbXZ7gDYxLiwrRy0sTSo8Lh06LDGHZUU6Hz4zJScpKSkqKy0uLy4vaHFvZWtUNSgsLTAlUrViVZuio7i/mnp+QEI/KSM6PD41NS44Njc0NzIxNDQ4NTU7PT06HyFAJSMjISIjJiMkJjwpJycqKSwrLS4uLi8xLy8vKikuMTEwMC8vLSopeCksLywuJCkuJyYnKy4vMC0uLyssLCsmKigvP3tQUE6RhYCMUlNSU1FQTk2YlZGNh1NpTVtgYmFgXl5hYmFgYGFiYmJgYGJoaWtrbW9xdHR1d3h4eXp8fHx9f4CBgYGCgoOCgoSFhIKXcmiAZZBxfFdId4p6jZKBaIVrgIJnT4dscXFzeXh6eo9Qhmtuamttcm5ucXBtdHZ1doybWVxZWV1YWVlYWVpaWllYUZiLb3JzcnF0cnNxc2pqX2VgYGJdX2hrZ2NuamRlZmpobm9mamx1bWxsbWpnYWFhXFtZWFldW11VVk1WYmpyd399h4t8VVain5qZmJihVaFVgFWrq6Sxrqy0q2ppZ2hobGpqb2N2dnd3cnFta2Verqimo5yclpWOeHZgV1FQU1JUU1hZWV1UVFZdV1JRVV9iaG9vamVla2NgYF5oaWhiXmBfXlNVVlhWT1VJRkhESlpqgZGSkJWYl56PjIiFiYeFgnJilnF0eI9mfHh+hYR/em9cRUteVEJYZWVjYWBkWVldOUlRVFBOS0lISUpPKlQyOTs7PD08ODg5OjdQRDxFSUpKUVFRT08wMzBYUyoqKSgnJy41My0rL4QzVTJIREZIRkYlJidLRi0xNDQzMS4vNDlKS0slRyYlIUUjRTk2NTg5MzExMC1CQCIiIT49Oz4hMDs7PTw5Oj0+PSckJSUnJyYlIyU+Hx4eHh8hIiEjJTyEOoA5Oz0+PDc+PDw7Ojk6OzwyOTw6PT8+PDs6Nzc/Pj48Nzc1NjczQUBERUJAQEA+PEJcOTY1Z2g1ZmhpRzVUY2BeXVtgZmRoZ11mSUVHT05PS0ZFS00tPkxMTU1OTUxJRDo1QjJpa1lbnEoqa0rXkm2KSFNus5dOVlRPS4yHhYA5LoA4Ozc1OTg3ODQyMS0uJiU2JkUpOpSgs2hqZnqoYF93tb9KPExANjAqUlhaMDEzYEs/PDk8Ojo7Ojw5PR4fOz87OTs+QD8iJG+VzcO7vr64ZWdscmpxd3x+Wl95bmxqWH54fnt7hH5/jZaViZGPiYyHhI+QioiPioiYqauus6uckWGJc2hiWm5+cjtAQ0dPOT49PT45ODs9RkpKSUdEOTo9QEZDP0BAP0BDQj8+PTw/PT48N2AwMjMzMVIzPkZkPywvS0VJTSxBTjs4PkJKUE5UWFxgamJBLjQ1Nzc2MysqLDI1hYASf4CAf4B/f4B/f35+f39/gH9/jICDf4l+A319f4SACn9/fn19fn59fX2WfAN9fXyKfQF8tn0Ffn5/f3+Efoh/h362fwWAgH9+f4SAAn98hH+HfgN/f32JfwGAkn+QgMZ/h34Ef35/f4h+lH/UfoV9i36CfYt+jH8CgH+MgIx/BYCAgH9/kYCGfwWAgIB/f4qAC39/f4B/gICAf4B/ioAFf3+AgICEf5WAAX+0gIx/BoCAgH9/gIR/jX6MfY9+En19f39+fn57fHx+fn6Af359fYV/hX6OfQp+fn19fH18enp6hXsGfHx7enp8hn0GfHx8fX19jXyCfYh8in2KfoJ9hH4Bfah+hX+lgAF/hYCEf4J+h3+Cfo5/jIACAgQAgLajnJmV15bdg4XcgYOC+Om/5p/d5YnEiYqIg4qWop+WlY+E7biK7ouVpqXtvdew0qyPt4pKls6al9OTiPn8r4Xa0ODz4rTr5t/g3d7f1eXn6e3x7+vs+fiBiYiMkZaZk5ehqeK9x9fV39/c2tvp3+7w6e/s7/f19uzz9vXp8PSAgPn18Pj7pMXy7/by9/L29/j19uvz9e/39Pf25un/v/2FhIH17e//iIeHhYWEg//28pvD6eqFmqKhnp+kpp6am5yepKObmpujqKusrq+wsrS2ubm5ur27ury/wMDBwcLCw8TFxsXFxsTExMb+nIWJxoS5rLOHgvHM793d29nZ7rXxgOu2x8bByd2JmpuYl5aE7MrNzc7L0tHTzs3Lw8C6ttLvhYeJiIiGhYaHjoiJioa+n5Ogr7a9v8XGy8KvtLeysLa9vru4saqtrq6yrLG0rqudpayvramnqaihl5STmJGBj5CSlpuWkO77gP+DhIeEgIWB79zV2eDm6/n98vT26OPrgN3h2uHi5oqIg4b/gf2A/fLHwLy6uL2/s722q6yusre1sbKpp6OWnIqRkJSJhIuRkYiD/IuZlZOMjoqbn6WgnZuaoqKanZ6Yl5Sbm5aUl5CKj5GKh4GJnLTN1s7BxtvYz87NzMfGuLetpZ+S5dzXzN3e3+Xagp+/wca/pffJz8rLgNbI7JS1vMK+x5nR5Onz9vTz8vT4/vTu94yRlJaVmJqYlZOXh+Xe5+Tu6eTs8fjv9I6bm5mYlouHgoL8h5KPkYmKi42Oj47o7vP6/YGDhYOC+fSNi46JiYeIjJOB6/v/gP79gICA/vaXlZqWlpaTlpn8+/mCgP789vf5/ZOrqq2sgKurrrCwho6Ok5manZ2bmv+TkI6OkZOVk5OPn7KurKqoqampqpS2sbCxr6ihnZqJkamqra+vr6uppJWSnaGjoJuZlpWYjdfg3trk8PuDiIyM64OEgoOEgf2ziICCncbKz87Kys3U3urhs+zZ1dLY0+Lj5/r6gIObsLK1tLa9oov3gJrlg7nu/uys44jjgetk/MawvsP2rfeRi4uBgYLujsLa49zd39Pb4cfCtaiwhquCkIeoy6WNmZydl5OMkeWZjfO4xfmt6fO3q4mDjbLEmoyF+vLp6ePn1+bn9P+A7erh5f39gPaLipnJya6ro5qRgvnk3dfd74CPmdK75e/6qrXugIGKjJKWl6Kjn5WYoI2UlpebloyQlKS43/2Lkpqloai73PqGjZKThoeG+fT7hoqKi5aTj4yIjZKTlZycm5WSlJKNjY+dnZ+lqKWim42WhoH+k5KMiIaB8cDutNOYueOux7jT28qj3NnDwMjP3oGBgP7/gYPKqbG9ys7IqJyZnqq3gHBlYF5ajFqDV1eCSktKjIJujHKgklB9UlJSTlRbYWBcXFhTmn5fn1hcbHOlgYl6mnppk3tLeJdxZ49fT6avf2CXiJGemIGflpKTlJWUjJKTmJyfnJeWoKRUWlpbXWFjYWNobqp9gYqKkpKPkJGYkZmelpuYlpuZn5ubnaGYnqFUgKKdl5yedYSalZ+dnpaXnZybnpacnZmcm56ckJSpgK5dW1ilnaKwXV1eXFtaWa2opnCFmZpaanBvbm5yc2xpa2ttcXBramtvdHd4eXp8fX+AgoODhIaFhoaHiYqLiouLjI6Oj4+PjoyMjI2/emhojmmRh+dmX7GRlI2OjYqLnYKogKR2goSBiJZgbW9ta2teoYWGh4eHjYuMioqIg396epKrYGJkYmJfXmBhZ2JiYl+BYlxmcnl/gISFiYJzeXp2dHh9fXt6dG91d3R2c3h5dHFqbnN1c3FwcXNtZWJgYV5SXV1fY2hkX5icUZ9SU1NSTlJSloyIjJCUmaGmm5uhl5WbgJKXkpeZnF1eW1ytWK9ZraSEgHp8eXl6cHZvaGxucXh2cnFpaWlcXlJYV1tUUVRYWFNQmFZgXlxXWlZmaW1qZ2NfY2ZiZGRfX1xjY2JfYFtVWFlUUk5TYnWOkImAhJKSjoqIhoSCenlxb2pdioeFfoiFhYeBT2N8foF8Z5VtcnN0gHpyj151dnp4fFp2f4GJioeEgYSHiYKBkFRWWFpZW1xbWFdaUIJ5fHqBfn2EiY6KklZeXVxcWlJPTEuPT1ZUVU1PUVNVVlaGhIaJikdISkpJi5FVU1ZTUlFRU1pPhY6QR5CPR0dIk5pgXV9cXVxYWl2Vk5BKSI6MhoWGiV5vb3FvEWxsbm9wUFFRU1hYWVlWV5JOhEyATlFRU1Jkcm9tamlqamttXnRwb3BuaWZiYFRbaWtvcXBwbGtnXl1kZGZlYmBgYWFbfIB/fIWPm1JXWlyXVFJTU1ZTpHBPTE1mhoSGh4eJjJCVnpp2jYB+e357hIOEjYtJTWJxcHJxcnZlWJlhk1aKq7Wlb4ZRs2mzVLWQfYeDp3yAp2BaW1VVV55UZHF4dHN1b3FzZWBaU1xOYkpVUWN+dVpSYmxwbmd2r3xz2qCwuXSWm3JoVlJWbndeV0+DfXl5dHNwd3Z6ej12b2tsdXk+d0FFaYiDc25rY15Wp5yWk5ejWGBnr52eqLR1iZ9VXF5jaGlzdXFpam1fYmRjZF9ZWVxrY3KPo1lcYGZkanmTqFpeYWJTVFSWjpNNT1BWY2BdWVRXWltcZ2dlX1lYVlRUWGVoaW5wbWplX2NXVKpkY19ZVVGUfKZ7gl97nXOFfIuVh2qRk4mGiY6WVlZWq6xYWYhqcXZ9f3xoX11haXNHLy0sLSlEKD40PzQdHBwzLStDTmpGIDcjJCYkJigqKi0vLixYVkRWLS06TGlEP2LSb2Gmr6i/yJd7fUIiP0IrIDozNjg6MzuEN4A6OTY+Pjs6Pz04NkFAICMiISEjJCIhIyQ8KywvLi4tLi4vNS4zMDE0MS4xMDEvMC4wLSwuGC8uLS0rJiouKy8vMDIwLjAvMS0yMi0uLywtKStMXZRQT02RhYiaVVRTUlFQT52Yl19lfn1LW2BgX2BiYmBfYGFhZGNgYGJnaWpqbQlvcnR0dnh5eXqEfAp+gICCgYKDhIaFhIaAhISDhLN1YFxtVnduilNNlnlnaWlnaGp8eJeFaHN0cHV/UV1fXl1eUo1zc3V1dHp4eXZ3d3Juamh+mFdaWlhXVVRWV11ZV1hVblBMVWFpcHF0dHlyZG1ua2lrcXBwbmhkamtsbmpvcWxqYWRqbWxram1uaF9dXFpYTFdXWF1jX1qAg4pIj0hISEdGTEiEfXt+gYeMl5iOkpmRjpeRk4yUmpldXFlaqlasWKqhe3p3eHNzdGtvaWBnam93dXBwaWhmXV9RVlRYUU5TWFhTUZlZZGBdWltYamxyb2tmYmdoYmZnYWFeZmZlYmJbU1ZYU1JKUWJ3k5eNhYiWl4+MiYaFhH2Ae3Z0bF+Kgn15fHl6fXNDW3R2dm5ZeFZdWVddU3JOYWJkYWA+TVFRWVlSUE9RUlNLTV05OTo7Ojs9PDk4OzRPRkVCR0VESU5VUFo3PDs6OjgyMCwrUDA2MzQuLzEzNDQ1TUdHSkolJSgoKEtXMzI2MjExMDE4MEVKSSNHRSIiI0iAWDo3ODY1NDExM1BIRSMhQkA7OTo+ND4+QD47Ozw8PSklJSYnJygmJCZCHx4eHh8gIiIjIzU+PDo6OTo6Oz00Pjs6Ozo5NjMyKzE4Ojw/P0A9Ozk2Nzk7Pjw5Nzk6OzhCQD8+R1FeMzY4O2U4NzY3OTZqSC8tLUViX11dX2Nna3CAd3RYXk5LSEtJT09NT00oLD1IRkdGSEpBOmQ/VDxhkZ6RVk0pa07NkNZ5R2Bqv4uLTkxNSEdGfDcvNzo4OTk1NzcwLSckKCctIikkL0paQDhMWGhqaXGZXVfUqq+EQVRSPzwuLjA+QjMxLklCPj46PkA9PUFBID87NzU6PiA/JC2AeYd8bWllXFhSoJiWlZmlWmBVYWiuvcxeaKdaX19ja254fHVrbW5gYWNjZF9XVldbZX+PS0pLTkpNVmt5QENGRjY2N11TWS4vLzVBPTs1MTM1NThCQT87NTMxLy40Pj9ARUZDQD03OjEuYDo5NjIuK05Lb04/LD9YPkhCS1RKOlMaVE9QVVdcNDQ0Z2cyMUAtLzAyMzMtKywtLjGFgBJ/gH9/fn+AgIB/f39+f39/gH+MgAR/f39+hH8Gfn5+fX1/hIAHf39+fX1+f4R9mHyLfQF8m30Bfp19BX5+f39/hH6Hf4d+t38MgIB/foCAgHyAgH9/h34Df359hn+HgJJ/joC4fwR+fn9+h3+VfoR/BH5/fn+kfgF9uH6JfYd+iH2Gfo9/jICMf4qAAX+LgIV/hYCCf4qAC39/f4B/f4CAgH9/iYAFf39/gICGf5SAAX+1gId/hIABf4aAgn+Qfot9i34FfX59fn2Gfgx7fHx+fX6Af359fX6Gf4J+jn0Hfn5+fX19fIp7Bnx8enp6e419i3wBfYZ8BH18fX2JfoZ9g36FfQN+fX2Zfol/h4CDf6OAAX+GgAV/f35+fpF/CICAgH9/gIB/jIACAgQAgJ6anaWh0I+DjbeLhYOCgYuPi8P8weyoio+PjpGRkJeR6qeE8veRh6eA/ensze3xv5TNhEmc3ZWay5iVoP6g24XetrC+wbzk6fXu4uTY2OPs6vbx8+nwgf+FhoqOlJ2bnKKntO3E0Nni5+Dh4ODy94D8+YTw5+7sg4D8gYD//PT2Y/T8/4KFr8WGgfjy+4D7/4b+hfP59vSAgoL79e7b4uGg3YWHhf3r6/qGiYqJh4SD/KXE8u7u7ICSnZ6kpZyYmpufpKCbmZuiqqytra6usrS1uLq6vcDBwMPFxsbHycvMycrKy4XMgMvJycnNj53u3K+f577OguXV1NDM0eOCwabevbbQ942Plp6ioaGdnpqH6djg29rTztDMzsbGxcjD64yHg4iJhYiMioWL9q6gp6exvLi8tq60uLTAtre+wMrFzs7Cv7++t7Sppqalm5mSnqOup7C0uLCsq6mfpp2Zm5aPkYaGg4GHgIaHiYqGhYaIhoH+/4aJjIKD9vrn5+bb3tnW2eTe3d/Yy9DV2czV29+qo624s73Lw7uqrau4uLOxrLu2r5+XnaCKhoyFipiLiYyNh42XlJ6ho56Unqamm5qhnqCcoJ2cn6GclpWVjpORjoudq77Ey9Hh3tHRxbzIxsLKxce9opaEgObh0dnMztTY7N3r8dXH2ZGF0M3X2dve083O0c/klNCk4ebZ7e/09fj7+v2B+eqHkZWVlJOUmpqYlo+DkJKLgu7m7uvp6/L4lZqalZqXmJeVk42GmJWPkoyKjY+SkPHv8YCAhIiJh4WB75aVlZOUj5WPiozc9vv8gICDhPv58IyWgJSWlJOVmZib9IKAgv/+/oCCg++frbCtra6tsLCxg4+Qk5ibmZqYl/6PkpKNjpKTk5WSkLOytLCrqaarrJ6NlI+MkJibmJOP+aWtrK6vrqqko6SOmJ+goJubnpeblonehIuPjo2JiIqNiufzhIWHgreMgoCKkJu4yM7PzMrI1t3pgOvel9DSzNjo6oWC/ID8gYCmvrm9rZGA+JHfirG7u7Gin+WD4ILUY/y5tebXjpW6jZCJhfmXzMLW5+Pg4NHDwrOyqqStgqqMw7eyxe7xwbqooImdsa+Tn6KhuPjk8Z7s/eS3s7Kwu5+W5OT2j5CF+/ry2eXy4uPP186snLmRjtuVgJKNlpidn6GUjIiJi4+PotPYsaeeifWNjJGPpqWlqK2qpKWrrK6xxc/a5Ort3Ofr+oaHg4eG+4OHiY2Oi5KTlZaVj4aNlJefoJqXlISHiZKanKCjpaKPlJSXpKasqKalqqOaj/v24fqKk5eVi+ez48DsudPSo46DvajRwr7GztX0GIGCgoazvbitpp6a25+qu83OrqOho6Wpo4BiX2BnY4NTS09oTkpISUlPUU9xmoumcFJUVFNVVVVaWJZzXqiiW1hnUKWbo5GrtIhvqnZKgKBqbIlgVGiwcp1hmHt4fX+Em5mYlpGTj4+SmJaeoZyXmVKoWFdbXF5kZGVpanSuf4WKkpaQkZGSn59SpqFVm5SXmlRRoFFSpaGbnGKboaFSVX6GVk+Yl59SnqJVolWanpyaUVJRnJuXiI6Pa5lbXFqsn52qXV5eX11bWq93haGenZpWZW1ucXJraWtrbXFubGprb3V3eXl6e31/gIKDhIaHiYmKi4yNjo+RkpGRkYWSgJGRkZCPkm57wKKF07yVn12Ph4WDgYWTYpJ4mnx4katjZWtwc3N0cnJvYJ+SlpORjYuLiImFg4GFgaVjYF5iYF5gZGJfYq5yZWdnbHJwdXRwdnt2f3l6fn6GhoyOg39/f3t4cG1tbGNiX2dtdW93eHt2c3RzaW9oZWRjX19aWVZQhFSAV1NTU1VWUKOjVlpeVlijopGVloyQj4yPmZWTk4+HiY6S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iYSHgPT2hIW7jZiYpaytrKKqvLy/yMvS4OXk3tqw5eDq8YCB+ICDi4f9g4aespiPho6F5ou5sK6to5+b44XoiNFhh6Kmi+2fq5CQj4T6to+G/v+Bgfz019G9vrK2sf+pjsHT5YDbwIqBzZ6epLjUwPzS8IHF8evXm6fqhobe0eTjw/CpgLq7t7y5tbGdnKCrrKu8zcLDq7myiZmVjYqKm6urqq2urrrBv7/l4dHV1NCgrbi+vMbKyNff6evs7eDm6u7u89ja3uXs7PqQlJWVkIeJjo6GiISFhoaSnqGioqOip6iej46PjYuUn6qspZ6bnZuYl6Wpq6OnqqqgjoWCiP6KmJeLLt+Q05zj/aLZ5tyRhOG8sNb0hJWOioqMkJesusTIxMbDm9OYm6a6rKmqpaCanpuAZm1qXFNzT1FNTExLUFVPU1JVVU1zml2XllhbXFhQim1Wlllvk1eCdn2ZoH5vbph3s3NJjZ9hb4tjWGRfWKx+YZl7d3RwcIGIkpOUkJGgm6VToFWfm6GpVVNVV1xbZGZpaGt4rYCDio+TiY2Pk6Gen6GioJqTlp6bnqOco1OdUZ6Anp1WU6J8jFdPUFBTVlFTVVVWU1Ggl5lSnE+alIeCfHV5oYFZXVtXo5qkW19fXV5cfoSrp6OfnZiiXm1waWZqa21xb2xqa3B0dnh5eXp+gIKDhoeKjI6PkJGSkpKTlJWWl5eYl5iZmJiXlZOQj46QoHpu0ayEiaCKh4aFho2XrY6AjF1YVWBkY2VoaWltdHp6dmZatKKKjoqEgYGMi4yJj2BpaWhnZGFlZmRkXpB0d3Z5cHNua2ZhZWhoaWxtcnyDi46MjJN+c3R3c3N1cnV1cG1tdHZ4bmZpY2VhYl9hY2BdWl5gXWSjoFhXWVdXV1pfVVhaWVteX11YVqWkoZWVnpaAkpeVmpePkoqKhn2MlJiaj3llZ2l2c3h0b2xuZ25naXF0cGtfX1hXWl1hY2NeU5paVVRTUFxhcHJtZWVaY2tlZGVlY2dsaWVlZF1eYGNqanZ1enSMlIuKj5eTk46Ih3x6g4B5aFOOm5qUiX6AjIyMl5d6a2ttcnh1gH95dHp+eHiAfn98hXJ4UI6CfomHhYyNiY1IioxQVFJRUldZXFxaW1pRUlVXWFZUUlVPj4WCUFxcXF1ZWVlbXlxTVFtWVVVWV1ZYU1eMgnuFSEpK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iZXp0Ka+s8m68ZrXa03Dw+Wv0qShxoaflf7Ii9qB1Lmwrb64ub7O4uzz9/39+f37/Pf+iYSRlI+Smpaoraqx3bTLzNTSztba2+Xo/O7ugOHn6PP89f7//4D57/aA5fH9+oCrzIn79fr/iYOJh4iFhfn28/T56PTm0M3IyMy/wLPFpfGIh4H37O/+hIiIssWB/Pvx8O7w7/GHkpSanaKmo5+enqKmp6uusrW5vL6/wcTHysvMztHT1NXV1tfZ1tfZ2tvd3NrZ19bS0MzMzcysn8KooLHe2NfL2N/r7r+ArveEhoqJjJOWk5GRkZCG9P6FhYWD9+HHx87Mx8X8k5GQk5KVi5GVkI2Gz7LAv7y0tLWysK+lprCzrba4saeiqLO/w8jDucPAucC7u7evsKumrq+pq6ajoJ+bnJ+XkpOLi4aGjIWIiI6Wk5SNjoyIgv+DgP7q/YH/+4D+/YWKgO6A8/Th4dPU0NHX2c/o8tvb59S6tbizucTFvLm1r6GfrLm+tJ6Nl5eOnaWcpJGMiJSKhImOl5udqKyqqqWimKekmqagm5menpuWnqm9ydHM0crIzc7KwMTEyc/MztLNzcWwnYb/g+Dm8evOwrXMg4j97u7l2dnLyuHd3s/c6dvd4tiA3/L504eJjY+M+eXo9fn9/vv6hImLjpCQkIeChI+UjYWRj5CPkZGOlZiVkv2PmZiYl5eWmJSXmpCPlJmTkI+RkpORjoCTkYmFhoSChoiI+IeVkpCNkJSSkpCM5f76/4KEhYeHhYCAkpOWlpeXlpaTh/KB+/749fiCgf3vpqWop6mAqKKio6X/jI+Qk5ORkZOUk5O2tbKytbK1r7CpnbO1s7OztbOuqJX/i5CPkImPlJKRhJasrKmpqqagl5WSgJ6ho6OdnJuZlZCD/5SPjI2LiYmKh4X33qm1vruysqmprqqsnqy2vb7I0NLCq6aorpzu7PD0+PeFhYuGhIGWpZP7+YOAhOWJyaqrp6Whiv3giueUyL+Foo2vkbDQ3u2Eibv9/PvzhYuNjoSE/ffu6878po+5y+SNi4GXsLbl0KDezqmV2cvGi6iep8O5iK/Fj6OviPWL35ynqKielo3n3PD37uT2gvrm4pWevMG1tbWxrrCus7m6wMfHubTh5dLIzuCouMGAysTN0c/c5e7o7fDh6OLtgIP1+P/9hIiMk5OPk5WTlpqagoOFhoaHio6MiqSoqamqqZaTl5Sdq7KytK+fnZydqrOyrq2op52VkZaQiJCAgoK98frOiJCLhqPfz9XJ2vyB3PqGo8HHysTDvqmipa/Az8qbmNebn6uopKaam5+bqayAbmJWU1J1U1FQUlRWWVpQV1dbVlZVTJ5WoZaWfm1iXWN6jU1QUlqOeWSAeZKKqny1aUqZkap2jWdbfltqY66KYpxShHVycnt2d3yGkZihoaShoqGZnaCoWFVeYF9hZ2Zub2xypXiGgYeHiIyLkZWVopyZUpGSkpqjn6Ojo1OelpiAk5uhoFF6ilednZ+jWFJXVFVUU5mXmZmbkZiRgYB9fHt0eHJ/bqZdXVmnnKCuXV5egYharaqjpKKhnZ9bZGVpa29zcG1sbG9zdXd5e32Ag4aIioyOkJGSk5WWl5iYmJmamZmbnJyenp2cmZmWlJOUlJR/wZaGg4qIhYSAhouUr4+Ad6FVVllZX2ZpZ2ZnZ2dgrLNgYGJhrpuFhImHhIKtZ2VmaWhoYGdqZmVfjXF4d3ZtbnFwbW1mZmttaG9ybmdnbXZ/g4eDeX58d396eXhzdG9sc3Rxc25raGdmaGtlYWJeXllaX1pWVFtgXF5ZW1pXU6hVVaiYolOlpVapqVpdVqCAoqKTloyPiYmQk4qboZmVnY14c3JudYB/dnNvbWNjaXJ0cGRTXF5YYWdgZVdTU1lUT1JWYGJlb3Fwb2lnYmppZGtmY2NmZWJhaHF/jJOPlIqHjJCPiImGiZCLjZSOjYR2aVGaT46SmpmCeW5+UVOZjYeEgH95eYiFgXV/iIF/g31fgImahFZYWllWmIeJjI2Pj46RTVFSU1ZVVVBOUFhaV09XVVVUVldTWVpZV5hVWlhYV1hZWVVYXFdWWV1XVVRWV1lVUUpaV1JPT0xJS01NjE9ZWFZTVlhYV1ZVgI6KjkqFS4BITVpbXV1fYF1dXFWMSpCUiYWFRUWIi2poamxsa2dnZWaZUFFRUlJRUlNUVVhwcHBxdHBzcHFsY29ycnNzc3FuaFyVUFNRUUtQU1JRTF1ra2lqamdkX15cUWVlZ2hkYmFgXVlRol1ZWFtaVldZV1WcjWpze3p1dG5vcm9vaXV9gYB+hYyNgGtlaWxfjoqMjY+QTE1RTU1OYGpfpKFUU45cmnRyb21pWZ+FUqx2npRkd2d9Y3WUm6BZXXaNkZKP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3dnV1tXTs5WuwLSV2dXajcnT3YDPgMGAhIaFhIGAiZSdl4j58e/r8viDgoOCge7dy+qKkZKRkpWUk5KVlpWTkpOL/+DBubezrqmbmp+gp6yuqaijpKOjovyD8ODUwLm9tLS0sa+spKi2r7SnpKenoaOnm5ygmZeVhIX9lJSWlJWTkJOPh4aB/4GDhpCckpOQjIiC+vbagNHT2uPi4N7m9Ovs3s/Bx9jSra6xr7e2vLq2taynsby+v6epo5yVmJ6orpmRmJuRi4uOj5Sao6qeoKOopJuSoJ6empmgsMDH29rV3NnRzMLKzsvQ0sWxy8vJ08S/wL7DxMCfgOrh8ePGwsbXgoGFi4z269rT0+Tx5NPc2tbc5eiWYJzrhoaJi4yPjY2Rh+vg9vSDjYqJjY6Lj4yLjIj//YyVk46NkJKRkZWVkYWbm5+amZeXmZiYlo6UmJeYmZeVko6QjYKTlpWXlpmYlpCKgvuVmJaUj4+QkY+T6Pz9+f+AgoSGgO6Sk5GVlJWWmJSV8YGC+vHx9vuBgoP3q7GxtbSws7GwsIWNjZGTlZOTlZSSirOztrSzt7m3tbWTsba2tLK0r6+qpPWGh4mQhoiTjYyE/aOgoZycmJmhoJ6RhZ+ho6CcmJmZkpGA+I+NjJCPj4+OjIa0tLy4tLe4q6mwrrGzp6i8gLu9u7arrLK1tb+3i+jq7Ojp6/aOoK69sJCGgJCW3YPgra2kpaCHtvjaieyjw6+GrPTSncaEoMjtie3w4M7d6ujn1MXIyc7LyPebgbLH6eC94YCAh4nhkoCx7Mnw4N6A1u6HnczN/9TN4f+lvL6ZxZCTk5OUjoiTmpuVkrK0s7a6gLrAyci9wMK3vsC5wcPEyMzNrK3Z5Ma5yOqjz9jj1efo6erx9Nzn6OXk4+6Eh4WEhoKDhIiPkZCNkY+MmJmZm52Xh42RjIqLj4+Rjq+1q6SdmpOUm5qsrKmoo6Sio52Zp6GgmpuXlpeSj9OK0Z3y0JWT/PLd2PSEkaScmJH1j6a1HLfDzdPaw6Gho6uwtKSho+inraGbmJuhp6unpqFHVk9STk9wTVRWV1hYV1hVYFtaV1laVFuKYGBZW2KATVlXUpSYVZZ8enRkjbNci6VgR6J/i3CJaliOfHtyZGSOY52WmJiLdn2EeYB4eoebpqZXqFVRVFReW19kZmloZGdqbHaug4R+hoSRlo6VlJiVnZSbl5mWlJiTmpyYmpGVlJucmZmhd4NRnaBSl1CZU1SgnpiZUJiLkI2Vi4CAf3h1cW1vanNzW5JcXVysoZqnW4KNXltaV6unpaOhoJ5XY2tucW9sa21wdXZ5fIB8gISHiIqLjI2Pj5GRk5WVl5iZm5ycnZ6foaKhn5+foJ6bmZqZmYSyg5CHZYeFiGKBh41ek4NVV1dVVVNUXmdva2G2tLGoqa9eX15eXqmYhp1dZGVjaGtqZ2ZoaWloZ2lirpZ+d3VxbGdfXmBfZWlqZ2hmaGdmaa5dp5mOf3t/eIB3dnRzb2lueXV3b2xtbmpscGdna2dlZFlZpF9fYF5fXVldW1VVVKRTVFdeZV9gYF9aVaKmkIqLj5WVlJCXoZuflIh/g42Ia2tubnV0dnJvcGtpcHZ2dWdpZWBbXWNrbl1XXmBZVlVYWV5ianBnZmlsaGNhaGZmZGNpdYGGmZaTmYCalJCFiYuKkJGHeo2Li5aGgn9+hIeGaU+SjJWKeHJye09OUFRamJGEf32GjId+hIGEh4OGWWOWVVZZWVpZWFdZU4yDjI5NU1FQU1RRVFFSUk+WmFRcWlZTVVdVVltaV05dXmJaWFdYW1pYV1RaXFtdXVxZV1JUUktYWllbXF9dXEBZVE6UWFtZV1NTVFhYWYiOjoqNR0pLSkpLillbWl1dX2BfXF6VSEmOhoOEiEZHSJFrb291dW9xbW1tUE9PUlNUhFOAUlNvbnBsbnR3dXNyXXBycnFyc3Bva2aVTUtMU0pMU05NSJJlY2RhYF5fZWRjXFVmZ2hlYl5gYFpaUJ5bWFhcXFxbXFpUb3J3c3R4em5ucm9ydWxufn16enhtbnJycHd0V4mIioiJipNUZHR9dl9XUl5mjlmqdHJwcGtZd59/Ua6Ag5OUZ3qvlW2EWnaKoFGDhHpwdHt6emxiZmt1b2uLWkljbYFbODczFhITQBMQG5uLlqKwZaXAcYCfmaF/fougbnt8XXFTVFlbWlROUlVVUVBsa2lsbnB6g4J7fX11fH55f4CAgoaHaGeLuINreJVrkI6TiJWVlpicnYiQkpCOjJlxV1lYVlhVVFRXXmBfXWBeXGVmZmlrZFBVV1JQUVVWVlVyd3FrZWJbW2BfcG9tbWdoZmVgXm9ta2doZWVlY2OWXYtjn4ZmZa2plpOjVl1pZWNfoWBscHJ4f4OIeGFiY2ltcWZjZJVobGNeXWBlaW1qaGKAIR0fHR0nGh0eHh8gHx8dIiEhICAhHyE0LDEnJik3ICUkIT5ZJz0vLTJCtb9Wq8DIiOWxz296SCdIQT0uJSQ5J0FAQ0hBMjEuLzEwLzA0PkZHJUMjICEgJSEiJykrKicoKSgtRDEwLzAvNToyNTY2NDk0NzQ2NDQ2MzY3MzIwMzOANDQyLzAnKxgsLhoxGzEaGjQyMTUdMiouKiwrKSknJSYmJCknKis5dlBSUpuOiZdVdXFVU1FOm5iWkoyKgkpYYGJkY2JhYmRnaGptbXB0dnh6fH5/f3+Bg4aIiYuMjI6OjY+Pj5GSk5KSkZCPjYyNjo57cIKTjVxgXmJaWltfTXqAaEJFRkVFQ0NLVV5eWKuuraCgp1dWV1ZVmYVzhU9VV1RZXl5bWlxdXFxaXlmbgmlkZGBcWFFQUU9UW11cXVlaWllZnFSain5xb3ZubGtoZ2VgZXJucGdlZ2ZiZGlfYGRiYGBWVZRUU1RTVFNQVFJNTkuPSU9PVmBYWlxcVlKbnoeAgH+GjI2Ni5CZlp2Sh32BjIRjZWdncG9zcGlrZmRpcXNyZGZkX1hbYGltXlZZXFZTVVhbX2VxeHBtb3JtZmJqaGhlZWx5hYuhnpyjo52YjI+RkJaYjYKWkpCdiYWGg4mLiWpMi4eTh3NrbndNSUtSWY2GeXFveHxwY2xramtpaUYET3ZEQ4RFgENCRD9nW2BkNjs5ODs6ODo3Nzc2ZWc4Pj04Njk7OTk8PDs1Pj9COTc3Njk4NzU1Ozw7PT48OTYzNTMvNzk3OTo9Ozs4NC9aNjg4NzIyMzY3OlFKSEVJJCUmJSUmSjU3NTk4Ojs6NjhUJCVGQD4+PyAgIk1AQkBFRT5APTw7KCQlgCcnJiUlJSYnKzs7PDc7QENDQT8zPT8/QEFCP0A9OVAlJCMnIyQnJCMhSTc4Ojg4Njc7Ozo2Mz0+Pj06ODo7NzgyXzc2NTs8PDs9PTZJTlNQUVVXTk5QTU9RS1BhXVhXVk1NUFFPU1I4UlJUU1JUWzhFU1lTRj86QUVXQIJbW1dYgFZHUGhKLGxFgOqPeG9RWHNniXh/MUJCOzk8Pjw9NS8yOTs4OEQqIi4wPi8eHyEMCQklCQgSjn+etcFlust7YHB5cktIUGA/SEg5QSoqLzExLicnKCgmJzw+PkJFS1toaWRoaF5laGNnZ2doampNTWJbTk5dektocXRpcnN0dXp7eGNpaWdkYnBCQ0E/Pzs4ODxBQ0FAQj89QkRERUZBMDI0MC0uMTEyMUhLRUA7OTQ0NzZEQ0FBPTw5ODQyPz8+Ozs5OTo4Olw6TzJTRjc4ZWVdW2AxLzAyNTRVLTEvLzEzMjIsJSYmKS4zMS8tQCorKSkrLS8xNC8rJ4WAAX+QgIh/hIADf3+AhH8Dfn1+hYAHf399fX1+f4Z+gn2RfAJ9fJB9AXyjfQl+fX1+fX59fn6EfQF+kX0Ffn5/f3+EfgN/fn6Ef4d+tX8BfIR/h34DgH9+jICGf4WAhH+QgJd/AYCgfwF+jH8Bfot/5n6IfYV+j30Dfn9/ioCEf4yAgn+wgAF/ioCFf4aAAX+KgAN/gICFfwSAgIB/q4ABf4qAAX+XgAF/ioABf5t+h32KfgN9fn2GfhN9fX5+e3x8fX5+f4B/fn59fn5+kH2Cfod9hn4BfYV6BHt6enuEfIR9hX6kfwN+fX6EfwF+kH++gAh/f35/f3+AgIV/hoABf5KAAX+MgAICBACAhYmHhoe5jY2PkJGVmpqXoJ2e2tjiq6iTkvWCirqJkJGPjfq1j/7X8M7imoa3vlio1YWpo8vArbqFuqGc67zwlOjp6ubU1bvxxbq6wLu7x9Di/oeHiYyTlJyXnqiipqqqrbm7+8fV1djT1Nrh7/H2guPm7urn7fHt5/zu3ejd5+CA7fP99vyn4YKA/f7zgYKEiIDt9PLl4NTr5ebZ1czQs7KtqLGzsauutNq6gYaE/fXuo9eJhoaGh4SBgP/9/frzgJKiop+eoKOprLG5ubu/w8TFxsfHycvLzM3Nzs7Oz9LV2Nzh4uPk5eTj5OLi4N/d3ty7iLXItv7U0/2Y1NLfoZAmuJflk4GC/YH++vb/m6aYioH68uru9oSFhIWHgoSIkJOXlZeXlJSEmICVlZaYlIjvz7CjnqC2r6yzpKudkZeXoPaKjpGMiYiC+ePXyMG4r62oqquno6GnqKuwqa2kjIaIkoiHgoqLkZaQjoqJjI6IipCSjZCOiYXugoeMhv/z+PuB7uPe1dPDysvTzcvDu7a+rK+0tbu5v8jLxLrExMG1rbavq6uptrKro4CjqJ2Yj5OampGXoKOTnKGsopyUpaq8z8nUzczYztbM2NrRxcXJy8vJycjMvKa1tLG0u8nT0czEw7T52tTIw83w+eyIn66sn/nX0tnh2tDS2NPf8864g4aGiYqMi46Oi4yQkYz6/4iIjY+LiomKjIuJh4GKkZOVkpGRkJCUkJaIh0GdnJubnJiamZycm4iNjo2UmpyZmJaRkP6TmJqamJ2dmpqZmP6VmJiZlpWRjI2Rhev6goCBgoKEhIKB9ZGTlJOTlISVgOX6/vvv8PyA//+Ag6uvsbS0sK+1srSGiZCRk5WVlJWRlIS0trOzsrG2trOuk7CysbCvsK+sqaWR+YyIhIuJj5OOjoeBpqmooaGfnp6hn4yPoKKfnJmXlZWPjILwjYuNjpOSk46/w8SzuL63t66uqaqusLGrm666ureztbW4xca+gLqvid/l7JOytbrDyKaIiZWToY2S96yoqaqlh7vJ9dGXi5Cxp4arteLN3p6eqtrr8eLP2t/j4dPAs6+0trj8nIKswuqgl+2CgKOgnZmQke3U3MevyLzQzt30xcu1/NPk5/T9nLjWlZOOi4qRoLC7vbu9u7W0ubnDxsS9wMa7ycrITsTEtr/HysfG8/bVwsXDlcna5ebn6uPB0NfZ29ve+vL5+fnz9/n+/YCBjJWTkZSZlZ6fn5qgoqqxsaCKjIuPm5unsq2ZjI2KjJ2ir7i1soScP5SIh5eTkJWUk4Cy7/nD+fvkqezyi4yIhIeKt8vO0cuov66opa65x8efmJymrbKnqKqr85+am5ecq66noqCUiVJMTk1MTW9QUFJUVFZZWVNdXF2AkItoa1ZWkU1TbVFSVFJQjW1TlIKMg7FyZpuiW5GiZHlwh3Nhh2SBcG2ohaBooJaSjYSMeKCCenl9enqBhI+ihFeAX2BlZGluZ2psa250eLOBiYiNiIiQlZ2bnFGSlpqVk5aZlI+alYySiZKLkpifmpt0lVBPn6KXUFFSVFGVm5WRjYuYkpGLhn+BcnJtaHFxbmpscI9/Wl1araSbb51gXlxbXFtYV6+rqaahVmRwb21sbXB1eHt/gYSHiYqKi4yLjI4Wj5CSk5SUlZaXmZqcoKChoqKioaKioYSfNp6IrIeUiqqEgqd6iYWSdnKSdalmWlmtWK6trLJyfXFjXa+rqq6yYWFgYGJcXmJnaWprbWxqa4RtgGxrbW5rX6WIbmVhY3Bqa3FmamBYXV5orWNmamJhX1uunZOGgnpyb21vcW5sa25vc3hydW9g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tLK1sK+usbGstb2xq7y0uba1usTBvruwrZaRs7m5vL27ro+CiY7rgMKim+2voqarqIzE09D9yKWkhqeeepzp2ZD1udaQ2fTfyczd4eTdvrGtuLW9i5r3rcTsp6ijlPmErrqvlpuUkY/ppcCdqKrCzOjhhPWHlfLFtbbQ7qWot6iZn6atrayhqKuwvcbMzM63mZ6ipaSkuMzNx83V0c/N8frv3c3IldHVgNzb8PLw1+Hk4+3z94KA9/f8gP6AhYeWnKGgn5uhpqirp5+ZnJyeqKupp6WinbWzsKyomIiLiYu1v8G/vLywrZ6Xj5qXkJCPgsz+2Y/Fy8zLnt7DtMK3qJuXna2zssDM2LagoaSmp7G4opyeoqyvpaekoKHtmZidp7CopKKgj4mFgEtOT1BRdVFSUVdZWVdXUlpcW2J8YViEVFZUV1ZwTlNQTUxPenpVY6pzjm+sk1aVl5Z2dY2Aambaq5uOf4d8Xq6yoZiYiaFgW1tXpI6AgIeMi4ySnFJVZGRycXN6eHWAi55WW41ub2xxcm16d35/fX18dnZvcm1wcm1rbWp0cmRlbHWAgIWFr7R6fHt6e4aIhoeHfoWBhIaIhYiJjYqBd3d4c4B8enl7eHZ9foJhbm1qZ4WJbHd6eXh2eXl3eHl6eXp5f42Uj46PlpebnaKkp6ejp6epqquvqKmwr6+np6Sct4O6n56dnZ6eoKGhoYSggKGMgIeYjJd+e6J7WI9YhnaFpJiPra1ZWVtaWlqqr19lZmVit62vqa22X2FhYmO5XWBnaGttbm1sbXBvb25tbGpsaF+ZjX5pZWdiZWdeVlVfr19jZGVcXFxdYWNhrrKlnJKJfnRpa3Bsa2pkYGNeW1teXllXWFZcVllgX1hWXl1YgFxVoFFWW2FZYmFkZVidio2BhoeHhoKEgoF8fIeQjoaEdWxvcW1tcXV3dHd6fnx1cGd1c2ddbmlpb21rbGtmYF1fYWBjXl9uc3qLm6eus6OSo6CbkIaJiJKfnZGMh42RhoWMgoCAk6yFg4aFgIaOjI+PioKAYX16fZRidoiDgoJ/gH53WpCDh5SGkZVXWZNVVlVWV1taWFdXVlRWUpuaUlRUVVVSU1FRU1FRmkxWVlhYWVZZWFVVVldOU1tcX15eX15eYWBdVGFhX19fXl9dXFhXTVleXl9eXmFiZGJjTlhWWFpXWFlXVFZZ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4uqEhYuWn6aqsL3DytHS0c7L0dXW2dzk6erp9fr+gYGAgoOCgoOFiIeEgf39+4D/gPz4+YD5+PPx8Ovp4+Lh3+Pm3t7U2NjV08zKx8XFxMfFx8O9v8LBvbq4traxsbKonqinpaCgp6KgoaGio6CipK2rqKWmp6eor6yusLCytLe0tsDGy8/N0dTU2uDvpJ6ShLaf5N/e29jZ2tva3OHi4cLiss2/07u3/7O/xNKt9ICXnoLE9/H7/P2ChoaA+/f5ipCUkoiB9O/u9P2BhYaIh4mDhoiVmpycnpmXm5iVlJaWiuqChIKB79/ApYaHiYryiIiIi4uHioWKiYzr/vyAgfv2+vj35dfKvbevqZGOhoeOlI6Nm5OLjImNioqFjY2JiIiNkIeA6+fs6eXWyMrM0IDU2t7b0cjKy9HP0dDbzc3BtrCusba8v8DCyr7BrbOroaK2qZ+dnJmup6ewrKWep6qvv9Xk+YSIhISDhIL38fTl2tLKycvGys3Mz8jHzMPFwrnDzdPe7vDavMTLxcO+vsbDvL2/vKyRqr29vLi1s7qws7GxpomBgoTVtPeDhYSAiYCJi4qJiImIiYHn9IOGh4qJh4aKjIuLiYOAjpSUkZOUlJKVkZCSjISJi4qQlpiYlpmXnJSInpmcnpydoaGjop+AkZeanZeYnKChnZyIjZaYl5qVkJCTjpHqjI+Mj5GQkJGSl5X3kJKRjo6OkJOUkf3x8vj38ff29vP9/Ierr7SzsoCzrq2rqYSJiIeMlJGQkI+N+Km0tLK0tre3trS0iqWpqqmsrayopaKQ+oiIjY2JjJONh4r5nZuboZubnZyXnZiBkY6NjoySk5KSk5CK9pKVko3Jvbq3sLG0sKm6vLS0sq20ure5tbWjr7++sb3Axsqwp7PCvbe5x8nDx7eKhoaG1YDKqZ/qoqCkqaSK1tvku4y9sszwnpNqh7G9sInZnpW+29HR3eDh17+tqbe3w4iU9a7M+rK6rLjApr3Ix8K/qZ+c6tSY6bqqssj709HO6eCNpKKFutbYjo6kwsbGurafk5abnaCiwszPw7q1sbezscjU08zS2tXa1/mBhvrX1JTQzYDX3P+Ag4aFhf6GhoaIioT6+oCCh4mJl52fnqCgoKShlpKE5OHh4u38iIWDg/+Fl5qB+YSGj5ecrr66uLy5nZqip5+Yj431tN2Puf+2//j7hNaLjcvc5+vm7OGysbO4vtGkm6KjqK67pqGjrbi5n5meoKis9qWqpamin6Sdi4aLjH1RVFRVUHRRVlpaW1lWVFNYW1xQh1hahlpMh1WFZYpUT1BLSUpKWI+eqoC7gFOXkXlzeI+Ib09kx2NTpJ6YlpiXp5hyj5xedHl9goyHkq1keoiPm6hXXGFna25zdn6DiYyLiYaEiYiKi46Tk5SVm52hUVJSU1RUVFVWWFZVU4SkgFKjoqFToqKenZual5SWlZaXmJKPjo+QkI+Pi4iFh4aEhYeGgoSGhIOCgYF+e3x5dHF4d3NydHRydHNzdXd1c3d+fnt6eHh4ent9gH5/f4CChYaHio+RkJKUlpqcsoeDdGiPdqOem5qYmZqcm52fn5+KxIOXjIN0craGhoOcjdV+gHxlk66prq2sWF1eWbGusGBkaGdhX7Ovr7a7X2FiZGJjX2BgaW5vbnNtbG5qaWdpamGgWlxbWKOYfmtVU1RWqF9fX2BhXl9cYGBlq7+9YF+4s7e2taaYjoJ8dG5fXVdYXWFcW2ReWVtZWllYVVxcWFhZXV9ZVJuZnp6aj4GFiYuNgJKYl5CGhoaIhIiJkYaIgXZva2pudHV8fYB1d2pwaGRkc2tfX19db2ZqcG5qZWtvdoGTnrJfYmBiYF9csa+vnZqTjIuMh4yPkZaQi5GHh4B8hoqQna2tnoSKjYiGg4OIhoCAgX52YXiJjYuHhIGHf4SAgHZeUE5PiHemV1hXVVtagFpZWFVVU1ZQjZJPUlNWVFJSVFVUVFNPTlhbWVhZWVhYW1dXWVVQU1NTV1laXFxgXF9aU2FeYWJgYmZoaWpnUFlcXmJcXWBjZWFhU1NYWVlbV1RVV1JWjVhbWFpbWVhZW2BflldYVlVVV1dZW1idjYqPjISJiomHjYpSanB0c3J0gG9raWdNTExKT1RTUlJRUIxqcG5rbHJ2dnRyc1ZmaWppbG1saWdmWZRNTk9NSU1RT0xNkGBeXmJgYGFhXWBeUV5cWVpYXV9fXmBeWpxcXlxafnx8fHV0eHVveHdydXBscXZ0dnFyZHF9e3F7fH6CcGpxeXZ0dYGDgIN4WllZV5+QgHJqqmpobXFuW4mQmHtcc2aGwHhvV2KHiHxij2trdHp1c3d3dHBjV1NWVmBMVI1lcoguHyFPaT8fHyAfGBENDSCAb62PjpqlzrS1to+G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iImLjoaH5//7gYWGhoeFgoiHg4P/9vTx7eHNwLSunqSUkpKTj5CUmoCWkouFgYaBgIGA+vj09PDm4+bo7ern29jb4d/V4t7Z0czQur/Jvb25x8fMz8W7raaioKubl5eRkJGQlJeTwNTc6ICCgYOEh4mGh4SD/vbx69/g3unL29LGxMrN09jKxMvIzNrc3dvg6d3Y29TVua/Dvb+5vb29wL/K0dHGyczEu4C4sq+tqq6xrqnD/db9/YKGhYaGiIiJiomHhvfo5/mAgoODgoOFhoeMi4eGhYyRk4+PkJGSjpSSlYWOlpaXlo+KjoyVmJqRjpybm56enZ2goaKiioqXmZqcmpmenZqYjIWUmZeWlJKRk5ORhvSNkZCPkJGTlZOUhoWSk4+Pj5CQkoCQjv6am5qgnp+fnZycloqprrKxsK+0rayriJ+cnJqcoKCgpKOek7OxtLSwsLa4trSQrayoqKmrramkpqTyioyNjJKTjoqIhYT/n6CgnZ+fnJqZmJLvl5WWmZWUlJWVlpSL9ZCN19PIx8C+u8XBu6+8vLO0tLW2trS2raSeqbPFu4CytrWst7zHw77CuMLKv6aKjIiNoNGloeGmo6Kkp4zY6bvQmc+3wYfTj4Jo75/13bDq17mfx87V2dzXxrCosa6+h5L1rdKBsr212dzM4sTDxtDJu62J7JqSh+Cso8KuqLndm4GJh6K82biEr5qnmZ2yvMS8p6SwsLLGyMfMzs/J14Da2Nre3sLHztPQ0fT/i4OFhK/p8/2Ag4SD3eTm4+Dj4oSHioyNkoyVlZWXnKKdo6WmkePJxu+LkJKTmqKio52gnKGpoaijoZL0/4OEjpmekZiqrp2cm4W+3+nKlZWS8YbCoqKrpbG/w8fN19znwa6xs7jCxoeZqK63wrOvsa6tsRSfnqKsuraq7qGmpaGioJGIhIuOkhFXV1VUU3tWVVpaWlhWVFNVWYReQ1yKXXFqYJJNVFFLSkiNemisb2OYum1Ppn1heH2aknJVUmDAcFZbalqnWKCZV1Ckkn+GjZSepFRUU1JQnp6eUFJSUVCFUiFTVFVWVVJWVldWVVRTU1NRVVhYWFdWVlVUVVRTVFdXV1WEUyZUU1NWV1dXVlVVVVRUVFJTVVZXV1VVVFRTU1NSUU9XWFlaWlpZWYRYBFdWVVeEWSNaWVlZWFlYV1ZUUlJUWFlaWlpc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kZKSk5CPj5D7l5mdmp6coJ2io46dqqyvr62AsLGtrqyGpqOlpaKko6OjpKSMrKyurbCvsbWztaqYr6+rqqamqqipqZX5k5OMj42NiomKh+uOoJ6YoZ6XlJaXk5LvmJiVlpWUlZeYlpGM7+jU1MDQ08zi2tzX1L63u6+ur622rbCqoaann6WutcS0urXGxsjAwcTFurOehpCPoL+Aw6in5KyrrbCzkuelzdGstraoy6/Yid1k0YOO24GD6ZSOwdzb3tvOrKWtpbSMmP670oamrK+1ubSs9MHT2NHFxrPR5emWkYv265aUm6iHxfLw646buujkm8DBvLSusK/F2eDm1czKwtDPzcvN0crS2dTL09nV2dj5g4qBgoKu+PyAhIGAgIDh5+3w9/38jo6Qi5GUkpaZloqTmJqOzsXciY6PkJeTkpGSj4iIhIWLlp+rtLGoo5GFg/6Fp5aysK+qpvOWxKKOqpmamMqzi8izvN3o8L++wcTS4dGwr7C0vMWxiKG1vse5qaimrrKjo7W9xK+joOOlpKKfjIiLjpCTk5OAVFVVVVZ/WFZXV1ZVU1FTVFVaYGJhYIhbTmiKUVJOSUlOS2COd5V2sahiUVJkUH1/Vk9xUFdTYspxZGppXl1kXVJSWV9dVJybnJ2foVBUYl5UUlFRUlJTYG9qYFhXWFdXWGd3dmZeXFxaWFZbd4SBa15cXVxbWFdog4BgVVlaWVdFVVRUXHN1YFpbW1pZWFdWVWBza11ZWllZV1dWVFRUU1thWVpcXF1cXFpaWVdXVlVSUlpdXl5eXVxcXFpZV1dWVFNVWVxehF+AXl5dXFxaWVdUU1FQnJmbT1BQUFJTUlFQam6KiYVpkHymnZqbm5udnYuygZOBa3mHt3KitLJtiXaAgotcXrWqrq6kqqxdXVyzr1heYF9ibmRiYLy2X2FiYWFvcG1sb2xjZWRmaGdloquwWVxdYGNmZWBeXKFyd3Z6g5OcrFxdXl6AqLxiZGdjX1xfr7FcXmG5tLGxqqurpquvppKSj5CPioOFfH16dWtpaGNfXl2xr1xcXLKknJ2fnZSapJyjnp+em5Wao5SJiHiGioV8foF7fHlyZGFnZGNocHBzYV9oX15VnmFgXl5csbSzX19cX1+xs7Kqp6ampaCbi5ifnJeYl5yAp5+fo5ecsK6hoqCalI2UlZSHdYKAgIyOk5ehlpqbnpCNnpWSk46CiId/Wn6fm4+TrFpaWVtbWllaWlWVgXl1iZuimpqXTlFQUVNTUFpiZGZfV1FWWVddWV9ZU11bX11dWVldWldWVk1aYmFhYmNhY2JlaGlYUVpbXl5cXl9hYV2AWY5OUlJTVVdVU1dSV5JXV1lcXFxgYF9hYJlXWVZXWFhaWFZWWJ9gYGVhZGNmZWlpWmZtbnBxb3Jxa25rVGlmZmdkZWVmZ2ZmWGxsbGtubW90c3RtYG5ta2llZWloaWpdjFZVUE9OTk1LTk2IWGNhWmNhXltdXVpZll9eXF5dXV+AYmJhXlqWjomOf4mLh5WTk46LfXh6cXFycnZzcGdjZWVkanB0e3B2c318gHp5e3t0cWZVYWBqkYhwdaR2dXZ5d1+VboqNdHN1ZnZpm2qkUJ5cap5fW5hlaHR2dHVzbFdSVVFdUFeNbHhNNyMhICAgREQjIB8bFhUTUX54cXBty8WAh4aHkWp8lpWUWWJ3nJdmeHZxamZpaHiGjZKFgH96hoWFhYaKhYqNiIGEiISHhaBmX1JSUnKupVdUVFNTiIuPkpqdnVxbXVpeYF9iZGFWXGBiWoSDklxfX19kYmJiY2FYV1FRV15pc3p3cGteVFGaUW9oeHZ2c3CjZIBoX3RoamczhXhUd2xwgomOcXFzdX2JfGhnam51emxXZnF3fXJmZmVqbGJjcHh8bGRhjGNkY2BTUFFThFUHHh8gICEuIYQfgB4dHBwbHB0gISIjMCIdKDIcHR0cHB4eMaOcjpLn4eqNhLyRl4FEKC4fIiIrXDAqJikqLi40NCorMDY6dXRycXFxODM1PDw6OTg4NzUyNDk+Pj08PDs6NS80Oj4+PTw7OTQwLC4zPD49PTw6ODcyLy84PDw8Ozo5OTgzMTc+Pz8/QD49PDw8OjQzPT4/Pj49PDw7Ojo6ODc/QEBAPz8+Pj09PDw8Ozk5PkBBQUBAQD8/Pj09PTw7Oz0+Pj9AQEFBQECFP4A+PTw7O3Jxczs9PD0/QUFAQGh4n5iAZIVwjoWEhISFh4h4fnqNekpdaKJkj6zWtMeLkJGMTlWlmZqakpmWUFFRmZVKTU5QVmNhYF65sltZWVZWYmJeXmNfVVlYW1taWo2Xm05RUlRYW1tWVFGNY2hnaXSDjJpTU1RUmLZfYmVhW4BXWqSnWFtes7GvrqeoqaSpr6SOjYuNjIV/gHh3c3FoZWReWlpZq6pZWlqwopWbn5eJkJiTnJibnZSOlqSRgYJygYSFd3l7dXd1b2FeY2FhZ29wdWVgaF9fUqVqaWZlY7/ExWdmY2dnv8G7tLe4tbKspZSkrKegoaKoubGusaSlu4C6ra+soZiRmZubjHqJiIiVlp+ks6eqrK6enrOmo6WgjZCPhFRxioV8gJZPTU1PTk1LSkpFd2NZVm1+gnp5dj0/Pj0/Pz1JUFNVTUQ9PkA+RUBGQTxCQUNAPz08QD05Ojs0P0ZEQ0RFQ0VER0tNPzM7PT8/PT9AQUE+OVkxMjQ1NYA2NDM3MzhgOjo7Pj49P0A/QEBjNTc0NDQ1NzU0MzRjOzxAPD89Pz5CQjhAREVFRUJFRD9BPTI+Ojo6ODk5Ojw7OTI+PD08Pz0+RUVIRDg/Pz8+OTk9PT4+NkYsKycnJicmJSkqSjY8PDY9Ozg3ODk1NmI9PDo7Ozo8P0A/PDlhXIBlZ2BpaWFvam1pZ1xVVk9NTU1TT05IQ0VGSEtOTVFJTUpSUlVPTlBPSUlFPURCR3RdR1R1XV9iZWVMZlp7f2RLST9AN2BDmILyZVpaQkuEaXhXQ0JCQTYmIiUjKSYrRzExIRwSEREREjQyFhUTEhAQDz9hXGdpZr6yl6WjrWlMXIBcWjhCTmNhPUhFQj06OzxFUFhfXV5eWmNjY2RlZ2NlaGRYWVtZWllmLjg7OzxNcng+Ozs6OVlaXV9lZ2Y+PT47PkA+QD8+MjY6OjhZV1w3Nzc2OTk6Ozs4MjArKiswO0NIRkA7MSgmRyg+OUNCQ0FAXj9QPDVDP0A+S1AjLyspLCosLCoqKistLyomJyksLzAuJCkuLS0qJykrLjArKzAwMSwpJjUlJyYlIB+GHoWAAX+QgAV/gIB/f4eABH99fn+GgA1/fn19fn9/gICAf3t/jYCGf/+AioCDf4uABn99foCAgIl/CXt/f39+fn6AgYSAhX+CgId/BYCAgH9/iYCCf5KAg3+KgIl/hICCf4eABX9/gICAnn8Ffn5/f3+wfoV/g36Ff7x+h3+KgIp/vIABf4uAAX+LgAF/i4ABf7qAAX+KgAF/jIABf4yAgn+vfgWAfn5+fYZ+AX2EfhF9fX5+fXt8fH59fn+AgH9+fox9BX5+fX19h36CfYd+AXyFe4Z6AXuEfYV+oH8Jfn5/gICAf35/hYCHf4+Ag3+ZgAF/iIAEf39+f4WAAn9+qIABf4yAAgIEAICWmJSSkdqYl5eXk5OUkpKQkpWepaaj3pqalZGJiIiHhvDGxObso/CZU8teV6uvu6Ga04iOj46ioa6ksainrqypnKyqnp2pq5WJhoaUpqWtvsGulpGVqK6+zNTUxKSXnq+6xNje7OPFpJ+xt8LX7Pn89c2koaaxw9jwgYXxtZWZm4CZnrng8fj90aGQl5qbmaPL+YOC7MOXkJWXlZKQkK3lhIPptZiXm5ubmpiXlJKStufqsZWWnqChoZ6dmpmVk5COnsOolZmeoaKioKCfnZuamJSRjIqIiv/U9ICEhYeKiIiGg/6Jne2imKWrxZDn4+Ph4sPysM2oxPmz+oyco92hjICPm5yhyNrzgvLo/IuOhoOD84KFiIuMjouLg4aDhYSJg4iYl5mXmZmYl5eWlZCP5P+AgYSFhomLiYWMifC5wry8vcC6uravuszQ1/r8g4WAgYD38PD2/f718e/t7evv6uzv0trWysvJxb/DxM3Kydfb3Nrb29nd2c/LysXEw7q3r4CtrLCurpCfmZuVmZORnKCZmOOxuLy8wMTCusO+v7vBwcS3p6errKOqpbiB/oCEhIiJj46EhYb/gf319e/z7uXOzc2+3tzl6/Ht7ujv3dvq493g5+Xd3N3h3d7Wz7/T5t3d4NbO1dfSyMDMt8bKyM3F863l/oGA/f6A5uyEhIaHh4CIiefS0Nfc5ODO2uz/goKEhIL7hZSVl5mblJWWjYqFk4+Ij5ycl5qXmZiWl5WTlYaVnJ6cm5icnZyfoaSMjZaSmJeWlJibm5iW6ISGhoaHh4GC+4GC6oyVkpKUk5WZl5aWi4WTk4+NjpCQkZKSg42Zmp2ZnJygn5yhgqaurKyqrTyurqywroikp6qpqaWmpaWmqIuqp6etq7GysrGvtI2vsK2sq6enpaSjoviPjoKBiIyOlpmepoyenZ+gnZeElICVjvGWkpaXlZiamJWRiuuy4Ojw9/bh19ni3N/r1bWyrautr6iprK2rtLm7sbu/u8PIx8PDycbCw8Gwi4+mlvOzxKal6rOvq626n6LCy9HDxaS4mM7l3IG6t2jwyJq7lPLhpqzc4dzbra3Bx+Chp4jH4YerxpW0s7mV/rjU1M7E6oDMsrmbvKCeoLKUgMuns47K3eXyg/mRlLPI6vDWxMfLzMnIy+f1/vro19HU0tHO1tfa3ePd6u/sgoeQ/f6Cqv35hPHl5evv8/6Flpucm5qdkZebnaGioZOYmMXFkpWMi4qopqWXiYaFi4mRjIKElZicwd3PxJH5hI2SpJmzro/I3UDi26ewsLKtrZii+sLItb3i8cvAwsDEz9q0tLK0w9HHvJuyu7yqq6+wsbqusLWur6Wioqbln6Kdi4OBgI2Rj5CRgFdYVFJSf1dWVldVVFVTUk9QVFtfYV+HWVpVU05NTk5Mh3iTr7KKypBcpUxQkYWEYlR2TlJUVGHLa19pZGVoZ2ZdYmBYWF9hWVRVVVtgWl1ma2VdWVpiX2p0dHZyZWBjZ2ltd3qBfnNlY2hnanaBh4iDcmNjZmhud4JERn9lWFxeRl5dbH2Fio93YVpfYWFfZHeQS0mEcV1bXl5cW1lYaIZLSYJqX15fX2BgX11bWlprg4VoXV5hYmNjYWBfX1xbWlhfcGNZXWCGYoBhYF9eXFpYVVNUmYSYT09RUlVUVFJRnVt1u4h1f4SXaqKdnZydiLh+knJ3p3rDbXl+rn9vcXp6d4ORp1yporJjZF1bXKxbW1xdXl5eYl9iYWRiZV5kb21vbG5ubmxra2tnZpytV1hbW1xfYF5bYV+ken57e32Benl1c3+JkJy8vIBhY2BiYLivsLK6vrSysbKxr7GssbWanZiOj4yJh4mIkJKRlZmeoqakn6KimJGSj5GQh4iAgH6AfntmdG5wbHFqaW9xb2+dbHB0dXl7enR8eHh0dnd4cmZmbGtkZ2V6W7NaXWBiY2dkXF9gt162s7Crrq2ijY2OiKKfp66wr66mqoCcmqWgnZ6ioJmYmZ2anpeShpmupaSmmpSanpuUj5aGlJuSlI2mc5emU1KfolKVnFhXWVlZW1uUfHZ5eoB9dYSQoVFQUFBPnVZiYmNjZV9dX1pVUl1ZVVhhYlxfXl5eW1pXVVdRXmNlY2JfY2NhZGZqWFVbVV5eXlpfYWFcXItOTz9QTk5OSUyUTU+MWV5cXF5eX2FfYWBYUFpaVlRVWFdWV1lQWmNiZWJkZGhnY2ZUbXFubWpucHBscXFWaGpra2qEZ4BoaldpZ2ZraW9wcG9ucllub21rbGloZ2ZnZ5dVVUtKUFRWXGBjalljYGBiX1xYWlpaW1eUXFlfX1xfY2FgXliZbpGZnqGjlY+SmJGSn4x3cnFv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iYiHhIOAgZ+Qn6ibg5GL+Objw62wzrabgpiclpOhnY2IjZWVqLrDtqnC4v6FgISKgu/8goGGi4yNioyM+vT6+faXlZWZlJSUlZOUlpiYmZiSjYSEg4mKh4eHhIfrtriytb27xru9tba7uLiuqq/B0Nvc8Pj29fPx+/v79fHw6u708+rH3ePU1s7HxMPJycrD3N/e2tnY1NHP0tjW1dTP09fQ1NDPz86qr66wr6qsgKW1r6moi7nExsO4vL28vLS2wczBu7Crn7OonaCxnNaFiYyIioeMi4mIg4KGioDt5+v17vf38uPX/vzw6fHv2ODi4tbW3ePj4eDc3NfXy9bf4e7o3tfS2NvVzMzQ0crDxLfmpdr0+fX4+Pr4/YOCg+3qg4SC39Tg2dTb3dzY3d7ZJtXf+4H1/oyOkZOVlJmUlJOUk4+A+ZKYnJ2bmZqTmZiWkpSEmpuahJuAnJ2foaOLj5WXmJqXlJiYl5mR8oKDg4SFgoiBg4SA+vKZmJeXl5SYmZyamfWSkpCQkpKQkpWUkfKZnJ2enJyaoKCfnoCoraqpqqmssa6vroekp6WpqaqrqKSlpoaoq6esra6vs7Kzr4ypqaqrq6uqpqemopGIoKipqKWlp6ipqKWAhqOem5aXmpiWlpibi4CXk5iZmpmUlJP71ri06fXy6e/g5efV09Ti5L+uq6uuuMDCvrq4wNDWyK/KxL/Atru7vLe9t6uChomNso/Om6jgrq2sr7K5v7/By8v2rqmxiM2Xkumbl2XLncPl4fKOo4fG4du5x9XV7qali8zuiqmUlMWAq7KSjLfIxrmCrb/PpoyJ8bmYnISJhfLf0o/i+YCBhZCXoNLI1OPt6Ofd1dPM0tLO1PeGgouL8ebm49nd3+Di4+KAgYbx8O+d+e/++/Tz/oGJjo+TlZKLjo+QkZGTnKWnqYK8mJqXnIHLuPSXzuvsipOan6Kgm5OPi9vXwbXWmZpKlbvi89Cvj7aDirW5uLq7t7a4vMO/xsKpuePhv8XGycrdw7u+xMzbybezjKrAs6+yv8W/s62pqaShoqWgiZroj5CUk5OLiIqLk5cIWFhXVVF7UlOFVYBWVVBQUFNYW1h9U1NUU1BNTU5mk2p/bldkPlanQnaKiYZxWntSVVZTUGLVa1pfaWFhaWtiZGhhYVxYX2liXmtvbGRdYWhvaGNudnVxcHJ1dXJvenRvbW6Aio18cHd2dG1veIVFRId4gYN6bm17gkNGfWZlc3Vye3p0eoKOfWVibzt9fnmAiI9KSYNsXV5iZXCJkUpNTExKgW5hYGJjZGNkdYVJS01JfmtiYGVoaGhnZWNhbYtTUk1+Z2BhZIVmgGVkY2FfXl16UVJ8S4SVTVBTVFRTUlFQTlFycIWCdWZzbLGhn4l+fZODZlloeXV0gHpuaW52dXp9gnZxhJ+wXF1hWp6oV1ZZXF1eXV9erKy1trJvbGxuamprbGtqa25sbGtmY1xbWl5gXl1cWV2fd3l2eHx7gnh7dnh8eHhzcnaGgJOfpra8ubm2tsDAwLy5uLCztbWtjpqdlJiQioqKkI2RjqSkpKOjnpqVkZegnZycmpygnJ6Zm5qXfIGBg4F7fnaGgXp6Z3R5fXx1dnZ0dXJ0fIJ7dGxrYnJnYGNwY5ZfYmRgZGBjYF9gXV1iZF2sqamurLO2samevbmwqq+rm6SlfaecmZ2goaCamZyXl5Wfpqezr6aemJqcm5mXmJuWkJCDom+NnaSgn5+gnaRVVFSZmVZXVo9+gnt2fX59fH5+enyLmk+WoltdYGFhXmNgYGBfXVpQmlleY2RiXl9ZXVxZVFdRY2NiYmNkY2JkZWZnV1daXF1gXFpgYF5gWZFKhE6AS05LTEtJjZVjYV9gYF9hYWRkY5pYWVhXWVlWWFtaWJhkZWZmZGVkaGdlZFJtb2xrbGtucm9xb1VmaGhsa2traWZoaFNobGdsbW5vcnJycFlra2xtbm5saGlpZlpUZmpsbGlpa2tsbGlVZl9eW1xfXVxdXWBXUV1aX2FhYFxeXZyAgm9xl5ublpyRlZmMjI+Xlnx0cXF0e4GDgXx7gImNg3CBfnl4cnd2d3N3cWlTWFtgg22UanaZdXV1dnd+goGCi42jbWpwVHdWZrJ1dlGaeJOnorBgbmd3e3dkZm5qe2BfT3SCS1AwQT4eHB89Ih4dGxliWmFdXFiijHp+aWppwa2AnluMn1FRVFpdY4B7g4+YkpKOiIWAhIWHiqVcWF9doJeXk4yOj4+Sk5JUY1WMjY1jqaCnopyboVJZXF5hYV9ZW1pbXV1fZmxubleCaGhpcmKdip1egpOUXGJnbG9saWNfXJKNgXuVdHBvj6bAnIFfe1Zcen99fX16fH1/fWxvbmMybYWHdHd4en2HdG9zd3+Je29u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18zUz9rM2KDR/f72+f359ff7gIGCg4ODgtjEwdbd2NbW19fU19vZ2tbUzOiDi4uQjouOjo+QkJKWk4OJlpmanZ2cl5qVl4CXlomGmJqYl5eXm5iZnKKhiImLjYuMl5eYl5aSkvP6hYOAg4WBhIL7g4TclpeZmJiXl5mcnZ2R5oCAgIGDg4mFhomH7JmbnaCfnJ2cnpudgKerq6usrK6tsbGwgqClpKioq6alp6akjKippKKfnp2bnJeSi46srK6urKqop6SgnoCFqampp6anpqioqaigjaCcnpuUlZmanZ2dj4OZmJmVlZOLiPCopaqj1unu7unw7+He393b06+qvsW8uq2zvMfQy8nHycK6xby6vMbDubeum4OIgfjq0tubwduvraiqrLe9v8LO1aPLys3G9Mmu5dSS7GS/gdrb0ZKKhLSgzsTPyWPZ6aSmitT2kKCwt4GK+ZS3qYDw3MfFt6WmscHJw6qPr5GNkI2BkYaFhIT+hIiYqY+9wcHIzfqChIXo38Oys7W7xNH7i5GLh/nw7uzs5v+Civfz8pyC/4WGiIiOipGUl5OVlY2FkG6Rlp+e1KaRlve/mb7rhYmNkoyHkJ6hoqCko6arpZC0uLm5tKiQ78u2s6nwroa1uLKno6Klss/Vy8LDw7C/7M3KzNDe5NzOxre+xsK3usCUwMG+vLm8s6ajpKmdkY3tuIP8qravr/OOko2GjI6Ji4BSU1FUUHhSU1FQUlVVVVRTVFNSVFVXelFUU1JRT4Z3maqnh2ZbNlNOZF+Uj5WEXohSU1dXVFBf5mtbXWlhXWVoYGBla3BvbWFXVVtgaXJ3c2xeW1xfY255gXx2c21oaGlwfX9wa3Zua25ucXp+e3V1enltc3aAgnx2dHR1gHtzb2B1ent6endyh0lHfnB1gYV8fYF/hIaLSIFubn2LjY2Ki5FMTktHgXBnZmt6h0pMTE5RUk9Kf25uZ2tub3B2kVdXVlZWVU6AbmZlaGpra2tqaWdogVNZV1dYUoBSg4uXT1GGUj9Qm153omu+vIB6wIlzm5OtYItyhGeoenRoZmpvg4J2ZmZqdoaarVimpKKeolVYWFhXWVteWqmgna5jaWtqamuFaoBrbm9ua2trZ2hfX1yrW1ubfHt+dnl6fH53eH16gXt6cXFwc3dzcG9ve4Wlrru7YmG4vby0trOfmZ2clZOUioiJ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0863t8O7xcbEtq2PgYXzgIyL4pfA4auys66tu77B0NiA3rzS1tnLute2zLvKiMG7aOG4npDUi6fHhrTRzNnkoKKN7IGR16WN8u6LioXm9fnu2bOs19Pd2tva0MWW78Wnope1zZSD2tzq84WWsLPJz9DOzM7I4YSIgoDfyL68uc7m7vSAk5yanqKUioCYhYqLqof/gvPz9fj7gICBm5yanJ5ynpqKjY2Qkt+JgtukzJKcmI+NjZGam5ibo6ekn6ioqKWDja+3uLqjn5ORkY2Mo7aR1qednZyfp7PH2+rQy8fHuNfs29bR1tzZuba1u8XFtr/K0qy6sbS0taWaoqmmiNqZ8uXg5urt7oPErKXJiZCRmJ+VgE9KUldUc0tUWFdQUVBRUlNTUVNTT1F8UlNSUU5ej3N9bldxTGJOSFBWmo9VTGGIV1RYVlZWU2H0b19cZmNbYWRfWl1ibG5jX1dYWV9kbHRwX1paXF9jbnh6dWplZWNiZ2Zyd3VtbWZnbW5xdnt6f3BsbWtwdHt+enl/dG5rZ2xsgG52d32BeoJ8c3BwbW51fnZ6gYJ8fouFdHF4eX6Bh4yPjUtOT05Kinx3e4KMkEhKUFJNUVJThnt5dHB1iEtJTFBWV1VSWFhPhXdxaWxvcHJ0g01SWFpYWFpYVU5/Vn2CkU1QUlJSU1NRUJ2hbGtuaXfXnYuPunS+fX2Pf3h1bcjBgGp9Zl9iZGhven6Ii5GToaCjVVVXWVhZWFdaXKiioaKgq1tlbGlqamZpZ2dnZmhqaWZua2poZWphoo+Aenl6enRwd393c3N5d3Z8gHt9f4B/eHFudq6np5+lo6GkpK2sqJKenJKamJKQj5KempiQmJWUj4uNk5ycnZ6amp+goJycgJWSj42LaoF9gYOAeXyChYeBe3J0d3uEho+MiXtwbWZmbWNiZFxXUlBSV2CPrrOyuLKwuV+4vV9jZmhlZWZfvbazsbCwpZ2npKiqra+or6WXh3Jpo6ifnaWnnqKjo5+iop6XmZuUh5WkcI+pp6WpoqKdVKSeoaRQnVBQnotxb3Z5gHV4eH6CgHF4enhxfXuHVlVRWFtdWlZZWllYWVxaVl5fYGJiXmNiX15aXFhSVmVlY2JhX2FiYWJkalJWV1dZXlxVVlVRUldRiUtMS0pOlJSTSkxMjldhYGJfYF9gY2RkZpNHR0tNUVNTVFRT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vMK9zdbg39nO1NHSy87U2eHq59nPx8jGxs2A1Nnb3tzUx8O+wsbFvsXO2+Tu8evcyL/FxMvV3OHo9oGCiY2OivfW1Njc2t7r8fmAjpCRkpKTivrk39/ugYSEhYuUmZqcn6CcivDZv7G92/mGh4eKkZmZnZ2Yk5iS+7fn0+n5g4aHh4eEgYKCgIeL25j/qqzlm6KP55iVkIjpzsmAiIiYnJynr667vc3Q0dLN2O3++Pr4/YCAg4WH9+/u7vCDh4WFiImRkpCOkJWVkZCSk4+QkJCLjOvd4OPb0su+tLW0v766sq+srri0triywLets6zh6IDw7/ju8+jk5+Tb2dzOzMfS0NDQys/bvNXWzM7MzcrU0tXT19bU0tTW0tCAy8rHycOkuri2sa+qr7Owsbqu1NXZ0+Hi2NbLw6+mrbGpqKqzt7y7zdLq9euPlZeTj5KQlJSRlZWTk5GPh4b8+vT4gYKD9OHs6+Xeybilko6OiI2Pytze2tfZ29/e2tnY28zO0eSc1fv+/4H7+vj4+f/+gP6A/vrz9dW7xMXNysyA1NHZ2NDNy9DX1s3d/oiGh4WMjIyIjImNjouKgYuUlZSVlpybnJmYmJeS5v6RmZuZm56fnpycnZ79iZKRlJiWjpKSko2O9+X+/ID/hP3+gYKFhoHwk5OWlpOWlZeYm5yZ1/eEgoiNioiLjo2O55SZmZedm5ydnJ2cm+P9/IPw+oCA/YGChITYnqCkoqKopKWmo6Oh6YeIh4mHjYyMj5KL75+pqKqrq6uopqWin4Gjp6imnp2hpaWlpqSGoJiMj46Pjpebm5yYiY6Uk5CLq5yPhYaJmqidy/ft6tW+v8PEzc3U0s+4usbHvbK8ztPOz9nIxby0rsvOw8K+sp6OiIGFnseA9pfD1aWqq6y0wcHI197bvdbT08W0vceg1IeN/aOYbb6cjoG/laOUvYy8z+SjpY7ihZqy4ZPJvLy6huXh6cGfsODd18fP0tXLnYL39PTTt7HEke7e4Ozw3+L6/r+/wc/OzsjS2eDi6YiEgPvswL3Ezu7x9/6Bg4eJ95aHjYimjIGAhP39/vz/goWFm5ucmZudn5aZnZmTqsev9qCjmJCNjpCTlJmfnZ+foqCbopudmuSasri1tbKlpqmnqqS8vPDWtK+5w8vD0+b86tzV0dPE1svGw8HM38PBv8TQ38bLzL3LkKSws7ipl431zIzy4uLh4+jr7O3r7fP7pseuqfyRlJ+AYVxVTlB5WFVUV1hYU1BPT1FUUlNRUHlSVFJ9Y1JcqIpnaTxXTTp0UZyOaFRhek5RWFpYVVVSXoxvXV9kZ19hYl1eYGJeXF1dW11dY2dmX15dXlxeY2dsa2dnZ2ZjZGVsbWdqZ2ZlY2hlb3Z7enVtb29wa21ydXp/enVwbG1ra3BpcnZ4ent1bGtnamxsaG52fX6Hi4Z8b2tvbnN4fH+DjElKTk9QTYd2dHh7eX2GhoxIUVBRUlNRTIp9fn2FSEpKSk5UVlZYWltZTod9dW9zhJJNTUxPVFhYW1tVUFdRiGSGf4yXT1FSU1NShFA6Xmy3e9qQja53fG62eHZzbsSvpmVbZGhobXJwe32JjI2Oh42ZpaapqqxYWFhZWaahn5+gV1pZW2BiaIRngGtsamdoaWdoaGllZaelpqeil46Den18goF/eXZxc3p4e3x2gn10eXSboFmlo6ifpaGgo5+Ymp6XlY+YkpKVk5ihh52fkJKRlZWenZ+cn5+dm52gnZqVlpOWk3qMiYaEhXyBhIKCh3uNhoiJk5KNjIJ8a2dpa2hoa3R5fH6NlKatgKhwc3Rwa25sbmxqa2xqa2ppYV+3t7G0XV9fsJ+jpaiijH1uXllZVldgmKinop2anaGjn5mXmouTl6FtkaqtrViqpqKio6imVKRSop6YmIVvcHBzcXR4eoF+eHVwcnh6dYamW1hXU1xcW1haV1hXV1hRV19fXl5gZGJkYl9fXViKgJ1dY2ZiY2VlZWNiZGWcU1pYW15dVlxcWlZZmIeVlEuWTZOTSkpMTUqWXF1fYF1fYGJiY2RjeoxKR0xRUVFSVVJUjF5hYmBjY2RlY2RlZYiQj0qGi0iPSUlLTX5eYGJiY2pnaWlnZ2WDS01PUU1SUFBQU06FZ2xqa25tbGtpaWdjgFFoamtqY2RnamppamhVZl9XW1tcXWJlZmdhWFtdXVxXal1VTk9QXGRcfZaRjoV8fX1+hYSLiol8foeHf3Z9h4mHiI6Df3hybIOFfXt5cWRZVVJUdJO5aoybcHJ0dnuBgomXnJuFh4KBenF3fV52TGDAf3JWlnNsX4xrbGSGY2lrgHhfYFSKTlg0dVEtJSIhP3ZncHNnZmliW1JWWVphaFivsbOijYZ+WIx/hIiJgoSTnHR2d3+Af32G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zPDu9vr59u3Z3Njd4dHf3NTY3Nvb2uHj29PNz8vGxMO/wsDGw8XAubq8vr+8uIC5u7exsJi7uLStqqOlp6OfpauIg4Pw+/6BhYSKjYeJ5IiNioeJhYWChoKDgYOanJqbk5KXkYuHh4WJi4iHiP38+oWPjo2H0L+slI2UmJiSjoiFjpedu8nZ1Nje29vl3dfWga/G7v78+vuA/YGB+Pn9goH9/Pzy2se7xMbMzMrIw4DI2NXT0M3V0tbT9YWDgYaIhIuMjIuPjo2LgYORkZKUkZKTlZqem5iXjeny/Ofp/YWUn52cm5uf+42RkpWTlZOTlJKQkYzngv/9g4SH/f2ChYCB4ZKUlZaWl5eVmZWam/eAg4WGhoeKio2MkJDjk5SYmJeYmZydnZuX4YT+9/vxgYCGgPuEhNyYnZ+fnp+go56boaPwg4eFiIuKi4uQkJGIg6mmqaepqqemp6emmoenqKijoqGhoaWjpZ7olpqen5ybmJqamJeYiYmQkb+hn56PhYWGmKOZtcTK0MjDwMTKx9TPztHCt7S50Mnd1cPHzsvUx7u9vLS5vLm0m4r8/Iqrk4CElL3asbm4uL7BvtHe4dLP1c/OwLG6uLmG0bD32Kbjab6B1ZyP6pe005eaz6WkkOiLmbbEycG7u77frbeqpZq30b26y8nVzayZgf/19vjw+OCElI+J4tXp8Y6wvMLS3NrZ1tjdy9rd4NXW1OKDhIWJjID/gYGB+/frnpSUkquYrIC3vLCmo5uio6WmpqijqKiqqaqsj8Tn0p2mq6eglpqcmaCkrKyrr6Glp6emoaWl3qezu7S7u7y7tbGvpsDIt7/W3t/i4t7m7+zHxMC/ysG1wcHDyODY1dbZ4N6/vLy8u6n6nayrjOqwgfLb3+Pm5ufj4+fq7/Hx9PT19oHFtqLlmYBYWV9hWHZRVVdTUVRWU1JPUFJVVVVUelCTkZGJhXVXdlQ0UUdaXZyamYBaYFRLTlBZWllYWFJf/HFbX2ZpYmNjXWJfWVtdW1paXmFmXV9jYmBgX2ZpZ2hlZm1laWtrbGtqaGpvcXNrZ296d3V0cGtxcnNvcHR2dnNwam9xcnBucoB1e3xyb29ra2toa25ta3d6d3V4cnZ3d3J0d3p5d36ChIlGR31qZ3FtcHN2enZ+h42LSlNYVE6HfH99gX+DgIhJSk1SVFhbXFxbVUmGiIqO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h5eZ9Y+TlJKSk5GRlZWUkpDohoiEhIaFiYP9gPzy74aSjY+Pjo6VmpmbmZPchoSHhYWJjIqNiY2I7ZKUmJuagJWYmp2bm5Tmgv/87fP4iIaFg4TYl5ebn6Kio6OgoJyd9YeHh4yJiYaKj4yPie6mpqalp6amqKWio5zyo6eppqWioaCfnp6gk4WXmJmbnJ+al5eYl5iFhOinn6CgmY2GmKS4ysmyx8TFysfDx8/L1dLVzsy+z8zCwsjW3NfT1tbUgMfCxLe3v7ulkoT/k7aBlJzA2aq3ure/wL/c597fpc/Ixb6ztbOzuOK9ysjRneimbr+ploKEqZyliovNmJP1kJ+4tby7ubeyktymr7CtpL6mo6yto5WElvv59fWB7/3gx56gltjK3IGXnZ/P09HSzdXX1NDe4uTm5+fe8PT5/5CjgKCfoZuCgN2egoaKnY2Ik5msub7GzMbDurq2tru6ubzGpcDVwLSzsqurrK+usayzsrKvsbKysbq7ucHM1aLb3t7b4tzN0Nzb1sbYz72xs7OxucnS3/DMx8HDz93Gv9LY4vDpzcTHzM65tbi7vq2e39HGkfPn3d7i4+Hf3uDg4+XoBezx8e/whfIE87PFrIChYVtbX4ZXT01SVVJTVFVWV1RUWFhUdV2SaG28mm5uP1tOPkFPnJZcT1xcSmlTT05UWVtaWFNfgnNfYWVpZWZnYFlcXlxbXVpiZWRfXmVkYWRmbWtramxtbWltbnJxbmxtb29ucXN3fHp3d3dzc3ZzcXJ1dXNubm9zdnh0dXVydYBzcnR2eXJyb2pqcHBxc3R8c3Fwb3J0dnNvdHV2fH19fHt2cGphamhmZWVpc3d9hImGjJBJeXFydXV7f316e4SLjEtLUVZaX2FeWEh7g4aEh4iLh0ZNUVFRUlZZVFZVVFFSTX1Uen+OmE9QUVFSU1GdnJpphZWbeHd1c3Bsw49nbYBrd6JebHyDhomOkYmLh4+KiYiIho+gqq6oVaadmKKenrBbX2BgW5yqrqiuWWBjZmprbmtrZ5ycoqWkqK2trq6vtLe6p7i3oZCHfH98iYaBgYiCraamp1tdsqyopaSck6appaaloqutraakn5yXm5idmJyYi4iTmpqQgZCIhYGFhoCAhId/f3mJjoOGhoWIjI6JiIOHiIqHhoSCfnd8cXFyrWNjYmVhZ2ZkY2VmY61wcmhp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fYx+in0Wfn59e3x8fX59fn+BgX9/f359fX5+fYp+j3yEew58e3t7fX5+fn19fX5+fpV/h4AIf31/gICAf3+UgAN/fn/KgIR/mX6DfwICBABT0bbnmozboKKSh4SLkY+SlJKVl5aRxPdmasaiboVNL15JWWrBy9q+obCT5vTykY2Nj56Um56YoE+1pKmxtbG2ubGstbe4vL+9trKss7e0uL3HzM6EzIDGz9DS1NTW1dbV09HZ3+Hm5uXk3eDa2djY3+Ld2tnY1dja29/g5t3e39bU1dXW1dLMzMrT2+Hc19nR1NLPx87V0c/Mzs7g5trV0NLUyr+7u8LKxdLT1+bq8PLp1MPCy83Hx9Pb5evh6Orvg4mMj5OWj4Xm3Nrb4Ovu8ePq3NzyhYCMjo+Tm52em5ebnp2T96vmy9fs9/2ChYaDgP305IWYm5ycm5ilvd2Jg4iymrGjrLy/09bR09vY0srL0tPT0fPw9POB7NnY2d3t+oOIhvbm9vP0+P3/+O77+ZGTkYjj4uLc3t/h5OTn4ujt79r5jIuKi4+Lg/DazMG4uu75goeB+ID0+/vw6uzk0fLy8u7p6+ro6+rh29fa19/e09DGy9fU0dO4xcHBw8jKxr7Dv8O8s6GfoaWusLW7v8HAvbu5trevrKispKqlnpaIh5aTjZGOjIiHhoSFhPyTkJCWlZCTk4qNiIKB793Cs52OkIaKiYaBgY2Sj5CUnZmUkYmQiYuYmYCy4uL6p9aBhoaCgfr7+fDY7vr3/PyA/Pf7+tbU4OLo07u9u7y/tsDDzdDNys/Iz+768vKFhYqJh4SJh4mHiofy7vv7/Pzo64WRkZKTlpSW8ev27vT78uv2/YT48/jz4ouQkI+PkZGSl5WQlZHjg4qKiYSHhoeD//iB//WdmpuanICVlo+MjJGM5oKCh4iGhoqJhoyMj/ONkZCWmZeYmJmVl5mE+v/59fj394GDhYGB2ZGXl5ubmJ2enZyam4WAhYeMi4mJioyOjYnykKKmnJuWl5qWm5eYn4CmpKOkop2enp6in6CHkJqYl5qbmJmclpeXl/DBm5uYpLfN1tfQ1dzU2ICqu8DAwcXEx8zSzMnW1+PcxNzb093c2dbR0dHPxczJxrnIq5uOk7W4opyix9iltbq7wr7H3N7TiPzF0M29srKxsbesp6HXm7v6xYJrY/XWgZOkorGOwY7G7Jaks7q8tI6+gN7Yj56uwci8x7P3u5eLj5eQi4j19PuK+YOkrbO0poCVm6Wq6tXg49vi493d3d7dzt/f4OTr7PD1/IH/gIWOoZLPooGHh5eI/oODk5mZoKSkpIuUm6Gkp6iptt+1lI7IyMvKys3Cvba7v8DFwNje4uLp3t7c3tCIrKmopaOioaGyv8CoqaSeoaewq63H1OHmysvR2uj5w8TLz9rYvbq6vwvBr6qvtbeV47Lr84TcGt3e3t/e3d3i5OXm6erq6ufr7/Dw8O7w7vKrgJeAmmdVgl9hVk9NUVNRU1RUV1lYVHOoVlueh2JzTzZSSV5jn5+TcFdgU4GKiVJQUVFdV1teWmGGbl5hZWllaGtkXWFhYWNmZ2RhXmFjYmRnb3BwcXFwcGtzcG9yc3V0dHRwbnN4eHt8enh0d3NzcG50eXRycXBvcnR0d3d8eHl7gHNydHJzdHJxcm91eXt4dHdyd3Vyam93c3JydHR9gHh0cXJ1cGhkYWZqZXByd4SEh4qEd2lpb29ra3Z4f4N7goSESU1QU1dXVEt+fHt9fIOEiHqDeHqKTE5QUVFYWlpWVFdZWFKJXoF4hJKZnVBTU1JQn5yVZHd5enp4dX6JpG9tgHCLbnpuc36BjY6JjJCPi4eIjIyMkK6qq6hYoZaWlJilrVpbXKigr6ysr7CzrqSurGdpZ1+aoaGgoqSlqKmtqauwtKW6aGdlaG1pYa6bjYR8fqmxXF5ZqKStrKSgp6CStbS0sq6ysbCwrqKblpmcp6iZlouNmpmZnYeOiYqLjpCLgISLiY2FgXd2dXd8foKGiIyMi4qLioyHhIGCfYF+eG9kYmxrZmpnZWJhYF9fX7ZtamhrbGttbmZoZ2Nisp+JfWlaXVdYWFVRUVlcXFtdZmNfXVVZVFVjZYClnqtzmFtfX1tar7CupZegpaSpqFapo6SmiYuWlZiIcXBtbG9pbXB4gHl1dXd0fZeenaJbW11dW1hcWlxaWleYkJaSkpiPklRbWlxdYF5hlIeNiImOhoCGiUeCgYCCiFVZWFZWWFlaXVxZXFmJTVBQUExPTU9MlJBMlptmYmRjYl5eWllaXl2MSElLSklKTE5NUVBUjlpcW15hYGFgYV1gY1SUlY6LjYyJgElMT0lLflhaWl9eXGBgYGFfYFJIS05SUE9OT1BQS0uEXmlqY2NgX2FeYmBi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gouN/uHS3fPs3tnb3ej169fS19jd8IWNj5OZnKCgoJ2dnp2Sg8GAxMfX7v2EiYeE/OKFm5yfnqDD0Mims65LgYqezMnAsbCywMXJztfR0NDYyvKDgoH7/t3Q0t3g4Pf7g4Ps6/X19fb7+fP29fL05Onr0e/z7+7s6erq8ubv6/Di8ouLjY2MjYiEhIqAhPv+gIKD+PH4gvn57+Pb3efs6ujq7ezl5uHe29fe29nX2trY0dTX0NS20M7K0dfSy8vR09DNuqakpayurrG1v8DFwr+2s6+psqqrrKmgoqCfgp6amZOXlpGNi4uIi4CPl5iYkJKJgPLp59nVq46TmKCUloSJiYGLi4uWjZCVlp2AmpOIiIqWlqSAwfaNi4aDg4CC/vr7/fv53+D4+/yA/OvX3ujo7Obl7+TFt7uzuL7MxcbGw8vvgf/+gIH58f+EhoaHiIeJ9eX29vPz+/389ezi7oWVlonf6/D19vD49vaAg4GB9vHmj5COi4uMkZSXlZSWlPOCh4qJhISChYqFhYGAhuOXnZucnJmYl5WSlpOV3YKFiISDhouIh4qMitaJio+TlpeYk5WYlpX0/v/x8fb3+fiBhoT+2pCXl5KXnZ2bmpeWmJLnhIyJiIiIi4yJjIyH5I6SnKCioaKio6agooOapaWio5+enJyeoKGh+ZeanJiYmpqZmJmWjYaxq8/d4OOA6+rg2uDW1c7NrrHFw8bGycvDv8jRztbj6dPM1uPd09HQ2Nzc0s3Q0uDUqJmbncW8m6Spz9Sptrq+wMXV3NW4iv7M3eDGsLaysrWty4D8zMKy38DKr3PFm/zh/9KtsuGjjOahtKTngNDR6t7yuJDJyZrW24KRn6CC/6WV9Ovy9feAifmvq7m1s7eysbC1vc7c3dbg5OLl5uTe1+Di5eXk4t7w9/yBgYOE//a8p5mYlr/6tbqjnp2anKalop6jqKaXnZyawsX7mNWkqK2tpqimnqChpKOkoKirp6ayub7Bwqn/qaGmra60vcbMzMyywaqoq7jO3+fs9YXt1sPCzebOnKwxvcjQtbi0s7i2rKmP5p/v6djZ3t/b2NXT1dzg4uTn5+Xl5ufm6Ofk6Onr6uzw7+3s7S6jdqKBiIdaVVhdU0yUUFdYVVRWiHKWTYtyU3ZoPFtRQEVSp6NWkFlgSoCDVUhOhFKAY1lUU2KOb15gZmloa3BnY2VjYWVfYGNkY2ZmampvcW9xdXh6dnZzdXN3dnRzdXh4d3h5eHlzcnVzc3l4d3V4d3l1dnh2c3J6fn98e3t8eXlzbnF7fXVwc3Z5dHN2d3l6dXNvc3d1eHFze3d3cW5sbXBzb2ZnaGpwdXd6hod/eXWAc25ua210cnN0cYCBhoaJj0lQUZJ/c3yMh355enyDioJ5dXp7fYpLT1BUV1hcW1pYWFhXUElpSnV7iJKbUVRTU6CRZHt8fn19lZOKcoOEX2R0j4uDeHd5g4SGiZCLioqQiKleXlyztJmPkZudnKuuXFyjpaysrK2zsairqaepnqGApZOqrKuurausrbGpsq+1p7FnZWZpaWtmY2hnZ2hiurtfXl6vpq1cqq2nn5mcqbGsrLK0ta6sqaSgm5+anJidmpmSmp2ZnoSWlZKYnpiQkJWWlpOEeXl4fn+AgYSMjZCQkIqIhYGFf3+AfHV4eHdfc3Fwa21raGRjY2FiWmtxcG+AaW1oYbasr6WeeVtfY2deYVRVVlBYWFdhXF5gYGVjX1RXV19eaFeIrWNiYF1eW1yxsrGwrqycl6KlplSnnI6Pl5abl5afk3xubGdsbnVvcHBwfZ1Vp6ZUVaSgqlhaW1tcW1yejpGPjY2TmJmUkYyVVGFjWYSFiYyNg4uLh0ZGQEKAfH2EWFlWU1NUWFtdXF1eW5FLTlJST09OTVFMTUtQiWFmY2JiYGBfX15hXmB7SEpLR0dLTkxOUFFQhFpYXl9gYGBdX2FgYJuUlImIjI2Ni0pOTZR+VVlcWV1jYF9fXV1eWoBIUlBOTk5QTkxNTkmHXF1iZWhlZmdoamZoVGJqaWeAZ2RkYmNkZmhonmFjZGBgY2NjYmNgWlZvaYSRk5KXlpORk4yLhYFxd4F9f4GEhX9/hImHipebi4eRnJmQjYmPk5OKhISLkYptY2VmgI1zfnqWlnF6fX6AjZufl4JZooaOj4BydnJzc2t4Tpt2bWmUiZOFWI1vva3Cm3t3m3dhiV02NCZHRoeEg3p6PR0dGx0/NxsaGhkXYmxdraqzs7FisXpudXJxdXFxcXR5g4qMiY+SkpeYl5KIhI+AkJCNmJ+jVVVVV6mmnm5gXl19sXx7bmdnZGZvbmpmaW1rYWVkY4qerWWVb3J3d3Byb2FjZmhmZ2ZvcG1te4CDhINxq3JkZm1yeYGKjY6NfoNsbG92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8+Do8O3vn5SZkJSVnJ6gkY6SkJOSjZCVl5KTm6Hrqt5hhYuJjIuJhYOBgP7+gPv/+YCC+Nvu6+De6O7t8PHx6+jq7e3lu669wMDAu87s/f+AgoOChYKF++zxiYuLhefp9vT0+Pb9/ID99fb08+jc4/br8eb29O34+///+vfp8I6RjoSNgJORk5OSju39g4iGiYSChISJgP6A8I6dm5mdnJ2bmJqXkpfz8YOKhIGCg4iJi46MiOuWlZCRi4uOi4yOkIzl/fn38vj9+vb8gYaB4vaJhYGFgoCChICBgoHkiIiGiIqJhoeKiIiF5pugnJufn6CdoKGkop/9oZ+foJ+enp2cn52fgJnwmZiam5qYmJeWl6Tezbzc5Obm4N7Y39vR0MnJwbSy1MzGzdPExMXLxM7b4ujRy97k3tbV0s/W1NjW0+fYs4+Il8S8q6mk3deotL3HydXd1tPZgofH0tLCsq+vsbSx2bfQvq3TkLCAu/dtZc2vvJf7trSksJaYstHX2+bm8+jqgOTdr82Sxd/g3+jf2K+jsp75+fr5kICQrcfIxrSQmbO9y7bd5+jp5Nnw9Pj29/j26fPv7+v4+P2BgIKE/umxnZaXmKWpmKvK2dnc097b2dTV2Nizq6b7pNHihM+kqa6pp62yr7GzrKaop6mrrbGuvsHEw56dxdTd4vP2/v/17NnCRdLDvLmwqsvOz9PVvbG1vMrNs52svcquqKqoqp3uxIj02tfW1tjW09HS0dPW1dng5eTj4uDj5OPj4eDg5ebm5uns7Ovr54CdoVh+qHyHYFhQWV1WTExVVoSElX94xZV1ekhmVkxfZKKjmW1WXlqKfoGFdUhLVFZXVlRlZl5imXReY2draW90bWZmaWRgZmVjZW9vb21sdXd1enl3e3p6d3R4fHp1eHt9dnl7fXx8dXd1dHh4d3d5dnp8dHd8fXl5fH15fH19d4B7d3B0d4B8enl6eXV2eHd0eHN4fHp5en2AfXd0dHRycW92bnJ0bmhnam95g391cXJ5fnpzcnR0cXV4fHxxd4mMjY2MhHp3e3h1e4F9e3p6e3mAfXx1fHt/jEpLSlNVWFpcXVpXV1hRTIpthXJ1gZNRVVVTmm58f4KIlpaLhYRmX1Jsb3CNmJqVgG9paW56i4SHh5u3tV5cta+nl5eTiY+wXVytnrK0XLCur7O0tLOwq6igmZ6k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4trG0trd4OTf3tnUz8S6xsbBudvb0tTHyL/AyOPT1dbV09De3N7aysvMxNLW2fLgpY2Ghre82ICmq+vSpKq0wsLc39Hy+/+CwczJvKqrqamsquG0obWXism+rvCUrqN2vPLTm6m9uL+iioza7f2EgYHt7/H2zpnI1OPi49jY1JivxcCsqI6DpIyUmMPFy6COi5KvxqTy3Ors6+f09/T0+Pv/94KAgICBg4CFh4mJieColIKEgouN+oD8iZ2ipayytLauusLI097iis7az7WQ1t7fzdXb2eDY2NnQ2dni5fLw+fD4gf/f8YP89eri1cvKysallaSgnJyhpqqyt8a+uLW5v8y4tZepwrmtp5ftsfyD5dbT0tDQ0M7Q0tPS0dDR09PZ4ePh4N/d4N/g4NvY3OPj4+Tk5ufn54CbnKRZW4GqgY5gVFJTVVWEh494crCMxX9VN1pVQEJTq51XjFteT3+Cg4ZNSU9PUllaWFlaXlpbiHNfZGZqbm52bmFkZGdtbWtvcm9rcXNxcHd5fH56enl7fX1/gIF8enx8dnp+fXp3eXd1cWZzc3N0dXZ5fHx7eXl3e398fHl9fYCAgXx5fX15eHx9fXx8fXp1dXqAgYB9e317d3N3e3V1c3NwcG9yc2pncX2Fg3x3d3p3enx8dXNzdHR2f35+fICGjol+fX56fHh4dnV3gIB8eHlxcYGJjYKGho2JSUxRVVZWWFtbUZCSTEeHiXdah3V+kqBRlGx8gqissJ2LkINluoC6tWGSWVmsoZeHbmZueXyWsri3XblfYKyhn52ZlaCho5GhXF21trKzq6eoqK6yW69csKWiqWVfXJuAbV20vbKsoGRlZGVmZWVmYmFgYGNjY2BjZGNkYby5vLq6u7GRnZuanp+jnJmao5ienJ+mp6Krq6mgnpuhnqCFnKGipqSln4CbnJmZnZFzhoqJjIqNiYiFhIWBgH18eHR0cXBvdHRwamq2YmJhZGJhsraxsrlgqai/ZWRjuMCprLG7tb2tZmJeX2BmYmBiZGFhXFlaV1tnmnWaX2dlZGRkY2NgYF9fX15fW7ZcXFtbsKmTh5aiop2anJ+am5WVmZuZlZKKcGpsfkaWoqOfnVRUo6NUoaObn1GXn5SRl5SWk5KOjY2TlJWXl5mReoF/g4B5foOHjImOiYmFgJxWWFdUVVZYWllaWlpZmk9PTFBP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t8PCy8HEw8jHztPIw87OxsnQ4eTd2t3j5OHo7fDq6OLj4eTq7PLy7O7t6OHf4eLi39/i2+Tl5eHh4+Li4uHi5+bk6+SA5+Th3+Tl4OPl4uDh5eHb3uLm5d7h4N/a1dzU0dfc3N7e1dLU1+Pr8fLs6+rj6ufh2Nbd39nRy8rQ0tfc6+rt6+Xk6eDo6ePXx83CwsvY0s/Y2NrY4e749vX4/P+AiImJi4eCgunc3+PqhZCWk4rYise3v82Gqdvg69jFxtK4gueA7/+l64SHhPfy3tW/n6zX94SFhYP77eHX1dDN2O3v8fT77u3m+fX2+fiB/vj29O3t9vb69vH9j4+PkY+O+tSq+IKCg4iKiYWEhIKDhIWBgICChYaGh4WChIWB/vrN29zT1trk4t7W0cvMyq6or6avsrW4uLe5v8LJsMzQyczd4OCA3tjRzc3KpL6+tbizuLWvrqiqpKCdm5qamJeRl5STj4uQi4qHjZGLiIiDhIGAhPHahYHz/PmB+/X08eXp6NGTlZKZnpWkn5ybmJWy6JLchomIhIWKio2Oi4aGh4aEiIiGhYOAgoL64Nfj8fLz9PHq7Onm6uTs6urW3OPg0M/N7PKA8O+A+//7gP6A/fT7+Pv85Nrs9PLs8fD18fD18vX0+Pbk0ejn4t3h2uzy6+3s7fP354OOjo+Pi42Sk5CQj4+ShoOSlJianJyShoWEgfX/25udnZqdn5ubnZ+enZn+hImNi4qIiIyIgoWD/tqQkJGZlZOTmJydm5iZ44H+8e3r7/mA8fL3+Pvv64mIh4WBgfb7jYWHjemDhoiKiIeDhIWHjoqC65udlZOZmJmbnJ2hoZ/3n56emZuYl4+NjIuLjf6OmZmXl5aV8Ofr2trKybTP2dzb3dHXz77C083Sycyy1NDGv8DExMLR1d/f2en779LZ2s3X0cnRzNDx46WKgIKvvOGAprHq1qaeqLzR19CV8PT1gLbKyb6sqaajqavxtJ+kwfrNs9ONxdGZ1M1yuYjw4r2hubGG6ov67oD38t7b4PXHiZ68wcG3qOfazcnAv7uKhZWGko+5u62HiImNrbSH5uju8PL7+Pbqg4SD/4SFgv6Bg4WEh4iJiInbnI2JiYmLjftm/oylp6iurrW4qbO6u72//anW76SP8o6pqZ6bnqCqtbq9vbq5u7K+wsK4tLe57pykra/EwsLDura2sp25vcClpaiqrr/As7OwvMW9sbGTsbmD1Zny59vb3dzOzM/S0tTSz9LS0dHPhM4W2N3b3NrW2Nra0sG+0tzh4uPi4uLj4YCYmZtSV1pci7F3lVxUiJqLb22phq97ZzthWEtaYaaknnJQXFmGdniBhIhmTlNRUVhcXlxcXFNXhndiZ2hqcG90bm9ucW1ydW1ocXBsb294e3ZzdHl7d32BhIB+enp3en5+gX98fn98eXh4d3d3dnd0e3x8eHl6eXp5eXd8fXyBe4B+enl4e3x5fH17enl9enh7enx8eHp7enRwdnFxdnl4eXlxcnN1fICGh4WEgnmAgn93dX18endzc3d4fHyGg4WBgYCCeoODgHpwdWxsdHh2dHp4fXuAiY+LhYqPkUdMTU1PS0lKgnp6fYJKUFNSS3JKdXJ7h2aEpKCnmImJkoBeqYCuuXikXF1craqcloRteJ21YGFfX7WqoJqWlJObpqaqrbSnpaCwrq6uq1mtqKmpo6Wvr7OvqK1hYWJkYWCij3WrXV5fY2ZnY2NiX15fYmBeXmFjZWVmZWFiY2G9u5SkpaGjqLGtqKGfm5iYeHJ5dXqBgYaDg4OLkJR+mJ6YmKOkpYCkoZuVmJd5joqDhoKFg35+fIB1dHJwb29xb2xwbGtqaW5pZWFoa2ZiY19hX15isp9mYbbCvGG4sLW4sbSwmWFfWl5jXWljYmNiXG+MWZxhYmJgYGRkZ2ZlYWFhYF5iY2FeXFpbW6+YkJafoaCjoJucm5qelpyYmYiOmJSHh4qZnICanVamp6FSolKelpyYnqCOhY+ZlpCUkpKMipOPkpaYlot3fXx4d3t2god9gYGEiY2FUlpZWVhUVlpcWFhXVlhRT1VXXV9iYltUU1FNkJWDZWZlYmVmY2JkZmVmZKFISk1OTUxNUU9MTkyUhFtaWmFfXmBjZmZjYGGKSpCGh4SHj4CHiIyLj4iLUU9NTUpKiIhOR01ViktOT1JPSkpKSUtRTEWKZGdhX2NgYWRmZ2pqaaJnZmZhYl9gWlpbWVlZnFpfYF1eX1+blZqNjIWCdYiOjYuOh4uFeHyGhYmDg3OLiH97fX1/gIeMlpaSmqmhiZCQiI+KhomFh6CVallSVHmWsEKEg6SWcGtxgI+Wk2udnJtQe4KAeXBwb21vbo9nW1tokYNqd1R8onKboleGY7qohXaIe1usaKWEQ39+eHt+gDweHByEG2w1c2VbV1diX2BwY2lgenxxUVRUVnF3WJSXm5uepKKhm1tbWKtYWVeoU1ZXVlhYV1hYkn5ZUVFRWFyYmlltcXJ2dHp+cnh8fH+CrYGXqXJhpWFzcmhmZmRsen6AgoF+gHqDhYV+eHV0lGJqcXSEhUR+e3p5aXyAg2xrbm5wfHNkZWJqcG9palprc1WMbKqglZSUkoiJjI6OkI2Ki4uKiYmKioqLkJKTlJOQkZKRi319jJCUl4WYAZdncnNyOTo8PVhtSlYqITdcv8KuwpPNcYKKs7zS7dDvwpRPKy4tOy8wNDc6KRwdHBwgIiEhIiIgJDEpHyMkJSYiJCInJickJygjIiYlJCUiJCckIiIjJSQoKSkoJyUlJCUmJicmJCYnJ4QmgCUkJSYkKCkrKSkqKisqKSgrKysuKi0sKy0uLi0uMC8uLDAwMDMyMTAuMDExLCotLjAxMjAxMSwuLy8yMzw7Ozo4LzU6OjQyODg1NzY1ODg6Nz06Ojg7Oz02PT06PDY5MzU6OTc3OzY+OzxCREA5P0RGISMiISMgISE8Ojk5NxwbgBsYGCsoY2dtd3qUppKLhHx4fHNitr7Ddo1NTk2Sko2NemNvlKpXV1dWpKCfmZKQkJeYkZeep5uQjJubnpqXTpiTlZSQkpyfoJuQiUxMT1BNTH52ZZVXW1xhY2NgX15aWVtgXl1dYGNkZWZlYmRkY8G7jqirpqWrtbOvpqOenZ52gGt1cHZ9fYR9gYCJj5l+mqCcmqemp6aknpibmn+VjoiKg4iGgoJ/h3t6eHZ1dXV2cXdzdHFxeXRvaHB1bmpqZmlmZmzBq3Jrw9LMacfBzdDIysGnYF1YXGJaZmFjY2Ngbn9PnmJkZGFhZmZramhkYmNiYGVmY2BeW1xdspiMlJqcgJmcmZOVlJGUjZGOj32CjYl8gH6MkYyLTJKSh0WIRYJ4fnyAgXBna3FwbG1pZ19daGRoamxoZVFOTUlHS0dPVEtNTU5TVlU8QkNCQT0+QUNAQD8+QDs3OjtBQ0ZGQTw8OTVhY1xKSkpISUtIR0lLSktJcSgpKysrLC0vLiwuLVhUgD4+PkVCQUNGSkpHREVXLldRTElNU01OUVFSTlAuKysrKipLSSsnKzJSKi0uLy0pKSknKi0nIlJCRUE/Q0FBQkZISkpKcUdEREBBP0E8PD07OzljOj9APT4/QW1wc2FhW1pTYGRjYWhgY11TV15bZF9gUWdlYFxgYGBeZGdtbmlugHt1Z2xrX2VhXF9YV2RbRT45Olp+gW9fe2hWV2BuhI6LX2liYTFTWVhSTExLSktKUDMsKi5VV0RLLUp6UZjzl8R32W1IWH1fU9qf4GYsU1VWVlxxLxgWFRUUFBQuaU9CQEJPYWBtZmtFU1NKMTU1N0tQO19fYGBiaGdnZTw7OW87Rzs4azI0NDM0NTU1NlhDNSwuLjM0Vlg3Q0RFR0hMT0RFSElLTXF2Wl8/NVc1PT45NjYwND9BREZFQkM/REZGQTw6OEUuMDQ3hENKPj08PDI8QEEyMDEwLzMnGxwcHR8iICEdIigkSESCfnl6e3lwcHN2dndzcHFxb25samlpa3F0cnFubW1ta2NUVGZub25vb3Fyc3KDfoh/GYCAf359fn9/gICBgYGAgIB/fn19fX5/f3+GfgF/i4ADf3x//4CkgIiBhYCFgYKAj38Ifn5+f3+AgICJf4SAlX8BgIt/AYCGgQSAgIB/moDUfwh+fn9/fn5+f5Z+gn+XgJ1/B4B/f3+Af4Cof5uAg3+NgAF/jICCf42AAn+AjX+GgIJ/hIABf42AAX+NgAF/jYABf4eAAX+4fgaAfH9+fn2IfoN8i32GfBh9fn5+fHt8fH1+fX5+gIGAf39/fX18fH2GfAF9h34BfYt8in6KfwiAgIB/gICAf4mABn59f4CAgIR/joAHf39+f4CAfpeAAX+jgIJ/rX4CAgQAgN7b4O2BioyNkNnsjoO6gnnMrNKqjj5qaUoyO1zT04yCpqaQ2NLT0dvogO2SlZWQl6GjnpqZjZNSxaq2u7zBw8jJyNLRz83Oy9PU3N3f4ODi3uHl5ejs5eLi4+Pk6ert8fL06tzg5uXm4dvq6eDo6ODi4t7e4OPk4uTl4uPl6OnigNzi4uTm5+bn5uHk5uPo4s3I39jV19Ta3uDg39rb2tvc3OLd6O7u5+/w8O/v7fDu6tzY3ODZ2dPMysrY5OHw5O7z6+fj6Ovo4tTOzcjIyc3h6+nm29re5Ovz9/r4+Pv89+jo7fLx/IiLkZyssK+yrqH7mLqhqtOvrbK0xNDNpOfmgPqUtu6A+YH7+vbq0s/JwtDvjoyA5NvdztDS3vT5/P+A9/X5/Oni4vTv8Pf59vb0/PPz//2mjpGRk5CSj4+O64yI68utkYSC/PuAhYaHh4mCgIGAgYaAhISAgYHtyd/n29DX29zi3NPR0syIiYqFhIL89vrn19Tc5Onk097m8vyDgIWBgPXg1pW0srivraytpaOhpqOaipCWn6CbmI6OkZKUlYuLh4+NjYWKh4aCg/b29tX4h4CBhYHv7Ojo4+frwp+snZ+omaDelbnc7+7w2IWJi4yIiYuOi42MiYeFhYaFhYSB7tvc4O3x8fD08vLx5uXq6u3r7Ojh3NvQxtv1+PTvgPP0+f38/v739fT2+vzo0t/h5uvl7PLq6e3w9/T5/PjV3uPh29PU2dvq7evt7vHz2ImNi4qNi4uMkJCMjpORhYGTlZeZmJyZl5qemZWV8JKfnqKfnp+enp6hnp6Y64mKjYyHhYaIh4SFhoDplJKYlY+VlJmalJKWld2A//fz7e74gPvv/vP6+eGEgoCA+P779YSEg4n2+oaBhIeIiYWFhYaIguSMnJ2dmZyZl5qem6GfkP+Rjo6MjZKampmZmJqX8JWZmpaVh+3t6+TdycXFrcHUysXI08y8zMzY093b2b29zLzAxbzEzM3Q0s/j59vWuMLX4dTT2M3U796Whf+Dn7jfgIjX59GTpqzB0Ma/ovD5+4K5x8e/r6yop6qt9rSfq8DGwpyMxrrO++WYjXTCuouLjKSJxrKN9o/t3fXgtdHf4MOyl4WBgYLegrrAuq6MgYGXhJGUwcCQjoyFj5aWxOPo5uvp9u/w7YWFhIaFhomAh4iMj4+QkIiL0oyrn56empuOgJCTmJmbj5OWlpSYnJ+gqMiSwYncuOOPgY+Mj5Odoquts7Kvs7OwuL7EvKCtss+bpKmps7m4tam3u7yov763qJ2ip7XJura3t8jGuLex6dOX++PY1M7T2d3ez83O1drd28/MzMzLysnJysjL09HV2Mi1ppaEh4uZutfd3+Dg4N3egJaUlptSV1lbXpasYFaLbGyig6uGdUFlXlM5O1CooFxLW1tRenZ1c3iBS4xUVlVSVVxdW1hYUViIeWJqbW9wb3Jyb3Z0cXBxbXRzeHl7dnd6d3h5enx/e318eXh6fHx+gYCAenJ4fHt8eHR8e3R7fXl6eXV0dnd4eHt7eHd6fX95gHF2d3l7fXx+fnp9f31/e3JuenRxcXBzeHl5enV1dnZ2c3h4goSEfYWGhoeHhIWDhHt3enx4dXV1cnF4fHqEfYSIgn9+goSEgHh2dnJydHV8hIR/e3t+goOEi5CNjY6PjoKCh4qIjktNUFdgZGNkYVqKVGtlf519eoCCiZCOdLCvK7Nsg6ZYrFqurqyjko+MjJu0aGddqaSlmJeYo7Gzt7Zcr66ztKSfnqunp6yEsIC4rqy1tHFhZWZlYmNfY2KeX12cinhmX122uF5hYmNkZWBfX2BgZF9iZF9hYbKXqbOmnqWrqrCroKCfmVxaWlZUUp6am5GHgoaPlZSJjpKfplVWVFOilpJsgoCFf3x6enR0dXp0bWNobnh6dHRqa2xuc3NmZ2RtbWthaGVlX2K0tIC1nLRlYGJjYbS1sLCrq7SMZWtkZGtgYYdYcoWNjpWMYGJkZWJhY2dkZWVjYV9fX15eXFmol5aWnqKkoqahop+Zl52bnpyel5KQjYiCkqKmn5qgoqanoqOjn52Zlpeekn+GhouTjZGWjYqQkpaSl5uZe3p6d3FrcXV1gIOAg4WIiYB/VldYVldWVFZZWFVVWlhRT1hZWl5eYl9dX2NfWl6VYGhkaGZmZWVmZWhnaGaITExPT0xMTE5PTUxNSpJfXGFgXGBfZWVfXF5eg0qSjIuEhY6OhZCEioyCTUhHSZCSioNGR0hQko5NSUxOTk5KSUlKS0Z9WmRmZ2VmYWBjaGZraYBfp15cW1pbXGBhYWJhYl6WXmBhXV1WmZuZlZKFgIB0fIWCf3+HhHuIiY+IkJCNe3yHeX+DfIOFhomKipmaj4x3gI+Ui4uOhoubkWZYo1Vtkqtwo6eTZHJ0hJGNh2+YnZ9QeYGAfHRycG9wcZJnWl9pbHFlV3hsc7K3cWZblYpobYBqe2aTfF61dK2GikAgHh0dHhkZGhsbGzQ5ZGFfXWNeYHRlamWAf1xYVE1VWlp6lJaUl5ahm52hW1taWVlaXFRWWFtdXV1cV1qQdHFmZWVnZ11dX2RmZVtdYF5bXWFiZnGXZYxelHqYVVVeWFlbXmJxdHl5eXx9eoCEiIJmaWp8Y1FrcG96fn58dn+Dg3SEg31xYmZrdoJpZ2hndHVubWuVkGywlo6Oi46Sk5SMjI2SlJWSioiHh4eIiIaGhoiMjo6OgXJlWEtMT1t3jZOVlpaXlpYwcXBycjk5Ojw9Ymk+OL/QxcqGvmp+iMOl5J2OidahTC0sLicxLy8tMDYgNh4eHRsehSGAHyQxKSAlJScmIiMkJCopKCUmIyYlJycmJCQlIyQlJSYoJygoJiMjJSUlJyYnJSImKCcoJiQoJyInKCkqKiYmJicoKCspJycnKi0pJScoKistLi8vLS8wMDMxLy8wLCorKiouLi8wLS4vMC8rLTA3NzcyOTk7PTw4ODc5NTQ2NjGALzI2NjI1NDM3Mzg8ODo7Ozs8Ozg5Ozk6PDo6Oz07OTs+QTo4P0ZERUZEQz49QUNARCEiIiMjIyEiHh40KlRcg596dXt8eX+AdMbHwm1+kEqQTJKUk5CJi4eHmrBiYVqopaiZl5afoaOkpVKgn6Slko2OmZGUnJ+foKCom5ukpFqATVBRUk9QSk9Qe0tJfHNpXllZr7BZXmFiZGZgXl5eYGReYmNeYWOylK26qqKqsLG3saSkpZ9UVFVRTkyTkpSGenl9hImGfIKHkZtSUk9OmYyGbYaCioB8eXt3d3h/eG5pb3SBhYGCdHN1eIB/b25rd3Z2aHJvcGpsxsTDpMFwamuAa2vHycLCubzEnWVqYmJrX2F7Sl9rb3N9f2FkZWViZGZrZ2lpZmRhYmNhYF1ZppGTkZifoaCknZuXj4uSk5SSk42FhYB7eoeTmJCLkZGTlIyLiYOAe3d7gnVgZGNmbGdrb2ZgZ2xvZm1ua1ZPTUhCPURHRk1QTlNUUlZZQkFBP0EePj0/QD88PkJBPDc9PT5CQ0ZEQkRIRD9Ba0dPSk5LhUqATk1PTFQrKS0tKiwsLS4sLC0sYUJARUQ/RUNISEJBQkNVLllUUUxNVFNLV01QUk0rKCgrVFZNRyYnJi5ZUCwpKy4sLCknJigoJEU7Q0VHRkdCQENIRkxMQ3ZBPz48PD0/QD9AP0E9ZD9BQT4/O2tzcm1oXl5fVFldW1tdZF9YY2SAZ2NrZ2NWXmdcYmVdYGFgZGdibGxjY1ldZmtiYGNaWmFaRj90PFd/iF6FfGdOXWF3iId+U2VoaDJTWVdTTEtKS01OWjQuLS8xPUQ5Sz9GbJBSh5Lzr3p6Q0hVf2Jg+bbudHIyFRUUFBYWFRcXFxYvMVNKSVFfYGN0ZmtGWFg5NzUZLzY6Ok1dXlleYGZiZGY8Ozk6OTs8NDMzNIQ1JDM2Uz9BPTw7PT03Nzg7PD00MjQzMTAyMzlBg0JhNlNEUSosMYQsYy07PUBBQUJCQERGSkUwMDA4LzA0NT1AQD47QENDOUJCPTUrLS0wMh0cHRwgIyQiJDxIR4l8d3VydXl8fHNzdnt+gH1zcG9tamloaGdlZ2xsbGlaSTkoGhkZKk1namxub3FwcIR+iX8JfX5/f4CAgYGBhIAFf359fX6Ef4Z+gn+LgAN/fH//gK6AioGCgIx/CX5+fn9/f4B/gIp/g4CLfwGAlH8BgImBA4CBgYaAgn+SgJV/j36Ef4N+p3+FfoV/kH6Hf5SAzn+dgAF/joABf42AAX+NgAJ/gI1/hICEf4SAgn+MgAF/joABf42AAX+GgLZ+CX1+foB8f35+fYd+BH18fHyLfYd8Gn1+fn59e3t8fX59fX5/gYGAf39/fX18fHx9i36CfYp8i36Jf5GACH59f4CAgH9/kIAIf39+gH9/fn6WgAF/n4CDf7B+AgIEAIDb3d3e74aKiovskbWDeNOx0qmcRG1uUTpFX8zO5tOgpZ341Nnb2tzf7ZGMmJualpaYl5eamZCXVcWqsLy/wcPEy9LPzs/R19zi4uPd3N3c5efr7ODe3N3e4t3f6uvx8vHw7Ofj5OLp6+7w8O7r4uHh3Ojg4+vt7evp6ufo4+Pg4oDg6Obi4Ojm6eru7ujh3t/h3uDg3N7d3N7d4OHh4ODg5+zr6uXc2tzo7/Xw8O7r7ezk4eHg5ODg3+Lv8Ovb6vLo7+/27u3s7+/o2M/IwsHEv87r7+Xl4NLV3evr8Ovl3NXa6PyEkJ6lpamvr66pq6mqoZzN3sGvqK6zqaeooPDW+oCUmsHs/fjy9vj+/fnd0Me5sLzp/fHk6eTrgvT3/fbw/oD08PPt8Ozk3Nvq9fL37O/x9/X3+4mJiImLjI6P6o6OjYuMjY6B5MSujoGA/oD8gP3+/v2C/vyC+/72xeHd297b1tjW19Td1de3h4OBhYiDgvbq8evv6t3b19b38+Pr64Dm2uLLu7egycTIycTDxLmosrWzrYikoJyZn5qalpGPi5GOk42IiYmDhYOB+fP9gIDhjIeFhIHr9fP29/f1/vm5wvSVss7q8/P2+vv17ejMg4mGiIyMjY2LhoaGhYaGhPjm3OTv8PLw8uzt9PTt6+v07ero5uPb3MzP4vP5+Pj184Dz+Pb3+fr07O7w9eTO193e4tTg6PHx5Ozp8fX09+TQ5ePg29DS1uTi3+Xw9/fz4oj//oCHj42LjI2QkI6PhveUmJaZmpqXlpmampSVjoCen56ioKGdnZ6ho6Kd9oKEhImJiIOFg4WJjIj2hZKUmJKSlJKUmJWPjo3jgYD++e7o7YD39/7++vPW/4D79fn3gP2BhIKGid+GgYCDhIWJhoWHiISA6Jyenp6cm5ufnpqdn533nqCenZ2dnJqam5ebm5frmpuYko3c2NjZ29nMxcWrt8PG3OLV4Ozc6trn6t3PrqjLu8DD0M3NybnG2Na9vMrDtcfU09S/y+rRkYP5gY608YDFjc68naOoydG9xov7+veBvdLLu6mnp6mvr/a1n6zG1bjQiNO01qWJyeuhsH2xvJilnpXvybiOgI6Ris7b39fN2du/m4L17syqpY2KgIn/mIOKp9jEvrasoZSMlq7k6/Lu94CEhYaIiIb/hoWFgYWHiPuCg4L8g8D6qZ6eopukk4CRkpabnpGVlZaZmZiWldKpraWXotzPz6X8hYydsLKxsbOzrbm5tre5ubimsLK3pLG5ubq5ubi0q7G4paiboqOlrrXBv7G1vMbRrP3Oi4HT0tHR0tPW2ev0+urL0NDX2tzVx8jHxcbHyMXDwrOmmIeFhoeIh4iJh4OSsdHZ2Nnb2oCUlZWVm1VZWVqXYpF1dKqGp4N/RmlgWERMVqGdoX9XWViLdnl7e3x8hFNRV1lYVlZYVlVZWVJajHZgZW1wcG9vcnRycnFyd3p7ent2dHRyeXt+f3h3dXZ3eHRzenuAg4OBf3x4eXZ8fYB/goF9eHl5dn12d31+fn18fnx9eXh3eIB1e3p3d319gICDg397d3l8eXp8eXp0dHd3eXp5eXp6foB/fXhub3J+hYmEhoWBhIR/f318f3t7fHyEhIF0f4d+gYGJhIaFiIiDfHdxbW5vanKEh3+BfXN0eoKDiIOCgHl7g5BKTlhbWl5iYWFeYF9dV1Z5lY2CeX2Cend5crWpxTdtcYeir6umqqywsK2elpGFfoytvbepr6ywYLK0urSuuFytq62oq6igmpelr66yqa2usK+roFlbhFqAXGCaXGBhXlxeX1WXhXdiXV66X7xgury+vmG8u2K9wbmPraemp6ajpKGjo6qiooZXVFJVVlNSm5KXkZSQioaFhJyZjZKQjYaKfXVzc5GMjpCNjYyEd36CgHxhe3d3dHh2dnJub2pua3FrZmdnYmNhXrWzuF1domZiYGFfrri4uriAu7vDuYB6lVpwfo2Wlpid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hH8Dfn19i36EfYV8BHt8fHyLfoV/h4ABf4eADn+AgIB/gH58f4CAgH9/j4AEf39/foR/A35+f5SAAX+cgIR/sn4CAgQAgNnb3Nvc7oLR+bONgtOn1Z2oU2x4W0BVacPK1KWPpJ+J3Nbf4uPl4+f6s5iZl5KLjZGVk5idlJhVxK+xu8PBwMbMztPV2N7j4+Ph4uLg393j5uDb3Nrd3ODi5Oru7ejp6Ojo6ert7+/r7Onn4t/m6ujm7Ovr7/Ty7Ofh4uHm5+TigOTp4tvn5ubo6unk2trT2d3b3drZ2drf4drd4+Hr7Ojt6NPf4uHn5+Pu8Ov09PLy8fLp5OXp5erh4efl4Obd4/Hz+PX17+vp4uLl4MrAxMTAtsfk5e308O3z+vTm3NLc8oaRlpWXm56kqKyvqaSmpJ2FsYLz1sG5ws7BsZbLlIu5gICd0ebm5+z09fDn6eK8qqejoKLB5PD5+oSDgYCDg4D8+Pf8+fPx8e7u6NnIx+ny9+/s5LSKiomJiYeIiOONjo+NjIuMjouLi4yNhuvUtpuG//b5/Pz8/fj79+TG59zV2NrV2t3Z1NXT0qGBgv2BiIyJg+vU2+TZ4OLv6dfPx8zKgMrX0+PGs5vDwMfHubu4rbCxt6+ki6Gbmp2bnZmUlpKVjoqLiYSFi4eEg4KAg4OJgvzjkI739YD8gvuDjqG5ydzz9Pby8+/x8/Xt7eri4d3JgYeNioyKiYeGiIWE9eri0trx8+zp5+3u7e3v8/f28/Xw7enl18fR6vLu8/f09PfygPD7/Pj2+Pfx8ubMzNjX4OHk2d7i6ezo6+/z+fbQ1+He2dfS1dPa4uzx8vf26OOLi4iIhYaBg4OJkJCRkor4lJKUnJqemJSSmpeampbunp+gn6Cin5udoaKhnpr1h4H/gIH88+/0goaIiN+ZmpualJqWk5OWlpGTguv//vz47uzwgPj094D/7NP+/IH+/Pn+goaD/PuH1oKGg4KGgoOGhfzw7fDMmpyeoKGemp2Zm5qdopz8nqGbmp6gnpudnZycnJLplpar9fLV0dvQztLIysKz0IKBgfPs6u778OPk6NfVwaa+vbzAxc2+rbGyu7rEwcPGtbvPzsrl9dmUg/iA/K+egOOtw8iiprbUzYGUhYiGhYTK1cWok5GSkpuZ3NHm8IGH6uvvgaWGztSFscf4zXR9sNatvqui5cO7i3+SjO6fidnJx+qhpK6ko8/EqPfDhYaSkp200cDEwLymmpeUpvH7/fj/gIH+g4aKhIqMi4qLjI+L6Orl4ubqrdWnn6CgmKSQgJCJlZaWlJSXlpGiqKueyZDu84/Air/PwoyJj626rq6lo6eeoZ+Zkq21t7vBwq+ipamrr6iws7S2u72qw6Ojp7C6ztS+v8CviceB8dTT0dTX3uPl5+zq7vf37M7Mz9DW2NTJw8C/vbuwmfjX5fmBg4eIiIeEhYeHhoaKjaXF1NbYAZKEk4CeVoqnh3x3rICkeYZPcGVdTV1jpaCZalBYV0x8dn1/gYODhI9mWFlXUk5PUlVTWFtWWox3Y2ZscnBucHJxeHh6fnt5eXZ3d3VzcXh7dXR0c3V0dnd4e36Afn5+fX59fX+Bgn6Afn14dHp/fXx/fXx/goF9end6dXt8eXl7fnlzeYB5e3yAgH13enF1eHZ5d3d3dHh4dXd7eYGCf4N7bXZ7ent7eIODe4WIiImGiIJ+f4F8f3d5fnt4fHd4g4OHhoeGhIJ/foF/cWxvcW9nb4B+gYmFf4WKhn98dX+ISlJUUlJVV1xfYWJdW1taVktwWq2ZioWLk4uCapd5cpRdcpOenoCfoqmopZ6gnYZ7eHRxcYussLa2YWBfXV9fXrizsbGxr66vrKymm4yJoamuq6midFhXV1hWU1VXlGFdYmBeXFxfXF5eYGFcoJJ9bWG7tre7u7m4tri5qZKvpqCkpKOkqKekpKOgclNTolJXWlhTkYOHjYOKi5KQhH55e3x+hICMeoBzbo+Lj42EhYN6f3+EfndmfXZ2eXd4dHBwcXJsaGhmYWNoZGFfX11gX2dgt6Vua7WxX7Zht2Fnbnp6gZOTnZqalpeXnJSTkYiGg3xbYGZjZWViX19hYF+ropqNk5+hnJqanp+hnp+ipqSgpaCbmJWNgoeboZyip6KkqKGdqKiioIChoJudlYGAh4OIh4mBhYqQko2RlZaamHt3d3RwbmxubnR7g4uOkI2FjFhYVFNSVFBTUlZYWFlZVJpcWlpfX2RfXVtgXmFhXptqamppaWpoZWZpampoZp5UT5lNTZaSjpBLS01OgmNkZGVfZWFeXmFeWl5Th5GRj4uGhYaMiolHjICDeo+KS5OTkJFKTUqNiE16Sk1KSExHSEpKj4iGjHxiZWhqamhjZmJkY2ZsaKFkZmNh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8vX29fX8+/Ty6/Dy8ebq9PPz9P2A9vP9//nw6OLi5OPXxb/BwsPIy+2Bg4Hy3NLU6PmHjo+Rk46NkpWenZ+foqOjl8KHgvjXzs3m+erAoe6qnp6ygOGh0c7QzdDMzs7M09HOt6Ssp5+8zNr+jo+MhPuB+Pbz9PT99fXt6+bn7dTAwbu3zdz5ho+UlJGSkZCO44qOj4uLjY6Ni4qNjI2Li4qLiIiG8+HIrpuH+fjz78/b5dzb2N/h39va29bQ1YTq6eTo84D77+fi3uro5+Dj1dPQ0fPOgL3S6OLRy5PBx8HJwLqztrG1saGpi5aZnZqYmZWXkpCSkYuKhIqNioD7+oSDhoyKgvrg+fv+l6m60un4+f30+vHv9PP09PH59u/t6OTb4N/dxoCJioiLiIb/7eTh7/Lt7uTK5/Dy8vX19vLs9fLz6+fm4826zefu9fPy9Pb08ff8gPz69vP2+/npy8nX0dDh5Obi3OHh4+bt8O7w4N/j083Z1djS0t3a2Ony9vHj8o2KjYmLh4iLjY6KhIOFg/COlpeampecmJSTl5eVnfuYoaCgoaKin5yfn6Kfnv+RkI+OlJCMioeNiIL7+92YlZqUlpuYmZiXlpea9e/v+ffz9fXtgO7+9fj699aBhYSAgIGDhYT4//+E0Pf7+4KFiIWIkJScn5+XgJybnqGhnp2alpqcn6GGkqGdm5yfnqChm5qZnZ2J9d7T4u3t283Nz9TQycrFuNLr6Obc5OHx7dzS3dHKzcOmr9DIxLq3urvJzLe6v8S6v8O0zdrx/Neeifn63aWGYO/BusG+s8vOxMHvgouPiZDJyreglJKQkpSR0uj+j5CDgICEgPWRwaDTu8r4zIX4enmy6qzVoqDv0r2Qj6Vy8JHY2tzi39ik+qPOzs2dhIOWkZqnwcvOz8nAvbqwr+77/oSDbfuKiomKjZCOg4GDhoT5+vv6+4KxvqufnJuVuJWXh5SZnJ2dn5+Xra2rzLb48sLbi4y0hsPl65Wqq6iykJSSkZaZl5ejtLCytLiYnaawtLazs7m3trW5o725u8LGzc+6nPOt/PDX2Njh6+vq6uqE6Svr4vLz6s/MzM7P0dHGpfitz4rJka/K9YGB/P6AgoKChIWHiIWCiIeGmLvSgJCQkZCMbI6RRXy1hKGDjlVyY2MqNjxUqZhak1lYZnJ0eHyDhYSJiYlKf1NQVFJPUFJVVFNWU1mNeGRpbHFta3BydXp9fHl1c3d8fXp9fHV3c293c3h6fHl5dnd7fXyBgH19f3p/gn54fH6AfHp2fYCBgH55cm1wc3x8fHt3d3Z8O3t5fIB+fXZ2d3V4cnF6d3d6eHZwc3l5fHx8foCEgYGEfHNzfIKDhH6DhIKFg4mJh4iNjYiEgISGiH99hIaAjEaIhouNioaDfn1+gHlvbHBucHRyg0dISIV5d3eCh0pOTlBSTUtOTlhZWlhaWlhSdFxbrpaOj6KupIVys4J+ho2pd5WSlZSVjYyOjpWVkoJ0e3hzh5CbuWhnZF60XrKxsK6wuLKvq6qmp6yZgYF8eoqUsV5hY2NfYmJhYJZeYWKAXF1fXl5dXWFhYmFfX2BeXVuimol1a16vsbCylaOupaGfp6yrqaanpKCgVpSUkJabUpyYko+JkJCSioyBgH9/l4B0g5SOgYFnjJKLkYuEf4J+gH50d2N2eHt2cnRxcm5tbW1oZmFlamZdt7VhXmFnZ2K8p7eyuGlxeoaUnp+elJSAjo6VmJmWkpuYlJGHiIKHiIeBW2JjYGJgXbKnn5qgpaGinYeYn6KkpaWmo56loaGdmZmVhneNlp6ko6Oio6WjpKWnqaWgoKGgloGCjoWCjY2Ni4mNjI2Nj5SVl4uPj3xtc3FzcHF5d3aCjJCLiZZZVllUVFJUV1lZVlRTU1GWWF1DXWBgX2JfXFxfYF9loGRqampramlmZGZnamlopl1bWVZcWlhYVVtWUZeYimRiZF9gZWJiYWJiYWWhjIqQkImLjIWFkYSJBHtLT0+FTIBLhY+LSXeUl5VNT1JTVVtdYmJkX1NmZWhpamVkYF5kZmlrWl5mZGJiZGNmaGNjYmZkVpmNiZeeopKIkJCPioSEf3qNnpuYkpiUn52RjJKKhYaCb3SJf393dnt/hYZ2dnl/eXp7c4uSn6aQaF2mpo6BZ7SZiZCBeZOViXthT1haVIBYh4d4aWBeXVxcW4aSqV1fVFFPTUiGV4BifWpxrqVms11diauFonl5tJqDZWyBXYwfGhkZGBcYG30bGRkYF11hbmpwb4KJiouGfHdxaGaep6pYWVhYp1xcW1xeX15VUFFTUpKTlJOXTnWwcWRjYWN9YmNWXV9gYWJkZGBzcnGIi2GltYGTXF9+W3l/nGR0dXN5XF9dXV9hX2BtfHx5dntf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h4mCgYOBgvr394CBgPr08/Pw7e7r8Ovi3d/h2dfE+YWE9enn8fr16fHt8vLy9tDj9P7y9PHu7O7t7OTXzcfV1sLD7/f07/b7+f34+YDx9ff8+u/e5tbF4NjZ7Onn4djj4uPi7fDp2PmJiYmA487O2NTf4uns8uzb/IiIiIuOi4yKh4iKjZCUkPeUlJSXmpmXlZeYm5eXmYaLoqOjoqChn5+fnJyZmJb8k5KNjpORjomGjIyJjYGBlZaXmpmZm5iZmZWanfCFhYGBgP349R3y9PuCgP7UgIiFhYSFhIWA+PyA/9icnp6goaOko4ShgKSg7Z+dmpudop2fmpyen56f+J+gn5ycnpycmZqamZeW4OLr5OLl3ufjz9La18jLzrbA2NvS29LO2t7fysfOyMbFq6bF1Na8xMjUysrNxsHLzcnNz8zzivGpif3itpXHo7+618jKy8WJ1fiJmpWLm8THtaeWko+Pjo7X2+z4ipCNgIyMjJifp7fwuNOpnd/ki5ODf6v2w+2xs4TKv5Skuom5m8rY1MSKjL3NqtTQi4uLmca4ytLY3Nze4+POzc7a3fj8/vn7hIiLjI6Qj/6AhoiJiYeJgpaYuJy8qaeomLyVmpicn6OPiomJi4yQjLqD3KuRmJiQv4G41fSfsa2ts5mYX5mC4djLr6mjnqekwruo6fWImaChqKersKukvsXDvsGigq+F8+Pl5ubk4eHu7Ojm5+jn5+nq5efs5s7Fw8CuhLzEl4WDhpGbnqe++bePudXu++7y+YGAgoWDgYaGh4qYgHuLfFx+U1OKwYWjipFXPGtkKz1FW6mUpX9VVld/U2Z0e3Z9hYyOkElPiVBOUVRQTk5PU1RTUFWMemVnZm5vbGtydXx7ent6eXx6fHd1d3J0eHZ3d3Z1cXJzdHJ0dnp+fn6BgHx8e3h8e31/gn97fn18e3ZwbXBydXl/f3+Bf359gHt9c3Z1dHZ5e3Z0dnp7e3B+f3p9e315ZXt7fHyCgIN/gX90bXV7fICCiYmDiYWLioaEg399hIaFh4eEiYyMiYqIjUlIR4uNhoGDhYSDfG1oa21xb3F7gXx7g49LSEVGSElNS0ROTlFVUVNUUU5tjZZZqJiRk56tnJCEcWO3rqe2SnGJjI+Xl5mSk42IiIuKiHl8hIyPk56ema+0YGJhX7qvr6+urayqrbCuhXaBfnp4e35y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mpyenKGRg6Kfn52em5ycnZyZl5T9nNTf3tTj2+Hn2s7Hu8HEysC81dre1tLI0N3d0M3NxcPPzK7IzsW+wc3JwcbCwsXOx8/h6fuG5pfy6uWmhIiWntvWx87Oi8iF8p2dmYqswrmqnpSPioiKh9DP7fiFgoWAhIeEhYKLkZ6yjNDbl4Hi7ZOlgX/B9MffnrqY5NCexG+U8deS68votsHqnJCPjZObnMzM1trZ29nXy8/cy9fZ8/T1+4OIiYWLjI2OhoaGh4mLiYaEl5SpjbKkp6qPx5uoqqyvsZeNjYuOkpW+vfHv6pOUjp/A//Tv74OoqLGG6rhfi46V4cK/v7y1sKOftJ/xk5GMg5W50tbU5vOcnYDFjezA3uPk5OPf3d3d1+jp5+bk5uTk4+Xl4eTdtfyns6OUlJSNjZaTm5qaoJ+it9aIj6PMharM4OXr7/z/+oaGiIhglI6DbmaaZYumkZlXO29nK0JLXK6dl25PU09lTFdWlHh+f4KJjZRKTVdNT09SUlBQT1BYWFZRU4hxX2Vjam9vZ2x1d3p2c3RwcHJydHJ0d25rbGhubXFzdHV2en6Bf398hH6Ae31+gH98foF/f3p2dXJ4dHN3fX59gH19goJ8enhzdnF1d3d2dnl8fXV1fXZ2fXt5d3yBdXV8eH5/gYKBgH2Ae4B3f4KGh4qCgYmFf31/f4SAgYKEgoyMio2MSEdHR0hJR4mHR0eKhYODf4B0cnRzdXNvbXKGR0hHg4OHioxJSUmARU1NT1BOUH6LaWJueoeEiZikk4R5dHd4b2lykqOboKuho6ijnKWop56ln5GIhomLkJuQpLa0pKixsrm3u7i2tLq4tq5+d3h3eHRxdnNTlZKKe4qdWJxeYmNiY2NiX2NlZWNhWmBgYWFfX19dW1hdXFpcXF5bhpKOjoyIgHdwaGNcX7Ghnm2dmk9YV1CPk5GQjIyKjI+ShYKJh4KBeXtwfoyOm2KPh4uRkouLiouBiYOGgW50bm5ycHNwbm9vb2lgY2h0go+brFtkam5scW9wZpVVUVOjn6WkoFBRTpeEloCXmpOXlpOOiYyNhoaNhH6em6WoqamrpqOqqaSkpqKejo6hoZyenJmcn4iBgIh/e4N9enKToqukp6KjoJ2iVaadkJ9Wq62qkoKOlJGRkIyLhoGHjoiWWVZXW1xeW1xXknt4hIKKhoJTWlRR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8vuAho2lio6Njo+LjZWTnJ+cko5PvaOnsLm+wbq+29zW08fRzNLZ29bTzM3IyszKz9Pb5uzs7uvu7Ofl6+vm6uju7uvl6d7d2OHf3uHe4eTi6+zx7/Ht5uTd19/hgOTk3+Hg4OHZ1enp6OXq7urk4tnY2dzg4+Ti6Obt7e/x8evs9vf3+Pj9/vjv6uXt7uvl6e708u7r8Pb9+/z7+4CDhYKCg4GCgYSD/Ovr5u7q6NPCxMTFvMHM5/2B/u3r6fH5+YCAgYiFiu+JqJ6Oh5WRiJvCwqmcmJ+YlpCNoNbfgPCFj5WViu7r+Pjx9Pj07NrBvse+s8n4+v78gPnz6uHzhoaB+ff36bK6s7a1sr65wszk+YD8+vj58OPp7/uHi4uKiouLioqMi4uLjIqJiYmFhYaJioaHh4bt6PT18u/t7fDz8Oro6OXcyrennpSF9eXNs52NhPvj19fZ1tzX4eDdgNTd0s/z8Y/Av8DIyMjBwbu9uL27r4WeprK+y9vo8oCJj5eirrGvsa+oqqqopKWlp6Wjo4L8h4iBgPv7+/z6+Pr09O/u6+Xl5+Xi4ODh2t7e6Pfw+PDz9/b0/v359/X19/Pt8OzdzOn3+OvVw73NysfHyM/GxcfBwLzh+vvy/Pj3gPbt2+f++f2Agvv59OLV0dvd6uHk5dzZ/4mFg4iMiY2JjY6OjYbp3tXWhoeGhImIiomMi42MiYmKi++UkZOTlZaTlJKUlZSWl5TkkJ6cnp+cn6Cgnp6gnKGQgJGRj46QlZSNi46Tko+K3JKXlJSWmpuZlpiYmpqb7JSUl5mWkpOVgJSTkZKRj9GA//z9gYGDhIaC9vb+gfCcmpqenZ+gnKGgn5+fnvCdnKCgoJ6enp+amZmbnf6doKKgnp6bnJ2bmZSnmp+Z1eLf293c3NLXycHJz8TDyLXd5d7U08vK2NXM09fIwuLetavDwsrIyNDKwsHBxsLGy9Lu/u6W8fbdn+OrgIvaldyruKH0iPuqnpqbkbzCuaOXkIyKhomCzdr2g4eFiIeKjIeE7Oz1gqTEotjQitXj8pqhiojU5r7LmL3b7ei19Imug7+G3Zfb2Lz4lpSaq+3K0tfd7O7i5ezs/uPj7vX19vGBhIeFjI6RkpKTlZeMkZaWmZmWnn+Wi4qM9qWDgJKerLGtn6GhoqCrprWIoaif89vq17qKk7nfu4DDm6SP3dLLxb+7ube0sqyfl6v8rK2uqqutra/dk5OQmJOKzK6sp7/e3+Hf3t7b2tTh5Obm4+Lh3tvXvpHM9u2Wk4+KiYuPkIiUmqClnaK03O3xkqqgoaavu5i3h6nJ2uL+8sGegFZLeahtlbKZmVg7cWgsRVJdrJuSZIlKkFKPXVdSln12foOGipJLTlJgT1JQUFBOUFVUWFxbVVWIcltfZmxvcGpreHdyb2lxbG91dnNwaWtoamxpbXB2foB9gH+BgHp5e3t2eXiAgH98fnh5d3x5ent2eHd1fYGDgYSAeXl0b3h6gHx9eHl3eXhycH19fXiChn17eHFxb3N9eXl4f3yBgoOEgnx9hYSChIaKi4d+e3d+gX98gIKHhIB+goiNi4uJiEZISUdISEZIR0lKjn6BfoWEhHlta29xaW1xgYtGjn98fIOMi0dFRElHS4dSbGZdV2JgWWiDhXdubHRvbmxodJmdQKZdYWVlXqeos7Gtra+tqJqKiY6HfYaus7eyW7Cro6GuY2Rivry6snx/eXl3dX56foCMmE+YlZaWkIuSmKhaX16EXIBaXWBdXl1gXl5dXVlXWV1eW11dW5+RmJqYmJWTk5WUkY6PjoZ5cmlkXlSdkIFwY1lTn46FhYeEhIGJiId+h35/mJtkjo6OlJOTkZCKiIWMi4JfdnqCiI6XnKRXXmFmbnZ3dnh5cXN0dXBxcnVzbnBZoldXUlCampugo6Kgm5qWlYCTj5CSjoiIi4qIj5CcpqGqpamnp6qwsKusqKiqpaCmpZmMoqqqoI57dX9+fH1+hnp+gHt+fJirqp2ppaSgnpKdrKapVlinpqOYi4mOi5GJj5OGiaxdWVdcX1xgW19fXl5XlYmBhVdYVVNVU1hYWVhYWFZWVleWXVpbW11eXF1cXoBgX2BhYJJfZ2ZoaGVoaWlmZmhlamBTXlxbWllfXlhXWV1bWVSMXWJgX2FlZmNhYmNmZmaZXl1fYV9eX2JhYF9hYF58SpSRlExLS0xPSoeIjkqSYF1cX19hZWFlYmJkZWSdZ2Zra2poaWlrZmVlZmmmZmZnZmVlY2RnZWNgalpcYoCMmJmXl5WUjpKHhIiLgICIe5eel46QiIqSkYmOlIiEm5V1a31+h4eJjoqFgoOGg4eLjZ6upGScmotor4ZxsnSeYXdoeTtuY2FfYl9+f3ptYl1aWFZYUXGCkU1P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v67H6Pz99vn59ff28uXi7P2BhNuyt7GvrbK2vMLFrv34+vr49fn39PHr6d/k7PGEioeJi4mIiouHiIiJiYeGiImLiIeHhdD7+/77+Pr49/318PHx6eTe2NfU1dbT0c7GyMrIx8Oky7u3sqqknpyAmpqZlZSSk5uXoJ+fnKKfoqelrK+2lr67vbu2trW5ubGztbG0srCxr66srqyrqqikoKako/OLiYKA/vz7+Pj19/fr7vfz6u7u5dnb3Oz///349vT19PWA/e/48/T49/D08O/s7u7ny8rLxszL0sjNy8LJxbbAwsfLxb296vj8ge2A2eqC//r8/oD++f2BgoD00sje5efM8ouJiYeHi4eMjIyNjI2Njov+7IeHhoaLi4aKjI6KhIuOjITnlJKVk5WYkpCXmpaUkZKT8Y6Yj4yPj4yQmKCfnqCenPqRj46Njo+Qk5KNkZSSjviAkpOWmJeWl5OXlZWdnJ/ulZWXmZeXlpaAl5eYlZWTz/+DgPf8+YCBhvmEgPP24Z6cnZ6gnqGcnKKgoZ+h75mcnZ+fnpqXmJicnZ+gkOefoJ6dnZ+cnZuX456anaKo19Pbz9fSz8rVxLzJyczNvbDz/v3u6uXV2tvh19Xp2djVuKW2yM3E2dfPy8S7wb650P+H9KmF8t6ZvdSAiPzG3J+QiIL7raaakIqYzMC4npCMiYqIiPTS3PuHioeGgoeLiIeC+u3yh8GE5ajTxPzN5emXoPl73P65s5TJhYPnuYytaZaB7LjU7KSfoKm+wd7V2dfa1Njk7vCR+v/9gP3r1eDf5Oby+vuAio2RkY+Pk5eUlJOX6qCPk5aDn4VEj6W0t7Wxr7CwrbOAxtH2kpjp7+GBoYqN2daUq5fb3OTY0M3Iwr26uLe1rqagyeyps7eyr7Kwr66vt8ja6erftamlp8+F3TfZ1dPZ4uDf3drNn9L4n4rljOCJmb+Rj4+QkYyNnqCytt3n+evu+6K0oPmv48TFxtP54KWgqolugJO9g5VsoJZNd2dgLEZZaaqdjluDkIOEXnSBf356Y3p5gYeKi5BKS1FigomOkJSUkJZRVFxZU1WJcVpcYWtxb2xteXducXR4d3R1cHNxbm5ucHB1cn16e4F/gIKEgH57fXp7e3p7e3p1dHx6enV7fHx9g3uEfYB7fnp5dHV4dnh6gHuAfXV4enV8f3yDfHl3dnRzeHducXR6fXx9f4N+e39/goKHiIWHhn+Cg4OBd3V+fXV2d3l/hYqJg4WLiYWEhYOKi0dHSEdGiYhISomFhYmIiIN9c3JwbG1qanF4e32FfoOKjIxHgH9/U15fZWNbY11YVqCera9lbXFxcHSGlLFbgF5gYmFkZWRapJywra2oqqWPiod9k6iytKuusa2tsK6kpa/AYWWhen53dXN2eHt/gWyZkJORkpKZk5STjoyHj5ifWl1aXF9dXF9eWVtcXV5YWFxdYV5dXVyIoJ+goJ6fm5ihmZSRko6Oi4mIh4aIhYWDfX5+fH6AcIt+fHl0cGtrgGpsaGVkYmZuZ25ubmtubm5zcnd2d2V/fIB+fHt6f353eXx4enl3eXp5d3p6eXh2cW5zcnGlWlNOTpudnqKhnpuYj5ScmpOXmZSIjpGfr7Cuqqmmp6enWKyhq6amqKWfoqCgnZ+fnImHhHl9eoN9hIV8hIFweXyAhHp2ep+mqVegTJGhWrClqKlUq6epVlZUn4Z8iI6TgqNgXV1cXF5cX15dX15eXl9cqZlXWVdWW1lSVlhZV1JWWllSkl1bXVpdYFxcYWNgX11eX5tcYVqEW4BdY2poZmdnZ6FdWVlZWllcX2BZWl1bV5lVX2BjZWRjY2BjYGFnaGmZX11fYWBhYmJjY2ZjYmF8lU1MkZSQSkpMhUlGh4mIZWJgYWFhZF9fZWVnZWebZGZnampqZ2RlZGhpaWxgl2ZnZWRjZ2ZnZmKRXVtdXmiNi5SNk42NiY+DfYCIiIiJfniqsq+ioJ6QkpOakpCckI6LdWt2hIuFlZWNi4iCgn98jLFfqnVZnY9pkqhrx52VTEI5NWdgX2BcWmSIfXlrXlpXV1ZXlXODk01PTkxKTU5KSEaGg4VFa0+RZn10kn+jw3JyuFujwJObdZdjZbGIaYVVfV9KHx0+d3h4fYCThpiSlJCTj5KcpqVhpaalVKaXgYmJjI2ZoKFSW15hYF9gYGNjYmFt+mNXWltUZ1JabHZ5eHV0dHRyeleai6p1cqyooGB2YmOOfGaEa46QmI+IhIF+e3h1dXNuZV93lmpydHJxcnBvbm90fYuWlo90bWtrho6OjY2OjYuKjpWVlDCSj4VmiqRvZK9qnmx2kHJycHBzcHN5eYGEkZWdk5mfX2RboWeWi4+SlqmOanmMfGdO4chedExgfo/3q8+C2PnT57eQTVhXUFU7RUhLTUk3ODg7PT0/QCEhJC9ITExLS0pGQyEiJiUjJTIpICInKywpJiUqKCQmKSkoKisoLislhCMbKCcuLCwtKystLisqKConJygqKywtKSYtLS0rhC6AMCkwLS8rLSwrLCorKSssLjIxKy8vKi4uLTEsKikoKCkuLiYlKTEwLzAyNi8rMDAzMzc3MzU0LzQ2NzUtKzMzLi8vLjQ4PDw4Njs6ODc5Njk3HR0eHh46Ox8iPT4/Q0JCQD07PDo4Ozo5Ozw+PD45PUJBQCA0NzwwSExRT0xSTU2ASo2Qo6hhaGtrbXGAfJFMT1NSUVRWV0uOjqajo6ChnY+LhYGWpqeroqakoaKoppqaq79gZJlwdW1ramtpamxsTWpkZWNkZWtobGljYF1ncntGSEZKTUtLTUxGSkpLTUdGTE5RT05OTGl4dnd1dHZyb3dxbWlpZ2hpa2poaGpnZmaAYWJiXWFmZYFycnBnZGBiY2RiX1xaX2dgaWlpZmhkZGxqbWttXHd1e3h2c3R4dm9wdnJ1dHF0c3Nyd3d1dXJtaHBvb5xNRkJBg4WJj46Mh4N4f4iGf4SEgXiFipyuq6qnp6amoqJYqp2no6OmoZmenZ6Yl5iXh4F6bG9scmt2dm2AeHdjbG9wdGhmcJigoVGThpRSn5SVl0yZk5RMS0iLc2FnbHFnjVVTUlBQUU1TT09SUFBPT02Me0ZHRkVKRT1BQ0VDPkFERDxtRkREQURGQ0FHSUZGRERGdUdKRURERkRFS1BQTk9PT3dDQkFBQkBCRkY/QENDP2w+R0hJS0pJSUaASkdITk5QakE/QUJBQ0VHR0dKR0ZFT1wwL1pcWC0tLk0rKVBUWkRBPj9AQUI+P0ZERkVGaUlLTE9PT0xISEhNTU9SSWxISUdFREhHSEhEZzo3OD1IaGt1bW9paWhvYlxnZ2pqX1uHjod6enlwdHR4cG93bGlnVExUXWFhbW1rameAX2FbV19wPW9TRndpbIyfVbGVZjItKSZJSURBPTtDYFtYSz86OTk4OV07SFYtLi0rKSsrJiUjQz4/ITUuYUdUSldNh7hmg/CB9/rHx1NgTmyjgHfFjdeJRxkYOHV3en2HXGVgY2NkXmJqcnJBZmZjM2RaRktLT1FaXl0wNjg5OjhENzg8Ozo5RJM1Li4vLjUrMT1DREJAPz4+PkQyi05lVUhuamI8RDg3Sj1DZFN0eISAfHdzcG1ramhkXVBITXtfY2NhXl+EXkdiZ3N/gHlhWlhXcHd3eHl5d3Nyd399e3l3bFBteE9KiE99WWFyXVhZWV1aUWBhYl5ua2VmZ2M2NzNhNGNmaWpnYl1YiNzivgR/f4CAhIEKgICAgYB/fn19fYV+h3+Hfox/hYADf3x//4CKgIWBBICAgYGZgAGBhICJf4R+iX+JgJt/goCLf5GAl4HcgAF/hICdfwGAp38FgH9/f4CEfweAf39/gICAiH+QgIJ/kIABf4+AAX+PgAF/joABf4+AAX+OgBB/f4CAf39/gICAf4CAf39/joABf4+AAX+KgAF/t34Nf35+fn19fX9/fH1/fYR+kX2EfIp9JXx8fH19fn1+fn59fHt7fH19fn19fn+BgYGAf39/fn59fX1+fn2FfIt+hH8BgIp/jIAIfnt/gICAf3+NgAR/f39+hH8JgH+AgH59fn9/kH6CfaV+Bn18fHx7fJV7BXx8fHt8hnsGfH19fX5/AgIEAIDI7srglER0d14iNUdvz9TRjcrjzcP0maCjpqywrorb2Ojw+PyBhYaIqMPGx8nK0eDr5uuAhfeKSbSloKSmusbLyrett+Tv597b19rb1+Lm6efk3dzh39vq7u7w7+Tn7OHn5OPi4d7c3dzf4uPf5+ru8Ozl5NDT2ujo6enl4ebk5oDm5+Li4d3g4uff49nW3+Dg2tXI1eLo7ebj5+js8fXz9ff08+zd6ujm6OXq7evr7evr6OPu8vb49/vz9/H4+/v7+vj+goKEgID8/4OAg4Ds7vDw8O7f0MfDv7esqq63yNfg4+v09+SUpJmN/O7j3ujSt6mwvMLnipafsr/T84eQlICNjo6OkJCRkI6J6+Hu8+/o0bXD1NPf9Pr49fr/gP/38fj05OPHr7GrpamprKyvwOP1+Pj8/Pf0+fTq8fTu8/Hv7NjX6/L5iIuKiomLiIeJiIuJiIeGhoTe7Pj79vbz8/Pw8u/u9vPs493Z2NjW2NbR0MjIycnKw7Th29jY4d7f2oDU09fT1tfS1NTSz8/PzcvLyMnJyMmgvry/uLW7ure2srGwra2zr7Gvr62vqaulqKWhop+djvCGhYGAgIKA+/r19PHy7trM3Nz5gf3+goD//vb4+fb68/T28fH39vzy8vP2/fXq2Mq+wMbLy8rCwMrDwsPFxcjHx8bJvLa50OHY7oD+gP3//P79/Pb6/oCBgv+A+dm71oOIiYeKiIiJio2MkI2KjY6N9vaEhYKEi4aLiIqOhomKhoWE65GRkZWVmpmZmJeZmJCSlIaAk5mamZiam5iQjZCPjpGHg5CSj4yRlJCUkY2Qk4+P3pmWko+RkpKXmpeam5ack4KXmZeZnZybmoCblpaWmJXUhIWDgIH/gIKC+4GH+4DXm5ieoZ2dnJuan5+dnp+T8ZmZmZ2fm5KSj4+Ni5GXgoqdmp2enZ2cmIqukpuXlaGozM7X09TL2svGyszGxMnGs6L2hfL68ODQ1d7c1t7i9YDm1LG20sbM0dHY0K6jtMbbgIDsro6I8aKbgID6s+rupZOIhoC0vZOIgqLCvbedjIeEhoaF8tfY6vuB/vXz9fn6gomB9/uGwpKo+uWw1L75ytj0nJH1hdp2rpzo5cud/+Ki4ZNjj7Cfq5mXnpXb4N7h2tfSz8TN4KD0+vv79t3h5uTl5efu8fuJjpGVl5aYlY+XlZPivqaoqeinijWRoKqqpp6qrbCysaqcmIix6Lm4wfnChI25wcrn3tXX39jRz8zIxsLBwb+6tbCCgrjDyMS/voS9R8DJ2ujo5M2po6Gu1NbW19bX09LO2tS/qIrevKOikpX4lYGRjZKPjZGbnKOgsdrFzeje5eqC96WilsLKwcLXlNmInXVij9WUKZKwmKp3RXJpXytFV2urn5Jce4V7eppeY2Vna25tVIB/hoyQkEpNTlBkhHiAen+Kko6UUFOeVYpzYVteYW5zdXJlXmJ8gXx2dnR3eHN5e3p6eXV2enh2f4GCg4J6fH93e3h1dHV0eXlzd3x8d32Bg4N9eHlsbnJ8fH5/e3d9e31/gHt9fXp5dXx4eW5scnJ0cW9jbHN7gXd3e3uAg4SDh4qIhn5zfHx8fXyChX+AfoKAgH96g4aKiomLhYeDh4qKiIeHikdHSERFhohJR0hHgoKGiIiFfXVxb2xoYF5iZXF6e3x/hYd+VGpnXqmgmpWajH13fIGFomJtc3+NlqdaYGJfYF9fYWJjYF1anp6qsKynmIOOnZ+lr7SwrrS4W7WwrLSwpKePdHdzbnJxcnOAc3+SmpuVlpaSkJSOipOVjpOTjo2Ag5mep11fXVxbXlpaXVtfXl5eXFxbl5WbnZqbm5qUkJORkJaXkYqIhoaFhYiIh4aBgoKAgH98nZaVlp+ZmJKMjZCRlJSPkpSRj5CPjo6PjI2OjY5uhH6Ee3uAgn58d3d2c3N6eHx6eXd6dneAcnVxbnBvbGGcVVRRUFFSUJ6el5mYm5mKg5STqFerrVpZrq+oqKunrqSmpqOjqKmrop+do6umn46DdnJ1e3p7dXiGe3x+gYB/fXd2eW9sc4iWj6GtWK2rp6qmp6OnqFRVVaZToo15jlxeXlxgXVxcXV9eYF9dXl9eoqFUVlRWWlaAWFVYXFZXV1VTU5NbWlteXWJhYmFgYmFbXF5YVGBjYmBfYWFeWVleXVxeWFJaW1lXXF1ZXV5ZWl1ZV4xmZF9cXV5fY2ZjZWZjZWBUYGJeYGVlZmZmY2RkZGGBT1FPTU2XS0xKh0lQkEmAY2BkZmRjYGBhZ2ViYmVfnWVlZmpraGCAYF5bWldcYVRaZWNkZGRlZWNbalZcWVdfaoaJk5GQipSHg4WJg4OIhXdsqVulqKKWh4yWjYqQkp5Tlot0eZCGjI6PlI91bXeEkllZpHtgXqJydmXNjbudVUQ8OzlqZFlXU2yAfXloW1dVVlVVkHl+ipNLko6Mj4yHRUlHiotIcFiAaqKVa35ujHykvnVnr2OvX46FuquYesWndLB4UHBXRXlycndslZqYmpOQjIuBhZNpn6WlpKCIhoiHh4iLkJKgWl5hY2NiYmFeZGNp+Xlqa2uealVaZm5ua2dxcXN2eXl2ZWKJqX19hKqKWmR4bXmYkY2OlI2HhYSCgX18e3l0bmhTTVJ0fYB+enl4eHh3eoCMlJaUhG1pZm2KjIyLiouJiIePinxqUH10ZmlmaLZuYXZzdXRzdHl5eXV+jYKFkI2Rkk2XXVtXgo6Li5domm+BaFyDp2qAaYFYZn+K2a3igtHt2uyzkk9ZV1FOYz0/QkNGSEUvOjc+QENCIiMjJDVKS0pJSUxTV1JSKyxVKTUvKCMnKCsqKCgpJycuLysnKigrKScpKSgpKikpLCsoLS0tLi8sKysmKigmJicnLS8nKi4tKy8yMjIsKi0mJictLC4xLSovLi+AMTMxMjMwLiowLS0lJCgoKSYmISMmLDMqKi0tMTIzMjY5OTgzLzUzMTMyOTw1MDQzNTUxNzo9PTs+OTk2ODo5ODc1Nh0dHhwcNzcgHh8gOjxBQkJAPjs7Ozw7NTM1NTw/Ozk6PT49MlZXT42LioaMgnl1e4GBm15obnmEhI1ITU2AT1FNTk5QU09MS4aPoKejopqHkqWnp6itqKauslawqKaurJ+khmluZ2NnZ2dmZGtvbG1oaGhkYmZhYGpsZGhpZGNbXnV8g0pNSUlJTUhHTEpOTk5PTExLem5yc29yc3RrZmhnaG5uaWVlZWRkZWlpaGlmZmVlY2RujYSIiJONioSAe3yChYiIhIiKiIWIh4WEhoWEhYWGZ395fXVze3x3dG5vb2tqcnF3dXVzdnJ0bnJsaWxqZ16JSUlEQ0VIR4iKhIaIioZ2dIyLpVapq1pXrKykpqairKGhoqCfpaatnpWUoKujloh+al9jbG1uaG17bm1vdnNva2VjZVxaYH6Og5SAnlCenpiYlZaSk5NISUiNRop1YXpTVFRTVVFQT1FSUVRSTU9QUIiBQ0VDREdDREBESkRDREE/P3FEQkRHRUhHSklHSUhERkZAQUpLSUdFRkZDQUJHSEZHRDtAQj89Q0NBREQ/QENAPmdNTEdCQ0RFSUxJS01KTEk7Q0NBQkZHSUqASkZHR0hFVDI1MzAxYC8vLE8rMFctUUNBRUdEQz8/QEdGQkFFQHRLSkxQUU5HR0RBPzs/QjlAR0VGR0ZHR0VARjM2NTU+SmVrdnJwaXNoZGVnY2Vsal1UiUiBgXx0a2xxamdpanQ/b2dZWmliZmlrbmxZUlVcZDk4alhQToZ5dGKAo3i1aDQvKionR0U9PDlKWVdVRz06OTg4OFY8RVFVKlRRTkxHQyUnI0FBITIoNl9iSFNIVE6IrmVq34j1gMW2mGphfsWRgfS/jMN4Rnx4eHxYZGZlZmFdWltUWF9BYWNiYl9MR0hHR0hKUFBeNjc5Ozo4OTk4Ozo9g0M7Oz1ZNi2ALzg8Ojc2PT0+QURXYzg/ZHNNTVBpUjE5RDhKfXZxdYGAend1dHNvbGtoYFdQM0BkZ2hmZWRjY2RkZWp1fn99cl1XU1pydHR0c3RxcG54cl5JMEBGRklJTYdRSFpaXFhUWWJhZFlfZ2BgZ2RmZDRfNTEwWV1fY3FQkH66vLPjyFeCgISBCoCAgIGAf359fX2Gfoh/hn6PfwaAgH9/fH//gIuAhYGCgISBmYCDf4x+h3+NgJJ/AYCTf5aAkYHegAF/h4CMfwWAf3+AgLN/AYCJfwWAgIB/gIR/kYCCf5CAAX+vgAF/noABf4WACn+AgIB/gIB/gH+PgAF/mYCZfgF/jH4Bf5B+gn+Efgh9fX+Ae3x/fYV+kH2FfAF9hnwnfX19fHx9fX5+fX1+fn59fHt7fH19fn1+fn+AgYGAf39/fn5+fX19hHwBfYt+kH+LgAh+e3+AgIB+f4yAA39/gIh/BYCAfn19t34BfYV8A3t8fJJ7BXx7fHx8hXsJfHx9fX5/f3+AAgIEAIC5tWuCgnZVIjREbtjS1I/C3MS01o6hoqCgpKqvrIDg5vTz/IGDio6RscbMy8zQ0dLU1dXX4d+DSaqcoKqqtMfSzLywsMXg2cbY5fH59Ojs7u7v7OTp5+jo5OLg3d7f4+je4d3m5Nvf5ebo7OXh6urm4tzZ5N/o5+Dk2tPb6erp6YDn7e7p7O/s3uLd1t3h4OHp5t3i5ejs7enm7vLz8fPu7+ns6+Lg6Ovn5uXm6/Lu7erv8vLu9fn7+/769vn99/jt+v71gYGCgYOFgf/u9fb/9vDu7/Xv8OTVubS2tbO1rri8vLyu6YL58d7Px7WXjpKcnJ+5zdHa6Y+01oKTj4uNjoCMkY2Oi4yMiYmJionw5+zMuNDQzs/h9/f0+fz/gPz8+/jv7/Tw1sKsqaegoaeuo8P8+fX1/fz9/Pvz8/j18uvp7+/v4eHcv/X28u39h4eIh4KA/4KDhYLG5+nu6ujl5+vu8vLy8e/u5uDe3tbZ2dPQz8/N0NDMt8vf39/c39/h3IDb1NDUzc7O09LS09HNzcvIzMjLysi6pb++vLy6srO4tLOwtLa1tLGyrq+tqqqkpaKln6CenvuBgICB/IGD9+/e4OPg4/Dm6YODgoKD/YD5+/r38/b7+vv//fn4+/z9+PTx6dXCxc7FxsXBvrO7xMzCvsjHzs/KydPEvcKxx+2A/oCA/fj3+v37+Pv+gYCAgYKC8dTyh4eIh4mIhomLiI2Pi42HjIjm/ICEgoKHhIaLj42LiIiGhoHikJSPjpOUl5WUlJSXlpKQivCVlZycm5udnJ2dnp6ZmZfzkY+OjpCVlJKSkJGQjYmD45GRko+VlZSXmJmamJubg5CXm5mYnZydnYCemJmYmZbYiYOCgYGAgYGAgIqKg4fjlJ6cn5+cm5eZm52bn5+d5I6RjI2RkpianJeVlJiYmOWYmpydm5WWkOKTi4aPipaWreXh2NTb3dnRxca+sqKRjYya3u/l4NjNz9bZ3uH09/Dt79a6qsfEvqq0tLq3v8fWgPjfr46Jg57olF+N8sH4rqCM+IXcopmUj7y/s66ZiYeGhoKE6tfR3vWC+vr59fL2i4Hz+4CG1JaqlY+L9bXUxf7S8f+Z/9mMevmhfJTs5+uC/LiPyoFZlvLKuNyhr7ve5t7h0sfHyrTvgISBPoKFgezr7fHug4uQlJmZl5eQl6Cek+LMsrSx1rKXoaWloJyPrrK5u5+3mbD0raWuv9faxfuU4qC55drT197VhM9bzMnKysvFyMaxn8fN1dLNxcbFxcvM09vi4t/etqWioLzc29va2NK9n5CEhPzykL60tKyagoOGoI2WpKaiprnMzsjn4trj5OiHiY6Un5rVyvq+gptpWYG4gbfWiYCNlF6Ac2ldKkNWZquZlF97hXVuh1lkZWNjZWpubUyDh46NkkpLUFNVa31/fHx/f3+AgYGCiohRi3FfXWRja3R3dWlhYW58eGt1fYSIgnl+fX+DgXt+e35/fXx4dnZ4eXx1dW53d3J1e3x6gHp3foB9enRveXZ8eXd6dG1ye35/f4B/hIOAgYJ/dHZ0bnJycXJ+eXB0d3l/gHp5foGDgoOBg4CAf3d1e398e3t8f4aDgn+ChYWAhouNioyIgoaKh4Z/hoqCRkVGRUhJRop+hIaLgoOGh4qDhoB4Z2dnZGRmYWhnaWxji1SoppeNjINuZWdtbm9/i42SnWF8kVpmY11cXYBcYl5fXV1eWlpbXF2loqqShZqbmJmmsrKtt7i3Xre1srOtsbawmYV0cXFsbm9yaHqfnJeWmpmal5aTlZqZlIuLkJCOhIeFeKSkop+uXFteXFdUpFVYXFt8jI2RkZCMkY+SlJaXmJaXk5GOjYqMjoiGhoeEhoaGeY6dnZ+cn56hl4CWkY6Sio2OlZSTk5CLjIyKkIuNi4l+coKBfoB/eXh9eXd2fH1+fHp6d3d3dXVwcW9xbW9ta6VRUVNVn1FUm5eLi5CMjJKPoFpaWFdZq1enqqqooqSpqKuvraqrq6usqaOinYp2dHt2d3d1c251fYZ7d4F9hYR6dXxwbXdzhaBXrExWrKmnqKqmoqeoVlNTVVZWn4ymXl5eXF9dXV9fW15fXF1aXlqWplNXVVNXVVZZXVtYVlZUVFOPXF5aWl5eYmFgYGBhYV5cWJphYmVkhWKAZWhnYmJfmltZWFhZXVxbWlpbWVdWUZFeXVxZYGBgYmNjZWRmZVdeYGJhYWZmZ2dpZWVkZmKFVVFQTk1MTUtJSFJTTk+HYGVjZmdkYl1eYWRjZWZlkl1fXl5eX2JkZF1cXF9hYpFiY2RmZV9gX5NYUU9VUVtZc52al5abm5OMg4SAgHdrXltZZJakm5iQjYqIiI+SnJuWlJiNfnKHhoBze3l/fYKHklmtm3xiXlx2sXVtxp6mX1BAdkB1aGFdWX2BeXZlWVZVVVNUiXl6gY5LkpOSjouJS0aGiUVJeF5sXVhWnW99cZd+q8d2tJtmXbqIbXy4sLlowYdun25LdayKgpCAZ296l52Ymo2Cf4F0nFZXVlVVVFZTlJGTlJJVW2BiZWRjY15lbGpt9oV0dnKScV9kZmViYFt1eH5/bY1nesp6cHaBjpKOtnSSW2qUj4yNkYqGhISFhIGCgX95eXVqYHyChoaDgH9+foKChouRkpCQdGdlZHmNjo6OjIh5ZFdMS40vh1FybW5taWRiY351eoF+enh/iIiElJCKkJGSUlBQVl5cjou0jF96XFB0l1+GnmaAV22A/MK++ojO5svprpZSWFlRTFg4QEFAQUJFSEYnPT5DQkQiIyQmJztPT0xMTk1MTE1NTlFSJzQ4LSgsKiwqKSwwLi4vMTEsLi8wLy0oKiktMS8rLSosLS4wLiwsKykqKCchKCgmKC0tKy8tKy8xLy0pJSsqLisrLSsnKS0vMDKAMjU1NDY0MSwvLSgqKScoNS4lKSkqMTEqKS4xMjIzMjU2NDUwLjM0MzIzMzU6NzYzNjk3Mzk/QT0+OzQ3PDg4NTc5NB0cHBwdHhw6NTk7QDg8P0BDPEM/Pzk6OTY2OTU5Njg6NllJmpuPh4OGcmlscG9ue4aIjpVYandHVFRMSUuATFFOTkxNT0tLTU9PjpKhioWgoaChqK6sprGxrlqvq6mrpquzrZR7Z2JjYWNkZFhYc3Bra29tbmloZ2pwbmlfXmZoZ11gXViEg4B/jktLT0tGQ4FFSE1NXmRlamtoZ21qbG1ucHNyc3FvbW5rbnBqaWpraGlpamCAjo6TkJOTmImAh4J/hXyAgouJiomDfX9/f4iCg4F/d2h5eXd4eXFxdnFvbXV3d3d0dXJzc3FybGxqbWhrZ2SYRUVHSodFSYWEfHyAenp/fp9aWVdWWatWpainpJ6iqaapraqmpqeorKafnpmDa2RpZWVnZ2ZfZnB7b2hybXRzZmJoW1hjYXaTUJ2ATp2amJqamJOWlUtHR0hJSIZ5mFVUVVJVU1FUVU9SU05QTFBMfoVCRkRDRUNESEtIRUREQUE/bUZIRERHR0lIR0hHSUhGREF4S0tNSkhHSEdHSU5NR0dEckFAQT8/QkJCQUBBPTw9Om5GRkRBRkZGSUtKS0pMS0BBQUNCQkhHSUuATkpKSUpGWjY1MzEwMDAvLSw0NDEyVERGREdIRUQ/QENFREZGRmlERURCQ0NDREQ9OztAQkNiRUVGSEZDQ0JmNDAxNjQ6OVeDf3h1enpza2JiXlpNREFAS3F/c3NubmllZWlob2tpamxkWFNlY19VWlpeW11hYjhtZFlQTlB/sHWAXp+ZdDkxK08rUExEQDxXWFJTRjw7Ozo4OEk5RExTK1NVUk5HRickREQhIjUrMywqMGlJUkddT4ehXrCyiYj/t6GBcXKbdbGGifW8jMjugXliQEZPZWlkZlxUU1RHXzQ0MzMzMjMxVFBPU1IyNzo8PTs5OTc8QD8+hUxBQz9POTGANDY1Ly0tQEJGSD+APkOtU0ZJTlNWVHNVXzREeHJvdn97eHZ1dXNxcG9uaWVgU09qbW1ta2ZnZ2drbXB0eXp6emNVU1JjdXZ2dXNuX0Y4LS1NRzFLSEtMTExJTGJdYGViZWVqamRcYWRkbGpkMzM0MjM1YWODdXCzqJ/N1FdacESDgYSAB4GAf359fX2Gfop/hX6UfwJ8f/+Ai4CHgZqAgn+RfoN/koCQfwGAkn+dgIaBAYCEgd6AAX+EgAN/gICKf4WAAn+AsH8DgH+AiX+GgIN/kYCCf5CAAX+QgAF/j4ABf4+AAX+egAF/joABf4+AAX+PgAF/iIC4fgF/hn4MfX6AfHt/fX5+fn1+kH2FfAF9hnwHfX18fH19fYV+IH1+fn59fHt7fHx9fn59fn+AgYGAgH9/f35+fn18fHx9i36Cf4iAhX+MgAh+e3+AgIB+f4yABH9/gH2Jf4N9tX6DfYh8kXuGfA57e3t8fX1+f39/gICAgQICBACAk3uEayUfMkRt1s/TkMLSw669hZ6eoKOhoaSqrrL64uX39v+Ch5CUlrvGxc7Sz9PW2tna4N/Q80mijaKlssTHzcbb3uHlz8m7xd/q8/P07evt7uvs6+zr5d3Y3Nnc4ufn6+Xn6urq6+jk5+Th2eHh39/n5+Lp7+jr6+bi3e/s6+oW6OXj5Obj3+Hj4+De5+Pl7unr6+fv9ITugPL17+Xo6ens5NbO3ePo6+rt8fLz8O/z8fTx8fH08PH79fn3+/Hs9vn1+/r8g4T5+O/3/oL6+Pru6erm7Onn5+Tcy8S5sK6upN7jp7/44cXIxKyZi5Cbr8/l8oaMmbTJ8ZOMjYuGgoGLjo2FhYaHhYeHhoKD58LJ2NTS2Ob79vXzgPX39vT49/P2+ffz8e7v4MKpm5SZg/T8/vz89vv59vf6/YD5+PT29+/y6uzU8ImIh4T89O7t6/D/gIKAgdjn8fHy8vDx7ezv8evr6evo5+Th3tfY0szLz8rN0M/TrOTl4d/g4ODf2dbb19TT0tTV09HQz8zKx8rIysjGxcaYvbi6gLe2srCur7KztbK0s7SwsrCrqqqnoqWjo6SioJ7d/f/x7ezs6PH18/Lw6+zr2OyDgoSF//qA9O7tgPz4//vx9fr5+Pn54M/Ey87Lz8K8v8K6sbrCwsbExMfLzMvPxsPAuczv+/r+//j29fn9/vvxgIKAgPiC/dPghIKEg4SHh4iGgIeGio6MioWHgOSDhIT+/P7/hoOHhoiJiYiGgsWGj5CMlJOXlpaVlpSVmJaT4ZmYl5yYm5qbmZydmpiamIGLkoyLkJCRj5GTkJOQjY3u84aEgYOJjJOWmpmam5qd/5aXmpuanJ2em5yXlpeXk9WGhoaJh4CC/oCAi439/oH0naCggJqdn5qbm52cnZybjYiYm5mamJOTmpqZlJmVlYvmnJ2ZlpqYqJKI/YKFhoSCgcPi1dnR197AppmTkI6Rko2Lj77j08zP0Njg0M3mhOnY1c/R1aeep7W3x8a5u8zjgIHuuo+C8JmXn83pgfCUkufXt4T3ppqixrqyspWEhYGGgYHgRNba44CFgoP5+f+Ch/7v/oKJ3p+wkpXPuLb6vNjJguWFkZvkvIKEfZFxvYvOoZ/z1amHYIdZjq7njaPi7/Ps5+LZ3NDxhICAg4SFhfmBgIGCg4SHi42am5yZm5ybitjKrrGu97qampqYkpGJo6iurbnAgIrcnaS1y869t6WIm5HR59vU2dvT0dLR0NDN0NDQy8/PyMvRzdLT0c7R0c/S09ba3d7c3M+qn5ufz9za297eroSChIL3xdvRuryzraeQkbikoqy/zMsey8/T1cPQ4+WA+IWYlJix2eT5l2JPfayAt9eGvcmHgI54b2otKkJSZKWSkV12e3ZtelNiYWJkY2RlaGxvk4WGkpGWTE9UVlpyfXyAf3t+gYSDhImIgJqIalViZWt2dHd0fn59gnZ0bXF8gomFg4B9foF/gX9/gHx4dHZ0dnp7dXl1dnp7fn59en56eXJ4enh3fHp2fYB6f396eHSBfX9/gHx5dnh8d3N1dnd1cndzdX93eXp4gYqAfX1/gISAdn1+fYB5b210en5+foOGhYaEgYSChYKDg4eDgYeEiIOHgXyFhYOIhYVHSYKEfYSKR4KEioWBgHqDgX5/fnxycGhjYWFfhJRzhaygjIyKfW9nZmx5jJikWV1jeYmmZV5fXllTgFNeX11ZWVpcWltdXllbnYuUoqCfoqy5srCxs7S0sraxr7O2tLCwq66hhXFoYmRSlp2fnpuWmpmVmZ2dUZqal5aXlJWMkIegXlxbWKehnJ2bo6ZTVlVYj4yPj5CTkpWTk5iXlZaVl5SUk5CPi46IgoKIg4eIiI50pKKen6GgoJ2VgJOYko+PkJWVkoyHh4SFh4yJi4eHiYtogXt9e3l3dHJ1d3l/fX57fHh9eHR0dHFscG5wcnBvbJamrqKXlZeVmpiVlpaSko+DoFhYWVqnpFeln5xWq6iwrqapq6qmpKWThnt5eHZ4cG1zdm9odHt8f4F/goSCfn50cG5zhaCrqaqtgKilpqisraicVFVTU6NWp4qZWllcW1tdXV5dXl1fYV5cWFpWlFZXVqSipKFYVFdWV1dYV1RRfVdcW1lgX2JhYmBiYWFiYF6QZGJgZGFiYWFhY2ZkY2NhUFZZVlZZWVtbW1xZW1hWWJKfV1ZTVlldYWJlZWZmZminXmBiY2NlZmdmgGdkY2RkYINSU1JVUktOlEpJUlaWlUybY2doY2VlYWJiY2VkZWNaVmFlYmRiXVxjYmFbYV5fWZVkZGJiZWJtW1CSS01PTk5OhJ6Tl5KXmoBrYVpYWVtZVldbgp2SiYyOkpaKiJlYloqMioyRcWxweHmIiYCCjZxYWqmBY1mtcW99gKPFbKhfWoiAa0xwZGNpiH13eWNWVlNWVFOAeYCDSU1LTJCOkElJioSMR0l/Ym9cXn9yeZ1xgHRMj19pc6CEX2Jfc1+pbpSAgMOefmhPblF+e5dbap2mqaSgnI+Mhp5VVlZVWVhYWZ1RUFJRU1VYW11lZWdlaGloZ/SIcXRyqnZjgGNhYFxbV21wdnR9lVhit21wfIuIgYiAXVtRe5WNio2OiYeGhoaFgoWFhH+Af3t9g4KGiIeDhYaGiIeIiY2Qjo6EamNiZISNjY6RkWtPTUxKjXCDfnJ0bnFuY2aKf3x/h42MioqKiH6EjY9NllBZVlpjfZCwc1dQeZFfgaJij5ptgPfitNiIhc/fuNiiklRdWFNOVDg/Pj9AQEFCREZHRz8/RUNEISImKClAUE5QTklMTE5NTlJRTkwwNy4yLjAyLC4yOTo5Ozo7OzozMjUvLSwsKy8uMC8uLy0sLS4rLCsqJSYlJSksLi4vLS8tLCcrLSsqLSwpLTAtLi4sKysvLDAwgC8tLC4wLiosLS0sKSsnKTIqKispMj4xLC0wMjYzLzU1NTYzLS0vMjY1NDc5ODk3NTY1NTQ0NTs3NTg3OjQ5NTI3NjU5NjMeHzQ3NDc8HzY3QD09PDY+PT1APkA7PDk2Njc3VYBofKWei46NhnZram14gIqTUlJTZW+LVk5PTkhBgEJPTkxISk1OTE9SVE5Pj4SXqKWjqKuyqqeqrK6sq7OqqbG0sa6uqKqaeWRbV1k+bXJ0c3BrcG5rbnJyO25ua2tta21ma2V/TEpIRYV/fX95g4ZCRUVJdmRmZWZpaW5tbnJxb3JydHJ0cnBxbm9rZmZraG1ubXJfl5WSlJiVlpOKgIOHg3+Ag4qLh3x3dHF2fIR+g318f4Rhe3R1c29vbGlsbnF5d3h2d3J6dXBvb2xla2lscGxpZ4mQnJOCgIODiISCgoOAgH92nVZWV1igm1ajnJVVq6qxr6Skp6SenKCOfXBoZ2VmXl1jZ2FaaW9tcXZzdHRxbGtgW1ljfpOdm52fgJualpicm5WHSkpISIpLjnaKUk9SUVJUU1NRUk9TVlJPSUxLeUdIRYKAg4RJRkZDRENEQ0E/YkVHRkRJR0pJS0lKSUlKSEZuTk1LTklJR0ZFSExKR0dEOT1BPj0/PkFBQkNAQT48QGl2QkI/P0JFR0hLS0xNTE59QEFDRUVISUpJAUuESIBEVzQ1NDY0MDJeLi0zNl5eMWpER0hERkhFRENDRUZGRUA6QkZDREM+PURCQTtAPkE9aUdIRkNGRE48MlkuMTIwMTJqg3Z6eHp9YlBFPj8/PkA9PEFme3RucG1wdWdkdUBwZWhmZWhTUVNaWGNkXmBnazk4aVxPR499cX2WkWdsMoA3X1k8KUxEQUliVlNWRjo7ODo4Nz05RUwpKiosTElMJydFQUQiIz0tNyspNjhTcEtVTC1ZSV5kj5yAi4igmepVYlqL0JuOlYXWleaKZjhHb3Z4c25nYGBRXzQ1NTU2NjY3XC4tLi8wMTE0NTs7PDo9Pj09gExAQkBcPDQ0MzAsK3ssOj1BP0mIMTqeRkZNVVJQWlpINjJVdnBud3t4dnRzcnJxdHRzb29tamxwb3FxcG5vcG9yc3R1d3l4eHFYUE9QbHNzdXd3UzIvLyxHO1FRTFBNTlBKTWhlZ2Zpbm9wbWtmW2JjYDJnMzU1NjpGa7mom6rz8FxYckBRYHGEgAiBgYB/fn19fYZ+i3+GfpN/A358f/+AjoCCgYWAAYGUgAF/kX6Gf5SAoH+NgAGBi4CEgYeAhIHggJJ/hIAHf3+Af39/gLV/hIAFf4B/f3+SgAR/gICAhH+KgAF/kIABf5+Agn+OgAF/j4ABf4eAAX+EgAR/f4B/n4ABf4aABH9+fn2jfgF/kX6Cf4R+Dn19foB9e399f359fX5/j32EfIR9C3x8fH19fHx8fX19hn6CfYR+hXwBfYR+Bn9/gYGBgIV/BX5+fX19in6Cf4iAAX+QgAh+e3+AgIB+f4uABX9/gIB9iX+DfbV+gn2JfI97Anx7iHwIfX5/f3+AgICEgQICBACAdy0gHCw+ct3Jz422xriosPCSmZ+fn6Kgpamusq3y5OTx+4KGipOUmb/IyMrN1NXX3t3d4uLS9EidjJGmrcTOvLjB7u/z4tDJwsbXzcLO6ern8Orl7O3n6ubn5NzZ4+Xj5ejk4N7h39zi4OLf5+jn6Onv7Ojn7u7s9e/s6unk5eiA5ePf5d7T2uTq6OLm6evs7+/x9O/y8ujq6Ofm5+Xk5u7t6urn7Ovo7Ort7O/z8/T19O/v9fX38vPz9/n18vHz7/P0/4CAgoSGgPv59P6FhISB//rs6uri6Ofo7vHq4N2+5+jH1dDBwOXZ3MSzsqy51vT2hY+lxNre5+LahYqGgPCA+fGAh4qHhoSGiYiEg4SA38G3vtDP5vP1+fby8vL19e/v8PLz8fLt7fHs6N/GqMru8PH0+vn9/f77+PuA///8+/f18/Hj5IaHh4eGhoWEhIKD7ufq5uK83O3x8fLz7unn6uvp6enn5uTl5ODW1tjX19XV0dDU06zk4eDg3N3d29mA2Njb1dPP0dHNztDR0NDNz8nGxsHDxZm/vr+6u7ixsbOysbCysLGwra2wq6mlpKWgoqGZjYmF7+zugP7+gYD69O/38vP37+bQ84OBgoL9+//8/YD6gvqBgYGA/dnUxszS0MrJwr2xt7a4tbrBwbzHyMbJyMnK0bu92PH2/Pz//v2A+fyBgPn69/n6gIGB/9fP9oD/goCDhISGhoOHhImIhoaF5u2D+4H/gf6BgIWFhoqIiImF/s788ezv9oqVk5iWmpaSlpKQ45aYmZicnJqbmpydnp2cm5TqkI6OjJCSk5GSjY+Tjo6N4pyZmpuZlo6LjIyPjYyNkvCXmpuYmZiZmZeAl5eWmJiVxvb3+Pr79/yDgIWJiYiIi9ecnaGdnJmanZmYmpmamZrslZWXlZeXlZqYl5WWl5CS7Y2dl5mZ7JaWlfOJhoKJhZGQxNS/r6CQlpaVlJSXk5WZlZaQuMbJy8K9v97p5NbV393Du7C9yK60ucC8tra88YeA9NCPg/6T6YeAyrzIg7SgjYL/i4e6l7bIubGqkoH6/4OA/OTc2+T/hYeFgIKJi4T3/fiBitWitJaaxL3h+rL7uNrJkI6VlJ/MouuMgY2/k5bE1YeUg9mliGKEYVHRmcri6fPx9of3+/v58PmEhYaB/4H//PqAg4aHlJmcoaChoZyK3dS0tLSJz6+AppCNjouNlZqclpqDrN+mnZifqbfSnLeAio+S5NfT2trV09PT1NDL0dXUy8zNzM7R0dTT0tXW2Nvd3eDi4eLh39vCm5eXq93f397cr4iGhvnv8dLEvbXGvbepg8SpucLI2drX0s7Q193W3+qWkI3Y8ZClY1CCVfSn5/DG5r25hoIpbjMuKT5LYKOOkV1weHBrcplbX2JiYmVjZmlscGyQhYaNk01QUFZXXHeEfoCBgoOHhYSLioCYiGdVVWVrd3lubHmJiIqAdXNwb3Zxa3OBgHyDf3qAfn6CfHx6dHF5fHl8f3x5d3d0cnl4enh8fXx8fIN+eXl/fn+GgX9+fHl7f3t5c3d0bXJ5e3h1dnl7fIB/gYSBhIh/fX18en16eX2Dg4KBfYKAe399gICDhoCFhYWDfX+EhoeCg4SFiYV+foJ9f4CKRUVGSEpFhISBiklISUaLiYCBg32Afn+FiX94e26LloaPjIKDo5aWiICAeYCQo6NXXm6Hlpmfm5ZaWlZUmp2XVVhbWltbXV5dW1tdWZqEgI+gnK23trm1tLSztrWtra+wsK2vqqmuqKCdh4BufZKVl5icm5+eoZ+eoFKioqCgnJmYlYyXW1tbWllZVlZWVFOXk5STk3OBi4+Oj5SRjY6RkpOUlZSTkZOTkYqKjIyNjYyKiY2NdqOfnp+bnJyamJiUlZGPi4yOiIaLjY+SkZGJiIaDhIdpgoKDf4B8dnV5eHh3e3p7end4enV0boBwcWxubmpgXFmclpdSo6BRUJualZqYmZyWjoGjWVdWVaeorK2vWKtaqlhZV1askIx8dnl4dHNsZ2BnbW9udHx6cX5/foF9eXmBcnqSo6erqquqrKqsWFeop6CkpVRVVaiPiqlZsFtYW1pZW11bXlteXVpZWZmeV6RUpVSjVVJWVYBXWlZWVlSgf5KKi5edWWBfY2FlYV9hXVyTYmNkYmNkYmJhYmRpZmVjXpFaV1hXWVpbW11ZWVxYV1iTaWdpZ2RjXVtaWVtXWltgmmBjZGFiYWNiYmJjY2RjYH6dn52gnZmfUk9UV1dZWFiEYGFmY2RhY2ViYWNjY2Jjl2BgYV9hYYBfY2FfXl9hXF2VW2ZjZGWcXV1ajk9PS1BPVVeLk4BwZVtdXFxaWVtXV1tZXFx/jIqLhH+Bm6KckI6XlIJ+eICLd3d6gYJ/en+mXVinjmFbsnCtbKekrllgU0o3Zjc1a2R6hnx4dGFWpqRUU59+eoCGk0xLS0hISktHiIqHR0p9ZoByXmB7dYKZbp1wf3NQUWhsd49wqWpjbqOFgpOjbHhlpX5vU3pZRpNqkZ+kqKaoWaGjo6CdpFhaWlakUqKgn1FTVldhZWdramtraGDuinR0dGCReXFbWFpYWl9iY2BwX3egfG1nanN+lHiHT01MVpOLiY2NiomIh4aEgoWGhX+AgVN/gIODh4iIiIqMjY+PkI+PkZKQiXlhX19rjpCQkI5sUVBPjYeLfHh7dXt2cmxfknuEhoqUlZGQi4WHjoiRkFhUWH2XYHpWTX9Ltnekso2tlql/c4C+hJ+Ov7+iypiRUVlcVU5SaDs+QEA/QD9BREdIQ0U/P0JEIyMjJygqRFFRUk9QT05PTUxRUk5MMzMsLDQ1NDQzNUg+Ozs5ODs6NjUyMjU2NC0xLysvLy4yLy8uKiosLysuMDAvKysoKCwsLSotLi4uLDEtKisuLi8zMTExMC4vMoAwLyksKygrLi4rKissLS0vMDAyMTU9NjM0NTQ2MjI2Ojo5ODQ6ODM2NDUzNzk3Njg1MTM3ODk2ODg4PDgyMjQxMzI4HB0dHx8dODg2PCAgIR46QD1AQT0+PT1BQjg0PDtafnqDgHh8npSbjoeFe36BjYtIS1xvfX+EgX5MRkJDeYB7eENGTE1NTVFUU1FSU1ORf3yTqaOts7K1saysrrOwp6eqrK+traWjp6KYlX1cWmpsbm9zcHVzdXR1djx3dnN1cm9vbWh4SkpJSEZHRENCQUF0cnNzd1RZYWRkZWtqaGltbm9xcnFxb3JzcmprbWxub3Bub3FxZJaTlJSQkJKOi4CMh4qEgHx/g3p2foGFi4uKf357d3uAYnx9fXZ2dG5ucnFycXVzdnRxdHdwbmZqbGdpamZaVVKOg4RIkI5JR4mFgImGhoqFfnakWFZVU6Onr66vWaxdq1laVVOkgoFwZWhnYmFbV05YX2JhZm9rYG9wcHBoZWRsX22ImZydnJ+gooChoVBOl5iMjo5JSUiTfXiZUaBSTlFQT1FRT1NOU1FNTEqAhEmGRYFDg0VCRkRFR0RCQz97XGFdYnR8RkpIS0lMSkdJR0VwTU5NS0tLSEdGSEpPTUpIQ2Y+PT4+P0BCQ0RAQUE+P0FvUlFTUExMRUJCQ0NAQ0NHckRFRkVEQ0RERIBGR0dISEVYa21ra2dpbDc1Ozw8Pzs8XEFCR0RFQ0VHRENEREZFRWpAQUJBQkJAQ0E/PkBDP0BoQklFRkZuQD88WjEvLjIxNz1yeWNXS0BBQD8+P0E9PD88PkBjdXNya2dpeX53b3B1c2NgWmFmV1ZaXV5cWVhwPTluYExIkni2boCthahDPDEwJ0UjJEQ/U19XVFBBOm5tOTdmPj1FTFEpKiokJSkqJ0VFQiEjOzA6LiozNFt0UnBMVk0zNk5eaHx52paZnP7Ni2ZnV4ppnKC1mOy1gKRPZnJ0dHByO2BgX15dYzU1NjRhMF1cXC8wMDE4Ojw+PT4+PDmBSkBAQjZPRYBALissLC0vMjIxXE1DYFJEPDxDT11RXTQvLz9va211d3V0c3Fxb29zdnVubW9wb29vcnNydXd5eXp6fHx7e3x6dmZPTU1ZdXZ1dXRVNjAuSkZVUk9ST1NRUVFGcGRraWtrbGxwbmViX15gYDY3N0xtY7CcpPqBx1Zre1FgcO/pwAqAgYGBgH9+fX19h36Mf4V+lH8Dfnx//4CLgIaBhICEgY+AAX+Rfol/hICDf42An3+NgAGBioCLgeWACH9/f4B/f4CAi3+EgIV/BIB/gH+EgKh/goCFf4OAhH8CgH+PgAh/f4B/gH+Af4qAh3+LgAF/kIABf4+AAX+PgAF/j4CIf4iAAX+PgAF/j4ABf4WABX9+fn59tX6Cf4R+B319fYB+e32FfgN9fn6JfQR8fH19hnyIfQZ8fHx9fX2GfgR8fH19hH4BfYR8gn2Efgd/gIGBgYCAhH8Efn5+fYd+h3+EgAV/gH9/f4yACH57f4CAgH9/i4AFf3+Af36IfwN+fX21foJ9inyQe4V8EH19fn9/gH+AgICBgYGAgIACAgQAgBoWIzts09Xfk7G2r5+E4fePkpicn6OkpKapr7Kq897i9P6Fi5GVl5rDy8rIx9HX2+Pk4eTk0v5EppOVmbOesrazosHNyt/26NnCzdPQwNjw7O789/fy8O7s7uPe5drY3dvU3ePj3uDb2eDq5ePp8PLv6uzz8O/s7+zr7evn5erqgOXl3+Do5+bj7+fp6ebs8fHt7uvs7eji5OTk4uTk4+rp7ezs6+3x7uvr5+bu8Ozv7vTv5u3w9fTv8fX29/Ly8Pj7/IGCgvf2/4P28v2DgIOCgoOD7d3p5+HZ2uHh5t6XnoyOiYiGhov9/4f/gYWRio2Zk7DM4ODg4uXy7OHagYL9gPX1+oKGiYeF/oCChoeD6dbV2dC2wuf38PDu8vHv8O/q6Orr6u3s6+7w5+vq58CF7d/Q5vL08vPz+Pv9+/v29vLw7u/h1IqIh4WHhYSEgoOChIL+gP79/O3j39za1NPN2OHn6Ofo6Onr5+Pd2dvd1dPMzNDLyM2n4d/e29zc2N3YN9jY09HP0c/OzMzR0c3Pz87Gy8jEw8ieurq1sbGws7Cxrqyrp66xr62tqqKUkYuG//33goaChYSFgYD99fH0+/z7+Ovq6OLP9YGA84H+/YCFg4KF/oTr39vTxM/JxsXHwcO7rrW4ube2u73BxsnExcrP0b/J6Pb09/v+/f75+fr/gP+A/ICA/oCB3s/o+Pv2goT7hIWDh4WFhomIh4iB2veA/v79+oH+goKGhoWJhomH99KAgYWBhIf864D09oOWmZWSk++LmpmYmJucnJqdnZubnJqZ8o2NkI2Njo2OkZWPkpOPjor2mpCWmJOVk5GSl5menJuX75SYmpmXmZeXlpaVlpeWkOKYlpqcmJWYmpmbm5qXl5XMmZaXmJaVl5aWmJqYmJqY/4qXl5iXlpSTlZOUlJWWko3UmJqXj4C5hpWUk5CA+4SNtLquh5KTkpaYm5iWlZWSkJabk5aPm8vRx8PK2+Ph2su4wcPU0cvKyLOmuri2scjpgPiA0ZWB7ZHFroOiyZrGq6WP+IWAu6TLybitp5SHhIKCge/h4uHugoGGhICDh4n/9YP9hIrFpraYmce+xaPR4qLor9HHlYCdppSduJDgmI2PtHhgko/+iKCJzaiLaEhoVtWTzerupe/z/YDwiImNjo2OkI6Kh4yNj5GYmpudkJSXlffGwaanqYHTq5mMiImJj5SbneW6qa6O7vDh9YWY1eG0mYqeu9rVz9rZ09HNwrWmoJ+knZeWm56ntsHNzs7R1NfZ3N/f4ETg4d/d1taslJSVvNrb396xioaB9qLj3tCxusjRw7ug+cfRzM7O6OLe7tzd2eH9jMzslKJoUYCy/7Hx+r2A+umMjotuIoAqJDU/WZyKll9tcm5lVpKfWVpeYGJmZmZnaW1vaYt/gpOXUFNWWVtdeH59e3qAhYiKiYiKiYKjiWxZWVtwZGxtbGt8cnJ+jIN4bHN2dm13hIGCjIaIh4WDgYR8en94dnt4bnl6eXN4dXN3gHx5fIGCfn5/hIOCfoGBgYKBfXt/gIB+fHd2e3p8eoB5enZ1fYOCe397f397dX19fHp7e32DgYSDg3+AhYKAf3t9hIJ8gH+EfnR7f4OBfoCCh4WCgnyEhYdGR0eEg4pIgYCMSkVJRkdISYB3g4J9dnd8fYB9WGRbXVlZV1ldp69gsllZYVxfZmN5jpyen5+iraifklRSnYCZnqRWWVtbXLBYWVxcWqCPjpCMfYytubOxsLOxra+sqqipq6ipp6anq6Ojop6BW52PgY6XmpiXlpqeop6gm52bmZaXjYpeXltZWldVVFRWVVVSnVCfoaCYj4yKh4OCf4aKjZKTlJWUlpKPi4iKjouIgoKHhoSIc6CfnpubnJidl4CYl5KQjo+PjoyJjY+Mjo+OhYyJhoaOcH+Ae3d3eHl2eXZ1cnN5fXt7eXZvZmFcWKOenFJUUFNTUFFRUlGdl5aanZyenpSTkIyDpVhWolitrFhbWltdrVqhlpaPf31xbm9xbWxmYWdrb25tdXV3e315eH2AgHuGoaynq6+wrKump36ssFasWKpUU6VTVpSLn6mnpFlbq1taWV1cXF1eW1pbVpCjU6Sjo6FToVRTV1ZVWFRWVJt+TUpMSE5SmZOan1ZhY2BeX5xbZWVkYmNkY2NkZWRlZGFhmVlXWlhYWVlbXWFaXF1ZWFenaF9iZGBgXlxcX2BlZGVimWBjZWNhYmCEYYBiY2NflWRjZWRhXWBjYmNkZGJhYIRgXV5fXl5eX19iYmJhYmKjWGFfYWFgXl1dXF9eX2FdWIllZWNed1JfXVtWS5JNVXR9dlZaXFxfXVxbW1xdWFdaXFlcWWmKjoiEiJOZmJSHeX+DkIyJioh6bn16fniInVmsV5BoWadvmJBujWnGcmpSTTthMzFqaomFeXZwYFdWVFNTkHt8gYpNTExKR0lLS46JRolHS3Zqc19ffHVjUHeOaphufnRUX3Bvd4pjoW1vcItqV3lsv2h9bZuFdlpEYU6gZo+kpW6dn6RSnFxcXl9gYWNhXVuEX4BmZ2lqXl9hYLLgdmVna1qUdmhXVVZXW11iZJGOb3dor5iMm1RjnaVwV0xUeI+Lh42NiIeDem5iX11fW1lXWl1jbXWCh4eHiYqLjZCPjo6Pj4+IiGpdXV14jI2Pj25RT0qLX4eFf25xdHx4dWajhYuIjZSelJCZi46KkaJYhJ9kfhFfVHqZwHqosoVdvsmIf3dtLYCghqSRg76al1FWVlFOQm1oOjo9Pj9BQEBBREdIQUI7PUdKJiYnJygrR1NRT01QU1NST05QU1FXMjQvLjA8NjU0OE1RMzc9Qj45Nzs7PTc0OTYxODI1NzU0MzUwMDQxLzIvKTAuLCgsKygqMC4rLTAwKi4vMzQ2MTI0NTUzMC8yM4AyMS0tLy4xLzIuLispLjEwKjAuMzUxLjY3NjU1NDc7OTw6Ojc4PDk3NDM0ODgyNDM3MywvMjUzMjQ0OTg0My81NjgeHx84NzkeNzU+Ih0gHh8hIjo2QUI+Nzc9PDs9MkxLT0xNTE5RkZ5Zo1JSVlBQVlBjeYWFhYmMlpKKdUJAeYB2gIVFSk1OUJxOTVBPUJKFhYmGdouqt6+trK+uqa2qpqGlp6empKGeopqcnJRnSXts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39Cr5bKpm/v9+cCuysKwsKSAj4mE/fr75dzk5O2FhISDgIWKiIT2gYGFibaquJeYwrSkiITgj/Si5ajMzKjKyqKguoXWkJuRomCEddSQheKelOGumXFSfGPajoi+z9ve3PP8gYSJi4yMho+RlpmgoqWnpImSko7pwrqcm5L70qCem5ydop2vsa6j0f2emdrMze9ugKfwoNSRk4DWz8/N1r+hlIeEh4mQm5uXl5aWlpKMiISKkaCrt8LW3N/b3Nza2dbUzpyXlJfL2t3bs4mEgNPVyLy2ury9ura1sOSyuL/Ez9fBydHl357oiZ2pelyHu4e8/oK1jIOpkqKUfyQbFSWAT2aojYecYXBuaWOnX3JsX4FXXF5fY2RmaGlta2WLhYKXT1FSVlxdX3V9fXx+g4WIkJCPjYyCn4hrV1hcaWZkdGNWc3lzeIaDfnZ0gIJwcGtub3yEhoOBgoJ4e35+goB+fXR4fX9/eXJ6gYWAf3t9gIJ+e3x/gIGDhICBgX5/eHwOgn18gYJ8fIB9e355fX2Ef4B4eYCBeXxzdH18eHuAg4SEg4N+g4OEfH13d4iKiYKEgoKFf4GFh4WGhoiIiYWKiICHf0ZGRUZGg4xHSIqGRkaLi4iDhIJzbnh5aZVbWVZZXWBjWmBdV6Sgs2JkaGpuiKqpnKKln6KjqqykpZWSWVSgoqunnaGtr6tYV1mklZWPj4CTmaKfl5+ssbCusLKxs6+rpJ2dnZqamaCkpF5bXV1aj5Sdk4yVlpWXmp+dn5mTlpKOgVteXl1aWldXU1RToFFRUlOfnKCfn5ygoJqeo5aPkIyJhnyFgoOGioiNjIqFhYmLiISJd5ibnJmal5WRkJOWloyPj5WOjo6PlZGMjJOQhICGhYeHdW19enl7eXd3e3t1amppaWmsWp2TUFRUVVVUVlVVU1FRUlVUopugUZqbm52blZORkZWUhqpXqaqrWVtaWq2Yn5ypXFersYJ0cGxraWxma3B0c3Fvd4R/f3l5eHyBkaWmpauusK2wqqmoqrBYV6+qqKenppeHl6mnsrKvrYBXqlZXWllXW1hYVlqecZGnVVVTU6CjoKFWWFZSV1lXloRQUVJOSUxPUU5Plo6Li5Ohn1ZkZGRiYWNkYmNiZWhlZV1aml1ZWFtXWFdYXF9dWltbWZBhZWRfYl1ZXVxcYF9hYWFemWFjZGNhYWFiYWFgYGNhX5ZkY2NjYmJeYWJgXhlhY2Jjh1hcXF9dXVxcXl9jY2BcYWCVYmJehGCAX11fX1xhXl1XlWRiWFFNTlNfbHeGla2bk6N7WmRnX1paXVtfXV1bW11dV1FRX4aFiouZnpB/f4aLi5SMiIiIhYZyanZ0e5VWpaWOYVqmboaEwrOxgJRZUkdnZmVpdYiBd3dtW1hXqaShg3p+gohMTE5MSUpMTEuKRUVISmdqcV6AXXlvZFljsWGKZ5BnendfdIBycoFWlGt5doBSf2eqamWqgXOujoBoTXZdqWRad36EiIqcolRXXV5fYFtiY2dpbW9zdHFWWlpZoddxXFxXq49qaGVmam5pdXd0aqKkZ3aYeXiNS263cHlMTUyLhYSCiHVfUkdHSktPVldUVVRUVFFRT0xKT1ZeZ3F6h4uNioqMjY2KiIRgXFpdgYuNi25RTUh0fXRxcXNybnNzcnGcfYGHjJKTi4yNnpZmk1hzkHhafZljgbReg2JjiI2MfngvLCU6gObI4qaQlVVYV1BMf0hKQjtTODs9PT4/QEJER0Y/QT4+SyYmJigrKyxDUVBPUFRVVldWU1FRTk01Oi4tMDs5Njw6LFc9Nzo9PT05O0JCPzs1Nzo7PUA9ODc2MDM0MzUzMjItLjAxMS4qLDE0MjEuLzIzMjAxNDU1Nzc0MzQyMi0wgDQyMTM0MDEyLy4wLC0wMjM0NzEzOjozNjIzNzc0Njo8Pj07Ojc8PDw3NTI0Pz89Nzg3Nzk0NDU2Njc3ODg6Njs6NDo2Hh4dHR45Ph8gOzofHz4+PDo/QDg1OzwzWklNR0pOUlVPVVJNkJCfV1lZWFt1kpGGio+KkI+Xl5GRgHlIO0OAhI6Kf4STlpNKSUyQhomFhYiPk46PlqetrKurrKqvraihmJaXk5KPlJiZUUlLTEpyeYBxa29wbm9xhHOAbnFubWVKTk5NS0pGRkJDQn5BQEFBe3h9f4B9gYJ9goZ5dHd1b2thZmJjZmhmbGxpZmZsb21pcGiLj5GQkIyKhoaIjYyBhISMhYaFiI+Mh4WPinyAfn+Aa2N0c3NzcG9wdXduZWNjYmGhUol9REdISUlISkpJSEZGSEtKjomMRoSAhoqMioSDgX+DgXqqVqipqVhZV1mrl5+bp1xXrrF7ZWBdW1pcVl5iZmViX2h2bm1mZWNqcYyjop+lqKukp6GgnZ6lUlKim5iVlZOFd4aalqKjn5pOlUxNUU9OUk1OTE+IW3qNR0ZFRYKFg4FFR0VBRUZEcF44OTgzLzM2ODY3aWJsY2RxfXlDTk1NSkhKSklJSEtOTEtBQG9DQD5BPUA/QEJDQkBBQD9oTE9MR0pDQEJBQUZER0dIRG1ERkZFQ0RERURFRENGRkVtSUdIR0ZFQkRFQkBER0VFWzw9P0E/QEBAQkJFRkI/Q0FkQ0M+hEGAQD5BQj9DQT87a0lHQDQyMjQ/TFppco2BdIVkPkZJQj0+QD9CQD8+PT9APDg7SmtpbW90dWtkZGZoaGxoZ2hpZGRVUVpXVmE1Z2daSkiLbo6Avp+XanU3LytGRkNFUGNaU1ZNPDk6c29sRTo8REwqKSopJycpKCdHJSMkJTEzOS56KzM1R0lXrVVgR2lIUk88SFRbY2xLpZC1s7iG3ZJ6Pj+tlHa7yubOpPOx8lU8QUNKTExYXjE0ODk5OTY5Oj0/QkNERUMuLy8wXm85Ly4sYE85ODU2Nzs5QEE/QpdaOWNcPjxGJT53S0orLDJmY2VlalhCNConKCksMTKFMFIvLSwsMDdBS1dibXR2dHV2d3dzb2xMSUdKZ3ByclU3MSU+TUtLSUtNS0tNT1ByW19lbG9uaWxjaGlHZlCX5N286u1fV21BTDE2dPHbtNCKqJzWAn9+hH2FfgZ9fXx8fX6Mf4R+lX8Dfnx/iIABfv+Ag4CFgQiAgIGBgICBgYuAjH+DfpR/goCJf4OAn38BgISBk4CLgQGAhIHTgAR/gH9/j4AEf39/gI1/BIB/f3+EgIV/goCnf4KAj38CgH+KgIR/hICEf4eAgn+KgId/kYABf4+AAX+QgAF/j4ABf4+AAX+QgAF/kIAEf4CAf7p+AX+Ffgd9fX1/f3t7hX6NfYh8iX0BfIV9hn4IfHx8e3x8fX2EfgF9hHyCfYR+CX9/gIGCgoCAgIV/BH5+fX6If5SACH17f4CAgH5/ioAEf39/gIV+hH+DfbZ+AX2LfIx7EXx8fX19fn9/f4CAgIGBgoKBhIAEgYGBgAICBACAwbLOhqm1sJ6P3oWDtKm0wcKLipiYmp2hpKmqrqTu5+n8hIqPlJygorzEw8fR09fc4dnM2ebY+kWT+Iacpa20utD0qMPQyMS4yN7o7u3UvsTBxM7p6efk4Ozp6uvi5Obt5+3n6+3k4Nvj4t7Y3Nbb2uHc4OPr6+vs7e3u6OTs8+9E5/Dx6/Lv6uTo7u3q6enr7e3i6+zq6eTk6uDS3ePl6O3x4uf08+316OTl8PTz9PT49PLx8vX09fT49vf58vX79/L17faEgID59/GAgYCA/fr7+vf28Ozi1K3mgvT19IDygYyShICAgv6F8JOht9nr4t7k6+Xm3+Lk6Off3OjfzuWB9/769vHu9v2A9N/d3d3X1tvo/4D28N/c6/Ht6+zu6OHd3dfW19jY2t7Z6YeIhsvxgoOGifbr49/s8PL07uzs68L9h4aGg4CEhoWDgoOD/4CB/oD//v78+vv19fj39fj1+Pj29cX4+ffq4NvW2NLJycm5ubm1ncPDytHNz9HT2NTS0M7Nzc/Oz8/Qz8vKysK7tbCrq6iojqSjpKWjpaapqrK4t7aysZPyh4iIhYiHh4mJh4eIh4GCgYD6/f339Pj29fDx7/Dw8kzv58T7gYGD+eTi5PGEhoeGg4SDh4LRt8S5sLi9uLnExsnLzMjJycm0sanc/fj28Pn+/fv19fn6/f79//v+//zstOH48PP5+oCB//3+hIGAgoOEg4TO0Ofi0NTggoSD/YH8gYKEhIWH7+OFhYeKhoGEhYWFiISCgPXr7OWZmZaampmamZuZmpmdnZqV7JSTkIyUk5KOjpKSk42Oj4vnlZCOj4+VkpOWmJeVl5uaioWampuZmJmanJiVj5CSlZLbmZeYm5iYl5iWl5aXmZeVgvWAlZKWmZqZlZOWnJqamJWY7ZKXlo6OkpSQkJWSkpSTkY7pgLeXpbXP3/fzgOfP5Obi+ee6l6SmnpGMk5CWm5aZmJaSgvz/icjPvMDE1eDMys/R49fUysa6ysSyoKm85P2D/eWUgumdnd6V9NuEkrWkmYX9+cPGx7yzt6WPi4X8+fKA1NXm4O2GiIOGgoeLioP0/YaLjrSnspCQvbTKq5KG5NWX4aDLoc/XxpCSrqW73qmDpJ6TsG+VhIKMmoSjofe/U4pjTXlsov2z1d3d2t///YGCgYSHipGWnqSil5CTkI3Pub2amJL31p+fnJ6fpZ6rqZmtipmOmczFzO3oxeKuopZousrXya+Ig4mSmZ2eoqOmraymo6OioaGlnp2bmpmYk4yJjpegpq+8x9PP17qWk5Ki0djYsYyD+fbPw8XGwLLAtLWstoGyubm8xtPThM+Dn6tgRmiPyJrRkI2xg+e2qriymCwbEyAeOmaAk4GPV2pybmdgoFxcc2Vtc3FWVV9fYWJkZmlpbGOIiIqWT1FVWVxfYHN6eXuChIeLj4d+goiCnotglFFfZm5wdI6XeG91bnFrdn+JjYN1bHBvcXiHhoN/eoKAgYJ5e3+EfYB8f4F7eHV6eXZyeXR1c3dzd3mBgoCBg4GCe3d/hIGAeYGCfoOAeXV5fX98fH2AgYN8gIN/f358g3txeIGDhYeIe32LiYSKf36BhYiGh4aKiIaDgoaFhIOIjo2Jf4GJiIWEfoRHRkZFh4WFSEdFRoiFhomJiYaFf3djhlGanp5Tn1VeY1hWVliwW6Jia32YpJuboKahoZqho6WkoZylm42AnValq6qimpqnrFWelpKTlI6NkpmtWa6tnaGsr6uqqqukn5ublpKUl5WYm5WbWVtahqNYWVxfp52VkJibmpyXlJWSea5eXVtYWllYV1VYVqRVVqdWqqeloaWlnp+kpKakpaSkpqWFrrKxpZ6YlZSQi4qIfHZ2dmuHhI2Sj5GTlpiAlJOSkI2OkZGTk5STj4+Oh4N+eXZ1cXJdbm5xcG9vb3FyeX18enh2YphVVVRSVFNTVVVTVFVVUFNTUZ6iop2ZnJ6dmJWWl5WZlI9/q1dYWqycnaCsXV9gXllcW19cjm92bmhvdnRyeH2EhYeCg4F7b2xrmK6pqJ+rsK2rpaSoqbEUsaurqKurqZ57mqmkp6quW1qwq6mFWIBaW1lahnuEgXuDj1VWVaRUo1NVVlZWV5mJUFBSVE9LTU9RUFJPTUyUkpOVY2RgY2JiY2FjYmNkZ2ZkXJJcXVpYXl1bWFdcXF1YWFpWlGNfXVpaXVxeYGFhX2BjYlhVY2NjYmFjZGZkYV1eX2NgkmZiYWNgYWFiYmRiYWNiYVSaXYBaXV9fYF9fYWdmZWBcYJVfYV9YWFpcWVpfXF1fYGBdm1V5XWl2hZSlplmdiZ2enKyefV9oamVcV1taWlxZXF1cWVCanViHjH+Bg5CUiIqOj5+Uj4mHf4mGdWlxfZmqWqybY1ifcX6pdr+/aGlhUEc2ZGVog4N8d3psWldWp6SaeIB3foCKTU1LTUpJSk1MiY1ISktlaWxYWXVwb2RbWq2qXYNli2R6e29QVnJ1fpJvXnx7dIxgi3RlaHJlh365lUd7Y0x1YnCebH+FhICJqKRUVlVYWl5iZmxxcGdbXFlYl8d0XFpYrpFra2dqbHBqcXBlf2VkXoeMdXaLiZKpblRMZWeAi39sUEdKTlJVWFpaXGFgXFtcXFtbXVpZWFdWVlNQTE5XXWNoc3+IhIp1W1lZZIeKim5RSYuHdHF1eHVrcm9ybXFUfoSEh4uUmF2QWnCHWEdifpdvk2dlhF22lpmdk4M0LSc8KjlWE7mMkk9XXFZOSndERktEQ0dIMzaEPYBAQkVFRjw/QUNKJycoKSssLUROTU5SU1VXWFZRUlBNSzY4XTAzOkE8QGRYXjw8OT9DR0BKTT47PT88PDxBPzs2Mzc1NTQvLzI2MDIwMjIwLy0xMS8uMi4uLS8rLi82NTQ2NjMzLy4zNTMxNTUyNDIvLC4yNDExNDc4PDk5Ozo5N4A0OjgzNj0+QkJCODdCQD9DOjk6Ozw7PTs9PDs5NTk3NzU6REQ6MjI5Ojw5NTYfHh4dPDw+IiAdHjs5Oj4/QD4/PTo2VECDiYhHh0dPVE1MTlGiVYxVWWuFj4iGipGOj4iPj5KSjomWiXqFSYqMjoh8e42RRoaDg4WFfn+FjZpSpVyklJioq6ikoqWemJSVjYiLkI2OkYV6RkhIaodJSk1Ri4B1a25wcHNvbG5vXZFQT0xITEhGR0VHRoNFR4pHjIaEgoiLgYKIh4uJiYiLjYtynKCflY+IgX15dnZ1aIRegHp2gYWChYeMkImHh4aDhImKi4yNjIiKioJ8dnNvbWhsVWBkaGdlZmZnaHBzc3JubVyHSUhHRklHR0lJRkhKSUZKS0iMjZCNh4uOjomFhYSAhoB7eapUV1upm5ugqlxeXl1WW1tfW4xiaWBZYmllZWtxe3t8cXVvZ11dZJSro6OWgKOqp6SdnJ+gp6adnJeamZeQcJGYk5WcoFRToJqXT05PTlBSUU5PbVheXFppdkhIR4pHhUJERkVFR3pkNzc5OjUyNTc4Nzk3NDNmZ2hyTExIS0pJSkdKSUpLT05KP2dDREI+RUJCPz9CQUJAQEA8bUtIRUJCRUNER0hGREVISD88gEVFRkVFRkhLSkdDQ0NHRm1NR0ZGQ0RERUVHREVIRkM6aj88PkFCQ0NFRktJSUM/Q2lDRUE5OTs7OTpAPD5CQ0NCbT1aQEpaYnCDg0h+Zn2Ae4yCYERMTkpBPUA/PTw8P0I/PTlwcEJub2RlZW50a2psbHZsZ2RkW2ViWE5SWmNqgDhsZEtGhGyLnXG0m1taPy8rJUNCRFtdWVVYSTs5OXBwZTw6PkJNKysoKScoKScmR0UiIyQvMjcsKDM0TEpNVKmkUGZJYkZSVEc1OVhhaX5zcrC9sceh8ZlEQ0hkpYfS2oT61KHrrnFnP0ZHR0RKY2EyMjAyNDc5PEBDQj0wMC4vOVZsOy8tLWRROjo4OTo9Oj0+OW5SNjWLUT06Q0NdcVMzLEFeZl5MMiYoKSorLC4uMTU1MzIzMzEyM4YySjEvLS85QUlQXWZtam9eRkRET2pvcVU5KkpUTUtOTk5LTEdKSk46X2VmZmtwbkJhRXfNmIzB185cXT1CTC5odujlws2DoJ/4lYqKg32GfoJ9hnwBfot/hH6VfwR+fH9/hoACf33/gIOAhIGDgISBi4AHf39+fn5/fod/A35/fpZ/AYCIfwGAin8BgJZ/BoCBgYGAgISBjoCLgQWAgYGAgdSAAX+RgJJ/g4CFf4mAsn8FgIB/f3+JgId/BoCAgH+Af4aAgn+OgIR/kIABf5CAAX+ggAF/kIABf4+AAX+QgAN/gH+HfgF/mH6CfZp+AX+Efgl9fX1+gHx7f3+Efox9iHyJfYJ8hH2GfoR8Bnt7fHx9fYV+AX2EfAF9hX4Pf3+AgoKCgYCAf3+Af39/hH6If5CACH17f4CAgH5/ioAFf3+AgH2FfgZ/f359fX21fgF9jXyHewZ8fH19fX6EfwiAgIGBgYKBgYSAB4GBgYCAf34CAgQAgPadtLWnmZHul7S5jd6nwr+Sl9r0kpudoKapqq+j7uPm+4SHkpicnqC6wsfJ0tnZ2tPHycnJveFClPuJpLa1uJ6g9rjJ29C2s8He7ev/+tHE0MrS5e7t5+jo5/Dt6+bn5Ojp4ePj2tXX29vd3+DX493X4t3o7evo6u7w8PDx8fX3gPbn6/D17+np7e3w7uzr6enq6uTp5ePr6eDo3uTl7PLy7e/v8uvs7evy8/X1+Pf2+fTw7fHy8vLp7vD6+PP7gPbz+fz8+v/+//33/oCAgYGA+/P48/Dm3bXa/+Pt9+Pg6ur2gYeF/PP4hpSajsLm5uLi4uXc5uPZ2tze4+Xc4+LfRN/S0e/t7/KDgoH449jZ2d3h4/CA/fT9/v35zc3b7eTk6Ozw6eHk39/Z2NG1h4eKz/aFhomFiImIhIX57ebe7evr0e6EhIVMhISDg4WDhIKEgP6B/4GB/vz59/X38fP08/Tx8t3V8vf5+PXx8ury7+vt8Ovm4uLd19LRz8zHwMDAvrq5ubWYubi2tbSytbe4ucHExoTEgMLFwsPCwb++v7y8t7SysKvqkI+NjYqKiIiFgoOC/u/9goOC//369PX08vr3+fr58vPo5bnj5u7tg4WHiYqHh4WGhoSDhPvfsLW7xb3AvsPLyMjJxLSqutjYz7u86/j29Pb49PTz9oCA/P73+/nuzuSD2tP6gPn+9vqA+Pn+goD6Zf2AguzD3ebp7PHl6Nfh3vX5/v3+gYGF3+uIiYyHioWFhYODgoWH/4H88tiSlZSXl5iZm5qam5aZlpmZhoWSlJOPkJORk5KTk5OPkZKF+pWQjpKTlZSXkpaVmZ2emPuUmZqcm5uahJcIk5KSlZDTlJWElICVl5iXmZeWmZeS0ZSSkpqeoJuakpSZmJaZn5LolZaUk5CSk5CRkZORj42Qi5i+6/Pt7enr64P89Ozl7/fq4LKPmJqQlJqOjJSVkY6VlYOFhIWDmM/Xy8nQyMvRztDN0c/KybW2xryjrO/7///vmYLwtpeG7aDu2qq8noTrnM66z4DCt7a3m4mHg4H+49HQ3OLrh4mKiYGChYiC94CAg4Szp7GVj7q43castbCa+96BsIvHkdPu5KzM0KevvuK7hJuXlat0TY+QvsisqKaY3VxLd1tJenqwgbPjhISEg4GDh4mJlJydn56cm5mWzLbEn5+c3c+WiY+Pk5yepJ/hxe+I2HOVp8Tm76rOn6mitavTuPnd9JGlpaWjnI3949rR1e75hY2Zn6GhoaOjoqKkoJ2YmJSSj4yMi5+ohvqMk6/U2LWLhqnNzr7HxsC9w8e7r7OOt8jYi+CHpLdfhld5o+u/gaKMx/L7vK2qzsZ1HhUPGDRjubC7gKFlcnJpZGCdW2lvXYFfcHNcZXuYW2FiZGdqa21kjIaJj0tNV1xcWlpweX19gIWHiYR8fH18dpGKYJdSY3J1dGtvrYF1fndpZW2Ci4mWkntxeHR5hYmFfH5+fIiGgX1+fICAen98dnJzdnZ3enx1e3VyenZ+g4B8fIWEgoSEgoeJgId4fYOFfHV3fYCCgICAf36Cgn6CfXyDgnqAe36Aho6LhYaIiYSFhX+Hi4uKjIyLjYqDgIOGh4R6f3+HhYCHRYaFiYyMho2NjIuFi0dGSEdFhYGGhIN+eWWHppOboJOVmpqjVVlZq6SlW2RoYIWjpKGeoKKcpJ+WmZucoKSdoJ+egJ+Tjqemp6RYV1eml5CTlZaWmKNYsa60tba4kpSaq6Geo6mrqKSmoKOenJR9XFtfi6NaWl1aXV9eW1yrn5iNl5GVhZ5dXV1cW1tbWltdWlpXWVasWa9ZWauqpaKfo5+koqOlpKSUkqSprK2rqamjrKmkpqmknpualZCNjo6Pi4aGKYeGgIJ+fWl/fnp5eHh6e3x+hYaHhIOFhYOGg4SDg4J/gH+Ae3d2dnKZhFqAV1VVVVNQUlGflqRUU1Ojo6GZm5mYn52dnZ6Zm5GOep6fpqddX19hYl5eW1xfXlxdr5pzcHN7dXl3eoWDg4SDdml1iIaAcX2hqqalqKqmpaWoWlqurqirq6KMn1yZkq9brLGoqFinqK9ZVqWrWFmieYKFhYmOhId+i46gnaGeoFKAVFaRj1FRVVJUT05OTVBNT06SS5eXiWBiX2FhYmJkY2JiX2VhZWNSVV1eXVpaXVtcWltcXVtbW1KgX1xaW11gYGFdYF9hZWVhoV5hYmVjY2RkY2RlYWBdYl+JZGJgX15fYWJjYmJgYWRiYIVcWltiZGZjZF5fZGFeYmlgll9fXlyAWFtbWVtcXl1dXF5bYX2Yop2doKChW7GroZ2jqqCZeFlfYVxgZVpWWVtXVFhXTk9QUlNqkJKKiYqFi4+OkI+RjoqIeXiEgG9yoaqvsKNnW6V+dmO8gc24iG5QP2xVYnKHfnl4eGVXVlRUpIh3dnuBiExOT05JSEhKSIdFREZIbGuAb1xZc3J0alZaV1u5sl56XH9ZfZCJZHiBdHaAn4JjeHJyjmZGe3GJln+Ni3m0T0ZzXUlxZnpSbpJXVlZVVFVXWlxlamtramdnZmOVxX5iY2KcjGVcX19hZmluaZOZmVWKgGlwh45zo25oUmJrh3GNeodQWlpaWVdOi4aCd3eDikpTTlZbXFxcXV1cXFxbWldVUlFQTkxPW2JNl1VZboiLcE9JX3d4cHJxcHJ1dXRwc1uBiJZem191jlFzU2yGqYRZc2KPsbuWnJOeo3svKSEtO1mUgoOAm1dcXFVOS249QUZCSz9FSEA/RVw6PT1AREZGRj1BP0FDIyMoKiopKkFOTk5RU1VXVlJTUlFOTDY5XzE3QUQ/RlN9ZEBBQT06PUdNSEtHR0FCPUBDQTszNDMyPTk0MTQzNTUxNTIwLjAxMDEzNC4zLiwxLjQ1MS8xNjMyNDU2ODmANysxOToxLC0yNjk4Nzg4Njs7OTw4Njw8Nzs4ODxBSEQ/P0JCPj9AOkFEQz5AQEBBPjg3OTo6OC8yNDg2NDkeOTc6Pz45PT49Pjk/IR8gIB45Nj08PDk6M1aEe4OHfoGHiI9LTU2YkZVUW1tSdZGQjoyMjoeRjIKFiIqPkouPkI2AjoJ9lZCTjUtKSpSKhIiKioyNk1Cno6ytrq2GjJSjl5edo6akoaSgpZuWi2ZKSk9whUpLTkxPUVBMTpCBdmhvaW9pgU9QUE9NTExKTlBMTUlMSI9MlkxNkpGJhoSIhYuIi46Nj35/kJaYmpeYmZKZmJOWnJWMh4eCfXp9f4B9eXiAeHdxdnNxXXJxbm1tamxucHF3ent2dXd4dnl3eXh5d3V2dXZxbGxtaIdOTUxNS0pKSkhFRkeLg5JLS0mPkZGLjIqHj42Njo+KiYGAc5yfqKZdYGBiYlxcWltfXlxdrJhrZWdyam1qbXx2d3h1altjc3NuYHiaoJucoKKenZydVVaAoqGZnZmTgZdXkYilVJyflpZPk5OdUE2Smk9PjWFcXV1fZFteXW12iX+BfXw/QkRxaTk4Ozk7NzU1NDc1NTVgMmZqZklLSEhJSUpLSklJRUtGS0k6QERFQz9AQ0BCQUNDQ0FCQTl3R0VCREdHR0hDR0VITExHdkBBREdGR0hHSEkOSUdEQEdFYkpIREFAQkOERSFERUhFRFs/Pj5FR0hGSENER0VCRk1FakNBQD45Ojo5PD2EQYBCQEdednx2d3t9f0qPh4N/g4eBeFs/Q0VARUg/Oz5BPDk8PDU5Ojs+U3JzaWZoZGlra21qbGlmZVhZZGBQT2lrbG5qS0eHeItixoWvrYtVMCdCJkROZF5ZWFhFODc2Nm5WPTo9RE4sKyopJicoKCZFIiAiIzIyNi4rNTdMSkBHSYBPsK5YcUpeP1diXUNRWVphbIqMf6qvqNmxg6RGT1JuwaKA5YWM+Myb8KpuM0BVMzIyMTAwMjM2Oz4+Pz48Ozo3VWZDNTY1VUw4MDIyMzc5PDpVi1otUIM+OkNFQm1MSzJATmJQUT9EJy0tLSwqJ0dERUA/QkYmKSwxMjIyMzMzNEk1NTQzMjExMC8vLztBMmU+RFVsb1YwKDVJSkhOS0lKT1BNT1A/WGdwQ2lLbb2I5KPFzbJfNUU/TV5pc+Tivsf/oKKLpqCYyZeKAX2GfoJ9iXwCfX6Jf4R+lX8Efnx/f4aAgn78gAGBjICFgYiAAX+JfgZ/f39+fn6ff4OAiX8BgJd/BoCBgYGAgImBiYCPgQWAgYCBgdGAAX+MgAZ/f3+AgICVf42AoX+CgIh/BYB/f3+AhH8KgH9/f4CAf3+AgJF/BYCAgH9/jYAFf4B/f3+igAF/j4ABf5CAAX+QgAF/kIABf5CAAX+IfgF/uX4OfX19fn9+e35/fn5+fX6MfYd8iX0BfIV9hn6GfAZ7e3x8fX2FfgF9hXwBfYV+Fn+AgIKCgoGAgIB/gIB/f39+fn5/f3+SgAh9e3+AgIB+f4mABn9/foB/fYR+DH9/f359fX5+fn19fYd+h32ZfgF9iH4BfY18Cnt7e3x8fX19fn6EfwGAh4GFgAmBgYGAf359fX0CAgQAgMCvnJGGy/Tn3eHTyuXFxLi1ls25t5CdnqGlqq2j8+nrgP2CjJmeoKW+xsrO1NbPx8fJyc/QvOE+kYWZq8aX+7f3mrTD3+LNv7rBz9r4gfXo5MzEyMvL1uvq6Ofq4d3S0Nbp4+jp5eba4OHi4ePo4+Xg4uDp6fDy8PHs+Pr17+zzgPPq7e/v5urr7/Lw8O/o4ujq7evn6+7s7+nn7vTz8/Xu7fHz9PPz9/P28O/09PLx9PL17+zy8fXx8+/3+fr7gP+AgP39++v2/f79+fv7/Pjy9ffy7uOq6s656OHe5/ru6PTs8vH/gPz6hJaZmJik2Obl8PPv3uPd4uLd4+jf2evmgN/TyNDV4+Li5+LOxL65w8bW4vbz+vn59u3K3un04tLZ6eXk4ubq7e3j6P6BhIXJ+YSIiIKFhYWDg4WGhoKI+OzhzvuFhYaGh4eFgoSEgYOCgvz+/v3++vb4+fb69vnz9fPt5+699PT38u/w8Ovu6ejr6OTk4eDi4d3X2Nra19zbgNjZ1dfU0KPT1NLRzcrNzMvMzsnIxcfDxsTCwcG8vL29vLy4tLOvrauLgo+Kh4mIh4mGgfqAgICEhYWB/vrs74D////9/P304Nva8PmKiYyKh4SMh4eGhIOFgoCBgoCB9LLFyszKxMfKwLe0uc3S09HW0M/LuM/5+fjz9fP7gP36gPb4+e7O4oD/gv3749fz/4OAgYH//ff+gYGBzs7e5ezy7+/r6e/38NvX3+WAgoLY5oiKi4iGhImGhoOCgoCFgIH6yZSTkpOUmJmZmpqXmpeVlJqW0P+FjpKVlZWUkpOTkJGQjIzajZGTi46VmZSUk46XlJKam/2VmJeYm52anJuWgJaWk5CSk9WSl46UlJSTlJmUlZWUk5aWzpWUk5OanJyZmJianJqampzjk5OWlJOTkpKRl5STkY6Nh9ux2Obq8OLh8fz++fvz8e/y3NapkJqYnJWXlpCBhpaQjouKhoeDg5fW1dnKzcfN1eLbz8fTy8HDu7i4u8Pi8frzr4H1x47lgKL02cyy4euiloaBs7S0rqq1nIuHiIiEz9HQ3N/0iI6KjoWEhIWE//6AiYm/sbaZkrau5bGnq6ymp5jt6YWr/bCFv+/oxYWRtqmvwO3DjKCdosB7UICNm6GL1ca1is5VR3BaSHd8v4u53+76goeIl5mam5ycnZ6azbnGnKCg186PgIKTkpuhpaKdmcHf24exvoCDg+DmwrDgqLefi4iKjJGH2aDC6PDxrsjwjKCjp6+v3cHR5ICKkJqioqmoqKWmpKajk6Kfja+coLuPr6aF3bbE0tjUz8S4v8TExdj8hJ25v11/UnKdyJ3ilKaa8fSGsbG1yKyAHxcQGjFjuaKr4JW4gHdtZGBah5qGfX59eYZvc3R3Z35rbVpiY2VnamxkkIqKSY1HTllcXF9ye3yAgYSBfHp7e39/dpGJYFFdan1hsYGrcHVygIN2b2hyeYCRS4+FgHRucHBwdoKBf3+CfXpvbXN/fIGCfn51e3t6ent/eXt3eHd9gIKEf398hYmGgX+MgIuBhIaEd3p+gYSDhIN9eH1/g4OBg4aCiIN/houMjI2GhoeKi4uMjYqOioiKh4aGiYeKg4CGg4eChYGGiYmIRo5HRoyMiX6Hi4yLhoeHioh/gYSCgX1jlpB/l5OSmaWdm6Odop+pVamnV2RnZ2Vxm6Sjq6qonKCcoaOcoaWdl6ajQ52UjJOXpaGZnZyOhIGBh4eRnK2qsLK0sqmLn6mvoZagr6ilpKisr66pqbFcXFyJqVlcW1dbW1pZWVpdXVleqJ6Tia+EXoBgYF5bXFxaW1para2tr66loaSkoqemq6qsraahon+ppqmmo6OjoKShoaOgm5uampybl5CSlZeUm5mVlpKTkI5tk5OSkIuJi4mJi42JiIaJhoeFg4GBe35/fnt8fHl5dnV0XlRbWFVXVVZXVVCdUFFSU1RVU6KelZdSpaSkoqGjnnuPjJCjq2BgY2JhYGVgXl1cWlxaWVpcW1usdYCEg4SBgId/d3R0f4KDfoN8enl2jqqrqqappa1Yrq2pqqqhi51ZsVy3s5uSqK1aV1dYr6ymrllaWol7gYWIjYuMjIuPk5KEg4+PU1RVi4lRUVRRUE9TT09OT05LTkpMk3+FX4BiYmBjY2FjYF9gZV9/olVbW11dXl5cXl1bW1xbWotcYGJcXmJkX19eWmBeXWFkoV9gX19jZ2VnZmNkY2BbXmCLYGZcYWFgXV5lX2JiYGBkYodeXl1fZGZmZWRkZmZlY2RolF9dX19eXFtaWl1dXVtaW1eQeY6am6GZlqivsq+vqoCmo6mWlXBbYF9hXF1bVk1TX1pXUlNRVFFSZ5SVl46Nh4qNl5WPipOLgoR8e3x9gZmnq6V2W6mJba+AxL+tkaqgcGZdV3V4dnNtd2NYVVVVUnt5dnx/i0tPT09ISElKSIuJRUlKb25xXltxbIBeU1RST1Jbu7tpe655VXiSi3RNVXJ0bXR8mYBdenp/mmtKdGt5fmexqJVrqUpBbVhGeGyJX3ySnKJWW1tmaGloaWlrbGmWxX9iZmWZiV5WYF9laWxraGaVjYhaknhOUFGprnhtgWpzWk5NTU5SSnhYc52hmmhyg0lSUlFWVGxjb3VDSk9WW1yIXj9WWllPXVJWZ1RpYUt5Z3mBg3x4dXByeHt7hJ1XZX+UUnJPZYCScZ9qd22uvWWNoZuYlH4tKyQ4QluPeXmYYHSAYVhRTUlpZUZHSkhGSkdKS0lESzA8OD4/QUNFRj5CPz8iOx4jKSoqLUNQT1FRUVNSU1RUVVVOUTQ7MzU6SkCCYnhaVkBAQUJCPERGRUolTUdDPjo7ODk4OTo5Nzk4NC4rLjU0NzY0NTA0NDMyMzcyMy4vLzI2NTMwLy02OTc1N0KARDg7PTouMDU3OTk7PDczODs9PDs8PjlBPTg/QUNGRUBAQkRDQ0RGQ0dDQEE8PTw+PEA6Nzk3OjU5NTg4OTkePx8fPj48Njw+Pz87Ojk+PTU3Ojk6OjVtdWp6enmAj4qJk4+TjplNmJdOWVtaV2CKlJKZmJSHjYmQkIySlo2Fl5SAjoV7g4iTkIqN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pp+3wd7ztID0yIvTgJ6FjYenzPOXi4ijwre9wbqqoqmjoqKWhIaDiPaAhouDioOAg4mKh/OBiI24sLacja+lg6auqK6rq62Y4IPk64PVpeur2ufZqdzqqZ6mx43hnK2jo8B0S2fFvKSVos7IsPjLWEh1WUo/eY/Tlr/p/4OHiIqUlJiTtqbEmZyW1MqMgISTmJuhoKKbipLy9YOuv4aJp77BvbKqqZOgo5yErKmOrLexws3C3oSeiZOcpqG/1tezxODGxM7QzfiOjoeJl6SqoaqoqIrNxL/QkY2D8bSnxdPmiZD4hKe0xdLRZoVVb5m9h8GPsqrNl4aHmK6yq6NvHxcRGzFjqpeSwYaqwbepgGNeU3hdVFVQTU9SU1pVkFNQjYOQbU2aYWRmZ2tmkIqMkEhMUFdaXGBxeHh+goB7e3+AgIGHgJ6JYFZlhI12jMGMd2x2gIV9d2xjX1x7SkxJhHZyaWxzdH+Ffn1/fX6GhXiGfH6Cfnl6fXx+f398fISHhoCAiIGMiH6Bhn9/fnpygIB9g4aAdH6HiIiChn1kc4GAgoaHgYGAiomGhoeMioeJj4qJi5CNjpGKh4uLh4V6gomKiYSDhIGDhYWDfoFHiYyLi4uKhIGHgXd8hoiKiIeAgoZ8XpWQko2Pg1pZVlSno5qeoJmioaZXXl9iZmhlYH2qra2toZ6hoaKgoZudn5qPgI2cqKKgmZWim5GCjo6JjIWPoKyvtK62no6fn6unpqmslJWhr6qzsK6rlZ9dsYtYWVlaXFtaWltaV1hbXVxfW1xcXFelpZ+jrVtYWFetqqirrKusqq2usqejpKepq6yjoaCblpeCp6mkpqWen5aYmJijoqChnZ6al5ibmZmXkpWYgJKWk4+Tk3STmJGNi4uKiYuLiIiIh4aGhIKAgYSCgoF+e318fHt3dXh3mVtZWVlWVlRWU1NXV1RSUaCfn5yeUlKjo56WnJylWV1fX2BgXGJhZGNhXl1ZW7JbXVxdX1xbW6x8d4OIg4BraX2Jh4GKhYOCfWt5fIB6m6yqrqysrquqgKejh5WzXFpbs7eyrKyknJWsrLBZqbFbWqF5iImHjo+Tk4yJipCMio+Sj4uFiH6QT1NTUlBNUU9RUEyVTktMTEt9XWFjYGJjYWFiZGNhYGJgYmWWTEqOhoeQmJxRU1pgXV1aXF2TZWRcXVpdYGFgYF1iZGRiZ6JjZGVjY2NkZmRggGJiYmBhX4NmZmVgXl9iXmBjYWRiYWFlj2BnZGZiZ2hmZmVrY2FdV1aol19gY19bWl5cXF1eXV1bbJCDf5uUnqCaqq1cr6uuqauspKmndl5iW1tYWVxZXlhQVFRVU5ygnpxVhI6NioeGi5OOjJWSkYuCgn54bml2gJSneVWnjWyjNXpodHGIoKtqYGBwf3p8fXpxa29raWlgUk5KTY5LTE9MUElHSEpLSYhFSUxtbXBgWG5nTVNWhVGAWLhpsrJgl26ZbISKf2B6iGxnaoFYmnCJfX6bZ0Zgn49/dHyso4vAo0tDcVtKPG1xjl96lKRVWFlcYmRmY4y+f2FjXI6CWFReYmVqaW1pYW+gomWMf1RVcpCMdXBpalVaXFdJZGBQXmNicHRpdUZSRkxRUlJebmxYYHBhYGRtco1GUlBMTFRcYFpeXl9Nb2plbE9OSYtpYXiDjFVYmFJrdX2WpVp9UGB5iV2HZH18lm1jZ3udlIiLdC0rJDpJZJJ5bINXbnt1bIBOS0BWNC0rKSkqKy0wMFUtLFRVVy8iXD5BQkJFP0VAQEAfIyQmKCktQEtLT1NUU1NXWFZVVFFSODk0OVBgZoOvhWtPUD89PUVDPDw5RSUmIzw7Pjc5PDk1OzYyNzc2OzgySTM1ODQwMTMzNTU0MjM7Ojk0NTs1Pj02Ojw2OTQyLYA4Nzk+OC80PT8/O0A3JjA6OTs9Pzo7N0NEQD5AREJAQkhCQUJGRERGQT5BQDs6NDk9QD85NjY2ODg5NjM1Hzs+Ozw+PDk3OzgyMzo7PDs7Njg8PTdzeHp3enBLSUdIkpGIjpGNlpOXTVJSVFhcV1NvmpybmYyLjpCRkJGLjY+IgYCCipaUkYOHnZ2RgYyLiYl/gY+bo6iiqZSFmZqjoqKmpo+RoLSxurWxq42HTJRzSUlKS01MTExNTEtKTk9PU05PUFBMkY6IjJRNS0tKk5GOkpOSk5GVmZ2QiY2RlZiYjYyHgnx/cJOVjY+Oh4h/goKCj46OkIqMh4aGi4iIhoKGioCGiYaCiIhrh4yDfn18e3p9fXx8fXp6enh2dHZ6enp5dXBzc3R0b21wcItPTU5NTExJTEhITk5KSEmQjYyJjUpKkpGNhpGToFZaXV5gX1phYWVkYFtbWFmsWl5bXGFdXFureW16f3p2Xl5ueHRxeG9wbmdTZGdub5ekpKmmo6ain4CcmH2KqlhVVqivp52ZlZGMnp6gUJqhU1GTZGhnYmZlbGtnYWFlZGJmZmJkYmtlYzc7Ojs4NTk3OTc1ZzYzMzM0WUZJS0hKTElISUpJSEhJRkhLazIwXlpaY2xsOTtCR0VGQUJEcE1NREVCRUhJR0ZESEpLSUx0RUZIRkZGR0lJRYBGRkZERkRfTUxLRUJDRUFDRkRHRUVESGdHTUpLSE1MS0tLU0tHRT8+e2hBQkNAPDs/PT1AQkJBQVBxZ2R1b3p8dYmJSZGMioiIh36Fg1lERT5CP0FDQUQ/OTw9PzxtcW9tQGZwcXFsaWpvaGhva2ppYmFdVU5JTVJcZFFDhn6Gs4B1Y2pogZV8TEdHUlpUVldWTUhNS0pHOy8qJydMKSssKSooJiYoJyVBIiMkNDg7MikwMTA8Qz4+PD0/SsNtv75kglRtTV9iVj5QWE5LTmBNrpbLycTmrIGmgFRRUYHkyJ3h5YGD6rybgM2MaztJU1ovMjI1OTk6OVBfRDU2MkxGLoArMDM1OTk+PU9fXlxJaUwvLj9fX09MTEwxMTArJjQ0Ky0tKzAvMjoeIx4fISQjJSgnJCsuKissMDdGKCcmKCwwMy80MjIkLCsqMionJEw7O0RKUTM3XjJCUGif8J/skaWyoUpUNUNGTTMzPXfk07jD746YitTt1Neadn9OW2JdVoN+i30JfH19fHx8fX5+hn+EfpV/A358f4WAAn58jYCDge2AAYGWgIp/iX7DfwSAgYCAlYGFgISB3YABf4+AhX+CgId/kIABf4iApX+DgIt/BYB/f4CAl3+LgAF/hYABf5GAA3+AgIZ/iYABf5CAAX+QgAF/kIABf5CAgn+OgAF/in4Bf5l+hH2bfoR9Bn+AfHx/f5V9AXyLfQF8hH2GfgF9iHwIe3x7e3x8fX2FfgF9hXyCfYV+D3+AgIGCgoKBgICAf3+AgIR/g36Ef4iACH17f4CAgH5/iYCEfwN+fX6Ff4l+AX2JfJN9jH6EfYN+hnwDfX18hn2CfoR/goCEgQSCgoKBhYAGgYGBgH9+hH2FfgICBACAzIjMx9He/Onz+fD4hYDs+/mGj4f+toK52vGDio/38PmEgYeTlqGjprXHy87Nx8TI09Xa7vHZ9DyQ54LSyZiRwI+rrL7a5tnNyL2ur8z1/vmC5dfIxsnP4/nv8O/j7Ozw9/Pz7u7u8PPy9fL08+vh5OPn5Onv8O7s8/Tx9Ovd6+2A6O/u5/Px6+7t8uzs6+jZ293f6/Du7fDm6uPs9PHz8/P38u31+PT09vn++fn79vP49+319Pj0+Pb5+/H9+fr9/Pj37/D18uzm1+jz9vDz8uPEi9fNxcLOzcrL0piIiob05fv49PL09fSBiYmLkIyQlaPb6ePR39rg3+Dc4d7KwtKA4ufo4Nnc18C70MvEysLR4+Px/fiC78fI3eLl6OTq7O7i7efXz9Th4NilgPLSgoeHhIaHiIKDg4GEgYCCg4OBgYOEg4SDgoL57evk6Of39vn69fn0/Pz39PHx8fLy9PDq7e/rxdnq7fDu7ezg4ebo6Onh4N/e3t3g397j3tfY1tiA2tfW1tnVrMzT0M7Pz8zKyczNy8rGxsLAwL++v7y7uLS0t7Wxrq6wsKuLiY2Mh4GEhYiFh4iHhf75/oGC/fTt+oGFjI+NjIiHioOLjYaEioqDiYeCg4SEg4CDh4aGg4P//+/Qs7S0t8bW19LIztLPy87EwNTU1NzCvvGAhIH/+O2Azt3+/IOA/ISBgP/7/4CEgeHhgYCCgf3KyeDk6Oju9fLi4uXr8fjx9e7s7OfI/IOHiYWE+/2BhIOC+viDgIKD1YWRlpmUlZmVlpicl5iWm5maj+eLiIOA+vaE/omMg4GC/fj73IeWlIyLiI2VlZKXmZWcnpqcgZmbm5iYnZqamJeAlpaVkpGSypSZkZGOj5KUkpeZlZSWlJCA+ZaRkoqGiYeKhYiKh4uVmJXalZOQkpWSk5STlJCQjuHT596y3PD27+zyhv76gfn4/YL9/PvtsJiRlo6Tk42QmZiFgvT06PSAhIP7qsjRx72+ytDU0tLRzMO5pqOyta+4u82k0aqMicOAo6e+gamH2ZSUxOzBvcHFuqewurq8sKKalpebgf38hImIhICGjY2M/4OGjLKrs5aKramEp6ejqaWsrqubi9rDvsbxwZ3Iir/Z4MmRut2qpbLanOWiuLWv04KVWaWwy9DBktfKu+jNq5d9aVVIe3id4Zu98pGVlpKjp82dpJHLxI6AjpOanKKRr4694KKz9YDwgvPV1MOtqqeXo6yLxI+/sqzCx+Tl9d3zkqmimKaVp6y4x/uVkJaOg/3Uysa8uJ2mnaS42viNnvTR0tL5kqy9vrSkwt7s7/XeaHhNY4Kjx46//L/LyI2Zk4Dtu7aum1wdFhAcM2GplYqn7522ua2glIaAiFd9dnmCkYmQkI2QS0qOnJtUW1idZ0dne4tNUVWPio9NSUxWWGBhY2x6fX99enp8goOHk5WGmolijVCTkHtulHNxbHR9hn93dG9lZXSKk41JgXtyb3B0f42FhYd8hYWFiIWHhIKBhYqHiIWHiYJ6gH5/f4KIiIeGioiGioJxgIOAfIeGfYyJhIaEiIGBgoB0eXd5hIaEgYJ+gXyDiYWJiYiOh3+KjYeKjI2MioqNiYeLiH+FhYeFjIiKi36Kh4uPjomGgICGhoF6cH6FhoKFhXlwWJSPioaSjoqMlWdbXFmhl6ajoZ6goqNXXVxeYVxfY3GfpaCQn5qenJ6bnp6NipeApKamoZuakYSJn5mRlYyYpqWus65dqI2LnqKkqKetrK6jraWalJmhoZpwWaiQWl5fW1xdXllYWVhaWVhZWllZWVpbWVtZWVmso6CbnJuopKeno6ejra6qpKCioqWmqaehpaOdhJOfo6ShoKKZmp6goKCbmpmYl5aam5uhnJWVk5aAmJOWl5qUeY2RjIyOjouIiIyLiomHh4SCgoKBhISDgX18fn56eHd6eXViWlxaV1JUVFdVVllXVqKgo1NToJ2drFpfZGFfXlpbXVljY11bYWFcYl9aXF1dXFpcX15dXFyxsKGReXh2aXGHh4R7gISBen16cYOGgYp7gahaW1muqKGAjJivr1xasFxZWa2qr1lbWp+eWlhcW7GJfIaKioqQlpOGhoiNkZWPko6Mjox4l09QUlBPlZZNUVBOk5NPTE1Pg1dgYWNiYWJeX2FkYWJgY2JjXY1QUU1Ji41QkE1QS0tQn5edjVtkYVxaWFpfX11hZWFlZ2RnVGNkZmNjZmVmZGCAYGNhXl1fh2NmX2FbW11gXWJlYWFkY15UpmNeY2BcX1laV1dXVVdcXl2JXlpYW15bXF1fYV5dW5WPnpJ3kqCknZuiX7GuWq+usFqxsK6ddmFdXlpbW1ZaYV9UUpmYkJpQVFOcdIqPiICAiYyNi4uMhIJ9bWlzeHd8fI1wjXpqaZqAfYaaaYxvqWZigJt+fX+Bem9zenp8dGZdWFZXSpOSTE1NSEZJTExLiUdJTGdrcF5Xa2pMUlJQUVZWVVNbZ6+hm563hWuFVnOFhW9RaH1oZW2CXJt1jImQqGmMVpSHo6iOdr+nlLmlk4V2a1hGemp4m2R4o2BjY2KBvYhmalmJfVqAWVtgYGNbeGOQmnB+u1ueWKOkontraWVYXmNPb1NyZ2Rranl8g29+S1ZWUFhKVlJdZodWWF5XTI9zaWFeXExVU1lkdodOV4hvb3CIT11rb2dccIeTmKesW3JIV2t/il6ArIyTjGVxb2TAo5OLhmUsKiU9SWSRe2d2l2FydW9oY1uAZzlISExSVlNTUFRYLy5ZX18uMjNZMB42RE4rLzFGQUAiHyAnKCstLjpLT1FRUVJUV1dXW1pSUDY9XzViaHBigWthTk09Q0FCRkhERERCREIiP0E8Pjw8Oz86O0A3Ozs4Ojc6Nzg3OT06Ozg7Pzs1PDw7PDxAQD9AREE+QzwsOTuANUBBOERBPD89QTs8PTo0NzQ3PT07Ojw5OzU7Qj5BQkBGQDdCRDxCREI/Oz1APj5BPjc6OTo5QTw8PTM9Oj5BQDw6OTY7PTo2Lzc8Ozc6OzM5On58d3R9eHV5gldJS0qOhZWTkZKVlZJNUU5QU1FTVWSOlI9/joqOjY+Lj4t/fYyAnZaXlI+Mg3uHpJyUmYuYoJqip6dboYF8lpydoKCnqKidp6GYjpadnpReSot6TFJSTk9QUUtKS0pOTEtNTk5NTU5OTlBOT0+VjIuBhYKNi42Oio2JlpeTjIiLjZKUlpSNko6JcX+KjY+Mi42FiIuOj4+JiYeGhYSKi42SjYaFg4iAjImLjpOMcYCEf31/f317e3+Af315e3l4eHl3e358eXZ2eXdyb21zcW1bTk9QTEdKSUxJS05OTI2NlEpKj46QpVlfZV9cW1hYWlRiY1taYGBcYl9YW1xdXFlbXVxcW1uur5yMcm5rWV12dXJobW9rZmljWW9zb3lvfKZWV1amoJqAhIufo1lWpVdRUp2ZnlFUVJaWVVFVVKJ6X2JnaGRpbmxiY2NlZ2tmaWRiZWVXbjc2OTk5amo2OTk2ZmU2NTQ1XkRKS0xJSUpHR0hMSUlIS0hJRV8zNTMvVlYyVi0wLy81bWdsZUdMSUVDQkRIR0VJS0lNT0xOO0VHSkdHSEdJSEWARUlGQkJEYktOR0dAP0FEQUdKRkVIRkI8fEhETEtGSUJEQEE/OzxAPz5cPzw7PD88Pj5ARENDQW1xf3RfcHh+e3mBTZCOSY2MjUiMjIt4WEdDRUBCQj9DSEc9Om1uZms6OzpxXHF2bmZkZ2hoaGdnZWJeUktMU1JTUFlMdm9lerZzeHuOWnpjikxEVGpTVllbVUtPVFZZTz02MC4uKVNRKiopJyYnKCgnRiMiIzA2OjApMDEvPj88QENFQD5Nab2vq67IflJhQFVcXUw1RFBMUFppUbGZ2Nrt/6r/nrlWYWFfi/LMmuDY6fPm37WR+K+IdEBIX4Q4gE1hSDc6LkU+KyosLzAyMEc6f2pDTH44Vy9YZW1SSUlHMTQ2Kz0uOjc2OjY6ODs7QCEmIyAkICQlKC07JictJyBAOi8oJyYkJiUoLjpEJyk5Li4uOyYtNzg4L0FUYnCi9J7OhpScnI5HT2JOUU8wMzg+3NjGrcLtkZiN3erc4ad6CXiHUlpdVVBMR4J9inwKfX18fHx9fX18fYR+Bn9/f35+fpZ/BH58f3+EgAN9foGPgAGB/4CCgI1/iX6sfwGAlH8EgIGAgJqB8IAFf39/gICEf5+Am3+DgId/DoCAf4CAgH9/f4CAgH9/hICYf4WAgn+EgIJ/hIABf5KAAX+EgAR/f4B/hYCEf6KAAX+RgAF/kIABf42AAX+Kfgh/fn5/fn5+f5F+hH0Efn5+fZh+hX0HfoB+fH6AfpV9gnyJfQF8hH2GfgF9inyFewN8fX2GfgF9hXyCfYV+BH9/gICEggSBgICAiH+EfoN/hIAIfXt/gICAfn+JgAt/foCAfn59f35/f4h+gn2KfIt9hX6NfYJ+hX0Ffn18fHyHfYJ+hX8GgICAgYGBhIKGgAaBgYGAf36FfYd+AgIEAIC50NT4hfT98dzg7erzgPWG/+j6jJWctYWLmaq1vO7s9IKDipKaoKGnt8jEwsPHyc/a5PH38NX2Qo7shM+4w4P9gqGuytDR0MS+uq65zvL2//n159HGycfO3NPv9vL07fDx8vDq6fH19PDs9Pbx7Obn6ubo6O3t7e7n7uzw8Ori64Dr6/Dy8/L08+/x7PHu8PHg5uHr8PPx6vD79e/v8O7o7PLx6OHq8/r49vn6+vn59vn16/X49O/s3vbz+vr8/Pv5+vj08eXv6uvm6O7u7tqn7tHQysPEyMTN2tzYzqCHjJGGgID3+Pr16+75gYaFjJWRkI+52tfX3efh4dzHv87d2oDdzsPDxM/Q0c21scvu6/f24ubo7NDN4dzZ4eDg4d/k5eXm6+Tq387L2//t3YOGhoaEhoaEg4SB/oCB/P+AgIL9gP3/gP7/gICDgYKCgOzn5OTU3/X89/Xz7fHu8PHx7+7u7fK67ejq8Ojq5efj5Ofj5+Pk39/g4eLf3uHb2NXW1oDRzsrNzsysxdPRzM7NzcnJycrIxcnGw8HAwby7wLu7uba2t7azsrCtrqntjo6LiYqJhoaFgoH99fD8gYaA9ZyamZiVk5GNkIyHjIWIhYaGiIeDg4mFhoaIhomGhYaBgPfz2sjE4NzV2dnV1dPHytnSy9LS3NLW3d7Vvsj8hOLF33aBgYKDg/38gYH7+v+CgoaEhoPe3OviwNvj5N/k6O/y6eXg2+zv9e/v7e7ezv6FhoaE/IH/gfmBgoL8/v74gteBk5OXlJaVlZeVl5eZnJubmJrahoKFgfn6//yFjIuHi42JiIvjl5aVl5CSjY+Sl5Sbm52fl5rYhISAh4qMjo2Qj5GTkZGLxJSQko2Oi4+TlZGVl5WUk4+SuYKGi5eYnaCbnaCiop6UlpWJ7JORkI+RkZSRkY2Phefc283Cqur29P2Eg4OCgIKB/Pf48e/sxpiDh4+GiZCEjpSQioKD+oCHhoSHgY7J2MrSy8HAytDNs6aetri2r6+rnZ+AkdDJu5HBgIjWl6bWtsbL/fnPu77Bsqeutbe9k5GTkZeag4L5goqCgYKLjoyM/4CGjLeqsJKLraqPwKWrnObj5uXu8MDHx8O+uNyf/rTdmMfe1r+NqM2sqrTamPmuyGi1xXWFU2ia2OuAwJjQzMiT38avkXdmWFCbkZXHi6y5wtmApaiRvb+MkZSdn5qNstHWqa2shIyCzs3C3LKnqp6nmeTEn9Dx1MLKvdbq+ZKSmamUqcTTjJ+o09LgmcHGzKa0pqqYq6SohoiEgfbv9IjjgabS/r29yNHlcG2NWWuApsX2ocqd1tvrpJ+zoaXwuaWahUUaEyEdNWexlo+u55Glta8GqJ6Q7qPWgHKBg5ZQmZqPhIWOi45LlFCajppVWFtnR0lQWF1gh4eKSUpPVFleYGRvfHl3eX1+gIWOk5aQg6CCYpRWloqjashlaXJ1eHd5b2xsZWt2i4uPjYyBdm5xb3J9eImKiI2HiomHhYWAiI6NhoCJjIqHgIKFgYOCiIeFg3mAg4SDf32FgIiDhoeIiYuKhoeCh4KFh3h+fH+EiYaBho2KhoaHhn6CiIR9eX2FjYuKjY6NiouJi4mAhoiGg4F0hoSJiYyNjouLiIOBeoJ9gXt8hIKCemSelZSQi4qOipGYmJeRb1teYFhUVKKkpqGbn6dXW1ldYmFhYH+dlZefp56dmoiFlaKhgKWZkI+OkIyQkYKBlrSwurmop6anlJWnoJ+joqOin6anp6iqpayilI+WtaWZXV1eXlxeXVtaWVerV1isrVdXWaxYra5YsLFZWVtbXFxapp+bmo2Vpq6qpaOgpJ+kpqalo6GepH6gm5+nn6GgoZydoZ+koKGcm5ucnpybn5mWkpOUgJKPi5CRjXeJkZCMj46NiouLjImGioiGhYWGg4OFgYOCfnt7e3h3d3Z4dp1bWllaW1hVVlZVU6KenqVYXVm2cnFvbGpnZWBkYV1iXWFfX19hYVxdYF1fX19cYF1dX1xcraiTgXuRjYSIhoOEhHR3h395hIePg4aPkIp8iK1am4eagFdYWVtcsbBZWKmprllZXVxeXJaZp516houKhoeKkJKMiIOCj5KWkZKSlIqAmU9QUFCWTZZMkU1OT5WXl5ZPg1RgYWNfYmFfX15hYmNlZWViY4lOSU9MjIuRkUhPUE5SVFBOUYxmZGNjXV1aXF5jYGZmZ2hiZYxXVVZWWVxeYF5fgF1hY2FiXIBjYGJdXVldYWJdY2ViYWFfYHRSVFZeXmJnZGRnaWllXVxcVZVaWFhaXV5iX19cXlmdmJmHhnWapKOsXFtcW1lbWrKurqeloIRkVVdbVFVaU1pbWVZRUptSVVNQUk9eh5WMj4mEg4uMiXZvaHV1enZ2cWRoY5KNhGuYgF9rq3iHrpGBfqKfg3p8fnZucnd5fWNeWldXWEpJj0pOS0hITU5MS49ISkxrbHJeWmxqT1xRV1m4uLSvsLeeoqCgnpqzd7h4lGB7hoBsTmFzZGZug1+kdZ1Ul6VfelBlgqixYZJ7tq6dca2fknppYFFJi3x0lGN8jKyTbW9agHhVgFhbYWNgWnqNpHR6eWtdVH+Gk6JuZmlcYFeCb1x2hXNtcWd1g4lPTVJaT11re1Jja4aEildbXWNQW01PS1ZSUkJERUN9fINKeEJacpBqcnmPpFlhgVBda4CPpG+KdZ+bp3l5h3+Ivo2AendWKCdCNUZmknJmd5VcZ3BubGhin2mEgENNSlYsWFpZVVNZVlgsVy1WUWI1NDM0HyAiJCYnQD8/ISAhJSYqKy46TUxNUFNUVVVWWlxXUFI2PmQ7bXCTZbhgXF5EQD9CP0BGQkRCR0JDQUVBPj4+Oz1AOURBQEVDREI+PD44PkRFPTg/Q0NDOz9CPj89QkE/PDU5PTw7ODlDgEQ+QEBBQUJBP0A8QT0/PzQ4ODc6QD06P0NBPz4/Pjc7QT03NTU8REJAQkFAPD9BQj85PDw7OzkyOjk8PUBBQT8/PDk2NDo1ODMzOjk4Ojl4fYF9eHd6d3+EhYV+XUtPUk1KTJOXmZWOkppPUU9TV1VVU3ORiImPlpCOjHp2ipqcgJ+Vj4yKjYSIiXx/m7avuLKdoJ+eiZCgm5icnJ6enKOlpqWnnqeejIWAmouCUVNSUlBRUU1MTEmRSkuUl0tMTpVMlZdMmJtOT1JRUlNSlY2IhXh+jpaTjY6MkImOkZKRjYuIkW+LhYqSi42MjomLkI+UkZKMi4uNkY6OkYuHg4SHgIWDf4WIhG1+hIR/g4GBfn9+gn98goB+fXx/enyCfH57d3NzcW9tbWtwbY5PT01NUU9LTE1LS5GLjZdTWlm6eHd0bmxoZF9jXllhW2FdXV1gYFxdYFxeXVtYXVtbXltaqKKMdmuBfHN4dXNzcV9idGpkcXWAcnJ8fndtgaVWlH+QLVJSU1dXpKJRT5aWnFBRVlVYVYuQmZFkZGdnYmRma21oZV9cZmltaWlpamNcbIQ4gGo3ajZlNzc4aWloZjVZQktMTUpLSUhHRklKS01NTUdLYDAsMzJZVFlYKy8xMTM1MzEyYU5NS01HRkNER0tJUFBQT0lMaT8/Pj5BQ0VFREZESEpISERfSUdKREM/QkZHQkdKRkVFREVSOTw8QEBESkZGSE1LRj49PTllPDo6PEFCgEZDQ0NFQHV5eGtqWHF9fYlLS0tKSEpJjoqLhoR/Y05AQEM8PD85QERCPzs8cDs+OTc5OElteHBxbGZkZ2lnWlROVFBXVFNMQkVIfX94dbxjYqVvfqaFYE9nYVBUVllRSkxQU1lBOTQuLy8qK00nKScmJikqKilKIyMlNDU6MSwzgDQ0Sz9DRrS8sqmptKiuraypo69slVxnRVVaV0o1QFNNUltyVrSZ8Ir08Y/MkrR3X2Q1Y472zp1549/o27u1nITvxZx/RFFfYVA+Py5AOCgpLC8xMDBHVJNHSUlPNy8+SWJwSUVHNTUxS0M2QUM+PT42PkJBJCQmKycsMTgmLC85Tzc8Hx8hIx8iICIhISIiHh4dHj47PiMzHCMvR0FGVX2+kbXqkJyanpWHSVE/VVhTODU/RXngvqShze+Ihui12eDbnnx7jk1QWVhVUUx6S0+EfAF9iHwGfXx9fHx8hH2JfpZ/C358f3+AgIB/fX+B/4CPgJR/h36/f4OAi4EPgIGBgICBgYGAgYCAgYCAh4HdgAF/i4CEfwSAgIB/oYCdfwSAf39/hYAHf3+AgH9/f4aAm3+EgAh/gH+Af4CAgIR/AoB/koABf4SAhH+JgAF/kYABf5CAAX+RgAF/kYABf4yAin6Hf5V+AX2dfoV9B36Af3x8f3+WfQF8iX0BfIR9hn4BfYR8jXsEfHx9fYZ+AX2FfBF9fX5+f35+f3+AgIGCgoOCgYSAiH+Efgp/f318f4CAgH5/iIAMf39/gIB+fn59fn9/hn6DfYl8iH2Hfo99BXx8fH18iX2DfoZ/goCEgYSCAYGGgAaBgYCAf36FfYd+A319fAICBACAk5ylppybnqenoaO3vILMjoSN5puZoaC5nrOxt8Ls7+/9g4uQmqClpq+vtrjBycnT5/X49OjP7jmM7JLUqsud3NeZurWpo6+1t7XB1NXh/4OCgoHly8vKzMvF2/n89evt7fDx4ebt9PTt7/L09Pby7O3q6u/v7u3r7vLw7+7x2+CA4PHy8vPy9fTx8O3x6+3o1ezm6ert8vDm7e3s7ezo6+318uzi+PT09fbp6+73+Pj3+/36/P78+fn5+vr69u/y8vLv9fXy8vHu7e3isoPDx9HUxcLHzMfOz87Mw8bHqIWTlJGMhv/1//7194H78viAhYaFiI22ztHUx7q2xszK1c6A0czGxcrQxcnNzdLR1N/w8PD1zMvl6Oro6Ovp5ejn6Obg4ePl597o0NLby+WDh4aD/4OFhYOCgoGEhYP9/4OAgPv//vv6/PWA//+B/oD+goH6vPDh3d3c29Pm7/Dw6+vr5+vhvu3w8e7u7Ojj5uXh4uDf3d3f2dXX1tLT0tTRztCA0NHKzNDNsbbQztDNzc/Oz83Ky8nIycjEwcG/vr66u7m4ube0sLGyrqangYePjYqFgfX6/4SLkZylpaWjoPWLlpOTkZWSjY6LioiEioiKiIqLjIyJh4eJh4WG/IT9+d7Myd7X09LU2d3W2NPVx8/P187S1tfa39/Xz8/Pnr/h//+AgYODg4H6+vf7+vz//4CGhoaDgPK50ePe4uHl6+rt6+Tg6vHv8vbr6uzY24SBhISCgPuB/IOAgIKFgv3//uH3lJaVmJmXl5aTl5aYl5eZmpj8/IyCgYCBhYaEhPz7+/j/g4KE9Jycm5mZmZWTk5OWlZifmpaK7o+Qj46NiIiHiISAh4iHiIiI1pSTkpKNkYyNj5GSlJOXlpWS0Zman5qWmZqbnqCfn6Cempud2ZGRk5WXlJORi42Gos3t8dbRxrv6+YSBg//09fj9+ezRxcO2yNOlg4WLjYyGiY6IhvyGgPj2ioWC/4KAqL3YxruvwrSpuLu1sLnO3uLEt6OTwsTCr6iA7bOi3vbQ96yL/Pndx7y2sau4wcK7p6KPiJKagvn2goODhoiOj4qI/v6KjcGxrZOOrKaZyb60nNvQw7i65c7Nxr+4ubC12aeCmv7woMvf0bOGs+C2v77ZlOCcwdPOZGt0jll5nM/u6+bVfNvd0JLq2dDRsIppUoBvcoe28p+cpviAg5ifoZ2Xo4zH2aHyvt2438qggqymoqiU7uS/3czy8/PX5fGHq9Pe3eTf5oLvxbXAz9TLgNK0zc640tOonLCyqJ2RiI+mtLy8utLndHGGVGJ2ka7I56G/lYaChJG1naiZmdCtrZyLVDgXIR8cN1qikIys8Jy3rqOXlZaL3JPkgI2AXWRrbWZjaWpoZ2lzdVWEVE5TjVpYXF9pU11dYmiHiYiNSFBUWmBjY2lscXR4fn2BjZaZl4+BnINflWOciKmAs6NneGlhWmRnaGhxfXqAkUpKSUiCdHNxc3NufZCRjoeIiYqIen6FjI+GiYmJioyJhoiGhYmJh4WDhoyKh4OHen1DfIiJiYmIi4mGhoSHgIB9cYN9gYODh4V8gYGBg4F/gIKKhYJ7ioeGh4h9g4SKi4WHjZCMjI6MiYmKjIuJh4KFgoN+g4SFgISCg35kUoWMlJmNjI2PjZWTko+Ji491WWRkYV5aq6Orq6SpWKqlqFhbW1pcX4GVmpuPg4GNkpCbmJqWk4+Rlo6PkZKYlZqltrOytJKMoKesrKytqqWoqKijoKOlqKqiq5KRl4meWl1cWq1cXl5aWVhYW1tZrK1ZV1eqra6tra6pgFmytFuzW7RcW7N+opiVmJiVjKCloqSdnJqYn5mBoqWmpKSkoZyfnJmZlpeWl5uTj5OQi4yMj4+Nj4+Qio6WjXl/kY6QjY2PjI6NioyJiIiJhYSFhIOAfn59fH58end4e3lxdltVXVtYVlKeoqdaYWZxeHh6enWqZW1pZ2NoZWJkgGJhXltjYGRgY2RkY19bXV9gXV2uXbCymYZ8ioaCgYGGi4aHgod6goCLf4SNjI2QjomCg4dqhZ+xsFlbW1xZrq2oqaiqrK9aXV1dW1ikd4KLhIiJjI+OkZCJhYmSk5eakI+VhYhQTU9QT06YTpNOTExPUU+YmpmJoGJkYmNjYWFggF5hYWNhYmNjYZ6WUUtMTElKSkxMj4+SkpZPTVCkaWloZWRkYmFhYWRjZGllY1uiYmFgX15bXFpbWVxbWlxbXI1lYmFgXWBaW11dYGFgZGNhXoNdX2RhXmBhY2VmZ2hpZ2BiZI1dXWFhY2BfXllcV22KoqaVkol+p6hbWVmzrKyugLOxqJOJiIGMlXJSVFlaWFVXWlRSnFNPnZ5YUk6ZTlJ1gpaNg3uJe258fnl0eoyYl350aGCChIV9ibSMga3NpcZ9WqOhjoR8eHNseIGEe2hlXVdZW0uSkEtKSklLTk9MS46LS01wcnJfW2xoVGZiX2izrqafprysppyZmZuXmap+gFloqZ1lfIR6Zktjfm1ucYVfkW6LpKJQV2GAVXKGoKe3tKRosqufb7elpKqTeGBLdWhqboSrbHRvo1hkaGlnY25fmY9tnp+lgoV9eVhqZ2NlVIR/bH1yiImKeH+GT2V/hoWNiYhKhGBZXmdrZUBpVF5iVWJjUEpWVFJNSURMYW11OXd1kKldZX1LWGRuf46icIBoYl1la4JxenJ4q5CGfndZUCdIPy89WIRtZHacYnNsZV5eZFyQXZVQWIA2PT9CQD4/Pz9BQ0tLMUUyLS9YODQ3NTMiJCUoK0JCPz8fIyUnKy4uN0BFSU1SUVJWW1xZVk5WNj9mRXh5mnulnWBgQT05QUJDQ0dLQ0VJIyQiIUJBQD0/QTlBR0hIQ0RFREI4Oz9FSUFCQUJBQ0JBREJBQ0NCQD1BRURAPUE3OgE7hEKAQUNCPj07Pzo6NzE8OT09PUA9NTk5OTs5OTk7Qz4+OEA+PT8/Njw7PkA6PENFQUFBPj0/P0E/PTk1OTk5Njk6Ojw8Ozk7OTQ2cHuDiHx5eXx6goGBfnZ7gGlLVVZUUU+blZ6gl5xSoZidUlVWVFZYeY6SkoZ4eISKiZeVmJWSkY+Ako+OjoyRjZKjsrGwsot/maSpqKirqaWop6ahnqGhpqmiq4h5f3CHTVJRTZRQUlJOTUxLTk9Nk5NOTEyTl5iWl5qVT6CiUqJTp1ZVqG6NhYOGhoN6jZCNj4iHhIOJh3KRk5KQkJKOio2MhoaBhoaHjIN/g396e31/f4CCgoJ7gIyAhHB2h4SDgICEgYOCgIN+fX+Ae3x9fHx5dnd1c3Z0cm5wdHBpb1RLU1JOTUuOkptXX2VxeHh7e3aqanFraWJpZ2FkYmBdWmJhZGFkZWZlXllbXF1aXKZbr7GReGx4c29ubnN6dHRudWdxbH9tdH17ent6dWxvc2GDmqmlU1ZWWFSAoZ6XlpSXmp1SV1hYU06QZGNoZGZoaWxqa2xnY2ZqaGtuZmZrX2Q6Njg4NzZsOGg3NTc5OjhrbGpefU1PTU5NS0lIR0pKS0lJS0xJdmAzLzIwLSwsLS5YW2BdYjUyNH5TUVBNTU1KSUlJS0tMUkxIRXxMTUtKSkZEQURCRkZDREOAQ2ZKR0hIQ0ZAP0JDRUhGSEhGQ18/QkVDP0FFRkhISUpLSEFCRGNBQURFRkRDQj5CPlFuhYh4dXBigYFKSUuRi4uNkI2EdHFwanR+Xzs8QEJBPT5CPj52PjpwcT45Nm06PVlnd2xmXWlfU15eWlRXYmVjTklHRHR7dn2pwYZ+o7CAnbxjOWFfWFpWUk9JUlpbUD07NjEwLylRTCcoKSgoKSkoJ0lGJiY1OTwzLzY1N05LR1m/t6acrL21tK+moKGdpbt4SkhybURVW1NBNUdcT1hecE+YhMH8/oGBmuaYvX5gZGlqmYjhvJVpx8HO2cy/p4rf07ydhH1ASjxYLTQ3ODh7Nz49h1I9W4VqT0NATjlFQj00Lk9NQkk9QkJGPj1BJjA+QT5EQz8jPi0rLi8uLRUnIiQkIiQlIiAjIyIhICAnNUBGTl6O253C8Y2cpKGmmIlMTTY0NTo4OzE6QG/Ux6ubtND4g/LMmpunwYxveJBXXFZQSklOSGg6USwxjX0BfoR9AXyFfYl+lX8Jfnx/f4CAgH59kICEgfqAl3+GfgR/fn5+un+EgISBAYCKgQWAgIGBgYeACYGAgIGAgYCBgeGAg3+JgAF/nIACf4Ckf4WAiH+GgJh/hoADf4B/hoCFf5GAgn+JgIV/BICAgH+RgAF/kIABf5GAAX+RgAF/i4ABf4l+g3+Yfgl9fn59fX5+fn2XfoZ9CH+AfHt+f35+k32CfIl9BXx8fX19hn4BfYR8j3sFfH1+fX2GfgF9hXyCfYR+hH8DgICBhIKCgYSAiH+GfgR/fn9/h4AMf39/gH99f399fX9/hX6DfYh8iH0Bfod9AX6XfYN+h38DgICBiIIBgYeABoGAgIB/foV9iH4FfX18fX0CAgQAgI78+YGGgYOFkJmfpJqm2pGVgJ+qr6WWk6iLwMfD5/P3hIaMlJWdo6aogeSAlrPK4/D09vHlxdU5ieyp14Wmr4qniaWdoJ+pvLrC0tbQ3fOBgvyA9tXOzcfIyc/w9vPy7uzy8ujr7vLz9PX29vf09u/w7u7s6+zv7vT07ena7u/1gOHf9fb19fHw8PHx8+7p9e7y8+Xv8vT18fXu7Onn4u3u8fTw8/Lw5vDr7/X29fXu8/n7+vr39vj7+/f1+ffz8/Ls7u7t7+7p4cCOscbDt7e/zs/MwsrLyLjAvc7P0Mu8iZebk4+NiYWBg4H3+fv194CAgv3+94+uo67Ex8rOztjXgM7Q0tTKydTg4urs6+rYytXxx9fv7+zq5Ojp7+rv6u/r8fHk4uDg0rj3/Pz73uPn/IKChYWDgYSDgoKCgYODgIKBgoGDgf79+v76+vj7/ICBuvD29Pbv8e7s3dnQysPExcnSr9/n6enr4+Li5N3e3uHd4N/X2dja2drWz9nT0tLTgNLRzc/PzbmuztDOz8vOy8zJx8fHxcXGxMS+u7u6vbm4uLKvs7KurKShm5HjhYuQlZinq6moq6mpp6aloqOf5ZSPi5COkZSRjo6Ki4uLjo6PkIqGiImHhoaHhOrf1c/e4N/j39HX1dHT19LT1cva0tDG0tvV1tLX19a5wu6E/oD8IoGEgf+B9fj59fv7/4GCgoaC/dDC297f5eHk7Ors7eLc4+6E8IDp5sfagoqDgYOA+vn6/oCBgIKC+/f858ORmZqZm5qZlpaYmJubmZWZmZHfhomC+PL+hYaC/PaA/oH+/YDbl6CdlZSYm5mXlZeZmpuanp3siJCUk5eVlZWYlZeWnJiXl5bkk5SRlJKQlJiSj4+QlpWWlpPckpibnZyZlpWamJ2gnYCgmpeYiu6SlJWTlJSVkYzTvc7V2M/PxbXO+oKC/vbs7dTPw7zT3N7UyMvMq4CKiIiFiY6EiImDgfTxhIiHh4L36ZDfyqehtczFxcXCqqbJ9vLk5uGx2YL1k4fRrK/3jMXpspL/hPfXvLS0qb/Ly7CqqJmWl5GE/P2IjoeGi42QjYCJg4GQj86wsZOHoaKVurOV1rjM3t7sgt7Z0NLR4tvb4t6U0+HRpfiDpMnb1MSUv//PwL/KhL6Sy9dybmVye5Rcc8Wx4v2F6NmSi/OCz5uD37qLc2pcUpSCdnyPr+iSrdP0h6H9meHw7qGxyMeYramkopCD7Y+gtLu4saiUiZOD/FqLlsSUuc+6zNPS1dT3vsnA5evhyNT0/emL/ILyhoWAfZRRX2V5jaHC4Iuvxo/d6NGV57y5qfXas7W6qZdZPC4lHxo2aKeaiY6/9Z27vLemjIX70o/g4eDh741oV5+eUFJSUlFYXGJjXGOVYWVVYGBjXVdWX0pna2qFjpBNTFBXWFxiZWdJfklWanmLkpaZlox4joJdmHilboWRc3VdZlpbW2JtanJ8gHWAjEhKjUeLenZ1cHJzeIuPjY2IhIuKgoWGiY2FjoCMjIeJh4WEg4WHho2NgoV2goOLe3yKiomLh4aEhoaFf32JhIiIfYOHiIaEhn9/gH18hIOGiISGhYN2f3p/iIqJin17hY2NjImHio+Qi4mKiYeHhX+AgYKEhIB8bFVzioh/foSTlJOHkJOPg4uKmJaZkoldZmhjYV9cWVdZWKeqrYCrqlZXWKysqWaBdHuMj5GTj5iblZibnJWTmZ6epaiqqZ6UnbOOmampqKiipqqyrrCsrayxsqelpKSXfKesqqqTmp+vW1tfX11bX15dXFpZWllXWVdaWlxbsrGusa2vsLS0WluCpaqnqKOkoqGWlIuGfoGEh4p1mp6goKKcm5yfmYCamJiVlpWMk5GTkZKQjJaRkpCRkJCKi46MfniRkI2Ni4yKi4iGh4eFhoiHiIOBgYCAf39+eHd6eXd2cG5rZJZYW2FmaHR5eHd6enx6e3t3d3OjaWNfZWNmamlnaGNjZGZoZGdoZGBgYF9fXl5copiLgIyLhYyMgIeHhIWHgICAdICGgoBygo2FhoOGh4h5h6pds1mwW15ZrlilqqikqqquWVxaXVmvinmBhYWLhouPjpGVjYWHjY+RkI6NjHuDUFVOTlBNlJOSlUxOTU5PmJOYj39gZWdnZ2VkYWFjYmRlY15jYVuIUE5Kj4qSTlBPkoxJl02amk6Lam5pYmBkZ2ZlYyZkZmdmZWhnmVtiZWRmZGRkZ2ZoZ2xnZ2ZmmGRjX2FgXWBlYF1dXoRkgGCOXV9iY2NfXl5lYmVoZmliX2BYml9hY2BgXmBfXImBjJOWj5CJfYyrWlquq6aplJCKhZabnZiNjI11VFpWVlVYWlFUVVFQmZdTU1RUUZmSYpiLdHB6h4SFh4ZycIero5aSj3GNX7RrbqN+h75wqbqLYqZYqJN8d3ZvfYiHb2ZjgFpbXVtUnp9TVU9MTU5RT01KSE5OeXJxXlhnaVNhWVOhm6yyssNot7KpqqqxrK20mWKOmo9uo1FmeYN9cVRtjHhzdoBSdF6JqFtVT1lohVVtsImnw1+spHZvw2WedmKsj21eWVJLiHlvdHWFp2Z4kaddeKpolce0bmx3eF1pZmFfaVNJhFJbY2prZ15SUVdJi0xSaUpdbV5kaWZpbHxcX1dmb2dZaIGLh06QTZ5ZYWdwiE9XXmZveoqUXXmGZZmtpHGqi4eEu72bkouEfF1PT0c/M0hsmHtkaIClZHN0c2hbWKuIW4WDhYiVVYAzZWEyMzAvLzIyNzY0OVo3OS85ODk0MjI1ICksLEBFRSYjIycnKy8wMyQ8JC0+S1NVWl1aVk5QN0BvV4Nle4Bpc2JTOTs9QUhDSkxNPUZIIiVBH0RCQT89P0NCSElFRUM+REJAQkJDRkZGRUdHRURCRURBPz9BQj9IRz1CNzw8Q4A5OUJCQkNAQD0+PT04Nj89Pz44Pj0+PDk9Nzc5OTc8Oz5APT49Oy42MjU9QEFBNDA6Q0JBPj1ARUZBPj89Oj07Njg4OTo8OjcyMVZ3dGxrdYOCgHd/gH9ye3uKiYqDflFXW1ZTUlFRUVNTnaGlop9SU1WlqKNifm9ziIyPj4eQloCTlZuclJKXlZGYnKCglYmXr4aSo6SjoZqhp7Gtr6qrqbCxpaOjo5JqjZGQjnuDiJhPT1RVUk9VVVRST01PTkxOTFBPU1Kcm5idm5+gpqZVVnWRmZaXkJGPj4aFenVqbnF0dWSJjI6PkIqKjJCLi4eHhIWDeoB/gYGDgH2IhISDhoCEg3p8goBzcIeGgoN+gX5/fXt7enl8f3+Be3p6eHl2d3dxb3NzcG9nZmVdi09TW2NndHl4eH19f319fHl6dKdsYV1nY2hsa2ppYmRlaGpmamtlYmFgX11eXluckYFweXlye3xweHVwc3VsbGxedG5tXnF9cnNtcHBwZoGmXK9WpoBWWFOfUZSZlZCXlZpQVVNYU595Xl9iZGtoa25tbnJsZGRnZ2pqaGZlV1w7PDc4OTVoaWlqNjg3ODhrZ2tkZkxQU1NRTUxJSUtLTUxKRUpJRF40MC5aV14yNDNZWC5jM2ZoM19VWFFLSk1QT05MTU5QTk1QT3VHTlBQUU9NTE5OUYBQVlBQTUpxS0tFRkVESElFQ0NETElJSEVkQEFDRUVBQEFIRUhLSkxEQEI+bENFRUNCQkRCQWRnc3l8dHZyZnCHSUuTj4iLfXhvbnyCg31ydHRfPD8/QT0/Qzw/QT08cGw7Ojo6OGZjS3drWVZdZWBgYWVRTFhsY1pXVEZwYLZtiIC1f3y2YJOteUBjNGVhVlJSSlRgX0I6ODQ0NDMwXFgwMS0qKyorKSckJCknPDs/NC0yNzRGRUSoqLe3tMljtrSwsLG9t7SvgEBYXVZGajdHVFtXTjlJZldXZG9Hb2en5oyGgIef4pi84VtnbzNokYZzwFWQa165r5KCho2I/ufWzoCvmI1MUF1kR2djPFWtdEE4Ozw5Q0A1MiwqTisqKSotLiwpKS4lRSUoLSYrLywvLy4uLTIlJiQmKCopMkNNVC5hOItihLPP/pGkpJ+dnJiJSE9OOFZlWztQPjtDgOLYyK6gssrq/OTDl8Ps+rOAcoSUVl1eXlRGRIRlPFBOSEhTMAN9fHyMfYN+h32Hfop/AX6Lfwp+fH9/gICAfX6Bj4AEgYGAgfeAoH+FfgZ/f39+fn6yf4mAlYGJgIKB24ABf5KAAX+bgKJ/CYB/gH+AgIB/gId/hYCZf4aAhH+FgIV/koATf4CAgH9/f4CAgH9/gH+Af3+Af5GAAX+RgAF/kYABf5KAAX+JgAF/in6Cf5x+gn2FfoJ9lH4QfX18fX19foB+fHx/f359fpF9gnyOfYZ+BH18fHyGewF8iXsBfIV+AX2HfgF9hXwFfX1+fn6GfxCAgICCgoKDgoGBgYCBgICAh3+Hfod/CIB/fX9/fn19h36MfQF8mH0Efn1+fYV+iH+DgISBhYIBgYyAAn9+hn2HfoN9hXwBfQICBACAztLAy8/g84GChfSJgIfsg4Wui5iOxaSXoKmAvL7Z9PyFho2Wl5qgoZ+N3fWBk5arxtrq49G2yjmI+sjIjoj8hYqMkZGdpqu1vsbf2tjf7fz6gYH118zWyMzByOPz9Pby8fT04u3v8/T59fXz8u7t7/Hw7evy7u/x6fPy5+ft8fGA7+3t8/Xz7O3t7u/09vPr8vX28e/u8fHy7urq3OTl8fHy8O/y9fPz9fPw8vbx9ff09fD39fH2+vr49uvv8u7i4Nzr7Ofl0J/evcC5lLm9v77BxszBt7S3ycjLzc3Tzc7Ok5OWmJaTmJaQjoWA/P//gPT2gP/1ipGcwsnAxdHT0tOA1tjR2Nbq5+/t9fXt4+Hk4sLE3ePv6eXu8vHu7ufm5eTo6+np4+CO9/j9+fr8/vzo4uXo/v+AgYODgP//hIWEg4KA///8+v+A/4H7+fb6+dzU8fXt8u3s7+3s8+7u7uzs4NTBw8S/wMHBwcXL2N3a39rb19nZ29zW2NXT09bT0s6A09TTz87PuqDMy83KxsjIxcbFxcLCxcK8urW0tLe0ppyYmp6enpifnqavqKirrKusqqipqaaop6alo5+doKCU3ZKRlJWUlJGLjY2LjZCNi4mEh4aGgoTu1dne5Obk5OHg3NrP0M3S29bQzNbc39nUzNjYzs7M0Mm5zfqAgP7/goAg//6A+vr69vH2/Pr4//v++ty22eDY4uXi3d/g6uzq3uiE7yby5d/E6fmJiIH57/j5gfj2+YCAhf3z9t7mlomGhIOKlZeUlpOYmISagJfgg4OG+PP0gISHh4iIgoaIjIeA5J2bmZOVlJqalJaSk5Wbmpee4ZKUlZKVlpWWlZiWmJ2blpWW7JGSkY2Oko+VmJCMj5CQlJSaivKbm5yZnZmampuWlp6dm5yamtaTlJSSlJOQj4vfzNDGyNHPz9O76vTezsnE0OPc2trW49rYgMnP0NSyiIuMioeIhoOAgfj4/YSFhIKJiIT7lp/D0tfb1cjHure3x+P97t/n3ZfTiufmuvyEgpDgiILMi4GI68GytLLJ176ura2Zkp2fkoeKk5ecmZiRlJOcnpWgo9WtrI+Hl5r0pI/qwsjH18jc287HwLm+w8vfhZ+6zNbg6u/UgKj7+6PE3dfCmeCY2czEyeCb7rbegoDvzeKBklZnkaTvlIiF9qK6mI+HgM2R9NTev5D5d21hW1NOSkRGlqGtk8zCuuyOi7K3u7yql+iYhoKDjZePgoHz+YOVnsjH9On5iYSIhJKVgI2Agvrx+3p6gYVKV1xjbnmPnK7G6pe51IK0MsPa54fCw76xoI2nvd/Ow6RdPzAkHxksXlWfk4Oe14iwwbuxqJ+aicSUrJWUna2949S3gIWHfYOIj5dPT1CXUk1Rl09ObF5kXHldVlxeRWVne46TTUxRWFlbX2BgUneFR1BRYnWGk4+BbYyCXaGSoXltz2xcYFVSWV9janJ2gYB8gYuOi0lIi3tze3B4a3OFjYqLiImMi3yGiYyOkY2NjIuGhYaKiYSDi4WJi3+Kh31/goeGgIODgYaHg4GEgoKDh4qGgYmJiIOCgoSDg399f3V2d4OFhoKCh4mGhIaEf4SIg4iLh4iEiYeEiI2NjIl9gIN/dXVyfoGAfXRekIaKgmeDhYaEh4yTioSAgpCQkZOUmZSWlmZjZWdkYmRjYGBaWK2urlemp1etpmNtb4mMiY2SkpSVc5mcmJqUpKGmpqqrp6Skp6WLjqKeqKekp6yurq+pqaenq6yrqaagYq2ssaqpq66un5uen7GvWFlcXlqysV1eXl1bWLK0sK+1WrVcs7Ctr6+bkKWpoKShoaOhoaekpJ+dn5mQgoWHgoOEhYWJjpmalZeTko+EkoCNkpCQkZWTjouPkY6JjY5/bYyLi4eFiIqIh4aHg4SHg39/fHx9f3tybGdpbGtraW9wdXt1dHZ1dnh5dnd5d3p6dnVybm51dWmeZmZramlsa2RmZmRmaWVhYF5iX15cXaqSkpWWmJWVko2Ih3+Ce4OLh4V9g4mJg4B5iod+gH6EfoB2jrRdXLKxWliwrlepqKemoaarrKyysLGtlXKBhX6HiYiFiYqSk5SLko+PkI+Siod6iZJVU06ak5eRS5KRlE1NUZqQkoabaF5bWVhbYmJeYmBkYmNjY2Rji09QTouGh0pQUk5OUEtQUVNPTZhqZ2RgY2NmZ2NjYWJkZ2ZjaJhkZYBmZGVnZmhmaGdrbmxoZmWeYWFgXF1gXWRlXltdYGJlZGlcnWNjY2FlYmRkZmBeZmZkY2JiimFgYF9gXltbWZCOkouNlJGRlH+jqpuPioqWo52ZmpegmJiPjo+TeVZVV1VUVVJRUVGdmptQT09RV1RRn2JlgouRkpGHh3t2dH+Tp4CelJiSZpFhrLmVu2Zmc8ZraJRcVlucf3V4d4eTfWxnZltVWVpTUVVgZ2pmZFteW1xbV1pbgG9uW1ZgY4tZWK2YpaWoo7azqaGempucnq9ea32GjJafo49soKJidIF9cVqAV353dHuLYpp3nl1etKO3an9PY4aQsG5nY7Vyk3p2aIBel2qznKiPacNhXFhZUkxGQkOFhIR1loV5pFdadXh2cmhch1dNSklMU1BISYCISlNXcW6GfINLTVRQVlZPVlBRoaW2Y2x1fEtYV1tgYnB3fYWbaX2RWX+QnKZjlYyJhHt2mZywmZSOXVFQST80T3JShG5ha4labXl2cG1nZVqBXglvXl9mbnaLhHaAW1lTU1NVWC0rL1ovLi9ZLzA6NTk1RjY0NzUeKSs5R0glIiMoKSsuMDEtOkAgIyMtPk1bWlRJUTlAdXKNbGW6ZltmRjo8QEJGTE5JS0ZKSkVCJCJCPj9HPUQ5QUdHQUJAQkVDOj9DREZLRUZFRUE/QUZFPj9FQUZIPEVAOj09QECAPT08Pz89Oz06OTo9QD05QkFAPDs6Ojk6ODc4NDIzPTw8Ojo/QT49Pzw2O0A9QEI+PzxAPzo/RENCPzU2ODMsLS01Nzc2NzVocnhwV25ydXR3eH56cm5xgoKEh4WKhouJWFhZWlhWWlpXWVVSoaSjUp6gVaiiYGhpgYOBh4uLjpCAk5iUlIyYlZiWnJ2ZmpqcnYOGnJajo56gqKusrqempKOnqainpZxXlpWak5CSlpaHhYeImJZLTVJVUp+dU1VUU1FNnJ6dmqNSpValo56io5KAk5iPk5CPk5GSmZeWkIyNiIN1d3lzdXZ3eHt/i4uEh4SEf4F/goJ9goF+f4aGgX4PgYaBe3+BdmSAgIF+eXx+hHyAd3l+e3h2dHR0d3VqY2BjZmVkY2tscXhwbW9xc3Z6dnh7en18d3dyam12dmmeZ2lta2xxbmZoaGZnbWdgXl5kYF5bXKiQjYyJiYSEg351dGxza3F8dXRrcXd3cnBneHVqbWlvbmiItFxZqaVUUaKhUJmXlpSRlZmZmqSho5yHXmOAZV5maWpoampyc3Rsb2pnZmdqZGNXXmM9OjdvbW9oNmhnajg1OGtlZ16AWFBOS0hJS0lESkhMSktKSkxKZjY3MlZUVS81Ni8vMi8zMjMxMXFUT01LTk1PUU1MSUpMUE5JUXVNUFFQUU9NTk1QUFRXVVFPTHhKSUdDREZES0xDP0KARkdJSU5EcEZGRUJHQ0dISEJBSElHRUVFY0RDQkFCQD4+P252e3JzeXd3eGqDi4N6dXV9i4V/gH6JfHpvc3d7Xjs7QT4+Pz07PDtyc3Q6NTU1Ozg1aElOY2Zrb29mZ15YVFdbZ2FXWFRHeGK+2q7DW11msWJcbDg0N2FUTE5OXmmAUD04NzIuLi4sLjM5PD8+Pjk6NTU1MTEwQzw+My0vNFlHT6+lsq6yrr+2rKeppqamqLlLREtTV1xiZVlIcHBET1hVTz5XOmBeYWh3T4Z2tnWB++j8l9CLttaMajo4M2BOgXptW010WJmGjI159IiVoamem5CNheK+oIB/ZFJyOzttT1BIQzs1SzYuKyspKCglJUxOKCsvOTtFSU8qLDAwMjQvNTk/k7fzoMnv9ZSnpZuakJaSjoiHS09UM0NNVF00R0FDRkp99OrTtrDO3PL437qTyO6IsYlqcoZPXmRdWFZSUEZfREo9OT1BRE1LSod8Dn19fXx9fX18fX19fn5+hX2Gfop/gn6Kfwp+fH9/gIB/fX+BkYCCgfSApX8Jfn5+f35+f35+sX+PgIWBgoCGgYWAA4GAgfWAAX+WgKR/CYCAf3+AgH9/gKd/g4CEfweAf39/gICAhX+SgAd/gICAf39/jIABf5GAAX+RgAF/koABf5GAAX+JgKh+g32HfgF9lH4MfX18fH19gIB8e3+AhH6gfYZ+g3yQewF9iX6CfYZ+gn2FfBV9fX5+f39+fn5/f4CAgIGCg4ODgoKFgYKAhX8Bfol/kH6KfYJ8iH2KfoN9hH6Lf4OAhYGGggGBjIADf35+hX2JfoJ9iXwCAgQAgK7G0s7a/4Lu1+72iIuK8YiWif6YoduCg+rGuIq50vH+h4iQlZibn5yameHyiZedn6SdmqezrsY6lZXnn9iR3duRo6mPnqy1ucDQ5dvZ3er9//uBhO7R0dTRzcjO5vfy9fHy6uzu6PTt8vP49fTz8+3v7O/v8PDv8PD19fDw9/LsgPLt7+r29PLx8fHs4u7b5O3x8fPx7uTq5+3t7ujn5e3u7/Lu8Ovp6+vu8vT59fH17vPt7/Hz9vTw7OHiz9rq4+Da4tSc6LDG2cm8uLeWurW2s62yysbKxMbOzs7Y1sjU09ugjpmXnZ2Xk5abkpKOjYaIhfyNqautuqa4ycnK0tjaQuTq8P709/T46uz07ejo697d5tfGz+Hj6Orv7PLv7/fv6e7s7+vv8Pr8/f/4+/j89vv28NnZ2umA/P//gIGBgP2CgoSBe////fn8+/v9+cH09fb39e/w7ezv6uvs8u7q6u7t6erq5+jj5eXbyMLDvb25s7W2pKytsLGzsbSwtr6/wMG9urmukLi5t7Szqqqmn6CenJuYlY+Qi4SkqKatsbe5trq1tbGwtrKuqrGwr6+wrqqmqauknqOhn6Cfn5+R9ISSgJGQjo2PjI6Hh4qIiIiJ8tLU1+fu9e/f3OHY2dTVz8nL1tbV2N3V0NTK2t3VxM7UycTfgYCB/4GB/4H48/2A/v369/f9gf36/v7vwcna4OHk4N/T2N3j6Oji4Ovw7+7t58zCgP6BioP78/D4+ID4+Pv//oSA9ePcl5iZnJ2bk4eDgIODh5eZm5ubmoDsgoeF9/yAgoWKiouIgoSLj4nni52YnJiYlpqVnpiXkpabnJia4pCPlY+SlpqZlpWVl5SUm5ST8oyRj4iNkZKRm5iSj5CVlpiamNWemJiam5ubmpiYkpqgnpqamovskpWSk5KVkrnM19PP0NPLu77Inr7OzsjJgM7P297f2eLY2NDZ1NjUu4aHjY6G/Pj6h4PxhI2OiYHwj7PS1LutvL7CwMvIx8XHyd+BhOvu+Mil79fwp4GTvPT/noS0nYKB8cu9vrrLyKmoqKmWlZ6floT7iouMi4yWopydoqy0tdykoYaBmJ/N7Ojj08bJwcPgy9rQyrrC0JHdgKO3u8DO3Nzf6fmA1on07pi1zdHKrYG6htnMuc7ouIvLgpGXjoKCjJq0bI/Zpc2YoL/Hp/j1vLKnrK6TwZfs0cS2p5yVj4J7bmJfWlJMREJEQ0OHiouGho6UkoiIkZKRmZ6an6Kcl5KRR0OAgUZJUU9XW1xndIObsbS9x9SAo7vcO4GbrLWotvz47+Pcxpb8qs+A1crtj0oeLSUgGChcU5iLhJK496TF29bFp5qZhtaduY+Pjpmuqrvdz7exgHKAiYaPn0+Mf4yPTFFXnVBVUpJZY41ZW5BtZktld4+YUFBVV1pcX11bWniDS1NYWl5bWmZsa4iCZWSzh7R1u61gb2JOXGVsb3N7hH99gYiTko9KTIp1d318d3J1hI2Hi4mJgoSEf4qDh4qPjo2MjIeIhYmJioiKjoyOi4SIkIqEeomBgH2Ih4iFhYaCeIJ1foSFhIWDg3h9eoKBgX16eIGAgIN+hYJ/fn6ChIWLh4KHg4iAf4SHiIiEfnp5bXJ7dnVveHFVkX2SoZKIhIJpgn9/fnp+kI2QjIySkpKcm5GZmaFwYGhmaWhkYWJoZGRhYFxdXK1pf4KDjXV/hIxMkpWamqWupauus6mprqinqKmgnqGWi5Gio6apr62xr7K7squvsK+sqqmvsLGzrK2mraivqZ+SkpajWrC1sllbW1qxXV1cXFtbtLazsIS1gLSFqKmrraajpqSkp6GgoaekoaKlpKGlpaCfm5+gmIqJiIWEgHp9fW5ycnV2d3d6d36EhIODgIB+dmN/fnt6e3V1cWxramZnZGJcXFpabXFvdnh/gX+Ef4B6eX99e3d9fHt9gX98eHx9dW5xcG5wcXFwZ7JqaWprampoZmdkZV9ggGNhX2FhqpaOjZifqZ+Ujo+EhX9/fXd6goCEio+Hfn10iouCbX6HgIacWFlatVtarVmno6tXraupqKivWrGxtLWnf3mDh4mHg4R7g4eMkZCLipGSkZKQjX53TZVJVlGZlZSYlUyUlJWXlVFOkIaUaWlqbG1sZlxXVlZaZGNjZGRkgFONTVBOjI5HTFJRT1BQSktRVlGFXmhlZ2VmZWhjamVmYWRoaWVnlmNjZ2NkZmpqaGZnaGVmamRjol9hXVhdX2FhaWdiXF9lZmlpZYxkYWJjZmVmYmBfWWJraWJjY1qaX2FfX15eXHaNmJOTkpOOhIGIbIKMjIeMkZSdn6CZoJaWgI+Wk5SRfVJTWFhUnpyfVVGVUVdXVlOcXneRjn5tdHmCgoyGhYKEhZFYW5ugqIp0qJe+iGFwlcDWe2mJbFdVnIN5e3qHhm1nZGVZWFtaVUySUFJST1BZYmBiaXB6fI5pZ1dSYGaRuLGtpqCjnKS9qK2lpZyhqm6VbHt9f4aRkpebgKZXk1iem19yfnx2YUlqTHt5eX6Sb1Z/V2Rua2ZocH2aZIO8i5x0e5CSgKyykpGCkIFniG6omI+LgHd0dGxpZFtYVVBMRkJDQ0CAfHp3dXl2c3Rwb3J0eoaCjo6LjIeDQ0B5e0FHSUxTVVBXXWh0f4OLipFYcn2YXHF9gniFwba+Mp+kl3bRmqhom5y3f1UpUEdANUtvUoN2aWZ9oGZ7iYh7amRkW5Blc1xaW2JvbXWJgXNxgEhUVFFVWi1UUVxcMC8zXS4xMWIyNlQwNFM6OyIrNEhJJycpKiwtLi0sLzo8ICMmKCsqLTlERVA6QEKMeqByq6daZj41P0ZKTFBMR0ZESUtLR0QkKEY/QUdHREE+QUM/QUBAOT0+OkM8P0JHRkVERUNEQ0ZGRUNGTUpJRD5ES0U/gEI8Ozg/QEA8PD06NDw0Oz8+PD07OjI0Mjk7Ojc0NDo5OTs1Pz07ODg7OjpAPjo9OD84Njs+QD84NTMxLS8vLS0qMS8qZWqAjoF5dXNab2pubWhrfnx/fX+Dg4SOjoGKipNjVFpYW1xXVVhdWVpYWVZYWalmgYWFjnF1f4GBfoWKgJCMkJ2UmZuimZuimZyen5aTlIWBjJycoaevrbCrrriwqK6vr62bkZibmZ2UlY6WjpWPhHt9gpBRnKCcUFJSUp5VVVJTU1KkqKekqampqqt4mZudoJaTmJeZm5GSk5qXlJWamJOXmJWVkJSYk4OBf3l3dHF1dWBjYWdqaWlsa3V5gHh5eXR1c21bdXNwcHFsbGhgYmBbW1dWT1BNU2ZpaG1ueH17gX18dnN9e3lze3t8f4SCgHmBgHRscG5sb3FycWi0bGxub25samlqZGRfYWNhXmNkrpeFgo2Tm5CDfX91dG5vamVocm5zeIJ4bGpienlvWWhwbHmXVFNWrVZUnlKXgJSdUJ+cmpmbolOhoaeommxgZGdoZ2RlXmZpbXJxbGtxa2lraGdeWThnMj45bm1tbmk3ZmltbWU5NmRdeVhYWlxcW1ZNR0ZER05NTUxMTD9gMzUyV1gqLzY0MTEyLC0yNzJTSlFOUU9RUFNNVU5OSUtQUU1Qdk1NUUxNTlFQTU1QgFNPTlJMTHxJSkU/REVJSVFNR0FFSktNTUpiSERERUhHR0RCQT1FTkxHR0ZAbEJEQ0NBQUBVdX15d3Z4dGlrb1ZvdnVxc3h7hYeHgYd8eXR9fH55aT07QEJAeHV2PzttOjw6OThuR15wb2FQVVpjYWpmZmJkZGM3OF9lZFhTiYLPgKRmaIW1vWxeckAzMl5STVBSXFpCPTg5MTAwLy0pVC0tLCwuNDk8P0VNUVRTODctKS41b7q1sqyoraSsu6ixq7KrrbFkakdKS05UWlpbXmc2XTVjZkBLU1RRQzNHOWVeXm+EXEV2Vm6Hko6Jlbjzn8bmiWdAQEZIQmKUiX5pXVVAgFdLgnmDhYyQkJuor6ifo6eil5SSkI6B7+HX2cvJu7SzsbKrscPP1uv5+/3w/IyF+PiAio+MmJOEiIWIk5malYeARUpNWTM8RklFS2BSS0ZOVE31+Ph2wr7d5OmA9ta0irHfgbOXeW6AlVdlbG1jVFBRSG1IUT9CPjs/PUFQTkpJhnwBfYR8EH19fXx9fX18fX19fn59fX2Ffop/gn6Kfwh+fH+AgIB9fZSAgoHwgK5/AX6zf5GABIGAgICEgQGAhoH6gAF/koCkfwyAgIB/gIB/gH9/f4CGfwGAnX8FgH+AgICFfwGAhX8FgIB/f3+TgAZ/gICAf3+MgAF/koABf5GAAX+SgAF/koABf4eAAX+kfgZ9fX1+fn2FfgF9kX6Cf4V+D319fH1+f4B9e32Af35+fpF9AXyOfYZ+AXyQewJ8fYp+BX9+fn19h36CfYV8A31+fol/BYCAgIGChYMBgoeBgoCVf5Z+BH9/fn6Qf4SAh4GGggSAgICBhYABgYSAA39+foZ9iX6CfYx8AgIEAIC4uej6/9/b7vP88YORh+6RmIeInpedorbqi470yKTn6veIjpGUlJGUmaPygZSinZ6kloyNk4fPOaLD46r17o6vlK6yoqW1vr7B4u7h2d3l8P/zhv355NfU1NDNzej6/fX0+fTt9eju8vr19vr29fbz8/T07u7u7+zp9vr59fDr5Gjt8O/s7+vm6O7u7Ovv6Ovn6/Hv8vPy9vX08vXz7e3z7+nt8tzm6fH08/L19fTz9e/18/Tt6O7v8O7g3+Hi2tnLoOe2vr7CvrrGub6/vZy0v8jKw8jKwsfLxM3L0NvX0NHQ37mMmKChnISZgJqZmpOas9XY0MnCxci8ttLs7enw8fDt7vH2gvno5eDb3d7m4Ofe4ODZwr/S1+Dn7O359+fv9PLs1tbw7/L0+Pb19PX+9/L18vPy59TRyef6/f/9gPv6+f6AgYH//vv49vT4x+Lz8vPz8Ozq7fHo5+3t7evr6+Xn5Ojl5Obm4uTmgObi4eTb3+HfyaS7uLW2uLG2uLazq6ypqKOhn6GdmJqcnpmamZWXmZqUlJaSlZL7uLW1tLSvube2r6+xsbO2trO1sbSzsrGurKuooqChoZyan6KgnoCElZWTj46Pjo6Si4iFg+nq7fWJ+sXn5+3k5OPi3NrX1tTQ0NHQy9Xg29fXgNfV2dHPz7bK9oSEg4KBgYD9gf35/fv4/4D8+/yC+/r59Mm/5N7X3uHj49rU7+zs4uTm7/Du7+rkycv2gomDg/r2+PD4goH89vn7+/z64tKYlZeamZmZm5mYmpqUg4CBg4OJ0YOEhICBhYKDhIWIiIiFiYuPh9ifnpqenJmUlJWbgJiZmZiZnJeV3ZGRlJOUlZuXlpaVlZOWmZWR+IeRkYyQk4iLjp6ZlJGTk5aWl9OWl5iVnJ2bm5qZmJaZoJqZmp3gj5OTkpGS/83gzcXBvsva4N7g0K3Ozc3IxcbF0tvb2Nng2NXUztfiu/uMiv//9/r8g4GHiYaOrdHa3tXQzcmfgLTCw765t7C6zPaJiOv2gOy6iv6Wju2hqNOE1Z+CzYj3+ODKw8PGuKmiqa6fl5mal4eJiouLjI+OkZCLgfmKkrifnoOClJ7E9fLl3c/Nzr/62eLX0M3top24vMjP09PS2NPN0NjMtLLOjKPzkK7H1NLIoPOo7NTX0s70xYrlkrG7gL6nlK/G3m1tgqJms6r9j5ych4ifoPXT8+3so5H4/aeThOjSvbKqm5aVkI6Jg319fHp2dXN1dHh5e4SKjI+XnaGmqbG3tbzE2OH2otrQ4IOTmZeko6CVmLiF0dPoiIGFtJOvu53HhInStkweGSchHC8sVJaXiP+IqNuWwOH55867FKGT+smJtsihgoOYoKm32ea6qbO0gHZ3k5qei4KKjJKNTVZVnVtcUVBbV1tgaZBZW5pxXIWGj1BTVlhYVlhaYoRIU1pWWl5XUVFWUIiAbIu8ldXFe4BkcmdcXWdwcHGHjoR8goaLlIhNjpCCenp8eHZ1iY+QiYmPioeMgIaIkYuNk4+NjoyMj5CLi4mLiISPkZGNhoN+aISFhH+Df3x9f4B9foJ8fnt+gX+BgoKHh4WChIB8foWBe4CDdH2BhoiJiIuJhYSIg4iEiIJ8f4CBfnV2d3dzcm1ckYCJiI2Mh5GEioqKanqGjpGLjo+Lj5OMkpKSnaGXm5mjhV9nbGxnhGWAZ2ZnY2yCn6OdmZKVlImAj56enaKinpibn6dbs6emo6CfoaWiqJ+gn5yKh5aZoKisrLi1qa60tK2Ylqilp6itra2rq7GqpKelpaKXiIuMo7C0tLJasbCutFxeXbe0s7Gur7GMnKioqqqloaGkp56fpaSlpKWmoKGdoZ+dn56anKCApKGgopmdnp2OaXZzc3R4b3N2dnJraWdoZGRiZF9ZXF1fXF1dXF5eX1paW1lcWqaAenl5fHqCgYB4eHl5f4SEgIN/gYGCgn98enhybWxubWxwc3JyWmFtbWxoZ2hoZGpiYF9fqKenrWO1hpqcoZiXlZGLhX9/fXl7fnt0iZeSjoqAiYeJenmDeo6vX11bW1paWK1ZrairqaiuV6ytr1uvr7CriXeMg3+Gh4iJg32TkpKMi4uRkI+TkIp8fJVOUk5QmpeYlZdPTpiTlJSTlZaGi2lnaGppaGlraWhqamVZVlZXVlqBTlBNS01NSUtMS09NUE1QUlRQiW1sZ2tqaGVlZGiAZWdnZmdrZWSVZGRmZWZmamdmZ2ZmZWlqZGKlXWNgW2BjWlpbbWlhX2JjZ2RljGBhY2FoaGZkYmJfXV9pY2BjZpFdYGBfXl+mjJ6OhoKAiJSbmpyReI6NjYmGiYuUnJyamJuUkZGNl6KEp1tZoJ+eo6ZUUlVXVFx2kZmblY+MiWWAc35/gH56cX6Np1xem6lYoYJhsHFzs3aEpXC8f2eLW6OjkoN+gYN4bGZnal9ZWVhWTU9RU1FPUE9QT0xHjExRamRkU1JcY4zCv7e0pqamoMasrqejpLtya3t9hIqNjY2Tk42LkIh2dINaaZtYand/e3RfiF6He3d3gJF3VpFeeIWAiHx2i5+3X2V6mWChhsdscoJgZ3Z2vau+tLR5arW1eGldqJqNg394cm5rbWpoZ2RiY2ZhYGBfY2RobnNzcnZ5en2BhYWBho6Rm6lvl5ynY2xqc3h5eHBwhGWbm7JhW1+PdJyoepdiaKOcWioqVEw+XUVijHxtu2J1k193jpyVhngUa2GpiFh2fWVWV2JnbXWJkHNqcnKARklXWFtVU1ZWWVUuMzNeNTY1NTkyMDQ8VzI0VTYmPjs/JSYoKSoqLS41PiAkKCYoLCwqLS8rSTxBY56KxrFye2JiQD05PklKSVFPSERNS0dKPSZFSEVBQUVDQUBJRURAQEc/QEY9QkFIQ0VMSUdIR0hNTklIRUpHQUtLTEhBPzwNPj8/Ojw5NzU1OTY5PIQ2Vzk1ODk4Oz07Ojs3NTg+OzY6OzQ8P0BCQ0NCQDw7Pzs+Oz06NTc2NzUuMTMyMTAuL11seXuAfXiAeHx/flplcXqAe39/eX6Ee4ODgo2UjY6Nl3tTWV5fXIRYgFtaXVhie5mgmpiVk5OGeX+JioqOjYiAgouXVqmenJqYmJmemaCXmJmUgn6Skpunqqi1saastratkISTjo+RlpeYlZWakYeMi4+OgG93fJGdoaSjUqKgnKRWWVisqKimpKSohYybnJ6dlpOUmJ6TlJqXmpqam5aXkJSTkJKTjZCWgJ6amJuRlJaUiFZfXF1fY1peX2BdVVNRU1JSUFNKRklKTUpMS0lNTExGR0hFSkmWenNycXVyfXx7c3R0dXyFhoKEgIKDhoeDf315cGlnaWtrb3N0c1tjcXJwa2hsa2RtYWBgXqaprLFltXyLiZWMioiDfHZubmxnaW1qYnuOh4F5gHV2d2dlb2qDqFtZVlZVVFKfVKCanpuZoFCfoaRXpaSko3xgbWRgZmhoaWVhdXR0cG9tcWtpbWxmYFxtNzs3OW9vcG1sODhtaWtqZGVlXXFZVlhZWFhYXFhWWFhUSkVFRURGWzQ1MjAzMSwvLi0wLzMvMTM1Ml9WVlFUVFJPT01TgFBSUE5PUU1MdU5NT0xNTFBMTE5QUU9QUExKgUlMSEFHSkNCQlRPR0VISExJSmVERUdFTEtJR0RDQUBCTEdERkpqQUNDQkBBdHCEd29pZ296gICCdmJ4dXVxcHFxfIOEgH6Aenh4dX6FbnhAQXl4eHt9Pjw9PDhFYHl8fXNramtJgE5WXFtZWFNga3c7PF1mNWNZSolujsV0c5Jgo3FdYTVcX1lTUlVVSUM/PD03MjAvLSkrLS0tLCwsKysqJ0wmJzQ2NSsqLTRswrqwsKqwsabAsLaxraq5VEROTVBTVldYW1tYWFxXR0ZPNUBkOkdSVlVSQ2REamRkaXKCX0V0VHCPgJ+gnarA3ICgyOuGoUxfMjM5MTE+TYt+hHZtNjNNZE5MSY2Pi4mMh4SFhoeJiYeKjpGXlpKYl5mborCpqqWimpybl5uWiYSDgoSHTV9bYTY7OjxCQUVBQUwyR0ZQKSsuUX/1/IiOaGnZ7+yCgPTQoN+Rt9msjNJhb4VSYHB9dWphFFZOh2k9P0A/PDtDQ0E/SU9IRklHi3wEfX19fIp9BH5+fX2Efol/AX6Lfwl+fH+AgH98foGTgAGB7oDFfwGAnn+agAGBhICDgdOAAX+0gIR/AYChf4eAAn+Ahn8FgH9/f4Cef4SAhX+CgIl/k4ABf5KAAX+SgAF/kYABf5KAAX+SgAF/hoABf6F+A31+foV9mX4If39+fn9+fn6EfQl+gH98e3+Afn6dfQR8fX19hn4BfI97AX2RfgN/fn2HfoJ9hnwDfX5+iX+EgASBgYODhYQCg4KFgQOCgoGEgKl/hICKgYSCB4ODg4KBgICEgRKAgICBgYCAgH9/fn19fXx9fX2JfoN9j3wCAgQAgM7Y6oPq7Pbt/4CKo5SI8pGLj5WinrennKabrMyNj63lgYyPkJOMi5GWpY6DlKSZmJaNkJSdjtBqqduwhYqe/5KWrbi5093Vysry+OLS1NLwgYKCg4Hz19nd19HQ3PL7+uvd7PLu5ebx7vL69/P29fPu7+3v8t7w6eX19fPt7+rrgPDy9vXz6+vl5uvx8fDv7uvq8PX19vPz8/Lx9fLp3+v28/f67e318PPx9/T18vbw7PH08OPo4OPr6+Xk3sij6bO5wbm5vsTJtr+1sbfDvaHCyMO+wcPFxsnOzMrL1tvUyNLP0sWPnKSfo6SfnJ2NocrX3eDh3drc1NHO0sLF6fX0gPbw9vrp59/e4eTj2tfe4eDd3t/g19fgxr/K6Ozv8PDt6+rs3LHs7PHw8O7u6+7y9PLz9vPv8e/yycvw3dre3fn7/Pz9+f78+fj39/f23uDt8u/w9O/s7efn6erq7Obn5eXj3+Lh39vZ3tva3d/f3+De3NvfrK+0s7W0sq6wsrKvOrCtrKerp6KioqaioZudm5iZmZaTkpSTkpaa87m4t7O1tbe0rrCvsbOztrOwtrKzra+vq6inpZ+ioKCEnYCamZnti5OVkY6MioyG7+3u+YqMjIyJioHB6eHq4OTm2uPa3NXd3uHY19jV2djaz9javbjSgoaFg4GBgoCBgYH///yA+u/6//yA//7+9dPC3d3g2c3g4eDg1OHn5Obl5O308+zs5LvC4/+GjIKEgPr794CAgP789/3//u7NmJWQloCYl5mXlpmYl5aVkZCRl5TPgIKFhoCChf+ChYmNiImOjY2OgfyZmpyenpyWk5SYlpOcmZqanY/ikZCTlJSTlZeWlJWYlpWVmJaAhZePjZCUkIiHkZWUk5qTkpOT8oubl5WVmZmZnJuZm5uanJuamYvgkZCB/fXyztjS19Dc5Nzb0oDL08bC5ePd29LRyc/S1tbb29DN1NTf1MOHivHu8fL5iPOUz/Lo0Nbg2dXR0tbWuK3BusK8sLO+3IWLguyChsSQ/s6E2ZHKtZLsv4Ssl/z79eDJuKWnmZWbpZaSmpuUg4iNiYeIjIuOjpCPiJWcu5qYgYCLmNX+7uvY2srUzfvZ3YDY2uaOuLrIztHY2d7d4t7a3NbS1vTY8/TVosn7gKLG1s7DrZLfrIzq2s3P34vytuyGk6Gpqp6dnaWyt2B3hkhcqIXspYqE7YPBvZfAjYqIh/friZDf1b6Tg9bIwLuyraextbK5vsfKzd/8+IGPkpCno7TC1NTQ3fTayqm+9daRwEK1o6+alJG/irCYvND07piF2nsmIBkmIBotLV1VlpSL/YWk1JG73vTw4s+3n5P1sYG6usOzjIGDj5amvObcr62ms72AfYSPT4yMkIuSSk9dV1OeWlZWV19cbGFZXlhihGJib4RKUVNTV1NTWFliVElSXVdXVFFUWF1UivZzqZVqcofWY2hsa2t6gX52dY+Shnd4d4hKS0pLSop4fYJ+eXh9iY+Mgn2Iiol/fYSBiZKQjpCQjImOjI6PfouFgZGQjYiIg4aAiIqMiYaAfnx4foCAgYKAfXt/hISEgoODgYCDgXtzfoeDhYmAf4iDhIKIg4SDiIF9goSDeH10dXt6d3l2bF2SfISPhYaJj5SGjYaChpOLcIeOjIaJjo2Nj5GSkpKZnpqRnZqajmFob2pra2hnaF9wlqGlp6mlpaafnJiajIydo6KApqGlqZ+hoaClqKifnKOloqCfoJ+YmqKMiZatrLCysK2qqqyfd6KipqSkpKKgo6enpaepo5ygnKGFi6aYl5mZra6ysrSwtrSwrrCxsbGdnqaqpaerqKWmoZ+goaKlnp6goZ6VmpqXkZOZlJOWmpubnJyal5x3a25wcnJya2tub2uAbGhqZmhlX19jaWZmYmNhYGJgW1hXWVlaXGCghn59en+AgX98enp6fH2DgX6FgH95ent6dXRxa21ubmpsbW9sbGyoZG5wa2djYWFdqKaosWNjZWRiZF2AlpCblJeWi5OLjIKJi5KOjJCNjYqEfIiFb3WPXGBdW1pbXFlaWlmsrKmAVqekrrGvWra2s6ySe4eGiIJ7iIiIioGLkY6QjY2TlJSSk414foyZUFVNT06ZmplPT0+ZmJWYmp2ViGlnY2hoaGloaGppZmZmYmFhZmSETFFVUk1QUZNKS1FVUVFWVFNUSqppampsbGtmZGRmZGJqZWZoamGaYmNlZWVkZmhmZWWAamdnZmhkVVlmYF5gY19ZVV9mZWJoYmFhYqFbZmNiY2ZlZWdlY2RkZGVjY2FZk19fVKejoI2TkZSLl6CZmJOOlIiIoZ+YmI+QiY+SlpWXmI+Nk5adlYhZWpuYmJqgV6Bji6ShkJWfmpORkJWTgHB+e4B7c3Z9kFpfWZ1XXIVlsJaAaKZqnZB3zp5rg2ippqKPgHhra2JgYGZcWVpaU0pOUVBPTU5OUFBRUE5TVmtiYFBQWGGfyL++tLSmrKjQsbGsr6JefH6HioqOj5OUmpeTk4+NkaOLm5qJZoelUmR3g35zZVKDYk2Kfnd8g1KVb5pcZnSCh356fIOSn1pwhEpboXWAunptY7BgoJB0k29qa2y0tGZsnJaDcmGfj4R/gH54foCBhIeKk5WgubFcaWhpe3yEiZSTlaLBoZB8j7aidZONhYFscW+bcZ6NobG1u2lkr3YvLy5YUURzU3VUhndqu2FsiFp0iZiXkIR3amGhelN4e39yXVdXX2VveY+KbGpncXWAS01QLVVWWlVXKy40MzNmODQ1ODs5PTY1NzQ7TDk6PD0iJiUkKCkqLS81Kx8kKScoKiwvLzEqR3ZFhIxobnzMXmVTRENCRktMS09PSz9CQUUjJCMkI0Q8REpGQ0NCP0RDPz1DQ0U8Nz05QklISEtKR0ZNTVBQQkhCQE1LSUVDP0KAQkNFQkA7ODY2ODY1Nzk5ODQ3Ozo6OTo6OTc6NzUyOT04Oj45OEA7Ojk/Ojs5PDk3Ojw8NDkwLjIzMDMzMTNmZ3J/dnd5foJ2fHdzeIZ7XG98e3h7fH19f4SEg4KLkY2HlY+Pg1VcYVxdXltaWlJnkJqhpKKcnqScmJOUgHyLkY6Ako2Ql4yPl5igoKCYl56hnZqam5qQlKCJhZWvrLGzsq6oqaybZIuLkI2LiYqHjJGRjI6QioOHgohxfZaJh4uMoKCkpKekqqijoqWoqamVk5memJqfmZeblZKTlpebkpOUl5SHi4uIgYOLiIaJkZKTlZSRjpRuV1hZW1xeVlZZWlWAV1NVUVZSS0tPVlRTT1FOT1NPSUVERkZITFCLg3h2c3h6fHt4d3h3eXmDgn6GgH94en55c3FvZmpqamZpa25qa2uqZ3J1cGpjYGBdpqaqtmZnaWdlZ197hYGOiIqKe4V9fnJ6e4OAf4SAgHhyaXRyXGaEWF1ZVlRVVlNUVFKdnZodTpaTn6SiVKqrqp+FaGtqbGNdaWpoaWNvdHFybW2FcYBsXWRocDk8Njo5cHFuOTo7cXBtbW1wam9aVlNYV1ZXVlVYVlNTUk9PT1ZTaTI2OTc0NjZdLi0xNzQyNjY1NS+GU1RVWFhWUU9OUU9MU05OUFNKdkxMTUxLSkxOTUxQVVJQTk9OQkZRSEZIS0ZAPEZMS0hPSUdHSHhASUdHSEtISYBKR0ZISEZHRUVEQGpDRDt1dHxxeHV0bXuDfXx3c3hwc4uFfH12dHBydHZ1e312dXp8gXhtQ0FxcHJ0dkB2THCBfXR6gXtybG1xcWBQW1ldWVNXWWU5OjVfNDhZSouEgcBvjoR0wodhbz9iYF9XUExBQT06Nzo2MzQzLicqLCwrKoArKiwrLC0qKys0NDMqKiwye8a8urK5sbOoxLa6uLiGPU1MUlRUV1daWl1cW1tZWVxkTldYTTtOaTZGVVxWUEk+Yko7bmlmaXhMeVqEVW2GpLW0r6WvyPqmzO6JlbFNYDw1NF0wVUc5VkpFQkJ0azIxUktGRDlhW1dSU1FTVFVUWl5aXFxeZGxoNTs8PUJAR01TVVFValRPSE1eRjA7NjU3LjY1W4H77+rm0MJkdfTvg4uD6sul/azehrmafLlea3tMWml0c29mXVVPgFw7T0tLQDg6PkRGSkhPT0dGREZGBHx8fH2FfIV9AXyNfYR+ln8Hfnt/gIB+fZOAhYHpgOZ/9oABf6aAAX+JgIR/h4Ccf4uABH9/f4CFfwGAoH+FgAZ/f3+AgICIf5OAAX+HgAF/i4ABf5KAAX+kgAF/k4AGf4CAgH9/pX6FfQJ+fZd+CH9/f35/f35+hH0Jfn+AfXt9gH9+oX2GfgF8jXsBfJJ+hX8Cfn2IfoN9hXwEfX1+fot/EICAgIGBgYODhISEg4SDg4OFggSBgYKChYGSgJKBAYKIgwKCgYaAFYGBgICBgYGAgIB/f35+fX19fH19fYp+g32SfAICBACA1O7Mt+Tr++qKmpOHhYXxiZSiqcSwpKWel5+qpbXG9pTOjI+MjYuLjJmsspyhn5WOio2Um5mLxnDYy47Ysb2oj5mst73d4NHGy/D55dzS1e+Ag4GCgvrk3t3d2NHX7v/57PXr8/Xv8PPz7/f19vL5+O3u7fDw6eXo6+328vD08vCA8eXo7fTv7vDx7+7x9PLl6+7o8/f68/P09/Px8vLr4ezv8O7w7/Pp7vTy7ujt4evj4+nZ4eLk493exZntusDLxbq7y8rEy9HMyb7AvsHHt6LMzM7Pz8nKycLKx83Vyb+/0NjT1NGak6SloZGbsNHL19nY19jW2dbV1dvU2NfB0PJ78u/v8t7e5+rv7Onm7OPh3uDV2dXU4d3Z3dXNzN7s7+nu6+SV5evx7fL19uzr7fDx8vLw8fDy88TH+fn4+fn12tHPzeL4+fj29vf28/DD8vHu8PDy8fDr6OTk6ejp5OXg3tzh1+Df3d3Y2dva3N3a2t3b2dacsbKurqelhamApKmko6irpKCgoqKgoaKcmZeWk5aYmJuTmJiKoLm1ubK1uLaysLK1tLGvr7O0sbGvrKqrpaSioqGdn5+cnp6emp6Z2pOTkYP3+fuCj46Oio6Ojo2MjouGvuXm5eTl4d7XztrW3d3a2szOz9DTy66l84aEg4SEgYCCgYCCgP38+v8O//78gf/8goOC38Td29uE3oDg5ODg2tzc4Onj5u319O7lvYGF/evo+YL2g4KFgf7/goH+gP/+7c2XmJaQkpaXmJmVk5KQkZCQlJeW8+yEg4OB+/z3/YSIioqGhomGiYrXnJyVmZqYnJSNkpqWlpqcl5mag/iPkpOQj5GSk5iVk5ebmpeSlIP8lJCPj5WUjoiMloCRkZSXkZKSkOGenZyampqcmpiUlZaYmpeVlZTKg42bm7zI0NLY4dLT39jN28zQ1MDJ5OXj2tjX09DP0tXU0tPS3tnc4M2Hg//s4I+43+n2/Prh0Nze4uzw3+bU4aivysCvrbLF6oL55ID73q2E/oXCiMK4p/Xcm4LQgfiB9M2gmoCelpSYnYqJjIuLgYeTnKGanJSSkpKMhaOZtpKR++uJltaChoP9/ffp8IiDitqAtsXBwNXY2drb2Nnf2+Lj5uOG4fX39vb49++5ja/8gJiwxMnDtpz9vpWE8+zk6YWvzPuRrsnngo6Rk5GRnqmnWV9nbDpETluWsJeerrmroKe2qW6mm4v10K2ZgdXEuaqxqJyco6aot8ju+4+y5p7Zhp6kkJK6rpOJg4DyypHccrCYo7K1tqeorKB5Sx8cGRIkIRsrJ1upmqGT/4WYt+qUttz5gu/QtJ2ShNKYx7K4sb3N3JWAk5OatMPev6ioqqaptoCFkn9zkJKYi09VVE9RUptUWl5kd2tjZWBaXmNjbn6jXX1RVFJTUlNUWmZrWF5dWFJQUldaV1CD5ZynfLeYpIFibWlrboGCenR1jJGGgXh6iktNS0lJi3t/f4F/eHyKkYt+iYGJjYmIiYWGkI+RjJGQiYuPlpWLg4WHiZKNiI6LiYCIenqAhoKBhIeDgIKDgHd9fnqEh4mEgoOEgoOFgntzf4GAfoB/g36BhoR/fYN1gnp6f3N2dnd0bHFoVJeEipGPhIaTlJCWnJWSiouIj5WFco+RlJSTj5KRiZOSmJ6OiIubo56emmpibm9qYGyBm5Wfo6KgoqCjoJ6dop6jo42Op4CppquuoKCmpamqqKeup6elp5ycmJehn52mnpWVpLCzq7Kupmaaoaalqayqnp+gpqWoqaenpqemhYepqaqqrKmVj42Nnq+xsa+ysLCurYmqqqepqqyrqqWinZ6joqSfnpqZlJiTm5iXlpCSk5Sam5iWmZmUkmlubm1uZ2dqamlnZ4BlaGJjamxiX19iZWRmZmFeXVxYXWBhZV5gX1hyf3uEfICDgn99fn97eXh1f4J+f316d3ZvbWxtbWlqa2tubm9scmycbm1qXKyus11nZ2ZiZ2dmZWRlY16AlZSVk5SMiYJ7iYaMjYiKfoF/fn9+cG6sYF9dXVxaWVxbWlxaqqajq4CrrK1asbBbXl6efoaFhoaEhIaJjomJhoiJi4+Lj5SYmZKMd1hbqp6Pl02PUFBTUaCgUU6bTpqdmYtqamhjZWhoaGpmZWNgYWJjZ2lmpY1OUE9Mmp6Wm01PUlBMUVFOUVKGbGxlaWtobGReY2hlZWhoZmhpWaxiY2RhYWNkZGdmZYBnampnY2VYrmVhX2BkYl1XWmNgYWNkXV1fXZRpaGZmZ2dpaGdjY2BiZWJeXl6CWV9qaYCMkI+Tn5KSm5eQmY+RlISMo6OimZiXkZCPkpSTkJCQmpeanI9aVKaaklx6maCorq6bj5udoKWrnqWVn3Fvhn5zcXN+m1akk1aplHNYrYBlmGaOkIbXw31nllaiU5uBZmNmYV9gZFdUVFJRS09VWFtXV1NTVFNPTlpUaVxbn5dXYZpmamjGwcC7xG1oaJpUd4SCgI2PkJCPjY+TlZmbnJpYkJucnJ+hoJ53W22eUV9qc3h0aliPbldHhoR8g1ByiapheZGrZG92enh5goqTUIBYZm49RlBfiZh/gYuNe3Z6gYJ3bmm6m4p0WpmYjoKEgHt5fH5+jpWqw2uLuYC4dX+Eb3GJg3BeX2bMoG2wZKKQk5uUjoSIkIZvViwvNzhmWU50UWySf3hqvVxnd5ZecIeXTpKDdGhkWY9mgHJ0c3uHjmJYYmFldX+GdGZnaWlsdIBNVkxDVFdeVy4wMDE0N246NzU7QUA8PDo0Njs9RVZsOzwlJycpKioqLzY6JSgqKiwsLS8tKSdHenGkkbOvw4lebU5CQkZDSEdGR01JR0JDSiYnJCIiQjtFR0hHQkNFQz82QD5DR0NDQTxASUhMSEtKRktSXVtORENFRk9KRUlFQoBCNjU7Pzs7PD47Nzg4NTE2NjM6PT86OTk6OTs+OzYyOjg3NjU0OTc6PTs3ODwxOzU1NzEzMTEuKC0uLGtyfYOAdniHiIKGioWDfH59gIZ3YXuAhYWEfn+CfoaFipCAen6RmpSTjl9VYWFdVGN5k5CZnJuanJ2fm5qan5udnoZ/l4CWlZuglZWZmqKooqKrpKWip5iYk5OdmZilnZOUp7S2rbKtpleDio+Ok5WRhoeJj4+RkJCQj5CRcHWYmJmbnpqGfn2AkKKjpaOmpaamqH+enp2dnqKhn5qZkpKWl5uWk46LhouFj42KiIGChYiPko+QkpGNi2BZWVhZVFVYV1ZTUoBQU0tOV1pRSkpNUlFTUk1KSklGTE9PVExPT01vfHaCeH2AgH17fHt1cnFufoSBgn14d3NpZ2hqaGJjZmZrbW1qc2uec3FtXKqrsl1paWhkamppZmVnZmB8iIWGhIV7d3BpfHh+fnp7bnFraGltYmimXFtYV1dUVFZUVFZUnJmVnICcnaBUpaJVWFmUbW5qa2pmZWdrcGtqaGpqbG9oa3F0dW9tX0hMjX9scDhjOzo+O3V3PjpzOXF0c29ZWlhTVFdWWFlUUlBNUE9QVVdVhF80MzQza21laDExNDIwNDQxNDNdWlhQVVdTWE5HTVJOTlFQTFBQRIFKS0tJSElLS05NToBQVFNPSkxDjVBLSEhMSUQ9P0pHR0lKQkFERGlMTEpLS0pOTUtHSERFSEZBQUFeREhRT2Ftb3B4fnJzfHZveXJ0dGt1iIiGfXp4dHNydHV1dXZ1fXt9gHRFPnhvakVdeHyBhYV2cn1+foGFen5xe1VOYFhOTk5TZDRhVjRnXFBGjCN2u2qOgX3Nt3JfbTNeMFtNPDo7ODg2OTAuLSoqKSouMTIwMIQtgCwqLykzMDBVUCszfWZpZ8vKxLe7ZGVlgDdHUVBNVFdYWVhYW1tZXmBhYDVQVldYWltZWUMzRHI5QkxSVVJNQmxUPzdnanSATGyEpWSBqNeIprvExMPCzN+IncbngJKXnq53Tk1JSkNBQkJANzY1Y1ROQjhkX15ZVlRVUlVUUldaWWZwPUNYO0suMSowLT01MCsnLFtOOpeD+e7r3cq3pqS5zdnpgoaQivPFp+2YvuiwmX/DWGJsgExWYnA7bmFXT01Da01hUlROUVFSQzlCQ0hQUFRLREVHQkJEiHyGfQF8kH2CfpV/B357f4B/fH2TgIWB5oDof/+AoIABf4SAg3+NgJl/jICHfwaAf3+AgICaf4KAhH8CgH+EgAZ/f4CAf4CEf5OAgn+EgIR/ioABf5OAAX+SgAF/koABf5KAAX+EgAF/pn6DfZt+BH9+fn+Efg99fX1+foB/e3t/gH5+fX6efQZ+fn19fn6EfIV7hHySfot/An59iH6EfYR8hH2Efol/hICFgQGCjIOFgo+BBYKCgoODi4QFg4ODgYGMgISBBoCAgH9/foR9AXyEfYR+AX+GfoJ9lnwCAgQAgOzGzNLI6/qHkJCGiI6Ug6OgqbKYlpSVnK2ZmrLU0NOHj7CAiIeKjpKcs8Gnn52Nh4qNoKmjhrluj7qI3uvlkIyWqrnJ69/Qx9Xy9OLZ0tntgIGCgoWB5tvd29bR0ub5/P3x7Pf17ubv8Pb2+vn0+PTt7+/t7O3i1ejn8PT18+7vgPXr7e7n8+Lt6vPw9fTx8O7x8vj4+fTu9fLs7+nq7O/m7PHy8u/v7evu6+ri7ero5OPi2NLb1bGJ1MOvsMC8wsLEw8zDyNXZ1tHL1cjDvsO8nM3R0szPwcDBwMLDx83Uz87JyszQ0rKIkKK9x9LL1tbU1djR19jT1uPf2Nja1dvGgMDr6eDi4Nnv8ejn6fXx3Nzc1djU1tvX1drR097Kw8fg6uD55+7x8vX47/D08u7v7O7u7fLx8MnQ/vz6+fb09PPx8fTj0c7Py93y8vjF6eXs6ers7OXl5uLj3t7l3+fm5OTf49/h4tra3NvY3dva3Njc2tfYjqWlqKarpaSfoKCfgKahoZ+coJuZm5yioKCdm5WZlpaVmZiTk5eWnICztbWttbGysbCzsK2wrbCvsKyurquppaSjoaOioJ2cn6CdnJmZifzd/YCKkJCRjZCMjpKQj42Pj42MjIrB4eTc3tzW3tfR2NTSzNDL1825tr/YzcPyhYaFhoOC/f79gvz1/PyAgPeAgoL//YHkzdPN09zZ2uPj4uDj3N/c4uvr6+Xo+PX4ztOEiI6AiIOH6+Xo/oaC+vmCgP79+vnIkZaUlZWPlpSRlZKUmJaTkpGYmI3OhICCgf/6/vaCh46HhoaDh4iLh8qHhoiNk5mWmpaRj5SbnJycmZrmjo2OkpGRkZOQlZaVgJKZm5eVkon3l5STkZGTjJCPkY2Ym5WVl5KU0pqenpuVmpmYmZiXlI6Xl5iXlYvXnp2GvtjR2NbQ1drh1d7Y2dPe2LXT19bX19zWytLO0NDKz9HR1NXj2syFk63e3+jn7eXu8Ofm3d/m6Pj1+vfv78+6x8Gzv83mgYL2/oLit5GEgKSs8pvqqufTxYS+lviE97CjoKikn6Goj42Wm5mEhI2WnZ2cl5eUjomEo5y0jo/134WQ0fr77u3l49aQgu74s7u5t7XM1ODh4uLb2eTh3MXPjOXv8PX8+PX27vf+ge28g6Ln+5S0yszCrZuC2LSWjJGNh+7t6vSTseCLpLVoc3yBgIF/f4SZpbO3YGVka3d9P0FETVRifbL6v4CNjZmdmZ6lp6qztsa5wunt8pPTqoGi3p93aFpWr62so52SlJuZlH5eST44MhUjHRYUDw4kLluqq6STh5ajuuOWv9fy+PLs2LmfifnauZu2oKWnr73B4tCnopKNrrTO3r+oq7OttsfhUJR+gIWAkptRVFNQU1daU2FfZWpdXVtbX2hdW2iDgYNZXm5NUVBSVlhfanNeWlpQTU9RXmJcTHvmdp11wcLAZF9oY2x4jIN7dXyNkIZ+e3+JhEqAS0mAfoGAfHd3gYuPjYF7i42HfoaGjY2Sk4+PjIiPlI+KiX9xhIKHiYyMhYSKfn1/e4R3hH6EgYWEgYF/gYKKiYmFgIWCfX97d3qBdX2Cg4SChIJ/gn19eYSBfnl5enJucnBgToqHeYGPiY6MkY+YkJSeoJ6alpuVkIiNhm2SmJmAkpmLhoaIjZCTk5aSlZWWmZ2bf1tldouRm5mgoZ+fopugop6fqaajo6egpZGIpqSfpKKaqaqmqKmxsKOhop2cmJufm5uemZmhkpCUqa+hq56ipqeqraWmqqqkpZ6hoKKrqaiKjrGurq6sqainpqSon5ORko+cq6yzjKWeo6ChpKSAnaChnZyXmKCZo6Gho56hnp6el5eWl5SamJeYlJiXlJZdZWVoZmplY2JiYmBoZWJfXF5aWFpbZWVlY19aXlxdXWJgXl1dXWVYfH5+d4B9fn57e3h0dXV7eXx3enp2c25tbGxvbm1paW5wbGxpbWG1nrNbYWZnZ2NnZGZraGhlZ2aAZWNjYoOSj4aIhICGgn+Hg395fn2Fe3B1hJWLhLFiYF5fXVy0srFbqqOpq1ipWVpZr7FcooyLg4WGg4KIiouKi4mKiouPjYuJj56ZnoGLWFthU11aXJ2Skp5UUZ2bUU6bmpiehWNpZ2hmYGZmZGdlZWhlZGVlbGtigE1JTk+knqRvmVJPVk5OUE5PUVJPgV1cX2FlaWZoZmJgZGppaWloaZpgYF9jYmFiZGJlZmdlamxqZWJcq2hmZWFfX1lcXGFeaWpiXWJgYo1ma2poY2dmZmhmZF9ZZGRjYmFak21tXYGWkJWVk5eZm5OZkpaOmJF9hZaAmpOKkI2OjoiNkI6SlJ6bkVlicY+Om5ebk5qalp+anKOjsq6vsaqrkICEgXZ9hJdVVp2oWJh9YVh2iLdusYW+vZ9ok2akVqJyaGdraGVmaFpWW11aT09SV1taWVVVVFBOTVtVZltbmoxUXJrJybu4tbezdGiopHR6eXh4hIyUlpaAlpKTmpiSg4hakJianqGgn6Gao6hVnHhTZJWfXG14fnNmW0x5Z1ZRV1hVl5aYoV1zlGB1jVRfanFycHJ4gZCbnVVbYWlye0BDR01QWGuNt4tgYmdwdnB6foCBhpOQkZSqsrVqtIpneah+Z2BSUKCdl5aGe31+fnxtXlRRUlcxZ2U4aWNhTnFMWop8c2ZeYGh7kl1wgZGUlZSId2leqZR8X3VpbW51enyPg25rZGFzdIGGc2hrb250gI6AVVBTU0xUXjAxMTE0NTY2PTo7PTg7PDo5PTg6QE9OUD0+PSUmJikrLC40PSsoKSUmKSkvMS0pSHpel3nEwtNlYGg/QElKSklHRUZJSkVER0omJCMiIyNBREpJRD8+P0FEQjYxQUdCOkJCRkZNTklKSEdTW1JJSEA2QkBDRUdGPz2AQTg1NjY9NT44Ojc6ODc4Nzk6QUBAPDk8OTY4NjEzOTA0ODo7OT08Ojw5ODhAPDgzMzUxLy0vKy1kcGZ0gHl+fIOCioGFjY+NioePiIV7fnlefoqLg4h6dHV3e4CGhoWEiYuMj5KSdU1ZaoSOmJOcnJqYm5SbnpmbpqKfoKSfo4mAeJeYk5uZjZ6hoKOjr66gnZ2Xl5KWmJSXnZiZoZGQla+zn5iIjZGSlZiOjpOTjYyHjIqNl5SVeYChnp+gnJmYl5WUmY+GhIWDkaKjrYSXjZSQkZaXjpWXk5GJiZWNmZiYm5ibl5eWjo6LjYmSjo2PjJKPjZFUUlJWVFlTUFBPUE6AVlNPTEpLRkVHR1JTVFFMSExLTU5UUU1LTU1ZU3l7eG57eHp7dnZxbG9vdnZ5c3l5c21nZ2Zma2poY2RrbGhpZmxhs5+zW2FmZmhkamdqcGtqZmhoZWRkY32DfHN2cm11cnF2cm9rcW92aWBpfo+DgK1gXVpaV1aopqZVnZOZm1CAl1FUU6GkVpZ9fHFua2hmaWlsbHBsbWxsbmtnZm19eH1mcUpNVERNSUt+cG5yPztycjw6cG9tdm1TWVdYVlBWU1JWU1RXVFJSVFtbUlszLzQ0dnB0ZjY0OTIxMzIyNDQyZExLTk5QVFJTUU1LT1NTUlFPUXdKSEZLSkhJS0lMT1GATlNVUU5KR4pTUE1IR0U/QkJIRlBQRkFHRUdlSU5NTEdKSktNS0pEPkdJR0VFQHFTU0hleHN4eHN2en10enh5cXdxZ359e3t6fXdvc29xcmtwc3N2eoF6c0ZLVGxsdnFxa3F0cnx6foaAio2RjYSFbV1aWFJZWWA1NWBmN15SSkaAd7HOc6N6uLOXZYNCXzFcQj49QD89PD01MjM0MiopKy8yMjEuLSwsLCowKzEtL1NMKzF5yMq9v767sGtheWRDRkdISFBUWltcW1paXlxaUlM1T1RVV1tbWltXWlwvVkMxQmJtQk1XV1FIQDhfUkNFTlJOjo+RmVlxpXSf0ISlvNKA2tfY2NHX6fuJm7TI2/GEjZSYmJ2kq7ZtOz4/PT8/Qjs+QkRHREpKUl1eMlRMN1udkpSNhYL97+PWvLOrrrC3xc7i8fb6gPno3MK0jMuGh7mbim9VVGBvgU9dZG5wbmpmW1BFfWxUPEtGS0tPU1FaWFNSSEBMT1VWSEJDRkZGS06HfJh9g36Ufwd+e4CAfnx/k4CGgeKA63//gKCAg3+TgJl/hoAEf39/gIR/CIB/gICAf3+Am3+HgIR/BoCAf3+AgIV/lIABf4SAhH+LgAF/koABf5OAAX+SgAF/k4AEf4CAgMV+BX9/fn5/hH4OfX19fn+AfHt9gH9+fX6efQd+fn19fn58h3sEfHx8fZJ+jH8HgH9/f359fYh+h32EfAZ9fX1+fn6Mf4aAiYEBgpKDBYSDg4OChoGQgAGBhoADf39+iX2LfoR9mXwCAgQAgL29zdPR8ICUnf+BhYSU+KSlqJ+enZOVkpqwq7q9uL/vjZSe1IiMkJqqub6zlpCIjJ2jqKaX+6BmnteB0fzcjYqYtbzL487JyOT0+u3d1NbwgYGFhYn/5N3e2dXU1ur3+vvy7/r49PDr7fP26Ofi5ubu7uzn7u7w7OTk9fLx8/P0gPT18vPt7+nu8fX1+fnz6ev47vT39e7n5drr7+7x9PTy7evw7Ovk7O3s4+Ti2t7l5ODbvZ/5wMi+ycnMo8G/y8jNwcHLyczZ1t3Nx8bFxMvLuZXExcjIyM3FwcvKzMXRzcrCyMPHyMzDrcjWyMbJwMnM2tbS1dPP2tXR1dbV1NfagNTDyubi49vj5urj2ODl5uLk5t/p5NLNzd7W3ebd16iToLLZ8vP19/Dq7e/s7vLx8+zt8e/yxM78+4CAgPv8+Pf09/b18/D09eHMy8O3zOLp6uzr6N/g3d3c2drh3+De3+Hd4N/e4OLc3dzb2trY29nY1dXThKKmqKelpqKioZ+cSaCem5uYl5SYmZqYl5iUlJOUlpeWmJWUlpmXmu64trW1t7a3tLKzsK+vra6sq6qpqKamo6Gjo6GinZ2gmIiFhISAio6NjZKOkI+EkICSk4+QkY+Njo6NjY7Iz97j4tzW1NDR1M/Kyb64u8zX19PQzsPQ/4aEhYKA/IH89fyC/fmA/v36/+/O6f6AgefK0ePk4+Xn4d7i4+ft8O7g5vTowvCDiIiJiIqGhYOFjfnl5uv5hIKDgPbRkZGTkZWVk5aOkpiYmZmXlZGVlZXH/4D4/4b89/P7goSGg4GCgoWCgIPXiZGTkof/9fb//vn29P37gIOFi9qSko+Rk5KTkJCWk5KTlpaUmI6O75mZmJaTkY6Oh4T19oWBgICAgseUnp6em5iUnJ2Uk5SPlJWXlZOa3JWXjd3a3+Dd3Nzi4eLV29bY1NXdqNvW2dzS0dLT0YDJysvM1Nrf4N/czbuXxODm9/H38OHd6ujq4dHe2+r+/YH23unEucS/z9mGhfTg8PPJoPDNmuON0rer4tGXhueH9ta5q6espJmgpJKSmZqai4mJk5iVlZiYloyCgJeiqI2G6870g9n29vHq4PKlt5y+wsHAu7K/wsLG1dbh5Onn5IDL0rX2+vrn8Pf08vTm9v6Cg4KCgum8hqz274ujt8DAt6iYgtrCqpaKhID47ejj5fCHkqzK2oOPUFtkanQ9PT0+QUhRV1uoYWNjZWdra2xwdnh4e36EioqHkZ+boLOmnJ2Uko+HfoB+dmZOREAeHBkXEx8cFxIOGh4kUaafpailnjOZnqqwye2QsdX3hIP/7dG3oY710KjzvOb7xKKvtK610u7gpKSuuLGrseCznKuxtMDX7viAenmDhYSXUFtgnU9UVV+jZmdnYmRjW1tZX21qdXZzeJ1eYWWEU1VXW2VtcGlUUExPWV1iYFWQaNGGt2+20qtgYWNnbXeHeHRzhY6SjYN8fYpJSEpMT5CBfoB9eXl6hI2TkIeCj46Mh4F+jIx9eXV2fI2Ri4SKiYqGgIGNhYmLiIeAiImHhoCBe4GDhYSGhoB7fYeBh4mFfXl5c4CCgIKGh4WCf4N+fnqAgYB4eHl2eX9+enhpXZmAiIGKi45zkYyXk5aMjZaVmKKfpZeRkJKPlZSEaY2NjpCQk4qFjo6SjpSSkY2UkpWVlo18k5+Uk5ePlZejnpmdm5ekoJ2goaCgpaeAoo+NpaKknqOjqaCcoqWnpKKooauomZWVop+hrKSfdWJtfJmop6eooZ2lpqOlp6WkpKOopquGkK+vWVpasrOvraqur62sqaurnI6OiH+PnaGgoqGdlpiYlZKQkZqZmpaZm5qcm52foZqampiYl5SWlZWSlZNVYmNmZmNkYmRjXlqAXVtZV1dVU1dYWVhbW1hYWFpdX19hX19fYWBkn359foCCgoOAenh2dXd3eHNycXBwbm5sam1ubG5ra3BrX11dXltiZWNjaGRmZmdmZ2dpamdoaGVkZmVkZGWJgIOJiYSCfn9/gnx4eXl4e4mVl5KNiIWTt19dXltZsVqto61ar6qAWLCtqrCnj5qnVFaah4KLi4qNkYyJi42MkJGOhY2akH2gWFtdXl5fXFtaW2KqnJWUmVJOUU+YimNjZmNmZmVnYWZramxraGhlaGdmfpWVm1OXl5ifU1NRTEdJSkxKSEuDWV9hYViloKOqqqenpK2qVVhZXpNjY2BiYmFiYGFmY2OAZGdmZGhgYKVpaGlmY2FbW1hYpKhcVFRWV1uKYWhoaWhmZGtsZGNiXGFgZGNhZpJnaGCal5ubmZaYm52ckJKRk5SVm3Wal5iak5KRkY+LioqLkZWdnZqYj4VmfpGWo6Cnm42OmJufm46YlqK0tFywnqSHeYJ/iY1ZWqWSo6eHa6EhkHevZ5+QitG9emupWaCIdWtqbmpiZWdbWl5bW1NRUFZYhFaAVVBMS1VZYFpUloWbVJrKzcm5tMZ+fWZ7fX19enV9gIGCio2Tl5ubmYiNc5uenpOZn56dn5WgpVVVVFVVmHhXbJ2aV2Rvc3JrYFVJgnZlWlRUUqGen5mcollldY2oYXZGVFxncTo8PD1ARUxMTpVSVlZZXGBhY2tubHBydHV6eXZbfomHi5SLhIJ7d3ZvcGxsaF5STlIqLC4wMWhkXFdRi29WcqmOhH91bGZmbm+AmFxugpVPTpmSgnRqYa2PcZl4jJl2Z21xb3mJl5NxcXR/e3VziXNja21ueIeXnoBRUFVVU1kvMjVnNTY2Om0+PD07Ojw7PDk6QkBKSkhJYj0/QkkqKywuMjQ2NSgoJictLjAvKlBBcXKpa6nFpF1mXD9CRUtFQ0VHSElPTEdFSSQiIiQmRERFSUM+QEFERk1HPjpISUdEPjpHRTo1MDI5T1dNRUZFRkRBQEc+Q0RAP4A/Pz49Nzg0OTs8Ojs6NjY5Pjk9Pzs1NDQuODc4OTw/PDo4OTU3Njo7OjU2NjY4PDw5NjM0ZmRxbnRzeGGBfoaEiX5+h4SJkY+WiIODhIKFhnVYe35/gH5/eHJ7eoKBgn+CgIuIi4yNgW+HkZCRkouTlp2alZmYkqKemp6goJ6kp4Chi4GUlZuWnZqhmJOaoKGenqGao6GVjpCenKCqpKN0Xm13iJKQj4+Igo6PjI+SjIuOjZSSmHeCoJ9SU1OioqGfnKCgnpyanp6Sg4R/dIKOkZGTk46Gi4yGgoGEjo2MiIyQkJSSlJibkpGSkJCPi46NjYuPjkpOUVNST1BOUlFMSIBLSEVDRkRAREZHRklLSEhISk1QT1JQT1BSUlaPe3p6fYCAgX92cm1tcXJya2lqaGlnZ2RhZ2lnaGRmbmlcWlxfW2JkYmJpZWdmaGdpaG1uaWlpZWRnZmVlZYNycXd3dW5ta21uaWNpbm1zfouPiIF7fJKvW1hZVVOlVZ6UnFGem4BRo5+co52EiJBKTYpzaG1tbHB0cG9ubmlucGtlbHZvZ4hLTk9PTlBNTEpKUo18cGxxPDk7OXBvVVVYVVZWU1dRVltbW1pXVlNWVVZhY2FoOWZmaW45NzQwLS4vMC4rLVZER0pLQ3p5fYaEgoCBiYVFRkRJc0tLSEpLSUlISU5LTIBOUE9NUEhJgVNSUk9MR0NCPkB4fUc+PT9AQ2dFSktMS0pHT1BJSEhBRUVJR0VJbU9PTXt4fX17enp/e3lwc3F0d3uDYoJ8fX51dXZ1dG5ubnB0d3+CgH11alJdbnB9eX5zZmlzeH56bnZvfI6PSYl5fmZZXFteWjc3ZFRlalhNgIB9ltZxnoSEyrZ3aIY1XU9DPz9DQTw8OzY2NjIyLCsrLi8tLS0sLCsqKiwtLy4sUEpUL3rAxcW9ucNvUDtHSUlJR0RMTU1OUlJXXWFfXVJUP1NVVlBUV1lZWlRYWy4vLi8uVEUyP2FpO0ZPUVJPSD85a11UTEpKSZSgn56mtWRzkYC89Z3LhKHF4v6GjI2OjZOYhYj1hYiIj42UnaixtrO4vbu4tbSxsLm5tLStoZuTlZaTl6CptsLL3/aGiYeEhPzhxbGd+siau/rDpJKBbFxXX19wf0hXZnI9PXVwZFdNR31mT2VGUVVJREhOTE5ZYFtNUVZfVUpMU0tCQ0VHTVNWWIZ8BH19fXyEfQF8kX2EfpF/B35+e4B/fX2UgIWB34Duf5eAg4HfgAF/vYCaf4WACX+Af39/gH9/gIh/goCWf4uAhX+EgIJ/lICEfwGAhH+LgAF/hYCKf4SAAX+TgAF/ioCCf4aAAX+TgAR/gIB/u34Bf4l+gn+GfoR9Cn5+gH97e3+Afn6ffQd+fn19fX58hnsCfH2Ufo1/hYAGf39/fn19iX6HfYZ8hX2CfoV/iYABf6aAhYGFgAR/f39+jH2EfoJ/hn6DfZ58AgIEAIDl7fP19oaPlYiGjJWVmYWlqpmam5WYoJ6Xtr3WzLTA1YePn6Km5ZSltLy4t5WHtpqko6Slk++eVO+dhueVm4iGnre60tfKw9Lr8fHp3tnX74OFhIWH+ubg4d/d3Nnj9fn4+/j29PX19fDk2efq7uLP6evq4+zx7uvjyO70+vn48YDz8fT39PDz8/Ds8vT08e/29/Ly5+Tg6evn7+ns7u/y7ujl8Ozn4efl5dXb1dPPxKGHzsLI08rGyMXK0MufwcfTzMTI0cvOyNHV18jPyMXBy8WqmcXJy7y+wcDE0NTPx77Lx8jN0c3M1dKwysjT3dfN087T1tfZ1dTZ2tnUzMvMyYDR1dW8xOXt6u3k6ubq6+nk4+Pl2tnY1dHZ3OXpv5qgo6Kyt8DQ7fLz9PX47+ru8fDx7u/uvM77+/v59/n2+PPz8fDs8e7u7Ons7Onp6uXOx8jLx8zb4+Lh393d3tbZ2tna293Z19ra2dnY1tXX1dHU0NPTyISho6KgnJacnZ2fpESenZmWm5icmJqVm5mZmpeWk5GWmpiWlpSXnJj5qbq5vbizsbKxsq6sq6qrqKmpp6ajo6GioZqIiIiGhYqVlI+NjoqQkYSSgJOSk5KRkY+RkZGPjY6Mi4qGg8q92d3Y1dvb1djHwsHT3tXa09XX0dDVz7Pgg4ODgP3/gPT8gfn2+f6A49Lo/IH9hYf+gfjWxOLp4tzj6vT47unh4+3SxYSCiICEh4WOiYCCjIqHiomL7Onm8+/KlJOTlJKRlpeXlJeamJaXk5WUgJKP2Of6/4SCgPz3gISDhYOEgoOAg4XqzYySkpKRkZKTl5eamZWXlpaRkI3MlJOQkZOUkZCMlpaUl5eXlpSSkMWBgPf3/IL6g4iRlZWZmp2dmpyN9Jucm5qdnJiYlJWWlZKTkpSVl5HU7Mrl3tbc4eLn4O7f1dnb2NfQ082n29jYgNbR08zGxsPDyc/M0uLh3dDAw6S/2+Tl6/r5/OLh2uXr3t7j4erc6u/o8tGwvdnrgYf85ubz37H544HNgZzur4q+oe7XotzLw7arqp6coqiRjpacmZKSjZmZk5OUlpWMgP2UoaWJg96+4/vzgf77mqiOwKuMnKm5yMPAw8vHy9negOru4tfX9dT2+Pf12/j39vT09fn48/Hj9/j7/4Di1LCPzIvr8IaYrLSusKuWiffRvbOjmpySlI+Oi4eH8/78gI+TqsdvfopJUFVaXjEyNDY4Njc4ODc3NzU6Oz09Ozs7OTs8OTMwLiosKScjIR4bGBUpIiAhS1dXq6aflJmfm5uhOJijtcv4n7DK2+jy5+TRyrOcjYT03L7tvr29xtSB27m0wc/qgISM4O3e8emA+tTv1NHc5Ovrg47rgJednpqaVFlcVlRYXl5gVGRqXl9jXV9iYV1wd4iAc3uIWV5pa22NV2Bqb2ttVUttWV9bXl5UimSAyIR2w3hsXmFhZml8fnZve4qIiIyGgX6LS0xMTU6Lg4KDgoGAfIGLjo2QioyLjY+PiX1zfIKJdWCDiIeBiY2KhX5piYqPj46GgIeFhomGgYWEgnyBf4OBf4mJhIR+enR9f32DfHt9f4OBfHiDf3t1fHt8cHhuc3JvX1KIh4yTi4mMiY2UjnGOlJ6ZkZSclpmVnJ6gk5qVkY+WjnxsjY6Sh4qKiIySlZGLiZaSlZibmJicmX6WlZumoJebmJ2en6KfnqSkop6YmJiXgJ6hoIqLoqamqKCkoqWmpaCgo6WdnZycl6Cip6iJaWtsaHB1f46fn6WpqqyjnaOnpqmlp6qFkLKysa+trq2vqqmnqKiqqKinpKenpaWloZCLi46KjZWamZiWlJWYjpCQkJKUl5WUl5eWl5WRkpWTjpCOkpKMUF9fXl1aVVtcWltdSVlZV1RaV1taXVtgXV5dXVpZWV5jYF9eXmFmY6B2goGDgHx6e3h4c3FxcXJvcHBvbWpqaWttaFxdX19fY2tqZWRlYWZnaGhoamqEaYBoZ2loaGZkZmRjZGJejXOAgn18hYaChXl9f46ZkJSPkpOLiJCMep5eXVxasLFYp69ZqaersFiekJuqWKlaXaxYqo15jZONhoyPlZmQjYWLlX1/WlhcVFhdXGJeVlhjX1xdW12blZCal4hnZWZnY2JnaGlnamxpZ2dmZmdlYI6IlDOeTkxMnp5QU1NNS05LTEdJT4WCXWBfX11eX2BkZGVlYmRjZGFgX4lkY2JiY2NhYF5mZWSEZYBkYmGDV1elpalXpldbYmVmaGlta2lqYaBlZ2dnamlnZ2VmZWRgX2BjY2RhkqCHnpqUmZ2coJqmmo+TlZOVk5aTdpqXl5OOkIuJioiJiI2Nj5yalY2EhXB8jZSUmqKjp5eWkJmclZaalKWcpqulrJN3fo+bVVqnlpqkk3qxo2KhX4ByuYlzr4fCqW+QgXtzbW1kY2ZoW1hcX11WVVJZWFRWVVZVT0qTU1ldVVGPf5SlrGbKynB0XoBwV2Brd399fH6GhISMj5mclI2MnIebnZyci56enZmZm5+fnZyQoaKko1GJeWdWgVmVmlVcZ21paWJWUJJ9c29qZGFhYFtYVVVWqBKmpllda4CaXGx7Q01XYWc2OT2EP2ZAQT9APz5CQkRFRkVIRUhHRz5AQT5AQkNAQT49PD17cGVYjnVfnox+c3Jxb2dkYml1hJpgbX6Fh4+Ki4B8cGRcWJ+Pd41xdXV3g0+EcG56gZdYWl6eoaCmoVanjJSMiIuPlZxXW5eAZ2ZkYmI0NjY2Nzk7Ojo4QEA7PDw8PUE/PkVLV1JJSlM8QEhIREorLzM1MjktJzgtLisuMC1PQFSxf2+xcGddZ0w5PkhHRUJHR0JBT09NRkgmJSQkJUBDSk1LR0hEQ0ZHRUdARUZKTE1GPTY6P0gxI0VISENITEZCPTFFREdGRUCAQD0+QD05PDo4NDg2OTg3REI8OTo4MzY4Nzs1NDY4PTo3Mjo4NTE3ODg0OC41NjY0NWhwdXx2dnhzd3t3YYGFjoiChI6Ii4eOkZGEi4aDgYd8bV59foN1fH97fYCEgXt6iIWJjpKPj5GNdYqKk56akJWTmJqbnpuanqGgnZeVlZWAnqKhhICWmJyemZmYm5mZmZueoZebm5eTm56mp4ZjZGZcVVhoeoiGi5CUlouHjpOSlpGUmXaFo6OjoJ6fnqCcnZycnJ6bm5uYnJqZmZmWhYGEhoCAhoyNi4mEh4qBgoKBhImPioqNjI6NioeKjouEiIeMjYhCTE1NSkVARkdFR0qAR0dEQEdER0dLSU5MTUxOS0lKTlNRUE5NUlhUi3SBf4F7dXN1cXFqaGhoaWZnaGZlYmJhZWhjVVhcXmBkbWtmZWRfZmdpaGlra2lqaWpqaWxqamZkZ2ZjZGJejWpwb2poc3RvdWtuc4WPhoqDh4h/fIaCb5haWVhTo6NRlp5SmZiAnadUkYOJlU6TUlSXTph8ZnR4cWtubnN6bGxmbnZiak1MTkVJTk1VT0ZHU05LTEpMeXJudXJuWVdYWVRRV1laV1tdWVdWVFVWVU5sW2NrMzEza243ODcyMTIwMSwuMVJeREdHRkRFRkdMTE5OS05MTUtLTWpMTEtKS0tISUZOTUuATk5NTEtJSWlEQn19gEJ9QkRJS01NUFRST1FMc0hJSUlNTEpMSUpLSUJCRElISUdwfm2Ae3d7fn+Cf4d3b3R3dXh3endff3x7d3N4cmxta2psb21vent6dGtuWmRvdHNzd3qAcXJwdnZub3RzgXiDh4GIblVYZGUyNGRaXmZgVI+AjHPUa3qzhXerh8GZTldJREA+QTw9PTw3NTU1My8tKy8vLSwsLSwqKE8rLC0uLU5HUVyFYsPDaVM7TEAwOT5FTUxLTVFOUFVUWV1YUVJaR1JUVFRLV1ZXVlVXWVhXV1BaW1xdLkk7MyxGN2RtO0FHTEtKSkU+cWNeW11aXFtbXF+AYWJjvLu9aHiPvPKbve6Jq8rt/ZCarKuqra+xsK6wpaGrq6u2ur6/u7y4vZ+ipKWoqaObl4uHhIHs0r6g/MGU3quVg4N7b19XVVleZn9NUV5naGxnZmBeUktGQHBmUVpERkVERSlOS0tRV204OTtsb251cjxyWmNhXlpaXWU0N2aFfJt9hn6Pfwh+fnt/fn1/gZOAhYHcgPB/+oABf76Amn+EgAZ/f4B/f4CEfwGAhH8GgH+AgH+Akn+RgIZ/lICEfwWAgIB/f4uAgn+TgAF/k4AIf4CAf39/gH+MgAF/k4CCf8Z+gn+GfoR9Cn5+f4B9e31/f36bfQZ8fX19fn6EfQZ8fHt7fH2WfpV/AYCEfwR+fn19iX6OfYN8hX2DfoV/pYCEf4N+jn2OfoN9hnwBfYZ8g32FfAF9iXwDfX18AgIEAICGj5ylpZWEgf749ISA57evtpyXkpGclqObt9PR0sOuufqQm5+hocWOsrWyrqqbsqSvsaqXg++cmN+g7vrTj4SGtcXG1M22ud/s9/fk3drb74KEhISF/OXd1tbV09Lf7/L09/j68e7x7ert8efg6ufm7vHu6+rl7OPp5uv17/T48YDu8PP28ezt8Ozq8u/x8evs6O7w4OHg6Ozo5Ozq7Ozu7ejf5eHj39PV1NTAmoLPsrrN1M/RztLQ1dnAyc7Irs7Tz8jPz9PIxMnN0dDHwMDIytDGtpbAv8PDx8bKxszLxsK/xsrBzMzPxMnMvajU19HP0dTV09bPzNDD1tbd29TY0IDP2M3SysG/2+Xz8vTy4vD16eDk6OTa19DT2967gZSdoqrX1dbTvL/J6/D2+vfz8fHz9Pa8zff39PX28/Tx8fPy9PTz8/Xx7ujp5enp6unp6uvs6uTPxsXGxcHG2dzf3eDc3dzc3NfZ2dXW19TW1dbR0szNp5eipKOinZ2enJueooCjpKWioZ+cm5eXl56dmZ2cmZeZm5iWl5WVk5aTg7u6uLKytbCvqampp6aoqaelmo38ioqHh5CampeSlpWUkpGSkpWTkpGUk5OTkZGRkJGQkY+PjI6OjYmDiIT917DP1drUv7nD0dnd3djc39zW1M/O0c7Qzb/vgoKAgP77hPr19YD759HrgYCAgoOHgoGBh4OF5sfG4+rv8u3r5+fhv+KCh4aLgYOJhYmFg4aKiY2Njo6NiorrwJGUlZGSlZWXkpSVk5aVlJWPjZSU/8v46vyDgv/u//yAgoKB8vb6gYL/0fGOloyRkpKUl5SXlpealJeXmpOO3pCPkI+OkZKQj42VlYCYmpmalJOS25iWkYyMkJKRkpiYmJydnZqXmJrUnJ2bmZqalpeRlJSUk5WSkZKTkuDI5OHi4t/c4d/k3Nzk29Le0s7U1dKw0tDTzMfGwsHDxcrRzdLL2crEwLy6p6nn2Obr6vbazNfq39TS3dzV3ebf3eDGs7W/3oOKkonz6OO5joDfmqbmk9m1x93G4YeV/M/Ew76xnpqkrJORnZ6Wko2Pl5KQkJOWl4yBh5KfsYDy06/P7Yniroi7x8msjYWGjY6fw8XBwsjLzeHl3t3kqfT19fTz4O32+PP7/4D///367IGA/P7//fb08vD05cme6JDj2/aGlJ+srainmob54b62soCppKWwpqulmon88NfUysPL2tvv/oGBio6JhI6bmoNGUVJUUVNRSUpLTEpKREN+gYyEjIyNhYqOjJCVlI2Jg4KDjpWcr8PgiZmgsLfByMe2t7GnnZGKiIHjxZ74mpGUorrN7YKL7u37h42bpquW/oGGjp6wvrSD+4mRmaSvrpL8gYBVWV5oamBVVaimplVVnX11dGFgXl1jYWhhcIaEhHtrdadhaWttbHxUaGlnZWRXaV5lZmFWTJBmr7+Ixs+iYFxfZ3BwenloaoKFjZCGgoB/iUpLS0tMkIOBe3t7eXmAioqKjI+RiYeGhYKGiH51gnt8iIqJh4V+hXuDgYWLgoWNh4CCg4aJgn6Ag4B9hH+BgXt8fH+Cdnd5foF8eH58fX+BgXpzeXZ5eHFxcnRtVk+HeoGPlJOSj5GRlJeFjJCNe5mdmpSampyUj5SWmJiRjo2QkpiRhGqJiYyNkZCTkJOQjoyJj5WOlZabjpOVinmfop2cm5ufnaCbmZ6SoJ+lpaChnICcpJqhmY6ImqCqqKyomKmtp6GmqaqioJqeoaCDVF1lbGyJhoWEeHqIoqWmramhn6Srqq6EkK2urK6tqqupqKqpqqqtrK2rqaSmo6epqKamp6mqqKOTjYuMiYKHmJialpmUlZWWmJKTkpCRk5KSkpOPkouNc1peYWBfWlpcWVhaYIBgYGNgYF9dXVpaWmBgXV9gX15gYl9fX1xbWVxZWIWCgXt7fnh2c3NubWxvb21tZVyjXV9eYGZub2toa2ppaGdpaWtpaGhpaWhpZ2dmZWdmaGZmY2VlZGJdYVyvlW54fIKBdnSAh46UlJCVmJePjoyLjomKjIasXVxZWa+sXKilpoCuoIudV1ZWV1hcWFdXXVhamoB7i4+RkY6Qjo6OeZdYXFtfVlhfXF9bWFxfXWFhYV9cW12ghGRmZ2RkZ2ZnZGZoZmloZmdhX2RlsX6Zj5hOTZuSpJpMSklIio2MSEqPeJxdYVpeX15fZGFkY2NmYmZoamRglWJhYWBeYWFgYF5lZStlZmZnZGNilWdjYV1cXmBgYGZnaGtsbGpoaWqMZWZjZGZoZWdkZmVlY2RghGGAlIygm5yamJmdmZ+YmKCWkZuSjZGTk3aPkJOKh4iGhYaHi5KNkIqWioWBf4BzcJSKlZqXoY+Ijp+YkY6WlZGboZ+bnYl8f3+MVlthXKGamoBnmmuCrmyjjqHOubZtc62Lf3x6cWViZ2tdWmBgW1hTU1dSUFJWWFdQSk1TWWVRmYiAeIueXJxyV3uFh29XUVNZWmJ+gX9/hIaFkJOQkJFrnZ6dnZyPl5ydmZ6hUqSko6CXVVSipaaknZqYmJaRgmeSXJSMllVaYGZmYmJaT5OBc29wbGtudW50dWZXmpGGhIF8hJCWoq1fYmlxdHmGi5OES1ZZW15jYVtiZWtnY1xVpZxRmIyKh4J3dHFvcG1nZV5cVlhdYWJrfJFUW2Jqb3Z3eG9wa2ZgXFxZUpB6XpRhW2JldYOYUlaVlZxVWl5mbGCfUlZbYmx0blGgVVdcY2lqV55RgC4wMjM1NDQ0Z2RfMzJgT1BMPTw7Oz4+Qj5JVVJTTERJb0JISUpISSkxMzI1PC02MDAwLi0rVUFurH20vptbX2I7Pj9ETD9CSkBFSkxMTEhGJSUkIiRGQ0lERERDREVGRUVESEtCQkFAPkNEPDQ/NzhESEhFQzxBOUFAQUU7O0NAgDw9QEE6NTg7OTY8ODk5NTY2ODo1NTc4OjY1NzY3ODo6NC8zMzU1MTMzODctMmVjanl+e3x5e3p9f3F5e3hsjIyJgoeJi4WAhoiIiIJ9foGCh4N4XXx7fX6EhIeEhoJ/e3uCiIOLiY+Giox/cZmfl5eXlpqWm5WTlo6cnKKinJ2agJqimaGWiX2PlJ+dopyKm6Kdm6KpsKeim5ydnn9LV2BiUGVlZGRcYnOKkZCWlIyLkZeXnnaDn5+doJ6am5qbnZ6fnqCfoZ+emp2YnqGfnZ+goqOhnY2Gg4KAeXyOj5GLjoiMjI2PiYqJhoiJiImIioaKhIZuSEtPTkxISElGRUdNgE5QUk5NTElKSklIT1FMTk9NTU5QTExNSUhHTEpShH9+dXV5cWxmaGVlY2dnZGRcU5lZW1teZW5wbGhta2lnaGppbGppaGloZ2pnZ2VkaGhoZmZjZWRkYVxgXLGTZmdpbnFlZ3R+g4mKhY2PjYOCgICEfXyCfqdZV1RTo59WmZiafqOXf4tNTExNT1VOTE1TTVCJbWVtbm9vbnFwb3FhhExPT1JIS1FLT0tITE5MUE9QTkpJTIRvVllZVlZYVldTVldXWVZUVE5LUlSTV2pkZzMzaWJ1ajEuLi5WWFUsLlZMdEVJQkZHRUdLSUxMTFBLTk9STEpxSUlJSEdJSUhIR4VMgE1JSElyTktHRENGR0dHTU1NUVJSUE9RU2hJSUZISkxJTElMS0tJSkZGRkhHbW+Bf35+fXx/fIN9fIN8d4B3dHd2dGF2dXdyb29qaWtsb3Rwcm54bWppZ2ddV3NrcXFvdmlnb3tza2lvcm57gHt4eGZaXVtcMzU7OF1ZXlZOfWqsgNB0poaewbGzaWFsT0VDQ0M/Pj4/OTY2NTIxLi0wLSwsLS4vKygpKiwzL1pPRU9XQ3pLNUpPTj8vLC0xMTZJTUpLTk9NUlVSUVQ7VFRVVlhPU1ZWVFdYLFpaWlhULi5bXF1ZVFFRTk5LRzpVOWBgaTxBRUpJR0ZEO3FnX2RkZmdvfXhwcHNrX6+llZOUkqK5vdbuiZGlsrjB3+765YGUmJ2ks62ep6y0rKaSif7u38a+saCSi4qDgoF5b2BXTUtOTlNcZHRCS0xRU1RWV09RTkpFQj8/OmNTP1s5NzY3QUVKJylJUVMsLjA1NzVhMTEyMzQ3NSpPKiwtLzIyLlcuiH4FfX19fn6UfYZ+jn8Gfn56fn19lYCFgdiA83//gJGAAX+pgJx/hIADf3+Ah3+MgI5/lYCCf5SAhX+CgIR/hIAIf39/gIB/f3+TgAF/k4ABf5OAAX+TgAF/x36Ef4V+hH0Jfn6Af3t7foB/n30BfoZ9Anx9l36NfwGAhX+CgIx/BX5+fX19iX6OfYt8in2Pfpl9kX4EfX19fId9BX5+fX19hn4BfYh+AX2HfgJ9fgICBACAqcvmhIzGnKe60uSBiIWtnIbpvbGupK+ltr/HuLe3rr7EipSWm6Kwr8eOoKStr7O4tLe3o5fPx5epoZn6gYDrhbPDxsayrsLn8vP23NjX3O2Cg4SHhP3k29TUz9TZ5PP59vb5/fXr6/Hr7N3g6u/w6u3v8Ovr6+/q7Obf8fPu9fGA8Pb08vb48vT29PHx9vTv8/Lu7OXy7+jh3tTk5evn5OLY2dXV2NO3mOO/usLKwru7yM/Qz9LT1M7SxM7N0K64z9TW0tfY3c/Ny8/WydLJzMbG08/FlsPFyMjHwb3IxcjCxr7Dw8PBxMfGw8XKssbe2M/NytPWzc3Z2dHQ1dTX09eA0dPS1N3a1sK/5Orb6eXj4+Hq5eDb2NvX3dCUj5ORjZTZ2tna1tTPy7e8xuDx9fn19/a80/bz8PP29PXz8/Lx7vLv7O3r7Ozp6u7s7Ozr6+vn6Obl49/k5+Li2sfBwp27vLy8ztvd2drW2NnU1tfT0M/QzpSio6WjoaGko6Whp6mAo6OjoaClqKakn6CdnJ2anJufnZqUkpORmZaRlOq5u7i0raqupaOYj4yIhIOA9/+I5fyenJucmpmbmZialJWZmJSWk5OSk5STj4+SjY+Pjo6Lj42PkYqIhYWAgv/po8K/vtLi2eDZ29vW3drV1NTQ1NbW19/Yv8L3/oH9/vr6+eWAz+uChYL9goWDiP6Agof/goCB58/P7+fj5N/Suv+Eg4mFh4eHhYiHhIiJh4qLioyNjYn405GPkJGUkpWUlZSWkpCSko6Rj5GTkLr28evxgPX69/n6+YGB8vH3goSC8sOPjY+LkpSUl5KUm5iYnJWTlpGU/PyVkY6TkZCTj5CPk5SAl5qdmpmYlOGXlpaUkJOTkpCUmpibmZ6amZma25ibmZuVmJiYl5GTk5GUj5CTkZG3n93f1tnT097X19rX28/T2OLX3d7c0rPFz8rLyMHBxMjCvsTGytjPv7qst7Kzm8rf6OTp3OXS4cz15eTm6dPFu7TAytjGztXf/pGI+YGD3a2AgN6M2Iij3br2wd+//rnbz9vSvJ2Vpq2SkZ2YlpGQkpySj46XoqCTgYqXq8rn4rerwt6lu7m3ur63kpKQjpWivtHS1NjRztrc4e+Ny/uAgYGB//r6+v7/7fT8/fn8+/GB/vj8/oD/gYGC/Y+RlJebiuu6jsKB3NTY7oCEh4X78+4r3tDd0savnJqKgerWw7KZj4Dt4uno7fWB9feBi42Gio6Tk5yYkqWprKerrISvUaunopyjpqS0vdDmhZeowNXq/4uOmKCyt7S1sLaxnpOTjojyvY/Yj+v3+b2Wq8nV3+6AjZ2Orqu30O+Oo6mtm6K30O+RqdKKip621f6TjY2HkIBpf5FTV3hfaHaFkE9UUmxkVpp+eHFncGl2en1ycXBqdYBdZWZqbHV3f1ZiYWdpam1paWleWY2Q5pKJfcZbV6lWZW9ycmhlbYGLjJJ/fH2Ci0tLTE5Nk4V9d3l1en+Fj5GQj5CUjYSAh4OBd3Z9hYWDiImLiIeIi4WHgHmHiIKMiICGi4mGiIqEh4mHhYSHhYGFhYKBeIOCfnl1b3t7fnx4eXNwbnB0dGZYiYCBjJKJhIKMlJORlJSUkJOHkI6RdYqcnqCbn6ClmZeXmZyUm5SWj46cl49sjY6PkZSPipOOj4mNhI2Ojo2QlJOQk5aEkaegmpiWnp+VlqOinJqfn6KdoXScoaGjrKilkoiipJmin52enqekn52boJ2jmGRdX1xZYIqOjo6LiIR/dXqEl6OksK2vrYKYrqyoqq2sraqrq6qnqqelp6anp6Wnqqmrq6moqaaopqakoaWopKWdjYmJbYGDgoCNmZmWmJSVlZKUlpGPkJKRZYRgVF9eYmJlYWVoY2VjYWFnaGdnYWFfXmJfYWBjYV1aWFlYX11ZW52Ag4GAeHR2bm5mXFtYVlRTm59VkLVwbm1ubWxwbm1uaWpvb2praGhnaGloYmJmYoRkgGBkY2ZoYmBdXVhbs6RpeXl6i5eMm4uRlY+Yk4yIjoiIiouOmI9+h62xWrCwrKmpnY2aVVpXqFdaWl+rV1lcqlZVVpmFgZOOi4yNhHeuWlheWVtcXVteXFlcXFtdX1xgYl1ZqJBlYmNkZmVnZWZlaGZlZWRfYmBiZmN2lpCMkU6PgJuXl5SQS0yJipBKS0uHdltbXVtgY2JmYGRpZ2ZpZGNmYmWrq2RhX2JhYGNgYGFkZWZoamhnZ2WXZ2RkYl9gYGBfYmhoamltamhnaJJjZWRmYWZnaGZhZWRiZWBhZGJifW+Ym5OVkZGalJSZmJuRlZiflJaVl5J6h42Ki4iDgoWIgIaFiouPlo2AfnR8eX1lgo+WlJmQmIuZiqybnZ+jk4qCdoCIk4iPkZWrY12oWFqVdFaYbalmea2UxrHDns2FkoqPh3lkYWhsW1phXVxYVVZbU1BQVl1bVExPVV11lZF4cYOVZnNyc3R4c1pZWFdbY3qHiImNioaOj5OcW4GfUlNTgFSopKWlp6iZnqKin6GgmlWnoaSmU6VTVFSjXmBjZWhcnHtdf1aNhomVTVBTUZaPjYaBhYF3bGViWVSijH5wYVhTmI+TlZijVKewXmFhYWNlaWxtbWpzenl8gYKAfHx+fXl1cHJydHyCh5JSXWl1gIuXUlZbYWtvbm1rbWtgXV5dMFaSc1V3SHd5f2dUZXeDjJVQWGFWY2JtfJlYZ2pjV19sgZlbb3xOUF5xiadhV09OV4A0QUQkJTcwOEBGRiQlJjw9NmVTT0RARkFJTVRQUE5LUVU/Q0NGSU9MRSoxMTs+OjYyMTEwNWRvoICAccRiXMpSO0BCRUNAPUJGRlJHRkhLTiclJSgnTUlFPkJARElJSk1MSkhMR0E5Qj09ODU4QEA/RUZIRkRESENEQDlCQTtCQoA+Q0A+P0A7P0E/Pj5BPzo8PTs7Nj08OjY1MTg2NzcyNTQuLzE0NjMyVGFrd3tzb213fHt4fX18eHxye3h7ZHmNj5CIkJGTiYiHi42Ci4aHf36Ni4NefYGChIeDfoWAfnd9dn6Ag4GFjo2JiYp4jKOelZSSl5mPj52el5ienaCbnzqbn6CjraqokH+Vmo+XlJGTlKKhnJubop6jlmFZWFhVTGhrbG5raGViWWNwgY2QnpmcmnWMn52anaCdhJ6AnZmdmpmdm5ydnaCkoqWlo6GjoaKhoaCcoqaipJyKhIJmd3t8eYaSk4+Rjo2PioyOiIeJjY1fTU1NTExMUFBTTlNWUlNSUE9VWFZVUE9NTFBNT05RT0pHRUZGT0xIS41+gX97c25uZWVdU1FPTUtKhohIfLhxbWxubW9xb21vaGmAcXJpbGdoZmZoZ2BhZV5hY2RjXmNiZmpiYFxbVlqxoWRpa3GBjYCQfoWKhY+IgXuDenh3dnuJgG+ApalVpKKcmpuRgItMUk6XTVFRWJVMT1GSS0lKhG9kcm1sbmxpYJFMSlBMT1BRTU1NSk1MS01OS05QS0aLeldVVldZVlhVVlWAWFZWVFJOUU5QVVJYZmNeYDNcaGVmYV0xMVZXXC4vL1JSRENEQkdKSk1JTVNQT1NOS09LTYWBTEpHSklHSkdISExMTU5QTkxNS3VQTEpHRkhISEZKT05QT1RRUE9QbEhJSElGSktNTEhKSkhLRkdJR0hdWXx/eXt2dn53eXx7fneAeXyBdXl9fXxlbHFwcG1paWttbGltbnF5c2lnXWNjalFfZ2xqbmp3bHdngnV2e4Jza2RdX2h0am9rYWo8OmU3OF9SSIiJ13GCq47LrcGZwmRWTU9LRTs6P0A3NTc1NTMvLjAtKywvMjEtKSwtMD1UWUtGTVQ6QD8/QURBMzIxMDOAN0NMTU5RT01RUVNZNERVLCwtLl1cXVxfX1dVWFhWV1hXMF9bXV4vXC4uLlo0NTc3ODJXRDFJNGJgYWs4Ojs7cGxtaGJmZ2BWVFZTUo6AcGVZU0yNhIqJlKJXusBiY2tnaWltc3x7coCAfH6BgoCAfHt1bWZcXmRgZ21zfUdLVGBCaW57QD9CRk1PTU1MTU9FQUE/O2NJMzooS0pFOSotMjU6PyEjIyArKzM8RCktLS4pLjU+SSouNyQjKjM8SCglJCUqBX5+fn9/hn4Gf39/fn5+kH2Ifo1/BH56fX2EgAF/kYCFgdOA93/9gAF/kIAFf3+Af3+pgJ5/AYCIfwSAgIB/hIAIf4CAgH+AgICLf5WAgn+VgIV/AYCGfwqAgH9/f4CAgH9/k4CCf5OAAX+TgAF/k4ABf8l+FX9/fn9/fn5+fX19fn5/gH17fH9/fqR9mH6Df4SAjn8BgIR/BoB/gICAf4aABX9/f35+hH2Efo19h3yGewN8e3ugfId9kH4FfX19fH2EfAF9hX6Ef4V+g3+GfoJ/h34Ff39+fn4CAgQAgKy//7GMrqmiqM/9pMjooJiQlqCL+tS0ycm4w7+5uKS1t4GYoaqspKiUnbaErLeqqbzQ1Mvn04a49IX4kvXx6YeWnaetrcXa8PX559zT0tnr/oKEgv7u3Nba2dPY3en08ezt7fPp7+/u8PDg3fH09e/r7+3u6+zr7OLt7u7v7PHrgPLv+Pb29fT39fLt7Obk4+nr7unl5+/q3NTg5Obl5eLe3MOlida1u7+Qssu8qru+xcbHxs/U1NPDwMbI1NLKn8rQ1tXR19TSz8bFy9HSy8bGz9La1MeXzM2/trO2wsLOycXHwsHFvLy9wrrCxc++q8jU1dTPz9TU0dfGy8/U1dXigODU2dPe4NzR077C1+bm6eLi19PZ29Pb3aiCjYiEivLR1dfT19HR0tLR19a9ucLT8vOy1/L09fPy8vTz7/Dv8O3s6+vq7Onq6evq6urr6+3r7Ovs6Oro6efj3uLi49qa3Nzd1su8vLi6tbG1tba7wsfNztCIqqyrq6upp6isrKyngKWeoqGjo6CipJ+cn5qamJ2amZGSjo6OlJWQioPVlpOSjoqJhYCBhIqOi4eLh4eDgvi5lZ2cnaCfnJucl5iWlZSQkpSVkpGQkJGQj5CNjY+NjoyMjo2Dh4SGhfHU1Mrj3+To4d3a397Z4d/h3dbV2dDX3tfUzdHTtMX8/f3/4NHygICBgoCAhIOGh4eChICChoaCgYSG/NXBzeG64ISGg4OGhoiJg4SDgoiHioyKiouRiovr2oyOiIqNkZKTkpOTk5GPkZaQj5ORlsnv7/Lw9/z28/Py9fv8/Pvz/oOIhOn0lZGTiI+RkpaWkpaXmpiYkpKSj9aSlZGWlZKNlJKQkZGXAZiEmYCamNmWmJaVmZaVlJGPkpqXl5mcmZmY+4egm5qcnp6alJSTj5GQj5KNkobSybrY3t3X1tfYz87Y2dvU5eTj27K/ua6x1s/TytDIxL++u7u5usG9tL24s7e6wZ3A6PDs39fX3OTw6uDgubS4ztra1czT2uHl74CDgIWMgM+aianE+YCZwKr55KDw+s2E2tfXyKOgqa2Tj5yampyUlZyalJGQnJ2ViJSmqMzazamjvNamv8O5vsbHzMSvpJ260tLV4ujr7O7s/Mju7vyA/fyAgPn7+/+B8IKDhYWC9/z9+vf5gIKDhIOHg5KVlZWWlJiZmpyW89PIw8eggOfSztHX1uSAiXuHiIj+7trHubCck4r/6N/NybqvoJOUkIqPkIyIipOTl6Oks8LF2t7f+IKOoaq5y+H9i4mPmqKrs7zBycvPzsKzqZ+RjYeC8dXBsquznJ+F2vCttMbWu9LK3++Bl8GA8qCwxdf1n9mHgISXssv4qKiPj5qWrM6ItceZnKCAYW2ecVhjYl9heJxpgopeW1deaV+yln2DgHV8enR0aHByU2dvdXVvcWFodVBnbmFga3l/fZqibOnObs1uq6qnT1RXXWJjc3yMkJKDfXd4f4mYTU1KkId9dnp8dXh/h46NiYiCh3+Jh4CEgHZ8hY6QiYWJh4mHiIiHfYeJhoaDh4OAhoWKiIqJiIqJhoKCfX18f4GEgHt9g392cXp8fXl5d3V5bFxPhHqBiGR/kIV5hIaMjY+MkZOWl4yDiYqVk41ulZygn5yhnZqakpGUmpuWkZGXl52bk2mTlIeEf4KMi5OPio2Ki4+Ki42Qi5KSmIp7lKCfnJqbn56ZnpCXnJ+goqyAqqCloK2vrKSkj4uXoaOmoqOYlpubmZ6fd1ZbWVVZmIKGiIWGg4SFhYOHhnh5gIyjpX6bsLGvrKyrraynqaepp6impaWopKioqKenqKqprKusq66qrKmpqKOfoqGinGyfnZ6WjIKDf4N/e358fYKHi4+Sklxpa2ltbGtqam5sbWaAZF9iYmNjYWRmYmBjX19fYmBfV1lXV1hdXFlVUIlpZGNfW1hXUU5QVltZVllVU05Pk3dpb25wdHVxcHBsbGpqaWVnaGlnZmRkZWRjZWNkZWNlYmNkZFxfW11dq5aUh5GQl56VkIuSkJCcmZmTi4qPg4iTkJGJi493ibCvrrCZjaaAVlZXVlZbWVxdXFhaVldaW1dVWFmgiH2DjHSSV1xbW1xcXV5ZW1lWWVpcYmBfXmRbXp+VYGJbXmFjY2NkZWVkZWRkZ2JiZGFohY6PkI+VlpKSj4yLj5SUkImRSlBOjKBiYGJaYGNjZmZjZWdoZmdiYmNik2NjYWRjYl1jYmFjY2eEaIBnZ2aQZGdlZGdkYmNhXmNpZmZoa2hnZ6lba2hnaGptamRiY2FjYmBjX2JbkoyBlpmal5aXlo+Pl5idlJ2dnZV7hH52e5GMkIiPioeCgYCAgYSHhH2BfXl8gIZsgJibm5WPkJSZpKGdn4N8f46Wl5WMk5WYmqJVV1haYFaLaV18moC+cZGIyNKXx8ueWZCOjYFraGprXFlfXl5dWFlbWVVTUlpcV1JXX116jIZvbX6MZ3V4c3V4eX96bWVhdoWFiJGXmpqcm6N8k5agUqKjVFSkpaaoVZ5UVVdWVKGkpqOjpFNVVldXWVdiY2NiZGJkZWVmY6GKjomAZ1KThoCEhYeKTXxRUVNTl42DeXBoYVxXqpmSioN7cm5pZmRiY2NjYWBmZmZwdHl+go2QkZtQXWhtd4KOm1RTV1xhZ2x0dnt6e3x2b2hiXVpVUZF/bV9TWElCNlZjVllncnF2bnqGSlp6VJBZYW55iluHVEhKVWFyjmRrTk9YVGJ2UHN1VVdagCktQC8nKisqLDdGMDo9LC0tNUA6bmNQVVFOVFVRUklPTTlFSE1OSkxBR0crNTswLjM4PUFjgWG5sGjIeLnAyUU7Ozs9P0RBRElLREA/Q0pOUCcmJEVDRD5ARD4/RUhKSUhHPUA6REE5PDo1PjxJTEZCRURJRkZFRT1GR0JBPkI+gD48QUBBP0BBQT47PDs8Ojo7PTs3OkA9NjM2Nzc1NTMyODQwMFpga3RTa3twZHFxd3Z2dHp8f39ybXNyfH57XoOKkZGMkY2KioGBhYyNioaEioiOjoZcgoJ3c3B0f3+HgHqBgICFf4CBiISLipODdYyYl5OSlZialZqNlJmdnqGqgKqiqKSxtbCqqI+GkJiYnJuclpSZmJadpXlQV1RQUXVgY2VjZmNmZ2VjZmZbYGx4jpJxkKWlpKGhn6GhnJ6dnpucmpqbnpqfn6CgoaOlpKenqaisqKuop6WinJ+en5holpaYj4Z7e3Z5dnN4d3h8f4aMj49WV1pYXFpaV1hdW1tUgFNOUE5QT1BSU1BPUkxNTVFQT0dIRkhKTU1JRkJ2ZV5dWFFMS0VCQ0lQTkpNSkdCQ31wZ29tcHZ4cnJybGxqaWhjZWhqZ2VjYmNiYGRhY2ZiY2BgZGVcXllbXKyTkIGFgYyUiYF6goGDko+PiH5/f3F1gX2Ce36AbYGmop+hi36VgExMTk1MUk5UVlVPUU1OUlJMSUtNg3FnaG5de0lPTEtMTlFRTE1LR0pLTVNRUE1TSEyDf1JUTE5RVFVUVFVWVlVVVVdRUVRQWGhjY2RgY2RhYV5aV1hgYl5WWi0yMlt4TElKQ0hMTE5PS09QUVBPS0tMS3JLS0lNS0pFSkpKS0tQgFBNTk5NTk1wTU9MS09LSUhGRUhPTE1PU05OToBDTktKTE9RTkpISEdKSUhKRkhDdXBpfH9/eXt+fnd1fHt9d4GBgXtjcWlgYnNtcWxybmlnZmRmZ2lvbmptaGRmaXFaW2ttbm1ucXJ1fHl3eGNgY21vb29qcnZvZ2Y0NTY5PTZdgFNPk8vfepeDxc+bxMaTOVJQUEpBQT9AODQ4Njc2MC8yMS8tLTAxLiwvMjA/VFNIRUxQOUBCP0FDQ0ZEPDk3Q05MTVNWWFpaWVpCT09VK1ZWLS1aXF1gMVkuLzEyMFlcXV1dXzAxMTEwMTA3ODg3NjU2NjU1M1NMVllMPDRkYV9ggGBiYjc8Ozs6b2tjYFlWUU1Kg316cWxjYV1YWFRRTk5NT1FTVFdhYmNnanF1dHtESVFUW2VodkJBQ0hMTlJUVFhYVlVRTUlEQUA8OWRVRjsyLyYbDRgoNTk7PTQtJiosGB8uHkAlJyosMCAxJiIgJSotNCIqJCIlISUsHCs1IyQmBX5+fn9/hn6Cf4d+jX2Kfop/CoB/enx/gIB/f3+SgIOB0ID7f/2AAX+TgIJ/qICmf5SAh3+WgIJ/lYCSfwWAgIB/f5OAAX+UgAF/k4ABf5OAyn6GfxB+fn59fX1+foB/e3t9f39+o32XfoR/BYB/f4CAhH8CgH+FgIZ/koAHf399fX5+fod9hX6JfZ18iH2Vfod9BHx8e3uEfAF9hH4Ef39/gIZ+g3+GfoJ/hn6Cf4R+AgIEAIDYhajtpqevutL0lavMp6iqpJ+am6KpqJHy09/EwbCpucXZnrK4s6mTjY6Wm6npkqW5usbr19TKp/7l5vL57YCzqZqvr7C+6+vv7evh3NjZ7fmAhIGD7N7U2dXY29nn+PTw5+/u8O7t6OPl4eXv6+Xp6Ozw7erq6uPh5ubs8fXx54Dp7fDr5Nnu7e/u5enh2ubs6urn4OPj4ebk3N7bwaON6sS4yMPCucK+tIzHy8C4wbzFwsfDy9DV09TQyMzV1NOhztXY2NXS2dLNzMbKx8/GwcXM0Mmso5nB0MvJvr/KxsPLzMHFycTIw8LFw8fFzNWpt83Nzc/R19bY2trR0MzU1oDi19je5eXe4eTc2cCyw9rW19jb2tTUwYqIi5SRkdXLys3Ozs3KysrM1NXU09DPyLKirOPt8PDv7u3v6+vq6unq6enq6Onq7O3r6+vs7e3s7PDx7urq5OPj4N/f4t6gytrc2trb2tnZ2dfS1dbW1czAuLWwpKuqoZ6inJmZmJuYloCYm5mVlZmSiYqMiYuHioeEhoT/hIGBg4WCioqRlJOOkY2KiYOGiIqEiIWC9vX68/T459udn6GdnJyZmpiYlJSWlZSSkpSRkZGPjpGPkI+Qj42LjIeJiofj2MnS6erl3efp49nc4eHl6Nne19vVz8/U1dDJzc7MztLGsM/T1fn/goCDgP+BhYeEg4SDhIL9goSCg4SHhYOKiMy0/oOEiImHjImIi4aCgfyCiYmHh4yNjYnd5YqLiIeOlJCRlJSRk5eQkZGTlpCUk9/h7+70+/v8+vX88vv0/P6DgoKFhoXHkpGTkJCRl5iWlJKSmZqYmpuYk5fVlpaWlJSSkZGRkI6NlgOYmZqEmRHSlpmXk5OVlJaXkpCSl5qZmISXgNmXmJubnZ2bmpiTlJOUlJKMkKfX6LbV29DY1Nff3tnn193Iwrq1wdPNz8mrtdnW2NjCycLCv7/Bwb7Auba5t7++u72zw+zn5tjT1+HZ58C9xdLQ1tjn6eTg2t/og4v59YP3/NSc4sOa3oictL3AytX9ipL41MrIvravrpiWmqCigJ+UmpiWkpKTl5SNipOgnsnLrJuYscWXsrq8wcPIzdHO0dTe3dzb5u3v9oW59oD//v38+vfl4ujx9Pr0gYSEgYCBgYOGhYaB+oWEhoeHhoyNjY+Ni5KVmJmZjIOc6IWlkIbSy+Hc1dXb3uXr8fz+ioyIjJOOjIP37OLVz83Bu7WqZKOnpp6XkpCQqauvvMnM3NjY8IGZrLO2sr7Jz87P0MGvpJuWkI+Mgvbj0tCmooKEhN3c4YeQjfzUqqys4Y21msrM2PGKrPaknJ2ltdyGxKiC+YecuuCerpWKjp+614uxwJedp7aAek1iiV1eZW16jFVkeGBhY2BfXl9nbHFjo4mSfXlxbHJ7iWt6fndxX1tdYWRtk1lfamt1nqatpY3VubSrratYYVtTZmVmboeGh4WKgX58e4qSS01KS4aBfH96e4B+hZGQjYSKhoqLhXx5dnZ8h4aEhYSHjYmGh4mBf4OFiYyQh3+AfYGIgnlwgoGDgnyCe3N/hoOCf3h8fHt/fXh5emtcUpGAgImHhoCJiH9hj5KKgYmEjIqMiY+Tl5aXk46RlpWUdJmfoaGfnaCbl5eSlpKYko6SlpeTfHNuipSRkIiHj4+Nk5KKjpOQlpKPkZCRjZSaeIWYl5mbnaOgoaCinJuYoKJMq6Gip6+vq62wq6qPgoyem52goJyZmIlcWFpeXV2GfHt+f4GBgH59gIaGhIWDg39za3qkqq6rqqmpq6impqakpqWkp6Wmp6eoqKmoq4SsEa+wraqqpaOjoJ6gpKJykJychJeAlpiYmJOXlpeXkIeCfnlvcnFraWtmZmRiZWFfYmVjX2BjW1dXVFRVUlZUUlRTnVVSUFJTT1RTWFxcVFtZVVRQVFVXUlVTUJOPkIyQlIqZbnJ0cG9vbW5sa2hoaWhoZ2lrZ2ZmZGNkZGVlZmVkYmNeYGFfnpeLj56el4+anZWKjZCAkpeekJONkYyGhYeJioaKh4WFiH94kpKPqaxaWVWqV1teWllbWVpZq1hZV1haW1lYXlyLeq1WWl9gXWFeXV9cWVisV19fXV5gX2FblJxeX11bYWZhYWVnZWZqZGVjY2hjZmWaho+SlJaVlZeRloyRjpaWT05NUFJQf2BfYV5gYGYUZmVjYmFmZWZoaGZjZ5NnZ2ZkZGKEYYBgX2ZoaGloaGZnjGZoZ2RkZGNlZmFgYWVnZ2dmZ2Znk2RlaGhqa2traWVlY2RlZGBicpijf5SZkpiVmJycmKKXnomDfXuHk4qMiHF6k5GQlYiKhISBgISGhIZ/fX9+hIKAhX19lZGSi4uPmJOeiIWKk4+TlaOknpqRlp1ZXaemWGelqo5ploZ9tWV0jZmbvsHQbm+qjIeJgnhvbmFeX2NjX1daWVdUVFRXVVBRVVxbfYdzaWd2gl9scXR3eHx/g4GCh5CPjo6Wm5yfVXWXTp6dnJ6dm5GPlp6gpqBUV1dTUlNSVVhZWlejhFiAWVhdXV1eXVxiZWVmZF5YgMJVa1xTmIqNjI2PlZSQlZmbpFdXVVdZVlRQmJCNh4J7dXNwa2ZmYmJfXFpbaGltdHl6hoWFjU1faGtwcXp+gH9+gXZtaWVfWVlXUZeJfHdaVkBCRHFzcUNFPnBXREdKbkVUTWtvd4VMY45fV1ddZn4YTnJkSIlJVGaAW2RTTE9aanlQZ2lRVlxngC4aITQpKSssLzMeJTIqLC4tLy0vN0BFP3BfZFZTUU5UV2JJUFJMSUE9PUJERVUxLDMzPF19k4hwv6SsubrGXDs4NUJCQUJEQUBBTUdERUVKSyUnIyRCRUVLRENHRkZLTU1FSEBGSUI6NTQzOUJERERDR05KRUZGQEBERUdHTEI7gDk7QTs0MDw6Ozw3Pzs1PUE/QDw2PDw6PDs2NzoyLTBfYmhycXNvdnRsUnt+eXF4cnd1eXZ7foB+gX95eYCBfmKIkJGPj46PjIiIg4aCiYKBhIiJhW9oYnqEf4B3eIODgIWDfoSJh4uIhImKjIiNk298kI+VlZiem52anZeYlZ+ggKmfpKq0ta2ws6+skn6Dl5eYoKKblpSHWVNVWVdXa1xcXmBiY2JhYGFnZWRmY2RiXFxxmqCjoZ+enqGdnZ2enZ2cmZybnJ2en52goKSpqKenrK6rp6ehoJ6cm52joXCKlJOOjo2MjY+RkYyQkZOTin98enNnamlgXF5YWVlXWlNQgFZZVlJUVU5ISEVGR0RHRERHR4dJRUFCREJGRklLSkRMTEVFQ0ZISkdLSUV7eXh1en93mGxwcnFvcG5va2lmZmhnZ2dpbWdlZWNgYmRlZWZlY2FiXWFiX56Xh4iSk4qBkZGFe36DhYuVg4R9gXt3dnZ4fXh6eHJxc2tvhoOAl5hSgE9Llk1SV1JQUlBQUJZOT0xNTVBNS1JUfGiRR0pQUk9UUVBSTklJikVOT09PUVBSSnuDUVFPS1JXUlJWWFZXXFNUU1JYUlVUfVteYmNlZGVnYmVZXFhiZTUzMTQ0NFhLSkxISUlQUE9MS0pOTU5RUlFMUXNPTk5MS0pISUpKSUhPgFBOUE9PTU5rTlBOS0tLSUtMR0ZHS01OT05PT1BsSUlMTE5PT1BOSUpKS0xMR0lXfYZoeH94fnx+gn97hHh8bWpnZmt1a2xnV2F0cnN3bW9sbGdnaWtqbmtrbGxwbGlvZF5ram1rbXF4cnxnZW50cnR0fH59e3VxaTk6Zmk3ZWhcWkt6fp7ccn6Nkp7Cx8lsYGpSTU5NR0FAPDg3OTo2MDExMC8uLi8tKywvMS9EUkdBQklPNTxAQUNDRUhLSUxQV1dUU1dZW2AyPU4pUVFQUVJRS0tQV1peWy8wMIQtgC8xMTMwWjExMTAxMTM0MjQzMjU2Nzg3MjBEXCs2MCVXU1ZZW19mZmdob3J2Pz49QENAPjptamhkY15cV1RUUU5NR0hHR0dOUVNbXV1jYF9nOENLTFBRU1dcXFtaVU1GQz4+PTo0X1lQSDcuJycmQkE+IyIjPB4TEh48HyMaJSYpIisYHCMcJCUmKDAaIx8hPR4jKS0dICEgISYnKRohKCAiJCkDfn9/h36Cf4x+in2MfoZ/CoCAfn19gIB/f3+TgISByoD/f/SAAX+YgIh/pICofwSAgIB/iYABf4qAg3+MgAF/iYCCf5WAkH+GgAF/lIABf5SAAX+UgAF/kYABf8l+BX9/fn5/hH6EfQp+fn+Afnt7foB/o32UfgR/f3+AjX+MgAF/k4AGfHuAgIB/jX2Ifpx9lX6JfQZ8fHx9fX2GfIJ9hX6Ef4V+BX9/f359hH6Cf4Z+gn+FfgICBACAjLWCq7K4x9+BoLLOrqqjpaKjr7C1tq6lra+S/tXAxtDY55Gosqqfj5aYnpicoqCf9Z/RsdiXmYuhk4nr7tyXrZqfss3k6vv4+fLr3tnT1+z2+oCCgvDg2NHR1NfO3Obo6Ofw8Ovp4urr7Ozl5uDo6Ore3tvg4eji5uXj4+7r6uyA7uXn5+Pm6+bm3ODe4N/d5+fh3+Pi39K5mofpw7bCxcHFxci1vci+wM2YuczPwcO/uMHDucDIydLJzcvIzdDVwqjV2NrV19fVys7Eyb/EyLilqbK3w8HMs73SzcvEvcPBx82/x8S/xcHEycnOz9Dc1sWnxcbAyszU2NHU0dfZzNaA1tvj5d/V2c/g09XV1cusss3S2drNovKVko2Tj7rFzcrLzs3NxsbMy83P0c7Q0tLR0M+6tLvH5Oro6vDu7+7s6+ns6+vp5Ofo6eno6efr7Ozn6Onn5eTj4+Lh4OHaktvb2dfX19nY2drX1dbW1drX19XQzs7Qy8nHzsrLzs3LzdCAz8nIycjJxaqFnZ2em5ygmJaVlJSRk46Qj5eXlZiWloyEg4KEgYSB//v6+PPu8vjx7+y4j56fnZqamJiUlZKSkZOQkJCTkpSTlJKSjY2MiYiLjIjt1svJ4ufn5Orn4eHn49/d3+La4dzZ4uXa09Xa0NLI19jS29XWycDO+oOCgYGAgoKGg4WEhYWEhYKAgIKDhoSGhYiC/sjkhYqIiomHhYeKh4mFhISGiYqHgIKIjIjR9Y6Qi4eLkpCTkZGOl5KWkpSVlJWTloPL8ODf94P8gfz7+vb18ezu7O38g4P36ZOTmJOVkZOZl5WWlZeXm5aYl5WW15mXlpeVko6RkZORk5iAl5qYmZial8+TlJmZlZWQk5WSlpWYl5WUlJKQldaamZePl52fmpqanJeTlpSP9tLe2+Cw2+Xc093Nubm6ur3J1tXZ3NHTws7CqMfi2tjUzMvO083HzM/GxLu7xL69v7u8uLLo3OXlycK2tcTL29XY0c3R1tfR1N39hoyOhvH7+eeAs4D2m879mK+Vofa4hMqD1d3JzdTGtKmVl6Kgm42JmpuVkJGWl5CE84GJjrm3qJSWpbKVsrOxwMTAwMbL0OHd2+Dn7Of/ufKAg4OA/Pz8/fvN1dDO1NTb5+308/j//4KGh4f8hYiLjZCQj46Njo6E+4GDhIODgd6Q/feVmN+Khp6Ap6Wp6LylnYGY9tSq8YOFg/3/hIaHjpCOkZeYmJ6gnZ2dl5KVqK2uqaqnpKGblJKYmpaWi4mMkI6KgPXo6eLSqJ6Xj42H/vPqi67Nx7K5w73S3YaAsKbSzMuPury/2vie392emZ6qyICzxIb5gZez35u7p6WtsrrfkbXnqKWtvuGAT2pLYWZpcoBLXGl3Y2FdX11fZ2ZubmlmbXFkuJWDf4SHj19zeXJpX2NkZWJlaWpplGCGhq14hHmEd2WlqaFeYFRXYnWDhpOQkYuMgn53eomOjUhKS4yDfnh6f4F3fYKEhYePioKBeYSHiYh+g3+FhYd+fnp9f4d/hIKBgouHhISAhHt+fnp+gn19dXl4ent4gIF9e35+fXhrWlKWgHiBi4mNiot8g42GiJJpg5KViYyJhoqMhYiOj5iRlJKPkpOXiXifoaKen5+clZqRkoiNlYp6eoCFjZCXgYiXk5KNh42NkpSJk5GNkpGSlpWUl5ihnI94kJOPlpigoJyfm6Gjm6OAoaarrKukqaCtoqSipJp+gpaaop+ScZ9fXFpdW3uAg4CChYODfn6BgYKDhYKEiImHh4Z4eIOMo6elp6uoqaenp6Sop6ioo6Wnp6ioqqaqq6ynqKmop6anpqWkpKeiZ5qamJaXmJmYmZmXlZaVlJmWl5aQjIuOiomHjouMj5COkZSAlI6MkI+OinhUYmFiX19kXFtbWVxcXllaWV9gXWJhZFpSUlBTUVNRnpeQjoyLjpWQj450ZnBycG5tbGxpaWZlZGZlZmhpaGhnaGdnYmNjYmBiY1+mlYqMn5uZmJ2alJSalpKQkZSMk5COmZePg4mRiIqAioqGjYiKgn2JqFhXV1iAV1lbWVxcW1taW1pYWFlZWlpbW11Zr4afXmBdYWBeXF1fWl9cW1lbXV9bV1hdXFeLpWBiX1xfZmNmZGRiamZoY2VnZ2hmaVqLnIyMl02ZTpiWk5CSkYmHhIyaUVKblmFhZWJkYGJmZmRlZWZmamVmZmVmlWxqZ2dmZGFhYmNiY2iAaGxpaGVoZoxiY2ZoZmZgY2VjZmRnZmZlZWJgZJBpZmRdY2psampra2djZWRhqpSgnJ95m6KZkZaOf3+CgoSLlZWanJGPgYqEc4eblJSSi42RkoqGjI+IhoKBiYSGh4eJg3SVkJ2ch4R8foySnZeXkoyQlJWQkZisWmBhXKGqp5uAdlamcaHDcYB4gs2pdKdrm5KDipCGd29fX2ZlX1RQWltYU1RWV1FLjktPUXN6cGRlbnVecHFveXt5eX+ChZOQj5KXmZWkdZhRUlFPnJydnpx6f3p6goKGkZidnqKmpFNXWlmnWVtcXl9gX11cXV1WnFBSU1JSUYputZJaXotvVlwkXl5hiHhuc1dmpItxp1hYVqmpVlRVW1tYWV1gYmFhX19eW1pehWpXZmViXFlZX2FhYV1cXFtYVFCXiYqEdlRMSERDQoJ8eU1abWxgZGRia29EP1FEW15eQWFmaXeLW4KEWVZXYHNIaHdLikZSYXxXbFtZX2Jof1Rpf1taYGt8gB0jHCkrKy8yGx8jMSoqKiwrLDAuMzc3OURMRn1rZFxcXmM/Sk5JRD1CREZERkpORlMyUWeVa3FycWluur/ITzk4NTc/RkFJRkZHTkhFQ0VKSUQiIyRKSEdDR0xORURCQ0ZITkc9PzZBRklHP0NBRkVFQEE9Pj9FPkNBQUFHQ0BBgEE5PDw4Oj04OTY4Nzo7OD4/PDs9Pz48NzIwaWJeanZ1enZ1Zm96dHN+WHB/gnZ4dnJ2enJ1e3uEf4OBfX+Cg3VojpCSjo6NjoiOg4B2fouCcG9zdX+DjXV4h4OBe3Z/gYaHfYqHgoiGhoyMjI+Pl5GDcImQipGSm5yanZienpijgKGnr7CupamhsKOnqKaZfXySmaSdjmuUW1hVVlVnZGZiY2ZkZWFfY2NkZWdjZmtsaWpqYWRxgJmgm56hnZ6dnJuYnJyfnpudn6CgoaShoqWmoqSmpaOjpKOko6SoomKSkY6MjpCSkZKRj4yNjY2Sj5COhoOBg4CAfYWCg4uLiY6TR5OKhoqKioVwR1JRU09PU0tLS0pMTU1JS0lQUU9UU1lNRkVFSEZHRYaBe3l2dnqDfnx6a2Ruc3BubWpraGhjY2JkY2Voa2lphGiAYGFjYF9hYl+nloSFmJKOjZSPiYuQh4ODhoh9hYN/jIt+cXuEe31wfHp2enFzbWx7mVBNTE5NUFJQU1NSUVFTUU1NTk1OTE9OUUyWeIpTVU9TUlFNUFFMUE5NSUxNUEtISExMRXOMUVNSTlJYVFhUVVFdV1tVVllXWFZZS293Y2BJZjVnNWVjY19gYFpXVVpmNjZncEpKTktOSUtPTU1OTU9OUk5QUFBRd1ZSUE9NTElKSkxMTVBPUlBPTU9Ob0xLT1BNTEhJTElMS4RNgExJSU1uUExKQ0lOUk9QUVJMSUtKSIF3gn+EY3+FfXZ7cWRnaGtqb3d1fH50cmVvaFltfnh3dnNzeHhxbnBzcG9sbndzc29tbWhWb219fmxpYF9qb3t2d3Fsb3Jybm9xeTw7PTtma2RdTUKGgtDofot1f9Wud6BlfFlNT1NQR0I9Tjs8OzoyLzIxLy0uLy8rKE8qKSo9S0Y/QEVINkJCQUZHRUZJSk5XVVRUVVhWXkBQKisqKU9PUFFRPT06Oj9ARElQVFZYXFstMDIyXjEyM4Q0hDKALk0mKCkpKShFOVlELDFBNTAxMjI1R0FJWjM8ZFVIcDo9O3J5Pjw7P0FAQEJCREVFQ0REQT4+RUpLSUlGRkJAPTw/QEFCQD9AQD45NWBYUEg8MSYkIyEkREFCJDU6Pjs7PDs/QSIdGxITJCgXHyEiJywbJCcjIiMmKRggJB47HiASJCkbICMjIyQmKxkdKiEhIiUug3+FfoN/kH6HfY9+DX9/gIB/fX2AgYB/f3+UgIOBxYD8fwF+hn//gIuAjH+ggKl/mYCDf5eAgn+WgIV/A4B/gIt/BICAf3+UgAF/lIABf5SAAX+QgAF/yn6Ef4Z+hH0Kfn6AgHx7fX+Afpd9AXyKfZN+gn+EgJN/hIABf4yAAX+GgAd/fHt/gIB/h30Of35/f39+fn59fn5+fX2ofot9BHx8fH2JfIJ9hHwBfYZ+g3+FfgV/f39+fYR+gn+GfoJ/hn4CAgQAgL2Ds7a/yueIqa+4sLK0sK2pqq65vL7AurWqoKaupYjs+oL8pKyllJCcnZqUl6WtpJ3P89nMjIyIg7ro3NLhlpaevtfp+vfw8Ozr4NnNy9zu+PaCg/rq2NLMxc3Rydbu6eTo6Ozs6+Pv7O7q6Ovr6+rr6dfk5+fq6ejo5+vr6+jsgOvg4d/d5ejh4dHZ28/e2tXApJGG2Mq6tbm9xcjBwszMxMLAxcvHwsnAv5bP1NLJy8TAys3HyszJy8nHx8nQ09C3ttbZ293g2dbEv7mnpqaqzNTUyNLNyMewsszRxr2+wMS7wcO/wMvFzdXX1dzW1NHPzq6vzNHLycnW0tHQ3M3bRNnIy9fc3NnW1dXc2+TW1tCur8WpiI6PjJCGhKq4xMnGx8rMycjJxcXGx83LxsrKysvRy9XVzLW0usXl7uzq6fDx7ejrhOiA5OTl4uXn6Ofo5ejm5ePk4eHg5eCixtjb2djX3tva2NfZ19TT0dPW0tLT0c/Qz87KysrGycnKxcjIx8bGxsXDrv6XlpWYkI2OjpGTkYyMi4mHio6Lh4iEhYCBg4KC/vbw9O3v6O3t8vL17/DFnJ6fnpqZmJiXl5SVlZaXkZWUkJGAkZORjYyIh/LVzsrf5Ozo4eHl493X3Nzj2Nvf3eXb3N7Y4OHj4dvW19vU2drKyMzI3P6BgYOChICDhIiDgoaEhoOFgoGBg4GGgYSBhtzUhYaGiYaIiIWGhYqJh4uHiIeJjYeEioaJx4KPkI2IiYmTlJOQkI+RlI+QlJWYl5WK2ZmAoJuWiYX58uXm7ezr597q6+3x8f/KkJWXnpecmJOYmZaVl5WRlZSYm5qU35aZlpWXlZGUk5GXlZaVlpmdlZeX0pSVk5OUl5mTk5WTkpWYmJqSkJGP64ijn5WUmp2cnp+bl5aYlZKE8ezf3r2ww8jHwcnc3+HEytnh09bi2NjW09eA0aLL4eLe28fG1tTQ0dLQzcrX0cPBu8LK0cumu7OepLXHzc7O3M3LysnV2NfPz9LsgpegkYmDgOvXm/XNru+LnoK+2IW3jYDonNbJy8e/s6eppqKZkYyXlob+ho2SkYb7hJCYwr6vjZOYuJi1s7m/wb27vrjW3+Tm6fSf0IOHhoKA6N3o7vqDgdnT19nW3uHl5eTj2tnh+fr39/n1hYaKjJGQjoyPkJKBgoOEhIODgtWI//qTnJyo4PT4/IGOo8Wqvfvm5aeDrbjRodu5x8rd2uvp6ezu7/n78ezu6oKD9/iDh+7m64KB+OSA6cy1vMChm7uyg4OSjo/sgob4tOTW06w4xbC/zNW3vsiwstLVv/WduMG0tr/K6o3C+7WTnKi+9aPZpPuEkazfmcOthY+u34usxcWIpcPf8Y+AbEtlaG1zhU9kaGplZmZkYl9hZm1wcXJvbGhmbHJsXKKkV6Zydm5iX2doZmJkbXRuaJnCs6lweXBtk6SfnqBVVFlrfIiSjoqKhoeDf3RzgIiNiUpLkId9enZwdnhye4yGgomFiomGfIyLjoiFiomJiIiFdYKFhYeFhoaFiomIhIeAhHl9enh/gnp7bXR3b3l2d21eU1GIiIJ9gIOLjIWEjpGOioaFjYqJjoiGZ5KXlpCRjImSlpCSlZCSkZCRk5eYl4CFn6Kjpaiin5OPhHd3eHWTnZqOl5WTkX9/k5WLhIWHjIaOkI2NlpGYn56do56Zl5eWeoCbm5eYmKGbmpumm6eApJWaoqanqKOjpauqs6ino4OAlHpdXFtZXlZUbniAgYCBg4N/f4B+fn9+gYF9f4GCg4iCi4yGdnaBjaapp6Wmq62rpqinqKeno6OloqSmqKepp6inqaeopKWjqKV1jJeamZqZn5uamJeZlZKSkJKUkJCRjo2Qj4+MjY2IjIyOiouAjIyMjY6NinmjXl5eYFlXV1haXFxZWVhXU1dZVlNWUVNNT1FRUJiRi4+IjIiMjpOWmZSbiW9xcnJubWxsa2tpaWhpa2ZpaGVlZmdlYmFfX6uUjIqWl56bjYyVmJOPk5KXjpCSjZWLjY+KkZGUkYyGhpKJjY18eX+Dj6hXWFpXWlWAWVpdWVhbWVtYW1pXV1hUWlZaVVyWjFtdXmBdXl9cXVxfXl1iXF1dX2JaWGBaV4NYYWNhXl1dZWZmY2RkZWdhZGdnaWpmX5ZscWxpX1mnmpCMj5CJg36Dg4WNjZ6DYWVma2ZqaGRmZmVmaGVjZ2Voa2plmmlsaWdpaGVoZ2VpZmeAZmdpa2RmZ5NjY2FhY2dnYWJmY2JkZ2doY2FiYJ1ecm5lY2ltbHBxbGlnaWVjWqqroZ6GfIeLh4GIl52eh4uXnZKXopWSj42SjW2Om5uWlImIk5CPkpKPjouVk4iIgYiPk41wfXpscH+NkZGRnZGNjo2VmJeRlJOfWWhuZF9aVZuAj2Wrk4m8a3VkmLF3sHZou2uRiYiDfXZtcGxoX1lVWVdPlk5RVFFMkU1TWHx9dF9jZXpidHJ1eHp3d3p3i4+SlJSaY4VTVVRSkIKLk55UU4aDhYaDiImMiomIg4ORoaWhoqSjWFlbXWBgXFteX2BUTk9QUVFPToRiq41UZGR+mZmAmJdNU26XhoCqmZh0iIl8gmOEbn16jY2QkpOVnJubnZeVm5ZOUJuYUFCWkYxNTJGBSH9xXlxcTE1XWTo7RUNJcz9Adld0bGtWaVppcnRkZ25jZXRxY2E+SVNgZmlvf1Bwm2dSV2BtjF+DXIlIUGJ7VnNhSU5ge01hc25KWWp7hVGAIxspKisuNR4hIysrLC0tLSkrLjM2Njg5Ozs8QUdIP3l1OWxLT0lAPkVGRUFES09SUXGdn59mb2BklL68yL1BOzo7QkVIREVFREZJSD9ATEtHQCQlSEdERURBQURBRE5FREpFSkhDPExLTkhFSUpKSUdEOUNERUVDRUVESEZFQEKAQDc8OTc8Pjk5LjU2NDo5OzgyMDJbZ2xoa2p1dm1ten56d3JvdXR1fHVxU3yCgnt9eHV/gnt9gX6AgoCBhIeFgW93kJKTlpeOjYWCcmhnZ2SDjot+iYeIhHBwgoN4cXZ6gXuChIKCjIaMk5KTm5aNi4qJcHqVl5KUlp+ampqkmKaApJWXoqeqqqalp6yrtqusp4KAlHhYV1dTWFBMXV5jY2JkZWZgYGJgYWNiZWVjZWdoam9qcnFtYGRxg5ygnJiYnqCemp2cn5+fm5ygnJ6hoqKlo6ShpaSmoqOiqaRyhZCTkpKSmJWUkZCTj4qKiIuPiIeHhIKHiIiEhoaBhYWIg4aAiIeHiYuKh3WMTk9QUktGRklLT09MS0pJRkpMSUhNRkdCREVGRIF5dXlzeHR5fICEiIWOh25xc3JsbG1sa2toaGdqa2ZqaWRkZWdkX19eXq2SiIKKiZCNgHyHi4aEi4eLf4CDgIV5fYB3gIOGgXpzdH5ze31paHB1gJRPUlJOUEuAUFJWUE5QT1JQVFJOTU1GTElNSVGCeVFSU1RPUFFPUlFSUE5TTU1PUVVKR1BJQ2tMVVZUT1BPV1dYU1VVVldRVFhYWlpXT39dYVpYUEiBdGZfX2FaU1BUVFRZWGZbS05QVU9TUExPUE9OT0xKTk9TVlVRe1VYVFBRT0xSUE5STk6ATVBQU0xOT3NLS0lKSUxPSUlMSUhKTU5QS0hJRXRFV1RLSk9SUlRWUU1LTktKSIyNhIJtYm1vb2dseX6Bb3B7gHh5g3h3dXN1bVR1fn16eW9xend1dXJ0dHKAfnNzbXF1eHNXXl9TW2NscHFyfXFubm50dnVucG1yPENHQD47NlxvXkuMka/we4Nnkq9+sHRlrExaUlBMSUhFSUE9ODQwMC0pTikrKyooUiwrLEFPSjs+P0s5Q0NERkZFRUdGUVRWVVVXNkcrLSwqST5ESVAsK0ZCQ0M+QkJFRENDQUJRXFxYWVtbMTEyMjMzMTAyMzQshCWAJiYlPzZOPSczNEpXWFhZLS9Ab29MaVxVRGJgWFQxNjBMTllaW19iX2FgZmJiY2BgMjFiYjExW1pZLClTSyZEPDMtKyUkJyUfHyMgITkeHz0yOjk7NDw5PD5APD5CQEBFQzUfFBMZHR8iJCkXHislIiMjJzEeKiQ6HyEjJhkhJR4NHyInFxoeKR4fJCstGoJ/hX6Df5V+BH19fn2Pfgh/gH99fX+BgIR/lICCgcCA/3+Jf+yAAX+cgI9/m4Crf5qAgn+YgAF/l4ABf4aAkH+VgAF/lIABf5SAAX+QgAF/yn6Hf4N+hH0Lfn5/gH98e36Af36QfQF8hX0BfIp9kH6Cf4SAhX+CgJR/k4AIf3x7f4CAgH2EfAR9fX9+hn8HfH9+fX5+fpB9Dn5+fX1+fn19fX5+fX1+jn0DfH19k3wEe3x8fYV+g3+GfgR/f359hH6Cf4V+g3+GfgF/AgIEAIDflqm7z+eFraqsn5+go6CjrK6ytbu0srCysri3sq63xLqhiYannp6anJueoKiut7nkyOKYqe7K/+vY08K/342pydvh8vf85Ort5dvX08/d8Pn3gYH64dHR0NHQ0dPZ7Ofi5ebq6e3v7+rk5uXr7Onk5ujm4eTj5ebm39vW4+Xg44Dk4uHh4d/azLiokIn61byot7m3v5m9wL+9u7y5x8bHxMbMxMzS2c7MwrWduszU0crJzcPEzNLRztHIycvLzsvM0KzCzdTc4sevpKGvvcfNy8zS09rSz83RyLunz87IxcfCyM3Cy8i6vMzHzdLZ3tjR1NPBmJK/1NXV1M7KyMLGzIDZ2NDP3uLU0uDLxcva3tzc18qMioeXk4yKiouour67vcDIzszPz8/My8rLx8fGxsnGysnHxsjL0cvKtLW0vdfi5+fl5+Xm5uTj4OHk4ODg3+Hm4+Dg3+Hd3t7d2pPb393a2trZ2NrV1dTQz9LS1NTS0dHP0M/Kx8bGx8THyMnGxoDDx8TKxcXCteyTkY6Mjo+Njo+MhISJiYWGiYqCgoKAgf+B+/Ht8PL09P2A/fj1+vL08OvQ/KKZmZybm5yYl5iXlZWWl5KJkpaWk4vw48zG3+fd4uji4+Pf4+Xp4+La3dzf4Nve4N7c4+Dj49fc3N3Z4tbSycDL7v7+h4SDgoKFgYCEg4OEh4aDhoKD/YOGhv2F/oDmxfmHiYSGhYeJjIqGhomDi4+Kh4mKi4eNi4O/ho+TkJCQkpaWmpWOkZGHjJKUjZCPlJfPm5yenaCcmp2fmpWJgPTg2t7j+4CD7PSZl5aXlpadnZucmZqampuUmpmenoL/lpmYlpWSl5OTkZOTlYCUl5qZl5SW15mVlJaWmZebl5OTlJeZm56bkZGQjs6UnZ2dlJuam6GeoZybjMGzzcvLx9ye5Orz7ubizNXa4trc4ebW1dvUysXJ1KTD6dzV4Nfg3tbTy8XKzMzEwcfFzNfawLidmsbW293d3tbZ2N/Qys3Z3djMxMz6iZaRjIeGgoDkrYH5p92El6viwsfcy++KvYDNytTSu7atrqKnpJqVnpiVmJ6moJCFhY2UvL2xi4+QuZ6stbu8u8DBu8fNytfim+WA/4CEhoLY3+Lk5N3e0tXS1Nrd3uPm4t/m3P2BgYOFh4uPjoiIiouPj5CRkIKIhoOBgIKB1OvrgY6io9vp+ID7/P2Ck4mKuezW5reU3IKOvIH5yaO4ur/u3dTb4djs5PuIiq/0kJa4mrKnsciusL+0mI2XnaWLj4b++fLg85zax8vnzcHa8sanxMHe1MW/t8y+we+K1pPtyqPJzOKGuYX3j5mjteKV0tLwg5Cj2ZPCvIOEjqPRksPMhpSeqdmWwoCBVWFpdIJMZWNjW1tbXVpbYWJkaW9saWhpanBwbmtzgHxtXlxxaGlmaWhpam90e3+lm7x/itqrzr+knJWVlVFidH6BjY6Pe4WMhYB9eHeAiYqHSUuUhXp5eXt7enyAiHx7hISLio+TkI2FhoWJioiChIeEgYKCg4SFgH96gYJ7f4CBfn5/fXx5cGdeT0+XgXVrdnl4gGyGiYeGhoF/jYuKh5CUj5KVmpCPiIBrgI6XlJCTl46Pk5eWlZiRlJeXmJCRlHiQmp6jp5SBe3Z+jJGYk5OXmZ6ZkpWalYt3k5KKi4yLk5eRlpWJipaSlpqfo5+Ym5mMaWWMnqGioJmYlpGUl4Cjo5ycpqqinqqalpuorKmrpppmYFhiYFpYWVppdHZxcnh/hIGDhIWCgH6Afn9/f4KAgoKBgYCDioWEdXZ6gZaeoqOipKKjpaKhnqCjnqChoaKppqSioqSgoqKhnmeanZ2bnJybmZuWlpSOjZKRk5KQj4+NkI+Mi4mKi4mMjo+Mi4CHjIyRi42LgJheXVtaW11dW11aVVVXVlRXWFdOTk9OT5tOlIuLjI+SlJ9RnpqZoZmemZaItXRtbnBvbm9samxraWhqbGdfZ2tsaGOpoY+JnJqRl56XlpGNk5mfm5ySlJWVko+NjIqKl5OWlISGiI6OmomGf32EnKepXFpZWFZbVoBZWFdXW1tZW1lYp1lZWalcqlaYia5dX1xcXF1eYmBdW11XX2RbW19gXlhiXlR/XGNlY2RlZWlobGljZmVbYGVmYWJgZGqRb25wbnBsa25vaWZeWKKNiIKBk01QkqRnZmRmZWdsamlpaGlpaWxnamhubVm0aW1samllaWdnZmdlZ4BnaWloZ2VmkGhjYWNlZmZqZWJiY2VnaWtpYWJhYIZhbW9tZW1tbXBvcW1sYIJ+k5SRi5xxoaWqpqCej5WZoJeYnaGWkpaQiIaIjmqHnpWQmJOamJaVjoqNjo2JiIyGi5SYhX9va4mRmJqcm5iZnZ6TkJGZm5iOh4yrXmlkYV5cWYCadVirgK9ndIC2naXVwcBxlVmKhYuIenNsbmltbGVbX1lXWVpeWlFNTVBUcnx3X2Jje2Zuc3R2dXl6d3+AfYiOY5JSpFJUVVJ+f4KCg36AeX5+f4WHh4yNiomPiqZUVFZYWl5hYFxcXV5hXl9fYFRUUU9PTlBPhbehS1RoaKKcmoCZl5ZOX2hogaGQmYWkqU5Wb0eDZ1ReYWd9dGttdG1xbnpARFh1R0hWRVJKUl9UTVtWR0FHSk5CRD+Ae3dueVNyZGB2Z2NwfWdZZ2h9eW5oZntvboRNcTtmXlZtb31LbFCOTlVbZ4NXfXmER1Bcd1JtaUhJUFx0VHF0TFRaXXdUboA0JyoqLzMbICEoJygpKikpKissLjU2NjY4OTw+Pj5GVFdPQj1NRUZERUZHSEtOU1mCf6N0gcaVtr7AvcXFoT07QEJBRkVEO0NPS0tJRkVISEE7IiVNSUZFRkhIRkhIRTg7REVNTFJVUk9ISEdKS0pERkhGQ0RDQ0NFQkE+QEA6PYBAPT4+Ozo5NjQvKC1fVVNRXV9gaFZ1dnR0cmtqeXd1cnuAfX17gnd5dnNcbXl/f3p+g3p7f4aDgoZ/hIiJiH9+gmyBiY6RlIFxamlwfIOKhYOIi46Ig4iNiYBogYB6fH1+iI6Fi4p/gI2KjZGWmpWMjYqBXVqFmZ6fnpeWlI6RkTGeoZqaqKyhn66dmJ2or6+wrJ5kWlNeW1dTUkxSVVZSVVphaGNlZmhjYV5iYmVkZmpohGmAZmZuaWthZGtyipSXmZeXlpmcmpiVmJyXmp2dnaSin56fop2goaCeYpKWlpSVlpSRlI6NioSFiomMioeGhYOHh4SEgoWGg4eIioWEgYaGjoeKh3uBUFFOTlBSUU5QTklJS0lIS0tLQ0JFRESGQ351dXd6fIGQSpCKiJCHjYmGfLcodGtsb29ub2xpa2tpZ2hsZ1xla21oYaegiH+SkoWMk4yJgH+GjZORlYSJUIN9enZ1eIiDhYFwcXF5fIh3cmttdY+Vl1RSUE9NUkxQUE1NUVFOUVBRllBPTY9QkEqFeJxUVFBRUFJSV1ZTUVJKUVVLSlFRTERSTkNpTldZhFeAW1pfXFRYVUtQVldRUk9TWnteXV5dXltYWlxXVU5HfmpfVlJfMzVgfU9OTlBOUFVTUlJQUVJSU05UU1lYSIxTWFhVU09UUlNQUlBQUFJRT05NTm9RS0lLTE9OUU1JSEpMTlBTUkhJSEhfRlJUU0xRUlFUVFZTU0pnaHl6eXN/WISAiI6JhIJzeX6DfX6DhXl2enVua2xzVGyAd3N7eYB8eXdycHR3d3JwdXFzenxtaFhTbnd6eHl7c3Z8fXNwb3V5dGpkZHM8Q0FBPz05Yk9FmJvpfYKLsZeo1L+/bYQ8VVBSUUhFQ0M/REM8MzUwLy8wMjAtLCsrLD5NSjk8PEo8QEQoRERDRkdFSktIT1M2TSpVKistLDo3OTo6OT07PT4+QUNDRUZEQ0dJXYQvgDE0NjUyMjIzNDMzNDMrKCclJCUmJkBiSSEmNTVfV1paWVovOE9ZT2NXVFJtdjEvMhg0OzI6OjdFPDo5Pjs8NjobGiAzHh0iHyIgIyUiJCckIB8fICIgIR86PT07QCQ9OTo/NzQ8RD04QD5CRENAQkdCP0kpORccERIhIicWHRUuHx8iJCUqGykwOh4hIygZISUdHB0gJxskKh8kJSMrGSOGfoN/qH4If4CAfXx9gICFf5SAgoG4gP9/kH/tgAF/l4ACf4CIfwGAin+WgK5/kYALf4CAgH+Af4B/f3+YgAF/l4ABf42Ahn8EgIB/f5WAAX+UgAF/lYABf46AAX/Mfod/En5+fn19fX5+fn+Afnt7foB/fqB9jn4Ef3+Af4SAlX+ZgAN/e3uEgAF9hXwCfX6Hfwd8f35+fn99jnwEfX19fJR9hXwBfZV8BX18fHx9hH4Df3+Ahn4Ef39+fYR+gn+GfoJ/hn6CfwICBACAkJqiwN6Gs5iemZ+joJudoZ2uvsGzs7u6t7ext8HGysfDvLmqmqGkn6ipqKevtrvkwtW/2r2Iv5DuqqWS/ZrM9JKqzNXp7ejl7Ona19TO0uP6gIH+6dfF0c7O0c/I1+jx7Orv6dXL3eLu5tfe4ePi2uHi5N/d4uPc2NHXxNXf0cGAtaCUjYDavaSusqWtsK+3tri2vLmhyb+9tbm4xcPFyczP0c7N1t3TzMnQvI/FxdLTz9XNyMfJ0djU2cPKzNbPy9DRlaqso6Ktu77Hx8m+vsHI0dTU1NXa2s7Uy6S+w8XFwMPDyMnQ0c3KytTVz9LX0baXhomOm5+90djc19LS1d5i2d7Z1NLZ1MvW0s3P09nOzqqx0dDGoIuKj4mjvL7Bv8bFyMjPz8rLz8vFysrLyszJxcjKysTGxMHJxcrOx7mqp661weLm5ebl5OLf4+Tn5+Pg4tzc39ze3dzZ1p/G3t3a19WE1hPS09TS0s7MzcrOzsrMzszKzM7KhMiAxsfDxcLAxsTGwsDolpaWjomMioiKiIWBgISHgoOEiIaDgoH26PP19PT7+fb9gPX1+oCA9YH8+8CZnqGdm5qXmJeYmZWVlpiWkYLy6eXs1r/o6Obc2tzg3d3W2+nn49zg5N/f39ze4Nni4+DY2drS09bP0svEydeAgIL++4GA/4KAgP6Dg4aChoWGhIiEhYOHgv2AgvnE4YmIi4mJiIqIiYiNiomHiIuMjI6OjI+NifjCkJGTk5WXlZmUko+SjZGNjpCTlIuOlJPTlZ2goZ+cm5ydnJ6emZibmZyVkYqJg7uRmpSWlZSal5yemJqZmpaalpmcnZ3gkpaUlpqWk5eWk5SAlpaWl5WYmJiVkNKbmZqcnZudnZicmJaXm5mcnJiWlZXKl5qblJWA/vj27OTc08LZg/vvgv/z3bHs8u/k0N7k497a19zV1NrRy8fL0tbPpMba2dbW2uHg283AwMLNzM69v6mzxdDo4cGey9fe4Nva2NzU0t/Y5tja2tTh8YKOkIOAgoD/hN+fge3Cfo+iwqKmmcPXpYqV79TO0cu6saecnJ6on7XGuKSkqKCTiIeIj624q4+Ilbmwubq5tb/BusvQ0tHV2N/ohqK+3PrS1t/h4OHd3Nzc2+Pj4+/v8Ozi3uOChIeKi4yNkI+QkI+Xnp6goKChoaKioJyOle73/5u1s6uA57P1+Pj19oK38L/u0+a3gOj4w8v07KmppbDt283W592vzqyr1NeL04uD9pugoI6XjJy+iaKO7pWP9PvjzoLs4Ozd3L/h4dXXyby+w7W6p9fOse2D9u/mgI+qxZmRps/3pPW2k5Wfrc+HvIHd6IGTrOqmv4GDhZvPkrS2gI+huc4D9KTPgFNYXWx9TWdaV1VZW1xZW1xYYW1vZWRtbWxubHF2eX5+e3h6cGZtbWtzcnFvc3h9pJaxnsCtdJp2wouHeMdqgJFZaHh8hIqEhIuJenl5dXd9jkpMloV9cHh4d3l4b31/hoiFi4VxbH6FkYl9gYOGhH2CgoN/foGCfHl3fW93fHRtgGldWFdRjH1vdnhrcXBtc3N2dnx6bY+Ih3+CgoiChoiMlZiYl52hlI2Ok4ZkjI2YmZSclI+Mjpabmp2OlZWelJGWl21/f3d3fYuNkpOXjIyQlJaZm5yboaWanZJ0hoqLiomNj5SVmpqXk5SdnZeYnJiBaFlbXmlwip2kp6Senp6ngKOnpKCgoqCXn56anaKnnZx9gJ2bknJdW11abHt8f32DgoOAhYSCg4SBfIKChYOFgHt/gYB+fHl2fHyAgoB4b213fIegoqKjoqOfnqKkpqekoaOdnaCeoKGfnZpvi5qamZaXl5aXlpKTlJGSjoyMio6Oio6Pjo2PkI2MjIuNi46IgIyLiY+LjYqJnGZnaGFaXFxaW1lVUlFUVlNUVFZSUE5Nj4qTkZCTnJqao1GcmZ1VVKBWpqeCbnF0cG9ubGxsbm9qaWxvbGldqqekp5aGnp6bjo+VmJGPh4ycm5iTlpiSlJOOjo+IkZWPiIuIfoKKiomDgYOIU1NVqqhXVqxXUqZYgFlaV1pZW1ldWlpZXFakV1mnhJlfXmFfX11fYGBfYV5bWl1gYmBhYV1hYFujg2VmZ2dpbGlsZ2ZlZmFlYmFlZ2dfX2VkkWlvcXJwbGtsbWxsbWlqbGptaGRgXVh4YmhkZmNjaGVqbGZoaWtnamdoa2xsm2ZqaWtua2hsaWZnaWhogGpoaWhoZmOQamhmaGtqbGtmamZlZmhmampoZ2VliGVna2VqVrCrqJ6XjoJ3k1+2rV64rZ9+qK2poZGdoaGcl5ablZaZko2Jj5OWkHOHk5ORkZOanJqQiIaIj5CShYh3gYmKn52DaoqXnJ6Ym5idl5WdmaaZmZqWnalaY2NaWlqygFycblqtmWZwfJKCiIS4u4lydKaNiImFd29oY2RmbGZ1f3VnZmZeVVBPTlBrd29hXWZ7c3h3d3N6fHWBhIWEhoaKkVVneYygfXyBgoGBf35/f3+Dg4mYlpaUjouSVVdZXF1fX2FhYmFfZ25sbm5tbm5vbm1qX2Ods6dkdHNvqXufgJubl5ZQjbKHpo+hipW0lHeAhn5aVlddg3ZscHdyV21RUmhfQmRFPmtHTE5BRj9HW0BPRG5EQXNzdGxEd2x5bmpcbnNqbmdjZGxmamGAfGd6SIqIekNKTlhOTFhyiV+WblFSWWJ3THBMfINJUmGGYGtHR0lYb1BqZEdQW2p0iF15gCMmKCwwHCMhJyYoKiopKSkmLDIyLS01Nzg6Oj9BQ0dKS0pPTkVISUdKSkpJTFBVdXWYka2Ua4Vv57W1p/1rYmE2PUA+QEZERU5NQUFFQj88RyYnS0NGPUNFRENDO0U7QUVES0M1MkBJVk9GRkZISEJFREVCQUNCPT0+QTo6Ozc1gDgzMzc1X1xVXmBSWFZSWVtdXmVjWn51c2xucXdrcXJ1f4aEg4mLfHV4fnJUe3mDhICIfnx5fIOJh4p+hISMgoCDhGBzdGtrcX6AhoWKf3+ChomKjI6NlZqPk4hndnp8fHyBgoiJjY+LiYuVlo+Pko52Wk9QU1xohZmfoqGcnZ2oYKOopJ+gop+WoZ6cn6SroqJ/f5+elXBaV1xOUl1fYmJoaGhjZ2ZkZmZjXWVmaWlsZmBlZmVjYl5cYmJnZ2ZgWFpmb3yUlpaXl5eVkZecnqCfnJ+ZmJuZnJ+dmZhugI+QkYWOgI2Ii4yJiYSCgoCFg4GGiIiHioyJh4aGiYiNhImGg4mIioaFj11eX1dPU1VSUE5JRkZJTEhJSUxHRUNCe3V/fXx/iomKlUqNiI1OTY9Pl5l6bW90cG9tbGttbm5qaG1xbWhdp6Who5R/kpORg4SKjYJ8dnyPkI6HiYyIioeAfn10gHyEfnl5cmdrc3d3c3Jvd0lLTJeWTk2ZTUiQTk9RT1FQUU9TUFFPVEyOTE2Sc4lXV1dUVVNUVldVV1NOS09SUlBSUUpRUEmCb1haW1peYV1gWVdWWVJXU1RXWllOUFVVelldYWFeWlhYWVZYWlZXWlZZU1FNSkRbS1FNTk1MUk5SgFVNUVJUUFRQU1ZXV3pRVFNVWFVTV1RRUFNRUFNRUU5OTUtxU1BPUVNRU1JNUUxKTE9NUlJPTUxMZkxOUUtSQIWAfHNvZVdSd1CXj0+XjX9li5CLgnaCh4eCfXt+eHh+d3Rwcnd6cVlsdXZ0dnh9fnpwa25xd3d6b3JjZ2togH9paFdxeHZ2cnV1fHZ1fHV9dHV1c3Z2PEBBPTw8dTxoUE2my4SCiZ1+g4W8xIVrZm1VUE9MRUE8Oj4/QTlESkM4NzUwLCwsKyw9SkQ6Nz1LREdHRkNHR0RLTU5NTU5OUS43QElUOzk8Ozs7hTqAPDxFUE9OS0hFTC4wMTIzNDU3NTU1NDk+PD4+PDw7PDo5ODIzUV1LMzs7Ol8/V1hZWVowaI9UZVRbWWV4WTg8L0E1NjY3P0A9Pj87MzcpJy0qGCobHDUfIB8dIB8iJSAhIDgfHz45OTEeOTtAOTk1PDo3Nzs8PEI9QT9NSkRPKVAuTkspKxsTDQsSICgZJB8fICMkKRkkHDY3HiInLyApHh4eHycZISkiJCksLywcLoV+g3+ofgh/gIB9fH2AgYR/BH5/f3+RgIKBsoD/f5Z/7oABf5eAin8LgH9/f4CAf4B/f3+SgK1/C4CAgH9/gIB/gIB/joAGf4CAf39/mICCf5eAAX+WgAF/lYABf5WAAX+VgAF/hoCIfwV+f35+f8h+hn8Hfn9+fn59fYR+CICAfXt8f4B/oH2Qfpp/moADf3t7hICCfYV8A31/foV/Bnx/fX5+fpF8BX18fX18i30DfH19hHwBfZV8BH18fHyEfYV+g3+FfgV/f399fYR+AX+GfoJ/h34Cf34CAgQAgJ2nvOGWvPaUk5edn5qcmpukqKmsrrSyt8TIz9HJw8rTzse3rKeqqqioo6ewtcHbrcXUuLCN9Jmrq6ecdY6NhYyZo56cqqK6uM/rhYyNjaXA2OTn6dvOycfLzM3PzdPn8O3x3N7k1tji3Ojf29vU0svDysCzqp6cjf7m3tHFvYqxgLW8u8bDybmvtKWwqauysLC2u7e7t7bBzcK9wMTGxM7Kzc/NzNDh18/O08ibtMXLyc3MwszGyM3N1NPHxsfAsLKssLeIvLu+wMPFxsXHv8HFxcrP1dTU2NXOy9HNpLe3xMXJxMLAx9HQ0tDKzNS7pZWUjYyNkZyblqjM0czWztjUgNrR19Tg39vPzMnT0cvTsKfY3NTR08vKs4mFvcLEwLvEwsLExsjNz8vMycjJxsbFxMHHx8PBycnFzM/Gwb25ur7BwLv9sr2+v9zk5OXj5eLg3uHb2tfV0tjX18+R1dTR0tPW2tbU0tDR0M7Nzs3Nz8/MysrKycbGxcbGyMbFxsLEL8fEwMXHwsC+35SPkI2JiIeLiYmHiISFgoKFgoSDg4Du8/fx+fz/gIT89/n5gID9hYCA+NWfnJiYlpiZmJKTkYPz7OTkhY+Pi4yN3sPf3N/f3dzf3+Lh5OLl3t/o4uLb4N/h39nc1cvP2NrMzdC60fSAgYKFgISB+4D3//6Dh4OBhYiEhIOFgoiBhIL8/8LShIeIh4uJjIyJi4iIiomKiIaKj46PkI+PiOHSk5OUlZWXmJeAk5COjI2Nj5GRjo+Wj5CV3oyknZujnpqdm5uUmJ2cnpqcl5ubnp6R7pWdlZiWlZ2bmpyYlZiUmJWcmJidn9+Tk5OXl5eVkZmWk5eWlJeamJiYlpXYmJeXm5uam5mbmZeXl5uYmZualZOX7Mva19Pc6/Py8fHw9vPyh4eJgPbvhvSAstLi4enq4ufWx8zJysfL08/F0dPV09DEpdLv8eHl19HM1cbF0tnIsa+22uXr4NbGuMGsudDc4t7f4eTezMfi3t/U1+j+hpidjYyD9/7zxpGTvdeCmKiIvfCOkqTOifvx09TVzrymoqGVmIqVlJyvrLG3xcfMyci1rKuYlZzGusOAxsnGvrTAwMjT2uPh397Y09bW0ISXq8TV4eLh4OPf4Nn4hYuMjYyKiYqGhYiFhYmIi5OVjJyanJydnJ6en5+goY6X9YGNpb+9svKCh/76+viC1sXO8eCOq9Hx+p64xa27s7e5zdq7v9H2y7bY4dXDy/Ppnef2g5qQipCNk/fOsIhMgISGhO7Y4q3chtLru7bOwJ/VycPaqcPz0cnYydHkyPv+meiB2c7RkN6ekOy99IGbzJu32eL3i7T7i4P18ICHyKnys4qRnLfXi6W8kYBZX2p/U2uNVFRWWFpYWVpZXWBiY2RmZWhxdnx/e3p/hIF8cm1rcXJwb2tudXqDoISgr6CdcsR/jo2LhGByZl5jbXNwbndue3iElFFUV1RkcXyDhoZ7eHd1eXt8fnd3hIyHjHh8gnV6goCMhYF/enp3cXZwaWRfX1ecjpKMhoNfeoB9hICKiIyAeHtsc21scHBwdnp4fX6Di5KKhYeLjoqOi5GVlJWYp52SkpWPbYOPlZOYlomSjY+UlJqakI6RjH57eHp+XoeIi4yPkpWSlIyOkZGWmJ6cnaGhm5idmHeBgoyQkY6NjJKamZialZebhnJkY2BfX2Fpamd6l5qaoZqioIClnqShqqelmpaWop6bo4N4nKCdnJ6ZlYFaWn6BhIF+hYOCg4aEh4qIhoOEhICAf398gIB9d3t8eXyAeXh3d3p8fXx4pX+Ghoabo6OkoqWhoKCinZyYl5SbnJyUZJORj5GTlpqWlZOSk5GOjI2MjI6Oi4qLi4uJiYiJiY2LiYuJioCPi4mOkIyKiZpmY2JfXFtaXFtaWFdTVFFRVFJST1BNipKWi5KbpFNXop2en1NTo1RVVldXppJwb2tsamxubGdpaF+uqKGfXWRkYmNknomWkZWamJGOkJCQlJWXjY+ak5ONkI2PjYiOhnyBiIh+f4Z3iKBUVldZU1hVplakqaZXXIBZWFtdWlpZXVheWFpXqq2EkVpdX15gYGFhXmFcXWBeXVlYX2ViYmNiYlqTk2dmZ2pqbW1raWZkYWJhY2RkYWJoYmJnmmR1bmpybmtta2tlaG9ub21uaWttb29loGNqY2VkZGpqaGpnZWdjZ2VsaGdqbJlmZmZqamppZmtpZ2poZoBpa2hoaGdmlGlmZmtqaWloaGdmZmVnZWZnaGRjZqCCjo2EiJOVkpSWk5aSm2JhZF2yrWGwfZSdnaWmoaOVi42Jjo+PlZGKk5SXlpKKc5Smp5yflJCNl4yJk5iPfHuAlZ+knZWHe4V2gpGcn56enaahkY2em5yWmaS1YGxwZWRdsIC1ro5kapKyaXaDapjFiJWTqXC/oIqKiYR5a2dmXF1SWFdgcW9xd3+Cg4WHeXBuZWVpgHl/goOBfXV7en+HjZGOjIuJhomKhlRfaHZ9hIaFg4WCgoGfVVpdXlxbWltZWVtYWlxcX2VlX2tqampra21sbWxtb2Fnp19dbX17dbVWVICcmpqYUaqRkKWWZ4DVuJZdaGtXZWFhZnJ1YmFsfWFZa3BkWF1ybk1ncDpGQT9DQ0h+ak1CPj8/PXNpcVFnQ2d4X11sYVF0cW6AYHCOeXiAdHuGdYqMT3RBdGtrT4JdVoh1kEtZeV5qfICMT2ORUkqIiElOgmSRZE5TWGZ6UGFqUoAmJiguHSU5JiYnKSooKikpKy0wMTEwLC83Oj5DQ0NGTEpIQ0JDSk1LSUVHS05XbmSMoJGScKVxmry7t4SCYlhYYWVhXWJUXVpeYzQ1ODM8PT5BQEE+QUVER0lKT0ZBRUhCSjo9Qjg/Q0ZRTUlHP0BAPT87OTg1ODRfXGNoaWtKY4BkbGlycnNmYGJUWVNSVVdWXGFhZmtyen52cHR5eXR2cnl/goOEj4h/fH54XHN/hoCGhnqBe36BgoiJgIKDfW1qZWhqT3Z5fn+Eh4WFh3+Ag4OHiI2NkJWVjo2TjG1ycn6Dg4GCgYaNjY2Oi4ySfGZVVVNTU1VcW1pyk5WVoJmfnICim6KhqqmmmZiXpKGapIJ3nJ+fnZ+Zln9SR2RmaWdla2lqa2xqam5sbGlqaWVnZmZjZmZiXGBfXWBiX11eX2NlZ2RginR9fH2SmZmZmJ2amJqdl5WSkIyWlpaRXIaEg4WJjZKNjImHioiGgoOCg4aGg4GCgoKBgoGChImFhoiEh4CLhoWLj4uJiJJdWVhVU1NSVVNRTUtISEVGSUZGQ0RBdH2Ad32HlExPkouPj0pKkExOUFJSmZFvbWlqaGxubmhqamCwqp6bWmFhX2BhmoONiImQkYaAfoGAhomJfX+NhoR8fnt8enWAdWpscnNnaHRofJFLT09RSk9MlEuQlZBMU3VQT1NVUE9QVE9WTlFNk5lzf09UVlVWVlhXVFZQUFRRT0tJUlhUVFZSU0l0e1paXF5fYWBeWldUU1RTVVZXU1NbVFNYglZkXVtjXVhaWFhQU1taXFpbVlhXWlpTf01UTE5NTFNTT1JPTVFNUU9WUE9TV3lQUVGEVIBQVVRRUlBOUFJQT05MTXFRTk5TUlFRT1BNTEtMTUtNTk9JSU17YGdiWVtfYF1fYV5gXm9SU1ZPkY1SkGZ5gYKJioaMfnJybnN0dn54b3p+gX56cWB4iYuBg3d0cHt1cXl9c2hmZ3+CgHp0a2FnWmVvdnd3eXh+fG1oeHV3cnN7goBDSUtERD93d3JjU12s6YCGkGyPv4qbjqJppWdRUU9NRT08PTY2LC8vNT5BREdNVFNVWEtFRkA/QU5ISk1QT0pFR0hLT1JVUk9QTktOT00tMTQ5Oz0/Pz5BPz48VS0xMzMyMTIzMTAyMTEzMjQ4ODQ8Ojo5Ojo7Ojk4OToyM1cyJ4A4QUA+aSswXVxdWzB+eFlgUztPgHZULCgnNDk4OTs8PTk7PUY7MjEsKiooMC8bLzEZHRwaHh0eOyYuHBsbHh04NzYwMh4zOjI0NDEvRUZES0NGT0pMTUtMUUlUVisqEhQTExInHBo4MzwfIygeKTc6PSAnLxwfPTodID4pMSwnJwcoKzAdIScjhH6Cf6l+CX+AgH58fX+BgJJ/roD/f6J/qoABf8SAAX+WgId/goCEfwOAgH+FgIJ/jICEf4aApn+HgAV/gH9/f4+AhH+ZgIJ/l4ABf5eAAX+VgAF/lYABf5WAjn8BfoR/A35+f8Z+hn+Gfg59fX5+fn+Af3x7fX+Afp99lX6Of6GAA398fISAA31+fYR8Dn1/fn9/f4B/fH99fn5+lHwDfXx8h30DfH18hX2FfAF9l3wffX1+fX19fn5/f319fX5+fn9+fX19fn5+f359fX5+fYh+BH9/fn4CAgQAgLDM9J+yy5eWlJmUnJ2cqrCwpKapra2xs7S/2eDj4tjHxr25urWvraqrqK2zwduam8b+nfiGleWwp6N1kJWOhpOWmqCTno+FioOFioeGiqieo9DFtbmrnrKzvMO+zdnp7ff02+zt6dmow62wqaGioqCnra6qsau7vb28vLCzxovEgLq6wMbMxbm5s7Ourrazqq67wr7CwqbOzsbDxcLGztjTzcjLz9PQ1tLPz9HKjsjKycjDvMDAwc7Htayjqqe1yNLKw8TBiLnBxsfAubzAwcLEy87Pzs/Q1trW0NDS1KyevLy5wsLIyMzRz8/ErJaZnpeblIuIiIufoaict8/UzsnOgMvNxczX0srOz8XKyrmjx9jd3djW19PQtrfqwbOuvL/Bu7e7xcnMyMnNxcPBwMHExb/DycLDyMnIxcG9u8C7ubzAvZ+t39/g17y7urzBz+Hh3dze29rX1NTTzZvC09DNys/QztDN0M3PzcnNzszOz9TR0dDKysjIysnLysTExsTGgMfHxMbFw8O93ZCKjY6JiImHhoiIhIaEiIiJjY+G+f2A/4GEh/6AhfyAgIKEh4P+g4WB9POA3YObm5mYjvvu7enwio2OkZCQkY2LhpCH27/e3uTr5ejn8+fs6Ozm5ezk4+fp6OTi5drZ1tvQ2cLG4YCEhYCD/4eDgYCCgIKC/IaHgIODhP+BhYaCg4iFhYXYxf2Ih4iEgoaLioiJioaHiIyKjI6LjImKi4qJz7LqgpaTlJSUkpGQlIuIio+RkpaPiY+QjfL9oKGfm6Cgnp2bnZiWmp6fnJebnZ+emNSUl5WVmZibmpmbmZSYlpmZlpealJuZyf+A+/v6gP35gICAgYL8gIKCgIOF/4K9lJyam5ucm5mamJuak5aWmpiWl5aVk6Pt9PH29ff28efq6anZ/f+A8v38gf/5ms7r8urg08XKyb/FzdTSzuHZ0uDX2dvkr8/t5uXi4NjQyq+1vLnW08DP74PUysvBvcnLp9fp2dvf4NvPzuPr4ODa2vD5kpuNiIqBgISN7b2N7bjwjqGy3cyT4p2D9ITX9tDIysCyrKKVlY2YlY+Sm5aRhImOjIKHqq2cn6LPucfLz8i1tLe4ydPS19ve3NPX3tDX4OXq5977jqWyx9jk24KFiYuJi4qNjo6Oj4CBgP+EiIqMkpSTlZiSlpeanJuZgpLyoqCfvryx/4imgISBgYGF2azO5+CVm87AiqSA/Mesndvv1sLF0uSHyPLI1s692tKZhIWFj5Ptjffo/9jy44ST4Obg2tPU5uPl25yW1cbHvtvYvNO2ut7V5c/L0dr2kKi5qaSb+oqr56aVg8CIi63CyuPX0N2Bm7yM3c7T1u/FveLMuYm245KvzKWfJWFxhlxpdFZVU1ZTV1lZYWVkX2FiZWRnZ2ZvgYeLjIZ8e3R0dHGEb4BtcneGl3R+p9mP5G57u5GLi2J1b2hgaGZrcWdwZFxgXF5hXl5kenN0mI9+f3hufXuBiYSLjZydpaKNnp+ak3B/cHVvaW1ubHF4eXZ9eIWFhISFfH2OYYqCg4eLkYt/fnp5c3F2cm1weoGAhIZ1lJKLiYyKjZWalY+MkJWamp6alICTlZNmlZWTko2Ii4qKkY6Be3N4dH6Ok46HhoNgh4yPkI6Ji4+PkZKYmJiZmpyhpKKdnp+ifnGHhoKLi5CSlJiXmJB7ZGVpZWdjXlxcXWpscWqEmZ2ZlZqZmZGZoJ6YmpuVmZqNeZGeoaGfnp+emoCDqoh7dX2AgH16fYSHiYaIjYCEg4GAgIGBfYCFf3+CgYJ/fXp4fXl6fYB9Z3ygnp+XhYSEhouWpKGenqCfn5uZm5uXb4WQjIuIjY+Mj4uNjI6MiIyNjI2OlJKSkY2Ni4uOjpGPi4uLiYyOjoyNjoyNh5ljW1xdWltdWVhXVlNTU1VXWV1eVZOYTJtSVVedTlWiUjdSVVhbV6dYWlejoFWWXW1samxmsqilpKZiY2VoZmRlYmFeZl+bhZiWmZyWnJOhkpmVnJiboZmThJaAl5iLiYiLfYV2gJVVWVlWWKldWVZUVlVWVqRbXVpaXKxWWltXWV1bW1uRhq9fXl9ZVlxiX11gX1hZWmBeYmRhYVxcXV1diXikXGtpaWtqaGdmamJfX2RkZmlgXGJiYaaxcXJxbXJxb2xqbWlna25vbWhsbXBwapJkZWRjZmVpZ2c+aWdkaWdoaGVmaGVqaomxWa+ys1q0r1lZXFtbr1pZWFxcslqAZWxrbGtsamZmZ2loYWNkaGdlZmRkZGqVmJSEl4CTj42LaJm1uFyrtLRdubJrk6quqJ+UiIyLg4qRlpeTpZ2UoJmbm6J7kqahoZ+fmZCMdXuAf5eThJGqXpOKjIeDjYt0mqSWmJudnJORpKyhoZuaqa9nbmVhY1xgZ6qGabCWwXB9iKuid9+WacVqoKOIgoN8cm1mWlpTW1hWV1tYVYBOT1FPS1Vzdmpra4p4goSFgXVydXWCiIWIioyLhIiPhIqSlpmXkKRbZm56hYqMVldaXFtcW11eXV5fVVZUqFZZWVxiY2NkZmNnZ2lramlYY6ZzamZ8e3S9WWpST05OUat/jpyUcHHSj1FhRYVqW1Bwe2xiYWd0RGB3XmdjWGtlUFpCPz5AQmo9eXB8aHZsQUlpaWlqZWdscHJtU0xwZmVhc3podWhpfHaHfn+AgotPWF9cXVSGSFuFZVZKeFFRY3R7hYF+g0tYcFB9dnd6jH5ymH90UWqBVGV2X1iAJywyHiMyKCgnKCYqKyouMTAuMDEzMjIxLjNAR0xNSkdHQ0FCQ0RISkpGSU5balt2o9SP3Vxtw8O+wI+Vc2hcXVldYlpiWlFXVFVZWFldb2Ndf3ZmaGdfa2Zrc25vZnFucnBicnJua01WT1dRUFRWV19laGRpZXJycnFyaWx+UXSAa2xxc3p1Z2RgX1tXWVZSVWFpaW1xZYWAeXV3dHeChoB3dn2DiIaKh399gH1WhYSCgn56fHp5gH1ybWNnZG17fXdxb2tReX56gIF8gIODhIaLi4qKjpGVmZeUlpeZc2R4eHZ9fYSHioyNj4ZxWVdaVllXUlFQUlxeY19/lpqYlZeAlpiPmKGemJqclp2ajXOToKOioKCfopx7dpl2Z19jZWZlY2VsbW9tb3RsbWlqaWtrZmlqZWZqamppZ2NhZWNlZ2poVHKWlJSPe3t8fIKPn5yYmJycmpiUl5iWcHmEgYB8goWChYGCf4GCfYKDgoWGjYuMjIeGg4SHiY+Oh4aHhIiAiYqIioyKjIWQWlFSVFFSUVBPTUtHR0ZJSk1SUkl7fkKFSEpOiURLjklJS1BUTpZSU1CWkE6LXW1sZ2pntqmloKNhZGZpZmRjXl5bY1yag5CNjo2GjYCRfIWEjYmPlYmChIODg4WGeXZzdGhuZHKHTVJRTU+WVVBNSk1MTU2OUFOAUlJTmUxQUk5QVVFRUIB6oFZUVU5LUVhWU1ZUSktMU1FVWVNSTExPT05zZI9SX1xcX11aWVdbU1BQVlZYXFFLUlJSjpdgYV9bYV9dWlhZVFJXWlxZVVpaXFxVck5OTUxQTlJPTlBPTFFQUVJPT1BNUlJvkkePlJBKk4tISUlISouAR0dFRkiLRmNNU1NUU1RTT05NTkxISUpOTEtLSktOTGNkX2JiYGBdW1lYSH2Ym02NlpdNm5hYfJGWkYh+cnVza3B4fn15jIV7iIGCg4tneIqGhIF/fHZ1YWhrZ3d2aHSLSm9pbWtmbXFbdXhvcnR4eHBvfYJ5enRyenlGS0RBRUCAQkR5alqvyPmFjpSmlHLplWi8Y31oUExKRT8+OzUzLjAvLi8yMC8qKignJi9GS0RFRFNGTE9RTkZDRUZPVE5PUFBPSUxRTFBVWl9dV18zNzk9QkRJLi4wMTAxMDM0MzM1Ly8uWy4vMTM1NjY2Nzc5ODg6OTcrMVU2KTVAQD1oLTyAMC4vLzF/alhYT0VDelosLxg0Ojc2PkQ9ODo8QiM2Ni0tKykxKxwYGhsbHDUeMzQ7Mjc4Hx41NjY3NjM1NjY8Iis6MzEyQURARj8/SktUUlNRTlMqLzAgHRgXDA8eHCEeOCQjKCsvPkBAQCMnLCRAQT09REIwT0xAJSkwHCQyLSUFfn5+f3+pfgl/gIB+fHx/gYD/f+J/8IABf5SAC39/gH+AgIB/gIB/hoAIf4CAgH9/gH+GgIV/jICif4WAAX+IgAF/hYABf4mAg3+ZgIN/loCCf5aAAX+WgAl/f4B/f3+Af3+FgAF/hYADf4B/loCNfwh+fn5/fn5+f61+AX+Zfoh/En5+fn19fX5+fn+Af3t7fn+Afp59m36Hf4+AAX+SgAN/fHyEgAJ9foZ9DX9+f39/gH98f35+f32KfAF9iHyGfQJ8fYZ8gn2KfAF9k3wOfX19fn5+fX5+fn9+fn2FfoR9hH6GfQx+fn19fn5+f39+fn4CAgQAgN6Dt6ixqKelmpWbmZ6usK+nqbCuq6ijoKmvtMLX5+Db0Me6u729u6yvtbzA2pWGupWp9vb+gs6loorelY6IhpmSm5uglo3/8fOFhoWA5+eAvbm5tJ2Ysq6ssLGqorDHy7zN1trZz8DuqJ+blJmppquhpq2rpqattbO4s7C8zIi4gLXBv8TIt7LAvbm2sbK8sbu7wbq6vqDXyMrMwL3KyMjRzc/Lys3S0M7OzsnIprfNy8O0rKOooqSqur/EwsTFzs3GyNDPwo3Av8TBu7m+vcHDwsfJyMrW3NHT1NfMztC5n7i/xMG9vcLAqpeQnZmVlJKVl5SGgpWcn6CknJarzLzKgMjWx9biztTg2NbCmrLMz9HV1dLV0tbAsevw6+3furS1zL+3wLu9vbu+u7e9wcmzur22vsPAv766vL+9vr3Bxsa/rKTd3dfU2dfV2tXTyLm1tbWzvMvQ0tTPyJLY1dPRztDKy8zFy8zJx8rPz8zJztDOzdHKyMrLysrLysTExsfDgMbFyMK8wsG72pGQjoqHkIqJhYaGhYWFipKPh4mBhISEgoOA/fyEg/6A/YGCgYCBiICAgYH97anv5ev0hZibm5mZlpSYjomRk4+Li4mPkd633+bi6u7p8PDj5ubj4uLg3uHh6+jj4d/g276r2f2Dh4eIhP+AhoWBgP6Dgf2CgYCGgIOAgYOFhYWGhIeE5cXphIeHhoOBgIKIiYeKiIqMhomOiI6Ji4mIgoa44/746Mvb7YWLjYqNkIqKjo2QjJeUkZOO/Oiio5+hn5yen52WnpuXmpqel5yaoqCgiOqXmJyVnpucm5qcnpqanJeXmJWXl5mR/aCcm5mbmpuVm5yYnZiXgJqcmpuanaDdkJmcnZybn5mblpiZl5OXmJqZmZaUmbLx9uz3+Pr5/fr36NuChYiO9IKAgYKE57Ht8enNwcnJwMnk3ubl5Oj28/Xz9vTk6cDO8e7k1b7GvMLf3NjPztDW3N3aztLHx8nc0rTL8urg29fb1tvg6uDZ3+T8i5eaiYqIgPqCheOaktXQgpenxZ7BiNK+qoP1v9a7xMG/vq6Vl4aXjI+WnZqQhIWPkYaNr7CYpqfDpcDT27q5ube62trMxsPFyMrHw8bT293e4Obm1cnKzc7fi6rE1vWGjImKjIyMiPb9gISJi4uJjJCQj46PoJ6cn5uTkJDopJ2VmZiU6fbYgJOPjYeK2KTR1tOLn3q/lruDiLq/ytvhh/LK6I7UzLPPsv/j3tH6/NLZiaLyhIKjj+LRgISD8OLa5bqxvazBl9rL5eLCsdHk2Nnp3ueTna6wubO3wcHN18fP1cmIoZvY7O2SufTE8oCIk6TT+If74+qH7bXP84+Bm8P325u6o7DOgHlJaGJmYGBeV1NXVlhkZWVgYWVkY2JdXGNma3WEjYmGfnlyc3V2dW5xd32EmnBwn4OY3cvRa66LiHevcmljX21kbW5yaWCtqKxeXVxZn59bi4mJhHFthIGAg4R9d4KSloiWnqKimpCjcmpnY2dycXVuc3d1cnN6f3yAfnyGkl+CgH6Hg4qMfHd+fn58d3V6cXp7gX1/g3Gaj5CRiIeQi4yUkpSMjJKZmZaUko6OdIWZlI2Be3R3cnJ3goaKh4iJkZCMjpGRiWKMio6MiIiOj5CRkZWVlZeipp2hoqaanJ2KcYKHjYyKio2MeWhgaWZiYmFjZWNbWWVpampuaGh9mImYgJahlKCrmp+qn6GUcYKVlpibmpial5mKf6mtqauhf3l3h356fXl7fHyAfnp+gId0e357f4J8gX97en17fXt/gH96b3Wen5WSl5eWm5aWj4KBgYB/ipicnZ6ZlGeWko+OjI6IiomDiYqIh4uPj4yKj5CQkJSNjI+SkJCRj4qKi4yJgI2LjYiEioqHlmBfXVtZX1pZVlRTU1JQVV9dVldRUlJTUlRToZ5VVaNUpVVWVVRWYFdXV1iroXOmn6SqXmxtbm1ua2luZWBmaGZiYWBlZ5t7jpOQmpqTmpyQlJqamZaUk5STmpmXlZCOj3pvj6hXW1tdWKlVXFxXVKxZV6dWVVNbYFpWWFlbWlpbWl1ZnYadWl1dW1hWVlleYFthXV5fWV1iXWJfX11aUlZ7m62ml4iZp15iY15iZ2BhY2FlYWtnZGVgr6Nyc3BycG9xcG5ob2toamptaGpqcnBxX5xlZmpja4RogGpsaWlsZmdpZ2hoaWOucG1sbG9vbmhubmxwbWttb25ubXFxm2NpbGtpanBqa2ZlZmNfZWVoZ2dkY2h5mp2Vm5ydnJ2amZGWXl9iZqtdW1xdYKR9ra+pk4mPjoWNo5+lp6ansKyurK2rn6eMkamnoZeHioKJn5qWkI+PlJqamJGVgIuMi5aSfo6noZmWlZyZm5+noJqeobNia29jZWKwXV+lcmumpWh3gpN5mG29rYtrx4mQfIF9endrWVpPWlNWV1tZVU5NUVJNVnR2ZnNxgGp9io93eXh1eI6NgXt4e31/fXx9hY2RkZGWmJCHiImJkl5zg5KkWl1aW1xbW1qgpVRXgFlbW1laXV1dXGBtbGptamRhYaB2a2JhWlmlp4pcWVdUVqh0kJCManSMh1lsRUViY2pvdEd8ZG5EaGNTY1J9cW9keXhiZD9Lbj49TkVqZERDQ3VvcHdYWmFXYkluZHJ2ZlpueHJ2hnd+VVlobHJqaHBvdn93eXtvTFxPeoWMUWeNHHGIRktQWXmTTIuCgUqEaoKLWltug56FXmtdY3SAMhkjJC0tLSwoJikpKjIyMS4vMzIxMjAvMjM0O0VOS0pFQz9CRERFRUlNU1pvWWihhqPnvb1t2bq5s/B7bGJbYVVeX2VcVpuan1hXVVGPkE58e3x5Z2Z9e3p8fXVudH19cXyGioqGgH9bVFNQU11dYVxiaGVhYWZsam5sa3WCTWyAZ3FscnVlXmFkZGRgXl9VYGFmY2huXoh9fn50cnl2dYB/gnV3fYaFhIF8enplc4qHf3RwZmhgXWFvdXh1dXR9fHh7fnpzVn16fXx7e4GBhIeHi4qIipSakpeYnZOVlIBkdnp+foCBg4JvXVFZV1RUU1VWVU1LWFpcXGBcXHORhZWAk6KQnqmYnauho5hwgJOVlZmYlpiUloVzlpyXmpBvZVxtZmFjYWBjZGloZWlrcV9lZ2Vqa2dra2dnZ2VmZGhqaWZdbZSViYWLi4uSjYyGenl6e3mGlp2goJqXYouGhYSAgn5/fXZ+fnx8gIaHg4CGiImLjoeGi42LjY+OhoaJiIMwh4WJgn6GhoGKVlRSUE9WUE5KSEdHRENIU1FKTEVFRUdHSkqNiktLj0qRTU5NSk1bhFGAnJRxpJ6fplxqam5sbWprb2ReaGhkYF9cYmaZdYCDgIqIfYOHfoSNjIuIhIGDgYmJh4R9eXtpYYGYT1VVWFCYTVZWUEuaUU2TTkxJU1JOT1BST1FSUlVRj3ePUVNTUU1LTE1UVVFUUVJTS05VUVdRUE5LQEZje4qGe25+kVNUVE2AUldRU1ZSVlNfWVVVUZWKY2RfYF9dXl1cVFpWUlZWWFNWVl9dXk16TlBVTlZSUVBPUlVSUlVPUFFOT1BTT45dWFlYW1xcVlxcWV1ZV1haWVlYXFx9TFFVVFJSWVJSTEtLSUVKSk5MTEpJUVtmZ2FnZmZkaGVkYndNUFNXkE5MTVCAUYxplpeTe3J4dWx1j4mOj46OlZCQj5CRiI53eI2HgnprcGtwgn56dHRzd3h2c21waWhrd3FfaYB8dXJzenh5e4J6dXd2gENISUNFRHk/QXlgYcPhhImQnXGMbsSxhGS2bFhKSkZDQz42NC0yLS8xMzIvLCopKSYwR0o/SEVOPEmAU1hKSkpHSFdYTkhEQ0RFRkZHTFJVVldbW1VRU1RUWTdBSk9aMTIwMDExMTBWWS4vMDExLzAxMTAwMzs5ODo3MzExUzspMjApKVVYTTUzMjEyfmFVUUxDR1RRLzAYITY4PD49IkE8Qic6NCYsKDUtLywyNjAwHB0xGxwfHDQ2HiBMHjo3Njk0MzUyNjA6OTw7MzFBTEZITUlOLjA3P0A5ODs9Pj43NjMcEBMdNTg9HycvKD0eHx8iKy8ePzw1ID4tQkUxMztETzUjMCorMgR+f39/qX4Jf4CAf3x8fYCBhH8Bfot/g36Ef4J+mH8Bfv9/sH/xgAF/moAHf3+AgH+Af4qAh3+TgJ9/hYABf4WABH+AgH+PgIN/moCIf5GAgn+XgAF/loABf5WAAX+WgIx/AX6EfwF+hX/FfoZ/CH5/f35+fn19hH4JgIB+e3t+gH9+nX2hfoV/iICCf5SAA398fISAiH0Nf35/f3+Af3x+fn5/fYV8BX18fHx9jXwDfX18hH0FfHx9fX2XfI99gn6EfYR+AX2Hfg99fX1+fX59fX5/f359fn+EfgICBACAmr+FpKWvrqypoZ6pqKissKqrp6mrpqemqLO4ubK6ytTVyMjGwrexsLW8z9uakdTJr+WQx4l/paGWboCMjYWSl5uSm5+ZhoSF+5GEhISF36fIwrzDo5STqK2hpKafpcK8xcC9zMbNlo+lnpuXl6ysn5ymop6UnKWsrrS0vr/GgbSAwcfQyMrNvL7Es662vb69vb+6w8i6ldLK0MzEv77JxsnL08zDx87Nx8PBqqaT76WpqsDJwsXIvLe3t8S4u7zKzczSzc/WxKnJxLy6vb/FvcLHwMjIzdDR0tbX1NLMyczHnrDAvrGklo6MiI6Rjo+QjpCLjJKNlJGSlZmfpqSQn8iAyby5vrrR1drOq7nVzsvKysnKydDLtKnu9/bu7Ojm7d+6rbDBv7u7wMC9v768u7m7wcHCxb6+vL69urm3u7u/wLWU2Nva2djY09jZ09rb3Nnb2NnMu6+vrbCGqMTNzc/Tzc3MyM3HysnIys3MysjKy8fJycjIx8fHxcjHwcPAv8GAwMPCwr3BwL7alpCKjIuKiIeDg4OFiI6LjIOCgoSEgvj/+/+Fgf32/oCDg4SGgurf39/m5/GFmZ2enqChnJmZmZiZmpGRmZaOk5WSjI+Pkve45eDk3Nza493c3dvf4uDj4ufo4eTdy8vih/zP+YOEhYaBgYKGg4KAgPn8gYKBgoGAhIP9gYCCh4WH8a7NhYWIiYiGgv+Ch4aIh4uNiYmHhomIhoeGg4XyufDz9/Xq5er57drf64iNiIyNjYyIkpCVk4HamqGfoKKioKCcn5yeoJqYnJ6am5ign6HNl5iYm5uXlZuZmZ6Wmp+Zl5eampidlvCUnZ6fnZqem5qUmp2WmpmAl5qam5ianemPmZqdm5+cnpydmZmYmpubm5qampOS6MP4/YD+/Pf7+PaMgISBgoiLiISMh4OAwdLs3cTS09Hb593g8/n7gID4+4D59en68bC2wLvf//rr2tvj39zHz9jW3OrVz8vEwcnk6cSw4efn8Obm8O/v8d/c8fCBkpePioyAgYKMiNOaibToiZ2s77+jjqPy4YjsrNLFyMnBtqOeiZOMjJeVkY6A/omOiImyrZ68uLaJkJ+1vL3Cw8nOyb7EysrNzs7NwsnKztje2NrNx8nL0+f47ePg7t2Fm66+ydna8Pv9gIeKh4eMj4+OhJadnpqXkpOTlvStoZiYm5He6oKAopaUjI21psLk4IWogdKmx8aj0LG35MmZy4DW6oLh1+7l7LHtiu3a5eP+nYeV9Pr9gdrI2Kvn17m+3aWj6tvCuMbilMWfhoKVkZaQmKbDr7rCzr7n+sDAwcL5m8jax/Cto/2vpZuZmazOjbGpg5GnstfDkfOhq6ec+6uQ8oe11vKAVm5NXV5kY2FfW1hgX19iZGFjYGFjX2BgYWlvcGxueX+AeXp7eHNub3eBjZp1eLOro8p8pnFqioeAXmVqaGJqbG5kbXFsXFxdsWZcW1xcmXqWkIqQdmxsfYF2eX12d4+HkYyKmZOYbWNybGlmaHV1bGpxbmpia3J3en5+iYmOWX2AiI6SjI6Pf4KIe3d9gH59foB9g4qBZ5iPk5GLiIeRjYyQlI2Eio+Sjo2RenVmoXN3dIWNhouQg4CBf4yEhoWPkZCWkpScj3qUkYuKjI6SjZGVjpWWmJubnKKjoaCdmZqYcHuKiYB1Zl9eW15hXl9fXmBeXmJfZGNjZGdqb3Bmc5SAlYuJkI2fop+ae4SclpSUlJOSkZaSgXmvtbWsqaWhqaCCdXN6e3l6f358f318fHl5f4GBg318eHl5d3d2eHV3d3Rpm52bmZmYlJmblpydnZqcmpyShHx+foFfc4WLiY2Sj5COio6Ji4qJio2Ni4qMjImNjYuMjI2Ni46Nh4mEhIaAhIiHiYSIiIeVYl1ZWVhYVlRQTlBTVltZW1RSUlVVVJqdm6VYU6ScolNYW1tcWZ+bm5ugoaVbam5wcHFzb2xsbW1ucGdob2xjZ2lnYmVlaLB6ko6TioqPmZWTk5KXmpSXlZiZkZaOhIedX7KQqFdZW1xWV1dcWVdWVqOoVVVTVVWAWVioVldYXVxdpXKOXFxeX11bV6tWXFtdW15eXFtaWl1eW11dWViheZ+kpaSck5alopWYo19iXWFhYmFfZ2VoZ1qYbXNxcnNyb3BscGxub2xrbm1qa2hwcXONZ2VlaWtmY2lnZ2xmaWxnZmZpaWdsZ6Npb25vbm1xbWxnbXFrbm2Aa21tbWlrbaFhZ2hqaG5sbW1rZ2dlampnaGhoZ2JinoCcoFGfnZygnqBdW11bXmJlYlxkYV1diZmqnYmXmpehqJyfsbW2XFuws1uyrqa8s3uBhoCfta6imZmfm5iKjZOSl6aSkI2EgYigpop7np+hq6SksK6vraCer6tcam1nZGeAXl9mYptyZ4++bXuEtJaBc5rctm+9doyDgX97cmNgU1lTVFpYVlVMlU1PTVd2dGyBfHhVWWV1e36AfoGFgHV6f4CAgH+AfICBhIqNio2FgoWGi56rop2apJZYaHZ+hY6Pn6SmVFdaWVhbXV5dVmVqamdlYWJjZaZ6bWRdWVWeolWAZFxbV1iPeoeYlWZ4iodicm1VcVxfdmlTZUJwd0FuZ3RtcFZ3R3JlamZ1Sz9GcXZ7P2lgaV1xZlpkclBVfHJjWmSOYo13ZF1rWFlXXWVwZW90cmR2gGlra2yMV3J3ZYJiYIxmYldVU11xTmdeSE9aYXxxVJNaX1xYrXZio1VndYSAHyUbKCouLS0sKiovLi8xMjAxMDExLzEwMDg8OzY1PEJDQEJFREI/QUxWYGxZZqitsMx8k3GEu7i0hXNrZ2BhYGBTXmNgUlNVoV9VVFNShWiEf3qBaWNjdHpvcXNsantyeXV0gHyGYE5cVlZVVGJiWVdeXFlSV11iZWtseHl9R2WAb3N3cXN2ZWhrYl5jZWNiYmVhaG9qVoV9gX52c3J7eHd6gnVucnd+fn97ZmVXf15gYHB2cXZ7b2ppbHdxcW97fnyCfoKIfWuIhoCAgoSIg4iKgomJi5CRkZeYl5eVlJaQZW59fnNqW1FQTlBSUFBRUVJQT1NRVlZWWFpcYGFcbpJ9komEiYyeoZ6aeYOblpOTkZCPjpSPd2qdpqabl5OPmI9vYFpeYF1gZWVkaGloaGZla2ttbWdlYmRiYmFhY19hY2FhkpOQjo6PiJGSjpKTlJGTkpSKfnl7e39bZ3l9e4CGg4aEf4N/gX9+gIWGhIGEhYGGh4SFh4eIhouLgoSEfYB/gIOAhIOCiVdQTE5NTUlIREJDR0pPTk9IRkhKS0qGh4aTT0mRiY1JT1VWVlKSj5GRl5ecWWhsb3Bxdm9tbm5tcHNoaHFtYWVnZF9iY2evcoJ9g3d4f4yHhYWDiY2Gh4WHiH+FfnR6mV6uip5RVFdYUFBPVVJOTE2SlEpLSktLUIBOlUxMTVRSVJZnglJRVFVST0yTSlBPUE5QUE9OS0pOUlBRTUpKhmGAhoWFe3J0hIR9gY5TU09TUlNTT1tXWldKgl1jYWJiYV1dWVxYWlxYVllYVVZTW11fclFPUFVVUE1TUFBUTlJWUlBPUlFQVFCCWFtbXFpZXltaVFpdWFpYVoBZV1dTVliBS09RU1NXVlVUU01NS1BPTU5OTU1JSXdXaGs1Z2ZobGtsQktPTU5TVlNOVFFOTnCCk4dxf4B8iJKFhpaamU1MkpVNlpWOo5poam5qgZSQh39/hH97bXF0cnWAcW5taGZpfIJrX3t7foeDgouHiIh7d4N9QkdIRUNHQYBBRkZ6Y2O7/IaLkbmId3WZ1a9prFhWTEpHRD85Ni8zLzA1MjAvLFEnKCgySklDUE5KLzE6R0xOT01NUExCREdGR0ZGSEVISkxSVVNVUE9RUVZha2RfXmRbNj5ER0lNT1hbXC4wMTAvMTEyMi41Nzc1Mi8wMTNWNyszKyUmUVgwPH82NjM0ZmVPVVFBRlhOMTIqMzs3OEI7JjwhOD4hOCw0MDIsMRoxLjMyNh4cHzg4NiA2NjQiOjcyNj8zMT05NTQ3QzFMQTc2OjM1NDY5Pjg+QzkxNzYjIyIgKBQUFBgkHiEsHSAnJB4gJBgeIRoeIiQuLBxCJSgoKF1CNFksLjAzg3+pfgp/gIB/fHx9gIGAkn8BfoV/AX7efwF+6H/ygAF/loCEfwWAgH9/f4aAh3+agJp/BIB/f3+MgIJ/h4ABf4aAg3+HgAF/koCOf42AAX+XgAF/loABf5aAAX+WgIR/AYCTf49+BX9/fn5/sH6KfwV+fn59fYR+CYCAfXt7foB/fpF9AXyKfad+in+TgAN/fHyEgAN9fX6FfQx/fn9/f4B/fH1+fn6GfAZ9fH18fH2HfAF9hXwLfX19fHx8fXx8fH2MfAd9fn5/gIB/jX2Ffgp/f359fX5+fn9/hX6Cf4d+An99hX4Df399hH4CAgQAgMv+ua21t62roKWssLm4rqinp6imqKeoq66wtbuysLW8wM3Mw7e5wMLGxt+ckNyA38mJoIycpZ2XeOKJhoOFmZ2Up6GcjoaNiYmOivj785HK08rj5reZi5ulnJ+opaq8x8ezvMTDz4aenq+to5+np6Wlnp6XnJqeqaa0ubC7wYa8gMXDwcG+0si6w8bCvbPAwMLCu8fDxZu+ycTExb+8uLu3s6qmm5ePioKIgYSKlvKfxMHBvrzFyMbDw7q8w73GyMvNz9XUz9m2sdHAtrC4ubzCxcG+vsXIysvLyszO0M/IxMSempOMi4qLkI+Pi4yRjJGNkI+EhIKAgYqHjZWbnpeUb6O9ycLj27uXqc3S08vIyMnIzcnJrKHv8fTw8Ojr6OTj5+XYs6SmwsG9wr63wcG7xsXGxbnBw8PDwMC6u7q6ubWJ2t3c19nc3trg3dza29rb19bY2dbX1trV1NC8r7Cvr6ysq7XAzMjFx8bJysrKyIXHgMbHxsbFxMPAvcHAwMLBwL6+urq6446NjIiFiomFhYaI/4KB/oKBg4P6/YKFgf3+//7v2t3h5O73+ISMmqSkoZ2gn6Ken5WfnpuYmJmbmpmTlZeYmpaPkY2UkpOX97rl4OTd3t7j4OXd3OPj6uXi4MnS6YaIiYaFhNzYhoGGhYKEgIOGgP+A/fz//oSEhP/+/fv7goLuzN7y4MnggYGFgomGiYSGhImIhoaHh4eGjIiIiITNxfD2+Pnv8e/rgf/zgoDe29/ohYiLh4yPkIfQnpyen56enqCln5eenaGinpygoKKaoJ6D/pWYmJedm5uamZ2amZmcm5edm5ucmZncnJ2ggKKen5+clJOXmJeZmpWdm5mWmZrzipiamZugnpmdnZmal5mcm5ucm5mVlZSc+v/3/Pf7gPXE1IyJkISBgYGAh4WEhYKg1ubn4+v7/Pry9/Dw+fuGhoGB+ejVwrS4zMzw+uvg5OTp4NfIz8/N2M/h0MLOwLjH0+HfxrS249/g4N/ngPPx9ev7hISTnpyNh4mMj4n2so3uzYGRobCk0IqXkpf4iuee28e9wMCzqZiOi4+UmpWMhIKLkYqLsq+jzsTBhZOms7W2tLfBwcfNzMnQx8zR1MXAxcLFqqq5u7zF1trv7Obk8OrY7ezw8/Py8Pf+h5SktMHS4fH5g4Canp6ZlJSWgJWV97SpmJablvmDiLOUko+TkK+vlOSym6ypr9yKzMuE9+Ld0N2Hmurbzuq1/drvhYmPndb1gpfojICC/ILx8Yzpz8vo7YC/47rLyoK+yObpuafk//Df+YCMg4OFr/bbh7HCxc/plte0oKGLo6+qt4LKorbYi7nEmJeas4iNkKLvDLLBxOijnKuKjournoBzkWhfZGZiYVteYmVqaGNhYGFhYGFhYmNlaGxwbGlsbnF6enVwcnh5fYWcdXa4bL+1doN5gomFgGmyZmBfY25xaXVvbmReY2BgZGGurqhmk5uSpqmFcGVzenJ0fXp8iZORgIuQjppebGx7eHJvdHNycm1rZWlpbHNyf4R5hItcgYCHhYWFg5GFfYeKiIR4g4GDhHyHhoZriY+LjIuGhYKEf353c2poY2BZXVdZXmeeaISGhoSDi5CNjIqAgomEjI6QkZOYmJOdgYGbj4mEioqMkZOPjIyQkZOVlpaXmp2dl5WUdG1jXVtbXGBfXlxcYV1hXmBgWFlYV1ddW15iaGtnZ4B1ipWPq6SNb3mWmJmWkZGSk5WSk3hzra+wq6ukp6Sin6CgmHtsan1+fIF9eoKCfIWChIN6gYKAf3x8dHNycnFyXp2goJqbn6GaoaCgnp+cnZmZnJ6bm5qcmZqUh3x+fHt5eXh8ho2KiIqJi4yOjIuLi4yMjIuMi4yJh4eDgYWEhICHh4iHhYKCg5lcWllWVFdUT1JTV6RVVKJTVFRVm5xRVVOhoqSono6Tl52jqqlZX2p0dHBucW9yb3BpcHBubW1tcG5uamtrbG5rZWdjaWhoa658l4+WkJWXnZuflpKYl5yVkZKDkathYGFeXFyXmVxXXFtYWlhbVadUpaSopVhYV1qnp6alqFhZpYiQmpOFmVhWWldeWlxYWlldW1haXFxbXWBbXFxZhn6cpKalnZ+bl1isoVdVkJWZol5fYV5iZGZgkXFvcHFwcG9vc3BqcG1wcm9tcXBwZ29uXLCEZ4BsamhnaGxpaGhramdqaWlraWqXbW5wc25wcm5oZ2tsbG1uaG1ta2lrbKldaGlpam5taWtsZ2hlaWpoaGloaGRlZGejoJWel51Tmn6VZ2NoX1xcXFtgXl5fXXGaqaimqbW4tbC1sK+2tGFhXl61pJaKe3yPkKqzo5qgoqaeloqOjoCKkoycjoWNgn2IlJ6bin2AoJ6enZmera+zq7ZfXmlxcGVhY2ZoY7SCZ7WkZnN+h32ibYKHhM9xt2+Sf3h5eG9pXFZUV1lbWVNPTE1QTlh6eXGNg39SXGl0d3h3d3t7gISDgIR9gYSIe3d6eHtqa3d4e4OQkqaknp6loZahn6Kko4CjoqeoWmJud4CMlqKoV1Roa2tnY2NkY2Omf29jW1tYsFhab1tbWFttgHZimopuqXNoeUlnaEaDcnFrbUVQcWdfclZ2ZXFCRkhPaHc/RWdCOz13QHdyRnJnX25zQ2F/YWVlU3x5enlkWnyMhHqHRk5FRUtviWpAXWxudolWgWlhYCJHWGlja1B6Wmh5T2psUlNWYlFUU1qPYmpthV1Zal9hW2ZcgCk6LicrLS0tKSouMTU0MC8wMTIwMC8wMTQ2Ojw4Njc4OkBCQkBBRUZKV21ZY6pmvalxcnKavLWzmdxpXFtfZmdcY1xbV1NbWFhcWJ+clFZ7gnuNknVlW2p1amx1cG10fXpqdHh2ik9YV2VkX1xfX19hW1dSWFdYXl5uc2dzfEhlgGlqaWlndGhibnFva19pZ2trYmlsb1h1fHl5enRxcHNvbWRhWllUTktOR0tTW4dRbHFzcW51fHp3c2tudG92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hYeI5t7U1M/R3NHS3cTI//Lx8N3dxM3K1MTD0tHS4crEvMbS5unX1N/SqrzL2uHp7OXk7eqDh4+doZaKjIiHjILXlpTL4YSAlKW019Tc+P65iI7dnNe6vMG6uKuVkY6Ul5mQg/yLjoqLssXOkIjHhqSurK63wcTCw8bHycnHz9TZ0724vcevm52iqKOjqLa+zNPc6dzi7O7w8PDx8/Pt7vf974GDgYWBhIecoaCfqq+yuciqqJO3xtTO1caY1ZKQjJPb0oui2ZF1obDvvfSfgeCChdXZgIiXluiF8oLM34LvgfmZo/mPhv6elpKGkN7ymI7+je/d6fjJwrW9jfSG19q0rKzd2YDu39yEkpyNt8rxu8fW5viVwNbFnYeguLC8up6kweGNu+WusrrL75eAqaOAwrKXoL2xvMa0hY2lgFtrcG9kZWBiZ2ZnbXRzb2ZjXmFgYWBhZmhoZ2hwamxpa2xybm9zdneFmWxutHBwp3NxfKeLhoJ3qbC9YF9qdW1vbWNeX2N5c25rYV1ZW4mNnZalrJh5bG1weHFtf3R9i5iJiZqehapkZ2dtcHFtamFdX2BnamZwcHR0dX59hGGHgIx+goiHjIKGjo2MhoCCf4KEiYeKg3hbbmRgX11bXl5cXWJkZmpra29laGJeXWukg4WKjo+NhYqOkYyJiH6GkJGUlZaUk5ycg3+VnZaPj4uQj5OTioyOi5CQl5mZpJyXj5CSf1VdXV9hYV5eW1ZYW11bYltZWl5jamtsampmZWhugGtng5WTeXOJiImLkpaYl5WQkZB7a6Oop6Kqqq6sqaqjm6GilmWBe3RyfXx8iIaGgYOBgYN+gHt6eXx0d3V3dFidnqKemJ6dl5ufnp+ho6GeoZydn56fm5uamZuZlpSWmZWWlI6FfHp8eXh2c3J2eHh7hIWJiIaGhoKFhYKEhIaJgIuJiIaFgH5+l1xYWVlVVVVUVVRTUqOfnpeOiomXmaFXkVdZWVpia3Z2cW5ydHJxdnFxcXRzb25yb25wcW9wbHBuampsbm9ta2ppamlpZ2poZrGBmpuXmJyYn5WUk5yVjYCKmltkZGRgYF5dXWOxiqJWXFdVWFpXWFlYq6mmpaNXgFWlWV1XoY2NoaKhnpyYhY2oW15bWlhZWFhXWFhYWVddXFdVp3mUm5akpKefnp+gWJCbVKSeo52glZCWmqZjXoxxdHBvbXBvcXFwbWxtbG5vbm1xbm9xbm2GZ21mamlpa2dmZmpuaWlnaWZlZWdpbGmVbW5xc3Bvbm9vaWtsa2tpgGpoam9qbm1aV2drYWZsa21sb2tqaWdnaWhpa2hoZ2V4mZuZnpuVkoiUZmNhW2JeYF5fXWCzYV+yeLpitV5bp6dfuGK+YmNipqCTjoyPmJCRmomLtaqqrJ2dhoyJkYaFkJGRnYmDfoeToaSWlJqUdoSOl5mhpaChqKRfYWZydm1jgGRiYWRdmmxsortrdYGKpaWv2/ygcHSwb4l2d3h0dWxbV1RXWFpXUJdPUlJYeISKY1qEVWpyb3B4foB/f4CBgYJ/hYmKhnRydX5uYGBkaWZmbHZ8iI+Xopianp6hoaChpKaenqetpFtcWlxZW1xrbm9wdnl6foh0fWh1fYSAnYdmgIdYV1ZaoZ1gbpZrdbnTdIlUQW1BQmdnQERMTW9BbzxbZkBsP3tLUnRFQHVIQ0M8QmRvSEWAR3Jlc35mYGBjYKpJb25gXV14e0V+e3hLT1RSb2d1Ym53go9XdIJpU0ZRYWVtZldbbn5TcIJhY2hyh1ZJXlxPbmFkXWljZ2xoXWNsgCwvMjEsLCosLi0vNTk4NjAwMDIxMC8vMzY3Nzg8Nzg3Oz1CQkNERkdQYVFcqGpsnW5gdLnAuLOr6LC7X15iamNjXlVRVFZsZ2NgVlRQTnZ1hH6Nl4tyZGZocmlleGZpdoFxc4CFc41UWFhdYWNeWVJPT09UWFVdW2FiZWxqcExqgG5gZmxrbmJoc3J0bWVpZmptb2twaWFMX1ZRUU9NT09NT1VWWF1dXWBYWlVQT1yCamx0eXp2cXd6fXh1dmpzfn+Dg4SBgIiJcGuGkYqEgoCGhIaHfX+Bf4OCi42OmpWRiYqKdEhOTU9RUU9QTUhKT1BOU09NTVBUXFlbW1xaXF5jgGBefZORdm2GhoeJj5KVlZSQj413YJKTkYuUk5qZlpePh46QhVZraGFdZmZmcXFybW5sbW9paWVkY2ReYWBiYEyTlZqVj5ORjZKYl5manpuWmZSVmZiYk5KRj5KRj46Qk5KVkYp/dXN3c3Fwbm5yc3F1fX2Cf3x8fHl9fXp6en2AgIOCgYKBfHh3kFBLTEtISUhHSEhHR46KiIF7eHiGhpFPhVJUVFZeaXRzb2pvcW9vdW5tbnRzbm51cXB0dnR1cHVyamlqbm9sa2loaGdnZGhmZa18j46JipGNl4qIhY2CeG5/lFljY2NfXlxcW2KrhpxQVU9PUVNPT1BOmJKRj41NgEuRUFVOkXx3hYaGg4N+cH6UUFNOTUpKSUpHSEdHSUZPT0lGimR5enWFhId/fn9/R256QoF6f3l+d3V6gI5UT3VhY19eXV9dX15fWlhZV1pbWVhcWFlcV1dpUVdQVFNSVVFPT1NXUlNRU09OTE5SVFF1V1lcX11cW1tbVVdXVldUgFVTVVpVWFdJRVBVSU5VVFZUV1NRT0xMTk5PUE1NTEtYZmRiaGVgXVVvVVRSTVNPUU9QTlCTUVCUYp1VnFFNiopQnFSlU1NSiIF7c290fHNxeWxwkYaLjoKBa3Fwdmhlbm1tdmllX2dxfXtxb3VtWGVsdnV5enl8g4BISUlNTkpFXUVDQUQ/cVtjy/mJio+VmpSl3PelaWqcTFdDQ0RBQDsxLy8wMDIwLVEqLC81SlNbQjpQMkFIRERISkxLS0tKSktJTlBQTEA/Qkk/ODc5PTs7PUVMVVhdZFxbX19hYoRhgF1fZm1kOTg2ODY3OUREQD1BQUJGS0A3KEFBQ0FRRkBWMjAuMWx9NzxTPUNvdDE1KCE+IyNBQSMiIyM4HC4ZLC8YKxgxGhw0Hh40HR8eHB41Nx4dOh47ODo8ODU1ODBSIzs8NDM1S0wrVFFPLSwvMEQ2OCIjJSgqFxsgIBoPDQ8UHxkhHyEnKhgjMCgpJyUoGhgjJSMqKjorKygqLSwxNTuqfgp/gICAfXx9gIGAhH+Dfqt/AX7HfwF+5X/0gAF/jICKfwKAf7CAkn+KgIN/ioCFfwaAgH+AgICMf5KAjH8EgH9/gIp/A4CAf5iAAX+XgAF/roCJfwF+i38Tfn9/fn5+f35/f35+f35/fn9/f7J+jH8Ffn5+fX2Efgp/gH97enx/gH9+j30BfId9A35+fbR+kX+CfIR/A31+foV9En5+f39+fn18fH5+fX18fX18fIR9EHx9fH18fH18fXx9fXx9fXyFfQZ8fH19fH2IfAN+fn2HfAR9fHx8h32FfoN/hn4Bf4V+gn+GfoJ/jH4Df39+AgIEAID+j4K6uKWmuLzAxMbQ0865trKzs7GqqKSqqa2xsrGusLGvq6ipt8LS756P0/mGrv/ek9e0n5qO0/X+/P+DoqGhoJ2Qg4eKmJSPlpqQ6rfI2tbFzdOym56blJ2inqS0uM3Zy8zTx5KSmamdl5ikmaagjIuXnJuonKSckYiIhPmQzoDLv9DFxMnQwrrJxb/IucK+xcbGwsnI2YOKh4GTlJCYkJKRmpigp66dnJmUj5KPgZ3Kxr++zNDHxcPNzsLEwL/KyMrJzsvMzNaltsPIzsrKx8fHycrCwbvCxMzRyczVz8jCx8LA/PuHiI+alY6Mh4qSkI+Uj4iHjIeKh42YnZWPkoCSkoaDkNDa2tDNyM7NwsbCwMCjpeTl5+jo5ezq7ujr6+XktKXW3+fm3buto7jFxb++wcHGu7i8vr7Bt8G+wIfN1Nnc2dzb3dvb3NvZ3NrX2NXY1NvT1djY1dfW0tPR0tHP0NDR0M/OyszIx8zMy8jD+aqloJ6loqOjoKOlo6OjooCkopualZKVl/GFg4SFhYaHh4eNjI+RkZadn6eqr6+srOekqKimqqurpqGhnqOkoqCdnKCeoZ2fnJyenZ+dm5qZlJWblZGbmpicm5uWlpWVlJGSib7m6uPg2+De2dHE1/SJiYOJi4iGhIWEg4eChYTg0YKIg4WCg4KDgYX6goGC94D4hIX40Nj28fv49PP/gICC39PnhoSFh4aGg4iJhomGgYWD6a3jz+n39enw6/Lxg//u9fr07/Pz9fr7/v/u156Xm5+fnJyfnp+cnJ2XlZeemZugn5iZo53rg5OZlpmZmpmXmJibmJiam5mZmpeaoJ6R6pyhoqCenZybl5acl5iVmICVlZWdn5uYifKYnJuanJ+dm52dopyamZqcmZebmJub/bWA9/T03u+b+oyOjIiJiYSDioWDh4OChMDUiYOCgfD+h4Tt5dzX1vuB8+TQ1cPP2dvUzrP/hfjy1sfM0sy+zM3e6O7s2se91N7g1tTn0NjAtN7w7vHy693e5/qKmqGdjYCFgIuQkIPClILKf4ycqLiFzqPh892RkuGj0sC8uLewl5SQmZaVioD9jIuBiL6byNTU4qevr6uytri5vcLBvry+v5mSoKmzu87CobCwpq6vqqupoJmenqDD2+Tk4uXs9vf29fT08uyA/IH4/vqHkoyEhIaHi5KS+aKH9o2Uj6af9IDfqqOdmo7v/7SZrL+cba2ny9Xk5OafkIzLtpiJ4eP28/qGlpHtkIiapceNhYWPgYyB+oSIiMeijoSA9vXm7eu3yPrr0urL2/GH0OXrnJqQ+rvni9HZ8ISdy/rdxdjq5YO+raSqv/uZveKwt7zU8pXho6emjfaV5aq1xdL4lqOk44CuX01qaF1cZ2lsbm93eXdubGloZmZiYmBkYmVoampoa21samlrc3qFnHJxstZznd64ermVhoJ4r7q3uL1hdnZzcm5jWV9jbmllam5opYmRoJ2Pl52AcnVza3N6c3iDhJWglpeclWtscX91cXJ+c4B4Z2hydXR+cnhwZ2JgXrFij0+MfpOIhIeOgYGNiYWKfoSBiYmKhouKkVpfXFhmZmJoY2Rka2hxdnpsbGhkYmRjWW2IhIGFkJOLioiRkoqKiYeSj4+Pk5GRkZx3f4qRl5SWhJOAlI6OiZCSl5uTlZ2alI+Wj46wpllZXWFfXFtZXGFgX2NgW1xfW11aXWVpZWFkY2RcXmaXn5+XlJCTk4uOjYqKdHSjo6Wko6CpqKukpKWhon9ykZmhoph+cmp7hYR/gYKBhH17en1/gnmCgINdlZmcnZucnKCfnp+fnqGfnJuYm5mAnpeanZ6bnZuYmJaXlpSVlJeWlZSQko+PlJSSjouvenVwbXFvcHBvcnJwb3Bvcm9paWVjZmaiWFZYVldYWVlZXl5fYWBlaWlvc3d4dXWfcnV2dHl7enZycm5zdHJxb21xcXRwc3BwcXFzcW9ubWlrcGtmb25sb25ua2poaGdlaGOAg5ugmpSMlZKOi3+RrGBeWV1iYF9cXl1cX1hcXZaPWF1XWVZXVlhXWqNWVlafolpcsIqNoZ6kop6apVFSVJCMn1tXV1paWVddXlxdWVVYVpxxk4OYoqKXm5qfoVisl5+mop+ioKOmqaypmY1tbG9ycW9ucXBxbWxuamdobmpscXAgaWpxbKJZZWpoamppaGVlZmpnaGprZmZoZmlubGShbHCEcYBvb2xqbmpsamxpamdvcGxrYKNpbWtpa2xraWpqcWhoaGdpZmZoZmlprHNRmJKNeo9jsmRlZGFhYV5dYl9dYV5fX4eZZF5fXaq5Y2GwqJ+Xl7Bap5uPk4ePlpiXj3u1X7GtlouLjoqAiouYoKWmmId/lJuclpShkJiHgJ6qqK2tpYCcoKa1ZHB1cmZhXWRmZl2LcGWmZm56g41mpYPP9cR3drFuhnh2c3JuXVlVWlhYUk6YUFBNV35riZWVlm9ycW50dnd5fH59e3l5eWFaZGhudIJ4YmtsZ21wb29sZWBlZGSAk5ucmpyfpaalpaSmpJ9Zr1mqr61dZGBbWlpbXmNkrYB6ZKNdZGGBcqSRZGBdXmK4uYJ3ipCZnXJeZmpzdXRSSEVfX0xCZGhzb3JARUFlQTtGTl48OzpDOkA+dEBBRGZIRUE+endwd61oZoN+bXlmeYRMdn6BVlFNiF5wRXR7iUtZdZh6anV/dkZrWVdcapBXb35iZmp5iVZ+W11eVYpVgQlfZm11ilVdYJ0hZTcmMi4lJjAwMTEyOTs6MzIzMzM0MDEwNDM1Nzg4Nzw/hECARUlRZVJgqdB2nNSddcXIt7Ow/cqtsrlaaGZiYF1XTVVaZWFdYWZik3l8hIJ3f4p2anFuZGxybHF1b4CJfHyBfFtcYnNoZGZyZ3NtXFxna2lzZGlgWVRRT5ZNc29gdmtmaW9kaHRwa3Bla2pycXFudHR6TlJOSlhZVVtWWFhfXmYraW1fXVxXU1VVTFdsamxyfH12dXR7fXZ2dnR9e3t8f35+gYhlb3uDiIeKiYSKgIODfYWHi4+Ii5OQjIiOhoabiktLTlBNTU1LTlNSUVVST09SUFJQUlpcWVhaW11WW2CUmpqTkY6Sk4mNioiJcGWQkJKQkIyUlJaOj4+MjW1ifISOkYVqXVdlcXBrbWxtcGtnZGdpbGRubG9TjpGTlJGUlJeWlpeXlpqYlJKPk5CWgI6TlpaUlpWSkpKUk4+Pj5GRkY+KjIqMkZGPioamdG9qaW1qaWdna2pnaGhoa2dhYl5dYWCXT01OTU5RUVBRVldZWlpfYWFpbXFzcHKccXN1c3l6enRvbmtxcXBwbWtwcndzd3Nyc3N2dXJxcGpqcWplcG5rb21uamlnZ2VkZ2N8gI+UjYZ+h4N/gnGGp11bVVxgXlxaW1lXXVNXWIyHUVZPUlBQTk9OUpBMTUyMj1FUoHp1hIGKiYV+iEJDRHp2ik9JSExLSkdOUU5OSkZKRn1cdGR5goN5e3h+fkeIdHuBfXl9e3+DhYaCdWxbW11hX11dX15eWVhaU05SWVRWW1tTgFNbVn5GTlRSVVRTUk1NTlJQUVNUT09RTlFYVlCEWFtcXF1dWllWVFtVVlNVUlRRWVtVVE2AUlZTUlNVU1FRUVZPT05OUExLTUtOT4NLNGBbWEhbRJVUVFNRUlFPTlRQTE9NTk9vg1ZPUE6MnFZUmY6CeXmOSIF5cHZnb3R1dHFhgJRPk5J7bnF0b2VtbXN4fYF3amNydnVvbXdrcWVle4eGiIiDe36Ah0hNT01GQj9FREdBbV5h1omKjJCXX5R8zvG8cGycUFBDQD07OjAvLjEvMC0rUyssLDNNQlxnal5DRURBREVFRUdKSUdGSEk2MTc6PUFKRDY+Pjo+QUFBQDs3gDk5OU1ZXl9dXWBjZGJjY2RmYzdqNWdrbDo/OTMyMjI1OztlNypfOD08TEdvYDk1MzM+knxSTFtfXFI3KTtBQkFAKCUkPCciHi8tMTE2GRwbMRoaHB4iGxkbHRsfHTkeHx0nMCAdHj88OT5aLTdBQDk6Nk1SLU1MTTAtLVcwMxsmLCksGBogJyIeISQXCxEPFhobKxshLygrKy0tGSghIiMhLRsxKCkqLTIcHy1VqX4Kf4CAf318fH+BgIR/hX6QfwF+sH8Bfph/AX7Nf4J+rX/dgAF/l4ABf5eAAX+ygI1/j4CCf4qACH+AgIB/f4CAin8GgICAf39/j4CMfwGAkH+YgAF/mIABf5eAAX+WgAN/f4CGfwF+j3+CfoR/BH5+f3+GfgF/jH4Bf6V+i38Efn5+fYV+CoCAf3t6fH+Af36OfQF8hX2EfgF9tH4Gf35/fn5+in8Jfnx8fn9/f31+hn0Lfn59fn9/fn18fX6FfAd9fX18fX19hXwEfX19fIx9AXyEfQR8fX19hHwCf32IfAp9fHx8fX19fH19hH6Ef4V+AX+GfoJ/hn4Bf4Z+AX+GfgR/f35+AgIEAICO0uO0h6Srp7LExMbHztHNysjNw7i0rayrrK6wsaquqKirrK+0wNLxnpbc+uyfg5uN5sGgmY9xxP+H74eRoaKwqZ+KjpCjpqKdl5/6l73IzdjZ2tCyrKGkoaCenKWxvsbPytHZvOymmpubl5udmaSsmJiiqKasrKSqsJ2Sm42izoDUxsXIyL/Ax8XFyr/SxsXE0MfBvsHGgo6RipGRho6il5SZmpubmZ6sn5uakJCPiuHDwdHMxcnTztDHz8zKzcPJysjPyszRztDOpbXGxr/Ox8LFxsTGycfLxtDExNLP0dbT0cbKyaD3hoKIj5KWj4eFh4aKi46MioaKiIOIhIaEiFKAl7/Vwai90dPT1tHS0MnLzqSi5ufq4+Xo4uvr4+bn4tyjx9fY2uDe4uHf3MOsqqi+vLu7vcC8wL+8uL+9hsrc2tzc2NvX2drg3dna19bZ2NnbhNmA2NbU09TV1NHO0c/N0M/My8fHysvNzMrMy8f6wcHBtsTFwb++vLvCxL+5vL65uru6u7i6t7WwtrGxrLG0s7Gur6qoqaytq6yprKyb/6OipqmloKCgoaCjoaCfnZuanZ+dn56dnpyhn6Ccl5aZlo+RlpeYmpmYlZaVlZOTkpaOx+GA29vNwtHliI6LiY2HhoeHh4mJh4OEiYiD+vuCzuWHg4WIh/3+/4P/gYH8/uTQ4Pn8+On7+/j8gYD/gIH55s3P9oSFh4eIhouJhoH/xcr16OXl8Pvu+e776fz2/Pn38vXq7vny9PXy8b2WnZ+enZyan56bnJucmZeZmZ2foJ6Zm52AldeTmZyZm5eZnpmdmpmbmJucmZWZlZWfm+eTpJ+goKGgo5mbmJiVmJuelpOZnp+enI/hlZealpman56dn6Kinp2enp2bmJWUm5iV9Pf38fHVyYiJioqPjIiEho2OhYeI//SDtv+Vgd7V0sK8zfn39fX+/+LZ28/Bv8PPys7DrIOA+eLoyMfHzs/HzeTt9O3hv9TT0MzTzsjHzLijqcr7+4D19vDq8ImLl5+YjIuB+oyJ4q2d+8+EkaGqw5vPgeOL4I6S6KTSvLi1qqCnnaGWj4qDhYyJgZHl54qEhbyjtrS/vbm6vr27vry6vJLw8vyFiI6em5eQlJWfoqmrsa6loKCArOrv8Ovj3t308O7x94Hv9vb1+/n88ouB8vHz/4eOjIzwq5nhjpCSrLHa0KujnpfrjPrFq5zm6Fu/z8Ov1IeW5oe3pJX8gPScpfSkgZiJ6/OenLm3hpKfn/HnivbX6YT35oSR/dri4dCnu/f07vOwu4uB/PPxgJqUnJi2js/c7PwqksWDhsTM5PuhuPyRoK/ClMPiu8PS44ij4aKptrWhl/+4xs7d9ZSexaeogFCMo4ZXYWReZG5ub3B2eXVzcnVwbWtlZmRlZ2hqZmlmaGtrbnB3hZ5vebfSz5Bxg3fEoIaCel+VtmCpYmhzdH54cF9iZHZ3dXFtc7Bti5OUn6GinIV/eHt4d3VyeIKNkpeTmqCLrnxwc3Nwc3VweIJzc3l8fIGBeXyAcGdwZW+MgJSFh4iGgYKKioyQhpSIhoWSjIaFh41ZYWReZGRbYXNpZ2tsbW1qbndsaWhjYmNdlYZ/jo6MjpeRlI6UlI+QiY6PjZGOkJWUl5V1g4+QipeSj5GSkJKUk5eUmo6OmpicoZ2elJiXdaRZVllbXWBeWlhZWlxeX15dWl5dWVxaW1lcgFZskKOTfIaZmpmalpaXkpaYdHGio6WgoaGcpaafoaKdmHCLlZSTmZicnJmWhXVzcH59gH+Agn+Cg4F9g4Bck5+en56bnZqdnqOgnZ6bm52bnJ+enp2en52amJmbmpeUlZWTlJaTkY2OkJOVk5KWlJCwiIeHf4uJhIOCf36Gh4N9gH+AfH1/f4B9fXt6dnt4d3N3end2dHVxbm9xc3JycHN2bbRycnZ4dHJycHJxc3NzcW9ubHBzcHJxcHNwd3Z1cGxqbWpkZWprbG1sa2pra2tpaWlsZ46ckI+Ef46gYWVhYGJdW1pbX2FgX1laX15ZpadXjKBdWlxdXausqFaoV1SjgKWYio+ho6CWo6OgoVNRolNUno+Ch6BWWFpcXVxgXVlWqoCEopWTkp2om6Sdp5ipn6WlpKGil52loKOin6CAa29xcG9ubXJxbW1tbmxra2ptb29uamtrZ5Rkamxpa2dobGhqaGdoZ2pqZmVoZGVua55mc3BycnNydGxva2poa2xugGlna25ubWxjnGdnaWVqa25raWtubmxrbGxraWZjY2poY5yZlYyMhIpfYGNkaGRhX2BmZ2BgYrWrXX+8bmCimpmJgo+xr6yts7OZkpaKgH+DjoqOhXhdsqCli4iGjY+Ii52iqqWdgpGQj42TkIyLjn5vc5G7uF+zsq2orGNkbXNvgGVkXrZlYqJ/dMOnaHF+hJR6o2nLf7t2drZzjHh0b2hhZVxdV1NRTk5RT0xbl6FiYV17a3R0fXt5d3p5eHp5eHhblZWaUVJXY2FdWVpaY2ZtbHBtaWVmb56oqKWdmJinpqWmrFmmqqqorKyupV5YpaOjq1phX2CmfWmUX2Bih4GWgI1pYV5alGyzjod+scmmiYd7ZXFDSXA/WlBJdjlpSE9sTzlDPmNlRkZUWDpDR0drZz5vYGo+cnFES3pobHBsgYKAfH1/XVxJRYqGg0VTTVFPW0Zve4WPVXROTWpxfohZaYxJUl1nUnR+Z2x1gVFff1ldZ2hhVpNpcXR+jVZdcV5dgChNWkguLS0nKzMyMzQ3ODY2NTg4NzczNDM0Njc5Njs7P0FBQkNIUGtVaKzMzI1wdHHR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geir4fL27fPm7vHZ+e6A8/vy7vX7+/b98fv8gYH0vZOamZuenICdnZ6hnJucmpeanZ6gm52enJec1JKcmJiZmJyXmZibmpydmpyUmpWbm5qbnNCdn6GjoqChop2cnJeam5mfmZqWl56enZLVmpaalJSOl5+fmpudoZ2cn5qZnZqWlJfL0fT99fLkgomVkIqLiY2Lg4WEi4aI/+vLwYi9xd3o5OXi5YDy9Pv48uzt+fDh4NHk2t7RxtOg3PDy48XO0NfSz+nx7vzXy8TQ4NbdzKKop+Lm4c6tyO3l3+vn+o+VmKahmpOMjYb8gNmgm9LchZalsNqsxtHegdqRlPqv1LisqKyfm6OXlouChZCSg5GCkIDXvYGTo6GjrLm+w8XCxcTDoYCBgoCAgIKGi5CQj4+RkJeoqKSjoqKizOTp7end29ns7+/u9P/8homKh4mK8vHb2Nfa+YD8///YvZ7Dh46Sr7/qybCnoaDGqt/Eto/3mmj1h9LEsNjkiPu+k7O9joCPl4WJ/YiLr5aU/r6eg9z6mpD39Kqmlv/jmZf9n+iChoW/8N/j8D2m+OKFmpqFm5K/lazb5b7b5vyWyoOiz9Pmia3N4fiertCIobO9x9DtlKLmz87E3u/F8L7Fz+CCm4i6u7u5gGZeUmtxbY1cZWRwdHZ5ent7d3h5eG1rZ2hoa2psbWtoa21ydn6IpXKCuMDUkNWNd8mth4N8Z5OxY1ywYWVxfoZ6bGZka3VzdHR2aWOEjZuemauvloF6e3tyeXx4d4mRi42ZmZt9Ynhye3VycnJ3dnRzfnZzeXZ/eIJ6e3l1X3SMgIiJjYaRjI2EjI6Jn4OajoCQio+FiYloYGhmaW5nWmhoZ2ptcm1pa2xnaGVkYlpjW1+Oh4yHkpWTj4mOioyQi4uSjI2HjYyPkI+Vc4GOiI+QmZqWmJOVkIqSjouLj5ORmJWdoqKjnJGrnlViYWBfWFeoVlldWVVYWFlZXF1eWqVvgJism6+mmo1xfZeam5KVl5p7dqioqKejnZ2ioKOgm5WBc5qdl5SVmZuclpmZmJmVjHhzbXF5gn99fX6AhGCVnqGgn5+bnp2coKGgoZ2dnJqcoZ+fn56gnp2ampaOkZOTj5ORlJiZlJSRko+RkpCUjnthgn+BgIGCgX2BgYKBgIF8gH9+fX17gH9+eXl1dnd5eHp4dXd1dHNwcnFubXJzdXN0ep12c3B3dnJ0c3Z2dnV1dHVxa29sbHBwb25ubm9vcGxua2lpam1rbGtsb21sa2tsa21tbWV0jp+uY2VlY2BdXVtbWFlaYmBhXl5aXFpXVlVVWaeFnVpbW1lWV1pYqFWRgIaRnp6hnZabnKGhUlBQUJyipJ6ekn97klpaWVlaVFWedJahpZ2hl6Cgi6efVaClnJifqKmjppqlqFdWoX5pbWxtcG1ubm9wbW1ua2ltb25wamxubWlrkGRtaWdpaGtnaGdoaGppaGpkamZqamlpa45vcHJzc3FzdG5ubGpsbGtwgGtraGhub25lkW1maWRkYGZsbGdmaW5qam1paGtoZWNmi32PmZOUlVthamdhY2JlY1tfX2VfYLWpkIdhg4eYoqCgnqKtr7ixqKOnrqOZlo2blJeLhZNsoKytoYqQkJWQjaClorGTi4WNm5KWjG5yb5ealop2jamjnqantGlsbHl2gHFsZGZgs1ubdHOnsWl1gIinhZ2o0n7DdXfHfox0a2hqYFtfWFdRTlBVVk9cV2ldlYFXYWpmZ212fYF/fX9/fmVOUFFPT1BTWFtcXFxdXGFta2doZWVkiZuipaGWlJSkpqSiqLOyXmFiX19ho6GRjo2Pp1irq62Whm9/W2Fji4+ngI1tZF9fd4CdjJFxwHmPnVSHf2l0ckF4XkZVWEA3QUc8PHA+QFRGQGldSj5haEVDa29ST0duYkhKeE1xQUNCZK6dcn9Xf25DUlVIVlVsTVZsbWR5gpBXdU1Yb3N/TmV2foxTW3BJW2Nnb3SFVF6Ac3Jre4h2imxwdH1HWU5oaGhngCwoJjdEQ04sKyoxNTY3ODc4Njg6OTY1NDU2OTg6Pj49QEFERUpPblRxq7nTicyAdNfduLWxm7etXlajWltiZ25pX1tZYmtoamxvY1t6fIaGf5CVhXh0dnZsdHZvbXp/d3eAgIVsVmtlbWtmZmdtbGlodW1qbmlzanNpaWdhTF1ugGprcGh1c3Rrc3Vvhml9dWZ3c3ZucnNYU1lXWmNcTFtZWF1hZWFfX2FbW1dVVUpSS097cXNwfYGAd3V6d3mAenZ9eHpzenh6fn6DYnJ/eYGCjJKNjomKhH2HgoGAg4eGjIiRl5mblouVg0dUU1BNSUmOSU1ST0tMTExNUFJTUZRsUZiwm7SqnpBtfJeampGSl5JzY5KVk5GPiIiOi5CMh35xZIWHgH5/g4aKhIeGhoiDeWliXV5lb2xpaWptclSNlJiYlpaTlpaWmZqZmpSUlJOWm4SagJ2ZmJWUkIqMjo2HjYuPlJaPj42Oi42PjpKJd1p4c3d1dnh3c3h4eXh3eHN3dnV1dHp4eXNybW9wc3J0c25vbGxsaGxqZmZsbnBucHeednRwdnVwc3N2dXV0dnZ4c2ptampwb25ub3BxcHFtbWpqaWttamxqa3FubW1ub25wcnJpgHSFm6tjZmZjX1tbV1dSVFZgXV5aWVRWVFFQTk5Sl3uOUVFRT0tMUE+RTIFxd4SChH95f4KEgUJAQEB6g4V9fXNoZH1MTEtJS0VGhF92g4R8gHmBf2yDekN5fnZyd4GCfIB2f4FDQntlWl1bXF9dXl1dXlpaW1dUV1tbXFdYWVdSgFRwT1dTUlVRU09RUFFQUVFQUUxTTlNSUVJUcFlaXF1dW11fWlpYVFZVU1dTVFFRV1hYT3FWUFNOTUpOU1NNS05UUE9TT05STkpHS2lQWWFcXWpKUFtXUlNRVFNMTExTT1CZkHZrSGhodX18fXx/h4uSjYaAgId/dnNqeXJzaGJyZlWCkJCEbHBwdHBtenp5iHJsZ3B6cXBjUFhQcnNvZVdtgXx5g4KJTExLUlBPS0RGQXU+b15jwOOFio6SsHuPpNF5u2tqrl5YQDk3OTExMS4vLSwtMDAtNzdHQGZXOj5APDo9REhLS4RJgDoqKysrLC0vMjQ1NDM1NTg/Pzw7OTc3U2BkZmNcWllkZWVhZW9vOzw8Ojo9YlxQS0lNYDNkY2ZXPStGNzk7UVpvX0A1MTJEXmpXWkp6Tk5EKkxIPkA+Iz4mICYqHRgaHBoZMRkYHRobMx0aHDI2HR41NSAeHDc3IiE8JTgeHh80QVZNPj80QjwmLS4rNDg3LTExMyQmKC0bIhQaJSQmFhofJiEOERQNExsmKCgsGx4tKyskKi8pMSssKikXGhopKisrhn4BfaB+Cn+AgH98fHx/gYCFfwV+fn9/fv9/rX+Cfod/AX6NfwF+kn//gJGAAX+zgIR/mYCDf4iAAn+AjH+EgIl/h4CNfwGAjH8EgIB/f5mAAX+YgAF/mIABf5eAhn8Bfo9/xX6Kfwd+f35+fn19hX4KgIB+e3t8f4B/fpF9BX5/f319tH6Gf4d+AX+Efgh8fH5/f399fod9Cn99fn9/fn58fX2FfAJ9fIp9AXyFfRp8fX19fHx9fXx8fX19fHx9fXx9fH19fXx+foV8i32EfhF/f4B/fn5+f39/fn1+fn5/f4V+gn+MfoN/hH4CAgQAgKajoonWi9K33Km3v9LU2dfSz83H0tfOysbEuLe5s7KqqbC3yNb/o6fX0YCf+7iK7tGdmJF+qeP9iYeSlZGst6+jnZKTpqeen5mlgq2+y8zl4u/Wuqmvqq+jp6uosMTRysHQ09KHm6mjo6Wjn52om5yYmqGnqKmwr6Oms7a3k7XEgMjEzcXRydDAx8+52s3a1tDN1dLLzsC02ZefpZ+XkJmdoJScl5qXnI6Rj42TjYr6g+a6xsvMxsXHxcO9w73Hw8PEy8nTyMLBv8TM0JKtu8e+wcPMztXRzMfFvr/BtbvEw8rS0svPydLZo+v18vP7gfb2gP+DiIyKhoOGh4H3os3fgOnf1tTT08m/tp6iw8rQ0KGx5+7w7Ovq5ePk6Ojl4qHA5+nm3eHe19zj19nd2trc2dvb28usqKSmv7nCi8fW2t/h3tra2djb293f2NfY2NjZ2tjZ2tfY2NbW0c/Ozs7DwsTHyMbJxMzMxsnIxsrFxImvw8C9usC9vrm8vr3Au7q9OLi4t7a0tbe1urm1t7a2t7Surq2traqrqKqqrq2pp6usrP2To6aop6KhpKOhoaakpJ+dnZmamZ6dhZwonp+ZmJuanJqUlZKYl5mXmZiZmY/26ef0jJGMjYuMioaGhYeCioGDg4SGgIiIg4WEiIiEiIaF6cj7hoSFhIGB3c7r8vj4+4D+7fuCg4OB/oD48fv4/PTp6uLzzdLkhIT+wMTo7fPv9YDq3vHe64Hy8eH37/SAgviAhIOD/f66kJuXlpWenZufoKCenJmcnJmcnZ+hnZ+cnIfql5aalZKXlpiXmJ2dn5qVmJqbgJyamZ2SltGhn6Okn52dop+amZ2cnJyfoKGfmpuXmpjPmJ6cmZiUkZmdm52VmaCgnp2am5yblZuNnPb4/a7wk5KPi4uOiPHz+efcv7/Iy9bh5c6u6ePq6OPr6enw8Pb59/bu6OPZ1c3Sz9bW09GXutzX4tjd4s7W+vn35Mq6sLTNgLq7vtvk3eLk49/azb/f2t7W3/qRnZ2fmpaLjY6OjYDNpKPR2ISZp7DstsnK8oDRipH/vc+uqLGepKqil4yBiZSRhIn/+s7Zj5CWmqObpp+dmJihsLvBq5iKhYGBgYSKjIuKh4SJvcTO087FvLnX3OHi5eLe4ufm6ebn7vL+goP/gID85OTi3+3/hczS1dO4oouUqam096OL17uin6G31su6wICFxJqE1ezW19DUgI+1no6EoqS+uaG2pLOrpISrxpns9oChsLeZw4WflpSNg4HlkJL79YSCxLuFo6yc65OrisuPrOqXlM+n4eHyj7X2suHj7I6szuy8msHPk42UmcHXF/WfmtDKz930jJef2ebwhJejgsW3r6mngF5hYlGFWJeHimJnbHx9f3p4eHl2e313dXJybG5wbW1panB1gYmye4m1sm+O4J940LKFgXtsiai4YmBpaWd6g311cWdod3hxc213XYCOmZiqqLOgiX+BfYJ4e399gpKelYyZnZ5gcoB7ent6dnV+cnNvcHh/f36DgnZ4hIaFaH6EgImFjIWTjZKIj5R7mI+al5OSmZWPlIqAlmtvdG5oY2lrb2VraWxpbWFkYmFlYF6pWZ2EjY+NhomNjYiBiYaNi4uJj42VjYmJh4yRlGZ8iJKMjY+WmqCcl5ORi42NhIiQkZWdn5ugmZ+kd5+loqOoV6akVKhXW15dWlhbXFireJyrgLKspKCio5mPiHR5kpaZmHJ6oaeopaWkoJ+ipKKhoG6FoaOjmZuYkJWcj5KXk5OVk5WVlox2cWxtfXaDXpKcnqKkoZ2dnJyfn6CinJyen56en52goZ6fn5ybmJWUlJWKioyPj4yRjJOTjpGRkJKNi196hYKAe4F+f32AgYGCfX2AEHp7e3p4enx7f357fHx9f3yEdIBzcnRvcXN2dXNxd3Z3sWp0d3h3c3N1dHFyd3V0cG9vamtrb25ubW1ubnBwa2ptbW9uaWpnbGtubW5ucG9osqimrmRnYmNhYmBdW1peWWFVWVpcXFxbXV1YWllcXVldW1qdiataWVpZVleSgZKZnp2hUqKTn1NVVVKgUJeXopyln4CRj4eVgIqXWFenfoGXnaOgpFaYkJyMmVWbm46kmqBXV6VUV1dXp6h8Zm9ramlvbm1vcHBubWttbWtubm5wbG9tbVyhZ2dpZGNnZWhnaGtqbGdlZ2pqa2poa2NnjnFucnNvbW5xb2tqbW1tbnBxcW9qa2dra5JqbWxpaGRgZmppa4BlaGxtbGtpamlpZGphZZyfo3CrZmdlY2NmYKmsr6KbiYiLj5afoIx6p56loqKmpqapq6+yrq+po5yRkYuOio+Ljotmhp6ZopmZn4+Ur6yrnId+d3iMgYKClJuWmpuakomBf6Cbnpies2dvcHJwbmZoZ2hoXph4famxbXiAiLWPn4Ci3X+7cHbNjIpxbHJiY2VeWVVPUldUTlWxto6XZGFiYmRfZWFgXFxkcHl/bF9WU1BPT1NXWVlZVlRZfoOKjYqDenaNkZaYnJyZmp2bnp2fo6avWluvWa+Yl5aUnqtaiI2QjXx5YmFwcHjJeWOVeWBdX26ki4WVZWeUplSHmYmIf2x2P0ZXTUk6S0tZVUlUTFRQSjhLWUNnbzZGTlBCXDlIRUhDPT12Q0Vtcz0/Y3pbdYJyp3KBY4xgYoZNS2dYe36JUmmTZ3l6gk5keoVqWmduUU5OUGl2i1tZdm9xfIpPWF14gYZKU1pIa2VgXlyAKCsrJ0QsUUxKKysvOTo7OTk4Ozk9PTk3Nzk3Oz09Pz0/Q0ZMU3lce6ura43OjnTh5re0rqC8rLBbWV1eXGdsbGlmW1tsbGRpYm1UdoCEgpGOmop7dnt3fHJ1eHR2g4h/dn+EhlBjc25ub25paHFnZ2NlbXRybnNzZmh2dnJXZmiAbGhtZXd0eGt1emB8dH18eHiDf3d+dWx/XWFmYlpUWl1gWF9cX1xhVVdUUVVRT45LhXR7eHRuc3h3cmt0cnt6eXd8eYF5dXV0en6DWm15hICBhIuNko6KhYR+gYF3eYOGi5SYkpiSmp1thIyLi49JioZGj0xQUlFPTU5QTpt1nK6Atq6loqWjmY+GcXiRlZqXammIkZKPjoyIiYyOjY6PXnGLjI2EhoN9gIl4fYOBgoOBg4SEe2VjXVtnYnBSjJWWm56XlJSTk5iZmp2UlJiZmJiamJqbmZmZl5eUj46Oj4SDh4mIh42Hj4+JjY+Mj4eFW3F6dnRwdnN0c3d5ent0dHhkcnR0c3BydXV6enZ3dXd7eG1sbW1sa21maWxwcG5sc3N1sWt0dnZ2c3N1c21wd3Nzbm1taGlpbm1tbGxub3Fxa2lubnBuaGlla2xxb3FxdHNst6imrWRnYmNgYV5aV1ZaVF5OUoRWgFVYWFFUUlZXUVVUU5CBnVJQT05NUIRseH2DhIVEhXV/REVFQX4+dHSEfYJ+cG9mdWVzgUlHimdpeX+Df4JEeHB6anZCdnZpfHN4Q0N+QENDQ36AYldfW1hYYF1bXV5dW1pYWllWWlpaW1ZZV1ZHfVFPU05OUE5RUFBTUlRPTE9RgFJSUlBUS1F0XFlcXVhWWFtZVVRXVlZWWFlaWFNTT1NUcVNVVVNSTElNUU9SSk1SUlBQTlBPT0pPR0VkaGxMjVZXVlNSVVGJjZOGfWxrb3R5gYFwYIJ4fn59gYGAhoeLjYeGg395cG5oaGVoZWRiT26CfYV6en5tc4mCgXZoX1pbgGxjYF9scnByc3FpYFtfe3Z4cXaBSVBOT05NR0dGR0ZBcGZyzemGio+StYCOnuN2sGZqtmxWPzo9MzU1MjAuLC0xMC01cX5dY0RAQDw8NDg0NDIzOT9ESDw0LiwrKywuMjMyMTAvMklNVFZSTEdCUVVbW19hXFxeXmBfXmJkaTY3gGo1aFRRT05UXzNQU1RTSzcoNUA/Q3NMQGhKMzAyPnlhUF0+QWJkJEZaT09JQiAgIiEWHh8fICEfIB4gHBwZHyIcLTAZIB8fHSMbHh4dIB4dKyEgPDoeHjVAM0BDOEwuQjZLNTlBKysvICgmKBcfKR8oJycWGR8oIR0WEgwOEBQjGCcrHB0qKSstLxcbJi4vLxgaGxgoJyYnJoR+BX1+fn59nX4Kf4CAf318fH+BgIV/g37ffwF+ln8Dfn9+uH+FfgV/fn5/fol/AX6Tf/+AkoABf6+AhH+fgIN/hoCHfwSAf39/hIACf4CNf4KAiH8BgIV/AYCGfwOAgH+EgIN/moABf5iAAX+YgAF/mICFfwF+h3/Ofox/BX5+fn19hX4KgIB+e3t8f4B/fpF9A359fbZ+BH9/fn+HfgF/hX6CfIR+A3x+foZ9Cn99fn9/f358fn2FfIR9AX6PfYJ8kH0GfHx9fXx9hH8Gfn9/f35+hX2EfoR/Bn5+fn9/f4V+gn+FfoJ/hX6Cf4R+hH+FfgICBACAh5WOr4aio47VwfuzvsbW0snNz87S09THy8XHw76ysrK3vsXSga+v0LLhmoHOivHUm5eSgaTM4vWKlKuoo7asl5efk4ynqKqYm4azsMXR3PDr49TCqZ+lqaWutKq+wr3FzdzWyoWtrKSiop6ko6SjqKSppaWmp6SttK2vsLSKtr2AvsXB3NHTx9HXyL/J28LN1trY18rKxb3vnZWbl4+UmpSaoJeYopKUlYyXl4+NjIyPh/LIxsXL2MzGx8O9x8fFys/Fw8fIxMfGxci/q/yuvLy+wsTHzd/WzcPHwK+4vsjLzNfP0dHUz9rcxoL8/ujl3eHg+v+AhI+QhvCFs+Xf5eeA5dTLztXX0MnJubeho7Krwuvr8vLw6OTh4+bn6cKg1t/e4Ofp3+bh497d2tfa39zb2tve2dXT18uvqYig0N7f4eHi4N7c3Nnb2dnX1NbU1dTZ2NjW1tXW1NLP0NDNzszLy83Oz9DKysvNycjHxcPB97i9vb26ury5vri7uLe5trgZuLe4t7S2tbS1t7i3treysbCvrKmvqqeqqoaoFaalnt2kpaKioZ6doKSkop+boJuVm4ScgJ6cnJ6cnJyanaCdnJiXl5SZmpuYjPbx7P6NkJCRko6NjYuJiIaGg4aHiYmDg4GGgoWFgoaGhYCCgIaFhYH6z8T9hOzR3ffx7fP1+Pr///+Bg4H//oD68PX19/nt8Oz0+vb/8cS+ruTo5+/y9+fn5vP56O798+P5gvv3hPL9hICBgP/2xJWYmJOVl52bnaCcmZqam5yemp+fnp+gnZ2aruLp6vT9iZWVl5ifnZmdmZmYl5eZmZqZnorxoKGdoaKgoJ6fnZqfn5+em6OfoZmWm5qfwZicnp+blJeZnaCbnJudnJ+dnp6bmJiamKj1/uTBhoaB7Nzg29DGv8zg4eLk6OXpgOrr5a7M+PDr6Ov18vj48PH27/D38OTj3NHQ3OXh08+nqeDl5ODt9YGD//ngzbimnKLD6/b47uvc4dvX5tnY0rnT3dngi5mipqGai4eQi4yD47SZirjuiZqrt4W+ysTzgbToj4nw8sW/rrKssqielJaXl5qew73wlpidpKWrrZ6ggJmWk5KXk6e0tLXBua6imo6Oj4uQka7Dy9TN1M7J2+no6N/Z5Ovw6uzs8fL3/YCCg4OE5uDk5e2DiIDLysrKtpbwlbKtsOekodjGnp2brIvPt8Du89GU76fy6efi0sXo2rGotMjOoZKqtqSXspO4v8Sir6yEhL2do56Uk5K5qqq4SLSlmIyvpJ/wk9/5m8imkYWm/46IvbO+5sPd9Iuv88zd0+yEpsqE3c+agZuEnpqp14Sfjubu94GRqpbNpoGOn66O3N3i4cnAv4BLYlRkUV9bV5WMpWptc3x7d3p8fH18enN3dXd3dW1ub3R5f4hbhJOvlsiNc7J40rOFgXtthZWjsWNre3l1gnxsbXJoY3t5fW1vXoOAk52ks6+qoJF+dXp+eoOJgJCSjZKXpJ2WYYGBenh3c3t5enl9en16en1+fISJf4CChWR/goCDhoKXkJOLlZuPhIqdgpKXmpqaj46KhqZvZ2xpY2ZoY2pvaWtxZWVnYGdmXl5eX2JcpJCMi4+cj4yNiYSNj42RlYyLjY6KjIuKjol5sH2JiouOkpSaqZ6UjJGLfIOJk5eXopuenZ+Zo6STWq2tnpqTlpanqlZYX2FaoF2Isq2zsYCvopqcoaWdlpmLiXZ3g3mIpqarraukoJ6fpKSkh26RlpeZnqCXnZqal5WTkZWYlJOTlZeVko2Tind0XHWYo6OjpaajoJ6fnqGgn56ZnJubm5+dnZydnJuZl5SVlpSVkpOTlJWVl5STlJaSj5CPjYqtf4GAgH9/gX6DfX97eXx6fYB8enx7eHp5eHl8fXt8fXh4d3ZycHZyb3NycXFxcnJ0cW9tnHV2c3NycG9ydHRxb2tvbGhubm9vbm9tbG5sbW1sbnFvbWtqa2lub29tZLGtqbVkZGVpamVjYl9eXVpaV1peYWBZWFdcWFlaWF1cWlVWVltbXFmrjIOmV56QkZuYlICbmp2foqGgUVJQoKJTm5GXmZudlpmTl5ybopl+fnOWlpadoaOWl5Wcn5ObpZqJoVeloVieplZUVaiihmtrbGdpam5sbnBta2xsbG1uanBubW5wbW1rcZCanaWrXWVkZmdua2ZraGhoZ2doaGppbWCocnJucXJwcG5vbWtub3BvbIB0cHFpZmppcYZoa25vaGNmZ2twamtqampta2xsamdnaWZyn6KQg15fWqWYnpiSiYOMnJ6en6Ogq6ioonqTsaulpKWtqq2uqamuqKywpp+dloyGkpiUiol2eqWnpJuosF1ftLCbi3x1bG6Coaennp+WnpaQmY2Oj32WnJibY293eoB0b2NfamVlXqOFcGmYwGx4gYhjlaGd4YClunNttK2Gf29wbW5pZF1cW1tfZYN9pmdmZ2pqbWxgYVpYV1hcWmZvc3d8eG5nYVlZWVVYWnKEipKLkImFkZubm5aTnqSlnJyfpaersVlbW1tcoJaXmJ5XXViKiYiHe3C2YnVydMB5c4CXgl5eXWlrkISWuL6hgMNxmpGQjoRodmVUTlNgZEg/TFRLRFA/UVVbSVFQOjtaRUtFQ0A/WUtMW1BJR0BUT1B1Sm19V4d2a1hbikpFaFhWdXF9iU5kjXd4dYFKXXdKfHVbSlhJU1JdeEpbUYKHjEhSYld3Y0lRWmFPd3l+f3BrbIAlOSgpJCkqLFFNWDItMTo7Ozw9PT48OjU4Nz0/Pzw+PkJGS1NBaoaokMWFaKNz3um4ta+iwJuepltgaWhiamhiY2ZcVW5qcGJiU3d2hIqMm5aQjIV4b3V3c36DeISCdnp/iIJ+UXFybm1uaXBsbWtwbXFucHRycHd9cG5weFdlaIBmaWR3c3ZweX11aXCAZHd9gIKAdXV0dYphW15bVFdYVVxhXl9kWVhZUlhXUU9QUFNNiX15eHuFeHd5c2x3enh8gXp5e3t5eXh3e3ZqmHF6fn+ChoiPnpCFfoJ9b3h8h42NlpKVlZaTnJuJTpWUhYF/goGNkElMUlRPkVeEs620soCwopucn6WclJmMh3R1gHF1kI+WmZeOioiJjY6Pdl15f3+BiIuCiIeIhIB/foOGgoKEhomEfnuBe2hkUG2RnJ2dn6GdmpaYl5yampmTl5aWl5yZmpaYlpSTj4uMjoyOi46Oj5GQlI6NkJONioyOi4ekdHZ2d3V1dnV6dXh0cHRydoB2c3ZybnJwb3F0dnV1dnFxb25raXBsaGxraWprbG5vbGtomnV1cnFxb25wdHRvbGdsaGVsbW5ubm9tam5sbG1rbnJtbWppaWhub3BuZbKtqLRjZGVoaWRiYF1aWlVVUVVaXlxTUlFWUVNTUVVUUkxOTlRRU1CZgHaTTId+fYF6eYB/foCBhISCQkJAf4NEeG51dnh6dHdxdHZ1fXZmaV96dneAgYN3d3R4fHB2fnNle0N9eUN3fkI/QYF7aFlaW1ZXWFxaW15ZVldXWFhaVlxaWFhZV1ZTVGh0d3+CSFBOTk9VUk5UUlNTUVBQUFFSV0uHW11VWltZWFZYVVJXWFhXVIBdWltRTlJRXGRRU1ZXUktOTVFWUFJPUFBTUFJRT0xNTktTaGlfYk5PS4x/gnx1bWVsfH99goKBiYiIgWByjIeCgIGJiIqMhoSIhYeLhXx7cmdhaG1lX2VbYoeKh36HjUxNioV2bF9bVVNfdXZ2bnVtdG5pb2RjZVxzeHFySE5TVGZPTERBSkZFQHRqX2bC/IeLjpVjhI6X4nqdq2hij29NRz09Oj47OTY0MzU4QFZTb0VEQ0NBQT0zNDAvLi0wMDg+QEFFREA6NjAvMC4xMkJLT1RRVVBNU1lZWlZTXWFkXl9hY2JlaTWENoBcUlFQUjA1MVBPT09IOFQ0Qj5AbExNaFQyMjE6TGdOXHN1ZlFrR19UU1JLOTk3JSMkJiYfHSMiIB4gHCEiIh4fIB0dIx8hIR8gICYmISMlIyEhJCIhPCAzOyxKQTY0ME4oKDUsKjMlKSwZHSUfIiAkEhgcFCMkGxIRDBARFyYYGxYaLjAxGRsgHSslGRocHRsrKy0uKikrin4BfZl+C39/gIB/fHx9f4GAhX+Eft9/AX6ZfwF+mn8Bfp5/iX6FfwF+lX/ggAF/soABf6qAhH+kgIR/AYCNfwaAgIB/f4CifwyAf3+Af3+AgIB/f3+agIZ/lIABf5iAAX+YgIR/BH5/f3+2foJ/m36Mf4R+gn2Efgt/gIB+e3t8foCAfpJ9tn6Ff4V+g3+FfgJ8e4R+A3x+foZ9CoB9fn9+fn59fH2IfKd9C3x9fHx9fn9/fn18hn2DfoR/g36EfwZ+fn5/f3+EfgZ/f39+fn6Ef4J+hX+HfgICBACAy7bIwpO6xb63ovTWhbLAxszLyMvS0szGvMjKzMrEu7rCzdyFwbzHhqqWhr+L7M6cmJODpsXb5vCEmJehvbujmZSZmZKcoqmYjaG7u8vS1+nW48S6sp+kp6qrtri8ysrC09jDl6KvsaehmZukrLKwq6SqqqiypqaqsLyssKv8t76AvMTIx8vG1MHJzMbc1tDJyc7R1M7U0NCFkp2Tj5eQoJWXmKKVl5uPkZCLj46NkoqKjICfzcbC1NTPys3HxsbHxc/LxcrGs6Srsb3N2NaXrb+5t7++w8HNzc7IzMXS0MjAztLU28zQxNPV1NGk9ILz9/j46/fvgYTvmMno5ujf1+KA1cjJ1NnQzNK9y8PDu5yctd/p6err4ufn5OLdorTX3N3c3d7e5uLi49/j4eLg3uLf2dzZ1tnS09vV0dLPtbCwssLa3Nna2dfW1dfW19jX1dPS0tXX0dLRzM7OzM/Oz9HOxMnGx8fMy8nIycjJx8W+7ra9ure8vLq4u7i5uLe6ureAtLi3srKytbi2t7WytLazsK+vq6utrKqrqqWnqKmoq6mnpdugpqOlnJ6lo6Shnp6hoJ+fnJqbmZmdnaSgoqKhoJ+cm5iXlpWP/vbx9IaUk5CRko6PiIeMjouJhoaHiIeKhYKChYSFhYaFiIaIiYb//YOIh4KDiYTsnsnb+fr19PeA9vz8gP6BgoD7gPuAgvv09ff06ubu7fWA//jxvMzt6evk5vTu6Onl9vL8gPry4PSE8u7v9v767/jwxpahmJKQjZOZmJqZnJqWl5ubnp6gnqGfn5yd5Mrj7PXx6ubQ193k6O/4gYeNlJmamZmYm5jZkqGgnqChnZ6koJ+ZoZ2ioJ+Am6CdnZiWmqDHmKKgm5qbkZWUmZqZmpyjo6KenpyZkI2F06/u69rv4+Ho9PCE/O/l4+np5uXo8N/f5d7gq+rx8/2A9+/2+fXu4+nm5uPh6u3t/+zf5d7c2bqTyuDW59/v6PWC2aqisMvS0unz7enPy+Lb09ng2d/eta7O7/2JnZuAqJqbmJCLkJGJ6baV8NF9i5+qtYm/ttTq8ZnChJKOjdzBw8nW3dHHxcfOyce6+pSbmZ2foKGyrqKspKiqoqSopqGjp6yvu8zb182+q6nEv8vPz9LLz+fu6/Hu9/2B+vXx8/by6/38gYH/4s3n6PiGgoPqy8fCvK+By5Oys7Lcm6+AxMSemZqrm4+ouoDS1/Ln6Mjl7ubd3ebbhpmut7/IqKOxyq/Ezby/3M2Q3dK2tsa9y9W3u52Wz8qzwNuXkoSh8+fZ5MSU5smTh/f8iq6yhtr+i6DB89mGgoyXvtuJ9PqK18GPrq/G+6Czjvj8hJOosI+A78CbsbT02+Xd0tLU0MyAc2dybVdobmtkYKmdVmtyeHx6dnl9fHh2b3d4eHp5dnd9hI9el56ocpuHc6V6z7GGg35xiY2bp6xfbm10iIZ1cGtvb2dvdnttY3WIiZeam66cqZKKhnZ8fX+Ai4yNmJePnKKSb3iEhnx5cnR8goaCf3qAgX6GfXyAho1/gHyzgISAgoiLjI6Lk4OLi4iZko+NjpOVl5KUk5ddZ29nY2pjbmZpaXFnaWxiZGNeYmBfZF5dXlZuko2ImJiTj5GLiYyNjZWSjJGNfnJ5f4eSm5hqfY2Kh46Mj46XlpiTlo6bmpKMmJudo5ibkp6fn5t6qFmlp6imnaWgV1mibZq1srWupq6Ao5iZoaWenKCPm5STi25wgZ2joqSmnqOmoaCccHqRlJSUl5aWnpubnJqcnZyZl5uak5WUkZOLi5aQjY+PeHd5f42goZ+fnZ6enZ6dnp+enZuamZ2emZmYlZeWlJeWlpeSipGOkZCUkpKPj4+Sk5CIqH2CgH2DgX5+gH5+fXp+fntjeHp8d3h4e358fHt5en16eHd3c3N3dnZ2dHBycnRzdXVycZpzeHR3bm92dXVxbm9vb25vbm1ubG1wb3Vwc3NycXFubWxra2touK+rrWBqaGRlZ2NnYV1hZGBdWlpcXVxfWlhYhFqAWVdbWl1dWaelV1xcWVhfW59og46iopubnJydnlGhUFFPmU+cUVGbk5eclo2Mk5WaUqSanHiHnJibkZKfmpaUjpyaoVOgmoqdV5uam6Gno5minIdtc2tkY2NnbGpramxsaGltbW9vcHBwb25sbZp6h4+TkouLgImOlpyeqVdcYmeAaWlmaWhsa5docnFwcXFtb3Nwb2lvbHFwbmtzbW1nZWxxhmZvb2tpamBjY2hqaGlpcXFvbGxqaGFhWZJynZeYp6CboKqoX7SqoJ2jpaCepKqfn6OdnHSmq663XbCoq6ysqqOloqGemaKkobCfkZaTlJSAaIuel6KYpqCpXZl0bnhKjpKNn6agnI2MnpaNkZaRlpN6eZKpsmBwb3lrbm1nYmZmYaqEa7OpZm96gYhmlY6u3fKEnGh0bmWbhoaKlZqRioeIjImHfa9mameEaIBybmNpYmVoY2NmZWFjZ2tud4WQkIZ6bW6DgIqNjY6IipuhnqSiqa5Zq6OgoaWko7OvWlqxnougnqdbWFmeh4eDf3popWFydHW5d3yFgV9cXWd6ZnqPYp+jvv+qd4uVkYyEeWo+S1VYWV9LTFReUVpiVVhpX0pZYldYYlhcZVFXSUtDYGBSWWVDPTtPcXJtbGJHcl9KR4CBRl1UPoKSUV5wkIBJR01TbH9Mh4lNfnFQXF9tilppUYyPTFRhZVNKjHBaaGiIeoB7dXV3dHOALyouKyUqKyorMWFaLjM0Oj89Ojs+PTo4Mz0/QEBAPj9FTVdEfJWibpeGcph23OW6trKlxpiToqZZYV5hbG1oaWNnZVlhaHBjWGl9fYiFgZaFkX5+f3B1dnh5goODhn90g4h6XmVzd3BrZWZtc3h1cmxycnKAc3F0eYFvcW6ZZ2qAZmxtbXBveWpxbml7c3FydHl9fnd5e4BOWGBaVFtVXVZbXGNaW15UVlVRU1BPU09PT0lcfnt5g4F8eHp1dHl7eoF9d3x5bGNn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wtba1uDm6+Lizd3j6tfm39vZ4Mq3ueWJn5ikoZ6VlZSbpqDwy6Wi/OODkqGss4m/yNiChs2S7pnCxZH48/mA/Pb29vr05ZGlgKOjpq6sqKewvLStr6imnaeloaCjpqaprK2tsLjI0t3f2NnW0M7H3unu8P2B/oSC/v79+Pru+4GEh4j06uaBiJqdnZePiYiJiuz92cqIiYKKyLu50Z6Wl6eIhZKpmKHWroLjtb3t5+HW6drmjofsy6vUo6C9l7OysbST2sXT8/TEUrTKydO64Zy5xuTIiuv6wcCvu7igxaGq9/vHnpq2jqTogJWLkpGqtt3f6+Hmh97e4dvVtt+xv7uywu3Cvtjxg5qchIWRiYa5nPvl1NTm7uHb2d6AfHp5dVpyc3NxcFRjm2yZaWxzfnZ0dnhxd3d0dnyChISOpGi4qp3OhpV40nPJqoaEgXaSlLRcXWhnY3J/inBtbW5ybG10cHtvaYKFk5mXrLKxo5SMe3p2dH19hYuMipeXl6SQYIKChYR4ent1eH2FgoN5goB/hXt9fXt1gX5bh4qAgY2JhIeLhoGLj5GjlZeUk4yVlo6dm6qCqGNpal9fY2loZWBdZFxkZmJfWlpbXF5fYWKUgpCWmJCbj5WWlpKLj4iCen+Fi5GVmpiSl5KOa3yOi42Ii4mNi5OUmJmdlo+SkJCTk5ubnJ2loZeckWGcpJ+ZmpOOqHuiprnBrK6yoaKAraWapKKanpaWj5p6jpmioIJ6iJqcnZ+cmYJpipaWk4yTk4+VmJeQj5mdm5ycm52ZmJeZlpKMj5ONkYyOkI6Liop6dXd9iZqfoJ6dn5+dmpqXlpudnpyZmZycmpycnJmZl5KSlpeZmJGPjoyPh4d1Z4iGh4eDhISDgYKDgYKCfn2AfX57fHx6e3t7fHp9fnt5e3Z3dXJ1dnR1c3F0c3NzdHRycmutd3h1dXJzcm9tcG5wbG5xb29rbXJ1dHRxcnJ0c3FxZqyvsaKrYmZnbGZjZWJiZWRgYWBdXVxeXl5gXVxZWllZW1ldXFxbWFlYXVhaWldbWKpeqYaBmaCgoJybm5yAUlFQUVJSUJibn56emZuanqKNiIqVmJ+Xm4d6oKKipaOZkpqOkZ2bm5ucoVOdlpaclpycopKho6KFb3FwbmdlZmZtb29nZ2ppaW1tbG1tbW5wbHFlbYySj5OPjpaQl4+inJKSmZCMg4OHipSYmplxcXJwcXJucnFzcnBucnJzdHMUb25uZWhwa2+FZGloaWJiXFZXWlyEXoBgYWNmYGBibXBvjquSs11bq6uprlyuqqqpqJ2mn6KZoKWgm6CmdbK5qqassllaW7CtrrKtq6Ousqujm6CYp5aVj4FZjIumqqmQiYWJn5OElpiOlJabmJWFlpqmkJ+YlZSciH2DpWNzbHRwb2lra293cqiOc3PDvGp0fYOIZ5egr4B1f8J6xHuYkWqrqK1ZsK2qqqyonWJwb3BwdHFsanB3cWlrZmZeZGViYWNmZ2lsbGxudoGKk5WRkY+NjIaXn6Olr1mtWlmtra2oqqKuWlxfX6mjoVxgbXFybWdjY2Jkq8algVpaWG2WiH6QYFpbZGhiZoB1dqCDjrNob5OSj4eFcWhwQz93Yk9iSklZQ1BQTlNBZ1hicHNUTVtaYVFsRFVaal1DbW5WW1RVUUdZSExwb1tKR1RHVn1KWlRYWGp0hYWJiY9SiI2NjIp4jnB3dG5zkHVyg5JPXV9QUFdTUWtbjoB3eIaLgXx7foAuLi8uKC8uLi0tKzhbR1k0MTY+OTk7PDc7Pj0+Q0ZGR1FsTJihl8qGknbGctfcure1rdqgrFdXX15XYmVrX2VlZ2piYGVkcWNZcXiHiYCTmJaJgX5xcW9tdnh+gYF5f35+i3pPcXJ1dmttbmlscXhzdW11cnJ4b3Bua2NwbkxtbYBlbmxkZ2xqZXBydYh6fHl5dX1/d4SBj26KVlxcUVFVWFhYVE9UTlVWU1BMTEtMT1BSU3lsfIODfIh7gIGBfHR8eG9nbHF3fIKIhH6Df3tdcIOBg3+BfoF9hYWJio+GfoKBg4eJkJGTlZyZkJaMW4OJhoKFfXqYdJyiuMKtrrKkpYCwqZumopiclpeQm3l8hY6Mc2t1g4WGiIWDbVVxfX16c3t8eHyAf3Z2homIh4eFh4SDgoaBfXp+hXx/enx+fHl4e25pbXeDk5iZlpeamZaVk5COlJiZl5aWlpWSlJSVlJOSjYySk5eUjYmHhImBgnBhgYCAgHp9fn16e3t5e3p1dIB2dnN1dHBzdHR3dXh3dHJ0bm9uaW9xb3FwbnFwb25vcW5wa695enZ2cnJxbmtvbm9rbXJxcGttc3Z0cm9ycXRzcXFnra2unKdgZWVrZGBjX2BjYl9gX1lXVlpZWVxZV1NTUlJUUldVVFNQUVJXT1JRT1JPl1abcmyBh4aFgH9+fwFDhEKAQ0B5e4GAfnZ4dXuBbGhseHl9cnVnZIWFhIeCdm93bG96dnV1dHk/dG1udG50dHhoeHt7Zl9hYF5WUlNUW15cVVRXVlVZWFhXWFhYWlZaUk9dZGBlYV1hX2dib2lgYGhlY1hZXGFqbm5uWFtaV1hZVFlaXVxZVltbXF1cV1dYTVCAV1JYZk1SUFBKS0ZBQ0RGR0ZGRkhJS05HRkdRVFNshnaUTkqIiIiOS5CMi4mHfoaBgniAhoF8gYZcjZOJgYeNSEpLjIuMjoiIgYiLhn10eHGAcnJuY0VyboSIiXVpZmV7cmZ2eWtsaWpoZ1tsc4FuenBra3RhXmR5RlBITUhJRkiAR0pNTHVmXGTU94uNj5KVZoeRqHh5tWywbX9gP2FfZDNkYl1hZmZhQ05MS0dIR0RBQUM+ODg1NTE0NTU0Njg4ODo6Oz1BSE5TVVRUVFJTTlleYGJoNGU1NGRkZGNjXmQ0Njg5Y15bMzdBRERBPTo7OztlalxJMzIvQFtYWGA1MTF7OUVKQE1HSGRRWG47QFVSUU1GOT4hHyglJSomJCchJiUlJiEtKSstLSUkJiYpJywjLispJxgtQSopJiklIiQgIkA7JiUnKCcwNhkeHB0dISQrLTAuMR03Ojo8OTMzJyknJSgyLTA1PR8hIB0eICAbIR0yLSkrMTMxLy8wjn4BfZN+C39/gIB+fHx9f4GAhX+DfuN/AX6ZfwF+u3+Ifpd//4CXgAF/n4CFf6uAAn+Ai3+HgKR/AYCNf5uAm3+ZgAF/mYAGf39+fn9/hH4Bf5d+g3+2fox/hH6CfYV+EX+AgH58e3t+f4B/fn59fX1+h32tfgR/fn9/h36Ef4N+i38Kfnt7fn9/f31+foZ9CIB/fn9/fn5+i3ygfQN+fXyJfYJ8h32Hf4V+AX+Rfop/in4CAgQAgN/U09KZ1sydpKnG0LWfmsSDs7y5yMDDx8zNytHZ4+7ziqKKzJ6j+Me1oY/bvpyYlIu4x4aCkI6YmZ7AzayQk5KRkpiamZyZgJOZlr7V3fDk4c/Gs6mon6GYnJ6lrMTP3tDUqZu1rqeoqa+qpaGgp6Smpaqlpa6zsLCmpqms/MS3gL7R1c/Mx8zAztHRxLvMwtTQxdDU5u3c2OqXmJaXjYyakoaHiZOQhZeMlZKCgoCFjYyGiOrDzNvb2N7Dt6aws6291NfX3dLN0c3O1dXTx9Saqs3Eu7O9uMDG08TOzdzh4NPQ0MjIzs3O08zGvcbMpdzu7uWRu+XY5eTh5ODo5ubnW9vV0NXO0srJwMangtjh5+bm6NKwsNLd0JzB09zd3dzg29/W2dba2Nvc2t3j3ubm5OLb2+He3NnU1Nvb09LS0tfTzdTP0Miyqaipr77P0dHU09LT1dTV1dLNzs+F0BLN0M3My83MycXGw8O+uYCyv7+EwIDDvr25ubm7vb65uLq3vLi3tri2trWztbS3tLKysq+ssLGvrqmppqupqKqmo6Xcp6mko6Gkn52gn5ueo6GinZ+fnqCiop+gkoj70fTt/4yTkI6Qi4uLiYmKi4yJj5ONioyJh4aKiYmIiIGGhIqLiYmRiYuIhYaLhoOIioeC+crL3YDr7viA//7++PuAgID2/f/++fn6/YGB+/n7/fbt4evt6f3ltdLv8PqB//z17OXx7fPsgPT1/fTy7fWA6vr2gff39MmYoKGcoJ+dnZ6enZ+dm5mamZWbnJuXmZudnJqy5u7q7uzv5+7w8vz79PP59f+A/fDk+f/09vrQo6Shn6OhoYCkpKWloqCko6Sjopycmp2coqGph4uPkJWYnZ+coKOnqKKjnKOho56coqOjo9rIhPz/gf/99t/t8u2B9vby9vnu5ere8PLq59Sv+YH5/O/q9PqAg4Ds2+347+/09O7u8ObWz9nO1JSyv66pvdfj2ebYztDV3ubz8vPo3/Hm4+Hn6oDr5u7h4c6/3IqTlqGVlpWVivuCgOS9nab0hJGWpK61hbnPjYeYpdXNm+rorqKcoquwsrW8vruuqKanrq2urqmkp62koKCfpa2ur6WopJ6ho6moqaytrbG5vLvG1dXGzczY3+fu8PqCgYD9/PbpgISF/ubh0v6KmJyclJWWkpGUloD+6uPylaCak9nQyNWkl5qopPH3kq/Z3dqHt7G76eXbztbX6/qFv8Ok67jM65+zv/vnyr3J3/vAjNnd2/3PwvDKyuen8verxcGvqLPEt6fIqL/GysmiqLeepam2wcTGy9PZ1dHJwb26uLW0tLW3tb3Hztrf4uTf3dnX0dLP2+HU1wra5ubj3tHe8ejjPnpzc3RdenNicWZzeWphaolTbW9ueXR1dnl7eoCFi5OYXHZwr4iM4a2Yj33EpIaEgHmglmRdaWZvcHGOlHtohGqAb3Btbm5aam5rjqCmtKuqmpKDfX54e3N0dX2AkZqlm6B+dImDfX9/hH57eHd+e3x8gHx6gYaCgHl5fIGyhXuBjZGLh4OIgo+SlYuEkYmalYuTlqeuoZ6damloaWJfamRaW15mY1tnX2ZjWFhWWmBhXV6jiI+goZ2ei4J2fIJ5h5iAm5mdlZCWkpGYlpaLl2t7l5KNhoyHjpGajpiXpKmmm5uak5OZmpugmZaQlZx/mKOknGmMrqKtqqqxrrazsrOppJ2inp2Wl4+Ue1yboaelpaiVe3uWnZNrg46VlpWWm5aakpSSlZKUk5KXnJecn52clpacmJaTj5GWmIyNjIuVkYyAk4yMiHl0cXh8ipeXl5qZmJudm5ybnJWWmJmZlpeZlZuXlZOVlZKNj4yNiINYfoaGhYeIhomFhIGCgIGCg4B/gX6Df319gX9/f3t8fIB9e3p8eXd6enh4dHRxd3Ryc3FvcZl3eXVzcXRvbnFwbG91c3RucHBwc3R1cnJnYLGTraOArmBoZWJlXV9fXV5fYGFdZ2hiYGFeXFtfXV1bXFZcWV5gXFxiXGBeWFldWVZcX11XqIWDjJSWnFKioaCcnVBQT5efo56cnZyjU1KbnJuenJWKk5SToJJ0jaGgqVeppqCYkpqWmZFSmJyknp2VnVOUoqBUnqGfiGx1dHBycm9vcHCAb3FvbWxtbGltbW1pa21vb212iY+Nj5CSjJKSk5qZlpSXk5tPnZKHlZmQlZqMc3Nxb3RzcnR1dXRxb3NxcnJxbG1qbGxycnZcXmFiZGdqbGpucHR1cHBocG9xbWtub29xlIhcq7FbsK+rmaitqlusqKetsamhppqpqaGekXavXa+Asaaiqa9aXFullaSwqaaqqKKfo52SjpWMlGh8hHt4hJajlJ2Sio2Rlp6moKOYkZ6VmpujpqWhqZ2djYOXXWRocWhoZ2lirltZnoRveMRweXZ+g4hkkqVxe5KYr6d+t61/c29zen5+gIWIhnl0cnJ2cXFwbGhqbWVgYF9iZ2lqZWiAZmJlZ2toaWtsbnF3eXqCjZCHi4qSlpyioqlYWFitrqieWFtbr6GelLJkbHBwamxtaWdqbLy8paJja2h0oZiGkWJaW2N7ubJui6mio3ugYGmSjoeAgHN4fEFcXE1nU2BxRVJXd2xbVVdldVZFZmZjeFxYdF5fcUt2c0xbWktGTFMcUUlWR1JWWltLVm9ocnF4foKAgIWLhoV+d3NwboRsHm5udX6FjJOUlZSUkY6PkI+bn4p/fIOEg4B5goyDfIAsLTAwKzIxNT4xMjMwNERgLjY1NTs4OTs+Pz9DSExRVT1WYqmDhOuyl4R60da6uLOw7a5hVl9cYWJgcXNmXmJiY2JnZmFiYk5aYV2AkpOdkY+Cf3d0dXF1bW1vd3V9goyDiGxjeHRvcXJ2c25sbHJub25zb21zdnV0aWdpbY9pXoBjbXBraGNpZHF1eHBqdm+CfnZ9f42ThoeCXFtaWlNSWVRLS05VU01XT1VUSklIS1JST1GHcnuNjYmLd3Nkam9ocoKGg4h/fYF+fYOBgHeCW2+Mh4J7gn2DhIt+iouYnJuMjI6IiI+QkpiSj4mQmHmHjI+KYIWrnaupqLGvt7Sxs4CopJ2hnp6UlI2UeU+GjZORkZOEa2qAiIFcbXV7fnt+gn6BeXx3fnt9fXmBiYKGioiGgYKIhYSCfX+FiXt8enuIgnyFfXx5a2lnbnWBjo+Ok5GQlJaVl5eWj5CTlJOQkpWQl5ORj5KRi4WJiIiCfFR4fn58f4OAhYB+eXh2d3l9eYB3e3h9d3Z3fHl5eXV2dHh2dHR1dHJ2eHV1cXFvdXBtbmpobZh5e3Z1cnZxcXNxbG92dHVucHFwc3V1c3JmX7GSrqGtYGllYGRZXFxZWltcXVhkZ19cXVpYVltYV1VVT1ZTWVlWVl5WWVZPUVVPS1RXVE2VdG5wdnmBRYmGh4GBQYBAQHiChoB8fHyDQ0J4enl+fndpc3V0fXRdcIKDi0eJg353b3hydG4+cXV8dXNtcz5seHY/dXd4bl1nZmBjY15eYF9cX11bWVhXVVpZWFRVV1lZWFxcX19gY2NeYmJjZ2RjY2RfaDVrX1dhYlpgZWxdXVpYXlxcXV5gXVpZXFpbWoBaVFVTVVRZWlpERkhHSUtNTkxRVFhaVFNLUlJUUE1QUVFUcWxLhpFMj4yIdIaKhkuKhoGEh4GAhXuLi4N9cl6NSYiMgn+DiUlLSoJ1g4qDgIJ9dnV4dG1tdWx1UmdtY2BmdHhue3Jpam5zeHlxcmhlb2tydX+BgXqAdnZoXmc+P4BCR0FDRUU/bzo6bGBca+iZo5KRkpRkg5VrfoyWopZvn3tQRUJER0hHSE9UWFNOS0pLR0dGQj48PDY0NDM0Nzg5Njg2NDY3Ozo5Ozs8PkJERUxTVU9SUFVYXF9gYzMyMmNlYlw0NjZmXFpUZjpAQ0I+QEI+PT0/bGhcWzY8OkViXYBbYDkyMjhThGpAVGNjY0RbOUBYVVBLRDg8PiAlJiM6JykuIyYmLCspJyorLyYXKConLysqLyorLSUwLiUqKScmJCgoJCYlJSMrKhsqPjg9P0JDQkRGSU1JSUZBPj08OTk4NzY1OD9GS1BTVVVVUlJPUU5UUEI3Li8wMjMwMjQxLY9+AX2Qfgx/f4CAgH57fH2AgYCFf4J+yn8Bfpp/AX6afwF+u3+Efph//4CZgAF/moCFf6+Ah38BgIV/g4CIf4KAkX8BgIl/AYCHfwWAf39/gIR/m4CSfwGAiX+ZgAF/mYAGf35/fn5/h34Bf5B+AX+GfoN/tH6JfwN+f3+EfgF9hn4Lf4CAf3x7e31/gH+8foN/hH6Df4V+i38Kfnt7fn9/f31+foZ9CX9/fX9/fn5+fYt8hH0BfI59AX6cfQN+fX2zfgICBACA8uvq3ZHKzcmx1NfOycapgsjPo8+9ws/U2Nbb4+7yg5zAos/3ruLivbONzbacmZWMzrDzhZGVm6KpwNzImJ2YkpWMjJWQjoarqK6kl5ijsLe2urq1rKelmpagrKWv0dTbxsuDnLS4q6yrqa6qraSlqaCoq6Kkp6uwqqWmrK2Ex76AxMXU0sXAwMnIyNXGwcC5w8zQytLn39rblJGTj4qUj5iRioeMmI+QjYyRlpGIivmIhoqP6pGnqLiyp7DIyMnMx8zR1dbX2t3SyMrR2tDJzs2eoMnBwcLBurzLw8TEyszL09vj6efZyMvMxMnP0cbEnuyI1erm5d7c49fp5eTj49iA09ney9vQxbvAmJ7i5OTm5ebi4d/esvPz0dne3Nvf2tvd3dra2Nvc2djc3uHd3+De4eHf3uDf3drX2tXQ0dfX1M/R0NDLyNDNycK/sKOkqaqstcjV09HS1NDQzsvLysrLycvLy8rMzMbKx8XFx8HjusDBwMDCvcC8vrq5u7u+vLqAuLe6ure7urm1tra1tbe2sa+vr7Kwrqysq6usqqyqqaalpqOJiKmnpaSkp6ainKClpqSioqGckYeB9+zl5fj9wuiUlpKNkIyLiYiHioqOjouKkYuKi4aIiYmKhIaIhIaMgo2MiYeJiYyJhYSJiYGLht/L0u3v9uvr8Pj5gPr//PqA+vbx9/f98fj6gf/+/Pv69erw6vHr7sLA7vH79POB+/v58Pf48vf1+Pv7+vvt7OXy9fz6+/7rx5qenqCfnp6an6Gfn5ubl56ZmJibnZ2amZyaneTE6+jr7u7t7Obs8f718Pf8+/768vX9gID78d/4pKOgnJuboqKkpqajo6Gmo6WAnpqeoaGioqG4m6Kjo6ainqCgoJ6hp6SloqGkn56gpKSoovbJgOr1+u748OHt69zq9PT79fHs7PXo7uXj7ujSs+vp7O7t7OPn5/6A9vL26u/r7/Xy4+Hezdfc2NSsicfI0+DZ2/TeycjH1dHU4OXt4+vh3tjJz97g4OTa09Ld2uOAgoeIi4rp94aIg/vgs5Wg+ZmVmKWrtP2w0q6ZyoGKoJWEkMqtq660t77Gv7eutLq7s66rqaahpLO2op+hn6Wzt7KpqKesqKqop6qtsbO0tLK4ubWsqKu1vsbExdfd293i5eX7gIT74ebl34yYm5+fnZ6io56dn4Pg7oSfsqOF09lb09SrnZ2ppJrY6L+Vxtu/tc313c/Kw8zP8L66t5qam77p88ODzuXx08zcs+6h0OrJu9TfvNDPzuKn2bu/ztaOs8jY+7uq2qaX3IT3qK6TlpeaoaWmpqWsramnqIWpIKipqKqtra+usrW3tbOysrKwsa7WhoHBxM3S6unj0uL1gIWCg3xacXJwZXp7eXRyZVOPjWh+c3N6fYCBhoyUmVJqi4Su0JXayqKae7WchoSBeKyGsWFqbHF3fYyekG5zbmpuZWVsZ2VfeXZ5cmhrdoKHh4mHhYB9fnVvdoJ7g5qdo5KYX3OJjH+BgH+EgIF6e4F5fYF5en6BhHt4eX1/W4d/gISFkI6Cf4CLioyWioaGgYmRk46SpZ6cm2VkZmRhaGNoZF5aYGpjY2BfY2hlXV6oXFxgY6Fod3eFgXeAkY+OkI6QlJiYm52flY2Qlp2SjJKRb3KUjo6PkImKlZCRkZeXlpqiqrGwpZeampOYnJ6VlnenYZ+yraympKuhs7Gwr7CngKGkqpmmnZONkW1wo6Wlp6ano6Cdm3+pqpGUmZiWmJSVl5WSkpKVmJKRlZaalJeZlpuampmbmpiXlJWQjIyTlJGNjYyNiIONioaAfnVsbnV2eoKRnZqYmpuYmJaUlJOSk5GWlZSSlZWPko6MjY6InoGFhoaGiYWHhYeDgoODhYOCgH+AgoOAhIOCf4B/fn5/f3l3eHl8e3h2dnZ1d3R3c3NxcXJwYGF5d3V0dXh2cm1xdHd1c3RybWZeWayhlpGgnXmlaWtoY2VhX11dXF1fYmJgXmVfX2FbX2BeX1lZW1laX1VhYF1ZWlxgXVhXXV5WYFuYhoORk52TkpKam1CboZ6bgJ2XkJeZnZWam1KhoZ+fnJeSl5GWk5Z9faCiqaWjVaWlo5ueoZqdmZyho6KhlZSPnaCmo6Wom4ptcXBycXBwbXBxcHBsbGpxbWtrbm9vbWxubG2ddoqIjI6Rk42Ij5OelpScnZqbmJKVmk5OmZKJq3NycGxsbHFzdXV0c3NydXN0A21nbYRxgHB+am5ub3Jua2xub2xvdHJzbmxuamlvcXJ0b6OIWZ6nq6Grp5mio5mkq6qxq6eko66kp5+cp56QeqWko6Smpp6gpLRbrKWtpKShpaijmJeYiZSZl5J3XoaKk52VlaqXgoKCkY2JkpedlJ+XlpaJj52gnqCVj5CbmZhYXV1gYaGugF9gW66bgGt2y4N7eX+Ch7uLqo6Nw4J2g3hpbZV9fX+Dg4mSjIN8g4aGenNubWtoaXFyZGFiYGRtcG1oZ2dqamtpaGtucXN0dHN3d3VvbG50e4KAgpKUlJaanZ2sWFuunaCfmWNtb3JzcXN1dXFwcWCooVhrd25ymp+QkmheXWN0eXiVqJl0lKKRi3KOjIF9d3djcl1ZVUZDRFdscldAXmxvXVlnUXVUX3FfUV9lVGJgXmdKZldYX2FATFVeclNNYkg+ZT5zVWJYWVpbYGNiYmJlZWRjZGVlZGRkYmJiZGZnaGhrbm9vbWxra2xubY9dXYd6e3mGhIR3f4mAMDIzMSoxMTIyNTYzMjQzN2tdODw0Njw/Pz9CSE9SMEdod6fNktHRoI95xtC6t7Ow/aCrXGBhZGpscXdzX2dkYWdfXmZfW1FnZGliV1hgbHBxdXh6eXV1bWhuenR3hYOHeYJOYHp+cnRzcXVyc21ucmtwdG1vcXV2bWlpamxKZ2GAZmVvcGRhYWpqbHZtbGxmb3l8dXiKhIKCVVZWVFFXU1hTT0tQWVNVUlFTVlRMTopOTVJWi1lkZXRwZW19enp6d3p/g4WFh4qDeX2Ahnx3fH1gZomFhIaGf3+JhYWEiomHjpWep6SbjZGRipCXmY2NcJdWk6ynqKCeqJ+1tLGusKaAoaWsmKadkYmPZl+OkI+SkJKNioiGbZOMeX2Bf31/e31+fXx8e32Aenl7foJ+gIKBhoeHh4iIhoOBhH97e4OGg35+fX53cXx4dW9uZF5haW1xeIeVkpGTlZGRkI6OjI6PjZGPj46RkoiLi4mJioKVeX1+f4GDf4KAg397fH6AfXmAd3l9gXx/fn14e3t4d3d1cHBxdHh3dHJzcnJ1cXJubmprbmteYnt5dXV3enhybXJ2eHZ1dXNtZV1XppqHfYaEaKVobGZgY19eWVlYWFleXlxaY1tbXlZbW1laUlNVUVRaTVpZVVNTVFhUTU1VVk1YU4h1bXJ0gHh2c3+AQn6EgX6AfXhxenl9dHl8QoB/fXx8eXR4cXRyd2RngoSMhYJDgIOCen1+dXd0dXh6d3dramhzd3t4e350bl9iYWJhX19bX2BfXlpaWF5ZV1daW1tYVVhVV35RXFldYGNlX1xgYWhjYWppZmdlX2JkMzRlX1uIXlxZVlVVWlteX15bXFpdWlwEVVFWW4RagGJQUlFQUk5LTVBRT1JYVVZQTlBLS1JTVFdTfG1FeYGEeoWCd4B/doGIhouEgH58iYKGfniDe3JigH59fX6AeX6BkEqLhoh/g319f3pwcHNndHl2dV9LZ2hxe3FugXRjYmJvaF9laGxldGxtb2Nod3t6enBnZm5qYDc6Oj1Aa3Q+gD46cmtfWm3+uKSSkJGTvnyXhYvCgXB3bVxUYUxKS09QV2BdWVNYW1lQSEVEQT8/Q0M6NzYzNDk7Ojc3ODs6PDo6Oj0/P0FCQUNEQj87PUFFS0pMVlhYWFlbWmU0NWNYW1pXOT9BRERCREZGREREOWBeMz5FP0VbX1xgPjU1OkxSUWRdXEVSXUk+PEdUUExJRzk6JiQlISAiJistJxcnLC8nJy0nLhoqLCkkKywoKiopLiYtKCcpKBglKCopJCQoJSAtIzAsMy8yMjM0NDU0NDg5N4U2DjU1NDMxMjM2Nzg6PkFBhEAQQUNCUDQ1Sjw1MDQzMy4wM5B+gn2Mfg1/f3+AgH99e3x9gIGAhX+Dfvt/AX6EfwF+vH8Bfpl/jICCf9aAAX/KgIh/r4CLfwGAjX8BgJN/AYCZf5uAl3+CgIR/mYABf5mAA35+f6V+AX+1foV/BX5+f39/hX4BfYd+C4CAf3x7e31/gIB/wX6Cf4V+jX+Ce4R/A31+foZ9Bn6AfX5/f4R+iXyKfQF+iH0BfpF9AX6Ofad+gn+KfgICBACA5Pfz7ZrP0tTZ3+Dh39zQxp/psojA4uDa4/P6g4SQp+W4w6zcgof8uIm5q5yZlYtzsvuCipOOnaC2zM+gmKecmZyMjY+RhKeqrKysuMXJx83PspaGhPX9g4aZpqq6wNfU2L2CmqCxsqWknqWrq6ytrbKts66np6mvsrStrbCAzcOAzMXa1MfBz8jDvMLKv7W8xtDPzuHp5dvlsoyNioaPlZSblZOKiYyUmpaOkpaUjIyFhISKh8q9ydDRw8nGxMXJwsXEz9XU1NDU09PPzdDKzMzQqpK9vLe7wcK/zM3DwM3Jyc3Kx8bT29zczdDJr5ibwtPHpc7c4NTf2ODl4tva0s6A0N7QydrgybK2wN7k4+Pl4ubm5NysoNbTraaz2d3d3N/m4uDc3eLi29/d3uDf4OHc2Nza39/e4N/a1tvc2NTV0cvP0cvLxcnGxcXIxsfGyMTFveChrq6vq7G+y8bIys3MzMrPzsvLycjJy8fExbb3uL67u7y4uLy8ubq9vr66u7yAvLu7uLi2srKyr7CurLC0tLK1sraxs7Gvr7CurqmlpKGipqOh0KilpqaloqGenZ2ShoL88uze6/+DgYSBgYD9gL+Ak46SiY+Li4uMi4yMjIqJjIqIiImHhIeJiImCiIeHjoyKhoiKjIqKjI2JiefS0fGA+u/37u/q/YCE/4H+9feA+fT5+/j8+Pn5+Pb5+vbx3enw7/HGvO389u/2+v3+//b18/r39Pbx9oH78/rn9fT+hYGC/ebSnqKgnp+enaCenp6fm5qZlp2enZ2YmqCjmpadjafq7+/t7u3r6Ovxg4WA+Pn39/Xr7vb57P/5+rmao6Ghn6GgoKOmp6aop6CqpKSAm6GfnqGioqO+mp6gop2aoaOjo56eoqSmoqOgpaqhpamm4r6w6PX48u3x7/f0+vDs7vb6+fn18fLy8Pzq6On0z8Li6u7u8ujq4uTh49/b2tLV2+Hu69zOx87N2d3Ovoym2evY6uTXyc/Mxsa71N7x6drp39fU1t7s6eDf3uDj9oqAgYGIgoaQkouKiYmC26yUovKalZemrLbhptfev4D8rNOJk7XjuK+xwMrMysPAwcGnmq7Bv7SqpKipp6Cfmp2lrK+pqaaoq6+srbK3t7m7u7O1t7y1rqqstK+ur7C0vMXHzdX7gfvh5ujngJGTmJmVkoyRkJGQj+iyw4mgqpaB59mA3s6qnJyhyJmItOywwNa6tYuBw8O/uNyintCyz86ypsiD2d/Wz8+1iPHu+KWEg8Kp84yMwP/rhvjBuMv48ZCJ7IuztL20xMvO2L+nh56Aj5qZmZmanZ6goqWjoaOipKSkpqmsrKytra6rq6+sqqytqqinp6ytsYOO9bPR1O/68uuAgY6Mil14ent+g4GDgoR8dWCWgWJ2hYWAhZKYUVRfca2apJC8d3bZoXihkYWDgHljhbdeZWxoc3WFlZhzbXpycHFmZmhqXXd6e3t7hI6Rj5OTfmlfYK+6YWVyfYCKjaCdoY1cbnWFhnt6d3yCgoGCgoeCh4R9foCEh4d9foBai4SAioiXkYaAjYaFgoiQhX6HjJORkaOopJuie2BhXl1jZmVpZmRfXWBmamdiZGdlX15ZWVpeXYiIjpSVi5KMiomMhouJkpmZmJWXlZiWlJONj5CVeWqMioeLkJGPl5iQjZmVlZmXlZSfpaelmZ2ZgnBwi5mPdpeippqmo6qwr6enoZyAnaqdmaWrl4WBhp6jo6OloKWlopx5b5aVenV6lpiXlpedm5eVlpqclJeVlpmYmZyWlJeVmZmXm5uXkJWXlpCTjoiNkImJhIeEhIKFhYaFhH+CfJN1fX1+fH+JlI+Qk5eWlpSZmJSVk5KUlZGNjX+sgIWDg4OAgIOCf4CCg4SBgYKAhYSEgH9+ent7d3l2dXd5enp+en54enh2eHh3d3Jvb2xvcnBwknh0dXV0cnFvcHFnXFmso6CMkptTVFZSUVChUnxaZ2RnX2RfYF9hX2JjYl5dX15cXV5dWVxcWlxWXVtbYl9dWlxdX15gY2FeXp2Nh5lRnpWbkpOPnlBUnVCcmJiAm5ebm5mcm52fnZuen5uYh5CWlpd+fZ+qpaCnqKmnqqGfm6Ofm5uZnFSfmZ+PnZyoWFRVp5qScnVzcHBvb3JvcHFwbWtraW5vb29rbHBzbGhuYWmKkZKMjpGSjo+VUVBOmpmWl5iQkJeXi5qZmnRuc3JxcHJxcHJ2dnZ4eHF5c3KAanFwbnBwcXKEaGhqbWpob3Bwcm1sb3FzcHFscHVvcnV0noB1oqqrop6hpKqmrainqq+ysK+rpqaoprSnoqCsj4edpKajpqCim5ydoJiVk4yPkZWenZGJhouJlZqLg19vmKOQoJyPg4iHg4B3io6cnI+clpSQkZempZ2cl5qesGOAWVhcWFtjZmBfXl9bmXloe8eFenh/goepg620oHL1ma5wdY6miH+CjZOWlY6Mjo53aXiEgHdxbG5tbWZkYGFkaGpoaWdqbG9ubnJ2d3p7fHZ3d3l1cG1tc3Bubm9zeoKEiY6pV6manJ2dWGRlaWxoZmBmZWdnZ62HiVxrdGZrppqAmoxpXFtehHdgfbyNkqCNrF9Ke3l0cHpMSmNSYmRSSls+Y2dhYGFUQXJyekpBPVpLcENBVHltQnZXVFx1cEM9aD5MTlVQXFhXYFVLQ1ZFUVxcXFtaXV1eX2FgX2FhYmFgYmNkZGRmZ2lnZ2lnZmdoaGdmZmprcFtlr3eGfIqPi4eANDc2NSwzNTU2Nzg5Ojw5NjVtXTw+Pz87PkZLKiw5T4uToI29bWzFk3izx7u4tbGSo61WXGJeZ2dvb3NgYG1nZmlfXmJiUmpqa2tqb3RycHV3aFlUV5+rW1xqdnZ6eIaCiHtLW2V3eW1sam9zc3JzdHl1eXZxcXN4eHZtbW5HbGeAbGp4cmdhbWdmZG12bGduc3x4doeKh4GIZ09RUE9UV1ZaWFZQT1JXWlhTVVhWT09LS0xQTnF0eX6Adn95dXJ3cnRzfYSEg4CCg4SDgYB6enp/Z16BgH2AhIeDi4uCgIyIio2KiomVnJ2ckpaTe2dje4uDbI2an5Ggn6ewr6enoJqAmqmdmKWrloRvb4aOjo6PipCQj4lpXoOBbWhmf4CAf32CgH9+fYCCeXx7fYOChYiDfoKChoaEiImGfoOHh3+Dfnh+gXp7dHdzcXB0cnVycG1va39tdnd3d3qCjoqLjJCPkI6UkpCRkI+RkY6LiXukeH57e3x4d3t6d3Z4enx7e3uAf3+BfXp3c3R1cHJvbG5ydHR4dHlydHNxcnNycWpnZmRna2prknt1dXV0cG9tbnBmW1inmZN9goZJSkxHRUSGRW1YZmFlW2FcXVxdXF5fXlpZXFpWWFpYU1dXVVVOVlNTXVhVUlRUVlVYXFlWVo2Bd4FEgnh/dHdzgUFEfUB8eXqAfHd7enV6en1/e3p9fXh0Zm90c3RjZ4KNiYKJiIeGiIB+eX96dHZydD92cnhndXN7QT0+e3V6YmZjYF9fXmBeX15dWllYVVtcW1pWV11gV1NYTktZYWBeYWVmZGRoOTQ1aWhlZGNbXGRlXWdkZE5aXltaWl1cWl1fYGBiYVpiXVuAU1pZV1lZWVtqS0pMTUpHT1FRVFBQUlNWVFVOUldSVVhXemBZgYeDf3d5fYaCiIKBg4uOjYiFgH6Cho+Eg32HcWh1fICAgXp9enx6e3RsbmZma3B4dWtoZ2ppdHhrZElTdH1pd3FsYWdlYVhQXV9lbGRxbWxpaG59enV1bW90f0WAOTc7OTtCREA/PDw5Z1VUb/e2nJCPkJStd5ymp3Xcj5xjaXRzVU5QXGNkY2BfYmFOQ0xUUUpGQUFAQj07NjU3ODk3Nzc5Oz09PD1AQkVHSEREQ0ZDPjs7QT49Pj9BRUlJTlJfMF5QUVRVMjg4Ojs6OTc7Ojs7PGRSSjU9QDlAYmBQYFo9MzM1VFFIR2xTTlxNUywqTU1KR0EhICYjJiUlJScWKi0qKSglFywsLSQXGCkjKxoZKC8tGDAqKCsxLxgXLBcnKSUmJygiKScmJjIpLzSEMwwyMzQ1NTQzNTQ0NDOENBw1Njc5OTo9PDw+Pj08Oz1BQ0U1OGZAQTQ0NjY1kX6CfYh+hX8KgIB/fHx9fYCBgIZ/gn6jf4J+3X8BftR/yYABf5uAAX+1gAF/jYCGf4aAA3+Af6yAhH8BgId/BICAf4CrfwGAh38GgICAf39/nICLf4OAjn+agAF/mIABf91+jX+EfgF9h34LgIB/fHx6fH6AgH/AfgF/hX6NfwN+e3uEfwN9fn6HfQuAfn5/f35+fn19fYV8iX0BfoZ9AX6EfQt+fn19fX5+fX19foZ9BH5+fX6Mfal+gn+IfgICBACAgPXb163j+oDy2dnm6OPd3NrDmtOz3tj7g4WKkaa3jsWj1ZKmivPkhZ6jnJmVjX7N7ISSkZikmKnRz7eboaejlp+TjpOJm6Cko6SorrGprr62tKqnp6WspJaVjY2Xpq+sqf2kqquwsaelqqutqaOmrbGcp7i0r66ptLOxrKqGxceAycXJzszL0dfQyMfJv8PFvNDZz8rl7ObgwYGQhYWKj5WZk5Oah4iPmZeSlIaUjIqUi/qGg4LwxcXUyM3L18bCyMDHycDBwcnQ08jQ0NPM083Lz7eQycLBvsbL1NTLzMG9vMPJzdHMzca+qqSYusfGzMrDx7SrzuDW4Nvf1uLb2sorzsvP3NXbqoDc4OLf3ePn5eTivaPO0dTS2d3PpKGx3+De4N/e4d/Z4N/f24XdgNvb3N7b3tzf3trd2dfU0c/V087NzMrOzcnGycXEw83Hx6mGzs/Oz8vIuqyopqqrqaeouMPGy8jKyMTEx8OPn8LAuru6vLu5ur27u7q+u7u9vba3trWvsK6us7KvsLGutLGtr6+tr6uqqKaopaWlo6ahn6Gg8oqVioWE//n27uqDgImJhYOAgoWFhIKDgfv9goCCuYeQjYqMjYyNioWGiYuMi4iJi4eChIOEh4eJg4GIhYmJioeIio2MiITsz8nrgYGAgYD6+v77+4GB/IH+/vv1+PiA+fX2+vv+/fr18u/n5PPt07TZ8fbo5ff5gPX79vXy8e729vj274D3+vr0gv7+gIaB/trjoqKgn52cm52doZ+doJuakZedm5ujnJqbn52Wmq/x8Ofu+PPw7fDr6YCFgfjv+Pfv7e7x7/3y/P3Ao6GkoJ+ko6Klpaeqp6SfoqGioaGdnpyho6XTj5uXo6KfoaSjoaOkpKCio6ippJ+ioqOe1efhqObj7PTy9/Pr7uDigPn18e6A8+339urs8+3c6vDA1evr8YD6gPjm5vXv5+Ps5dbX4uTo3dDJwsbPxcnRpJzE4NPHxbvCpqGqurvl8Ni/0Nrf3t3j5+/n3tbb2N75g46C/pCQiY2NhouK986vk5X3ppmdp6uxzZPZj46Ojs763Jzrhc7FxcbHyc3MyKOIgIeIjJOltL24trWsn5qfnqatr6mjo6mrs7O7vsHHxMG8vLi0sa6ysKWmq7C3vr/Av8PExbOppamvxdjd4Ofu+/zu+fr+/4PMsMT3l5yQ+tTN686tl5acm+WMhev368i7ZcPctrGvs5anqMaKh8yV1/OGl/iH7uDs97DZnO3m1trDUM+AiP/1yeTN4vTf2dzylPqN3Nv2wsvJw8qwmImD9tr8usXxj5aXnJydnZ6ZmJmdn6KmqqmmpqOhoKOnqaempamsra6trK+vsc6J/9fe8vaBgEuOgYBqhpJLj4GCiYiFhYaFcFuKh5uClk5QVFtxgXGpjbWDlHfSxnWGioWDgHpupqpga2txeW15mJWFcXZ6d210a2hrX2tuc3J0eH6Ad3iGf394dnVzeXZsamVlbHiBfXy4d3x/hIV7en+Bg355fIGDdH2Mh4SEf4uKhoF+XIWHgIqIi42MiY2Tj4iJi4SJjoWUnJORqq2noolYYlpbX2FlaGRlaltcYmpoZmZcZV5dZV2pWlpap4qKl46RkZqNipCIi46KiIeNlJaOlJaWj5SQkJSDaJSOjYqTl56elpmQi4yQlpmdlpiTjH96boeRkZWTjI+BfJenoaqlp6Ktp6aYcpuYnaqlq35anJ6gn52jpaOjoYh0kI+Vl56ilHFweJybmZqZmJuXkZaWlpSWlZWXmZaWlpeTlpSYmZeblZGNjIyWlY6Mi4mLioeEhoOCgYmDhnBekpKSk5CNgnl2dXl7eXh6iI+Sl5OUkpCQkY1kcoiFgISBgH19gX99gISBgYWFf399fHZ5eHh9fHl6eHR7eHN1dHJ2cXFvb3Fvb29tcGxrbW2lY2lhXlytp6GbmFVYVlVTUVRXV1dWVVGcn1NQU3xgZWNgYmJhY15bW15gYV9cXV9bV1lYWFpaXFdVXFldX15bXF5iYl9aoYqDl1JSUlRSnJyfgJ2dUFGcT5qcmpaamlCalpicnqGgnpyYlI6NmJGCdZChppmYp6hXoqeioZ2dmJ6dnZ2TU5ygnphUo6RXVKWPoXN0c3JxcG9wb3JwbnFtbGVqcG1tc21rbXFvaGxznpKKj5mVlJGWkY9PU1KakZmVkZKOk5KckZqZgHRydHFwdHR0gHV2dnl3dXBxb3Bwcm5ubHBydZNdY2FsbG1vcXFvcnJxbW9wdXVxbnBwcG2Po55xmp6lqKitqaKlm5+urauoWqmirKqgoaqll6GnhZapqKdZsVytnaGtpJ2coZmMjJeWlY2HhoCGi4OGj29ngpWLhoZ5gGxnbXd4nKCOfouUmZiYgJ2epJ2Vj5OPkqdZXlerZGJeYGBbYF+ojntnbsGSfXuAgoedc650doGKvcyyfrtjmJGPkJGSlpaSc1tbXF1ibnuCf3t4bmNgZGFkaWpoZmdrbHJyeHt9goF/enx4dHJwcnFqa21vdHd5enp+gIJ0a2lscH6Nkpaanqirnqanra5ZgJGHkaZlaGDYk46di2tYWF5hr2hZuMivkoWU7IxzbmtrS09OXz9FYkNldTxHc0BxbHJ0T2FOcGtgZVdcPEBycVhrYW14aWdocEVsOWFhcFVfXU9bUkVDRHZpeGFxjVJWWFtaW1tcWllbXl9gY2RjYGBfX2BhZGZkYmNmaGprampsCmxuhV+5mZGPjkyAHTk2NzM8PB45Njc7PDw6Ojs1NGRSVkBIJigrMklbZaOJsISKbcC7dZ6/urm2s6vYoVdfYGZvYWZybmtkaG1rYGlgYGRUXF1jYWRnbG1jYm5oamZnZmVta2BdWVleZGhkZ5hkaW10dm9ucnN1cGtscndpcn56d3hzfn14cWxHaGeAa2xsbmxqbXVwaWtvanF3bnuBeHePko2IdEZSS0xQU1ZZVVVZS05TWllWV05XUU9VTolLTE2Kd3aCen59hXd2eXJ2d3NydHh/gXl/gYJ6f3p6f3Jbh4KBf4mNlJKKjIWBgIWKjpGLjYiDeHFnfISFhoZ/gnhzkKKapJ+jn6qko5WAm5ieq6Wse0yGiIuKh4uPj5GQeGd/eX6DjZGEYWBoh4OAgoGChH53fHx7eXx8fIGEgYGBg36CgISGhomEfnh5e4iHfXx8enp5dnN2c3FudnF0YlWGhYODg4J5b21ucnVycHOCiY+VkJKQi4uOimBqf314d3h6d3JzeXZzd316en6Af3h4d3Zwc3NyeHh0dXJtdnBqbGxqb2tqaWZpZ2dnZmllZGhooWVpYF1ap56WjYRKTUtJR0VJTUxMS0pFgodIQ0ZvXmNgXV5gX19ZVlZZXF5aWFpaVFBSUlFUVFVPTVVQVVZWVFNVWVpWUpJ7bn1GRkVGRH5+gX9/QEJ8QHl7enaAeXo/eXV4fYGEgX97d3Nta3dxZF13hIl+fYmIRoOGgYB8fXR5dnZ1bD51eHZwP3h3QT15aYhlZmRiYWBeX15gXVteWllRVltZWV9YV1ldWlNXWnBiXV9qZmZjaGJeNTk5amJmY19dWmBhaF1mZV9fXmBcW19fXV9fYWNiX1pbW1qAWlxZWFRZW152QkRCTUxOUFNTUlVUU09QUlhZVFFTUlJSbHt6WHV8gomHiYiCg3h7jIuJhkmHgIiHfn+IhHd+hGtygIKBRopIhnh7iYJ5dnhvZmhubWxmZ2diZmlhY2xWTF5rYmBiWmJOSEtRU25tX1Ziam1xcHV0dnBpZGdiY3KAOjo0aT8/PD4+Ojw7a2FaVWTwyaGUkpGTomydaXiCg7jBoXCgSmhiYWFgYGNkY005ODk6PURLUE1KSUM6NTczNTc3NjY3OzxAP0NFRkpKSUVGREJAPj4+Ozs8PUBCQ0NERkZHPDY1NjhFTVFRVFdeXVddW11fMlJMUFw4OjV8VFUkZFhAMTE1OnJKNGl2X1FJSXxSSUZDQyIjIyQgFiUhKCoWGSwYhCsuJywbKysrLCcpGBoyLisuKi4wLSwtLxguIykqLSonJyEjJSMkKUcpNDhCUi4xMoUzCTExMTIyMjQ1NYQ0GDU3OTs6OTo8PkBCQkJERUZQNmxWSDo3HQF/hn4Bf4t+AX2EfoZ/C4CAgH58fH1+gIGAhn+CfrB/AX7PfwV+f39/ftF//4CegAF/hYCFf42ABn9/gICAf6iAhH+FgIV/BICAf4CGfwGAmH8BgIx/AYCEfwiAf3+AgH9/f5yAjH+DgI5/moABf5iAk34Bf5B+A39+f7h+BH9/f36If4V+AX2HfgyAgIB9fHt7fX+Af3/Qfgt/fnt7fn9/f3x+fod9BH9/fn+EfgF9hnyFfQF+hH0Efn59foZ9AX6GfYJ+i30Bfo59AXyFfaZ+AX+FfgF/AgIEAIDn/YProtfk7YWF/eve0ce8x83ayY+YjNOJkaCy1LDI6uXt/LPVhvyGn5mZlYyG0fuCgo6SlKWf0dq4ppijqJefoZeVgpyrqKOjq6nDvbm5tqitsKitsrWvt7GtsLvDyr2t/7Kzp6ejtKqno6urprCwsKqwrrGxrqyus7q/tYnH0YDLv8TFzdLP0sPV0svCu8XMztHFxsjLurWm25CLhIaLkaSLlJeYjIuSlpCZm5yOjYqLjIyFg/KTwcXKzsLGy8rWzc3Jx8rCx8HJyMrN1s7S0MPEycSHw8HIyb/GyM3HzMfExL/Fx8etmZaywcS/xcXEx8LDvsKwqdPe3Nff0M7Y2TjXz8/I1K6p2dbc3d7c3d7kx6TB2Nja29nV1NfVzsOho7HX3ODi3tzc29nb29zf3Nvc29zc2tvb1oTVgNjc1dTW09PU18/P0NLNysnMxMfKxOPFzdDMy8zOy8zLzcrJx8W/s6Ofn6GinqGio6LWnaiwuLi0tba3trO1ubq4vb27u7e0srW1sq+zsrCyr62rsK2trq2qrKylnpGIhIT49/Dy9PHs6u6FjI6NjYqJiIaHh42IhIaIiYD3/vWAMYCAgYP+/b2NkI2Sh4SEh4eIiIqMiIWEiIeIhYeGjIaCh4WFhYuMjIuNiofbysro//2EgoCBgYOAgf+A/v33+/v5+Pz8+f/59vn6gPvw7fDx6PLq3rjR8vT8+Pf3+oD3+ff0/YD48+3h/Pn3+vrx7vD8+4H++tDb94GEmqCgn52dnJ6dm5+cmJWdnpuboKKfnaCXl9HQ+Pnx7fL19vn49/PwgPnz6uz08/v59v369P/22qOkoYCipqKkpKOnqKeoqaqdoaGioJqcnqOmqeWBnZyfoJqjqqOhmaGkn5yhpaSlo56jo8bb8uul7vby9vTx//f26fX5++3w7fr2//Lu9vju8vPn6qvDz93s5ebm8ejj3O3m1tfr59PT397Tu8nW0djSxsG0kMvTzdHAwbmyrK25ydfP3IDKw8rX083c6Ofg1NLX2uX0hJeei/D2i4mBh4iHgM6ljZ/6rp2gp6qwwfHYtIiUvpWBl5yOqOXGv8bN086jkpCOjY2KiomOlaa8zNfAuLSps7a1s6mvr6+yu7rEx8LCw766tba1vbywnKy2ur/Cw8nIxaedoqO1zdXQ0dDLzcnLzIDOztLU0KGt3Nn5gdvnwrf1xqqUmZyW2P/D35Caqq2we62wr7jswLWphK3G09WCiOz5gPXdzv/H0M2H8vCI3sniks3Pg+Lq0PTs5dKbkuzS5c3HzcW0tZ2XrtO7jsamureXo8fR7Pb8oKKjoq6yu9bVx8Xc1tne9oD/g42Rl56ipg+oqq2wsLCztra6+4Xs1OuAhpROj2aDiYtOTpWKgXt1b3Z4f3VZcWiLV1xofZ6Pq8nJ2+KbwHXec4mFg4B7e6i4X15ma217dJufhXpweHttc3ZubVxuenZyc3l1i4aCgX10eXx4e36AfIR/e36IjZOKfbiBhHx8eIl/fHmAgXyCg4N+h4WGhoJ+gYiOkoxeh4+Ai4WKiYyPiI6Dk5WPiIOMkpWUiIyQkoSBdZRjX1pbX2JxXWVnaF9eZGdkampqYGBdXl5fWlmoZoWIjJGIjJGPmpGSjo6Ph4uFjY6OkpmRlZOJio2KYJGNlJaNk5ablZmWkpGOk5WUgG9sg4+RjpGQjpKNj4uOfnuepaSgqJ2cpaWApJ6fmqJ+d5qVmpydm5yeo491jJycnZ6el5WYlY+HbnF7lpmcm5aVk5OQkpGSlpSUlZSWlpWXlpKQkpOSlZqTkZWSk5WWjY2OkYuIh4qChYeDl4qRlpGOj5KNjY6QjY6KhoF4bWtrb3BtcnNzdZJscXqAgHp7fHx9enp/gYCEg4Fwgn57enx8enl8e3p8d3V0d3R1dnd0dXVxa2JbV1ejoJubnJ+alplWXV9eXVtbWVdWVltYVFZYV06ao5VSUFBRU56efWVmYmZdWllbXF1bXmBbWFlbW1tYW1pfWVdaWFlaYmJhX2FgXpeGhZOiolRTUoRTgFFRn1CdnZmbnJiYnJyZnpmXmp1Rn5WWlZSOlZGMdoebn6akpqaqV6ampKCpVaCZl42inp6jo5mWl6KhVKaki5aqWl1wdHNzcXBvcG9ucW5qaW9wb25ycnBvcWpqj4KbnJORlZeWmJaVk5NQnZeMkpeTmZiUmZaQnJiZdXRzc3d0gHZ2dHd4d3l5eWxwcXNxbGxsc3V4oVVmZmtsaHB0cG9qb3FtbG9zcnJybXBxiZmrpnOlqKanp6ezr6uhp6yvp6ekra22rKqwraipqqGidYiNlqGZm5uknpyUpaGRkZ2ejoyVlY1+h42LkpCEgXhfhYiGjH9/f3lycniEj4iWiYOJgJKOh5OamJKJipCQmaRYaG9hoqlgXVhcXVxXjnFjdcuXf32AgYeRuqyRcYS0iXF8f3GCr5KLj5WalnNkY2FgX1xcXF9lcX2IkIB4cmhucHBwa3BwcHJ4d3+AfX1+enh0dXV6enJjbnR2enx9gYF8a2NmZnGBh4eIh4SHg4SEhIWJgIqGbZGojZ9RisCDeKOJalVYW12av4atbXR2dqr1f3FtcX1XTkw5UVxgYD9AZWc6bmJcelphWkF0c0RoXmhIW1w9ZW9jdnBsXEpEZVpgU1VbWVJRRkZRYlhIXE1SUkZKV2d5iY9eXFxcYmRqeHh0cn16fH+NSpdNU1ZaX2JlZmdpDGxtbm9xcXWqX6mMkIA5Ox87Mjo5OSAfPDk5OTg0OTs9OzRGPU4sMDxTd4eoxcLLyo6zcOGKwLu7ubfA2rZYU1lgYG9keHZqaGJqbV9maGJjUF5pZmFhZ2JxamZmZV9lbGlrbnJueHRwcHR0dW5lk2tvaWxpe29ua3BybXRzdXJ7dnd3dG9yeYCCe0xtcIBvamxrb3BqbmJ2eXRtand8fXpvd317bm1hc1NQTE1RVGBOVVVVTU9WWVRbXF1TUE5PUFBLSYtZc3V5fXV4fXqGfH56eHpydHB2ent+g3yBfnJzdXVUg3+GioSJi46Gi4mHhYOIiYp3ZWF3g4iFhoSChoGDf4F2c5WfnpymmpulpYCjnZ+bonZkgX+EhoiGh4mPgWd6i4qLjIqFgoSCfHVfYGd+gIOCfnt6eXZ5d3qAfX5/foCAgIKBfXyAgoKEioKAhoSEhoh8fX2AenZ2eHBzdnOJfoeMh4OEiIKBhIWBgn54cmpgX2FmaWZpbG9vimRncnl4cXN0c3ZzdHh7eH58e4B9eXV0dnZ1dXh3dHdvbW5zbG1ucGxscGtkW1VRUpOQi4uOkYqHiE1SVVRTUVFPTEtMUUtISkxLQoWPf0ZEQ0RGhIRyY2ReY1dTU1VWV1VYW1RSUlVUVFFUU1lQTlNPUVJbXFpWWVdWh3ZweoaGRkVFRUREREFCgEB9fHl8fHh5fYB8eH93d3x+Q4J2dnVybXVxb2BufoKJiYmIikeFhIF9hUN7dHFnfHd2e3pxbnB5dz57emd1jU5RYmVkYl9dXF1dW15aVlVaW1paXV1bWltUVG9ba2tjYWVnZmdkZGJiNmpmXmJmYmhkYGVkXWZkeV9fXF5iXmFgXmJjYmNkY1RZWYBcWVRVVVtdYIE8RkZNTkpSV1JSTFBTTk5TWVVVVE9QUmd5iYRahIeChIWFkIyJf4aJi4SEgoyKk4eEiYyGiImAglpqaHB6cXJzf3h3bX18bW55e2pncHFtX2VqaXFuZF9aRlxgXmZdX19ZUlFVXGFba2NdYmtoYWdpaGJcYGRjaYBsOEJGO2RqPTs3OTo5NmBYVG/9zaWXkpCUnbCcg2yIsIRpbXJla35kXWBiZmNJOzs6Ozo5ODc5PUVOVFhLRD82Ojo6Ozo+Pz5ARENHSEZGR0VDQUA/QUI+NDxAQkNEREdIRjgyMzM7R0xISEhGSEVGRkZIS0xLPU9XSVMrSm9MSIBpWUAvMjU4aIhWZENDQUFbhU5HRUdGJiMkHiUnKikWFiopFioqKS0nKCgXLisYKyYsGiorGi8vLC8wMC0bHDIsLykpKiolJyMgJygqKSskKSgmKS44QU9UODc5ODY4OT0/Pj9ERUVFTStYLS8zNjg7PD4/QUNERUZGR0liOGNJRAN+fn+FfoJ/jH4Cf36FfwuAgH99e3x9f4GAgIZ/gn6xfwF+t38Bfpt/AX7Qf8uAAX+bgAF/r4CJf5KAg3+FgIN/pICGf4mAAn+Aj38BgJJ/AYCFfwGAjn8BgIV/m4COfwGAj3+agAF/l4ABf+B+hH+Cfod/hH4BfYd+DH+AgH58e3t9f4CAf9F+CXt7fn5/fnx+fod9CX5/fX9+f35+fYZ8iH0Ffn59fX6HfQt+fX1+fX19fn19fod9gn6qfQJ+fZJ+BH9+fn4CAgQAgLnR4/W61sXO9YGA8uTRwMjR0c7O1aeM8qqIuYjEs4DJh5WCrYbd2ZuZlpOOj3P7hpSRj5qjobfZwaWYl5qXmZ+Vo4aVn6Ggn6Srur/Ovr/IwbiwrqmssrSztayoqrq7vvmmt7S0sq6qqaeno6yxsam2vrW3vra1srm3vL6485eWgJ6ZmZain5eaj5eRmJaRmpiWmJevvMfEu8aAiI6RjIyfmJmKkoyVl4qWlpeUkomPjYeIjY+KjdOuxbvCxMDCxsnHyr3KzM/NxdLR0dbI0dPQztTTx72Jqrm7vLfFyMrI0cfCwKiepMHLz9TOzMTBwsPGw8XDxMHIqarW3tXS0dTXgNHJz7rPyt7d2tba2dnWwqO62tjY1tPX09TNydDPzs3PwKKlrNDc2drc3dza3Nze4Nzc2tnY29nX1dTW1NLP0M3P0dHS09PU0cnIxsnDyMK+gLXJycrOzMvOzMzIxMTBw8HCxMTEx8bFxcLCvb+/tq+ooZuamJiWk5WVlJCXm5mZbJiUlJGSkpWUlPuFiYaEhYKA//7++/z1+IKEjo2QjI+Ni4yJhYiJjoyNj4yIiYmIh4iFhIX/gYCAhffv6P6B//+AgPO7j42OiYGKioiJiIWIh4mGioyJhYmLh4CBhIOCho6HiYLdzs3wg4GBgYSDgIWFg4SC/YCB+ffx+Pb0//33+4D8+vT3+vz59vb59+u5yPr6+u/y9/v8/vX4+/nx+Pfy9fXq9u339Pjt7IOCgoL5yYaVlZmXgICAg4qeopqdn56cmpednJyZnp2fn52dhLDw8fnw9ICD+vD2/fbw9/jx8Obk5/T99Pf8hv/EiqCjgKOkpaKjo6alpqalpJ+hoqWkoZqcoaOmqf3nn5+coKCeoKihmZ+go6GgnqGioaOnjMf27/HewoD5hIGCg4CAgIWEhf/06vTq5t/l9fby7efx+PGp4fju7Ozw8ODi1dng6OL05+ny5d/Yzs3EydLPycnTxZyz1fHZy8vEucK9yM7cgOHa5e3X09bW7Ovn6OHf3N/W7IONlJmbgv2LhYiKh+m9mI+fhqScmaass77Cys2ElYb9jbaGm9qE1MDDvZeWnaOdlI2LipCWmZufprG4u9Pr4NzTy7e1sLC0uLrBwsHBv7q2tLW2ubetqbC4t72+vcG0nJ2en6nHzdDT1tTZ1tDQgNXX0MvL16e889z9gdTtt6fdtKSXl57F45nQy6Gss6SDnbe8vJWxy9rSw8Kp1fvUmeWXhY2Q2+qH7Pby44Khk4Df+oiN/vCL2f7Iy+GS3IORkdn7mN6n2sKswOCskLOG2bvfyrev4qeUrZSajoG966GG2P2Lx8vbjIaRk5CFiYeIEKm8t9HuobO0tre3uLviivqAgYaIlHWDd3uTTE2RiHtweH5+fHyBamSxfWCFaqSYbrF6gXabdsO7h4WDgXx/X7Zga2hocHh2hZ2OeG9ucGttc213X2hucXBuc3iDhpWGhYuHgXt6d3l/gYKEfHd4g4OHrnaDg4SDgX99fXx5gIaEfIiRi46UioaAiIeNkYuuameAbWlqaHBtZmliaGRqZ2RramhpZn2HjIl+illdYmRhX2xoaFxjXmZoXmhoaGZkXWJhXFxgY19hkHqHgoiKhomMiouMgoyOkY+JlZaTlo2Vl5GOk5SJhWF+ioyNiJWXm5iflpKQfHBziZCVm5iXkY6OjpGQkY6PjJF7fJ+ln6CfoqUwoZqfjpOQnpyal5uZm5eKc4WenJyZlpuWl4+Jko+NjI6Bb3R2kJeTlJSVlJCSk5WXhJSAlZaTkpCQk5KPjI6MjY+QkpKRkpCGh4WGgYWAfFOAi4yMkI6Pko6OioaFgYSCg4WEhIaFg4J/f3t+f3hzbmpoZmVkZmJmZWRhZ2toaGdkZGRjZGdmZq1aXFdWV1VTpaSno6SanFJSWllbVllZVlhXVVdXXFtdXlxZWlhXVVlWVVWAoVFQUVWbkIOcUJ2ZS06WfmRhYl5WYmBeXVxZWlpcW15gXVlcXVpUVVhXV1xkXF5YlYyGnFRSUlFUVFVUVVRSU1GcT1CZmZSYl5Wfm5aaT52bl5qdoJ2Zl5qXkHKAnp+hmZ2kqKutpaaopp6ko6ChoZejm6OgoZiVVlRTVaKHX2iAZmtqWVhaXmNxdG5wcW9ubWtvb29tcG5ub25uW3KUlZyQlFBTnJGSmZWQlpmTlI+SkpiblJWbVaF7Y3J1dHV1dHV1d3Z2d3Z0cHFydHVybW5xcXR5tZloaWlsbGttdG5obGxxbm5tcHJxc3Rgh66pq52KWq1cWlxcWlpaYF5eta2Apaylo5ylraurpqCrr6tynqeenaGioZGWjpOYn5iqmpytoZ+XkI+Gh5GMioSNg2l5kaaTjpCHe4J8h4yWnJKhp5CMkJCfnJmZlpWTlo+fWWJna21arWBcXl9do4NpY3Rtjn94f4OIkJajpm2Kefl/l217q2WfjI2IZ2Vrb2pjXlyAWV9kZ2hrb3V6e42hlpGKgnNzcXJ2eXt/f359fHh1dHR1d3Vuam90dHh5en1zYmNjZWqAhIaJjIuOjIiHiImHhIWMb522jKBRhsN7bZd+Z1ZWW32adpWbeYl/cV/urXR0T1FeZ19aVExkdl9KZkY6QUNibkFucXZqQlI/P2l2QENMdW1EZXhbXmVFZjxCP1xpRWNJZFdOV2dPRE88YVJlWVNOaE1GT0VKSEVqeFFGeIhMdXZ9TUlSUU5JT05PX2tteY5ib29xcnJzdZdgsxBGRD0+NT03NzweHjs7ODM3hDuAPzxEclNAXVmblGu0dHdsknTP7r28urm6xYCzWmNeXGRsZmZybWViYmJdX2VibFJVWl5dWl9ka2p0ZWRsa2tpaWdpb3J0eG9nY2lpbY9gbGxvcHFwbm1uam91dW55gn2Bh314cXd2en98j1ZSV1JRUVpWUFJKUk1SUk9WVlRVUmoqb3BoY29HS1FUUE5cWFdNU09WWE5ZWVlXVlBSUE1OUVRQUntmc2xzd29zhHaAbHZ6fHpzfH9/gHeBhHx4fHtwblRzfX6BfYuNj4uSi4aDcWVleoKGjYuMhoKChIaEh4SDgodxdJeem52dn6WimqGPfnqIhoSBhoWGg3dic42KjIeEiYSEfHV9e3p4emxeY2V7fXl6ent5dXl7foB9f3+AgIJ+fXt9gYB9enx4eHuAfoGCf4B9dHVxc29zbmxKdn+BgISAgYWCgn12dHF2dHR3dnd4eHd3c3Jrb3FrZWJdXV1cW11ZXVxcW2FkX2BgXl9fXVxhX1+iUlVPTk9MSpOTlpOShYVGRU1KTUlNTEpMS0hLS1FQUlRRUFFOTEpPSklJiEVDRUqCeGuDQ4J+PkGAfHRhXF1YUF5bV1dVUVNTVFRYWVVQVFZSS0tQTk5UXFNUToV9dYNGREZFR0dIR0hFQ0RBfD9AeXp2eXZ0fnp1ej98e3h7fYF+eHV2dG9aZn9/gnyAh4qMjYWGhoN6gH16fX10fXd8eXlxbT8+PkB6Z01SUVdWR0pLT1NfYVpdXlyAWlhWWlpaWFtZWFlXWElSZGVsYmY4OWpiYGViXmNmYmRgZGRnZl9gZTlrU1BcXV1eX11eXmBgYWNgXVlaW15eW1VWWFldY5VuSUpKTUxMTlRQSk5NUE9PUFNTUlRURWmJhop+b0mLSkdISkdJSE1MTI+Jg4uCgnqAioqJhYGIiYWAWnmDdXJ3d3ZrcmtscnhvgXN4iX+AeXJxZ2lvamhibGFNWWx8cW5vaFthW2Fia3FodX5oZWtncGtnaWdmZmhfaDo9P0RGOnE/Ojw8OmVYUVFsicClkJGSlZqPk5VmiXrkeotjcJZNb1tdWT89QEJAPDk4Nzo9P0BBQ0dJRlBaVFKATUg/QEBAQUNDR0dGRkZEQD8/QEA+OTg8QEBCQUJFPzIyMjM5SEpMTEtLTEtJSkxMSUhJTj9TYkpUK0hxSkJgUj4xMDJKZlBpVUZMRT83lG1KRyQhJScoJyckKS4nFyUZFRYXKCkYKi4sKhYbIRgsMBgaMC8ZKjArKy0bMRoYGCw+MBgpJS0nIikrJiQnFisnKyorKy0pKCsoKSUjP0UoJkhOKEZFSCkrMjM0NDc1NT5ERkxdPkZGR0dHSEtaOG6JfoJ/jn4Nf3+AgIB/fHx9foCBgIh/AX6xfwF+nH8Bfrh/AX7Of/+AhYABf4eAh3+cgAF/hICEfweAf3+AgH9/oICEf42AA3+AgIp/AYCpf4SAgn+egIZ/goCSfwOAf3+bgIJ/loCGfgJ/fop/0H6GfwF+hX+Nfg1/gIB/fHx6fH6AgH9/z34Je3t+fn9+fH5+iH0IgH1/fn9+fn6EfIt9An59hH4DfX1+hH0Lfn59fn19fn59fX6FfQh+fX5+fn19fop9AX6OfQp8fH19fHx9fHx8jn2JfgJ/fgICBACAjfjA157fgvzcyOTw5unq5+nT1Mzc0enrg6ypzoHa48OXrtWFvrKamZWOiH3l/P6MlZagm6S539msm5STl5SimZ6ElaSipKapq7u7tLvHxcPArqixtLu2u7Onn6yzx8e+9q+lsbawsqyrt620pKyzsqystbi6s7Syrbe3srmUo62ApLCdrre3saavoZ2brbOssa23vbnbzsjG1633i5uZmZ2WnYyNkZ+Om5mglpiLj4qQkZaRh4iEidDBvsC9uMK+xcvOwtPP08vOzc/V0tTNzMzMz9DKzcycnLrDyLy4w8i6n56nw8S+w8nMzs/Qy8i/xcTGw83MxsPOxaWnycfEzcmAyc6kktjd3Nzc2t/ew6K61dnW09TY2NbT0dbTzMjNzczPzNXOsaWpwNnX2tfZ397a3dzg2tnb2dzV1tbY0NPX08/N0tDO0dHFzc7NzsvCxp6Qy87Mx83MysvOx8XExsTGxsfEwcPHxsO+wMDAvb69wb/BvL25uru1t7S1tbi3s7SAuLi5u729vLq4gYGamZmZlJSWmZKSj5GRkI+OkYyLjYyLjIiKjpCOj4+MiomGg4P8hIiD/fyCgvf9gPP39IOF9Pjx/PC4j4uEiIeJg4aNi4mJioaDhouLi4SDiIKBhY2J/c3M1LfjgoKCgYOCgoWDhYaEgoWB9Pv/+/T2/fbz+f6A/fr//oD8/vn69vb05rrC9fPu9u3w6fr79PL18O3x9+7ugIL/+P379f777fqDhPfxyJKanZudnpqXl5WPgPf+g46bl5ianJibmpijnZ2dmaPy8fD3+PH99Pjw+oHs5u/69/Tp4Oz0gPuAh4GAqZqipaKhpaaen6SjoZ6kpaWloaGAo6agm56jp6mF1p2boJ6goKGknpqYn6SioqChp6OepJrp7vT576/7h46E/P76hYqE+fn99/jt6N/o7fDv8PX18O/15aPl+vX07vf33OLg5eTd5+rm8eXh5+Hf4cfV1NzOyNHMvJnA1Ma1vru6tK7K0Nbb7vLn4dfNz+D57Nzq5OCA2PmJm5ygoZCFjYGEiouD6cukkJvooZyXoqqzvZC23OjgpaKpqricibP00r66r6ukoKCgn5iXl5aen6aws9Do8ujg5vT8++vc0b+2ury9wL66trCzsrCsqLaytri4qKqcko2ZnbHUz83JzdXRy8rHxcrAv8TM05zD/Mvq5LzvxbKA37Wfjo+V9JGJzJmWtbGVnZCszJ6xuL7R3ri/1a38soaEn4mGleHV7Orj/qaS7oWIhoDf6YiOg+uCvezhvPqAyKv+/ZmVkYGF5Lnfu62gvs/khJS22YHw8MK3vpTFl56SloTyot6Fk5iA8IT4iIqKlZCPi42Nn7+PqK2vsbKys9GAZreDj2aFTZeDeImPhYiLi41+joaLhJCcX4eMq2283LGCmLp1pZuHhoN/em+5ublobm10cXmKpp19c2trbmp1cHReanFwc3N1doODfoOOjIiHeXN8f4SCiIF3cHl/kI+Ir392gIaAgn5+iIGJeH+HhYGAi4+RioaCfIaGgotqcXqAcn5qe4ODe3F5b2todHt3e3Z9hIGhj4mHmHqpYWxram1mbF9fYW1ha2htaGtgYl5jY2ZjW11aXo+GhIaGgoqFh46QiJWTlI+SkZKVlZaRkY+OkZKNkZRxcoqTl46LlJqPdnR5j46Gio6QkpKUkY+Ij5GSj5aVjouUjHd6mJaUm5mAmZ55Z5ygn56enaGgjHOElZmYlpaYmZaSkpaTi4eLiYmLiZKNenNzgpORko6SmJeSlZOZlJSXlJaPkZKWjpGUkY2Lj4yKjI2CiYuJioeBg2loj5OPiZGQjI2QioaDg4KFhoeGgoSHhX94fX5+e359gX2AfX57enp2eXZ3eX17dniAfHx/hIeFhIKAWVVjYmJhYF9iZWBfW1pZWlhYXFZVWFpYWlZbXV9dXl5cW1lXVFOcVFlWoZ1TUpieUpWQjVJUjZGNm5V5Y11ZXFtcWFphXVxcXlpXW19dXVdVWlZVWWJfrImKkX6VVFNTUlRSUlZUVFRSUVRQk5qdmpeYnZWTmqCAnJmgn1Gdn5ycmpuYkXR5mJeTm5SZkKSoop+knp2fo5iWU1anoKaknaeklKFWV6Kfh2drbmxub2xpaWZjWayxXWVua2tsbmtubWt0bW1ta2yWkI6YmpKelJiQmVKPjJGbmJaOiJKWT5ZNUlBScm5zdXNzdnZvcHVzcm90dHR1cXGAc3ZwbW5zdnlfjmZlbGttbG1wbGdmbXBwcW9wdXJudGukpqqtonWvXWRdrq+sX2Rfr6+yrq+lo6Cjpaenqa2wq6isnm+ZqKWim6epkpeUm5qVmJ+ZqJ2bo52ZmoWTkpmOiY+KgWmBkoZ6goB8enaPkZSZqKudmJOJiZaqnpOinJiAkqxgbW1wb2NbYlpdYmRcpY1xZXK8h353foKIkW6Qsb2+k56qjZR8a4u4l4aDenZvaWlqaWRkZmRoaW10dY+jqaCaoaquraCTi395fH1+f3x6d3JzcnJwanNucnR1a2tjXFlhZXOIhoWEiIuIhIKAgYN+fYGIjmykwoCTkHnKg3KAmYBmU1RYnGFqlXVzjn5oa323gE1QUlNiZVRUYUt5Wz4/Szo9Q2FkcGpjc0ZHaz5CQjxdaj5AO2o8VnFqVnY/YEl1bURBQDg3YU1iUUxIU1tlPENTYjpraVRRVEBWRUlFSkSCVHtITlNIiUuRT1FRWVdVU1RWYXVVZ2tsbnBxcouAOm1KRjM6ID44Mzw8Oz0/PT8/VElGRk5pR3OAo2i3zpt5jbJ1uM+/vry5t6bwtK1gY2JmZWpueXdpZ19eYF1oZWlSVFpaXF1fXmloYGNwbWlsY2BobHRyd3FnYWZnc29timhfanFtcGxseXR4Zm12dXJzfYKFfnhya3R1cnpZXGSAXWdQYmtrZFthV1NRXGNfZF9nbWuIdWxoeGOKUFpaWVxYW05PU1pSW1lfWVxRUU9TU1VSTE1LTndybXFvbHNtcHZ5cH17fnh6e31/f4F+fXt3eHl1en9iaH+HioOBipCHcGlvg4J4e3x/gYGEgoN9g4SHg4uKg4CJgW1ykpKQmpmAmp1yV4aJiIaIh4uLeWJzf4KEgoGDg4F9foSBd3F4dXV3dn94aGBgbHt3eXZ5gIB6fnyFf3+DfoB6e32CenyBfXl4fHl2eXpvdnh2eHZvdFxfg4mFf4eGgIGDfnt3d3R3eXt5dXd7eHFnbm9vbXBwdXF0cXNubm9qbWpsb3Jxa2+AdXN3fYB+fnt5VUxYV1ZUU1JVWlVTTUpISklJTkhHSkxKTElPU1RSU1NRUE9NSUeESE1JiIVFRX2ERXt4c0ZHcHRxgX1yX1hRVVVWUFJaVlRUV1NPVFlWVU9MUUxLT1lWmXp9g3F+R0RFREhFRUlGRkVDQUQ/cXh9enZ3e3NyeoGAfHmAgEF9fnx9eXl1blpgeXZ2fnZ6b4eJg36Ef3x+f3VyQEKBe398doB9b3hBQnd4aFRZW1hbXFdUU1FPR4qQTVRbVVZXWFVZWFhgWFhXVlFnYGBoa2JrYWVgZjhfW19nZWReWWJlNGExNjQ1UVhcX1xcX19YWWBeXFleXl1eWlqAXWBZVVZaXmFNZ0pHT0xNTExQTEhITlJQUVFSWFRQVVB+gYeLgl+NTVNMioqJTFFLhoeLiIqDgH1+f4KEhoqLhoGFeFFqdnhzbnV+aW1tc3Rucndwg3h4hX58fWdxcHhtZ2pjXkxdb2dcZGFdWlVpaGxwfYFxbm1kYWt7bmZ0b2uAZ3hBSEZGRz86Pzs9QEE8bmJXVWjasqGPjpGUmWyBna68lJGbh4dvX3GEZ1dUTEhCPj9APjs8PDs/P0FFRldhZF5ZXGRmZFxUT0dDRUVFR0ZEQj4+PTw6Nzs7P0A/Nzg0LywyMz1NS0lGSE5MSUhGR0pHRUZKTjxTZkNMSj55T0eAaFpALy4wYD1HcENEUkQ7PlWHTikmJCUoKyUmKiMrGBYWGRcYFykoLSwrLSIZLRcWFxgsMBkaGS4YKy8wKDAYICgxLRgZGBgYJycsJyUmJissFiQoLBkuLCgkJyIoIyQlJiJJKE0qLS8uYTJiNDY5PDo4OTs9Q1E3PkFDRUdISVQBf4V+AX+Rfg1/f4CAgH17fH1+gIGAh3+DfrF/AX65fwF+nH8Bfst//4CtgBB/gICAf3+AgH9/gH9/f4CAhn+bgIZ/j4CPfwGAnH+CgIl/BYCAf39/jICCf5CAjH8BgIp/AoB/hIABf5yAAX+VgAF/h34Jf39/fn5+f39/0n6Nf4V+AX2Hfg1/gIB/fHx7e31/gIB/zn6Ce4R+A3x+foh9C4B+f39/fn5+fXx8i32Hfod9An59hH4HfX1+fn59foV9BX5+fX19hX6JfQV+fX19fox9A3x9fIR9A3x9fIt9iX4CAgQAgLS/jIGbzd7d/PTY2+z4//r49NvphfaNrorLs4bF95Cfxv/7kJ+amZaSh3ff/YWTl5mhoqW92NCympSWlpeWm5mIiKmrq6mirrnBxcjFwsPJuaepsa+rrbCusbC2wdLFgqO3tra5pqiiqrO1r7KxpLCqpba1tru8uLa8t6+olKWtgKq0oqe3vr6lpaelpZusqq61srO0vMPLzNbG0I2WlZSYj4qJjJmLkZWUoZaRmJuSmZKVjZCMiYeB/r3NzsHGwsbGycbK0dXc0tHPys7WzsO2w8fK0M/X0amOur+9oJWZqsLCwsfFxcnMy8vMzM3PycG/wcXHysfJytTEvqCZyNvmOby9u9TS19/f3dvHorfb29vZ0NXW0tLV1tTU09PS1dXU1NTX087RzrqlpqrL3NvZ2dfa2tbT1dXV04TUgNXX1tXU1tHQ0c7LzMvMzs3HuubKz87Q0M3Izc3HzMzIwcfIx8bFxMbEwsbByMS/wsPAv73Bv7u6tri5s7e3tLS1t7W3uLi4vLy7vLmN55SWkZWUlJGUkJCMiYiLjY+Oi4iKiouJj5GOioiJiYmLhYGFgISH//v++4H8/v/3/P3+gPz19fb39vHovomOi42NhYmJiouLi4eMiYWAhoaHhoKE19na746MiYnhxYCGhYSEhoWDhYeFhYH68/r8+vz69ezy/Pj59/f4/Pf68+/47b6ktOb59uvv6vT0+/nx7+328vX25Pz/gfbv+fLu7fjm9YDy4MeWlpScnJeanJmdnJmbgJyemo2A/vH2g5GUnpaZoZyctdvt+Pf8/vT29vX09uro6Or8+vbj6fT6//iC/vjnwZqfnpyfo6SinKKhmpqip6ShoZ6io6Gjn5yhqJHGn52gnpefm6CimaCZnaGfo6OlpqPzz+ny8vfr3saLhoSFhP//8+/6g4WG9e/v84P0+uzlgOno7ezo+Our9oaEhYD48+7v8PH35vHl3fLlzcjOw9DQyeTgz93g596hqdHP0dXXzM7T1eHY3d3j3s7b5vbq6u7u5enp84GLmKCpoYyIjJGDgpKPgM6dhpHdipKRoKyzu9+O26+nqv6NoryCn/CX8czJ0tbe1sa4qJiXmJ2eoqzSgPr36fDz7Onl2u709oSE+/LU1Mi1tLKzs7Cnt7mysK2aiIaLj5GTpr7BxcXIy9DP1M/QzM3LyszU2ZzKkdPm1LLot6LJpJ2QkJ2JtJTmqdSe1aSe6ZKz7q+t07nDhfH+iqysk/HT/PuU6r3Anpqb4P3l74TgqYCJlpKc5/GW48riQ+Hdk/rihorLy+mEit+H1IDKwdKSisKr6YDyu8ze3urXwt+slMLNspaYqJydl4WTj46VnJ2JrrGkjo6QnteUp66wsrOAcHpgX3KIhYCXlIGAi5KYlJKPhpJRnWB4bauYcqXhgI6q3tt+ioaGhYN4Zqq6YWptb3R3eo2gmYJxbG5sbGxxb2FfdHZ3dXB4gYmLjouHiI6Cc3h/fXl8f36AgYSMl45ceIaEhYh5e3d+hoiEhoV4g4B8i4uMkY+HhYmFf3tsdHoHd4Nyc4WLioRygHRqdXN2fXt8fYOIjo2UjZBiamhnamBeXV9mXmRoZW5oZGltZGljZV9jX1xbWayEkZGHjIiLiYuLj5SXn5WVk5CRmZSLgYqPkZSRmpZ4Z4mNj3dtb3uOjI2QjoyOk5GQj46Qko+JiYyPkpSRk5WcjYd1cJips4+HhZeWmqGhoJ6QgHSCnJydm5OYl5KTlZWTk5KRkZGPjY6NkY2Kjox/b29xi5eWkpKPkpGOi4uMjY2OjY6PkZOSkpGRjIuNi4iJhoeKiIV9pJCUk5eUkIyRk4qNjoZ+hYiIh4aEhoOAhYGHhH6Agn9+foF/e3x4eXl0eXp4eHp8eX2Af32Bg4GFgmKbgF5gXWFhYV5iXl5aWFVZW15cWFVXWFhYW11bV1dZWVhaVFBUUFNVoJ2cmFCanaGZnJubm5WVl5aWlpN+XWFeX2BYW1pbXl5eW19cWVRZWVpYVlmQlZmpZGBeX5qDUVdWVFRVVFJTV1NVUJiSlpianZuXkJigmpqamJmdlZqXlZuVgHtscZCcmZCRj5ybpKOdnp2jnp+ejZ+kVZ6WoJqXl6GQnFKclIhpaGdtbWhrbGpsbGlrbW9qYVewqaxbZWlvaWtybW15h5CYmJ+ilpmYlpaakpGOjpqZmIqNlZiclE+am5KHbW9tbG9xcnJudHJranJ3c3BxcHNzcnRvbHJ4aIVqgGhra2VqZ2xuZmxmam1rcHF0dHCokKOnpquimIthXl1fXrCwp6avXF5eq6apqVqlqZ+en6CmpKCto3CrYF1aVqWkoaKkpquZoJeTpp6KhY2Ejo+Gn5yMmpiemGxxi4qOk5eLjY6OmJCUlZuXi5OcqqGhpqaeoaGpWV9pbnVwX15igGZcXGlpXZZvXmqsb3dze4KIkKhvrYuHmuSEj55pfrxwsJGQlpaclYh+cmVlZWhoa3GPsrCjqKulpKGVpaqtW1qppI+OhXl2dHR0cmt3dnBvb2VYVVlbW15se36BgoODiIiLiImHiIaGiI2QbK1phJGHdc+AbY13Y1RUWll4cLB9dad+nXNsqrSEblBMXlNYP3B5QUtURGxbcHJGZ1VXTEhHXmtgbD5lVTo/RkVKam5JaFxuaWlHdGs+QlhXZDk8YjxXN1hXXEA9V0lmOWdNVl9eYVpSX0pCWmNZSkxVUlVSTFJSVFdbXFJnaWNYVldfhFtma25vcIBISjg2REY7NT08OTtAQUJBQ0VGTCdXPlNgopFwn8ttgZvX5JTDwMC9vLWWzLZaX2BiZWhsc3V0bWNgYmBfYWZlVExdXmBhXGBlaWpsaWdpb2hfZ25saWxvbnBwcHJ6cUhhb21wdWVpZmx0end3dmp1cW5+fH+Fgnd0eHFqZlhfYoBfbV1cbnZ0WlpYWl5SW1tdZWZmZ2ttb212cXJSWVhWWVNPTU9XTlFWVV9YVFhbU1hTVVBUUE9OS5BtenpvdHN2dHZ1en6AhoCAfnp7hYF5bnR4enx6hYFpXX+Eg2xhZG1/gICE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4uPn4eDd3N3lgOnn84SKi5uloZqJjpyXjZOUi+ywiJDW4YiLnaqwuMPIztnDxKW6rZSel5vUifTc29/d3N/e0sW5sbCqzerw+ID2/fz94O/r7ubj6Ozs6PiCg//17dW6sKyytL2sjISIj5Kboa6yt7q+w8jNysvQyMbJxcHBxsWM253d29e83bqlgMSenJGVooXiiZjq9cSjuJiT0aSBm9XMydiZquOb+97M39WMj6DoiYCDiYfU2/La47aO/qXo+f/22vnkg4D4z9nTxefXr4CFioP1nLPFs7K4jfexudS21+bj7e+Iy7nZyt3m5p+uvNK978CYoZGir7KWr6eQmJeXqqOnoNWMp6+ygG9xcnVZYJ6MeoGXk4uLlU9SUVJTY22mmKrBpN23i22Tc8K6iIWFhIF0X8Rva2pvcXF2eJKwn4h9dXRqcG1vb16rc3dzd3NzfIeNkpOOj5KHg4N/g4WBhIWEgIKOj4+LrIp7hYOAfXlveYeKiH6NhYaMhn+GgYeSiYWNjohzdHGGgGx8hHh1fH14dGlqc3JwbXV6foWAgIaDiYOQoGJpb29rZ2RhW1ljX21iY2VgX2ZkXmFeXWRmY1xcoG+JkY6Uj4iJiIWKgoyWmZaUk4+Uk5OQio+LhoeOlZ2SsWd5kpmQj5CQj5CQko6Vj4+Sh4eLhouJhoaLkZWXk5idko6OeYSBgGaXmpuWm5eajHKAmZaclZqdmJSWkJKVlJWRkZOTj4iNjpCPjpGTk5CRko93b2tzjJWUkZOSkJGKjo6Nj4yLj46Nio2MiIGEhoeMioyOhJyJko2Uk5KNjIiLkIuQioiKioeIh4aChIiEg4uJioaBg3+EgYKAgX9+ent5e3x+foSAboN9fn2Cgn+Eg2qGXV9hX2JgX15cWlpcXl5dXV9gYF1bXF5XVVRWVldXV1NRUVFPn5qWmZxTUJ2bUFBNlJmbm6BSnpqWjHuCXWNcYGBeWldbW1daXFpaWFhXl4mWnFxiYWNjYmFkXl6GjlVUVFVVhFOAUE+Yl5qZT5iRjZSaoZ6Oi4+RjJSaU5yCll9eYI56gZianp2clZueVaWmop2Zmp2fmpidn5GZnJqdWJ57n25naGppa2lnb29vbGZtbWxqaWlpYmZpWqqwq6pbaJyBnpGNlJqXlZSUlJiZkY6NlU5PUZWOjpiYkpueo4Gya21vbGqAa3BxbW9xcnVzc3V1d3NscnFxd3Vvc291a2xubW5ua2Zoamtta2xuamx1dWeZr7SstK6fmXJfXF1hWKywsqqsWV9eq7WqrbGwWlmtr52foaOjpaB6t15eXFe0s6+spqCqWauoopiPj5aLenx5dXyDfY6Nk4+BYIGNhnuKhYiSoJGAk5WWmJ2VlJiTlJiYmaCkpKtcXl9qcm9sX2NtbGRpamWpfmBoo7Vtb3qEiY6VmaCtnaWQqJx9gnp7o2aynZudm5qbmpGHfHd2cY6lqrBarbOzs5mjoaSfnJ6jpJ6rW1ywp6KOeXNzc3R8b1pTVlpcYmdxc3V4fYGDhoOFiYSCg4KAgICDgmCzcoiIhnjGgHCHcWVUVl1Xl2F0pMWfe4NpZNGkQEtgXFxoSE1lSm9fVF9aQEFLZ0I9Nz49WmByZWxfRHhSY2p0cmJ2bT47dV5iYlpsYlA3OT06aEVPVU1KTz5pS1FdTlthXmFkN1BKXllhZ2VFUl1oX3hiU1dOW2JlVmYOY1heXVtjXV9aelVnbG+AR0hJSjQ2W0w2N0FFQkBDIyUmJys7SYmKo7+f16N9Yohyze7BwMC/vKyE5GliXmBjYmlqdYJ1bWtnZ11kYWJhUIldYV1iX15lbG5wcm5vcmtrb2xwdHB1d3ZxcXpyb2uJcmVtbmxqZ19ndnp4b3xydXx4cXhzeYZ8dnx9dWFfW2+AVWZtYl1kZmBcUFBZWVZUXGFob2lpbWlrZHaAU1leW1lWVFJNSVNQXVJWVU9QVlZQUk9NUlNSTU6CWnJ6eX54c3Rzb3NveICDgoB+eX1+fXx3e3hwc3l/iYKaXWuCjISDgoGAgoKBfoaCgol5eHp0enp3eICHjIyLkJSJg4RwfHeAVYCEhYCGgYV4YmyEgomBh4yIg4V9fYB+f3t6fH16c3h6fn18gIKDf39/fGZeW2F5goB9f359fXR6eXd5dnZ6e3p3enl2bnFxdHl3en50iXyHgYeGh4GBeX2CfIV/e31+enp2dnN1eXZ1gYCBfXR3dHl2eXV1dHFtb21wcnR0fXhufXR1dHt7eIB/ZXJNUVRTVlNRUU5NTE9RUlFRU1VTUE5NUEpHRkhHS0tLRkRDQ0CCgHl+gkRCg4NDQj95fH9+hkWBf3txZXVXW1NZWVdST1RTTlFVUlFOTkyEeYePVVtaXFtaWFlTU3V6SkVFRkaEQ4A/Pnh2eXc/d3Fudn2DgG9sb3BudnxDfWt9UE5SdWZodnZ7ent1e35EgoJ+eHR0eHt3dHh5a3N2c3RDdFyDXFRUVlRXVFJaWlpXT1dXV1RTUlJMT1NHho2KiUpUelpsYl9lamVmZWRiZmdgXl1fMjM1Yl1bYGFfZmlvWYtUVVhUUYBUWVtXWVpbX1tbXV9hXFVdXFpfXFdaWVVOT05NTk9LRkdKTE5LTU9MTVVWTHOKjYuQinpyV0xJS09GiIyOh4VHTEyEjoSJjoxHRoaIfX59f3+AfV6OSEZEP4aFhYeCeIFEgHx1cmxvdGtcXltWW2NdaWNpY1xJYWtlWWRhY2ZyZYBkZmZobmhmaWVmamxudXh1ej08PEBGRkY+QUhGQENFQ3NfTlq05IqJjpKVmp+NjJiPppSdmnZ0bG6MSHhkYWJfWlpYVU5JRkdEV2VoazZla2tqW15bXFpZW1xaV14yM2FaWU5BPDs9PkI8LSgpKywvMzo9QEJERUZIRkdKR0VHRIBFREZFM1w6Q0NCPXhJQVtPPi0uNDNXQFBydGBFSDo5nHodHiMjJScgIicXJyYjJyMWFhkqGBYXGBcoLC4rKh0aMh8xMDMvLjQxGhcvLC4vLC8sHRgWFxUnIyYnJCYnICsnJygiJCgoJScWJicpKSkrKyUtMDQzNzc0NjM4Ozw7Qg5AP0NFQkJAQD1NND9ERoR+gn+Jfoh/C4CAf317fH1+gIGAiH8BfpV/AX6efwF+un8Bfp1/AX6gfwF+pn/OgAF/u4ABf6SAhX8HgIB/f4CAgIV/AYCGf5KAhH+KgIJ/i4CEfwGAjn8HgH9/f4CAgIt/AYCRfwSAf39/mICEf4KAkn+DgIt/nIABf5SAiX6Ff4V+g3+GfoJ/i36Ef4d+AX+wfo9/hH6CfYd+Dn+AgH98fHt7fX+AgH9/kn4Bf49+gn+mfgJ7fIR+A3x+foZ9DX59foB9f35/fn5+fHyJfQF+hX0Efn5+fYV+hX0Efn59fod9gn6HfYV+kn0BfqB9hH4CAgQAgK+xr7O3tvOO6OTp7vbx+PuBiZyr0ajRmbeYl5umtfOFsamZmJiUkYt9842UkpuclpSiyt7fwp2fmZmTmZ2ch4iusLW3sLW0vsXOzdnU0Mu4w7WqpamxsbS2tba+zMySoLiwuLmsrbapobCzsayxuqujw7C2s7e5vL/AwL+irq+ygKqqsr21rrKntqilmqqsnqCwtbu1xce9wsPdoIWSj5ShlJOQjpSIjpuKhYySk4+PlpSDg5aRjJaUity1yMrOxcXFvNDOycXGxs/b08/LzdbMxbW0yL6/xMvGv5Ovz9HSwcHFycPFyMfMycjHxMDFxsi/v727vMLKx83Ox8jMrteWPpLH39vY1bmkvdXU0s3N1NrY0MbSxMzK09PQztPWzdDSzc7T1NDTz8/R09HOtv+ml5qgrc/Z19jZ2tTS0tDUhdCA0c/Ly87Oz8/Kyo2fwcTLyMvFxMTLycjIx8fBvsPDxcbExcfGx8LExcfHxcLBxcG9v7+9vLy7uLq5t7e3ta6ys7e1trS2taW7jpCQj5CPkZGSkpKRkY6Sk5aXko6Ji4mMjIeHhoaDg4SHgvv29/X09/T3+vb4gYL9+Pj5/P/+9/mA+ff46sf8j46OiIeLjIeDgoaA4NTa34WPjY6RjY+RkYyQjI6LiO2/8YSFgISAgv2B/uz2+fry9vbx8+79/OTs7uzz+u/O34qQjI6Rkov8zr7j8Pz6+O/1+/v2+Pb29vvv8fb5+ezx+P3/wIKaoJeWmJebnpqemJ+fm5qem5mYnJiAmZqemp+coZ2a4Kb08uvz8P6A9/D19O/y8eXo8f79gvjr6vn58+/k6qWen5yem5udmpmjoKWmo6GioKKmpKSjoqanqaWmoJ6gop6hl6CmpaOmnaCdo5+bn6SbreyE+O30+/L43sWDhPP4/ICChPaEg/nv7+Pl9ff98/6B7uzo6fCA9u7rp/P4/4ODgfT0+u/p7+309PXt5dvWyM3V2dTPx9Dg3erU2KeQy9fk6uLu8+Xd2OHu8erp6ebv8/vy7unx6oGPnKGxr6idlZWbl5iil4buxZWU7cT7jZ2ssbW8ia7mqqekhYX17s6ij7yN+Nfd4+jo6ubo4drg7uvt9IaJgoCA6Onr9+7m4+Pm7Ovq6enq6u3wy9Xn7Obcv62inZ2UmLC1uLq9vcLGy87NysjGw8DAwL66hNSrztbZutu8r86qqJyiq5ueuJG82u/IzaSixIDg+PmAqoKBkrHvuISJ98Pk5PHV2rLf09qZ9YOBjZ6Ogpffio+EqvfT9IPWs6fT0+49n4PnnMLSyoLZwtCE/NG1q4LwhpvOtuK7xIKFwPv+hPHp5ebD06CC3NOyy76vsJWYi5KblLili5/ApLGHqYBsbmxtcXCjYqmWkpGYl5qdUFVlcpiJr4CZh4aGk5jNcpqTh4aGg4B8bMJqb2x0dG5seZmqqJFxdHJxaW5zcl1hfHuBhH2CgIiOk4+cmZOQgYyCeXR2fn2ChYKDiZeWanKKgoaGfHyEeXSEh4OAhY1+dpSCioiKio2OjoyOeHx6fYB2eICIf3h/eIR5c2R1dmpseX6EfouOg4aHnHJdZmRncGdlZGNoXmNuYmBiZmVkYmdmWFtoY15kYVqNgIyPkoyMjISTko+Mi4qSmpWUkZKZlIyAgI+HhYmQjolpgZmam46OkpWQkI+Ok5GRkpCLj42QiYiJhomOlZGWmZKSk32VaV9nj6KcmpiEcoGUk5GMjZadmpGIlYiPjZSUj42Qk4iNkIuNkpOPkY2Lj5GPjnyqbGBla3SLkI6TlZWNjIyKjomJi4qMi4qEhImKi4yIimBxh4mRjZGKi4uRjYuLiYuCgISGMIWGiYqIg4aKjY6Kh4eLhYKGiIWBgH99gH57fX9+dXt9gICBf4CAdH5dXl5eX15fX4VggF1hYmRlYV1XWlpeXVZVVFVUVVVWUpmPj5CTl5acnpmYUFKcmZqbnaCgmZuZlpmUhqxiYmFbWVxeWlZWWFKRjJWdX2VjY2ZjZGRkYGRgYl9cn3+cVFVQU1BTn0+ajpWWmJSXmZWXlKOfiZKRjZGdmIiRXGJdX2JiXKaLfZKWnp6cgJWboKCeoqCfn6WZnKOlppmanp+lfVppb2hnaGhtbmtuaG9ua2ltamlna2hqa3Brb21wbWyda5iVkJaUolKYkpaUk5STiIySm5tQmI2Lk5WXlIyTbnFwbG1ra21ra3Rxdnd0cXJxcnVzdHNydnZ5dXdzamtraWtjam9vbXFqbWtvgG1rbnNteKNcrKSor6iqloxeXqytrlhaW6lcW66jppqcpaasprFaoqKho6itqah0p6WsWlxcr66upaCro6ajpJ6VkI5/h42QjYqFjZiVoJGUc2SKkp2jnKSnnpSPmKOknZydm6WstK6sqa+lW2Vtb3x8eW5oaW9sa3RtYauNammugJnJbnqEh4qRaIa2iYeOeX/eyaiCcpFqtZianqCfoJ6hnJacqaaorV5gXFuhnp+ppZ+cnJ2jpKGgnJ6foaOJkJ6hnZaAcWhmZF9ic3V4eXx8gIKEhYSCgoKBf318fHhZr3l/hYp6u350i3ZrWl5kZ2t7cIeusZuQcG2XpoR1dDhNaDw8RVJtUj49bVBkZW1fXkxkX2NJcT8/PE5DP0FjPkM9SHRedj1iT0lkY3BNOWRFUlpVOVpTWzlqWkpHOGg4QllOYU9PNzdNa245a2doa11lTz1raVpmZV5iWFtVWl5aa2FTXGthalJogEREQ0RGRVw6YFJFREZFSEcmKzlGcXykepaJdXeEiMBxrMvAwMC/v7qb3WVmYWlmX2BqfX59dWJnZGJbYWZlUE1kY2lvam9tcXN1cHp4cm9md3FnYmRsbXF1cXBze3ZUXG9pcHJoaG5mYHF2cm91fG5mhXR5e3x8fX97eXtmZmBkgF5ja3RnXmVgbWNaTF5eUVNfZGxocnRqamqCX05WUlZfVVNRUFZPU15STlFXV1JSV1VKS1VQTVRSTn1rdXd5dXR4cX17enZ2dXyDfn58foaAeG1senRwc3x5dlluiIyNgYGGiYWCf3yEhYeJh3+Cf395eHt5fYKHhYyOiYiIcXxYgFZ6ioaDgXBfa3x8fHZ5gomGe3GCc3x5gYF7d3p+cnh8d3qAgX2BfHp+f319a49XTlRaY3h8en+BgXl2dnR4c3N2dXd4dm5vc3V3end6VWV5eoSBhn+BgIaBf318f3Rvd3d4eXh5fX59dnuAhIaBfH2CfHl+gXx3d3VydXNwc3ZzgGlxc3h4enh6e29vT09RUVJRUlJUU1NSUk9UVllaVVFJS01RUUpIR0hISkhJRHxycW92fX6Fhn9+QkN/foGBgoSGf4J+eXt6d55bWllTUVVWUExLTkqAfYePV11aWl1aXFxbWFtWV1RQiW6ARUU/Q0FEfz53bHJzdnR4enZ4d4WBgGp0c25zfHlvekxSTE5RUUuIc2R1c3p9fHN4eXt6fXt7f4N3eX5+fXFzd3Z8X0dWXlVTU1NaWlZZUllZVFJWUlBOVFFTVFhVV1ZbWFd8TGlmY2dlcTlnYmZjYGJiW11eZWQ2YVtZXmFkYl1lTldWU1VTVFZUVV5bYGBcWVtaXV9cgF5cW19gYFxdXE9OTUtMREtQTktQSk5NUE5MT1NOWX9Jhn+Di4WIdXBLS4iHhkVGSYRJSIh+gXZ6goSJgYlGf357fIOIg4FWfXqARUZIh4eHgHqDe3x3e3Zva21fZmttbGhkaW9pcmluWUtobHV8c3Z2b2plbHV2b29wb3h9hYKBMn1/d0FFSEZOTUtGRERJRkVKR0B1aldasrj4iY6SlZeZYnWcd36Kc3LXtJR1Z3ZJdlxbhFw1W15bWF5mZGRoOTs3Nl9bWmFfXFlaWl1dW1lYWFlbXUhMWFtZUkI6NTIyLy88P0BBQkJFRUWFRgFFhEOAQjJaPj9AQz5uR0JdU0MwMjY6PlBLZGRoV0w7OmKESjM0GyAcGxwfJR8VFSkjKCgoJSYhKSYnGCgWFhwaGBgjKxkYGCUvKi4aLSgnLSotGhUqGCcpIxcrJiYVKCYkIhUmFRUlJCsoJhUWJy4uFy0qLjQzODIfOTw5PDo6PTw/P0ELRUNJQzw/REJINEGHfgF/iH6FfwyAgIB+fHx9fX+AgYCIfwF+/3+QfwF+xX+ugAF/3IABf6KAi3+CgI9/jICEf4+Ag3+GgAJ/gJZ/h4Cdf5+AiH8BgIx/AYCKf52AAX+TgAN/fn+Ifgt/f35+fn9/f35/f4p+AX+MfoN/tn6Qf4R+g32Gfg9/gICAfnx8e3t9f4CAf3+QfoR/tH4Ce3yEfgN8fn6GfQx+fn2Afn99f35+fn2EfIh9gn6LfQ1+fX5+fX5+fn19fn5+hH0BfoZ9DH5+fX59fX1+fX19foR9BH59fn6FfQZ+fn19fX6HfQF+lH2CfgICBACA/Zuqqam0urn9i9fr7PyEhpKgw53PsJHv7PWrkrCF5fKZlpeXk4yAeIOWk5aUnaisuMzk07ygopyblpCel4WNu7vEv7WwrKmzusPIx8nIvLiwra2wsbSuqKestb/NtfCqq7G+tbqwuKilqrC+rLKzvra8sra6tbG0v727wKCquq6Arrq2vLe1tbm2raurqqqrnKK0ubm/xdLIx9PM16KYk5uTlZuPlZKQkJOHlJOYkYiNk5ORi5GRj4qSk+/AwcXIxMzMzMXHucjGydHKy9TP3djYys7Kw7/Gw8u8zK6a0dPX1tfUz9TBwsHJzcrLzsfMxcTAz8vIw8bAxMnEwK2j3+CA3rKTtKmgwdLU0dbW2dfWz9LIycvFxc7Jzc7T09DQ0dTRz8zL0dTW2NfW2KGJtLK0tLGxlZKWnr7P09HP0tDO087PzczOzszJzM7MzKiIx8nMysrJxcXDwsPHxsLGwsPGyMjFx8fLzMXIx8jHyMbBxcbExL68vLu4trm7ube4urqAt7W0uLWxtLW2r7qPjpOTk5GQkZKQkI6NjpKQkY+MjI2Ni42MiYmKg4WB/ICBgPf7gPz4/f6AgP778vDv+fj6+vb2gIL++fjrwuSMh4eJiYP01M/X4ICQjpKTkI6MiYmMk4uOkI+PjImLidTCgoSEgvf69/X39Pfu8fv59ff2/PmA7O7z6eHS8ZWQjJKLjoyQj4+Sl/a8vOLv9u/vgP/9/uzv6Pn09PL35vj158SXmJ2dmJqZkpydnJyWnZ2blpyenp2am5qVnJ+hoJ6fkqiA/Prw9PKA+Pb49/v08fjt9fT6/oWC8/D3+f7t9OvDn6KenZ2eop2Zn6WipKCfoaGen6WAoqKhpaioqKenl6ShoqCgnqGio6GhmJ2Zm6CYm4PS/v6D9vTo6vrurYOEgoSF/++C//X0gfSA8uHq8ezn9PiD8/Xn6PDz8+yr/IWKiIOE/PHw6fT+8ff25+Pl183f49Xc1NTk4/r19ujd0JCv2ODj9vWG8uvm6vb69vf1/PmA8+mA0+2A9ICPn7SwtbSfmJSam5mTlZD5zZajg7/li5mmsLm/0vrc4NXLoNn5o77/poi2k4Dr6ejh7uzq9oGDgoCB+f6A7eX18ero8+vo4+Xw7e7u6uXdxrrAwsTIyLS2rsPV3uf6gID87u7q4dnY1tHMzcjMy9DQzIjct9vk3qzlyMCA3bO1qqq2nrDGseGCn8LPpqecnpyDivOKlpCPnMC+n9iV8emJ1tDix+b8/ZLKx//84s7Ly8708v/d+dXJvdPqv7HBie/ozIyu74COwJaM+In39vfb3syKiofN16CcpPmM5fKA6uuWmfWmiIHN8r7Mvd6slp6ksLKhr5yelamksMSAlmBpaGZtcnKrZp+gm55RVF5oh32tlHrT1NqRfZVvv8+HhoaGg31yZWdxb3FudX2Bi5qonIt0d3NxbWhzbWBlh4ePioJ9end/hIuNjI2NhIN9e319foB6dnh8g4qUgql6eYCLg4h/iHx4f4ORf4SFkIiNg4WJhYODjIqHj3V9hHmAeoWBhoKAg4eGfHl1dHd1aW19goCGipGNiZOLkHFpZWpmZGpkZ2ZlZWlcZWVqZFxgZGZkYGJiYF1kZaCIhomOi5OSkIqMgY6MjpSOj5eTnZqdkpWSioaMiJGDkH5wmZufoKKhmp6Oi4qPkpOVmJKVkI+KmJSSjo+LjpOQjntznZ2AnHxogXhwho+TkJOTmJiWkJKHiI2HhpCKkI+UlJGQkJOQjoqJkJOVlpaVmXBacnJ0dHBxX15jaoGNkI6MjoyJjoiJiImMioiEiIiGhnFikJKSkZGPi4qJiYiJh4KKioqOjoyHioyTk4uPjpCQko+Kj5CMjISCgH57en5/fn6AhYiAhIKBhIOAgoKBfYBgXWFiYl5eX2BeX11eXV9fYF9cXF1fXl5bWFdYU1RSoVBRTpCTTZ2fn5tOUJ6ak5GVnZqcnZqaUVKin6CZgJtgWlpdXFajjIuUnVlmZGdnZWNhX15gZ19jZGRjYF1eXpB+U1RTUZialZiZlJeRmKCal5mYm5mAkpWYj46MqGhhXWRbWlheYmRoba5+eY+VmJSWUqinp5abkqGfoKCikqGbkoFpaGtsaWtqY2xubW5pbm5rZmtsbGxqa2tnbG9wcG5uZm1RnJyUlpVRmJmcm56Yk5iTmJWYmlJPlI+RkZuOmJWGb3Bubm9wcm5rcHV0dXJxcnJwcHSAcnFydnh3d3d5Z3FtbmxsaGptbGxtZ2xpa25pa1qMsLBbq6qhoq+leF1eXF1ds6VaraanWaZZppufoJ6frq9er6+hoqarqaV1r1xgYFxesaeln6mwpKuomp+aj4SUnY+YkZCena6pq6CWi2N7mZucqqpepqKdoKetqaqnr6pZqqOAk6lbrlplb4B+gYFya2htb25qa2axkWpzYZaybXeBh42Rnb6ts62qjb3sjpnRhmyLaluloqCbp6ajrVxcXFtbr7Rbpp2opqCepqCcmZqinqCgnJiVhXl9foCGhXl8dYONl6e0W1ispaWgmJGNi4eDhIKFhomKh16zhoiPi2/Eh4CAmHtxY2NsaHWDiKpmepqQcW9ruY5AQHFBSEVCR1lXTF0+bGk/XFtiVWh0cEFZWHlzZVNbXFpvcXVfdGFaVmBtV1RaP3JsXTtHZzY/T0Q9aDxpam1bX1Y7OzlZWEJCR2k8ZWs3Y2FFR2tMPzxkdVplYHJeVVxeY2dfaFxeWmRianOAXTo/Pj1DSEhmPF9ZTksnKDRCYG2jj3bOx7uBcYhrx/u/v8DAvbqqh3BrZWdiaW5zeHt6c21kamZjYFpmYVNTcG11dG5qaGNoaWtsbG9vamxra2tsbnBraGpscHJ4aIJiX2RybXJqc2hlbHJ/bnN0f3h9c3V5dXBxe3l0e2NqbWAkY29scGxqbG9u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oqKioaSpq6uQmaKjpKCfnaGkpKWfo6KboqGb64SC//r49/r/9ObZq/SB9/jx9/r4gOzp6/Xn6OTl5+Le3OXr9PPt6Ov5//rwovXxhIGChIKB9vfn8f3t3N7b1Nrf6NPNzNXj8O3v9e3T1smO9snj3vH4+vni7/Pz4+vw7/OBg4GA+PT19YeQo6+9tK2pl5KUjI2TjoXn1pOPhsHdi5ams7i+xpi06bvpoJadg9PyjKaCgK+Qg+/q6Ob1/YOEhIOEhoqLiIT7/O3r7e/w7ujq7vL06+zcvMHEzMfFwrWpra6srrnBytTgi4qJiY6Ql5aHjI2PioqJhqbq5IOLhdaM+ubltrmss8TGzY+eoIHBtYDSwca+xP+Ewf/47PGWmLz84cn52tGv4NbtgMXoy9fXjvLUT/v+4cbTgNzexdvF0rzoqsy29cvAsK6pqZbnztPM1s/L346J6JOQhu/8gueHkYiAhI6ElZmH2IyxuJKLjNuk7dnl19PIy7SksK+3sqOPnYSAZVd2WmNnbXJ2fGVytV5icINyqJ6ei3islaW40GuPkoWEhYOAeGWwbW5xbnFyeYCHqLi4m4NyZGRiYWd2Zl6Ei4WAeXuCgYSKhY6Pl5WVh4OFhIOSl5KOjpGIhZqbXnKBeXiHhoSCgI52gYWGiY6KkYGEjZGQhoyHhY2Mj2+DlYuAh3qBgoSLhXp9enN0eHFvc3Fze4V8fo2YkZKMo2BoamNgY1hkYWVjaW1laWJnZGlgamJaYmRmamdgYl6aiImOkY+JlIyMjo6JiZSQk5aKlJaWkJibjo6Oh4aIi4h9YoeSlZuloaGXmJ2Xk5OVkpOZlIuIiYiIhYyNjotxq5KinaRDopdrZWhyk5aWmZKTipGQkYyJjZiXnJaYl5eQi5GMjo6RkI6Sk5OUmJSRYGN2dnt6e317fHt6d2pgYmdvg42PjY2PioSMgIqHiIh/o4qQkJGMiYaFg42LjouJjpCQj5KSkZCSjpCNioiDiI2OiY6RjYSCfn19f4CCgoB+gYGFg32Dg357eXZ4eXuOXV9fX2JfXVxcX19dX2BhXV5gX2BbWVhaWlpYW1lVUlNWUk9PTlBPoFGUj5CSkpmanKCflp1TVFShn6CigKOXgY5al4qNk6RfZWlpZmVoZmRgX15dYmFiYl5hYl9eYGBgYl6GiFGZkpKUmJGXjZKgUZucmlNTnpuZho2uZWFlYWRlYWVlYmprbGpdWl6agHaToKGfoaKjopikVaZVnKNUhKNrbGluamxrbW5ubWdpbm9ramltcHFvam9tbG9tgHFxcGltkZ2dlJiYn1Sgn6Gfop6dT0+clZiRVE+XmJmYnJKQc7BydXBvcnBybnBrbnVzcGxyc29vcG9ycnNzdXh6elxkbm9vbGpna25ub2tvb2pvb2ydWFetra2sr7GqnZd3qFqpp6Oor6ufnZuil5uWlZ6ZlZWeoqqppaCirrKrgKduq6ZdXFxdXFuqrJqkr6GTmZWMkZiikY2Jk6Croaato4+UimCpjp6YqKusrpyjpaKYoaanqltcWlqvra2tX2VzfIaAendrZmljZGllXaWYZ2hjlLBsc3+Hio6UcYy5lsCJhZaEtshyhWaGal2mpKSirrRdXl1cXV9jZGFesLGkgKSjpaOfmpqhpqWcnJV+goOIg4GCeG5wb25xen2Ij5lhYWFiZWZqal5fYGFeXl5ccryjV1xYlXy5p6WBcmRodIWLYm57XJmRXpiIjKTbjD9Yendsa0lGWnhjVnBjW0tjW2Q7VGhYXFtDbV1ydmhUWjxcX1JdU15RaUtbTXFZVExLOUZFP2ZeX1hiX15hPjxjPz06Zms2Xjo/Ojc5PjlDQjpcPVBTREFDa1NzbHRvb2ltZ19mZmtpYldeUoBGPkw4PUBFSUtOOUBnNDhBVWGclZmJb5OBlKrDaqHLv76/vbu1kt1vZ2diZmdsc3WFiIh8c2RYWVVUWWZYTW9xaWhjZ3BubnJqbm10cnNsb3R0dIOHg4CAg3lygYBKXWpiYG9wb29tfGRwdHR2e3l/cXJ8g4N1eXV0end6W29/doBwYmpsanFsZGVjW1xeWFhaVllkb2RmdH52dm+CUFdWUE9QSFRQVVNXXFVXUFZSU05ZUklQVFVYVk9RT4RzcnV5eHJ9dnV2eXR0fXuAg3V/goR+hIh+f310c3J1dmpVeYSHjZaSkYaHjYmIh4qHh42HfHl+e3x6gYGCfmWMd4eCi4CKglpWV116fXyAeHlweHh6dnJ2hIOIgoSDg3t2fXh5en59eoCCgYGFgIBSTVpbYWJkZmVmZWNhVk9RVl9xen15eHt2eXh6eXdzdHVvj32ChomBfnl2dIKBhYF/iIuKiYuMjImKhImGgn94f4aIgYeLh3p4dHNzdnh7e3l1eHh9e4B0e3x3cnFsb3J0gVJRUlFUUk9OT1RUUVJUVFBSVFNUUE5NTk5PTE9LR0VHSkVBQUBCQolFeXJ0eHh/fIKIhn6DRUVFg4KHiId9b39ViXx/hpZVW2FiXVtfXVtXVlNTWVdXV1NYWVVTVlZWV1N1cEF6c25yeXZ6bnKBQn19ekNEfIB7fGx1lFZUV1BUVlJWVlJdXV5dTktQgmlidXt8enp8fnpyfEB9QXR6QGWDWFhUWlVXV1laWlhRU1laVlVUWFpcWVRZVlZYVVpaWVJTZGxrZmtrcDxwbnBwcWpoNDVoYmNbNzNjYmJiaGBeTYlbXllXWldaV1pVV1xbWFVaWldYWYBYWltbWltfYWJHR1BSUk9LR0tNTk9LT1BNUVNPdkNDhouMjJCMhXh2W4BFgX55foN+cnJtdmxzb3J8dnFxd3uBgnt2eIePioRVhHxIR0lJSEeDhG92gnZudnJrb3SCc25ocHuDdXl/eGpyZ0d9a3p0gH1+fnB2endtdnx+gENDQoBDgn9+fkNGTE9UUE1NRUJEPj9DPztqaVZZWafOhIuOlJaXmmp5nYKsg4GOfKe1ZHNXY0Q6Yl5fYGZqNjY2NTY3Ojo5NmVlXFpaW1lZVlRYW1xXV1RGSUlNS0pGPjk7PDs9QUNITlU2NjQ0NTY5OTEyMzQyMTEwQWZVLC8rTURnX4BrWUQ0Nz9OUDdIVT5dUzJQSUtdf0IbLzc2MzMaHB8jIh4kISIdJCEkFCEkIyQiFCYiJyclIiMTJCUiJSIkIiYgISAmIyEjISAhGyUsLi0vLCoqFhcpFxcXLSkWKhcXFxkZGhkcGxkzHiQmIiEeOjRAPkFARUREQD1DREZEQDxAPoN9h34Df39+hH8NgICAf318fH19f4CBgIh/AX70fwF+n38BfsB/0IABf72AAX+ngAJ/gIx/g4CIfwGAhX+bgAN/f4CKfwaAf39/gICGf5GAjX8IgH+Af3+Af3+ggIh/AYCHf4KAhH+CgIl/nYABf5KAA35/f4t+AX+gfoZ/m34BfZB+hH+EfpB/hX6CfYd+EX+AgIB+fHx7e3x+gICAf39/hn6Kf6J+kH8Kfnt8f39/fn1+foZ9hH4MgH5+f39+fn59fHx9hXyNfQF+hX0Bfod9AX6bfQp+fn1+fn59fX59in4BfYZ+k30CAgQAgJ+lorLwlay3y9ba5rC/spPJw9DMjqG23/PLhNDXl5WTk5CMgG/piZWUlJSjqrPB2vbz0K6fmpWZqa2a3OqTl6e/xL+6ra6+w8bR1NLUxMC2ubS7v7+4t7Wtub7Lwf2qsbm0usSxr6mvqKa9tLC1ta+5ucDEsqu5vre6v7qLur/DgL63ur+2vKysra+rqbK5qZ2fpK64uby6zt7mzp/li5b+jJGTi4CKk4ycpIiRi4mHiY6ShoaCl5KYlZKTiYDLxsvRy8fIxs3DycTIyMzUwsjF1dvj1tfV3MrJy8/Fx8mUn9PR0NTS0svLzMrMzMnEwMTEx8zIx8XCyLjFuuXd5eG5UJmmz9LYyJiTwNfd29rY09LRztLY087H19XTzszRzcbN0tDW1tbTztWug8DIw7nBwMHAwcLCw7/Bvrq0rpSKkZSivMvLycrLy8rKxe2yxsjKhMmAysnLyMTHx8bHycjJycfJycnIxcTGxcK9wcHCv7y6vby8urq8ure0sqyztrSysraxrrO1sK2q5v2Qi4yLko+NkpKQjI+OjYuLioqJiIyIg4iJiYqHh4KDhIOFgoD69/n0/feB+/r/gvn6gPv49PmB9/b+99PKy83cg4yMi4uLj5KAjo+Sjo6PjIiMi46Mj4iIj5COi5KSjpGJiv66xvXy7t/c5u31+vj4/4L/gfPM3YeQkZGTloyRjpGUlIySlY6RiYaLi46M58zD44CE9fSD/YKAgYPuzIubnZyboJibmJqXlpmSlJibnJaaopuZmZmdn56in6WgnsPj8/n69vju8YGA9fj7/v/5+fz1/fn29YKE9fv+gYD5+Jzx94KChID5hIKCgYCHgoeGhomIiYqJhouIjI2QjYyCkpujoJmhnZyipKOlo6inpJu4+IqJjomFgPbp7e/srOPs7PP7+P7x4evz7ezi29Ps5N3p1efZ19Xe1Nvh6ufnn+D8gfj3gPz4gOuA5/Xv5+TZ3+3g2tjU0tTb5+j+9ey9n6CyrouV4/6Ch/X38fPu9vDu7OPq7fr18PPv8/SAjJuns7u3qp6cmJGPlJmE/IG/rZ3YyYeSna2zub7L59fqk9ugjoOa8I2Xr4e0mIXe5u/1/oWHiYuNjIqFiIb//4H76OLq5+Tf3efw9cGAwsXEwMDBwb25v8DN29LR2NPzkpGNkZiXkpaIjpaamZqVlLDZhZWemOuj/YHzvr+3tdbj5cHHluvXi6rnwsijmcKG/P2BnKXKisCZvPH9poXh2dvn5sruytbdxYH0quqShtzl5ffs+fjq3M2Xo8mKysLF3Z21vOvBx+TZ+e2A/Lks2ZCT8Y+sgYGhwbCmopONivmo+oyKlJekobbV7/j27Mzf1s7m1qaUnJqgpKqAX2Vnb5ZebXSCioyXfZKKcqilsKaFkZ+90K1vrLqGhYODgn50XbZocGxubnp/iJKkuLabgXZyb3KAgnOdrGxteYyRjYh8fIiKi5eXlZeKiIGGhImNjYWFgnuHh5ONsXh/iISJkYF+e4J9fY+GhIeJhI6OkZWFf4mNiImMh2OIi5CAioKGh4KIfXx8fnp5gId1a21xdX+Ag4CQnqeScJpdZq1eYmRdVF9lYGt0XGFdXFxfY2RcXVhnYmhmZGNdWY6Lj5WPio2MkomNiIyLj5uJjoyWm6aanJqfkI6QlYuLkWlzm5mZnZublJKVk5SUkI6NkJCQk5CPjoyPhYqFpZ2loYKAbHWSlJiMaGaHl6CamJiSjo2LkZeUj4qZl5SPj5KOiI+TkZeYmJaRlnhXe396cXp7fHp9fn6AfH57eHNwX1phY26Ai4uIh4iJiImGoXmLkZOSkpKRkpCRkIqMjYyQlJKTk5GUlJSTkI2OjYqFioqNioSChYODgIKFhYF/f3p/gn6AfH2BfHh9gHx5dp6pXltcW2BeXGBhX11fXV1bW1paW1pdWlZZWVhXVFNOT1FSVFBQmZOVlJeTUJ6colSgolSfmJObVKCfqaGKiIqNmFphYWBgX2JkYmJkYGBhX1xfX2FgYlxbYGJfXWRjYGJcXa56fZ2ZlIuKj4+Wm5ucoFSlU5yAh5ZeZWdkZmlgZGNlaWhcZGliZV9bXVtfXpqMgpVUVp2dVKNVUlNWmYdhbG5sa3Bqa2lsaWlqZWhqbG1nanJrampqbW9ucW1zb26CkJWampmdk5dTmpycoqOYmZqWnZmVlFBRkpeYT02UlGKhp1lZXFitXlpaWFhfWl1fX2BfYGOAYV1hX2JjZWNjVV1kamlkamdobG5ucG91dHJsfahcXWJgXFmsoaSjn3SXnJ6kqqivpZigpqGgl5CHmZOPmYqXi4yNkYyXnKKhoW+ds1qtqVmvqluinKafnZ2RlqWZl5aWk5KZoqC1q6WDbGZvblplna5aXaespaejqqioqaClpbCAq6WqqKmpWWNtdX6FgXhwb2tlZGltXrRdin1xoJtrcniBhoqOmLGpvHe1iXp6lspyeIlpiW9hnKSqrrVfYGJhY2NhXmFgtbRbrp+ZoJ2Zl5igqqqBgoKEgoCBgX56fn+Gj4uLkIukZWZiZGlqZWldYWZoaGllZX29YWRqZaCKtVyAq4d2ammAnaKIiXOroW6AqYeLdLCXQXl4PUpMWjhXQVBuckw4XlxhaGlaa1liY1Y7blVsPD5kaWZubG9xbGJcQEVaQFlXXGRFTlRtV1trYXVvOXFVYD9CbEBMOThHWExGRT88O2JGZz4+RUZPTlhpdnp5dmZvbmx6c19XXF1hYmWAREtOUmlAREhQU1NeUmNgXpqcqqR7fYaluqBpsui/vr29vbyugtVmaGJjYm1xd3uBiIZ4bmlmYmd0dGSCjFhZYXR9enZqZ3FtbHZ3dHVscG53dnt+fnd5dW13cnlzjWFncWxyfWtqaG5qaX51b3J3c3x+gYR0bnV7dnV2cVFxdnqAdGxvb2hvaGdlZ2RjaXFdUlNZXGVmbmh0fop2WXtMUYlNUlFMR05UT1tkS1BNTEpLUFNMTUlUUFVSUVFMSHZ1en96dnh0eHB2c3d2eoVyd3SAhJGFioeNfXl6fnR1fFpli4qKjo2NhIKFg4WFgYCAhIODiISDg39/dnRtioKKh26AWmN7fYJ2WFNwf4qCgoF7d3dzeYB9enaDgoB7fH96dHqAfISGhoSAhWpGYWJeVmBhYmBlZ2dqZmdjYV1bT0pSVF1rd3hzc3N0dXh2jWd7g4iHiYmHioeIhn+ChIOJj42OjIuOj5CPioaHh4J7g4OIg3t4fHl6eHl/fnp4eHB2eHaAcnN5cnB2enVwbpSXUkxQT1RRTlNVU09RT09OT05NTU5SUEtOTUpIRUM/QUJERkRDe3N2eXdyQICCiEWEiUaDenV7RYaHj4NsbnmCj1VbV1ZWU1lbWVpbVVVXVlNVVVdVWFBPVVZTUllYVFROUZtpZYF5dG5wcW52e3yAgkaFQ36AcIJRV1lXWFpOVFZZXFtNV1xUV1FMTUpOTHxzZnNAQXN2QXtBPj9BcGhOWFpYVltVVlVYVFRVT1JUVVdSVVxUU1NTVldWWVVbV1djY2RoaWluZGc6a2ppcHBlZGZjamVeXDM0XWBhMjBfYUR3ekRERkODSUdIRkRNR0tNTk1LS02ATUlLSk1OUU1MQUJGSUhFSkZHS01NT1BWVlVQYIBHSE1LRkaJfoCAfVlydHZ6gX6GfG10fXl4b2dfb2tmcGNwY2VjZWJscXt9e1N1iEWEf0ODf0h6cXl1eHpucIF4d3d1cnB2fneIf3tlUUhNTkFKdoBBQ3Z/eXp2f31+gHd6eYGAfHZ7enp5PkNJSk5RT0tJSEQ/P0FEOnhCa2hjq7d/iImOkZOWnZ2PoGeignl3lMBiaHhbZUk+XmBkZWg2Nzk4Ojk3NTg3Z2YzYFhRVFNSUVVaX2NLSUlKSEdHRkNBREVJTkpKTEtYNzc0NTg5NjgwMzU3Nzg2NURrMzU4NVRQZTSAbl1HOTlJXF1LUkyCZUFGWUhJPXNcHDg4GhwdIRsgHCAjJBUTISIiJCUjJiEjJR8UJRYlHRUlJSQmKCgmJyckICAjFSMjIiUgIB8kKi0xLjEvFy8oKhgYLRgaGBcaHRsaGhoZGTEcLx4gIiIlIiM5QEVFQT5EREJHRkE7PT9AQUKFfYd+EH9/f4CAgH99fHx9fX6AgYCJfwF+lX+CfqB/AX6+fwR+f39+2n/RgAF/voCCf6SAhn8IgH9/f4B/f4CEfwGAiX+hgI9/BoB/gH9/f5eAhH8GgIB/f4B/hICCf6GAiX8BgI1/B4CAf39/gICFf4SAAX+XgAF/kYACf36Gf6l+B39+fn9+fn+efoJ/k36Qfwd+f35+fn19iH4Rf4CAgH18fHt7fH+AgIB/f3+Ffop/A35+f55+kH8Kfnt9f39/fn1+f4Z9CX5+fn2Afn1/f4R+BXx8fXx8in2Cfot9Bn59fn19fo19AX6OfQd+fX19fn59jH4DfX59h36SfQICBACAv7e3ura8gKrN34OVp4rMxd/km5Ke25GdgvaYn5WVlJKMh3t15IqSkZKbnqKqw9bt8NStpZ6joZmruIySqaStk4KAjpGUq7/By8vO08e5t6egoaGqpaGinaKsuMb/tra/v721xcK8rKy5ubW1n7GwsLKzub3DwrG8urG8uoO4tLmAqrOrv763r7CsrKaptbOtpKijqqurvMjDwdLOxLKJmaSXj5mIkJaPh5OUkZaHh4SGhYiFkJOUmpSPkpeNgYjSytrDw8bCx8jFydTMxtHHzdTQ1eXa1NTYyczOysXJxtC8mMXVzcrU1dLNzMbFw8S/v8LBv83KwLajmtvf4NSkm8CA1dDW0s7U09aznafP3NzUzdTb0c7Tx8/L0dHN0djMwtXUzszL0tTLiY2vsLq+vLu3vbrEw729wMC8vry+vrq8uK6Wh4yRmqvAx8b/qMrMycjHx8nGx8XHycrLysjGxcTFwcTIx8XFxcTBwcG9wMG9vr+/v768u7i1srK0sLO1tLmAtLOwrq6vrq6sqoTTiYqSj5CNi46OjY2OlJOPjYyLi4aKioaJhIWC/P6Gh4aEhYP27fiChIP+/PiCg//x8Pn88fXi0dTX5PKHjY2Qjo+Rj4+Pjo6Nj5CPjYyKh4qKh4iKjo+KjYmLj5KMjJGNjYuL9sDe4+Pj4vP/9fn7gv3U0umAjJaSlZWUk5KSkI6WlpORk5GRkYuOi5OMjYySkfbSys3s9YCB/PbU55mVmZybmKCflZeZlJaXkZ2ampialqCcn6CdnKCjoZydnM7U8PL5+fT67u6A+4D3+vr4/Pn89fb3gPuE+vn+/vz86cGXlZaYmpWXl52amJGWio6PkZONj4+AjYyLjY2QkZSVm5GbnZ6foKCgoqeppqKip6OF2o+OioOGiISEgoCAgfu3/enn4+Hw8+ry6+zx3+Lc29HT84KC+/Xg6MrU4dvVz9ac2ff58vf49oH8+vfa3d7Y3N3W2u7u4OLk68y/squ4sa63uLm7m6j1iomD+ff27/H26tzl9IGAg4GIhoSFg42cpbnCwsatnJqhk4yMpaKZgcmqpvLC8o2TpK60u8L/nerez/G4hfq5jp+eroasgefP2fGGjpaZlJiUko6MiYX/9+79/Prt/oaE/Obd29rNxMG9wcvMztDV29rPyMeGk5aSkJCTko+RlZ2joqCbl6jrmKSrovPBiYKA6cLBubfu/Orh1aSCvIPg5rq/y7V88uOr6aS2lo2Uw/Lw2Ivq4urr+fijvvrDudffvZHyz5D81Lu+ztTgsta9pdfCvPTWldHo78PDw+Ha//zWjpLijIuMmIfAhfuhua+Vj5eQnZOOk5uVh/KKxaaDh4GHgKPz9YXfvrrasJ2dvs+AcHBxdHd9VXKDk11re3Crp77Ih4OOwnqGbs1/i4SEg4KAfW5jr2hubW10dXmAlaKztJ2Ae3V5eHGBi2ZsgHmAZ1xbZ2lqfYmIkpOVl46EhHdwcHF6dXBxbXJ6hJK2hoSMjouEk5KMfn+MioeIdoSDhIaJjpCUlYaLiH+Lil2GgYeAen14jIyFfX17e3d4hIJ7c3RudHR0gY6KhpKTjHhcaXFnYmtcYGVhXWZnZWdbW1pbW11bZGVlaGNhZGZfWGCUjpyJioyIjI6KjJeRjJeOlJiUl6Wfmpuej5KUj4yOipSHb5Cfl5SdoJ2YlpCPjI6Li4+Ni5WUi4R0a5qdnpd0bYiAlZGXk5GTkpmAbnCPnJ6UipCWj42SiZKOkpOQlJqPiJiWkY6NlJWPXl1wb3Z6d3Vyd3V9fXl5fH97fX19f3x9eHBiWF5iaXWDiYmueZKVk5GRkZKPj4+Sk5SVlJGQj4+Pi46SkpGQj4yKi4uHioyJiIuMi4qGg4CAfX+BfoCCgYaAgn98enp8e3l4dl2NWVpgXV9dW1xdXF1dYmFeXFxbXFhbXFlaVVZSmZhRUlJSVE+TjZhSVFOipKBUVKCTlZ+hmpmLiI6PnaddYGJkYWNlYmJjYmBfYWJhX19eW11dXFxeYWJdYFtdYmVfYGRfXl1erIKSjo6Pi5Sbl6GmWKSIi6GAY2tmaGtqaGVlZGNpamdlZ2RiY15hXWVbXF5lZKuSh4GWnFNUopuGnWtlaWxraXFwZ2lrZmhoZG9sa2hsZ29rb3Bsa29ycGxubImJlZSboZyekpNSnFCXm5uXm5idmpmWTphSl5eam5qajX9kY2RnaGNlZmxmZVxhWFxeXmNfYF+AYV9dXVtiY2VlaV5hY2VmZ2hpbHFzcm5vc3Fck2NiX1hbXVpbWlhXVqp6rJuZl5Wfop+npqKilpiRkY2QpFxdsauWm4OLlZCNipBqlKiooqmrq1qxr62Qk5eQlJaQlKanmJ2gpouEdmxybmxyc3V3ZnKqYl9Zp6alnJ+ooZigrVuAXVxiYF5eXGNvdYOLiIx6bWxxZWBhdHNtXI56drOVvG5yfYOHi5C9ebexpsSddu6meH5+immBXqWOlaheZGltam1oZWNiYV62rKOsq6uhrFxbsKOdmZeKhYJ/goiHiIqPk5OLhoReZ2lmZGNlZWRkaG5xb2xqaXnAa291bKajYl2ApYp0ammVsaSekHpkh2Ouqn+BiI+Sim1VbE1VRT48V3BsXT5mZGtrdHFGU3JSTF9mUEZtVkR3XU5QWFpiRlpRSWJXT2xiR2NnbFdZVmhmeHFjQUNhP0A/RTxZPHFJVEs9PUE+Qz48QEhGP3JBXlM7Pj5DPk95e0RzY2J0YlldbXqAU1NUWVteOkhQW0BOXWCcnLLBf3V6qW18acuRxL69vLu7uqGFvWBlZGRqaWxyeXuBhX1vb2lubmVxeVVabGFlUUhKVFdXZnFsc3R1eXJtcWdhX2FsaGNkXmJob3iSb211dnRuf313amp6eHV2ZHFxcnN4fX2BhHV5dWt2dE5wbHGAY2NhdHNuZmVjZWJhbGpkWlxWWltXZHRya3Z5dGVLVFtSTVVLTlRRTVNUU1VKSUhJS0tJUVNTV1JQUFJNSk16d4R1dXZyd3h0doF9eIJ2fYR8gpCLh4eLe3t9d3R3c390YYGOh4SOkI+MioF/e358fYB/fomHf3plVn6Agn9iXHBWfHl/e3l8fIRwXVt3hYh9cnmAd3V9cnt3fH57gId7c4SCfXl4gIF9UUpZVl1fXFlVW1ljZGFiZWhkaGhpamdnYlpQSE9VW2dxeXqbbYWJiIaHiImHh4iFjICLiYmJiIKFjY6Ni4mGg4KDf4OHgn+ChIeEf3t4eHV3eXZ4fHqCfHhzb3N1cHBtbVZ3TE5UUlJOTFBRUE9PU1JQTlBPUEtOUE1NR0hDe3tCQkJEREB2cnhBQkKEh4VISYd4e4SEfX1ybHd/kJhVWVpdWVpcV1ZXV1ZTWFhVVFVUT4BSU1FQU1VXUVRNUVdbVFRYUlFQUpdwenJzdGx0fHaFh0iFbXeNVl9YW15dWVZWV1VbW1hWWlZSVE5STlVLS05UU496bGJwdkBAeXNlf1hQVVhXVl1bUlNWUFJTT1hUVFFVUFhUV1hVVFdaWVZXVWpiZGRpcG1tZGM5azZkZ2diZoBkaWhmYjNiNmFgY2NiY1tcSEZHSktHSUpPSUlBRD1AQkNIREZER0dEQ0BHSUtJTURDQ0RERUZHS09RUU5QU1NFb0xLSUNGRkNGRkRCQINfgnBwb2xzdnV+fXp6bm9pamdrgUlJhoBvc1xja2dlZGlOa3uBfIGEhEaLh4ZnbnZwcYByb3WFhXd8f4drYVJIS0pKT09RU0hTe0ZEQHZyc2pue3hxd4BDRERJR0ZFQ0hMTVNWVFhPRkRHPjs8SElJQW9oZrOv34WFi5CSlJe4aJuVj7mcct6daG5ueFliP2pUVV41OT1BPT48Ojo5ODZmX1lcXFtYYTU1ZV5ZVVFOS0lGRoBKS0pLTk9PSkdHMjc4NTMzNjY2NTc6Pj07OjlCajw9PjpbXjc0bV9DOThPZmBZUVBGXDxlWUVGSVdqQTUfNR4fHBwcISUiIRMhIyMjJiggICYhISUmIhYoJBYoIiEjIyQmHyYiISckISkmFSM1KCEgJzAuMzQtGxwvHBwaGxkeGiUwGx0bGRkbGRsbGhwaHyE/Ij0kISIhIyImR0YkQ0BBSEI9PkpVhn2EfhF/f3+AgIB/fXx8fX1+gIGAgIl/AX63fwF+v38Bftt/0oABf8CAAX+bgIJ/hoALf39/gICAf39/gICNf6iAjH8BgIR/nICGf4KAhH+hgIp/A4B/gIp/A4B/gIh/noABf5GAAX6Mf5V+gn+TfgF/n36Df4p+mn8Gfn5+fX19iH4QgICAf3x8fHp7fX+AgIB/f4R+jH+IfoJ/lH6Rfwp+e31/f39+fX9/hn0Jfn5+fX+AfX5/hH4GfXx8fH18iX0Bfo59BH59fX6QfQN+fXyJfQN+fn2HfgF9jn4DfX59h34DfX1+iX0CAgQAgMy9zdjt9OCax/mpkcvB2+edjKDc3fP4g7i9lpSTk4+HgHXohYmOmJaWo6SrxOHu58+pqKGfop2msYqZqrKpq6Salpiio5uXn6itq7fAuq6rqKann6Gho6WqssKhi7nAxMTFucO1w7qmsLy1wKmmt7W7s7q7wMWzsri2vbuCta+ygLCxpLO3tq2trZ2wuayknaGnoqWwsa7Hz8y/1cr0kZCRioiEi4efnJKQiYGN8YCChpGNi4eGjIWIgpKG9P/2q8/M0sm4s83IxcrKubzHydTTztPW4uPZ0MLK1djSxcvPzMiZqNXg1s3CwMDAxMvCwsPEwL29uqGC1+DguZ2oz9LRVc/S1dfUzs3O0NTHn5290dLV29jSzs7R0MrPzsjU1tPUzsnI0c+t8aKtq6aws7CytLu5vr67ury/vbu+vb26trW6trKxr6eXhIH81p6vwcrLycnIxMaEx4DIycTCwsHEwb2+wMHCwsK/vb7Av76+v8HAwb63urezsLKysK60tLS1tK+ssa2tqausm7uHio2NiYmLkJGRj5COjYyLi4qKiIiJiYmA/YKFg4WB+oD99vj0/oCD/+z6+/fx7dnW2d7j8YOSkZCQko6Tk5KPkZKQjouQj46NjYqIjICKioqIhoiKjY+MiIeMkYqLiIyMkZCTl5DbrdTs9PiA/IT11dH4lpWVjo+QkpSRkpWNjo2Qj5SMko6Ih4WKi46TjpeUkpORiNTPyfD50ImZlZuUmZmXn52Tl52dnpSUnJmcl52goZyZop2boKGblpbtwe/z8fT46/X/8vT19/z9+ID5+oKC9/fw/ISG+PX2+PzrrYiinpqamZqbl5qbnJiPjpaVlZOTkpOXmpiZmp2gm5yqiJmboZ2hoaCjpqelpKWdmoOBio2LjIqHiYKDh4eHg7S+2ebt3+ba6ezs3eDZ4+Tixs7c6fX48+6Ch4Hq4+Tq6NKV1vn3gYD19/3+8/nt64Dp8Orr7uTq2sGjhpexusHAwayqqLTA0tWov4WFgPTx9PHp5O3i3Onu8/L094D+74CaqLvHxse2o5OJpLWrvbug9MamoYnCzoaRnau1u8HKq7vx0aLAwYSLw4WbmqyJsvzw29jX7IuNjY6RjImKjYDsgfj6goODg4qB9PuFh4CA+YCAgPf99+3YztPV0+WUkZiXlZaRk4+lpqWnpaGWnarqqqusqILDjvzkw8K6uoyE6uKYjK2VruT8s7PFppem+uqBj5bL4LjQoLuy973wsMDD0ejN5PTy8eDI4efkh/GO1OTN4/rwxdL+wLen2oXvtcPJyuvJ4vjf/oT7gaaE58uMnyeI1/aSo9nZm7Gco6upzfD1gYuPlOT28qqtpMaM5fm36u+A7sGB7tOAeHWBiZeel2iLtH50qqG2vo5/i762zdFsmqCFhISEgHt1acFmZ2lyb297e4GUqrSxnn1+d3Z4dHqEZHKCh319dWtoa3R5c25yeoB8g4yHfXt5d3hxcXFzdXl/jHRni5CVk5OIk4iVinqCjIWTf3yKiI+HjYqPlIWEiIaMjGGFf4GAgX1ygYWEfHx7bX+GeHJrbXRub3h2dIqVlIWZjKliYWNdXFldXW5sY2NcV2OmWVhcY19eWltfWltXY1ymrql4ko+Wj4J+ko2Mj4+ChY6PmJiTlpqipp6Xi5CZnJWKkpSTjmx6nKWel4+LiYqOlI2Oj4+KiIiGcFuYn5+Cb3eUlZOAj5KVlpKNjY2OkoltbYGNjZCWlI+Kio6PipCPipSWlZeRioeSj3efaG9tanFyb3Fyd3V5eHZ2d3x6eXx8fnt3dnx4dHNybGNXV6mSbnyMlJWTk5KOj5KRkZKTk46Li4uPjIeIiouLiouHhYiKiIaJi42NjIqAgX56enx9e3p/f34TgH55dn16enR2eXF3V1pcXFlZXIVfgF5eXFxbW1tZWVtZWFCfUlRTU1CYT52ZlZObUVShlJyclo2MhYaTlpupXGdlZGNkYWVlZGFjZGFeXmNiYGBgXFtgXl5dW1pbXmFjYFtZX2NeYF5gXmJiZWhhknGIlpiaUJ5VoI+Oq2lqbGVlZmdoZWVoX2BgY2RqYmVgWlpVXl1fgGRhamdmaGdgk42DnqSMX2tna2RpamlwbWRobm1uZWZsamxobHBwa2pxbWtvcmxpaKF9lJaWmZyQnKaZlpabnpyWlJhQUZmWkJhQVJiUlJacj2tcbmtnaGdoaWZpaGllXlxiYF1bXVxfZGdmZ2hrbmprc1ZgYWZkaWlqbXBwcG9xgGpqWFheYF9iX11gWlpeXVxZeoCOmJiOlYubo6KTlJGanZ2DipKfqaumolldV6CYmp2bh1+QqaZbWaaqsrSqrZ+ioaajo6mgp5mGb1lgb3N3d3hra2pyeoiMcIJdW1SenaKinJefmpakqKyqqqxYsaVZbHeGjYuMf3FlXnN/doeHgHKqiXZzYo6eam93gIeLjpSBkb2ohJ6icHyvcn58iGuGtKaUjpCfYmNmZGZjYGJkXKVZp6laW1pbYVqssWBjX16vWVqtsaqhkIqOkI+aZ2ZqaGdoZGVkdHZ2d3VxaW98w3h1d3NatGi0o4x0a2xZXaWeZ2OIZ3y1t3t7h3WplH5yaD1CQV1sT11GVlh0VXBIUlVdaVtnb2prZFNfY2E7bURdY1ZicWpUXntXUUtoOXRQVV5cb1pmcmR6PnA8UT5oWEFHQGRyQkhhYD9OQURKSVZnbjxBQkVrdXVSUk9iRHF+XXd7QHltRoV6gFZWXWJpa2hJZYRfZZ6bs7uGa3emqb7Ea6XWvr28vLq4r5bvZF1faGdocG9wd4CEgnlrb2tqbGZpcVNgbXBkZWBWU1dgZV9WWGBkYWhwbWtsa2hqY2NkaGhrbXNdVHV4fn19cX50fnVlbnhxgm5reHd9dnlze4J0c3h0eHlOb2lrgGllW2pubmRiY1ZmbWNYUlRaVlZdWlpwfXttgW6JTUtOSkdERkhWVVBRS0hTi0hGR05MTElJTUpLSFJLho+MZHl3f3ptaH56eHl6bnB5eoSEf4KEjpCLg3d6g4eAdXqAgXtcaYuVjYiAfXt+gIeAf36Ae3t7emRJfIKEbFtjenx6gHZ4fH56dXR0dnt2XFtrc3J2fHx3cHF1d3R6eHJ+gYGCfHZzf31og1JXVVJZWlhYWV5bX15dXV5jYWBjZGVjX2BoZF9fX1lTSEmUfWBufoiKiIiIhYaJiYiIiYyEf4KCh4R9f4OFhYODgHt+goGBg4WHh4iEd3h1cW9yc3NxeHV0GHZ0bmxzb25qa3FsaEhNTk9MS05QUlJQUoRQgE5NT0xPTktIQYVCRUNEQnxBgXt4dntBQ4R4gYB7c3NsbXqCiZVSXVpbW1tXXFxaV1lZVlFRWFdWVVVQUlZUU1JPTlBTVVdVT01TWFNVUVRQVVNXWlN9YG54enpAfkaBd3aMXFxeWFhXWVpXVllQUlFUVl5VWFFJSEVRUE5TUVxZgFdaWVV9dGZ7f21OVlJXT1VVVFxZT1NYV1hQUlZUVVBVWVlUU1pWVFdaVVJSf1ZjZWZpbWVsdmhkY2draWNiZDU2ZWJeYzQ4Y15eYmddSUNQTUlKSUtMSU1MTElDQUZEQD4/P0FHSklKS09RTU9VPEBCRkNHSElMT09OTE9KTUJCgElJSktKR0pERUpJSEVdYWtxcWVsY3F4eWpraXJ0dGBlbHmDhXx3QkVBdW9wdXFhRml9e0RDfX+HiYOHeX1+g4CAiYCHeGlXQEFJSkxLTUdJSU9TX2JQXkRBO2tobXFvaXNvbHV3eXl7f0GBdj5ITVZYVFVPSEE6SFBKVVZMdWVigGJao7h8hIiOkpSWmXZ6no10lppqdqdja2l0Wmh1Z1VOT1k5Ojo4Ojg3ODo0WjBcXDEzMzM3M2NlNTk3NmQzNGNkX1xPRkxPTlU5Nzg3Nzg1NjQ/QEBBPz04OkJrQT0+PTJoOmRwYUI6OjA0YFo6QFtOT2RdQUBEPHBfPjYeHh0iZCIfIh0fFiIfKB8iIyUlIyYoJycmIicoJxUqFiQoIycpKSUmLCQjISYdKSEkJSImJS81MDQaMhoeGzAsGiobLjMaGh8fGh0cHBwdLTEzISEgIT5DQyYmJiokQUI/SUckTEwrWVeHfRJ+fn5/gICAf318fH19fX+AgYCJfwF++X8Bfo9/AX6Of4N+uH+wgAF/ooCCf8CAAX+ZgAF/hYACf4CFf4KAjX+wgIZ/A4B/gIR/ooCGf6KAk3+CgIR/goCHf5+AAX+QgI9/mX6Df4p+gn+ifoN/j34Df35+kX+FfoJ9iH4Qf4CAgH98fHx7e31/gICAf4Z+in8Efn9+foZ/gn6EfwN+f3+KfpF/A357fYR/A31/foV9An5/hH4EgH59f4V+hHyJfQF+kn0Dfn1+mX0Mfn1+fn59fX59fn19in6DfYR+g32FfoV9Bn59fX59fQICBACA5e7viJGZsr6eltK8ycyaj57dzsfni9jclpKSkpCKgXbK5omSjo+OlqCpvc/m9+fOtq6lpZ+qs6aEpK+suKqrnp6ZmZKdsMDGxby7rJODiY6NjJGfpqKmpa+9v+e8sLO7tre6ubK6tqqrsrS2uaqps7y8vL2+s7W4uLKxuYCyureAuL2/q7Opr7CxtLaxp6yQo62rsLWqtbvJzNDT3K7FjY+Vg4aK/pSRkaKgkY+I/4D0hoaPh/Dy/Y2Yo62/xtGetsvNz83Cw8TIyczUxMnEyM/SzcnI0+DWzcHKw8PGzdTX0tLBi73S0M/Gy8PHxcLEyMjAwZ7gy+PRqJy+287H09SAztHQ1dXUzs7OxMrRzrShosvbz9PU1tjc29va29Lb1tbY1tLJi4itt7S2t7Ktr7WwrrG0q7Cxubu9uLS5uL68vLu2trSyrKqop6elnI6EgomMk6Ovu8PGxsjFxMPBwcG/vLu9vLq6t7i8u7y4uLi5vL27uri2tba0tK6vsLSysbGAsK6urqusqq6orKi2kIuKio+SkI6NjIyNi4yHiYyNjoqJjIqFhISDgYOFgouKhoWDgoH57tDC0Nni6POGjo+OjYyOj5CPkJKRj4+Oj5GPi4yOjI2MioqJiYyMiYaMjo6PjYyIjIuPkpOPjY+RiY2Lj5CNida86vf/0szgjomQlo+AjpSWjIyRjJCNkYyQkpiUj4KA/ouLkZKVkY+QkZeQkJOSkIuo2ZebmZebm5qeoJydmZuanJqen52YnZyeoKOeoaKfnaCjoJjvtIHo7+38/PT29OTu+v/4//yAg/767Pj+9/mF7+v2gPjvnp+goJ6dm52cnZmYmZmZl5qVlpeYlpaAkZqYlZeZl5mevfuamZeVmp+eoKiqqqippMXngYGAhoaGg4WIhIKIhYHzlenu/IX1/P/u8vf9+e3p2dzn3t/x6vbw9u7yhYX/5NTX4aq79vP6hYWHhYGC9Orh3/LfzrKYgISNj5OYnp2qrbCttb3L1uDh2LTL9vP68t/t4tvY6uSA4/SAgPnu7O/7go+jsNLOwp+boJecnZuqrqKF2LGfmMyv4YiTo7G3vcXQ19L5vYe/yo2PreKBh6+UyoqB7oKPkZSPio6JjIeCi4CDiIOHiYWC5vn7goSD/4SRiZaanpqbnZmTkJWfnaOioZuWkZSkra+yrrCyqavcxbm8uY3hnPmA5M3GvMGiifLn1cKEur7Dl7CxwsHEiYKGi7S25M7gst+wg+aHkdXzho/F5tX0iPTNxL2D9Y+LieGA4t/b1tK039DWy+unvZ2nv9Ohg8Pwhv+B3c/y+4OFg4LK0tuBjYLVrK2+h46QtZyFw5+umonyjZz/+YfLnZ7G+YWKhIiC2eGAipOYVl1qf4p5e66dqquRgo7FsafCdLTAhYSEhIN9dWqptGlxbW1rcXh/jZysu7KdioR8e3d/hXlffoeCiH1/cnJtb2hzg46RkYiGf2xdZGVmZWl0eHN3dX2HiaCNhIaMhoSHiYOLiH1+hIaHi399h5CQj4yQhYSHiIOCiVuCiIaAiIyMe4N5fX+AgoZ/dXphcXp1enxxeH+RlJSVnXuDX2FoWFtdqWZjYG5vYmFdsFinXVxhXaajrWJsdHmKjpdvgY2Rk5OIiYqOj5GXi4+MjpSWkI2NlaGZk4mQi4qOk5iZlJaMZImal5aQk42PkI6OkY+KjHCdkKWWdmuDm5CJlJWAj5GRlJKRi4yMgoiSjn5ubYqYi5CTlZaamJmampGalpSWlJCKXFdveXV0dXJvcHRxcHFzbHByeHl6dnN4eHx6ent2d3Z0cG5tbW1rZV1WVFxhZ3N9h42Qj5GNjY2JiouJhYWGhYOCf36BgYSBgH6BhoeGhYJ/fX9/fnl4fIN/e32AfHp6eXd4dnxyeHl5XFlaWl5hYF5dW1tdXVxZXF1eX1tZWllWU1JSUFNVVVtZVFZVU1Ogmn51ho6WnKdfZGNhYmFiYWNkZWRlYmJeYGNiX15iYF9gXl1cXl9hXVpgYWBiYWBcXl1iZGZjYWFjXF9cYmNgXZN9mZ+khoeWYVthaWOAY2lqYmFmYGRhYl5kZ21qZlZUqWBfZGZoY2JjZmpmZmhoZWJ0lmlsaWhrbGtvcG1uamtqa2ptbm1obWxtb3JtcHJubG9ycWujc1KLj5Cdm5aXl4yTnaKanJlPUpuZi5WZlpVTkY6XUJiTZWttbGppaGlpamdjY2VnZmlkZGNlY2OAXWdnZWZoZmdtgaBiX11dYWhpbHF0c3JzcIiaWFlZXV5eXF1gXFpfW1amY6CmrFyora+gpKWuqqOik5qgko6fnKmlp6SlXl6wmIqPl3J8oaCpXV1fXVhapJ+amKeXjXlmUlNYWV1gZGRramxrcXeAiZKUkXuKpaGppJOimZWTpqCAm6pZW7Klo6WwW2R0fJWRim1rcGZpa2p2enFcmoBva5aDqmtzfYWJjZKZo6PFl22fr3uBqcdpa4l0mmVbo1pkZGhjXmNgY19YYVlaXldcX1xcorC2XV9fuGBoYGxwc3Fyc25pZ2tycHNzc25pZGdze3t9enx+eH7HjX6BgGPGb66AopJ2bXBpYamglIRoiYqebXp6hIKZpVBAQVNTal1lS2VOPWc/PF9vPkNSamNrQHFaU00+dEVBQWk9Zl1cW1tOZmJjXGpGVUdLV2JGPllxPnk+ZmFzdDw8PTxbX2U9QThmTkxXOTo7UUQ5UUtSSEBwQUl7eEFjTk9kfkNGREhJeYKAW2FjOD9QZG5mbqCUpqmIcXusnpq1brjrvby9vLu3sZ7cvmJnY2RkaW5xeXt9h4Z8dXRvb2xxc2dOaXJscWRnXl9aXldgbHBycWhmY1ZLU1dXVlplaWVqZ21zc4V3bm91b21wcW54dWtqcHN0eGtpc39/fnd+c3BydnJwdkxscm6AcHNzZWxfY2dpaW9oWmFKWF9bYGFWXGN3eHt+gWJmS01SR0pKhU9MSVdZT09LkEeBR0pOS4OGjVJXYmZ1eYNfbXV5fH1zdXV7e32Bd3x3eoCCfnx7f4mEgHd/fHl6f4WHgoR6WHqKhIJ+gXt9gIKAgX98f2OCd4p/YldqgHZueXxvdnd3enh3cnNyaXJ9d2pdWXGAcHV6fX6CgYOFhXqGgX2AfXt3UENXYF1dXVtYWV5bW1xeVVlcYWFhXVthYWZkZWZhYmJgXVxaWltZVExIRk9WXGlxfYOGhoiDgoR+gIJ/e3p9e3l4c3J1dHp2dXV4hH+AendzdXRzbW51fHdxcnJvcG9ra2hwZ3B0ak9LTUxQU1NSUE9OT09PTE9QUE9LS0xLRkJBQUJFR0dMSENGRkNCgn1oX3F4go2XVltaWFdXWFhaW11aWllXUVRXV1VVV1ZVVlJRUlNWVlJPVVVUVlRTT1FRVldYV1VVVk5RTVVUUlCAf21/f4Ztc4BUSlNeVFZcXlNSWFNVVFVPV1piXllGRIxTUFhYWVZTVVleWVpcWlZUX3pWV1RSVVdXW1xZWVVWVVVUV1hWUldUVVdbVllaVVVYW1lUflM6XGBfbGhkZGZeYmlvaGllNTdjYldfYV9eNl1cYzVhYkpPT05NS0pMTE6AS0ZGSUpLTkhIRkhGRkBKS0pMTUpKT2BvQ0E/PkFHSExQUVFQUVBjdENFRUlKSkhJTElHS0ZCfkp9gIZJgIKAdXp7g4R+fXF0d25oc3KBfIF6fElJhm9jZW1RWXRzfUZGR0ZCRXx5d3WEdG1fTTo4Ozw/QEFARUVHSE5TWmBlZWSAV2JzcXZzZ3NtaWh5c213P0GDenl5gD9CS09fWVNDQ0c/QUFDSUxGPmxkXlycmMqAhYuSlJaaoJCIoX1fkqJzepawWVp1Y3xCOGI0ODg7NzM3NTg1MTYwMTQwMzUzM1xkZjU3N2g2PDc+QEJBQUI/PDs9QUBAPj47ODU3PkJDREKAQ0Q/RWhGQUJBNm48YnFjQjs+PDZhW1NPQmNXWzg/QENCWG4nHR0iIiUiIxwhHhIiFB0jJBIUICclKBUnJSMkFCkXFhYpFikoJiYmIigmJycqIyQgICImHhUpMxozGzAvNTYaGRkZLTAwGxsYIR4dIRocHCAfHCwhJCIgPSIlRkINIConKD9FJysqLS1TWIN9hX4Pf4CAgH99fHx9fX1/gIGAiX+Cfrl/AX7BfwF+hn8Bfoh/A35/foR/g36+f/+Al4ABf6aAiX+4gIh/l4ABf5CAgn+igAN/f4CPf4KAh38IgH9/f4B/f3+fgIJ/joACf36Of4V+AX+WfoJ/in6Gf6t+gn+FfpJ/hH6DfYh+E3+AgIB/fHx8e3t8f4CAgH9/f36Ufwd+fn5/f39+nn8Dfnt9hH8DfX9+hX0Lfn9+fn59gH99f3+EfgJ9fIt9CH59fn19fX5+hH0BfoR9B359fn5+fX6SfQZ+fX1+fX6EfYR+g32NfgF9hH4FfX5+fX2EfoJ9hX6CfQICBACAk5iqudLnx63ZsKmjiZWw7t+5343rhpqTk5ORjoV4zcSBkJKMk4mVoaq4wtv38NSsq6ifo6Csq4egsK+zvKyoj6KcnaSruMfCvbG0sKKkn56dkYb96vWAhpaX/oKxsLK2srC2uLm6ubSzqrG0uLmzqqyyt7i7t7m0tsG3tIC7tLGAqbG7samsrL6sqqSspa6xopmsrbiwt7rF0tTUztLCjY+Rj6SnnZmehp6Yl//9+/eAjJKjrcXOxMXAzs/N0srEjaDKvsXGxcTBytDMz8/Iycbdz83Tz9zS1dfOzMrDysTG1djX06CSwcbAy8jGwb7GxLae0LnVr5+q1NbT1NLS1cuA0dHMz83UycrJyMXGz9XQz6ykqs/Y1trW2NXb1tbW3NnS0KaBqLGxs7a2t7W0ra+uo7C1trSysrCtsq6vrbKsq6mrqa2op6ypqqyrqaOho6GgnZOGg4KHjJGYoqy4vL2+vry9vbm6ubi5trm4sbO0uLq6ureys7WxtbSxr7Cwt6+ArKuwpaqsrKyqrqq0ko+Ni46OjI6Oi5CQjouRj4mPjo2J//uAg4aEgf2B9Pf02tzm6fD0+4OPlJOUkJCRk5SSkY6Nj5GSkpOWk4+Nko2PjYyKjY6Ni4mMjI2LjI6NjY+PjouOjY2MjYyPk46LjZCOjo2Mi5GMoLPaipGQkZGVmZOAjpOWkJCMiIeIjoyUlJeTg4OEg4WPjJmSkI+JkpWSjpOQ/M+ImJuYnJmcnpyemp6enZiZnJmZnZucnp6gnp+coaGdoJufn4Gs5u7j7fr37dz6+/fp//z5/Pj3/4GB8vb9gO7yg/f68/TWyZqcoZyWmZuenKGZj5eWoJqYl5qVnJiAk5WZl5eXlpWd3eOem5mYn6OjoZ2gnqOi/NeJh4GCgoDw9POFhISGioqEzcuC5O3p//mA/IDr6Ozu7vvu7+/w8PHzgfL3gPfa6+ri8PTBr/37gIKAgYSEgvDStKWgpKahp5qXlJSZoKOjo6eio6eqtsbKu8DIqq/m6ezm5+vy6uSA4PH7+fLv6/L4gYaWo6Cdrsu/s7OxrqaYlaOujIPYqqeArL77kJ2rtrvCyuX62/q5hMPPq5qht7/tqaDlrKagmZSTkZKXlpuWkpWKhIqIhIPi9IOFg4KHiYWLj52hn5yenp+hqbOztraysre2qJqdm5mgo6Skn5v3zKarp/PjkOyA6tzMxcnEjfb47ITdqeLvz7vHzdrAvcGLvKzHt5Gnw4HM48jT383P5ffX5tfQxLn5jsjU0KKgjtHbrszihPmq5bvzgdimtqjVmbOR0sGCvd3chO2z2taUmoLF39vouIW8kP2V7KeiyLj7uZiQkYiTrY6un56llYuNmfaAg6j+iIqAV1tpgpiom420kY+TgoqXzbydu3bCcIeEhIWDf3hsrJxibW9qcWhye4CKj6C4tKGBgX94enaBf2J5h4WIjn98ZnVxb3V9iJWOiHuBgnd6dXR0bGK6rbRdYm9ttl6FhISJhX+Eh4aHiYaGfoSHioqHgYKGiouOi4yCiI+HhFuKgoGAeoCJgnp7eYp6eXN7dXt+cWp4eoJ4enyIlpKTkJiJYmNlY3JybGZsWmxqZ66urq1YYWVzeoqUjIyJlJeVlpCLZHCSho6OjoyHj5SRk5CLjYuekpKYk52WmpyVkpGMkoyNmZual29ojpOMlpOQjIqRkIVwkYOYfHB1k5OPkI+Pk4qAkJKMjIuUiYeHiIaKkpeRkXp1dpOcmZ2XmJWclpiXnZiSk3VUa3FwcXR1dXN0bnFyaXF1dnV1dHNxc3FycXVxb21vbnFtbHBucHBwbmtpaWloZWFXVlhbYGZrcnqBhYaHhYKEhoKDf3+BfIF/ent9goSEhIB7fX98gX18enx8g3xjeXh7cnV2dnl2fXp5YV1dXF5dXV5fXGFhYFtiYFtgX11ZoZ5PUlNTUZ5RmJ6lj5KbnqGnrFtkZ2dnZGVkaGlnYmFgYWNkZWNnZmNhY2FiYGBeYGFeXV1gYGFfYGFhYWJiYl9hhF+AYGBkYl9gY2JiYWBeZGFrepVgZWRkZGltZmJpamZlYltaXWJha2hsaFZVWltbZGFsZ2VjXmdqaGVqZ7CQX2lraGtpbG5tb2tvb25oa21qaWxra25ub21ubHBvbXBsb29YbY+Si5CamI+Enp2YjJ+dmJuYmZ5RUZOVn1CRk1CWmJWAloeEaGpuaGFmaGtobWNZZ2duZ2VkZ2NpZFxjaGZmZ2Zja5mTZmJfXmVsbG1qbWtvba2QX1xZWllYpKemXFxeX2JhW4iLWZeem7GsWq9VmJygo6Swn6GgoqWkpVqmqFiolaOgmqWngnasqVdaWVpdXVypk31uZmdoZGdfXFtaXGKAZWZmZ2Vnam12gIN7f4RvdZygo52eoqujnZilrKympaGnq1teanNwaneMhH19fHlzZmRtemFdmnl4XIGOw3B4gYiMkZaovqrFk2yktZOSnJ+Yv4h/sn96c2xnZ2ZmaGhraGdoYFpfXltbnKteYF5cX19eZWdydHJxcXFzdXqDgYWAhIGBhIN5bnFvbHFydnhycciFb3Rxrcdlo6OYeXF1f2Str6ZboYOdvZ+Ah4qRg6ymSFlNW1M/SFc+YGdXWFtUWGRvX2dhW1lUdERaYGNIUUVibFBZYj1wSGpRcTxgSlFHZEdTR2FcPVFjZD9sTmRhREc7WGZnblY/WkN1RGdKR1wZVGlURUNFPkRTQlJLS1BJQ0RNfEJEW4tLT4A0OEVfdIOHfqeJiIp6e4Orp5Cub8SFv7u8vbu5tqbys15iZV9nYmtwcXJvc4GGf21ub2ltaW9sUGVwcHF3amlTZGFdY2hweXFqWmFmX2ZlZWdeVaGVmU9TX1qUSm1tbnNvZmttbnFzcXJqcXR3d3RubXR3eX14eW1yfHVzS3FqaYBiZW9qZWVfb2NjXmRcY2dbUl1gaV1eX2p6eHh0emtNTlBOWVtUUVdHUlFQiIiLjUhRVWNpdoB5dnJ9gH+CfXpXWnhtc3Z1d3J6gHx9e3Z6d4p/gIN+iIKGiIKBf3t/en2GiYmGYVqAh32FhIJ9e4KDfGd4a39mXVx0dHFzc3N3b4B0eHNzcXpxbW5wbnF5gHx9aWRjfISBhoGCf4aBhIOJhH5/ZkNQVlVXWltbWltXWVlTWVxdXl9fYF1gXF1cX11dW1taXVhYXFpcXFtZVlZWV1ZUT0hLTVBXXmJnbnV6ent4eHt8d3h0c3NvcnJsbXB4eXx8eXFydXF5dHNvcHB6cl1tbHBkZ2lnbWxzdGxVT1BRUFBQUlJPUVNTTlRRTFJQTUuEgT9BQ0NBfj93fol1eoWKkZacUlteXF1bXFtfX1tYVlhaW11cV1pbWVZYVlhXVVNXV1RRUFRVVVRUVVWEVoBTVVFQUVFSVFhTUFFUU1VVVFJXUFhmfVNYV1VVXGFYVF5fWVdUTUtPVVRdXWFdR0VKTE1WU2BaWFZPWl5bWF5ZlHpPV1hTVVNWWlhaVlpaWFJUV1NTVVRUVlRWVFRTWVdVWFVYWUZPYWFcYmloX1hsaGNdbWplaGNkZzU1YGFpNIBeYDViYmBgW2FMTlFLQ0hLTktQRT5MTFJJSEhKRUtIP0dMSkpLSkZOcWZFQT8/Q0tMTUpMS01MfGpIRkRGRUSCg4JKSkxMTkxGaGtFcHdzhYBFh0B0eHx8fIp3fHt8fnx/R35/Q4JsenhxeHpiVoF9QURDREdHR4FxX1JLSkpGR4BBPjw8PT9BQkNGRUdKTFNZW1VWV0lQbHN3cXJ2f3dwanN6eHV2c3Z4P0FISkdAR1RRT1BOTEY+OkBKPj9yY2NXlKnqh4uQlZidoa2mjKF5XJeqjIWOj4KgdW+VWU1GQDs6Nzc5OTw5ODk0MDQzMTJYXzU2NTQ4NzU6O0JEQkBBQYBAQUZMS0xLSUdJSUE6PTs5PT0/QD09bUQ3OTdYczddcWhEPkBHNmFiXDJlWW1sV0BERktGXFIhIiAjIR0fIBIfIiEiJCMiJSclKSYmJCIqFiMlJiIZFykrJCYoFSohKCQpFCUhIx8lHyMXJSoaKi4vGjItMjAcHRsvNDQ0MBsgHB8zHDMgHiEgNh8dHyEeICIfJyYmJSYoKCtRKysxWi4whn4Qf4CAgH99fHx9fX1/gIGAgIh/gn6zf4N+hH8BfsJ/AX6Nf4R+xH//gJmAAX+VgIJ/hYACf4CKf8GAg3+pgIJ/pICUfwmAgH9/f4B/f4CGf5+Agn+NgAJ/foZ/g36HfwN+fn+FfgN/fn+NfgR/fn5/i36Hf7F+lH+EfoN9iH4Pf4CAgH98fHx7e3x+f4CAlX+CfqR/Cn57fX9/f359f36FfQJ+f4V+BIB9fn+Ffop9AX6QfQF+hH2CfoV9AX6FfQF+h30Ifn19fn19fX6EfYN+hX0Gfn5+fX59hH4BfZB+B31+fn59fn4CAgQAFcjo+o+BxdeU+fz0r7n85M3jjvOMnISTgI+Ge2m27I2Xj4+Ni5qiusnX7O3r06mlpaSfoqqji6SvtLaytKqfl5mWqLKqv729sr3Cp5mdnpeZl4yNk5iVmqyX5r2rramdoJiXmZ6lp7KvsaatuLu4qq+srrG1sra6uruzrvW2rravqpyxs6+gwK6rqa2tqq6qn6qzsre2tLfIgNTQ0NSTgo/ygP/5gYWMk5WWoqy4vr/Dw8W9uMvEvcLJzNPCyszT5JqvxtDRxtLAysPNv8PPzMrR1M/X0c/b29be19XSz7q/xcfOx8jFjJ/DxMLEyMPBqc6vsaes0drQ0srMysfP0dPQ0tfT0c3IysfIysDIxsPLy8zJp6Ov0NjYgNjT19fX2djHiJeusrK5t7S4u7e7v7i3sbOzsrGrq6yvrKipqqaloKSnrqytqKqnpairop6an52bm5mZm56cmJiXkYiDgoKGh4ePl5mhrbK2s7SxsrS2uLW5t7WztLOzsrS2srGwr6ysqaqtq6Wpqa2tq7WOko6JjZGRk5WSkY6KgIWQj4yOjPnt4Nbl4ujn6vH7/oWQuJqfm5iTko+PkpGQj5GQkZGSkJKSlJaVkIyOj42OjI2Oi4uMj5GQkIyQjoyKjIyNkJKQjpGNj4+OjpCRkZGSkZKSkI6L7tXZhpSRjZOSkJSTkIuTlpWYhZCEhI2IjpKZj42PiIOLjY+Ujo6KgI2GlYyNjY3T95KWmZ2cmp6alZydn5+em6KflZydlZeZlKCenZaeoqCjnp2bhaz28/Ls8f7y5Oz4+vXw94CD+On0/IGA+PX+/frt8/L79/OViZadnJaUkZOamZeWjZqaoJqTkZqWlpWZlZaXmJWXnJ7fxp2fn5iXnKSkoqGZnJHOgP74gv+BhoSKhvTz9PT0/4OKhqXn4+Xz9u79/f/v9fr89fnz+PH18eTl9IOGhf/q9PLr5/faxp3w7v2A7tvFtamowcCzra2lnp2gpKOnpqqjoqKenaGll6Ctt7jH3syltOnw+/Dq8u7j1t/e6e3g7/D3+vP2nK7H1eXiybCkrq+ygK2mpKeT9cOqo+ez2YaVo7C5vMPL6ILc+reR4em1rJqL47meqYzVtKqdkJCWmZqboJ+fnJGTkvvy+ISDgoOJjYmJoKOopaSfnaOnrrC0uru+u7i5m5qYmp+goqWqqJX406evpPvzjt3x687IzfOhjomGx+Cm28T0wcXP4eCM5oSHZ7PHoqScqOqt6YP3xsuAjMjWys3G3tH4g4Lm3/nr7fXAyfr3gofF9cy+vrTS68TXy6m85+bzooKHiJLMkZTr/e6BlPWUi+ns846C6ZOgmLOZoOrel6Saqp3l+r3MppO+vpD6vJ6ttLKAe5mraWehsn3P2vOgodK7r751xnWHgoOEhYF6cFuVuGt0bG1raXV7jpiitLOzoHt6eXp2eX15ZXuGiYqGhn1zbG1qeYN+jYmKfoeQe290dm9wb2dob3FucoFvpZCAhH92eG5rcHJ4eYSBg3uCjI6KgIOChYWJhoiKio2FgKuEeoSAfHpugoV/cIp+eXp7eXZ7dm10fHyAfnp6iJWSkJJpWWOnWrKsW15iZ2pqcXyEioqJi42FgpGMh4mPkZiJj5Sbq3F+j5aYjJaGj4qSiIuUkY6TlpSclpWenpmgmpiWlIOJjI2Tjo6NY3SQkI+Pko6MeZJ/f3Z4kJWNj4mJiIWLjY+AjpGWkY+MiImGiIqAiImHjo2OjndzfJSampqUmZiZmpmNXWFtcXN4dXN2enh7fnd3c3V0dHNvbnFzcG1vb2xsZ2pscnBxbW5sa21wamdjaGZlZmRjZWdnZGNjX1hVV1lbXVxia2xxeXt9e316fn6CgoCEgH5+f399foKDf3x3eniAd3R0dXRvcXF2d3h9X2FeW19hX2FjY2FfXFhgYl1fXJePh4GRk5yanqausVxmfG1xbmpmZWVkZmVjY2FiYmNkY2NkZWdoZGFhYmFiYGFiX19hYmJhYF9iYV5eYGBiZGdkYWVgYWBgYWNjY2RlZGVmZGFfpZWZXGhlYmhmY2hoY1+AZWlqa1dkWVhhXWNob2VjYV1ZYGJlaGJjYWRdamRlZGWSrGdpa25sa25saG9ub3BubHFwaG1sZGdpZXBtbGducG9ybm1sXXGdmJOQlJ2VjJObmpiTmE9SmY2UnVNSmZijnJiQlZSbnJlgW2VraWNjYGBnZGNjW2Zna2VfXWVhX2CAZ2RkZmhlZmprk4JmZmZfXmVucG5uaGhgiK2oWK1YW1hdWp+fqaiqs1xhXm2elZWlpJ+mqK2ipqyuqqulqqSop5mapVtaVq6fpqahnaiRhmujoa5ZpJaGeG5re3pwbGtnY2JhZWVoZmllZmZiYWRoX2VvdnZ9jIJrfKWps6mkq6mAoJKXlJ+jmKalq6+qqWl0h5Kdnot7cXd6fXl2dHVor4t5dKaFpWdye4aMj5OYrWKsxpN1ucahp5F4uZN+hm2jhXxvYmNoa2tscG9vbGVmZ7Cqrl5eXV5hY2BidHV4dnZyb3N3fH1/hIeKhoOCbW1sbnJyc3Z6eW7Lj290bbHNZJyApp19eHukcWNhXoybf5mSw4SGi5iYautcPlJbRkhESWxObTxxV1c6RFhhVlxaaGFzOz5pZHFsaXFYV3BvOj5UcVxUWE5hcVhlYk9YbWx0Rz1AQEZcRkdudW88RXJHQm9xc0I8aUFGQU9CSGZiRE5KU01vcVtkUEZgYEl9YFZgY2aAUWx8VFmPpHPEztqDhLasnrBuxoi+u7u8vbiyqYS9w2VpYWNjY21ud3l4gYCEgGlqampmaGlkUmZucXNxcmphWltWYmtmcmxtXmZxYlxhZGBgYVlaYWJfYW1ZhHhsb21lZltXWVtjZHFtbmpweXt3bXJwcnJ1cnR3dXdvbIdsY2uAZGFVa25pWm5mY2VlYV9lXlZdY2BlZV9danl1c3ZWRU6FR4qGSUpNVFhWX2Ztc3J0dHRybXt1b3J2eoFyeoCImGRod39+cX1zenaAdnd/enp8f36Ef3+FhIGJhIJ/fW93fH+FfXl7VmeDgoCBhYB+a3ppamJgcnhxcmpramhub3KAcnZ6dXRybm5sb3JqcXFud3d5emRgaH2Eg4N9g4KCg4J7TklRVVdbWVhcYV5iZl9fXF5dXF1ZWV1fXVpaW1lYVVdYXFpbWFlYWFlcVlNQVlRUVVNTVVdWVFRUUUtJSk1PT1BZYV9lbm9va29tcHB1d3N7eHZydHRzdXd4dHBtcWyAbGhnaGZgZGVsbG9yU1NTTlJUUlJUVFRSTklRUU5STnp0b2d1eoWFi5GdoVVfcWhsZV9dXVtaXlxZWFdZWFhaWVpcXFxbV1VYWVdYVlVWVVdZWFZRUFRYVFJSVFRXWFpYVVdTU1JSVFVWVVZXVldZV1RTkoSFT1lWVVpYVFpbVlCAWV5eXkdVSklUTVVbZFlVU09LUlNXXFRWVFZOX1dYV1p+jVNWV1lXVlhVUllZW1tZV1xaUlZTTE9RTVdUU09XWFdbV1dUSFFsaGNhYmljXGNpaGZiZTY4Zl5iaTk3Y2JsZmBeZGNlaGZEQUhOTEVHRERJRkVGQEpKTUdCQEdEQkSASkdISktJSU1NaltFRUU+PkVMTkxNSElEY4J7QoVCRUNIRnh3gYGFjkhMSFZ2bWt5enV4fIR4fYOGhId/gXp9fXFyekVCP350fHx1c3xmX098eIVGfnFkWE5MVlRMSkxIRURCREVEQUE+PkBAQERIQEVJT05QWVRKW3l5gnp4gH2AdWdpZ3B1a3Z0eHl1cEJGT1VcXlZNRklLTUpHRUtFeW1jZrOmyn+IjZWZnKCkslaNoXhnsriSk4V0p4FudVt6VEtANjU4Ojo8Pj09OzY5OV9dXjQ0NDU3ODc4RERGRERAP0FCRUVGR0hKSUdGOTo5Oz09PkBDQjtoRTc6N1x3NlmAcWdEQEFbPzg2NEpdVHZWZ0JDR05ONoMvHyEiIB4eHiMdIxQnJCUUFiMnJSMlKCcnFBUoKCwrLC4oKTAuFRUjKiQlJSImJyMnJyMkKCszKBoaGRstGx00NTQcHTUdHDc2NRwcNh0eHiEfIDIxICUkJiI+RCovKioxMSpSNDI3OT0Efn5+f4SADH58e3x9fX1/gIGAgIl/gn67fwF+oX8BfqN/BH5/fn7Nf/+Am4ABf5OAjH8DgIB/xICDf6uAgn+kgI9/goCEf4KAjH+ggIJ/jYAFfn5+f36Ff4Z+g3+YfoN/jX4Bf7l+kX+EfoN9iX6EgAx/fHx8e3t8fX+AgICRf4N+pH8Kfnt9f39/fn1/foV9AX6EfwZ+fYB+fX+GfgF8i30Gfn19fX5+iH2Cfop9gn6RfYR+EX1+fn19fX5+fX5+fX19fn59hn6CfYV+gn2HfgF9hX4CAgQAgIXUqNnB44y459HM4ezc6I3ujKCUkpGSkIuAa7TIiZ2cj4uMg5qyvNjf9fLWxa+rqpudr6uQjK2rsbW2sKynop6UnZyrub+5vre7q6CZnZacmZiSkpmVk5Kj1bDWyNTJu8nO0tPa28u+wr22sK+uq6SVk5aUmJqWnZyko5bkpaiugJyhkqClppynlqWalY+UkoeChpOOmJuaoK20tqytqL6ltLrH09vW0MfFvbzBvLC1v77BxcbQzMHHy87SyNHR08jbj6bAycy+wLzH1MzJydjSztPWxMjT1sfJzs3My8jOzMfOy83N1tW+iKfEycikv4Wir9fe5NzZ197S0tPU28rMRM7Wz83R1NPN0NHNzc7KzczMyc7K08yopLDK0NfT08yaiaqsrrOwtrq/v724uby3uL66trO5trS0rq2ppquwr6yopKeshKuArKmpp6KjpKSmqKeop6Wlo6Ggn5+emp6gm5eXlpKLgv7+04aPjImSkJCSk4+SlJSXnJ+eoZ+npqiopqKkqKenpqGgoMTv/vf7+fHs7ejg5Onr7/L4+v2BhImUlpeWm56fmJyenJqA05eUlJCQjY+NjI6XlpSXlJWWkZSWlZKOjZAHkJGRkY+NjoSPX4uNj5GRjYuGjpGTkY2NkI6Oj5CQj5COjI+RjYnh0diCk5KQjpKVlJKPkpKPjIqSkY+NioWMj5CTlJOSj5KHipGTj4iRjI+Qk5SQjN3hkpuXmZaWnpmUmZiboJ+foqSnhJ+AmJiZnKOdmp6doKCenpuKq/376vPvgO7v7e7u9PP4hfr38fLzg4T8/u/5/YKA9OPs+OCslpWYlpaXl5GXmqCdl5ial5mWkZeQnJqdmZiYm5qboKOCsKGdnp+dlp2mpqqnm5b2i4SFhoSFj4+Hh4f/9+zk5u716J319Ono8vT88OSA+Pzr9vKC/fn+gPDs+/iBg4T18/zw9ILp6dmUzMK7sLa9r6uYoaWqsLWxsKajlZSaoa+1s7Gnpp+Tj46al6GttsbJvJej5viB9YDw8/jr+//98OTKzf2Um6OmusTHyLOipsCxrKOtuqCVgOvDuJS4sOCIlqaxtrzCyuLz0vfJsYiA6Nj3lu2MgYGpnvXBrqafmJKWm6CeoaGhioqOh4aEhISHh4iOj5OYlZaZnJ+sqKuvsLi6t7Wtjpecn6OfnqCrpIv0+L28q/aAj7/V2c3J15Gqk4qN+dulhrzR7sLJ09vTouSXlMfkyLTJxr+en/WF2e/y9OXm7fzXzN3ByM+e8P5K5v6EvrzLgNDTzMve4tLGi8btvf/t1q7W6tHOv4Dp7OvnhoyEn5784KKmi4L6pPqIka7TgouolK64pa+elpemwba92dSiwsTp/YCAWaKHs5+8dqLYwqy9zbrBdMV2jISCg4OBfHNdj5pod3ZsaWtkd4mSpae6uaKVg39+cXSDfmlngX+DhoeDgXx2cGhwcH6KkImLg4d9dW90bnJub2prcm5paXqZhaibpZyOmJ6io6apm5CUj4qHh4aBfG5tb2xxcW11c3p6bqF4eH+Ab3NlcHl6cXhqdm5rZmloXlhbZ2JscW9xgIeLgYCBhXqCh4+boJ2Xjo2HhIeFfYCEhIiMjpSTi46SlZqQlpeYj6NndIaOkIeJhY2Xko+QnZaTlpeLjpeajo+TkpCQjZSTj5OSk5abmYpheo6SknaIXXN5l5qflJKSmY+NkZKYh4lDipOOipCVlY6QkY6NjYqNjIyKkIuXkXZzfI6SmJOUj2pabW1vc3Bzd3t6eXd4e3d3fHl1dHp2dXZwb21rb3NycG1qbYVvgHBtbWxoaGtrbW5tbWxsbGtqaGpoaGVmZ2ZjYmFeWlWop5BfZmNgZ2ZnamlnamppaW9zc3NtdHV3dnVvcnh1dXVxbnGMo6ikrKmgnaGblZ2hpKSorKyvW15gaWtqZ2xvcGtucG9tXJRqaGlmZWRjYWBhaWdkaGZnaWRmaGdlYmFjgGJkY2RiX2BhYmJhXl9hZGRgYVxjZGZkYWBiYGBhYmNiY2JgZGViYJ6RllxnZGJhZWloZmJoZ2NgYWhnYmFhW2FiZGhpaGdkZl1hZmhlXGVgZGRoaWZknZtla2hqaGdtamVqaWxwb3BxcnRub29uZ2doanFsaWxscHBtbGtfb6CegJCWk0+TlJKVkpSVmFKXl5OUl1JTnZ2Sm5tSUZiNlKKPdGNiZmRkZWZdY2ZraWVmZmJkYl1iXWdna2hnaGtpam1vWHRqZ2hoZV5ia2xycWhirGFaW11aWWFhWlpcsaqinqClqKJuraufnaSmrqOXp6ueqaNbrqmuV6CfrapXWVmigKWspKZZnJuRZo2CfnN0eG5sX2Voam5wbW1mZVxcYGVtcnBuaGZjWlhYYF9lbHF8fndhcaKvW61cq6uvprGzsqabh42wZmlvcH2EhYd7cnGFenlxd4JvbFyni4Jri4aqanN9h4uQk5enuaXEopBwzcPfjM1yaWeGfb6Pf3hxa2dpgG1xcHNzc2BhZWBgX11dYWBhZWZobGppbG1vd3JzenuDh4SAeWJqb3F1cnJze3dlxqR+fXOtbWOGlpB6dYFmeGdgY66Qe2WGqaiFiY+VkpDUTUZebVxVXlhXTk1iP2Bwb3FpaWl5ZV1iVFZaT21zZ3ZAVVFYO1tdVVxkZGNbRFpvOFOAdGFMZm1dXlk/cHBtbkFEP0xKfGtNUEE9ck90PT5QYzc+TkNTWU1WTUlMU2JcYnJxV2luiZxTgEOHeaSTr3Gcv5+So7WmsGzCh8G7u7u6uLWvir+sZW5tY2JmX253eYB6gYJ5d3Bvb2Bhb2hVUmloa2xtbG1qZF1UW1tkbnNtbWNoZGBdYV1fXV9cXWReWVhpf22Pho+Gd4SJjo+PjoJ4fHl3c3RzbmlcXV9cXl5bYGBlZFx9YmRpgFhaT1ljY1lkU19YVVFVVExGSVNNWF5aXGp0eHBucGtlbm93hImFfnVxbWxyb2Vna2xxc3R8e3J3e36Be4CBhXySXF5tdXlwc3F3gHt8e4iAfYCAdHaBhXd1e3t6e3l+fHyDgYCCh4Z4VWqBh4dkcExdYXp5f3NydHxxbnJ2e2ttgG93c25zenpzd3l1dXVydnV1dHl1gnxkYWp3fIN8fHhYR1JSVllVV1pfX15fYGFeXF9dWlpjX15fWFhWVVldWllUU1ZYWFdXWFpYV1dTUlRVV1lYWVlZV1ZWUlZXVlNWWFdVVFJQTUqQk3tUWlhXX15fY2NgYGBeXWVqaGhjam1sgGtnYWZsa2pqZmRogI6TiY6Nh4SJhYCJj5KRlpqepFZXWmZnZGJlZmZhZmtnY1SHZGFgXVxYWFhWV19eWlpYXGBaW11aWFZXWlhZWVhVVFdWV1ZUUlRVVldVV1RZWFlXVFRVUVFSVVZVVVRSVlhWVIuDhFBZVlNRV1xZV1VcWVNTgFNcW1NUU05UVFVZXFxZV1lPVFlcV01VUlZWWl5bWIiAUldUVVNSWVVPVFRYW1paW11fVldYVk5PUFRaVFBUVFdXVVVSSVJva2NkYDVhY2FkYWFjZTdhY15hZTc4aWhfaGU2NWVeYm1gVUZGSkdHSUlARUhNTEhJSENFRUBDP0dIgE1KS0tNTU5QUUBQSkdHRkQ+QUlJT09KSYpNREZHRURMS0NDRoaAeXZ6gIJ9VIWBdXZ/fIN6cH6CeIR7SIZ/hEJ2doF+QEFBcnd+d3hAa25oS21lYVFPUElKQkZJSk1PS0pFRD4/QUJFR0dIREVDPDs6Pz1BRUdMT0xAUHaCRIFFgIB+f3d/gYB1bGBle0VDRUVKS0lNSkdHU0tKREdQRkU/fnNuYJ2k0YKLkZeZnaCjraCGoIh7ab61yn3IaVlXcmudZU1IQjs4OTw/PT4/PzQ0NzM0NDM0NjU2OTk6PTo6Ojw8QT8/QUNFSEZFQjQ4Oj0/PT4+Q0E3ZFJBQDlZPDVQgGpfQT9DOEQ7NzddUkxFVlxUQkVKS0lNdiUgIiUiISQhIRUWNxQnKCgnJycoKiYlJyYmJBgoLCswGCcoKRcnKSgoKSkqJxgmKyMqJyUhKjAuLSoaMjMzMhwdHR8eNjQfIB4cOSE4HBwgJR4gIh4kJygrKioqLTAvMjY5NTc7UWU6En9/gICAf358e3x9fX1/gIGAgIl/gn68fwF+on8BfqF/AX7Qf/iAg3+hgJN/kIABf8CAg3+sgIJ/pYCGfwGAiH8BgIV/goCFf4KAhn+hgAF/jIACf36Lf5d+BX9+fn5/hH6Df4V+AX+wfgN/fn+MfpR/hH6DfYl+EH+AgIB/fXx8e3t7fX+AgIC2fwp+e31/f39+fn9+hX2FfwZ+fX+AfX+GfgJ9fIl9BH5+fH6OfQF+hH0Ffn19fX6IfQF+jH0BfoR9hX6CfYR+A31+fZp+AX8CAgQAgM3Xl4iLje6B1duJ+fiR64mglpaVlpWPg26qwPOInp6Rg4WPnbO/1ebu5cy0rKKgpqOfrP+Mrq+4ubGuqaufnqGYq62+wrvCvb+1r6WVmZyelpqZkpWVmKKMj7/Gw7+8xcbOx8jU0tfe4efb3ent8fHx6dnm5d/X0NHS0ZvvlqCYgJCSko+inqGgm6Gzra6xvcC6vcXLztvW29zm4+zq64G3x7+8yM/M0cjEy8jSx8W/xsO4zMHJ2M/NwMnHx8XBxszP0o+pxL7IyMfB0M/F09TT0d/Py8/QyMPB0dTSzczBzMvOxsPRxcPKzKeKnoLipszc4t/e2dTY09TV19TU1NnYgNLTy8vO0c3Oz9DQzcjLy83M0crNy87S0sqwparCoYuyuLm2s6+yrq+ys7i2u7m3ta6yrq6vrauqrquppqSsrKmnqaeoq6qsrq2tramppaWnqKqqqqmnqaalpKOioqOlp6WmpqafoKSgnNWwsbKzs7Kvrauvrq2nnJmZmJSUkoqIgIuJioiIiYmDhYiNjYyNkJGYn5qfoqCenpuhoqSjo5+doKCfnp6cnZ6cnJyZlpa/ipaXmZaUlJSRk5OSlJOSkJKTk5GNjIqMj4+OkpGPkpGQjo6OjY6PjI6PjouPkI+Mi46QjoyLjI6OiYqLhN7K1YmTlZSQkpSTmZiUlZSRlpSMgJGRkImMjIqQjpaYko6TmI6Pj5OLiI6OjY2YlpXxzIeTmZuXmJ2cm5iXnJeYmpyYpKWjoZ+foaKZlqCenaOdoaGjnJuHr/f1/PvwgID05/rw9vzw+vj67/WAg4KA/Pr08/uBgvvo6+yw9Jqal5qXl5WXmZiXm5eamZ+ZlZ2bk5OVgJeZlJiYmJmcnY+Xm5ydnaOgnqCioaTd3YqLjY2HhIKF+4KCiIP5+fLu7OnZntn/gu3o9PLz9PLx5/Dx9/ju6vP184D7hIWIgvv2hv3aw8TC1bSXv727v8Svm5inq6qqpKSmprGsnJ6YnJqgqq+loZuRlZSaprO4wby5sZql04DvgPL5g/eAi/vU65CQipCMl6+1s7bJzdC8r7a5sLe0uritlorWsa6MrrPmipams7m8wMPO08f43fj//faZm7eh/LSXq5XqvKuno52cnZ6fkJOXjI2LhoqMjoyOjo+Tj4mKjI6R6Oj5hoySm5yZjoyTm6OnpaakpZ399JDAta+Bg4mYgLzKz8nunaWUiI2CnIOrk4K9sba30cuIjafguqiho62OsP7f1P7/68myxuTazMH07Mzo346Z6Z2XrbDt2oDk+NKH7Yrh6tW0vc+S3s7p+4Hf6emd7/XekoOrl5iW6IiJrpzepruB4syap7K4q6+xqJWur5zCsLGzvJfqyvSJjcmZgKixfXJ3ht90sLVyz8t1wHKMhYaGh4eCdmKNk7loeHhuZGZudoeRo7C0rZqHgXp4e3Z0frZngoKLi4SAfH91c3VufX+QkoqRjY2EgnhtcHN0bG9xbG9ub3hnZ5GVk46MlZecmZqhoaSqrLKnqbW5vLu9tqiys6ylnZydnnWqa3NtgGdoaGd3dHRycXSDen2BjJCLjZSam6mip6iysLi4uWCHk4yIkJaUmI6MkpKZjIuIkIuCkouRnZSUiZCOjYyJjJKVmmZ0hoKPjo6JlZKLmpmYlpyRkJSVkI2LlpeXkpKJk5KVjoqYj42Qknhlc1uccYuYnZqYlJGVkJCQkY+Qj5SSgI2QiYqPko2Njo6PjYiKjI+Nk4yPjI6TlI59dniKcFx0d3l3dHFzb3Byc3d2enl3dW9ybm9wb25ucW9ta2pxcG9sbW1tb25vcG9wcGxtamptbm9wcG9tb2xramtqam1tbm1vbmxoaG1paZSAf4B/gIB+fHl9fHt5b2xramlqaGFggGNiYWBhYGBdX2BkZGNjZGZrcWpwc3FucG5wcXRzdHFxc3Nxb3Btbm1tbG1raWqAZGtra2hnaWhkZmZjZGNjYmRkZWNgX15gYmJhY2NiZWRiYGBhYGFhXmFjYmBjZGFeXmJiYWBgYWNiXV9gW5mOlmBoamlkZWlnbGpmaGhkamlhgGdoZl5hYV9kY2tuaGFobGNjY2ZgWmFiYmFsammokF9maWpmaWxra2hobGdpampocnNxb25tb29nZm5ranFtcHBybGtdc5yZnZyTUFCYjpuUl52Tm5udlJdQVFFRm5uXlJtRU5+RlJVwpWdoZWhmZWFkZmNjZ2RoZmpjXWVlXl5igGRmYmVmZmZpa2NjZGVmZmtnZGRlZWyRlWBhY2VgXFlbqVhYXFqqraekoZyRapy1XKSapaKjpaKemKGiqaign6mopFakWFleWKilXbKXhIOGlX1ifHh1eHtvY2Fqa2tsaGZmZ29sYWNfYmBmbW9pZ2JbW1tgZ21xdnNxb2FvlFqpgKuvXq9cZLCTpGdlX2ZjZ3p/fHuKi4+CeX+Ae4F/g4F6a2SYfXpjg4u1a3R+h42PkpScoZvFscvR3NqIjqCKzo94iXWzint3c29ucG9wZWhrY2RiYGJkZWRlZWZoZF9gYmNlm5urXWFlbW5pY2NnbXN4dnh3eXS2rmCCenZccGJugIiAd3OQcHZqYWRcbFuDZmeRe4CAko1klIFtWE5ISU8+UnlmXnV0alhNWWleXVZxbVZlZUNJZkxKSlByZD5odV0/aUJna2NQVGBLZl9seT9pcXBScHdpRz9XSkhFakBAVkxlUFs7aWRJT1VWUVRUUEVTVk9mWlxfZVGCdZBUWY92gJijc210eL9claBmubptu4PBvL29v8C8rpDErrxfbm5lXV9laXBweYKBfndwcGtqamNfZo5QZ2hxcGtpam5iY2RcaGd0dW5zb29maGddX2FiW2BjYGJgXmZVVXt/fXl3gIKHg4SLiouOkpiOjp2hoZ+fnI+amZONhYaHh2OLV19ZgFBSVlZjXF1cWlxrYWZseH53e4GGiZiSlJWfnaajo1Jpd3RweYF9gHdxenl/dXNveHRpenR7hX9+c3x7eHh2en+EiVhdbmp3eXt2fXp0goOBf4R6e35/eXh0fn9/e3tyfYCEfXqHfHp9f2hWaE54V214fnp4dnJ2cHFwcnBxcHV0gG9zbW9zdnJzdHR2dXBydnh2fXZ4dXd9f3ttZGZ1X0dXWlxbWVhZVldYWl5dYWBeW1VZVVZYWFdWWVhXVlRZWVhWV1hXWVhYWVhYWVVVVFZYWFlaWlpZWllYVldWVlpaW1ldXmBXVltaV350c3Z3ent6eHR4dnVyZmRmZWJkY1pYgFxbXlpbWltVWFleXV1cXl9lamJoa2lna2dqam9ubmpoa2xrZGVjZWZlZmZiYmR4XGRlYV5cX15bXVpYWVhZWVtcW1dTVFNXV1dWWFZVWVhXU1ZXVVVUUVRYWVVUVVRQU1VVUlNUVlhVT1FTT4d9hVZbXFxWWF1bYF1YWltWXFpUgFxbWVBVVFNYV2BhWVNaX1RVVVpSS1FTU1NfXVyReU5TVVZSVVpXVlNSVlFTVVVUXVxaV1VUV1ZPTldTUVhTV1hZVFJIU2tnaWZhNjdnXmdgY2liaGhqYmU2NzQ1Z2ZjYWU2N2hdYWNNdklKSEpJSERHSEVFSkZMSUtDPkVEPj9DX0ZHRUhISkpMTUpEQ0VEREhGQ0REREloc01OT1BNSEZFgENDR0Z+gn18eXJnTXiLRnpye3d3eXh0bnh6gIB4dn1/ekB2P0BHQnt4RoZwX11hbVpEVFFOUFJMRUNLTEtJhEaATUpDQz4/PkJHSUdGQj0+PD5CRUVIR0hGP05sRH6AgUZ/Qkp+aHlMSUNIRURQUEtFT1BUT0xQUExRT1JQTkZHdmZoYqGq4IaLkJmdn6KkqJKEo5SuxtDGen2QfrZ7Z3dkkV1KRkI+PT4+PTY4OzY3NjU1NTc3ODg3OTYzNDU2N0+AUFcwMzU5OTczNDY6PkBAQUBBPmNaL0M/PTA+NENhUkI+TT1BOzY2MDY1VUo8TD5BQ0tHM2FSPiMhISEiHSEoJCUoKiknJCUoJiYlKigkJygXGSkaGSUnLi0XKSwoGCsYLCwsKiYnFykoKi8bNDQ0HzY4Mx4bIR8fHjYdHSMgMyEdIx02KSAhJSUoKCssKS0tKzIwMzI2MkJHXTY6a2YQgICAf318e319fX5/gIGAgIl/g36XfwF+yH8BfvF/+4ABf8SAAX+9gIN/rYCCf6aAhn+CgIx/hICFf4KAhn+hgAF/i4ACf36IfwF+hH+KfgF/kn4Cf36EfwN+fn+zfgt/fn5+f35/f35+fpl/hH6DfYl+EX+AgIB/fXx8fHt7fH1/gICAn3+DfpF/A317foR/A35/foV9hn8Gfn6Afn9/hn6DfJl9BX5+fX5+hH0Hfn19fX59foZ9AX6EfQh+fX19fn19fYZ+AX2EfgV9fn5+fZZ+BH9/f4ACAgQAgMHnoNuxqtna2p+Fj92AnpeWlpaTkId3u8SKm5SDkZL6g4+svNHY3+DSw6mll5+XrLCpgqixt7i9ubSnqKqlqZ+Yq73Eu77DuLy9paWclZWdlp2blJOemZrkwMzKz9LNz8zV09PLw8rL0crFw8fXxdPZ29HT1sjV2tnh25qMsrCtgKKmrbawtcC1tKqwt8HExMe/zMLGzN/h5uno3+Db5MOCwsjJv7W2wNLK1MW+y8fJ0srKyMzZz8/SzdLQxsnGwNHOydOOqsbH0MXEycfCvs3LycvH0cnMzsnQysvMzd3U1s/Hy8TVw9TMqZDJjofA0tLN0dPR0NPX1dXQ0tfV0MzNI9TR2NPV1c/S1tfW2djZ1dPTzM/Qzs/Q09HO1c+wjI2Usba5hLSAs7Wwra2zsrK2rq6tsausraussbCtrKmop6mlqqurra2tqKmop6SamaWop6apo6OgoKSlpaGhoKSgoKCdoKGcmZiJg6yurK2vsbOxr7GtrKypqqeoqqqrq62oqqWmp6anp6ilpaWkoqGipKinqqSjoqKdnp+fnp6emp+goJ6bnJqAmpmcnpyXloymlJWQjpCSkpWTk5KSkJOTkZGSk5GJjY6Mi42Qko6PkY+Pj5GPkY+MjouOjo6QjY6Pj5CRj5CNi4vrztHhh5KTk5iOj5CSlJSUkZOZm5yYlpCQi4qOjIuJjpOYk4uRkpGLiImLh4iCk46Sk4DM/pOUl5iVlpaampeAkpecmZ6enZ2hoZ6anKCdn52eo56fn6CloJqZh6f7+O739/WAhPbz++32gPLw/P38+4P9gYP5gfT37u/8//Lu4p2Vl5qZmpmUl5SOkZCVlpiYmJWSkJualZWYnp2RlpOXl5qXgZmgpJ2aopqjn5iBz4iDgYaLh4KAhYH1+oKB/P+AhPzwgPfskfiGgfbd4fmA//jz3eHe7Nzi6u/09vqJgIPdyMzY54OEgomNi+2Xx8XCv8Gyr7OzsrmnoJ2fn56in6eqvbyuqpuUkp+dlpaPoayzu7e4sbqtps/4/fD04sv5l5qYkpCNh4SSoJ2lyL3PxbvCwLuqrq23sqyhg9mxn5GAy5m67Yqbq7S2tru/yK6t8/Wk9dzzyPmDytW+2Z6tnIj75cmto5iOmZuZkYySk4+SlZWWl5iUk5KMjYPu6erv7/Xx9oGDm56koaCin52cmubri6qtp+ONhvWywMvGk5+kl4OA/8ap9M/u+6WrqLnLxJGX1oqhv7rcr9Oy2sfo25BZgbzSyNP02ezyjoH68ZbHhqbI3drn06zG1oLv+dqFgcfp19z/9ImfuL+W6omPjo6LlZuIpbfym5SkkqyZ2beBrKzCsaCaspqsxMCspbCxvLrS6+WNqYS/2K0PoMWY156Svbu2hG91tGuKhIWAg4F5apyfbndwY3BxwGRug5GhpKmpnpN/fXF3bn5/elx7goeKj4qGe31/e350boCQlIyPk4qLjHp7c2trcmtycW1td3N0ppGbl52fmpmZoaGgmJOZmp+al5SZqJijqKmfoKKVoaSjqaNzZIR+fXV5foeBhI+Dhn+ChpGUlZiRnZKAlJqqq6ytsKqpo6+ZWoySk4qCgoiWkZeNh5GPkJWRk5CTn5aVl5Sal42RkYuYl5ObaHeOjZONjZCOi4aTkY+OjJSNjpGQl5KRj4+dl5mWj5KMmoyalXpli2Jdg42LiY2QjImMkpCSjI+UkoyHio6NlI+Tk4yPkpOVmJiZlJKTi5CAkZCSlJaVkpiSfWJdXnF0eHV1dnRzdHFub3RycXdwcHBybm9vbm5zdHJxbm5tbmpvcG9xcXBsbW1samVkbG1tbHBqa2dnaWpraGhpbGlpaWdpamhnZFpbfn17fX1/gH9+f3x7e3h6eHh3eXt6e3h5dnh3dXd3eHR0d3VycXJ1eXg5e3VzdHNwcG5vb3BvbXFxcG9sbWtra2xtbWpsZHNoaGRiY2RlZmVmY2RjZWdiZWZnZF5hYF9fYWRlhWGAYmJhY2JgYl9hYmJlYWBjYWNlZWVgYF+fj5KfX2dmZWtjZmRlaGdnZGZrbW5sa2VkYGBjYWBdY2draF9kZmZgXV9gXFxZaGRnZ1qLsGVmZ2dkZmZqaWZjaGxpbW1sbXBwbWprbmxta2xxbW5ucHRvamlcb5+bkpmYlk9Sl5adkZeAU5aSnp+gnFKbUVObUJWXj5CdoJWUjmdjY2hnaWhiZGFbXFtgY2VkYlxbWWJjX2FjamhfZGRlZWdmV2NpbGViamFpZF5ShlxYV1peW1lZXFinrFhZsLJcsKVap6BirV1XpZWbrVuzrquYmI+ckpien6OlqF5XWpaKipalYF5bYGSAY6higH57en1yb3FxcHVqZGFhYmJlY2lrdXZua2FeXWVmYF9ZZGtvc3FxbXFrbJKxtqeono2ybG5sZmRkXlllcGtviX+Mhn+IhYN3e3uDe3lyXZp6bWSRc5O+bXiDiouLjZGZhobAw4XNs9Oqznm2rZ2ufol4ZbKhjnp0a2NsbWqAZWFmaGVnbGpra2pnZ2diYlqgm5ufn6OfolZYbXB2c3J0cnFwbqSYXHZ3c6p5Y7CAd3NwWXF0al1btYpwu5yux3J4d4SPinmdoFNYZl1qTl5RYVhqYUM7U1xZYHNkbm9DOXJxSlY/UlloanFjSVliPGlvXT07VWthaXpzRFJVWk0vdENFR0RCRko/TlhpSUdRRVNIa1o8UlRfVktJV0pTZWJYVFtaX2N2i45WeWmcs4+AmL6Nt4J4m56kdmJrtH2+vLy9vby6tZ7WvHJxZllnbbdfY290e3h4eHR1a2thZ11pZ2FIYmdrb3Rwb2drbmpuYlppdXhxdXZtbXBmamFZWWFbYmBeX2djYYJ4gX2EhoGAgYqJh4B7gYOHgoKAg5GEjI+NhYSFeoSIh42KYVNtZ2aAYGRpc25vdmxtZ2tveX6AhHuIf4GGl5aVmJuXl5CahklxeHtya2xyf3uCd3B5d3l+eXh2fIaAgIF9hIF3fX12hIWCi1lidnZ9dXV6d3Vye3h2dXJ7dnZ3d355e3l2hH+Afnp7d4V3hoJuWW1LSGVubGhtcGxpbHJwcm1wdnRuaWqAbm92cnZ3b3N1dnp/gIB7eXtyeHp4e32Af32DfWpSSURUV1xaWlxaWVlWVVZaWFZdWVlZWlZXVlZVW1xbWVdXVldRV1hXWlpYVVZWVlRQT1ZZWVhbV1ZVVlZWWFVSVFZUVVVUVldUUlFLUnR1cXN1d3p5eHdxcXBtb29ubnF0c3VMb3JucXFvcnBvbGxubmppbHF0cnVsamlpZmdlZ2doZWVnZmdlYWRjY2JiZGVhZV1mXl9bW11cWltZWlhZV1pbVltbWlZQVFNSUlZaWoRVgFdXWFVWVVVXVVdVU1hUU1ZVVVZWVlNSUol7f4xUW1lWXFRXV1hbWFhTVl5hYF9dV1lRUVdVU09UWV9bUVVXV1JOUFJOTUxbV1paT3WRUlNTUlBTUlZVUU5SVlRZWFdWWVhWU1RWU1NSU1hUVVRVWlZSUEdPbGZeZmZkNjlmZWpfgGQ4ZWFra2toN2U2OGc1YWNcWmVpX2BdSkZHSUtMS0ZHRT8/PUNGSEdCPDs8Q0RAQkRLSkFFRkdHSUo8Q0dKRUJHQUhDPzlfRkJCREhHRkVIRH6ERUaLjUmJfkR9eEuER0F6b3eFR4iCgW9vZnFobnR2eX18RkBCbWNkcX9JRkNHgEtKfUVYVFFQU01MT09OUEdDQkJDQ0VER0dOTUlIQkBBR0dCPzk/Q0RHRkZFSEVMboiKe3tyZ4JOUU9JR0ZCPkNIQEFPSE9MSlRVUklNTVFMTEtAcGFYVJWOt/GKkZidnp+ipKd9cZygcLeaqoywbaejhpdteGVKa1xSSEM6NDo8CDs4NTg4Nzg7hDqANzc3NTUwUk9QUk9RTVAsLTo8QD8+Pz49PDxdTS89PTtbRTd3XktAPTE8QDszMF1FQHJrZms5PT1ESUdGamMwMDEoKCMnJCklKCYWFSInJiYpKSopFxUsKhgnFhknKCktKiQlKBcrLisYFyktKyotLhcZLjAhNh4fIB8eHiAeJCQkNh8gIiAlHyYlHSUmLiooKi8qLTIxLSwwMzY6R11nOl9cjKSFDn99fHt8fX19foCBgYCAiX+CfoZ/AX64fwF+/3+Tf/+AxYABf7iAhH+vgIJ/poCHf4KAhX8BgIZ/BoB/gIB/gIp/ooABf4uAAX6Kfwp+fn9/fn5/fn5/hH6Cf4R+AX+OfoN/hX6Gf7V+nH+EfoR9iX4Bf4SACn58fHx7e3t8fn+EgJt/iH6Mfwl9e35/f39+fn+FfQF+hX8Hfn59f399f4Z+A318fIx9gn6IfQh+fn19fn1+foh9Bn59fX1+foZ9Bn5+fX1+fYp+AX2dfoJ/hIACAgQAgIH/htbm8bfLkIzJ45uXl5iYlpGHecO8hKevq62kh4H/lL7N3eDh2MKspJaYnZygqJSGrLe1u72/ubeloKWwq6i1t7O9wrixvLmuoZiNl5qdmJqYoKKioOKVrrSyusPIzMPH09TV2NXTzdDX1tXO2dfQzc3O0NHNysLDyYSYt6esgLOqoaSvqrS3u7W6tsHDvr7CwczK1+Hd4fDu6+Di5ubnwcrFy9HIzsHHxsjR1szM0NjMzs7IxMjq4crUxcTUwc/d28nVnLHLysbBw8a5wL3LzsvExcPNxrfNy8nJyM3RztXV08m/m4XUybfe4Nqhl83W1tfU0s/RzNHS1NPUz9LRgNTRy8zI0NLNzM3Sz9XOzdfV0s7R087KzNDY1tXO0s2+l42SqbOzrrC0s7Kurq2psKytqainqaWprKqopqalpqWnpaWpqKipqKemoqSgnp+goJ+enZ2foKSkpJ+bnJmZlJiam5ybl5WV2qSsrKyrrKqnqqyloqWqpJ6qrKmorKqmgKWnpqampaSioaWmo6KmpqWmpKaoqKGdnJqen6Chn6Ggn6CdmpycnZycnpyemZuZ+tGUkpOUlpaQkJGRkZCQkJOTk5GTkY6MjI6PkpCNi42OkI+Pj42Nj46LkJCPkIyKjY+NkI3/087TgI+QkJCVlI6Pj42QlJOVmZWUm5qclZGSgI6Ui4aJjI6Nl5eOjI2Ui4uLgYyRjI2HjIHN6pCPk5aXlJOanpuZmY+YnJqanaCdoaGfn5qfoKCgopydoKKhoZ6giYzpgPv49oODg+3u/oP2+PHs9fr7/fr5+PqChPf3/f/5/4H8+sTdnJ2bmZmblZSQjZGSj5GZlpSVkZCUmJWPDJaTmJOZm5eVnZr0moSdTaKbpqOQ0oSI//j/iIeB/IL7/v6A+PTw7O3y/f3/6KeB7vjg7/Dk2dvcz+Xd0NfY0eHY1tvl4PKOhIqSlJONlIyGhYWFp7HQ1M3Gt7myhLSAsKacmJymqKSpqaqtsqyilZmZj5GUj5qgnKCepq+2s6C2zs+Co56YmJSXlYyOi4uIprfEwsnW3dLDuLO9waassLGijYDTsJmOy5O7+I6dqbCxtbm8v4GI4YHR1MPZg8e+me794OKkr6Sbm5eI9NO2rJ2cmpacm5mVmZ6hm5uWk5CA7+75/Pny+fT5+YWam6Okpqamp6ah4+6evLmw8o+B2LnP19O+nZ+VgOz94PC2u4/S9qirpbnCmKexhI+Gi4vlk9f//ZCMkd/c7u33+s2S7OLa4dfHs5btge6PlJLy1uPUzIj269eGhe/d6/OojITt/oeR+ePY+oePoJusnMadjoIfsdawmJqgvbW047bR2dve4a/J2vDzkJPNi7rWtd+l1oB78Xi4xc6apnZypb+IhoaGh4aAeGqfkWmCiIWGfmZiwXCRn6uqqqKRgHtwcHNydXpoXX2JgoiMkIuJe3Z5gn98hoqGj5OLh5CMgndwZm5vcW5vbnV3eHelbIGIhYuVmJuSlJ2foqOhn5udpaWinaaloZ2ampucmpeRkpdlbYd5eICBfnR2gHuDh4qGi4eRlJCRlJOamaatpqa1s7avr7GzpYiRkJSakpeKj42Ql5uSkpSbk5WTjouQrqeRmY2OnIuYo6OToHB+kY+MiIqNg4iGio2NiIqIko2Bk5GPjoyQlZOYmpeOiG5hkYp/mZmUbWaKj5GRjo6MjYiNj5GQko2OjYCRj4iJh42PioiLkI6UkI6Vk5GNkZOOio2SmpiVj5SPh2pfXWx0dHBydXRyb25sbHNxcW1samtpbG5ubG1ta2xsbWtsb25ub25ta2dpaGZoaGhqaGZlZ2hsbGtnZWZlZWJlZmZlZmRjYo50eHl6eXp5dnl7dnN0eXNwent3eHx7eIB3eHh3dnV1c3J2dnR0eHd2d3V3eXhwbm1rbW5xcnFzcW9ubW1wb29tbm9sb2tubK+QZmRnZmZmY2NiYmFiYmNmZmdmaWRhYF9hYWZjX15hYmNjYmFgYWNiX2NiYmReXGBhYWVjtJGNj1plZWRlaWdjZGRiZGdmZ2pnZm5vcWplZoBgZ19cXV5gYWpqY19gaGFgYFhjZl9jXmNcjqJlY2VnaGVka25raWlhaGtqa21xbW9wbm5pbW5tbXBrbG9xcG9ub15dl1CdmJZRUVGNjZtSl5yWkpibmZycm5yfU1OZl5udmZpRn518lWlraWdoaWNhXFpcXlpaZWRfX1taXmRiXYBjYGVjaGlnZGlnnWVmZmZnamNtaFyHWVuqp7BeW1amVaanplWlpKajoaGur7Sib1WYpZOioJWLjI6Glo2Fjo2JloyRmaOfrmheY2hpamRpYl9eXV5vcYaHg39zdG9ycnR0b2dhXmFnamlra2xvcW1oYGNiWlxfW2JmY2RhZmxxcIBme5CPW3hybmlpa2xkZWJiXnSCioaMl52WiYJ+hop1eXt7cGFXjndrZpVyk8Rxe4OIio2PkZJhaLBmqa+jrm+np5Hi1ry7g4x/c25qYKqUfnhubWtobGxrZmxvcGxsamhloZ+kp6Seop+lpVpsbXV3eHd2d3d1pp9tg4F7rXtemoCEfXx6f3FyaVuntp+jgZFnqrd3eXSCi3Clq1xfWVlci0RgenVDQURmZW5rd3hbP29pYGBgV0hGaDtoRElHa2BrXVVAdG5hQUBvanN1TEhCcYBESndrZnpCRE5MVUpjS0U7V2FWSEtMXFpbdFlpbHBzdVpqc4ePWVyPbZiyl7qNvgxuyV+UpbiJlWppp+iEvoC/vbixn9+ubnt/enx4ZGGzYHV8gXl4cmtobWJgZGBfYlNHYmxjanB1cnNoZWhxbWtwcmxzd3JrcnJrY11UXF5hXl9dZGZoZ4VYaG9udH6Ag3t7g4SGh4eHgoeOjYuGj46Lh4OBgIKBf3p7flJacWJfaGhgYmpmbnFzbnNwe358foB/fYeFkpeQkaCfpJmZm5+QcXh1eoB7gHR5eXqBhX17fIR8fn58d3mWj32EeniHdoKNjoOPY2l4dnZ1dnlvdnJzdHRxcnF6dWt9fHp6dnl9eoCDgHh2XVF2aF9zdXFUTWhucXBtbm1uaW9xcnF0b29vc3Fra2lxc21rbnNyeXV0eoB6d3V4fHVydXyEgn94fHl0WE1FUlhZVFRYWFdVUlFQWldYVFJRU1BUVVRUVVZUVlVXVlhbWVlbWVhXVFZUUlNTVFVUU1JSVVlaWVVSVFVUUFNUVVNUU1RTe2xsbXBwcG1sbnFtamxxamVzdHBwdXJubXFwb3BvbWpnbG1pam5vb4Bvam9xb2hlZmRlZ2pqaGpnZGVkZGhlZmRjZWNmYmVgnYRhXmBdWltXV1ZYWFlXVlpaXVpdVlRUVFZWW1hTU1ZYWVhXVVNVVlVSVlZUVlFQUlRVWVWZfnl7TllYWVleW1VWVlRWWFdYXFZVYWNkW1ZYUllRUFBRUVNbXlVRUVpSU3tUSVVYUFZSV1B7h1NQUVRUUVBXW1dUVEtUVlRVV1pWWVhXV1FVVVNTVVFSVVdWVlVXSURnNmpiYjY2NlpbaTdlaWNgZWdjZGZmZ2o3N2RjZWZiYTVnZlNtS05MS0tMRkRAPj9BPD1JSEFAPTw/RkRBREFGRElLSUZKSXGEREFFSENKRj9eQUN+fohIRj95P3p8fEB8f399eneBg4d7UztodGt5dGlhYmVeaWJdZGNgcGlvdHx5h1BFS1BRUElOSYRGFFFOXFxYVU9STk1PTk1LRUJAQkZHhEWASExLSEJFRD0+Pjs+Pz0/PkFESUpIW2xpQ1hST01NUE9GR0VFQE1UVlFTWV5aU09OVVhISktLRT44YV1YWpuOvPuPlZqdnqGioaBfWY9Ti5eTjF2SlXrCu6GdcXlnUkE4M1pNQkA7PDo3Ozs6Nzo8PTs8OTg2UVBUVlNPUU5TVC+AOjk/QEFAQEFAP1pMN0VEQmFKNW1jS0ZERj4/OjFXXVBVTFxLXV09PjtCRztrbDU7Nzk8VSQoKykXFRcpJykpKi0pISspKSkoJiQYKRYpFxkYKygsKiYYLysoGBcsKisuIxcbODkeIDo4NTwfHiIiJCEoIyEfJj4oIycmKSgpMC0UMTM1OjowOEFPXjg6aFyKpYywiK4CfHuEfQV/gIGBgIp/gn6IfwF+t38Bfsd/AX7HfwJ+f/+AAX/GgIJ/s4CEf7GAgn+ngA1/f4B/f3+AgIB/f3+AjH+CgIZ/AYCEf6KAAX6KgA9+f39+fn5/f39+f35+fn+LfgF/l36Nf7B+oX+EfoR9iX4Qf4CAgYB/fXx9fHt7e3x+f4eAlH+Kfot/CX17fn9/f35+f4V9AX6Efwh+fn59foB+f4d+gnyFfQF8hH2Dfo99B359fn1+fn6FfQZ+fX19fn6FfQZ+fn19fn6EfYt+AX2TfoN/hIADf358AgIEAIDNhO3bl/2VgqjBmJaWlpeUkYVywbWAprKnnZ2dnJmxyNbR0t3d4cytmp6coqSsq4iUsbO4trS1vautpqSorrO0uMi5vr21tKKqpZyim5+loZecoZmdppGGu724uLaxtr+4t73FxsjOz8vN1sjDw8XHztHMzdDCvcLMxb/4o6+usoCmrK2tprTAt726u7a7s7rHvsPFw9rg4uDl5+vo5+nwsovCwMbKx8PR1NLP0cfSycvNzdzSztnS2dTi4tjQ0dDPztfi4eKfoLe5z9TEwMLKx8PAu8XIwczIxMzOys7L1MDGsJqUkJfvxN7e4d/g39iblcfT0tTT0M3LzNXR1NHT1HLS0dTOz9bLz9PT08/Vy8nL0szMztPSy9DR0tTRz9HQz9TQx6uTkZSlsamtr6+tsKqur66rq6yoqqaoqaWkpKWrqaSlpKGipKCgnJiVmZubm5mXlZaXnJ6in5yZm52amZmYm6CdmZialOKsq6ysrq6qqaOEqVyqrKyrq6utrKanpaWkpKWioZ+mpqioqKWhpKilpaainaKhoqOioqOio6Cfnp2cn5qbnJyampybnp2axf2Uj5CWl5KMipGSj5CPkI+Oj5KRkY+NjY+PkI2RkI+MjISPgJCPj5CKjJGPjImA4NPM3Y2PkJKPjZGSl5SSjo+RkJWUmZiWlpWYlI+QjJOWkY6Oi4mVlJGSkJeOjYiJj4qPj42Bwt6LjZGWlZiYlp+hnZ2bm5OTnZ6dnZifpJ+go5+joJyfoJeem52jop2CwJmL+O7k3dni+/2ChIH/gfnz8YKBgIGD/vaDgYD86P3/+v6E+/iZlKGgnJebmJqWlZeTnpmYmZyZl5mZkZSOkJSKkZaTl5eVmpP7mqOcn5yZoZqUgvqL/f319vP48fT6hYOAhYqB9vj3+/eC+fu6yuv06eqA+O3v+uvmzbi9xd7thJKPkYyJj4KZoZyVj5WLiIuChY3LgKbIzdLIv7u4ubawrLCvrqqpnqOusa+popebopuWlpONj5CeqqisoqSmqa+woL72m5uipKeRj5GSipaVi5OyxtPm5uLT08S4tbm0sreyp6SYiNGon5PcoMDxiZWjq66wtrm+2Nq68/CjmI2C/K2qo4iP5Nycr6SdnZ2gnJWPh/vKgNnUwbGkoJubnJiKgoSC/Pb0+fT08JScoKOnqainpqqp3emktLas7Jvr0c3S1tvdnqOXgvSIgcLdmcjU4Ki3oqy7vOC8q56qq5iS6OHRptS79rnhwMm+5vrd3cSUlZya1oHL84WR1oSP5OiEg4n0nqDiz8a6ysu467Hq9oaGk5z1KoWWlqGc3eKup7S2sLSauo+syb666bSv7bi62IeYh4PLjsbYr9uyy4Pi8gq2csq2fdF5aYmihIaAh4SBeGWhj2eDi4F3eXp4d4ydqKKiqaesmYJ0eHN2eHx8YWyDgoN/gIOLfYF6en2DhoeJmImOjYiHeX97c3hyc3h2b3B0b3J6amCLjoqKiYSIj4qJj5WWmJ2emZyjl5OSlZmfoJydnpKNkJuVkLR1gYODd4GDf3mGjYiOiouIjYWAjJeOlJSQpamqqKytsrO0truKYIqJj5KQjZeZmpeXjZiRkJSVoJeVoJmem6ionZeamZiWnaamq3RygoGSlouKiZCPioaDjI+JkY+LkZGPko6Wh4x9b2tpbKWHmZeZl5iWk2pkho6Nj4+KiYmLk46Rjo+SkI2Qi4yTiIyRkpKNlYxQiouQiomNk5ONj5CQkpCQkZGQl5SNeWZhYWx0bG9xcW9yb3FycnBucGtta21tamppanBvbGxraWlraWhkYV9jZWZlYmFhYWNmZ2poZWRmaGSEZYBpZmZkZGOdfXp8e31+e3p1eXl5d3h7e3p5ent8eHh3d3V0dXFycHV0d3l5d3N2end1d3NsdHJydHJxc3J1cHFvbm9zbm9ubm1tbW5xcG2MsmdiY2hqZ2FfZGVkZGJkYWFiZWVkYGBhY2NkYmZkYmFgYmFhYmRiYmNbYWZjX1tWm4CUiZhjZGZlY2JlZ2toZmJjZGNpZ2ppZ2lnbWpkZF5namdiYF1caGhjZGNqY2JeYGZgZGRjWoeaYGFlaGdpaWducW1sbGxmZmxtbGxob3NubXJvcm5qbm9obmprcHFsWYRrYaqlmpGKjZyZT1FQn1Ofl5dQUFBTnphTUlCci5qemoCfVKGcYmVwbWpmamZoY2JkYGhlZGZpZmNiY1xeV1xiWmBkYmVkY2lkpWZrZGZlY2hlYVWuYaqpoqOgo6Cjp1tYVVhdWKWop62rWayrfYmfqKKjWKmZn6+hnYp7gYqhrF9oZWhkY2ZabnNva2dsY19hWV1ki2yAgoZ/eXVzdHNxbYBubm1raWNmbm9uamhgY2pkYWBfXFxbZG1tcGhnaGtvcWd+qW5vdHd5amdqaWJramFnfYiUpKKhlJOKgnyDf3x+d3R2bGKVeHNrpHqWxW93gYeJiYyOkaSmkMLDhn91bNeXpJt9ery2f42BeHJub2xkYF2tipORhXpyb2xsbm1gV4BYV6mlo6ahoaBpbnJ1eHp4d3Z5eZ+cdH1/eKuCrJeOfX6Bl3F2bF2rYVuIkXaXorN4gXJ5hYatwJlxdXZvaX1oY0tiW3VSbVlXVWp0ZGNYRUVKR1Y4U24+RWE9RGZsPz5Bb0VFaGBZU19ZUnNTdXtDQkpQeUNNS1FOa21YUlhZVR5aSlpCVGdfXXpdWX9kZXpNWE9RknGhsZG2lLl82deAk16qoW64bGKS0by7u72+u7mvltywbX5/dm1ydXVwfIKCeHV3dH54bWVpY2NlYmBLVGhlZV9iZnJrb2lpbnN1c3B7bHBxbGxgaGZgZWFjaGZfYGJdYGdaTXJ0cXNybnJ5c3F1fH+BhYWChIuAfHyAhIyNh4aIfXl8hYF7mGFrbG1iY2xtbGZxeHJ2c3Vxd251gnp/gHyQk5SSlpacnJ2fo3lPcnN4eHVyfIKEgoJ3f3h4enyKgH6JgoeFk5SIgYOFhIKHj5CXZ15rbXt+dHN0fXtybWx0dm94eHV6eXd6dYBxd2mEW4CAZ3RxdHJ0cnFQS2Vram5tamlqbXVwc29xdHNwcmxudGlvc3V1cHlxb3B1b25yenxzdnh5fHp5enp7gn94aVVPTFNcVVZYV1VZVldYWVhVWFRUUVRWU1JUVFlYVVZXV1VWU1RQTExPUVNRTk1OTU5QUVdUU1FRU1NUUlFTV1ZWVIBWVox1bnBydHRucWxycHFwcHJ0cW9vcXFtbm9vbGlqZ2dmbGlucG9ta25ybGtva2RqamlraGhqaGxoaGZnZ2tlZWVkY2JjZGZlY3mkX1pbXl9bU1VcWVhYVVdUV1hZWFhTU1ZXWFpYW1hVVVVWUlNVWFRVVlBRWFVST0qFgHWFW4BaW1tXVlpcX1pXVFRVVFtYW1tZW1thXFVVT1leWlVST05cWlNUUltSU09RWFJWVlZNdX5NT1JVU1VVU1pcWFdWVlFRV1dVVVJYXFVVWlZZVlFUVk1TUVNYV1NEZFFJgH5yamFgaWU0NjVsOW1lYzU0NDZpZTc2NWVbY2dlaThraIBHSlJRTkpNSkxGRUZESkZGSEpIRUNDPD84P0Q8QkZFR0ZGTEh7RkdDRENCRkNCPIhLf314enZ5d3l7Q0E+QEZEfYB8goFEgH9eZ3R6d3dBfG91gXRyZFxgaX2HS1NPUU5NT0ZXW1VRTVJLSEpERktkSldXW1hUUlFRT01JSktMSoBHQkRISEdFREBDR0RDQkE+PT1BRUVIREJCREdIRFt9UlFVV1pNTlBOSE9NREVPVltkY19YWlVRTVFOSktER0tIRW5gXVyuocP8jpGYm5ybnp+fqZV7n6Jwb2tbr36Ji3VrpJxvfGxYRjk4NjEvLlZDSEpDPzs6ODk5NzArLCtTUIBQUlBRUDY6PT9AQEA/P0FBVlE6QUM/Xkhib2dHRkdVPkA7M180MUtZRmdbXT9EPUFGR2SDZ0tPTkhFTCspIycaKiMoJiYmKiwpKiUYGRoZKBYmLBcYKhgYKioXFxgrIiMqKSgoKiooLyg7PyEgICI6HyIiJCU9PSgpKiwpKictJBoqLisqMC4vOTQ5SS01NTt2YpCmiK+MrG+2rQl9fn1+gICBgYCKf4J+5n8Bfut//4ADgIB/x4CCf6+AhH+ygIJ/qIADf4CAiH8IgICAf4B/f3+EgAV/f4CAgIZ/BIB/f3+jgAF+iYADf35/iX6Gf4V+AX+IfgF/jH6Uf7B+on+EfoR9in4Bf4SAC399fXx8e3t8fX5/i4CQf4d+i38JfXt+f39/fn5+hX0BfoR/CX5/f359gH9+f4Z+AX2IfIR9AX6LfYR+Dn1+fX1+fn1+fn19fn5+jn2EfgF9hX6CfZR+hX+EgAZ/fnx8e3wCAgQAgJzt4ZWO04Wjl5aVlJSSjoN0wLP5p7Sxp6KVl6CpxNrp9IGGguu4oouHmaawtKL/qLavtsK7r7SyqKilm6ups7nFx721xMi1paOZlIqOlZSfmqOiop6P9Ky2vb66u7Wztrm9vsTDvrvBwry1ubzDxsW/wsPGxsbDwMTEsOafq62ygLWzsruvra+2xcS/vL7DucLM0dTY3uXl5eHo7/Lt6Ojn2L/L28nOyMfKztDS0NLR09XE09Pg2trf2tjT59nMyMve09fV2uKmlr/Ex8bHzMzSwcbO0szFysfIx8nLvqmelZKxr66Iz9/e4ODf4uHe39zVoZm/ytLU1NfQysfOzcnLgM7N0cnQzM/Nz9HJ0s7Qzs3Oys7Q0tHJzczL0c7Qz83P0dLT1NHMt56Oj56strSzs7GurrCytbCvq6iqrq6qq6qsrKyrp6WfoJ6kpaGgn5+dmpqdoaehn6GempybnZ2hm5qgn5ybm57xmrCnp6mqqKipqaqvrqunp6mqqaysrKSjgKShoaGlp6Wio6ippqWnpaempqakpaWkoqSkpqOjoZ6ioKCenZuampqWmp2cnZeZmJWqj5eVk5SSj4+OkI2OjZCNjY6OkJKRkI+NkI6OjpGSj4yOj46NkJCOj46JgOXQ0+WGiYqPjZKNkZGRk5KSkZGSkY6Uk5WSlJeXlZWQj5CQgJOQjpGOjZKLjpSPk5CNjIyOjpGShcjkkpWTk46RkZaYnJ6ek56fmpmYl5ubm5yhoaGio56ipZ+cn5+dnqSinPzOoqWooqCjn5+YgvXr4t7b3/LxgoKFgID//IOEg/j1//f0gIKB2sCXm56gmpiZlJmYlpyampyZlZqXmpuWkpSUgJiUkI+VlpCVm5eOjJeco6Obnpuq1oeKhIyPgYOEgIP6hpSLgf77+Ozy6e79g/SBmPj1g/DMwtXNzczi/oiJjY+NkZKUkI2LlJOcnJyWjYyMiYiEhoyQ7qa+vL+7wrWqpaitqqKfnp+pq7O5sLKkopWTk4+PjY6WkJekqa+yqaSrgLG0sq6rqNeHoJydnqCgk46YoJSesLnJ1+bc3NfNyL7CuK+4ubSonofpyLmwnt6Zp9iBkKCprK+0t77JjoPO+uWU8Z6kibjJrP7cscSRsayhnaCko5iMmpqbop6WlI+G+djRwqyimI+HhYSOqaSjpamrqqanp6DB+6yvrqPUodj4gPPZ3Oj7mpyYh/SGhuOD58Wv0dbM1OOJm57j5p2R/qaH5YDTjfGE543ExIeCjpG/mYPykYeA4YDng+CDs6TY65PC0N/1k/P8xPiChoff3aaFkI2SmY2Eq5GEg4qsoISJkcPPxLSasLGznLXchtiA8vCWuP6lzM6gw4639OLawfTsgITFuXp0rm2MhYWFhISDfnVnn43GgYuIgH1vdX2Cl6m1vmRpZLSKeWVjc3p/hHS3fomChIqEfoODfH15cYKAh4yWlYuFkpeGenltamJnbGp0cXd2d3RosH6HjY+MjIiFh4qOkpeWkI6Ulo6IjI2TmJmUlJaWlpWTkJSUgqZzfIGFgIeGho2BfoCFlpSQi42VjJGan6KlqK6uramutri3sLO2oIqQnZGVkpCTl5eXlJiXmpuNl5ehnZ+loZ+aqJ6UkZSimZ6co6l8bIqMjo2NkZCWio6TlpOPkY+QjI+Ph3hwa2iBgnxdjZmXmZiXmpmXmJaTb2Z+ho2QkJONiYWMi4eJgIyLjoeNiYuIi5CIkIyPjYuMiI2OkZGIjY2KkI2NjY6QkpWXmJOPfG1gX2Zvd3Z3dnRycXR0dnJwbm1ucnFvbm5xcHFwbmxoaGdrbGloaGhmY2NnanJsamtoZGZkZmVpZWRoaGVmZWeibX93eHl5eHl7enp9fHl2dXh4d3t7e3V1gHZyc3J2eXdyc3Z5dXV5d3l3dnZ0dnR0c3N0dnRzcm9ycXNvb29tbWxobXFtbWlra2l2ZmpoZWdlYWJgYmBhYGRiYWNhZGdmZWRiY2FhYWJjYV9hYmJhZGNhY2BdV6CPkZ9cXF9jZGliZWVnaWhnY2NlZGFoZ2dkZmppaWhlYmNjgGZmY2RhYWhiYmpkZmRhX19kZGdoXYqdZWdlZWFjYmdqbW5uZm9vamppaGtsamtwb3BwcW1xdG9sbm5tbXFwa6qPcnR1b25wbW5pWaefko2MjJeVUE5SUFGhnVJUUpmUnZqYUVJSi4BlaWttaGdnY2ZmY2dnaGpnXmNjZmZiYGJigGVjYF5hYlxiaWVdXGNma2tlZmVwk2BhW2JlV1hYV1eiWWFcVqutq6CmnqOtW6ZaZ6+qXKeLhpePkJChtWJjZmdmaWlpZ2VjZ2dwcHBrZWZlYV9dYGNmp218d3l1eG9oZmhsamVkZGVra3F1bnJpaF9eXl1dXF1hXGBpbnFzbWlsgHF0cnFucJRgdnFwcnNzZ2Rsc2lwfoKLlZ+YmpePjISFf3mChIJ2b2CkjoR/c6h5hK9qdH+EhoiLjpKYbGilybl3x4KMdZ64n+XGkp93kYt8dHBxcWpgaWlqb2xnZWBbrJONgHVtZ2BbWlphdXJxdHh6end3d3GNu3V4d26ZgpmtgKV+foWxbnBtYK5gX59aoph9pJqQk5xcaGu68YxsoF9Jcz1fRHM/b0ZcWkE+REhTTEFsRD46YjtlPmdAXVVsdU1cX2p2SHN8W31BQkJvaU1CSklLUUhEWUlAPkNZUD9ERmJoY1tJV1dbTlx0SnlNlZRbe76Dqa2Fo3mi4Mu/pdDNgHWsomtpqXu/u7u7urm4tq+Z3q3SfIB9eHVscHZ1fYSJjEhLSIpvaFlVY2ZlaFyRY2xlZWhlYGhsam1qYnFvdHZ9em1mc3lrYWNaWFJVW1pjYGZjY2BUjmRsc3Z0dXNwcHJ2en9/end9f3hwdXh+g4R/fn5+f39+fICBcItcZ21xgHFwcHtybGtuf313d3l/dHqFjJCTlJWVlpKWnqKhmZufh3F2g3h7eXh6fn+CgYKAgIB0fn+Ig4SJh4aCkYmAfYCLhYqKjpZvV3N2dXN3enqAdXd7fHl2eXh5dXh8dWZfW1tycWRGanRzdHJwc3JwcnBwVlBgZmxvcHRwa2dvbmprgG5ucWlybG5rbnVtdHBxcXBybXJ0eHhuc3NweHR0dXZ5fYCDhH56aFtOSk9WX11dXFpYV1paW1dXVVRVWFhUVVVYWFdYVFVRUlBUVVJTUlJSTk5SVFxXVVZTUFFRU1NVUU5SUlJTVFeEYnRsbGxubmttbnB0cm5sam5va3Fxc2trgGxpaWdscHBnZmxvbGtwb3FubnBsa2pqbGpqbWtpaGZqaWpnZWZkZmJeY2ZiY15fYGBnW2BeWVpYVldVWFVVVVlWVVZVV1lZWVpXWVZXV1dZVlNTVVZVV1RSVFFNR4p7fIpRTlJZWV5XWlhbXVxZU1RXVlJaWFdVWF5bW1xVVFRUgFhYVVVTUlpTU1tYWVZRT1BWV1laT3OCVFVSUk5QUFRWWVtaUFlaVFVVU1ZVVFVaWFhWWFVZXFZSVFVUVVlWUYRuWFlbVVNUUVNQQn12aWJiYmdjNDI2NDVraTY4N2RdZGNhNDY2XV5JTE9RTUtKR0pIR0lKSEpIQERFSEdCQUNDgEZFQ0BDRD1CSkZDPkFFSEhDRUVRdE1OR0xOQ0NDQUF7QkZAPHyDgXl/eHqDRHpCS4Z9RX1mYXFrbW17jExMT1FQU1RUUlBNTk9XVlZSTUxNSUhFRkpMe01TT1NSU0tGQ0RHSERDRERJSUtNSEpFRD9AQUA/Pj9CPkBERkhKRkNHgEtKSklJTGtIWVNSU1VVS0lPU0pPVVVXXGBZW1lWVVFRTkhPUVFLSkF1bWtpZ7Wdrd+Gj5WYmZqdnqGlal2MqJtkqW5zYIObiNKzfYdmfnljUEA5ODUuMjEwMzIvLiwqT0NCPTg2MS4sLC0zPjw8PT9APz0+PzxNYTw9PTlRTF6AgHVLSk1mPD07NWA0M1YxYF9dW1JMTVAuMC9mnF5JUykmMBcrGSwXLRoqKxgYGBkqHBkwGxgZLhkvGS8aIB4vMR0tMDM4HTU3LjUZGhs2NCYeHx8gISEhJiQjIyQpJSIkJCotLCsmKiwsKi0xIEMsZGk5VZpzmJ97l3KRyKuVhayqB35/gIGBgICKf4N+jX+DgIp/AX6nfwF+pH8BfqR/AX7Df/+AhIABf8mAAX+rgIR/tICCf6iAgn+KgIh/hYAFf3+AgICFfwWAgIB/f6OAAX+IgAJ/fop/AX6Ef4h+B39+f35+fn+Jfpp/sX6hf4V+hH2KfgF/hYALfn19fXx7e3t8fn+TgJd/CX17fn9/f35+foV9AX6Efwl+f39+fn6Afn+EfgR/f399hHwMfX19fn1+fX59fn19hH4SfX5+fX5+fn1+fX59fn5+fX1+hH0BfoR9Bn5+fn19fZx+CH9+f35+f39/hIAJf358e3t8fX19AgIEAICNk+2fupaVlJSUk46JgXPNxP2fp66uqJucpr3O7e/z8P/76OC+srautZSRlISLtLi5ucTGvbOyrqqrqaiosLvCwdTIwsfKw6SbmpqJjZSep6agoqGSgJmptbm1u7i8vLazr7W3s7O4vLzAwMPAt7KwsL7Fwreyr7a+up/tqK2xs2Wzr6+0ubuwrbm/wsK9wsbHvr/A1NfX4uXl5/Dr6PH17rCQycTUycTCxse6vNLSyczPysrQ29rS2t/e6+nh6+PY2OPU3tXd4rWDvLm+ucbK4MnCw8a5wsTIwqKampWzuLaa1Kfe4YTdgN/e3d3h4N/c3tWimr3LzNXZ0cvGzdLRzcvGwcfLyMvMztDRy87N0M/PztDQy8jKyMrPyc3MzMzLzMzLysvLzszCrZeNkp2lrrOyraqpq62qp6imqKqmqqanpqWmqKempKWoqKakoZ2bm52fn5ygoqCfnJyWl5mZmZaWl5aWlteqgK2vqq2rq6Wpq66vq6urqqqop6ytqKemo6KlpaSoq6inqKurp6KkpaWmp6SkpaSio6Wmp6Kin52cnpybmpmYlZabnZqZm5WWloe1kZGUkI+Qj46Lj4+QkI2QkI2Oj4+Mio2OjI6OjpCOkZCPkY6Ojvrgy5jqiI2NjI6PkJGQlZCTgJOPkZOOj5OSkZWZlpCVlpSSk5OPk5OMi46LjouNl5CNjY2Ijo2Qk4uQgsjnjJCSkZGOk5aanZ+Zmpydnp+dnJ2cnp2bmqCcnqShoKSlo6KnpJudopjp1p+lqKGhpp+koaWln6Kgl5iai4D69enq4uHe8IaD+vT/gYCBgoGfj5yagJ+bmZqblZSamZmanZqVlZKbnp2Zl5efmZeXm5SUlpqZoqqCnZ6jnpid163j8PCBkYuKi4mGi4mKipSFgYaC+Pf0yru/ws7FiNDV7IeBgoD3hoOAgfyGhoaSioqJi5CQlZCQjo2NjoOHhIWViJCYhrumwLa1vrSrqZyinpmcoKOngKKttri3tp6gkYmChpWSnaGcoKimpqWnq7a5w7u+vbK74IqblpGMjJCJjpKWnqy1ydPY3+Tax8TJvcHGu7jAwaiZg9+8tqbyoaLpmJOcpq2xs7e8wMmpl97/3pX9kpeMyumb17frlPKhraKbjJqhpqajnp2io6CfoqWrra6vqqWcgIiOj4yPlJSWk5CLiYKHjqypranO38n089/ekafHzcvAsLG4n/zzn+XI0a3KztLh67Cdiqzc1LHl87CPmPfb9ePc2cjxgPjh3Pv594KVmIqHjdD0p4mIsc6Pi6Gl7ImqgIuP/fbq3//w3deisIyS2oOcqbnChsrisLvQuc7kxOP2F4GBjYqjzJC50LiGlsaj1+TmrNjLiNLQBnV6xYSehYWEgIB7dGaqnMh8gIeFgXV1gJShubq6ucK/rqmQiIyFim5sbV9nh4iIiJGRh4CCf3x9fXx+ho+UkaGXkJWYk3pycXJkZWtze3l0d3VoW2p4goiGi4mMi4aDgYiLh4eMj4+TkZKQiYWDg46UkIiFgoeOi3Spe3+BhIaCgIWKjIOBi4+SgJKQlJmYj5CRn6OkrK+ur7i0sLa7uIhlkIyYkpKNjo+HhpeZkZOWkJCWnZuXoKOir6+mraeeoKibopykqolbhIGGg46QoJCKiYyDio2QjHNtbmyChYNuk3Gam5aWlJaZmJaWmZmYlpmTbmd+h4iRlIyGhYuPjYiHhoCGioWJiYyPgJCJi4uOjY2NkJCMiImHh42Hi4qLjIuNjIuKi4uPj4d0Zl5iaGtydXNwb25wcW1tbWtsbWpubG5samtsa2tqa21sbWtnZWVkZmhpZmpraWhmZmFkZmRkYmRkYmJkjnp6e3l8eXl2eXt9fHp7enl6d3Z6e3d3d3RydXd0eXx3d3p6fHl4dXZ1dXZ3dnV2dXJ0dnh5c3RycXBxbm5ubWtoaW5wbWxva2xrX39mZGZjY2NiYF5iYWRkYWNlYWJhYV9dX19eYGBgYmBjYmJkYWBhp5mNaaNcYmFgYmNmZmRsZmloZGdnY2RnZmRnamhjaWxqZmVlYmdnXl5iYGJfYmyEZIBfY2FjZ2FmXY6gYGJkY2NgZGZqbm5qbG1tbm5sbG1sbm1raW5qbnNubnJzcXB0cWpscGmhlm5zdXBucm1xbnBxbG9sY2VoX1eqpZiblJGMm1dTl5GaUE9SVFJmYWpobWhmaGhhX2NhYWNqZ2JjYGdpaGVlZWtmZWVpYF9iaGpxcoBWZ2dpZWJmjXKZpqNYZ2JiYmBdX15gX2NXVFlXpqerin2Bgo+KXpOVpF5bW1yvYV9cXLZhYGBrZGNkZGdma2hoZmVkZV1gXV5qYGVrXoBrenNyd3BqaWBkYV5iZWdpZ252eHd3ZmlfWVVZY19namVobm1paWtudXd+eXt8dn2dYoBwbWdkZGZgZWVpb3h9jJOVnqKaioaPiYuRh4KHiXdvXKCHhnu2e4K6enN4gIaJjI+RlZyEdrDOtHrVfYV6qc6QxKHIfciEjYB1Zmxwc3NwbGlub21scHF1d3h4dXJsW2BiX2JlZmhkYmBeWl1rcnFyb46ujaSXgoBXcYWKi4N2d3V+a6ijeLGTl2+Eh4uUmniUhXGDeF52dlpHSnhqdmpoZl50PXVranZ5dz5HSkJBRmV7V0RIVmRHRFJTcERcQEZJhIR8bIB8c3FXYEhLZ0BPVV9jQml4WGBuX2p6Zn2MSk1WVWqUcZmtmW99ppHO1seYwLByr62AZ2y5i8m5urq7u7q2sqyW6LjRdXR9fHtwb3iFhI+Kh4SGhoCGd3V5cXdbV1hLUGtra2pvb2hma2pqbW1sb3N4e3aEeXJ1endiXV5fU1VaYmpnYWRhU0hSXWZubnNxdHJuamlvcW1wdXl6fXt9enNubGx3fHlwbGhvdnNfjGVpa3GAcWtqcXZ5cGx1eX16en6Cgnh7fIyRj5SWlZedmZWeo6B4Unl1gHh1dXh5cXGChHx+fnl4foeEfYaIiZWXj5iUiouTiJCJk5h7TnJvdHF7fo58c3R6b3V5eHZiXmBccHNyXnFTdHVwcG1vcXBwb3NzcnB1clRQYGhob3NrZmVtcW+AamppY2lvaWxrb3N0bG5vcnJzdHd2cm9vbWxybXFwcnNzdnR0c3R0eHhyYFNKTVJUWl1ZV1ZVV1hWU1JQUlRSVVJUU1BTVFFRUFJUVFNSUFBQTVBSVVFUVlVTUVFOTlBPUU5SUlBRU3pucHNvc29vam5xdnNvcXBxcGxrcXRtbG1DamdtcWpvdXFvcnRwbmttbWtrcW1sbm5sbm9xcGpra2hmaGVmZGJfW11kZ2JjZV9hYVdoWVteWFpZWFZUVVRYWFRXWYRWgFRSVFNUVlVVVFFWV1dYVVJTjYJ3WpJQVlVUVlhaWVlkXmBcVVlXUlRYVlZZXFdTW2BeWFdXVVtaTk1STlNQVF5TVldXUlVSVFhSWVB2gU1PUlBQTFJUWFxbVlhaWVlZVlZXV1hXVVNXU1dbV1daXFlYXFhQUVVOeHRTWVtWU1dRgFRRVFRRVE9GSU5GQH53bG9pZV9nOzZfWmI0NDY4NkhJT01RS0pLTERCRUNDRUtJRUZDSEpJRUVFSkhHR0lCQEJITFJSO0VFR0RBRGNXeYN9QlBMTU1KR0lISkpLPjtAP3l9g2ZcXl9ra0Zvcn1JRkdHiUtJR0eLS0pLVlBOT05QgE5SUE9OTU1PRklFRlFHTFFGX0tUT05SS0ZFPkI/PkFFRUdFSU5PUFBFSEI+Oz1EQUdJQ0NGQ0NFR0hMTlRQUVJRWnBHUlBMSUpJQ0hJS0pNUFdaWF5jXlJRWldYW1VRVFdPTEF6bW9ptJyr6pqPkJSXm5yeoaOmfWiVrJVou29vgGWMn3uulaptrXWAcFtGPDg5ODczMDMzMTEyMzQ0NTUzMzApKywrLC4uLy4tKyspLjgzMjIyQl1XeWdLSTA7RERFQj07OjJSW0V9VU0vNjk8PkA2W1M+Qj8yOj0iHR42MjUzNDMuMRs3MjE3OTgbHB0dHB40Nh8cHTE2IB8hIzogOSUfIiREQ0A2P0Q/PCYoJCU/IyYmKSkhKTErLTMtMjUxPEUoLTQ1R3ZhiJ6NZnahh7GoonuYimGclgSBgYCAi3+Dful/AX7mf/+AhoABf8qAAX+lgIV/toCCf6mAgn+TgIh/BYCAf39/hYABf6SAAX+HgAF/hH6Qf41+hH8BfoR/AX6af7J+oX+EfoR9i34Bf4WADH59fX18e3t7fH1/f6aACX58f4CAgH9+foR9AX6JfwV+fYB/foh/iH2Dfoh9AX6GfYZ+B319fn5+fX2EfgF9hX6IfYR+AX2RfoZ/hYAMf318e3t8fX19fn+AAgIEAICu05iTkpOSkpCMhH1yy9Dwlp6gmKGcjqi4xNvo7PuCgP7jzq+1tLa4vLih8qO9uMK+u8G+v7SvprK1r5+owbTEysjSvM3It7mqqqiioJ2ZmaCkq5+BkrS4t7S0ur27u7zBw8LAv723tbOur7O3t7m5uLW4tq20ur6/u5z2qqiwsoC2tKessbe9vLm6wLi0urG5wMvP2Nva29rg4drj5dz1/97Sz8vLzcu3yc3Fv8XHyL7LzNfW2dbO0Nvd3ebn4uLp2OLg4+Hd1t+99L3EzMbNycK8vLutlpmToLi0uL2xrYvG3eTk4ePk3tvZ2dXR1dja2dzZ2dOhmcHQ09jW1NbRzYDV09HMzc7LxcfNyMnJycrKzMvIyszNysbJyMXDxMfHysrJycnMzc/KysfBw8G8rJiTkZilq6qqq6ytp6KipaWlo6GjoKKhpaakoZ+hoKKin5+goqSgn52dm52dm5qWkpGTlpiYmpnvhq2uq6mko6mpqKiqqqipqKmnqKmnpqSio4CkoaGhpqWqrKSop6impKGhoKKhoaeloqGio6OinZ2enZqbm5iTlZaXmZucnJqak5WXlOfOlJWSkY+PlJSPj46MjIyOjI2MjIqKjY6MkJGNjI2OjoXv39nS4fjy0KKCkZKRkZWVkJKWkZKOjo+MkZSXmJWYlZOWmJeXlJSUlpqVjHGPjYuMj4+MkIyPkpCOkI+T8s3vlJWXlZWOkpKZkpqZmpmem5qinZ+inpygo5qfoqGdoKCkpqSjp6ihoJyj3e2moaKjnqihpKOkpKOjn6GeoaWkpaWenp2kpJSThv378PLs597eyK+Xn5WXl5ucm5ydn4SdgKGanJ6bm52WmpWYn5mXnpiYnKSdncPkqKejoaHwqISC8oX+jZKChoSD897v3+Pp3t7g5Nzo/f6BiIWS/L34gouAifn//f2GjIqHhIKGgIWKkI+Lj46TnpqOj4iIlY+OhISD9f7tmau1t7CzvLm2srKyq6qxqrCzuq+qoqKenpSOgIqQmZWbpsDBrqejprC1tri4tbi+vK7D9pmamYyOk5OWmJeZsMXK4uTg29HPxMTHyLy3vrq/nIv128S3rYKvr32LjZikrLCxsra9wtCymdz83Zinz7Ks8JCqsYD7oaj5naeemJmdoKSjpKOioaOnqq2ys7CulamprrGytbW0s7SxgKSPnNvT1dSBoLfm3tzn2eyKmpiNgvyA5sKz4cbJoaauvcLZ8uyb8s2K6aTz14qn2p2XlPuKjImS3vbyj4WRiomxl6WehbGqm4v6hIeSmpqnj5CM+IyNgoaQkvuRkqWlnJmTi5SVmpbGsMK1sMTL6uiOnID7mveSs9LEmsar3IC+C4P4tNTElNnLjpf/gJG1hoODg4KDgX14cGKkobZzdnl1fXdsgY2Vp7G0wmViw62fhIiJio2OjHmze4+Kk4yLjoyPg394hYiDd32Th5SYlp+LmZeIi39/fXl2dHFvdXqBdl9og4eHhIWKjImIi46PkI+PjYmIhYGBhoiGiYqJh4iHgIWKjpCMcrJ9eoGDgIeGen+DiY2NiomQiIeOhouRmp2lqKinpKuspqywp7zJsJqVk5OUloWQlZCLjpGTiZOSmpmem5aYoKGhqKmmp66fqqWnpaKdpYythouSjJKRi4eHhXxrbmp1iIKIjYJ7YIeUmpmXm5uVkpCPi4eLkJOSlpSUkG1ng42NkpKQk4+IgJKQj4mKjYqFh4yIiIiHh4eJiIaJjI6Mh4mGhIKDh4iMi4mJio2NjomKh4KDgYB3aWVgY2txcG9wcXFsaGdqamtqaGlnaGdqbWpoZ2lpa2tnaGlpaWdnZWZlZmdnZWNgX2BjZGVnZqJee317eXR0eXl2d3l6eHp4eXd5end2cnR1gHRycnN2dnt8dXl3eHZ1cW9vcXFyd3d0cHFxc3JvcHFvbG1ta2dpampsb29vbW5pampoppBoaWdmZWRmZGFiYmFgYGFgYV9eXl9iYmBjZWFgYWJfW6aVj4eOoJuKbVxnZ2Zoa2tmaWtmZmFjZWFlZmlsaGxnZ2psa2plZWZpbmhegGJgYGJlZGJnZGZoZmVmZGerkadmZ2hmZmBiY2piamlraW5qaXFtb3Fta25yaW9wb2xubXFzcnJ2dm9uanGap3RucHFtdW9wb3BwcXFtb21tcXBxcGxsam9vY2Vaq6uhoZuYkY6CdmdtY2NjaGlnaGlraWloaWxoaWtnZmlkZ2NlgGxnZWpiYWdxbm2FmnBuamhpnnRfXKterWFmXF5dW6qXopiZoJmbnKObprSzWmBfbLqEtF5kXGOxtra2YmdkYV9eYVxfZGloZGdmanJuZWZhYm1oZl5eXK62qGVudHRvcHh2c3Bvb2xscGpvcXhycGtqZ2hjX1teZGJlbX6AcGxogGtydnZ4eHd6f392iK1ubm9jZGdna2tqbH6Nj6Gjn5uTlY6Pj5OIhIiDim5hrJ6NhX9dgINkb294goiKi4yPlJmkjXm0z7d9i7CZldWCnaR01YuIyX+GfXNvcG5wb29ubW1tcHN1eXp4d2Zzc3d5enx9fXt8enJidJSLjY1YeX+agIZ+hYWTWGVkXVWkUpJ8dJaamHZtb3l/j56ad+B+UIhejnpIVWxPS0t9REZESWt+eUlES0hHW0xWUUJdWFBFekRDS05MVEdHRnxISUNFTE1/S0pVV09QS0ZMS05OZ1xpWllsb4eGU1xPnGaycZCtoH6kkMR2s3vZnLWjebSodHvQgJfcurm5urm5uLSwpozXvrZpZmtreHNld3t5f4SCikZDjIWDdHZ1dXd4c2GNYnNscmxrb25ybWplc3ZyY2d6bnl7eYFteXltcmtta2hlZGBfZGhuYktQZmpqaGpucnBub3JzdXZ2dXJwbWhnbnFycnFwbnFvaG51eHt3XZNoZW1wgHVzZ2tvdHl5dnR8dHN5cXZ8hYmTlpWTjpOVjpWVjKGum316d3p8eWt2e3Zyd3h5c317gn6Cgn6Ah4qJj4+MjpiKlJCSkI6Kkn6PcHV7dnx7d3Nyb2dbX1tleXN4e3NkSGRscm9tc3RtamlpZGBkam1sc29vbVNPZW1scHFwdW9ogHFxb2prb21paW9sa2xramttbWtucXNzbW9samhobW90cnFwcnV2d3JzcGptamxiVlNNTVRYV1ZaWVlUT01PT1JSU1JOUU5PU1FQT1BQUlNSVFNTU1FSUVJRUlFSUVBMS0xPUVJVV4dRcnRyb2lmbm9ubW9ybm9rbWxvcm5sZ2lpgGhnaWlsbHR2bW9tbm5rZWRjZmZobm1pZGdlZ2dmZmhnY2NiX1teX2FiZWZnZmRdYWJfloBeXl1ZWFhdWVVVVFVVVFRSVVRTU1RYWFZWV1NUVVVSUIx8dWpve3dnWFBbXFxdXl9bX2FaWFJUVU9XWFtdWVtWVl1eXFxWVldcYltPgFJPT1JWVVJZV1lbWFZYVVmReolTVFVSUk1PUFdPV1ZXVVlWVV1YWlxXVFhcVFlZV1RXVlpcWlpeXVZUUVh2gltUVlZRWVRUU1RTVVRRUk9QVFRVU09PT1NRSEtCfX90c29rZGJbV0pQR0VFS0xLS0tMSklJS05LTE1IRkpGSERHgE1IRUlDQkdTUE5fak1LSEVGbVZNSoVKgkpQSEtIRYFzfHN0d3FzeYB3go+JRElJVZJni0hOR02HioqKTFBOTUpJS0dJTFBQS05OUVlVTU1JSlNOTUVFRYKJfkhKT09KS09OS0pJSUZHSkdJSk9LSklKSElFQT9BRkVGSFBRSEZEZUZKTExOUFFTWVhTYHtPT1BGR0pLTUxJSVJaWmVjX1tYX1pbXF5WUlZRWUhDfHhuaWhXlquEko+SlpqdnJydoaWuhWubsZtsfaB/eKhlgI5muXZ8t3N8bltLPjY2NDQzMS8wMjMzhDViLDIzNTY2ODg3Nzg2MzA5QD09Pik9UXJYSEpAQSQqKykmSSI8NTpSXHU7Ly0xNj1BPzyJMhwyJ0U+ISQ5ISAhPCEiHR05Pz0iIiMiIiYjJiQgJygmI0MjJCUnJCYjIyNEJSaEJDE5IyQoKCYnJSImJicmLisxLS00OENMLzMyY0eDX36cknOXhbVommGoe49/ZpmRZWzBAYCMf4N+jn+CgIt/AX7PfwF+pX8Bfqd/AX6Vf/+AiIABf8uAgn+ggIl/s4CDf6qAgn+dgIp/pICCf4WAB39+f39+f36Gf45+hH+DfoR/hH6cf7Z+n3+GfoJ9jX4Bf4WADX99fX18fHt7fHx+f3+igAN+fH+EgAJ/foR9An5/hYAIf4B/f35+gH6Ifw9+fH5/fn59fX5+fX5+fn2EfoN9jn4BfYl+AX2GfgF9lX4Gf39/fn9/hYAQf358fHt8fH19fX5/gIGBgAICBACAk5GRkZKQjoh+d2y/zYCep6CSkZqepLXc2e39+PeA8uHRwriuwMbIyraCjLHBw8bAvsDBubiup6aprbetucXDzszIy8LBwrWzqqagn6OmoqKhqKaIh6Wssbe3tLK5vMPEw8bHyMK/vLi5tra5vsC4tLe3s7e9xcS/wb6Q/KulpK6AsbWwqaiusrW7vLfAsau0rsPOzMXIzc3S09vc3OTp+PrzrZPP0NDMzsDA1cfCurbFxcPMws3a4OHX1ODl4+Tl6dzZ5unq5+Lc49qFwNbJyrmgjZaTo7W6u7OxtJnRpdzg4ePg49/e3d3a2djV0NXR0dfU1NjU0tCol7PT0tDMz8yA0NPJw8HDwcjGxMTBx8zIxs7MycjJysfKysnIxcPDx8fExMbGx8XGxMfGw8XFycvJx8q6ppqNi5imqaSjpqihpKWnpqSjoqKlpqWgnJ+dn6GhoKKhnpycnZKcmJeYl5eTk5SXlpaSxKmpqquvrKiqqKiqq6eipaCio6akoqWnp6WApqajpKqnp6SjnqGkoKOkoJ6iop+foKKgoqKboZ+bnJ2XmpiWl5aWnJybmZeYm5aXlpSVvu+Vk5CQkJOSj46Li4yPjYyNio6Ljo+OjI2LgurZ2dLW7/bz8fPx8fX1scOUl5eTlpialY6Hi5KSkJWPlZiXk5SYmJeWl5KUlpSWlZOAlI2NjpCLjJGVkYuQjI3lqMiSmJmYlZOTjpWXnJibop+amqCho56ipKaioKGioqKjo6CfoqSnpqOjpJ+eyISjpqCdpJ2ho6Gmp5+foaKfqKelp6SgoqaopKijpqOioaKlo6anoa2LnpyVmJWbn6KlpqainJ2dnqKhnZeWmJSXmpmAnpqam52flZufo+CWgoGC+82ZyNnh2OHj2trr7eT+kZeQh4WLk5SWjISNjYuGg4yHjMXlkoaLiIeOh4SEg4eFhYSBgoiIl5GTiIWOlJGPi4uHh4uAk42OkI+IhbOFr661uby+ysTBwregpaSnr7Oor6WwpaSkpqinoaOepqqrqbCAqaikpa63tbm4usjHxL7d+pWbnZ2lpZukorO+xc3h6+Pf18TIvbzCwLu+urauloHcvcS9mNaesd2BjJqlrbCyt7u/ws+tkNCA9rzirqaikNiEsMeIiKeh8pmqpJ+anKGmqqmnp6eoqaujm6ytrrKytLS3t7a0p4ag5NXX1YKAiOGA4d/ume+Nm52Qhv36+82h+/aQmKqgwsDBztbbxZOb9JWR6Z/p+r3EvPWgjISEl4iJnYuck5WQ/v6DsriLkp6hpYaBioS5gYn5mYaJlICpldL1l9mqvKOsvqWtpa/n0b7gjYmPmpbD9pzA2ced1caKjs/Lx5i/xsDGiOidnf6v3ZkBhISBgIB+enNqW5mfYnl+eW9vd3p/jaqmtsO/v2K6raKVjoSRlpeaiF9nhZKTl5CPj4+KiYB7en2Ci4ONlpWbmZiYj4+NhYWAe3V2eXt4eXd9fGNfdn6BhYaCgoiLkZKRlJSWkI+OiYmGiYyQkYmFiIiGiY+Uko+TkGm6fnl4gYKFg3t7gICEh4qLh4+EgIWAk5yclZmcm5+gpqmpsbTCw7+IaJeXl5OYi4ubj4yHg46PjJSMlZ2kpp6apamop6mtoqCsr7CtqaOppF6ImI+ShnJkbGZxfoSGgIKFbpJwlZeXlpSWlJaSkpGQkIuHjYmKkI6NkI6OjnNneZGPj4mNiY+Rh4KAgIOCiIeGhYGHi4WEjYyJh4eIiIuMiYiGhYaLi4eFiIeIhoeFhoaEhoaMj4+NkoZ4a2JeZm9wbGpsbGVqbG1sbGpqamtramlmaWdnaWpoaGdlZGVnXmVjZGVjY19gX2FgYWKFe3p7fIB+d3l2dnl6eHR3cnNzdnVzdXd5dnZ2dHV6Mnh4dHRxc3Zxc3RwbnFycXFydHJzcm1ycG5vb2tubGtraWtubm1raWtuamxraGmEo2hmhGSAYV5fXl9fYWBfYF5hXmFjYmFiYFuilZWNjZyhnJmamJean3KCZ2tsaWlsbmZhW19nZWVpYmhsamdobGtqaWplZWlnaWlmZmBgZGZiY2dsaGJnZGSgc4xlaGlpZ2VlYWVnbWhrc29paG1vc21wcnNwb29xcHBxcm9ucHJ2dXJzdG+Ab4tcc3VwbHNrb29scnNsbHBwbnR0cnRxbW5xcW1wbXJxcG5ucnFzcm11YGxqYmVjaGttcHFxbWlqa2tvbWhhYGRgYmZmbGloaWttYmZsb5llWVZXqIlljZedmJ+gmZqpqaK0aW1mXlxhaGlrYlxiY2ReXWNhZ42kal9kYV9kX16AXl5hX19eXF1hYm1pamJgZ2xraGRkYmFiWmhlZ2dlX159VW9udHd4d397eXp0Y2dlaG1wa3Fqc2tsamxubWloZ2tubm5zb25qbHJ5eHt7foiIhoKVrmpvcnB2d212coCIjZGhp6GfmouQhoSMioWHhYF9a12egomEbqR9i7FpcHmAgYiLjpKVmJujiXSpaMqavZWOiXi5cqK4d3GQhsV9ioR6cW1ucnRzcXFxcnJzbWh1dXd6ent8fn9+fXVfc5mOj45aX12XiICIX5RZZGheWKOfn4Fop7NxbXdmenp8h4uWoYJkkVZUiWCHhmJkWXtSRkJBTkhGUEZRS0xJfHo/XWNFRkpTVVhGQEVDZkBGf1BER05DXVFrdkt3WmRTW2VYWVVdg3FlfFFQV2Bgh7l6nLKkgKyjdoLBvriHpKWepG6+gH/PkbyIAri2hLeAtbOqnoHCtV9wdG1kZnF1dnuMgoeNiodEgX+Bf312fX17fnFNUmpzdHlxcXNzcXRrZ2drbnhvdnx4fHp3d3BxcG5wbGhkZWhraGhla2xRSVlgY2hoZWNsb3R1c3R2e3Z2dHFxbW5yd3Zva25vbnJ4f396fXpVmWhjY2ptcG5nZ2yAcXN2dnF6b2twa3+Jh3+ChomPjJCRkZecpqajdVJ9fX57f3FwgHh3cGx4eHR8c3uFio2HhIuPj4+TmIyJlpibmJOOko5Qc4B3eW1gVVxVXGhvcmpuc19xU25wb2tqbGprampqaGpkYWdjZGtpaWpoaWlXTltvbm9obWpvcmhjYGQTY2pqaGdjaG5pZ3Fva2prbW1vb4RsgG1zdG9sb25vbW5rbW9sb293e3p4gHdnWk5HUFRWU1FTVE1RVFZUU1FQUVNTU1JPUlBQUVJQUVNSUFFUS1JPTlBQUUxMS05OTk9qbm5vcHVxamxoam9ybWtpZGdna2toaWpsaWpraWlvbG1pamZoa2lpaWVjaGlqZ2hrZmZmYmlogGRkY19iYGFjY2NkZGRhYGNnYWJkYF92kVxZWFdXWVZSU1NTVFdVUlNUWFVYWlhUVFNPjYF7b2l5fnt5e3d0dnlYbFxeX1xeYWBWUUtRWVRVW1NaXVpXV1xcWVdaVldbWlxcV1VOT1VYUlNaYFtTWVVViWJ1U1VUVVJRUUxSU1pVgFhhXlZUWFteWFtdXVpYWFpZWFlbV1dYW15cWllaVVVsSFlbVVFXTlNTUFZWT09TVFJWVlNWU1BRVFVPUk9TUlJRUFRSVFNPWEVMS0VGREpNUFJSUU5LTE1OUE9JQkBFQUJGR01JSkpMTkNHTE5tRj48PHNhSXF3fXZ7fHV5h4Z+gI1TVk9IR0lRU1NLRktNTUlITUtRa35TSk1KSExJR0hJS0lISEZHS0xWUlNLR01SUU5LS0lJS0JOS01NTEdHXjtLS05QUVBWVFJUUEJFQ0NGSURJR1BMS0lMTExJSEVGRkZITEpJRkZKUFBTVFZeXVlXaH5OUlRRV1dOU05XWVtaR2NlYmJgV1tVU1hXVFRRUVNJQXVjbGphr6LB8Y2Rk5aYmp2ipaapr4NqlVqvhaWDdnBkklqHoGllfHawcX9zYlBCODc4NzQzhDIFMC4zNDSENkE4ODc3NC88Qjw9PSkxQXBUSEsrQCQpKyknR0FANy5aYU5HOiszNDY7PEphTykxGxwzKUJAJSU2OyIgHBsiICImIYQjSkNCICgoIyQoJyckJCQhLCQkRSYjIyglKiU4PSUuKC0pLC4rLSwtNTU1RC0wNDc7YpVrjaCTc52YbHiqno5ke4F6iVycaW2/leO7i3+CfpB/AYDdfwF+4X//gIqAAX/MgIJ/moCQf6+Ag3+rgAF/p4ABf6WAB39/gICAf3+NfpN/gn6of7V+n3+FfoR9jH4Uf4CAgYCAf359fX18fHt7fH1+f3+egAN+fH+EgAJ/foR9gn+FgIV/BH1/gH6Hfw9+fX1+fn9/fn59fX5+fX2NfoJ9j34BfYd+gn2Pfod/hYAHf359fHt7fIR9CX6AgIGBgIB/fwICBACAjo2OjYmFfXDJzPCMnZ2Yk5CYk5+21OyA7uuEhPzmy8TBt77H0Mq+ovqdtsG+x8rDyca2rbKpsq+vs76/xLnSzsrFxdC/vrSsrqmonpeboqSkoIeKr7/Avbm7urzBwr6+wbu7v7y6v8HFxLq5trO1vMjJxL27xszJw66CjbKtpqFdpLW4sKuwtK2ttLexrqKop6i0ysrLx7/E0tjV4ebn7/D44tDM0cfAzsXRz8vNyMPIv8fQydnJ0dbW4dHR2NfY1+nn3tbV7eri5tGuwIeflJu1u8CrrK+5tKCxjszdhN8K4uHd3t3Y19ja3oTZP9va1dPS1NXP1c2zl6jK0M3HyMvIzcrHx8jDwsbMycfJzMzKz8zDx8TDxcHBw8bFycrAwMTDxMXFxMjKxcfHzYTKgMTHxr+6q5qPioeMnKimpKeqp6KinqSnp6WjoqqjoKSkoqKgoZucmZKTm5ucnJmXm5iYlf7+sKyrqKusqqyopKWppqOkoKOjpKSjpKOkoqKkpqSjn56koaGhpKGjpKGgnpuhn56hnZablpiWmJabmpWXmJmYmpeXlZGXl5iemZaUgJCRqYmUjI6OkJCOjpCPj4+Mi4qLjYiJ+uPe2s/V8Pfy8/Dv8u/19ff39v744qTlkJGUkY2SlYuRlZORkI+TmJSTlZOVm5iWk5GUlJWVkY6PjZCQkpeVkpGRhtLaiZKC2tfzkpWbnpyVlpSWm5ybo6CgnaWenaSfoqKhoKOfoqSlgKShpaaopKOkjrySqKSloKKjoqGko6SkoaWopKain6GmoKSlq6elpKCmpaGlnqWhpqmJsZugnpqWm6GfnaGlqKSdm5qeoaSioqCdnpqYn5+kmJufoZucm5uApKGpp5rX9I+Lg5Wbmo+SjZKRjpaUno+Tk5ibkJOJ/pSIiYqEhIb6gKn5ioWDjpOD/pCKivaGipGLjImMiIyNj5SPg/uEhpWgk4yQl46KiY6SjO+Mmay0tay8u7iupaOloq2utcfKvbitqKqtt7q7xL+6uLi9vKykqaq1srewr623v8LL19rQ3PSInKGZkZGatsDDzend4d7d2MvRsMC9utHGx7ajj4begNDKv6v8sIejy36NnqetsrW0ub7CyZX+tu7925yd8ayxmNOLs8+C+4yT4JWtsqynnp2an6Sqq5SqrbKytrW4uLW2urmohaPp3drUg6736+Dh/MDriZSXkYb5+4Pexdiey6G2o7DKwsbWjarEj/aA05SLx+2Ojo70oZjt3d//jo+IToGEppaBlIyXqLCMs7Dg3J+KpaKXrpmfnrSvqKmlgpiltLu0tMzWl62B5pGhlqi88ZS009e6jbio+ovLw6eIxOTW9aavg6Kc+qrUl5GPj4B+f39+e3dxYqmjvm56d3JwbXNtdImjt2O0r2Rkv62YlZSMkZifmY54tnWHkI2VmJOZloeAhX2Eg4OFjpCViJ6bmZWTno+Ph4GDfn11b3F5e3p3YmN/jI6MiYeFiY6PjYyOiYmMiomQkJOUjIqIh4aMlZaTjo2VnJiTg19ohIF6dYB5homEf4OFgYGHiIWCen19fYebnp2YkJOgpKGrr7G6u8KylJSZjoeSjpiUlJORjZGKj5eQnpKanZunmJeenJ2aqaujm5yysKqsmoCHYHNvdIeIjXt5e4SCcX5hipOUlJKTl5eTlZaSkpSVm5WVk5KVlY+OjZCRjpWNe2hzio6MhoCGiYeMioaGhoGAhY2Ih4mLjIuQjIKHg4OGgoKEiIiOj4OAhYODhoWDiIuFiImPjpCRkYiLjIWDemxhW1lcaHBta21vbGhqZmlvbW1sbG9qZ2trampnZmNkYmBgZmZnZmRjZ2RjYay1f318d3x+eXt3dHZ5eHN1cXV0c3Z0dXNzc4B0dXZ0c3BxdnJycXRzdHVzcXFvc3BwcnBqbmlsamtpbGtpa2ttam1ra2tqbWxscWxqaWVndWBnYWBgYmJgYGNjZGRgYWBgYlterJqWj4eLm6CdnZqanJqdnp2fnqiil3CgZmdqZmJmaGBlZ2dmZWRma2hmaWhobWloZmVoZ2lpZYBiY2NoZ2hsa2loaF+RmGBlWpaXqGdnbG9tZmdmZ2xtbHNvbWxzbm1yb3Fxb29ybW9xc3Jwc3N2cnJyYoFmdHFybm9vb25xb29xbnF0cHNwbG5zbnFydnNwbmxycW1ya3BtcnRdc2Vsa2hkaW5sam1wcW5paGhrbm9ramhlZWJja4BrcGZoa25oaGZnV2tmcG5nka1nZFtqb25namRoaGZraXNlZ2VscGZpX61qX2BhXVxesnmxYV1dY2dds2ZhYq5fYmhkZWNkYGZlZmpmXLBeYGpyaWNlbGRhYGdqZqpeY29zc2x3d3VuZ2ZnZWtscXx/d3Rsam5weHp6gX54eXl8fYBya21teXd7dnZ1fICCiZGTi5WqYXBzbmhnboSLjJGonaGdnpuQlHuHh4OWjIuAdWZgnZWRin/Ajm+JqGZwfYWLjpCQlJeboHTJlMLRtIGDxIyUe7d4psRwznB2t3qNkIiAdW9qbHB0dGN0dnp6fn1/f319gIB0XXOYkY+MWoSsn4CDgpJ4lFVeYV5Xn6BUjX+QaJt9fGdvgXyBjmB4pIGlTHtWUHiLS0hId1NOd21wf0lIQ0JCV0w/S0ZLV11IY2Jvb1ZIXFhNX1NYUmBcV1pYQk1WXmRjZHF3UV5LhFdgWGmAr3KQq7CXc5SK2X/CuJh5p7+zxYuQbIR9youyhoOAfoC1tLSzsa6njeHEzG1wamhpZ2xkZ3iGjUmCeERDhH94e311eoCEfXJej1prcW50d3N7e3Bqb2hub3Byenl8b4B+e3Z0fnN1cW1wbGphXWBmaGdkUE9ibG5ua2tpbG5xb29ybm5xbm92eHd1bnBua21zenx6dHN9hIF7bEtWb2xjXoBib3Nwam5ybGxycm5sYmZoaHKFhoaAen6OkYqUmJien6WXfHmAeG95c315eXh2dXlzdn56hXmBhIOPgH+FhYaEk5WOiIicmpWYh3BzT2FfYXN1emhkZW5rXGNIZGpqaWdqbm1pa2xpam1udXBvbWtxcGppaG1tanFrXU5Ya25sZoBna2lubGhmZ2NiZ3FraWtucHB2cGVpZ2hpZWVpbm13eWpla2lpbGxpcHNpbnB4d3l9fXR2dnBvZlpQSkdFTlZUU1VWU09RTVFWV1ZUU1hQTVNTUlNRUU1RUE1MUlJSU1BPUU5OTYqbcW9uanB0cG9sZ2lwbWtqZGhnaWppa2doZoBnam1qaWVlbWhpaGxrbG1saGllbGhnamRdY15hX2FeYmFcYGFjYWJiY2RiZWJjaGFgYFpcZVVaVFRVVldWVVdZV1ZVVVRXWlJSlYWAeGxsen14e3t8fHl8fXp5eoF6dVaDW1laWVZZWU9VWVpZWFdbX1dVWVdYXlpaWFpdWV1bVYBSU1RaV1heXl1aWlF4fk9USnt7iVRTWFtaUVJRU1hZV2BaV1ZfWVddWVtaWFZaU1ZYWlpXW1xfW1laSWVOWlZYVFVTUlJUU1JUUVNVU1ZST1FXUVVVWVVSUE5UVFBUTlJQVVdEUEZMTEpHS1BOTE9QUk5KSUlNT1BLSUZFRUBDS4BLTkZIS05IR0VGPUlFTEtJa4lSUEdTWVhQVE9TUlBVU1pNUE9TV1BSSIJTSUhJR0VGglyISkZGS05Fh09KSoBHS1FNTkxNSk5NTlJNRIBFR1FZUUtNUktJR01QTYBEREtPTkhSUlBKRkZFQ0dGR09RTUtKSUtPVFRTWFVTUU9QUIBKSEtLUk9TUFBPVlpZXGNlX2Z3RVJVT0pITFlcWVtoXWBeYGBXW01WVFFeVVRPSkNBcnFvbGm4rp/H74qQlpqfoaKipqmrrHW9hqu2nG90r3J1ZJFfgp9uwGFqoWp9gHFhT0I4NTU2Ni00NDY2Nzc4NzY2ODgzMT1APjw7KEp/dIBMSE01QSMmJygmRUMhOjREOFdhRS0tNDY5QC48Z1FZIiodHTRIISAiPSMiNzc+QCIjIiMiJyQhJCMjJigiKSdARCklKSglKygqKS0sLSsqIyIpLi4uLzI3LzUnUDI2NDxYiWB/m56HaIiAxXKhl3dafZGDlnJ2WG1qtIzWt7a1tYh/g36MfwWAf3+AgIx/AX75fwF+qH8Bfo5//4CMgIJ/zYABf5SAmH+rgAh/f4CAgH9/f6eAAX+ogAF/poABf4SAgn6XfwF+h3+DfoZ/BX5/f39+jn8Bfo5/t36ef4V+hX2MfoJ/hYABf4R9CXx8e3t8fH5/f5qAA358f4SAAX6FfYJ/hYAKf3+Af39+foB/foh/D319fX5+f39+fX5+fn1+foR9kH6CfZl+An9+hn+GgAd/fnx8e3t8hX0Hf4CBgYGAgIV/AgIEAICKh4J+enHO5YaWlZKVk46Tmq681eP+gPqCg4H228W/vr69ycvSuoWTr7m+xr3GxsLBw7KhqKeosLi4u8DGxb7NxMHEwra0r6WnqqWkpaSipaWJgpyrs7SxrrfCwL69wcXBwr7AvsDGxcK+u729t8TEysrHwsDEvbGsmoCYrrKxrYCtp6y2s7azsKqqsbO4uLK0rrC1wcjQ0szTzM3c1+Xl3+7xronPz8i+xNDHwLvAyNDDx8jLz9bK19nW0drU0tLc5d7b1L6fnpiVmrjOleadv62vtsC0mayEvd3b3+Lh3t3e397d39/c2NjZ2drX1NjW0NPQ09bT0tXQz8e+naW8y4DK08rIx8XIyMnKyMrNy8vJxMjJysjEx8bFxcXDwcHIw8LHyMTByM3IycrKxL2/xsPFyMTGx8C9vr+/vfqLnpWQkImPnKOppaimpaekpKWjoJ+hoZ2dn52cnpyRkJWWlZOVkpWZksalr6qpramoo6uppqOpqKSdoqenpKSnpqSlpIClo6Who6SfoKOin6Ojo6Skn6Oko56dl52dnZuYmpianZubm5eampualpKRlpSUlpWUmJeTi6KOj4+OkZGOj5CMioiE9eDY1NLU6fv69PXz9fby8/n5gP6B/f76/v359fPApYSUlI+TkY6NlJKNk5OWlZWTlZiVlpiRkpCQlZOTjoCLjZGUlJSQj5Hx0OyUmZSXmpOSiNzX442XmZiaoKSdmJqdm52moJ+gop2iqaehpKClo6ShqKWmpaGGxqClo6WooKKeoaOkqKWqqqOeo5+opZ+goKGkraSnqKGrsqanpqKjoqOliZuenZiXmJybnaGfo6akop+ao6Olp6eknpuenoCamZydn6Chn6OqkpWno56L2oaKmZSOiZOXj46kl4yWkY2QkZiPlp+ampSZkIiEiIyKgIOljJSRk4yEh4WGiIaMiImLi/2BhYmHh4uSkY2HlpOVlZGO+IOalo+F+vmCyYOdoZykpra4vrq1tba2u76/t6umrKu2vrq2t8PCwcnS24Dd087Cvbe2tLe7ubu7x8vR1tnWxsfe+paVi5Oeq7vCzNHg6d/h4dPKyb/CyL66vLeqj4Ha0r+2r6Dxpoaiz36Qnqetr7O3ubzAxerElsz48MSMj/+7n/vPn8XPiMS0wqrrkqiuqqSjo6aqqqqvra2ura6wrK+fhsPayMbC6b/16oDj6aDe7oSSmZaK/PuB7c62h6XLr/jO94Dx4ZXapamxk4WBlfmnvfWCjJCTkJX7ivr7kZmImo+8qp+dk76EoMyRgrSKnJGBjJyiqKmnrr2yo7a3ztvu5fqghfWZwPyWttbbwZrb8tPA+cPZvJTV7dLK8I3snKaS2JG3lZGRkI6Mi4B6eXVwbF+ptmp1cm9wbmtudYaOoKjAYr1jY2O6ppWQj5CQm5mfil5thIuOlI2TlJGTlIV2fXt7g4qIi5GWlo6ak5CSk4iHg3p8f3t6enl4enpiXG17g4aBfoSPjYyLj5KQk46Rjo+UkpCPjYuKiZORl5iWko+SjoN+cV1ugISEgYCBe4CIh4mHhH9/hIaMi4aIgoOHk5qio52lnpynoK2wqrm7hmGXl5CJi5OMioWIkZeMkJGTl5yRnZ+bmKCbmZigqaOgnIp0dXJqcYmcb6VvjHx/ho2Aa3pahJmWmZqZmJeYl5eYm5mWk5OVlZeVkpWSjY+MkJSRjpGPjYaBa3GBjICMlYuKiIWHhoiIhYmMi42KgoaKjIiDhoWEhIaFg4SKg4SKioaDi5GKi46Qh4GCioiLjYqLjYaEhISHhrBjbmdjYFldZmlvbG9tbG9tbGxqZ2hqamhmaWZlZ2ZfX2JiYmBhYGJjYYN2f3p6fHp4cnt5d3V5eXZvdHl3c3R4dnV1dIB3dXZxdHRwcnVzcHR0dHd2cHN1dW9waXBwcm5rbmxvb21tbWpubm5sa2ZmamlpamlnbGtnY3BjYWBgZGZiY2RhYF9cppeRiomKlaOinJ6doKGenaCfUqNUpaekp6mmoZ9/b11qaWVoZWNjaGhiaWhqaWlmZ2xqaWpkZWZkaWdlYYBfZGhqaGlmZ2iskaBnamdpbGZmX5iWn2Nqa2trcHNtaWtua2x2b29vcWxwdnRucW5zcXJvd3Jyc3FeinJyb3J0bm1qbnBwdHF2dm9pbmx2cm1tbm1weG5yc212fHFxcW1ubnBwWmRqaWVkZ2tqamxsbnBtbGtncG9wcXBsaGZqaoBoZ2pqa21ubXF2ZGFsZmZcl19jb2pmYWptZ2R3bGRsaWVmZ21ia3BrbGhqZF9cYWJiW191ZWpoaWRdX11dYF9kYGBjZLNcYWNgX2NpaWVgbWpqa2dkr1xua2dfsbFdj1hkZGBmZ3N0eXhzc3NxdHd6dGtobW50eXd2eYJ/f4eMlICZjouCf3t5eXp9e35+hYiNkpOShoibsGtsYmdvd4KJkJOgqKGkpJWNkIiKj4aFhYB4ZVuWkoiBfXStfHOMsGhzfYWKjo+QkpSYnLaeeabKyaN2etOWhNavhLG3caeTo4u+doaKhn53dXV3d3Z4d3Z3dnd5dnhuYImMg4J/oJisnICHimCTnldgZWJZo6BTmoZ3WXGggpR0i0mNjGWXdpeWYVFLVpJka4BDRUhJR0yARoGCTFBIU0llWFJSTWhDVXBJQGBLVEtCSFNYWVhWWmZcWmRkdH2Mi5djUZdmiLt1kauxnH+2ya6p67rHooO6x6ymyXS/foZ2r3echIGAf358e4CurauonYPT0W5vaGdpZWNma3Z3fHqMRoVEQ0OGfXd2dnZ0fnyDc0tUZ2xtcWtxcnF2em9hZmVlb3Jydnd6eXB8dnRzdXBwbmlqbWpoaGZlZ2ZQSlVfZmdlYWRxcG1ucHZ0d3V0cXR4eHZzcnNzbXl2e3x8eHV5dWxmW0pcbXBwbIBsZWt0c3Vyb2lqbnB1dnFybm5xfYOKjIeOiIePiJGUj5qedFB+fXZxc3t0cWtudXt1eXt7f4R6hIeDgIeDgYCJk46KiHlkZmNVYnmJYYlceWptc3hsWmFEZHNucXFvbWxubnBydHNvbW1vb3Jwb3JtamtoanBsam1samRgUVZkbYBtdm1tamhoaGpqZmpvbnBsZWlsbWtnamppaGlpZ2lxaWpvb2tocHlxdHd2bmhpc25zeHN0dW9sbW5xeJ1SW1NNSkVES01XU1ZVVFZUU1RST09SVFFQUVFRU1JKSEtMS0tLSkxQUGtoc25tcW1rZW5saWhtbGliZmtrZ2lta2psa4Bta2tmaWlmZ2loaGlpa25sZWhra2RlX2RlZmNgY2JkZWNiYl5lZGNhX1tdYV1dXl1cYmBaV19YVldYWlxXV1dUVFJPkoJ3cGxqdH9+eHp5foGAf317QIBCf398gYWCfn1jWVJdXVhcVlJTW1lUWlpcV1lVWFxaWlxVV1dXXFpWUIBOVVpcVllZWlyVeYFTV1VYWlRUT319g1FWVlZYXGFZVFZYVFZiWlhZXFZaYF1VV1VaWFlYYVtbW1hKaldXU1daU1JOUVNTVlRZWFJNUU9ZVVBQUE9SW09TVVBZXlRVU1BSUVNSQEVLS0dIS05NTE9OUFFNS0tJUE9QUFBMSEdKS4BJSUtKS01NTE9USEZKRUdEdktOWVVRTlVXUlBeVE9XVE9QUFRLU1ZUVFBSTEhFSk1LRElWTVFPUExGSEdGR0dLSUhKTIhGSUtJSEtPTkpIU1JRUk5NgkVUUUtFg4RHbT5CQkFDRU1OUVFNTkxISUxLSURGSUlOUk9MTlZVU1hcY4BmYWBaVlJRUVJWVVhWWVpeYmNhWV1qeUxMREdMTlVXWlliZ2JkZV1YW1RVWVFQUU9MQj9rbWhlZWi0pqfP/4yRmJ2fo6Wmpqeoq8CXcJayr49la6h0ZKKOaIOOYIh5h3qpa3p7cWNVSD86OTg4NjU1NDQ1MzQwM085NjY0R2N7Z4BKSy8/SCcoKSgmR0UiPjYzLzplZ0UxOR9AQjJHPWBcMSIaHjUtPEIhISEgHCJFI0RFJCUjJyMrKScmJi0kJyskIiwlKSglJioqLC0tLjAuMC0sOUJKUVg1L2FDZ5lkf5idjHCjuqCRyJ2hfGSIlYF/pmCbZm1jon7DtbOxsK+uroZ/gn6OfwWAf4CAgP9/s38Bfol/5IABf6mAAX/PgAF/jYCSfwOAf4CKf6eAg3+IgIN/o4ABf6iAAX+ngAZ/gICAf36ifwF+kH8BfpB/AX6FfwN+fn+5fpx/hn6FfY1+AX+GgA9/fX19fHx8e3t8fH1+f3+VgAd+fH+AgIB/hX0Dfn9/hYAOf3+Af39/fn+Afn5/f4CFfwp9fX5+f39+fn19hn4EfX59faZ+Bn9/fn9/f4aACH9+fXx7e3t8hX0IfoCAgYGBgICIfwICBACAfHt33NfpjZWNkZKQjZmarsnV+PH8/oOAgfrkybfAwtHR1cnFlYWktb/CwMXAw8O+s7awpKGtpaWutL24ycrCzMrJvbeusKWhnKGhn5+hpamQiaCvsre3sbe3uLi9vr3Avr29v7q7vb+/vr7GwMPCvLOuurq8vru0rZOAm6yztLGAqaWmq7W4ury+vr68uLa3t7e7v7/BwcnV2d3V2dba497e7vfizdXXz8nIzsnHwMHFxMfWyMvLx8fM0tXZzLynpZyUi4uow8PFy8jDxLyfy5SmlpCr/7fY3Nzd4Nne4ePb2NjX1tXX2Njb19nV09TV1M/R0NDSz9HQ0tDPy82/pJ6AtMrNy9DOzs3Ny8nMzczNysnJx8nJyMjJxcXGycbEv8LBxMTGwsPJx8jJy8rEwcbDxcbCxL+8vLm6sfGowby7vry8rpyRiomHho2WnJqZnZybnaGel5idmZmXmJWVl5iUl5qamv/1qKutraqkqKSnqKKkpqGkpKeopKSkpqKln6KApKKlpKajop2fpaalpqKjoqGiop2fnZyamZicnJ6bm5mZl5eXlJOVmZeWk5KQlJSSkJSXmJSErY6TkYyJ+uPg3NTP1ez8/vv59/Xy+Pr69vX09/32/IOB+/n6/f/++vP38+mruIuOlpCPmJONlJOSkpOblo+WlZSSlpaVkZGSjIOAi5KTj4+PiN/dg5OZmJWYmpual5mWmZDt39r9naGfm5+kop+bnZ+inpSjoKGenaCio6aipqWZqaLf3qaoqaOoqKeqp6elqaejpaiipqero6OfoqGppp+kn6Onq6WnpqerpqmEtp2cnpqXlJWanpqcnJqfoJ2cnKCiop6hnZygnKGAnKGdn52hpaSlpJ+Fn6Oq0omQjYWNk5CRipWZhYWFkpSNkY2WmZmZnJmem5ebnpeP//SJhLSLjouKjIaSg4WIjoWKjYeIkomHjYiDlZSbn5SUlY+RkpOQlImFhIWJhYDxroSTnKCkqa2xt8HT3ODk5N/c0bqzrre5vby2vsbJ0dWA4t7W09fT2+Ha19HLx8nFxsvKy9PX2tLP1euJop+UscHI2+7o5O/62NbSycPMzMvS1LiyoJSI3dXTxLuf5oyGrWuBjZahqa+ytbm8v8XNh+Gg1f/+1Ji/oay6sZfV+KW99bufq6Pzm7G+urKtq6uwtre1rp21oeGgh9PX0PvAjI6AhoSVlY6Rpa2wpIiQkpqH+auIk56hg5+6u7/wjOncuqqWg46Es8SMpZSQ9paF+5yYm6qfhc+Jm6GXlKWR/MGknae2rM3KvZ+pobPK6O3j95CH6O7rp8D5mLLN29Gzk9L+6NKEyreWg7Pc6uHuhI7lna+f9qzbmZCOjY2NjIqIhICAcG1ntqy2cHZtcHBva3Z1hJykvLC+wWRhYr6umomSlKCeopeTal95iJCSj5OQkpGNhIiEeXeBeniAho+KmZqSmZqaj4mChXp4dHh4dHV3e35pYnF+f4eFgIaFiYuPjoyMi4mLj4qLi4yOj42VkJKUjYWCi4uLjIiCfGpcb32Hh4OAfXp8foiLjI6QkJCOi4mJiIqMkZGTkpqkpqihop+iq6epuMOkmKCdlpGRlZGQh4iPj5GdkJKSjo2SmZyglop5eG5pZGR6j46Sl5WQk412i2R0Z2V5tICSk5WWmpSZm52Uk5ORkZGTk5OWk5WRkJGRkIyRj4+RjY+Pj42LiIyBcW6AfoyNi5GQj4+PjYqLjY2MioqMiYyMiIeHhoaHioiGgYKDhYWIhYWLiYqNkJCIhImJioqJioSBgX+BfqZ0hoJ/hIKDeWpiX19bWV5kZmRiZmVlZmhnYmJlYWBhYmBgY2NgY2RmY6eneHt7fHp3eHR4eXN1d3R2dnl4dHR1eHR3cHOAdnV2dXd0c25xeHh3d3N0dHRzdHFxcHBtbGtvb29sbWtsampramdpbGtraGVjaWhmZWhqa2heeGNlZGNgrZyXlJCLjpmjo6Chn56coaKinp6eoKSdo1dVoaCjp6mopqGjnZhuf2Nka2ZlbWhjZ2VmZ2hwamNraWhmamloZWVnYVmAYmloZGZnYp2XW2ZpaGZpbG1ramtobGWjm5exbnJvbG90cm9sbnBxbmZyb3FtbG5vcHJwdHRqeHKgnHV2d3B1dHN0cXJydXJwc3Vvc3R4cG9sbm52c2tva29ydnFxcHJ1cXRcempoamdkYmRpbGZoaGZqaWVlZWlsbWlrY2RqaGyAaG1qamlrbm5vb21VZm50k15mZF1kaWdmYm1vXF5famtlaWRsbW5scG5xb2twcmtmtaxhYH5kZWNhYV1nXF5gZl5hZGBjbGNgZGFcbGpwdWprbGZoaWhlaF9eXl9jYFyreVVdYmNmaGtvcneEi46PkI2LhXhxbXN2enh1e4GEi4+Al5WQj5CNlJaTkY2JhIWCg4mKiY2Qko6Mkp9fcnFpgIyQn6+sqLC4mZaVjomQkZCWmIWBcWthmpiYjYl5tnh4l1tqc3uDiIuOkZOWmJyharOBrc/PrnyggYmYknyw1Zeu3aSEjYXFfo+alYuFf3x9gH99eWx+cKp/YJSUjK6YYVmAT05gaGNkcnl7clxgYGhapXJaaXxwUF9tbXWeYZ2iqZpjTVRQcHNKVU5IdU5HgFFQU11TRW5KUVVNTVZLfGdVU1phXHFtZlRcVGFwhYeCklZUjZOScIm7do2lsqqRd63Qvrt7vrCKdJu5wrW/bHO4fYx/xY65hoB/f39+fXt5dnOApZ+O6szFbHFoampoZXBrcn98h3mEhkZDQ4aEem94eoJ+g3l2VEpeaG5wbXFucXJwanFuZGJtZWVobXhwe3pyen19cm5qcGdkYWdmY2NlZ2tWTVphYWloYmhmaWlqbm5xc3Fzdm5tbnFxc3R8dnh4cGdjbW5xc29qY1VJW2hwcW9haWRjanN2d3p7enp4dXNzc3V3e3p7e4KNkJONiYSHjouOmqOHf4eFfHl4enRzbnJ4d3iAdnl6dnZ6gIOIgHZmZltUUlJkeXt/hoWDhH1pc05eVVVfjGBsbGtucm1wc3Rsa4RsgG5ubG9sbmpqamxsaG1sbG1oamhqaGdkaGBVVmRtbWxzcnJxcnBsbGxvbmtsb2xubmxra2ppa25raGNoaWtrbmtrcnJ0dXZ3cGpxb3B1cnRuaWlnaWmDW29rZmxrbmVYUEtJRkNHTlBOS09OTU9TUExMTktKS0tKSkpLSk5PUVCGgI5pbW5wbmdqZWxtZ2dpZmhpa2tnZmhsZWhiZ2ppa2tua2pjZGpraWtoaGloaGdlZmRjYmJjZmRjYGNgYV5dX11bXWFgX11aWV5dW1peX15cU2hXW1hWVZqGfXlzbXB6fnx6fH6AfX6AgH2AgoKBeoBGQ3d3eYCFhH99fHV2VGhVgFlgWFpjWlRZVlVUWGRdVFxaWldcXFtXVlZPRlBbWFNXWVSEfUtVVlNTV1pbWVdXVFdRf317kl5gW1dbYFtYVVhaW1dPWlZZVFNVV1dZWFxbUWBagHpbW1xVWVtZWlZWVllXU1VYUldXWlNST1FRWFVMT0xRVVhSU1FUV1JXRFpMgEtNSUdGSEtNSEtLSEpJREZFSU5OSkpDREpITElNSkpJS0xMT05MO0ROVXBKUVBKUFVSUk5WWUhJS1NTT1JNU1RVU1dVV1VSV1lTT4mDS0tgTE5LSUtGTURFR0xGSktIS1FLSU1JRFFQVllQUVFMTU9PTE5EQ0NGS0pGgFo7PkBBB0NFRklLUFeEXYBbWlhPSUZLTE5NTE9VWF1fZWRiYmVkaWxpZ2JeWlhWV1tcWlteYF1eYm5DUE5IVVtdZnBsaG1zXFlaWFRYWVhcX1NTS0hDb3Z7dHd0zae04IOPkpSYnqOkpaapq7C2b614m7SvkWeFbGZxbF2FqH2Yxpx7fHKlaHZ/eW1hVUtEP4A9OzoxOTN0Zj9cU0dUYEI2KyopMC8vMzY3MystKysmSDUvOVFLJCowMjVQME1UZlwsGx0cLT4iJSIhOx0gQyUlJiknJEAkKCopKSooSS4oJykrKzMyLiotLTI4QUdIUy8wVllhTGuWYnqPnZWAaJW0pqVtootoV3KHkIuaW1+TZBBxaa+L1bGurq6vrq6tq6moBn9/f35+fpB/g4D/f4t/AX6qfwF+hn/lgAF/qYCCf9CAAX+FgJl/goCNf6OAgn+OgIR/nYCCf6mAAX+ngAV/gIB/fqB/BX5+f39+qn+7fpx/hn6EfY5+gn+GgAJ/foR9AXyEewV8fX5/f5CAgn6EgAJ/fYR+AX+MgAZ/f36AgH6Ffwx+f35+fX1+f39/fn2EfgR9fn59hn4BfYd+AX2Sfgh/f35+fn9/f4eACH9+fXx8e3t8hn0If4CAgYGBgICMfwICBACA39TW9/2Lhv6PkJaOnrfR3+j7/PX5gIX52cO1u8nLxdDQx5qBpLC1xMbKw8jLyca4ucC4rauzs7avwrvA08bDy8nGvrOjqKirpKOkpaSkp5GDn66us7W1prO2uru8v769u8LAury8vL6/u6u4t7a3urC2urO2ur+/wImFo6uvs66ArrCsqaist7/Bw8TFv7i2tru5vL/BwcfGztTW297q8ezk5uSzhtzK0dDGw8vKyMXAydXOx8S9qJ6MjY+PmqGst8PArLOussG/vrrJ0ePDr8Pm8LHX1tXa2t3f2tva2tvb3Nfa2dTW2NvZ1tfZ2tnX2dTU19nW0tPNy83Lzs3MysWAr5mlucXGy8nLy8nExcfHwcPBw8TExcfGyMbHx8nIxsfEx72/wsXDxcbFxcS/vLm6v8XDwMK/w7mY57O6ubm4vLy8vbm4trO2tKaYjYqHgoSCipiVlZmcmpaVlJKQkI+Ih5KRib2mqamtp6arqqilqKumpqOipaiopaCnp6Sgmp2AmqGfn5yinJuboaOgoaGZmZmcnZ2dnJeWmJ6cnJWZm5mbn5uYlJiXlJSUl5ORkZiWl5WTj4X2wLfi2Nje6fr/gP/8+PTy8/j//PPy+Pj57/P9/Pj3+vz39f769/n59/X7/fz777DQlpWTk4yUj4+XkpGYlZial5OZj5aTj5KNj5CAlo2Kiena8pOVlZiZlZicmJmbnZmbnJ2enpmH6ObmkaWlm6GfmKSnm5uhnqahm6Gfpq6tpaOlm7qDoqmtpqepp6mlpKqqqaimpKSgo6SpqKSipqirqKilpKOlqqqqqKSooZ2miZidmpiVl5yhoJ2goKOhoqKgm52gnaqkoJ6hpKSAnpiYnp2ipKOho6eHpNm+jZCOj5aXh4WUlpSTj4qFhpaPjpmWkZ2fmZ2aqKKfoaOXjI6Hi9zDkoCCioSTiYeHh4SKjpaOkoiAi4uSoJ6fkZiOl4+QmpaIkIiDh5GVkv2Ag+ebgJCeoqCmtLK7wcrT1tzk2dDLw8vKysfU2MzLx8cOy9nV1drY0tHS3N3b1MyFyoDX6vbw7Ordy9Dlh5qlqr3T2eXm6vDr69fXzsfQz8/KyMq9ta+cherIwMW2jeGFhaXHeYmXoqy3vb++wcTEx8yM763ojI71tuT+zJLz0bCW+MnpmMKa/fC+/pWiqqumoJicppyGoqOLoq/skNWhnqLrnpaSprG3s5KWlpyYhfz9xmHU66C2zr6b1LLr8ZvS3dyB5ob1lufbpdKUhpmmgYyTmLSwpK6jzMvPz7K4qrjB1tjL1oHU7v+FhI6VpsDwkKbB1dXIrIzXj6CZpvy9upTxnMHl4dagtaLyn66kgLfxo5GPhI0Ii4qIg316eXiAuqupvrxraMJub3NreI2hqLC+wbm9YmW+ppOHi5iZlZ6dmHNffISIlJSYkJWZl5eLjJSLgHyDhomBlI6SopiTmJiWjoV4fH2BfHp8fHp6e2tfcn5+gYSGeYKCiI2Oj4yLiI6NiIuLi42Pi3uIhoaGioCGiYOFiIyMjGBhdn2Eh4KAgYN/eXqAipCSlJWWkYyJiY2LjpCTkZeWnqGipaawtbKtr6+IX6CTmpqSjpORkI2Kkp2Xko6IeHFkZWZnbHN5foCDeYB7fYqLi4aTmaaSgoacpnuWlZWZmZ2clpiWlZeWmJSWlZGSk5aWk5OTlJSSlI+SlpeTjpCKiYuIjIyMioiAeWlxfoeIjo2OjYyHiImHgoSCg4SGiYiGiYaGiIuKh4aEhn6BhYeFhoeGiIeBf36BhIqKh4iEiX9qnXuAf4CAg4ODhIB/fHp/fHNqYl9cV1dVWmNhYWRkY2BgYF5dXV1ZWF5eW353eXl8eHR5eXZ0eXt2dnVzdXl4dHF4d3RxbG+AbXJxb2xwbG1tc3NwcnJsa2xvb3Bvbmtra3BvbmZpa2twcmxqZ2traGdpbWlnZ2xqa2ppZl+ui32XkY+RmaSkUaSkpJ+fnp+lpZyeoKSkmp6mpqWipaWhoKajoKOjop6joZ6gmnSRa2loZ2FoY2FnZWZraGtsaWdtZGpnYmZhZWeAbGJfXaGaqGZnZ2prZ2lsaGpra2dqbGxtbWpdoqGhZXV0bXJxanN3a2twbXVxaW5tdHx7dXN0bYNccnd6cnN1dHVycXV2dXNxcHFtb3F0c3Buc3R1dHRwbm5wdHV1dHBzbGpwXGdqaGZlZ2tubGdra25tbWxqZWZpZ3NtaWVscHA8a2dma2psb2tpbnRbb5GCY2ZkZm1tXl1rbWtqZ2NfXm1mZnJtZ3FzbW9ueXZ0dHdtZGVfY5yIZ1hZYVtlhF+AW19haGFnX1hjYml0c3Rob2duZmVta19mXllfaGtosVlcoWhSWmJjYmdvbnJ5f4SGjJKMhoR/hIKCgImNhYaDhImUjo6RkY2LjJOUk4+JiImLjYuRnKKdnJqRhYqdX293eYiZn6enqaylppWVko2Vk5WRj5CHgn5wYayTi4+HaLKAa2+RsGhweYGIkZSWl5mbnJ+hbr6NvHF0yZO50aV60LSagNG0yoCges6+mc54goiHgHlzc3hxboN8bX+Jw2uSZF9jnW5oZnR8gX1lZ2ZraFmqqoOfum5tenVnlXulqXXDwYdNilSPUHxyWHFNSFFYQUtMTmBfV15ZcnBycV5gWGNAb3x+dH1PfpGdUE1WXnCLtW6DmqysoYtxr3KAgZflubeI3YekvLOqgZWExICKgWaTyYuBgH5+fn18e3l0cG5taYD10Lm9rmRhtGZmaWFpdn19foWGf39DSIt6cmtvfH53fX56XEpgZmdwcXRudXl4e3N2f3VpY2xucWl5cXOEeXR5e3l0bWRpam5paGlpZmZlVUlbYmJlaGhYYmVpbW1wcHFwdXZya2xsbnBuYG5raWhsZWltZmlscHFyTE5jaW9zb4BucGxoZ2t1e31/f4J9d3N0dnR4enx9goCIio2OjJKYlZGTlHJKgniAgXp1eXd1c3F6hH14dnBhXVJUVVRYXmJkYWliZ2ZodHZ3dICGkIN1bnt8Wm9ub3JzdnRtcW9vcG9xbnBua2ttcG9sbGtsbGptaWtvcW5pamRkZmRoaGdmZztZTVVgZmdvbnBubmhpaWhkZWRmZWdrbGpraWlqbWxqampuZGdrbWdrbW5xb2dkYmVtdXVwcW1yaFd+ZIRmU2pqbG5sa2dla2hhW1JOSkREQUNOTExOT05LSklISUpIRkVKSkdmbWxqcGtla2pqZ21taWpoZ2hsbGlhZ2plYV5hYWZkYl9lX2FhZWZjZmheX2BihGSAYWFhZ2RgWVxgYGZqYV9bX19eXV5kXltcYV5hXl1YU5p2aIB3cHB4f4NBgYGDgX98e4SKfn5+gYSAg4WCf31/f317f318en1+eXx6d3RuWX1gXVtZUVlSUVVTVV1bX15ZWF1TXVlTV1FTVFxTUE6GgIdSVVdYVlJVV1RYWFdSVFeAV1dWU0qCg4BVY11VW1tTXWNTUldVXVhRV1dcZWRcWl1YakdaXmFYWFtZW1dVWFlYVlJSU1FTU1dWVFJVVlZVVVBOT1FWVldVUVRNS1FDSUxKSUdJTU9OSEtMTk5OTEtGRklIUk1IRU1QT0xIR0pJSkxIR0xTQVBoZE5QTlBWV0qASFRVU1RQTUlKVVBQW1VQWVlUVlRfXFtbXlVMT0pNeGZMQUFJREtHSEdFQURITkdNRkBKSk9YV1lOVE1STExRTkRKREBFTU9PhUNGeUs5PEFBQURKSEtPUlRUV11YV1VTWFdWU1pcV1hZV1ZeXGBjYV9eX2NlZWFeXWBgX11eZGiAZmZlXVVdbUJOUVFZZGdqaWhmZGRZW1pWXV1dW1lbVlRUTkaCdXF3cWPLna3a/Y+Slpygpqmqq66wsbO0crB5nFxepnuatY5jtqGIbKWLpWyFZaqcfqpkb3Z1bGFUTEc/UGdjX3J+wEZhODMzRTQyMTU3OTgxMC8tKyZKWUNSgj9cLzU0L0M3TVM+cmg5HjopRyVBPSQnICMlKCIlJiYqKyotKjM0MzIvLy4zOD4/QUYpTFVZLi0xPFFsj1tugpOVjHlimGRxb4HIlZBlrGh2iISAaHpqm2RuaFeO4LWFrQmuraysqaelnZKFfgN/f36Nf4KA/3+5fwN+fn/ngAF/qoABf9CAin8BgKZ/noCDf5SAg3+ZgAF/qoABf6iABH+Af36lf4J+qH8Dfn9/vH6cf4Z+hX2Ofgx/f4CAgYGAgH9+fX2EfAp7e3t8fH19fn9/ioAHfn1/gICAf4V+AX+NgAZ/fn6Af36EfwF+hH8KfX1+f35+fX59fZx+BH9+fn6Hf4iACn9/fn18e3t7fHyFfQR+f4CAhIEBgJB/AgIEAID2h4aKiYyJjoudsLzi+IL56PiCgIPt1cvEv8bHzdHHwqiCpLO5s7jExc/Ry73FvLS2wMKyq7e8x83DxNDY0s/PxsW0rqqoop6bnaGenZ+S8YqVmJWYmZqksr24urWzubm5vrersLKvrKmsqq2ho7O2ta21sLjAu7y385mttK+zsoCwsrO2tK+yvcDCwsTExb6+wL24t77Fx8rR19TS2N3k6fPz8+CFt8vN19XPwb+on5yklJGNkJakrrq+wruytKurur67ur/DwsXHtcuvnsmNu5v6ytHN0tjY2djW1tLS2dna29nW1tfW1dXW1NPT0tfY1dPT0NDR0tHPysvLzszHyUjLxqPch6q8x8jGxMXBvsLEyMfBwsHCw8jFxcnDx8TFw8HFx8THy8fIw8LCwsHDwr3BwL6+w8G5ioi6vL66u7q7u7a0sra3t7mEt4C2trWxsKSUj4uGg4T99fH8hYySk5mbnPj9rayqqqutqaekqqijo6KtqKyrraqko6Wmo6GfoZ6goJ2fm6Gjo6Sin6Cgn5+jop6inZ6en56enZ2fn5iXmZiXm5iXmZKOjf/z7+7q6OTd5e7/goGAgYD7+vv5+Pj79u/z9/v89Pf49YDy7vHz9/z7+fn39/v09vr8+Pr5/v38/f+D7LDglJuZlZiXmZ+UmpuZn5qSlJaZk5WZlpWYkPzb44uamJmbnZydm5qZmZ2cm5yfnpuaoJ+bnJ+cgeTi5pKlo5ukmp2gnqWkp6KgqqqlnZ36wp6jqaimp6iqqqekpKCrqKWnpqWmp4Cioqijp6akpKSoqqehoaWqqqmop6frwJeZnpyYlJ2XoaahoqOdoqGko6KioqWgqKijoKmmoZicnaKkpKGmqamZ7sWLi5eTk5Kam5iJiI6hnY6TkIqSjpadn5SaoJ+fn6akm5abkZSVlZbRxo2OlZyhkImMlZWQkpOVjYuMmZGJjoCUnJeSkJSdmJeNkISA+YD+8+/uhIKD+r2ZkZWamKegtbevurvN1ebV2c/HyM7U2NfW2tjT2Ojm5OHk3tvW3dvj2dbDusDEx8zY39vm6erp5urfxLr7j7LQ1tTZ6vb28uPd7d7Kx8HNz8LGuq2gkYb/1cPGxayLypODmcF6i5ilr4C3u76+vb7AwsXK986Z04KM8q/4t+fXv8SqiuCl4LzO9a2O1K+Is9n5+oGuvIO0z4eo/8CJ3uWZo5mXorK8u6KXoJ2jk4zvzJfqouXyhoOf6PXk2ZyPjpGBgfaAn4yDoY+6qfGywsLm2Le+8dbhgoH06f7q+f/hgqnDweiIm6zD1DTb18Wsj9+fz/SRupzSue+9jLDR8/LxkqqU2I6npJLqrd6fkpGPjYyLi4mGg4B6eoF54NDZgL5oZ2pqbWprZ3WFka++ZLyrumNhZLWknJWRlpacoZaSfV97hYqFipSUnJ2WjpaPhoiSk4V/iY2XnZSWoKShn5yVlYeBfn14dHJ1eXZ2eGusYGpta29wcniHkIqOiYSIhoWKhH6DhoN/fX5+gHR2hYiGfYV/hpCKioircn+JhYeIgISGhoaEgYWPk5WXmJeXkpKTkIqKjpWWmaCkoJ+mqKyvubi8r2KCk5aenZqRjXtzcHVsamRmanJ4goiMh39/eHiChoaGio+LjJOEmYRxjmKCa6+OkI2RmJiZmZaUj4+VlZaWlpKRk5KRkJOQjo6NkpKQj4+NjY6PjYuHiIiLioaJgIqIcJVccoCLjYuKioWBgYSIiYWDgYKDiYeHi4SIhISDgoaHhIeKh4mEhIODgoaGgISGhYWKiYNgXYCBhIGCgYCBe3t8fn9+f36AgoF/f357enBmY2BcWViooZqiV1xgYWNlaaiwfHx6eXl5d3Z0eXl1dHF6eHx8fHl3dnd2c3JwgHNwcXFucGxydHN1dHBxcXJxdHNydm5wcHBvb3BvcHBsa25tam5sa21mZWa3raymoaGblZacplVVVFNSo6Oko6GhoaCcn6KlpZ6ipaCcmpudoKeppaWko6agoKOkn6OeoqKeoaZWnHWdaW9tZmhpbXRobW1qcm5maWptZWdsbW5vgGWylp5ibWlqbG9sbGppamlsbGprbmxqaW5ua2xubFmenZ9mdHJrdGttb250cnVxbnh5dm9vr4ZwcnZ2c3R2d3VxcHBudnJwcnJxcnNvb3Rxc3NxcG9ydXNtbHB0dHRycXCfgWVmamhmY2tlbXFsbW5obWxvbWxsa21pcXJraHJxgGxmaWpucXBqb3V1Z6GHZWNtamlqbm5tYmJmdXRna2ZiaWdvdHRqcHV0cnN5dnFtcmhqbGtrk4lgYGRqbmJcXmZnY2ZnaWRhYm1nX2JqcWxpaGpxbGliZlxZq1mxqKagWVlcrYBkXVteXWhmc3NudHaCh5OIi4WCg4aJjIyMj46LgI+amZiWmZaUj5SRl5KQg3+DhYiJj5OQmZ2enZugmYd9r2V/mJuZnaiysK+jn6+hkZCKkpKIjYR6cWlhupuOkpF8ZJp5boWmZXJ8hYyRlJeYmJmampugwaV6qmhxw5DPl76olpyKc7qJzKaz0JJyq49tj6/JyWqhqlyBo3OLv5BfgJKUa3NranJ+hoV0anBucmZgpoxls3uRk1ZZdZ6omZJwfoBqVlWdS1hMRlVLZFuBYmhqgHpnbI9+hU1Ok4mXi5aei1JyiY2taHiHm6y0rqCLdLSApcN0m43HteGxepuyycXBd4x5rnOFgnS6i7eIgIB/fn19fHx5dnNvb3NqvamrgLxjYGFhZWBfW2dwcoGHR4Bxf0JARoR9eXd1eHh8f3d0YkphaW5oaXBxeXx2cHp2cHN8fm9pcnJ6fnd2gYaBf314em9qaWdkYmFjZmFgZVmETFFTUFJVVVtpdW9uaWZtbGxybmZkZ2ZkYGNhYlxbZ2lpYGpobXNub26EWmpybnFyc3Byc3NvbXF6foCChYODfn58eXNyeICBhIqMiYiPkJKSm5ygllJrd3yEg4F4dGRcWWBZWFNVVltganF0b2loZGRsb3Jxcnh0dn9xhHNicEpiUYJpaWdrcXFzc3FtaGhtbW9vbmppamtpamxqaGhma2ppZ2iFZoBlYmJiZWVhZWdmVnBGWGNrbmtqaWRjZGZoaWdlZGVka2hpb2drZWhnaGxtaW1wbXFtamhoaGtrZmtvbWxzcmxQS2ZpbWpqaWdpY2FiZGdoaWdpam1qaWdoZmBXVFJNSUqFfXh9Q0dNTVFRUYWXcW9qamtsZ2lna2tnZmJvbHFwcYBsZ2doZ2NlY2djZWRfYWBnaGZpaGVlZmpoa2hlamNlY2RjZGRjY2RgXmFhX2ZiY2dfXlqhlZKNiYqCdnV6gkNFRENBgIKHhIGAg4F9goKDhX2ChYJ/fn58e4CAgIF/fX51eXt6dnp1enl0dHxDd1uDWmFeVldYXmRbYl9bYVxUWoBdXVRYXV5dX1aSfINSWVVXW11YV1RUVlZZWFVUV1dVVFhYVFdZWEd/fXxSX1xUXVJVWFZcWl5aV2JhXVdXjGlZWV5eWVpbXFpXVFRRWVVTVVVVVlZSUlhUV1RSUlFUVVNOTVBVVlVTUFByXUdHSkpHRkxIT1NPT09ITk5PTk1NSoBLSVFRSkdRUE1HSkpOUE9JTlRUSXRpUU9YVVRUVlZWTEtPXVxQUk9MU1BXW1pRWV1bWVleXVlWWVBTVVVVbWJFR0hMTkVBQ0lLSkxNTklISFFNRkpRVlFOTE5TTk1IS0NAekCBeHZzQkNGhF5HPjs9PENDS0tISklRVF1WWVlYWSBaXWBeXmJgXV9kY2FdYGFiXl9eY2BgWlZbXVxbXWBfZYRngGtmWFZ5RFRlZWNkaW5ra2RibWRaWlZbWlNXUk1JRUKIdW5yc2hdpKKpy/mQlpugpKapra+vr7GytLjRom+TV16ui9GUqo9tcWNUhmabhJKtemGTfGF+l62wWbCFSnGeb3eAbEJaUjI4NTY5Oz09OTE1MzIvK05KMWBVRz4kJzpGZElDQDZFQjkwL1goKSUjJCItLU0tMDI5NzY3R0RFKCpSVFtUVlhVMkFVaINQYG6Aj5eUinllnG2NqGSKcKWQrIBbb4CPjZNccWKNW2loYKGFz7Curq2trKytrKqop6ampZD2yrcBfo1/B4B/f3+AgIC6fwF+qX8BftR/BICAgH+xgAF/1ICEf4eAgn/GgIt/hYCpfwSAf39/mYCDf5uAg3+TgIJ/qoCCf6iAA39/fqd/gn6ifwJ+f4R+g3+/fpp/h36FfZB+Cn+AgIGBgIB/f36FfYJ8hHsEfH19foV/DYB9e319fHx8fn9/fn6PgAl/fn+Af35/gICGf4V+AX2IfgF9in6Cf4d+hX+KgAt/f359fXx8e3t7fIZ9BH5/gICEgYKAkX+DfgICBACAgIiJkpWZlKfD2eb194H7gYD9/ufe0sLIvcXK0dLRoIaauMO6vL7Awr7JwseurrKwtLm+uLG5xM/P0L/Mzc/GxcC4rayqpaOmpqSlnp2W7am5vbu8wsC4u6yipqWflZqampydoquwsbKvsq+ts7W1t7e0ubSmsrGvp9aZqKmorrM9r7G1sa2trK25vMHFw8bBwMTIx8XHycnL0dDU2dzi6Ozl5PLuw+aZoKKdmp+mtbq8wrq6u7OzsLS0t7rJw4S5bMC5s7rAvr6dp+2iztXW2M6x86rS0NTX2NTP09bT1NbV2NfX1tbU0s7P0M7Mzc/QztDR0M/MycnKysrLy8nCu5fvgKGtl4+Zs8PHycrGyMjIx8XHxs7Oy8PFxMPCxcXEw8PFxcPExMPAw8XDwIbBeb+9r+mfv72/vL27vLy5uru5s7S3t7W1trO0tba1trG1s7O0sK+urKigk4qHhYaEgsmHi4uOkZaepKWmqKKloaesramqrqiloqWhpaChqqajoaShoaKjnqGioJ6ipaGeoqKeoZ2anZ2cmI2Hgffu7urp6Ofh3Nvi7P2EhICD//766vH1+/2A/fr4+Pr59O3v+fP1+fj08u3v8fT4+Pr3+PT5+f7+gPv29/v9/Pr9/P76gIGA4bTpl5WYmJuWnJucl5ybkZGYk5SVlZGI4uiFl52em5eVmJuem5qWmpmcm5qamJeZoKCdmJudnZ2eoJL56OXui6CjoJqgn6SbpICpp5yWweihp6Stqqmmp6iip6qin6qpp6WpqKiqp6Cfo6WnpaapqqmqqaampaisrK2glpGVlpydn5iZmZebnqGin6CenKOkoJ2fn5+kop2coqSgl5qcm5mcnaSgtpqQmZiWkIqIi5WaoZuTiZiblYqJl4uflp6elpSUmpicn6aXl4CRj42QkJrd2JyflJKVkZCPgZCOjIyKkJCWj5WYnqCQiJKXlpeZkoWDgPSF+v7t4tnX4+WAhOaeh5aSlZ+wurS8zNDOzdHOz8W7ws3Qy9Tb4+no7Ovt7ODg1snU3dLX0cy/vb7CzMfFzdrb29vdwteJ+Obc/pOuxtLd7t3s7efw4oDNzMS9x8HIxLvDqKGOgOnUwsG1oY7hnYKSr218jJunr7S3u72+wcPFy9DdmOadxfKTl4DIiKms3sXGq4rYqPCqpc+Nw/umgJm0s66qycOaprO1uLOnn5+mrLKuoq+oqqiVkdbb2vSYk+HipOX+//btu9rFqa6sraavz8TW1rrS01Dv6oWOh5eqtc3c8ISPlqG1xNPd39XGtqGM6baAruiLsYjiwtOJ3JW33fSA+ojs3KjkkKenlve8hrmVkZCQjo2Mi4qHhYJ6dXFuctTMzu37hYBgaGlvcXNxgZWkrrm3YbtiYb+/rqmfk5mPlZqgoJ91YXOKkoqMkJGSjZeQloCAhYKGipCKhYyXnp6gkZucn5iYk4qAfn57eXt7eXt2dnCuf4qNjI2Rj4mOf3h5fHlydXJwdXh4gISDhYKEg4CBgYOHhoOKhXuEg4F7lHF9fn2EioCFhoqGgYGAgIyQl5iXm5eXmZuZl5eZmpyhoKOnqKywsqysuraUom50dnNwdXiDh4aMhoWFfH57fn+DhY+MhIWGhomCf4WLiIZxdqRwjpKUl494qHaQj5SWmJaQlJWQkZOSlJSTkpGQjoqLi4mJio2MioqMi4uIhoWFhYaIiIeBfyRnn1Nnb2JfaX2JjI2OjI2Li4uHhoaMjIuEh4eFg4aGh4WGiIeEhV2BhYeFg4OEhYWHiIeEfKByh4SGhISChIOAgIF/ent8fXt9fn18fYB/gHx+e3t9e3l4d3VwZmFgXVxaV4thZGJkZWdudHV3eHR1cnl+fXl6fHl3dHdzd3Nze3h2cnSEc4Bwc3R0cnZ3dW9yc3FzcHBxcG9tZV9bsauqpqSgnpyYlJOapVhXV1hZqqinl5+kp6hVp6ajpaOin5iZoJ6gpKWhn5qcnaChoaWkpqOmpainVKOfoKKkop+joaOlVlRTlXaia2ZqbXBrcG9va25tZWVsZmhramdhnZ1caG5vbWhnaYBrbmtpZWprbmxqamhnam9tbGhrbGxsbm9mrKCfpGJvc3Bqb251bHR4dW1qh6NwdXN7d3Zzc3RvcnRua3V0c3F1dHR3dG1tcHBycXF0c3NzcXBxb3N2dnhwZWJiY2hpbGVmZ2Zpa21tamxqaGxtaWRlZWZraGRia25rZWdpaGZlZYBtbHxta3FvbWhjYWRqb3ZxamFtcGtiYnBkdWxzdG1sbHBvcnR6bm9paWZoaHCbk2xuZmVoZWVkWWVkYmNhZWVrY2Zpb3JlX2dramtsZ11bWahcrrKimZOTnJ9ZXJ5nVVxaXWRwdnF2g4iFhYWEh4F6f4eIho6SlpiVmp2gnpaVjYCGiZGKjYuIf35+gIaEgomSlJWWmISYY7KmnrVqfY6Xoa6fq6ypsaOSkY2HjoeOioSMe3RmXK2ciIiAcmesfmp6l15rcnyFi5CUl5iZm52fo6iveLR7m75zeGada4WHr5qeiXGujdifm7d7pdSHcn+RkImGnZZ5g42Nj4yCe3h9gWWEgXWAe3t5a2eZnZq+eGWbnHidqq2oooq2qoB8enh0eIV4gIR4iYSSkFNZVWVzfI6fsmJtdYGQnaq0ta6jlYRxvpJmi7hvlnnQtb+BvX+Xucxqz2/Fsoe2coSDdcKVa5iAfn9+fYR8Dnp4dXBrZmFhrqSgtr9mgFlfX2JkaGVqdXt+gHxCfkFAfoF6fnx1enF2eX5+fltKWW50bG1tbW5rdXF5Zmdsa3BzeXVudXqBgH9zfX1+eXp4dGtpaGRlZ2hmaWJjXYdncHJxcndzbXNnYF9fXVhcWVdZWl9jaGhqZmlmY2ZmZmdnZW5pX2dmZWBrW2lpZ25wgG9xdHFtbWttenuDhIOEgoSHiISBgoODho2LjY+PkpSVkZGem3yFW2FkYF1hZGxtbHJrbW1lZmNmZmtsdnNqbG9tcmtrcHVza1xfflNoam90a1l+WWtobG9xcGtvb2prbGttbGtpaWdnZGVlY2NkZWVjYmVlZWJfX2BgYmNjYVtdRkxyN0RLQUVRY2xtb3FtbW9tbGdmaHBvbWRmaGZkaGlqaWpsa2lpamtna21qaGlra2prcG9saYBYb21vamppa2poaGlmYmSEZjtnZmZmaGhpZWtnaGtqaWhoZ19ZUlFOTklHb1RYVlZYWWBkZGhsZ2lkbXNybGtwbGdjamhsZ2hva2lnaYRogGNnaGlmamxpZGdnZmplZWZkZWRbVVKfk5SOjYqNh396eXuGR0VERkaLiop+hIeGhkWKiIODg4KBeHmAfHyChYV/eXp6eXp6fX6AfICAg4FAeXN1dnNzc3p5en1BQDxsW4dZV1pfY15jX15YXVtTVl1WWVxbWFSBf01WXVxZVVRXgFdXVVRRWFhaV1VUU1FWWldVUlVWVlZXWFCKg4GFT1hbWE9VVlxSXGBdU1NsfldcWmFdXFhZWVRXWVFNWVdWVFdWV1lWUFBTUlNRUlRUVVVTUVJQVVhYWVNLRURFSUpNR0lKSUxNT09OTkxIS0xJRERERklHQ0JKTEpFSEhIRkRFgExLWVVVWltZU05MTlJXXFhSS1VWUktKV01bVFpbVlZXWFdZW2FWWFNTUVJSWXpuUFFMSk1KS0tDTUxJSUhLSk1GSUtQUkhES05OTlBMQ0FAeEJ7f3NvbGpzc0BCcks5Ozk8QkpPS0xUVVJTVVddWlRXXV5cYWFhY2FkZGViXV5ZgFZcXlhdW1pSUVBRVlNTWF9iZGRnW25Hf3dtfkdUXF9kbF9oamhuZVlZVlBWUldTT1hQTUZAenNpbGdeXbGfnr7sjpqboaWnq62wsbO2ubq9wMd/s3WRqF5eUHtTZ2SBcHFjUH9nlnZ8n2yRtnhnX3Z6c254b2ZucnBtaGNeWVdUZlNORklEQTw2NFNbUG9ZOUxMP0tQUVBOSG1mUU9OTEhITUdMUE1VUFJRLjI0PkpUY3GASlRcZ3WAi5OVkId8b2CffFdzll1+Y6ONkmGMWm6Hl0yVVZ2QbZNcamphqIxwuqmqq6yrqoWrDKmloZmJheG+rLSxYY1/BIB/gIC9fwF+qn8Bfqt/AX6lf4eAAX+ugAF/tIABf6yAAX+7gI1/hYCIfwGAnn8BgIt/BoCAgH9/f5WAgn+hgIR/joCCf6uAAX+pgAJ/fql/gn6hfwJ+f4h+gn+6fgF/hH6af4d+hX2Rfgx/f4CAgIGBgYCAf36FfYJ8hHsEfHx8fYd8AX+UgAR/fn+Ai3+Rfol/joANf39/fn19fHx7e3t8fIR9B359fn5/gICEgYOAkn+FfgF/AgIEAICWoJebrLvI6O6C/vX+gIGB8+bVz8nNwsvGvsHGrI2WuMC7xb7Dub2/wMW+uK6tqra7uKS8urzO0czJy8fKy8e1srOqsLKtr6uqpqKjl+agt7q0ucHBwsnCxcTQy8q+uby5trOqpqqhmpWQkI+UjImDhIyQj46Vl5aQwJ2jqqiqrE6srq6rq6irqqSrub/CxMTEyMzNycfHxMG+wsLN4efk5ePg5eHOn4bNxcK6ura1tsLEr6K5ub7AwsO9vMDJx8HAu7i2rpyl04q10dnc29WE0YC5iIvJ2NnY2tnY1dnc3NrX1tXV1dbW09HR0M/NzMvJy8fLy8nKycfGyMa7mPb+oLCwsLGwr6mRiZe2wMTDv8TExsK9vby+wsTEvr2/wMPCwsXFw77BwsTGxb/Avr24tra5uJT4sr6+vsHAvbq5urm6ubu2tbe1tbe6traxsbK4t121tLOxsrKytbSzrq2sqaypqqmqqqmknJaRjYqGh4WHhoSDg4WFhoSA/4SDgYD+gISEhYeF/v39gP/6/+rf+vHq6uTk4N7f2drf6On2/YKDgoCDhYOAgYCAgIGA+fqE+YD2+oKA/Pv5+P769/L49vDy8PDz9fPw8vT08fb5/ff3+Pz6/YL9/fb//vr9//v9gIGBgP71+/7ltN6Wm5mdn5ybnZKSkpiXlJqF2dr2k5abm5uZmJuanJ2bmJmVmJqdnZiWmpmVmp2gm5eenaCbmZqcnZ2Vh+jk6vuSn6Ckp6ik+YC6kKKjpKmorKqpqamoqqqpqKaoqKOlqauppJ6gpKqnoaWmqqmrqaOnoaampqrL4pmTmJuanp2Zl5qYo5+hoqSempqgmpeWlZeampagnZ2io6KZlJqenpiZ4s+im56fj5eYjIqFkJyZmZCVk6GNipWXoJ6bnaOgnJGPkZuWkI6HhICHjJiezPGXl5SSjZCVkIWBgv2FjIuJkJCLjJCUlIuE/oCJgoOEgvHa3/Py7oH7gIP68O3405uPprGysLCqsLy+xc/Ny8jFvsDMzNDU0tzh3+Ps+YH57dLR0tfSztXNxszOysrQzczPy7Xqk5eH2PWDjobxgoWVssvY4uTn5eDr3YDaz8TGycPIzMC4nJWK++zEw762tZ38v4p6lLRufYmYpbC4vMDDyMvNz9HQ0NuV7Kbkip6knfbDjsHt5660rJb9zKHZn6OM8drQwIicmajmjqmyrrXD0OH4k6mzuri2uLSsp8i0maGilZ2enJqZlPvt9/6Bg4WGiY6XmZugp664wU3FyMzNysS8s6ecjffRroTIjsSL06OG58rK9LD8ncXa6/n695iL6KnZhZylmYbjsIG2lI+OjY2MioqKiYeDf3t2dHFrxLTC2e30+oGAj4BwfHJ0gY2Wr7FiwLzDYmBht7ChnJaZj5uXjpGWgWhuipCKlI2SiYyNjpOMiYGBfYqMiHeLjZCfn52Zm5ecm5iKiIeAhIWCg4GAfXt8caZ4iI2EiJCQj5eQkZGcmpmRjpCNi4h/e312cGxrbmxtZ2VfXWJmZ2ZrbG5lhXN4fHp9f4CAg4SBgH5/f3uBjJGVl5eYnaCgm5mYlJGQlJSdq7GvsK2qr62ddWCZko+Gh4OBgo2Me3CEgoaHiouIiYqRjIeIhYOAeW1yk2CAk5mcmpWSkpOUf15hjJeYmJqZmZaZm5yZlZORkZCSkpCMjIuMi4mIh4mHiYqIiYaEg4aFfWelpIBmcW9vcHBwa19dZn2EiYeEiYuMh4B+fH+DhIWBf4CChYaHiIeGgoWEhYeHgYOBgHx5e319ZKh9hYWGh4eEgoGCgYKAgX6AfXl6fH98fXp8fIKBgH17enx7e35/fnh4dnN3dXZ3d3h4dm9pZmRgXV9eX15dXV5dX19dWrReXV5ct4BcYGBhYGC2tbJatLO5rJ+xq6OjoJ6cmZqUkZSanKOpV1hXVVZYV1RVVFRVVVanqqinpaSjp1ZUp6empKajop2npJyem5uioqCcnaCem6GlpqCioqajplWop6Gnp6OioqClU1NUU6Sdo6SUeZhsb2xwcnBucGdmZmtraHBgm5itZ4Bna2prbGpsbG1uamdpZWlrbm5pZ2poZWpsbmpmbmxva2prbW1uZl+ioKSxZ29wdHd4dLCBY29xcXZ2eHZ1d3d1dXZ1dHJzdHBxdXd2cWttcHVxbG5wc3R1c25xa3FwcXWKmWdiZmhmampnZWhlbmtsbG9oY2VoY19fXmJmZWFqZ4BnbW9uZ2JmZ2ZhZpqXeHR2d2htb2VkYmlxcHBna2p4ZmRsb3VzcHJ5dnNraWpzb2doYl9hZW90kqtra2loZGZqZlxZWa9cYWBgZWRfYGVoZ19bsFlfWltaWKaWm6moplqvWFmsoaGqkGhbbHJxbm1qcHh6f4WFhYOBfYCHh4mLiYCPlJaZoKlWo5uJioyOioiOiIKGhoSFiYaIiod6qW5wYZOsXmhirF5ga4CUnaOnqqiirKCelI2Nj4mNlIiDbWlhrKmNi4eEhHS+lHBkfJxea3N9ho2SmJ2eoaSmp6amqK90uYKvbHyBe8SdcJm9soiOjHrLqIa3eXZqu6mfn2x6eICIunOHjo2Un6m3xnSGjpKSkZKPh4WekHl9fHV5eXd2dHDIwMjKZ2dpa21yeHl8goeMlJygo6WmpJ+ZkYZ+csmni2idcJxxqYN00Li6553YiKS1xNDLx3pyvIitanyEeWmxi2aWfnt6enl5eHp6eXh1c3BrZ2Jcp5CXqLe7v2RjbYBhamJha3Fyf3tEg32APz5BfXt2dnV5bnh2b3J3ZVBVam5pc21xamtqbXNubmltaHN2c2N3dHV/f356fHh7e3pwcnNrcHFtb25uamdpXX5jcXVrb3V2dXx2eHmCf4B5c3VubGxmYWReWFVRVFJTT01ISExPT09RUlRPXmBlZ2Zpam5rbW1ra2lra2lud3p/goOEiYyMh4SDf3x6fX+IlZqXmJiSlJOEYU+BfHlxcW1rbHV0YldsaGxucXFwcHN5c21wbWxoYlpab0dgcXR2dG5sbW5xY0VIaHFycXV0c3F1d3Z0cGxpamhqa2hmZmZnZYRjgGBkZWJjYl5eYWFcSndzREpISUpKSkhAQk1fZ2xqZmtsbmpiX11fZGVnY2JjZGlpaWxramVoaWpubmhqZ2ZiXF9hZVKHZm5vbm9ua2pqbGpram1oZ2ZjY2VpZWdiZGRta2tpZ2doZ2dramlmZ2RfZF9hY2NkZmZfWFVUUU9QUFFQgE9PTlBRUlFOm1NUU1KkUlVXVldUmpqeTpucqJqOnZKMioCEgX19d3R2fHyDh0VGRkZHSEhFRUJEREVFh4mKh4eHhYhERIaFiIeHgn97hYV+fXl5gX9+fHt+eXV8f396e3l+foNBen13fH58e3l2ez49Pj13dHh2bFl9X2NfYGJfgF1dV1hZYF9bYlJ/eoxSU1hWV1hWWVlZV1RRVFBVWFpaVVNVU09UV1hUUFdVWFVUV1dWV1BMfn2Ai1BWV1xfYV6JZE1XV1hdW15cXFtaWVpaWVhWVlZTVFdaWFNNUFNXU05RUFRUVlRPUkxSUVJWZ3BJRUlKSUtMSUhKSFBOTU1PgEhDRUhDQD4/Q0ZFQUhER01OTUdER0VEQkdveGBcXV5SV1hOTUtSWlhYUFNSYE9NV1ldW1laX15cVFJTWldRUk5LTE5YXXSBUFFOTUtNUk1DQEF8QURCQUZGQUJGSUpEQXw/REFCQT93bG94dnVCfD9BenNyemVJP0lOTElIRUhNgEtPV1dYWV1XWFxbWlxcXmBfYWRpNmdkWFhZWllXXFdWV1NRUVNUVFZWUntPVElpf0ZNRnRAP0RSX2NmZ2hmYmlhYFlVVVVSV1tVUkVDQHZ0aWppaWtjsqiboMr5kJmdo6issLS4ur7Bw8PExcfOfrByj1VeYFmPdFd2lY9kZmRZgJl7Y4xZWU+HfHFrWGxvfKJldoOOmKKtusRrc3l7eXZ1cm1odHNgXltYWVlYVlZTn6Cko1NTVFZZXWBjZ2twdnuAhYiLjIqFf3hwaV6mjXNYhl+BXIlrWqKTlKxvmmF2gYqPjI5eWpZujFVjaGNZm4RqtKSjpKWkpKSmp6enpaWjDqGZjoDZsayztbO2XllhiX8HgH9/f4CAgL1/AX6rfwF+zX+3gIJ/tYABf8SAAX+EgAF/hoAEf39/gJV/joCIf4KAn38BgIp/hICHf5CAg3+ogIR/h4CCf6yAgn+pgIJ+qn+Cfot/AX6NfwF+hn+GfgR/fn9/o34Bf5Z+CX9/f35+f39/fpp/iH6GfZJ+BH9/gICEgQWAgIB/foV9B3x8fHt8fX2EfIV+iX+WgIR/mYCEfwd+fn19fHx8hHuCfId9BX5/f4CAhYGDgJN/h36DfwICBAAenrK81ujq9PDk+P6Hiv3u4s/EysXJz8fEvLajjKCxhLyAytLOxrjEwb+3qrKqsri2wsHFyMXGxMrKyc3bvrqwrKqnpairra2uppyT2ZqtuLW9urq+vMDExMfLzcG+uK+vuLe5vcLBvrq5vMC5p5SaqLm+sraxqqajwqCkqKioqaqspKmqr7G4tbK1srCwtbzCxszJw8PHztHHwb7G1uPm3dOAzMfMw/urzci+v7e4u7e+ubm7t7a3urWysbS9y8zDpqDSjbPMzdHR0M3Nz8/X1dDS0sqggrHW2NjZ19nb3NrY1dTS1NPT0tHU1NLR0M3MzMzLysrLysrMv5yC/5uqrrGzs7Cwr66srK6mj4yWssDCxcbEw8TEw8TBw8TEwsLEw8SAwcPDvry+wcTCu7q/vru+vK75mL3AwcHCwsC+u7y7vLy8ubq7uLi2urWytbW0tLW1sbCwr7C1t7KztLOtrrCura2vrKyuqKakqKajpqqnp6emqKiopqepp6SmpKOipKKin5ucn56dnZqcmpiBl4aGhYeEhf39/oCA/IOA+oD//vwEgIGAgISCGYH4+/j7+vmA/vr29PX//fr8+fb59PXz8/CE74Dx8fb29vyA+fb79/r6/v79+fr58/P49vuAgISE+oD/9/n8gYPqtdSSkpSXl5aRk5eR/dbc/ZKTlZublpmXmZeYnqGhoZyenZycnJmZmJuZmJWXnZ2boJ2cnaCcn5+goKChnqCXgu7u9ISOveufnqKlqKanpausramtqairqamtp4CgpaWnpamjoainqaOdqqenrK2rq6empqaMlJWWkpOXmpugnp6gnJqgpKWgoaGYn6Chop2YmZiVnJaYoJiln52eoJqQiJ+gnqqnlZienp6WjZWMlJiWmpqVnZSKl5OUn4mXnqetn5mQkI+VkI6LgYOFlMf6mY+Mhfj2g4eMkZCWj4CCiImLhZKVj5aVkZWD8Ojf8ICCiIyG/P/73dvq9Pv19/r016SRvMnDuLW0tLzHxbzBw8XDv8LBytPU1dzr8Pb9+/z449/m4+Df3drez87MzdHGvO2RlIyMifuIiezp64qRkoyKj5Srv9DU4+Xc3dzb2+Hk1djKzsLBqZ2T+vLq14DRxsXEhOK+hH6TrWh7jp2nsbzBwsDAvsLEx8jLztP0r4Sx84mbqqOQ9qvzsvabwJyM3O2XqIGGg+HY5JKpz+CEmrLC0eSDjqS1x/aKlaKuucPJ0tbe3dPMzMnI0t7p7eno5NnTx7irm4//49rKqJeP6LOP2rqhi+nJwsvmjrPhgjmgwtjwhoOBlv65+6rS/JCeopCA3baNz5yOjIuKiYmJiIeHh4SAfXh0cW3Fwb281PWAgPj5hIuWk418doaOoK+wurSou8FoacG2qpyUmJSXm5WTjIh2ZneEiomKiZWempKIko6NhXuBe4SLiJKRlJiVlpOamZibqZGOhoOAf319f4KCg313b5hygYqHj4qLjouQkJCTl5qRkIyEhYyKi5CTko+Kio2Qi3xsb3qIioKFg397eYl3eYR8gH6BeoB/hIaMiIaJioeHio+VmqCcl5aZoKGYkpCVo6+yqqGal5qStn+alouLg4SIhIqGg4aBfX+FgH17fomUlI11b5JifoyMkJCRjo6QkJeXkpWVkHFbfZeXl5iXmpydm5qVlJCRj5COjY+PjYyMiYmLi4yMjY2KiouEa1epZW9xOHN1dXFzcW9ubnFsXFxle4eHiouGhYeHhoaCg4ODhIODg4aEhoaCf4CBhoR/f4KAfoKDeaZriIiIhImAhoOCgoaFhYKCgn9/fYJ9e31/f31/gHt4eHh5fX95fH59eXh6eHd3enh4e3d0c3h1cnR3dXV0dHV2dXN0dXZ0d3R1cnRycnFtbHFubm1sbGpqWWJaWllbW12rqqtWVaVVU6NTpqamVVZWVldXVlZVo6WjpqSmV6mkoaGhqquqrauAp6iioqOfmZudnJ2enqGhoKVUoqKkoKOkpqSloqWln56in6JTVFZWolOmn5+eUlKXd5ZpZWhsbGpmamxltJaasmVlZmttZ2poaGdpb3BwcGxubW1sbGpramtpaGVnbG1rb2xrbG5rbm1vb3Bxbm9pW6iorl9kg6RubW9ydnR0cneAeXl2eXZ1d3V1eHFrcHJ0cnZxb3RzdW9pdHJxd3h2dnJxcXJgYWNjX2JmZ2ZraWlqZ2Rqbm5rbGpiZmdpaWNfX2NhZmJlamZwamhoaGNeWmtubn19bG5zdHVuZW1kam5sbnBscmtjbWlrdWNudHyAdXFqamluamhlXF5fbYy0a2WAYlypqVtdYmVkaGNZXl9gW2RoZGpoZGZapp+Zo1dZXmFdr7Kvl5WfpKqjpqmnk3Bfe4aBd3Zzcnd+gHl6fH9+e35+g4qMjZKdnqOppqejlpKWlpSUkpCUiomHiYyEgK1tb2pmY7NkZaqmpGNramJhZmp7ipaYpqecnqCdnKCflpiAjZOIinhuarOvrJ2XjY6QX6WTam1/l1ppdH+KkpibnZ6dnZ6go6WnqazCimeHumh2gX1vwIjDj8iBlnlts8F4gWt3ccq4xniQp7tsf5Gap7lncYGUosZveISNl56jqq+zsaunqKOkq7W+wb+9ubKropWKfXTQt66eiHVvto50s5cohHLItq+0yn+WvW2Enq7BbGlpfdWZzYioynJ9gHFkro9wqoN5eHl4d4R2GHV1cm5tbGllX6qhnJmkuWBhvrllaHBvaYBfa2x2fnp/fHF9gUJEgX58dnB0cXV5c3JqZ1tNWmRqZ2dmcXl0cGdxbm5qZGtla3Fwfnt7enh2cnd4d3yDdHNubm1samprbm1waWJcdWFtdXF0cG9xcnh6eXl9gnp5c2xqcW5wdHh3c29ydXdzZVVaY3N1am1paGRjZWNnamhnaIBrbmdsbXR1enZ3dnJvcXR9hIiMiIOChYuLhH9+go+am5SMhIGFfJhofXlzdW1uc25zcGxtZ2RpbGllYWNwe3p0YVpwSl5oZ2psbWtqamtxcW5xcm9WQ11xcXFzcXR2eXV1cXBra2lqZ2VoaGdnZ2NkZGRmZ2dmZGVnYU9AdkVJSoBMTk5MTU1KSUhLSUJDTF9pam1uaWhpamhnZWhpaWlnaGhramxrZWFkZ21tZ2VoZmNoamGIVm1vb3F0dHNwbW5scHFvamtraWhmamdlZ2loZ2hpZ2ZkY2RpbWVoa2ljY2VlZWZoZmZpZmFeZGBeXmRjZGRiY2RlY2NlZ2ZpZ2hiZIBiY2FdXWJgXV1dXFtbS05GSEpKS0uJh4dEQ4JDQoVEhoaIR0hISUpJRkZFhIqChIaJSImFf4OEjo+PkYyGiYKCgX56enp4e3p5fHx6gEJ9fX54e3p9fHt7gH16d3t3fEA/QD52P39zcXI5OmxafFdRVltbWldbXleZeXuOUFBRVwxXU1dTVFRYXlpZXFeEWU9YVldWV1NRTlBVVlVZVlNVWFRXVVZWV1hVVFBHgoWOTVBoglVUV1leWllXXV9eWV1aWVpZV1pUTlVWWFVYU1JXVVZRSlVSUlhZVlZSUVJThEaAQ0VJSUhMSkpLR0ZLTU5LSkhBRUZHR0A8P0RDRkJGS0hQSUhHRkI/QU5VVGFiVlhcXF5XUFVNUlVVV1dUW1ROVVNUXE5XW2NmXFlSUlJXVFNQSElKVWyJUkxJRHl3QENHSkhLRj1BQUI/R0pGS0lFRz50cW5xPD1CR0F4fHpranGAd3x1d3l5aU5CVlxXUE1KR0hOT0pNUFNTUFNSU1lZW15oaWhnZWloYWBkYV9hXVtcV1hWVlhTU3lPUE9NSn9JS3dxa0VMSkE/Q0RNVVxcZmZfYGBfXF5dWV1VWFFTTUhIfH5/dnRwc3Zan6mds9Lwi5ujq6+zub2/v8HCw8THx8h/ycrgj15yjE9ZXlxPi2qfdahjcVRNg4pSWUpVT5SNmGZ8laZkcn2KlJ9WYW99iKReZm92foaKkZWZmpaXmZiZn6OmqKWinZiUiH50aWKwnY6BbWJbk3FdlX1tX5yOi5CYXGuHTFtteIRISUpapnahbIWdWmRmWlOZhXG/p6CgoIafGKCgnp2enZyWiuzUw7OvuFxdtK9eX2JfV4t/goC/fwF+rH8Bfqx/AX6cf7mAAX+2gAF/2oABf4aADX9/f4CAf4CAf4B/f3+JgIZ/AYCbfwGAkX+EgAJ/gIR/BYCAf39/ioCEf7CAB39/f4CAf3+tgAF/qYADf35+q3+CfoR/gn6Uf4R+hX++foV/Bn5/f35+fp1/iX6GfZR+BH+AgICFgROAgH9/fn59fX18fH19fXx8e3t7hHyGfYZ+nn+HfoN9hHyFe4N8hX2FfgV/f4CAgIWBg4CUf4Z+BH9/fn6FfwICBABP4urpgPns6u/6g4H3+PDWzNTXys7Fxci3m4SbsbayysDOycTZ1cvAyMWxusG1u7GyscLJxcXCyNHOzMm/wbWuqrCrqquqraappJ+R3JeipoSxgLC0v7u9wru6vLmztLOwubaytbu3uby+uLm/w7mxuLGstbuxvsCmyPOapqunp6+tqaqqrrO2u7y7rq2qrrG1u8LHyMnJzMzOycrJztXR0cvHwsrRvNi6wbfAwL20sa+xt7W5vreyr7Szp5qu4o2wy9DR1dbS1NfV0s3Kyc3Q0c/PLtHSsYibx9PU09TY1tLS0c/S0M7Mzs3P0NDOy8vOzszMzMrIupuLiaGwr7Gwrq2Er4Cur62qq6utr6uTi5Sju7/GxsLAu7u7v7q+w8K7ur+6vLu5wb68w7/AwL64uZb7s8HAwcHBwL+9vLm8uru0urq7uLa3tba1tra0srWwsbS3srGxsbCxsK2tra+vrq6vrKyqq6qpqqmoqKmnp6ako6SmpqempqenpKWlpKKjop6ZmICeoJyZnZeVl42ChYH+gISEgYGA/fz7/vr5/YCA/v6B/P6Agf/19vHw9fn4gPv19vf18+7v+Pr7+fX08vLy7u719/P49Pb3+f78/YD58vz9+/v6+vX08fn8+oCEhIGA+vmBgv+A9/2D8ua4w4mblZaW59XUhZWSlp6bm5qbl5iUlAWdoaCdn4SdgJycnp2Ylp6dmJKcm5yfnJ2bnKCdnqCclpiWmp2loZiblc+flqOhoKWipaeopKelq6irpqqpqKmoqqKoqKyppaOjoqSloqCnqKioqq6urqykn6rjl5eZmZWYnKGfn5+coKemo6OioaOgn5mdmZqVmZiVl6KjoKaWnpyTmpewu4qMgJabraqkoJ6fn5qYmJWOk5WZo6GjppWRoJmhlpSdoqeyqI+Rl4eAg4r/h5PA0P2BgoGFiIaHlZuVlZKUkpWTk4yJ/O7f4uyDho6D+PqF/IKE/4Hu4e/w9Oz25+3q4te2pLTP09LZ5d/e2cvKvr/Bure6wMTG2t3X1OPs8+zo3uLegNzt6e7o3t/lzLzli4uMjpiNhIWMhObc4uaEjJOKk5mUl4+XlKezxtfh3NHR1tzc1dbMzsfKv6ePiov+47ihtLConoXPjniMtWt9ipijpquzub7CxcfHycfJzNDV2OKP0rDfiKe00ce2lYDLqqD2oMyb4sK8sJ6fmI+H/eHKw7KWTI/+6OPf4ufj7oP45vT+hv/06Nnc4u/n5dfa2+js4trj6Ov4h4+bprLG3fiImKm00dvMv8PI0feU37D6osXtiJmpsKWZhOW+nfW3lIuEiSKIiIaGhoWDgX97d3Rva2zNuq2vzO/5/YKMiYmYmpeToLPSgK6zrmG8sbC0u2Nivb+6pJmfpZqbkpKThnBdcH+DgZWMmJGMoJ6WjJKPfoiRhIyDf4GQl5GSkJSemZmZj5CIhIGGgoGDgoR9gHx5a5pyen2Hh4WCgoWOjY2RjIuNjIaKiIWNiYWIjouOjpCKi5GWjoaIgX+HjIOPknyPr3B6fnx8gISCgIB/hYuMkJCPiIWBhIiOkZeampuanZ6em5yanKKfoJmVkJmejJmHjYWKioiBgH1/gYCEh4J+e397c2t7n2J5i4+RlZWSlZiYk4+Ni46Rk5GQlJV9Xm2NlpaVlZmXlJSRjpKPi4mKiIqKiomHiIyNjY2PjYuBal1aa3VydHNygHJzcnFwb3BubW1sb3FyYFxmcYGEi4mGhH18fYJ+gIOCe3mAfH9+fISDgoeDg4SDfn9psICJiIiIiYmIh4WChIKCfoOEg4B/f3x7fH5/fnt/ent8gH16enx7enp4eHl6e3x7fXl3dXd2dXd3dnZ2dXVzcnFxdHR1c3J1d3N0dHNygHNyb2ppbXFubG9qZ2hjVFpWq1ZaWldWVKamp6mlpqpVVquqVampVVetpKGdnqOpqVmpoqSlpqSioqmsrqmlo6GgnpqboqWfo52foaGop6VToZumqKelpaWjoZulpqFTV1ZUVKOgVVSgUpyfVJWOcoZhbmlpaKKZk1xmZGhwbW1qC2toamdlbXFvbG9thGyAa25taWdubWlkbGprbmxtbGxvbG5wa2dpZ2ptc29pbGiTb2lycG9zcHN2dnJ0cnZ0eHN2dXR1dHRtdHR5dnJxcXBxcG5scXNzc3R4eHh2bmxzlmNjZWVgY2dtaWlmYmlycGxtb2xraGhjZWBgXV9fYWJqbWxwYmlnYGdldHtbYWiAbIB9eXZ1dXVybm5rZWhsb3Z0dXdqZnFud29sc3d9hH1oa29iXF9kt2Jsh42sW1tZXV9eX2luZ2dmZ2ZoZmdiYK2kmpufWFxgWairXKpZW7JYpJqmpqigp5ugoJuUf254io2KjZaRko6Fg3h7e3Vzd3t9fouMiomUm6CbmpeXkZKAn5mdmpeXmoiBp2ZnaWlwZ19iZl+imqOmYWhuZmtwa2xmbmd2fYuZo52TlJibm5WXkpOOj4p4Z2NluaWEdIWBeXRjoXZoepVaa3V+hYmOkZacn6GhoqSipKanqq63b6KDqGZ9hp6bjXVloIZ+v4Cme7Wak4t9f316b9bDq6GYgX1x5NDMxcjIycpv1dXX32/V083Cws7SzMrFwb7HycbFycrM0nR8h4+frL/NcX+KkqmuopiboqnEeLqQzIKevG14hYqCdme0l33Fl3x4eHd2d3Z2dXV2c3JxbmxpZmJeXKyZjIWYuL6+X2hkaHV1cG12gqCAgIF5QoF2dnh6QEKDh4d5c3qAdnhwcHFmVEVUXV9dbmlxa2V3d3FpcW9ga3drdWpnaXZ8c3FscHl1eHhwc25tbHJvbnBub2hrZ2BVc1xlZm5saWhnaXR0dHVvb3R0bnFwbnRxb3F2cXR4em5wen5zbHFpZW91a3l7aWeEXWltaWmAcm9sbW12e3x+f3txcG1vc3p/homJiYiKiouHiIaJjYmJg397god0em1xaHFycGpsaWpraWxvbGlkZ2BYU2B7SFdlaGlvcG1vcnFua2hmamxubGxvcF5GUWlvb25ucnFubWtpbWlmY2NgYmNjY2BiZ2poaGtoZWBNQTxIT05OTUuASk1MTExKS0lHSEhKTE5DREtWZWdubWlmYWBgZV9iaGhhX2ZiZGRhbGpnbmhpbGtkaFaIZXBwcW9wcG5vbWxubW5obm5ta2hpZmZmZ2toY2djZGZqZmNkZGVmZWNkYWVnaGdqZ2ZjZGRjY2RjZGRiYmFgX19hYmRiY2ZmY2VlZGKAY2NeWllaXV1cYVhYWVRGSESJREdJRURDgoOGioiJjEZHi4pFiolFRomFh4WHiY2LSoqBgYaFhYSHj5CSjYWEgoKAeHmAgnp5cHd7en+Af0B6c35+f319fnt7dnx8dj1AQD49eHg/PHA5cnI9aWZUaFJfWltag393TFJPVV1YV1WAVlJSUVBZXFpWW1lYWFhZV1paVlRZVlBNVlNUVlVVVFVXVVZYUk9SUFJUWVVPUk9yV1FYVlVZVlldXFZYVlpXW1daWFdZV1ZQV1ddWlVTU1JTUU9NUlNTUlRYWFdVTU1UaUNDRUVAQ0dNSklFQEpTUUxMTkxKRkZCRUA+PD4/QEKASEtMT0JJRkBHRlJVRkxQU2RkYF5cXV1aV1ZUT1NUV15bXF5RTVVVXlZUWl5iamRRU1dNSElNjktUZmh9QUFAQkVDQ0tPSkpISklLSUpFRHpyaGdpPD5BPHJxO3E8PXk+dW52eHpzem5yb2xnWE5SYGJdXWFeW1hSVE1OUU9PTk6AT1JaW1dWWV9iX19dXltdZmJmZGFhY1degk5OUU5YT0ZHSkRyaG92SExRSUxPSEhCRT9HSVJbYV1XWFpaXFdbWFxXWllORkRGiHxmW2tqZ2lmvKmpyPKLoKeus7W5vMDDxsfHyMnJysvLzs7Se59nfElUW2RlXlBFb11Rgl+DX4yAdmthWVlXVFCThnRsZFhSmZWSlZyam6FXpqCls2GxrrKtpauusK2foJmgoZybnpydo1dXXmdxeoaUT1tjZ3d5dHFwcnuVXJFzn2d8klReaGpmYVeciHjQsJ6dnJ2dnp6fnp6fnZuamJiWlZGIhPLOq5idtbSxVV1YWmdlXVlgaHoEf39/gIV/goDAfwF+rX+Cfqx/AX6Xf/OAAX/bgAR/gIB/hoCHfwmAgH9/gH9/gICIfwGAnn8BgI5/hYAJf3+AgH+Af3+AhH+FgIN/t4CCf66Agn+ogAR/fn5+qX8Gfn9/fn5+k3+FfoR/CH5+f35/f35/uX6Kf4R+oX+JfoV9ln4Ef3+AgIiBB4CAgH9/fn6JfYd8iHsBfIR7AXyUe4h8jH0Hfn5/f4CAgIeBg4CXf4h+i38CAgQAgPjs9enygfv03urh3NjHvMbTxsbDyMSbhKHHubS4s8C4wsrDycu/tbyuqba7ubawqLTAzsXHw8jD0NPJv7q2s6qtpqWkqKufoaCR15CXmaq0raywsba9ur3Bvbu2sbattLWwq6mqsKyxtLe5usPJysG+ubOxubjBvJTkiZiep6ikgKSnqay0tbW1t7q6urOurq6wtbW6wcnNy8rIw8nR0M/V19PRz8vQyZyJxMTDu728tLGxucHDu7i5rJnxoMPW2tfT1NTS09HPycjP0tDOysrR0s7LysrLw5KHss3OzNHO0dXPzc7Nzs7LzMvMy8jIzM3MzcmvlY6Tpayrrq2trbGvgK2tr6ytrKqsra6sq6uqqainl4qNl7XDxMPGxsXDwby7vsDAv729v8DAwb7BvcC9p/Kcur29wMDAvr28v8G9u7y8vbu2tbS2t7i6uLezr7OqrbK0srOxsbCvra6vsa+vr6utra6urKuqqqmppqaooaakpKOmoZ+in6WjoJyhoqKggKKhnp6dnZybm5qamZmRiPeDgf+Cg/38+ff0+vb1+fr6+/r5+/j++/r28/Hu/Pv3+fT28fX28PP3+fj28/Pu8+/x+vr7+PXv9fHy8fL89Pby9vr3+vr09Pr9gIj+gIGBgIKAgP35g4OE/vmAgID5/P7AptDO34qVlZSVmp6fn56egJyYk5ednZ2enZ2cnp6doJ2amJeanZmVmJqUn6Ohmpudn5uenJWYm5mVm6afmoSqg6OloqWlqa2mpaesrqmrpaanqqmopKWtqK6oqKarrauoqKmlpaiqqaiqp6uurKXwl5OWmZ2emZ2fnZmWmJudnZ+en5yjo6OboaKfnpWYnKGagJailJidl5GSl4+2voKPl5icpa6wpaOgl6Chn5uinJqZloePkpudm6CioZqgnJujqJ2LjpWUh+/4hJS23o6LkJGXjo+Uk5GUkI343+nj3eLp8v+DhYiMiIz/goeEgvb47u/t5u3w6+70+Pbu9vXqv52dxtfY0ebl5NvX2M3Eu7W0gLGyuLzExMvW193b1tLNyMnY2NTbu8/7hYOIiIaRg/mNj/zy8vT1+4SEiICGhviFnpeaq5uXrbO5xN7s9O/s7uvf1t7c0cOsoZj30eLQu7zFwMevoo7hoYCPo2d+i5CZoqmvu7++v8HDxsrKzc/R2Nzf5OuJxZS81eCEo7uwi/v1gPHlzr2cjIPoyK6U/Mieif7x8uXc3djPzcvIysa/sJmI+f6Il7C9zMzO3uju8/3+9ezo7o6qzO2XoqfN84qgv9PlgY+grLe3q6COgOjFpIbWqpGLiYaFhYWEh4aEhYWEgn99eXVwa2fAsKiZnrXH2uP0/4qIjJKXlJiqssTK7Pv1gLuwt663Y7+4p7KrpqGRipOelJaSlZFvXnOShoCEf4iEjpWPk5WLg4V7e4eOiIaAeIKLnJGRj5SPm5+YkoyIiICCfHt8gIJ4enhtmGtzdIKJgoGCgoiOi46QjImHgoiCiImFf36AhYGFh4mOj5WbnJKPioWDiYmQiWykX21yeHp2gHh+gYWQjI2Ojo+PkIqEhYWIi42QlZuenZ2blpyioJ6jpKCem5uel3Rgj4+Nh4iJg4F/hYyMhYKDeWqmbomUmJeRk5SRlJKQioiOkZCOioqRlI+NjIyMiWdffpGRj5ORk5ePjY6NjY2JioiIh4SEio2Oj418Zl1fbG9ucG5vb3FyfXBxc25ubm1vcHFvbm5tbW1sYlldaX+HiIeKi4mFg399gIODgYGChIWFhYOFgoaFdZ5qgoOEhoaFhIWEhoeDgYGChIWAf31/gICCgYF9d3lydnp8fH99fHt6eXt6enp7e3h6d3l6d3Z2d3Z2dHN1b3NxcXFzb29wbnNxbm1vhXCAbW1tbGtra2pramtlWqRWVqpYWauqpaOjq6WipqenqKmpqKOnpqmmoqGeqqmmqqOkoKOinZ+pqaaloqGepZ2epKSkoZ+an5uhnJilnp6coqilpaSdm6OpU1yoVlZVVFVUVaWgU1NVpKBSU1SfoKN7b42PmWJoZ2Zna29wcG9tbGmAY2hrbGxtbWxrbmxrb21qaWlqbGlmaWtnb3Bva21ubmptbWVnamhma3Rual13XHJycHR0dnlzdHV5e3V3cXJ0d3VzcHF4dHlzdXN3eXd1dHRwcHN1cnN1c3V3dXCkY2BkZWlqZGlpZWJeX2FmaGppamlwcG1nbGtmZl9fY2pmYW2AZGRlYVpfZlx1eVJmaWltd32BeHd2bXR2dHB3cW9ual5kaG9vb3N3d3B0cnJ4fXVlaW5tY6u1YG2Am2ViZWZqZGRnZmVoZWSumaCcl56hp65aWlxfW16qV1pZVqiqoKCgnaSmoKSmp6ehqaehg2hmgoyNiZeXlo+Pj4h+enVzcnKAdnh9fIKLi5GQj4yGgoONjImRfZC3YmBlZWFqX7RoaLSsqq20tmFiZ11iYrNgdG9vfG5oenyDip6oraqnqKiimp+elIt6c2+ylaiaiIqRjZSBem6tfmZ1ilZrdHuAh4yQmZydnJ6goaOkqaqrrrG1ub5rmHGSo61nf5GJcMnEwbl+qpp8cWq7pY55z6CAb83Bxr+5t7KsqaenpaOelH9x1N53gZGcp6WjsLjAw8vOxr68vnKLpsB8hImmxG+Bl6m4aHOAiZCRiH9xZricgmyui3l3d3d2dXNydHV0dHRzcnBua2diXlqllot8fIuWo6WzvWlpam1wbXGBhpOWsb+6gH90fHV6QX17c399endqZm97cnRxc29VRlZuYlxeWmBeaG5qa21nYmRgX2tybWxkXGRrfXNuanBsd3l1c3Jxc21vaWdnamtiZmNYc1heXWpwZ2Zqam51cnR3c3Fua29pcHBsZ2Vnb2lucXJ2dnyAgXl3cm5vc3J5c1uAT1tfZGViUmNobHJ9e3t7eXx8fXVwb3B0eXl/hYqLi4mHhImQjouPj4uIhoSIfF5OdnZybW5va2trb3Z0bmlrYVN7UGVudHBqbW5qbWtqZWNpa2tpZmZtbmmEZoBnSkVea2tpbW1vcmlmaGZnZ2FjYmFgXl9maWpqa11MREJHSEdKSUtJS0tKTE9KS0tJS0tMS0xLSkhISEI9R1Bka2ppb3NwamdjYGNpaGZnaWlpaGpoa2dra2CFVGZoam5vbGtsamxtaWlrbG9uaGdmamxrbGpqZmBiW15kZmZpZ4BpZmVkZmZnZGZoZGdkZmdlZWZmZWNhYWNdX11eXmBcW15aYWBdW19hYWBgYF1fXVtbXFxbW1tcVkiCRUWJR0qLh4WDg4yJh4iKiYiLi4mFiouOi4iJhoyMiYeFhHuAg36DjY+JhYKBfIV+fYKAfn58d3tzd3p1fXV0dXuCf35/dghzen4+RnxAQYQ/IkF7dD09P3l3PTw8cHBwVVVxcntQVVRUVlhaWVpZVlRTT1OGWGVXWldVWVhWVFNVVVFQU1VPWFhXU1VWVlFVVExPUlBOU1pUUkpeSVhZVllZW15YWVteYFpcVlVXW1hWU1RbVltXWFZaW1lWVVVRUFNVUlJUUVNVUk5zRkNHRklKRklJRkI/QEBGSIRKgFBOTEhKSUREPz5CSUZCSkREQ0A7QEk/S1I9TlBQUlphZmBfXVZeXVpYXllXVVJITE5TU1NYXFxXXFpZXmNbTlBVVEyCjUtVYnZKR0tLT0lITEtKTUxKf21zb2tvb3J2PT0+QD5Bczg8PDtxdHBxcG51eHJ0d3h3c3d2cFxIRlddgF9bYmJhXFteWVRRTEtJSUtKTExPVFJVVVRST1BSWFdVXU9ojUxKTU5LUEiFTU2BeHV6goNHR0pFSUl/Q1FKSFBDPUhJS09dYmZjYWFmYVtfYFtYTUhHe2l7dGpudnR7a2hpuqeivd2GprK0uLm6vcHCwsPFx8nKysvMztPV1dbYgHWOWmx6gklSXVxVpaKckYZ9bmJZnIZzX594XlCUhoaBfXx6eHJwbm5vbWdhXLC0X2BobXFwcnqAgIOKiYN9fH1OYnqPW2Rnf5NTYnB9iE1WYGdtbWhiWlOajHluwaibm5uZmZqZmZubmpubnJyamZiVkIuF8dLAoZKZnJqUpa1gDV5eXF5ZXWlqb2t9hX+FfwGAxH8Bfq5/AX7Bf/WAAX/dgAd/f4CAf4CAxH8DgIB/h4AKf3+AgIB/f4CAgIh/uYABf6+Agn+ogAF/hH6pfwZ+fn9/fn6Nf4l+hn8BfoR/un6HfwN+f3+GfoZ/AX6bf4x+hX2ZfoJ/hICFgYmAhH+EfpF9gnyRfYR+hX+FgIqBhICYf4t+jn8CAgQAgPvu/f360s3KyMrT1cTG0sPBwsW6iOSBpbW3w8a2tLu7y8XAvLu4r7q5s6+wtbKut7rCyszFwcbHxci+trKyrrCsqKGlp6mfnYnOj52Qnaaqsq6sr7GwtbKyt7Gsp6mrsa2npKqsqKazsre+tbW7wsHAvrW3rau8voGEnJidn6mogKSirbSxrLexrKq6u7u9wr63trWysrjAxsvOy87NzM7T1tbX1NHW0r+IqMS/wMXCtbG2uL2/u7y8q8qQz9PS0NDT2NjT0c/OzMfHyszJyMrMzc/Ny8zKycWqgZvAzMvLysvLy83Oz83LzMvKy8zNyLygkoyetLe4tbCur6yrq6utgK2rra6rqamsraytraytqaqoqqysp4yFip63vbu7vb+/wMC/vsPDwb7Bw8G9vbSGhLW7u729vbu6ubq6uru3tbm5trK0s7a3tLK1tbKzs7Ovr7OzsLCtra6urK6sqKyura2rqaqrrKyoqqimp6WkoqGjoaGhn56eoKOgnqCioqKhgKChnp6emp6dnJybmp2Xpt6EgoH+/fz6//v9/v2A/fr29ff1+PX18/Xs7/Xw+PPy8vT47+3x7vPy8vPw7vDz/Pz29/Dv8vPy9O3s9ez07Pf59/j08vT3+Pz8gYCAgP2B//+Bg4CCg4OC/YCDh4Tux8XUgpaTlpmYlJecoZ+dm5+hgKCYmJyhnJ6bn56doqShnJqanqCfnpabl5ufn56ho6Cgm5ygnpuXnZqVoJuLt9mdoKKinqCio6Wmqqepq6ysop+qqq2pp6imqKmqqKiuq6qppqanqaerqKisqq2tqZGEhpaTm52amZuhop+ampaYoaedm52joJyfoaWelp+am5yegJ6jnpOXoJuaibGzsIP4lJWYmpqnp6uhoaOgoaCZpKWilJSPkJienpKan6KdoaKgoJqZlpGPhJOYgoyLsdWSmpedloru9/PY3+eA8u3t7+br7IGHj4qMjpWIjIaLgIOHhof97eDf4O747+/x8ebs8YLxtqC3uc/d4urp49nX0sjFgLqwpqmzv7+7wsG+xbfAuLOvqZ+6gY2IioeEgoyJhIKHgJCN+9/o9YGKgO2B7PbykI+PjZqss8TV08W4tLK5y8zq8O3s3dzZ042SlZSLgoDx6dzR3MOvkoHbvb64oqNdZ2+ElZqfpKits7m+wMHGytHT1NbX2tne4ePo7O37jqq7TczngJGPkZ+2z+T/i5KUlZCPjoqC7N7c2dXSx8a3r5uSiImWqbTDzNfh7PT/hYqOkpigp6+/wr/O2M3IwrKmlI6C79W0mfHEoo6KiIeGhYcChYOEgih/fX15dnJtamW8rqWZkZ6YudHs7/Xv7YCEiJKYnaW3utrp6/SEgID9gLqqt7m5mJaVlJWfn5GSn5GQkJOLY6BdeIODj5GCgISHmZWQjYuIfoiKh4OEh4J9iIiMlJSQjZGUj5aPjIeGhIeDfnh8fn93dmWUbHVqd4CBhX58gIaFiYeHiIOAfICDioN8eoCBf32Hho2UjY2RlpWUkYaHf32KjFpgcW1xcn18gHl5f4iHhJCKhIKSk5OTl5WQjY2Mi4+Ump6fnZ+enZ6kpaWloaCkn5BieI6KjY+Lg4CDhYmKhYOFd49lkZWVlJWVmZeTkI+OjYmIi4yJiIqMjJGPjI2Kiod0WW2HkI6OjY2OjI2Ojo2Mj42KiYmJh4BtYl1odnl7eHJvcG1tbm5tOW5tb3JubG1ubmxucHFybm5sbW9vbVpWXG19gn+AgoODhISEgoiJiIWIioeDg35dWX6CgYODg4GAf4SBgH59gIF9e318foF+fH9+fXt6enl5e3t6eXh5e3p4e3Zydnh4enh1dXd4dXJ3dXN0cnFwb3FvcG9ubW1tc3BtcHJycW9wcW1ub2xtbGtrampuaHOWV1dWqqysqquoqqyrVqyrp6empqmlpaSpo6CfnaigoKKipZ6bnpqgoqGenZudgJ6mp6KjnJqcnpyfmJefl5+XoaajpKKdn6CfpaVVVFNTpFWoqFVVU1VVVFWmU1ZaVpuAgZBaaWdoa2pmaGpwcG1rb3BvamptcWpta29ubHBzcWtqam5vbm1na2dsb25tcHFvb2prb21rZmxpZnBtYH+XbnBxcm1vcXJ0dHh2d3h6gHhwbHd4eXRzdHJ1dXZ0dHh3dnZzc3N1cnd0dHZ0dnZyYlhaZGJoaWdmZmpqZmJjXV5pcWpoaG1qZ2lqa2hhamVjY2RjbGpiZGpqaVVtbm5XsWtpam1teXl9dHR1c3V1cXh3dmtpZWZtcXBmbnN3c3Z2dHVvcG9raF9sb19nZXyUgGdtam9pX6SsqpGWn1qnoqKjnKGgWFthYGBiZ15hWVtVVltZWamil5aVnqehoqSlnaKlWqV6ZHZ5hpCTnJyXkZKQiYh6cWhqcnl4dn1+e4F4fXh2cm9thl9rZ2VjYl5mZGJeZF5pabeYo7BeZ16sYKy0sGtqaWZvfIGLmJSKgXt7gIGMiaaqq6mZnZiaY2VrbWVgX7Kso5yijX9rX6OPj41/ilBZXW99gYaKjI+UmJuen6KkqayvsLGysbW2tri7vspwiJeku2l2dXeCk6a5znB1d3d1dHJuacK3t7Swrqekl5GCfHR2f42VoKivt7/EzGtvcnd7gYaMmJyZo6min5mMQ4J0bma+qI95xaCFd3Z2dXV1dHRzc3Nyc3NycW5tbmxoZWJeWaiZjX91fXSMmbCwtbCvX19ja3F1e4qKp7GwtmRgYL2Admh0dXpkZ2psbXZ2bG97bm1ub2hJc0ZaX19rblxaXWBxcW5ra2lia29uampvaGFoZ2pwcnBrb3FvdXBwb29vcW5qZWhqa2JhUHFXYldgZ2hsZWZobWtubW5waWhmZ2lwamVhZ2dlZG5wdXt1dnp+fHp4cHBoZXV2SVBdW1xdaGYbZWZqdXZzf3lwboCBf4CGg315enl6fICGiouJhIxlk5SSko6Njod2UGR3c3Z3cmlpa21ycm1qbF5xTGxubm1ucHVzbWtpaGdjYmVnZGVmZ2dqaWdmYmNjVUJTZGpoaGhnaGRoaWtnZGloZmVlZWNdT0VARlBRUk9KR0hFR0hHR0hISkyESgZLSUtLS0yESIBKSkk+P0ZTX2VjZGNnaGloZ2ZucG5qbm9raGhmSkVlZmRpaWpoaGZpaGhnZWRnaGNgZGNobGhlaWhmZGNjYmNmZmRjYWNmZmRmYV1iZGRmZGBiY2RjX2RjYGJfXV1bXF1fXlxeXl1iX1xeYWBfXF9gXF1eWlxbWVtZWV1aXnhFRlFFiIyLiI2Ki4uKRYuNjY6LiYuJiYeKgoCCf4Z8f4GAhHx3fXd9g4KAfXp4eoGAfHx4d3h9e3lzcHt0e3N4fHp7e3h4eXl9fD89PT54P3x+QECFPoB6PT5APWxcY3FHVVJUWFZQU1ZcW1ZTWVpZVFRWWVRWVFlZWV5eWlVUVFlaVlRPVFBVWFZUWFpYVlFTVlRSTlVRTVhTSWR3VldYV1NVVldZWV5aXFxdW1RQWltbV1dYVllYWVZXWllXV1NTU1VTV1RTVlRVVFFEQEJHRElJR0dISoBLRkJDPT9ITklJSU1JR0lISUdBSkRCQUJBSklDREdKSjxISUk9ilJQUVJSXF1hW1tdW1xbV15fXFJQTU1TVVVMU1dcWVtbWlxXWFdTUUpUWEpQT19uSk9NUU5GdX98Z2dxQXZxcHNtbmw7PkNCQ0VHQkE9PTk5PDs9dW9pZ2dwdYBwcHNzbXN0QXZURU5NWF5gZWdjYGJkXlpQSkJCR0xLRkpJR0pES0hHR0ZFX0hST1FOS0hPTElGSUJNToNtdoRHT0V+R32EgU1LSENHTU5UXlpSSkhHSlBNX2NkZFleW14+PkNKRkVGhIF6dH50bmRjvKyvuL/fg5OYq7y8uru7vWS/w8bGxsrO0dLS0tPU0tPU2Nzd3+Z8kJ+xwGp2fIOMlKCps11fX15bWVdVUpuUlJCOjomFfXl1dHJydHp9gIOGio+Ul05SVFZZXF9kbG5tcHNwbWpkX1pVT5aMf3LKs6KYmZqahZmEmCuXl5eWlpeVk5GOjIb238y1m5aFkI+fnZ+YlVJQUlldYGNuaXuBendBPz95lX8BfrN/AX7wf/+A1YAFf3+AgICJfwGAvH+EgAR/gH9/h4ABf4SAhH+7gIJ/sIABf6iAAX+Gfq1/gn6Gf4Z+AX+HfpB/jn4Bf6R+j3+Efgh/f39+f35+fqB/iX6GfZ9+hX+JgImBmICVgYSAnH+Ofo1/BICAgH8CAgQAgOf4/ebJy8vJwsfCtLS+xcrCqIaMp7qyn5GEmLC2tLa0vLurrq+2u7i3rau2urCrt7THyb/Fxsq5vriwqamno52foaChpKmfhtiMnJKVl5akp6eqrbizsq+usauloaqnqaWfoJ2npKSlrrm9vL68ur67t7q0tLq/vuSSnaSdoKOne52YmqOnqauur62ttLG7vr21tra0s7OxtLfAxs3NycnP1tPX2dnU1cevj8G+vsbHvLewsLe7t7m7u6fkpc7Pzs3MzsvJxsjHyMjHwsbGxsvJysjEyMnJysnHw5TxscvGycnMzsvLzMvLysrHuaOVkJWlr66vr66wtLSzsYSydLCvr6+xsK6srq6zr62vraurqqmpqaakpKmfi4SMmLG6ubm7v7++vby6u7i5t5n/rLu7vcG9ubq6tre2tba7uri5uLe1tre2tLS1srCzs7OwsrCvsLGvrq+vqqyura+vr6ypp6anpamnpaKkpqWlpKSkoaGihKGAoKChn6Cen6KenJ2emZmanJydnJqbmpfMw4eEhP769/eB+fv5+vr3+fj3+/nx7+7z+/bz8u/x8vby9Pf0+vjz8/Py8vb69vf09PD49PLy9PLw7+7m7fD2+fzy7/T3+/b0+oL5+/33+P3+/P77hoKChYOD8M7K0fCPk5yYlpeamJmAmp+fnZqeoaKgnqKgpJqdnJqbmpqcn6KbmJ2ioJydl52bn6ChoZ+goJ+fnZ+hm5qfnJ6XzsKZpaWkoaWmpaWjqKenqKSkpqemo6Smqqunqaqmp6eqqq2tqKuuqqmwrKusq6uosrGos9GamZmenqOhl5uaop6akpaho6CgnaKmo6KAopyhn52cmp6praykmJOJkJezrqqujdqXnJqbnZSZqrSgoZ+em6Sbo6Sgm5ucm5iZnqScl6Wdnp6jnp+bk5aSjoaNkpiR0bv59Nrp+4SGhouIgYHygomCh4L/+v6HiIeLiImGhImHg4KIh4WAgIP3//Tp7oD78vTz9/n87sicntKA193p1c3JyMjQwMTOxMDAv7vFx9PKurm4lKDK7vP/9ueBjYGP/oOD+/2B++Xv+4WC+Y2K+4P39/2Ihf+IkZaksLrKz77Jx83qzLXBztDc1cqQp7y4qK2gmpWalon12KyRgfeEl6itnpSA1bOrXGpodX2Fkpeco62yuMDIzc/Q09eA2Nnb3eDi5ebo6ern5OTo6Onq7PmGk6O2x9Xs/YOJj5aeoKOnpqWimpicoam0xcrc2NLX1NzSzMG8rrWxlon76NXDq5qQiYeFg4SEhYWFg4KCgoOCgoGBgoGBf3x6d3Nwa2e/sKKRg3x0goykx+T69P39+/6GgY2KkaGmtLzJ1ewJgIKFg4CA/OnxgKi1uaeTlZaSjY+NgoWPk5mSe19fcH56bWRdcIGEg4aDjY99foKHiomJgoCIjIF6hH2Qk4uSlJaHjImEf399e3d4enp5en10X5ZpdGxvcnF8fHyAgomFhYKBhX95eIF/gHx2eHZ+ent9hI2Rj5GRj5KPio6HhYqOjqFtc3pyc3l+RXZ0dHp+f4KFh4SDi4eUmJaOjo2Mi4uIio6UmZ+em5uhpqKmqKejpJaCZ46Li5CQiIN/foOGgoKDhHKidpSUlJGQkY2LiISKG4mFiIiIjYqKiYSJi4uMiomHZqZ9j4eLi4+Rj4SOgI2NjIFwZWFibnVzc3Nyc3Z3dXJxcnNycW9wcHJ0cnBxcXd0cXVycHBubm1tamhpbWdZWF5oeX9+fYGFhoSDgYCCf39/a6x5goGEh4N+foB9gH9+f4ODgYF/f35/gH57fX98e359e3l6eHh5e3l5fHx3eHp4ent7eHZ2c3JwdHJxA25wc4VygHBwcG5ucG9vcHFwcG1vcm9ubW5paWpsbG1samppaI+EW1lZqqmlpVanpqOlpKOlpqapp6Ofm52loZ2dmp6fpKCgo6Opp6Cgn6CgoqWjopyhn6WhnqCjn5uYl5GZnaGlpZ2anqCfmpmhVaOjo52gpqekp6RXVFRWVFSchImPpWJkgGxpZ2lraGlrcHBsaW5xcm9scG5ya21samtramttcGppbnJwbGtnbGptbm9wcHBvbW1sbnBra25rbWiOh2x0dHNwdHRzc3Fzc3R2cXJyc3NxcnN2d3V2d3R0dXZ2eHlzd3p1dHx4dnd1dXJ7enR8i2dmZWtqbmtmaGdqZ2JdYGlsgGhmY2lta2xrZWlnZ2dmaHB1dW9nY11iXW1qZWdXm2xwbW9vaG16hHR0cnJveHB3dnJvb3Fwbm1vdG5reXJzc3hzdXJrbmxoYmhscGuVf6+umaOvW11eYl9ZWJtVXVhdWq6prF1eW15eYF5aXltYWFpZV1VVV6Ssop2hWKynpaWrgK+zpopoZ46RlJ2PioaHhYt/goiEf39+en1+ioR4eHdgcZW0t7+0qWBqX2q6YF+6tV22oamyYV2tami3YbS2u2Vht2Jpa3Z+gZCWhY6LjaiMeoSSlJ2Xk2Z0iId4fXNvbHBsZLOdfmxfuGFufIF2b2WnjItOWlhkanJ7foOHjZKYgJ+kqKusra+xsLGztba4uLu8vby6ubu8vLy/yGx4hJOjscLPa3F2e4OFh4uMioiDgoSIjpOhpLKtqa6nraelmZeMj4x3bsm5qZuKf3l0cnJxcnFxcnJwcG9wcHFycXBxcG9ubGtqZ2VhXK2djn5vaWBnan2Zsr6yuLq4uWNeaGdsEHl8hIuUnrFhYGRjYGC8qq+AaHJ5cGZqamhlZ2dhY21xdnBdRUBKVFJIQ0FQXWFgYmBrb19hZGltbG5paW5yZFpiWmtuaHBvcWVqamhmaGdlYmNkYmRnaV5NdVVhWVtcWmZlZWpocGpqaGhuamNhaGVmZF9hXWZhY2ZweXx7e3d2enZwc29wdnp7gV1ibGFkZmqAY2BhZ2loa29xbW15dH6Eg317enp4d3R2eIGGjIqJipCVkJKUlpGRgWxVdnRxdXZybmlna25qaGprXH1YbWxsamprZ2RhY2NlZWNfYGFjaWZmZGBlaGZmZGJkTXlda2NkZmpraWhqaWhpaWpfUUhBQElOS0tLSkpNTk1JSUpKSkmASElKTExMSktMUk9NUE1LTElJSElHRkZIRDw+SFFfZGNhZWlqaWdnZWdkZGdWglxnZ2hrZmNjZmRnZ2Vna2tqaWZmZmdoZmNmamdkZWZkYWNhYWFiYmNmaGFiZWJkZmZjYWBgX15gXl5bXF9eXl5fYV1dXFtcYGBgX19dXlxeYVyAW1paV1dZWVxbW1haWVd5Z0dFRoSFgn9EgoKAg4aDh4iJjomDfXZ6gHp3d3R5f4WAgIOChoN9eXp9gH96enZzfX2DfXx5e3t2c3FrdXl7fH13dHl7e3Zze0F3dnRwc3l7eX58QDw9Pz09cWFnboRQUlpUU1ZWVFRVWFlWUllcW1qAV1tWW1NYV1VWVVRVVVdSU1peWVRVUFVTVlVWV1ZWVFNVVVZXU1RYVVJPbmlVW1taV1pZWFhWWFhZW1VVVlZWU1RVWFlXWltYWFdaWFpbVVlaVVVdWFZWVVRSW1lVWWRJSEZLS05LSEhHSEVCPkFISkVDQUdLSUpKRkhHR0dGR05gUFFNR0VARUJIRUFDPXZTV1VWVk9SXWldXFtZVlxXXFxYVlVWVVNTVVlVUV5XWFlcWltYUVVUUUxQU1hTcV19f3F6g0NFRUhFPz5nOkE+Qj97dXg/QT9CQUJBP0E+PDw8hDuAPHJ5cWpvPnp0cXBzdnp1X0hEXl9hZF9eXl5dYVdZX1hSU1NNUlJWTkVGRzpPdI2JkoyFTVRKUpBLSYqGRYJvcntGRX5PTYVIhIWKSEV+Q0dFTFFSWVpPVFJRYlFFS1NXXFdZPkZVVk5RTEpJUE5Jhn1tY2CzXWlvcWxoZsa/3IBZkY2gpaq5u7i9wcbKzM7Pz9DS0tPT1NbZ29zc39/g3Nrb3t/g4eXwgImUoK22xNBtcXR6fYGChYeHiIeIiomKi5KSk4+LjIiIiIV/eXZzcGtlvLOso5uVkZCEkQSTlZaVhZQ2lZWWlZaWlZSTkpKQjouG/+rRtaOWhoeAjKCqqpyhop+iU05VUlZhYmdnam98QUBBPzw8dmlqyH8Bfq9/AX7Cf6CAAX/RgAF/4IAFf3+AgICEfwGAwH8BgIp/hoCFf7yAgn+xgIJ/p4ABf4d+rn+Hfod/AX6Ff4N+kn+FfgF/rH6EfwZ+f39+fn+Efg1/f35/f35/fn5+f39+oX+FfgF9h36Dfah+iH+igKR/kn6Mf4aAg38CAgQAgN3d1MTOzsjHwcG/wsXFuKCFla+wvMS5tr28uaSWiYqpr6+vrKm3sK+ur7C5sLjCvMbOzM3Fxrm9ubOpoqKjpKOcoKOin6KB0YGVk5iTmKKhp6yrqqusqqysqaGipKyooZ2eoaCfnqGlp6+3sbKysrSvs7OxvsO1r9CenZqjoqCfUJ+nppufpKipsbSwsbKxtLa3ubSssaqvsbGyuLq9xczO09vZ3ujl4ea/r6XGyMzMw7+6trm8tbCztLKihrXLy8nIy8zNysfGx8bJycnHw8LGhMUNw8PHx8nIx72HhL7IyYXKgM29o5eLkq2zsLGurbOusLCwrq+srrKxsrGysrCvr6ytr6+wr6+ura6pqKelpKWko6OgoKKioZmFhIKMq7zCw8TEwr22ooCfvb29v8K6ubq3tbK3uru5uLq7uLeztra3uLa0tLazsKystLWwsbSzsbGwra2tr7Cuq6ypp6ioqqqogKekqKempaWlqKKioaGko6OjoqOioKChoJ6dnJ6cm6Ccl5ibnJmZmJj2qYSB//v9+/r29fj2+/j39/T09O/u8vz8/fHs9vH28vTs+Pf5+Pjy8vP37O/v8vLu7e/x7u7s6e/r8vDy+Pb9/Pj19vr6/PP3/IH9/vr9+ICCgoOEhYDlK83Qz/eJl5WUlJmXmZaVnJueoJyfn52ZoKKgoKGhnJ+cm5+goZ2fn6CenJ+EnICanJ2ko5+im5+enpycn5yWoKKb3r+NoJ+go6OloaOmo6qpqKenqKekp6OhpqmpqKSoqKalo6Ssqqioq6msrautrKivqqmqqvKZmpqZnqCioJ2dm52dpJ6fpKObnZuZo6SaoqCkpqKlnJ6gmaGppZyJ/Ya9uaq7r5GskZWhm56dpYCqubSqqqyspaOvo6Gfo6afjZmhqJylqKGinJSclZuXko2bnoiEiY2R5JeAgoT+/IiH/YeCi4OBgYWC/4KN/v6Kh4KJjIeMioiKg4SFiIDp9fv5gfrz+/3p9uzn6PLw7+XSn5Gzycm5xtLJycrL0s/K0NrO1tjZ3L+74ouH+vKFgoCEjYCC9PKEhu/U3u367/Pw8vX/jIX/gPiLk4SNjIaGj5Woq7+7tbe51tjQydPZ19jPn8exqaWbmJWIkKCim4z6zrSkmpqjqaSempGRkY7w1s3MppGitWBseICPmp2hqbC3wcjLztLW2dzf4OHi4eLi4eHh393d3t3d3Nza2dze3mvc3d3f3uDi5OXm5+fl5OXn6Ovt8PLw8Pb39vf293t8e3x9fH19fn5+f3+Af31+f4GAfn59e3l2dXNvbWdhuqudj39xzc3J15G1zOv3/YSBgPuKiIeUjI+On7e/0dTx5IGKh4KEhIyGg/7t5ICjopqLlZqVkoyNjI6Tk4d0XGV1dX6De3l9fn5vaGFke3+BgH5+ioKChIOFjoOJkImRmZaZk5OHjImFfnl6enx6dHl8e3d6X5Bgb21zbG95en6CgICAgX+Af314enl/fnl1dnl3dXV8f36HkImIiIiLg4aGg42RiIOSeHZze3t4doB3f35ydnp+foaIg4aKiYyNjpCLhYeAh4iJiYyPkpeeoaWrqa2zsKyyj4F5lJKVlo6Lh4SHiYJ+f4B8bWB+jo+NjI+Qko+MioyLjIuLiIWEh4aFhIWFhYqKjY6Qh15chYyLjI2Ojo6QhXBlXmFyd3R0cnF1cnNzc3JzcHBzcXNyc3lzcnJycHFycnJzdXNxcW5ubWtqa2ppaGZlZ2loYlZWWWB1gYeLi4qKhoBxV22EhYWHiYGAgX99e3+ChIKBgoOBgHyAfn9/fnx+gH97d3Z8e3l7fHt6e3x6eHh5e3p3eHV0dnV2d3VzcXRyc3N1dXVwcG9ucXFyc3FyhHGAcG5tbWxsbXBsaWptb2xqaWqwcVhXq6msqKajoaShpaKjpKOjoZuYmqWlppyYoJqhm5+ZpKKmp6afn6Ckm5+go6KenZyclpiYl5eTmJaaoaCkpqSfoaOho5mcoVOlqKOnolRVVVZWVlOShY2Mq15pZmZmamlsZ2dsam1vbW9vbmo6cXFvbm9wbG9sbG9wcWxsbW1sbG1ra2xsaWttcnFucGttbW1qam5sZ29vapiEY3FxcHNyc3BzdXJ4doR1gHNxdXFvdHZ2dnJ1dnNzcXF4dXN0eHZ3d3Z4d3N5c3N1dqplaGlnamtsamhqaWlma2doa2pkZWNhaWxiaGhtb2xvaWlpZGxybmhcq1RzcGRwaFl7aGtzbnJwdXmHhHx9fHt3d4J2c3J3eHNmb3V5bnd5dXZxanJtcW9rZ3N0Y19jgGdrpmlbW12ysWFerl1XW1RVV1pYrVdgra9gXlhfYl1hXl1eWVpaXFedo6ijVaWgqKecpZ+amqSjo5qMamF3h4V5gouDhIaHjIuJjJGGjI6Sln+AoWlluLFlYmJpXmGzs2FjspidrLmssKiqrbdqY7letWZtYGZnYF5mand6iIOBSIGCmJqSjJWZlpeSa4h6dHRua2ldYHBzcWW3lYJ1cHF6fnp1cGdrameyo5Wbg3KEl1JbZmx7h4iJjZSYnqOkp6uvsLK1tbe4uIW3eLa0tLWzsrOzsrKztLW0tLS2t7i5uru9vb28u7y+wMLDxcfFxszOz8/P0GhpaWlqamtsbW5ub25ubmxsbW9ubGxrampoZ2ZjYVxYp5qLfG1gqKKepW2InLK4uWBcXbZlYmJvZmlpd4yQnZuzp2BnZF9gYWhjYr2tqSFvcW5ja29tbGVnZ2hub2VWQkRNTFFWUU5QUVBIRkRIXmGEY0ZvZ2hpam1xZ2hsZmtzcHJvb2JpaGdjYmNkZWNdYmZkYGNJbU1eW19YWGBiZWhlY2RlY2ZnZV9iYGlnYF1eYV5eXmRoZ291hXKAam5ua3d8b2xnZ2Via2hlZGNsa19hY2RkcHFtcnd4ent9fXlzdm5zdXd3enyAho2OkJeWmqCdmJ56amN6eXt7c3BubXBya2dqa2ZQSF1paWdnamxuamhmZ2VlZWZjX2BkYmNhYWBeZGVoamtmR0VlZ2VlZmdoam1hTUM+P01PS0qASUlOS0xLS0pKR0hLSUpKSkxLS0xKSktMTU5QT05OSklIR0dHRkVFQ0NFRUVCPEBDS15pb3Jzcm9rZVlFV2ppaWptZWVoZWRiaGttamhqa2hnY2hnZ2hoZ2hqaGRgYGdoY2RlZWVmZWJgYmNkY2FiYF9iYGBhXmBeYF1eXl9fYl2AXVxZXV9hYl5fXl1dXl9dXFxZWVxgXFhaXV5aWVlal1ZEQoaGi4iDfX5+fISCgoOBgX55d3V/gH94cn12fnt+d4B+g4GAenh5fnR4d3l7e3t6eG9xc3FwaW1sb3l8fX56dnh7e3tyd3c8d313eHY/QD8/Pz07bGRwco9MV1BRVFZGVVdQUlhUVVhWV1dXVVtbWVhYWVZZVFRYWFdTVFRVVVZYVVNWVFJUVlpYVFdQVFRVUVFXUk1YWFJyZ09ZWFdZWFpVWFpWXIRagFlYVVhUUlZZWVlWWFlWVVNSWldVVlpWV1dVV1ZSWFJSU1V5S0lJSEtLS0lIS0pKREhESEpJRENBQEZHP0VFSk1LTUlKSkZLUEtGP3g7T0tDTEM7XE9SW1ZZV1teaGdhYmNhXFtlW1hXW11YTVVZXVNbXFlaVlBXU1dVUk5ZW01JgEtOU35PRENGhINJRn1DPT44OTo/PHM7Q3l4REI/QkRARENAQD0+PkA9bXNzbTtxb3Z3bXZycG1yc3NuZkpEUVxcUlpfWVtdXGBhX2JoXV9gX2JSVXhTUY+FTU1LU0hLiIVMTotzdoGLe3l2ent+TUiIRINNVEZJSEI/RUhOUFhTSFJPT1xcWFNYXFlZVD5MR0NHRkRFPD9MUlJMjX90amlnbnBua2tkZmZmu7iww7yy0vaElqWrv8nGwsXJys3Mz9DR0tPX2dnb2oTZAdqE2XXb29zc3tza3d7f3t7e4ODh4+Tm5+fn5eTn6uvt7u/w7u/19/j6+/6BgoOFhoeJiouMjY6Oj4+Oj5GTk5KSkZCQkZGOjYyHg/vo0rqii/Ply8V4h5Ghop9SUFCcVVFPWFNVUltoZ2xod20+Qj88OzxAPj14c3THfwF+sH8BfsJ//4DUgAR/f4CAxn8BgIV/h4CFf76Agn+ygIJ/p4CJfq9/Cn5+f39/fn5/f36IfwV+f39+fo9/hH4Bf6d+BH9/fn6GfwR+fn9/i34Ff39+f36nf49+iH3HfqB/hn6EfYZ+BH9/f36Of4mAg38CAgQAgNTPx8fSxs3EvcLDrZeLk6qurbvDube+xMa5vcrP17yjj4mHm6ato6KstK65u7fFyL7Fvrq1qbeytaSeoKCjpqOfoqWWk+rah5KYm5qkoqCkpaappqSkn6Kmnpujo6Oll5eiqaOclZynp6ispqOoqq2prK2ssLe2o8ufo52fr6yigJ+iqJ6Vl6OspKywtLW0tbK1ubq2tbCqrq+wr6yvt77CxsnN2Obp49XSo6OsxM3Ixb+5tri6uri1trW2o5bFy8fFysvLzMrKxsTEw8PDv8HBwsLDxsbGxMbCv7/AxbSDirrIxayZko2UpKmpqKqpqKmtr6+trqunqqmqrKytsK+sRKytr66tr6+vrK2vraupp6WmqKWlpqalpKWjo6OioqKjoY6Dg4SVpbCggpi7vL2/uru6t7i1tLW4u7S1trm2s7O1tbW0hLKAs7S0sa+vsK2usLCyrq6srK6vrKyrqqiqp6inp6iopqWop6emo6GgoKSio6Kkn6OkpaakoaKdop+hnJmcnJiampqYmJiZmI6V94CB9/j1+/f29vuDgf729/Py8vT2+ffy8/Xx7/b4+Pr8++7u9/Ly8/H19e3t7ens8fDp7u7v8uqA5u7x+/r5+/Xv8PHz9vH0+oD+/4GAgIP+7MzM0dz7j5mamZaYnJaZmJmSk5KXmZucoJ6ZmpyhnqCeoJ2bnJubn6OfnpykoqSblJuhn5mcnJyho6KcnqSnpKGgnJ2dl+7BiaWhoaOfpaSmqailpKWlqaempaWlp6ekrK6no6eppqOAp6ikpKqoq6ypqbCuqqigpKyrq5DX6IuVnZWbnp+ioaWaoKqqpaWuqaimoKamnZqbl6GenaCho6edoqeUkpe/t7G5xc+9ioyRmJWZmZ2foK+4q62wr6SdoKampKann5egm52lop6en5yem5yWnJSNi5GVlY6GjPqY7YWAg4SI+oCAg4X7g4X4hIuJh4WBh4389oGG/4OChoWGjYqA9/Dt+/Px+Pj+g//4/fX8/ffz6uvtyaqyxtHK0M/Hy8nP39vKxb6roL/2kIqLi42FkZSKgvv49fj79P3v6v6BhIH26/b8gv+Ii4KFj5KCiISCh4+cm56YlZyjprCuoZqss62Vz8pxw8rO0NjXv6WYiIL68+Hd0rq+s6WgmZuYkpKMk5eXmpWLhfjh1s/HtJmbtWRtd4CIlaCmqq60ucDExsjO0dXW0c7P0M/Q0tPR0dTY2dzd29zd3d/f4OPh4N/d3dve4OLh4+Xd4ujr7Ozt73d4eHh5enmGeEF3dXRycnFsZ2Zht62kmY59ari+u9v+jarH2uLk6NbG54b2hIyRj5Odh5aruL/U5u/29vqDh4GHhIWChPTi1tDZ04CcmZCTnZGakoqLi3tqYWd2d3R8g3x7f4SFe36Hi499bWFeX3B6gnp5goeCi4iHlpmLj4qFg3qFgYh6dHh4en17eHt+b22pnWRqcHNze3l3e3x/gHp3eXd4enVzeXh4e3BwfYJ7dHB4gYGChoB7fYGCe32AfYGHhnaPen93eYeEe0l4fIN5bW96g3qChIiKi4uHipCRjYmHg4eGhoaFh4yVmJqbnqiyta+lond6gJGWkY+LhYOGiYeEgYGBgm9njJCMi5CSkpOPj4uIhIaAgYSEg4ODhIWIh4iFhIeGi35bX4SOjXloYV1gaWxsamtraWtvcXNwcG9rb25ubGxtcW9ubm9xcnJzcXFvb3Jwb25sa21vbW1ubmxqa2lpamhmZmhoXFZYWWZyfHBYZ4OEhIaCg4N/gX98fX+BfX+AgoB9fX9+fn18fX18fX9+engVd3l3dnh6e3p7eXd4eXd4d3VzdHJ0hXKAcXVzc3Jwb25ucm9ub3NvcnJ1dXJwcW5zcHJua25uamxtbmtqa2xqZmKpVVajo6CloqGjpFVUpp6joZubmpyinJebnp2Yn6OhpKinnp+kn6ChnqGhnZ2emJicmZOZmpibmJSYm6SioaWhm5uboKCcnZ9TpqpWVFRWqqCKi4+Uq2SAa2xrZ2ptaWxpamVlY2Zpa2xwbmtqbHBtbmxubGpsampucnBta3Rxc2tma3BuaW1ubXBycGttdHZzcG5rbGxopIRfdHFyc291c3N3dnNxc3J3dXRzc3J0dXJ5enRydXZzcXV2cXB3dXh5dnV6eXR0bXB4d3ZkkJ1cZWtjZmdoa2uAbWZocXJvbnZxcG9pbW1hX2VkbGtsbm1ucWltcGJkYHdwanB6gnleZGdsaWxtcnFzgYd8f4J/dXFyd3Z2eHpzbnRvcHl0cXByb3Fxcmxya2ZlaGxtaWNmtmekXFdaW16oVFVWn1VaqFtgXFlZWV9ksataXrJcWlxcXWJgWamlpK6ApKKpqKZVpaOnoKapqKmgoqSIb3WCjISHh4GFg4mWlImFgXNribdsZ2hoaWBscGdgubW1uLmyva6tvmJiXq6ksrlgumRoYWJqbV9iX1pdZnFucGtqb3N2fnpwZ3N7eGSLiYWLkJSbm4d2al5asK6dmZWEiIB0cGxtbGdnY2dqa2xLa2ZhtKqloZaKe3+WVFxmbnWAiY2PkpicnKChpaipqq2rqqqrq6ytrq6ur7CxtLW0tbW1t7a3ubi3t7a2tri5u7u7vrq/xMbHx8rMhGYBaIZpQ2hoZ2ZmZGNjYV5bV6Wdk4h8blucnpSpumqCmqepp6iajKlisF9ma2ptdWRvgIiKnay0uLe3YGNeYmBgXV+woJmZoZyAcG1oanRqcWtjZGRWTEdKUk9NUlZTUFNWVU5OVFdZT0dBREhXYWhiYGhqZm5qZnFzam1kYWFZY2JqYFpgX2FlZGFkZlpYgXVQWV1eXGFeX2NjZmdhXWBfX2JbWmNhXmJaWmNpZWBcYGdoam1nZWpsbmdrbGdrc3Bka2lwaWl3c2qAZ2pyZ1taY2tlbHBzd3h4dHZ9gH14dXJ0dXV1dHR5gYOFiYqUnp+Xj45cYml5fnZzcW1tcXNxbmtraWlSTWhrZ2ZqbG5wbm5oZWNhYWJeYWBdXV9gYGJhZGJgY2VqXkdGYWxsWk5HPz1CQ0VERURCQ0dJSkdJRkVHR0dGRkdKSUdWR0dJSkpNTE1LTE9NTEtIR0lKSUlJSkhHSUdHSEZERUdHQDw/QU1XX1hFUWVpamxmZWVjZ2dlZGZpYmRnamZjYmRkZWRjZWZlZ2lmY2FhZF9fYmRmY2OEYYBgYWJhXV5cXl1cXF1eXmBdXl1bWllaX1xZW2FcYF1gYl5dYF1hXl9aWF1dWltbXlpaXV5bV1CBQUJ9e3Z9fH19f0NAfHd4d3V1dXd7dnF0dnZydn16fIF/dXd+eXx8d3t8eXt8dHF1cmtxc3FxcGxxdX56dnp3bnJ4dnVucnY+fIB+QT8+QX52ZGpweIpQV1hXU1daVFhWVlFQTVJTU1RYVVNUV1tXWVVYVVJUUFBWWVdWVF5aW1NPVVlWUlZXVVdZVVBTW15aVlRRUlNQgGdKXFhZW1ZcWVpdWlhWV1dbWFhXVlZXWFVcXVdVWFlWVFhYU1JZV1laV1VaWVRTTVJYVoBXSWtwQEZKRERFRUpKTEZIT09MTFJOTEtIS0pAPkVDTExLTU1OT0hMTkJGRVJKRElTWVRHTE5SUlRVWFhZY2piZGdjWldXXFxbXF5XUllTVVtXU1NWVFZVWFJXUk5OUVNUT0pOjEt3Qz9BQkR1ODc3Zjg+cj9CPTk5PEFFfHk/QoB7Pz5APj9DQT11dHV+dW52dHE5cXN0a3J0d3lxb3JeTVBWXVheYF1gXmFmZV1bWlBLaZJWTk5LUEhSV1FLk5CNiIyGkYOAi0lKRHt1f4ZIiktNSElRUkNFQz8/Rk5KS0lGSEpLT0xDOkJHRjhPTktRV1thY1hNRT08fXx0dXRudWNvZmRiY2RjYV5hY2ZqbGhnx8XGyMK8us/yhJGgqrK8xMnIyMrOz87Q0tPU1dbU09LU1dfY2djZ293d3t/d39/g4uHi5OPi4uPk5Ofo6unp6eTq8fX3+Pv+gIGDhIaJiYiJi4uGjD2NjYuIh4L98OXXw6iM7+W/wddyhZaemZCThnuPVJZPU1hYV11NVWBiYGt0cnZ0dDs+OTs7PDk7cGtoanVxxX+CfrF/AX7Df/+A1IAEf3+AgIh/goC7fwOAf3+EgId/wICCf7SAAn5/poABf4l+sH+DfoV/CH5/f39+f39+iH8Ffn5/f36If4l+AX+gfop/in6Df4R+An9+qX+Xfol9v36Xf4d+hX2KfgJ/fpF/iICGfwICBACAvr7Iyc7My8zGpYaJp7e0vbq3sJ+jsL/EwtrT2r27v7WwqKqWhvj/ipqvt8HAusLDvrnBvqSlrquppJqbpKOnpaSmnpDO3oiapZmeoaWpoqmsqKWfnp+amJiXj5adnqGdmJiXnpmam6Ohqa+kp6Wsra2rtKqqqrWW1JWcmJampaWAoJ2jpqehk5ihoaSjqKm0uLatrru2s7Ctsa+wrrCurrG4u73GytTZz82+kKnCzcHCwbu6u7/Fw8C/t7axpqfCyMbCxsbLysTJxcbGxb+9v7y9wMbFxsPDxcPAv8DBwcKw/8+GoK2urKqqq6urrKipqKaqq66vr6+urayqrKqqqqeAq6inqaqrrKypqqysq6uqqqinpqempqeopaOmp6WhoKGio6Cgo5aE+9eEmK+5sre8urizsre4tbS0srGwtrKysbCusrKwsLKxr6+xr6yrrqytq6utq6ysqautq6qqqamop6ejpKSnp6SlpaOipKOjoaKfoqWipqOhoqGcnJ2joaEFoZ2dnpyEmYCWmJeUmJGT64SDgICCg4aDhYOC/f+C/fn89PH28vP09PX9//z5/fj19fXz9Oft7+ny7PD18/Dx8/L09evw6vXy8vXz9v/07uzv7fj++fL6/IGB6szIytXxjJWVlJaZnpSSlpmZlpaYmpSUk5KUmZWXmpudnZmcnZmWmZiWmp2en4CdmZugoKGamaCjn52enaKfmpeco5yboZyenJvnvIShoKKfo6KjpKWlpKeloKGnqKOmqKakpKmkqKuppaWnpKanqKqqqaqqq62qra6trqiqqqiVga2A+fDr4+Lj6vP3/fmAhIeJjJWTjZGco6Kcn6eppqainp+gpaWjlv6WusnBroCvsLamz4KLkJaenZqanpqhp7awtKefoJqhm4qfoqiisaCilpabpKegmJ6epJyempGHjYqMl56Msc6D8fuHhoeKgfPy+IKBgYOFhICKg4L7hoWNivWBhYKDgIWHg4P+8/H+gYL4+Ofy7/jw8v/+/fn79/Tj3crFyMnHyMXN0sqzr4DUioyLjIuMiYeLj5ONgYGGhIWMg/L86uT49viCiYWBiYGEgoWNkpmJkPLnipCKhYqGkJSelJucn5KNgprUw7/E0tbf2+HIybqonZyVjJGH8fbn1Le4tK2up6eimJmjqK+3xsnM1dDIy8SwqKWaguPStp+gra+4YWZseIONkJmbn3eipKirsLa7wMPEys/Qzc3O0NDQ0tTSz8/NzczNy8rKzMzG0NHS1NbT0tDNzc5mZmZlZGNfXbOonZKEeW5js6qzwsXrjpWpx9Xv/u7h5PLv7fnv/ISCgPGSmriwpsbAwt/17/aDhoCGiYiJi4Hx9/Tn19LL0MrFuoCKi5OUmpiXl5B2XmB2gX+Hg4B7a213goOBk46SfHl9dnNubmJaq7Rmc4SJkpGKkZOOhIyMd3iCf316cnN7e358e314bJalZ3J5b3N3e353fH57eXNzdXNxcXJrcnd3eHVzcnF4dHV1fnyAhn19eoODgn6FfX+BiGmYc3t3coB9f2l5dnyAgnhscnp5enp9foqOjICBjYuHh4iJhoeEh4aFh42QkJibo6ienZFofJCXjIyLiYiIi5GQjYqDf350doiOjYiMjZKQio6LiouMhYODgYKCh4aIhIKDhIGChIeHiX2zjVpob3JwbWyEbWxqa2poa21wcXBwbm1ubG5ra21rbmtqamttb3Btbm9vbm9vbm1ubW5ubW5ubGpsb21nZmdoaGZnaGBVn4xdaHh/e3+DgoF9fYCBfnx8e3t6fnt8fXt5fX17e319ent8eXZ2eXd4dnd6eHd2dHWEd4B1c3FydG5vcXR0cXJycHBxcXBub25xc3F1cm9wcGprbHJwcnJubXBuamxsbWlqamZqZmCdVlZUVFVUVlRWVVKdolOkn56bl5mWlpiXmqSoo6KopqKgoKGjlZ2emKCdn6ShnJubm5qZlZqWn56dnp2dpJ2YlpmZoaWjm6KlVFediICFhpGoYmloaGhrb2VlaW1saGhqbGZnZWRiaGZobGxtbGttbWllaGhlaGxub2xpaW9wcmpob3FtbW1rb2xoZmtzbGtwa21rbJ+AW3FvcW9zcXBzc3NydHJub3V2cXR1c3JydnF1d3dyc3Vxc3R1dnZ1dnZ3eXZ5e3l6dHZ2dGdYc4BTn5mVkZGTl5+jpqdUWltfYmZlXmJpbWplZ3J2dHRxbGlpcG5rZKlid358bWxtdGeGXWRobHFxcHFzbnN4hYGCeHN1bnNuYXJ0enV/c3RqaWx0eHNuc3N4cXNwaWJoZWZwdmd7kV6osF9dXV1UnaGsXFpaW11aV15cW65dXGRhqoBZXFpcWFteWVmvp6WzW1ymp5uinqOenaaipaOoqKaZk4SBhIiEhYWLj4l5gJxmaGlqaGtpZWhrb2lfX2RjY2dfsbupo7e0tl9mYl5lXWJfYmltcmRsrKBhZmFeYV5maHRqbmxvZF5aaJeHgYeTlp+boY2MgXRubWhfZl2lo5yQfDl/e3h3dnZ0b2xzdnyDjpCRmJWQkY+CfXlyZLCmkYKEj5eeVFleZWx0eISEh4qLjpKWmZ2hpaaoq62Gq2Strq2sq6uqqausrKyurqmxs7W2uLe2tbS0tlpcW1tZWFdUn5aNg3huZFmaio6Tla9rbnyUoLLCsKKpsaqpuq+4YGBdqWtyjIF5lY+PprWxtWFiXGJlZGRmXa60s6ialpOXk4+HgGJha2xxbm5ubFVDQ1JbWl5bWFRHR01TU1JfW11QTlBMS0lJQz57h09bZmt2cmlub2thZ2VXW2ZkZGFcXWVkZWVkZ2FXb3tQW2JZXmFkZmBlZGFgW1tfWldYWVJaXl1eXVpbW19cXV1lY2pwZmlob25pZ25lZ2hwV3Vla2dicG9wMGlkaW1uZVdbZWZnaGtrd3p1bGx7eHZ0dHd2eXd5dnR0enx8g4WPk4mJfVJneoBxcoRxM3N4d3R0bG1rWFpla2llamlub2ltaGZoaGJfYF5fXmNjZF5dXl5dXWJkZGdfh2M8Q0dIR4VEKEVDQ0FAREVHSEdHRUZHRkhFREZER0REREVHSUhGSkxMS0xNTElKSUqES3tJRkhLSkZFRkVFQkVHQjx4ZkVOXGRcYWdmZ2RiZWZmY2NiYmFmYmNkY19gYGBjZmdkY2RjYF9hX2FdX2RgX2BdXl9eX2FgXlpbXllZWl5fXVxcW1pdXFtYWllbXlxgXVpbXFhZWmFeYGBaW19dWVtbW1hbXFldWVR4P0GEP4BBPj8+PnV4Pnt0dHBtbWtqbW1sd3l2en97dnd3en1uc3lze3t9gX51dXd1dXVvcmt1dHZ6dXR3cGxqbnB8enVtdHc+QHNlY2t2hlBXVlRWWVxQUFRZWVZVVlZPUFBOSk9NUVVVVlRSVVZSTlFPTFBUVVdVUlJXWVlSUVhYVldZV4BZVE5LUVhQUVhSU1FTd2FHWldZV1pWVVlYWFhbWFNVXFpVV1hWVVRYU1dbWlVXWVVWV1hYWFdYWFlbV1pcWllTVlZVS0FNNWdkYWBfYGRqa3B0Oj4/Q0ZJRkJESk1JRUZQVVRUT0tGR05LSER1RVFWU0hJSlBGaEdMUFRZWVZXWTZWWl5nZGVdV1pVWFJGVVZbV19YV01MT1dbVlJZW19YWFZRSk9NTVZcUF5rRXmAREJBQThnanaFQIA9O0FAQXpCQUVEdz9CQEA/QEM/QH96eIBBQ3d5bXJwc2tobWtwb3Z1cWpqW1hbX15dXWNnYldheFJVVFFQUk9LTk9TUEdKTUtLT0qFkIF5iIaMR0xLRUxHSkhKT1NWSU59ckRHQ0FDQUdIUUdJRkY7ODdAXlNOUVlaYVxgU1NNRkVCRUM8Qj5zdHBpXV9iZGVjY2NiYGhnaXB2eHmBfnx9fXl4eXhw0tTMxc7j7fuCiJGapa+1vsHExcfHxszS2dnc3t7g4N+H3mLd3dzd3t/h4eLj5OLh5+zw9Pf29/f5+f6BhIWGhoaDgPbp3s69sKCO9NrPvaq/cHJ8jpOgqJOJjpaSk6OTmVBQTodVWWpiW2pjYW50cXE8OzY7Pz08PztudHdxaWZma2loYqV/gn6df4J+sn8BfsR/o4CCf8WAgn/mgIJ/i4ADf3+Aun+CgIZ/w4CCf7WAA39/gIt/moCKfgF9sn8Ffn5/fn6Ff4N+in8BfoR/AX6Jf4R+gn+efpN/h36Of4J+pH+ffoh9t36If4h+hn2QfgR/f39+jH+JgIt/AgIEAIC9zNHe4NW0j4mds7KlqsXCvbS2mK67vLfI0M27s7W5q6qfnKmqtaqsoZ2Yk5OkqcCxvLK5u6qkop2kmpydo56eo56Lxur9i6CXkJajnJimr6qqnJqenJaXlZeWkZWYmpqXmZqUmJmbmZ6oqqihnqiforSwpaerrvLvkJmXoJqZmoCeoaOmqaiqpZ+hoZueo6Gcn6WfpbK6raSlrbS2uLKvqqeorrKytbnAvsOqjsbVv7y5uLq8vMHEv8DEw8O6pqrAxMbExsPDwr7Bw8PBw8PAvb27w8TDw8G/vcC8vb/Aw8C/sISBo7Cwraqxsa6uqq6usK6srqmsrKutrq6sq6mpqoCqqKuoqKusrq+uraunqKeop6amp6inpaOho6Sjo6WipaWkoZ+enqCgoaKUhoSGh5Giq7C0sLWyrrWzrrCvsa6vr7O1tbOwsK6xqKyws7Cxsa6rqqelqaaoq6umqKikpaakp6WkpKSio6KioqCfoKCko6KhnqGio6KkopyeoaOhooCgn5+dmpuamJeYmJqcmJOuyoWFh4qIh4L+gP+EgfX6+PH6+PTw8/P59vf77O7v7fT3+PXx7+Xo4PH59O3z7u3u6ufm5+fp7u/r7fLw8vLm6PHy9Pr05NLKztPX74iRk5WTl5aTl5WYmZaXmpuWmJqVl5iUlZ2fm5aUmJ+goZ2ZnYCcmJmdn5mZnpygnJ2loJuWnaGhm5uenaGfoJycoJqdnZSW4L+Gn6SioJ+joqGhoqKioJ6hnqOjoKGkqaioqKelp6akqKelpaimpamrraqrqKutraypqaqnopuriomNjpCUl5iTkpKWkZKXmJiXlJKOh4T69vX07u/k2+Dc1tjY2YDWoaXa3N/k7+jo9vfe6+SPlpOYl5GYmp2eq6uyvK6ip6ydmpGSlq2Ym5qakZuempmXmJmSkZWbnpudl46Gi5GTldS44/Dqgof8h4r59/aCgYn1h4WEg4CGiIqKgvuDgP+A/oX6/oT47+by8vv9+oP07OXl+/nx7/T4+IH+hP3xzoC8wbqrqsLqjouFi4SFi4yRiYySkpGRg4CGh4uEhIOA9+rj8//o8oD4hYSGhYeNi4+BiofyjYOC/YyFi/uLn6qzp5LakcfGt8HFyMbEv8e8ub+yvMDBusS1pqewpJmXioyXloiEh4aC99zi29PN29nn2OXm6NPR3dfW0tPPw6uckYCGgfDTvq6rnZqooZ+lWV9tc3N9hYKJkZKPkJGQkZKQkJKRkI6NkI+Qj4uHho2KhIuIhoODfXdwaG1pYLa6vrjj/46YoK7E0dvg4NbA09Hf6fLi4feHkouHiI6cl5KSmJqSndH06uftgP+Fg4qQgv+EhYOC593h3czGxsTGwsLUuYCKkpmjpJ2BZGBwgH5ydo6Lh3+AZ3Z+fXiHjYt7dHV5b25lZW9veHBxamloamh4eZCAiYCJjH54eXV5cXR2fHZ1enhpj6SwZnduZm17dW98g358cHB1cW1ubG9wbXBwcXFucHNtcHFxcHZ+gX94d4F5e4eDe3uAhLCubXh0fXd0dX15e3x+goCCfnp7enZ3e3h0dXt2e4mPhoCBhYiIi4eGhICCh4qLjpCWk5iAZZSeiomGhouMio6Rjo6Pi42GcHqGioyJiYmLi4eJioqHiYmIhoOBhoeFhoaBgIN/gIOEh4SEfV1XanJzcW5zdHBwbXBwcnBucW5vbWttb3Bwb4VtbXBsbnFycnJxcG9sbGxtbG1sbW9ubWtoamxramppamxraGZkZGZnZmZdVVdbW2Nxd3p/e397eH18eHh4e3d5en1/f315e3l8dXZ6fXp7fXl1dHJxdXJ0dXRydHNvb3BxdHJxcnFvbm1vcG5tbm2EcYBucHBwb3Nya25wcW9zcG9vbmpramlpaWpscGxoen5WVlhcWVdSoVGfVFGWnJmSmpqVkpaUnJmaoJSXmJedoqOgnp6RlIuco6CZnpmZmZOQkZKSlZudmJufnaGil5ednp+knZKJhYqQlaNdZGZoZ2lnZWlma21paWxuaGdqZmhoZ2hnbW5pZmVocHFxbWlta2hobG5paW1rb2tsc29saG1wbmprbWxvbm9rbHBqbW1maJqDXm90cG9vc3JxcHFxcG9tcG1xcW9vcXV0dHV0cnRzcnV0c3J1cnF1dnh1dnN2eXl3dXV1c25qdoRbgF1gYmFdXFteWltfYF9dXVxaVVGal5OSkZONh42MiYqKiolocKKfn6OspqOprZycoWZsam1saG1ucXJ8e4GJfnV4fHBuZ2hrgWxvbW5nb3JubmxtbmlpbHB0cHRvZ2FmbGxtmYChqaNbYK9eXaGio1hYYKpeXFtaWV5fYmFbrlxYgK5Yr1+wsFuqpJymp62sq1qmoZ2cqaiemZyfpFWkWKuli36CfnNxi6tpZmBnYGJoam5laG5ubWxgXWNlZ2JiYWC6rKezvqyzX7djYmRiZGpoal1nY6pkXFuyZV1jsGNxeYF3aJFmkpCEiImOjYuFi4N/hHyDh4aDi310cntxaWpfgF5naGBbW1tZrJ2jnJaRnJiimKCippaVnpqYlpmUjn5za2dkvaugkZGKhpONiI1PVmFoaXF0c3iCgn6AgX9+gH59f4B9e3l8fX59end4e3t2fHl3dXRwamNdX1lQnpuck7LJbHF5gI6XoqalmYKTlKCotKeis2JqZmJkZ3ZwampxIXNqcpy5r6qvX7xiYGZrXrphYF5fqKGlopSOj4+RjY2dhoBkam50dXBdSEVQW1hOUWRiX1daRk5SUE1YXFxQTEtMSEdDQ0pKTkpLRkRKT01aWWxeZmBoa2JfX1tfWV1gZV5dY2FTbH6ITl5UTVRiX1tjaWRkWFhdXFdYV1lZUldaXVtXWVtXW11aW2FrbGtlZG5lZHBrYGFnbYeNX2dja2hlZoBpa21vcW9wbGhoaGJjZ2RfX2VeYnJ8b2ZpcHh7fXZ2cWxtcXR1eXuBfoRuT3uBcG5tbXJ0c3Z4c3R4eHhzWVljZ2pnaWVmaGRlZWRiZmdnZGFeZGVlZGJeXWFcXV9gY2BhW0I7RUhKSEVISUZHRUhIS0lHSUVGRUNFR0lISEhHR4BISEpGSEtLSkxMTUtHSEhJSUpJSUtLSUlHSElHR0dFSElIRUNBQUNEQ0Q+ODtDRUxYW15kYGRhXWRjYF5eYl9hYWRmZWViZGFlW15gZGJiY19eW1lYXVxdXl9bXFxYWFpbXltbXFxbWlhaW1lZWllcXV1cWVxdXVpfXlZaXmBdYIBfXV1cWVxYV1hZW15hXVxmXj8+QEVDQj1zOW48Omt0cGhwbWhkaGtxbm91a25ubnV8fnp4d2puZ3Z9dW52b29ubGtrcGxtcnRyc3h4eHZub3N1dXpzZl5ga3N4hExRVFVUVVNSVlJXWVNVV1lTU1ZRUlJQU1laVVFNUVtbWlRRVoBUUVJVVlJTVlNYU1VdWFNPVVhWU1ZYVltVVE9QVFBUVE5Ue2ZJVltVV1lbWFZVVlZVVFJVU1ZVU1RUV1VWWFZTVVVVWFdVVFdUUldYWlhYVVhbWlhVVFVTT01VPzk5Ojs9Pz46OTY4Njg5OTk4ODc2MzFdWVZWVFZUUllaWFdVVYBVRFWAfHuAh4F7goV3enxPVFFVU09UVVhYYF9jamBYXGBVU0xMUGNQUlFSS1NWUlJRU1NQUFNVV1ZbV09JTVNTU3Rfdnp1QkV8Q0BrbG08PkV1Q0E/Pj9DREVEPnc/PHc+fkV9fkJ7c2x4eX57fUR6dHFscG9pZWdpcDlsOXFuW4BVWFRLTGqNWVRNU0xLUVJUTk5SUFFSSkZLS09LTEpIjoJ6hYx8hUmLTkxMSUpPTVBFTEp5RkBBfkhCRXhFT1RYU0hjRGJfVFdVWVdWT1BLSk9ITVBRTlRLRkVLRkNEPj1EQz88Ozw7dW10cW9qcm93c3h5fXZ1e319f4KAfXhxcIBvceXc4uDn4d/78+nvhJKjsK25wLzCz83HycvIxsXAwcPFwcLAx8XGx8O+vcPFvsfEw8C9t7Cnm5yUgfDk1L/U43d3eXqFjJSTjINpdnuKkZyNhI9OVlJPUVNcV1JQVVZMTmp8cGxuOnM9Oz1BOnI8Ozs9b29zcmllZWZoZmRyYsN/g36xf4J+xX//gNKAgn+HgAV/gH+AgL9/xoCCf7aAgn+WgJB/i34CfX6yf4V+DH9/fn9/fn5+f39/fop/Cn5/f35/fn9+fn+IfgF/i34Df35/in6Yf4d+An9+i38Jfn9/f35/f39+hn8BfqR/m36Lfa5+hn2TfpN/AoB/hYABf4SAjX8CAgQAgMXGw6qJkKKuvsLCs7m+tbe5ycG3r8K9ysi4tLO0ra6qqaKgq62ptLCywsa7q6qRkJKPk5+onp2enp2Zl5ebnJqb9cLviJGNm6KipJ2fjZ+pq6WdnJyenpmXl5uYmpmSlJmSk5mfn5qjmpiXnZqYpKScpKSipqChy4KUkpKWnZmRgJWQkpyfpa2qoJ2ipKSco56Xm52jpa2wtrmvsrexrq+5s6ypp6qusa64treOmdDRwLizsrK2vMO5ta62ur2opqzBxMbHw8DEvr6+wMDBv8HAwMG/v8LAw8HDwr6/vMLDv7+7u7GIgKGvrqusrKmrq6qsr62usbKur66vrqyrrKmogKuqqKmpp6epqqqnoqOjo6GkpKSjoqWjpaempKSlpKKkpaWlpKShoqKioZ6ipaeknI6Dg4aHiJioqqmus7GusbOxs7Gys7Kusa+rrK6trbCurK2np6apqKipp6ekqKioqqiloqOko6SkpaKjo6CkpKSgoaCgoaWloaCgop2enp2egJmdnJ2enJqWmJWXmJeWmOWfhoWGgoKD/4L9/fz7gPbw9vn7+PXy9/Tx9vPu8PH1+/r59/fy+PPu7u3q6ujl4+Tk5uzv7Ovv8uvl9Pjz8PTs1L67xcrcgZGUlpGTk5GVlYyPlJmVkZadm5eZl5SVmJiTl5SYlJWWl5ieop+WmJaZgJmcm5qcnJyfoZ+fnp6fnJ+eoJ+dnqKen6OfoJ6cpKOTyMONn6Chn6GfoKKco5+eoaKhn5+io6GinqKrqaWlqKWlp6Knp6anp6Snp6epqKurra2ur6enqKKf0OOIkZGRkJGTlJOSkZeXk5SVlJOVlZSTkZOYmZmWkpOTkZGSjYmQgNK27omOjpCQnZyQmZStzYWLmaOYn5WgmZuaoamxvLe1paOcl5eVl5ucn6Olk46OlJWZl56cl5aRkpaZnpOTi5CRlf+fy/X/iYGBgIqA8PXf9IaFgYKKg4CAhPX6hIyD6e/z/IKBgfr7+YCA5+v9/fH34+Xh8uzY3/H28/D4/4iHgILcuK/yh4WC+vyEioONjJCJioqQj5WWjIyDko2JiJGN+vKAgfHf8oqIioCFi4eDm56VmoeVjoiBiomMiZauo6L3zYyQkJGSnKK0vLi6rb7Hub+71+/06er67+ji6djU0c7OxcK0r7OroZuVjpaNhISKjfbw8ev2g4GDhIuPioqCgICGifjs2redoJeVl5aZjpeqqq+yr6mko5eVjpGTkob69uTo29bT0OH39rrgh5Cepaemrq6vub66097S2N/Y6+Dn8NzXy8/b9ubv4+n9hY6TiIqZoJmepaKsvri+vsbEzd353MjigYmQjoqRkP3s7O3k0tTOz8nExby/w8/Lz8jEgI+Pi3liZ3N5houLf4KFf4KDj4d+d4eBiId6d3Z2cHBubmdnb3FtdXJ2g4R9cnNhY2VlaXR9c3N1dHJycHB1dXN2tIuyZGxncnl6fHd5aXiBhHx0dXR1dnNxcXZ0dXRtbXJsbnF3dnB2cnBvdHFvfX92fHx6fXh5j2Jyb3F1e3VvgHNvbHR5foOBe3h6e3t1enVtcXJ0eIGEiYp8gIeEgYWMh4OCfYGEhYOOioxmbp2bjoiCgoGFiIyGhH2Fh4t2dXuIjY+RjIeKhYWFh4aGhYiIiImFhIeGiIiKiIWFgYmJhIN/gHldVWt0c29xcW5vb25wcnFydHZyc3FycnBvcW9tgHFvbm9vbW1tbm1saWlqaWhra2pqaWxqbHBubGtsa2lqa2xtbGxpampqaGZqa21rZlpVWFxcX2x2dXN5fn14ent5e3p8f394eHd1dnd4eHp5d3dzcnJ0c3N1c3RxcnJydXRycHBycHFxcm5vcW5ycnJwcXFxcHR0b3BucWxubm1tgGdtbG5wbWtnamZnaWlpa59mV1ZWUlJSnFKdnZ2cT5eTmJqhnZuXnJuZnp2an6CipqilpKObn5uYmJeWlpWTko+Nj5Wcm5qdn5mUoKSfnqGci3t7h4qYW2doaWVpZmRoaGFlaGtoZmlvbGlraWZnaWpmaGZqZWVoamtvcW1maGdpXGlra2psbG1wcW5vbW1tbG9ucG5tbnBucHFtbm1sdHJmiYVgbm5wcHJvcHJscW5ucXFwbW5xcnBwbXB3dXJxdHJzdG5yc3N0dHF0c3R1c3Z2eXl6e3RzdG9uk5NZhl2AXFpbWV1dW1tcXFpaXFxaWFldXVtZV1hYV1hZVlRZg3mtY2dlZmZycGVsaHmSYGNwd21xanRvcG92e4GJhoR3dnBsbGpscHF0d3hoZGZpam5tc3JsbGhqbXBzampkaWptu22OrbVhXFxaYVacopWpXV1aW2FcWlpdrLBeZFyhpqmAsltaWrCyrlhYnqKysaSsm5ybp6GSlqCiop+nrl5bWJR8drFkYV+2uWBmYWloamZnZ2xqb3FoaWFtaWVia2q7tl1esaCuaGRmXmFmY15ydWxwX2xmYVpjYWNhaXtycaqQZWhmaGlwcoCGg4R4hIyDhIGXpKejoaymoJ6gk5COi4qAh4V7d316cm9qY2lkXV1jZa+qqqOqXFpcXWFkYWRfXWJlt6yfiXV4cHF2dXdweomGiY2PiIWEeXZwc3J0asXEtLGpq6upscC/lKpob3mAgX+Fg4eMkI6lr6SipaOtpqq0nZmNkZyzpqegprleZWpiZG91b3R7eH6Mh4uOlJadqr0eo5SkXmRqaWVqa7assK6llpqVlpOQjoiKjZmWmZGPgGNkYldFSVBTXGBgWFxdWFpZYlpWUVtWW1tST05NSUhHSEVFSUlITk1LUVZTS0pBQkZKUFphWlhZWllZWFldXlxfiGWJTVhUXWBhYl9iUl5jZV9ZWltdXFtYWFpXWltVVlxWVlxiYVphX1xbYl9da2xhZ2ZjZWBicFhkYWFlbGZegGFZWmJla3BuaGVpaWhgZmBZW1xeYmludXdnbHVua2x2cm5sZ2pwcG53dHdUV359cWxpaGdrbXJpZGBrbXRgVV5mamtsZmJoYmJhYmFiYWZnZmhjYWZlaWhoaGRjYGdnYmFfYFpDOkdLSkdJSkZGRkVGSUhKTE9LSkhJSkhHSUdGgEhISUpKSUhHSElIQ0JERkZIR0dHSElISkxKSEhIRkNHSEhJS0tISUlJSEdKS01MRT46PkRHSFJcXFpfZGJdYGJhY2JiZmZeXl9eXVxfX2JgXmFbW1pdXFpcXF1ZW1paW1xZV1lcW1xcXVpbXFddXV5aW1teXmFiW1tZXVhbXFlYgFNZWl1dW1hTWFRUWFhYW4xPQD8+Ojo6bDtsbWxrOW1pbHJyb25qcXNwdXVyeXt9gH57e3x1eHZ1cG9va2prbGhma3B0bW90dnNxenpzdXd0aF5kbXF/TFRWV1FUUExRUEtRV1lUUVRbV1NWU1FRU1RQUlJTUE1RU1RaXFZPUE5RW1JVVVJUVVVYWVdWU1ZVU1VVVlRUVlpYWVlSVFNQWVdPZ2RMVFVXWFhVWFlWWlRUV1dVUVNWV1RVUFRZWFVTVlRVVVBTVFRUVlJUVFVVVFdYW1tcXFNSVFBQaWuHOhY4ODc1Nzg3NjY1MzQ1NTU0NTY1NDIxhDOANDMxNFBXhk9RT1BQWVdNU09ccEhLVV9VWVBZVVdVWl9kamdkW1lTTlBNUFRUV1pZTElMT1BUUlhWUlFOT1NWWVJPS09QVJNRZn+GSkJCQkg8ZmdidEFAP0BGQD49P3V2P0ZAanJ5gUJAQHx8ez4/c3iFgXN4a3FrcnFmZ25xb2yAb248OTphU1aQVFNPjI9NUkxTUFFNTk9SUFJUTUxDUVBOSlJRjohHRoR2hFFPUEZITEpFV1lQU0RQS0ZBSEZIREtYTk90Y0ZJR0dGSktRVlRSSVBWUE1KVlpaVlVbWFVUWFNQUFFSUFBJSU5PS0pHREhFQkJGSYB6fHl9RENGRUaASUZJR0ZJTpSPiHxucW5zfYCBgI2enKGtsa2loZaYjouKjoT1+Ofe3Obp5ujz6rrMd3qFi4uEiYWHio6Hm6GVjZGMmI6Sm4J8cXJ+loiEeYCOSVBUTU1WWlZYXFhaY11eXmJjZ3F+al1mODo/Pzw+QXRwc3RvZWpobGlkZWFkZnEEbnBqZcF/g36zfwF+x3//gNGAgn+GgAJ/gIR/AYC4f8qAgn+3gAJ/fqWAA39+fop/gn6zf4V+hn+Efol/BX5+f39/hH4If39/fn5+f3+TfoN/hH4Ff39/fn6Wfwd+fn9/fn5+mX+CfrB/hX6Mf5x+jX2hfph/h4CUfwICBACAnIqSrsG+raivsLSqp7i8tsjJuK6/sKrAuK+sqrOmpqyloq+nrrLGzLy9v8i5vMzGubCllYiMh4aLjJOan5mZkdrSh4yOnKWjqqSgnp6VmZ6anJyampWWlpqUmZ2akJGXmZGUm52bop6XkZeamJmfn6Kio6KfnZSqgZGNlJiYmJeAi5SWk5mWoaSkoJ+enqiqop+mqKinqauwsrexrqypq7u+qqWsp6WssbCxsrOJoNDOxL62tLG1sLW2s6ustLKqpavEyMPEwr+8vbvAu8LBvcDBw7/Av8TEwb+/vLu9vMK9v8G+u7mwjPiQqKqorKytrKuqpqqrrKuvrKipq6qpp6aAqKioqamnqKmqqammpKSgoaSkpKOjo6KgoaKkpKOioqGgoaKgn6CfoaOgnpydnJ+enp6ampuViYH9gISIl6Wvsrm3tK+usK6xqKyvr6urrq2sraqmo6aop6ajo6alqKampaSkpKOkpqenpKSioKKhpKOfn6GhpKCgmZ+inJyenZuAnJ+dmJiam5qYlZSUkJGP9/v6iIeFhoKEhYD2+v3+7PD9/fr8/vPv8erq9Pj2+Pr19Pbs9Pb09fP07/Dv5+fu6OXn8fLr5uv06tvEvcDEzdj1io2Ni46TmZmUlJKOkpCSjo6TlJeZmJSVmJeYl5iXmZWVk5SWkpSSl5uen5qWm5eAmZmamJybn5ufpKCgoZ+dnZ2foaCipKGdnqGfnJ6Nz9eVoJ+bpaKcn6Ken6Ohn5yboaGdnp6gl5mdop+epKqnqKajo6SpqaeipqinqKiprayqraioq6mkoOPOgZGRkI+NjpOVlZiRjY6Pko+Qj5STk5SVlpqZlZaXmZmUjo2TjLOAtYqRkZOOiIyKmZaVluWelYyVmJiampyho52hqqGirrOqp6Ofk5OVk5KNgIaIjpKSkY+XkZ+Zo56eoaCZlJiXm5GQl43CvoGMj4z/gPz8gIKBg4X3h4WC+oKLkJKFhoSBgPj9+Pr67vb/8eTx8uL19vTr8PLm09DT3dze6Obt8PKA+YL28drOzN6CioaJg4eNhoiGiYmKjY6NlpuJi4mTjIT6iomIg4aJi4WFjYeRjZedm5OKi436jIaJkIyLmZ2S5eyalpeSn6KVlpeds7aurrKvv8vLwszb5trr+YH69vyD84OE8/T07uHX49W9uLqnqaScmpaQmI6Qjo6Omp6VguaA+Pjwg/3l3uDly76toqCfnaKjqr/Kv9bd2N7IyO3s19LHwLanrrS0tLjEwZbJ7vTi2d/s8uzu6uno5/D2iYGG/IiLhYGEh4iLmpGQoKOhnKe0w9TFv8zQ4d/a49bd7v2DhYWD//XJ0OTu7evavtHY3dfPzMHCytLHv8rSxsXOzcCAcGJnfoiFeHR5en93dYCDfY2Mf3aFenKFfHRycXZtbXJtaXJscnOCiH5/foZ7fIiFfXZuZFxfXmBkZWtyd3JybJqXY2Vmc3t5fnt4dndwc3d0dXZ0dG9xcnZtdHl1bGxvcWxvdXZzdnRwbXBzcnN5e3x8fXp3eHF2Ym9rcnV1c3KAZnJzbnVxeX+Be3l3d4GCeXl+fn18fn+EhYmDf3x7fYmMfXl+e3l9goKGhYZhcZqYkIuGhIGBe4CDgXx9gH14bnqOkIyNioaEhIGHgomIhYiKjIeIhoyNiIWGhISFhIyFh4iFgYB6X6Fdbm9tcXFxcHBwa3BxcXBzcm1tcHBvbmxebm1tbm5sbGxubW1ramxpampqaWhoamhnaGlrbGtqamhoaWhnZ2hmaW1qZ2ZnZGdnZmdkZmZgWlWuWVteaXF3eoF+e3Z3enh7c3Z5eXZ2eHh1dnVzcXR1dHNxcnRxc4RwgHFxb3Byc3RwcXBtbm9xcG9wcXF1b3Bqb3JrbG9tamtubWppamxsaWZlZmJkY7GhoVlYVVRRU1VPk5ieoI+SqKqjpKOalJeRkp6ioaOln52knaKkoJ+cnZygnZWVm5SPkqCfmJaaoJuSg32ChYqVqWFjZWNjZ21rZmZkYGRjZGFhZ2ZoaW1rZ2ZpaGppa2loZmdkZmhjZmVpa21taWdqaGtqamlram5qbXBtbXJvbGtsbnBvcnNybG9xbmtuYo+UaW9uanVybXBybW9zcGxqam9uamxtbmZpbXFua3B1dHVzb25wdnVzcHOEdIB1eHd3eXV1d3Vxb6OGVV1cWllZWVxdXmBbWFhaWVdYV1lYWVpbXGBfW1tbXVxZVVRYVHB9Z2trbWdiZGBubGtspG5vZWxubnFvcXV2cXR7dHR/gnp3c3JoaWpoZmJYXF9jZGVmZWtodm94c3N2dG5qbW5xaWpwaYuEW2NnY7RbsYCvV1hUV1ysX11arlphZGZcXFxaWKyxrK2wqLK7q52qq52urq2lqKuhkI6Nk5KVnZuhoqSrWaijk4+QoGBmYmVgZWxmZmNmZ2dqaWZtdWZoZW9nYblmY2NfYGNmYGBnYmpmb3RzbGVkZrRlYGFoZGRtb2WepHFtbmlzdmprbG+Bgip7eX55ho6Rh4+YoZajqVmqpqtZplpcqaytqJ+WopqFgoV1d3NtbGpmbGWEZoBucmpborGyrF24pZ6fqJSJfHZ1c3J2dHmIjoaXm5efjo2rqpmXjYeBdnyBgH+BjYxpkrC0pZ+mr7WysKuwraqytmZhZb1laWJdYGFjZnNraXZ5dnB4hJGekoqXm6ehnqifo7O/ZGVkY723kpWirrGtnoqZn6KdlpSLjZOYkYqTmwWOjZeXi4BPRUlXX1xTUFRVVlFQWVxWYGFWUFxVTltWT0tKTEZHSkdESkdJSlVYUVJSV09RWldUUk9IQkRHSk1OU1xgW1xZeXVRUFFeZWFmY2BfX1hbX1pcXVtaWFlYW1FYXlpQU1hcVFhgYV5hXltWXF9eX2RjZmZnZGFiW1pVYVxjaGdlZIBXX2FdY19lbHBqaWdnc3VnYmdoZ2VnaXBxdW9rZmRkbnNlZWppZ2ltbW1vckpXf350cGtpaGplaGppZmRnZmBQX21vaGlnZGFhXmVgaWhiZmhrZ2lobW1qZ2dkZGVkbWZnZ2RfXVxIdkBIR0VISEhHR0dER0hGRklJR0dISEhGRSBHR0hJSkhISEpKSkdGR0VGR0dFREVFRERFRkhJSUhIR4VGCUdGSUxKR0ZGRIRIgERGR0E8On5BREdQVl1gaWZiXVtgXF5aXWBhXFteXlxeXVtZWVpcWllZW1haV1dYVlhZWVpbXV1aW1pXWlpcXFpcXl5iWltVWl9YV1xaV1pfXVlWVlhZWFVVVFFTVJd5dENBPz45PD04amtxdGhpfH53dXJtaWtnanR3eHl6dXV5TnJ9gXp4dndydXJubnFsZml1dm9scXlza2BcYmhueYxSVFVTU1dbWVJRTkhOTVBNTlRUVVdVUU9SUlNTVFVUUE5OT1FPUU9TVVZWUU5QT4RSgFNUV1RVWFRVWldTUlJWWldaW1hSVlhVUlVNanFVV1VUX1pVWVtVVlpXU09PVVJPUVJTS05TVVJPVFhWWFVQT1JZV1VRVVZWVlVWWVlYW1ZWV1RSUXJhNjk4Nzc2Njk5Ojs4NTQ1NTMzMjMzMzU1Njg3NDQ0NTUzMTAyMURdUlVUgFdRTE1KVVNSU35TVk5UVVVXVVdaXFdZXllYYmRcWVVTTExNTEpHPkNFR0lKTEtQTVpUXFlZW1pUUlRTV1BRVlBpYUVKTUmCQoB9PTo2OUB4Qz89cj1BQ0VAQUE/PXl/eHp8d4CHenJ8gXaFg4J6fH51ZWRiZ2Vna2hqa29zPG5qgGFpb35OVU9VTVBWUFBMTU9NS0pLUlxPUE5ZUUmKTkxMSElLT0pJTUZQTlZZVlBKSkyBSkdITkpJT1BHbndSUFBMU1ZLSUpMVVRQTk9MUldZUVRWWVJaXDFbV1kuVy8yXF1hYV1WXlxRT1FKTU5LS0pHTEhMTE5PU1RNQ3mIioRIfIx+e32He3VuamtraGtqanR4bnh8eHtwbYqLe3p2dHFrcXZybGxycVZ5lZmJg4mTmZaYk5STlJmbVlFVmVNXTkdISkxNWlNOWFlYUVdfZm5iW2RkbGZjZ2Jkb3g+Pz88c3FZWGJudHRvX2xwdHBraWJjaG5pZW10aWhvb2PAf4J+tX8Bfsh/p4ABf9CAAX/WgAN/fn+IgLd/zYCCf7iAAn9+pYACf36Mf4J+tX+CfoR/BH5/fn6FfwV+f39/fol/oH4Bf4Z+mH8BfpR/AX6Jf4J+mn8IgH9/f4B/gICcf4R+AX+4fgR/f39+n3+EgJ1/AgIEAIClvcK4xMC5uK2lr6yvtrq0ury5ubGjp7O5sausramvoqqsu7KxvsHCu72+xsa9sqikqaikpK6lo5mQiIiJh/3S+5CWkZGZm52qpqWmn5OXmZSXm5eUkpCWl5aVl52al5WSj5WWl5iVnpmUkZOUl56emZ+hoJqYh5+DhpGWlpCVk3GRjZCTkJOVmJqanqKemZ+fmpugop6hrbGwra61uLSsr7Kyrquuq6Cjqqyrra/9nLi4v8S6rbS1tre+wsfFvrqqn5y8wMLCvru9vbzBwsHBwL+8vLu4usHCwsG9vr+/wsPCvL6/vb67uZn4/Z6mqKmpqYSqD6eloqChpKWrqKqoqKenqYWmV6WkoqGgn56hoqKhpKSjpKOmpKGgoaGgoJ+goJ6enZ6fn6ChoqKgn5ucm5ubmZiamZyamJCD+fz/hIiMmqqsq6+ur66urK2rqamoqKmnp6moqKmnp6ikpIWjfqaloqGkpqWkoaOlpKKioKCfnpycnp6dnp2bnZ2XlJicmpaVlJGSlJKPj4eK3IGBg4KFhYGDhICFg/77/Pv9+ff08vX4/fz6/YD78fDy8PLp7/Hu7OXk6ufn4enk2Me8vsbRz+y4hoqKi42Qj46QkJKTlZOQlJCMj5OSkpGTl4SWgJWXlpmZmJeWlZaXlpeWkpSWlZecnJeWmZqZmpiYmJmhoZ2fnJufnpydoZ+epaWepZ6gmpTnxfSbn56fnJ6gnZmamJWbmJqhnZejnpybm6Cim5eUoZ6cpaGlqaikoaWmp6OloKOon6inp62qp6emp6j/y/mWko6JiIeFiYmGiIqMgI2Qj4+SkpOVlZCPkJWYl5qbmZWRjo36gsOVk5aWkIyOlIuNj46TjIKUmJaSkZSZl5iYo6ikn5mboaOts66mn6Ock5aLiYqLl6Cbn5mUnJ+ipaOfoJedlJmYmZuZl56AqPCIi4uMj4eAhIaFhPeFhYmBhI6PiIiKl5yEgfr2+/yEgID66+vw19/x7vj68/D1597m3uPh5Ozu9oCHiYaDhYSA3b62zPCBhpCLhIiKhYaNnJmQi4iSjoj89oGJgISEgIaTlY6VkpmMkZecmZmYoIyKlpaXkoeO+NORl5edmZGeoJ2hqrSZmpelpqa0uMS5wba2v8vJwsfM6O3h9e7z+4eJgI+Jh4f9gYP5goHv2s3X6d3Kw8Gkq7GnloiOi4OKiIDy6OLb4NjTy76wn5egmJaUsq+1yc/Vzeba2+DUt7XIyNzf6veD9LeAj4uJgICGiYeGgoCGhfyAiJSik52imqyrsLy8wc/W0cjU2N7n7uzs+/fx6+/wg4iGh/iC6drJxciwGZOZvci8zs3PxszPzMjN0cvV19LHvayRlJGAdIWIgYqJgoF3cHl3eX+Be4CCf396cHJ8f3lycXNucmlucXt0cn19f3t9foOFfndvbHBxbm11b21mYFxfYGC1lLhsb2lpc3V0f31+gHdtcnRucHNxbmxscXNycnR4c3BwbmxwcHFxb3ZwbG5wb3B4eHN4enhzdWd1ZGRtcnNtcnGAb21vcGxub3J1dnh8eXR6enV2eXt1eoGEg4GAhoqFfn+DhoJ/g4B1dn6Bf4CCrnCHhouRiHp9fn9/h4yQjIeGe2tvh4qKiYaDhYWDiYmJiomHhoSCfoGKi4uKhoeIiIqLi4WHiIODgH9ooqdoa25vb29wcHBxbWxramttbHBub25QbGtrbWtrampramlnZ2ZnZWdmZmdpampramxsaWlqamppZ2hnZmVkZWhoaWhqa2pqZ2hnZmZnZWZkZmVkXlWjpqtZXV9qd3h2eXh5eHd1dnSEc4B0cnR2dXV1c3Nyb3Bub29vcHJyb25xcnNxcHFzc3Fxb3Bwbm1ubm5tbm5tb25pZ2tua2lnZGJkZmRiYl1dklRUVFFVVFBSVVFVU56boaGin5+cnJ+ip6SiqFWlmpadn6OYnKCcmpWVl5aXkpqWj4V+gIiTlJ9+XV9eYWJiYmNlZoBpaWlnY2djYGJmZGRkZWhoaGdnZmhna2tramhmZ2lnaGZkZ2hmaWxqZ2ZpbGtraGloaG9va21ra25tbG1wb292dW11bXBsZ6CIqm5xcnJvcHFua2ppZmxpaW9rZ3Bsa2pqb3FrZmRva2l0cHN4dXBucnR0cHJscHVudXR1eXd1dYBzdXa0haFhXltYV1VUWFdVV1hYWFpZV1lYWVpbV1dYXF5cXV9dW1hXVphTiG1tb29rZmdsY2VmZmllVmtvbWpqbG5sbm11enRwa21ydH6BfndydW9oaV9eX2FpcW9zcGxydHh7eXV3bXJucW9xcnFvdFx1qGBhYGNlX1peXVxbqYBdW15XWWFgXFtdZ21bWayrsbFeW7GjpKqVnaqmra+opaugmaGfoZydoaGnWV1gYFxdXViag32Sr19jbmplZ2djY2l0c2poZW1qZryzYGVbX2BcYm5uaG5udGhscXVzcm93ZmJvbm9qYGe0k2dsa3BvaHFycHF4gmttanR0dIGDioCCh319g4yMhoeIoaWbq6eqsF9gZmFeX7NbXa5cXauak5qooZOMi3Z5fXZpYGZjX2RjXK+pop2fnJaRhn1wanFqaGd7eX+Pk5aPpJeXnJeEgY6PnqCqtF6xgF5sZ2ZeXmNnZWNgXmFjuV9ibntvdnlwgIB9iYuOm56YkJmboamysCyturauqq+zY2ZkZrZhsKWYlpeDam6KlYiZmJiPlpiWk5ealZudmY+Je2ZpZoBPXF5YXl5ZWVNNVFJTV1pWWFtZWVVOUFdZU0xKS0hKRUtJUE1KUVJSUFFTVldUT0pJTk5NTFFNTEVCQENFR4twj1ZbU1RfYl5kYmRmX1VaXFVWXFpWVlRZXFpZWl1bWVhXVFpbXFtXXVpWVVdZW2NkXmRnZV5hVlhVU19lZ19kYn9hXmBiXV9gZGZjZ2pmYGZmYGFmaWNncXFwbm92d3BqbW5taWhsa2BgaGloam2HXXFxdXVrYmdnZ2ZscnVybm5jUFZkZ2loZGFlYmFnaGdoZmVjYmFdYWlra2lkZmZmaWpoYmRlYWNgYk93bkJDRkhISUlKSUpHRkVFRUdKTklJhEgaSUZFRkdJSEdEQ0JCQEFBQUJFRUZGRkpKSUiESYBISEdGRUNDRkdISEtNTEtJSkhJSkhGRkZIRkdCPHZ+gURHSE9dW1ldXV5eXFpcW1lZWVpdW1xeW1xdXVtcWVhVVVVWV1laV1dZXV5bWlxdXFtbWVpaWFdYWltZWlxbXl1XVVdaWVZWVFJTVFFOT0tIbj49PTs9PDc5PTg+PG5rcoBzdnNxcXJ1eX96d39CfXBsdXh7cnZ4c29ucHFucWx1cGxlXGRveHyAZUtLSk9RU1JRVFRXWFhTTFBOTE5QT1FRUldUU1JRT1NSVVRTUlJQUVNSU1BQVFNPUVVTUVBSVFJTT1FPUlxYU1ZTUVVUU1ZbWFhgXVRdVllVU39qh1hZWYBYVlhYVVNST05UT1BUUExTT05PUFNTTkxMU01NWFNXXFdRT1RYWFNUT1JWT1VUVVpXVlVVV1iDYGg8Ozk3NjU0NzY1NTc3NTU0MzQzMzQ0MjIzNjY1Njc1NTMzMlk0a1hXWVlUT1FVTE1OTVFPQVRXVVJRUlRTVFNaXVhUT09VVoBfYV9ZU1ZRSkpEQ0RFTVRTVVNQVlhdYF5aWlRZVFZWVldWVVlGV4BISUpJSkZBQ0NBPnVAPj85OkJCPz5AR00/PXRyd3pCQH94e4Fxd4J9g4d/fIV6cHRydXBwc3B0PkNFRURGSEZ3ZGJxjE1QWVVQUlBNTVBXV1FPS1FST4yHSoBOR0pKR0pUVVBUUllOUVRYVFFSWUtHUlFTT0ZKf2hKTUxRU01TUk9QVVtKSklPTk9WVVhTVlBNUVVTUE5NW11YYF1fXjMzNjQxM2IyNF80NWNdWFxnZV1aXlNSVFBHREpKR0tLR4iGgX1/fXh3c25jXmRdXFlsaWp2eHlxgHd4fV99dHF8eoOBh49Li2RKVVFRS0xPU1FQTUpPUJNKTlhjV1xfVGBgXGJiZW1sZl5kZGdscG1qcWxnZGVnOz49PnE+dHJoZ2ldTE1ial5tbW5ma21raW1va3BxbmlkWUlKRL5/g362fwF+tX8BfpN/qICCf9OAg3/PgIJ/jICPfwGAmn8Cfn/RgIN/uYADf35/o4ADf39+jn8Bfrd/gn6LfwF+jn+EfoJ/l36If4V+kn+Cfp1/gn6mf4aABn+AgH+AgJV/pH4Df35+jn8Bfp9/hIACf4CffwICBACAycK3sr+ysbWwraW5tLiyubq3t66ns7aysKmmoquktbW0sra5u7y8vb28uLu/u7Czsa2op6SloZqbnZmZluLylp6ZkJGcnpygpKWjoZ2TmZuVlZmbmJeVkpKUk5WXm5uVkZOTi42Tkp6hl5GPkI+Rl5aVmaKanN63hYqGiYuMjZCAkJeOjImVlp6XnqGenqGXnJ2dnZ6hm6OwsqytrK6vqqOnqKioq7W3sq2qr66xqOKatrWzs8rDt7a6t7i/wsTCvbGbjba4uLa3uby9vLy+vLq8v7q6vL/AvL/Bwb/Avr68wcG+u7++vLy6taX87I6oqaanqKemoqOkpKanpaSkp6ghpqirqaelpKGhoqSjoaGfoJ6goqGio6OmpKKjo6GhoqOihKGAoKOioZ+goaChoZ+enJuamJiYm5mYmZuamZeXmZqZlIyB+PX/goOEi5GXnKOlp6empqanpqSlqKmlpKWkoaGjo6Oln6CkpKOenp6foJucm5udn6KcmpygnZqbnpuYmJWXlZWVlpORkY2QkI/Es4OGhISEh4eFhIeIh4WC9vX+/PaA9fj39Pf1+PX19ezs7e3x6u3r4NXBwMTCytbb3fWIko+PkY+IvKmEi4qOi4mKj5GPkI+MjI+NjZGRlJiVk5KQjZOUlJmSkJCXmpaSjpGXlpSYmJWYmJmbmZaWmJiVlZqYlpqcnJuZnJ6koaebnKKhoaKglYPO04qhoZ+enZ+bmJw7npaWlZuampmZnJydmp+hn5Wcn5mXmp2ip6SZoKifnKOkp6emoaOioqaopqWnn6SjooPV6JKQj4yKh4mEi4CIio6Ok5OTl5iYmZSQjIuOkZWXko2Kiovozt2Oj5OZn56QiZSOjI+QlJeagZ+koZuXlYyDhpmgoZyZn5uWm6Ksqqqtn5aZpJ+imJWSlJCTmZybl5iWmKShnZqalpqXlJSXkovDv4CKiISCh4uIgoCG/4D/hpKcoJCWlpGIhPuChYCD/v789uTj6uDp7+fu7uHw6+Hi5N/g5+nn8YCCiI6KhYD+8uTo7OnLsbvU7Prz+oKGi4+Ln56UnJeIhYeGio2JiYSSjIqPkIuLjJiUjJSgmqmUlJyhnpH62PugopOMl5qFip6coJ6OkJeYpK2uq6i3samyrrS3vsq2p9PNyszEy4Dh6u3l6/mDgoqSlZeWjYqJjoyEh4j/gPaE9/T94drey7++s6SyqKihrp+cm5OIh+jd3ca9vbjL0djx+Ofd6t3X6Nr2/vqEkpOBwIaOk5iWl5aXnaaeoaqtuLzLzMTF2OLq8PSAgoGB+/6DhoKAiZCFgoCA+ebn6tnVzMfBytDHyhzIt6ystKOHnLq9xb60ub/CwcXFxLSfjZuqvcO+gI+If3yGfHx/endxgH2AeoODgIB5cn1/e3t1cm9zand4eHR4enx+fXx9fHp7f352eHd1cXFub2tmZmhlZWOYqW10b2VlcXV0dnp7eXh2b3J0bm1zdHBwb21ucG9wb3N0b25vb2hpbmx3e3Nta2xrbXNxcHV+dXqpi2JlZGhqa2tvgHB2bGpmb294c3d4dnp+cnZ2d3d5eXV8g4WBgoKDhIF6fn59fX6GiIJ9fH98gX6fb4SBf36RjIKChYOFiIuNjIl9Z2WCgYF/gYOGhYSEh4SDhoiEg4WGh4SGiImHh4aHhYmIhoKFhIKCgH1xq5Zcbm5sbG5ta2dqa2psbWpqamxsgGxtcG5sa2xnZ2doaGZmZWZmaGhnaGpqbGpqa2pqamttbGxsa2lpa2tramtsa2xramlnZWVkZGRnZmRlZmZkYmJjZGRgW1SjpatYWlpdY2psb3JzcXFxcnVzcXJ0dHJyc3NvbnBwcHFucXJycm1tbGxubGtra3BxdG9tbnFwbW1wBG1qaWeFaIBmZGRiZWNihnVUVlRVVVZWVFNXWVhXU5yao6WkoqSjnqKdn6CioZSVmpyfl52el41+f4OFjJaYnq9hamhoamdjiXZcYF9iYV9gYmRjZmRgX2NhYWVkZmpnZWRiX2ZoZ2plYmNpamdkY2VqZ2VpaGVpaGhpaGZmaGhmZ2tpaGlraiFpaGptcnB0amtycXJycmlckI9gcXBvbW1ua2lsbWdoZmuEaYBsa2xpa21tZmtvaWhrbXF1cmdvdm9rcXJ0c3Jtb29vdHZycXJscnFvXIeSXFxbWlhVV1hYWVlWWFpZW1tbXV1dXltYVVVYWlxdW1hVVlmUhZxpaWtwdnNoY2xmY2Vnam5rW3J2dXBubGRcX29zc29qcG1obnV7enp7b2lsdXF0aoBnZmlmam9xbmxrbG13dXFwcm5wb2lqbWljh4dbYmBdXGBhYFpYXq5XrVtla25iZmZlXVywXFxbsq+sq52epZuip5+mp5qrqZ6gopmaoKOgpllbYGVhX1uzrqWmqKeQe4OZrLiytmBkaWxpd3VudXBkYmZiZGhlZmJuaWdtbWloaYBybWZteHF+bG1zdnVqtJiydHZqY2xxX2BwcXJyZGVrbHR5end1gHt2e3h9foWPfHCOjoyOiY6fpaehoqxbW2JmaGtsZGFhZGReYWK2Wqtesa+3pZ2hk4uHgHR/d3hyfnJubWlgYKWdnYqBg4GQlputsaSdqZ+aqZ6zt7JfaGpdhVJha2xwbXBwcXR+dnd+goqMmJqSkJ+nrrS0YGJhYLm6X2FgXmNrYV9dXbSqq6+ioJiTkJidmJmYh3+Ah3lgcIeLk42Eh4qNi4+OjYBwZW55homFgGNfWFZeVVVXVFJNWVdaWF5eXFxWUVhaWVhST0tNRk1OT0xOUFFQUFBSUlBRVFRQU1VUT05MTEpFRUdGRkdse1NdWVJTXF5cXF9gYWFgWV5fV1VcXlpaWllZWllbW11cWFZWVlNVWlhhZFxYVllaWl9dXGFqY2eKb1ZZVlhbXV5hW2FoXltWYWNsZWlqZmptYWVlZWdpZ15lb3FsbWxtbmplampnZmdzdGxmYWZma2h9W29sZ2R2cWhpbmtrb2xwcHBlTEpfYF9cXmBiYmFiZWNhY2VhYGJnaGNmaGaEZVRjZ2dlYWRjYWFfXFN9cDtGRkVHRkVEQUNEREdIR0VGSElHSUxJR0ZFQkNDQ0JBQT9AQERHRUVGR0lHRklISElKS0tLSklIR0tKS0lKS0tMTUtKSEeERgRJR0ZGhEeASElIRkRBPXl6gkVIRkdMUE9WWFpXV1lZW1tbXF1dXFtcXFdXWFdXWlZYW1xdVlVSUlhVVFVWW11gWllaXVxZWl5aV1ZTVVRUVFVUU1NQUVBPcVc7PTw9PT48OTo/QkJAPW5seXx6dnh6dnt3eHd6eGhqbnF1bHV5c2ZdYWttdn6AgombVl1cW11XVHRdR0xMUU9NTFFTUVJRTEtQT09TUlNVUlBOTUtRU1JUTkxNUlRRTUxRVVJOUVJPVFFQUlBPT1JQTU5TT09TU1FPTVBSWFddUlRaWFdYV1JGbnBLWFpaVlVVUk9TVExOTVJPUE9OUlFQTVBRUUtRU05NUFFUV1KASVJZUU9WVldUVFBSUVFWWFRRUkxSUlFEYF84ODg3NjU1NjY3NzU2ODU1NDQ1NTY3NDIxMjM0Njc0MzIzNFpbe1RUVlpfW1FMVU9MTU9TVlBFV1xdWVVSTEZIVlpXU05ST0tRVVtZWFtRS05WVFZOTEtOTE9TVVNRUlRVYF1YVVeAVFVUUVFUT0lhYUNISEVDRkdFPzxBdDlxPEBGSD5CREM/QHpAPz99fnx5c3N4cHV6dn98cIKAd3h4cG9zdHF3QUFFSEVDQYF6cnh9emddZXqLkoqOS0xPUU5aWVNaVUpITEpNUE5OTFROS1FSTk5OV1NOUVlUX09RVlhWTH1qfVSAVk5IUFRFRVJRVFJGRkpMUVRVUk9YU1BTUFFQUlpNQ1ZWVFZQU1tgX1xaYDIyNjg4Ozo1NDQ1NjM1NmczYTViYWllYGFbWVlYUFdRUE5VTkxLSURHgnd5cWttaXN2e4qRiIGLhoOPhJOUjElQT0dnSlJUV1RVVVNWXFdXWVtgX2dBaGJga29ydHQ9Pz07b243OTg3O0A6OTc2bWlscWtraGlnb3RxcnFhW1xjWkVPYGVrZl5eYmVkaGdmW05HTVVdX1q9f4J+t3+CfrZ/AX6Tf6mAgn/ZgIN/x4CCf46Aon+HgIJ/zoCCf7yAAn5/o4ADf35+j38Bfrh/gn6LfwN+f36KfwR+f39/mX6Hf45+qH+Dfqx/j4AEf4B/gJZ/ln6EfwF+mX+EgIJ/ioCpfwICBACAwbqwuruwt7uuuLSxs7Sysa2tpaavr66npqOlrqymrrS9ubO2s7u1tLi5rqaknKCosauioZycnJmZjobl04eSl5KSk5WZnaOhnaOgnZyemJeWk5aTkpWSkYyMjpCRkJKUk5SWjIqZlZORlJiQjZCNoZuRjZiZmqvD/YCGjY+FiI2AjpKRmJeUmZeTnpuipZ6YmJedqq2jqqurrKCor6amqqajqq2wsa+lrrW1r62wsqndnbOvsbKxtra1s7G2vb66v8GslPy0v8G1sra8vby5urm9u7q7t7i5vb28vrq7u7y8t7a7vrzAvrq0sbK3sZDp65WlpqOhoJ6enqCioJ6en54un6ClpqOlo6GipKOioaGho6GgpKOjn56fn6CgnJ2enZqbnJ6enpybnpucnZ6enISeA5ycmYSagJmZmJaXl5aUk5aWlpSTkZGPj4+Lg/Tr7fLz8vyBgIWLlJifoqKinZygnqGhn52cm5ucoJ6dn56cn52dnpydm5aWmJGXmpiYm5qYmpmYlpaXmJaUko6OjPeThYiIh4aFhYGA/v78/Pj3+fTx9/Tp5uzq5+vh08bAxcfN1dXW4uj5gIePkpKPkI+WmZKPj46MjpCP+5vXiouMjpKUjoqOkpSVlZKRkY+UlpaUkpOVl5OXlpKTlZWXlI+UlZOVlJOWlJWTmZSZmZiamZmYmJiQlZKVmZebo6Chn5yfo5ydlN+685uhoJ2hnpicmpuYoJuanJWZl5iYlZaXoZ6ZmJaYlpybgJKanqGjoaOcnqWin6Kdoqamnp+ao6ehqKakoKKD0uWNjpSQkI6OjI2Lio+Pj5CRkJCRlZaTk5OSjpCOjpGSkY2Lh7ip24KLj5CUoJudmZKUkpGUlJ7YyZmio56ioZ2Sg42WlJeenaGwnJabpqajrauqoZugnZSai4yZnZqfnZ2XgJ2SlZSSjpSYj46TlpeWl/es3vCDkITw/On4iYmBh4SDiJSXjJGRjpSXjYWC/YSQjIuD7+/y8uXg5uLl49XR2urn4ubn2+Xl5ez09vj/gfvj5+3o6ebewbS6ye73gYaEjISEk46SmZiRipOOi4iCgomMjo6emZaci4+bmpiXkpiigJbc3pqnopiTjJeUhYiWm5WVlpaNiKSol6Gim6Sim5WWo660srW3uK7NxMjWwsOzusjQ1+Dd2+XU5/zy9v2A/f7+hZGPmZ6UkpOTkIyGgICJgvn5kYz/+/76/+/Yy8jMycC4sb/IwcC9vLSuu7K8xLe+zMzGup/CxsfU1OPa4OLjR+rb1dfl/4CBhYiUoaGfoJ+aj4eHiYqH+NzR197Vy8PAwcfBwsfSztDAsrnAxcPEuLmvnO6Hs7S0v8O/r56PkZ+xvsHBtbPBgIqCe4OFe4CDeoF9ent9fHx5enNze3x5dHNwcnh2cXZ5fnl2eXV9eHh6e3NsamRobnVybWtoZmZkZFxXlY5ia3BqamtucXN4d3R4d3V1d3NxcG5ybm1yb21ramxra2tsbnBwb2dlcW5tbG9xbGlsa352bGh0dnh/lMFhZmhrZmhtgG5wb3Nyb3J0b3d1e354cnVzeIKEe4GBgYN3fYN+foF+eX+CgoOCe4CFhH96fYN+l3ODf4GCf4OCgX9+goSFhYmLeWS4goqKfnt/hIWEgYOChoSEhICCgoaGhIeEg4KEhIB+g4WDioaBfHp7gHpgm51iamtpZ2ZlZWZnaWdlZWZkgGNkaWpnaWdlZmlpZ2ZlZWlpaGtramdmZ2ZnZ2RlZmdlZWVnZmZmZWhlZmZmZ2ZoaWhoZ2hmZmdoaGZlZGJjY2JhYGJiYmFfXV5dXl5cVqCam6GlpKpXVlpfZ2pvcnFwbWxvbXBvbGttbG1ub25tbWxrbm1ucG5ubGloamNqbGtqgGxsamxramhoamxpZmVhYF+vYVlbWlhXVVZTUaKipaSfnaKhoKWilZOamJeYkoqDgYeJj5WWmaOnsWBna2xoaWZsbWhlZ2ZkZWdmtmmWYV9fY2VmYl9jZmhoZ2ZlZGJnaWlnZmZmaGVpaGRlaGhqZ2JlZmVmZGVmZWRkamZqamlrbWtraWlpY2ZjZGhmaXFvcG5scXRrbmidgKhscHBscW1obGtranFsamxnamhpamdoaXBtaWdmamdsa2RqbG9xcHJsbnRwbXBqcnV1bm5rcnVuc3JvbXBch5BYWV1aW1lYVlZUVVpaW1paWVhZXF2EW4BYWVhXWVpaWFZTdnCaXWZpaWx2cXFsZ2loZ2hpc5yOb3d3dHd2c2lbZW1rbXFvc31uaGx0dXJ9fHtzbW9uZ2xgY29ybnNycGpxZmpnZ2RqbWZmaWttbGyvdZ+oXWdfqbOjr2JiW2BcW11laGBjY2FlaGFaWa5bY2FiXKWmqamdl0GgnJ+flJOap6OeoaCWnp+gprCwsLRasqGhpqOnpqCHfYWRr7RgZWZrY2Fuam52dGxlb25rZ2JgZWpqZnVwbXVmZ4RxgGxxeG6WnXJ7d25sZW5tYGNucmxubGtjXnR3anJybHJya2lpcnyAfoCChHqRjZCYiYp+f4qSmKCdmqOWo7StrrNbtLW0X2lnbnNqaGhpaGdhW1tiXbGuZ2O0rLGvtKuZjYyQjomEgIiRjImGhoF+ioGJkYWJkpKMiXKQlJGbnKqgSqWko6ydmJylulteYGNve3p3dnRxaGJjZGZjtaCXnKSemJGOkpmPkpehm52Qg4uQlZKUi4uDdapggoKDjI6LfXJmaHJ+hoiJgX6KWV9aVV1dV1laU1hWVFdaWVlXV1JTWFdWU1FOTlJPS01OUk5MT0tPTU1QUEtHR0NGSk9NS0pHRkdFRD47amtQV1hSVFZXWlxgXlteXlxcYFxbWlhcWFZbWllXhFWAVFVYV1dZUlFdW11cX2JcWlxZbmVbV2RlamV5o1FWWl1YWl5gYWFnZWBlY2BrZ2tvaGNjX2Vxc2hwb29wYmpwZmZpZGFpbG5wb2RqcW9oYmVua3ZgbGRkZWZoaGpmZWdoa2xxdGFQiGBpaV1aXmNlZGBiYmZjYmNfYWJmZWNmYmFlYGJiXF1jY2FoZF9bWlxgXUpxZz9EREJBQUA/QUNEQ0FBQT8+P0JEQUJBQEFCQkFBQ0JFREJHR0dDQkNDRUVCREZGQ0RERkZGRERIRkdISEdGSUpJSkhJR0hISElHRkZERUdHRUSFRYBDQ0JCQ0E9b210eHt8gkJDRktSVFdZWVhUUVVTVFRTU1VTVFZYVlVVVFZaWVlbWFpaVVRWUFVXVVRWVlZaWVdWWFlbWVdWT09OkElDQkFBQDw8Ojp1dnh4dXZ5eXd7eG5vcnBxb2plYWFmbHZ9f4aRlKFXXmJjYGBdY2JbWFlYVIBXWFidWHVNTE1SVldQTVBVVlVTUVFRT1NUVFJQUVBRT1NRTVBTUlVSTlFRTU1LTk5MTk5TUFVSUVNTU1FRUkpMS01RTlFZVldVVFhcVVdTe1+CU1VVUlhWT1RRUlFXUlJUTE9QT09LS09VTktLTFBPU1BKUFFSVFJUT1NYU1BSTiZUWVdOT0xUVE1TUk9OUUNfXTY3ODY3NTMyMzEzNjY2NDQyMjM0NYQ0gDIyMTEzNDU0MjFIUXlKUlRUVl1ZWFVRU1JRUlJceGtWXF1bXlxZUUZPVVJTVFNVXFBMT1dWU11cW1VQUlFMT0RHU1ZUVVJSTFRNUFBQTVFUT05QUVNTUYJVeoFESkR3gnV7RkU9QT4+P0ZHPkA+P0NIQzw7eUFJRkZDeHp8fHJtgHZwcXRqaG58eXV3dm53dHJ2e3p5fD18cXR7eHh2dGJdZG2HkkpMTFJMSVFNT1VVUUtSUk5MR0ZMTlBNWVZSWUtNVVVUVVBUWU5pcVBYV1FPSlFQSEpSVVBPTk5GQVJVSlFQTE9OS0lITVJVUlRVVExaV1lgVlZOT1VZXGJgWmFYgF1lYmNkM2RkYjQ5ODo7Njc4OTk4NDIyNjVkYTk4ZF1gYWRiWFFSVFVVUlBXX11bWVpWVF1XXWNYW19dV1hMX2BdY2NsZGZkY2hdXF1gbjU3OTpCSkpGREJAOzg5Oz49cWRgZm5samloa3Fqam53cnRpXmVpbW1vZ2hgVn5FXlxcD2NmZFtSSUlRWV5gYFpZYbt/gn65f4N+tn8BfpR/qoCCf96Ah3+7gIJ/iYCef5GAg3/JgIN/vYACfn+jgAN/fn6Qf4J+uH+EfoN/hH6SfwF+hX+bfgF/jn6lf4J+un8EgH9/f5CABH9/gICxf5GAnH8BfpN/AgIEAIC5t62tsKapqqOlrayio6annp2doaKjoqWorayss7OxsLS2s66xsrSrrqOgoKCjpKShnJebn5iUj5CC0u2HlJSRkI2Wk5GamZiamZOUmpqUkpeUlJGRj42Pk4uLi42NiYuPj4uLiY6MhoGLkI+LkJCYlZOWlJeJme+BiIyF/PqChUeGgoeKioOIkpKRl5udnp+YmJehqKanqaqrqKaopqKmqqunqJyjnZSPkZOSkYuPjofAmrewraqtq661uamzuri7v7+4mtS2uYS2cLS2trW3tre3uLu3tba3u7a2tre5u7y5t7a5u7u6uLKus7WyrqSE4P6bo6SnpqSfm56fnp2ZmZyeoaShn6Gjo6SkoqWlpaGen5+enZycm5yZmZubmpiYmJmanJiWmZiamJiWlZiam5ucmpqYlpaYlZWEkYCPj46NjY6KiIyKi4yMjIuNjI6MioyOjIfB1Y2Jg4D7+v79/Pjx+fz+9/yIjZCWlZWVlJqYl5mZmZiUmJiXmZaWmJmUk5KRkpOVk5WRj5CRj42JjeiFhYWEgvr49O/r2MvFyMrJy9LU1Nrg5Onw+4CFj5ONj5GNkpCTkpCQkY+PkICPjpGRjYyPj46Oj4uN2aT1ko6IjJKSkpaTkJGNlJCQkpKVk5aSkZOVlpCMkZWRkJKQj5aWlJeYlJOZlpaam5yYlZaYkJCRkpaWlJCXmZydmZqempDuzuL04d/lhJmenpyempqZmpyWlZ2fkZOcmpWQk5iWl5ydmJqdnJyZmZWdo4Chm5mlpKGhpp2jpZ+eoKGhm6Gjnpf/3+SQjo+Uk46MkZCOjpGSkpOOjY2RkpOSkI2Qk5KUk5GOjoyH9IOyh5CHh4WKk5eYpKWil5WSkZaYh5yVo6afoKGgm5+YmZmVl5ufpKSen56en6WkrraxoKCbl5SYj5aclZeanJiSl5eWjoCQh4WPlpCTk5WZk9mw7fuA/fqAhvj4gZGJi4qD+oCKko2Th4mGh/uNkY6OkIT9gP3w7Ov7/9bo5efn3trW1Nbl6eTr6ejp/IKDgujwgYD54eHd4ODDuMrQ7PiAgYOEi4uNhYeMkY+Mio6KiISOkI6UnZOXnZOWmJmaisv+k5SZmoCakY6TpKmmo5yPiZiZloOFhf7ug4aLlYiNmJ+foJqUnJ+ZoaCZmqCjpqmqsLnAvrvM0s290M3L7d/Vy9nj5faEioLt9oSQi4aCiYuFjJOTiIOJio6IkZmUlJaUkI2IjouGhIWChIWFhIP9/+bi2e3UtIKGi4eFhYaHiIqPkYuKiUGHjIyMiouKgu/p4eDc19bDs627vrm1rrXAzcyzts3Jv8K1rJ2cuM3IvbOwprO5rYrj/IuNlaW4usLDwr28trbDwYCCgXh6fXR1dnFyeHlvcXN0bWxtcHBxcHR0eHZ1enp4dnh5d3R1dnhwcmpoaWlsbGtqaGVnaWNhX2BXi6RhbW5qaGVvbWlzcXFycm1vdXRvbXJxcW5ubGhqbmlpaWpqZ2hramdnZmppZWJqbGtnbW11dHF0cnZra7NhZ2lkvLpiZntmYmZpZ2NncG1qc3Z4enx2dnV9gn9+f3+Cf3t+fHt/goF5e3B2cm1pampoZWBmZV+Db4SBfnx+e36Dh3qAhYaJi4yHbZmGhYGAf398e3x+goCBgoSFgX+Af4R/gIGBg4aJhYGAgIOEg4F7eHx/fXp1WpmoZWlpa2xqZ2OEZoBhYGNlZmlmZmlqa25tamxsbWpnaGlnZGRkYmNhYWJjYmFgYGFiY2JhY2NkYmJgYWJjZGVmZWVkYmFiYWFgX15eXl1dXFxdWldaWltbW1pYWlpbWlhaW1pYg5BjYl1cuLa1sa+trK+wsauoYGNkaGdmZ2ZqaGlrampoZGhoZ2lnZoBqbGdmZmZlZmhmaGVkZWVjYFxglVZXV1dVoqGdmJeMhoGBg4WHjZCUmZ2hpq22XWFpa2ZpaWVramxraWpraWlqaGZoZ2RhZGVmZ2ZiZJVxrGViXmJmZmVoZmNkX2VjY2RkaGZoZmRlZ2hjYGRmY2NlZGNoaGZpamdmamhobG1saIBmZ2hjY2RkaGdkY2hpbGxqa29sZqmRm6ScnJ1bbHBvbW9qa2lqbWdmbG1kZmxraGNkaWdobG5paWtrbGlpZW1ycWxpdHJwb3RscXJubnBwb2pvcWxnso+PWlhaXl1ZWFtZV1dZWltcV1ZWWVlaWllWWVxbXVxaWFhXVJpTdGFnX4BgX2Rrb294eHRramdna21gaGt0eHN1dnZxdWxub2xwcHF0dHBxb29xdXZ+iIRzcWxpaGtka3FrbnBxb2tsbW5maGFeaGxnaGlrb2qafaiyWrKvWV2usFtmYWJhW61XW2JbZFxfXV2rYmRiYmNcrFewp6WlrK+XpKKjpZ2Zl5aWn4Cin6Wko6S1XV5eqaxcW7KdmpmamoR+j5asuWFgYWFnZ2hiZWlta2hlamZlYWlraGxza3B1bG5xc3NmjLZqbG9ycWtnbHp+fHlzZmJtb2pcXV62qV9fYmxfZG5xcnRvaW9xbXJybW1xcnR4d3yDioaEkJSRhZOSkKyimpCbpKWwXjZiXqmuXGdkX1thZF5jaGlhXGFhZmBmbmtqbGpnZmJnZWJfYF5fYWFgYLe6pJ+Yqpp9X2FmY2GFYEZmZ2RiYF5kZWdmZWdeq6Who6Gfno6BgI6QjId/hpCcnIeIm5mQk4iAcnGIm5iQh4V8h42BZZ6yZGNqd4aGjYyNiYaBgYyKgFtaUlVYUlFQTlFWV1BSU1VQTk1QT09OUFBTUlFSUE9NTUxMSktLTUpLRkVFRkdJSUlGQ0ZKQ0FCRT1mfEtWVVJRTVZWU1pYWF1cVldeX1pXXVtZV1dWUlNYUVFSU1NQU1VRUFFQVVNQTldcW1ZcW2FfXWFhZF5QlVJZW1SamlRXFFdRV1paU1liX15lZ2pqa2VkZGxwhG59cW1pa2ljaGVhXmVcYlxWU1VVU1FKUVJKYV1yaWNiZmVnb3Nla21scHNzcFdyZmRgX19eW1tdX2JhYWBjZWFfYF9kX19fXmFkZ2RiYmFiY2RiXFldX1tYUz9ob0FDQkRERUNAQkJCQT08Pj9ARUNDRUVFSEhFR0hIRUNFQ0KEQQRCPz5AhUGAQ0NDQUFDQ0REQ0FAQURFRkdFRkZEQ0NCREFBQEBBQkNDQkNBP0FAP0A/Pz9BP0A+PD4/Pj5fd1NQTUqXmZWQioWEho6NhYJNUE5RUVBRUVVUVFRVVVRSVVVTVlNSVVVRUlNTVFZXVVdVU1RVUk9NSWk9Pj49PHNxcG5sZWFfYGSAZ2lzdnmAg4eQlZxRVV1gWF9gXWNiZGBfYGFeXl5aVlpZVVRXWVpaWVVWfVyMU01IT1ZUUVRRTlBMU05NTk9TUFFPT1BRVE5KTlFPTlFQT1RTUFJSUE9UUlJWVVNOTk9RS0xNTVBQTkxRUVNTUVJXU02CbnqDe3h1RFJWVVRVUVKAUVNWTU1UUklOVFBMSUxPTExQUk9QUVJSUE1KUVdUT05YVFFRVU1SU09OUFJSTVJTT0qDZFs2NTY4NzY0NjQyMjQ0NTU0MzIxMTIyMjEyNTQ2NTQyMzMwXDVaTVBLTExRWVtaYF9ZUlJQUVZZT01WXWBaXV1bWVtUVFZTVVNSV1iAUlNSUVRZWmBmZFZTTktJTUlRWFBQUlNUUlFSU0xNRkZOU05PUVRXUnVbdnhAgH1ARX1+QUpFR0ZAcDc6PzxDPUFAQ3VDSEdGSEJ+QYF5enuBhGt1dHd3dHFvb3B2eHV2dHNzgkFCQnx+Q0KBbWhmaGxcWWtugI1KSEdJSUlLRkiATFFOTEpQT05LUVROUFVOU1pRU1VVU0hkhExOUlRUTkxQXF9eWlVKRU9STUJDRYR7RUVGTEJIT1FRUU5ITE1JTE1KS01MTU9PUVZbWVRcX11UW1tZaWNfWV9kZGY1NjRgZDU6ODUzNjkzNjg5NTIzMjQyNDk4ODo5OTg2Ojw6ODkCNzeEOVhtb15aWGJYSDc4Ozk3Njc2NDQ3OTg4ODY5Oz4+PkE7amhma2xub2RaWmZqZ2VeZGpzc2Jkc3Jsb2VeUlFjdHRtZWNdZ2pfSHB7SEhOVmFgZWRmYmBcXGRiun+Cfrp/gn6Ef4J+sn8BfpR/rICCf+SAgn+EgIx/qICCf4WAlX+fgIN/xICHf7qAA39+f6KAA39/fpN/AX66f4R+B39+fn9/fn6GfwF+iX8BfoZ/An5/mH4Hf39/fn5/f4x+oH+CfpV/gn6sfwWAgIB/f6WAiH+XgKh/gn6PfwICBACAp6mmoqOkn5+cop2do6alpaCgnJ6in6GfqLW2s7ayub60ubO1s6qqoJ6dnZyeoaGcmZWVkZWVjITY0YONkJaWlZOVo6KVl5uYlZGQkYyRlpCTjo+IjpWXk5CQlJWQiYuLiYaIjIyLioH0hYuOjo2IioyJjI+T9Jjo4u/+/vn5+PqA+PL1gIf9/YGFh4uQioaHh/+ChoWDiIaHiIWB/YODh4mKkIqYlpWeprDAu7q4sbi7oNOTrq6vsaCgqq+0q6qqsLSyr7Wftp+2t7KztLa1tri2trKxsbm5ubi6ubW3trO3tra0s7a0tbi5ube5tK+vrrCsnPrf95ihpaWjoqCgoaFYnp2hoKOjoaCfnaKioaKin5+eoKCamZqcnJuamJqbm5mbm5mZl5aWmZqZl5eXlpWWmJmZmpiQkpOTlJORkJCOjo+Pj5SWkpOUkpKSj46Njo6PjoyPkY3O2ISlgKamo6WoqaelpaKdlpOOioaC+vTz8/Py8PHp7Ovn6O7w8O7u7Onn4d3n4N/l4+Pd3eXn6OXkz9bm5ebo5enz9vyEi4mNkpSUk5OTlZKUkJCRkJGQko6IioyRkpKUlY+LjIiGjpGLjY6RkI+NjY6PkZCMhb2pg5CTlI6QkY+RkJGSfpCMkZOSk42OlZKSjpKTk5CRjo6WmZSRlZGVmZaXmJeYkZKTkI+PkZKTk5KSkpGOlpyYkIvszdmIl5qZmJmZi+rh3N2Dlpuek5GVnJqXlZGSlJeQlp2emJydlpOVmJqVlZ2bl56YlpijpKWfoKCgm6CkmZiempmW69n0k5KRkISSgJGRkI2Oj5GTkI2Oj5GRkY+OkJKQjZGPh4OBw7LQiJOWkpWVj5KVm6GenJaioZWZm5yZgp6ZlpeZnJaUmZ2alJKYn5ycm5menZqVl5agnpuprKelpJ2ek5CdmpWVlJmWnKKeoJuam5GWjY2Oj5GHwcPy8N7q+oeIhoKEg4aHhPWAgIyJhomHjoiB+vCCi4iFg/j0+P386uuB+efp6/bz69/f3t7g3+bn3Of+/PL8gPj56Ozs6u7r2dvi49HDvMLZ7f2BgoSKkJagmYaEiYiPkYmNl5eRmZWOpaWHx+WDhYmOiIKDi5Odo6Klp5+Tgv+OjITt+PL4hY2Hh4D2hZCVif6SgKGgi4iQ+4uSnp6hnZOjoY+ToqmonKKktLOmpqCbmau5093Fx9vY3Nnk2+Dv7+fm8+f09fLm8e70gPb99vjxgoWD8u/v9f6FhoLt7PTw69nRzbSa2ODX2unj7t3m6d3X2uHf4+jXzdG/tMDBvsC/t7errrKnoL3VxsXGu6+zsri6IrKtraqyt7StqqmrsbWikIz0zcfB2ZC2t8DDsq+5vb24trGAdXZ0cXNzcHBscWxscXRzc25ta21wbW5tdH9+e315foN5fXZ4dW1tZ2dlZWdqbWxoZmJiXmJiXFiRkV5lZ2ttbWpreHZscHNxbmxrbWltcmxwa21na3Bybmtrb3BrZ2lpaGZnaWhoZ1+yZGhtbWtlaGlnbXB0uWasqrS+vbm6ub2Avbm/ZGfAvmBlZmtvaWhoZ8RjY2BgZmVlZ2RhvGNkZWVlamRzc3F2e4STjo2KhImMd41qgH5/gHJze36EfHh4foGAf4Z0gXWEgnx9foGAgIB/gH18fIOFhYOFg36Af32AgIF+foF/gIKEhIKBfXl5eHt5bqyZomZqbGxqaWhoaWmAZmVnaGxsamlpZ2tsa2xsaWhnaWlkY2NkY2NjYmJjZGNlZWNjYmJhZGRiYWJiYWJiYmNjZGNeX2BfX2BfXl5dXl9fX2FiYGFiX15eXVxbXFxdXFxgYl+KlHZ2dXZ4end6fHx5d3l3c2xpZmRgXbKxsrKtqqmsp6mpp6qsrKypqagjpqWioKWhn6GenZ2jpaSjoqKWmKSkp6WjqbCzuF9kYmVqbWyEa1BqbGhoaGdqampnZGVmaWprbm1pZWZjYmhrZWZoaWdmZGVmZ2lnY16DeV5naGhgYmRiZGJkZGRhZGZmZmBgaGVmYmVmZmNkYWJoa2dkaGRoa4VpgGJjZGJhYGRlZGVkYmJjYWdtaWVip5CWX2lsa2lqamGgmZeWWWdrbGNjaG1raGdjZGZpYmlub2lrbWhkZWdqZWVqamhsaGZncXJzbm9vb2tvc2hmbGlqZ6GLnF1cWltcXFxbW1taWFhYWVpYVlhYWVlZWFhZW1tZW1lUUlN+cYxfgGhrZ2pqZmlscHVycWx0c2ptbnFnXnVwbW1vcWxpbnBvbGltcnFxbm1xb21rbWx3dnF8gHl0dGxuaGZwbWtraW5scnVzdW5ucWluZmVnaWlgh4irqZyls2FiYF5fXV5gX69aY19cYF5lYFqxqVxiX11cr66usa6jpV2wnqWpsa+pgJ+enJicm6CglqG3t6+3XbS0pqakoKWkkpSXmY2DiYudqLhfYmNpbnJ6dGRiZmNrbGZpcnNudG5pfX5njqVfYWVqY11dY2p0eXZ3enRoW7FlZF6mrquxXmdgYVysX2dsYrFocnNkX2awYmlxcHNwaHV0ZWl0eXhwdnaCgXZ3cm1sgHmClp6Ki5yZmpecl56qqKKhrKGqrKidqKWtXLC1r7GrXmBhsa6us7hgYV2pp6yrqZqXk4JpnqWbnaymq6CipZyWm6GgpKqck5iLg4uNio6Mh4h/god+doyhlZWXjIGGhouMhYCBfoWKiIKAgIKGiHdnYqmIg4CRZYKFjIp8e4SJBImGg32AUFJRT1FTT1BNUk5OUlRUVE1MSk1PTE1LUFdWU1RSVVhQU0xNTEdIRERBQEVIS0pGREJBP0NDPz5pbExRVVpYVVFTX15TVVpbWlZUVlJXXVhbV1hTWFxeWlhXWFZVUFNUUU5QUlJUU0yTU1dbXFpUV1lWW2BlnE2VkJyjop2dm56AoJefVVWdpVVXV1pdWFhcW6lWVVFQVVRUVFBMlVBQUVFTWU9dYGBeZGt+enl1bnR3YWtaaWhqalxbZGhxaGdmamxraW9cYVpjYlxcXWBgYGFgYV1bWmJjY2NlY11gYF1fYGBfX2JgX2JjZGFlYVxaV1hYT35pZkFERkhHRURERkYwREJERElKSEZEREdHRkdHRURDRkdCQUFCQUFBQEBBQkFEREJCQkFBQkNCQkNCQ0JDhEQaRUJERENDQkJCQT9AQkJDQ0RCREZEQ0NBQUCFQlZER0hqdGJjY2JiZWVnaWlmYmNjX1pZV1ZTUZeVlJSRk5WUioyLiIuPkJGMi4mIi4uJi4OCiIOFg4WMjo6MjYKCh4uSjYiPl5ifU1lWWV9iYF5eX2FeYYRbgF1cXltWWVxfYGFiYFpVWVdWXF5WWFlbWllWVlhZXFpVUG5iTVVWU0tOT05RT1FRUExPUVBQS0xVUVNOUVNPTU5NTlRUT0xRTVFUUVRTUlJKS0xLTE1QTkxMSklJTEtRVE9LS4JucUlRVFRTVFVOgHZsaENMUVNKSlBUUExLSEtLgEtIUFRUTU9RTEtMTk1JSU5PTFFKSkxVVVZQUVNSTVJVSUlOS0xKdF1nODU0NTY2ODg3NjQzMzIyNDMyMjIzMzMyMTIzNDM0MzAvME1KbUlSU1JVVlFVV1pdWldRWllQVFhdTEpZVlNTVlhSU1daWFRSVFhWVVNRVVNSUVNTXFxbgGJlYFhXUlVOTFVUUFBPVVVXV1VWUVFVTVBOTU9QT0hjX3Z1aXF/R0hGQ0ZDQUREeD5HQ0BBQ0lEQH95Q0hFQ0OAgoeIhX5/SYl4fH2FhoF4eHd2dXR0dGtzgIB5gEJ9g3h5dXN4dWlra2tjXWVtfIaQSUhJTE1PUlNMS1FNUlNNgE9VVVFXVE1dXUpkdUVGSE5JRERKUFhcWlteV01Bf0lJRXh+e39ES0dHQnlDSUxEekdPT0ZBSH1ESlBPUE5IUFBFR05TUU1PT1hWTk9OS0pSVF1hV1ZiYF9cXlxjaWhjYGdeYWRhWl9dYzVnaWhpZDc6PG9vbXBzOzo4ZGFkY2RcU11bUkFkaWBibGhtY2dqZF9jamhscWlkZl1XYWRiZWZkZV1fYltVZnpzcnFpYWVmaGtkYGFdZGlpZGJhYWRmWUtFdVtUU2RHXF5hYllYXmJjYlxWuH+Cfq5/AX6Mf45+BH9/fn6JfwF+in8BfpV/AX6Uf62Ag3/ggIJ/lYCvf7KAgn/AgIN/iICEf7SAA39+f6KAA39+fpR/AX68f4d+iX8Bfol/gn6Ff4d+AX+VfgF/k36Zf4J+kX8Efn9/f4R+hX8BfoR/AX6GfwF+sn8BgIV/g4CFf4OAx3+Ffo1/AgIEAICjpJ+alpWao6OhoaixsKqro6OfoqOlp6yur66wrbi7tbGvqqimoqGioaCenpmVoJyZnJiRj4zWu+yOjpWWlJaXmKGkn52bnZSTko+NiYqRlpORi4+Oj4yRjY6LjoyMiYmMjo2KiYuGkI+Dg4WGgvuFjIqEjJS+4oeMjIqOjoqOjYCNlJCHjIiFjpCUjpianpSWkJifqbOxqZqju7KttLewrrG+vre9tcC7wb62rrm0sLOxoNWNsKyvrba0tbGxq7K4ubSurbKnnY2wsbG1trm4uri3tbazrLO0sbOxrK+wsbW2tLK0trC1tra3trSyrqytsK2qqqaY8NfxlJ6fnqCgn4CdnZ6dm5qYmqCenZ6dnZ6foJ+en6CfoJ+dmpeUkpOWlpWXm5iVlpWUlJSJjZKSk5aXlZORlZWUlpSUkpOWlJOSkJCOj5GUkZGSkZCQjY6Qj46Li4iGvdKkpKejo6GhoJ+fpKKjn56gn6OmoqGen5+gnp6ioaCeoaCfm5mZm5eWlxKVlpaanZyZmpydm5mampiYm5mElyiVk5aWlJORkZKSkpGRkI+Qj46QkpGRkI6QkpKMj5COjoyNiomMjY2LhI0UioqMj4yPjY2QjY+PjoOvuYaPkJCEkoCRjY2OkY+RkY6OlZWUkJCRjpCUkZKTlpWUlpKWlJKQlJWTlJORj5GOkZSanZqWmJucn5bo0NaFk5aPk5eRkJKWk5SRlJOE49ne3PKLmJOWoJ6Vl5qcnp2doJuQlpmamJeXmZ2hnp2doZygnZ+bnZ6dlJqhm6CcmJPS0eqTlpSSj4COi5OPjo6Pj4+RkpCIhYqOjIqLi4qSkI6Ni4qK+YWogJSZlI+WmJKimpybnZyUmZqhqqeiocbHn5ORkZOVm5yWiouRkJGPlZiUiZGTmZqZkpOJiJObpaerrKuqqqeflJKMkY2Pm56foqOalpKHhoeTlJKUgM7d/4D86Pb8g4OFgoCJiIuJhPnu64KAiZGE//+NgfHx8ubu8Ov384L66u/8+oKB+PHz6NnYz9DZ4ubp6fL79+fn6fTx/PXp5eHa3t7m7uLIwNHT5YaNmJ2flJaTn5aIl5SWiZOQkpuPztuOlJKPko6Hjo6PlJugopadoZmHjJKPl42Mh5OShIWNjJyWjoCKiI+NjZWXjpKMhpSbpK2lnKKYk4+hpKeTkpWapZugk5CTpqKpsr27tLW8wLextsbEx7TY0bjJ1d3ZzsnW3MW84Obu5t3TycPDxtHe4+Tg3tzIycC3u6aDqb67vb++wMfMwMK+xsLCubO8vr24vLm5w8m7taukoaOouby4squtuCawqqumpa6trKursbGdjIb32dfX3d3T2vuG/+fpiKSnrLG2saympYB0dHBrZ2ZrcnFxcHd/fnl3cHFucHBydHh5eHZ5d31/e3l2cXBtaWlqaWlnamZlbWhnaWZgYGCUgKllZm1ubG1tbHV7d3V0dm1tbWlnZWdscXBvaW5rbWltamppbGppZ2Zoa2tmZGdhamphYWNlYrllbWpmbXWPrWhpaWhsbWlrbIBsb2xna2hmbW5xbHZ4e3N1cHV7gYiGgHN6koqAiY2FgIKRkouRiZKNlI+JhIyGg4WEdZNnhH6Af4qGhYGBfIGGhoJ+fX53bWiAf32Cg4WDh4OAgYKAdnyAfn98dnp7fYCCgH6BhH2BgoGBgX99eXh5fXt2dnRqppKfY2loZmhoZwplZWZmZmViZGtqhGmAampramlpamhoZmVjYV9eX2FgYWJmZGBhYV9fYVdbYGBhY2NiYF5gYWBiX19fYWJhYWFfX11eYGJfXmBfXV5cXV1cXFtbWFd8kHZ1eHV1cnNzcnJ3dnVycXNydnhzc3FzdHRycXNydHV3d3dzcG9xbm1vbm1tcHJxb29ydHNwcHAbbm1wcG5vbm5taW1ubGtpZ2hoampra2hqaGZohGkRaGhpaWZpaWdnZWZkYmZoaGWEZ4BlZGZoZWdlZGhlZmRlXXeBYGZlZWZkZWZkYWJiZGJlZWJhZ2dnZGRlYmNlY2NkZ2lnaWVnZmRiZmZjZmRiYWRhY2VrbWtnaGxucGihkZVdZmhjZWhkY2RmZGRgZGVbnZWbl6lia2Znb25maGpsbmxscGxkaGpqaWloam1xbmxsboBpbWxta2xtbGVscWptamhmkoWYX2BeXVtZV19cW1paW1paWVdTT1RXVlRUVVZaWVlYV1ZVoFdsWWhsaGRpbGd3cHJxcXFrb3B1fHl1dIqNdGpqaGtscXFtY2RqamtnbHBsY2hobW5tZ2hiYGpxd3p/fn18fHhyaWdiZmNlcHJ2d4B4cG5rZGFibW1rbFuRnrZbtaSvtF1cXltgX2FgXa+pp1taYWlftrZkW6mop6GqqqWuqVqun6S0sl1ctKqsopeWkJSaoKSmpa20sqSioKuptLGmop6WmJujqJ6MhJGarGNocXV4bnFueXBjcW5uYm1rbHRrjZdna2lnaWVfZ2dnaoBvcnRrcXVtXmJnZW5nZmFra2BfZWNxbWZjYGZlZGptZWllYWpvdXt2cHVtaGV0d3hpaWxwd25zaGRmdnN3fYaGgYCEhn55fIuLi32Wk3yIkpuZk5Cbn4yCn6atp6GZk46Pj5aeoKGgnZ2PkoyEiXtce4uJiomHipGWio2Kko6Ohz6Di42NiIqJiZKWi4iBfHl7fouMiYSAgYyDfn97e4KCgH+BhYR0Z2GulJCNkZKLkKdasZ6fX3V3en+EgXx3doBSVFJOSkhNVFNRU1piXVdVTk5MTk9PUVRUU1BSUldYU1FPSkxKR0dIR0ZFR0VDSEZFR0ZDREdvYIRTVFxcWVhYU1hbWFlZXVVVVVRSUVJZXltbVVpXWVVaVlZWWFZVVFNVVlVOTVJNVFdQUFNVUpxVXFpWXGRwkFZbXVxfYVxcWoBZX1xVWlhWYF9gWmJiaF9gW2JnbXN0cWNpf3dtdnp0bm18eXN5c3p6gn14c3pzbm5tX3BSa2VnZ3Vxb25tZmxvb2xoZWVhVlBhX15hYmVkamhlYmFfVl5hXmFeWFxdXWFiYV5hZF9jY2NkY2BeW1pbXVpVVVNNeWlrQUREQ0VFRQlERERFRUNAQkeFRUFHR0lJR0dIR0dFREFAPz09QEFAQUVEQkJCQEBBNzs/QEJERURBPkJDQ0VCQkFDRUNDQkE/PT5AREJCQ0JBQUBAP4Q+QDs9Xm1eX2JeXl1dXFtbYmFgXFteXmRnYmFfXl9gXF1hYmNjZWVlY2FhY19eX11cXWFkZGFjZWdmYmFiX2FlYmCEX4BbX19dXVpXWFhcXF1dW11cWl1eXVxbWVtdXFlbW1lbWVlVU1hdXVlaWllYVFZYW1ZWVFRYVFZVVlBjaE1SUFBSUVFST0xMTU9NUE5LSlJSUk9QUU5OUUxNT1NTUVFNT01MTFJQTU5NSklOS01QVVVSTk9VWFhRe25zSFBPSUxRSx9KTU9LTUlNTkN1cHRufkpQSk5WU0tQUlFTUVFUUUpOhE8kTk9SU09OUVNNUE9QTU5QT0pRVE1RTUtKalhjOTk4NjU0NDg3hDYINTUyMS0sMDKFMIA0NTU0MzIxXzVRR1NUT0pSVFdlW1taXFpTV1heZGFcXmtzXVBPT1JRVFdVTE5SUVJPU1VUSU5QV1VVTk5ISVRbYmFiYWBfYF1XT09JS0tPV1daW1tUU1BKSEhVVVFRQ2Vwgz9/dHyAQkFDQENAQkRDfnl3Q0BFS0SDg0lDfH1+d4B/gYCJhEWGenuHhEZHjIWHfXBxa3B1eHl6dHeCf3Nzc3t8iYZ8dXRtcnV5fHVmYW52g0tNVFVXUVRSWVNJVVRVSVRRUldNYW9LT0xKTktFTEtNT1JVVk1SVlFERklJUU5NSFBQR0VKSFJPSkhFSUZHTE5JTEpFS05TV1NOUk1JRUVPU1ZLSkxPVU5PR0VGUU5NUVlXVlRVV1FOT1pdWU5aXEpQWGFfXFxjZVlTZGhwbWtpZWFiYWNoZ2ZlZWdfYl5YXVRBVmGFXkdjaF9jYWdkZF9cY2VlYWRjY2twaWZgW1hbXmhpZ2VhYWplYGJeXWRjYWBgY2NVS0V4Yl5bXl1YW2s8dGltQVNTV1xfXltVVLZ/g361fwF+hn+Cfrt/AX6Uf6+Ag3/cgIJ/+YCCf7uAg3+QgIV/rYADf35/oYADf39+ln+Cfr1/BH5+fn+Efol/g36FfwR+fn9/iX4Bf4V+gn+lfpR/gn7/f7N/iX4Ef35+fop/AgIEAICYnZ6Zk5SYlpKVnaWnpaSipaqqp6mpqK6qqaursr+wsaioq62inZihm5abmJWblIyRlImI5brT+YaTj46TmZuYoaOkm5mUlI6MkpSVkI6LioqMipOMiI6Qj46JiIWJjIWEiYiDiIaFi5aNhoeQj4SFjpeOiI2ojZWdnJ2fpKKbmoCbn5+Wn6KhqZujrqmop6mrpKihnKGlrq6urLLAvry5sKqvtbe8p5qwtrWnoqy0taywrJvWiaysra+qqKuxsrC0sbKztra0q5HeqK6tqayxuLezsrGztKyrq6qqrq+tsKytrq+xs7Gtq7C1tK6xsKutrquoqKmppaSb9dXWhZicnSqdnJqYj5CQlJeZl5eYl5ygnZydn6CenJqam5mXlpiZmJiYlpWTlJGTl5eEkYCTk5KRkJGSjY2QkY6PkI+Qjo2PkJCRkI+Qj42MjoyMioiKioqHg7XfoqOhn5yfoaWinp+doaGfoqGcm56amZ2dm5mamJ2cnJeYmZqZl5eam5mamZebnJeWlZaXmZmUlpWXlpaUlJWWl5OXmZeXl5aVlZOUkpOTkZCQkI6Pjo+OkYCQjo+QkIuJjI2PkI6PkpKOjYyJioqNi4aKjI2OjY+OjoyMi4KtvImRkJCUkpGPkJWRjo6RkZCPjoyLjpOVlJKTj4+Mj5OUlJGRlJaUkZKTl5WTjpCRkZOXlJSZi+bT14aTlI+Pj5CQj4aNkpGOk46Pk5eWlZKQifTn5+TsgJCZnQGYhZkRl5WbnpeSlJqYm5uanqKamZyEm4CflZibm6GblMHi/I6Qk5KRjouLjo2Njo+Lio6RkJCRkY+LjI2OkJKQioeGhsaxv4yPlIyNkIiPkYqRkZiXnKOlopugpKifg5WTm5qVlZ+bkZWWk46dmpGMkJOTk5CLj5KSnJmTjIyXn6CfoaiorbSsoZmQlZOTmZmZmpqMmJWIgoCIhoiMkpP0rtL9hf+CiYKBgvmEjpCKhYWCh4r6gYuLiYKNk4yPgvqA8fOA+e/79e3v5Ovx7+/i4trc5O3u7+3w+fX8+fr+9vTx7uHe4dzi5ejp9fX0+/Xx1MTS0tv4iYySk5mWoZ+apYrhxc2Ij5OQk46MlpmcnJqTnJ+amZyWjoCPi4qJkpaZlpKUlIqDjZmTk4yKiYeKkJ6pp6GThZChrKGXoZiclpqlqqiysqunpp+goZmenqCboZeeoKCkj5GgoJ+jpLe0ra69ubq7vbzCyLu2t7zAyc3Dr7a+wczMy8fFw7i7vba1qo7/rrG2ubazwca9u727trS4tbvFwbq4sjeqpqCeoKe1vcfEvrWzrLe3vbq5vbq7ubOljv3x59/i3eHa19bU4PaChP71/YaD7OmDlZuZl5SVgGxwcGpkZWpoZWZtc3Z1cnBzd3Z0dXV0eXV0dnZ8hnl7dXR1dmtpZGtlYmhnZGdhXGBkW1qcgpa2YmpnaGxzc3B1dHdxcW5uaGdsbWxramloaWllbGZjaWtramdoZmhqY2NoZ2NmY2Focm1mZ3BuY2ZvdW5pbnVtcnl5enp+fXh4gHp+fnd9fnyFeH6JhIODhYR8gHx2eHyCgoCAhJGPj4+GfYKJi5J/dIeLh3x6goaHgIN/cZdjfn5/f3p5fIKDgIOBgoOCgYJ8Z6R6e3dzd3yEgn1+fn9/eHd4dXZ6fHp8eXp6fH6AfXl2eoB/eX18dnh4dXNzdnh0cmuqlo9WYmRlQWZmY2FcXV1gY2dmZWVkaGxpZ2lqamdlZWRkYmFhY2RjY2RgYF9fXV9iY11cXV1fX15dXV5fW1pbXVxcXV1eXV1ehF+AXl5eW1teXFtZWFlZWldVeZRydHJxb3Fzd3RwcXBzcnFzcm5tcG5tcG9ubW9ucW5wbW5wcG9tbG5wb3BvbXJ0b21sbm5xcGttbG5sbWtqbW9vbG9vbm5tbG1sa21rbW1samloaGdmaGdrZ2VmaWlkYGRmaGloaGttaGdmY2NjZmSAYGRmZ2dlaGZmZGNiXHiDYGVkYmdlZWNka2diYWRjY2JhYWBjZ2hnZmdhYWBhZWZmY2NmZ2VkZWVoaGZiY2RmZ2poZ2xhoY6QW2ZnZGNjYmJiW2FlYmBkXWFmbGtpZWNdqJ2inqNYZWtuaWtra2lpZ2ltb2plZmxqbWxrb3JoaGuAamtra25lZ2trb2pmhJCkW11fXV5cWlpcWlpaW1lYWVtaWFhYV1RWWFlaWllWVVVVgHSCYmRpY2NlXmRpYmhpbm1ydnd0b3N2enNdZWpwbmlqdHBpbW5qZHNwZ2Vna2lqZ2NlaGhubmxnZGtxc3R1fH2Ahn12b2ZrampucHBwbmSAb25kX2RiY2VrbbF6lLZgt1xiXl1brV1lZmBdXVpfYa9bY2JgW2RpY2RbsFurqluvqbWtp6qkqKyrq6KhmpyjqaeqqayzrrOysravsLCtoqGhmpygpKOnqauwra2WipeZoLhmZ2ttc296d3R9ZaSNkV9jZ2dqZWRtbnNzcWpwc2+AbnJtZmdjY2JqbXFvaWprY11kb2trZmRkYWNmcXt5dmpfaHR8dGx0bG9qbXZ3dX9+eHV2cXFybHFzdG91a3FzcnRlZG9xcHVzgH52doOBgYSHiY2Rh4OEiYyUl5B+hImLkpKSkI+MhYeKg4R9ZrV9goaHhYKOlIuIi4mIhYiGipQ7k4yKh4B7eHZ2f4qPmJaQiIWAioqPjYuPjYyKhXpmsKSclJWSlZGOjYuTo1VWpaKrW1mcnFtqbmxraWqAUVNTTEZHTUtHSEtSU1BQT1JVUk9RUlBUUVBRUVdgVFVPT1FSSUdDSUVDSEhER0I+QkVAQXFbbotOWFVVWl1ZVFdWWVVXVlZSUVhaVldYVlVUVVNbU1FYW1taVFNPUVVPUFNSTlFPT1VgWFFUXFxRVF1lXFZfXl1gaWlrbHFvaWaAZ2xsYmlraHFjaHNwcG9xcmxvaGBja3N0cm1xgHt7fHNpb3R2e2hfdXt5bGRtdHJpbGhbcE1mZmhpZWRla2tna2prbGpqbmtSfF5cWFNWXWZkYGFgYWFZWVlWV1xeXV9bW1tdX2FeW1haYGFaYF9ZW1pXVFRYWlZTUIFtYDhBQkI5QUFAQT0+PT5ARENDRERHS0hGR0lKR0VDQ0RCQUFDREJDQT8/QEE+PkNEPz8+P0FAPz49Pz89PT5AhT8FPj4/QUGEQoBBPj1APz48Ojo6PDk6V3VcXVpZV1teYl9ZWVddXVxgX1taXVpaXFpYWFtaXlxdW11fX19cXV9gX19eXGJjX2BeXl1gYVtcW15cXFpaXWBfW2FiYWJhXl5eXF5cXl1cW1paWllYWlldWFVXWVpUUFZZW11aXF5dWldVU1RVWFZQVIBXWFdVWVdWU1NSTmRlTFBQUFVQUE5QVlFMSk1NTEtLSkpMUlRRTk1JTEtMUVBOTU1PUExMTU5SUE5KTU9PUFNQUFZOgG9rR1BRTU1MSkhGQUpPS0dMREhPVVFPTExHgHx9eH1DTVNVUVJQUE9NS0xSVlBMTVJOUE9NUlRLS05LTYBPT1NKTlFQVE1JX11mODk6OTk4Nzk6NzU1NjU0NDUzMjEwLzAyMzIyMzMxMTIyT0lqTk9TSEpNSE9UT1ZYWlldX19cVlthZFxITFdaWVVVXVZPVFNRSl1aUE5RVFNTT0xNS0tTU09OTVdcW1pbYmFlamRdV09TUVJWV1ZWVEtTVIBMR0tJSU1TU4ZbbodGhkJHQkFCfURLS0VDQ0BESIBCSEhIRElNSU5HjEaChEeIhpCFf4N8goiHhXp9e3l/hoSDg4WKg4SAgYOAg4KAeHl7d3l9fHh7e3yBgYRvY2xxdIZMS05QVU9bWFhgS3hkbUpKS0tMS0pQUVRUU01SU1FRU4BPSkpGR0hPUVVTT1BPSUNJUk5OSklIRkhJUFtaVk5ESVNZU0tSTU1JS1NSUlpYVFNUUE9RTFFSU05SS09QT1BFRE1OT1FNVlVNTFZWVFhaXWFjXFlaX2BmamRXW19fYmJiYF9fWlxeWltTRH5WW2BgXlxlamNhZGNjX2NhZWxsZzlnZF5cWVhYXmltcnBsZWRgamtvbGtta2llYVhJem9oYGBcYF1aWlleaTc4a2ZwOjttbkJLTE1PTU20f4R+vX8Bfr1/AX6Vf7CAg3/YgIJ//ICCf7aAg3+YgIV/p4ADf35/oYADf35+mH8Bfr5/hH4Cf36FfwF+iX8Bfop/BX5/fn5/s36Lf4N+/X8BfrB/jX4Jf39+fn5/f35+h38CAgQAYZKUlY2cl5iYk5SVn6eqpKamrKevsLGur6alp6OpqaelnaOtlZWYmJ6PkJGOkpWRi4X73b3S6OLsiJWZnJOUn5+bmpuZlJCOhYWIh/+AiZCUlouJjo6F/Y6Tk46KgYCNgIGEhYCHhYWFhIaEko+Kjo6KjZmIrZydn6WjpqKcnKOgp6ahop2ao6ChnqSttbOupZ+po52nusHDta6qu8K+wq+ru72tq6mdn6GamZ+koqaeoKuf8P6en5yamZmZnZWgqqaqrK+sqqmfoo+opaqjpqegoqaioqmkoZ+hqKiqp6Wprqmnq4Cvsa+sq62vrKyusa6qqKinpaChoqakm//Xz+uOmJeZk5COjo+Sk5OSkJebmZiWlpWXmJiTkZOVk5aXlJaWlZOSk5SUkZOTkpGQkJCRjo+PiomLjI6Mj5GRj46LjIqMjouLjIqKjYqJiIeJjouGgqjkoZycoKGdnZ+en56hn56bm4CbnZuZlpeam5qZmpmbmJyenZqYmJaYlZaWl5eZmZiXm5aXmJWUj5CQk5OXmJWSlJSTkZKTlZGUkpKRjo+Pj46NjI2Oi4uOkJGQjYuMi46PkJCOj42Ki42MjIqKioyMi4yMjY+NjZCQjIuIiIuOjvmtv4aSkpSTkJCRlJKPk5KOjoCRlY2Nj4+Sk46QjJGSkI+VlJGUkpKMlY+QkpGTkJGQlJX039bjiI6RjpKSk5ORjIiOi4yPiYiMjZCRk5GQjI2Nj5WTlZONgOvo5+n2ipadnZuXmJaUmZmbmpSan5uXnJ6foJ+doaCVlp+gk574gIuKjpCQjo+Ojo+MjI2KjJCQjYCLjIqD/oOHjIyLjIuGi/qCmu6MlZaYlpGVnJ2TiYaWmIqSmJ2fopqfpqehiICZmpmVkpyfmZCSkpOQlpmUlJqSlZSKk6KZmZSOh4qHh4OFkZqamZyiqKCalY2SkJiZmpqblIyLkJGPkIKMj5Hir9eD9/qBhoWKhoqGhYmHi46OioCHioOEiouIiYiJgvj8//j49PLy69jU3drW0dfe4dvc2uLt5eXm+IiMh4KCgYWBhfuCiID3/vzs5OHb4unw+YCFiILw5OPs8oWVnJ6Ix8WKlpKJipabkZSTj5SRlYiRm5uQmZaWmpKQjZGfo5mUmpWKhJGYjYiNkZiYmJyVjJKbnYCho6Kan6Gmqq6vrqyqqK2op6WioJqjnJWeo6avpqebnaGpo6imrKOdoKiuur66wMK9ubu8sqWur6elsrmytq+lrra7s663u7q6tbCtq4faqrm9uLm+vLy4squuqampqKemo6SlpaWjqq2wt7avrauqrbO6uLConpGC8unh4fD16hvWycvb2t/Y5PT1+oCEgoCAg4WD9tvDxOuHko+AZmdoY29qbGpkZWdvdHVtcHN5dnt6e3l5cXF0cnl4dXBpbXViX2JiaF1gYF5hZF9YVqiWhJiooadkcHFxaGpzcm1ucW9saWdfX2NjvF9na29xZ2VpaWG6aW5va2dhYGpfYGRjZGNkZGNkY2dlcm1rb29rbnZmfnt2eH+Agn13eICAfYODf395dX97end9h42LhXh4gnt0fY2Sk4iDf42Tk5eCfo6QgX59c3V3c3J3e3l+d3qCdKq0c3RxcHBub3Ntd315en6AfX19cnBmdG92cHR2cHJ1cnB2c29ucHd3eHZ1dnp2dHZ5fX15d3l7eXh6fnt3dHRzcW5wcXR0b7WYiJiAXGJhY11bXFxeYWJhYF1kaGdmZWVjYmNiXl1fYF5hYl9hYmFgX2BhYV5fYF9fXl1eX11eXltZWlpcWVxfYF9eW1xZW1xbXFxaWl5bW1pYWl5cWFdynXJubnJ0cXFycXFxc3Bwbm5sb25samtucW9tbm5wbnBycW9ub25wbW1sbW2Abm9vbnJtb3BsbGdpaGtqbnBuamxsbGtra21pbGtra2lpaWhoZ2RlZ2NkZ2pqaWZkZWRnZ2lqaGloZWVmZmVkY2RlZWRlZmdoZ2ZoaWZlYmJmZ2ezd4VfZ2ZpZmVmZmloZWhmZGNmaGJiY2NmZmJkYWVmY2FmZmVmZGVgaWRlZWWAZ2VmZGhorZ+Ym11hY2FjZGVlYl5cYV5eYFxbXmBkZGdkZF9gYWBlY2VkXlehoZ+irWFpb25saWlnZmpqbGxnbHFtaW1ub3BubHBuZmhwcWdrn1NaWV1fXltdXV1cWVhaWFlcW1pYWFVPm1FUVldVV1ZTVp5VabJmbGtramZrcXGAa2JgbG5lbHBzdHdyd3x8eV5dcHBua2lwcnBoaWhoZmtwbWxwaWxrZGx2b3BrZmBiYGBdX2dvcnNzd353cWpkaGhtbnBwcGtmZmtraWlcZWhro3qaXrC1XWBdZF9jYV9hYmNjZGJgY15eY2NhYmJjXLG2uLGxsbCvqpqUm5mYl5uAn6CcnZuhqaOjpLBhZWFeX15hXGK2XmJcsbe0pp6enKKorLJcYWNfsaSlrrNjcHR3Zo2OZ2xpYmRuc2lramZraGpfZ3BvZm5ubG5paWVnc3dvanBsYV5pcGZiZ2pvb29xa2NpcHFzdHRwdHV3en19fHt6en56eXl3dXB2cWxyd3pBgnt9dHN2fHR4en52cXB0eYOHhYqLiIWHiYN4foB5eYKIgYR+dXuDiYR/houKh4R/fX1hmHyKjIiKjo2NiYaAgn+Efi19eXl6ent5gIKEiYiDgoCAgIWLjIR9dGldqJ6Xl6CimoyGiJKQko+apaSoV1qEVwpZV6OUhYalYmdjgExMS0NQS0xLRkdITlJTTE5SV1FXVVdUVU5MTUpRUk9PSU1UQ0FDQ0c+QUJBREVAPDpyZ1xyhn6BUV1eW1FRVlRRU1ZWU1BMSk1QTpJMVltdXlRRV1hQmVheX1pVT09XTU5QUE9PUFBRUlFUUmBeWV1dXF1nWmptaWhub3Jva2pugGtycWxtamRtamlmanN7e3lvZ29rYmp8g4Fzbml4gH6AcGx6fG1ra2BkZmJhZmlmamJlaluAil5fXV1dXFtdVmFoYmNnaWdpbGBWTFROVlFTVU9SVlRSV1RRT1FaWVtYVllcVlRXXF9dWFdbXlpZXF9dWVZWVFFNT1BTUk+DcFxjgDs/PkA8Ozw9PT9BQkE/Q0dFRERFQ0NEQz8+P0A/QkI/QUJAQD9AQkI/QEBAQUFAQEA+Pj8/PTw8PTw/QUE/Pjw+PDw+PT8/PTxBPj49Oz1BPzs+UnhaVlZbXVpaXFpaWVxaW1lYV1taVlRYWlxbWVpZXVpfY2FeXF1aXVlcXF1dgF1eXl1iXF5fXFpXWVpbWl5gXlpcXFxbW1teWF1cW1pWWFpZWVlXV1lWVlpeXlxXU1VUVlhbXFlaWVZVVlZVVVVXV1dWVldZW1hWWVlVVE9PVFdWl2BnTVRTVVFPUFBTUU5SUk5OT1JJSEtOUlJMTUpPT0xMUVFOT05OSVFNTUxLgE5NUE1RU4Z+endISktKTk9QT0tHREhERUlEQkdJTE5STEpFRUhGTEpMS0ZBenh2eH9ITlRSUU5OTExSUFNQSk9WUk5SU1RUUVFVUkpOV1ZMS2ozNTU5PDw5Ozs6ODY2NjQ1NzY1MzIwLVkuMDIyMDExLzFbMk2IU1hXVVBMUFdXgFJLSldWT1lfYGFhWVxfYmJBTlxbV1NSWFtXTU5NUE5UVlRVW1NWVUxUXVVVUU1HSEdIR0lRWVpZW11iXllUTlJQVFRVVVVTUFBUVE9PRE1QUnxac0Z/gkRGREtGSkhGSEdGREhIRUlFR0lIRUdITUiHjY6IhoOGiIJybnV1c290gHl6eHt5foeBgYGLTE1JR0hISkZKiUdKRYaLhXl5d3N3fX+ERUlMRoN7en99RlNWV0diaE1RTkdIUVZRT05KT01PRU1UVE1SUE5QTU1JS1NWT0xST0ZCTVRMSExPU1NRU05HTVJSVFRUUFFSVVhaWFdWVlZaWVdWVlRQVVJPVFhZgF9ZWVJUWFtTWVtgWFNQT1JaXV1gYV9cXmBbVVxdVVVeYlteWVFWW2BdWl9hYV9cV1ZWQmxaZWZiZWlpaWdlYGJfYGBfXl1bXFxcW1pfYWJpZ2NjYl9fZGtrZF1WS0ByaGNhZWReVlRTW1pdWmFpam46PDk6ODg6OWpjWFp2RkxJsX+HfpN/AX6KfwF+nn8Bfr5/gn6Uf7KAhH/TgIJ//oCDf7CAhH+igIV/n4ACf36XgAF/iYAEf39+fpl/AX7AfwZ+fn5/fn6Zf5t+iX8Efn9/f4t+hH+FfoV/gn7/fwJ/fqp/kn6If4V+g38CAgQAgIWSlZ+noZmbnp2co6ilramoqqWpq6qqpqSkoaeon5mek5SAhZSWkJSWmZqRk5GMgNO22e/u+oeIjp6hnpycopqUkZWRkJGNh4iCg4GGiomQlI+KkY+Pi4uJkJOLiIuOjpCI+P6DgIiLhYCEhomJjoiHiYaUy9CWoJuaoqOgnZyVgJ6gopuepqGioJ6loZ6isZqempukoqGhrKysmqeyra6vubq4tLatqpmgpKWhpaKqrbaqnZ+j+OyYoKGfoJ+glKWnnJmfo6WlpaGbg9GWoKSmpaOfmpydm5qbm5udn6OnpKmrq6yoq6mko6SmqKmopqOkpamopqWpp6SkpaOlpqOUHe7LxuqLk5aWk5KTk5WTkpORjo2RlJSTlJSSk5OQhI6Aj5KSk5ORkJGQj4+QjY+Qj4+OjI2Pjo+Mi4yOj4yNjIiLiYuJiYqKiY2LiYqHiIaE9Zz2oJ2bnaGho6KfnpyZnZyYnZ+fnZyamJeXmZeYnJqamJSUk5SXmpSUlJeZl5WZl5iVl5eWlpiYmZiXlpOUkZSUk5OSkpCQkY6SkpCQj41Hj5GQjIqFio2Ojo2NjIqLj5CPj46PjYmHhoWGiouLi46NkI6Mi4qMio6OioaKiImMjYeIh+2xsPySkJCTkpCQkJGUkouJi4uEkICNjY2QkJCPjpCUkpSWkJKUlZOWlI6C8eHX4YCJh4iGj4+NkJCPipGQjY+PkIeJjZCQjZGRjoyOjYuPj42OkZaPkJGPkpKM/Ozv8PH0hZObnZ+cnJielZSbnpqhnp2cnJqXnIj6lIaKi46QjomIi4yMjYiEg4aKiYOFhYeIiISGhICDgoWHh4G1sc+HipCQlYyIjJKZm6GYh42Vk4iMkpOPmZudoKOyvpGIhoiNiI+ampiRmJeVj4yTlJWXnZeMiZGMioqNi5CLj42KjZOJjI+SmqSan6SbmJiamZaTkJaXlZibjPrdztLXzbTT+4mOioySlJSJhYCDhIaIjpGLj4yTjoCHipOJhvv1+fLt7vHx7+fl5e3y6OXn7OLd2tbc6f/1gIH+g4eGiouKiYuEhoD3+Ojc29zf4+7y7ez2ho6KgIeNhfnIpqO40O6MlpyXjZCUlZiVkpqdoKGgmZOPkpCMkpqZo6aeko+Ok4+RlZGLmpSWk42enpqan6KipKawtK2soICkpJiWm6KqpZ2ipKyuqKKfoqKfrKWpsKirq6Onrq6sioemq660sampqa2sq7GusK21t7Cwp56srqygpqijqbK1tLm0tLW4u5bWla2roqOrpaioqaimq7Csqaunoqaqq6yytrS3trOroZePiffbzMnS4+7y7+jl7Ojb09Ti5OXk8BTs8PSCiID48+7u7eXRxcTDurK22IBgaWpyeXJqa3BvbnJ1cnl0cnRwdHd2d3Nwcm90dW5obmNjVFdhYl5gY2ZnYGJhXVWNf5+vrbdkZmt3enZ0c3dvbGpva2ttaWNjXl9eZGhmbG9sZm5sa2lpaG1vaWdqbW5vaLm8YV5lZ2JfZGRnZm1nZ2xpeJifdHhydoGBfXt6dIB7fH54e4J+fHl4gHt4eop1eHV2f3x4eYGBgXB+iIGAhI2PjoyNg4Fzd318d359hYmPg3Z4fLOpcXd3dXd2d2t8fHJvc3Z4eXp2cl6XbXF1eHh2cWttb2xsbW1sbnBydHN1dnZ4dXd2cnBxcnN0dHJxcnN4eHRzdXNycnNyc3RxZQihhn+YWmBiYoRggGJhX19eXFpfYWFgYWFeX19bWltaWltfX2BgXl5hX15dXlxeX15fXVtcX19gXVtbXV5cXFxYXFpbWVlaXFpdW1tdWVpZV6Rlq3JvbXB0dHZ1c3Jxb3JxbHN1c3Fubmxqa25sa21ub29qamprbnBoaGltcG5rbm5wbW5vbm1vb3BvgG1ubGxpbGtqbGtqZ2dpZmpqaGloZmlqaWZlX2RmZ2dmZ2VkZWppaGhnamZjYmFgYWRlZmVoZ2lnZmVjZGJnZmNfYmBiY2ZfX16mf363amZkZ2ZkZGVlaGZhX19fY2RlZGFfX2NiY2FiZGZmZ2ljZWdnZ2loY1usn5WaWF1cXFliWWFgYl9fW2JhXmBgYVldYGNjYWZlYl9hYF5iYV9gYmZfYGJeY2VhqqCmqaWnXGdub3BtbmlwaWhub2txb25tbGtpbmGkYVlbWF1eXFlYWlxaW1dUU1VYV1JUhFWAUlRTUVFTVVVSd22PYmNpaGtjX2Npb3B3b2Fla2tjZWhqZ3BxcnZ6fYJpYmBjZmJpcnJvaW9wbWplamtrbnJvZmNqZ2ZmaWVqZWhlZWdrY2VmaW12cHZ6cm9ucXBta2lsbmtucWWznZWamZJ+lrVlZ2RlampsY2BdXl9gYGRnYmaAZGpmYWNrYl+2s7qzrq2wr62lop6psaajpamin52aoKq+tF9gu15iYmZoZ2RlX2Fcs7amnJ2dnqGrrqmrtWVqaF9iZmCyjHJugZOpaG5zbmdnamtvbWlxcnN0d3JtaWpnYmdxb3h7c2dmZ2tnaGlmYnBra2djcnNwcXZ3dnV3gIWAf390dndtbHJ4f3pydHd/gHt2dHR2dYF5e4J7fn53eIGDgWVhdXh8gn95eHp+fX2BfYB+hoaCgntxf4F/c3h6eHyCh4SFgIGChYlqlGyAf3h4f3t9fX59fH+FgYCDfnh7fH+Dh4uIiomHgHZwaWCslouHjZifoZ6cnaGekImLlpkXnJujoKKjVlxWqKWioZ+YioKDgnt2fJmAR0xMVFtUTU5SUU9SU1FVUk9RTlJUVFRPTlBNVFVNR05GRjk7QkNAQUVKTkdHRUQ/aF19j46YVFNWXmBdW1peVFFQV1NTVFJPUExNTFNWVVlaWFRcWlpYV1dcXlZTV1dYWVSYmlBNUVVRTFNVWVpfVlheW2uAimVsZWZucG5ubWeAa2ttZmpzbGtnaHBpZWh6ZWhkY2xpZ2dycHFjb3l0cm99f316fXNvX2ZsbGZranB1fnJmZmmLgF5lZWRkYmNYaGhgXWFkZmhpZWFHbk9RVlhZVlJOT1BNTExPT09RVFdVWVpaXFhaWFJRUVFUV1hVVFVXWlhUVFdUU1RXVFdZV04vd2BYZDo9Pz89PT9AQkE+Pj89PEBDQkJDQ0BBQDw7Ozk6O0BBQkJBQEJBQD9BPj+EQIA+P0JCQ0A/QEFBPj4+Oz07PTs8PD89QUA/QD09OztwRX9aWFdZXV1gYFxbWVZbW1dbXV1aWVlYV1ZZV1ZZWVpaV1dWV1tdVVNVW15cWl5cXlxeXl1dX19eX19eWltZW1lZW1taV1ZYVVpbWlpZV1pbXFtZU1daWlhYWVdWVlpbV4BXVVhTUVNSUFFVVldXWlhbWVlWU1VSVlZSTlFRVVZWTk9NgWBfkVVQUFRSUE9OT1JPSUdHSE1MTE1LSUtPTkxLTE1QUFNTTE5PUU5QTkpDiXxzdUJISEZBSkpJS0ZGQ0pIRUZHSEFFSkxLSE5NSkhKSEdLSUZHSk1FR0tHTE1KfjF0eH57e0RMUVJVUlJNVE1MUlVQV1NSUFFQTlRIcT82NjU4ODY0NDY4NjYzMzMyNDMyhTMFMDIwLi6EL4BITnhRUVVVWVBMTVRbXV9WS09VVUxPU1dVW1tbX2NbblpRTlJUTFNaWFhTWFZUT0tVVVRVW1dMSU9MTU5QTFFMUE1NUFNLTVBSVl1ZXWJZVlZbWVZUUlRST1JUSoN0bG5ybFpsg0lLR0pOT1JHRkRERUZGSk1HS0hPS0dKUktJiYCLk4yGg4iIh36AfYWJf36AhX59fHmAiZiRTU2USk1LTlFRTU9KTEiPkol+enl6fIKGgoGJSk5MRUhMR4NhTExdbntJUFZSS0tPUVZWUlhXV1haWFVPTkxGSVJQVlZSS0pMUUxNTEtGT0xNS0VRVVNSVlZVVVdeYl5fVldYUE9TWYBfW1RVWF1eWVNSV1hWYVlaXVpgX1laYmNhSEZTV1xdW1ZUVFlZWVxZXFxjYV1fWVBZXFtTV1hVWl9hX19aWVtdXklpT19dWFlgXV5eX15dYWZjYmNhXF9fYGNnamhqamhiWVJMRHZgWFdaYWVmZWNiZmNZVVhfYWVjZ2Vqazo+Og5xbmlnZWFYU1VYUk9WcLB/hn6sf4J+kH+Cfr9/gn6Vf7SAhH/NgIN//4CCgIR/qYCEf6yAhn+XgAJ+f6GAA39+fpt/gn68f4l+mn+afgN/f36Lf41+h3+Hfv9/An9+on+YfoN/jn4CAgQAgL25yfyYpJ+oqqGfoaSqqKSprKymo6KjpqejopqZkI+Mko2EhImNjI6Lj5SMj4LGt+Dz9vTy7oGTn6SinJ6gnJiUkJKQjYuNjJGLh4eEiYqJh4SEjI6OjZGTkJGLjZKYlI+H9fqAgIiIiouMj42OlpKOkImRoPSLkpSRmanDqKiigJiam5eRkpmgop2opqScpaagl5mdnaGssZ6os7Wur66dnay3tKOanqOktLKpo6emqrGxppiTj4bajZydnaWinqGeoqCbnZ2gn6CVkIOK/peZmZ6fnaCmoZ2io6GeoJ6foZ+fn6GfpKeipamlpKGjoZ+fnqGmp6moqaOlo6SjoqKjJqOfjMSawdDsjZWSkpCSkpKQkZOSkpKQjo+Ojo+SlpSRjJCRkJCPhIuAjIqMj42OioiGh4eGh4uLjI2OjYqJiYWCgoSEhIeJioiHhYSFhYPbmIWgnpuYlpeXnJ2ZnZ+bmZudnJyam5ydm5ubmpmYl5mZmZeXmJqZmpiZm5ucnJmYl5eYmJeZmZaVlJSUk5GUlI6MkI6QkY6Qj5CPjo6OjIuMjYqIjY+OiooFjY6KiIaEjICLiomIiYeHi4mJiYyPjIqLjIqIi4yIiIqHiYmIioiJh4mGiIT/vK7skY+Ojo2Tk5SRkI6MjIqKio6LkpKUkJGRio2OkJKUkpGSkI313NvigomKi4mHhYqGjJKSi4mMhoqSkYuNj46KkZSQjY+Sj4mNjpKPi4qNkI+Mi46Ki5GTk4CQkJGQk4j+5+3s7/PvgIaUmpianJqcnZyYluXNuoWKjIyMjo+OiYqKi4uKi42Mi4aDgoKDhYiGgvn6goSD4fCxhYuRioeNjY6QkJGUlJacmZOWk52Yi5KJhpOYmJ7sjYCBiYiGh4iCjY+OkpyZkJCPmJmZk5OUkJCRjYyEgoeKiYCNjIuMiY6MiYmHiYyPk5mkpZuO9enW1NTI2uX+i5OSj56b9bHFgoWJhYaChYPygYiJhoiChIyUkIyC+oOKjouPj4qHgIL17uvw9/z59+/z7eLg5unw9feDhIWDgIiPi4yOio+Vi4mHhvrx3dHa/4D39P3/g4GAgomM8LrNgpKPj4CJ8uLe3Onm8oWKkZeXkJOYnqOgnJKLkZaZl5idoKGbnJ2bn5ibmZ+ioa2qqaiqqaqjnqKhnJmdpqWmoKGqrq6uq6yxqKutp6arrKKjrLKrra+qpqCloaOnpI+Rn6KqqqesqaapqammpKOqq62jmaCloqzAwcC2r6yrrbGztrnEx029ms2Pqamlm56lpamqrK2rr6mlrbKxqKSflpKNi/zv6N/U0Nfj5eXt49nOz87NzdLV0+Py+/br9fLt6fbx6t7c0NPLytLGwcPCxrzHwYB7e42ubHVwd3hycXFyd3Vyd3l6dHFwcnV2c3Nsa2RjY2dfWVhaXl1gXmJmXl5VhICktrezsa5icXl+enR0dnBubGlramhmaWhsaGVlY2doaGhlZGpqa2ptbm1taWtwd3JsZ76/YmBpaWhqa21tbnZybG9qc3C5anFzcHeDloOFgYB4eXl0a2t1e315hYB+d39+enR0d3h7goh3goyNhoaEdXeDi4l7dXh9f46Ng3l9f4WJiYF0bmtknGp3eXh+enV3dXd1cHFxdHJ1bmtgYq9rbG1xcm5vdHFtcXJxcHBub3Bub3Byb3N1cXR4dXNvcnJwbm5xdXV2dXdydHFycW9vcSZycGSGZYSNoF1iX19dXl1eXl9gYGFhXlxeXVxcX2NhXlteXl9eXoRbgF1cXmBdXVpXVVdXVldaW11dXl1cXVxYVlZXV1haW1paWFdXWFlYl2VccnJwbWtsbG9wbnByb21vcXBubG5vcW9ub29tbGxubm9sbG1vb29ub3BwcnNubW5ucG9vb21ra2tsbGtpbGxnZWhnaWpoaWdpaWhnZ2ZlZmdkY2dpaGVkgGdpZmNhZ2hmZmRjY2RkYmFkY2NkZ2hmZWZlZGNlZWJgY2FiYWFiYWJgYV1eXLWDeKNnZWNkZGhnaWZkY2FhYF9fYl9mZmhjZGRgYmRmZ2hnZmdlZK6amJ1aXV1gXl1bXVpfZWRdW1xYXGRkX2BiX11kZmJhY2VjXWFhZWFdXWBigGFgX2FbW2FjYmFiYmFlXayboqWnqqRYXmltam1ua21tbWlnn4R7VldZWFlcXF1ZWVhZWVhYWllZVlJSUlNXWlhUnJ1RU1KPl3RfYmhhYGVlZmZkZmppanBvaW1pc3Fna2Rha29wdqhZWl1jY2FiY15naWhsdHJraWhxcnNtbG1ngGZoZ2VdXGBiY2ZlZGRhZWRhYmFiZWxvcnt8c2q0p5eWl5Ccprtnbm9rd3Oye41fXmJeYl1fXqpbYGNiY11fZmtoZFytW2JmZGdnZmReXq+sqK21uLS4srStoaCmqa+ytGJfYF5bYmppamtnaW1nZGJhtK+flp27X7e0u75iYF5fgGRmroKRXmtnaGSuo6OiqKayYGRrbmxpbHB1e3dzamRpbG9ubXFzdG5ucW5vaG5ucnV0f317fH19fXZ1d3Z0cXR8e3p1d36AgIB8fIB7fH16eX6Ad3Z9gHp8fnt2cXd3eXx4ZWJwdn59eoB+enx9fHl3d3t9fXVwdnp3f46OjoaAVn5+f4CBgYSLjYlukGd/fnlydXt6fX1+f3+Ff3uBhIR+e3Zua2Vir6Oflo6MkJibmqKakYiJiYqNj4+LmaSvqaGmo6Gfqaafk5CIjYeGiX99gYGCfIR/gFRUZINQVFBYVU9PUFFVUlBVWFhTUFBSVVZVVE5NRkhHTUY+PkBCQURCRkxGSUFjYoCPkZCQjE9bYGJgW1xdWVhXVFdVU1JVVFlVU1JQVFVVVFFRV1dZWF1eW1xVV1tfXVhSk5lRUFlbWltdXl5fZWFdX1plXqVdZ2VfaHSHdnpzgGdmZmNcXWRoaWdzb2pjb3FqYmJjZWlwd2ZyfH11d3NkZnZ+e2xkaG1vfnlvZ25ucHl4bGJfW1J0VWNjYmlnY2ZjZmReX2BkY2VcWU5LhkxLTFBTUFJYUk1TVFNRUk9QU1JTU1VRVFZRVFhVVFFVVVVSUFRYWVhXWVRXVVZVUlNVQVhZUWxMWl9nPT89PTw+Pj9AQUJBQUA/PT49PT5ARUJAPT9AQUFBPTw9Pj8+QEJAQD07ODk7Ozs+P0FBQkFAQEA8hDqAOz4/Pj89Ozs8PTtpR0daW1pYVlZVWVlWWFxaWVtZV1ZUVldaWFhaWFdVVVhZWlhaWltbXFtdX19hYVtYWl1fX15eXFtZWVlaW1haW1VUWFZYWlhaWFtbWVpaWFdaW1lWWlxaVFRYWVVSUVdYVlZVUlFSU1NSVlNUVFZYV1VVVVSAUlVVUE9SUFBQUVRTUU5PS0tJkWddgVJQTlBQU1JTT05OS0pJSktNSVBQUkxNTUZIS05RU1FPUE1Nhnd2e0VGRkpISERFQkhPT0VERD5CS0pGSEpHRE1QSklNUU1GSkpNSENDR0hGRkRHQUFISklGR0dGS0R8cHZ7e3p2QUVOUU8LU1JQUlNSTU52XFKENYA2ODg4NTUzMzMyMzQ0NTQxMDExMzUzMVxcMDEwVmFXTE5TTUxSVFZVUFFWVlZYVlBVUl1bUVdQUVpbW2CGRExNUVFQUVFJT1JRVF1bU1FSW1pbVFRVU1FSUE5GRkpNTlBPTk9MTk1KTEtNTVBUVmFjW1WJf3JxcGl3fY9QVlZTXoBbilloRkZIQ0VCR0d+REZJSElERkxPTUtBe0dOUE1QUlFPSkyNiYePlJeTk46TkIaDiY2SkpZST1BOSU5TUFBRTE5WTUtKSomHeXJ5lkyQjJKSS0lISU5Of1trRE9NTEp+dXV2eniFSU1QUU9MTlJWWltdUUdMT1JPT1RYWVNRU4BRUUpOTVBTUl1cWVhZW1tYV1pZVVJVXVpZVVdcXl5fXFxeWltbWlleX1hVWl5WV1taWVZaWFtcWEhGUlVdXVpgXVlYWltYVlZZWVlTUFVXVVtnaGljXltbXFxcW1xiZGBLZktdX1xVV1lYXV1fYWBmYV9kZmReXFVPTUhFenJtZixfXF5laGduY1tSUlNXWVlZVmNsc29pbWtrbHRva2RhWl5bXGBXVlhYWlNdWYR+qn+Ifqx/gn6Qf4J+wX8BfpZ/tYCFf8iAgn//gIaAhH+jgIR/t4CHf42AA39+f5uACH9/gICAf35+nX+CfrR/iX6Gf4N+iH8Bfox/AX6Kf5J+kX+GfgF/hH6Gf4N+hX+Hfvp/AX6af7B+AgIEAIC5vcjYzsPQ7IqYnpKao6OipKyqpp+ilp6elpOOjpOUk5CSjYGCgoeRiYuG6L665oL//YSC8YGQl5eWkIyNjZKPkI2MhIaKjYuOkpCJhIaKhoqOioeHiYmJi4aIhoeHhIWJh/7+h4mJi4uLipGRjIySk5eN75iUmo6Wl46PlpymnoCgnJ6jmpSUkJSmsLKrnqCenY2SnZqSkqKWi622s6mlnp6srZukoZmeoaunqLOqppuRlKmop6GckuHwk5mcnqKin52kqJ2ZmZqboKGdk4OvipmhoZ2coaGfn5uYm5eVmJ6cn5ybm5ueoKChoqOhoaOfnJudoZ+fnp+foqakoZ2P9Ejg2eeChYSA89TN0dz+jI6Nj5GQjouKi42JhoWEhomIh4aJi46NjY2Mi4qNh4uKhoiFhIOIiYiJiYaHiIqJhoiIhoiHh4WHh4aEg4CEgvq+nIyZmpmWk5GTlJWYmJuZlpiYmZWUmpmXmJmXlpeXlJaWl5ibmJiYmZiYmJeVlpaUk5OVl5OSkZKSlZWQkJOWlJKMjoyKkIyKiomJiomLi4qPjomJi4uIiIuJiomKiIqLi4qJioyOi4qKjJCRj42PjYaEi4WHiomGio6KiYCLjIiEio+NjImNkZOSjN+uyYmSlI+NkJCQk42OkZGTkIqKi46Qj42LkYqMkJKK697Y2fmJiYuMjYmAgP6Bjo+MkI+Ni4qHhYWHi46Hh4qNj4+JiIyOi4eHi4mIioyNjYeIjI6SkpGOio+NkI+KiI+Uk5KSkY+F8ufo8/j0+fqBgoCl0uOLjo6Ni4uLjYyLiomHiIaBgoaGh4iC/fb6/IGC/vn544um2I2OjYSKhoaDiIyRjo6LjJOZnqWXkIuQkI2QjIaTnYzy5oeFiY6QjoqCgIeIiI+dm42AgomTlpWOjpGPi4yLh4WIhIeIjImSkYv86NjOzM/Q1N7c6oSQk42JjICQh4aQl52XlZOQieevs+j5gfz7+4H59oKIh4f58YGKj46JiI2NhYOIgf6F/e7z8Orr+vn/gPn7/YKC94D38/uEgYWJiIiHioaNioyGg4WDgfTm2evs7PH0gIaG8PPyvrXL8YCFiIWLjI6TlJOYmpiL/Ovp5uTj6u3/i5SWk5KWmYCQlpubnJ+Xko6UmaOipaipppycmZ+hmpmjoJ6ho56koJ+gqrCwsre1r7eztaytq6qqqKSnrKmxsKempKSqopWenp2iqp+snp6ho6GgmKOlrbavqKuopqyqqLK+u7Swrqmut72+vbu9vLqe2PudpKKepKWnpqGbmJKLhYKBgf709TTw7uvr7eft7+Pd4N/h5OLWycLEwr3EydPY5O/m8OXQwMfe5t/e497Nzc/Oy8XCtrnDvb+3gHx9hpGHf42lYm90am50cnFzenh0b3JqcnFqZ2NjaGloZGRgVldXW2NbXFiYgISqYLq3X1+wX2xwcG9pZmlnampsaGdhYmVoZmlvbGVgZGpmam1qZmZoZ2dpZGVmaGhnZ2ppxcVoaWlsa2tqcXJubXNzd3HBbHJ3bnV1bm93foZ8gHx5eoB3cXBrcISMi4V5e3l4a295dm9vf3Roho2JgX94eIWEdHt4c3d7g36BiYB9cmhrf39+eXZrnrJyc3R3eXl3dnl6cm9ubnF4eXVtXHxlb3Z2cm5ycnBwbW1va2lrcW9wbm5vbnBycXJ0dHJzdXFubW5xb25ub3BydnRycGWsgJyZpV1fXFqrkYyLj6RbXFxfYWBdWllaXFhVVVVWWVlZWFpcXl1eXV1bWl1ZXVxYWFZVVFpbWltaV1hZW1pXWVlYWlpZWFlZWFVVVlZWVKZ+aWRtbm5saWhra2tsbHBuamxrbWhlamxrbG1sa2xsaWttbm5wbm9vb25vbmxqbG1qBGppaGqEZ4BobW9paGpsbWplaGVjZ2VjYmJjZGJmZWVpZ2FhZGVjY2dlZWRlY2VlZmRjZGZoZmNjZmlqamhpZ2JgZV1eYWJiZGhkY2RlYV5kaGdkX2Noa2tloHuPYWdpZ2RmZmVoYmJkY2VjX2BhZWZlZGNmYWNmaGGjmZOSrFxcX2FiX1dWrIBYYWJgY2NiXl1bWllbX2JbW15gZGNdXGBkYl1dYFxZW11fYFpbXmBkY2JfW2FfYmBdWmBkY2RjY2Ncpp+gqKqora5bXnSHlllaW1taWlpdXFpYV1VVU09QVFRVVVGfm5ubUVGgn6CVXHGdaWhnXmJhYV9jZmplZ2Rma3J1eG5pZIBpaGZoZWFrdGebpWJgZGhpaGZgXmJiY2h0c2dcX2ZtcW9mZmpnZGVmYmBiX2JiZ2Rtbme4p5yTkZSVl5+fqV9qa2dkaGpkY2pwdnBtbWtlqnx+qbNbtrO0XrWzYWVjY7SsXWRoZ2JhZWhgXmNfu2G5q66rpqa1tLldtrm6YV+wXICwr7VhXmJkZWVjaGRpZmhiYWJgYLSmnKytq66yXmRls7a2iIOTsl1gY2BlZmhramhtb21ktKajpKKgpqm2Zm9wa2lsb2Zsbm1vc2tlYWZrd3Z4enx6c3RydndwbnRzc3d7dXp1dnZ+goKCh4V/h4OEfn+Af3t1c3Z5c3p9ent5eHh/dWlyc3J4gXiBcnJ2d3RzbHZ2foN9eHl4eX9/f4OLiYWCgHt+hYmKiIeLjYt0nrZyeHh1ent8e3hzcGplYFxaWK+npaCgnpyenaGgl5SXmJmen5OIg4SBfYOGj5Oco5ymopCDipyhn5uZlYqLjYuJhoR7fYN9f3yAVlhfal9TXG9CTE5DSFJSUVJYVlRPUkxTUk1LSElPTkxJSkY+Pz1ASURGRXZfaY9PlpNOTYtLV1pZV1JPUlNXVVdVVU5RVVlXWV5dVExRWFRXW1hUU1ZTU1dRU1JTVVNTVlafn1dZWl5dXFxiY19eY2VqYahcZ2tjaWdeXWVue2+AbmZobmdjYF1gcHt9dmptbGpbXmdnYGBzY1p6f3dwbWhodXRkbGhiaGtzcHN9dG1gVVpvbm5pZl19k2FgYmdoZ2RiaGlgXVtbYGdoZFxKYExQV1dUUlZWU1NPT1FOTE5TUVRRUFBPUVNTU1RVVFZaVFBPUFRSUE9QUlRZWVhVTXuAbWp2Q0NBQX1vaWJfbT4+PD5AQj88PDw+Ozk4Nzk7PDw7PkBBQEFAQD8+QTxBQTw8OTk5Pj8+Pz48PD0+Pz4/Pz4/Pjw7PT4+PDs7PDw6cldPTlRUVFFOTU9RUVRUV1dVV1ZXUUxSVVVVV1VVVlZTVVhaW11bW1paWltbWllbXFmAWFVVWFNTVFRVXWBZWFtfXVlUV1VRV1RRU1JTVFBVVVNXVU9NVFVUVFdUU1NUUlVWV1VTVVhdWVZVWFtcW1hYVlBOVk1KTlFQVFdUVFVVT0xSV1RRS01TV1ZSgmR1TlNVUU5QUVFUTUxNS01MSkpMUVJQTUxSTE5PUU1+dXByh0hPRkhKS0lCQoRBS01KTkxKRkRDQUFCR0lDQ0ZJS0tGRUpNSkRESENAQkZFRkBBQ0VJSUdDQEZERkZAPkVIR0lKSkpCd3J2fH15gIBBRVRdYYQ1gDQ1Njg4NjUzMS8tLS0vLzAxLl1dYGAxMGFhYFk3VX5TUVFKTExNTFFUVVFSUFNXXF1hWFFMUVFQU1FSXWNUa4ZQT1NXWFZSS0pQUU9TYWFTR0hOWFtbVVNVU05QUExLTUpNTE5MVVVQjoF3cGtvcHJ9fYROVVVRT1FUTExTV1xWgFZVUk6BWlp+i0ePi4pIioRGSklJg3xFS09OS0lMTEVFTk2VTZOJjoyFhZSTlUuQlJZOTIxKjYmPTktPUVNRTlJMUUxQSkdHR0iGf3eIiYqKjEtQUYuQjmJcaH1CR0pITE1OUVFNUFFQSYR6eHt5dXh5hUtRUU1NT1BJTU9NUFZOgEhDSE1VVVhbXV1WV1NYWlRTWVdWV1pWW1ZXWl1gX2FmYl1kY2NcXl9eW1NOUVRNUllYW1taYVlOVVZXW2BWYVNUWFdVVU9XVltgXFdXWVtgX15gZmViX15ZXWNjY2JjaGllT2yJV1pbWVtbXlxaVVZRSkRAPTxyb3BraWZlaWduLW5nZGdmamloXVJQVFFOVFVcX2lvanJsYVhdaWppa3BtYWFjYl9dXFVWWVVXVYh+o3+EfgZ/fn5/f36tf4J+j3+CfsN/gn6Vf7CAhH+EgIZ/wYCDf/+AioCDf52AhX+IgAF/uoCIfwWAgH9+f5aAhH+CgIV/gn6ffwJ9fqh/i36Rf4V+B39+fn5/fn6Ef4J+jH8Cfn+Jfgt/fn5+f39+f35+fpF/iH6Df4d+jn+JfvF/gn6Rf7d+AgIEAIDE2dXQ1tnd28/E0OH1jZaanZ2imo6GiI+VlpCYlpWckoyJgoWEi4qLhOCtsNTh+Pr7//Pq7oqRjo2Mh4GBioOChYSNi4iKjIyLiYyHgIeOjIqLhoaIhYD/g4L8h5CMiImJhYiFkI6DhouNi4qTjI+YlpaSq8CLjo6NkIySmp2VknOZoaCfn5uanZqVpKSam6aloJ+ho6OblpygpKCgqqutpp+rqKqinKekm5qbkZ+loZqenaewq6mlppjtxomXlJmYl52ho56dm5iWnaCloJ6ViMqOkZicmJqdnZ2ak5WcmJiXmJaZk5abnJidnZ2cnJ+enqCdhJ+AnJ2ThPLj1uL9/v37/f2ChYSCgoL41cHCzeiFioyKiYyMiYmHhISEhYaFg4SJh4WFh4mKioeHjYmHhoOEh4mHg4WEhYWGhoeFhIaHh4aFhoeGhIWFhYPsmL2QlpaWlJKUlZKTlpWUlZOTlpKTk5aVlJSVlpWWl5WUk5aXl5iXlpVel5eVkJORk5KSkZOUkY2OkI+RkI+Njo6Qj5CQj46OjoyMjI+Qjo6PjIuJio2LioyMi4yLjImJi42MiYeMjIqKiYeJjY2Jjo6LjY+Li4OGjIiGi4WDgoSMioiNjI6TkYSPeJCPjf63uOqOk5ORjZCTkI6Nj5CQjpGRjo+QjInw3dPS2fuEhoaHhIOEiYWBgoGEh4yPjoyMio2HgYiHiImKi46LiY2PjouNjIuKiYmHiomJhoCDiIyFhoeGho6SjIWGgP+BkpOTkZGPioaKkYyKi4qL8umYgouLjISKDIWFiYqKjIqKjIeEg4SBgIL6+PXs8vLhlaHmjIiHiYaJioiIhfr3/YOSkIr9iZGSmZaOkY6TjIaLkZSWqrGTjpCXj42SlZKIgf33gI+XnZGIi5OVlZiRkIj78+bZ4tfU4uLX39bd6PeIkZGHhISEg4iMjYqJhYSGjI+TlY+Uk5Odk4eLgL+kyPyA8/2GgviAgP6CgID//oiPlZucko6JgoL3/fjs4/SFhIiEhYaDgf7+hoL/hISHhIeKjIKAhv74gIOIi4uKgfT2/fbm8vb5/vvy7cuxt+X08fKD+YKJgoaIiIeQlpuTjpOWl5SWlpaLg/rm19Dc6e/t9YCJjJGSlJKQmZmamp2Yl56doKGkoqapgKSepKignZyen56ekp2qoqawq6Kenp+hqa+mp6elopyipKurqKWmnqOfo6uwrqunrbC0saqnqa2mnqOkraqnpKWpqKuqq62qura2t7Kvu7GlpZrL/IiHg/vs4tzV1N3zgoSC9ubr8fXu5OTk0NHS1cS9zMrV2cu7sbO4vcDExcnNG9Ps7tbAwsnIv62wvsbQ1tLb3Nja07izxb67wYCBjYqGiI2SkIiBjputZWttcHF1bmRdX2VsbWdtbWxzaGJgXF1bX19hWZNxepultre4u7OtrmZsZ2loY15fZmJiZWRqaWdpaWhoZmhkX2Vta2hrZmVmZGHBY2G9Zm5raGlrZ2llbWxjZmtta2xzbG91dHV1h5FqbnBucGxwen55d3N7f3x8fHh3e3l0gYB1dIKCe3t8fHx3c3l9gHp6g4WHf3qFgoR8d4J/dXJ2bHh+fHJ3d4CIg4F+fnGuj2tzb3V0c3d4d3J0c3BudHV5dnRtYJJmZGltam5xcHJvZ2lwbWxsbGptZ2pvcGpubG9sbXBvb3FthHFAbW5mWqKck52vsbCvrrJcXl1bW1utloaGiZtaXF1aWVtbWVhXVVZXV1dWVVZaWVhYWlxcXVpaX1taWVVVWltYVYZYBVlXV1lahFmAWllYV1hXVqJpgmdrbG1raGpsaWlramlqaWlsaGhqbGtqamtsa2tsa2ppbW9ub29vbW5ubGdqaGpqamlpaWdkZmlnaWdoZ2dnaGhpamloZ2dlZmZpaGZmZ2VmZWVnZmVmZ2VnZWZjY2VmZmFeZmdlZGRhYmZoZGlqZ2hqZmdfYGUPYF9lXlxbXGNiYWdlZWtphGd+aWdmuYF9pWVoaGhjZmhmY2JkZGVkZmdlZWdkYqubkpGVrltcXF5cXFpdWlZYV1tdYmRiX2FgY15YXFxcXV9eYF5dYGNiXmFgX15dXl1fXl1bVVdaXVdYWVlZYWVfWVlSpVZlZWZjY2NeWl5lYF1fXl+qm2ZUWFlaWFhZWVZXhFmAWFZWU1BQUFFQUVOioJ6XmpqSYm2hZmRiY2FiZWNkYbKxtl9qaGO8Y2lscXBoamZsZl9jaWxudHptZ2hxamdrbWljXLWyXWxzeGxkZmtucnJpamOyraeeoZaUoKObopmfq7Vka2xkYmFhYmZoaWdmYV9hZ2pucGhsbG10a2VpYIyAdI+3Wqu0Yl+4X7pgXV25uWVpb3Jza2hkXV6zta6loq5gYGFdXV5dXLO1Yl+4YGBjYGNmaF1cYbq2X2FmZ2hoX7KzubWosrS4wcG4s5R9e52rqKtfsV1jXmFkY2Fqb3JsZmtwcW5wbm1lX7SjlpCbpamprlpkZWhqbGppcnBwcXKAbm10dHZ3d3Z4e3hzeHp0cnJ1dnR0aHF9d3yCfnh1dHV2fYB7fHp1dHF4eXt7ent7dnp0eH+EgX56gIGCf3x4fH94cHV6g4F9eXyDgICAf4B8iYSFhoKAi4J6em6FsWBhXrGjmZWPkJilWFhXppqfpaigmJqajI2Qk4R+ioaLj4slgnt8f4GDg4SGiY2ipJKCg4eIg3l9h4uSlpCYlpKRjoCAjISAgoBZY19aXF5hYVtWXmZzR01QUlVZUUpFSlFUVk9UUlNaU0xJRUZFSUlMRnJVXnyDkpOTl4yEg09TUFNUUEpMVE1LT09YVlRWWFhXVVZRTFJaWVhaVFRXVFKfUE+XUVpaVlZWU1dUXl1SVlpdW1tkXF5oaWhnb3ZfYWBjZ19iaWxqaIBscG5tbmpna2hjcG9naHN1bWtucHBpZWxrcmtocXV4cGt3dXhsZG9uaGVrYW1xbWNoaHF5dXJtb2aWcVtkYGdkY2ZnZ2FjYWBeZGZraGdgUHBPSU5RTVJTVFdTSkxUUU9QUU5QSkxRUk1PTVFQUlRTUlRQU1NVU09RSkN5cmdsfEV8fX18f0FCQUBCRIBtYmJiaz09Pjw6PT07Ojo5Ojo7Ozo4OT08Ozs9QEBBPj5CPj09OTk9Pz06Ozo8PT4+Pjw8Pj48PDuIPIB0R15OU1RWU1BSU1BRUlJRU1FSVVJTU1ZVVFNUVlVXV1ZVVVlcXFxbWldcW1dUV1ZYV1hVVVdWUlNWVVdVVlRUU1dXWFlZWFdYVVZVWVdUVVZTU1JTVldXWVhWV1VVUlRXWFdQTlZXV1dVU1RYWFNYWldZW1ZXTk9TT05UTElJTIBUU1FWVFVaVlJTU1JUVFOaZ1+BTlFRUExQU09MTE5PUE1RUE5QUk1Lhn93cHGGR0hISUdGREhFQkVERkVLTUxKS0lMRkBGR0ZFR0hJQ0JHSklFSEhHRUNFQ0VEQ0M9PUBEPj8/P0BHS0Q9PjdxP01LS0ZHR0NAQ0pEQUFAQnhsQoA1NDU1NDU2NjM1NjY1NjMwMC4uLi0uLi8wYWJfW11eWT5KgFJQUFBNT1FRT0uLjZNMWVZSlFBYW19YUFJQV1BMUVhcW1ZhWlNVXVdTWFpWUEqQjEtZXmNVTE9WV1hZUVJNi4V4bXp1cn+Ae396gIeOT1RWUE1MTE1RVVVPTkxMToBSU1VXUlZUU1pUS05HZVJphkN/hUlGhUeMSEdHjIpLTlFVVVBPTEZHhYiGgH6KTU1NR0dKSkqQkE5LkU5PUU5RVFVKSU+Yk0xMTk9SUkqIiY+OgYuNkJaXjo1zXldygICASYZHTUlMTEtJUFNVT0lNUlNPUlJRSkSEdGxmb3h7d4B2PUlMUFJUUE9XVlZVVlNSWlpbW1xbXl5aVFpdV1NUV1hWVUpTW1JXY2FaV1ZUVV1hXF1dVlJRWFlcW1tfX1lcVllfZGFdXGFhZGNfW2BgWlFSV2FeXFpdYl9eXl1gXWdgXl1cXmpfWFlRYYJFRkJ6b2RjXV1lbzw8O2xiZ2xuaTNlZmVbX2FlV1FbVlpdWFBJTVJVVVZXWFthdHVmWFhaXFhPU1tfY2ZjaGpqa2NWVl5YV1iNfpx/jH6ifwR+f39+mH+CfsR/gn6Wf6qAin+GgIZ/vICDf/+AjYCEf5WAhn++gAF/kIADf35/mYCIf4J+in+DfoR/AX6Pf4J+i3+Cfo5/j36df4R+DX9+fn9/fn9+f39/fn6Kf4Z+iH8Ffn5/f36Kf4J+h3+TfgJ/fpV/iX7pfwV+fn9/f4h+g3+5fgICBACAvsK+wMvGwcTSyMW/uqu0ws3h/42MkZeUj5CWkpOTioWGhouGjYjkraLE2NDQ2ubs5ubu7/LzhY6NhoSChoqHgf7/homGioiJi4aDgICGjImE//6Cg4iHhYWB+oCFh4SDhPyBh4CKkIyMioWDkpKOjpOVh434kY2KkJGSmJaRio+Al5yXkZSZlJ6cn5qalpqflpelpqGYnpOUm5aZo6Wjm6Kjp6ejp6Sin6iooaGemJurrKiYo62traqoqJPG446UkZGTmaCjlZebmJidlpSTkJ6N/c2Ij5KPj4iJjoyPk4yQlJiYm5eWlZeZmpqgop+ampiZm52Whvbr3+eAhYWEg4JRg4KCg4OEhYWFgoKBgYSGhfjXy8nJ2P6LiImFgYCEhoaHiISFhoH++f2CgoL9/f+EhYSChIP+goSC/oCEhYGBgIGDhYSDhYWFhIKDgs+L8ZmUhJMnkpGVlZaYl5eSk5SUkpSVlpWVl5eUlZaWk5aVlpqalo+MkJOSkZSVhJOAkpCQjIuPkI6NjIuNjZCPkI2NiIqQkZCPko6LjIyMi42Njo6OjYyLi46LiY2MiYqLiIuMiYuOjI2MjY2OjI2Nj4yOjpCRj4uMiYODhoeKiYWLkY6Rk5SVkYyLjIqIh4PWtMX/jo6QkZCMjYmLjYuLh+/c2NHX8oGGiIaDgYOCg4UXhYGDi4aDgoOKiYmHiYuJhYGAh4mGiY+EjoCQjIyMiouBhIiLioWIiYaFio+QiYWDg4uMiYL9/oeOjouEhIeJiYOA/P6DiImLwN3PiomJiomIiYqJiYmMjY2MjIqFhYKA+fn8gIH19fv776KowImPioaGhoyFho6MhouF//+HjJGOkIyJi5CWjZGUj4iHlZ2b3P6Bh4aIgoGDg4CB9fDe1NHO1N3o4uLh3eTT0uLU3eLu+Pfs/YP+iImChYeGgPD5hoWD/YWGi5SWlpOXlpOLhYeMk4yChJGNj4SJj/S4u+f4/vv9gID9hIWOi4aB+f+Hh4CDhPjp74CGhP3y/PL8g/r5+v6Ch4WFg4OBg4WDgvz/hoaAgPbx9v2Bi4CRkYuFhf+AgYbpuq7E2O7u5+vu8fiCg/6EhIWDi4+NjZKYk4+JiJWXlpSSkpaMj5eVkZORjYb27+Dh4+Ho5eLj6feBhYePj5acnZaQlaGhrKibm56YoqScm6OpoKGXmaOclpWcoKCbnaCjopulpKSopKWlo5+jp6+qppuWlZiksWW2rJyhqaqwsKialJWUmJaQi4eC+fTg497X0Nbj8f/79uvs9Pn5gID84dDWzsjL0dnb2drV1cO6w8vQ1NHGx8bDycC9wMPAvbW9vLq7v8PK0tzQwLW0sr/Gu8bFtau3vrizs7K4tYB5eXd8hYF9f4mEgn98cXiIlqG3ZmVpbmtoanBqa2pgXF1dY19lYqJ6dY+emZigqrKvrrKxsrFgaGhiYF9iZmReu7tkaGVpaGhrZmNhX2NoZWLAwWNlaGhnZmK9YWVmZWVlwWNnYmpvbGtqZ2Zwb2xucXNnZbpvbmxzc3R3d3Rrb4B3fHVvc3Zye3p8dnZzdXlycn+Ae3J5b3F5dHZ+gIB3fn6Bgn+CgX16g4N8fHVydoODgXN+hYSFhIOFc5CsbW9tbXB1eXxvcXRxcHRubW1rdmWvkmBlaGRkYGFlY2VoZGhrbWttamhnaWtsbXF0cm5ta21vcGpcqJ+Wm1ZbXVxaWgFahVsYXF1dWllZWFpcXKuTioyFjqpdWVpXVFJVhFclVFZYVaekp1dWVaOjpVZXV1VWVaZVVlWoVVhZVFNTU1ZYV1dZWYRYMVeQXKZsaGhnaGhoZ2lqbG5tbWhpa2xqa21tbWxtbGlqa2xpbGtscHBtaGVoa2lobG6EawlqZ2dkZGhqZ2aEZQ1qaWhmZ2RmaGhoZmlohGSAZWZmZ2hpZ2ZlZWllZGdmYmNlYmVnZGRmZGVlZWZnZmhnaWVmZ2praGRlYl1bXV5gYFtiaGVoamlpZ2JhZGRiYV+bgYu2ZWRmaGZiY2FiZGFhYKmZl5WWqltfX1xaV1paW1xcV1piXFpYWF9eXVtdYF5aV1deYF5fZWJiYWNmYmGAYF5fVFldYF5ZW11aWV1iZF1ZV1dgYV1Yq6tbYmBeWVpdXlxXVaipWFxdX4OLiFlYV1hWV1dYWFhZWVpaWFhWU1JPT5qaoFFTnZ6hoJlnb4dkaGViYWFnYWJpZmBlYLW1YWRpZ2lkYWVob2ZpbGhiYm93dJ+qYWViY15eX19dsbGAopuYlpqiraiopZ6lm5eimaKjq7a2q7Nct2FiYGRlZF6vtmJhYLZhYmducXFtcHBuZ2JkZ2tlXl5qaWxiZmu0hYaotLa0t1xduWFgZ2diXre5Y2VgYmK1qK9eYmC1q7Oqs16ys7W2XmJgYmFgX2NkYV+6vWNiXFyyrLK5XmZpaGSAYGO9X19kq4V2hpWlpaClqKiuXF21XltdXmNmZGVscW1pY2JucHBtamluYWRsamhpaGVgsKuhnqKjqKOfo6axX2FjamptcnZxa251doF/c3Jzbnd3cnN8gnd3bnB3cGtqcXd3dHV3enZud3d7gXx8enhzdnqDfnpwa2lwe4SHfXBid39/hYN8dHBwbWtpZF9cW7ComKCblI6QmKOspJ+VnKSoqFZVqJGFjYmHiIqRko+PjZCBeICHjZSRioyMiIyFgYKDf3x1fnx5eXyBiI2Sin96eHeBhX6EhXpyfIJ+enh0eHWATk5MTFRST1BZVlZTT0hPXGd0jE9MUFNQTlBWU1VVS0dISE1JUE6AXFdzhHx4eoOPioWJh4iKTldXUlBNT1BQTZaXUVRRVVRWWVJQT1BVWldSnp5SU1dWVVVQlk1UWFRTVJ5TWFNbYlxcW1dWYmFdX2RmXVClYmBeZmdpbGplXmB+a29pY2htZ25rbmlqaGhsZGdzdXRscWhmaGNnb3FxaW5vdHVydHFta3Z3bm5nYWRyc3JjbXZ3dnZ2eGNxilxhXV1fZGdqXWJmYmFmYV9eW2hYjm9FSUxIR0JFS0hKTUZKTFBRUk9NS01QUFBWWVdTU1JRUVNORXx1aWg7P0BAhD96Pj9AQUJDQj8+Pj4/QEB5Z2JfWl1xPzs9OjY1OTs7Ozw5Oj06b21vOjs5bm9vPD08Ojo5bzg5OXI5Oz05ODc3ODo6Oj09PTw7PD1lQ4BVUVFRUlNTUFNVVlhWV1JTU1RSU1ZXVlVWVVFTVldUV1dYXV5aU1FUVlRTWVuEWoBYVFRRUVVXVlRSUlRSVlZXVFVUVVlYVVRWVVJSU1VVV1dYV1hYV1VWWVZUV1dTVFZUWFpWVVZSVVVWVlZUV1dZVVdXWVlVUlRRS0lMS05NS1FVVVdYVlZTTk1RUFBPTHpnbItNTlBSUEtNSktOTE1Mhnh3c3SFR0tLSERCRUVGR1ZHREdOSEVDQ0ZFRkRGSUlGQ0JJS0hJTktLSUpMSUhIRUc7P0RHRkFFRUJAREdHQj49PUZFQj10eUJIRUI9P0JDQDw6dHg/Q0NFXF9UNDMzNDMyMzQ0NoQ3gDUzMS8vLi5bYGMxMV1iZWNeQlFsUlNQTk5PVE5QV1VPUUyPj0xQWFFSUU9TVVhQVFZTTU5cYmCAfFBQTU9KSktKSYqEdXFyb3eAiYSBgnx/d3aCen9+hI6Rho5LkU5PS05PT0qGjE1NTJFNTVFXWlpWWFdWUktNUVZQSktST1BHgE9Wk2ZlfYODhIVHSI1LSlBPTUmNjEpNSk1MiHuDSk5NlI2Vi45JjI+QjklOTk9PTkxSU05MlJdQUEpJiYeMj0hOUE9MSUuRSktPg2FTXGl6e3Z5e36CSEqNSUZFRU1RTUtOUlJQSklTVFRRTkxPR0lPTkxNTEtIg353eHl5fnx4gHl7hkhISlBPUldbVUxQWVdhYVpWVVBWVlJVYGdfX1NRWFJSUVdbW1hZW19gWl5eYWZjYl9cWFtdZF9dVVBPVV9namNTWWFgZWZiWVRUUlBOS0VAP3x1ZmxnZWJjanBzb21mam9xcDs7cmBXWlVWWFlfX2BhXl1QSlNaYGZkXV9eJltfWlpdYFpVTlVUUFBTWF1gZl9XUlJRWltTVlROTFJXVVNSTk1Kk36Tf5B+in+Cfo9/gn6HfwF+hn8BfpF/gn7Gf4J+lH8Cfn+jgIR/loCHf4+ACX9/f4CAgH9/f4aABX+AgIB/koCDf/+AkYCEf42Ahn/AgIJ/i4CCf4SAA39+f5WABX9/f4CAhn+Cfo5/gn6TfwJ+fYl/mX4Cf36HfwZ+fn9/f36Yf4h+A39/foZ/gn6FfwZ+fn5/f3+FfgF/hH6Lf4J+hH+Efod/BH5/f3+MfgN/f36ef4x+0X+SfoJ/vn4CAgQAgNvi5uXa0MHIysfNxbu1srW1p5imvdDl/IHxiYX+h4D7/4SH+7+fq8LL0tfT5OjzgYH48fvy7u/8h46GgoOB/oSDgIGBhYaFhYyIhYaCgYWEgIWGhYaGh4mKiISCiZGTjoqIiYSDhYiAg4mKiYyIjJGIidaaiJOXj5ebk5qcmJSZgJqbk46RiZCNmKaoo5WYmJuio56cmaGVjpOXk5yaqKiWm56epaiprZianaKioZ2Zk5ifo6GbmpiXlpuhnvHF+ZCSjY6Qk5ihmYuIlJiXlJOPko7w68mFjIuKkI6PkI+OkZSZmZqanJ2WlZqZmJudm5OF9Ojm4/OCg4aJiouKh4WGBIWDg4OEgoCBgoSGiIeJiIeHhIKB89PGw8PD34CIh4WFhoaHh4WCgfmAgoH9+vr/+vn39viA/vz28vT2gYKBgYKBgPv//4D+/IDqnqOJlpOSk5OUlJSVlZSXlpaXkpKPjpGTko+PkJCQkZOVk5SWlpSSkpaUkpGRlJSTkZKPkJGSj5CPjY6RkICQkpGOi4yNj5GQkJGOiIiMj42PkI+RjouMjI2OjoyLkJGOjIiIjI6Qj4+PkI6MkJKNjYyJio2LjImOkZCQkY6KjY2IiYmOj4+QkpKUkYqJjJCNioqNkJKUkIfUuMyDkJKSi/jf1cvQ34CD+4CBh4SChoKChYaBgoCDhYiEgP+Bg4CIh4qKhoKFg4CEi4aLjouOjYmEhIaIhomHhYaHhIuJiIaFhIiKi4qIgoWKgIOGiY2NkY+OioKAiI+NhP+AhIPs/Jr5iYqJh4WFh4iKh/eGiYuMi4mHhoX+8ujr7PP79/Tkn6ey5vKHi4aFgYKKioeIhYyKh4aDgoKLi4yKioaFhoCKhJGYjoyTlp2Rhp3f3ujm4/mDh/mGgYKDhI6Ggv2B+oKBhYiFg4H8+PP+g/v+/4OJh/n/+vf3/ff6gIuQmpeTj4aOj42LhoOGjYeNj4mFgoecn5+emIvcssLl8vL+9/H38vP/i4uD9fn7goWI+4D07fjt+Pz38en3gv/8+PX59YD3g4aD/P2EgoCEhYeNjYeBgYL8gYOGhImF67upv+z59Orw+PLx8viDhoKDioeGgYSDj4mKgYOJjIuLj5OUk4+QmZ2cmJiUlJiYl5eWl5ucm5eXlZWSioqIg/zz+PX27+rw7+3q9PiA993b5fv77ur4//j3gIWGh4iFhomKiImNhXiBg4WGhIeLi4qHj4yHgfuChIDv4ung19HRy8LW4NnZ1dXX0MnC0N/c4ubWysbT2uXh5fHt6NrLxdDZxsbQ2OTp4eXo1c/K0+TSv7qvvLu4t6+0vr29z9XRzdbRw8K+usDIybzDtrjAvca8rrihscG1rK/Fz8rFz9eAlJmcnJaNgISEg4Z+d3FudHZtYnGHlqa6XKliYLdgWK6yXWC0hm97kpmcnZmnrLZhYbWxua2rq7RkamNgYV65YGFdXmBmZ2ZlaWZkZmJhZGVhZGVnaGdqa2xpZGFob3BsamlqZmVmaGFkamxsbmltcmpsonBncHZweHx1e3x6dnqAfHxzbXJobmt1hIeBdXp5e4KCfHp2fnNobnNweXeFhnN3eXuAgoaJdHZ4fXt6d3VwdXyDgHh4d3Z1e4GAu4+7bGxnaWpscHRvaGRtcHBubWpua7CrkmFmZGJnZGVmZGNoa21tbWxvb2ppbGpqbnBuaF2to5+YoldXWFpbW1xbW1tVWlhXV1VVV1hXWFlaW1pbWlpbW1lXoYyGf31+kVRZWFVUVVZWV1VSUp5RU1KioaOmop6cnJ9So6Gcm5+gVVZVVVdVVKaqqVSnpFSaZm5ja2loaGhqaoRpgG1rbG5paGdmamtqZ2doaGdoaWtpaWxtbGppbGtpZ2hqa2loaWdnaGlnaGdmZmpoaGppZ2VmZ2lqamlqZmFiZWhmaWlpamllZmVnaWlmZWtsaGZhYWVoamlpaGhmZGhpZGZlYmRlZGVhZmloaWlmYmVlYGFgZWdmZ2hoa2ljYmNmgGRiYmRmZ2lnYZeCkF1nZ2dirZuVkpOaWVywWlpeW1ldW1tcXFdZWFpdYFxXrVhZXl5iYV1ZXFpXWmBeY2ZiZWNfW1pcXFteXVlaW1leXFtbWldbXV1cW1dZXlZYXF9jYWRkYl9XU1pgX1mrVltapqhhn1dYV1VVVVdYWVeeVVdYgFlZV1VTU52Wk5WYnqWhnpZqcXyiqWBmYWFfYGVkY2JeZGRhYF5cW2RmZ2RkYmBgYV5pb2Vja291b1xvqKWtq6W1YWOzY15hYmRsZWK5XrdfXmJkYmFgurWyumG4u7xgZWS2vLa0srq2tFxkanRxbmtkamxqaGRgY2ljamtkYl9kdnZ4eXdzaaJ9jau1s723srWwsLlnZmCxtbthY2W6XrOsta20tbKuqLRfvb25t7exs2JlYri5YmBeYWJka2tkX2BiuV5fYWJlYauKfIWjr6yiqrOura6yXmFeX2ViYFxeXmhkY11eZWdmZWZnaGdkZW1wcG5uamqEboBvbXFxcW1wcXFuZ2ZkYLeyt7m1q6qurKiosrVbtaOdpbq+saixtrGtWl1eX2FhZGVkYmNlX11fYGFgY2VjYWBmZWBcr1teWaOZnpiWkI6IgI2VkpGMjZKNg36JlpWbnpCFgIqPmJWWoaWfkYWCipCFhY+XoaSdn5+OjIqRnY5+eS14hoWBf3l9goCAi5COio6MhoeEgIOIiHyDdnqDgIl9cntpeIN0b3SEi4iEjZKAYmZnaGJdVVxbWFtVT0tLUFFJQU5fa3iIRoFQTZFQSIiISE2OZlRfdHt+gHqCho9NTZGMkYeGiZNSVk9NTEqVT09MTk5SU1JSV1JQUlFRU1JQU1RUVFVWV1lXUk9WX2BZVFNYV1dXW1dYXV5eYl9iY1xiklxhY2ZiaWppcXJvbW2Ab29mYWVeY19qd3ZxZmppanN1a21sdWteY2dibGh2dGVmaWpxdHh7ZWhrcHBwbGhgZm1zcWlqaGdkbnVyonadXl9ZWlxfY2djW1ZgYWBfXVlcW5SAbUpNSkdLSElJSEdKTFBRUVBSUk1NUU9PUlVUTkV9cGhhaDg4OTw9PT4+PT1uPDs7Ozo7PD08PD09Pj0+PDw+Pj08b19aU1JOYDg7OTc2ODg5Ozg2N2k2NzdsbW1vbGhnZ2c3bmtnZWZnODk3Nzk4N21zcjhubTlsSlZMU1FRUlJUVFNTVFRXVVVWU1NRT1JVVVNUVFNSUlRXVVWEVxpYWVZUVFRWVlVUVFJTVVZVV1VVVVdWVlhYV4RWgFdXVldUTk1QVVNYWVhZV1RVVVZYWVVUW1tYVVFSV1laWlhYWVRRVldSU1NRU1ZUVFFWV1ZYV1JPU1FLTExSVFRTVFNWVE1MUFNRUE9RUlNWVE57ZXJJUVJRTIl6cW5yd0dJiEZHTUlHSUZFRkZCRENFSExIRIdDRElHSUpGQ0VEgEJFS0hOUUtNTEZCQkNEQUNDQEFCQEdFQ0E/PUFDQ0JBPUJHPD9ERUlIS0lIQzs6QEdDPHU7QEB6ckJlNDQyMTEyMjMyMV80NTU1NDMxMC9ZWFhbXWFkYmFdQktjjpdTU05NS0xSU1BQTFRTUE9LRklPT1BPT01MTlBNVltPTVVbgF5ZQFaLiJGQi5lPUZJQS01NTlVRUJVMlE5NUFNPS0uTj4qTTpCSlUxSUZCVjYqMko+JRU5UXFxZV09XWFVUTkpPU01UVk9OTFBfYWJhXVaFYmeAiIeQi4iNjY+TUE9JgYaNSUxNjEeHhJCIkJKQjYeSTZeUkpGTjY9MT0+UlE5NgExPUFFWWFJOUFCTS0tMS09MgmdVXXmCf3iBiYSDg4hITEhITkhIRUdHUkxOSkxRVFJPTU1NTktMUlRUUlFNT1VVVFNTUVdXWVZWVVVVUE1MR4R/h4mIgoGCgHx9h4dDhXRveYyHgYCIhn57QkhJSktISEpNTU5RSkhJSElKTU5LcEpJS0lEQHtCQ0B1b3FrZ2RjXVZgZ2FeWlpgW1VQX29qamhgWVVcXmRgYmppZWBcWltcVVxhYmluaWtqXVpZYGtdT01OXFtaWFNWW1lZZ21qZ2hmYWJdV1teXlZbUFReW2JYTVJEUVhPSUpXXV1aXmKYfgt/fn9/fn9/fn5/f4x+gn+HfoZ/AX6zf4J+yH+DfpN/A35+f5yAhX+fgId/jIAEf4CAgIl/AYCGf4eACn9/f4B/f4B/f3//gJaAg3+FgIZ/A4CAf5KAAX+9gAh/gICAf35/f4qAAX+JgIt/hH6kf4h+A39/foh/A35/fod/hH4Hf35+fn9/f4h+nX+Nfgt/f39+fn5/f39+f4p+AX+HfgV/f39+fox/AX6Gf45+sn+NfgF/jH6bfwR+f39/5n4CAgQAgMjLwsjEsaipqqqzqaa0s7u0pJylrrGimJSgvcLW6/f+9cCpsNHSyNLa4Nvh6enw7/L28O7t9Pz9+v/9+oD39YSHhv75//6Bg4SEgYSGi4mFg4WEgfv0+4CKjoqDh5CWioWKj4yIhYiGg4OIjIeOl5GJh5XWlJOMkpaYpJmZoJGagJ2Yi4yRl5iRk5eSiZyknZegqJeWmJ6mn5manp+boaOemJiXmJyfpaCgpqimnZ2jm5ufoaKeo6OcmpmQi5qRx8yBj5eWk5CTl5yflZGTlZSTlZOTkfTtu4CIjZGPjJGSjpCSlZWWlpSTlJGG+O3n4+j1/YGFhoeFhISFh4mLioqKTYiHhIH/gP7/gICAgYOEhoeGh4WFhISCgoGCgOXIwsDBxNr5goH+g4WDgfv+g4KCgYH//P+AgYD/goWHhYWAg/31/P+BgP+BgfbAh+KThJcdlZSUlZWRkJGTkpWPkJWUkpOUkpGQkpKTk5SUk5WFlDyTk5ORkZCPk5SSkpKRkJCNjI+OkJKQjpGQkJSTkpCMjpGPjouHjZGQkI+OjY+QkJCOjo2Ojo6NjI+OjYyEj4CLkJGQj4qIiY6Pj5CRkI+Mi46PkI+Qjo+QkZKPjI+NiYiLiIiIio6SkJKWk46JiYWB0YaczNPY7ICBhIaHhIGBgoODgYH+/fyC/fiAhIiKivuBgoOFhoKFiID7hImIioqKiIaFhIaH//b8/IGGgYOJiIqHiP+Bi4aFhYOFhvb6gICGgPX3hIiMi4KBhoj//IKBgoDymvDbgoOEhoeGgf6A/4GDhYSDg4aHhIWEgf/59PHr6PbhkqKy7/7x8PD9hYeDgoSHhoiHh42IkI2K9oiIi4yOj42KiZGOjoqGj5WalbSK/YGFh4WFhPaKiIWCgYeKhYuTjIuOjIqJiIiFhY+GgYCHgvmFiI6Tjf+EgIKC/OiAk5KPjISBg/mCgomGgYiHhPqE9pWen5yblpqVlZ6gi9qzx/6JjIT89f6Cg4Dz8/mEhoeHhISDgoH5/IH3+fjq9fv88+3+/Pzy8fH0/oKGhYSDgPn29P33/oSFgvHAqrHS4+jm8f7+9f/27fmCgISB/4D37efr9Pv//Pv3hIeGioqGjZCSl5SQmZWSlZSTnJaXlpKSlZqem5qXmZyWmKGloqGhmZ6bnJ6gpKOamJ2bsK/JvLq9tbK1s7izrKuoqayzsqGPoLCys62epLPH0t3Z3MjD18vN3uzjy7vFxdTe5+/39e7m2dPj9vno3NTO0dfe1U3f3drZ2NPY28rAv728yszRy8fDubzBxcrHxLmvxMW4tauksK+yvMnLzMjS1dTM2djJvrK3uMDDu7Cjq7rAzMvTyKuup7rV0szMy8S9wYCHiYKGgnJrbG1tdGtnc3R9eWpjanF1amRkboaFlKm2vrmKdnyZnJSbpKiip66ssrCytLCwsLa+uri9u7phu7hiZGS/ub6/YmRkZGJkZmpoZmVmZmTDvcZma25oYGRscWlmam5taWZqaWVlam1nbnZybW5vp3NxanF1eIB2eX5yfIB+e29udHl4cHJ2c218g3p1f4d4dnV8hHxzdXp9eX+BfHZ3dXV4eoB7en6Bf3h4fXd3e31/fIGBend2cG54cI2WY2ptbm1paWtucGlobW5tbG9vcG21qoteYmZoZmNpaWVlZmppamtqaWpnXqqinJibpq1YWlpaWVhYWVtdX19eXYBbWlZUpVSmplNTVFVYWFpaWFlYWVhYVVRTU1KTgH99fHiIn1JRoFRWVFKgpVZVVFNSpKSnVFRTo1NXWVhYUlSjnaSpVlWoVVWiglqhamxrbGtqaWpqamZlZmdnamZma2ppamtpZ2hqbW1qa2tpa2pqamxsa2tpaWloaGxtamxsa4BqaWdmaGdpamlnampqbWxramVnamhnZWJna2pqaWhnaGpqamhoZ2hpaWdmaWdmZWhpaWllaGlpaGNgYmdoZ2lqaWhmZWhpaWhoZmZnaGhmY2ZkYWFjX19gYWVqaGpua2ZhYV1blFxrjpOUo1hYW19hXVhYWl1bWVitrKtarqlYXIBeYGCqWFhaXFxYWl1VqFpfXV9fX11cW1pbWq2mqalXXllZXFxeXF2rVl5aWFdXWlulqVdbVqOkWV1hX1dWW12sqFhXWVWnbJuKUVFSVFVUUZ9QoVFTVFNTUlZXVldUUaKdmZmVlJ6VZ29+qrasrKy4Y2RfX2BiYmZkYWhnbWpmroBhYWRjZWdmZGRramhlYGhuc3GBXb5gYmRjZGO0Z2ViYF9lZ2JnbmdnaWRjZGRkYWFqY19lX7RiZWltab1jX2Fgu6tdcHFtaWNgYrpgYGZjXWNhX7hhsXB5eXd2cHJsb3d4aKGCkbplZ2G4tLpfYV6ztbliY2RjYWBgX12xtV6ztYC1qLK5ubCsvbq6s7KytLpgY2JiYl+2srK7tLdhYl6vh3V4kaCloqiztrK7sKezXVtfXbmyqaers7i6uLexX2BgZ2ZgZWdmbWxocGxpa2xsdG1tb2xscHN2dHNwcXNxc3l6d3d4cnRyc3N2eHdycnVweHaHfn+BfHp6eH55cXFwb4BxeHZpXmt1dnhzZ213gY2WkZKEgpKJi5iknIZ8hYWRmp2epqWfm5GNl6annJaPj4+SlouSj4+PkI+Rk4d/f4CBi4mNh4eFfoCFiY2LiYJ7iYZ9eWxodXd5gImKi4mSlZWOl5eMg3x+foaJf3Jrcnx9hYaNhXF0bXuPjYqKjIaAg4BaXlteXFJNTU5NUktIUVBaVktHTVFUS0ZHUGRldoeRmZRtYGeAgnuAhIZ/gIaEiYaJjImGiZWdmZOVk5FMj45PU1SdmZ2eUlNTUk9QU1dVU1FRUlCak5tQV1tVT1JbX1ZSVl1dWVdcW1hYXF9aX2djXWFWkGllX2ZnanhubXRoc4BzbWBgZWxtZmhrZ2BtdG5ob3VnZmhweG5obG1taG1saWRlY2RnbHJtbXFzcmxtcmtrb3Fzb3V0bWxrY19rY3B1TlhdX15aWVtfYFhXXF5cXGBfYF6SfmlFR0tNSkhMTUlKS01MT1JPTlBORoF7c2pob3I6Ozo7Ozo6PD4/QEA/PhY8PDk3bTdubzY3ODg7Oz09PD07Ozs6hDczNmFTTlJRUVllNDJiNTc2NGZsOTg3NjVpam44ODdsOD1APTs0NmplbHE8PHU7O3FcQHtRhFQgU1NUVVVQUFBRUFNOUFdWVFZYVFJTVlhYVldWVFZUVVaEV4BWVFVVVVlaWFlaWllYVFNWVFZZV1ZZWFhcWlVTUFJWV1ZSTVRZWVlXVlVYW1taWFdWV1lZV1dZVlVVWVhXV1VaWVdVT01PVldZW1tZV1RSVllaV1hVVVZVVVJPUU1ISk9MS01QVFlVVVhUTUhKSEh4S1dzdneARERITE5KRUVHShtJRUSFgYFFhoFESEpLTYRGRUZHSENGSEKBRkuER4BGRUNCQkF5dXl6QEhBQUZGR0NFej5GQUA/P0BCcnc+Qjx1dkJGS0Y9PEJFenQ8PT05dk1pVzEwMTM0MzJjMmIxMjEwMDAxMjIzMS9hXlxcWFlhWj5IYZCajo+Qm1JQTE5PUVFTUFBWU1tYVI1PTk5OT1FRUVBXVVNRTVdbYF5oRYCZTlBQUVJQk1ZUTk1KTlJOVFpQUlVUU1JQT09QWVBMUkyNTlBWWVWaUExNTJOCSllaV1VRTlGWTk1ST0lPTkuPT45aYmNhYFxeWFlgYVWFZWyMS01JjImQSUxKi4uOTExNTUtKSklJjY5KkJCSi5SamZCNm5eXk5KTlJlQUlFQT4BNlZGQmJOVTk9LjGhUUmNrc3N4g4eDi4V9ikpJS0eOiYJ/hIeLj46PjUxOTlJQTFBQTlFRTlRQTU9SUllUU1JPUFVbXlpWVFZYUlZdXFpbW1ZZV1lbXGBfWFZVT0xRYVdWVlJOUlNYU0tLSkxPVFJHPERNTk9NQ0lRWWZva2lZV2xlW15seXNdUllZZWprbG9taGNcW2FrbWZgXFhWXWBbY2BeXFxZWFlTUVRRU1tZW1VYWlVXWVtfYV5YVWJfV1VOSlRTU1phYmVkbG1qZmtoYWBZW1teX1NKRUxVV15iZVtJS0dXZWFdXF1cWFi8fgZ/fn5/f3+Efo5/g36bf4J+yn+CfpR/A35+f5SAh3+SgAR/gH9/k4CIfwOAgH+EgIJ/hYAHf39/gICAf4eAhH8FgIB/gICEf/+AmYCHf42ABn9/f4B/f4WAAX+JgAF/jICEf4mAAX+IgAd/f4CAgH9/iICCf4SABH9/fn+HgAN/gH+MgIl/iH6PfwF+kn+DfoZ/AX6ZfwF+hX8BfoR/gn6IfwF+iH8Dfn9+jH+Efgx/f39+fn5/f39+fn6JfwN+fn+RfoZ/hn6Df5B+hH+LfrN//36RfgICBACAt6+op6W0r6OnrKyzw7ewrKSlpqevsqOmp6Opr6yZjZyUrNPs9Pfw6u7y8Onr6OHi4uXl7oGFhYH69fH4+vn5+oKGhP/8/fr37/SEhfz1/Pj29Pn++/f6/oSHi46Njo6NiYmNjpSZjoeHhIyQi4eOj4zihvOMkI+Ok5eampycl5eAmJeNkpeTl5KUmJmcrKSjmZWZlJSSiZKfmZWamJ2kmZaNl5qWjI+OjpeYnqSdm5+cmpyXmaGZnaKfm5aVk46I3qrIg4qMjo+VlpyTkpaYkpGSlJmfn5Hz2Kn8iY+OjpGPkZGRioDx6+Ti5/uGh4WEg4SEhYWEhYeHhoeHh4aGhYWAhIWEg4WDgYCA/v7/gP79gIKEhIKB/4GCgoWFg4GCg4eG+drPysnExNjwgICDg4SFhIOA/fT2/YCCgoODgYCA/vn59PD2+PPWkrSOmZaUlZSTlJSSkJKVlZSXlJKSkY2QkJGOjpCNjY+Sk5OTkI+PjpCOjo+Pj5CNjZCPjYqMjI9EkpOQiomMkYyIio+Oj5CKjI2Mjo+Ni4yNjo+Oj5GRkI+Qj46OjY2Mi4yLiIeJiY2Oi4yPkpKSj42LiYuMj4+NkI2LjY6EkICOjpKTkZGUj46Qjo+Kjo2JipKSjYLv1s/JztHb7YGEiIqIh4iHh4iGhIWAg4SFgv/9gICCgoSHiP6Bg4D3+IWGhID9//mCiYaGhomJg4KBgoSDg4aHhomIhoWCgoGAg4KDhoSFgoH46f3//oOCgYOFiYX9gYf+8P+BgYL3rt+4+4D59Pj8/Pz5+Pr59/r37fz/gIKEhYeGgfH5+PLnyoCZv/vv6/GAgPjx9IWFgYWIh4eJioqLjo+Wk/v6hIiMjIuMk46OlJOVkZGKjO3tz4CKiIOKhpaRhIaIh5Kci4uWk5CXk4mMkpORjI6SkJOSkI2IhIHt8vP1+fCGi4P+gYKG/4CAgomKiIT8g4mOkpqhoJWQipONhoSIj5CXm5uch8W40/j9g4SCg4WGhYKGi4eAhIWJj5OMg4SEg/z08/Pq5eTx8ufk6er09/aDi46M+IOCgYT/+Nq0nKvIzszZ39bh6ezx6/2G+/Hl5/Tt5N7d5+jsgIGB/4D//oCHi42JioeBkYCRjY2Oj4yNkZeWkJSYk5Wbm5uZlpeXlJaZl5aVkZmampmZmpqgpKDwj8TUu8LEuK+7yLSypKykmrvLw6KXnaqosLKoo8DZ4N7S0Ne0wNPg6ezv49nQz9Xj4uPc4/Tr4uzr+u/t+fv76uHYy8XBxsjGwsPGzM/GyMnOw8DAs7K2tji4vry0rLbBv7ixsL/FwsPOz9Lb2MnCzMfLzMbDvamhqqmrraq2xMTEx8/MtLHK2tLW4N3Yvbm5toB/eHVzbnZyaWtubnWGe3Vxa2ttcHh5a25tamxubmVgb2l9nrO6vLSutLi3sLGspqemp6atYWRiXbSxrrW6trW1X2RjwL2+uru2uGNkv7q/vbu4vMK/u7/EZWdpa2pra2pnZ2tucnZtZ2hlbG9sZmtwcrBguWxwb25ydXd3d3h2enZ8eG1wdnJ2cXJ2en2JgYB2c3dyc3Jtc312cXd3fIN3cWlzdnNpa2tqc3Z7gHt5e3d2d3N2f3h7f3x5dXRxbGeke5ZgZGJkZmxscGhobXBqaWpscnh3a7GaebZhZWVlaGZoaGdiWqmoopyerFxcW1lYWVpbW1pbhVwzW1tbWllYWFdXWVdVVFOkpqdUp6ZVV1lZWFaoVVZWWVhVVFRVWluqlYyIg31+i5xTUVRVhFY/U6OZmqJTVFNUVVNSUaOgoZ2an5+bjmF7Zm5samppaWxraGZoa2tqbGlnZ2dkZmdoZWRoZWZpa2xra2ZmaGhrhGmAaGpmZmpqaGVmZ2lqbWtlZWdrZmNkamlqamVnaWdoa2hlZmhpaWhpbGtqaWloaGhmZ2ZmZ2VgX2FiZWhmZ2lsbGxoZmNhZGZqaWZoZWNlZmhpaWlnZmpqZ2drZmRmZmZhZGRhZG1sZFuql5GNkI+Wo1pbXl9eXV9eXmBdXV5YW1yAXVqwr1lZW1teYWGuWFpXqahcXFpVpqulV2BeXFxfX1lXV1haWVlcXVxfX1xbWVlXVllYWl5ZWlhYp5urq6pYWFhaW15aplVarKCsV1harHWQcpuZmJ2hoKGfnZ+fnZ6blZ+gUVJVV1pYVJuenZmTg1dpibaqrLNbW7CrsWNjYGKAZmVlZmdnaGtscW+4tl5gY2RjZWpmaG1sbWtrZmixp5hfaGVhaGRwbGFkZWNsdGdncG9rcW1laG5uamdobGpubmxqZ2NgrbGzuLy0Y2div19gYrpeX2RlZWG2X2VpbXV8e3JtaHFqZGJma21zdXV2aJGEl7W5YWJgYmRkYmBiZ2WAYGNiZmprZWBhX162sLKxqqimsbGopamptbazXmVoZ7dhYWBiu7eegXB6kpePmJ6VnaKjpaS0YbSroqSwq6SgoaytrV1eXbdatLNcYWJkYmJfW2lpZWZoaWhmaW9uaGxwa2x0dXRybm5uam1wbW1taXJycG5ub291enepXYeRf4aAhnx1f4d5d21ybmiCjYVtZml1dXt5bm2BkZeVjIuQcoCRmp6gppuTjImPmJaYlZqjm5mhnqqioq6wsJ2VkImCgIKFhISChYuMhYaGi4WDgnp6fHx9gX96dX2Fgnp0dH+GhoWPkJGYlImEjIiNjIuIgHFudnJ0cWt0f4CBg4qHdXQLiZKKjJSVk398fHyAWlRPT01XUklKTU1UYlhUU05SVVRXVkpPTkpOT09HRFdTa4eUmJqVj5GSjomKhoCBfn5+h01QTkqJhYGJi4yLikpPUJ+dn5yYjY9QUZmZnpqXlpqenJicoVRWWFtbXF1bV1dcXmNoX1lZVl5iXlldYWWeTqpjZ2VhY2dqamtuamuAbGphZWpmaWNlamxueXNzamZqY2NiXmRtZ2Rqam51aWJcaW1mXF9eXmVla3FrbHFtbG1qbHVucnRybmhoZGBbjmV2UldTU1VcXmBWVlteV1daXWNpZ1qPc1uFRUpKSUtKS0xMR0F6eHBra3U/Pz48Ozs9PT08PT8+Pj4/Pj09OjpxOTo5OTs5NjY2bG1vOG9uOTs9PDw6cTk5ODk4NjQ0NTo9dGZeXlxWUVlmNTQ3NjY3NjY1aWFiZzU3ODk6NzY1a2hpaGVsb2tfQlxMVlZSUlJTVlVSUFFUVVNVUU5OUE9TU1RQTlJTVFVYWVlXUlJUVVaEVIBTVVFSVlZVUlNUWFpbWFNRU1VRT1FXVVZWUVNUVVZWVlVWV1hYV1hbW1lYWVhYWFVVVFNTUU1NUFBVVlNTVlpaWlZTUU9RU1hXVFZTUlNWWllZV1RUWFdUVVlRT1FQUExQUU5QWlhQR4V0bnF1cnV7RUhKS0lKS0tLTUpJSUNGSIBIRIaHRUVHSElLSn9CRUOAgUlIRUF/fXdCSkdGRkhHQD0/QURCQkZGQ0ZFQ0JCQT48QEBCRkJCPz10cX97ej8/QEJER0J1PEF4bnY8PT53U2RNZV9fYmZmZmVkZWNhX11ZXl0vMTIzNTQxWlxbWVhPM0dwlIWHkUxMkY6UVFFOUgNUVFOEVIBXWF9dl5RLTlFRUFBVUE9WVVZTVVJVjm16TlZUUFdTXFhQUFBPVl1SU1xbWFxYUlRZWVdUVFlXW1pYVlRRToqOjIuNhk9UT5ZLTVCWTE5SVFJQlU9SVVlgZmRcV1RcVU9NUFVUW15cXFBtZnWNjUtMSktNTUtIS09MRklJTE5PToBLTE1NlJOTlIuEgY2UkIqLi5KVk01UV1eXUFBQUp2ah2pWX3BxbHB1bnJ3eXt0hUmDgH+BioeDfXmEhodJSEiQR42OSk9QUk9OTERSUUxOUFJQUVJVU05RVFJUV1hYVlNUVFBRVFNTU09XWFlWVldWWVxZdzxhZlZbWFBLVVpOToBGTkdCVFtZSkRGUU1QUUpKWWVqZl5bYU5YYGdtcHhtYltcYGZlY1phcWtobm14bmtzdHFkYFxXVlNVV1dWVlleYV1eX2ZhXltUVllaW11dWFNbZGBYU1RfY2NkbW5wcmxlZGtmamZiXllPSU5KSkhDTFJSVFdfXk5MW2deXmZmZgRYVVZXtH6Ef4h+g3+HfoJ/jH6Zf4N+y3+DfpR/BH5+f3+LgIZ/noAGf39/gH9/hoABf4uAiX+JgIR/iICLf/+AlICIf5KAgn+HgAZ/gICAf3+EgIN/oYCFf4eADH+AgH9/f4CAgH9/fpJ/h4CHf4Z+BX9/fn5+j3+CfpB/A359fqV/hn4If39/fn9/f36GfwF+ln+FfpZ/kH6EfwF+hH+SfgF/jH4Hf39/fn9+frB//36SfgICBACAr56sta6psK+usamzvqicmZmqqb7Lxa+tqpiYm6/I7oWKkIPw4dzd1tDW293j6uDc2Nzp94D+7ufn7/yBgYKJiYKBgYKDhIiD+/yEiImAgP2GioOHhYT7+4KGjI2MkI+NjI2OjImLk5KPiYyTko+Li42gsIWLkI2MjZOVkpWbm5iAlZ6gm52bmZOUlZORlZijopeVopmUnJiNlZ+Yk5iekZSXj4iPm5KJh4uNkZONkpKQj5KWk5+cm5qemJKZnI6NjIjgpNCIjo+Slp2blpmZl5OOjY+NhZGdlfnGj+b9+/Px7enq9oGFhIKDgoODhISB/P/59fuAgoOEhYSEhYWEhIJOgYGDg4KBgYKB/fz6+Pr7/4CBgYD//ICAgIKEhISGiImIiYmJh4WFg/HZyMXAv8bL4u3x+/r4+vv/gf2AgPz19/j49ffw2pSR/o6Sk5OShJA/joyNkJKPkI+Nj5CRjoyNkJOSjo6Qk5GNj46OioeJiIWFhImKiYyPjI6OiYyTlpSRk5GRkY6LiIaKj4+OjouKho8XkI+Rko6MjoyOjIeJiImLjZCPkI6NjY2Fi4CPjoyKh4qNjYyOj46MjIqKjY+SkpKUk46HhY6Oi42TjoaC5tHM0NHQ0eP2g4yHhIeHhYaEhoqKiYaCg4WDhIOCg4KCgYOCg4mHg4eFg4H/+f39/4CAgYGA//6CgYCEhYOChIOChIOFiYKAhoWDhIaH/u74//+C/4SEgvz8/ffz+4CDh4aAgYSFg/2AgIH+++213qHs+vz49/v48fT6+/79/4D9+vHu9vj+hIaGg4H37LjXl8z3+/yAh/315+v+hISB/IL//YOGh4aFg4aJioyEgYCGhYWGiIqMkY2Li42Qj5Ckivf+h4eIjJGTkJSPh4eLjZGbm5OQjo+Qlo+Ei46Pi4CLhYj49vfy94qMkJCNg4OIj5WLjIuMjZCVk5qcm52bl5WD3dL5iIuMk5SWl5SVjY2VjZGV/sa2ueOC/P6IiIKFiYf/ho6LiYL294SFhv7v8fTy6unr3+Le5/zy9f6Bh4qLhIKA6LmipcPTzs3KzdLX29bY19jW1dPf6eDh5vH6giuCg4KEhIKCgvr3+vb3+PuBh4T6+IOJhYGFiImDhouJiYyNj5KVk5GWmJaShJOAlJOWmJeYnZual5SOhJiiy7+5v7zFvcXNv7Svubu1rbCzr6ukj42YopmYrMjKzMnHytrm5d3e3uXm497f5NzV29zNyc3Jx87Z3eLf1MfJ0MW4s7e4u7m4vr69tLO/vLGzrKiusaejqKunqa6xp5usvLm3wLayvcHS2tPGys/TysUewsfGwsO9wr/K2dXGwcbGw7nFzdjOwrats7KkmKq3gHVncXdyb3VycXNtc3trZGRkcG55gX1wcG1iZWd6kbNkZ2xgr6WhoZ2ZnqSlqrKpo56irrpiv66pqa22XF1cYGFcW11gY2NlYby9YmRnYWPFamxlaWZlwMFlZ2pqam5ta2lpaWhoa3Ryb2ltcnFtaGtxeINkanBubm5xc3JzeXl4anV7e3h7enl0dHZ0cnV3gYF2c4F3dHt2bnR9dnN3fnF0dnBpbnpwaWlrbHBxa29wb25xdHF8eXh5fHZweHpsaWlmpneYZWprbW90cW5xcW9saGZpaGFpcW26kGKisa6npqKenKVXWVlYWVmEWmBYqqunpqtWWFhZWlpaW1pZWVdVVldXV1VVVlWmpaSkpaerVlZVVamnVVVVV1hZWVpbXFtcXFxbW1tZpJKIhoGBg4ianJqgnpyenqJSoFFRoJueoKCeoZ2QYmOzZmpsbGmEZ4BmZGRmZ2ZmZWNlZWVjY2VnaWhnZ2hqamZoaGdjX2FgX15eZmVlZmhnaGhlZ21vbWttampraGVkYmVqaGdnZWZqamlpaWpsa2xsaGVoZmhnYGNjZGVnamlpZmdmZWNiZGVkaGZlZGFiZWZkZmZlYmNhY2ZpbGtrbGplX15mZmRmaoBmX12jlZGUlpaTnaxcZF5cX15dXl1eYmVjXlhbXlxdW1pcWVlXWllbYF1aXl5dW7Str6+xWVlaWlisp1dXVlxeW1laWVdYV1ldWFZbWlhaXF2sm6SsrFaqXFtZqKamoZ+nV1tcVlhaWlenVVVYrKuke5NomJ6cm5yenJqdoaChoXShT5mYlZWbnKBUVldVVaObeItjkru8u15kurKkprljY1+6X7u7YGJkY2JgY2hoaWJeXWJgYGFiZGVpZ2Znamtqa3JetrtlZGdobHBsb2xjY2dpbXV3cGtqamtxa2BlZ2lmZ2RltrS0rrVoam1tamBfY2lwZ4RogG1xb3V3d3h2cW9gn5S2Y2dob3Bxc3FwaGhxaW1xvpKHiahftrlmZmFjZ2S7YmppaGG0tmRlZb6urbGyq6qsoqSiqbuztsBgY2ZoYF5dp4dzc4qZlJSRkZabn5eZmZmVk5OdpZ+eoKmwXV1fX2FfXl1etK+xsbCwtF5iX7KzYGRhgGBkZ2dhYGVkZGdmaGpsamhqbWtoamtrbG5vcXBtb3Jvb21raGBia4uDgIOAhn+Gi394eICAeHN2d3h5c11cZ29mZXaKi4uKiImRmZ2Zl5aZnJyXl5qVk5iZjYmKh4eLkZWZl4+GiI+JgHt/foCAgIODgnt7g4N+fnl2eHlyb3R2NnJzdnpxZ3R/enqCeniChY6SkYmMj5OJhIKIh4SGgIB9hpOPhoOGhYR9iIuRiYJ5dHh3bGNxe4BSSFNXU09UUlFTTlVdTUhJSVNTXWJcUFBORUlMYXqZVVRXTId/fX16enx/gYaNhYB8fomVUJ2Jfn6Hj0lJRklMSUpMUFJTVFCYm1FTVU9QoVhaU1VSVJucU1ZaW1tfXltaWlpYV1ljYmFbYGlpZF5gZ2RzXGFnYV1gZWZjZGtsaWdmcHFrbWxsaGloZWNnanZ2aWZ0amdwa2Rrc2xnbHFlaGdhXWJuZF1cX2FkZV5hYmJfZ2lkcW5vbnFrZW1wYWFfXJFef1hcXV9fY11bXl5cWlZWWVpUYGlkqHJHd4KCf312bWtxPD4+hD0KPj4+O3J0cW5xOok8Bj07ODg5OoQ5gDhra2xsbW90Ojo4OHBvODg3Nzg6Ojs7PT08PDs6Ojs8bGJbWVVRU1djYmJpZ2RlZWs3aDU1aGJlaGtqbW1nRkyLT1NVVVNQUFBPTUxOUVJQUVBNTk5PTU1QUlRUUlNVWFVRU1FRT0xNTEpISFBRT1NXVFZWU1VaXVtWWVdXV1NTgFJPU1hVU1VSU1hZWFdZWVtZWFhVU1ZUVlNNUFJTVVdaWFhVVVRTUE9QUVBWVVRTTk9RUlFUVFRSU1BSVVdaWVlbWFBJSVNTUVNYU0xKfnZ2enp4d4GNS1JLSEpKR0lGSE5QTklFR0pISUdGSEZGRUdISk1JRUlJSEaMhIWFhkJCVERFQ395QD8+RUlFQkJCQUFBREhBP0RCP0FERXxtdHp4PXhCQ0F5eXpvbXdARUQ+QENDPnQ8Ozxzc3NZaEVhYmNhYWRgXV9hYWJiYC5aW1hXXF1fMoQzgGJbSFhPfqKholFWnYx/hZpUU02XTpubT1BRUVBNUVZWVU5MS09OT09PUE9UUlBRVVhaXVVFkpJRUVRWWV1YW1hPT1JVWGBhWlZVVlhfWE5VVlhWVlJVlpOTio5VVVdYVVBQU1dcVVZWWFdXW1tjY2FiYV9cTn92k1BSU1dYWVpWgFVOTlZOUVSNa2FifkiKi0xNSUtOTpNLUE9PTIyKTE5QlYmNkI+FiY+Kj4qQopaaoU9RVFJMTE2JbF9ecn55eHJwdHp8c3Nva2hqanF4c3N2foRGRklJTEtJSEiMiYuNj46RTFFNj5FMT0tJTVBQTU5RT09RT1FSVE9NT1JRTU9PgFFTVVVXV1ZYXltZVVJOSUBFXFVUV1VeWVtdVU5MUlFOTVBTVFROPDtCRD08Sl9eXl5dXmNnaWVlZmtsbGdobWZhZGZfX19aWFpfYmRiXFhbYmFaVFVUV1ZWWlxcWFpgYFtcWVZXVlFPUVNQUlNXUUhVX1xdZVxZZWdwc29oa21uIGZhXWFgXV1XV1VfZmBWV19aVlBcYmVbVk9MT1FJQUxVn36Ef5F+AX+Gfo1/gn6FfwF+hn+Cfpl/gn7Of4N+lH+Cfop/i4CFf5WAh3+EgIJ/koCRfwSAf4CAjH//gI2AiX+jgIV/hYCCf5aAhX8FgH+AgICGf4iABH+AgICEfwF+j38BgId/hYCDf4Z+gn+Ffgd/f39+f35+nH+Efp9/hX6af4N+j3+FfgN/fn6GfwF+hX8Ffn5/f3+Qfod/m36Jf4d+BX9/f35+qH//fpJ+AgIEAICnp6iln5OZoaqjl5OcoY6H+5ejqKytm46XvOmGi4yIioOFiIKHiImKg4GB7Obb1MK7v9jt8fn9/fWCiYL8/oeGhIL58vOEh4T+//yCio76goOCgf+AgP/9gIGIjY6SkJGOjY+KhYSIk5SLjZaalJCP44Hrj46MkZGNjo6Rk5eZm4CZmpaXlZiWl5WQjpyQkpWWmqCYl5aVpaignJmYnqSXhJGVk5ONmJ+ZkJSNlJWTkZOQlJWZm5qbm5mclpKVkZGOiYuL16XahIqNjZaXl5mblI2OiIHz7O3r4t/jw9i38Pn8+ff6/oCBgoODgoGCgoGChISA/4CA/fr4+vuAg4SFgoCBgICAgYD9/vz59/j6+vv+gP+AgIGA/ff09Pb7/YCA+fmAhYaEg4aJiIiGgoKAgfzr18nCwL+6ub/P3+vx9fn0+P/94JqI5Y+TkY+OioqMjoqKjoyMjoqOkI+RlJSTj4+PjpCLiIyMi4iLiYSHiIyRkZCPko6LjI2PjI6OjY6PlB6Ujo2Ni4yOjo6Tko2KkJGPjo6Rj42MjYuKioyPkI6Ei02NjIuKiIyPj4mLjoiMi4mIhomLi4qJioyOjo6PkI+MjZGPkZCQjouIgvno2c/RzsvK0t/xhYmHg4SDg4OCgYeIh4KEhomHhYSFiYaGhYSDJPuBg4OEh4iEgYGEhoKAhIOB/PuEgoOIhoKChIL0+v+BgIL8gYWEhIKAgYP/8/f/hoWFiIL14ujn84GDgYGGhP/y5e32//Ds6uKt2Zzr+fr8+vT28vLu8/2BgoKCgPr99/T28vT8gYL/6KKwpN+CgPf0/PmB9/Lu6O/5goKB/P739/D7/oP+gYeEhIKLi4SB+4GGhIKHiIqGiYqKk/DuxIKF/4KIioyPlo2AiY2RjomPjIqHjYuJh4aNhv79gvr3+fiAipeRj4eLiY2Ni5SG6v6IjpGPlpyam5OS/P3+9Orv/oaSmJaSjo6SkpOMjZWPjZCbmZmR4MPE1PWC/PmChYWHjo6Ig4eKiIWB/oKE/4CEhP7ygob8gYWFgf759OPApJyhxtvh5efd0dOA4OXe29Xg6uLU0Mzb5erz9vz6/IH/+oKD//j4hPj8/YKD//mAhYOHiYKDgfqBg4OEhYaFioiJjISGio+XlJOQi4qNjY2PkpCTko6Pjo+OuIO4vMDGvsO+vsfMwqWjraWSpayjnZCTmZ+YlaHAv7O/xr/FuL7Cyr2ppbi6ucPDwcRg0dHHwLy9vbm4raeuraOgpqemnqOkp6qmoZ6boaabmZqboKeop62qraeek4mdmZ2jrKWyv7zByMXBycjGwrSjnZ+grrq0x8C2s76+trCsrKmoqqSprLCqq6iopamkm6KkgGtrbW1oXmVoa2RcWmJmVVCTXmltb29jWmSDqmRmZmRlXmFlXmRlZWZgYGKvqKKekIuQpba4vcHCt19lYLm5ZWNhYbexsmJkY7q8u2Jpar1kZGNiw2JhwMBhYmlsbHBvcG1sbmllY2ZzdWxtdHZzcHCvXbRubGpxb2ptb3NzdXZ3anR2c3Rzd3V2dG1seG1xdXZ4fXl5dnWBhn99eXd6gXdmcXJxcmx1enZwdG1zcnBtcW9ycnZ3d3h4dXh0cnZwb2xnamujdZ9gZmhpcnNzc3RtaGhkYLKtrbCppaiKlYGora+tq6yvWFlaW1qEWSRXWFlZV6xWVqqopqioVldZWldWVlVVVlanpqSioKGio6OkUqWEVICkoKCho6iqVlaoqVZZWlhXWVtaW1pYWFdXrKGUiIOBgHx5fIeRl5ufop2gpqaVZ1ynaWhnZmVhYWRnYmJmZWVnY2ZnZmdqampmZmZlZ2RhZWRkY2VkYGNjZmpqaWdqaGZnaGpmaGloaGhtbGdmZmVmZ2hobGxnZGpraGhoa2lnZkNmZWVlZ2pqZ2RjZGZoZ2VjYmZoaWJkZ2FkY2JhYGJkZGRjY2NkZGVmZ2ZiZGhnaWhnZWJhXbGkm5aalo+Mk5+tYGNhhFw/XVxbYWFgW11fYl1aWlxgXVxcW1xcXK5aW1tcYGFbWFlcXVtZXV1ar6tbWlpgXlpbW1mkqbFZV1qsWFpZWVpahFmAWFmqn6WtXFpaXlijlZiYo1daWFdcW62hlZ2kqp2dnJd2jWKVnp6fnpqcmJeWnKFRUVJQT5qcmZeYlJacUFOjmWx1cKZhX7i3wb5jvLixqbC5Y2Nhubu5t6m1uV+3X2ViY2FqaWFdtV9jYV5gYGJfYGBjbrGikGJkvGFnaWpscWqAZWdramVqaWdlamdmZWRrZby6YLazs7ZfZW9rbGZoZWhoZnBkqrtlZ2pqcXV0d3Bwu7u7raatvWRuc3Jvampvbm5naXBramt2dnVwpo2PmrJftrReYWJjaGZjYWZqaGReuF5hvGBjY7uuXmG1XWJhXrm2saWJdG5zkaCmq6+mnKCArK+nn5qhqaKVj4uZo6mvsbWtrVu0sV1fuLS0YLS2tF9guLRdYV9hYl1gX7RgYmFiY2NhZGFjZ15hZmpvbW1rZmRmaWloamlqaWZnZ2ppf1Z+gYOIgYWDg4mLgWtrdG9hcXlybGBhZGpmYml/f3R9hIGGenuAh35wbXx8e4WGg4Zgj42FfXt7enl4cHB3dmtpcXNxaW1wdHZxbmpma3FnY2VnbXJycnh1dnJrYVhmY2hxd253gH+Ei4aEjYyKhntsZ2hodn52hYF9fIKDfXh0dnNyc21wc3ZwcW1uam1sZmxrT1FOS0hGQUdLUU5GRUlNPjxuRk1OUFJKQ0xrjFFTUU5QSk1RTE9OT1JMSU2Lh4N/cWxwhZWWm5yYkVBYUJOSUlFPT5WQlFVYVqCem1FYWpmEU4CoU1Kho1NUW15eYmFhXVxfWlVUWGNlXmJrb2tnaaFKn2RhX2RiXmFiZmVoam1pa2dmZGlpbm9kYW9iZWlqa25naGlqdXl1cGZlbHZqWGRmZ2lgaXBsZGhgZ2diYGViZWdrbW1ub2puaGZrZWVhXWFjjl6BT1RVVmBiY2JhW1hZVYBQl5SZmZKPkHNuWnV2dnR0dXc8PD0+Pj08PTw7PD09O3Q6OnVzcXNyOzs8PTs5OTk4OThubm1raWhpamxtN2w2Njc3bGpqbG50dTk5b3A4ODg3NTk6Ojs7Ojk4OnNrY1tZVlZVUVFZXmFnbnFqb3V1Z0dAeU5RUVBOS0xNUEtMUYBRT1FNUFRTVFZVVVBQUVBTTktPT09OUE5KTU5RVVZVU1ZTUFJUVVFUVVJSUVVWUlFQTlBSVVZdW1NQVVZUVFNZVlRTU1JRUlNXWFVRUFFUV1VTUU9TVVRNTlJNUVBPTk1QUlFQTk5RUlJTU1RUUFNZV1lYVlJOTEiQiH52fXp2c4B6gYpPUU5ISEhJSUhJTEtLR0lKTEhGRkdLSUlJR0hJSYlISUhLTU1GQUFGR0VESEdFgoBHRkZMSURDREFzeIBBPkF+QEJAQEBBQUJBQUBCfXF4gURCQkQ+cWRoa3RAQkBAREJ7bmZvc3dtbm5pUmVDX2FhYmFeXVxeXF5fLzAxL4AuW1xaWFtYWl0wMWBbQlJejlRRnJmmoFSclpWUmJ9TU1Gbn5qZjJeXTplPVlNTT1dXUE6XTU9OS0xLTEpOT1FZi2ptTlGVUFZUV1leWFNVV1dTV1VRUFRUUU9QV1Camk+Tjo2NTFReWVlTVlRYWVZdUYSVUVRVVFpfX2FaWpWWl4CLgoeVTldcWlZTVFZVVlBQVlRTVF5cXFd/bWt0hkiJhUhMTE5RUE5LT1JQTEmQS02VTE1OmY9OUpVMT01IlZaQgWxeWV18iYyMjoqChpCSi4R8goiDd3BpcnuAhoaGgIFDhYJHR4eEiEyLiopJSYyISU5LTlBKTUyQSklJS0tNToBST1BUS0xPVFpXVlNPTlFTU1BTU1ZTT09QUVBZOlxeXmNeYV5bXF1aS0pRTUJOVlNPQT9CRkA9RFhcUVZaVFdNU1ZdVUtJU1RUW1tbXV9fXFdWVlZUU01MUlVQTlFRUktOT1FRTkxLSU9QSERFSE1SVFRXVFJNSUM8SkdLUVVOVi1gX2VraGZpZmRhV0pFRkVQWFFdWldXW1xWUU9RT1JUTE9RU05PTE5NUE5IT1CQfgF9in6Qf45+BX9/f35+hH8Nfn5+f39/fn5+f39/foR/BX5/f35+mH+DftB/g36Of4l+iH+OgAN/gICFf4uAin8CgH+EgId/BICAf3+OgJh//4CFgIt/nYABf5CAgn+JgAd/f3+AgIB/jICEf4WAhX+GgIt/AX6Nf4WAiH8KgIB/f39+fn5/f4R+AX+GfoN/h34Cf36JfwF+jH8Gfn1+f39+l38Dfn5/hH6Nf4J+in+HfpR/hX4Df35+jX8Mfn9/fn9/f35+f39+hH+jfhB/fn5/f35+fn9+fn5/f35+iH8BfqJ//36TfgICBACAqqWemJ6flYyKi5GOj5aRiJabnoqUrNODkYyGgIKDhoWC/YGEgfyBhYeJi4qKi42Ig/Xe0NDMzMvJv7zJ2eLm6veDiIT79Pj+gP6IiYWFgoSDhIeGhoyK+/j6iouHiZKPhYeJhYOEg4ONj5CPmpeTj5m2j4+Eg4mTlZaYlpuXlJmAkZaQjo6MkpeWlpWWlZSVkJOcnqGfmZqfmpWVjJCWnZSTmZubj5CNiYSIkoiHi5WZkI6UnZ+dnJKRkpeVlZeZj46PkI+H0afK+Pfh3ezz+O/u8PaDiYiIh4SFhoT3zc2Y1Ozw8fH09vr8/Pn09PX6/Pj5+f+A9vaBgP34+fz8/v8wgP/9/YCA/fv6+/n5+vv+//77+vn38/Pv6O78gIGCgYKBgoODgP2DhYSDgoKEgoOEhIaAh4iHg/3w3tDIwcPFwb+iyaj3ho6QkZOTkpCRkpCNjoyPl5KRkpGRkY+NjI6MjIyNjY+PjIuLj5GPjo2NioiJkJOPjYuLjIyIiY2QkZKRjoyMj5COj5CNiomJhYWJiomNkpKRkZGSkZKTkY+PjYyNi4qLjpKPjYmJiomJiYiCgoSAhYqJi4uJhoSCg4aH/e/i1s7MzcrIy9Th7/v9+vr9g4OCg4GDhYSChIGAgoODg4GEgYOFgoKEgYCAhIaHhICEgYCFhoSD//yCgvz9gIKHhICB/v+FhIGDg4WEhYSE/4GEg4GFgYCBgoD2+P2ChIP9+4GBgIGDg4KAgYOChoeD+vmA/YD88uzw/vzy2J3knOL3+vn39PLs6+3t7fT29/j29vn///z69vHz8vS/65zC+PPq+Pz7gIL8/YL+//jz9ez6gIGA9oKC9fr/gICB//v8h5CFgYKA/vz+/ICEhoeLkJGMnYKAkI6Oj42GgoSCgYH9goiUjY+Rj4uMiYeHhIOBhYaAhIKDgoeGgoyVj4WE+/zyhY2RlpuVmpGQloqFgvmBhYKHjpSSkY6UmJOUjZeWjYqQlYyNjpKLiIuNj4ngxMDlg4iIh4uJi4uPk5mTkpSRiYH28ur06+3z/YWMjoXkuqSotsLO0MfGztTS3NXNztPQ1tfW3drY2t3n6OPk6/Hp7feA9vzu7P2DgfyChv/5+/+DgoP8g4GCgP38gYeHhIiMi4qFgYKBgoaFhYqIh4eKioyNjIuKi4uJiImL1vWy09XErbSvvNDCpK61uLayn5OTjY2ZpLCmpau4wNHd1NDLx8TCvbrBvra2n5qenqOsrKatrKi1vrS1q6uvraeqqKiip7FMr6mnrbCys6yqqK+0q6yyu7utqrG0sKuwsLW0sKCWqb/Hv7y6v8DN0sW3ucLGx9DRwMC1sqSjo56lraq+vbq+vL67urq2sKGgoamnqIBwbGdjZ2ZcVVVYXltdZV9YYmNmWGJ1kFxpZGFdX2FiYVy0XmFetV9jZWZnZmZnamZhtqCUlpGSkI+HiJKfp6uuumFlYbu0ub5gv2lpZmVjZGNkZGVlamm+vr5pa2hqcm9lZ2lmY2VkZW9wb255dnRzc4dwb2VlaHBydXZzdnJydYBtc29tbWxwc3N0c3NzcnRvcnt9gX97e395dXRsb3Z9dXV4eXlvcG5rZmpzamltdHZubHB4enh4cHBxdXV1eHtvbnJ0c2qed5O3tqiosLO5sbG0uWNnZ2dmY2RlZLiXjWONoKWnpqenqaqqp6Ojo6eppqmpr1inpldXq6epq6usrRxWq6mpVlaqqKippaOkpKWnpqOko6Cen56ZnqlXhFiEVwlVqFhaWVlYWVqEWSdaWlxbW1pYq6OYkIuHhoV/f26EcrRiaGlpamlnZ2lqaGRlZGZtaGeEaIBmZGNlZGRlZWZnZmVlZGdqZ2ZlZmJhYmhsaWdmZWdlYmNlaGlqaWdlZGhqaGpraGZkY15eYmRkZWlra2tsbGtsbGloaGdnZ2VkZGhraGViYmRiYmJhXF1fYGRjZWZiX11dXmFiurCnnJWWlJCMj5WirbS1sLCyW1xcXl1eX11cXUhaWlxcXV1bXllbXFdXWllZWVxcXVtZXVpaXmBdXK6pWViqrVdaX11YWa2uW1pXWlpbW11cXLFaXFtaXVlXWFlYqaqtWVpZqqmEWIBZWFdVVVZXW11aqKaqVaegm5+uraeXbZlnkp6gn5uYlpOTlZWWm5ycnJmZm5ydnJuZlpeVmHqZZ4m5tKy7vr1fXre+ZcfEuLK1rbpfX12yYF+wub5fXly5ubdhaF9dYWG5tLWvWF1fX2JoamltWWFta2tramRiYV5eX7ljZ25oaoBsamZpZ2VlY2BeY2VkYmJhZWVgaXJtYmO3uLBhaG10dm9za2txZWFgtF1iX2RrcW1raG5zbm9qc3FqZmxwaWprb2hnaGttaaSMhqliZmZkZmJiYmZqb2prbWtmYLe0rLOrrbC3YWdoYaeGdXeBjJeako6VmZmlnZaXm5ecm5qhn4CenZ+qqKGfpaeeprKvtKeot15asl5ht7W2vGFgYLlhX19dubtfY2JgZGZlY19cXV1fYWBfZWNiYmRlZmZlZGRkY2JiZGqgpn6Sk4Z3fHeBkIVsdHl9e3dqYWFZWGRud25tb3Z7iJWPioSDhIeCfoOBfHxoY2dobHJ0cnl3b3d8dlh6c3V5d3JzcnNtcnl5dHN5enl6dnNwdHt2d3uBhHl2enx6dnt8fnt1aWJxgoqDgX1/goyRh32AiIqHkJKDgnt7cm9saHB3c4GAgISCgX18enZ1a2ttc29vgFNQTEhLSkM+Pz9DQkNIRUBHSU1ASF59UFlUUEtMTU5NSY1JS0qRTVFTVVZUU1RXVE+RgnR0cHFzc2xtdX6GiYyXUVZRl5GWm0+dWFdTVVNUVFVXV1hdXKGipF5fXF5oZFhbXVhWV1VVX2JiYm5saWlecGVkWVlcY2VoaWZpZGNqgGNoYmBgX2VqampoaWhnZ2JpdHN1cW1tcm1qaGBkaW9paWtsa15iYVxWWmVcXGBobGBdZW9xbm5lZWVpZ2ltcGZkZ2hpYIVhfJ6ejo+VlpyUlJmfV1xcXFpWV1hWoH9sR19sb25ub21ub3Fua2xucnNwcnJ2PG9vPDx2cnN0c3JzYDlycXA5OXFubG5tbW1sb29taWpra2pramVpcTk6Ozo6OTk6OzpwOzs7PDs7PTw7PD09PT49Pj47dW9nYV1bXFtYW1BeXZBOUlNTVVRQTlFSUU5PTlBXUVBQT1BRTk1MT4ROgE9RT05OT1NVUE5NTUtKTVRXVFRSUVNST1BSVFVXVFFQUFNUVFZWU1BPTUdJT1JSU1ZYWFhZWFZXV1RSUlJTUk9OUFZZVFBNTU5NTU9PSktMTFFQUlJOSkdHSk9Sm5OIfnl6fHNrcHaAiI2NiYmLSUlKTEpLTEtKSkZGSEhJSUdKQEZHSENDRkRERUtMTkpFSUZHTE5JR4aCQ0J9gUJGS0hERYaHSEU/QUJFRUVERYNDRURDR0NCQkFAe3d4QENCeneEQCVCQT49PkBBREVBd3Z6PXRta3J+eW9lSWxJYGNkZGBdXVpYWVlahF+AXFtdYGBdW1pZXFteTGlSeKGakqCjoVFQmZ5VpqSalZuToVNRTpJPTo6WnE5MTJibmFFYTk1QT5KMjYlGSktNUFRWVVFBT1pYWFhXUlFQTk1Olk5TXVdZW1pWWFVUUU9OTFFTUlBPTVBSUVddWVFQlJOLTlRYXWFcYVhWXFFNTIyASE1KT1VaWFdUWFxYWlVcW1JQVVpTVFNYU1FSU1RQfnBofElOTEtNSEdITVFWUVJVU1BMj42Gj4yNjZBQWVhQjW1dYm13gYR9en+BfIeCeXh8dnt6eYB+fHt9h4V7en5+c3aBg4h7e4tJR4tITpaOjJBKSEmQTUpKSI6OSE5OTVEEVVRTT4RMgE5MS1BNTk5QUVJSUE1OUE9MTE1PcGxbaGdgVFdTW2VbSVVXWFdVTkRDQEFLU1pRT05VXGZtaGRfXV1gWlhfXVVURkNHSE1TU09SUExTWVZZUlRXVlBSVFVPVFpYVFNZWlteWlhVWl1VVFdcX1dVWFlXUlhXV1NQSUJPX2RdWlddJ19namFYXGRlYmNkWlpTVE1NTUlPVFRiX1pfXVtYV1hXWVBPUFZTUpd+in8Ffn9/f36Lf5B+g3+EfgJ/fo1/g36Wf4J+03+Ofol/g36VfwWAf3+AgId/BoB/f3+AgJV/ioABf5KAi38Dfn9/+oCSf6eABn9/gIB/f4aAgn+KgAF/ioAIf39/gICAf3+OgAR/f3+AiX8Bfp5/iX4Ff39+fn+Hfg9/f39+f39+fn5/f39+fn6Gf4R+iH+Cfox/AX6df4N+jX8Bfp5/hH6Rf4h+hH+pfgV/f35/f4R+BH9/f36Ef4J+oX8Cfn3/fpJ+AgIEAICZm5ycnpqXkI+NhoWSmI+Sn8XW64eQj4uMgvn6hoWNioWBhob9+4GHiIaGh4mEhYGA/fz5+PmB//jz7eTZzMXBwcLBwsTAxtrv94GEh4qG+/WDiPrvhYaA8fmIhIKHh4uNiISKiYSFhImPj4yOkJPfkP2PhPyChYeMlZSPjpOXnFealpeamZeZmpOHh4+TnZicoZiVmJyboZmSlp2Xk42NlJOUk5WShYqTj5GSioiEg4qYnJWE/4+ilZSRjYOG9+zY0ce/r6mhn5qPpfeKhoeFhIqKi4KBhIaEhRCBg4WA58XcnMrb4ufo6uzxhPRK9vj6/fz8/fz8/fv/gYH//4GA//3+gP/89/f8+/78/v7/gID+/P77+Pr8/4D+//z6+vj4/v7//fn7/f38/v/69Pf794CDhoaIiIeFiICGhYSBgfvq3tLFwb3FyczOz9PZ4On5hIeIjI2LiIiLjYyMiomKiouLioaEhoiNj42MjZGTk5GRkI6Li42Ki42Ni4qIhIiNioyLjJGRj4+NioeKj46Oi4qJjJCQj46Oj5CQkZCQkZGOi4qJiomIiYqHg4OFgPDl3M7Q1c7NyMfExlvPz9nu/vz8gISIiYqE/YGEg4OCgoGEgoSEgYOAgf+Cgv/49//8goP9/oD19/77/ICAhIeGhIKBgoL/gYKBg4GCiYiEg4OEg///h4eDgYOEg4SFgoKDgP3/gYGBhICAgf+A/fyBhIeJhoGA/ff+gYSDgoKB+oCGhIH07/aA4pLnquDu8PLx9PX09fX1+Pb08/X5+fj8/fr9/f7788iIrq3K5Oz6+Pf68ICEgfz79/eA+vmA+f79h/L3/4aBhIP8goCCg4CDgf759v2EiIWGhIKBhISGhoni77KBiIyGhIeAiYmGhICEgoSEhIWAhYeKi4L+gIWB/fSFhYWHi4qMhYL/9IGOjYuWh5CF9oGA+PKFioqMjoP/jYyNjo+QkJWTj5KQjYmNkpKMj4yPjILy8ICVmZKRkYne1snagYuQjYqSl42Gh42RiIWHh4KGgPPdv7y8yev15uPZ09ve4dLY2MyAy8PN3M/F2Nfc4dXK1d3U09za29rb5+vn7u7s9/rt7+/57e/o7vz89/j8+vyBgYOBg4D9/YeJiIWC94OMhoODgoCCg4eHg4CDhoSEgv2Eg+GDn8O+tKqnsr27tLSzqqKjopydqaGirLG5vMfT39zMvMK+urW1t6ejmJajqq6lp61jrqekqLC0raqoq6iprK2trqaorbOsrLCypp2ppKy0rq2op6muq6ipq62ut7yxrbXCxMO5vcbO0M7Y4NzR0tbTz8nHv7e7vbyysLGysKSbpKqdk5CTkpOUl52lo6CZnp2elpORgGNjZGRlY2BcXV1ZV2BkXl9qjJusY2poY2JcsrViYmllYFxhYrSwXWRlY2NkZ2NkYWC+u7i1tl65s6+sqqOXkY2MkJGRkYySpLm/Y2Voama+u2VowbhpaWO5vWlmZGlobGtlZWtsZ2dlanFxbm9wda5oyHBkwGNkZmt2dG5tc3V5gHd0dXh3dnh5cmlob3N9eHyDenV3enuAenV4fXdzb21zc3Ryc3Jna3JwcnJsaWZjaHd8dmbCb390cnFvZ2i+tqSdlIt9eHJxa2F4vWhlZ2ZlbW5rZGJkZmVmZWViZWZir42SaIqWl5eVlpecn6CgoKOkp6urrKyrq6yqq1dXra1YgFitqqpWqqikpamoq6qrqqpVVauoqaelqKutV62sqaipqKipqqurqausq6mpq6yrrK2oV1laWltbWltcXFxbW1pZWFisoZiOhoiEh4uMjJCVmp+osl5hYmVmZWJgY2ZlZWNiY2JiY2JfXmFiZmhmZGVoamppaWloZWVmZGRmZWVlGWRgY2hlZmZnbGtpaGZjYWRoZ2djY2JkaWmFZwFohGldaGVjYmJjY2JiZGJeX2FerqWflJWYlZOOjIiKkpKYqLW2tFxeYWJiXbBbXl1cWlpaXVxeXlxeW1yzXFyyra20sFpcsLFYqKqxsLJbWV1gYF5bWltZrVdYWFlWWWFfhFsUWa2rXl5bWVpbWlxdWltbWK2tWFiFWYBYsFmtqlldYGFeWFiuqK9aXV1aWFelVl1bV6GcpVqgY5xzlZqZmpmbm5mampmampiYmp6dnaCgnp+en52Yf1lxeZSpr7y7u761YWJeuru6vGG6umC0vLxmsbO6Y15gYLZeXF5fXV9euLOyu2NnZmVhX11eXl5dYaCcgGFmaGRiZAdnZmFgXmNhhGKAXmRna2piwmBiYLuzY2JhY2tqaWRgv7Zga2xrdGZvZrphYbq1ZGhnZ2phvmpnZ2hoampvbGptbGlmaW9wamtnamlhsrJgc3Vubm5ppZqOn19na2pnbXJqZWdqbWRfYWJdYl+2oIeJi5u1va+so5+mpqian5+TkI6YpJeOmpeepJeAjZeek5CamZqZnKaqpaqqqba2qquosKiuq6+6ure5vbu+YWBhX2FduLhjZWRgXaxeZWFeYGBeYWJlZGJfYWNiYV64YmCgVmuGgnpxbniDgHp6eHBrbW5qaXBsbnV4fHyFj5aRhX2Ggn57fH5wbGVma292b3J3d3Fucnh7d3Rxc3BYcXN0c3RxdHR3dHd8e21mcG1ze3d5d3R0dnRzdXRydHuAd3R6hIOCe3+Ij5CMkpaUi4yPjo2Li4R7foGBeXh5fHtwaW5wZl9fZGVkYWJobW5sZ2tmZWFfXoBMTE1NT01MR0ZEPj1GSkRETGp7j1RZWFRTSo6PTk9YVE5LTk+TkEtSU1JSUVFPUk9Om5uXlZdOlpOSjYuEd3Bra29zdnZwdomdolRVWV1Yn51WWqKXVlhSl55bWlpeXWBgWVdfX1hYVl1kY2FjZWuWUK5iVqVWWVteZ2VgX2Vpb3JtaWhsa2tvcGhfX2Znb2ludm1mbHFvdG5na3NqZmNjaGhpZmdlWWBoZGZoYVxYWF9tb2hZqmN1aWlnYlpapJ+Mhn11aWRfXVVLXKFcWFtaWl5eXVZUWFxaWllaVlhaVZZ0bElZXF5gYGFhZWdpaGhtb3CEc0Jyc3RzdTw8d3Y8O3Rwbzhvbmttc3J0cnN0czk4bWtubGxydXc8dnZ1cnFvbnBxcG5qbW1ubnBxcXBydHI6Ozw9Pj2FPAI9PIQ7gHJoY15ZXF1hZGZoa3R+goaNS0xMT1BOTEtOUFBQTUxMTE1OTUpHS0xPUVBOUFNVVVRVVFJQUFFPUVNRT05OS05RT1FSVFhXVVVUT05SVVNUUE1LT1VVU1JQUFJTU1NUVVVTUE9PTkxMTVBPTE1QTYyDfXN1e3VxbWxrbXN0eIWRgJGPSEtNTk9JhUdJSUhHRkZLSk1OSkpHSIxIR4mEhpGMSEiIikR+gYuJjEZESExLSEVFRUSDQ0RERkNFS0pGRUREQoB+R0ZCQkJDQUJEQkNFQICAQUFCQUJCQUKDQ399QUVISUVAP397hEJEREJBQHc/RkQ/cm13RXhHcFNkYmBggF9fYF5fX15eXF1dXl9fXmFiYWJhX1tZTDhQY3+Rk52dnqGaU1JPnJ6doFGam0+Wnp1XjpGXUEtOT5ROTE5PTE5NlpKRnFJUUVNQTk1NTEtITH9uaFFUVlJPVFZVUlJOUk9RUVBSTlNVV1lSnlBTUJuSUlFQUFVVV1JPm41MVFVUgF1QWFGRTk+VjlBUUVFWTpRTUVJSVVZWV1VUVlRST1JZWFNUUVRTS4mKSlpeWFhYU4J8cn5LUFJRT1ZcVU9PUlRNS09RTFBOkX1maXGGoKWYloyDiouOfoWFeXVudYJ1a3d4f4V4cXh9c212d3h3eYGFgIaBfYqNgoaEiX+EgoaQgJCMjZKSkktLTUtMSpOUUFJQTUyQTVJOTE1MS05OUFBNS01QUU9OlU9OgT1MXlpXUU9VXFlSUFJRTk5MSklQTlJZXmFeYmhua2NaYV5bW19hUlBISE1RVlBTVldTU1dZWlNRUFNRUlRXV1lUVVpeV1VWVk9LVVJXXFZVUlBQUk9OHVBOS01TVlFQVFtaWlVaY2hpZWdmZWVoa2loaWpjhFscVlVXW11VTlNTSkVFSEdISElOUVBST1NPTUlIR5R+hn+Cfoh/gn6Lf4V+AX+TfoV/C35+f39+fn9/f35+lX8Gfn5+f39+v38Bfoh/jn6Uf4N+mX8KgIB/f4CAf39/gIt/goCIfwGAl3+RgJF/3oCTf4aAAX+PgAN/gICFfwWAgH9/gIV/ioABf42Agn+NgIJ/iIAEf4B/f4eAg3+GgAF/hIAHf39/gH9/fp5/in6Df4R+C39+fn9+fn5/fn5+hH8Bfod/hH6MfwN+fX6XfwZ+f39/fn6Jf4J+iH8Ffn9/fn6GfwF+l3+Cfod/hH6Tf79+hn+CfoV/AX6SfwN+f3//fpV+AgIEAIChnp2YkZOOkpKOhYWevdr6ioyD9/6FhYGDhIeIiIuH+oGEiIaBgISIhoSEiIuGhIGA/fv4/YCDgPjz+4H98PH7lZKOj4mFh4Pz3NLR0s/PzMbJwcDH0Nfa4vyDgoSGhIOIhoKFhIOCgYKHj5GPlYSHt4iKhoSJh4aMjYeIhYuRlICSjYyTlpWNjJKYlZWUmqSak5qen5aZj46KlpGMjpONjY2MjIySkZGRjI+Qj4mI+dvJ2eHh1MO/sKeinqKosrzDycnCv8rIxby2qJCz4YOGiIeIiouNkYaFg4aF/vb6/4T8gOK94KDS6evq6+/19PT19fb19fj59/b2+Pj49vX19zX7gP//gP75+ff19PPz8u7u9vj39vr49vj2+Pv5+fLr8fb7/v/+/P39/f6AgoOCgoODhISDhIWFS4SFhYaGh4aFg4OFhoeIhoOCg4SEgv779vDe0cnGw8XGxcLEycfCztHT09Xb3uTn8fT8goWIi42Oj4+Qj4yKi4yNjIqJiISKjIyHiISMgI6Nj5CLiImIhoeGh4WFiIaDg/316uXi3trW083JwsnQzszOzcnGyc3i7PSBhIGBhISEhYWIiIeHiYmLiomIiIaGiImIiIiC/YCAgIOHgYCDgoD9+YCCgYSGgv//gID9+/yAgYGBg4aHhIGAgYGBgIGDhIGBgoKDhIaGhYOEhIGEgIWFhYKChYeHhIOB/4KDhIL++oCBhIL++vX7/f6DhoWDgfv+hIOBgYD/+/no5Ozq4rHu/8Xy9PL0+vj6+Pb2+PTz8/b39Ozm6e719vT39tmbxZnI6fX6+/P16/T1+fLy8e/w9vj5+f738Pz5goCDgvr9/YWIh4D3+vn7gYOKiYSBgID/+oSEhYeDiI+KgqL90/6JkYn8goGHio2JiY+MiI2NiIOBh4KB/P+Ah4aEh4OFhoH46viD/oWOiI+QkIeIh4CDhISIiYyJh4yNio2NjY+FhYyHjI6NjI2UlI6Ljo2B8oiTk5OJgIGKi46RlZeM7djP4PSJkJGUkpOUlYyI9tXBgL3Azuf5g4aBgvvx5d/s+u/v7efv8vbr8ezY0tTh5NLP3NbKxdDe39bMx8jQ0eX0gPHh9/j6/fPy+fj49/Xs9Pr38+3v/4D3+vv/hID0/4CAgoaE+vD4gIKFhYT7/ICDg4OC9p2Btr+8t6ahlpyqtbiyrq2ytK6zvL63tbnAv73Jd7yus7O1r6WlsKyloZySjIqXnaKuroyFiI+hsLK1trW4u7y6vL67trSxsre3sri2tc63t7zAurG2vbzAvrzAu7Wwr7e4vLy5xMjEwMjQxcbKx87IyMrJxMGurKytraSjr6ijo6adlZWRkJKcl5qllZCIiI6SlZ2fgGxoaGRfYFxfXltXWW6GnbdlZ1+zvWVkX19gY2NkZ2KxXGFlY11cYWVjY2Noa2dmYl+6uLS4XWBet7W7YLyztLpvbmtqZmJkY7WgmJiZmJmWkpWRj5KboqOqwWRkZmhnZWdmZGdmZWNjZWpwcXB1aWaSbW1oZmloZ21uaGpna3BybHFsa3F1dW5vc3d1dXd9g3l0enx6cXZvcW55c21vdG1ubWpqam9vcHFsb3Fvamm7npirrqugkIt/eHNvb3B2f4aLi4SBi4mHgnxzZIWtZ2lqaWpsbG1uZGNhZmXAuLq9Y75foYWWa4yZmJeXmoWfSaCfoKSlpKOjpaWkoqGho6ZVpqVUp6WmpaKioaCfnp+jpKKjpqSho6OkpaOinJidoqaqraupqainp1RWV1VVVldXWFhZWllZWVqHWYBYV1ZYWVhZWFVTU1RVVKalo56SiYSCgoOGiIaHjI2LkpOVlZaeo6alrbG2X2FiZGZnZmdoaGZlZmZnZGNkY2BlZ2ZjY2ZnZ2doZ2dpZWJkZGFhYGJhYmRjYF+5squnpaOfnJuVkIuSnJiTlZaSjpCSo66yXV5aWlxdX2BeYWJgYApiYmRjY2FhXl5hhGKAXLFaWlpdYFtaXVxasbBcXFpdXluysVhasq6uWVtcXF5hYl5aWVpbWlhZWlpYWVlaWlxdXVtZW1xYXF1cXFlaXV9fXFpYrVdYWlqxrVpcX1yyq6arrK1bXl1aWbCzXl1bW1qzsK6dmaGgnn6kp4WlpqKgpKGhn5ydnpqZm52fm5OAjpGVnJ2bnJqLYoBskKq4vby2tq62tbexs7O0s7e4urvAtq68tV9dXl21tbJgZGVetbexsVpcYmJeXly3t2FjZWNgZGdkX3Grn8Bmb2e6YF9laGxnZ25raGxqZ2NhZWBfubxeY2NiZGFkZWG5rrpivmRrZWxubWVnZV5hYWFlZ2mAZ2RpaWdpamlqYmJpZGlra2prb29ramxpYLNlbGxvaGBgaWprbG9yabCelqW4aW1sbGpsbWxjYbCZjI2Tn7TAZmhjZcG2ram1wbOztK+1tLmus62blJaippeVoJyQio+dpJ+Vj4+ZmKOsXKyfsbCxt7KutLW5ubivtLe0sa6wvl+Atbm7vmJftb1fXV9jYrWrsFpcX2JiuLpfYWFhX69oVYCGgn1wb2ZocXp9d3J0d3hydn6BfX2AhYGBiX5wdXl6dnByeHNvbGdgW1tma292dlxZXWBqdnd4d3l8fn9/f4B/fXt5eXt5dHl5e498fYSIhHt+hIOJhIGCfnhzdH1+gH8xfIWHhICGjIaHi4eKhYaJioiFdnV1d3Zwb3hybm5zbWZnY2NmbWhpcWVeVVZcX2JqaoBUUlJOSUpHSUhEQURac4OXVVZNj5tTU05PTU9QUldSkEtQVVNNS1BVUlBRVFZSUVBOmJaRl09TUJmUmE6bkZCNU1NVWFROTU6SgXp1dXR4d3V6d3h8hYuLkKVXWFtdXFxfXFhbW1lWVFZbZWhmbV9OdmBfW1leXVtgYFxfXmZpaYBnYmBnamplZWdraWlqcHdpZGxub2VoX2FcaGNeYGRdYGJiY2JmZGZnYmZlY11dpIeFlZaVj4N9cGlhXFtdYmlucW9ranJub2xnX05sk1peYF5eX2BgY1lXVlpZq6CkqVmpVY1pZURaYmBdXF9kY2NjZWdmZ2pramprbGxsa2ppaytuOG1rN21qa2xra2ppamppbW1ra25taGlpbG5tbGhjZmptcHJycXFwb242hDcNOTo6Ozs7PDw7Ozo6OoY7Ajk4hDqAODY1NTY3NmtramthWFVYWVtfYmJkaGhmbnJ2dnd9g4WBhYeLSUxOUVJTU1VVVVFQUFFST01OTkxRU1FNTlNUU1JTUlRVT0xOTkxNTE1MTVBPT1CdlYuGhIB9fXt1cm52fXt0cnRybm9vgImMSkpGRUdJSktKTU1MTU5PUlFSUE0DSUlMhE2AR4dEREZJTEdHSUdFh4ZISUdKTUiKiURFh4OERUhIR0dKS0dDQ0VGRUNDREVDRERERUZGRkVERUVBRUVERUNER0tKRUNAfj9AQ0KAfkNFSEWGf3d7fX9ER0VCQYKDRURBQkGDgoFybnd2c110emBzcGliY2JiYV9fX11dXV9fXluAWFlcX15dX19UP1lZfo6Yn6CXm5OcnJ2TlpeWlJianJqhmJCdmlVRUE+UkIxNUVNNkpSLikdKTk1KSkmRkk9TVFFMUlhTTlR8fp1VWlKUTk1RU1ZTU1dUUlZWU1BOUk9Ol5lNU1JQVFFTU0+TiJVToFJZVltcXFVVUktOT09RUlaAVFBWVlNWV1ZWT1FWUlZXVlVWXV1YVVdVTI1RWFdZUk1PVldYWV1fV419d4SRUldXWVdYV1VNS4t6cHB0fpSiVlhUVqicko6cq5ybm5OYl5uOk5B+eHmCiXt5g35waXOBhH90amtycHuGSYZ5ioqKjYiGi4yQkpSMjY2NjoqMmExalJiYl05Mj5dMSktPT5GIjUhIS0xNk5RLTU5QTIJJPF5fWldPTkdLVV1eWFlbXVxWWV1dWllcYF1eZ2FTVFVWU1BSWVVQS0dDQUFJS0lMTTw8PT5KVlhbWlpehGBTXVtaWVZVVlVSVldZaFVZX2FbUldeXmBbWllUUlBOUVVaV1NaXFtYW15XWV5dZGFgYmRiY1dYWl5ZU1NaU1BRVVJPUk5MTlRPUVlOS0JAR0tMUlKQfgV/f39+fop/AX6Rf4R+B39/f35+fn+Efoh/kn6Vf4J+vX+ffo5/hH4Gf35/fn5+nH8EgH9/gKV/pYCdf6+AmX+cgAF/ioCCf4aAB39/gIB/f3+qgAF/hICCf4SAhn+FgIJ/hYCJf4J+nX+bfoR/g36Ef4R+h3+Cfol/CH59fn5/f39+kn+Cfol/BX5+fn9+qX8Bfo5/hX6Kf4h+hH+mfgF/lX4Bf4R+BH9/fn6Ff4N+hX+CfoV//36WfgICBACAkpGJg4uMjpy11fGIhomHgICDgYWLj4+KiIH69viA+YCFhoeG/YGC+/L5/fuDhISBgoCAgYOB//v5/YD18fT48/qEh5OPg/qAgYKCiYiEhYeIiYiLivLm3drT1NPW3MrHysLBxsbFx83PzdHZ1cuU9Mnk6Obx9vTz/PL9+vD3/YWAh4qG/v2Ahf7uh4OFguvs7vT68/Dy9Ojq4ePV1+Tgzbi6uLeys6+so5mXm6ShnJyepbSyqrfBvcbGxL6/u7zEzsnFw7i3vMfTyb22oJiVwPyKiYqHg/2ChIuNiIKBhIaIgfjs4eHHq82Zzejy9vf49/X09fb2+Pn+//78+/v9gIJMgoGB//v8+/f19vXx8vf6+fb19fX39/j3+Pj7/Pz7+vn6+vuAgP/9/fr7+/r29PP19/r+/4GDg4OEhIWFg4GAgIKEhIODg4KA//v+gYSCgIGCgoGBgYKBgPv5/f3++/n29vj29PTu6+bk4drU0srHxsfGxcbGw8K/vL2+v8S/ub68uby9vry5u728vraxra6yubq9wcXDwcOxq5G+8vz+gIaIh4eIhYSDgYGCgoOFgf37g4eHiIWBgvfyg4P7+f6DhYiHh4eEgIGBgIGDhIaBgIGCh4SGiIeEhoWEg4L9/4OA/4H/gYH7/4ODhIKCgoSBgIKBgYKC//T9gYSDgoGBgoODhoeFg4SFhIWGhYSEg4WGgoD9+vv/+/j7gICCgoD8/PqA/YGDg4SDgPv6+/j3+/7w6+3z5c+W5ZjY8PHs7vD29/b3+Pj18/Dq6evr7O3ugOzo5+XKk7icy+fx9PTx7Or59eHi5vf/8fiB9ff0/oGA/Pvx8Ozt/4iF/PyCg4aHgIL69vP09PeBhoaDhYaD/IGEkpiMifKGmvaA/fD4hIWIhIGDhIOGgoGAjIWDg4iA+PH49vvq5vb2gpCVj5OXlZGOhoKEgPf/ipGOi5OTj4+PgI2Uko+IiIyKhYGFhPrz9IWJj4iKjpualIuOkY2QkImQl5eeoJmXlpuYlJOD29TV3PXgwsHG0+eChoL+goqLjI6NjIyPioeGgYH27e7v6Onv9Pft4uPr6OHY2trl6Ovn6dnQ08/O29vf4dzb7vDs7f2AgYT99oCA///89vqBgPX2gPny8PX6+Pbp3OXr7vX27uv7/YH99/6C/v6F9qbomKSqoKChpamvuK+usbG5w8TAqq+0ube1sbCrrKqxtLy9uLm0rrK5uKyippeFmKOinJmZmpyjqLLGx8DAwLS7vLq5rqyoqK2xqa+2rqquuMDDxcTEwcO9xca0tbm/v8O8tbGxMLWytLaztMXFx9XIv7+/yr+3sq6yr6idop+bmZOSmZuWkZuioaenl4iIiImKkI2PjYBgX1lXXl1ebIGZr2NiZmNdXmFhZGdramZlYLi2uV+3X2RnaWa9YWO9s7i6umNjYV5eXFxdXl66trW6X7W0t7i2u2Fia2livWFgYGFmZmNkZWdoaGppta2mo56dnJ2djo2SkI6RkpOVnJ2XmqGgn3G1n6+uq7S3ubrCusO+t7/FZ4Bpa2jGxmRoyLxraWpovb69wcfAvL/Bs7ewrZ+ks7KfjJCNiH9+e3lxZ2RocG5rbW1yfn13gYiFjYyJhYWAgYePioiHfn+Dh46GfnxvamqRwWloamhlw2VmamhkYWFkZ2hhubGqrJZ7jWeLnKGjo6Sko6KipKOlp6qsq6mop6hVVgdWVVWoo6WlhKE2nZ+kpqejoqOio6SkpKOhoqWlpKOhoqKjU1SmpqakpaakoqGfoKCjqq5YWVhZWVpaWlhXVlZXhFgKV1dUp6GiU1RUVYVUgFNTVFRTop+io6Sjop6bnp+goZ+gnpqYk46OioyLi4uKiouKioiFhYWHioiFhYODhYWHhIOFhoODfXp4eHh7e3yAhYWCg3tzXIawtbZbX2FhYGFeXVxaWltbXF9asbJeYmFhX1pcr6ldXq6prlxfY2FhYV1ZWlpaW1xeX1xcXGBdgGBhYV5eXl1dXK+xXVy2W7VbWq6zXl5fXV1bXlpYWVhYWlqwp69bXlxaWVlbW1teXl1bXF5dXF1cXFxbXl5bWK+srbGtqa5bXV1dWausqlitWFpaW1xZr6+tq6uusqWhoqmekmqhaZSioJqeoqalnZ2gn5yZlpOSlJOTlJaVkpKRgINgfG2TqrK0tbKsrbayo6Omtbyutl+ztLG3Xl66urGvq627ZGG1sVxdYWJfYLevqKenrF1hYV5fYV+1XF9rcWlnslpsumK9rbBeYGRjYmNiY2hjYF5nYmBhZGG6srm4uayotrZgbHBsb3Rzb2xmY2Rftb9qcW5qcG9ra2xqb25sgGVnbGdiX2NiurS0ZGdsZmhteXhxamtuam5sZWtycnh5dHNxdHNwb2CclZuit6KMi5KarGNoZsdnbm1tcG5sbHBva2plZcO3tLKsq6+xs6ukpqmqpZmZm6qvs6+xn5GSjo2YmJ+knZmprKiqul9gY7y0XV26urmzuV9dtri5tLO3gLu3s6aao6iqsbOtp7K6X7y2u2C7uWK0dZxsdntxcnBwc3h/eHd4eYGKi4Jwd3yCgH53eXh5dnl7goKAgX15en+Adm5yZlxrb21oZGRlZmptdoWFgYKDe4B/e3x1dnVxcXJrdX10bXF6gIKHh4eEhYCHhnd4en+Ag4B9e3p8eXt9LHl4hIOFkYZ+fn6Hf3p4eXx6dWtva2hnYmJoa2ZgaG5scXBmW11bWllcWFlagElHQT9FREVRaICaXFdYVlFTVVFTV1paVFNPlZSXTpZPU1VXVZpQUpuRlpeXUVFQT09OTk9RUJ6al5dMjYuOkZCVT1BZVk6WTU1NT1ZVUVNSU1RUVleTjIWDfoGDgoBwb3RxdHl7fYGIi4OJlJCKWYuPopyVn6OjpKicraulsblggGJkYLe2W1+sm15YVleip6irr6ysrq2bmpGRhYiWlIJydHJuaWtmZV5UUVNZWFVVVlpmZmFtdXB3d3ZycWtrcXl3dHJnZ2xwdnFqZVVSW4CqXV1gXlmnWFldXVtXVlhbXVeoo5ycgmBkRVpkZmZnaWloaGlqamtscHJwcG5tbTc4Fzg3OG5rbW1paWpqZ2pvc3Jta2xrbW5vhXAnbmxqaGdmZjQ1aWlqaWxtbGloZ2lrbXJ1Ozw5OTs9Pj07OTg4OTs7hDkFN2tlZzWENoA1NTU2NTU2NjZpZ2lpamloZWRoa25wb29sbW9tamlnaGhpaGdoamhpZ2VlZWZqaGVmYmFkYWNjZGZmYmNdWVdaW15dXWBiYmBiW0o+bIyOjUZMTUxLTElJR0VFRkZHSUWGhkpOTExKRkiHg0pJgXuDSEpOTExMR0NERUVGRkhJRoBGSExIS05OTE1MSkpLjYxJR41HjEZGhIpLS0xJR0VIREJEQ0JERYZ7g0ZJRkNBQkREQ0VGRERFR0VFRkZFRkVISUVCg4CBhYGAhEVGRUVBfX58QYJDREJCQT56fH17eX6CeHZ5gHRsT3JNa29rZmlqamdgX2FhYF5dWVhaWltdX4BfXVxcUTxXW4GTm5yblpSUnZiKjZGcoJKZUZeXlZ9OTJWWjYyMkJpVUZOLSElMT0tOkoyEhYOGSk5MSUtMSYtITFhhWleVQVGbUZyRkk9RVFFRU1JSVk9PTFNOTVBUT5SRl5KWiYWVl1BbX1lbYWFeW1ZSU06Rl1VbWVVdXVlZWYBWXFpZU1RXVVFOUVCXkJBSVFlSUVZfYFxWWFtXWllRVl1dYmNcWVleW1dWSnRwd32QgGhnbnqMUlVSoFRdXF1gX19eYV5bW1lZraCal42Nj5GVkIqLj46Ifn99hoyUkJSDcG1raXR4f4N9eomIhISWT05RmJNNTZucmJSZT0+ZmICXkZGWmZWQfW55g4iPj4iFj5NMlZCUTJWWT4xWb05TVk5PTVBVWl9bXFxaXV5hYVRaX19cXVpaWFdTVllcXFpbWVZYWlpTTE9EOkZKSkRBQEBCR0xVY2FbW11WWllWV1BOSkhKS0dQVU1KTVVbXWBgYV5fWl5eUVNWW1pcWFRUUzBUT1BTVFVfXl5pY2BhYGtmYV5dYl5ZU1dUU1JOTE9QTUpSV1VZWU5FRkRERUpEQ0SLfo9/BX5+fn9+hX8Dfn9/hX6Kf4R+AX+GfoV/AX6Of5p+AX2PfoR/Bn5+f39+foR/w36FfwF+i3+HfpZ/hYCgf4KAj3+UgIN/joDBf4J+hH+QgIJ/h4AHf3+AgH9/f52AC39/gIB/gH+AgH9/joCDf5qAh3+FgAV/f3+Af4aAjn8Bfp1/k34Bf4R+gn+HfgR/f35+hn+Gfod/AX6Gf4R+BH9+fn6Sf4l+jX+CfpV/g36df4t+BH9/f36Of6d+B39/f35+f3+FfoJ/lH4Lf35+fn9+fn9+fn3/fpR+AgIEAID8hpu1y+f6/4CFh4aC/4H+gYaKk5eRh4aJiIiA/oD+gIGEhYH//fr8+/z9gIH///z7/4CDgoKBgYD3+oKDg4OEi4iJiYiEgP6CiIWFg4KBhYWJhPz06uz6/v32+ISB+ezv6+zu7Pf98+vb0MfGjM6Iqp6lp6Cuta2Wp6uhoKGcoYCmp5ybm5aZm5SmqZ+wtbW3sK61t6uvsb7Fxb6+vcK+traxurixs7CtsLO5ubKysKimqbO2tre0oaa0wsSwpbnJysLEwcHN0M/R0dLOvbGkmq7P/I6FgoSEgoOCg4eKio+QiIGA+/2C+NCmuom33e7z9fb3+fn5/ICBgoGBgoKA/UX7+fj39vX3+Pn7/Pr5+vr49/38+/z8/fz7+PDq6e/5+///gIKDg4KA/vz4+Pn5+ff09fb4+v+BgICAgYGAgPv4/P6Bg4SEgweBgICBgYGAhIEFgICA/4GEgoCB//6AgP/++/z8/f7/gIKBgYH//v35+fr7+vXx8vHu6+no6OXk5Orl3dzj5+bm5eXi4efn6/P08vHz9vjxtJTF4/+DhYWFg4GEg4KDgYCAgoiJiYiC+/n6/ICChYH8gISCgYCChIODgIGCgYGDg4CIjoiCg4ODhIOBgYaEgYD++4D29e/0/ICCgoKAgoGBgoODhISBgoP//v36+ID//fz6/YKEhYSDgoKEhYGDhYSGhoOAgIGEhYKC/oCChIWDhIaFg4OA//uBhoH8+v6Chfj09/j57u73+92d5ofG5uro6Onv8vTv6+3v7u/0+Pj08vHr5uXj37f9qpu22un08vDr24DV4viA9tvo7Ovx6O/4//3+9uzz/vb16+fq6/Px7e3t9/qBg4KBgIKCgIOJgIGFgIaDgoSCg4SGhYjP98OKioaFh4aHiIaEhIaHiIWFg4aFg4D0/YSF/vmAh4iEjpWdnI+FgYSKjIWLi5CPk5aRi4yTmZWSjpGLi4+JhYSBgff3/4CJho6XlZOQkpmXkZSXmJWSmZeOj5CWm5mZm56UloqB4sm7r8HJuMTK3feGiYmSkpGQioWGiIWFhYmPjYWA/PTs5ubq5dvl7/P08urm4tzd2uDx9OzVysHL1NXn7ezm4erk6ezn7/j3+f729vn59/n+/v/59/b38Onq8vv29Pf3+YD16+bt7fHu7v/7+f2CvO6Ml6CkpaqvqqmYlqyusbu7s7m3sKSkqa2trKqtwsO7srSzq6attre0qKWjoqaTlZyVlJ+vt7CnnZytrKqvopqlr7K3ubGprK2wtK6vq6uwucnEwr+7vbSssLCwtLm6vby2rLGrp6Wmp6mlnJuioqSppyOjpKKnrKWgm5aTkI6OjJGRiIqIjJKPkpaShoDy94uTm5mH7zOmXnOHmK++v2BjY2JfuV25XmRlbG9qYmJmZmdgv2DAYWFjZWLDwr6+vLu7XV65ure0t1yEX4BgX7a4YWJhYGBmZGVlZWFdtl1jYmRiYmBjZWlkvLOsrru+vbi8ZmS9sLGtrq+rtLuzqpqTjZBlkWJ8cnV1cXyAe2p3eG9tbmtuc3Zua2tnaGhicnRqfIODhX16goR6fX6KjouCgIKKhXx9eH97c3Z2dXZ4fHx3enp1dXd+f35/fU5rbnyGh3pzgIyNiIqIi5KQjY6Pko+CenJpepm8a2VhY2JfXl5gZWpqb3BpZGPAvmG5nHyAXn+XnqGjpaanqKiqVldXVVZXVlWnpqalo6GEoxqlpqOjpaSjo6iop6inqKimo5uWlJqkpKWkU4VVJ6iopaWkpKSjoaOmqKqvWFhXVldXVlaop6utV1hYV1ZXV1ZUVFVWVoVVNVRVValVVlZUVFOkpFNTpaalpqanqatWV1dXVqurqqeoqaqqqKWnpqKgn5+emZeXnJeSlJibhJoSmJednJ+lpqSkpaanpXtehaq3hF0uW1pdXFtdW1paW2FhYWBbr66tr1pbX1yzW19eW1laXFtbWltcW1tdXFlhZ2FbXIRdElxbX15cWrOzrqumqrJbXFxcW4RehF2AWVtbrKusq6pYsK6rqaxZWltbW1paXFxYWlxbXV1bWVpbXV9dXbNZXF5fXl5gX11cWK+rWV1ZraqtWVyspqmsrqKjq66abZ5ciJ6fnJubn6GhnZiYmpqbnqGjn52clZGQkI92pnBrhJ+nr7K0r6Keprlft6Kqq6yyqrG1vLu7samAsb20sKinrKyxr6mqrre1XV5eXV1eXFtfZFxeYV1gXl1fXV1dYWFom6iQbGtnZGZlZmdlZGNkZmdjY2JkYV9ds71kZL69YGRkY2txd3dsZWFjaGljamptbXN3cWpscnVxb2psaWhsZmNiYGG5vMJnZGpwcG5sbnZ0b3B0dXJvdHKAa2tscHZzc3Z4b3FnYKWQhn6MlIaPlabAaWtsc3NzcmxnaGlnZ2ZobmxlYL67ta+tr6ieprG3ubauqaafn52ktbeul4+Ij5WVpKmpo6CppKisqK6ztbe+trW3trW3u7u7tLKytLGrqrK6s7O3tre0rKeusLGrrLu3tbhghZtfaG+Ac3J2d3JyZGNzc3eBgnx9enZxcHR4eHd2eYmKhHx9fHh2eX1+e3Rwb25wYGRoYmBoc3l0bWRjcXBwdGlka3FzeHp3c3V0dXdzc29wc3mEgYKCf4F6cnRycnZ6e36AfXV7dnNwcXJybmhobW1ucXBtbm1ydW5saWZlY2FhX2RkXF4RW11gX2FkYVlVm59aX2ZmV5eAeEZbcIKYpadUV1ZXU6BRnlBVVlpcWFNUV1VWUJ1Qn1BRU1RRoKGgpJ6ZmEtMlZeTkJVMUFBRUVFQk5NNTU1MTVJQUVJRT0qQS1JRUU5NTE9QVVGXjIWHlpqalJdUUpqRlI6PkpCapJmShXx1eFJyT2RbX2BaZWtlU2BkXFtcWVuAYGNYV1hTVlZPXV1TZ2xqbWZiaGtfYmVucXFsbmxwbWdjW2BgXl9cW19gZGNeX19aWl1kZmhsaFhZZHR4aWBwfHt1eXZ3fHlzd3d3dmtqZVtog6VgWVRVVFBPUFNYW1tiZl9aWaysVqCFZV9BVGRpa2xsbG1tbW44OTg3Nzg4N22Abm5vbGtrbGxsbm9ubm9ua2tvbm1ub3N0cm5nY2BiaGhpaDU3ODc3OG9ta2xtbG1sam5xcnV5PT09PDw7OThvbnJ0Ozs6OTg4NzY2Njg5Ojk5OTg4Njc1ajY3NzU1NGdpNTVpamprbG5vcjo7Ozs6c3R2dXZ5e3p4dHVzb25wcnJAb2xqb21pam1wcHBta2lpcHJzdnZ1c3JydHdXQXB9i0lKSUlGRUhIR0lGRUVHTU1OTUeGhoeHRUZJR4lGS0pHRIRGHkVHR0VGSUhFTVNMRUZJSElJSEdLSUhGjImBgXyBioVHgEpLS0pJSUlIRUdHg4GCgX9DhoSBfYJFREREQkFBQ0RAQ0ZGSUlGQ0NFRkdERIRDRkhKSktNSkZFQoWCRUlEgHx/QUN4dHl/gXZ3gIRyTndDYHBwb2xqbWxqY11eX15eYGNlY2FeWVZVVlZMb1RZc4eNlZWYk4eEj6NSnoiTlJOZPI+UlpeWmI6Gj5qSkYmGi4qQjYqIjZeZTkxMS0tNS0pNUkpKTUtPTExNS0tMUVNZfnNuWlpWVVdXWVhWVIRSgFFRT1FQUE2RmFJTn55QU1JSW19kZFlTUFRYW1VWVlhXXWJdVVVdYmBeW15YVlpVUU9MTY+RmlRQVlxbWlhZXl5ZW19gXFleW1RWWFpfXl9fYVdYTkl/b2dfbXNkbnaGnVZXWF9cW1tYV1haWVhYXGJgW1eqopyXlpWNgomSlZeWgJCNiYKBf4WSlpOAdW1zeXV9hYiCgIqFio6KkZiXmJ6VlJSTlZecnp2XlpaWk46Nk5eRkZeTlJGMiZGSkIeGlpKPkU1nakJITU5OUFRRUkdIV1dYXl5YXFtZU1JUVldVU1VhYVxZWllVUlVYWVZRT01MUUNER0I/RlFZVUxERFFQP01NQj1DTFFVVVBMTk5PUExNS0tQVmFgYF1bXlhQU1JUWFtaXmFdVltXUVBQT1BPSkpPUVVYVlNVVFdcVVJQTYRMFklJRkBEQ0VIR0hLSEA/dHdFSExMP2kBfYd+hX8Dfn9+jH8Dfn9+hX+HfoJ/hX6Hf4J+jH8Bfot/iX6Cf5B+AX3kfpF/A35+f4R+jH+IgKN/hoCOf4iAhH+VgAF/hoAEf3+AgIh/hYCsfwR+fn9/k4CEf4SAAX+ggId/kICFfwGAhX+XgAF/i4AKf3+AgIB/f3+AgIt/AX6cf45+AX+dfph/A359fpV/Bn5+f39+fqZ/g36ff4t+k3/PfgN/fn3/fo1+gn2FfgF9AgIEAIDvhIODi4aBgICChImLhoWJi4eIjY6Oh4WAgYaA/YCDh4yMiYeDg4SE//Ps9fr6/oGCgf+Ag4ODiIeFgoCCh4eIi4uNi4eFh4qKhYKEhIiQj4uKiYH7/v/4+f3y9PXw7vj969zb7IL/8ODg38mhzeihpZ6fpqqspJqnsr+4sLOysn+ytbm5tLKysrW2t7Orq7Kwra+1tK+yvMjLxMC+ws3NysXDw7Oyt7W6u7i8xM3DuMHJy8G6t7e4tr28sKuysri4trrN19jTy8jBxs/MysO3s7mlj6LO/ISIhoSDh4mDgIWJi4+OjoyE+fuBgui7rdqZzuz2+Pj3/P////7+/4CBhICCgYSAQ4GCg4KAgYKBg4SDg4OCgYD+/P379vP0+P2AgYD8/YCAgP/+/v//gP/9+/38+v3//4CBgYGAgICB/v3+/4CAgP+AgYGEgAH/hIGAgoOEg4L//Pz9/Pv6+vj3+vr7+/j6/P6AgID++/z79/f18/L2+PHt7Onm5+vr7e3s7OXh4t/f4+Xo5ujq7e/w8vLz8/Xz6ZqF5/eEgoKDhIGAgoaHhoWFiYqFgYKE9vP5/4SIiYWC/fr9g4SEgoWFg4OEhoOAgoKDg4CCg4OB/f2A/v7//v3+/YCDgv36goWDgoGDgoOEgv78gYL7gIGA//r5/YGCgf6AhIGCg4D0/P+C/YOFhICAgoSCgYWDgvv7gIGEg4GDhIODh4aCgP7+gYDz+Pz7/f75+/v38Nuk8YW64+no6evo5ejt8fT09fj4+vz5+vr59PPq1Krsp6DK6/GA6d7d7ezv+e3k2eDr7+3l5eT1/Pjx+Pf7+PPu5+To6uvi6Ofr7/n0+4KB+oCEh4WDhIWDg4KCgYH///6FgoCFg4D/oPTchYCJh4yKiIiCipGRjpGE/4OG8uv+hfr9iISJjpCNhO/z8/ySkYiEgo+VlZuYk42Eh46NkZSXjf6Dh4WAiYyFiIL6gYWMkpWak52jmpSal5GPkIeJkpOIhoeJhvzq4Mm4tbrW9oeJh4SFhYb/6NbIwcHG4P6Hi4mMjoiCgP338PPx6Ovm3OTn5Nvh6vDx6+7w8/Lu6ejq49bU19jh7Ovp4tXY2uXm3ODg3d7X4Orx8/f6/vv0+Pbp6ent6+WA3OeAhoSEgP7z9oCCgoL47OLp7uWw34KNl5yalpqanqOfmZ6cnaemprCno6Wfoauyra28u7axp624vby2ubWysrGroqCioqWjm5WenqWjo5uZoKimoaOmqJ+jqKOem52fpaywra6sr6ykn6Knq6inqKGloaOtrKaio6mmmpecnZ8rn5qUlJaeppyem5iVkZOWk5WZmJWPjIyGgoWLkI6OiomWl4mDgPeCm666zYC2ZGFfZmJfXl5fX2JlYmFkZWFjZmhoY2JeX2Jeu19hZWlqaGZiYWFhvrewtbe2ul9gX7xeYWFiZ2VkYV9hZGJkZmZoZmNiYWNlZGNkY2VraWVjYlyxtbi0uL62ubixrbe6sKahrWLAsaGenI90jZ5wdG5vdnt8dG14fYR9dnh5eoB6foOCgYB9en5/gHx0dXp3dXqCgHyAh4+Ui4SAhZCQjYqJiXp5gICEg4GFiY6IgIiOkYiCf4CCgIaFfXl9foODfn6MkpKOiYeDhY2Mjol+en9wYHKcwmNkYmBeYmVgXmNpa21rbWtlvb5iZbKKeJVojaKoqKamqqysq6qsr1hXVxJWV1hXV1ZWVlVWV1hXVFZXVliFWSpYV6yrrKqmo6OlqFRVVaipVVZVqqmpqqhUqqqpq6upq6usVldXV1ZWVliErYBWVlWqVVZWVVVVVKhVVlZWV1hZWFeqpaSjoaCgoaGipKWlpKKkp6hUVVWpp6akoKCenJyfoZ2ampeVlpqanZ6enpiVlpWVmJqbmp2goaGhoqSko6Sko3BZo7VdXF1dXlpZWl5gX15dYWJdWlxeqqmvtF5hY19ctbCxXV5eXF5eW4BcXV9cWlxcXF1aXF1dW7Oys7S0srGzsVpeXbSwXF9eXVxeXV5eXLKtWVuuWlpZraelq1lbWq9YW1hZWlenra5ZrlxdXFhYWltZWV1bW6urWFpdXVtbXV1cX1xYWK2tWVikqK2tr6+qrK2rpZhvo1l/nKCen6CfnJyeoKGhn6GjooCjoaOioZ2cl4puoXRxk66xrKOhsbGyua2onqOprqufnZ+ytrGqsrC2trGqpKGoqq2jqKipr7q3vWFgu2FkZ2RhY2JgX15dXFy2tbVgXVxgX1/Ac6SjZWFqaW1pZmZfZmxram1kwmRkr6m5ZLq9Z2VpbG5sZLW4uL1ubmZkYm1xcIB2dHBrZGZsa21vcWq/Y2VkZ2pkZl+2X2RpbW90bnd+d3F1c29ub2Vmbm9lY2RlZb+vpZWHhIabs2RmYl9iY2O7qJ2Sio2Wq8JnamdpamdjYcO9tbi3r7KupKqsqKClrrW3r7GytLOwq6qvqJqZnJukr62tpZicoa+xoqKkoKKbooCstLW3ub28uLmzqKqvsaykmqZfZWNiX760t2BfX2K4qp+mrKV9j1ZfZmppZWVkZ2tpZmlqanJycnlxbnBrbnV7dnaBf3t3b3R/gYB9f3x6enhzbGlpaGtpYl1lZmxqamRjaG5uamtucmxvc3BsaWlpbXN1dXh1dnNubXBycm5vcDtrbmpsc3JtamtwbmhnaWlqaWZjZWdscmlqaGZmY2RlYmNmZmRgX19bWVpdYF9eXFtoaFxXVqlZa3uImoCeV1RUXFdTUVFTU1ZWUVBTU01OUVFVU1NQUVVRn1BRVVhaWFZTU1VUnJGGjI6MkU1QT5pNUVFSV1RSTktNUU9OUFFSUlBOTlBSUE9PTU9UVFJRUEqQlJiXnKKWmZaMipmcin58j1WhkYOCf3BcbINiYVtcYGJiX1hgaHRtZ2tqaoBoaW5uamloZWdoamdfXmFgXmBjYmJnb3d8dnFucXt7eHZybl9fZmdqaGZqb3ZvZm51d21oaGtram5tZmRsa2xpaWt4fX99eHZydHl6fHNra29hUmKIqFVZV1VUWFxWVFlfYWRjZWNdr61aXKR8YG1KYm5wcG5tcHFvbm5wczo5OTw6Ojs7Ozg4Nzc4ODk4Njc4Njg5OTo7Ozo5cXBxb2xpaGttNzc3bW45OjlycHBwbjhyc3N1dXN2d3g8PT2EPAs+enl6eDs7OnI5OYQ4gDdtNzg5ODg5OTo4bWhmZmdlZGZlZmlqbGtoamttODg4b2xubmloaGlpbG1oZmdnZmdsbnByc3RuaGloaGprbm1zdnZ1dXRycm9vbW1QPXGLSEZGSEpFQ0VJSklISE5OSEVHSYKAhYpJTU9KR4iDhklJSEVISUdJSUtGQ0ZHSUpHgEpLTEqQjo+PjoyKjIpGSEiLhkZJS0pJS0pLS0mLh0VGhEVFQ4SAfoFDREKCQkVBQkNAeX+BQ4FGSEdCQkVGRERJRUR7ekFDR0dERUdIR0tHQkGBhENBdXp/f4KCe3x/fHdtUXU9WWxsaWpqamhnZGdlZGRlZWVnZ2hnZmNjYFhHgGVWXn+YmJGJjZ2amZ6Tj4WGi4uGgYiKmpmTjpSPl5mZloyHjIyPiIuHiZKdmp9TUZtRUlVSTlBQTk5OTEtKkZGQTUlHTk5QoVl2hVNOV1RZV1NRTlddXFpcUZpQUpCNnVeam1ZTWFxfXFOTlpiYXFtVUU5bX11kYl1XUFNaWlxegGFaoFRXVVZYVFVQl09TV1tdY1xlamNdYl5aWl1VV1xbUVFRUE6ShoBxZWJjdIZMTUpISkpLjn52b2hsdImgVVZUWFtWUVGjn5icm5OWk4mPk5CHjJSZm5WZmpyclo2MkYh+gIJ/hZGOjYZ8f4OOkYOEh3+Eg4mPlZOVmZydmpqVgIqKjZGNh32HTVRTU1Cdk5dQT05QlYqBiI+GYmU9RElKSUdKS0xOTEpMS0tQUVFXUVBTT1BSVVJRWlhYVlBUW15eWFhVUlNSUEtISUhKSUQ/RkdNS0tHSE5ST0hKT1NNT1JPS0pKS05QVFVXVVZUUFBTVldUVFVQU1BRV1dTT09SMVFOTlBQUE9MS05QVFpTVFJPTEhJSkhKTk5MSUdHQkBAQ0ZFQ0JASktCPz9+RVlrd4YBfpt/AX6Lf4d+BH9/f36hf5F+AX+HfoJ9536RfwR+fn9/hH6Of5yAiX8IgICAf3+AgICFfwGAiX+IgIR/BICAgH+HgAF/iYCSf4OArX8Dfn5/k4CEf4WAg3+VgIl/BYCAgH9/ioAIf3+AgH+AgICEfwSAgIB/hoAFf39/gH+MgIJ/jYAEf3+AgI1/AX6cf65+A39/fo1/g36GfwR+fn1+j38Jfn9/fn5+f35+h3+EfpR/AX6IfwF+mX+Jfod/iX6If8J+hX+DfoR/h34Bff9+kH4BfYV+AgIEAICChoeDhIaMj5GRk5COj5GPi4mQkoqFiIqFg4GAg4WFhoSDgIOHgoCAgPj9gICCgIWEg4SChIaKiYP7+oCEhYaHhoWCg4SBgP/9/YOJjZCRi4aKiYT9+fz58/H3+ff3+PuChPXy4dXV3+Xj47Lg2pmkqbS0u7Wpq6+vtK+ysK2pp4Cir7GxraimrK+2sry2tb+9ubSxr7K3uLbCzcPMxsDDwr6+t661ucTBxMnFx8rMwb+7urazsbGsrsC7vLu7xMXBuLLH19fTz9LPxb/FxcPGxr23uLOkm6fH64GDgfTz/PiFkJCLgP/9gPf6/v+B/+zNsL/9pc/n7/Dz9/n7/Pz7+hP6+vz9/YCBgYGCgYCBgYKDgoKDhIQihYKBgYGCgoKB/4D//Pr7+/v+/f3+/v37+/r4+vz7/YCCgoaBgIOCgf//gP79//+BhIWEg4GBgYCA/fz3+Pv7/P3+/Pr5+vv07u/x9Pn4+Pj5+fn6/Pj3/Pr7/v77+fj3+Pn29/bv7u3r5+fn5eLh4N/f4+bp7Ojq7vL0+Pr49/f54oOK89/+goKD+vn7+/r0g4SIi4eDhYaHhoWCg4GChYSB/P6BCP7+gIKA/oKEhIKAgP+BgP37/Pj2+v7+gYKEhIKEh4eEhoWEg4aGhP78gP7/gYKB/vuBhIKCgPiAhoOCg4SAgIKEgYCEgoCCg4GBgIGChIL+/IWJiIiLiYeEgPyBgYGCgv+AhoiBgPyCg4WD/u2+kPOIstfY2tzk7/Lx7+/z+Pj29vb49PH4+vr37seAkMeXtNfp6vDx393n5fPu5uHt8/Pp6OHi5OTZ4+Ph5eHk6/T18/L4+fHy7d3i7+np8fz89ff89vuChYqLiYqHgoCA/oD+goSFjI+OjPiHheb+iIaEhPv9+vmFjZGNioqGjJGTko6ChPr6+oKF/YGChYWGgf+EjoyLj46OkIqLh4iAlpiZh4uO9YCEjZCTko6SmZqXl42MlZWYnZyUl5aSjIWGiv/awqWlrbXB1/D7/IGIhPv78ePv+fH2+f/79vuB9uDOu7i7wMrd8oOHiomFhYT+/PL38+ro6eXd4+bi2tfT3+Pe19zYyMfQ4OHk5ePm8fLp6enq6O3o4Of3/Pfu6+iA4e337+vw6+33+ID+/P6BgoCAgP39hoaC9O/q6+u53Yqlp6iknaGmqKSgl5qamZeSmqCinJaWlY6Kk5eboJ+amZeiq66sqK26qKGjp56Ym6eoiYmipKKho6CTjoyKipSfsKacm5mdoaCdnqSioKKfoqOlpqOdmp+ooaKdnaCfm5o1lpWal5SWmJqXkZCRlp2jp52XlpydmJKQjpCYnJydn5uWkIqKjI6PioD+jKOywtn1hYOCgYKAXmFiXl9hZmhqa21qaGlqaWVkaWxkX2JlYmFgYGRmZmZjYl9iZWFfX1+4vF9eYF1jZGVkYmNlZ2ZhuLddYWJjY2JiX2BhXl28v75iZmlsbWReYWBds7O2tbCwt7iztLa7YmS1sqGXmKOqqqqDnpdpb3N+foZ/c3R5e354e3l4dXR/cXt8fXp3dnl8g36EgH+IhIB8e3x+gYGCjpSIj4uHi4eDg3tyd3uFg4WIhYeKjoeHhYN/fHp7dnmHgoSCgomJh397iZSVko2QjoaBhYaFh4iCfoB7cGp1kKdbXFmsr8DAZm1saWLFw2K7vcPFZMS0mn6Aq3KOnaOjpKaoqqqrqoWnCahWV1dXWFdXV4ZYEVlaWFhaWFdXV1hYV1arVquphKcOqaioqamopqalpaeop6iFVIJVhFaAVKeoVaipqqlVWFhXVlVVVlVUpqOdnqKlp6aloqCfoKGdl5qdoKKio6Oko6OkqKakqaioqqilpKOio6OgoaGbmpqal5eXlpOTlZWVlpeanZycn6ChpqmpqKisolxXo6K1W1xdr62trq2nXF1jZWBcXl5fX15bXFpaXl1arrBbs7OAW1xasVtcWltcXFu1XV25tLCurrK0tFtcXV1cXWBgXmBfXVxfX12wr1qws1tdXLKuWVtZWFinV11cW1xdWVlaW1lZXFtaW1xbWllZWltZrqtcYWFgYmBfW1erWFdXWFesWFxeWlqwW1xeXLSqiGWlWXiPjpCTmaKmpJ+foaSkoqKAoaOfnKKko5+agF6Gboehr66ytKmkpqO1sKajrrSzqaednaGjmaChoaahprC5ure0uruxr62kprGrq7W+vLW5vbi+YmRoaGhpZmRjYcBgvmJkZWxvbWzBX1quwGdkY2O8vbi2Ymhta2hnY2htcW9qXl+1ubxkZb9hYWRjZGC/ZGuAaWhsamtuaWlmZ3N0dWVnabNeYmpsb3BtcHV2dHRranJydXp5c3VzcGpkZGi/n4t4en2Djp+wuLhdYl+2uK+hqK6psba8uLO4X7ajk4OBhoqUpbdkZ2lnY2JhvLuzuLSsqauooKWoopubl6GinZeYlouLk6Cgn56boK2upaWmp6mAsa2nrru+uK+uq6WzvLOvs62ttrVfv8DCYmRkY2PCwGdlYrWtp6uug4tab3JxbGdqbW5sa2VnZ2doZWpsbGZjZ2djYWdoaWtqZ2dlbHFzcW51gXRtbnFnYWVublVWbG5tbG1pX1xaV1dga3hxa2xpamtqaWtubGpubW5tbm9ua2g7a3BqamZnamlmZGFgY2FiZGVnZmFeXmNpbW1mY2JmZ2hlZGJiZ2hpaGhnZF5aWl5hYl1Up15ve4uftmKEYIBOUFFLTE5TVFVXXFlXV1hXU1FXWVFNUlhXVVJSVlhYWFVUUFFUT0xMS4+US0pMSlNVVFRRUlJVU02OjEdMTU5PT01LTE1LSpaWlk5RUVBTUExPTkySk5mXjo6XmZWUlpxTVZSPe29zfoqNimV0c1ZaXmxtcmtdX2Vma2ZpZ2ViYIBcZmdoZmJgYmNpZG1oZ29raGVkZGdqamlye3F6dXF0b2hpYlpdYGpqbXBub29xa21ubWhkZGRgZXJpamptd3d0cG13f356dnt8dG10dXN0dG5qa2lhWmJ5kFFSUJaZp6ZbZWZhWbOyW62vtLVctaKJbGZ+UmZtbmxrbG1vb29ubQ5tbW5vbzg4ODc4NzY2N4g4Ejo7Ojg4OTo5OThxOXFubG1tbIVuCW1vcXFwcHFwcYU5hDoOOzs6cXI5cXJzczk7OjmEOF83N2plYWNmaWtrbGtnZGVmY19iZWhsbG5tbW5ub29ucHVxb29ubm1samtsaWprZ2hpa2lnZmZlZWdpaGZnam1paWxub3N3eHZzdnI8NHx7hURERoKBhIaEfUVITlFMRoRIgEdGRkRFSEdFgYJEhoVDRUOFRkhHR0dGRYtHSJCNjYiHio+OR0dISEdIS0tKS0hFREhJSYeGRYaJRkdGiYVERkREQ31BR0VGSEZBQkRGQkBERENDRENCQkJDREOCf0dKSUlLSUhEQH9BQEJCQYBBR0lDQoBEREhGg3tjSXVAU19agF1ha3BvbGVjZWpqamlnZ2NfZWhnY2BRPV5Wc4uXmqOikIuUk6GZkI2WnJ6SkoWChoZ9ioqGiYWKk5menpufn5OSkYWKmJCQmqakm5+lnqhXVFZYWVhVUU9Omk+eUVNTWl1dXaJHRJKcVFBOUZ2dmJhSV1hWVFNRVlpeW1dOUZiagJtRU59SU1dVVU+cUlxbWl5bXF9ZWVZXYmRlV1laj0tOVlpcWlZZX2FgYFZUXV1gZGNdYGBdV1JTV52AbltdYGNreYaMi0VKR4eJg3h+hICIjZKMiIxIh3lvY2NnanKBklFVWVdTUlCYm5ialpCMjoyDiY2Jfn97hIWBfH14bW1zJYKCgIB8fouPh4aHio6YlI2QnaKbkpGTj5aclZKXlJOamVGhoqOFUhekoVVUUZWNhYeKZ2dFV1dTT0xPUlFQUIRKgEtJTE5OTEpLSkhHTU1NTk1MS0lQVlZST1JaUEpNT0hDRlBQOjxPUlJRUlBJR0dCQkpRXVZQT1BSU1RVUlJPT1RSUVFSUU9PTVBVTk9OT1JQTkxJSU1LTE5OTktIRkdJT1VWUE1MTU5MSUhGRkpLTE1PTEpHRURGR0dFQIJNXmh0B4aaVVNSUFCpf4J+jn+Cfox/g36Kf4x+gn+KfoJ9636Df4R+hX8Dfn5/hH4Bf4Z+kn+bgAJ/gJR/jIADf3+AhH+KgMp/B35+f3+AgICGf5KACX9/gH9/gICAf4eAA3+AgIh/kIAKf3+Af3+AgIB/f4WAAX+YgIJ/iYABf4WAAX+FgAF/hICEfwF+nH+3fop/A35/fod/hX6Ef4R+jn8Gfn5+f39+hn8BfpJ/AX6bf4x+g3+NfgF/in6Hf7p+BH9+fn6FfwV+fn9/f4Z+AX3/fot+AX2GfoV/AgIEAICQjIuPkJCRk5iblY2Lg4KDhYyKhYL8/vr7/oGAgYCGhYKBgoGA/IGGh4OBgYSEgoSDg4eLio6Lg/z59f2CiY2H//b6goSCgICCg4OChIqJh4OC///6/YKEgvn2/YGCg4SB+Nbb39zr9ejx34DhmLKrmoyPn62yq6WhsMfIu6+kqoCYmaGksLGxsbSzsLvDs7S6tbrDxsS6vsrFv7a0uMDJxLvAw8W+xtPDur++urzCvrm8wcfNzMW3vMXHxb++yM3Pw8HAwMzRw7m3tLO7w769x8fHycnBxLOdkZ+11fqEgomHi4yIgPL8gID+/oD58oCB/fnqzbe/8qXX8/j5+ff19YD29vf39PT29fj7/P3/gIGA//z8/v///4CBgYCBgIGA//39/fz9/fz8/f38+/z+gP/+/fv59vn/gYKAgIOEhISFhISEhYSEg4OEhISDgoKDgoODgf/8+vj5+/39+/Xx8vLw7+7v8vT09fXy8fP08e3y9fP2+fv7+/n6+fn5+Pr49VD19PL0+Pbz8/P29/Ty9ff38evw9vr6+fb3+fj544iH5t/59fqCgv+Fgv+BgoaFhISFgv+BgYCEiIOAgICDhIODg4KEhoKCgoOHiIWGhoL9gYWCgIGEg4SFhYKFh4SDhIaIiISAgYL//PuBgPuDhISC/fn+goKC/v2ChIKDgoD7+/2Cg4KBgoGChIaGiImFhYeKiYeGhYaIhoOHh4GAgoH69fj4+PP08uu9ivWYw+Dk5ODb2Njk7vH4+ff18/Hy8/X08+7kyZ3prrPZ5ubj5e7q6evygO7u7urv8e/v5+jo5t3p8Pj26evp8evr8PDn8frz9Pj5++vj5erz+ICBgYSGh4aJiIeJj46MiYiHg4Pz7/+FhYiLi47j6o7th4aC/4GDhfv6gpGTjpWOi4mHgIeBiYmKhYSB//j/gIKFgf3/gf+Ek5adnpiUl5SPiYmRiYeMkY2WgJOQk5CKjZCTjoyMjIuOh4Pz1casq7awtMPY8vz7gPv9goOFhIOGgv3q3tjNzeH06ev+7t7s7evi4Ozw+vrr39jRzc/N2ez+h4L9/f3u8fL18efn94SA/Pfy7+7o2uXq6Ofp5enw7+rr6uHg4Nza4uTk6Ovw7+zw8vf/gID/+ff2gP7x8fP4gIH7+vn1+vv28fDg3LT6ip6en6OjmpaUlpOTlZ+loaOopZ+emZeXl5CKi5Oaj42NioiOmZ2dm5yloaOlqq+uqKOjnp+glIqGhIaOkpScp66jn6Sen6Snqaaenp6hoZulrKmoqKOkoZ+UkJialpmipKSkoKGhoJ2WkZWbK6GZkKGmoqSlnpWUm56alpijn5qUk5CHh4mRo7jP3faChoeJioeIiIqPjI6AaWZkaGlqa21xdG9oZ2FgYGJnZGBfvMC9vcBiYGBfZmZjYmNhX7tfY2RiX19hYF9gX19jZmVpZ2K9t7K4X2RnY7y1tV9hX15fYmJhYGJnZmNeXba2sLRfYWC2s7lfYGFjYbaZnqGer7iqs6VamWd6cGNcX254enVzcHyPj4N6cnmAaWpxdH17e3t+fnqEi3x8gXp/hYaFf4OOi4Z8e4CFjIV+goWGgYaQhX6CgYCBhoSAg4eKj46HfIGHi4yHhYyOj4eHhoaQk4N6enh3gIiEgIaGhomLh4x/bmVxhZ65YmBnZ2psaWK4w2Nky8ljwbtjY7+7rpeFhadxk6aop6elpKQlpaSkpKKjpaaoq6ysrldYV6yqqaqrqqpWV1ZWVlVVVamop6empYangKanqVWqqqmpqKWkp1VVU1NVVlZXV1ZWV1dXVlZWV1hYV1ZWV1ZWVVSnpqakpaanp6WhnZ6dnJuam56fnqCioJ+foJ+coaSipKeopqWkpaSkpKOkoqCem5ufo6Ogn5+hoaCeoaKjoZ6ipqmqqaanqKammVpTm6W1ra5bW7NfXLRbgFxhYF5fYF22W1taXmJdWVpZW11dXV5cXV9cXFtbYGBfYF9bsFtdXFxcW1teXV5eXlteYFxcXF5fXltXV1mvrKxZWa9cXV1csauuWltbr65aXFtcXFuwrq9bW1taXFtbXV9gYGFcXF5gYF9eXF1eXFhbW1hXWVippKipqaaoq6iHgGKsZIGSlJOSjYqKlp2gpKSioqCfoaGioaCclYRmmnqBnaurqaqwrq+vtLCxsa6ys7GupaSko52osby7q6ikrKaqsK+mr7aztru8vK6oqauyuGBhX2JjYmNoZ2ZocG5saWhnZmW4s75kY2dqam6unV6qZWZjw2JjY7m4YGpsa3NugGtoZmFpY2lnZ2NiYMG8wWFhZWO8vWDAYm9xd3hzbnFwbGdocGdkaG1qcnFucW5pbG5xbGlqaWptZmO6opV+e4KAhJKgr7W4X7m7YWJjYF9hX7innZmSkqKxpqe4q52ssK6koa6xt7ero56ZlpiZorHBaGTAvbmnqamqpp2gr19cgLW0sq+uppqnrKilpqKmq6yoqaqkpqelo6ipqa2wtbOur7C3wGFgwLi3uL+0s7a7YmPEw8K8vr23s7GlpIOmXGtqaWtqY2FgY2FiY2ptbG50cWxrZ2hqaWRgX2NpXl1fXVpcYmRlZWlxbW5ucXZ3c29uaWtrYFpXVVhgZGZrcnZuVWtvamlramxsaWloampmbnJwcHJvcGxqYV9lZGNnbXBxb2tsbGtnYF5iZmpkXWxwbXBxa2ZlaWtnZGZwbWhjYmBYWVtkdIWWoLJeYGFiZGJkZWdqZWeAVE9NUVJTVVZZXFlTVU9OT1BVUk9Om5+cnJ5SUFFQVlZTU1NSUZxPU1NPS0tOTUxNTExQU1JVVE+YkoyTTVJVUJKKjUxOS0lJTE1NS0xRUlFNTJOUkJVQUlGZl51QUVNUUpl8gYN/jJeMlolHeVRjX1ZMTFVbYV9bV2V2dW9nXGKAUlRaW2JiY2NlY2FqcGNkaGRob3BxaWx4cm1mZ2ptdGtkaWtsZ255bmhramtsbmxqbnN2enlyaG10eHhybnR3enJ1dHR+f3FpaWdnbXVxbHFycnV2cnhrWlFccYumWFVbW2BhXFSdq1latbNZqKFYWq+xp5B6bnpOY29vbm5sa2wZbnBxcW5ubm1ub25tbTc4OXFvcHBwb283OIQ3Ejg3cHBubmtqbGxsbW1tbG1uN4RvDG5tbG43ODY2Nzc4OIQ5BTo6Ojk5hDoBOYU4Djdsa2xrbG5wcG9raWlohGcFaWtrbW6EbApraW5xb3Fzc3JwhG0abG1ubGtpZmZrb25samptb25sbW1ubG1xc3SEdYB0cG9oPj97d4mBgENEh0tIikVGS0tKS0xIi0VEQ0hMR0NERUZGR0dHRUZIRkZFRkpLSUpJRYdGSEdHSEdGSEZHSEhFR0lGRURGSEdCPkBCg4GBRUSERkdGRYWAg0ZHRoaDREVERERDf3x9Q0VFQ0VDQ0VISUpKRERHSUpJSEdISYBEP0JCPz9AP3h0eXx7dnd6fGZMfkZXYmFgXlpZWmNoZ2hkY2doZmZnaGZkY2FXRGtec42am5mboJ2dm5+ampqVmZmVkYeDhYmEjpWio5OQjJGMkJeWiZGcm52gn52Mh46Sm6FUVVRWV1dXWVhWVV1cWldXVVNUlo+bU1NYXmBlmYB2TJNVVVGfUFFSlpRPWlxZYFpXVVNNVVFZWFpWVFKjnqVUVFdUoqRTpVVhYWZmYV1iX1pVVl9XU1VZVl9eXF9bVVhdYFtXWFlYW1VRk3xyXV5nZWdyfoyRkEqPkUxMTEpKTUqNfXRwaWl8j4F+jYN5h4mDeXSAhY2Og3x7eHd5eYCDlKdaVJ6WkIOIiYmEe3uLTUuUlJKNioV9ipCNi4yGio+RjpCSioqLiYeNjo2SlpqYk5eYnqVTU6Wgn5ylnJudolVWqaimn56cl5OShIBhdkVQTUtLTEhHRkhISUlOUE9QU1FOUVBOTUxKR0dLT0RDRkNAQkZHRkRFS0hKS05TVGdQTU1KTUxCPT8/QklMTlNYW1BLT0pJS0pMTEtNTU9PS1BTU1RWUlJRT0hFSktLUFVVVFRRUVBOTEhER0tQS0JNUE5RUkxHRkpNS0pKUk9NSkpIQ0ZKVGFxgIqeU1VVVldUVlRVV1FTlX+Ffot/AX6Sf4R+hH+Dfo9/hH4Gf39/fn5+hX+LfgF98H6Ifwt+fn9/fn5/fn5/f4d+ln+DgId/iICPfwGAiH+cgMt/gn6EfwaAgH+AgH+IgAF/m4ABf5mABn9/f4CAf4SACH9/f4CAgH9/hoCDf56Ai38Bfpt/tH6Tf4N+hn8Nfn1+fn9/f35/f39+fpJ/g36EfwR+fn9+o3+NfgN/fn6Hf6B+gn+LfoJ/pH6Cf4l+gn+MfgF9/36Mfox/AgIEAICMjY+QkI+LhYSEhYaHhoaEhYmPjouD9fD1+Pj09vb4gIKEg4KB/YGJhoCBgICCgoODio6QkY6HgoeFhoiFiIqD8+z5hoaGiIaFhIKCgYOFgoGA//z8gYaJhv769/mEjoX15NLa8fqDgfjZhNWKn5yenpKZmZ6jqLG0rLKurLewqQWlpaOqq4SneaKnrbKurrqzsMDAvcPLysrGubG0tr+5q6ivtrawsL3Dvrm7x8zPzMfFyMjAvLq/yMe+vMnIvry8xcPDxcnCxsnIzMzCura2v8PKzcbCwri3wb6woq3F2vaAgfj0gYeFgICDg4H79PX39/n594D008LL8pzG6Pb19vaE9ULz8/T19fT19vb3+Pv4+fn39fT29/f5/fv49/f29PT3+vv9/vz49fT09PPy9PPz8/b6+vn5/fz6/YCAgYGCg4GAgYOFhICDgoKB//77+fn5+vv7/Pr28uvu7efg5ers8PDt8e7r7fL09PXz8/P09vb4+vnz8vHy9fTx8/Lw7e3z8/b38/P2+Pr69vb29fPy8/Hx8fL2996AgdHggoKChIiJh4iIhoWEhYSCgYKEhIGBgoCAgoODhIOCgoODhYeIiIeIiomEg4CC+IOEhIWFhYSFhIKBhIeIiYSBgoGBgoGBgoKCgYOEh4mHhoaEg4SEg4KBgICDgoOFhYOFhICBgoH/goWFhISEhoWEhYaGiYiBgYD//Pn+gIGB//Xx9PT258CUzLaPxdzg3d3e3dzc4Obt8PPz8/Hx7ezv7dmyi9m5v+Dv6OLn6YDc1Nzp8u7y+f+A9fPt4uXr+PTe4NjM1e779vfz8vDx8/v++e3w9/f28feA/Pr9+/Tx9YKHhoaFhoqMj5CUk5GOj4uJiIiHgv//gIGAxu2bgYmIhYeJhIeD/IGTi4KKiIaAiI+Sk4mEgf74/IGDi4aEg/r094GIjI6KhouLioj9/oCFiIuPi4mNjYWFi4qK+97Oy8G8u7i2tsXS5v79+O/k4+bj4+fj2dnY3+Hy8ubi6vTv5+ja3uLh39zf5+vo3tjm7+3u+/Hq7/6ChIDw2c3Nv7e0uL7Aytfq+ICC+Onw9fv+3tHV1Nbn8PPq5PH7/e/l29nf3d7e2tXT3ujx8u/p6Ubv8PHz8vDw6uXj6+vq7O/r7erq5eru8ffoo+D/jZKYnp2bo6ajn56eoJ6eoqamm5KUlpiXmpyYlZiRi4ySlpOTmaKrqqmshK80rq6wtbazsqWjqqqknZybnqKlpqSorK6wr62pqqekoKCmp62spJ6bo7XHuZ+bmp6gpaimpoaqL6ignpiXm5qan5qamJKOjIqIgoKLl6O1w83b7PyDhIWFgoKHh4iLiomIjJCQjo2LC2ZnaWtramhiY2RlhGSAYWJmbGpoY7izub29u7y7vGFjZGRkY71fZWRfXl1cXl9gX2VpamxqZF9kY2VmYmVmYLGstGFhYmRiYWBeXl5fX1xcWrSytV5kZ2S6t7S3YWhhtKaYobS7Y2K8plyLW2tpbG1jaWZobXJ7fHh9enmEf3h0dHF3d3Nzc3Jvc3d7eHaAgHl2g4GAhY2Mi4d9d3p8g390cXZ9fXh2gYeEgYOKjZCMh4aJi4WBf4OKioSDjYp/foGKiIiMkomLjYuPjoaCf3+EhomLhoOEfX2Gh350gpihsl5fuLVhZWRgYGNjYr+5uLm6wMPCZLydjI+obIidp6aoqKelpaWjo6WmpaWlpqUnpKWno6OjoqGhoqSkpaimoqGhoqChpKamp6inpqSioaKhoKKgoKChhKUEp6elp4RVeFZWVFJTVVZWVldYV1VUU6OhoKGhoaKjpKWjoJ2WmZmVkZabm56enJ6cm5+joqGhn6CfoKKioqSkoqGgoKOgnJ+goJ6doaSpqaWio6KkpKKioaCdnp+dnZ6fpKWYWVWNnltbXF5iY2FiYmBfXl5eXFtbXl5cXFxbW4RdB1xbXF5dXl+EYApiYV1cXKtaXFxdhF43XVxbXV9fX1pZWVhXWVlYWFlZWVtcX2JgX19dXFxdWlpZWFlcW1tdXVtdXFlaWlqzXF1dXFxcXoRdgF5hYFpaWbGsqa5XWFmyrKanp6qhhGiNeGGEk5OQkJGRkI+Slpudn6Cgn5+bmZyeknlhmH6GprOspaqtpZ6msLeztLm+X7Szr6irrrizmZuXjpSvvrm4sbCxtLe8u7etr7a2s6+3YsC9vbu3trhhZWVmZGRobnBxdXRxbXBsamhngGZjxcVjY2KToWtgZ2dlZ2lkZ2K5X29oYGhnZmBnbXFxaWZivrO2X2JpZWNgtbC1YWhrbWhla2prar6/ZWhsb2tnbG5naGxqar2nmpePiIiIhoSPmai9urSso6KloqKnpp6dnKOjrqyjn6myrqeonKCio6Keoqmtq6KdqrCtrritgKaruF5iXq6ckZGIhYOEh4mQmam1XV6yp66xtbecj5KSmay1tqyms728samgnqSiop6Zl5umrLS0sq+wtre4t7Kwsa2pqLK0sLCwrK2rqaGjpamtoGyNql5gZGhmZGttbGpra21sam1yc2pjZGZoaGppZGJlYF1dYWVkY2dtc3FwbnJ2dnV1dHV3eXp4eHBvdXdxa2pqa25wcG9xdHV2dXJub25samxwcXZ0bmpobHiGfGZjY2VobG9tbnJyc3Nyc29naGNjZmNjZmNkZF9cWlpaVlVdZm97hY+cqbdhY2RjX19iY2NmZGNiZGhqaGdmA09RUoRTJk9OT1BQUVFRT09UW1pXUpmWnaKhm5ubnFFTU1JSUZtOVFNOTUpIhEmAUFRVV1ZRTlJRUlNPUVJNj4mRT09PUE9PTkxLSUpKSUpJk5KXT1RWVJmVkpZRWlOWi36El6BWVKSSTG5MWVdbXFNaUlNWW2RlYmZhYWxmYF1dW2FhW1xbWVVaYGNfX2hiYW1tam95eXh0aWJlZm1pYF9hZ2VeXmtyb2xsdnp8eXWAc3V2b2pobHR1b254dW9ubXNyc3V7c3V2c3h3cG1ra21tb3Fsa21nZ3BwaGBtgYyhVlekn1VZV1RVXF5csKmnpaerrq1bqYx9dXtKXGlwb3BxcHBxcG5ub29vbm9vbm1sb25ubWtsamttbW5wbmtqaWppam1vb29ubWxqamttbGtEbWtqamtvbm1sbWxsbjc4ODg5ODUzNDc4OTk6OTg4NzZra2pqamtrampra2lmYWNiYl9iZWRlZWNpaGdpbm9ub25tbGuFbEBpaGlqbGpobW5vbWxubXBycG5tbGxtampqaWdpa2tsbG1wc209OGB1RkdGSU5PTU5NTEtJSUhGRUVGRkRDREJDhEY4RURERkVHSktLSkxOTEdGRoJFRkdJSUlISEhGRkhKSklEQUFAQEFAQEFBQkJDRUhKSEdIRURFRUOEQoBFREVGRUNFRUNDRESGRkdGRUVFR0VGRkZHSkhBQUGAe3qBQkNDhH14d3d8d2BKYVJAWF5dWlpbWl1eX2FjZGRjY2JiYF9iYVxPPmdmdI6ZkYmPk46LlJ2hm5qdo1GZmJOLjZCemoOHg3l8l6SenJOTlJiepaSej5GYmJmSm1Wno4CnpZ+fo1hcWlhWWFpeYmRnZmJeYFxaWltZVaioVVRTfXlUVFpZVldYU1dSmVFgV09YWFdSV1pgYltZVqafoVNUWVVVU5mRlFBYW1tXU1hYV1ifoVRWWV1aWFtcVVNXV1aahHl6dG9vb2tocXaBkZCMgnd2eXh4fHx4enqCgYyLgIB5foN/e390dHV3eXh9hYqFfXmEi4eGjoN+goxISkaDdG9yaWdnZ2lrc32Klk1NkYaNkZiegnJzc3eJlpqRi5ikppmRioiNi4qIgHt7h5Canp6amp6fnpyXlZaSjoyWlZKUlpGSj46HiYuQlohUX3RCREdLSUlQU1NSUVBRT09RUxlUTUdKS0xLTEtIREVAPTw/QT8/Q0hOUFFThFVpVFRTVFVVVk9OVFROSUtLS01PUE5QUlNVVFNQUFFSUVNWVVhVUE5OVF5pXUhGR0xQUVNSUlVUVVRUVFFLS0dGSkhHSkZHR0NBP0BCQUJKU11qdn2IlaBTVVZVUE9SU1RWU1BOUVZYVlJPln+JfoZ/AX6af4N+j3+DfoR/hH6Df4Z+Bn9/fn5+ffV+BH9/fn6If4h+AX+FfsN/k4DOfwN+fn+sgAF/t4ABf5GAhH+DgIl/gn6af5J+AX+gfgF/h36Vfwh+fn9/f359fol/AX6Pf4N+hn+Dfop/gn6Nf7t+g3+OfoJ/v36Cff9+hX6TfwICBACAkJKRjomHg4KDhIaFhYSCgYSSlI2FgICA/vTv8fP5+vyCgvz/g4KEgoKDiYqFhISFhYSFh4uLiYiEgIGDgf76+fyAgIKCgoGBgYKChIOBgYGFiIiJi4qHg4CDh4qJjIz/7/T0+YGLiPXYi7/vk56voZCWmKSdl5eUmKOvt6+ep7CAr6CXnZ+foqOioKSmpaKjoKStrq+zvbuuq7fIxcK1t8a8rbGzrKi1sK2xv87W0M3Mys7NwLy6sqioqqyssLm2qqKsucXExMzIw7/OzcTAtry/v6+moZ+orKyur7a6s6CUlKCwyeb6+fz39vaBgP2AgYWF/P6EhYL5/IGD89S+2IdjqMzq9/b49/f6+fX08/Dw8fPy8vT19PTz8fP09PLy8O/t6+vq7O7x8/T09fT29PLy8vDu7u/t7e3u8fDv7ezu7u3s6+zu7+/x8PH09vf29fTz8vT29/b2+v39/Pnz8/b49vX2hvdz+Pr6+fv8+/j29PLx8vf4/P37+ff19fb28u/x8vX4+Pj38O3s6u/z9fT29vX08/P09vj7/fzghoi4yISDg4eEgYODgYODgoH++4KFhIWFhYSDhYaGhoWHiouHhoeIiIiKioqJiouJiYiIh4iJioiIiImHhoSDDYSEg4KDgoL9/YCBgYKEg4CCgYKCgoGDhID6/oGAgYGDhISEhoaFhYeJi4qJiIqKiYmKiYOBgv379/r4/IGB/O3QrpaUqdSAhPfn1LuopKCbl5idp7PAydbq7+rau5fsuqzI3+jr5ufw8+TV09PS2uHp8e3s7ujo7+/n4d/p8e/h1tzw7PL17uz39vHn4eHc3IDp8PDs6unt7fj/gvz8gIOFh4mKiYuOk5WZmJiRkpKRkI2LioiKioeHhbXYl/uMiYqPj4mIiI+WlpaSj4yNioSFiIGAhIaF/Pr6hID8+4aPkpCNjYCHiISGiIyPjI2E7d/Pua+rpKGotL3J0+L18ejw8vDy9Pnz9IKA9/v9+/n+8YDx7ebb2+Dq7u3w6uTt7ube4NrW1t3s6OXt7+zp7/f2/oKC9vTy8/L/+uPk7vf8gfrt4dXJtre7uLjDyc7V4fD8+PDx7ubu8+7v/ID39O/w6efo3drd3OLm6u/3+Pb07+rq7e/n0tXq7+3p6/D3+fb3/Pz945LUipaVl5ycnJ6krYCsopuamZyiqKyqoJKUmJuZmZ2foJ6goqerqKSipqejoqaoqayrp660sq6pqqupqK+1t7KwsK6yta+rr7Owpp2blZCKjpORmJubn6Ghp7K4trKto5uYoLGvsaqmnZydpaqnnZ6dmpiQi4iIiJCD8Y611+6AiYuHhIKCg4aHiIuKiA+FhoaFiIiFhomNkY+Sko+AbW5saWZkYF9hYmRkY2FfXmJub2lhXl5fvrSsrLC1tblgYbu9YWBiYF9fY2RiYWFjYmBhZGhoZmRgXV9hX7y4trheXmBgYF9fXV1dXl5dXl1hZGRlZmZiXlpeZGdma2q6q7KytV1mZbunY4GmZW5/cmFlaHFrZmZlaXJ7g35weIFWfnBqb3BucHBvb3Jzc3BwbG53eXt/h4R7eYKNioh7fYiDeHp6dHF7d3Z4goyTkZCQjY+QhYKAenJzdXd2eoJ/c2x1go2LipCMiYSRkImFfYKEhHl0cXGFdlN7f3xuZ2ZwfZOxxMLBu7y+Y2LDY2NmZ8PGZ2ZhuLleYLKZiJdccoqeqKiqqamrqaWkpKKioqGgoKGioKCfnZ+hoJ6enZyZmJeXmZudnqCho6Oin4Seg52EnICdnJuamZydnZ2cnJ2cm5qanJ+io6Ghn56dnp6fn5+ho6OjoZydn6Ggn5+goaCgoaGipKSjpKWko6GfnJydoqKnqainpqWlpaSgnqCgpKamp6Wfm5mWmp+hn6CgoaGgn5+ipqenqJxeV3ONY2FfYV5aXVxaXFxbWbGuW15dXl5fXgFehF8gXV5hYmBeX2FgYWNiYmJjZWNiYWBgYGFiYWBgYF9eXFuFWglZWllYqalXWVmGWoBZWlpbWVtcWKuvWVhZWl1dXFtdXl1eX2FiYmFgYmFgYGFgXFpcsa6qrKuvW1uyp5J7amh4llpesqaXgnRwbmllY2VrdICHjZydmZB+ZZ+AepOjp6ejpbGzp52enp+lqrC1rquuqaeur6agoK2yraKZorSvt7qytb+6s6mhpKCgroC0sq6wsbe3wcdkwMFjZWhqbG1rbXJ3eHp7eXN0dHNycG5tbGxrZ2dnh5hrumpoaW9vaGdnbXNzc25tamxqZWVnYV9jZWO8u7hiYL25Y2tubmxsYWdnY2VnaWxrbWWzqJuJgX55dXmCipOYpri2rba4tLW2urOzX163urq3tryxsoCupp6fpa2vrK6ppK2xqqGinpqZnquloqqtq6ittbW7YGC1s7Gxrri0oKCpsrdetamelY1/gIWEg4ySlpulsr25sbOwpqyyrq65XbGvra6oqKyhnZ+coqirsLi5t7Suqqyws66bnbC1saursLe5tbi9v8SsZYVbY2BiZ2dnaGx2eIBuaWloaW1ydnRtYWNmaGZmamttbG1vc3ZzcHBycm9vcXJydXNvc3h4dXJzdHJxd3x+eXd2dXh7dnJzd3RuaGdiXllbYGBlZmZqa2pud3p2cnBqZmRreHR1cGxlZWZrb25oaWhmZF9bWlpaYVidXn+dr11lZ2VhXl1eYWJkaGdlYw5kZGNlZWNjZWltbG9wbYBcXl1bVlRSUVNTVlRTUExKTltcV1NRUVKjmpSWmZ+goVJRmJdMS01MTExQUE1MS0xLSklMUlNQT0xJS01Lk4+NkUpKTEtLSkpKS0tMS0pLS05RUVNWVVJOTE9VWFdbWpyMkpSYT1lZpJBRZYhXYHBgUVZVXllUUk9UXWVtZlhfZYBkV05VWFdaWlhXWltaWFlXWmJkZmdtbWdmbHNvbmNkcWthY2ReWmRiYGJsdnl3en19gH91dHFqYWJjZWNma2lhW2JtdnNxd3Rybnp4cW9obnFxZmFeXGFiYWNkaGpnWVJRW2p+laSmrKekoFVVqVdYXFyvslxbWKWlVVihiXJyQWpNXWpwb3Bwb3Jwb3Bvbm9ubWxrbG1sbWxqa2tqaGhmZ2ZlZWVmaGlpamttbW5samhpampqbGxsa2xvbmxqaWpqa2tqamtraWlpa21ub21raWloaWlqa2ptbmxraWZnaWtqamtsa2ppaGhphGqEbBRqaGhpa2tucXFwcHBvb3BubG5vcoRzPm5raWRlaWpoaGhpamxubW9wcXN1bD0zUm5LS0pMSERHR0VGR0dFhoJGSUdHR0hGRkhIR0dGR0pMSUdJSktMhE44T1FOTUxMS0xNTktKS0xKSEVFRENDQ0JBQkBAenxAQUFBQkNERENDREREQkVGQX6DQ0JDREdIRkWESIBKTExMSklLS0lJS0lDQkOAf3t+fn9FRYZ7aFlOUF92R0eDfXRmWE9KRkNCQ0RJUFNWYWNjXU9CcWRle4qNjomIkpmKfoGGiI6SlZqWkpOPjZOTi4aHk5mVh4CInJmjqJ6bo56YkIqOiomUmZmVlJScnaexW6yuWl5gYWNlYmNmaYBpa2prZmdnZmVkYV9dXl9cXVxua1WjXFdYXl5XV1ZeZGRkX15aW1hTVFhVU1ZYV5+gnVJRn6BYX2FeW1xQVlZRVFVZXVpdVpSLgHBmYVtYXGdudXiEkZGLk5KRkpOYk5VST5GPj46OlIyOiH93eX2BgYCCfnyDhoN+gHt5dnqHgIB7goaGgoaMipBKSomIhYWDjYt6eH+GikiLgnt2cGNkaWprdXt9gYeUnJSKjIuDipCKiZZNk5ORkIiGioB9gIGKkJWbpKWgnJSQkJKUj319j5WSjY+Xo6aho6ipqo5MWkNKSEpMS0tMUFlaUk1MS0xRVFdVT0ZKS0xMS0xNT1BRUXhQUlFRU1VUUVFSUlFTU1FVWFdTUVJUU1BSVFdVVFRSVlpWUlNXVE1IRkI/Oz5ERUlIRkpMTVJaWVRRTklFRU1bWFhSUEpKSk9RUU1NTEtKRkRERkZKQXZKZoGST1ZYVlFMS01RUVNXV1VTU1NRU1NQUVJWWlpeX1yYf4h+BH9/fn6Zf4R+nn+Ffgh/f39+fn59ff9+A39/foR/CX5+f39/fn5/f4R+/3+ifwN+fn+NgIJ/tYCCf5GAgn+bgIZ/goCIf4KAln+8fgN/fn6cfwR+fX5+mX8Hfn5+f39+fpF/mX6Cf6d+gn+MfgF/m34Bf6p+AX33fgF9hH6dfwICBACAjIyJhIWGgoGDhYaIhoP/goiMjIiIiImHhYSFhoT99PmAgICBgP79+vyBhIeIioqFgYGEio2IgoCBgIKCgIGEg4OBgP+Cg4L+9YCGhoSB+PL6gPyBgv399v6JiYyNjouEgoH9+4eLhdyP1f2kpaOenJuQjIOApLCroKmZi52IgpeAop+erKqttbSxq6ior7u4vMC4vri0uLewtLWvucXAvru8tLm6tbi/wcLDwcTAvKylq7KzvMa+qLC7vriwqKuupZqlq7GysrW5srC0u7GwtLrExsXDxcPCwsHDxMXCwL26vrill4+ar8HU4u3u8O/z/IGAg4eIhIaKiYeIhoSA/YIl8tbI4v6Xudnu+Pr7+fj29vf5+fn4+Pj19PTw7vP09fPy8fP09YT0QPPz8fHy8/T08/Hv7Ovv7uzs6eTj5Obp6ujp6evq5+bm7O/v7u3u7u/x8fPx8fDy8/Hu7u7z9/r7/v+A//7+/v+GgID9/oCA//z7/v/+/fr4+Pj18/Du7O3v8/Py8e7t7vLz9PT5/fr4+v6AgP/8+fX2+/bfjpGRmu/9goL/gIH7/v6Bgfv/g4SDhIaGhoeHhoWHh4SEhoaEhISFiIqMi4aFhIeIh4aEhoeFhYP///36/4GAgYOEhYeFgYGBhIaDgYD59YD5gIWGhISGh4iHhYSEhYiJh4T8+/3+/4KHiIWDg4KFiISBg4WEgv/7+vf58e7euJybprfkhIuKhYaIioyNjIqHhYWEg4H35s6h1Jiyvbu7xb2wsL3BvsbL1Nzl8ODW2tnb4OPr5ern2eLj3t/a3OLg3Nri6/T07/uD/v/69/Pn7IDv9e3t8err5u7u7/T9/ICGhoWCgYSFho6YmJqXk5KUlZmalJSVkpCUj4iIgr3ljfSRkY6Ii42Nj5GPjouGhomFiIqJg4eHiImFgID9hYeA7ebn5trVzcLBv8G9vLu6wdLf7ff68Ofc1tra2tzc2NXY6vbz7uzv8e/r8+zm6enUzIDN1d7b5/Tv5Or3goD58u/r7PLu5ODf5PT8goWDgfj29P2EiYeD+ejo4ujq7PT3+oD08+vg6/Hw6u/q4uDo6tvQxMDGw8bEv8HM0NXi7/qChYSCgoP/9efg5ezv8PDj5uv0/4eGgvnq4eXk5+jq6e7m4+br68Ty3qCqqLCvqqKal4ChrLO7urSvrrCxsLCqoJ6clpmgop+QiIyVl5uhoaOpqKWioaWoqa2wqqiopaCgpamqpKOoqaelp6yppqSipqaVi42Rl5idnJWSlJ2nq6iioaSak5OXnZ6bnqGdnJqOiIuOkZWbpKqyu8jT2uj1+fjdvLGzw+CBh4eJiYaFhIaMiRGJi4qIipCSkIiHjZGOi4yPjYBqamdjZGRhX2FiZGZjYb5iaGtpZmdmZmNgXl1eXrSutF9fX2BgvLq2uWBjZGVnZ2NfX2JmaWZiX15eX19dXWBgYF9fvWBhYLyzXWFhYF+1r7dduF9ftrWwuGZmamxtaWJfXre0YmlnrWmPrXR0cm9ubGJgWFVxfHpxemxeblxXaIBxbnB6eHp/fn15dnh8hIGDhX6DfnyBgHyCgXqCjIeEgIJ7f397foSHh4mGhoKBd3N4fX6EjIV2fYWJhH12eHlvZG91enp5en14d3qAeHd7f4eIh4eIhoSEgoWIiYeFgX+Df3FpZHCFlKGqtbe5t7vDZGJlampnZ2poZmloZ2LCZUq7oZSltmuDmKWsra6trKqpqaqqqqiop6Siop2boKKioKCfoaKko6OjoqKhnZubnZ+goJ+enJudnJqamJSTlJaYmZmZmpycmpiXmoWcFZubnZyenZycnZ6cmZmZnJ+ioqSlU4SogKlWVVVUVFSoqVRVqKSjpqinp6WkpaakoZ+enJ2eoqKhoZ6dnZ+goqOmqKWkpadUVamop6OjqKaZYFxaba+0XV2zWluxtbddXrS2XV5dXmBgX2BgXl1eX1xcXl5dXV1eYWNmZWBfXmBhYWFfYGBdXluvr66ssVpYWFpbXF1bWFdYgFpdWlhYqaSoV1tcW1tcXV1cXF1dXmFiYFyrqq6vslxhYV5cW1pcX1tYWVxcXLOuramrpaSafWhrd4SjXmNiXV5gYWNkY2BdW1taWlyzqJd1lWd+iYmJjYV8gJCSjJKXnqGpsqSdoZ+coKWspqmllp+hnZ+bnKCdmqCrsri5tsFlgMC/wMC4rbGzubKytK6uqbKyr7O/wWJnZ2hmZmhpam93eHl2c3J0dnl7c3F0cnF1cWxrZI6eZ7xvb2xnamtrbG5ramlmZ2plZ2lpZGZlZmdjX1+5YmRer6qrqqOfmI+OjI2IhYOEiZOgrbO1r6edmZ+fnp+gnJiaqbWzr62wsa+sgLSuqayrmZSVnaWirLazqq+3X121sK2oqa+tpaOipK+3YGNhYLazr7dhZWNht6alnqOkpq6zslqpqKSdpqytp66ro5+mq52SioqQjZCQioqSk5airrdgZWRiYWC6s6ijqK6xsrGkqKmvv2loY7mqpqqrra2tq66npqmsrIygkG1zgHB1cnFtaWdtdHp/fXZxcHN2dnZxaWdnYmZsbGpfWl1lZ2tubW5wb2xqaGtvcXN2cW9xbmprb3Fzbmxub25sbXJzc3Fub21gWlxeYmRnZ2BeYGdvcm9rampjX19iZmhmaWtnZmZdWl1fY2dqcXmBiZObn6evsbKcg3x8iKJgZGRmGGZkY2JkaWZlZ2dlZ2xubGZma25qZ2lta4BYWFZSVFVRUFNWV1lUUJRKT1RWVFVWVlNPTU1PUJeRlUxMTE1LlJWUlU1PUFFTU09MS01RVFJOSkpKS0xLTE5OTkxLl05PTpeQS1BQUE+Yk5lOmlFRmZaPmFdYW11dWVFPT5mXVVtamFdzk2NiX1lZWlFPSEZcYV9YYFRKWUlEVoBbV1diXV1jZGRfW1xjbWttbmZqY2BmZ2Vqa2Nsdm9saW1maGllaG5wb3FvcW9uZWJobGx0fHNhaG1tamZhY2VgWF5iZmdmZmdjY2ZsYWBkanR1dHJ0cW9vb3J0dHNycG5wbWFaVV1re4yUmZqdmp2mV1haXV5bWl1aWFtbWVeuWRCijHyChklWZW5ycnNzc3Fxh3AObmxtaWhsbm9tbGtsbG2GbCJqaGhqbG5ubW1ramxsampoZWRkZWdnZ2hoaWloZ2Zqa2pqhmgXaWloZ2hpZ2RlZmhqbGttbzhvbm9vcDmFN0dvcDg3bWtsbm5sbGprbXBxcW9ubm9wc3JwcG1sbG9vcHBycm5tb3M6OnNzcW5vc3JpPjxFUX6FRkWFREWFiopGR4eIRkZFRoRJgEpIRkhIRUVHR0VFRUZLTVBPSUhHSk1MS0hJSkhIRYSDgYCGREFCRUVFRkRAPj9CRUNBP3p3fUNISUZFRkZFRUZGRkdLTElFfn6DhIZGTExHRERERkhEQkNFQ0J/e3p2enV0bVlNUFtjd0dKSURFRkdISUhEQD0+P0FEiH9wVmxLgGRyc3F1bmRmcHBscnmBhYuRgnyEhoiNkpiPko19houIh4KEhYB9hI+Yn6CerVuqqaaln5SanqSdnJ6WlI2WlpebqKpYYGBeW1pcXF5jaWlsaWZlaGptbWRhY2FhaGdhX1dxbU6bXl5cVltdXF1fXFxcV1VXVFlaWVVWVVZXVVFSgKBWV1GRjJCQh4R8cnJwc3BsaWltdoGLkJGKhHp1eXuAhoiDgYOPmpaRkZOVk4+VkIuMinp2eH6EgoyVkImNkkpIiYSCfoCFhH19e3yGjUpNTUuMi4mQTVJRTo9/fXd6enyCg4REf4B+eYSJiIOIhX58gIJ2cGpqbmlrbWpqcnd5gIKOmlBRUFBQTpeRh4SMlZyem4yMjJKjWVhVnpCLjo2Qk5eWnJWTlZeVdXxqU1hWWllWUEpIT1dcYmBbV1ZXVlNUUU1NTUlMUE5MRUFFSklLT1BTVlVTUVBSVFRWWlZUVVFOTlJUVVBOTk9OTlJXV1ZUUlFPQz5BQ0dKTk1DP0BGRk1SUU5OTkdEQ0ZKTExPUk5OTkZDRUdKT1JZYGZrdH2BiI6Pj31oYmBofk1SVFVUUVBQUlhVVVdXVVddXlpTUlhbWVZXXVqOfwF+jn+DfoV/hH6afwZ+f39/fn6Ffwd+fn5/fn9/hH6Jfwl+fn9/f35+fX3/foZ+jn8Cfn+Fft5/AYCFf4aABH9/gICjf4KAiX8Rfn5/f3+AgH+AgH9/f4CAf3+mgIV/kICDf5GAhX+PgI5/kYCEf69+AX+Vfp5/BH59fn6bfwR+f39/u36Cf41+hH+EfoR/in4Bf55+hn+OfoN/kH6Cff5+nH8CAgQAM4eIhID//4KJiIeFgYKA+v+BhoeFhIaEgPz9/YD+9/6DgoKEh4eLjIqGhIWGhoKDh4aGh4SGgIWB/oSEgYGBg4P88unr7fL9+/6AgYOEgvz6/Pn5+P2A//z5/oeLjYqEg4D/gYKA1YPM/pugnZmhnZKSlpWVoKafkJKanaattLipmZScn6Kor7OuqbLAsaakn6SknJWVqbCxt7mzub69wcXHyMrIwry3urGvsbS6ubOxs7m8vbi2d72+vLq4u77DtaSlop6ps7OsqaexusC+ub20sK+stLvLzsDAxcK8wMzLyMDBwru8vraqpKeosNf0gPv+goOA+/rr7v2Fg4CBhoeEgoKCgfbgyL3UgZ272vL8/Pv8//36+vn5+Pby8fT29fb19PX08u/v7e/x8fL1hfcW+Pj49/Xz9PX08/Px7+3t7vDy8/T19IT2R/X29vb5+/v59vX09vj49/b4+vv7+/r5+vr5+PXu7PD1+fr59/Pw8/r///v08fLt6Ofo7evs6+rs7e3t8fL1+Pj6/fz9/fz7hfxO+vv8+/379++4uPPl3/r9/oCFhIKBhIX/gIGBgYKDhIaHh4WA94CFhIOHiIaGiIiEhYeHhoWFh4eFg4KBgP77/IGEhIODgoGB/4D/gYOChIOAhIGAg4SEhoaJi4yLi4iIiIaEgoGChYT//Pr5/oaHhIL/+Pj7+vHv+fz+g/TXup6ip7bggYeIiYmKi42Ojo2KgPeEiYaD99+7oov32+D3iY2Oj5CPjo6Rj4X77t7Y19DHv7q6v8W7srq9u7Sus8HIztrh4eDa29zj6vX49PD4gPeA8ezq8Pv18vv49+7r6+7y+Pfx8oGIioyNjY2Ojo2Ni4mKi4yMjo+Qjo+TlZmamJaQiLbd/tiGhoX/gYKEgfv17fbv5dXPy8K3ubzDwrq0srGxsbO0u8jMz9zr+Pjw8YCE8u3q8PyA+uji6+rt7ezr7e705+HY09bd4ebk3t7f4tqA3uTi4OXl6/j2+PLq49/m7OHd5NzU293V2e77gIKE//v7+/n6+4D37+7w9/39+IGKi4L6g/nr6Orq8u3w/Pv06+Lq8//7+fnm6/nu5ePh29rXz7+2ubfBzMzP0c/P1Nrg6O7x9IGCgPr/gYiMi4iB+vLv7+73++im0tKAjpSQlJuAnaCcnaGorKqnp6Ohpqqura6poZmRi4yQkYmFh4uOkZecoKarqaeoqqijoqWsqaakpq2wq6uqra6ysa6ppKOkpKq0s7GvpJ2blZeYmpuckoiA9vDwgpWhqrGztru9v8fL1uHt+v37gPr19PHt6+7x8u/w94CB9+zhz7u5vdT0hIYVhIaGg4OJlZCMiYuNjImJiIeGiIiIgGZnZGLFxmRnZWRiXV5bsLRcY2ViYWJhXre3t126tbphYGBhY2NmZmZjY2RmZWBhZWRjZGNiZGRjX7dgYF5dXV9ftqylqKqtt7a5XV5gYV+2tLW0s7K2Xbm1srZjZWhlX15ct1xeXpxbi7Nsbmxpb2xjY2VkZW5ybmVobWxzd3t/gHRoZGttb3N6fXdzfIp9c3FtcnNsZmR0eHh9fnh/g4KGi42MioaFgXt+enp7fH9/enh8goODgoKHiIaDgoKChHpucXJvd39/eHNveYGGhoKEe3l5eH1/iYqChIeEgYaPjo2IioyIiIqCdXB2d36dtWC6vmJjYb6+srXDaGdlZmpqFWhmZWVkvaqZkJ1cboOYqa+urKyvrISoZqaloqGkpaSjoqGioqCenZ2foKChpKampqWmp6elpKGfoKGgoKCenJubm5yen5+gn5+hoqKhoqKjpqipqKajoaGhoJ6dnqCjpKWlpaalpKSinZucn6KkpKOfnJ6mq6uopKKkop6cnISdgJycnZ2eoaGjpqaoq6qrrauqqammpqSjpKSjpKShnn17o6Ois7S1W15dXF1fYLRaWltbXF1dX2BgXlmrWV1dXGBhYF9hYV1fYWFgX19hYl9cWlpZsKyrWFtcWltaWlqxWrJaW1tcW1lZW1lYW11dXl5gYmNjYmBfX11aWVhZXFuugKysrLNeX1xbsaurrq2opq2usFunkX9vc3eCoFtfYGBhYmJkZWRjYlutXWFdW6uXfG1hsZ+nvGhrbG1ubWxrbmxiua6inZuVj4uIh4qPiIOJi4mBd3qGjZSepqaloKGhpa2/xL65wGTAurWzuMK+vMbCvbKur7K3vr66uWNpbG9xgHBwb25ubmtqbG5vcHFycm9vcXN2eXd2cGqHlK6eZWZlwGJjZmPBvba+tquempiTjIqJjo2GgoF/fn6AgYaSlpijrLe4r69eYrCqpqy5X7urpKyprKyurq6ssKWjn52foqSnpqKioqaeoKalpaqrsby7vbiwqqWprKGfpZ2Vm5yVgJmrt1xeX7q3t7e2ubpfs6qoqa+zsaxaY2VftF6xp6Wnpq2nqra1sauipqy2srGyoKOup6Cfnpmbm5iKgIKBiZCQlJaWlpqgpq6ztrhiY2G7vl9kZ2VjXri0s7Kxur2qcoePWWFlYmNnaWtoaWtwcnBtbWlobnJ1c3RvaWRgXV1gdmJbWFpeYGNmaGtvdHJxcnNybm5vdHFvbW50d3JxcHJxcXBubGpqbG5zeXd2d3BqZV9gY2Vna2JaVJ6bm1Zlb3Z7fYCGiYyTlp2ksLu8uF22s7Oyr66wsbGvsbdfX7SqopSDf4GUsWFlZGdoZWRocm1oZWZoZ2SEZgNoaWiAVFZTUaWlVFhVVFJNTkySl01SVFFPUU9Lk5WWTZqVmE5MTU9RUFJSUU1NTlBRTU1SUE9RT05PUE5Lk09PTk5OUVGakoqLi46XlJVLTU9OTZWUlZGQkJdOmpeTmFRXWVZRUE2ZTlFTjVF2kldaWVZdWlJSVFNSV1xYTU9VVVlfY2iAXVJPVFRVWV9kX1licGVbWVNYWVNPTltgXmBjYGZra25xdHNxbGtoZmpkYWBiZ2diYWJpbWxoZ21ua2dmam9zaF1gX11iZ2hjYF9ma3BuaGljYGFgZ210cmtucW1qcHp4dW9ydG5xdG1iXGBhaomeUpqeVFdWqKmeoq5dW1laX2E6XlxbW1ytm4Vyc0BKVmRrcHBwcXNxcHFwcG9ua2tub21tbWxtbmxpaGdpa2tsb3BxcXByc3NycG5sbYRuBmtoZmdoaoRsBmtrbGxsa4RsPm1wcG5samttbW1sbG1tbGxsa2tramlnZWVnam1tbWxqZ2ltcXRycG9xcG5tbnJwbm5ucHBvb3Bvb3FxcnRyhXOAcnFwcG5wcXBxcG1rVlpvanaBgoRDRkVEREZIhkNERUVGR0hJSUhHQnpBRkZFSUpIR0lJRUdJSUlISUxMSEVDQkGBfX5BRERDQ0JCQoNCg0NFRERFQ0JDQUFERkZISEpMTEtJR0dJRkNCQUJFRYSBf36ER0hGRYh/fX9+d3aAgYOARHxrXVBTV19zQUNEREVISUxMS0pIQXxDRkNAeGtYTkqNg5CmXWBhYmJiYF9jYVaek4iDhH96dHBub3Nwb3V0cWtla3JydX6FhYeFioyQlaKnpKKqWaagnJugqqWirKikmZWWmqGsq6SiWmJkZWVkZGNiYmJhYGNkZWVkZGRhYWKAZGhqaWhkXW9ph4paWFahUlNVUZyXkpyVjIF/fnhxbm93dnBta2poaGpqcHl7fIOLk5aOjkxRj4yIi5VMloiDiomLiouMkJCWiIWDhIiLjZCNhoWDhXx/hYODiIiNl5SVkIuGgYOLh4GEe3J2d21wg49JSkySkZGTk5STS4uDgYKAh4uKhUdPUEmJSop/fH17gH2CkJGPi4KBg4mFhoh5e4WAfHt4dHZ3dWtlZ2Vud3h+goCAg4aKkJSWllFRT5mdUFZZV1VRnpiVlZOcnotaY2I9RElHSEtLTUpLTVBRTkxOTU5SU1JQUlBOSkdDQ0ZIQj9AQ0VISk1RVVhXVldXVlJrUlVaVlNRUldZVFVVWFdXVlRTUE9RUVRYV1ZWUk5MRkdJSkpKQz06bm1tPUxYX2NlZ21xcnZ3e4KLlpubT5aRkI+Ni46Rk5KTmFBQlImBc2FgY3aPT1JRVFZTUlhjXFZTVFVSUFNVVlZYWVeEf4J+iH+Cfoh/B35+fn9+fn6afwF+h3+JfoV/h34Bf4R+h38Ifn9/f35+fX3/foZ+Bn9+fn9/f4V+i3+Ffv9/mX8Dfn1+hH+HgAF/jIABf5iAg3+IgAN/gH+egIV/hICKfwGAiH+NgAF/hICFf4R+i3+lfgF/lH6efwh+fX1+f39/foR/oX6Cf4V+AX+1foN/h34Bf4h+hH8Cfn+wfgV/f39+foZ/iX6Cfdd+g32SfgF/jH6Cf4l+l38CAgQAgICAgoSDgoOEg4ODgoaJiouKioPy9v2ChoOBgIOKiYaEh4aGhYSFhIWEgoWJh4eKiIaFgoOC/oH/8Ojo5+3w8/j39+7u8evm6O3y7ejt/oD6/fv0+P//gfbw9v+C//r+gPb19Pn/wubWiZ6kr7Kro5udpqOhmJqknpmYm5mjraqpgKWhmpihn5Ocs7+1sqmsr6ijoJqWm6KorK60t73BvrWxrq2xub7Lz8PAwrmwrbS3vcC2rrG1uri2t73Bxb69uba1s6qpt7u5trOwsLWws7amoqSlr6yor7nFw8XIxMTGycjHv7u/w8fNx8G6r52WkpejqbHN5O729/b7//2ChoaEM4SFhPv/gv/79e/r5djLxt2Dma/I4/T6/f38+/v8/f38+vf28/P09fb29vLz9Pf3+Pj5+YT3Jvb39/j5+vr7+/3+/v79/Pv6+Pb29/b08/Lz9fb29vX3+Pf3+vz5hfgG+fj28O/yhvNo8vTz7uzs7vDv7evt7evs7/Dx8vHx9PX39vf49/f29vf5+vr+//38/Pz7+vv9/fz8/f3++uCFjq2q6PaAhISFh4eHhYWCgoaIhoODhoiJiIiIiYWDh4qIg/7/goL+g4eHhoeGhIOBgYKEg4CBgYKCgYGBgP+BhYeDgICEh4iGhISGh4aFhYSA9/iAgYD+gISFhIKDgv3+/fj48unx/Pn09/nu3sq4sbC1ze2DhoiKiYeHiYuLi4qHiIeEgfPUu6CK89rd9YiNkZKQjYmHiIyNjIuLio2Mi4uKioqJh4qKiImGhYeIh4iGg/76/ID28Ovi3NfY4N/a1dDS09LW0cnHyM7R19TOzMfHwLm0wtHOwcHDw8bOzcrZ4OLk5uTZ1tjg4N3Y1NjY0dbX2NLPyMfOntrZlre0s7e4vMLEzM7V3uTo5/f68vLo4N7c3eLe2NXb5O3t4Nnc5+Hf5trS1Nvd4+jz/oKDh4WB/Pj48YDs6+vl6u/v7+zq6uzu6+nn5uft7+fm7PSBg/vz8ufh4unl6fHv7Ozr7fDt7O/x5t7e5fGAgoOA/fb19ff8+vb9hIP6+Pft6Ojn7/fv7vHx8PHw7u3v+O/u8YCF+/Lr6PP9gPXs6u7k6PaAgID78Ojm5+PYz8nCwL26ubq+xs3LyoDKzczNzsy1/bzbiY6QkY+RlJiZl5mfm56joKu4qJudnpaXnaCkqKadnqahoJqVk5SVkpKRhoKFh4iYpKWhm5WOh4H+gPv5/IKIjZCQlpufoJ+bmJ2ksLKwt7y+w8rMzdPf5+fr7enp7fHx9/7++vXy+v6Bgfjv8/f8//z8gPz5gCaFhoOChIT77erw7+vl18KxrKzA2vOBhoiEgYWGiImGhoaFh4qLhoBbXF9hYF9fYF9fXl1iZmZnZGNer7G1XGBeXl1gZmVjYmRkZGNiYmFiYWBjZmVkZmRjY2BhYLxgvbGpqKWqrbC0s7Orq66opKiusq6prbpdtbazrbG5u1+zrrO7YLy3uVyytLS2uIygk19tb3h7dnBoaG5sa2VncGxqaWtpcXd1dIByb2hmbm5jaX2Hfn11eHt1cW5oZWlucXR1ent/goB6d3RzdnyAjJCGgoV/d3V6fIGEe3N1eoB/fX2Ch4qEgn57fHt3dX+Cf317eXl/en1/cm5ubnZzcXiDjYuMjYmKi42OjoeDhYiJi4aCf3luamhueXuBmq+4wMG+wcPAZGhpZy1maGfEx2XEwbu1sqygk42dXGl5ipynqaqpqKinp6ipqKWjoqChoaOkpaWhoaOGpQako6SlpKWEpiulpqamp6iop6enpqWio6SjoqGhoaOjo6SkpaakpKippaSkpKWlpaSinp2fhZ4Wn52foZ6dnZ+goZ+dn6Cfn6CgoKGgooejMqSioqKkpaapq6mpqaempKSmpaSlp6eoqJpaVmdwoKtaXVxdX2FiYF9cW19hX11eYGJhhGCAXFpeYWBdsrJbW7FcYGBfYF9cWllZWltbXFxaWVpaWVhZWK1YXF1aV1daX19dWlpbXFtaWllWpKVXWVmwWFtbW1laWa6wsa2spZyksK6oqq6mm4x/enp+j6ddX2BiYV5eX2JkZGNhYmFeW6uUgHBhq5yitmdsbm5sa2hmZmlqaWiAZ2ZoZ2ZnZmZnZmVpamdoZWVoaGdoaGXBvb23saujoZ+hqaeinpmbnp2fmpKPkJidoaCbmpeXkIuIlJ6ajoyOkZWcnp2nq66xtLKnoqGnqaagmp6emJ2eoJyclpabcZOSaISBfoOEhouNk5Saoaapp7a4sLGqpaCdnqOgnJmgqbSAtKWfo6ump66hmpugoKSosLpgYGFfXbaztK6loqOhqa6uraqpqq2wrq2rq6yys6morrZjZcG6taiio6mlp62tq6qpq66vsbOzp6CfoqxcYGJfuLGwr7G1tLG4YV+zsrOqpaSiq7WtrLKzs7Kuq6ipsaelqFlesq2moaeuWaumpaqApKm1X15etq2moqOjm5SOhYKCgYGChYqQkJOVmJaYmZeFsoGYYGRmZmJkZ2pramptam1wbXN7cWpsa2VlaGpscG9pbHRvcG1oZmVkYF9eVlVZXFtka2tpZWJeWFOkU6Wnq1hbW11dYmdrbGtpaW5yeXl6gYWHi5KVlpulrKywsa5ArrCvqa2ztLGtq7O3Xl+1ra6wsrW0tl25t11gYF5dXl6xpqWsq6ilnIyCgH6Oo7ZhZWZhX2NkZmdjY2RjZmhoYYBMS01PT05OT05OTUtPUVFSUVFNjo+TTFBOTExOVFJOTU9PT01MTUxPT01OUU9OUE9OTElLS5BKkIN8e3uDiIySkpKJio2IhYiOko6Ii5dMkpWTjZKbnU+Pi5SfU6GYm06Sk5OYn3R7c0xXWWNnYlxVV19fXlRUW1dVVldUWmBeW4BYWFJPV1ZLUWRtZGNbXmBbWFNPTlBUWFtdY2VqbW1mY2JgY2hqcXRsa3BpY2JlZmlqZWJjZWdlY2RpbnFramdlZWZiYWtta2hnZWZrZmdpXFlZXGViXmJrdnV2eHV2dnp6enNwdHh6fHhybGVaV1RXYGVvip6jqqqmq66sWl5fXTZdXl+0uF63sKScmZOGeG5zP0hSXWlubmxrbGxsbW9vb21ra2prbG1tbm5rbW5vcXFwcHBub2+EcAFxhnCAcXFxb29ubm1ra2tqaWloaGtsbW5vb29tbW9vbWxra2xtbm5samlqamloaGlqam5wbm1tbm1ta2psbm1tcHBxcnBxcnNzcnNxbm1ramprbW5ydHFxcnBubW1vbm1vcHByc2w7MkRTcnlBRERGSkpJR0dFRUhKR0VGSEpJSUpJSEOAQkZKSESAgEJCgENISEhKSEZFQ0JDRUVFREJBQkNCQUJBgEFFSERBQERISEZDQkNFQ0JCQT1ycz5BQYBAQ0VEQkNBfH1/e3x2bnaAfnl6fHZyaV5cW1tpekNERkhHQ0NERkdHR0RGRUE/emlaUEuQiI2gW19gYV9dW1lbX19eXVsBWoRdgFxbXFtZXV1aW1hYWlhXWVhWp6KhnJqZko2Kj5mVj4qEhYiHiYN6dnV8goiGgX57fHZwbX6Lhnp5ent9g4aEj5WanZ6ZjYqKkpSPiIKEhYCFh4mDgXt5gFprbVRtaWRmaGx0dXp6fYSIi4mWlY2PioSCgoSJh4SBh5GbmouEh46IgIaNgnt8f4CDh46XTk5QTkyTjYuFgYGFgomOj46JhoaIiYeGhYWEiouEhYyUUVOblZSKhYSIg4aKiISCgYOJiIeKin50cnmFSEtOS5GIhYWIjIuGiUhIiYmKgnx7fISKhIOGhoSDgH59f4d/foFFSIaCfXd+hEJ+enuDfYGNSkpMgJKKhoSCf3ZvamNjZGNjZWluc3N0eH19f357aodaaUNERUVESExNTkxMTkpNUE5WXlVOUFBLS09PUVNSTE5WUlJPTU1OTElHR0A/QkJASlFRT0xJRT46dD11cnQ9QEJERUpPU1RTU1JWWmJhYGZqa251dnV4goyPlJWRj5GOh4qPOo6Ig4CHjElJi4WIiouOjpBLlpVNUFBOTExMjYB+hIN/em5hXF5hcYOVT1JTUE5SUlNTT09RUVRYWVOTf4N+n38Cfn+XfgF/h34Bf4R+BX9+fn5/hn6Cff9+lX6HfwN+fn+Kfv9/lX8Ffn5/f3+dgAV/f4CAf5eAAX+TgAZ/f4CAgH+HgJZ/kYCFf4R+pH/HfoJ9sX6Ff5x+gn+ZfoR/iX6Cf5d+gn+GfgF/h36Df5t+g324fgV9fn19fat+gn+IfgN/fn6Hf49+kX8CAgQAgIeEhIOGiomIhIKEio6PjYeEg4KCgYL//YOEhoiJhYaD/ICCioyHh4aEhYOEhIOA/fz//fn1/IH06OXp9ICDgfrr6PL4+vPr5+Hj5uvu6Ovt6fDz8/Lt8/2DhYWHiIP9+Ozu79ye1OeWpaShm5ipqp2lrquqpKOsq6alqrS5tbKsgJydpaajn6awt7i3uLGuramor7GwrKixsqyxs6yqsry7ur/EwL64sbW+tq62vsTDxdLV09DR0s7JxcG5srO0tbGuq6Ggq7G3uLa6tba6ucDHxL+9u8HDycnFw8rNx769y8+/ubnGysa5qKu6v7/CvLi8vbWqoJmZornT6fuC/4GDfIWC/ID9+Pz9/4L//4KEhIP99u/j1d32h5Oju9Tr+oCAgP7//vv8+/z8+fr7+fb18e7s8Pb29/b2+Pr7/Pv6+/3+gICA//78+/r6+vv8/fr49/f3+v39/Pn29PT08/P29fT19fTy8fHw8O/t6uzw8fLx8/b4+Pn69fLz8/WE93319PT08/Hx8vT19PP19ff39/j39/b2+Pv59fHr5eHh5ujq6NyesITTxvyAgIODg4SFhoaGh4WFhoaHhoSHhoKDhYmLiIWGhoP/gIGChIWFgoGA//6BgoKBgYKBgoKAgYD/gIGBg4SCgYKFiIiFhIWFhoaD/4CBgoD28/n/gIWBQIKEhIL/8Ovr49LAsam0vtLvgoeHhYSIioeGhoeKioiEgO/PsJ2Qguzr/IiRkI6OkJCNjImIiomJioeIjIuJhoSEh4CGg4SHh4SFi4uDgomIiIqOjoqEhYSEh4WFh4mJi4+NiYmJi4qMi4qKiIWDh4aKjo2Mj5GQi4mMi4iKjYeDiI2TlJCKiYqLi4mFgIGChYqLi4qC+vf184CA2ozopuj17Orr7fb8/P3++/j48Ofr7PX+9e/o6+fk4uLn8ezq5+Pk4YDg3+Lj4N/h5u3v8/Tz9ffs5unq5ePk6u3v8/Xy6eHa3N/k4uXk5ers6eTd3u/9/YCBhYP/++fu9vby8/mAg4CEg/79g4L79PHz+f35+PPs6e3v6+72gIaIhYOEg/z4/4OBhouB8OXt+YGBg4D8gPz49vTy8/T09/39+PLu8PLv9B749f2A/fv4+oD9gIiJhoaFgfv9/oD//v+Agf78+/mE9YDw4+Ha08/Mzs7LxsHBwsnV08u+t7O1ubu1tLi5urm3ubm+ubfBv77Ew8LEwsHExs7c6+3h3d/j7fP6+fLt7PHv7/Pz+v79/P2B+/n39PT6/PT5/fjx8Ozs7u/x9fj5+oGDgoGEhYSBgYD49fX5/4KFh4iIh4aEgoSHgPn69/Tw5hjm8fDm28vIxbq+zd7x/YSD/oGGiYeGiImAY19eXmBkY2JfXmBma2xrZWFgX2BgYb+/YmFiZGViY2G6X2BnaGNkY2JkYWBgX1y3t7u7t7S6X7SqpqixXmBftqupsbW4s62qpaeqrrKsrbCssLKzs66xuF9gX19hXrm3rbG1p3KPn2hxb21qZ3R0aG50cnJtbHNzb25xen98eXWAaWhsbWxpcHqBg4ODfXt5dnR5enl2c3p8d3p6dHJ4gH9+goWCgn14fIJ8d3+FioiGjI2MjJCSjoqHhH15e3t8enp4cXF6foKDgYV/gIWFjJSRj4uGh4eLjIqJj4+KgoGNkIV/fomMiX9xdIGFhYeDf4KEfHNsam94iZ6uvGLAYmRyZmPAYsO+v8DBY8LBY2RkYr65tKqcn69fZW+AkKCsWFhXra6tqquqrKqnp6impKShn52gpaampaWmqaurqqenqapWV1arq6qpqaurrK2urKqop6aoq6yrqqinp6elpaalpKSjoaCgn52dnJuZm5+ho6SlhKgLqaSipKOlpqWlpqSEoVCgnp2en52cnZ6ipKaop6alpaaopKCdmpeTkZOVl5eTam9RkI61W1tdXVxdXV1cXF5eXl9fX15cXVtXWFxfYFxaW1tZr1dYWFpbW1hXVqqqV4RYgFlZWltZWVmxWVlZWltZWFhbX19dXF5eXl1Zq1VXV1alpa20WltbW1paWlxbWbGnpKSflIZ8eH6DkqhbX19dXWBhXlxdYGRkYV5bq5N9cWdcp628Zm5ubG1vb2xraWhoZmVnY2RoZ2ZkY2dnZWRkYWFkZWJjaGhhYWdnZmdsbGhjgGRhYWRjYmRnZ2pubGhoZ2loamhmZ2dlY2VjZmhnZWlubGZkaGhnaWplYWZobm9taWhpamlnY1xdXmFmZ2dnYry8urdhYaFgnXiss6ilpqq0u7q7u7i2tq+lp6iwubKuqauopqOjp7Gtq6mnqaenpqiopqWmqK2tr7Cws7SqpKipgKWhoqeqr7W2s6ylnJ2eoqCjpaasramjn6CvvsBhYGJgvryorre4sbK3XmFeYmK+vmNjvLSxs7i5trSxrKqsraittmBkZmVjZGO/vMNlYmZsYa6iqLJdXV5ctV22sa6trKyur7O6vLmzrq+vq66xrrZdt7i3tly1XWRlY2NiXrW3gLdcuLe4XFy1sa+vr7CvsK6mpqCbl5SWlpSPiouMkJmZlYyFg4aJi4aGioqKiYeIiY6Jho2Kh4uKiYqKiIuNlKCrraOipaivs7i4s62rrqysrqyytLKytV64t7Sxsri6sre5tbCwrayrq6yws7W2XV9eXF9fXl1eXK6srLCyWlxfJ2JkZWRjYWNlX7W1s7CspKOsrqmimZmShYiUpLS8YmC5XmJkY2JkZYBRTk5OUFRTUk1LTFNYWVhRTU1MTExNlpNNTU5RUk9QT5NJSlFSTU5NTE1MS0tKSI+PkpKOipBLi4B7fYdJS0qQhoaQlZeTi4iBgoWJi4SGiYaLjo+MhYqTUFJSU1RQmpOIiY6GXG15UVhYWFZTX11RWGBeXVhWXVxZWVxjZmJfW4BSU1hZWFZdZGlpaGhhX15ZWFtbW1lXXmFdYmdlYmVqaGhtcGxqZGBlbWhiaG1xb294e3x7foF8eHNwamZoaWpoZmRdXmhqbnFvcWhkZGZvdXBtb3F1dnp4dXN5e3dxcHt9cGtpdXh2bmNlbm5rbGtqbnNwaF5WVV1whJeoWa1YWoBcW7BZrKaqrK5ZqKZXW1pZqqWhloWEjkhHSVJea3E6OjlxcnBvcG9vcG9ydHNwcG5ta25ycnFwb3FzdHNxbm9wcTk5OXJzc3R0dnd4ent5d3NycXN4eXh2dHN0c3FwcXBub25tbGtrbG1sa2hpbW5xcXN0dHNzdG9tb3BycnFwcCxubW5ubmxra2tqaWhqbG1vb3Fwb25tbnBubGpnY2BfYmNlZWVGRjhrZ4dFRIRGUEVEQkRGRkdISUlIRUVDPz5BRUZDQUFAPnk/QEFCQ0I/PT15ekFBQUBBQ0JEREJCQYFBQUJDREFBQURHR0VERUVGRkJ7PT4/PXd4foVDRERFhESAQkB9dHJ0cmtiXFhcX2p5QkREQ0NGSERERUdJSEVCQXhmWFRQSImUpVpjYl9eYF5bW1pZWlhYW1pcX1xbWVZaWlpZWFVWWVlYWFtbU1FWVlVXXV5bV1hVVVlXV1lbWVpfW1dYV1hXWVdVVVNQUFVVVlZVVFdbW1dWWFdWWFlVUVWAWF5eXFlZWVpZWVVNT1BUWVhYV1GYlpORTU+FS3dgjpaLiYuNlpuXl5eTkJKMhIWGj5iRjIiKh4aGiI6alpOOiYqHhoWIiYWDhIeMi4yNjpGUiYKEhoKAgIWIiYuNjYiAd3Z4fXt/goOJi4iCf4CMmJlNTlBOmJiHj5STi4qOSUyAS05OlZVPTpWOioqLiomKiIB9f4B5fodJT1JPTE1Mko+TTElNU0qCeYGNSklJRodEh4SBfnt5e36DiIyMiYSFhoSGiYSJRoiGhIhGikdNTUtLSkaGiIhFioiHRUaKiYiHh4qLjoyDgXp3dnV4enZxbG1wdoB+d21pam5xcWxtb3B8bmtqa2pwbmtxbWpwcG9xb2ttbnWBi42Eg4WFiY2WmJSOioyKjJCNkpOOjJFMkY2MjY+VloyNj4yKjIuKiIaDg4aJikdKSUhMTk1MS0qKgnx+hEZKTlNVVVVTUFBSS42OjYyLhYKIiIB7dXh2bW53g5CXUU+YTlFTUVBSUpZ/gn6IfwF+jn+HfgF/hX6Df5l+hn+HfoJ9/36WfgJ/foR/An5/hX4Df35+hH+Hfod/g4Cgf4OA4X8Ffn5+f3+egAF/iYCCf4yAAX+SgAF/hICEf4qAjX+QgIZ/g37sf4R+BX9/fn591H6Ef4l+hX8Efn5/f5B+h3+DfoV/hH6EfwJ+f5V+AX+EfgJ/fod/CX5+fn9+fn5/f9B+AX+Wfop/hX6Mf5R+A39/fod/AgIEAICDhYOFi4mHjZSXkIuLi4WDhYiLiYaLk5GHhIeKiIeEgoSGiIiGgYCEhIOEhIKA/fXp5Ofu7+/o4eLr7+71/YSFgYH67+zt8O3n6u7y9vb29fn8+vv59PmBg4ODgIGDgPTx5bPy0PymrbCxs7Wzr6uprK+tqqissK6klZCVpLCztYC3sbGyq6eioKy3raSpq6qxs7Ozsa+yucPJyMLAwcrIxsrIw8K/vsK5s7S2t7W1usnO09HPwLa/yMXAtKurr7S7ube2tLWysKuprK6vsK6vrKemqLG7urKwtLa3vMHAv8LBvcDGzMC4u8HAvcC+uri1tbO3tbWzs7W2tqmamae0uFTE1urz9O/v8/Pn6fT4gYD9/v2AhoSDhoqJhfff0cvZ9omZrMHU5fP5/P7+gIGAgP78/f3+/fv6+/r39vj5+vn39vf5+fj49vT0+Pr7+/n29vf2+PqE+Sj39fj5+fn4+Pr59/X09PTz8vHw8fP19PP08/Py8/T08vLz8/Py7+7uhe+A7u3t7u/x8vT29fPy8O3q6ujm5eLh3+Dg3uDj4+Pk6ObHgJGL/tT8gIKCgYKBgIGChIKChISBgP+AgIKEg4KDhIOB+fj+gYKAgIKEh4iEgoKCg4KEhoaHh4SAgYKDg4OCg4H9/oKDg4OEg4KCgP399/r9+fn+gID//YSJh4H3586AubG0u8HK4PaDh4mKjY6Li4uKiIaC79jGs6OUhfLq/YqSkpKUlJORkZCOjYuIj5GNiYuMhYWCgoeMhYKChYiKh4eJh4aEgYD/g4WJioyPkJCRjouLioyNiYeJi46PioqNkJCLi46LiYqLjY2MioKChoeJj4qJjIuFgYWGi4+Qj46Aj46Kh4OFio2NjYqGg4H9+v7+gIOJj4yEhYn8peCQ2vn18u/r8PX+gfr6+vz9gIH66t7l7Orl4uPi4OHe4efs5ubs7evh2uDt9vHj2uDl8fL09/X19vn09PPx8vLx7uXn8fXz8Orp6Ovs7Ort7vD2/vv48e/39vf6+fr8+4OGh4iAhYD7/oGCgoKEiImG+fP4gYP/+4CB+/P19/uBgYKC//Xy9v3+gIKChYSDgYGDgv777uPm7fX5+Pn38evs6/Hx8/+Cg4KBgIGB+vDy///5+f6Dgf329fPx8vL0/f749vX39+fe5O3w8PHu7fPx7evu9/+DgfDugoT+9PHr5+r1/oR7gff5+/nz+4H99vb38uru+YeIgPb6+Pb5+fr5+fyBgoKAgIKA+PX29fHr6ufl6e3u8fXx8fuBgP346/KGhYSFhoeGhY6Ri4aFg/ry8/X6/PyChoD8+ff29vX8gYL87Obm6vD59fL09vn3+fn3gvzlysjFv8DM4fqFhYGAgGBiYGJpZmNmam1pZ2dlYF9iZGhmY2dubWZjZGZkYmBeX2JkZGNfX2JjYWJhYF67taypq66sqqejpKywr7S5YGBcXbeysbO2sqqqrbG0tLSytbe1trKtsFtdXV5bXV9dr7Csha2Pq3F2eHl7fXp4dXN2eXVycHR3c2xgYWdyeXt+dIF7enp2c25td311cHV3dnx9fHx5d3l/hoqKhYODioeGioqFg4GBhX97fX+Bf36Ai42RkI+Ff4eQjYqBeXd4e4F+fHt8fn18enl6eHh4d3d2cnF0fISCend6fX2Bh4aFh4eEhYmNg31/g4OBg4F+fXx7eHp5hHpVfHx0amt2goiUo7K4t7KxsrKnqba7YWC9v75gZWRjZ2pqZbqnmpSdsWJrd4SRnaaqq6yrVVZWVqyrrKytrKqoqamlpaanp6elpKSmpqSlpKOjp6qrrIarWaysra+urKmqq6ysq6qrqaelo6OioaKioaKkpqSkpaOhoKGioZ+foqOjoJybnJ2en56fn52cnqCioqOmpaOko6CdnJqWk4+OjI2OjY+RkZGSl5mGVlxWqZWyhFqAW1pYWVpcXFtdXVtZsVhYWlxbWVpcW1mtrK9YWFZWWFpdXVpYWFhZWVtdXV5dW1lZWlpZWVhaWKytWVpaWl1dXVxZraylp6qmqK1XV66sW2BgXK+ikIJ8f4OHjp2sXF5fYGNkYWFiYV9eXKmWin1za2GzqrpnbGtrbm9tamlnaGmAaGZtbmpnamxlZGJiZmpjX15hZGViY2RjYmBdXbpiY2hoam5ub3Btamtpa21oZmhrbW9sbG1vb2ppamZkZmhqamhmYGBjZGZsaGdoaGJeYmNnamxqaWlpZmRiZGhrampmZGBfvLq+wWFkaGxpYmRnvHaVY6C3sq+tqa+0u161tbWAuLleYLepoKatrKqop6Wio6CjqK6npqusqqGboa21sKOboaWsrbO4t7e4urSzs7Kzs7GupqavtLGtpqalqKmpqKusrLG4trOsq7S1trm3uLi4YWVmZ2RguLtfYmFiY2VmY7awsV1gu7leX7mztrq/YWBgYb+zsbe/wGBhYWRjYV2AXV5et7atpqqts7e1tbSup6ekqKist15fXlxdXV22sLO7ubCwt19euLKztLKxsLO8vLSwrrKypZ6krK6urquss7GurK+2vGBerqxeXre0tLCssLq/Yl+zs7WyrLNctK6wsa6orbllZl60t7a1uLm6ubi6X2BgX19hX7aztLSwq6pQqamsrq6wtbKxumFgu7essWJgX2BiY2JiaWtmY2Jht6+ws7S1tV5iXba1sbCxsbdeXriqpaWorbSysbS1trOzsrNguaaTko+JipSktmFhXl2AUFFOT1RRTlFVV1RTVVROTE5RVFJPU1taUk9RU1JQTUpMT1FSUExLT1FRUlBNS5eSh4GChoWEf3l6g4aEipFMTEhKlJKVl5qWjYuMjpCPj42SlJGPi4iMSkxNTUpMT0yQko9tinGIW1xeX2FiYV9bWl1cWltaXF5dWlJLTVhiZWZtamNgYV1dWVZeYlhTWl1aX2BgYWFgYmdvdXVycHF2cm9xb2tsamlsZ2NkZWZiYWd0d3d0c2lkbXh2c2pjY2ZqbmtpaWhpZmViYGRmZ2hmZWFdXmBlaWZeXWJlZWxycW9xcm5vdHpuaGpvb25wbYRqfGZpZ2dmZ2ptbWNZWmVweIOSo6mlnJeYmY6Qn6dYWLCysVldW1lcYWJdq5iJfXyGRkpPVl5nbW5vcHA4OTk6dXR1dXRzcnFxb2tsbW5vb25tbnBvbWxsampvcnR2d3h6enl6e3t8fHx6d3h5e3t5eXl2dHJyc3FwcG9ubnCEcjxxb25vb21qa25xcXBtbGxsbW1sbGtqaWxucXN0dXNwcHBubGtnY2FfX15fX15gYmJjY2ZqYD9EPnluhUKEQyNBQEFCRENDRURBP30/P0BCQkFCQkE/eHh9QUFAQEJDRUVCQIRBgERGRkdFQ0BBQkNCQkFCQXx8QUNDQ0VFRkVBfn14eXx3eH5AP358Q0hHRIF4aWFeYWVobXeBRUZGR0pKR0dIRkVEQndpYFRPT02UkaNcYWFiZGVjYF9dXFxaV11fW1hcX1taVlRYXVRPT1NXWlZVVlZTT05QoVNSVllaXV9gYV1bgFtaW11ZV1pbXV5ZWl1fX1pZWldVVlhaWllZVVRVVlhfW1pbW1RPU1RYW1xbXF1aVlRTVVlbWlhUUlBQoKCkpVNWWVxXUlRXoGF3UoeZk5GPjJCVm0+WlZWVlkxNkYN8hYuIhYKDhIWHhIeMkIuLkJGNhX+CipCPiYGEho2Nk5qacZmZmpKPjoyMi4iHgYKLkI6Hf358goWFg4eHh42UkI2HhYqKjJCQkpSUT1JSUlBNlJhOUE9OT1JUUY+HiUhKkI9JSYqDiZCXTk1MS5SMjJCWlkxOTlFPTUpISkqPjoN6fICJjo+Sj4V+fnt9fYGOSktJhEeAioSGjo6Ih4xJR4uJi4qGhYKFkJONiomOkIN7gIiKiImIi5KRj42OlJtQTo+MTlCalZaRjZCYmk9MjpCSj4qTTZWOkZSRioqSUVNNkZWUk5aWmJaXmk9QUE1NTkyTk5SUkIyLiYeJi4uNkIyLlExKj4uBhk1MS0xOUFBQWFpVUlMzUpqSkZGRj45KTkqTko6NjYyQSUmPhIGBhImQj42Qk5aVmJmZUZuIdHNvaWt2hpdRUU5Nrn+QfoR/lX6If4R+g33/fqh+BX9/fn5+iH+Gfot/hID4fwV+fn5/f5CAAX+KgIN/nYCCf4mAiH8EgIB/f4SAi3+NgId/g36ofwF+xn+Efoh/A35+fYp+AX+FfoJ/zX6Gf4J+iH8Jfn5+f39+fn9/hX6Ef4Z+in+Tfod/iH6Cf59+Bn9/fn5/f4h+gn+GfgF/iH6Df4p+h3+RfoJ/hH6Of4d+g3+HfoJ/kH4Bf4p+hH8CAgQAgMLV6vmAgoSIiouKioqLjImJi5Sfl4+MjI2RlZeQi4WGiYWA/f6Ag4aGhYeDgf3x5t3g3+Dh5OTi39/m7fiAgoD8gYP+9/T09ff6+/77+/rz8fPw5+br9P6Cg//9/Pru58uL1fOetLe2tLCqtL63srO0trezqqWorbGwrqutqZ+ngKqmqrOxrqqgoKCiop+lrrOyrbO3u7e0uLS2vsPHzdHRxcDBubu/w8G7trzEwsK/wsjAvLm5t73LxcHAv8LDvLu+wsjDu7zBwb+7u7u3t7q2rauur62xtLS2vr67ua+op6qqqKGenJmep7C2u8XLyMXDvrW7xsnHw7y0sbO2tbStV6WioqChqbfGztXh8P6AgYGDhP//goP//PuAg4SEgYCA+/j07d/T1d/tgY6ap7fK3Orz+v6A///+/v79/Pz8+/n4+fr6+vj29PLy9PXz8/Hv7uzr6unp7ITvgPDx8PHv7uzq6unq6+vt7+/x8/P08/Hx8vP19fb39PLy8/Lx7+7w8/Ty8PDx8O7u7uzq6ers6+jn6evs7vHx7uzs7/Hy8vPx8fL19Oa555GZh9T+hYaFg4SFhYWEgoGDg4KDhIKA/v6A/vz+gICAgYGA+/n8gYOA+/yCg4OFhYKAgICCg4OC/fv19f2EhoX//fz/g4OFhIH+gYL//YGCgfbcycC4ury9vsfZ8YOJi4yMjIuIhIH/8+LTxbehk4j07fT/hYuMjIqGiYqPlZeXmJqal5SUlpWTlJaUlpWQj5CQlJeRiIWJhoKB/vuEh4eFgoKGhoaHjImGhYaHhYOBgoOCGIKCgYOFhoOChYWC/vqAhIKCh4qMjYyOjYSJgI6TlIyHiIyTmJWPjI+PjI2PjYuFhISA9/HvgYmDg4iJi42Pjo6NiYWFhdH906Lf8Ovz9fLv4uLv8PP5//78+/z+gP/9+/z+9+7l5ebn6+Pe4PiB9ufp7OHZ2Nrb4er0+vz9gIKCg4aEgf7/gYCBhYL89O7p6Ofu7uzv7fDs6/TzT/f39vr17+z19e/r8/b18fH2/P2Dg4KCgf7//fn4+4WC/Pz68evh3en09fDw7e/x9vf49vL1+fXv7/H5gYD59vL2+P6Bgfz2+fbr4+fs7vCE9ID1+4OFhIOA9+7z+fXz+P+DgfPp6efs7u/3//317+nn7fP39vDv7ufk4ePo7/T19PX5g4iC+vf7/fv77uvx9Pj59Ovw9vTy9PPv8fb3+Pj08fHw7e3r6/D59vP19PHv8vbz6ubp7/b58Ofn5+v29ung3N7k6+rl5OXq6/OAhIGAgTb/+vLk4On4gIaJioSB+/z69vf8/P2Agfj19fn//fn3+Pv6/f2AhISEhYL8+P+C/+zUv7vBv72Aj6Cyv2JjZWlqa2prampqZmRmb3pxaWdnaGtvb2pnYmNlYl+9v2BhYmJjZWJgvbOon6OioaGlpqSioqiutl5fXbdfY723tLSztLe5vLm4t6+sr6+op6qxtl1dtbS0tK2ql2WUqG59f358dm93f316e3p6e3pybnB1d3d2c3VxaHCAc29zfHt4dGxsa2traXB4fHt3fH5/e3l9e32Dh4eKjY6Gg4R8f4SJhYB7gouKiYSGioOBfn+Ah5OLhoWFiYmCg4eIjYmCg4mKiISDg4CAg4F7e319en1/fn6DhICAeHNydHNybW1tbHB0eHx/h4uJh4WBeoCKjImHgXp4eXx9fHc/cnBwb3F5hpObo625xWRlZGVlwcFjZMG8uV5hYWFfX2HAvru3qp6do6xdZGx0f4qWn6aqrFarrKusrKuqqqmnhKSApaWlpKOioqSlpKWko6SjoqGhoaKjoqGgoaGgoZ+dnJqamZqcnJ+io6SkpKOjoaGipaamqKmmpKSmpqSioKKmqKWjpKWlpKOjoZ6cnJ2bmJeZmpmampmXl5eanZ6fn56dn6SknYCdXF1Xja5cXV1bW11cXFxbW1xcWltcW1mtrFeAr66xWlpaW1pZrKmsWVtYrK1aWlpcW1lYWFpaWlino6ChqVtdW6yqqq5bXF1bWKxYWa+rV1hXqJeKg31+gYOEi5qrXGFjZGRjYV5bWrKqn5OIgnVsY7SytbpiaGdmZWNmZ2txdHR1d3d0cXFzcnBwcnFzcm5ubm1xdXBnY2dkYWGAwLxjZWZkYmFlZWVmamhlY2RmZGJgYGFhYGFhY2RkYmFkZGG9uV9hYGBkZmdnZmhnZWZnaG1yc2tlZWdudHJsa29uamlra2tmZWZju7S0YmhjY2lrbGxtbGtqZmJjZJ2yj3elr6ivsK2rnp2oq7C4wL67uLi8Xr27urm5squlpKWApaihnZ+1YLWoqayim5udnaKps7u8vV9hYmRnZGC6ul5cXmFft7Guqaenra6tsK6wray0sra1srKuqKSrramlrrOysLG3vLxgYWBgXry+vbm3uWRft7i2rqmhn6qztbG0tLa2ubm5t7O1t7Kqp6mwXFyxsK+xsrZdXLKssK+noaWAqqyusbCwsbG0XV9eXFmspayyrq6zul9erqWkoqaoqbK7vLWvq6its7a0ra2tqKemqKuvsbKwr7FeYl61s7e4trarqK2vsbOwqq2wr6+ysa6wtLS0s7CvsLGurq2usrq4s7W0srGzt7Stq6+ztriwqaqpqbGxp6Cdn6Wsq6enqq49rrVgY2FfYL25saSgqLNcYWNlYF20tre1tbm3t11etrSztbq4tLGxtLS3t15iYmNjYLiytl23qpmIhImKi4BseoqXT1BTVlhaWl1bW1pWVFVeaWBYVVVWWFxcWFVRUlRRTZiaTlBSUlJUUU+Zj4R7f319foGAfXt8goaPTE5Nl09Tn5uYl5aWlpiblpWUjImKiIGAhI2VTU6VlJWVj4+BVHWIXWpoZmRhXGVtZ2NlZWhnZFxXWl5gYWBeYV5UWoBeWlxiYWFeVVVUVVRRV11hX1thZWZiXmFeYmpvb3J0dm5qbGVmaGlpZ2RqcXBxbnB2bmtnZ2ZsenNwb2xra2dna291b2dobm5ta2tsamtta2ZnamdiY2RiZGttbGxjXV9iYmFbWFZUWF1iZWlxdnJwb25qbnZ1dHJtZ2Zoa2trZk5hXVtZW2NwfISMl6GtWFhYWVuwsVtcsq2qV1lZWVVTVainp6SXioeHhkVHSk9XYWlvc3V1OnNyc3NzcnFwcHBubW5ub29wcG9vcHFycXKFcUJwcHBxc3Jwb29vbm1sbWxra2psbW1ub29wcnJycXBwcnN0c3R2dHJzdHRzcnBydnd1c3R0cW9vcG9sa2tsamdmZ2iEZy1mZWZoampra2ppam5wbVhlNjxCboNERkVDREZGRUVDQ0REQkNEREKAf0GBf4OEQ4BCQH18fkNFQn1+Q0NCRERBQEBCQ0NCenZxcXlBREN8eXd6QEFCQj97QEF/fEBAP3hqYV9cYWVmZ2t1gkZJSktLSUdEQUCEgHdwamRWUE2Slp2mWV5eXVtZXV5jaGlpaWpqZ2RjZWRjZGVjZWVgYGFgZGZgWFZaWFVTpJ9UVlZVVIBUWVhZW2BcWFZXWFdWVFRVVVZVU1RXV1VVV1dUo59SVFNTWVxdXVxeXVlZWVpfY2NcWFhZX2ViXFtdXFlaW1tcWFhYVZ6Wk1JaVVVZWltbW1paWlZRUFSEkG9hipKKkpSRjX9+i4uLj5aVk5GRk0qXlZOTlI+Jg4GCg4eBfn+SToCUioyPhH18fn2BjJqkpqdTU1JSU1FNmJhMS0xPTZWPioSEg4uLio2LjoqIj42Qj4yLhYB+hoqHhIySkIyNlJmYTU1MTU2YmJaRj5FPSouMi4WBenqGkZSRko+QkJKTlJSQkpWPhoGCiklJi4mHiYqNSUiLhImGfnd5fX+ChoaGh4CHjElKSUdEgn2AhYOChohFRoN8fHt/goONlpWPi4aEiI2QjoiIiIWEg4aLjY6OjY+TTlJOlZSYmJSTh4GHi46Oh4CGjIyMkJCNj5OUlZWRkJCRjo+Lio+Zl5OWlpORj5KRi4mNk5udk4iGhIeSlId+eXl9g4KAg4qQkZdSVlNRUTacl4+CfYSRTFJXWFJPl5mal5eZl5RKS5CPjY+WlZKQkZKRkpJMUlJTUk6Tj5VNmY17a2dqaWiEfp9/gn6If5B+Bn9/f35/f5V+gn+IfoJ9/36qfoV/B35+f39+fn6Hf4l+i38BgPN/Bn5+fn9/f5KABn9/gH9/f4aACH9/f4CAgH9/jICFf4OAhH+FgAh/gIB/f4CAgIx/ioCJf4R+p3+CfqF/gn6mf4N+kH8Dfn19lH4Bf5B+AX+Pfod/gn6Ff6N+hX+GfoJ/m36Cf4Z+gn+QfoV/iH6Cf6B+g3/HfoV/h36Gf4h+gn+NfoZ/BH5+fn+IfgICBACA+ObPvLS8xcnO0dntgYaHhoWHiYmKi42OiomHhIWEg4OC/vb2/oGA/fv49ff09PTz8ezi3NnX0tfg5+3t9P6EiYiE/v38/f779vH09/b08ezp6+zx9vn7+Pr//YCA+t+d09WCnqy5uLm7u7y9vLm2urmxq660vMLAvru3trW0srOAurq0uryvqa+vraqpq6yrqaGepK67vb3CyMjHyc3S0M/W0s/S0crCw8S9tbS5uLW3vLuwp667v8DEyMbGx76ztr3AxL67v8DBure6vsLGyMfGxcXCu7m2sbK0tLWzq62/yMXBvre3uLq/x8jGxsS/vMDFyc7Qz83KxMPHx8XDwb5Av7u4vMG+u7awqaWcm5+kp6yyu87g7/n/+/v/gP///v///oD//oCCgP6AhoWB9+na0tDb74GMmaOwvcvb6PP7/oSAOP7+///+/fz7+vn5+fj49/b19ff18e7s5+bo6uzt8fb39PDt7e3w8vLx8vX19vf19PTz9fb39fHwhO8c7u3s7O3t7u7t6+np6+7w8vX39/f5+/v5+/38+oT4gPf29fb39vTt4rz4m47ctOyCgYKCg4OCgoKAgYOEhIKAgIKCgIGEhYSB/vr9gYKCgYGB//3+//+Bgf/7+vyCg4D++vj094GA+/j+gYD9+/fs3M3At7W5v7/BydrvgIKDhISEgvzz6drJvLOrnpGG/fv+goqVmJOUkY+PjouGgoOGgIqOkpaWko2NlZualpOQkpOUl5mZmJucmZaVkI2LiIqMi4iJjI2MiYaFhYaJjpORkY+HgYKIiomGh4qIg4CDhIqLiYaJjoyLi46Rl5qWkY+PkZSVkIyNj5eYjYaJiYiGi4yOjouLjYqGipGVkouQnqGVjY2OjI2MioeIiYSBgoOGWImJ+bPp7q7l8fb6+PX7g//4+PqB/f2FiIiGiIeHhIOA7uTl6evp7e/v6uvr7O7t7ezq5urv8PX3+PX4+vTs7Ozv8vHu7vT39O7t9v2AgP759PT5goaB9e+E7D7u8fDu8fTu5unt7Ovm5uzx+4OHiYX68vDr5ur0+e/s8e/z/IH+9/T6//r2/YD9+PP19ezy+fj29PP+/fr8gISBgP707u/19vXu6ujn5+ju8Ovu9vPv7+/n5ubp8vfy6e33+/+Dgfr8g4WCgf36gYD48e/s6ezs6fD48+bf3tvi7vD1+ff6+O3u+YWE/f6Dgv6Bgfru5+Pl6u7u6eLl7fL39fD19vbz8/X6+vHl4efs6uTj5unr6eTi4eLp9fj5/oGCQoGB+vf39O/r7/qA+/z69vuCgf/78e35//6DiIeGhoaHhIOC/fv7g4SD/vjz9/6IkIyGgoH/gIKGhYGBhYeGhYSEgYC9rpuLh42Sk5ecpbZjZmVjYmRmZmhoaWlmZWNiY2NiY2K/ubrBYmG/vLm2uLW1tbKvqqGcmpmUmJ+lqqistV9kY2C3tbS0tbOvq66wr66uqaepqq6ytbaztbm5Xl65pXGPj1lueIKBgIF/gICAf32Af3h1eHyDiIWDgX17enl4eYB/f3uBhHhzd3d2dHV2eHd2cG5yeoSFhYmOjY2Oj5KQkJaUk5eWkImIiIJ6en+BgYWHg3hvd4eMi4yOi4uLg3uAhoeKhYOHh4iCfoGEhYiIh4aGh4eCgX97fH19f353d4SLiIOBe3t9f4KJiomKiYWChIiLj5CQj4yHh4uLiYmIh4CJh4WIi4mGgXt1c21rbnN4fYKJmam4wcS/vL1eubm7vr+/YcHBYmVjxGJpZ2S+saOal56qWl9la3R/iZSepqusVlZXVqytrq6sqqinp6eoqainpqalpaWkoaCem5mYmZqanZ+goaCfoKCio6KhoaSlpqempaWkp6ipp6OhoJ+fnoCdm5qanJ2cnJuamJianJ6foKKioqSlpaKkp6eko6KhoaKhoqSlo6Cbl3+iYVqSgKpbWVlaWlpZWVpYWlxdXFtZWVtbWVpeX15bs7G1XV1cWllYrqytr69ZWa6qqatZWVerp6aipFdXqqiwW1qxsKyjmIyCfX6BiIiKkZyrW11cXoBdXlyxq6OYjYSAem9mYLu9wGJpcnVyc3Bub29sZmNjZmltcXR0cGtrcXZ1cm9sbm5ucXR0c3Z3c3FxbmtqaGpsamZnaWppZmNjY2RmaGxpa2tlYWJmaGZkZWhnYl9hY2hpZmJlaWdmZWhrcHNwbWtrbnBxbmprbHN0a2ZoaGdmaoBsbmtnZ2hlYmZtcW5na3Z5cGhqa2pra2lmZ2ZhX19gYmRluIKfnnmkrbG0sq+0X7avsLReurpiY2JhY2NjYGBeq6Kjp6morLCwrK2ur7CurKqmoqWrrbG0tLK0tq+nqKeqq6uoqK+ysKmnsrhdXbm0sbG2X2Ner6qnpqempqqrq4CxuLKprLGxsKqprLC4YWRmY7iwrKaip7K4raqurbK7YL22tb3Fvra5Xrq3tLe3rrC0srGurbe1srVeYF9fXrqxrrC1trOsp6Sjpqmwsayus6+qqqqhn5+jrbKvqKy1uLxhX7i7Y2VhXrazXl2yrayrqq2tqq+zraGenpylsLK1uIC1uLevr7ZhX7e6YWC4XV62raekp6ywsa2mp62wtLKtsLKysbKztravpaOprKqlpKSmqKahnZydpK+0t79iYmBfuLa3s6+rr7pgvL+9uLxhYLq1q6q3vr1hZGJhYGBgXFxdtLGyXmFgubKusbllbGhiXl63XV9jZGFhZGZkYmJjYoCXjXxtZWZscXh+hpZTVVJPTk9SUlRUVVVTU1JQUVFQUVCbk5KaT06amJaTk5CQj46KhX14dXNscXh+hISKk05TUk+YmJaVlpSTkZOUko6KhYSHio6TlpeUlZmXTU6YiV5zdUxgZWpnaWtqamtqaWZoZ2FdYGRpbmxqZmJhYGBfY01rbGZpamFdYmFdWldZWlpaVFFUW2VnaW5zcXFzdXl3dn16dXd3c29wcW1nZmprbXFybmRcYGpra29zcnNyaF5hZGVpZGFlZ2diX2JnboR0gHV3dW9raGNjZGVmY1xebXRxbWtmZmZnbHR1dHV0cG1wdHZ5e3t5d3Rzdnd1dnRydHJxdnp3cm1nYV5YVllfY2hud4eZqLC0sKysVaenpaOhoVKmqFZYVaVTWVpYp5qLgn+CiEREREZNU1lfZWxxcjk6Ojpyc3V0c3NxcXN0dXZ2GXV0cnBvcG9raWZjYWJkZmdpbG5vb29wb3CEcQ1zc3R0cnFwcHN1dnNuhG2AbGxramppaWloZmRjY2ZoaGhqbG1tb29vbW9ycnBubWxsa2tsbW9ubGloV2Y5PWxbekRDREVFRENERERFRkZGRENDRUVDQ0dHR0SCf4NERUVDQkB+fX1/f0FAe3d3eUFCP3x4d3N2Pz55dn1BQX+AfnZuZl9bW1xfYGJpc31CQkIYREVFRIWCfHRrZGJfWVRRoKKkVVxmZ2VnhGWAYVtYWFpdYGRoZmJfYGVqaGZiX2FhY2ZoZ2ZpaWZlZWJfXVteX1xZWltbWlhWVFRVV1ldW11dVlJSVlhWU1VZV1JQUlRYWVZTVlpXVlRWW2FmYmBeX2JlZWFbW1tjZl1XWlpYV1xdXlxZW11aV1xjZmNcX2ptYltcXFtbW1hUVFNeT05QUFFVV6FtenlkiY2PkpCNkE2Ujo6RTJSTT1JSUlVUVVBPTouCgoaIh4yOjYmLjI6OjYuLioaKkJSanp6anJ+XjYuKjY6Oi4uQlJGMi5SYTUyWkIuLkU5STI2JiISGgIqLio+UjYWIi4qKhYWLkJdOT1BNjoSAfHl/i5KJhIeFiZFLkoqHjpSPipBJjIaDhoiEipCNi4iGi4eBgkNERUZHjYeEh4qJhX98enh5fIKEfn6EgoCAgXx9fX2ChH94f4mMjUpJi41LTEhHiolIR4eDg4OBhYaDh4qEeXd5eH+KgIyOkI6PjIKBiEpIio9MS5FLTJKKhoSGioyKhH6Ah4mNjYiKjIyLjI+Vlo2BfYOGhH5+goaJhX55d3d8hoqNlU5PTU2VlJeVko+Sm0+ZmZiVmlBPm5aMiJKVk0xQT05NTUxISUmLiIpMUE+Xj4qOlVJYUkxKSpBJSk9PTU5QUE9NA0xOTYx+lX+EfoJ/l36Ef5l+B39/fn5+fX3/frp+AX+Gfgd/fn5/f39+hH+Hfox/hIDmf4R+gn+ZgIN/hoCFf4KAhH+DgIV/B4CAf39/gICQf4eAi3+Dfv9/kn8Efn59fYh+AX+EfgN/fn6Kf6x+gn+FfoN/l36Ef45+AX+IfgF/kH6Ff6J+BH9/fn6EfwR+fn9/mn4Jf39+fn9/fn9/rX6Ef4h+AX+FfoJ/h36KfwZ+fn5/f3+FfoZ/AX6NfwICBACAgYH9+Pf2+P+BgPfl2tLLzM/T2NjZ4fL9/oKEhoeHhIT+9PLy8vT6+/n6/YD++Pn59fPz9fbw7PDy8vPw7vmCgfz29/v6/YCA+PHu8PP29vXv7fT7//37+fr9gYHin9LIgKGloJ2foaOkqK+1rKWkqa2wrKiloqe1ubGsrKqnqq+AsLS4sqipsLnBwrmvrKyspKqxsq2qqqytsLSyrq6zqqa3ys/EvMTO0dHLyc7Nxr68xcO0rq+ysrW5ubzAvrmzsbG0uLGvv8fDvry/vrawrquxvsXCw8K8vL69uba4t7KvsLi5ube7wb29w8fKx7+6u7/BxsXAwL+9vsTIxsXGw79pvLm6wcPFxsXFwLu4t7e4u7i0tLKtq6KZmJ+nr7vFxsjR2+Hq7+/7gP77/ICBhIOA/4KFh4aDg//28ezi19PU2uX1g4uRmKCqsr7N2ODq9P6ChIWGhoSBgYKCgoD8+vr7/f+A//z8/Pv5hfiA9/X09PPy8O/u7u3r6eno6Ovu8PHy9PX09PX29/j5+/v7/f37+fn5+Pn5+fj29fX19/f3+Pj28/P09PPy79mhvYSkjs/2goODg4KCg4OFhoSCgYKDg//y8ff6gID+gYODgoD7+Pn/goGBgYODgoGA/f38+vXs38/BtbS2vb+9vsKAztro9Pr89+rczsO6squnnZKOiIH+goaLjpOTkZWXlJCMiouPkIyHiYuOjo2Oj5GRkI2LioeFhIaLi4+Tk5KPj5CNio6RlJKOjYuKi4iB9u3q8Pr/gYKBhIKChYeFhoyPjYuLioyMioWEhIaJjpOWmZqamZeZnpqWk5CQj4yIiYuAjYyKjJGQjpOcmY+KjJCUj4qLi4qMjYuPj4yKiYyRkI6Ojo2Nj4yHh46SkY+Qj4+PiYiIh42Rga7i4aHcgIWIiYWDgPz+/fft7enyg4WDgfny8e3w8fX194D+8O319/Py7+zu8O3r6+7w7+/y8/Hs6+7v7/L2+Pj7/v+AgoeKhoRshYWB+PHu8vf8+PLu7u/q4d/g3Nfd6uno6/Ts5uPm+IH17O3q7O/1/P3z7/H6gP3s4uTq9vX0+P6AgoKA+vv/gYKA/Pfz7enp6u/09ff5+Pv5+oGCgoODg4WC+/b19fDx9PL09fHs7fP3+Pn2hPRD+//79vLz+YCAhIyOjIaCg4aIiIeGhID+hYyKhIOEgoGAgYOB/f6AgYGA+v2A+/b2+fyAg4OBgfvt7fP2+Pj69/Lx8YTyW/T29vn8/YD9/YGGh4aFgfXy9v2AgP77+fv48/X1+oKDhIKBgoSEhIWHh4P99PL/ipCOi4iGh4mJiomHhoSDg4OEhouKhoOB/f6BgoGAgYODgYCBgICBgoWDgIAhX2C9uba0tLtgX7mro5yVlJSXm5qcpLS9vmJjZWZlYWG6hLSAtbu9vL3BYsG4t7Wwra6wsaqmqauqrKqptGBfuLKytrS2XVyvqaaorbCysKuor7W5uLe1tbhfYKVuhX5WcHNvbm9vb25xd311b29ydnl2cm9tb3t+eHNycXB0d3h7f3pyc3h+hYd/d3Z2dnB1e3t2c3JzdXZ4d3Z3fHRve4qPh4CAh4+Rj4uJjo6IgYGKiHp0dXZ3en5/gYWDf3t7fH6Be3mGjImFg4eHgXx6d3uFioeJiIOEhYSBf4GAe3h6gIB/fYCFgYCEh4qIg4CBhIWKiYWFhYOFioyJiYqJh4WBgYaIiouNjomDgICAgoSCfn58eXhxaWhudn6LlJOUm6Sps7huuMRjxMC/YWBjYl+8YmVoZ2Vlwrq0r6eempmcpLBeY2ZpbXN3fYaOlp+mrFhZWltbWVVUVVVVVKalpaWnqFWsq6ytrKqpqamop6ako6Khn52cm5ycmZiYlpaZnZ6en6ChoqOkpKSlpqeoqaqqpqSGpRuko6GgoKGhoqOjoZ+fn56enp2QandSamCQrFqGWx5cXV9dW1paW1yypqarrFlas1xfX15asK2sr1lYWFeEWIBXra2trqujmIyBeXp/hoiGh4mRmqWvtbaxp5uQiIJ9eHdxamlmYsJlaWxvc3JwcnRxbmtqa29wbWlpa21sbG1vcXFvbGloZmNiZGlpbG5vb21sbmpnam1vb2xsamlraGG1rayyvMFiY2JkYV9jZ2ZnamxramppampnZGNkZmlscoB0dHRzc3Byd3RxbmxramZiY2ZnZ2Vnbm5rb3Zza2lrb3NvamtqaWpsbG5tamloam9ubGxtbG1wbmdnbXBubG1rbG1nZmdmbG9kgJiUcJ1cYmVlYV1arrGxrKOko65gYmBes62tqq2vs7S2X7yxsba2srGura+wrKmpq66trrCxr4Crqqytra6ys7O2uLpdX2NlYmBgYV61rqyvs7eyraqpqqijoqOfnKGsqqipsqyopai2X7WvsKyusbW7urKwsrpfuqylqKy1srC0uV1fXlywsbNaXFuzr6yopaWmq7Cytre0tLO1X2BgYWBeX1ywra6wrbCzsLGwrKiprrO0trOysn2ytLu+u7e0tLheXF9mZ2ZgXF5fYGFiYmBct2BlY19hY2FgXl5fXrq8X19fXbW4XbWurrCzXF5eXFy1qqqurrCytrOvr7Gys7OztLa3ur2+YLu6YGRmZ2ZhtbG0uV5eu7u6vry5ube5YGFhYF9fYmFgYGJiX7qzsLxnbGtoZYRkI2NjYWBdW1pbXWFmZWFfXba6YGFgX2FjY2JhYWFgYWFiYF1dgE9PmJSRjY2SS0uRhX95cnFzd3p6fYSSmplOUFNUU09NkYuMjZCTm5yamp5RnZWWlI+NjpCRioaIiYiJh4aRTk2WkJGVk5RMS5CLiYyPkZORjIuTmp6bmpeXmU9RilhlY0ZeXlhVVVhaW11jaWNdXF9hYl9bWVZZZGVeWVhYWFxegF1gZGBZWmFnbGthWVhZW1VaX19bWFhaW1xeXVtdYVhSXW51bmhudXZ0b2xxc25mZG1tYV1eX19hZGRobGpmYmJjZWhiYGxxbGhmamljXVxaYGtwbW5taGlqaWRhYmFeXF5kZWVkZ2xpaW5ydXJtamttb3Nybm1samxxc3FxcnBteW1ra3BxcnJyc29sa2ppamxqZmZmZWZfV1ZcYml0fXx+hYySnaSmslmtp6dVVVhYVqpYWltbWlqvp6KdlYuHhIKDhkVHSElMUFJWXGFla3F1Ozs7PDw6ODc4ODg3bGxsbW9wOXJydHV2dXV1dHNycG5sa2loZmVkZGOFYShjZmhpamtsa2prbG1tbm9vb3BwbWxsa2trbGxraWdmZWdpamxsa2lphWoLY01fQU1EZ35ERUaFRYBGR0VDQ0RFRoh9foOEREOGRUhISEaGgoKFRENCQkRDQUJBf3+BgoB5b2deVlZbYmRjY2VrcnmCh4iFfHRsZmBdW11ZU1RUUqZXXF9jaGhlZ2dlYl9eX2NmYl1eYGNjYmNlZmZkYV5dW1dXWl9eYGNkZGFgYl5cYGRmZGBgX2BkYVNXnpORlp+jU1NSVVRSVVlYWFxeXVxcW11dW1dVVVdaXmFiY2ZpamhpbGllYV5dXFlWWFpcXFpcYV9eYmlmX1tdYWNdWFlZWFlbW19fXFtZXGFgXoRfgGBeWFddYF9dXlxeXldWVlZcYFdvfHVbgktOUVJNS0iMjo+Lg4SEjk9RTkyQjIyIiYqPkZZQnJCSmJiVlZGSlZeUkZGSlZSWmZiUj42Pj4+Qk5STlpmaTVBVWFRRUVFOk42Mj5OWko2Li4yJhIOGhH+DjoyLjJOOioeKmlGYj4+LgIuMkJaXj42Ql06XiICCiJORjo+SSUpJRoiJjEdJSI6Kh4N/f4CEh4iLjYqLioxKTExNTEpKSYmGhoeEhIaCgoOBfX6DhoeLi4qLiouRlJGPjIqMR0ZITlFRTElLTU5OT05KR49OU1JNTk9MS0pJS0qSk0tLSkmNjkmQjY6PkEpNbk1LSpCEgoaHiIeJiIeIiYuNjYyNj5GWmZlNlpRMUVJTUk+SjZCUS0qTlJabm5eXlpZOT1BPTk5QT09RUlBMkYuKlVJXVlRRT1BQUFFQTUtIRkZGSEtOTktKR4uOSUtLS01PT05MTk5NTk5QTk1Ogn+GfoJ/j36Hf4t+AX+SfoJ/hn6Cf5J+Bn9/fn59ff9+yH4Ef35+foV/AX6Gf4t+jn+MgIZ/AYDKfwZ+fn5/f3+QgIV/A4CAf4WAhH+JgKV/AX65f4Z+438Ffn19fn6Hf4h+hH+JfgF/oX6Jf5x+AX+NfgF/in6EfwZ+fn5/f3+Qfoh/nX6QfwF+jH+CfoR/A35+f4V+hX+WfgN/fn6Gf4R+gn+Jfo1/hH6Yf4J+kn8CAgQAgIGBgoGDhYT+9fL0+fj09/r29vb59Oja0MbBwMHCwcHExMXL1t/r9/r5+vr5+fbz8PDx9Pn9+fHq6Onq7PHz+Pr28evo6Orr5ufs7Ovu9Pr7/oWKioaB+/z46saT4NiBlp2lr66no6CfoKKlopuYn6mwraWipKiqqau0tqifoaesgK+vrqqlo6Gjpaavubyxm6KtrrCyraOdn6itrqajqqqko6Wts73Fv7e0srW1tLq+vb2+vLm3tbezuL66uLi7v7y6t7a4u7u1raqrr7OysrCxta+srKqqrKytr7a+trKzsrK1tra2tLO0uLm4s6ymqq63vcDCxMXEwsHBxsrLycK9b7a3vr/Cx8bJzsSzrq+ztru+v8HCw8G/uba7urq7ubawqaeppaChq7K1ubq+ytPV1Nnf4ufs8vT49/X5/YCB/PuDhoSBgoD68+/l2s/IxsbGztrs+YCFjpWaoaius7i/xsvR19/n7vP0+Pv8+/v6+IX3gPj5+fj39vX09fj7+/v8/f79/oCBg4KA/v37+/v8/vn19PX4+vn59/Pv8PDv7/Hz9PX19PPx8fL08eG5gqqMwZ7W9vz9/4CBgoD9+/r5+fr6+vn/hIWB/oKFgv307uXWzMvKxL+8vLqzrqumoZuYn6mtsLS0saujmpaTj4uE/ff3gPj2+4OKkJaanZuVkpGQj5KTkpGSkpWUko+NjpGTlJeXkIqGhYqRkpCMh4KAhIiHg4OCgoGBgoD7gIaHhYOFiYuKjY2LhoaJjpKUkImHhIKHioyUk4yJio6OjImHiImKjY6OjImJiIqOkY+QkZGNi4+TlZaSkpOOjY+Qj5CTlJWTgJKRkpCQkJOUj4mLkJKUlJKUlpOQjIuKio2NjIuMj46JiYyJhIODio6MjIyIhoOB/vjt1J3gvf256P6A+/qAgoD/+fPx+ID88O3x8fDy9/z39PHt493i8Pf3+Pj18Ozy8vL49/j18vP09vL08u3t7ejo6+/v8vP3/oGEhoD4goX/gO/p8PuA+fX4+Pf6/fv5+PXy7ebm8Pb08e7t8vTq5Nzf5e7s5eHm7/Lw9vf1+vj59/j39Pf7/ICChIeHg/379/Hy8Orn6/H19fiAhIWIiIaDg4KAgICChYeGh4mHgoD/gIKCgYD//oGEhYODiYiBgYOGhIOEhYWEhIL+/P2BhIaDgID49vqFiYmHhoSBgPz7+ffy8fyFiIqLiYOA/P6Dh4qJhoWFhIL+9fDp5uvz9fLy9v3//Pj08O7v8PL18u3r6u/2+v2AgP338u7x/IGBg4WCgP+Ag4OEhYOEhoeIi4+PjI2Oj5GSkYyFgoSIh4WEgoGCgf+Hj5GQjImIio2Nhf77DP79+Pb+h42NiYaDg4BfYGFfX2Fesqyqr7S1sbO1srGxtbKnm5ONiYiIi4yNj4+QlZ+nsLq8u72+vLu2s7GvsLS6vbiup6WnqKqvsLS1sa2opqWnp6Kip6anqq6ys7ZhZmZjXre3s6eMYo+KVGNobnd1b2xqaGhrbmxoZmtzeHZwbW9zdXR1fH1xamtwdHV3dnVxbWxqa2xtd4CBeGludXR2eHRsaGtydXVwb3V1bmxuc3iAhoB5dnR2d3Z9goKEhoWDgH18dXd6eXl6fYB+fXx8foB/eXJxcnZ6eXl3d3p2dHNxcHJ0dnd9hH16ent7fX5+fnx8fH+Af3t2cXR4gISGh4iFiXSLjY2MiIV/f4OChImIio6GenZ3eXuBg4KEhYaFgXx6f4CBg4F+eXNxc3BucHl+f4GBhI+Ympqgp6qtsre5u7q2uLteX7u7YmZlZGVkwbu1rKSclZGPjZKbqLJbXWNnaWxwdHl8gYaKjZGVmZ2goqKjo6Ghn4SegJ2dnqCio6OioaGipaampaWmpqaoVVZXVlSnpqWko6OkoqGhoqSmpqWjn5ycnZycnZ6foKCfnZ2dnqCfmH9WaVN7bJi0t7W1WlpaWK6tq6moqqqqq7FeX1qwW15ds6umn5SNi4mFg4KDg4B9e3dzb2pudHl+goJ/enVubGtpZmG7Mre5vLzCZWtvc3Z4d3NycnFwcnJxb3BwcnBvbWtsb3BxdHVvamdlaXBxb2xmYWBjZmRihGEnYmRivmFmaGZlZmlqaWxsamVkZmpucW9qaWZkaGprcXFqZmVpa2pphGeAaWtqaGZlZWdqbGprbW1pZ2ttcHJvb3BraWtsa2xvcXJxcG9vbm5ucnNtZ2lucHNzcHJzcG5pZmZnbG1ramtubWdnamhiYmJna2tqa2dlYmG+u7OecqKDsYitu162tV1eXrq2sbC2Xraqp6mqq62yt7Kxr6uinqSzube4t7Suq7EGsbG4t7e0hLKArrGxra+vq6mrrq2wsLS6X2NkXrRgY72uqK63Xraztra0tri1tbSxr6ulpq2yr6ypqa+0rKaeoqavrqilqLG0srW0sra1tbOzsq+zuLleX2FjY1+1tbOsqqehoKOqr7G1XmFiZWViX15eXV1eYWNlY2NlYl5duV1fXl5eurpeYWIJYWJnZmFhY2VkhGIqYF9fube3XF9gXVqvr7VjaGdlZGFeXLa1tLSxr7liZGZnZWFeuLhfYmVkhGJoYb21sKyprLO1srK2vL68uLOwra6usbazr62ssLi+v2Bgu7WwrLC7YGBjZGFfvF5fX2BhX19iYmNmaWpnZ2hpa21rZ2JfYGNjYmFfXV5du2VucHFtamhnaWliuri7ubOxumRqamdjYF+AUE9OS0pLSpCOjpKVlI6QlJGRkZKNg3pzbGlnZ2hoam1tbnN9hI6Ympiam5malpGOjY6QlJeUjYeFhoaHjI6Sk4+Mh4WEhYaCg4iHhomPlZWYUldWVE+ZmZWLc09vbUNOUVVcXFlWU1FTV1xcWFZZX2NjYFxaXF1dXGNlWlNUWFuAXVxbWFVUUVFSUlpiZV1PVFlXV1lXUU9SWlxcV1ZbWVJRU1hbY2xpY19eX19eYmRiYmJgX19gYl9hZGFgYmZqaGZjY2ZoZ2FaWFheYmFgX2BlYF5bWVhZWlpaYmtlY2NjYmRlZWVkY2NmZ2dkYFpcX2drbG1ub29vcHByc3JxbWsQZmZpZ2hra290bmRkZ2lqb4RxgHBva2dlaGlrbm9uamRiZGFbXGVrbW5ucn2GiIeMk5SYnaKmqaeio6ZTVKSkV1paWFhXqaWhmZKKg4B9eHh8hIlGR0pLSktNUFFTVltdX2FjZGdpamtsamZkYV9fYGFhYWNlZmZmZWRlZWhqa2tsbm5tbTY3ODc2bW5ta2lnaGdogGdnaWpqamhlY2RlZWVmZ2hoaWdnZmZoamtpWj5IN1ROcIKDgoVDQ0NCgH9/fXx9fX5/hkhKRYdGSEaGgHx1a2ZoaGRiZWhoZWJfWldUU1hcXV5hYmFfXFlZWlpZVKKfo6iqsFxgZWltcG5qaGdnZmdnZmRkYmNhX1xbXmJkZ2xsXmVgXFtfZmdjX1pVU1ZZWFZWVVZVVVdVo1NaW1lZWV1fXmFgXllYWV1gYmBcWlZTWFpcY2NcWVdaW1taWVlZWltcW1hWVVVXW11bW1xcWlleYWNjX15gXFtdXVtcX2GEYoBhXl5eYmNdV1leYWNjYGFjYGBdXFtcX19eXV5hX1hYW1pVU1JYXFtbXFdUU1OkoZmJYIFmkXOVoVCcmU5PTpqSioqQS4+FgYWGh4mNko6NjImBfoSSmJaYl5SNipCQkZiWlpSSlJSVkJKSj5GSjYqMj42PkJWeUVRWT5RQU52PiYCNlUyUkpSTkJGUk5SUkY+Nh4iQl5aTj42Qk4iEgIWKkY6GgYWMjouQj4yQjYyKjIyJjZKVTE5PUVFNj46NiIaDfXp8gIWIjUpOT1JTUE1LS0lISUtNTk1MTUtHRoxHSEhHR4qIRUlKSEhNTUhIS01MS0xNTUxMS5GPj0lMTUpHi4CLj09TU1FQTkxKkpGRkIyKkk1PUVFPSUeMkExQU1JQT1BPTZeRj4uGh4yNi4mNkpOQjoyKiImKjpORi4mHi5CTlEtLkouFg4aRS0tMTUpJlEtNTU1OTEtMTE5QVFVSU1NUVlhYU05LTVBQTk5LSkpKlVFZW1tYVVNTVFNMkZCTkQqNjJVRV1hVU1FRh3/KfoV/hn6Cff9+334Ef39+foZ/jn6yf4WApX+DfoZ/hICKfweAgIB/gICAo3+GfrR/AX73f4V+DH19fX5+fn9+fn9/f4V+AX+yfoR/A35/f4V+AX+xfoZ/jX6VfwF+hX+CfpN/g36Ff4N+iH+Hfod/gn6Jf55+gn+GfoZ/AX6gfwF+i3+Hfod/AgIEAICCgPr7gP35+fj39vf7gYKGh4D08vX29PT5+/v59vHt6+fg3+Hh2s7Dv729v8C/xMS/v8HGzNDT19zl7vX2+Pr49fT08u/s7O/5/fLn6O70gIWIg/329evPounQ8pGgrLKvqqirqKGdnZqanaexr6yusbOvq6apr7GrqqyvrqmloYCkrrGtqKmvuL28vL69v768v8C8tra6uLS7w8XCv7+6tLCxtLKtrK+1trCtq7C7w7y6t66tq6yxt7y8vb/DxsS9vcTHwbmzr66ztrrByMO+wb+7tbOysa6sqautrK6wtby/vLu7u7q6vcG6r6mjoqOkqKu0urazubq1srGxsLCyrwumo6Woq6+zs7GztIS3gLm8wcG8ubq5tq2moZ+ipquvq6moqausq6utrq+vr62ur66qp6Wkn5qYmJyjqq2wtLrCx8fDwMXIytDa4efv9/6BgICChYWDgPfu5+fv8u/n3NPS09HQzsnIzdPa3+Tu+P+ChYmMj5SWmJqeoKKnqq2wtLu/v8DCxcXExsnO09XWEtbX19jY2dve393c3t/d29vd3YXbgNra3d7c2NbUz8u7jbGBg7OFtNvq6ubh2tLKv7WuqKOdlo+Khfvr3tLDtKyppKq92fP59e/m6Orw+4KEiI2OjY6RkpOTlpiWl5mdn52amJmZmpmbnpyVjYyOj5CSkpOSk5KRk5WVk5SVlZeYl5WSkI6Li4mGh4uIgoCChYaEhIaEWYWKj5GQkpSSj46OjY2MiIWDhIaHh4SCgoaIiIeJiIaFiYyNioeIio2Pj4uEg4WKjIqGjJSSjouJi42NjI6Ojo2OjYuLkJOQkZCMjY+NjpCPjY2PkpaTjISDhIYyhYaJioiFhoqMjo2KiomDgYOBgoSFh4iJiIODiIqGgPOAiZCRjoXXkMfWm9b0/v/9/f6EgID/gIWHg4D+/P6CiIiD/Pn9gIKA//z38Ozt9v399+3p8fj59O/v8PL1+fbt6e3u7PP6gIGBgoSIhPnu5e7+gvn4/fv28fH0+f33/ID+//v59Ozx/4CAgf79gYKBgPvz8/j7/fv29PHv8vP08/H19/X2+/6AgoSAgYODgfz29PHz9Efx8PX7/Pj3g4qNjY2MjIuJiYqLjIyOkJGPjYuJiIaHiIiJiIaEg4WGhYaFg4KBgID+gYD+gIGDhIaKjIqIhoWFhIOBgYKCgIT+fP/+/oKEgoODgf338PD1+4GCgv/49fL0+oGEg4GBgYSGhYD3+YCDhYWA9e3s7/Hw8vqDh4qJhoD5+oCCgoSDgoKChIiJiYmIh4mKiYqKiIWCgICBhYmKiIWAgP318vX59/Tu7fqEhYKCgP+ChIKA/v6AgYePjoeDgoKBgYGAYV+5uV+8ubi3tbOytV1fYmRdrquur66vtLa1s7Ctqaeknp2foZySiYWDg4WFhImKiIqMkJSYm56iqLG2tri8urazs7KvrKyvub6zqKitsl5kZ2K6tLOvnHmnj6NeaHJ4dnJxc3JsaWhmZmlyenl2d3t9eXVwcnd4cnJ2eHdzb2uAbnZ5dnJ0eoKGhYSFhIWEgYKDgX1/hIJ/hIiIhIF/fHZyc3Z1cXByeHh0cnJ2f4aBf3t1dXJydHl/gICEiIiDfHyChYN/end1eHp8gYaEgIKBfnl4eHh3d3V1dXR0dHh9gH9/gIGAgIKFf3VuaGhpa29xeH57e4CCfnt5eXd3eXYLb21vcnR3e3x7e3uEfYCAhImKhoKCgH12cm5sbW5ydHFvb3Byc3FxcXJyc3NydHZ1cW5tbmtnZmdpb3Z4e3+FjJKUkY+UmJqfpaqvtbq+YF9fYWVmZWK9tK6stLa1rqafoKKgn5yYl5mcnp+gpaeqVlpfYmRnZ2hpa2xtcXR2d3h6fH19gIOEhYaIio2PjxePkJGSkpGRkpOSkpOUlJOTlJSTkpKRkYSQgI+OjIqGhHpccVBSdFl+naeno56XkIyFfXh1cm1pZWJesqeelop9dnRwdYWdtru3sKqtrrK7YWJlamtrbG5wcXF0dXN0dnp7end0dXR1dXV4d3FramxucHFxcnFycXBydHVzc3R1dnd2dHFvbWtraWZnbGpkYWNlZmRkZmNkaG5vBm9wcnBubYRsZGlnZWZoaGhlYmJkZ2hnaWloZ2ttbWtoaGttbm5qZWRmamtoZWlxcGxqaGlra2pra2pra2tpaW5xbW9vbG5wbm5vbmxsb3J1c2xlZGdmZWVlZmlramhoa2xubmxsa2VjZWNjZGSEZoBiYmZoY12vXWdub21oqW+TnHOkvcPBvLu9X19eXrldYmNdWrKxtF1hY2C5trpfYF+9ubOtqKmxubm1rqu0ubm0sLCxsrO1sqursbOwtrpgYGBiYmRfs6qkq7lft7S5trKvsLO5vLW4Xrm7ubq3sLS/Xl5et7VdXl5euLGxs7W3t4CzsrCvs7W2tbGzs6+vsrVbXF5cXmFgXreysa6wsKyqr7S0sbFgZmlpaGdnZmRkZGNjYmVoaWlnZWRjYmNkZWZlY2FfYGFiY2NhYF5eXrtfXrhcXV5fYGNmZGNhYGBgX11cXl1ct7m7vL67ul9gX2BgX7qzrq20uV9gX7q1tLO1uiRfYWFgX19hYmJft7pfYWNiXbGqqayura+1YGNnZmRet7hdXl+FYQdiZWZlZWRkhWUvY2BeXFtdYWRkY2BdXrmyr7O3tbKsrLhjZGFgXblfYWBfu7teX2ZsamNfX19eX1+ATUyTlU2ZlpaWlJGRk01OUVNOj4yPj4yNkpSRj4yJhYSBfX1/gX10a2hnZ2tub3N0cG5ucnZ6e32Bh4+Vlpmdm5eUlJKPjI2Qm5+TiImPkk1QUk+YlJWRf198anxJTlddW1laXVxVUVFOTVBaY2NgY2dpZWBbXWNkX15hYV9cWFNhVF1gXFhaYWdqZ2VlZWdoZ2hpZWBhZGFdZGxvbGlpZmFdXmBfXFpdYWFdWlldZ29pZ2RdW1hXWFpdXV5haGtnYWJnaWViX15eYmRma3BtamxrZ2JhYWBfXlxdXl5fYGRoaoRogGZmaWxnXlhUVFVWWltiaGRiZmdkY2NkZGRnZF1cXV9fYGJhYWJjZWZmZmltcXJtamppZmBcW1tdXmJjYF9eXl5gXl5fYGBfXl5fYmFeW1tbWVZWV1leY2RlanF4fX16eoCBgYaPlZyjq7JaWVdYWVhVU6CbmJifoJ+YkIiIioqLEomEgICAf3t3d3t+QUJERkhKSoRIC0lKS0xNTlFSUlNVhFaAWFtdXl1dXl5fXl5eX2BgX2BgX15eYGFhYmFgYWFgYmFgXl1dXV9YQEk0N1JAW3SAgHt2b2pnYFpYV1dTT0xJR4mCfXlyZ2FfXGFwhZuhnpiTlpecpldYW19hYGFkZmhobG1sbXF1dnRwbW9ubm1ucW9pYmJkZWZoaWppamhnaWuAa2hpa2trbGtoZmRjYF9aVVZbWlZVWFtcWVlZVlZZXl9fYmRiYF9fXl9gXlpXWFpaWldUVVhZWFlbW1lYXF5eW1laXF9hYl5ZWVpeX1xYW2NiYF5dXmBgXV5eXl9hYF1cYWRhYmJfYGFfYGFhX19hY2dlXldVV1hZWVdXWltZVleAW1xeXVtaWVJRVFJTVVZXWFlYVVZcXVdSmFJbYGFfWpBZcHpeiJyhoJ+golNTUVGfT1JTTUqTkZNMUVNPlpOZT1BPnJmUj4yNlJmXkoyJkpmZlpKTlZaZnJuTkZKRjZOYTkxMTU9TT5GIf4WUTZKRlpOOi42QlpqSlUyWl5SUkImAjJZKSkqRj0pLS0qOhoeLjpCPjIyKiYyNjo2Ljo6KiY6RSEpLSUpLSkmNiIeDhYSAf4OJjImIS1FUUlFQT09OTU1MS0pMT1FRUVBPTk1OT05PTk5NS0xMTE5OTEtJSEmSS0uSSktLTE1QUlFPTkxNTU1LSkxMSZCPj5CRkI9JSkk3SUlHjImHipCTS0xMlJCOjpGXTlBPTUxNT1FRTI6RTVBTU02OhYOGiIeKkE1QUlJQS5CRS01MTYRLO0xPUFFTU1NUVFNTUk9MS0pJSk1QUE5KR0iQioiLj42LhoWPTk1KS0mTTU9OTJWWS0xRVlVPTEpJR0hKBX9/fn5/iH6Ff71+hH+GfoN9/370foh/mX7Cf4R+lH+Vfv9/xH8BfoZ/BH5+fX2IfoR/AX6Ff4N+hH8Gfn5+f39/nn6Hf4V+AX+MfgF/iH4Ff39/fn6Ef5Z+iH+Nfql/BH5/f36Tf4d+hn+GfoN/hn6Kf4J+hX+IfoZ/gn6hf4p+hX8BfoR/gn6MfwICBAAdg4OA/v2Ag4SDg4SGhYWGh4iKioeDgoOFhIKAgICF/oCAgPz19vf19O7n8/r59/Hs6+Xd2tjTy8K6t7S4vL/BwcG8uLq9v8THxsbHyMrO1Nnc3s6l9tTnhZefqba5t7WooKCim5qdnJeUk5OVk5GTl52eoaSoqKajpKirrK+yr7G1saqqrKyvs7W0sK6vraurs7m7vbm0sa+xtLGtra+vsUu0uL/CvbawrrC2wMG9vcHAurOxsbGvraqmqbCys7W8wLq0r7G0vcLAu7Wysrm+vrq3srGytLa1trGusbS3vcLGycnEw8fFvbi1srGEsEGurq6ytbWzramrsbrBwcC9uLSyr62sq6uvs7Sztbe2srCyt7auqa62tra4tLGwqqanrbKzsrK1uLm3tbWyr66troavgK2tr7CxsbG0uLm3tLGys7OvrKqloqOnqKemq6+yt7q6t7S1ur+9ura5v8LBwsPExMTGx8bJy87T2d7k6/X37uDUzszGvr3EysrQ087CuLe2s7Curq2srK6zur+/vr+5q6WioaCenp2emYeBipecnZ2alJGTl5udnqCho6Whkvz0gIujxOf7gP6BhIiMkZGPjo2NioaGiIeGh4qMioyRlJSVmJiVlpiYlpSSkpKTlJaYmJeXl5WVlJKVmJiWlZaXl5eYmpiWlJKSkpGQkJKUlZOSj46Rk5ebm5iWmpqYl5eVl5+mpZ2VjoyRk5aXl5OQj4+OjYyKh4SDh4iIhYSEhIWHFIqKiISAgYKChYuOjo+Tk46NjY6OhI+AkJOWl5eTkpWXl5eWlZOVmJiVkI+RlZWOiYqSnZuRioiHiYyQk5SVl5aTko2FgYGFjI+LhYWLjZCTk5GSk5KPj46MjY2Ki46RkI6PjoqNlJWUk5GRlZeVk5GOi/rGjdblmcf1hIODgf/8+fv7gYSC//r3+Pj2+4D8+/z8+PLs5+4W9PqFiIaC/4KEhoiIh4SFhoaFg4OGiYSKgIuGgf758/X6+/jx7Ozr6/Dw8PT07+nl7ff9gIGChIeLjYiA8+7v6O307unu6+Lf4uHh5OrzgIOBgIKC/Pz9+Pb19ff7/f+EhYWHhICAgYKCgoCA/4GFiYuKiYiIiYqMjIuKiYiHhoWEhIWIh4WC/vr6/Pv4+fv8gIGDgf7+/fj3P/b3+//+/Pr8/4CBgoWGhYWGh4iHhoSDgYKFiIiGhIOCgYGBhIeD+vX19fPx8fDu7Ovt8fX39fT4+/yChYWEhISDKIKA/4GB/fb5/4GCgoGAgISHiIaDgYSGhYODgoGB/fn5+v6Cg4SEhIKEgCeB//Xs6vWCioyIhYOBgYGDhoWEg4KAgIWHhIKAgIGChYWEhYiJiIWAYmFfvbxgYmNiYmNkY2NjZGRkZWJfXl9gX11bW1u0tbW2tltbsaqrra2vrKe0vLy5sa2sp6GfnZmUjoiGg4aJi4yNjYiEhouNkZOSk5OSlJedoKOnnHy1maVeam50f4J/fHFqamtlZGdoZWNjYmNhX2BkaWpsbXBwbWtscXJzd3o0eXp9eHJwcHF0eXx7d3R0c3N0en+Agn97eXd4eHRxcXR2d3p9goR/eXV0dXqCgn+BhYR+eIV1gHNvcnd4eXl/g397eHp8g4aFgXx6eoCEg399eXh5e3x7fXl2eXt+goaHiYqFhYiHgX58enl3dnd3dnZ2eHp5dnJvcHV8gYGAfnt5d3VzcW9ucXV2dXZ5eXZ0dnp5cm1yeXl6end2dXFtbnN2dnV1d3p8enl5d3VzcnN0dXRzc3JwgHF0dXd3eHp9fXt5d3h3d3Rzcm9tb3FycXF1eHp+gYODgoOFhoaFg4aMjo6OkJKTk5WWlZeZnKCkqKqvt7u3rqWhnJWLiY+WmJ2hnJGJiIiHg4F+fHt8foOKj5KSkIh9eHd2d3Z2dnVvXlddZmpsbWxoZWVnaGhpaWtsb29lqqRgMnSRssZlyGRmaWxvb25tbWxpZWNkZGNjZmdlZmptbm9xcW1ucHFwbmxsa2xtcHNzcXJyhHFDdHd2dHN1dXV0dXh3dnNxcXFwcHFydHV0c29ucHJ1eHh0c3d4dnR1dHZ8goF5cmxrb3Fxc3NvbGtsa2pqaWZiYWVnZoRlFWZnaWlnZGFhYmNkaWppam5uamlpaoRrgGxscHNzc3BvcnNzc3JxcHJ1dHJsa21wcGtpa3F7eXBramprbG9xcnR2dnNybWVgYGNpbGlkZWpsbnJzcXFxb25ubmtsbGlqbW9ubG1saGtxc3Jxbm9ydHJwbm1sw5hnkpxskLRhYWFfvbq3uLlgYmC7trGys7O5Xru7vb25tK+rgLG3vGRmZWLCY2RlZmZkYWNkZWVkZGZoaGZlZWZiYL25tLa8vbqzrq6sq7Cvr7S0sKqnrra7X19hY2VnaWVftLKzrbK4sq2wrqWipKSlqbC3YGJfX2BftrW2srGxsbS4u71jZGRlYl9gYWJjYl9fvmBiZmdmZWRlZWZnZ2dmZ2dmgGVjYWFjZWVjYLiysLGvra2vslxeYF+8vLq0sa+usLS0s7O1uV5eYGNkZGVmZmZlY2JgXl9hZGNjYWBgYF9gYmRiure2trSysrGvrq2vsrS3tbW4urphZGRiYmFhYWBgX71gX7iwsbZdXl9fX2BjZmdlYmFkZmRjYmFgX7u4t7a5HF5fYGBgXlxcXF1dtq6mpa9eZWdjYV9eXVxeYGCEXhFfZWhlY2FiYmJkZGNkZmdmY4BPTk2YlktNTk1MTE5NTE1NTk9RUE1OT1BOTUxMTJWVlpaWSkqQiYuOjo+LhpObmpWMhYJ9d3R0c3FsZmNfYWRpbXBxbmpudHh8fn59e3l4en6Dh4+HZ41zf0lSVFljZ2ZlW1VWV1FRV1ZST09OTkxKTFBVVVdYW1tZVlZbXV9jZIBhYGNeVVJQU1hdYGBeXl9dWlhaXV5hYF9cW1tdWFVWWVtcX2JmaGJdWllbYWlsaGltbWhiX15eXVtYVFVYWVtcYmVhXVpcX2VpaGVgXl5laWpnZWJhYWNkYmRfXV9hZGdqbG5uamtvbmhlY2FgX15dXVtaW15hYWBaV1hcYWVmZYBkYV9dXFxcWVhbXl9eYGNiXlxdYmJbV1pgX19gXl1dWFZXW15gYGBjZmhlY2NhX19eX2BgX19fXlxdXl9gYGBiZWVjX11dXV5cW1tZWFlcXVxcYGNmamxubmxrbG1tbGtvdnl6fYCCg4KDgn5+f4KGi5GXnqmtp5ySjYuGfn6FikWLkJSRh4B+e3dybmxpZ2hrcHZ6enh0bWNeXV1eXV1cWlVHQklSV1laWFVSU1RWVlZYWlxeXFOKh1Flhai9YLxeYGJlaGiEZl1jXVtaWVhZW1xZWV1gYWNlZGFiZWZmY2FhYWJkZmlpZ2hoZ2hoaGxwb2xsbWxsa21wb21raWlqaWhpamxramlmZWhqbXFxbGtvb21sbGpqcHZ1bWZgXmJiYmNjX1yFXYBcWldXWltaWVlaWVhZW1tZVlRVVVVWW1xcXWFhXFpbXF1dXl5fX2JkZGRiYGNlZmZlY2JjZWRhXFteYmNfXFxia2lgW1taW1xgYmNkZmVhYVxVUVFUWVxYUlNZW1xfX15fYWBdXFpWVlhWV1pcWlhZWVZaX2BeXVxdYmRiYWBgX4CpgVV0fFh7nVRTUlGin5ubmlBSUJuZlpiamJxQm5mam5eTjYeLkJVRVFNPmk9QUFJTU1FSVFVUU1NVWFhXVlZWUk+cl5GSl5iWj4qKiIeLiomNjYqFgYePk0tNT1FUVlhUTY6Ki4WKkIuGiomBfoB/foGGjU1QTk1OTpSTlZCOjXqOkZaYmVBRUVJPS0tMTE1NS0qUSk1RUVBQT09QUVNTU1RVVVNST01NT1FRTkuQjY2Pjo2OkJNLTU5MlJOSjYuJiIqOj46PkJRLS0xOT05PUFFSUVBOTUtNUFNUUlFPTk5NTk9RTpKQkpOPjYyJh4aHio2PkI2Mj5CRTIZQDU9NTEqUTE2VkJOXTE2ETkRQU1RSTkxPUU9OTk1MTZeVlpWXTU5OTk1LSEhHSEmOhX58hEhPUExLSklHRkdIR0ZGR0hKUFNRT01NTE1PT05PUVJSUAV/f39+fph/hX6Cf7J+g33/fv9+336CfYV+An9+/3/tfwh+fn59fX5+foR/hX6Df4d+AX+LfoR/AX6Wf5d+iX+SfoZ/i36NfwF+mn+JfoR/jn6df5R+i38Dfn9/hH6Uf4V+i3+FfqF/AgIEAEiD/v39+fj3+PyChoaC/fX1/YSJjo+LhIGAgf/8+ff29fX5/f/++fTx8PTz8O/y9Pj++/f6/oCAgoOFiYyLioqIh4iJioiGhYSFgzqCgOrAmOfGzuyNo6uus7Ozsra6u7ausLe0raWjpaGamZyfpKOempiboKOlop+jr7SupqGfoKGgoKauhLEfs7Ozsa+vsbGwrq6wsq+usbKtqKanr7Oys7OxsLCxsYSwgLW3tbe9v7/AwL24tbKxsrO0tre3t7m6vb67uLi5uLazs7e7vr++vr/BxcfHxcK9t7W0tLa4ubu8vcHDwr69ubGpqayvs7W3t7e4ubm3t73GycO5tLS1s6+qrK+0tbOvraywtLi7v8bJy83NzMvIyMjGw8TGyMnIw768u7u8vsHAgL69ubWzsrGytbm7vsC8uLe0srKyrqqqrKurra6vrq6xtbu+wcPBvrq0r6yrrK2sra+wr7CxsrKytLW3ubi3t7a2trW2u7+8u7u+v7+9vb6+vby9ua+ppqGcm5+fmZeZmJeYm5uXk5COjI2Pj42MjIyNj46NjpOZnZ+ipaquqaOnNq6zt7aupKOnppqF5NLzn8frhYqLjY6OkJOXmZWOiYqQkpGTlpSRkI+OjIuMjI2Li4uKioyPkoaUDZOSkZKWlpORkpKTk5OGlHaVk5KTlJSUk5OTlJWXm5qXlJOSkpGTlpiamZmWkYyMj5OVlJKRkZOXmJiaoamjmJWYlpORjpGUk5GQkZSXlpWQjY2QkZOWl5aVkpOZmpSPj5CQj42KiIaHiImKjJCTlJWXmZqbnZ6al5OSlJSTkpOUlJWUlJaWhJQOkIqGhomOk5WRi4mPlZWEk4CVlpWUlZSTk5KRkZGSl5iWmJycmpeWl5iXlpeXlZKQkJSXlJKPgsuV3teItN77g4L++/n7/frx7vD2/v/+/4D9+vr6+fn49PL6gYOC/fr8/vz07+zn5ujq7fD29/by7/Dz8/Lw8PX29PTz8vHv7ejn6+zt7u30/oOB+/z++vb2+Fz7/4OA+fj7gYOCgYKDg4GA///27u3z+/3/gP/18PL3+/348/Hx9vj18e7t7vL3+vv/hISDhIWDgoKCg4WHhoWFgoGBg4ODhISDhIWFhYSDgf349/v49fT19/qAg4WGhIiAhoH+gIKB/4CChYaFg4KDg4OCgoD5/IGDhYSB//v5+v6BgYD69vj8/fz7/v/+/oCCgoKBgP/9/4OEg4GCg4SGh4WCgICBgoSFhYSDgoKCgYCA+/Xx8fb48u/0+fj39ff/goGCg4aIiouMjIyIg/rv7/f/g4WFhIOB/Pf6/f78gIMShYiKiYWDg4KBgf77+/v9gYeJgGbFxMK8ube3u2FlZGG6s7S6YGVoaWVhXl5fvLi1s7GwsLW5u7u2sa2rraumpairsLa0sLO2W1xdX2FlaWdnZ2RiY2RlY2JhYGBfX15dXVuniW6iio6jYXB0dnt6eXl8gYJ/eXuCf3dvbW9sZmRmaW5ua2dlaGxvcW5sb3d6dW1pgGdnaGdnbXN0dHR2eXl3dXJzdXd3dnZ4enh4enl0bmxudnp5enp5eHd4d3Z2dXZ6fHp7f4KCg4OBfXp4d3d4eXp8fHx+foGBf3x8fX17eHh7foCBgYGCg4aIiIeFgXx7e3t8fn6AgICDhIN/f354cnJ0d3p8fX19fn9/fXuAiYyHZH55eXt5d3NzdXl6d3NycXR3e32Bh4qMjY2Mi4iHh4WCg4WHh4WBfnx7e3t8fn58e3l1dHNzc3Z5fH+Afnp4dnV1dXJvcHBvbm9xcnN0d3p+gIGCgH56dHBub3BxcXJ1dnd5e3yGfRB8e319fX59foKEgX+AgYODhIKAgYB/e3Jta2hlZWttaWhpaGdnaWlnZGJhX19hYmBfX19gY2NiY2VoamxucXV5dnJ1en6Af3Vram1uaFyfj6l2mbprb21ubm1vcnZ4dG1oaG1vbnByb2xsbGtpZ2hpaWhoaWlpa21vcXFwb3BwcG9vcXV1cnFyc3R0c3N0c3JycnOAcXBwcXFxcHFxcnN1eHd0cnJxcXBzdXd5eXh1cGxscHJ0cnBubnBzdHR0eoJ9c3F0c3Bta29ycnBub3J0c3Jva2ttbW5wcW9tamxydG9sbGxramlnZmRlZ2lpa25wcHFyc3Z4e3t3c29ucHJxcXJzc3Rzc3V1dXR1dXFsaGlrbnJzc29qaW50dHJxcHFyc3JxcXFwcHBvcHBxdXd1dnh4dXNzdHZ0cnJxb2xrbHBzcG9vZZhqlI1dgaK6YmG7t7W1trStq62zuLi3uV25t7e3uLi3s7G3YGFfube7vbqyrquoqKmrrrG2t7ezsLC1tbW0sra1tIS1gLOvqqirrK2tq7C3Xl63uby5tra3ur5hXrW0t15gX19fYGBeXbu8tKyqr7S2t125sq+yt7u9ubSxsLS2sq2qqamtsbS0uWBhYWJjYWBfXl9hYmFgYF5cXF5eXmBhYWJjZGNjYmC6s7K0sa6trrCyXF9iY2NiY2RlZGNhXLdeYGC+CF9hZGRjYWBhhGJyX7i6YGNkZGLBvLm6vWBgXreytLm6uLe5ubi5XV9fYGBhwb/AYmNiYGBgYWNlZGFfYGBhYmNiYmJhY2NjYmC8trKyt7m1s7i8ube1uL5hX15fYWRmZ2doZ2Nes6qrs7pgY2RjYmC6tbe5urpfYmRnaGdkhGIJYb+9vbu9YWdqgFanp6WgmpeXm1JVVE6TjI2STlRZWlZRTk5OnJeTkI6NjZKWmZmVkIyLjIuHhYiKjpSRjI+QSElLTVBVWFdWVlNSU1VVU1JRUFBQUVBPTUuHalJ2YWd7TFhaW19fXl5hZWViXF9jYFpXWFxZVFNUVltcWFVSU1RVVlNRVV5iXldVgFNUVFNSVVlaWVdXWFhaWVdXWFlYV1ZYWlhYXFtWUU9RWV1cXFxaV1haWllZWVpeYV9hZWdnaWpnYmBeXV5fYWNkY2NkZGdnZWJiY2NhX2BjaGpramlqa29wb29taGNiYmFhY2NkY2Nna2toaGVeWVlbXV9gYWJhY2VlZGRnbW5pbWJhYmJfW1ZXWFxdW1hXVlldYWRobnFzdHRzcW9ubmxqamtubm5qZ2RjZGVmaWlnZmNfXVxbXF9jZWhpZmJfXFpZWVZSU1NRUVJVV1hZXF9jZmhraWZiXVpYV1laWFlaW1tcXmBiZGVmZmVjYmOEYmdjaGtnZmdoaGViYmRmZ2hpZl1YV1RQUFVWVFRVU1BRVFZVU1FOS0pMTEpIR0ZGSEdGSEpOUlVYXGBiXVhaYGZrbWVcW11eV02CdI5niKZgZWVnZ2VobHBya2NdXWJkZGdqZ2VkZGNghF+EXoBdX2FjZGVlZmdnaGdoam5ubGtrbGxsbW5ubWxrbGxqaGhpaWloaGhpa21wb2xqaWloaGttcHJxcGxmYGBjZmlmZGFgY2ZoZ2ZrcmxjY2ZlY2FfYmVlYmBhZGZmZmJgYGJiY2ZmY19aWF5hXltcXVtaWFZUUlNUVldZXF9fX2BiY4BkZmViX11dYmRjZGVmZ2dmZWZmZGNjY19bWFhaXmJlYVpYXGFiYGFgYWNlZGRlZWRjYmBhYWNnaGZoa21qZ2ZmaGZkZmdlYmBgY2ZjYmJZhVx7c01rhpxTU6GfnZ6hn5eUlJidnJyeUJ6cm5uampuXlZpQUE+amZudm5WRj4qJimCLi42SlJWSj4+SkpOSkZWWlJWTkpGPjIeFiYqKiYeLkUpJjI2QkJCRk5aaT02SkJJLTU1NT1BQTk2ampSMiY2TlJdNm5SRlZmcnpqWk5KWlpKNioqMkJSWlZlRUVFSUlCETw1QUlBPT0xKSUpKS0xMhE0oTExLSpKOjpGOiomJiYxIS01OUFBQUVJRUU9LlUxOT5pMT1FRUE5OToRPOEyRk0tMS0tJkY2Jio1ISUmQj5OanZybm5uZmU1OTk5NTZmYmk9QUE9RUlJTU1BNTE9QUVRVVFNThFIYUFCclpKRlZeSj5OXlZOTlJhNTExNT1FShFMaT0qKgIGJkEtNTU1MSo+Ki42OjUlMUFNVVFGETwlOmpiYmZxRV1oBf4h+hH+Efol/m36af4N+hH3/fv9+5X4GfX19fn5+/3/xfwR+fn19hH6Cf45+AX+KfoN/q36Cf4l+BX9/fn5+iX+JfgF/l36ff4p+jX8Ffn9/f36Nf4J+hX+FfoN/i36Gf4N+mn+Pfo1/hX6Gf4Z+jH+FfoN/AgIEABiIh4eHiIeDgoKB/vv4+YKJiouLiYaHhoKFgAH+hP+A/P38+vj17+zu7u/u7e7v7/H2/YCB/vv6+/v9/fz8+/yAgoWIiIeGg/rq0KyF18/i+4iTn6ivs7S4vsC9uru9vr++vLexsbe5ubq8v7i0ubqtoqOnrLCzs7O1uLWxrqmmpqOfn5yWkpairrSyrammqq2vsbS7v7u2t7y9ubS2t7WAtrq9uK+qqq61u77BxMO/vLm4t7m7ubi3tbXD0djUy8XCwMHEwry3t7m7vrq0rq+0trW2tre5u7y7uLi3s66sqq60urixsLKxsLCxsK2tsba7wcPBvLiyr66xsrK3u7u8vLy9vby6ury+v8DAvLi2tba3uLm1t8HMzMXAvr7AxMOAwL25tra4t7a0s7a4trOys7W3uLi5ubm3tbW0t7u9u72/vbq1sK2srrO4urq6vL27t7GurrCwrq2trq2sqKakoqCfn6Cjpqissrm9u7i2tra4u768ubW2u7/CxMbHxcG3r6qnpaaqq6mjoaCgoaKhoaSnqKinp6KgoqSnp6Wlp6pvqKWfl5KQkJSanqWrrKijnp2dnp+kqaijnpmRhfPpg6DD4/qDhYeKjI2NjIqIhomOkJCNi4mHh4iIiYqIiYqKi4+RjoyMkpeVk5SUlJWUkIqIiIiKjI2LioiJi4yNjpGTk5KQkJGTkpKRkZKUlpaUhJEqkI6LiYeFhouPkZSWlpiYl5eXlpWVlJSTkZKSlJOSkpSVlpWTk5GPjYyMhI5GjIuNjpCSk5iampydn56ZlpWYnJuZmJeWlpibnJucnp+fn6KinZeWmZ2ipqSioZ+dmZaWl5qcm5mWlpeal5ebnZuZlpSVlYSUgJOTlJSUk5SUlJaYlpSUlZSSkZGRkJCUl5mVk5OSkI6Nj5CPi//gu5Xz6oqryN/w+Pv+/vz8+PHu7Ojn6Ovv8/X2+Pz///389vTz9e/m5+75/4D/+/f09ff8/oCCgPv39fDv7+zn5+3x8O7s6uvu8fHu6+nr7/Dv8PDx8/T18u3ub/Dz9PPx8PHz8e7v8PDx9vv8/P3+/Pz/gYKBgICBgYCAgoWHiIiF/vr4+4KCgYGDhYWEgfv39/T09/f3+Pn38/T7hIeGhIKCg4SEhIOCgIGA/vz8/v+AgYKDgoGA/v+Bg4WFh4mIhoWEgYGBhIP//IX9BP6AgoKEgHeBg4eLjYuJiIWBgIGCgoGBhIaHiImJiIiJioqJiYmGhIKA+/yChIWFhoaGhYeJiIaGh4iJi42MiYaFg4KDhYqNjouJiYqLiYWCgISJiouIhP729vn7/vv39vf+gYOEhYiLi4iEgP6AgoGBgoOEhoWDgoWJiomIiEVmZWRkZWRhX15ct7a0tF9lZmdnZWNjYl9eXl9fXry9vr/Avr6+urawp6Olpaempaipqq21u15et7SztLW1tLKwr69ZW1+FYoDDuqODYZePnK1cYmlvdHh6foOGgoCBgoOFhIN+eHh8fXx8foB8eX5/dGttcnZ6fX59f4B9enh1c3FtaGdkX11gaXR6eHJua25xdHd7gISBfn6Cg398fn99fICDfXZxcXR5fYCChYSAfXp5eHt8fHp5d3eEkpmWjYeEgYKFhH56egl8fYB9eHR0eXmEeHZ5enx7eHd2dHFvb3J4fXx5enx8enl5eHR1eHx/goSDgH57eHh6e3p+gYCBgYGCgYB+fX19fHx9e3l4d3h4eXh1dn6IiIJ/fX2AhIOBfnt5eHl4d3V0dnl4dnR1dnh4eHd3eHZ1dHR1eHp4eXt6eHRwbm9xd3x+hH8Ifnt3dHR2d3WEdD9zcG1sa2lpa2xucHBzdnx/fnx6eXl6fH9+end4fICDhYaFhIB4cm9ubW90d3Vxb25ub29ubXByc3JycWxpam2EcHFzdXVzb2lkY2JmamtwdHRxbmloaGlrcHR0cG5rZV2poV13lrHEZ2hpamxtbGxsamlrbm9ua2hnZWVlZmdpaGlqamtvcG5sbHB1dHJyc3N0dG9qaWpqa21ubWxqamxtbW9xc3NycHBxcnJxcHBxcXFycYRvEm5sa2lnZmdrbnBxc3N1dHNycoVxY3BvcHBxcW9vcnN0cnFxb25sa2tsbGxraWhpaWtsbXJ0dHZ3eXl1cnFzd3d1dHJyc3V4eXl6ent6e319eXVzeHyChoOCgH17d3NxcnV4eHZ0cnN1c3J3eXh1cnBwcG9vcHBvcIRxC3N0c3R2dHFyc3NxhW+Ac3h5dnNycG5sa21tbGm+pYdoopteeI6fq7G0t7m5uriyr6unpKWorK+wsrO4ury8u7azsrOupaarsrVbtbOysbGztrhdX122srGtrKuppaWrr6yrqaiqrrGyr6yqrLCxsLGwsbKys7Crq62vrayqqayuraytr6+wtbq6uLm6urmAu19fXV1dXl5eXV9iZGVkYLWysLNeX15gY2RkYmC3srCtrbGztLa3tLGxtmFkZGJhYWFjY2NiYF9eXrm2t7q7Xl9gYGBeXry9YGJjY2RlZWRjYmBfYGJguba5u72+wMFhYmJgYF9fYGJlaGlnZWRiX15fYGBfX2FjY2RlZWRkZmddZ2dmZ2VjYV+6umBhYmJjZGRjZWZlZGNkZWZoampoZmVjYWFjaGxsaWdnaWpoZGFfYmZnZmNftKysrrG0s6+vsrpgYmNjZWlpZ2RhwWFgX15fYGFjYmBgY2doZ2dmgFhYV1hZWFRTU1KhnZqbU1laW1tYVlZUUU9PUFBPnp6dnZ2bnJqWkYl/e3x8fn5+gIKEiI+YTUySjYuMi4qJh4aFh0ZITFBQUFFTqaWTdVN6bG92P0ZPVltfX2FlZ2VkZmhqbGxqZmFgZGRiYmNmYmBlZ11UVlpdX2FhYGFiYF5cgFhWVlNPTkxHRUhRWl9dWVVTVVhaW15jZmNfYGRlYV5gYV9fY2VfWVZWWVxeX2FlZmJgXl1eYGJiYGBdXmlzeXZvbGtqbHBvaWVlaGpraGJfYGRkYmJgYGFiY2JfX15cWVdWWV9jY2BjZmViYGFgXV1gY2Zpa2llY2BdXV9fX2Rnb2doaGdnZmRiYmNjY2RmZGNiYWFgYF9cXGNramViYWNlZ2VjYmBeXl9dW1hYW15dW1lbXWBgX15eX11bW1pcX2BeX2FgXFlUUlJVWmBiYmFiY2FfW1hXWVpaWltdXVxaV1ZUUlBRUVNUVFdcYWVkYoRhPmNlZGNgYWVoa25xcnFtZV5aV1ZYXF9dWVhXV1hZV1daXV1dW1tYWFteYF9dW11dW1ZQSkdHR0pPUFVZWlhWhFOAVFhcXlxbWVZNhXdFXXyYrVxeX2FjZWRkYmBgYmZoaWdmZGFfX19gYWBhYmJjZmdkYWJna2lmZ2hoaWpmYV9eX2FiY2JhX19iY2RlaGpqaWdmZ2loaGdnaGlqamhlZWVkY2JhX1xbXGBjZWdoaGhnZWRkYmFiYmNiYWNlZ2dlZGUKZmdmZmZjYV9fX4RggF5cW1pbXF5jZWVmZ2hoY2FgYmZmZGRkY2NlaWppamtsbG1wcGxmZGpvc3Z0cnBua2dkZGVoamlnZGRmaWdmaWppZ2RkZWVkY2RkY2NjZGNiY2NjZWdmY2NlZGJhYGFfX2JmZ2NhYWFgXl1fX15bpo91WIJ2SWB2hpCUlpmcnqGegJeSjoqHh4mNkJGTlZufoaGel5WSlJCIiZGboVCempaVlZebnU9RT5qWlJCNjYmFhouPjoyKiYmMjpCOi4qNkJCNjY+SlpiYlI2NjpGRjouJioyMjI6Pj5CUmZmYmZuamZtPT0xLS0xMS0pLTlBQUU6RjYuNS0xMTE9QUE9MkY2OQ4yLjYyKi4yMi46UT1FQTUxMTE1OTk5NTE1Nl5SVmJdMTU5OTk1MmJpOUFFRUlNTUVBPTUxOUE+Wk5aYmZmanE9QT02ETHJPUVRUUU5NTUtLTU5OTEtNTlBRUlJQUFNVVlZVVVNRT06Xl09QUlJUVVVUVVZWVVRVVlZYWVhVU1JRUFBTV1lYU1BPUVNSUE5NT1NUVFFOlI2Mj5KUkY2OkZhPUFBQUlVVVFBOm05OTUtLS0xOTUxNUVeEWYp/hH6Pf5h+gn+Lfoh/hX6Eff9+/37tfoJ9hX7/f+1/hH6Cfad+AX+IfoN/un6Pf4R+iX+Ofo9/hX6Hf4J+j3+Ifql/gn6sf4t+in8BfpF/AgIEADaGg4SGio6Pjo6NjYqFg4OB/4GCgoGA/f3/gYKFh4aEg4D9gIKB+/b09v2Bg4ODgoD69/j6+/6EgICBg4SEg4WHhoOBgPjozq2N5dHX7omZoqSmqq6xuL/BwL68uLa4v8PFys7Kw7+/xMG2rKuusLCrp6eprK2rqKekoZ2amp+io6aqrq+tqaejoJ6eo6uys7CppKCcl5eZnqatsrKuq6utrq+ys7Gws7nAw8XGxcXEwb24tbO1t7q6t4C2uLy8ubazs7e7vb6/vrq3t7a2uLq5t7i7vr26u7u7vLy7uru+wL+6ta+sra2tr6+vsbW4t7KvsLS2tbO1uLi3trOvrrGztLa4tLCwtLm+wsPEw8PBvLexra2xtba3uru8vLy+wcXIxsXEw8TJzM3Jw725trSysK+xsbCwsaugl4CZn6Wopp+amJmdoKOlp6y0vL+7trGvrqypqaekoJ6fn6GhoZ+dnKKrsrOvrK2usK+tq6ehmpKNkJmgpKetsbW0tLKysrOzrqmoqaWhnZycnqOprK6wsrS1tre2tKuflpWZnZ2eoKCZlZWdp62urKqnpJ+cm52goqGdlZCNjY+Sl4CXmp+jpaWhmIyDhJOuzef+i5SWlJOUlJSTkY+PjouHhYeJi4qKi4yOjo2KiIuNjYuLi4mIioyLiYeFhIKCgoOGioqJhYWIjZKWl5aVlZaVlJKRkZKTkYyIhYaJioqLjo+OjIqJjI+QkJCRkZKRkJCRj4yLioyQk5ORj4+RlJaYmR2ZmJiZmJWSkpOXmpqXlZaZm5uamZiWlJGPkJOUlYSWKJeVk5OUlZaWlJKTl5qYlpeZl5aWmJiYmZqamZeXmpybmZeWlZWUlJSElRuUlZSXnaCgn56cmpiYmp2fnJqam52alpSVl5iEmYCYl5WUlJWVk5CRlZaVk5OSiem1i/D9l7PS9YH9+vr8/fv49fb08/T09PPv7Ofm5unq6OTg3uDk6e3w8/b39vb08fDy8vT4/Pnz7+3r5+Xn5uHe29vd4uXn5+Ti4+Hh4uLj5+vu7u/v8fuCgPn4+PTw7uvt9fuAgf/48+/t8vn8/Fr9+/j29/n6+PTy7+7u7/H0+Pv8+/v8/f3//4CBgoOFhIH69PP2+Pv7+/3++vb09vTx8vb49vHu7fP4+vjz8vP29vb09PX+goaHh4eJjI6NiomLjoyIgoGCg4GGgG+CgoSGh4WCgoOEiIqJiImLjpGVlpWUk5OSkZGRkpKRkJCQj4uIh4iJioqJiIiJiYiIiIeGiImIhYODhIWHiYuMioeGhYWGhoWDgPn09P2EhoaD/fXx9Pj7+fj28/Hy8vHw8/uA//z6/ICBhIiKiYeFhgaLkZGQjYkrXlxeYWZqa2tqampnY2FhX7tfYGBhYL+/wGFhY2RiX11ctlxeW7CqqKmuWoRcfFqvrK2wsLJaWllaWlxeX2BiZGJgX162qJR7ZKCRlKRhbHNycnR2eHyBg4KBf3x6e4KGh4yQjIaDhIiEe3Fxc3V0cG1ucXR2c3Fxb21raGhsbm9xdXh4dXFvbGlnZmpwdHVzbmtpZmNjZGlwd3x8eHZ2eHh5ent4dnh+goSEhSiEgn56d3Z3eXx8e3t9gYB9e3h4e31+f39/e3l5eXh6fHt6e36Af35+hH2Ae3t7fX9/fXt3dXZ2dXd4d3l8f356d3h7fXt5e319fHt5dXV3eHh6e3h0c3Z7f4OEhISDg4B9eHV1eHl6enx9fn59foCEhoWEg4KDh4qLiIN/fHp4dnRzc3RycnNwaGJkaGxvbGdjYWJlZ2lrbXB2fH57dnJvb29ub25raGVkZGaAZ2hnZWRpcHV2c3JzdHZ1c3Jva2ZhXmBma25xdnl7enp5eHh5eHVwb3FsamdmZ2pvdHZ4eXt8fH5/f312bGVkZmhoaWtqZGBhaXN5e3p5d3NuamhqbG5tamRgXV1fYWVlZ2tub3BvaF5XVmF2kai+a3R2dHNycnFwbm1tbGpnZWcaaWpqamtsbm9ua2lsb25sbGtpaGlramlnZ2aEZV5oamppZWZobHBzdHNycXBvbm1sbG5ubGhkY2RnaGhqbG1raGZmaWxtbW5ubm9vbm5vbWtqaWtvcnJwbm5wcnR3eHd2dnVzb2xsbnF1dXNxcnV4eHh2dHFvbGtsb3FxhHJxc3FwcHJzc3Nxb3B0d3Z0dnd2dXV2dnV1dnV1dHR3eXl4dnV0c3JxcXFyc3NycnF0eXx8fHt6d3Z2eHt8enh4eXp4dHJydHV3d3h4d3d1c3N0dHJub3J0cnFxcGmwhF+cpGR8l7hhvry9v765tbK0tLSEtoCyrqqpqqysqqagnJ2gpKeprbG0tLOwrayur7G2urawq6imo6ChoJuZl5mcoaWoqKWkpKKgoKCho6epqquqrLVeXLOys7Csq6mqsrdeYL24tLGvtLu+vr68uLa3ubm2srCurKysrbG0t7i3t7e4t7m7XmBhY2RkYbqzs7a4urq7vHi+u7e1trSysra4t7KvrrS4ureysLCys7W1tri/YmVkZGNlaGloZmZoa2lmYWBhYmBeXl5dXV1eX2FkZWRhYGBhZGZmZWZoam5xc3JxcHBvbm5vcHFwb25vbmtoZ2doaGdmZWVmZmZlZWRkZmdnZWNjZGVmZ2hpZ2WFYzNiYV62s7W+Y2VlYbmxra6ytbSzsrGxsrKwrrG4Xry6ubxgYWRnaWhlY2NjYmJma2tpZWEGTUtMUFVahFuAWldTUVJRoFJUVFRTpKOiUVFTU1FNTEqTS0xJiIB9fYNER0hJSUeIhoaJi45ISEdJSktNTU1PUVBPT1Cdk4JqVIFwcHxJUldWVFdZW2BlZ2ZlY2BeX2Vpa3F2dGxoaGxpYFlZXF5eW1hZXF9hX1xbWFVRTk5TVVZYXF9gXVhVUU2ATExQVltbWVVSUE5LS0tOUldaWldVVlhaW11eXFtcYGRnaWloZ2dmZGBdW11fYWJgYWRnZWJeW1tfY2VmZ2hlYmJiYWNkY2JjZmlnZWVlZGRkYmBgYWNjYmBcWltcW11eXV5hYmFcWlxfYF9dX2NjY2JgXFteX19hYmBcXV9jZmmAaWpqaWhkYFtXV1pdXmBhYmJiYGBiZWdmZWVkZWptbmplYl9eXl1cW1xcWltcWVJMTlFUVlNOSkpLTlBRUVJTWF1fXlxbWlpYVVVRTktJSktNT09OTU1RWF1cWllcXV5dXFpYVVFLSEpQVVhbYGRoZ2ZkY2JjYV5aWVtYVlNQTk97UlVYWVteYGFhYWJiXVVPTlBRUFFSUUxJSlBYXV5dXVtYU1BQUlRVVFJNSUZHSEtPUFRZXmBhXVRJQkFNYnyTp15namppamtqaWZlZGRhXl1gYmRjY2RkZWVjX15hY2JgX15bWVtdXVxZWFdXWFdYWl1dW1dYWl1hZGVkhGNOYmFgYGJkY2BcWlxfYF9gYWJhX15dX2NjY2RkZGVlZGRlZGJhX2BkZ2ZkYmJkZmhqa2ppaGhmYl5eYGNnaGZkZWdpampoZmRiX15fYWNjhGQgZWNiYmNkZWZkYmNnamlnamxrampra2ppaGhnZmZpammFaClnZWVmZmdmZmVkZ2tubm9ubGlnZ2ltbmtoaGpqaWZmaGpqa2tramlpZ4RmgGNgYWRnZWNkZF6bcU57gVBkepZQmpiYm52cmpeVkpCRkZKSj42JiIqNjouGgHx9goiMjpCSlJSUk5KSk5WXnJ+blJCNi4eFhoWBfXl4eXx+gYOBgYKAf3+AgIOHiYmKi42WT02VlZaSjoyJipGXTlGgnJmWk5ebnJmWk4+NjpCQbo+MiomJioqNkJSXmZmYmZmYmZtOUFBRUlFOlI2Nj5GTk5SVmJaSkZKPjY6TlpaSkI6SlZaUj42OkJGSkZCSmVBSUlFQUVRUUlBPUVRUUk9PUFFQT05OTUxMTU1OUVFQTExMTU9RUVFUV1pdYGFhhGBhXl5fYGBgXl1cW1hVVFVWWFhXVlZWV1ZWVlVVVlhXVFJSUlNUVVdZWFZUVFVUVFNSUJyZm6NWV1ZTnJSOj5KVlZWTkZCQkZCOkJdOm5mZnFBRU1ZXVlRSUVFQUVZbW1lVUZB/AX6Ff4N+iH8Efn9/f4V+hn+Gfo9/hX6Eff9+/37+fv9/7n8Ffn5+fX2EfgF/zn6Cf4p+gn+jfod/pX7kf4R+hH+RfgF/hH6SfwICBAAdh4iIiIeIiIeFg4KBgICDh4mJhoH7+P+FiYiFgoGFgAGBhoKAhIWGhoOA//+AgIGA/vn19PX39PH0+vDdx7Kchenl+YuWnJ6jp6aioaSpra+wtLu+v727ubm7vsHDxL+1r66tqKSlqKyytLOzsbCtq6mmpKSjoJuZmZmYlpSRkJKVm6Koq6yrqKeoqbG4ta2loqatr6+trKmloZybm5uhpqmqsbaAuLi9wsXHxsK+vb7Dx8jHx8fDwMDDxsfJycW/vLm3t7Wxrq2rq6ywsrKytLKxsLG0t7e1tri8wMPFwLawsLW8wMLEwry1srS3uLm5t7Szs7KwsrOztba1s7GytLS2u7/Avbm2uL3AwL27vsPFw8C9vL2/wMC/vr+/wMPGxMG9urkst7W2tbKwrqyrrKupqKiprbCxsbO1trKtqqinpqerrrCtqKakoZ2cnp+enp6FnRygpquvs7W3t7i6vL28ubKspqKeoaKkoZ+foaKjhKJqoKOmp6Wlp6qvsre5u7u4tbO0s6+poJmTkZKWmZueoqWoqamop6iqq66ws7Kwrqqlo6OmqKqrrK2urayppqOioaCenJyfqbW+w8bJyMS0oJGHg4matNDvhpKanZqYl5eWlZOQj5COioaEg4WBW4CA//38/YGFio2PkpWVlJKQj4+QkpWWl5WRkJGRkpOUlZaWk5GQkI6MiYiIiYyNj46Ni4mIioqKiYmKjIyMjZCTlZWWmJueoKKgm5eXmZqZmJaVlZWWmJucnJqElxuVk5OUlJKQjZGVmp2em5iXl5aXmJmam5ydn6CEn2agoKCho6OioJ6dm5mYmZucm5mXlpWVlpeZmpubnJydnJmWlpiXlpWVlpeXl5iYmJmam5uZmZqamJmampmYmZqbmpmXlZSUlJKRkZGSlpiZmJiZl5GG68KfhPL/kqfB3fD2+fby8vaE+Cb39PH2/YD/+/fx7u3s7O3x9PX29PHw7/Dy8/T18+/t6+jp7vDv7YTrDers8O3n5enr6uno6OqE7IDt6uTh5uvw9ff4+/z47+Xg4ebq7O7v7/L2+fn4+fr6+vv9/Pjy7Ovw+oGCgf359fPz9/r59e/s6unr7/P19fX2+v+A/fv9/f6AgoD8+ff4+ff3+Pz+/vv49vb39/f08e/w8vT19fb7gIGBgIKEh4aFg4ODgv768/f/hIaIiouLi3SKiomGg4GBg4iPk5WUk5COjY6Pj46NjY6Oj5GRkY6LiYeIio6SlZONiYWEg4KCgYGAgYGBgIGBgoSGiImIh4aFhYWGiIiHhYWGhoeIh4SB/4CCgYD9+fTz9Pf7gIGA/Pn3+4CBgPz6+Pb6/4GEhoiKiYiHhwFghmGAYF5dXFtbW19jZmZjXbCts15hYFxYV1ZXWFpbXFxcW1pZWVpbXFxaWLCxWlpbWrOvrKqsrq2tsbiwoJCAb12fn69iaWxsb3JwbWttcXN0dHh9f4F/fX19gIOEhYWCe3l5eXVxcXV4fX9+fXt6eHZ1cnBxcG1qaGlpaGZkYGBhY2cubHJ1dnVyb29udHt6dG5rb3N2dXNzcG5rampra3B0dnd8gYODhoqNjo2IhIODhoWIBISCgYOEhYCDf3x6d3d2c3Fxb29xc3Rzc3Rzc3N1eHt7ent8foGChIF6dnd7goWHiYiCfHl7fn+Af316eXh2dHZ4eHh5eHZ1dnh4en2AgH57eXuAhISBf4CEhYOAfn1+gYKBgICBgoOFh4aDgH58enh4d3VzcW9vb25tbGxtcXNzcnR1dnNwbRNraWlqbXBycG1sa2lnZmhpaWhnhWaAaW5ydXh7fH1+gIKEgn95cm1pZmhpbGpqamxtbm5ubWxqa21ubW1vcnd7f4KDgn57eXl5d3NtZ2JhYmVnaGptcXNzc3JxcnNzdnh7e3p4dXJwcXJzdHV2d3d1dHFubGpqaWhnZ2pyfIOHiYuKiHxtYltYXm2Fn7tqdXx9end3dnWAdHJwb3BwbWpoZ2ZlZWRjY2LEwsDBYmVpbGxucHBvbWtra2xucHFycG5tbm9wcXJycnFvbW1tbGpoZ2doamtramloZmZnaGhnZ2hpaWlqbG9xcXFydHZ4enh1cnN1d3d2dXV0dHR2eXp6eHV0dHRycXFzc3Fva25ydnp7eHZ0dHMIc3R1dXZ3eHmFe0h8fHx9f39+fXt6eHZ0dXZ4d3VzcnFydHV3eHh5eXl6enh1dnd1dHNzdHV1dHV1dHV3eHh3d3h3dXZ3dnZ1dnd3dnZ0cnJzc3GEcIBzdnd2dnd0bmawkHRgrrZndYier7S3trS0uLm6ubi3s7CzuFy3s6+ppqWlpaerrq+xsK6tq6usrrCxsK2rqaanq66sq6mpqaqpq66tqKapqaioqKmrrq6vr6+sp6WorK+ys7S2t7OroZ2eo6eqrK2tr7S3t7e4urq7vL28uLOurYCxuV9fXrexrKqpra+vrailpKOlqa2urq2usbVbtLS3ubtfYF65trW3uLe3t7q8u7i1tLS2trWwrKmqrK6vr7G2XV5eXmBjZ2dmZWVkYr+5s7a8YmNlZ2hoaWhoZ2ViYGFjaG1xc3FvbGlpaWtramlpaWprbW5ta2hmZWZobG9ycE1qZmJgX15eXl1cXV1dXF1eYGJkZmZkY2FhYWNlaGhnZmVmZ2hpaWZiwWBhYF65t7Sztbe6X2Beuri3u2BhX7q3tbS4vF9hYmRmZGNhYANRUVKEUQNQTkyESiNOUVRTT0qLiI5MT01JRURDREVGR0dHRkZFRUVHSUpLSkeOkIRJao+JhoaIi4uLkZmUhnhrXEx+eYVLUVNUV1taV1VWWltbWl5jZWZlY2JiYmRlaGlnYF1dXVlVVVdaX2BfX15eXFtaWFZXVlNQTk9PTkxKRkVGR0tPVFZXVlNRUFFXXVxWUU9TWFtbWltYVVKEUIBUWFlaXmNjZGdrbnBvbGdlZWhqamlqamhmZmlrbG1samViYF5dXVpZWllYWVtcW1tcWlpZW15hYWBgYmRobG9rZGBgYmdqa21rZmBdX2NjZGVjYF9gX11eX19hYWFfXl5fX2BkZ2ZkYF5gZGhpZmRma2xpZWJhYmZoaGdmZmZoaxZubWtoZWRhYGBfXVtYVVRUU1FRUVJVhFdwWVlXVVNSUVFTVVhYVlJSUE1LSUpMTE1NTExNTUxOU1daXV9iY2RmaGppZ2NeWldTVVVXVFJSU1RUU1NSUlFTVVVUVVdbYGRnaWloZGFfX19dWVVST09QUlRTU1NUVVRUU1JTVVdZW11dXFtaWFhYWohbAlpZhFgbV1VTVVtiZ2ttb29uZVlQS0hNWm6Fnltla25thGwqa2lnZmdmY2BeXVtbWllZWFiwrq2tWFpdX2BhY2RkY2JhYWJkZ2lpZ2NihmOAZWVjYmNkY2FeXFtcXV1eXV1bWVlbXFtbWlxdXVxdX2JjY2NlZ2lqbGtoZ2lsbm5ta2tpaGhqbW9wbWtpaGdlZGRmZWNgXmFna29vbGloaGdnaGhpamtsbnBwcXBxcnJxcXJycW9vbm1ramttbmxpZmRjZGVnaWtqa2ppa2tpZmZ+Z2ZlZGRlZWVkZGRjZGVmZmVlZmdmZ2hoZ2doaGloaGZkY2NjYWFgYWFmaGlpaGlnYluaemBNjJdXZHeOnaGgmpWUmJmbmpuZlZOXnE6alY+IhISFhYiNkZOUlJKRj4+QkpSWlJGPjYqMkJSUkpGQkI+OkJSSjImLi4qKi4uOhJBwkY+LiIqOkpWWlpiYlI2EgIOHjI+QkI+QkpOSkZGSkpSXmpuZlpKSlZxQUE6XlJGQkpWXlpGLiIeGh4qNjYqHhomMR42OkZKUTE1Lk4+Oj5CQj4+TlZWSkI6Oj4+Pi4eEhYaIiYqMkUtLS0pLTlJRUYRQPJqWkJSbUVNUVVZWV1ZYWFZUUlJUWV9jZGFeW1lZWlxcWlpZWlxdXl5eW1hWVldaXWFjYFtXVFJSUVFRUIdPPVBSU1RVVFNSU1NVV1paWVhYWltbXFxZVaRRUVBNl5SQkJGUmE9QT5uZl5pQUU+amJiXmp5QUlNUVVRSUVGUf4N+mH+CfoR/kH6Dff9+/37/fox+mn+Efv9/zn+EfoJ9lH4Bf95+g3+WfgF/hX6Df5x+jX+FftZ/AX6Ef4d+g3+EfoN/hn6JfwICBAAHgfn3+f2BgoSDgIKCgoOCgP6AgoaJiYeFg4OCg4OCgf759/uAhIiJiIeHh4iGgv/8+vj39/f07uHQvauWg+fc4fKCjZacoKCgo6SlpKKfn6CfoKCho6Olqa2zvsbLz9HQzMjEvraxsbO1tbS1t7u9v769u7i0raSgoaSoqq20t7i2t7q5ubq/v725WrKsqKirra6ur7KztLSysKumpamusbKuqqWioaSoq62vsbKysK+trq+trrC1tbW0tLe6urq4trW3t7SvrrK4vcDDxMPBvrq5vMLIysnFv7q5ur2+vb2+vr28vYS+Qbu2s7Cvr7O2uLi4tbKvq6utsbi/xMbEwLq1srGuq6ioq66xs7a3tbOys7S2ub3Cx8nJx8bFxL+4tbOztLSysbGyhLMPtLa2tbS0s6+qpqanqqyuhLADsrKxhq+Arq2urqyqqKalpKarsbW4u7/DxMTCwLq0sbGwsq+qo5+cnZycmpiWk5OSkZGTl5ygo6SkpaWkoJuamqCjp6qrq6uqqKekoqCgoKOoqaurrK2traysra+xsrK0tre5u72+vry4sqyopJ6amZueo6irq6usrKyvsra3uLm6vr25t7gzurqzp5mMhPr0/IqcsMXd9oaOlZmbm5mXlpeXl5WTkpGPjoyLioiHhoWHi42Oj5CRkZGShZEfj42LjI+TlZaWlZWUk5GPjYuIiIqNj5CQkZKUlZaXl4WWgJKMiYmIiIyQk5WVlJWUkpOXm56goKChoqGfnZuamZqbnJqYl5eYm5ybmpyeoKGhn5uZmJeVlpeXl5aVlZeYlpWUk5SVl5eWlpeYmZiWlZSVlpeWlpWWl5qeoqSjn5uZmpycmpeUk5OSkpGQj4yMi42Pkpicnp+hoZ6cmpmXmJ+mgKmno56cmZeVlpeYlpWUkI6PkpSVlZSSkI+Nh/PSs5mF8e2AjqK1yd3s9PX08fDt7O7u7Ovv8/b3+f2BgoGBgoKCgYKCgf759PP18+vn6Ors7erk4OHj5ejv8vHw8PLz8vHy8/Lw7uzp6Ojp7PDy9fj7/f38+/r7+/n4+vz+gP/8E/uAg4WGiIeDgP3/gYKA/Pj08/GE8h31+f2Agf//gICA/vv49PX4+fj5+vv8+vr7/v+AgYWAG4GDhYOA/Pr8/4D//4GDg4L99/T1+fyBg4eFhYSEhYUYhoSCgP79/f+Bg4WGhoiLjpCQj46OjImFhIQYhYaIiYqLjI6MiIP+/Pr6+/z7+fb19vr9hP8n/v+AgoKChImOj4+NjIqIhICAgoWIioqJhoL/+vn+g4aJi4uLjI6ShZUZk5COjo6MiYeJi4yLiYiHh4eFg4KDhoqKhgxguLW3u2BhYWJiYmGEYAxeul5hZWhoZWFeXFuEXIC2tLS3XmBhYF9eXV1fXly0srKwsLGyr6uhlIZ4aFmdk5eiWF9lamxsbW9wcXFvbWxtbGxsbW5ucHJ1eoOJjI+Rj4yJhoJ8eXp7fHt7fH6BgYB/fnx7eXNsaGltcnV3fH5+fHx+fn19gYGAfHl1c3N1d3d3eHp7e3p5d3NubXB0d4B5dnRwbWxucXR1dnd5eXd2c3V2dHV2ent7enp8f39/fn1+gIB+enh7f4KDhIWGhYSAf4GEiYmJhYF+fX+Cg4KCgoF/fX5/gICBgHx5d3Z3en1+fXx6d3NwcHJ2fYSJi4qHgn17enh0cXF0d3h6fH58e3p6ent+gISIioqJiIeGgh18d3V0dXZ1dXV3eHh4eXp7e3l3d3ZxbGlpamxvcYRyBXN0dHNyhXMRcnNzcW9tbGxsb3R5fYCBg4SEhTiBfXt7e3x6dW9saWlpaGdlZGRkY2NiYmNmaGtra2xsbGlkZGVqbXByc3NzcnFwbm1sbGxucnN0dYR2XHV1dXd3d3h4eXp8fn+AgH56dnBtamViYWNlaW5wcHBxcXBxc3V3eHl7fn58e3x+f3pwZVxWpKGoXWt8jqO6Zm51eXt8eXd2dnZ1dHNycXBvbm1samhmZmdqbG1thW52bW1tbm9vbmxtb3N0dXRzcnJxcG9ta2hnaWtsbGxtbW5wcXFycXBxcXFuaGdoZ2dqbXBwcG5ubWxtcHN1dnd3eHl4d3Z1dXV2d3h2dHR0dnh5d3Z4enx9fXx4dnV0c3N1dXV0c3N1dXRzc3N0dHV1dHV2dnd3dYRyNnNycXBxcXN2en19end2d3p6eXZzcXFwcHBvbWxramttcHV5e31/fnx5d3Z1dnuBg4J+enl3doR1M3Nxb2xpam1wcHBvbmxrame6oYlyYKypW2d3hZWlsbe3tbGwrq2trKmoq7CztLW5Xl9eXoZgcV+8t7OztbOsqautr7CuqKSlpqirsLOzsLCxsa+vsbKysa+sqaenqKyvsbO1tre3trS0tbWzs7W4ul69u7tgY2ZnaWdkYLy9YGBfubWxr62trausrrK1XFy3t1xdXbm3s6+wsrOys7S2trW0tri5XV5ehF0bXmBhX122tLa5XLi4XV9fXreyr7G0t11dXl9ghWGEYDVhYF9eu7y+wWJkZWZlZ2lsbm5tbGxqaGVkZGVmZmdoaGlpamtpZWC4trW3uby8uri3uLq8vIS7J71gYmNiZGhsbGtqaWhmY2BfYWRoampoZWG9ubm9YmVnaWlpamxwc4R0GXJvbW1samdlZmhoZ2VjYmNjYmFgYWRnZ2SAUJmWl5xQUlJSU1JRT05NTEuVTE9TVlZSTktISEhJSUiOjI6SS05PTkxLSktMTEmPjIqIiImKioiAdWleUUV1bXB7REtRVVhXWFpbW1pYVVVXVlZXWFlXWFlbX2ZrbnBycW5saWZgXl5fYGBgYWRoaWpqaGdlYlxVUVFUWFlbX2E8YV9eX15eXmFiYV5bWFdXWVtbWltcXmBgXl1YU1JVWl5gXltXVFJUV1teX2BgYF5dWlpbWVlbX15dXFxehGEcYF9hYl9bWVxgY2VmaGhoZmNjZ2twcXBtaGRjZYZoA2dmZoRnQmVgXlxbXF9iY2JhYF1aV1daX2VrcHFuamRfXVtaWFVVWFtcXmBhYF9fYGBiY2Voa21tbGtra2diX15fYGFgYF9gYIRfWGBgXlxcWlZRT09QUlRVVldXV1hZWFdYWFlYWFdXWFhXVVNSUVFTV1pcXV9jZmpucXNwbGlpZ2dlYFpXVFRUUlBNSkdGRURERkpQVFdZWltcXFpVU1JVV1mEWgRZV1ZVhFQBVohaHFlZWlxcXV5fYWRmaGtsbWtoZF9cWFNPTk5QVFiFWztaWltdXVxcXmBgX11fYWNfV09IQ3x2d0NQYXWLo1tiaWxubWtpamtsbWxqaGdlY2JhX11cW1tdYGJiY4RkAWOFYh9hYF9hZWhpamlnZ2ZlZWRiYF5dXmBgX19fYGJkZWZmhGQVY2FcWlpZWVtfYWNiYWFgXl9iZmlqhGttamhnZ2hoaWtsa2lpaWtsbGpqa21wcXFvamhnZmRlZWZmZGRkZWdmZWVkZWZnZ2ZnaWpramhlZGVlZWRjYmJjZmpucXFtaWdoamtqZmNhYWBgX15dW1pZWlxfZGlrbXBwbmtpaGZmaW5vbmtpaYRoMmlpZmRhXVpaXWBhYmJhX11cWZ+Ic2FSlZNPWGRvfIqVmZmXlJKQj4+PjIyOk5aYmqBShFMBVIRTblKinZiYmpiRjo+QkpOQi4iJiouMkZSUkpOUlZOSk5OSkY+MiYeHiIuPkZOVmJmamZeXl5aUlJWYm0+em5tQVFZXWFdTT5uaTk9NlZKOjYyMjIuLjZCSSkqTkklKSpORjYmKjI2Nj5CSlJOTlZeYh00YTlBSUE2WlJaXTJeXTU9PT5mVk5aanVFSiVOAUlNUVFRSUZ6enZ5QU1ZXV1lcX2FhYF5eXFlXVlZXV1hZWlpbW1tdXFdSm5iWl5mbm5mWlpeanqGioaGgo1NVVVVXW15dXFlZWFdUUVBSVVhaW1lWU5+bm6BUWFpcXFtcXmJlZWRkZGJgX19eXFlXWFpaWFVTUlNTUlBPUVRXV1QBf4R+jH8Bfo5/hH6Lf49+hH3/fv9+/36SfoN9hn7/f+h/hX6CfZh+i3+4fgR/fn5+iH8Ffn5/f3+Mfgd/f35+f39/kX6Mf4R+A39+foR/hn6Sf4R+n3+Tfph/hH6nfwICBAADhIH/hP0K/oCBgoGBg4WFhoSHEYiKi4qIh4eIiYuMjIuJh4WEhIMngoD57+PUxrinl4qA8unt94CDhYiMkpebnqGjpaalpaWmp6Win5yYhZU7l5mcoKSmpaSmqa6zub2/wcLGyMnJxb63sa+ytLOwrKioqaqrq6iloqGhoZ+dnJqYlpSWmJyfnpuZmZuEnmehpaepqKekoqCip6mqrKyppqSjoqSmqa2ytLSzsrK1uLm4tK6pp6Whn5ybmZmamZiXlpeYm56fnZ+jq7W4uLq8vb28vb26ta+qqautr6+vsLO2uLi2tbW0srCvsK+qpqiutLm7ura1hLY8tbW2t7i6vL29vby7vL28urq6u7y8urm5ubq6vb/BxMbIx8TAvLm4uLm7vsDBwL66uLe1s7GuqqSfnqCihKSAo6OioaKkpaampKOjoqGgoaGio6Kio6OioZ+gn5+goKGgnZ2en5+io6KhoaKio6OlpaWmqKmqq6+ytre2ta+poZeUlJiepquvsLCztLe3t7m8vr+/vr27t7W0tba3uLm6vL29vLu6u728ubOspaCak46MjY+SlpyipKSioqWrsLM2tLW2uLm6vL29urm4t7Sxr6+trrCxsq2jl4uB9vSAipehrL3L2+z8ho6WnaGhoJyXk5CQkZKTh5RRlZicn6Cgn56fn5+blZKRkZKRj46PkJCQkZGRkJCQj46PkJKVmJqamZqamZmam5ycnJubmpeVk5KSk5SWmJqcnZ+enZybmpmYl5WTlJaZm5mXhJYKlZWVlpaVlJSWl4WYB5eWlpaXlpaElBqSkJCQk5SVlJOSk5SVlZWWmZ2enp2cm5ucnISbMJqZmZmampmam5ydnJqYl5eXlpSRkZGSkZKUmJ2cmpWUkpGQkI+PkJKUk5CMiomJiYSKYomIh4aGhoWEg4H13cGtoJSJg4WOmqayvszb6fP5+vj39/f28u/w9vr/gYKDhIOB/vv6+Pb19PT09fTz8Ozp5+Xk5ujp6+3w9Pj7/f6AgoODgoGA/vv4+Pr+gYODgf/59PLzhPQN8/P09ff6/oKEhYSEhISFgIP/+fX19/n7+vn4+fr6/P38/P6BgoKCgP36+Pb08/Pz9Pb4+fv9/4GCgYD89vPz8vLx7+zr6+zt6+rp6urs7Ozr7O7w8fH3+f+AgID//4CA//v39vf+goiNkZCPjo2LioeFg4GBgoSFhoaIiY2Rk5SUlJOSkZKRkI2Kh4WFg4GBJIGDhoiKi4yMi4qIh4eJi4yNjo6NioaGh4mLi4qJiIeHhoWFhYSEI4KCgoODhIaIio6RlJaXlZSTk5OUlZeZmJeWlZSTkY+NjIqHJ2JgvLq5ubu9YGFhYGBhY2NkZGRjYmJjZGJgXl5eX2FiYmFgXlxcXIRdJly0rqWZjoN1Z11VnpeaolRXWFlcYGRoa21vcXJxcXJyc3Fua2dkhGGAYmNlaGxvcXBvcHN2eX1/gIGBhIWGh4SAfHd2eHp6d3RxcXJzdHRyb2xsa2tqaGdmZWNhYmNlZmViYWJlaGhoaWtub3BubWpoZ2ltb3Byc3Fwb25ub3BydXh6enl4eHp9fXt5dXFvbWpoZ2ZlZWVkZGRjY2NlZ2hnaGtyen5/gYOEhCGDgHx2cnFydHZ2dXV4e35/fXx8e3h2dXV0cGxucnd7fXyEeQl6eXl5ent8foCEgQuAgIB/fn+AgYKCgYWAN4KDhYeJi4qHhIB8e3x9f4GDg4OAfnx7enh2c3BraGdpa2xtbW1sbGppamtsbW1sa2pqaWlqa2yEbRFubWxra2ppampramlpamprboRvJ3BxcnJzdHR0dXR0c3Z4fH59fXl1b2ZiYGNnbXF1dXV3eHp7enx+f4SAIn58enl6e3x8fX5+f4B/fXx9f39+e3dzb2ljXlxbXV9iZ2yEbj1xdXl7fHx9foCBg4WEg4GBgH57enl2dnZ3eHVvZ15XpqVXXmpzfo6cqrrJbHJ4foCBgH15dHFwcXJzdHR0hHMNdHZ5e319fHx8fXx5dYRyA3FvboZvFG5tbW1sa2xtbnF0dXV0dXRzc3R1hXYrdXNwbm1tbW5vcXJzdXZ1dHNzcnJycXBvcHFzdHNwb29wcG9wcHBxcXBxcolzM3R1dXVzcnJxcG5ubnBxcXBvbW5vcHBwcXR3eXl4d3Z3d3h3d3Z1dXRzdHR1dHR1dnZ1c4RyHXFvbGtrbGxtcHR4eHVycG9ubWxqa2ttb25raGZlhGYFZ2ZmZWSEY4BiYV+1o419c2phXWBocXqDi5ahrLW5ubi2tra1sa+xtru/YWJjY2Jfu7e2s7Cvrq+vsLCvraqopqSkpqioqqyusLGysrJaW1xcXFtbtLKwsLG0XF5eXbaxrKqrrK2trq+xsrS3ur5iZGVlZGRlZWVkYbu0sK+xs7SzsbCwsbK0tim2tLZcXVxcW7a0s7Cvrq+wsbS2t7i6u19gX164s7CvsLCvrqupqKqqqYSohapXrK2trrS2vF9fX7++YGC+u7e3ub5iZ2xvbWtpaGdmZWRiYWJiZGVmZmhqbXBydHRzcnFwcG9ua2hlZWRkYmJiZGZoamtrbGtqaGdnaGpra2xsa2djY2RmhGgLZmZlZGNjYmFhYF+EXh9fYWRmZ2psbnBxcHBvcG9wcXN0dHNxcG9ubGtpaGZkC1FQnpybm5qcT1BQhE8VUFBRUVBPT1FRT0xKSUpLTU5OTkxKhEkfSkpLSY6JgnlvZltQSEJ6dHiAQkRFR0pOUlVXWFpbXIRbRlxaVlNQTElJSkpLTU9RVFdYV1dXWl1hZWdoaWptb3BwbWhjXl1fYWFeW1hYWVpbXFlWVFNTVFJRUVBPTkxNTk9QT0xLTE+EUkBTVVZWVFJQT05QVFZYWltZWFZVVFRVVlhbXF1dXV9hZGRhXVhTUVBOTUxLS0tMTE1NTk9QUlRVVFVYXGJkY2VmhWckZWJdWlpbXmBgYGFkZmdnZWRjYmBdXV1cWFRWWl9jZGNhYGBghGEEYmNkZoVoC2dnZ2ZlZGVmZ2ZlhGQwZWdoaWtsbm5samhmZ2doaWttbWtpZmRkYl9cWVZST09RU1NSU1JSUVBQUVJUVVRUhFIlU1RWV1hYV1dXVlVUVVZWV1lZWFdWWFhZW1tbWllaWltbXFtbW4RcQV9gYmJhX1xZVU5MS01RVlhbWllbXF9gYGNlZ2hoZ2ZkYWBfYGFiYmNkZmdnZWRjY2VmZmVhXVlTTUhGRkdJTE9UhFZHWV1gYmNjZGZnaWpra2lnZmViYF9eXFtbXF1bVk5HQoGDR1BcZXB/jZuptmJobXJ0dXNwbGhmZWZnaGlqaWpqaWlqbHBydHWFdApvaWZkZGVlY2NkhWULZGNjY2JhYmNkZmiEailpZ2doaWlqamppaWZkYmJiY2JjY2RkZWZmZWVkY2NjYmFfYGFiY2FeXoVfCGBhYmFhYGFiiGMEZGVkZIRiI2FgYGFjZWVkYmFhYmNjY2RmamtraGdnaGlpaWhoZ2ZlZWZnhmYsZWRiY2NkY2FeXV5fXl5fY2ZlY2BeXl1dXFtbW11fXlxZWFhYWVpaW1taWViEVy1WVlOdinRmXVVOSUtTW2Nrc32Ik5udnJmXlpaUkI2Ok5eaTk5PUE9OmZaUkY6EjBaNjo2MioiGhIOFh4eJi46SlZiZmk5PhFCAT5yZlpaXmk9QUE6ZlI+MjI6PkJGSk5WVlpibUFJTUlJSU1NTUlCZk46Njo+RkI+Ojo+Qk5WUlJVMTU1NTJeUk5GQkJCRk5aZm52eoFFRUU+cl5WWlpWVk5COjpCRkI+Oj5CRkpKSk5SVlJSXmZ5QUFCgn1BQoJ6bnZ+lVltfYmAUXltaWFdVU1FRUVJUVlZWWFhcXmCEYkZhYWJiYV5aWFZWVlRUVFZYWlxcXV1cWlhXVlhaW1xcXVtXVFNVVlhYWFdWVlZVVFNTU1RUVFJRUFBRUlVXWVxgYmRkY2JihGEOYmNiYWBfXl1bWlhXVlSCf4Z+pH+KfoR9/37/fv9+pH6CfYp+/3/if59+hn+dfod/hn6Ef5B+i3+SfoV/j36Ef55+B39/f35+f3+GfvV/AgIEADr8/oCA//+AgYGCg4SFhYSDgYGChIeKi4uJhoOA/vz59O7p4dfLwritoJWLg/z5/oWOmKCkpaKhoJ+dh5xTmZaUlJKSkZGSlJeZmpucnZ6dmJOOioiJi46Rk5SWlpaUkZGSkZCPj5CRlJWUkpGSlJeZnJ+hpKampqeorK+xsrKztLS1tbSzsrGvraysrq+uq6iFpYCjoqGdmpeXmp2hoqSkpKOenqCjpaaprbCxr6+vsbGysK6rqaimp6uvsrW0s7KzsrGtqKSjo6KioJ+en6KlqKilpKWmp6iopqSioaGioqKjpKSioaGgnZ2en6Gjp6qsra6trKqrq6ysrq+wr66trayppaKhoqWprK2urq6trKqopgOlpaWEphinqamqqainpaWnqKqsra+xs7OysK2rqamEqBSnpqWlpKWlpaanp6anpqWlpKWmpoWnNaampqepqamop6inpqanqa2urq+wsrS0srKxsbKztLW2tra3uLu9wMHBwcC9urSxsrO2t7m6hbuAure2tLOysa6rpqKenZ2eoKCfnp2bmZiYmZiYl5aXmp6lqrC0uLu7vLu7u7m3tbKwrqyqqqioqKmrr7O4vL/BwsPCwcLBvbeuopePi4qLi42PkpWYmpmTjIWC/oCGj5mkr7jCytLd6/mBhYqOkpSVlZaWlpWVlZaWl5iZmZqZmJcBloSVJZOQjo2Oj5CQj5CRkY+Pj46MiYaEgoKFiI2PkZKSkZGQj46Oj4+HkAqRkZGSk5WXmJmYhZkIlZKRkpOVlpeElhOVlpWVlJOUlpiZmZmbnZuZl5WUhZKEkxCSkpKTlZeXlZKTlJaYmZqahJs2mpmXlZOTkpOUlpeXl5aXl5iYmJmZmZqbnZ2dm5mWk5COjo+PkJGSk5KSkI+Ojo+QkJGSk5SWhJc2lpWVlZaVk5KSko+Nh4L6793HtaaalZSXnaawusbS2+Dk6/Hx7uzv8O3r6ejo6uzt7ezs7e7uhPAV7+7s6efp7vL2+fr6+ff19PT09fX1hPZq9fX19PPz9fr9/f38+vbx7evr7O7u7u/y9Pf4+vv7+/r4+Pf19PPz8/T3+/6AgoSGiIaC/vjz8fDw8fP3/P+AgYKCgoGA/vz69/X09vn7/Pv+gYKDg4OCgYGA/fz49vX09PX3+4CBgYKCg4SFLYeHiIeGhYSDgoGAgYGCg4KB/4D+/4CBg4OEg4SFiImJiomKjI2Pjo6MiomHiISJSYqKiYqKi4uJhoOA/4CChIWHiIiHh4iGhYOA/v78+Pj5/YGCgoL/+/r39vb3+v+Cg4WGhYSDgf/69e/q6erq6+zu8fb8gIGA/vw7tbddXbq6XmBgYWNkZWVkYmFgYWJlZ2hnZWJfXLWzsKuno56XkIqGfnNpYFmpp6xbYWZrbWxqaWloZ2aFZURkYmBeXl1dXl5fYmRmZmdnaGloZWBcWVhYWlxfYGJjY2NiYGBhYmFgYF9gYWJiYWBhZGdoamttbm9vb3BxdHd5enp6e4R8DHt6eHd1dXR3d3d1c4VxM29ubWlnZWRoam5ub29ubWlpa21vcHN2eXl4dnZ4eHh3dnRycnBxdXh6e3t5eXl4d3Rva4RqaGlnZ2dpbG9vbWxsbW9wcHBvb25vbm1tbm9ubWxsamhoaWlqbG5xcnNzc3JxcXJyc3R1dnV1dHRzcW5raWptcHN0dXV0c3NxcG5sbGxtbW5vcHFyc3JxcG5ucHFydHV2eHl5eXd1cnFxhXCFbw5wcHFxcXBwb29vbm5vcYVyhHEVc3NzcnJzcnFxcnR3eHh4enx+f35+hnwwe3t6ent9f4KDhIWFg4F8ent8f3+BgYKCgYGAf318e3p5eXd1cm9ramprbGtramhniGUkZmlscHR2eXx9fX17fHx7eXd1c3Fwb25tbW1ucHR4e3+Bg4SEhIM7gX12bWRdWllaWltcX2JlZ2dkYFxZr1hdZW54gYqRmJ6ns8BkaGxvc3R0dXV0dHRzc3R0dXZ3eHh4d3WFdCNxbmtqa2xsbWxtbm1rampoZmRiX15eYGRpbG1ubm1tbWxra4RsBG1tbGyFbQRub3FyhHEKcHBva2hoaGpsbYhvEG5tbW5wcnNzdHV3dXNxb26FbBRtbm5ubW1ub3Fzc3Fubm5wcnNzc4R0C3NxcG1ramlqa21vhHERcnN0c3R0c3R1dnd3dnRyb22FawpsbG1tbGtrampshG1Pbm9vcHBwb29ub3Bwb21sbGxramZjwLeolYV5b2tpbHB4gIiRm6Gkp6ywsa2sra6rqaemp6mqrKyrrK2ur7CxsK+tq6mlo6WprbGztbW0s4SyBbOztLS1hLQZs7KxsbO4uru6uri1sK6sq6usrKytr7Cys4S1H7S0s7OxsK+vr7CytrhdX2BjZGNgurSwrq6usbS4vb6FXxpeXbi3trW0tLW4ubq6vF9gYWFgYF9dXLe2s4WxGbS4XmBhYmNkZmdnZ2lpamlnZmVjYmFhYWKEYyDFY8bIZGRmZWVlZmZoampramttbm9ubWtoZ2ZnaGhpaYRqRWtra2lmYmC/YGJkZmdnZ2ZlZmVkYl+8u7m2tre7YGJjYsC9u7q6ury/w2NkZWVkYmFfu7i0sK2trq+wsbG0t7peXly3tTWTlEtMl5hNTk9QUVNUU1JRT05PUFJTVFNST01Ml5aVkY6KhX94c29oX1ZOR4eFiUhNUlZYV4RVM1RTU1NSUlFQTUtJSUhISElJS05PT09QUVJST0tHRENDREVHSElKSklHRkVGRkZFRURFR4RIQEpNT1FTVVZYWFhXV1haXV5fX19gYGFhYF9fXVxbW1pdXl5dW1paW1tbWllYU1BNTVBTV1dYWFhWU1FSVFVVV1mEWi5cXl5eXVtYV1ZUVVhaXF1dW1pbW1tZVlRTU1JSUE9OT1FTVVVTUVJTVFVWVVNShVEPUlNUVFVXV1ZWV1dXWFpchF0hXFtbW1xcXV5fXl1dXFxZV1RSUlRXWlxdXV5dXl1cWlhYhFkxWltcXV1dW1lYWFlaW11eYGJjZGNiYF5dXVxcW1pZV1ZVVVZWVldYWFhZWVlaWVlZWIVXCVZWVldYWFlYWIRZKFteYWNiYmNlZ2dmZmVkZGVmZmVkZGNkZWZoaGdoZ2RiXVtcXV9gYmKEYwFkhmMGYl9dWFRPiE1cTExLTExLSkpJSUtOU1ZZXWBhYmJhYmJgX19eXV1cW1taWlpbW1xeYGJjZWVlZGNkZGNfWVFJREFAQUFCREZJTVBRUExKSItFSU9XYGlyeoGHkJ2qWl5iZmpsbG2HbhRvb3BwcXFwb21sa2tramhlY2JiY4RkF2VkY2JhX1xaV1RSUlRYXF9hYmFgX15diFyFWwlaWlxdX2BgX1+EYAZdWlpbXV+FYIRfEF5cXF1eYGFgYWJkY2FeXVyEWxRcXV5eXl1eX2FjZWRiXl5fYWJjY4RkEWNjYV9dW1pZWVpcXmBgYGFihWQKY2NkZWVlY2BdWoVXZlhZWlpaWVhXV1dZWlpaW1xdX2BgYWFgX19gYF9eXV5fXl1ZVqaej31tYVhUU1VZYGdudn6DhIeLjo6Li4yMiomHhoiKjY6OjY2Njo6QkZGQj46MiYaIjJGVmJqamZeWlpaXmJmanIWdEJybmZmbnqGhoqGempSRjYuEiimLjI6QkpSVlpeXlpaVlJOSkpOUl5ueUFJUVldVUZyXko+Ojo+RlZyfUIRROFBPnZuZmJeWl5qbm5udUFFSUlFQT09Om5qWlJSUk5OWmlBSUlNTVFVVVFVWV1hXV1ZVVVRTU1RUhFZprFevsFhXWFdXVlZXWlxcXVxdXl9hX19cWVhWV1hYV1dXVlVVVlhZWFZTUaBRUlRVVlZWVVZXVlZUUqGfnJeWl5pPUFBQnJqZmZmanJ+kVFZXV1ZVU1KhnpuXlJOVlpeXlpianU9PTZiUBn5+f39+fpZ/kH6Dff9+/37/fr1+AX2Nfv9/3X/xfod/i36Hf4x+iX+Kfpt/BH5/fn6nfwF+jn+HfoR/iX6If45+BX9/f35+AgIEAAH6hPlA+vr5+Pb08u3n4NfPxr63saqknZaPh4H37uvu8vj/hYuVnqWoqammpKKhoKCipaiqqailop+cmpmXl5eYmZudnoifEJ6cm5qanJ6goaGgn5yYlJGHjxGQkJCPj46Nj5OaoKOhn52dn4SiBaGho6SlhaYDqKuthLCArq6trKyrqqurq62tr7CysK+rqaimp6ipqainpqepq62wtLa0s7Kvrauqq6upqainpqWlpaerrrCysrK1uLu9wsXEwb26ubi2sq+sqaemo6GfoaWoqqutsLGysbGwr66tqqimpKOkpaWmqKqsrq+wsbGwsLGys7O0tri6u7y8urcetLGwsLCvrqyqqqqrq6upp6ampqenpqalpaanqKiphKoSqaqqq6urqqmpqaqqqaempaWlhKQFpaeprK+EsSCwr6+vrq6trq6vsbKxr62rqqimpKKhoaOlp6qusbS1tYa3SLi3t7WzsLCwr7CwsLGxsrGwsK+vrayqqKiop6akoZ+enJubnJ2foqOjoqCdmZiXl5iZmZmYmZmanJydn6Glqaytrq+vrq2sqoSpaKqqqainp6alpKOhoKChoqWpra+xsK6rqKSfm5iVk5SWmp6go6aoqaqqqKenqKqsrq6sqqinp6ipqqurqaWioKCipamtsLW5vsLExsjM09rg5unt8ff9gYKDhYeIiYmKiYqLjIyMi4qKhYk7iIeFhISEhYiLjo+Oi4mHhoWEhIWFhoaGhYOBgYKEhYSDgoKDhIWHiIqMjYyKiYiJioqLi4uKioqLjY6FjyeQkZOTk5KRkJCPj5CTlpmbmpeWlJOSkpGQkJCRk5SVl5eXlpSTkpGGkgqTkpSWlpeXmJqchJ5ZnJiVk5KRk5WWlZKRkZCRkpKTlZeYmpuampmXlJGQj46NjIuLjI6QkpSVlpaWlZWVlJORj4yLi4uKiYaB8uPUxbapopyXlJSWmZyhp6+4wMjQ197j6Ovu8PKG9BX19/n6+fb08fDv8PL09/n7/Pv6+/uE/Bz9/4CAgYD+/Pn29fX4+vz9/4CA/ffy8O/x9Pb3hPgE9/b19oT3CPb19PT1+P2BhII7g4OCgf77+fj5+fr7/P38+vby7+7u7/Dz9fX29/f3+fv8/oGCg4SDgoD+/Pn3+Pn7/oCCg4SEhIOBgP6E+wz6+fj3+Pr9/4GCgoOHhBiFhYSCgPz6+vr9gYSIi4yNjYyMjIqIhYOEgQqCg4ODhISFhoeHhIgKh4aFhIOCgoOEhISFCYSCgYD+/Pr6/IT9GP6AgYGA//z6+vr8/4CBgYKBgYD9+/r6+gGuhK0Crq+EsDuvramlnpeQioWAe3Vwa2VfW62mpKaprLFbYGZscHJycnBubGtqamxucHFxb2toZWJgXl1dXV9gY2VmZ4RoE2lpaWhnZmZmaGpsbG1sa2hkYV6HXTteX19eXV1cXWFma25samlpamxtbWxrbG1ub3BwcXFxc3V2eHh4d3d2dnV0dHN0dXV3eHl7fXt6eHV0coRzV3JwcHFydHZ5foB/fnx6eHZ1dnZ1dHNycXBvb3F0dnh5enp9gIKDhYaFg4B+fXx6d3RxbmtpZ2VkZWlsb3BydXZ3d3Z2dXRzcXBubWxsbW5ub3Byc3R1dYR0DXV1dXZ3eHh5eXl4dnSFchBxb25vcHJzc3Jxb29vcHBwhG4Hb3BwcXJzc4ZyCnFxcHBxcnJxcG+Ebgttbm5vcXJ1d3h5eYV4VXd3dnd3eHl7enh3dXRzcXBubW5vcXN1eHt9fX5/fn59fX18fHt5d3Rzc3N1dXV3d3h4d3d1dXRzcXBxcXBvbmtpaGdnZ2hpam1ubm1saWZkY2RkZWWEZHNlZmZnaWtucnV2d3h4d3Z0c3FxcHFxcXBwb25tbGxraWhoaWptcHR2eHh3dXJva2dlYmBhY2ZoaWpsbm9wcHBvb3BydXd3dnRzcnN0dXV2dXNvbGpqbG9ydnl9goeLjpGVmqCnrrK1uLzAxmVmZ2hqa2xshGs8bGxsa2poaGdnZ2ZlZWRjY2RlaGttbm1qaGZlZWRkZWZmZ2ZlZGJhYmNjYmFfX2BhYWNkZmhpaWhnZ2hohGkxaGhpamtsbW5tbW5vcHFycnFwb25tbW5xdHZ4d3RzcXFwcHBvb29wcXJzdHRzcnBvboVsG2trbGxub3FycnR1d3h4d3d2c3Bubm1vcXFwbYRsJm5ub3BxcnN0dHNzcnBubW1tbGtpaWprbW9wcnJzc3Jyc3NycW5shGtBamhkvK+kl4qAeXRvbGprbW9zeH6Fi5GXnKGmqautr7CysrKxsbGys7S0s7GvrKurrK6ws7W3uLe2t7i5ubq6u7uEXh66t7WysbGztri6vF9eubSvra2usbO1tra2tbSzsrOEtAizsrKys7a6X4dgMF66trW2tre4ubq6ure0sK6tra2vsbS1tra3t7e4uLleX2BhYF9eu7q4t7m6vL5gYoRjFGJgX726ubm5uLa1tLW4u75gYmJkiGUNZGJgXru6urq9YGNmaYZqEmhmY2FfX19gYWJiY2NjZGRlZYRmCGVkY2JhYWFihmMaYmFhYMC/v7/AwcHAv79gYF9euba0s7O1uFyEXQdcW7Oxr6+vAZSFk4SSVpGRjoqGgHlzbWdjXlpXU09MSYyGg4SFhodFSE1SVVdXV1VUU1JRUFJTVVdXVlRRT01LS0pKSktMTlBRUVJTU1NUVFRTUlFQUVJUVlZWVVNQTEhGREREh0OAQUFAPz5BRUlLSUdHR0lMTU5PUFFTVFVXV1hYWFpcXmBgYF9eXl1cW1taXF1eYGFjZWdmZWJfXlxdXF1cW1lYWltdYGNmaGZmZGJgXl5eXVxcW1taWFhYWVtdXV5eXmBjZWZpa2ppZmRjYl9bV1VSUE5MSklLT1JTVFZZW1xcXFsHW1paWFZUU4VSBVNUVVVWhVcXWFlbXF1fYWRmZ2dmY2FfXl5eXVxbWlqEWwFahFiDWoRZClpbW1xcXV1cW1uFXAtbWltcXFtaWVhXWIVXDFhaXF5fX15eXVxdXYRcKl1eYGFgXl1cW1pYVlVUVFZXWVxfYmVlZmhoaGdnZ2ZmZWNgXVxbWltaWoRbAVqFWwpZV1dXVlVUUU9OhE0PTk9SU1NTUk9MS0pKS0tMhEuETEZOUVRYWltdXl5dXFtZWFhYWVlZWFhXVlVUU1JQT09QUVRYW15fX11aWFRRTUtIR0hKTE5QUVNWV1dWVVNTVVdaW1xbWllYhVcuVlRRT05PUlZaXmNnbXJ3eXyAhIuSmJ2hpKissFlZWltcXF1cXFxdXV5eXlxbWoRZhFgtV1dXWVteYWFgXltaWFdXV1hZWlpaWVhWVVdZWllYV1dYWVpbXF5fYF9dXFtbhFwfW1paWltcXV5eXV1eX2BhYmJhYF9eXV1eYWNlZ2ZjYoRgQV9eX19gYWJjZGRjYV9eXFtbW1paWllZWVxdX2FhY2RmaGhnZmRgXFpZWFlbXV1bW1xcXV1dXl9hYmRlZGRkY2FghF8MXl1cXV9iZGZoampqhGk+aGdkYmFhYGBfXViilot/dGplYFtXVVVWWFxhZm1zeX+Dh4uOj5GSk5WVlZaXmJmbnp6em5iWlJOVlpmcn6GEoj+kpaWlpKWlU1NTUqGem5iXmJueoKOmVFSjnpmXmJqcnp2dnZycmpiXl5iXlpSTkZGQkJOYTlBQUVFSUlJRn5yEm4WcA5qYloSUA5aZm4SchJsSnVBQUlNTUlGioqCfoKGipFNVhFYWVVNSoZ6dnJybmpqam56ho1JTVFVVVYVWXVVVU1FPnJubm55SVVlbXV1dXFxbWVdUUlBPT1BRUlNTVFVWV1hYWVlZWFdWVVRTUlNUVVVWVldXVlVUU6WioaGipKWlpadUVVRUpaOhoKCho1JSUVFQT06Zl5WUlJx+h33/fv9+/37zfv9/z3++foR/i36Cf5x+iX+efod/iH6Jf41+kH+FfrJ/in6Ef4d+h3+FfgICBAAqm5aSjYmEgPjx7u3v9vyBg4WHiYqMjpGUlpiam52dnZuZl5WUlZeZnJ6hhKIToaCgoaKio6Okpaalo6CenZydnYSegJ+fn6Cgn5+foKGhoqOkpKWmp6alpKOioJ6dm5uZl5WUk5OVl5mbm5qYlpOQjYuKiIeGhoiLjpGUlpmbnZ6goaOoqq2vrq2rqqinpqepqqqrqqmrqqmnpJ+dm5qbm5udnqGlqKmqq6usra6wsrW3uLi4t7W1tba2t7e2s7Gvr6+wRLK1t7i5uLe2tbW2ub2/v768urezr6yqqKampqenpqWkpKOkpaanqKmqq6yusLGysrGwr62sq6qopqOgn6CipKeqrbCyhrM2tLS0s7OysbGwr66traytra+xs7a6vLy8u7q4trOwrqyqqaioqaqsr7G0tre3trW0s7GvrquphKcKpaWlpqeoqqusrYauHq+urq6tq6qpp6alpKOjoqKhoJ+goaKkp6mqq6qqqoSpGaqqqamqq62vsLKztba2trW0tLOysrGxsbCIrxKuraysq6ysrK2trKupp6SjoqGEoAehoaKjpaaohqkBp4SlOKSkpKWmqKqrq6uqqqmopqOin56enp+ho6aqrrG1uLu9vsDCxcnNz87MyMTAvry7urm4uLe2tLKwha9isbW4ubWupp+alpORkI+OjYuJh4aGh4uQlZmdn6Gkpqiqq6yusbS4u77Aw8bKz9TX29/i5urt8fT3+vz+gIGCgoOEhoeJiouMjIuLiouMj5GTlZeWlJCNi4uLjIyMjY2MjIyGjYuOD42MjIyNjo+Pjo6NjYyNjYWOEI+QkJGRkI+Ojo6PkJGRkpKEkYCQkJGRkZCQj5CRkpSWlpaVlJOTkpKRkY+Pjo2NjY6QkZKTlJOSj42Kh4SCgP359/Tz8/Ly7+vo5ePh39zY08/MycfEwb25s66oopyXko6KiIWDgoKFiIqKiouLiomIiIeHiIqNj5GTlZeanaCjp6qvtLm9wcTIzNHW2t/k6Ozu8Afw8O/v7/DxhPIc8/T09PLy8/b6/f779/Hs5+Xl5unr7Ozr6unp64XsDe3t7u/v7/Dx8vP09PSF8y/09fb19vb3+Pr8/fz8+vf19vb3+Pj3+Pf29PLx7+/x8/T4+/v8/Pz9////gP78+oj4Dvn6/oCBgoKCgYH+/fv5hfc3+Pn6+/z8/Pv6+fn5+vv8/P39/oCBgoODg4KBgP79/f38+/v6+vv8/P3/gIGChIaHiIiIh4aFhYSEIYWFhYSCgYCA//369e7o493Z1tPQzMjDv7u4tbOxramknypwbWllYV1arqilpKaqrlhaW1xdXmBhZGZoamtsbW1sa2lnZWVmZ2lsb3GEcg9wb25vb29wcHBxcXBua2mEaIVph2pua2xsbW1ubm5vb25tbGtqaGdlZGNiYF5dXF1eYWJkZGJhYF1bWVhWVVRTVFZXWVxeYGNlZ2hqa21xcnR2dnRzcnFwb29xcXBxcHBxcXBvbWloZmVmZWVmZ2lsbm9wcXFydHV4en1/f4CAf359fX6EfyV8enl5eXp7fH1+fXx7enl4eXx/gIB+fXt5dnNwb25sbG1tbWxshGshbG5vcHJzdHZ3eHl5eXh3dnR0c3NycG5sa2ttbnBydHZ3hHgHeXl6e3t6eoZ5Inh3dnV1dnh6fH+BgoKCgH58eXd0cnBvb25vcHFydHV3eHmEeAl2dnV0cnFxcnKEcQZycnN0dXaGdzR4d3h4d3V1dHJxcG9ubWxsa2ppaWprbG1ub29ubW1tbm9vcHBwcXJzdXZ3eXp7fHx7e3t6h3mIeAR3dnV1hHQLdXV0dHNxb25tbGyEawdsbW5vcXJzhHQGc3JwcHBvhG4Cb3CEcoRxdW9ubWtqamprbG1vcnV3en1/gYOEhomNkJKRj4yIhYOCgYB/f39+fXt6eXl5enl6fH+Cg4F8dXBsaWZlZGNjYmFfXVtaW11hZmltb3J1d3p6e3x9gIOGiYqLjZGUmJygpKerrrK1uLq9v8HCYWJjY2RlZ2lqbIRtEWxsbG1vcHFyc3Nyb21sa2tsiW2FbIVtFWxtbW1ubWxsbG1ub25ubWxsa2xsbYVuBG9wcXGEcANxcnOGdRJ0cnFwcHBvbm1sbG1ub3FxcHCHb4BubW1sbGxtbW5ub29vbmxraWZkY2HAvr27u7y8vbu3tLGvrammoZyYlZOSkI6MiYR/enVwa2djYF5cWlpaXWBiY2RkZGNhX15dXV5gYmRmaGlrbnBzdXh7foKGiYyPkpWYm5+jpqmrra6uraysra2ur6+ura2urq2rq6yvsrW2tBGwrKilo6Olp6ipqaiop6eoqYeqCauqqqurrK2troWtA66vsISxNrKytLa2tre2s7KztLS1tbW0tLKwrq2sq62vsbW4uLm6uru9vb5fvry5uLi3t7a1tLW1t7ldX4RgBl+9u7u5uIS3Cri5ubu7vLu7urqEuSi6urm5ul5fYGFhYWBfXry7urq5uLi3t7i5ubq8X2BgYmRlZmdmZmVkhWMhZGRkY2JhYWDAvry3saumoZ2bmZeUkY6LiYaEg4F/e3dzMFhVUk5KR0SEfnx6fIGFREVGR0lKS01PUlRWV1hZWVhWVFJQUFFTVVdZW1tbWllYVoZVhFYLVVJQT05PUFBRUVGGUoBRUVFSU1NUVVVVVlZWVFRTUlBPTUxLSkhHRURERUdISUlHRUNBQD4+PT09PD0/QEJFRkhKS0tMTU5QU1VXWVlXVlVUVFNUVldXWFdXWVlXVlNQTk1MTU1OTlBSVVdYWVlZWlxdXmBjZWZmZmVkZGRlZWZmZWNhYGBgYWJjZGVkYydiYV9fX2FkZWVkYmBeW1hWVVNRUFBPT05NTExMTU5PUFJTVFZWWFmEWxNaWVhXVlZWVFNRUFFSU1VXWVtchl0GXl5eXVxchV2CXIRbM1xeYGNlZmZmZWRjYV9dXFtaWVlZWlxdYGJjZGVkY2JhYF9dW1lYV1hXVlZVVldYWVtdXoVfG2BgX19fXVtbWlhWVlVUU1NTUlBQUFFSU1VWV4dYFFlZWFhXWFhaW1xdXV5fX15eXl1dhV4EXVtbW4RaglmMWANWVlWHVFlVVVZXWFlaWlpbW1taWVhYWFdWVldYWVpbW1paWVlYV1ZUUlBQT1BRUVJVV1pcXmBhYmNlZ2ptb29tamZjYF5dXFtaWllYVlVUVFRVVVZYXF9hX1pVUE5LSoVJNkhHRUNCQ0VJTVBUVlhbXWBiZGVoamxucHFydHd6f4OGio2QlJebnqGkpqipVVZWVldZWlxdXoRfDF5eXl9gYmJkZWVjYIReBGBgYWKOYRdiYWFhYGBg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g4KBgH9/fn18enl6e31+f39+fXx6eXh3dnVzcoRxHXBvbm5tbWxramloaWlqamppaWhmZWRiYWFhYmJihGELYmRlZ2lrbnBzdHSEdRN2d3d4eXp8fX1+f4CBgYCAf359hHwEfX19foR9En5+gICAgYB/gICBgoODhYWGhoSHMYaFhIKAf35+fXx7eXh1cnFvbm1samppaGdlY2FeXFtZWFhZW15gYmNkZWZnZ2doaGiEaR1oZ2ZmZmdoam1vcnR1dnh5enp7e3x7enl4d3RycIRuAW+GbgdtbGxsbW5vhHAOb29wcXJ0dXd4eXh4eXmEegt5eHVzcW5tamhmZIZiBWNkZWVliGQaY2RlZ2hpa2xtb3Byc3V3enx+gIGCg4ODgoKEgQyChIeKjY+RkpOUlpiEmQGYhZcXmJqbnZ+hoqOjoqGgnp2dn6Cjpqqur7CEsgi0tLS1tre3uYS6Nbm4t7a0s7O0tra4ury7uba0sa2rqqusrq+wsbGys7Oys7S0s7Kxr6yppqSioJyZl5WTkZCRhZAEj46Ni4WKH4mJiYeEgoB/fXt6enh3dnVzcnFxcHBwcXFwb25ubGyGaw1qaWdlY2BeXV1dXl9fhGAEX19fYQNJTE+EUQ1QTkxLSkpKS01QUlNVh1aEVVNWWFlaW1taWltcXl5eXFpYVlVUVFVXWlxeX2BgX15cWldUUk9NS0lIR0ZGRkdISktNTlBSU1VXWFhXVVNRTkxJR0ZERENBQD8+P0BCREVHSElJSoVLDUpJSUpKSkxNT1BQUVGHUIVPglCFT4dQMVFRT01MSklJSEhHR0lLS0tMTU1MS0xMTE1OTk1NTk5PUFBRUVJSUlNUVVdZWVpaW1yEXQdcW1taWllZhlgNWVlZWlpaWVlYWFlZWYVaFVlYV1dYWVtcXFtbWllYWFhZWlpaWYhYDVlZWFhYV1VSUU9OTU2ETodNBE5OT1CFUQZSU1RVVleEWANXV1iFWQVYV1VUU4RSClNVVldXV1haWlqFW4JchF0QXFtaWVlYV1ZWVVRUU1NUVIRVLVRTUlFRUFBRUlJTVFVVVldWVlVUUlFQT01MS0pKSktLTE1NTk9QUVNTVFRUU4RRBlBQUlNTU4pSh1EFUlNVVVaFVQ1WV1dYWVlYWFhXV1ZWhVUGVlhaW1xchF2EXlJdW1pZWVhYV1dWVVRTUlFOS0hG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amtsbW2FbgVtbGppaIVnFmhpa2xvcnV3eXt8fX5+fn19fHx7e3qFeQN6enuHehd7e3x9fX18fHt8fX5+f35+fXt6eHZ0coRxEnJzdHV2d3d3dnRyb2xqaGdmZoplBmRkY2NkZIZlFWZnaWpsb3F0d3l7fX5/gIB/f359fIZ7FXp6enl4d3d2d3d4eHl7fH1+gICBgoSDgoKKgQeCgoKAgIB/hH4WfHt7enl4eXl4eHl5ent8fn+AgIGCgoaDC4KAfXt5eHd2dnV2hHcDdnV0h3MjdHV2d3d4eXp6e3x8fHt7enh2dHNxcXJzdHV3eHl5enx9fX6Efwt+fHp4dnRzc3JycoVxCXJzc3R1d3h5eYV6DHl4eHd2dXNycXBvboVtHGxsa2pqaWloZ2VlZGRkZWZnaGlpaWhnZmVjYWCFXwhgYWJjZGVmZ4RoJ2dmZGJfXVtaWFaqp6akpKOjo6SmqKtWWFpcXV5fX19gYWJiYmNkZYRnBGhpaWmGaA1paWpqa2xub3BwcXJzhHKCcYRwCnFycnJzdHR2dneFeBN3d3Z2dnd4eHl6ent8fH19fX5/hIAHgYGBgoKDhISFF4SCgH16eHZ0c3FwcG9vb3BxcXFwb25uiW0UbGtqaWhnZWRjYmFgX19eX19gYWISU1RUU1NRUE5MS0pJSUhJSUpKhksMTE1OT1BRUVJUVVZXhFgeV1ZWVVRUU1JRT09OTk1NTEtLSklIR0ZGRUREQ0NChUEpQkJERUhLTlBSVVhaXFxcW1paWVhVU1BOTEtKSUlISEdGRUREQ0NCQUGEQAlBQUJCQkNDREWFRgFFikQORUZHR0hJS0xNT1JTVFSFVRxWVldYWVpaW1xdXl5fYGBfXVxZVlNRTk1LSklIh0cJRkdHSElLTE1NhU4NTU5PUFJUVVZWVldXV4VWhVcPVlZVVFNSUU9OTk5PUVJThFQGU1NTVFRUhFMHVFRVVVZXV4dYA1dXVoRVJlRUVFVVVVRUVFVVVlZXV1laW1tcXF1dXl9fX15eXVxbWlhXVlVUhlMNVFVWV1hZWlpbW1xcXIhbB1xdXl9fYGCEYQRgYF5ehF0DXl5fh2AGX19eXl1dhlwGXV1dXl5ehl8RXl1dXFtbWlpZWVpaW1tcXF2GXw1eXl1cXFpYV1ZUUlBOh00ETk1NTYZMFk1OT1BRUVJSU1RVVlZWV1dW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cxF0dHV1dXZ3d3h4eXl5enp7e4Z8CXt7e3p6eXl5eIR3F3Z1dXV0cnJyc3N0dXd6fYCChIeIiouLhYwNi4qIh4aFhYSEgoF/foR9hHyHfYJ8insHenp5d3Z1dIVzCXJycW9ubGtpaIRnBGhpamqEa4RsBW1ub3BwjHEFcnJyc3OEdIJ1hHYJd3Z3dnd3eHh4h3kLeHh3dnV1dHNycXGJcA5vb25ubm1tbGxramppaIRnEWhqa2xub3BxcnN1dnd4eXp6hHsQeXd1c3JxcG9tbGtqaWloZ4RmE2dnZ2hqbG5wcnR3eXt8foGCg4OFhCKDg4KCgYGBgICAf39+fn18fHt6eXh3dnVzcnJxb25tbGxshGuJbA5tbW5ub3BwcXJzc3R1doV3EXZ1dHRzcnFwb25ubGtqaWhohWeEaAtpaWlqamtra2xsbIRrCGpqamlpaGhnDUxLS0pKSUlISEdHRkaFRQ1GRkdHSEpLTE1PUFFRhVKFUQRSUlNThVSEU4VSD1FRUE9OTU1MTExLS0xMTIRNCExMS0lIR0VFh0QWRUZISkxNTk9QUVJTU1RVVlZXV1hYWIlZMlhYWVpaWllZWVhYWFdXVlRUU1RUVFNTU1RUU1JRUVFQUFBPTUxLSklISEdHRkVEQ0NDhkKEQTNCQkNDREVGSEpMTlBSVFZYWltdXV5eXV1cWllYV1dWVVRTU1JSUlFRUFBPT05NTU1MTEyETYROg02FTFZNTU5OTk9PT1BQUFFRUVBQUE9OTUxKSUdGRENCQUBAPz49PTs6Ojk4ODc3Nzg5OTo8PT5AQUNERUZHSEpLTE1OT09QUFBRUVFSUlJRUVBPTk5NTU1MTIZLCExNTk5PUFFRjFIKU1NUVFRVVVZWVopXElZWVVVVVFRTU1NSUVFQUE9PToZNhU4PT1BQUVJTVFVWV1laW1tbhFwRW1taWlpZWFdXV1ZWVVVUU1OIUoRRAVKFU4RUhVUBVIdVhVYIVVRUU1NTUlKEUYVShFMMUlJRUE9PTk1NTExMjEuYTJFLCkxMS0tKSklJSEiFRxZISUpKS0xNTk5PT1BQUFFSU1RVVldYh1kHWlpbXFxdXYVegl+GYAFhhWCEX4VeFV1dXFxaWVhYWFlZWVtdX2FjZGZnZ4RoEmdnZmVkY2JhYGBgX15dXFtbW4VciF0GXFxcW1tbh1wGW1taWVhXhFYKV1dXVlVUUlFPToRNBE5PUFCHUQVSUlNUVItVhFYFV1dYWFiGWQdaWllZWVhYiFkPWFhYV1dWVVVUU1NSUVBQiU+ETjRNTUxMS0tKSUlISEhJSktMTk9RUlNUVVdYWltcXV5fYGBfXlxaWFdWVVRSUU9OTUxLSklJhUgQSUpLTE5QUlVXWVpcX2BhYYViBGFhYGCHX4ZeDl1dXVxcW1pZWFhXVlVVh1SKVQRWVldXhlgYWVlaWltbW1paWVhYV1dWVVVUVFNSUVBQhk+HUIJRiVIJUVFRUFBQT05N/37/fv9+/37/fv9+/37/foh+AgIEAAShoaKih6MLpKSkpaWmpqenqKiGqQWoqKinp4qmCKWlpKOjoqKihaEBoIWhhaIJoaGhoqKio6Ojh6SEpSimpqanp6ioqampqqqqq6ysra6vr7CwsbKzs7S0tLW1tba2t7e4uLq7irwGu7m3tbOyhbEGsK+urq2thKyCq4SqBqmoqKempoulCaSkpKOjoqKhoYWgCaGhoaKio6SlpYSmJKWlpKOioaGgn5+enp2dnJybmpmYl5WUkpGPjYyLiomIh4eGhoSFBYSDg4KCh4EKgoKDhIWFhoeIiIyJhIopi4uLjIyNjY6Oj4+QkJGSkpOTlJWVlpeYmZmam5ucnJ2dnp6en5+goKCRoYuihKMGpKSlpaamkKcHpqalpaSkpIWlh6YXp6ipqqusrK2ur6+wsrO0tba3uLi5urqHuyG6urq5ubi4t7a2tbSzsrGwr66trKuqqaimpaSjoqGhoKCJnxqgoKGhoqKjo6SlpqanqKmpqqqrq6ysra2uroSvFK6ura2sq6qpqainpqalpaSkpKOjhaKLoQ+goKCfn5+enp6dnZycm5uFmoWZEZqampubnJ2enp+goKGhoqKiiaMSpKSlpqenqKmqq6ytra6ur6+vhLCGsZCwiq8Grq2sq6qpiKggp6amp6ipq6ysrKuqqaioqKenpqSjoZ+enZybmpmYmJiFl4OYjZkGmpqam5ubh5wBm4mch5sOnJydnZ6en5+goKGhoqKEo4akgqWFpIejhaKIoYiihaGCoIehgqKHoYWghZ8Gnp6enZ2dhJyDm4qaA5uamoubgpyJnYSeDZ2dnJybm5qamZmYmJiGl4KWhZUUlpaXl5iZmZmampubm5ycnZ2enp6Gn4mggqGRogahoaGgoKCHn4agiaGDoIShiaIFoaGhoKCIn4OgBG9vcHCHcYRyBnNzc3R0dId1BXR0c3NziHIKcXFxcHBvb25ubodthm6Jb4JwinGEcgVzc3R0dIZ1DXZ2d3h4eXl5enp7fHyEfQt+fn5/f3+AgIGCg4SEhYMHgoB/fXx6eoV5A3h3doR1hHSEcwdycnFxcHBwhm+EcAlvb29ubm1tbGyIawVsbG1uboVvIm5ubW1sa2tqamlpaGhoZ2dmZmVkY2JhYF5dXFtaWllZWFiGVwVWVlVVVYlUB1VVVlZXV1iRWStaWlpbW1tcXFxdXV5eX19fYGBhYWJjY2RlZWZnZ2hoaWlpampra2xsbW1ti26Ub4NwkXEEcHBvb4ZuiW8XcHBxcnN0dHV2dnd4eXp7e3x9fX5+f3+HgB9/f39+fn59fXx8e3t6eXl4d3Z2dXRzc3JxcG9vbm1thGyCa4hsD21tbW5ub29wcXFycnNzc4R0g3WGdhF1dXR0c3JxcXBwb29ubm1tbYRsj2uFaoRpBmhoaGdnZ4xmDmdnZ2hoaWpqa2trbGxsh20bbGxtbW5ub3BwcXN0dHV2d3h4eXl6ent7e3x8hn2DfIp9g3yGewl6enp5eHd2dXSEcxFycnFxcG9wcHFyc3NzcnJxcYRwCm9ubGtqaWloZ2eKZoJnhGgEaWhoaIVpBWpqa2trimyIa4RqF2lpampqa2tsbG1tbm5vb3BwcHFxcXJyi3OLcoZxi3KDcY1whW+EboRtg2yEa4RqhGmRaIhpi2qGawlqamlpaWhoZ2eIZollDGZmZ2hoaWlqampra4Rsg22Hbolvg3COcQxwcHBvb29ubm5tbW2GbpBvg3CHcQlwcHBvb29ubm6FbQNubm8FUFBRUVGJUoRTBlRUVFVVVYRWBVVVVVRUilMIUlJRUVBQT0+LTohPhFCCUYtSg1OEVIdVg1aEVwVYWFlZWYVaCVtbW1xcXV1eX4pgBV9eXFtZhlgFV1ZWVVWHVIZTglKPUQVQUFBPT4pOBU9QUFFRhlIgUVFQUE9PTk5NTExMS0tKSklJSEdGRURDQkFAQD8/Pj6HPYQ8iDsJPDw9PT4+Pz9AjkGGQipDQ0NERERFRUVGRkZHR0hISElJSktLTExNTU5OTk9PUFBQUVFSUlJTU1OdVIRVhVaKVQVUVFRTU4dSiVMVVFRVVVZXV1hYWVpaW1xdXl5eX2BgiGGCYIRfGl5eXV1cW1taWVlYWFdWVVVUU1NSUVBQT05OhU2GTAJNTIRNC05OTk9PUFBQUVFRhFKEU4dUC1NTUlFRUE9PT05Oh02CTJBNhkwGS0tLSkpKjUkMSkpKS0tMTU1OTk9PhFCEURNQUFFRUVJTU1RVVldXWFlaWltbhFyFXQFehF2JXIhbiVoGWVhXVlVViFQLU1NTVFVWVldXVlaFVQxUVFNSUVBPTk5NTEyNS4JMiUuFTIVNjEyJSw1MTExNTU1OTk9PUFBQhFGCUolTilKMUYpShVGEUotThFKFUYRQhU+FTo9Nik6RTwhOTk5NTUxMTIhLhEoKSUlJSkpKS0tMTIRNCE5OTk9PUFBQh1GIUgZTU1NUVFSGVYdWhFWDVIpTjlQBU41UCVNTUlJRUVBQUIdPAVD/fv9+/37/fv9+/37/fv9+iH4CAgQAAauMrB+rq6uqqqmpqKinp6ampaWkpKOjoqKioaGhoKCgn5+fhJ6GnYSch5udmoSbj5yKnYWeh58PoKCgoaGioqOkpKWlpaamhqWEpIijB6KioaGhoKCEn4iei52FnoWfiaACoaCJoZmiiqOIpJ6lh6aHp5KoBqenqKeoqI+nhqaJpYSmBqenp6ioqIephqqKq4esia2ErIWrhqqFqYSoBqenp6ampoelhqSHo4eiiqGFoIifj56InwKgn4ugA6GgoIihhaIGo6OjpKSkhKUWpqamp6enqKipqamqqqqrq6ysrK2trYWuhK+EsIWxhbKNs5G0DrOzs7KysbGwsK+vr66uha0crKyrq6qqqainp6alpKSjoqKhoJ+enZycm5uamouZiJiJl4aWhZWNlIaVhJaDl4SYhJmDmoabhpyLnYSeiZ2HnImboJqGmYmYjpeFloaVkJSZk4iUhJWFloWXhJgGmZmZmpqahJsGnJycnZ2dhJ6Dn4Sgg6GFooOjhaSJpYWmhKcJqKioqampqqqqhauLd4R2FHV1dXR0c3NzcnJxcXBwcG9vbm5uhW2DbIVrhmqGaYdoj2eKaIVphGqIa41shm2Jbgpvb29wcHFxcXJyhHMCcnOEcohxhHCCb4Ruhm2IbIpriWyNbaRuim+ScAVxcXBwcI1xhnKGc6R0jXMLcnNycnJzc3N0dHSEdYh2hneLeJB5hXiHd4Z2BnV1dXR0dIRzhXKHcYdwi2+JbohtlmyHbY1uhm+GcIRxBnJycnNzc4R0D3V1dXZ2dnd3d3h4eHl5eYV6h3uGfId9iX4HfX1+fX1+fYx+hX0NfHx8e3t6enl5eHh3d4V2GnV1dXR0c3JycXBwb25ubW1sa2tqaWloaGdnhGaTZYxkiWOFYoljhWSDZYRmhWeFaIVphmqIa4lsiGuPaqRpi2iOZ4Zmh2WNZJZjimSFZYdmhWeEaIRphmqFa4Zshm2FboVvhXCLcYNyhHODdIR1hnaNXRRcXFxbW1taWllZWFhYV1dWVlVVVYRUhVOGUoZRhlCIT4tOAU2ETopNiE4GT09OTk5Plk6ITwhQUFBRUVJSUphThVKYUYVSh1ORVJ9Vi1agV4pYk1mFWIJZkFiJV4dWBldXV1hYWIZZAlpZhlqKW5NchVuGWodZhFiEV4dWj1WZVKJTlVSGVYZWhVeFWIVZhlqTW6FchluDWoRZhViGVxhWVlZVVVRUU1NSUlFRUFBPT09OTU1NTEyNS4xMi0uVSoZLhEyETYVOhU+GUIlRjlKJUZRQmk+eToxNgkyGTQJMTZ9MiU2LToVPhVCGUYdShVOFVIZVhlaKV4RYhFmEWoRbhVz/fv9+/37/fv9+/37/fv9+iH4=","name":"royal_esplanade_1k.hdr","id":263,"type":"FileEditor"},"264":{"outputLength":1,"height":null,"title":"File","id":264,"type":"TitleElement"},"266":{"value":"royal_esplanade_1k.hdr","id":266,"type":"StringInput"},"267":{"inputs":[266],"height":null,"id":267,"type":"Element"},"271":{"x":-497,"y":256,"elements":[272,275],"autoResize":false,"buffer":"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3NrZ2NfU0M3LysnIyMjHyMvQ1dtxc3Z5enl2c+Th3dra3ODkc3Lg2tXS0tLT1dfY2NbU09DNzMvMzM3Nzc7O0NTX1tHMycvR1tna2dnbbm9ubdnY1tXX2nBzdHFubW5s0MzLy8vMztRtcE5xbmtqZ8zLyMXCwL+9u7q4ub7EyWlsbGnNy8XAwMPHyMbDwsTIy83MyMK/v7/Bw8TFxcjKycXBvb2/wMC8uLnBxL27vLq8wsG+vb9gYWOEZAZjY2RkZWaEZ4DOzs9pbG5wb27W0czEwcfN0W1vb29qycXBvry3tbW4YGHAYmVnaGdpa2psbm9xc3FtamdmZ2hpa2tqaWlrbW5ubm1sa2pnZ2hqa2trZ2doZ8fDyMa/v8TExMbIyMbBvru3uWNnZWdrbWtnyMbGyGVlZmdpamxtcHN1c25qZ8nIZWhkY2NlZcXAu7a1ubu6uLa2uLq7u77CxcfHx8bFxMC5s7K1uLu/wsG/vbu6vL/BwcHCxchlZWRkZMrKx8PAv76+vr28vL2/wMLFx8jIx8fJzNBp0c/RbG9v1crGxcTFx8bExMbIzNRtboRtFGxq0GlucXJycW/db3J0dHRzcXBuhG2AbnBycnNzc+DWzsrFwL/Au7m8vr/CxMLAwsbM0dbZ2dptbm9wcXBubXB1d3l6fH9+eHRv08/OzdBrbm5sampq1ddvc3V1dHLe19LPy8jHxsnNzcvO0dHUbG1tbnF0dnh4dnNxcHBxcXN0dXV0cuHf3uDi4nFx4+Tl5+fm4+He29gK1dPS1dvg5XV1doV3NHZ2deXf3NnX19bV1tTS09PV19jZ3N7e2tbU09PU1tTPysrN0dHOzc/R0NDTa2xsbW1tb3KFcyJycW9t1NHSamtqamprbG5wcXFxcG5ubdbR0NPW2Npubm5vhHEwcnN0dXZ2dXV0dHV3e31/fn19fn+BgX9+fX18e3p4dHFu2djabm9vb3FzcnBvb29xRHFxcXJzcW5s2NfW1dTQzMnIyczPz8/Q0NLUa2xtbW5vbWxsbWzWbXF2d3d2dHNxcG1sbm9tamlqbG5ub3J3ent6eXh4hHeAeHl4eHd4eHl5eHd2dnZ0c3NzcW9tbW5ubW1ub3Fzdnl6eXl3dXJva2hlxcG+vL7CxWNlZ2hpamppaGhnZmdpaWloaWttb3Bwb3Bwb3BvbG10h5mgmYyEf3t4c29ubGtpzcfDwsTJzdJsbnBycXBvb3FxcW9sa2loaGlxe4SJjI1QjYuJh4SDg4OCgYB+fHx+gICBgYKCgX58enl3dnV0c3R2dnV2dnTk4uDb2+BzdHPj4d3a2t9xcnN0dHV3eHl7f4B8eXl35d/f4+Tk4+Pi4uOE5DLj4N3a2drZ2NjY19ja3uHmdXd5fHx7eHTl4d3b3N/j6HZ15+Lg397e3t/g397d3Nzb2IfWgNfX2t7i4t/c297j6Ovr6enrd3d3duvq6ejr7Xl8fnx5eHh1497c3Nzd4Od4e317eXd16Ofj3trX1tTT09TY3+Pmdnp6d+vn4dva3eHh3djW1tjZ29vX0tDR0tPW2Nna3uLj4d7b29/h4N3Z2+Xp4+Hi4eTq6OTj43Fyc3Rzc3JxI3BwcG9vcHBvbtzc3nBydHV0cuHd2tPP1NnccXN0dHDX19nchOCA43V15HJzdHRzdXd3en1+gYOCgH57e31+gIGCgYB/gIGCgoGAgH9+e3l6fH5/f319f33x7PHw6Onv8O/w8fDt6ebj4OR6f3x9goWDffPw8PJ7fn9/f4CBgoWIiomFgH3183p6enx/gPr18Ovs8vf28u7s7O3t7e/z9/j49vT08u4i5uDe4ubq7vHv7Ojk4+Xp7O3t7e/xeXl4eHjs6OLb2NfZ24TcN93e3d3f4ePl5eXm6Ox37Onpd3p67OLf4OPo7Ovm4t7b3OFycnFxcnNzceByd3t7eXd05HJ0dniHeYB6eXh5eXh2dHPi3Nra19TU1c/KzMzLzc7Kx8fLz9TY29vabG1vcXN0cm9xdXVzc3R1dXV2dOPi5OTod3l4dXJvbtnZb3J0c3Jy4t/f4+Xm5+fo6OLc3NzY2G1tbG1vcnN1dnV0c3NycnFyc3NycW/a19XW2Ntub97d3d3c2tfT0EbNysjHyMvQ1dtvcG9ubWxra2tsbNTQzszKy8zOz87Ozs/Pzs7Q0dPU0MzKycnKzMrEvbu9wsTCwsbLzMzOaGlpamlpbG5vhG4ibWxqaMzKzGhra2xra2xub25ubWtpaGjMx8fJzM/Sa2tsbYRuF29vcHFxcHBwb29wcnV3eHh2dnd4eXl4hHYVdXNxbWpozs7RamxsbXBxcG9ub29wgIaFhoeIhoOA///9+/jz7uvq6+/y8/T29/n9gYODhIWGhIKCgoD+gIaLjIuKiIeFhIGBg4WEgYCAgoODg4eLj5CQjo2NjIyNjY6Ojo2NjY6Pj46NjIyMiomJiYiGhYaIiYmKjI2Qk5eanJuamZaSjoqGgf349PL1+v2AgoSHiImIWIeHhoWEhYaHhoWGiIuNj4+Oj46Oj46Mjpetw8vDtKqknpqUj4yKh4P/9/Hv8fb6/4KEhoiIh4eHiImJiIaEg4KBgoqWoaeqrKuppqOgnp6enZyalpOSk5WEloCVk4+LiYiGhYOBgIKEhIKDgoD7+ff09PmAgoD+/fn19vyAgIGBgIGDg4OFiYmFgoKA+PHy9ff29fTy8fHx8PDv7uzo5eTj4uLj5OTm6vD2/IGEhoiJiISA/fj08fH1+f6BgPz38vDu7u7w8fHw7u3t6+nn5ubm5+bn6Ojq7vHx7YDp6Orw9vv8/P3/gYKBgP37+fj6/oOHiYiFhIWD/vn39vb2+P+Eh4mHhIKA/fz48u/t6+no6Ont9Pn8gYSDgPv48+7v8/n8+vf29/n6+vn17+zt7vDz9fb2+v79+vTv7/Lz8u7o6vT58/Hz8fT8/Pn5/ICBhIaHiIiHh4eGhYWFhDqCgP/9/YCCg4WEgf359u/s9Pr+hIeIiYT+/Pv8//z7+/6Dg/+AgoODgoWIiYuPkJKUko6Lh4aGhoiKhIuAjpCRkY+Pj46Lh4WFhoaHhoODhYP89/z78vP6+vr7/fz69/X08faGioWFiYuJg//8+/yAg4OEhIWFhoiMj42JhIH9/oGCgYCBgf348u3t8vb18u7s7Ozt7fD0+Pr7+/r6+PTt5ubp7fH19/Tx7+zs8Pb4+fj4+v6AgYCAgP358+0I6efo6urq6eqE6x7u8fT19vb3+v6A/fv8gYSE/fLv8PDz9/bz8fDv8vmEgH2BgoKA/oGGiYmHhYL/gIGDhIWFhIODhISFhYWGiIeFg4D78+3r5+Df3tfU19rb3+Ph4OLn7fL1+fz/gYSGiIqJhoKEh4iHh4iIh4SDgfn19vb6goWFhIGAgP39goWGhIKA+vLw8/X2+fr8/fr19/r5+4GCgoOFh4mKioiFg4WCDYSEhIOB//z6/P7/gICF/xL8+fXy7+zp5+jr8vj/goODg4KGgQ/++fXz8vP09/n49/f4+fmE+hf59fHw7+/y9vXw7e7z+Pj19Pf6+vv9gIWBJYSHiIiIiYiGhYOB/fv9gIKDg4KCg4WHh4eGhIKBgPv19Pf6/P+EgC+Cg4OEhYaHiImIiIeGhoeJjZCRkI6Oj5CRkZCOj5CPjo6Lh4OB/vz+gYKCg4WGhYSDAYWIfZJ8i30BfMx9h3yvfYh8vn2GfIN9hnyQfZ98iH2IfIJ9qHyEfYZ8iH2IfId9j3yEfbN8kX2DfIZ9iHyFfYl8A319fK59knyIfYR8j32CfIZ9rnyFfZl8B318fHx9fX2OfIh9AXyHfQF8lH2ZfJN9hXyHfYJ8hn2QfJZ9hnyCfZJ8i32pfJF9g3yQfYd8qX2DfIx9AgIEAEeLiIJ9eHZ1cnBtbGzTamtsb3FydHV3d3h5eHh5eXh5eHZ1eHp6dG5szsfLz8vLaGpwd3d1cGrR0mptb29ubm9ubG1ubWxsb4RwgHJ5eXl+fXl1dXd1cnFxcXNwa2loZ2doaM/Ny8rMzc3N0GtsaczKy9FsbdfWbG5ta2nTa25ydXRx29TP0NPUzsnJ0G1vcXBucHFxb29wcXd6d3NxcG5ubWvQzszHx2lxdHJucHNxbmxqzmho0mpra21vcG9ubW5wcnJzdXV1dnZ1gHR0dXZ1dXR0dHJxb29wbmxr0tNrbXBxc3Ntz8nGxcbJy8/R0NHPzsvKzM3O0mrT0NLRztDV19fZ2tnX1NLS09bY19bX2W5u29vY0M3Ozc3MysfCwMHBxMbJz9TY2tna3uDc1dHOztDT1tXQ0tvj5eTh3NbSz83Nz9LV0c3O1dzegNjPyszOzdDW2tXRzc3OztDRz9DY2dbY29jS1dzecXPi293g3tzc4Obq6eXm5eTl5+x5evDq5uPg3NnV1NPU0snAvLq+wsrW4OTk4uN2e3x5d3l9fnx3cW9xcnFwdH+Hg3Zt0c/P0NJtdHp9gIqYt290e399f3dxcta/t62kknlvgGnPz2ttbG1tbG5vcnR0e3p1a2bJZ2tvcN3Z0c7P0dTSy8nR2XN0cnFxb21saWtra86/vcTSbnJwa2pqa2psampqbnN0dnd7fHt5dnV2dXFub3BubWxqamlpaWjOzczKzGZmaGpqaWjHx2pvcXBqaWpnZ214R1RbYWBTQm9qa21ugHBwbm9xcXJzcW9xcG9ubGrPzczO0tLOztDT1dPVbnBt1dPU19ja299ycnHd0M7S1NXS1NPMx8G+wsnNycXGyMnDvLzAwL++vsHCv76/vry7vb24s7CvsLK0tri/xcnJxcDBw762r66uq6mkqK6wsbGysbGztbCnpau1w87XbdTRJtZtbWvNx8LCw720ra60vcTGxcXIys3O0dbY0cfBwL/Dyc1rbW5uhG2AbmzU1dlwcG3Py8nJzGdqbW9ydHR1dXd5eXh149zUz8vHxMXJxsLFz9zldXLe2tLN0dHQyL68vsDDxs3PysjJys7MzMvIzc/Q1dbccNlsbMzIztXe5XRycG9ubW3c4ebj4N/idHXm4eN0dXZ3enp1cW9wdHl7eXh2dHZ1dnV0dXdpdXJydHR1dG7U0c/P0dDNz9Zs2dbWbGxub29vcHF1fH17dnJwcXN1d3d0dnh3dnR0d3p/gHt0cHF2e3p6e3p4eHp9fH2GjYh+dnFwcXFvbW9ubW9wb29wcXFubW1tbnFyc3R1dXZ2dXR0hHUednVxcXRzcXNzcG5ubnJ5fX99fH1+gIODgoeNkJGOHIeDf3t4d3d1cm9tbNNpamxvcHJ0dnh4eHl4eXuEfD16eXx+fndxb9TMz9PQ0Gptc3h4d3Js1dZsbnBwbm9xb25ub29tbG9xcXJydXt7e4B/end3eXd0c3NzdXNuhGw+bWzY2dnZ2NXT0tRsbWvS0dPZcHDf33Fzc3Fv3m9xdHRycNvV09ba3tzY2N90dndzcHFyc3FydHZ9gX15dXSEcIDd3NnT0251d3RvcHNycG9t1Gpq1mxtbW5xcXBwcXF0dXR0dXNycnNzc3R4e3x9fX18enh3d3d0cnDc3W9xdHV2dnHX0tDQ0tfb4eTk4+Hf29rc3uDmdOfj5ePe3+Pl5ejq6eXh39/i5unp6erteXju7uzk4eLi5OTi3dfT09TY2oDe5Ont7+7u8fLu5+Lf4OTq7u3n6vP7//758uvn5eTl5ujq5+Df5Ovt5tzZ3OHj5u7x6uXi4+Xk5+nn6fHu5+fo49zd5Op3eOzn6erm4eHl5+nq6Orq6ejn63l57+rm5OHf3drZ2drYz8nGxsvQ1+Ps7uzp63uAgn9+gIWIiIR9e4B9fXx6fYeOiHtz3t/h5OZ3foSIjZimxHR4e3x3d25mZr+sp6OhloR9efP1f4B+fn17e3x9fXp4e312cN1vcXNy4t3W0tPV2NbQztXbdHV0dHV1dHRyc3Nz38/N095xc29qaWxub3NzdHV5fX5/f4KCf3x5d3h3dHN1eHh3dnZ3doB3dnbs7O3s7XV0dXh3d3Xf3XV6fHt2d3p6foujZn2LlJJ9YZmHgoB+fn57e3x8fX57enx8fHt5d+nn5+jr6ODc29vf4ed5e3jq5ubo6Orr73p6ee3g3+Xp6ufn5N3X1dXc5Onn4+Pm5+DZ2+Di4uPl6u3q6uzr6ejq6+bi39/g4YDi4+Xr8fPy7efo6ePZ09TW1NHMz9fa2tnY09HT1c/Dw8zV3+bteOnl7Hh4d+ji3uDh29HKyc7W29nW1dfX19XV1NPQysvP0tnf4XN0dXV0dHV0dnTj4+d3eHXh3dvd4HBxcXN0dXV2dnZ3dnRz4+Ph4uPj4eHj3NPO0Nnic3Da2oDW1t7i5d7X1dbY1tPU0svHx8rP0NLRz9TW19zc33Heb2/Sy83O09xxcHBxcnJx39zb2NfX2W9w29fZb3J0dXh4dG9tbnF1d3V0cnFycnR2dnp8eXVzc3Jyc3Dc2dfY2tnX191v3dnYbW5vcXJyc3J1eXl2cm5ub3FzdnV0dXd3dl1zcnV4fX96cm9vdHh3dnd3dXZ5fXx9hYuFfHRubW9wb21ubWxtbm1sbW9vbWxtbW5vcHBxcXBxcW9ubm5tbW9xcW1ucG9ubm1paGlpbnV5e3l4eXp9gICAhImMjImApaCalZCPjouIhYOC/4CBg4eJjJCSlJWVlZOUlZaXmJeTkpSWlY6GhP31+v/8/IGEi5KSkIqC/v6AgoWEg4SGhoWGh4eFhIiKiouKjZWVlZqZlI+PkY+LioqKjImDgoGAgIGA///+/Pv39vX5gIOB+/b0+oCA//6Bg4OBgP+Ag4aAh4WC/fj09vr9+fLv9YCEh4WDhoiIhYWHiZCUkY6NjYqJhoL7+vn2+IOJjYmEhoqJh4SC/4CA/4CCg4WIiYiIiYqNj4+PkZCPj46MjIyPkZCQj4+Ni4iHh4eFg4H+/4CDh4iKioP68+/t7vH0+fz8/Pr48/L09vf8gP35+ffy8/eA+fn7/Pv48/Dv8PT39vb4+4CA/v757uno6Onp5+Ld2trb3uDk6u7x8/Hw8/Pu5uDd3eHl6OXd3+n0+vv38erm4+Hh4uXo5eDh5+/x6+Ld4OTk5+7z8Ovl5efm5+fm6/b59vf59Ozt9fqAgf339/jz7/Dz+Pv7+Pj49vX2+oGB//px9vPu6ufj4+Xn5t7W0dDU2uLv+v79+/+GjY+MiouQk5KOh4WIiIeFiJOalIeA9/f6/P+Ei5KWmaS13IaPmqCen5SIhPHSxbmypI+Hgf/+g4WEhYaFiImMj42PkpGIgf6AgoSD//br5ufp7/Dt7Pf/h4iEh4CGhoODhIP86ufv/oOHhIGAgoSEh4aGiI2TlZeYnJ2amJWTko+JhYWGhIODgoKBgoGA/fz8/P+AgIOHiIeF//2GjI6OipCYnavD6pa50N7auY7buKedlJCNiIiJiIqLiIaIh4aEg4H++/v8/vry7+7w9vj9hIaC/Pf2+Pf4+f2BgYCA+u3t8/f69/n37ubi4Ofw9vPv7/P17ebm6+vp5+fr7evr7e/u7vDw6uXh4eLj5OXm7fP29vHs7vLs5N7f4N/c19ri5OTi39zb3d/Zzc3W4O32/oD69fyBgoH69O/w8evh2tvh6/Lw7e3w8vPy8/Tz7+fm6u71/P+DhYWEgoKDg4CEgfv5+4KDgPj3+Pv/goOFh4mKiYmHiIiGhIL++vTx7+3t8ffz7ezw+v+BgPz89vP5+/z27uzt8fLy9/fw7Ovs8O3t6+fs7u/19vuA/YCA9O3v8Pf/goKAgYKBgP////37+/2Cgv75/IGFh4mMjYeCgIGFi42LioiGh4eJiIeIiYCGgoOEg4SEgf36+vv9/Pj5/oD//f+BgoOEhISFhYmQkZCLhoOEhYiKiIWHiYqJh4eLj5SWkIiEhImOjYyNjYuMkJSUlJ6mn5WMhoWHh4WDhYSDhYeHhoeKiYeFhoWGiYqLjI2Njo2LioqKiYiLjYyIiYuJh4iGgoGBgoiQlpmXlQuWl5ibm5uhp6qrqIx9AXybfYZ8iH2CfKt9iXyDfYR8BH19fHyFfQF8hn2KfJZ9hXyLfQR8fX18pX2CfId9k3wBfZd8gn3WfIJ9knyCfZl8ln2FfIh9iX6JfYJ8kH0BfIR9jHyMfYV8pn2FfId9gnyLfYd+lX2NfIN9iHyDfdJ8B318fHx9fX2efIp9Bnx8fH19fYV8jn2PfIJ9n3wEfXx9fYZ8h32HfAV9fXx8fKB9iXwEfXx8fPZ9AgIEAIBxbWnPZmhqa2fIyMnOatbYbGttcXFwcG9vbG5xc3d2c3Bub3Bvbmxra2ppbG5uaWdlZmlpamppamtqamptcHFvbWtraGZlZmhpampraWdlyMrLy8rKzGhnZsjDw8LFyGZpamtsbGpoaGpqa2pqacvHyGhsadJtbNLOzczNysjNa4Da1tba2WpsatDNy8bQbNfKydLUbHR5eXh0bWrPy217em5rzs3Qa2xqx8PSbnFxcG9ubWxqaWpsb3BvbWxtbWxsbm9xbmhpZ211e3dta2tvcG9tbGxsbW5vbWppaGdqbm9xb29ucG9raMzM0thycm9ubGfLx8bKzGlra2rQzcbHyoBmZ83OzMzIxMfMa21vcG1t0cbIzGhqatLVbnR4fn99endx1MW9s62xvsrU19vd4HLb1tTS3nFwcG9ydXNx39fdcXJx2dXW1NPU0tHS0tLP0Nnd3OJ15+Dd3N7leHz27Op8h42Hfe7r6t3Y5O/k2dzc29/g5Obk3uPp5ubrdunodIDZxs5ydXl35Nza4Ofo6Ozu6eTm6+zs7Onm5+7v6OHe29rb1tLR0Nbf5OXh4XJycNXPx8bJzM/Va87DvL/GzmttbNLO0m91fXyAgoGDfHLccHVx1M7Nzs7Nz9Jras7Kz2xxcnFvbmtsbt/b1tLac3h3c2/TzM3TbGzSzMjN09lra4BpZ8jGY2RlZ2dpampsbGvQ0tRq0crFwsLDw8HCwcC+vL7Cw8XIyMjGymhramxucXJwc3R5eXhz3N5xcd7fcnN0dXl8eXZ0eHl35dnQz9njdHd+g4CBgoKAeeno7fKCl5uP//Tu8Pr7f4B+f/fq3uLp5+Lf3d3m6ezu6uDf29jd5YDj2uDo7/Dx9/Tt5t7Z0tbd4tzh49jNysvU19TPyc7Wa9DIymhozdNsbGzRys9rbW1raWfOaGloaNDP0MvEw8nNysnGzGpoZ2hramfHv8DFzmpqac7Gx7y4ub7Jx9DTzMzLaGjOy83R1dTR0tHRaWpoxsDFyM3P23J5fHlvZsdpcIBxcm5tbnBxc3R2d3p8fXzu5NzQx8DBxcvMz8/Q0dfUyMXGxcC8x9ZwcdzWz8vP2HBzc97a2dPXb3JsycfJ0tDKztTT1tdwcnJ7g4J/fnx0cnJydnZ5enh+d9bIxc7Pys7MxsppbXBwcHFyc3N1eHh6e3l2dnyFiYuTl5WXlI2Mi4CJgnZ0dHN0dHJvbnFzdHZ3ent7eHd2eX6BhISDgn12c3JzdHh5d3V1d3l5dnR0dnd2dnZ3eHdyceLldObj4Nvb3uHj5uXf4nRzc3Jwbmxq1ddub9rX2W9xb2xqamnJxstoa2xpycnOcXt2c3Fua2xsamtvdHd4dnNxbmtqa29ycxRzb23Ya21wcW7W1dXZb9/gb2xvdIV1gHN1eHt/f3x4dXZ3dXRzcnNycXN2dnJua2xvbnBvbnBzc3V2eXx9eXZycW9tbG1vb29ubm1satLS0tHQ0dRsbW3X0tHQ0tNqbG1vcXNzcnJxbm9tbGvT0dRucG3Xbm3Szs3NzszKzmzb1tXY12pratDOzcrWb9fIytbbcHh+f357gHZz4dx1gYBzcdbV2nF1ddrS3XR1dnV0cnFvbGtrbW9xcW9tbm5tb3FzdXFrbGpvdnt2bWpqbm5tbGxrbG5ub3Jzcm9sa2xsbm1ub3JxbWrOzNLZdXd1d3Vx29PQ09Vtb29u2NfS1NhtbtnY1NbS0dfccnN1dXN04tnd5HR2debogHh+goeIhYJ/e+vf2c/Gytbe5ufq6+x46OPj4vB7eXl7foF9ee3l6nd4d+Xi5ePj5eHe39za1dXf5ePnd+ff3d/g43Z47ubneoaLhnzu7u7i3+z259ze3t3g4uTo5uLo8/Lz+n/7+Hzl0tp4eXx659vY297c2t7h3tve4+Tk4+DegODn6uXg393e4dzX1dPY4ebo5ud2dXXo6uTh4N3g5nTf1M3O09pwcXHd2uF2e4J+fn+BhoJ673p+eunn6evq5OTkdHLa1tpwdHV2dnVycnLm39nV23FzdHFw29fa4HNz3tXQ09jbbm5tbNfXbW9wb25tbWxsa2nNz9Rr19PS0tPRgNDQz9LT0c/R1dbY29jW0tdvb25ucHR2dXh5fXx7duTndnbn5nV2dXR4eXRxcHV4d+ba0tDX33J0en98fX5+fXbi4eTneoqMgerh3d/n53V2dnjt5N3j6Obf3Nvc4+Lh4+Hd3tvY4ezu5enu8PDx+Pf28+nj29zh49zf4NfPztDaad7b1tLa5nXn3+BycNrecXJy4Nzkd3p6eXh37Hd4dnbp5ejp6Onp5d7c3eh5eHh6fnx46eHi5+97enbn3uDV0dTa49/n7Obo6Xd36eHd3uDc1tTU2G9ycdjP0M/PzNRtdHyCe3Pidnx9f4R8gHt6eHd1dnd2c+De4+fn5ePg3tfTz87R1tXN0NbX0s3T3XFv2tfSztPccnV039rY0tlyeHHW1dng2s/Q09PX2HBycXiAf35+fnd1dXR3dXZ3dn143tPR2dnR1NTS2HJ2eHZ0c3R2dnl9fn5+e3d3fIOFho6TkpSSi4qJh4B1cnJxenJycW1sb3Fyc3V3enp5d3R1d3p8fHt6d3Nwbm9wc3Nwb29xc3NxcHFzdHNycnJzcm5r1NJp0dPW09PV2Nrd3NfbcHBwb25tbGzY12xs1dTXbnBubGtsasrGy2lsbmzPztJye3Vyb21rbGxra3B1eXp3dHJwbm5wcnV2gIqFgf+AgoWGg/77+fyA//6AgYWLjY6NjIyIio2QlZSQjIiJi4qKiomKiIeIi4uGg4GChoaJiYeJi4uMjpGWl5OPjIuHhYODhoiKioqHhID9/fz7+vv/g4OB/vf08vT4gIWHh4iJiIWFhYOFhYSC/fn6gYSA+4GA+PLw8PLy8veAgP/5+v//gIGA+vbz7PiB/Ozt+v+CjJKTkY2Hg//5hJOShIH5+fyChYP48f+FhoaHh4aIiIWDg4aKi4mGhYWGhIaIiYyKgoOAho+WkYiFhYqNjIuLioiHiImKioiFgoKFh4qKi4uOi4WA9vP4/4iJh4eFgf318/f6gIKBgPv69vn+gICB/vz29e7p7fOAg4WGg4P/9Pf9gIGA+vuBiI2UlpWRjIX76NzRys/f7Pf5/f7/gfv29fL+gYCAgoWIhYH+9/6Cg4L7+Pr29PXu6+7x9vPz+/34/IH89PLy8veAgf708oCMk46D+Pf16OTy/e/l5+fk5+jr7ezm7Pb19/6B//6BgPTg6oGFiIT67uzw9vb2/P/69fb6+/z8+vbz+fz17+zo6Onm4+Pk7Pb8/Pr8gYGA+vjw7e/x9fyA9+zl6PH8hISD/fn/hoyUkJCRkpWNg/+CiIP48fDy8vH3/IOC/fn9g4eHhYSDgICA//Ts6fOAhomHhP/4+P6BgPXr5uvw/oKEgIOC//2AgoOEhYeIhoWCgfv8/4D+9vLv7Ozt6+no6ejl6O3x9//9+vb7gYKAgIGEhoKDhImJh4L7/4KB+/qAgoODiIuHg4GFh4T+8enl7vqAgoqRjIyMi4mB+PT3+IGQkIXz6+nt+PyBgYCC/vTs8fj28Ozr6/Pz8vTw6Obi4ev2gPjx9Pn7+Pf//vz8+PHp6/H18PX47uXj5fD28+7o7/qA/vr8gYD4/IGBgPr0/YSGhoWDgP6Ag4GB/vr9/fv8/fry8fL9hIOCg4eFgfnx8fX9g4OB/fPy49ze4uzo8/r3+/+Dgv728/b8+/j4+fuAg4L47/Lz9PL9g4yVl4uB+oSMgI+QjIyMjYyNjYuIh4aFgfn19vXz8fb7/v38+fb2+fXq6vDy7uju+4GA+/jy7vP8gYSB9O7w7/mDiYL18/b//PP1+Pb5+ICBgImSkpKTkouIh4eLio2Oi5KL//Lv+fry9fTx+oWKjIqIh4iJiIqQkZKSj4yNlJ2fn6WopaeknpycgJuUh4WFhYaHhIGAg4SFh4mNkJCNioeJjZKVlpWUkYuHhoaHi4yKiIiKjIyJiImLioiGhYiKiYSB//6A//7++fX19/z//vf7gIGDg4OCgYD//4GA+vb5gYSDgYCBgPfy+ICDhYL39PeGko2Kh4SCg4SCgoeMkJGPi4qGhISFiYyNBH19fXyFfYR8A318fL99h3yDfYZ8j30JfHx8fX19fH19iHwBfYV8g32FfAF9hXyIfYJ8hX0JfHx8fX19fHx8vX2EfIZ9hXyEfYV8gn2IfIZ9hHwFfX19fHyJfY18AX2FfIh9Bnx8fH19fZF8AX2GfAV9fXx8fIV9l3wHfXx8fXx8fIR9pXyDfYh8AX2GfAZ9fX18fHyKfQR8fX19iHwFfX18fHyJfYV8hX2EfIJ9hnyEfYJ8i30EfHx8fZZ8jn0GfHx9fXx8jH2GfIp9hHyEfYZ8hH20fA59fHx8fX18fH19fXx8fIZ9AXyEfYx8h32FfIN9jnyCfYp8g32HfIZ9AXyRfZh8gn2GfIN9hXyDfYt8lH2KfNV9A3x8fYx8iH0HfHx9fXx8fId9g3yEfYN8mX0CAgQAMMrMysnLzdFpaGlpaMzQbG9wcnR3eHp+eXRwbM/Ly81oaM3LZmVmZ2tsbW9xcnBwbYZshGqAbW5vbGlnZspnaWttb3F1d3d2cmvQz21zdnVya2llZmprbmtpaGtrbG5sa23UycTGztHR1m1t0M5sa9Bpam1tamjMxsG9u7zBy8zHztdwd3Xa1dPHxdZwdHR0d3t6dnZ5e3l4enx8e3d3dnh5eHV0dnFvb3J2dXd3dXp9goiGgYCAf4CCgoOBf3p3d3l8fX9+fHt6ent9fX16eHh6foOFhIB/fXVx4NvedHZ3dnZ2dHV1dXPi4ed37Ofs63Z4dnRx4uJxc3Xn49/f5HV1d3Z2d+nq7nx8fXh5e3nr6Ox4eHp4en567Ovq6Hd1dnbn5XZ7fHt5dnV0cd7a2txtcHBsbW2AcG9yd3h0c3FxcW9xcNvU0dba2NTS09HUxsXI0dDP1951dG/a2d3d4nNzcnN3fHvk4OXi4XR15OLk39zf5OXn6enveu53enjr5ubf5PDp5unv8+7u7eHV3eLY3nN043R+f3x32tbd3+Pf5eXb2NzY2NrX3uHc39zc5d/X1dHGxNqAc27Q0dTQ0tXXcHJzcW3U0tLYb9rTxMHJy83Rz8nP1dbRyM13hoN/eHp8d+Lc19PW4eDZ4Hd9e3dz4ODi5erq5+NwbWxwcW9sam1s0dVvcnR0b9XR0tLWcXVxz8bMymZnycfJaWrU0tbX19zi3+F4dNbMxWp1dHHg33DbcXHkc3OAdnp4e4J8cWtvdXNydnJvcnZ3d3Z3c3Z3eejghK+9moTt4t3d3t/n5uvu5eDW0M3T1+Lj3+Lo487JzNnY0M7a3NPc39/n6/mBfvDt7/P56uDo8OfX0crGyc/P2+F1deVz4tjX1NLT2tvSz9XZb3Nx2dtzcXFyc3d53MnMxcTGydEqbNjR02xtb25rbGtty8lwd3Rwa8e+u7zBw8fFwMbMysXDxMjKxsvVb9/fhHKAcGxvb290dXZ2ctvV0cvMzMe8x8pmZWdmw8JkZmVm0M9tdXHZ29zq7unj2czGytlxcuLl5uDb2dHP3Xfz9HyDhYF8foF8e3x0atFqysXO3XV5e+nf3d/Xz8m+vcHBxMrMzMvSa9PV19TMxcrRzG5v0W9x23Fxb3FzdXNzdHNyc3WAdnNzdHNw021ycNXNv7/PbnV8hZKelYp519ZucnDc3HB0dXp3d3p6e3nn53d35eDf4+Tl5ePj5uXl5eDk6OPe2tDL1uHe3Ojq5uTndnZ04drY19vf4XFx4XV7fHl3cnNv1tx4f393c3N2eXd0cnBxb23a23FxcXBrbG9ycHJ3dG4Y08XDydBtcnZ1dnZ3b2nOz9LPy8jGzs/NM9PX1tTU1ttvbnBwcNzgc3Z1dnh7fX+Ef3t2ctzZ295xceDgcnN0dHd4eHl4enh5d3Z1c4ZxgHBxcXFwbm1s121vcXN2d3t8enp2cNbUcHN1dXJtbWtscHJ1c3BvcXFxc3BwcNfLxcXO0tLVbWzNy2tq0WtucHBtbNTOyMPCwsXNzMbKz2xycc3Gwrq90G1wcXBydnVycnZ2dHNzdHJubW9wc3Rzbm1uamhoa29ucHFtcHF1dnNwKW9ubnBwcG9taWdoam5ucHBubm1tbm9ubWxsbW5xdHV1cnJxa2nPys5rhW6AbW1ubm3W1tds0MnN0Wtvb3Bu2tlrbG3X0s/P1G1ub21tb9ra2m5tb25wc3DY0tNra21rbnFu1tbY129ubm7Y125yc3NycG9ubNbU1NZqbW5qbG1xcHJ1dnNxcHBwb3Jw2dLR2N7e3Nvc29/PzNDY2Njg5nZ1ceDh5ePldHRzdHaAenfd2uLg4HNy3dvc19TX3N3c3NjccN9ydnLb1dbQ1eHZ19XW3OTo59fM1t7V33V36HV9e3h019LZ2tzX2dXLy8/LztXU2t3Y2dbW4NzW2dvU1Ox9eeXl6OLh4eB0dHRybtTQztRu2dLCv8rS2N3c2d/k5N3S1HeBenVydnl14t+A2dHT29jR1W5wc3Nv2dfV1t7j3tdqamxub21ra25s0tRsbW1rZcXGzNDWcnd01tDZ1Gxs0s7RbGvS0dLQ0tzi3d54ddvTzW55eXTn4nDZb27ccHF0eXh5f3x0bnJ5d3V4dXFzc3FycXNxbmtr19V7m6OHddfQztDS09vY293V0MiAxcXLztXV0NPZ1MC8wM7Nwb7IyL/HyMfN0+F3deDc3d/i1MnQ2tbIxcG8v8TF0Nlycd9x4NjX1NLT2tvT09vdcHJv1tpzcnJyc3V218XKx8nNzdBq0cvSbW1xcW9wb3LV0nN7e3hz1s7M0trb4d7X3ePg29rj7e3m5+98+fh8e3mAeHd0dnR0d3Z2dnTj4dzT0dDLws7RaWlsbdLUbnFvb97ZcHl45ubo8vDn5OXj2tfbbGrS2eHm5ezp4OJy3d5zfH98en6EgYGCeW/acNnS1t9zc3LY0NDU0MvJw8TKzNHT0M3N1m3T1tzZz8bKzshsbtFvcNhubmttb3Bubm5tbW+Ac3Ryc3R0cNZxeHXf1sbF1XF4gImXpJySgeTicnRw2ttwc3R2c3N3dXVz3eF1deDa2dzc2tTLzNDU2djU1NbSzMe/vMnU0M7Y2NLQ02xtbNPPzs/U1tZsbNdwdnZ0cm5wbc3OcHh5c3BvcnV2c3Fubmxr1dRtbm9wbW9ydHBzeXcZcdbIxMjNa3F1dXd4eHJs0tXZ2NXT0djY1YD3+/r4+fr+gICDhIP9/YOHhoeJjpCSl5KOiYT++vv/gYH//YCBgoSIiYiIh4iGh4aFhoSDhISDg4KDhIWGhIKBgP6AgoSGiYuPkY+OioP8+4SJi4uIgoKAgYWGiISCgYOEhYeEgYL78Ors9Pf4/YGB+PeBgPyAg4aHgoH99fHt7Wfu8/z79Pj9g4qJ+vLt4uL3gYWFhYiNjIiIi46LioqLiYaEhYWJi4qGhoiDgYKFioqMjYiMjpKUkY6Mi4uPj4+Ni4aDg4WJiYyLioiJiouLioqIh4iKjZKTko6OjYWC/vb4g4WHhoaFhIQ2gv78/YD58vj9gYSEg4D//4CBgf759vf9g4OEgoGC/fr6gIKFhIWIhP34/YCBg4KFiIT//v/+hIKA/v6CiIqKiYWDgoH++vr+gISEgIKBhIGEiImGhIODgoCDgfz08vj8+fb2+fn76OTm7+/u9vyEhYP9+vv3/ICAgIGFiYb58/v6+4KC/Pn68+/y+Pr8/Pf7gP2AhID17+7m7Pnx7uvs8vn//u/k7fTr9YGD/4GKiYWB8Ovz9Pfy9PCA5eXp4+Tp6O7y7vDs7Pfy7fDv5OL7hYH3+Pz19vf3gICBgYD9+/j8gv7y3dbf4+jt6ePm6enl4O2Km5SLg4SFgv368+nl6ebi7ICLkIqC9u/t7vX29/qAg4WHh4WCg4aD/P2ChYmKgvfz9vj9houF9Oz3+YCC/vj6gYD69/f08/qA//r6hYHw6uuBjYiA+P2C/oKA/4KEiY6Oj5eUioSJko+Oko2Hh4eGiYmMiYeCgf/5kLvKpY3/9fDx8fL59fb58evg3Nre4Onq5unv6dLN0+Lk2dXf39Tb3t3m7PyEgvjz9Pj98Obw+/Pk4NzX3eXl8/iBgP6A/vT08u/u9PTr6/VI+YCCgPX3gYCBg4WJi/rl6ujs9Pf9gf73+oCChoWCg4GC9PGFjoyJg/bu6+708vPu5uzy7unq9Pz99vf8gP78gYKBgoOCh4WDhIeAg//8+/X29/Lo9vuAgYKB9/iBhIGB//eBi4f78/D7/PXz9PLu8f6CgPr8+Orr+/74/YH8/IKLj42JjJCOj5KKgP2C+e7x/oSDgvnx8Pfx6uXa2eDk6vDw7vH9gf79/Pfu5uz084SF+oOE/4ODgIKFhoSDgoCAg4eIhoeIh4L3gYmAh/7z4N/xgIiQl6OqnpWI+f2BhYL8+oCEhIaCgoeGiIb//4KB+ff2+fn38uzs8PL08+3w9fHs593Z5vLu7Pj59PX6gYKA+fPx8vj8/YCA/YKJiomJhYeC+fyIkJKKh4aIioiGh4WFgoH+/YGDhYWBg4aLiIuQi4T+7uvw9YCHi4oPjI2OhoD7/Pz69/Ty+vr4h3yFfYJ8jX2EfAR9fXx8nn0BfIx9gnyWfYh8B319fHx9fXyGfYx8g32GfM59g3yLfQR8fHx9hHyFfQV8fH19fYV8hn2DfId9g3yHfYR8hH2CfIl9hHyTfZN8g32FfId9hXyCfYx8BX18fX19lHwDfX18hX2dfIJ9h3yFfYR8AX2QfIh9iXyFfYh8in2CfIV9hXyDfYR8B319fHx8fX2JfAV9fXx8fIR9B3x8fXx9fXybfYJ8hX2nfIJ9k3wEfX18fYx8BX19fXx8h32IfAR9fHx8iH2CfIV9lHwDfXx8jn2KfIR9gnyEfQV8fH19fYx8gn2JfAN9fHyMfQJ8fYR8g32RfAF9iXwGfX18fX18k30EfH19fYV8iX0HfHx9fX18fIp9BHx8fX2efIN9h3wDfX18iH2CfI99gnyNfYV8iX2KfAICBAAnyMxrbGtnw8LEwsDEzGlsa2prbW1ra2tqzMXBxGRoaGhmaW5xcnBqhGaAZ8G0tLbEZ2fIyWhtcG1qzs9pbnBubWxvcm7R0dBrbW1tbnBvbmxsb29szsxsb29wbnBxb2vQz2tq0cvEwMhpam1saGdnZ2ppaWptbmlrbdjQydFtbGxucWzTbm5xcXN8g3x2dXh5eXl3dnZwe32DtprB2MuW6XOGxYd8d3Z4eHaAdXp4dXNu0MfHz2xtbnFxb3BwcG5vb9rT19pvb29sbnBwcW5sbNbT129ta9LQz8/LycrDwmJkZWhoZ8W2ucpyeoiNh3V0c3Nxb2ttbnV1b2xqaWxxdnl4dHN1dnZtzs/OzcjHzNXZ19PSz8/R0M9rb29rbW/d2tXU0thwc3JucHKAcm7Szc3N09HOa9faceR3f3nic3V04eHf2djZ1Nfh6ebg3eTt8/p8599ydHJzc3BwcXR3d3JycnVzcd5y2Nbg33Li5Hl5397b2m7cc3N0dnZ3eHdycXd9fnt7hIiAd3LhdHR1eXVzc+Di5HR16OPk5+jt7urf1tjngYl53NnX3N2A4dnNwMXW29vY3ujwen3q39Tf5N/kc9FrhYF4cHDecXJw1M3V225sbNjOy87MzdPT2eTpd3h2dX2Gi4N4c3h25OzygIWEf3fQycnIytLV3XR5eHp4ctfX2OFy5eV3eXXe2+DhcXBv09JwdHDW0Nji4+Xn5ePl4Nva3d91fnx2b3GA0Nbb29t1b8xpaGdnZ2lsbGfLxsJseHdyc21tbGtvcXFwasvNb21ubNTYcnV6enV0dnLh2dTT5Hbl3uDb2+Pkc3V5eXh4enx57ezr597i7Pjw7vbx7/f07+rt/X9/8X2GfOno6ufl5OLf2tvi7PTw8fbs5+fr4+bs7efacHp4dOOAcnR1cnFu2+bte37b0tveb2/c1M/Oz9TUbnR8fnt0bnF2cWrCyNVwcG9wb3J2cm1pZWNsgZSfmpeMdG7Zz8/Nz8LCys7W3OJy2Nve2tJpamtvb2zQaXFzcG9wdHp7c27azcLW4d3c29XYcnp+f4R618bGx8DL09DR7oiNgXh0cXGAbmtocHd3e39/f3546OHY1M69vL3AxdVt2N/d4N/b1NXT0dPacHRw2nNy2szEwL3D0XBzb25ydW/R1XV1dHJ2eHZ3dHNzc3V3eHh15nd5eoCBfO/u7PH4+/Do4t/a3efu9vT07uzseXx8fXx+e/Pw7OLe4+fn2tTc3dvU2OZ5fIBVeufc4HV5fYGCgX901tHM0uPrfH/r5eTf2Nzse3x659necnR2eN1w4eXY19RtbWttcm3UzszQ0WdnaGppamjDvbu6ury+v8PJZcfHZWfFurjBY2Vkx23f33JycnHX1djV0tXec3d1dHR3d3d2dXPi3Nrfcnd2dXN2e32Af3l0cnFwctrQz87acHDc4HN5e3dz3t9xdnh2dnV4enbh4NxwcnJydXh4eHV1enlz2dZydnd4dnZ2dXLg33Jx3drX1990cnV0hHKAdnRycnZ3cW5t1tHJ0m1tbW9xa81panBxcnl+eHRzd3h5enl3d3OBhpbkwd/kwYTSgar4k3pxb3FycHF1c3JwbdLO0NlwcXFyc3J1dnh1dHPd0tjfc3R1c3R1dXZxbm/b2t5ycG7X1tbV09bc2tpuc3N1dHPayszefpCmraCFf32Ae3p5d3h6gYF7eXd3e4GGh4R/foGCg3ni4uHh29nb3d7d3uDc2dva2nF1dnJ1duPYzs7U3XJ0c3BycnNw19TV1djT0WzZ23DccXhy125wcNjX1tDQ0czL09fTzs3U2d7kcdLMam1sbGxqamttcHFubnBzcWzVbs/L09Nr09JwcdOA1tjab9xwbW5xcXJzc3Fxc3Z3dXV9gXpzb9xwb3BzcG5u1tfYbW7c1tPS0NLT0szIytV2gHTV0tDT0tfRx7u/ztPT0Nbc5nd66OHV2t/e6nvrepSQh3x45XJxcdrU2+BwbWzVysrPzc/d5Onr7HZ2dHFzc3l7eXZ4dOPs8Xx+fXtCdtDCvr/Cys7VbXJydnVv0tPW32/UzWptbM7Lz9Jtb3Lc23N0cNzU193c3uLk5Obi3t/h33R6enRucdTa39naenjfhXOAcnRybNPLxWt2dnN3cnFvbXF0c3Ju1dhybm9v2ddxcnh4c3Nyb93W09Dab9fU2NPP1NVsbW9wcG9wcXDb2tvYz8/V3NTU3tvX4N/e2tvecXPddHxz2+Hn5N/c2tbOzM/V2dfW2dLN0NbR09PO0c1qc3Ju2m5vcG9xc9rY3XR30stJ1NhubdnSy8jJz89rcXh8enFoanBvasTL1G9vb3JxdXp1cXFwc32RrMHCvaeHfPLq5+Xn3N/m5ePj5HLb29zb2W5wcXRzb9Nqb4RxgHN3eHRw39bK1dbP09zg4HF0c3J3c9THyMvBx8e+vdp/hnx1c3R2dXRyd3h2dnh3d3Rw3+Dc29XCv769wdBpyM3R1tbQyczJyczSa21oymtrzsO/wcLH0Gxqa3B1dnDT1XZ1cm9zdnZ4dnV1c3JzdHRx3nV3d3x8dN3b2t7k5tzUe9DRztDW297a3Nvd3XFwcHFxcm/b2tfOytDV1cfCyMvMxsnTbnF1btHM0G1wcnV3d3duzMfCxtXacXTd2tzXztDbb25v2tHXb3F0eN9w3NTV2NlxcXBxdXDc2dbW2W5ucHBvcW7Szs7NzM3Pz9HWbdXWbW/Xz9HccXNx4RL8/oOEg4H29Pj18vT8goaEg4SEh2iGhP/28/iAhYWFg4iMjZCOiYSDhYeG+ezq7P2Dgvv9gomNi4X//4GFiIaFg4aIhPv7+oCCgoOGiYiHhISKiYL08oCDgoKDhouKg/36gID99/Lx/oSEh4aEgoGBhIKBgYWHgoKC/vbw+4SDgIaB+YCCh4mNlZuRi4qPkJGRj42NiJWXn9221ti1fMp+o+6YiIKChYSCgoiHh4aC9e3u+IKDg4aIhoaFhYKDg/3y9/yAgIGAgoWHiISBgPv5/oOCgf/8+vjz8vf2+oCEhIaFgvfk5/uOobrBs5WOi4mHhIKFiZKRi4eEgoeNk5aUgI+Mjo2Mg/n9/P718/f9/vz8+/Xy9vb3gISFgIKE/vXs7/X/goWFgoWGh4P8+Pf2+/n4gP3+gf+EjIX6gISE//368/Hx6uv0/Pr59fn68vmB/PyEhoSDhIGBgoWIiISFiIyJgvyC9/P9/ID8/YaH9/v8/oD9gYGCh4eIiYiFhYmNgI6MipKWjYWA/oOAgYWCgID39/qAgf308PHx8/Lu6OLk8YaQge/p5Obm6eTaztbo7u/q7vT/g4X79+/1+/f/g/eAnpuSh4P/gYKC+fL6/oKCgPXk3+Dc3Obp7fP7goiKh4iGiIeDgoaD8/Hzgo2TkYrv3NTP0N7p+YeNi42JgvTygPL+gf//hImF+PD4+oCBgPn+h4iD/fPy9fT2+/v39e/t8vj3gImJhoKD8ff89faIhvqCgoKEhYmQjIP/9/OEkpCKjIWEg4GGiomHgPHugICCgv79hYeNjYeFhYH+9/Lu+4D59Pn29Pv9gYGCgoGBg4OB+vj39O3u8vju7fj18/z8gPn19/6Cg/yDjIH09/z49PLv7Ojp7/b9+/v/9vDy+fX6//39+ICLiIL+gISGhIaF/fv/hor17/v/gID/+vTw8vv4gIqSl5WLgIKJh4Ds8PmCg4OGhYmNioeHgYCHnLrR0Mmzj4H88/Xv8OPq+fr6+/+B+vz++/mBhYaIh4P/goqNb4yLioqNjIWA+efW6PHo5Orv+4SLjIqMhO7e3t3R19zZ2vaNlIiCgoOGhoWCh4iFh4qIhYOA//308Ozb2t3g6PqA9/z8/v369vz79vb8gYWB/oaF/e3n6Ojw/YKBgISKjYX394WFhIKHiYmMiYaFhISDgID6goOEh4aA9vXy9Pj89vDs6+bn7PH28vPy9fmAgYGCgoOA+/r68fD3+/rs5u7y8urt+4OGioH28PaBhIiLjI2Mge3m4eb4/4WI/vj7+fHy/oKBgv3y+4KGiIr/gPz28/f3gYKBg4eC/ff0+P+Dg4SCgYOA8/Dy8vH08/P2/ID9C/+Bgfnu7fmAgYD/gnyEfYd8i32EfJB9hXwEfX18fIV9gnyJfYN8jX2CfIl9BHx8fX2FfJF9hHyGfQF8ln2FfgN9fn6OfYR8jH2EfIt9Bnx8fH19fYl8hn2EfJ99kXyGfYZ8iH2HfAx9fHx9fH19fXx9fX2RfAN9fHyRfQJ8fYR8BX18fH19hHwCfXyUfQF8h30FfHx8fX2MfIN9kXyCfYd8An18hn0EfH19fYR8g32LfIx9g3yFfYh8hn2EfAZ9fHx9fX2EfAh9fX18fH19fY98hn2FfAN9fXyJfYN8jn2CfIR9gnyIfYV8AX2HfIl9k3wGfX18fX19mnyEfQF8hn0FfHx8fX2EfIJ9h3yLfYN8lX2MfAF9hXyGfQF8i32KfIZ9inyTfYt8AX2MfAZ9fX18fX2HfId9gnyRfQF8hn2UfId9kHyEfYN8iH2GfIJ9h3wGfX19fHx8hH0CfH2FfIZ9hXyHfYp8BX18fH19hHwEfX19fAICBACAvcDHzcfBZ21tz8pnaWbEwcPIzM3Kx8XEZGlsbGW5tLrAumd7i5CFdG1taWZkvLa7vb23t7vAZMbBZmhmY2NlZmVkxsLFaXBta9Vtbm5r02ts1m9ydHJvcnJxcnBxcXR2cXVxbWxyd3fn5Hl4dHd8enh7fHd9fnl2c+N94tDU3NaA1m7YydHQa9G+x81pzmt0e3Nycm50gXx4dndzb3B1b25of4+DbHJuc3JyeHt5e3h2d3p6eHZy1M/U1o7Ge3l3sX7menl5e32EhoF21dBzeYCChIaBe3h5eoiUn4xx0ddxcXB0dXBsamlq0MZq0MXCxsbDwsTIz9FqaWpraM3IwrqAwGVqaGhpbG5wc3RxbW9va2llxsK7tri6xGhpaWhtcHBsaWZlZmtvbmxpaGdmbWtoyc1qyc5pamrPbG1samnNysG9v7vDxsPLz9hucnV4c3Byc3F0dXRzcM/ZdnRy0s7S2eTe2tPQxshsc3h2c3N6fHh4d3d0c3N3c3JzcnNzdniAeHdzdnx+hX53dXJ4f317gn2Hp7msoZZ31c7X0WfKbHqFgYGAiIuDffbr3N3h2ep7593b2NrR3OV2eXTi4nNy29lv3nFzc3Jwcmxvc3Byct3Qyc5scHZ4eujIx8rL2+Dp2Nbhd/f+7Oh1deXj6+Ti43N03+P2+vh7dnZ4dud2eN2A2Ox6en9+fH166uTdcXd1dG7V3Ofg6nrv8uzr6eTr59bX0dpzcW9tcHFw2svKZWVkbWxwcNxtbnFzc252dXNxcHN1cXJ4ent3d3h3dHLg29fi54KQjoqQhYF9dnl5enh6d3R3debo7Hnt6OR0eHrh1tTZ4Op4d3rj5ujr6+np63WA43d6d3p7fXx8gXp8fvDu7uHr9nx7eXp6d3Z2dOLicnTp4N1yc3Z3eXl5en19gIN6cnp6f4R422/b1dJscXZybnVtx9l2bGdpbGxubG9yc3N1c3RycdPI13LV1G16eG9ubM9p0MbHx9HSytPX0NPf2NHXb+Hc1djT1NPIzHN6cG1HzcRpbGnRd4yDdnN0dHTo5uLZ2uHi39/i5NzT3vHu9f/69OzgcdjJzdPZ2+N6gX999PPr5NfLzNbkgHl0ddva1tjidnZzcnOEdIB1dXRz2NDM1dZtbNLOyNDT1tjbcHRxbdDa5XTk1s7Y3XNzc+Pc3+Li2NTp4dLQ4nvw7PDw5e3xfXrPfo2Heu6B5efqgISBffPx7uTc2d3k6uno8PT48/Hw593Y29na2+Lf4eLm5uXq7urt6dnh3eDa2OLh4uTk5eDg53p6eHh3eDV8g5VWW1aQi313eXV0dXXo3t1ydXNw4XNx225xcG/b0crIyMa5r6mor7a0s73UcdPFuLe7uoDY1tne29hzenjh3XFzc+He3t/f3dbW2t5zd3d2ctXS2NzUdImZn5OCe3x6dnXc09jd3NLQ1t505990d3V0c3R0dHPm5OJ2fXhz5nh6e3fpdnbod3t9enV7enh4dXh3enpyeHZycHNzcd3dd3ZxdHp3dHZ3cnl7eHV05X3j0Nbg34DidebX4eJ05NLZ3nHec3uBfH+BenyHfnZubm9wc3dxcm6Nm4lvdG5xcXN6fXx9enZ3enl3dXDRztbakMmBh4vMie58e32CfoKFgHXVz3F3fX1/g4F9enp4gYuZinDQ13Jzc3h8enZ0cnPh1XLh1c/S0tHQ09ne4XNzdHRx3tzWzoDTb3NxcXF1dnh6eXZzdnd0cm/b1c/R3ODjcXN0c3d5eXZ0cXByeHx7eHRycW5xcG7Y3HHU125wbtRvcG1ratLQyMPEvMLEwMfLz2pucXNxcHFxb3Fwbm5sy9VzcXDS1NTV3NbTz8/HyG11enZyc3p8eHh3dXFvcHVzcnNwcG9vcIBwcGxvc3J0cXBxbnJ3c3B2cnydtKeXjHDMyNfZbthveoaCgX2Bg3hw183EyMrJ33bj29vX2dHd5nZ4deXkdHTk43Pkc3Z6e3t+eXx9eXd04NfW3nN0dnR14MHCztrt7O3Y0tpw3+Xa2Gxt19nk3dXRbnLd2+bn53VxcXJw3HFz1YDR5XJtcG9tbmzS0tlzent5ctrg6N3kefH17vDw6u3j1drY43h3d3V5e3rs3+F6dXF1cXJv225vcnR0cXx8eHd1dnl3d3p4eXV1dnVzcdnPzNbbe4qKh46BfX53eXV3d3ZzdXl46ezwe/b29n2Af+vg3d/k8Hx+gu7w7+7r6+7qciXcc3dzdHV4dnR1cXN029jYzNficnFwcXJxcHBv29tvbtnPzWtshG6AbW1vb3FyamVtb3R2a8xoz9DPa290cmxyasPUdGxqbnFxcW1rbGxsbW1xcG/SydVx19dufHlxcXLfceHY2dTe4Nrp8+/w7+Li6Xfq4NjVzc7SyMx0fHNw1M1uc2zRcYF5cnh6eHXh2drU0tHNy8/U2tTL0+Ha3+rm5eficdrN1d+A5OTod3hyb93i5efcz9DV3HlzbW3N0M7O1W1qZ2hqamlram1ubW3S0NHc23Fw39nQ0tLY3OBzeHd13uPqdunf2uPldXZ15t7f3N3Szujj1M/Yc9vX4ODS2Npwbbhxfnlsz2/KzdJ0eHJv19bWz8zIys/V1dbe3d7c4OHZzsbGxMhXz9rT0tHU1NPZ3dzg28nR0NTQ0NnZ2tzb29jY4Hd1c3JwcXd/j1VfW5uNfHV3cnFzdObc3HJ5d3Pic3TjcnRzc+Xe2NjW1svCvLjAycfH0OF34NbO0tfXgO7u9P369YGIhf77gYOC/Pj4+vz68/P4/YSJioiB7+vx9++Fn7K5q5SKiISBgvbs8PTy6+nv94D89YGFg4GAgoOCgPjw84ONiIL/hIaFgv2Agf+Fio2KhIqJh4eEiIiMjISKiIOAg4SC+vuHhoGEioeDhoaAiIuIhID8ivzo7PXxgPWA/e76+4H75e34gf+Ci5KMjpCJjZqSioWIiISGioWHgJysm4GIg4iIiI6Qjo6JhoiLiomHg/Xu8u2Rw4CLjc2N+oWGiY6MkpWPhPHrgIiPj5GVko2Li4qWoa+egvP6goKBhYqIhYSCg//ygPzw6+7u7ezu9Pn6gIKEhYL9+PHqgPKAhYKBgIWHioyMiIWJioaEgP338fD5+/+AgYOCh4qKiIWCgYKHjYyKhoWEgoaDgPr+gvX7goSD/YOFhISE//bs5OPe6evo8vf8gIWIioaDhYWChYWEhYLy/ImHg/Px8PP/+/jz8+rtgo+UkIqLkpSPjYmGhIKDiYaEhoSDgoSHgIeGgoaKio+Lh4eD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2+Xo7nh+g4R3dnNtbdrmf4P29PJ8c9XW6+N3dHN1dtzYb3eAeXZycXd3dXRvdXl33NducXHf3nN1cnDcztHVbdLIzNPdcHd+enHM2nRz29LL4eXd2ejh1Njm4tHd6N/m3cvG297bgN3V1+R75ubd4+rr7PDr7eno7/b25+qC/PbZ3uTn6dvb5u70f/Pr4eDc3t7o8/j58O7h09zo5+Hj4ut45ebm7+7he3x47Ori6Hnr5ufo6XZ7ent68Hp5eH58gHx6e3p6eXVyc3Ry4HN1c3N1dOXd0m95e314eX+EiI6RiHpybWtsgHNzb9dvcnFtcXZ3eHZ12WzTbXN0c3RxcG9rbW1tbm50dnRzbtPXcHLZztRwdHRwdHDZ2NhxdnR1eXTc2eDlc3Tf39PN0dp1eHTmdHJ0fICBeXt6dnd0cXV1dXZ3c+HifnnT0Nrj4+To5OLqeHd5eObk59zT29Xn6OblzsPR2NrQgMrN2eTc1tzh2uDi3trf5Obm4uTr7ubl4OTg3Oji2NHR3XZ9fHd1dnPf3N7c19xyc3NwcnR2dOLidHV2dHZz3+Dd3nXo4uV8e3Xod3Ry39vZ3uLi29pycnp7hpGShN7Oz83N2ODZ2XWGfePm5uZ15uR8gu3b193n3+d3c+Hh29bhgOno4eZ1d3Ta2uHg2Nzm4t3b5ezp5OV27+ze2+Due+jd29/c39rg29XW0s7Mz9jUzdnl4NXZ3djV1dXa2Nje3+JycNdydnN1cXJ3eNTQzdPXbs3N2G5ub25ta8nLzM1qamlvbHNw1NVx3tbe2NrLx8TT2d50dddzen59e353dHNxgOLh3uLjdnZ5eHqBfnN3dnV6c2+AgXxzcHJydnNzcd/fdXR3g3l0fH142dDJ09fg4ODc3dvHw9DZ4u317dri6d7e5efld+vnenx58n3x6uHb5tXE43Xh4OPc2HBz4uLm7vN/jY+Cd3l6enp8g4eUmpmSf3Jw2+La0tt0l55nfWZogD5KTX14fXt/dnJ0dnl4dnV0dXZ9fn57d3Z5c3hz2Nve1Ofh591wenhzdn19ctt1cnVzdnt/fn577/Dm39bR197ddH9+g356d3N3e3h+f3tzdXPY2dlxcuTq4dbg2NXX6unm3efh3efs6OLn7uvs5ubm4+Ll5eTod3fq59Pc4eHngOfq59vc1tPSc2/W2mtubnNzcNRv19LZ2+HXb3d0dHRzcM/a1tPeb9nWa3FwbnRvb3h0eHp5dW9vc29wc3V1duPc1tbf2dTDxchr0NDT1m5w1W3S1GzXcNjT1Nbh2N/i3t3a2HPc3d3V3uN26eTc39Hc4nN14eTi4NxxcnR2cm1uBXFub2vUhG2Aa9du2W5z0HJ4eHZz3NjddeHi6ejqdnyBhHl5eXV36+l5eOLl5np37eTo3nNwcHR23tpucHRub29tb21sbWlwcnPd3G9yc+PicnFubdrW3+N04Nfa3uVyd3x6ddbedXTg2dDg39bS39nLyNHXzNLUzNfRwbzNz8nMxcjTc9na0dl33dfW3t3k393j6OfZ2HTj4MjO09fg2NPV2t904NzT0MzMy9Ha4ODX1svCy9XTztHO1m3V3tvf3cxvcm7b2c/Ub9rY2NfYbXNycm/Ybm9uc3F3dnR0c3VzcW1tcG/YbW9sbW9u19LOcHh6e3V1en18foF+d3JvbnCAiIeC/YKHhoKHjY6Nioj+gP+Ei42MjYqIhYGEhYOCgIeKiYeC9/uDhfzs8oCGhoKGgvv6+YKIhoaJhPn1+v6Bg/z/8Ojr9YOFgP2BgYWOkZGIiYmGhoKAhYSCgoSA+PiKhOPi7vf19vr39P6EgYOA9vT37uXq4vT39/be1efu7+GA3OPy//bu8/bt8/Xz7/T8+/n3+v//+/35/fbx//3z7ez4hY+PioWFgPby9fby+oKFhICBgYKA/fuAg4WEhYD17Ov1gP/5+oeGgP2CgoL+9/L3+PTt8ICAiYiSnp2O9+jo4uHx+O3tgZWK/Pv5/IH79oaM/+3q8fzz/YSA9/bs5u2A9/jw+ICFgvPv8+zh5/X18O33/vz8+4D//O7q7/2D9ufl6+zu6u/q5ujf2trf6OLb6fn16Ozx7uzu7vLx8/z3+YCA94OKhoaDhIqN+vbx9/2C8e75gIOJhoSC+fv7+YGAgIeEiob8/IP99Pz2+Onm4+7x9ISG9IOMkZCNj4eGhIKA//jw9fiCg4iHhYuIgIeJiYyFgZealIeBgoKFgYOA+veDhYmXioSOjYb06d/p8v7///r49+Lf5+Tk8/rz4+3z7O719/eA/vuHioT/gPXw7Ov87tv4gfTw9fX7hIP98ujr9IWVmJCGh4aMjIuLh5CbpauYh4Dz9PDv/oOrvYnPuMCAhKOHqJGTj5WMh4mKi4iEhISFiI6Oj4uGg4mDiIDu8vXr//f9+YCMh4KKkZCC+IaEhoOChoqGhYP7+vHq5uj0//2CjoyPioeLh4iLiZSXkIeHg/T3+IGC/vvw6PPm4OD5+vTl9vHq8vf08Pb++fn1+Pn28PDy9PuCgf/96fP59P6A+/b16e3o6OqDgfb+goiFhoSA9ID59f38//aAiYeIioqF8vv09P+A/f6BiIiGjYiHkIqPkpCLhIOLjY2Gg4KC/vn39/z3+Onu84L++/v7gIL4gPf5gPyC9Orq7/fk7fX0+vjzgfj4+e7x+YH99e/y5vP7gIP8/v//+4CBg4aEgISAh4OFgv2BgIGCgP+C/oCF9IiOjIqF+vT9hvv3/Pr9goqQlImLi4SD/v2Fhvn3+oWA8ev59oKBg4aH/fuBh42FhYWEiIWDhIGIioj/+YCDg//8gYSCgf3y+P6C+u3t9f+AhYuIgvD9hIL68OX8/fHr/fXm5fP47fj78v335N3z9/OA9Onr+ob07eHq8u3q7eju6+z1/Pro6YD59dzg5ujo3d7o8veB+PPr6uTi4er0+vju7uPZ5e/s5Ojr9YD3/Pr//++ChoH//PD3gfz39vf9hIyJiIT8gIKCiYiPi4mJiIqIhYGBgoH+gYOAgYOB//r2hI6Rko2OlZeVlZqXjoiEgoUEfX19fIp9A3x9fJN9B3x8fX18fHyGfYN8hn2EfIJ9hnwEfX19fJN9BHx8fX2KfIR9snyHfYZ8iH2CfIZ9hHwLfXx8fH19fXx9fX2IfIh9iXyDfYR8BX18fH19h3yCfYl8g32PfAF9hnwBfaJ8A319fIh9hXwEfXx8fIZ9hHyHfQN8fH2LfAN9fXyKfYV8mX2CfIl9m3wIfXx8fX19fH2IfAF9hXyCfYV8k32FfAp9fX5/f4CAgH9+mn2IfIh9AXyKfYl8kX0FfHx8fX2gfIJ9j3wEfX18fIZ9Anx9hnyHfYV8A318fJZ9inwBfYR8CX19fH18fH18fYx8AX2GfAF9h3yCfYV8i30BfIV9Bnx9fH19fIV9BHx8fH2FfIl9CXx8fX18fHx9fYR8hX2CfI99B3x8fX19fHyEfYR8AX2FfIV9BHx8fX2cfAF9kXwBfYx8AX2WfAF9hnyDfYR8AX2FfIV9AXyRfQF8hn2DfJF9AgIEAIDSaWpqz8zUb293e3p2cG3Qy9TVzc9rb3V4elU9VoB4cW5ua25ra87Pa9bRyM3NzdRu1tLMxsvRycHDyczO0Gtszs9t1c7KzczUcW9wbWx1e3HNbnLUy8XQzM3MztRwcnt5cd1ydXx25uZ+enfg5HV2dHLc1NfL1ODedXh5f+zk7YD07eLqf+7T5+7n4+bi6/Tu6ufl5uHj4ejo5+fg4nd3enV0enp5fHl2cHJz5d3b0M/ee3x/g4SCgoaIiYR+7ut8fn6Cft7E1Xt9gIF+eXbg3NvW0tDNy8vO0MzQ2uLf1c/V1tXi3tLPcHNx0cbS187J0dXX0c3OzcjUc3t63cTC0IBwdXmBg312cNTNztVtb9XY2tHQ1t1w0crM0tzY0tLT19Zvb83L1NXR2HFxcHV1cnWAedjj4HF4e3Xd4uTh39/f3dXMwMba4ejr6PJ/e32Bg4Ds7Xny+n+Egol76e/se33j39PgeH5/e3fk3+Dlc9t1dHdzd3p2hY2Bc+Ny2MXG2IDUz2vUxcpqaXF02WvFvs7O0NvOv8HK2trQ0Mne19x2dG7Ta3PS0Hl52M7Jz8djYry5t8DCwtLawsDJ1sS4sre8xMPB2NvOtLzK1c3C0HFyc9PHy2l4d23Wcnd7dN7Y3N/c6ufj53bn2+HU193S6Nvf3dra3XZ2fHrowsDJzNDY3IDc2eFzbtducdbRzNDManR1enqIhomOi46SdW5yc3p1cXl1dnh5e3t2fHZzdXdzc3N4dnR0c3HZ1NrhcdlyeOrteejc2NzY1dnbcuTqd+bkdXpx0nF2dnZy5Xl3dXXf4ubm7Onq7+jl6ern5+vo4uHkc+Pgc3Tm3dbZ5dzV6Xl6eoB94XPh4tvh13J1dXRxbm9wc3NzcXBvzchqb3R1c25ucmZsdHRvbWVnbHB3eHDNycfKaG1saMnGws7Kzm1uzM27xNXUzrm4vr7DbH3VbGzYbm9yc9rj4N3J1XBvcXSCg4Plz3B1c3R3dHLYzs3LasfGaGppZmK8umFlYsS/wMK9yIBmY7xiaGZqbWdmw7m7vsjExdFrzcnBxc/K1nRqzcjM1M1qa2tqbW9zdXRvbtTV1crR2OB32MTE09tzc97U3uHZ0c7Nzc7Wcnd2dnHbb3Jybs/GytFpamvMysjFy2xt1HFqznFzcm5vc9vX0dLVbtvW08/R4XlybXB6eeng3t/h4IDh6N3p5d/az8/c1ex/8u7s5N/t8Nvi5Ozt5OPh2NPf6u/w8HuCiomA7eTj3tfg49zhen7ven6DfO3ve/J94dLh4eTvfoeG9+nyf+7w8nz06d7Y1tvc19jh6Ojo7Xjn5d/W2HTn3Nbf53V0deDZ3uns6ONxcHJydnlx4c3H3N/W1oDicXFx497ec3R9goB8dHHZ1t/Z0tRudX2BjG9Sc5mDeHR1dHZzctzfcuLe1tzb3OBv3d/a0dTZ1M/T2dnZ3HJy2t9z39PQ1tbednFzcG10e3TVc3XW0MvV1dTS0th0dXx6deZ2d3t16ud/fHji53h7eXbl3t7Q1eTkdnd1fOzl6oDt59/le+jR5+7n3+Hf5+/p5ePg3tfZ2N/d2+Hf4nl4d3Jyd3Z1enZzcnJw3dXUzsrXdnV2e3+DgX9/gHx34d93eHd8etjD03l7fn98eXbj3tfQ0tLT0s3Lz9DS2uDi3Nfe4uDp5+HnfX5859rf49zY3uDj3tve4dvmfoiG6tHY6YB9gYWJioaAfe/n6O54e+vv8+zs6e146eTl6O/p4uTk6el4eNzY4d/Z4HV2dnp4dHV+ddbi4HJ2d3Hd5ufe1dXX1dHOxMjX0tfh4el4dHZ5e3je33Hg4nJ2dYB13N/adHbX1cnWdHt8enbg3N7jctx1c3ZxdHl1g4l+dOl46tvd8oDw8Hz05Op5en186XTd2e3s6fDk1Nfj7+ne4tzn1+J6eHTkdHzl4X572tPR2Npxc+Pj3uDb1t7cvbrH1s3Iyc/Q1djT3d3XwMTR3d/b6Ht+gu/f33B9e3fxfX59deTa2+Hh6+Xi6Hju4+XV2eTe8uHg3tre43d1enXny8vQztTe5YDq3eJ3c99wc9jV1tvUanFxdHJ9fIWQjY2RdW5zcnl3dX54eXh4dnVzdXBtcHVycnJ2dHNycXPg2eHrduV4euzqdd/W09rb1tjZb9zhct7ecXdw0W5xcXVy4nd2dnfaz8/Y4uHl6+Th5eXg2drc2NndcNrWbW3a1s7O2M3H23FvcIBz027a29Te2XR2d3d0cXNzcm9vcHBw19RrbHF0c29xdWpxenhxcm1xdHh+gHnd3N3bcHZ1cdXTz9vV2XJz1tfH0N/Z2MXDx8TMcYTjcnHcbnJ1duPp5+jV5np3eHiCg4Pt4XyEgYCBfHfj4uHcc+Lqe3t4eHTe12xva9nd3NjNzyFsb9txd3N2d3Bv2tDR09vX1uJ02tTP1NzW43pw2NHU3tqEcYB1eX2Hhnl1397e0tTV2GzLw8jV1Gxu3Nvi3dHNztXW1t90dnZ2ctxwdnh13Nzg53RzdN7b3+DkdXXne3PZcnV1b29z2tPT191v1tDSz8/ac3FvcHd36ODZ2uLl5Ozh7uzq5dre6N3vfuzo593X5erT2dvi5NrW1MvF0dnc3dtxd1t+fHLS0dTSy9XY0tRzeOJydHZw2t9y33TRxNPS09x0enjh1+F24Obod+rj3NrX19fW19zg4uHgc+Df29HVcuDX1N7mc3Jx2NHU2ePm5XJwcXN4enHcx8fe39XcgP+BgYD/+v6Eh5KVj4aAgPn1//33+YKKkZm6robD4aSNhoWChIGB+vuA/Pjz+fn6/4D7+vXv9f3z6Oju8/X6goL2+4P88evx8v+JhoaDgImRh/aDhvPo4u3s6+fm74KEi4eA/oKEiIH++oeFg/j9g4eFgvry9OTr+viAgoGH//X6gP747/SD9tvz/PTr7efv+vf08e3v6Ovr8/Dx9/X8h4eIg4WJh4WHhYKBgoD99vbt5vKFhomNkJKQkJGPiIL49oOEhIuJ8dbphoiKjIqFgPHu7ent6ujp5OPm4uPs9PTs5ezx7/r47/CAg4L15urt5uHo6/Hr5+7x7PiHj4z54OPzgIGGi5aZkIeD+fDz/IGE/P389PT0/YH48vL3/vnx8fP7/oOD8O34+PL5gYKDh4WAgYqA7Pr5gIaHgfb59/Tv7ers6eLT1+nn7PHy/YWBgYWIhvf6gPz+gYaGkIP3//2DhfHw4/CAiYuIhP33+P+B+YSChoCFiYWTmYyA/YD04OL8gPv5gf/v94GChoT9gvHk8fDu9+vd3+f29uzw7Pzv/ImJg/+Bif32i4nz6+z3+YGA8/Dt8Ori7fDRz97t3djV2trZ2dbn7ObM0eH09/H8g4SG9uz4gZCLgf2Fi46F//Lv7+38+PT8g//y9+Xn8un+7evp6O/2g4KJhv/X2+fo5/D7gP/2/oSB/4KH//v5//qAh4iMjJmXnqmlpqyNh42Nk4+JkYqJiIiFhIOJhYGEi4mJh4mDgYGAgPrx8/yA94CD/v+C/fHr7erq9PmB/f+C/P6DiYHygISEhoH8goCBgfDp7PT/+/v/9/X7/Pfw9Pjz9fyA+/mAgP727Oz37+r+g4KEgIf3gf7+9P77iIyLioaCg4SGhYSDhIX69oCEio6NiImOgIeQj4iHgYaIjJWViv7+/v2BiIiF/Pfw/PX3gIL19uPs+/Xx29jc3eiAlP+Agf+Dh4iG9/f2+eX5hYCCg4uJjP/vg4uIiYuGgvn2+fmB+P6GiYmFgv77gISA+/r8+/P2gICB/YOKhoeKgoD35+jq8u3t+4H17+Xr9+/7i4L78fT++IGDgoGFiY2VlIuH/fr78fT4/YLz4uX3+YCA+/L2+Pbx8vTz8PSBhIOGgv2BhomE+PX4/oGDg/r19fT5goP/hoD0goeIgYGG//fz+P+C/vb08vP+hYOAgYiE/vDo5+vrgOnu5PX17eXY2ujh+IX58/Hn5PX64ejr8vLr6+3h2Obx9vb2gYqSkIT08fPv5+vt7PSDhf+ChIaA+PuA+oLp2Ofm6vaBioj/9P2D9vz/gv317+zr7ezp6vL4+vv9gf79+O7zgv3y6/P9gYGD+e/x9Pv8+oCBg4SJjIL/5+L5/PH4B3x9fX18fHyIfYZ8hX0Dfn9+iX0DfHx9h3wBfY18BX19fHx9hnyIfQN8fX2JfIV9AXyEfQd8fH19fXx8hH2HfIR9h3wBfZh8jn2GfIx9gnyFfYN8h32ZfIN9j3yDfYR8iH2EfIJ9h3wBfYt8gn2GfIl9g3yEfZJ8hn0FfHx9fHyFfQV8fHx9fYR8hX2EfAJ9fIt9Anx9hnwEfXx8fIR9Anx9knwKfX19fH19fHx9fYV8gn2efAZ9fX18fHyEfQF8hH2JfAF9jnyEfYt8BX19fH19hXypfYR8B318fX18fH2IfAp9fHx9fHx9fX18hX0BfIR9k3wFfXx8fX2IfIR9Anx9hXyOfYJ8lX2EfIR9hnyCfYx8Bn19fH19fIR9hnyHfYJ8h32EfAN9fHyFfQV8fH19fYZ8A319fId9iHwBfYd8gn2FfIt9h3wBfYV8gn2LfIV9AXyEfYR8g32FfAZ9fXx9fXyGfYV8AX2GfIZ9knwBfZZ8hX2JfAN9fXyEfQV8fH18fYZ8C319fXx8fH18fHx9jnwBfYV8AX2FfIN9h3yHfYd8AgIEAIBoxrS7ZWl2d25wcWxvc3qGkYNua3N2cWxqyMFlam9ydHBr08nGw7zBw8DEytFwdXdua21tbWzY1nBxc3bp6Hd11s7Yx7zN1dPRy9DR0s/TzcS9utNrustxg4TYv8nP1snGx8nRzHBtb9nabWzTfq5xw5qLeHp4d3Rz19Rv2XbY24Dd029u2NHWz8zZ4eLjdOHqfHno4ud5e3146Np8fuvf2+ji1dx36vTw3Nfg29rueefrgIiE7unod3d8g4J5eoB24t/c3eTj4nV4d3R16ePc3t/e2OPe2dbPz9Xfc+HX3NjN33yWuamDdtnLbNXN2XPWydvj53Nx4t7qguvV0NHQ0IDY2djMxM3Nx8zO09nV2tfFrbzIwsLCvr7CztDWb3DbcHLX2dbSzdDOz93c1OHh299+kI57cdTT4+TY1uV+eXl/io2Jfuzh6unl5O6BhH18835+g4CChYaKjImIkJCLmaijkveBg+vu9fbr8u/o6+h8e+Tp3ODh4d3h29rW083NzoDW09bV0nLhcW7CwdTi08/Jw2lva9V2d27NzMvRcuN5cnfs2+Lc18W/w8LEz2xzcnJ0dHHRy8LFytDXfHp6e+Fw3HHa13N7f3pz4d3PxcvQ1NLa2NHYbm/Y0W1ubmxva27aeXPb0mtxc3Z6dnnh2tbS29HU183Mycts3uXT39bAyYB1hI6Cfn9229jYdHhwdndqw75ub2xwcnh/e3h9fH9zcG1pbG1oy9ff33jo3d3j7HZzdHNw1dvX5HVydOHkdOTf4XTcdnZ6eXh57+vm7OjkeXt25XR4eHp6fnt55+178nqBfH73+O57gX18gX+Af3t2d4KEg316e3t4e3jP09jf5oDm6eh6g312dXV5d3p0cXFxdnVzdHdzc3uW1IOZs3Ry0cvEvry6x9bV0MTFwNNvctjg29bKy9rQ2nDa2tpt19LV1sXAy8ls0NTa2s/PzGps2HBw19XW2trRw7y4tb3Kz2tmtbrCwM3Oz8O/urW4xsC+xmZkZWS7srG4XrK6ZGZlaIBqwWptdHJy2NDNx8vKyGxubMzDwsfEu8DHrbRlaMnJznB4e9Vwb3l1d2/Hx9HUydXSzs7Y125wdXPg2t5u1cvO4NnOwsnExtHSy8G/x8zNy8S4ucu2t7y5u7u9w87Cx8XDzG1sc2vIy2ltamvJ0dTjcd/i1dDU39fEwtbd3sjlfIDY4trh3Nze2eTf1tvc4+7d2+Lg1MvU2+R6c97g19jg5nNzeHp5697Mx9bkd+Z16Ofj4d3T3nRz2tna2eXW2tzR09rh2+HY1NPT3ujo5ux24eLf1N7l2t/d2dPR2efl28/S2t1wc3RxcXJ1cm5vcHB0b8zSysTHwsjNzsXBx9XY04Bz387VcXaEhHh4eHd8gYqitZx8cnh8eHRw09Fvc3d6eXRw3tXW083Q0c7U2+B2enx0cXFycnLi33Jzc3Pe23F03djfzcLS2tjX2N3b3Nnd1tLNyuZ2zNZwe3vRv8XGzcTHzNDSyW9wc9vabmzTfKdtupOFdnl2eHd33txy3njd3oDj33d47uDf4ePn6eTnduLlenfk3+J1dnl04tR3eOHV0N3b19103ejo2NPc2NHhc93heYF94+DjdnZ8f395fIF35eDa3OPg33N2d3Z37OXd3d3f3+ri3dnR09zmdeTc5ODY7ISfxbWOgvDjduXg63jd1Obu9Hx58Oz3hezd3eDi4lXp6Obd1t7Z0NTW3ebn7O3cwc/e2Nba297c4+jxe3zzfH3o6+ji3uXj4+zl4PP07PKJnpmCeOPb4+Hb2eB6eHd4gomGeeHY5OHc2+J2d29v23Jxc3BzhHiAeXl7eXWAjYt+1W9wytHZ2c3W3N3c1nJ13ebW3N/h3+Pg4tzW0tXX393d4OF68Xp97e/69/Ds6OZ6fXXnfX555+jk6H77hHl77OLu5+nb297c3OZ2eHV0d3l34N/b3+Pm6Hx1eHvlct916uNze399d+rl3dXc2t7a4OHe5nNw3t+AdHR1c3Z2eeZ6dOLdcnd3dnp2eNzQ0tXg2d/m4+Xi6Hz39OTu4MnUeoqUh4OCeunn63+Bdnp4bcnFb2pma2puc3JvdHd9dHJxcnR2cdve4+N47ePd2ttyc3JzctjZ0990cXHc3m/c29xx13Jvc3Z2d9/c1tjZ1W9ycd5vcHBydHk3dG/W2nHicHNsbdXY021ybm91dXRxcXByeHd2cnBxcW1ubcLJzM7W19fVdH12b25tcnFzbWxvboRwgHV0eHqV2ZOwzH943dnUz8zI0Nza0cTEwtZubtTa2dXKz97R2G/b3N5v2dbY2c7O2M9v2tza3dfd23Fw2XJ039vc3t/ay8rKyMzU1m9syNPVz9vd5eHe29nb5uPi229vcnHVz87ZcNjfc3V1d3jceHd6eXjg4t3V2djXdXd129LYgN7ZzNXdxcxxcdbR13N0d9l3eIiLi3nNx9Lb0Nzc2djd33N0dnPg291v2M7N3tzVydfX2Nva1s3N1dzd3dfGzOHO09fT1dTT0dXHxMfL0G1tdG7W12xxcHDX4+Dkdezw5OLl8+/b2Ovv8t31geLs5O/r6erl6uPb3dzi6d3i6OPYbNPd3d50b9re1tba3G5vdHh46t/RzNfndeZ26+fm6ejc5nl55eLi4OjZ3d7S1t3f3OXc1dTR19/h4el03+Pi197j2N7c2djY297h4NfW2dltcHFub3B0dHN0cW1zctng1dLV0tjf3M/M1uPe3zKB+ubvgIeXl4mIiISKkpy3zbKQh4+UjoeD9vKAhZCQjYaA+u7u7eny8+7s8P+IjpGHgYSAgP/+goKBgPv9hYbz7Pno3/P89u/s9PT18PLs6eTf/4Tm94aamf7h6Orv6Ozv8ffzhIKC//6AgPyUyILZqZqKjoiHhob594H8h/f2+vOBgf7y9fLx+v78/4L3+ISB+u7ygYSJg/rrhon67On39O74g/j+/+/q9O7n+4D4+4ePivr0gPiAgIeNjYaHjoP89fT3/vr3gYaGgoH++/Xz8PDu/vPv7+nr8f2B/PP58+T4h5u1pomD+/aB9+76gOvb6vX+gYH/9v6L++vr7u7u9/j37OPq6OLm6fD18PP048fX6OXp5t/d3eju+IGA+4KE9fPv7uvw6uft8Oz+/vX8j6Shi4DugOn18eTk8oSBgYSNkY+H/e329O7t+YaKgYH9g4OHgoSIiYuKiYqQj4qUoZ2R94OH8fL5/PD5/Pn48oCC7/Ti5+vq5+zq7Ofh3OHj6+nv8PCB/oCA7e37+/b08/CAiID3h4mB7+3p8IL/iICF//P/+Pnq6O/t7/uCiYeFhYiI//zzavT08/WHgoWI/oD8gPr1gImMiIH+/O/k6+7y8Pn69f6CgPv5gYGCgoiGhvyIhP/4gIWFhIiChvns6ez67/L07e3q74D//+/559PjhpqolYuKgvf3/4uRh42Og/fyhoSBh4eJjomJkpObjouEjICE/Pz9+YP+8fP4/oOEhIWB9PTw/IKAgvz9gv/3+YHzgoCEhoeH//35+vn4hIaB/oKDgICCh4SB+P+D/4GHgID7//uCh4GDjYiGhIOBg4uLioWDhIaCg4Dj6/D0/Pz+/4uSioaGiIqHi4iFhYaKiomJjImLkK/4m7jfkYn88+rn5IDf6/r17+Tk4/eBgvX69ezj7v7t9oD8/f+A+/f7++vq9+2A+vz8//b4+YOC/IOD/vv9//v15uLg3uf2/4WA6e7v5vb3+fLv7ezw/vz6+4KDhYP58O36gfX7g4aGhofxgoSJiIj49/bx9/f5hoaB9/P3/PXo8Pjc5YCD+PT/iY6S/4CJiZmYmIvs5e705vLw7O75/YGAgoL/+f+A+e3s//v05/Pv7vb38eXj6/Lx6+TZ4/jd4urm6evv7/Hl5+Pl7oCAioL4+4CGg4L1/vn/gPn87Ofr/fPb2vH19+H6huvx4u3s7Ovj7Ofe4+Pr9+jt9vLm4u3s8YKA+/zw7ff/gYGDg1yC/vHd2en9gv6B/fr4+vbq9YCA8u/w7vnr8/Xm6fHz7/ry7Ovq8vr49fyA9vr47PX67/v59PLy9vz+/fT2/P6AhIWChIaJh4WGhYGGgvX+9fD08PT6+e3p8P/7+gR9fHx8lX2CfId9i3yJfYJ8hH0EfHx9fZR8Bn18fH19fYt8C319fXx8fX18fX1+iX0FfHx9fH2EfIJ9iXwIfXx8fX18fHyEfQR8fH19h3wBfYl8CX18fH19fXx8fIl9h3yFfY98AX2GfIZ9B3x8fXx8fH2FfAZ9fXx8fH2ifAV9fXx9fY98hX2HfIh9h3yEfQF8kn0DfH19inyCfZR8BH18fX2IfAd9fX18fX19hHwFfXx9fX2LfId9h3yEfQZ8fXx9fHyFfYx8BH19fHyHfQV8fX18fId9jHwBfYd8h32DfIZ9gnyTfYR8AX2FfIV9hHwLfX19fHx9fHx8fXyGfYZ8BH19fXyIfQR8fH18hH2DfJV9iHyXfQV+fn19fY58gn2JfAV9fHx8fYh8AX2HfAV9fXx9fY18gn2QfIR9hHwDfXx8hX0BfIV9h3yDfYp8CX19fHx8fX19fIZ9i3yEfQR8fHx9pXyEfYJ8hH2EfAF9jnwBfZh8gn2GfIV9hnwDfXx9h3yCfZd8AX2UfI59j3wCAgQAgMdlyGdkwWJkZmRmZ2psbmTAvsLFxWVlwbvAYGJmamZmbGdgYmhsZW9/enNov8LCvL/Bv79lZ2dtdHh5dnR3cdDVd3dqbHBzeoB5eHt5d3Z6eXN2dXV3dHymgH9vioFzbnHSx9DMyMhobGtvZ2tty2jIZ2hpaGx8fHFoaM1qamhrgHFxcWvPbGvP0Ghqz8rOytjL0Nre2dfk2djd0tLW3+Xm3tXX3tjk291xeIF5eeDUysjGxcXO2nLg3dxt2XN7bmxoaWdiZWxqaGdmY2ZlZmZmaWptamFevGNnZ2O7YGVkZGVivcTGaGtscnN7bGNpcmlp0b9uaWxraW9vbmlnaL63gL6yrbO6ushuz729w8dtyMJr19XRztLOaWhpampmZ2tscWxva8fO1M3Lz212e4N/eXx1cHBt0tRwdnPO093a29fidnl+7Hl7fnbr4OPY1uR8gPHx8u37gPjxgID6gP+B6t3e0tHV3vCAhIP97fP7f/j37Ht7e3p8fHrz6oCB6uXZgNbdz87O193qdtfN0W7W19XVz9PMzW5uzM7FyMzP0dvT1Nvf4eDX09jhend95dJ0eHp4e3p6fN3tgfDs7959jISDePN+fHh+6vHs2uJ55ut/ddvfdHZx3nNzdHh7fYhsqdmGfXduwMlpcG9ta2xvc3JvcG5yb29t1OB1enh039HNgNTX1eNy4N/j6uZ0ctnh2t92fnx4dmlpa3Jw0nVxdHV6dHl3c3Bu19Vrz3Ny2GtscNrTa3B3d3p+d+Ho8O/m5/X24d/i8HmCeeF35ubrdnZ5gXx+hpqMfOmChn6Hf/Dl6e3j43v25+Ps7/bqen557nx5eX+DhX7s6O58fIF+gIJ/gISDfnx7dnFvc3l4d3l3eXx3cnbk4NfT0t7Nx9fb1sfI08/R0NvY4uPi4+nogYLtd+Tk1912duDg43Vzc3Dd3nF1cs3ac3Bqx8jIy2vDx3Vuy8vDw8TGy8DAyNXQw8rS2NHJvsPPcrzA1HHUbNJtccnOzNBta25obWpuaMZrb8XJgK6mtMXEx9dtb3PNasDHamrMZm3SysnLztRwb8jIztPT1c3BvL/EzM/OxMDK1czIyMbU19rOxdnR08/Ex8vT3tjK2dbg7ex35t3c4dvW087K13/XzMbf1XB6eoaA5tvT2M3R08/Mb2/Z3dbJ1HvUxKeoqLTKx8LQy8qyt8bM0MvJgMTAydXSzd1yzcjN2Npu2dZsbWzUb3Ju1sfPb23NyM/Wc9bLwc3X0sa3vsvJx9LQcW/Q0tLQ1dTT09jE0dXV2XPf4nfZ28vEyNLKzcfF13d33HJ2cm5xctvV1c/Kys9paWxp0WrIwMTKzNTOyMjAwsXKzMrPaWpmwcPCy9NpZ8fEgN1w33Jv1m5wcW1vcHN0eW/V0NDQz25v1tHXbG1xdG9scm5pa29za3SIg3twzc/MxcfLzsxoa2xxdnp7eHR4dNTTdHVsb3RzeYB8eXl2dXZ6eHN1dHd7eZ7VipNieX1ybnDRx9PRz9FscW9zaW1x0mrHaGlpaGhvdnFpadNub21wgHNxcGzVb27Y23Bx3NfY1OLW3uTm49zh2Nne09TY2+Ll4tzc4dro4OR1eH13eeTa0szDxcnT3nPk3uBy5HqBdHR0d3Vvcnd1cnFxcXRxcXJzdXV2dG5qy2hwc3HVam5sbW5rzs/TdHl5g42YgHFzenJ059J3cXV4d3p4eHV0ddnTgNfJxMrO0OB769zb4+V76ut/8O/p5e7xfHp5eXl2eHt7gn+Ce+Hm7eXk6HmAhY6KhIR7dnRx3N1zeHXR1+Lh49vcdnp+53Z5fXHd0dnU0dt1d9zZ29jjctvVcXLgcd1vzMHAuru8vspucnHb09ndcOHi2HFwcGxucXDb1HZ43d7UgNLc1Nra4ujxeuXh5Hfo5uLn6vbz94GA6uPW1dbX1t/f4ubq7PHo4N3geHN55tl1dnV0dnRzd8/deeDc4td2gXd3bt1xb21y0tPbz81s1dVzbdLacnNw2HBxcXR1epB0qs59fn120tpxd3d1dXh6e3t4d3N2cXJx2uByd3p67eblgPL57vR+9vPp5u96d+jv6vJ8gIOAf3JvbnNvz3NqbWxzb3d2c3Jw3d5y3Xh14HV0eOjfcXZ7eHd5ctXZ4OLf4OPg1dXW43J5cdZy3drabm5ucmxudYd9cth2dm92btDIztTW1G3Uy8nQz9TObG1q0mxsa3BzdHDRztVvb3RwcXFugHRycHJwbmpoaW5sam1rbG5paGzU0c3Ny9fKydvf2szJzMbHxc3D0NLS0M/QcW/XbtPSytFub9na2W5rbW7Z2XBzcMzbc3FsycfM1HLT03p03d7R1Nrc3M/T2uPh1Nrc3N7i29/le87V4nTactpwctHW1t90c3VxdXJ4dN1ydtXagLyzw9fY2+l4eXrfeNnbcXDbb2/X09TU0tRxctTV1tfY4uDTzM3P2NrYz8vU4drW19bk5OTXzd/X2dTIy8/X4d3O29ng6+l26OPj6+ro5d/W4obl1s/q33R+fYV729XT19Ha2tzhc3Pk59za64nt3sLKyMzi4Nfi3+fMzt/j4tzZbtrV1eHh2eN22dPW3d1v1tBpa27gcnRw3dDZc3Tg3OTqfenc1ODo4djI0N/e2uPfdnTe4eDZ3drc29/I1drY3HXj5Xna3M7KzM7DycfI13Jx2HJ0cnJ1dOLf5OHd3+R0en546Xbf1Nfc2dnY2t/bheIN5nN2dNrZ1NfjdHLf24D9gf6DgPmAg4WAgYOIiY6E/fr8//6Ehfz2/oCDiY2HhIuHgIKFiYKPoJeQhvv/+e7y8/H4gYKAhIqPkY+Lj4j3+ImKgIKHhoyTjYiJhIOEiIaAg4OHi4eq3JWuiJqVhoGF/PH9+vv7goiHjIGHif+C94CCgYCBjJKLgID8gYOChICKiIiC/oGA/P6Bgvv09PD/7e33+vXu9Ons8OLh5+3z9fLm5evq/vf6gYaMhYb77uXh1dPY6PmB+fb8gf+GjoWGhYmHgYSIhoOFhoaIhYWGh4qIiYaBgPuAhYaG/4CEgoOFgPX4+YOIi6C9yp+EiI+Dgv7nh4SIh4SJiYmDgYDv7YD14tjf5+n6iP/p5uzugfLyg/v++vT7/IKCg4SFgICFhoyIi4Xz9/jx9fyDjJKdl5GSiIGBgPX2gYiE6u/79/jw9IKGiv2Dh4yA9erz7OXxhYf5/Pzu+YD47H+C/4D7fOPb5d/W19zrf4OC/fX8/4H//fWDg4GAg4SB/PSFhe/p24DX3dHX2eTq94Dy7vaB/Pv5+vb+9faBgvHy5+br8PD48PL4+Pr+8/H2+oaBh/rrg4aCgoWDgobo9Yf++PzthJGHiYH/g4KBh/n3/fD1gvz+iYL2/YWGgfqCg4OIjIyhiM7+nJaPh/H6gYiIhoSJi42MhoaCh4KDgff9gISGg/bo5oDy9u35gfnz7O35goDt+PT9hYuPjo+Cg4iRjf6JgYODioSLioaFg//+gfqJhf2DgoH8+oCFiIWFiYDv9v7/9vP8/e/z9f2AjYHxgf37/YGAgIeChY+lk4P6i42Hj4X78Pb8+PiB/vTy/Pv/84KHgfiCgYCHi4yH//7+hIOJiYuLh4COjouLioiDgICFhYWJhoeKhYCC+fLw8e797uX4/Prr6O7r8Ojv5fT18fDu84aD+4H5+vP7hIL29PuCgIGB/fyCh4Ps/IaGgO/s7vOB7u2IgPHt3+Pn5ubY2uPr6N3k6Obn6+fs94bf6PqD+oP/hIj2/Pf9h4WIgYWBhoD3gYPr84DTydjr7O/9gYWJ/IXv+IKA+oGE+/X8/Pr9hof28PH08vbv49/h5vHz8OTe6v/78O7q9/b57d3s5url2Nrd5O/q1+Pk7/z9gPz09f759vTu5vGM9erj//WAiIaPifr08v35//v7/4OB+/3w5feR9uPFysTM5uDY5N/jxcXZ4+fi3oDe29/r6OT6g+vl7Pb6gf/7gYOA/4OFgfzp8YGA9u/z/on/8Of0/vjt2uDw8O/68oKB8fPy6+/s7/D03uzx7fOC/P+H9vjq5+nw6PHs6vmFhv2Dg4KBg4L/+/747vH7gYSIgf2B9unq8fT38/L17/X5+fj4/oCDgPP07vH/g4H6+QZ8fXx9fXyKfYV8BX19fHx8kn2IfIt9gnyXfQR/gH9+hX2GfId9A3x9fIp9AXyIfQd8fX18fH19nXyFfYl8Bn18fHx9fJp9AXyEfQF8hn2DfIx9gnyLfYl8AX2FfAR9fHx9hnyNfYZ8i30FfHx9fX2HfAR9fX18hH2GfIJ9hXwJfXx8fX18fXx9iHyDfYR8BH18fHyHfQR8fH19i3wFfXx8fH2IfIJ9knwFfX19fHyIfQN8fH2EfIV9AXyEfYV8C318fH19fHx9fX18h32CfoV9gnyQfYJ8hH2HfAF9hXyCfYR8in0BfIt9DHx8fXx9fXx9fX18fId9jHwIfX19fH18fHyKfQF8hX2GfAF9h3wEfX19fId9g3yafZl8BH19fH2EfAV9fXx8fIR9Cnx8fX19fHx9fX2EfAV9fHx9fZV8Cn18fHx9fH18fX2EfIh9A3x9fYl8DH19fXx9fHx9fXx9fYZ8gn2rfAF9inwBfYV8hX2JfIJ9hXwBfZp8AX2FfA99fHx9fX18fX19fHx8fX2EfAF9jnyCfY58BH18fH2LfAN9fXyGfYd8hH0CfH2QfIN9hXwEfX18fAICBACAy2lq1GpqbHF3dXBqbXJwaGlvdnRxcHBzeoKNh3tsbW9rbGhmam5yfo+KdW1+cHBqZ25wbXNxcXdzbnFzZ2trbG9vbXBycGttcG9oyM1ratBrc25sa2vTbXDX0s/TbnB1cc3KzsxudHFxbWtpbGjPzMzFZWZnaWxzbmhna3BqZ2uAZMPIZ2tubm5tbnJxdXZwd29rbnB1ed/Uyc1vdWtxdG5ycm3PamrTb3J2d3dveIRTUnBpyWduamxoZM1oyMXCwszIurvCxr+3v8fGamfGvsHGvsvWbdTP1G/VycXBzWzRadHKzcnEyWtwc3xp0WtqysDEzMvJysZrcXRwcnJs0MiAxsbCu83QzWpucm5sbGxpb2lpbm5xdHVveIJ6asrUbmtrcG/NxstrbsvExsjN0WlqaNBta8xpwMzNampscnVwbmq7vMnNbHJxc29zdnN1dHSChoOAe3l16eTX2tni3N7fd+3m7/Z7fnmAiZCblXt+fnvw5Xbk5PB9e3nr33PV19+A29rY2ebk53uGhYF7eefreuLY4HNwbW9zz7jBy9HMbdbV2M7bc3J1c+Pf3NVy3uJzdXR5gXvrdOqAfHp6e4F8f32CfoH38vXl7vj59oODgvp/e+58fXt33HPeb3J1fJ6UdnV7c3JwcXd4c2zZbtJsb2zTcHN1dHLUbXDXcXDSz3KAdXLh3d7gy7zG1NvQx8nS1eXgeYiIf3p4fISHgXXjdXqDfHp3dNbR3tPCy21vdG9tcXFvcXDV1NPf2nBzdnN2dn57dHLY0G9zcXfb1tx+lOt6lHd1dnPf4OTodnjg531139zh5tzic3R2d3J0c3N0dHTj4Xl5dHhxc3Z6doKDe3iA3Nje5+bd53t7eXiEiXx3fXuCkpiCeNhyd3Ny39Dg7n3z7+Xs8eroes/XzdLgc3p5cnDY2XF2dnPndXV1cXZ/dNnVxsXW1c7MysfG1s9xc3V2bNHRwrvGa2hmyGjIvsfQ1djZct7k3MvR1NPGytvU0NdvcXN0dnN0fHN2cnHa0G2AbnNwysPQzm7GaWlmw77Du8PMzNDYznXecHJ1c23V0NLPz8/W19jjdN7T0NPMw9XafImEgHLZ1dLUwcjMyc/OzdrXcd/k2tLedOF05eLc3dPgctza2s/M3NradHXc2NTQ39rU3s/O3+Paz8ls19Tb3NLBzdHAx8K438XLzMjD2HKAe3pwbWpsbmm9xMHKaM/KaGppysO5t728v8XDwsjKzMrR0MrN2OHW1tbf4NrZ3N/k3NTQ5d/b39bkenx6fHnk0NB1gXPT2N10d9TDw7nDztLW1tPV29PR1NfS0dPb1NPTzsa9zNds0NHN1m3NytjXz8/M1HDd4d7PwMPLbNhu2M6A2nBw3nBvcHJ1cXBsbHBybXBydG9ucHB0fIiYkH9ta2tqcG5rbm9xe46Kd257b3BqZ21ua3Fwcnd0b3Fzam9wb3FzcHF1c29wbm1s1tlubNNscm1tbG3WbG3UzsjPbG5zbdDU1dFudnV2c29rb2zW09XSb21sbnF1cG1oam9qaG2AaMzRa2xubWtsb3NzdndxeHBsb3N4edzUzNFxdmxxc290dHHabWzadnp9gH96hJNdXntx2nB3cXRvct9x2dTT1+fj19jd29XT3N3Yd3Xh29ze1d7ldebo6Hbj3dzb33PjdOrg4tzU4Hd7hpN333Fx3NXc2tXV29Zxc3Z0eHhw2tWA0tDKwtbZ2nN3eXRxcG5tdG9vdXJzeX13f4Z/c9fgdXd4fHri3eZ6fOfh5OXq63Z4efN/fOl23OzqdXV5gIN7dnTOzNXWcXV0dXF0dnBtbWx2eHd1b29t1dDFysvT0dbTbNvZ3uJyc25zeoGLhm5ycmzWz2vU1NpzcW/c1G3LzdWAzc/Qztjc4nN7eXx7eevwfOng5Xd4eHl74s3Y4+/qeObf5uDuenh7eO7o5uB36O16enl9gHfoded8d3Z4d4B8dnJ4cXPZ1tTM2t3a13N2d95wa9VxcG9v1G3UbW92i8CneXR7dHJucXl4dHDcb9hvdXTjdnd4eHjidXjndXLb1nKAdHPm5eXk2tXi7ezi2dfb4PDyf4uPhoF9f4R+eXHdcnF9d3dzctfR1srCzW9wcXBvcHFwcnHb2dPf4XR3d3R4d3x2b3Dbz25wcnrd1959kOR5jnJwb3Dh4N3ecHHP2HZw19LU1s7WbW1vbmppZmhqaWvNyGhoZmhhYmRpZ21qZGSAu7S3wcK9x2xvb256gXdwd3V1f4Z2cMdscm1qzcjR0m3RzcbN0tDPbLrCtrzNbG5rZ2jJyWdrbGnYcXJzcXd+csjOyMnY19PU0dHO2tR1eXt6b9fd1M7Wcm9u1nHd0NLV29/ddN/e3M/V1tjOytLM095wcXBvc3N1fHR5eXnr23OAdHd33tbg2nPTbG1tz8jSz9TZ3N7e2HvndHR1d3fn4OPj4Nvh5unveOfe2M3Szd3heYF+gXbj3tnXxc7T09rUzNvddeTm3NfjdeFy3dfX39ffct3c3tTQ2tLWc3Xh4NvV3tzW3dfU4ejn39p15eHo6OLS3eLS19TM8tbW1tLO3nSAfXpvbGppbGvG0M3dcd7dc3Nz5OXb1dvb3t/g4uPc3t3l49ra4+rh39zk4djW1NfY1M7L3tfP0c/geHx6e3nn09N2gnbc4OV4e9/R08nS19jc3t3f5eHl5ufj4OPs5eHf2dXO2uJy3uHb33HX2OPi3uDa43Th4dvLxtDXb9px3teA+oCA/4KEhoqNiIWAgoeHgoWLj4qHio2QmKa5r5uFhIeEiYeEiI6SnbOtlY2kkIyFgomKiZCLjpWRi5CSg4mLi4+PioyQjIaGhoeC/P2Egv6BiISEgoL/goT+8+71gIKHgvf4+POBiomKh4OBhoH//Pz1gICChYmRi4eChYqHg4KAgPj6goWIh4WFiIqJjI6HkIeBhIaLjv/z7PiGi4CFhoCHh4D7gYD9hYuQlJOMnMGHgpaC+oGIg4aBgv6A+PDt7f367Orr7evp8PLvg4D47fL47PX/gfn3/oX89PX0/IP9gP/19vXw9oKPrcSR/4KB8+Tq7u7x9vGBhYiEh4eA+fWA9PLv7P/++4OIi4aCgoKAh4CAhoSHjJCHkZuSgfL/hIKChoX08PiDhvrx8fD5/4CCgf6DgvqB7fr6gIKHj5OKhIPq6fX3gISDhoGEhICBgIGOkY+MhYWC/Pbq7evx7fX5gvr19/2AhICIkJikm4CGhoD99oD79vyFhIL+9YDv8fyA+vr2+Pz4/oCIhYWBgPb9g/Xt+YKBgIaJ+uDs9vz1gPfz/PH6goGDgf34+fWC+/qAgoGFh4H+gP2HgYGEhI2JhYGHgYL28fDj9Pr6/IaIh/uCgPyFhISD+oL+goOLmMW5j4uRhoSDh46LiIH+gPaAiIX+hoiIhoT5gYX+g4L59oSAh4P+/Pr85Nnl8PHp4eDl6fv/iJaZjo2Li5GRj4H9hIWTjouGhf70+PDm8YGBhIKBg4GChYL69/H7+YCEh4GFh5CIgYL874CEgYf38/2OpfqDpIeCgYD9+vf5goXz94eB+vT4/PL5goSFhoWGgYKDg4X/+YSEgoeAg4aLhY2LhIOA79/g8fft8oKGhoOQl4qEjYyQn6WOhO6BiYOC/PP8/YP8+Ov0+vb3gN3s5Oj5gYaGgoDz9ICGh4D9hIWGgoeNgObp4OHy7Orq4+Xk8+yEiouKgfn/9/H/hYKA+oT/7/Hz+///hP3//Ovv9Pnw6/Tp6viAgYODhYKFj4aKioX99IGAhIqJ+/P/+4j9goKA8un07fHv7/T67YX/goOEhID69vb08e3w8fP9gPPu5dnf3OjngY6HiID57+rq1t/j4Ojl4fT3g/3/9e38gvuB+/f1+vL+gPr19eno9+7xgoP29O/q9fLq9Orm9Pn58/GC++/y8+7a4ufW4d3W/93f4+Ll+4KAjY2FhICAhILu9Oz6gv76gYCB/vnu6/Py9fn4+fv1+PT9/PLw9/7x8fP++O7u7fL49ezk/fr08+37iIqHiYb+6O2Ej4P19fqEhvXq59jh6u/y7+3y/Pj28vTy8PX78vHw6+fg7/uA+fz3/IDw8Pz38vDt+YL9//no3+jwgP2A/vYEfH19fMd9BXx8fX18hn0DfH19hHyEfYR8iX2EfI99gnyTfYR8iX0EfH19fIh9BX5+fX18hn0CfH2PfIJ9h3wFfXx8fH2FfAN9fH2GfIV9A3x9fYh8h32JfJV9gnyFfQV8fHx9fYZ8C319fXx9fXx9fHx8iH2EfJJ9iXwBfYR8jH0MfHx9fHx8fX19fHx9inyGfQZ8fH18fHyFfYZ8AX2FfIR9hHwDfXx8hn0DfH18jH2IfAd9fX18fX18hH0DfH18kX0HfH18fX19fIV9C3x9fXx9fXx8fX19kHyLfQF8h32GfIp9hXyKfYJ8hH0HfHx8fX19foV9hHwGfX18fH19hnyLfYJ8jX2HfI99AXyEfYR8AX2HfAF9hXyFfYJ8hH0BfId9jXyFfYV8BX19fXx9h3wBfY18jH2CfIR9hHwFfXx9fX2KfAJ9fIV9inwBfYh8hX2NfAF9hXwDfXx9hnwBfYh8gn2PfAF9k3yJfYR8Bn18fH19fad8hX0LfHx8fX19fHx8fX2cfAF9hHwBfYh8AX2HfAV9fH18fAICBACAaWZqbGppam5vcG9ubnVsa25zdHBzbG9xeXJuc3d2bGhubWx2bGtzd3R3cWdmb3Fwa291cWtvaWlrcXZ2c2lnbWzTbGptcMvAaXN6dXRybm3Pzc3Exm7d09LK13Z2bczSeHt0cHhwbW9raWpq19ds1svObG5xbM1qa25yaszNxb6AwWbNZcRoamXJZ3Bwb2xram3Lc3Pe3tjYc3Vxb3V0c3Jxc23NycNvgHduxsjN0NJ0d29tcNnVxcnT1HFy2Xd0c9bU0NNx2sjY3NfZ2NHJx8nNwLS4zm9w3tPZ2nHTbHZ3fNXCwdrLx8vJvM7ac3J30L2/0tLWa83OwsbQ18u+vLuAw8fAyGrPx7y7xs1scnLSx81ub2xvcm9xdnN1dXBweX1/d3VydnmGjoe65nx9fnl8eXl2dHR5d3p+eXPbbXBv0Nlyb9vZ0c9yeHt5eHFqx8nPampvcW5zdnLXcXF6goF+dnVx2N5v18zDzuN2cN5y1tvf4HpwimmZj4h7enRv2dKAac7PcHZ1z2ptb2942N7Tc9526NzX39fT1tjW1tzX3NrUz83NyL3AubrK23V8d97d3dt0dOHldndzcbGYyHji4Xrp5OR5gHnoeNl27Oveztfj4n+NjYeGe3nh43enfLeTgnp5gId3xrrIa21xbs9qdXTbbHV9f3F0dnZ0cW1sdnWAc3l8cM5ybtTZy8rX2Nlwz9HKx8zU1MzX0MXFyL3Exb+/wMfFz9TCvcbDy8jLx9XXzc3X23RsbW1wa9Js1G90cXDheHt6fHh0dnd8fX55eYacioWAd+J0g4GIi42GgHx9en57fHx8e3588evp5eN1fHx76+bn5N3h4N7reXRxc+GAfeLm6Xl52+Xrdnrn53p9fHn0jIfl0dV3f39/gIiCcnPN0M10c3VudXvleHl2cHLm49915nNyhH13cXWBenNyb9vY2nJzbtfDrsLS19HLwcVudG3V3XTh083ZxshxcnBubNXObHByb2vPx8bDzM7Gw7nI0MrMyc/P0NXW2thuc3CA19XacHqMgIKJiW97eoJ8cXNv13Db0dnS1m7U0+Fyd3Hj2s/R2eXj3enf0c3Mw8pucW3CzcXDztXV393Z1Mnc2M7SydfX19XV1s7S1NPNzMbG021wctvW1NvZyMfCz9hxcG51cGttacrK125s1M6/wcHR1dxxddVv085qaM7HaWqAbHBucMrK03B9f23GysdkZGbEw8XOztHY09rcdHR36PDsd93W49/Q3dPf1cfZ5OXV2Nzn5+nWzczXedfY5d5ubNHSxL7B0GttdnV3dGzKw7q/y8jYcnPf39xwcG3P1dnTbW9v0WptcG3KyW5ybWtpa2lrbGhqbGpraMdlZGxxb2yAbGlrbWloaWtrbGppa3BpbXNyb2xwaW1weHJtcHRxaGhwc211bGtyd3V4c2pmamxvb3J1cWtvaWtrb3N1c2xobm3WbWpudNbKbHJ0cW9ua2zW0M7Iym7X1NjQ2XFycNrfe314d35zcnZ0cnJx4N5x39PVcHJzb9VtcHR3cNnUy9CA0G7eb9Rwcm3YbXBwcW9vbW/NcnDV19fedHFvbnV1d3Z2dXHk49V1hH965ubn6eh8f3d2eOzs3N3n5Xl77X96fOnp6u5/9Nzr7enr7eXg4uXp2svK23V47uXv6XjjdX17g+rb2e3c2drZ0N3ddHV64dbP1tbdcdzXztXe5NnRzsyA0tjS1m/Z2NPO1dpvc3PW0tRvbWpwc3Fxc25wcG9vd3t5cXBvdHZ+iJHN+np3eHN2dnRxcXN5eXh6fX3wd3d35fF+fPLz8fCAg4OAfnhx0dDXbmxwcW1xd3TbcW5zeHZ1bW1rz9Nr1srCytlzc+Nx1tbY3XlvjHmrkoV2c25qzciAZ87ScXZ302psbWly1tzPcd515drW3Nrb3t7g4uPg5ubf19nc1MvQxcXW6Xt/fOfo6+V5eezwfX98e72Rt3np5Xnm4ed3fXjndtd47Ozg0dfe3XmFhoB9c3DY2XCujLKIfnl3fIJzz8fRb3B0c+F2fnvlcniBg3N2eHp4dnZ1e3uAeX6BeeB2c9ze2dzi395z1tfR0NLd5dnf2dHS1s3Rz8/R1Nza4dzQ0NPM0s/SzNPRzs/T0G9qbGdnZcZmyWdsa2jPbW9wcmtnaGptbG9ta3aJd3JvacVmcm5yc3RzcG1sam5tcW9tbnBu1szS0c9ubmxu0tHRzMvW2tLac3JwcNiAdtja23Nz1N7ec3bg5Xl8eXfwiYTn2dt3fXx8f4eDdHPP2td4cW5tbWzQbWxubWzR1dhp025ue3Vzb3J8dG5vbtrY2nJ0ct7Kv9Hc4drSzdN2enTb33Tg19TdysxwcnJ0c+HidnZ2cm/b1c/P3NvV0cfW3trW1t3b2d/f39txd3OA2tjfcn+Zj42Uk3J4eYKAdXV16Xrs4vHs8Hvs7PB7f3rx5dvb5fHx6+jh29fVztV0d3PN1dLL0drV3NnW1MfX087QwMzL0NXZ287O1NbQzMzQ2G9yc9zY1NnVz87C0ttxcHF6c25wbtLS3W9s2t7V0c3Y1d1wcthw2NJsbdXObW+AcXRyddrb5HqHhnPU3+BvbW7T09fc1t3o4t7ceHh45ObectjS4N3P08Pb4dXg3d/W3Obo5e/e2NfffOHn8Oh1dePl3NnT4nR1foCBfHbi2tLT3NXfdXXn7Ol2eXnh4N/hdXVy129wdHHY23N0bm9sb2xvcWxwbmpoZcdoaGxubWyAioaIh4SFhomLjImGiI+HiI6RjomLhImOmJCKkZWPhISNkIqVi4qTmJOUj4eFiouMiYuRjIaJg4WFiY+Sj4WAhoP7gYCEifzwhY2PjYyGgIH79fjt64D99fjw/oaFgfb+kJOMiZOGgYSCgoKA//yA/vDzgoiLhfyBhIWLhP/88/KA84D+gfyChYD9gomKiYaEgILyh4b+//v6g4OBgouIiISChID57+aCkoqE+PT2+v6Ki4KAgfz96ej2+IKD94WEgvHv7/mA+OT3+fr9+fLw9Pn56Nfa8IGC//P39oT8gouJjvrk4v7w8PLu4fDygoeM++zp8fP8gPf57O/4//Lq6OWA7PTt9oL9+e3n8P2EiYj88vaDg4GGioaGiYaGhIKDi46Ri4uHi46XmYi77YiOj4mLiomFgoSKiIaJiob9gIOC9P6EgPn6/f+LkJGPjoeB8/P6goKFhoGFiof/hYWLk5GPhYSB9/yA++/g5v6Ggv6A6vX5+4qAo5HUspeHiISC/fmAgfn1gomJ+YGBgISM/PzsgPaB/fTw+/z2+vr08ffx9ffu5uzw5Nbc19Xk+IOHgvPy+/eAgfn9hYiCgsCSv4P+9YH39PuCiYH4gOyC/f705Oz5+4qWmI6QiIX6+4PXseGkk4yKj5WG7+bygYSHhf+BiYf/gYyTkoSHhoeGhIKAiYmAiI+UivyJhfb87+n1/PuC8e/o5u719+r28OXl6t7i4+Tl5u7o7fXn5fLt9e7w6/f48/L4/IaBhYKGg/6B+4OHhYL9hYuNj4mFhISIio+MipSpmJKOhv2AkI2Rk5OPi4aFgYeEhIOGhoaA/PLz8fKDh4OB+Pf58+33+/P9hIOCgvqAiPn7+YGF9v79gYT4/4aHhID/kY777fGDiIWGipaSg4Tx+/uMhIKBhob/iIqHg4L8/P+A/YGBk46IgYWPhYGGgfn5/YWHgf7n1Oj5/ffz6OyDiYP5/4P98ez45emBhIeFgf3/hYWFhID58u7s+Pjw693w+fX08fn6+Pz7/fqAhoOA/vb4gZW4qKGkoIGKiI2JgYSB+4H77vXw94Dz8vuDhoD/8ebs+v///v/38uvn4O6DhYDn8Ovm7vj0/vn29OX17+Lx6+rr7vDy9u/u8O/q5uTo+IKEhPv6+Pr38O/k7/yFg4WRioSFgPby/oKA/fnq3t3z9fuAhPmB9/SBgP32gYGAgYOChfPy/Yicn4f1+/yBg4T07vD07/X/+fXzg4OC9fn9gfHr+fno7Nv0+e36+Pnt8vj68fvu6ufxh/T2//mAgPXz6ejo+YCBi4yNiIDw5tvg8Or2gYD4/v+Cg4L09vn9g4OC+YCChoP3/YeKh4iDhoeIiIKHiIeEgPqCgYeLiYjBfQF8hH2CfIh9hXwBfYV8BX19fXx8jH0GfHx9fHx8hH0BfIV9hXwIfXx9fH19fXyIfQN8fX2EfIt9g3yEfYV8hX2GfAZ9fXx9fX2EfAF9kHyCfYR8An18hH2LfIN9hnwBfY58AX2GfAZ9fX18fHyXfYJ+kX0IfH19fXx8fX2EfId9g3yIfQF8iX0DfHx9hXwEfX18fYR8Bn19fX5+foV9CXx8fXx8fX19fIV9Bnx8fH18fZl8g32EfAR9fXx8hX0Qfn19fHx9fHx8fX19fH18fYd8h30FfHx9fX6IfYN8hH0FfH19fXySfQN8fX2HfAF9pXyGfQN8fXyEfQF8k30BfJN9hXyEfYl8hH0OfH18fHx9fXx8fH19fHyEfQZ8fX18fHyJfYN8hn0BfIV9BXx8fH18jH0GfHx8fX19inwGfX19fHx9hnyFfYJ8hX2VfAZ9fX18fHyPfQJ8fYV8B318fHx9fX2PfIN9oHyDfYp8iH0FfHx8fX2IfAp9fXx9fHx9fXx8hn2DfIR9Bnx8fH19fYp8B319fXx8fH2XfAF9hHyCfYZ8h32HfAh9fXx8fH19fYR8BH19fXyEfYJ8j30BfIZ9AgIEAIBsZmpwd3Nycm9sbHRzcnJwb25wb3B0dG9zc3Ju2G9xdXh3dGtuem5tbtPb2c/O13RydW9satPUbnGUmK1ua3Jrb2rJaGxscHJxcXZ2d3TW1XJydnJwb3F0ctnRydp3cnLg1tbUzM3Z3eHpeObg38+8v7fOb9Pcz8DKx8fFyMnDu4Cps87PzMXOvMPG09LP1tLQzc3GztZ11cTGydHQ1XBzceHW2nXc19LQztXb23d43nTk4dnh4tzh5NPX29Xb227cc3TX1dTSxszJbt/TcHhzdGxpy8pycNJz3WxxdWzOzdLJyMPExcKusr+/w9HNx8nQaGlraXBzc9LS0GnI1nBzbIDXccZsdHFybnB2c2tsenpzbGtrbm1vbnBsb21nzXJu0HFsdXZzbs/IfH98foN/zspyctp3e3t6e3p0cnVLSk6VgXvednV3hZWRIUpBMhcuU6mOgnl2cndzdHZwbc3ObW1q1W5wyWvGu8xwcdHLaG15bcnFzM1s0XZ0b31ucWvLzIDLz27HbHd1cG9xbdHKzdTOa8zR1t/T43d3eN7j4NTR0dXl4ODt6eLjeXrleXbid3x/7Xx46Xx/gdvdiJp54+TfdOLN0uPX3tnU3XJvcXR5dHN3d3FzcG9wdnJxbnmJin57ccjOcG9udeHPx9LMztFwcNFqbGxxbmzQzcTL1HB1dYB9eXt8e3Zy1nBv0HNydHTg23Vuas3P0HBqx87NZ8bBv8nKxMNxa8/J0sjLydFv0Md0dldPa5aCdNBub2vU1mVpatDWa2xt1dVvcd5u08bLc3pxcHJtz8xw4t5/fnt2cnl2dHZ65nRyct1y4Hh1c3rf2eTv6eHk5ezb1OPo8vfo7YDg3ufk8O/r6e3v5OzqdtrZctjY2tLO2W9ubnF2doDadWzOamvW2XB5fnFxd3l0c2/af351cHTj3OLZ0snG0M3TzMTL2dXO0eJ4dtLFusB3eXl7e3LV2W9wdW3H0G/QbnLXy8zO1NHO0tHXzMfNzrjCycbGzNDN1Wxvzc5sbXBxdIB5e4F6fH9u0NPY1dd+dNpzeYB8dchqzKmyxtNudXp2b9DMxLu7ucBuc3Nvb3Rv2G5vdnF2eXV1cm5qysbCv9TQy2xqzb6ytcy/x8HKysrGxcfGzc3BxsLOds3Ky8W+uMhsvLS8usJyy626goJ2c3yJhIBz3N3YdHtwzMbIzNTUyIC2tra3ysDJyMjQx8/My9DY1djRwc/P0Oh3fefn4tzf6ODc0szUy9Hh6XXg3t1x0cptanWEpaeAaWlqyMLFxcbB0c3ObnJ+eXFvbHFpaG7Ma2jGycC/uMDKyc3Pz8fJxMxs1NBpZ23MbWvIyW1tbWyTkpu2gXdydXJwbtbZ1NDYb4Btam10eHNzcm9ubXJvb3BtcXFubW9xcW90c3Jv2XBycnV1dHFtd21rbdXZ1dDU129xd3NxcODdbnidhZtycnlwc3XjdHZ0d3h1cnR6fnjf4HZ4eXVzcnV5eOXk5uZ1dnPf2tvZ1tje4N7gctvj5N3f4tDhduHo3NHg4eLg5OTb0oDF1ebf4N3hz9bY3t3f5tzg4NvR2uF43s/S1uDd3HV4dOPe33Pi5Nza19ve4Hl87Xvw7ejs6+Pq8eTi4t/t7HbreXfl6Ofo3t3Zdu3leX16f3l57OR6eeqA9Xh9gnno4ufh5Nrh4uHNztfU2eXd2NzebW5vbnV5dtfY0GrT3290cIDUbL5obm9wa2twcGtpbm9tam1sbW1tamxucXBr0XNy2HRtc3Z1cdXVnZyEfoOAzslyc9dwb3R5dnJubnp+cVaMe3bZc3N2hZrDaYZiUl9zZragk4qHgYR/fXx1duDdcXNz4W9z1XDPwc5vb9babnF4cdLP2Nt24358eYh3eXXb3oDZ2XDPb3l3dXd1c9/W1dTTbM7X2ePY4HZ4eeTp6dzZ3eHt8PDy7/DpfX/te3rtfYGA7Xp45Xl7f9jXlrJ/6Ovmd+fY4vHm8e3q8Xp4eXuAe3p9fnl7d3d3fHl7dn6IiIB7c9LUc3d1eeXe3evk5uh4d+R2e3l9enfl4NHU3HR2c4B6dHR3eXRx1nNz1HV0dXPc13V0cN3h4Xl14eTkdufg19na19t8c+fj6ODh2t502tV4fXNueJKAddNub2rRzGpsa9HYbWxr0dJtbNNqyr7Ga3FtbGxoycVq1c1zd3ZxbHBvbm5y121sbNNv2HBub3fV0ePh3Nja0s3ExdDX3eLa34DY19/a4+bk5ePg2t/jeODbdN3c3NrX33FzcnByd3/YdG3SaGrW2nB2eG5vcHFtbmzZe393c3Ta1d7V1NHP1trh29TX4NvS1d92edjIvcR0eH59eXTa3XFzeXPW3HHac3PXztfY2NjW3d/h09DX4t3d2Njg4t/Z5XV44eV1dXh5e4CBfoN8f4Fy1t3f2+GDeu94eoF8eth15sHM1+F3eXx6deDh1srN09F3fHt4dHRv12tqcW5zdHJ1dHBs0tPQytvX1nBu18nBwdPGzMjPysrNzc3LztDKzc3be9jX2dXOytt3zcPNzNV4x7fYp417dnyHhYJ14+LacHZz2NLP1uPh2IDM0M3L3NHa0NHZzNLU0c/T1NjUwc3FxuRyc9rg3tnW2eDd08rUztXl7Hns7et35dx2dICh4uegdXR25d/Z2ePU3d7ifYCIgXt5dHhzc3bcdHDc4NTSys3QzdDS08/V09hw2thuam7ObWzT1XFvcGyHe4y4hXtzc3BvbdDV1tnfb4CEgYSLkYyMi4aEhImFhISBhoaEgIGJiYSIiIeC/IKGh4uLiYaEj4KBg/v++fLz+ISGjIiDgf/8gZPWvseFgoqBiIf+hImGio2IhoyOko3+/IaKjIWAgIOFhfz5/f2DhIH++fn17e32+fv/gfb59+fe4dnxgfX97t7v7u3u9Pbr4oDT5Pv59vD75ezu9/r1+e/z9PPm7fmG+/Dt8fv4+oSHgf/4+YLx8Oro4ePo7YGC94H57uvy9u72/vX1+PD4/ID8goP4+Pby4+PkgPz0gYaDioOA9e+EhPuF/4CFioD19vvu6+Lq8O/U2enp8P718fX9gICCgImNifn//YD1/oOMhoD/h/GEjYuLh4iPjoaFkI+LhYaGiYeJiIiGioiC/4yI/4mFjYuIhPv0lpWQkZWR8uqChPmEh4qNi4iCgpWjppXYlov/h4eLnLHnkbSxp6Tg9c+voJWRjJCLi4qDhPr3gYSB/oCE9IDu3fGBg/v4gIKLgfDv9faD/IuJh5mIioP4+4D2+IDzhZGOiYmFgfnu9Pz5gfX19f7w+oOEhPX59eHf4eP1+vb6+PTtgof+hIH4hYuI/YaE+4WIjOnmmrSH+Pj0gP7j6vzy/vv1/YKAhIWJhISIiYSFgYODiYKBgo6fn4+LgunsgYSCg//z6/r19/6FgvWCh4OIiIL39Ojw/ISIhYCQjI2Qj4iF/YaG+ouHh4X9/oiDgfv8/IaC+f38gfz48PX38veLgv/6//P18PaB+/OKjoKEjKiViPWAg4H+/ICCgPb/g4OC/v+EhP6B+OnwhYuFhIeC9O+B/fSLkZGLhYmEgIKH/4KEgvuF/IaDgYfx7fz9+PL06+jb2unt7/bt7YDl5O/x+/37+fr47/H5hPz0g/r49/Tv+YGBgISGiJP7iYL/gYH/+4ONkIKBh4qEgoL+j5GHgoX89P708O7t8/b67uXq8+vi5/SAgu/i1t6GipCPioD4/4OFjIPz+4L3goT67vP69vLw9/X47Ofv+erw8fH1+fry/YCC8/aChoaGjYCTkZOKiYqA9/39+P6ShPuAhZOLhu2C/dLd7vmEiZCJgfj57uLq7OaDiYyIhIeD/oCAh4SKjIiLiYKA9/Dr4vv7+IKC/u/h4/zy+u/59vbz8/X1//rs7eb6jfTy9e7l3vCD4NTf3eiE3MnenIuEgYWSkY6A+fvwgIaA7+jl6vT88oDi5OTq+Ov37uzx5ezr6ejt7/Tv2uXh4/+Ag/T9+/Xx9Pbv49zj3eb0/oL7/v2C+O+Bgo2ezOCogoKD/PPx8fru+vb5iIyZkouIhIaCg4f+hYD19+3t7PP48PDy9vH19vyC/fyBgIX9iIH7/IWDg4CnpbXjnI6GiYeHhf3++fr/g5x9AXyMfYZ8hn0GfHx9fn9+h30BfIt9gnyJfYR8g32KfAF9iHwBfaF8AX2HfAd9fX18fHx9iHwEfX18fY58BH18fX2HfAN9fHyGfQd8fH19fH18hH2TfId9DHx8fH18fH19fXx9fJl9BHx9fXyGfYJ8hn0FfHx9fXyJfQd+f399fX18hn0Hf3+AgoWFgYx9EXx8fX19fH19fH18fHx9fXx8hH2EfAJ9fId9hHwCfXyHfYV8AX2GfIN9jnwZfX18fX18fX19fH19fH19fXx8fX19fHx8fYl8mH2CfIR9h3wDfX18hn2FfIp9BHx9fXyEfQ58fH19fXx8fH19fHx8fYd8gn2HfCJ9fHx9fX5/fn19fXx9fX18fH19fXx8fX19fHx9fXx9fHx8hn0FfHx9fHyKfQd8fX19fH18hH2efAR9fHx9hnyHfQh8fX18fX18fIp9AXyFfZJ8gn2EfIZ9gnyEfQZ8fH18fX2XfAR9fXx8jH2FfAN9fXyFfQJ8fYV8hX2HfId9AXyLfYd8gn2VfAF9h3wBfYV8BX19fn5+iH0GfHx8fX19n3yCfY98B318fHx9fHyKfYl8i30DfH19j3wLfXx8fX19fH19fHyFfYJ+iH2FfAF9AgIEAIBwZGhiwGZpbWbEZWrXbW9ybG9ub29tb2rMamzOb3ZzcXRwb9TT1djVytTY1sjHzc1ubnJ0b2/S1XahYYp7fHN5dHR2cm9s125uzm9scnJ2dW5ubW5sac3TbdLO09XV3HV04HTe3t3V5YJ/dtvV3dPZ5Xroc9PY4nrj5OTk3djh64B9etjX2ePe1MfJ0MzK13FzdXV0cW3T3nZ5d3t85HZ1dHZ6fHh3d3V4eON3e4GBeHV2cHB12Nt3e3Vw1NjT1dl0zcbZb2xzy8nJxr9ycmzUzsjMxL/DvsjObW5pxsXEwb2/s7W9xcbOz8zV1HNr0IiKd2trb2pudHRtbG3NxMPKbIBvcHLZ09R3dm1pbXV5enty125vcXV4d3R6gId1fICChHdzenV2ent+et7fdH5/d+B1c3N0c3Bxb25rdXpxcmTJ1MTJ09BrbXKCjJaLhYKCgn53cWxva2hovMS7ubvDz8XDx8S5ycHIxMnK0cG8vWdsZ2nQyMHCacK7wcnLv8ppa4DRzdPUbtLY0tDV1svM0nBxeXbcc3Tg2djHxsHU0szY43jl4uXi7t3rgvTyfPGIgefn5eLw9e6Kg+7j6+jl2uJxd4uTh4d/fHnldnN0dN7T0XDddnV3dN9wc3JycG5rac7R0crSw2nMv9bO0tvL0NbM1dLW1d3ZcHl01Nnc19R27VXX4nd2b3NycW7c29ZucW5ty255f3PKztDLu7vSz87Fz2fJZ2rGZ2xnbmtoZWhoacvMZrxqd3RpaWhrw7GyumdpwGy/t7VlaG1sdHOfs3tqbW1vdXJqhGuAymdoa8twcnt9dtxyeHd1defren7f6eHo7eXj3tXq1sfS4OTd5vDs6+N149zU6YV81d/m43h4eHRycnNw3tridXFvaWg+g9HR4XJyx21wcXt3fnJ0cnFxdHLZ1tbbd37o6tbS3OPfeXhzdXR8cd1vytVzdHPm4tbSzdzda8zLa9WA1NRqaLW4ycjMyWfOz8ZqaWbNyWXJx8ttzsbBxs7Ev8fRbW/bcXZ8dn5tRk9ud8i/yWtvacTP0tHPx8jXcnFvbmx4dGm1wMhtcNHVxcTP1nRuz8nRbGtu09jPzGZvd3xywr9kZmrOx2pwdW/Hu8VlxWhpY7/ByGZnZWxua72/xsOAyMzMcGtv0s7DwMnUycjO0sXBzW3Qz8XK1czSzNLY0crC3NDT0cfCysS+wby7zN/nzNvSxsfN3Ofa7OfQdXbgds7O2XPT0cfQ1tVr0r+2wsO5tcDJy2pt0GbLwHBsZ3N1amfKvbvEzm91fHHP083T1MrCzcvQ08vK08rMz9nMxMwnyMpv1NHOyM/PatDU1Gxxb21zbWpsa25tcXHSbNRrbG9saGpra3R3gHJrcGzQampva9Nra9ZrbnNzcnBzc3FzcNtvcdlyeHd2enZ03d7f3tjV4NzczcvY2XFxdHZwcNnYhduEmnp4dHp2dXVycnHecXPceXV6eHx8dnh5fHt36Ot25+Tm4+HofXrmd+Dc293xgHx54uDn2uDld+Jx2OXmfObi4eDk4+XugHx43Nnc6unh1NHb3N3ld3d4dnZ0cNjhdXp2dnXecnJ0dXd3c3JzcnFx23BydndxbnJxcnbf4HR5d3Tl5+Pn6Hzf3fB8eH3l7PDw5YmOhPjx6+vr7vHo8vWBgXzv7ujj2tzW2t3l6vT28uznfnnpjIZ4enV2c3h5dXNzctvT0tVtgHBvc9vZ1HNzcG1tcXNzdG/Ua2pucXJycHV5hHN3d3ZzaWlybW5ucHJuzdBudnlz125ub3Jyb3Fub292eXyKctXbyc/VznF1dIeet62aiIWHhH53dHh2dnbX4tvY2ODu6ufp69rm2N7g6eXk1dbYc3l0devi4eJ22dfd3+DY33N4gOfc4eR03+Db3uDi2tXccnN4deFycd7b39HS0eHe1OHqeevm6uPn1+d86+l36YB53uLi3Orx7YeA8ezy8PTl7Hh7jpmPjIN/eeN2dXd35uLgd+l7e3x67nZ3dHZ3dXR05Ofo5ezgd+rX4d3f5dfa4tre4OPl7up3e3bd4OLc2HThgNPddHRzdHN1dN3Z0m1xbmzMcHl8cMzT2drTydja29Xfc+h1d+N1eHV6eXh1d3R15eRz4X2Minl6eXfaz8rSc3Xde9nJxW9ydXR5eaa3f251eHN5eHJzcG1sz2tsbMxxcHd3cdZvcnBubtjac3bT3djk5t7e3NXf0MPP2t7Z3N/hgN/bcdjPxtZ7c8zU1dh1c3JvcHBxbtbT3nJucXO1haXUydhwcNBwcXN6dXpvcW5tcXFw2tTW3XqA7OnZ1eDl3HVzcHFyenDebcjXcnNx3tbKycfW2nDb2nLd3N5vcMvW5Nzc23Da4NVvc3Li22/Z0Np23tnU1uHa1NTWcnXccXd9gHiPq3mbinjJxc9udXDU2dzZ29bY4HNzcnFxgH1wv8vVcnPY2srHzdFwbdHQ0Wpsb9HYzMtrdHl9dcfBamps0cpsdHlyz8PPb9ZucW3X2OR4fXx+f33c3OLe3ePofXh65eTc2uHt4uDi5d/f6Hvo4Nza4djf197k2tnU7dzk5NvSgNTPyMfEwdLh5snTycbFydXd1OTezHR13XXP2N512dnQ2ODndN3RztbVz8rQ1t1ydOBx5dl6eHR+gHRv29HM2eh5foN44+LZ2N3V0t3W2dzW0NjQ1dfg0tHY0dRx0tTW0NnZb9XW121wb25zb2xubW9tcnLUatNubm5ra25sbHJ0gJGHiIL+goSJgvqCg/6AhIiGh4aEhISHg/2ChPmBiIaGioeE+/3+9/Hv+/v75+Lu8ICAhIeAgPj5luuSs4iHg42Ih4eEg4L/gYH0hYKJiY2IgYOChIKA+vqC+/Pz8vH5hYL4gPLx7uv+iYiE9e/27vb+hf+A7/n9h/z6+Pn28vL9gIaC6+ns+vvz5+Pr6+v4goOFg4KDgPf7goSBhYX5gIGDhIWKh4WHhISF/oCCiYuDgIOCgoj9/4aMiIL3//r494Pt6f+DgYfx8vPy6Y2Uhfj49vr39fbt9vyFhYDw7+/r4eXc4ev1+P3//v78iYL8lpOIhoOGgoaIg4CBg/zx8vqDgIOBhf78/omIgoCCh4iGjIT7gICDhomIhImQmomPkZGPgoGKhYaIioyG+fyHkJCJ/4OEhIaFg4SBgoCJiomgivz96O3z9oSFhZy01MGwoJydmpOJg4iFg4Ls9ezp7PL89Pb29+X17fXx/f786+7wgYeBg//08vaB7env8vLr9YCEgP/0/v+C+Pz4+/7/8+/4gYKJhvyCgvjw+efk4PPw5vP6gvv49+/76fuH//uA+YqD7/Lv5/P38IqF+O/1+P/2/oGJnaqfm5CLhfiCgIGD/Pb0gv+FhIeF/4CEgoOEgYGA+fz89f/xgPnj9PL2/u/19u3z8fLw+/eAiYTy9fnz6oH+gO36hYSBhoSDgf3+/YOFhIP0hI+Vh/X9/frv6P39/fb/gP+Chf2Dh4OIhoWAg4CB/v6A/4mYmISGhIX65eT0hoj8i/Tn6ISHh4WPi8HaloGGiYmRjoeHgIGH/YGBgfWDgoqLhP2EhoGBgPj7gobv+/T7/u/t8Ojy39nl8v37/v76gPr3gPry6v+Shunz9/eGhYaDg4OFgPfy/YWChYjxusv16/2GhveGh4OLh42Dh4OAg4OA9fDu8IOK/Pvr5vP784OGgoWFjYH+gejzg4SB/vfs6uf3/YD29YH8//+BgeLr/ff7+4H9/fWAg4D//IH+9f6H/fny9P/58fb4g4T6g4qRgI29/KnasYzq5veFioLy+//9+uzu/IaGg4GBkoyA3+r2hYf9/urs9fmEgff3/oGChvr/+PqBjJOXi+7qgYGC/vWDiY+I9uj1gv2BhIH69f6FhYSMkIr09v7z8/f2h4CB+Pjt5vT77Ory9+3u+ob87+vt8ufs5unp5Obe9uv09OnegOTh297Y2Ov8/OT45t3a4/P97f395YKC+4Xm7PWB6+ja5/P5gPbl3erw5d7s+v6Chf+A//SKiIaTk4GA++zq8/2IjpOF+fvw9Pnu5vbv8fbr5e/q7/L/7Obv7/WC8fHu6/n4gPj9/4CGhYSJg4GFhIWEiYn/gP+EhoiHhomHg4yThH0BfIR9BHx9fXyLfQR8fX18h32NfIZ9BXx8fX1+i30EfH19fIx9A3x8fYZ8BH19fH2FfIN9hnwHfXx9fHx8fYh8gn2MfId9gnyFfQF8jH0BfIp9gnyEfYV8B318fHx9fX2FfIN9inyDfZB8A319fI19hHyEfYN8in0BfJh9gnyEfQF8j32GfJN9lnyEfYR8AX2HfIJ9hHwBfYl8hH0DfH19i3wBfYd8B318fH18fX2HfIJ9h3yJfQF8hH0FfHx8fXyEfQF8iH2GfAF9kHyDfYV8BH18fHyHfYN8hH0BfIR9i3wFfXx9fXyKfQR8fH18h32EfAd9fXx9fHx8lH0FfH19fXyFfQF8hX0EfHx9fZV8AX2EfIJ9hHyIfYN8hX0Ifn18fHx9fXyNfYR8gn2HfId9B3x9fHx9fX2HfAl9fHx9fHx8fX2GfA59fHx8fX19fHx9fHx8fYl8A319fIV9C35/fn19fHx8fX19iHyIfQV8fHx9fYZ8CH19fHx8fX19hHyFfQd8fH19fXx8hH0LfHx8fXx9fX18fHyGfYd8g32NfAF9qHwIfX18fXx8fH2GfAF9inwGfX18fXx8h32FfIR9l3wBfYZ8BH18fHyNfQN8fXyKfQICBACAydJqZszCwszR1GrMy9FwbNTMyca9zHqwsYq4jnpv0GlwysjGx8DE0tDGzMHMw8PQx8bQ13JvbNbZbXPf3W9xc9rVc3Z2fohzxMja3d/az9zkenrWx9Z0d+LW2tvj0c/PznPTztXP1uPc1MzR1d7g3+bl3d/a3erg7HR2cnR26OeA3dnb2915eJPLj3nc4MTAwdZ9eeHd2XR6b3J5enh1e4SKiHfhcHJxdHlwbWttbs3Rc+B0d3Fy125xdnBxeHTX2M3X4nR2dHl+eHh0b9l0cXJzc9h0cXSBenVyd3duz9PPw8XIyczEx8jKxs9qbW+xqd1ub3p4dHBtbmdvbWnNas6Aw8SAe5KooYFxa29sa9NrbnZwdHdzb2p3fHRy3Nbb1s/X2NLgeOnd3+5/fXh27unrh4V44N11eHrt9PF48Hx749rgd9vGxc5zc2lscGhsbmtqb3BswMvUaG9nw8PFZ2nHyMW9wLKzu81u1m1tbMXLzMa9xcXDzG7PcMy9aHBzcdGA0m9x1td1c9pyduTW23N04t14c3h6eH59f3x8gX17d3Hbc3h1cXLO03V7f4B+e3t8eXd98PPz6OvteYGBf/Hhe3p6k353e3PMytHP2W3a0s/ZdH9xeXDWb25sbG5t09PKyW12fHp6dG5tcNHBwXJ6b2/PwdBvcn17fnV8bdNwb9aA0NhxbXHS1m3GzsvTx8vI0c/Nz8nHb8ttzWtsb2lnZ2pmxm5lZMZowMFoycnAv8DLqrPOxsC7xcxqzMjHwLXAt66rscK/Z8e7wMe8xcrUzbnEwbmzvW9uZmhpZ2hqx8bNd3ltadN1eHNufHh0hnrheXjm4OHc2uDh3OHn1Obo4ceA09zV3+l2dXZycdXa39TT49nZ1XV4dnp4cNVuc2iDa9Z1cnF4dHBxfnV0dn9/d3t5enBuz9jL1+Tb2tvfdnbleHl+fHt2391ycnmFlHTTx8jJz23IamlmYsXIwsXCaWa9ytZr03FsbHDW28zR0MrKzMzM2dDP1N922ce+vLe/zcuAcnN0dnVw1NPSbNrTamrQ1dbc1uDSyMjK2L7Rz9fR1G5tcG7Pb2xt0M5weG3Jub/Lymhmamduc3JvZMRrbWxmam5lamjEwGS6XmFiZWPBt7fFZV28umRiZcFlyshkt7LDyMnKz8rIwMDYxMHAxc9ucW/Lxb/V29jQ0d7UztLU2dyA4c3U5OC5v8nQ09XPzMzKxNPQw8G90tzZeJGynHbT1XBvb3KLxrt0bGlqdnFowLWvuLC9x9Jryt/bcHbYc9jR1NZw09jWcnHW1tjb2thtbW7U0m1t2NLY3XLK2NLV1tnT1drRx9Nt3Hd4eHjSyWtt2c3Gxdfa49nS125s19fQz8+Azthwcd7V0t3m4HDY0th0cunh3t3U4HjI1GuAhIV653F63dvU2dvY0tbS3NLU09zizs3W33p4cuDmdHPi5HJ0deDecXJ2e4Z1zNXd19vc0tjZdXTVydFxdd7S19zg1tHMznTb4/Ph3Onk3dHP2N3c2+Tk29rX2eLW4XFwb3Nz4OCA2dDT2dd3eJXVknrc4MW/w9p+eOjl3nR2b3J2dnVyc3mBgG3Xb3BvcXZubGxwctfYdeJ0eHN343JzeHVzdXXd4tjZ3nJzdHp9dnhyb9x0cXBxdNx1cnB9e3dydHdz3uLe09Xc3+Xh4uHk3uR1enu8q+Z8en9/fnl0dXJ4dXLhc+CA09KIiLjUq4B4cnJtbdtucXdwcnNucHN4eXR029Xc29je3dbkdubc5O17enl26ebsgnx45eJxc3LY399w2m9w2M/XcdfFw9B0dW1udW1wc3BxcnVxzNbgb3Vw3+Dgc3bg5eDa3tHR2/N+8X5/fObs6uLc4tzc5XTeetrSdXh3deOA4XJw2NRxbtNubdTP1W5u1c1va3BycXl6end7gHp3dHDZb3RybG3O1HJ3ent3dHV3d3V57vTu6O7odnl4eurcd3l7nIZ9f3jc4OPj7Xbp3+Pte4R5gHjjdXNxdXZx2NrW021zd3d4c3Fzduba1HmAeXrj0uJ5fYSBh3+DdNxyctuA4ud1cXTY3nTX3tjh2uPd4uTd4ePffupy3Xd7d3Fzc3Zz33pycdtz3N117Ond3tzqztfy6ebg6PJ98Ons59PZ2dTSz9/fdeLU1d7b3tbb3dDV0M3T1HN1bWtscHBt0NLTd3pwa9BtcHFqd3NtgHXUcW/V0NHOztbU1tveytza2MGAyNfU2uR0cnNxb9vd2dDN2dHUz3F3dHV3cdZuc3eodN12c3F2dHJxfHVydXt5dHt4eXRy09jS2N/T0tjgdHPadHd5d3p229lvbXOKmnbY0dXU1W/VcHBwb9rc1t3XcnDP2N1v23Jwb3La3dDW08/V2drU3d3g3ed5387Ev7e+x8WAampxcXBu0dDTbNPPamvR1NPW1N/W09bX3MHV0tTKymtrb2zHbWtp0NFwdm7OvcDP0Gtrbmpxb3JwaMdsbGxmam9rcG7Y1nDVa21udHPg2t3kb3Hi2nNzfOdz6OZz1NDf4eLg4dvh39zo0tbd5Oh6fn/n4tjm5uHc3eHV2dzZ4+iA6dbZ6u/Ixc3S1NnQztPOxNXTyMjC1+TifJKtmnfc3HR0dX2Z18N8d3V1gX102dDL1sbU29xy2N/gc3njdtzV2OJ23+PgdnXc2trd3dltbm7T13Bs2NXb4HbT3tfY1tbR0tjRzdxw4Xl7e3rZ0XBx2NnT0OHd4eDe5HZy3t/e2dSA8PiAgPzw7vv//4H58vaEgP339PPq+YrL44OYlJGF/oCI+Pvv7uzy7fDq8+fp4+/96efy/YqIgfn6g4T//4GEhf/7goOGi5eE6O/49/v57fT1hIbt5PGAhPrr9Pj56eTm6oLt6u/l6vvw6N7g6vHv6PP37u/v8P7y/oCAgIGC/fqA8ujj6+qAgKTxnoPw893Y1OyMhfz584KJgIKJh4aFiJGamIP/goGChoqCgoCAgPH2hf6BioaF/IGDiIWFion5+vb5/YOHh4yRiYaCgPmFg4GChPuIhYOSjoeBiIiB+f746ezz9/318/P48P2Eh4a9od+IipKOi4iFhYCIgoH9gv6A7OqantX8zZWMhoaAgP6BhIuFh4WAgoGHioSE+PT46+Xx8en7hPvu9v+FhYKA//v/kYyF/vuAgYL4//qA/YKC9ez2g/Xh4fOJioGCioGGioaGiYuF7vX9gIyC+Pf3gYPx9vTx9OHf5v2G/oOFg/H18uvk7e3y/YH4iPbogIODgPeA/IKA/PyGhP2Dg/31+4SD/viFgIWHhY2MjYqOkouJh4P7gYeEgIDr7oKIiomGhIWGhYGE/v38+P/7gYWGhv/vhIiKsZmKi4P19vr1/ID99vP5hJWCi4X+gYGAgoOA+/zy74CGi4yOiIWGh//t64eNgoP15PSEiZKOlYyTgPOAgvmA+v2DgIPw94Hs8O758Pjx8vXx8fj3iv6A+IaOioGAgYaD/oyEgfuC9vKA/fvu6en21d/57+7r7/iC/fT48uDp5dzZ2Ozygfnj6PLr9vT/+ujv7e7v8YaIgYKFg4OB9vb9kZOEgP6GiYiAkIqClof3hoX9+fju7fn6+vX34Pf9+dqA4evn8f+DgoWDgPj68+no+/P28oeMg4WIgPWAh5PSivyJiISMi4iGj4aGhoyLhY6Ki4SA5e7n8fno6vP9g4D3hYaIh4uI//2CgoijvY//8/Xy+oP3hISBgPr49/74hYLs8f2B+4OBgoT7/+7w6+js7vHu+vLz9P2G++rg3dnf6u6AhIOIhoWF/vv8gfz3gIL+/fn89v/z8Pb4/t718/72+4GBiIT2hYGA+fuGioH34uX1+YOAg4KLjIyMgfmIiYqDiI2BhoX9/oP3gICAg4H+9vf+gID784GBiP+A/v6A7On+/Pn7/vX26uf54ePi6vSAhITx6N/y9O3l5/Ln5efl8PSA+uHn/PzV4eTp7/Po5+zj1urp29nS6PjzhqTPsob394KCg4ql8fSUh4ODj4qE7+Lb5dnp7fmD+P38gYf+g/Xs9fuD+f79hYHx8/f4+fmAgYH9/YWB/vf+/oPm/PT29/n19fjt6PmA/4mLjI337YCB9vHq6Pv5+/37+YWD/vr39PQEfHx9fYZ8Bn18fHx9fYZ8C31/gH9+fX19fH19k3wOfX19fHx9fXx8fX19fHyGfYl8B319fHx8fX2JfAF9l3yFfYd8hn2GfAV9fXx8fI19AXyKfQR8fH18hH0BfId9hXyJfQF8hX0BfIp9jnyEfQF+jX0JfH18fHx9fX5+h30BfI19iXwBfYR8hH0WfHx8fX19fHx9fX18fHx9fH19fHx8fYR8jX0LfHx8fX19fHx8fX2JfAV9fH19fYl8BX18fXx8hH0SfHx9fXx8fX18fX18fHx9fXx8j30BfIV9gnyLfYZ8hH2CfIh9hXwBfYR8hX0BfIZ9hHyJfYN8hH2DfIh9DHx9fXx8fH19fXx8fY18BH18fXyIfQl8fX19fH18fH2OfAF9jHwBfY98iH2DfIR9AXyJfQN8fX2UfIV9iXyGfQF8hX0BfJN9iXwDfX18hn2CfIZ9hXwCfXyEfYV8B319fHx8fXyEfY98AX2IfIZ9CHx8fH18fH19kXyEfQl8fX19fHx9fX2FfIl9AXyJfQR8fH18hX2EfAx9fXx8fX19fH18fH2RfIN9p3yFfYJ8jn2IfAh9fHx8fX18fYR8Bn18fHx9fYZ8B319fXx8fX2EfAF9jHwCfXyEfQR8fH19inyCfYV8AgIEAIDUzGNmysPLyMbNy9NubMbGzM9rb25ybW5xgXaAenNpaW10bNNta8vR0MnHz9lweXl1eXN3enh1cXJ033NyeXZ04tfseOvj391zo4HqeHnre3h5fOPx8N3j5Onn6eV87+Hb2NDKyM7b19XS3+zo5Ov3in5/6erd1OHb7fDrenzl6YB+g3nP1t7g23OAe3Xm29nT5+noeXp6gn18fHR63d7a1m5x5ejcb3VwcXRyz9Jwc3TZc3NzcnZ7cnV2fHV6d97f6IF/5nfp6+14det36OZ4eXd43Nrbc3Nw29zi5uLb2NjR3tTJy8rU09Xjcm9wcdTW4eB2dn15eeLRbtXccNHadoB/d3p9e9zXcnR3eXjWy9N1cXJ1dXZ7eXFtxMXO2XR9cnJ15eLj3Mt3dc293+55foF/8vh/g/Pv3NXcys3Y2t15d+Hb2dzldXrn1eqAfoF4dnjo3nDabGnJxcfMyczGa21tbWtubWtsc2rMbnDM0NHY3tbQ0tLUcnHf4dvY1+Dj44Dp5+bb4e3c39rS2dvc7ed5enx13m/gdntzdW/fddXBxdhy3+F5cel55+Tu6eDrjYDc19LV2+eHeX2Xw5GDgHmCd3d74eTc6HPR0MfIztbPaW1tbmrNZ8RmaM/Xc3+Cd3Fsb9bSz25vb3+ijnTZbW5r0M7PcG7CbWnA0HJxa8fAuoC1tsHHbnBras3Z0MqAlZNxbnRyctpv1M1pcNhr0mxsbm5pycnEum91wcpqyLzExL3ExszT0sjP2crU2tjSyd3VzsvV4NqapHfRzut24Hd+dudzdYV2e+F5fnbX1XR6eHB9hZJ1c3N4f+DgdHNvbnzldt/jc3Te43J13NmE0lptg4B3e3fl4nbj3t3a49PUzNvufX54doJ04dx4c+Z0fXx13XF1e5KOdOJ7hH57eHZ2z9jN2eLb2dLb39fde+Tj2Nrk2dnab2rQatDNamhwdICEe3J2eXVw12904dvO2N/Ozc3M13TW1drd1eHcdYB9gIR75s3T0M3Y4+DV1dPd1NHRy27O5uLMxGtqbWxqa2xmwWCruGdzd2xnxGlnythuaWdpbWxwcdPGaXFubmhnaWNmaGxtatBzcnZ4bXKBkYuEaGfEwGdkbGRjwr23tb7Fw79lbG+EuYjHZWywrbfAy8i1u3BubMq4yHNt1NPSzt7n6ITTgMvW5+Xm3dDX29fR2ufi3dhzeHzTt8vM0M/MyMbP0cHC09HQ4t1wb3TWy8TEybnIaWjNy7WyxM1qc3N32tbZ0NbU0N3d2m9zdXF7gmfOz8zJzMbMb3HYcHBycNN0eIWWhNTN3nBwb9Z00HFx2c/M1dJw177Eyci/ysxnaWrKwb7JDsa+w8/Iz2psbGprbmxtgOTfcHLe297d2t7e4HJx0tLe43V2dnpzd32SgImDfG9ub3Rt2HJv0NXSy8vO23J7e3V3cnV0c3NubnLdcXF3cm/c0eJw2t3e2W+Xd99xcdt0dHJ13+fg0trf5OPk3Xbm3Nna0sjKz9jRysPT2tXV2957c3Lc39PN2Nnl3d9zddzfgHl8dMvP1NbUb3p3c+Lc2tbh4d51dnd2dnp3cHbb3NnZb3Ln6NxxeHFxdHDM1nBxc9Zwc3NxdXt1eXZ5cXVz3+HgeHLSbdvk5nJy5Xbl4nd3cnHb2tpzdHDX2+Di4tra3tfb2tXT1d/i4udzdHN14N3q7Hh6gn1+49ly3eZ35eh5gIF6f4GA5+N0eHt7eN/X3Xd0dXR0eHx6dHHTztPcc3t1dHTl4uXg1Hl318bj7nd+fn3y+YGG8u7h3N/R1+Dc3Ht33tfT1t9xdNfI2nh4enJxct7dcd5vbtfQzdjS19FucHFzcnZ0c3V8d+t8fufu7vLx6OTm4eR6eu/t39vf3+PkgOfh39LV4NTY19jW1dno4HR2eXXkc+Z1fXZ4c+h73srO23Pm5Hl04nTh2dng3umLguTUzdLW3IB3fX2QgIB9dn10dnrd5uPkctnc2NXZ4txyeXh7euh143R03uV7g4N6dHBx2tbWcXBxhKCNfOx3eHfw8/N+e917eN3wgX534d7WgNDS3+V5e3d24Ori4Iuim3R0eXN13nPc2G1z33HhdHR5e3rn6uXehozl53jq3u7t5ejj6ezo1tzl4erh5eLX6OLY1+Hs3ZGZctHP43DWcXZz4m9yhHJ01nF5dNXQb3Nzbnd8iW9ubW951tRzdG9ueNty2N9ycNXacHDSznrjdoeAgG9ycNzWbt3Z2s3Z0c7E0N5zdXBudWvRymxo0mtycW3Oa251kotv13V6dnRwbW/I0M3Y1szU0dvZz9Jxy9LJy9vZ4OBwcNxx395ycnd/iox/dXZ2dHTeb2/Z2NDT2NLS1dXbddnU0tHQ1dZ1eXV4fnTczdDNztbY2dLV1NfT19bQgNHV18/Ia2xyb21sb2rFY7DBa3Z7b2zMb23O1W1sa2xrbW9vycNrb2ttaGlrY2VlaWlnyWtqcHZrcoKbo5NwbtfXcnF2b3DX1M7N1NbT0G52eICSfemHfM3O4ePp6dfUeXZ33s7aeXTm5eff6e3s4+Tl4Nne6ubm49nZ2d/a3ubigOLheXp618Td2d3Z2NbW4uHL1ODe2unqenmA6uLd3uPR33Rz4t/Mw9LXbXd4euDY3NPY2dXa2dlxdXRweoZy29PS0dTR0mxu2XBucXLWc3aDjX7Y0ttyeXXdeNt4eOje297cdN3M0tjc1uPjdHV139fT3N/c4erj6Hd5d3R2eXl2gPr4gIL79vz07/v3+4OD9O/5+4GBgYmGio6hjpOQjYKChI2B94GA8/X08fDt9oKKjIWJhYmKh4iCgYX+g4OJg4L+7v2B/vj7+oC1jf6BgfqFhoWG9f7+7fT19/j89YH47Ovt39vd3uzp4+Lo8+7u9/mLg4b0+Ovk7Ov++f2DgvP2gIWJg+nv9/v0gYyGgf3x7ur49vOBhYWIiYuLhIv8//39gIH+/fSAiIKBgYHu9oOCgvOBhYSCh4yGioiNhYiF/Pr9iYb2gP3//4OB/IH6/YWGg4L29feDg4D6/fz9+vPz9ez38ejo6/Ty9P2AgoCC+PL6+4SGi4eK/vCA+v+B9v6HgJCLkJKP/vuGiIuMifbt+IqDg4WGiI2LhoLp5er1gImDgoP++fv26oeC5tTz/oCIhYL2/4WK+/nr4uXW2ePk5YCA7+3s8f2ChPbn+4uLjYSDhP38gv+Bgfzz7vv5+/WBhIWEg4iDgYOKgPeBg+/38/f78unt6OyAgP397uru+Pn2gPr3+err+Oju7Ofo6er/+4OEh4L+gv6DioOIgf6J++Tm94H/+YeB/IH+9/n+9P+Yjf3u5Ov3+4+IjJOukY6LgouCgoX0+vL4gPDu6evw+/qCiISEhP+B9oGE9v2IkZGIhISD/Pb3g4GAlcWkh/+Bg4D9/v2EgOWBgOz8h4eB8+LfgNvX5O6BhYGA8v3z8pexsIKBioOF+YH7+IGG/oD/g4GDhof5+/fwj5L2/IL56vv27vXy9/b46e3x5fHu8O/r/fTn5ez38a22guTg/4Dxg46B+4GEloKF9oaOhfHsgYiGgo6WpoWEg4eQ//+IioKAi/2D+P6Agvf8goPx8o3+gJOSgIKJgvLygPj09u767e3f7fyEioaHjoH79YWA/4GLi4P2gIKLsqmF+IeTi4eDgYLp7+bs7eXo4/Ht5OqB7+zi6/31+fuBgf2C/vuBgoqTo6OUh4iLhoT+goX//PP4/uzt8fD/h/n18fTy+fSFjYmMkIf84Ovt7vT08+rw7e7s8/fwgPL3+vLyg4aMioeIiID6gOb4h5KWiYP3hYH2/YKBgICAhIaF8/GBiISGgYGCgICChoeC/oqHjJCFjaC4v7OHg/v9iIWMgoD7+PL2+fv59YKKjZePb9GBjubh9vv++ufmhoSB9erxiID79PTq9/7/8vDu7urw/fb29Ont7/Tt7///gPjyg4eI7dbt6vDw7Ofm9Pbd4vHo4/r6gYCH9+vq8fjh+IKC/vnk3e/4gYqKifn4/fD19O719fSBh4eDjJqD9fT18PLv9YGD/4OBhYX5iIiWpJH08f6Bh4P3he6EhP/x6/LygPHa5O/z6/b4gIGB8+vp9fHp9/3w+4KGhYKBg4CCBHx8fX2IfIJ9hHyRfQN8fX2HfI19AXyFfQR8fHx9hHwHfX19fH19fIR9inwBfZJ8g32JfAd9fXx8fX19hXyEfYd8iX2EfAV9fXx8fIZ9Bnx8fX19fI19EHx8fH19fH18fHx9fXx9fHyEfQZ8fHx9fX2SfIR9hHyFfQh8fH18fH18fIZ9gnyFfYN8in2EfIV9hXyCfYR8hH0EfHx9fYp8gn2FfAV9fXx8fIZ9Bnx8fXx9fYd8i30DfH19inyCfZd8hH0DfH18hX0CfH2EfAd9fHx9fXx9hnyCfYZ8BX19fX5+iH2EfAF9h3yFfQd8fXx9fXx8h32DfId9EXx9fX18fHx9fXx9fXx8fX19h3yEfYR8iH0JfH18fH19fH18hX2EfAV9fXx8fZp8DH19fXx8fH18fX19fIV9Bnx9fX18fIx9gnyFfRB8fXx8fX18fH19fHx9fX9+hH0DfHx9inyGfQV8fH19fIR9AXyGfQF8h32MfAF9iHwGfX18fXx8jH0DfH19inwBfYd8hn2VfIh9BHx9fHyFfQV8fX18fIh9gnyNfQF8jH2CfIV9iHyEfQV+f35+fYh8CH19fXx8fH19m3yDfZJ8g32HfIJ9hnyEfYp8h32HfAN9fXyEfQF8hX0LfHx8fX19fH18fX2FfAF9iHyDfYp8iH0CAgQAgNjY1tXCzMjU2sy9t8PG0s/DwGjVysjOaWpubWzGaMXAxcrQZsLRbcvGv8vIw8ltb2xqdHFrb23WcNVzc3Rydnp9fXXUxdlx32994Hp6dXp8fO1619p5fejq4+V6eZ6ye+Pq8ODi5N7q3t/h4d3q7eHY3tvY6ePZ3N/j2eDn4nr1gOXo5N3o43bp4tLRcoeEdnjb3d3cc9rSzebgz9Z4c9x0eYN+fYR3dG5v3GrQc3NzcmzZ1NRy4eV14nXq1niIeHh7fnp7hpuegXqBfubm6Xnr4+LX3t/l3HB03NXd0XJxycXS19jVztjUznOCgG9s2nPf2G9xb23S2Nbd1s7Uc3V2gG9vc25xcHLg0NLV2tfSbXFwbW95eXRzcnBzc3txdXaIbYtyctd3d+nmdnZ3gH+AhIGBjanHge3v7NfU3trg3M/L0Nfd49re7+3o5ur38+na3t3c6ujr7OJvb3p9393VcHTUc3R0btfe3ths09HKa2/Vas7N4OXi5dzl8njn4efogOzu8Xrod4KTe9bb4tzg4NrXztPl5Nzl4uLse3h63NXT5nrT3tzR19/O19PNxbi60W5yc3F0fHl7g39/es3Y3XNxcNvd18/S3tbOxNPX2c7a13HedIVsuLO5ds7Jz9HO0tTU2tza2XJu2tJ5fefW1dDXeNvPz3Pp6ex6eeno5dzcgOR6dHWAd9J5eXBwdNXP0trYc9Jw3MzQ1sXM1tbg0tNvbnF1cG7Vf4La1HPKZmtib27NbXRxcHHa2OPkc3Z1ceTb2L3L2t5128/c4d7c3eN2dHbV19LQ29bQ1MzL39fS2MzN2G9tcmzOcGtrcG1/jH9ybHB1dnh2dtvRytZ14XN1gH3g5HN4deRz0trbd3rh1HJ6dXVzhYB6dXh+eXh6c3F2f3x0bWxsbt5ydHl/f3V3dnJ1b83QxrvLy8rO1dvjc9i3xNfT08rIxNhv2cvNdndwb3Fxbc/M23Vxb97d19PPxdnl0cTCzM/a2d7l4d7T1WxxbM3LyG9oaWttbWlox2RpgGplwrvHZWZqbXVnZmrGa3HHurZzcHB3fHZs0ODYdHLY1tTHx87TzNBwc3JzeX10b99weX5vzWtxyWppzc7Pa9VpbG9v2mxt19DQzWxxcnJzy8Zwzctow7fDcXHCx8LKwcLPyMnJcniBinfS0M7YcHJv1cfSy3DOxM3JzdLKzN3JgMrR0ddtb3DX1dfHxMLH0czCxr5zb2hszsfAv8XOxs3Y19jP3N/e33POw9BwenhzcsjI2HBv2cbM2YKFfd3YcnVwbXJrbG9ucHh3dd9zc3bY3ORy0dB14uPZ2tTccm7a2c/Kx8jOcMjJ0c7RycvOzcbR1tfd0tTj2s7TytDV1d7cgOXl59nO4OPk5OLY1N7c5+bo7Hru4N/peXl+fn3qe+7r4uDld+Tqe+Pd1eXg2+N4d3N1fHhzdHHacdh0c3Jvc3V4dW3Fvdhv3W542HRyb3R0duh31dh2fOTj3d54eKO4eODi6NnY2dTa09Xb2trs693W3d3W5NzQ09PT1ebm3nXjgNzd29jd2HDd2dLRc4eBd3fc4+PhdN/b1OXez9F0bddvc3x4eoJ3dnFx5nDac3V4c3Lh3dty3d9y12/d0HKAcW9wcG9wcoOKeHJzdd3Y3HXi2t3c4ODq4HRz4+ns33R439vg5ujj3OLg33iGhnh143Pj4nR0cnLd4trc3NjcdXV0gHFxdHB0eXLd3+Dd3dzbcXRxcHJ5enp3cnR2dHtydXeOeZJzctx5eebnd3d2fHt9hIF/iqK6gPX49uXg6eLk3drZ3eXo6eTo8fDm5+fw6drN0NLQ3N7g4eB0dXt85ObhdHbgd3h5c+Dj5ONz5Obid3nrdd7f6eri6N3m73bl4d7jgOTh4nPcdIWTeNrb4uHg3tve2+Dk4eDq7Onse3d78erh9obz8+zd5/Lo7ejn2tPS6Xl8eHp5fHl7f3x+etjk5nh4d+zr5+3n5+bh2ODj5uDn6XjqfJF4z83Ngubf5eLg4OHk6Ozr6Xdy4Nd5gfHf5urleeTi4Xnw9PJ7eu/q8evygPJ9fX+EgeN+gX9/f+zo6e3ofOZ57t7g5tLb4d3f19dycXR3cnHYfYLZ1XLYcXNueHXgeHl2cnXk4OHldHN0c9zc3cTO2Npxz87b2tjZ2N51dnXa29LR2t7b4djX5t7b4djZ5HVzeXPddnJwdnKGlIZ6dHZ5eHx7e+Ha1d924nV3gH3j6W9ycNptyc3OcXPSyW1ybG9tfHd0cHN3dHV2bGxxenZvbGtsbtducXZ9e3B0dXF2cM7Uz8DKz9HV2d7pdd3H1ubl39fX1eFy3tXcf4J3dHRwa8vM0m5sbdrb1tXSxtrm1M3LzdLb29rk5ODc2W94cdnXz3hwbm91dnBv0mpugG1qzsvOaGdsaW9lZmzIaWvJvr14c291fHZvz9XSc3Lb0s/JzdTPzMhna2lpbHRrZtFqc3drz2px2W9v2NLWbtZqbW5t2W1v19je3HN1eXZ83tJ34+F04NzmgYLh5d3j2drc2NnZeX2Einng29bgdHd05dnm3Hfg09fU29jW0uPSgM7Y4OR1dnbi4Orc2NfZ3dfS1tJ7d3Jy3tvV09vf19zg3dnGzdna3HLW0Nt0e3VycsvI1Wxt1L/Ez3l7c8/QbnBubHBqam9vcnBwcdlwcnfg4+dz3dt26eni4tvoe3jq6dzX1d3iet7Y3tjb2Njc3NLa4d/m3uDn29La1tra2+bmgPf6/fLm9fP4+/Pm2+bt+/Xv8oD96ez7g4SIh4b5g/f29/j7gPP/hfb27fn28veGhoKEjouDhoH7gvuGhoaCiIuMi4Py5P2A/YCJ9oSEgISFgvuB5+qBhPLv5u2BgrXRg/Lr9Ojk5uDi297l5N/3+evk7fDq+OnZ5ebo5PL49IL7gO7x8vH79oH/+/Dwg5qTh4f7/fn2gPTw5Pvv3uWFgveBiJWQj5eIh4OC/oD8iYaJhoL46e6B/P2C9oD964KTgoGEhoGAh5+ki4OGhfTt8IL98/Ps9fX+9ICD+PP274GC6uXu+P737/j184eZmIOA/oH6+YOFg4H0+/X/+/P3hIiIgICDhoGEiYT/+v36+fn5gYSFgoGKjI2Kg4SGhpCFiImrnayCg/eIhvv8g4KCiYmJj4yLlrDKhv79+unl7urt4NXV3eTn6uPh7vjt8/f/++7i6e/t+/z///aAgomK+f72gYT2hoeGgff1+vuA/vz2goP+gPfy/vj1+env/YL98/f7gPr+/YDzgJWphOv1+fP09fL38fT4/PD5+vb/hYCC9e/n+4Xx/fbl7v3x9/Dw4tjf+IKFg4ODiYOGj4uKhOv394KCgvz8/P37//jx5fL19+/2+ID8hp2A2tbdi/by8vL19/X2+Pv7/YOC+e2Ehvvv9PXxgOrn7YD4+/2Eg/z4+uzzgP2EgIOLhumDhoKEhPfw7vLzgvSC/evt9eTs8/b58vuFhIiMhYP9k5f+94L8g4SAiYX6hYeDgIT9+Pr+goKFgv75+tvm8vWA7+v4/Pf49/+HiIfz9fHw+vv49unq/vb0/e/u/oKBhoP3hYKAh4ObqpmKg4aIiIiHhvfw6vmF/IKFgI78/4KHg/6A7/f4iYv87YOKg4WGnJSQi42Wjo6QhIOHlo+GgICCgfyCh42Uk4WJioWJge7w6NXj7Ovs8vz/gvTT4vHu6+Lh6P6C/fL4jpGIhomGgfHw+oOAgf748+/m2e/75djW4Obw7+n39vHo9YCGgfrx7Y6IhYaPkIqF/oGGgIaC/vr9gIKGiJCCgIX7hIb88fSUjIaMk4uA8vr1h4T88u/m7fb59fKBhoODiZCGgP2EjJKE+oGG/IOD+vT7gv2AgoKC/oCB/Pn694GIiIeJ9+6G/PeA9uv0iIvw+Oz16ebr6/Drg4qSmonx6OX3goKA+On66oLw4+nh6ejo5fnkgOPu8/mCgoP28/zs6+nt9eze5PCMg4CE/vLt7ff+8vn/+fbj8P3++4Hr5vWEjIeFhOvp+4CC++vy/peckv/+iY2IhYmBgIODhoaFhPOBg4j4+P6A8OuB+/rw8e36hYP7+e7l4ez0herp8+3x7Ort8Onv6+398PT+8OLp4+ru8Pz5knwBfYR8hX0CfH2FfAR9fHx9h3yJfQN8fXyJfQh8fHx9fH19fIZ9Bnx9fHx9fYR8hX2efAF9h3wBfYR8hX2EfAF9h3wDfX18in0DfH18hX0LfHx8fXx8fXx9fHyPfQR8fHx9iHyCfYR8gn2KfIV9BHx9fHyEfYd8in2HfJJ9CX59fX18fX18fI19onyEfQZ8fHx9fXyEfYR8CH18fHx9fXx9iXwBfYd8An18hH2RfIN9hHwBfY58jH0GfHx8fX19j3wJfXx9fX18fHx9jHwGfX18fH19hXwKfXx8fH18fHx9fYZ8hX0BfIV9hXwDfXx9i3yGfQd8fX18fH18hX0BfIV9hHyEfYd8AX2IfIN9kXyEfQF8kH2EfBN9fH19fXx8fX19fH18fHx9fXx8mH0BfIt9i3wBfYp8BH18fHyHfQZ8fHx9fX2VfAZ9fX18fHyIfQF8hH2DfIh9Bnx9fXx8fId9BXx8fH19iXyIfQF8hH0LfH19fH19fHx8fXyEfQN8fX2EfIV9C3x8fXx8fXx8fH19inyFfYR8g32EfAF9jnyDfYx8hH2QfAR9fHx8hX0FfHx8fX2EfAV9fX18fI19C3x9fX18fHx9fHx9hnyCfYd8AX2afAICBACAz8G7w7zTaMnLzcXEwMLM1crGytTPz8/FvbrFy8bKz8zAs7fNwL+8wtTPyNzLz9Vzc3p2dHd3dHDa3dBrcG7ayb65ysvMbtK+03FvcXBry9jQbm7VxnV1c9na0trZ0NvgeOHl49rV2NXYz8/d2+d54uLf3sXJ73fndt7ogt/n5d6A5Xnf5Onj2NDOydvd3+d0d3lyeX10dXHgcnNv2nR9cdHR2OF2c958fON1dnTjdXvb53l39vPn8PTp6oB78/Lvfebn3dfh1tvb4d7pfNdzcN1w0MnS1MfQ0m/QZsbGz8vHa8rP0mpvy83f02tuzsfIzNzS0dvT23F33OHi53Z3d3iAfejg4357gn1dr4Hl5Hd6eH1/eHZ+eHN/m52BfXyBend8duJydXl2enuIe+nY3HPhdHl4duPl09PT1s7Iytja0tvWw9rZ3N3Ox93d6+nghfff6tnP0+Hi5uXtdX12g3t8i8DMfbDKf+DYbXDcb3JycXF0d3Zx5N7m69jc4nZ35uaA4ufi3+vM1O16duPb29vR09Z1cdzc08nYc9nX2NxxdHNxd3N12nHifHrW23Fw2MvEy9DLyM3UzNza3eLnddvf3G50eXNycW5sybu9u8zM0M3N09lty9PFtdlu19LEyMlx2dLJxL+1ystrzcpszcVwem7GatFubm5w023RxczIxcaAzM7N4tF52N/e09zpeHV3dIB7cXDfz83KvsLKb8za19vGbnd11m9vbW1pb3NoaG5zenhub3JucHBwdW9t0Mpwb9HOz83I3Nlu2ttub9XY1dLWzcXBz3B2b9PG0uHk4tTP0tTOysrKcHBsatTV12xub33R1Gxub3N4fHHa3nFucdmAb29sam7Ra3HLz25wcXjd2c3TcNLHympybnBycXpzc97bcNtycXHccd58d3eGeHNzfnp02czc3t7O2M/F0XFtzcZxcnFwbddtdHeLeWxtz3Byc3Pa1tfT5OXa3Ojj59bp5dbO18rKyrzC2NLSyNTW1m9wc3Jta8zKx8fFymbHZWeA0MLJvrXF03ZydHPX2dLR0dF0dnFudePe2svk5ursdHbQ1tvk4s3VcuLb4ePb197ZzdLh3dri4Nvf3Nt54uLk393X29vQzsXBx8zDy8nFv8bU0crEyNDcb8zJ087UydDOvsjJ0NXU0MjKz9PX1NvP43HMzdTRz9PU29TU3NjXdtuA4OHb3nFx3dHX3NTS19jR29VubNHOydDL0m3Z0tLaz8/idnp83NzjeHh4dnZ2eHRrdHXGanFvbnp6dtDV1dPR28nM1+rqend41NrX4Xd8ennlcXZy3Npwct3W2dDO0m9wcnBw2c/Z09LTbdZr1XJzcM/LysbCycqyqL/CvsjFw9GA3tbQ2tbtdN/h3t3f3t3g7fDr5e/n6ezm2dbe5d7j7O3dys/o2dnW3ePk3vLp6+97fYR/f3x8fHfo5+NzdXTl1tPT4N3get3T4XZ6d3Rz4uPYcXDazXt8duLi2d3g3uPjd9/n5OHf4t7h3tvl5Ox97OHg5MfN73zwe+Xqf+Hl7N2A5Xji6vDr5N7Y2evk4ulxd3hzeXh0dnLecnRx4nmEddbV09xzcNd3et5ycnHmc3LW3nFy6uTZ4Ofe3HZy5uTjeOLf29/r3dfc6erwfuV7ePB55ODp6eDp6Xvrduri5uHjeePn6Xh94+P08Xl55uTl5Ori5eXi6nh53eHl7XZ4d3WAeubc3Hp1e4d+2n3c2G90cHR1c3B1cnF3j5F4eHl3dnZ1c+V0dXd3eXyIeuTa5nPhdHh6duTi2t3g4dvX2ebn3uji0ubj4+XZ0enr8vPrif7u8OHZ1eDj49zpdHZyf3d2iNbbf8rYg+7ueHjqdnZydXV1d3lz6OTo6tvk6nh58uuA5+/s7vDS1+p5eOnd3+Pf3d58d93a2dHectzf4+R2cnR2fHd233jrfoHq6nh35tnT1t/Y19jf4Ozs7OrodOHm4HN3end3dnd139PZ3+vo7vHu8vJ76+TdzfB68uji4uR97urj2s7D19h349904uJ6f3bgduZ2dXV45HXd0Nnd09WA3d7U49yA1tze2eLoeXd7eIqDeHbr393i39fdeOHu6uXQc3t24XFzcnJxd3tvbnJ0eXdzdnNvdnd5fHZ34NV1eOfg4OHZ5ut14ep4eu3v7Ors5NzV5XqAfOvc6PL28+Xi5Ojk4d3ee312d+np63V4doDd4HN2d3p8gHPk7nl3euaAc3N1dHXfcnXX3HF2eH7k6N7jdtvN03J3cHBzcXt2c9zdcdpwcHDddt55eHeHeXNve3Zy1Mzb2d7S0cvJz3Nz3tp1d3d1cNtucHymh3Jz2nV2dXPc293a5uTa2d/b4dDm5OHd39LV18vP5NbOytTZ3nR0dHNxb9XUzs/L0GrMa2uA08rQxb7N03JvcXDR0cjMy8pvc3Bsct3S18jZ2eTndHbGy9HV18PDbNXV2dXY0dPQyM7a19LV087S0c1v0NLOz87O2d3RysjOz83F0s7M0tTb2NbR1djpdd7h5+Lq39/h0dra3d3Y0MrS09PY0tfO2nDS0dTU09bT3NPS2dfUdNmA3d3X2XBy4NLQ093Xz9nY39ZsbtrW1d/V2HDc2dvi2djidXZ10tTacW5vcG1uc3Frc3TFZm5tbX98dc7S0s7N2cPH1OPhdnJxydTU2XF0cnXjdHh03912dePa29bT029ycnFy3tHa3t/gcd9y4nZ5dd/h3tnU2tzEwNTW0dve2uGA8OTd4937gPnz8u7o5OTs/fvy6/jy8vbu3t3k7OTr9PHiztPv3dTR4/Do4/vx8fiAho+LioeHhoL9/feAgYH96+fp+fr7iPvt+4aJiISB9/z2goD56IqJhP387/Tw6vLwgPDz6uPi4OHo4+Dt6/mB7ebn48vW/ID3gO/1ifL5/PCA+oLx+P757+ri4vv39v+AhYeBiY2FhID/hYaA/oaRguzl6/SFgfaKjPiCg4H2goXy/IKA/fbu+P/284SB//z6gfb57+r25d/r+PX+i/OCgf+B9vT29uz4+4T6gP3x9/b2gvT5/ICF9e/6/ICD9u/t7fn0+Pr1/ICC7vX4/oGEhIKAif3w9IeBh5GL7or38oCFgoiHg4GKhYKKnZ6Ni4iKhYWGg/+AgoeHiYeai/3t+YD9g4mIhv/57evs7Obk5u717O7r1+nm6OjZz+vs8/Pqhvrt9OLa3+3r8e7/gIeDkoiIl+rvitfXiP/8gYH5goOBhYODh4mC/Pn5/u75+4GA/vqA8fb1+vrd4/mBgPnt6vHw7e2Gg/bw8Of4gPj5+/yEgYGEi4eE9oL+jIr2/oOD+Ofe4+rg4eXr5/v9/f7/gPb+94CGiYaGhISD+Obt7vjx/P34/f2D9O/l0fiA/PLs7/OF/vz38Ojf9fmF/feB+fyHjID0gfyCgoCD/4Dy4+zs6OeA8PTp++2N7O/49Pj8goKHg5CIgoH88O7t5eDqgfD+9vLkgIiG+YGFhIOCi46CgoWHi4yEhYSAhoaEi4OE+vOGhv769vHq+/6B+/+Bgvn7+Pn89Ojg8IGIg/Xh6fr59uzp7fHq5+zngISAgfz5/YGFhJL8/IKGhYiOkYL2/4KBhPyAgIGCg4X6gIf1+YGFi5D//vT6hPXn74SMhoOGiJOLiv/6gfmAgID6gfyMiIqbioOFkYmB7OLz6evp7eXi6IKA9/OCh4eEgf+ChIuzmYOC84OFhIH18/Pu/v7v6PTz7dD38ufj69vd39LX9e3t3+z0+oOGiYiDgfX4+vz4/4L7gYGA//H36eHx/IeGioj7/fTy8u6ChIODge7o8dzq7fb8g4Xg6PL5/OXrgPrz//777/Xx6fD//fT19uvx8e2C9fTx8vPw+f3x6d3f5ufd6ufm6u/27evj5u//gvb5/fT88fLz5O7v8/j08ev09vX69fbq+4Ds8vfw7/Pv9+/v+PXxhPWA9fXt8oCA+evo6PT08vn1+/OAhf749fz1+oD/+vj67Oz7g4aE7PH/iYWHiomLj4uDjZH8hoiIh5SRiPH19PHu9trf7fn6hICA3/Dx9oOEgYX/goWD/PuFg/jz+Ozv9IGBg4GA9OPx9ff3gP6A+oSHg/nz6u7s8e/QyuLk4Ojn6PKGfAF9qHyJfQZ8fHx9fX2HfAR9fHx8hX0KfHx8fX18fH19fYh8AX2NfAF9h3wGfXx9fHx9hXwBfYx8iX0IfH19fXx9fX2EfBB9fXx9fXx9fX18fX18fH19h3wGfX18fHx9i3wGfXx9fXx9h3wDfXx9hXwGfXx8fH19hHyCfYp8gn2EfIV9g3yEfQV+fX18fJV9AXyIfQV8fHx9fIR9mnwBfYt8jX0FfHx9fXyJfYd8gn2KfIJ9h3yCfYV8AX2EfId9CXx9fH19fHx9fY98BH18fHyIfYt8AX2FfAF9hXwBfYh8DH18fH18fH19fXx9fIR9Anx9i3wBfYZ8iH2HfAF9hXwEfX19fJd9BHx8fX2HfAV9fHx9fYl8g32OfIR9g3yEfYJ8h30GfHx9fX18hX0FfH19fHyEfYR8BH18fHyJfQp8fH18fX19fH18in2KfAR9fXx8hX0BfId9AXyEfZ18hn2GfAR9fH19h3yEfYZ8hX2IfIJ9h3wBfZN8AX2bfAF9mHwBfY18AX2FfIJ9i3yCfYZ8AX2HfAZ9fX18fHyLfQF8h32LfIN9hHyEfQh8fX19fHx9fYZ8hX2GfAd9fH18fX19kHwCAgQAgMvDxsrNz9PT09rQvsnX29XVxcDW0tTS09HUddnNy8zUysxw3G3SysLGxM3AvnFzam2BnYTj29Tb39xw1sK+zNW+ysbJcnJ90WjP1tBsdG/KbmjNbG3P1dNxdYB3b9VrcHNwc9rO4HPoeODqeuze6HbMyOJ1cdje39Pi2ejcdXd1gHVx1NnXztze3Xt2ctvbbNlzdXxzzNZ1e3JzcXFwb3Fzbs3SydPn3+Pj4+fqe/Xw3t7w8ePn8u/t2djo53zyeujY3Nvg09zh6ujgcnl22dLSxdVv2NlpcHVxcHNybtBxeNien4GBeo27h9re1dXg33rpdOTj4uzy7+fx8PTr7fD3gOeA6vf29/d99vx98Ox4gpeEenhu3955bmtt0290cXFycnNzdnx+gHZ6eeDadOt3eIiA4ex+fX/k2+Hp6uN8i3nqfXR5edTVznR12c/Fxdx6fX176+Pg3NfU5OLh7Ofo699/gHeeinRwztrex87P4ODkdXR1enh3e3p0c3Z5dnZ4gHF4gHly2XV8en19dHHW1NjY2tnT13Jw09bl0szdd+Dbb9bU23NzcXF1c3V3fYh+c3NzdWttdHHVcN/kbtvZcXV0cG/b4nFz6HJ0cdLQ2NXJwcppZWhytcnJyNHKwm9ubGtuzG11bcnP0m/adXhtbNHLzmptcYyPi3zTwb7QyMzWgNG/0eLWzNp13XFw3nfZdnDN2XRu2XJvdG/YzdjQ0NfOz9hwzm1w1sXFw9DUy8HL0GtsbWjOzW/XxNDHxMTOysjVbXLP2tTgdnh3eN7Y4tjfz8jUws/edXbZ2t7c1uPc3t3k5N7S1dXD1dvYz9XP0nbTv2xucGvNxr7MzsnNyWrOgMbU1dlvcnBv3NbY2dHV1HG/0+LacXVye3J7fHt8ed1zd3t05OLa2+B0enp7enZ87nbj7eHY29LLzdXDvsbK0W3Nv7nDbs7M02zXb8rFz9vPwsvNztzb5Ovq2NTldeF8fnh5dm1nZ3NzcGllYmbDuGLCaMK4s8rKxsTHxcfHaW5rgM/Qz83O1MHHc3bZy2x4cnLQ0cXEzODa2dbT09vH19nT39Pgdnh3fnl2gnNz3Nzl3tnY44mGeuTX0NjY1d/h4dLH3urZ2uXi2M/Qzcvc3tXQ1M/Kx9DQ1M/ObMrAu8nBwbu3sbi+vs5rbm1q1NHQcp/DjW9wzdx53Npz43N039DbgOfb6+np2d7p29jd2d/YzdPS1eB6dePb1dTW2djQ1NptyG5y0MTZamxs0mpub9FqzdDQ0sfH1tvh3uHe2M/h4nlw1dBydnTddHPf2tzdd36IU5Zy3NbZ39TP2eJzytXb4N1z4NfPx8HM0Nze09fV2d/db3TKw83T0cfK0dbLxNrOgNXN1NbY3N7a2tzWztTj5N3f08ne2d7d3drfeOHf3dvf2t526XPg3dTa2ubZ2Ht+dniNtZLz6+Xn6+V469LU3OfT29fadnaC5HPe5OF1enbUdXLgdHTe4+N6fYp/dt9xdHNzeePS5nTlc9joeuzf6njS1e15eObo4N7r4ezhdXh3gHd24ePf1djl5397eOjndeh7eoR43eJ5fHZ6c3h3dHR2ctrczdTj4eLd4eHmduXf1NHi5trb3N7b0MzZ33bqd+PY4N3jzNrh3+Dgc3Vy3dbXzt9x4ORxdnp3dXh5c9Zxd76Kknt/fYuuhuPe0tTl5XjhctvX2N7j3tfi5uXe5ODoHdt86Obd6u505eZ04uFyd4t9dXdx2Nd1bG5u1W5zhHKAc3BydHd6cXNx2ORx4XNzgHra4Hh4euPa2uTm436Oeut/e4F94unjfH3r2tDY7YB/fXvq4tra187b4d/k4+Pd4n1+e72ZdXDX4N/X2Nnb4uN1dnh5eXl7fXZ2eHp3dnl3fIJ7dNx0fHp8e3V359/k5OTm5+l4d+Lp5+Da4nbo53eA4d/gc3h7dndzc3V7hHtwcXN1b3J5dOFy3eBx4OJ4eXV2deHmc3XndHZ04Ojj3NrX4nV2d4Ta7O7t8+7ge3l6eHfpeYB35ezuevWCg3t67+rpdXp6kI2EguXX1ube3ubj0uDw7dzeeOh2c+J64Hx319t1cuF4eH155dTk29zq3N6A5nnleHnn2tTR2tzY2N/idHF1dejfd+rf4Nvh29/W2OJ3gOHp4Oh5fX196OHq5Ozf2eHQ2u16eefl5ePh7Ovm5u7v7ODa5tPZ4+Hb4N3kfOfbe3x6eOvh3ubm39/keOrh6ubmdXh3duPZ3uDY2dl0wNHm4nN0dXpzf3x6fXnmc3OAdnTi49fV3XR3d3V3dHndceHm3dTc0crL0cW+ys3Ub9DJws1y2tXfcuBy0c/c5dTI09XS2uHh393Szt1x23l7eHh1cG5tdXiDi3Zwcd/Wcttv183B2NrZ1djb2NZycnDa2NTV2dXM2HZ23951dXNzzM/Jycre1dTTyMPMwNPTy9OAytlucHJ1c3J6cnPg1tjY0MrIdXRt0srAxsbIz87RxsHU2czT3Nzb1dnTzdvb1c/W09HP1tPf2tlw1tLI1cnIyMXEysnK1m5zc27W0tR2ocCKb2/Q23PV13Dcb27RxtLTzNjT2NDS1cjIzc7UzcTHxsfScm/Y1tXP1t3g1NLUbM5fbXHSy9tsbmzTbnJz22/Z2NbUycbS193a397Xz+PmeHLf2HV2dOZ1dePa3OB1fYZkoHPe2N3d0dHg43XR1t3m43fn3tbRz9/c4ubg4ODl4+J2e9bT3ubk2Njd5N3Y6dqA5eDn5+zw8/X2++rg5fP69vjp3fn6/Pj79PuI/fv29f7z9oP9gPXw6+3x++7ng4eAgZG1lv738O7184D32N7u/OTu7vKEhZH1gfz++IGJhO6Egf6Egvz//4qNm42A94CDgYGB9en9gP2B7vmC++vzgOTi+IGB9/nv5vvx//GAg4CAgIH18O3j5/DzioWB+PqD/4WGkILx+oiLhIaDiIWChYaE8/Tj6/z6/vv8+v2D//jk5fr55+rx7+jb2+vxgP6C8+bu7vrm8vf4+/2ChIH37/Ho+4D4+oCCi4eFioqE8IGHvX+ikZONnMOV/v3u6/r/hvyC/vbw8/n17/j9/PH68/aA8Yb2+/P4+4H6/4D1+YGInYyGiID//42GhoH8goeGhoeGg4SFhoyQhIWC9P2A/4GAkovy9oWE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dHV0cNbOz8/W1tPRw8tsa9fTz9PZ19XR09uActRtb9zX3NTIydfV18zb23d2euPndufjdHN6gpy4en+Jh+3j2trqeYF9e3h65+XZ09Pe2nLc29bRztl01dXKbnaFnIi+qnRx4XJ0dHjldN10dW9zasvMamhrbGVqaWfJZ7zMZ8plZG57dGtrZ8rPysC/vbi/wcrM1XN+iXPZ0cuA3XHd19fa59XV6Xt9eHTh1dna3uDU0t7q5HjmfHZ/euhz18/W1N3idnl6dnp4e+bP09HJxNXV4tTk4Ovu6nTT3s3W0unu4+Dk6+jbc+R9duHQz83cx2zU2uRvcHPbbHNyb83Wbm9ua2vLy9VtyMzCbW52dXPXytXW0dzgze7OydaA6MnPy9zb2NPd2tHK19/f5tvx6szQ7OZ6d33c0svHwb7DysLHvsvV0nDL087e19HT1cpycm7FycjR0M/M09fR1NbW1d7UxdbV43zi3N/d0trl4dzg1dHY4czQ1ODedtjh4N/b49jV4OPj5tvY3XF1dd5w0nDX09ZsdG5qyntzcnGAcG/X2NHV1tXT2t7cct7Xdnrf2+Hicn6AdnF0329yb3R8i3zX3NlzcXaJgNhyeHp6c+Ha2HXh4+Pd6NzW19Lb5ebm5OHu5eDT2Nvf3nR14OPk33bi4OTX1NvXdN7a5HbodnPh4XR1d3jm63uI49jkcnHc4ORzdHt3cnfje4N/dduA1ddzdIJ9cnLgct7e2t3p4uDm5XXd3nN5ee15dnd64HXnc3bi4HV1b9Pi3eTo6d3Z5Ozky83Nx83W64aJrX/i4uPk6nh63s/gdXx349bX23XVcXF1eHh4fH7q7NjadoWDcX97iX5wdHVybnVx53Z0d3FybdPQ3HHXzczT1NzieHaA4ep27+Hg39x4dnN8fHt1cXF033Pjc3La19N0cnVxb3F3goN5enN7d3XicXLUb3Hg3NrbcHLXc+DidXN22tXe5efm3tTc2t7d393l5d925HV2dnh+3N/T19jR2Xh01+Xdcnd3c3V5dXFzcnFt29hwcX55e3d0dnN3cnFxd3F0cnaAdnN2fH19dOTa1dHY23rk3ePj73l99fHp7ubq6evn4d7nd+3teXx6dHl2cdtym3dub25zb29zcG1uaHJvbXJzdXuGqpuIfHnp13jm4dvf53nh4ndzenl9eH5+goGAffLk65uB59TH6eHsen7v5OTj6eJ5e+d58Xt+fHz06OPi1tKA1NHg4+rrd9/m3nl6f3fl6H3n2uHn4uR149zZdOHjcm94dXyFd3LZ3eDe3dLf4+fq6N7Z2tja5Nzu4+jhz9/j5ePrin3p5Onf6Od26efm3NnZc+Xfe3936urp6n54e3jf5nl0d3+4f3l4dOPd5uPl5+Ld0uN4eOzg4uTl5+Th4+iAd994duPi5d7SzNLW3Nbf43h3dtjmc93gc29yfJatc3l6edrT1M/ccnt2cXFz4tnNy8zW0nPS1dXT1+h41tvScnyJn4fiyXZz4XBycHTkcdtydHF2bdPScG9xcmxycHDcc9DgcN1ucX6XsIF1ctnb18zJxMHIztLT2nV9hXLZ1c6A3HDWzdfV3tPS3nN1c2/ZztHRz9vQzNvj33LZcXF3dN5w0s/R1dnab3V5c3Rxb9DCx8W+vM7H0MTT0dji4m/S1sbNzuPq4d/e6Ovhd++GgO7S2NvjznDX3OFvcHHUbG1tbMrMaGxraWvMytRszNbBbW91cHHPx9jbzNnbzePAvdKA2cDMw87OzcnQx7+8z87O08jb2sTA1NVycHPR0NDSysLI1tLUzNnZ3nPV2tXi1tnW2OB3eHfY2tnf3N/e5eLc39va39/Zx9XX53/g2t3d1Nvn5tve2tzc4NnX2ePfeOfd3eHf4dza4OHh3tzX3XB3d91u1XDXztRudm9sznpzdHCAgYH7/PH3+Pf3/P/+hPzwhIv9/f/7gouNiYSF/YOIgoePoZH6/vyHgoqonfuBiI6Ogvn284L6/Pr2+uro5+Lo8vHx8u7+7unl6O719YGB9fX69IL59v3v6/bzgfr4/oL9g4D394KCgoP7+4WZ+O3+gID6+f+AgoqEgIb+iZKNgfKA7/GAiZWQgoD8gfT18PT/9PT09oDz/IKHh/6DgIOG+IL+gYX//4WHge378vj9/evm9Pvx09jb1N3n/ZOk0pD49/Pv+ICA7N74gIiC/fju74LtgICEg4KDiYv++sbIg5aVhZaSoZWAhIuGgomC/oKDioaDgvrr+YL86ePt5fj9hIGA9P+D/+/29vWJhoKKi4qDgYGB/Yf+gIL38eeDgISDg4aLlZeOkYiQiIT+g4PzgYL++vr7gYDugPn7gYCG9vH5/v/88+j59/r59ev4+fSB94CDhISM8/ju9vXs8IaB7/z1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eHri2tDY1dXWgGzQ0MnIxcbNzszJ0W9ua2nE1W/Sc3JsdXp+fHt+h657vpt4dXN2c3budnJ7e3R3cXR0eODN1s3a6Xfjed/b4djP0uDi1Mpy19nU19fWwMTGxclufnnQzMzCwcDM09TNdZeGz7zEzL7M2dFyysTK0dLNxc3Gv9NncN/Ua9lxb2xtgGprbNPS09LNuMnKz87K0G/g1292bcvF0Gtva3B1dm9qdHHPbW5xam98cW1sbM7P1dDX4ndwe4uBgHPL2tbl4O/r6drcytzh29/g5uPk6u3w4nvx5tXj5Ofi5tPi3NPKy8vKys7NydTKwsrDxsrHxsXFwM7Lz8jS1XF10MvRcdXUgMzU1djZ3t/f4OF4287K09Ld0NJud8TA1eDMycnLx8y9xs3W29XPzrjOxsnZ0MDEzMrKw8XV0b/HzcvR1d3GyNjYc+XHy9V3ddXPbXJvcNR0dOLZ2+ndeePl3eXh4ejs193R1u3o4eTm2NTd3OHezM3XzHOBd+Fz1MLDwsfC0dbZgOXi3Hd53uHe3t7Y2+HheI2OeeDg5djY1ODd1dZ1cG3Y29/a1N7d2t1wcnF6c3LUb3BwcXDS09rWdXFydHdw3XLe0NTf3uF03eXi3nh/49/p7dnZ3tnI4OHe5XN4eXB1dHfheHva53d65nSLk3nc4uZ0deHcd399c9PX2MZxaG5vgHV1cXJ2fHZ4e3Db2Xrg39zY3nLZeOLu5uTg4dXX3XR5z9Tf2XJzd3l129fL4HvlfoDm7tvk88PE1uV63NPg3tp23d7k5O/k4uLj3t515uBycnZ1dG9xdXZ2e31+eXTZdnRxe3lwdm9pbnJvcd514Ntyc3Rz5XLb3OXf5OaAndeBgPH36ufi2trY1+Hm7O15e+3r7Nvb4tne3eZ2e+Z15uh2eHp7fHl4ded1eO7c6HfkeXng3+Tn5NzZ5XPX4nLi5OHacNvacnF1d3p3dHd3enVydnZ1c3RwdHR0cnFycnJ3c3HWc3XS0c1/t9OqtNvWbm94d3BydXt3c25ubXV0c3J2gHJ2dXZzdH11cdTdc3Xj6ZiF4enp5ujqd3h533p35d7i5OOCh4R9eu7U2eiDgHh3duHf3XJycHhtc2xvbnB6enlzcXN43d3c4+vmcul41XTh3eLm4ube7uzd2dne5YZ8dnR9fn946n+Bfnx+euTe6Hna1oJ3eoDq7u3l6djd29rZgO966O15eep57e15e97k4u16gOp6d3d5e9fm7OF0d3fj4OTqe+rw3sjQ0tjf3dzS3OJ1h/aUe4p5eNfV5NvZ3d/i5eR4e3uzhuZ1duXmdXRyctHSb3ZxcXR+enRzcXJ3dHNx19R3dJ5wz9R0jNfl5uLm5vF7d3l7fXvm29fe5uLhgHHZ29bRz9fe3dnO2nNzc3HY3XLbcHFwdXZ7eHd7gLGXyY9tbG1vbW3WbWxyc29ybG1udNbFzcvX43Pfd9fX3d/T197s3dN24+HW193h0szPzdBxfnzTydTLy8vS2drYd5SH2MLI0sPP3dh21NDK1djW0NrRyd9xdOPdcOJ3dnF0gHN0c9/Y2djUw9DQ1NPV127f2W51b8/L2G9xaGxxcm1ob23Mamhvamxyb2xras7Gz8/O2Hhzf46OnoLR2tHf2enn6t/gz97e4dza39zb29vczW3VzL/Pz9TS2svX1NDKysfR0dLSz9bVzNHR0s/Qzs7NydTT1MbQ1nR30cvTcNPSgMnNytXX2tnd3dhx1c/P19rcz9F0eMrI2uHS0NLT09vN1tTc3Nna2sfe2d7q5tjS29rd0NXh3MrP2NDU2NnIy9vicuDP1999fN/ZcHJwc9tzcd7b2N3Tdtze1NzY2Nrf0NXGzuji3eDl2dTd3t7e1dfp33Z/eup13dLZ1drX4uXugPDp5ICD6PLz9Pbr8/bvhZqbhvf8/vH89Pr58veHgoH6+Pn98Pr2+PyBgYOIhIP1goSEhILv8Pf2hoODh4uD/YL77vD2+/mB7vX07ICH+vD39eDi6ujc+Pr2+4GGiYCEg4T6goTr+4GA+4GbpoTv+PSAgPXwgY2KgfT7/OmGgYaGgIqLgoWIjIeKjIH56oTz8fPt8IDxg/T9+v33+vT094KE4+bz8YCBhYaA9O/j+oj9iYz5/Ojw/8nD2O6C5+b08+uB9fH28/748fP39/SA//uDhIOHiYGDhoWFjpOUjYT4jIqGlpCEioKBhIiCgv+E/fqChoiE/oHx8f74+PCIuP6GgPT/9+vp4u7j3ebr9PiBgPz3+OXl7OXq6PWCjPyD/v6Bg4aFiYaGgP6DhP3u+4L3g4f9/vz/+fHp84Hy/YH7/v7/gfv7gICDh4mHgYeGjIWFiImGhYOAhIKCgYKEgoKGgYD1g4bt7emHudCz5v79hIKJ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zsXJ0dje2Hbm3szO2t/l4uLW3nhv1d3T5eFz23bj4eXl6eDd3nd34NHR087O1sjCv2vIw7K+vths0dfWyMvKx87Tb9LS1IDf0tt43+LpdeFx593a4HZ0c97XcdrWdnbJa2dqcG3RytFv3Ntx3HWCeI+HceLa2+fZc3Zxyb7Khpl45dXW08jO3tnY6uLp3e3o2+jy7eLh3uuJlY/z6Oze1tnP3Xjd4tbN0OPPytnXfHXPzszIxcrFa2tpytBxaMO2qLS1aGxnxYC7wcvLwMNvbHJwe8+5w8TBv8i/u7q3v7bKxbvNy87c0MS/xr/A227K0nDWxNPb1tfY49/T2G/VysvI1NTWbNLab35xxsXKeXBwbtPZdHh0cnFw397Z5dvl2OTr4+jp5d/a5uD04trh3N/UzszR1tDQ0s/U0M7g3nLRx8/RztTb24Dv8t3Y2OHm3N7r7Hfn5ejl3uLgeed2deTm4d3U29zQ19zd4HBx3NZvb3Fu1dNsbG1vcnFybnJvcdHTbNfZ2dxub3d7c3J0e3d3cG6Ae9je33RzcNzgdoB+eHV5hod4f4N8duLX2NnbdXJ8feXTderl5+Hh3Nvj4OLhcXZw29rab4B0eHRzcm1sb3DfdXPb23TYd+J3d3p9e+Tmd+ve5e17f3t1c+fj4uV04Nfm1dvc3uTb4uV15Xng0dfaw89ueZuTenRwc3t3d3XU5uh04uTi6X/g5nZ67ensd3fn5e54euzi5ON0entzc+bi2d7j5XXi3XTY33Xm3+Xm6OHp6Ozm5YDi4N/c4ePnfujr6unn6Xbr293pdXh4f3Th0NZ5e3nudHTmdHd2ceJ0d3bndnZzd3R04OLh5NbS23rf4NrY6uTi33Rz12903NXXcXN4e290eX2Be3mCent1dXZ3fXl8fOd8eXp13uPY1+qB6XWAgH57enVxdXZw23+EOG9w5Xp02oDVcX+CuX3fe3bmdeHmd3Xhc3bhdnd25+fm5OXk4nfl4HZ2dOTj4+Z/eHh4epN+dHN6d3V9bYF0dHqCeHRydHJxdnRvcHDSznZbkXN4deHf4eHq8eXl5ODhdnJ6eHZ0cnZ4eHd35nZ9fH6Fe3pzb3F5dHR4dHp5c9vjeXd2e+fe2oDa6u3q6eTt8PPz8/Xv4PDg5NrndHXidtvT23Lbcnh533Di2XR04950deVydXN25nbfc+B0e+zf0MjhdtjS3XqI3M3OzN5ydeN2c3Tf2nPV09nbynB0ctrUxMbEzcvLyMzS0dDTb3RsbHVtcn7T3Nl5cNPdgp6mcm5uzG1ta2hybhBvuWptbm1rb3Nubo3KgW9zhG+AcHJp0Gtu0XB2cHN0eH1wcnJ0fnPa1dXSbd5zdNra2+Db0MXX33fVzc7U3OLdeero1tPc4OXk5tvid3DU2cvc227Vb9nR0tna0M/Wdnbp4uPk4uLs5N/Xeujf0N3b53Xo6u7i4uDb3dd03tva3dvmed/b4XHbb9rS2NBubm3S0GyAy9N0cs5ra2tuacrH02zQ12zWcnt3joRw3NnV3dp0eHPOxc+WvH7e0dbX0tPX18/XzdfQ3dfO2NvX0tXQ332Ef93e3dLN1cvVctba1s7P1tfR3eR+dtjc2dPY39t2dHTo6Xt33M7By8hxc3Pa0N/g2tLZeHV8eX/fz93c3dbd1dJs0s/Uz+Xe0eTk5+7l29rc19jtdtzldODR2N7X2Nbe3NHZcNPQ1tHZ19Vs1dltcHHV1dFvb3Bt1tdxdXNwbm7Y1Nne1N/Y3ePf3+Tj3trf1eff293e4NzW1tnb1tfi5+bd3e7md9/Z5uXf6PHwgP744uDh7vfv7f3+gffz9vHx8e+G+4CE/vnx9u/5+u/4/P3/hIL9+YWFhIP+9oCBg4WNiYqGioqH+vuC///6/4CDjpOJiYyVj42GgpOO+fv/goOB/f6EjIiDgYWRk4KNjYWA8+Lm7PWEgYmN/eWB//z79/Xw8P33+PyAhoH78/WAgIiNiYqLg4KDg/+HgPb5g/aD+4GAgoeE8vuC/Ovw/IWLh4GA/vft+oD48f7t7vP0//v3/oL8hfrr8PXe7YCOuqyKhICCiYWFg/H7/oD3+Pf5i/z8gIL99/qAgfLy/4GD//r39oCGhICC//fr9fn5gPn0gvL0gf72/vz+9fr4/fLxgPDs6ebw7POG9/bw+fz/gvnn7/2ChIOMgfrl64iLhv+Agf+Bh4WA+YCFhP+Dg4GHg4H49O/06ODthfX17+f9+/bzgoP2gIf98vyFhYyQgYeLjY+IiZOMjYWGg4OIhYiI/4mDhoLy+enp/Y39gI+Sj4iKg4CFiIH0nP6DvoD/iIL5gPCAkZ/VhPOIgfuA9fqCgfyDhPiDgoH89/Py+fbzg/33goSC+vf4+4+JiIeIoo2GhYyHg5KC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v8HJx8LDzMfM1dt1gnfY19DS43R2d9rjgrWQevCHgOvxfX3h0dfS3t/c1M3F0NTU4di/xNjVcHrWbsnC1GvBzb7FysrBzMvT3MvOy4DO3eXg2nHW1d1xb9TLwM/a0dLOzsrMzeB0d9rV2tnT19fTxtbSyM3d0tPY1MTMbdTR0tfJ0MzWx8zdztXX4N17hofofnp33nl87+7a2Mzeenl25nPgdHJ2eHBx3uLTzMTF0MHJbr7EzcPCvbjHyMy3xL91h3fUzsayt72/w9DLz4DJwrmwwG3Pu7rJy9zP0szNzbptdYWFd9bCwdbi2dXd1s3HtrDN33rm2Nfie+Pez9fSc3Pb4eBx3M7O19xs0c/G0NRs09dva3BsbnFzz3aJdJp0cNrW4OR5d+p5d+Tr23LW5+zqeuTo6Xbf0tfU1dzWyMt1c3PhzM/Yzd9+3dvX3oDw49vz+ebX5Ojj7XvrgO3e2t544N/gddzk5XZxc3Pf29fW1nJtcnTieXHb39/n5XZu295wceJ03NJvcnDZ19Zvb3R2e911eXJ14XZ7q2155dp6dt3ccuPd5HmDn3no6ICCfXXa0cnXd3l2fefsd3pzdNt53N52dHd5dHNzdnuAfYB1dHZ2dXLc0XRxcXJwc3Z04t7YfYGZeefr7/nq5+DhfHnj5OV7d3J1gX14c3d55eLUc3rndON6eefZ4eJ4fr2N5OZ04Hd6e3no3+Tl7OrieOjY4eLufH98g3/q8/v5fu/44OPxfHvteezr7u15efTr63Z2d314cHbk49nZ397cgoCifnl14XDa4+PqenjY29zkeOnl6eTh23V8eHd0dnV06eZ4d3d5fHl1enyAh3h26XZ/gXp14nPT4Xt4c3Dbdn5+eXR0cXR1eoR65OJzcXd8d3RzdXrZb3J8fXt56Ht7dXZ2e3+EkZiA8e7se3t7g3vq5+fc5ep97Onn6OTe3+HfdIB0cuPhdOJ239LWy9HadXiMqnt75np15+js4+N3dnXsdXHi5NLidHR2dd/X2d/ebbaFeXJ1d355fX5+eXVx1HBucW9tc3t4dG9ydnl0c3h5e3lzi353dXBvb29yc3dxcW5wcnFxdHSBoHpzcm/QcHNxcXJwcnRycdrRc3h5cXZ1dYBydXp5eHp4dubo4O50debj5+vr7+3r6+3q6ePkd3bj1Hp24njS1HTa4OF0dXl6cnFycNDTzWzbbHBwbGts1NZvd3aDXoN7dXZ0d3bY2N1u3dzf6tbfeHXe3NzbcYF3mUt00nrVy3J1cHRvb21qa2xycXJydX5+dmxpaWxxaWhlYYBla2psZW1waWtwb2hyamzNz3F2dnFwbm9xdnN0ctZzcXN2cnBwcnFxcXN1cm7PbW5sbW3V0tHX1M7L0c7O2eBzenPT1NLX4XR1dM3Tc5Z7btd6dNXdeX/s2tzb5Onl5d/a5Obk6urb1/TrfIjneN/b6XfX3dDT09jS1NPV2MvM0YDR1Nnh33Lh2dpydN3SvMvQ0djU1MrLzd9zeeLg19nT1tjYz9XT0tff293f3NXdc9zX2t7P0dLbzdDj1dbU1tJye37fe3Nw0nV37OHS0sfSb3Bs0WjQa2tvcm1x2+fb1NXa59reedrj59zd2djl6ejV4t5+ioPu49zHz9jZ3ePi44Dd2tHF13vn1tPj5PHm6ePo6dN2fYydhuXW0ujv6urw5t3Vw7vM2nPd0NLXdd3Z0tXNbm7Y29tv3NLP2eNx2tnY2Nlu19hubHBrbm5wz3GLjZ5va9HN09dvbttxb9Pb1nXT4OHndOHm43Xk393c2+bd0Nh8e3jq2tjZ1/CO6uXj6YD97d72+u3f7fX4/4L9g+3n6vKA9vv6hvb2+ISCg4L/+vDu8IOAgYD6iIP/+fX994CA+veAgv+G9e2DgIH69PqDg4mKjv+Hi4SD/oOI3ZiL//eNiPv1gv/2+YONsoT7+YuPjIP47OTuhYaBi//+g4eAgOyF9PSEgISHgIGAgoaLioCDg4mHh4X+9IqFh4aBhIWC+fPpiI2nhPT2+P/u7ubtg4Dv7feHg4GDkIuDgIWH//7tgYb+gv6Hhv/s9fmFj9ie9vuC+ISHiIX+8ff3/Pbvgv/39O7+goaCjIbv8vr/hO/44+v9gYD6gfn08/iAg/z1+YCBgYaEgYT9+/Dz+fnwjYCwjIWD/YH2/fv9hIXt6uz3gfz6+/f/9YSOiIWAgoGA+v+Kh4mIioeDiYqNl4aF/oGLjIaE/4Du/Y6LhoD3h5CPiIOCgoiHiZSL/v+EhIuVkoaLjpL9gIGKiImD+4mHgYKAhIqPnaWG//z+g4KEjIP79vTr+v2H//76/Pry8/n5gYCEgf74gv6G/efp3eDogYOl0oiG/oWA9/f58e+CgoH/gYD7++b7gYGDgPDw9/7/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b3B0dGhpw8PRa2/azsrEy3N6s3vS3Xx7t7WF6Xjq3nRz7OR4fOny5+fq7/Pvg4rp3snIwczRzM/Nw8bX2NDFxnFx1W9s2NnW1dXK09fEv8LZusHdz9LVgNbR1efl7Ojg6/Lm79vd2uDm7uTj5ufX0eLc2Obv9Orp7ezs6NfW6eru8ubyfnzi3+3sf+rf4t7O1ObfdHJu1taJV5fT0M9xenrj53Tm6Hfb2dTV097Q1M3Z2s7Pxs3l39PP39jIytbRzdPVyM7LycnO1s5rcHJxz8zU2NTJ0tbWgMfPa8LBx87G0tTWz9hvbm1zgDcoPIBz29rR2unk19fS19LSd8zN1ed74tXc5+Xs19/a0tly2drj09vbzshucNbV07zJxtrW3+LU2trSdY2S19blcXTk5ul45HR0eN5xdeHa09DjdXlydnvh1czMztnb2+Tj3Ojr7eHi3N3V0sXMgNDl5ODg4OHm6NvY6dWao33oma/k2NPa2+Hh38/cct3Z2M/P1m5zc93Sz9dy4N+IuMaC3+J4en7f5+d36nju6Hl9gHlz4Nred4d9d3F1d255bJFydeV063h46uXr6+168HmBk3p6dezkeHVzdHRy29tu3XFy3HN6d3h2dop94+LdgNlzc+fn4+F04G9z5nR1cnN1dNvnjpyCee7r1u3j6eTr7Ojj3tnT7Pd9gnp4dXR2d359enh7eXfn4nuTd8fj5+Xd23l4d3TfdHNydXRxeONydXHbc3fneODo6erp6O936+ryefLqe+t45PB+fvHw6OroffDo49/Y4Xntd3t8d32GgJp+e3h36enn6Xbg4OHndXV2eXZzdHTf4+fn7uvq7vHo4ejqd3d5gHhydXLa5HXhdeBxbHZwctnU125vbm54btXTbXR7fXxwdXd4in59fHbccHFvcHFxcHNyeXd0d3x5eHrldYF6eXd1d3fjeHh45djf493T3Nrh5XNzcuDldHLggHTdcXTie3jj3tjcdd7c4ePr8O7u7ejw5+97jH3ofXzt833j4+jk3uPo3tXHzNJ2d3h1ctzgdXt2cHF0dXJ8enZ+b3p0a2p8dHl1d3yAeX17gKBddHR0dXDab3Ft2HV4dHR2bnF5dnV0cnR0dXh8enRzyuOAceDg4t9ydHd4eHZzgHLZ3NrnfHbb4OXq4trn7uTj6d7i2ePo7dzg2dbneXh3f3Xg4nLf2H962tRxcW1ueHZvcXV0cnJxdHV3eOdzdXvpfHXb2OXs4eV7f4Tw5+2Bhbee7+bne+fp3t7c53V1cXHfc3Bu0t5xb2xvbG1vdWlse3lycnF6gHZzb21rc3hsgGZybGpscnJvbW9xb3HYbdfXbtPZ19TUbm/TbW1t1Wtu1nFubGxxbXJwdXZ4dm9vzsvTb3Dg1tjR03R3qHfP2nJkpqN54nPd3HBv4NlxeOPr5eDi6vDsf37t4M7LxdTc2t/c09fh5uHY23l75nh04OHe3t3N0NjFxMzgxMbb0srOgM3O09bZ6uTe7eHg7NbZ3ODZ29/g2d/Lx87U2Obw7uPk4Ofc2dTS3+Pj5+HndnXY0N3gdNfT19XFzNrYcnFv2NqCVpfIyshqbnDQ2G7V3nLY1tXX1t7X193m5eDh0t7v59ve5OTb1OPh3d/c09fU0tnY4t9xc3d23Nvd4uPX2drfgNHTbs3HyM7J1tbWz9pwcHB3i3xVTqF64eHY3Onp5ObW1tTXec7M0OJ01MfN1tDZztzW0+F439zf0tjaz8htbdfZ18vLytzZ3N7b39/ZdZGS1dDfb3Dh2+J03nFyddtwc+DX0s7fdXp2eHvj2NfW19/h4uvr6O3t7uPl3+Dd18bJgN737url5O/7/Onn+eaXmoD8pbj18u709Pnx9+/6gfz29urw+oCFg/rs6u2A/viTy/CO9vuEhor0/PqC+YD8/oOFi4WA+/PxhJqSiIGGjIGOhLKCgv2A/oGB+fH8+v+C/4GKnYODgP/4hIKChIWE/v6C/oGC+IOMh4aBhJuO/f74gPCBgP////iD/oCC/oOGgoODgfD+l6iJgPj25v3v9ezy8e7r6OPf9vuDjYeFgYKAhIyMiYeKhoL684SfgdP2//z38oaJiIX8g4OEhIKAi/2BhIP/hIT8hPv7/fz49P2B+PX7gf/vg/yA7/yCgvn97fb2hf/28u3q94D7goiIgIeUgKaLiIKC+/35/IH39+/7gICChICAgYD09fb2/vv6+P7+9f39goWGj4iCh4T4/4T+hfyDgIqDiP74/IGEgYSNgf39gIaNjI6AiIqHn5aTk4f/gYGBg4WKiJCMlY+Jio6Jh4r+gY6Jh4iDg4L0g4eM/fT3/fvz9vH1+4CBgvf/hoH6gID8gIL7g4P3+O7vgPTx8PH4/f3/9O7/8vqCkYLwgID8/4Ty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2ujsztvp8/+UfuDnzNTY0MPI09DDz9DHx4Bq087Wzstu1GzL023V2dt0cnh329vlc+Hk3enl2+Lj4NbOzuz04NDayNzU4dTg3IF909Liz9nX2NPO1dDQ33DV0MhwztBpxL/Sx8TBx2xmxs3NzMZs1tPX32/WcdnVb2/Pbm5rtL3Y1tHX1Nzg2djcxsbh6uTpeePTz9Xo2M7R1oDe2se4ynqG3c3R23XSydFwcddzb9FwcXRvbXLTbtPXc3J1e3h6eXt5cnBw2fd5d9LUxst+6OLn6+bk4+PY4Nzh1uTu1snf4+nl4OLe397a3dXE0MfFx77MxsLU18HMy9LW4MrVdG9r0WzXa9TE1mtu0nFvd2zQzsJueHrVdHHN2RrZ0M7QcHhw3c/idnjj0tHX0Nzl3NjR2dPO0IDS0NXje5p9ddTU2Ojlenl3gHxy3nXU2tnac3x63nLc4ODb4HB6cHJwcn564eF4gqyP3urseO58hHd6gH2Bi4CWdXPk5+d05Ht0eXN1d3Jx0NV0c3Lc6HTkdON1dd7W2OLfdHh5eXh57ujheXzfdevi6Hfc1t3X18/NxnJ1d3d0hYBycXFt3N3dcuHbcnLj53Z3c91yeHqBgICA7/bw897c8O3v7u7q6PaA8u3z6O3rc3Z3dXV+c+Pa4HpzdHBwdHN0bnBzd27c2Nja1Nnd23N9ddXS1XHecnJ0dnrf33yHfHnmdXfl63t3duzj8ubq5+nl6e3W6HrX3t/e7Xl7eHnq7YDl4dPid3XkdHV4d9rJ3eHi4eFy4d505Hx7eu3o397k7nx+8/Tr7YWF6X589+vy6tvd4eDe3uDZ2XJwcnNyb3N1eXRsbHFvaW1tcXZ53XN7fnh3d3p8fIB0c3Fxdn9+fHVsbXFzdXZyc3pzcnV3dHR8eG1zb9vk3tpz4tzh0eN3eYB2dXXkenqAiIOJen+JkHTj39J1d3R1enh6dnjje4Py8nnt6O7y9/bx7t/f5+Tje3yPeNzY0d55eeDc3N/dd3R14NZzcXFzb210dHRwc3TkdHjqc3mBhoJ66O7vevPj5uri2Nl1gXZ84XN4cNvactJw13LdcnBsbnDabXJ12XJ0cjdy2uDW5ePl4eXec3mDeu3wfH2MgeHh9ubW1erh3Orf2t1133RybnV0dG1xcHd2e3dveHR0edXbhHWAd3Xi3uDj6eHm4u3u9/fy9vDy9OPq55uLffPt5djj3ePn59rZ2HTv5e3n7ejmdXF1eXHXd3JydHd1d3R1b3Ryd3F0bG5ub3Bx23ZwdHRvcHBrzsrXvMDUbNfRcnJucHF4dndv2dlz4uZz4HXfc3h4hnju5eDj2dnf4+Lc2+Tue3mAdXZ6c2+Sznt0zdzfc9rR2tfTzdTdx9ro6PKMeuPp1N/k4Nbe5ubZ4OTg4HTk3+Pb2HHccM3QbNXPzW9zdHDP0dtu09nY4t7X3tve1dPP5PXk1OLU5+fu4u7qhoLk7OrZ4eTl39Tc59rsd+LY0Xbg5HLS0OPT1dPYcXDT1tfS0GxN2NjY3G7TbtbSaW/ScHFtv8rV1NLT0dXZ2Nrcycvf5tfidd3U0tnn2M/S293fzsTQfIbcy9Pfcs/I0m9u0m9t0mtscG1rbdJr0tVsa3CFc3Nybm5x1OJydNnY4+B25dnd5uXe39vP19HO0NbcyrvW2d3a1NXX1NbY4djO1tLQ1NDc1tfk59Lc3dza4c/adHNx2m/bbdXQ3W9x2HJwc2zW1cpvenzZeHLS3N3e29tydnPj2uN2d+XZ1NzY3uPg4NLa2s/RgOLf5e2Ds4mA5+bt+veEg4GMi4H3gvX49PKAjoz9g//9//n/hY+Bg4OEj4f/+oiRup3z/fuA/oCMgIGHhYp/dpqCgPn9/YH9joGFgIKFgYDm7oGBgff+gf6C/oSF+PL0/fmAg4SDgYL++/KFifSB//P+g/v0+ff77+znhYqKioWfgIaFhYH+/vmB9/KAgfn9hYaB/IOKipCMiYT09/T35+f+/Pv48PHv/YX59vru8vaAhYiHhJGE/fT7ioSEgICIiYuDg4aJgv/7+v31+P/2hI6D8e71gPyBgIOEiPTyiJGJg/qAg//+g4KC//X89vf18+7v9ub3hOr19Or4hoiAgf79gPLs4PWBgvyDhYqF7936/v359YD884D1hIGA/fbp5vL7hIX//fHwjY30gYD46/j56+z18fb39fP9hoSEhoaCiIqNiYGFi4qBhIWJi4/+hIyRi4uMkpKSloSEg4SLlpqajYKDhIaKi4iJjISDioqLiY6NgYaA9/rz9IH/9vjo+IOHgIOGhPmIh4uWkJeIkJ2qhv/88oaFgoSKjImDgviGi//9gPfw9PX6//z16Orw7++BhaiI7+bk+omL/PH2+/aDg4H684ODh4iEhIuK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1HLSyrnKxtTX2d1qcHB0bdvg2dPY5Nja18vQ24DQv8TFxszLyMbO2729yb3UwsnR3/Lx8eXjm4/f1NDV3NPMy83avdJ64HCNbb69xMPExca8t7m5zcrI187W0nJuc9LP1uDa5nTi3tTU19ba3tTS187Uy8jR09bSxr3K19XQy9PYb3DZzHFvbru/0snCvMdtc3PXzcxwdm7TcN5xdIBy4uqBe+l9edp15d51fHje3+bm2eTg2dXfeuzL3bjb0tnb19rZ1dvc2NfMzdhu1M3O0sy7x9DR1dfI0MzU3crJ18bBwm5tbG9rZ2hkxcXLaGfGx9DO0MLIx8toasjNaWTO0dTU0td/cNPN2NXldHV96OXV3+bd1dfS1NHc19rh4YDW1c/W4d7c39fVsoeEd3JzddtzeIOZenV3f3htcXR0c9hycnh2eXN2dndydXl2cHbl39Xg3+Hi6uTq4ubq6e7b4Obr5Ofr4Onqd+rc4+blgXhzeHXje3RzfOp6gXl45+PY3YR4f+Ti2tTX3urm4eXeeHru4t53dHPe4dPR29xwcIDd2G5yb3V2b3Fzcd/g43NvfnHV09fpfIJ1i16B3uvs5uaAhYB94e/s4n/t6d3p4eTw3nRyb9tydXLecdzZ3s3Y4eLk29vc4nd3jH116trWeHbgenZ2dnl54Od56ud8fXzreX15fX18eXp+fu94gH774ezl3+jWdXrsenXa3OF3dmt55PDictvh4tZydeTW2Nzi6OJz4HR2dHB3dXVz2Xff5unneuzr8eTn8PD59ufz8OPl5tjk4uPjeXl3dHN249rddnDLytLYcNxxdnLPcG5vd3pydnl1cXN2eHx8eXZzcXJwb3NxcXJwbXFzd4RxgHTXbnDe3HDX2dhydHTi4HPRcNjZ1drgcnV3dtpyfW502Npy23J0dnh24uXicHl8kX13dOh3fnp5eeft7n5+7/Xt6eHg4Nre3ebq5ON1i450b+B03+B7e+PidHN15HFydXlzcePk5Xt0dOV3eHrj4t3m6ODmdXno4X56dOHl6uXegN3i4Nvn6ePmdX3S1dre3310dnR5cdpzb3d0eXNzcHZ1cXZx1eBycnV13NZvdHVydnl3fuXbduPcc3t02dbd2HV4d37i3HRw3eXecnh7d+nrg3CG5dbg29PX4+jm5nd153LM3HaA0edy23bW3HJxbXBxcXFwcHFwcNZvcYGhc9N2gHZ4cnJ1eHh213Nw03JtcdxwcNR7e3LR027cddnXcuLc4Xzi39Pbc8vXcNzc2eJydXVz4M7Fz93hd3V2dXTc2ujicd7hduHc3dCQoobezNvbc3mMeXp03t7d2N123tLo3dvV193o5uLo7PiAgPXp3NXY4dbQ2t913eDN383Z2NjZgGxvbnJt1djVzdLf09vWy9TT1srJyMvX1NPR1+3Qy9HJ18bT3+f17vHn+NDX5t7f4+jl3OLf59vlg/R+m3rY09fZ3t7g1dDMzNvV2NnP3Nlzb3PR0tbd0d5w3NjT1djc2dfY0NzU2NDP1NfS1c3R09nZ2tTe4XJy29h1dHXOztjVgMvGzW1zctrOzW5zbNZt1WtqbNXXdm/ccW/TcNzWbnVy0NLb29Hc2tXS3XfdxtS40NPV08zV2tLY2tXTzNDabtnS2d7azc7b2uDk2d/b4uLW1OLSz9J0eHZ2cnBycNjc3HBw1NPb3N/N2dfZb3DV2nBs2tzb2NTPfHDZ0tjV2XBzEXfj5dXd49zU3NXa2uLg4uPigODr4eny6+z99eu+ipWIgoOE+YeMnruSioqXkYSGiIqH/oOEkoyOhYaHioSIiYaCgf/y6Pfx8/j/+Pnz/f38+efz9vTt8/ns/P2A//X5//yPhIGJg/+HgICJ/IOLhYD87uPtjYKL+/r27fX3/v739+yAhP/184OBgvP05en2/oOBgP/2gISBh4qEg4aD/f/8goCOge/q6f6JlIWmgJPv/P3s8oWNiIbv//3wh/b05vfn7fvrgIGC/4OFgveA8vH54/H3///79O78hIOah4D97OiBgvSAgICDg4Lx+YL99oSFhvKAhIOEg4CChImG+ICIhfvs9vPt/eeAhf2Hg/Lw9oCAY4T5//mA8vb79IGB+e3v7fT8/YL6goaEgYiFg4Hzguv2+PWC+vz86+zy7/757fX67fDv5PTp7/aFhIOFg4T46vWHhOzs9vyD/oOFhO6Fg4OYl4eLjIiEhoaHjY2LhYOEg4CBg4SFgIKBhIiDioiDh/+ChPr7gPb29YCCg/r5ge+A9vHu9vuBhYiH+YWUjYj28oL3gYSIhoL49feBiI2kjoiF/oGHhIOB9Pf1gIDy+/Xt6e7r6fLt+//8/oOcoIKA/4X9+4qJ/fqDgIL+gICDhIOA9fr+h4OC+YKFg/f68fn69v+Ah/32gIeFgvT5+/jw7/X37/3/9/qCifP29Pf9jICIh42E94KBioiTioaEiYmDiYP2/oKBiIf99oCFh4KFh4KJ/fWF/P+FjoLv8vrxgYeHjf/6goD39viChISB+/yct5r15e/p5Ofo9PfvhID8gNz2hJDw/4D6hfX8goWCg4WEhIOBhIWFgPiBhpvJifmMjImDg4SNjIf4hoL0hoKD/4WH+o6PgvH0gP+H+feA/fLzhvj16/iD5PWB+vrx+oOFg4D949ro+fqGgoGBhfLu/PyA9/iE+Pf37pmflPzk8/qFjKCKjYP7+vz49oX55P7v6+Pl6fz48erx9YWD/vfp5erw5+Pu9YP2gPDh8+n6+vv8gIeDiYH6/vTy9P/u9e/e6eblz9bZ1+Lk3dbg9tLO18ney9ji6Pvz9Oz52dzv393k7+bg4tzq3+qG+YGnhOzo7/D09/To5eTj9PT1+/H9+oaBhfLw8/32/YL8+vX09+zs8fHo7eDn4uDm6uPo39vg8O/u5/DvgIH4gPWEg4bv7//88+vvgIWH/vDwgIWA/oH5gYKB+/2JgvqFhvSA/fGBiIL28/v26PDr5eXtgPHb7sbl5urr5Obp5e719vPs7fqA+Ozw9u/Z4u7x+Pzu8e/7//Lu+ufi6ICDgoSBgIOB9/r8gYH39fL0/u34+fiAgvf/hID7//z49+6NGYD67ff0+oSBh/Hy3ur449/m3uLj9u/q7e6KfAJ9foV9AXyOfQF8j32ZfAF9hXyFfQF8hH0BfIR9hHyDfYt8CH19fHx8fX19hnwEfX18fIl9g3yEfYR8hH0Cfn2FfIR9hHwBfYh8CX19fXx9fX18fYx8hX0GfHx8fX18hn0JfHx9fHx9fX18in0EfH19fYd8D319fH19fHx8fX19fHx8fYR8gn2HfAJ9fIh9Anx9hHwBfZR8hn0FfHx8fX2EfAZ9fH19fXykfRF8fX18fH18fHx9fX18fH18fYV8hH0JfH19fn18fH18hX2DfId9AXyFfQV8fHx9fY58hX0MfH18fH19fHx9fX18hn0KfHx8fX19fH19fYd8B319fHx9fX2NfIJ9hXyGfQF8jX2CfIR9gnyIfQh8fH18fH19fYR8hH0HfHx9fXx8fIR9BXx8fX59inwPfX18fXx8fX18fH18fXx8jH0BfIV9AXyJfRp8fX18fX19fH19fH19fXx8fXx9fHx9fHx8fYR8BH18fH2EfIR9hnyFfYR8BH18fH2EfAN9fn2EfIZ9hXwBfY58gn2KfAF9iXyFfaV8gn2MfAV9fH19fZJ8g32GfAF9nHwHfX18fH19fYd8HX19fXx8fH19fXx9fH19fXx8fX18fX18fXx8fX19inwBfZN8AX2WfIh9BXx8fH19iXwGfX18fH19hnyCfYV8g32QfAICBACAxMHGxczQ1MfMyszW29PXcn513dxydHXY2W7PyGlyrKPdzM+Ac27YbXZvb3N2bd3gcdzB1HLje3XX33BvcsXQ1eXh3tDQwMzTz9BrbW96dHR0b3Z6eHFvcXlv29/Z4efz7uzq6OJ56Obj2e3e4HV16fHt8u18fnPec3Dh4XXh3OiAffKAfu3v8urqfIitjYJ8f/L08O13fe5343+LfHro6OhzdHFszbzV2dractnQzM7E2M/R1mzQzdVxc+Df1d/h1Obi5Obt5Orx49zm7evofXXUx9Lg23R3cXHJz3txcnR0y3xzcnR22m5y3NDf4HPidnHc4Hl2dnNxdIB/fJGAcXGA3nZ7gnJ0b27PznNv2WttzG/abnt7enRzu4KQeHhxbdvJ1XB02s3Oz8zVbm3Oy8/S3Nnb1NTb09FvddDUxsvMdHXPy3Bzb3t4dnF4cXhzdXaBeG1qcW9/b2xtc392cdnFc3d0cnN3eHJzcnl4d3V6eXBwb3Z51tjf0btvdZJ5cNaAcm9y1dVzeIWJfX+FmnNydn1zcHZ6fX96e4GL7XmChPT47+Xk39Xd6PfzeXXk0nN03tPb2tzb2tbjdHnj43Lcdn+CcoWAg3x0e91z1nd8fH9+fO2Agnx/cud2eHx5eXWCgXp6eXt2489/dHd73Hd0dnp1x8zJw9tsbHNwyM5vcICAf3Fy33NydNl3d3hydnd1dXbdcn16bc5xd3J0bWZkbG5pZWtzbG9tcHJveHJx2HByb3Z1dHJvcHLeb252bc/UxMJucWxxcnBsz2xxdWl1bXBtbHJraWpucHtwaWfRwszIymlmx2xpyL9mxmtyd25rZ2fDxsnIZ2xpadFrbmxr0W2AbsHKacpraMNnaXdwcNVxcXHXbWpybWtob2xva7rH0Xl+bXFybWfNa89tcm5ycGnHwsLB1tJrxLe/zMbPx8DMx8lztMPX2NrQz8DN3H6Hedna0tvUzdjW1tbC4YHXx9bG0NbV2+DTvNbmz9jV53zj2OXk3XV249Xi3NvfcnTWxcGAytG5yMTR08/Ct8rOysXNysLL0dDAxtzcckx3wc1na8Noa2jKyGlsa9DKzMDJxM65u8e/ysHPys7U3Nhy2XjJ1drm3nN2eHfc1MjT1Nhw3LvV2M3O1s7PztTDvM7IZs7Mz8rLx2nQwMHOcHXSwbrKycrTxsrHwMS9yL23wb9wyGmAysdux8jGwMHSasdtfH3a4HDYz8ve2NrVxcbTz8/F3trWxdLS1MzQ0s3Gzdfay87Nycy/zW9t2dvVbttzb25qb2lvedZyb3Thd3h2cXDRwLPBxc7Qz87Kw8JxdX5va21tdcrEzdPV1tXY2NPLzW9wgW/QzNnLy9XU0M7a2czSzMsn2NLW0dnc2s/QzNLd39bccHtw29dwb27b2W/Szm5zvrnf2teAdnPjhHOAeHxx4ORz4NXhd+d5d+HmdHd2zNba6ebo3dvS2+Xb33Jwcn96dnd0en9/eHh2fXTg4tzd3ezr7Ozt6YDx6+fk7d/gdXbm5+jl6Hh5eud4dubmdOLc4nXjeHfc39/g3HF6onxybnLb3eHccG/bcNx3f3Ny2NjfcnZ4efDc5ODh33GA4+Xn6+Dw5ubldN3V2nN25OXf4+/f8+vo7O7n6/Dv7PL28ut/e+PQ1+bleXt3etnggnd4eHfXfHR3eHnpc3Tj2Ofpd+l2duLfdXR2dXR0eH1+j4Z5eut9f4N1dnNw1NZ3cd5zdtxz53N+fXt4d6Z+iXt8eHPh2OF3eOLb3uTe6XaAeufn5ert7vPu6fPm4Xd+6+zc2Nd2d9zceHtze3p1c3p1e3d4eYB7cG51c4F3c3N4gXt13dV4e3hzdnt6eHl5fX16eH58dnV0ennb3d7bynN3lHh023Fxc9rWcnaFi3t6fotvbnB2a2xzdXN0cm10ftdueHjj7+fe6enm6O33/XyAeu7jfHvr5O3s6uvq5O56euzudeR4foB0fnt+e3Bw3HHQdHV4eXp14Xl6dXls2XBydW5vcHd1cXFwcGvXwnZrcHTRcXBxeHbI09LO43J1eXbc3XR1io90d+h5dnbbdXZ4dHd3c29x2HF7gW3Scnd1dWxqbHFzbm5vdnBxaWxvcHKAbWzObW9scHRwa2pra9RpanRv1tDM2XF0bm9ycnDXcHJ3bnt1dXN0eHBwbXJ2fXNvbtDPz9TWcW7TcHLc2HTadXqAenp2dtra2NtxdnJy5XR1cXXgdXXN0nHbdHHVb3B6cnLWc3Rx03BudHFybXBwcGzAz9d5f3B0eHNu3XHeeXyAeHV1c9vY3NXn6HXcy9Hg19vZztXP2HvDyt7d4djYztXmfoh+5evo6+HZ49vg3dX1i+/l7uDk6uXp5sy90NrP2NTddNXR2NvabXHg09rc4OJycePW1d7p0eTW4efi2NDd4eHc3+DZ3uLfz97p53yE2dXgdHTgdnl26956fnzw5O6A3uTh5M7W29Xb2uDh49rh4nXaeMnV1t/XbXV1dd3VzdXW23Haxt7e0tDSz8vR3c/P29px4eHe3+DbcuPV0d54fODTzdfV193V1tPJzM7ez8rR1XfWcNrYctra28rJ2nPYdnl3399w39TQ3NfZ1svO09HVytzb2c3Z1N7a2NzW1NlV4N7Y397Z29DacnLe3Nps3nZzdHN6cneB5Hp3dOV7fXh2c97Z0d7g6Obj4eDg4HmAh3VxcnJ60MvN0dDV2dnf29XVcXWJeOnj7+He6+jr6fP25Ofh4oDp4OXi6enu4evj6Pz79PyBjIb/+4SFg/r6gvnxgIXQxf/19pWHgv2Bg4GBhImC/PyA8+PwhP+IhPL7gIOC2+vu+fj87O/m7/34+ISDhZONioiHjJGQh4WDi4H2+PHx8//8/v/784D48u7w/+zxgID7/f3+/oGDgfSCgPX9gfnx8ICB+IKC8fb6+/2Bi6uOh4KG+/759YCB/IL5iZSGhvr6+oGDhYT+5/Pz9/aA//r4/Ov88/T7gPjy+YGD/frv8fjm9urw9PTq7O7x7vH+9/GFgu3a5fb5hYiFh+z5kIOHg4fqioKEhob+gIP98Pv+hP6CgPT5hYOGg4ODjpGMoJKEhoD9iY6Tg4iCgPbziID+gYD0gv+DjY2Lh4a3f5qNi4aB/ev5gYX98PHy6vuCgvXx7vP9+fvz8fn284WK+f7x9PWLjPryhYiCjYqGhYyHkIuLjJaRhIGKiZiLhIOGlYuF/+uFiYiChImGgYGAiImIh4uKg4KAh4v3+Pzz44SFqoqA/4CFg4X/+YSJnqSNj5SthYKDjIaEjY6MjIiBipT9gouJ9fnz7Pf67fTy+fuAgv3yhIL47vLz8/Ly7/SAg/v7gf2GjpKFlpGVjYOC/4T2iIiJio6I+4uO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ysLOyGnW09DMzMHP0tTX0NPBzMvD2XHVzMjMz8nAxcjOztHQz9DOusfN1MvS3Nbh4N/c19Li3eDcdXd4d9/h4XXd2HDO0Mxtbc7U0NnTb3HPcMzOx8nHta1o0XFozMbKaMLFw8XMx7zOfoPPv7zI08rKwLW1vMXRa85p0mhqwM6Az2xozWtubdFsx8zJu73CxMnJwcPDxNrd29DU3M3h18bX29Rw0c7UztDHxMZszc1vb9LKzm5vb3rZz9PQ0c3Q1MvQatTPbnuL487c09LMzMzSzc3HyMvSbmluac/MbWhwa2nMas9qyNjQ13l60cRtyMzHr9DMxszLzNHR1dTXb3CAdHN1cHXXcNPYb3pz1G/XzNBy39LX3t3U1Nhzb91ze4V1ctvfc9nZ3XTY2t92dObqd+zs3neBfHjg5Xp33OF7fHHo5nSBdODT2uHj2M/a0XOSoox2eXF1c4KJcNRyc3qEeufpeuLb7Ox473d6hHrj5eHW1/Dy1tri1tXT33/n4dyA3+RzcXDZ29jX3uZ1dNzX4nDZbm50cXBw2dXV1NRw029uzszKcNnaenZ26tbicnR3eHZ0d3Z3eXdz293c0eXpenvxfO55f39663p6euV4gOPh5OboeHLidHl2dXZ0c+Z1dOV4dXd7eOTm6Ozk5OjreXze5+Hnenfq6+3n733veXqA63166X556Nvb3tnb6+557Hnq4nV5dXbngXx1eHx85+XreXnv1eLh2efk7ezm7np+9OLs7+Ld6urrenh/fod9eN/Z4HLhcHWNfm912+Da3nF6cHp9eXqGfZunfXdydHR9fH16eHjx8Xt54IN9eIN56H54dnt4dOBxdIN6dHp3cnaAdnDbeHyOfnx1b3V3gHNvbnBt03NxdXNwcm7Sc3RwdnV1dHRy3tbg1OCCgt/c2+fe9N/s5+5+qnqAguXk4ux6foeDgHl17Xp2eHnkkXx3joJ1fXx0dXNwcnN5entyco5vc3RzcXF2d3x2cXbl3HNv1nBw33fecHLcenHL0szAy3eAdHiCf3x35OXn6+np6OLm4OLi4NRwct51dHNucWxtbGlob21ramtvZ2ppbXdvaW10cnB3c3Nrz8zKbnN1b93Q1XZzdoKGopCAhX1wgnh0dttubnVycHVzdHR2d4F6c9Zz33Jxd3V06vDj19t7dXtz5nRydHhydnbT13F3eXVw4HGAcnFyc3ZxdXN1dXZxcHPedHd4cnLYynPW13V0cGzJxsdyc3R3jq1x2c/b3t3c2eOC5tre2+ba19vh2NTP0crd19/n6Ond3+Lg4eHi4ODk4N7l49/l5ODb1dnd8oLh7e/e7fKC8drX1uDW2W7U2dxweHrj3M/Z1dvf2tvi3t3L5XaA4d3l4HTq5OHd3tra4uXj2NzP2NXN5Hfc2tbY2dHN0tjT1djY29vdy9ja39TX29ze1dTZ1NDZ19/XcXB0c9fY2nHYz2zLztFubtTY19/Yc3bac97k3OLfy8N253516OLmc9zh3eDh4dzmi5Hi0tHe6uHf2tTO1NvkdeRz4XByzt+A3HRy13F1c9xw0dTUyMPJy87MxsfDwdTa2NPU19Tg18vY4Npw19HV3drU1dZy1tdzcNrM025ucnfa19vb3NnW3NjfcOXneYOL7uDq4+ff4N7p5uHb2NvdeHJ1bdfTcm91cnPgctx01uLs74GC6Nt02eTcv9rd2NTU19PX2dTbbG+Ai4aIgof1ge/+hJCI+YH76vGE/vH4/f7y8fmJhPuDjZSFgPj1gvXx9oHu7/aEgPr9g/3874CJhYHz+YiA7PaHioD+/ICQgvvx+P378+3684evzcOQi4WNi5uegvSBgImThPf6hPLx//uH/4CCjIDi4N7U0vL56Ov75+Pm8ob07OWA7PiCg4H9//bu8/uDgfHz/4D6goOJhoGB+/X7+v+H/YKD9e7rgvb7h4SE/+3/gIWGh4OAg4KEhoSB9fTy4fX5gIL6gPiAhoOA/ICAgPCAh/T3/v7/hoD6gomFhIaBgf6Dgf6FgoSIg/j7+f3z9fX7gIDn+ez1gID5+f3x94L8gYKA/4WF+YmD+fDq8erq+PSA+4D9+IOIhIP+j4iAgIaH9vL2hIL94O/u5fHw+vfz+oKE+en19enp9u/1goOMjJaLg/Ls+YD7gIiqloCI/v/2+YGLgYyTi4uuodLEjIiAgIGEhYeEgIH//IKA7YeEgo+B+4eBgYuHgPCAgJOHgIOFg4aAg4H7iY6qlZGIgImOmIeHh4mE/YqChomAhoL4h4uFiISGhoaE9uj34+mGhOjg4vDp++Tv5OqDwICHiOvp7vuBipaSjISB/4mHh4j+pY6HppOHkIyEhYSEhoiMi4yFhJ6Bg4SHhYGCiJCMgoT++IGC+IGA/Yb7gYP9joLo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ycnHzNfXzdTNz9/Uw7jAu8zVxb+7tby9xdZ02dnDuM3AxMvT0MrY0tjJ1ul20uB30sXectx02+LYbGzT1dbO0tbSzcC+wcDHbmfIy2lvcMq+u8bGzMnVbs/ExtC+x8bFznHQssC8xtPW0MrMzcS3dDVMycJqaMvNb4BvfXpwbHBvx8HByWlwy7fAwr/Jy8vLv8PJytLK3Hdzxs3I0NTY0sJweHGZfXFw3t9tc27QyMzDysrB19tz3XN04dLC13h3cX3XbtPR1NBubm/Ey8hpaWpny8fAx8xnw8XFzshpaGluz9FucHFybuHc0W9z0czA1NXT09hw2szU1YDf3trU0tnj49DV1uB33NnP1Nvgh3/JyMx23HLi2d3f2uTb1eB1eHTnc9/jdOPp5+t4debh5eve4ON0eePUzNnMz9fV0s7UbW3RbXV5f1NigGxsb21zeG51e3V13HB1dHFx33Tr5+zv2+fr4uPxf+d65XV3eHbt8vDu7e7p7eDQ2YDX3N/l4uPmcnN01Hd9eN13fI983nJycNZscHR2dd/fcXN0bHBxcm1xfISKb5mDd3l4d97X2r+P1nmGgPLseu3s5uXc0O3wfXt463x653TrdnVy5d55ded753Z4eH14dXN3dnl8dXZ1doJ4eXZ05eZ04N3R3OLgeeHb4+5+9PTr8IDq7vLvfPR98/Hw8ePnee/jfoR9fX135+d2d3h7e+PlduLV4Nze3+XZ3uN/9vfy+IHz9u3y2/B/f/Pd6+DneHbjdnd2d3eBcd/ldnZ7e+Xae3d1dXd9e3aJa4LccXZ4eH1+dX1+3el/7+95f956fnvn5OJ5eex5eeXlgO125uh474B4eHd4enh3d9vi5HZ2c3NycXV3dXN1fG9vd3pzetXgdnp2e7iHf39349xz6Xjq6Ojr6OR8jIqN6eLp5+Dn5H93d+ro8u3s5Xh7e3R0duB5dnRzdHV013WBfG92jYHf1tiBenlvcXV0cnN3cW7VeHNwc3N0dHBvcHTQdnPZ2nPdc4B23NLa5XTje3ne3+fy4dDj5eXg1trf2tnb1XFtcHNzbG9xb29tcW1vbnJ0cnJtaXNyd3FubnN1fnuJdW93gKJz2trbd3LY1nVzb9bT1XN2dHJ6d3JucG7TxcrOb3lub3R0cXZzc99zdHh6gHp1dX9223Fz1Nfe5tvW2H+NfXTl1oDT1t901OJz4uHh3NvfcN7TcN504XHgfNzW2N/jctrcc3Fzcdnc1tfOzs/g3uDg49/b4uDV1eXs1+l76o3se3jb3+fh0d7gdeLd1+bk4eTe6uPk19bQ0uTh29ba3+LZ19ba5d13fNrTyMPP0dHV1uLg4+ba5OTcy9/c2NbZzNfa3oDv4NzZ4ufg3ODY3+jk2+HY1ObeyL/Iydzk1NDKxs/X3+t87Obd1N7V2N3b2dbc3dzT1ep52d5209DdcuN329/ecHHc2tnR1djX087R1dLddnDa4HV4euTZ1uHj597jduDV2+LV39nV23rnxtTR093e09HU2NPFe0Vd1tVwcNjacYB2gX91b3h22dDI1HJ02MXQ0crN0tnSw8vT1eDY5nx32dXR4eXd29B6gHeigXRx4uVxdHLe3NnQ19jQ4N1y3nR35NvN43t7eoXtdOzf4eJ1dHPQ1tVvbm5r1NrV1tty1djX2910dHV35ep5enp7ePLn4ICF4t7Q4uHb3uZ04NfY4YD4+fTu5+/8/u/29f2I9vDr7PX6l4vj3uWG+IH+6u339frv6/yEhIH/gev7gvj8/f6EhPj3+f719v2Bhfzq5fbr5vj18/L4goD6goySmYW4u4SDhoKJkIKMk4aH/oCHg4GA+YH//P373vP46en9h/mE+4GChIH5/Pny8PLr9fTg6YDu9/b/+/r+goGG+IqRhvyFip+J/IODgfqBhIiIhP/9g4mLg4iIi4KDj5afmKuRhoSEgu/m6dKb54OUhfj0fvX37fDo1fb7hYSE/IOA+YD/gYSA//iFgf2G+oKEgomFgoGFg4mNhIWChJSDhoSC/v6B8/To6/Dxg+3k8PqD/vf0/YDy8/L7hf6G+Pz68/LygP7uiZKQjYqC+PWCg4OEifr9hPzs8evo7e/i4+2G/vv0+YHq+Oz01+yCgfbh9uv4goD3goSFhoiVgff9gYSIi/70iYKBgIWNjYegk5v5gYGGhYiKgIeG6/KG/fuChvODgYL39POBgf2ChPv1if+A/f2A/ICBgoODhYaCge/6/4SFhYSGhYmMi4eLkYOFi5GGkPH8hYqDivCXi4yA9fOA/YH79ujt7up/kZKV9/P39u/x8ImAgfLw+/379IGIiIKDhfyKhISEhYOE84eVj4KKpZf++/iWkJOAgYuLjIyPhYP8k4uJjYaLi4SChorziIL194D2goCE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uMC2wMTDxb/F32+9x9HS4Np3g3p2ztHKw8jBxsXIys3UztHPdY13eHDP18vPcHLa1dXKz85o0GnTzM3L0cvQzdvL0cHNlozVzMfCvcfQ09jb0crOxLm6x8rEyc7NcG5vctXT0svHytPScG9v4+KkgHzS08vRzdbT19d00dHH1tPX08bicnLezLzLzuna4dtw2srI0dTW0M7d3sy40tnKdW52eXfcdHHYws3N0s7M2HJ0cG/Dzs7NbnBya2draWpzc3FsZ8fKbnh3ztTL0tbW1W91zMnOztrMzNXW1cvQxdjjgEpw18/Z321vdN50cnF0gHNzcG90a9XVam1vzstubnRx1G1zb3tudW5wcHBtbmzWcW5yc3By2XJw3XV46njc33Te19/WdXl1dXZydeDa3dN05Hh05dHX0t7WcNvcb3BvbHR1c29teHZyfnl2eXJxcd3cdXzf4Ori4XZ69eHx5u16f3vl6nuLfOPmd+d3geXkgPF463t9dnx23XNydoqEcnZ3fnPg2+Pi39hzdHhy3HJydHRzbm5zc3l2eXVy43Vz4uLk6O/08Hjx6+rp6nh77urn7OjY3nd5fH19ennld3d34Hl0dHZ2cd10dHV143R3deRy4XJ3enl26Ojq7Hh4c+TheX9453p5doLi3Hnrdnl2gN7r6ep57e95end369LZeHyCeXZ1d9Tf3XTfcHB4b3F3eoFog3Lod3fh4Orr6unr8Oju9u3b44V++ft+7O97fnzqd3Vzfnx213V6eXZ2eHV1eXTlfnx7fHp8fHV0fIB9enp6hHt7fHrrf8neePN/8uPc5Obq5uTr5+3qd9bg6Ip9gNvZ5+t2d4F6efDq5vGBhIHm7Onm7Hh6eOZ8dnh7dnqAhHrd29x8d915enaCfnh+e+nd33d4en+UeniKenbkeHfo6u7s5uHw8H95dnV9d3p0c3R7eHl9fnl3dXN34Xd4eXh7fXl8f4J7fXXk64R94eV1dXV0cHR1eXt7dnJ4cn56gHlzeHN3enmB7uDc2ODmduTj4+jp4dba2Xh02nDfcnF3cXF7d3B6e3Te33Z7dXFzdnx/z9NwcNl12m5xb3Da1c9teX2El3LTb3JucnFxcHNxdHJ1d4CLcXBxi3BucG/Y0dLWcc97duTa1Xl16HJ14t/hcejr7n56dn3uf3fZ3drlgOHc3OHX2ed12sjN4Xx74ON02ebl6XZ15XJx3+J24tnPzW7T1M7S09x5ctbTzdnZztnS3+Xc1dHX3eTf29Tc6ul629HQ2NpxcOBxd3tzdHLZ1NjV19/Z2OTh1d/h3tLY1t7h4drZ0svS2dXJzdTcbnF31ttw2d3d3eB0cOHb29TXgMhv3nJ0ioVv1dtxc3Pd49nVytTO0tjS29nc8nrU2+vq7O97iX562ODc1dLLz9La3NXa2tzbd5B7fXfc4tTWdXLd29zT2dNt22/e3NTY3Nzd2eXO1MjPko/i1NTMytff4N3f1tjf2svQ3+jj4e/geHR3fOjY1dHQ0NnTcXBvuq6MgIHT18/SzuPX0tpx0dHO3tnY2cbacXHiz8DJ1Ork7+R34N3Z1+Dj4ODn7OPa3+bbeHOFhXrddnfg0dXU2NvV3XV5eXrZ4+LVc3N3cWtxcW94dnRvbNPScXZ10tjW297h23d83trh6PHi4/Dy7d7g0ODpkGJx3tba2Gptb9NvbWlqgI2MiYiMgfz2gIWH+/aEgomH94CEh5SCi4WFhoaBgoD8hICChIKA9IOD+YeJ/oPq8ID05+7phIeCgoSChf3y9+2C/4SA/+/07f7zgfn8hIiEhYqLiYWAkI2IlY6JjoaDhvr8gonv8fzz74CA/un97PSAhILx84SZhvD5hvuBi/fvgPuA+oeLhI2G/oOBh6KbhoqLlYb99Pr38emBgYeC/IGFhIOHgoOGjJKNkoiF/4aD+PD3/Pj//IH9+PDy+ICE/Pn0+ffn8oKFioiJhYL0g4WD94eChImEg/yFiIaC+YOFgf2B+oGGiIWC/fz9/4OAgf34h4+D+oSDgYv58YL6gIOBgPD49/2D/v+BgoKF+ubsgomSh4aEhvP9+4P6goOLgYCMkKCCl4H+goD16/H6+fn+9ffs6/Te5oaA+v6A8/uDh4X8hYWHj5GH+oiNi4iEhoSEhYD7jIuHioeHiIODiIqIhoaEjYKDhYD3hdTwgf2F//Pq7/X69fX9+Pj5gO/395KGgOrt+vmCgYuEgP339/+KiIn0/f77/YCFg/yMhIaKiI6QmIv7+vWLhfiJh4WPiICHhPr19YSGiYeaf4CThoH4g4H1+v7++e//94mBgYKKgIeBgoGKh4WMj4iGiIKE/IWBhYWJh4OHi5GHiID3/5KK9/6Bg4SAgYaJi4yMh4GGgJGLgIeBh4CEiIeN/fLy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a9bIvtNy5cnb1NLJ1dPP3Nzr6O7eyMXb0dHfytHY1+La43eujtvZbtLY0cjMzNXWyNLs0cvH4eLY2+rb3NfdeoeD09bH1sLT0MjHxG3LysLDxtHQycvV09jc2nBvb3h4dXPJzcjT0M3h3eDj24Db33Te2tHV4d9zcHHj1cfOx8vOzNbTceHacm7S1M/DcXh3cdDb19LY4dTW4etzddzf5OHPwcjOytrY2NXUymppzWRqY2VlYr/GaWrPbHegq21w2tdw1tTSyM1vccfQ0cPQx87Uz7vM3+TExtDO2OJ3fNzH2ISHxtPY0dRradNtbIB03dTc1nbfdHXe2dvU3dPfc3Z55t1ydN513HDe2nVxcd7V2HFw1nFzc3V6eH193tnd33N2cnZyc3Hd2d/ieN/Z2tjW0NHc4NtycnTc1tDd1Nhzcc3fdXZ5c9XScHd7eXbY1Nhz13d34uZ26njw6/Lp8ep9gPXofe7reoB44HV2c4B3fNjOd9rkduZ5dnTacnFycXp14djZ2tbi5fDt6Xh6lI98eHl3e955d3l7dN3d63p77e7v5Xzwe+Z67YF8537z6N7e0N3g6Onldnbf4HZ4enLj2nJ0c3V2enLd3XV6e3l5fH12d3nw8Xl6+YCCgn6AiIeEf3x9enjzeOTf3uLqeoB/end7eHh45nl6eOje43hz4+Bz2dJ6d+eBd3N1d3VubnB/eXN2dm9v1G56dux2eHff5Ozi4uDu7+znfX6I/YiAe3yAf33jd3ba33Fz2XJ7cnlyfXnpdnrngXd7eXx6dOZ4hnjk5+bbeICFfHR2cXZ7eNp339x05XJ2d3J5c+N54oDkdnt153qAdnt1dnd6foSCf3x7fO3vfXvp8Xnf7+/qe3x5fet3dHNz4HJzc3ZxdXRzeX2EgIVzc3F3yJt7cdjY1HZ2eHPc399ygHB4dnV1dG3cdHd1boV2fHZ7fXl6e3J3c+R13953c3Z65OPheXp15Ojl4nV2eHd5dHZ2dXV1eIB3en9/fXp45/He1dTj0d3benrk6Ojs4+Lo4+Hj5Hp8eHLebdXV1tTKxdRyd3Bud4G+fXdzcHlybIyBZ21taXBuaXB1fnJycG1zb29rdXh1cXVvcnZ2bNZvcnd2cXV4dd9v1+Dhc3nf2/B33uZ7e+vf59946HTs7OB25uXg6nXje4Dn5uTn6OPb3d7e3N5ycnN8iHd03ubXcW1xb9bdd3XP189sb9dv0dFubHHc3HTVz3DecN3d49fVx9Hc9nTa3tfd6ePT3dffdNfR2XZy0G7W1dlycXDb0MnR0nR1endz4nLi19HU09/Y0MnP1WtqbdXWa3NtbW1uc29sbG5vb3Bzc4B1dHdv3dng2NXY293O0eDU3t/mc9vWytt689fi5N3Z4uHY3t7k4ufdy8TTy83dx9TU1N7Z5XavjOTicdfY2c7O0N3h093q2M3K2NDMztnIysPFbXl3x8y/zcXT2NbY1nTa1M/P0NfZ1NHV09jc2nFvbXN1c3PDyMjTzMja2ODg1oDZ23Db083Q4uN0b3Hm2sjRx8zNys/PcNjXbW3W0c7Ec3ZycM/U09LX3NXY3txvcdne4+HTy8zQ0ePk6OPf3XNx1m1ycHFybtrYb2/Wbnazr2xv1dVx3tza1Np4euDj4tbi4OLm4tHg5vDY2N3c4uR1e+DR3oaJ3OXd3+Fwb9hycYCH/vj984f3gIX89/Dx9ur8gYSG/fuBgv2F9YH++YWCgPzx9oGA+IWJhYiOi42O+/j9+oGEg4SAhILx7fb3gfXu7/Hs6+34/faAgoT58On/8PGAgOr6hYOHgfDxgoqMiIj37vKB84WG+PqB/4L98//z9e2AhPjqgPj0gI6E+oSGhICJjO/agej2gf+IhIL2g4KFgomC//P38/D2+f737oCFn56LhIeAhveIh4SIge71+oKD/v3+9Yf8gfOA/ImG+4X99eft3unv9vz8goL084KGiIH/94GDhIaHioLy7YCHhoKBiIeBgoH79oCA/4SHiYeKlpGRiYaFgIL2gfXh5/f9h4COg4WJhYKB+YOEgv7q8YOA/f2B9O6KhfqPhoSFhoiChIeXjoiKjIeF9YOPhf2Dg4Pl6Pbl5uf46ffwgYWM/IeIgYWHjIb9h4f4/oOF/IaMgIaBjYf+gYb+joKGhYSFgPyDkIL3/fr4hYySh4GCgIWJhfKC9PeB/YCChoGJgPyI+ID4goiE/YSMgIOBhISDhYuJiIOCgfP6hIDw+4Du//z+hYeDiPiAgoKD/YGGgYSAiIOCio6VkpSDgYCI1KyMg/Xy8IqGhoH4/vqGloSRjImMi4L+i4qHg5uKkoyRkYuMjoKJhP2E+v6JgoiK+v35hoeA/P7994KDhYWFgoGCgIODhICFhoyLiYWG8fvv5eL1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aMaPlmlpaMHSvbm0yMXAvsC/u7e91NnW2t7jjOHV0+HSyHvv3Od64trW29rU1+Xqc9DH3+Tdztra0dfW2d912Mrb4eXedNtz3nFyb3DcdW5ryW1xb2rVcG9y2NHJy3B1cHlydtze6HTb2NridXzhcuRy4dvLxcvZ0Mnb34De3tPW39zM33mBiX9z3ejf3+fl5X148Onh6+Te0uXd2tnhedvWv9Pj1MBvdXxv2dty5IPV0MptaL1oZWnDvMFoy8fI0mrWzWttbHJx0Wtu12zUbmzQy8nHzcfD0dHNyMjGxcnJycjKysbOz3Ld1MvX09XOzNDLzMtsbNHJbHPN1YDa2dvj4nNy4+Pa2tl21tjl3Nx21NV04nd053VzcdNwcHDbbtbTcG9y39vX6uZ1d3Lo5uV17nbj3nfl33Xj4t7g2uNx3t3f3nB1dXPl43Z9cNLN19ZvcdZw4tvbdXDYen94c4aJc3J0eHl9d6Fx2+Hpdeft6N3n4up7e32Fe+jkdoB23rqTd3l6end0eeTfcnN1dnh2cePWdn1+d+Pc09p9ePLe4+h2dene4OF92nd43nh6e3/t2t/n6e33fXjj3ZV77ut6eedzdXfccXR1dHp+e3V34HN5eH95dt3a2d7X5XeAjH50fH533nbn6OWEkXx5e3t2dnF033d7eXZ04XN1d4BzeeByfXx1eXZ2c+Z4dXd229t0cXd3eebm43bx8Hjq6nni3dzh2ufodX90b3BydHl55d9zdeDY336AfvDxfn3r4t91eHh67nh14np9eHx2ennp5PLpgY/6/PeBf4GAgfHu8vt+gICBgYuAhX98fex+fubn7o9/8n99eu/q3+vp6oDs7nuAfH15d3l7eYF763p6fObl6OdzdX17eunt53l+f4bi5Ongd3bednZ3cHd0e3RxcnRzbnJ1d3V1enl8enZydnV2ct92dnXjd3Z4dHV4dnp5fH18gHp4dnRmiuexhYbv4e/d3uPi2dZ34nve4HXgfIF2dHN1eHV0d9ve4XN6eoB0ennic41saXTm6nZ+dNp46djT4+V6d3lzcnjh4nnm5OR2en+Ddufc2XuAc+JxenR4mZN7dG93dnRscHRta9vP13J7eHNzm2R/4XN6defq2OZy5KCCdnLadnd2cuHd2HLlcuPkeunmeOro4+7p7uf5gYF+gXvo4dvj2tDT5nbr7oDm5ePw6eX0gaeF6ejjenp36M/j2uCRfnd0cXJwcuHc1N3h4eTl1NPe2Nrgdd9vb9PXa9Ztb93U18fXz2zM125xceDY2Nja19rY09bPctF1d3PVcdnQ0dPK3oRx2d7e4OF0dtvZ0M5udG/PcW7SzdLQ1GttytLPztF5cW/V2nF03IDdcdXXcNyDjnNxb9Dj0cvJ2Nvc3+DWz8rS4OXi4+bwo/Tb2eHQyHTh0+B01tDS1tfQzNrib8zC2OHcydTV1NnV2+Z22MbN1NDOaMZnymloa2zWbm1qzW1zcG7bc29u2NTEw2tsaXFqcNjV3HDc2tngdHnbbthv29fLzdHX1NHg44Dg4NPZ3dnM2nV/gXhv0djUztXW13Nu0Nna2djX0drS1cfTeNzMvNDb1clwcHRw3t9z33TY2NRycNZzcnXb2Nd04d7b3m/e025xbnBu1G1w3nDcc3LY19na4uHc6O3s6uHf5+bl4N3l49vl43nm4d/l4uHd3eHi4+J3dODXc3fd3ID09vr8+YSB+ffy8OyC7e7+9PiE3uWF/YaD/YWCgO+AhYP+gvbygYSD+vXu/vuBhoH89/aA/4D+7oD99YL/+/r58f6A9ff5+oCFgoD8/4aPgvPw+vuDhPiA//n5hoD1jZKGg5ichYSGiYeKhciD8/n/g//+9uvx6POAgoSMgf3ygICA+tGoiIqQjoaAhv36gYCDgYWDgPnrgYiHgvzx4+yGgf/q8fyAgP7w8euF7IGB7ICFg4P66OXv8vf9g4Du56GE/PiDhP+EhYj/hoeChIyRjoeI/YGHiY+JhPbu6/Hn/IOMm4uBiYqC6oH9/PaMnYiJio2GhYGD+YSIh4WD/4KHhVqBiP2BjYeEh4OEgvuGhYaH+feDgIWEg/j79YH+/oL9/oPu7fD58/X/g5CCgIWHhouJ/feAgPTp64OFh/n5h4X98faChYWC/YKA+YaIhYiGi4f/8/vrhI/++/uEgICC9fD0/oCCgoWDjoKGgYWC84CB8Oz5j4D7hIKD+vny+/Ly9/mHiYSHgYCFiIeNhPuFhIj99/z+gIKDhob89vOAiYiO6u328ICA9IKFh4CFg4qFg4GEg4CChomJi42MkYmDgoWEhYL7hYSB/4eGi4WGjImMh5CMjJCIhYaBhqj9t4CJkPzo++Hx9PPo5YL6h/r6g/2NkoWFg4aGgYKB7vb6goiF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aG1r2N7V0+Le6t3j8H18d+Tr4fDa5t7b187c3t/R083HytPP0MrNydXQb27adt19gN7L0uDZ3dbj3Ojq74Tl4c3XxsbO0c5tcmpxbWbNaMRqacnDZ7+4YbZkZmZngGlqZ8dsZ9FnacRwa9DCZsVoZ768aHBuusdr1NrV1t7YzdrU29/e3NLY4uLmdnXo3M7i39TO2OPk6uLQ5erb6uHndHp4gcXU1dXT19DS1dNubNVq2G5xjHxxd3V0foLn0c/l3tXfeXJzc3Hb1nRy0sLJwsTb0GzCydzg4XPe2s7agHFz125s09ja39PU0tXSdeTm2Njn4uHe297j0t3k59ro2tjUy9PTztbSytN1dmzWbm3Vb27HvMrJzcPEyMtt0WxvbG7X0cpucG5vanZxzcfZz9DOas+9zMPCacnAvMPAzcLDys7J0MzOutFtbNDLzdXUxMLRz9fRvcLC2N1z4NvYgOHo5ODn3uPe7ebj5+npenp46e/v4Nnadnd6eXV5dHV73HXg4uZ1dHnoe3bv2uLi5+V3dOh75nd4dXffdnTd4uHT13F143RydnZ55ON0debgdXvgcJB6c3LV1sTMdnFv2NTXcXR1eXd0c25ycG9tcdjcdnZ9dHV5fnl15YZ9euJzgHd3c3d5dHh7d3d9eHVy5Hh8eoOEffTteuJ14+Lq7H3o4eDhdnzjduVz6Ol/e3nv6/Ly9IL3f/H37/F7e32Be3175nbpdnqYe9d04uHa6Ht+eHV1f3zfd3WHgnx8d3p3dnTi3uXU23WCd+J5d4F+ddnQ2uXlddpy3nF3fXl4dXR2RXN2dnd1cdDfd3N3fHHXenOdfnJ1enp0eHd5cXdzenh3eHZ6cnVzend1cXffdXPgc3h1d3h4d3h6eOnm6Hd46/SAe35+f4R8gH7gd3Z363h3eX+E63p/9O3s8fX29fd+kcWDe36B8+18+n5/e4B6fYGDhIB/hYB/6t7penrw6/J4dnl8dnZ5e3x9gXvx8Op6fXd7eHd3enl/gnV4eHZ0c3p0cnRzdO15eero6PDgeXp7dXXc2XF0e3tye3V4dnRzeXXhdXd6goF6gH12dHl1dXp6eHyBf4Bzdnh5feR04Op8fn6ThX5zc35+eHnbd3VydXt2cXNzcXHZbG5xdHR2eHJ6dXVxdXR7eeSLcnWAdnRz3N/XzXRz2+DX19LE2HV3eOTg5nl3eHPqdurj3d7f593V3OPX4HJ0291zdm9y4HJ3tnx2eXWJhnJ7gN/g3nLh4OR24t52debm4XN02XF3ctnVb3JyfX50c3Lg33Pf1nR5fn5+5t7meuHk5N3f5njf5OTidHF2enl44nPh4eJxc3Z9g3N03tx1foB1c3TV19vfz+Pe23Nzcdnf0s/U0NvV2N5zdXHd3tTf0NnS29bR4ODg3d3Y1dTc2dfZgNvV4dpxb9534n573M7c3NTb1NfM1trggtvbytfU0NPV1m5ya3Bvbd5y2nJw1thx0NBpvWZsbG5ta2fKbGjPaWvScXDZ1m7Zb23P1nJ7dtHac+Xk5N3j2dXh19nf29XNzdfa129x3dLI1NLLyNbU0+HYyN/g3Nzd33Z7doDDz8/XgM7Wz9LZ1HFt227ZbXCDdm1wa2xzdtfN1eLb191xcW5wbdrbd3TZy87Nz+HYcMrQ3OrldODa0NlzdN5xcN3d3NrW1NLV13Te39TP5t3e1tLe4Njb5enV4t3d193Y3dne29bjeHZ24nR14nZ31tPh4ebi2t7fdd5xcG9v29PRcHFvN3BweXHMzNrT3ON26dnf4eN87NzW3N/s3Nvs5eHo4+nY6nh5497m6u7d29/k6ere1dfi5XXo4+OA9Pz28vXz+e7++/v39viDg4H7/vrx7+6AgoaEgIaDgofwgPn9/4SGh/iIg/bs9/f4+YWD/YT9gIGChfSDg/f7+/XygIT/gIKEhYT8+YGA/vWEh/6BqIyChPb16PiOh4T78/uFiImOjoyLg4aDg4OH+/+FiJCEhIyPiIL2k4mK8YCAhISBh4iDio2NjI+LhID8hIiGj4+J//6F+4X5+v/+h/j18PaBh/mC/YL9/omFgv30/fb0gfiC9Pn2+oOEg4aChYX7gv2DiLiK9ID59+n9iYyFg4CPivuGg5eRioeBh4CCgfnx/unzgY2E9oODjImA7u33/v+D+oD6gIKNiYaHh4qAhYiHiIaB8/yIgIeMgviMhJuLgoqIh4WJh4uAh4GLiYeHhIiDg4CJioaDhvuDgfqCiIaHg4OBg4WE+/39hIH2+4eFiIaKiIiGiYv7gYKC/ICAgYSH9YGC+vn4/v37//2BmfWYgIWH+/mB/4SGgYWAgYiKiIaGjIiJ9+z6hIX79P+AgICBhoKBgoWHhoqG//z1hIaBhoODg4eGjY+Cg4SC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enZycnd2dmtw2XBubdTVyMzN2M/Ev8PK0dDS0mvL0XBsb2xwbtTR0tTS2ODR3OXa2Nzt7eDgceBv1cLQ0N1ub81nZ2mKam1oZW1ucnRwd293c9nNgG6Qfn56zL/NaWpkZ8W5urVncm3GvbvLasfLdtK9zNK8wMTHbm7W0rrHdXJzd3DTx7fL1dfTzNLZydrZ2tK8vNbRzMvH0MLKx9hta3FqaMrMb3R5cNfSdtnc2XR1cNB4fNfc2eN1x2txhHh/b82+zMG9wNba32/Nz87GvczDz83IgNhudHVz4HDQ5+Xb1eaOjd/j3tfJx87X1Nbn5OLW0tnT09TadW/Tx9PWa8ps19FvdHDPyc9oatbU1tvV1nVuzsZtcXZ34nR12trecWpmadLVxcXMy8jIxMu8zdTAcMvHzNrOz9bS2ON1eHRzdM/cctjR0NDW13CHXm3VzdhxcdrLgOTq6u3s5njl4N3d6+Z66efm4eHc4Nvb3ODg1dfmdHbl4+R0dXp3dXx4dHN03eN2c3Nz4OJ05vF7eXp3dHR1ct9ydnZ4eHVx13F2dN5y2+DccnF4dejq2NnhcXJ0eXTedXV0cnFvcNxv129wcXB0dXJ0bnaHdHp1dHhyc4GFf3V2gHt0cXZ0d3Vw1XFzeYN25HZ7eXPm4nJze3556N3g2nRvj318gHl3dnBz43h36enpenz18u7x7n7394GHgHz38/N76Od153l3dnp35el5eejqdHfp6HZ3c3fjdHV4eXl+8Xd26ubl53V5e33jeHl7gIB439zfdnLmeXV1dXPidHd1gHZ7e9V6fHp5eXN1dHR5fHXYcd7jd3Pf2n504+F1cXZ2dXh3eHVxcnp1c9vb1XJxeXh4cXN5cXR4c3mNjo2A9vXs6uzs5e98eet/gHt14Xbs7+Pm6oGAgH7z7/F9hPLuhIOE64CFkH97gIF86n6B93l7e3p5eH99eORxdXrh5956gHh2eHh0dXh8fHl7foJ8dn97eHd5e3flfuN7fOR+e3dydHl53nR51NXbc3JzdICEgH7q7fN9gHfi0nqBdXh1eXdzc3V0eHd4eXh1dXZvhIB8fXl2enV4gHp3f3l68fP1f+2AjbiRfXt+f+3qeOjjeHJ3cdlzfnZ1bnt6b3qBenR1gHR2hH5lRoDgeoKD4HZ2dNPW0Xl0cHRxdXZxc3V5fIeId3h55OLk8Hjq6Hfs4t/reHl4fefuf3ru1fHqd3bm13h+eXSBdd3b0tvffHt4hXvs7nrg4uXs5Ovr7eru+OPo5Ozh6djje+/w7eLW5fDu6Ol6fNvmdXVz6OLpfd/r6d7hgOp2dnp0hnt3eHhzdXZwcoB3cXJwcnF/fHd0eWttbNJry21v0tBpa3Jub5vfb25tanNzdWtt2m9tbNTaz9nc4d3T0tvd4+Hj4njg4HV2dnR3dd/a3tPT0djJ1drY2tHn5tzgdOFy0sbW3Npwc9pva2yHb3Fubm1sbG1rb2lqaMfBgGmkjnRyy8LLaWtsatPMzcFqdW/a1crYb9DRft3H193CysjNcHTd2bzIeXN1d3PRx7/P297b09zW1Nzd39XIz+Dd1NLOzcrNztxubnJta83Nb3R5cNDQd93Z3XNzcNZ6edra2OF41HN3jXx/b9XL1szIyd7c7XTU3djQx9bR29XNgN5xd3h14XPZ6+Pa2Oqenezs5uDV19bi2tro8Ojc3t7X2NfcdHDYz9nab9dt3d1vd3LW3dtwcd/a2dnW2HVwz8hxbm5v1W5y2NrheXd0dOTi1djg4uLi39/X5erce93W3OTV09XU1+V2e3l4e+jnduTb4OHl6nijdXrs6/B9fPDkgPLv9fjy84Dx7e7s+fSB+vr28fTv8/Ho7Pbw4eb/gIL89/uChYmIh4uIhoOE//+GgYGC+vSC+vqEg4eDgYGAgP+EiYqJioqD/YGHhP6C+v73goKGgPv+8PH4gYWIjYX+iYqJgoOCg/2B+4OGiImMkIqLh46di4+KiIyDh5Oak4OEgImCgIiIjYyA9IKDi5SE/4aMiYH++oCBi4yE9u378YOQqo2Nk4uIg4KD+YSD9/r0hIH7/fT19oP7+YSNgoD//f+A+PqA/IeEhIiD9/6EhP/9gIL+/YKDgoX9gIKDgoKH/4GB+vn4/oGEhIf0g4KFiYuF+e34gYD8hoGEgoD9gYiBgIWLi/KJjomIhoCEhIKLj4X2g/r8g4X89o2A/f2BgYSEhoeFhIaBg42Ihf38/4aHkZSQiIWMhYOLh4manJyH/P/39vn08f6KhP+Ii4WA9oP9+u7184WFgYP8+fiAh/r1iIeH8oOJlYaEhoeE94WG/YWCgYaEhoqKhPuAh4r++/WLgIeFiIiCg4eHioiMjZGKh4yJhYKGh4D2h/aKivqOiYWChIuK/4OH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dtrPwtJwcXV0bGnPamxzdG3Nx9XEz3DXzNHO1d3Z5tPY3Xfa0tnTxd12c3N1ctvPzdLOz83L2NfY4nLi0tLgeHRz3NTMyMvU0mtw0IyAcdjZ0trh1nN15dTOzdZvcXeAcdZsbWrLb3iAbG5uzMlwbnuBaXBwcmdmzczMas/L03TUztTQ19DLx8nH0J2HdNHJxLfJxb/DycbAz8vPz7dmlWzQ1GvMzcXAv2jQy87OamlszcvFycvP0HbTysfK1NBwc9DScHabhN7Z3ttxcWxtb3jT1tBu1HDXzsPHy8HLwM6A1MrP0tTO2dN0cXNw2W5wztHP1MPBzdHOcs/JysW/z8rJxNfYbNTa39LMxsJr3HHQyMvY08/X4Xjm6et35+Xo53Vv2XXczsTPzc3V28/Sy8LYz8hthnHUzHLS19DX2dx3fKKj69np6nqC6uF8eXDb5NjT2dXBxMbFws3T0tNqdNyAdOF5dXPh3nd4ddnf4N3e3m7ecdvZbG/c1NtzcW5xcXd3bnV7eXBvd3B1d3BwcnF1ctfieHZ2eed2c+Xb4uTb1uN+fH7m6eXi5eji1eB0d3l4eODheOnh29xw2uF14ODa1OBxd3Vze31xeHtzdOJzdHx6fHZuenR3dHN4gIaFen2Ag4F9e3bneXdxeXV1dnZ4d3V5eHR5eHZ3eHd3cnfld3h0d3l5d+R0fnl4eN7r6uTr8ud79tzo9e7fe+HldeN3foSCeXp9nX55euR4d3iGi3/h3eh8e/N1f3x4d3l45+x4d3l3deV05HV3eHh8fI3hfd90c3V4eH92dnlx1uJxdniAfn94eHh2eHl8dXR3d3t5enZz5N3k1XN4dXN13XV4cd57dXh0dHV5dXZze3N9dnN3dXBzdnd3cnZ2fH97d3NzeH6EfPGAfOno5+Xp8ux4fPbx9fnw8PeChol/9/mE+H317+eEfXyAeIB63XuAfneCfIF9eXp1eHp2eHZ44el3eOSAdHd5dN93dH6DgYSBe3l7enPgcXp7gJF5dHl8eXd2enrqeOR46Xp3d3jj29dxeXt5e81xdXJ5fOfrdnp15HFy33V2dXR5d3XodHN26eZ5f3Z4gXt3cnh3eHt4enV1d31+53h9enh2eOPm6Y1633d8go3DeO106tN5aqftiOx36+KAgHt2dHTiduTjfHx7193p4XV25n13d3nDgoDm4HR5dt14euPe2XR5dnx5d+ne5unm5nR2eeh0ctHgdXJ6fHx16XXgdXjgenXZ0N7j43N23+TodHbv6dba7vF/eejp7Ozq7Ozk7fT7fe/xfO7n3nrm5dfb5nbk5ud0cnJwbnJ0d3CAbXJ0duNw33Fz3W54gXZ7z9uAcdHa1+Xk3nHa1tjXbthzcdPUyNdzcnd3cWvUb3B3eHPg1N/L1nPbzdTP3ODY5NTf5Xfj1t3a0954dXV1ct3T0tDNytHV2tjd5HTh2tPgd3Ny2dbRzc7Z3XFz346EcNbUzdLd13Ju08jHw81qa3GAbNFpbGbNcn2CbXJy0dh3c4GHb3V4d21u3NjYctfR2nXa1NvY3NrU0tDS2puSduLX1tPd3d/d4+Ld6eLj6deDvHro7Hjb2tLLy2vX0NfWb21x1tfW2NrX13rf1c7N089ydtPecnWWg9ra1thwb2xwcnbT2sxw4nTi29bS2tDYzdiA3dba2t7b4N52dHZx3nN14uPd4trc4ubmfdfc2NbV5NDR1N/ccNre3NLGy8tv227QydDY1dTT0mza2NZt09Xc33Nx3Xbc3NLZ2d7o6ubo6Nfs69t3o3zl1nTd3NXX2eBzdZqc2szb3XZ/4999fnnx8efg7OTW1drV2+Lp7O93feeAgfWFgoD79ISFhPL4/ff7/IH/gv/9gID89v+FgISGhY+Og4mSjYGCi4SIi4WFhoGFgfX8h4KChfyAgvzr8/v46v6Mh4ry+frn9/716PmDh4mFg/fzgf3y+fWA9fuE9/n59PuBiomIjJCBi46Fh/6Fh5CPk4+Gl46PjYyRlZqZjY1nlpSLi4T+h4qFj4yKiImKhoOIh4OMioeHiIiJgYb8g4qFiIeKhv6Fj4SFhPD9++7z+euA/+Xz+/jmgOzxgPSCiZCMg4OIr42Eif6FhYeUmIj17feChP6BjIeCgIKD8vqAgYSCgv2C/4SDgIyJld+M+4KCgoWIjoSFioT2/4GJi5OSjI6PjomNk4SCi4iLhIKAge7v9OqCiIKDhvuGhYT6jIOGhIKDhoKFhI6Gj4WDioiDhYqNjIaLio6TjoeCgoONioD9iYX39fT19P33gIL+7/j58e7zfoeJgvP7hv6C/vztiYOFjoWMhvaKgJCOhJCMkIqLjIeFh4WIhIT8+4CG/YOHhYD2g4CPl5GYkYyJjouC/oKNjI2ih4GFiYaDgYWI/4H5gfyHhISH+fv/go+QjZDuhoaFiov8/oCEgP+Agv+HiYaFioiG/oWChPr7iI2Dg4+Ih4CEhoaDg4aDgoeOi/6IioqGg4X29f+kgIfvgYiOnvSE/oD75YiJs/2V+YP/+JCGgYCC/4f4/ouLiOvu/fmAgP2LhIWK7puR/viCiYP1hojz5eqBgYKJhIP+6Pn/+fuCg4f+goDp9YSAiYmJg/6F+oSG/YyE7eT0/PyDgPr//4CA///l6f7/h4Dz8vXx9PPv5/L6/4D2/IHygO3mgPXw3+bzgPT8/YGAgoSAhoiIhICChoX+gf+Eg/yAjJyKkOr0k4P0//z2/v+C+v37+YD6hoP99OL4hoeNjYaA+YGDi42I//D+5/SF/OXr5+3+7fzq+/uD9+r47ufvhYKEg4T+8O/u6urq4/Pu8v+A+ern+4aAgPf37Orv+PuBgIb9oZWC9Pfw8vv1hIH17u3s+oKCioj9goOB+YibooeIh/z6iIeapYeKi4yCgfr4+IT18PqE+PDw7PLq59zh4+6yrIT16ufe7unq7PL07vz28vrlkeaB+PyD9vXu7O+A/vf7+oCAgvXy7fDy8POL+u3q6PDwhor39oKDspv59/f4gIGBgISGjPP884D4gfju6OPo3+ve7vDp9fTy6/b3hYGIgfyCgfL39PLi4/D59oTp6+Ll3PHk5uv8/IL9//726O/zgf+C/PP5/vf09viA///+gPb6/v6EgfyE9/Hj8PX4//799/bp//nwhL6K/e2E+vnv8/f8hYulqfrn+fyHjfbzFYaNg//87+Xy7N3Y5OPk8fj//oCH+wp9fH19fXx8fX19hnwKfXx9fHx9fXx8fJd9gnyEfQN8fX2HfIN9iXyFfQN8fH2EfAR9fHx9hXyLfQF8l30BfJd9AXyHfQF8hX2HfAF9hnwFfXx8fXyLfQF8hn0GfHx8fX18h32CfIV9A3x9fIl9AXyKfYJ8lX2EfIV9BXx9fX18o30DfH19h3yCfYd8hH0IfHx9fH18fHyHfQF8kX0FfHx9fXyEfQF8jH0BfI59BXx9fH18hH2DfIV9AXyFfQl8fH19fXx9fXyHfQZ8fX19fHyTfQF8hn0GfHx8fX18hn0NfH18fH1/fXx9fH18fIV9B3x9fHx9fX2EfAN9fXyHfQt8fH19fXx9fXx8fIZ9hnwIfX19fH19fHyGfQh8fXx9fXx9fYV8B319fHx8fX2GfIJ9i3wIfXx8fXx8fH2FfAR9fHx8jX0GfH18fX18hX0EfHx9fYZ8AX2EfAR9fH19hHyGfQF8hX2FfAF9i3wBfYZ8hX2MfAF9hHyDfYd8Bn19fH19fYZ8gn2FfIR9BXx9fX18hn2CfIp9CHx8fH18fHx9i3wDfX59kHwGfX19fHx9hXwBfYR8g32HfAF9hnwEfX18fIR9hHyGfQZ8fHx9fH2RfIR9A3x9fYl8AX2LfAF9h3wDfXx9iHwFfXx8fH2EfAR9fXx9j3wGfX19fHx9hnyEfYR8B319fHx9fX2PfAN9fXwCAgQAgNp2in3apoB0c97O13Hi5tzfd3XhcdzZc3Tdc3Rvc29yeHR/cmvMcGxsa2psamxuamttbHFwampud3d1cNbBdHDklIN3d9vZdNdyd3fa4XPm5NLS2djm6dXhe+/z8feD8ID67e566ul46d/l1nB6fXJwdHFwbGt3cnZ0cnR2cnZ2gHlzbGluaHNu1HVwcHFwcndydHR0cN10dHhxd3R3eXh7dnt8fX6A7eT1fevo6YF+euN7enh02dhy3XNwz3N5d3Z2cGdkZ2ZmZ2loZ2lpZm9vcXl21XF7cXFwcXdzdXpw3dJ0dXhudXp3dHRyfHl5bGt1eHZucXBsz3Fx1mxvcnBrgG9tbnB2fHiAe3tzcXR13+R2eX12c3N0ee3m9Xjm5nR8fXx6hH+CfH5zcXPhbnFtbW1xdHp0gXpvcXZuc4J3cXfmf3l4ennue3l58+x34tjj4XF61nF4cnJ3ddzU0tHZ38jeydBva9B0b2trbWdtb2tqcm9ucnZ1b3N2gHZ7fXl4gIKDg4KHhnh/lYZ7dHV4eXd7bXWAenpyeXZ3dHt3eHZ929vjddfTdHt1d3VygXx5g313fHqDfG9vcYN1dHBwcXFvbnl9amxtcHlzfnJwdXN6dHpybGhzbW5rc23Vcm53d3Vqd2xycHNpacnK0GxrcnFsa8nQbXDEw8dti8B7bHFxgHDWbG7Wb3R3fHJyeXne1nF3c3JraWtoaWprbHRvb2tzcHucmnh2dnZ8d+J6goR6dHfSbnN2c3FxbWhsbW1wbtFwcnBtcm7a4nWFcnN63nV1b911cXp3c3HY3XTe23V0cN3Qy9Vvb2xqw7pmwL1sbWvDw2tsZWpwamzMa25tym1ugG5r2XByd3Pbz3Bxcm1vcdfYcHJvzcty2cfP1G13bdva1NLQ49p3c3GhgnLZ193W1c/ccNbV2tLN197V2OPh3eXa2+nn3b/U3N/b2N573tzactXcy7yUbdra4OLTddXL0NPV1nLX0snVcd/U0uB1cuHeyN7ZxcNzfXtwcG3TbnR0gHh03eN0enZ1etxyc27d1td52cbRb3DNyLrBxMbHwMDKy8XJcMnFadHQbWppcG51bXBwZWVocW9lamxpwWrGccbBy9PJ0tvIzMrWxMfIymzSbNNs0NDM3sTU0cvTxtzf3czU3nt0cm9v1cXRamzT025ny8rFzHnNxcLCx8nM2tXUgLbFa2ZswrrByctkUm69sLq3Z2rCtMTUz2xv1MrX2NhwbtfQz2lraWvPzmlszsXFbWbJzNFr0tjc5Ovk28/A2NbOysjV0s/PymrSb9XPbXTOyM7W0sjZ2W13fHFz297XfXx1f4Dj6+LR2nF5eOXba27NztzgdnHY3cfB0c3Ny8rTgOZ7koHns4d5ddzS3HTf3tjhd3Xicdzic3XmdnVyd3d5f3uFeHXbdnNyb3J0cHN1cXN2cndzbW1vdnh2cdjLd2/qkoF4euPnd+p6goHv8nvw7eLl6OPu8ODjfPHj3+Z54Hnn3+d47u566+bn4nF6fHJydnV4c3V7dnZ3d3h5dnp4gHp7dXR6dYF65Xp3d3Zyc3JydXd6ded1dndzd3R4eXl6dXZ3eHd44t7qdefh4n12d+J3enl24OZ25Hp53Xh+fnx6dnJydnR0dnl4eXx7d315foOA6HeEenh1c3l2eXp05N54eXt2fH59fHl9gYCBdXR/gH55eHRz5Hh13nF1eHV0gHp1c3h5f3qCf310dXl75uh2eH12cnJ0eerk7nfq5HJ3eHh3fXh7eHdxc3XgcXZ1eXZ2e4F9h4B3eH10dIF3c3vufXt7e3jseHd79ex48e/x83mC73yDgX59ee7s6+vt6Of56ut7d+l8fnd4d3R1dnN0eXVvdXh1b3V1fHF3eHZ0gHh6fHp/gXV7nYp8eXx9fnt/cnmCgIJ2e3p6dH99eHd/7ez2fvPlfH94eXt5g3t7gn14enV9dmtocYd5eHR1dHJvcHd9a29ucHh2fnN0enh7eX95dXF6dHZ3fXfof3mAenx4gnh6fIB4eOTk6nt8fHx4eefvfYLs6vB+k8KIeXt9D33md3vmdXZ7fXRwdXTX14RxgG5ucHBwcXFyd29ya3FteaCYc3BwcHVu2nJ3end2eNlxdHhzdHRzb29ydndz4XFwb3BxcNvgeIJucXnec3By4HR0end3c9/ieOXkeXpz5+Da53yAgHnc23fj43l8fePgenlxdXlxc9huc2/UcnNybuJ1c3dy2dJzcXNycnLe4HNzgHDTz3Ph1drccHd04uHb2dPi4Hd2cZ6Bc97Z497X1txz29bd1NPa3dXa4+Dc5Nfb5eLfv9Lb3d7X13rd2txx1NjHweBy2drf3stwy8PM1drbc9rX09pz4djW43h02s+/z8q7uGx0cmxpZ8lpbGxxbs3UbG1wcXbUbXJt2tjbedjLgNNxdtfSxM3P0dPLz9XY09d00tFu2txwbmx1dHxzeHlrbnF3dGtwcXHScNd00c/T08/V3tPZ0drN0dHUcuB04XXV2Nvnz9ze2dnR4N7Zzdbad3JwbmzPyNNrbdjYcW3V1NDXe9rU09LX2Nrj4+PM2nRxeN/d4+rne5GN2MvYznJ1bdjP2ufhdXHe0t3d3m9t2dXMcGxua83Oam7Yy9NvatLV23DY2NjZ3NrZ0MPY2dXW1eLh3N/jdOd46eF3f+Xk4eHi0dfaboB7cnPa49+BgHmDgebo4+LkeXt69vN5eNfX6O2AhPLq29rl5+vt2+GA84aynfy7lIeD++z3gv379/mFhPuA+PeAhPyCgYKHg4iPjJaHgfSFh4aBg4iDhYeChIaEiYeBgYOMjYuC9eSGgP6ckYWF+fyE/4WMjv79gP316Ofv6fn+7+6D/fHw+4TxgPr1/4L+/oP68vz4gouPhIWLiIyHhpCMjYuNkZGNkY2AjYmBgYqFlY76h4aJiYiLiImLioqC/oKFh4GJhomLiImFg4SFhYb67/6D+fT1ioWA9IGGg4P69oH7hoT2hpSPjIyJh4SGhICChYWEh4aBiISGjorygo2Fh4OAhIKFiYL/9IWEiYKLj4qJh4qOjY+AgIyOi4WFgoD9hYP4gIOJhYKAiIGAhImOiZOQjoOAhIP1/4aGjIeBgoOE/Pv/gvz+gIaJiomSjpKPjIWGhv+Bh4WIhIaMlI2YkoeJjYSFlYiBhf2Jg4KDgP2CgYP894H88f7/gIr5gYaDgYWE9PDx8fL07v/x9oaD/Y+PiYiMhomOjIyWkYuRkpGIjpCXio+SjoiAjYyOi5GOhIy6nYqFi46OjI+DjZSSl4aKiIiAiYeGg4n59fyB9++Ei4aIh4SRioaQiYSKh5OMgIGHnI2LhYSGg4OEkJOAhIWFko6Xj4yQjpKMlo6JhIqEhYKKgv2MhYuHioKOgYaHiYGD/Pj9hIWGh4OB9P2ChfH194NnjI+AhYiAhfqBhfyBgoeNgoCIhPv7gYWHhoGCg4KAgIGBiIKJgoiHlMq9iIeGh4qD+4SKjIeGh/SAhYiFiIeHgIKHiIiG/ISFgoGGgfr/iZiAgYz5g4SA+4SAi4WGgfX4h/v4hoaB+e/t+oWIiYDt54Dw74KChO7vhoaAiJCGhf+ChoL3hoSAhIH+g4SJgPrugIGBgIKC/f6EhoLz7oP67vL7gYSB/vro7Oz9+4iDhLSWhvz+//348f+G9fH07e709Ofr+Pbv7fDx+/D00Orw9vPv9ob6+vyC9vvk3eKD9vX5/OiC6+Po8fn7hfr07fGC/O/v+4aC/vfo//ro54aRkIaDgPiAgoGAh4Pw/oGGhYaK94CEgPz2+4j78PiDh/jy5OXs8ezr8Pn5+PuE9/CA+/qEgoOLiZGFjo6CgoWNioKFh4nzgfiH7e3w8u3y9Ont6fno7OrxgPqD+oHt7/D94fP07u/k9/X26fn+jIaFhIX77/uAgPv7hID38vP8kvvv7fL37ez89/uA3vCDgITv7PH/+I7tterd5eGAgOrc7/z8gYD78/n8+oKB/vXygYCBgPX5gIX/8PyIgfn6/oH0/Pr5/fXv5Nb49fDy6/v79ff6gP6G//iEi/37/Pr38Pf9gJ6UhYX6/PSSkYaSjvP27unzgIKA9/qAgN3h7fSFhPX14dzx8vP05eoFfH19fXyEfQR8fHx9hHwJfX18fXx8fX18i30BfJZ9BXx8fX18hH0KfHx9fH19fXx8fYp8AX2EfAp9fH18fHx9fHx9hHycfQF8jH0BfJB9C3x8fH18fHx9fX18hH0HfHx9fH19fJd9AXyLfYJ8ln0EfH19fJN9gnyIfQZ8fHx9fHyNfQF8lH0BfIV9B3x9fX18fH2EfAN9fXyGfYp8A319fLl9Bnx8fH18fLV9AXyNfYN8hn0KfHx9fXx8fH1+foV9BHx9fXyIfYJ8m30BfIZ9AXyNfQF8hn2CfIV9BXx9fX18hn0IfHx9fHx9fX2EfIR9Cnx8fXx8fX19fHyHfQV8fX19fIR9AXyEfYJ8hn0IfHx9fX18fH2EfIN9h3yGfYd8AX2ZfAV9fHx8fYR8gn2FfAF9hnwBfYR8AX2EfIJ9h3yGfQF8hX2CfIV9DXx9fX18fHx9fHx8fX2NfAZ9fHx9fHySfQR8fXx9j3wFfXx9fH2QfIV9CXx8fH19fHx9fYR8AX2MfIN9hXwDfX59hHyCfYV8gn2FfAV9fXx8fIR9DXx8fX18fHx9fXx8fH2TfAd9fH18fH19iHyFfYN8hX2FfAd9fX18fH19hHyCfYp8AgIEAIBrbsrQx9LGysHOy9nScXXbcuDbz9DV13B0c3Ny03PT0nh1c3Jzbm5vb2xucNC9b21ua3FwcGxscG1szW9tcG5tcnVwan1vdnR2c3BxdXZw3nvc5ON6eO3x4Xp3dfLe4ufv6/Lb6fH0ft7U5OLNc3983dt53m9ucYJrdIB0cG1wboBucnBxbXBwctC/amttZ21yd3h3X5V44XRygX10cHF0dnp73Xbm5eh6evCCf/B9fYOB9ICGgHdHkYZ8eHZ1eHd7bmxubGxrb2htaGZoa29lwG1tb9baeHd1fHzjd3V0eHPa43F413p5eXZ2l8mng4N32nF1lnt1cnd1d3V5c3VweYBv1XB1cnB3cHNze3PY1+R06OB42tLX2uJ3c91wcnp6db2BdnN0f3p6cG5uaG9ucXpzcGtocHBtaWh0d3VrbmxwZmdzenqBfHR24uV56nLed3p3eOh03XHQc3LIbdJtcNZpx3HEvmZobWC3Y2prdG5tb4Bhc3N7cG5vcXt1d3VwcYBwb3B6cnJzf4zAfXSDe4Z3eX14bXR4eoGHedRrd3V0dNl6eHXc3XF6cnZz3t9zct1vd3h5eIB5fHh03nN0dHJ1dnZ3enJycHF2cHV3dXRzeORyc3F3c4SFeuJ5eXh2eHfm2NJ0cG90csrU0cJta27Mxm9wam3OkHpvyXB31XBuyR/Vxs7TctFyeG5ub29ramRtdm5yc3F3dnd3dHt3c3RzhHaA5+je4Xx7cXh0f3Z3a3+5eG9wdnNueHRxbnRycG/h2HR1enzkfYKA5uXf4uDXdNt2cdNwddJtdoN5b29z18twcW9ybXF1dXXEwGVrYbOzrWllZWpra2zQb9lyenhv13Fy2sjRzm3ScG5tkXxs1GxoxmbCx2ln0WNqbmtvZmltzMSAa27KxW1pZWrJdXFsbG500svR3tvM0dng4X/p4IGU4vLj29/Z2eDh4+HaxdRxdMHNx8nUbHyGcm98dHJt1MrOzM3S3eDN29XU0nR0cW12eXNybnDc1nN03XPX1XBxcNjb3dvX29PUzba+ycHLy9DIaGlrZ8TDbG1nbs/UzI10bMqA19XW1Nxxc2/Y2spt0dZzc82+zMmyzmxtgMVqZce4xdDUwMLB1NK/xsfHu8XLw8XOxsfAyr68cYVzbdDZbNTXa3N2cM9mamfG02rIztDY29HKyoWWv8jIx73AvM92b9XLZ89uxs3CwMltbc3BycHLzNrG1c3MydzX1NLZ3NXZ2sxd3dN7bW/Ux73Nx2poZmlpxspwfnNvbtfS29TY2dPU1HBpzsTKys/QyGtub2vDxMrOxcbNanl4deDc3OZ+33h8eFPc2djV1XfX7n7pc3HazdHR0szV2G9tz81oa21ugHV44+Dd6N7f2tvd5eJzeOF05+Db19rccXZ3d3TZdeHdfXl5d3hydXZ1dnV45tN3cnNxcnJzb25xbW/RcG5zcXB0dXJvfXF0dHZ1dHN0d3XkfN7o6n157+/od3h06+Lm6fLw8N3p8PJ+4d7z69t/iIPu6YDmcnN4iXV/jH57d3l1gHR3eHlydnZ44NZzc3Rycnl+fX1tlnnfc3OAfHZ4eH15fHvcdOTh43p2635853N2eXfieIB/e1yKf3p4dXN+gIJ4d3x6eHd8dnh2dnd7gHfffnt66eZ9fXyAfe17e3p+e+rpd3zpfHp7eXmXvZ9+f3fWc3ObenN1dnR0c3V1d3V/gHbkdnp1dnl3eHqDfPXw83768Xvo4+js93976nh4f3yC3Yl5eXp+enhzcXNudHZ6gHp7dG95enl4eX+FhXh8eHdxcHl7eYCAd3rr5Xjrdut6gHt673nwe+l7feN793p58Hjrhenme3yCdd52foCEfoCBjXJ/fIp6dXRxe3Z9end0gHFzcnd1dXqCirx8dnx8hXh3fHhzeHp8g4l853Z6enl46YB+f+70fIN9fnv09nx87XZ5d3d3enF1dHLYc3J1c3h4e3h8c3FvcHRxc3l2d3R543NycXRxf4Z76n56e3l1ee3m3nd5d3584ebq3nh9fOvkfn96fPKXh33sgoPteXzngO3n7Ot653x8eHR0dHN2bXF3cXNycnVzdnh0fXd0c3FycnV45Ovm5X5/dHp3f3h8cILbh3JzeHZyfXd0cXdycnTg2XN0eHjce4F719vh39/aduB4d9x3fOB0fI99dHV329J0c3Z3dXR5fHnX3HB4dNrS2Xx1cnFzcnDVct5zeXZxgN50d+LX2dhw2nFycrKGc9l0b9Zt09pzcuVudHZ1dXFzeOffcXXb1XRybXPcfHd0cnJ23dPZ4uDT0Nzd2nvf3HmH2Ord1dnZ1+Hg4eHZ0NRzd8vY0tXZb3yEc296cnRy3M7Z1tfY2NjJ183PyXJxcHB2eXJxb2/T1G9v1GzNy2xqgGzN09vT0NTS2NPBxtDL1Nvk2nN0eG7W03N1b3DU19OEdXDO1dXS1dpwcG3Z2NFuz9Z0ddXI1NO+2HJxfdRybdLL3eLl1NPQ3d/P19LWztfi3+Du4+Pc6ODTeYp4c9vgcdnbbnV3cthsbWrY4GzX3N/l4t/Y2YWV1Nzf3dna1ueBgHno4XLld93l3dXnd3bj3d7Z3N7r1ePk4N3o4uHe4Nzd2d3V5N6Bc3LZzsbV2HFtbnFv1NV4h3Z0ct/X3tna4tve43h0493h4eTm33F2dnLY2uDZ0treb3p7e+Xi3+d53HZ+qnXn4t7b3nnh6nvueXnn1dnd3N7e4XR04NxzeHh3gIKF+/zs/Ovz7ffv/vuChfuA/vny8fz6gISDhYD4g/fyi4mJg4WAgYWDgYSH/+OGhIWAgoSJgoGFgoT2hICIg4GIioWAkYKHhYiFg4OChIH4hvX/+IeF///6hYGA/+7v8fv+/ub2+f2E6en48uOCjYf7/4/+goeLoImWpJSMiYmHgIWMjYyFg4OC9u2DhoeDgIyUlJOYv4n/goGQjYKAgoaFiojygf75/YSD/YqI/YCBhoP3hI2IjrrDkImFg4OJipCGhoyLiomQiY2JhoyNkIT2h4WH+viKiYiNi/+GhoWKhfr/gIX9iIiIhYai3LmSlIn5goSYiYSHioWIg4mGiYSMgIP7g4qEg4aCg4SMhPfv+4D394H28PXu+oaC+4CCj4yR7ZmNi4yPjYyIhYeDioqKmpGPh4KNjYuIiI6UlYaMiomAgImMiI2IgIP6+IP/gfyEioaC/oL9gOyCh+6B/4CA/YDzjv34hYqShPqFkJCYlZSgtYiUkZuRiomGko2WkIqKgISEg4uIiYyWn/WNhYyKmYiIj4WAiIqPk5mJ/4GJh4aG+oeKhv3/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1NDW2+Dm6+fs7u3m5ejV0dHd13HOb23Mx3OBg9dra8bNo5a5v8XQbc7RzW91cGxxb3LUcXRtbHJzentzdHNvcHHeb4DbcdjUyMfCv8eIU2/V1G100dHXzsHM1cxwbNRuzHJstnbSysnY2dzXcW9xdnTX1drc4NDYzs5xbnmH2uHU0293xsXKzc7Eyci0sra9vMC9tcXGx8XMy8K1zsHIxs7Fy8y8v8LH0snItcvZ1dnOz8vSyMHMy8PIbm7X383RvtLO14DWzMLHZ3XR1nFv2tDIzcTM2M1weOji1dfb49nZ0nh429DM29XJbXZsbtDF02vJusnMas3CvsXM1dPSwnZxbtXV0crHvsTDzXCKbGfJxcy/aGhta2dqaWpqbm7S3dnO29J14OPU0O+Df9Xg19Pl63hy4+ro4OF2cXPa2dDF0Gpxb4B4eNdxdXNxcnRxcHBycXFvc3F0cXFxc3BucW/Ka3F7cHl1dHRyc3Vy29x0cnF9dnPgc3l0dXzNprZ/cHeDc3VzcXF1d3Tg5HfieOHY6HR2deLjdnl2eHZ2devj6HZ7dX1zenZzdefp4+PnfHrs7H987ux9eXzw63l8fnN4eHp3d4B2eHh1eXl3ddlxd+lydXd0cXp7eHB1dHTidHh9fXl6dHNz4+zteOfj43N4e3tzeXZ5epaugYiGfXnp7YB3en56fH9/e4SBeHx4dXl3gXd2cnl4d3rj9vx9fvN/gYJ99H19e3zpgXx/g3x66HV5d3h96OLj79/s5nd8d3d7dXZ3doB5eHh5d3d2dXZ5fXx8gOHrf3/09ID5gP6BfYN83Hl+dHh6fnl2gH94eXaGd3V8fHp3d35/jHt8eHmCfXl6enV5dnh1dnt6d3h8e3h4dOHtee3te3d88e19g/Hy9vV43+p9fnt4efh4fXx0d3ncdHJ0eX96fYByeHd1dHh1dnd0dYB+dnR4eHh8dHZ8enp5hIN6hIV7gIJ+e3Fz53RyeMp9eOh349PahHZ3dXyDeHh5enp4eYB9eXt2d3V2c3Z33HN1dXNzcnBucnNzeXN2cXN4eXx1fHyJdXh6fXbndoB/0dZzeHp5dXl3d3XoenV47e93fH98fX7n7Xbz7eR15HN33oDgdeVx3XqCfH15eX19end2eXV3e3B9dG54eoiEenp1c3d9dHR0eHt7gHfi43J0eXV1ddvleHV1dnndcnVxdN9z5t7f2nN23+p45+ju6+jk4OLhf4aSgYHq5nuljXfd3XZzfHd3eXnr4Xfe3uXo4XN1e3ZzduJ9ddnbd3Jyc9/j24Dc59TK093h0tnd0XVx4G53en3f4OXh4uTm4tnmdXV8dOt3dXh6cdp4bW1uamxwbnBwctd8f3LX2d3g39l1zs7U2Nrg5eDg4+Pd4NnR0tfc12/Qcm7S1XSGht9zc83RopTAxNHZcdnf3nN3c3FxcXXcbnBpaW1vbnRscGtsbXDZa4DacNvb0tbSy9KjZ2/a2nB019fZ1MnR1tFsatVvzHBs2X7Wy9DZ19vac3B1eHXe293c3M/Yy9FycXeD2t/a1HB31M/T1dzN0tTNzNnd2t3c1+fk5uPo6ODP49nb1t3c297Lz9Pa4tjbw9ni3OTe29zk39na3dbWc3Lf39bZ0NTZ3IDX0cvPbXPY2HVu3tTP0tLW3dZzduHd1tXg5t3Z3Xp029TN19vSc3xycNvX3nLYzNvgcuLZ0djY4OHZz355duPi4tra09jX3XqZcnDa1NnScHJzdHFtbnBxdHTe4OPe4td+6Ojc1PCEfd7m4Nvp6Xl25Ofq4ed4dHjo5OHc4Xd+eICDhfeAhIKAg4WCgYCFhoSCh4CFgoSHioOFh4X2h4WRhI+KiYiEhYaF+fmFgoCPiIT/g46HiZDf0LCWgYmXhYmFg4KGiYH7/YL1hPbs94GDgfr8goaCgoCBgP33+oKIgYuDhoKAgPb66/D3hYLv8oWD+PWBgIP+/oSIjYGJh4yIioCHhoaEh4iGhfOAh/2BhYyIhI6LioSJh4f+gYaNjYiIg4CA/f/+g//49oGGjZCFjIiJiqK0jJrHoYH9/4uAgYmEiIyMhpCMg4WAgIWCk4eFgIWEgYPw+P+Fg/uChYmC/oKEgYL2iYKIjYaC9oCEhYaK/vb6/+79/YKKgoONhIWDg4CEgoWIhYeFgoaGiouFhunwgoX79oH6gPqCgIWG8oKNhoqNjoiEk5GLjYyjjouUlJKOi5OXpZKSjpCVkY2OkIyOiIuGiIuNiouRjYmGgPj+hf7/g4CH/PaAif//+fuA5euDhIWBgf2Di4aBiIr4hYWHj5aOj5OFjIyJh42IioeGhICOhYWKi4yQhYiPi4qKlZePm5mOkpSNi4KC/4SBiLuGgPaC9+fylIKFgomNg4KEhIOChY+NhoyHhoSDg4WH+ISKioaJhYOAhYSIioaJgoOGioqFjI+miYiLjoL9hZOQ/f+FiIiIgYaHhYL/iIKG//+CiI2Hh4n9/YD9/vyB/YCC+YD8hf2A+YqTjo+KiI+NiomJi4WLlIiZkYqWmKOgko6MiYuSi4yJiouLjoX//ICChoKBgPH9hYKChY38gIWAhf2D//v89YSK+/yD//v8+PX09ezuhImahYn28obBn4Xz7YWAjYWEiYX++YXz9fr/+ICFh4OAg/6OhvT3hoKBgfj76IDo9ePg6vP97/n78YiB/4CMkJT////1+Pr68uf/gYCLgf2EhoqPhf2Pg4SEgYSIhIeHiv6QlIL3+Pj39u2A5OHl6vD4/Pj8/f/4+Pzw8fb+/oPxhYD09Yeam/6Cg+jsy7ba3uv2gfb3+YOHg4CDg4f+gYaAgIOGh4uEi4WEg4X/gID+gvz67/Dq5ezEj4D8/oWM+fr/++z7//6FgfyF+YaB5JH97vP8+f71goGBiYX4+vr9/ez96+yEg4qb/P379YGJ6urq7Ovl7fPi3+rs7ezl4vXy9fT5+fPg/e348Pz6+/nn5uv2/fHu1/P89Prz8e346uDs6t7lgIDz+urv5O/z/ID59Oz1gon494aA/PTl6+zu8PSAhfLv5+T1/PHu84mH+ezr8fXogo2Eg/z0+YP05ff8gPjs6Oz0///454uFgPj2+PHx5+zq8IiqhID59vXsgICChoKCgISBgYDz8vLq8uKD+vrs4/2OiOr36e3//YOA+fn/+f2GgIL/+vjx94CJhgN9fXyYfQF8jH2CfIZ9AXyFfQN+fX6MfQ18fH18fXx8fH19fXx8h32DfIl9hXwNfX18fH19fHx9fX18fJF9BHx9fXyMfQF8iX0HfHx8fXx8fI19BX59fXx8mX0GfHx8fX18hH0BfIR9AXyGfQF8hX2HfJd9Cnx8fX18fH18fXyEfQF8sH0MfHx9fHx9fX18fH19hHwDfXx8hX0BfIZ9AXysfQF8hn0FfH18fHyYfQF8nH0GfH19fXx8iX0GfH19fXx8hn0QfHx9fHx8fXx9fXx8fXx9fKV9gnyGfYJ8hX0BfIR9Anx9hHwFfX18fH2JfIV9gnyEfYJ8h30DfHx9hXyGfQV8fX18fIR9jnwDfX18hH2KfIR9AXyFfQF8i30EfH19fYZ8AX2TfBB9fH19fHx9fX18fX18fH19hHwEfXx8fId9AXyOfQR8fXx9h3wHfX59fHx9fYh8BX19fH18hH2HfIV9iXyEfYR8gn22fIJ9jHwGfX18fH19iHyCfYl8gn2GfIR9BHx8fH2EfAF9iXyDfYl8hH2EfIt9hnwBfYV8gn2GfIJ9hXyDfYV8g30CAgQAgGplZGZoaWtqdHt5eG9vbdZ5dn2cjnXWdnp4eXd253Hjc95xdnFyc3Bubm9ubW5vc25pcXxzeZeEaXFpdXuCRVVwdnV0e3h319Xe197k5Hvx6unsfejm3OV37+txenR2eX14eXNy09KJenTd49F13nZz33N5dHF2d95zcGpmZ2lsgGx2amVsa29ufnh3dnnYdoKLfXN1c3hxcm9z3uDV4evueefweXbm8Xp/7oH28X574nZ5d3Z+ent/eHt7c+NzeHJ2cNF1fXFwcHBxcW9rcG5pzXBycHBzdNlzeHN1d3Z0cXR7dHl3duB2deZz3nN46H2AenngfOXo5Hd4fHd0e4d7gHN0fXfec397fn16dXl5e+mfl3l7eYKC2nngdndzdX18fICCb3J0cG11cnJwbYFvcG5wdnl4cW5saW9xdXZ4c2tucXFrcXV0cWpzZ2lsdW9wbGlobmtpbWhpZGdjYWphYmhht2ZseWdubnNxfnZ2b3R3fHNxcXV9fXl5d3N33d6GgICGfHN2d3Fxc3Ryc29xcG5zdXN+f354dHhzdnJxeuByeXp3d4p8eHl3cHZ8d3R2fHp0eXlxdd52c3N3dnjfd5d7cnN1eHd5cnVqb25uaXHV2mxszXN1cG1wb3J1b9hv1tXUddjddHTf0eBzdHZ6eXngdXp/d+2Eeu3kf4J+e3V0gN1/htPdb3RzhXyEhHXP43d+dXVzcnlycnRwbHNzbmpsb3xud25pbHJoyHd9dnZwz8TMbczXcHNyeXPSdGtsbMbFvWtva2hua2loa2dkZ3Nlt2RqZWNoamO5u2JoZG9pbs1ub2bYc3F5gHp01Yh3yc7Fx2hwatNsy9HO09jM2cnSgMnJ2trWc3dx0HNvbW7ZbnCCcHhtyMS9yLvIbW5nZmxqaWdqyG52cXZwcHR9eXV2cnJ1dNRvzthzeOvk1uPP4ebc283n693h59bv5uFypnvo5+Dh4NvUcHJq093Zz7260NDExtDJznFw3NF2ctzic3TV2nF20NXRbWvL0WlvdXXOgNtvdG9ycnPa19ne4OZ8fHR21eJv1M7RcHducs5yrdbNx8/Lz8rZ2d/R09TJ0M3Y1L6/y9HU2dN02dbd0NHC09Hb0tnPcNTPyrqwt7W5wMbJwr+9vsLAu7K7uWdoZsdoaLK2z2pry9TUzc7K08/GysTAvbdy0L3MaWpy0deVhMC0gL5zyslnaWbEvcPLz9Jra8jHwsbNbdnOydTMam5tw7/I2c7W085raGtwzWpszGyEa81pymxrZ27Ry8fBzc5s1czTdNnL0G9rcG5paG1raGhjX7vAvsRkaW3Sa9jL2drS39zd5Nve1dXq7Onm6OBydHHX3G9zd29ud81mzWZmZmVjgHh1c3Ryc3V3fIB9fHVycNZ2dnySiHHTcXZ0dnVz43LjdOBzdHFzdXJxcXVycHRzdXBtcnh0eMyRaXRrdHmBTlxtdXJzeHJy19TZ1+Tg5Hn17u7wfOrk4ep17et1eHl6fYF5e3R22tOWg3fp8eZ36nt5635/fHp8fPF5eHZ1eHl7gH6GfXiBfXp3gXp5enzfeJqjfnd7eX51eXd25+np7fP1e+v3e3nn7nh+8Hrq4XV35HN0dHd6e3h5dHt9evB8gH2Ae+eBjH1+fX9/fnt5fH956Hx+eXp+f++AgH1+gX59fH6AfYN+fe15eOt343p86Hx9d3badufl53Z4eHV0eoR8gHd2fHXodHx4fHx5eHx5fOySjXt6eX+C5n3te3t6fH98foF/b3V3dnV7d3V3dIh4eXZ4fICAenl0cnp6fX1/enV1eXh5e3x6eHN7cHJ2fXt8e3h3e3h7hIB/fYF+foSAfH9743aElnd7fX16hHp1cXl7f3l4eHuDg4B6ent+7OaNgISKgXh7enZ4fHt7e3p3dnV6eneCgH54dnhxcXBveeV3eHl4e5V/eXl3cnV4dnR4f3t3dHNvdOJ6cnNzdnjfd5iAeXl6fHl7d3t0dXZ2dXzr6nV36X5+fn18e3+CfO967O/nffLofHv05fR6en+Cf3zmd3l7duZ8duTfe3x4d3NxgN5+hNbdcHV0jXuFgXPK3XF5dXJzdn13enZ3d3x+eXd4d4J6hH12en101n6AeXt429XhcuDjeHp7g3zrf3Z4et/c13R5d3N5eXZ0dndzd3102HZ9d3R6f3rl4nB1cnt4fOV3eHPjdHZ7gn544ZB94Onh5Hh7eel56u3o7vDl7d3hgNXb6OTcdXZy1HBvbG7cbnSEd3523tvW2tbient2dHV0cXBxz29zcHJwdHd+c3V5dG1yddp01t12eOng2dvP3+bY2NLc4dLZ4NTi4d93v3je39re2NfRcHZt1d3c1srG19PR2uHW2nNz3NVwcNbabXDR029yztHNcXDW3m9yeHXPgM5tb2ptbm3SzNHV2dt4fHFw0txv1c/QcXNsb9N62NvSy8/KzM7Y3uLW1+LS19Hh2c7H0NXW4N544Njg1NTI0dPf1d/dc+Df2NDHz9LY4eHh2tPb2eDX0szR0nBxb9Zvbr/Mz3Buz9zf2NrS2tbP2NLN0Mx74szacXF43eHNwM7IgNB7295xcG/T0tvi4eZ0duDf2NzjduXk2eXddHZ018jT3tPc2d9xb3B03W5x2XGIcdlv229vbnTd1dTP2dx34trdeuDY33Vyd3l1cnd2cXFubNbc3NpucXLdceTf5uTk593f5+Pe19bp5OPh5eNyc3Pf4XN3fHd0e+Fv1nFxdHdxgISBhISCg4OGkJGSkIiGhPmMiYuKm4T0gYmEiIiF/4L4g/+Fh4SFh4WEhIeFg4eIioSAiZKMj8WngoqAjZKeiY+Bh4iGjoiF9Ov07+7084H//Pj8g/r38vyB/PyAgoOFhImDhoCE8+6ZiIP6/faC/4aD/IaHhYGDhP+FhoGAhoKDgIeShIOMiIeCkoiGi4z7h5+kkIqMiYuEhYOA8fDw7/v9gfL7gYD1/4CC+oP/9oKE+4GFhIiOjYyQhYuJhP6FioaMifmOnIuJiYuLiomGi42E94iKhISFhvuFh4KBhYWCgYOGhIiDgvuAgfqA9YKI/YeKg4Dwgfn3+ICChICAg5CGgICAioH8gYqGiIiGgYSDhf2ln4eIg4mM9Yf7hIaGjJCQkJOTgIeNjIySj4uMip+KjYaMkpeakZCKiJKTl5idl4yNkY6LjY+PjIaPgYeHkIyLh4WEioiJkIiJh4mFhY+FhIiC9YGIkoOIiYyIkoeIgouLj4iGg4mPkouHhYeH/vuSgI6Xj4OJiYOCioiGiIWAg4KGh4aQkIuHiouCg4GCifqEhYuKi6mQiomJgoeLh4WKk4+KiYqAh/uIgoSFhon5iKqRiIiKjIqLh4uBhIaFgYf9/4OD/Y+OjIeFhYiJhPyD/fj0hPvvgoP85v2AgoeIh4P8gYOIgPuIgP37jY6JiIKBgPqNnPD/gISHnZGbmIjq/4SLhoODh5GMkI+NipCSioKHh5KJkomBg4aB64mOiIuI9fH3gPv9hIeJlYr5iYGCgevu6ICIhIGEhICDh4SBhZKG84SJhIKIjYX8+YCGg4qEiP+GhoD9goCJlIyF76GN+P3z9oGFg/+E9/j09/jr8uLwgOLp+/fxgoaB9IOBgID9gYWYiJCG+vDr9er4hoeGhIWFhICE9YaKhYmDiIyUiouLhICEhfGA6PGCg/ny5Ova7Ori4tXq8N/j7eH08euD8IX1+fj6/fz5g4mA+P/9+Ovg9fXs8fnw9YOE//KDgPX6gIT394SH8/z2hYL2/oOIj4r5gPuAhIGBgoH36u72+P2FjYSD7PuB9O7zg4qBgPKO7v7s7fLv7O739fnu8/Xq8u399efg6vD09v2J+vL96+/h7+319fj1g/n26+HW3d7j6vD19Ozx8Prz6Ofq7IGEgPWBgN7r9YKB9fz+9v3z9e3g5t/e39uG+t/wgYCK+P7LwuvjfO2N+fmFhIDu7vj6//+Bgffy8vf4g/P66v77goSE8Ov2/fX79v+BgoSJ/oCC+oaohv2A/IKDgIT27e/i8fGC9uvyhvzp94WBhIiFhouMhYaBgPr+/v2Bg4T/gP/09/X39ezw+O/s5eL/+ff2+f2AgYD5/4GHjYiJi/uA9oGEgo99CHx9fX1+fX18hn0FfH18fXycfQJ/fod9h3wBfYR8AX2EfAN9fHyKfQ18fH19fXx8fH18fX18hn0BfJR9AXyMfYZ8EH18fH19fHx9fXx9fHx9fXyMfQF8hX0BfI19AXyGfQF8jn0JfH19fH18fX18hH0FfH18fHyMfQF8in0BfId9A3x9fMh9AXyafYJ8n30BfJd9AXyGfQF8kX0FfHx9fXyJfQ18fXx8fH18fH19fHx8hn0BfIR9BXx9fXx8hn0FfH19fHyIfYJ8mn0BfIV9Bnx8fH18fIV9AXyEfYN8jn0BfId9gnyGfQV8fX19fIZ9A3x9fYR8BX19fXx9jnwEfX19fIR9AXyGfYZ8iX0BfI99Bnx9fHx9fZN8g32HfIN9jXwVfX18fH19fHx9fXx8fX18fHx9fXx8hH2CfIZ9hnyEfQZ8fH18fHyEfQN8fX2ZfAF9jHwBfZV8C319fXx9fXx8fH19jnwUfXx8fH19fXx8fX18fHx9fHx9fX2GfIJ9hXwBfYV8g32IfIR9Cnx9fXx9fX18fXyEfYZ8CH18fHx9fHx8jH2EfAV9fX18fZN8BX19fXx8hn0DfH18hX0CAgQAgHhybXBybXrMbG5tbsnF0dZtb3d4dJp1dnVzdW9wdXHhcHBvc91ycm3ccnF1cHV6eXZ3eXV2eHd3dm1uc3R3d3hxdnlqZXV41nDYb3x3b29wcHLW4nTb0+Lkf4Lz8eLj2XlzeOB3gODY03Jxbc9tbddyb3DZbm1tbG5ucdVzb3dxgG1mwr/AamdZb2xtcHh7d3t8gnFzcHJrdXNtcNVvc319eOTk43d00oPBg+PW6+RzdH2FfnqCft6NhXhyeXhxc3Fz2tfWdHpxcGt0b21p0Gxvcm5wcXF2b27neHp5d3RzeHt3enp6d3d6bXJydnp7g4mAe56ChZF6d4iOfeiH6XyGgH93fXzreOrgfn567uzyfHx7gHzv4n98fIF36niHeniGhXt7eHRyaHZ6cW11e3Rwenx3lHxvcnyBdHh1fXZ0dWxqcW9xcXRyc3F5bHdxbdVrcnFwfW9qaWTLZWq1ZWuLVG5jbGVnzGjIbWptc3F5ctl5dXl7eOvj6Xp3d3SCfn2KgHftjnt2enfXdnV/eXV0fN92fICEgIiCf4J7hoGsgIWEenl+fXZ1cnl4d3lzdnppam1ydXdweXNscXpzdHVvdndwbGpqbWtxbmpnZ21ubWdhZnBvbmdjYWxtb2lvb3FvZ3Bva9Zt0s3X0pB+dnZ56Xp48ep76XyB5eBydXRvbNFvgM5ueHpwdXdwc3N0enrde3Nvbnh7e3l7epBrendzbG9tbHNxZ2tubm9rbnBtaWhmyGlk0G3Owm1rx2PMwchpbmjQaGhkZW9rZsZrZWRlZ2KwYWdqYWRlaINrZGZtam94b2ttbGtybnVubHBybmtqa3HSyM5zzs570dZxx9He09hxgNDcc9fW29vY3nbe6tnVydfM0m7T1M7Hy2tqy8RnZmVhwsFjZcNratHQzYKb2NN8gn114NnRzMfledvehOTh4eXn4e3p+ffu+vLx9uHj6vLn7OmA5ODe6ujf19fOw8rEy8jRcHDLyczW1tLQzcrO0svJxMxozHHOfnVsyL7Dl5FtgNFwa3BudX9ycszG1c/Lxm3Ub83TbczQdXB9c3JlnszQysPOxMzh083fc8TKbNLTyr7EzNfN1G5w2NTT3eCFic7JztfX29TP5NfZatLPh3RwasbJxGpsx8PNzWnQvG3PaNDFxMjIyMW9zcXBamtqd87L0MrHxcTCwcRxb3DN0sy6gLa/u8a9y8fAjX3Iw7u8ys3Tb9HYb9Xe5Xh13dze3OFydHFwcW/Zb2ttctZxeYJ4cnV2cnBtc3bf2c3NzMrgdOLX2tzObWllZb5haHBwa3GBdmZnbXVzc2/QcG2Mg3nex+F2e3vC0HHY0srRc9prdnV5eXBubWpnbnRrbWxucW1xgH53cnR2cHvac3JyctbZ2dtwc3V3dY11cnNwcW9tcnDicnJzc+Bxcm/ecnB0b3B0dHFvc29xcHNydG1rbnV4dXtzfX1zdZaA4nDdcXx3cnFzdXPZ53Th0dfffX7v8Obl4nt3eud4f+rk4Xp8dul4eOl6e3vrcXd9eHd2eed7eYB9gH975uDpfnp8fnZ1dHV2eH19gXR3dHhyfHp2eOR1d3+BeOzr73x21YbDg+DX5+Rzdn2EeXR8etuEfXh1ent3d3V56OLge4V7fHiAeXVy4HV5e3d2eHl+eHTyfYCCfXd4gX99foB/e3x/dHd2dnt7gIJ8eZl+hpJ0dX2Adtt92nR/gHxzeHfndtrSeHp88PL5fn2AhXrr4nt8f4F78nuBeHmDg3x8fHd5cHh+d3V5fnt3e3x+l4J1dn6CeX17gH57f3Z3fn19enp4eXZ7c3t7deZ6fXt9iICAf333fobugISwkIh6hnh58Xnpf3p8f3uAffKEf4KDf/nz939+fHuHhoaRgH/uioJ+gHnaeneCe3Nxd9lwdnh7e4B4e3x2e3q1dnl4cnN3dXBzcXp5c3l4foJxcXR7fn95fnduc3h5e4B8g4J+fnt7enl9f3x4dnx9gHx3dn+CgHl0c356e3l9enl6dHl3dul25t/q6JOGg3+A8399+fJ/7oCD6eJ0dHJxbdpygNdyeX14eXx1dHR3fXzcd3JwbXJ3eXd3foxze318dHdzc3Z1cXR5dnh1dnV0cXRz3HZ14nju43d543Tt6ud4gHvueHZ1dn18d+J4dHVzd3HVcXp7cHJydJl/cXF4dHiHeHJ2d3d3dnpxcHZ5dXBydHrm5eV86O2G7Ox72+Pq4+R2gNfec9XW0dDU13Hb4drSz9ng3XXk5OXf5n144uJ1dXNw3N9ydd5zdNrn4Yif3tl9g3x15N7V1MzgdtfZgt/g1+Lc2NvX4+PX4tvc3czM09nP1NZz2dHT2tjZ0tXW2NnZ1tPWcXLO0Nbe3N3g1tre5ePh29pv13nTiX9s0MTFi4ltgNVvbm9tdYB0cs/J2djN0HHab9jacdPPdm59cnNv19vT0MnUz9Lk2tbnd9LZctrg08XQ19vV3nR12dve4tyFkNXR19jY2tXW39vccd7muJd5c9jQym903djc1m/az3LVa9PW1NTY4+Hd8ODZdnR0hN3Z2tvY2dvU09d6enff5eTTgNHZ29zc5eDWqZXS18/V29jectzhdt3e2nZ23drT2txudG9wb2/TbWlrbtVwd4R1cXJ0b21pcXLf2dHWz9Ppd+rh5OffdHN0dONvd398d3uOg3Jydnl7d2/PcXGZgXni1Ol4foDS4Hfr4djhduRydHV4enNvcG5ubnNxcnJxc3J3gJGKho2Kg4/7h4GBg/by+vmCgoeIh6SGg4aChIGAg4L/hoWChP+ChYD+hYSHgoSKiISFioWFhoeJi4GBhoqOjZGHj5KDmdiX/oP+g46HgIGDg4L0/YDw4+bphYf///f08IOAg/mAifT29IGFgf+Fhv6Dg4T8gYODgoGAhfmCgImGgIWA8eTvhIGri4CCgoaKiZCNlICFg4iBiYmCg/+AgY6OhP34/4aB5ZXpjPDk/PmBgo2UjIeQi/iZlYuFjo2Ih4aH/fr2h4yFhYKPh4OA/YGGiYWChYeLg4D6g4iFhYKDjo6LkJCPi42OgoSHh4uLkZOMhrCOlKSEhI+ThvuQ+IeQgIyBhob/g/LlhYOE+vj9gIGFioD27ISBhIeA/oSMhIWSkIiIioeJgIuQioeNkpCKkJKUu5iHiZOYj5OTmJORlYqKk5CQk5GOjIiMgIqHgfqEg4OEj4aEg4D/gInzhInh45GCkICA/ID1h4ODhICJgvmIgYeEgf79+oKBgIGNjo6dgInti42Ii4bwhYSSh4OEivqEiIqNiZCLio2Fi42+ipWPiIqMjoaGhIyMiIqIj5aEhIiPlJaNlI+Bh5COjZGMkpOOjYaHiIeLjImFg4qMjIiAgpKQkYeFgYyMjoaNiImNhIuGhP2A9erz86GRiIOD94OB/vqD+IOI+PeAhIOCgPqCgPOEi42CiIyCg4KFjo37ioSCgYaNj4yMkZyCjpGOiY6IiYyKhIiNh4mDiYaHgYKB/4KB/ob+84WG+4D99v+DiYP9gIOEg4+JgfuKhYOEhYT3goqJgIKBhqOIgYKGhoaTh4WHioqOio+HhomKhYKCgon79O+I/PqQ+P+E5PH/9/yDgPD6gvLz9PDz+IL6+PXr4/P09YD3/P7y/ImF/v2EhoOA+f+BhPaBgPT6+pav8+mLlIqA9u7o5t78hO7wj/n57/n18PPx/fn0+fHu7dzh6unl8e2B8ebl7vP16+vs6/Xw8+vvgIDj4+v39PXz9fL1/Pn08vuA/pH7ppmC9erw08WEgP+IhISBiZWHh/Dv+PLo64P8gvb9g/r7i4WViYWA6vvr6uTw5vX+8Ov3gObrgPH65dzp7vbt+YWF9/n3+vOWo+ni7Ozt8+nr/vH2gPz8waCIhPn264OK/vn++oL/8Yf7gPbw6uvp7u3n+vDrgIGCkvPt8vDp7+/o5/GJhIL1+/nlgN/q6fHr9fTmoJLn7OLj7/LzgfX7hPP8+IaI/vr5/P+EiYGDhYf7hICChP+GkKePh4uKhYKAhYX/8+jk3tz3hP/z+vzrgYGChf+CipaXkZeploSGiI2gkILxg4K3lon66PyCiYnf74D39+zzgfeBhoePkYqGhoOFhYiGiImHhoiSh30BfIR9hHyPfQF8hH0FfH19fXybfQZ+fn18fXyIfQN8fH2EfIJ9hXwUfX19fH19fHx8fX19fH19fH19fXyHfQF8hn0GfHx8fX1+k30BfIV9CXx8fH19fH19fYR8iH0BfIp9g3yJfQF8in0BfKJ9A3x9fIZ9Cnx9fHx9fX18fHyFfYJ8hX0BfLN9AXyJfQh8fX18fX19foV9A3x9fId9AXyFfYN8in0BfoR9AXyHfQF8030CfH2EfIV9C3x9fXx8fXx9fXx8hX0DfH18jH0BfKF9Enx9fXx9fHx9fXx9fHx8fX19fId9AXyGfQF8oH0KfHx8fXx8fXx8fYV8BH18fH2GfAF9iHwBfYV8BH19fHyEfQ58fH19fH19fHx8fX18fIR9hnwEfXx8fZZ8AX2PfIJ9j3wOfXx9fH19fXx8fH19fXyIfYZ8CH18fXx8fXx8h32LfAR9fHx9iXyCfYV8gn2LfAN9fHyEfQV8fHx9fYR8Bn18fH18fYt8hH2KfIN9jHyCfYd8CX18fH18fHx9fYV8hn0BfIR9AXyMfYd8AX2FfIR9AXyPfQF8hX0JfHx8fX19fHx9hHwCfXyTfQICBACAbneKfH10c3VvfYLW0OB153Z3e+PS43yMy354cXPZztDW0eLY0t7i33LN1dVw33R2dHd3enh9dHDednh3dnBtc3Nwa2pmaGZqbmyAx2rLy2x2cnDZ1tfc19R1hHPZ3dza5ufh4X/q3/B5e3eBdHRxcdXKdHZybWxsa8lsbm9u09OAyc/Qxc2909LWcW9qaXiFh3d4c3B0b3Jv0dJsdHVyb3F0eH17dHXkgZh7fX1+6HN3f3t8cXVzenx4d3Z4c3B5b3Z+oHp7eW1wb2zdc9fac3N4cnR3bm91dH17fHl1eX16eHp7fHl4f4J67H5+f4SDfnt7f+Xx7H17f4CEi4N9e3qAent7gYF2fnOHfHZ5fIKFh4J/evb1d+h5en1+kI+FfYZ8eHh7gIKWfHh1d29wcHl4Z2p5cnp8cm9xd3t3dXR0cnR5dm9xcWtyenp76Xvj13bl2ORy3tJsv8JmZmO/Z2NkbWVoa2jObXJ0dXd0cXffdH3l3t/j1t16eu3qgY59f+iAf3t7hYB9fnh+foR/eXRvdXp1fH10foGBeX1+hIGFhIiBgoGHgX97d317dnl1e3Zuc8+iemhzdXFmZWdvc2xufHhqaG1zamZpbGprbmpkbWpsam1ta3NpcHltcXdza2pubXJzdXLadnpydX5zfYOAdIB94oZ8d3Vzh3503dJvdHqAetrQcX93cXBxend5hIN443HhcXl3c3VvenBuc3ZubW1yen51bXJxbGxvb3B3btNudM92dXRvd3Jybs+30GzLwsjHbm1pbsxqa2dzbnRx2cPJytRxlXJszdXedXXf2HBrbXJyb25qgGt3b2puaWps0WvR2HVwcnd4g2/V0NPdwdKA2uDg19hy3szI1W/Q1W9ubm9scW1qZWlkxmNnZm3Mab5lZMRpZWtr0G3JacrYbW5rbtPTa8bBxsjIzc/T13R/dJbjdnfodnbgdeV5eHt+8X7t7ezf6O3p3uDX33PY2tXQ1GnLzcHKw77FhsnP1tDN1MrYfMfAyWjIxr9lyWrIbnqAm2xxz8DMzm1vbctw0N10321tcXBrysbW3NFw1sbRy9PUbMfDw87LwMlzxLvJytDSx8fRc8e+zHF60m9v0Ly9wsPO0MjPybvO0nJ4RlNwz8fCt7bKb2htaWxtyMG/xsq5vb/LeZbXycfBxMXJa8h0cnHb2HNwcdpw0eDg2tTY1daAwbTCx7PMbXC3wMHBusG8a9HSyMW8tM3a1cXFzbbFz8TWeHLTzMrMbtbP3Npzd27J13d2cnvd3Hp34Onk4dPS08pzaWZqbGlpa29xdXpsb3J0cW5td054zWtxcnVuybvdbdVxctnM3nHT1eDe5tbW5nNzfYrdcW5ub3NwcnB1fnSAcXqGfHlxcHNxeXzW0N9x4HFzeOTW53qHxHx3dHLa1t3h3e3e4ePi4XPY2uV16XV2cXJycW93cWzdcnRydHFyd3l3dXd6dnVze3yU3Xfi43h9dnbm5eHq6OOEk3fk4+Pb4+rn6oDv7PyBhoGJenp5efHtgYWCe3h5duV5fHx66u6A5+vn4OHc9vb4hIB6eoSJg3h9e3eAent65uV1e3t5d3l4f4KBeXfrg553ent65HZ3fnp5c3V0dXt4dnV3c3B1bnZ/oHx9fnZ4d3LteujpeXd/eHx9cnN1eX58fHh0d3t4d3h3e3Zye35253p7fIF9eXV3ed/u5Hh4d3l9gnh5eHiAdnh3eYJ0d3CAeHN2eX19gHt4c+fndud2d3p9lYx/eoN4dXl4fX+OeHZ1enV5dnx9cXKAeX2CeHZ0eXl3dXV5enp/fnV6fXyChIOC9IT59IL8+PmA/P2C6fJ/gH7yfHt6gXh7fHnvd3+BgYB8enz1gIX58e/y6/WBgv/9i5GCg+2Agnt9hIB7fnd6e4R/eXpzeHx4fH13fX59dHJzeHV1enx3fHh9d3dycnl5d3l5fHt3fc6ih3V/gX1zcHR3fHd9h4F2enp/d3N4fHt6f3lzf3t5d3x7eH52e4R5eIB5cHJ3dHZ1eXjfdnp4d352fYB8cn196It8enh7j4J55uR1eYOAfd7ZdoF3dnV4fHp5f4B563TldHl3c3h0e3JxdXx1c3R2fYF6d3t3dXV3dHJ7ded0eul7eHt5f3h6duvh6HTl1+Tgd3ZydOR3c3B8eHp55dPW2+F2l3l12uDjeXjg53Vwc3h6eHVyhHB/enZ1dHJ04nPe4nV1eHp6gnfj397jzOKA5+bn2+R05NrY4XXl6Hd0dXV1enp1cXVv3W5xcXzgdt1xcuB2cXRy3HTgcdrfdHJzc+TectXU0M3S0tTa3nd/ao7adHTbcG/Tbtdwb3B04HPb2NrM1dbPzM/M1nDW297a3m7X3trf2drhoeDi6uTe5Nnsi9rX3HPc3tBv1m7NbXWAg2tw0sXS1W5xcM5vztJv1Wpqbm5sysrV3NFu1Nrb1t7mdN3R2N3a0+V72NTb3N/e09PfddfU1niA33Z24NS+1Nvg3Nbc3dnj33d/T3h029jNx8HQcm11cXFz4Nrb6Ozd2tbnj7fs39vc3tviduaFeXjo43h3eup66O3y6uLl5OaA1s/W2cnjdnrW1eLf4OHdd+7p5uHf2Orx5tnW273P4M3ifXrb2dLSct3W5d91eHPS13R1c3jk3Hl35efo7N7i6uN+dXB3d3d1dXh2dn1vcHJ0cG1xelqG2G9zdnx01tLveOR3deTU4HTb4ODk5d7a5HZ2f43meHRzc3Rzc3JyfHWAgoickY2GgYaFjZD08v+C/YOFif30/YqW1YuHgYH06Ozy8v718vj/+4Hq8PeD/4WGgIODhIKKhID/hYiHhICDh4iHgIOGhYGEi4me9ob9/4iRhYP++fj++fWTpIDz8e7h6uzl5H/v7f+EiYiShIWAgfb2i5COhIKFgfKAgoCB9v2A9v388PLf+/7+iIWBgpCZmIqOh4OJhoiF+PqBhYaHhoWBi46NhIT/krmEh4uK/4KHkIuMg4WDh46JjIiLiIOLgYaMq4uNjYWHhYD+hfn5goCIgYeLgIKFh4uKjIiEi5CPjY2KjomFjY+G/o2KiZGLh4KBiPD68YWChISNmZGJiYaAi42Lj5aJi4OXi4OGio2OkoyFgfz/gvqChIeJpJ2OiZKHgoiGjpGhioiHi4iHhYuNgoSSio6VioiJjY6Kh4mMjI6UkIaJiYGJjYqF+oH573/58fZ//v+C8PSChYH7gYGCiYGHh4L9hIqKi4qDgoP4g4f88u/14fOBgf/9i5eHifeAiISGk5KIjIWOjpWQioiAhImGio2Hj5ORiIqIjoqJjJGNkI6TjZGLiZOQjY+NkI+Hjfm9mYaTlJKHhouMko6PnJiMiI6VhYeGjIuLj4aCkIiOjI+RjJiNk5+Nj5aPh4iNh4mHh4XygYaEhpCAiY2JgoiH/5WNiIWGk4mD+vaAg46Ajv/3h5eOioaKj42IjI6F/YD9gYqKhImHkIWGjJGIh4mJj5WMhoyJg4WIhYKNgf2Bh/qJgoOBjISFg/Xg/IP77PX0h4WDh/6AgoCNh4yG/+vu7/KCpoSA6ff5h4n4+4GAg4yOioeEn4aUi4OIhIaI/4L2/4mChIqMmIX89Pb95veA+Pr/8fmB+urn9oL2+oSBgoaHioqIhImC/4CDgo39hvqAgfyFgIOC+YX7gPb6goGAgf/0gO7r6uTq7e34+4eUr8P8hoj9goH1gfqCgoOG/4T/9/ju9Pfs7e7p8oD3+v34/ID19Oju5OPnmeXs7/Lr8vD4lPLv+oT8/fGA94L2g56A94GF9+X6+YKEhPOF7PaC/4GAgoSC9/P///aF9fj69fX8ge3j5+3r3eaA5d7o7PDr5uz2guzn8YmV/4SB9ebZ5Ofv6+Xs6OPw7JCgvb+G/Pnq49/3h4GKg4KD+vPw9/zu6+v6ncj+8e7w8vHygfaMgoD18YCBg/+B7/v/8e/y8/eA4NXf5tLygYXl7O/t6+/kgv/28evn2/L/9uro7Nbf8OH7iYX19Ojtgvnx+PuBioHq9YKAgIn774WA+Prw8u7v9/WMgoCFhoODg4uKipKCh4iMiIWHkIK3+4CEhYqB6+X/g/aDg/7q+4Ly+fr8//b3/oaHjZv9hYODgoWCgoaEjIqLfQt8fHx9fH19fXx8fId9i3wGfXx8fH18in0BfJJ9BHx9fHyEfYZ8g32IfAR9fHx8iH2CfId9AXyEfYt8j32CfIx9AXyGfQF8nH0EfH18fJt9AXyJfYN8nX0EfHx9fLR9Enx9fHx9fHx8fXx8fXx8fX19fIh9AXyIfQN8fX2GfAR9fXx8hH0BfOV9AXyMfQF8iH2CfIR9gnyMfQN8fXycfQR8fX18iH0EfHx8fYR8hH0BfId9hXyEfQd8fHx9fXx8kX0EfH18fId9i3wBfYR8A318fIt9AXyEfQZ8fXx9fXyEfQZ8fXx9fHyEfQN8fH2JfA19fX59fH19fH19fH18hH0CfH2LfAF9hXwBfYd8AX2IfAx9fHx8fXx8fH18fXyFfYR8CX19fXx9fHx9fIV9hXwBfYZ8AX2HfAF9iXwJfXx8fH19fH19jXwFfX1/fn2GfIZ9iXyCfYd8DH18fX19fHx9fX18fY58gn2HfAF9kXyCfYR8AX2EfAV9fX18fIR9BHx8fX2IfJR9A359fIV9C3x8fH18fX18fHx9iHyEfQF8i30CAgQAgHJ5cdmCfnFzcXZ5c3Fzcnd5dXt2eHZ4f3d7eH1+fHt4fH6BfXx6e3Z46XuB9Ot8f+92eXx3e3/Z19twcd1ycHFzadHGamlzaWlmaGNqSndvbXBlbWhoc29pbcvG3tjegNbZd9/h3ufUxXrY6njldG1udnR4c3Jx2HBzcnnn3c/ZgG5wcXTMyHFtcGttcXV8xqZz23B1495wdXHhdXZ5eXV4e3+Btn91dXfyeXd15up6e3p233Nze351fXlucXh5cHt6c3RycXHfeX11cnyAe3t6eN9zdn/x8Ox8fHx6g3x4cXR4eXJ5d3l4eXqBe4F7fHt663+Df317dHuAe4WAfHR4gHlzd4B2dH9/e3V1c3t7eHN+63zqfnl7fIGChXyChoB/gH19fn9/gIB/f3x8fHt2d3Rzd3t2cHN9cnV+cHN1dnF+h352d3tscW924tt5b9Zrc2vNbGbJZ29ubW5macrCZGi/dGzHbXBwdH92gnBudHZ+dILd1ODjd3TU0NfQ4HR6gHZ1dnd5dXl7fYN+fnt6eomDinl8e398gXuFfoGIunp1dYuIcnVz3nJ4eHV4cnN0eG60kG5xamprbGxpamNocHVtaWl1bWlsdHtxb21tcW9tbGlsbG5pZXB6cHBsZ2xna2ptcm1ucXR2cHjkeHd3ffB68et6hpCDfX58fnLPetbRgMxwbWl1cHN4cnV6fYaIi4V/dnODee98doB7cXBybXBwcXJzdXFrc3N1eHN6e4R66t/ldn6Cd2l7dcrK0XFucnDU1mtpaWjNa2twbnJ0cXnT02xs0XHcb3bU0s9u2MbM0GtrbHV2cHtxcHVxcXl54Nx0fXp2d3ZwdHLYz8RubsN0gJhr19Juz290bdZtyWpsb210kXduaMlpZWVlZ2ZhyGtoadJxb9HmddqBc9nXdc3P3dJvcdNra9HSzcbSzmxvzs1w1m5zbXLP3NtvcXBtcn91b3J1cXB0dXV1a8lqzW3RaWxzxsXN0eLUzcfaxsvIvcjTzMXV0d5yc93Yb9jX0883gDpqcdFyc3h6j4dxcHJxd3R418/Qbs7Ny9dsdHXRyMXHyLvC0YDa3s/dd9zb1dnIytXY0tnVc8/JaszJamxuzsrMac/Tzr7MzcjI0cjKxMzI0MbAyMHLxcBly73Bwrixt7i3tLG0ucbIxszN0dfS1tfV2tt4foTh597l2c/W29fMgNPP28TX2XF23drU1HHZcuHUzq+/v8zKzdfZ1dnU4oB43eDT2OTf3/Hm4+nn7+Pe4uT08H+C7efo6tbbdHVz0M7Lbd1wd3d1eXG9oXNybG9ramvFucK+v8VjZ2llZ2fJ1NPQ2HDecMrV0OTi0s3YzMbH1G3a4NzMcYBwc292b3VxgHZ7cNqAe29wcHFzb25wbnFxcHVxdHJ0eHF1dHd3dnV1eHl/eHVzdHNx33Z96+B1et5xcnZ1eH3g5uh2eOp8fn+Ee/r2gYOHgoF9f3l+V4t/eX1zfHV6h397eeHh9vD4ke/1gPf99vbs147w/ID6g318gH2Be35/73p7fHvw7OfrgHd3eHvg5n58enp9f4CdzKl+7Xl98PB4fXvxe3t9fXl4e3+H1Xt2cnLodXZ36+16gH945nd2e394gXxzeXd5c3p5c3N0dXPieHl5eIGEgH59fOt3eH/x7+59fnyAgn55dXZ5enN5dHd1eXh8eX12d3Z04nh+eHd1cXd8eH98eXR3gHNydX53cXl9enZ1dnh5eHV86HbjeXR0dHp8fnV4e3Z2dnd1dnd6fHt8eXuAfHd0eHl3fIF9fHqDeXmAdXp7fneBiYJ9goN3fn2B+e+Cgvt/hYD4g3z4gISJiIR+gfXseXrohnrqfH9+gIeBi3d3foOEf4nv5vf6goL49/Xv+4OGgISDhIOCgoJ9fYN+enl3eoN6hHh4eHp6fXmCeXqF1Hh0eIeCcnRu1nF4eHh+en17gHi2mnp7eH1+f4OBfXiCio2EfXqIfnh5gYqAfHl2e3h5eHV5dnp2c3yFf3t4dHZzdXR4eHZ1dnd6dnjoeHh2fOx47OV2goWAe3t5enHOetnWgN17fHaDeXd5d3V7en6Ahnx4b3J3dOp3d4B8dXR2cnN0dXd3eHVxdnZ2dXB0dXp45N7ldXZ9k4B2dNTY5HZ0d3bq6HRyb3PmdnJ1d3h6eYDc3nFz2Hbhd3nc2tlw5dra3nV0d3p5dHt1dXt0d3h53d52fnp3enl2eXXi4Nd4d9uBgKJ77et45nd6deRy2m1wb251nnxxctt0cHRyc25x3XVxctt3c9rtdt5/d+Hfds/T2txxdOZzdN/d3dHb2XZz2NVyyWhuamrMzcRmaGhlZ3VtZ2xubGpxcnF0a9Fs0nPecnN72Nnk5vbh4Njn2uPg3OTs39vl2+BwcuLic97f2dRCgFlwctVub3JwjYdqaWtpbWxvzsbLadDV09tveHvh2tfc3M7Y4onm5trrfeTZ4tnMztjX19racszPadLMb29x19bdbtbb3c7b1NPV3drWz8/M1s/L2NDX19Ry4dXe3dfT2NvWz8zT2+DW193e3uHY29vW3Nh2fYDf6t7p49jh5N/WgN/b5tTd4HR26eHZ23bkeObc1rTIzdjV09ba1NfP4X1z1d7Q1NnT1uHW1dnZ2tLU09LZ1XN23ODg4Nffd3p96eLjct9wd3FxdHC1tXZ0cXVwc3Hf2Ofj3+N2c3Z1eHfe5ubp5nXrddXk4/Pz6OHt6trc6HXq8OHed4R1eHN3cXZ1gIqKgPyUi4CDg4WGgICEgIKGg4iGiYaGi4WLiY2NiYqIiY+TioiFhoOB/YSH/fiEhvuAgYWCh433+PyAg/yGhoeKgvz9h4OPiIeFiYKIjaqRi42AiYKDjoiBgOni+vD3ju71gPf99fPkzonp9378hoCCiYWJgoaH/ICCgYH//vz/gIGBgobv9oaEhIOEhYGh/MmF/4KE/P+AhoL/hIWKioeFiY+X04yDgYL/goKA+v6DioqE+oOEio6Hko2BhoaLgoqLgYGAhIL7iIiDgo2SjIyHh/6CgIj+/f2Gh4yNkYyFg4WGiYCIg4WGiYaMhY2EhIWC+oqPiYiIgYqQjpmVkImOgIuJi5SMhI+UjYaCg4eHhoCK/IX+iIaHh42QlYqPkYmMjI2Ki46OkZGRjY6Sj4mJi4uJj5ONi4+Yi42UhoqLjIWRmZSOkZOEhoSI//SFgf+AhX74gYD/hIeOjIuFiP35goTymob6hY2KjpaLl4OAg4mLg4/15vP3f4Dv6vDq+YSIgIODhoeHgoeGipCIh4iHiJWNk4GIiYqIjYWQiIyR34mIipyZhouE/YWLi4uRjIyLkIjcqoWIgomJiJKQiIOTlZeOiISWioOMkZqSj4yLkJCOkY6UkpeRjJqjmJmQjI+JioaIiIWCgYKHg4X9goCAiPuD//uGlJiOiYmJi4PuivT0gPaHh4CPh4iHh4WMjJaRm4+Lg4OKgv6CgomIg4SHhIWGiIeLjYaAh4WFg4CDhYyG/f39hIWN6tGGhO7s+YWCg4X//4KAgIH9goCHhYmNiZH9+4GA+IT/hIb58faA/vDz+IKCg4eEg5CFgIuBgoiJ9PiEkJCLjIiCg4L+/OqDg+yOgLmD+v2E+ISMg/+F/4KEg4KJsY+DhPyHhIeEhIGB/4WAgPeEgO7/ge+KgfPxgeXn8++Ag/qBgPn4+ez6+YSF+vyD9IGGgIP2/fqCgoKAhJOLgoeIhoSJioyLgPeC+oT8gICJ6Obq7vvo5t3s3+bl5Oz47On07/qAgv32gPX49fKOgLOLgPSBgoiGq6ODg4aCiIeL/vPzgPf79viBiIv67+3s6d7k7ovv+OLwgvPs5ujc0+ry7vH4hPnxhP7xgIKG+/b7gPb29eDw7Ovu+fPu6+/x/PTt+/P17fKC+O3z8evl5+rn39vh6eve4Ovp6e3n6Obi6uqDjpTw/Or59+jt9/HqgPPv+eP1+ICD//Hw7YH1g//17MjU2ebh5+vy7fLq+YyA7fHn7/Ps6//r5e3w7ufo7erx84KJ+vr8+/DygoOE9fX0gfyAh4SDh4C6oImIg4iChIH58Pz4+f2CgYOEhIL5/vz9+oH8gOby7fv+6ujy7+Dk+YD//fDzg5SAh4GHg4eEBH19fXylfQh8fX18fH19fIZ9Bnx8fH19fIV9gnyJfQF+jH2FfAR9fHx9hnwFfXx8fXyJfQF8hH2EfIR9gnyLfQl8fX18fH19fXyOfQZ8fX19fHyEfQF8k30BfIp9B3x9fX18fHyZfQF8n30DfH18tn0MfHx9fXx9fX18fX18h30IfHx9fXx9fXyOfYR8gn2FfKh9AXzDfQF8hH0EfH18fIl9BXx9fHx8lH0BfJl9DXx8fH19fX5+fX18fHyEfYJ8hH0BfIh9DXx8fX18fXx9fXx8fH2EfI59gnyJfRN8fHx9fXx9fX18fH18fX19fH18iX0BfId9EHx9fX18fX18fH18fX18fH2EfAV9fXx9fYZ8Bn19fHx9fIR9g3yRfQh8fXx9fH19fZR8BX19fHx9hHwFf399fXyNfQR8fHx9hHyDfYh8AX2EfAF9i3wNfXx8fXx8fX19fHx8fZZ8AX2afIN9kHyCfYR8A318fY98gn2TfIJ9hnwIfX19fHx8fXyPfYZ8hn2FfAN9fH2MfAF9hHyJfQICBACAbnPi4nV4d3p0dH5w2HJ3b3N623p1cXV0cHJ7ceLj3dxzfpuFe+pycuXX5Hns6u565Xp8dnLl23Z04Hh9d3hvddnSa3FqanqHZL5gamotenBlZmBlaWloZ8XIYWjMyszZbm9v2Nd13dfbcd3Zc3Bu2YnU1GxrydffdHRz5Hd23+qA5+hvztLRc3XVdth1b3HYcG90eeNxcslxd3d6dHp4zXJxeHTMcnR05nR3f3uAgYeCf46Dd318eHlxb2xzcHB0eHx9cnd2dXp2d3Z55Xl7d3d7fn53gYDzenp6dnp4d+Zycnd8iHt9dIB6goR6dXd1f33rd3WBfHmKgHl4fHx+etuAcHp7eHl/d3N86OR8fuvren73hID4fn18goCTj32Bj4iDiIyDfH+FkpaFgoGKh4B8fH55fnRreHt3c3Ntc3t4cWtvcXVsbouWbtZy3N9zdXhye3NzcHVxdnFzdXJ0cXBuaHJ4dG93fH1+fXRvdXHTcXVzc9Vx3tlz4HLgeHlycnmAcXNzcnRxc3N4cWzX2210dHyEcNtx2n9xbnKAfn58e3V7hH2Bdnp8dHZ5aW1uenhzbmxxam90cHB1dYKBcHBxcXRxcnF8eW90dG5vd3Bvd3d1cG5xdXR3en90hZBob3FmZ2xybXF1b3BucXJ3dtB4d3p+foaEhnzu5N/gdXbidHKA5JF+b3h0cd7hkX12e3J2eH97gH6AeHt+dXd5d3p6eHvOcW95dXJvd3R7enqM9IR/4Ht1dXV4dG9ubGxwdHLWz3Bvb2xvbnBvdHLUbHBscXF1zGtvamttbMprbXFx0XZ3e3l35nNzcnRydnhxc5l6iImIfnnvzdvb1211xrppw2iAxMppatJzcXjKbofe1thxdGxyZ8rQam3Vxmlu1tHSdXnf4ObW5N15eXh1eHbif356e3uE4Hl5cnHWc3LYcXd8eM7aenN3dHJyenx91Hhzb39wbXNtd3dsbdVqvm3O0tTQztfc0cjQ4nnk49DK2d7d4+PO1Xd659rZ0cnUztXO4NWA0HHc2d5ydnXZcuDVbtna2MnUzcvUzc7F2NfZk9DG0MXP1NbLx9fT1dvo49vh5NrV1tLR09Zt0tRpbM1xdW/Rz8xu422/y3DRbHFwcczN2NRtzWfGxWptgnTCubqtwGtuvs3M2s3NzoLf4N/Y2eHb5+fb6X7Wz8/Sc+HM29/Y5NqA0NDEwcjDv8/Jx8/GxMzKwsPBxs/YycXIztrSy9zRyczS1NPPzsfY39bLdHWNeXeAie/x8uze5+jkytri3Ofm5XNzdd/U5YZxbs7Ka2zHxc1rZsByy8LDY2Zqa2pr0nBwdnZz59/n5+p56eza4eDl5nrU2NVz2dJubttwbtnZbtiAc3Xl4nR3d3l0cn1x2XF3cnJ54Hl0cnh3cW99dOLj39t0g7aPeuN1c+fY5Xfg3uB03nN6c3Tk3XZz4Ht/eYJ5f+3neoZ/gJOqfux6gohUn4N8eXd2fX58f+/1f3/7+fT/g4KC+/SF/vvyh/z8iYKA9JLz9Hp77vPyfXx/8nt57PKA7vF56OrkfYHugeuBenvoent8fvF6e+B6hICBfISB4Xl4fnnXc3R02m9yeXV6eX56eomDdXt7en53dXF4dnp6f4GBd31+e3x5fH5+9X2AgH6FhIR5hoL2e3t8eXp8eeZ1dHqAj3t9e4B7gYJ6dHNzfHnldHR9eneFfnd5fYB/feKAc398fH1/enV97uh8e/Ptenv2fXvzenh5fnuMg3Z3hX55e314cnR4gYN3eHV/gH19fYJ8g35yfYB7dnt2fYF+dm5zeH92d5+je+x+8vN/h4eBhoJ/f4J+g36AgoGDfXt6d32BfHd9hIKDgHx3eXjuf4B/f/KA+vaB/oP/hIR9fYCAe3x7fH99fHyCfHfx832EfoKNeO147Yd3eHmCf3x7e3Z6g4B+eH1/eXyBb3R5gX97dXh7e32Dgn+CgJqYd3p/f4F+fH2Ig3qAgn6Ag313fn18eHV1eHp4e314ha9wd3pwc3V7eHyCenp4d3V6d9F2dHR5dX5+fXfj29XadnTlcnSA5pSAeXx2c9vkjX9yfXd1d3l1eHh4cXJ3cXV4eHx9f37RcXF9d3R1enN5dnWD6YB84XV2dHR6d3FzcXR3enbg2nZzc3FzcnR0dHfecXNyd3Z833Z5cnR2c9x0dXV32Xx7fHt46XNycnRwdHNvcY12gYGBd3bpweLo6naA3dx54XWA3t5xctVzcnHznYTc1N12eHF2c93jbm/a13Nz3ePbeX/o4ujh5d56eXNxdHDZdXhxdXZ623Z0cm/VcnLad3V7fsvPdGxycG5ubXNzy3BubHpvbnFudnpvb99y1nXe4OXi2eLp4Nja737v8dvT2uDi4+TY33l75eLh2dfX1N/U3tSAzWnMzcxrbm3NbdXTb9vf39Pf29nh2+HX5+vnlefc49nd3+HYytnQ2Njt4tvh3dbc0c/S1Ndu09RpbdFzc23T0tNt22zGzG7VbXNzdtXQ4N1x3HDc3nN2kIvf393R23p92+jl6d3c14/l3N7W1dna5eHe5nnX0dTbdOHT3Nrd5eGA2NXV09nV2Nza3eTe2drb1dPT19zh3Nra3ubh3+bm2Nbe3+DX2sfY19HJbm5/b253fdjb29nS3t/X0Nfd2+Lb23Bxc9/U44V3debfdnff4OR5duF95+fmdHh3eHZz43R0eHp25uHi5N948O7g7Ovv7n/d4uN65OZ3d+dzc97cduGAgoX+/4aHho2GhJKB94WKgYOP/42JhYqIgICPhPz/+/KBkKmXiPuDgf7w/YT3//6A94CHgID8+ICB9IiKhoyDiP37hI6DhpqvgfeBi5CD04+Eg4GChIODhvn9goP++vH8fnx+8u2B9/b0hvr9iYJ/95jz+YCA9vr5gYKB+oOB/P+A+P+A9Pb9iIr8i/6KgYP7gIKEivyCguqDi4mKho6N9IOBioXug4OE+YOGjYqQkZaSjp2UhIqKiY6GhICGh4aKjZKQg4uKiYeDh4eG/4OHhIaMjoyBi4X4hIaGhIaJh/6Cg4qRo4qQipGMkZKGgoCBioj/gIGJh4KSi4SHjo2NjP2AgIqJiIqKhIOI9vmEhP34gYH/h4P/g4CBhoGXkoSGnJWNlZqQiYyTnqKQk5CamZKRkZOMlIyEj5KLhoyGjJGPh4CGiIuBgbjFhfyF/f6Fj4+IjIeGh46Ii4mNj46Si4iJho6UjYiQmZaWkYqDiIP7g4iFgvJ8+PeA/H/5hYaCgoaAgYWGhoeGhYWLhYH//ISNiI2UgP+B/pCCgISPioqKiIWJlZKRiY+Sio6UhIuMlpGIgoSIhYiQioyQk72+jZCRkpKUkZKempCUlI+Slo2Lk5STjo6NkJOSlJeMosSGjJCBg4WMh4mQhoiFhIKGg+eAgIOIho+NjYP9+fT9iIT+hIOA/q6NhYqFgPT7oY6CjIWGipCJjo2MhYeKgoeKjI2Pj47yg4KQiYSEioCFg4OT/4+I/ISFgYGIhYGBgYWHkYf7+oOCgoCDgoOAg4X4gYWDh4iO/YSKg4WFgvmCg4SF8IiHi4mE/4OEhIODhIOAgpiDkpWVjIb/wvH4+YKQ+e2C9oKA+/2Bgv2Eg4b8q5L+8/qGioCJgP/9gIP+84GB9/nwhob99vbu8/CEh4GChoH/jIyJiIuS94qHg4D0goX6hYiTk+7+jIaLi4iEjI+O84yGg5WDgIWAjI2Agf+A74X3+frz7/T969zl9If8/evg7/Dx8+/i54OD9e/w4ubt6/Tn/viA8YD2+fiEh4T7hP37gfz7/u/59e/16/Po9/7+tPfq8OLq6vTm3Onk5t/68+nt8ezs4+zs8POA+PqBhviJjob//veE/4Pl6oP4gYuHiffw/viA/ID29oKDlZD27+ze64OG5PDw8eTl5Zny6vDn5e7x9/Pt9ILp5+bugfjp9vLv/PeA8PLi4+7p5vXy7PPt5ezr4eDf4OXt5N7g6/Lw7Pz06ev09vz0++n5/vPrgoGXgIGKkfT5/PPo9/Dq5ev17/79/4GAgv7z/pSCgvnygoT29v6HgfaJ/fz5gIWIiIaB/oGAhIaB+PT0+PSA+vvu/Pr9/ofs9fqE9/iDhPyCgPr8g/wEfX18fIh9AXyFfQF8iX2EfBF9fX59fXx9fXx8fH18fHx9fIR9BXx8fX18hn2CfId9BXx9fX1/in0EfHx9fYR8Hn19fXx8fXx8fH18fH19fXx9fHx9fXx8fH19fXx9fYR8DX18fHx9fXx9fH19fXyEfQR8fX18h30BfIR9BXx9fX18o30BfIp9AXyHfQF8kn0BfI19AXyJfQx8fH19fHx9fXx9fXy0fQR8fXx8oX0BfIR9CHx9fHx9fH18kH2CfIZ9A3x9fNd9AXyJfYR8Bn19fH19fIZ9gnyXfQF8jH0EfH19fI19gnyKfQF8hn0BfIZ9AXyEfQF8hX0BfJB9EXx9fHx8fX18fH18fXx8fX18hH0Ffn18fHyFfQ18fH19fHx9fXx8fH19hnyGfQF8hn0BfIR9BHx9fXyEfYJ8iX0BfIx9BHx9fH2LfAF9i3yCfYx8DH18fHx9fX18fXx8fY58AX2ZfBN9fHx9fXx9fX18fHx9fH18fH18hH2EfAV9fH18fIR9hXyCfYd8AX2LfAF9hHwBfbF8h32PfBd9fX18fHx9fX18fH19fHx8fX18fXx8fIZ9AXyFfYV8AX2HfBB9fHx8fXx8fX18fX18fH18AgIEAIDcbm9zbdJrb3Z7dmjVcnRtbnNwe0Jy2m/N03lClHR3g3LabXN4c9bU1NBuc3Bv3G9vb3R2dnpucuDbct5ycnR2c25rdW1sc3Z3bGmOd2XHwmtmaGd2bstwb2xvbm7QyszIymtt0JxvceXk29rZeeDYcm7NwdV13thtdNRyb253doB6d3FybXB5cGxzfW5sdn1wbXJ0dXZ7gIF52Ol4en2B732Egeh7goKDeHt6e3h2eHl0ddhtbHF3eXB4e3hxfnJvf3Z0dXNwb3dyc3NwfYB8cN17e+t2d32CjoZzg359e3t5e3lz2dLUc4aReHuFc4F/fHh+fn94fYWMgul0gXZwb4B+dX12dnd2eHVzfIKBe3qAenx6f370fIJ+goiB8e5994KEh4SJkI9hJYyFhn99iIWEfIOBj3t7dW9rdNJwb3Z0dHRwbm5ydHlxc3hycG9ycXN0d3p0dX98fHlwkIl51Gpvc3R4dnRwd3p0dHNvfH98b+PtdHzX4nvgc3rkdNfXcoB1dHJtaXBuc3V1cnJzb5lwbm51c25rcNLYbGxzeG57g3x8eXt1eHx2dXd2dnt7cW5vyNV0c3Rscm9xcnZ9c2tyb3Bub2dwb3BsZW5vdHV2dXB4d3p7rIl7hYR7fYGBf3Z40mtucnVxcdNucnx/dnh6enWqgXt/gXp5dXqCeX9/eoB35XF5c3pxc3V2d3d7fnp0fHl4f3R8enFwcnZ5cnRwb3J2fHvpfnnfenbld9zVc29/c8nP13Rud25xa3XadXJz09LU1XB64eF6dHbpfHyIfHPadNh1c23Ob25s1nLPa3R7dHrl4Hd4eujud3l37Hd3d3pvhIB1cHBxcXF4eHd0dIDfgn5w1d7Z1XaHc2dqZWVpZsRhYsBla2lxxL7HztR11W90bMZxbG1udW51cG9zdXh33nd5fHdyeHNwcnZ2cYaFcnBwZm52cmt2bG1scXeMx8XO1c97deHY3dXZ3tHP189w199ycd5zhOjZ18nQ197c3NTNeXdwcnNubuPJ0NDKzIDZ1el7zs/Sz9bM1NrW0dHPyNLCw7i+wc/HxNLTy8G/wM7YwtDOz8rUzczc4dnietzj1MvY1mttb3FxcXuJb29oZ2drZ8fIv8tpcWtsa8zRxsTLy8PFwcW9xMjDxs3Q0N9y3dfb49rY2NLX5Nrd4d7g4evd3t7X18rJ0crZ18zY04DX1dDaxtfPzM3KytHE08rU38nNys/W19vf33aYh23P0HFvZmdoym9raW5xct3Sb3V6deFyeufg6NXa4Nvodd7o2W3Py9LJxM/X4dpyc3JszMhnvsNlYrtmashncW9tcW912t3YeOxzdnXj5nfl6Xfh2d5ucNjXeW1vb2/YbHXKz4DlcnZ7c990dn+Dg3XkfIV5dXp6ilZ65nfX1ndNi3Z6gXXldHt5dOje5950eXVz33Z0dHd4eIdzc+Lsd+l6eXp/fXt7hHt4hYSGfHezkHXm54J6e3uKfe6AgX6AfoD59vb194GB89KCf/n48/b3h/35hYDv5fSA9vd9f+p7eXZ7eYB+fXt7eXiCfHh+hnt2gYh+e358fX+DhYmA4PB+fn9+736Jhux6gn6AeXt8f318fX58feV0dXqAgHd9f394g3hzgXt+f355d4F/fXp7gIeFffWEhPx/goKEkJGVf4CCgn9/gX556+blfouLfICFcoB+enh5eXlzdYCGfuh4f3d3c4B9eHx4enl5e3l3eH5/fHt9eXx6e3ztd3t4e3135d1z4XR0enh7gH+ERYB6e3Z2fn6AeoCDkIGAfXh3g++Ae317enhycXR4fYJ9fX16dnV6e314fH95e4iAg392ppeB53h7eX59fXx2fX96dXZxgYSBde7zfoTq9oP5gIf7f/HrfYB9fn16d319g4OEfn6Ag6yAeHqBf3l1euzseXd5f42Fi4SCf4B4e358enx+fYOHfXl63ud+fH94fXp8fYCCe3l+fn56fHV8fHp5dHZ4e3t6e3Z5eHl5qoV3gIB3eoKBfnl533B2e316eeJydYCDeX19fHiqgHl9fnh4cXV9eHt7eYBz4HJ3c3t0eHh9enl+goB5e3Z1eXZ7eHVzeHp/foF6e3x8fn7xfXrpfHvoefHreHeFe+Do7H55fnt6dn3pent46N/i43V75eV1cXTnd3mBd27Xb9pycW/acnFy3nbddXl9cnfj3XN2ddvhcnJt3XF1dXlugH5zcnR2dnJ6d3VxcoDggIB23+Lj4oKYgnV5dnV6deNzduZ5eXR82tni6Ox86nl5dNt9dHFzdG5zcW9vcnJw029xc21ucG1qbG1ran+FbmhvZ2xxbGdtZGlra3Sp0crP09F0dd7c3dfd393X29hz5N9xb9xxgeLV0MPKytPX2tLWfX15dXRwcOTJ09LNzoDc0u9709PY1tzR3d/d3OHZ0eHa3NLS1u/n3+bl5eLd3eXnz93f3dLd2dji5Nziddrf0M7R1mtoa2ttbneBbm9oaWdradbU0dhveXZ2dN3e1djm5Nzi3d/W3uHZ2t7f3uZ04dve5Nnd3tvb5N3e4+Hi4ufi3djZ3NbY4Nni3dHe1YDe2tnh0drZ2NbV2t/W397h7Nrb19bf5uXr5Xqqk3be2HNzcGxq0G9sbG9ua8/Pa29ybdhsceDh3tPc29fgdePr7njm4NrQz9rh5up8fX975up44OJzc956d95vd3V0dHFz3+HYeOt1dHbl63js8nzv5+t4eOvrgHh6d3bmdZfc4ID+gISLgvyAhJCRkYD5iZKDhImEnoeZ/oLu7oeN+IaKlYP+gYaHhP/y+PSAhYOC/ISEgYeJhpSAgfn7gfqDgYOHhYCAiYSEj42Th4HWpYL+/YyFgoOTifuHhoOFhIT58vX39oKC+c+Cgvv69PTzh/73h4X37PuF+/+Chv2FgYCIhYCKiIOGgYSOhYCEkoSAjZSGgYiGhoWKjJCH5/iCg4aI9YWMifeDiYeKgoKCh4SGi4uHiP6Bg4mSkYeOkY+Fk4WAkoqMi4uFgo2IhYaEjJKLg/+GhP2Ag4mJnKr0k4iLjYmLj4qE/Pf5iZ2fiI6WgYuKhIWEhIWBgIyUivuEi4OCgoCMh4+IiYWHioeDh4qKhIWFgYWAhIX4gYWCiIqG/vWA+omJko+Unp3ogaWbmpKPnJqbkJaVpJGRioSAjP6LhYmHioqDgoOHiZOHiYqDgoKJjIyGiY+HipiOlJCFs6mO/YaKiI6LjYuHkJGKhYaBj46Lgvn/gIPk84T6f4T5gvfyhICKiYiGgYeIj5GRjo2PjrmLhoaNioWChfz/hYCEjNvUo5ORi4+KjJGNjJGTkpaYi4eJ9/qKiYuGjIeNkZOXkIqNj5CPj4qRko2MhoyKj5CRko6TkZKV66WPnJ2VmKKdmY6O/oGDi4yKiv2Ah4+Vi42Mj4rOlIyQkImHgISMh4uLhoCC/IGHgIaAgYSFhIKIj4uDioODiYaPi4WGi4yRjo6IhYWEhoX7hoL1g4LyhP36hYGRiff9/4qEjIeHgYn+iIeB+PPz+YGJ+P2FgIP/iYyUjIP+hf+JiIL4hoOD/In6gomQg4X/+4ODhPv+goSB+IOJiI6Ak4+DhIaGh4eQi4iBhID4kY6B9f/7+oykjYCEgoSKg/2ChP+FiYOJ9Ovz+f6G+4SIgPOKhIGFiIKKh4OGiYiH/oaFi4SFh4SBhIeFhJ+gh4WKgIaSjYSNgoKEiY2l8+z5/vWJiP/6/fX3/fbv8euA+PmBgf2Cl/3t6ODk6/Tw8enlh4yGhYWAgP/f6+fa3IDm3/qD4OHn5ufb5+zm6e3h2+fe38/T1/Xn6/fu8fDm6Pb64ejv8uXv5uz4+vD3hvL49uz494GChIyPj5qpjo2FgoOGhPj18fiAj4OAgvn67u719/Dy7vTs8/Lu8vb28fyA+/T0++7y8+ft+vP1+vb8+fz29fD3+vDv8+r8+eb474D4+Pb76ffv6+fp6+ze6efp+t/e2d3l6e/7+IXTsoT59oSEgICA8oSCgoaEgvv8g4mIgPyAgfz58Ozr7e76gvb//4D58fHn4O/3/PqEhYaB9/uB8veAgPKKh/uAiYiEiIKG+/3whP+AgID3+IH7/oT69v2Bgfr5ioCEgIH+gar3/QF8hH0BfIZ9AXyHfQh/fXx9fHx9f4V9AXyEfYR8hH0BfIl9BHx8fXySfYJ8hn0BfIZ9hXwGfX18fX19hXwOfXx8fX18fHx9fHx9fXyefYJ8hH0FfH19fXyOfQF8nX0EfH19fIZ9AX6JfYN8k30BfJp9AXyGfQR8fH18h30CfoCSfQF8on0BfJJ9Dnx8fX18fH18fX18fXx8mH2CfIR9gn6SfYJ8rn0BfIZ9AXyYfQF8on0KfH19fH19fH18fIR9g3yHfQR8fX19hHwIfX18fH19fXyFfQ18fXx9fX18fX19fH18hX0LfHx9fX18fH19fXySfQR8fX19hHyJfQR8fX18hH2FfAZ9fH19fXyNfQF8nX2FfIJ9inwIfXx8fX18fX2LfId9iXwBfap8AX2GfI99hHyFfZN8AX25fIR9gnyFfQF8hn2CfIR9A3x9fYh8BX18fHx9iXyEfQt8fH18fH19fH19fId9FXx8fH18fX19fHx9fHx9fHx8fX18fIV9BXx9fXx8AgIEAIBubtNqbmvCyGVu0WrJ283R23l5d+J14nHccnZRTXt8eOZz2M7O4HffcN7b4G/YcOTedHx44XN1dnNzcnF0dnZ1eXnnb+pz7OPXcHeEdnFwa2jMaG5wcHFtydlr1dXX23l0eHRwd9JyeHh0b3B+dObb4Ojm3+PhdoGAd3hz4eR23YB1dH5ucn12bnF2c3Fud3Z1d3FxdHVxdHx563h66njf63d5d3t3d3iAfXt2eXp9fX95cXp0bXFsiW9pa2ducnmhhtdwcnHcc3Z/cXFwcXZw1HV1c3J033V2zniAdnl1cXZ7dm1xb2hybmpra3JzfHqA3W95e3iFiot57X96gXJ2e4B2eHJvb3R1bnNzdXt3cHVwc3B6e3p7d3p4eXJ3e359hYmIgYSKjp6NhouGhIGGhIWBfnd5gHx1dHBwbmpwfHtvcHHSd3N0enh2fHN6e3x8dnV9eX15hXl3gXx8eHvaonlvdXF1dXV3fIGVpXp3eHR1eXd1dtzlcnl7r4l8z3d4doB7enR0enVwenZ6fXh6fnt1c29zd3VtbmxvcnZydHN1dXd4c3Z3dnl0dHRze3l9dmtzdnx3bGpqenFyaWt2a3B2cnR7fnVoa3Jwbm5ubWltam9wcW9ub3bSdXV+fn57f3t8c2xybWlwc3RzeH97d2pvdHN0eXd0gGxtbnRydXBzcoByeHRwc3RzfHN0enZydnJ2dG1ubnB4e352anZ7b2x5dHZ0c355m3VteHhycmxubnFzcG5ny31paWdvwNFtbXDVw8jUdNza0uh84s3c3nR0cXR2cnJyeHNyfXtwc3PSm4Fydt3abnBzdW13eHfg5nh4dX9xc3N0c3Fyc+GEh3drbIBrZWhnZcjDaGp0aGPCyM/Tas3Rb3RscM/WbNDKccTOeMrMcnnRcW9sbt13hIKrgXt3e3LS1XV5cdJ3dHVueoCCi3TMzGlobn5r0MhobW19wnJszXPa0nHe387abnHXbXV3e33c0Mvd5t/S19fDxMfJusfGxr/Qc8vO03PWyMvGxTXBenvmy87EyOLNxsbHz8nD2uC7z8DJ1jWMzbDB0HpvwMzL0M3SzcbNcNRu3c/VcNzb1MbXbYRrgGLBa8JnbmduuGVnZsfKx8vFymzU0NPFv8rTbGvLyWxs2M/Mysxx1NTW02/dzNBxcdrh2NTcbm2JbdPOzMpqbW1rcW1ybGtsaWjJzMvI0NFuc3Lg5t/p5njleuTj5trX2d7P29XM19XQbGtras1sy8bMcnV3dnV4eHN0eul2eeXlR9XW1tzgdHV4i3px3HF01thwb9jSbdFqbWtsaXBva3Zsy9TV1X531th133J2d3Rw4s7XpZfgcuN4fHNzdnd83nd0cW/Xz9dogHl66XR5dtrnkYDded7m3uLmfH974XTlctxwdHdgeXp46XTl3d7vgfR98vX4fPN9+fF8gn7tdnZ1dHRxcnZ4eXV9ffF673v57+Z7gIqCfH97eup5gX5+f3rr+Xz48vTxhX1/fX6F63t/g358eoeA7+ns8vr19PGBhoV9gHn08nvogHx6hnp7h4B5foB8fXeBgIGDfXt7fXp8goD7fn/0fOTzenx9gnt7fIh9enZ5en5/g4F6gn9/gn+ZgXt8fIGBhbyI7Hp4e/F9g4uAfn6AhX/ve3x9fnrneX3cgIl/f316f4iCeH5+eYKBe3l7en6ChYPuen59eYGCiHz6g36Ce3+BgH5+fX59goR9gn1+gn96fHh6dnl5d3h2eXl6dHZ3eHd5fHp4eXp7hnx7ent5eH18fnx8eX6Bfnp5eXp7enuIhn18e+h8fXh8fn1/eX59f355en99fnuBfXiCfn99f9yjfnp5e3p4fYGDiJmifoF+fHyBgH157/V7gY3MmIrrhIWFIoeGgX+AfHd9eX9/foGEg35/fIOEgHp4dnt8fX5/f39+gH2Ee4B8eHp6eX9+g3p1fIGFf3d2dYF5e3h3e3N7f3uAgIeAdHV7fHt5e3t3eHd8enh1c3R31HV1e3x7eXp5fHRweHZyeXx7e4CIhoF2dXt8fH19eYp2dXV6dnpxdnR2end0dHh6gnl6gH54gXx9fXl6eXl+goJ+dXuBfnaAfHp5eISFs4B+eX6AeXZydXN2enVycN+Ec3N0fNjpd3l37uHY5n3w6On3fu7j5+55enZ3eXh1dXt5eYGAdXd22K+KeHvr6XR2eHt0fnl73+J4eXaDcXN1d3VycHHffYl0cHR2dnh4ee/md32DeHPe4Oboct7idnt3duPod+TleuPmgOPid3zkd4B0dHLieYWE2Ix4dHVrzdBqcWrEbmxraG97f4ps0dFpaG+GbNHbcnZ1nuBzbtp36+B04uLa4nJy3XF1cXuD08rD097X0NnZytDS183b2tzO33ve4uJ77dvg3NTTf33y1N/P2e7b19ba5Off7PPb9uDl9E2y7tzd54x/2t7e2tnb2IDO0HDWb9vMz27Z2dTO2W9ta2xuas1tyWpybnLHaW5v1trR19bfdubm5dbY3+h1dd/bcnDY1tXZ1nfk49/UcOHS1XJv293Z3ONxcqZy2NTO0mxzcG9zcnNxbnFvb9fW19ba025zceDk2uPfct92293g3+Hi6Nfj2dPk3uB0c3Ru0Fpt0czQc3Z6eXJ2dm9zdeFydN3a0dHT4uF3eHeLfXbhd3/j8Hh46+d26HV0dHFwdHNve3HW2drYfHrd2nXjdnd7d3De0N6mm+V3631/eHp5fIHvgXt7d+nj6neAgoT+gYaA7fmOiPCB7fz09/mJi4j+g/yA+YCFtJyHiYT9gu/o6P+F/YD/9v6A+IH//IOLif6ChYWFhoCChIWEg4iG/4H/gP/69IOSo42KioiJ/YCHiYmIgvD8gP/4+fiMh4WBgor0goiNiYeFkoj++PX4+/Xz8YOOjIWFgP7/gvlJhoWQg4eUi4SKjouMh4+Njo+Kh4aIgYSMh/+AhP2B5viAgIGIgIGCkIqHgYGIjYyMioKOioKKh6SLh4eCi4ySr2z+goKC+4KJk4SHgI6H/YWDhYWD/oOE5YOPhYaIhYiWkYOFhoSNi4aBhIiLjIyN/oOChYCPi5SF+4uFi4OEjYiLh4SHiYyFiomLkY6FioKGgIeFhYeBhYaHgIaGiIeOkoyKjI6QoZWUl5eVlZyamZWRjJSZlo6Lh4WEgYONj4SFgvSHiIaMjo6Qi4+PgJGRi4ySjZCOmI+JlZKUkJHLqpWPjo6Pj5SYmp+zvJWSjYuKkYuHgvv+gYSIypuJ7oOFhYuGhYaKiIWNjJSXkZOWk42Lh5CRjYiEhouPjo2QkpaRkI6Lj4yMj4yLiYqTkZeMhI2RmI6Eg4GRhouJiI6FjpGOlpmdlYeKjI6MjYqKgIaIhouMjIWHiY36i46YnJuWmJmWj4aOiYaKj46NkpqZkoaIjo2Ok5ORoYiIiI2KioKCg4OHhoGAhoWPg4WNioOKhIiGgoKDhYqSlIuBiZGJgpCMiYiGk5O9i4eOjoaHhIOFiYiGhoL8loWEgozy/YOEg/7k5++F+vLs/oT74/L9gIODg4iJh4eIjoqIlZOFiIr3yqGGh/7/gIGDi4CKiIv5/IuLhZiCgYOGhoKCgv6ToIqAgYCAgYGA/fKAho2CgPL4/P2A9vqDh4SD+vyB+/iH+PuN9fuDivqCgYCA/omYlv+jioiMhPP+g4iB8YaDhIGLl5eYiPz8gYGMq4T9/YWIgISY9YeA84T59IL/+e75gYP9gIyHk5n/8uz6/fjs7fPe6Onv3u/r7t3vhezv8oH14OTd1tiFhfrf5dfe++TY3eHp6uDr9dr63+v7gP7z5Ob0kIXq+ff68Pj/7O6C+IH/8PCA+/z+9P+CgoODhoL5gveEi4eI8YCCgfr47vPv8YL4gPP67ev1/oCD/fKDgvzz9Pr6i//+9u+B/OvwgoD59vX3/oGC04r++fT2gYiGgoeFiIWChYKC+/j79Pz3gISD/f/0/PiB94T4+vrz9/n/8P318v/++ICBg4D2gPTu9YOIiYmFioeAg4b+gYL2+ufn6Pf5gYKBlIWA/YSP+PuAgf37M4H/g4WGhoWKiYWRhfn8//mOifn3hf6DhYWFgv7p8MSz+YL9h4uDg4GAi/uIgoOB+vX8gQx9fXx9fX18fH59fH2FfBF9fX18fXx9fH19fn59fX18fYR8D318fXx8fH18fXx8fX19fI19B3x9fH18fHyIfQF8hn0DfHx9hHyGfQF8iH2IfIZ9BHx8fXyZfQd8fX18fXx8oH0Gfnx9fX18iX0BfIV9BHx9fXyXfQF8iH0BfMh9AXyxfYJ8hn0BfNp9AXzcfQF8hX0FfHx9fX2EfAF9hHwBfYR8kH0BfIR9gnyIfYJ8jH0BfIp9gnyFfYR8A318fIR9Dnx8fXx8fXx8fXx8fX18hH0BfIl9Bnx8fX19fIl9gnyFfYJ8hH0IfH19fH18fH2EfAN9fXyFfZN8BX18fHx9hnyCfZR8An99hHyCfYl8B318fXx8fH2FfIZ9A3x9fIR9BHx9fX2GfAF9h3wGfX18fH19hXwBfYR8Bn18fHx9fYV8hH2EfIx9hnyDfYV8A318fY58hH0FfH18fHyKfQN8fX2HfIZ9C3x9fXx8fX18fH18in2EfAZ9fXx8fXyFfQh8fHx9fXx9fId9AXyEfQR8fHx9AgIEAIDMbczQ0Gxvam5yb9Nsd3J4cttxdHZ6dXBxdXJ3fn57eXl4nYB5duXk5HrUx9Dc2djWcuFydOFzd3Tq6Ht5edl2dXZtcHVxd3h+dXJ12dl7eXJweHFzcm1scG7XeXxzcYV6bW9vc4B2dYJ7envi1HR0dnl+7vD5gfPx7Ozv8O7n2YBzeXNudHZue3mIsXh7eYh2e210eXl1cHV3eXR5e3ju5Xrt5uZ2591zcnV2dnR4bnBvcnJ0dWxsbmxtbWdlcGtniWV3c29y1W91eHh1c3d2ct1wdXuJlnxy5HR2eXh3d3Z0eXR2c3R2bnpwdHN4fN6FgYXvfoaElKyEhYCEfnp3dIB75eB2d3V3b3d9enJzcW1seJSJdXx4fXeKb27YdnmGgIB5foaIgYGKloeAfXp6eHV5dXJ4e3p8f3d/enl4eXh2cdjWc3p9dHJ6jn97hYCEd3p5e4CAjoeGhIeBfnvpfoKFe4CGgX93d3Ztc3FydHRtjnpvanFsxdTXaWxzcnF1boByeHR7enR4dXh+eH5zgXaJqn1+fIF8b3F2dnt+eG50dm9xdHhwfGxzenJvdXl3cm91dHR3eHZ4dW1xen6HcnBteXx4dGtqxcdmbndsbdF2cXd2eXt6hYh+gIN/f3h5g4V+c4F2cm1weHFvamltxcl3dXFvcHx2d3ty2Xd0dG15n4B1gHTVeHx5d3Z1eHp5eIB+cnRvbm11cnBya3N4a2tud25ub2Nbf3N2d3h8d4N5b21uZ2hoZGhzbWZtaGZqbHFsy3h21dZ06HmM5+DvfXZzdnt6dnNwzstqwXHLam9xbHZy1G9xc3R5cHB+V4Lkc3944XRvbnRvb9Bse2xmZ2NlyoBua2twcNN0b9x3dHXg4+Hf1OXPxtzccXp0c9XNamdpbGtmw8G8aHNrzGqDftFxb29ucG25mXTk4dDf1Xd6dXx6dXjeynFvb23XxsKBacvNw2fDxbrBvcTFwszYb8bKbG9paM7R1dnU3tnFxMvQa7q9xmbBxLrC0d3FyNV1it3Q4IDS0NTd0tPRusDIzMbW1dHJzdbTb8RxfcC5yGbBzMrEy8TPbtHM0W3Ny9Xez8DI1dHczMnU0Nra19bO29jU0Glnz2rCyGlqZ8NmacbFwbzMzcfXbc3Bvrq/xM3Qwmtsy8tsbNDWbMPC2HR1cXTa12tlb2tvbHFwy8zPbmttc4JxcYB11tTlycTXz+PtfXzf4ut4e3p43ujcynd0wsbF2dXV09HVzWrNbm5x1NBv387U3OR4cnF2eOTM2d3b2nne4Ntx1NvP0m7D2XZ0eXZ4enmHQHd7dnF1d2/Zc3JvbdXUztTNzNdzdW1v1NnYydLZ2MjS2c7O37N40nDPa9BrzMjJbIDqfezk73h8d3l7dvF6f3t/eOR2dnV3d3Fvb291dnl1cXJzjX13eert8IDq2+H29PLif/N+fvR6fXvs7n15ftx4fHZzdXh6fH6GeXh97eqBfHV4fXd9fn97fHnuh4d+eo+Eent5dn9zd4Z+f37r33l6eH1/+fT6gvf29PT29/bt5YB4fnl2e315hISQuYOEgY2Bhnl/g4J/fYB+gX+DhX/28Xz49Pd69PF+fYCAf4GEfYJ9fX+AgXV4foGChYF+h398pHOIgH2B8Xx8gYB9foOBffN+hoeQlX917Hp9fX18fXx8f3x9fIODe4V5f3l9geaDg4rveoR/i6qCgn6BeX17eYCA7fmBgoGEfYKHhn1+fnx3gpWKe39+gXuQc3LidXmAe3p4en9+e3yBin97e3h5eHh7e3l8fnp+gnyAfnx+f319eevqfYOFe3h/j4B9h3+CdXh4eYB8i4KBgYN9enbofn6Be36BgYB6eXZzd3Z5e3l5lYd7en573ef1fH2BhIOEfIB9hIKKiIKCfYGEfYF7hHuQu398foSAd3qCgoaKg3uAgXp9foR7hHR7fnd2fH55dHZ8e3x9gH18enV3f4SGd3p6hoqEhXx55el5gYh8eul/en96f357g4N4enx6eHJzenx1b3t3dHJ4f3t6enmA7/COiIKAfoWCgYZ87H98gHyEr4B7hXrbenp3dnd2eXx7fYWBf4F7enyBf36AeoOCe3h3fnd2c2BzgHJ2dXZ8eYF7c3V2bnByb3OAd3V6dnV3fHp54oN96eV36nuD5dzjeXZ2dHl6dnZ239xz1nrecnJ2cXt23XBxcXB0bm58WXzcb3p04HRwc3l1dNp6in96fHl37IB8end5eOJ4dON6dnTk4eHe0+DT0ODneH53dN7fcnN0dnNw2dHUc3p033OLheB2dnRzcm/DmXDc38LVzXBzbHR2cHPcy21vcHDa1NiNdt/l53Xt8t/o4OTZ4OLvddTTbXFra9fZ3NrZ5OLM0dzhd9bf6Xbl3tvg5+3d3eF8mOfb54Dc3N7l4+be0NHZ3Nfh6Ofj5fDrgON+hdbR33nZ39jW3dbbcdvY3HDX0Nzk0MXO1tjf0tTf3OXm3+bs5Ofj23Fw4XLJ1nNzbdRsb97Wz83a3NTicuHR0c/Y1d3g03Fw09FvcN7ecczM3HV2cm7S1mhng2lua2xvz87ZcnJzdH9xcoB23dPdxcbUzdXceHXT3NpzdXNy2N3XzX160c7V3uDi3+Tn4nPZcXFz2tZw3dPX291ycnF0deLL1tfX1XXi4eFy3+DZ33PP4nl3e3d0eHiFVot7d3J1dXDcc3J0cNzd3eTb4uZ+f3d75+3l4+To7uLn8efm9a2A6nntdet26uXje4D7h/r1/YSGgISFgPqDjIaMhPmDg4WJh4CBhYGEjI2JhYSGo4yFg/n3+4fu4un59vXqg/qCgvuBgYH+/YaEivSEiYaChoiGiY2Rh4SH/vuLh4CAiYeMiYaGhoP3i4uFhaCPgIKBgoyCg5KIior/84SDgIKF+/j8g/v7+Pj+/f748oCBiIKAh4qDkZGhy5OWkZ+NlISJjY6GhYiGhIKEh4L69oL8+/6B+vuGiIaJioyQho2Jio6OkYOBhIeHh4WBi4aC47SPiIaH/4KEiIeAhoqIhP6GjJKfro2B/4OIjIyPjoiLlIyJjJaUjJmIkIqOjPaNj5X4gY2Ik7WJh4OJgoeEhICN/f+Fh4KIgouQiYiLi4eFkqmekpmTl4+jhIL7g4aTi42IjZGSjJCVn5GOi4aJi4uNkIyTlY+NkISMi4aHi4qKg/n0hY2RhoaMoZKOmZGWh4mGiI+Nn5STkpOLiIL2ipCUiJCUkpGKh4WDiYWIioeEpo+Fg4iA5/j/goGKiomLgoCEjIqRk4+Oio+WjZKKmI+r05eXmJuUhoiQjpOYk4eOkImMkZaKlYCLj4eEjJGKhYKLiYqMjIeJiYCGj5SchYeJlpqYk4mH+viEjZaIhv6OhI6KkYyOmZmLj5ORlIyOlZmTjJqRjIyPl46NhoaL/v6VkYiGhpGLipGI/4yKi4STwYCQm4z9j4+Jh4aGiIyMi5KOio6IhoePjImOiJCThoaGjYmGhbXqk4SNiY2QkJ2Si4mKgYaFgISSioWJhYGCiIeD8YyH+/OC/oWQ/e37iISCgYuLgoeG9vaE9Y78g4eJg4+I+YOEhoSJgoGVgJP9go6F+oaCgoqFhviInIyGiISD/oCFhICEhPuGgvyFgoH8/Pn46vXr7fr/iZCJhP79g4OEhoOA9O3ugYuF/oKkm/uGiIKAgoDbtID6/OD17IKKgIuJg4X97ISChYX87/GWgfn69oD69eXw6vDt6/P/gefzgYWAgf/6+Pz4//jh4e74hO/z+4D06+ru/f/p5vWDqfzv94Dt7Oz27PPr0tnn5dvw8vPs7fv2iPGGkefi8YTx9Pfx8u75gvjy/IDu7/z/6d3o7+3u6uv27vv19/39+vr9/IOB/obz+4aGgvWChf/27OXv9Ov6gf/y7erz8Pb86YCA9fKAgvv+hO7r/4iMhoT+/oKAo4GCgICE9fj8hIOFhY+BhICJ//b/2dvv5/P6iYbt+PiBg4GA7/fy3YeE4uPl+P37/vf/+oD4goGF/PiC/ezv/P6EgICEhf3h7vX19Yj59/aA+Pzx+oLo/4qJkIuLkJC/mrCPjISHh4L/hIODgPv6+f70+fyLi4KD+vvy7evw9Ons+OTs762H8oT4gf+B/fX0hAV8fXx8fIZ9AXyFfQF8lH0EfHx8fYZ8D319fH19fH19fXx8fX19fI19gnyMfQF8kX2CfIV9BHx8fH2JfJ59CXx8fXx8fH18fJp9AX6EfQF8iX0BfId9AXyVfQV8fX19fI59gnyYfQF8pX2CfJp9AXyYfYN8l30BfrV9gnyFfQF8n32CfIp9AXyJfQF8n30Cfn+bfQx8fX18fH18fX18fHyJfQZ8fH18fXyGfQF8iH0Hfn18fX19fIZ9AXyHfQF8hX0HfH19fH19fYp8hH2CfIZ9C3x8fH19fXx9fX18h30Cfn2FfId9gnyEfQl8fHx9fXx8fH2KfAN9fHyEfYt8BX18fHx9iXyCfZZ8CH18fX18fHx9h3wFfXx8fH2XfAx9fXx9fHx9fX18fX2IfAF9iXwMfX18fH19fHx9fHx8hH2CfIh9g3yIfYl8BX19fHx8hH2EfIJ9inwIfXx9fX18fH2FfIV9hnwFfXx8fH2EfAN9fHyIfQF/h30BfIR9h3yEfY18DH19fH18fXx9fHx8fQICBACAw8TOb9Xa1trc4Ntz4t/j33nc1dnb3G5wzsxvcHBydHZ1cXRydG5wbHNwcnBwa8zWb3V033TdeYOWjH7mc3h7eX5zf3t0dHVwc29/fHh53Hd4cnPVdXd8dXl5eXd4eNh8eXHdd312c3p3fYF233bf4Ozn6nuB9ux37XjmfH13dnGAdHZ1dXtwamxtdnt4dHh9d3J2eXp7d3qIg4eAgXh3e3yCe3jq4nd2dHZ7eXF8cWxkZG5unopyb3BwdniJiHRuz39HcG54eHZ7dX2CfHRybuHf2HV8gIZ6d3F/fnx/fHJ2b3Z1eKV0dnNua3J5bXZ26XJ67fKBf4F7fIKGgYF9gH6AdO18enp9c3V5dnd0cttwcXFyenVxdHx2dnh2fIZ2d3xxeH+FiX+BioiJj4WBfoGGe3x7d3R0dXxydnR7fHZ+8oOIhHiDfnuJd3V5fnmDgIZ0h4eIg4Wflp6OhYSGhX15hXp0gXeAgHp4d5y0f3RvcnVvdXR3fXp1dttvc35ycHOAd357Zs1pcXNonIB1c3lyb3p/enx+cXB0eX59dHR7dHJ2cHR2dHx6d3JzcnN7dnt7gIiEhH+BgHN6foN9enl0e3V2dW/BdHdsb3uDeHl9gX2Oinp/fHx+fX58eXN1eoSGe3Z3d3dxgnh6dnt0f3t5eHhwd1pnamtpd3pzbHdueHWAcG52fXt7eHaEj314fnpxaHJxbnN0b2pvcXF1enl2c357dnZ0dnpxcHZ1e7FwcHl6gHhpbtTLcHJzdnZyc3Jz2XB0dHRzdOF1fYR6fnh7eHXNcY51b2pqamhpaGlxc3Nwb23Pxc5xcm1wdl92cHN4dnt5gHt3e4Bsam1qa2twasuA0tXRyXBy32/Z1HN+7s3j5HF1dW1sd9Ftyc3Fam5sZm5yb8rAw21tb2Vkw8BobWhubHV8irJxeOLk9uPufXh5fnp+eXZxb3VxbL1pbGpreHk+VXBuaGlqz9DNbMzIxdfK0cTNysPIv8FnychnvGvJy2zEx25syX58cdHUzXd44uWA0MK+33jTy9bOxb/C0MxqacvLu8bKydZt19Nvk27Q3tfV7OPb4Nl11uKB7OTg0+bm29jU59vY08nW1tDKyHPZb8/NyNPY3NLLbs3SzcnIytFqbXPXy9jXz8/T1mrTucfBx8K5vL+ggmbM1HRzcm7GxdHQb3SAeHRx4ut64t/m9oKAguzp2cnL4eaQ19Bx4HR7heN5dnPSc3Zy39nn1nPecW7VzttxenRyoIRxcW52eXbeenlxeHV25Hl0deLn5nRx232An3Xe3u+5cXKIR4Z623N7eXJ1dX6FcWxpbGrFxdXOamxtb29rzc1qbsnF0GrHymt+jm1ta85yc8t9b3By09aA3enseuz19fP38u9//Pb174Ls5+3k5Hh429x0dHV1d3d2c3l3eXl2e315f317eOnxfX5/73rpf4aUi3rrdXl6entxf3t6d3h0fHaEgHp65Xh7d3jkfn9/enx8gX99f+uDgHboeX95en58gIV77oDz+P/7/oSG//x/+3/6goF7eneAeXx9f4R6dHh3f4WEgIGDgHt9fHt8d3mDfoN+gHp4enyCfXz08nx9eYKBhHuIfXt4cn1+speBf4KAiYqblYSA9aZeg4KMiH+EfIOIgXt3dO7s6Hl/hYWBfXmGgoGBgXp9e399hLGEgYF6d3uCen1+8HeE9vOGe398fYCBfn17gXuAdOp9fX58eXt/gYF/ffh9f3+BhIJ+f4aDg4KDhY9+e3x5eX6BgXt7gn5/hH53e3+Ee4GAe3p9gIh+gYGCgnmB8oCHhHmDgX2LfXt7gn6CfoWChYWEgn6YkJyKhYGDgnx6gXt2f3qDhHt7fM7Ggnt5fYB7gH2BioWDgfJ/g4yEgICAhI6Le/R7gYN5tI2BfIN8eYGCfX+Bc3V7foKAent/en+Ce3+De4GAfXh6enh/eHt+fYSDf359fXZ9f4SBfX16hH5/fnrZfn12doKJfHp9gYCKhn59fXt7enx6dXVzd3+BdnJ1eHVygXd3cnp1gIB8fn94gGR8eHh3g4J3c3txeHSAcXB2fnt6eXmDjnp3fXh4cnyAfYOFhHx5fnh7e3d0dH17dnVzdHlyc3d2gMJycnx7hoBucN/ZeHl5fH55eXl143R3dHVzdNl0d352eXN2eHfPdJF5dG9ycXN1cnR7fXt4d3Xf0t14dW9yem91cXN1cnh0eXhydn1wc3Vwc3J2cdyA3d7c1HV233Lc03J648jb4nBwdXJyeNtz3N3YdHV3c3h2deDT1ndzenJy2Ndxc3B0cHZ7jbhvct7U4NTdcW5xc3J0cXFsbHRxbMtucnFyfoEzT3d1cW9y3tvbctrS0d3T1s3U0tDc1OB37PB523rh4nnT2nV03IeDdefl3X595+6A3M3G3Hbb0+Dj2dbU391ycdfd0dfd1tlv19VumnHP18vN4NTR0dJtz9V339jUyd3d1tDV39nT08ba19LX0XTfc9nQ0d3e4tPRb9Tc1dfT2N11eXji4ejk19ne4XLdy9fS2NXLx8+yiW3V2nV2dXLR0tjWbHGAdm9r1tVw2NXX33WAd+Tl1cvN489t1NRz43d+guN2eXLNc3h14d/n2XXgdXXi3+h5f3h0p4t1dXJ5e3XVcXVvdHFy2nJxb9re4nR04H+DonDSzurAdXGndJB41nBzdm93dnyGdnNwcnXZ2ufkdnZ3dnl04+Z3eeHh6Xbj6nqatHl7eOl9feaHe3x+6+mA6vP6gfv//Pf/+fKA/f7+9ob19fX4+oWE9PiGg4SGiomJiIuIjYeDhImFiIaEgPP9g4eE/IH+iJCflYj+gIWHh4uBkIuJhIeCiYOSjIeH/IWFgIP7h4eNhouJjYmKjf6OjIP/h5CJio2Ii46B8n/2+f/3/YWK//mA/oH/h4OBgoCAg4aGi5CIgIeFj5WVkpGVkYqPjoyMh4eTjY+IioSAgoGLgoH/9oOEhYyMkYqZjIeBgIyL07aPjY6Qlpi+uI2H/MiAhISSj4iNiZKYj4SAgP7y9oWQlZyNi4aVk4+QkomNi5ORlcmUlJCLgoiThIiD/YCI/P6Jg4SChoiIhIeIi4iAg/+GioWFgoSGhIKGhPmDhIaHk46IjZiTk5WUmKGSkJKKjI2Wlo6MlJCTmZGJi46TiI+NiYaLkJiPkIuOjYCI+YWRkIaTkI2ej4iOlJCWkp24nJSVko2toa+ck46QjoiIkIeDj4mQj4iJjOXfkoiEiIqDjImMko6IiPyBhpGKiIiAjJaTg/+Fio+CuJWPipWLi5mblpeViYeNkpiYjYqSh4eMh4uQi5ORkIiKioeNhIaIi46Pi4qKiYWKj5aSjY6LkY6PkYr1kJCEh5afj42TmJOhn5GTlJKRjY6NiomIjZWVi4aHi4qIm4+OhY+KlpKOj42EjIq0g4SCkZKKgY2Dj4qAhYOMlZGQkJChsZyWm5aPgo2NjZKUkIqIjIaIioaCgYuKh4WChYuChYmIktaFg4+OmpSChv/4iYuLjZCLioqG/YWJh4iDhPmEh5GGjoaMiYfuhKOOh4CFg4SEgISLjYuHhoX+8/6MioaJlqaTjIyQjJOQlZGIkJaChIWBhIGFgveA+v367oOC+ID37oGG/+L1/ICBhYGCiv2E+fv3g4WGgYmIhv7y9ImEjYKC+/iBhoOFgYqQp9qChv7z/vT7hICChYWEgoWAgIuKg/SHjYqKmdOFhY2Jg4GD+vz7g/vv8/3w7+Lu7Obt6PWB/vyA6oLx9oXo8ISB95WRgvj38omI+/2A6tbU7oLt4vL27u7q+PaBgfn/8vr+/v6B+feBs4Dt/ero/Pj18fWA6fWK/vPy5fz36+Xp+e/r6N3v8Ofl4YH5gPPq7Pj6//HtgPb59vPz9vuDh4b/8/706O75/4H95fHv9/Pm5/HLn4L7/oqMi4X2+//7gYiUiYOB//2E+Pf1/oWAhv7/9OHe/K5q5uqA+YCKkfuEhYDugISB9fH97IH7gIHz7P2Fi4aD4KmHhYKJjor8hYSBhoKE+YGBgPb9/oaG/5OZx4T6+/fOhoLqv8eI8oCEiYGJiZCdh4OAgYDz8//5g4KDgYWA+vqChvn5/YD4/oWgtIWFgv+Gh/iSg4iK//0EfHx8fYd8AX2EfAF9hXwEfX18fJR9CHx8fX19fH18hX0BfJJ9AXyEfQF8in0FfH19fXyJfQJ8fYV8CH19fHx9fH18qH2CfJp9A3x9fo19g3ydfQV8fX18fI19AXyLfQF8sX0BfJB9AX6ofQF8in0BfL99AXyufYJ+v32CfIl9AXyGfQF8iX0BfJF9g3yFfQF+lH2FfAh9fXx9fHx9fYR8hn0FfH18fHyHfYN8hX2CfIt9hXyNfQF8hX0DfoB+hX0EfHx8fY18Fn18fH18fXx8fXx8fX18fX19fHx8fX2GfAF9iXyCfYd8Bn18fH19fYl8BH18fH2TfAN9fH2IfAF9h3yDfYh8AX2JfAV9fX18fIR9hHyGfQN8fH2EfIJ9hnwRf358fH18fX19fH19fXx9fX2EfAd9fH19fHx8jH0BfIZ9Cnx9fX18fHx9fXyEfQN8fH6EfQR/fX18jX2EfIZ9Cnx8fX18fHx9fHyGfQR8fX18hH2CfAICBACA1MjM09nW3dtxddHi293d6eR3c+Dm2XJ6f4Z3dXVweY1xcnd/e3p0fn51hoZ0ampqa3lrbG1zeHZ4cnt7d3BxcXZ2eXp5c3BvcXJ0dnR0fHbmfoqKg314fH55fXdxc3J9e3yOi3Z2dXNscXZ6bnp12nh3dnt6f3p85Xh3ee7pdOWAeHfedHl6dnx3fXh4d3d/em9teX5yeH+GfX6BeXh/hn1+c3N3dYJ1d3t2eXZzd3RxdnN1bG5wcHV2cXmOg39+hndva3eUdHyBe3RzfYGCfX13dnuDh4Xg3nl4eHZ9hXx2c3B4b3J1gnF2fH19fHeEfnZ5efKBfoKCgYKEgIOEeXuAhIWPf3x7dXhz2nJ2eHVxcHBxdXJxdm1vbG51znNtbXNycGp4b3l7eXmCiIKEg4aJfX56f3Z2eX+BeW50fXZ/eIN5fXh4fX+Ag32DdoJ7hY17gH2ToYXBqHp5c3Z5feF/hHl4gX+Bg4B1eHpve3d3e3RxdHd4d3ZyeXR2c3NyeXSAd3VqcHVzcW5ycHJvcnJwdnd5enZwbW12end3eXp9fnyBe21xb3CBfYF7eXt2eHx/eoWFiIB/dnt+d3p2e315c3p4b2/UcHFxd36Ji32Gf3qBe3B4eH5+zN5/eXR2fJ2Kint+gYN+f4eOhoCBdHV8bq544X15pzxgfn6tenV7e3WAd3Z0d3h2f32Dgod6dXuBbXRweHtwa2luam1tcHJ0dnV4eXV3fnx2eHxzcHJucmxscXV2eHhydXtzc29z332HfHt9eXh01HJ5fpXecHh2gXl3bnR2b2ptcW93hW5wcXp5bm57c3R8dHV1eWtubnJ0cnJox2VrbW7W0m1ybG5sam2AbnR2caOddXdz1t7a39Xq2+HadHDWcdhyztHV09a7wWpnampnZmVmbXBmf2PDZm5saHJ5amlwcnTieHjf33XddN3g6NfXc9VubtV8asTRaDRYxWZsxcfHxs1taWzNacnGZ7q0ysjGwG5txMXIacprz8vIu7y+1NbRyt1/2d11492AdOPS0Hvb0dVt0czQbnBpxM1lZsXJytPK1bnW2Nzjdd51goF94N1ycuHa3XTi4Nx10NfT3+Hm4unr6ofq4t/X2drV2N/sed7c0dPe19jc0dfk3uCA4svZz87SyMjEyLjEbaJnfGlgt2JsZmZraWxxc3N9eezz9/zx8vN7enrmeN2A0t+Fkdu7vtFw09lsoHTW3+LlctnX4nTXbnLfbtvW1dbX2tVtbH6W0c7HeW/Wzsrdcnd9dXXi4N97e+TleXbR2uF1enZ25ePZ3NXoeHZ5dHZv4G7Yb29qdXJubcxucXBwa29xaszQZ2hpa9dtbG3PwtV21nB2dHB7fHDQcM3O0m2A8vDw8/r4+vuBgen++ffo/fmBgPf+5XuFho5/eXl1eohzdHp+e3t4hIB4iYd4cnFzc4BzdHR4e3x3dnt7d3Byc3Z3e318d3d5dnV2dnV4f3TofI2KhH15fX59g353eXeAgICQh3d5eXt1d3qBd4WB84OCgYGAhIGA9H+AfPXweOiAfH3mfIKDgIeAhIODg4SHgnt3gIB4fIOLgYCDfX1+g36Be3p8fIh9fYF9gYB7gH58f36CdXp9fIOHg4udkIqLj4R7eYWngoWKhH57g4OEe313dXmCiYnu8oB+fHqDioKAfn2Aen59h3h9f3x8gHp/enJ2dul+eX2BgH+CfoKGf36AhoiVhYKBfX187Hx9hYF8fXx8f3t8gHl+dHuA5oB6eIGAfHmAdHt7dXd9gHx9fYGGfHx4fXh8fH6Dfnd8hn+JgYuChoB+hIeFhYSGfYmBh5KLg4CXr4nAqYB9eHl8fud/gHp4f31/fXx4fHx0fHyAgn15fH5/fnt9gX6AfHx9f36AgoJ9goSBfn6CfX56fnt9f39/gX93dHV9fn5/f3+ChISHhHp4cnWDgYJ8fHZ0dHl7d4CAhH59eX6Be399f4F9f4WAeHXjeHh5fYSlu3mBe3d5d3B0dHZ6tLh7d3R1eZKCgnh6enx6eYCYgH6BeHiCfbt/7IF9sWVkg3+yfnl8fHaAeHV0dHFxd3V9fYR5dHuAb3Z0eXt2cnV5d3V0dHJ0dnR3e3d5fnx5fn13d3hzd3Jwdnt/fXt4en91dXV05IOJf3x9enZz0nB4epXZcHZ2f3p4cnR1cXBxdHR9h3F1d3x7dHJ8dnV7c3V2em9xcXR2dnNv1Wxwb3Pd3HF3c3R0cHKAdHh3eaSed3h43OLb4N7w6PXoen3wfOp+6O7o5ejY3XV1d3h4dXNxdnx1im3Xb3Vwa3d7bm9zb3Tgc3bh4HTpeebg8eDdeeV3deWCb9LgcWNx4nJ21tzd3tx1c3XabtnYdNfR6eXi3nl22dvcct1y5+jj1trY7e7n3OKH4+Z47OaAdeng2Xjd1tpx39rgcXNx0dpwb97d3t7V077W19XebctsdHNx0tBsbNLR0W7V19twycrM0trb1dng3YLZ1tLN0dHSztjgc9rXzszd09bY0tnk4OqN6dfj4d/p3Nje4NTZdp13kHhx1Wx6dG9xbGxra2xyb8/T2tvT2NtubnHXcdmA0N2JmOfP0+V44ul72nvp5+XldNvV3HPZcXXkcuDd4uTk5Ol3e5Ww49/Uknnl29nsdoKHenjs5eR6e+XmeHXT4el2eXVz3tnW3NThdHJzcHNu2W/bb3Jtd3l0c993dXh4dXl5derqeHh6ee16d3fZzN576XR5enN/gHbieejv8HyA+vPz9fz09vN/fuXy7+7i9PKBe/b974OMkJ+RiIqIj6GKjJGXlJOJmJSNpKGNg4CChJODhoeLkY+Jho6NioCBgYaIh4yIhISFhIWEiIOKkIL+iJuUkImGiIuLlI6Eh4SQkI+tq4uKjY2DhoqPgY2L/4yIhomHiYCG+YKDgPn+gPyAhIb5hYuRi5OMkYyQj4+RjYODjYqAhY6Xi4qNh4iMkIiKgICFgZCFh4qFi4mEiYqIjo+ViIyPj5WXkJi6sp+epJOHhZvUjZCWkYmFjZSVjY6LhoaOkZD0+YiJiIiVnZGOi4qOh4qNmIiMj4yOjoiQiIKEgv2LhouNi4qLio6PiIqAkZKij4yJgYaF/YSIjYeFhYKFioeJkYmNh4qR/5KJiJCMiIKMgYeKhYeQk46OjpOXi4yHjoaGiY2SjICJlo+XipaJjYiIjpKTlpCYjpyUqLeymZSzzpzjxY+Jg4SGif2NjIaFjo2Oi4yHjI2AjYqPkImDiYyNjIqGjomKhYiJiYeAh4eCh4iIhYSJhIiGioaMkpKWmZOMi4uVmJaVk5SUlZCXk4WGgIGUiZSLiomGg4mKiI+Qk4uLgoyOiI2IiouLi5GLg4H7hIaCipSrto6WkYuSkIWMi4+S1dmNiYWHjKmWmYmNjJCOi5GtkouRh4WQovCD84aB9NvIjY3PjYqNkoyAkY+QkYuPlpacnaOSjJWciZKOl5mQiYeKhoSDhISGiYWKj4qOlJGMlJaMjIyIj4aFiIyUkIqIi5GHg4WC/ZWcj46RjoqD94GJk9P+g4iLlYuKgIaJhICEhoWQmYGFiZKOhIKMhoaKgImKj4GJh4uQjYuE/ICFg4X+/4SKhIaFgoWAg4aGg6ylgIGB8Pry9vD+7/7wgIL4gvyH9fj7+f3j8YOAhYeCgoGCiZCGqIL+hIyIgo2Qg4KFg4X+goP59IH5g/fx/PHxg/uEgf+QgO34gorH+oCF9//99f+Iio3/g/nzgO7l//v37YSC7u34gPOA+fz55OPi9/jw5vKW8PGA/Ppsgf/z64b38fWB/Pf/gYSA8fyAgv38//7s8tj5+vv+gPKBhoiJ+vuEgPr494L7/fmA4OTj7PDx6u738Ivl5+fh5enn6O37gfLz6/D58u705OXw7vyd++v89fH99ez2+u/zi8+Em4uD+YGLh4GFhIGAgoiD8vr5+vr9/oCDhfaB7uj7mrD+2+D0g/X/gsmF9f77/ID38fuC84CC/YD69fr//Pn9gYXR2/n06KCC8+3q/YCOk4CB+PT1hob5+4aE6fj+goeCgfnz7vXu/oSDh4OHgP+C/oGEgI2LgoL5h4eLioWJiYL6+4GAgoL+hICA7t8Q9Yj+goiGgo+OgfeC9vz5hIh8gn2HfAV9fXx8fLp9AXyefQF8iH0LfH19fXx8fXx9fXzTfYJ8m30BfJV9AXyRfQF8wn0BfOV9AXyjfQF+hn0Jfn19fH19fYF+xH0BfIh9AXyEfQF8p30BfIR9gnyQfYl8Bn19fH18fYd8jX0BfIt9CHx9fXx8fXx9hXwPfXx9fXx9fXx8fYCAfH19hXwIfX19fH18fH2GfAh9fXx8fH18fYt8GX18fH18fH18fHx9fHx8fXx8fH19fXx8fX2LfAJ9fIR9DHx8fX18fHx9fHx8fYp8AX2KfAF9jXwBfYx8hn0BfIx9h3wKfX19fH18fHx9fYR8Bn18fH19fYR8Cn18fHx9fH19fH2HfAl9fX59fHx8fX2EfIV9DHx8fH19fHx9fXx8fIR9hnyGfQN8fXyHfQF8iH2CfIR9CXx9fX18fHx9fId9Bnx9fHx8fQICBACA2HHj4tCIedjjdHbZ4OXp3+Lk5+Ph63X0e4F7eHh8fYJ/eXx+d4mAtYhtcHFxbmpoa3FscHN7gXx8eoF7dXxzd3Z8dXB4cXN5fH1xcnh5dn2Bg4R+gX59f3qAeHd+cHR3fHd7wHx2dHl7hXp9fHp3dYGDfoCDgoKDgYB7en6CfXiAfGhydHt4ent2bXV2enh3eXNwdnlzg6R4fYKQhYd7fYF/d3V2dHV6dXh9cG1ucXhwcnNtc3V1b3J6dnx8d3d4eXpxe31+lYt7gHV5enWGe3l+dXRydXR0dnmA8uPldtpxc3Jwb3F5e3t5e4B5eoWDenmJinp5iIN1e358gXt9gIGAf356gX12enx8dnuAfX58foN+fHNvd3h6cHR3dHpxcm9yd3N4dXl0c3V1eoF5eHuHiIN4eHNzeHN1dYJ6dHiBfXx1dHJ1eHx+enx7ent9d3xucnTcc3J7eXt7fnZzdW18enN4fYGBe3l3eXJ2cG50c3dxdnR0dHxzen+Uc3uDkIKAf3lwenJvdXh8jXxveHR8dHpzcX55fHV6e3t7dXV3eoOJi4N9fnR9fXiDen55gHpyfXypdnR7fn2KfHV7fHx3cXZycG5xcnR0cnl3cYuIen9+bXJtc3Z8juV/e29teHZ1cnd0eGpw1W9zeH19fXZ5f3iAend4ejZyeHpzc3h3eW+Ad3J4eXl6enZ5dXqId4KBdnZ8dW5wcHLPb3FudHl1dXBvaW5wdXJxcnNqbW5qaW5ubm1mbm9wbHFranB1cWxxbG1uam5wdHJtdp5xfHt4dXJwdHR6bnR3c3FyiXpwenNyeXFyb9lx4HDVb9V1d3R7cnWOhnhpymfCy316c3V72diAbdtzv9fV3NzZcN3S0tfX5Hhx3OLSc9d2a3Bsami/u8RncWRhYGNmdG9HPXZ8b250cHl1eXF5dHhvdXPWcG9u1t2Sd9jZxMvd0dHLa21xg8jIxs1ux8nGvcrHzszCys/Jv8LBxb/Kw2lrxcZta8TE1dHDy8m6ydbc3G/f3HN5eeKA0tPc19rW0HHAxmlqy8zKzrjM09fX4XiS3NV75eTc0Nbo5Hl93uPj4HJv0tnXb8vUf51wdtHd5XZ/4unWzc7X29ve3trf4djM5tbS1NLf2c7E1HLYeNbHbtrQxsfCv163uGNhv2RiaGRqZ9N0dXHbdHmPVu72gPX1iYN8enZxdtaA1c1uftDVvNDY09Zz2tBwc3l7cnJxcG9w1thwhKFy1dbFbMjH0mpra8rJ089x1trMzNHZ29PQ1dLc4N2Aenp0cn13dXrt9ffreHTodHx4enuQc3ty1mttbW5lacxpY2tsa2xsZ8PBacppaXJpbGpuc25x2tN5dnJxcW/U2G9sa2yA84L+/+ufi/H5gYLv9fn57/D18u7r83rwgIN8d3h7enuAdXl8eId1so53eXt4dXN0cXlwcXV8fnp6eoB8dHpydnh8eHZ5c3d6e3x1eHx/eX9/gX16enZ5eXl9e3mAdXV6fXl9tH55eHyBin2Afnt4d4KBe3l7enx6eXt8d319eneAe3d1eH+AgYF8cYF+goN8fH14e314hKJ6fYKMh4x+gYWCf3t+enyCgoSJgn9+gYV/foB6fX98e3p+foKCfX1/fIB6hYSKmpCAhnyCfnmGfnt+d3d4eXt7en6E/vb+g/2Dg3+BfIGFiIWDgoZ9gYiBfHaFiHZ3gX51eXx8fnp9g4CAgX9/g4N9fYGCfH+Cf35+f4OAgnt3gIOHfoCFgYh+gH5+g4GDfX98fHZ4fn94dXqEhYJ6fn17f3p9fYR9fHyCgoN6e3p6fn5/e39+gIOFeoB2eH3tf3+Gh4mGhH97fXeDgXl9foOEfHx+fnd8dXR8fYF9g399fIF6fISrdX6DiYKAgn97gXl4gH+Bi4J3f3+CfIB5eIB8fnx+gIB+enp/gYeNj4R/gHl+fXyBeXt4fHl2fYDYe3d7fX+EfHh8e3t8en99eXd7fn+AgIaBd5CMfoJ9cHZydHR8puR+fXZzfXl8fnt+f3N34HZ7e4KCgnx/hH6FgX5/gIOFfoR8fX17f3eAfHd7eHl7fXh5dnqEeH19dnZ+eHd2dHbXdHVzen56eXh3dHd2enh5enx1eHl2dHl8eXd1d3d6eXp0dH16eHZ5d3h3cnZ3fHl0gLF4g354eXRydXF2bXR2cW1xg3RwdnJzeXNzc+Jz53fgc+B5e3Z+dHaojn515HLb3MuLe3eA496AcuFxx93c4djeeObe29vf5X514+rfded6dnt8enPf3t13fnVycG9xeXpwZ3+BdnF3dXh0dnN8c3dxdnffcnJ16OmOfufl2tvr5eXgdnd8jeDm5Od75Ork3+jk5ODR2NfX09LW1Nbf3HZ44+iXo+ji7+ng4uHX3eTn5XXc3XR6fOaA3Nrd3eDd3XfQ1XR03+He49Hb4N3e3HSJ081219bOw8bV1W1z09LT1W1rzdHMacrTmulvc9Ta43J84ObXycbR2dLX1dDY2tLI3tPT0s/Z1s7H1nfhfePYduTb1N3Z2nDc3XR01G1scW1ra9Nvb27VamyEYMzWbtXTc3RucG1vdduA39t2h+bo1eTv5el47N93d3p7dHNxcHJ04eF2m8p96Onieebh7Xl2eODf5uJ84One2Nrg5Nrc3Nbd49+GeXdyb3h1c3bk4uDSbm3ZbXV1eXmSd3x54HN2cnRvc951c3R7eXt8d97deOh2dX92eXh3fXh77O6GgYCCf3ry9Xt5eXqA+4L//uuojev2fX3k6e706Orx9Ozo9ID8hI6JgoOKi4mRiIiRiZqAw5yHiouOh4WFgouEhoqRlY6MiZCLhIuDh4qPiYSLg4WMjI6Bg4mKhIqLjYuEh4SGh4iJh4WPg4OLi4mS/JGJh42Sm46Pi4mGg46PiYqNi42Mi4+Lho2RjoqAj5OHi5KTlJeOhZKRlJKMi4mGioiBjrGGh4uZjpmJjI2Lh4SGgYWNio+TioaFio6Li4+HjI+Oi4yVkJmXkpGPjo2GkpSXqaGTmYqSkI2ZkIqMhYOBg4eDgoGE+/f7hP6IioeFgYeQkI2Lio6GipSNhoCUlIGCkI6Ch4yJjoaLkYyAi4uKjYqDg4iJgYaKhYiHhouKh4WDjJCVjI6Wk5qPkpKSlpKTjpGMjYeFjIyCg4eUl5OJjYqGjISJiZaMhYaQkJGKioiIkZGTjpCOjpCTh4+Agof9hoSQjZCQk4uGiYKQjoiNj5WUi4qKjYSKgoCIio2IkY6OjZWLkZrKipaeppmAl5CKj4eFi4qJloyBi4uUi5OMiJWSlZKUlpWXkJKVlp2lppmTj4mNjYqRhoqGjYWBj47tiIaLi4yYj42Uj4uOjJONiIiNjpGMjJSShqagkJeSg4mFiI2WsNWVlIiFjouLjYeLi4KH/ISLj5aVkYqMkoiPiIWIi+aRio6DhouMlY2AmZGXlJWWl5GVkZakkZaXjIyTjIiGgoX7hIWCiY6KiYeEgYWHi4mJi42EiYeEhImOioeFioaIh4mFgo+Nh4SLgoaIhIeIjYqBkviIlZKMioSFhoWNgoqOh4OFm4uBjIWFioCBgf2C/YX7gvqKjI6Th4y3o5CC+IDz+eSeioWO+v2AgvyD4/nw+O/5gPft7u/w/IeB+v3tgPuJgoaIh4H09PmIlIaCgIGGlJLA3Z6kkImRjZSOkouSiI2DiYf9g4SF/P6bhvz85+//+vTygICFoPDt7/eB9vbu4vLw8/Dk7vP06Onq6Oj09IGD9fWn1e3m+fTp7ezg6/n5+oL194KKiP4D9fX5hPuAie7zhIP5+Pb/5vb9/v//iab17o7++vTk6v7/hIv8/v/+goDz9/OB6vSX0YCG7fP8gYz4+una2+jq5evs3Ofm39ry6OLl2u3l2tTogPCE9OqB//Hu9fX5gfz8hob8gYGHgoGA/oOFgviAg62P+f+E+/qOi4ODgIGE8vbvgpP0+Nx57/rs9IL97YGCiYuEhYSChIT9/4acvoj//vKA9PD8gYCD9/D19If1/uzn6/bz5+jp6+/68YuFhoGAiYKBg/3+//KBgPmAiYiKi6WIiof/hYiFiYKF/4eBg4iFiYmC8/CD+oGAi4GEgoKIgYT//5GMiYmFgv3+g4CAgAt8fXx8fH19fHx9fYt8An18jn0Bftp9AX7YfQV8fHx9fPR9AXz/fYV9AX6NfQF8j30Bf6B9AXzMfQd8fXx9fH18in0EfH18fIV9BXx8fXx9hnwBfYZ8B319fHx8fXyGfYN8iX0Cfn+QfQh8fX19fHx9fYh8hH2EfAF9k3wGfX18fH1+jHwGfXx8fX19iHwFfXx8fX2KfAV9fXx8fYd8gn2EfAh9fXx8fH18fIR9BXx8fH19mXwGfXx9fHx9hnwGfXx8fX18hn0OfH19fXx9fX1+fHx9fHyHfQV8fHx9fYd8A318fIp9gnyEfQp8fHx9fHx8fX19hHwBfY58iX2EfAN9fXyJfQF8hn0BfIh9BHx8fXyKfYJ8hn2CfIR9AgIEAIB55drXdqqQePDo6+TV08zS09/Y2dTd2NPQz3bTb3lye3hzcXN0e3J0g3VweHFvbGpxztDVdHJ3eHJzeYCGfHt5d353e3V2c3p3cXt5d3t6fHeBfHrzg358g4d9eH6Cfo18fXt6fHh2dHx/dnt7eXd3eHt9eYR2f3ByendwXId9eIB6dHJ3dnd4dnx2eX55enlzen6Bfn9/fn+BiY6hjHx4dHR4d3p1eHV0c3t13tFvanx1dnVwaHJwa25xcnh1dHd7dXt4e4h/god+e3hze3t8cXR6gnOPioyEo3V0eeV5eXd4dnJ1f3x3cX1zfnd8enx0eIeCf31/fn3dfoSAe3p9doB8eYF4hH142XZ76Hl+f/R/gISHiZCOhoZ+fn59g351d4B/eX94d3V6dXZ2eXd6eXx6enl8e3Z6dHF1cmxuc3l1d4F4eXp6f32AeoOJgXuBf3l4eHp4en17dXp1fHt0fHN4d3x3goF8dXV4cXd0cHd5dWt0dHh2fnp8e4KFgIKDioCTjoyIiY2Ce4J8cnJ8dnuGfHd5gH95eXd5eXp4e3yGgHh8f36Fg5yZgn59gnuCfnqChHeAiol+e4SGe3R4gXV7pqZ+dnB0cG9vbnd1cnF3dH58fnyAfHV3d4F6cndycG50bnF2dXZxcG1wbHB0dX9vdHZzbXdsb4Z3fXh0dXdxb4Bwend6dnV0enx9dnN5eHlzeHdxc3h7bdHXdXigiHp5eW1yfLZ3cnF1zW9nc21sbHJ1dW1vbdlucHR5b3B5b3dzdHV3dXZ5b3J3dXiEdNjaenV1dHVubnNydnBxcnB3dW91cHh3cnVxbnTZcXFtcIVyb21yfmlta2ptbXNzc3VtcoB12tPcdW7XenN0dn92eXLkeORwc9hwbXFyaWRoa2nJZ3GFgXRkbGjFZ2ZkaW5xam+NeHuNem5tc3RvdHHXzW9v2uJvianac97d4djb4nDb17/E2M7Dv2NpY7/Dv2Vsu2Roy2vHzc6vyctuzc9q1NLQ1dHb18zOxcfT1dPP0cTL2IBw49x339bY3M7F2tRy13PTa27Ra8Zs19rW0NZx3djV1dpz4NPW09zf4NzQ09Jy09XgdNnS0MvS29fN0c/S0Nfi3nTbzdnn5+N7deGT6uPT0szk5+TZ19jJwry9vri0wrthaWnD0cO9yr9s0HN2eHHie9V4eHqAeON2d+KEeXfoeYDsdnl30NTbz9Xi2t7Y03Z649rZdXV04Xjg1tbTy9t4eNLWeNPW18zPwsrb2NDUxHHWbnDZ3+By4tvd4Nfg2uGQioB+hYWHee/k5Hh13trW0tvXcnNzcGvIZ2poaWZsa3FwcL2Ca2xw0XFvcW5s19bdbtVzcthzeXzfcXVzcuZ4dYB55t/jer+if/v6+vbr6ens8vz29vH39vDr7oHve4J7gn55d3x9hX17YIJ7g31+enyC7+roe3x6enl3fH6DeHh3en15fHd5eX56dnt8enx5eXV+f3rwgHp1eXx3dnt6eIN8fHx6gH17eIOCeX19eHh6e3t8eH93gXV0fX19hIiDel5+eXh8e3x8e315gIN+gH52fX+BfX+AfX59g4qdin17eXZ7en57f4KAgYaE9+h9mpiEhYaAe4B8dnp8foJ+fHx9en57fYN+g4d/fn56f35/dXiDhXmPjI+KqH9+gvmFhIaAgYCIiYKEiX2HgISFf3d6iIV9fH5+fuF+goF9fYN7gYKHgoyDfuV9gviBgoP+g4KDh4eNjIWDfHx6eH59fYGGhoKGfn+Ag3x9f4F/hYKEg4WEg4F+goB8gYJ6fYCGgH+Ff4KBgYJ/gHqBhYF7fn97eHh8fYKGg3+Df4WFfIR/g4CAgn6HhIJ9fX56fXp5fYR9eH6Agn+HgoWChoqCfH6ChYF+fX2Bf3yCfXh3f3l4gnl0dX18fXt7fXt+fH1/hYB7fn1/iZO0pIKAf4GAgoB/g4V7fomIgH6DiH15gISAiazFjYaAhIB8f3+Bfnl2fnh/hIF8fnp3eXqEg36DfHh8gnyAgYOCgX19eHh6e36Ejn6ChoJ8hn6Aj4GKhHt6fXh0dX55fXx4eH1/f3l2fH59en59dnZ5fHXt7n1/r5CCgYF3fYbjg399geZ8goB4dnR9gIB7d3bkdHZ5f3ZyeXJ2dHN2eHZ3enBzeHd8h3ni4nt6eXl5dXV3d3d0dHV1enl5eXiAeH16eHh4euV2eHZ3jHl3eXuHcXRxcG5udHJxcm52c+HX5X145Hx0eHZ6dHZx5XXldXnqd3h+fnh1dnt65XOAqqGOdX162nBzcHB2d3B0lnp7jHtycHR0cXVz3tJxct7kcoOT4HXi3OXf4ep37Obb3O3o4eN3eXXi3+B2et5xde2Ae97v683k5nfh5Hfr5OHf1+fj2NPR297i49jUy9Hhct/cd93V1NfSy+Dccdtu02ls0WzObNPV1M7Wbdza29fdc+DU3NPb4+Tg2djZddrf6XTf4NjO3N3b09XVz8/S2dBy0sjQ3uDbdXHekN/j19bT6Ojl3tbh4t7Z29/U093bc3aAddzcz8rXx2vScXNwbNZvx21sbXNuzWpr0n9wa9Ry43N2ctHY3tjb4+Hi4tl6fOng5Xx6efOD8evu7OvygoXt9IXv8/br6N3e7O7l4Nl25XN15enpd+vj6urh4tndjIB4c3l6f3Xf1d12duPk3+Dm5Xl5fHZy4XJ1dHVzdnZ6d3oey454d3rrfnt+enft8vN663l56n+Ch+52eXZ46np4gITz6eqB1K+A//v77+Ll5ufr9PL47/X58enyifKBjoeRiYCCiIeTioeMjIOMhoOBgYj9+PqHhYeIhISLj5eMh4aHioWKg4WGjIeFjI2Ki4iKg42Gg/uIg4KEiYKCioqGl4mJiIeLioeFj5KIjIuJh4iHiYyJk4aRgYGOi5Crm5WLgI2KhoqKjo2LjIqOko2Lh4OJjY2GhouJioiRmq+ciYeCgomFh4WIi4aGjYn+74G3rI6QkIqEjoiDhIaLk42PjY+HjoyNlY6TmY6MjYmSkZKHiJSXhZuVmY+jhICD+YSBgYWIhIaWkoqHj4KOiIyKh4CBk5CKiouOjfqNlZGLjpOGgI6OlYuYjITmgIL4g4aG/4SHio+Unp2Tk4uMioqQjo+UmJiRmJCRk5SLkJGSj5KRk4+Rj42NiY2LhYqMgIOGjoqLjoWIjIqPjZCLkZ2QipCSjYuKjIuOlI+Lk42TkYWRiI6Oj4uTk46IiYqHiIiFiI6IgIWKjoyVkpWTm6Gal5mfgKGdnJaYnZWOlpGFhZCMjZWNhYmTkpGOjZCOkI6QkpyYkJKTk6Gp29GXkY6Qj5SNi5WWh4yZm5OMmJuSjI2Uj5nU9Z2VjpKKhouMkI2Iho2Hj5ORipKNhoiLlZGJkYmGiI+JkZaUk4+PhomJjZCbo5CUl5KLmI2PoJGZlZCOlpGPgJCYlZqVkpCbnZyWkJOUk4yPj4eHi46C//2HjMCgkY+Pg4eW85CNjJD+iIqNhISAi42Jg4eE/YSHjZCJiJCIjoyHjZGKio6AgYWCiJCD+vWJgIOBhoKEiIaHg4SHhY6KiIiDh4uIiIaGif2FhYOHooqJiIuZgYSDgICCi4eFiIGHgIb78f+Jg/qJgYSCiIKHgP6C/4GE/oKEjI2FgIWJiP+BkbiwnYGMh/eAgoCDiYeCiLWQla6Uh4WKjYSKh/73g4T5/oGm7fuF+Pb9+Pj9g/z64+799fL4g4OB8vTygYf7goL/h/X++tDw84D1+4D99u7y6vv/8+3m7vX3/vHs5ej7gID68oTw6ubu6eD39oD4hP2Ag/eB8IL++Pfz+4H78+rq8IDw6Ozk8Pj19evs8ILv6v+A+vjw5/X9+e/y7+3y8//4g/Pg8vv6+IeA+ar4+uPm4/z9+fTm8vTx7fT58Oj39oCEhPb67+386oD4hoiFgfyI6oWDgYmF+4KG+pqHgfmDgPyBgoDk6erl7Pvy9vPphYb67vKDhoL+ivzz9vPu84KI7faG8fH46PHl5ff67vjtgvOAgPX094H69vf47fTr8piRh4SLio+B/u79goL8//f1/f+KjoyDgf2AhYODgIaFiYiJ0JyDgYP8iYWIhoL9/v+A94aG/ImNkfyChYKA/omDBH18fHyEfZJ8An18jH0Bfoh9g3ygfQF8p30Bfq59BHx8fX6tfQF8m30BfI59CHx9fXx9fX18/32zfQF+1X2CfI99A3x9fop9AXyXfYJ8mn0BfJd9Bnx8fH19fIh9Bnx9fH19fIl9AXyEfQV+fX19fJR9C3x8fX18fH19fXx9hnwBfYh8DX19fXx8fH19fH19fH2GfAR9fHx9k3wEfXx8fYh8Cn18fXx9fXx9fH2FfAF9hXwBfYt8BX18fHx9j3wBfYZ8BH19fH2UfIN9hnwCfXyEfQN8fXyFfQ18fX18fX19fH18fX19inwKfX18fHx9fX18fYZ8BX19fHx9jHwIfXx9fXx8fH2IfIh9BXx8fH19hnyFfQF8j30BfIV9DHx8fH18fX18fX19fIR9A3x9fQICBACA7uni531+ft/s4+fsd9vMdt52eOne5OF2deJ1fHurgnV7eniAd3h3g3J7enl2cHBxdXHTcXBycHRxdHfGdnRwcd1zdG+GcHl9dnRzdXJ4dHt0dniAg35+g4B8fXt7fX59e3d4e3t5ent2dnp5d3t2foaRi4h/goJ8f4mFdnx5dnUoe4J8fHh2fIiAfoCDgX59d39+fnx+fH17f4SFf4CDgYWEfn6FfHR4eoR5gIyF2HZydG13fnp2cXN1e390dXx233Nydn11fH6CgXl0fn98f3l+eneAfXl9dXuNe+Z5doOCeH17f3Z8feaAgH+C64mFfoSMgoKDfHyFjHhwc3Nxc32BdXV8e3lxdnp6gHd5gXt6enmEhn+Agn95eXx/fIJ0e3x+j4VwcYZ3dYKAgHx5eHl3en18d3R1dW9zgXl4c3Z7eHuKhIB9iImEgpOshX92eH2CeXx6en6Af3N7cnt5eXt8dYi+kX56gXx3dIV7dJWLh3x8eHp5fnx9e3VzenZyd3h2eX+Af3V+gIJ9f3t7enp2cXh6eYB1hYCEhH56eH1xanmEfXl6gn+CgXd3gHRrc29zdnB4fnV5fHp2eHp9gmFzg4NzgXhzbW50eHZ5e3JtdHR0eXZ/d29yeHh7enJwcXFzcnRscHlwe3aAjHdqcnFzZ210cnJ2e3Z5dHFwaXB2ZnB4dnt3dXF1dHpxdXZ3dHZ6qYByfHV2eHx4dnPZd3HUa3Vt021uc3J7eHXLgG54cnZxz2pwaHBzdnRtcHJ0cnRzcnZ2c3V4dXRwfHN3eXDh3+Le33F2enVzdnR3fHd7cXF6dnxvdXFuendudHBzcHd6bGlldm1wcnN3dXHP1uFvxm1tb3BvdGt0cHTR2XBxcd13bsNxe3Ju1tpuaWdnbGZoamhkwMC6vchmZm/LgMXOc3RybHZ4eHFtz9JwcHFzhHvhcn11R2fhfHvxeet33dp1dXZu29TAy25RYsRobGhquL3GZmbCbGtsysO+yLnJycjP1MnC0NDNxMFveHZx3tvV4NjQ4nl2d97o3NTYb9fd3tV1bMzFys9vzdHG0Mva5tjez9fb0+DW1tPc2c7NgNrQ1dzWdtLMwsHFxc5scXFictPa4d593ed8eel41HZ94uNx1NfYcdPHvsjLz8vJyMfExW7JycfFydHHydDN13J8dnhx03Z0iHl8fIB7e+l6fPJ+5ejt7t3h09HU23nZ5tLUd+LX3tjg1trd5XbmdePZna93eXV5dnjn5dre3ePZVODa4XbU2N7l4nnq2t3n5uvhfJ1/dXWBrKOB6+p72tXTzNHVdW51zNJzknFvcnJy0MttbWps2NJtbHBy5eF72NzV6Hl48NjW4trpd+Xtd3x6d3x9eoDt5uDpe3+H5vPu7fJ94tt66Hx88+vs5np66nl9faqEd318fIF3enqCdoB/f396e3l8eOF3eXl4fH18gtB/fHd56nd6d4p2fH96ent8d3t3fHZ3eHx+fXh9end2dnd6eXZ1eHh5fXx9f355e3x7fHmCh5WRh35/gnt/jol5fXx5dYB8hH5+enl9iH9/gIKCfXt1e3l5d3l7enh9f4J6foSChoWBfoZ9eX6CgX+DgZKI6IB+gn6Fi4V9e3t7gYR7e4B87nx8eYB7f4KEgX13fX1+goB/fn6CgYGEgIWRhfiDgo2Jf4OAgHp+gO2BgYCD8oWDf4ONg4ODfn+GjH14e3x8fICKj4GDiIWEgIKDhIp/f4WBfn5+hYZ9g4KCenuAgoGDeX+AfIx/cnSEe32Cgn98eHl8gIODg4SCg4CDi4WFgYGDgoWQh4J/h4aAfYqag3p1d32Cen17foSHhHp+e4KAf4KDfYrNnH+BhYB5eIh/epWOioSAf39+g4KHgoCAgnl8fIB/fn+DgoN3e3+Ff3p9fHl5dnJ6fn19coB6fH99en6CfXV+jIZ9gIWDiISBfnxzfHl9fn2BhHx9gXt3enl9gXWGhIhphYB7d3mAg3+Cg3h1eHd8fXyFfXl7f4GEhH56fH2Af4B2eoZ8hoCOmIV6gHx/dnt/fnyDiIGFg358eYCAcoB4gH1/e3p3e3t/eHt8enp9fa+GfIV9fICEgYF66X565HV7dORydXl3hIF/23eBeYB63nF1c3R4e3pzd3d4enl4dXh8eXl7d3p1fXh7enXi4+3i5XF3e3Nxc3F0eXV4cXd4eYB3d3h2fHp2dnV3d4CAdXFtenJ1eHl4dnbb4Op13IBzc3Rxc3pze3V45OV6enryf3rce4R7d+fseHdzdn12fXx7d+Tm4dvlcnN83dzieHh3cHl6d3Ny0tNyc3R1hn/pdoB6bYLrf3zweex13N1zdHVt29fP3IF3cdh1eHN32d3hdHfhdnJ0287R1svV393m59jR3dnb1NByfHh13NrX3IDQy+B2dHLa4tTY33DU3NzZc23TztDYcdHRzdTN3ubc3NLW2NXl1NTU2trV1t/Z2uTedtzY0NDR09p2eHeGf9ji4d932uB1dd9yzXSA5uR03OLhdujcz+Hg6Ofi4OLd5H3j5N3a3uDZ3N3Z2nR7d3Nv1HBseW1tcHNta9NwcNxz2IDg5ujf5dvX2eR+4Ozc333r3+Lc4dzd4+157Xjp45WhfHx6fXp79O/i6uXu6O7i9Xzm4+ft63vx293j4uvgd6p5cnZ9oZ996u9/6OTi4OntgnqB4eJ7l3RweX144tx1eHh45+FzcXZ37uh+4+jf8YGA9Ozj6ujyfOntd3l3dnd6eYD68un1goaT8fzz9/eB6uSA8YCD//f59IKA+YGHh76SgoiGho6Dh4WRgoqJiIWDg4KJhfaEhIeDiomJjNOLh4GD/4KEgJqCjZCLiIiKhYqDiYCChYqLiomNioWHiYeLi4yJhoaJhoeJiIWDiImEiIONlaCdlo2TkomMmpaFjYmIg4CNk42Lh4aLmIyOjZKPi4iDioaHgoeGh4WGjZKIjJSPk5aPipWOh4uPj4yMiaGQ7YaDhoCKlo6IhYeKkpWKho2H/IaIho+HjY+RkIeCjo6Pk5KQjYuTkY+QiYuWiPmGg5GMhIiHi4OHiPiGh4eJ+I6LhIyVjYyOiImSmomChYSEhYCRloiIjouIgIOIiI+Fg4qFhIKCkJOJj42MhYeOkJCWipGQkJ+ShYiajpCVkY+LiYmIjI+OiYqKiYaIkY+NhYmLipGgl5WPnZ+XlabDl46JhpGZjo+NjZCYlImSi5aQlJWWkaP7vJOUlo6KiJiPhaaalo+LjI6NkpGUlI+Pk4+OkYCTk5WZm5qLkpSVjoyOjouMh4SPkZOShpmQlpWUkJGXioGPnJONkJmVlpWLiYqAiISHioeQkomQkpCLkZSVmpGlnqOKoZeRi4qSlo6VlYmEh4eMjIiWjIiKkJSVk4yIjI6SkpOHi5iPn5epu52PmJKWh5CUkZKXmpWZlpCOipKYhzuRmJOZlJCQlJSXjI+Pi4iNjseYi5KLiIuTj4yG/ouG+YGJg/6EiI2KmpaS/YiVjZKM/YCKg4mPk5GJjoWQgIeNkYqJjISEgIiDhoiB+vz++fiBi46JhoqFjJKKkYeKjYuRh4iDgYqGgYWEiIiPlIiEgpGHi4uPkIqG9Pn3gvWChISGh4uBjIWG/vuBg4H7hYLogo2DgPj/iYWBhIuCioyJhv389vD/gICK/fP+iouJg5CRjIaE9fmGg4aInI//gIGJg5ra/oiE+4D+gO72gYWHgPf45/+WmoL7hoeEiff5/oGC94SBgezb4urd7PPs9/jt4fPy8+johI6Jhfz49frs5/yIg4Hw/vHv+IDw+f77iIH16vD4gefo3eTi7vDo6eDq6+j47+nr8PLp6Pnr8vnvhPbu5OTm6O+DhYbirfH4gPz1gfL9gYH5gOGDivr/gPH28oD68+bz9/779fP67/iJ+/z18fn88vf88feDjIqKgvODg5aEg4OHgoP4goP+h/Xz+fbz9OTd3+uD6Pbl5ID16Ozo7uvw8v2B+4H99JqkhYSBhoCD//719vX78vf2/4Pu6/f784T98/D78/3zhb2NQIGBiKqNif/9hvbz8ezv94mCifT5haWEhIiKhvjwgoKAgf3+goGDgf/5ie3y7P6DgPrv6vf2/4T4/oCEhICEhoKEfIN9hXwHfXx8fXx9fYR8A319fJh9AXyNfQF8630Dfn18kX0BfJt9AXyLfQF8hH0BfP990X0Ffn59fX7ZfQh8fX18fX19fId9AXyFfQF8nH2FfKh9BXx8fH18in0JfHx9fX18fX18hH2CfIp9hXwGfX19fHx8iX2CfIZ9D3x9fX1/f3x9fXx9fH18fIR9hHwEfX59fIR9CXx8fH19fH19fZF8hH2HfIN9hXwBfYR8gn2EfAF9mnwBfYd8BX19fX59hHwRfXx8fX18fXx9fXx8fXx8fH2MfAF9i3yFfQF8iX0FfH19fH2KfAF9hHwBfYl8BX18fXx8iH2KfAF9hXwBfYd8h30Ffn18fH2GfAV9fX18fId9gnyEfYJ8hH0DfHx9hHyCfYZ8A318fId9AgIEAIB59OXvenx3eHrn3Xl3d4F5j+CDeeXh7erp6uboeevpen5/d3bh1nTidXmGhHJ5c3JvdXd7d3lyeoF/gHR3dN3LeHZxbHRvbnp0cXFsQ0mAdHR5f3x6hIB8iYB9gIJ9fn56gH19enJ24XJ2enx263t5enl5ent8fIOFfHd3e4SHgoCHhHaEhICJkIuGiYOChIKDgoCBgXt8fXx4eH+GrqmPtYJ9foKGeXl3d3l3cY52eHLObHFydHl46IB6dndy7X97d359enuAeHx4eICMfn1+dnnbc3h+fn1+gH175nx7gIKBhXl+gn+GjISJgH6kin2Bg4d/gn6CfoSMhYF8d3hze4CciIV+gHx4iYOBfXd5eXx6fH12eHh4e3h/dn56dnp5gIKDhIOBcXR6cnd9fXl3fnx8fX9/fYN7enR8e4F7enqAfoGAg4N5fXt/g4WBgX9+gYV+fHd2dXNzeH1/fXVud3RzdneIg32Cd31/gYF3en2BfHp/h3x4eH98dn+IgIGAfYB9gYOBfH2SiX96fYB7g3p4d3N8iIiQeIaGqIFzdXl/c5OWenh6dXt4bXd0cXJxcG5xd3uAeXt/ent7gIOCfXx5fXh7hnx0dnV4d3d7dHl8cnWBd3t1d3N1enN1enZwcXtzcmptcHFyc3F1dXJxdHx2dHSLd3JwdXd6iJJ2cnNycYCAdnN0cm50dnd5cXlxcWtudHl0eXl133Z3heN5gnp+d315cXBtcHFrdHV1b3dobm1rbMtsbXJwcnp1eWlvcWpvfW9ygnF9dXVxdGvT2HHZyG534NZ0c3N0eOJ2e4aCeHV16XR7d3NzfOFvcnVyc29zbm3Vz3Bub3B0eHdxbHB7bYBqZmrIc2fK0mxzcsPL0uV3hYl6cnRuzW5rcW7LZMZoaGRuZGlpxddwcHJ0cnPYdnRz4+Fzc+Dg087WdGxwcm1w1Gxtcdxzc+Dl4s7YxtPVy8Lg0Wxuas9sY267ycNqbM3T1NXZ1tncv6/Kw9RYcsnRbHBuaNHAu7zNbG1ucN7d04DSytvX5XHe5HThbm3e2NjX4tjFztfTztm61szS0trd49LT2N3ZyMbV2NbMys3TccnJyM5wzbR4cNfS2N5xdOR0cczd4dnSedDLfd/e1tnNb8/C1NpyyMpznmp0jm1uyczCw9DBxMF10MbNbtNwdHd81nJ65uN6fNvj0unt6ed56YDo4t7f2tbX18+AzN3d49jVxdHX2Nxu2m5u0NRwd3Bwb+Pd0c9w0dHO1txs2M/Qy8/TwMjT1dTg2d7e2Xbgd3l7duN64t52ht12dd3X4dbW0c/f2NbbcNXccd3Z3OHo23bmdXVzc+DZ5uGB4OLV4NTH2NbK3XJudeB65uR5fel7eYB27N/reHt3eXrr5nt8foaAmfqJf/Pz9fj29PPyfvb0f4ODfnzu5Hrse3+IiXqCe3p4fX+Aenp4fH98enNyduXVfH57eIF8fYaDfnx3U1KHenx+fn58gXx3g3x5fH15e3p6gICAfnl99X+CgoV99H+DgYWBgH19f4eGg31/fIaLgYCGhHeFg36BjIV+g399fn58e3h7fXh7e3h2dXyBrKSQs4N6fX+DfX99gYODe5t/hH30fX19foJ/+YSBfn5573+AfYaDf4KIgYN+g4aVf4SFgH/sfH+EgICDhoJ+9YKCg4aJiYCEgn+BioCHgHuZhXx+f4ODgoCEgoaQhoaBfYB8gYCejouGhoSClY2KhX6AgIJ+gYJ9e36Ag4KEe4J/fIB8goWIh4mHeHt/e32Dg394gn+Af4KAf4N/gXyChoyGgoGHhoiHioKAf3x5fn97fHp4foF6e3R2d3d6fYSHhH13gYCBgYCek4KGf4GBhod3e39/fX+Din98fYJ/e4WMhoWEfoB+goOBfYGdkYeCgYWDhX59eXN6goCJdoSGtoh4eXyBd5ecgXx+gIeFeoZ/gYGAfXp8fn+AeX1/fHx6gYKBfn+Bg4KGjod+goCEhYOHfoKFenmFf4J9gHt+g3+Ah4N8f4iCf3V7gH5/fn19ent7e4R+fHyPf3t6f4GAlqt8d3h3dUaEfnl7fHl/gX9/eX97eXd5fYF+fH166nh9h+t7hHt7eIB/eXhxfHl2fH+Cf4B4ent6d+V2eXt5fYF9gnJ2d3J1gHV5g3V+hHeAcuDideLSdHzo5Hh3dHV56nd7hX53dXPhdXl3dXZ86XR1e3h6dXh2c+Hfend4d3h5eHRyeIZ3eHV55H937fJ8gn3i3uLtf4KCeHV5eOB3c3p533HkcnNxd25zctzddXN0d3N03XZzdeLkdHTe49jO3Hdxc3ZydeBxc3fneHno7O6A2OHU4eTh1+vgdHZ253d1edTh13Z64eLh3+To5N7NwNfT3Vx31t9yd3Ry3tbSz9lzcHFz4d/T0s/b2eJx1+J033Vz5+Tk5vDn2N/j4uDly+HZ3tze4+bY19zj3s/N29vc1dLV3HTUzc7Tc9SufXPg1drec3XacnDQ3N7W4HfRz3uA5eXe4td23tPe33bY2nnjfn6ieXXb39bb4tPT14Pk2dtv1nB0cnbTcXPV0W5x0s3D2ODd3HPg4tje2tze4N/ZkdTk6+7l6Nrl5e3zePB7fOfwfX96e3ny8Onieebj4Ozwee3n5+Po59nc5+nl6uHk5t9443d1dHDjeefsgI7ygH428OTs4OPh4uzr5+t65+l15uHf4uPadOd3dnd48O/49Iz5//b68evy8erye3l963zu6Hh96nh2gIH86PiBhYGBgvrzgYCFkIWf54+H9vX8+fj79PKB/PqDh4mDgvryhPyEh6CUgYqCgoOFhouGioSMj4+Ng4aC//GNi4mFjYqKmZKPi4SwkZmJi46RjYyVj4mYjYuOkomMi4iNi4qFgIX7goSIiYH/goaGiYSFhIeIjoyJhIaIj5eMgJaSgZGSjZOcl42TjYyNi46Mio2NhoqKioaFjZGxs5/Ck4eMkJKHiYeKjIqBo4aNg/SBhIKAhIH9jIiEhIL+jIyEkIyIiZKJi4eNkp6KjY2GiPeFjJGKjYuNiYb9iIuPkJCRhImLh4uThoyEgKCLgoSGiYmJhIuJjZqRj4mGiIOKgKucmJOWko6hm5aSi42LjIiMi4eJioqPjY+IkYyLkY+UlJqXmpiIjJGNkJaWj4uRj46LjYmIi4aHgIiKj4yLjJGQlZSWkYqMjIyRlIyOioqLkIqJgYSEg4iLl5uYjoSVkZCSkLmqkJaIi4+RkIKFiImIhoqVjIeHj42IlJ+ZmpiWgJOYmJeTlK2imZGSl5KXj42IhIyXlqKOn6HTnoyNkZSHs7iUj5GJlJGDkoqHiIiJhIaOkZeOkpWSkpKanJuXl5aYlJmlmI+UjpSRj5WGkJOFhJaMjoqPjo+ZlJecmpOTn5yZjpKXk5aWlJSSkZGPnJSRkaiVj42Sk5OouY6Ii4mJgJeLi46KiI+PkJKKk4uKiYmPkYmIiYL8gIOQ/ISNiImFj42Eh4CKioWPk5WQl4aLiYaG+YaJjYuOlZCbh42NhouVh4yZiZOJhoWEgPz9hv7sgYr4/4mGhoWK/4SKlI6GgoH9gIeFgYOJ/oODiIWIgYWDhP79jIyPjo2RkIqHjZ2MgIqGiP+Ngff6gIiH6uft/IWSnoiAhYLxgYCKivmB/4GCgoiAhYL8/YSAhYSChfmEgYP8/YKF/f7z7feEgYWJgob+gIGG/4aE+fr95eza6Ozp4P3zg4OE/4mvj+r374KF+f38+vv++/zj0+zp+YSL8v+Dh4eC+/Dv6PqFg4SE/fnzgPXm9vD6gPP+g/qDgf/4+/f+9+Tp6+vq583k4OTk5/D15+Xo8e/b2Orr7t/e6e6A5uPn7oPuwIqD/v33/4SH/IOC6vj+9umK7OqK/fj3+/CD+ez9/YT1+In8iIqkg4Du9Ovq+OLj6YP3+PuB9YCDhYz0gYP4+oSE9/bo+fz59IL6gPfq6+zu6uzo4LDf8vX27PDe6+76/4D5gYDw+oOHgYOA/vzz7YDw7O35/4L18/ny+PLn7fX78fz7/vzxgvWBhYeA/YX6/YeU/oOC9+757u/o6vTy9/yC9vWB/fj49/fugvyChIKA+vX7+433+vP76eHr8ez/hICD/of7/YSL/YSCBH18fHyFfYJ8iX2IfAN9fHyFfQR8fH18ln2CfIx9An9+mX0BfIV9AXzCfQF8hn0BfIV9AXyTfQF8iX0BfP99/328fQV8fX19fJd9AXyYfQl8fH18fH19fHyFfQF8h30BfIZ9AXyJfYJ8j30IfH19fHx9fX2EfId9AXyEfQN8fXyHfYJ8hn0IfH19fXx8fX2FfIZ9B3x9fX18fX2MfAx9fX18fX59fHx8fX2NfAR+fXx8hH2FfIR9iHwHfXx8fXx9faJ8AX2EfAV9fH5+fYR8BX19fH19hHwFfX18fH2FfAF9hHwDfXx8h32IfAZ9fHx8fXyEfQd8fX18fH19h3wBfYp8AX2LfAZ9fH19fHyFfYR8AX2FfAF9kHwCfXyEfQl8fXx8fX18fX2LfAR9fHx9hnwCfXyEfYR8AX2KfAx9fX18fXx8fX18fX0CAgQAgO/z9np68u976Xp8fHl1dnuBgXh35tx13HThet7nd3mAfoGAc4J6dXp7eXx6dHN5cm5rb3NvdHFvdHh9fXt9goONiHl4gnmWb3N5hmtubWZsb3BsdH6nfn9ze3iBfXl3gYB0d31ycnJ3dnFvd3p9f3Z7dXN6dHh6cn97fXqEibiEgOB8dnqSioGDiYN7fIKCeHp2f3yCenp2e4CEiom+j3+zgYiChY6EiIKFl4yDkYiEfnx7e+d0eX93enp3iPDqgoGDf4F+end3eHR2fHx4dnd3dnh6fXl4dYV7gH56gpKSfYN+f4N+en98e3p2e3h1d3l3goJ9fnp6gn6Ghnl5eHt9gH16fYCDgoJ5gH97fXyEfXuAfXt6eH5/f4SJhXlze4B9eICFgYF4eX+CgImEd3x4fXZ0b359fn6Gh3+AgoaKvoN0h7B5gnp4d3t4eHx9hX6BfXh9ioB9fISIfoN/gXx6fH97eIGAe3l1el9ViXp6gfN7foGIhXmGhnV9iJCHhIV7gHx7dnl2dXh0fH16e394gHR2c3JtbXN/h4eMg3l/d3l4d6GSgYDAtoKAgYF/f316gnx/gXp5eXuCd3l9hoF+fIx4eXd3cXF1dnlxbXFte3V1eYN+fXx4dHR5d3V7dnJudGhvbXlxf3VwcXJ2dXV0dHh1dXt9dXF2f3twc3N5dnd5gHh7e3Z2fHx7dXVycHVzc3d0cHqBe25ydnp8gHp1cXRydpN3cHNscHFrbXNva2xxbWtub3VxbHFzdHhtbmhww3JvdW1sa3Jzbc1ubG10ampvcXZ4Z2lqanN1c397gYF5dnuBgnbm6X3oeX56b8/TxeB0dI9ucm90dXmUe3ptcG52gGdva2rFamXGyW9/tsHWz9DRmnHY02jPbW5ubGFmYL3Da23PwcnIaHt1bGpraW1zcXR1e292dG1xcHVvbGrRcW1yamloaGxp1G5ycnJ038zAxsrSfNBuz8pvd8/Fas5scm91d3JubHhwzs9qbWNkbGloycPDvszMy8nQ0m/d1nPdgO7peMnf3tXV3+Lm0sPe2M3o39zX4dTPwNrX1NfN39nWysrb2djTedTV5NnSeeLS2dTL2MvLx9TWc8jOzsrP2NRt1s9r03fZb9tyb3Vyd29v0svRwdLX3nHU0MPR19fZ0szAw87MynPVvc3N0HHgcHVy5XPT13nUztLX1N11eXTpgObi3Nfj4Nd34szQ5OSH087OzeN6c9nn2d11c9LgcnN2etrTctnRzOTg3G924dmO2uJzdnR0eubd1dzS0dt6d3V07HTg4NPQzd/d2s7RyNnT3XmV9+7m5dfm5+Tl3dPS09PP1mzUb3Js3NrU2dFwbtfg39Z1enF4dn94fXt59H7wgOTn7nh37Op35XZ4enp4d3x/e3l97vV89H/1g/DxfoCHg4CBeIJ7dXh8foCAfYODfXp5foJ9hX96fXt8eXR2e32BgnZ6gn6eb4GLl36BgHd9en5/goqrg4F3f32Khn5/iYp9gYh/foF/fnx9fIKCh3+EfX6FfoCCfYiGiIaKj7SJgO2CfX6Zi4KCjIR7fYGAeHd1fHt+d3d0d319hIS3iH+yfoN+gYmFhYKBkYWAjoiGgoSChvt/g4eAgoOCjffshIODf4CAgHyAgoSFi42HhIOBfYGAh4GBgIqAgoB7gYuPcYSFgYKAf39/fX5+g359e3x6goiCgYF/hYKIiYCBfoKFgISDhIaJiYh+hIN/f4CHgn6DgoGCfIOCgoeIh315goaGgIeKh4Z9f4WCgImIenx7gXx8d4CBg4SHhYODh4qRvYyAjJ2HjYSBgIF8e39/hX1/enh5gXx8e3+EfoB+g359goV+f4eDfn98gnRtjIB9gfF/gYWEgHmAgHR8hI2EhYN/BH5+e36Ee4CDgoKEhoOLgoCCgHZ2eYWGhI2Ce356ent/qqGLjKSphIWFgYGCgIGCf4CAe3t9eoB2dnuDf4F/kH5+gYGBf4CCh4F7e3qGgX2AiYKEgn17fn1+fIOBf3iCdXt7hH+Kf3h6dXp7eXh3fHp4gIB8eoGFgnl7e4J9e359fX15fYGFgoB8f3x9f4B9gn58gIeCeHR3e3+Cf3Z1cnF3oH53dXh6enR2fH55d3t4d3p5f397en99hHp6c3jEe3Z+dHN3eXp433h5d352eHl/g4FycXVxd3dze3d6eXVxdHp9c+boe+x6fXt34efd7nt4kXR5eXx8gJ6Fg3l9fYd5f3173Xp144Dmd4Ck2Ovp7ue0e+TmcuV2eHh3cHJu2eF2eOLW2dxuh4BzbmxucHNvcXN4b3VxbG9tcm5wbtl0cnlvcHFyc2/bcHZ0dXro2s/Oz9yC5Hbn6Hp+59Zz33N0cW90cW9wdXLP1m9ybW5zcnLc1tjV4eDc2NvgdeHgctvj5HXK397U24Dj5OvczuTf1+fh4uLj1tXN5dvf4t3m5+Ha1+Xn5OOK4dzp3NqA7dri3dTe0dXW4eF31d7V1dna3W7Z023XcNNu1m5wdG91bm7Z3uDU3d/kcd7b0dzm5+Xh2cnO09HVd9/R29bWceBwc3Lic9LQdc/MzcvL2XB0b9fc1tfa4uXeenjv4d3s84jl39/j84B84PDo63l55et3en996OZ64+LW6ermdnvp4Jja53N2dnZ95tza4Nvf4Hx6d3bvd+vw39/j9fLv6uXj7ebgeIjq5+Hi2uTm7PPu5Ofq7Ozsdeh3eXjq7O7v9H157/Hu8Hp9d3t9gHh7enbreeSA9/j8gIH//oT3gYOEg4GDh4uIg4L7+oD1gfiG8/uDh4yKiY6CkIiDh4qMkI6Jj4+Lh4CGi4eQioaLjI+Nh4qSlJuXiouXjrSDkZuqipGOhIuLjYySmsiSi4CIgpCLh4WRkYSGj4SGiYmIhISJjY6SjJGJho+IjI2Gjo6SkJeXyJKA/oyFh6qbkpKcloqKk5KIiIWMi4+EhYKGioqRka2LhrCJjoyOl46Tj5KllY2gl5eNi4uL/oCEjIOHhYKR/faJiImHjIuJhYqLiI6Vk4+Nj4yFiYyNh4aEkoiLh4GJlpmBiYmKjYuIioiEhoOHhoKFhICOkYyMiYyTj5iWjo+LkJOAk5aVmJycmo6WlI+SkZmSkJeSk5SMlZOSlZ+YkoyVmpqPlpyYl4+QlpOOmZmKjoyRi4aBiYOHiI6NhoqPk5bMlImbu5GYjIqMjouLj5GZkZGPjI6WkpGOlpuTlpWWkI+VkpGLlpCLh4KDtJSUgoCH+4ODio6LgYyMgIqUoJaVlY+AkJCKkI2Mj46XlpOUl5GYjo2Oi4GAhpWYlaCUj5OLjI+QvKuWmM+7lJGSj42Qj5GZlJmYkpKVlZySkJehmZmYqZWWmJiWlZaYnpKLjImako2TnJSXlJCNkZaXlZ6bmpWgk5uepZypnJSSjpOTkYyNk5GNlZaPjpObmYyPjZSNjI2AjI2OhoiPkI6JiYyJjY6OkpCLkZyUhIKHio2RiICAgoGGsY+IioeLioSEjY+IhouEgomHkZKOjZGSlo+PiY33kImOhoSIjYuG/omIh5KGh4mNlJGDgYWAiIaBjYiNjYeBhImMgPv/if6EiIWA7fXq/YaDoIGGhIqMkLOamImLiJSAgoqIhe+GgPr2hJPJ5PXv9PjKiPv9gP+Gh4uKgYSB9f2HiP/1+PuAnpmFgYKAhYqFiIiOgo2HgIODhoCBgP2IhIuAgICBhID7gYaGhYn/6uDc3+uY+YL8/oqR/fWC/oOGgIOHg4CCjIb3/YeLgoCJhoP/+vXt/v759fX6hfj3gPGA/P6A3PPz6e33+Prw4fny6Pnx6efs3d3J6+Lq7ujy8/Tp5vDz8/KR5ebw4tyH+tzk49bp39/a7vWF7vHq8/L9/ID48oD4hPuC/oWEhoKKgYL79/rs9Pj/gfLu4+75+Pjs69/h5+fvhv3p+/b5gfyAhob+gvLviOzj6ujm94KFhv9p+/X16fr47YH05+b2/Y/p4uLm+YWB7fz2+4KA9v+BhYqL+fqD9u/l/P37gIX+8cP1/YCEhIWJ/fny+enx+oaGg4L9gv/97urp+fT16urh8+rug5f+/PDu5PL7/f767/L9+PX/gPuBgYD7hPwThIH4/P3+hIqDiIWOgoiFgv6E+Al8fHx9fXx8fXyLfQl8fH18fXx9fHyqfQF+uH0BfLJ9AXyIfYJ8oX0Bfv99in0Cfn+EfQF8/33ZfQF8m30EfHx9fIR9hHyUfQh8fX18fH19fYR8B31+fXx8fXyHfQR8fH19hHyXfQF8iX0BfIV9hnwLfXx9fHx9fXx8fXyKfYJ8h32KfAh9fHx9fHx8faN8AX2FfAF9i3wBfYd8CX18fH18fXx9fId9h3wBfY58AX2FfAp9fH19fXx9fHx9hnyDfYh8AX2FfAF9hXyCfYR8BH19fHyEfQN8fH2GfAd9fXx8fXx8hX2HfIR9Anx9jnyCfZB8BX18fX19hXyCfYR8in0DfH18AgIEAGLy7OXv8vh+gNvs6+vi2uj08955fXjR53fhfX90c3dydHVzc3V6tm7Qc3Vycm3GdIt6amxyc25rcXJvc3NwcXN+dXeBg4aHhIR4bnFyaW9zboVUc4J8gnl4bnR5dXNycXFwdoRwgG5w1m1ycG53gHhxcnZ1cNlwd4B0cWrZc3V11HV9d3d8fHl/jHyFiYeCfnh+gH+DhI2DhX6AgX1/gYKIg4WIg4aDjomEf35/e3h5fIJ/d3h9gHV443d763h6fIN/gIWFgHyBe3d2fHhzcXV3c3t5dXtxf4OHfoV7gn5+f4OFf31+gIKBgYN7foSHg4OB939+en56oo99foR9g4B9hIeIi4aFio+JgX6BgoCDgX2Dg32JjYJ+fnt7fXZ4e4CDhIKDWYaCgHukjXt8fX1/f4N+h4aPhIiMfXx+hoF+fnx9f3x6eXl1dX59dn97erGliaKJmpCGh4iFh4GBiXyAgoKGhYGDgH99gnt8fIB+eH54f3yBf31/gYR/hoF5fn13enx1eHl4eH5/f3dwcnN0cG5wcXBzdHJ6dXd1c3J3d35+fYOOoM+BiIWGgYWCgYiNf398f399g3Z1bXeNhoJ5eoaAen1xcnh6fnp+gYJ+eYZ/goF+f353eXpzdnhubW5wc3R0fW12gH1wfHx2eHd9eXV1fnt4cXt+cYC6d3h7hYKWg4F6e3l5dHl5dnJ1c3BybXF0coLKa29w3Nt0b3B6d911cnB5d21dfG9vbnN0cXZ0c3NxfnJydHJyeIlydHd7eXh2eNtzeXNvbtJwdHBta29oa2doxmZsa2p0cW11dHble3t9gXd5gHh3eXl15HLW2Hd7c6J2aGhsZsCFcm7Bb2+HcGpza8nFaGlr1XuDcW1pdm1qyHJt1mtpbmrHa2jRbmpubHxzcM7Zt+HObW9ucHB0gHh7cHVzctbS3Nl0dn58cHHWcW12cGlwa29wcNDZxnrPxs7L0dty1n6Jz9JvbnDUcNHJbW9ogIFnbmhmbmVlZ2ZoamNnZ83WxcnV0+Da1tly4NVx1Gx0b3R0dubT2eLQv+bw7OV33tTczdLnd9PN1tDKzNDJ2nNxdH97fN7Jx8/R3t7a59TT3tzRznPjxNjNwdLGz211dGlqa9PU1c3OcdrM2nV309LVzNHZ2dnWztjM4dvI1HPZgMtw08/OyNVx4tzgfuHpeOHk3up4iH54eNx02tXN2MzZ19vJwcvnlXzd2+N34ujd4djb1d3VcXHlctvf1dTb2d3d3OHV4+Tnd+Hm6nh6dd7NrHB6dM/Vctnd0XXZ2XBwdm914et/hIHs6N7s593rfuPp3d3UdW/S1tfb0NRxcXJ3GnB039hxcHOddHJ5dXN0dnd6eeno7+N343nvgPPs7fH2/oCC4/Hx7vLo7/n67XyEfOb2fPKFhnx7gXl6e3d7f4K8dd57f3t6duuBlYR4fYOFgHx+fnt9fHh6eHxzdXt3d3p9fnp1en54fn97kWKDjIiOhYR3fYF+fn1+fn6CfX2AgoCB7X5/e3uAjIWBe4KAfPR9jI+Gg3/4g4iEgPSCi4CBhIB+g4yAh4iEgH14fH5+f4KIgH56fHp3eHl6fnt+fnt+f4iBg35/f318gISIiH5/h4l+fvmBhPuChICGhYOIiYSBhYKBgoeDgYCEiISJhYKFe4WFioKHfIKAgISEh4F+gYCCgIJ9gIiKg39+9oGAg4eBppaAhYuBhoOBgISFhYmGiIqOiYSChoWEgoJ9gYF6hIaAfX5+gISAgYWJioyMjmiMiYWCrJSFhIKCfX6BeoSGj4KHjIGDhIqGh4iGiYmIhoSAgX+ChHyEf3uknYGOgZiOhH6Afn18fIR7gYGAg4SAhIKBhn6AgIKDfH6Af4KEgoGEgYKCioN7gH56A39/eYR9gISHhIB4e36Bc36Ag4KEiIeKhIeDf3x/foODfXyPeM6ag3yAf4GAgIiLgXx6fH58gnl3cnyVioWChJCHhIN7fHt9g35+f3x5dHx7f4B+fXx7fn+BgIR6ent8f4F9hnd/hHqAfXx7e4B8d3qFg311gYV0g8t+eoKMiKGIg4GBgoF9gIKCfnl8e3p8eHt9eo/fcndz4eZ5dnaAeel7end9e3VyhHp4eH17eX57eH15gHh5fHx6gJF9f32EgX59f+Z5f3d2duZ6fXx7fH54fXp36Xd6enl+fHV1d3rmdHd4fHR1c3R5enjtduTjgH54pH11d3p22q9+dt2Ag5yCfIR77el7I3x873OWe3h0f3515X19+H16f3PgdnPidnR2eIV+dtvgtuTWhG+AbnF8dnhxc3Jw1NfY2HNzfH1wcttycXh0bnVxdnZ14ObXhuHS39nf7n3klZza0nFxb9dw1Mhyb22paXRubXVucHNxdXlxc3Lb4dPX3dni2tjacd7TbtJrcWxsb3Hf0dfc18zm6ejpeeDY5Nje8H7m4OXc1tra1eJ2dHl7ennh08pm0tTc4Nvh2Nrg3tbTc+XS39rT3NjmdoN8cnFu09vVyM1szcnbeXrb2+PY3eHb3d7V4tjj3tPXdtrOc97f39zheOri5X/i53bX2dPcb311cHDTcdrX0eLb5uLk2dPd8qOJ7OLmeu7shORo5uPfd3ftee7u5+br5evq7Onh8Pjzfezx9Xp9e+i1nneCeN3geO7o433x73x9gXp98PR+goLt497m4d7le+Xw6u3le3TY4N/f1+F5eHl9dXzp5nh1eZ56dnt6d3l6fHp87u3y5nfpfPOA+/Tm8PP+f4Hd6e3o8eT0/f7wgIeA7vuD+IuKgYKIg4OEgoeIkN+D/IeLiIaF+o2pk4OIjo2IhYuLiIuJg4iIjoWHkI+QlZSWjIOGjIKGhYO8h4mTlp6UkoWMkIuMhoiHhIyDhYmMioz9hIeGhIWWjImDiYaB/ISQk4WFgf6FjYeA/oiQh4qOjYqPmoyUmJWPjYWNjoyOkJePjIaHh4GCg4SJh4yLio+PmJSRiYqMioSGipOPhIWLkIKD/IGE/ICEg4uLiY+SjImRjoyLk5GMiY6SjJSRkJKHlJOWiZCCiYWFioqNh4eJjIuIiYWFjpOOiIj/iIeIjYawnoqMlI2TjYyAkJWSl5OTmZ6Yl5OamZmXl5SYm5OcopeRkpGVlY6RlpmYmpmigZqUkIy7pZSSk4+Oi4+Ij5WbjI6ShYSDjIqNkZCRk5GSkZSRkJiZj5iPjrmvmqaZqqacm5ycm5mYoJSYmJeXl5GSjYqRioqOjo+Ij4uPkZiTkZOSlJOcmI+UlI6AlJSNkZCPjpWXlY6EhouNkIyMjYyNjouPjI6OioeOjJOWkI2gabJrnJebmZ+cm6KomZeUkpWWm4yMg5GroJuXl6SZl5qQkJCRmpKSlZSRi5aPlJSSkY+Mj5KNkJOKiYuMk5OVnYyWnZCbl5WWkJeQiouVkY+Hk5KGltWNiZCcmbCAmZiTkI+NhoyKiISFhYCEgIeLi6f6goeC/P2GgYKIhf+DhIOMjYWzn4uJjpOTjZWUjpeRm5GSlJOTlq6UlpCZlY+OjfuHj4WDg/6Gi4uJhoyHjIuH/4GKhoSMiYOHhoj+hoiKjIOFg4CGhoH/gvb4j4+IuJGGiYyE+L6NhviRla+AkoiTh/7vgYGD/YeYh4aAj4uD+46J+YuKjIH7hID+g4GChJGLhvf/pP/4gYGChIGFkIiJgIWDgfLx8/WEhJGRgYX7iIONh4CGgYaJhfz/65Xv4fDs8/+G967H+POEg4L8hPDsh4iGzYSMh4iPhYSIhoeMg4KA+v3p7vPw/u/x9YGA//qC+oCGgYGDhP3p7vPn2vX99/iB6ubx3+r8ifLp8ufn5+Tl+IGAgoiFiPLo3uHk8fLn9e7m7/Lr64P74fry6fXu/4aSjICCgfv/+fLzgfjp9YaH7unx4+718vTw5/Tp/P3v9oTy6IDz+Pv4/IT79/aC9PmF8fTx/IKWioiE/YeA+ffy/PL8+/nm4Ov+rY768faD+Prv8/X49ffsgID/gP3+9/X38Pf3+PXv///9g/X9/IKDg/mvnoCLg+vxgP779IX9/YKDiICD+P6GiIn27Ov07ubtgPD67fbyhYDt+fj58/uGhoaKgIX794KAhK6GgYaEgoaEiYeC/vv/8YD8gviGfIJ9inwHfX19fHx9fI59AXyFfQF8o30BfpZ9AXyMfQF8hn0FfH19fXy4fQR8fX18tH0BfLJ9AX7qfQF+lH0Df35/7X0GfH19fXx8hX0BfIZ9AX6cfQF8hX0BfIp9AXyKfQF8i30EfH18fIl9BXx9fX18h30IfHx9fX18fn+GfQF8h30EfH19fId9BXx8fXx8jX2EfIZ9AXyKfQR8fHx9hnwNfXx9fnx8fX19fH18fJJ9inwFfXx8fXyGfYp8AX2GfAF9iXyGfY98AX2IfIZ9hXwGfXx8fH19kHwEfXx8fYV8CH18fHx9fHx9hHyFfQJ8fYx8Bn19fHx8fYl8BH19fH2OfBZ9fHx8fX19fH5+fX19fHx9fHx8fXx8hX0FfHx9fX2HfAF9hXyCfYZ8hn2CfI59hHwEfXx9fAICBACA4ubi2+l0eXh6fYGAeep1gaCKfObdznR52+F2cm54dNx9fG1wcm9zc3h6cnRvcWhtbW9xcHR1bHJubnZydHBvenp0c3l1e3h9dHVvbnJybG51dYDVdXF9dHqAfHh2nodzc29udHRycHZ6fXZ5eXR9enN9en2F4nV1fHJ4e3uGioWApH6FioGLgYODjYmMhoOBf/F+g4OGiYGDfnaAg4p9f3x/e4GGh4B7enx+eIKIhH53end8fnN2dHBydXd24nl6eIGAgXp3fYGAe3WFfnN1eHJ3bHN0dnR1bH19hH9/f4F9goF8fXV4e3uQho55fX6EgYSAeX6Fg4KDho+Cf3yFgI2AsJeViYWKgYGEh4yHhX50eYWGgYKZe4iEg36Eg4KFhIiKhYOBgHt/foeOfIKAf4Z8dIqAenx/iIaHioaHhYB4eH14g313fHl5fIJ7fnlyfJylhYiBe3h9g4Z+gX15dHp3d3qHj4B/enZ/eXZ7eH1vcXh8eX98f4KHfXuGenyDeXiAe3x8fX6ChYF0f3x3eHV9e3h+hXx4cnFzbm9yam5weXx4g5Z+d3d1e4CCf4V+eH1+fICLgHuFfXRwdHh9fX2CfHRweHp4e3l6f3h3eX1/fX6EiIOCgYmCfYOBf3l9hH97c2x5foCAf3h3eHN7fIN9ioh5hIOLeGx7f5ltdXt+eHeAfICEhYB7bXhyeHd0bHB1cXBycd7dcXd2cHNybmxudWdqZ2hzdnRuc25tdnBqdXVtcG5wcHJ4gGxpeHBwcHJwdnxzam5wdXNxcGtuenV1cnd0fHF+dnd0dXd3enZ4fn55enl9eHh3dHRxd3BycWrUe3bh6tLi43qAfHhsb2xscHGAenN7f9Zucal1cnRzaW1raGdkcG1wbnpwzGhse25vam1xbXBuZ2psatNrbm91bW9ycdJ433vheIHO3OXWdtnb23WGcdhpyM1rbGlkacJkY8rWashubnJ4iORxdXXU1dRrbsRnb3RpaG5laWtqZGllYl9pY7PUxcvQ28vOyMbKkHWAlInSyWnKaWtuZmrLx29obm9ybW1txdLO13rYyc7Wy9DQ0NbP13ThdYqYdnPb0djY28zLz+J1hujc0unjzdLW1M7Lxml/dsjAwcVs0nHb0tPV39jQ0dzTx8zfztfZ3+bf0tHUzd7Z297f1dDW2+ja6umWeHd1cn97cG9wcHRwaW+AdHHUcdHX193PxtBykXTMc5a3fHTgdXHOyN7f3djh23DZ2s3R49/c4eLs1eV13eXqdeV53efm6Hro6H7r5eTbf3vouuN16tJ4guHzgImA6ezp9nzue+bleODdcHVxcG5wc3Ft1WxvbWzTcG5ucXZycpHb2Gxwc251b3BzbW7YcOOA8PPw7vZ7fnt+fYCAgPF8gaCKgPPw53+A9vJ/fXuAf++Hg3h7end5e4CCfH5+fXh5eXuAgoSBfn54en99fXx4f355eXx6gH5/en17fICBe359go3SfoCFgYGHhoOFrZeFg35/hIWEgoWJioSFhH2Hh4OIiIyV9X+ChoODhICLioWAlYCDhH+FfX6AhoWIg39+fe9+gISBhn5/e3d/gYp+gX9/fIGFioF+fX19gIKJh4F/f3uChX1/f3t/gISC+oCEgIWHhoCAg4WFgnqHhn6AhYOGfIKBgYF/doKDhX99f4KAg4WChoKChIKVjJOBiIaHhIGDe4KQhoSChIaCgHyFfoyAqY2KgIKDfn1+goiBg395foaFg4SYfoyDf3yCf39+gIOFgYKBhISHho6UhoWDg4aBfZKKg4KDiIWFjImHh4aBhYaEiYZ/g4F+goV+hH54fpuphYmAfH2Bg4eBhIKCfISDg4KRn4qLhoCKgX+EgIR3en+Cf4SAg4WHgH6GfoKEfX6AfX+AhIaJjIl/iIOAf36DhIOGj4aDgH6EgoOGfYB+goeBi7aEf3t7f3+Dg4aAf36EgoOPh4KKgnt3fX6GhYWJhX96gYCAgX6BhH19eX2Ae32GiIR9fIF+eX+Af3x+hH99dXN+g4qFgX53enZ7e355hIJ3f3+He3F/hal1eX6Ae32Ag4SHiYJ+cnt5gn5+eHyBf31+fPTsd3x/d3p6d3V4fnN0c3iCgIB6fnd1fXZ4fXp3eXh7eHyCjHl2hn57fHl6gIR9c3J0dnR1c25yeXh4dnd5gHl/d3d1eHZyeXd3fHx4eXuAfXp/d3Z5fnt8fXfpgXrl7tvn5Hl8fH51eHZ4fH6AiXqBh+Bzdr58e35/eX16eHl1fXx+f4h963p7iHt7eHd4dXl1bm1vbtpuc3B0bnBvcNl343jidXrU3eTZddbf4XiMduFy2t9ydHhyd9pzdeDndd90dHV5gt1ucXHQz9Jtb8tqcnZtbnFqcXRzcXZxcmx0b9Hm29rW2tLWzs7LjXSAjoLWzW3TbGxwbHLX03ZvdHRzcXF30tva4Xzg1djm2dXX1tjQ23LfdIWQdHDXztPU2M7Lzdlzf97W0t/dys7Y1NfZ1nCGfOHP1NN13HLXz8/b497a2eXg1dbk1N3W29/Z0Nbd0t3a1+Lf29/c4eje6OaTd3Vyb3h0bW9ucHNubW+Ac3Laddzi3N7e4+N3uYvddpS4f3fhdXXg1Ofo7eXt6Xru8eXo8e3q8PH04+x76+nseup85unq6Xnk5njl4OfZfHrrquN69Nx7heHtfYN74+js9XrweOjld+bgdHd0dXR3dnV17HN3dHXmenZ3eHx5eaHq53R5fHZ9ent7d3frefWA/P/49P6Cg4GBhIeGhfiCiraVhvz36IGH+P2FgoCLhf6PkYSEh4WIiIyRiIuLi4OMh4mOkJKTi46Hio+Li4qGkpGLiYuHj4yOhoiEhYqLgoeJjpvTh4mSiomRkI6QvaWQjYqHjI2KiIyPkoqNjYOQjYmRj5Oc/4eGiISIioaPkY2ApYeJjIqQhoaJkpCSjYiJiP+Ii46NkoyOioWOkZmMjomMiY6TlY6KiYmJjJKamJCKjIiLjYKHg4GBhIiG/ISKhIyLjIWEio+Rj4aXlIqNkY6UhoyOjIyLgJCSmZSOkI+Lio6KjIaIi4ifkZ6GjIuPjIyLhIiXj46Oj5SMiYSMhJSAuJqWiouPhoiKk5mVlZCIkZucmpqzlKKbl5OdmJiXlZeWj5KRj4+QkZmejY2MjpSNhp2Xj4yOkY+QlpSSkI2KkJWRmpSNkpGPlZqTlpGKka69nKCXkZCUnJ6Xm5iSjJKPjo+cp5GRiIKOhoaKiI2Ag4qSkZeSlJSbk4+bkZOXjY6AkJCTlpifn5uPnJWNjImQjoyQm5KLioqQjI6Sh4qJkpeVocmdlJCNlJiZlJuTkJSXlpqrnJKilo2IjJGZmZedl5GMlpiZm5aWnJSUkZSYkJOYl5WPkJSNipCSjIuPl5KMh4SQl5+ZlY6MjoqPkJSQnJmOl5efjoCTl7CBi46WjI2AkJiYmpSMg46IkY2MhIaLhoSFg/38gYmNhYiGgoKGkIGIhIyUmZiQl4yJlouKkY6KiYuNipCYoYuLm5WSlJCVmJ2XjIiLjoqKhYWIkY+NjY6OlI+Yj46Li4eDi4mLkJGJi46Tj4yRi4qJjoqLjID3jYP1/uj7+4mPkJGFh4SCiImAoISOmf6Bh+iMh4uJgIeDgYSDjYqMjJmK+oSFloWHhYaIhIqIgYKGg/+CiIWLg4aFgvaJ/ov/hIrr9PztgPH6+4OTg/qB9/uChYeAhvmAgPj8gPeEgoWGk/2BhYn88/uAgfKAjJOKipCHjpKOjJCIh4CIgen57+32/vX+8vn7q42AwbT/9IH+gYKEgob/+IiBiIeGgICD4PPq8oXy5u746+zx7fDn+IL+g5mmg4Dx6O/v8uTh5faAj/ny6vz95+338/X484Gdjfvt8vGB+YP87vL3//rz8v7z5eb77PLx9Pnw6Ov05vXv5fLy6ePq7vvq+fmWhYOEgY6JgIGEgomGgIaAi4T7h/z9+Pzy7PCBwpPtg6TKi4L3gIH05/r2/PX694D3/ezq+vH0+Pn97/WB8vb3gPiB9/r79oL19oL47/jpiIT8w/OA/+eEkO34hI2E6O7y/YD/gvj7g/74goaFg4CEg4GC/oKEgoL7hYGChIeCgqr4/ICDiYKJg4aFgYD/gf6FfIh9AXyFfQd8fHx9fXx8hX0BfNV9AXyafQF8q30BfP99/33YfYJ82X0DfH19hXyOfQF8k30BfI99AXyIfQd8fXx9fH19hHwLfXx8fH19fXx9fHyFfQd8fX18fH18hX0KfH19fXx8fH19fJF9i3yEfQR8fH18hX2CfIh9hHwBfYt8An18hX2JfIJ9jHyDfYR8A318faR8kX0CfH2HfAR9fX18hX0DfH19iHwBfYx8B318fHx9fH2EfAR9fHx9hHwPfX18fXx9fHx9fXx8fX19hHwIfXx9fHx9fHyJfQF8hH0BfIh9gnyKfQN8fXwCAgQAgHro6N95fX15fnt/f3h7fXl2d4aAfXd9eX6AfXp5fnd9hnvkdnd7d3Z4gXxzdnZxdNp1fHF3eXJ3b3RucW9tcXlzc3uAfoKAe3V24XHec3N1dnd833V9hHGOjXfKa3d3cG1vb3FvbmhwdHp6dn15gn51fXx+hIGCiX+Ad3uCkYV1gH9/gIGHh4h/goB8e394en2Bgn6Af4CIhIV3eYKCgoCCf3p5fXp4eHp9cHp7gIJ/eH+DfH15fH99fXl6fXZ/fnXhfIJ6g4ODgoqTiH5/eXZ1g36AeX16eXZ3dn58gIF0goiAd36Dfnmvg3V6fHuAfXl8dXV7dX90e3p8fH+ChIGGgHyGjIakhO2Jgnnqg4SHgKB+gH56gn53g3t9foOBgISKgYOIhIqMiIOGkYeGhYqAfXt+eoCAgYR+doGDgHqRW2p/e4iCen97fHd9eXV5dXV3dox4fHd1eHyJhXZ9enl3cX6FeHZ/bnZ3fnx6f4F/eHR5eIGMfHd9foKIj4J8eYGKgIeKhX+Bh4WGqJl/fH95gXx3e36Afnp2joN+doGYjHZwdH+AfnV7dnWEg4N9iYZ/fn1/fH+AgpGXg6mHgIKAhH2AdnJ/mX19eH5/foJ8gIKCin+FhYaHkbSAf4GEhHuIfXp0eXp1fHFxdXR4f3Z9fnx4d36MgYOHfX11c3V+enJygHp/fIKDgXx1gYJ5en98fpyBdnR0cG5t13J5dG5zdnd3c3R3cnp+eW56e3R6bHl4cm9sdHRucW9rcXJ6enh4fHRzZm9vh391c3ZufX6Bf3aVcJWQem9sbG5rcGx1gn11d3F8enJ1bHV1f3xzdWtra3B0aWpra291d4F/eHnQbnNugG50amVwdXmChnpzenJu1cbOaG9tcW+YkXR4gG9wcGlqamxjy295gnBzcHR0dXPVb9zK09txb3d4cnnVydPRbnrg0nLgbnDX0GnNvGNhYrbHdGvOamxucOTWdNZx1dnacHRwamxsbm9qY2hlaGNebnBVachn1NXJxWzPatDHybvJgMtqcGxwacNmxbnBZmrD0MvZ2draztbHxdLLwsHP0cbK09fQz9nf2trZ4Ojc49nj5nnh5np7e4KN4ODn6d/geXx11M/GZ2hqa7+/yLzN0s1yfm/a3NLRz9Hi1uLs6+jn3/SA1tfazs3ZyNbMw8/Qz8nWcdjW3XBy4eHhd3PRf3Z5gODZ09DC1XXV0MPJ2NzbyW56bnFxdnx133jIh99ycJR8cNzO33Pa3tl04Ofed3nV7uPr6ung4OTZ6Hbtee3Y4dp6eKJ99PzRkZPh5PN8fnt35XNykXh2cnV1ccza2G9raGxobGtqbXt/fnfZc9t0dnncdnd2dXRwdXnWdHNz23V4gHvs7OV8gYB7f3x8fn58fXt5eIJ+fXp9eXt9enh0enZ7gnrleHp8enl9g355f3p1duB8gHqAgoB9en16fX17fYF7e36FgoSEgnyB8n/5hIKBg4OF8YCFiX6Sh4Tuf42JhoKGhoF/fnl/gH+BfoF9g396fXt+goKDiYCBdnyCkod5gH+AgIKGiImDgoN+fYB6f4OHhYCCgYGJiol8eYCAfn+HgH97f4B9f4KDeYCDhoqKgYiLgYN/hYWEg4KEh3+Jh3zwf4R+gYKEhYyQcoWGgn58jIiKhISBgoGAf4J/gYJ1f4mFf4SOgoCminyAgoSIg36EfoCGgYd/hYKEh4iJiYeMgIGJjYmkhfiJhH3whIWKhJ+CgoaChoSAi4WCgYWEgIOIhIWGhIeLiIOFlIuLjIyEhICBgIaEhYmEf4mKhoCmh4GAfouGfYSEhYKHhoKHgoKGhJ6KioaFhoWRjoKIiIOCf4iJgICKfoWDhoWCh4qGhYCFgoaXhYGFgouOkYaAgoeRgIyNioOEioiLqpqDg4N8hYJ+g4SFhX+ClY2Igo+vpYR7f4qFhYB/fX6GhIWGi4uDgoGAf4GDgI6PgqWEgIF+hH+De3aCpYGCfYGBhIR9f4GEj4GFgYOBjKp/fYODgXuJgIF9goWEiH18eX+AhnyCgXx5d3qHgIGDfnx8en2Cgnx5gH6BfIB+f315hIV9fIF7gK2HfH18ent77Hx/f3d7gYOCe3mAfH+Af3l/fnh4cn59fXt5gX56e3p2eHyBfHt6fXZ2a3JzhX11c3ZwfXx/fHeTdZ6cfXZ2c3t0enl+h4J5eXeAfXd5dXp9hYF/fnd3dHp7dHJ0cXV3eX9/eoDbd3p3gHt+dnJ5fH55i395fHt25drldnp9fnyFjoGFj3h4eHNyc3Rv3HR5hHJycnh2c3LdceHT1OV3c3d5dnfUztTRbnbd0nDgcXPe2nHd1XBxctbdfXfldXd1duPVcdNw1s/Ra29raGxvb3JvaW1tcnZxgoSBg95w4eTc2HTbct3Q0snRgNNxcXB1cdVszMbNbnDR19Xb4ePk4OTT09rb09Pf5t7Z4+Ta2uLk4OLY4N7W19HS1W7S03JubHB3x83R1c/MdHhx2dTWc3V3d+LW287c3t14inPc49/e3M/c0Nni49zb0OR92dzd0s/b0d7Z0tba1tfbc+Lb3G5v19bXc3LLcnF0gNfa2NXR43Tb2NHR4eXk13R4c3V2eHx66H3Eiud2dpOCd+va6Xbp6uZ64evneXnY7eru7uzi5ubb5nnxefPf5d16epN68vO9i4vk4+97fHx77HZxjXhzcnV5duPu5nd1dXp5e3Z3dn6DgX3lduR1dXnjd3t5eXt3foLoent763t8gIf9/faEi4qEiIWIiImHiYWDhY6LioSLh4eKh4SDjIGJkIb5gYaIhYaHkoyHjIeEhP6Ij4aQkIuLh46KjY6JjZWMjI6VkJKRjIaJ/IT8hYWDhYiL/oqTmYqbd4/9hZWPjYaLi4aGhIKJjIyNiY6HjouEiIaGjIuOk4yNgYiQoJaFgJCQkJGYmJqRkpKMiYyFiYySlIqNiomRk5KCgomKiYeQi4aEiIqHi42PgoyRlZqWjpaZkJSQlJaSkYyQlImVk4P8h4yCiouQkpuljZOUkY+KnJaYk5STko6KhoqJj5CEkZePho2YjojAmIOJi42SjIiOhIiNh5CGjIqNjpCRko2TgImPl4+kiv+NioP/jY6TjKWPlJaPlpCNnJSUl5ycm5yinJ2fmZqZkoyNmI+RlJiNioOHhIqKiZGOhI+PjYfR6rSLiJmSipSTlpKYl5CXkJGVk7CWlpKPkJKfnI+WlJCMiZWXiYiSg4aJjo6MkJKTjoqPkpiolJCUlZ2hqJ2RkZSigJyempGRmJOYxa+VjZKJko6MkpSTkImLo5yUjp3Ct5SJjZ6ZmJGRj4+fnZ2cpaWcmZibmZubm6uzoMmemZ2Vm5OYjIuYupmXkZmam5uVl5ubpZObl5mWos6TkZaYmZCekZOQk5iRl42JiY2Rl4yTk42Kho2cjpGUjpCJiouSj4mHgJCWkpiVlpKKlpWLio6HjLuWiYaHgoKA+4eJiIOIkJGRjIuTjpSYlI+VlY6ShpKRj4qKkY6KjoyGjZCYlpSPlZGNg4yLnpeLiYqHkpOVkImwgpWXkYmFg4mBh4qRnJaKioSSjoeNhY+RnJeSk4qKh42Og4OGg4aHiI6OiY/4iI6KgIyPh4CLjYtyio2Hi4iE/vH9goeKjYyao5Kapo6PjIiKhYiB/YmNmoOCg4eFg4L7gfzw7P6HgoeHhobs5O/tgIf67oH+gYX9/ID674CAgfH+jYb+goWEg//wg/KD/fn/hIuIgoiIipGNhYuJjY+EmZbepvWA//7y7IL2gPbp7+nygPqEiISKh/+C++n0gYLx9fH7/v/28PTe2ufl29bo6+Xf7fLn6fL18fn09fzy9u/2+YP39IWEg4eU8PP4/vLxh4yA+/fzgoOHh/729uf59PKGnYH4//Ty8eTy5PT8+PHt5PeD5+vr3d3q3/Hn3/D48+v2hPv2/IKE+/z8iIXui4eJgPz48vHm+YP58uno+v7974CIgYGChImH/YrOkP+CgbaVg//u/oD5/fOD7/73gYHj9/T6+Pbr8fHp+oD8gv/o7umCg6eC//zEkZPo6vaAhYSD+oGAoYeCgIKFgvT//IaBgYaDh4CBg4qOj4n+gfmBgYX3hISDhIaCiYv7hIaE/4SJBH18fHyefQF8jX0BfJl9A3x9fIZ9AXyFfQN+fXzkfQF8iX0Bfrt9BXx9fX18s30Cf37/fdh9AXzkfQF8in0BfoZ9g3yFfQF+jH0BfIp9Anx9hHyGfYR8FX19fHx9fH19fHx9fHx9fX18fH19fIR9CHx8fXx9fHx8kX0Efn18fYR8A318fYZ8hX0HfH18fHx9faN8A318fIV9hnwGfX19fHx8hH2HfIN9j3wBfY98D318fHx9fXx8fH19fH19fYZ8AX2IfIh9BXx9fX18hX0NfHx8fXx8fH18fHx9fYt8A318fYR8hH0IfHx9fX18fHyEfQF8iX2DfI19B3x9fH19fXyIfQd8fX19fH19AgIEAIB0cnbYd3l7b+R1vnqAe3zo3/B9eI154+fge+Todn146Hp8gXp8g3p2enp4cHV0iKh5gWl2eXp4eHSTeHJzdXJybW11c2xs2nh1eHZ4eXF6eH1qa2vGb3JxeIp5eHR6fXiCgsGAgnxzc3Z4dnV+nX99fHt8eoO0iHx744qJf3d5f4CGg4GEg4h+goeGiYeAfX53en2GhYiglH5/foB7d3Z/gHZ3e4KIinV7e3h3dHtxenpzfXt1dXd+eXd5e4F9e3p/gHuCfH6ChZCUiZWEfX13c3J5dLKYdoB+fYSIg3iAhIWGh4SCgIB+eZNyeYGDhn51gX98hHuDhXl+e397hIuCeYB7enuCdnl4d3l3fHl3dnVwdHF8f4Z8g3WAfpZmeYKJiYqvg4SHhIWHfoWKmH135np4eX2UoYWDgnt7fYGDf4SHioCKfHt/foB1cHyAeHyEg4eEh357eoCGhYB4eH/FkXh5inl7h4N5hImChoV/d3SBfICRgoB6fJqFloF+gHZ7joCNhn+Af32GhoSIhoWTj4iFiI59foB8eomBdnp4eXB1f3t2fH+CgXuBgoaEgXiJe4B/gHh2f4J+f4GIhYaIfHuCf359fnd5eYR0d3l/d3l+eXaShIGEgYSJhoOEj4eJgo2cfoF/dX93eHRzdHt/d3R5fXt4eHZ7d4R8fnyChnx3eoB5dHB3d3J3d3J+fn6GhoWEhfWDhHd8gnZ9entyeXx4gn2Jjn17e3xyeH19f3h0dHVwen5ydXJsant8cnh1b356d2ludYN1cniBg4V/gX95h3Z5c3B0d3JtaGdvfHByd3hzjpF0xm5we5+sc3tvbHJyd21vdHN0eXZpcWZlZ2pmbIBvc3dyd3R3c3N2b3TYyMjIbclwa3F7eHJybWt1cHBucmltbmlweG92fIJu2uHacXN4fO3lf3fg5uPr59/c43d423d0dHN133XYcXrKy9JnadtrdGppbHB3bW53b29ybHJ1dHNuaHFoeWxpbXpqx8poyGls1tLU5dHa2dfK0Mxx0YC/w8huf2xpyGjKZ2dlxcJ3pXxzb8S+wcHMztXM3t/h5und0NTj5XnU4Ovkc3Xd09ZxceJ13tN2cm/W19PUzMxzcWxva21q0tLB0GlnasjI03HLz8zThNnY2tje0dPf4t/k5OXh2Nvf1sidbsPGzm/b2Nfc3ufb1nHX0nVq0qV1dCDYbWnLv2rR0M3AwcLJzm3Q2W51ct7e3c9zeXx0cHN1d4RzXHXacdhu4HTbcXp95eN77urh693me3706fmfpe7ufvnx6uaAfHnecHh4eXR0dXNydnXidtVrbWxqbWxxdWxxdGtt2L+NeXFxcuJ45XbldHftdut9d318dXl3enhvgH57fPGAg4R68XvafH9+fvHu8314inrq7+x86up0enbodn5+ent/eHd/fXl1eHqKn36Edn5+gYGAgJyGgIGCgH9/fISEf3z8iIGCf319e4WBiHp6e+Z8fYGJloWEf4OKhYqR3YmGhH1+f319e4GpgIOBf399hcCJf37tjot/eXyCgIOEgYSDh3+ChYSIhYB9gHp6fIOEhpqRg4WDg39+f4aGfn2Ch46PgIWFhIJ/hYCGiICHhn1/f4eFgYCDhoOCgoWFgoZ/foGCiY2CjYCAgYJ/fIB8s5t8g4F9g4iEfYGDg4WGhISDg4N7so57g4eKgXuHhIKIgYWKgYWBiIWJkYuDgIGDhYl/goF/g4CHhISFgn6BfIWJjYKEfYaDnnR/hYuMjbGFg4mGhYWDipCghoD8iIOChJSZiYaFgICDhomGjI2OhIyBfoeIiYB8io2Gh42PkYuMhYCChIqIg32Ah96ff4KOf4GPjYOLj4qMjImCgYyHiqOTj4aHoZKhi4aFfH+igJWHgYWFgYuJhIWDgo6Jh4SJjYGDiISCkIqEhYKFe3+Egnx9f4ODf4ODiYSAeIl6gYGBdXZ9fnp+foOAgYV8eYGCgYCDfH+Bjnp+gYl/f4GCfZmHg4GBg4SCgYWKhIKBkaKBgYSAhX19fXt7g4N8f36CfXx5dnx5hYCFgISHhIGDI4mJgYOEe396d3x7enx6gYGD73+If4GIfoWChHh8e3d/foKJhHqAd3l/g4J+en18eX+Ddnp2cG9+gXZ8eXOCf3txc3WDeHF0fYGAenx5doF3fHVyeXl4dnRze4N6fHx/fo2Medl1dn6UqHt+dHJ0d3Zyc3h2eHt5cHpydHVzcnR2dXRzeHh5eX6Ae4Hw5+jqe+d7dnh/e3NycnF4cnVydm5ycm1xdm+AdHh9b+Hf4HN1eX3n6X544OXp5+Tc2d91c9hybnJydeJy23B71Nrabm3ddHpzc3RyeXBvcnJtb21ucXFwaWp0bX91cnaGdtjecuJzdd/Z2unb4dfbz9nXguLQ2eF3f3Vw4W/ecG5x0tB5q357d9zOz8vW2Nzc5Ozp6ujc09He3nSAztPb2G5u08fNbXHddd/UcW1x4s3Y1srRcXNxb2xxbtva1t50cnLh2dx42N3Y5ZTf0dTY5dLW3+Li4t3k6uPq4+DWmXjZ3+J05N7e4eHj19Fu1NN3dt6teXnieHbg2nXl5ufg4N/k6Xbc33N2c93Y2s9ydnlxcHJ0dXRzc3V14XRZ43PieOV2fX7q5Hrs5eLx4+V5evLn97zB7ex78urm5X5+eeJ0d3l7eXl7e3l4eep56HZ8fHp6e3t/dnp8c3Lfo4d8dnh363vldOd0d+Z16354fn98gICCgXmAhoKD/oiLjYH1gNWChYSF9/D9goCTgvj79ob6/IGIgv+DiY+IiI+EhIqKiIKHhp24jpSEjo6PjoyNrZSNjpKSjouJkY2Fg/+KgISBg4OAjIqRgYOF94eJio+Tj46MkJaSmpillJeVjoqPi4iFjuyKjoqHhoWM2pWGhfeYlYqDh49fkpGNkpKWjpGUk5mQi4mKhYaJjo+SoJmMiouKhoiEjIyGhIuPmJiHjIqMiIaLg42Ph46Ph4eMlI6MjpGYkpGOkY6NkoyKj5CZm5CckIyPjYiIjYm+q4eRkIuUmZKIi42EkYCQjZCOicKbio+Vl42FkYqKj4aIj4WJgoqFjpSLhYSEh4uAg4OCiISNiYmJiIWJh5GVnZKbjZaVr4KMk5qcnsqXmZ6ZmZePlJqtioP+hoSGiaCnkY+LgYOHjpCNlJickpyQi5SVl4uGlJePj5aZnZSWjImJjpWTjYeGjt6mhYWWhYCEko9+jZWNk5SJg4KSjpOxnZSMkLufsZ6WlIeMuquXk5STjJGWj5KKi5uWj4+WnpCRlZGSoJuTkpCSio+alZCRk5WTiZSUmZWRiJ+QlpaXjYyUl5WZnaKgo6iYlqCempiclJSUoJCTlJmTlZqWkq+cmZiWmp6alZigm5qUqMCZm4Cbk5uXk5CNjpSVjYqMj4+KiYaMiZSNkIyTl5KNkJOSio+UjZORj5SSj5CNkI+N+4mOh4uSiZSQkoeMjouVkZmgj4uOkImNlZaYko6QkI2UmouRjYeFl5yRmJOMnJiRh4uMmYuDiI+Uk42QjIqYiY6Hh42PjIaCgYuXio6OkY2tw4CL8oSFj7C/ipCCg4mLj4SGi4mJjo2CjYOHiIiEiIuLh4eHiIaEgomCiv7z+v2G+4yFi5iUi4mJiJaOj4qRhYiJhYqQhoqPkoD//vqAgISH/vyJgvP5/P368fH4hIHwgoCBhIf/hP2Dj/b+/4KB/4iOhIKChIuAgYaFgYWAg4mIiICHhZGEmI6Ji5uH+P6A/ICC9/Hl+ej38/Tl8e6F+ejz/IiUhID+gv6CgILv7IzdjoiC79/c2OLk5+Lu9ff19+3m4vb2g+z1+/aAgfbq74CD/Yb79IOAg/3w+fnq9oeJh4WAhIH/+/f/hYOB+fP8ivP58f6k8+nn6PHg6PPx8PPv7YD48vj69uqyhu70+oH89vn5+/769YH18YaB8a6Giv+JgvXtgPz5+vHw7e76gvH3gYWB/Pf37oWKi4ODgoeJiIWFhIf7g/uA+YT5gYeJ/PmE/vbx++vxgID97/7S3/T5g/7z8u+HhIXygIaGhIWCg4KAg4T9hv2AhoeEh4qNj4eJix2AgPjfooiAgYD+hvuA/YGE/4L/ioKJiYOLjIyKggR9fX18hH0BfIZ9g3yEfQp8fHx9fHx9fX18pn0BfI19AXyNfQF+ln0BfO99AX6xfQF+kn0BfP99430BfNJ9AXynfYR8An18mX2DfIR9BHx8fX2IfAN9fXyFfQt8fXx9fXx8fH19fJx9Bnx8fXx9fYt8AX2EfIR9CHx9fH19fXx8hX2SfAF9hHwOfX18fHx9fXx9fHx9fX2GfId9hHwHfX19fHx8fYR8AX2TfAZ+fXx8fH2IfA99fHx9fXx9fX18fX18fH2IfAZ9fHx9fX2EfI19DXx9fH18fXx9fX18fH2GfAp9fXx8fH19fHx9hHwEfX19fIt9A3x9fI19AXyGfQp8fXx9fH19fH18in0CAgQAgJh4dnh6dnh4d36Df4B733zt5nnsfHt+fHTic3Z4fn1+e4Z+gHvnenZ4e3V0dXJ5end1dnJwbHV2e3N4cm93gIt0fXV3d3p6enbe1211bm1yc3BucIRycoJ0enp5eHJtcnx3dHyBdm57fnh6fYGFhvCFf36Bf4qJhH19f494e4GAgH18eHp7hXl2e46PgYB7dndz5oJ/jJCKin17jXl45H18eXuHiIyOfHx+eXh3dnJ5g39+eXzfvXd5eYCLiZCDf4GDhYKAgYF/gYmCf4aIhYaCgnl1e4V7gICAe4CDgIKBfoGCg4GDwXiAfX96hIN9eIR/gHx/fYGFf4d+gH2DhX59gIuFfoR8iYWQhX98gX14hISAgIB8gH+Fg4mSj5CJhpOHjo6EjIeNk4eIj4d4fH16hn90eX+AgYCIeYCFhIB/gYCHfHp8dYGGenp4dX2Be3qBeHuAe318g3d4eIJ/fHZ5d4CCgImLjI3poI2NgHx4eoOAhoaGg4N9gpmNf3t2e3t/gIJ4d32BgoSLkZCBjYWDh4mBoIGHhX2AgXF0dnd4enqEg32EgIKIj5d5iXmAgoB4eHx6f4aIlOiDgnx8dnyWh399e3l/dnt5fnp5eHJ0eHp6e4Z+g3R8gH58fIF7goCFfXSKg4J8eHh9d3h8enJzb3h7eHl6dn56dHx+jYWDgYN4gHhveXR2dHNwdnd8eX6FgoJ4g4iMhIB9fH5/en6AfH19fXuFfYV2cGpydm9tcHZ4c21ub3d3dXVscHt1enp0e3V8eHB3h33feY19eXp7d31+gX2Be3yzmYN4bXt6d3iEc3J0cXZwbXx9eHJycXd3eHd3cXN5dnh4enRvbnJxgn1vgHXZbnFwbHBvcnlybm9tb3RzzntsdYhxcmloaWh1b3NtbnFvdnPe3Xl45ObkeuB63tvkeXZyd9/dbXVxb3F1cs/UxsnRb9rN39fg1tDTyslycW9s0G1u2dvXcd7Z6eV2fHp0bnTYbGxpcWvIbM/Oy9N1cHJxysvb18/CwsHIzc3HgMppxWTDZMPLasvDx2nOzczT0tjAyMbR0dPo1+Hi0tPX3t/d7++BfHzlfe1/eOfqeObb2+d2dOV6fnt8e+PjXXfdcnfC3+By38zM185zWmbSbnpS39rUcd5wccbG3NzpeHfW4d/R1tZxa8i9w8m/xGhzxsbOaM5t1tje18C9w9PIgMbFwL/Fx9Jubtdq2HBwbtBrcnDRbXNs0HWIgHdt2Hdzc3RudnRxcXBvb2ttdHNw2tx8eHl9eXrneImCfHx6eYaA5et16HbY2OHb39h6c3t5c9xw23BrcHF2cLBxb21wdnFtedDTdXRucHF0cnlzcnp2lHrT33J3dXp4f3x1dHWxgJh6dXl6d3d5d3yAeXt56H3y6nnte3yFgnrte3t/hYSDhIqCgX7pend5fHl7e3p/goB/g35+fYKDhYF/fnuBh4+BhXx9fIB/gX/08n2DfoCDgH9+fJiDg5uHiIiJh4F9f4mHgomKgnmChX1+f4CEhPWGf36AgYqLhn6BgIx6gYaEgIGCfYGDj4SChZaYioqFgoJ89oiIkpiTkIWGkIB+9YGDgIKKi46ThYWIgoODhYGHj4+MiYzmt4SDgYmPjJOJiYqMjYmFhoaDhImAfYKGgIWBgYB9gYyDiISFfn6BhIWFg4aKioeN2oGGhYeDiIqEfYeDgoGCgYCEgoOBgH+Cg398FIKAfYR9hoOLhoJ9ioSBjIyHh4uEhYWAjYyLhoOMgIeKgYmHjI6KiY2KfoSEhZCJhYOIjIiHkH6DhoWFhYmIjoaFhYGNkIaDgYCFiH9/hnyBgX6ChYd9gIKGh4V7gYCIh4aPkZGR6KWUk4aEgYWLio+UkI6Nh42omYqGgYWIiox9gIKEgYGHjIZ7iIJ+hH9+ln2Dh4OIjIGEgoCDgoiEgoSAgIaOlnyMhIeJgoB/goCEh4iP8oeDf4B2gKKMhYSFgomBhYWOhoiFgYCChIiGk4uOfoWIhIKFiIGHgYmBeYqHiIWBgoWEhYWEgIOBgoOAgH98gHx1gIOPh4eJioWIgISAg4GBgoiChHt+hYGAeIGGin5/e35/f4B9gICCgIB/f4mBl354eYCEfX5+goV9fn58eXx4eHFyfnp7eHR8eX5+d36Pg+1+lIF/fn18fYGCfX95esiXf3dteXl4eYB4dXZzeHlxfn52dXd0eXh8e3t2d3t4eHZ4dHJxcnJ/em514XJ1d3d9eYKFf317eXd+e+KCdX2Md3p1cXJxeYB1dXBxc3J4ctzdd3fm5ul353rp4ux5e3Z74ud0enZ3d3993uDW3d526Nff4uHa2d7Z2Xd0dXHcdXLf29Zv3dvb4nFxdHFtctxzdXB2c9Vx2dna33RycnLU2OPk5t3c2tzg5OHkcd1x12/Y2HLY1ddz3dnX4eDj09bW1NPS39LV1YDLycrHycvQ0W5qc8xqz25pzdRr09bK1W9w3nR6eHV53N1uetpzdsXj5XTi1s7d1oVzceF1h2Lf2N1043Fx0s3c2eV3d9ze5+Dr7Xp55t/d4NfRbnHW3uJz3HPn5e7r3tbe6+Xw5+Pj4ePgdXDfb9lxcW/bbXR02nJzb9Nzg3tyaWPXdHFxcmt0dHJzd3Z1cXR9d3fn5n13dnd2ctpwfnlzdHR0gHna4XXndt7q9e/69n15fHp47Xvvd3V4eoJ9wn5+fH5+fHiD6umAg3t4d3p5gXp5fXyfguTweX59f32GgXt6d7OAsYiGi4yNiY2HkJKLiob8hv72gP+Ag4eFgPWCg4iRkY6OmI+PjP6Lh4iNhoeJhY2Ni4yOjIqJkJCVkI6Mh5CXpI2ThoaFiIiIhf34goyFhYiIhYSEoIqMopCVkZSWj4qPmZOPlJaPhI+Ph4aIiY2M/IqDgoSEkJKNh4iMloiLlJOAkpGOkZKfj42Tqq2UlIqFhoH3iomTmJKTiIiRhIP9iYuIiZSTmqGQkJKLi4uNiY6XlpKOku7Fh4eKkpuYm5CRlZibl5CTj4qMk4qHkZGNk46PiYWKlYuNi42FiY+Ni4yJi4qPjZPSiZCRj4+UmZOLlpCQjY2Mi5OPlY+PjZSTj42Al5OKj4eTkJmRjIiQioWWlJGRkIqNjJKTlp6bnZaTnJKam5CWk5ygl5eem42QkI2YkoeGi4uMi5eDj5WSjoySlJuRj4+Kl5uPjYuHjpKKipOKi5GNjpSWioyPlZKQhYyIj5CRlpiYlu67nJ6Pi4WKkpCYm5qWk4yUr6SVkIuNk5SAlIuLjpCMjJKZk4SWjYmQjoyri5GSj5OWiouOjpCOi5SPi4+Mi5OVnIaXjI+TjoyOjY+Unp+r95+bk5OKl7Whm5aWk52OlZSblJeSjJCQkJSVoJebiI+RkI2Ok4uVkJeRiaKdnpmZm5qXlpeWjo2KkZKPjo6LkIyEj5CdlZSVlY6AkomQjpWSkJGXkZSMkJqSj4KJj5WLi4iJjY+MjZOSk5OUkp+WppGLh5GTioyOlZePj4+OkJSRkoiIl5SWkImPjYyNhIqbjfqFl4qJiImGjJGQkZWQkuy3l4yBj4yKi5WKg4WFi4iBkpGMiYiFjYmNi4qEhYyHiImLhoSEhYaTkYCAif+AgoGCioWLj4mHhoKCi4n6k4WRqo2OhIOFgY6JiIWEhISKhPr6hoX6/P+E+YX78v2EhYGF+PSAhoGDg42J8vPq8PSA/ezz9/v0+Pvw9YWDhYP8hID+/vqB//37/4KGh4WBh/6Gh4GJh/yF+Prx+4SAgYPp6vj28evu6u72/vyA/4L7gvqA+PiA8uzxgPby8fr4/Ojt7O7p6vzp7e7Z3+DZ4uTy94GAh/eA+4eC/P6B/P3z/4GC/YaLhoOI8/KEiveChdf4/oL+8On57pmggPuBt4v8+fWA/oCA6ub8+PuAgOr1+PH9/oWA+u/1+vX7g4r9/f6A9YD7+fr549vm9e6A+PDm6+7z+IKB/ID5goSB/YGJifaFhYL5i6KWi4H9kYyNi4aRj4uLjIyKg4SPh4P+/IqCgoKDgvmAj4qDhIODj4r7/4D+hPLy+PX//oeBhoSB/4L7hoGGiZGJyYuJh42QiYaP//+NjYWDhYaCiYGAhIGriO78gIaEh4SQioWFiMyOfQZ8fXx8fXyFfQF8i30BfKN9gnykfQF8oX0BfIt9AXz/fct9AX//fd59AXy6fQF8j30BfJN9DXx8fX18fHx9fH18fHyEfYJ8h32FfAF9inyEfQd8fX18fHx9hHyGfQF8hX0CfH2EfIR9jXwMfXx9fH18fH18fHx9mHwLfX19fH18fX18fH2EfAN9fXyFfQt8fH59fH19fHx8fYV8Dn1+fXx9fX58fHx9fH19hXyCfYZ8gn2GfAh9fXx8fH18fZB8EX19fH18fX19fH19fXx9fX18hX0BfJF9gnyGfQF8iX0FfHx9fH2GfIV9A3x9fI99gnyOfYJ8i30CAgQAgIHd2XTXgHx7fXt1hId7eICFe315fnt5d3t8eXp7gX5/fn16enxzfHt6eXR5e316cnlyd3Zwd3Nyd3KCd3R5fHZ3e3V1dn2Ol3l1f313dHFtdHt5dHV5fnV3enV/c214gHd8en2KoYCCiYWMi4WDf4KCgYWAfn+Df3yAgIaCf4B7gIWAgnt8i3V9eXl8dHaCdXl1d3h4enh4c3l7fXx6fH16fH+FfIeFenh6eIB6eHx8cnp7fIB+cXN/gISGpYDmfXx9d3p+iIeBioJ7gYp+gnyAiX+KhYKDgIaBgIJ+goeFg4V7gIV0dYZ8fX97d4iAgYuJgoGDgIaJhYiLioyIh4SFgImCgYB9ipKjkIeAfoWCgX6DhX6AkYSEhoOQiYSJjJCTioWJiYyGkoyHg36AeXh/gn6Ei4KJiH6FfnyBgnyJhYGGfn+Jio2Dfn98hIWCyJKFi4aRjI+GjYuZjZGDh4eajouGh4GGiXuAfYqdf4SGeXxzgHd5e3l3d36HgoGGgHqAgH+DdXt9end8hoKAiIyLjoqJf4GGhId+dn+Mhnt9e32AiIKEhYGCfIB1e46He3l8dXh4f4OHf4R9eX+CeYB/f32AhoJ9l3d3end3d3x3fnl0fYGJeXl+jH94dH6DenmBdXKKfHl6dnh7e3Z8eHN4enh3cnR6b2tzbXZ8iYF4foJ7gIZ7g3V9eHxya3Z4c357c3F1eX19f3p6e3t4e39+hIB2dHFvioRub2dxfHNucnZ6c2xub2txcnZ2j4uBe4KDhH58gYDrdYGKeuJz4HV3eXd0doKEc291f357dINzdHN2fXp8c3d5f3l3eXN2enR9e3hzdXZ7eNXUd3V0cHpzynJygHFudWxxb3Fy0mxzfGxxbc91fW910nRzdXV2enx7e3OJgHx6euh6eHfc6NzZdNtv2W/RcG1qbdjR0W3Y0m3Q1dlr087LynXN19NwcnDRw2vU0dRrcYx9ctXaddbVdNzZcN9xcHHMbm9qamrFzmvF0M5vx9DW2dJt1NDSbmrHxcbNgMxt06jSw8vJx8nNydLPyr69yMRx09fc0tXW3OPa1OHa3Nec0XV1e9956ODqeHvn7eLY49Lxeed409Hh29LP29HW29Tf5uThz8bLzOLS2dR32G5wcNRqa9ja5Ort7ujj4ePkgHjb1txvx8XKaG5ydIusc3J2cNPOytnW2dzF1trhgHfZ3OLjz9Vta2xvbM5paX+AbdRzc9Xa23Jz3d9wd3Jzb3BvcW/WbdJ0btvP0HNvcm/heOjk4XaCjHx+93rpfo2+fOV15uHc293je3h24ntzcnBxcHPLa394c3du0Ghra3VubGxkaGppanJzcXV3dnxu3nR1zt3i39LX3HXk43l8gIbq3XHbfn15eXh0f393dn2BeHp2enZ4d3l5dnh5fX1+goF+gIB3fn99e3d6fH97dX54fn58goGBhoKUiYiKiYOAhX99goasqoB7g4J/fn5+fYOEfn6BhX6ChoCLf3iBioKBgICKtoCChYWJhYJ9e39/f4KAfnyCf32AfoWDg4F6EoaDhoGCkoGIg4WJf4OOhIaCg4SCgIF9goKEgYGDhIGFh4uCjY2CfoOBh4CChIeAhIaHiIeCf4WGjZCoifeGhYR/goWNjoiQiYGKj4aKhIeLgoyJhYmIj4iGiYSIi4mJioOMi4GCj4WJioOBjoeFjYuDg4KAg4SBhIWEhoiEg4WFhIKDhIyUn4+FfoGHg4OBhIiFh5iJgIaFhI+Gg4KGiIqCgIOEioSOiYaHgod+fISGgoaOiI6NhYuCf4aGg42MiYqCg4qNkYqFhYSLioPGj4eIgIiBhH6BgouGhn+DgpaKiIWGg4iLfoSAkJuJj5GGiYKPh4mLioOEipOLiYyEgoeIioKEg4GBhIyGhIaKhIeChICCh4qOgIaBi5aNhIeEhIGIhIeGgoaCi4SKnpiNg4WEhoWLjo+GhoOBg4d+g4aGg4qOi4ajf4GGgYB/hoKGgoCGjo+CgouSiIKAiIl/gIl/epKGhIKBgoWEgIKAfYKEg4F9gIR9fYN9goSLhXyDf3qEgIJ6gYOIg3yCg3qHhH58fHyAgISCgIGBgYB+g4WIioB/f4Cal316eYSLg3+Agoh+eX98d3h6fHySjIh+goOEf3+EhPh+hIuA83v0fX5+e3l7hIJ0b3R5fXp3gXR3dnh9fn54en2BfHd4d3h6dXl7dnF0dXx32NZ3dnVyfXnbeHd0dXp0eHV6eel3f4l5eXbgfYR1et12gHh5dXl6enl1cYB5dHR03XVzdNre3eF15XTjduB2d3R26Onod+nqdejl53Xk4Nzbft3m43h2eeDQdOXi5nF3in513uF339tz5d5x53RzcdRxdXJzctvdb9LY2HTV3OTe1nDX0dZwcdjW1+Dhduix5Nnm5OLm5ubp5uDU0NfSdt/ggOPT2dTZ29LQ1dfTz6jIam961HTV09NrcNTd2NbRzd505HLa2Nvi083e1NjW097n593Vy87R4dbf3Hnbb3Fw2Gxr1dXb1d3e3dva4+N8eubk5Xbc1+FzdXx8kLF2eXh03tnf4t/b5tHY2+B12tze5tvcc3Bzc3DfcHCKjXDcd3nfat/acW7Z2Gt4bXNvcG9zceJ06H135N3ieXZ6euh65uTpdnqIe3npdN13hK5z2nHf4+nv7ux8envufnl7eHp9geB1g315fnfidXp3g3x7fHZ3eHh3f358foB+gnnvfHzm7fX16+vyf/X3f4CAkP/xgfaRj4qMioiXl4uIkJWMi4mNiIaGiIiGh4mOjo+QkY+Pj4OQkY6NiY2Pj4yGkIeLioWLiIeOiZuSjJaTjYuSioeLk9vHioSRkI2LiYqLkZCMj5OYkJSYkZ2MhpCajpCNj5/si4qQjJGPioeDioqLjImLiY+KjZKSmJaTk42AmZWYkpGhjpGOkJWJi5eLj4qKi4uMiIiEi42Pi4mOjYuNkJSNmZqNjJCNlZGRlZiNkJCRkI6JhYmJjY+kif6MjIqGiI2Zl5SdlIqUloqQio2SiZKPjI+NlIyNkI2Rl5OSlY6Ul4yPn5OXlJGLnpWUnpmOj4yKj5SQkpSVmJmXlZmAnJiVlJOeqbWgl4yNk4+QjZOTjZCikI+NipqRjI6VmZqRjo+Sl5KclpSWkJSMi5SWkJmimp+cj5SKh4uMhpaPjpKHiJGUl5GKjYyWk5Hpn5KYj5iSkouPkJmTlYqOjKSVk5CSjJKWhY+LmayRl5iNkIiYkJOUlI6QmaWdmZ6VkZSAlZiNjo6LiY6SkY+Vm5eak5WPkZiZn5aPmKKajpCLjY6WkZeTkZWPmo+WqaGWjpWPj46VnaGWmpWTl5yVnaCcmaCkpJ3CkpKYkpKRl5KUko6YmqGRj5ailY+Ml5qQkZyTjqicmpealpuYkZOOh4yKj4uFh4+HiJCKk5ahmIuSj4mAlpCSiZKSmpGIkpKKm5WKhYWIi46Tj5CSlJOTmZ6iopSRkI6pnoaHg5CYj4qOlJmRjJOSiJCRlpOspJyTl5aVjYmMjf+Di5CC+4D4hIeHiIeKl5aJhYqTlJOOm4mLioySj5CKjI6UjYmMioyQko+RjIWJi5GN/vuLi4mEj4n0i4qAh4WMhYiBh4j7gIiUhYaG/I+Wg4r5iIiKhYmMjYuKgpiOh4eH/4eDg/f+9/eC+oD8hPuGgoCA/vX8g///gf3+/4D8+vL0jPX89YSDhfPkgfv3/4CHopCF9/iF+PKB+faB/YWCg/eEh4GDgPP7gO328oPq9v3++IL68fOCgvb19fmA+ID7svbn+PTy9fn3/PXv4d/o4YH08/bk7u3z+vTv/Pn49cTxgoWP/oj+9vqBg/n77uzt5/2C/oD07fX97ev76+/w5+/9+PPn3ebq/ev484v9goWI/oKB+Pn9///++Pbx9fOHhPb19IT5+/+ChYuIoNWJk4uB8urw/Pjz+9/o7faAg/T29fnx8oCAhIeB/YGBqK+D/oaI9/39gYH+/YCJg4aAg4KGg/+B/4uC/PT2hoCDgfqE+Pf7goWRhYX7gPKHlsWD+IH79/v9+vqGhYb/ioaGg4SGifSAk4yIjoT+gYiFk4uKhoCDg4CCiouIh4uIjoD5gYDn9fn46+3ygf3/iYoFfXx8fXzBfQF+9n0BfP99/33/fYh9AXyEfQN8fXyqfYJ8hn0BfIp9AXyGfQF8hH0BfI99BHx9fX2EfAZ9fH18fXyEfQt8fHx9fHx9fHx8fYR8DX18fHx9fX18fH18fHyFfQ58fH18fH18fH18fX19fIV9B3x8fXx8fH2FfAZ9fHx8fX2FfAN9fH2PfAF9jnwMfXx9fX18fXx8fH19h3wDfXx9l3wIfXx9fX18fX2LfAl9fXx8fH18fHyKfYt8AX2GfIV9AXyFfQp8fX18fHx9fXx8iX0IfH18fX18fHyEfQV8fXx8fIV9A3x9fIR9Anx9hnwEfX19fId9AXyGfQF8lH0DfH19h3wFfXx8fX0CAgQAgHpyc3NxdHd2l5d6fI93eXt/iYCDfoB7eHyGfn2AfYV9gYF8enfoeJGCe36Bf4F8fH1/fYB3dn99enp9fHVxeXZ4fH6Ag4OIf4F3dn5+gH13hHp2iXt6dnl4coCEf31+e4GCen17g4R/goWCgIGDrs+Eh4V+gYCIgoV9goeFg4aBgIyDeXx+hH19gIV8dXx4cXd3dHJ6d3x4cnd3gH97iHx6g4mFi4uCgHx6dqKGgICDd4J/en1+gISAe4KIiYl+fXd9dnR4n4d6dn2ChHp8gYeFgYN+foOAenyBhYaBfX1/iYaJhYV/eHqFhIWBgIaNk46EiY2DgX+EgYB/g3yLiIiHgId+fYOGjZt+hYuBg32BeoOJh4myg4iMqZeSi4iJj5uQg4GEkYyPlIGFloiNg3t+d4CBf4eChIaIg4GFiIqFgXZ3e36AeHl7e4KCiJKBfoOHhYeKg4WMiIF5f318foeGgoGEgoKChoWIfn17eXlrbnZzfnx9fXWAdnB1goZ/kYqDgH95gnh+fHp8i5DAio2AhISBhIeEh4V8c3R5fIJ+fXx8fHZ+gn9+eXx3f3p9foGKfYN9eoSFin11eHyKcXt6enh3enBva3BydHl7fXV5d3R5dnV3d3d4e3mFgIN8e3l7fXN9dm55dn12d3R1eIB8e4KBiX+IfYSBfHqEb31+gnZ2gHNzeIFzen6Ce3CEfYqAdoJ8foh7e3R3d4F2dHV1eHR7cHVtc3CJf3p5d3RwdGlvcHRpaWx2cXJxc3KDall2eXp4gXp3eHh+dm13fXFyaXZ0eXx8e3p5d353g4Z4eXdxdHB2dHV32H7Sbnd5dXp1cXF0dHF1enFwenVub3Bwb3NygHV9fXt7dXd4eXR6d4J7eHNtc250cGxzsYV2c3OAeHJ0eH56h3d8c93bcnJ2dXRxcNzLbm7Oxshr0W1x19vN1dbj4HXYdHJyc3Z04ZGFz9DLy81szcrJwMvH1srVcdbQ4m9zzdDa2NfSa23PzW1wbtJzet/Q09fQ0cfSzNJuws/QgG5vac7QxMjLwtTFy87VyMXQ0G/Ty7++v8XV1tXXw8bKzmqZtsRtbs115N3gg+Z23dvT9+Lq4NbXzsPiz9bcybvM0dLBxbjMxL69ucy+ztJqaWlqbNfb1N3f3d/d0drh4+3Z4NzScdXCbNXHyMnN02xr18rKb3DV2NXo597a3eTggMTS3drRbW5ra2lqZmvGZHF/cm5ubtZ133J5c99xdnTY39vb3Ntw4XXg13rtdXfu43OCdoGFfXl63+R1gXpzdHh7dNJydHh7et9vcndyanN1cXBviXJsbnFycW5tdGxtbG9uz2tzdXJuc3N6e3dzdHV02XR61tja3NRxcnBzdG9zgH55dXV1eXt5m598e4d1f3t/hH9/f3t7enuBenl8eYB3fX15d3Xld4+De4SBf394d358fIJ7e4GEgoiMi4SBiIGDhoB+gYOLhYR8en+AgX1+h397joCCf4GBeoSGiYODgYeIf399iImBhYaDgX5/orV+f4F9fnuBf397gIOFhYaDgIiGfn6Dh4aGiY6Ff4iGgIiJhYSIhIqHgYSChoaDiIB9hYWEi4mDgYB9fbWOi4mOgoqGf4OBhomFgYeLjI6JiIKFgYOF1ZaJhIeJh359goeIiIiJio6Kg4KHjIqIhoeIjoyOjIyHf4KIh4aDhIeOkoyEh4yDgoGDhYOAhH+OiouJgImBgoaEjKB+hYyEhIKDfIKGhIKufICGoY+LiIOJjJmQgnx+hoGGi39+kouPhH2CfYWEgIeEgYaJg4CGjI+MiYSHjJCSi4qLioyKj7CDgIGIhYSGgYGLh4J9gYKDho2PiYqNiYqMjYyQiYmIiId9hYmGkY6PjIWOhn6DjZKMn5ePgIeEi3+DgoGFjprIiop/hISDhYiJjoqEgYSLi5GNjIqKjIONjYmHhYaGjIeMi4qSiZCLhouOj4N9hYOVf4eHi4mJjYWCgISGiI+OkIeLh4KLhYWFhIODgIGOiYeCgYF+hn2EhH2EhIiDhH+AhoyHhoiEiYKCfH99eHqDdIGHh4KCgIGBgYh6f4SFf3aNhZWKgoyFhZGFiYCCgoyHhYaEg4CHfoN/f4CViIOFgn98gXd7foN/fn2JhYOBgIWYknV/hIN+g4GAgoGGgXt/hX18dYB+gX+AfX15dnx2f4B2eHVvcnN1eHd5337fcn1+fIV7d3d6eXR6e3d1fXt2dnd0cnZxgHV8e3l8c3l3dnV7eYaBfnh3enp7dnF7q4N6d3d9dnFwcnZzfnZ6cNvacnR4fXp7d+7ieXno3dl04HR35uXU193i4nTbc3FycnV24qCP5ODk4Ot98/Ps3OTk7d7ZctvQ3XBx1dzk4ubkdXXZ2nR0cdVyddfLztPP08/W0tx00NzhgHd4deTo3OHn3vDj5ejt5d3q6nzi2snMxNDb29fezdbV3XDCxcxwb9By3dnYe+R019ne6OHj4tza2c7c3N7s38/g4eLV2tTa2tLU1N/U3dlsbG9sbdDRx83T0tXg1t7h4Ojb5uLadeXYdufc4d7a6Hh04NjYcnPe49nh39/j4ujogNLg6ebddXVxdG9zbnPabHyLdnFwb9tz3212cdpyd3ff5ePh6+Fz6Hbi33Plc3Tg4HV6cnd+eHJx2+ZzfHh1d3l5deJ1dXd4e+h4eH14eH1/ent6iXx0dXt4eXd0fHl7en1863Z7eHd0d3V6fHh2enp7836G7vTy9/F9fXt+e3l5AYuEhICJjYurrZGOnYWMiY6Tio6Mh4WDhY2Ih4mFjoSKjYmHhfyEoZKIk5ONj4mIiYuLjoaHj5KRkpmVjImPiYyRj4+SkJuRkIaFjYyQjoyakYmdjo6JjouFkZaTj5CMlJaLi4aOjomMi4qGgYOSmYSIiIGFg4uKj4qRlZeUlJKblYqOkYCSj4+SmY+IkY+JjpCLjJGKkY2JjIyTkZKakI6ZnJuloJeSkI2KuJuWk5aJlJGKjI6Sko2Ij5KRkIuLgoWBhIrfm4yIjZGPh4uPk4+Ni4uLjouGiYyPj4yMjIuUkJORk4uDh5CQjoyNk5yjnJKVmo+PjpGSj42RjJ6bnJyek5GXmICgto6WnpGQjI+JjpWPk9qIjpS1n5mWkpebqJ2Nh4uYlZidjY+onJ2RipGIkpSUmpWTmZuUkpeYlpGMhouNj5OMjI6PkpKXtYuKjJKRkJSOjZuUkYqRjo6QlpmRkZaPkJGSkJWPjY6PjYKMjYyYlpSTjJWOhY+cnpqyp6GalZ6Nk4CRj5KhqMebnpCamJaYnpyin5WMjJOWnJiXlpeZk56empeUk5CWj5OSlp+TmpaTnJ+jlo2Vk6yQnZmcnJ2gmJWQk5eZoKCimJyWkZmVlJWSkpOQjp+bmZKRkI+YjZeVjpiYnpSUjpCUmZCQk5KYkJKKkJCLjJiElZmblZSRj5Sei4CSlp2ViqGZrZuLmZCPm5CWjZOWopiTlJKVkJaMkYmJiqGWkZOOi4mOiIuPloyOjKCWl5WSkqTPpo+RkYuQiomMi4yHg4mSiIuFko6VlJeUlpSQmI+cnpGTj4iHhoiLior6kPyDjZCNmY6LjI+Nh4uNiIKQjIWIjYiGjIqNmJeSlD+Mj5CPipCNnZeUjYuPioyHgovNmoyIiZKJhIOJjYmXiY2A+PeChIaGhYWB//aChPnt8ID4gYX9/O3v8//7hPWEgoCDhv2smvfu7uvwhP769ebx7vzy8ID07f+Ag/Dz+fz9/oKD8fOBhIH2gIb27fD48PTp7+bzgOXz94ODgPr67+/x5fXo8Pb48+j39oP48N3i2Of08fT85/f1/IHX5O+BgvGD+/b2i/yA6ezu//f9+O/s7d/17+//79ru7+/d4dXn5YDa3+Dw5/j9goOEgoP///f89fT2+er19Pv95fL19oP56ID56Ozp6/mCgP3w7oKC9fbx/ffw6fH6++Hx+/f1goOCg4GGgIP7gJeph4ODgvaD/IGJgvqCiIf7/vz++/eB/4L474P+goH5/oGKgYqRiIKC+P+BjoWChYmIgvWEgoSFhzj7gIGKhICHioWEg5SHgIKIh4eEgoeFh4SIhv+BiIiJhYmHjY+JhoeIiP6Fivb5+vn3gYGBgoSChKV9AXz/ff99/33/fZl9gn6ofQN8fXy/fYJ8h30LfHx9fXx8fH18fX2HfAJ9fIZ9A3x9fYV8AX2JfAZ9fHx8fX2GfAp9fXx8fX19fH19inwHfXx8fH19fY98AX2OfA59fXx8fX18fXx8fH18faB8hX2RfAR9fHx9hnwHfX18fHx9fY98iH0BfId9Cnx9fH19fXx9fX2GfAt9fH18fH18fX18fIh9gnyIfQF8hX0BfJl9AXyOfQN8fX2FfId9AgIEAIBtbXNycXVwfI6deH+LeoqBjIWBeXl6eHR05nh6foKKg36A8n+DgYCAgoF/fn99739+gIKBhIJ8hISAg5iDfnp5ent/fXt8fHt6fXt9fHt6b3WGf39/enR6d3t5d5h7dHh/fnqEh4aDgoaEfHx9hIF+g4WCfn6Hi4iDfYqHhYB/f4CFfXuBfIWBhIuBfoOCgnx64nd3eoCBfn6AosaLg3x7g36HkY+Gf4V7fIR7fnx5koJ3cnJ1eoOJhYqMh4yLhomNf5Z8f32FcHNzfn98fYB8hoSJj4mCfnyEfnx/foJ+hIWGjo2Tj42Cj4CBiIuOk5OAi4mFf4KAf3p7go1yb3tvdoBzeoOAgXt7gH+Cg4KAgYODhYOBe46GhJGTh4iPh4qEfnt/g4+Lf3mDgoudq4mCgoWLioWHiYWOgIKLh4WFgn5+eYJ/hJCTjouGj4+7jIqFiYODhIF/g4B8eoF2dHN5eXyDf4OCfX59eX57fnp6eX12foJ5e4aFiZWDh4SFgYeDfoB+fYl8g3x6gIaQy4F5h7KGg4mPj5F7fHZ4c3mDiHuFg4WAd35xeX17XZB9c3h+gnd5gXyAgIGDfXl4ent3fnt6e4GIcnVveIGCfH97snp1eoJ9fnp3foB8joCJhYJ9dnZ8enpueXZ6dn99foSBl4yFhYaHgId/pIuHiIR+g4qRhICCg3x+c3uBbnJzf4F7fth8f3Z4dnZ1bXRzdnJ2c4B6eHh2e3+BdYJ8dXqBfG50iXFobW1obHNyc3Rza3FxcXKEcX92coiQcnhqa213bmpnfHBqb29ubm9zdIN7c3h6gZtydnpwdHqIfHNwcG10cnFqcHJxc390bGdtdXl0b3h2dYBwcnd6b3V/dnZuboXTcYF+dnNtcXDWdHp8dHlycG5vaGx3dm90cHjc1nt7dXNx1nDQyaxdwMfVb9Bwcdnc3t7c5XZ02XNwcHJycN92eG3Xzc/Cw8jOzcO/vsfMbGxs1tly2NfUfXBu2HPb453JdXN3deuBd3BvasfFyX28dbqzwYDNxsrOz9Xb4tx11cbOzGnKvrzIx85zyMDJ0tBq1M7Gzm5rxcTMdXTT2t93iPB44+vm4+fz6fR77ZeJ14fmzsjExs/dw8TDzdfiyd7YfG9pzW92d3HV2NLd0d7X29nr4OfYzeTXzMLN33TY5ePacW5was7M0Nff2dZubWvMcG1wz4DT09x63NTTdXPcctVx2mx1cOBydYBzcH95cHHZ03Jw4HV56HR25+V3cePkgYF639bYeIBjfYCFd3bzhIOygeflfXXddHHidnbgb9l2bXVydHBvdXp1e3p3bnJ1dnhwb3F6d3F5f35+43R1fX97dnNxamx2c3Jwb25ubmtydHNu0oB3dnp6dnh2f5Ote3+EfZuEkIeFgYF+gYB68Xx/goWKf3x/732Be3t8e319fXt44nx5e4KBgn55gYSAhZyHgn9+gX+Ff4OCg4B8gH6Afn1+d32HhIaFhIGHhoqBf56JfoCHh4CFh4qGhoaHhIKDiISDhoaFg4SKiYqEgomHh4SCgYCFf36Df4eGio6HgoeHh4OC9n5/gYaGhYSFr82OiIF/iH+JkIyEfod+gImEhoaCo4+Df36BhYqKhoyIhouPiYyQg5qEio2ThoKEioyJhYmCiIGFiYeGh4WMhoWIh46GjYyJkpKVkI2EjH+Ag4eGjI19iIaFf4OChYSFjKWDfouChoCGipOOj4eFiIaGh4WDgoGAgoF/eoqFgYyNgIKGgYWCgX2Dh5CLhYCGhYmYooaCg4WMjYiKh4GLfH6IhoWHiYeGgYmDiJOTkIiGjZHbjYmEioeFhoWJioeGhYuDhH+Fh4iOio+OiYuJhoqMjIiHh4uFi4+GhY6MkJ2JjomKiI2NhYCEh46DiYKEiZGU0YZ8cXyEhomQkZCBiISHhYuUno6WkJCIgYl+hYqGaayMhISIiH9/h4KFiYaJg4ODhIeEiYeFhIyVfoJ8h46OiY+NvomGjI6JioeFiIh/lYKLi4mEfIKGhod8h4ODhIWEiI2KopeEiImGgYt/kIV/gIB7gISJhYCChoKGgIuRgYKBiol/hbyChX5/goJ/e4B+gn+FgoqDgX18goGDfISDgIKIin+Hq4Z5goJ6fYeEgIB+eHx7en+SfIaBf5GbfoZ7e32IhHt9kYR+gIB8eHh6eoN8dnh7gIR2eXp1dnuHenR0c3R6e3p2gH9+f5iBdXR3eXd0b3d2dYBvcXZ8dXuCe3h0dorddoR9eHV0eXffe4aIfIJ5eHN0cHF7eXN3d3no5H2Afnx+7nzo68Nl5uzveuh5euvp6uLd5nZz3XRwcHR0d+Z8hXzv5Oja1+Lp7uXh4ObldHZ35eR14N/eg3d16Hfh44OdbWtwcNx2cXBwb9HR1oDHidTN24Dj3+Dj3+bo9O596tzn5Xjp3tzi3OKI28/Y5t9z5tbU2nVz09TXfnPQ2NZzf91w09DS0NHf19525ZWN2Inu293a3ePu3dzd4ens2eXhf3Nu0XFxcG/R2Njb0tbX2tfn4e/e2Ojg3tPf9YPp8u/ze3l5c9/h4uzp6eR1d3XmfXh34oDi3ud9297ffHTfddt26HN3dud0eoV7d4WAdXTh43Z05HZ643R24+N0cOHgfX1329XadId2enx9dXTue36aeODednfldnXqd3XndOx+d358eXZxdHh3fnl1c3N4eHV0dXd7enZ4gHx85HR0fYN9e315eHmDfHx9e3t9fHmAgH546ICDhoqMiIqJkaGxjJOYiqyRnpWRjY2LioSD/YSJio6UiYOI/oeOhoWIi4qKiYeD+4eEiIqIioWAi46IjqOPioqKj4+PiY2LjIyKj4yPi4mJgIWUjo2NioWJjJCHg6mLgYOKioOIi4qIh4iJhYaHjIuJj5GQjI6Wl5eTkJuYmJSVkoCYkZCVkZeUlZuUjZKPkYmH+oKDiYqMiYyLveiZkouJlI6Wo6GWkpqLkJqWlZWSsJyNiYOIjZKSjZCRjpKYk5abjaOKiZCUh4eJj5GQi5GJkpCTmZCPjoeSjYyRi5SNk42Ompiel5WLl4iGjpOTmpyMl5KQi46NkI6OkquEgIyEioCIipWRlI2Ljo+Rko6NkZCRlZKRi56Wkp+gkpGUjpSPi4WOkKKbkIiWlJuqtJiUkJOcnZeZl5ObiI6ZlJORkI6Lh4+JjZudl5OPnJ7olpaMkpGQkZCRlpWTkpqNjIiOkZKXkpSRiYyLioyNj4qHipSJk5mOj5qYnKiSnJaZlpudlICSmKCTmZKVm6Wo0JWNcXShoqiyr66anpiXkpehppehmpyWi5eKkJiSgbGYkZGam4+Nl4+VmJaZko+OkJOOlpGQkJqkjpGMlZ6imqOf1piWnKSgnpiWnJyTrZmkn5uXkJSWmJaNlpORkJWSkpqUraCRkJORjZiOqJmWl5aRl56km4CZnZmclKOolpmVoqSXm+SWmIyNjYyLh42Pk5CVjZmRkI2Lk5GSi5SVkZabn4+auJmKl5eNkp+bmpeVjJGKi4uehZGJhZmcgo6Ag4aUj4mJqJmOlZKSkI+RkJ2VjpGQmZuMkJOKi46hj4WFhISLiYqHkJGQk6OYiYuNkJKOh5KPj4CGiI6RhYyVjI6Ghpz/hpeQi4iDh4T+jJmZjpaMi4eKgoaQjYaJhYj99oiJhIKC9oL09NeA8PT/hPqFg/77/vv2/IiD94OAgISAg/qHjIL66unc2efy9+no6fP3gIGB/v6F+fr3jIOD/oT8/5m5g4CFhfyKhYOBgO/u9I/Nkuzi7ID17Ozt5+/z/fiG9+v5+YL87evx7/WT7uLt/vWA//Tv+4eE8PD3jIL09fSCkPmA8vHv6+n68vWC+qOU4ZLy29ra3eHt39/h8Pn96/34kYeC94aHhYHy9PT87+zg5eb68vnv4O7l5d7p+IXw/fj4goGDgvnz7vD++/iBg4D1h4GC9ID17v+H9Pf3h4H5gfSE+4GFgf+BiJWIgZSMgYL49YGA/IGF/4CC//mBgPz/kJCI+PPxg6u6iYmOgID7hoq2ifv9iIT7g4D/hYH6gPuIgYqGg4KAgoWFjYWDgoGFhoGAgYSHhoKGj4yL/4OFjpOOiYqGg4WPhoaGhYOGh4aKi4qF/5l9AXyIfQF8i30BfOF9AXz/ff19An5/mX2Cfv99tn0Bfqx9AXyIfQF8kX2CfIV9DXx9fHx9f3x8fH18fX2GfAN9fXyGfQR8fX19jXwQfX19fHx9fHx8fX19fH18fIZ9AXyFfQZ8fHx9fX2MfAF9hHwBfYZ8AX2FfAF9hHwOfX18fHx9fXx8fH19fH2IfAZ9fH19fH2QfAR9fX18hH2UfAF9hHyEfYd8B319fXx9fX2EfA99fHx8fX18fXx9fH19fXyJfRl8fH19fH19fH19fHx9fXx8fX19fHx8fX1+hX0BfIR9DXx8fX18fX18fX18fXycfQF8l30BfAICBACAd3p4fXuHg355e3t5f5KCfXx/d3l6enx6d3x54oF5gYWFgXyHhH6Ben97g3nleHl4fH9+e39+fYCHgYOOwYeCe4CDr5B8o3h2cXV8f4CCgHyBgYqMhIB+hIF+e4qClHuCg4CBfX52fpN2enh8fn2AhIiDgoKChISDf36IkK6QgICAfnp8fn57hH6Hi42GfoB/hnuGhqCChX6DiIWIhIOBeX+FhoOBiYqIho+HiX6MiYZ/eXiAdreNpIGLi4qPjYh/hY5+e4R5e3V4hIODcnF7eXx9f4OGgICCgYGAh4GFiYuNjYmMioOPiomDhYZ+hIrLg4eEgIF7e32Ff3qDg4SIgn6Af4CEh4F+gISEg4+DfIKFiIeBipGTgYaLjZOgyZuJjIWRiY2bfX55fnt9gYWDh4KAhYmIiIOEgnuHeIaLh4GCfoSEi4mSpZSMi4yLk4OEgYGEf3d1eX5+fX+Gem9teJJ9eYCEgH98eXx3e4F/fH15fHl+fYiGe4OLg4mKhYZ/f36Ag4GIiIiCgImIeX+BgczXh36HhoOAdHt5fnZ7eXp+fYJzeXx/g5F5g4KHhYh6lYiDgoKPhpSGfYGCenh4e4J/e3p8enV3enhyeXp6eoCLh4mIhYN7fX18gH9/gXx5d4F5in+Afnh7dXp4fn57hoCBf4qFf32LeX2AiIuLiY1/gn6AgH18fH12hYB0eH93dnW/fH55d3iDhHt7dXSEkXmAfnqBj3mAeHZ7f2xydXFveoN8dXZxdGyAc3Z/f4J5dnFzcnF2cW9tdn55d35ydW53bHp4c3RzdHh1c3B3e3h+jX98d359fXV+b3hvcWhpam1jZGltcm1vbmVtdIR7fmxsbGuAb2/LzHuH0HekeXN61tJyenhzfYiBbnJ5d3Jmbnl4cGprdNh2cHF4cn1yeXBxbm/Va2p2c2rNeXlw1nRxcXB21tjS1tTMbXZtc29sb3eBbs9tccprzLy8aWvBjm5qZM5qatG60nN11218hW5/jHBtbG5w02ttZ89tybJ+gcG8uciAfnXK2NrJ0t7Nz9W/1NbU12/EvMTP1cfO08vM0tfY1tfe2NbZ4dDH2dnc2ePb3Nff4dnU5Xt82NTO0cjL0trY4Njc7eHq5+3m9er55eDe5eLSzuHZ4Nn31dTW4tPf4t/e0tKhmdbU1NTe3XLX4MbD19jS2NTdcm7aam7X5eLY1+OA7ePo5t3j7X6L5tHR4tvZfH+yeud8eHXs4OHgc3DSbHBxc3VzcXZzeHV4cnJ2ceLP4dxzeHrs3dX37u7t8urbeIB5gXlycXHc6eZ1fXxzfHbpkrSGhHnnenZ8eHN03OHhdXZ9enx4eXpxc3Rudm9vbmplaWdpamlpaWttdXd2e3yAeHp2fHyDgXp5eHp3fZyBfHl9eHp8en59fH177YV+g4WGhICJh4KBf4KBh4L0fXx6e3x/e4F+fX2HgIGUyYmDe4CDtpWBqX59e3yAgX6CgICDfYGGfX2AhYN/e4qIoIGEhYeFhoWAhJ9+goeKh4aEjo2IhoeHh4OGg4OJkayRhoaAhIGChISCioeNkZCKhoWGj4GMjaWKjoiLkIqJh4aAfIGEhYOAh4eGhY6GiHyMioqFgH6Ff7GMo4WJiYmLjol+g46CgouDhoSFkJCVhoWHgoKDg4WMh4qJiYqJjYeMjJGQkoqNjIOOjImFhoV/g4jDhouIhoN9goSKhYOFh4iOh4SAg4OIiYiFiIuLjJmQhoeKiYWAhoqOfYCDg4uWuZGGiISOi5Cch4eAg4J+gIWFiYeEi46NjYaJh4GKe4WIiIWJhouLj4uWp5SMi4qNmoiKhYeLiIOCiY6QkZGUkIR+iKCMiI6QjI6Ig4WBiIqHh4eEiYWKiZCMg4iPio6NiIiAgH6Ag4eOjY6NipPDlYuLje/IkYqQkYuIfYaDhoOKiYmQkJWFh4uKjpWAhIWJhol9mIyEg4aSh56OgoWJgoCChIqJhYaGiIKEiId/iIeIhouUlJOQjouHiYWIi4yHjIiCho2Gk4mJjYeKg4WEhIWEiIeCgZCJhIOOfXqBg4eFhIR4f32AhYOChIWCj4uDhYSEg4O1iYaBfHyIjIiFfXqIlYCEgIGHjn+AeXl/hHV8fHt7hJGHgIF9fXmLf36IiJSDf32Df3yAfYB8g4qFhIp+gnyDeoWCf4F9fHp5d3J3dnZ4hXp3d3x8fn2AeoB5fHd4eHt0d36AhIB9gXd+go2Bg3Z3eHeAe3nj6oiU33yfeXN719VydnZxeZChcXV9fnRwcn1/dnFyd954cnV3dYh2fHV1eHjkdXeAfnXkg4J45nt3d3d74uPc4+HadX11e3x0doWQeOV2eOJ25tnZenrin3t3cuV4dObJ23N01m18fm92f3FwcXJ12XB1cOV55tWLld7Tz9mAh3rV493N1+nj4+vc6+/m63vd1t3k59rd4NjX3d7d2+Dm393g6N3W2OPd1+fS3NnY19LP3Xh32tze2djc3+3r7ujs7uTo5Obj7eLt3tjY4NzQy9fQ29nu2NfV39bj5+nj4d+gmeTg4t3n6njh6NbV5+7i5N7gcnHmcnLi5OXg3d6A5ebg3tfe6HiE3s/M29vZd3m+euh9eXbl4eTic3TdcnF1dHV3cnd2eHd4c3F2dOnV5uR1enz36OX68/b59u7geIJ+g3x3dnXk5ep2e3l1eXbii6h+fHbheHd/e3p45+rodXZ5eHt5fXt5enp3gHd6fXt5fnt9gH57ent7f4B7fnuAiI6JlJCbl5GMjYyIjK+Pi4iLhIaJiIqJhoiG+o2GjImLiIOMiYaEgIaGjIP8hIWDhYmHhImIh4qTjo+h3peNh42O0qmMvomHhoaMjoyNh4aLhY2Qh4iJj46LiJiPo4iMjYyOiomEh5yEh4qNjYqKkJGLiomLioiKhYiUm7+gkJWAkI2Rk5KOlo6UmpePhoiGjn+Ji7GLjoaNk46Qj46KhYuRlpORm5yYlqKanJGin5mTi4mRisCatI+XlJKVlpKJjpmPj5qQkYqJkpOXh4eOi42Nio6WjpCIi5CQlIuSlpuam5eal4qalpSNjI6HjJPDk5iTkpCJj5KZlY+TlZSak5CAkI+UlI+KjpCRkp+VjJCWmZiRmqCjkJiam6e046+fnpWimaK0k5GNjYeLjpaUl5SRmp2bl5CSj4yXhJKTkY+Tj5STkpGez6aWlZaZpZGWkpOYlo6Mkpucm52kno6LlrGalZ2gnZuWkpOMkZeWkZSOj46UkJ+ejpmhmaGgm5qTkY2Al5ign6OgnqvQqqGio+OcraOtqKWilZ+bnpecmpiinqGQl5+gpa+Xnp6knaOSuqifnaGtn7GllpiclJKSlZyZkpKVl5GQlpOKlZWXkZmenp2emZWMj4+QlpaYmZaSmKOcrJ+do5qdlJaTkpWTmZaSkZ6UkI6ciYiOl5mZmp6RmZaAnJyZmpyUpqGUmZaXlpjVo56XkpKgn5iXj4yappCTkpCXno+SiouRmYSLjYuLmaqhmJmVl4qllZignp+VjYaKiYaHg4OAipCPjZeKjImSiJeVj5SPkJKPi4iMkJCToZSRjpSVl5GWjJOLjoaFiImBhY2Rl4+RkYqPk6aWmIOEgIGAhob4/pai+Y2/j4aP//6Hj4yIlKKWg4mTkIeAhJKTiYGCif2JgYOFg8WBioGBg4L7gYKPjID3j46C+4eEg4KI+Pz6+v32hI2Fio2Bg5WkhviBhPeB+unohYX0nIyIgP+DgPvc+IOF/oOQkYGKmYSHiIaO94KIgP6G+uORiOnj4euAjoLk7e/W5PLl5/Pb8e/u84Dm3ufw9Ozz+fDx+fX29Pn/+PT2/e7m7/by7v7s8/Hy8ezo+4qH9/X47+nt8fn09u3k5+Dp5OHj9e787vDu+/vr6fro8Ob94eXl7d/u9fPs5uivpOrm7ez6/YLz+Obf9/vy8fH3g4D/goH8/vrw8fOA//j28d7p9oGP8Nvh9fPzhojPhvqIg4D09/v1gYL2goSIiIqKhoyJi4qNg4OGgf3t+fSBgoT85eT+8/f7+/jhgIqFi4WBgYH5+/6Dh4iCioL1ocWQi4b/h4OLhoOD9/39g4SLiI2IioqIi4iHjoGFiIeFiYWEg4OAgYWHjImKjY6bfQF8kH0BfP99/33jfQGB/330fQV8fH19fIV9gnyGfQF+jX0BfIx9AXyFfQV8fX19fIV9hnyKfQt8fX18fXx8fH19fIR9CXx9fXx8fH19fIt9Cnx9fX18fXx8fX6EfIJ9jnwBfaN8gn2sfIJ9hnwBfYp8BX19fH19jXyCfYZ8hH0EfH19fYR8A319fJB9hHyDfYp8iH2DfIZ9AXyFfQF8hn2DfKB9AgIEAIB5eH12dW9/en5+gXx5fHl/dXV4eXp7enh4euV7gn+Aiax/8Od9evx8fX6Bd3dyenh2dnJWeHR7fn1+fHh2eYGCjKaFfY6EgoKEh4KAf3uGhYSAhImHh4uJgYGGhoWEgXqB7Xd+eHqAgXuDfXx8g3yKjX6DhoCBjXt9d3x+fX98eICAf3Z6eHeAgYKCg4KCsoB+gYR/gYCEhYeDg4eAgoB6fIeGjoR+foOFjYiDhoqFgHd/f4yCioOLgYSPiIaNkIqJi4iFgXt4e4SDfoF2fYV8gYWPi4eBfoCCenuJjY2PjJKOiIeKjZSGmH+EhIKZjYaJiYeKiYSIhYuDhoWEhISBgYCJiIaDhIOBg4yPi4aMjI2KhoeKkIiChYWMi4aNi6KKiYvYk4WGhoGIfomCe3eAfH6SioaFgoaCgY2GiYiKiIiCjYSRk5GXqZWLiYuQk4mIhI2EgX98fXpzen1+fHF0bXt5dXyAgX+Bgnl2h4WBgH+EfH2DjIeGh4WHj4uMioeLgIB8lY2LhYiBhXeAfX+AgZWEg4WGfYJ8eXp7gYmDfXt7eHp+fHh7h4ycg3+EiYSKioKHf4GDhn2Eh4OGhoSBg4eEf3x8hH58c3qAiYB4enl6gYeAe4SGf4B/eXR5eX1xd317dnZ0eHVycnVub3Z4eoJ/f3t5goSGg316gIOKjIaIhIB+g39+fXd2eXZzb3N6dHR3e4WFjX59g3p1fox5eXp6fHR3dXRyc3l3dX18e3R6dnBzbG1tdG5zcnJzcnRycmxybddxa21zc3ZrdW53bGhmeKt2e3Z4e3qBgoWEf3l5dXV1cnt6dHh5dnJweXVudG11dGxqbm1jZGtoaW1xcMhtdoBwcm92dXduc3Z4e3N4fYZ9e3N4cXN0dYV+c8tq0nJx2dnoeHPS4nyAXHJ3fIVua25tbG1sb25pam1weXJ0cHBren10bGl3kXhucXN3endu33jt3eV01XRybmvPa2tnamlgaG1tbHFsamNqbnVvysZyaW7AxsnHxMzHyNHCz9zZcoDf4Xx9fIHiy9PLxcHIys/OzLXL027Z4uDY3OPb2+bd3e7p5tbq7NbU0uB74+Th19na0tbk2HTRbsrVz9Z1cnPm3dLm6vH39d3v9ef24uPi3djd4eDW1tnPynuD4W90y8XR0dXS2uHiz9pzcXfi19fe1tfS0cXS0tPP0NXN4/Hg5YDw5un8fobt64Hf2NfQ2uvteXt/gXrz5urr6nXge3PhdXRvcnF1gXdxdHV5e3R6ioXl7nx89eftd3j7fenq69vz8Ofm7uF3duJ2ffKAfejeiH+BhXl8eNvd4nZ7eHd/gufmf3vu4ut/e3d0cnpyanh4cnZscnF0cXR3dXZ4dXmBe4B4eXx5eXWAfICBhIJ+g4CEe31+foOAgIB8f/J/iYOCjamD+/iBgf6AgIWIgYCAg4WFhIeAhn+GhYOChIB/foGBkLSLg4+Hh4iJioJ/fnqGhYWCf4OFhYuIhYSGh4eJh4GG9YGFgYOGiISKh4iJjoSTlYWGi4OJi3+FgYKFhouIhICKjYSFgoCLi4qMiYqIoYWCg4aCgYGJiYqLiIyGiYWDg42Mi4F7f4OGiYaFgoaGgXt/gYyCh4WHgYKLhIKGh4KHioeJioeFhI2Mi42GjZSKiYyVkIuHg4aEe32IiYmHhYuIgYKIio6DkoCDgYGTiYKIhoWEhYOEhIqChYOEhoiHh4CJiouGiIeGhI2OioSLi4uGhYeJjYaAgIGGhIOHh6GIhYfHioSGiIiPh5GMhH6IgoaUioiKiouJiZSPkI6Qi4mGjYCQio6QpYmHhImPkYqIiJONiomJjYeFiI2QjYCFfYiIgISKjoiHiYCAioiFhIWLhISLkIyKi4uMlI6QjYyMhYB/l5GRjo+JjIGJiYeKjqmMiIyMh4qHhIWJjZaQjYuMi4qLh4eHkZiwioiLj4qLi4OHg4WGjIKIioaJiYiFh4iIh4ODi4OEgYGLkouCgX+CiYyIhImMiYuJiIWIh4mBhoyJhYeGiIaDg4R/gImHhomIg31+g4aFf3x7gIGIhoGEgYCBh4WGiYWFiYaGfn6GgH+AgpCKk4eDh4N8gY55fn+BhX+BgoJ+gYaAf4WCgnt/fHZ5dnh5gHyEgICFhYyEhIGBff2Cg4KFiYyBiIWNgYB9h6uBgnuAfn6FgoiCgH18eHh5dX5+e32AfXt6hH55f3qBgX2BhIV9en56eXh7fep7iICBf3uCf4J3dnp3eXJ2d4B2enN3dHd0e4uGeN904Xl36Ojsenbc53mBdHN3g5F1c3d7e316fntyc3R2fnZzcG9rd3t0bm18nXx2eHd9hIF043vn5uh43Xh1dHLfdHRxdHRtb3BzcXVvbGducnhy1tZ5dn/g5+7x5Ong3N3R19zfcIDc33d5en3n2+Lf2d/l5u3v5NTj5nPf4N/X097R0tnU1+fd4M/k7NfX1uh85uTe2N3a19fk3nfbdN7f3uB6eHfo49fc3+bi3dPb4tfe1dfb2dja4d7X2t7Z34KI6HZ42dLa3OHg4urp1+V5d3vk3Nrf2dXX29Hc4OLX2ODZ5fHh4YDo6OfwfYHs6Hzn3N7W2+bmdHZ4eHPl2dTY23HZd3LhdHRxc3N1gnVycXV3dnJ0hIDf5Xh26uDqeXv7gfDz9uL8+PHv+O17fO55fPF9fu3hgXp9gnx/fu/u7Xd8eHp9gOTqfHvp4uh/e316eYN7dYKFgIF5f3t7dXZ5enl+eHmAeYCIioyHhIKOio+QkI2Ji4qRhYaGhYqKiYiDhPmAi4aFhod+6+p8fPZ+f4KGfH18goKEhYR5hoGLiomIiYWEh46RodKYj6OXlZSVmY6LiYWQj4+Li5COkJSTj4uQjo6OjIeL/oWJhIeLjIiQjIyNlIuZnIqOkIuPlYeJh4uOjZSQj4CSmIyQjoqVk5KVkJCO94mFg4WCgoGHiYmLiI6LjY2KjJiYmZCLjJGUm5WSlJeWkYqQkp6TmZWUj46ZkJOVmZGWmZSTk5CLjpqVk5OHj5eOkJaelJCMi42NhoqXmZeXl5qVjo2RkZeLmIaLiYmek42Uk5GSkY2Rj5eQkY6PjpCOjoCQlJSNjIyKiJGVj4ySkpOSk5WYnpyWl5mioJuhob+inp7uqJyYlpedkZ6Vj4iOiZGlnJeYlpeTk56YmJSVlJKRmo+blpyi3KGZlpyfoqCcl6WcmZSRlpSLkpeamIqPiJeWkZmeoJmcnJSQoJ6YlpielZWcop+cnpubpZ6hnp6fkYCLp5+cmZqTmImSmJqgpcOqp6qro6iinJueoquhnpuclpubl5SYorDJoaGjqaOmppufmpiZnJWanJaYmpeVl5uVlZKRmpKTi4+UoZmNj4qOk52Tj5aWlZWYlZKYl5qVnaGgm5+eoZyZl5eNkJaQkJWSjYeKkZaVlI+LkpWenpibloCUnJmanZicn5uck5ObkpCTl6OhrJ6an5ePl6WMjY6PkYyNj5CNkpqWlZybmpGWk46Qi42Pk4yVlJKRjpKOiYOIg/qEgYOLio+BlYuQioWDj6yOmJGVlZScmpydmpOQjI2Ni5OVj5KVk5CSnJWOko2Vko2Nj4yEhYiHhoeKif6KloCPjIuQkJGFio2PkYeIjJaNjIeLhIiGjJ+Yif6D94eC/Pb9hIHv+4ecvIGIk6eCgYWIh4qFi4iBgYSIkomJhYWBjpeOhoKTwZSHi4eMk4+A+of9/fuC8YWBgoD+hYaBhIWAgoGFg4qEhYGFio+J/vqUi5L5+fv4+f729fTm7fb1gID4/YeIg4Ty6O7o5OXr8Pb69eD2+oH2+/Ly8Pzw7fHr7fn3+eT7/+jm5fiG+PXv7PLw6ev894Xpgfv28PSIg4H57tvm6vL059fn9eb57uzx5+bm7erj6/Lp6o2W+IKC6+Tv9/jv+P3+6vmDgYX57vD48vHx9On7+vvx7fTr+f3v7IDw7+73gIf574Ty7e7h7Pv/g4SJioP/9/j9+YH4iIP9hYWAhoWIl4mCgouJioKDlJT1/4iB/+zygID9gvby8NX39vPu/PiDgfeAgv2CgvXukISHjYOHhv/8/4CEgoGEiPL4hIT99/2KhYiGhJKKgY6VkJWJkI2OiImLjoqOiImRiZp9AXyFfQd+fXx8fX18jH0Bfq19AXz/ff99/33ifQF8u30BfJx9D3x9fH19fHx8fX18fH19fqZ9B3x9fHx8fXyEfQF8kn0FfHx9fX2NfAN9fHyEfY58AX2VfAF9inyDfYR8g32afAV9fXx9fYt8g32YfAV9fXx8fYd8hX2FfAV9fH19fJF9C3x8fX18fHx9fXx9inwKfX18fX18fX18fId9g3yGfQd8fH19fHx8mn0CAgQAgHtzdnJzfXN1cXZ1d3l1dnh6eneAe3yAgIeEfX+DjI6KhIGBgIeAjIuDhID9g4STiYSEen16eHN1dHt1b3N2g4B6d3Z6e3l6b3J2eH1/h4OJhYOGjomFi5COjouFfoOEhpJ/fn17mIW2hYCAfIF+gHp/jaOHtnuCfYaAg32Bf3uCgICCf36EfHx6f4WLiIVjhIWKiYaLiIuIiomJgoZ/gIeHjZmQj4mMioeMipKMhYeGgIWGhoiIjIeGgoyGhYaShoiBho6NjIaCgoaMn5GBi3uLzoqKi3qDg5WDjIyGh42TkpaRlImPjoGNh4iNh4eFgImCfoiKiYWGhICChoaIi4iLbImFkIN7f4J/gpGOipl/h5KHiJGTi5SJl6WMj5WLj46FkY6AgX+AgoSDj4iOe3yEg4N+hYKAioaEhIekhIeWzJSGiomLiqxGhXt7fHl+eHh7eYB/gICCfIODhoiGh32FhoiAiIGEipORgoGBgISDXoiHh4iZkIiGi4OCjZqIh4iFf5GFgomGhIV/fHh9hIWGhYKCg4R8goZ/hIB8gX59hH+Agn6EkIOCf4WKjYmMh4WGhISChYKDgX2Bg4OCiImchot9f4N/dnZ9gIB6foOEf4CEjoSFioR/e318fHx5goiDgHuAe4iPlnx5dG92eXZ7fnmEg4CMg3iEhoeJiIR8doKAdHF8iX5yc3V0eHp/foN4foB0eXl5cnV0dnp4fHeKfnl1d4d3gYB8eXh6fYJ3cnN3dnx5d3N4dnRydnFwe3ZudH50eXN3fXl9eHZucnuGhoCDeHl+e4GDmId8dnZ5dXR2dXR1d3h2cXJ1c29wbHNxamdsbWBvdnpucm6IbW1zbXByfIWBcnF1cXZ2xtFudXh4cHV6cGxycW9ycXNvcG5+g3bjeIGMgeV14HFzcXZ02G9tbW+DwtRtys5zd3h2e3p+e3J2c3FzcHdvctPBa3HH1oDU1c/OzWx2a3NwbGpua2vOxHFqa25ncWRrbLtkY25oaMVocW1rydDQbHbg5NPLx9TR09vk4tjZx9TSbMrY09jc2uF4eOLd33Z8deB5woLi5ODe03Z13NbJxdPWd9BvbMrV1M1wblPFa25ybm1r2NXe3NHQ5OTm6vTt8uLa1t7Sd4Dj2tractDP2NbR0G7U18nKzHNz3tvQ1t7bd3Ha4NvZ0uV0c+fj6OPp6e7k6ePmfubf6uTy6Ojn5ujm5Nvm43jpdnve5+Hh3dd02thy4XF0129w2nBy2XPjeHl/fet5e3p66eZ85/Dl5+Lp6uLh4OHg6d3j6+fm3nfyfebnc+F0dy54opWA8YST5vL2fYd/5XrsgXt0cHJ0eXV3eHhze3Z6e39+eHzpm4qCf311dt14gHh2ent8gnt9en9/gYN+f36BgHyCf3x+gISBe3yCiYmGgX59f4d+iYp+gH/7gIOMhYKFgYWBf3+AgISBfXx8h4SBfXx+goKEfIGDhYeDhoKIhIOAgoB9g4aDhoJ8e4KFiJR8foGDqJDDk4mKh4yJioWJk6uLrIKIho2KiIWKhIaKgImLhYOGgYJ9g4eKh4Rng4WGhoOHh4yJiYyGgomDhYqIiZmGh4KEg3+DgoeCfoKCgYWEhYiBhoaDf4WCgH+HhYWChZCKi4WGio6TvJyNmIGOwJuKi3+CgZCBioeChIiMjZCNjYiMjoWLiYiJiIeFgomCgYiMjIuKioWGiImMjYuLgIeIj4iAhIeFipOQjp2Gi5CJiIuLhoyFj56Eho+GiouEkI6Dh4eChYKEkYqOg4iMiY6IjoqIjoqGhoiqh4yb1JaIjIuNjrBci4aGhoSJg4KGhYuIiYqKhoyKi46KjYSJi4yGioWHjpSVhIODg4SDg4WKioiLmpKLjJOKh5GfjomOgIqHl4uFi4qIi4qHhYiOjZCSjYyNjYWGi4WKiYeLiIiMh4qNiYqRhIKBh4uQj5CLi46Li4eLh4aFg4aJiYqLjaKOlYqKjIqHhIyOjoqIjIqIiYiMkouMlJCJhoiDgoV/iY2Hh4ODfY6bp4WGg36DhYeJi4aMio6XiX+Fi42MioeDgHyHhHx8h5iNgoOEhomHiYWJf4OFeoGCgnt9f3+DgoB+kYWCf36JfoeGgIB8fX+FfHp6gYCGhIJ/goSBgYJ/gIiDfoGNgoF+goaChH59dH+CiYuDfnp+gYOBn4V9enl9ent9foGEg4WEfoCAf3x6en99enV5e3B6gYV8f32Ve3p+gHp5eoKMiHl2enZ8edPadnp9fXV4fnNzd3Nxc3h4c3RxfIB05XiAiH3kc991e3l+e+18fX1/rurveuPie3d3eXZ2d3Rtcm5ydXZ8d3ns23p64ezr7OXi3nN+d3p2dW9zb3HZ0XpwcnVxe3N5f9dxcnt3deF2enZ13eLbc3fk5NLNgMLNztHV3+DX5dXd3XPZ4eDb4+XhdHXc09hwdG/RcKR32dva1s5wcNvb1c/c5Hrac3Pd4N7bdHFc1m90dXJzcN7e597a1+Db2tzg3tzWz9Xc1nfp4d7fddvb39rZ13HX2s7Jy3Fy3dvS2tzfd3Pc6ODg1+Z1c+Lb4dbf3eTg5uLjgHrk4+Xg6+Tl5Ofp6uvj6+t57Xh96vLn4OHfduDfd+hzdeB0dOJxc95y43Z1eHbfc3V1dODhdeXx6Ovw9PTx6u328vnz9PXy9e9++n/y9H/2f4F7rKSA84ee8u/reH544Xfnfndyc3d6fXt/f317fHp4eXx+d3jHi4B+fX10eeN5gImGiomJkoiLh4yMjY6GiouKioeNiIeHjJCLhoeKk5SOiIKFhYuDj5KFiIP/hYmWi4qJgIiGhYOEhYyHgoKEko6MiYiLj46QhYuMj5GNj4yQjY2Kj4yJkpeTlJCJhoqLjpmCgoSEo5K8kY6PjJSRk4yQnM2XyIuQjpeSk4yTkI6VgI+Wjo6QiouGio+Uko2ojI2OjIuOjpCOjZCLhYqFhouMj6KSlpKTko+Uk5mTkJGPjI+Rk5WUmJaUj5WVkJKWjpCPkJ6YmJOTl5ebvKCQn4iZypGWloeQlqeUnJmQkZadnJ6bnJGUlIqWkpOXkpGQjJePjJWXl5aWlJCPkI+Tk5GTgJCQl4qEiYuHi5iVlKOIkZiTkZqfmaCYpricn6ujqaOcpqiYmJeUlpWVpJuejY6Uk5SOlZKRmZeUlpW3kJOn5KiWnZqcm8yUoZebmZKZm5KXlpqYlpOXkZaVmJqWmo2ZmZ2Tnpeanaiok5OQjpORjpCXmJWXr6ibnaOalp++nZeagJSPopWOlpaQkZCOjZKbn6WnoaGnpqCgpp+jnpicmZKemJuemZ6om5mYnqOoo6Sgm5ubmpOVlJKSjZGUk5SYm6+XoZGXmpiRjpicnpSYmpiTl5SZoJaYnaCXlpqVmJ2ZpKulo56bl6e5w5mZkImPko6SlJGZmJmnmYuXnpuenpeUgIuYmI6IlqmbjZGUlZqcoZ6jlZqbi5OSkouLio2TkZSSqp6YlZOelJ6cmJaTk5Sck46OlJKXkpORlY+Mio+GhZOQg4mZi5CKkZiVlpOSho6VlpyZkIyQi5KTrZuQioyQjI+Rk5WZmJ2alZeYmZWSkZmTkIuMjICMkJSNko+tioaPgIuOkJifm4eDjIiPju/6h46QkoiMkoaGiYeDiYyLhIeDj5OF/4WNmIv9gPaBhIOKiP6HhoaJr/v/gPXzh4mHiYiHi4mChoKFhoeMhYb86YGC6/r+//n59YKQhoyIg4GEgYT98o2Ch4iBkoWSl/uDgYyIh/6DiYSC9v73gYj+/evmgNvk5Ofu9u/q9uPx9YL2+vn1/fz/g4X39fSBhoH3gumR9/r48euAgPXz6+b1/YjygoD3+/r4hISE+4KFhYKBgPXz9/Dr5fbr6/Hz8fbs4uXs5oP++Pv7hPPz+Pfx74Dy+ejj64CC9/To8PP2hID3/vjy6f6Dg//4/O/y8/v0+PDxgIL07u/w+ffw9Pf79/Lt9vaB+oCG9f749frugfryg/6ChvmCg/6AgvWB/4iHiof+hYWGhvr4gPL89PT0+Pbv6Ov18/Pu9vTq8+2A/IT69oD4gYSCx7mI+Yqm9Pj6goaB7ID4ioaAgIOIjYuOkI+LjYmMjpOSiozdo5mUkJCGiP+Kq30BfOF9AX7DfQF+9H0Bf/99r30Bfv99l32CfJV9AXyEfQN8fXyFfQF8hX0FfHx9fHyRfQR8fH19h3yKfYJ8iX0BfIV9AXyEfQV8fHx9fZB8AX2HfAx9fXx8fH19fXx9fX2FfIJ9hnwEfXx9fYR8BH19fnyGfZJ8AX2EfAF9hnwBfYV8gn2GfIJ9hnyCfYt8AX2PfAR9fH19hnwQfXx8fXx9fXx9fXx9fXx9fIR9AXyEfQN8fH2TfAd9fH18fH18hn0MfH19fHx8fX19fH18nH0CfH0CAgQAgHp5fXji6Hl6foaEeXh5enh5e315fX14fn95fuiZlIJ9hIGEgnyEg4OCgIKEgoJ8f32CiIOAgHd/iXh+c3N9joF4ent6eHd2eXl4en6ChYiFhoiP2JWFk42EiYeCfXuifIF3hoiLgHmIfn2FhYiLhYeChIeIjomGhH+DiIiHg4J+gIqGgYyIiYaCioaJjo6FiH9/hneFh3l2gImVeHp9fZKDeXuAeoJ/hIqGio+Qj4iUiIeIioeDioyHi4SJhJaUlYuKiYyQjIWIg5KSg4eIhouKj4WBhoKJjJ+Mk5yUlJeRlY6RkJqPiouEgYaLjo2JioSIoYOKkI2HjIGFiYmMh4eUgJCNl5SHhYuRmIyMlIuHi4iOkIyLhomNk56ZmI6SlIuHi42Hh4CFgoCHipKBhoiHgI2bjoiAh4mJioCChYyHkq2LkYaLiY6Ig4WFgIqChYeDhYN5gIWCf4F+goB9eXqGgK+alIuGiI2Hh4ePiYePhYyQjJGWoKeLiIh/goVqhHqGgIWQkIqFiIeJjYh2foiGfn2Ai4B8gX14e4B/gXqJg3yIfXx+fn6BhYOLi4yEiIiNjIGIh4KBg4aMh4SBiI6LiqOHgIJ+hH6DeYB+g4WAe4KCgYCDgIqBfYuHgYuBfHqDtIKGgH+piIF/eXl9iYCAf4CCf3l9fYB7g4aHgouBhHx1gHR7dnWCeH53dXxwdYuAfH2Ggnl/fIKFgoKEgnh7d3V223NqbXZ5enRzfX16fIF8enx3enh9g4ODgJaIeXx9fH59foF+eHuDkHt3h3x8cnh1eIt4fH59fX59gId9e3x8d3F6gnFvd3Jyc29xdHR3hXp6e92BfnpucHR3c3RwZ3N1gHR2cHJxbW1vb3Nsbmxte3R3dnl5cWprbnV1dXBxdXt1eXx5d3h4dnV1d8WA1dBz3dV2cHFxdIDl19/neXmCfH74gvB9f3zo59x4dXPWb3Nyy8vKz252z299fHNsbmxvbmttbm5yZ2lra2NoY8auv2NmZGhozc7Y0Nl0eOJ38P/wgODg5ejgddfaz2/WcHDQ2uB249nZ4HNt33d72t92dXft5eLj7OZ3febf2dPR1NPQ2t/T2srYcWtsy8txbW2+0nFu0XPMcnjD0dd54enf1d/c2nLe33Xded/Zb87Vd3Z1ceXY4HXY0uDYcYXVxc/X1uLh1uPjdOjr5+7zffPt1eDigOnf2t7W2dLV1eLRbcTKxtVuiHbY1nRx4nHY2NzQ08PP0MXOzNni5dPUeXx3fHvxeXzje3qAe313eO3d4N/T3+Xc4ubXz9902NHjdHXc5OPg33Xc2O57f+zrfHh9gX98e3hzenZ5bnR2dHJvd3V1dXh1eXiEdnN1c3l3cnp/eHyBgH1+gX3r631+f4SHe3x9fnx+gIF+g4J/goN8gfCXlYR+hH+CgXyCgH5+fH6DgYN8foCIkYqFhX+IjoCHf36HnoeAhIeJh4eGiISFhYOBgoF/fX5/rIB3jIaBh4SAgICxhod+iIqNg3+OhYKIh4uOiY2JiIiKjYyHhIOGh4eHhIZ9gIeCf4aCg399g4ODh4uEiYiFjIGOlYeEjZKfhIaNj6KRiIWIg4aBgYOChIJ/goCIg4OHioaFjo+BiH2AdYWJi4KDhImKioWJh5aYiouNiY2QkY2IiYSGhZOChIeDg4WAg4GEg42KiIeFhoeKjo2JioeJnIaLkZCKj4iIjYyOhYaPgIyHj42CgISQkYeMko6JjImQj42JiYmHjJKOi4WIi4SDh4qFhYOIhoOIiYyDiYeKgpWglJKIjY2OjIKEhYyFkaOOjYaNjI2GhIiFgImFhYqIj42CipOPjpKOko6Mh4iRjqzEmpCLio2Kh4aLhIWJgoWNiYySoKuSk5WPkZV/komRgJGbm4+Ljo2OlJGFkZWUjY2QmY2KjoaDiI2MjIiTjYaPh4qJiYeLiYaOjYuHioeNjH+Kh4SDhIWKhYWDhI2NjKKMhYqEiYiUg4uLkJKJiIuMioiKhIyHgI+OiJOGgoeMtoqLiIiriomIhIeJlYqIiIiKiIKGhIWAioyOi5KIioaAgH+GhYiSiIqIhIh6fZGIg4SNh3+GgoSEf4F/gHyAgX2C2oN8fYWDhn6Ah4SDg4aBgYF8fn9/hIODf5SKeXt8e4CDgoSBen6GkoB+h39/eX59f4yAfYR/gHx8fIF8f3t/enaEm355gn59fn19gYKHkIeFhrGBgn53en1/fn11cXx7gHyAe3p4c3l5eX14e3d7hHyAen2BeHN2d3l6eHV1dXp2dXh2c3p6end5d8GF7N535uB5c3RxeX7k2t/gdXJ3eHfrd+J1dnTh4t55eHbod3Z75ujc6XuF53iNnIp0c3N0cm5xcnN8dHZ2eHF5dufe4XZ2cXVz3OHf1NNwctpv4OfdgNnU4eLidtvZ1nHecXDU19lw3tLY2HBt2HV20dlvb2/c09PU3d51eNre4NrX3Njg4Obg5tXjdXFx29l2cHLP3Hd14HXedHfG0dJ23tvU0tjb2nHc43jneubectTYdHNybt3V3HTb3OvmeZDh2Nvd1OHXzdract3c0trcdOLg0d7kgO3q7O7m7uTo5fTmfOjp6fF+rIHq63556Xbp6/Lk6+Hr8OTp6/T08+The3p1d3XmdnbVc3N9eHt3eO7o6O7l6/jz9vrt5fmA+PL+gIH48vXy84Du7vl+gO7ye3p6fn56enl3fHZ4cHh7eHh4gHx9ent7fH6MgHp4dn18e4OFfoKDgIaIi4f6+oWHiI+UhISFiYaJjI+KkY6Ij46Fjf+jnouEjIaIh4KIiYmIhoWKiIiChoeNjo6LkIaRl4iNgISUrpCJkI+SkZCOkouMjYyLjI2LjIuNsZCDmI6HjouHiIahhoqAi4+RiYaTiYmSkJiblZqTk5WXm5eSj4uNkZKSj4uGgJKPh5KNkIyIj42QlJeNko+Mk4WTnYqGkpuriouSlqGYjo2RjZKLjI+OkJGPkYyXjI2PkIyNlJV9lo+VjaGkopmWmJmZlo6PjJ2fkZOUkpefoJeRmI+TmKqTmZ6WlpKMkoyQj5iQjI6Ii4+UlpKTk5CVpo+VnJyUmZKPlJKWjo2YgJaSmJiLi5GZm5GTm5WOk5CZmpqVlpWWn6qmpJyjppyco6Ofn5mdlo+WmZyMkJOWjaGroJuSmpmbmYuNj5aSoL6dmJKYlZuVlJmYkZmUl5uZoJ6PkpualJeTl5STjY+el7nxrqScnKGbmJeckZKYkJSbmJees8ednZ+YnJ6Zn5OcgJympZuVmJaWm5qKl6CemZqeqp2bopyUl5yalpOgmZOdmZiXmJienZqioqGanZugnI+ZlpOPlJOalZCRkpybmqOWlJaUm5yrlZ2eoaGbmJ2cm5iYk5yZkKSioaicmp6o9aeqoaLSpaGdlZiZppqZmZiamZGVlJeRm5ygnKaZn5WNgImQjY6ZkpmVj5iJi6Scl5qloJecmJuck5eYlpGYl5SZ4JuPk5yanZSRnJqWlpqTlZSLj46KkZSUkamchIuLiZKUkpiWj5agtJqVpZudkZeVlqeVk5mSkI6OjpSLj46Pi4eVsI+LlpKRk5GRlpieqqGdnbCWmZeMjJKTkZOPhZCNgI2Si46Kh4aGho+LioeJlYyPiIyTh4CEhouMi4WFhIyEhYmDg4aGhIKDg9KR+u2G//mFgIKAh5H97fX6hYGHgoP7hvuCgYDy7fGDg4L4hKOH9fTr9YGL9IOat6aAgYSFhIKEhYeNhImIi4GJgfvp+IKEgIOA9PT87fKBgviA/f7wgO/r9vv/hPj8+ID9gYDx8/yB/vH4/IOB/oiL9P2AgoL/8PX0+/iChPLx8Ofo6ury9P32/uv+iIKC+fqJgoPr/4eE+oXsgYfd5++H/Pvy7vPy84D69YH3hv39gff/iYOFgPnt9IHx8f36g57y5+3z6fvz5/b5gf388vn6gf714OjsgPTs6fDo8+vx6f/zgeXw7/uEyor6+IOB/YH29vzo8eHs7+Lt7Pn9/u7ohoiAhYL9g4LxhIKKg4aAg/vy8evc6fHu8ffq3fN+9fD8gIHz8/T09IDz9f+DhfX6gIGCiImEiYmGjYiLgIaLiIiGkI+PjY+NkZOjkIqEg4yHiJCTiY2PhH2CfJV9AXz/fZx9AX7/fcV9AX7/ffZ9AX65fQV8fH18fIZ9hHyFfRB8fXx9fX18fHx9fX18fX59hHwDfX18lX2DfIV9hXwEfX18fYh8DH18fHx9fH19fHx8fYR8Cn19fH19fHx9fX2GfIJ9jnwVfX19fHx9fX18fH19fH18fX18fHx9h3wLfXx8fXx9fHx9fHyEfQR8fHx9hHyCfYp8AX2FfAF9kHwBfYR8CX19fXx8fX18fZB8hX0EfH19fId9jXwGfXx8fH19hXwIfXx8fH19fHynfQICBACAgHx8dXV4eIN2eHqCiYd+fXt6fYB6fIB9gYZ7foWPi4CIi4uFh4F/hYSCfoCFeXrrhYB8eX+BgoOAgn5zhX6Cvop5d3x7b3Z6fn1whIeDhoyJlY+egZ6Pg4h+h4aFfoGJgnyLjImGgYSBfoaIg4uNhpKHh4eCgIJ/fnx5goN7iX2AhYWDg3iBh4OIfoN/hISDfnp4eHmBh4aAfpOJhYB6j4yIhX6Og3N6fHyAgoSBhYWHjI6XjZaMi46TgYCFhIiOlpSWjpCQjIZ8jI6HhIV+g4WKipGNh46EioiMjoyRlIqSkIyOjoKNkIeJhn5+h4aHko+NhZKKj5KVmYyJiISRlpeAkIaKjYuGkZGkrJWPlIyQhI+SjIqNhoGJkIyEg4KIjoiHj46Ih32IhYSGgoODjoiKiYuMh4WIhYqJh4eNh4mPi4+QkIuOjoyRn5qQkIiRg4p7goCEgoN5fYODiX2Dj4N+hISIj4x8hYqDjJCNhoaIi4aHj4mIjYqFh4mFgX6DxJOAY4qDhoWHjIaPjI2OlYyJiIqAfnyhgX19g4OJiomJgYJ7e4CDi4ODkIuJio2MkpWOiYN9hoiPmyQvkISChoB8en54d3+Be3x2dIF+gH9/fH+DhoaQkX+IfI+Be3uAeIWHgYSDiIuBg4KHh4F9in2Din6Bgn6BenuDgH97f4GAe4OAi4SWjJ6BeHGCeG9yfXh+foeIgoODgIB/enp/e3p5dXrKfnlte4J4dnh3c3t5eXN4gnl6eXuHeIR8g3+BfoZ6d3p7cXR7eHyAe3Z5eYGYw351dXB4cXJxdHV9eHd5f3x7enZ+fIN2b3d1f314a2t0enNsdm9sdHZ1cHB3dHlwgJuAd3NxbXKAZmtpa3Bucm9rc252n3yRamxtdXN3cHd4eHd3eXd1d93d3Xp3gX10cHR2eXZ6fm1rbHDSbnBwcuN3d3Z6fYt+f3zjdHHj4N9zfnVxbnRy3dZv1NjO23NxbWtqaHHUb2pjZWjIcm9qy8jCyW1tam5zdd52duHX18ra3dWA1tbNdXJxdHBze3iBeH1ydHZ33dvc3uF2d3V7zNbcd+Tu5d+vgfLy8/Dp6uDj7OLj5uDd2NHY3HPZzcbI3NbWcMTJbc5rbczHbM7Hz3PTydvd2ubd2dPRfNna4OZ0eex12NtxxcZrgHlxrIBv0srWz93R1eR4deniduTX49zn7N6A1djb1N5v1t3cdnRv3NLF39fgen5753546XV3dHLb3trXydvU1G1xcdfVbWzX1nh2c418b3uDd3Jxc3Fy03Pacdve29nbfoR+c3Nz5enofXnkdnd5eXbh6XeHe3h8dn16eXd5fHtzbG50c3F1dXlyc313e31zeHh2eHt3foaBgYKAg39+enp6fIR8fYCFjImDgYB/goWAgoSBg4aAgYSMin2EhoWBhX9+hIWFf4OKgH78iIeBfIODgoKAhH93h4OKxJWFh4yJgIWJiYV6hIR9fYGBiYaOeo+LfoZ+g4ODf4CKgn2JiYeGgoSFfoSGhIuKh42HhIaEhYODhImEi46GkoSAjouMhnuEioiLhoeGiYqKiYSCgIOMkY2IhZaPjYqGmJOQiIeWjoCBhYiIiYuFh4eGi4uPipOPkZKVh4aDhoeIiYCEgoeKiYmDjZOJhoqHioqKi5CKg4d+g3+DhIOJjISNioiMjoaNlIyRjYaHiomJkY+MiZCKjZOWmY2Oi4ePlpeAkISIi4WBiYuap5GNkoyQh5GPj4+RioWKjoyDh4aJk4mJjI6Jh4CHh4mHhoiFjIWGh4uOiomKiYyNiYeLhYSHhoqJioqMiISOlJGKiIOKgoqAhYeLi46GipGRk4qNmo6KjIyPlZCBio2Ii46KhIWGiIaFi4qLkJGOkZGMioaL3ZqAcJCMioqNj4mTkJGQkouNj5CKjIuzioeJj4+YmJWXkY2IhYeKj4mHjomJh4mKjJGIh4WAhouTp1JRmY2JjYuJhouHhoyOjI2Hg5CNioiJiIqKjYyTkYSOg5iJgX+HgYqMh4mJio2EiIaKi4SEjYGKkYqKiYiPhIaMh4mChoiHhImAkI2mm66SioeRjISFjYWGh4qHgYGAhIOEgYKGh4aGgoKth4d8hIaBgH9/fIB+fXh+g31/fXyJeIJ9gYOCfoiAfoKDen+EgYSIgn+AhIiq74uGgYSIgIN+f4KFf3+Ag31+fXl/g4yAdn19h4KAdXN7g3x7g3p6goN9fXl+dn92ia2AfXx8fIOQfH17eoF+gIl5fniDuIKMcHR0enp5cnh6ent6eHh3eeLh33t2f3p0b3R3eXh9g3JydXbdcnNyc951dXN2eo14d3Pgcm3a2NtzhHZ0cnZ15uR16Ovi7Hx6d3NxcHffd3V0eHvwgH166OTc4XZycHF0dd51ct7b3NHi4dmA2d7UdXNxcWlrcnZ9cHdtb29v1NbS2N1wdHN7zNbZdd7k3NmfduLi4uHg4dXg4uPh5Obi393i43Xl2tbd6Orfd9zadeVzc9nRcd3V3XXc1OLm5O/l4d/Ve9nU2Ntvc+Z03eJ22dx4iXx3toZ64Nnc1+LW2Nxxcd/cctzZ3t3o9OmA4ujo5u966PDre3h05t/Y59zgeXx67n947Hh5eXnt8O3o3+7v73h7fuzmdXbq33x6eIt4b3l/dnRzdXR04Hjpee3u7vfxjpSShYKA+Pv6hIL1fn9/fXrn6n2Qfnl6dXx4enh6f4F8eHh7ent/fYF8e4R8f4B4fHp4eH17gYeEhIKAkYyMh4WEhY6EhIiPl5SPjIuKjpOLjY+LjJGHiIuUkIONjpCLjoaHkY+Rh4mRg4X/kImCg46Qj5ORlY+El5OW4qCKjJSRhYySlJCDkJKLjI2KlY6ThKCXjJSMlJOUjo+Yj4STko6KhoqJhI2Pi5eWkJmQj4+JioqIh4eBjI+Ik4iAj46MjIKIjYuOhYuNkI+TkY2LjI2YmJONiZySj4qFmpaSjoydk4WHjZGSk5aPjpCPlpSZkpmTkpOejY+SlZaeo6GhoKKjnJSKlZeNio2IiIePkpmVjZWPlpOYl5aZm5OdmpWZl4qUm5KWkouKkI6TnJeWkJqRmJygpZOQi4iTl5qAlIiKjoyIkZSjrJiUnJWZkZ2eoJ6jnJaepaSZnJiZn5abn5+ampGcmZyXkpGQmJCSk5eZlZeamJ2dmJaVjpCVkpiRkpGSlI+cp6KfnZehmaGWm5qgn6CTl6Cgopabp5uWmJmcoJ6Ml5uVmpqWjZCQk46NlJCSm5mUl5mVkZCZ9KuAiaCZl5aanJOdl5ubnpeXmJ2WmZfBmZOWnZyhop2glZGMjZCWnJeZoZiXlJiZnKOalpSOl52nxvW2qJ2an5uZl5yZlp+in6CZl6OjoaCfoKCipaOrqpukmrCfkpKblZ+inaCeoaOYnpqgoJWVnpGXopWVlpWck5Wcl5uSlpmZkpqAnpq5qLSZj4eWlIuNmpSamKChmZufoJyclZmcnJ6fmpvNoqCQnKCYlpKPjZKMiYSLkoiLiIuWh5OLjZCRjpiNipKQh5CYlJidlpGSl5+z4JuTjI6TjIyGiI2OioeNk4uPjIiPk52SiI6OmZeVh4WQl5CKkoeIjo6Mi4aMiZaKns2AjI2NiJGhiIyLi5SRm/uKj4md8ZWsgIWFjYmKgoSIioiJiIaEhPr9/IyKlJGKhYuQkJGVnYaDhof8goOCgfuFgoKHi6qGiIT9g4D89/2CnImFgYaE/fmC+f3u/4eFhYCCgIn/jIiAgYX+jYqF+/fu9oSEgIKGhPyCgfrz9On4/vaA/P/xh4eEhYCDi4yUh46Bg4WF9vT0+v2Bg4GK4+vygPv/9fGrgv76/Pz49uzw8/r09/z08O31+4H67+7u/Pv2guvvgfOBgvTqgvzx9oL25vHy8f39//X0kPbz+PuBgv+A8faD6O+Dk4eAw46A7OXw5/bp7viBgPr1gPXm7Oj3/vCA5+rw9PyD9v7+hoOA+e/m/vP6hYmF+oiD/YCDgYD7/Pz27v77+oGEhv79goD8+YuMhqqPgI6XiIaEg4KB7oHzgPXy7PDqjpaVgX9++vj/hYH4gYKDgYD0+YigioaIgImEhoOFi4+HgYKJiIiNi4+Dh4+HjY6GiIiIjI+PlZqXk5OvfQF8/33/fdJ9AX62fQKBg/991X0Bfpd9g3yQfQF8hH0BfIl9Bnx9fXx8fId9A3x8fYR8h30BfIV9BHx9fX2EfIZ9A3x9fYp8j32FfIR9BHx8fH2EfIJ9knwBfYd8Dn18fH18fX18fH18fHx9inwBfYR8CX19fH18fH18fId9iHwFfX18fH2MfAd9fHx8fX19hnwHfX19fH19fIR9iHwJfX19fHx9fXx8jn0EfH18fYV8hn0GfHx8fX18hX2CfKh9AgIEAIB9fX5+e4KBfXhxcHR9fHh6eXeAd3l+jICEg3t9fHiBen2Cf36ChoODf4V/hYJ/f4Z9f354gHuGd3N+dHl6gn58iIJ4d3t5cnR6foF/gYJ/iYWFiY6JioWIhYWHiouJh4qLjImOiIWBfYySiYaHi4qLjIKGgYeFiIePg4KBiYaNiYCJkYuVlYyUh5GJhISKhY6Ag3x5oZGQmpSUkIuQj4qNm4qajJOBgIaHhZCIhICFg4eDg4qHhoSJi4h7jYaLgoGJhIiUkY6QhYyJhJeKh46Ah39+f4aEg4N7hIGEgIN/f4Z+iI2YhoSEiYuKhoWNkI6Fg5eIiomEiH+EhIiBhI2jiYCDin57k4uOlZyQkpuWlpKJkIyKiYuZhomFfX+CeX1+gYaEioWAg4SAgILDjY2KjImIipeGj46GhYaEhoOCf4CEfoaHspCDgIJdj4aLi4eGg3+IhYaEgIKHhIWCh4uKioyOiIqLlI+Kh4mMj42Ii4uMkIiJiYuLhYKMg3+Af318hoCNgYiJh4WNiJC1jIeOh4SIiX+RiYaFiYuLhYCFn4SEgH17hoePg4mSkY2HhoeHiYOFgX6CiYKOiomCgYd7d3V3dn+CjHqAfYKFiIKCen6GioSGgIKOhYR9gYSBfnh7hYOBh4WAiYaEiIiLw4WEf4KIf4B8fX9+hIaJfYB7UmB/f4CNioyNjH90gHt9g3h3f5d5dH19fHl8eXx7eHh6eXR4e6qIhHyDen19g4OMfn+EgXl7hnqChHt5dnN7gYWFjZJ+cHB2cW9zd3N1cHV6fnd5d3d0cnN7hXx1c3d+enR5krF9dnp7gYB3eHd8gX11dHV3d4t8bGl3fnhzb3Nyb3pwcIBvamtrbHJrbb9pb3VxdXl0dYBwbXZvb210dHFucHFwc3t7dnRydnh2cnV6dnd4enp+eHZ2c3l8fayKdnp3dnp4gXVzd9pygXtye31z03R2ctDTenZtb3B13dzcdHdw2shpZ2tnamtmfMhqzGlnwGvGy8lqy25s0H91ydrL1m1vcIBscWtxdnBvcHt8fISGuXbi3X/r4Mzb3ufu6+97eO737PL48u337+fj9Obt6+jq1tzq23512tvi09bT2s/Ow9jRx9bP0nDf4HZxcHtzddXaxXN0ctl1dHV0ee13dXiWhnfa4tzH19Rtc3jU2uLT39Xmc3V2eIB85urr49ruiYVzy4Dj6tjZ1tDagd3i29/g2N536HTc3NZ03nV32tLJxcTFxM3M0c7T0NvKb3V2enp6fnyL4Hp+eHd1end2dnN0dnyLd3R1b+Dhbdp/fHZzdHh1fn+AkIx/fX1873uEeuRzd3l5dW3UcHPZ0txzfG53eHXafYJ8eoR6dnh/e3t+gHyAg4B+fYOBfoSDg4F4enyEgH5/fX6CfH6BjIaHhoOFhIOGgIaGg4SHiYSCf4J9gYKBfoeBhoR/h4SNgoGJfYOBiYCCj42FiIqGfH6Bg4SFhYSDiIaEhIeEiIWHgX+Cg4aEgYSHiIaKiISDgY6XiIaDh4aIhn+AfoaGh4eOioWGiouPiTOGj4uQk4mRh46JhIWLho6HhoB8m5KRmJKSjIqNjoWIkoeTipKFgoaLjJSMiIWLhoqHhIqEi4CNi4GMiZCKh4uGi5CLiI2GiYuNn5KOlouSjoyPk5ONkYqLiouJioWFi4mPlZ6NiIiKjIyHgYiLi4KCk4qMjIqRiY+Pl46Pl6yRioyEgpCGipGTio2Sjo6Kho6NiYuOso+SjYiIjoWHi4mNjJGNjI6NioqJw5CPi4uGhIaQhY6PiYCHiYaMiYuIho6KkZLGnYmCh3GVh4yNjIqLiY2Ojo2KjI6Pj4qOjY+Lj4+IjY+ZioqHjYqPjIqKio+SiYmLjY+MipCLiYyMi4iRkoKKi4iFjIeSuZGHjIyJjI6Fl42IiJKTkI2Lkq6Sj5GIiZCOk4uOkY2Mh4mJi4mGi4uMjZWNnoCal5OSl4uHiImIkJKch4yLjJGSjoyAho+SjY2GiZCMiYOIjISBf4GLioSPiYOLh4WGh4i4hYWAhYyJh4OHhoSLjo+JjYdaYYqHlpOXlJWMgIuHjJWMiZSskISKioaAgoGDhYGBg4N9f4KkjImChXt8e4GEin9/hoSAg4+Ch4mCfoB7enyDhYePloJ4en98e3+BfH17goeIgYCAgn+Cf4eVgn59fIJ/fX+TsIN7f4KJhXl8e4GFhHt7en5/i391dIGGgnt6e3p3hXx/fnt8fHqCfHvbdnp+fX6Ee3+KfHZ/enl4e3x5d3l7eXmDgXt4dnt7d3FxdXJycnRydnJyc250eoB4nHR3enNzdnV8cnJy02x7eHJ5eXLScndz19x9d3Fzcnng5d14enjp2HNxd3N2d3aM43jpd3fheOTg3HTidHHYfH7S49Hcb3FwaW5obHNra2lrbnF3d7Rr0cpz2dTGzdLa29bccm7Y4Nfc4N/g3+Dc1+jb5+jl49ra5N15dt7e44DW3uLp4+PX4+Pj6ujtd+ficnJwdnRz09vEdnV133dydnNz4nN0do+Ed+Dj39Tr6nh9f+Tl597j3ONzc3R2enfg4uba1ed9gHPJ4efc4N7c54Xl597f493jd+t24eXjevV8gvPt6uXt6ujw8u7p7+bo3nZ4eXl4eX16ldd1fHV1ckl3dHNycXR2f42EgIN/+vt++YqJhICAhX+CgoWUj4B9f3rsent55nx9f35/fO17fvXu8XyDen99fOuDiH9+jH14eX98e3t9fH6BgIqKjo2Jjo2LiYGBhY2LioyMjZOJjI+bkpCQi4qIhomEiIaJiIqPioqFjIaOjImLk42MjoeRjZiLh5KFi4+ZkZCbl42Pko2Gh4yPkZKTlI+ajpOXmZeZk5aPjZGVlJGMj5OPjZOQiouGl5+Rjo6TkZOQiIyHjo2KiY6IgYKKiY2HgIaOipOSjZOJkYiJipGLlouKgoKkmZWbl5aNiY6Oho2bjqCXn5GPlJqaoJeUj5WPlpGPkpORjpSVlYmYlJuUk5uYn6ekn6OZoJ2XqJmXnJCYmJWXnJmWnJaWmJaTlI6QlJWeo66alpqdnp6YkpianZSRo5SWlZGWj5SPloyQmrGVgI2PgoKWjpGcpJSZo5+el5GenJidn9GhpKGZlpuNkpaYm5mfmJiVlJKVlO+bnZiblZSWpZaiopmVl5OZk5OOkpmVnZ3krpaTmY2qmp6goJ6am6CenpyYmZ+dn5ienqCeoqKanqCtnJyYnJmamJeWlZmckZKSl5qUk5uWkJKTkYyXgJ2Jk5SSkJuUocmglp2bl5ubk6eemJegoKCblpyym5iakpOamqGYnaKfnpianJ2el5uZlpeemqupqp6fqJmZl5yboaSumaGbnaWjn56TmqSoo6eboKuno52kp52alZikoZymoJiinp2eoaThnZ2Vm6CampSYl5ScnZ+TmZCInpOPgKGho6KelIiQj5Kfl5amwqGWnp6dl5qanZyYl5eblJecuaakm6CTlZGVlZ2SkpWWj5Gaj5SWjYuIh4ySlZqhrJiNjZKRkJeZl5iTm6CkmpqZmZKVkJikkI2JhI+Kh4yeu5CJi5GamIuMjJKYlouMi5CRoZKFhJOYkoqEh4iEk4qLgImFiomKlIuL9YaMk5KWnZGWppGLl46OjI6PiYmHioiIkpKNiYiOkIyEhouKiYiMjI2Gg4WCh42KrHmEiYeGh4SPhYSF9ICTkoaSk4v9ipCK/P6UioKBgoT0+O+FiIT/8YGBioSEhIGV9oP+hIDxifr4+YL/hIHwuqHp/un/hIaFgIOIgoeLhYSEjI6LkJPtgPfuiPvy3u3t/fz8/oWA+v/1+fv68/f48eX86/f88vnw7vfvg4D08/rp7Oz57+ze8ezp9e/0gv/8g4SCjIeB7ebGhoaD94iFhoOC/4KChaSXhv368+T8+YCEivL09O737/6BgoKDjYf5/Pvv6v6NkIDdgPj87/Pw7fSK8vbw+fTt84H+gfX08ID/hYv/+fHv7fDw+Pn48/z3/uyBg4OCgoSHhZLsgomDgoGIhoOEgoCDipiNhYeE/v2A/4uJhIB/g4GDiI6dmYiFh4P8g4mF+YWHiYqGgfqDg/Tw+oaOgYuLh/yOlI6LmYuEh4uKh4uLiYyQ/33/fcp9AX7/fbR9gn7AfQF+yX0BfLB9AX6KfQF8h30GfH19fXx8hn0IfHx8fX19fHyIfRF8fXx9fXx9fHx8fXx9fXx9fYR8kn0DfHx9iXyCfZV8gn2QfAN9fHyGfQd8fHx9fX18hX0BfIZ9hnyDfYd8hn2GfIN9iHwBfYd8Cn18fXx8fH18fX2PfIl9AXySfQR8fH18kH0FfH19fXyGfQZ8fX18fHyGfQF8kH0CAgQAgICGeu9+e3l+e3x6gIWBhn+KhoF9gX6AhYSBgJ6q5HZ9gHx4fHJ7e3l7eXZ1eHt9e3+Afn2AgIF+fnZ4fIJ4e5F4fXuBf3p7fYB+f350gXt+h4mBhY2OiIuLnYmPlJKIjYWBf4WHgoWRj4qIio2Qj4uRiJCKiZWTi5CRjo2GjY6SgJiSkZGRlI2OzJSUkY6AfoeIgnqUh2+XhqKYkJ+Sj5CV+IiKjH2Cj4uKmImGio1/fnyKiIGLiIuKf3eLh4WFiomCf4CJg4uFhIGFh4KGioSLhIiFhYWGi4OCioeInYWDiYGGg4iJiISKg42Eh5CXlY+Gio2KlYaMi4mKi4+Sl5GQgIiMkY6RkZmXl4eOkY6Pj4aVjY2DjI2KkYCGiYqEgYOFg4F8foZ8foaIi4OEiYeIh4mMjI6Sl5WMh4OCjZCKjIeCiIWKhH+FhImQjpuMmpeLg4yNiYKHjoaHi5WPjpGYkY+HiYeSj4uLjJCNg4mWjYiGi4mMjI2QgYGGjI2Qj46SF4+QkIyMiX+Gho+JhoWEhJKOh4F8iYF3hIOAgYiIhYWJp4SEhX2DjYOOi4mJfIqGiYiGhH6Cf3h3gnqGjH99fnx/fISAhHR5g4R8f392goCFgYN9foKDe3t9fn99g4F+fYKAfoJ8eXV4fXl/fYF+gYN/gnt6gniBjYKBfn56hYdWlIaOgH+DgIJ9dn53gnVzd3aDe3l3f4GCf4CAenV6d36Bfnx1d3t9fX9+f4CBe4KVfYB9e3t3fIN4e3t+fneAgG5qc3J4e3ZxfoGAf3h5fH54dHBxb4mGfoGBgIF/eYZ+dXx7f6mZeHN7eHZ1dXt1dHR6dG1wdHF1c3N3cm9wa2xtbHBwbGxvaW1ycXl6enh0dnl1onZxcGbWdIuAj3h2eHZxdW9rcnF0fXh0cnJzcXJ1cHJ6eHh6dHl7fH161XR1c3l12HZvfXZyhn11b2x2d3PZcXDcbcrTztxy29TPc3dw1HJwcnBvbMdrb21py8DHz8bIcG1qZ3Jee2loamtqw4zNZs1rdXNzcXN1eXjlenXg6Znh5+ny+fTt9vmA4Zbnhn942dzf1NjW2+Hk4ufs5up44eHS1c3Y123WcGtsbtTLx8bTz8hr1Miwa8fPzHDb09VwcXJ0S3l02NGNfNra5Hfc1eBucHFvb3R/ddjndtzo5dzb3NrZ4dXdct7X2uzveezo7OTZ2eTj4enq6nfp3ujo7IF34eXn4uh3gndl5uTo5NSDdHXg4nFzctpyd3566+d25ersfX+CeujphHl353h3d3N6dtrgd3GGeHt233t79Xz17OzffH13dH15cW90c3t6fvfs8oR/et7ZfHd0d36Ke3t6fYGCfXx/fn55eYaChYGAgId88Xt6en9+fHx/hH+FgIKFgX2Df4KIhoKIscH8fYeKhISJgIaEhIaCgIB9gH+Ag4SEg4eGh4GDe4CDiICCoIWEhYqNh4mOlI6Nin6HgoOIiIGDhoWAhIKQe4OGhn6JhIB+hoqFhpSQhIODhYqFhIiChYGCjouCiIuHhICHiIiAjIiGhomLh4rcjY6OjoSEjI2GhJePg6GMpZiPmJCOjpD/hoeKfX6KiYmVh4qOk4OGhZCMg5GLkI+GgJCPjIyLjIWChYyJkImIh4uQjY6QjZGOj42PjouSjIqRk5GjkI6TkJORlI+OjI2IkYqHipKQjISJiomPh4yMjoyOkpifk5CAiImNi46JkI+PgoeKi4yLhpGOjoiNmZCShouPiYiHi42Nj4qNkomJipKSjI+TjYyMiouKhoiLjIaIhYKJi4eIhYeMiYuJhYqHjZWPmouTk4aHi46JhouMh4mMkoyIjJCMi4OGhI2Li4uOj4uFiJSPjIaJiI6Nj5GFh4mOkJCNkJKAjY6Ph4qGg4aHjoqHhoeEkY+LiIaTioaOkJKPj5iRi46QuYuNkYuNlYqRkpCRiJGRk5WSkIuQjYWHk4qWmpOQkpCSlJWQl4iLkpSOjYqDkIyMjI6Lh4yNiIqNjI+OkY+LjZGRi5CIiYWHiYeLhYmGjI2FjomGkIiOn4+RiouHkJOAW6CVm46Pj4uMhoOMhJCChYeFk4yIgYeIhIKFf3uAgIaGhoN/e4CChISEiIiGhoyiioqIh4OChYyCg4WHiYCLiXx2gH6BgXt2gYN+f3h+gYOBfoJ+fZqNiImHhIODfIeCeICBiLKgfXmBfX5+foB6eX2Bfnl7f3yBfn+CgHt8eXuAfHyAgH5/fHZ2enl9e3p2dXl8ebh9enhw7XyOkH2AgXx5fHhzeXd6f3t3e3Z1cnN1cHJ9eXZ2cHZ3en530HN0dHp32nVvfXJwkoJ1b253eXTjc3TjctPV1eZ34t3eent44Hd6eHd5d+V4fnp36+Tn5d/Yd3Nxb3dsmG5ub3J11YiA1WnYb3Rxb25ram9v1HFrz9eLys/V1tzY09fi0oLHe3d00t3k19zZ397i3+jp5OZ24d3Y29vd23DfdHFzdeDU0tnk4Nt16N7BdtPV2XPg08p1c3J5eaB029WLftTd5Hfi4ud1dHRycnV9d93kduDr7OXl5OTe3tvdct3Y2ufoeO6A7PDp5Nzh5uXo5+d14+Hq5uV+dd7p6ObreIV87/D27eSIfH3s8nd3eeZ3eXt46ex13+DkeHp6d+bhfHZ15nh6fHmAfe70gIOxh4h+5Xl573rx7O/de393d4J/enp9foOAgv/49YGAfPLrgn17fYGIfH1+f4CCfHp+fXt8eoOBhIGAi5CB/oWAgIOFhoGJkIqQjZOTjomNi4yQjoqMuMf9gYuOhYeLgIeJh4uIh4iGiouOj4+PkJSTlI6RiI2SmI2Oq4+QjpGUj5GWmpiYk4mWjY6UmJOZnZ6WmZikjZeamoyYko+KkpiQkJ2akY+Qk5eSjpCKjoeGk4+GjJKLi4iQk5WAmpaVmZWZkpb3mZ2bl4mNlZiPjqGWiaeSqZuTn5ORk5T+joyThIeZlpSjlZSYmoqLiJKQi5iVmJyQiZyemZienpaSlZ2Yn5iWlZaXkJKUkZaWmJSamJegmZWfnJ3CmJiamqGcoJuZmJuXnpWQmJ+emY+TkpGZj5STk46PkJmalZKAh4mLj5GPmZiZiZOZmpmZlKKdnJadqqCol5+gnpSUmpudkpCSl4yMj5iWj5GUlZKYmJuanKKnp6CgmpWgn5ufmpqhm5yXlZeNlp2VpJGeoJORmpuXlZqgm5ygqaKfpKqioJWZk56dmZaYlpGMj5yWkYyPjZWXm5yPk5ebnaCen6GAnZ6dlZiVj5SVnZqUlpiZqqijnJeim5KeoaKfmaCbmZyhyZqcm5ecp5umoqCelaKjo6SjnJifnJCXopelqqOcoaKioqmgp5WZoqObmZmPn52fnZ6al56fmpqcmJuepKKZnJ+dmZ2VlI+TlpSalp2co6Wbo5uXn5afq6GgmpqWnp+AidKcoZaWlZKXk46alKSVmJ+gsaWgmKGempWZj4qSkZmVlJCLjZGUmJaWmJqalJ22mpuZlpWVlp+VlpibnZShn4+JlJSZmpWQnp+cm5OZnqKamJqWlLWoo56ampWWjZiShpGRm/LclI2XkpCRlJWNi46TjIeJkYySj4+TjoiLh4iAiIaLioiLi4SIjouSkZORjpKYksaYkIyC/oufooaLiomDiYaCio2PmJKOjouKh4eJgoSNh4qIg4iLi42K9IaFhY2I/4qFlIqIqZmKg4CKjof/gYP/gu3x7/6E/Pb0h4qE/YmNi4iHg/SCiISC//f/+PbyiISCgYqaxYGChISI9qaA/4D/g42KiYSEgoiE/YqB+f2l7PLx9/v16/D75orEjomH6/b76/Pr7uvp6/P08PWA8/Dn8PT5+oH9hYCDhf308PD9/POC/e72hfL294L/6teGhIKM9NOE/PKcjfD0/IT09/+BgYGAgIKJgez0ge35+e72+/n3/vb4gPfu8f7/gvyA9/318O/1+fP9+/iB+vD7+f2PgvH+9vX+go+B+Pb79OySg4X6/oCChPmEhIeE/v6B8/f5g4eIg/z7iYKC+4SEhIGGg/T0hIKhiIqD84OC/4P88/Tog4iCgYyIgoOHhIeDg/v9+4yJhP31jYmGiI2ZiYeFh4qNhIWJiYeDhZCMjIwEfX19fJl9AXz3fQF+in0BfP99/33hfQF+/32YfQF8o30BfIV9AXyNfQV8fX18fYR8CH18fHx9fX18hn0BfIR9hnyFfQF+hn0FfH18fXyJfQZ8fX18fH2KfIV9jnwBfYd8An18hH2HfAx9fHx9fXx8fH18fHyEfQ5/fX18fH19fHx8fXx8fIh9A3x8fYt8AX2FfAF9jHwBfYV8gn2FfIN9hXwJfX19fHx9fX18hH0GfHx9fHx8hH0GfHx9fX18hn2CfIZ9BXx9fXx9hHyNfQh8fHx9fX18fJd9AgIEAICIgoKAeYSDh4SFh4iBhoR+hIaNkISDfHx8e356e3iKhH56dnWCfYKCg4R3eoCEeXh/f358fXx8eXd2enl6ent6d3eKd317g3eBhoR/fH6HgYODiIqHiIJ9gIODgYV6gHh7e4KEhoSBioqBfYOGhoCJj4eJi4eFjYaRj5eNj4yJjICMko2TlJKPkZKUl5KIi5mNh4GLgIaMjJGwi4iLhImYhHqEhoGBjpOQiYSFgYeHioiEgYaLeJqIe46LiYWAhYSGhIGIi4aIjJeQkYqSiYWNiY+HjpGSioyHgIiFhqCLgYaDiX59hYuJgoOCh5OGgICKioePiYyQkIyFgpGrg4uLiIB9hoWSkouKj46MjJCHiouNkY+MkIqEjIuLkpGHho2Oko+Oh5COhYuKhoiNiImOhYiOho2MiI2Ql5aOiImGhYKGgoSJiIqEkIOIhKuTk4+IhIOQiIWNh46UkIySlZOOlJKIjoqMkX+BfoWCjoODfYmDiYqPi4WJh4eXj5COjZGMmYCSjJGSko2Ih7yHjYp/goqQioSLjomCgYJ/o4aGiJZ+goWFf4qMi4mQipCGfoSFhYWDfbp6gIqVjIeBh4eIkId+f3l7da95gYWAgYaAiYR9fH98fHt6fHh5fHiCfXh3fXx4fXt9gnaAenh7fId8fHd4eX9nfYJ8eYN+hIODhYeGgIByp4OHfn+Le3NzeYGDfYF9eHZ+en97f3qBf4F8d3Z0eLGGf3V6eICEeH+BgoB8g498fHV1c3p2eHNycXF3fHx2fHVvc3V8c3eBfH+BgoJ9dnOEc3BpbXF5c3uDfH58f4x5hIiFg3x+gn58enl9gIF9eoJxenV5gHV4enxvdXJ7d4B0cnVydXR8dXh/eX54cndycG92dFVpa2Z1e3h4fn95dnl6e3dxlXd8dHNtjXZtbnV8b3V1eXlvjnx4e254d3dxbmlrdXiVemxraW5tb3BpbHRxb3tqzM3S1HLW0NFr1tHSbM3PbcvR021tcM3NbMTZ0nN0b2ppbM9pbGZjY2RkZYBjamfNym5sx83YdN1wc+jz6PPp3+/f2eHs6dNzidjWcnt35HHbfczN1+Hg2NnX33R439DSaGzNzs9t0c3Bz9bZ2XV/znB15HJy2uF9dtPc1XLWzHJxcnTm39/n5efdceHQb9nb3NvY39jY33RUUod9d3LV3HJ53szYfdbW5ul3d4B5fuzu6+3m9vLx6OnZ1+bp6eh8f8N/fOno3uHf5dbn233i2Nbq5Xpzd358fHx473yAgfPp9Onue3h75I+BgHt/eXh8d3R5b3p2eZV3gPZ+gH+Ein15iHV3gnuAiIWFiHyDf311gX188HuAe+V2h4OJhnl5gHd9fnV4gH14gomGhIB+fX16dH1+hH6Bg4R9gX99gIKIjIWCfn5/f4OChIGak4qEgYCNhYqOi4yBhIuRiIiNh4mIiYiHiIeFiYaIhoaIhYOVhIuJjISMj4qGgoGHhIWDhoiIiISChoeIiYuGiISGhomLjIiGkY2Ef4OGhoKJi4iHhYKCin+Ki5GGioaFhYCEiISJiomHiIiJjYyHiZqNhYWMg4uPjZOxjYyNipGWioCHh4WEjpSRjImGhYyKjIyLhouQf5uPg5KRj42KjouOioaLjYiKjpSPjYyPiIaMiIiFiZCPiIyIg42Qj6ySi46Sk4mGjJKPhoqHjZWJgoKGi4iMiYyOko2IiZaui5GSjYCChoiUlYyOjo2Ljo6JjIuOkpKOkZGKkpGQl5iMjJCSkpGQiZWQio+TkJKVj5GSioyTjZGPi4yOko6FhomKjYuIi5COj5CKlIeNi6yXlpOPiYuSjoePjJCRjYyMj4uJj4+DjomOk4iNi5OOm42OipKOkY+UlI+TkI+jlZCSjo6MlYCNiYqJiYeDg8mGjYuFiZOalY6Tk42Lh4qLwZOTk6KMjJCLh4+Pjo6VkZmQiJOUkpSWj9OOkJignZaUlpiZoJWOkIyNiciNlpyRk5qTmJWQjI+Nj5COjYuMjY6Wj5CPjY+OkI2NkIWMiIiJi6SQioiLh4x9jo6MipKPlJOTkZGOiYB6tI+PiIqUh4GDiZCQh4qGg4ONh4iEiISJhYmHhoaEh8CUj4eKhouPg4mKi4iGi5mHiICCf4eEhoKAfYCHiZGFjIJ7fX+Aen6FhIOBh4aDgIGbgn17fYGKhIeJg4OCgYt4g4SDhHp9gXx8e3t8fYGCeoV1enl8hnt/goR+goGJg4B8fX98enyDeX1/fH51cXd2dnh+fGx0dXF8f314e395cnt4gn13oYCCfH13kHt0c3l/c3t7enpvkX18gnN6eHt3eHJ0e361hHh2dXVzd3dzc355dIN0297i6n3r5eh16ubod+TieeHk6X16fOXjdtzk33R4cnByeN5vdHBubnFvcIBveHHY3nJvz9PTb9dubNfb3OPd1uHW09fg39RyjtfZcXl35XXlf9vW2ubj4N3e43Z34dnZb3La2dt35OHU2tfd23F6zm9x33Bx2OWFe9/i3HXZ2nBycXLd2tff2t/fceHbduXg39zc4ePm6npcaIZ8dnXj53Z96d7lhN3f5OF4dYB0d97i3t/c6ern4O/Z3Nzf1NlzeLF5eerm5+7s8+r284n36+z38H56e4B7eXl26nx9fO7m7efveXh46Ip+fXyBfHt9e3uAeo99frd7gvh+gHuDhHt4i3p8g3uAiISBf3uBgoB+hIB79359f+58iYGIgnl5fnl9fXd5gH53fYGCfoCTkJCNhpCQlJCSlpaPkpCNk5OboJaUjo6NjpKQko2nm5OMioeSiZCQj46BhYyRiImMj5CQkZCPkY6PlZWWlZSSjoqfi5OVmI+Ym52Yl5ecl5mTmJmWmJKMlJOTlJiOk4uOj5GVl5SRnJWMiI2QjYaMjouJiISFjoKQlJqPlpORk4CRmJOal5eSlJCSm5ePk6qXjI6ZjpOclJ2zkpCVjZOeioOOjo2NmqSgnJSSjpWSlpKLiIyUgp+Vi52bm5qVm5memZWcnZeanqSempWdl5GZk5iTmqCel5iTipiZmceajZCVn5aRl52dk5iTmqOPiYmPlpCVkJSYm5ePj526jZSUjYCChoaWlI6QkpaVmJqTlZidn6GeoZ6VnZ+eqKWYlpydopyaj5yckpqdlZSclZeYjpaZk5ubmJ6jqqmhnZ6epKGeoaGfn6Cao5KWkquempKRiYyZkIyXlJuhm52doZuboJ+Pm5aXnYuPjZaPnY+RipaRk5KXlpGTlJKonpibl5uZpYCbl5eUmJWRlM+WnZuUmKauqaCoqKSfnKOewpygoriZm6KclaCioqSspK+nm6eoqq2ro++coKeyrKWgo6Kjr6WanZWZkvGbpaidoKmfqaKemp6bm5ycnpqcoZ2knZucm5uZnJmZnpSclZeZnbGdnZaZmKCfpqSfnKSdo5+en6CdmYC/9p2ck5SfjoiLk52imp6dmZqkn6Saop+kmJ6XkJGPld6nnpSbmJ+nl5+hoJ2bn7GZmJGOjJWPkY2KiY+XmqOYpZiRlZealJqjnp2boaCgmpivl5WOkZSflZyglZeSk5+IlZyanJOZm5WWlJGUk5aVjZmHjYuPmoyOkpSLjY2WkYCMjJGNjIyXkJGYlZiQi5aTkY6ZlbWNhYCLjImHiY+LhI+MlZKK3ZeakZGJo4+FhY6UhY2LkIyCpZCOkYOKio+HiIKFjpDgnYyJh4mHjIyGh46IhJGB8PX4/If39f2A//z9gvz8g/L3+IOAhfn4guz7/YOKhoCCi/+EjIeDg4WCg4CBi4T5/oSD9Pz6hf2Bgfz+9fz16vTm4+z29eeApfH1gIuF/oL9jvDo6PT18e/x/ISH//n8gIX8+vuF/vrp8/X8+IWN7IKF/oGB9/+Phfj59Yf09IOGhYT8+O7y7vj+gf71hf7z8fLq6+Xl9IGgu5mMh4X7/oGI/ebzkO/v/PyGhoCEh/r+8/nv/PXw7PHm4u338vaDhcqHhP789vTx8OH08Yb15un384WAg4qDgYOA94aEhP/2+/T9g4CA+JmJi4ePi4aLhoOJgZCEiLGAhf+BgIGLjoeEl4KEiYGGjoyKiYGKiYiBh4aD/oSJhvqCk42WkISDjYiLjYWIkIyHkJSVkv99/33/ffV9AX6NfQF+/32UfQF+wH2EfBh9fHx8fXx8fH18fH18fHx9fX18fH18fHyGfQF8i30LfHx9fXx8fH18fX2NfAt9fXx8fX19fH18fYl8C319fHx8fX18fHx9h3wSfX18fX18fX18fH19fHx8fXx8hH2HfAR9fHx9iXwDfX5+hH0IfHx9fXx8fH2EfIR9kHyFfYl8AX2FfIh9BHx9fX2FfAR9fX18kn0BfJl9BXx9fX18lH0CAgQAgIKHiM6fjoeNhXl/e4KFiIWIioqEhIKFhX6Jg3x3fHZ8hHZ6dX5/mmiJfHiCfoGBfoGCg3h6e3102258d5mKdnp7foOAfYKCgoGAgX+AgH59eoSJd3N6fIWPgXx4f4KAgoF/gYOKjI2KkIiLn5OGg4OHgISJh4iBhIiBgIGFgYl/gISIhouLiIeKiIyMi4qKhbeSgIWIfYCMjIm/iX2BeH1+hH5/fX+AfoSBgYeChYiFh4+Kj5KIuI+SiYmKhIB/hYeBh4eDhISGhIeOkIuRlY2SlY+NkIuKi4+RjISEjIiKj4mEgo6Mjouam5KLi4aJiYl9jZGNjI6KkIqIhoqKqZGXgI+Qj4GJkI2Ui5GMlY+Uko6Qj5KZmJiQlYiNmYuJko+Rio2LkIiHiIeIj46Ni42JlJGQkYmMmJKLmIyOjJCKiqeSjomIiY2Ki46DiYuNiYGBjI2HhYuHlpaKiomIkYqNjY6LioeQjISBh4ODjYCGhoeNm7WGhIqToI+Wl4+UkZiRgI+VkJqalJuUko2Rl5KMjpiLfH+BgH2BfYCHhIuPh46BhICDgoSIhIeFg358gYGEh4WCgYGEiIWLhIWIg4l8hHuBfoF8e3iBgHaBg56OgImSh4aLgn+AjoeBfIqBhXt+foyIiH56gYWLm4qLh42ShXaCf3p8fXp3goWChIiAjn2DgImHgIWAi5t3dX+Ihn5mZHx+eYCCg397fIajhHl2lYN4c3l8gHl9c3d4cHKAd3x8fXp0eX+Bf4V8hXN4fbJ9eYGognx6h36CiIFveYmXend9c3NwcnVvdHd8eHh1enV9fX2DhpGHf4Z9jYKHf4N5fnh4c3Z0dnx7enh6dnRydnh3gHNsb3JxcHF1bHR+dXd0dnZzdHN3YHRvb21vdHZ1ynB5bm92cXCShHB0dX5wdXJ1c3J6dXp3dXN9cHejgn19eXp2b3Jwc5N3cGzKy9Fvd3B0c3FzcXDY4dzZeH5mycdqbWFuysppxM9ye83ec29s0NRtbWhlZ2txaWhgZq9eYbjCgGPRxsnR3XTj4d3j2uTW3evg1KLQ2s3WdeTnd+Ru1dTU2aLc1dfP1ODnxdfe4uF5eHh2fHZt1XBr0m3Mys7QztDe4czExdPc7eDh283a0/Db29PeeO53duXo4uDe0dvb3ud4Zovq2NbbdXV+2YCL3XJx183Y19dudNR9g3B3d29xgNzi3+nm6O/q6PTw6/PZ5u12dnbmd3x/g3joeN/n2+924+DV2dzfcW9te9PbdHLfdOvo9H3x9O778Pn6/ID894J7hnh/gJZ87HiChvD5hYJ8gX9+gH+Afnd/e3Z2eeeBg4WChn+HgoGJg39/gYCEhIeCiXiAfH5/hH+Cf36GiYqMgHd7fqyMf3yEe3F4dnl+f4KBhISBg32ChYGIiIGBg3+Hj4CGf4eKq2WShICEhoaJhYiIjYKFiIiF+YCMhqaXiIyNiY2IhYuLjIuKioaIiIaHiJKXhYWJjJKckYyHi4+OjYiKioqLi4+LkIqLmZCIhoqKiouNjI+KiI+Lh4aLh4+GgIiMio6MioqMiYqKjIiJh8KTg4aKg4WQjZG+kYiLhYSIjYuKi5CQjpCOi4+MjI2Ii42MkJSLsJGUj46PjYiKjpCIjo2Ii4uPiIuQkYqQkImNjIuIi4iIiI2Si4mIkI+QlJCMiZGOj4yamY+KioWKi4d/jo6OjI2Mj4uKiIuOr5KYgJCQjoGIj4yQhouGj4qPjYyMjZCWlZiSlI2Uno6OkpCSj5SUm5KPjpCPkZOTk5KOl5WQkIyNl5SNm5CMi42LjaWSjYqIi46Oj5KLj5GXlY6NlZSSjJGOm5aLjYmJkIuMioqNjIqTk42NkI6QsY+TlpCZn7GQjo+Zp4+TlZCQjZOMgIiPhpGQjJKLjYWKkI+Lj5iRi46QkZCUkI2OjpOSkZKLiYWIiYyOiY2Oj42OlJKcmJmTlJGWlZGXj5GUkZiOmY+Xl5qWkJCZloqVl6eWjJWakJKWkIuMnJeTjpuRlo6MjZmVk4mHio2Sn5GTmJSVi4aMi4aJiYqGj5KOkZSMmYaLgI+OhIuIkKJ/f4mRjopua4mIgIqHioqIiZW2jomFs5CGhYmNlI6OhImMhISQhIqIiYaAhYuLiI6Gj36BiMCDf4WkioSCioKKioh7hIuXgoGHgYJ8fYJ8f4OGgoF+gn6EgX6Eg4qFen12gn+CfH93f3x+eXp5eH+Dgn+Af3p5e35+gH53e35+fHt9eHuCe3d4e3p5fHl7gIJ7eXh1e3x74Xx/eXR9eXadh3V4fYZ6fHp5eXh+enx3dHF8cXiYg3x6d3l5cXRxd597dXXf3N94fHZ6eXZ0cXLX4d7efYZ25+V6gnSB5ud35O6BkOPrd3Ny4uJ0dG5rbnSDeHVzeuJzeerpgHLs3t7k6Hfl49ri3eDS1+LZ1pTT3c7deOfpd+t34+Tk6LPs5ufi5Ovtz+Dl5uJ4dXKGeXRv2HBw3XXc3eTi2t7w7NTQzNfZ6N7W2s7YzuTY19LYcNxyct/f3N/l3OTn5ut4bonx39nbd3d73YOc6Xl26d/l3t1yd9qFg3J3eHV1gOjp5+zk4ubj4O7p6O7b5eZ0c3HfdHh8inbqeNno4/R+8Ovk5+rye3t5g+TmeHfpduvo8Xnr7+n16/Ly9Hrz8395g3mAgZyC932Ij/H1fXt3fHt6fH6Cf3p+end5e+6BhISBg4CNhX6Eg3t6f3+CgH9+hnl/e3x7f3x9e32BgYODgI6VlsalmpSbkoeQipCTkpSVlpWRko+Tk5CZmJCPk4yVnoyTipKRv5KZiIOJiouOi4yLkYaKi42H9oCSiLKgjJSVlp2Tkp2bmp2dnZeXl5KRk52ijYyPk5ukmJSNk5WRkIyPjo+UlpaSlIyPpJiKg4aIg4iMi5GHh5GNi4uSjpmNgJCWlpuZl5eZlpaamZaUlMyejJCWio2Yl5bKloqLh4iJkouLjJGTlJOSkJWRk5OPlJWUl5mRt5yfmJmempaWnqCVnZyWmJeYk5idnJednZKXl5WSl5KPjpWYk5SQl5eUoJuYlp+dn52traGbm5ecnJmPnp+enqCfo52cl5ucv5+lgJqXmIeNmJabkZmVn5menpyen6KnpKOdoZWbqJyaoaCimp2Yn5aUlJaTmJ2Zm5iWnp2bn5eaqaSeqaCgn6Kgo7yspp+eoKaioJ+Wl5ubl4yKl5qVk5iXqqicop2dp6GhoJyfnZWgm5SSl5KUsZGWmZSiprqVk5SfrJidnZeZmp+agJifk6GinaWfoJaao6SipbClmZicnp2inp2gnqSlpaidmZKYmZ6jnqWioKCgpKOtq6mkpJ+jpqGooKGloKeeq5+ppaWgnp2qppamqMGxoayzoaWooJearqGdmamfp56horKwrp6aoKGruqmrrq+4qp+rqqSkpaWfpaeko6ecqpOZgJ+fkpuXpLOLjpiioZ2QmqGkmqGhoZyWmaXSnZaT0qaZl5+jqaSlmqCflpWimJ6bnJOKjpWZlZ+Rn4eRmOuWkpvKo5mXopieo56OlKOtl5edlZaNkZWOkpSWk5CMkouSkI+YmJ+akJaQnpybl5yTmpSPioyJiY+Uk4+RkIyKipCQgI+Jj5OSkZGWkJWelJKSlpiWlZSXt6CUko6GjoyK9oyRiISPiojIoYmOkZqIjIqKiomSio+KiIWShI2/mJKPjY2Lg4iFiK2Qiob++/+HjYiKiYSEgID2/Pb3ipOA+/aCi5eY9vyB8P6HlfP9g4CA+v2FiIGAg4uhioeCifmAgv3/gID+8fH5/IL47ufw7PTj7fvw6r7t9uDwgPf4gP6D8/P1+tj89fXv8/n82fH4/PiGhYL7qImB+IKB+oLy7vLx5ev799rW2Obq+fLt7uTy6P/w9fL4gPeAgPb18vj77vn8+P2FwJv+6ebtgYCG6tbR/oGC9/P19vWBh/GcmYGHiISBgPr5+Pz39/rw7fr39v/o9/2Bg4L7goWNnIT9gOPt4/yB9e7i6fH4g4OAj/XzhIP4gPrs+4Hw9uv/7vv9/oD//oiBjICGiaeG+4GLkfv/hYeBhIKChYeKiISLhYGAgfaGiImGhoWPi4mQjYaGi4yQj5GOlYSNh4mJjYuQjI6YmZybp30BfpB9AXz/ff99/33XfYJ+sX0BftN9AX6IfQF8q32DfIl9hHwXfX19fHx9fX59fHx9fHx9fXx8fX19fHyLfQZ8fX18fH2FfAF9i3wBfYR8Bn18fH18fYR8AX2MfAx9fX1+fX19fH19fH2ZfAR9fH19inwDfX59hHwJfX19fH59fH19hXwDfX18h32QfAR9fX18hX0CfH2EfAF9hnyEfQp8fH19fH18fHx9iHwDfXx8iH0GfH19fXx8kH0BfKJ9AgIEAICDioiPhaSyxY+CgISEg4J+gnqAfX+Senp7eXh2eHp6fISEiYB9hX6Hf3p1fXqAgoGGgoeFg4iChIqHgH+KmIB9g42DiIiIg4KBhH56dXB7g3lchYiFgoSHhYGJh4OHiISFgoWHkIaMhpCRjYaMk4+SlpSTlpaRj5KciZGFiomKhoCIi4OKiomGiIWJi4aHioiKk5OQhIaJhoh/g4d8dYeHgYCBgYaJj3yGgYF/fXd9hH+Bfn6Bgn6Ei46ChYmKhYiDgoiLhI6PjYuWjpKRlYyMioaLiq2EhYeAhIGHhIKJi5CCgoyLi4GFjIaMg4eJkoiRh4uNjIqRjJCWk4yLlXqIhoCHh4eCj4+Kh4aKiY2WmZOVlY+QlY6SnpKWlY6TkZOWmJSOi5qTlYGGgYSBiYuFh4eMk5OMj5CQkIiIi42KjImVwIWfjoF4iJSMjZCDi4iQjYyJkpGOj5aXlpaUiZCRlZWQnZSWhoSLhYyKiI2Ph4SOjoiKgIuTlIKMlpGQkZSSk4CMlJWTi5KYmJiVlY2LjY6WlKKNgI2IfoaFhY+Iho2QlJGPj46UkIeOh4yMh4GNfYOFiIWEhpKHh4iMjomIg4aUgXuOiYN5gIN/f4B5g3x8gIOHg3+FiY+Kgn+Sj3+DhIiQkGCCjIWJjJmLf4KHjIiDgoKCg3qAe3l+gIOCfYqGhoCDgoqGi4W2fY18e4F6foB/en2Be4B6goWAk356iY2ChHV7enh/gHJ4eHp4enV0c353cHiAgHp/eHpycXV9d3VxeX94eYOOfH9/fIKDeYN/fXx5cHV5cH16cXOEe3Z3dnt3en+AeXhw1GqChH+Bg4SAfHt3eYx3cXVyc3V3cHFycIBvbXdwdnN0gXN3hn17d3V7dHBubHFyaYdyeGx3eHt5g3TVeX6BUE5/fHuDjXt6jnZ6e3d4eXt7eYR4knV9fIB3dHR0cHZ0dXFvcdfN0dNsb9ZxcHPb1n3c7+2Ad3lyb3B0v2ZmbXBsgHNtaHFnyWttcMdqvsO8t7doxGXJxMppz4DEz9PRz9PQy9Hf2+Ddeo7fe+Lr2tfVys/f3uXZ0tVz23RufnBw1dLPyNDW4uDh4993fXd3bMFxcHBwb87Z1NPR1uDk09jc6OTl5N/ggZrTzebu497k4+bk297a5/fs4uTd5mh5feTU1PHe7XaI3m91eNVwbsjT08/Tw2zbd3Ta3oDn4uRyduLj4njm5tzY3NPndq6n3errf3fufOry5+rk5OLs9Hx66OR13nRz5d7j6HXy7Ory+fzufvx/8fj2i4B9fO5+jISGduDh33h763t8enp8d3x6d3uAe3yAe3Z0fHx5fHx6gn14iX9/hIB8fHiCgn56d31zc3+GfIN+h4iIh4B9g4GIf5mbqoZ+fH+BgIB/gHuBgYSQfYB/fHt8fH1/gYiHjIaDjYqUiIOAhoWJjIeIhImHgoWChYeKhIiOnI+MkJiKkI+Ri4iLjYuKhYSOkotskZqQi42SkYyRjIuKj4uLioqOk4yRjZKSjIiIkIuMjI2MjpCNi5C0iJGHiouKiICIiYeLiYuKiouJjYuIiomMkpKPhoiJhomEhYuDf42OiYuOjJOWmoiSkY+QkoySlpCQi4yNjouNkZSLjZGSjo+OjI2PioyJiYaOiI6Mk4mNioiNkb+Kio6KkIuSjYyUlZuOjpaTko2Ok5CPio6Ol42YjI6Ojo2QjY6Sj4mJloSKiYCMkJKLlpWUj4uOjo6VmZWWlY+PlI+UoJKUkYyQkpOTlJKOkJyanI+Yk5OOlpGNkJGVmJiQkJGSk46OkZCPj4+aw4qekYSAjZSPkJWNkZCVkY6NkZGMi5OQkZKOhYqMkI+JmpCShYaOjJCSkJSXj42UlI2QipGWkoSKj42OjpKPi4CIkI2NhYuSk5ORkIuJjI+Xl6STiZmUi5WVlZmPjI+RkpGRkpKVkoyTjpWYlZSdj5CXlpWSkZyUkZGUl5CNjI6bioWYl5CKkZORkJWOk42NjpCUkI+PlJiTjoihooaKjJGYqHKIk4qLkZyOgoaJj4iIh4iKjISLiYqNjY+Jgo+Ki4CIhI2KjIi9gpKGgY2Fi42Lh4uQiouFi4+LnIeFoZuQkISJi4uSlISLjI2MjIqDgIuFfoaLi4WLh4uCgIWKhIKCiIqEhoqVgISFgoWDfYKEhoiFe3+If42Kf3+LhYGBg4eBhYeIhn5423GCg319foF+fn18fpiEfoOAfXt+ent7e4B6eoJ/fX+CiXp9h3p6eXl9fHh0c3iAdJF/gHV7foKBkHnke4KDZGB+fHqAiXh3h3J4d3N0dnd5eIF2inR8d3l2cHBva29ucXBucdfO19tyc+J2c3Xg2Hnc6Oh9d3p2dHh84HV1eXx5iXx1cnpt23N0dtt02tvY2Nt43HLi4+N35YDW293e3drSzNPd2N7ahJveeuXm2trf1trn3+bg2N6A5np3hXh56+nm2+Hh6efn6eV1fHV1bs5ydHR1dd3q5N3e4+fq4N3d6OPm4OPkgpra0ens4Nrg4Orq5uXc3u7l3eTi7oN6gOnd3PHl8XeA3nN5eeB0dtTh5eLm13Xqfnnn7oDy8O54eOjr6Xrn5N3h69zpesu22+boe3bse+3x6PDn7vP3+39+8u577Xl56eLo7Hj17O3y9vr2f/p+7/PzhoB9eul/i4eKfOvq5Hh46Hl7fX59e4J+eX+DfX1/fnx5f4J/gIKBk4h9hoSDhoOAgH2Cg4F/e4J7eoWIfoN9hYSEf4CNlJOYkrK2qJ+TkpSVlJGQj4qOjY+hiYmJiImIjY6PkpuaopeSm5itk4qFi4eKjYeKhoyKio2Li46Qjo+ZqJeSl6WYoqGjnpaXmpWSjImTmZCGm52Vj4+UlI6PiomLjImMi4uNlY6UjJSVj4uQmZOVlZmZnaCfmp/XmqSZnJyemoCbnJiem5qYmZmVmpiWmJaboqGfkpWWkpaLjZOJgZKTj46PkJedo5GbmJKTkYyTmpCQi42Pj4yQlpiRlZuemp6bm56jnqKhnJijm6OjqZ6emJaanMmVkJCOkpKalZKYmaGSlZ+dnZmco6GjnaCiq6Conp2doJygnaCmo52dp4eXlVKWl5eOmJiXkZCXlpqgp6Olpp6fpJuisaOlopudm52enpqYm6mipZOYj5KLmJiSmJedpKWcn6CfopygpKinqKe15qK0ppeRnqWYmp2SlJKWkY6RhJaAoqGkqKKdn6CjoZmrnpyLipKPlZOWmp+Vl6GhlpyTnqSjjZuinp2go5+em6OhnpWgpqano6Odn6Smsay7pJmppJqjoZ6knJqfo6ampaanrKeep6OssaijrJ6fpKShn52tpKSnsLCnp6Sqt6KcrqmlnKanpKOonaihoaKkpqOdoKeAqqOemLS2mZ+iprHFgp+to6esuqmanqOqpKKhpKinn6amoaeop6KapJ2cmJSdmJ2XxZSmmZWimaGkpKGlraOjmqGnobWcmrixo6STmpiYoKGSmJeYk5aOiYiUi4eRmZ2Un5mdkY+UnZOQjJKXkpGbpo2Tl5OXlo6XmJydmI6SnJKAoZqPjp2WkI+OkY6SlJaUkIf7kZmZkpKTlI+Oi4qPppCMkIyLi46Fi4yPkJCZk5KTlKOUl6SXmJiXn56Vj4+SnYquk5KEi4uNjpiG+YuPkq6di4qHjpWFhZ+DioqHiouPkY+ZjKuJlJCSjYaGhoKIhImEgoT98/f6goL+g4CB+eyAiPD9/YiBhYOCh4r4goOHjIiZioaBjID6h4aH9oLy9ff094j8gv/3/IH15Obm6Orm39/j8ej17ZOw84T5+ezn7t/o9/L78urzkP2GgpmDgvr18uPr7/T29v78gY2Kh4HwhIeEhoHw+/fu7PH19ujm7PTz8Ofn646639j6/vDn7fSA+vv39uvt/vTp8u/+xoKI9uXi/vL/gJD1gYWH+4OE7fr89/vmgfyIgvn4/vb1gIH0+vKB7+/q7vbn+oO0ofD7+oeA/4T4/u7x7fDy/P6Dg/n5g/iAgPr0+vqA//T1/vn784D8gPT39oqChID6hpOQlYT5/vSBg/yDgYKDhIOLiIUqh4uFiIiGhIGLjImJiouakoqalJKWkY6LiY+RjYmIj4uJlJqQlImXlZWQh30Bfsl9AX7/fax9AX7/feV9AX7/fYV9Anx+uH0GfH19fX9+o32EfAx9fXx9fX18fH18fHyHfQF8i30GfH19fXx9hXwHfXx9fHx8fY58BH19fH2NfAJ9fIV9i3yFfQF8hX2RfIJ9lHwDf319hnwJfX18fX19fH19hnwEfXx9fYV8Bn19fHx8fYd8Cn19fXx8fH19fH2JfAh9fXx8fXx9fYR8AX2HfAZ9fH18fHyEfQF8hX0GfHx8fX18s30CAgQAgIeIgYqKi4WFi4mJjYeKhYWHhIuBfoOEfImGhoSBiIOBgnqCiYCBf4OBh4uHhoqLioqBiYqJkIiPh4GAjIyKf4aEhIONhYZ6fX1/gnx4foSHk4mPjIqDfoKDgIWJfX+FgoiPiIuGjYydn5WSko+TkJOTjpOKk4yHkYKJkISKiIqMgIeHhY2Hgo2EgomLkJCRkI6Oio6Ri4uPlYiBg4aEioaNhZKTgoSJkIaBiIiEhYWJgIKDhH1+goeGgZOMjI+PlH6FiIeMjImIi4+QjJWQk5CSjYiMj4OQioGDhoWKf4eLlYiEkpOPjoyFjpSMhI6OmY+HkJaXj5CkmpvJmZSalJCQgJSLjpCXjo2Lg4WWg5eblpiWlYycmKazlpCWoZeblpmUkYyRkJSOio6Lh3+Cg4B8gYyIi4uJjomLi4qEhIR+hYGTk6OMj4CEjImJhoeHiYiDiH2HlZGJjZKNk5WTk5WOlY+VkIWSjI2LhYuHiYiKhYuHgIqJkI+NjIiKi4SOk5GSgI+RhoyEgYeChH6CfYiOh5CRkY6ljoyMlZKIgoCAhIWMj5KNi4mWhYqMkpOJh4yJkI6GiIyMgoeGjIiMjZKWiJCCf4WLhYaDpYmFdIeThIGKhnx/f3+EiYSIioeDg4KFgoOLhYqGjYN7joN+g4KEgYeFf4OAfYaDhn6HeXh8gYCCgH+MjYaIhIF4h2SDi4l+foB/foB+gYCIgoiWh4iKh46kgn5+fXV6eHh9fXt9fqmBgIF4dH+ZdnN0hnNwb2lzeHxzdndzd32Bf3p2end0e3p8e3R2enRzdHhyc3h5foB7e4CGgYOBfHV7h4WEhYKFgYh1d4CLgH15d4OEdHJ2d3h3gHB3eHt+gH+AfXV6eX10g3ptaXZtbWdqb2h0cXRzcXV3cXJ/f3SHdXJydOV8dHyKiXhycWxyb29wbXB6cmxxdHNzdHR3eHl6fnZyeHZsb2prb9ZtctTYcm/cc3h508hzhbl3cWpvbWhpbWprcGvMbW9qbWhp0svKz8lsa9NnyWlsgNHLzsfM1uLU4uvd5NXg6uzY29hw1NHP3eTm4+Ll5Yp1eOTg33bl09ngy+HU1Nra33Z52HVx4HZ0cHbg2uPf49rg7/bo3+/f6vj64uXh7ufh5n513XWFee145+Xj5OPifezg3eXn7evwee163YjW0MjebW5z29tyc3La0c3K0NhtgNzV4dzh7OXl8+vy5u3bxuTd6NLSfoV7e+p8fn/y8PuA8enm8/Xp7X97fnh88OV7fX+B94H3+/l/+ofs3uz08O/w5uHhdeN3eXbhdXeD5eWKk4KAeHt/fHV0dnR0cHZ6d3V2fX2CeX+DgX99f3p15Yx8eIB7foZ7enx9fH+FhYqLgIKCeoKDgnx+goGAhYGCgIOCg4uCgYSFgYqHi4eEi4aGg3yCh4CBgoCCh4iIho2OiouFiYuDi4mNiYKDj5KQi4yKi4qTjI2IiYuMj4mFhYuRl5KSjo2IhoyNi4+ShoeNh4uQiIyEjIyaloqJi4qJiI2Kh46JkIqIj4eOlYuNjo+QgIqKho2Fgo2CgoiJj4+PkI6LjY2RjYqNk4eAgoeEi4iQjJqbj5GRlo+NkpaQkI6Qj5KVl5CPj5CPiJiTk5WSlYSMjpGNjoqKjpGRjZGPj5GTjISOjYaSjYiOjZGTjJGToJONlJiQk5CMlpeOipKRmo+KkJKUi4mWjJC0jYiOh4aKgI6IkJGZk5OSi46ei56empiUlI2bmKzKl5CTnJaYlJiVkI2Oj5OOkJWXlpGSk5GPkJWRlZSUmJOTkJSRj5CMko6al6SPlIqMkpGRkpOTlpOPkoqQnZKLjY2LjY6MiYyGjIiRi4OMjY+PiJGLkJCRjJCKhI+RlZWSko+OkYuQlJCPgJORjJWTkZiPk42Qi5CWjJSUj46ikJCUoJuUkIuNkI2QjpGNiomZi5OPlpuVkpOQlZaOkJORiI+Lj46Pj5eaj5aHhYuQi5CMt5OQd5CZkI6VkImKioqPko+UlpGOjo2PkI2Sio6MkomEmI2Gi4mLiZCQioyMh42MkImTh4eNjIyNgImSkYmLiouEknCLk5KGiIuOiouMjZCQiYyVhIeJhY+iiYeIiIqOjIqRkI6PkLWQjoiCfYqlf4OBm4SEg32FioyGh4iFhoqNiIeChYGBhIWIi4OCiIOAhYh/hIWHh4WDhIaIhIWFf3p8hIKBgX59e4Bxc32HgH9/fYuMfn19fH+AgHeDgYCAfn5/enl7eX13iYJ6eIR8end7f3iBgIB9en2Ae3qGg3iJfXh6eOp/d36Ljn54dnZ8dXZ5eXmDenN0dXZzc3FycHN1enNydnVwc3ByduJ0eOPoeXTpd3t64NV6itOBe3h8eHV4eHR1eHPcdXlycW5u2dnZ4dlwcd5x3HR3gOXX3NjZ4unU4+va5Nfg6ezf5N5w1tXT4+Xj4uDl45N0defo4Xjx3ufr2urd3ePj43R21XJw3HVzcHTe3ebc4+Dh6enj3OPX3efp4eHZ5d7e5oR13nWBdOZ15Ofn6+ztfe7t5+nn6ubrdup15oHk4N7ueXd56el0eXjp6t7c5Ox4gPDr8OTj6+jm9On56fDl2O3o7djSeX10ct11eHnp6fF78unj7u7m5nt7e3d36OB2d3x97Xrt7+x684Tp2u7y+fb47e3offWBgnzvfYDH+vWfs4eBfX6CgXx+f4B+foOCgn+Bg4OHgIODgoB+f3x+9ZKAfoaBgYmAfn9+gH+BgoSFgIyPho+Rk46Ql5SVmJWWkpWTkZuQjJKRjJiTlpWQmJGSj4KOlYeKh4uKi5CNi5CQjoyFi4yHjomOiomLmpyak5OYmpiflpiSlpWYmpKNjpSXoJqdlJaMiI6RjZKThIeKhoqQjJGJlJOjoJKSlZaVkZiTkpePnJaRmY+YoZSamZycgJeWk5qUjpuQjpSWnp2cnJuXmJygl5icppWNj5WOl5Obk6ChkJKVmpKOk5eQkZOSjpCQlJCQk5iWkKKZmZiWnIaUlJmWmJiXmJ2ak5qZnZmcl4+bm5OcmJCUkpaYjJOVpZqVn6Obnp2XoqSblp+fqZ6YnqSmnZywpajHopyimZeagJuWl5edlZOSjZGhjqOnpqSlpJyrp7fOpZygrKOkoaWhnZecmp6ZlpqcnJOUlZKNj5uXmpyhp6KioqGenZuWnZmqpredo5WZnZmZl5iZmZePlI2Wp5+WnZ2dpKSmoqOfo56nopWgmp6dlZ2anJyfm6KbkpydpKOdm5qWmpagpaGhgKKmm6OhnaKdoJeblqCmnqepo6K4paKns6qhnJmZnZmdnaCcm5yvnainqbConqKfoqObnZ2clpyZn56eoKmvpKqamKCmo6qo6K+smK22rKWtppqamJWcm5eeop6bm5qgn6Gqn6OfpZ6XraCZnZ6ioamnnqKinaikppykl5icmJ6dgJuko5yclpmRn4qfqaudoKWopaeloqijmZyqk5aZmaC+mJeXlJKWk4+WlJGWl9Kdm5mQjJq9kJGPrJCMi4WMkJGJi4qIjpGRlZKRk5CPlJeenpWVmpOSlZiPkJOSlZSQj5SZlZeXkYuRnpuXmZWUjZWAg42ajYqKiJaajI2PkJGQgIeUk5aYmpuenZqYl5mWqJ6SjJiNiIKFiYCKiIuFhIqNh4aVkoKeiYKEgP2Jg46gpZCKiouTjo+RkJCbkoqOjIyLiYiKiIqKkIiFi4mBg4CBhv6Chvv9hYD8g4aE9OGDlMSJhIWMioeHiIODhoH0gYiEhIKB/Prz/vWAgP+A94KEgPrm4ODi6+3b5/Df8OLr9/nt+/aB8PDv/v/9+ff3+aqAgff38oL95u/y3vfk5fDz+IKF84KA/4eEgob69fvy9+7v9/Xq6PXo8vz/8fjz/PHt9I2A84GOgv+A+PXs6/Lxgfj18PXy9vH8gf+D+5P89vD/gYGD+/aCgoP4+vLr8PuBWf36/ff6+/jt/PP47/Tn3Pbw/+rthouDg/2Dg4H19vyB+/Ds+f73/YiGh4OC/vmBgYKE+YL1+/+A+Inw3u/6/Pj48+/wgv2IjYX8hIap/fvM55COiIqPjIeHhIkjjY2MioyLjpOMkJCOiYOFgYD/l4eGjomLlYyIiouKhomOkJX/ff99/33NfQF+vn0Bfv99oX0BfKZ9DXx9fXx8fX18fX19fHyPfQF8hn2FfAd9fXx9fH19k3wBfYp8B319fXx8fH2LfAZ9fXx9fXyEfZd8CH19fH19fXx9hnwBfYh8BX18fXx9hHwIfX19fHx9fX2GfAF9lHyEfQh8fX19fHx8fYd8hX2CfIR9CHx9fHx8fXx9inwLfXx9fX18fX19fHyffQF8kX0CAgQAgImSgYmWioeKi3qGkYiFhoOBg39/j4WFhIaHkY+R2JiIiIx/hIOEgIGHfIGCg4iMjYeLjo+OiY6SjIGLmY2JhIeDioeGhIGEhYOGiJWLgYeDhH98goOEhoqPgoqIj4yLhoqHe4WHiIyLipOVjY6Ym5mbkZGPjY6VjoKWiIWIgIaIgI+RkpGgvJONho2RjZOLjIGEi4iPiomLiYWIjZWNhpSGhY6NkJWRjYmIhIKMhIqFhYOFiI6IjoaJhJCmjomplJOSl4yVkZKUipCJh5KOmZCOhoaFiJSNhoSEiYuZk5CSk4uPjZqNjZOCmZaAhIeHgY6Lj42Vl5OYl5Opk5KYl5WNgJ6/l4mSjY2MlJWZ3eypnZaRkY+RkpWTjpOSl4qTmZqWmp6Xjo2TkIaTk5iPjYeGiYyIiIeMiIeJiY6JiY+Qi5WZl5SPk42olJSNhYuHhYeZi4iJhISOkIqEi4uHkZBkiouVjYW4yI6Hi42KkIqKh5GSi53Vjo+PjpbsmJKgk4WFgIiPi4qLiZSMqYSFiX9+g4CBhYaHj4+Nn5GMjIqKiYmTjo+ElYqTmZWOkJGMkoWMiIyKiIqAiYqHjImJjZCVkYyKjIyKiJGblrqUj42Ph46Qi4yGiYuJjYyQkY+Jg5CYiYCJioyJh46rjIqKjIeAfoeDhJWFhoOFhYJ/iX5+fH18gIB6dn97eoOChouChn5+g4SGhXeEhI6PiIiFiZKHhoiGh4KHgYKNdXtii4CBe3xzdXxxdn56doqEfHp4fH2DhoF7eXx3fXSFeX95d3x4dnODenl2enp5c250d3N9eqqnen95h4l8eXV/koWAgYGDfnl7gYGBg4mEfIWDfH98dnl9gHh4eHeGeHd6f3p5enJ1dGl0b3BtdW9wV29yc3p0dHdzc3p2dtVydnh1fXt9eXZ8dnV0dX12dnV1gHRxe4B3bHJ5dXh8c3iAenF6hHZ4dHBvcXLSbW1xcGzTdWlra21sjGptx29uatV3gmtqbNRz3dzc2HLbc95ub3px0M2/z8XagNDF0ePh1tjo7nRz3HDScnBzdHdu2tjW1HHU3dba2d5x1t/W4d/a49na2N7l3ddy0nd1gt/TyntueOTT5N7Z3OLh5urn8nnrjIXR6+bt3OLz9vB2eXrxeON43Yzk6uro5vDo29bh53p32Xl5g3h4eXl34Nje5OXO3HHRe9LfdeXYgM7h3N7R4d/g0N3oeXfi0eLs8Oz0f3/26/R/iqfs5O3r6Onx7vPt7YB9e3vm4nnweHv46PH08+vy6a164ut6fH2Ddnp6e3V0enSDeX2BfXl7e3t5f3x5gHJ0d2x0eHZ3d3d9g3h6goSKgoR/gHt5dnx/gXh/fn+CgYSNhYmKh4uGZ4CHeH+JgXyAgnhxiYKCg4GAg358jYSDg4KFjo2RxZKLiI2CiYeKhYSKgIOFg4aIhIGEhoeGhYmNjIOLlY+LjI2MkJKMioiKi4mNjZaPiY2Oj4+DiYqHiYyOgouKkI6SjpGPjpGPjJKEjYCJiY2RjJKKjYyLj5ONiJiQjY6HhomQkpGNmreNiIaMkI6VkY+Gh42KkIuKi4mGiI+XjoePiISKio2UkY+QkY2Pl5OUkpSOjZOYj5SSk42YqpKNo5WWkpqRkZCPk46RiomMi5KNj4qMj4+blpKOkZGSnZaSkpSQko+bkZOXjKigi4CNj5CJk5CRj5STj5KPi56Hh4yKjIWQvJeHlJSblpudnNfypJqQkZGPkJOTlJCTkJaLkZKTjpCUk5CSmZuQorWck5KQkpOTlZORlpOUlJWZk5CTlY+WmJeUkZWQqJWWkYuTj46NnJGQj46Ll5SPjJGSkJaUX5GUnJiMt8OUj5KSjC6Ui4yMk5KNpemSjo+SmP6Zl6OWi5CSl5iXlJSdlKWSmJ+UlJWRk5SUlZaXlKKQhI2AkpGXlZmMlYyUmpWUl5SSmJCWkZiXl5aKjpKOjouIi4yQj4uJjIuKiZGal7KVkJGRiY+Sjo6GiYmIiYyPkI6KhY6YjYaQkZOSjpStkY6Pko+NipOVkZ6Sj46QkpGPlo6Mio2OkIiNjYeGi4uOk4mPiomMjYyKg4yMlpKLiomLkomAiYqHi4mLhY6chI6Rm5CRkIyDhIl+hY+MhJiSi4iHiYiNj4mFg4aEh4GRhYyGg4aFgn+OiYWFh4yHgn+FhYGLhLS1goR+iYx/e3mBj4WBhIGBfHp8gH9+g4mDfoaHgIKBen2BfoF/f416e36AgICDfX6AdoJ9fnqCfH5ofoB/gn2AeXx6d4J6e910dnp3hH5+fHeAenl3d4B6eHl5g3Z1fYF3bnN4dHZ2cHZ7d294gXZ7eHR2eXjlc3d4eXTqf3V5eHt4mHd54nd6c+Z+hXBvbtpy4OLj2nHZcdlub31z293Y5drx59fi7e3a2d/kcW/WcM5vbXB0dG7f3t3gct/f2+GA4eNz3Oji5+Te7ebo4+no5eN44Hh2gOLX1n12ee3k8O3o5PDn4+rp6nXif3zH6Obp3Nzr6uhxcHPpeep/8o/4+vj19fPs3Nnb4nd14nt6g3l2eHh569/n6e3a7nvrlOfrevXm4O3v6+Hs6uza5+Z8evDm6+7y6eh4eOrc6HuHsetj4+zr6urv7vTt7Hx/eXzr6XnyeXv58fH08+by5LV33+h3e36Ad3x9gHt9gn+NgoaGf3h5enx8g4KDjXx9g3d+gYGBfXuBhXp4f4OGd3+Bgn+Ae4SFhIOLg4WEg4KHgoaEgoJ/FY2Vh46bko+TloeKnpaTlpSTlpKRo4SWgJiioaPQpJ2anJKXlJeQkpeFjY2MkZSTjY6SkZCIj5GLhY+Zk4yJjZCYmZqYlZeXk5WWo5mPk5GUkIqRkpGQkZWJlZWbnJ2XmJSOk5aUmpWYl5mTl5ybmJ2WmZiXnKGak6aemZqTlpeeoKCdrNaclpGVm5qgmJqSlJuboZqXm5iVgJafqJ2XppmVmpmcpJ6Yl5SPj5mTmJSWjY6Ulo+WkJaPlqifmLShoZuflJWUlZ2TnJSUm5mjmZeOj5CQnJSMh4iKjZuXk5WYk5iYppyiqJi1rJibnp+UnZubmaOfmp2bmq6UlZuYl5Ccy6CPmJealp+ho+v0sqqhoqGfoaWlpKCigKCllp2fnpidn6CYlp+fl6quq6Gfmpqcn5ybmp6cm56fpKChqKmjqq+sp6WnotCkpJ2WnZmWl6abmJqWkZ+hmZWbmpifnYWcnqahl9LmoZqbm5melZeWnpuXsvygnp2fqf2npLOgkZSao6SioaCto7eeoaeenZ6bm5ueo6euq7uogKOjpKKioaSho5imnqetqKappaCkmaGbpaOjoZafpJ+in52fpKuooaCipKKirbm247axrrCkrKynp5+enp6boKGlop6Yoa2dmaGjpKKep8+opaSnpqGhqqynuqqnpaalo5+soJmYm5mdk5ialJOam5+lnaObm6GipKSapaGppJ6cgJufqJ2enp+gm56Wna2QmY2lmpuYm5CSmo6Wop2VrKKblpOXlZialI2Lj4uRiJuQmJSQl5SUkKWcmpWcnpuUkJWXk5yXxL+Vl5GdoZKMiZGdk5CSkZGLiIuSkZCUm5WOmJmRk5GKjpOQlpeWppGUl52amJmRk5aIlY6QjJWMjIaOFpCPlo2KjoyLloqP+oKHh4aRi4uLhY2Eh4CTjIyNjZuLiZSZjYKJkImOkIWMkYyDi5WHiYeDhIeH/YCEhoaB/4qAgYOEg6KChfmFiID/kZyDgYH+h/3+/vaB+oP+g4OTiPj66/vm/+/h6/n96unx/IGA+4LyhYOIiouA/Pf3+oL2/fb8+/+B8vnw+/Xv+u/q6PX38OuC94SDk4D77eiNpIP37v349vP9+fT8+f2B+42G1vj4/ufs/f79gIKC/oH3ge+j/vn4+vr49ufi6/CCgfaHhI6BgYKBgO/u+Pn17PaE95nz+ID79en58/Tq+/b15fL0goDv4u31/vb9goH78P6Cj73q5vXy7+/y8/v6/YeLgoP5/4P/gID58Uf2+fTv+fC1gvX6g4mNj4aIiouHiI+GlouRko2FhoiKhY6PjZmFipCFjI2LjYyLkZKEgoqNk4aKhYaEhoKKjI+JkIiKjYeIkoSMAo2MiX2Cfv99/30EfX19fpx9AX+0fQF+/33KfQF+7n0Bfox9AXytfQF8hX0BfIl9BXx9fX18hX0CfH2EfAR9fH18hH2PfAV9fXx9fIZ9hHwBfYZ8AX2OfAt9fH19fXx8fH1+fYx8BH18fX2JfAl9fX18fXx9fH2LfAN9fXyIfYd8Bn18fXx8fY18gn2HfAh9fXx8fH19fYt8hH0GfHx9fH19iHwEfX18fKt9AX6XfQICBABtiISDkoqHgoiDhYCBg311eXZ7e4GFfX+Ag4OIgIClkX99dnN+h4OFo4SNkIyLiI2Uj42Lho+LioiIiIaPioOSi4iIi4uHhod/j4WKhYaEg4eEhoGDioyPiYCGh4SHjImalI+Ki4iMjI+QkZaQiYSQgIyMkZSYk46Sk4yMiY+UjoeQjo+NbY2UsMiMhoeBgI6KjYmUh4mQmI2OopWQjY6Lg4yLj5SUj4SNi46MhIOIo42JiI6dhY+JjJONh42Gj5igm5uTlp2fl5iYiJeLk4eYjIeKjYyOkJKOjZKVkYyeoIyNkpKKjYyIi5iOiY+Oi4mQgJaSmJiUio2akaaUlJSSh4ufn5SZkZOMg5GRhdaimpSXoKSfm5ONkIuTkJqIkZqYmp6dm5iTlpifjYyIioaLkYiPlIyMgoGLi4qKipiShIGZi5CYm5CUl5OVjoyPkpOWio+Qi4yIlpqYloiJj4zblouLhn97i4WJhoWIi4eTjpCPgI6VjJCIiYuQlJOTlpuRjJKKiYd8gFuTlol+fYF/h4uDh4aQipWYnIqGhpKOjJOVlZWKjJGUlZiTiomJgYaAkZCKl5OPkJGKk5CUlJSZkZSZlo6MjZKGh4yJhoeGiYqTjIyPiIiKipCLp4+TipSSkYKEfIeBgoqagXd9gYJ4hYqBD4t+gY6Jgn6SnJaKg4WBioSFgIF5fY6HgouGhoiEiYSNkIuGhoKNhd6Eh4SHgYGDfIF9cXB7dXx3en9+g3mAe3h8eIOEfH1/f3l7gYCJh32Af4KAfo6ijIOCe7N/eoB4d4CDgYN6cXB0dHl5knd1eoWKi859fX6AfX1/foKAg4R8gIKGlX98nnR8dH5yc3h1e4F7gHh2eXJycHR0fnZsbHFtbXNxeG10d3NyeHNze2t6oH5ud3J3cnR14OR7d3pydHJtgHd3dnZ0eoR2d358dnh1d3Zzc3Bxb3lscHZqc29wb3lyec6Dd29ycHdubGpvzWvVd3BybXpybnZydXbkend4dnVucGzQas/P2dLPd3J1coh5gN/cd+LV3XTZbdxxcG/XdnTZ39rXcN3X1djc5XTt7drp79nn8Ozm5ej16N/e4Nzcb3Zz4tx3snx97+Lh2d3b0NPg4Hp5e9fY23jh4ud54+Lf4Nvb4e555eHo6+zw6tzc3ejp8Xx75fDneuXldtrgcdJw19bc33Z10dnddtTdb9bZfOPj3evs8YGHhXzs8Onleud77eft6uuCg/D2eoR6fOrz7nh3enuC4JN6dHl4d3fl6vHv6vF77+nrhX52f399fXV2eX97d3p+f3t6fXd8eHdyhIKCd3V1cm+Adnx4g3+Mf4KMh4SNhH9/e3d2e8p9dnqAe39/fYaHgYOHhYWAf318iYGAfoSEiISDhYN/gH+FhIiLh4aIiouOhoimlYmFgH+GjoqHgpeLi4eDf4SJhoWEfoaGhIOEhISPjIaTi4qKi4yIiYuFkoyRi4uKiYyNjYeHjZGSj4eMjoyOkpGhmZWMjoyPkY6QjpSKhYyMiYeDhYmMkoqJjo6NjIiPlpCAiZKRkZN1kpeuyJKPj4qJlZCSkpuPioyMhoiYjomHiImFjo+QlZSQi5KNjo6JiJKzl5OTmaKNk5CRm5SQk4+WmaObnZORlpaSkJOLlY2UjJqQjI6QkJGUlZCTlZiWkKKpkpSTlI+SkZGSlo+Lko+QjZKSlJqalY6Qm46ek5OTkomAiKunlJuTlpSJmZil9KKZk5SZmpaVjouQjpSSmYmTm5WUlJWRkZKSlqGbmZeZlZWXj5OZkJOMjJORkZGUnpuLiJqOj5iZjpGUkJSRj5CVlZaOlJWPjI6UmZeaio2PjNqak5OTiYeVlZqYk5WXkpmVlJKRl5CRjI2RmJ2amZyelpSAlJCVlI6gcKSonpWUk5GWl5KXkZmRmp2jkYqLlpCOlZSXl4+Qk5WTmZWTlJOOjoybl5GdmI6Lj4eNh4qKipCMkJaVj5GQl4yPlZKMj4uPkZWPkZCOjI6OlpCzlpSSmJqXjIuHkIyNmquSiJCSkImTmY+VjJGpqJKRoKWfkoqOipWAjY+PkYyHiZSOipOQkI6NkYuRl5SVlI6WjcmJiIaIiIiJioqIgoOJiImGg4uMkYiOioSJh4+OhoaJiYSEiIiRjoSGhYaGg46fkouIidKJg4yGgYyMioyCfHt/f4OEoYWDhI6SkpeCgoGAf36CfoF+goF9foOJmYODrHmAfoR+gIMmf4OLgYCAfXZ2dHl9hoJ7fIJ+f4KAhHt/gXx8gX19gXiFzYR0enaEe4Dp6396fXl5dnSAfHl2eHh+knN7eXt3eHR1cnNzcG9ueG5wdmtzcXNzfXZ+3Y6AeHp5f3l4eHnfdeR7dnZvfHRzdnF0c9d0cHFwcGtubdVw1dbf2dp9dnhzinjj4Xji2d1x22zacG1v3Xh23+Tg4HPp4uHk4uh27vHd5OfW3uvj4IDg3+3o3uXe29lydXHa2HCffHjo3ODi4t/e3+rwfHl51tzddt3a4Xbj4Obr7+73/H7y7+/w9vbr3OXm8uztd3bi8Oh66eR55up46Xrq7e7zgYHu8fOA6+x55ufs5+Ho4uZ3gX967u3t63zre+zp6uztfoPr8XmEeHfj6+p2dXl3f1bkn396fXp7ffb4/Pny+X779u+Jf3eBfn98dHZ2fXx5fH1/f3yBeH57eniMjIuDf4B9eYd8fnl/fIp+gomGgomDgYF7enyB24OAhYmBg4J+hYOAfoB7foCLiYeXj42Jko6TjY+QiIOJh42OkpWMj5OUlJmQkaydk5CEh46Zk5J+oZOZlI+MkZaSkY6Kk5KQj4+OipSQhZKOkJKRlJOTlIyakZeUl5iYmpiXk5Wen6SflJqbl5idnKukoJWYmZqcm56cpZyYn5+dnJeYnJ+lnJqfn5yZlZ6lnICWnZybn5KcoMTjmpaYlJKgm5+hq5+do6SanbKlnpuempWdm5yhop2RmZWVlpCNmMmYk5GZpY6Uj5OZko+Vkp6jqaSimZmgn5uamo+dk52NnJCMj46JjY+Pio2SlJSQpq+bnqCjn6Wlo6Otop6lo56Xn6Cdp6aelJemmKqbmpaWjICOtq2Yn5eal46enIfysamkpa2xraqin6KepqKrlp+moaChoZ2cl52graKgnKGeoqafpKugoZSYoKKhn6OtqJmUqpmeqqSfo6Wfo56dnaKkpZqgopqXlqCmoKGRlpaU/aSYmZiPipydoJ2bm56app+hmpuhm5+Xlpyhp6WmrLCjooCmoqOlm8Tjub6uoZ6hnKaooaWgqKGrsLOgnKKvqaesqqyroqOjpqasp6KmpJycmq6opLKtpaWroqukqaipr6yssrCrrK2zpqiuq6ano6SlqqKjo52cnp2oosWqrKavsa+ioZ2moKSyx6SZnqKinaiuo6ygp8vKpqO3vrqmnaSfrICnqKemnZmbqKOfqaampqSqpaqyrKqnoK7B4aGhnaGZnJ2ZnJeLkpqVnJeYn6KnnaSgmp+dpqSdmp2emJeenqejmJmZmZiRnLGkmpeQypeXoJWXnZ+eoJOMi4+PlZSzlZKVoaajj5KTkI+OjI+MjY6PkouOkZmqkpC+jJSNmI2Ok4CQl6GWlZKVjpCLlJKel4yMk4qLj4uShoyOiImRiouSgpHOloWLhYqFhYP1/IaDhoOCgoGTi4qHiIaMm4KIh4iFh4OGhoODgoWEjIKFioGKiomHkImR+6OQhYaDioSAgILygP+OiIqEkYeDioOCgvyGhIiGiYGGg/uB9/T+9fePhoCHgpaB+faE/PH7hf6A/IKAgfyJhff89vmB/fn19/X9gP796fPx4OTx6efu7/v57fz79vqCh4D38oCps4b55+3r8ezq7PP+hIGE5+7xge3w9IH5+/v18OT1/4D3+/74+Pjy5vDu/fn/g4Hy+/WA8/KA8/2B+oL3+f3+h4bw9/qE9YD6gfj2/f/y//f+houJhPz6+faD+oD18fL59YaJ8PuAjIKC8/3/g4SJiZD+xI+GiYSAgPf4/fv0+ID+9/qPiIGLh4iIgIKCjo2Ii4yOjImMg4qHiYecm5iNiouIhJOHioOLhpWDipWPjJOKiYeEgoGI1oeGiY+JjI2IkJGIiIuHiad9An9+3H0Bfv99hn0CfoD5fQGA/32SfQF+y30BfsV9gnypfQF8in0DfH18i30BfIh9Anx9hXyGfRB8fH18fHx9fH18fX19fH19hHwBfYZ8AX2TfAl9fX18fH1/f32KfAt9fX18fHx9fHx8fYh8AX2NfA59fXx8fH18fH18fH18fYR8CX19fHx8fXx8fYh8hH2EfAN9fH2FfAR9fXx8hH2DfIV9AXyHfYZ8BH18fHzEfQICBACAhIOAgX+CgoF8enuAgXp6gHWBe3uBhH+Ah4aJeoWIhHp3eITCfoOCe3qMiYuMk5mckp2Zmp6eoJaSkIuPoZCUnZWIjYmJgo2PjoyDi4mFfXyAhIeFjJGnjZKTj42Mg4SZi5Okh5CLjIuIjZeXmI2HjI2Yio6Vj4qLiJCKiomNioeAh4OAhIiFjJGOkIyBhImJj4eLiMGJjY2SioeChJCNi5KUn5OSjI6Ijo+QjIqJi4iKjI6RlZmTk5uRlJSNi5iTkqOYlJaOi5eMjYiGhJiPlI6Qi4uRiZOXjpGSloyRjpaKjI6QjYqKhoeDhpGLko6Oj5eYl5mal5Obn5mbmJiUkZCAkJGKjpCOjJCGiYuUk5OMj5GRkJidjZOYi5KbkYuEi52clI+PipmYkpuamZGYkYuIi4mKj5SPhY2LiJGGjY2GioWIioyXf3+Eh4WOj4WJhYODiYqOkYqOlpKVio+Rj5eQkpOMk5CMkJKPkZeLkJOOkI+MioiGj5GUjp6fpJqXlZGAkpuSl4iXjIqBgYCCgYKPkImHmJWOj4qRkJGUkpmai46OmZGNi4yRko+PjZKOjomMkZaYnJOUl5GWlaOQko+QjoiLkY6Ni4aLjJaclZeJioyHi4yJiomHjIKIg4eEiYeJiYqJjYl/f35+fImJgHWCi42CgImJioqDi52GiIqFhI2AhIGAgoCAhYmLhomIgYiGg4B9g4qUm4iMkoZ8f4SFgXqIh4SAhX6HhoGAhoeDgYB/hHuDh3qAfHp3e3V4fHl/hXJ2cHF2d355e3Z4cIB8f3l9hpSCjoaEhn58d3t2iHZ6hpubhZqFg32DfnyKkYZ8ent4e3d6fXR2eHZ0c3Ryb3aAe3x1dXVydWpyenZ1cmppbXFwzm57cHRwcHVzcXJyeHZ0c29wempqb2xucnFvbodzdnp3enpzdYCBeHV7enp4gHx8fHV2en2Fe313fHRvdG93gn9ydHXbed90d3fXy2xq0M7KcXTUbtXd5HZzd3h6d3VzdH1xcthu1HaosXl2ddeA2Nx3b9DZ2nN9kNva1tzh3NXh297Y1HRscnN519LeeeHv8PT294PugYPy9oHu6Ojfet7b0tbb18t0eXPZ6efj53vl2uDh3ODhd3LP2uPb2+F53dvg3N51eHjm8+7f82iM7+Dt6ejl8ul3deHhfnjm4ebqeODa5+Lp5NrQ0M/V09KA2srj4ubndHTc233ldHZw1nN4eHd+gPn6gfH3gf79f+WljX2Ac353e3d9eXl1dnp+gnp5fXzte3585uN8fnx5e353eHd0dXl7gIB9eXd+d3h+gHx6enZ/f4J+en94ent6eYCJe4CChIF+gH97eXxxcnmCgX57gnt7louEgoWIgoaAhIWBh4SFh4iHgoWMi4aIi4WPhIOFioaGiYyNgo6RkIuGhZDCio2KgYCMiImJh4yMgYaCgYaHjImJiYiPtpeVopSKjIuLhIyQkY+KkpOSiYeOkI6Pk5iqkpWYko+OiY6ckZewjZOPk5CNkJWWlouHiYqQhomPjYiKjZGPj4+WkY2AjouHjJCNk5iWl5KIjZGSlo2RkMqSkpCQiIiHho6Mi4+Vp5eYkJKQlJiak5GTkpKUkZGQlpeSkpmSlJKMi5eZl6SXlpiMj5qUlY6Pi5ySlI+Qi4yPi5OUkJOVlo+Uk5WRkZOUkZCQjo+NjJSPlZKWkpmUl5mXlpOYmZGVlZSQlJKAkJOSlZiWlpiOkJCVlZaSlJWVk5mejpWZjJGbk46LjZ6clZCQjJWUjpSVl5GYlJCRlJSTlpqWjJCVlJmSmJ2SlpKSmJikjYyPk5CamZCUj4uKj5CTk4+PlZOWjJCOkJaSlZaSlJOSlJqWl52RlZqVlJSSkI+Nk5WXlJ+ZnpeUk5AojpKQlIyakpWTkpeXlJOenJWUqKKWlo6WlJaZmJ+mk5WXn5iSj4+TlYSUgJGNjI2OlpWVjoyPi5GNmYuQjZCRjY+Yl5eUkZWWnaOgnpGTk5KWlJSTlZOXjJOOlJCSk5SSkpSZkoyQi4uGlZOIhI+Yl4mMj5OUlo2TpY+Sko+PloyLiouIiYqMkYuOjIqQjo6NjZCVnqKYmZ2SiImIiod/iImFgoqGi4qEhoqOgI2Jh4OIgIeOhIiFhoWJh4qPjpKXhYaDhYqJi4eIh4h/joiFg4qRoIyWjIqRi4aAhYCVgYaNoK+Jh4eCe358eoqShn99f4GEgISFgIOJg359fnx6f4KEfnp5eX10gImGh4B6enyCg+t+iICEf4CDf4B+f4OGg4GBgYh2d3t6e356gHh2kHt8fnt9fnh3gX53dHh5dnV5eHd1b3J0d353end5eHN5dnySh3NxddZ433V7eufneHjq6eZ6gOd04t/hcW5xb3Fub21tc2tt0GzXdZ7IeHZ329/heHLZ3d5zfY3a2tbc4+HY5OHi4uB5cXR4fefY5Xvk6ejn4eBy0nNw09xxgNnf4+V35ODZ4uTi1HR5eNzp6ufqeebk6Ofs6Oh4eN/m8d/k43jj5Ojp4nN0euj07ub2hZn34+jn5OPs5nd15Od9eerk7vd88uv18Pf47ujn5+7p7Pbm8/Hw63Z339R45nN2duNyeHVycnbf3nPb3XLo6njin4l5fHN7eHl0end2VHRzd3t9eXh5euV2e3nr53t/fXx/f3x6d3h2en6BhoB7fIF8fYKCfn9/fISEg4B9gX5+gH54foZ7f4GEgoCDhYWCg399hYiHiIWIg4OXkYWGiIeAhoCQkI+UkJKUlpKPkZmYkJGbj5+Sj5Wak5SWmJWJlZiWkIiGlNKRk5ONiJeVlJOVm5uQmpaXmZWbkpKPi5K2l5efm4+Qjo2KlZmbl5GZnJuVkpqcnJqfpLWfoqOempmTl6aao8GYoJ2em5mdoqGhlY6Tk5uPlJuZlZSYm5mYl6CbmICXlI6UmJadoqCioJScpKKonqWj7qKloaKYlJGSnJqTnqS8pqmfoJqbnZyVk5SSlJeQlJKcnJaSnJKTk4uKm52Zqp2cnZCVoJmbk5KOo5eck5aOjpGJlZeQlpiclJycn5ucnZycnJ2ampaYn5qioJ6ZoJueop+dmZ+im5yen5ibm4CZm5WVmJiYnpWYnKWlqqapqamor7Siqq6dpK+hnJWbr6uknZuToaGaoaOnnqShoKCioqSlqKWXoaOepJ+ip52inZ+lp7CcnqGknqupoKGcl5WcnKCgmZmgm6CTl5SRnZSWmZSWlZabnpqbo5WaopqcnJmZmZqioaeks7G7r6qpqICor6qxo7SprKelpaimpLGupKK2r6upoqqjp6yqsbqen6KvqqOgnqOmpaaoqaakoqSkrKypo6Kpo6uquKatqKyrpaiysbOuq7K2vca/vaqtraWqq6enpqKpnqeepKCioKWlpqerpp+hoKCfqqqgnKWxs6KgpqmnqJ6ou6SkpqOjroCkoaGhoqKlqq2kqKSkraupo6OmrLe7rKy1opicn6GelqakoJ2inKSimZqdop6cm5OdkZumlpqXlZSYlJefnKCpk5OOkJSUlZKVk5OHmpaUlJmkr56woZ+nnJqSmZGnkpafs8WYhJmXkJeUkKGpm5OOkJKVkZWVjpOclJCQkY+MkoCYm5WQj46RhpSemZiQiomMjo3+iZaKjomHjYmMi4uRjouNjIyVgYKDhIiJhoSCoImMj4uOj4mKlJKIhIuKiIWMiIeKgoKDiZOJi4iKgoCEhIegloKBhfWI+ICIiPrzgYD49/WGiviB9vf6g4GEhIeFh4WIj4OE+IDzh+TxiIOG8YD394iA9v37goyf9fPr8vHy7vn3/PT1hICDgon26u6A7+7t6+rleNl3d93te+nr8vWE+vft+Pn26YGEgO38+/b2g/jt+vf79fiEgOv2/fD29YT19/3//YWBhfn97eH70aX77Pj1+PT++4CA+f6Lg/z1+vyB9Oz28vj49+7w8fj094D/6f33+/qAgvPphf2AhYHzgYeEgYKA+PmD8/Z//v+G9beZhouBjIqLiY2IiIeEio6NhoSHhf+ChYL18oOIiIWIi4iIiIeFjY6RkouEiI+Ji5CPiIqKiJKSko+KjouJioeDipOChIaJiIeEioOEiYCCiIyOjoiQioqjtI+PkJKNk/99/33/ff996H0Bfq19AXzDfRR8fXx9fX18fH19fHx8fX18fXx8fIx9BXx9fH2AhH0LfHx8fX18fHx9fX2MfIV9BHx8fH2GfAd9fH19fHx9hHwBfYd8g32FfAF9h3yCfYZ8AX2FfIN9hXwCfn2IfAZ9fXx8fX2EfAF9k3wKfX18fH18fX19fIZ9CHx8fXx8fXx8l30GfH19fXx8vn0BfoZ9AgIEAIB+iY2IgHuIhYeEhIiLpZqEg4uNiYR7gYB+gHp/hX59eHh4fX58hoKGiYyNjYWPlZWRmZmXm6qYlpmXnZmfkZydkYuJjZCWiY2FioSRgIONe42Hg4WFiomMjoeIhIyOj4yMj5uMlpKGjIiOmtS1pJiVk5WTkI6Rj4+NiZCLjoyOjIB/doeMb4aIiIWKg4CBkLS2jYiEoI2KiIuKi4WOnJuVlZCSk4+Rj46MlYeKhYOGf4qRjZSJkZOWmZ6kmJmflJeUkpOLiYOMh4yJjYyPlJWOnJCOl42Sj5WUjo2akZGUkY2ZkpKQkY6PmoaQnZCPoJSKlJ+WlJCRk5KXk5GSiY6IiYCJkIiMiY2Iko2NgouWjY2Pj4yUdZiVlpKHjJiPj5ORl5OIiIaRmKiLioyWlpSalKWRmpGSj4ySlI+OjYeIioqUlJOVlYqMjpKWlYKEkYeAhoCRiomJjJGZkIiMjJaMmY+KkZGWipGOjIyNioOGh46MiZGalJaYmZGRkpKVj4yPj4CLl52Lj5GUlpGSj5KLf4uDhIaGjI+SkJGMtZaemZyRl3+EkpGPkIyHlI2XlZKVlpCXlpailZejoJ2boZKLkI2QiJCKiouEg4GakomKjY2TjY2Ki42LhomLjH+EhH2EiYeFlKOIhn9+foSFkPSMhIGHhYGGiYqZjoqJhYKBgYWIkoCHin+GjYKyxYmAhIuRh3+CgoiCgICBgYuGgX+Jj4eCfoh8iYqMiISFg4KLi4J+gYSAhISGfYF/fYB6f4iHU4CBenp1eKZ5jYB4gYB9fX+AhIR/fXx6fXaHe3qAfXZ2eXmAjIGCf3d6f4B7iG6Ih4yLnYyAcHN7cHNvcWxsanV7dIB7gXl3b3Ftg3NvcWxwaWNhZmxwbnuAn353cnx5c3d1cX1yeHV3cntzcnN2edx2e3d3enh3fnp8goJ8e3yCf4J8gXx753d4d4HmkcV+d3bh3NfYzs/c129x34CymHhycXJvdXRu23F1dnbmdXbkeZZ33nhxc9tx39JueHV0ctHbdYBwdYRwcNXSz87fdnPf3NHN13Nzb9DEx81sdJPd197i4+jk9P/+9/LqgfXw6njrdMvc3dDFzMfV1dHJ0G5wdN/R4eDd2uHU4+Bw1s7MxWrWbG3PaW/d4+3h4OHn9eTl7+zt6u7h4OV4c3fbeN3e5eXa6O/q4uHZ3Nlu0XDX3nB03YB3dnZ3duLo6Xd5e3t8fX2CgImGhP7y8X/y7ezl6uLeenXieHlw13WCd35+e3l1foODgH18f4F5dXnlf4NnbX1+fI2FlYV3eX9+d317eXt9f4GEhoSJgXqBfHyBhYuIh4OBhY2EioyOhouJh4GAiHx2fXyHhYOCh4GGhY2NiYKJh4CDjo2NgnyHhYiGhYeJqqCEh4yKiYN+hoSCi4CIkIqJiIqHjI+IjYuNj4qJjIKHjYyKj42NjZKIh4qIjI2Ti5aajoiHiYqSjY6KkYuXiIqTgZORkI+PlpeTlYyLiI2PkI6LkqSRlpKKjYmKkcbqlYyJi4qIh4iMjYyPi5SQl5SXl4CKgJCaipSUko+WkIyQmr7GmpSTq5qQjI2JioaKlpONko+TkZCRj5KSmo2QkI6QjJCXkZqRl5WWlpubj42SjZCOkZWTk42WkJSSkpKUlJWOnJKQmJGTkJiYlJGalJSUkY+Zk5eVlJGVno6UoZGOv5iNlZuUl5SVmJmcl5iakpSTk4CRl5KYlJaVm5eVipKZk5CSk5Cqk5ycnJmOkpuQjZGUmZWMjYuVm6yPjI6XlpKYj6KNl4+UkpCVmpWZm5WWmZecm5WalY2RjpWWlIqLko6IjYaalJGPlZeel5GSkZeQm5OPkJOXj5aVlpSXlY6RkZaTjZOal5aXmJGUlpWXlJGSlICSnKCUkZSXm5WWlpqViZSSkpGPkZGTkpSQsJyinaKYnoaMmZaUl5ONl4uSjY2Rko2SlJKajo2XlpSTmI2MkJKYk5qXl5mTkY+km5GTk5GYkZCQkJWRjJSWl4iPkYqRmZSVn6+Ylo+RjZCRmeiTjImMiIiNjpGflo+Oj4uLi46TlYCLkIKIkIi8zZKJjZOZjYqNjpKRi5CMjJePjIiQk42LiY+CjYWJioaGiIqQjomJi4mJjYqOh4qJhIyEiJKaZYmIhIN+hb6ImY2Fko6JiIqIjY+LhIWDh3+Th4WKh4GBhYaKlIuLh4F/f3p2gXx+fYKAnId+dXqCfICAgHx6eH6EfYCEh4ODfH9+jYWHhoWHfn14fH9+fIOHrYZ/d4B9eH16doF2e3l7d4N7fXt/gOl9gH58fnl3fHl4fHx4dnV6eHt3e3d23XN1dHzfmr97eHjp6+Tr3tvk5XR15ISymHh2dHZ1e3l37XZ3cnHgcXDaco1x13dxc99y4c5ud3FyctPddIBydohyct7e2NnjeXbj49nS2XV3eeHb3uF2fJfr4t/k4d7Z5+/t3+Hbc+Hh2XXmddPd6d/W4Nvt7ePi63l9gP7m7ezu5+rj7eVz5eXe2XHgcnbjcG/e3uvq4uXu8ejq8PHv7eno5+16d3rqeuTn7evl8PXv6e3x6+l453rr8Xd25oB6eXh4euHk33N0dHNzcHFza3J3c97f5Xvy7Orj7e3wgXvsfH534nmMd3x8d3l0eX2CfXx7fX55dHjne352Znx+gZKJm4l6eoF9eIB5eHt6fXx+f32Dfn2DfHx+hIeEgnx5foaBgoSDgIeGhIB/hoF8gn+GhoKChH+Dg4uKiYWJiYCPm56bkoqZmJmZlpqdwrWamqGfnpiPlJWRmYqTmpOQjpCKkZOQlpGUlpWYlo2VmZaSmpmWmKKUlJqXm5uhlKCjl46PkJGXkZSRmpOjkZWhjaGbmJaYnJ2boJiZk5ufn5yan7Oeop+VmJSVn+DspZqVmJaVk5KXlpaZk5yXnZufn4CShZahr5qamZSclpKWpNrwp6OhwqyjnKCcnJaeq6egoZqdm5aXlZiaopSYlZKUkJOak5yRmZeanJuimJaYl5WVl5qYlI6Wj5SSl5aanJ2UnpKOl46RkJSZk5KfmJyenpunoaKjoaKkrZmgrZ6c8aGSm6Sdn5ucnaCloqOimpqXmICUm5SWkpaVnpiYjpmkoaGkoqLL6LGwsK2boq2fmp+gp6OYmZSeqLyalJqiop+pobOgrqWlpqGmqKSnqqSnqKizsqyyrqGnqbG0sJ6grKGZn5OpnpuYnJ6onZWWlp2VpJqTmJqflZybmpeYl5CWmKKfmaKuq6yqraWoqqyuqKGjp4ClrbSjo6Wssaurq7Cpmamgn5+eo6Wjo6Wdv660q66gqY6XpKGen52YqJ+sqKesq6Wqq6y3qKu2tbOxuKimq62yqrKurbGppaO/sqWpq6WuqKWlp6upoqisrpyjpp6mp6qotcanp5+enaOlr+iro52gn52kp6+8sKmno52cm6GmroCmqJmiq6Lf+ayipq+6qaSnp6+qpailo7CppZ+ss6ein6uaqKOppqOioaWqqqGgop6fo6Gnm6CblqGXm6m3hpqZk5GKjsaTo5aNnZiSlZeYnqCbk5aTmI+ll5Wbl5KRl5abp56emZGSlI+PnLCam5+gwaWYjJGZkZWSkYyJh5CWjICXnZiYkZWUqpuYmJSVjImCiouLiZee152Ph5SUipGMhZKHjouMhpKIioeKi/2IjIeGiYWEi4mHjo6LiYaOio2Ii4eK+YKEgo79m9KEgoL8/vH67PD9/IGE+5S+qIiChIaBiIaA/4GGgoH+gIH9hqmG/4qDhf2C/uqBiYSCgu/9hICBhquGhPv77u/4hoP6+erh8oaFhvXw6viAgZj99u/18fDl9/7/7e7qf/b674H8gOj8/vfo9e/+/fjq8YCDg/bl8fLz9PPs//qA+Pz08ID+gof/gIH9/v3w5Onx8ujs7+7y8vb39f2Eg4X2g/Xw8e3q8fXz7u/y9/mA9oX+/YGB+oCEgoGAhff8+4OFhYaKhYaHgYyJh/vx9YP89/Xu9fL4h4T8iIqC+YmbiY+OiomCi4+OiIaGhoiDgIP7jJDIsYuLh5WRo5WFho2Jg42GhIeKjIyQkJCSjIiOh4mMkZWQjIiEiI+GhYqPiZKPjYmKlY+JkY2XlpKQlI6Rj5mcmZKamOx9AYGXfQF+/32PfQF//33/fat9AX6tfQF+wH0BfJZ9AXyEfQJ8foR9iHwDfX18i30BfIR9D3x9fXx9fX18fX19fH18fIV9gnyGfYV8gn2FfIN9hHyDfY18B318fHx9fH2MfIN9inwBfYR8B318fX18fX2SfAV9fX18fY18CH18fXx8fX18hX2DfIx9BHx8fH2HfAd9fXx9fX18k30FfH19fn7CfQICBACAgYWViYSCi36Ffn57eImae3ltcHl6eICPi3uDjoeHgIGMjIiEh4iLkY2Vj5GUlJOWm5uQoLGWl4yKjZCOlJeNnZaRjIuNjIqJi4uLiIaGiIiFhoaJi4WDjYOLjZSMjYyRk4yViZCQiI2RmZ+XlpyUm5aQlJiVjI6Iio2JiI2JkYmAkrKakImIg5aHkYWHhIOLiZOJhoyJg4qOj42Gh5ONkIqUkZuVlY2Hio6Qi4eRiouXhI6bl5mal5WVlrmdmZiempmSl5WTlo6Wi5ORj4+QlZSOj46Ri5OWk5CIj5aSlpWbmZqal5KNiYyEipWR66KWmJOKkZKUk4qRkpWYnJmrlZWAkZeNkpKJfp6LiouPj4mVkYmNjpGUjpGTk5OWmJuQkpOUkpKal5aVlI2ZjoqEkpiXlpSfko2SmJ2Ym5qWt4yNlI+RkJaYlZOVlpaBkWiOjYeBkH+Ej4yJipSMmI6SkI2VkJKQlpSPlZGWk4+joZSVlJKUlJqam5GJipeSmJaWmJOAkIyQjYmLgpCdko2MjYiPkI6Qj4+UkJGIj4yQk5aIko6Mj5GUjpKWkY+OjpOVlZaOkZuVnpOVnpaYnJmZiZCOjoaHjo2EkISMh46NjZSIkI6NhYyKko6MjIWQiYKHg5KhkMCDh4uKiYuOk4mYh4mJiYyTi5aQjYySiY28moeFjpKAiYOGiIaBdoSGiI+Hh4aCg3yGfIKDhYuPkoGBhoqAh4OCfXyBgoCEhpiFloWBf4B+gX2Egn2BhIKB7d6If4OLf4N6dnVzf315hHV4enh8e3x5eXh9e3t3f3R3e3d1dXmOhY+Pf32AfZKAhHyHe4SMh4CChYN7e3t4dXRudHd3dnKAcnVwbXFue292aGtycHNyeHuBgHqcioRxdXV7uZGAd3iKfHN4eHZ9hLlzd3xycnJ4eXl4enN0dXx6dHd4c355eH57e3Ladnt6fHp4gId763V33dl0cdLc2ttu19jUb29xd3BtcHR4eHR66nd2dYKRdm9ubX10c3V2dnfe5tjX4HKAceJscXS4ftLU23nneOPa3NrR3Nd7dNx01efo3tLv3M7Q3+nwgPDq8+727H/u7dnL4np13HR1cN/R1trZb3jo5erq3tjm4ePq4Nvk3tXf4djYzXDX49fY3uHj4eHp5eHh4OXb6NXb3Xdz54Xzeu3of+fj23FxbWtszWdsx8xqbnCAbnV0dHN1dnpzcXp6dnlzcneCr6t/gYOC/fj6+PV87nblc3xyfH+JfHJ4eH2AfX+Cg3+ahvqBiYOEiYSEgujZen17gH+EhoODhYOCgICFgYGAgYR/goN9hYWDg4yEnICEh4mAhIWLhIaEhYiGkoSTi4uLjpGOh4SeiYaUsoGBioqAgoWZjYeFjYOMiYaGg564jo2FgYiJhomXjYKDjYqIgoGbjIaEh4eJkIqNho2LhoiIjIqBkJqJjIaGiIuJj5GKnJOKhoiIiYmKiYyLjYmOkZGQkpKWl5COmIqOj5CMjY2PkY2WkJGQiY6Sl5iRio6LjY6IjY+OjI+KjZCMi5KTl46Al7KcmpKWkKGToZOTkpCUkpuTj5OOhYuQj5CMiZGMkIyTkZmWlJKRkZWWkpKXk5SZjJScmpeYlJCTka6UkY6Uk5SPk5OWmJOZlJmYlZeVmJaTk5WYkZeal5OOj5WQk5GYlJSZl5CNkZOQk5yX48WhopmOlZabm4+Wk5ian52qm5uAmKCXnp6ZjLGXlJSVlpGYlpKTlpmcmJmal5SXmaCVlJGOi42Tk5eYlpGelZGKl5qYkpCZkY+PlpuVm56dw5GSlZGTlZeXlZKYlpeLm4OamZKNmYuQmpSRkZyUnpOVlZKYlJOUmJiUmJOXl5SjoZmYlpKTkpeXm4+HjpeTmZiWl5OAkY+XmJeZkp+on5qWkpCWl5WVmpaamJiRko6TlZyQnJeWl5ealJWYlJOQkpKVkJCIipSPlI2PlJGSlpaYj5WVmJOTnZqRnZKZkpmamJmXmpWUkZKSl5OSkIqTj4uPjZynncSVlZqamJ+copWkkJCQjY+YkJmRkZKTkJXCnYyLkZOAj4qLjpCHfImMkZeSkJGOjYmTiY+NjJGTmImIjY2FjomIhIGChouMkLOLooqMjIuFioiMioiIjIaI3cyNhoqUiYyJiIeGko+MlYyJiYiKiIqHh4SMiIaFjYKGiISCg4aUkZiWh4SHhZqDiH2GeX+Efnp8f397fIKDg4F/gYJ+fXuAfYCAgIaCmIeNfoCEfnx4fYiJgnqgjYd2eXp+uJWBeHuSh3l+fnyIkN+AhId9fH2Cg4J/fnh6d3x5cnZ1c3t0dnl3dm/WcXh0dnV0fYV65XR45ud5dN/i4OBx19nXcnBzdXFucnd2dnJz3nJydIGSdnJycHx1c3V2dnfi5NjX4XOAcuN0dHbOgtjS1nXid+Tl4+ba5OB7deNy4ens5N/r4NPV1N3hdNrX4dzk3Hfg5tjM3nx25nh9e+ze4ezefXvr6uXm3tzi39/n2+Hp5Nzl6OTj3Hfj7OHf5u7t6+Tr6uTo6e3l8Obs8X157Ibrdejnf+3x6Xl9enl57Hx76e14d3mAeHt4dXV2dXx3dHl4cnBubm51raJ0eH988/Lz8fJ77nXldHtye4CMenN3c3h7eHZ7gXeMfO14fX1+hIKFh/TtgoaBgH6EgX6AgoB+e3p+fXl5ent0eXx7goB8fIWCi3l8f4N8gIGEgIWEg4OBhXqMhoOCgYeEgoKZioWWroWDiIiAkZKpmJCQl42VkY2MiafBmZeOiZOXkpisoZCToJyakpaznpiVmZiYoZick5aWkpKWm56Up7Wgo5qZnZ6cn6SbrqCWjpCPjo+UlZmYm5WYmZiTlpSYmpOSnJCVmJyYmpyfo56nn56elJecn5+akZeTlpaNlZWVkpSLkpKPj5OUm5GAm7qhn5WYlKWXopeYl5Wcl6KcmZ6dlp6ioaCbmaGcnpefnqqnopyUlpqdlpOZlpejlZ6ooqCgmpCVmLmdnZ2io56Ul5WTl5KXkJeVkJCQlZOQk5OZkJuenZyTmaKdoaCoo6OjoZuZmZmTlZyY/+WioJiOmZ2hn5WenKGmqKa0oqSAnaWdoKSek8KdmZucn5qkoJqfo6irqKioo6Soq7KnpKKYm5iipKaoo52pnpuVpqqppqWxpKWkqbCpra6x8p+gpaKkqK2uq622tbaivOe0saqdq5ugqqOfnqifqp+ioZ+loaKiqKehp6Gmop2vraSnpKGjp7CxtKigo7KstK+rrakmpaCopaCmnK25rqqlpKGop6Smqqmpp6idoJuio6mYpZ2goaGknqCEpYCprbGvsKips6yvoaSrp6esqauhqKmqpKawraevpa6nsLGvsqyyr62mq6mwq6ion6mgm5+drb6v56KlrKuosK+0qLqnp6SlqrGmsaaho6unqem2oZ6oqaCZnKGjnpOjpKy2rqqqpKOgraKmp6irrLKjoKeqoKqlqaCfoKOmpKW+oYCyoqCkpJyjn6ajnp6flpz77J2UmaSWmJKOjo6blpKekZGSlZiYm5iWk5yXlpObi5GVk4+PkaWcpaKRj5KSspObkqCRnaKckpabmpKSl5WUjoyRko+PjI2TkZCUjquTnY2MlY+Qj5WmoJiTxamijJCNk8eomI+RqKCLj4yKj5/2h4CLj4WGh46QkI6MiIqJjYuBiIiFj4iKjYyNgvuDi4iIhoSJkoX/gYH294OA9/78+oD6/PeCgoaKh4WGiomIgIH5gYOFl6yJgoSElYmFhoSGhfr97fH7g4H/koaH0ZXw6PKF/YT48vf47vbziYL4gfL+/+/q/+na3ebv9YLt5fD09oDyg/b36tv2iYL2g4aD/Ont8+6HhPj1+/309/v19f7z9v738fz8+vryhfj97vTz9u/v6Onq4+Ho8OT16/DtgYD6iv6A9+yH/vz0goSEgYX+g4T0+IKAhIaKiIWHh4mPioeMjYaGgoKCjLCsg4OGgv/7/Pr4gP2B/4GLgI6UoI6CiFaEiYyHhoeKgZSG+4CFf4GIh4uL//uJjYyRjJOPj5GUlZCKipCPiomJi4WIjIaPjouIkYmXgYWIi4WIiY+NlZaUlJCbjKGZmJKTl5KOjbOVkqjjjpCYmP99/33OfQF+/33/ffl9AXyJfQd8fX18fH19hHwEfXx8fIx9AXyQfYV8A319fIV9Bnx8fH18fYd8BH19fH2MfAF9hnwBfYV8Bn19fH19fYV8gn2UfAF9lHwMfX18fXx9fHx9fHx8hX0FfH19fHybfYV8BH18fXyTfQF8iH2CfMF9AgIEAICMhI2Sl5GIhomGiomBhpOJi46Ji4mSiIOKh4aNjYuHgIKCk4yOj4mJkZKTkpiMl5GXoJqcopqRlaedlpeSkYuMjY6Kj46MjIiIhoWHf3qBfn+GhIqJi4SUlo6Th4uHjIqIl5aWkomMkpKTko2OndKelouVmZ2Oi4yPkYqOj5KJlYCVkZOWlY2Kj5KLj56ZkoqQjIyJd5CSj5PGk4+Ni4WZhomEiI+NkYeJjYuKjJCFiZGSlI+GmZmdnJuboJqYoZqXmZqYkpGOkZGTj5OSko+Sk5OTjpGYjZCMkY6Zm5efppmfnpaWkpmXk5iQk4+Vk4mOjYmSkI+RiYuJkJCUkpSTlTeXpJWSjoyRjZCQjJOVkJGtlJePmJeWnI+OkJSYm52bm5mJhomSrZWfl4ySk4+NiouPkJKZmZWShJCAk7CVlpSWmJ2empWKkImNiYuRkZCJh42LjZKUkImOk5eXmJeOkZGYlZXFlY2VkpSRjouRlJialJqYo6mVlJ2Xir+WlZmPjo2SlJ2akJaYlYmMjZCsnZGOjY+Rk4eKjImKiZORlqGRjZOMm5ORmZWRlo+MjZGOiJ2LlZGRk5aOlpaAjpGPlZKPioyOiquUi4mSlJqQi42IjIuKg5SQiIyNjY2PjI6RiY2Ug42RiouKipGPiIaIhI6Oh4yUj5CNi4mDi4iHh4SMjY2Sho2NhYaCg4WQhIGBgX+CgYKQi5GRkImEhIeGioiIiYiGhYWIh32CeX+BhHh/gIKEg3Z+goWFiYaAkIKEiI5+fHR3d3p6en15fYB7gYx+dHN4dX+AgHp5enx3gHSAeICRqIKVf3x3f3l/endpiIOIhYyDeHx6cIiRcW6BkHZxdHl4cXFydHNzbnFwcXJ0e317dnN/f4F/dnZ4fHR2cnJwa21sZ2pvbm5sZGZwbm1xfL1ze3N0cXyid9SAcGx0cW1tdXJ0cHV13XR16u3j6uzwe+rc3XR0c9Zz229zeId0cXDUdnZscHd1c3V3cXVweHJ0cnN52XV1duTddnXjeXJ1c9TT2d/khOHc3+Xf4+fU39Rzb29ybnBt1dRyctHY29/Y5fR/9+jk7u3o4fPn3N7X1ed739OJtdDg18hPznDZ3t/Z39l47djo3uTieXzqf3l5eNx3cW7T1Nbf3tfe6Pbn9enm3N133Oju5dNvdHpxbm9rbMpsaMLRb9TU2HJwb29xbnBtb3RycHdwb4R2cntzdH59enbv5IGDe4CAk392d3h+gHp7c3F4d3tveIDgeIN7j4Z5fYOCf+t6eH+GfOPu6XR6eXt5f4SYoYmIhYiKi4F/gYedjIJ/h4eJgX2Df4t+kImGhICGkYaLjYSMg4qFgYmQhYaJgoyElbTUjYeJkICGgImPlY6EgYWEh4mCg5KJjZSMjpCVioqMjYqMj4eEf31+jomJi4SEi4mKiouDiYaKjY6LjomBipmQjZKPkI2Pj4+Ji4mJiYqMjo+RkIyUkJOVk5OPkImRko2VjI6Mj4+Nm5mZlYuLko6RkIqKktSUj4mRlZSNj4+Tl5CRk5iRmICXlpSXmJSPkZSRk56blY2QkJGNgJSTkZXJl5ORjoyejpGOkpeWmpOSl5SUl5mSlJeXmpWKlJORk5CRlJGRmJKTlZuYlpeUl5SVkJWUlJKVlpiXkZWakZGPk4uUmJOZn5OZmJaWj5OVkpiTlZKVl5CVlZKXlpiak5OSlZSYlpaUlYCaqJ2bmZudnJ2blpmYlJGxl5eSmpman5OSkJaWm5yWlJKIhoyRrpiblZCTlZSTkZOZl5afmZSVlJSVlpvJlpeUlZicnZqWkJmUlpOWoJualJCWk5SZl5eQlpqamZyclZaUmJaV2puOl5OVlJKSk5WYmJWZlqCjko6WkIPDlpealICSkZOVnpiQlpiZlJeTmqyfmZaWlZaakJWXlpWQnJqepZqWnJWjmpaamJSck5OUlpWOnYqTkZKTl4+XmpWZl5yZlZKUl5K2mZOSnZ2il5SYkpOUkImZlo2Qj5GUlJOUl5CVm46WmJSRj5KYlpKRkI+VlI6SmZaZmZOUjZOSkJGMkICRk5SLi4yGiIiLi5iNjY6PjY+KiZCLkJCQjYyMjY2Njo+OjYiFjI+QiJCQj5KajJCMjY6OgYaGhoeKi5SLjJGWiY6Ii4mMjYuMi4uJhoqVhn1+gYKLjIiEhYmHg4h/iISJm7CJp4mFgIiDiIN+bX2Eh4KIgnd8gH2SnoB8jZ+BfoCBhoeDgYiCgoF8gnt8e3qDgYF9eoSBg4J8fn2CfX5+foB5fn1+f4WBhYN4d355dnqE2HZ9dnp4gK597Xt6f318fIB6e3Z2eOB0c+fq5Ojq7Xzr5uR5fXfgdeBxdXqId3R03Xl4cnR6d3NydnN2cXdydHN0fNt1dnjo3XV05Ht1eYB629zf4+WG3N7d4NrY3dTc2HVzd3h3fHjw6Xl22uDk4Nre6nnr3tvj5NvY6ePj4uHd7oHx65XC5u3t4uN46e/x4+rme+jX49zX23N04Hx1eHjofXt34+jh6urj5ev58f31+OzkfObn5t7Yc32Cenh6dnnme3vt9oH4+fZ7enh3dYBzdXN1e3p0e3Rzc3JycXZvbXd4d3Tr5IGBeX5+kn13dnp+gXx7d3Z6e35ze4Ptd4J+iYF2eX1+f+95eYCIgez593p/gIN7foCVnH2Aent+fXd0d3yShXl5gICIgX2Bfol7i4WDgnt/i4aJi4WHgYaEgoeKgoKDf4eAi63Rh4OAiYCbkpyfpJqRjY6Mj5CHipuTl5+ZnJ2jmZqen5mdoZeUkI+Onpiam5WUnJmbmZ2Ql5Sao6Ghp6KXnLetn6OgoZ6goJ+ZmpeVk5SWlpaXkYyVkJOWlJaVmZGen5mhmZyZnJqWpaOjm42SlpeYlZOQndegl4qTmJiQkJCUmI+UlZqRnYCbmZicmZKRlpeUmKeimpWalpmVdJugn6HkpaOinJqvmpyVmJ2VmY+QlpSUmp2VmZ+hp5+VpaWioZ+gn5ico52dnqCamZuYnZmblp2bmpicoaKknaOooaOhqKCqrKeutaarp6CemZ+cmpyVl5WcnJOYmJeeoaKhmp2do6Gkn56eooChrJ2dmpqhoJ+hm6OooqLIo6igqKinr56en6SjqaijoaKYk5uhvqeqpKCkpaShnJ6np6q4raaopaWjoanuo6ShpqiwsrGtpq6pqqipv66popyjoKCmpaGaoKWlpqWnnZ+doqOh1aSYoZyenpubnaOnpqSrqrS5pqStqY/Yr6+yq4CqpqytuLKnra6rpKemrMi3rKmop6esn6OmpaGdp6Oms6Wgpp6up6awra23r62ssKykuKGopKenqp2lpaClpKymo6Soq6i8sKusuLnBsq+yqqyqqKGzrqSpqqmprKeorKSnq5ypq6elo6exrqqop6Wwr6estq6traammaCenJ+eo4Cio6meoqSdnZ6gnbCkoqKkoaSgnaqmrayqpqSipaWmpaeoqKGho6Won6egoqWqmp+goqKilJuenaKgoKibnaGikY6Hi42SkI+Tj5aZl5qrmY2PkpKboJyTkpWWkJqLlpCYrMSdr5qYkJqXoJuYluyhp5+nm4yOkountpCLnbGQjYCRlZeQkJSTk5GOko2PjY+YmZaOjJmWm5mRk5OakZKSkI6IjYuIjJCMjoyCgIiHgoiR54OJgoaCibyC94KBh4eBhoyIi4KFhfWAgPv37/X0+oD17++BiIPygvuDhImahoOD/Y6Kg4SKh4SBhoSLh46IiIWGi/2DgYL99oOC/ouDiYCI+PX2+fmU7/Dz+fb2+uv48ISAg4SDhoT//IOB6vDt6uTt+4D25+Hq8efl+fLt7ejm+Yf27ZC46vz16fGC+Pv89vn4hv3r/PPy94KC+4uAhIP7iIiE+vzw8enh4urz5/Tp7OrpgvH5+vPsgIaMhICDgID0g4H1/oT8//uCgoKAgoCBhYKEjIuFjISDg4OChImAgI2MhoD/7YmMgoeHnouBhomPlo6RiYaOjI+BiZD9g4uGlYyAhImKh/yBgIiMg+z8/YGJiIyGi4+ptZCTjpCTlIuHiI6kk4iFjY+YjoqOi5WJmpaUkYeLm5OcnJOYkpmVkZOYjImNh5SMmsj8l5KSnf99lH0Bfv99532Cfv998n2Cfst9AXyMfQN8fX2GfAp9fHx8fX19fH18h30BfJJ9CXx9fX18fH19fIR9hXwBfYp8h30EfHx9fYd8AX2OfAV9fHx9fYV8AX2GfAF9hnwDfX18hH0EfH19fY98AX2FfIh9CXx9fXx8fXx8fJp9gnyWfQF8in0BfIV9g3y/fQICBACAiIaOkImOlZKSkY2Ij4eQioh/gIR9hIGKi4aHg3+HfYN+f4eDjYyOkY+MkpihmaWgmpyZk5udn4ylq5ONjJSPjI2Rjo2Jg4mAiH2BhpCJiZaTkZaVkYyRi46MjY6TjpGOjo2SjrGXnY+Uk4uQlaK2l5qbmJydoo2QkY2TkY2Zk4+Ai5WajayNh5GQkZGTjIuRiYqQmJ2emImgqYyUjYuKhYqMko+Ki42Hg5CP0pOLiY6UmpaWmZeak5+co5+emZmVlpeVlpucnJaTkJiYj5GPlYuSjIuQkZSQk5SenZqblJiVnqOck5WVlpqakZKMjYiLkYiOj5CZkY+WlJKXkpSblpQamJOTl5SZkpWSk46NiIiJhY6XjpeTjZKWmJmEloCZnpygpaihlJmOipWOjJSRi5SPi46Pj5WPkpSSkpCRlpeYl5mfnp20l5qempygrLiKk5eRko6Ui5KTiZKMiIyCioyNj5WQipCQk5OMjpKPl5iWopKSk5afmJCWrpOZk5ydoKChnZuZmJaWlpmZlJWUnJealZOVlpGSkY+KjYiPk4CSk4+IkIyTmpqQj5qal5iRlJGOkJSXjpSilpOUl42VlYiQiYmKlo+KkY+TlJKWmI6Tk5WWkpSUj5KTlpWPkIyHh4eNg4iRiY2Ij4aQhoqHjYeWlpSPnpuh4JOKhYmLmISJmp6Qi4qIko+KjYuNiYaWkYyPjI2LjIuBfoiKgIOKh4CLioWQg398f4h8hX+Fh4GAgYSGjoZ9gX56foR8eX99fHx8eX59fXx4dn18fHuHgoGYh4WBf4WNfoB8g3x/ipqEf3mEhqKJfISAfYKBfXB1e5J7eYmPfoB9lJmXhIOBfHl5dn15bG16d3l3fH92eHd3f4eAeXJ9en2AfXVycXV0doBzcHF8cnN0cnBucXF4c292bmxzddrodeXfhH2KiYB745t4eXZ4eXSHe353d3v28+b24OXZ3dtxj+Bw2WxsaWtpa8jLz29ty9Fzdnl3dnd3cHlyfnV1eXZ3eHZ1btJpa2xu2drQ0dPZ3HvW2tx0dOXpeHvg3dnd3Obh6ujV3uXW4YDq7Ofo6PLy8vPm4+nc2+Led+Prg+/W43zm6ufm397icXx0eHV25+7leXzv7fJ9gOzadnhx0dhu19zY4uLg5vbt7ufZ4N/d59tx3NFudNVw0HHY1Nrc1HV1cdp3dnd03uB8iXN04XZ8dnd7ept/eHR+e3jngYCDqHp2gfHw6HnohGJ4dXd7ddbabm9wdX1/moF7eX17hH95hXt9gnqDlYJ8enl67ul6hX76/H19hZK0hIKIhYmNiIaJhYKBhImJhoiFiYqDgIKAg399f3+DgoGAfYGBiIyKipGIhZCOiYOAhYuDhYCCf4GEgoaKiIaIhIWKhY6KioWFi4iOipGRkI+Mi42GjIiFi4iPiIuMi4WHjZKLlpGMjIyKkZaViJqgjIqLkY+NjZSRjYiGkYmTh5CNl46RmZORl5ORjZGOkIyNjpKPkI+NjY6It5Wdk5KSi4+Rl7mTkJGRj5KYi4+Pj5STkp6Zk4CRmKKRuZWSlpebl5aRkpSKjJGVmJ2Yip2mj5aRj5GKkJSam5WYmZSRnJrLnZWTl5mdmJaVkZGIlJCSlJOQkpCTlJSRmpudlZSTmJmRk5OWkZqUkpSVlpKVlZmYl5mSlZaboJqXlpaanZ2Wm5eVlJyZlZeXl5uUkZqVk5aVlJuZloCblpmcmJ+Xm5mal5WXlZWUnaKYn5mVmp2cnpubmpaSk5CSl5yYj5aRkZyXlZuYkZWSjZGYl5yYnJ2bnJmal5aXlJebmZajkZWZlJacprOPl5ybm5efmKKhnaSbmJuXl5iZl5yXkZaVmJiRk5iVmpiaoJKTk5Oblo+UqI6PjZSTloCWl5iXmpuZmZmdnJOSkJeTnJqZlpqYmZiYlZKRmZ2ampePmJaZoZ+WlaCcmpmTkpOQj5OakZakmpWXmpScoJSZkpSToZaSlpSYmJiampKWlZaXlZWSkpWSmJqWmZaTkJWYkJablZSPmI+Xj5aUkYiSlpWSnJmdzpOSj5KWpYySn4ChlJGOjpqXj5aSj4qImJKRkYyRkJGPiYaPkYaJj46SkYqUiYqEi5KIkoyOkIiLjI2Nk42IjYuJjJOMiY+NioqHhYyMjImDgIiGhYaNioupko+MjI+Xi4uIjIeGj6KKhn6KiqaMgImEg4WGg3l9hJuGf4yRf3t5j5OSiomKhYOGh4COh3yBi4qKhISFf399eX2DgHt3foCChYR+fn2AgYV/fH+If4CEf359g4aKhH6AfHh+fuHodezif3qIh3175IV3d3N4dnODenpycXbt6ujz3uXd6ut8nul37HR1cnRyctzd4nZ32+J2dnZ0c3V0cHZxfXd1f3Jvc3N0cdtwcHJ15IDp5d/m4uSA2t3ec3Lh3HZ95Ofk5ebp5/Hu3+Lo2uLk4+Dc2efm6Ovh5Ord3+PkeuzwgfHb43/m7OLq5ODecH10c3F039/ddHfi4eZ0dubXeHd14OV35+zl6O7h6PTr8vDo6+ro8Ox67eh8f+t65n3u5fDz7oSDfe58fHx57O6BhYB0dOJ0eXVzd3eje3RxgHt33np8f5p4dovz8/J89IN/fH2Cfejod3h4fYOFmYN8e398gnx4hXt9gXqBnYR/gX1/8u56gn/18nd3fIaue3l+fH9/fH2Df4CBhIaEf4J+g4F9gH+AgoJ/hIeKhoSDgICBh4eFh5CFgomLhn18goh8gYCTjZGRkZKWlJCRjoyTipWQkYqMkYyTkpyem5mWlZeRnZaVmZSgmZyempaYnqeerKahoqGepbCvnbzInpiXnpuYl56ampSSnZWdkpaWoJeZopuYnZqYlJmXmpmbnaagpaGdn6Ke5Kivn6KimJ6fqcKfoaKen6KnlJiYlZyYlqGbloCRmpuSqZOQlpifmpyWlZmQlpudoKKekqCqk5uWlZiQl5ignZaZmZOPmZe3mI+PmZ6jnp+ioaScqKeppqScn5ygoaCeo6ammpiXoKKcnJuhmKSdmaCbpaGjpKeoo6SenpyhpZ6XmZmdoaKYm5eXlJiZk5eWm6KZl5+dnKKenKSgnoCinZygnKGbnpmclpmZm5uXpK2hrKefoqelqaOlpqKko6alqKyonKOen6ympamlnaGfm6Cko6iip6urp6KlpqWlo6Wpp6W4oaWrqKuxvcWcpKmjo5+mnKSmn6ejn6CdoqSjp7CloKakp6SeoqOepaWjq6Cjoqatq6KqwqqtqLK2uYC4uLSxsrGurayys6ytrLSvtLGurbOur66sp6OgpqusqaGbop2mr7Gop7u3tbausa+rqauvoae1qaKlqKOss6aro6emtq2orKuxsrK1s6qwr62vq6eqpamorrCprqilo6SonqOupaegqKCrpKuoq6GzuLOsubK69bCnnqaruZ+ls4CypaKfnKuooamop6Ghs6yqq6Wop6qqopuiopqaoKKmpaGvopyco6yfqKSnp56fo6eosKmiqKSjpq6hnaGfnpqTk5eWlpSTjpWZmZimoqPHqKakoKasnJ2VnZWXpcGblo2am7mikp+enKGjnpCZoMqfmqWtl5CPo66qnZuZk5WVlICclIWKlZSTkpGXkJCOi5OZkY6FlZWanJuTkpCVlJmUj5GbkpGTjIuJjI6Ui4WJhICHiPL9gf/5j4adlomF+pmFhYOLiIWYjYqDgYL/+/P+5/Xk9PSBpv2D/4GFgIOAgvPy/IKD9v6HhYyKhoqLho2Dj4iFiISBhYSGgv2CgYOE/4D/+fX59viN8ff1gYH++IWJ+/b29e758vv25vH56vPx8O7s5vXz8vrv9P3s8vHxgvj7i/7j64P2/Pb8+PL9gI6Eh4OH/v74gob89/+Bgvzxg4WD+/2A8/Xv8/Pq8P34/Pfr7e/w+PqB//mFifmC84T27/f27IWFgPGDhYWA+vyJkoCChP+Ei4mGiYrDjISAjomF+4yJjr2CgK7+//mA+Y2DgYaIhPb8gICDiI6UuZCHhYqGjoiAlIKDh4GKro2FhoGC9vSAhYH8/4OGjZnBj4uSj5OUkI+TkpKTlpiVjpCNkY2Ih4eHioiGj5KZlZSRjYyOlZaRkJiNipKWj4mGj5iLkP99/33/ff99/323fQV8fH18fIZ9AXyNfYl8BX19fH18hn0HfHx8fX18fJR9AXyEfYd8Cn18fHx9fXx8fX2efAh9fHx9fHx8fYd8hn0SfHx8fX18fHx9fXx8fX19fHx9kXwJfXx8fX18fXx9hXwEfX19fIR9gnyEfQF8jX0BfId9BXx8fH18hn2CfJt9B3x8fX19fHy5fQICBABwkYmQnI+NiZCRj4WMioyCjIWCh4qFhX+GioyGiYSLiY2VjYyDhH2EgouHhYeNj42Plo2Pl46RjoWYjo2EjIyKi5CJj5OLjY+LjpCHi5KTjoeQoJaWk46Jl5OOkIyOkI6KkpCJj4eJj4uQjIyJlY+XlISVgI6VjISMkoKTj5KUk46Pk5mXlpyimJWWkpaUi5CWjZybnJOVlZCUkZOdk4iblJWYlJSYlpSWloyQlY2JiY2MkphynZ+Rlpiem5ibmJWVkqGdkpSVmI+RhpCemJCek5GRlZqdnpybmqSnoJmbnKulppmdm5ean5mWmpicm6CfnqCZgJSSl5+ZmqGZmZmYm5mhmpqdlZaUjpOUlZGSjJuUkpeJiZCNlZeXl5ygk5+XlZ+alpeUnJmcmZOMkJCNipiSkJCYmZiNrMyWl5aUmZ2TnKOgnp2bnJ2bnJabn5SXlJCNioqNko2QmJKYk5SVmZ2el8i+qKWkn42YlZWUmJSQmJSXgJOWm5qVlZSUk5OYlJCbl5SOmJOWk6mUkY+NkZiVmpSVkp6Zk4+Wko+UkYiQl4yPkpuNmJKRjJKXi5KNkZSXj5CYlMCVlpaXko+PjJSYlJuWk5GVkZOQiZaOiJGNkY2Si4aIjIeFhIeIjIKGipGGnaeKhoaChI+OhpKRf4iNk5ebgJuamqKTlYqcmpCNhamtkI+Nh4aIiYSJhH+Sj4+YnpORotLjhIODkIl7g4udjHCKh4SLiIWKgXx/iIaGfnmDiHh8eXh+gn5/hIGEf4N/jICFf4KJgoSBgYl3doZ/g355goyOf3+Dgn+Hen2AgX6IiIeCfYeGi4iHgYCEgYSCiYaEgH19hIaAgIaFfoN5dHh2h0aIeXl6fn+Bgn96gYaKpHx9eHeAgpR8dnN2cXR1cnR0aG5wd3Rwc25yam5xbWtxc3h7e3eLfYB1d3J8c3d2e415e+J+eHp5d3l6deHzd+1143J55eRy1Wxsc9dy1G5t2X90gn55f4V9dXNyb3CAfnZzgHR0bmxwzc1scG1qanBwa27abm/V4N3m2+fr6OF22tZz29fScHDU03Hf3tzne+Tk6uPk7O3j4eF2eNnW63rt5uPo6XiA5990cnZwfHCDgHl1c3J3enh7eel97u7v7Xzf4txven18eeTp6Orh3+zk4trW2NXT2+R6eHd2fXh95OTmgNrW3+l4fHzqi39553V1eHNybYx+dHVydXF24eLqdXp54OZaXHl3end2eOLec3V0eXZ2fHx6fa7K4nl7fX59gHp/f3l6fYCBf3lzdn6PgH+EgYaIhamFg4yFgICBgYV/eYh/ioiMlY+KrI+Pi4aDgIB+gIqIg4ySioODhoKEhIuODIWIfoiKhYeFh4yGjYCIgYOQiIaDiYqJgIqKioKMh4WJkIuIiYuPkImMiImJipSNjIeLgYmKk4yNjZCRjZCUi4yWjo+Qh5mQkYuRjo2KkYqSlIyMj4+SlI+Rl5iTjZagmJWTkImUkoqPi4yMiouUlpOWjo2VkpWSkoqSjZGPj5GTj4+ZkoyTm5GZlpeZlYCQj4+Vk5aZnpaUk46Vk4eLk4uWlZiSkJGNko6ToJOKmZWXmJeXnZ6coKGZnKOcl5ealpeZcZSVi46PlZOOkpSWk5OgnpiZmJmTl4yWn5uTm5SSjpKWmJaUlpSanZuVkpmiop6ZnZuYnJ6YlpyXmpyfnZudl5CQlJqXlpyXlZSVmICVoJqbnpmam5WbmpmXmpWmnZ6gl5ObmJ6cnJqcoJagl5aempOTj5WVmZuamJqdmZSfmZSQmZmYkK3NmZyclpmblZeenpqXl5qalJmWl56Vm5mYmJaYnKCdnqKen5yamZuarZXDv6aenpqNmJaYl52bmqGanJeYnpyYlpeYmZqcloCRm5aUj5OSlI+vmpSTkZSdmaCYlZWinJmTmJSUmpmOnaSVmZqimKOal5SXnJKXkpWYmJCWmpazl5iXmpqVmZWanZminZaWmpaXlpGblpWbmJmXnJqRlZeSkZOVm5iSkZWelLe6lpGQj5CYmJalmYmPlpucoZuZmZ+Rj4eWk46RioC7vZmUlJCOkZGNko2GlJOUlJePkqbL25CRkqecipKbqZaFlJCKkI2Hj4iDiIyNkYeGkpmKkIyKkY+MjZCMjIqLh5CIi4eGkI6NiZCWh4WXkZaQjpKgo42LkZGGiYOGhoWDiYiIhH+FhYmKjIaFh4eHiYmFgXp8g4N+gIaIhYuGgYCFgpBum4KEgoaDg4F9eYGEiJ98fXp/h4qchX97gHp7fHt/gnV9fYOCgoSAhHt+gnx6eXyAfoB6jnyAdHRwe292c3aLd3rlgHZ3dnV1d3Pc5njqduZ3eebreOF1dnvjeeV3c+GDdX13eXyAeHJycG5vfXt0dHd4eHd23+BydXJwcIB4enN243Bz1tra3Nnl4OXkdt7jeOXk5HV04OJ25eTc4Xff2uPc2ePk3t/gdnfb2Od57ePg5eZ2febmdnN4cn50hX52cGxtb3JwcnLadujt7+h45uvpc3Z5c3Hl8PDu6Of27vXs6Ovs6urufnx6eIN9f+fq6+bk6+15e37oi3935YB3dnt2cm6GfXp3eXZ1fe3s8HqEgvD1bYmAeoR/fXz08Ht6fH5+foOCgIKeyfeDgIWCg4V9fYN9f32AgIB8fHyAk4SAhYJ/gH/Fgn6DfHl4e32DfnqHfoGChoqDfZiDhoWBhIKEhIOKioiMjoeGgIaEhYWKjoWHgYaOiIWBgoSAhVKflpakm5aRlpeVjJaVlo2Yk5OYnpeVk5mdnZaWk5eYnqWdmpKZkJeYn5mVlpydm6CrnqKvpamjnq6gnpKZmJWUmZKdo5udoJ+goJiYm5qVjJSkhJyAlaWknKCZn6GhnqyqoamhoKWgo5+emKGYoJ+hnaCbl6CYj5aajZmWmZqYkpSUnJqan6OdmpiWnJmOk52VoKKjnJ2cmJuXmqSai52XmJqVlpiYlpmak5aZlJOSnZifpamkqJmdnKelnqKhnJiZpqGbnJyelpuPnaainaqlpKCmpaeAp6KjnaeloZqanKajopedmpmcoJuZnpufnqGin6KdlpScoaCgp6Gcnp2enqaen6OdoaGdoqSnoaefs66ssKSfo5+mp6Wmqq6gqaKfp6OdnJykpampp6OlqKShq6SemKSlpp7G9KeppaCipqCmra2sq62tr6ippayypKWioJ+hnqMip6KlrKirpKanrKvIqvfuxbi3saGtqKqmrKilr6qrp6qxsYWtgK+1sau2sKynrqyvoMiuqaajqrKzu7S0s8K6s62xq6isqp6mr56gp7Cns6iopqyxo6yjq62sqqqsq8yoqaqqp6Srqa+1sbm2srKzrrSvqLewrrWysauxsKaqraekpqqvrqeorrWrz9OvqaekprKyq7qxnqewtri7srKyu6ejma+tgKiooMjOsKmlo6ClpZ2inJWmqKqts6muw+LwoKCht66Xo6/DrYmwqqWvqaKsn5qbo6OonZmps6Gno6Glp6Cgop6gmZyWpZmdmZ2loaSfpK6ZmKyio5mYm6ixlpaZmpOZk5WTlpafo6KblqGgqamqoKCmpKSjopuVi5CUlpSXnZ2WgJmRiZCMoZS4k5SaoJ2dnZaQmp+gxpKTkJGcoraclZSXlJaYlZWYiI6OlY+Mj4qNgYWJg4OGh4qMj4ufjpaHhYSMgYiEiqmJiv2ThYWGgoGDgPD8g/+C+4CF/P+C9oKBif6I+4WB+5aJk4+OkZeOhYWDgICPjISEhYeGhIf5+4WIgIOAgYmHgoT/gIH0/fb58f36/PqB6fCC9/j4gYL1+oP8+fD4hfbv++7s+fr39/aDgvDr/4X+8u72+ICK+/mEgYmFkIaelo+IgYOFiISEgvOB/f34+ID7/f+Ch4uFgPr9+fLn6Pv3//r4+/bw9f2KhoSEi4SJ+v377O70+oOGif+ZgI2H+4WFioWCgJKJiISDgYCJ+/b+gYiH9v+h64eCiISChf/7hYOGioyKjo6Li7T//4eIjIuMj4eNjoeJhoqNiYOCg4mdjouNiIqMjNyNiZSOi4qMjpWQipiLkpGTmJCJp46NjYaIiIuMjJCQkJWakY2Kj5GOi5GShoyHjpaTko6UA5iUmf99tX0Bfv991H0Bfv99mX0BfuF9AX7BfQF8iH0VfHx9fH18fX18fH18fX19fH18fX18ln2CfIl9A3x9fYl8DH18fH18fHx9fXx8fYR8AX2KfAZ9fXx8fH2FfAR9fXx8kX0CfH2EfAR9fHx8hX2QfId9h3wIfX19fH19fXyOfQp8fHx9fX18fH5+hn2CfIp9A35+fNN9AgIEAIDMjY6blI2Ww5OPjZOih4WIf3x/g4B4foGChYiHiYiHh5aan46JjI6MjYqFm5yFjYmVlZaUlouKk5GQiI2OjJiKkZORkI2LlJWHl4p8lp+PfY+alZOSmaCdmpiQmZmPhYmTkY2DjY6OjpSVkpGRk5KYk5iOko2FkYmNi5GajpSaoIChoqGknZ6gqJuWmqCbmpyXlpibnJmQlJWTl56ZmZ+hppabp6CaoJ2XlZKWnpybmZaVj5SSRpueoZ2gpp+alJCYk5aUlpaclpSVlYePpp2do5ydo56al6Cdm5mhnKOnl5qeqqKln6CeppuapqOjpJ+nm6Gdnp2XpJ2dnZybpaKdmICZm6CgoJ6gnZSfk5aZlJWVjJWVj4qFi5mPk5yXmJeemKCVm5WPmZyTmJ+WipeYkomNipmNk6Cemo6SkpycpJGboHShnJidnZqbnp6ip52ompqZl5KPkZORho6OiImQj5CUm5eYmpeYnJyWoaejnJ6Znp6npJuZnKKpmKKZlJWPkYCXmZOKkJWNjJKLjYySkpiNipCSmo6XkY+VkpWZlJ6Tk5iTjJSOjI2RjJGPjY+QkZKOnJ2Wl4uLkZOOkJCcl5OPkJORi4qPk4+Zk4yVk5eQlpeXlJSMkYqMg4R+foaJh4qLiIqNl4iIiIOPiIuOk4mKl5nAnJejlo6TjoyQlJWSoYCVnqmWk4mGhI2RkY+Di42MkZaRk5GOiISBiYWBhoN5ipCFjYaKlZGVj4iGiI21gol2f5KUd3mAgoaGh4GEi4aDiYx/f4SYgovYgXyJgoODgYSGfoV+iIp9hn+tgHuNhIiEhYR9c52FcIKxg4CFiYqKioaCf36Eh4KChYCDgomCeYB4eXyBe32JfXyCf4KEg4uSe4d8fIR1d6aaeHN0d3J5cm11f25tbXJyb2xybdRtcm9ubddxdXZxdHB0eHZue3l3fHh/gnl9d3l5dnp4eHjp43Da29Zwc27QbWzS0XPJb8VqbtFu1nN5d297dm7a1IB6b9Vv09LZcnV0dHNrbXBwb4B5eXZ3cXFwa21z3tR139l3dux43nHYcW3Rac/G09HXdXJw33V24XfR13rY3HV013t0deV06nZ7f3vddHp0dXFvdn+Edel9jHfygI7v73fqeX/x5np6d37q7fV959vqeOPndefe5uF4dnl6fX3mfICMhH3pfnl1etXQfXh5eXN0c4B24XN0hn57dXd7eYB4eHdwfHN2htFxeoSRjXOCd3zefnp4eXV0d+/m7ICD63vrfHh9fYV6end6foF7foCFeIKAg355fICDh4GIfoyKhH91e3qEh4aGjZGXlpGRiJGUlo+HgoCLhIaLi4yPiYyIhYWCjoWCi4KDeoSGiomIiZGRkYDKgIKPjIaQuIyMiZKji4mNiYeJjYyHioyPj5COkIuJhpWXmo6FhoqKioeEmJiFioeNi4uNjoiLkJKRjpGPjZiNj5KTko2Ml56RnJKInKSYjZeelZSQkpWRj42KlpaSh42XlJKKkZOWlZmYl5STlpOblpqVl5WQl5KVkZiaj5OVm4CZl5SWkpaTmo6Nj5OPj4+Nj5GVlZeOlJCQj5GRjpSXmY6Un52YnZ2alpOUmpqcnJiYkpijUJybnJeWnpaUjJGYlJeYmZqamZaVlYyQnZmWl5OUlZKQjZeTk5KVlJudjpGVoZehmJaWnZORnpyanZifl5mYmJaRm5eWlpWVn5qXk4CWlZyamJWbmJOflJWamJ+hmJ6em5aUmaOam5+Ym5WYkpmQmZaSm6CYnaKaj5ugnJqcmLyXl6KgnZSXl6Gho5KaoniknJqcmZmZl5aYnZaemJWZmJeWnaGemZ2bmJidmpmXp5iYmZWSk5SQmp2dk5aVmpylnpmZnKWonaCcmpiWm4CeoZ+ZnaCbmaKem5iamJ+WkpmWoJSdmZacl5yemaGWmZyalpuemZuamJmXl5mYmZqXpaOenpGWmZuTlZmmn5uVl5uclpKZnpedmpCWlpqTlZecmZ2XnZmcl5qSlpmcmZmbmJSaoJGSko6SjJGQlIyQmqLToZmgmpSVj4+QkpKPoICWnqeXlpKOj5eYmZeLlJeUlpaWlZiXkJGSmZWQk46Gj5WFjYeKlJKUkIyMkJLHjpaCi5mZi4uTlJaUk42QlpCNkZOKiouaipDbiISNiYyKiIyOiYyKlZeKmJOuiomajpGOjIqGfaOKd4bDhH+DhoiIioaDfn2CgX1+g4KHiZOIf4CAgYGHgoSPgoGFgIODgIuPfYd+f4V8f5+hfH1/gH+EfHeBi319fH+AfX6AevB3fHx5eOp6fnx6e3V3fHlxe3d7enh9f3l5eHd2dXl7e33s6Xjl6+h9fXfqe3js7H3fettyet9y23F0dHF7eXPj3n16dut26+HhdXh1dnhubnJyc4B4enZ4dXJza2xx2tZ14N53dOh45nPjdHLicOLa4t/keHRz5XZ45Xze4pju6Xt33nl0c95y5XF2e3rjdXl2dXR3fY2ZdeN+l3LhdYTg4HPkenzs5nR1c3jl5+9869/seOfxe/Ls8e58e31+fH7lfYOMgnvteHt6feHcfH5+fXZzdIB05XZ5l319eHh6e353fIB7gnyEl+p6gYqdjnuCe3vpf399e3l5e/n494OH8X71fnh6f4N5dnh5f3x7fn+HfYB9gX95en5/gnyZepGxgn95fHmBgn2AgYOHhoODf4qMjIiEgHyHg4WGiIiQiIuIgICBh4SBiIOCgIaIioWEg4mKioD4mZinnpmjx5+cmqK1mJecl5OVm5iSlZiampqZmpiWlqSmqJiSlZqamJaQqquUmpekoKaoraKhqaeloKKfmaGVmpqcm5iXoaSTnpOFnaaVhpqknJqVnqako6Gcpqijm56opJ+UnJqdmqCem5uam5qinKGZnJiRnJWYlZqjk5ufpYCjoqGjm5+fo5mVmJ2ZmJuamZuenZ2UmZeWmJuXl56go5SbpKGaoJ+ZlJKUoJyhn52blpy3l6msrqqrsqmknZyjoJ+dnJqcmJiXmo+brKWlqaakq6ahnqqko56gnaKklJidp5+noKGhqZ6dr6enpZ6knJ2bnZmRnpmam56hqaOhnYCgoKako6OopaCoo6Wnpqutpqmqo52YnqefnqWdnp6jo6yhqJ+bn6eboKeelaWrpqWpo7qfnaqqqZ+kprCvtqGqsoGyrKqwsK6tr66xtayzp6ekpqGfpqmnoaempKKpqqelv62qraimp6ikrbCwpKmlqquzr6iprLO4rLGtq6+rrICzuLGnrrOtqrCqqaaqqrarpq6vuay3s7C7tbm9tbuqqqqgn6OjoqSnpailpaeoq6yptrqxrqOiqa+nqKq9urWusLO0rq2xt662s6murbGorLCvrK6orqmup6eiqK+1r7Kxr6+1vqupq6Sxqa2xta2wvsj/w7jCuK2vp6anp6ems4Cmsr+qp6Cgoaywsa6gqKymqaytsbSwqKenrKunqKGWpqmbpJ2eq6aqpKCfoafpm6WOmrK0l5iipKilpp2fqaCepaiYm5+9oK35oJypoaWhnaCfmJqTo56UoZnDnZqynqWfoaOdkLyljaHxn5iepaakpaCZlJSYlo+QlZSbnKeakYCRlpmemZmnmJeclpmbl6Orl6GVlJ2Qkr+7lI+XmpeflI6Zp5GPjY6QiYiMhf+AiIWDhP2DjY2IioSHjouCjoyKjIqTk4uOiYWHhYaGg4f6/YD1/viGhoH2hIT+/IfyhvCCiPuD/4OMioOPi4P/+5CKgPiA+fT8h4qJjIuAgISGhYCNjouLiIWFgIOG/PGE9+6DgP6E+oD5gIH/gP/1/PT9hYGA/oOE+ofz86H++4OB8YaAgPeA/4GHi4r1gYeFhYCAiKG1g/uRvoH9g5X39ID1gIP9/IGEhIf/+fyA8ur6gPj8gPfz+fOCgIODhYn3hImRioL8hYWCiO/ti4uNioSEg4CG/4OFp4qHgIKEhIaAhYqGjIaMpfWBiJOfmoSOhov9jJCHhYGCgfz7+4OI8YH/h4GEi5KHh4aFiomGiYmMgIeFiIWCgYeJj4muiqjZkY+JjomVlZCSkZCUk42LhZGSlpKMiISPjYuJio2WkJWTioyPmJSSl4+NhpGSl5KSk5yfnv99tX0Bf/99jX0Bfv99/33sfQF8hX0BfJt9F3x8fXx8fH19fXx9fXx8fXx9fH19fH18h30KfHx9fX18fXx8fJR9EHx8fXx8fX18fXx9fH19fH2FfBZ9fX18fX18fXx8fXx8fX18fX19fH18hH0BfIp9D3x9fX18fX18fH18fX18fIR9C3x8fH18fHx9fHx9hHyGfQF8hX0BfIR9gnyIfQF8kn0BfIl9AXyHfQh8fHx9fXx9fNJ9AgIEAIDdi5WXiomPhYikjY6Jjo+MhISEgYeDfoCFg5WFho2Nh4+Tk4mHgIaHgoWOn5WMlJSXkpWXlpGPlY6QlJGXkJKVlJ2Zl6m0i4mLkoyIgoiOkZOXkJqYkpKMjo6Pgo6Xj4qSioybkI6Mio6QioiHjY+XlpGEiYaFj32CjoiPkZOYloCXoqGmm5OeoaGflo6ViI+RmZSWlZqYl6Obk5OYkpegs5SVlpScn5ualpOYlJaSmZycn6GZlpiel5yTmKKfl5yYm5SZnJ2bmJ2jnp6TnJqVmZmWnJSRkpeUoJKgnJyenqGbpa+up6Cmo5+gq6KloqSmqqKoqqmmqJ6jnZqhoJmpoICcmpmrmaKjsJ2amZmclJmXjZeSkZODrZaXpaqZlqKgoaGapJqanpGbmZ6cl5eWlZWYmZeVlZSUmZmboJuemZeYmJaXobqpn5mZlaimqainopifk5iRmZKSkpGYmJSRl5WXmZ2elJuWm6WWlpeZnqWbnp2jnZCmkZn7lp2dm5iTi4CWl5GelZOQkY+RlZWOj5CVkZSdm5OQk5KOjZGcqZyZknqVkZWMlpGQkZCTl5aQkI6Jj5ySmYyKjJKLj46Oi4yRioiNjoyIk5CSkJKNkJKOmZ6Tm5mYk5WamJSwjY+KjouIjIaFio2Ofo6Lj4uNmJChpKSkoaOnlJSVjZSXk5eQmICPjoySkouNkI+KgoWGjIiLj5qVjo6Ni5COi4qJiomQi5Ock5OXmZmVj4mNhYmHi4KEjYmIhIaIj4qQiYuLqLGJkJSJh42SjYuEgYF/g4GBhIOChYV+hIN/hIWIhX6Bfod+g4V9gqSDdoGEhH+Ci46IiJOFlJSKi4J/goeIh4R5coB5fnyBh4p+gXt4fXmBfX54g4p5e314c4V+a2VqdW14eHdvcnVicnR4dnNubnBwe9Jqa3N3eXlzeoJ5d3V0hYB1f3uAe417gnp6e4abf4B5fnzh3XV7gXaTfXNy33Zx0W1t0txxc4B0cnR0dop0b9PdctXVt4Z1d3V3gHaByNBqbYB9f3ptbWtr0Wltb2/Hzd11d3Xk19ra1NXX5d3N4uDh5+d64enn4nfmhX7md+p75Xl26ODZfe146ensd+t7c3R1c99zc3t9gXfmevPrfeft6efr4OPl3/Lp7vP36ebk3ujue357fHt653R+f/zy8YKOe3x8hoCQ7YSHeXt8eXl4fYDdeXh24Ht54I57d3p5dNpz3XJycdvk4XR82Xd5dYB2dnJ2d3npdnp7fXx/e+58fH2Cg4p7lYiChIKFhYV9fYKCfH94eXx9goF+gYJ2h4aKg4B7nIuLjYqQjYyQlIWJi42NhoGFhoqOrcWDg4SIhouShYWMi36DfHl6g4SKi4mHkIDpjJaXjIuQiIihjI6KjI+OhoaJh4uKiIeNi5qMjJCQh5SWlY2MhoyNioqNm46HjY6PiYyNj42JjIqOlZKWlJWWkpmWlaezj5CZnpaQkZSWlZWWj5mal5WOj5CRiJOakpCZj5GelJWRlJeZlZSTl5mfn52TlZaTm42SmZSXlJSTkiSQlpOWjoqSlpeZj4qUi46Qm5aWk5uYmaWflpWblZedrJSTl5WEm4CYlpqXlpOZmpeampiVlZeVmpWYnZmQlZ2ck5iZnJiWmZ2XmZCUkpKSl5aakpSVmZOekpqYl5WWl5OeoKCemaCbmJaemZqYmJeYkZecmZmYlJqWl52dl6agmpiXrZagoKubnJ6bnZqhopqelpaajbmhoamsnJqhnJydmZ6XmJ2WnYCeoaCen5+cmpiampydnp2gnp2knp+blZqXmZuet6OblJKQoJ2ho6Kfl6GZn5ehmpmdmJ2el5WalpWVm5eSl5SUoJaWmJaZn5mdn6WdkaaXn/ScnqGgnZuXnJ2apZqZmpyan6Khm5ucn5uYnJ6XmZqYlZSWorCjoJyKoZqimZ6cnYCcmZygm5qbm5WapJ2lmJmYnZebnZyXm56ZlpyclpSdmJiVmJGTlI6bmpOZmZiVlpqbma+Xl5SZmJOZjpKVmZWIl5SXkY+Zl6Kgoqago5yXmJWRlZeSl5KZkZGPlJuXl5qZk5CSkZeQj5Oim5WQk5CYl5KRjJCPko6SlYuOlZSXkICQio2EioeOh4uSjo2JjZGTkpWOkJKosoyRkoaGi4qHh4SDhIaLiouNjYuOi4eMi4ePkZSQkI+MkoqNioeIqoV7g4WGgIGKj4eJkYSRkYuJgn+CiomKioKAg4qFhoyOg4N+foJ/hH+BgIaMfYF/fXuLg3Bsc4F+h4aEf4OGdH9/goCDgn58fXyL83t5fn9/f3qAhXl2cnSAenV5d3x6iXeAd3Z2f5N6enR5eN/ddnx8do97dHjqennsenrm53d2gHZ1eXl7in587u165t20jnp3dXh9eYbL2nF4iIiEdHVxcuJub3Fy0tbidnZz5eLi5d7d3u3r2evn6u3ufOjs7OZ56oCDful363nkdXPi2tN24HLd3t9y33Zwc3Rx3HF0d32AduF36+l34ubq6fDr6ejj7+jt8fPs6eXf7vN8fnt6envveX577+ftfot7d3mBfJ/nf4Nzdnl5enh84nh5eOd9e+qSfHp8e3r1gPl/fn719fJ+j+B/fXp/e3l1d3h57Hl9f1iAf4KA+4KBf4SGin2djIKCgYKDhICAgoSAhHx/fX2Bg3+Bj56EhIiAgnyhooaJhIeGiI2Ng4qNjI+JhIWIioqx44SEg4aDhI+Cg4yIg4aFh4SMipOQiouUgPKXnaGXlpyRlLCZm5aZnJyVlZiWnJyYmJ+arZianp6Xn6GflZeQlpWPkZaom5ado6agp6msqKarn6Kqpqidn52bo56cs8GVkZuelpCOkZeYmqCbqaaio52hoKSWoKyloKWgnqyhn52cnqGZmpebnqWjpZebmZyik5ahmJ6bnJ2bgJiioKOYk6Cho6KblJ2TmJyknp6coZ+fq6WbmqCcoam7nZqbmZ6dmZmVlZuam5yjqKerrKihoqOepJ6kraujpqejmZ2en5+dn6GgoZqjo6OmqqyzqqmnrqWzoqqmoqKeoJqiqKSmnaahnJujm6Kcn5+lmqOmpqWlnqScoaafnKqjgKCensaZqKm4paitq62osLOpsKmhpZa+p6WpqZubop+hoqGqoqiro6ajo6SjpKOgoKClpqimqqasqquwqqmmn6emqK2yz761rKqluLa3urawqLGmrKKupKOoo6mrpJ6ooqOlrKehqKSltqeorKmstayxs7uvo7ypsfqxsrWzr66lgLO1rr6yr6+sra+1tayrra+urrW4tbi5tbGrr7zJvbi2prOtsaWtp6qsrbCztbCxq6SrurG3qaars6yxsbOxs7qzrre2tK25tbWytqyvr6m6tqiwrqyorbKxss2vtbK2ta6zqqqxsK2crrGxq625t8XHys7Hyce7urmttrizta2xgKqopaeoo6Soqqego6Syqq+xwbeysrOstLGpqaSioqWip6qgoqmqrKKfnKCYpKCpoaWuqKmjpaqrqKukpaXEzaCorZ+fqKyopqKaoJ+ioqCjn5ydmI+cmZSbnaWlo6OdqZ6jopubwZ6SnKGhm5ymqJ+iqZqrrqOelpOWnqChoZSPgJOclpqfopaXk5OWkZiSlJGepZGVl5ORp52FgIWYj52cmZGVmYGRj5ORj4iGhIOU/IGBhoeKi4iPlouKh4qYk4uSj5SRo46WiYmJk6iKjIOMifr5g4qMgqyJgYL6hIL8hoT4/4aFlYaJjYyMoYiB+P+D9/bXoYuKh4yWj5vV/YGHTZydmoqJhYP/gIOCguzx+oKEgf72+vvz8PP9++f69Pb9/IX6/PvygvqOiPaB94T4gIH78OuG/IH5+/uB/IiAgIKA+oCAg4eNgveB+/WChPOA+fn29+7+9fr8/PXz8enz94CDgoOGhfuBhYL/+/2Gk4GAg4yGs/2PlYKGiYeHgYbygoGC84aH/KCDgYKCgviD+4KCg/z/+oGK5oSFgIyDg4GAgYT8goKEhYKHg/+GgoKIi5KBm4+GiYWIiIV+f4OGg4iCg4eHjo+Kj6G4lpaakpIvjL/mlJSLk5OVmZuRmJyZmpOJi42QkKu+jIyPkpCQm4+RmZeMkImMi5ORmJqWl5//ff99/32jfQF+/33/fZB9AXyhfYJ8iH0IfH19fH19fHyLfQV8fH18fIp9AXyJfQF8hH0GfHx8fX19j3wBfYR8Fn18fX18fXx9fH19fHx8fXx9fHx8fXyFfQF8hn0FfH18fH2UfIZ9B3x9fX18fHyIfQF8iX0IfH19fXx9fXyGfQx8fXx9fX18fHx9fXyKfQF8h30BfKZ9AX6sfQICBACAk5GVjY+Fg4iKi6B+hIKBiIyIioqPj42RkYyblZWRlZSRkZKOjIiFgYSHiZCJkZSVl52alZGUlI6ZiIqHjYyUkYyOkpWanZWUl52mptmLjIiPwZaRlJaWkJuZkYaGjpiUlpiYjZORj4mPl5WQkpGUlZOMkIuTlYiHkZCNjaGVmpoOoLSbnqeXn5iWlpSXmpeElYCelpOQmZeRlJiUkZGSmpSakpOZm5OWmI+emYySmJyglJSOnZyQm5GQk46ZmJ62Y5ybm5igoZ6io6yjpaCXlZebmZeXl5agoKGbo6SUmpuZmKCcnKCalpiUpKCqnaSiqqipt7Cxqqyyo6qrraSnpZ2fn7+bo5yUnpqYmZablY5pkYCSk4yTkpeYlZeVm5mfoaKfmKCin5uhnZybmZiXnJaVlpmhpKSemJqctaKYlp+coJ6am5uipKSln6GaoZyfoaKUkJiNmZeUkJCPkZGSko2cnpCXlJmap2ONnJ6nnpuan6qjq5idl6Ocl5KUkpCNl5eckI6UiZubmJmZkpuUk5OipICwnpOXlJaWkpiTl5uZop6hm4mTlI2SmpeOlJyesJSKiZKUtJGXkZOMjoyMh4iTh4uPjouGjZ2dhI2PgomNiYuJg4eJj5SMiY+Lj5SNiYaTh4uRsZGQhIuSkY62oqeiqKWbo6Cfh5GWiJCXlZiVk46JjJmEhouFhYGJhol7f42GhICUj4yNi5eKg46YaJualZuZmpqXmpqSmJGSkZCWloyUjYZ7h4OKh4iOjYSZq7KQhoqHgomMjYiIiYmJqJGRhoGAfYN+fIiDiIuAf3t+fX5+eXh3hHx1bn6Jg3+FnY2DiImJkICriYCDgJCMgIF9fYKJgH+FfIZ/f4SEfHV6fI98gIB3eHd4c3d2dXB4fW13eo99cXNvdHBwa2xsbW9vbnBqcXp8e3x4cXZ1enx7fHx3eXV4cnJ3emtxdXF6dIOFent/g3l8iHh7eXh2cHhx1HJ3b3RvcXdxg3B1dXRw2mzQ3XDgeXhqeHRtcnJwjWxtdmxvdXDJa3NrcXV2fHLezHZ34oDf4nv17OHYz9TazuB15ODkded56OLnfvbsgIn36Hjq4+DMdOd3d3fh4OHhc27VbtlxcnR4fe3w+vX19PTy/up77Ozk6+nq6eTk3+fhdnZ4eOR0tYyM7JGQ8/Py9n99f/iBgvx7e3rlfoGWg3V98X6CfX3wfHnkeuPe0nR4cW10cm5yeXJ5d3p66pKHk4l+gYR/7IB/evB9fHV7fIF18HiCh4SFhIaNfn+BgHyDgIWDi5F8f3yIhXp9g4WGfoJ9gIKBfoN7hISKioSLh4OGZo2Jj46Eg4uFhomJjYiHiomupYB8fIWId32AhYKAg4WFhoCPj5OPj4mGjY+OoYOJhYSKjYaIi4uLioyIhpOQjYePi4+Qk5GQjo6Nj5GRlpCSlZOQk5CPi4+QjJiRjo6UkZWTjY6Vl5mVkZOZn6iq3ZOVkpTgl5GUmJaUnp+ZjY6SmpWWmZeOkpKVkJOamJeXlJeYlZSVlZ2dlJKWmZWSoZaVloCYp5GTm5SXk5OUkZaYlJOSlJSblJWUnZiWmJiWkpCPlpKXjJKWmJiZmZKfmo+WnZyek5SRo6eWnZabnJiZl6G/apmZmJidnpueoaeZm5mVkpadmpmcm5qdnZ2WnZ2OlZSVmJqanJ6clpmSnZuklZeUlpOTnpmXlJOZkpiaoJicnoCYmJzDlp+alJubmZyan52acp2fnZqgnJuWlZiYnZqfnp+ak5qempmgpKCjo6Ggo56bnJ2goZ2fnZ2es6abm5+bmpaSkZKVmJmcl5mWnZueo6SZmKCVoKKdnJ2cnJmYmZOgnpKUlZqYpmaIlJedmJSWnaylqZmenamknpaampeYnICbnpmTmZClqKOloZmim5qXp6SxmpGWlZeXl52anZyZoJ6jopSgo5ygoZuXnKSou6GYmKChw6CjnZ+ZnJualJejmpyio5yaobKxk6CekZedlZeWkJeXnaWcm5+doaOblpWck5WbtJyZkJaYm5W4o6aip6GZnJuaho6TjJOZl56cmICVj5ShkpOWlZOSmJaXi4uZkY+aloyRkJiSjJSdfJiXkpSTkZKQkJONkpGPjZKWl5GalIuCi4iNi42QkIWaq7STiYuEhIqLjYaIhomMqZOTjomHhYmHg42JjZKJiImKiY2QjIqNlo2FfYiRi4OHoYyDiIeKkYS9kISEgY+Kg4aEhYCHjIaDiIKIg4GFhoF7gYSUg4d9f3p9e39/fHiDi3+GiJ6QgoF/g3x9eX19fX5+e3t7fYSCgIB7dnh3eXd3enh4eHZ5eXl8hnV1d3Z4dn19c3R9fnZ3gnZ8fHt4c3p123J4cXVydXp1hnl8f3x46nnd5XLkeXlwdnZwdnp+rnV2g4B4d3x23nB5c3FzdX956Nd3ddzW33jx6eXc4+H05PB98e7we+163tjhdePdd3zl4XPi4d3LdOh3d3no5+jrd3fsd+Z0c3d3duTh6Obq5uzo8+V46enk6+7s6+jr5ezmdXR3eeV3z4mJ56Si6u3o7Ht2d+h2eeh2eHfien2PfnN25YB6f3p58np79YLy8fJ/hYN8iIh7h3yBeX184o6Bi352eHt55n1+evGDgnx/f4F67nd+g36AgYKJfoCDgYCGgYeGjZKBhYGMiYCCiIeJg4WChoOCfYJ+iIeKiYSKioSIa4uHjoyCgox/goSGi4aGhomhmoSIho+Qg4qJjoeFhoaFhYCdmZ2bl5GOkZeYp46TkJKcoZufnqKjoqWhnayjo5yioJ+cnpybmJeTkpaXoZqeoaGiqKeloqalnqufnZmemKCal5mem5+dmZeboKyu4pmclpvlop2gpKGfqqeglZSdqaOjp6WYnp2YlJmioJ6fnaOkoaChn6anm5mgop2bq5+fn4Cer5aXnJCWj4+QkJWdmZibnp6moZ6ao6GbnqGcmZiYoJqglpqgoZ6goJaloZGboaaroKSjtrqkrKGgop2in6rsi6ako56mo5+hprCkp6aioaWvpqaopqmrrayhqqiYnZ6enaOhp6iloqKcqKawn6SipqSlsayrqKetpaurr6anpICfn6PonKihnaWlo6elqqiogqqppKCjoaGYlpqbop+nqaamnqmuqaarraippaSdoqKkpqqpq6inpKKkxbWnqquprKimpKCorK+wra+osrKytLiqpa2erKmmoqKdpaGhppyur6GmoayrvoKZp622r6ups8S6vauwrLmzq6iusLGutoC2uayssqe5urW0s6uxqqmpu77LsqOrqayur7e1tLOvuba7tqOwtK+0ubOtsby7z7Kop7Gz1q21r7OqrquurK28r7G4urSutcjHp7SypKqzqKqpo6ypsbyxsLWxtru1rauzqa21zbSyprC0tLDfwsW8xLy0vLq6oq60qbC0tL26s4Cwq667o6irp6inr6quoKKzqqm4tKuurLGwqbLEubaxqauhoqKhoaahpqakpKevrqavqqGWoJujnp6lo5iuxsuon6Kdm6Olo5+in6OjwKippJiZlZiUmKCbnaSbnJqbnJ6inp2dqJyUiZiimpaevqacoqCgqZvQopeXkqKhlpmVlYCXn5iWmJCWjo+TlY6GkJOrl5uSk5GTjpOUkY6ZqpSho8aolpeSlIyMioyKjIyKhoqHio+Qjo+LhYiHiYmKjYqIiIaKiIiKlIGGh4aJhY+PhYWPkoiKmIiPjY2KhI2E+oOJgoaGiI6InImKiYaB+4L0/oD9i42CjIqCh4qM3ImMmICOkJWO/oOLg4eKiI6H/uyGg/bs8oL/8+fd2uDy4/eA9PP5gvmC7uHxgPn3hYz9+4D7+/Xggf6Eg4b9+/r/g4L/gv2CgYSEgvv3+fT4+Pz0//GA/Prv+vz7+fXw6/f0gYGChfyC/pWU/8+9+vv4/oSCgPmAgv+ChIL6hYmmjoCE/4CHi4WE/oGA+of49/ODioaDi4yFkYSNhomI/p2RnZCEhoeF+YeJg/uIh4CGh4uC/YGJj4qJi4iPgYSHhIOHhYmIkJSChYORkYaKk5SSjJGNjIyOhoyHlJGWlY+WkYyQiJeQl5WIiZaJiYyMkY6PkZC3rImKhpCThY6Rl5OOk5OUkv99wX0Bfs99AX7XfQF+/320fQF+/33CfQF8jn0GfH18fH18kX0BfIh9CHx8fX18fHx9iXwSfXx8fH18fXx8fH18fH19fHx9hHwFfXx9fX2EfAV9fXx9fIV9inwBfYx8hH0BfIR9A3x9fYR8C319fXx9fXx9fX18hn0BfIR9CHx9fXx9fHx8jX0BfIh9BXx9fX18h30BfK99AX6hfQICBACAjpGdlI6Ag32BmpCBg4mOhoiOj4eRi46Mj5KSkJqXmJycm5qTioiBjod+goOMlZCVkpeZppqKlpSSmpSLj5COkoyZkpaUm5SWnpyTjJO6oZGZnp6Xlo2Iu4+cmZuZmZyYmJ+mmZONi5mZjomHm4yXkpWVkIqNkIWUu5aamb+Wn6OAqKKpr6uco6akp6Cgo6CgnJqkpp+dm5mWmJqhk5CTkpqcoJ6jkaaZoKKgn6WemqOcmZuWm6W6o6KYkpGLlpeclaOWqJeYoaamqaimo6SioaSfnZufm5qYmJCfrqCin5qbmJuho6Sim5ybmaCjm6Weoqavq6ukqpqnq6atpaanoqGAs6GclJqbmJScmpudnpqVkpKZlZKamKCVlpuanZ+htKCjpZykpJ2co6CioqGdnpaWl5iXmZ6TnJabmpabn52amZyhlp2em5udpJyckYqXnpOWkY2LkpSWoo6Tm52XnZ+ZlqGnnp6akpyHk4uQmKCYl5ublqGem5aknJyenZ2UmpSAnJCRlpaUmJm5iY6Vl5OanJ6tn5+jn6GXo5+an5mbmKWtpaCgmJaZk5SbjoqXlJaWpJufoJqZmpqblJWTi4+Kk5CQk5mSiJSTl5aOmpWRqpmNipeNjo6Ig4uGiI6Tro+Gh4qLiYiMhoyQlYeNkZaWo6aXmp2clZmOlpOOk5O0kZGAio+RjZGGjI2QjIqEgIOAhYSGhoOJjoeLi46Rjo6PkouPjo6NlZaTj5WRkpeTkpObmY6ThYeEhH6Hgn5+h4KJfX19hn+Nh4qMio6RiY2OmoqBfYKMgnx/eXZ/f3V8hXx7e3x/eYuBd3R7fYZ8f5OKf4F8mI57hIN/fHt9f4GDgICAgJmIhImEh4F6eoB3gIOFhYaAhoGMioSDgnuDenpyd3p2eXJ3eXV4c3N0cXNzdnd9eGt3c5p6c3Z+e3l5fnp/cnB0b3F3dchtcnd3cXB7e3Z3dol7enh3dXV7f392foNxe356dHN9jH11gHbY4d1vb5C0c3l1bdBvx3RrZ21laWqAaHBvcnHacd5udn53cnh+gIF96/CCoPHsl+js6IyE6IHw6H/46+fkfOF2eXTidm9vcnHigHTc3uDh4HLgcHh3ctJw025vcHZ9e36B9X6BgfSNg+t4fH944+Ph4t5yc310d+zmfHmNin6AhOjqfYh88H35f/Hx8fD6/YSIhIF/gX+A8vB8eXmAe3Z0dnl3fHZ4fHl5f3J9hIB+e3p6eX1/dnuNg3p5gX2B3ep6gPqBg4eLio+HjYmSiYaCg4KBfXV7d3h7f4J6g4GIhYaDgoSIiImGfH6CiIWFgYKJi4mMk4uVjI6KhoCOi4+KjoeHiIOHg4OBd3mGhIiEhoWFjI2RiYyAjpGdlI+FiIaIn5KFh4yOiIeMjYmSjpCMjZGPjpCPkZGSk5KRjI+NnZaPkpGanJiVj5GUoZeLl5WWo56VmZmXmJWakJWVmpOYoZ+SjJK2nI+Znp2VmI+Ls4+alZaWk5eVlJmfk46Mi56ckY2Mo5SfnJubmpWUmJCdyZ2gmbCUmpyAnJaanpuRlZiVm5aWmZiZlZeen5mTl5aRk5adlpOUkpmaoJmejZ2UmpqWlJmTlZ6XlJeUm6XBm6CbmpuXoJ2hnKmXqJaXmJubnJ+enJqXnJ+bnJmZmZuYlY+eq52gmJeXlZyfn6CgnZ6fm5yim6CXm5iem5qTmYqVm5edmp2em56Arp2cmJ6cmZacnJ6doJ6dnJ2jn6CnpKqcnJ+an56gs5udoJqgnpmXoZ2gpKago5yinaCgoKScpJ2hoZ6jpqmjoKOmnJ+hn5ucpp+clZWgqKCjm5mbnZydp5eXn5+XnZuWlpyimpqXlJmMlZGXmqKZmJ6cmaCenpqlnp2fn6Gcop6Ao52fpKGjoqG8k5acnpyhoKKmmpmfnJuVnJyZnJyemqmzpaKjm5ifm5+imJefn6KhqqWopqWgoaCfnZ6dl5uZn5yfo6egm6KhpKSbpKGbuayYlqCamp2ZlJ2WmJ6lxJ6VlpeZmZiak5ueoZeYnJ2gqKeam5ybl5qQmJePlZbDm5Yrk5iYmZyTlZmanJuXk5STmJaWlJGSlY6SkZCTkY+SkI6MkI+OkpSRjpKQkISRgJqXkZaMj4yNi5aSkI+XlZqOjYuOiJSNi4uGiZCKj4yZkIaHi5SLhoyGho6OhYuTjoqMiYuJlYyIhomMlImOs5iJi4ihloSLioaEgoGDhISDgoGShYGHg4aCf3+Ce4KDhIWEfYB8hoSAgIF5gX5+e4CAen16foJ/f3x6f35/gIGBgImHeIJ8nH56f4F/fniAfoZ6d3l0eH993nF1ent2dHh4cXR2gXZ2dHRzd3Z3enJ3fWt3eXVxb3uHfnqGfuvp7HVzj65wdHVv23PXgHh1eHF3dXZ5dHZ05nfwdXl9eXV4eXl1ctfgfa3q6Inr7/OLheh/7ON10+bg3XnfdXp34nd0gHN0dOWCdubh3+Ljded0enp56Xrne3p5eH93eXrndnl56YuD6Xd8f3Xm6+jp53Z4f3Z47OV6eIZzfHyA5ON7fnjuee956Ojp7OzqeXl3dHR1dense3p5gX5/fn1+gYJ+hIR9fYF2fYR+enp3eHl7e3h/jYeEg5WLifT8gH3zenx8UX16fnh8eoB8en19gISDgoeDgoWIin+Fgo6IiISDhISFiYeAhYaKjY+Lh4yLiIiOh5GJiYiFgIyHioiNh4mKhoyJioiDgo2IjIiJhoSJiIyGioCWmKOcloqMhY2mmouOkpiSkZuclqCbnZueoZ6an52fpKGdnZuTk5CblY2TlaKmoKOepKi2rJuppaavqZ6dnZufmqWZnZqflp2npJWRmLqllqCpqJ6ekY6xmqWjo5+hpaKiqa+fmpWVpKSalZWrm6mio6SgmZiak6HPoKKdtZmhpICknaOno5qen52gmZuhnZ+cn6Woo56dop6fo6qemp6coaSqpquZqpykpaKgp6CfrqekpqSqtdKus6qprKCqpaqtuJ+vnqClp6Wmp6qopaCnrKmsqamoqaikm6WtpKWhnZ2coKelqqilqKWipKqiqaKio6unqaKmmqGno6ehpaKfoYC2oZ6Zn5+bmKKip6isrKioqa6oo6mkrpuXnZ2go6O5o6itp7Gvqaavqa2vsaukoqalqaenqqKroqOipauxtq6usLausq+ur6+2srOopbS5rLGppaSmpKeyn6KqrKWrsKeosrmxsq6qsqSuqq+yurCus7CosrCuqrqxr7GztrC4s4C6sbO8t7e3ts6lqa6tra+ws72vrbOwr6awqaeqrK2svMe2tLmzsbessLOopbSytbPEur++urW2tLezsrKqs663tLW7xLquuba5tq63s6zTwqmlsqusrqynsamssrzesqekqauqqaulq7O3qa+yurzHyrm8vrmytKiysqqxs+m2r4CprbCxuKyutLW2s6+rra6yra2tqa+2r7SzsbKupqWjnp+fnpyip6ajqaeora6trbmzqrGko5+hm6ejoJ2nqbOen5qkmqujoKOhoqqjp6KyqJuanKifnqGYlp+jmJ+noJydm56Xqp6XlJmcpJaZy6uYmpa3rZefnpyZlZeXmZqXlYCTqZuUmZCTjoiHj4aQlJWVmJCWk6GdlpmZj5eRlIyUl5CUkZWYkpCLjJCNkY6Qkp6ZhpCKppCIjJCOjIqQjJSHhIeDh46M+YGGj5CHh42Mh4iJmYmHhYWEiYuLj4WOlYGOj4uHhZGgkYmQivn/+oCBxP2EiomC+ob4lYuIjoSJhYCBiISHgfyB/oCLkYuEiIyLhoDs8Yi8+/Wn8vb1kIjwgfXxgPD98vGF9oKIhPqFgIGCg/6Tgvz6+f36gf6BhoiE+4P4hIGBgImCgIT7gIOD+JKO+4CHiID3+vn8+oCDjIOC//uGgZKBhoiM/PqEi4H+gf6A9Or1+f79hIeEgYCAgID8/oODg4uIh4iGioqMg4uPiYeNgo2UjomIhIaFiIiDh5iNg4OdioTj8Hx+835/goWFiIKIhoqEgoCBgoN9eX57fn+DiIOMjJmTko6Lio2RkpGGi4qNjo+KiI+QkI+XkJuRkY6Ig5KNkY6UjZCQjJKQkY+JjZmWmpSYlJGUlpiSlv99/33/ff99/33LfQF8pH2DfIh9A3x9fIx9A3x9fIp9EXx8fX18fH18fHx9fXx9fHx9hHwGfXx9fX18hX0DfH19hXwCfXyEfQN8fXyIfQh8fX19fH19fIR9hXyFfRJ8fH19fX59fX18fH19fXx9fH2GfId9gnylfQV8fH19fNR9AgIEAICSlJKNjYyViYaLjoSOlZCTjI+TlZWbkoySmKSgoJumlZaYoKOjjY+Ji4+MipScjZmVl56cnJuVlpSQloiOkZWOjpqYmpd6mZWYoK2rnJuhnpKNlZqdmZPpmJOclpKXk5aYmJaWmJWWlJiOlpmelpaRmJeUkZGaj5ORjqebkJmXn4CVmZmfoJufpK6anJmen5aUm5uXnpmZl5mUlqGgnKGfmpyfqKGip6ScnaWnnpeprKahnp6hmp6ZlpiYnZmdlpmdpamcop6fnp2hpue6oJ2doqSZlZmgpqOooKSrrayuqKOlnaukqqeerqCdnqGor6Kdo6WinJ+pmayopaamqqmpooCklp2bm6Kfn6CdlpWUmZqdw5yjmJ+XsaOamZmbn7KrpqaooZ6ev6WlpJ6gop2dmpucnJWfqpqUlpadl5OXmpOZkZSunKOfn6Kjo56mnpmXmJqVl5KWmaKbnaCcmJ6ap5aVlZiXsKCUmJWbnpmcnZWclZSgn5SUmIyPpaSWl5Scl4CbjY2UsamPipKZlZeVoJ6ctaOYlpefnaGlnpapop2hnZ6Xo6menZabmJiVmZadoKWcn5ugp5uYkZyXlpKLkJCXl4iPiomKlZOSiYyQlpeKj5CPjo6ZwbyHr5yIiZWvjoeVkJOUj46IjJCMkJCOkJKVlYqXkZSOmJKTkI+Mp5qbnGmUkZaSkIyRiYaNi4eIiIOJjIKGi4yHjY+OjI6uiYuHkJCQiZSUlZaakpCNmY2UkI2Pio6CfIKAgIJ8g4F4fn+AgX2BgIGIhIOLiImIjY+Nqo6KjYqDjJaMiIqIgoeDh4SDioB9f4N7h4eEeoB+f356gXx0fYR7d32Cd3yFi4eGkoiMkZeDgoSHgIGDg39/h4GBgHl3h3t1dHd4c3JydHd4hJ1/enp8eHx2dXF1d315cnd5c3N0dnBxbnJ2g3RweGttcHBzc29ydXh2iHx2gHxxdoBye4B7eHx4fHd9fXmAf4d4eneEbnl12GtzeIB3dXF6h3l+f3h1dXV+lG9uc87S2dZwbW7Rbm1wdX/2+HuEhO/x6Hnf5uv25/rs6Prp9vXz7nnkdn5/dHl3dHd2fXd2cnFxdXJydHd028rUcXJvctdydOTj2+N2h33sfoCDgO+E4Hh2f+nn3HDS2eB5eX+Ce3p7eXXYfHV2d3t48ICD9vJ+hIuDhIP3+YH6+H3r63l9fn6EiHl9eXp6e3V3d3x7gX7pe31/fIGLhYSAh4X2gHt8fnuA7oSH9318+4aBg4p+9H+EfoWFgoXwhYKFgX95g++Ce4mAgH99fX+GiIGBhoh+fn1/hoODjYSKn5SFjY2Mi5aHg4aHiouKjYWBfRN6iYh6f3d5f3q6g4KFho2KkJCPgI6RkImNi5aLio+PiJGVkI+Ni4+SjpWOi46UmpuZk6GOkJKWnZ+QkYySlZSTm5+RlJSTl5WUlo+UlJadk5ibnpeVnpmcl5WUkJKYoaCTlZubk4yTmp6blt6bl6GcmJaUlpeWk5KUk5aYnJKXnKCYmpaZmJaVlZ+Wm5mYtKSXoJyigJeYl5mYlZebp5WYlpydlpWdnZqbnJqZmpWWmZmYmZaRlpqhm5uem5WXmp6Wj6Ojn5eXmJuZm5eXmpidmJ6coJ+npp2loJ2WmZqb2bCYmJigo5yXl52hnJ+Wlp2cmpmXkpWRm5mfnpymnp2coKivoZ2goJuXlp6PnJyamZufn6KbgJ+Rmp2dpaOkpKSinp2foqDCnaOYnpmyopubmp2eqKKbn5+cm5rQnqCfm5ydm5yanJyclqKympyhn6ikoaWooqaenbKeo52eoaSknKWgnpqdopygm5+eqJ6en5uZm5ihlpaXoJq2qpmZm56emZ6fmZ6emqulnZ6im5eppp6bnKGegKGZlp28spyYn6Kdn5+oo6C3ppuam52anJ6claael5+cn52nr6Ohn6Glnp2hnqChopuenJ+lnp2cpaKhoZqgoamrm6KcmqCoo6CXnJ2jopaYmpqYmqXJxJe8qJeZoq6clpyZn6GenpeanZ2hnJeYmJuakJ2YnZqhmZ2Xl5OqnJiYQZSVmpicl5mYl5ubmJKTkZqYkpOWlpKTmJqZm9SWlpOXlpSOl5SUlJaRj46XkZmXlpWWl5GMlJCVlZGXlI6WkpWThJCAko2Jj4uNjJKQkMOUlpmRjZSgk42Sj4yTi5CNjpSJiIaMiJGSiIiLi5COjYiOhoGJjIaDiIqBfYaJhYSQiIuNlYKEhId/fIF+fHyEhIODfX2Kg4GDgYB/foF/e3eAnH17enx5ent7d3t9gX59gYN/fXuAfHh7fHyNfnuDd3Z2eHyAenR1dnh4hXlzeHZvdnl1dnp1dnpwdHF2d3R5eHxydHKAcX165nR5eHN0cnaBd3qBeHd2eIadeHZ629/r7HdzceFydHJxdOLidXt64+bleuHm7O7m7+Xj7uTt8uvqdd10e3hwdXJxdHR3dnVxcnJ1dXd4e3rr3Ox8fXp8835/+vKA6up6iHzkdnh5eOh+33Z1fenq5nXh5u57eYGAeHh6eHbfe3d6e3t26n7n6Xd8fnV2c9/iduzvfO3senl6fX6DeXd7gIB9fH1+goGGf/J8fXt4e4B9eHR7e+l6d3p+gYX6iYj9gID2gX+Chn7wfIB9g4F/hPqGg4WDhH6J+YmFjoc7h4WEgYOFhoGCiIuJiIiJi4WDjISKrZiGjYyKiI2EhIWHioiLjYmHioiVloqSiouRiLWNioiIjIWJioqAl5mYkJOSmo+NlJiQm5+cnpuboKOgpJ6YnaOsp6agtZubmJ+lppSXkJGVlZKanpOWlZmgn5uemJycmp+UmaChnpypn5+dh5uXl52pqZyepqealqCorKah06WfqKOcnp2ho6SfnKGgpaOnmKCmqaKlnqOhn52bppufoJ28rJ2opauAnqCho6GgoqWynKGepaabnKWjoKOknqGinJ2lpaSopaClqrGoqqqnn6SnqaGara6spKapsK6xrKmsqKejqqepqa6uoKmjoZydoqPmuaKgoKqtpJ2eoqumqqGfpqqnqZ+coJuooamno7CloZ2jq7CkoaWopKKjr56traimp62qqqCAopOcoJ6npamrrKikoaaqqdKnrqKqosCnnJmYmZusqaWtrqusquWvsa2nqaqlpqSoq6yissKnpKinrauorLCrsaWjr6OwqqyvrrGss66qp6muqa2mqqq1rK6zrKmvrbyqqqmvrMq7qqytsbayt7myubOxyL2ysbivq8XAtba0vLWAuquss9LIraOttbGvrLWzscy3rairrKussKqfs6yiqaaoqra/tbSxs7eyr7a1u7y/uLy4u8K0tLK9t7S0qrOzu8Gqs62ssLu1saivsbi4r62xsa+xuNTMqtK+q6u4zrGqtLCytLGxrK6zsbW0r7Oyubmtvri9uMC2u7azrcS7ubiAsrC0ra2qrq6strOyrLCstbersLOyrKywsbCw5qysrLGyraWwrK2ws62oqLOst7CwsK6vp56ioKKhnKShk5+cnaKbo6Gjp6Cep5+ko6eoqPGrrq6noau2pZ6hoJygmJ2anKOZkpKXk56flpSZm6CfnpadlY+XnJeRmJ2TlJ6ln5uAp5udorCTlJWakI+UkZCQmpuYmZOSpJeSkZCSj5CVlJGQmsWWkJGRjJCPjYyLjpOPi5CQioiJjIWDg4WJmYmEkIKFh4mOi4mKjo+On4+Gj4yAiJGIiY+KiZCGiIWNj4yTk5mOjoqZgo+I/ICHioaGgIiUh42Ti4yMjJm6iYiM+fyA/fiCgoH8gIKAgIX8/IGKjPn894Lu9Pj47fjo6PXu+fv3/IDzgoiKgIOCgoaDi4mGg4SDg4OCgoOD9+j2goODgv2DhP338fSAjoP1gYSHhfuJ+oKDjfz494H29/2Dg4aKhYOFhIL0h4OHiIiC/4n9+IWIjYCAfPHxfvj2gPD1gICAgYOCjIGAgoWEg4GChIeIiof8g4eHhYiSj4uEi4z+hoKBg4GG/YmJ+4CA/oaEh4yF/IKHgoeGg4b4iYSGhoSAjf6Jgo+HhoiEhYaKjIeEiYuFhoaGi4iHkIiOvJ+Lk5SSkZeLiImLj4yQkYyKjYuamouUjo6WjceWlJOTmJCWl5fFfQF+/33/ff99/33/fah9AXyUfYR8BH19fXyFfQl8fH19fXx8fH2OfAJ9fJV9g3yEfQN8fX2EfAR9fX18hH0NfH18fX19fHx8fXx8fIl9AXyGfQR8fXx8hn0IfHx9fHx9fHyTfQF8i30BfIZ9B3x9fXx9fXyFfQF8h30BfId9AXy/fQICBACAk6KOkouYlpSMk5GdlI6SkpOZnJKUlpSRlIqIjoyOjpWNlJKVmJaVlJiPk5uTkpSXipacoJ6jqp2UlpOalZWVk5KplpaQsJuTqKOdl5icmZyamJOcnaCZlo+KnZ+Zmp2enZaenJqdlZ2cpKOfqqOloK2jnZqam5uYl5SNj5WalpQ2kpGkoqGfpKiqp6CfnpygmJyemKSfqeGWmpqdmZmWmZGcnqCboKCgnZ+gp6inq62kqp6coJqchJiAkZOXnJ6coqaqraalq6OioJqkoZ2loq6fnqGopqeioqqpsq+xqrWwsLOjrsWnqKunmZ+kpKelnKSkpqCyoKuhoqejoqGdqKakpqTVsa+vmKGcnpmfoKGdnqmXpJfNsaihop6eqaCvoqWsprTOraSooKGiqaChpqSjqKKnoJ6ZmpeAnJmep6OfpKCaoLSupJ+po4ylt6KlnpeWmJyak5KbnZyenJ2ioJqfnJuYmJebnZWcoKCesaShoqignpeemZ+bnaCUkJWQmJmWmJeelpObm5SZlpudo5yempqZnaGeoJ2inaitpKeanqmpoZSRnZ2hnZuYqZ+lnJqcopmZl5eZmJSAk5COjpWcuJibl5imo5CPj42KjpqSl5eTmZ6ku5mSmqmemaOjkI2PkZGRlo6Nl5qQjZKPkJWOj46OkJORlpCOkY2Ji5WaiZOfmJSXmI+cioaNiYyIgm+CjY2DiYCDiJKMkJeMkpKckYqMkIqKi4iMiIaGhIeFiI6NhYGGhH2FiIGAhYSIfHZ3enp8gYKGioWKjZqOkZaTkI2Hf4aBhpCKhZmMf4GGhIV/foCFi4CDhYl9fX12hIN+fHx9fn1/fnaGhH6GhIaFgIR/fIiBg4eChoOAf3qEfHp7eHSEgnZ+eId2d3R3c29zdH6AfXqBhnN6dnd2dnR0d3R0cnx9dL15dHOAc96keXJ5jHDPdHt1b3rrd3ODgn6BeHN2c3N4eXR0fHKGf32CfoXxeHZ/nYd+b3NxdXR7fO/u6Ht+fH13c3BvdnVzdn5wbGxwa3N02t/e2Nzr4OTg9fL19Hp0d3h80nfW5enu6Hmt6nV3eeJ5d97nfOR2c317enl24el0eHjn5OKA2Ht6cnNwbXNxbXNz3Ht/fPPofHp5fnt/e3ri4uDlenZ5eHZz5Hl0enp7e3jte3+Cg4N/hIyCh4aMgoiC8vf69vyBipKF/4CEkY6BfX/we+jj73x6fn7x6n6AgoN+f317foWBeYJ76316fHx7hYKEiIGQt+qOiYyTiHyHgIV/h4dGiIiLj4iVjYmGf4qGf3p4g4mFi4eEhYuGfYONe4GEg4GEjYatgo2rjJKJiI2PioqCgoB/fnyCgH95g46QlpSUlI2SjY+QkYCPmYuOipSVlI2TkJqTjJKPkpCUj4+Tko+UkIyTkZGSlY+UkJGXlJKSm5SYoJmXmJeOlZaYlpmdk5KSkJiWlZeYlsCYmI3RlIufnJmXl5qVmJiXlJ2gpJyblpOhn5iVlZeYk5ydmpyXnJqfnZaemZqWn5qWlZicnZycnZaYn6KdmoCWlKSfmpmdoKGenJiYmZ2ZmZyYnJqjzZOWmJmYmZaalJ2en5qenpqYmJecnZqfoJqglpmenJ6YmZyel5ucn5+Znp+hoKCcop2enJmjnpygnKaYlZuenp6amp6coZydmKKcmpyQn7qfnqShlp+joaGelpiZnJOjkpyVm56cnZyYpYCgm5ucz6inppSfnaOepqippKSvnKWfy6yknpuanaehq6KdoZqi0aKdopqcm6GanZ6em6CZn52dnZ+fop+gpqKboZuUnLClm5agnYueuaWmo6KhpaqnoKCmpJ+jn52goJygoJ+anJqdnpiYnJuZpp6cnaSenJihn6Wip6aemaCXnoCdoKGfp5+ZnqCYnJefpKqlpKSgoaGjoaCfopymqKKimZ+jp6GZmaChn5yZnKyfp52bn6aeoJ+goqOioZubmqCn2qOopKW8uqGgpaOen6yhpaOdoKSnu5yXnq6mpK6to6Kho6OhqaGbqKqjoKGbmaCZmZmam5qcoJman5qYmaGhkYCYop2VmJqVsp2SmJibm5aElqCblJyUlpqim52hmZeYnpWNk5aSlJiWm5mVlZORkJOYl42Lko+OlZiWmpuhlo+QkpGNk5GSkoqKjZiPj5GRkY6NhoiHjZqUkKOViI2NkZGMiY2SkIaMi46JiYqFk5KLiomKjo2OjYOLi4aJiIqJiICMh4WOhYSHgoOBfoGBi4aCg4F+iIOAh4mdiIOFhYB8e3t+gn96gIlyeXh9fXx/fYB+gYGJh37KhHh8eOSvgHiCl3fZe355cHnyc3F9fnx8dnZ7dnh8end3eG17d3V4dX3ic3J4lH97b3R2e3l5duLf3XJ4eXx1cXByeHd1eYBycYByeHN3d+Lp6uPg6+Pl4/Ds6+l3c3h2fdiA4vH1+PB8we56eXnhfHPi6XnjdnZ6eXh3dNfkcHV24efi2319dXl4d315d3p55X1+euvdd3V1enZ7ennm6uPpfHh5eXh07Hp0eHR1dnPidXp6d3hzdnlxdXl9dnx55u/z8/R7gYl98IB5gYWDenh+93307/yAgYOD9PF+foGBe3x5dXeAfnR+eud8fH2AfYSCgYZ9iKzkgnyBin97h32BfYKDg4GDiIONhYSCfIeGgX98ho6Ij4mFh42HgomRgIiMiomFiYalhZKwjo+LiI2Vjo6Kjo6PjouQj4yHiY+NjIyIjIeLhIaJjICfs5iflqOioJifnaigl56doKOnnp+jo5+lnZignZ2hpZqdmpqam5eTm5WYopmXmZePl5qamKCjmZaWl5+foKWjncygopjGnJOspqKeoaeipqOjoKeqqaCbloiio52coKWmoailoqOdpKSmpaKsp6mkrqWfnZugnp2fm5aaoKWioYCblqmln6ChoqainZ2em6CboKCdpKCn7pqcn5+eoZ2inKeoq6Wrqqimp6OpqKSpqqaqn6OrqauoqK2vqKmnq6ulrq6vq6ilqKGhn5ymo5ykoaudmp6jpqikpauqs66wqbSvrrGgsMusra+rm6SpqaqlnKanp6O4pLCorK6sq6airICmoaGi3rCwsZ+sq6+rtLayr665oKyu4K+inJeYm6igrKGiqqe25ravtq2pqa6mqK2uqa2qsK2opaWmqKiosbCnrKWfqMO0qqSvrZSwyLG0rqekqayooqCqqqutrK2yta+2s7CtsK2ys6yttLSzxLe1tb21sqqxrbSyt7qwr7m1vYC9vL68w7qxt7etsayxtby2t7Wzs7W2tbCprKOsrqiupquys6ylqK+xtLCtr8W3wLe3vcO6ura3tbazsa2tr7bA87zAvb3RzrOvtLKssb+1vLuztru91bCstMW9uMXFtrKzt7W0u7Ouu760sLizsrmxs7W3uru7w7u6v7SwrLi8qoCzvbWvsrWtxrClr7G1tKiSq7i3qbGprLK7t7rFt7e2vbGnrq+kp6qnrqulpaeopKmvraCdpJ+aoqSfp6euop2eoaGgpaSnpp2ho7SnqK+wsq2roaWkrLatpLmrl5ycoaCdnZ6go5aamJ6UkpOMnZ+bmZqdpKKioJWfn5edmp2amoCempiimJiclZmVkpORn5mYmZePm36NkI+di4eLjomFiIeOlJOKlp2EjYyOkZCNjpCLj4qSkoftjICCgfe8iYKNm4bxiY+HgozRhYOTkY2Ph4KIgYOJi4iIkIOVjoqPiJL/gYKIp5WRgIaFiIODhP/+/oeMjI6JhoOGjoyHjpaFhoCFiICGh/z8+/Pu+/Dw7//8+v6DgYKAh+GC5fb5//+Cv/yDhIX4h4H3+oX6g4CKioeIhvf/gIKC9/z274qKg4iGhIuJg4aE84iGhP/tgoGDhoaMh4f8//X6hIGFhoSC/4WBhoODgYD6hIiNiIqFio1/hoSFfIJ87fj7+fd9g4+D+4CAio+Ng4GE/4Py7v+CgYWD+/KDgYeJg4aFgoaPj4WLiP6GhYeIgouKiJGHl779ko2SnI2EjoSIg4qKiImLjomWjYqKhY+OiYWDjZSKkIqIjJKOhIuTg4iKioqJkI2vjJjJk5aQjJGakZCHiomJiYiNjYyKjJWWmZeWnJSblZebnv99/32XfQF+tH0Bfv99/33LfQF+pn0BfIZ9AXydfQF8jX2DfJR9jXyFfQJ8fYV8DX19fH19fXx9fXx8fXyHfQV8fH19fYR8i30GfH19fXx8iH2EfIZ9AXyHfQF8j32FfIR9AXyHfQV8fXx8fIR9gnyOfQF8330CAgQAgJGUkJaTkpOll6egj5OliYucjZGNkp6PipGSlpSXlI6SkpKRk5WUk5GYlaCnnp+gj5CSoZiTqqiZi5Wam5OVjouPj5OYm5mUk6OpmIudlZycmJGRlpyXmpuckZWqkZGSlZygmpmin6O8qKqioKSpoZqampOTmI6SmI2YmpOOl5iWgJWanZOimZ6cnpuUmZWTlpahnpyemqTVoKamra6inZ6jnpqbmpmbl5aXnqChoKGjpKSfqaWlpZmfopmhnpybm6mlo6Sam5ypm56cm6GhraukqrSkpKit0Kmlp6ettKuzuriwqq20sK6o4sKtpqekpqiuqqmpraOrqq2qsaaopZaZgKCjoZ+qwq6pnaGmoKKknqGjn6GfnaGjpKqspZKcnqGjrIOao6Gir5qsrK6jqKOqqZyeoaCrpaegqa2rrKimoJ+gnKKsrqmloq6qyKCdoqOel5aZmpyYkKOYrKadmZminp6fn52ZmpagmqKkpJ2mqaLJoaGlnqCYqailnY+TmZySRJCVl5mcjJaWkpOalp+mpJ+anqOYlJeYmJ6cmpilnqaamqihn56bmZeYlpignJWalpmbmZSRi4eUkIqQi4+Rk5aXk5SThI+AlpOPlpWQlJ6eoqKjoJ6hoJ+VoJuYlZ6TlJyWl5WZhIqkjo+Mio2UkZGTnZ6ZmJaQjpaQkY6NjouTi6Bhk5KHiouKi4eEjHyJiIWBiIuIf42IjYyKjpWXj4yKiIyNi4qKioSIh4OIjJSJjIeIhoqFhoCBhoiCfXd3hICCiYKEg4mAkYaJj4eRi4yGhYKLi4qOrIqQhn+ah4uLgoOHiIh3e36Fh3qCgI2/gHx+hIeCgouNg4iCeXh+f4CHg4GOh5SQfnp5hnN2bHV+d3V+eH94dXh4eHlxb3J2fH6Aj4J3hIVxc3Z/c2xxdHV7eXF2eIh2e3Zxd3p2dXp0bnducHBtb26AdnR/f4aDf372enp5dXdzeHZ4eXR3gHuHgHp6gXh8dON1eYCFeId7eHl3eHh3dnZ3cnduam9pcGZpZ2psbHRv5Obs7+t99vJ+5IOAeXp5fubb13Ny0dra093U3d/b2t/W3o7jeXV15XV7dep6dnz26vDxjH54enRvenl7fXZ1d3mAfHx974V/e/F/fn+HfHt9e33hdHZ4f4iBf4GAeoB7fnyCg4aIi4KG74CBgISCfv3v+YF/hIKLfYH1g3/3fn+EfHrr5nnnenuBfYB/hoeOhI+JgIF/ffGDgYR/gHt7gX76gYCAko+KjY2LioyEgIKF9oCMnZqPj4eRjI6D9oKIhoM4hoeMjoiGhoWBf3h6fX6EhYCGiomHiIObmJSQh42OjI+JhoWFiH9+goSGhoqKkYySj5GMiY+Sk5GAjpKOkpGOkqSZpp+QmayOkKqPlZaVo5GOlJWYmJmVlJSUlZOUlpWUj5WUnq6fnp6QkJOamJOmppmRmJqclZSUlZOOkJWWmJKPn6WVjZiRmZuZlJeanZ2fnp6TlqiWlpeXnZuYlZ2YnrOkoZyZm56bl5ialZmemp2lmqKmoJygoZ1xmZ2glaGZnpyfnZmamZiZmqSbmJmVnc+Zm5yhpZyXmp+hnJ2dm52Zl5aWm52dm52cmpifmp2elp2hmp+dnp2cp6SkppycnKWXmJeTmpqiop6irZ6cnJ+1npqbmp6jmaCppqGbnqWio5/ivKOdoJqbnaGEmYCTmZmenKOan56Vmp+enp+qwqqkmaKnpaGioKGkoqCbm6GhoqmtqZijoqanrIWboaCep6+foKGboJ+ko5mdoJyim5yXnp6doJ2gm5qcm5+kpqCdmaal0J2epqWnpaKlqaumnq6kr62imp6ioaCioJ+bmpidm5+eoJmfoJy+mpqfmYCgmqaoqaKboaipoqCko6SpmKKloJ2enKKlo56aoa6in6Kkn6WjpJ+qo6ifn6mknpybm5+enqKopJ6nop+ioqGen5uno56jnaGipaSjoaOkoKCeoqqnoqeinJ+nqaukpaWfoaSjmqWinp2rn6Cppqenq5ietKKgnpycoJ6bm6ChnYCbmZeZoZubmZiVkpmVpmKdnZOYmpmemJaekZ2cl5SanpqUnZiamJeZnZ6am5iYmpmbmp2bk5eQjpKUnZGVjpGSk5CSjpCXl5SRio2TkZCXj42Jj5GJio6GjI6Qi42LkpSUm8WQloyInI6RkIqMkJCQh4iJj5WEiouZ146LiY6QiYCFiYyHkYqFhIuJhoyGho+GlZOIhIiSf4R7goqGgoqGioSCh4aJh4KAhISFhICMgHWAhHJ2fIR9eH9/gYWDf4GBjHt9d3h7e3p7g4F8f3p5end0dXd1fHyBfXp58Xh7eXd4dXl1eHNwcnh0gnt2eYN3fnjwenl5fXaDe3Z1d3d0dYB2dnZ0eXJweXR4b3N0dXR0fHzs7ebl3Xbl4HPOdXdydHV85+LfeXvh7fLn8unw6ufo5+Loj+p7eHnmd3p05HZzeOnc5+mJfHh7dXZ+fn+Ae3p5e317eux+d3TgdXZ4gXt9fnuA6XZ3dnl/eXp/gXd9dHZ1dnV4fIF5euB4enmAfIB48OrxfHt7fIB3eul8ee98fYiBgPz5gPOAfoN8fX2Agol/iYN8fXx78YGBhoGCfn+DgPx+fXuHhH+Afn+Bgnx6fYHwf4aRjYiIgoiEhoH1gYOCfoGEhoyHh4qJioqHiYeDhIR+iIuKiIiCnZiTjoqNjY6TjYiKjJCMi42KiYmQjguRi5CLjoiDiouOjYCcopqhnJmZraCwqZWcqZGTqpaYmZyllZWamJ6fn5ubnJqbmZubnZqTmpejq6GhoZGXlaOclKmmm5CdoJ+enqKkpqKoqaejnZmrsZ+Top2kpKKXm56knp+jpZuitJ2dnqCnqaShqqasxbO0raqrr6ulpKGcnqGZnaWYpKeelKGkoICeoqacqaGlpaOinaOfnqCjsKeko56m6qOjpKirpKCiqqump6elpqOhn6KlqamprKmmoaykqKmgqq+mrqiopaStqqmqnqGirZycm5acnKOkpKazpqGhqcyjnp6co6qfp7KxrKiqs6+xrebFsausqqutsq2rrK+lrKywq7Cpq6iZnoCjoqGhq8ewqp+nrq2wsK2wsrGtqKSrqKiurqeQm5ueoq6lo6mqqLTwr7K2qrWyuLaqq7GrtK6tprCwrrCrrqimq6evt7y2sa6+u+Cus7e2taimra6uqqC1qMG7r6uutbK0tLCxraqpsKuytLavt7qz3LO2urK4scDDw7ivsr2+toCyurq6wK63tbKytbG1urmzrbS/s6+0tLC1sK+osqmwpKWysa+xra6xsrO3wLu0v7y7vru4sbGsubOts66zuLm8vrq8vbi1tLXAu7i+ubW5w8bGwMDAtba6tqy5tq+vv7K1wr67vMOpsMyzs7GurbSxsbLAwbq3uLa3vri1s7CtqICuq8CJubSpq62rsqystqW1squjrbKuo7SvtLGvtbm5sa+tqq6sr62vr6arpqOprbmssKeop6qmqKCfqqujpJ2eqKSipqGgnKKmnKGpn6uqsKitqLS0sr70qa+jmbKgpJ+anJ+jpJWXnKKmlp2Zqv2dmJedn5qZoKOdqaCVlZuZmICfmJaimKWfkJCSpJGXjpWelJOYjZCLg4mIioiFg4mMkJGSpZeKm5yGiY+VjYWKjoyTkoyNj56JjYWDh4iGhI2HhI2Ch4eEhIWKhY6LjoeDgf2BgoOCh4aMio2MiIePiJeNiIiQhYmA/oSFipKFlImEg4OEhIWHiIeFjYWCioSKg4CFgoKAgYSD+v319e+C/vuD7IaHgoKCivnv6YCA6vb67vv1/fv4+PX097H7hoGC/YOHgfyDgYP/7vn+n4eBiIKCjo+Qk4qKhoeIh4X4iYSD+IiHh5OJiYmIhv2ChYeKkIaGiYqAiIKEgoaFiIeKgoXzg4aFiIWB/PL4goOEhImBhID/i4T5gYOJgH3u7X3sgIGIgIOChImNgo6Jg4SEg/+IioyEhoCBgn79f4J/jo2HjIuKi4yFf4GF84KKmJaKjIWQi4yG/oaLioiHiIqQjIuNkoqKgoSEgoeLho6Qj4yMhqSlm5KLjIqJjoeDhYqQiYuPkJOVmpmemJ6Wm5GPm5qfnP99/32jfQF+hX0Bfv993H0Bfv99zX0BfJZ9AXyffYV8BX18fH18hn0FfHx8fX2NfA19fH19fXx9fX18fX19hHyRfQV8fX19fIl9AXyVfQF8hn2DfId9BHx9fXyFfQR8fH18kH0BfIl9AXyPfQF8i30BfLx9AgIEAICXkpuWmpaRjZSOiIiFiomEjImZlo6UkZKVk5CTkJGUlpSUjqGUjpCTlZienqCgoZucmKCaoribkZ6WopeanJmTmKKgmZ2bmJmfpKeXkZqYnJ2YmZialoadkpGSjJqYiY+QnZqdlZmbnZ2go6egmpemlJOYkpKSlJyan5aSkpadm4CTnpiVopeblI6Sj5OWmpWTn5mZmZydoaaimpyaqZyppJWYs6KjpKGYpqGhpKCipqOlo5+apJuemJygnp2ioaCdpKWsn5+ep6GZn6WjoaSnpamsqKiqpKepqqahsLCurqqrqLWrsKSlmp6hqKejm6ylrK6rr7Syr7Sxq6uyr62lo4Crpqeop6+tr6utpauyoqainJ6hoaSgpqSiqqiZnpynoaahmp2hpKuqp6Oqo6enqKmfrqinoc+uqqqkramnoZyZoKKin5ymqqiroJiloqKkr6yXn5yhl6Sgm6WknaKkmZ6jnKGfo6aqoJ+rtLurq6uvrKuppp6joamjlJuSlZWVj4COkZKXnJeUk5mVk5ObmJuaoZ+mnpWdnZ2akqCkm6Scnpygo7OZmZqZmJ+elpugo5WSn5iZl5SWlpyUm5OVk5qWnYyZnceZkY6ppIqRopWUmpmbmJmcoKWin5eanZ6cnp6ZpWaci5KamJKQlJCOlZadnp6dnpaVlZSejpOXmJihnoCYl7hqjJSPmI2Ok46Nk4uXio2Oj42OhIeJfIaKiJGPkYyMjICLmo6IiYaIjZCWjYySkpONh4qIjYqghIiMg4B7gX6Ae3+AioqNj5KMi42JhYeFh4V6goKKgn9+fIOJin6Fg4eEgnd4hXx+cXeBgXx3fX19gYF5goWJiIiKgIiChICFf4J7hYZ+e393dX19e317e3yAfH92fG55gnaCfXW7i3+AeX18dHx6enqEenp1dnx6hoJuf4B/eXfdeHrdd+zkeHvidnVzeXhzdHt3eXN0d3V3cXh5fX12duPgd36CdcV6fnp2dXt1dN13797j53x15HR1dn12dXd2eHxxem5w1oBtaszRb3l0dXbi8Xbw94B+g/qB/vH0gYXq4eXk3+bd4Xjt3ODk5uT13rDh3N+FfHF0dXh7e399eIKSgOZ3enZ4f3Z5e2/Itn1+eniAf3l2fIB7fYCAg4B+fX6A7IBgf3XceIGB/IKA9IP2gnx9gH19fn99moP1hIeMmIeRjIaBgYB+iIOGhYWGf335g33+gY6Bg/6G/faYiICAgfx+gIJ8hn6MhoaAiYSKh4WTlIyFj4yNkpKMgvuShomEhY+Gg4KMioWFiIB8j4WHipSPhXx+gomBhIibn42OkoqMj9Sgi5ONipCRhIONiYeMhoOGhYKHio+KgoeOkJeWmIyHjY+Pj4CRkJeRk46Pj5eUjpCSlZWNkouZmI6Pjo+Rj5CSj4+TlJOVkKCZkpSWmZylo6Cen5malZyVm7GXkZ2YoJmanJuWmZ+bmJmYlpWaoaSXk5mXmpuYmpyam5Omnpydl6SgkZWXo6GimZqcnZueoKSempmmmpqem5uen6akpp2ZmZ2fnYCWnZybpp6inpmcmZucoJqWnpmWl5qanKKinJyep5uoo5eXqJ2eoKCWn5ydnpuanZqbn5qbpZ2fm6ChnZygoKCdpKatnp6eo52Vm56dmpybm5+hnp+fmZ6foZ6apKKgoaCenaehpaGhnJ+hqKWkmqihoqCdoZ6dl5mbmZmcnp6ZmYCgm5yjoaemoqKlpKiooaakn6Wln6Sgo6OipaSdoJ+qqKumoKCgoqOinZ2hnqKlpaagrKSmnrilop6ZoqOjop+ZoqOinZqmpKGknpqhoKCks7OcoqCkmaegoKKlnZ+kmJ+inqCbn6CflZaapbCcnZ6moKGioJ2koKypnaWgpKSkooCjpKWmqaKgnaCYlZigm5+dpaOqoZ2moqann6itoqulp6eqqrWgoKGioqiqoqess6WfraSppKCioaafpaGjoaamqJ+pqsyon5+6tZqfr56goqGgnJ2fpqajn5ieoKCeoaGepmugkZqlpJ2en5yanpyioaCfop2YmJuklJeen56lpoCgn8xvlp2ZpZiYm5WZop2nnp6fop+kmpucjpednKOdn5uYmJGZ2p6Wl5GQkJGaj5GXmJeWlJGTl5Smj5SXkZKRkZKSj5GRlJCUlpePjYyOjI6MkY2GkJCVko2LjI+Wlo2Xk5aRkYaLkouNfYaRlI6Ii4iGiouEiYyQkI+Qh42JjICQjI+LkZKLiouFgYWGhIOBgoSIhYmDiH+KlomTkIG6kIV9eoB9doB8fHyFe3x8e4B+h4h2hIN/fHvvfnvoffj7fobvfX53fn11d3p6end5eXt7eXt+gH14eOHcdXx+ddZ7f3x2dHx2dOV48uXt8X136nl5enx4eXt4eXx0gXh13YBydNvfdn98eXnp8Xjr6XR1d+V05t/nfYTu5+/t6+3n6n3y6evu8fL97bPs5u+JgHd2d3l6e316dXuXfuJ0d3J4gHp+gHfCroSBend/enVxd3h4ent7f3x7enh55Hx5gHnkeYB97Xp68H3te3t8gHp7e318kHzoeXt/iXyEf396fIB5f4GBg4KFgHz5g3/8gZGCgf+F/vaUhX5+gPp8fX55gXyHgYB/hH6Cf3yFhH54f3p9goJ/eOmKhIaEhYuJhISLh4OGh4F9ioGGhoyNioeJiYmBgoJykIuIjYeJjNeoipKNjY+MhYeOjI2Oh4aLjYmQjpeLhIWJh4mKjIeEiImIi4Cbm6GdnpeVlJ6alZWTmJiQmpSiopienZ+en5ugm52hoqCinK+kn52foqKqpaKfn5iZk5uVnrSXk5+cpZ2dnZ+eoqunpailpaSor7Cglp+eo6WjpqeoppetoKKjm6ilmZ+grKuvpqmtrrCyt7u0q6a4pqKon52boaelqaGbmJ2mo4Cdo6GgraWopp+lo6Wpr6mjrqeioqSipayooqOjrqSxraKjt6mrqauirKmqrqupramrrqios6irpquvqqitrKmlra22o6SiqqSdpKmnpaenp6ippqOkoKSkp6Kcq62qq6ipp7SttKiso6aqs7Kuprextbiytre2rrKyrq2ysa+opoCroaKkoqqqpqesqrK3rbOtp6qqp6qmq6mkqqqgpKGsqaymoaOorbWzsK+zr7OzuLyyw7u7st22srGptLKwsa6stbO0sK25ubW6sam4tLG0xr6mrayvprWwrLe3r7O5rbK6s7WwtLa2pqitt8SusbC7tLe6t7G5tMO9srewtLW2r4CusrW4wLezs7ivrLC2sbOwt7W7tK61sbS0rbm+sbiwrrC3ucm1trW0srq+tLvByrq2xrvBu7e6uL+0vbe5uMLAxbbEyfXDuLfV0rS4y7m6v768t7e4v768tLC1urm5vr+8yYq+sLfBwLe0trSys7O7urq8vbWztrnGsre6uLa7toCwrdOWqrOxvq+wta2wt6+9tLW1ta+wpauuoK6ysri3urW0rqSr8K6inpmanqOroaOrq6qopqWlrai7o6qup6ejpqOhmZ2hp6Wpqq+ppquopqimqqifqKaspaCdm56oq52rp6elppqbpJ2cjpago5+YnpmYm5+YnqGloqCglpyZmICZkpeQm5+YlpiSkZial5mYmZibkpSLjoCMloqYlofcn5SNipCPipGOj4+YkZGPjZGPmpiDkpKKiIT9hoHygP/8gor3hYSAiIiEg4uJiISGhYOEgYaKjoyFhv7/iZGUh/mMjYiBgISCgvmC/e/1+4aB/oGDhouFh4qGipCHkYaG/ICCg/X1g42IhIP0/ID7/oaBg/h99/L2hYz37v/++vz29oT85u3x8+//6ujy6vKPiIGBgoeIjI2KhImwkfqEh4KJlIqPlIjTy5SUjImOi4KAhIqDiYuIjouLioeI94Sih4HzgYuI/4B+83/7hYOEioiFhIOBmYT5goWJlYiTiomDhYCCjIiHh4aJgIH+hoD+hJOEhfqC9vmShH5+hf+DhYeCjYWPiYeCg4GGgICJi4Z/iYSHi42JgfmVi4+KjpiPiYmTjYiIiIKBkIaMjZWVjYeKipCHi45ekpGRl5KUlsinkJuRkpSRh4mRjo6QjI6Wl5WenKeck5OemqCdoZaSl5iXl/99/33/feV9AX6gfQF+/322fQp8fX18fXx8fX18ln2CfI19Anx9hHwDfX18jn0FfH19fHyFfQ98fH18fH19fXx9fHx8fX2IfAF9iHwEfXx8fI59AXyffQ98fX59fXx9fX18fX18fXyLfQF8k30EfH19fIR9BHx9fHyFfQF8mn0BfJ59AX6rfQICBACAmJKSlJWfl5CWk5STkYmRh42Wk4yXkZWcmJaek8PSkquon5ylnp+Xk7KonJ6gmZucnqSbtK6hk56ZnpmUmZmbl6GkoKCdm5qmn4GhoZ2lnZycoZqYmZycjpSJko6SkoyRlpqboZqWoKSUo5vhoZuQnpaWlZeSmZ2hmp+cnJuemqCAoKGem6KfnJ2al5WZmZ2gnKSXoqWcmpyXop2foKKcnpuXnp6hqJ+bnJeZnaGeqKirs6WkqqajmZebmZ2xqaSemZykqaOtoqiip6ylpaGgo6qdmaCopaeiq6ajm6+sraOmr7K7uLCnn6Wjp6qmp52knK+nqrSurK6ytcazsa2jo6CApqOmoZ+ioamfnpiiqqGfoqmnrK2oqaejpauknqCfqKekpqKYpcGzn6SqnaCdo6vJvqOblKqupKOfoJmgnaKgqq6jrKGsq6yrqKWjp6uypZyco6emrJ6fo52jnaSfnZeRmpqanZyam5eapJ+cnqKioJ6mo5yZmJqgoZyuoJ2gmJWAm5mUk5Sak5+topelpZ2lo6GdpJ6eo5aem5WZn5edoqGprp2UlZacmJ+inJ5qSp+Pm5mgnqidoaCbpJ+kmZKVmZiWkZOanZOWmJCYjZKvrl+dl5SXnKekm52emaCWoaClq6GXlJqUkpGQj4+NlJeWmZ2fnrGuoJ6TlJSYmI6Wl5WAkpigmpubkZGPlo6SnqFj05aZk46PhpKNjJGRlZGYipKRko6KkJKOko+PjpCWlpSVmZqan5yYlJOVjpeVk42Jio6Li4aDi42Mh4ORj5G6lYyOioh+gZWKjpaLhZGLh4mDhIN9g4qFkIuKhYyAkoSCfn95eHh3eoB7gn2FiYSDfHyAhIF9hYuDhIqEhIOWiHl8g3uBfoR5cXZ4lXR8gXd5dnR5gpJ9enx8hHN3dnl7eHt/e3V2cYJ9enNz1tzm5nNzc3l1en19fYh2jXSEfH1y493aeXp7dnV7eXZ5d4J+hvN+eXZ3eHxycIOA6HjYdOR14+h57Xl7j3FveXx5d39wdm2AbXJ9dnh16+WL8fmB//Xt8vPk7vR+9ILs5/N+fut6eHnkeeXm4dfwhvT54+3g74F96vL1fXXld3vk3Xd1fHZ1c3jc53N6f31+fn5/efHve396iXd9gHx/foB/e3d28Xp7hH2F9/z9gfl7fXx8fPt+goOChoWCgYiKhYGEjImF+YOA+3159H2A9n1+gYGCjO9/g3vk8+989P2Dg4aGfoWHhYOCiX+CgYGCiYWKjIeJkI6Gkq2VkY2LpZGBh4GCgoN8m3x6fIGBg4WKi42OioiLkJGGh4GEhZKUlo+JioqLh4uDi4aFjZCDg4uIiImAioeKkYuJjI2RiJ+gk5CLk5CNmJKAmJKSlJScl5GVk5GRj4uQiZGWkpGWkpWYlJGYj8nNkKOhmJWcmJeUkauon6KemJiYm5yTrKedkJuYmpaXlZWYl5ugn5yamJmhmYqbmpiclZeanpybnp6imaCXoJ2fn5mYnZ2doZqYn6GUoJzVoJyXoZ2gn6GdoqSnoaKfnJudmp2Am5yam6Cempycmpubmp6hnaSanaKemp6aoZ+hp6WgpqCgoqSnrKahoJqanZ2bnZucopeZoqCgnJujoKPOtqmmoqGlqKKooKScnqeenpuam6KalqGoo6SgpaSemKajop2gpqWurKWhnaKepaekp56ima6hoa2inJ2dorCin5+anJ2Aop+koqKhn6WcnZykq6OioqekqKaipqKhpaeloaGfp6Wjp6adp8yynqOlm5uboKm5u6SemsepoZ+coJykoaenrbCiqJ6op6alo6Oloqivopqdn6Okp5ufo6Kpoaajo6CcpaSipqSgopqeop2Zm6OjpaCrrKajoqCjp6Kzqqesp6eAq6qkpaaooqm3o5ejopqiop6gqKOjp5+nqKWjqqGiqaWtsaafoqOln6SlpKVgXKqap6m4qLKpqaWksKavqaKlqaWnpKWnraGhqJ2mmZy2t2alnpqepLGpoaSioaaboqCnq6WenqOhoJ+dnJucoKGfoKGioa2roJ+YmpqcoJSgoJqAmJ+noKOooqGeopueqa5xzp6inpqemqagm5qZnJuhlJydn52anp2bm5iVl5WZmpOTl5qXmpmUkI+UjZeWlJSRkpOTk4+Nk5ORj4qUk5a7nJaXk5OJjKCUlZ+UjJmUj5GQkJGJjJCOmZKSiY+LmIyMioyLioyKjZSOkoyUlI+RjImAjYyKjpGGio6Ih4aaioGDh4KHhYmDfYGEnoKHi4OCgH+BhpWFgYJ+hnd6e36BfoKHg31/e4mEfnt77Ov08Xx7fn96en9+fYl3m3eEf3136ubqgYV/fXd6d3Jycnp2euV/d3V3en52c4SC8nvle/R88e5563h4hHV2fIB+fYd7gXiAd3h/en158uiE7O5+8+fd4uzh7O557Xve2+J1eOR6e33zgPb07Of/iPr55Ovj8Hx76+3ue3bneHzo5Hl8fnd2c3jd6nN4ent9e3l5derleHt0hHN5fXl8eX5+enx47nl6h36F+fr6f/B4fHp5efF6e3p3ent2c3l+fHt8hX586n2A93x68H2C/oB+gYGDi/OBhoPy/fiB/PiAf4KCfH+AgIB/h32AgIKBg4CDhX5/gn95g5eEg4B/k4t+iIqIh4qEpoeBiImKh4eKj42MjYeIjIyDgnt8fIWHjYqFiomKiZCMkI2Ol5qNiY+PjY6Nk42PmYuHiYqQhpWXjYyLlI+MkpGAopubm52moJmenJ2empWbkJuhm5iemZqhnJqdmMnPnbS0rKeyqaqfm7ixpqann6Cfo6edtK+hkqGaoJqZnJ6hoaOpqaSlo6GroKGmpKGoo6Wmq6eoqa6soaeapKCho56ho6WlraemsK+gsazyrqabqaGfnZ+YnaSooaajo6GmpKmAqKako6eloqKfnp2eoKalpqqjpqylnqKcpKKjpqejpaahpqmrsq2pqaGipauqrq2utaWmsK6tp6ewrbDgwrCspaStsamwpaigp6+lqKOlqLOlnqitpqiipqKclaOkpJ2hqau2tK6oo6mlrbKssKitpsO5tsK5tLa1uMa1srCnqKqArqyxq6qrqa+kpaGts6yrq7Wvs7SvtK6srrGsp6mkrKmorq2jr9C+qLC1p6uosLrIzLivrOW7s62rraiurLa1v7+wu66+tba4tbaxs7m+sqmorrO1urCxuLO/ub65t7GrtbSytLKusqautqqqrLSzs7O8vbSxsK61u7fIvbvAureAurWwsLS1rbnGu7K/urO9uLe3vrW2uq64ubqzuK62vLrBw7iutLi6tLq/u8CTgMi0xcXbxtHGxsO+y8LLwL28wsHBvb7Dx7u6w7rDsrjd5oHKvbi+xNLIvb6/vMG1v8DFzcW4ucO9u7u4s7a3t7q4vL3Avc7Nv7+2t7a6urK+vraAs7a+trm7srWxurS6w8mF27a6tq2zqrqxsbO2ure9rLS1ta6mq6qppqKfoKCnp6Gmra+ssrKuq6yvqLa1sbGtra+tq6KjqqqopJ+srbHNtK2sp6ednriprLSqoa2koKKfoZ6XnaairqejnKWbqJubmZqZl5manqaco5qjpqGgmJeAmpuZnqKXmJ+ZmJm0oJSUm5SZl5mVjJCQt4uQloqKiYaKkaiSjo+QmYuOjo+UkZeWk4uNiJmSioaE+fr8/YGBgYaDhpCPjZ2J0oqWjYuD+/Pyio+MioeOioSEgomChvSLhIWFiI6CgJGK/4Logf6D/f6C+4CFl4CAipCLjJSIj4aAhISMhYWB/PCJ7faE/fjy9v/y/PuD/YTt6/WBhPSCg5D+hf/58Or+iP714u/f7H2B8fb6g4P9gof9+YeIjYOCgob0/oCFiIiIhYSEgP3/hYiFlIONkIuKgYWEgoKA/oKCjoGA8/n8hP6AhYKCgfyBhIOBhoaBf4eJhoaIlYyH+YSA/oOA+ICD/4CCf4OCjfaFi4fz/fqC+/1/f4eGgYaJh4qJjoSHhIWHiYSJh4CDiIV8iKWPjomJqZiKkZSPkZKPtY6Ii4uOj5CQkpSXmY+QlJaQj4iKjZiYl5OLkI6QjJeTmJKRl5qQipSUlZaUoZmeqpyZnZ6jmKqtoJyZopuYoJ3JfQF+/33/feR9An5/oX0Bfr59AX7/fat9hHyRfYN8jX0BfIp9Cnx9fH18fXx8fXyTfQZ8fH18fH2IfA59fH18fHx9fXx9fX58fYV8AX2GfAx9fXx8fH19fH19fHyHfYJ8iX2CfI99AXyFfQV8fHx9fIV9AXyQfQl8fXx9fXx9fXyGfQp8fX19fHx8fXx86X0CAgQAgJeSl5eVm5WVmZmUlpWXl4+Yo5GOlp2flaSavqShr6izopymu5qcmaCan6CmoZ2in5yinKGcl56mp7KfmpmWmZyjlp+onZSbnp+qpaKfmZqflZybn5eTkYqZjZGWmJ6cmZyVnqKen6GdnZ6lr5+foqafnp2emqGjn5yjo7Grr6mhEKu0paKso6mkpZuam5SaoJ+EooCfo6KmqqCepqqXoKGcqJ+irZufm5eXnZ6coaCkp5+pqqaroKKenZaerLCgq66uqbKuqqSsqKSlpqGenqOfnqChoqmooqaiqqWlrqyutbOyr7CnqqeoqaWryJusmqmoobynn6Owr7Crs7Oxsq6ssKeho6OfnaikmKapqaqnpKiys4DfqLCwqq9vmqiwqaSlpKekpa2rpKyspJ+rp52zuaSUprKvq6WlrKioqp2ioZ2iqqSlqK+no6WrsKGlq6ukqKumqqWsqZ63n6aimJuUlZOVl5Wlo5ynn6Kko5qioKCazJqkopaioZ6dn5ujoK6ampyan5+woqmmoLWimZyuqKmppICro5uco6Cdm5+em52hn52gnZ2cpaGknpeTm5acm6OVoKWcmqe1qJaalJmVlpiUkpainZubmJiSmI+Tmp2ZlJmZlJeZppOQmJWYnZWUlZqVm5eVlI+IjIuXmpeel5eeoJygo6ucjZqln5mcl5OfnJ2NkJSUj42RmJKYkpWQkpWTmICzkI+RkZKTi4yTkI2WnZGUkJORnZybk5aakJqWnJCOlXChlImHgLqQjIOHiIWIiYJ/kImLhoiKioiHiIeIgoOEf4aNloiKio2Hh46Kh4iLjouFiIh+oIWKkICCgH6OnI+IiIWFin+IxnyBg4SMhoiZh4R+gICCgX6FfH2Bg359fYB+fYF7eoCDf3p5eHqDhYRLUnd3dnZ0cXCBdXJ0dHZ3d3tzdnB2ZmxxcsxxdHFxdXR3en9+bHd2eHt6eXxzgYN7fHt6c3d8fHt/c4GEf3x7hX7niXXe2XaM4uzyd3TmeHZ4gntzd3V4eXlzd3JxbHZ7dOjm53bffOPg53XsfPSCgYCNgYT2oIGBh+ru4Xnh6eHk6eftfe175YD1gfH48vP0e3ff7Xrj4OJydnZ3eHh5fPXogfzo7+nlfX0ylYyHhnyCeYF79H5+fH5+7/SIh/X3fIj1g4KGgYaGg/l+gIGC/P+ChZGLi4mFipCGhIJ5iHVyeXl2dHuCfHl85Xd8fIJ+jGyDfIKGh4uHh4qXin6AiIiHiYaFj/WIgpOOjJaXlJCMjZKWuZCTioKFhoWBgIV7gX+CgY6HjYqLkIaBipKUk5SGqZCNi42FiouEi4SFh3+ypZeGioyGh4GehYyIg4eLipGMmJSYlJqXmJ6ZmZmAko+Tk4+Uj5CRkIyPkZSRjKG8kY+TmZiOmo6rlpKdnKSYk5u5k5WRlZKWmJ+bmZ6emJqUmpeUlp2cqpubmJebnqCVoaGalJicm6Sfm5iVlZaSm5uinZuZlaCXm5yeop+aoJafop2anpqZmaCmnJ2hpqGioqOdo6KfmZ2apJ6fm5KAmaKWmJ2Znp2fmJuZl5ugoKKin56cm5ydopuaoqGbpaShpKCjrKGinpiboaOhpKGjpJ2ioJ6hmZ2cnZijrq+kqaamoqqnpaKlpqOkpqWin6ShoKOmo6eooaWipKGgqKWmqqako6WgpKGhpqapwqGunq6qpb6jm5ulpJ6cn6OgoqCAoqafnqShpKGqqqKoq6qpoqSjpqjGoaSnpKtzmaiwqqWlpqOeoKWnoamsoZ2mp5+xt6GYpq6qpaOjq6elqaK5o6GjqqajqK+mpKSsr6CjoqagoKOfopyno5y4oquqoaeip6Slp6W2sqiypqmqqaCoo6Gdy56hoZegoqCjpaKqqsGAp6eopamouauxrKe9pZ2cpqKnpqSqpqKlrKeko6ako6SkpqOmpqekqqeopqGfpKSrp6ucp6mloamxr6CjoqumqqejpKewrKqoqKadopyep6mioaSnoaWkr6Ccp6OlqKSjpKyoqaeqqaWgoaCpqKGonZ2jpZ+gpayfj5yop56hoJqAo6KhmJyho56enqWip6GkoaOin6DAmJabmp2blpidmpagp5uemZuZo6CelZeVkJiXmpSTlm+km5eUkLmbl5SWlZCSkouKmJCRj5KVlZKTlpmXkpKSjJecpJaTkJSTkZKRj5CUmJWQk5SKoYqRloeLhYKNppOQj42TmpSez46MjIqAkYqNm4yJh4qMjoyKiYOEhoqGh4KHhoeChIqQhYB/f4CGioZkdXx/fn59fH+PgHp8fn+BfoR8fnuDeHyBg++Ch4B+fH59foGAdnh1d3p8eH12goKAgYF/eHd7fXh4bXl7enZ2gn/xinjr6n2M6+7se3/teXh5fXp1dnSCeXZ2eXiAeXR9fXnz8fV453/t6+x063bneHZ9cXTpmnd1fODp3Xjk8Ovu8u/2fe18533we+ry8e7weXji8H3p5ed2eHd4d3d3eOnmfvft9erpfXtDm4N+fXd7dXx68Xh6enp88/iNifTzeoPse3p9enx8eeh1dHN24+t4eIJ8e3x5f4F8fH2Af4x5d4CCgH6Ei4ODhvR9goCFg46IfoOHhYqGhISLg3p7gIKDhISBivOEf4yHhIqIhH96fYKKp4eKh4eGh42LiY6Ch4aKiZOKj4yMjoWAhYiGhoqAmoyJiIqGiYyJkI2Qj4evppmOkZGPkImZjZSTjpCRjJGQm5KTjpGOj5GQkJKAnZqcnp2jm5udnZqdm52blK3Kmpedpqabp520o56qqLKloKvSoqOfpJ2joqeinqSkoqSgpqWepa2tu6ilnpygpKiap6mhnKCmpbOuramlp6qirKqyrKmkn62ipKeorqymqZ2or62qrKWop7G1p6iorqalo6afpaekn6amr6uuqqKAp7KmpbCosautpKiko6SqqaqnpaKhoKGjp6CeqKaco6Whpp+hqZ+moJudpamoq6qtsa20tK6xpqmrqqOtuLyrraWmpK6pqKWqrqyur6yppq6ppqiopqurpKejqKOjrKuus7GysLWus6+xtLO63628q7+9s82zqqu3tq+rsbKxtLCAsbasq7GwsKuzsKKtrbCvqrCyuLz4s7S2tMWIp7i9t7Cuqq+rrbe1sbq3q6exr6u/xbOksb+4ra2ttrK1trHAraeota2rsLy3srW7v7CytLWwsra0urS9uq7RtsC8rbKrsrCwsKzFwrTBtba5ubC5trWu+LG3t6y2tbCytLG7vtCAu7u8vMG+z77Dwb7bv7y7ycXIxsHFu7a5xL69u73AvL6/wL7Avrm8x8XHxby6wr/Fw8m3xci/vcfQy7zBu8fAxMG7urvHwb28u7ywubG3w8fBu8LFv8HBzbe1wbu+wru6vcS/wcDDv7y1uL7Fv7vCuLfCw7zBxs+5k7jEvbK0sq6Au7e7sra7vbS1tsC8wLq9tre7tbvntLK1uLi3raqtqqe0u6usp6qos7CupairpautsaunsZq9ta+sp8y1r6yuraaoqJ+erKWloqWprKissLOxrK2upa+zu6empaefoqalo6Kmq6uipqWayaSjqpeamJGjp6CcnJmgqaKs0J+dn52App2fq5qYkpKVmJiZnJmZnqGdm5WalpaPkZmflo6Njo+VmJiAno2LjYyOjJOompSUl5qcmZ2RkoyUgIaGifWEhoSBhIqKjZGRrYyEhYaIg4iAjpCLj42Mh4yRlI+QhI+Qi4KBi4b8l4P584GS8vrshYv+goGBi4aAhYKSiIeDh4WAhYGHiIH7/f2A8ob9+f2A/4L9gnyLe3/4oIODjfX674Lt9fHw+fn7g/aB7YT+hPT++/r+gIDu/4L6+P6BhIOChoSDgvnygv/v9u/wg4GU95WRjoSNhImD/4CCgIGB9v2Pjvr+goz8goCEfoWHhv+ChIGC+Pp/g5CFhYWBi5CJiYqAiZ+GgYqGhYOKj4eEiPqAhYWKhZOKgYeIh5GLioybioWHi4uLjYiJkvuIgpKLi5GOi4iFh4yduZOZko+Ok5aSkZeLj5GTlqKSlY+Qk42JkZuYmpqKp5aMipGNkZOOkY6Tkoe3sKGXmpuXmZGlmKCemZ2emKCcqaKknqGcn56dnZz/ff99nH0Bfv993n0Bfrt9AX7ufYJ+mH0BfIp9AX6cfQ18fX18fH19fHx8fX18k30NfHx8fXx9fHx8fXx9fIV9AXyEfQR8fHx9h3wHfXx9fH18fYV8CH19fHx9fHx8iH0DfHx9hXwDfX2AiX0BfIV9CXx8fX18fH19fId9AXyEfYJ8mX0BfJp9AXyMfQF+yn0CAgQAgJqXn5+YmpianpydoJeYmpuem5STlp2Xm7KaoamgrKW8qZ+dmJKUlpWamp+iqKicpKKhpqKgpqCrp6Onn6Gkpp+fmaCio5Ohp6Kmn6Srop2prJ+moqKgnJWblZqao66lpKCkp6GppqGhqqeeqp6jnaWgo5+hoJudl6SsoJ+hprCngKSlqrCop6mhoKWkp6ekoqKpo6iiqaOinaSnqJ2elpuel56coaGiqKqnm6Kin6SjpKGgnKCjppqhnqGlnqWiqKWlqbCusKqjpqKoqaafnZ+bnqC6pKOrmqCen5qdqq2qpLK0prSvqKalprCioqamoZ2cnZqgpaWoqq2uqLSknqWjgKOmoqSsp6WprrCptbGnq6elqq60qbW6u7KxsrKpx6enoqikoqypqKmxrq6vramrpLCio6+qq6Wop6Gmnp+hn6OloaSnpqatyLiyp6impKWip6qhmqChpqulp6mgoKSYlJufnaKgmJ2ampaaXpqdmZqYnJmntZehoqagpZ6soaKmgJ6cnaClnaGhqp2geKmgp6ysqamhpKytq6qlpauno6CeoqeZpKOdnLqnXqSitKqen5ukpJ2soJuXlpydnZmgnJ6cl5OqoJWWm5OXlJqenZydn5+eoaWun5WQnp2ZkJqlnJyjoJqbi4uIjpOKjoWVlp+dk6GaorGhlJyaq6KZnJyggJuyl5eamo2OkZGKjpecl5KVkJCQlY+SlJWpjpSYk5aclZiWlZSVjp6WmJCYkZWZkJWcl5OQm5GTko+UlpOOkZSLhomFjIqJkYyVj4yJiImDf4V/goCIiaOZ6I+NiYeNiouLhn+Sh5COioWEhImQi4iKhYuBgYmBj4J+fIeHgoGMgJOJiJJ2goKIioB+hn2BgHuDhXx4f4p/gX+Jg4aEh4N+hI2chX15dnh8cHVuc3Rtcnh0cXR8d5B3b3Z3b3dqcHNvcY9/gH+CenZ8goB/doF+f32PdXx8fHR7end7eI+TdnNzdXp2gHaBfXd1dXPieevm2Hl6eYOFfHl3hnt4fXp0gHdydOLg5HPc2HZ7pPDz7ez3+PqCh3+LhYODfoR7eHnc3eDd5HLa2eR46Orl7uvk6eN67u987njq2d50eHyAgX95f315fn7whIaH+fnveIDy8Hx6dXyDfYGCfHvefH6Ai5T+jomEgYWHgIKDgYKAg4SDgvj3if+Bh4qE8n2AgH55gHV9gXx/endxeoR87Xvr8PB9gH58hoCQhYGGi46Pi4aCjY2MhYuKiJSPopGIkpCSko2WlY+XiImFhoZ/g4KHgYuMio6Kg41Yh4aEhYmGlYqJjI+MiZOPkYmFgoSJhY+Jl5eMgIKJk5CNipCQkpKUj4+CjIuXiaufl5eUkJOTjJaXJJaTl5aQk5KUlpSXl5STlJaXlI+Sj5SQkKWPl5qUnJmwopiXlISRSZWTm5mcnJOcmZmZl5mhmqOenJ+ZmZudmJubnKCmlp2joJ+bnaSXk56kmqCgoqCbm5+cnZ2kp6KjnqSknqOgnJyjo5ulmp+boaGEooCdn5ykqZ2anaKnn52doaagn52YmJ2dn6Cdn6KopaWip5+km6OnqaGgpKenpqalqaOho6ajnqKmo6eko6KfnJ+fopmfnp+loaWmqaenqrCnp6Shn5+kpqajpaalpqzIr62ypKWlo5yboaCenp2kl6Shnp6dn6mdnqWnn56cnpuipYCgpKOpqqavoaCnpqWmoqaqpKGjpamhqqSipKOkpaOqoKaoqZ2dn5+etKSlpqqmo6qnpqSoo6SqqqaloKWcoqqnpKKlpqGppqiqqKiqpaapqKWryrOtp6WjoaCiqqeinp2fpqmkq7Cmpaujoaqwq66tqKenpqCnf6anqaenqqSvuYCfp6anoaSfrqaorKalpKqroqOlrJyidaugpaanpaWgoaaoqKympqqnpKGhpqyhqaynpLywc6upuK2kpqKsq6SwpKaeoaqmpKawq62qpqXDtKWnr6SloqSoo6Kgo6alqa3FqqCdrK2nmqKppaiwr6iro6SiqKyjpp2oqKqmnKScoYC2o5mjoLSqoqWiqKG4nZyjo5qbnqCYnaapoZ2enp6ZnpqdoJ21lJmdm56knp6en5qYkaCbnJidlp+fmJqjmpeUmZKVl5OZm5iZnaCWk5SQl5SQl5WalZiWlZeWkZWSkY+Vlayf4pWTlJGSkJWRjoeXjJWUko2Li4+YlI6Nh4yFh4CNhZSIhoSIiYaFjZaTkZmGi4qPj4iGjoWLjIWKjIOAhYyCf32Cx356fXh1fIWUgX59fYGEe4B7gH91e4J8eHmDgZqBeoOGgIp7f4N+fpKDgH2Bd3V3enl9dnx+fn+ReIB+f3d7enyAeZ+aeHd2d3h2fniBfX19fnjoeens33t2c4B4endzdZh6eXt4d3l1d+vq8nnq5Hh8p/Dx5+Po5+p6fHWDfnp7eHx5eHrl6O7x+Hz16+578e7r9Ojm8Od99O988nrr2t50eHt9f314fXp1eHnpg4KC8/Hoen3q7nx9eH+De318en3lf3+AhYzugn94eXt9eHd5eXt5ent4d+jngID2eX2Cfel8fHt6eHiBjIOFg4F+ho6G/4T7+/eAgH59hICMgH6ChoiIgYB8hIOBe3+EfYiFloqCh4OEg32EgX6DeX6AhYaGhIaNh46Li4yLhcWViomKioyGkoyHiImHhI+NjomKiY2PipGMnJqQh4WLlJCNi5CPkZSXk5CHjY2SiQunm5SQjY6Tj4qPkoCko6Wln6Kgoaaip6ihpKOlq6Wfn6Cknp+3mZ+knKShr6men5uYmJubnZ2io6OnnqeppKekpKynrqmmqaCipqqjpqSpqrOjrrWytKyvtaalsrSqtLKysKqnramqpayxqaqhqKmosaynprCvprCjqaKrpaqoqqmhpqGvtKilqLG6sYCurrW9tbO0qKiurqyuqKeorKenoaWcnpScoqWenp+lrKiopqahn6Oin5ifpaGopqempaWrrraps7Cvta2xr7CmoKavqailn6CkrK2qqaerp6erw6yosZ+kpaOfoamurKmxtai0sqytrK+7rK+1tq+sqqmmrLCsrq2zs666q6axsICusbC1u7a0tLu7s7y0rqmrqqyts661u7uvrq2wrMWwtLS4sq+2sLGyuLW2uLavsKm1qK+7trSrsrGoq6mtrquqramusq6vtdS+uq+ysrKzsri6tbG0ub7CvcLEu7m+tbG8w73CwLi7uLixtYu1t7a3trq0xtWvuLe7tr62x7y9v4C2tLO9wbq+wsy/x4XQxcnLysjFvr7DxcPGvLzCwcC9vcLMwMzLwb3Yy6PGwNTIur26xse/zcHBurnFwb+9yMPEwLu52cq2tb61urW7vr+9wMPDw8fP4ce7tczJwrS8xr/CyMfAwLm6ucDGvsS1xMLGwLfAtr/XxLnFvs7Fu7+5vICy4bOzub+1uL29tLS/w7q0urq5tru1t7m43bC2ubO1vbOzsbKrrae3rrKss6u0ta61vrWyr7evsrSut7mzs7e5rKenoaumoamlq6aop6aoqKGnpaalr63BvOyyr6uoq6usqKKXr56moJ6anp6jq6WfoZielZWflKOTj4+Zm5mZpICxsLWvk5qbnp+WlZ2VnJ2Vnp+Qi5GckZKQmPySjpCMiIqPoI6Ni46XmpGalJubj5SclpGSm5eylIuRlIiRgYeIgoOmlY+Lj4aFiY2Lj4eQkZGSqYySkJGIkI6Pko26sYmHg4eLhIqDiIWFhoWA9ID29+uGhYWNkoiCg6SHhYmFgICFgIT+/f2B8+2AgsP5/vPr9fL7g4eCkouMi4iPhYOF9vb8+/2B/vT3gPj29f/06/7ugfv2gfyA8+jugoaKj5KSiI6Hg4aF+omGh/n57ICD9fmDg4CHjYODhIGE8ImIiJCX/ImFe3yChoGBhYOFhISFgYH58oT/foSJg/CFiIqHh4CFkZ6Pj4mHgIiRhv+D/f78g4WBgImGlYZ/iIySkIyEgoyNi4WLi4qWkqeTjJOPkI2Jj4+KkoaJiY2NiImHjoqWkpGVkIza05CNio6PjJ6WlZiVlZGdm5yOjo2Qk4uSkaGjmYyMk52blZKXl5mbnJqZjpaXoJW3qJ+gn56in5iiov99/33tfQF+n30Bfp19AX7/ff99xX0FfH18fHyRfQl8fHx9fHx9fX2HfIx9hXwFfXx8fH2IfAl9fHx9fH18fHyMfQt8fX19fHx8fX18fIp9AXyFfQF8kH0EfHx9fIR9AXyQfQV8fXx8fLZ9AX65fQICBACAnK2fnaCempicmJWeoKGYmZ2bnpydmqWjqZqioaWgn5SFn6SinJaSlJKbnZ6XoKSepa+znqKop6itq6GamaCkpqijr6SpoaGfo6ihnKSqpamCpJ2lmqGenp+cn56aoqqepKqppqmfn5OfoaSfo52oq6ehoqWhopyZmJ+knKmksayAra6vrK+urLG0p6qvrrCoqa6fo5+fl5ykoKCZp6WfoaSlmaGZlJSWp5+pqKuhqqSooqWoqp+ooKGmpqSdqaOiqaqpo6epr6erp6ynrKilraahoaSeoqChp6Sdn6yqr6uwbbSssq+sq6arpaiko6yqq6ijpWzLxquoqK60s6ujrKaAqaKrrKaaoqeqoKioqquqqKKiorWrrq2zs7WxqrCysMHBpq2roquiq6+qqqqoqbOwsKWmqKqkp6OjoKiUoaCko6OcnKCiobGysaWnq6Kmpqeko5+so6eoqaakpaGjpaCsnqmgoNqtpp6cpamlpaiVoJ2SnJ+lpaWgn56mqJSem5uAoJqblJiYnpylr6i1qKSirKqsr6unqra2rbW1pqKiq6mnrKavnZ2opauqqKKjnqzEpaannK7DlaKknKOhnp+joKCfnZGZl5mgmquhqZ+fpKWdpJ+pnpiXm5iYmZmho8uepKqgoJ6WlZCPi5CSjpWOl5iYn56boaKln6OypKGrop2AnJeXlpWQj5KWko6WmZWbmpSNlZKwpJKbqZaPk46NkJyZl5iamqOdlo2OkWiOkpSZko+Sj5qOjo6SmJiZmZGMk42RkYqPjoqOhYuIjoyThYCFg4OCgoKKj4mFjI+Qi4qMhIeDlYeKfoGVgYeJjJCRjY+KhomSkZCLhYWDhIeEgYaAh4F7hoqJhoKFhoGLhpCThoyQgYKJiomGlIuMiYWKiH+Cf39/fHpwe3t2d3N2dHZ2dXF5eXVweXVzcmlnZ2lycWh0SUZ5fHp8cnZ3foB4g3h9fKOHh399c3R/eHh/d3lxjo92c3Z0fIaAeXt6d3Bwenx7gHp8f+N6fIJ/f3d6enOAdmx0dNrp1OjigIV/6qvwe/H0goJ884KBgn176nfp4nTk3OfmdYB7dux4eH/zhoHzf+ns7/vxffjr4+V6eHmGd3iF8PaChfP9hoP7+/j7/f2Nd/f07IJ/hYX48X5+iPmEkIuL/IeKhYJ9fn9/9oODgYWFgoKD/vuHiYeAi36AfIKAf358e36Afn9/gH6Aen6BfYR9+X5/ioiDfoGKg4mNhIWAf4aPioiEhYGIjI+NloiSi5aJi46PkYd+ioKJjYeKiZWNkY2Vk4+ai4OAiYWFiIWHj4aMmJOFi42IiIuVjpaOi4uQjZaRiomOl5SUjJuUkZKQk5aRkpqSmJWWmJeZnJ2AlKORlZSUlJOUj42WmZuRkZKQlZWWkZmWnI+YlpaUl5CBnKCgnpiUlpWbnZqTmp2Xm6GmlZuenZygoJ+XlZqcnKCbpJ2moKCanaKcmaCloKSEpJyjn6agnZ6eo6SgpKufoaSkpKahopuio6Kdnp2foaGcoaKioJ2cmZ2el6Cbp6CAoaCgnZ+fmZ6flJebnJueo6uioqemoKanpaSepaekpaimn6WmpKCgrKSnoqWdop6jo6Wnq5+loqOioJ2bpKGhp6ynpKSjpqOmo6eip6ilqqWenKGhpaOlqqSioqmjo6Cqg6mgp6WkpqGjoKCeoKajpqagom/VuaqlpaqysKumsKmAq6SwsqqcpqapoqWmpqmjoqCdnrCmpqerpqikn6Ojo7O3pbGuqbCora+np6ejn6Wkpp+ko6Koqamtqa+hq6itqK2joaepqri6s6esr6aqpqimp6OvpqypqqakpqOkqKe0rbavrNe6sKmoq7Ctra+iqKWfpaWqqqejo6OoraClo6SAq6SsoqKjpqGmqaWtqKGgq6anqKGcnaeqpquxpaOhqqaiqaWwoqSqp6qop6Kloa7Dq62uobHBnKaooaimqKmurKuqraGloqapprKnraGfpaOhpqKso6ChpaKio6WrqMCiqK2mpKajp6SnpKmqqKuiqaekqKekp6Oln6CxoqGnpaSApKOioqGgmp6emZqeoZ6jo56XoZ2yqZyir6CZnJacnaijnZygoKmgm46RlmWTlZyelpeZlKCSkpKZnJmam5mTm5WXmpaZlpaXkpaUmpqhlZOXlZiUkpKZmZSQlJiYkpKTjpGSoJGUh4ygio6QlJeYk5OMiIuSjoyJh4yMiYyHhoqAjIiCiYqKh4eKiIKLh56niYyOgICEhYN/jYSBgHuBf3uAf4WGh4R7g4KAfnt7e358fHp+fnt5f399gHh7fXyGhnuFcVKChIF+dHh2eX10eHR3dqGDgn19cHJ8dnZ6dXl0mZZ5dXp4eYF8dXZ4eHR2dnd2enV4e+R3d317fnp9e3eAe3R7fOf23ejgfH933qHkduXke3l13Xl4eXd55Xny9Xn06/f2e4R6dup2dnnqfX3teuLk5fLvevLm4ed+fXyHenqE6+x7fenxfXrs7e/y9PiQe/j39YJ9hIXw6Hp7gvJ+h4OC53t/fX15ent78H18e3t8en1+/vqEiIN+in58e398e3x5fH5/f4GCg4CCfoGAf4F/+35/i4iAfYKHgoaFgIKAfYSMhoqDhX6HioqLkISKhIuAgH+Eh4B8hYWHj4uKiIyHiImPjIyUjIiHjoqHh4eEioWMlJSGjI6Iio2WjpSNi4mOjZCNjouPlZGSipWOiY6Kj5GOkJaQlZCUk4SSgKe2pqmqpqOgpJ+eqKqrpaKopaqnq6WtqqycpqOin5+YlKSnpaCamJiXn5+clZ2fm6OrsqCorK6qrKiflpWfnaKknqmkrqiopKespKGrsKyxhrOttq+1rqiqp6umo6qyp6qtq66wqaihq6+xq6+rs7e1rrKzsK6npaGnraSyrby3gLi5u7m8vbi7vrCzt7a1r7G4q6unqaGlqaWlnayrpKSurqKqop+foqqgqaOlmqKepZ+hpaylsayytrezr7iup6qmo6Olp6+ora2zrLOyq7SspKOmp6qrrraxqqq2r7OrsYWuqa6tqq6tsKitqqy2sbKwrKuA9M6xrqqxubiwpbOtgK+ntbqwqK+ztK6wr7Kzrq+qq6zBt7S0s7Syq6ioq6rAwKq5tbC4sbi3sbKysLC2s7Knra6wsK2tsq+6p7azubK2q6u0s7LAw8Cws7WrsKusrK6st66ts7W1tLSztba2w7fBt7brwrmysrS4srK3p6+tprGzvL27tre2vL2ttra5gL62u7Kytry4v83L1s/FxNTMzMvDurzFyMPFyru7t8K9u8TAzbu5w8HEwMG6w7/Q68/Q0sbW67nHycDHw8HBx8O/vL2xuLO2vbrNwce5vcPCu8G7x766uLu4ury9wsHju7/Fu7q8tri0uLW7vr3Eu8bGwsfEwMO+wLm7zbu8x8O8Ibm8ubu7urG4u7a2vsS7vry2sL262c69wtC/trqxtbfHwoS6gMe8tqiutIKys7e7srCyrbqsrK+5u7m7vLivuK+0tK2wq6aon6ejq6uyqKaqqa2qqqqwsrCosre3sLS3qq2ouaaqnZyrmp+hpKeoo6afmp6loaCemZucnZ+dnKGknpmcnpual5iclaKevcedo6aVlZ6dnJesn56Yk5aRi46IjIyLCI2GkpKPkZGThJSAkZaWkI2Xko+OhYSFg42MgI3Zk4+TkZCEiYmNkIaNg4WErZORi42Bgo+Hh46JjIOtrIqHi4iOlYyAg4WEgIOHiYmLhoiI/oiFjIeMhYyLhIqAhYP1/+b46YOHhPK+/oL794eIgvuJh4uFh/+B/v6B+/P//oONhoH7gYGF/oiF+ICA6uvu/vaA/Pby+4uIh5mHhI/5+oGC7/iCfvT08vf7/5iC/f36iIKLkf/7goCI+YCMiYbuhIiGhoGChIP/hISBhIN+f375/YeMioaWiouIjoqIhoeLioiKiYiFhoCDhIKIhf+AhJORh4KGjomOkIaKh4WMlY2Mh4mBiomLjZKHkItPlo6PjY+SioKJhYqMi4qLlI2PjpqZlJ6RiIeTkpKVl5aakZagnI+TlI+RkpiQlZGSkJaWmZWVkJadm5yVop2YnZmfoJ6iqaGnoqWlpKSopqB9AX6vfQF+/32QfQF+kn0Bfv99/325fQF+/32XfQJ/frJ9AXyNfYV8DX19fXx9fH18fH19fXyFfQV8fXx8fYR8hH0JfH19fXx9fXx9hXwBfYR8h30IfHx9fXx8fX2GfAV9fXx8fIR9Bnx8fX19fIR9AXyIfQF8iH2CfJt9AXztfQICBACAqKOil5ydn5qTnJyXoJibm56YmpeWmZean52boqaqn6uloKKmoZ+dmKCim5+jnZ+goZ2lpp6prKSmnpulo7evp6qlr7eqrqWeoqShmqCiqpSho3ioqq2jq5qlrKSnrqmln5ycnp+go52hna6grKaepKKho6KipKOgn6ClmrqjrKuAraWppKuxqaupqaaurK+os6+hpaOmnKesqaCfn6KepJ6npqmlqKCopqStr62fp7GstLOhpqGko56fn5+mp6apramoqaeoqKu6rK2sp7G0raappqmjraelra2osLGqrbSwurGusbOrsLK8rK6xsLGzrKyvsbW5tbi+t7e5t62vsK03q7eyq6qzrrK8s7Gysb61r66vsqyvuK+yrLO0sLa7sKussMDAxKmmpaavsbCsqKqmqqutuamhqISkgJ6mr6+tqqimp6emv7isrbywq6qipKWZmZuknqSho6KloaKgnaOmqq2eoaW0pJ6jpq2ppKKnnpuTk5SemJuel6SjnKCsmp+emqKspKWnrautqaCqZKeZm6imtq+Gu6Pgq6jFqKemo6Onp6V/raivtaedo6Czr7yjpZ2sq5ugnZ2hgKGlppqioaKfo6SgoKiqtFuupq+sr6mosNKhoKGip6Srrpyfp6yioaCfoZyZmZaakZGTopWxlZiTmZqhpJuXmaShrampp6WloKapqp6TkJegxqCgnpuWn5yWk5GTi42Pj5CMjpCLlZaWpZmUl56Ylp6akpeRlJqchpaKkK2alpGWgJOSlJSMlJWXlJCVjoyJiYuNi4iDhomGhYGEhJCFhYaHjYmNiImHh4iJh5CMioeFhIeKhY6Gi4WGgpWHhZCJj4mPjYyTk5GRiY6Tk5WHj3+HgoKZjIaFj4aGiJCHhZJ7g4aDhYKAffB7g398d3t/foN9fH+HeXiAfnd7dXZ9gHpzgG5sbW5zduJ5enx1enl0cXV2eHV0cnZ1dnl3fXp8eoCFfYCBiHqChHh2iHN3d3p2fX90d3Z6d3ZzdH17eH2Ag359dXl4dHFzbXBxdnx52njse3x88O3w6fR/gIqHg4DzgPZ9fux66erj6OWZfn7l6XnkeXx543l4fYPq3nrs5evZgHN3do5/d+F3d3v2fO+DhfGA831+f/qGhPN9g36CfH57fvmDg/7/iIp/hv+BhPqBiJKBf/f59oaA/fh/f4yFgoGFfoJ+gYSHhIWHhoF/fYSCgYF/iYWFg396ho+JiY6FhIuMjYeCho2FhIaDhYSIlYiDhY6BiIyQjYuJhYeIh4muRYmIhoSZiYmMkZGLm5iPiYuEjIqPh4+RlY+Ni5GPro+SjJOSlISGiYmNmZeRlpqSoZyRlpWUlJKPl5qVlJGUp62rlJaklYCsnJqRmJmak46XlpSalpaYmpWXlpWXlpSYmZWamZ2Yo6Kcm5+dmpaTm5yWmJqYnJ6dl5yemKGim5uYmp2bqKOeop2jqqOno5ufo5+anqGplZ2cfp+lrKKonJ+kn6ilpqanqKSloqKjnZ6bqp6np56ko6Kmo6SlpqSioKSat6OmpICon6CdoqafoZ6fnaGho6GqqJmhpKefqK+wpaajq6OnpKajqKWqpqimpKuqp56fqaOorqOnpKemo6GeoKKjpKSnqKeopKShpbOopqSfp6iin6OgqKSsp6SopqOrqqGjqKuxqqamqqSqrLSlpKemp6ahoqOlrK2nqrCpp6uppKWnooChrqyopq2mp6+opqantayqqairpKWspqmgqKmlq7GmoqOoubm+qamssLe4ta2kpqGjoqKso6Spq6uqqqarr6+qqKejo6SkurOnp7avqaqhpaeenqCooqWkpaanoqSioKaqsLGjp6u5raWqsrazr7CyqqujoqSrqKmqoqurpqezp4CqqKapsKmoqqqtq6qkq2uomp6mo8Okg6+ax6Okv6ysqKWlqaukhqumrbKonKWgsq7FpqyhsbKmq6ioqKuws6isqKmrraqjoKmpr2SknaWkpqKjrMuio6SkqKSqrJ+gqKynp6ampqSko6Gknp+ktajDp6qkpqiuqKOfn6aiqqamoYCioJylq6+kopqiqMakpKShoaqqqKWgop2ipKSknKCfm56bm6eemJebmJmfnZWZmZyjpJOhl5uyoZ+YnZeVmJiTmJqcm5edmZaXmJmcm5qTmZuWlpKVkZyTkpKRlpWXl5eUkpSPjZSRkY2Li46RjpWPlpCQi5qMh4+HjoiOi46NioCIiISHjJCNhYt/hYOGpp6HhY6Hg4ONgIGRfH+CgISDg4DygIaEgX6AgYOHgn2BiX19goJ6f31+homGgX9/fn+Egf2BgoB5e3h0cnh4f3l3eX14enl1enl2c3p9dXl5fXR+f3Z3iHR3dnx3gX50cnJ1dHVzcnd2d36Af35+dnt9e4B6enl5d3h8e+Z78nt6fO/n7efteHuBfHd35n7sfIL3ffPy6u7nkHp74ud153h5eu17fIKF9u6D+PD16HyAfYyBfe96eHfve/B9fuJ56nd3eO1/fe56gH6BfX18fvaBfvT1hYV+gfuDhPV+gId5efHz84OB//2Dg4qDgHx/en56fIB/gX5+gH6AgH+Gg4KDf4SCg4WBfISLhYKFgXyEhIaAfYSJhYSDhYOGi6GKhYeNgYeGiYaHhIOHh4eFmYqMjImZhoWFh4J+kI2MiImEi4mOg4mKjYqMiZCPyo+QiY+SkoeMi4uPlZOSkZKLlJaLjI6MjYuLk5mSkYyNoKOdi46cjoCyramgpqimoZmkpKCopKWorKanp6WnpaSpqKWpqq+nsrCop6mjoZ6Wo6OanqGdn6KhnaKlnaapoqKcm5+aq6OdoaCptaitq6SqraumsLK6prKzi7S1vLK5qLC1p6qrp6OjpKGlpqqqqK2uwK64uK61s6+zsLGwr6qqqbGkv7G6uYC8tLm1vMK6ube3tbm5ubG7sqappamfqK+tpainrqeuq7GtsqqtrK2opKqrpp2gqqets6Sqp6urpqisrrOzsbCwq6muq6yusMGzs7GrtrWyr7OxuLG4sq61saqxsaisr6uzqKenq6Wtrbuqr7O0t7awsbO1u762ucG5ubi3q66xrYCtubqyr7m0tL63sLCxubSsr7G2srG5r7Ors7Cqq7GkoaCnxr3GrKyssr29vLSurqyvs7O8sK6ys7Ozsqq4u761tbKvs7WyyMa3uMm9tbetrrKmp6+2rLezubu7t7q2tLnAxsi5u8TVxLq/xsnGv8DBt7esra+2sbK2r7y8tLvJuYDAvrrCzMLCw8XLzM7I043Rub3CveLCpcyz67++3cbFv72+wcG3isXByM3Dt8HB1NDzyM3D1NTEysfGx8jMzcPHw8PCx8bAu8bJ1aLIvcbGysXCzP/BwsTDxsXT1by9wMe8ure3uLKxtLO9tLe5zsHwwcK8wMTNxb62tbm3wr+/voDAv7rFysy+u7S+xdjCw8C9vMTCv727w7zCx8LBu7e3srSys8G5s7O5trS8uK6yr7K3uqG5rrLLwL20urKxs7SssbS4trK2rKmqqKquq6mkq6ypqaGkpLCnq6mqtK6ytLKtrqyopbGppqShoaWnoKahp6Ghm6yblqKaopuhn6SpqYCmppqfo6WnmqCUnZqdzLyenqmfnZylm5awj5KXlJWRkI39hZCNjo+TlZeempacoJORm5uPlJGSl5qTjIWEgIOHiP6FhomBiIaDgYuJj4iFh4mEhoaCh4iGhI6Tio2PkoWPkYeJnIaJio+JlJKFgYGGgoSDhImFhImJiYuKgouLiICIi4SFgoiLh/KD/YOCg/vz+Pj/hISOiYSG+ob6gYX5gvz+9/76pYmJ9fuC9YCDgPeBgIKG+e+B9Pf+7YGGhKGRh/+EgYL9gfmHh/SE+4KBgvuIhPiAh4WHgoOBhf2Fgf38iIiChP1+gPB8hI6BgPz9/oR++/qBg42Ih4ePiI2IjICQkZCOkZGMi4iPioiFgoyIioyJgY2Yk5GTjIeSk5WLh4+XkYyMiYeKjKCJg4eQhIqNkZKUj42Sk5GOsZCRj4ybiYqNjIqGm5eRio6HkZSUipSVmJWXlZuW1JGUjpWWl4uOkY6Sm5qWlpqTn5yPk5eYmJSUnqWhnpufs7eyn6CyntJ9AX7/ff99vX0Bfod9AX6OfQF+pH0Bf/99130BfJ99AXzGfQZ8fXx9fX2FfIZ9B3x9fH19fH2FfAt9fX18fH18fX19fIR9A3x8fYR8hn0TfH19fXx9fH19fH18fX19fH19fIh9BXx9fXx8hH0EfH19fIV9B3x8fH19fHz/fYx9AgIEAICeoJ2XmJmfn52YnKCdm5ual5OZnJCYlpaZo6Siop6dl5qhnZyXnaLNz6akp6qlpaaoqKKhoKGiqKyqoa6iqa+toquoq9qrnp2jpJ2isK6co6ekqLOmq6WvtrCsqaGoo6Oms6CdqZ6fpKGioqapp6Kjnp+pqqCqqKitq6qur6espYClp6Shn6Kip6WkprG3tq+orKOmoaupq66pqKWqq6uloZ+wpaqwqaOspqqop6ussbSusL+pqamtoZqlpKOlpayxr6uvu7GstLC2tLC1sLWqtLSsq6ussKupsKGoobiusbG+vbOusbGutLyur7Oxraeqsq6wta62sbHBvbGts6yusoCttLWwqqyqt7CusrS9ubm7vsK8rLi8rbWsq62xtrK6tbW0s7mysLClpqepqqulqaWqqLC0rqWpqKyxo6Onp66psbSgoaajqrWjrLKwrq6msKeko6Ofnp+goKClnKOjn6mkqbGtq72ip6qnpqaqpaart7JtsLCqoaerqa+0sbuuryulqaelrKWlp6agn6GipZ+iraaqp7OstK6wrrGxpaamo56cnqGqoaGnpqKshaeAw6irqbCiwrurqqeqp6amqaipd6mipqemp6qlqqd2qaSjp62rp6Wpq7KnqbOgZZ6hp6qhpJ6dmZyhko+ZmaKooJ2alqCcmKKbmaCwoKChm5iimZyfm5vIqqmknqqkm6WemZWOjoqKloeEipaVi4KQkJifnaGYnJyZmZKTn5OYj6CAmpiel52io5yeoJykmpSYlZGOmZuxj46PipCKi4WMgod9hYWHlZBmi4uHhoSGkoOCi4eOi4yNk5KQj46PiZCLk5CGhoKIhIJ/hZGKkoaVmo+PiYSJjoiFhISIi4SIkF2fmoWUp4OKiIWOfIKMlYZ3g3h9fXp8enuigoF9eYV+hX6AfJCBfXp7dXhxbnZwcXZ2dnJ6jHx5eH52fIF4f3h5e3t8f4F9f3d7e3yCen5/gnp3foCAfHt8hHt1cnV5dHd9fpV9eOp5d36Cgn18dnh1fnp3enl3eHTo5eqE7n2S9fCD7/bxgfv0gPTu7eaCenl6eux4eeh4ennp53l4geeAgIKAgX97fOt74ex0eXbfd3V3fX19wISFjYJ9gIN9f399e3Z3eOJ2eqh/gn5/f33wi3p7o/L294SHh4+EhoD8+4vy9vv6fO6C8XWBfn18g3WDhIGDfHd3fn+AgISCho1RgJOQmYZ5jZefiIiHg4Lkjn1sgn+Ii4mIhomGkJeNjI2LjI01jIqQh4SKiYyLiIqPjoWOhISGhYWIiImRj5OVkIuPk5GKiJeSi4yIk5iTnJSSmJ6IkqCYlJmEkBeZoZ2al52fm5iZn5ChnJ6hmp6cmJyinoCbnZuWlpebmpWQk5eUlJSVkpKWnZSXrpOWoKKfmpycmZ+moJ+bn6HJy6CanKGbnZ2enJmcm56anqGgmp+XnKKjnaGdosuhm5+mpqOiq6qZnZ6doKacpJ2iqaKjo6KgmZ2js5+gqZ+gp6Wmo6epqqSjn6Cqp56jpKOlo6Gjp6Cmn4Cfo6GioKGhoqGgn6OnpaGeop6fn66sq62rqqqrraikop+tpKaqpZ6lpaiko6SkqK2nrLynpqmso6Gsrqmqp6qqqKaotKuorqmvq6aqqa2krqukpKaorKalq6GmorWpqq25tayoqqqnq7OmpqiopKGnsKmwsKuuray6tqmnrqiqrICmqq2ooKOirKeopqatqairr7Sxp7W6rbCmo6Klqqeuq6urqbGqqaqjqKerrq+tsKOmo6uurKiur7CwpKSppqqmsbWlpKipqrimp7CuqquiqaOjoqOfoKSlpaSooqiqpKmmrbOvrbqiqqunpqesp6mrubJ5s6+ppKWpp6yurLOpqoClp6mosKusq6uoq6yxsaurt7Gup7KorqmoqauvpqqrrKmtqamrq6itp6Ovqq2tra7Tr7CvtKHEvaurqKqrqaqrrKt1raerrqywsqytrIKrpKOmqqqmpamrsKqstadnpqivsaippqWjp66gnainsLOqpqWjq6elqqilqLSmpaajnYCmoqSoqKTMraunoq2mn6agnqSkpqOhr6Ggp6+sopmjoKCjn5+anp6ZmZSWpJucmKWemKCWmp6bl5udl5+XlJiYk4+cnrWUl5iZnZial52UloqRkZKgmWyRko+PkJKckI+XlJqUl5WUkZKPkJCNlI+VkYqNho6MjIyNl42ThYuPi4CKiYWIiYiEh4uTkYmLkmOmlpiQrIeMiYqVhIeRm4d8hXt/f36Af3+jhYSAfoaAhIB/lIWAf4N/g317g36AgYOBfYOPgXt6fnR6fXl9eHd3e31+fXl7dn15e4F5e3t9dXN4enx7fH6EfXl4c3Zydnh4i3l36nd2fYCBe3t4eXd/fIB7e319fnn58viI+YKM9/aD8/XtffnxffT18e6Ff359e+x5eul5enjq63h3gOh+gISEg4GC+4X4/31/e/F+e3d+e3q7end+eXR1enR4ent8eXp99H6EqIGDfX5+fPSNfn249fbvfn99hHx+efL0hPL7/v6B+ob6gIWEf36DdIB9fER/fn2Bg4KDhIKAgIeCfIyIlYV6jpWciIeHhX/1j3l3f4CGh4SCg4KDi4+Ih4uKjIyJipmGgoaEh4mDhouMh42FhomMioSJOIqKj4uLioyMhoSUjomIhYySkJmRkJahh5CakY+Si4yLiI2Wko6LkZOPjZCXi5mUlpqXmpeRlp6ZgK2vrqakoqimoJugoqGjo6Skpaq0pqvFqqm2tbKurquiqq+rpaGoqtzfp6OmqqOlqKeno6WkqKSoq6agp5ugp6Wfp6WoxqulrLK2rLG+vKmusa60vrC1rbS6srCpp6mmpKu5pqm0p6arqayoq66vqqyoq7O1q7Kyr7SvsrO4s7izgLS4tbOvsa2vr66utbm2s7Cxramnta+qraahpKSmo6emqLavsbiypa2rrKmnqaqxtq6vvq6us7Soo7Czrq6tsbGsqaq5ramysbi7uLy9vrK/v7W0t7e5s7S7rbCqwra0tMC6sKyvr62xu6uvtbGupqixrrW2sbeytcfCs7C2rrG0gK61u7aytbS8srGpsLKurbGys7aqxs25vbKwrq6vqrKtq6qqtK6trqSpq62vsa+yqa+ss7u5srq7vb6trrSwuLTByLW3urq5xbC0wb64u7W+uLm4urS1ubq6uLu0vLu2u7a/xsK/07W9vrq5uL23uLzLw57GxL21uL69xMjH3sjJgMLFxsfRyMrIyMTDxsnMxsfZ09TL18XNxMDBxsq9wMXDvMG/vsO+vMW8uMXDwcTFxfnIz8zTwNzZxsjFxcTBxMfGxI7Iv8PFwcbJxMXErcXCwcLGx8bEy8zSy9Dcxoa/xMvMwcG8vLq+xLe0v7rK0snEwbzGwbzEv7q/zLu6vLaxgLu3usC/uuTHxcG5yMO7xL++vry/ubvMu7rDzMe4rbm3uru6u7S4uLGzra+9srOsvrm3v7S7vb64vb21vLKusrGpprO5162pqqitqaumr6apnaioqb+0gbKxrK2srbmtqrOxsquwra2ppaOqrKqwq7SvpqSYnJuamZyon6ucpqqfgJybl6CmoJ2bnKClnJ2phcSyyK3Tm6KenKuUl6GslIWOhomMiY2MkMmal5KRnZaYko6qlpCMjoqMg4GIgYOGhoWBipqJhYSKgYeLh4yHhYWHioyJhoiCiYaLlIqKjZCJho+Rko+PkJWQiYeFh4OIjYqwioH7goGJkJCKjIiIhpKNgIuNi4mFgf38/ZX+hJr+/Yj5//KB/feD/f7284mDhIKF/IOF/IaHhf7/goCI8YKDh4WCfYL4hPT/gIWC+ISChImIheGIiY6IgYeKg4eIiYeChIP8hYnTi4yFhYWD/o6BgdL38Ol7gIKJgYOA9vSE6fDw7nrvhv6AjIyMiZKAjImJQo2Ig4SIiIqKjY+RmsyImJWjjYGVnqWQkI6LivyciLGMi5OVjouFg4SMj4SFiomNjo6RnY+JkY6RjoaIioyFjoaIjYWPOZGQkpaRkZCVlJCPn5iRk5CZnpmhm5iapImRoJaSl4+Qj46WopqZlpuhnJmao5GmoKGppKqppamyrf99/330fQF/z30Bfop9AX6PfQF++H0Bfrd9BH59fX7hfQF8kn0RfHx8fXx9fXx8fXx8fH18fH2EfIV9DXx9fXx9fX18fH19fXyHfQh8fXx8fX19fJZ9AXyJfQF8hH2DfId9A3x8fYR8BH18fXyWfQF+kX0BfuF9AgIEAICfoKSinZygnJigouKdo6amnpimmpSVmZOTo56gnpiXmJSSk5mTlZafm5ydp6mqr6unq6SusKiopqipp7Snp6+tsrGgqaunn6alsq2opaalpqalqbKrqJenpKCkqKWup6iipaSeoJOdo46mpKShn6amm6SlpaKlp6qsr7Kur66qq4CqqKmjqqSooaeop6mqrq6psquzv66Zq62rraWioqmwpampsKuppa+yr6ysrbO1r6qwqKqtppygoKiko6enp6qpqqmrra+mr8+yrKyysbC1tLOutrm0r86zu662srGws7KvsrWtsraxsre3sbSxp7G1sLaztK2vs7q8t7ewr7WusYCtsamtraqus7Gur7HIf722tr6yuLa2tKyxrZzNtLG0rLG4tre0sa+0srS3sa2rq6umqLNuoqqwsK6trKWryMifr7KysqyttOO3t7eytLO2ubiysKq0rKWorKmrp6OgoqSloKKsqaypp6uuoqSwqKiyo62qm6aimqenoraxraWlsoCqsK+ysrm9pZ6joqSsqKWio6ejp6yxrqyyta2moKCenKObmZ2opaGjoqWjpqOioqGepKmqr6SoqKysqqCboZ2kpaSZl6Cqo5qfpqWim5iampmao6egoKGcnZ6ioaXCo6uqqbSkqaqkoJqanZ2loqadnp2jo5qYoaWlrp+koqanoYCyq5ibm5yFnZegnaOgm5eVk5SOlpCIjo6ToN6YkJChlqCepp2Yn5+dpKOhnKKaoqGgnJudmbWSlZSPrZeRjZiLmZSRlaCOjZOQjJWWkpGRlI+UjJSblo+UiIyPkJGVj4OChIiHiIiFhYiKoaGSoJeHh4yGgYSKiYqMjouLh4SMj4CKlI6JjJGLkpqRhIuOiKiJi46IgoaBg4N/eId8e3x5u3V7e3h7eX+egH12e3h4mX94fYR+eoN6fHl5eX13gYF8eYF9d3Z2cXV4dnN0c3R6f3x6eXt+hXeOent5d3x9fHZ3eYeGenl7dW14fIR6enuGg3x75HZ7d3x6dnbj4dub2IBvcnPUeHBydnaCd9zr6Ofw/vj5g397hXZ3enh86Xl2dHt3dnx3dnR0fIB/iIeDgH+Cf4H0+Ozp+4SD7Xt4eYN9fenmgIOBf4B97Op9fXx+fIiHefB8dup/oX90fIWBdnryfYSEiIyPgIKBh4SH6/17en12eHl7foF7goWDfYR8eVWAf357eHV8gIGBgoeDiIqGjoeFiIqPiY2kkZOKioyVt66Vk4yNj5qMi4yNj5KOi4iJiYqAhX2CjIqMkouHjIqKiouDgYaqi46PlI+TkJaXmZebl42MhJMnjoSSlpqSkZCUkZObnKGspZ+ooZ+hqKGgmJqVk5aWk5iPmLWjuJuegJeYmpiTkpmUkJOYxpaam56Zmqacmpidm5moo6ein56fnZ2eoqCfoqain5+jo6ChnpiblZ+lnZyZnp+gopqaoKOnpJqjq6yepaOsp6ShoKKgn56hpKWknKKfoqKlp7CnqaCkpKCinqCnmaypqqSjqqqhqKeloaKkpqinqqmpqaOmgKSlqaOopKiiqKmmqKOkqJ+mo6m7rJyqrrCwrKelqbOopamppqWjqqyrpaWlpqempKqmqK2pp6uprqqoqaurq66sq6qtrKCnvqmlpaipp6moqqSrr6qlv6mtpKqlpqaqq6usraeqrairqqymp6eepKypraywqauvs7Svrqiqr6qvgKutqKynpqeppKGjpbt+sa6wt6y0sbGwqKypnsqyq6yjqK2rq6mnpaypsLOwsK+xrqqrtXekrbGxrqyqpqrEw56vr66opaesw6+tramsqKqsq6WloaujoKSqqKyopaWlpqmkpq6srKmrqqqhorCqq7Ootrqnr6ulr66qvbWupqGtHKapqa2xvsWsoqmoq7Kuq6mvsq2tr6+vrrGwr6uEqg+tqaSmrqympqarsLGxsLKFroCxpquprrKwq6qtqbCyrKqmrLSvqbW6vLGuqayrqKWts6usraenqq6ssM+qr7GuyaerrKekn6Okpqyrrqepp62qpqOoq6myoqmoq62ku7empaanlKqgpaKnpqCdnKGjo62qpKimqrHiqJ6ZpZ+ppKmfmp2cmKOfn56hnainp6Gio4ChxJibm5ivn5eToJOimZidpZiXnJ2anZ+bmJeWkJGPk5uXlZqPkJKUmJmVi4mNkZKUlJCRkY+ooZCck4yKj42Gi42OkJWWkpSLi4+OhpCJiIuSjJarlomLkY+tjYqNiIKIhYeHhn+OhoODgsl8gYB/gn+Eo4WDfYF9fJaEe3+HgIB9iHx9e3t8f3iAgX99hoF8e3x5fn58fH18foCDg352e3iDeI12eXh5fX17eHd6hod9e397cnl7gHZ3dHl5dHjedHp5fHp4eO7p5aXld3p55YB6e3+BiX/u8+7y8ff19X5+eoV5e3t5fex9d3V8eXh+eXZ1dYF9fIN/fnx6fn2A84D07/D+g4TsfHx7f3967el7eX19e3zy6H5/fHp5gYF59YB89n6lg32CjIZ6ee98gYCEhYh7fX+Dg4bu/ICAhH6BgIGGgnx9f317hHp6gICDgX17foOEgoKEgIWFgoqDhYaGjIOFk4iIf31/hZuVhIV/gYSLgn+BhIaJh4aEhYiUhEyHgIWQkI6SiomNjIyNjouKjbWNkpGVjY6RkpKQj5KOh4qMjY6PjYKLjZGLiYyOiIiOjpGelZGjj46QlpKTjpOSkJSVlJiUma+nxJaXgKuqrKymo6qnoKar1KisrayoqLWrpqSpp6KzqqyopKGhn52hpaCho6imoqSrrKeqp6Onoa6zq6unqayqrqKip6etq6CqsrCnr667ta6sr66trKyvvLeyo7CpqaqtrretrKalqaWooqarm7CsrairsbOosK2tpqenp6ilr62wtK+zgLW0trK2sLOpsbKwsKitsqyytLLJtqSxtLGvrKShp7Soq7C1sK+ssre0r6+ur7CwrbOvrLSsqKekr62qqauopqOjoaKpsKSzz7Wzs7O0s7e2tq+2vrWvxra7s7iys7O1tLKytK6zure9u725vLqvs7i0uLS2rK2yury2s62ttrK5gLS2tbe5tLq+vLm6us2Ht7KvuLC1tbm4rbWxo9u6srOsrbSztrOwrrazuby3trS2sK2uupuuu8G/uri0sbjR0Ki5u765trm7/b++vri7uru/wr6+usfAvsTJx8nGwL69vcC3ucK+v7q8u72ytMW8wMm8xtK1wb22xcTB1c/Iwr/PgMnMy9LU3uLHvcXEw8/Mx8TIysPFys3OztHUzsPFxcPCxru3u8S+t7u6v8LFxMnMy8jL0NHWyMvK0dXRxsDEv8fHvbi1vsW+s8LIyb25trq9u7jCy8jJycPBw8vKzf7FzMnL/cDFxcLBvcPExcvLz8TCwcfIv7vEx8TLtLi1t7u2gNDLt7q5uqPBtLu1vL65ubq8vLnIwrrAvcDN/r2uqbuywbvEvLa7vLXBvb26v7nDwr+6u7+70q6ysq7Ts6umsqWyq6eut6mnra6rsrOwrK2vp6qiq7Kuq66jpauvs7expKOnq6ytrqappKW6vay7tKmmq6Wdoaenp6qopaqenaSmgKGmoZuhqJ6qu6acnqSh7KWgpZ6an5mcmpiOn5aTkpHnjJOWk5iXnc2gmZWYkZGjlImPlYyIl4eJhYaCiYCKiYSDi4aCg4SDiYiHiYmFh4eMj4uCioeZjbmLj46LkpOPiYeIm5qLiYuHgImNk4iFhIyNhIn3gYiDh4aCgfj4+qv8gIKFg/WKhIaJipWF8fr08PL7+/iEgYCNgISFg4X5hoGBiIWGjIeGgoKMiIqOiYaEgIWAgPf78u/8iIj5gYCCiYaC+vWDhIKDg4P29YmLi4mIkI+A/4SA/Yevi4OIlY6Eg/6CiIeIjI5+fYGAfoDk+Xt8gYCDh4mNjIWKjo+JkomGgI+OkI+IhIeJioqIjIeMjImTjIqNkJaRkJqUmY6LjpSupJORiY2NlIuHioyPkZGPjpCUnI+Rio2XkY6SiISIi4qOkZCRleKUl5edlJeZlpSXl52XkY6YmpmalYaUl5qQkZSYk5KamJuon5ill5qcpaGjm56fnKOlpKiiqba52qqq/33/fY99AX6lfQF+/33TfQF+/33tfQF8h30JfHx8fXx9fX18h32IfIl9AXyWfYV8A319fIZ9gnyGfYJ8iH0EfH19fIl9AXyMfYJ8/32SfQICBACAmpudnaGkoJ2fo6GZnKKkqKenoJuanZqdoamgn6Klo56hqaienKGdsqmjp7fbvKqzubKvsri0r6ynpqWgqKGkoaawvrCtraKxo6eprKqqqZyirqWpoKWvrKOip6Cco6etu6yrsLClqaeoraGdpaOkqqakrKalpqmprrC5tK6yrquAqKyqrairrq+qsK2srq+srqqjtKino62uqrOrqKSipqCnpp+npLCqrbCyq7K1srWyrK6nrqessaSjp6yo2bOvrbStsq2wsa6vzK+wqqezwb27uL24tbzHuLKvr6+wqauqqayyq6+wtbi6u66ytamwsrS4s7euuLevsrWwqLO2rrCAsrWvs7G1s7ewsLOxvs++s7Wuubeusqu4s6+3u727vbKys7OztLKsrbS2t7Swp62orrasrrautbeysa6ztaiou7u5trCxv6Swsbq3tLKzuK6rtry0t6Wms52rr7CvqaWqqLKwq6y/q6Wita60gaeiq66npaeeoaGrp52ro5+mpr+Ar6qjqrqep6Kcn6Oln5min5qspKq2t7G1uLmuo6Ggn5edl6Clp6OipLGin6CgpaSooZ+tsbOooKSjp56ZnqCepamooaafqrKypJ6aqaWjn6Obpa6mrK6pqKaoqaehm6SdnqC1qamiqKChnZ6goa2Wmp6dl6Sml5umv6WeoqKnp52AoJeWpKSmoKS6oaWto6KnopSkj42ZmJ2bnpifpJOPi5OVnJ2ioaCep6ajmp2amZqalpmTk5CQkJqgmo+Qlp2fn5qZlpWYooiMjIuQkJqYmpGVlLShlJWOi4uKkY6PkZCalZePmpOXlZKJi5OOjIuPjoahkYeHjIiEhoqGi4yPmYqAjo+NkIuMi4yEio2Sio2GfoiKk4SBfXp+gHl4oXR7en15fX53fXt9gIJ5eH99mHt3fYOBfoKIfoGDeXqEf3uDeXyDhX2De3d/f4iCg3ryhYN8gICBfoe9Unx/e3mVe3N7e42CenqGeXl4f+2JhefteX+BfnyLf3Nyc3V2dXF1cnaAdXJxbnJ+eG51d3aCd3jmdnXi1uR5hnjb7et/8n97fIOCfXmbgn5/fIV9gYmBfH5+9fX59IB69X+BgX69eH1+e3l47Ot8f4KGgoGDh4N8gYN/foyPior9fH96hqJ4fX9+gIKA9YKAhYiLiISAiYaPgXuMhv2FgYODhIiAfICDgIkhhYWAfn97foSQhoOEk4eJh6aFhX6Dh4nQjJOLiYmNjouLhIxbjpKUkpWSjImKjo6Yk5SPlY6SjoqSlJONko2Rk4aOi4Sdlpeco5OUkpaUk5acmJaMjpaanJyhoJqgnKKcpZ+dnp2fmp2cmqSknZaan5eTk5STl52hnKGimJ+eoYCVk5WSlpuZk5ibmpaanJ+mp6ahnZ2enaCgp56dn6KhnZ+pqqCeoZyzqZycqcetlaCimZaco6Kfm5ydnZeblp2doqm0qaelnKegoqOopqSmoaSyqqiipLWto6SopZ+kpqeypqWop5+foaerp6OpqKmtqamvp6amp6epqrCtp6qopoClqKqspqqrrKWppqKjpKSioZqso6alq6+utbCuqKaooqmqoKinrqipqaqmqqmlqqaoqKamqK6yrKmwsrT4t7Cssquuq6utrbDarK6no6u4sK+tr6qnrLaspqaoqqusrautrLCpraqusLKyqKitpKmsrbCtr6mxsquusqulsLKsroCwsq6vrK6sq6ampqWvxbKorKWysq6zqrGurLCxs7C0rK6uq6urqqaoqaytrayssbG1u7S1trC2t7OxrrOxo6Cys7GtqKu4oa6vtbOuq6mvpKKss62xoaOvnauusK6pqKurtrKvrbqsqaSyr7WHqKassa2ur6qurrm1rbWtqK2ryYC0raiwxKerqqKorq6spbCqpbGmqrCvq62wtbCsq62tq6+nrrK3sK+uu6mqq6qwr7Cqpa+ytayqsK+1rqywrqqxs62prKiwv7+xrauzsq+vrqirs7C0ua2opquuqqSgqqapqsqxsKuwqayoqKyvv6iusq6os7SnqbTBrKWmoqispICrp6m0rq+srMCkp6mkpaqnobKfmqalrKmspq+3p6Cgo6empaemop+jpKGan5qcoKCcnJuYl5SRm6GckpKUnZ+im5uWnJ6nk5Wcm5ycoZ6ck5SSqqCTl5GUkpCVk5KVj5iVlo2YlJmXl5GTnJWTkJaSiqSSi4yRkJCVl5WTkZOXh4CJiYuRjIyMjoqPkJKJjYd/h4mSh4WDgIOHhIazgIWFiYKGhn+Gg4OFhn9+goCffXp/g4GAgot/fX94d4F8eX96fYSCfH55eHx8hoCAfPKBgnyCfoN/jMdigIF9fJ5+d358i4R8eoh+f3x943993OR0e3x8e41/dXV6fHx9eHl4eoB6eHd3fIZ9e36FgIqAe/V7euvk9X2He+f184X2fnx6fnp2c59+dnp2gHh6fXx4fHzv8/n2gXv5foGCgLh+gH1+f3nw8Hd6goN8fHl+fHh7fXh3hYZ9fut6gXyOrnqBg4GBgHzrfXyCgYKDfmiDhI6Cfo2G/YaCgYODhn92fX98g4CDg3+AgX+Ch5GGg4KQgoN/n3+Bfn9/hMNzioR/f4KBgYGAgIGAhYWJhYqJhoSFioiUj4yIjIaLh4iLjo6Nj4uPkYeQjoiolpebpY6PjJCPio2Qi42HiY+Sk5KVmo+Uj5KNlZKTkZGSj5COjJSVkY2PlpKRkpWVmJ2emaKglJmZmoCmoqSkqa6rqauxsKqsr7Cvs7OrpaGin6WlrqCgo6mkoKGoq6GdpJ+3raOnttu7oayyp6Onr6+tqKapqKSpoaSkp6+5rK+sn66jp6ispqiqm6Oup66orbu7r66yrqaxsbTDsbG0sqSopamuqaaur622srC0raqnq6irqrezrrSxsICytLS4sra4uLO4tbK0tre1sau3rKyrsLCwtLGtrKitrLKxp7Gxt7K0tbats7SyuLa1tbKzsbS3qaWtrKrrsrGrtbK3tru7v7/gvL6zrrnHvL27vrizuMO5tLS0t7i2tLazs7mws7K7ub29sLO5rbGztbiys6uzs66ytK6msLextIC1uLi8vb68u7i2u7fD2MS5ubO8vbi9tr++t73AwL2+tba1t7a2trKzt7e4tbKwubi7w7m6v73HyL+9tr3AsLDIysvHwMDQsLu6wL+6ubW7srHBy8TJtr/PucrLzsrGwMLAz8jGxdbEw8HRzM6Tvby+w7i4vre8vMrHv8zGwcjG8YDVyMXP6sPIw73Dy8rGv8rGwdLCxc3NyczN08rExMXGwMi/x8vKxMHE2MLCwr7GyMrEwdHY2szDxsPLwbq/v77IzMS9w7/K19XGwcDMyMfJysPL1dDa4dDLyc3R0MK7xsK/xO3Hxb7Iv8S+vsTH2sDFyMa+zdC9wMvmysDBvcLCuIC8t7nHwsW/w9i7u8K+wcjEvdO6t8PCyMbFucfNvLKwub6+wMTBvL7DxMC2uLa6u7u1tLGxsamqt725rquwu73AuLSxtbnGqaqwsLW0u7m5r7KxzryqrqakqKGrqqqtqLOytKeyqq6qqJ6hqaOioaqmpMWxpqasp6SnqaampKWtnoCgoJ+joJ2gpJ2ipKecn5iOl56wmpeUj5KZk5TBjpOSlo6Vl4+XmJyhp5uZop3Ako+QmJSMk5uPjYyGgJGIho2Gi5KSjJGJho2JlY2Kgv2HiYWKhI2Hne6Dk5eVkaqUi4+PmJKJipaLjIqO/5OQ7/qBioyLip+OgICEh4qMhoqHjoCPjYyKjJKNhIiIhZSFhPmBgPrs94CMgOr59Iz9hIWCjIiFg7yShouGkIiIjIaDhYT/+/r8hYD+goeJibWDh4eHhoL/+4CBiImFhIeMi4eMi4iHlZSJifmBiYehzoaOioiJhoT2goCGiIiJgmOHiJSEgJGG+oaGh4uLkIqFjZCLlYCTkouKioeKj5uRj42dj5CLrImIgoOJi7tWkY+IhoyKi46Lh4uNj5GVjpaYkY2QlJKempSTlY6QiIiLjY+LkIySlYyVk47PoKClrZaZlZaQj5GYk5WOkJufnZuhpp2hm5yTnpiYl5aWjpOQlJydl5ecopqZlZmam6Kkoqqsn6WoqP99/33ufQF+/33/feJ9AXyJfQF+kn0FfH19fHyffQ58fX18fHx9fX18fHx9fJR9hHwDfX18i32CfJJ9AXyMfQF8h30Bfod9AXykfQF+530CAgQAgJqbn6KepJyfoJ6jlJGpqqufnJ+oqKCkoZ+nq6Ogn52en6CkoqagpJyhp6qxtrmyuLq1wcDFvbbAs6OpraOjoYGoqamsrKupqaSlubKjrKWmrKqnr6yfqqGaoqKrq62vt7uus7a2urCrpKWvrauxqKOqp6mnpq2uraijqrCrr6+zgKmtp7Ctrq2+trius7i3trSyubWxq6ypsK20ss+osqijqa+lqKetrKurqK2ssbi3uL/DtK6up6errKeqtKaqsMW+tLCvra2op7atqrmxrqqqrbS2trm/vNfIsc6zsrW1rbG4srG6t7izsLqrr7Cwr7uvtrbcyrO3ubm6tKyzsq6vgLG4tK+uqbOtsru0t8F0vbm5xLq5srK2s7Wqs7O3t7a8uru7uLKtrqaqtrS4srGxurW0s7WsrrW2vL68wsbGvLG8s7Kxrq6puamnpKuurKaprcO4t7uu18W0sqyysKGosKmrqLCrr6yoqay0raetrKyspqqspaqhpKypqKump6SmgKqnpqzBs6ykn56boZqfpJ2eoaaiq7Kstbi6sKmpn6KhnZ2ipKWpoa9jsqamp6i2p8l0rsC1pqasp6mkoaOdpbGpw7yyr6uytbCko6qiqpuunJ+ksKGdpKOmn6uiqaOkqJ+mqaGnoqufoJ2bmZSgn5ainp+anZmcn7qCmZ2gn6aagKGkoLGjoamkpqKmpKeno6irq5+ZnqFmo5aOmLKPlZWUmaakn5+inZ+kmJupn5qTk5irk5OVi5KXuZ6ek5uXlZqZkYqLiY6Pj4mMlI6TlJGUk5KgjpOWkoqWlJqSl5WVlJBwj4GUkZOTj4qPjIaHh4SIhIR/foSGg36EioaDj5OcgJGOkIeBiomHiYSMkJGMhYSPiYuKgnyAeXh3dHh5fHuBeYGCe4CFgYN8e4KCfnp8foB/fn2Bfn6Bhnp0e4V9enhxeIB6g3l0enl5d3bg5Hp31YGDfIF8fX+Eg3h+e3h4enqMk3V2eX1+e313d3mAmYZ/e4KUjId3dpR6d3RxeGx3gGx7fnNud3V0fXZwenp/4IZ6fHV4d354d3XbdnV5fIqKfO/xfIF+fXt6dn7riX156+V9hH6A+oKCgH9+gX/lgnh3eXiCfZV+g4f4gX+FgIiKhYSOg4CBgoyCgX2BgYb0gIaBfoKPj4mGgH97f39/gvjzgICDgoeLho6IjoaFh4eJgImGiIWGgoGH1ZB/gIGGi4iUhIiIh4yAiI2Mj2uhlJOQgoSYh4WJfoWOjpKTjoqJmKCIi5CQjZKTkIuMu6GVk4+RjaKjiZObmJqUk5GdrpyZoJeWj5KOkJGnmJyYk56hn6WaoJuamqGjoaKenJ2XnJadoJuamJqUm5mSlZiVmpyfgJiYm5yZoJmZnJqclJKjpKafnJ6npJ+joZuhpqCgnpudnqGkoqSeoJqdoaOlp6ieo6OapaSooZ+poZyio6ChoXCkpqeqqKajo6GjtLChqqSmqaensK+msqqipqOppailqqufoaerrqunpKSwrq20rauwsbKtrrSxsaulqK2nq6qugKapp7Grrqm4rrCjpaaoqKWjq6mnpqakq6ewr8+kraehp6yioKSqqKypqKuqqa6vrLS4qKKnq6uvrKeus6eqsL+7s7KxtLOsrLevrs2zr6yoqa+qra2wqsO7p86rrbi2q7C2ra+8sLSurrmrrrCvr7yttLPLwq+ytLOxrqetsKysgK6zsausp62mqbCnqK9xsK6wubOxrKqsqqukqa+xsLC0sLGztLCurqamr62wrKurtLW7vLutq62prqyssra2sKSzr62urK+rvK+qq7GysKqnsLmxr7Gj1Lmsqqiwr6SttLCxrrSvsa6rqqy0sKqzrq+xrbK0rrGrrbezsbOusK6sgK+rq7HJu7arqa+tsqutrqyrra+rr7Orra+wraitp6usra+ws7K0qrd7ua+srKy3qch4rruzqKyyr7Gwr6ylqbSrw7uysayzubSrrrKttKy8rrGyvrC2saysp7OosKurr6ywr6qurLOurqyrrKq0tqq1sLCsrqmtrtyZq66tqqyggKSopritqrSrr6eqq6ymoKior6ikpqtrr6ShqsClqammq7GspqKknZyjmZupn5ycn6S6oaOil5idxqGflJqXlJuclZKTlJmdm5icn5ygnJiZl5ihlJSXnJSal5WRlpaRkJB4komZl5aZl5WdmZSTk4+Rjo6MjJKVko6TmZCOlZmegJORk4yIjYuJjouOkJCPhYSKiIqNhoWKhISIhIeJioqMhIiGgoSHg4N+foKBf319gIF/g4GAfn59hHx5f4uDg4Z+goeBg4B6fXt7eXrw9H+C7YyLgoaAfnyAgXuAfnt7f4CSm3t+fYKCfX56d3d6lH94d3yTj4t9fK9/eXx1enJ4gHF7gHl5fXt9gXt2e3x/7It5fHl+fYR9f33penZ5eISDdePkd3t6fXx7e37kioF78u5+hH9+9YF/fX19g4Ptg3x8fXuAeot+f4Hxe3d9dnx/enqFfnt9fIeBgX2Df4T0eoB+enuHhYGBfX57f4J/hPn9g4SEg4WHgYN8hHl5e35+gIKBg4OGhYSM3JODf4CEh4KKf32AfYN+gYeEhmaUjIqIfYCRhIKFfYOIiIqMh4OBjZeDhYeIhYqKh4GDo5mMi4eLhpycg4+YlZeSkY2Rn42Mk4yMiI2Pj4+dkI+Lho6Pj5SNkZCOi5KSlJWVlZiTmJSZnJaXlZiUm5qTl5qVmp2dgKWipKqkrKWoqKivnp6wsa2mo6SyqaGlo5+npqGhop6go6esqa2lp6Khp6ywr7Orr7Gls6+xqaexq6CjqaOioXGprq6wrKmkpqKkuK6bpaSjrq+wurisubGrsK2zrrOut7mrsLa4ubawqaWzs7C4rKmwsrOwrLS0tK2nrraxtra5gLG0sLq1trLBub2ytre6u7m1vLazrq+qsrC5us2tuLWvtbqvtLS2t7W1s7e0srq8u8HCtK2xsa+ysq2vtKWpsMrLwLzByMK6uMK0s8e0s66vsbq4uru9uM3Cstqztb26sra6rrO9t7u5s8Kyt7m3s8CyubjTxrK2ubi3squxtLK0gLi/vrq5tr2yvsO8vsSLxMO/yry4trW4trquuLy9vLvAvb/AwLu3uK+1vr+/uLexuLjAw8Kzs7q3u7y8xs3Nx77NxMHBvr65x7mzsbq6t6+wt8vBv8W3+NXGwsHKx7nBycHEvsjCw8C8u8DNxr7IxsXFu8DBucC2usbIxs7Kz8zOgNDLys/p1s/DwMPBx8DDycXGysrGytDFyMvKyMXJwcTEwsLIzszLvM2azcXExcnbyfGNy93Uw8TIw8nGwsG7wtDI4djOzcfL0MzCx8nE0MjdzdDS49bc1M7RytjJz8fGzMHJyMHExMvFx8HCwsHN0MHPzMvGysPCweqzt7y+vsW1gLzCvtTGxc7Cxb3DxMjIwMrL08jJxsyCz8C4xNm6wb2+xs7Kw8DDvr7DurrLvry7usPWur7Ar7W88sG+sLiwrbWxqaGipKuvrqezubO4tq6ysa63pqiqqZ+rpqikqaimpKiIqKCxsLCxqqeqqaCgoJ2jpKSgoaaqpZyhqKGfpaiwgKOjpKCdqKSjpqChpqalnZ6loqKjmpmclZSVjpOVlZWYj5iXkpecmpyYmJudmZWVlpOTlY2TjY+OlYiDi5SNiY2EjpSOlY2Ii4eIhYL6+4OC8Y2JhYiKiIiQk4aOi4mHh4aYtoSGhYyKi4mDgoSLp5KKiY+xoJuOi9KNiYmEkISNgIOUl4+JjoyJkIaAh4eO+JeBh4GGhouHh4f9hoKDh5KSgPv/hIyJi4mJh4zgloqC/faAhYKB/4OCgoSFiYf5jIWDhYKKhZuGiov+hYGJgomKhoeWi4WGhZSLjYaKh476hYmFhIqXlY2MiIiDhYWAhPX5gYCBgYeMiI2Jj4iJiouMgI2JioWKhYWP/5yJh4iRlI+diIuPjpePkJWUl5Whl5eVjo2fkY+Th4yOkZGUjoiImKWNkJSUkZiWkYuNo6SWko2TkqmrjZiinJ2QkZGXqJOPmpWUj5eWnJ6woJyZlJ2dm56TmJaSkZaUl5qYmZiRmpednZqXmJyYnZyXnqOeo6Wqv30Bfv99zn0BgP99m30Bfoh9AX7GfQJ/fpx9AX7KfQF+6n0FfHx9fXy9fQF8in0BfId9gnyMfYJ8hH0BfId9AXyLfQF8lH0BfJB9gnyqfQF+5H0CAgQAgKKcmqulo6SbmZ6lnZOerqKcpaezpJ+ooaippKCcpKCdm5ugoaWmo6Sjrq+lq6Wrr6CcvcKvt7Svr6afn6qlp6KcoaCntLuzt7Cnp7eiq6meo6Wcpaapn6GbpqS3sK2wrquprba0sqywsrGoqbGztb2lp6Woqq6vsLOwtK6ur6exgLOnqKmzpaysqq2tsrWztLOvr6yqpaerqaizq6OtsqWgrqmnpaeqq7Kws7CwtrGxtba3rrWprLG3yb+rqLCysq2nrKyBqrKtn62ssrevrKqrqqu3sru+vbvBu7q5vsq0tLO0uLSyuam0ubSxsLK0sbC5vLixsbC9tre1s7e8sr+0gLi4vLW4qbG2sr+8ztHFuKy4vrzNr7OuqqmpsK+yt7WxsKywrbitgqGvt6m1qau4uLS0r7y1u769xMTFw724urm7vbuxrKynqqiyra60sa+3truttLSzusDCwLy7ubi5ur+vuauprq2rqbWzqZilqaWqnaKdorKeopm+rKmosrWqgKyqpau5oKGbnJykpKCnr6GloKStqaSjtK6ztbOrqKCisrGlqaanq6qou7W0ta6wqbO5u8a5tLK4rLa2q6uyqq63xL20qrKyrLq/o7DBsGuqoqyuqqOuqq2vqK2muaKmtJWwq6alrMe6saWqp5ydo6KkmKGgnpmYmJSSmqKbmJiXgJ+bppq6raSnq6GqpqGjnKGpn6doqXCpoZmdl5OinJSgmJuuo6LBo6ufopiYnJqZlZmkmJuUl4uTl5OWlZG5jZKQmYaJkImKkI+QiYWOk4aGjZGZj42Sj4uPkJKPl5aXkp2ZlZuRk5KOkI2UkZWMi4SJi4+EgoN/hISBlsqIh4yhgJSTkYaQnI+IiX2OiIyMiYqShIKFfICDd397eHh+j4qHh4F6gX5+fH6DgISCg3mDgYeDgISFg3eEf31/gX6BfoGAgYiCj4V8fXR6d3Z3f3p6e3d4dXFxdnR3en14dXBudd58fXp4gevre3rsfX1+jJqFen13dn58mH5/fnt6enF3gHJxdHJucXh1dXR34nuEnqB9dXZ6dHV2dOF/f4H3hYOPiISCfo18flBxdXh153V+fH3sgH6Eh3qDg3nofX10dnl2eed5fYPbhJN+gIR9foWFiH+ChIT2hI6JhoeRi4iEgIWHgYKBgJd8gIeAhfiBgYOAgYSQioWHkpSNjImPjYWEgIOJho1/gIiEm4iCh4SGhY6JjIyJkJ6MjY2XjJCUl5WPlo2OgY2GhomKjpaRj5OTmZiPnpaYmpKTk5CVlZWRl5STlKCckZGUlJibnJ2dmp6unJeKkI+Tjo6ZkZaal5uin5ublqCglpufnqelnZ6fpKadn6CdoaCfoaCdpaKio5emgJmUlqKen6KYmJ+knpWdq5+eoqKtn56joqWnpaOipqWjoJ+lpqSmoqCfo6OcoJ2ippuYrbSjp6inqKminKWjq6mhqaetuLmvsKmloq+gqqunrq+osLK8rq6mqqi4rqqtq6mkp66sq6SpraioqK+xtcGrrqusq66rrauoq6eoqaevEbKrsLC4ra6wq6uqqauoqqmlhKSApqqqqLWup662qKWxq6qmqauqr6yxq6qrqqqvsbKrr6mqsbnWv6ysrquxsaqxr26rtK+lrq+0ubiwsK6srLCssK+trLSxrq2uxK2tq66usK+zqrC2sq6sr7GrqrK3t7KwsLy1t7KwsLGqtKqsrbCsrqassK22sr7Btqulr7SyxKuAr6mnqamxrq6ys7S0s7SxubSKqLW5rbWurLG0sbGvuLC0s663sra0rqqsrLG0sKusrairrLGysLWzsrW5uqmtqKausbCurq6wsLC2wrK8srCztLGwuraun6+xsLWrsq+zwLCzqtC4sa+4ubOyrqy6y7Cyq6+wtbazub2yta+0uLSAsa24sre3sq2tqa+9uKyyrqmtqKizsrGzrrGstLu9y7qzsrestbWtqbCpsLbCu7KssLCquMClsMKwbbCqsq6uqbKwsKmrsqvDqKq6lLGsqqauwruxqq2wq6q1rrGps7WyrKmrqKWqsqmppqitqbOmxLOrrK6lqamjp6Snrqepf6mAgLCqpq2oprKupa2joa+jobueo56lm52goKOio7KmraKomJ2jnp+imsCVmpuklpqjnZ6io6OdmJ2gk5OXnKadmp6emJiYkpKXl5KQlpOSmpKTlZSUk5mXmZOVkJSXnJKPkI+Vk5Om4JOQl6SVkZKKl6SWj5OLlY6LjYyOlIeHjYeAjI6EioiEhYmXkoyLhn+HgYB/goqDg4GEe4J/gn59gH99c359f4GAgIB/hH9+hH6Ggnt8dXp7eXyDfIGAgIB9fH58fH+Bg398dnZ884OEfXp96OZ+fed2dXWBkH16fHl7hIKZfH56e3p9enx6e359enh9ent7e+x9hZqne3t7gHyAfXx57YB7fOt7e4J9eHd1hHiAa3F2eXfodHt5euR8eoCDeX+AeeZ+f3d5gH2A836Ag8mBjXp6fXl4gYGBfH5+f/SDi4eEgIqCgXx4f4B+gIGBlHx/g36C/oGCgn+BhIyGhYSKiISDg4GCfn2Bh4WNhISLiKOLgoWBgoGJgoSGgodrkoSCgYZ+gYWJiIiOh4mAiYWFhYOHjIeFh4WLi4aQiYqNiYuJhoiKjYeMiYuLlpGHiY2OjZOVkZSRk56Sj4eOi5WRj5GKjYyMkZWRj4+Mk5KHjpKTnJuWlpWdnZecmZiempmcmJafnZudlJ+AopmcraapraWiq7Gon6e2pqSpp7Oho6Wipqijop6loaGcn6SoqKqoqaausaiuqrKzqJ+6wLG2s6yppZqYpaKnpJ2np666vLO1qqaltaOwsKmvr6ixsrqur6iwrMK6uby6uLi7wby4s7O0rairr7K1ya60sLKytre4trC5s7O1rreAu7GxtLuvtLextba4vbzAv7m5srCrra2rp7etorC7rqq0ra2ssbCxt7a7trO0srC2treutrCxt7zdxrCusLK2trW7umW7vreptbO5vbq1s7Swsbi0u7e4tb27uLe4y7W2tLW3u7m/uL/Gwru5v7+1sri+u7OwrrqxtLCws7awvrWAubq9ubiutLm2wLvIz7y4rbjAvcm1u7Gur7C8uLrCwr69uLe2w8Gisb3Ds72zs76/vLe1wLm+vrzCwMfFwr7Dxs7QzcbDwr++vMPBwMTCvsbIzrzEw7/Kz83Ny8jFxsXK1MHKwL7Cw8TD0c7FssLGwca4wLrB0by+tejKycjU2NCA0M7J0uHExL3CxMrJxcvNv8a+w8vLxcXUzdPUzcTEvsnb2MnMxsPHxMbV0tDQyM7F0tna5tfOzM7D0NLGxM7F0Nfp2c3CysfA0dm/y+HShNXO19PUz9bS0MnL08fiwcTYm8/HwsDK5drOxMjIw8TQys3C0NDOyMLDvLe+w7WxsLeAwcDOweHLxMbKwcrIwcG9ytLM0a3Su9bMwszEwNLOwc7Dw9bGw9/AxLi+tbi3tre2uMm7wrq+sbvBvL27tN6usKyynqeyqquwsrSvpqqtnp6kp66lo6uppqqsrKu0r62qsa+rtKqtrqmppqyprqOinamwt6alo56koJ+226CcorWApqWooq7EsKero6+mpKWjo6WXlpuRmZuQmJWQkJmqop6emY+Zlpiam6efoJ+fkJiWl5SPkZWTh5SSlJOTkI+NkYuOk5CbkouLgoiHhYiPho2Mi4yIh4mGh42OkIuKg4CG/oqMiYGF9/eHh/6Eg4aUno6Lj4yLko2nioqHioqRjI+AjY6PjomHjIiHhoj/ho+psoOCgoeDhISB+IiFhf+JiJKNiImHmoqQhYKGiIT9gImGhfeEg4yMgomLhfyKiYCBiYKF+YOGi9uIl4GEiIGDjIqOhoaGhPSDj4qLjJeNi4mGjo+Lj5CQp4uNkYiI/n+Af3p7gIiDhIWMjoqIiYuIgYNEg42Jj4aFjoypjomNiouJj42Pk4+Wq5eSjZSOlJKWk5Wck5WMlpKSko+TmZKPjouQkYyYkZOYlJaVlJaZnJWbmZmapqCElziUlpmTlpSdsp+elp6bqKCdopuclpeboJyZmZCYlYiOk5SfoJuanqOmnqGhnqalpaegnqmlpKSVov99xH0BfuJ9AX7/fah9AX6RfQF+sn0Dfn1+/33LfQF8hX0FfHx9fXygfQF8jH0FfH19fXyKfQF+hH0BfIR9AXyIfQF8h30BfJJ9AXyWfQF8/32UfQICBACApKKd6K2dlp2hmZ2bm5qen56hnJ+hYpunqKmkoqCkoZ6em56gp6Glp6mnsLWvoqyun6m2tq2zqa+woKeqqZ2joKappK2rr7SytKyurqiopKTXpqScpqumpaStq7GsrK2vuKSxqLO5vb65wbe/urW0rK+2trm4sry/ur25ur3AurOAtKyqraumqKynq7K4sLS0rbC2tbG2rbK0raqtrK63sKmvrrCsqqmmrLa6qbS2sr2vsrizqaOjq7ays6qoqqqrr7KyrquqtK6ys7ewsq+forGrqbW2srnBwb+6vsDEtLO8s723vLevsLW6ubu+ub+9ube8urm4tLW2u8Wvube6traAwL+9uL2+u7W8usC/vsG/wcnRx7uysrGzqbG6trqyrK+2srqgpq6psay6rayprri4vbi6w8LCvcTCx8XJydDFvru/uLq2tbq1urWxs7G0vKynqqiurrGuqLW7tLWwtrayt7Gprq6rrK+qoqWor7CurbKorbusrqWrpqqopqSuqcCAoKKjoqWjn6iqnpieo56orae2wa+xrq2sr6mir76qtMysqa2xwG60qqy5s7yys7SzrrO3t620uLKzqKyqpa2qqLO+vbmxr6+sr6aotrO0ureopq+/w7elsampq7ezrqy2s6qqqayw0sXBqL2rpaeurqifoJ6coZ6ioZudop+XnqWAqqepoKubnZ+Wn6mpp6KuqKOomJqnopygn5+UnJuRkqudnaqupaWcoJydp5SVopiVoJ+UnpiXk5WWlpaip5uPlY+RhoyLi5iWjo2RjYySlJSXmZqhpZ6YmpiKmJSXkpqVm52Vj5qXhY2HjZCSjo6OkY+IjYuJgoeJkYaDhouLjoeAgoeEeXuBhWeHgIx9jI2KhYqLlYeLjomIm4uBfYyFh4yKgouCi4B/i3l+koGCg3+Cf4J+e4CDeoeCgIaDhIqCgIKFhIOHg3+Be4KKg3N45Hd8dn+Bg52Sl4B6cm1ycXJsb3F4fXR2e352fnuHgI2DeYh5dH2AfYf/gn2BeXJyeXWAd3l8bXVxc3V2eHp9fYSfe3eBd3l7fXt8dHh7e3+ChJLuf393d319c3t3d3J733h6eH/98I6BgIHy8vJ6fXrtg3h87n1/9H74gfWKioSLh4SIhHqCgomFgYWPiImJi4iGgoSGhI+HhH1+goiDhoOEf4KGgIuPiYuZiIqHkIyGhIiAhYSHjZipi4uLioyHi5WQiImGjYuMkI+Th4mLjY2HhIGAgXx9goB/ho6Fi4WLi4yCl5SbkJeVmJKXk4+UkJGTj42Kl4qNk4mcnJydjJSVoJeYlpORkI+SjoyRmZWZmqGgm56VprCcmrKiq6qysaSjo6CqpLKqpJ+hoaGjqZ+joaVxmZqY3amamJqfmqCenJyhoKSjnp+fcJmlpaShoKKloJ+hoKOkqaKio6Wjqa6nmaCflJ2oq6SmpKWonqSnq6Spp6yvqLOvqqqqrqyvsKusqqvrr66pr7Opqqasqa2qqamrsp+soaqsq6ipqqavrauppKWEq4Coq62qqamqq7Cuq62prKytpqippaeqr6apq6Snq62qr6mvsaqrrKysuK+rsK2xr66wqq+1uKerrqq4r6+0r6uqrbK2trWtqamprLKzt7CqqrKxtLi/uLu4rK65sauztq+ytrGyrbGysqamsayzsbSxp6+tsK+trq6zr66ssLK1sYCws7K5x6mxrbCqqbKxsKqtsayprKyurq+0r7G6v7exqa2sr6iwura6tK+0trO4oqq2sbeturCysbKztLeysb23trCzs7Wzt7e+tbCwt62urayurbKsr7Kyu8OxtbqytraxtK+0tbG0usS2tbi3sre1tLW4tayusre6trS3sLLAsoC1sLaytbGwsLizwqiop6mrrq+4v7myu7+2v8a3vMu7u7i4tryzrrrGsrrQrqqzt8tvr6aqsrG5s7O2tbK0t7iwtriytKqxr6mvrKmwuLizrautpaikqLSxtb6+sa+1yc7ArrasrLC1sq2rtrasqqutrszBvau6sK6xuce0r7W0r4C0rrWzrK+wsKqvuLq3s6q2qayso6uysaykraWlraCkr6mlqKirpbCwp6a8rquxr6epoqGjoqucnKehn6Wknqqio56dnZ6fpaujm6Cen5ydoKGrp6GhoZ2anp6cm5ycoqOdmp6fkpuXmpablpealZOcm5CSkZOWmJKSkJOSj5KSkICPk5iclo6Pk46QiomNjoiOk5d7n5SXiJGPj4iMjJWKjZGPjp+QhoOOiImJioOLgYmBgIt/fo2BgYWCgn2Cf31+gHSBfX6GgYSKgIOEhoSAgIB+fnp9hYR5e+1/g3yCgISXkZOCf3p6fn+BfXx8gYJ/fH9+eHpze3SBf32dfHd8e4B2get/fYF9eHd/e3p8gXR+eHp9f32Bf3uCj3l0enZ4eXx7fXp9fXt9e36J5Hd8d3mBh3+CgoF6g/F9fn2A/vGMgoCA8/f8gIWC/YyChf2Chft+9n/rgIB6f398gIB5fICFgH1/iX9/gYF+f3x/fnuFf39+g4mNiYuJhoKFh4GJi4CEh5OCgYSNh398f36Bg4qYq4mKi4eJgoKMhoKCgYWBgoOGiYKEiIqPioeKhoiEhYmJhoyRiYyJjIqJfpGNkYeLiY6LjYqFjIyPjoyLhpGGh42GlZGUmImPkJKMjpKQkZCPlZGQlZuSkpGTjY2OhpSej46hlJybpKOVmZaVm5minguamJqcmZykl5ubnICjop3/sKGdpqelqqmopaqprKymqKiBoa2urqqpqa+qqKmpqq+xrKywsbG5v7iqtLOlrri5rrGqq62gqK+wpammq6+msK2ur6+1s7WzsrGrrdqvrqmxubCyrreyu7e4uLrBrristLWzsbKyr7q1tLKusrm8u7q3vcG9vLy+v8K9t4C6srKyrqisraissbiytrixsra4trqwtLavsLGvrr+1sLCtr6+trKetusCwuby7x766vrewra6zubi3sK+vr7O2vsC6s7TAtrq7wLu7t6uwu7Outbawsru3t7O4ur2wr7y6vrvCv7e+vb2+vr23vLazrrOytrOytrfAyrS6ury2t4DAwMG7wMG+usDAwsPCxcXFzNLFubKxr7WqssS9xb64wcTBx6+3wr3Dusu7ura2tbe5trnFurq1trm9vsTL1c3JyNDFxsTCyMTIwsLHxMrQv77Bu77BvsK8xcrDxsbPxsHGwrvAwb6/x8O5v8PJzM3Mz8jH08rKwsbBxMLCwcnE4IC9wMHDx8jG0tbNw83RytTfy9Lex8nJy8fPycPS59Dc+MzGztXyiNDDxtDO187P1dPP0tfWzNPWzs/GysrEzcfHztrX08rJycPHwcfV1Nzi49TO2fH15M7Yzc/R19PKytbVycfGyMzy4uHJ3c/N0dfg0sfNy8fNyM/NwsTCw7m+yoDPz8/F1sfIx7vAzMnMxNHKydPEytjPytDO0cTR0cPC48/O1tXJyb2+urvFtrPDvbvFxrnGwr+7u7+9v8jOw7i9uLmus7WzwbuytLaurK6vrK6urLW4srK4uau6try3vbW7vLOtuLSgpaOoqKulpKOopqCqp6mkqKmqn5mcnZ2hn4CfpqOhqK+yhbGoq5ijo5+YnJ+pnJ6koaK1o5mUoJqZm5uUoJefl5WilZWnlpaYlZaOk5KPlZqMmZeXnJeWmJOUlJqYlZeVkpOMj5eQgYP5hYeBh4eRiqGnlpOMh4yKiYGBgYuRhYiLi4SIgYuBlZCMvIuIioeAhfuLiY+KhoSOi4CMjpSCi4ODhYSCgYOEi6aEgYuDhIeIh4uChYWDhYSHlveEiIOIj5OKkI+OhIr7g4WAhP3wioF/fe/w932CgfmLgIX7g4b8gPh/74eKg4uJhYyQhIiKj4yIiJCGiIyPjIqJi4+OmY+MhIaKjomLh4KChIiDi42GiZaEhIWTjH9/g4CAg4GIlq+Ih4qEiYKEj4qGiIaOh4qPkJCCi4yRk4+LjYuMiImNjoySmY+Uj5GMi3+Tj5WJj4+Sj5WTi5WUlpyYlpOflJWYi52XlZiIj4+bl5iZl5ycmqScmZ2moJ6fop6Zm5GeqpeVqZiioK6tn6CenaeirqakoKOinqWmnqOgo5V9AX7/ff99/32SfQF+/33ifQF+xn0BfIZ9AX6hfQF8qH0BfIx9AXyEfYJ8hH0SfHx8fX19fH19fXx9fXx9fH18/323fQICBACApaCenqKhpp6YmZucoKOho6CfnqGjoabVpamhqaeyqKKgoqiiqKCoq66wr7Cwpq64q6iqqrCyqLGsqampr6mtqrSoqrKvs7C0rLSxs6ysqMNvrayqqq6wrKq7trK6ubG5tbTFv9i6u7q5uLS0rr+/t7extb2+wMa5t7O7r7i5v7SArs2ioam0sKy3tLK8tLa8tq+wpbDFtbSwsq6yr7O1tLSxsry0taexsLSxtrWutbm0u7C5rKejqrWzqaqrsbWsqK+xsK6wtLWzsb+hrKyopKuptLe5tLbFwsrCv8bCv8CzwLy6srHCtrq4tLy6xr/DwMS+uLe+v7q5wrvBtbu1wL6Av8G+w4bSycO7wrm+wr3Ewba9wsO9t6+3v7K3wbq6s7S2srK2r7K2sLu0uauxvLewrLO9vri4srHtwuG/wby2vb/Au7y1r6+0q6qwqrC4r7GzqKqprrKws7a+u8i8xbK3vbSxrLOws6+wuLu0uri5rObPqbGyr7CwsKuvrqu0tq2Aq7Cwpq2prbBvpKGipp+dpp6orbOrpa3Vc62usKapqaB4pqy0wLm+uLKzt7OtprCrpaaop6atqaKrsamopKuwqrCxqrOtrqiwsKupqLOxjrSoqKerrrCwsK+vq7S8rrW5sraunaiqr62vpqmstrWnsKqqo6agnaign6Gmq6Sop6aApJmfmqCdoZ2gpKqenKOeoKahsbypqp2doaKboa2UoKWfm7GonJ6OorilrqufqaGboJqdl3mXnpSYlpOalpqfm6KemZuUk5+YlZuZk5SOjY6Wko+Xn5WXm5aXnJKUkJCNlpuUlZaXmZqOiYqLiY2Qko2RnI6QlZWUkY6NgYuIf4CAi4uFhXp9fH+Fg4OWhYqQmYmIlJaJg4yNoo+Dg4KFhICRgoCDh3uFhomJiIaBoISBg358f4mJgraFeXd+jImBfoR/gIGHiYeCg4uDgHx8eICNhoGBfnmAgHd3cHdzb2xw0WxudXd7fIJ8he93gIWBhIKFf3yEfHSBiIF/k4N7f3yAen99gnboe3+CgY2DgIaCgoKKh4CBfH19gH6GhIOIh4eCgH53eISAdXNweHfofH55gYP5f4KF+4aHgIGFf4Z/fH95fHv5goeEgYCLiIeA/4+KhYeJg359g4SEg4aLipKPiZCHhn+HgXt+gYSKg4SRiYWGgoeLhfuGgYeUi5SOso2Ai5KDg4WKi42HiI2LgICNjZKOkI6Qjo+MipGIhomRj5GFg4COiYR/iI+PjI+Nko2XmJOLjouYj5KTj5eOjpKUkpWJjJCVlZablZiqq5Kdl5SXkZWMhJCtlI6Tl5CinpmdpKKcqaKmoKminKCfnqKbmp+imLScm5ymk5ydnZygoa2AnJqamKCfoJybm5+en6WjpKSin6Cjn6PJoqWhpqSwp6OhpamkqKGlpaempaSlmqSsoZ2en6Skn6impaWor6arpbCnrrKurqyvrbSwsaytq8uFs66sqq2uqaayr6ivrqitpKSyqcKnpqWmqKWnoK2uq6mjpaussbWqqqSwp7O2wLeAtNusqrC3sKivq6aopaappqSknaq8r6+rrKiqqa2xs7Owr7iwrqiwsLGytbKtsrKwtqy2rqyqsrq9uLWurrGzrrK3s66vsrK2ucquuLi0sLSwtba4sa64tbq2tLi1ubiyura5s7G+tbe5sri1wLq/u7u1srK0tLCxubG2rbOwtLF0s7Sxs4+/t7Krt6uvtLC1s6istrixr62yvK62wru3tbe9t7O1r7Gxq7WwtayvtrOxrrK0tbW0r63hvevAt7GqsK6xrLCtq6uzsa+9t7/Lwb7FtbaytLKzs7Gzsru3xay0t7O0rbGwsq+wtbmzubi3r9fHr7WEtoC8srOxr7W3rqqvsa20sri8erm1ur+3tr6zube0rq+25qe5t761trarfayys7+5uLGur7e3s7C6tra3tre1vLmwtbyzsKqxt7C4trO4s7KssrOvr664upXAtLSzuLu7u7Sysq6yua+1uLa2sqiytLiztrKzs7u5rrW0trO2rqy3sYCzs7O1sLGxsa+mrq2zsrSur7C5sKquqKiup7G9rbCnqrC0r7TBpa6vq6a3r6Oll6HCo6uooKaioKihqqmKqK6ipKKgo6CkqqSspJ+inZmkoKCmo6CjnJ2fop+cpKqcnqKfnqGYm5mXk5qdlpmXmJqclJOSk5SWlpWPkZ2Sk5aXmYCXlpWMkI+IjJeVk5iMj42Kjo2PnoyQlJmKiJOVi4WQkqeTiYqJi4yFmImGhYh7gYOIhIGBgaKGgn99fH6FhX2ugHp3fIyGfH1/ent3e4KBfX6IgIB/fXl/jIN8fHp2fHx5eXmBe3x8gvB/fX+CgoCEfYDndX5/fX5+f316f3p0e4CAeXiDe3h9fXuCfoV883p7fnmPfHt+e3x7gIR/gHp9fYB+f35+gYKBfHt8eXuIin+GfoF+/IOCgY6N/oGCg/yBgn6AgYCHgoKFgYKB/oSGgn14gn9/efSLhYCDg4R8fH98e31+gX+FgXyAfoKBiYSBg4SGiYOFkIiDg4CEh4L4hIB8gI6IjoWsg4KKf3yCgoaJhIaJiX2AioaLioiGiYeMiIqQiYaOk4+Vi4mHkouLhYqNjImMiIuHj5SOhomFlIyOjIuQi4uQj5COhoqOj46NkY6SoKGNlI6MkI2VjY2XyqeWkpOSmJGPjpSSjZeSlJOclpOVk5eWlpeeo525oJ6grAiZm52emZ2eq4CppaOjqaivqaOlpqerq6iop6OkpqelqManq6auqrqvqKissa6zrLS4ur67uruuuMGxq6ioq6qhqaWjpKawp66pt66zt7OztLmzvLi4sbSx4a25tLOvsbSxrby6tb6+ub+4use+3ra1srSxr66quLaxtKyyuLm+xba1rrmvubq/uYC156imqrexpq+tqbCvs7a0sbKtuNG6uLO2sbKxtri5uLSxurKup7GvtLi7uLS7vLrCt8K2rauxur22s62ys7OuuMG9t73Av726zKu1tK2sra63ub+5tsXCxsPAxcPDw7vGwcO7uMi/wMC7vrvIwcbBwr64uby9uLnCvMK2vbe/v4DCwcLFodPMxsHJvcLDw8vIu8HIzcS7srm9r77GwLy3usG/wsi+wcK7ycPJvb7Fu7Sssri3trWxruPD7s3KxL/ExcrBxL26usG7usjCytXJyM2/v7zCwsDCwcjG0MXTvsTKv8K+xcTHwMXM0crQ0NDE6Nu/xcnJysrZyM3Nx87QyIDGy83Bzs3W2I/TzM/SysfUxc7Q0MbCx/yQzc/Xz8/RxYjJ1Nbl3NrUz9HX08zH083Jy83Ny9HPxczUyMXBys3Hz87H0svLws7OzM3J19qo3dDPzdXY2dnT09bQ19/S2tzY18/Bzs7T0NHIzM3a2MvT0tTR1cvF08fIx8vLys3Oz4DOwcnIzs3Tzs7O18vGzsjI1s7d6tPVycrQ1MnQ3sHO0svE3NTFyLXE48XNzL7Gv73BvsC7lb/IvMC9usG6v8fAyMC4u7ezvre1vbu1tamsrrWurLjBtbq/ury8sra0sKq3uLK0s7Oytaqmpaekp6mppKeypKSqqqunpKGXnqGdooCztbG0paWfn6WjpruipqqwnJqkqp+YpKe/p5mamZuak6WZl5ebkZyfpaGdmpOvlpOPioqOmpyTzZiPipGfmZKPkY2MjY6Tko+PmZGQjYuHjp+Rh4aDgYaHg4WDjomIh4j9g4KFh4yLk42S/YGLjYyMiIiEg4qFgI6SjY2dlI+TkICKjomOgf2DhoiHkoiFh4ODg4mMhYmEhoWDgoWFhomKjYaHhoGGlI+ElIOHg/6EhoKNi/p+f4DwfX98fYF+iISDhH+AfvyBhIB7d4KAgXz1mJKKjY+LhIWHgYKChomHj42Kk4qJhI2JhIeJiY6HiZaNh4eDiIiE+oN7gZLllYiuhYCEj4KDiIqNjoiHjYp+gY2Iko2Ni46PlI2NmI6LkpaSmIuIhJOOjIaPk5WSlZCRjJaXkYWIhZaLkI+Nl4yOlJaWlo6QmpuZmZ2WmrO2kZiOkZiXoJaUntqxoKGkvLenoqClnpaim5+epp6coJ2inpydpqujyKaprMChpKemoqakttB9AX7/fbR9AX7/fYR9AX6PfQGAh30Bfq19AX7lfQF+/32sfQF8iX0BfJp9AXymfQF8hX0FfH19fXyNfQF8iX0BfKd9AXyEfQF/330BfqR9AgIEAICspqGkn6CfoJqfnZydqKKgo6alqamin56jpqCdnqSjmqWdoaKlt6ansbO0sK2zsbC0r8i4rrGwtrS3pM62qqOgoKeoqaywq7WwtLKvpbOmpmGrkYSkrqusrMG3t7e8rryztre7ub6/trWxt7jAv8DAud7Bwrm+vLaxubi3y7K4toCwrraptLWxsMG0s7G0wr+7yszTsLWvr7istbCrtK69rbS627ap3r62urOzuLKuu62utr2zsaeyyb5+uLCutbq4ubOwtbK0u6+wsayut7Cwt62xsrnAucS/wcLDxcfPysPCv8HEvMC6vbvAvbu/w8mMvbS8u73Fwr+6zr6+wLq9woDHy8zDwsvGysnLzcjEw8G9ur69wr2yv7+5try2uLW7v8G9usG3tLKvsrm1v7u6t7/Durjut7e6vcC/xcjAxba8vLy3uLaxs6urra6wsrCtr7qrp6uzsK+4t7a5urOss6uqq622wLWzubK6sLa3rLimrKqqprKys665ramprbSxt4CxubS0sbetq7GuqamnqZ6hnqKvq6q3wbnBta6hq6qkr6qxtrK5w7Kyqa2hqbOpp6egoKmmoq6rprOytKmwqaumtKysrK20rbeytrGqqqilo6OoqZ6hpKC1rLumrq+7tLKps1O8n6Sjppyms6qrqqqpqqyrr6uoqqqnoqaosaSks4C9pKOioKWun5mVmpefnZ6cmqiroKelpZucsaCjo6CfmJiXmqCqoZydoZutp6OfpKSemaabnpqflqWUmKOynJ2cjpiQk5aZpJuQkZGPl5iZl5aNkJ2ZmZ6dlqaakJ6Vi4mOkY+KkpCRkZaZm4+SjZGNkoGNkpaRlJSMlZKHioiHiICKh42Qh3+DiIWMmpCLiouHi4aKh4eJi4aGjomPpoh9jYF+fYyIfIF7eYmPhYmKhI6KgYuAhYeNqoSFg4R+h4d/g399g4R9enyJiYaEgIuDgIV+hYF+f3+Dhn11hH1zd3xwenZ3gel5gIOeiYR9hH1+dX5/e36M8oOQgYKmhIONfoB/eXqHhomEgv79hIqGiIaAfX6fk4OBgnx4fImB4Xp7e4B1eXh3f4iGl4iFgoaGgYiHnID7m4+DjIqBgYWCh4WBhoOGhIKBgo2CiYmLhISFgIB/g4iKgouGjYGGiYyQjJGUjISDhYeOh4aPiIeGjIiJhYSFiImJiYyPjoyRjYeNjoCTj5CKhIKJiI2Lj5WJiJKWkpSJhIiNjIuGjIyDhI+3o4qEiIWIh4iIko2MjpGSjouPkJCRkJaQkZGQlJKRnYualYyKjpKPj5Sam5eek5uSkpWHjomMk5eYnpqZYKWjqKqjoKqiop6gnqSspJ+en5ucoJyZpKannKCgpqOcnKenp4Cjn56dm52cn5ydnZ2bp6Kfo6Olp6iioqCnqKejo6mro6ukp6eotaCfpaSmo6Clp6etp8KvpKenrq6zosq7sa2oqrKwrquwrrOssbKwqrarrYGxgYKkq6mpp7ewrqutoq2ipqesq7KyqqmqrK2xrq+wqMeysauztausr7CxwbC4toC0tLywuLWzrryuqKSmsa6uur7YrbCtsbetsq6qsau4rbG42bap9ry4vLe1t7exu7KttL20sKux2cJ4vLOutbe2tbCwr7O2vLK0tLO0vba1t6+tsre6s7i1s7KytLW9uLOxr7a3s7W0tLW6s7e4vcGBtq22tbW9urm0zra1t7Ows4C1trexsbW0uLa4t7Sxs7Sysre5u7ewu7q2tbq3urW5uby4tr22tLKtr7O0tK+srrS4s7j3t7e6u727vb+3w662tbi2trayta+ys7m5vb27ucW2r7C1sLC3tLK3ubWyubGtsLK6wLeytrO7sbi8s8Gxt7W1sbu6urbGt6+tsbSwtoCwtrKysLmztLm7tbW2trC0sLW+tbXCyb/Lubiwt7awtrS8vLW2urCxrrOst7+4uLezsbe4tMK/tMDAwbe9s7axvbi2sbS4sbizuLOytLS1tLi9wLW4urTEucStr7G6t7OvtmPGr7S0tKy5wLa4tra2s7Oztq6ttbWzr7Czu66wvYDBrKytrbG6r62us7GzramqqLG3rrCurKepu66ysrGtqKqqq6yxp6Kfop2rp6SgpaimoK+oqaWnn6qeoqeypaepnaWdo6WnrqadoaCgpqeoqKGXmKKdnp6dl6mglqadl5Wcop+cq5+hn56inpaXkJeVkoeRkpSRlJWQlZGIi4yRkoCVkpaXj4mOlZGUnZSOjpGLjYuNiYiHiYeFjImRqJCEm4WBgo6IfIOBgo6Ph4iNiI2HgImAhIaIrIGDgYN7gIJ8gX59f397fH6Eg4CAgYZ7eYN9f359e3p7f3l2gn14fYR3gnx+hPB6hIaTgX56f3t8eYB6eHSB5X2He3qIeICMgoB/eHiCgox+eerpen9+fnx5eXyZk4OAgn16gJCJ8YGBgIR8e3p4e4GBuo9+en+Be4OEmnvrjIR5gIB7e359gYB/gYCEfXx7eYF6fn+Afn6BgIOEhoaKf4SBhn2BgYCBe4OHhIGEhYaKh4aOhoaDhoOGg4OEiYqJhIqJh4WKiYKHh4CLiYyFgH6ChYeEhYx+f4eKh4uIg4eNjImFjYuFh4+3pY6HioiIh4mHjo2JjY+PjImRkI+PjpOPkI6OkJGQm427ko2Ojo+Mj5CRlIuVjpKPkJWPl5OZm5qbnJiTa56TmpySkpiVl5WWkpeemZiXnZyanZqbm6GhlZqbnpuZmaKloYCuq6aopaeprKSop6akq6ikqamqrKyopaOsrayqqbKzqrKrra+xv6ursrW2s7G4tLa4s8m5r7GwtK+yos+7r6qoqrK0srS4sbu2uru5s8CztJ7AfYiut7CzsMO6ubm9tL2vuLvBvsPAuLSusrG1trS2seC+vri+wbW2uLm4xrG3uIC1tb2vubq3ssKyr6ywvbu5yM3bub+7vMa7w7y2vbTNsre937ek5Lu5vLazubqzwbWzu8S7uLK548aAu7Ktt7zAwsDBvbu/xbOztLK2v7e5v7W0uL3BusO+vrq7vsDIw76/vcLEvMPBwMDFvsHCyM6Ov7jAvsHJxcS/38K/wLe2vIC+v8K5usC/wcHFx8PBx8nEw8nJzce6xMW8vL64vLjAv8bCw8rExsXExMfAw725vMLEvLf8ube6vcLBxcrE1cDFyMzGx8bCyMLExsrLzczIx9C/urzFwcDNy8vP0cnDz8PAwcDL0snFx8TPx87UydvIzsrIwdHNzMffx8TEydHO1oDQ19XW09zV09vd1NbU0sbHwcTNx8fY4dzs0c3Fy8/Q2dbf4dre5tfZ0tXG1N3U0s7Hx87OyNTTy9rY2MvUy8vG1MvP09Pa0t3X3dfQ0NPQ0NPW2MvOzsrd0NjJzdHg29nR24v0xcbFx77J1MzQ0dLR0dPX29XR1tjTzc3O1sLD2IDlzNDT0dbfzsnHzcrOysfFyNDc0NHOzMXG4MzO0c/NxsnFyszUy8G/xsDQzcfCysvDwc/Axb/EuMm3vMbSv8TIsrywt7q6wrirsLGusrG1tK2io7Kwtbm6tM2/tsa6rqissa2ntausrbC2s6yrp6upqZ+oq6ynqamgp6WbnJ+oq4CwqrKxqKKpsKuxurGpp6efpJ+jnZ2bn5ubpaKq0Kaazp2am6ejl52Wl6aqnJ2cmKCckZyTmJyixJiXkZCLjZGLjYuKjo2HhYeRjomIho6Eg4yIjIyOkIyQlIuFkYuDiZCDjYqKjf+Ci5G5kYqDiIOEg4qHhIKQ9o6hk5Gcj5ahkYCNhICNipOGgfb3hIqJiYeEg4aaloyKjIeDhpqV94mJiY2DhYOChYyJ2JuFgYWGgoqJp3/ykol6hYR+fX58g4CAgoGEgYB+fYV9gYGCf358e4CEiIuOipCJj4KEh4mNhYmOjYaHioyRjIiPh4iJjYqMiYeIjY6LiI2MioaKiYKJjYCWkpKMhISGhoqFiI2AgIuOjI2Lho2Vko2Gko+HjJC0qJSMkJCSkpSUnJqVmZuYk4yUlJKUk5iTlJKSlZOWpJPVoJqYmJiSk5aTlIqSi5ORkpiOmZafpamqrqekh8miqaqenqSfo6KnpKirpqOjo6GfpaWlqK6voaelp6Ofn6ioptB9BH59f37lfQF+tX0Bfv99830Bf/99/32EfQF8kH0BfJF9gnySfQF8ln0BfP99qH0BfqR9AgIEAICnqrGpqaOhn5ydn56YoKCkpKqqr6mtrrWrqKyvrrCmoamjpKGmqKmqtbK0u7q7uqyur6mirKuvpre8s663ra6pqLGpp6usrK25sbG2u7KxqKukp6erqq6uubO3trXJt6y8t7u7vMXAube4wb/BxLqyqbO0s7u5r7GsrrS0vq2wsoCxrqSjqKu1vMDVt7i4s7e3usS3tLGpr7estLKrrrO1s7a7v725t7jIvrW6t76strOwwL65r7S0urS3vbm7urGot7mxt7W4wrO2tLa5sLa7trO0trq9vcTBxMDDw8jCysC/uba9vry5ury9w8fAwsLsx8C+u7u7vsLHvMLCvsLLwIDEysrExMDHxMrPzbzCwr26wsPIwMK7vb3DwMG+wcO5wrm2wr+/v8G9vLC6t7m8vcXLzMi5vLe2ubq3xMG9v7q/vcW9ubizu7OytK+1vbKts7myubKwsbm5t7Sxs7SwvbO4trqyvLu5v7+8vL60sbi4sbCys2++u7C8r7K/t7W2roCzt62tr7Wzq7GwsrOurLGrqq+zrquuura6ubmztbCrsr29tbSztbSzuLChra2qsq2xsKertaqyrKeyqrG0sqysqquktKrKpr6wqKqnrKarqaerqaSsp6qpq7CxtLOwsa+xsa2vuamkp5mauLCsrrCrsbGusLSrtbOnq6mmpKakpICmpamioKiumZydn6ucnp6XlqiopaejpZuhnqGZmJmYmp6enJ2boJ+lr6q5oaOlpZedoJ6poZqft6OeoqypoaCapKeompmfoZqTm56SlJeXk5yWmMajpJuWlI2SkpiSj5CVj5SZm5uamZuSkpOMkJGXlZGKkI+MkYiOkoyIhoqNkICPjIyKhoyGiIaJhYuFh42Lj4uBioaMi4aGhY6VZYmDg31/cXqDhX95goGLi4eOg36DfHuCg4WHgIOBgX97fn+Aen1+fICBeX6AhH2EhoOCg4mJiomGhYOCenx2eXB3eH2AfXx6fHuDhI2giIWGhX9/fX6CgX2OgoKKfn97g4aChD+HhP6EgoaIgYB8gX2Eg4GDhYiCg4ODgXx4eYV3feF4dXbmenh4e4d/g4qHiYKFhYOIj4j/joiLiouFhn6DiYOEgoCFhYeGi4qNlpCQn5eNhISBg4WEg4qOiomLkYuOjZGOl5GNhYWDhYeLjIqFg4mDhoeIiYWTgoP4+ZKGi46Sj5KYkI6KjoyPjpCOipSTlsCJjICRh4SKko6QjY2PmI+SjYuOjYiJjY2Mj5GUlZeUjpeSkpKPk5CNjY6SlZmTnZWRjD2PkZKGjZGPkZOSl5eXkYSNiZmVk5ijk5aYn6KamJifo6alpKajn56gmJyblZ+co5uapqWkp6anp6qqqKqlgJ+ks6mnop+dn56enZqhoKOjpKyqpKapramkqaursqynrqippquqpqSsqqqrqaivpKmppqCpqaqgqqunpa6ssq2wua+rq6qtrbSws7e6tLKtraerqq2sr6u2q6qrq7qrobGrr6ystbOpqKuvrrC3ra2mrbGvtbmvsK2utLe/s7i3gLe2rqyurbK3vsavra+srrO1wLe0sqyxuLG2sq6us7Str7a4uLa0tcW9tbi2xq22trPBwbuyuLW+tri8tra1q6aztK6zsba8sbW0t7ixtrizsrG1trSzt7a0sK+0t7G2srOwsrq6u7m5vLq8v7e5ud+/t7eytbS2ucC3ure0uLyzgLa7u7e0sbe0uMC+sLW3tbC2tLizuLe3t7y4vLq6ubPAubXAurS0tbS2qri2t7O3u8HEv7i9t7i6vLnCw8C/t7y4wLiyta+2srS2trzEurW7v7q8tbSyuLm1srSzubS+tbe3ua62tLC3uLWzuraxv761tbu4bMC/tb2xtcG4t7exgLe5srCzurivt7i6u7m7u7m1ur68u8DGvr29vbq+t7K6xMO6uLe5uLjCua+5trW5t767srW/uL21r7yyur63tbe1t7G4ts2xw7i0trW7uL29u7+8uL62ubSyt7e3uLK1tba0tLvBt7CzrbLFvLq6ure2tLi3vLTAvbG2tLKxsbCugK6vta+utr+qrK+xtKqwr6qmsa+ur6uuqLCtsK6trauprrKsr6upqKarpK2foaemnqKkprCnoaa7pqKnrqidoZynqaienqSmoJumqKGhpqqiqaKjyKuooJuZkpman5yXmp2amp+hn5ydn5eXmZWVlJyXlY6Pko2SjZCQjY6QkZSVgJSSk5WPlo2Oi4+MlZGQmJORj4iOi4uKiYiIj5RokYyKiYuAioyKhH6Gg4eIg4uDfYB5fYOGi4iDhoOFhH6DgIF8gIB8gIV7goOEf4eJg3x6fX17e31+f4F6fXh8c3p5fX59e3p3d3l8g5aCgICDfXx5e3t8dol/gYR8fHyBhICDgIeB+oJ+g4V6enZ6dYB/e319gHx+fXx+fHl9inuD74F7e+16eXp5gXx6goCBeX15eX2DeuZ+e39/gn2Ae4CIgIB/gH+Af4J/gH+CioSEk4uFgIODg4WFg4SIhICAg4CDhYmKkI2HgoKCg4SIiIeDhYyEh4eHhYeahob++I2ChIWGBIaKkImFhneFhoV+i4mJ7YKFfIyHgYaJiomGhoiNi4+Ki42Nh4iIhoeIiYmKjYuNlI+QkJCTkIyKio+RkZG5j4+Rk5GYjZKYlJGVlJaVmpeQl5WgmpeYnY+Pj5OWlJKQlJeXlJWUlZSWl5SZlpKbmJ+WlJ+cm5+en56hoqGkn4CmrryxsKiopqSkqKWip6iqqq2ysKqtrbKspqyyrrqwqbKtqqeqqKuosq+vtba1t62usK6nsrG1qre7tbC2srWytb+zsLKxtLa/t7e/w767tbWxtbGzr7Kvu7K1t73OwrbHvsO+vcS9tbCxtLa4vrSyrbi+u8PEubm1try+xre7vIC9u7Ovr7O+wc3Rtrm8uLq8wM7Fw8C3vcW9v8C7u8DCu7vAwbq2s7TGu7W5ucixvLm2xMS9s7q3v7m5vbe5urKtwcS7v7vCyru+ur3BuLu9uLa2uLe4ub+8u7i7wMXCycPGwb7Gx8LAvL2+v8O7v77Yw8LCwcPDxcrRx8vDvsLGvIC9vb+6uLi/usDMy7jAwr+9xcXMxs3FxMPDwsG/vr+4xr+7zMjHys/R0MPIxcLAv8HIysW5u7O1uLu4xMXDx8LIyM/IxMO/x8HEx8jP1svFytDIzsXFwszPzMnMzdDN3czRz8/CzMrGy8zJytHKyNbVzs7S0IPX1MnTx83az87Oy4DT2M/O2N7e1d/g397a19nSyc7OycfP19HQ1djV3NjR2uns4eDe4d/h6dzI1NDP1dLY1dDU283VzcjQytHX19TS09XP49342fDg29vV2dPV1dLT0cvVzc/KydDS0NLM0NHV09Tb5NHIy7y92c/Lz8/M0dHT1NnQ3drM0tHP0M/Sz4DOztbOytbjyMrLzNPIy83IxNLQz9HJzcPJxcjCwMC/v8XJycvMzczN1s3YxcPExLq8xMPRycPJ5MjExdHGt723vsO/tra7vbavubyxr7S2r7WztODCxL65t7G3ur63rqyxqqywrqysrLGmp6qlqqu2s7CkqqulqKKmqKWgoKSqq4CsqamqpbGpq6WooqylpaunqKWbpKKlpaSlpq+3naegnpeYiZGYmJKIk5KampWblZGViY+ZnaOflpyWlpCNjpKRiY6Pi46RgoqJioOIioeBhYqLiYqKi4WKgomDiICJio+QjY2LiomKjpW2i4qMjoeGhIiEiIWZkZWbkJGPmpqSkHGQh/iFgouOgoKDhYKJhIGEhYiCgIKDgoCAgZCBh/uIgoP+hYODgoyDgoaFiH6BgX2Di4PziISJh4yIiYGHjoeFgoF+goCDfYB/g4yFiJ2SjYaEhIKDhoOKkIuFhYyGiY6QkJqShoB9gIGDiYuJhYiOh4SLgImfhoP39Y+DiIuNi42TiouJioiMioqIgpGPkt+Gi4GPhYGJjI2Pi4ySl5OXk5CXlpCRlpaUl5WWlZiUj5WRk5ORl5aRkJGTl5mXzZiVk5mYnZGVl5GOkI6UmZ6elZ+aqKCen6aWl5aeoJybnKWqqqioqaaioaSepqKeqqexpqOqC6ejqKOgn6SkpKah/33/ffZ9AX7/ff99qX0BgOV9AXyafQV8fX19fJF9AXzFfYJ8/32GfQICBACAp6ekoqOeo5+cn6Keoaufp6Kmoqqko6qopqaqpaqflpehsKGmo6KfqLG1t7WyubiuvLayq6unq62zvLa8xa7Muq2zsKmssbeys7W0rKyurLOppquusbm0trK4uLnBvb7HvMDEvry9ysbGtsS7wru8vL+7ua2yu7a3rre8vbi7ubqAw7y3rbnDub+/xLq9wL+8e7DCvbSur7K4t8aut7Oprbu2srW4ubi3wbC0vce8vLbPs7DHs7Kwus+9vriyuLnBv9Wusrayv7exubq1zrK5t7XFv7y+v8HBiMrFvsbAxszCwri8tri8t7q+w8jTwsW+u72/vb64vru0t7zCw8Hp0sSAzs7Jw8HCycnLvcvAwcvHxcO+urW5vLexv8S+wsPFwcLBubyztLi+vMPEyL3Tvry2tcDQtLq+ubm5t8Orq7W7uLC4wMS/vLyyra6nrLGzqKuwusW5sbi0vr65vMDDvbu6uMzCv8K5t8C8w8PHu7m3vrS/u7W4urbAwra2trK/rLuAtbSxubPBa6ywsrCtrriusK+1tLO0vbixs6u5qa+yvLmytMC4srOztrevt7e4uba7r7Swsa+orayln6i1t7awZLGpr7KwsbGvsLCvq6ikqaugrqqjramrsKmwrK+sq6itr7awp6yspq+jqb6ysq63rLSnoqmtsbm4qrOyrrCqqa6Asqqwraqoop+foKqgmqumpamrq6uppZuimqiuoaWanaWdoqWppqWlo6ioo66kop+hqKeer6ygpKykn6C4sKWgpqetn7KumJKf0JWWk5SPlYyWnZqWmZukkpCTm5apgYmKio2blpyak5qepaiin52fpKCTlpKOlIyLlJCMkoiNjpSAo42LjoqHg4yDhoaCh4mFhoyKhZaKkJ6OjIyIjY2ZhYOEgX2Bf4CCgX6DgIyJhH2Eg4SBgYKCfYV9hIF9gIiDfYB6gn15dnh2eYN9foF9fXt8gIKOgoWPlYaDeoDleHhzfoF/gIiAg4eIh4yFjoWDgYB8fn54iY2Aj31+gnyAfHuAfH1/roiBhIeF/YaKhIyD/IKHh4L/hI+Bf/b09IbseeyneYWHf4OFioqLiouCi4eDhpKMjIeOjoiKi4aEh4SJhYGB/oaCjJGMjoqMjouVh4GIk/6Eh4eFhoeKj4eQi4qQkY6Nj4mKh4eHjISBhYeHjoh/e4WEf4qDio+NkJCOkoiAjsOskYiH/oeJh4SJhpKNlI6Ej5GKkJCLj5aRko+ZjI+Nj42Pio6MkZualo+Uj5GQkpWZo5+bmJ+al5een52Tm52boJOOjI2QlI6Xk96QkZKTkZedqKaRmJqNkZWYnJ+XmaGjtqWrrKKZmpqYmZecoaWooqOkoqGloqOppKWrqqOApqalpqahpqKhpaaeo6igpKKjpKqlo6qrq6uuqrCoo6SsuqqxrKimqK6ysa2pqq2ouLayra6prK2vsK6wt6a6rq2xr6ysrrGurbGwrKuwsriwrrKwr7Otr6qtrK2yr7C9rrC0r62vuLS1rLWwt7CwtLm0tayvvLm2sre4u7a4tLSAuba0q7W8uLq8w7S2uLazk6q5tq6qq66zs8GutbOusri2tLe6tLO2wK+vtMC6urjSurrPurayts27ubKutLW6w+Gwtru3wrq1urq3zra5uLbEvbi5ubm0griyr7Syt7+8u7S8t7i9uLq8wsHKvL63tbe4uru2vbuysre6ubXmvrWAwb68trS2vby/trmvsri1tri4tba5ubayubqzt7a7ubu9u7u0uLq6t7i1tbPfvbu3t7/Os7e6ur28u8Swsri9urK3vry4t7q0srS0t77Btrm7wcW8tri0vLy3u769ube2s8O9ubqzsba+wL3Cvrq9xrfHxLq/wLzBw7O1s7C5qbaAtbKwuLXEbbS5ube3uMS5v7y9wb+7xL/AvrzHtbW4wLu2t8G7trq5vLqxu7q4u7a8tLy5vsG7wcC1sLfExb+6aby0u7q5uru4urm5uLa0ubu1wru1vbe4uLG4tLSysLC0usC8t7i5s7extcO5ubbCuL6zsrO2ub6+tLm4tre1sLWAs6uytLOxsbCvrLewq7izsbSxr7Kvraqsq7OzrbCsrrKqsayuqaaoqKytp7WrqKamrKult7SprbKrp6WwqqOfpqmsocK1op2p26OmpqWhqKGpr6mkpKasmZmapaS3kpmVlZWinJ+YkJOVmZqWlpWWnZqUlZGPmJKUnJSRlY6Uk5mAqZmTlZOQj5iUlpSMj5CNjZKMiZmHiZaJiIeFjY6bioeIiIWGhIaLioaLh5OOiIKFhoSEhYiIgIqDiIN/gIiEgYWBiIOAgoSAgYiDg4V/fn19f3iEdHV+iXx9fYLxfn13f4B6e3x6e4B9fYF/iIGAfH17e3x5iYuBj31+gHt+fHuAgH59rYJ7e39/6Hl7eIB77H2BgoH7f4Z/ffP39IT2fvadfIaGfn99g4KBgYF3g3x5foeAgHqAgnx/f317gYCEgX5//IJ9hoeChYCBgH+JfnqBivmAfoB/g4OBhYGHhIKFhYaEhYWGgYGDhoSDg4KBiIWBf4WEhpmEhoaFh4eFiYOAibijjIOE+YSHhoWLhp+QlZCEjIyEioiDhIyLiYiRhIqJjYiKhomIi5KUkIqNiIyMjpCSmpWTkJeQjY+Wl5SKk5WUm5CPkZSUlpGVkLyQlJaWlZieq6mUmJiLkJOXmZmVk5ibpZmhoJqUl5iZmpmbnJ6hnZ6enpygoKCln6GnqqOAqa6srKynq6mmp6ykqq+lqaiop6ylo6iop6WrqK2onqOuvqq0raijp66urqqnrq6qtrGvqa2rr7G2vLe7wKzHu7S8t7S0t7i1tre0sbS3uL2ysLOztre1t7O4uLjEv8PSvsTHwLm4wru6rri0vLW1ur68u7O3wby6uLy/wbm/u7uAw7q8sL3Hv8HCyLu/wsC6vbTHxLm4tr3ExMy8w8C4u8K/vb6/ura2vampsb+3uLbPtrbKtbWxudK/wLu2vMDL0vHAxMO6xr+4u7m00be7u7nIwLu7vLq6ecG8t7+5xMrEwru5ubi/uLi6wsHMvcG5uby/xMfCycXAwsjKx8TuyL2Ax8LAubS1wMPGu8W7wcvFxsjEwcTFxb66w8a/wLzDwsHEwsK+wcbNys7Ly8jtxb21sr7Js7m8uLu7usezt8LFwre/wsbBwca/v8LCxMzPwcLEy9LIwcLCzM/Hy9HW0M3Myd/WzdDFxcfMz8/SzMzN1MnY1s7T1tPb3c3RzsrXxteA1tTT3dfpgNHY29nY2efa29jX29fR2M/PzMjWxsnR3NvY3evk2t/Z3NzS29nY2tbZzdPQ09PO0tPNws/f5eHYgd3R3N7e3eDd3+Hh29jU2drP3tXL19DR0srS0NLLysnO0tjTztTTy9PIzeDR09DdztbIxs3R1NrdztXS0tPRz9eA3dbc29rW0c/Lx87HxM3MzdHS0tPPyr7Fv8rOwMTAxc3FysrPzcjIxMXGv8vBvLvBy8rC2NTKzdfLyMPqzcC6wcLGttnPubG96bS4tLOttq21wbm4vb/GtLW2xMHWp6+rq6q2rrCpoaOorrKsrq6wtbOqrKuqs6inr6imq6OnprGAxLGtsKyoprSvsa6kpaahoaSena+hp7Wlpaajq6u3p6Ojn5mcmJ2gm5SclqKbk5KWmJeanaGflaKYmpOLi5aRjJGMlJGNjYuHh46IhYmHhoWIioqXioyWoI+Mh43/iImCjZCKi5CHipCQjY+Nl5CPioiIh4mFlJyVrpSSlIqOjImAjoqJxY6Eg4OB9IKIg4eB/YKEhYD7hIyBgPb9/Yv8gvmcgIyMg4KEiomIh4d/iIR9g46KiISLj4uQlIyIjYmNh3+A/4aDjI+JjIeHh4aRgn2Flf6HhYiFiIeJjImQi4mMioqJioOFgYGFh4aIiYuKkY6JhIuJiZ+Gh4mGh4eGjIeAjta/koiI/YSIh4OKhqeXnZeMkYuCiomHipKRkJCcipGPk4uQi5CPlZ+fmpCUjY6JioyNmZKQjZWQkZKYnZuRnZ+boJeUlpuYmI+WjrqUmJqamJ2guLOWl5iKk5men6SanKOnuae0sqqjo6Oip6SjpqirqKihpKGkoqOopqessKj/fZB9AX7HfQF+/32vfQF+uH0Bfv99/32efQF8rH0BfIV9AXyEfQF8hH0HfHx8fXx9fKJ9AXyPfQF8s30BfPl9AgIEAICws7GutaesrKGnrbG1sbazsLWpraisq6egoq+mqaWpqKOtpaGjqKe5uqytsLC0rqqsuK+rr6mzt7W+tbasr763tbKtrq+1trS3trewrqjHr7qyrqm/sr2+vbm5wra6tLS0w8nIy8HKycrWyMfBw8DJvcPcwb+8vsHDwL+2xMbGu4C4try3vbfQwry4vr+9wLi9u7fAvLS4sre5r7S51rKwurGyq7u+t7e4u7y1tbS0tLGxpKmmq7G3er63tLvBvrSyuMPKw8TEwMG4ubu5tLzAxr+2ucLF0by+w8PLx7+/xcK/vr26v8K6vLvDvcLBv8PEwsLEysPMvdzEvry+v8e/xYDFwsjBycnDwr/ExcnEwcO/zcvLwMO8uLu9trmvtsXBwsLHxcTIvLC5wbi0w9C+w8a8wby9wMjIyb+4vLa8v7+/wL/Hv7/BuLm0tbauuLWvsberuMK+x7a6ycXDwMPBucW4uru9uL23wsK9uLW0uba0r7a1s8K8u8bEvLiyr7e0sYC/t7S6v7Oqs7bAsrG3uJ69vLS2t7SysbWxw72vsrS2tb69w7S2u7G5vLa2wL22vLa8urq3rrCtq7OwrLGxsq7At7+utrK0tq+wrLKqq7+0r7Oss6mqsK2zubS6tr2upaqjnKmwtK6xtLOxtLeusLGyr7Croau0rbKxwrq2t7muroCvrbCqtLqyrqqvpKunqsC6r7Csp3Cuq6qqsauvrK2wp62hprWuq62moK6xq6a0paeip6Kuoq+mrbmqqKWprqywq6innaOkoZzglpWVnaGXkJSanZ6elpqTk5ORmqmSjIiNk5eZmJyXm5+hoJ+bnZ2fm5aVmZebl5mWk5aOfZGMlYCPi42biIeLj4qPkIiHhIWKj5WJj4yViY2Nj4uCh5SKiYSHhYdTYoZ/cnWEeX+AgoOIj4R6g3x9jXx5e3uAfYN7fH16e358fHqAfHuCgYSDjKiFgoB9goaEhYF9fX+EoZt6d3WDioWLhImIjH6Ag4OCsIl8h4ulg4qgmoaLlniBgoCBg/f0iIGBgY+Di4Z/i4eEfYOFgoWGjIGHfX1+f4B9fYOGgPX5gIqKioWSh4mGiomDkoeIkY+UjoWHhISBgIeFgo2Im5OKiYiQgpWSipGKhYmIjImLj46GiouGi4+Tko+LkIqGkI+Kk46QjIuPiY6XiIeHiIh/+viKlYWKlYuKioB9sYmKgoOEgIOLiIaKhYePiZKNjoqKlpaZlpeYmJyVmpWMlZGOkIWNj4mRio2TlZqZkpehqJmVnKadm5eZk6GfmNXdlKiHjouNh5aZppvMlY6SjI2VlJaVmpyrmZSZmJ2bnp+go6Chpp6goKGop6upnpqYm6ClqJ6fpamjqarFroCjp6anrKSmo52ipaSooqioqKqkqaitrqqnp7Wrrqqurauzrqyvra24ta6tr66xra2wvbOusKq1t7K0r62mq7SusK2qrKqvr66vr7KtrKrYr7exsa28sba0sauutK6wra2tub25t7G3s7DBsLOwsrC9tLrWuLe0tre2tLStubq+sICwsLextq/Gu7ayuLe1ubK1sq62sq6xrbK0rK+x0a6tt7Gyrru6tbm5vrm1uru6u7i+tLq5vLq8gLy1rbC1t7SxtLy/urq6ubizs7a1sra4vrmzsru9xrS2trfBvbi5wry6uL6+v8G7vr3Gur2+vL28u7e7wLfCs9C/ubq9vce4voC+u764vsC+u7m/vcC8t7Wyvry6srq3uLi8triwucO9u7rAvr7Hu7a+xry2use2ucG8wL26ub/Cxr65v7rAv7y7urrFu77Aub6+wcC9xsW8vb+0usG7wK+xvbe4sbi5sr23t7u8s7iwwMC4sbK2vLu5tb7Avci/u8XCurizsri1s4DDtrO6vryxtrrFuLq9u4rAv7rBwcC/vb++zca2vL6/vb+8xbi7wLfBwbq5v7q1urnAwcLFvL+8vMLBu7++vbfHv8m5v7q8wLi8trq2tci+ur60urOytrKzvLe8tsC1sLeyq7jBw7i6vLy6u721uLq5tre2rre7s7OyubKur7OvrWywr6+utbm3tLG1rbSvsMXAube1s2+ytLKxs6y1r6+xqrCqsLq0s7WvqK+wq6W6qKyop6e5pLOpq7aooqChpaaqpqWkn6aqp6bfoqGjq6ukmZ2jo6SlnJ2ampybprahm5igsqSmnZ6WnJubmZqEmICXlZaXk5eXl5KSm5eMm5melZOVo5KRkZaPlpaNjo2OkZKWio+LkYeKjo2JhIeWjIiFiYqMXXWRjYOEkIOJh4iGh42EfoWDgpWEgYODh4OJgYSCgIGEhIWAhIODiISFgIicf3d4dnp9fXx8e3l/i6effnh0fIB7fnp7fYR5e3t+fYCxhHiDiayEiKaXh4yqeXx9e3308YF6e3iFeYB/eoWFgHt/f359fn9+hn6AfoKFh4WIjYP593+DhIB6hnx8fX5+fIh+foSDiYN+f3x9enmBfnqDgZCJgX58hHeJgn+Eg4B9fIOBgoSBfIKGgIWHhoGCgoSCfIKDg4mHiIWFiYSHkYCHhIaFhoH++oeQgIaNiYWIfcONjIaIiIODiIeFiYOGjoqQioaAgImIiYODhIaLgoeJgouKi4yGjYyKjomJjouQjoqOmJ+Tk5eelJGOj4mWlZO9u5S5j5eVlpKanJ+UuJWWlZGQmZiYl5ueqp+XmZmamJeYmZycn6GanZidoKGipRCalpebn6Wonp6ipJyhoL6ngKuzrqyup6OqoKatrrOusrGys6qtqa+tqKWltq2wrLGwrreurK6wrru5q6qvrLCqp6u5rquwq7a5uMG6u7K4x769uLO5trSysLS0tLCzsNW2v7a0rr+0vry+ub7Hv8LAxL/MzczNw8zHxdy/vba2tsG5vuS9ura1ubu4ubG/xcm8gLy7xr/Gv9nMx8HJycbJwMG/vsa/u7y8wsW6vsLnvbfBt7iuu7u1uLe7trK2t7e3tLewtrS6u7+Hvrezub/Cvbe8xsnCwMHDwbi5urq1uLq+uLCxuLrEtLS2ub+6tbTBvLi5urq8wrm6u8K2urq3vL67u8LJws+f2cjBvby4wLS4gLizt7S7vry5usG+w8XCwb7OzM7EysXFxsnAvrK6x8LBwcfJzNbHw8rSxsDG1cHDw7u7tbq5wcDEwLrBvsTKyMfKydTIzcrFxsTGysDKy8PDx7rE0c3Rw8rY1tbR2drR39bY2dnS2NDe3NTOzdLT0M7K0NLP2tPO2dfPzsnI0c/OgOHX0tzj3NHa3ujX1tnakuLg2Nrb1dLOzs/h1snS1dfZ39/r2t/l2OTi2tjg29XZ2OLd4+HY3dvZ4t7T3NfZ0Ozf6NPh3Nzg1dvV29PS6t3Z3dHa0M/Vz9Pc1d3Y4dLK08vAztnaz9PS0tLR08zQ0tPQ0M3DztLLzs7c2NTV2dHRgNna3Nrk59/a0tPIzsXE2NXQ19fTg9HQzc3MxcrDx8zGzsTO3NPQ0crAzc7Iw9rGy8bFxt3G18zS38zEw8DDwsfAv7y3wcbCv/y3t7W+wLetrre7vb+3u7a4ubnC1LqzrrTKt7qvr6Wsrq2trKqtsLKyrrCzrLGxs6+utKyesq61gK2tsL+qqKqwqa6voqKfn6Klq6Cno66fpKaqpp+jsKaknZ+foIGmo52OjpuOkJCSjpGZkIqTjo2kkYqPi5COlIuMiYeJi4eGgYOCgIaDiIWRqYyJiYqOkZCPiYiHiZKjnoyIhJKVio6EiYqUh4qJi4jEk4mWnriRm8uymZ6rhoyPcYmG/veKgoGAjYKMiIGMiIaBhYWDgoWGhI6Eh4aHioqGiYyB+fmAiIiGgY2BhISEgX+MhYePjpeUj5GMjImJj4uGkIyelo2NiZSEm5OOk42Kh4WJiYmRjoiPjIWJjpSSj4+TjoeMjI2TkpOSk5SNkJiIhIKAfPDrgo9+h5GMiIyB8ZONhoeJgYONjYySio6XkJWNioOFkZKWkJKVlpuRl5WOl5WWmI+amZSaj5CTkpOPiY6ZoZCQlZ6WlJGWjZ6gm7fDmsqQlpOSjpmXm5e/mpmZk5GVlZmYnqGsn5meoaOhoqSipKOpqaSkoqSqq62rnpianp8Lp6qioqano6qo0LD/fbh9AX6+fQF//32YfQF+/32GfQF+/32QfYJ+3H2CfJ99gnzRfYJ8/32JfQICBACAqrCwtrK7uLm5sau7tbK0rau1sLOsvay2rrLQurCrsri6rbCwqKevtr6us7m4srKpraqlpqSoqKzCrcu6rMO9sre0qq65vcG6s7e3w66ssK6zwLm4yMrQw8a7uK+wtrewtLW6ucHEw8TFv7/Jvr++wL64ysy+wL/BvrvJybW+68SArb7Kxr+8wLzBt8LFwMHEw7/Gwrq6wr+6vcHqubq5vsK4vbrDw77EvrfExr67v7a8vbOws7Sxs7i3t8DDysnEyc3bzNHJx7/CycPFvsC8tbq3u7O2t8K5wrzIwMTIx8DHxcXIxcrSxcbAysfLyMvHwsrOzMnJzr7Fwb26v7/Iw8WAvcjY4sTHxMjNzc3PzMO9x7zDwbzCw7+7wb/AwL29ub21ury4wMa5uru8uLe4wsO+w8LMycrHzcbAw7+9vn+/ycLAwcLGxby8ur/CtsO2wr+zs7G0t8DCwtPMwcXDvMC8v7u3wcG4uMW8wL66tbm4sbOzsa63sIG/wcS3vMfOx8CAvbu5trS5t7W1tLKvs7rCvcS+sL22wLWys7mrsbizsLq7vMLAtra5vLe4tba9tb29ur68uMLFvLy6vbu8u7a1r7S+urWzrLaysbi9triztbK0u7evrrGzura4u7q8uLq4ubO5srGrrrm5u7vH0Li5srGvvrO2u7i2sbG1tLduecCAw7rDwbm2t7Guq7KstLa9w7vCs6qxr7CxqKu7q62ur6ijqaWop6ulp6einaGxqcOkoqq0n6expau1rq2sqaKooZiTmqKdoaWemaCdm6Oelp+dl5qfnKKempudlqSul5OSjY+Rjp+Uk5WcoZmbm5mempmRnaSanZiRjpuPj46PjI2AkIyQm4+MkI+FfISFiIuIlJOPjY+Mj4yXjJGMio+Xlo2GiouNi4mAe3Z7fnZ5fIqKiIiNe4R9gIGEgIeMf4SCgYKIhoaFhYSUiIZ7h4V8eX3qnYKGgYqDhYZ8enuDh4GFg4R3e3+Hg4eGgYSBhIaFf5OJhYF/f4mLe3yAiIaGj4iAhn/4goKCgIeMg4OIjX6FiI6HhYOBgoKIiox/gX+AjYSFhYOAg4GKjYeHiouLlIuJiIaDhY+RkI2ThImJjIWMgYSChYeIhZqPnIuLmZCKhJGLi4mIicC9hIWHlo+HjJuIkYuKjqG5g4aKiIOAn5SOh4mHhIWJiJuXiZOMjpuMjoaAgJCLj4uJh4yLi4uQk42Ji4uPkZOVl6SgnZaRn5KVi5uRk5iSkJGTiJmRko6JiJacl5uYn6Ogn6GilqCYoKCjo5WUjI6UlJKSlYmNlpikqZKXoJmZnJmUlZeekqCblp6hm6OgnZ2eo52jop+poaKjpp6prqWrqqWoqK6kqqKrqKiAoKOkqaatqqmoop6spaCmnqCmo6OjsqWsp63Mua+psbi6ra2tp6WqrbWqq6+vqq6qsrGtsbO0s7C+q72vpbaxrrOwqa2ztLOuqK2vuaqssq+zu7Suvbm9triwsKqvtrWws7K2srW8uLi3srK+tLOztreyxsO4uru9t7W/wK6z3LaAorK8uLGvtLC2rbm4t7m8t7e8uLKwtLSwtLjmsbO1ub2ytra7u7nAubS5vbm4v7W9yL+9vL24t7i1sbS0t7Wws7jHt8C2uLS0ura6ub+6t7y8w7m5t764vLnBu77Cv7m9vLzCwcTLvr64wby/vL64tLq+vrq9wrW5u7a1vrrCvsGAuL7O0ri9ury/w8bLycK6xLzCwrvAv760u72+u7+7uL68wcK5vsG4uLq6vbm+wcC9wLvKwL25vr26v7y8vI2/xr28u7q/u7i1tbq/t8O7xsW5tbW4trm4tsK6s7e2sri4vLy4wL62tsW6vbq7vcHBv77Bw73AuoPEwsGztL/Eu7eAubi3t7m/vrq9vb6+vb/GwsbDtMC8w7++wsq8wcjBur/Av7/Aub3Cw8HBvb6/t72+u8XAvMLGu765vLm7vri4sbfFwr66tL27u7/Cvb68vrq9xb+5tri3urm9vLy+u8TAw8DEvr+1t8K8vLfCyb6/uLa2xbi5trm4tbKzr69rd64UsKqzura4vLa4sLi0tra7v7zKvLGEtYCtrbqlrK+xq6qws7OvtKuwsKuqr6y3zK2vsrymq7Kkp66rqKmnoKupoJuiqKWpqaCco5+hqqOfqaWjo6KhoqCdnaGgrqWjo6Gcm5qXpJqYlpudlpiZl5qXmJOboZifnpaRnpOWkpSSlZWSk52QkJOTjIaLjI2PjZOTj42QjY6MlYCNj4uJi5GSiIWIioyNjIqHho6IgoOCjomJiI6AiYOFhoiEiIyAg4B/foWDgoCChJGLiIGOjoSBgfSZf4R9hH+Ag314eYGGgYB9gXh4e358fX18end6e3pzjoKAf3+AiYyBe3+Fg4SJgoN+/YKCgoGHi4OBhYx9gYKJgX59e3p6fYCEhXuAfX6OhoOFhH+AfoKFfn6Cg4KMhIODgoGBiIuJhol7fYCAfoR5fX1/goOAkYWRgYCwgn58h36AgYCCp5yDg4COhoODlHyDfH+Bk7Z6foSDgnueioiChoWFh4eHoJuGkYqJlYWDf3uLioqJhoSJiYiIjI6LhoeJiomIh4qTkWiLh4WQiIh/ioqKjoqLjZCGl5GTjoiIkpeTlJCXmJaWlpiOmJOcmpqbkI+KjZGVlZaYkJGamp+gk5ecmpiZlpSWmqKXo6KanZyYn52enaGinZ+fnqeeoKGjnqarpKaloaGipZ6hn6CeoYCqrquurLOtr6umpLSxr7Orr7Svrai3qbGqrc69sayzur2wrq2nqK+0vLCwurWysqqwr6qsr7Kztcq51MC0yse9wryztru6ubWwt7nEs7K4trfAurbFwcrBxr+/uL3GxsPHxsnEy8/KycW/wszCwr/BxbzVzsDCw8bBvs7OusP6zYC3ydXQysbIxMm+ysvDxsXBv8TAubm/wb/Bxt/Bw8HFyr/DwMXCvcO5sLm7t7S6srjCvLq7wLzBw769wsLGw8HEytzIzsHCv77FwMO/xbu1u7e9tba0v7vBvMa9w8nFwcbGxcnHy8/Cvba/t7m6vrq3vsbJycrRxsnJw8DEv8fAwoC4vc7Wtr28vsXIy9LSysTRxs3MxMrMzcLKzczJzMrEx8PHxsHJy8TGy8jJw8PIx8HCw87LysXJyMjOzMvLs87Wz8vKxszGw7y+w8m/y8PT0MbFxsbIztLQ4NnS2djR1tPY1c3T1MvN3tDT08/S1NPOzdDSzM/Lj9jY2MrM2uTc14DY2tra2OTi397b2dXY3ebg5+XS39nf1c/T2svU3NXU3+Di5ePa2+Hj3+Db3N/W4OLg6Obi6+/f4N3e3N7c1tTQ1ubg4d7V4d3f5+3j5+Tk4OLp5dzY3Nzj3+Li4ePg5N/g2t7Y2M3Q3dnc2OPt19nRzsvez9PT1dPRzdDO1IGY4YDk2uTn5OPm29fK0cjQz9XX0uTZz9bS0dDGyd/J0NjX0c7R1NPO0sjMzMXDycDV6cvL09zH0NvM0trUz8/IwMnDt7O5wr3CxLy5vLm2wry1wL25vb29v7y4uLy5x5S4uru0tLSuvbCtrLS3r7KysrWztqy4wLW6uKumt6isqqyrrICuqKy3qamsraKaoqGhpqCpqKKhpKKlpa6lq6Wgo6yrop2fo6SkopyWk5mUjoyPm5aVlZyOlYyQk5SSlpqMlI+PjpSSko+Pj5yQi3+KiX59g/6fjZSPmZSWmI6KiJGXjZCPkoiNjpGQjY2HhoKFiYqCo5KSjo6Nk5aIhIuPjI2UjYCJhP2EhIaFjZCJj5OZhoiJkIWAgIKEgIiNkoSGgYKSh4yKhYOGgYmLhYGGhoWQiIKFg32EjIyOjY+EiIqLiI2BhIOEh4mGn5CgjY+5lY2KlYyNjIuMrqaKi4qWkYyRooqRio6Ss/eKj5SSi4OjkIqFiIWFhYeGoJuDj4mMl4qJhYCCjouNi4iGiIuMjZSWkYyOjo2PkI6RnZ2Yko+bkpOFkYuMj4qJjI+HmZKWkIuJlJiRlJGanp2foqWZo5yjo6Sml5aQkZecmJiZkJGXmaCjlJifnp6empWYnqygrqyhoqSipqehoJ2inaOkoamhpKSlnqaupKanpaiqr6mvq6yprP99/32/fQF+uH0Bfv99h32Cfq19AX6vfQF+830BfLF9AXz/ff59AgIEAICoq62xt6yttLW3tba6tbm0r7Gqta21sKyln7G3rrS1qsC2s7W7gsW0trWvuLq6zK2wsaysqairrqqp5ba0x8K0r7Gxuri3u7u1v7y/tcLHuLi9xb3BxsvAwcC7wrq8tbKqsLS4ssDOw73BxcS8ypbAxLW6r7e7v7y3vbi5uMHNxg+0x83Je725uLe3s7i8xcCEvoC8usLCvMfBv7iwucC+wb2+x8TBycfGx8nKyMDCu7m6u8vDrr3BucC+uMLBz83Dv8XNz8bF987CxcC5wsDCwL29xcWAwsvAzsHO0tXIxcTMycTM2MnGzsTGycnExsbJxcm+vcC9v8TBwcLGy8fMyMHh0Me/xs3IyMfFyMnEvcfExYDEx77Hw8TM0Mq+ur27tbe8ure/usDCwsTDvcbEx8jDwsvIx8nDxsi+wsbAv8LJy9LQy8TEwcPAucfPu720s7u6vsW+vsbHzcS+vK25ube3u8S4vsDCu7y/vLq9vrm8u77Aw7u3ur2/ub+7v7rW08O+1NPDt766vLa0tri5vrq8q4C+vru0sba5saywrauxvLa3uL3Cs7e5vrW9u7y6v7/HvbnBu7e3tcC3t9TAu8TAs7nAabSzur65vLa6tLzEt7O1s7C+sr28tby9xsC1sreysq+zsLGztre3vru6s7q9tL64ssKyssHCzrvBt7vGxMGytsG7urO5qaaura2susC+qICurqyvsLi3uLW6s6+ztK2sramknZWum5uco5qYmaSkr6Crrauro12oZMqorapkoammpKmntqOcnJ2jnaSckI+WlJacn6evn6Gno5yam5SUnJOYmJqamZeinp+jnKCbmKGfpZmjjpOWoJyUkZGVlpOblZOli4aIiomOiJKYjpKPjICLjoSBgIOCfomIjoWSkomTkqaPhH97f35+iYaDhpCYlqOIlImHg4KAiYOBhYOAiYaKiYiJhIWAiYF9gYR9fJGKhH9/gIV/fYGLkXd8f31+foGBiozFnYqPg4iGhIN9f32EhoeKh4GFiICEjcB4e3yEh4OGj36CfX56eXp8uIj2g4CIi4aHi46ZlIyLjIyJhYaOiIaOjImFgYeHhY6GiIuRf4GMjp6ShIWHgYiAkY2FgvCAkoGMi4iIipKYtH2BfoWRiIeNi4OHhZGOk4+TkJGOiZmWmoWFjY2OkIiPkpGNjoiLioyNj5WUm5SMh4+LjoiNmZaMhYKIhYeKiYqJlI2Slm2alqKZlaKenZuZmpeYlJSQkY6RlIzcvJOYj5SWlpucoJ6goKCko6ShmpyZmp2WmJGRmZmbmpiSmJGVn6Cfm6Sem56hn5SepKi8mpuYnpmio52moaegoaenn6GlqKajp6uqrq2tsLGwqaiuurWsgKGmqayxp6asqqilqa2oqqalp6Srpq+tqKaitLqytLesvrOsqrGCvKeop6KrrK3Fq6+0s7O3trm4sK3hubDAt66prrC0s7S1tbK6u76wucCurbG3r7G6vri2uLi+uby1t7C0tbWwusm5srW3trK9kbW/sbixuL7Cv7q+ubi1u8O6gKq3vLp1sbGwsrSxt7e9urSztbOwtLe8ucC8u7qyur66u7a2u7a0uLe2tr2+vr3BvLq7vNTFsLm8uLm2rri2wL62tLm+vrW12766vby6v72/vb27wMB7vcK6wrjDx8m9u77Fw8DH+cS9wbu7vMC6ur/DwMS7vL+7usC7vby+vr3EgMK51MS7ub3Dw8PHxcbFwLzEwsHDxbzEw8TMz8TDvcDBv8LBwcDIx8XBwMK/vL/AxMHAwL+8vcC7wMC5vb+7t7u/wcXEvre4ubu6tMDGu769vr3Cw8m3tbi7vrq0tay6u7y/wsa8vsLEu7e7uLq8vri8wMHBw7+6vsDBu8C8v7fQgM28uMnJv7nAwMC+v7/BxMjEx7HCx8jAwsXHv8DExb+/w72+vsjKvr/AxLq/vb68wMLMwcLMycXEwMm+vtTCvMLAtbzEabq4wcG9vr2+uL/Gvbq6ubrCt77CvMDAyci+vMG+wcDCuru/wcC9wL2+ubq/tsC9tryytL25wbK6s7G7gLq4r7K8tri1u7OxtrKxr7a/wrK3tbCzsrSsray0rquws7O1trWxravLsbCvtbCrqrCxtKmqrKepoF+jas6kr6p0oaeoqK6qt6aioqWrpq2pnZ2jnqCipqu0oaapqKOkpqOjqaCjnpyXlpSamJuclpqYmKKgrJ+plJaZqKOblpKWgJiTmpWTpo+Li4yNkImPlY6QjYuMkJCSkJGQkZaRkoiQkoWRkaGajomKjoeHlomGhIuOjJ+BioJ/f39+hoKAgn97gH+AgIGDgIN/jIiEiYeDf4+Lgnx8f4N/gYOMn3l/f3t8eXt7foOok36Cen9+f358fn6EgoOIhX+Ag32Bi7h6gH59g4iBgYt/hIF/e319f8aF731+f3p6foSOn39+gYOBfXqEfX+Fg396dnx9e4R+goqVgIKIj5+Rg4OAf4N8jIiBgPCAi32FhIODhIqPuXh5eH6Jh4KGhYKFgo+JhoWGg4SBf4+Rm4GAg4SHiYOHh4SBg4CDhYqNjo6Nk4+JhYiJgIqJjZWSiYSBiISFioqMh4yGiY2Lg4yDf4eEhYWHi4aLio6JjImNkYjKrY+Ti5CQkJKTlZWUkpWWmJqWk5WVlZ2WmJOSmJeZlpWPk5KUmpyXkpuXlZebmpKan6PgmpyYoZujop2in6WdnKOjnJygo6KhpKSioqKho6Oinp+irKukgK2xtLe9s7O5tbWztbqxsq6trKuwqLKurKmfs7uytriswLevsLZ2v6+xsay3tLfPrrK2sbG0sbe5trX9w73Vzb61tbi8ur3DwLrEvsKzvsa3trrAur7DyL3AwsHLxsjGysTHyszJ0eHNwsXLyMHNm7/Kur+0u8DCwrzDvb+/ydXOgL7O19KDycTFxcXDysrVzMnIzMXDwcfJxszHw8O7wcbExcPEyMTCxsG+u7/Av7i+t7i5u9HEs8HFvcO+uMTF1dDFv7/GxLzA7MfBwsG/w8DFwb64vr6FwMa6xrvGyMO9vL7Jx8TM68rEx767vcK6vb/EwsjAv8PAwcnGxsXKzcrRgM7C3c7BvcPIxcbGxsfGxMDMzMrLz8fQzM3V2dLOyM3Qy8vMycLMysnJycvLx8zMztHOzdHOzczGyMvCys7Kx8nP0dTPycTDwMXCvszXxczDwsfLzNTBxMzS2NHNz8DN0NDR1NHP1dve087V0tHR0srP09bZ3NbU2dvc1dzb3tb3gPbi4fj46t7r6Oji397e4uPd4s3k5+bc2dzf1NHX1dfb5d3d4ent4ODj6Nvh3tzb4uXu4uHs5d/d2eXa2/bl4Oro2uDwgt7a5url5uLo4+vz5+Lk4Nzo2uPo3+jp8vLi3uPe3d/f2dne39/e4+Li2t3g1t/Yz9nK0d7c6Nfh2NrlgOfn3d/s5ODY3tHN083Lytji4M3T1M/P0tfT1M/a19Lb3djX1dHLxcHqysvM1c7Ix9DS2crT1tHUyorJgfjEy8aNu8HAv8S+zbmvsbK8t8G7sbG5trm6v8bOvMDCwLi3ubKvua+1s7KwsLC4tbi8tLm4tsLAy7vEq62ywryxrKuugK6osKuryqekpaSjppymq6GinZqcopycmJmXkZ6ZoJSfoJSioqafmJWUmJOSopaSkJmdmqyQmZGRjpCQmJSSlZCOlZSUk4+Ti4uFjYiEhomIhpmckI2NjpWSjpKcq4qPk46NjZGNkpa1pY+ShIuPjZKIhoWNjI+QjoiJjoWIksCBgISHi5CJjZSGioSFgICBgfyN/4OJi4KCgoePxYGBhYiHgoOMhoaMi4iCfYWEgYuEhI2ff4GLj5mTg4GAf4WCkI2FgvWEkYKNjIyMj5ig1oSGgoiWjomQjYeOiZKPko+Vk5SRj6Wqy42OlJWTlo+SkYyHioeLio2Nj5CRmZGLio+QgJKOkZuYj4mGjIuLkZCSjJOLio2NhJCIhJGNjo6PkIyOjpCIjIyRl43GsJaZkJGQj46QkpOUk5idnqGgmZ2XmZ6UlpCQmJmXmZmTl5OYoqOinqSenaGnppukqq38oqGbop6jpJyloamipKqooKGmqKWmqKqpqKqqq6urp6istreypX0Bfsh9AX6VfQF+0H0Bfv99730BfvR9A359foR9AX7jfQF/6n0Dfn18sH0BfP99vn0CAgQANqiosrCtqq+xsq683riwt7iyubW4s6y0tLCrn6iwt7Ksx8DBvbW9wb26ta63t7m7s7Cur72ysoWwgK6wtrS8wLq9vLzBvca7x8K90cq+w8W+ucG8wL65vLrAvL31usC4tsK/xMXKwsLBvcO6tsG/uLaztLOyubbAuba5tMnGub6+zsfTzsHAwsC+u8G9wL26u7e/vcPCwsW/xsfLwMDFyOXOyM3P0svGyc/GzsvAysa7uLq2vb7Bw8DBgMDFx8nLytuDz8jIx83GwsG/wb29vL25vrrDzc7MxdXDxcbLy8rPz9LUz9HY0M7O0NXRy9XRzcrOzdbPz87UycPQ0s3JxsfJw73DvsHBztDHzMW8v7a0uMjL0cTJyM7Cwb3HzMi5t7e6usLGwsnNw8zEx8rG0sfFysDCxcnGzcbFgLq4wr7GyMPF0NDM1svEx8bMx73CvbrEvMHOuLTGwsLFw7fCub29uLnCu7u2w7q6xMXAysjIw8K8zcW+2NB9wsPIxL7ezMi8wb/LwsHAtrivsLa3w8G9xcXF2tPHwLaoq6etsLO2tMCAwMDLw8LEvLu4vMi/vrm6ra+yr8q5vsHAgMbhxL63wb26uMPEw8DDwsnNxMOzwr67tra0s7G0rby6va+2s7Grt7Ovt7e4tqy1ury5uLfKvrivfrm+v8PAyMTMw8bH0cS3u7a8wLm9sLbIrLusqbiwrbCys7S2uLqytra7t6qxrqatt6qtq52otKuppKVnrKytsaewqqykqkKtgLSvo6qoqq6nrKy5rKqmqKOkqamkt6Kbl5yfl5elnZ2foaSYmJmVlp6oo5yRm5qdo6SeoaGfo5qinqSenJSNkZSOkZaUnpuZmZGUkI+Uk5SXkpKbmZWMiY2LiYiGiIKEioKKkYufhY2LlG2QiIJ+hHx+f4OCgpyFiZaJiI2TkYuIgIiCgIN5goWBhoKDhoSJepJ9ent6eHp8gnp4f46GfZSDg4l6f4GCf4eEhoKCkISHhHt/fX6Ih32CgqGHhIKFg4aDhIN/hpOBgJWKhICCfHx9eXV7f4WJh4GGiISFiZKXpo2Vk4uOiIONgYCJ8YOCg4aIjIyHi4SSioSMi/S0iYGOgIeKh4mHhoOKhI2EiYmIjY6IlZKKhoaHh4qOj5GPhoiNjZSUiouIhouMi4+GjYKLj5iVl5GViZOslpGQno6RkJWQkJCSlIuLjZCChImIjY2TjJWPk5eTtJCWkpGZnJiZlJuWm56ak5CTko6Pl5eNlpmdnZyWn5+Xmpqbm5idmZ2eRZqenZyYnJqbo52iqKmjoKGinqegmJ2ZnamtraWkq6XDoZWSm5udnLCwo6etrqqrqaWrqrWxrqvJsqews7O2sbOvrKukroCnprCxraerqqqnscarpKirp6unrauor66sq6Wss7q1sdK8urSuurm1sa+rsbGytK6vrq+9tba5uLe6vLSysayztLGzs7q/v8C5wbqyxcOwt727trm4vru4t7W9urrysriysri2t7i+tre2tbq0sb6+u7i2urq8wL7Dv7u9t8S+tn66tsC2wr62s7i3uLa/vsO/vL63vLq5uru4tbm8wbm6v8DXwbu7vr67trq/u7/AusPBuLe/u8C9wb+6uLm9vbu8vMdwu7e2usG7uby9wL67vcC9wr3FysnDvMa3uru9vr7CwsfExMXJv7+7v8O9uMPBv7zCwczIxsbMxcHMysaExYC/u8K9vLzIy8XMxcDDwLzAysrPxcfDxb68ucPGxMK/vsLDyMzIy8fBxsLCw7/Kvb6/u76/wr/Gw8G6ucK6wsK8vMTFwMi9t72/w767w8G8wrzD0Le0wb/BxsK6xMHFxcHDycK/vsq+u8HDvsXEwr/AvMe/uM3Lc7q8w7+60MXBuoC9vM3HxcnAx724wb/EwsDGxcbh1srGxbnCvsLCwsbHz4PIyM7Hw8a+vru8ysLFw9HAwMW/0sbGxsXG3sfAu8bBwL/GxcS8wb7FycLCucbEwr/Avbu5u7fEwcW4wb67tcLAv8bCxcS6wsXEwMG9zMK/uoa7u7y9u8C4vbm5u8a9soC2srK7ub22vcy1vK6vuri0u7q3t7W3t6+xsbe2rbGyr7nCtraxqrO+srOvuXK6r7S0qa6srKWpVq+/tqStq6ysp6qsuKelo6Wkpqmvq8yvp6OmqJ6frKKlpamuoaOioaKnr6yhlZuZnJ+fl5qbmp+YoqCwoqKbkZejl5iVkpmYl4CUj5SRkJKRkZKMjJSSkIuLjo6Rk5STjI+VjJCUkKOHjY6VcpKMh4iMhoaJiYeDnIOHooiGi5KRioiGhIGDfIGDgIKAgoWGkYGgiIOGhoODhYZ+eX2KgnyYgoOUf4KBgYGGf4J9fomCg4B7gIF/goB7foKfhIJ/gn+CgH9/eoGRe4B/noqDgISAg4F8dnp7gYJ/eoB+e3yAhIq4hIiIgoaBfoh+f4Xvf3x7e31+fHl7eYaAgIWJwLCHgImDhIKDgoJ+hIGHf4SCgYWEf4uGgX5/gIKCgoGFg3+AhYWJioSAfn6DgYCGgYZ+goOIhISCh3+MrJCMjJyPj4+TjpGLjI6Ii36MjoKEiYqLio6GjImKjYeihY6JiYyNiImGjYqPkpOOjZCOioyQkIuQlpiXl5WZm5OUlZWWkpWRlZaSl5WVk5WUlJmUmp6cmJSWlpGalpCXkZSdmqGcnJ+cxp6WmJ6goaCwrJugo6OfoJ6dnp+koqCetqSbo6SlqaWnp6eloquAuLjCwL64vLu4sL/bubC0trK6tbq0sbi7trOpsbi/uLLMwcC9uMLFwb25tLq6vcO7vLm8zb69vbm3uLq3ub28w8W8w8TEysXKvszFv9TTvcDEurO6tru6tLu8yMbI8sLLx8PNxsrM08jHxMbMxL7LzMfCvb+9vb+7xL++v7vPzMOAyMXTydbTw8LGx8vK0NHT0c7OxMjIycrLyMTKztXKyc3Q99PKy87Ox77AxcDFwrvCwrm3vbvBwsTFxMbEycXEwb3ResK+wcfPw8XGxsfAuru5tLe3v8nKxsDRwcDBwsLHyMnMy8rL0sXCvcHDv7vGwsPDy8rRzs3O1MnD0M/KysqAy8rFvsfFw8LO0crSy8PHwrzAzMzTyMfHy8TCwM3R0crLyc7Q1trP0NDFy8nJzcnVys3MxM7P0MrV0MrAv8bAzNHLz9fY093Ow8rHzca+xcC6xb/J2L7A09LX2tfO19HV0s/R2NLS0N/U0d7f3OPh3dva1+Pc0uvpidnb4N7X8eWA6N/n5PXx8fPm69/a3t7l4dzj4+H88+bh39Ha1NrZ3eDh77Hw8/nz6vDm5uTj8Obo4u7Z2N3Y7d7h397i/eLd2OTg3t/p6efh6Ofu9O3u4fHt6+jk4uHf4tzq6O3Z493Z0t/Z2uTi5ejd6ezv6+vm9unl3J7e397j4ujh6eTm5/OA6t3h3N3l4eXa4fPR28zL29fR0dHSz8/V2tPa3OPh0tvY0drn1drUyNXl09TN1oPf09jXzdXS0cnPg8/YyrjAvLu9tLq8x7u4trq4u8LGw+TJwcHEyr6+y7/CwMLHuLm4srK5w7+zo6yqsLe4sra5vMK7xMDNurmypKu4q62oqbMSr66vp62mo6ipqamioaqlpZuahJyAmJyVmZ6VnKGerZWgoaqOrJ2YlZyUlJiYmJSxk5eol5SboqCXlpORjY6GjpCMkI2Nk5GWh6mKhYWEhoaMkIyLkJ6Ui6WOkZ6KjJKTj5iVk42LmI6Pi4KFhYSKioOHjKSPjIeGgYOGhYmCi56FiJ2SioaMh4mMh4KGhYyPioKFhn+Af3+Gjd2DiYmBiYODkIeJkP+JiIeHiIuJhIWAjYeBiYl7s4eDjoaLjYuLjIOMiY+GjouKj4yIlZKOiImKioyNjpKOhoeRj5WVjYuIhYuQj5eSmouQj5iTlZCUiZnKl5GPnY2OjZKOj46Sk42OjI+ChoyLjY2SipOOkJSOsIuUjYtqkpSQj42RjZOUjo2Ii4yGiZCMiY+Ul5WVkZWXj46QkZOSl5KanpuinZ+am5iXmZOZnZuWlZaYlp+bmKKdoa2tr6qoq6XepJ6an56goLOwoqeqq6mqpaOnp6+sqafCr6Ksrauyr7OytK+vvf99w30Bfv99tn0Bfql9AX7QfQF+un0Bfop9AX/qfQF+/30DfX18j30BgP99yn0CAgQAgLGysqyur76ysa21qcK3s7G3uq64raympK2vr7Gwur26u7q+wLSxu7zCsbnSssezubSysruxrbO2s7S5vL/Is7ezs7a4y7+/8s7NxdTT47+4vLzBwMK/v9XVvsC8u766wLa6wsbBx8rJy87Cvb3NxsO/w768vrm8u76+wr2/x8bPgMfIy8/AzcvEw8DGwcTFwLzJyr+8vcW/xcTFycPEysvGx7zHxcjHy8TFzsnJxs3Cw8TAwL3PvsDDvbfCwcTKydLR1NDNw8fAwMHHysXNxcXJysfKv8bKfM7NzM/Re8PY2NLT09zd39fX3NzZ1c/U2NfPzdjWz9TP1NzY2dXY2tPdgNzPz9HL/tPXy8/Kx8nCwbu5ucHI387HzsnMzNHLzsq+xcC9xsbEwsvNxsrQ0tHGx8nTy7u/vb2/wcHEvMbRylvPvsrfy8HDv8nEvsTHx77Fv8zGxLjCxcbJy8HQxc2+w8K2t7i1tLq9trm6vMDPwMXH3sTKv728w7+/4MrFwsHIgL/DwdDVv7i2tbGvuLu0uL7DxNK6xNPAyr26urKzsrK0u7G7v8PGv8rIxcLDwMa/vry9v7W6srm0ub+7wcDBt7S4s722uL27tr29vru4tbK5vbm2u7q5srm0tLy5trCntLa1ta+4uLXArbO4vrq8wL/Hwrm8v8S6uru9v7O1v8hygL+yx7zKvLq+uLq6urypsLG2sau0sr7Otriyuri+tbNutqy3sLWzr6ynpKihq6ewqqSssrCmqamprayqrquvrKCiraKbopynoqKnpqioq6/Ap52Znqikna2qoqqopaWbnJ6UpniejJWdmp+gm6KlpKCapZ5wpqOglpiYnJiZnJycgJeZm5Cbl5iXjoiUnpicmJyYjpKNjIeFhIiAhYGIiYWOk4yPj5OJhoCAfHp7e4B4fn6Be4KJk46Ef4OBg4Z/e4KBfoiHkImDg4aIkYB+eXx8fXeBfH5+g3p2sIN5houFhYOGhod9mYeCipGBgoCEfH2Ee4aYeoGHioaKiIeGhaiMgIaFgnt6foN/g4N/e314gIN/iIOJh4CKiI6KoZKSiY2JlImHmXuBfn+DhYCCf4WNgomQiYeKkZOQiYODg3+DhYiWo4+QjIyOk5KPk4iHh46TmIyVhoiIioGFh4eLio6HjJmhmYiQioiKjY6SkpGSkJCQlZaQjYiPkpKPlZSalZ6hgJiUlpWSjY+NkpaUmJWdsJuemJWTkZiUn42bjpaYk5GQkY+alpGKjIyLj5KZoZ7N6pqmmZiXmYydjJmZmZeVmqCcl4+Ylp2hoaOpdK6pqaelmpqqq6mppKOioJyjmpqdnpuhsbGqqK2xqqyqpaWopauvqLCxtrKtqqy0sKutrazVgKquqaalpriopaWro7uuq6uxtayzrrCrq7KysbGvsbSur6ywsKmlrrC3qrTWr8Gwtq+ws7izsLW4sbW4u73Ctry2trS0wry13MC8tMTN6rWzuL3Bv727us7Jt7W4ubq1ubW4vL23u8G+vsG3trbDvb69wr69wb3AwcDAwr29wr3CgLu3ubqvubqzt7W9u7/DvLjFyL6+wMPBxMDBwry+wsC9u7e/vLu6ubOxubS3uMG7vL26urnFtrm8urW9ury+u8G/w8W/u768vLy+wrzDvb7BwsHEub7AgcHAvMDBe6/EwL2+wMLDw77AwsHCwbvBwsbAwsrHwMXBwsbDx8HFxsLXgNXAwcLA9sbKwsrIx83GxcTBw8nJ59DBxb/Dxs/HyMS8v7i5wsbCv8XGv8HMyMW9vsDNxb/EwcLAx8LCuMHLxGjLucTeyL/AvMS/ub2/wbvBvMe/v7W6vLy+xLvMwsu+ysm/wsO/u8XFv8C8vcDJvsDE2MTJwL68xL691sfCvL3FIr3Cv83SwcHDxcHDxcjBwcXFxNK9xdfGzsLEx8DDxMbKzsCEx4DBycjEvsO+x8LDwcjJw8nDycbJz8nMyc3CwcXF0czL0MzHzMrJxcPAvcXKxcbJyMa+w8DAx8TBv7vDxMjGwcfFw9C9wsTGxMbDwMfBt7y7w7++wcC8s7a8v3C3sMO70MC+wLy8vsHHvry3vbi2vbjBzbq6r7SyurSybrKtubS4uIC2s6+prKizr7m0rbjCvLCwr66zs6+ura6yqaiurKipp6iopqqvraqxtMuuop+kraejr6ykqqmrqKSoqZ+wgaSUnKCdoqGboaOhnpigm2uhm56Tl5ealZWam5uWmZmSmZWWlYuGkJmTko2RjYqMi4+Ojo+Ti5KNk46KjpGKjoySiICMio6NjImMkYeKhoiChYuPi4N/goSFi4WBh4WBiIOFgn9/hIaZhoSBhISDfYWChIWFgnu0gXmEhoGAfoGDgHqPg4GDiX+Afn9+f4F6gp98f4aEgIODgn+BpouDg397fYGEgYOFgX19eoB9eoF+g4B7g4SHgJCGhH6CgoqEg6N9goB/gYOGgYB9gIN7goOAf4OOjoqJgYKDfYKChZCViIiDgYOJiYmLgYGAg4eLgYuAgYGFgIOCg4eDhHt9jZKKfoiJhIODhIWEhIWEhISMjo6MhYyOjoWKiIyIj5GKh4mMioaGhYyOio2IkJyOk46Mi4uPipSDkIeOjo6MjI+Ol5WSjVqMjIuPkpebmL3gmKCZm5uekJ+SnJ+amZagopuXkpeXmZmXmJ1vopicnJ2XlJ+fnJqTl5qamKOcn56dnZ+op52boKGeoZ6cnqGeoqWjpaWopaSgo6qopaiop8uAv8O9uLi3yLiyr7itx725t8HGu8S6uLKxu7m4uri+wby9vL3AtbK5u8Cvvd2uyLi9u7e9yb+4vL64ubq9vca1vry8vsDTycP0zsm/zdf3t7O4u8DDxsbG2dXEw8K+wrvBu7/Exb7FzMjIysLBwNHJysnLxcHDv8HAwcPIwsbNytOAysbLzb/NxcLExc3JydDLyNbUyMPHzMfKxsjMxsrQzs/Ox9LP0NDPyMXLw8O+xLy9vr/Aus26vcXBvcnFxsbDycbMzMe+xMPFwcnNx8zIwMrMxsjAyc2UzszQ0NCKuMrJv8HBx8fLxMjMy8vKxczN0cvO2NTM0srQ08/SzNLU0eqA6czNzMr70NTN1NPR0c3OzMnHysz21MLGwcjL08rNz87Uzs7X3NXO0NHHxs7My8XIz93Ty9DJycbKxce+ytfRjuXI1vbWzs/Jz8zDyMjKw8vF1MzMwMfMzdHVytvR2crU0sfN0M/Q2d3W2tnZ4Ovc3+L44+jc29rk3dz06OPe3eaA3+Hj8Pfl4uDk4N/k6OHg5efk8tvi9OHs39/j2dzf4ePq1+Ho7u/q8O/p5enk7uno5ujm2+HW3dfY4d/i4eTa2d7b6OTk7uvl8fHu6ujk4eju6+vv6+vg6OHh7Onl39zm6Onm4ezr5/fh6ezx7e7t6e/p3ODd5N/h3+Df1uDn7YKA4tnr3/bo5eXk5OHi6Nzf2NzV0NfU4vPc39bi4uvk4Y7h1d7Y3NrV08/LzcnV0NrWztzl49PV09LU0cjJwsXGu7vDu7i9ub26u8HGxcTN1vjOwb/EzsfAz8u+xcDAvLS4ubDGpLilq6+ssrOrtry+vra+uIbEvb2ur7GwrK22treArK+wprSvsbCkn6ivqKeip6Kgo6SmpKGeoZmcmZ6cmJyfmZyaopWZlpuZmZqbn5WblpmOkpyhnZOMkpKTl4+JkI6Kk46VjYuKjZGmi4iChoqIg46NkJCSkIvokYuWnJCUjpKSkYeaj4yLkYSHhoqGhouEjaqEh4+Oi4qJhoSGppSAjY2LhIaLjYmKjIiGiIOMjoiOiI2LgIaDiH+RiIiEi4mTjY2ihYiFiYyPiImHi5GEi5CKh4iQlJGNhoiJhYuKjJmZjo+IiIiMjoyOhYaEi42Rh5OFiYyOhoiLi5CMjoSIm6CUipKPjJCNkJKNiY2MjY2UlpaSiIyOjIeMjpGLlZolkZGQkY6JjIqQlZOXlJ2xm5+YlI+NkI2aiZWJkJCNioiNipWVk4SQV5aanJ+Yt9aNlI6OkZmOoZKgpKChnKmrnpuVmpqbm52fooezoKKlrKairKqlpJuioqOgqKCgoaKhn6emmpugpqSpp6Snq6eqraesq6+tqaWos7CutLO16/99130BfoV9AX7ifQF//33AfQF+n30BfsN9AX6PfQF+/33/fdh9AX61fQICBACAsrKwrai4r7G1q7bAsrG5ura+uLOyq7CxsLC7vL7CvMC7vcDAwLq5tLmzt7+0vMu5urm5uri7ure3t7S4tcXEu7u4u7q6vcbP0cTQzsXCtrK7v8bOxci/0cbKxMHLx8LDysnR19HIyNaPzsjNx8XNysHDwcLHxLjGw8fHyMvKz9GAz8zR0MrHzMvKwsPDx8PCwIXCucHDxcTVvsK8yMjLyMPFvce+x8zUzs3P0dXX09PUzcjEvMjMw8HDycfHxdDY3M/QyMvOyc3OzdLP8dTRz87NycHIysbNy83Pz9HLztLU2d3T3d3g5tzX3dbN0M/PzsbM9NbO09fX3dbS2dbV0+aA3c7RzdHS2drT1tnVyM7H1MzRytDMx8rTzfjg2NjP1dbQzcvTzcrI0MzT18/M0c/Txc7Mz9HMz8fGysbC3dDK2NDLxsjGxszNzsnGys3Dy8TBxsi+vsi9wMHJy73LyL7AxL3CvLi4vbe9v8Hg0sfK1tV5z729u8DFwcHDx87Pw8qAxcG/v7RwvbWysquzsbm2vsHBv7vBvczFwsbIxry5vcC/sre3yMvNbcjAvrm+wr3Bz7y0vb23v7a/w8Czusy1tbxswsS6t763xbq+ysa7wr7AwLq9wcDBwsXEw8TCw8HHvrO8w7/AvLq7ur26vre4vbvFxL2+vLe5ur3Cr7i1ubmAuLS+fLy4rrG2urq7uLazsbCxpqmyt8C5ubvBw7qxu7u4t62pr6i0q6ikqbPLn6ulpq6qp66tr6mtrbOvr6mioKOfoLumm6StqKujo6uonJ+XoKGan6Obm6SkpqGloJilm5qdmZKYnKmlnqWelqSen6edn5umv6qhoaCenJ6cmqCAnZWcm5uamJWWlKGdn5uhnZmSkpGMjJKFg4SKhIeIkIyPqJSWiZCWj4WJgYJ5fXiAf4GJjI6Mi4SHfoB+gH59iH2CgImKioiAgoR7hn+AgHqKhI2BgX+KhXh+hX+Ii4aJiYWCiIeCg36JgYF/f3uDhIGIiIaAiYuGhoWDgoOFiYuAiIp9foh/fYOShYWBgH99fYGEiYGHgYWIjaGsk5CHkYyIhYeHg4eChXuBgIWOg46ChIeIgIGFjpCBiX5+ho+JhYiNjpyMmZa0uI+Sj4yJi4mLk4yNh5OUioeIiouHj5CSkpKWk4+NipSOkpOekZSPjYiNg4GHiZGTk5uelJufnKeAmZOelpSQipOTk4aLiYyTlZiamJKLiJaTipeampqXlqGak5iVlo+VlpuYlZ6enpqTl5yfl5KlmpiXko2Lk5CRnpqem5+dpqign6mqn6eoq6ChoqGhnp+spaWen52cn6Olpq2rsaynr7qys62spaSqqaartKu4sbiyv7azq6upra2ArayoqKSvqamuqbO6ray0trG4tLGyrLGysrO8t7i5s7WvrLCwsa2wqrOxtLuxtsCytbW0tLS2uLW2uLS6tcTBvrq3uLe2sbS8vrPAv7u9tbG4vb3Dtruzxr/BuL/Fwrizubi8wr22v8uCwL3CvL7BwLq8u8DDwrjEvr+8vsC8wL+AvrnAvru6wcDDvb2+xcTAunbDvcbFxsfZv8C3wsTEwr++ucG2vr7Avry4ubq+u729vr25tr/DwL7BwMDDwcfIysDCvsHDvsHHwsfG4cnHw8HCvri/wL7Ev76+v766u76+w8m/xMbGzcXBxMXAwL/Cw73C38nEx8nHycfFysbHx+SA18TGwcTFyMjDxc3JvsPBz8bLx8rIxsPJveTQyMW/x8rEvrrBwL+8w8HGycHAxMfLv8XEyMjBxr6+xMG/2cjDz8i/vL++vsHDxcK+w8jAysTAxci/v8S5vbzDx7fKyL7FycHJxMDBxb/DwcLi0MLFzdqAzMDBv8bJwcDEx8zJvscXwsTCy8F2zsfHx8PJw83BycXDxMPHw9CEx4DJxsXKz83Aw8DIy81vysXBw8bKyMrUx8HGysfMxczQ0cPJ2MXIzXLS1sjCxsHKwMHLycDGxcXFwsLGwsPDxcTDxMPCv8fCusTNy8rIycfEx8HFvby9ubzCvLy7ubm9wMS3wLu+vr69wnW+vra4vr++wL/Bvr++v7a4vb7Fvbm8vIC+trG7uLe5sq+3sL+0tq+1v9+wvrWzurWytrWzqq6vsKytq62sra2uz7Olqq+rrqunra+jp6OnqKWorKSjqqyrp6upla2nqKunn6Corqego56XpKCgpZyemaK2opiampeXmZmXnpuVmpeVk5SQjIqOjY+OlJOSj5KSkJahk5CQkYCLioqMhYehjomAiJCNio6KiYWJg4mIhIiKi4iIgod/hISHhoSOg4aCh4mNjYKChH+KgoB+eYN8hHx8fIRxeX2BfIOEgX+AgHyAg4GDgYV/gICBeoGDgYiGgn+EhoCAgH59e3yChoWIe3yFgH+Cj4aHg4GCfnx8gH98gXt8fX+QnICHg32Hgn57fn9/gX+CeoCCh4yFioKFhYV/foWMjYOLg4SMkYqGiIeFj3+Jh726hIiGgn+AfoSNiId/ioyEhoWCgH+HhoeJiouJiYaCiYSEgoqAg4CGgoeHhYSGjIuKj4+HjJGRmJKMmI+MioaOjo+FjIiKjY6PkY2NjIqTkIeSlWWVk4+OmpaPkpCSjpKRk5CQmZWUkZGUm56XlrGfm5uWkpKYkpKcl5aUlpWdnpeXoKCXnqGim5ycmJiSlZ2coJqcnZqbnJ6go6Cjn5qjrKetp6mkn6KjnaOmnqWgpqOvqKelo6WpqYC8vbe3ssG2s7Wrtr6xsLa7ucK9ubezubm5uMLCwsS9wLu7vr6/vLy1uba5vrO6w7m+vsLExMXDwMC9tbq2x8TBvLu8vLzAvsfJvcnFvcC6s7vEx9DGysLYzs7Fys7KvrrBwMTJwry+zI7ExMrIyc7Ox8nHyMvKv8nFycrM0czT04DQydDMzMfKzNDIzszS1dLWotLNz8/Rzt7Gy8fU09TSz8/J0sbP0tXS0MzNzc7Nzc3KycPEy9DNy8rJw8bGzc3LwMC9wcPCxc7Izc/w0dDHx8jAuMHDw87JxsnLy8TGxsvJ0MTLz9DV0c7S083OztDQys7z0cnMzczPyMfOy83O44DcydDKz9HT08/Q2tjK09Lf1tnN087EwMW65NPLysfU2dPQ0Nvg2tbc1NTSxMHKzNDFzM3PzsvNxcXIxr7RysPPzsvGysjMztDRz8vT2NDa0M7U18/J0MjNzNbWxNrZztLZ0t3X1Nbc2N3d3v7u39/o/pbl09PT3+jg3+bq8O3d5oDf3dvj2Irl3Nzd19vZ6drj5OLj4ujj+u3q7u/w6Obn6ebZ3uDw9PWD8Obi4OPp5ef14dng5N3m2+Hm5tbc99ne5IPv+O7q7+r37uz48ubs6enn4+Po5ejo6+ro6+no5O3l2uTs7O3r7e/s8uzy6unq5Ovt5+Tg29rZ3+TT39jg4YDi4OmL5eLX2d7f4ubn5+Ld2dvN0drk8Ojo7PL06Nvo497e0c3Uzd3S08zS3P/K29DT29nU2drXzc7Nz8nGv7u7vLq608G2vcTDxsHBycvAxb3GycLHx7y9xsDDu8C8hrizs7qzrrK6wr61vbOpvLa3wba+uMTrxLe5ure2uLSwuYC2rbGwsK6tqKGgpqKmpqyvr6utraWsuqaipaahoaGmnqDDpaCWnKShmqCamZGUj5eVkZeanJiXk5eSlpWZmZejlJqSmJmVl4uLjYiSi4uJhZGKkoqLiZBvgYaOi5OXlZSUkoyOj4qKiZGIi4qJgYmLiZCPi4iNj4eFhYOAgoaTlYCSlYiJlIyLkKKTko6MjImHi42OhoqFg4GCkZ6Jh4KQjYyLkZKQko6Th4yLjpaNlIaKio6Fg4aRkoOLgYKNlI6Kjo+NnIiUktTPiIqJhYGEg4eSi4mCj5GJiYyOjYmOjIyMjpCJioeBi4OIi5ePk5CTjZKNio2OlJGPlZSLkZeYoICYj5qRjoyGkJCUiZGQlZiZm5yXlY6KlJKHkZWUl5STn5yUmZiXlJmWmpiWnpmWk4+Ok5eMj6ybmJuWl5ael5mhm5qXmZigoZydpaWcqKyso5+fnZ2boammqqSpqqOinpyYmZmhoJuls6yzr6+ro6qroqiso6ylrKi0r66qqayxtOh9AX6nfQF+/33hfQF+k30BfqF9AX6ZfQF+wX0Bftp9AX74fQF+/33/fap9AgIEAICxq66qsLKytrSvr7O1tLC2srKysLiwt7W4u762uLe6v77FzcvFubu/vrW5urfbvb/Jv8jIwb+7uLKytby3r7HGtLO5ubbAwcXMyMnKytTKx87GzL3I0MnRy8vHxs3OzNPP0dTY0tzFy87N1czEyMzFvbzKxMjLwLfCycvN09fk4oDU2NXY0tfS08XDwsDDwMC7uLC+vcPHxszCx8jJzcTHzsXEzr7Fzc/M09nS2NPLztnW4ePa0sy7xMDFu725yM7L0NPDzc3P1NLU29HI19LQz8LCxsbIxMXGx8zW3NTX2+HW2NXY3eDS4tvd1dHUycTN0NDWy83V1tTX1dzX2dXY1IDY1NLP2tXW4tLV1M3GyM3SwcHJ0tbT2drd1NLR3t3c3tTI0Nzgx8zNz9PU2ODZ19bL1NPf3tbT0tLNzNXRy8PS0sXAys7DycvFyM7Hzr/ExMjBx8jKxse80cjOx8XFwcTAvsC+wcrgyMbJz8/T0MrOycjGvsS9vsK8wL3S18bIwYC7uru7vsu/yLa8uLOxwcS6ury+vLu/wb+3uGvKyNDMyMLIwsPEab3CwsjFyMHJxcTGwsa3ubu9ub+8xMbB1MvBycnDxL5keWu4tLbHxsbAw8bLwMLDzMjN0tHJxcXFycm2xMTAwcrRwLq8u7vDxMzGzMTAxsfPv7Svu721u7q6uoC3tba2t7G3r823xMK1b7mwwrS6q7W6drKzsbPEt7W6srCxrKi2sK2orqums6Wspamtr6mwsrm2uK+nrrC0tbOqsrOwq62ytqm6raqqrbWpo6qhoJ2fmJCTlZiXm5yfnpicnnWUnpyYmpSfkpyXoZ+ZnJmXmqKuq6inpqeknJWTnYCXm6ubpKqfnpucopuYmZibj5CXg46Kk4eMnIyGh4+Ph4u1t5SPj5GGhIyPh3V+en2GhYaCh42Oh42LhX18hniJeYCAhYKEhouUioWJh3aDhYuJj5CVjJKVhXqEgYmFjIqNi4aIhoWGlpGAfn6GgIF8hZF/iXiDh4WFkYGEg4GRioCLg4WGh4SIgYyMhYuGhYmIjoqPjpCQjIyLjYyCgfh/iY6Jh4KIhn56eH9/jYSGiYeJkomKqIWhl5CDgH2Ch4F8f4qSkY6PiYyTlYyNiYmOhoiIjYWJg4mIjomFioODkY6TlJ+cmZmSkpOPjpOQh4iGiYqMi5OOkJKalprUxL+csYCelJSZl5WOko+TloqOm6WRj5CWlpCTkZC0nZaYnZyYnJiZmZSalZOVl5SVm5mdmJeRmZybk5ibnZWXmJienI+WmpabnJ6opKurqaimoZ2opaykp6OiqaSjqaylqbSpqaWjq6u0pKq0vLS+xLGroaWqp6StrbGor6WurrS1vLWpqYCqpqypq66tsbKurLG0s7C0sbCysbWvtbS2tbmusKuusq+0u7u2sLS2ubG0tK/VtLS7sri3tbW3ubO1uL26s7PPurm7urS0r7K4ubi8vtTGw8bCw7i8wr3Dvr23uby/ur69v7/Cvsm3vr/B1MfAxsbBvLzGw8fHv7y/xMTDx8jY0IDAxMPEwcXEyb+/wMHJysvEv7rGw8fIx8nBwsLExru/xbzByLy/wsC+w8S+w8K7u8G/yc3IxMO4wb7CvMDBxsbDw8W2vr7BxsbGzMW+ycrFxr++wsPDwL2/vsHGycXFycvDx8XFysnFz8zLx8fLwcPHysnOxcjNysXIy9HMzcnPz4DTzsvH0MnJ0cXLzcvGyNLdysjHycvExsbMxsXDx8bJzMC3vMnKur3AwcXHx8vFyMW+xMDHx8PDxMnFwszIxMDPzsG/x8q/wcXAxcnEzMDGyMrCx8fLwsK2y8XKwcLDwcbDvcLCxs/wzMnMz87Oy8jGx8jJw8jGyszDxL7Q1MTHxIDAwMTDydbM38vPz8rMzc7DwcDFwsHIysjAvWrIw8rJx8jQysfGbMHHx8/KzcfKx8nNz9LHzM/Ny8zI08nG2NDM0tfV19JtgYbGwsDOzcvEyMfLw8HBysXK0c/Iw8XEyce4xsjDxs7aycTIyMjPzdTMz8m/xMTKvrm4xMbEx8S+wIC/vry+vb2/tsm7yMa6cMC6z72/tb/BeLe8vbK+sbK6tre8trPAvLeutbOyvrO4sba7uLO4tr21sqylqqesra+mq6uqqa2usKm5rauoqrGopa6lpqquqKWmqa2pqKyuraWorYOhqaalo5+pmqSepqGeoqOen6SupKCbmJiXk5KVoICZnKiWm5yRkJCTmZSTlJOZkJOaiJGQmouPm4yHiY6Phoe3tI2Kho2Fho+TjX+EhIWMiomEhomKhIeIhX+EjICPgYWFh4WFiY2Th4iKiXeBhIV/hIGDfICEfXiBfoSChoOEgn6AgH+ElY2DgX6IgX9+go+AhXqEh4aCinx9fXmJg4CFgH+BgYCCfomJgYeAf4KChH6Bf3+Be3x9gIN7evN9hImCgnyDgHt5e39/jYODhIGFi4WFnIWglpWJiYiIjIeEhYqNiYeFgIGIiIOFg4SKgoeFi4KJgYKEh4WFiYiBiYaIipOOi4mDg4OChYiJhoGBgoGDhIqGiYuRjpDEsqqPn4CRi46Qk4+Ljo2OkoWHlZ2SiYuNko6QjYuelZWWlJKQlZOSkI6Wk5GQkpGUlpSWlJeSlJqZlJmbm5eanJyioJWYm5idmpidmZycmpmal5afnKKdnJuXnZiWm52anquiqKGdpKOsnJyprqi8w6ipnaOooqSqp6ihp6CkpaeorquhpYC3sbWytLe2uLeyr7S5uLK4tLW2tLi2u7m9v8C7vbq8wb3FyMnBu7zAv7WztK7VtLe/ucPDwMPAvre1tby6s7TPvbrAwLrAu7zEwcHCwN3JwsjGyMHN19Tg2tnQzMzMw8jDwsLEvs24wcPH2c7Izs/Kx8XRzNDSyMHGzczN0M/j24DHysvKzM/I1MnKy8vQ1tbOysDOyszPztLL0NXY3dPX3dPV3M7S1tXR2dvW3NPNzNDQ2tbYzcq9xsTMwcPDxcrGx8q4wL7CxsnKzcnD0dHOzcrMzs7OysrKxcfNz8fHxszBxMTGz9DI2dPW0tHXzsvR1dPax8nPzsjHx8/JyMDFw4DJxMPBzMvN287U1dHJzdnj0M/O1tbKy8bJycfJ0dXZ49XL0uLl0NPX2Nva1NHLycbCy8nV2NTV1NjNy9bRysTU2M7M2NvQ09bP0djS2c7U09bN1NbXzM/D2tXZz9TW0trW0NXV2eP94OPn7u/v6+Xj4OPh19vZ29/V2Nj29+Pn4IDZ2NbU2ufe8t3l5d7i5ejc29vh3uDp7u7k44H07/Xz8Onu5+XmgN3l5u/v8+vx7Orv7/Dg5OXm4ePg6uXj9u/q9Pj1+veCnI/t6eX18/Dp7env5uXl8e719/vz8O/v9PTf7uzm6fH76eTq6+z29//4/fXr6+vz5drV5ejk6OXc3YDc29zg5N/i2fHg8evchObe++To2eTkieDk4trp3Nni2dfYzs3d19PN1tTS4dTa0tXa2NLY1eHX1s3EycrMzMvCysvJx8zT1cnezcvIy9PHws3DxcXJwLu+wcXDxcvPyb3CxKGuubSzsbG7rLeuubezvLu4u8PQw8C7uLi3sqyouYCytsWvtLapqKaprqqpq6qto6eum6entqGktKOen6Slm53d0KKemqCYl6KnnY2XlJaenp6am6GhnKCinJSapJWolJuZnZmZnJ6jmJWWloCQlJeUmJibj5SYjISPi5KRmJGTkY6RjouRopqNiomRjYiEiJaHjoWMioSGkoOFioSbloCalJSUk5CRi5uSi5ORjZGSl5KTjo6NhIKAhIV9f/mDjJaPkYuWkouKiY6MmI6Pj4mKk4uLqIK5o42GhIOIjYeHiJGWko+PiYiMjYaIg4OJg4qIjoaQiImJjYqHjYuGiYWHh46IiIuJjI+Mj5WVjI2PkpGOj5aPj4yUjpLJsqeVpYCWkZKTk46Lk5KVm42SoamZkZGVmZGTkI6lmZaWmJaUmpmYl5SempiXnJifopucmJiQkpSRjJKUlJGUmJqgnJKXmZSak5OZlZ2goKGhnpumoaykpKKfqaWgq66nqrKko56ZoZ+rmqKttrHEyaurnqWtqKWvra+nraetrrW0u7isr/99/33/fZ19AX6KfQF+n30Dfn5/xX0Bfoh9AX7LfQF+/327fQF8/33jfQICBACAvry7uMG8tbS3t7W3uLi3u7vDu7vAvcC8tbjAvLe5v8DLyMHOzMPKxsbFxsrEx8XHw8XGvb6/xL2/va67v7u2u7S1s7i/wsTQzMTDysjAxMfGw77BxsfEws3XycfSzsXJ39TT0s/hzsbIx9C+ycjDwsC+xsTAzHbRz8zP2czO29OA0tPR1NXUysrExMq/xsXDxczIysjLzMzNy87U0s3NzM7FynTTzsrCx9nR0dXm0tTRzdfX4dzW09TOzNHDzcjY1djMzMfOxr7ByMvKzM7V1tHPzNDTy9La9tXc39vZ1+Lb293d3Nfe4d7V1tnV0cfP1NnU0sXU0dPM19jX39fX2M6A8trc2trU09zc29jX49mh1c7N19XY3tPg59zf3uDj69Hm1dLOzszQz9LNxt3W22p42NrZ2NvIz9TFxcfHxsTO0cbKx8DFx9B7ynPTzcfIzM/M0dnm18zTz9DQ19XR6dXS2Nzhf8vNxsnKwsLFzc3O29PIz8TAxMfFwMG/vcLGytiAxcjAw7nAv7u+vba9v8XDwby9xMrPw8S8xsC9v8rN1MzNxcfNyc3DwsvQxsbJbbq+w7+8wNDExb/AxbnDw27BwbzCw8XHxcTCxMLJyc/Gys3NzM3Gv73JwcjPw8XIx8jHvsG/wMPHxcPGv8XGaGm9u7a4ur7Kzru3tbm5tbm2tLuAsrC2tb26sLazvmW9wnXBvLi2r622sbG2tba4uMW0sK+tsbCwrq2suKSmraqvpLGpqrWwsbGtvLa6ubi7vLOztLCyqKaqr6yorKKlqa+po6Wfn6Wdmp6co52dopuYo5uboZuSlZeSlZScnpqnqKyUnaahnpuhmpifnaifnJqclJ2Al5mZm5iZlJielqGXk5aXk6WTkZqOl46NiYSJi4aLi4iKhoaMh4aBh4aIh4iBhYaHh4uFiYGAhoiHgYGAf4R7fH2Dg4h/fH2BiYB9h4V1gpGLh4iEh4aAiZCMkY2Tk4KEioCDgn+Ghn5/fH+EfJSNfYDvf4J5foSGhoN+gH2dt4uAgYmLhIqT446PiI17fn6BhY2GqZOQlZuSjYyNj5CPlIiRjo6RkIiGhoCCgeuBhYODhYSDg4mHhoKIhY+FlIyMjIiLkpOZjpWOkYeHiovWkYmKioyNhoaHiI+MkY2SlJiZl5mjo6GumZGOjI+OkI2TlpOPj4uZoIuTmJqXnKCdoY2AlqGom5iVl5KNjJaKk5WglJKXq5eQjoyQlJqYlZ2dnpWSlZiXm5aWnZ+boqCko6OmmJydl5yOm5mZl5adnZOclpqYnJ2jrKSdnLKom5+coKSqqqyloaWrrqahnpyooqSlqKmwr6i4sLmupKWnpeGpqKyqqa62t7q3rrDAwbe8wLyArKurqbGvq6uwsK6wsbCvsLG5sbO1srq2s7S8ubO1t7a/uLO9urO3uLq6vcO5vrm7vL28tre5vbe5t6+9wMG+wrq5t7e6trW/vLq6v8G7wsLCwL28vbu6usDKurrFwbm5y8PCwLzXwr+/wtC/y8vGwr+7w8G9xH3LysXHz8PGz8WAw8TEycnKw8bDxMe/ycrHxcrJysTFxsXGxMPJycbCw8W8xYbFwb65wM/GwcbhwcG/vsbEysXDwsfFxcvCxr/Ixse/xcLLyb/GyczMycnQzsnFwsTHv8XK8cTLy8bExM3GxMjLycXJztDJxczMycTNz9fU08nWzs7Iz9HP1MvNzcaA383MysvEw8rKy8nI08yRwM7MzcrNzcHIzMXKycfO18DNw8HDyMbIyszGwNPL0nl3x8TExszAx8zDxsbIwr3IzcLDwcDDxs59xXHLw72/wcXDxc3YzMDGw8LEzMbD18bFyMzQdcXHxMrJxsLCxsXEzcfBxb/AxsrKxsbFxMjKz92Azs/GzcfQzcrOz8jOzdLPx8HBxsjNyMrE1s7LzNDP183KxMjQzM3Gx8/Wz9DWccrO0s7KzN7S0svMzsjNz3TNzsjMz9LWzszMzsvOzNHLys/NzM3HvsHKwsrRycnKx83IxMjFxcfLzcvOw83JbG7Hw77CxsvX2MbBv8bHxsfGwcmAxcTEvsrKvsHAxWnFyHbHw7++u7nDvr2/uru8usm4tLW2u7q9t7a7w7KyuLW4r7ezsLi2tLOut6+1s7GxsKyurqurqaqxtLKvtaqsrq+urKqop62qqqeoramnq6Wlrqqnr6ukqqumqKKmpp+nq6mVnqaknpicnJaamZ+WlJSal5wClpGElICSkpeVoZiWmZuXppSSl4ySjo2Kho6NjZCQj5KNjpWPjIeNjY2MjIaKjYyMkImPiYaJi4qGhoWCiYGBgoaGiYJ9gYOMhISMiHqAj4eBg4CDfXqBhoKFh42Of4GHfIGBfoWIg4WEgoN+lIh+hPyKjISPjoyMh4J/fZuyhHyAgXmAiYDBh4mGjH1/fn6Agnuag4CEiIN/foKFgoSGfYF/gYGCfn2CgYOD9IiNioaKiomIjYuJhYuGjoePiYeIhISIiI6Di4WKgoWJi9mSioyJi46Ih4iKj4yWjY2LjImFh5OUkKSWkY6Ki4iFgoaHh4WFhZGYhYiLi4iMkI6VhIuWnJGSknqTjo2KlYuPkpqWk5mom42OjJGRmZqXlpmVkJGQkJGVkpGUlJKZl5mVl5uSlJiUnJGcmJuZlZ2dl6SenZ2enp+moJ+ctaicnJeXmp2doZmXmaGkoZ2amqSdnJ2en6Ojoay65aumpqio3amnqaWjpqqqqqOenKyqpaqtqoC5trW0u7qztLm+ub6+vrq9vcS6vL28wb24usHAury/v8jDvMLAtru6ubq7w7a8trq7vb62trm+uLizqr3Dwb7Gv7+7usPDv8nEu7vBwbrDyMrKx87V1tTX3eXQytPNw8PWz8/NyefHwcC/zrzO0s/O0M3U0szVrdfTy8/XysjTyoDGxsfKzs7DzMrN08jR19LR1s/QysrLysrLzNbVz9LY2dLZi9rW0cnQ3tbS2OXU1NHP2dff2NTU1c3Nz8bNxM3OzsbLyc/Jv8LIycfIzNLTzMzLzc7Hztb6zNXZ0s/P19LS1NTSzdLV1c7N09PRzNXW4d7ay9fPzsXLyMjNxsbJxYDi0dTS1NDN19jY2drk4aL/2tTV0tLTxc7U0NbV1uHu1OTY09XZ2Nnc3dbO39reqpvQz9LV3NHd4NXU09HNydjb0dbV0tba5JDbgOTf1dne4t/j6/vn2ODc2Njd2tbr19je4vif3t/a3+Te2+Ll5uXw59/h19LX293Y2tjY3Nzj8YDf4NbZ0NjW0dnd1+Lj6+vl3d7l6vDo6+P18+/w+fL88vDp7/Hv9Ozt9v329fqD6Ozw6+To/e7u5uTr4urvierr5uzw9v339vP08fPy9+7w9PLx8uvg4e/i6/rv9fj4+/vz+PDx8vf49vnu+PWDh/Dr5ujr7vf55N7d4eTk5eTg6IDi4OLd6uji6uv1gOzvoPzn4eHb2eXf3+Lh4uXj9eDa29nd29zY2NniztDZ1NfL1svL08/Qzsza09nZ1djXz9DUzc7HyMzSzMnOwMXIzsnExr+/x7++vLvDvrzFvr7MxsTNx72/v7e4srm7uMXIyLC4u7eurLSyrra1vLSysLSttoCurK+wsbGrrbOvvLCpsbKtv62osqasp6mjoamppaajn6Kamp+cmpWenJ6enZSYmpqboJ2lm5ykpaWhnqChpp2cnKGeoZiPk5ObkpCWk4OMnpiTl5OYkpCWm5ecnKCmkI+WhY+MhI6Ri46Njo+Gm42Bh/+KjoaRkZOTjomJiL3iloCMjpGJjZbClZaPlYeLio2OlI60lY+SmpCIh4qNj42Ui5SQk5OUjYqOioqJ/46RkIiLjIuKj4iJhoiDioKOh4eKh4qPj5eLlIeKgIOIiNGQjI+QlJSOjYyMkoyVjpGSkYqGipeYkqKZk5CMkJCMi5KUl5GSjp2jh4uOjouPlZOZh4CPnKSYl5eXlY+MmIqQkpyUkJaooouLh42OkZOSlpualZiYmpacmZmdnpuioKCbnp+Tk5SPl4mWk5WVlJqclJ+XmZeYmJiempeVsKeanZycoKOlpp6cn6utqaWkpa+jpKampaepoq+566ehqKqq3qurrKWmqrKztLOmqLq3sLe6t/Z9AX6rfQF+632Cfp59An9+l30Dfn1+mX0Bfsl9AX6PfQF+rn2Cfpx9BH59fX7/fdB9AX6TfQF8uX0BfP991X0CAgQAgMnCv8PCysDCv8azuby2ury4vrq8wMa8vsPDwb29wcDAwcO/0MvAwMXCx8LBytHDz8q/xLm8sb64s7K4tb/Fwb260M7IycLO0tDQxcDAysW/yMnLzsrPydDKw7/EwsbI7ODS3tjW0dPN0sDDyMvKvsbHz8TNzJnez9vGxtTP1dvTgN3SztbNyMfNxMLEw8W/wba6usnIzMrNw8zR1MnS1cbKxsXGycnXx8nM1tLX09PV0MjM19jY08jS197U19Hb1c7JxMfM2OHV09LO2+Ha4dXU29DR3dbP0NHJ0NfW19jX4t/g7eDk3dzZ3dfV2s/P1tDGytTU1dfT0tTRztzf29bTgOLT3djY5drk393a0MnQycjYfOvg3Njg59rU4uji4d3b3d3m493f0tPOw83S0dLZz9Xd1tLYuHTMysfUx87Ny83KzsvH0s/U0NjRzcjC0NTS0tHQ2c/VxsjIzMLSbtXQ09Dg6NHDysPCycXAb8HFv8DCyMXGxszJxsjLzcjKucXGgMPAv8S+xMXAx7+/ucfHxMvFz77IxsLGvcO7vsHAp7/EvrrAx8DHx2dpysO7wMO+vL7DxcNrvL7Ew8LEwMPBvsHCwcbMZc3JyMfDzcnCvsC9ysbGysvHxL3Dz8nCys27xb29wsnEysPKy2prZcnKxmnLysfJxMPAv7ixtrK1tLe0NrO3v762rba5tLG1tbevvbi8u8C3trq3uLm8uM24r6u2sqmpsaxdraKluba7u62orbypqqirqISwgL3BxLKytLG4sbK1tKewpqCgpqi2oa2np6OuoaKfp6anq6q2qambn5Sam5yooambn6yk66uamJ6ZmZWSm6SqoqejpaKmmJqYnJSXlZWRmJSJkpeNi4eQl4ejlZmYkY6ShI2OiomMiYt9goOFhomKh46Kh4aCjIF/fnuBhX6MlYKDgI2CeX2DjnqCiYGKh4OHgn2Ae3l1e3yGhoeEjY2Ig4mPmImPjISIioeDfXyFfn2ChIWAgIKAeHuOen58gX2AhoaBgoyNi46Mi42Lk4iEiqqDgIaGhoSKiZKQjpehl5SfjJeNhouNi4yOmpaHipSPjI6PipCWjpONi4eCioeEfoiHgIJ/iouCiY2Xl5ubkqaZkY2NjIyKhpCRmZiVlpGNkIyNlIyRkJeamZGFiIqOmIuLjpiNmJGKkJCKj56eoJikoJ+Xl5mim5CRk56RkY6PpZKdmZqVnKSPmpeSjo6JipCTmaGgoKSgnqOloJuZmpybpY6Zl5+moaSoppqdo6aloJaVRpSampmYoKyYmZuoq6Sjoqikq6GloanEt7DLp6avp6WorKqmo6WlqKWjoaqnqbOxvKajqa+tr7Kus8i8tb67r7O4ubm3useAuLGwsbC3sLWzuqmyt7Czsa62srO0t7S2u7e3tbW6trO1t7PFvre2vrm9t7e7w7vDwbi+uL25w7u3u765vsO/u7zQ0MPDur+/wcK+vLzGxLzCvsHFvsK9w8G8t729v8Hh0cbOysbExcTIvsbJ0M7GycbJwMPDiM7I0b+9ycbIzceA0MjHzcfExc7IxcjMzsvLwMTEzsnKx8rAx8rMxczOw8PBxMHEws3ExMPMyMzLycjCvMDHxsfEusHBxb7BwMzJxcK+v8TP0MTExsPMzsvPysjRyMrSz8nJysLHzs3MzMjPzc7ZzdHJzM3Tzs7WztLV0MnP1tfY1dbS0M3O2NnUz82A3M3SzMzSx87KysjDwsrGxdF22dDNyc7RxL3Hx8PGxsXFys7Rz9DMzcrEztHQy8rCytDLy9a6fNDOy9THzMnIzMbIxsHNy8/L0cvLxL/Iy8jHxcTKxMrAwMTIwtt10M7Pz93e08fRzc7T0cxzysrDxcXKyMjI0MvJycnMxsa6yMiAy8zM1c7U083UzcnCzs/M1MzWxtLPy9HMz8vM0M67zdDLyMvKxcrMbGzX0svQ08rKztLVz3HKy9HS0NbO1M7Mz9DO0dZpz8vNzc3Sz8vIysjPzc7OzMnHw8jQy8jV0cPIy8XJysXLysnNaWpmy8fBZ8fHxsrJy8zOy8rMy87PzcuAxMTKysW+xcfDwMTBwrjCwsTCx8HCyMLCwcG6zse3s76+uLvBv3DDsrK/uru7sa64yrK0tLaws7Kysbi8vbCzs7G0s7W8uK+4squrrK65qrOuq6q1q6qnrq+ws7G5s7WnrqKop6i0rLCko6qn1Kqen6ajop6cnZydmJuZmpmgmJeAmpyQlpaXk5uYlZyomZSOlpuMopaWlY+OkYaLj4yOkZCThouMjY2Qj4+VkI+PjZqMi4mDiYuCi5GChI2EgYKKk4OIjYGNiIWHhIOLhYiGiYqLh4aBh4SEgIWKkYSJh4KHi4mFg4CJg4GEhYaDhYqMh4mch4mJj4iJjIeCf4mEgIKAf3yAf4aAf4e9hYGEgYOAg4GGhH6EioGBi4CLg32FhYaGhpGNf3+JhoOFh4KKkImOhYaFgoqJhoWOi4qFiox9g4WJiIuMg6WPjYqOj46Nh5GPkpGMi4SBgoWHkY2PjYyKiYaAiIyMm4qLiJGJj4iFi42IiZWTkYqSkZGLi4yTj4mAi42XjJCOjZyNmZWWkZegjJeSkYyTkpGWlJiemZOVkJGampKQkJKUlJ2Jk5KZnJicoJuSlJ2eoJ2Xl5WampyaoKeanZ2jo5yamJuaoJqemJyyqqjOo6Gnnpudnpydmp+hpKKfoaijoquruaijpaimpqqmrL62rrSyp6itrayoq7WAw7q4vbzFvsS+ybK7wbm4ubS/ury9xL7ByMPDwcDEwby6u7bFwbi4vre9ubnBxbzHxLvAtLu1w7u3uL26wMW/vb/U2MvNwsfEwsXBwMLJxsTMy9Da1+HY4NfQyM3Fxcb23cvWz8/JysfKvcbM0tTIz9DXztLPidrO08O9zcfJ0M+A08nIzcnFwtDIx8vO0M/MwMXEzsvMys/Ez9Xb0tzj19jX2dfb2ODX1tbf2NvZ19XSztLe3NzazdTU2NTX09/d1MvFxs3X287O0srR1dLa0dDd19jf3tba187S2NTT0tHa2Nfj1dvQz87Uz9HZ0tjc1tHV3dja2trT08vN1tnSzs2A3c7Y0tTdz9jW1tTNyNbPzN+B9uHWz9PbzcnY29nd39vd3eTn393X19TM0tPV09bQ2uPf3+bEhNjV0drL0c3O0M3T0c7d29/Z4tra1NDd5ePj5OLr4ujc3t7i1/SC5uHk3vH+9dvm3t7p5+OC4+bd4N/l39/d597f3uHm4eLS3d2A3dnY3tjf39ri3N3Y6Ovr9Ov03+zr5/Dr8/Du8vHY7/Dq5u317vX3gID79Ozz8urn6vDx6YDm6/Tx8PPt9Ovq7vHy9/+A/fn59/P38enk6OTu7O3w8Ovq5+z78+/9/+z07uvu9O749/j+hIWA/PnxgPj18fTt7enn4dzg4Ofp5+eA4eHt7ung6+3m5unt7+Do4+Tj6uHl7efm5uXf9dza0uHf19ni3I/i0tPn3uHg0czS5M7Qz9TP1NbU1N3g3s3MzszQy87T0sTPxr3AwMXTwcvHxcTPwcC9xsTGycvaz9HAyLq/vLzJvsW1t8C79sG1t8G+vbq4ubm4sLW0trW9sK+AsrSor6+zr7ayrrXAraujqa6dsaiqq6mprp+nqaKio5+jk5ucm56lop6lpJublJ+TlJKPmZyUo6yXmp+Zk5ieqZacopOfl5WVj42VjY+KkJKXk5GOl5aVkJKaopSZlpKanJeSjYmSioiOkZGIiY6KhYiVh4uMk4qIkJCOkZqYkJCAjIqRjpaQjpn0lJCUkpOPkYyVlIyUmI6Ol4mXjYaMj5GRlKWYiIuUkYyNj4qRmY+UjIyKhYyHiYaQjIeGjYt9hYaPi46QhrCQi4aJiomGhJGQl5aRlJCMjIqNmJSUk5OQjoqBiIqMoY2OipSOlY6Kk5WOjpqYmZKZlZWOj5GalY2Aj46ZioqJhaOIkY6Pi5SdiZaTkYeLiomQjYyal5SZl5ikpp6dnaGin6uTnZqdnpqZoJiNkpygop+am5yhoZ+aoq2WmJWgopyYm5+cpZ6imaLNsanTpKKrpKGprq6spKWko52ZmaGfnrCwyKqlp6+rr7Crsse9tru3rLK5urays8L1fQF+/32cfQF+pH0BfqV9AX6OfQF+u32Cfot9AX6PfQF+pH0Hfn5+fX19fqx9AX6IfQF+/33/ff993X0CAgQAgMHBycvBx8XFw7/DurS2vL63tri5v8nHzMTKxr/Bvr/Dy73I0MrKysPBzM7Ly9DR0s3Q3sLDvrzBvsK/xsbExMLBwMHezNXMxMvHyMbHzs3Hx8N2hs7Jx9HJyMDJxs7Gy9jKz9LN1tbWzsrOzc3R0NXbzM7SzNna1+LV1MzX1NLLgMzNzM7HwsvS1sTFw7zDxMbAvb7Jw8vQz9HP0c7F09HSxcm70MnLytLd0MrUztXZ09DW2N3l2Nng0tbe2ezf19fT0tbh09PO0M3R2dnX19zd3dfV0NDQ0tHS1tbK9unc3tXV39TW19jW19bp6ILi1uTZ09rW0NHU1tPV1dXe2uHcgN/X29/T3+Hg+eDf3P7W19/j3OHU397e39nh2d3m4eHo4uDd3+Da1drH1tra2dfX1dTa1NjT08vS2dPS0M7U0tzZ29nf3NXQ3Yrf18/M1drQzMzO0snKx8TKxMjNyc/P3tnX2MXEvMfDy8nN2MrBxcXEw8PLxsbIvr/AvMzJvcvHgMu+vMnDvcC9urzBycq+wl1pzMrMyMTBvrm4u7rAxsLGxcXJycjIwsfGacHBwL/DwMnJa8a+vrq8x83Ix8XQy8vUxNFuztDIa8vMxs3IzNHKyMXDxcfHwLxkZcG/wsq/zmnLw2jLxcbNx85se3/Ow8DIy8bJwMvIwrjDubG6uLixgLS1tbxrxMS1sb3AxsC3uLy1tmfDubm0x77Gtr64uLe1uq6us75os7m5v7m0wb3Cr6yxuKiho6ylp6a7vbmxuLu6u7C1srW2tLOrqKmjq6elqqqpoqavbqijpKanr6ufrJ6dm5adlJWhrJqfoLCalKadmZeSi5SXmZuioKKjmZSQC5WcmZeYmI+LkYuMhIiApIuHjI+Ok5WIiYeLkISGm4iKgoOKhoeFgIKFg4N9foCIh4GAe3yGgoZ/goF+iIyVhXmBg4aQjISNiH17jnl4o4iFd4yVlpGSjYmPjIeGhomCioeF3IB3fX15enx7foGDiIVydX2Gh4aDjYyBlaGDiI6KhYqEgoeDgoCCg4aJsYeAjZOJiJ6Wm5Wck5qOn4mKiIyJko2OoaeamI+HjJCUj5GNjYqAgImIhYWJi4+LkYyVmpaWmZyVk5CTkIqNlY+JkpGRkJSTjo2Nj4bAioGTlJiUkZeSn5SYkJaRjpKRjZWMjJWVl5mZnZubnKKkmqWfmZiWnZubmpONkpCTk5eVlZNxiY2UkIuLjoiPjZObl8Wlop6YnKSkmZibm6Oan5+koKSnoJ6enqScnqGhpqOlnqOdoqGhop6qqqmqsqyksbSwqKilq7israyvr7WyrK6tqKmap6qoq6ioubq8t7WsrrCou7y2tbi1s7a/sq65b7S4ubWAr6+1tq6ysra6t7izs7O4tbGxsbCyurvAub26uLy6uLrAtLzEvb28uLS7vLm8wL2/vsLcvMHBv8a/w8DCwby9xMC9vtTDzMa+yMC/u7rCw729un5+yL/AxcXIv8XDyMLHz8jJzcrR09rLyszLy8/MztHFw8bCysvM1srJwc3MysSAyMrLz8jG0djcz87Qy8/T0MnGwsfCx83Ky8rPzMPNysq+wrXKxcbM0NnRyM/Kys3KxsrHydLFxsu+xs3G1MvJx8bCx9TKy8fKx8jMzcnKzs/OycjHy8vLzs3R0sTp39LTysvWysrNz8/Qz+Pif9vQ2tPV2dnW1NXZz9LRzNXP09KA1NHT18/X1tTt1NLP6M3R1NjP0snOz87SztjNztPLyc3GxcfMz8vL0s7R0tPR0NHNyc7O19DQytTW0c7My8vHysjHxMfCwcHLf9HMxsTJ08fFxsfLx8nIw83Jys7Jzc7a0dTVyMnCzs7T0tPdzsnIy8rNx9HM0dTMysvI0s/HzcwWz8XG09HP08/Mys7Tz8rNXW3S0dbSzoTPgNHP1NXPzc3O0dHSz8vS0XHQzs7Oz87X1XDPycvIyNXX0tHQ19HN1sbRcM7OyW3R1dDW1NbZ1NDOzM3Q1MzLa2zPzM/Sx9Bszslpz8fLzsrNanV9zcXByM3JysTN0MvF1MrFz8zOycbJyMtyz8zAvcnIy8S9v8C/wG3Nw8C5z8LIgLvCvb+7vMC8u8HHeLu6uLq4tLy7z7u4ucC5srS3rrOwwMC3srq5uLaus7SzuLa2srCvqrKwrrKxs66yuXOwr6yrq7KyqK6oq6mmq6Glq7Gjo6iznpqtpqGgnJqioJ+eoZuanZeYlpqemZicnpmYmpaYlZGTk5yVjpKUkJSSiImJgI6Tiouhj5SKipGPkY6NjpSOjoeJiZKQjImGio+NkIiMioaTlp+NhIuJiZKNhomKgYObh4G0kIZ1iI2NiYmGhYmJh4aGiYSMi4nih3+EhYKDhYWGiIaNlIKDio+PjIiPjICWn4KBhYN/hIKCh4SBgoSDhYaphIWGfnuKgoeDiIGKgISWg4aFiIaLg4aRmomHgXx/hI2KjIiHiIKCiYeEiIqJioWGgYWHhISFiomJiIqLiY2Vj4qUkI6MjYyHhYiIgq+JgomMjomIioWOhYqJj4uMkZOSlo+Lj46Qj4+Vk4+OlJWNl46MjouRj5GTkoqRj5SVm5eSkIuPkZGOk5qVl5OUZpqVrJmWlZGSmJqTkpSUm5OXk5mVmJqUkZKUmpWYmpuem52Wn5mcnZudmaKhoqOln5qkp6OboKKntaelo6KjpaKboJ+fpJqkpJ+gnqGtrq+tqqOnqaKsrqurrqyqrbGpp69yq62tqYC6uMHEvMDBwcPCwru9ub69t7a1tbvEw8bBxsO8wb/Awse6xMzGxcbBvsbKxMbLysvKz+nBxL+8xL2+uMHCvr/GycbG5dTZzcLOyMbGxcrNys3MjZTl3dve19bN1dLUytLc0tTW0trX2MrEycjJ0NDV3tLQ0snT0dDUzMbBysjHxIDExsfMxcTO2tzOzs3Izc3Kxr27wLzDy8jO0drZ0eLg49XZzObd3N7j693V2tTV1tXO09TW4NjZ387U39ro2tfT0s3T5NbV0dXS0dLX1dPZ19rY2dTY1dnX1NbXyvDl19zV0t3Q1NTRztHQ5eN/4Nfk3drh3dfX2NrR0dHN2NHX14DZ1djc0t3d3fvd29j42Nrc39ra0dTW1trW4NPV3tva4dzX1t7e1tXY09fZ2djS0dLU3drm3t7V3t/Z1NDOz87Y1tbW2tjU096I6OTf4Oj46ujr6/Do5ubi6eLi5ODn6Pbt7e7d3dPg4Oro7Pnr5ujr6+7j6+Tm5dzX19fl49rj4QXm2Nfl4YXdgOXs6+Ppg4Xw7vby7e3o6unt7fT68u/u8/b7/Pzz+PWC7Orq5ubm8PKB7+fs6Ov49vPw7/fz8f3t/Iv8+vOC+fvx+PL2/Pbs7u3y9fnx84CC+fX5/fH7g/32gv/5/P74/IOOmPvy7/f79vft+Pbw5PHm4ejj5+Tk6ervhvby5ODugPH169/j4+XigPLo6OH87/fj7OXo4uDn4N/m8aHj5ODi29fh3O7T09bb0MrO1szO0OXk2dHV2NPTx8zNztLS0szKycPNyMbJyczDydOTz8vKy8zW08bMwMK+t72xs73FsLS5ybOzxr67ubKqtLOys7mzs7SrqaSrsq6vtbeyrriygLCqpqelrqShpaunra+hoqKoraGguqOpn56opaajn56inZiTkpSfnZSWlZmgmZ2TmZCQn6SrmZGam5ulnpicmY6Ooo2H0KCRgJGbnpqclJKampiVlZqTmJWT5IyEiY2HiI2MjY6Ok5iAhIySlpiTn5yLn6mKjJKQjZOPj5OQjpCQgI6QlaiVmJeNip2UmpObkpmOo4uNi5CMlo2MnqmWlYuCiY+XlJKNjYyEg4uKiY2RjpKPkouSlpSTk5aSkYyNi4eMlY6KlZWUk5WWkpGRkYq8j4WQkZKLhouIlIqNioyKiY+QjpaOjpaUlpaVm5mTkpeYkJqRjpGQlpeUlZCFi4mLdo+XlJaUj5OVkoqLk4yOiYeRjrKXmJmXm6KknJydnKSbn5ydl5uZlY+Qk5qXmZmdoZ+imqScoKGdoJaioJ6gpaGbpauroqajpa2ioqGlqa2qo6akoaSZpqShopufq6+xsa+trbCnurexsLSytba9srC5g7Kzs6/SfQJ+f/99mX0Bftt9AX7GfQJ/fph9AX6IfQF+kH0Ffn19fX6QfYJ+hn0Efn19foZ9g36XfQF+jX0BfpJ9AX6wfQF++n0Bfv99/32rfQF+hH0CAgQAgMnQzsnHw7/FvcTDxcC5uL+4v7e5wOPDxcLHy8rCzM7W2M7Qzs/Ty8PBxcnKzMnPz8nCxLu7wsC/wLq+xMjJyMPJ087Vy8jLxszV7dTKycbMxcjP0s7lzczO5cjRyMnJz9ff1tHU1tHZ2dHW1M7g0dDUy83N1NjSy8rU1NnR0tjUgNLO3NnZzM/U1tDTzM/TyMvTzs/Qz9bi2tjT1M/O18fWztLIy9XSy9HNzcvN0tDU2NXT2+Le2+Hg4Ofm1NbY29jb3tXd2tPl49nY28/b49zW2M/T19rg39ja2dvY29zl3NrZ5IPX3ezd2OXa39fW1NfR0NbN1+Tc2ubd5ejl6+HggOHl497i3djd3sna48zY193a3dTd2OLi9OXm7uPq2t7U3dbc2+Xg3Nza3+La3u3l39rb5efc197R2Nva1Nza09XT1NPY18vR19zWzcfH0tHGz8/ExcrOzrvCyc3Yzc3N1NrYzcHKycTAzMXGice/zNXRz9rPxcLTycO5vbu/vsHHgNXPx8vKw+K7s73AwsnIyNDVbM7MwL27rrfEyMTHvLnGs8nEx8bExczHx8jKwMTBxsPR2cvJy8jDxcnLz8fJzc3SzM3MyMfNysjExMnHxMvJzcjRytPQw8hoashoxsjIz8i2vMLHv2RjZGdkaMfExMjN0M3LwW7Nx7jAsbO7sbq/gLrCvsW8wr24wra7v7m5vsG/wcK3v7mzurS7w9DFwsXEure6urazt7Sytra3w73Ft7ixr62ytLGxtLG3rqywtLO0sq6spamqqGSlqKuqp66nq665r7ayr8esq62jn6KjpZywpJ+ipqKjoJ6YmqejoKCalZyTl5Ogn5+bl52dnZiogKGdmpGWkJGOjZOKgJGNiKGTjo6NkZOPjYWHiJKKkLyUjpGLhYiIiYuDi4+NiIiHiYqKioWIiYeYh4OHhYKFjoKEjImEhoyDhISFgICIjYOIiIaJiYaCh4uJiIOCjIKDkImHgYB+gIh8gn18gISHg4CJ3np6fIZ7eHaJiZCHgoKJgIyKh42FiIuJhISKjY6DkqGIiI+SlJaaop2bn5iOjpqLjYyOnqeUjqeYjY+Ulo2OlpWQiKSGh4yIjIeWmJiYmqOQk5ydmaKakpafhYmyiIOJlZSQlJaNi5aLl5bEj5eap6uopq+bnJeWj5GnjZGQjoSTm5WTjqOaoJ+dnqWboZ+agJqZoaGcnJmXp5KWl5iTnJaaopufl5KUlpqUm5ifmJqeoKKjpqGjpqWhraWor6qxr6imr7qutayknKOcnpqZlpigopybo62eo6yrp7OurqqvpKmoo7Gxq6aqrKmrqKipqJysqbG6xMG9v7e9uMOzsby4t8C2sriwubKrvLi5yr/DgK64ubW4tLO6tbm6u7m0tbe0u7S0utK6vbm8v724wMLMycHDwMXHwbq4vsC9v7rBwr69w7u/xcbCv7y9usC9vru8w8HHvcHFwsbJ3MS/v7zBu7/GyMflysfK48fOy8jGyM7ZzsrNzsXRzsnMzcnpzc/PzM3L09TPzcfRztLOztHNgMzJ29LSyc/R0s7Sz9PUy8vUz8vLzM/a0M7N0M3L2MrY0s/DxdDNx8nFysnMzcnL0s7Lzc7KxsjHxczMvsHCx8TKysXS087c3NDO0MfP09DQz8fHy87Tz8/Q0NTQ1dTXzcvI1njFy9zTz9jR2NXT1NLQz9PM0NvU1tvW29rU19DNgM3Rz9DT1NDY287b5NDa2trW1M7Szc/Q3tPU2tLazNLDzMXLytLNy83N09TNy9zT0c7S6eHPzNXM0dPTztPNyczLysnOzsTL0tXSy8TCztDEztDHyc/U0r/Fyc7VxsnJ0NPVzMbOzcnH0M3MhsfB0NrR0NzVy8rc1NHN0NDWz8zRgN7U0dTW1fDNxNHa1NfT0tPab9re1tLUxs3V4Nbczc7WxdXQ0dTR0tjU1NnXz9HNz8vW3NPP0c/N0dXX29LT1dTb1dbX09La1tXS1NnV09jZ2NLZ0tvZzdBqbdFt09DR3NrFzM3Ty2loaGlnas3LycnP1dXOxm7Py8LOw8XOxNHVgMfMx9HJzcnKzsXGx8TCw8HCyMvAysbAxcDCxM6/vr/BvMDBwr/AwL66u7q4vLnBt7izs7e8vra1ura5sKyvtbO2tLKzsrO3tXGytra2rratrLC2r7SxrMOvrq+np6ytqqW3rKenramopaSgoayopqmjnJ+bn5mgpKejnZ+fnZqugKGgnJScmZuam5+YkKCalaiblZKQlZWRj4qKjJWNkbSTj5KPiY+SkJKNk5OTjoqKiYuJjIeLi4ihjIeJiYiMlIeMkY+IiY+IhYWIhoSOk4uPi4iMi4OAhImHiIWDk4eHlZ6choaFhomBg4B/f4aKiIWQ9YWEgIh+gH6Kh46EgIGHgIeEhIuDhoWDgoKGiIyDj5mAfYKCgoSGjYqNk4yFh5mIiYWGm6GIgZyOhIWJioWFjIqHhJeDhIWGh4KJi4iHhIp7f4OGhY6OipKdiYqji4WJjI2NkJCHh4yEj4+3h4qMjo6Rj5eLjY6QjJCmiI6OjoeVmY2IhpePj46Ki5CKjY+MgI+Lk5WUlZOQn42TlJKQlpGTmJKYk5SVl5uTmJaZkZOWmZyYnJaYmpiUoJWXnZienJWSnKGcpZ6cmKCcnpublpmfoZqYn6mdn6akoaqopKOnoKOin6usoJueoZ+jo6Chp56knqKqs7CsrqyxrbSmpq2rrLGqq6+ttK2otKyss6mqgLS6uri6uLm8usDAw8K4tbq0urKxute8v7zCxsO9xsjKzcHFwsjLw768xcjEycXOzsXFyMHGzM3GxL/DwsfHxsHHz8vTycjLxcnQ6MnCw8bRztfh5eL75N7e7Nbb19LR19/r3tXW1s3X1svT1NH12NLZ1tfY3t7a1czV0NPOy8/NgMvI3dXYzdTa3tvf2d3dzdDUycTEx87a0dHU2NbV59fn4OHU1t7Y1dnY1dPT1M/T2dXT2tzX0tTT09zfz9LT2Nfb3dnh4trp7dvW1snU0tHQ0c3R19zk5N3e2uDZ29zl3NzV3n7Q097OzdfS19HS1NjX1drS2Obd2OLV3t7Z3tXVgNnc2tre3Nng6NXk7drm5eHf2tfb0tbc7uLk6+bv4OLR1c7Z2eHd2Nvc4t7V0+Pe3drb9vPd1d/V2dzd2OPZ19rY2Njc39bf5+zp5N/f7O/g6+7j5ezv8d3j6uzy5+ro7/Pz6ODp6eTj7+nrneff8Prz8fvx4+D36ePd5OXs6efugPzs4ejr4/7h2evx7PLy8vX7gfj67urr3ev3/vT46+z45/jz9vjy8/nx7/Ly6u7p7ur2//Pz9vTx9PX3/PPy9/b+9/n69vX9+fjy8Pfx7/Pz9O/58f798PmAg/mB9vP0+/jj7fX/9oKAgoaDhf308fL4/Pr17IX68+bx5OTu4O/1gOvw6fTv9fHt9efq6ODe4OPn8ffs9/Lq8u3u8f3s6evp4uXn5+Hh5OHZ3drb5N3o2drTz9Ha29XW2tfbzszP0tDU0tDQzc/T0ofP0NHQyNHGx8zYzdPR0OnS0tXNyc7Ny8HTxL++wLq8t7WvscG9ur24s7u2urK4uLayrLCysK3FgLSzsqy4tLa2ucG2q761rMGwqaimq6upqKKipKykqt+spqaim56en6CYoKOfmZiYmp2bn5ygoZy8npiYmZaZoZOZoZySmZ+YlJSYlZKgoZicm5Wcm5SVl5mYlpKRl5GTo7Grko+NjpGJiYWGi5CUkIyX/IiMiZKJgYOSkZqTkJCXgJWRkJWMkpKSkJGXmZ2QnqmJhoyQj5GPmJeXm5ONjJuLjo+PnqqWjayYjo6VlpCQmZWTjayNj5OUlpCen56cmqGNj5ORj5eSi5Ofi42ykIyTmpqXmZqMi5KIlZPDh4yQk5WVk6GSlpWUjpS/jI+MjIWWnZGNjqGYmZWQkZqSlZaSgJOQmJaVlJSUqZSbnZyYnJeYnpWZkY6RkZSLk5Oak5ecoqSfo5ucnpyZpJycopyinpeTnqijsamopKujpZ+emJqfn5WUn6ecpKysqLGurKeon6Cfnamqo56mqaywsq+sr6Cim52lrq2rsa22sruurLOztLqztLWwta2pta+ttqyt/33kfQF+/32IfQF+pX0BfsZ9BH5+fX6KfYZ+iX0BftJ9AX7/fal9AXz/ff99kH0CAgQAgLrAyMHDxcLbwsjL1szQw8fIxcO+xsXGxc3Fzc3Lw8ze28/LzL3V1czGyc7c2c/X1NPG0MnK2vHg28u8w8HKysfGyc/JytbczdPX39nHx4fgzMvLxsjKxMbKytXdzdPS28vM2dnf4NnX4M7Z1sfQ0M7UzNLP0d3b1NPS0tjQ1d3ZgM/a1Nne3MrS1szW0NbVyNT44dHe2dfj4tzU0NLT2dDW49jW4dTMzczL0crP1dnSztTZ3dPX1dzd4d/c4+Dj2tvZ3tvj2Nvc1snQ0tbV2N/f2NzX0dvX1dfb2tjV2Ibf5OTp8+zx7urk4Obf1tfe5eDc3NnY4ODt5OXq6+vl6OTvgOfh6ebm6ubn6+nm9+jo4uDf3tra2+Hp49/l4t/p8NrZ3Njd4eXe3uLi5ODT2dzc4tvi2uDk3+bj1dzaz9LW39DQ0tvR0M7MydPV0MrLy83NzcXDwcO/x8zD0tLT0s/czNPO0cbPj8fKz9PPzMa9wsLFyMjNy8/GyMLFyca+vMfDgMi/x8POzsPIvMvK2MzKw8lrwMXIxsW8u7/Bwb3HyMtqdWjJzM7FycjQzNHRz8zIycNrz9DQydDN0tPVztLNzczR1cvL1M/HwsvBy8e/xGXGx2nKyc1mxXRUasi9ycrFuWe+x8jHwcLCyWds0NPPcmrQz8zAys3IysrNy2h1v8K+gMDBwsDEyMDJx8G4trvAu8K/v72zvsS6yrvDx8POxcO3uLu1ubm1u722tLy7s7i0r62usbSzuruytrq6srS1uLCxta6trrWxsmyqrLGwvayjorCor7y1tLW5rquuoaCko6StpKCkqKqwpqirpbKqp56slJOWk5WYn56Zl5iboJqggJuimpyVkZeSkpGIjJSVmpWTk5STkYuTm5NliZGRkZKPk5qvjoWFjJeLnbeKh4WEh5iAiZF/gYSMjIiIi4mMkIiLg4WHiYaGhoWMiIODf4CcjYujf4WQgId+gn1/gICDiX+JiZWPkYuBgYKDhoKEhYN/gpiWhIqJkYeEhouNhoKCgIiMj42Ih4KBhoF/houMhYiRiImNjZKQk5qTl46Fk4uMipCKlpaWi4mTlaGQkI+Ml5iJkI2Oj5GNkZWUk5eZlKKalJmfnJKYoJ6XjJKPjJGKkpeIjaaQkJKRlJuamaCclZmenJuYyZuhvZSOi4yTko2Nj5rUn6GZmp2am5qcoqGigJ6Tl56Umpqbm5iamp2YoJqdlqGwmJOZmpqVn5yeoaSeo6Glopqio6KsqKanrqqopKCipZ+ht6egp6iip6OhoaOkqqWjsKilurCyqqSlq6SnqaixrbCxrrKus62ttXShqqivu7i4gcLEx8G7tL62tbrAucvGwLy6vLS2x8DExcjIgKuzu7a3tbLLtrm5w7vAtbi8urm1uba5uL60u7++t8DVzMK/wrbHyL+5vr7JycDJxcW8ysLG0+nc2su9wcDFx7+9xMjGx87VxsbLz8y8vnvTwL+/vr/DxMjKydTYy9DN1sfJ09DS08nN0cfR08jS0c/SztDQzNTTz87Oz9HP09fSgMvW0tfX18vN0crSz9PRxdDu1sjRz8vX1tPOzdDP1M3R3NHS29HLy8nK0cvN0M7Nx8rMzMbKyMrIx8bCyMfKw8TGx8bNyNDW0cfN0NHMz9PTy9LNydLR0NLS0dDKzoXNzs7R3dTX09XT0NXUzs/V3NLU0s/O0tDd1NLU09PP0M7XgNDM09DS09DT19rZ6dve29jW1tPQ1NTc19XX0tHb483HzcrO0NDMzdLT087DysvIzcrSz9LS0NnZztTUzszP1czNztbKzc3Ny9PU1M7V09XV1c/OyMvI0NTK0tPT0MrXyc/N0czTm83R09jV0srGyc3S0tHX09nOzc3M09PR09rYgN3S2M/a287SydfX39fT0tZ0ztLT1NXV09vd3NTV0NJvgW7T19zQ1M/W0NXT0tHQ09Fu1tTSy9HP1dXX0tnS1NPZ3dfX4NvV09rR39vS127c2HLa2tts1X9ucNTL1tbQz3TR19jY09DO0Wpu0dHSc2vT09HJztHO0s/T0nR8ztjTgM/Ry83MycPPzcnFw8nKxcnJy8rByc7AzMDCwr7HwsPAxMvEycnEx8W8uL7Auby7ubq6u7+8wsC3u7q5tbe3vrS3vLWzuL27unW1tri8zrevq7SorrKsqq6yq6iyqqqurrG6rKWmqqqupqirqK+pqKW3oaKloaCfpaSloKChoJmdgJekoqSfnKCem5yUmJ2dpJydmJuZlpOXoJdqj5STk5aPlJnAkoqMlKGWob+TkY6Qkb6IkZ6IhYiOjYmHi42Plo6QjI2LjYuLiYeMjIiIhYq6mJKxiIaQhY6Eh4OEhoSJjoKNh4+JjoqGg4aEhH+GiYeDibWahYmFioJ/hIeKhISCgIeKh4eDhoKCiYiEiI+Kg4OKgoKHh4mIipCNkouEj4qLiIyFjYyLgYGLkaeKjYyHjo2DiIeIiYyKjI2Ih4mIg4uFgYSLjIWMlZeRio+Oi46HjpSHjqGOjpGQjpKOipGOhYiLioqInImQs4yQjY+Uj4uNi5Suk5KLjZGOkJKRlJWUgJKLjpWQlpeZlpSWkpSPlJOVkJqplpadnZyWnZqXlpiUmJqZnJaampmgmpeVnJqYlZSXmpaXrp2Zn56coJ2dnJ2cpaKeqKCcr6anoJ6fo6CkqKmup6mrpqiipKaor3SfpqSmqaepa6qssLGuq7CprK6yrLS0s6+wsaqst66ytLS2gK+2wLq+wb/ZwsfK1c7PwsfIxMS8xMPGwcrAyMnIv8vT1MbFx7jNzcO8vb7Jx7zHxMS6yMDF1ufh4tXFy8fN0crIz9bS0tvezM7T2NbIyYrp1tbY2djY1dfW1eHk197Z5dXU393f3tXS3MbR0sHNz8vS0NXV1N3c19LTz9LS1NnVgM3Z1Nvc3c/V2tDZ1trXx9T02cnU1NLb3NnY1trZ4tnf7ODf6t3W1dPP1czP1NLQzNLX2c3W1N7b29fU19bZ0dPS09TY0NXY08jNzc7N0dja09vX0N3b2drc3d3W2IPc3+Hi6+bo5ODc1dvWztLY39jb3dzb4+Lx6Ofn5eTd29XhgN3V3tna3NrY39/d7ODj3tnc29jT1tjk4eHk4eTv9t3b29jb2tzX2eLk5N3Q19zZ2tfe1+Di4Ors3eLf1NfX4tbX2+bY3d7e2+Xm6OLm5unr6+Pj3+Td6O7k8O/w7ur15+3q7ebu4ejw8ff38+zk6ejv7e3z7vPp5+nr8vPt7vf2gP3v9Ov08uLp4PTy/vHu7PGE6e/x7+/r6vP49vH18/WBmYH4+vrt7uz17/Ty8fHw9fCB/fr68fn29/b58fft7+3z+O7x/fjw7/vx/fvz+YD69YT4/P+A+6jlhv7w+/rw5IHs9vv++/n2/oGG/f78iIL9+/nu9vjy9vX6+IKN8PvygO/w6vDw8uf09O/o4+bo3Obm7O3m9Pbp+e/z8+728O7l6Ozm6+nj6Onf3ODg1dvY1tfZ2t7d5uPZ3d/c09XW2c/Q1MzL0dbU15LS0tXZ8dPJxdTIz9bRztXY0MzXy8vP0NDYxr/BxMLIv8LGwcnDv7rOt7e+urq7vru2sK6wt7G3gLC5tbi4uL+8u7+1tLm3wLayra+vrquvuLCSp6+tra6mrLHWpZqao7Gkteyhnpyen+GXorKXl5eenJeRlZOUmpCTjpGQmJSWlJGal46Ph4+wop3KlpyqmKOZmZKVlJKVmYyZlJ6YmpOIh4mIjIqOk42Lmdu3kJSTmJGQlZWYkY2LgI+TkpONk4+RmpmXmp2ckJCXjY6TkpWOkZaRl5KHmZSWk5iQmZeWiYeTl62QkZSQmpmPlZWWlpuamp2XmJuak56XkJOXlYyQl5mTjJOUlJiRlpuLkauPjZGRkZWQjJGLiY2VlpaSrZidv5KPiouQkI6PkZy0np+ZmJuXl5WWmpqZgJSOkpmVmpqdnZednJ6Ynpudlp2pkYyTk5KKk5SVmZ2aoqOjopifnpymnZ+eo6GfmpqdoJ2dt6WdpKWiraiopKehqKGZpZuXr6ivqaaqr6akpqSppKmsp6+tsLKwuJieoZubpKapbbO1u7i0rbatrq+2rri3tK+urqaptKy0tbm6zn0Bfv99j30Bfv99iH0Bfqp9AX6OfYN+j30Bfpx9DH59fX59fX1+fX5/foZ9AX6IfQd+fn19fX5+i32Cfst9AX7QfQF+/33/fcV9AX6HfQF+mn0CAgQAgMHGy8bOxcnJyMnJzsbJw8K/wsTFys3OzNLN08/YzsfR1MvKysbJz9HRzdDS0tLW297W19PS1NLJ0srKxr3GxcrHwsbQ2tTUzsfU2c/Rz9vS19HY2dTVz8/S3dbJ1dnb2NPQ1tnO2NTX1dTa1c3Oz9Lb09HO1d/Y29bY2c7O0ePWgNjT3dTT087T1szX1tfS1t7f3+ff4eLi4dbWztHU0NLc2tPY2N7a1tjR1tnY39zS1NTk3OLd6uvj5dzf29zj7Onk9eHh5eHf3ubf297V29/l39Xe6t7h3uPX2Nzk6OHl5+Dm4Ofn5ODo6ufr9e3t6fT19O/o6PH08u/s9N/q7OT3gOjj6Onb39vi4efk5/Lm5Ofh3dvd5ufi4uTj3NrV3N/i3tjX2dbb4+Tp7ODZ29vS2drc4eTt/t3W2dbk3Nfb2c/Y2NTU1NnYy8/WzNLXzdDT1snQ1MzKyMbQ0nHZz8/QzdLTz8nQw9TP1MzVdcrK0cfMx8bGyMbA0MbHz8vJxL/FgL/Hw83MbMrOzc/S1M/SyczMzM3Ky8fGyMW7w8XMxsLNzdLJxMnDz9Rv09PRzNLIyGnP0dXPbWptzNjX1NzP08/Qz9LO0WnNy87PxcnJxstmacrKaMvPym5Na2e+yM3OyMzKy8nKxrvKymppa5p4csnJ2tPQys7M0XNqzsnPyMnIgMHIzbzEyLzEvcDLxsTQwc+7wsXCuLa1v7vDxMCvucbAt7W1ury3u7vAusazu8DHunG0tba/vru3t7S9try6urK4urixqrG2rrC1trC0tbKusbaupauxs6uvrKq0pZmjo6SnpKujoqCxq7StsKahnZijl5SXlpyVk5qSlZGTl5yegKqblpeZkpCTlZCRk5aRlpFqlJyTjoyKhYqRg42UlZmfl6KPiYOGiICDgX2Gg4SLiYeOiY+IjquOjpOhlJWOipSLio2QjImJjYePkouTjoiLiYyJfo97gIJ/hYuDnImOi4qVio6LkZCTkYqFioiHg4mHgYWLkpabjoWEhIWDgYGCgIqKjIaCgnyEnI+KjJOXj5GOjY6Nh4yLiYiLiI2IiomKkaOGkoaQlYaLl4uclZChnJmem5OZlJOWmJWXlpuXmqKzlJuflZGKlZKOioyKjpKNm42Qj46QjpKXkpuUkZKMipqVm56jmZ6XjpeSm5uQkZWQmI2KlZOVn52XlJaZn5+2gKSin52ZpaKan6GdmZ2dnqOmqKKmoZCPlpmLnZyXo5+dnZmnpaKlop+nqaqsqKmusK+nqqmnqKqpsLCsqquwqaSgoqmws7eqs6q1rqansKqrr6ytr7S3qrO5taurtK+urKmwt7+4vrzLwsHCur++tri+y8C/vsHEw7y1vcO+v8DFgLG0uLK6tbe7u7y/xL/CwL66vr29wb++ur24urnAurPAxcDAw8G/xcbBv7/AwsDBxcPDxcLExMnEy8TDwbzGyMzGwMDIzMnKx8TT1srKxcy/w8DGycbHxMbI0s3F0dPT0MzL0c/I0c7U0tPY1s/S09Pc1tLR1d7Z2dTW19DQ0ODVgNbR2tHS1NDS1c7Y1tTO0NjU0djQ0tHT09DTzNHSz9LZ1c/V1tzW0tLP0dTS1tTQzs3XzdDM1c7N0MvLxsnL0dDO4s3P1tfZ2OLY1NXL0NDZ0szS3dPW09bOy87T1s/V1c/V09fW0s/X29TU39HPytDS0s7LytTZ2NjX28/W2dbkgNjW2NjT0c3R09jW1d3X2d3c09HS19vW1trX1NbW19XU1dDP0MjO1dHR1czJ08/LztLR1Nfe89nQ1M3a1s/W0cvU0MvJy9fRyc3TzdLY0dDS1szV1c/OzcnR0XDVzM3QzNHV0s/XyNnS1s3WdsvN1s7Y0tPW2dXN1s7S2dXX08/WgNDY1drYcdXWz9PU19fb09XX19bW1NPU2NjT2Nvd29jf2dnQ0tXL09Vv1dTW0dbQ0G3X19nTamprzNfV1N7T1NLU19rV3G3X1tfa2Nfb2dxvdNvZbtPT1nZXcG7R2t/c1tva2NPW1cjU0WxrapZ3b8rI2NnY0NPP1nlr0tLY09XRgMzS1MrP0MbMxsrSzs7bzNnIysrMwMPBw73EwsS2w8zJwsPBx8bBwr/Dvse2vMHIvHS6u77ExcC5uLS8t7u4urS4vre1s7m+t7e+v7a5u7e0s7ewqKuur6mtrKy5rKOur6+zr7Wnp6Ovp6yoq6ako6Suo6Snp62ioqifop6fnqCogLmko6SknpqfoJqbn6KbnJqOl52Wk5KRi5CXjI+Wj5OXkpyTjoyTmZGXko+Xjo6UkIyRjZCJjKCLh4qUio6IhY+Ki46NioaHh4CHi4OIh4iMi46QhpyEiouGiYyDnYaKhoOMgYaBhIKEhIKCioiLhIqGf4KEh4qOioWHioyJhoSDgImLioaEhoCGpZGHh42SiouJh4eHg4mIh4aLh4yJioyIj6KDjoGJi4GEj4GQhoaTiYaMi4KHhIOHiYiJiouGiI6whImOh4eFjpGMi4+LjYmIlIiKioeMio+RjZGNioiGgY2GiomPiZORipGQn5mOjpONkoiEi4uMlZSPjJCTlZSmgJaYlJGPnJmUmJmXk5WUkZaXmJWbnZaWm52RnJ2Zn5uZl5aem5qalpaYmpmalpeanJ2XmpuZmZ2anp2cnJyloJyYm6GjpKagp52oo5+fp6OkqKapqquvoqetrKSpsqusqqanpaqlq6q0rK2zra6tqKettaytr7O1trOvtbizsrS1gLi9wrrDvb7Avbq7w76/u7u5v76+xcbHwMO+w8DIv7fGycPExsTCzM/LysnJy8bIyMTFycbKzNTN08zMzMTN0dbMxcjV3dnW0cnZ29DT0t7U19PY2NbX09LX4dvT3eLj3tvZ4uLW39rZ19DT0MjIzszU0M7O09vV29HW187P0uLYgNnT4NjT2dPZ3tTg3d3V1NzW0tnQ09fa3drd2ODe3N/m4Njd3uLb1dfS0tTR2NbQz8/h293Z5+Db29TW09bV2tfV69TV3trZ3Ofg2trR1dbe1tHV39fa2NrR0tfd4tvi4tve2eDd2NPc3tnZ5Nzc1uHj5eDb2+Xp7Ofl7tvj59/vgOHa3uDZ3drf4Ofk4ezj3+Hcz9HM0tbS2N7e3eDh5OHe29fU1dDU3dzf5+DZ497S1tvc4uTu+N7a2dfk3tne3dfi4d7c3+rn2uDm3+bs4+Tl7ODr7Ojl5+Xu74Hy6urr6Orv6ufu3/Ls8OfxheTo8ebw6ert7enj8Ovu+ff58+/1gOzx7ff3gPLz7/L09vb88vHy9PPx8/P1+ffr8PP59vX/+vvz9vnw+f2E+Pf58vvy8oD7/f/7gYCA8fz49Pzu8+/y9f35/4D6+vz9+Pj6/P2AhPz7gPj4+ZK4hILw+P/68/bz9fH4+Oj4+YKCgKuShvXw/v7+9vv1/5CB+PL38/XvgOnw9+nv8uby6+/47+325/fl8PX57ezr8e328+7c6Pjv5+jp6uzi5eTn4OzV3+Xu4o/c2+Hq5+Pc2dbf1tzZ2NHW2dTMytHb0dXf4dfd3tbU0tbPwsfO0MjPzs7dzcLMztDT0tjLysXUy9HMzsPAv77Mvb3Ewce6ub+xtayusK+4gMa1s7a6trG5urm5ur62u7i6tLuyrKmpo6mwo6qyrrG1r7apoZifpJugnJeinJ6no6CknaWbnbiXlZummp2Ukp2ZmJucnJibmZKcoZOaj4+UkZOVjKeNkJaRl56Xq52hnpqjlJaPk5CRkoyJjYyQjJOQi5CVnKGjnJGSlZeRkYyMgJGSkY+Mj4yWv6OZmqGlm5qXk4+RipGMi4yQjZKNkZOQl6eIk4SOkIKJl42hm5epnZmfnJOXlJSYmZeZmZ2Xl5+4k5iakpCKkZSQj5OQl5aWn5KWjo2MhoeLiY+MjI2GhZKPlJeelp+YjpeVqZmNjpONmY6MlpSTn5uWkJKUl5WqgJiYlZCOnJqUnZ2dmp2cmZ6hoZufn5SSmZuMm6Cao5yYmZSfnJmdmpuho6WnoaSlo6aeoaWioqmlqqmmpaWupqOjpKirrK2jqqGxrKurs6ypqKWlqLCypq22tq2ssqmqp6WqqrStr668tLi4s7Kwpqmtt6uvsbS4ubWyub+6uLu7/33/fdx9AX6QfQF+mX0BfqJ9AX6HfQF+hH2Dfo19AX6JfQx+fn19fn19fX6Cfn6OfYZ+iX2CfrZ9AX7ffQF+/33/ffF9AgIEAIDJzNHSzcrLycrEyMvKesnAwcPGxsThyczWys3N1cnQycTHy8bHeNPSz9PW19HX3drT1NnZ29TV0dPT7eHY1MzLz8vT1NPR09LP2dv32uPp19fW3uTf3ODJ3dnTy9DT29Ta19rT097g0+Dd3dzZ1N/c2s/W193h3uDg14mG0tbY1YDV2+bb1drg3uHa29zf3+Hd5eTl4+Lk6tvs3eTd29Lg3uXl4YDp4uHj187b3NLU3t3j3+Pm49vg4Nnk4+bw6erl7Ont59zx6eLa5url3OTr5+nm/9/o5+7t8Ofl6+fj6u3l3+Hf4uXj3u7f7Oru7pX4+P36+vbu8PLs6uP+8PLr7oDs793j3+bp+Pvv4u/p8ObzhO7i4uXh5eTk2+Xl0tjf39/Z3tnr2d7o6fDf4vDo19zX3d3g5eXj2t7X19zZ4eDX2+Xd4uTT2d7X1uDa2dnPz9bK2YRV1tLV1tXUzdLP09PWycvM0dLZ1c/W4eLW1NHEysrPzMnDyM7M09PXbtDMyIDIzcvOx8DJyczExtrU127R18rQyM7Ny8vKxsbEwsJpa2zKxml6zM/V0tTT0MfL0cXBw8vMZ2tt1NXQzmzT0M/H0dLU0c7Q1c3RbWzWcWlqa2rRa9RsamxtzNpt09jc2trTzM7Ky8bKysl20nDSc25v0WtsbGvO083Oz8vJzWZlxYDFxMHIzs7KxczJbcW/a8DIycO/0LjAxc3OyMa9vbhmwLG9ub29wM3MxsXNsrq6u8DIqrXNbcG5r728trq6vbS8s7K2tLi1s7uwsbS0sbOzr7OrqK6vtsW2sK6zsKmiramrqqq0sLOwta2tqKmsq6Gho56djpKTmJSZlZWblZScp4CcmZKSlI6OjoWOi4qKk5aQmJiWmZuclZ2Zl5qSlpqenpWdk5OQjZCRjIh+hIeNh42MkI6IjpinmZKVm4yhlomiioaIkoiJhIWDi4WDio+AiISEioGGjIGBeoKEhIt/i5SPlI2UjI2KiJCWipCUj4CEiIOBfoKHkX+CiIiJhoKCg4CFjouPlIiHjomShYaOi4eMkY6PjIyOjpGRnmWViZGUgpKNg4+QjpCTmJWRkKKxwZ+RnJmVlpGJlpKNkIyKjpGao5uYmZaOmZWXkJSalpOflZGPj4yOiYuLk5GRl5KdoZakmqGep7KalJeWiJCWmJKSm5mZmZiYmZmYl6Kio6Wjp4ChpJydm5ycmLS63Kino6SdoKCenZygmZydn6GfnamgrK2srLKvq6+tq6muwsCxsKyoqqOsrqyura6qqKm0raqopaSoqbCzpaOmr7GurbS3srGxqa6ttrC4u6+2trOxrLGzr7a8s7zBurnDwcPBwcC7u7+8v8O9wr++wsTKycbJ0ICxtbi4uri8vr+9xMnKhMrDwsTDwr7TvL3EvLy+xcDGw8DEx8PDgMrIw8PDwsDAx8O/wsfFzMHJycrJ/N/QysPCx8PHy8rLzcnGzcz0ytHVxcPGzM7KyNHD0NHOyMnM08/V0dTPz9fUzdPTztDRztfV1s/S09bZ2tnW1oiE19fZ2YDU2uXY0tbY1NbP0M3Rz9LO1NHW1dTX28/e09jX2NHc1dnY1nfa1tjd1s7b2tPS19PVz9HW0s3Oz8nT0dHW0tHMzs3U0c323trO2NrTzNLY1tjX59TZ2Nna3NfS1dPR1tnV0dbV1dnY09/R2tjU05TU1djV19XR19zX2NDt29vZ24Dc3tbX09XW3+HY0NnT2dHefNrS09rW3d7d2OHh0dLV29rS0sfWy87W1trOz9jRytTW2drb3NvZ09fQ09bT1M/MzdXQ09TLzNDNzdvW1NrS1NbM2oFW0tDV1tXQz9LS1dTVzs/U1tjd2NPW4uLZ1tjP1tLW2NTO1NXX29jabtjX0oDV3drb2dTb19jV0t7V2m3U2NHX1NjY19fV1tnY2dxzcHTY126Bz87Sz9LW1tHW3dTQ0tTWbG1u19jV1W7d3NfQ2trf2tja3NbWb3Dcc21sbm7YbtdubW9wztlt1tfa2uDd19zX2NPW1NN52nHTcm1u0WpramrQ19ja2dnU2m9u14DX1NDOzc3OxtHQctDNcMvRz8m/1L3CyNPQzMjEw8Fry8LLx8bExM3LxcLNtr7DwsbPrrzXbca+tMG9ur28vbq+uLi6u8C9u8K1uri6tbm3s7Stqa2ssL2yrK2xsaulsa6xsLG6tbaxtamqqKmpqaSpq6qtpK2wr6qqoaCioaGouYCuo6ChpKCenZOZmJeVnJmYn52ZmZWak5mTk5eQlJeampWZkpOTlZiZmZOKjo6VkJCMjoyIiZCckIqJjoGVjIGfiomMk42QiYeIjIiDio+IjImMjoeKj4eHg4mKiY1+h4qBh4GKgoSDgImNhomOjoSHh4ODgYKFjYGGjI2MiYSDhICFioWLj4WDiYWNg4SIh4aJjImIhoSHiI2LmHyTi5GWhJCLgY6PjIqOlY+Ih5Gwu46Ej4uFioeEkY6KjomFiIqQmJCMj4qLkYyQjI+TjI2UjYqKh4qMiYyLk46RlI+Xn4iUiZCSna6Sio6QhpaVlo+OmJORkpKRkJORj5WSk5WRlYCVl5eVlZeWlrGtsJWUlJWRl5iYmZWbmZudmZqemZ6Un52em5udl5qZmJaZramem5mWmZWYnJqenJ+bnKGqp6Kkn56goKWonp2epKOko6Wqpqispqimraatr6etrq6ppqeqqamooqqrqKWvsrOwra2rrrCsrLSztrOys7a6t7K0t4C7u8G/v73Avb+7wcfHg8nAwsXIx8bdx8fQxcXGzMPLx8LDxcTFitDPzs/Q0M7N08/Jy83Jz8fNzc3R9+fb29bU1tHY29nW1tHO1NT3z9XXy8/S2tzX2d/K3Nzc2dnd5t/l3+Lc197cztjUz8/Oy9za29TZ29/f397c2ImS09nb2oDY3/Li2d7k4ePb3dzc3N3Y2tbW0dDT2c3i2ePi5t7s5uro4H7m3d3f1cvX1s/R2Nbc2t/k4N7h4Nfj4N/j4eDe39vf2NL/4NzS4N/b1Njb1tTV5M7X2dzd4dzY3drb4OTd2t3c3N7d2eLX4ODf5MLo6e7r7Orj5+ri49366evl6IDn6N3b193f7O/l3Ojj6t/vhuXY09rW29/f2ejr2+Dl6Ojk5dXi0tTj5Ofa3uvi2+Hb4OPn6Obi2t3Y3uPg5OHd3+jl6ezh5efm5PPt6PDn6e3h9J+Q8O729vXu6+/s7uvt5ebr7u/28enu+fjs6OfZ4dzh4+Db5Ors9fb6gvn48oDx9vTz7+jx7vPs7P/2/oD0+u3y6+/v8vL2+P3/+fmBgYT29oGk9vP17vD09O72//b39Pz7gIOE+/329YD++frx/P3/+vT4+/L5gIH/h4CAgYD8gfyCgIGC8/+A9/r8+v768/b19/P49/SL/oP2hoKF/ICBgYD3+vj8+/n2/oGA+ID07+vq6+7t7Pb5ifXyg+bx8u/l+uTs7/j39/Ps7OaD8uDv6urp6fPy5uPv0t7j5OXy0N78gejf0eDc19za29Pd1NPV0trX1d3P09TW0dfUz8/Hw8jH0d/SyMnQ0sfAzcvOzM7Y1NbS1cnJx8jJx7/CxL/Ct7/DwL3BuLW5s7G0xIC4ramprqyvsKq0uLS0vbm3vbm0tbS4sLizsLStsLW1tKyxqKmlpaqpqKWeoqOqp6qmpaSgo6q3pp2epZKkm5CwmpmapKCim5ian5mRmJmPkYuMkIiMlIuOjJSamqCSm6KbnZedlZSOipWajpCVlImSlZSWkZCTmIyQmJmblpONi4CKkY6VmJGRmpWhkZSYlpKTlpOPjIqLiIqKlY6SiJGWhZaQhJOVlZKWo5uUk6G9+Z2RnpmVmZWQnpmVl5OPkZKaoJqUkouMk42RkZWdlpmjmpORj46SiIaFj42OjoyWoY2ZkZqbq7yhl5eYi56Tk46Ql5WSk5aUlZaUk5yWlZSQl4CUl5OUlJeXlrCrrZycmpyVmZiZl5OXk5abmJebm6aZo6WnpKOioaOioZ2kuLKmpJ6eoJynqaappaijoKiurainpKGioqiqoZ6hq62vrrG0qqysoaKir6uzta2xsq6npqippqaqo6qurKizsLKurKqpqa+qsLe3u7m4uLy/vri6wo19AX6XfQF+1H0Cf36nfQF+yn0BfqF9AX7CfQJ+gKd9AX6RfQF+j30Hfn5+fX1+fo99g36EfQF+jX0Dfn59hX4DfX59hH4DfX1+jn0Ifn1+fX5+fn2Efoh9gn6LfQR+fX1+kH0BfpV9AX7/feZ9AX7/feZ9AgIEAIDOzdDV1dHNxszRxsnNyMDJwsK3xMjKwM/IzM7Fx8nSys3KysLKz9TS1NLg5trb1d7g2Nnf4drF09LS6tHSzdHp3dbe3t3W2tzc5eTg5eTe6efp4Ofg2+3V4ubpc9fk4dzg3OLa6ebm4eXn2t/T3eHe3tbY3eLg4OPl2+jk1uDh3IDY8eno4Oju5Oje4eft593c4eTi3+Xr4+bm5N/c2d3c3Orl4ePd7efi4+bn4t7W09LS09vn5Orn4d7b5uHc5OTf7uvo7ufm9O/f5+728PDw7uro6eHy7ert7O3u7Ofo6uDn3+Xh3ePe4u7r8e35+vX3+/Dt6Pfw8fHv7vDr+fXp94D39Pjt74vu7e/w6PHo5e7o6fT18e3p6uzh5ufl2dzj3t7b39/l4+fm7PPq6d/e4ufd1+De4uLe2eDl5Njj4t7edeLggeTd3tPS3czU0d/S2NvZ0nNz4ePe3ODZ09PW3t7h19fQ3Nl6487edNva0NXg3N/Yz86H1dzW0tPKfNDV0CTPy87YzcjPzMvGyc7O09Pf29bV1tVt0tnSzsLIwcDIZ2VnztGE0IDNzsxmyWhoYmZiZWRmZWdmZmrGbGzOamppamxsbnGS0nNxb9BpbG5saXJvcXBubm9vcWxramxtbGzSaszLz87PbG9t0mxsa87RbmttcNfY1G3LbcrGycnBxsvCx8nddMnL09jCw8trzMW7u8rHwbXRwsLEwLu2t73Dxb7Bx77ByYC/u7q3wL2+xcK5vcO9w7a+uru8xLu7q7u1s7OwsLu6sa2ztKq+r7C1uLK3t7W+trO2tLW4sKipqq2sqKqrtrjFr7OqsaizrKOhop2TnJqTnZqRk5WRmJihnJSWlZaZm5CdmpWRlZKUl5CTmZaTkpmRkZGSk5iZmI+clY2XjpyKlYCTh4qJi4qNiY+NiYeIiI+WmZSjkImUi4mMjYmMhomLhIaHiIiKiIiEio6IgYODg4yLgYN/h4WKjZiMkpKTl42QioqNjJKIiYeGi4iJiYSHhY2Ht4mPh5iUh4uLi4qHi62hiouOioaHkYaJiZKEjYODjZqYjZigkoyPlYiMh4qLkICRi4+VjJOPlI+Rl5aUjI+OpJ6VoZuVkZaVmZGTkIyTjoyRn5eWlqGbmJehpKWVj5aclpOfl5yzoZmWl6Chn5aMko6Ul5aZl5KgnKGaoK6mna+ft6SkpKamoKGqnp6mp5+opZ6fop+foqGjoqaooaChoqCdo56loq+zn6yrtLessVixsqyvtre4s7Sxrquyqb61q6+tsrKvq6qxpKOvrqy0s7WzurG8vbi6tLa0sravwbm6ury0uLS4tK6ys73ntsPvwcXFv7fBxbu3vsTHztzAwLy+ur7KzMzRgLq2ubzAwcC9wsfAxcjEvcXAwbbBxsa9yMDExr/BxczHysjIw8fKysbEwsjKxMPCxcfEw8nOx7vJxMzw0s7GxdvRx8nKyMTDxsnOzsvS08zUz9PN08rD1cfQ0tl0zdrW0tTR2M/Z0tPS09bM1M/b3Njb1dbX2tna29vU4d7T29rZgNLo39/Z3uTZ2tTW193Y0dTX19bV1+DW2NrZ19fW2NTU3tfV1dDf3Nnb3t3c2djT0tbT2d7V1dHPzcvY1NTR0NHZ2dLY0tTe2s3V29/X2dvb2djY0t/b1tzb3+De19nc1t/Z3drU2dPT3tfa197d2d7i2tbW393c3d7c29nl3NDZgNnW2tHUfNfX3NvX3dnY3tvb4+Hc2tjZ4NXd3NrT2dva2dbTztjU09LW2NTWztDS1c/L1tbX2NfU1tjWzdfTz89z19Z/29nd1Nbe1NnX4tbW1NPObm3V2NfV29HPztLX19jT0tLZ2HXj0t974NvV2eTe3NjU1H/a4d/b2tR43N7ZCNnX2t7W1t3ahNaA1NzZ4dvW2dvdct/h3uDa3tvc4nRxcOLe3tnc2NjX2XHacXRucG5vbm5tbWxscNBwb9xubm1tb2xucXrVcnFy1Gxubm9rcXBvbWxsbW1ua2xrbG5vb9hu2Nvc29dvcm7Wbm5t0tNvbm1w2NXSbMxw1NPc2NDV1tLV0OR0yMvP28uAz9dw2M7FwtDSzMfay83QzMnGx8vQz8fK0cfL0szGxMHIxMXLyL/ByL7GvMG9u8DIvsC1xb69u7i6xMG7tr68ssm3tba4s7W1srexrq+vsrawq6yvsrCtrq23s7+qrKaroqyuq6irq6Wurqiyr6mrpqOjoKemoKOjo6KknKagn5yAnp6cnpaanZqTk5eSkZGSk5eVlZCalIyUjZaMlZeOkJCTkpWQlpKOi4mIjI+Ri5iIhY6Hh4uOiouIjY2EhoeLiIqJjYeNkY2IhoWEkI6EhYGIiIqIkIeMjImNhIeDgoeMk4yLh4eMhoeGgoaFjYauh4uFkpGEhoeJh4aIo5yIiYqAiISHj4aJiJOGk4iGjpaii5CXjYqOkYmLiYqIj5KIi46IjYaGhYiLiYaEiYebkoyVkImGi4yPio6Mi5ONio+XjouKkY6LjJaan5KNkpmSjpOLj7aWj42OlJiYlY2SkJOYl5WSjJeQkYyUnpmRopSjlJmZmpeTl6mWlpublZmUkZJ0lpOVmpaYlpyemp2en5uYm5qbmKKjl5+ao6GYmpiZlJieoqCcn5qYlpqWqaKan5+jpKGenqSdoKmppauopaSqoaeopautq6ipq6Wyq6urrKiwr7Gvq7CusMqqtt6ws7W2q7K1sK+vsbK+zrS0sbizuMG9u7yAwr3DxsnHxLzEyL/DyMO/x8THvsnNy8LKwMTGvcLF087S0NDKz9DR0M/P193W1M/S0svHy83FtsTCzvnZ1czR5trR3eDe09HOztLSztPWzdfW2NPd1dLn0d7h7ofa6ubh4t/l2uXd3dne4tPc09/k3NzX2d3i3d/g4tnl4tbk4t6A2fLq6d/m8ODj2N3i5+Pb3N7c2dbb4dbb3t7d3t/i4uHu5uLi2ufg29/j4d/d29XS1tbg6OLn5uLe2ubc1+Dc2ePe1dXS1eDh2OPk5N3Z2djS1dTO4t7a4N/h4t/Z3t/d49/l493g2t3n3+Ph6+vo7vTq5OLs6eXl5OPl4e/q3euA6ubo3eKG4OPl5OHo5OLl4+Dr6ODe3dzl2eHk5uHq8Ozs6Obh6Obo5uXt6Ovm5ufq3trj4+jp5+Xn7urf6Ofi5YDs6o/w7vPl6e7i6OXy5enp6+iBhvn9/fr+8+7t7fHx8unm4+7ui/vm9on28urq+/Lv7eXlje/6+PX174X3/fgz8+30+vHr8fHt6Ojr7fbz//z3+vv9g///+fv5//f5/4SBgP/9+vT38/P194D6hIeAhYKFhISAgoCE9YSC+4GBgIGCgYOGyvuIh4b7gISFhIGIhoaEgoKEhYaDg4CCg4OC/IH5/P7++YKHgv2BgoH6+4SCg4f//PmA8YHy8/v16/L37e/o/oPi6/T/6/X/h/3w6Ob09ezi++ru8e3s6Ovw9fXs7PPl5/Pq5eDc5eHh5uHX2d7V29KA2NTV3eXa3M3l39va1tjl4dbR2dvP5tPS1dfP0M/O1s3KycvO0sfAwcfMycbKztva6c7TyM3BysO/vsPBucPEu8fEvLu1sbSxt7Goqqeor6+rubSyra+vsLStsbe0raywrKqrqquusa2msqqhrKKvpKyuo6emp6OpoqihnJibl56Ao6ijs5+ao5uXnp6XmZaYmo+QlZeTlJGTjY+RjIWEgIKPkIWOjJeXm56omJ2amZuTlJGLio+VlJeSkZaQkJCLkZShmr2TmJCemY6Sj46PjpO7sZOWmZONkJiOjYqYiZiLh5GbrJGXn5WSlpeLjouLiY2SjpGXkZmUlJOWmZaSjpGAjqGbk5uWj4uRkZSPj42NlYyMlKKWkpKfm5eYoaWmj4mPlZCMlY2RwZuVlJmfo6ahlpiVl5qXlJGNmJOXkpqooZWpmK2bm5mZmJKRqJKSl5eSm5eRl5mXmZucn5ugopmZmZuXkpqYoJyqsZ6ooKqsoKGkpJ+kp6uqo6ahoaGlobhJrqerqq6tq6mmsKKmrKqmq6qpq7OqsrGtr6Smpqisp7ivrautpqmnqamkpaas3qax8rG3ubesuLu0r7W1uMHMt7a1u7m8xsTDxNt9AX7/fap9AX66fQR+fX1+j32CfpF9BX59fX1+in0BfoZ9AX6YfQF+iX2Dfol9An59jX4EfX5+fYl+BX1+fn59lX4CfX6FfQl+fn59fn5+fX2EfgZ9fX1+fX6LfQF+h30Bfv99/33/ffR9AgIEAIDJy83L0tLR0c7QzMrNy8XEwsnKx8fO09HO0crUxsfFysbDz9/Ly8fPy8jW19Ta09nZ1dzah9zV3dvW0srU0dTS1dvS49Ph3ePi2t3j5OHd2+vo8/Di6efh4ubg4eHj4N3n6Ovr6+jp5ebq3+Pd5ufl5uzl5ub16+Ps6ujn5uXr64Di5+fm4uDo5uPi5erv7OTj6Ozn5Onm6OLd4OPd0dfa3uPf4eTc4+jp6eCI49bS3t3k4dzm6erp6vOZ6/Ln4uTl7uru6ert7enq49nn5eDq8uvt6ur17e3n6+jk5uHl7Orq6+j1+fSK8vHw86Dw9/v5/fz49Pb5+YH89fny9frL84D56++A//eB+PP18ffq5+jp2uGKgOLl6unn7urh3uR74uDe4OLe4t/q7ebq7vDv5Obl5Obk5uji6ePs5+Lv6OPg5ebfduXY1dba4djW2OB+3OHi3+np6OXd0tLT1t9v1dnb1dPS3eFXdNd/23DY0tLT0t3Z09Pi4NrXcHHL0dLTa1/Q1W1u0tTW18/QxsXQbtXWcXjZ3nZubtPOZcVkZMLAxMhoatbUbtbW0m9s0NBoZWVoc3tnbXJubntobWqDa2dra250cXB0fYRvcm9ucm5sa9Rv0W1r0tDRZ2praM9qa4RsgG52b3Bqbmp0b29xcHBtanZqcmtsamhqbs9oyMfIxLq+zM/R1dHU2M3E2cHKwcvH0tLey8zLysDDwcDAwsRqyMHAwcDHbnC7wcO/yMHFyMK+wL/Fu8S/vLjCxMW9tre4sbbEsLK4sa2/sbK2rbast7u5srS8r661u7a4rq6mrKmsgKusqsy2t7Kyt7CppbSqp6GdnZ+Uo5qYnJWkoJuWlZWXnKOfmpmUlZePlpadnJWJjZCQlJSTl5WXmpuRkqGbmJOQppiPlY6IjZyRiYqNo5aNipeZpZCMlaeRiY2Qj4+LjY+Kio+ZiYWOiIaKhIqRiISKkYiNi4CFj4iGioaRkpKQgJSSlImIl5KPjpSWhYaHj5OMjYqGko+Mio+UjH6HjomGgoyPj4yFio6EkY6Nm5OGk5aHlouGl4+UnJmdmIyaxJGUjYuWjpGQkZqUj5iTlpKOko+TkI6UkpCXoJ+htJ+al5eXkJGXnJSSm5iTl5iXl5exn56cm5SZmZ6ylaCZnpabgJWXl5CRkJWRmpqcpK6gnp6em6Gnm5ufqp2tpaWhn6Gcnp+poZygnqiorqefmZ+lpaGnpJnO3LCusKqlp6ejnLaYn6Sjn6ersqi/ucCoubi0r7W1tbSxrrewq6Sno6Ofp6mxrqutqq2zsrWvtLm2ubm+99y0ubq6vLWuube5ur67Hbi8r7jJxMjKvb/Gu9bYzce/uby3uLPAsbzCxtrIgLq8ube+wcPCvsPCw8XDvb68wL67vcPGxsPJxdHJycnKycXM3snMyMzIxc/Iw8bCxsTGysuJy8nO0MzHxczJy8zN0MfYy9HLztDKzs/X1dPQ19Pb1MrM0dHU29fY29vU09XV2NXX0tHU0tjR2NXe29rb4NnY2eXf2Nzb2Nfa2dvfgNjd3NrZ2dzY1tbY2t7a1NXX29zZ39/g39rf4N7U2dvg5ODc2tPT19zc1H7a1NPd2N3b2d7d39vb4oPY29TV09bb19nW1tza2dnVztjV0Nje19nW1+Dd3dnd3dvg3OPn5+fo4uzs6IHh29raj9fb4N/j4d3a2d7cceLb3tve5L/cgOPY2nPl3HHb19nY39fc3ODV2oV7293g39vg4d7d2nXV1dXX2trb1tvZ1dTV29nT19jV2Nna2tjd2NzY2OXb2NbZ3dt63tjZ2dzg19fW4H/W1dbT1dnY1tTPzdHV3nDV2drU1dTf4Wx013nddNrW1tjW3tvY1OLb1thwc83W2NlugNfbcHLd3t3e2dzS0tty29lyd9zac21v3dxv3HBx4N3f3XFv3Ndw3NzfdHPe3nFvcHJ/hG9zd3FxeWtxb4Bwa21tbXJtbHB3fmxubm1xb21u3XLccnLj4uRzcnVz3nJycXNxb3B/cnJscW13cXBwcHFta3Rrcm9ubmxtctVs1dTagNfP0dfV0tHQ1NjRzNzP19LXzdTV283S0NLK0MvKxcjJb87LzczKz3BzxMnIxM7GxMq/vsK+yMDIwsC+x8fLwb++wbm/1Lm4v7e3xrq6vra7s7q7urO3vbGxur+5u7O2sLWxtbCvqNKysaqrraump7Wxsa2trq2jsamlqKKxq6WigKCdnaGmoJ6Zm5uimZ+foqCbj5OYlpqal5iWlpmakpSimpaTk6aYkpeSj5ShlY2Mj5yTiouUlqKOiZGfjoiKjYuMi4yNjIqOooiHkIqJj4+RlY6KjpKHjI6Bh4+JhouGjI+PjIyJjIaFkYmFiI6NioeGi46Hh4aBjIiGhYuPiXyIgJSMh4SNkZCLho2NhI6Mi5qSiJibiZiKi5GHjJWRk46Jk7qOkYuKko2PjY2RjoeOiI2MiIyMkpKQko6Nj5SSkqyWjo+QkI2MkpWPjJONh4uMjI6Pq5eYlpOMj4+VoomTkJaQlpOYlpSTk5SRmJWSl56RkZKRkZWelZWUm5CdmJmWgJacmpmaopyVlpWkn6GdlJOYoJ2dn5qVtcGipKSgm5+dm5a/k5uamZWZmZuTqKCkkKKgoJ2ipKOloaGspKShpaKmo6ipra2srqipq6qnoqSop6iorNS6pauusbezrbWusbK4tK+vpKy3sbOzsLGzqsbMvLiysre0t7bCtbzAvcy6gLy+wL3DxsbDw8fIyMzPzM/N1NTS0NXV0c3PxtHHx8bLx8XN3srOy9DOzNrX09XNz8zIyceFyMjN0tHOzNTR09LU18/h0trV1dbLzdLW2NPO2djl4dLb4+Hi6eLl6Ozm4ubn6Ojm4OLh4ebb4Nvi493e5ODf4O3n3+Xl4eDi4+fpgODn6ufk4Oji5dvg4+bl3Nzd4N7a4d/g3dvk5ebd5Ojp7Obf3tTV2t/i2obg19bg2uLi3+Xn7+rm8ZTk5t/d2Nnd1trW3eTj5+nl2uLg1t7m3eDe3+nm49/i4uDl3eTm5uPk3ejq7YTj3t3djdre4+Pr6eLg4eXidefj6eTq9K3xgPbl5Xrw53bm4erl7eLg4OTU2oR83+Dk393k5+Tm6YDs7evt6+Tj3+jq5ent8/Ht8fHs6+zu7+jw6+7q4/Do4+Hq7+mM7+fo5+vv6Ojo8Ynq7e7r8vj29vbt7PH0/IDv8vXs6e77/ZiD9JH4gPPs6Ozp9PHu7P749veBhfD6/P6AgPv+gYL39/L57vLn6faC9/iEjf7+hYCA//+B/oCA/Pr8+YCA/fiB+/r6goH8/IOBgoaZooWKj4mHkICHhaOFgIODhImFg4iRooSHhYSIhYOB/4T8g4H9/P+BgYWA/YGDg4WDgoWliYmDiISPiIaIiImDgIuAh4GCgoKDh/6B+/f5gPPo6O7s7e3p7vXs5/3p9u/58vr4/ers7u/o7Ons7vH0hPPy7Ojo7oKE5Orr6PDm5erh3N3a49zl393f6+ru5OPj49ng7tzc5dvb7uDf5Nzj2OHh3tTX3c3I0tnS08rMyM/LzszKxvfS083P0c/IyNfLyMC/v8C2x767v7vLv7aqgKqko6mvrqqmp6mxqa6xtLSuoKWrqKysqq6rq7Gyqqm7sq2oqbyup6yppau+raKjorSjnZyprb2koKnCp5+ipaOkoJ+fm5ebq5KRmZCQmJWYnJSPkpOIjI2CiJGOkJiSnqGinqCcnJCOnJSQkpeaj4+PkpiSlJONl5SQjZagloiQgJmPjIiTmZqWkZeXjpeRj5+WjpqhjJ+R1ZiPk5mUlJCIk7mNko2Mk42RkpKZk42Xj5WRjpGPlJGNj46NkZiVl8Sgk5WTlY+QlpmUk5yVkJWVkZGUuJybm5mTmZWXqI6Zk5uUnJidm5uYl5eUnJaUmaGRjJCSkZyhmJSWoZGclpiWgJSVlJaYopmUl5Ojm5+akY6Wn5qZnpaOqb+fnqKhnqOhoJrLlJycm5WenqOZr6mtlqmopqOnqaamo6CppaSkqaaspKupq6mkpqKjq6uvp6yuq7GvsebGpamrqrGvpq6pqquvrauxpa28try+trS1q9HTvrq3t722ubXCsru+vdC8tH0BfvV9AX6OfQF+rH0BfoR9AX6LfQF+hn0BfoR9BH59fX6LfYJ+in0BfqR9AX6KfQF+jn0Bfoh9Bn9+fX59fo19gn6EfQV+fX1+fol9EH59fX5+fX1+fn59fX59fn6EfQx+fn19fn19fX5+fX2jfgh9fn1+fn19fYR+AX2efgJ9fqN9AX6GfYJ+/33vfQF+/33pfQICBACA1dLL08vL19WCz8zd0s/Rz8XP1tHE3NXZ6d3Z1NHUy9XLytPV2Nb20d3W1dnf1Nbg3eTd2OXe19/Wztnb4N7W4OTb4O/l7OXz5d/d2u7w4OPr7Ovn7Onp3Nzk5+ne7ZHn8Pb08O7v543q7+ro5uTo3+Dk4Oft8OPr9O/58vLx8emA6eTh5Ojb3+Xl4ujz7+no6uvu6Ozp5Obg3Zbi29/Y2+Lh5uTo4+Ln6/H26ejl6uPp6ePd5eLo4euL6Ojs5fDt+/Hw7/Pm6ejx6Ono6urm8ert7Ofi7+7o8Ozm297f5OXr5+3s7PHz8O7u5/z29fj+gvP29/fz8/b08u727PLs5u2A8PDu6u30gP7/8/T58/Dv6oLr8/fy+fv05+np5+Lq9eji6/Pq5+na4eXu7enu5/D07Ojl5enh5ePi5OLi5+fl3t7g4+Hl4dve7Ovw6+Hl4Hjj5+Lh4dvh4d7Y2+DhXdjp7Ovh3+7j4NvT3NnbdX7d08/V1NbZ4dzb4JTe23BqbG+AbHBscmtqbGx119ricIbVbddtcW5raWtsbmppamNnwGVpatZxcnFxcGBsbWxxcmhoaWpwdG9qbnJsbXBme29obnJsbHHLbXCOcnJ0dndwbWxsbXNuaM9ra2xrbG5uam1rbnBtamxqbWxwc3FtbW9ubGlvcnFycW5vbNTHZ2hlZ2qAysbJbNTR3NbW1NLZ1M3KwcPM09HOysrL0tDPy8fAydPR1sPAyMXExb68wMzAwL7DxsHAw73Av7/Au7q8xMbMt7e0v7vCZ8K+u7W9vb23tLy6rrW1sb7Os621tb6xsbGqrq+xurK4tLGxrrW6uKqnrKetpqKfoqKopaOen3uhnaKAqJyUmaaempySlp2bn5aTjZGPr4iSlZWRmJGXlpaYmJOUmJKQkIiLkY2fkJWdj5WUnZWNhpqYj4yNk46FiImMjo2KiYuKhKKPkI6WlImRj5Cbi4aKjZSRiYmDhoSJjpGTh4eIi6uJiYOIi5OQio+Ih4mGiZGWlJSYkJjElY6Si4+AiZCXhJ6NiKiXkpCWkZKMj5GPjouIjoWMgI6glZSUnJCXn4eQj4uRipKUk5iSlZyelIyLl52ak5GPiZaboJydopiWmJqSlpySnbafoaSfmZqYmqCYl5WaoZ6doJ6cpIuPla2smpyZk5acnp+opaSmpKahn5+dnqCipKampaCfn6OAp6edoaOksZiTpaSwpaqmq6+tqKaroJahpampq6ywrqmzs6irq6unrq2xrbC4trOxs7a0tLG3q67PsbGwrquvraKqqK2yqbi8qbOws7G/urW/wcK9u760sLO5u7qytLK5s7y9vMPFycXMvMjDwsHDu8bDzcG2zcbExMHP1dTO2c+Avr+2vbm7x8V4wL/OxsTGxb/EyMS6ycXH0srLysjNytLJyMvL0MzwxNHNyszRxMjOxtbNytPNycnFvs7Qz8/Fy83HydHU2NLf1NDQz+Pf0tPX2NjX3tnb09LY2tzR3X/V3d/e19fWz4rY2NjW19jc19bc2d7d3tbd4dzl5OXo6OSA4+Le5Ojc3uPh2t3j4Nva3d7l3OLf3N3a1pXg2t3W2d/d4d3f3Nnc3+Hp3dvY29Xb3dnZ4Nze1t+J1tjd1trX4dfX1tnR2drg2NnY19fS3NjZ29fW4ODc5uPg2Nvb3uDk4+nk5Ojq6OHg2urj4OHpeN/i4uPg4eXi5uXp4unj3eGA5efi3N3hdObo2tvh3dzc2Xnf4+jl6fDp3uHh3t7d5dvW2trX2N7V2t/k4dna0djb2tnZ3d7T2dra29vc3N3e2dre4N3g2dbW4eDh4Nba13Pb39vX3dbd3t3W2t7ghNTi4eLb2+jg39nU3tvec33j3Nfc29ze4tza4IjY1G9qbXGAb3NvdW5ub2501tbccYvdcNlucG5qa2xtbmxucXB13HBzct9xcXFwb2ltcG1zd21tb3B2eXRucnZxcXVre3Nrb3NubnXSbnKZcnF0dHZvbW5wcXl0cuJzc3Z0c3R0cXBwdHRyb29ub2xwcm9sbW9wbm1wcnBxcW51c9vSa2xpam6A0tDSbtjV3NrW1tPc3dnb09DS19LOyszO0tPTz87J1N7a4M3J0cvMzcjGydHIycbIy8bEx7/ExcXFvr/ExsXKvLizvrvFasLCwL3CwMO8usLCuLu8uMPMt7G3usC2tbSusLCxurG2sa2sqrG1t6ussq2xrKioqqusqaiipHqnpK2Arqacn6mkoqObnaKdoJqbl52dwZidn5yXnZaYlpibnJaXmpWVlYyQlZKikZeaj5KPlZCIhJSUnYqKj4iFiIWLjY2LiYqNha6NjY6Ri4SLiYmSiImMjpWRjYqEhoeLkI+Qh4eLjbKMi4OFiIyLi4yHiIiDhomNiYuNiI/IlYyPiI2AiZCVhaCLiKaQjYySjYuKjo+Pj5GLj4uRhIyWj46Jj4mOoYmOj4yTjZGTkI+Mjo+Ui4iHkJiUjYyIgo2Rk5KQmpCOkpaOlJiNkqeQk5KQi5GPkZWOkI2Tl5aTlY6NmISKkKqqm5mYkJSXk5GYmZeamJiWl5iXlpiZnZublJSXl5YZm5qUmJqfq4+Ll56ilZqYnqKfnJ2hnJWeooSfY6WhnKmnoJ6inZmfnZ+ZnKKgn5ugoaKno6een8SnpaOjoaSim6Oip62lr7KkrKutqLOqoaurr6morquprbGxr62wrLOwtrWvsbC0s7SrtLa1tLauvba8sqi9uLe2tsDFxLzDu4DJycDIv8HNxnvBwtLLyM/Px87V0sfaz9Dm1dLNytHL1cjKz9HWz/DI087Mz9bLz9TQ29DK0MzGys7H2t7f3tLW2MzO2dbg3Ofb1dXP4+PQ0dvf4d/m5Obe4Ojp6Nrnkd7j6Obk5OLckuXo5ODd2+PZ29/e5ejl2+Hn4enm5efo5YDm4uTq8OLj6ebe4+bi3t3i3+Xd4d7b3tnWluDc49vh5eLp5ejj4eXo7O3g3tvh3OXl4Nzi4OXe65ro5+bZ5eDq4Nnf4d3j5evg4ODb3tLh4OHk4Nvp6OTw7efb3dvg3uHe5ODh4ujl393W497e3eV44eXk5eTk5OHh3+jf6OTg6IDq6+bh4eV26u7k5+zp4uvlf+Lk6efs7+jg5ujl4+Xx6ejz9vHu8eHj5+zs6fDn8vTz6ejp7OTp6uvq5eLo7Oji4unp6Ovo5ubx8fTz6ezsgfb59/f99/7+/PX3/fyX7f39++7y//f08Oj39fqEj//58vb18PX99fX8mvz+hICCh4CEiYSKgYCAgIv39PuArPyB+4GEgoCCgoGDgIKDgIf/goWD/4ODg4KBg4GDg5GYg4WHipWWj4iMkIiKjYKbi4SKjoiFj/uFhsSPh4mKjYaEg4OGjoeB/oODhYKDhISAg4OHiIaEhYOFhIiLiIODhoeEgoeKh4WEgISD//eAgoCAhYD99/eB//r/+fT08Pv79fPq6fL28+3o6evz8PLw7+nw/fn/7ubv7Ozw6ujs+Ovr5+nr6Obo4Obo6uni4+nr7PDi4dzn4vGC6OXh3eXj5NzZ4+HU2trV5PTSy9TV28zLysLExsjUzdXPy83J0tbVycjNxsrDurS3ur25ubS3iLy8w4DHuKitta6qqqOnrKeln6Kgqa/jrrW3tK+2rbGtrrGwq6yup6ijmqKnpbimrbCiqKOqo5uVqamzoKCmm5icl56goaCbnaCYzqCgoKSelZ6bmaGTkZSVmpaTj4mLjZScnqSYmZmavpSRio2Rl5WVmpGRkYqNkZWOjpKPmvqgmJ2Ul4CRl52HoYuKq5eUkpqUk5CUlpWUlpGYkKCHkZ2Uk5CYkJWjjJGSjpOLkZKQko2RmZuTjo+XnZiSjomEkpWWkpKmlI+SlJGWm4yUpZSXlJKQlJGSmJCRkJecnZyck4+ahYuRqqmXmJqWmZ6dnKKdlpmWnJaampmcnJ6in5+alpaUlICYl5CWmqCuk5Cdo6SVmJSYnJuXl56WjJadnZyfoamnoK6uoqCgm5abm6Ccn6WnpJ+io6ezo6mbnsWjo6ChpKWmoampqrClsbKeqaaloK2npLGvr6uprqqjp6mqq6apqrOutbawtbO1srmst7a2tLmywrzCubDEwr28u8fNycTNw4h9AX7VfQF+iH0BfrF9AX6efQF+tn0BfpZ9AX6JfQF+vn0Bfo19AX+OfYJ+i30Dfn19jX4IfX19fn59fn2NfgV9fn5+fYV+AX+bfgF9kH4BfaN+gn2FfgR9fX1+wX0BfrZ9AX7/ff99/32GfQICBAAX0M/N0dbW1Prr19bd397W29fS187V1dWE2oDR2dXP1svWysjY2Nzb2N7Z09zc1tnY4ePd6+Dn4dve2d/n2c/c5ebo7/j/7PHp5ujs8+vh5+Xn432C4djT2trh5uHe3uPp8vXq8vDs8ef26+bl6OLl4+jl+ujq7ILy7/jv9vDy4fH16enj5efs6PDn5+/l5ujh5unp8Ozg5PLc5oCF7vr08+vwhPb19vby7ujs7Ovk6enu+P3z6uqEePD18+zl8PX7+e3t/PLn8fPy7fHp8+nu6u/t5Ovu7eru6ufz7O7r8PXv6+309fTx7u/q8vX5gPn2+fD8+P6Bg4OB94D3+/jx7PiCm/z09/by+f/8gv+HjvmC8fn9hoH26vTy84Dk6vWQ5/Px8vT08ujz7e3k6fPm8/T28ujk4OTj5+Xs5eLj6uLv6+rt9urt5dnl4ueL8OTj6uDndeLm4uDh3t7Y3d7V2uJ57O31hO7m3N7p3dzS3HPT29zX1tducNDVdGtrb4JtbW5tbWxpbG5xdHByb3Fwc3BubtHPamttbGpucIBtamtqaWhob3BvbG50bm9ucW5sbW5tbGpwc29wcnBxcW5xcW5rZ25veKRybnJxbm5wcHFwbWttb3Fvb2tnZGp0amzQamxvcW5wcWttbG1vc25ub25oa2xx2G1wbXJ0dGxqZ8vIZmlvbG5szW5tbWvTy8zLaMTN19zV19PN0czMyYDEx2psx9LHyMfBw8PBt7q5ucK/wMXBycXFwcPDwcPEv77BwcnJvby9t8XDwMS8wbizs7exqa+trq2wvLWqsbrOvbGyr7Cuq7CrsrezurSssLKAqaisqqOjorSvtLOrq6Wip5+fnJyZnJ+ZoNqgnZufl42RlJyblpuYlJuVlpiYmICck5mTkpiQlY+UlcyTl5KYjZuUk5GUnZeQlpCOkZCSkpGLlZmTk4+JhImLipCPkZGNk5STlaGaiJCQn46LjImGg4KNlIyGhoSFh4aGf4aLkYOHjISNkoiOk5a8k5WTl5OOjpKSjYqWjo6TjouLj5SUjIeIiIuQiIaKi46JjZmcl4CTnJaVmZSbmJeXm52VnZmYopaTlJKWlJSYlJWTm5mWoqKXoZuTlZqenJmhrJ6ipJ+apJ2XlpuamJ6UlJafmp6lnZuim6KgnaKVmZ2eoKWgpryrpqCdmKGnnqKmqaaipaippauprKeknpakpaSenaKipqOmqaKtqKaip6qpqKSjrWWnq6yvqa6xtq+yu7i0sbewuLXIrK+rq6fNrbKwtbOtp6itoKmkqrCwubatr6+zsq+6qra5t8LVysrJvr3Bxb21uLS4vL3Dw77I1cPGzM7HwsnDwMnHx8nOzsDEycXLzMrT0NXb1YC7vLe7wMbD7tXCw8jMy8XKysTKwsrLyszMzdDL0M7N1MzUx8fS0dLNzNHOztHSzdDN2NXP2tLZ0M/RzdDT0sjP2NTT1t3o2d7Y1dbZ3NfR2trc3np/4NnU2tje3tvV0tfa4eLX3d3X3Nrr4dvf5dze29/c99nX2nfb19va5OHl14Dq8uXn4uDk5+Ln3tvk397f2OHi4+bm3eHq2uV+6/Tx6+HlfObm5OLg3Nve3uDc39va4+fh29qBcePn5N7b3ePj5N/d6t/W397b2t3Z3tXb2tzZ1t3g493k393p4+jj6/Ds6+nv8e3s5+bj6OnqeOrn6d/q5+Z1dXZz33Lg4uXh3IDkfp/u5+fq5+jp6Xfreojidt3h53p259rl5uvf4eSN197b3N/g4Nng4+Tg4eXY4t3c4eDf1tvd3tre2tvY3dnj393g6N/d2tDd396J6N3e4tfcb9bc2Nve3+Dc3+HY1tpx3tzged/e1tvo4+HY5Hbe4+Df3d1wcdnbdW5tcoJucIBxcXFwb3BxcXR0cm9ycXNxb3Ha1W1tb25scHNvb3BxcnBuc3Nyb3B0cHBwcnFvb3FxcG5zdnJxcXFycXBxdXJua3FyerFzcHJycHFxcnVycW9wdnh3d3RxcHeDc3TkcXN1dXN1dHBwb3FydnNycnNtcG503G9wbHJ1dG5tbtjSbYBtcG1vbdRvbW1r2dTZ22/Q1dre2NbW0tfT2dfP0G5v1dzT0tPQ0M3MxszMz9jS08/L0cnIw8nJxMbGwsLAwsnMv7zAuMjHxMnEy8HAwcS+tL69ure5xL2ztb7Owbq8trq5tLSwsrKvtrCssbyZsbG0s62tqbOrrqymqaSipaOqpICnpKaqo6nMo56bn5mRlp6lopygm5WZl5eamZebk5mUlJiRlZSXlquQlY6Ti5OPjoyRnqCPko2Ki4mKi4qEjZKNkIuGgomLiY2Ok4+HjpCKi5GTgYiKloyNjo6LiYiUlo+Ki4qKkI+RiIqRkoiJjoSLjoOFiYmui4+NkY2Lio+RjYCKmYqJj4mJiI2Uk4uIiYyQlpGOkZGRjY2UkIyHjoiGioWMjIuMjpKQmZKVmpKUjY6Qj5CQjo+Nk4+LlJWLlY+JjZGWlJOep5aYm5SRmZSPkJSTkZiRkZGVj5WZk5CUjpWWmJuVlJSRkJSQlaSZlZSUjpidl5mcm5eVlpiZlJqXnYCenZqVm6Gfl5ecnaOfn6GdpaGdnp+goJ+dmaKeoKKkmZ6go5yboqKcnKKao6K1nJ6cm5zGpKanq6ypo6SpoKOfn6aiqamgpaSnqKivn6mrpqe4srS1rq20trGrrq6ys7Cysq6zvayyt7qzsbq4tbazsLK3taqwt7O1uLnAvsHHwIC9wr7Dyc7K49XIx83Q0sjS0czTy9XU0dTT0s7Kzs3L0snSx8bT0dLNzdHPz9bY0dbU2tbQ2tLZ0NPU1djg3NHY3tfZ3eP13uDe3N7d5t/Z3tve3YGQ29bQ29vh49zX1dne6erj6efk6uT37Obo7Obo5uvo++bj5n3m5+rm7u3y4oDy/ezu6Onq8ez16+ny6+jp3+Tm4uPh19ji0eCN7Pj17+LlfOjm5uXm497l5erk5+Xj8Pfu6eabgfL19ezm5unq6dvd7uTa6eno5ezh6+Po6u3r6fLx7+fr5+Dp4uXk6Ozm4+Lq6OXi397a3t/idePi5t3p5ud2dnZz3XLe5Ofm5IDnfaLq3+Hg3+Ll6Hfuf5vwfOXo8n976t/p6ezf5uyY4u3q6u3v8erz8O7o6/Lo+fX29fDt6Ovq7eru5uLk6+Lv7O3x++/v697p6OyJ9ezx+/H5gfn/+/v++/nx9/fr7O+A9/H7hvbz6e379PXs+IL0+/z49/uAgfb7h4GCh6GEhoCGh4eHhIWHh4iEhYCEgoSCgYP++oOEh4aEiouEg4SFg4CAhYWCgYGJg4WFioiHiYuMi4mNkIuIiomLiomLjYuHg4uLltuNiIuLiIeHiYyIh4SFi42LioiFgYeVg4X+goSHiIeKi4aIh4mKjomKiYqChIGG/oCCgIeKioKCgf/3gICAg4KDgfSAgYKA/fX7/YHv8/j/+vn59Pr3+fjv8oGC8/rw8vTv8fLw5+3q7Pby7+/p8+vp4+nq5ujm5uXm5e3y5uLn3fTx7PDm8ePd2t/WydTQz83Q49vJztv14NHS0NbTzM/LztDL08zIzteoysfIxsC+use/wb61uri3u7m+t4C4r66tpazWpqaip6Sdp7O+vbW6ta2yrq+ys7G1q7KrqaykpqWmqe2nraiuo62npaGms7WlqaSkpqWmpaidpqmko56UkZSYlpuboZ6Xn6KeoKaoj5eXoZWVlpeVk5Khpp6WmZiZnJmXjpGVm5GZmZGWloaKi4rAjpSUnJyYl5mXkYCOmY2PlpKSkZacm5KPjpCRl5SNj46PiYmSkZCPm5WSmJKWk5GQk5aPl5SUnZGQko6VkpOVkpGRlZCOmpaMlo+Iio+SlJOfrZugopyXn5iSkJSSkZmSkJWbmJ6gl5OXkZWWmZuWmZybnZ6YnK+fnJqXk5ugl5abnpqanZ6hl5mXnICdmpeTnaKhmZmcnKGbm6CZo5+bmZ6goaOfnKOcoKKjmZ2jo5ydpKGdnqKdp6OznqCgnp3qp6mqr66qo6WpoaOipaqprKyioaGhpKSqnKisqa/Iuru1qaWqrq6qsK+0tLS2sq21wbW2vcC6t7y5tri3trq+vLW4wbq8wcLFwcTKxdN9gn6ifQF+o30BfoZ9AX6TfYJ+tX0Bfod9hH4CfX6GfYJ+iH0Lfn1+fn1+fX19fn6IfQF+rH0BfoZ9AX6NfQV+fX19fol9AX6GfQR+fn19mX6Cfcl+AX2VfgF9iX6CfYZ+AX2EfoR9AX6OfYJ+zX0Bfv99/33/fZZ9AgIEAIDR2NTc0NDIq93Z0t7b3dze2d7Y19bVz+Tb09rT1NXV0uHi1Nbl2uDc1Nzm5eTa4eDX3Nnc5t3p2+3y7+Xk3eHp6YXx5eHx7fPz7OqE5eHi6urj5OLW19fe3t3c4eLk8PDq7fPr7vny8/Dw7/Lq5Orw9/T8+4KDgPz99Pz1gvHl74Dygu/r3+vw8Onn8fTz7O/w6u3y8P/y5Obx7eTm6+7s6fLu8fX0+fj06u3m7O3u8/Hy+IKVgvOBi/7z8Ozq8u708fH08PTw8fD1+fX29Pv18e7r8vHt7Pft9vb78+fu5+/69fLw8Pjy8+7ygP37goOChoqDiYeEqYaFhoGFgoD384D7gYGC/Pr79vn7gPv38u/2+PT7/oD+8vWEgPXwlu/n8vWBgPyC/vj07Oji437t6uTt7Ozi3ul67ezt6ufo6+np7enphZPm5Oz95evw6ejo5eXl5+Xk4eTh5eVz3drU4tfn5ebi4uXn5np42eDZ2m7Wbm3VbW9v3W/b1W1wbm9sboBsbG1wbm9zcXB2bnJybm5ua2pubGpqa2xsbG5uam1pbW1ub23X1mxubXFzb29rb3Bwc3NxcHNycXFycW5sc2ltaW19c3JxbHBwbW1tbm9yaW9rbGxpbGdoZGloY2RlZGdrb2xybm93cXNzcHdubG53a25ta210bmpubGxxcnZwboBtbnJrzGzSas7SaWxpbM7Gcm1s0W3KzsnEysjHx8nAx8zUy8rGxXjRx77AxcTFzMnXycjO0M7GzM7Nx8nF0XTEwcC+wr+5uru/v7W1vLa3trexsK+nvbOktLa2sK+usK+yrLGxwWy9u7e4taimoqiqoreiprCuu7Otsq+qpZyakoCXnZ6aoqShn56dl4yQlY+UmpGZnZWWk52TkY2VlZOMh46NjI6RnZecl5+gm5yYmpaYlpSQk4mNkZWSkZCPlpCHioyJipCIkpmPko+WmpuOlJaxnZWPko6QkIaHnoONfn+Eg4aOjYeEgIV/ioqKhIORk5aMj5SZj4+SkZCLmpORi4CTnI2RkI+PlJePioqKhYmJj5iQkY2XmJCYmZmSn5yeoJuinZ+Xma6Ukp6TlpuilJSYk5malpKYl5eZoZ6goZ6in5uhnKKUk5OYmqGYoKKbmJuikZORlY+kprO4qLypoZ+impyrrKGpqa6vnqefr6aZoqCgoKWin6CjoaOioKGmooCopKCmq6+usLOwo6KrqaKoq660qr+7va6wsbaqq6vBpZ2gqKiorqupqquorbmtqaqytq6orre3sLW4tbeptK6xuLCzxLyzs722srG5tLm4wsDDy8PCv8O4s725usHAt7/A6svDxsXCwMPMyMvIwtDN0MnMzunL0MvQ1dHT2d3X2IDBxcHLwcbAkNLNy9DOzMzNxszHx8jJxNbPys7Nys3NytvXyMzZ0NLPy8/X1NLKzs3Hz87Q39ffzt7j3tfS0dHW23/e1dLh2drd2dd32NbZ4ePe5OXb29zh4d7g5eXh6enh5eXa3ejg4efk5Ofg3d7g39vd2W1ubdvc2OLlhOnh6oDxh/Lr3ubs6+Xg5+rn5ebm4ubq6fLq2+Dt6OPm6uzr6Ozn6Ofj5OXj297Y3uDh5d/d4XaFc9x/fOrj4d3f4uDh4uPk3OHZ29rb3Nnb3OTh4d/e5eXh4u3m8PHw7eTq5+vz7urm5+7q6+bpeermdHNydHdxdXNymHR0dnF2dHTf3YDkdnd46+rw7urqd+7w4d7h5ePl6HTn4OB5d+bmo+ni6OZ1c+N26OPp5+jn537k3NXc3OHd1N525ODg3tzb3uDg5eDieIXZ1tvq2N3l4t3j4eDg39za1tva4OJz3d3X4dvo5ePf3+Lk4Xd22uXe4XXjdHLgcXNw4m/g23FzcnRydIB0cXJ0cnJ3c3R9cHRzcXFwb3FzcnFxc3NycnRzb3FtcnJ1dHPi4XBycXN0cXJucXN0dnRzcXRzc3R0dXJzeXFzbnOAd3Z0cHV1c3NzdXZ5cHZ0dXZ1eXN3dnx8dHV2c3J1dnJ1cXJ4cXN0dHpyc3R6b3Jwb3B0b21xb25wcHNubYBucXZu023Vbdfcb3FucN7ZeG5s1WzT2dnU1tbV19jL09je0c/Nzn7Xz8rMzs7O1M3YysjLx8jBw8XAwcK+yW7DwL6+xMO/wsbLysLCyMPBwcS8uretxbutucDAvby9vb28tbWzwXS7vLm6t7CwrbW4sMapqq2qsqunqKWkoqWlnYCkpqWhp6imqKWloZqbn5qbn5SdpZyemaSYl5WcmpmQjpSQkI6PnJCVkJaXkZSUlpKWlZKQkIiLi46MjYyMkI6IjI+KjZSOl5aOkIyNj5GHjZCynZGMjYyPkYyOn46WiYeMiYySlI+NjY6KkpCLiYSLi4uChoeMiIuPjo+LnZOOhoCOl4iOjYyKkZSPjIyPjpGOk5qNjomOjouQkI6Ik5CQkI2TkpOMkKCRj5aTkJajkpOTkZiYlY+UlZGQlpOUlJKWk5CXlZqPjIyQkZWNk5aTkZeckZKRlpCfnaSllaqWlJ+Zk5CYm5CXmZyilJuWnpqPlZaVlpyZl5eZmJebl5qdnICempednqCdnaKfk5agnpyfoqWknLKos52hoaWen6KunpqepKamp6WjoaCdpKyhoKClpqKcpaeoo6itp6ucp5+ip6Skr6ymp7CtrKywq66praaptbCytLevqrOtrbW0q7CxyLavsbKys7e9uLe4trq6t7S5tc+2uLi8wb3BycvCw4C/xcHKwMS5gsnKxc/Mzs3Qy9HOztHQzN7W0NXR0tLVz9/cztLf1dfVz9Tc2NnP0tPM1dPU39be0N/m5t3e2tvi4YDk2NTo4OTq5+Z/4Nvd4t/a3uPY1tbd39zd5OTj7Orj5ujh5fLp7fDt7vHq5Ons7erv63l5ee3v6/T1h/Xr84D1jfTq3eXn6eLh6+3t6uvt6Oru6vXr3dvo5N7h6Ovs6uzr7uzn6uzr4ODc4+fp7Ojm7X2Nedybm/Hn6Ojp5eHn5ejt6O3p6urs6+nq6fTv7e3p8Ozi4+nd6erp597m4ubu6+fl4+rl5d/hdeXjdHRzdnpzd3VzmXVydXF2dnbq6YDyfn5+8u/v6ursevHx6eft8O7w9Hnw5OR7eezowfLm7u57ee178evv7u7v7ony6OTs8PPw6fOA+fj7+fL29vbw8+/wgozp5+/96vH49/b9/fz9//v79/v6/v6A9vHp9ez69vbz8/f694SA8P3394H9gYH8gYOA/4H++4GFhIaEh4CGhIWGhISMh4aNgYeGgIKCgIKGhYaHiIqKi4yMhoiBhISGg4H9/4GFhImLiYuHi42Mjo2KiIuJiYiJioeFjYWJhoqZkI6Nh4uMiomJi42Qh4yJi4uIjIeLh42NhYWFgoSHioeOiYmSioyMipCJiYuRhIaFg4WJg4GGhIOJiY2EgoCChIiA9oD6gfr+gIOCg/74joKA/IDy9/by9fP09fTn7fT97/Dv8Kn78+nq7e3s8uz57Ozy7/Ho7e3o5unm8ofr6+rp7Ovk5OXl6N7e6OHi4uba1tTH49jJ2ePk3dnZ2dbUys/O3orT08/PzcPDwMnNwN+7u8G+ycG+xMG4tbq4sIC0s7CtsLKvrq2urKOosq6yt6u1u7K2srmys6+2tbOpo6ajoaKisaeyrre5srSztKytrKenpZ6kqKqqqquqraqgoaScnqScpqifoZygpKWYnZ/NqqCbnpufoJiatJehlJOXlJafnpWRj5GOmZqUkY2XlJKHj4+Uj5KUlZWOopeUjYCUoIqRkZKRlpqRjpGUjpONlpuMi4aOkIqUl5mTn5ybm5SYk5SLj5yMjZaSkZihlZOWkZmYlI+VlJGTm5eUlZKYkYuTkZqNjpCYm6CXnqKblp2hk5SSmpKmqbO0mraZlp6clZafnpWen6WrnKKaoZiKkJGQlJ2bn6GdnZmfmJuamoCcmJaZnKKfoqWimJehnpudnqCimLStwqOmpqmbnp+/mZGYnJ+dn5ydnJuaoqyioqOsq6WipamopKWspqiep6Omramst7Otrbatq6mvqaystK+utKussrmxrLWxsrm7r7q+2r+3tLeyrrK6t7m4tsC+wLm8ute3wru+xMPAxsnCxYd9AX65fQF+iX0Bfqh9g36FfQF+hH0BfrB9Bn5+fn1+frJ9A359fZF+Bn19fX5+foZ9AX6JfQl+fX19fn59fX6EfQR+fn1+h30Bfol9AX6MfYJ+lX0Bfo19gn6EfQx+fX5+fX5+fn1+fX2qfoJ93n4GfX59fn19hH4HfX1+fn59fpF9AX6XfQF+pn0Bfv99/33/fZ19AgIEAIDd3NbaztnV7efW3+bl3+Di4OXh4ujb19/p4+HZ2t/e4Nzh3t/p5Izl2trk6OHX8N/h3Nzg3+KB5dbk6+vs6O705+3x8ffs7evn7Orq4efo4eHg4d/h9Ozm5+nj5OTu++zy+O708vbv8PTx9fTy8fj+hf+AgoKDgPz7+fl+8/Dx9YD48vH28vXs9fT5//Ly6+3q6vLvm4CA7+r78uvq7vPv8pTx8+z08/n29vX09PP08ezz8YD5+/CA8vv07vLz8vLu8vn37vP78vTt6erl7vP99IKA7/Xv8+r26+7r7u3x7v3v7/L07fX08vrs/PyBg4SDgoeEhYqDiIyOg/+JhoGB9YD79f6Eg4SMhIOChYCQ/vWB/fz2/fn27fn38Pfy9vr27uXv4+zs9frymXvreH954Xr08fjl5/Dxfenpe+fb5+rp4dvd+Yfk3+b38Onz9PiGeX3l5OV34+GBdOPd4Nnh6n3k8+7r2+no7Orl4eR1dnNydXFwc3BwbXFt1NdvbW9tbYBubW9vamttbWxua3Nzb3Bvd3xyeXd9bWpoa2xsbHVydG9ubm1tbGtycXJzcnFxcXJ3dXZ3c3R7c3R1dHF1c3OJd3x4cHRycnd1dnF8cnFzc3RxcG5tam1qa2VmaGhmaGpqaWtua21zcXVzd3R1cnNwbmxqbm9sanNycG1rbWtrcoBydXd3225tamjMa2lqas5q1G3WbdJr0MzMzszGzdPR0dfU2tXP2c7JysTGxsnFyszO0NPS09LL19DOwc/IzsbEurq3vL24tbe5ur7B17rLsraxs7i4sre7ua6ztr25t7axtrSvuHB4ua+wuLKlnqamo6OlpLa3urW1qqqkoaOfnICjopqdnp6Zk5yeo6Kpop+en5iTlZaYlJyboo2TmZqTkZOPiYaQjYuJoa+NlZaglJGXm5aVkI6OnJGUl5iRh4mNiomRj5KXnpeSmpWYlqaZnJaQjpCPkY2LgoaBop+NhYmJkYqNh5OKiYWFio6ShoaVlZaNk46RjoySjIySkJCXmoC2kpOXlZWYnZOin4+SlpqWl46OkJCNmpqRm5uWkJSZlZmWmp6Ulq2im5udmpuTnJ6Vkpiak5CTj7ifppmemqOeop+ioJ+jnaCVpJihoqCcnqGQlpCPnJqhnLu/urGgpqido6Smp6KhnqSeoaSdzqmgoZyho6WknqSrpZ+jpqiopjCorK+rr7XSsK+qnaCnpaOosKm7rbWrrqivqKasqKa4va6vubi5srOjpLGrtrm8vbCEtEyqsK+hrLG2tcOyuLOxq7Oxr7qsrbOvtr/Bv7jAv8O5urq6vbWytbixuLi4vL+8w8rBvcK+wb/HysnN0MrM1dDX1tnT187Szc7r2t3gd8vJxcnCy8XY0MHHzMvHyMrKzsrQ1MrFztfT09TT2NnY09XQ0djVitXP09ze29f12djU1NbZ24Df0drd29vX1+DW3N7d4t3c2tfc29zW3eDf4t/h3tvp4d/i4t3c2eLr4eXm2t3d4tvf4+Pm5OLg4uV04W9ycXNxhOSAduTj5ufs5+bo5uTj6unu8erq5ubl5u/qmnB65OHx7Obj5urn6Ibl6eTp5unl5ePg3+Hj4d3k5Hjs7OF45uzn4eXn6ufh4+Xk3ODk4eXi4OPf4uXt5Xh34+fh597s5ejl6ejt6/fp6+3w6e/x7/Pn8u15eHd3dnl1dnl1d3p9dOKAeXZzduLm4+d4d3h/eHd3eHaH5uJ28evm6+bl4Ovs5+3n6uzs6OXr5uri5+fnmHrpeIV+5XXn6/Li3uXleODfc+PV4OPm49zb6YDk2dvo4dje4eZ9c3Xd29py3d5+cuTi4uDk6Xvk7+rj2OTi5OXi4uJzd3d3enV0dnRzcXRv296Ac3J2dXR2c3Z2dHJ1c3N0cXZ0cXFud351e3x+dHBwdHV1dX93dnFydXR0c3F0c3FycXBycXR2dXZ5dHJ6c3Z4d3R4d3WJdXx3cXNyc3Z3d3F7dXN2dXd2eHZ3dnh4e3Z2eHh3eXh3cnR0cHJ4dXp1eXR0c3R0cnFwc3RxbnV0c3GAcXRxb3FucHJy2XBxb2zRbm1vcdZt1m3cb9Zt2dna19bT1dbU0NXS08/P3NXT1tPV0tbP0s3LzdDMyszFz8rEvczFzsnNxMbFzc3Iw8XHyczP48fXwMa/v8LAu7/Cv7S6wsW+vsC6v7y5wpGKu7Kzu7mxrLizsKuqq7KvsK2upKeApaWopaOrqKGlpamln6WjpqSnn6ChpqCbn52dmZ6fp5WZnqCcmp2Yk5SWkYqHnaeKkJCZkY+Sl5KTkI2KlI2PlJONhIuQjImPjpCOloqDioeIiJeMj4yKioyNj46Oho6Jo6+VkZCPlY6RjJyOj42OkpKPg4SPjIqEiIeKjI+WjY6AkI2PkZSth4mLio2QlJChmYuJjJOOj4mJiIeEiY2FjY+Ni42Ri5KPkZOGip2Yk5OWlZaPmJiSkJSWkY6Qjq6anpGTj5SPlI6Sj5CVj5KKmo2WmJSSlJmMlJOUnJuclbCtopuOk5aOlpiamZWVk5iYmZqWtp2ampaYlpiXk56gnJaAnJyem5uXmZiTlJu9n6Ojmp+loZ6fop6nm6OdoZ+lnp2inp2rq6GhqKiqpqecnKikrKywtaWppqippKWknaaqrrC9sLCsqKSqqq24rKyurK+ys7Crr7C1r7O0srOsq66vrbazsrO3tbzAuLW5t7y2vbq5u7eys7q3vrzCu8S9vrkFvt/EyMyAwL67wLjHwNrPwsfJy8XGysfPzdLXzMjS2dbX1tbX2tzX2NbW3tqF29LT3d/e2frd2dXR0tXcheXV4+nm5+Tm7N7i4+Dm3+Pf2+Hi39fe4dza297d3vHq5ufn4N/Z3+vh5ezj6+zx6u3w7vDv7ezt833vd3p5fHvy8/b4gPb08/iA+e7p7ezt4u3v9frw8ezr5+jw6Jdfe+jg8+7o5+nt6uyA7O7p7urw7+zr6u3t7u3p7et98vTleOPl4t3q6+/w6+/09O3s9uvx6OPm3OTq8up9e+bq6Ovh7unr5+jl6ebx4ePk6d/n5eXp2ebidHV3dXd6d3h5cnZ3dm3Yd3h1eeyA7+zyfXt8g3x6ent6kfHufff17vDs5uLu8erz7PD19e/n7uXq5e7z9auB+oKQhvOA+/z/8O74/Yb8+oL36fLy8Ovm5v2I8Obo+vXs8/j+joGE+fn5gvv7kYH++Pjv9vqG8f719OX09Pn79/b8g4aGiIuHh4qIhYKGgPj8goOGhoaAh4aJiIWChoSDhIKJiYSFg4+YipCNmImHh4yNjIqUjY2HhIWCgoCAh4eIiYmKjYuMkI2NjoeIkIuKjIyIi4mKpo+VkImNi4qOjo6GkYqIi4uOjY6MjIuPjo+JiYuKiIqMioaJi4aIjouQio2IiYeJh4aDg4iIhIKLi4qGhouHhYmAhomKiv+Cg4GA+YOBgoP6gf2B/4H4gP79/vv28vT19e708fTw7frz8vjz9vP58PTu7fH18fHz7frz7eTz6fLs7+bl4uzt5eDh4uXq7P/k7tzk3d3i4d3g4uDV2+Pl3t3d1NbUztXDoM3Dx9HMxsHMx8K9urfLysrHyMHAwb7BvLiAvbisra6xrKe1uL2+xr27ur63sbKyta+xucSztbq8s66rpJ6dpKKfnrnHpq2rtqmkpqukqKOioa6mqa6tppufpp6boZ+jo6qhmaCcnJqpmpyYlJWYmZ6fn5eemca4pZ2dnKWcn5eylZONjJSbn46SpJyZkJeUl5SWnJOTlpORlJWAz5aRlZGTl56aqaKUlJablZeMiYmIg4+TiZSZk5GYnJaZlJeYiIygnpeXmpqZj5qYko+VmpaSl5SyoaSWlJGUj5GKj42Ok4+WjqKVoKOgnqCllZyXlqCanJSlpaOdj5ibkZaYmp6ZmJminZ+fl6+XlJORlZSZmJWfoZ2WmJmamJaAlpiak5adrp6hoJmco52Zm6GbqJelnaOjqaOeo5+crK2eoKipqKKklZafmqKkqLOfp6SsrqSpp56kqbCyxrO0saqhqKOmsqOhqKiwtrmzr7Gwvri+ura1rKqws7a9ubi5vrS4x7WvtLC0srm7uL6+ubvEv8XCx8HEu728vNnAwcSmfQF+j30Bfrl9An59hX6EfQF+l30Dfn9+in0BfpF9BX59fX1+mX2Cfpp9jn4BfYR+hH2KfgN9fX6YfQh+fn1+fn59fod9BH59fX6JfQF+iX0Lfn5+fX19fn19fn6GfQF+jH2NfoJ9/36KfgF9hH4BfYR+CH1+fX59fn1+z32Cfv99/33/fZx9AgIEAIDQ0tHVztji497f4uPp6vDj5eXZ3ePT4Njk59/c4uvv5d7a3uDh3uSA3t7t4+LZ197f0szh297bhuDh5ujj4uLg4+j29vLx8u/w8/Tv6uru8O3n6eXm6O3l6Obp7PDvhOv6+vn0+fv6hoH09/j58fCBgpOBh4D8+fmE+v74gfPzfYDv5uvq5/Hx5+Tj5uvp6/ft7fP1gPj08vyK/Pb08+7o8fP2+Pr4gffz9fDw7/Tu+feC7O7v+O/z8fPs5O70+v6BioKB/Pb++Pj2+Pr7+vWAiICA+vPx+PD28/TwgOvv9PP29vX094X3gfn4kICAg4WIgoSIiYiGhoqKioKKh4mFg4CCg4H9g4T/gYKEhf6D/YH59/x/gvr38ICG/vjv8O7x8vj++f3ugIDo8fR6ghVJfnqH9e/t+H/08vPy9/Tw+vbw4N/k7uji5+rs9vD59n1+8vPw4+p+d3Xb3HJyc3R3733r8n/x5u7pdHNy3HRwdHN4cG9wbWxwcnVzcHVzd3VulIB4cXRzbmxxcHZ3dnh4bnJub25xcG1ubW9wdXZ1cHFycXZudnRxcm92cXJzenZ3c3NwcnZ1c3Nyb3VvdXZ0dneJeoB7d3R2dXFucnRwcHFucXV0d3NwcnBtaGxqZWlobHBsbm9rbW1vdZJ0cm9vcnFwboNzdHNscXFtbm16bnBubYBxbHJydXp2cXBqzmlobXRv22tscGtua23Q09pvb3De3Nnez9TPzs3Q0szJzc3OzsrLzsvNz9HT19rTzNLOysjIvrzAtr/AwWW5s8G3rre8ubrEvbG2wLq+xL65ube0u7i8uLKssLLAr7C/rq2sr6OhorGzu7m4r7SwvKipq6enp4CqpZ6amp6ZlqCXnp+fn5qcmpaWYJqXk46TjZSTmp2lqKaYkpiTl42QlZaOkI+Xko2OjZCQlKOPl5WWmJiXjqeZl46SmJOUl5KTj5aNn5WalZWTiYiLioqckJCNjIqSi4mKi5ijppqOiI6MjY+NiYmTm46Oh42NjJWVlpCTqJKbmICLjYqQqqeKoJqdlpGVlpCPjpOenZiQnJ6Zn56glpyYnZ6fmpaRkZaVl5WVuJeZl5OZk5SVj4+Vm5+hq6Colqinn6Cdn6CcoJudm6SnoqGjt6iglpiYoJuZl5aco+SXpp+lyKSqrqKhqp2hn5ygoZykpqKrqqqqpKalq6GmqaOjqYCz5a+loKWssKWqpqWps6iprqaorKusqq2vrK+1tKytvL+srKyxrbWvxLC4tri5urO4sKmtpqmppbCyt7+tubiwsbG0s66usri4ta+4s7i3tbq0t7S+u7+1tre6ubS/vLrJyMnGxMTEwsnLzMzRztXb1NbO1NnZ2tza2uHb3dnY1YDDxsXKyNDV1szOz8vQ1NfMz9DHzNDF0MvT19DT0d/h3dTQ1dfX0NV61dTg2d3Y1Nja09Dk3+Ldhtza3t7b1tXW2d3g3t/h5ODf4ebj3uHk5uPh4uPl6e7l497f4ebjft3r5uXe4eTie3no6err5OJ2dH90e3Hh4eR46Orqe+vreIDq5evq6e/06+nn6e3p6O3j4eXndunh3+mB7Onn7Orp7e7s7OzmduTf5ePi4urm7ux74eHk8O308/Tw5/Dv7+13f3Z05ODq5eHh5Ofi4N91enR16OTk7Ojw7/Htf+nu8u7y8/Hw8oz0fvXwiHt5e3l8d3h6enp4d3t5eXJ4dnl1doB2eXfoeHrsd3h4ee546Xfk5+19f+nq43l+7url6efm5ePp6O7qfHvn7+93gGpLdnOK4+Hj63bl5OTf5OLg6ujn4eTm7+ji4uHf5eDm5nd34+Pm2t93dHPe3XN0c3R05Xfd5Xfm4erodXV043d0eXl9dnN1cG1wcHNycXV0eHdzooB7dnl4dHF2dXp5d3h5cHV0c3F2dnN0dXR0eHl6dnZ1cnVvdnVyc3J3cnR0enV7dXd1d3t6d3h0cXhyd3d2eHiHen99end5eXd2eXt3eHd0dHh2eXd2eHh5dnt6dnl3en55e3t1d3V2eJF5eXd1eXd3dYR3eXVwdXRzdHOFc3VycIBxbG9xc3dzcnJv229udHly1W1wdG1vbG7V1dhubW7V0tHbzNHR0dLV19bS1dfU1dDMzMrIyczPztHJytHJy87RycfPxs7PzW/FvszCu8DJx8XMyLzAx8PJzcbBwL++w8DDvrqyuLjEuLjDt728vbeztcG7urCxrLKssaWoqqepqYCtq6emqKikoKacoqKjoZ+gnZmZYqGenJual5qYnZyiqamal5qWnJCTlpqRko+YlJCRjpCSk6KLjY2MjY6MhqKTkoqSlJGPkY2MiY6HlI+Wk5KWjo6Oi4qWioiGhYaNioaHiJWiopeNjJCSk5CNhYaPk4qLiIyMjqKdlY6TnouUkYCEhISJnJp/kI6RjIyQko2Mi5OXlZGGkZOMkJCQiY2KjI6RjIuJi5GRlJWXuZWbmJaYkpaTj46Tl5WZnpGWhZaTkpKSlJiSlJOYl5ucl5aZpZuTjpGTnZmYlpKSkr2HmJKXtpeanJaXnpGWlpecnpWamJSbl5ucmZ2aoZyho5yanYCj2qKZmJ2mq6KmoZ+jqqCfoZudoKGloaWrqaepqaSiqq6ko6Oop6ukuKWrqaqsr6qup6SppqWkoqusrbWosrGrq6qsqaioq7K0trC2rrGwqrCvs7C6tbaws7O4tbXDu7O+vb66trm3tru3t7a7uL3Euru3vru+ur29u8XAx8PDw4C/wsLEw8/Q0sPHxsHHytDGytDHz9fI18/Y3tbV2eTo49jV2tjZ1Nl62dnp4OTa2Nvb083c09XUgtrg5+rn5ODf4ubu6ufk4t7e3+Xg2Nvh5uPi5+fp6/Ho6eXm5OXhfuHu8vLx9vj2hYDy8vHy6uV6eol6f3nw9PKA+f37g/b5gID06O3u7vj38fLz9ff18/zu6u7uevDn5+2F8e7t7+3r8O/x9PLveunm5+Xk5Ovk9PSB6u3r8ejv7unp3ebt9/h9iHp67+fw5+nj6O3u7+16gXx68Ors8uvz8/TsfeLo6uXn6uXl5oLkduXmh3Jwc3V4dHZ5e3p4eHp5enJ5eHx5eoB6fXrufH7weXp7fPR77nrt8faBne7s5nh/8vDq8PDz9vT59vrufn7q9PyAjquah4Ki+/X2/YL8/f/4/Pf0/vX06O3u+PTy9PTy//j//YKD+vv78/iGgYH49YGCgYKC/oX1+oD79Pr/gIGB+4aDiIiPhYOGgoCCg4WDgoaEiYeDtICOiYyKg4CIhYuIh4iJgIeIiYiOjoqLi4qLj5CRjY6NiIuBh4WEhIONiIyLk46TjY2LjJGQi46Kh42Gi4yJiouhkJeUkY6RkI2LjpCKi4uHiI6Mko6NkJGRjI+Oh4uIjZKMjpCJjYqLj7ONjImIjYuKh5mMj4uEiomHiYeZh4qIhICHgoaHiY2JhYaB/oKBho2H/ICDioSFgYL5+fuCgID49vX+7vj4+vj7/Pjy9PXz9O/u8fHx8vf7+frx7vTs6uvx6Obu4+/v7I3i3Oze2ODn5eLq5dfd59/p8Orj4uDf49ze2NPJztDi0dTa0tfS1MrCxNXRz8bEu8O+0MHCxcDBvoDBubKytbizs761vLu7uba4tbO0g725uLW3r7i1ur3EycKvpqmjqZ+jqa2np6OtqaGfnJ+hobOco6Skpaion72qppqgo56cnpiYk5eSoJqgm56hl5iamZmsnp+dnp6knJiXmaiytqadlpmYmJudkpajqJualZeWlK6km5KXtZehnICPkY+VqKaInJibl5abnZeWkpmdmpeLlpiRmpuakpaTlJSTjo2HjJGTl5eY05WYmJSYk5iYlJObn52hppaaiJaSjY2KjJGNkI6Vl52gnJ6jsaaclZeVoJmYlZCSk8uKmZOWl5SbopqcpJmcmpWanZOXk5KblpqZlpicpZujpZ+Zm4CgxZqQjpaepZ6kn5+iq5+eopucnZ+koaepqKSop6Cfqq2goaKnpKymuqWqp6qqrqiwqqeuqKunpKqrrrWnsLKsq6iqpqCgpKuvr6q0r7W4srezta67t724ubq9vbvBuq+9ubazsrKyrbO2t7q+vMLIu72zu7y/xMfFws3GycTBwad9AX6PfQF+pn0Bfoh9gn6GfYZ+C319fX59fX1+fX1+k30BfoR9AX6MfQF+in0Bfo59hH6LfYR+iX0Bfol9BX59fn19mX4EfX5+fYR+Dn1+fX59fX1+f319fX5+jH0Mfn59fX1+foR/fn5+hH0Bfpd9gn6FfQV+fn59fYV+BX1+fX1+hH0Efn5+ff9+oH4BfYV+AX2HfgZ9fX1+fn6pfQF+y30Bfv99/33ufQICBACA19Hh0NTa2tre3Nzj4eTo5/zpgPPl5eXq5eHgjPHi69zq3OTk6+vq4ujq7OT37+bp6ePp7+Di3uHY4O3o6ez3+v7w9PTv9O/89/X69/b0/Ozq7vHm5e/38PPu8vL78/rw9vv6+Pv5+f758fb3gPuBhv+B/fz9+Pv9+//z+3/49YEYffn08PHu5+/p7Ork6uLq5+vx7vHw9YL7hICAgfyChP73/P2FhIP+hPT4+PX++Pfz8Of1+vqE9vPm6+r0/ICA+IeGg/3+9Pv8/vaBgID8///79PSA7vX19/nt8fj39Pf///+Bg4D4hYWBgP6GgIOEmYqGhouHgoWGhYSGg4SFhIeCgoWHhoGE/o/+9fr8goH3gfiVh4P7g4WChPmA+oOBg4H9/YH9g4D38XzyeoJ8eoCXh4P/9Pz69Xzz+ISA/Pn78e3p7efs6ury73r4+H6A7uzt4ef06Zx0dXjl2NjY2dx25ejr6uh134Nwb25tcXF5c3Vxd3Rvdnd3ent5dnRzdnR0eG1rbXl6eXt3hHl3d3NzcXFsb3NsbWxucXSAdHF9cm90dXN0dXd0dnRwdXB1dXV2e3h6eHd2d3h6c3d2eHiDfXN5eHd1eHd0cXR2enh5d3h2d3N3dHJ4dHJvbmhvampua25uamxtdXpwcXFwc3N0cW1ydG9tcG1ram5pa2hzcG90cXR2cqt6btNpac9sam50h3R0gG5wampu1W2Abm5wcmxub3HS0WfGzMjPac/P08/Gy83PydHM1dfWztLPyMXBvrW7urq9w7+2u8XGw725vb7Kwr21u7m+vryzu8G9t7rFt7S6w7uyp67CtrKtqaavrKevrKetrLW3sKuksq6qt6ejnJqfnZShaLGgZpWYmJWWlJ6UmJqWmKG2lZeAkZ6eaaKgmJmblpGUlZeLi5aPj4iOmJaSi5SRlaKWl5iZj4+th5GLlZORl5iQjZOUmciakYSNiouLip6MjoyNl5OOipeTlKqJl5OUi4qPnpWhroyIjJGPiomRj5KPlJOPiY2Km5WTnJyclJickpSck5aZlJKYmJ6ml6CglJmclp2Aopefnaaoo5mZnp2glZ+Xkpafm56VmJeak6GerqegqKOkopefn5+bn6Gwq5e9sZqcpKarqrCrqqWZoZ2gm52kqrCvrrqro6GgqaWno5SYmqikp6mnrK2wr7W9s7Ozrqfbt6eipbCsoaixqayuq8K7q7GpoqumvbCkr6ywq7Gp1LBkr6ivsLCxvq6ys7C3u7nAw8e6ucDL8c2+zb2xs7e1urq2usmvubq4vbS7tsDCuL3UvL3AvbW8uL3BvsC6v7rBzcHDztTLz9KAz9DMxtLF0NXUy83FzdbQ19Xs5OXY2eDU1NrQ2IDKx9PGzM/Q0NXU0dXS1tXS6NV248/S0tjSz9B/3Nbb0tzQ19rf2tjR29nZ1u/l3t/j4uTp3eHh5Nzf6uLf3OTo8OLm5eXk3eTi3ubl5eTq4uHq8efk7O/m6ePn6vPs8ebu7Ozr7e7u9PPr7up563h963jr7e/p7vPz9+fweO/qeYB27+3n6+rn7+rt7uft5+vq6u/r6Ojpeet2d3Z0deZ4eerl7Ox7eXjqeubm5+n07+7t7eLr7e5+7PHt8e7t7nZ26n17d+rq4ebn6eN1dHXn7O3s6Ol45u3w8PPr7fLu6unz8fB6fH7xf358fPR+enp9kYB6eH16dXl6eHh3dnd3doB4dXR3ent5eO6I9fDw83l463jrlHx77Xt8e37v8nt6e3bn5XLmeHnx7XrteIJ7dHaKenjr4ejn6HPj5Xh26uXp5ubp6OPo4uDm5HTp6nl75+Pn3uDq4Kx2dnjo5uvr5+h46efm5+N04oVzc3Nyd3Z+eXl2eHVxd3d2enp4dnR1doB1d3t0cXR/fXx9eIB1dXVxdHV3dXh9dXZ1dnd4d3WCdXR5eXd4d3h0eHZzenN4eXh5fXt8enh3eHh8dXd3enmFfnV8e3p5ent6dnh5fnt7d3l3eXd6eXmAfX9/f3p/eXh7eXl6dHV2foF4d3h1eHh7eHJ3eHRzdnR1dXd0dXB5dIBxcm9ycnKhfHLbbm/gdHF0dpN0dYFscGtsb9VtbWtra2hrbG7Q1GzQ1dHWa9PQ1tLMzNLTzdTN0tLOx8vOzc7PzcjQzMzLzsvByM7Q3MfExMPPx8K7vr7Dw8O2vsTCvsLMv73Axr+6sLbCvL62trO5v7m/tq6xr7O0sLKnsqypt4Cpqqimq6ifrIe8p3uhpKahoqKpnaOinqGmtp2bmaaeaaKclZqZmpWVmZ6UlJyXlpKYmJSTkZKLjJmPkJOUj5OrjJSQlpKNkZCLiY+RlMGglIuUlJWSkKuTko+PlpGMiJGKjKuEj4yNiYuQoZOapoyJiY+OjI2SkJGMj5GRiYyJloCRi5GQkoqPkYqNlY6QlZCMjpCSmIuOj4eLjoqOlYuOjZKVk4yNlJSYkZuUkZSdmJyRkpGQi5WTo5qUnJeXlouSlpSXmKKtqZfNtJeTmZaYkpaRlZWPmJWampucnZ2ZmaiZkpqVnZicn5iWmaOgop6dnJufnKCnn6Koop3hvJyYnIClopieo52dnpmvqZujoJyjnbClnaWlp6SqosOmp6KnpqmquaurrqetrqyxsrKmpqizy7Gsu66nqaytsbKvsLupsbCwtK+yqrKwqq3Arq+0sKywrLK2t7izu7a5trC1u8K2vMBwvb65tb21vMC/ury1ubu2u7TLwMO7v8W/xMjCy4DHws++wsjGxsrKx8rHy8nL39Vw39bZ3N/Z1tiU59nh1uHT3d7m4uHa4uDf2PDm3d3f3uHm2d7i5Nvg7ubm6PH2/u7w7u7t4+zm5Ovp6enu3+Hn8+zq9vju7ePm4uzj7Obr8PHz+Pb2//zv8vF88Xt/73ns7u/q8vf1++/+gf77hICA//nz+PXx+/f+/vb88/f18vTt7O3rfvF6e3p4eu59fu7n7+19e3rve+jq6+309PLx8efy8vWB9O/l5+Tk8nx874GBfOfr4u3q7+t8e333+/v18e986fDw8vLn6ezp4+Tu6+l4eXngeHh1c+J3dHd4j4F8fIB+eXx9fXx8fH5/gICBfXx+gH98e/SL+/P3+39973npknx77Xt7en/x9oGCgn/493z1gH/49YD8gImEgISZhoH98Pv8/ID4+4WB/vT27+3z9PH38/T5/ID//YOF9/T28fL+9LmBgIP99/399viB+Pb3+feB+pCAgYKChoaQiIqGi4iCioqJjo2LiYeIiICGiI2EgIKOjYuLhpGCgYSBhYaKiY6UiouKjIyQjYmWh4mMi4eJiIqHjIyHj4qOj42PlJKSjo+OkJCTjJCOj46clYqQkJCOkJCNiYuMkY6PjJCOkI6RkZCXlJGQjoiPiIqMio6Pi4uNlZ6Li4uHjIyPjYiOj4qIjIiGhYiHiIKQiYCGiIaKiom8kIX+gIH/hYKIj8mMi52ChYCChv+ChISFhYCCgYP5/ID2/fn+gPn4/vnx9vv88vz0/Pn27vLz7e7v7OPq5ufn6+fa4evr+uPg5uX07Ofh4+Hn5eXW2+Le19bh09bX39vVydDV1tnPysfO0MjOxbvExMnNyce9y8XBzYC+vbu6wcGzxqfaxYq6vb24ubfDtry7t7fCyre3tMC7gL2upaupqKOnqq+lpa+pqKOorKmno6SfobCjpKiroaS+mqWdpaKdoqCalpygosipnpacmpqanb2goqChr6ehmqKYm7KOnJqgmJeds6KpuJyZnZ6bkZKalpeUmpiTjZGOoICblp2cnZebnpaaopmcoJmQlJeYn46Wm5GWmZSYnJGUkJiXlY6Nl5edlJ6Uk5admp2TlpWVkZ2arZ+Yn5mUlYWPjY2NkpqnpZXbxpiWnp6jnaSeoKCWoJmZl5WWnaCenrChmpyYoJuen5KVlp+anZmYmpedm6WuqKmtpqHqtJiUl4ChnJOZn52dn52zrJ+npJ2mobiqmKKipqWqpMaop6KnpaWntaanpqWsramusLSkpae65ta1yrirq62qr7Kxtcivs6+tr6eqpK6zrbLNtLS6trK6uL3AvL+/w7u9wbWzu760u7tuuLi7sbq2v8LBvMG7vsjEzMXZ0NPGx87GyszDyZJ9AX6IfQF+0H0Gfn1+fn1+in0Ffn19fn6VfQJ+fYV+A31+foR9BX5+fn1+jX0Bfod9Bn5+fX5+fod9g36GfQF+jn0Efn5+fYR+AX2cfgJ9foR9CX5+fX59fn5+fYR+gn2Efgp9fX59fn59fX59iH6FfQV+fX1+fo19BX59fX5+h32EfoZ9AX6FfQJ+ff9+on4EfX5+fY1+AX2JfgN9fX6EfQF+3H0Efn19fpN9AX7/ff99xX0Bfpx9AgIEAIDY1tXV1NzZ19vd2NbY1+bl4ebk4ezy7vCB9Orz+Ovi4ufe5+vr9fHw+fyA8/z48fT37e/x49rY2OHv7Ofn7uz1+fb4+/j//IGGgf////P29uvygOf4+oKBgOyB8Pnz9e7u7vHr8fmAg5f29v7+/oGCgomA/f2A/fz+/PaA+vn494D58/T06eru9Hntee3w8u/s9PX5goGB/f2BgIqCgPz4gf+EgYqGiYaA/vn4+PX27vjy9PGG+vz1gf72/vKS8fqB94KCh4eEi4+FgfmB+vL08PHygID8/Pjt+fn/+veBhoWDgYeGhICChYKCgIOEhIaJg5CJioWHhoeSh4OEi4iIhXKKiYSLipCDgICHiYSEgoKC+IaCgYKEg4KBgf/3/fj1/YD/goKDhYKB/oH9gH70fHx9gX75+YGCgICA/H9/gvb6+PTs8PB46+rp63x9fHx5gu/udex96H7q3Nna193e4W9wcnR1dHF0bXXWb3FscXJwb3aEcoCCd3d9enp5eHR0dHFzdnFyeXhzdHN0e3d9eHt6dXN1cHJuc3F0c3d3enZ1cnN1dG51c3B2dnRxcXZ4eXx4fnl2e4l3dXF5cHR4dnl5fXp7eHt8d3h5fHl5enx8foJ7fHRxdXJ+dnZyc25ycnJ3dXR3dHp5eHV3dnt3eHR3fHFqbYBxeHNzd2xwcXBvcm9ycnVyenFra2hqbHF4b25pbG9samttenzZbXNuc3Fva2tvcG15bmnRztTVz9DK1dHTbNLXbNfb29rRztPVysLFxMDAxb/Fy83IxsjBvbq5z9PHwcK+v77CxdHeu7G/uL+2vLiztbWtqq6tsaeuuq6rqay00ICxr7avqqWtrqekp6armp2hlXqgn5aao5+ho6CinpygmZeWoKGgnKGdtGiekpWdmp6Um5u/YqGqnJKUkaWbmJaalpuVoqKcnqGVl5SboJiRjpSVkZKQjo2gj5aKj42Wko2LjZKOjo+Rj4+GlZSinZePkIuTnI+Nj5OWjZCRkpuXkoCKjoyOkpKSnJiWmpOXk5SXmpqanJ+inJ6fzpmampqmnpmXnKCbqZmhn5yhn6KXmbSim6CjmpugmJiUmJ+enpuhpaCeoaiepaSanZ2iqYC0pZyanaOanKmvsrC0qq2mqqKql5KSnqCoqaetqq2sq62uqqukpqCqrKiloK6vr7W0qYCyrKSjoa24rqGopaqts6+usrK7urmtubiqrLSyrqy4r6uutLPAubq1srnArLKyrqixrauxrbW4trO2sbKssriytbq3uLm3vrq5tLK4uLmxtbazuq64wsO/xMC9vsC4vMLEzGzNycnIwcjKw8zAyc7PztLSxtPNzMzU0NHY19bv5Arf4NbU2MnW09TXgNLQzs7R1tTV2tzW0dbU3trT1tHP19jX13fd09zf2tDU1dDX2Njb2dfh4XLc5+Tc3+ni5uzl3+Lf5e7q4N/j3ufo5OTo5eXhc3hz4+fo4ezs6fCA4PLzfXt74nzq8u7v6O3p6uXo73l9qfHv8fPxeXp5fnbp7Hfu7e7z53jr7e7sgPHy9fPw7fP1eu157e7x6urv7/F7eXno6Xd3gHp47ex47Hh1fXh8enfw7e7z9fXt8+/t5Xvt8ex69Or36pbq63fmdHV2d3V6gHl37Hrs6+7q6+p8fPT18uzy8vTv6Xl8e3p4fXx7enx/ent6e3p6en54j3t9e317fId9eHd9eXl2NXh6dnx+g3p3d32CfXx+fX3we3l1d3l4eHh69vH39PT0fPJ6d3d3dXbsePB7eul2eHZ2dOjmhXaA5XNzd+Pn6unl6et26+fp6np5eHp2fujpdOp55X/y6eLn7O/w6XZ0dnd2eHR3c3nid3hze3l2dnp3dnR2g3h4fHl4d3d2eHh1eHx5eoV/eXl3dXh1d3N1dnV2d3Z5dnt5enZ4d3p5eXp1eXl2e3l4fXx6eHh6enx7eH17d32LfXtHeYF5fH59f3x/fX98fn+BhHyAfHx7ent+gXt8eHd8eYeBgn18e358e3x6eXp3fX18eHl3fHh6eXqAd3NzeHx4eXxyd3l4dneEc4ByeHVvcXFydXV9dHNtb3FvcHFyhYjdbnNucW1taWttb217cGzX1trY1dbP1dDSa87NaM3Q0M/HytHUzcvS0M3Nz8fK0tPMzc3Ix7/A1tjMxsbDxcLIyt7uvr3Dv8XDxcG4vL2ztLW5u7W8xru3uLa2zbWyt7Oxra+tp6iqp62ipYCpoHuoqZ+iqqemqKWnpKGnoJucpaShm56ctGmhlZegnqKYnZrSbqOpm5KVlKealpWYlpiToqeYmpuPkI+Tl5KQjJGTk5WSkpChlZaLkZGWlI+PkpaUlJWSkZCFko+YlI6Hi4uRnY2Lj5GSjJGQkJOSjIiOi42QkI+WkpCVkJOOkICPko+OjYyPio2Sxo2RkI+ik5CMkJSOm42TkYySlJWMjaeZk5mel5mdlZSUkpiVlJKUlZKRlJmSl5iRlJaWpIK6o5iZmZ6RkZudoJqbkpaTmZegmJWVmpmfnpqdmJmanJ+hn6Wdm5qeoZ6YkZmYmZydm6ajnpubp7WjmJ+dn6CjnnudoZ+mp6Sapqqcn6Oinp6qoZ2hpaSrqKmnoqi2paytrKitqaarp6mrq6eopKaiqa6prK+ssK+utLCysK+2s7iztLWxuK60ubu1urWxsbWwtbeztGK2trW1sLW7sruvu7++vcHFusO7u7zDvcHBwcTXzMbIxcTJv8vKytCAycXIxsrTz9DU1c7Iy8vV1tLe19jl6ejneejh6e3i293e19/h4ejk4evueOXr597g6OPl6uHb3drf6ujh4OPe5+rn6e/u8u94e3bo6evl8vHr8oDl8/V+fHvhfurz7fDq7ezt6u72fYHB8/Hz9PJ5e3h+durtdvH09frvgP37+f2A//z7+/n2+/6A+4D6+/749fn4+YB+ffPzfHuDe3v18333gXqCfH98duvr7fDx9O729Pfxg/f89Hry5/jqu+rwe+l2e319en+IgH35g/35+fPz8H99+Pbz6/Xz9O/oeHx7eXd7fHl1d3p3dnV3eHp7fnqQfoB6fHt7hXx4e4J/f36AgoN9g4OJfnt9hImEg4SCg/iBf3t7fXp2dnny7fPy8/uA/YF/f399ffZ/+4SD/oGBgYOA//2Cg4OEgv+BgIT4+/v48fj8gf78/P+EhYKEgon//oD/hPaM//Dr9PX8/v+BgYSFhIaEh4KH/4WHgYeJhYKLhoeEg5aIiY+LjIuKiIiAiIWJjYeIj4yEhISChoOIhImJiIqNio+KkZCSjZKRlJGPjIeLiYWLiYePj42Li5CPkZOQlo+PlqmUlZKck5SWlZaUlJKUkZOWlZeSlJCOjY6Pk5iUlZOQmZSgmJiSkYuTkZOWkpKTkJWUkYyPi5KMj46Sm42GiZCXkZKWiYyMi4oIjIeLi4yJkomEgoCFiJGHh4CFhoSBgYOZmv+Ah4CGhIWCg4eJhpOIgv/3//v2+/P8+/6B+vyA+vn49+/u9Pft6e3q5+br4eXt7+bm5eDd2tj0++/r6+ns6ezs8/vb1dzV2djb1NDW2s7L0NLRw8rVx8PCxMzvzcnQysnExsXAu766wrW5wbSbw8O2t4DBv8DFw8fCwMS5trfDwsG7vrnRhLqoqrCssaiwsdWcxse3r6+sv7Gtp6mnqqa0t6uvr6CjoKeqpKGcoaKhop2enaueopmdmZ6cl5WYm5uanZuYmI6gn6ihnJSZlZqmlpaepqaYnZudo6mdlZiSk5KTlJ2XlpyXm5eZmpyYmpubnoCZm5zRkZaVla6cmpWanJillJuZkpeVlo6LpZiTmJ2Wmp+Xl5SWnpybmpugnZqbn5ebnI6QkJKbebWck5WXnI+RnaWno6mhpJ6imZ6Qi42VmaKko6SfnZuZmp2ZmpaZlZyenJaSnp2fpqWfpqOem5ihsqGUnJucoqeio6SmrrCroXSttqaqr6+ooayinaKpqrSwrK+qrrqor7Crpq6poaSeoqSgn6CjpaawubGwsKysq6uzsLWxrbOytKqrramxpLC2urq/uri2vLi7vbjA17y4uLq0ur22wLK7w8PEx8e+yMDAwcnCwsfHyODRzM/Ixsy8ycTDxph9AX6RfQF+nH2Dfoh9CX59fX1+fn59fot9g36FfYV+A319foV9AX6MfQN+fX6IfQV+fn59fYV+BH19fn2Hfot9BX59fX1+hH0Ffn19fn2JfgJ9foZ9gn6JfbN+AX2JfoZ9An59hn4GfX59fn59hX6CfYV+BH1+fn6HfQF+hH2Gfgd9fX59fn1+iH2KfgF9/36xfgF9jn6KfQR+fX1+0H0Bfpd9AX6KfQF+/32ZfQF+/32VfQGAp30CAgQAgN3d1dTT2dLW1tnX3NbX4N3h3+Dh6e7x9/ny7Pfy7enn7uni6OXy8PLvgoCC/vz8gvjz5ePo5+Xf5YHu6e32+4D7goKAgIOBg4j/g4r2+vqA+f3/+P7z9oKH/fv3gPv//Pzz+fDx8fb9svnw+vj4+/yAgPyC/fv+gYOEg4aGhoGAgICCgYGA94H48/j38/H2/vP4+PuDgPuCh4H6/v7+goD+goKEl42IhoKJ+YD/9PCA+Pv4+P79goH99fqB/oaDgoOAhoSEgoKBgYL7+/X49vz8+/72hIGC/4SE9v+AhP2BgYCFg4aEi6aHiIqGiImGiYiGhoiNjo2KiY+KiY2Qi4yHgIyMi46OjYmJiIqKi4eIh4WDhIiChoWEhYSEgYSCiID/hYGHioWD/v+Ah4D5+36AhYGA/fyEhIKBhICDhICC/fz47O/x9Pl99/Pye39/gHuAe/B+7PDn5HHed9vfcXV4ent3fn13eHV3dXV1cXV4dHd4eXh5c3R4eXZ6d3h5dXh4gHZ0eIN0cHV4eXV15Xd5dnF2e3R0cnJycHx1e3V2eX17eXh1eXJyeHh1dHl5dnZ0eHWAfXx6eXh4eXR3eHZ3dXV3dnh4d3B0b3F2eYp6enyBf4d/f3h+t3h2dnRxhn9ydn19gXd2d3d5e3h1dXx3fn94fnh5bnBtbm5wcXFzb3V0gHhyc3R0b3JramxsbXBzb29wbXFrbG3Ybs5ubm5vcW93cm9qaGttbm1ubNlt09hw19jYa2rUbM1q1NXS1M3F0NLDztHPztLZ0c7Wz8zJv8bDfb3KvcG8vbnBxsK9uMO+x8S/t7fAwrm1wL+5vrerrLWisbqyq7W2srGsrqyjw8vBgKiro5yunJ6moKWrqqOhoKmcnZ+dnZygoJ6VmpiYk62WlZehkJWUlY6QnpGXlpaSlZWhmZaTk5KYkI+Wk5aWlZWbnZGal5aVlpOTlY+UPJSvnI6Rk5aak5WSlJKLkpaQjZuWmpmdmJCSn5ONjJSTjJOZoJGJkZedkJeanJiTk5SagJuSnZupnaGmo6Sgm5ympKeYnZeNmIiTkpqTlZWlnpibnKaYnK+rm6WmnqGjpailpLudn5eXnqGgmpmamZqeoaCepqCgm6KXnKGpo5+po62yramotaujnpednKHqrq2yrLOzq6esqquqrq+pqLGqraytsK+trMmzrr+xsrCxqKWpgKuvs62rs7e2tLS1sb27trC1s7PPr7Ozurm7xsC5s7S3tLOvsrGxwMG1tb27ur27u724q7m6xbeytr67wci8xrvpxMG4s7rEv7K7vcC3wMLCwsbKytXZztbOx8zJyMvJzsbEx8TGztXW1drW1Ofa1c7NzdDZ1d7m29TZ1M3T2tXYgNTTzdDO0s3V1dnW2tfa3NjZ2NXT19jc3t3Z0uDe4Njc4+Pd3t3g3t3ZcnBz4ODidunq4+Tq7uzm6H/s5Obn7HfkdXV0dHNxc3Xgcnvb4+Z05+/y7PLq7Hl97uzqeu/z8/bv9evt8PL7rvry+Pb1+fp/fvp+9e/xeXl7eXp6eXZ1gHV5enp683708PP18u709+/z8fR8ee54fXfv9PPzfn71e3l4hn14eHeA8X389O588fXw7PDzfXry6O1663x5eHh1eHh5eXt4eXvu8erv6vLy7vLsgHp89H996PR7ffB6enp9enx7gaJ7fYB7fH16fHt6enp/gIB9foOAfoGFfX12BXl6eHp9hHxcgH+Be3x8enx6f3h8e3l7e3x7fnyDffR+eXx+eHnv7nh8d+7xeHh8eXnt7Hp6eXZ4c3R1cnbo6+7n6+/y9Hrr6+p1eHd6dnp26Xzs7Ovodup55+l3eHd5enV6eXaEeIB6e3h6f3t9fnx+e3Z4enp3eXd3eHV3eXp3fIx6dXh7fHp56Xl8eXV3fXl3dXN1dn93e3Z3eX57eXx3fnh5f358eXt8eXd2eHiAfn58fHp7e3h7fnt9fHp9fH+AgHp9eX2BgIt9e3x9foR9fXd9n3t6e3t4ioN8f4GAgHt5enp8f4B8eXl8eX17dXt5enN3d3h6enx7eXV6eHlzdHV2c3VycXJzdXZ5dHR1cXRwcXLidNx0cnFvcG51cnBtam5xcnBwb99v09dx19XUa2vUbM5u2NjW2tbN19rM1dXOztDTysnSzcrJw8zIgsPOw8bBxcDHy8O/usTFysrHxMDEx767wYDAvMfEvL7FsbrEtq+4u7e1srW1r8XWza+zrKa0qKivqKywsKqnpq2no6OioqCkpKOZnZudnLWfn6C1nKGgoZucpJuenZuUmpuooJqampmdk5GZlZSSj46TkYeQjY2NjpCQlpCYRJqomY6Tk5SalZSTlZWQkZaPipSMj4yQjYWJloCMiYqRlIuRj5SMiZCXmpGVl5aVjI6PlpePlZKejo2QipGPi46UkZaIko+KmouVk56TkpCclJCPkJqNkaCilJiYj5aVl5qYnbKWl5GRlpmYlpWUk5SZmJWWm5mWl5qSlpyilZKakpialpSWoZyYmZmcnJrXnpmdmJ6YnJygnp6aoYCfnJykoJ6bnaCgnp3Co6K9qqqoqaCgoaGhop6doqekoqOkoquspaGlo6O9oaKfp6Olq6iioqSnpqWnqKintbWsqLCpqKqopqimm6qtu7GrrLGvtbestrHVwbq3s7a8ta+1uLyzubq2tbm4s7e3s767tri3tLq3wLu+vLu/xMG/vhfCwL/OxcTAw7/BysfO1czHzcrHzNHN04DIxsLAwcjEzM3Qz9TP0NXW2dzZ2+Xp6+vm5Nrl39zZ2eLf2t3c4+Di4Hd0eOfm6Hns6uLi5+no4ul/7+rs8PN88X18e3t8d3h97XmC6Ozxe/P19O3w6et7gvXy7Hru8O7t5/Hr7vL3/6746+7o4+jpdXbteevl7Hd4enh8fXx6e4B7fn9+f/eB9fL3+PXx9v7z+Pn7gH3vfIB57/P19n59935/fZSEgH98hPV+/fPvfvb49fLx9YGA+PDzfu9/fHx+en19fXx7eX197/j3+/T7/fX27YB8fvqCgvL8fn/0fHt6fXl7eX6jd3l6d3d4d3h4eHd5f39/e3qBf36DiYKDf4CCg4CDhYOCgYCDgoJ/gH58fHyBen9/fX99fHl9e4N994B8gYV+fvf3fYR++/6BgoeFf/n4gH5/f4F9gIJ+gfv6+u/09vr+gPr7+4CEg4aBhoD7iP3+/PmA+IP8/oSGhouKhImIhYaGhYWHiYWJjYuOjo2MjYaGjIuJioaJiIeJi4CNi4+uiYSHi4qFhP+GioeEh4+Li4qJiouWj5WNjJCVk5OTi5KJiY+PjoyOjo2MjY6OmpeXk5OSlJSSlZiXlZOQk5GSkpOMkIyPlZSmko+RlpSdlpiRm7yVlpWUkaWcipKbm6CWl5iXmJmTjo+TjpWTjJSOkIeNjY+PkJOUk46SkYCSi4uNjImMh4WEhYaIioaIiIOHgICB/oT5hIOEhIeFjYqGg4CEhYWDg4H/gfb5gvn2/oGA/ID2gfr39fnw5O/04+rq5ufs7+bm8Ofj39jg35zc7eDn4ebg6u/j4Nrl4ubn4d7Z3+Xc2uHe2+jg0c3ZwszWycLQ0s/QzNLOw+Xi2IDDycK918DAx7/CzMzHxcLMwr/CwMG9wsG/tbiztLDJsbGyx6uwr7Goqbars7O0r7OxwLWwrKmpr6ahqaeqp6OhqKmbpaKhoJ+fnJ+cqIKhraKYnJmdopubmp+elpyhmZOcl5yeoqCVl6SbmJefpZqhorKhmqGiopWbmJqXjpGPm4Cdl5+bqZydop+jn5qboJ6gkp6YkJ+NmZWglZiWoZyZmpiglZmmpJOamZCTl5uenqCnnJ2WlJuenpqVlpORmZWSkZmVlJWbkpadpZmXnZmho6CcoaylnZqWmZeT1aOgopuhnJiUmJiamp+bmp6moaOgpKekpKHXop+4o6Ogn5iXnSCcm6CbmqKmpKOjpJ+rsKefqamqxqOno62srrWyqaipp4SkXKOjsraopqynqKiopqyrorG2xravsLCusbatt7HMtrGppKmysKmxuLuyu7q5uL68tbm6scLBu8LBvsG7xb3BwLzAxsfGw8XBv87Ewr+9vcLNytHVy8bLxLzCx8HCqX0Hfn5+fX19fol9AX6FfQJ+fYh+B31+fn19fX6HfQZ+fn19fX6LfQF+h30Hfn59fn19fY5+An1+jH0Gfn59fn5+hH0Dfn59iX4GfX59fX1+hn0Hfn59fX1+fY1+in0Lfn5+fX5+fX1+fn3BfgF9hn4HfX1+fn59fYV+gn2Kfoh9BH59fX2HfgJ9foR9BX59fn19r34Bff9+i34DfX59kX4OfX59fX59fX1+fn1+fX6YfQF+/H0BgP99/320fQICBACA3Nra19bl5eDj5Obq7uvx7Onr5uWA7+nx9O/4mvX07/Dw7u/t8vz2+vv/hf2E+/f8lfHs59ba6+H87fr09P39+YD7/YGA/oiGi5KDhIP7gIL194CAhYODgY2A//mE/4P8+/77gPL6//628fDz9/6ChYKEgYOCgoCHg4WFiYiDgIWAhoSHiYOGg4n09f34/fr7+X+Ah4qEgIOHgYH5+v38g/6BgvXwhoWH+f6BgIKA/fn3+4CFgoWHhoiDhYeGh4SDgoOEgYH+hIP+/4GBgP2A/YGBi/+AgoKC//76gPz6goWGh4qJiIWOk4qLj4mRj4qJjY2KjI+MiouMioiLjIeOkI6Aj4mOjJGOjJCIjYiHiZGKjYiIjoqGi4+RiISBhoSCgoSQgIOChoiJhoODg4WEhoN/g4eBgYOJhoiGhoWKgYCB/Pv6+YH6/P79//h/f3+DfXh9fX3te+vodnR2dXFyeXp5eYOAgIOAf319fnp6fHl4fHV5cnV3fn57e3d6dZB8dniAenV0cXJ9dXl5c3l2eXp5f359d3l1dnRye6t5ent6fXt7dHR/cHV0eHp3d3l3eXh6d4B8foB9e3h6gIJ8cXSHdX53cXl7dHVyfIR8enZ2e3l7f3uDeX15en2PeHhzd3p5eXh2eKF5enx+e3l0eYGCfXp6e3p4eXZybnJxcHJycHGAcHZ2dHN5eG90ampsbnt2cG9uc3VvcXhxeHBzdHFvb3TTbm9xbHVvcHBubG1tbtjT1NBry2ZnycfIxNHS1NHY3NTb1dbY2t/c1NLQ1tnJxMvDxHzAwL3CyMi+ycHEyszKxsvFw8jExL21tbi1t7m4r6rCbLq5urG4t7Ozs62uqaiAsaqqoKGfoJ2apaK2rKKmpaWdmZeamJqanJuemJ6dnZyTmZmTl5WZmZyekqGiopicupCLlJOelJeQkIuTlpmWm5mXk5WTjI+YjpGNh4qOkaaYko6UlZORjZWSjpuYk5qalquikpiklJGRkZKRkpSUk5mboLCdlpaKkp+Zlo6alpeAlZ6lqKalp6KjnKqZl5udnJebk5CakJWNlpKXoqGmoqGYoaOqpqOmnqWupp+moaSioJ7DqKWjtKqho6Cno6SmrKiqpqGZn5uen6SjpamusLK6tKzJp6CfnqShpK6u5rKx36imqKy0rq60q7Gmsq2usq2sq6m5s7CxqaalsLCzsrGAua6tq6mwtLK2usCuy7y8sby+wLu6u7S2uLXFv7q4ubu7sre4sLTB1ry9tr2/v8C/wb68vLe4u7e3vbrAxsTGyce/ycTDvbq6u9Liwse+w8LBys3P0NPR0s/RzMHL0MjSyc7V8tfU1tTU0dbf193U4Nnd2+He5uLl297a2Nfa1tuA1NLTzs3a19PV1dXa3N/j4ODf3Nx13Njd39vkjObm4OHi4uPc4ebh4OHhdeF14N7mh+jn5+Lh7+P15e7j5urx7Hjv8Xt35nZ0eH5yc3TgdXjm6Xd2end3doV27ex+9n/29vr1ffL8/v+2+vb19/2Bgn+AfHt6enh8eXt6e3p4dnk8e3p+gH2AgIXz8/z6+vfy8np5fYB8eHp/e33z9PX5gfl8fO/pfnyA7fF6fIB++vfy9nx8en18eXt5ent5hHo3fH16e/J8euzuent883z5fHyK8np4enr09fJ78vB6e3x7e3l3dX+Een2CfYSCfHt/gHx+gYB/f4SAgIJ+g4eBgHp8fIF9fX55fHl5e4B7fn18gYB+hIaIgX57gIB+fn6IeXl3eXt7enp6e3x5enl5fH15eHl9ent6eHd8dXd47/L18n32+Pjz9+55d3Z9dnJ2eHjpeOjreHp6eXV4fHl2dH14d3l4eXd5e3p6e3p6fXd6dnV2e3x6eHV6gHaIe3R5fHd7eXuGfH99eXl3enx6fn19e315enl3e6J5fXp5fHl9eXqFd3t6fX15enx7e3t8eYB8fX97fHt9hImCd3qNe4J+eICEfX97h5CDgnx+fnx+gYCFe4B8foGgf395fYGBfn16fKJ7e3+BgX55fYWEfnp4eXd2eHh3d319gHx7e3h4dXl5dnZ8fXd7c3N0eIF6dXRzd3lydHxyeXJ1d3V0cnbXb3Buc3RucG9wcG5ubtjW1dZu125v2dzb1dze3NfY3dTYz87R0NPSzczL0tjSy9LM0YDIycXLzs7FzcTEx83GxMnGxMXCw7+1t7q6u7y8t7PJdr+7vLO0sLO1gLSvsKmqs7CuqKmnqaaiq6a/taSnqKijnp2fn56hoqKmoqShn6Kco5+anp6fn6SelJ6goJWbtZCQlZWgmJiSj4yVkpSQlI+Mio+Oio6ZlJaVjo6Qk6yYj46TmJaVlZiVkJuXkZKUi5yWiY+ZjI2Pj4+NjY+Oj5SUmaiWjpCIlJ6YgJaOl5SUj5aWl5CQko+Pi5mLkJOVmJOVjYqVj5OOlo+SmZialpOMlZicmZeYkpeemJKZlZWUkpG1l5SVopqWmJmdnJ+eoqCgnJeTl5KXmZ6YlpmZmZebnZuzoZyho6SioKeiz6ChzpuZm5yhmpqpm6OYoZ6eoaKenZynp6iro6WigKqopaakqaGgoKClp6OmqK2a2rWto6qoqqeop6KmqKSvrKipqaurpq6tqKu1xLOzra+wsK+rraytrqqxtbCzt7K2tbGytbCus7i4tLa2uMvWvb+5urm1t7i2u7q2tbi6uba9vrnCuLq73sLAwsHEwsnOxMjAycXFxs7L0MzQy8/PBdDQ08/UgMzLy8bI19bT1tfV2NvY1dHP2NXSeenk6OTi6pPk4d3d3tzf3OPr4+fm6nnleOPi6YTq6urc3Ong+OTu6Ovz9fR9+/qCfveCfoB9enx88nx+6+l3d316eHd+d/LrffJ97uvw7Xnr9fn7t/Xv7e3zfH57e3h6eXp5fnx9fYB+e3h8dH59gIJ+gX+D8fL9+P39/PuAgYeKhH2BhH9/9vn5/IP7f4Dw54CAg/L5foCEf/748vd9fnp/gH18eH1/fX9/fn+AgH599n189/h/gIL/gPt9fIfye3p9fvr8+H3183t9fn5+fHt5gIR7foF8gYJ+e3+BfH6BhH2Af36Ag4GFiISEfoCAhYKChYCHhIKDhnx9eHh9fX2Dh4mBfn2CgH59gJV7fX19fn9+fX1/goGFgoGChH9+f4GAgYF/f4V5enrx8/HwfPP39vj99YGBgYqDgIaHh/+E//yCgYOFgYSLiImGkIqJjYyMi42Qjo6Rjo6QiY2HiYmPkIyAjIaMiJqPiYyQjI+LjKuNj4yGiISIiIiPjo+OkYyNjIqRs5CTk5KVkZGQkp+Mj42Rko+OkZGRkpOPmJSWmZaVlZefqpqNkKaRmpaMlZmRko2aoJeUjpCTkZSXlp6SmpWZn9GYmZGZm5qYmJWX35mZnZ2cmZKXoqCWkY6RjouPjosyi5KSk5GRjpCMkZGOjZOSiY+FhoeJlY6JiYeMj4aIj4SKg4mMioiGjP+DhITLiYKFg4OEgID/+/7/hPuAgfz8+PL6/vnx9Pny9Ozu8Pb39e/t6fLy6OHp4uyU4ubg6fDw6PPq7vH07uzu6eTm5OLc0NPa2Nvd3NTO5IXR0tLIzs7R09HKycHCzcrKwsXCwr68xbzqz7/CxMS/uLe6ubq9vb2/t7u5t7iutLGop6Sopq6so7W2uYCuttampK2qt6qrpKGbp6irp66opaCmo5ygqqGjnpeYmZy6o5qVmJqXlZOWlpKinpicm5KmnZCYqJybn5ydmJaZmp2jo6a1npWVipOdlpGJlZKUkp+kqKOipaOioKubnJ2enpiclJCbkpeOlY6RmZabmJeOlpqfmpaYkZegmJKaloCXlZSWx52ZmKiemJmYnZicnKOgop+amJyYnZ6hnJmdoqOhpqWjyKWdm52gnJijotqgn8yZmJyan5mZppmimaSeoqmrpaSjsKulpJual5+gnqGgpp2cm5ifoZ2ipqyZ9cCypa6rrKmmp6Kmramzr6eqqainn6inn6OvvKyspqepqlKtra6tsbOwsre0tLeztri4ub+4t7q7tKyoqKayv6+1s7S3sLW0sre9u76/wr61v8K6x7y/xNjHwsC+v7q8w7rAu8XDxsfPy9DKzsTGxMXGycXJlH0BfoZ9AX6OfQd+fX59fX1+j30Gfn19fn59h34FfX5+fX2IfgV9fX59foR9AX6EfQF+hX2afoh9in6EfQt+fX5+fX1+fn59fYR+hH2Tfg99fn59fX5+fn1+fX5+fn2EfgZ9fX1+fX3ifoR9AX6GfYl+BH1+fX3/fsp+BX1+fn5/iX6EfQR+fX5+nH0Bfp99AX7/ff99/32QfQICBACA393g3efq6ujv6e3n6Onr9PuZ9/T98/f47vD29vD49/Ts7fn4goD+l4OEhYKCgPj9gfT1gu/s4PL0+Pjy7/r4+v2Bg4X9hIeIhImAgYCBiIWEhYKEhoaFg4OIgICEg4qEgoCAg4aAg4P+hoGA//uAhISCgYP+g4eIjoWDiYiDiYeAg4eLh4SGiYiHiIWHh4CW/oGGhYOEhoaHg4T8f/v8/YGBhoSDhouIg4OBgPp/9Ht9goeGhomJhYaGhYeJiIuFiYb9hIiIiIqMhoaDhoSG+4OFg4OChIGB/vv++PaAi4iHh4qNj5GTkI6HiYmPjJKSjY+QkIuMjIyIhYaKiIKJh45Djo2PjY6Rj46Sko+OjI+TkpKPlI2JiY+OhoWBgojzhon8gYWFh4aGjIiBhYOBgoiKhoT+gYKKi4mKhIiDhYWEhoKBg4WBgIODgIKDh4GBl4SDhIGB83l+e3t8e3d8fn18h3+FfoCEf316fIJ/f4GBf4N9e3l6e4F+fHp6fH58e35+d3h3dHJ4c3lxeIt7e396eYx9d3uWe3t2eHyEeXp3e31/fH97enZ8fnt6e3t+f39/gVF9d3p9fn18fHt8fnp0eHqJg3d7gIF8f3+AhIB+f4iAfoN4goGAent/fnx2f3d8eXp8gnp/fYSAd3qIfHx8eYCPfn+Bf3p5dHF2dXN7fHR4eXR3dHBwbXRzcH14c3F0d4CBdXJtdWlsbG5yboZycHVxbm5wcHJyb29zb3Ju0mlraGfLy7/Ky89yas/Y1dja19fY29h2gM/V1tfNz8/HyMdxwtHDxYLN0M7KzcvBzsTGv8LAu7W/wr64s7S4ubCxrMK6ubK1sbSvwLK1rbKwp6elpKCZnqynqp+mpqWmsJ+gmKOeoKWenZ2ZnJSTnJWWoJmYlIqfmpSbl6Oim5yjmZuZoJual5mXmJqWkZyYnaKcoJyfkJeVgI6Oj7SOkpyTmJidtqmcm5KQlI6QjI6YmJadl5WRkJKTlZSSlJiXmpSdpauemJaUlJWhp6OPkZyhp6aoraiepZqfnJ+glaqjoaGfoKKcnZqano2SnJSbn5yen6aqpqSjo6CmoZ6qpq2op6ettaOmp6icoqStqp2moKijoqWdoqaogK+1saqpq7GppaWqorKoo6OonqOrqa2prKafnKqwqq+xp660oauwsKmpsKe3samoq7KqtLewsbmwv7Czsam3t7Syr7Gwr6uts7y8uce+v7i1tbTBure3sbe+zrS0uLu7uLm9u73AxLzCvrzIv7q+wcW+wMLFwbzMx9TBxMzGwsDAOMLJzs/SycfBztTQ2d7c1s3Uzs3NztDQy9TU2NDa3tHW293a3+Lj94Pc1dTc3OTj5eTi4uDi6+TiVM/P0tDY2tjV3NbX1NTX2uPqm+bh6uDj4tna4eLi5eTf2t3h3XRy5JV0dnVzdHHc53rq7X7x7+bz8PTw7env7u7ueXyA73t4eHV7dHd1eH16eHl2eIR5gHt/eHh+fIJ+e3x7foF8f372hYKA//qAhISCfn71fn99gnx6fn16gX56f4N/fX+DgoKDgYKAe43xeXx8eXt7e319f/R79fj3fn1/f3t9g4B9fX5/+n70e3t8f3x9fX97fHx5eXh7f3yBf/B7fnp6en14e3h9fH/1f399fn1+fX37gPr69vV9hX58enl7e36AfX14enx/fH+AfH1+f3x+gIGAfoGDgXyBf4ODgIF+foGBgIOAfnx7fICAgX+FgH1/hIR/f35/hvOFh/d9f3t7eXh8e3d+e3p4eXx7e+15eICAfn53enZ4eXh5d3d5eHh5eHd5enZ3eH16eYh9e318e+15cYB8fXx8d3p5eXeAeX16fH97enh6f319fn15fXh4dnh6fHt6eXh6enl5e316fn19enx4fHd7j318gH19iH95f5l+fHp6fIJ5eXh8fIB9gX58e4GDf3x+fX5/foCDYH55fH5/fn9+f3+CgHyAg5aOgIGGhICAhX98goyFhId/goSCfX+DgX56hXl/fH5/hHyBf4OBeHyJfn99eHyGe31/fnp8enh7fHt/fnh7fXd5eHZ3dn59eIF8dnV4eoOKe3hze3N1dHR3c4d1c3dybnBxcnFwbWxzbXFw2G5vb3Lg4Nri4eR8ctre2NfW0NDS0M5wyM7P0c6A09HM0tN0xdrHyILKzsjGxsS8xrzBwcTCwr7JycK8ubq9vba1sdS8uba4trSwv6+xrbKxrbOysqukprGrraSrqamquKGgmaCcnqGfnp+fop2ZpZ2fpKGhnpmqoqOfm5+dmJWalZeZnpucm5iWlZqSi5OPkJWPk5KUipGRjpKVsZGAmaKYmJiZvaecmpSUmpSVko+XlJKUkIqLi5CSk5GOjJGOlI+WmJ+ZlZKMjo+aoKOKjJOSlJKSlZGIjoqQj5aYj56WlJSQlJiTl5aYnI2RmZKXmJSVlpufmpmYl5KWkpCal5uZlpmcpJebm52Vm52lpJefmaGZlpiSkZWXnaOempuAm6GamJqim6ujoaSnnaCmnqCdnpqWkp+hoKSkm5+klqCpopmaopyup6OkpamiqKmhoaujrKGjop2pqqinqayrrKqrqK2rqtSurqqnqqevq6uvqq262Lewtbe3tLK1sLCysquwrqy5sK+ws7aytLW6s6+9usO1u8S9t7a1tbm+vb8zt7iyuLu4vMDCvru+vL28vrvAu8XEyMLJzMHDw8PCysrM43TIwcDJytLS0tHQ0tDS2dHRgNDO0c3X3NvY49vb1dbW1dzhjeLh7+jr6uDk6enh6Ofg2tjj4HZ05K53d3ZydHHg6Xrn7n/u6eDy8fLt7erx7vT2fX+E+4J+fnt9en18fYKAfXt5e3x8fHt8f3d1enqAfHh3dnl8eHt8739+fPTven9+e3l773l+foN7en59eYJ/gHp/gn59fYB/gIF+gYJ8lfV/hISBhISFh4WF/oD5/vqAgIODf4GJg39/gID9gP2AgIKFgX5/gHx9fn+AgH+EgYWC8H6Bf36AhoGCfoSBg/Z/f3t9e358ffz5/Pj3gIeCgH19fX6AgHx8dnp8gX+Fg4KCg4OAgYKCfn1/goB7fn2CgIJ/f36AgYOCiIWEgn+AgoGCgIN/fH6Fhn9+e3yE7YWK/IKDgIF9fIKAeYKBgoGFioaC9n57gYKDhX6AfH9/fX58e358fH9+f4KCgYOGjIaGm4iHhoSE+YCGhYiJjIeMjoyHkomPioyPjIuKjJKQkZKQjI+KiYeKjJGNjIuKjIyKgIuOkIyQj4+KjYmOhoqxjY6Tj4+mk4ySvpKRjY6Rmo+RkZWUmJWYkpCOk5WRj5CRlJaXmJyhmJGUl5mYmJeWl5qXkpWXsaOSkp+TlJSUm5eWnamhn6SZnZ+dlpmdnJqVopWbmZqdpZuin6Sil5qnmZmYkZekk5WXmJOUk5GWlJKWgJWNkZSNkI2JiIWMioqXkY2NkJGdrpuLhoyDh4eHioafi4iLhoSFiIiHhoKAiIGGg/mAg4KD///1/vv/jYD1/fj7+vT3+fv6iPP49fju8Ozj4+uA3vzk66Dx+PTy9vTo9Ofp5+bi3dfj39vV09fZ2NDUzfjX1s/MycvJ18zQycvMgMnO0NDKvcHNxMq9xsPBwtS6urPAurzAura4tbmxrreuq7CqqaWcsamrqaqxsq6ssqutrLSxsa6urKiup6CnoqSnoaOhopefoJudobyeoaqanpuc2KyjpZ+fp6GjnZqloaGim5WRkJeXnZ2bl5yZoZmio6ujoJySlZKcoKOKj52ggKGcoqekm6KcpaWnpZqpn5uZl52hmp+bnZ6Ki5GLjpGOkpSdpJ+enJqWmZWPm5idnJaanaOUmZqelZydp6OVnZablpecl5mdoKWmoJybnaKenp+mobCooaWqnZ+nnqOfoZuUj5uenaKmmZ2ilaKuqaOnqqGupJ6bnJ6Zo6WenKaagKaXm5qTn6Cgo6OmpKiqrK2wqaayqqyno6ekqqajqKOnr9Czp6yvr66ssK2usbOts6+tubKusLS5tbi6vru3xMDUuL3Fv7i2tre4u7q8s7Sut7u3wMbEv7i9ubm5uLi8ucO/xL7Ew7i8vry7w8fH233Kw8LKyMvJycLDxsfN19DRkX0BfpJ9A35+fYd+Bn19fn19fo19BH5+fn2jfgZ9fn5+fX2GfgF9mn4BfYp+BX1+fX19jH4DfX59k34BfYx+AX2IfoV9wX4EfX5+fZF+AX2jfgF93X4BgPt+AX2EfoZ9gn6KfQF+in0BfoR9AX7/ff99/32afQF+kH0CAgQAgODe6N7i5urt7fDo6vXy+/fu9vH79/37gYOCgoGA/vjy8+zv8PuEiIWGiYeEhP6AgIL9/YH8gPfw9ID8+oGB+/n9/4CBgIuIhoSGiIaDhY2JgJ6FhIGqh4iHhPyHjYiIioqIjIaJiYiLhoOHg4KIiYeKi4WFh4mDhIKBhIKMjKCQgIuJkY+MiYmMiIiJh4iBhoaJ/4KOh4aJh4iIhYWCgY+IiIyMioeIgoaGhYSFh4SBgoSDhYaIiIiKi42MiouKhoiMiouJjIqJjJGIh4uDg4WEhP+CgYWEiISEhIODgYyNjZCRk5SUkZKSjqeSlI6Ojo+VmZKPkZKPjo+NiIqHi46PgJCOkZCPkpGOjpSTjI2ekZOZj5ePkoyPkYyNjIuGg4eGgYCEgYSJiIuMiIqH/oKEjYWHjIeIipeSh4aIhIeIhoiHhoOE/4iJh4eHhoqIhISEhXx6e4R9fnx7f359fYWFh4aAgYCAfn16enx+f4OEg4GFiYGQfXx3fICBf319fX97gHl3eHl7gXp5d3t7eXl6e3Z/gH17eHt7eHl5eXp4fXeElYJ+fnt6e3p8fn57fH57en9+gX9/gnh9f31+gXp9e3p8e3p7eId7fH56gIGAgIGBfoJ/g356eHx/iXt9fHt8g358fH58gH19fn1+eX2Ef4GBg36dgYKDf395fHt7d3l8gIB3eHZ1eHd1dXN2b3F1dnlzdnJxn2BwcW9zcXJsc2xva3NwbnFrcWtwdm9vcnRxbG9tbG9wamtqa3/WbNHi4dTSduTc2tPXdW/N09PTy9LLb9DOxcS7vMTKx8XHxcjFyMXJxsJugLyHvLu7t7i5vbu1sLO5uba7r6uzsK+0r6usgK6uq7GiuKejpaadqKh8ssCppKimqaWio5+il5mUlaCcmZmVl5GMlpqXnZGdmJydmJ6bmJycmZ6cnaKbn6CvoaikoaCamZiXlJOblo6SmqCUko+Sk5SYm5OUlYiRk46SlZOZl5mRk5KGkI6NmY+Wk5WXnaGhmp6aoaCirZuaoKKhgKWepKKnnKmYmJaalJmVlpuZkpKYlpWbn5iZnZ+bn6WcnpyfqqifqbCxqaOdoLGppbCtoKWkpaSloqGypZikpqqvr7Kjnqesq7Gurau8tK7srq6mrKq0qaaoqqyrpaKqra+roZuoq6uspr+4ure8p6iiq7Cwqrayt7W1t7W8vrq2gK2xr7Cxq62wtLGyubnCyNK+xcG7vb+7u7Sytbm+vLrTwdHBwcXAv8G/wby/vsi2xMjLxMfBycTIx8jNxMzHmsfIzMDKubi4w8PPxtrTy9TW0dDW3dzp3dLQ1OHRzM/RzdHL09nP0tTU2Njf6ODX1uHY1dXb4t/d3eDW4uji6ObfgNPO2NDU19rd3N7W2OLf6uni9OHo3uHcb29ucHBz5ufm5+Tl4el2eXV2eXh3duVydHXn6Hfxe/Ps8n328nl68vHz8nh4dHx8eXl5e3p3e4J+d5B8e3icfX5+eut9gn17fX59gHp/gH+Dfn2Af3yAg3+Bgnx8f4F8fHx7fXuBgZCBgH59hIOCf4CCgICCgYB8gYKE8nyFfnp+foGBfn58e4N/gIGDf35/e4CBgIGAgX59foB9gH+Afn5+gYB+fH9+fH+Bf4B8fX17fYN9foJ9f4KBgfuAf4CAg4CAfn17eYF+fX99fn6AfoGCgJqFhX9+fnyBhH59foCAgIODfoJ/g4aFgIWBhIOBhoOBgIODfH2JfoKJfIN9f3l8gX2AgYOCgImHgH1/ent9e3x9e3987Hh6gnt7gHx8f5GKf3x9eHt6eXp8fHp67X5/enh5d3t7d3p7fHd1d4F7fn1+gH17eoB+fnx6e3t8fYB6fH19fH5+fHl9gXuTeXl2e35+fXx7e357gHl3eXp/hYGAfYB8fHt9fnmCg397eH1/fYB/gYB+gXyHk4N/gH9+f35/goOAgoOBgISAgoCBgnl+gH2AhH+BgYKGhoSGhZuFhYaDhIaFhYSDg4aFiIOBfoOEkYOEgn6BioJ+f4SBhIKDgX9+e3+Ef4CAgXyRgIGBfn98gHx9e3x9gH92eXh4fH17e3Z6dnd6e353e3d5q3l1dXR3dXZze3V1c3t3c3Juc3BzeHNzdXRxbnBxcnd6dnZ0d43mddvn59bPct3X183Sdm/O09PX0djUeeDd0c/Hw8nOy8jJyMnJysTGxcJvhcF+wsHDvsHBxMO/uLa5tLS4sbS/ubW4tLGygLS3trmrt66rq6mgqal6r7SnoKShoaKapJ+poJ+gnaukpaWgo5yeoaOjopidl5yZk5mXlpqZmJyZmZiSk4+fjpWTkZKQkZKRjY+UkImPmJyUk46SkpOWm5SYmYuSko6UkY6Sk5WQkpOOlpaTmoyRj5CUl5iUkI+MkZGWoo+Oj4+NgJCMkpCSjZuPkpGXlJmVk5aVjpGXk5SYnJWTlpiWmZ+XmJWWnpqOl5yclpCOk6CanKajlZucnZycnp2snpKamZqgnZ6WlJmdnaehnJ2tpqHqo6Cao6KsoqCioqOknZyjo6Sjm5agoKCfmrCnq6a7oaCYoaOinaemq6uprLCvr6+tgKaop6aopaOlp6eqrq66u7mrs7GusLKvsaqnp6mtq6m2rsm0s7eztre1u7O0srims7S1tLezubW6uLa4sbayi7a2vbfEubi2vLm8tcG7t8DHv77AxcPRxcPAxc/CwcLDv8W9xsjExcfFxsjK1M3GxM7Fw8DK0tDPzdDG09zW3NjTgM/J08nP09fZ3tzU0dnW29jR5Njg3+XicnJycXBw4eHc39ja2uF0dXR0d3Zzc91wcnTm7nrxeezo63jt8Hx89PX7+Ht6d4B+e3x6fX17fYSBepF8eneVfH1+e/B/hX98f318gXp+f32Be3l+fXp+gH6Ag319gIR+fXx6fnmBgJWDgH19hYWEgICDgYGCgYJ+hIOK+YGMhYKGhIeGg4KAfoeCgYKFgH+Ae3+AgYKChIGAgYOAgoKDgoB/gYCAgYOCf4CCgYKAgH5+gYZ/foR+f4F/f/Z/f4B/hIGBfn58eoOBgYKAgYCCfn+BgJyHh4KAgYGGjISChYeFg4aEfoB6gIOBgIJ+gIF/hIOBg4eJhIKHgYSIfYR+gXyBhoSFhoWCgIeGgH+Eg4WHhISEgIGA83+DjoSDhoSDhI+Lg4B/fIGBfYCCgX6A+YaIhYWHhoiJhoeGioKCg42IiouMj4+MjJKQko6JiIeKi4yHiY2QjpGTko+RlY6pi4qGjZKTkpGRkZKQgIyKjI+Um5WTkJSSkY6PkIqSlJOQjpWVkZOTk5GQlo6aqZqXl5WTlpeZnZ2am5qVlZqXmZmbnZKXmZaYm5WXlpaZmZaXlLiTk5OPk5WWlpmanKCgpJ6cl5udtZqbmZeapZ2YnKCdop+gn56emZyinJ2dnJfCmpubmJmWnpqdmJucgJySkpGPkpKQkIqPiIuPkZSNkYyO3sOIiYiKhomCi4OGg42KiIqEiIOFi4SDhoeEgIKDg4eIg4KBgqz+gvP//evmgfz3+PT8jYT3+vr36vDlgvDq5ebd4er18vP19Pbx8uzu6eCAoNaH1dTU0NTU4OPf1dDW09LQxsfVysbOzM7RgNbZ1dvL1s7Hx8W4w8Ka0NfGvcO+wcK5vrjCtrKvrbawrq6nqqGfqKuvsKayq7KxqbKuq6+uq66qqq6kqaa1pq2npKWfnpudmZuenZWdpKufnJaWl5SXn5eeoZWfoJ2moZ2fn6GampqQmpuaopedmZuhpammoKKZn5yhrp2Ym5ubgKSeo6Kmoa+doqCinJ6bm56dlpedl5WYnpWSlpiXmaCbm5qboaGVnaGgmpKNkqKbm6OilZiZmZ2enpurn5Gbm5umoqmcmp+hoqijoJ+xqqH5pKKbp6KtoKCkoaSknZuio6KhmpqlpaKgl6ymrqnBo6afo6KelpyYn52coaClpqaigJ2dn6Cinp2go6Okpqm4ur6tsa2qra+sqqOjoqaqqqm6rMSurLCprLCss6utrrWkr6+xrbCrsq61tre/usPAdMG+xbjCsK2qs7G4sMXAu8THvr7CxMTSw7i3u8m+urq9uLy1wcjCxcPDxcLEx8S8usW9vbzEy8bEwse8zNXT2dTPl32Gfoh9iH4RfX5+fn19fn1+fX19fn19fn6EfZh+AX22fgF9v34Bfdp+AX2XfgF9/37RfgF/pH4CfX6FfQF+hX2Cfod9AX6TfQR+fn1+pX0Bfv99/32yfQF+wX0CAgQAgOPj5Ofl9vL19Pj//4KBif6BgPn2+ff6gfqF/f75gf34gfuAgIeJhYeFhoOD/IOGgv/+g4H/gP//gID6gff7/4D7gYGChoeCg4qGjYmWk4mMi4eIiISJioiJi5SLioiJiIeIh4iKiYiMiYaaioWKlImNiomJiIaEhYiGiISIiIWLgJCLjIqJiIeLh5SLhIWDg4SIhYWJhYOMn4iHhoiFhYSHjZyJkZCNiYiKhouKiYyKiIeDi4SJh4yMjIqMjIyQjomLioqLj5KNjJCSjY6Kgo6LhouHhI2OkoqTi4uLh5OHlo+UkJiYk5KRkpGTlJKWlpmWoJmUmJOUko2Qi4mLioeKgImIi5aSkpKUkpiYmZyVk4+Wl5KQkpWSkY2Ojo2NmpGEioSFjIONkY2KiY6OjYeWjYeFiISEiIyIh4aHiIiGkYeJgvuD9X+Bh31/ho+DgoWHhYSFg4KFg36Ag35+gIODhYWFg4KHf4B6eXl5fIaFfoCGhIWFh4GFg36Af3t9fnlxgH1+enmFh4F9gH1/f3+Agnx+fop+e3x7gIGAfHp3fHp+h4B8gH5+fHSBfZB+gH97f3x/gIOXg36BfYGDe4B7f354e397eoJ5fH59fYODhYWDf36Af3l5fX2Bhnl8f4F+fX97fYB+goOGf3x7eHx+fX57gYGDgX98gH93enx5enh3gH+Ffnx7fnh7dXJyb3V2eXR1c4R0dnd3eHd0bW9ubG1wcHN0cXJwcHZ3d3V3cXRyb3Btb2vQampqaczQ0m/XcdVv1G/a3Ntx09ba1dvVy8zNz8zOysnFyMvP0srO09bQw7fAw7q+tru4vMXKsrrZwoHNwby+vr3CvcXMtq6upauzgLSwusTAuqispqWYmpqnp62loKKwpqacnKKYnKCWnp6fmpiZkZOKmKiWnqSgmpycl5+bn62hoqCdmpqQmaGen52ioKSdnJ+impbUlpWYl5CSjZuSkpWajpSSlJGokpSNk5SSoJuPkJjGpqKUlJKRlZCZn56hoayYn5qnrbGjnaaegJ2lm56gnqSfm6uxlpaco6OhYqKglJWanaChoaefpa6pnp+hnZ6emqObpZ6hrqOnsqypop+ip62nnp6tnrGyqKaktbquwa+utLS0rbSxya3AsrCttqilq6Wnoqywp6qwrK/CrqSvrrSvtsK3uLSsrqursbm0trSzsLWvrbm3tbK2gLW5u6yzsbCzsrazubK5vbvBwL+9t7zAurGxsr+3u7y4w8HDwsW8v8DHwMSGzdDX0snLzdfJy8zFy83GydPNzMvUzs7NxcfR0sTFx9nMxcbT0drS3N3t4tzT19vo4s/Nzs3S0tjb1tPY1+HX3dvf5OLg5eDm7ePh4uDigurj7+vqgNXT09XS4Nnc19rd33JyfOl2dufm5ePoduB35eTidenic+R0c3p7enx5e3p66nl7eOzseXjze/PxfX76f/H3+Hvwenp5fH16eYF8hH+Rln+AgH6Afnl+fXp9f4iBg4B/f3+AgYGDgoGFgX2SgHt9jH2CgX1+fXt6e39+gH2AgH2CgIaBg4KCgYOFhJCIgoGCgIOGgoOGgn2Ek4KBf4OCgoCChJCBhIKCgX+BfYKBf4N/fn14gHt/foGDgX+Af4CCgn6Af4CAgYN9foGEgoOBe4eFgISBf4KChX+HfoCAfIR6hX6CfoSFhIGBgYCDgn+CgoF+hoF9gYCChIGEgoKDhIKFXoKBg4mFhIWGg4iJiImCgH+Dg4GAgYOCg3+BgoOHlouAh358hXmAg398fIGAfnqHgXx7f31+goOAgH59fX18in2BfPR+7Xp+gXZ2fYR4eHt9enx8enuAgHuAhH5+f4GFf4CAg4CIf359foCGhHt8gYGBgoSCh4R+gIF+f4F9dX5/e3uGiYWBgYGCg4GChH2Bgo+Cfn5+hIeEgYF+gYCEiIOBhISEg32JhZqFhYSAhYCDhIyphH2AfIGBfoN/hIWBhIiGhYuChYaEhIaEh4WGhYOJh4SGhoiJlISFiYqHhoaDgoCGg4WIiYOAfn6BgYCBfoJ/gX9+e3x9eH19fYB+en+Gfnx7fnt/fHt5eHl5enh7eYR2eHl5enl5dXd4dnt6eHp5d3Rzc3l3dnV2cXFwcHJxcW7dcXN1deXn63jseeBz13Pa29du09XY1t/e2Njd393b1dLOzc3O0cjOz9POxbvEyIDDyMDIx87Z1rvB2sJ7yr66vr27vbi+wLOzt6yusq2vtby6u66xsK+ioqWurq2roJ6so6icpa2jpaafpKKioqGhnqCcn6WboKGak5eTjpiTm6mbnZ6amJOLlJmVlJOXlZeSk5aXkY/VlJOZmpSUkJ2WlJmakJaWmperm5mVl5SPmYCUjJCYwqaqk5OUlZOMkZKNi4uUho+Nlpmdk5CXkY6TjJCTkJiTkZ6ljo+VnJyaZ5yakJGUlpuZmpuYnKSmmJ2gmpubl5yUmpWWoJibp6KfmZWbn6afmpinmKymnJyYn6GbrZaanqKgmaSdz5y0pqWlsKShpqKknqWtoaOqpqa3ooCbop6inaOvp6mnoaGhoKasp6moqaiqpqSsqaqkpqipq5+mpaanp66sta21sq2ur7Cvqq6xr6iprLWtsbOttrW8trqwtLW5tLZvuLy+urG1ucC2t7q0ubq0t7+5ubnDvb68u7rBw7W1tcfAvbzFxsS7xsLSxsK9wMPSz8HByMfPzh3R1MrHycbNxcrJycvKyc/KztbQz87Nz4Ti1eDa2IDT09PY1efh4uDf4uBycXjecnLj4uTl6nnneero43bn4XPgcXF3eHR1c3Rxcdt0d3Pg43Z06Hbs73h36nrs7fR89Xt7e35/e3yDfYN+kpyBg4SBgX97gIF/goSSh4eEhYaCg4OBgYB/hIB7kn97fol9hIKBgoKCgICCgIN+gIB8goCGgoODgoCChIOEgYKBgH+BhYGBhoKAiaSFhYGDgIF9f4KQgYWFhIKCgoCEg4OIhoOCfoR9goGEhYWEhYSEhYSAgoKChISGgoOHiIOEgXiFgX+DgYCFh4qEj4KEg36He4h/gXyBg4B+f4GBhoeFiIiLhZCIg4mEhYWChYGAgoF/gYB/fX6HgYKDhIKFiImJgn9/hYWEg4aIh4eChYWGiJ6Pg4mBgomBiY6LiIeKiYaCkouGhISBgoWHhYSBgIKDg4+DiIL/hfmChYqAgomTh4iKi4mIiYWGjYuHjZKOjY+Sj4+OjIiJi4qViYmLjZGamJCRlZOUlJaQlZSOkZGOkZKPh1WUlpGSoaSemZybm5uYmJqSlZajlZKTlZuenZiWkJWUl5uWmJ2fn52XpJ+4nZ6blZuWmJmx2J2WmJSam5WZlZuclZidmpmfk5eXlJKXmJubnZ6do6OchKCAt5iYnp+cm56ZmqCdoKKmn5ybm5+enJ2ZnpugnZuWm5uUmJeVmJiVnqeZlpOXkZaQjYyJjo2Rjo6PnI6Qj46Pj4yGiYmGi4yLjIyJh4aFjIqJh4iDhIOCgoKEgf2BgoKA9/n9g/6D9YD1gvz8/oP4+v74//nr7e7v7uzr7Ojv8POA9+r09frz6t3o6uDk2dzX2uXiyNPy2YTu39vh4+He1t/jz8nMxszZ1dXf5uXgy8/IyLe4usXEx8O2tsO7wLK5wLW5u7K4uLeyrq+nqqGsuqqytrGrrauls6uyw7W2tK6rqp+psauvrK2qq6akpaein/alo6yopKCZpZicmZyQmJaAnJy5n6CcoJuZpqGanaPOq62Tl5udm5SdoZubm6WYn5ykpqqfmqGbmqOWm5+fp5+drLOamaGqqaiBqqaVlZSXm5WWm5eco6mYm5+anJuYnZWdmJqkm56pop6Yk5idpZ6bl6iYqKefn52qtK3FrK2zta+iqaX+nrqkn6G0nJuin6GAnKatpKOqp6vHqKGlnqWbnaafpaKgn5+cn6SdoJuZnZ+en6yrrqmtr6usnqWjnZ2gqKespa+tqKupqqqlqq2loqSjrqesrqiyrrOprKOlpKyrrWiyuLu2qa2xuayusayztLG0wL29vsW+vrq0tb7BtrS3yL23uMXFx7rCwtPBw7onwcPNxbm5wL7FxcrNxb+/vsS7wMPHysnJz8nM08vIycjJe9XP2tbWjH0Gfn5+fX5+hX0Lfn1+fX19fn19fn2KfhV9fn5+fX1+fn1+fX1+fn1+fX19fn3/fv9+Bn5+fn1+ff9+8H4BfYR+Dn19fX59fn1+fX59fX1+qH0Bfv99on0Bfv99mn0Bfs59AX6FfQICBACA2+Pp7+jl8+/08fmE9Yby+oCAgvL9+Pf5gIODhv+G//39goT/iISFg4eGgoKFi5GDh4WEhoeDgoaCg/n2gH59+YOFhYeKi4iFi42HiYiHiY6RiYyLioiOkIiHj4+di42KiYiMjYqJjY2PjZCUjIyHi46Kj4qEg4SHh4qLioaKjoyAmpWKh4qJi4yKjIeIi4mMmI+MjpKQkJCTj46NjY6JiYyPi4iHiYKFh4eKi52RlY6MipGQioiMjI2Kjo2MjIyLiIyKi4uKiIuQkpCOjYyNi4yJjo2OkI6PlIuMiI2Li42YlZWYnaegmJmRlJGQjZGjmZuXlpeZkpaUj46OjY2Nk5EGk4+Ok5OUhJNGlY2TjpCQmZWTkZKZlZSPj5ONhIeJhYeLi46NkpCOjY2LjpKGiomNiIWCioqKhYWGh4WJho2Li4iGgoSCm4P0f4OKhoeNiISDgISFgYB/fHx7eX1/gYKBiYSNe5GBf3l8foyKg4KDhYOIgYV/g4CAe398eXyAfYOLfYKGfoJ/fIGChIGAg3+CfX58fYCCfoB1fHp7fH2Bg39/foCCgX+AhYWAe3yEgYJ/e3t/goWChH9/gXt9fIF/kIKEiISAgop/gYCCgIOEloeFgIWFhIF9f4KDhYJ+hIN+f36Dg4CDgX9+gYN/foiDfn53fnqAfoJ/f36Dfn98gH9+eoF8d3d6d3V0dHJ1gnd6eXZ6eXZ2eHNtbnBwcHFycGxycG1rb3RzcnZwcXNxcG9vc2pv0GrU1GzZb29wd4LX1dlvbdR2bNTW2djS18vQ0cvLgM/R0M/Q0s3Kzc/SzMfGwsPRu7m/y8LAt9C/wcLAvsLFxbnAuMLJur23uLaztrWzsbK2sa2pqKWlpp6npqS6tKCfoaabmpWWm5aalZSVnqeWl6CdnJ2rpKCflqScnJ6anp+ZnKCfoJ6goqSkn5uuqbKmpKmipJmalpijspyXmJWdgJuXk5yMmJaYopKPjo6PqpmUlZiNloyNk5ScraGmq6qoqqeloKCgvKKlo6StrKiinKWkoZ+Vo6SmpqikpaGloaeqqKyspqyfsayqqaWqnqGnn6bOq6SupqiurLG9r6WppKavrLikoqqgrrCzrLC+sLessMy3s7iwwbeusrWzsamsgLaqt6ejrLexs7W5sLbNvLS2trq7tbm4tLSztb29try4tLCxs7Wys62ztL+4ub65ubi1sru9v7m3ubrAxcPBwcG+vcC4vry5tbaysrm8vsDKwsvEz8nGysrKy87J0dDJz9HT/MzL1dTI0c7IysnGx8TJy8/T0srV2dvs4tjP09ffLN/b4d/W3NPQ2czY087S19rZ3NjY0tvq7uDm7vbt7u3q9Ovx8/Lr4+Xn7eXjgNPb3uPc2uTf39vfd9t93ul3d3zm6+Tl5XJ1dnjqeOzo6Xt57n16fHl9fXt7foOFeXt6d3h6eHl9fYH++4OEgft/fn19fX58e4KFgYF/gIGFhoCCgoB/hYeBfoOEkX+Afn5+goWCgIGBgX+BhX5+eX2AfoKAfX1+gIOFhYR/goSCCo+KgYCEhIaHhoeEgoCDlYWBg4WEg4OHhYSBg4V/gYSJhIKEhH6BgoGAgo+FiYKDg4eIgYCDgYKBhIKFgYGCgYOCg4KAfn5/goGCgICEgoJ/goGCgoOEioOEgIOAgH6GgoCChZSLg4aBhIOCgIKOhISAgIGDfoGCgIKFhYeHi4eJhYSHh4mIhoWHiYOIhCmEhIiEhYKChoKFgIKGhoOGi4OEhIKDf4KAfX+AfoGEfX9+hIKBgIaFhIR+gHx/fIB+f318en1+k3/xfH+DgYGGgXx+f4GBhIGBgn+Af35/gYGCg4iFjX+dh4Z/f4CKhoB+f4KAhX+Ef4SAg3+Cf32Bg4GGjX2ChIGFg4GCg4OBgYSAg4CCgoGFhoOKf4aCgoGBhIeFhIOEhIWCgomKhoCCioaHhoOEhoqJhomDgIOGgYaEiYeWjY2SjomMk4iKh4yHiYqajIiIiYWFgIOHiImHhYqKhIWFh4aDhYOBgISEgX+KhYCCf4aChIGFgn5+gX+AfH5/fnqAfnt8f3x+fnx3eoN6fX13e3p3d3h3dHR4eHt5e3h0e3d1c3h7eHh6dHJyc3J1c3pzeed16up1gOp0dHR6h9nY3HBv1Hht09fc3dnd1Nvd19ja2tnV0tLOy8vR0s7KzcjK18XBxM7IxL7XxMPBvrm8vL6xt7O+yLi5sbGuq62wsbG1uLiys7Cvrq2mrKijsq+joKesqaehoaejpp+enqWtnZ6no52hq6ScmZSfmZebmp2em52fnJiXgJaYl5aRkaKbqZeXmpSYkZaSlqCrnpqal56gl5OekZuXm6eXl5KSkaWXj5CUj5iQj5OQkpuOk5iYlpiXkI6Pk6mXlJOTlpSSjoqTl5OVjpydm5qalpWVmpSenp2dnZqek6GenJycopqen5ibvJ2Wn5eZnZ2hraKan5qdpaW4n56jgJuopaWanKOVnpmasqGeoJeooJmgoqeknqGtpKyioaWspqisraSnuaieoJ6jpqWnqampqKeusqytqqenqKmqpqamq6q1q6qtp6ippKKqrK+ssK+wsbWxsLCxsLC0sLi3s7CvrKuxs7e4v7nAucS6u7q6uLa3tbm2sLS4utm4uL2+RbK5uLa6vLq9uLy+vL67s7i+xOfZyMG+vMPBvcLDw8XDxMzByMvHycjLy8vNzsvO2tvM0NTX0NHOzNPK0dLV0c/U2OHb2EDM19rd2Nfh29/Y33fXfNXecXJ43unl5OZ1dnd55njn5eN1duV4c3NwdHRwcnd+hnV4enl5e3d4fHh77Ot7enjyhHyAfn59e4CCfX59fH6DhoCCgH99hIZ/gIuMlomLiYSDiIiDgIOCgX2Ahn+BfIGHhImFgIGBg4KEhYR+gYWDkIyCf4SCg4OEhX+Bg4GDjIWBgoWGhoeLiomGh4iCgYOGgIGDhYGDhoWHh5SKj4eFhoyMhIGFhIWEiIiJhoWGg4aFh4c0hoODhYaEgoKChISEgoeFh4iIiJKFg4CDgYB/iISChIaPioOHgYWEg4KEmYuLiYiKjYeJiIWFgISKhYiEgoeGiYmIiImLg4Z/gICGgYGAgoiFiIOHjImFiIyEhoiFhoOHhYSEh4aKjoWHhYqIhYSKi4qFhoeGg4iFioaHhYN/g4KXhf+Dh46Kio+Lh4iIioyPiouOioyKiYuLiYuJjYqShq2Pj4qPkZ+cl5OTlJKVj5OPlJGSj5STgJGWmZeeppeeoZufnpycnZ+dm52YmpaYl5ieoZyilJ2Xl5eWm6CgoJ+hoKCdnKCinJWVnpubmZeanqWkoaOenqCanpyioLWmp6ekn6KqnJ+dop6jprqsqKioo5+ZmZ2cn5yZoKGbnJygn5qfnpycoKGcmqWglpiSnZmdnZ+dmZiagJiZkpaWlpKbmpKVmJKSkZKMkJuSl5SQlpSPj4+Lh4aKiYuKjYuFjIiEgIaKh4aIgYCBgoGFg4qBhv2A/v2A/4CAgIiQ9fX9goP6iYD4/f/89Prs8/Xr8PX3+PX2+fTz8fb28Ozt5un64Njc6ODa0vHd3d7e3N/j5tjf1+Xt3uDZgNfQx9HT1tXW29fPzMXFxsW9w7++z8m5tru8t7Wwsrm1uLKvrrm8qKitqqSqt7CrqKCwqKmrqK6wqausq62nqKmsrqaivLPFqaeqpqmhp6Oms8euq6akraqim6WTn52hsJ+dmpqZsJ2Xl5qXopuVl5SaoJmdpaWnq6qinZ2brpWagJeXn6GimpWgo56ak6Smp6irpaGeopmenp6am5iajKKhoZ6epZyfoZygz6icp56gpKOptqmeo56fqKWun52lmaiop56irZynoaO9qqmrobGqoaeqq6ecnqaZpZuWoKqkqa2up6vLsKajnqCcm5qcm5mZmqSnoKOdn6CjqK2srKitgKy0qamupaeoo6OnrK6rrKuxtLSxsKytrKywp7CyrKmsqKqvsbKwt622r7q1uLq7urm5srWyq7KytNW0t8DAtcC/u7y/vL22t7u6vbyzur/E5tfGu7m6v7+9xcO8wr29xrvDwr/AwsnJy8fHwsbQ0cTLz9XO0NHQ2MzR09LJxsvOA9vR0Yt9CH59fn19fn5+hX2Efgh9fn19fX5+fZZ+Bn19fn5+ff9+/36KfgF9/37ufgZ9fn19fn2Ffgh9fX1+fn1+fv99/33/fbp9AgIEAIDq5+Xs6e/v7Pb27/j3+ff09P/69Pv8gYGBgoCDgoD/gISBgoGHg4OHhISEgoWBhYaKjImEh4WFhoGCh4OEf4SSiIuIio+PiI6LjIaJkYaFiouNio6Ij5COjpKQkZSSkI2KiYmNjYeMjpKPj5eNi4yNlY6MioeBiIWEg4uRjouOkICNiYyQjomLi4+NnoSIhYeIiIaNjJCNjo+Tk5KQioyMj42QkYqJh4iGh4mFiIyPkJCPj5OZjY6QkZSWkZ6Li42Nio+PjIqOkpKSj46Ljo2NjYiVk5OWj5KMjo2OkpGQkZKVlJeal5qbmJuSkY+Lj5GTmJmin5uilpuWmJOWkZGSlYCWkZWTl5WWk5mXmpeal52jnJ6bmZScl5STkY6Mio2FioaIi4yQj5aalpSOiomMio6PjImGh4mHioqGh4eIho2IioqKhoSIhoWBhIaMioqHiYiEh31/fHt8eX18fnl7g4aGhIaIgIN8f3uBfXt+gYOEhoSFhYGAfoCDgX5+fH96gIB/foaBfYOEiYSDgIGAf4aHfoSAfYCAf4B8h3p3enl+f3t/e32Df4KCg4uFhIGCgX2Cgn98e4CAf4GBgn9+goCAjHh7f4CBfH1/gISDhIeHf4KFf5eCgYSAfH9+foaEfpeQiICDg4WAgY+Bg4qXh4eImX+AgIKBgoGAfoCBjYCGg4CCgICBf4B9d3V2b3eBe318foGqenp4fH18e3x5dXZucXl0cnJwd3pxcnFvcG1xc3NycHJzdHVwbnDe23VweHN2ed3O2m91bW5xcG9wc3LSxMvMymnT0s7Z2tjW09TT1tLIxsTPzHHEz8jDwsDCw7q5u729x8LEzs3DyL+xwWuyu4Cytae8wLywsK2spqenp6Gko6+kpaSem5OUlpGYk5yemZzAuJqZsI+XpZ+Rl5ecnpiaoZ+bna6Um5OYnI+ampuWm6CdmJacpJ+aoZummJqik5ufnpeQlI+PjZmVmpiSlo+qnp+dmpehm5eVmqChoKKhn5+ho6edlpyhnKurqqrt3ICoqZ+moqeruaqppqaxqZ6fnJ+in62spq6tp6rAt6air6OdrKCtpqiepbDJqa+vpq6vqaGortavr6i5qqyptbywycu4ubW7yby2vqu7ya+5tru6yqyusa+5vLG0t7G2vLSywriyrLe2u7KwtLixr7azsr24trC1sru7vbm+wb6/sIC1s7W2tLWjtrnBt7e6uLvAu8LAt723wLa/uLnEysDBx77Fvci/xsTLwsXOyNHVxs7T0tHK1crOycfCxcbGysXN0s3fwcrGzdfV19Db2NDW2tnT2d3g39vk3ODd1+Db0drKzffZ3N/h393j5ejp8vHt8Pfx7vz49PPp7+3n5uvl6oDf3drf2t/d19/e2OHk6Onq6vXv5uvpeHZ0dHN4dnLndHp4d3l+e3t+fX1/fX56fHt9f3t4fHl8fXx+g4GCgIKRgoR/fX5/gIWDhYCBiIB/hIWGhIR/gYKBgYSBg4SDgX5/f36CgX6AgYSBg4mAfn9/hoKCgYF+hoSEhIuPioWIiYCFg4WJh4WJh4qImIKDgoOHhYKIhYiDg4WIiYmJhYaFh4aIiYaFg4SDg4J+gIKDgoOCgoaMg4WHiIqKg4+CgIGCgIWFg4GFiIiHhoeEhYOEg3+Ih4eJhIaBhIOEh4WDg4SFgoSFhYaGhYiCg4B+goOChIWLiIWMgoWEh4WHhIaJioCMiYmHiIWFgoaGiYaIiJqKjI6LiYOKhoaFhYOEhIeEiIWGhoWFgoSJhoWDg4KDgISEgoJ/goSAhIJ+gH1+e4J+f4CCgICDgoJ8fn6CgYB/goKAhX+AgYKCgYODg31+hIWDg4OIgYeDhoSJgn9+f4GAg4GChYODg4KFg4CCg4WBhoCEgoiBfH+Ag4CBgIOCgYeGfoKBfoKFhYiFk4OEh4KFgYKEgoKKh4eHiJKIhoSGhoWKjImGhoqJhoeHiISCiIeHlIGFioyOiImLiIyIiIyKg4iLip+MjY2KhoiHhY2Kg5qYi4WJh4iHh5GFiI+giYmKm4KChIeGhoWFhYODk4KGfoB+fn9/f4KBgH5+eYCIfn9/gIGxe3l4e3x6eX18eXp1eH97enl4fIB3eXd0dHFyc3Nyc3R1eXp3c3Tm43dxeHN2eNvQ2W9za21tbW5vd3bc0tjY2XHY2NPY2NXX08/Q19TLzsvX1XXK0cjDxMXHyMDCwsK8xL+9w8C4v7qzwmuyuoCxsae4u7qwrqyuq6qpqqSopKymqqapp6apqaGknqGhnKG7tKOjxZujqqecm5ieoZqcoKGfnrSUnJWWmo+Zl5iVmqGbl5iZnpeRmJSdlJaflJicnJeTl5KUlJuVn5mYl4+nnpuYk5OglpCPkpiamJmWlJGTlJeQjJCSkpqWkpHDtYCWmZSZmKGirJmZk5Odl5GTk5aYmaKgl5yalJewqJiWr5yWqJqqoaOXn6SznaGhmaKinpigo8KlpaGuoaWjq6+fwbqZmJWaqKGeqJintpyjoKqqw6ClqaWwsqamqaanrKKfrKOho6ilq6alqa2lo6ynprGsqKSrqK2srKaqrKqupICnqKmqq6merLG1ra+wrrKzsbWyrbGvtK62q6y1urOwtq63s762vLzCt7i9t7u8s7m8u726w7rAvLy5urq3vrjAxcPQvcS9vsjAwbnAwr7Fx8a9vsPFyMTKxcbCwtPQyNHCxejQ0NbW2NHT0NDP1dLO0dTPzNXS09TQ2dfX1+DZ3IDa2dTZ1NvZ09zb2N3f4+Df3uzp4efsenp4eHh7eXTqdnl1dnZ7dnV6dnd2c3dydnV5fHl1enp+gHt+gX58d3mFe399foGCfIB+f3p8hXx6f4CBgIF9gYKCg4mIi4uMioeGhISHg3+AgYSBhIyCg4OEjYeGhYN+h4OCgYmPioaIi4CIhYaJiYSHhImIloGEgoKEhYGHhouHh4qNjo6LhYeFh4aJioaHhoeEhIV/f4KHiYmKiY2ShoeIiYyPipeHhoiJhYqHhoOHi4yKiYeGiISFg3+Jh4iKhIeDhYSGioqGhoeJhoiKh4mJh4uEhIB+goSGiIqRj4qRhomHiYaJhYeKihaMiImIioeHgoaGiYWHhrvaiYqJiISKhIWAhISDiISKhoiHhomHioyKiIaGhYmIjo+OjIqPkIuOjIeFg4WDioSFiYmIh4qLjIiJipCPi4qNj4yQiYqLi42LjYyLhYWJioeFhYmDi4iOjpaSj5CTlpaYl5eXkY+PjpKSkJOSlZGbmJigmpabnKCdnZuenJyjopienJaam5uem7ELnJuemJyZl56bnKaEooCrn52bnJyZoKOenJuhoJ6goKGdmqGenq2Wm6KkpZ2fn52gnJ2iop2jqajFqqmppZ2gnZ2noZq2tqSdoaCin6CtoKKqxaamp7iam5yfnp6enZ6bmqiXm5aVlJSUlpmYlpSXj5ihlZeXmJnHkpORlJaSkpSSkZCJiY+LiYqIjpKJi4CIhISBg4OCgYKDhImKhYKB//yFgIaChYn97/yCiYCBg4KCgYeG/O/19fWB+vjz/f/8+/fy8/j27e3n9fGG5u7m4OHh5ebb3N7g3Ofm5e3r4eni1umB1eLZ2svh5ODT1dLQy8vKyL/CwMm8wb3AvLu5urS5trm7tLfOxbCuzqKrsICvpKqqr7Ktr7Ozr7LEp66lqqyep6WnoaaspqKgoqikn6ijr6KksJ6mpqajmJ2Xm5minaShm56WraGhnZyYpZ2YmZ6lpJ2goKChoqWpnZWXmI6al5id69CfpJmal5+frp2hoKCtqp6fnqCdm6GglpuclJqwr6GataOdrZ+uo6WZpICrwaWqq6Grq6OdpKjNqaqlsqapprG2ps3MpKWhqLmqqK6drsiirKiwrr6dn52bpqmfoainqa2jo7Ckn5ydmZ2Ul52jn52joKCpqKinramwsbOus7i2uaqsrautrKqfq7KyqqyurbWzsrWxqq2rsayzrq66v7a1vLC3r7ettLS5r1eyuLO4ua6xtLGwrrmzu7rAvsC/vsK4v8PA0bW9trjBvr64xcS+w8jDvL/CwcTAx8HJxMLPx8HIvL/10NHX09LP0s7RzdbVzNLX1tbg29jXz9LQz9Db0daWfYh+AX3/fvF+AX//fv9+q36CfYZ+g32KfoV9AX6RfQF+l30Bfv99/33/fYV9AgIEAIDs7vPw8fH19fuChfz79ff/9fD88/f49v78oYKVioKCgIKEg4GDj4SLgYSDhIOKgYWKj4+PlI2PioyFkICGhoWJi5CIiI2NjIuLjI2HjYuOkJSRkZCWkZGVkZeSkpiXlpKMlJCQj5GSkpGQkJKLkY6NkpGfkI2Mi4WKiIqNjo2PixCPkJKQjY6NkI+PlpOOjI2LhIyAjouRjouOiomKi46Kio6Qko6HjIuLkIeKio6JjJGKk5ONkpGUm5iUlZKQkJGTjo2RlIuJiJGfl5COjY2QkZGQlpOOkIyMioyOkZGSlJaXl5mYmJ2XlZKOkYyNkpKVmpufmp2Ynp+dlJWXlZSVmJuXm5maoZufoqCdnZuVmJaWlpUjp5mVlJKRk4+OkI6NkY6PkI+WkZKTl5KWjpGNjZOSlImMjIuEioCLhYuKhYqJhoaHioSPfoOEh4uHlYWGhIJ/f3p+gICDgYF/goOHhoaIiIiHgn17fHx3gICDhYWDgYGDh4KDg4d+hoJ+gIB/hIGBhI2DjIOCfYWrh4mGiYOBgIGDg4R6e3x9fXx9g3x/grWDhIWPj4aHg4uahYGAf32Ag4OHh4WFh4CGiImEgoN+gIB/fHx9f4OJiYeFi4mKi4GAgn+Hg4KEg4OEgYuGhomIhYeJhoWAiIeWg4yHhYuJiYCFfoJ7gIaEiYOCgoB/gYOHhYF+eXt4dXh5dnt9hH98jX99fX6Ae358fX15cXRycXJ4eHJxd3p6coF4fHqCeXZ4cnFxcm5sboBrbXJ0fnV2cnF1c3J4cnJ0c3BxdtjTbtLWa9XY29vc2dje0tfMzsfOzsnOzcvOzM/PecrCwrrCwt7Dx8XAwMbCb7a9u8LDuLy/v76nxLKsuKyyqK2rrrOlrcGjmZqjlZ6UnbWdl5mcoaSipaagoqGnn6Ohm6OpoKOel6WWlJaaloCmrp2cmp2kn5mYmKKhoa2mnqCYnZ+gmJaemJiakJCZl5uUn5uWkqmamZucnp2enJeQmZycmZOYlpuXoqWjraOnpqmjta+lrKimpqCmrq2onaHOrqawr62kx9SqqKmqqqyoqKWioqa7paCypKKjoaq0prCprqmpqrGwqa2woKuuqYCvoaSxq6u8s7PEwMO8wbuytsOwq62trrDrrbO8v7S8u7e6vMHWr7iztb2xt7m8r7Kxr7i7wba4v7jFvb3GvL68vc7CwLOzr7awsrO2trfEvMS3wci9vru+w7vBw7/ExMvIy8nAx73Hx8TJxMDDyMPFwMXTxsXAysTL0srI0MHLy0XNx8/MzdXQxMzKzNLP1NrI1N/R3dnRys/c2N/f4d7f3t3S4OXe2NiB5e+D4eTq5+qA6ufs7vL1hPPs7vXw6u7t6ens7eeA4+bk4eHe4Nzgc3fj5uPr8enn8enp6Ojv56B3i4B1eHV4e3p6fId9hHx9fn9/hnp8fH18en57gH6EgIt9hIaBg4KHf3x/foCDhISGf4OChIeHhoWBiISFiIWKg4WIiIeDgIaDgoGFg4SDhIWFgIWDg4eHmIiGh4aBh4aKjIuKjIeAiYqMiomJiYqJiI2KhIOGhYaFh4eHg4mIh4iFhoeIioeJiIqMiIWIiImMhIaGh4GFiYOKioWHhYeNi4aHhYGDhIeEhouOiIaDipKMhoaEhIaGhIOJiYWIhYaEhoaIhYWHhoeEhYWFioaIh4WHhYWGhIOHhomFiYWJjIuHiIqJiYo0jI6LjIeJi4eJjYyKjouKi4mJiIaUiYiGhIOHh4eKiImNioqJhoqEhIOHgoeEhoSFiImTgoSECIKCg4J8gYJ/hYGAg3+IeX19f4J+jX6ChIKChIKHiIiJiISBhIKEhIODhYaJhYKChIN+g4KLhIOCgoGEioaFhYmFjoiHiomHi4WFh4uEiYSDf4emh4qIioSCg4SGhoqChYuNi4eGioWIi6+LioqTloiIh5Wli4iIiIeHiomNiYaGh4eHiYaFiIaKjY2Ai4qLioyRjYuKj46Rk42Lj42SjYyLi4mJho2IiImIh4mLioqGjI2dh4+KjJCNi4WIg4aAhImFiISBgX+AhIWIh4WGgoOBgISBfIN/hH58kH57e3t8en59foF/eHp5eHd9fXl6fn98c392eHaFenR3dXZ3eHZ0dnNzd3Z+dHVxb3OAcW93b3BycG9yc9nYcd/fb9nb2tfW19rc1t7Y1tLX1tLRz8rKycjKhMfFyMHHxNvBwry0uMDAcbm/ub68s7W2trqtybeyvbCzq66tqrCkrcmsp6q0pq2mrMKpo6Spqqqoq6ehoaGkm5uamJ+in6KhnKqenZ6dmre6mpeWlp6blpiAk5+amqSclJeQk5ibk5Kbl5iYkpKWlpmUnpqYlribl5eXmpaXmJSQlJaXk5KTk5KPl5qXnJWXlZOPnpuXmpudnZeboJ2akJLFmpWfoKKct7+foJ2dnZ+dnJyYoqG3oZyunZ6bmKConaSgo5+goqennaGkl6Smo6qhn6ifn6eZmKCAnqCapKKdp7emo6SkoqTZn6WqrKepqaaqqKvbp6aipKqhpq6xpqimpqqprKisraexp6aqpKmoqbqzs6ypqbCsrqqurqqzr7ixtryyt7O3urS6ubW3tLi2t7autq64u7i9t7a4uLe4ubrFvru6vrvBw766wbS7vLy2vr2/ycS7xMM7w8XAw8Wyusq+ysfCvsPLx8XFxsjLycfDy9TMx8Zz09p8ztLW0tR709HV0dLRcM7IzNLP0tPV1dre4N2A1tvZ19nX3dvkdnrp6eTo7eTh7eXo7O318r99j4F4d3R1dnZ0eIF3f3Z4d3l6h3Z5fH58fH96gH2Dfol5fn56fX6CfHuAf31+f3+AeYB/goOGhYWBhoGBhYGHgoWIioqHhYuIiIaIh4eDhIaJg4mJh42MmoyJiIaAhYKGiYqIioZ8ioqLiIaEg4SEh4yLh4eJh4iHhoeJhYqLio2JiYuKjIiHiouMioeLi4uQhoeFiIGFi4OLi4eKh4eOioWHiIWEhoiFho2Ri4iGjpyRiIaGhIeHiIaNjoqNiYmGh4eKhYaIiYqHiouLj4yNioeJhYSFhYWKiYyJi4aKjYuFh4SJgIyNi42IiY6JjZCPjIyKh4mGh4aEl4eGhoWEiIeHiYaHi4mJiIeLhYWHi4WJhYuLjpaVnIyPjIqLi4yMjIWNjIiLjYyNjo+Nl4aMjJCTj6mOkZCOjI2JjI6Njo6LiY2Mjo2Mi42Oko6LjZCRjJOWpJiYlpWSk5iTkpKXkJ2VlZiagJeclZeaoJefm5yXnr2hpKKkn56dnqCgopiboqGgnZ6inKGn16ioprCvoqCcqrihnJycmp+jo6ilo6GlpaWnoqGioKWnpqWjo6GjqaShoaalqaymo6ilrqimpaOhn5yloKCjo6GjpqSknqeou6OrpKWpp6adopugmJugm5+ZlJWVFZSYmp2em5yWmpeTmJiUnJWblZaomISXgJSXlJWVk4uOjImKkZKKi5GSj4SYio2LloyIjImKioyHhoeChIaGkYWHgoKIh4WNhISFgoCBhvr2g/7/gPr9/fz6+vr/9f729O/08+7t6uXo5ejrnebd4dba3Pnh5ODa4u7qhN3l4Onn29/i4OPN8tnT3c7VzdDPz9fGz+jEubrBgLO9tcHkvrW1ubi2s7WyrayutrGzs7C6u7O1sq23qqqsrafP0ainpqevqqKjoKmnp7eroaKan6OlnJ+upaelnpuipKadpp+emLahnaOip6OioZmWnKOfnJufnJqXnp6YoZicmJqZpqGdnpiUlZOZo6Omm57VpZigm5yWvcWZmpibgJmcnJ2emqWlu6CdsZ+bnJqkrJ2ln6OeoKOpq6GoqpypqqiuoZ6pn5+pnJyoqKyntLGrs9Cwq6uqqajSnaGnqKClpaOoqKzwqKajoqicoaesoKOhn6KkqqWmqqayq6y1sbe1tsS1sKKfoaurrq6xsK21qrWqtbyssKqsr6eurKmvZ7G3t7i4r7Wqsa+vtK6vtLm3uLi5w7ezrrKttbm1tb+4v8PEwMnDxMnGucC8u7+6vb6st8i9yMa+t7jAu8DDxsPFxcfAx8/GwL11y9d709bZ1teA0czNztHSc9XR09rW1tbV0dXY1tKJfYJ+jn3/fv9+/37/frp+Bn19fn19fpd9AX6OfQF+/33/feh9BH59fX6FfQF+hn0Bfo19AgIEAIDv+P+C+ICBgoKB/P75/f338v34+/77g4ODhoiMiYmIiIiLhYSHjIiGhIGIiYiJi4iNkIuQkJCRjImIhoaFhYOFkI+MkpOMj4+RkpCOk5STkI+SmJSPlZWWlJSSl5iRmJOak5eTj6WWmpaVk42LlI2Qjo6Sj5KOioyKhoeMjZCOj4CUlY6Oj5COjpCPkI+QkY6NkY+QkI+Nj4uLiYuMjo2NjI6Uk6CWlZCOjpWUm46MjI+NjJKRjpGXmZiYmZOWk5OVj46Mio6Oj5SQr5iQj5OUkpaWmZOXk5CTj4+VkJaTk5OPj5Sal5iYmpWXlJGUkpGSlpmZnZ2fnZ+lnqCVlZaTx4CXlqCal56am6Gam56kn5SZmJiYmZ2im5eanJaSk5SPkpOSkI2QkouSkZeSmJSTlpGMjY6YkoiLlJKPhIqRjYiIiYWHiIKGhYaEhYSJiIuCho+ChH19fn2Ch4SBgYOGg4iLioqJi4WCgHx7fXmCgJCDgIWCg4WChYKDgYOCgoOChFqChomLjoeGioiNg4eOhIWEg4CDhIOCg36BgYV/f4N/gYCKhYOCjImGioqIiYaIhoaCgYWFhYuGhIqZh4aIjY2GiYuDiIV8e4KEh4SLioWRkJKLg4eFhYuEg4mEhICIiouFh4uOkIiIhoeIjIyHhI+hiY+CgoKEj4SChpeCgoeKhZGJhISFf3t7eHx0hoiAfoZ/en2AgYCBgH5/gH58e356e3t9fHt7gIKGno92eH2BdXJxdnJxcGtsbW5yZ25zcXR1c3Fvc3FzcnV3c3TcbnJubmpoamrQ2G/S2NXY2oDa1tXN2NDL0dHUz9TW1tvKw8m4v77EyMPEw8XKxs2+xMrAyMLAur66rrS9sKmrqqezr6yoqaevsKSep5uXl72tpqKdnqCtpamem52pop+onpyenaKioJ2fn6Cgn5mjnaWdoaalpKKimZ6eopudm52kpaGioKKwo6OZlZ+enaKeqYCjqJ6Xm6KYoJ2anJ6doamXl6KXj6LCpJaeoKGrq7ismquoqqfCqqufmZ/KpLOptKmxra2gqqqnoqihpKyop6aioZqmnKmkr6iknK6uqKetr7+8sbayqqSutquos7Gup6+wr7S7u927vLq5vrq5v7+3ucrVrq2zqbm1tbK1v7u9woDCw8DbyMK7tuu2v7O6trW6vMO8wsXDxr68vby9trC3vL6+yb+6vsC/ysjFzMm9wMjpysjGyMjCw7zCxcvQytfTzM3MysrMyMzOx8zJ1NDT4MvRysbLydHVzsjMyb7EycnBxcvT2NbX09DR1NDT2d/Q3dra4efq4+fu6Orp4ODZ0SLe39zr6+vu6v7t5uXp9+zq7uno7/Xl7Pzz8vLt7PqB8/T1gOHn7HbhcnNzc3Lg4eHq7Ovn7err6eR3d3R3eXx6eXl5enx5en6BfX9+fIKDgX9+e35/fH9+foCAf39+f4GDf3+GiISEhX+DhISHhoOIiYqHhoiKhoKIh4eFhYOIjIaLh42GiIOClImOi4yLhoWMhoeFhIiFioqJiomKi46LjIyKgI2MhoqKi4uKjYmKh4aHhYWKh4qMi4mNioqHioyOjIqJiY2Lm46NiIeIjo2Th4WGi4aGiomHiY2OjouMiIyJio2KiomJiYeHiYejjoeHjImIi4uMh4qIhYeEg4yGi4mJioaFiIuIiIiJiIuKiIuKiYiJi4iLioqJiZCJjISHiYi7gIqJkoyHjIiJjoiLjZuPiIyNjYuLjI6HgoSIh4aIioeJi4qJhYeIgYaEh4WJhoaHg4SFhpWRg4OKhoR9gYaEgYKCgISFgIOEg4KBgYKFi4KGj4mIgoGDg4aLiIaFhoaCiImGhISIhYWEhIWHhIiHoIiDh4SEh4aIhoiIi4qJi4qMgIiJjIyNhYWIiY+FipOIiYiHhIeIiYmKhYiKjouKj4qMiJCIhIOMiYWLi4iLiI2LiomIjIuKkIuIjJqHiIeLjomOkIySkouKj5GRjpCOjJaVl5GLjY2Ok42Mko2NjIyOj4+IiY2PkYyNjo+SlZOMiJGsj5eLioiHlIeDiJeFhYaIdYKOiIWJjomFhoSFfIiKgoCGfnx+foF/f317fYB+fX2Afn1+gH9+foGDi6OPdnh7f3V1c3p4eXl1d3d4e3F3eHd5enh0dHZzdHFzdG9x2G51cXFxcHFv2Nxw1dfW2uLh4N3Y3NLKy8vLyMjJzdbHxc/DxcTJyoTEgMXFyrq+wru/vLu4vbmxusKxrqurprK0ra2tq7CwqKewqKanuriqpZ6eoaunqqKdnauknKOampmYn56inKChoJ+blp6WnJWYnJubm5yWm5ygmpmVmpmalpqWmaycnpKRmpaWmZWemJ2XkZKYj5eVj4+SkJOflJecko6fvp+XnJiUgJqbpJ+Wn6CgnsOgo5mVma6Yn5ihmaSgoJeho6GaoJiaoZ6hop6hmqOZo6CnoZyTpKWgnqSmuLOoq6ihmaOon52lpKajpaqnqqumxqChnZugnp6lqKaotsCkpKqgrKipp6WtqKmrqaqluKmnpabBqrerrqmnqqqtpqiqqa2rqaqsgLCuqbCws7W2sKyuram1sqyxsa22vNK5uLe3uLK0sLKztLizu7ayt7W2uL26vL64tbW6ubjEuLy5uL/Axs3HwsbCu7/DxLu+xcnIw8TDwMHEw8LFzcHHxMnT0tPNz9DNztHQ1tLO19LK0tPV2dbj1tHS0+LX1NnV1Njbz8/b0tTYB9PV5Hvi4uWA1NreatNscXN1dejr5+7r5t/q5uvs7Hx8en1+fnl3dXV2enV2en97enl2fH59fXx3fH15fX5+f35/f36Bf397e4SCf4KFfoCBhYeEgIaHh4SEhomGgYeHh4SEgoiNiZCOlI2Qi4WVjZKOjYuIho2IiYaHioiLioeIh4SGioqMiopAj46GhoaJh4aJioyLi42Kh4yIiYmJhYmGiYeKjI+MioeHjYubkY6Kh4iOjJGGhoWKhoWJh4SGioqMjIyIjImIi4SJgIyMipCO3JSJh4yIh4uKjYeNi4eMiIiQi5KNjI2JiIuRj4+OkI+Oi4eJhoKEh4qIi4yLiouSjJCIjI6N+I6NlI2HjImKjomNj6eSiY2MjYuLjY+HgoaKiIeLjIiMjYuHhIiKgoeFiYOLiouOjZCQkKKdi42UkpCGipCOiYqKiIyNgIiMjY6MjIyPkZyNkJmRkYqKi4mMkY2KiY2Pi5OUk5CRlJCRkJKVmJSambmakpmVlJSRlI+PkJSUlZeXmpmcn6GjlpaZmqGWna+goqGfnqKjoaGinKCfpp6gp6Kkoaylo6OtpqKppqGhoaajoqChpKelraikq72lpqWprKerrqmwgK6koqaop6Gmo5+sq7CqpKanp6+opaulpqSjpqion6Kmqaukpqanq66vp6Ww16eroZ+en6qemZ+1m5qcoJuooJ2ipZ+bnJeakZ+imJafl5SVlpmWl5WVl5mWlZaZlpSTk5OQkJWZm7amh4mNkYeFg4uHiYmFhoiIjIKHiIaJiYeFgISIhYaEhYiDhf6BiIWGgoKDgfj+gfL29Pf7+fby7/Xx6u3u7+zv8PL96OLr2t7f5+rk5efq7+zx5+vu4+rl39rf3dDb69XQ0M/M2NbQzMrIy8m7tb22s7bQ0cO9trS1vbS3raeoubOvubCurqyxsbKtr66xsa2lrqetpaitraypgKuipqarqKilq6+yrK6prL6ssKKjraiqq6ivp62hmpyln6ino6Sqp6qroqSonJioxKyipp6cn56ooZWdl5qZzJ2hm5ebzZ6nlp6apaGgmKSknpmjmp6moqaloqegrKKppK2hmo+eoJiWm5+sp6SnpZ+XoqqioKqsraisrautrKzRgKqsqqyxsLG4urO0vsapp6qeq6elo6Orp6esrrKrwbCppaW/qLOkpqWlqKmspamrq7OvsbO0uLClqamqq7Cvqa2usLq2s7m2rrC64Lq4s7Owq62nrrK5vrjAvbS1sK2vsrK1ubW6ucK9wMu5u7KxtLa7wbu5xMC6wMfHu77ByMvCPMXBvL+7ubm9x7nDw8TLzMzExszIzMzIysPAy8bA0NTV3N7u3tbY2evUz9LNy9LYy9Hi1dfX0dDfet3a3AV9fX1+fYV+jH3/fv9+/37/frt+AX2IfgN9fX7/ff99/32mfQR+fX19AgIEAHf58fr9/vr6hoKEhoOEgoSRh4KDgP2Bh4eIiY2LiYmIioiGhISEiISEiYiKiISGhoaJi5KYl5STj42NiIqGjIqMjIyLjo2MjZKSjo6QjpORmJubl5eblZeXj5GQlJmVnKKYlZSRkbSXlJORjo6LiomOjZKPkZOLjYWOgI2OlZaQk5GTlo6Tk5OSj5KVlZKSkZGVmZKSkZOSlY2Li4yRj5KVlZibmpOTk5KSl5iTj42Sj5KQkJWSl5mXmJSQlJWrmpORlZSXlZWUkJWVmZeYl5SUl5GRk5WOlJKYkZiVmJOQmZqbmJqdnJ+Yl5SWkZGSmpeYl52ioqGmoKOfgJ2blr2dmpmVm5ian56gnqShm5ucnpuan5mbnpianpmbm52Sk5OUk4+OkJCSk5OWlJmVkpSUk5WOj4KVkY2Ni4uPjZGOioeDhoeGiYqHg4SGioeJhYeIhoOBgHuFh4SChLGnh4mKjIaIiIiGg4KBf3yFhoOFhYaFiYyFg5CBfoaGgIOChoaFgouJkomHjImOi4iGhYSEhYCDgICCgX9/gICAgZaJi4SIioaFi42IjIyKi4qGgomGiZCIjIySkZeQjo2OjMKQj4mIiJKEhIKIhpWMjIqTl4+KjZCFjYWGioeFhomEiIaIkY2PlJOLkoqQkoqEhYWMnY6HiIiKgomLhYCKgJKHiYqJioqHg4SBf398e36AhYqCg4B/fX+DhIGFg39+foB9gIt+f31/p3l/in2Ce3l2fmF5eXV4d3SCcWxrb3RvanBxbW11cnd3dXt3eXp4dHZ2gW9vbnBvbm7UbG5x29rU0s6IxtPMdc7IyM3Lz87Q0cu/w8PFwsbKycOBwMrIgLvHwMm+wbC1tr65vbe4qLi1tbWstqSvqq21saekqniwp6Kjp6uho6ieq5ezn5+anqOipKGgoqaamqGhnJ+lo6axv7eaoKKnqqyqqqOqpaSjnqWqop6rrLChpZyenJunq6SrqbCvo6Kknp+fnp6foqChop+fmZqXm6aiqK6isLDAgLWssrGw0KmqqKegqqyqp623q6GiqqiiraOnure1pKiprKy+qKqorq6rqa+sqqSzq6e1ua+0trmxtMW3tLS8wbe7ubzXr7W5wbCxtrrAw8S6yLq+uri3u7y1sbW8vL29vsC/xMnFw8XHvraytMa7vL3KzMbIwb7Bwbe2v7S2vca4gL/AuL3CxsXJysvFv8PHxL+7t8fHx8TJyNPHycbKw8nI0tjVzszLzcbGzMXFwuLUydTlyszMzcrMz8LJw8zQwNDNzfTU1NnV1dLV2NjU1uHc0NfW2dri3+Ln4Ozn3tzb1+fn6ujo7uzs6ejl4+Xk09rg4ezr8fbq8/n/+fD29vP0BPbr9PiA6eLs7e7o4XdzcXRwcnJ1gHp1dnPic3h3eHl7eXh4eXt8fHt+fn99fYKDg4WBhIKAgX+ChIJ/goF/g4OFgoaDg4OFhIOEhIKGhYSGiYeLiIuOjImJi4eJiYaHhomNio+UjIqIhYWijIyNjIuKiYeEiIiKh4iMhoiKiYqLioiHjo6Ah4qJi46JjYyNjIqJioqIiomLj5WNjo6PkJKNi4yMj4yNjo2VlpGNjo2LjJCQi4mKjoqNjIqLhouLiYuLiIuNnpGKiIuKiYmIioWKi46NjY2Ki4yGh4iKg4eFi4eNi4yJiI6PjIiKjIiLiouJjYyLi46MiYeLjo2Lj4uOi4uKiMGAjY2Nio2Ji46OkI+Tk4uLi42Ni5CJio2Gh4uHiYmNhoeIioqHh4mJh4iGh4aKiYeJhoeKhYSKjIiCg4GBgoKHh4aFhIeHhYeGhH+AgoaFiYWIkYmHhoWBiYqHhISkn4SFhoeBgoSFhYOGh4iGio2JiIaGh4uOiYeRhYaMjYuLj4+AjYiNi4+GhIqIio6Nj42LiouIi4mIioqJioyMjIqdkZGHiouHhoyOiI6Pio2Ni4qOjZCVjpGQk5KWj42LjYrIkpKNj5GekpCSlZCajo+LlJiSj5KUjZONjpKRj5GTj5CMjZOPkJOTjZWPlpqSjIyMk6iUjI2NkoqRkYmEkJWHh4iAhYWIh4eJioiIhoSGhomIgoSAgH6AgYF/gYB9fH1/fYGWgIF/gKx7gYl/h3x6eHxjenp3enp8jHp2dnt+enZ6e3V0e3V5dnN4dHR1dHFzdH5wcHFzdHNz3XFydeLi4N3YndHb0XPLw8LIxcnMzM7IwcrGycPGycXBdsHNzcDIv8uAvca1vb7Au7qzsKe3tLO2r7Wkr66ut7asq7GDs6eenaGmoKWppLOdu6Whm5qfnZ2amp6gm5ygo5ufoJ6gpa+slpianJ2cmpuZnpycnJmZn5aVnJ+jl5ySlpWQmp2XnZuhopaVmZaXl5WSlJWSlZqbmpWamJqhmpyfmJ+ir6agop+AoNmen56el5uYlZCVpJuUmKCgnKadna+trJyfpKmntZ2fnaChnp6jnpuXpZ6Zpqyjp6iqpKOvoqCeoaWen56lup+jqLSfoKCiqKiqoq2kqKemqK2tqKGiq6qqrK6vp66vq6etr6mkpqnEr66ssK+nqqipra2rrLisrLHBrbCuqK6AsLGetLCxsK+tr7CztK25tre1t7e9t7u1t7K1tbzBwLe2uLu3ub+7ubPSxL3BzsG/wb/DxcS8xMDHy8HHwL7aw8PDwMDDyMjKzdPZ18jMx8nM0s/LzcXQz9PV1tPW0M7JyM7P0NLU09ja3dDT1tPe297f1tra39nU2tvc3uLc4eeA29Pa4N/e3Xh1dXh0dXR1fXl2eXbqeH9+fn+AfXl4d3h3eHZ4enx6eX59fH57fX17fXx+g4B+f35/gYCCfoSBhIOCgH9+en2BgoGCg4GGhYqMi4aIjYiKjIeIhoeLiI6WjIyKiIecjY2NjIuLi4mHiomRi46SjI+OjYyMiomIjo+AiYuJio2GiYmLi4mMj5COjY2Nj5CLiYqLjI+JiYqJjYqMj42XmI+KioyLjZCSjIeIjYmLiYeHgoaIh4mLio6Pp5SLiY2Oj4yMjoiMjJCOjIyJio2HioyNh4uIjomPjIuIhoyLjouOkI+VkI+KjYmEhImHhoGIjIuKjYiMiomKheiAj46OiYuHiIqJi4uSl42NjZCQj5OLio6Jio+Ki46RiYuMjoyJiIeHiIuJhomPjo2SkpKUjo69lI6Li4qIiYuRkY+Oi4+PjpCQi4eHiYyJjYqOk4yLioqDjI+Liou2rY+TlJaPj5KUlJGUmJmWnaGcnJeYmJuck5Kfj4+XmZeWnJ2AnJefnaSZl56bnp2cn5+foKShpKGhpaSjo6OkoqG4qKykqammpayqpKqqpKWlop6mpauxqq6ts7G4r62sr673trawsrLDr66rr6q1qqqmsLOurK+zq7Srq6+sp6mooqWipaqoqrCvqrOss7qvp6Wor9izqaqqrKGnqKKbpquen6GAoKKjop+gnpyfmpeamZ2emJuYlpSWnJyZnJyamJqemp2+mJiVl8KRmqmZn5OQi5CLiomFiYqKpYqHhouPjIWKioWEjomPjYmPi4qLiYWHiaCGhYWIh4aD/ICAgvr79PTxvuf07Yrt5ubu6+/x7+/l2+HZ3tvh5OTml9/s7d7q4vOA5OrY3+Hj3d3Y1cfY1NTWzNbAzsrK0Mu/vsaCx7u3t7q+tbm7ssKrzbW0r7O5t7m0srCyqaanqaSpqqOpr6+ymJ6hpqmqqamkqKmmp6WqsKensbO5p66kp6Sgq6umrKqwrqGhpqOjpqiop6ilp6mio52oo6Wso6GhnqWktKmgo5+AoO+cmp2flJqVmJudqKKanqKinqeamKyopZabo6iouqCkoaamo6Cno52YpZ6Yo6mfpKWmoaKupaWjpauhpJ+lwJ6mrLunqayvtbe3rbmtsa6srbGwqqWmrq+ura2uqrCxqqyusKygnqPCq6qpr7Strq2pra6qqbWrrbbKq6+upqiAqau1tLK2tbS2trSysay4tbSxs7C8srSutrK3tb3EvrewsrWvtLe2s67CubO6yrW1trW1urmxubbBwrrEwcDhxMLGwL28v8HCv8XPzMDBv8HBycbHx7/KxsbHyMXJyMrIxc7R0dLU1dfX2c7MzsrR0NPVz9Xa3tbS2drb3ODX29qHfY1+AX3/fv9+on4BgP9++n4Bf6d+BH1+fn6FfQV+fX19fpN9AX6hfQF+/33ofQF+/X0CAgQAPPzy/f6BhoKBhIOEhISFhIeGgY2HhYWJhYSFiYeIiYuKjIiJhoWCh4qajIqHioyKhISKjI+UlZKUmJGNjYSMWYuNjI+PjpKTlJORl5SWlpiWnpmcm5SUl5KVlpOamJqYmZeZl5OXmZeQj42TjpaUkZSYk5WTjIuMkYyHjI6Mk5OWl5OWlJqWlZmUlJOSlJSalZyYk4+bkpSUhJWAj5aWm5eZnJ+enZ2Yl5uVj5WUmZqalpOTlpWUl5mbnJiTlJKZmqCmlKagmpuXmZWcpJSWl5aYqJ6eoZmWm56anZ2dlpecnaCjoqGjoZuWm5mVl5SZm52doJ2dqKOloJ2anZ2foJuanJ2bmqSoqJ2go5mjm56XmZ2cl5uampeZmp+AmZaYl5aXk5OPi5GNk5qUmJqblI+PlIuRupmVmZKUkZSUk46QkY2IjYqOj42Li5CNgoyMiIiHhIKDfYKGh4iLi4uMjI6QjpWTkZ+OjImHiYWEgoSNiIiMiY2Li4yGhoaJg4eEm4mIhYyMi46QjYiFgoSBfn+Ag4R+f4WDjIiFhIaAiIaJjo6NiIyTjoyOkI6Mk5CXkIyMh4iIkIqLlo2RjIqOlIqMjYeOjIeGg42UjY+PjpOOjIqNjYmMg4aGhIuIhoeMio+RkY6Zj4+Oj4+Mi42MhIeQjIiLk5KIiJGGhYmIi42RnJSSjoyFgYKBen18g4WIh4SFgoaMhIWFhoKAgYiAgn6AgX9+enl5enh2e397fnx+jX55kHl+enlzd3l0dnB0dXZwcnV1d3Z3ent5fYB6fnp2cXRwcGtv2t3a29jccIhxyNDP1c93ztDOzdHPy8/WzMfCxcrJxcvScM7O23nGPby9t8C2vby7sW+70K+3srS7t7nJurOwuLSqnqG9sKWApKaooKWjnaitpKOlpaGorairqKWmrK6gqqatr66moKmqppmepqOko6SeoaK1naKisrKtpaurpqCjpa2ppKuysK24tqaqpaWnsaumpaSoqbSqpJueuKWkrailqailo7Glpp6ip6Kdqq+puMW5rqqorrWuqrCsqKior6WpoaWwrbKAu7G7rqq0sLWxrq60wLu3t7e2ubm6s7bEw7/evLO0tbrBvra1tLm0tLGzvLm2u8HEx8TMwby0tcK4xMXIz8TR1cDOxsXFzsXIv73Cw8TDwcjCwMTCt8Cxsbi9t7u2x8zDxr/Ezcvax8TGxcbFxcPBu8LCvcjEwMLAxMnGztHGwdFj59rf0eWUxMny1s7Lz9LVzc7T1dPV1M/R1tzW5tro8trX3NbY5d7j4fbo5+nj3tra3eTo4t/p6+zp4uny7+/r6/D66vrz7vDv5uLr7uje5e3t4fLz+Pn+9fPx9fH2/vr39/2AgOng6up4eXRydXJ0c3J1dXh2c356dnd7d3d3fHl6e318f36DgIF+goSRhIJ/goSCgICDgoKEhYKDh4KAgoOGh4WHiYeFhoWGhoeHhomHiYmJiI+LjoyJiIqGiouIj4yOi5CPjouJjpGPi4yNkI2Tj4uLjImMjIeJi5KPjY+OjZCPNI+PjI2MkI6LjoyMi4iMjpKOlJGQipaOkI+Pj5CRjJGQkoyLjJCQkJGNjpKNhouJjo2OjYyEjTmPjo2SkIuLiY2MkZiKm5OOkIuMiZKdjZCPjYuXj4uOh4WJjIyOjY+JjJCPkZCQj5COi4uPj4+QjZCEkYCPjpaRko6Lio2Nj5CNjY+Pjo+WmZiPkZOMk4yNiouOjoqOi4uIiIqNi4uMjIyPi4uJhoyIi5GKjI2OiYaHi4OGqIuHioOGhIaIiYWJi4iFh4aKiYiEhYmHf4iKhomIhoeIhIiLjIuMioeGh4iIhYqLiZ+LiouMjoqKiImMiIaKh4CMi4uNi4yMj4mLiJqPjYuPjoyQkY6NjIuNi4uNjY+PiYqQjZOPjY+QkIuNkJGOiYyTjoqOkI+NlZOYko+QjI2OlY+OmY6Rj42OlI+VlZCYlpOPjJaakpKUkZaTkJCVlpSXkpKTkJeTj4+Rjo+SkI6XkJGSkpKQkZSSi46WkYyNloCYiIqVioiOjI2MkZyPjYqKhoiKi4aIhImLi4eDg4CEiYODgoF/fX6FgX+AgYGBfXx8fXt6foB8fnt7int3knh8e3l2e317e3h8fn53eHl3dnNzdnVyd3p1gHh4dHd1dXFy4ODe397gc4l0z9XV2tN20NHMy83Mz8/YzcvIxc7MyYDJzm3Hxth3ymDBx73Eur25uLR7t86wurK1vLaywa2qp7azrKKevqugoaWopqeopq2ypqaopqCip6CgnZqdoKaZoZ+ipKOdmaGmpJebo6Khn52boJ+ym5+gpaOcmpybmpaZl56dlpufnpuhopeal5icpZ6amJmXlZ+bmZOYs6Kfp4CfnqKjoZmlmpyam6Omm6GgmaOuopmWlpmin6CupZ+hn6SeoJucpKKkqp6km5mkoainpaOmrqqloJ+goaKioKKtranLqZ6eo6itq6Wnp6qmp6WkqaWjp6eprqqzqaymqLOmr6urraSstKm2r66rs66yq6+0tLGsqK2rp6uuqrWsroC0ubKxqre8srKtsLWzwrOtr7Czs7O1t7S7urO9ure5t7vAvb/DuLK/zMbIu9KgsrfaxLy5wcLIvL/Gw8TCxL27vsLAzMLY5sTEyMTFz8fGx9XLzNHPz8jJzMzPys3T09DS0dTb19bSztLb0t/Y2tzc1tng49nS19rc1N/h4t/k2gvb2d3Z3ebh3t/kdUfb0tzdcXdzdHp4enl5e3d6eHSBenh7fnx7e4B8fHt9e317fXp6d35/j4B/e4CCgHx7f35/gIB9f4SBgYeGh4aFhYSCgoOAgoSDgIeFhoaGhY2IjIyHh4mEiYmFjImLiYyNj46MkJaTj42LkI2Tj4uNk42Sk42OjZSOiouLiY2Mjo6MjYqPjYyPjY+OjZCSlpGXkY6Hj4qNjY6Pk5OOlJSVjo6QkI6NjYqMkIyHjYuQj4+MiomIh4aKjY+Uk42Ni4+Ok5iLmpKMkYyNgIuUnYyOjYuJmZCNkIiGio6LjYyMiImNjZGUlJOVlZCMj42JiIWIiImHiYaFj4mMiomKjo+RkY6NjIyKi5GUk4qMj4iRjZCNjpGPi46MjIqNj5SQjpKQjpCLjouIjYmJjouOkI+Qjo2Qh4rqko2SjJGPkJKSjY+Tj4yQjpKRkI2NIZKPh5GUj5CPjI2OiIyPkI+TlZWUlpeXlJmYlLCWlpianYSZgJ2cm56YnJydnZmbnKGanpurnpyan56epKakn5+fop6boKKmpZ+hpaSsqainqqunqa2tqqSosaqnqq2rqbSxuLGtrqipqrStrbyvs7Cvsa6yt7eyubawrqiztq6vrquwrKmpra2sr6qqrKetqaSmp6Smqainsqmrq62urK6zsKmugLaxq6+6u6mor6SipqSmp6y6rKqnp6GgoqKZmpeenZ6amJyYnaKen5+gnZydpZ+bm5qZmJOTkZKQjZGSjpCNjaSQiq6Nko+MiY2PiYyGi4yNiIiMioyJiYyNio2QipqMjoWJhoaCgv7/+vv5+4GTgebt7fbyjfH49PT49fX0/OzogOLj7ezm5u6A6er5kO6Z5+rf6d3j39/aodj6zdLN0NfPzeHMyMTSzcS5uNjJu729vra2tq63wbe3ubu6vMC4uLOsrLC1qa6ssrWwpaCqq6icoq2vraurqKypwqSpqbW0r6mvrqulq6myrqiws6+psrKkqKOkpqyjoaOfo6KupaSdgJ7WqqizqqajpKCbqJudmJqjrJqgqKOwuq2gm5iZpqOkrqajpqKoo6WeoqSprbOqr6OfqKKmoZ+fpbGtqammp6mqqKSmtLGu5aygn6KmqqmhoqOrqqurrrWxrLKzt7q0v7Ctpaexq7a1t7astbiotK6trbeusampq6+wrq60sKyygLGnsKmprrWurqm2u7Gzq66xsL+xra6xtLOzsrCstrOttrCrq6ersKuxtK2ouM7IxrbStaquzLuzsbi6vbS4vby7vr65ucDCw9HI1uHMyMrDwtDExsbXz8vOxsS5u77FxsLFzMjHxMHFzcrMycnQ18vY0M7Q1NPZ4eTc1tjV1cnWENXV1dfS0tDX19vi3dvZ3W+Eff9+/37/fr1+AX//fph+hn2DfoV9AX6SfQd+fX19fn1/iX0Bfv99/32cfQF+3H0BfgICBAAu+/2A/IL6gYGEiIOHhoWFhIeLhYmIhYOKi4WIh4eNh4yMjIuMi4yQi4+MjZGUlISRgJWTkpafppuVkpmMjpOPkZaVk5aXmp2enJaXmZ2gm5qZmJWXlZCYl5qanJWXl5qbk5qSkJWWm5STlpePl5qXlJSUmJSRlJGTk5KXlZmYmZmcl5udmJmVlJiTmJmam5+bmpiWmpqcmpuXm5aVlpuanZ2XmJyenJ2blpSUmpSZmJ6mgKSiopuco52gn5SRj5aYnJmimpiZm56UmZyWmaCamJqbm5+epKGmo6Skpaacn56dmqKfo6GkoJ+lm56clp6bmJ+dm6Gjo6ykpqKen5+fo6SknJ+hop2jpaGlpKGan5+amZicnZ2bmZ2gnZ2ZmrGqlZSXmJWUkpKUk5SenpyXl5qdgJyVmJmblZealpeYm5mWmJCMi4eFh4qPiJGQkI+Oj5CKjY+NkaOOjIuJjpGQjo2OjZCWk5WWkZKbeY+Jh4mKko2KkJKXj4yLjIuIjIuLipGIkJGNj42Ig4yIiYiHhoaEgoCCgoSHgoiCg4WLiouMjo2QkouNkZCNh4yQkZWjh4SQgI2LjY+PjIyNk46OkpKOjZCRk5KOkY6SiYuOkYuSlZGLkJCVjY2IhZOMi4iKjY6TmJaTl5yXlJOQj46QkYKKl5CEk5GQioyRi4ibio+Lio2RjomHjoeOi4J+f3+BhYWLl4OEhoWHh4eJhYSIg4KDh4h/gICAhIJ/f4B5e3x/hnt6gJl6enR7dnt2eHV1dnp1bnF6eHh5eHx9gnx1eHZ3b3Vwc3Bpbtdu4HLh59jmeN3a2eVz4dfV0WxozMvP0s/Q1c3IxMfK09XVzsjBxr7HrsHBvMPBwrm9ycWuyrKuubS3usG2qbGsqaSlpKmrp6aqpaysrKiorqCmqqerq6CgppqegJ+hnZ6joaOinamvsqmmo62lqaOfoMWnrKipn8fIsqmjqqSjpKCuqaemqaimtKmxr6+tpbKrpqqqqqamqKKfoKipqKelp6SktpuenbKmqp+gsLCspbK9trKxtKixpaemybaqraqqprCyuLe3u728s66tuLe0r7Cztra9tLGwtbW5gLe7ycjCxcDNxb+4wL3Gu7K1vLa+vMW5uLXLw7jEwLm4q7i8vbvBxMDAvcPNysnP0c/EwcK9vsbEx8rJysbEwL66t8HMv7nExsfSx8XDwMDg4cfHzci+ysHDvbq/y87UxMzQ0MrE1IDOxMbOid3S4ODOzNrWyszNyMPT3NvQ2t7eTtDU0szW1N/W09PZ2+Tn5uDl4d7b2Njc39vX3+npmd/k4+nz7+7y8fDu7enw96P3+/Lz9/Tp6O/u6ujq6u7x7/Tx8PiD+fb4+/n69/6AgoDn6Xbpeep3dHZ6dXd1d3d2eH16fXx7en5/enx6eX57fn6AgIGBgYWBhICAhISFg4aHhYiGg4OJioeEhIyDhYqGh4qIh4qMjIuNi4qLjZOUj42MjIqMjIeMio6PkYuOjZCTjJKLiI6OkoyKjI6Gj5GPjIyOkI6MkI2Nj42SkJKSjxCNj4uOj4uOjIuQjZCPkZOVhJCAk5OWk5OPk46NkJWTlZOMio6PkZGQj4+NkYuOi46Tk5KSio2UjY6Qjo2Jjo+RjJOOkJKUlo2RkYySmpKQkZGOkI+RjI+Mi4uOkYmOj4+PlJKTkpOOj5SQkZKQlpWPlZGOlJOUmpOUkIyNj46QkZOMjo+Pj5OUkJSUkIqRk4+Pjo2Aj46MjI+Rj4+LjJeYjI2QkpCOjI2OjY6Sko+Mi4yPj4qJioyHh4qGh4eKioiPjIyKiIeKiY2HjouKi4mKi4iJi4qUuIqJiIWLi4qIh4eGiI6Nj5CLjpl6kIqJjIyTjIiOjpKNi4yOjIuOjo6MjYeOj4uPj42Kko+QkI6QkpKQj5CAkZKRjJGNjo6Sj4+Oj5CSk4yQlJSSjY+QkJegjYuYlJKSk5KQjo+VkJGVlZGTk5SWlpSWlpiSkpWYkpialZCWlpmVlpKRnJqYlJOUk5WYlZGSl5aVlJKTk5WWh4+akYiSj42Jio6Khp6LkYuLjo+NiIiOiJGSi4iKiYiLhoqYhIaAhoWHhoSFgYCFgoKEho1+gIB/g4J/gIF9f31/hHt6sHl7d315f3t+e3x+gX51d317enh1eXp9dnBzc3RwdnN3dXFz5HPodOHk1+J12trb5XPi2NfRa2jQ0NTY1NvczcfDx8TQzs/LxcDAw821xMO/w767uL3KybTMtq63r7K0u7iAqre0r6unpKmkqKippKqsrKmprqSnqKSlpZydppyjo6anpKeiop+YoKaspJ+dpZ6gnJmbu6KnpaOYta+hmZOcl5qcmKajn5ubmpefl56dn52aq5yan5yal5WYmpyboqOlo6CjoaC7mp2dxsmkmpiio5yYoqegn5+moKeen6C3qaAHpqKin6ekooSggJ+Zl5iio6WjoqKjoqeamZqgoKOkqK2spqmnsKuppKyqsqylpqyor6q0paWktK6ns7WvsKewsa6sq6umqKKosK2vrbGwsLCyrrO3sbKvr66tsK+wsKyxvbGssa+0v7Sws7Syycmyr7i0rbiyuLSytr68wbe5vby6ucRwwrq6xHfKYcXO0cG/y8nBv7+7uMLFxb3Dx8XAwsC8xMbNxL/AxMbOzs7HysjKy8jL0tTQzNHU1Y7Jzc/T2dbV1tLS1NPR1NeM09vW2+Lh2tnb3tva3Nja3dzf3t3keOLd4eHg4OHlc3aA1ddv2nDec3J2e3d5eHh2dXV5dHh3dXN8fnh9enh+ent+f4CBgoKHg4SBf4GDgn+AgIGFg4GCi42JhISPg4WLiIeIhoKFhYeKjoqHiImOjoiJioqIi4uFioeMjY2IioyOkouUjIyTlJmTkJGQipGTkI2Oj5SRjZKLjIuIjYqOjYyAjJCOkZKPkI6LkIyQkJGTlZGRj4+QjpGOj4qOjIuOlJSXlY2Mj4+OkpGPj4+TjI6LjpKSj4+HjJOOkJCOj4yRkZSQlpCSk5KTjI+Oh46WjYqOjouOj5SRk5GQkJKTiYuMjIqQj5OSk5GTmJGRkY2Rj4qOi4eNjY2XkJOQjZGSkpSAlpiQkZGQjpGRjZGRjomPkYyLi4yOkI+Pk5WRkY6PoqCRk5SWko6Li42MjZWTkIyPkpaUjI+SlI+RmZWVlZmWk5eUlZORjpSUmJGamZeWlJaXkJCUlKXalZaVk5qbnZqZmZidoJ6gopyerYufm5icmqGdmJ+fpZ+dnqGhn6Siop+AoJuho5+gn5yZo5+ipKSlqKmmo6OkpqagpqGjpayrrKipqaqtpqqvr66mq6yttcaopbWwrK2urqqpqrGsrrO0rrCzs7W0sLWxtaurr7GqsLKvqLCwt7GxrKi3rqulpqemq6+uqq+1sbGxr6+wtLSirbuzqLaxrqaprqijwKWrpKOApqimoKCro62tp6KjoZ6hnqKun6Oko6ShnqCdmp+bmpudoJSWlpadnJmZmZOTk5SYjYzFjI2HjoiPiYyIioyQjISHkI6OjYqPkJOMhIiHiYOKh4uHgYT/gP+B+v/v/IPz8fD+gP75/fqDgPv5/P73+/nr5N7k4vDw7ejg2d3h7tSA6erl5t3g2d3v68/hy8DMxMjL0su7ycjGwsC8wr69ury2vb+9ub7Gub3Bvb+8sLS6rbCxtbGws7Cwq6KssrKrqKayrrKura7ytbq2tKfJw7OspbCoqKqmta+tqquoo7Clqqepp6K1oZ2ho6ShoaKgo6WtsK6mpKejqMqfoJz/7qKAm5uprKeeqqyinaConKWcoaC9sKitpKWjq6mqra2wsrKppaKsq6qjpqesq7OqpaWppqimqbGtp6qmsKynoKamsKehpK6qt7TCsa+tvrOqs7OsqZ6oq6yrr7Swsayut7S3ubaxq6iqq66yrbCwsrCusa+tq6mxuKuntLe6y7qzsa6Ap73Frq24sK22sbmzrrS6u72vtLOyrKu3bLWsrbZ9zrK9urCrvryztLazrre+vbS7v8G7wL67xcXKv7y9wsXLzs7HysbExsPDysvHvcLExHe5wMXHy8jJzc7Q1NXR1dZ9zNXR1tzi2NXZ19TV09PU1NLQzcrUcNLS2t3a3Njgb24GfX1+fX59/37/fuZ+AX//fux+BH1+fX6EfQF+hH0BfoR9gn7/ff99sH0BfoR9AX6xfQF+j30BfpV9AX6IfYJ+AgIEAID7+vX//YL///uDh/78/4KFlYiChYODhYaGio6OjZGRkI2OioqJiomNjYyMjpGRl5SYk5SNk5eWm5WWlJaQlJCXlZOTmpOXm5mXmpmcm5iYlpKQlZmYmZeUlpubmZebnZ6cmJ2hlpSYnJmblJmdp6ScmJWTmZeZnZqalpuWlZeYnICbm5Wcm5qYmZCal5ibnJmXl5iZmZubnZ6gnZicm5udmJyZmZqdnKCkoaCkmJqXlJednaCmpKGgmZWanJaVmZ2hnqegp6KgoKKenZ2cnJydnaCko6Wlo6Whn6KcoqedoqGgnaOjpKOqoqaknJ2hmaGgnp6Zo6empqenqaakpKSpqiStsaejp6inpqmjpaWrpquoo5yko6Sgmp+en5ufnKCYmp6kmZSElYCTlZqcm5mcl5mfn5idmpqTmZmXlZmSmJWako+QkZKNkJCOlZGUkpOSlJmRj5Cbjo+SkouPlY+UkJKUi5GMjomOjpOVkJGLhY2NkpGSkpOQlZCUjZGSjo+KjZGRkI6QkI2QlY2JiIeJhISEg42GhYiLiYmNi4qLjI+Pjo6NjI6MlICPlIqLjIuLjI+PkJSQlpmTlJGPoJWflpCQkJKTlJOdlI6KjY2Njo+Tk4+Qj5CYkpCMi4uQjYaJko6Rk5OUk5OTkI+TjZWOj4qJkJGPlJGUkpSRj5CUkZCOj4yRkIqLj46DpISHhYKHiYqKg4OIiYiKjIiFhYWEgpmHiYKDf3+KhYCFhYR+gIGWh359fn1/fYR9fXiBfYR9e3d8gX55eXp+gH1+eXR3eHdycnKXcXB2dnV6dOrm4XfldObq5d/g2NNsamnObHLMzc7SycfEyMnD3JPVysfIwNTCw8R4bLi5t7m8vbnhuLa0urW7u6+qrrC5sLGmrrCypq2spa6so8Gug4CprLGpqqy6pqinpaWhoainusmlqaCvrqeup6WnoaCks5ybmpuZo52hn6uutK6sn56moKWusaWwrbWpua2prqqkrK+4tKippJ6mo5+so7Gpp62ptcO1bqqhqqCxubKrn6qprrGjqaekqqexpKOmta+rqq61sMbCxLatsby8tbKzroC2tLWvtbe+vbi3vr6Y8cXCt7W8u7O5xL7H0su6wLTAyby6tsLRvcHHzcPGx7/IxcfCwcrHys/Iy83G0MjFzL7ExsjJx8bIzMjNys7Fz8jUwsvGxcDAxcC8wsjJwMmB8L3GycTJw8XEvfa7ytLQ08zN0szLy8TK09LM29PU1tXP2lje19HS193Uys/SzMfNy9Ph2dPe3eDf6eTg39vh4uzZ6d/g3drW2OX83ePt6uzy8PDt+PD08vf++vn29vX6+Pfv7urs+PH1/vmA/KH59vz48fr89ICF8/38e+bn5e/uevDw7Hp/7Ofpd3mHe3d6d3d5e3t9gIB9goGAgIKDgoWFhYeFhoOEhYOKiIuJiISHiomJiIiHioaJhoyKiouOiIqMiYuNj4+PkJGRj4+RkI+PjIqMkZCNjJGPj46LkJWLi4+Qjo+Lj5CYlpCLiomMjI6UkpGPkoSQgJOSkYuSkZGRkImSkpGTlpSTkpOUlJKRkpSVkY6TlJOXkZaTk5KTkpaXlJSYjpCPjo6PkJKVk5OVkIyQlpGNkJGTkZiOlJOSkpSUlZWTlJaUk5WXlJOTkZKOjY6MjpONkJCQj5OTkpGVjJKRjY2VkZiXlJKOk5WUk5OVl5ORj4+SJZGTl5CNkJKTkpSPlJOYk5aTkImQkZOQi4+OkI2Pjo2LjpKakY6EkICOjY+Pjo2PjZCTlI6QjY+JjY6Ni46HjIiOiomJi42KiomKjoqLi4yLjpOLiIuUi4qMjoaKjYmNiYyRiI+Nj4qOjpSXkZCMiY+NkZGTkI+Nk5CTj5OUkI+KjI6QjouMjpCVmpWTlJOVkZKSk52VlJSVkpCUkpGUk5KUk5OUlZWTmXOSl46PkJCRk5SUlpeTl5mRk5OQnpiunZaYlpiYmpmim5aUmJqdnJ2gnZiXlZaem5qYmZuhnJWVnJaWl5aXlpeXlJadlJuSk4+MkJKQlI+RkJCPjI+SkI+NjYqPj4uMkZGJpYiMjIuNjIqLh4aJiISFhoOBhISAuIiIgYB8e4eChISDfYCBlYV+fHx6fXuBe3t4gX2Efnx5eoF+eHl3en17e3d0eHh3dXV1i3R1eHh4e3Xk4t1z33Hj5eDe4NnUbWxr0m1zz9TV1cjGxMXHx9iS08nIxMLWxsTBgm+9vLq8u7y32ri1tLq2t7qvrK6xubGxqKurq6GAp6ilr62mxK5zpqaqo6WksKOkpKCioZ+mo7vMo6idrq2mq6Oho5yaoLOdm52fnqOanJmgoaelpJmcpKCjra2goZ2jmaSZl5qXk5mcoaGZm5iYn5ydqKKupaOmprPBr3O2m5+XoaWfmpCbnqSknaCem6SlqqCjpK2ooqGfop2sr7iAnJWcp6ehpqWgo5+empyfp6qloqephsCsraamqq2lqa2nrreypK6nsb2sq6auuqivr7Gprq6mrrGwqqq0sLSyqrKyr723tL2tsrO1tbKwsLSvtrS5s7ezvK6zsbOxtbi1sLe7urS4bc2vuLy8v72+v7TesrvBvr+9vMPCwsS/wcthyL/MxMPExcHKy8fDx8jVycDHx8PAw8HH0sfAycnJytPMyMXFx8rUx9XU1dTU0MzR5MfN19LQ1dba2N/X2dTT2d3c3d3a4eLi4dnY1uLZ2+HfcuWM6OPr5d7n6OF1fN/k5oDa2dff3XPk5uR3fujl5nZ3hXl1eHV1dnp6fH+AfYGCgICBgX+DgoKEgoB9f4KAh4SIhYaChomKjImKi46Hi4aNiIWFi4SGioqLi4yLioqJiYiHioyLjouJjJOUj46TkpKRjpOYjo2QkI+PjJGVoJ6ZlJSRlZSUmJGQjI6KiYyMkDGRko6Uk5OQkoiRjo+Sk5KQkJGSkpCOjpCRjoqSlJOYlJiSkpOTkJKWk5GXj5GQkJCShJFukpOPi5CWkI+TlZiXoJWZl5KPj46QkIyQkpCOkZOPkJCPko+Qko+Slo2Qj46Mj5GRkZePmJWSkZiSlpWRko6SlpWVlZSVk4+PkZWXl56Uj5GTlJCRi46QlpKXlZGLkZCSjouRkJOQk5OUkJOXnpWEknuRjYuNjo+Ok46PlZSOkpGUjZOWlZKWkJaSmJaWl5ibmZmVlZuXmJaYl5qbkpCTnpGUmZyUmqCcoZufpJuhnaCboJ6lqKChmpadm6GhoqChn6ahqKOqq6eooaOmpaKen6KiqK+npaWmpqKlpaayqainqKenqaiorK2tq6mEp4Clq6atpaeqq62usK+vr6mvsqitray/tsy8s7Oys7S1s7+1sKyxsrS1tbq5srGwsru2s6+vsbexqauyrK2vsLKwsbKxsbiwua+wrqyytbS5srGur6qmqKyrqqenpKmnoaSoqqHYo6eop6impqahn6WkoKCinpydnZ6d6KKkm5mVlYCjm5yenJSVmL6akpCRkJOPl4+OiI+NlI2MiY6VlI+RkJSXlJCKhIiJiIWGh6OGhYmIhoiB//33gvuA/v/6+f348oCBgP+DjPf39fbq6Ofm5eP3qvHn5eLe9+Le3biF2tzY1tXVzOnJx8bOyczNw77BxdLKzcPJyMq8wcG8y8a804DFoLq8v7i3t8CytbGvsq6utLLI3Kywo7OzrbavsbeyrrfasK6usa+4r7Oxu77AvLurqK2rrLizp62psae1pqCinpmjo6qrpKampKylpayjr6ehpaPF6MiNzJ6noKyxqqWbpKOmp5yhoJ6pqK2jpKSspaGgoKikvb7Iq6CkrayjpICkn6ajpqGmp6+spaOmp4rGr6+lpaqqoKSupq+6uKmvqbTEsKyttcmyu7i6rq2tpa6tsbCyvLm6u7OztbK/tbC3paurrq+tra2zsby1t7K7sr+vtbG0sLO2tK61tLWtr3HFqK+wtLi1ubiv2Kq1vLe5uLm/u7m6sLK5tq67tLS3t1qzvcG9ube7ybits7W1srm8xdPEusPAwb7IxMG+vsPH08HPycrGwL27w9O7wcnFxMrKzczY0tTR1NjW2NjV09Xc3trW1NPi19nf2nDYe9fW3NbQ3+Hac3rc4N2FfQl+fX19fn59fX3/fv9+/37/ftB+Bn19fX59fod9Bn5+fn1+fot9AX6JfYJ+o30BftV9AX62fQF+0H0Bf+p9A359foh9BX5+fX19AgIEAICBgPr7gIGB/4CCgYaDh4yGiYSFiJKMiY+MjpGTk5GTlZCOjY2JjIuxoo+Vk5iWmJedmZeYlZqanJuSnZaQkpOUmJacpZyZlqCfn6afoZ+anKKenp2ZmpqcnpudnaOhoZ6fnJaWmJqal52bm5mcm5udnZqdnJudmZiam5qbm5ufnoCenZ2eoZ6gmp2dnJybppipoJmdm56gop+ho5ugn6Cho5+dnZqbn5udnZuhmKCho6GgqKKhp6i3np6cnZqno6KnqKytp6OgnpyfoJ6gobOnoqOeo6eioKSlpKyrn6impKSkqKGnqKalpKqloKKdoJ+jpamorKWqo6eqq6+rqqqtrymztLGto6mlqKelp7qnqKmhpKCkq5+ipKKkpKOhoJyanaGgm5uem5yZmISagKOfnZqbnZeTkZKRlZaXlpeamZ2Yl5GQjo2NipGTlZCUkpOVkpKVk46RjpOPj5OPj5OjlpKMlJSRlJSTkZCOkZCKj56LjYmOj5SWlJKTlJKOi5SSj5CMlJiRlpWTj5WLiYuEhIKHhI2HhYmJjYWJj42Sj5+VjpGPkZGVlJOYkpKSgI+OjI6QlpiWmJeTk5SOkJWTqI+MiYuYp5GWko6PkZGUj5STlo+Nj5OTkI+MiIuKiI2Pj4yOkZaXkY6PjZCRlJWalZWTkpSRlpiXkZGMko2MjI6RkZ+UjpWSkY6NkoOOjJCYkYuMkIeOjIuIjIaIhYeIhoqohomIhY6HhYuNg4B+gICDgYF/i46Fho2IhIOFf356d3FzdXt5dnuBe3+SfXdzc3N3enR6eXCGfnl2f3d0cnR1d3d0cHHg3NtubW5scXF40cvJ0MzQ3NHFycXMwcrUzNLOxsO+w8nFxbm9yr24uLmswLq8vr60sbGysra1uLOys7SyrLKrn6mgoqastK+tHayyra+1r7itqayrr6yoraivoaKprLCnp6CjoZ+khKGAnpmdpKWxsqefpKShqaqZn6OlsKqsvLSup6i3v7mxs6ilpaOkrLGnramoqZ+mpaWbq6ito62yuLTq1ae6va+pprSknKOupqqmpqGlqrKutsXBubSvt7a6trCrsK6ttK6yura7vL7FwsPFvby8yLy7x76/y83M0crGwsvGure5v8CAusDLw8vJwcbH0MzDyMrJ0szJzMrJy83Fx9LB09HHw8nOz9TL0dnQxcXNxMTCwMHIwsHK0cjMyMzX1dHJzdDMzNDL0s7IysrOzcvB883Hxs3T0NbX3uTf1Nva29rV2Nzh2tfT1tnd29ze2+Xe4+nfhO3w6enh6Ofv6u3o3+Ph5uQy5Ojt5fKB8fX3+YD784D//4D79IHz8/b2+O/z7vLy/Y74/fv49fWBgv7/g4L7/4D5jviAeXnw8Xl6eu51d3d5dXmBe314e4CFgXyDf35+f39+f4GCgYODg4WFopGGiYeIhYmJjouKioaKiImLhZGMiIyLi46JjZKMjIqPj4+XkpWVkZKWk5KSjo+QkpSTkI6SkY+NkJCMjY6Rj46VkpSSk5OSlJKOk5CRko6OkZWTlJSVmJWAlJGSkZORk4+SkpWWlaOWppuTmJeYmpyampiRlJOSk5eUkZWUmJuYmZmWoZKTk5SQkJqUkZWXqZCRkJaTmZSPkZKUlJCRkpGRlZeUlJatm5eXk5eZlJKUk5KYmYuUkpGUk5mSmJiWk5KZlJKSkJiVmJmdmpuVmpOVlpealpOUlZSAlpaWlJCRkpaUlJWplpaXkpORlJiOkJGPkJKSkZGNjI+Tk46QlpWWk5CRj4+Nk5CQkJGVko+Nj42PjY6Mi4uMkIyOjI2KjpCLj5GTi42NjI6NjZCPjZCMj4yLjomJjZyRj4mQkY+TkZCQj4+QkI6TpI+SjpKSkpSUk5OVlZORmZd8kpSPlJePlZSSkJeTkpWSkpKVkZmWlpiWmZKUmpablqeZlJeUl5adm5iclpeXlZaVlpicnJudmpWWmpOVnp6xmJiVl6Cvmp6ZlpebnJ6boJ2flpSWmZyam5qanp6anZ2alpeZnJyZl5uXmpiamZuYlpKRk5GUlpaSk5GWk4SSgJCij4qRj5CNjpOIkI6SmpSLi5CHjYyLiIuGiIWHiIeJmoKEgn6HgoGFiIB/fH9/f4F+i4uDgomFgYKHg4SAfXl6e4B+fH6CfoSjfXl5eHh7f3d8e3OFh3p2fnVzcHFydHRxbHDg3NxubG5tcnJ61c7Jz8vS287L0MzTyc3PztPMgMjDwL/EwL65t8O8urq3qr+1tbm3sa6xs7Cxr7CsrK6wrqeyraSvqKisrbWrpKKkoJ2lnqagm6KgpqafqKOpnp6kpaihop2in6GjoqOkop2ZnJ6grLKjnaanpaqvnJ6fnKOcmqSenJqapammn56Xmp+gnKSnn6ejpKagp6Kkl6SdgJ+aoaeqpeHEmqyxp6SfrKWhpK2qr6iln6Kio5uirKmem5qhpamoqKaqpqWqpKatqqiprLGqqq2pp6ayqqiyqKezsLC1ra6rt7Stp6qsrqiqsq21squvtLqyrrKys7m0sri1sbK2s7W4rLu5saywtra4srrSta+yurSztbW2u7e2Jru+s7eytcHAvbm9vry7vru/wLq+vr/Cv7bXxL6+wcfBxMTIy8jChMlWxcnM0MrJxcbIzMbIx8nPyczQw3PR08/LxcjFzc3R0c7T1NnW0NTZzth03OHj33Lb02/a227Y13Tg4+Tj4d/g293f43vd4eDh3dxzdeXjdnXp6nbpguuAc3Lf3XByceFxdHJ2dHd9dnl1d3uBfXqCf35/gYF+foCAfn9/gICAn5WBhIKEgoaEi4eHiISKio6Nh5SPioyLi4yGiZCKiIiQjYuPiYuLiYySkY+Qio6Qk5eUlJOYlZWVl5WRkZOWlZOalZaUlZSTlpSRlpSUlZGOj5CNjY2Pk5CAkZCVlJmWl5OVlJSWlKKQoJeOkZKSlZeUlZWOlJSSlJmWkZKRlJaRk5KMnI6UkpWRkZuUk5aayJKVlJiSmZeUmZmfnpmWlJCOkpaSkZSolpCPi5CSjo6UlZScnY+XlJOVk5eQl5eVk5Sdl5WVk5iUl5aamJmTmJCTlJWalpaWmpsZnqGem5aXlZeUkpOuk5SVkJORlJmMjo+NkYSTgI+PlJmZlJaZl5eUkJCPj4+Wk4+Qk5iSj46PkJOVl5qXlpidmJuampianZicnp+am5qZm5mXmZiVmJacmZqem5qgsaOgmqSjoqampKGjoqKhm6G1mZ2Znp6jpqakqKuspqawrKqrpquvpquqqKeup6erp6inqqSsp6Wop6ujqLCtgLGrvq6oq6erqrCurbSvsbOys7CytLe3tLazra+0r7K8vMi2sq+vus+xt7GusLW2uba7ur2zsbO3uri4trS2tbK0s7CsrbC1trKusa6vr7Gyt7S1tLS3tbq7ubSzrbKurKupq6rHq6aura6rq7Kiq6mstKylpayhqKaloaWfop6ggKKhpL+dn5yXop2aoKOXlZGVlpaWlKunnZykoJiXm5SSj4qGiYqUkpCUl5GXwouFhIOEiZKJj46EloiJg4qDgoGEho2MhICB/vv7gICDgYaHjfzx6vPs6/nx7fLt8OPo7evv5+Hg2Nvi3dzN0d7TztLUxd7T1NbTzcvP0c7Py8vEgMTGycW8ycG0vbi5u8HLwrq2u7WzubK7s6y0s7i2rrixtaqstLa9uLmxuLSztrS2tbOvqq+zt8DCubK5trW3waOrramvp6Sro52enqm0sa2upqepqKGoqZ6joaWlnaOhopemoqmosbe3stPDnrG0pqSgqqCanqejp5+gmqKjqqWtgLqzqaSgpaWpqKOgpKOjqKOkraWnpKSppqaqp6mqtayosqilsK+wtq+vrLa0ramtsbWws7+1vrius7K7ubS7vLq/ubS1sq+zta6ru6e4tLCur7O2vbW61riwsL65u7y4trqzsLa2r7Krrra0tbG2trW0t7O3tbKzs7W2tKzdtq6uY6+xrrGyuL69try/vb24uLu+uLeyt7vDwMTDxsbAwce6c8fMycW/xMTLyMvKxMrGysjFxcrAxmfFydDUbtfPcNzbbtTMcNPV1drc2djY3eHjftvg3dbT0m9w2911c+PjdOaC4Qh+fn19fn5+ff9+/37/fv9+0n4Bf41+g32Hfv99/33RfQF+lX0BfoR9Cn59fX59fX59fX6LfQF+hn0Mfn59fX5+fX1+fX59AgIEAICDhIOCg4iHhv+IhIeFiYuEhYeFg4eHioyPkZSUl5OUk5aUkZWTlJKXmJiWlZeZmpiZl5GOk5aQkpKRmJSVlpuXmJeYnJmeoaammK2gn52YmZmgpaCeoJ2emZyempyem5+enZeen5malJahmJ2fnqC0n5ycmpadl6KdnZ6hoaOepoCoo6ClpJ6lpqSmo6SfnJ6fnZ6gnJyeraCeoaGjoqSko6iqnZ2eo5+bmqOfnJyfraegoaelpKCfm6KgmZuqoaOgoaSmo6impaOkpaanpqSnqaWgmqCcnqGlpqaopqalqqPK1aijqqapqqamqaPLoJ2qp6GpqayppKmlp6uoo6eopoCysrSysa2uqamstqepqqelp6mosK2oqqitp6WqoqGipaKbnaCam6OfnJucop2inqOZmZaXlZeWmZiVmpmcnpqfmJadl5SPkJOSkpaUmJOWlpSWlJOTkZGTjo6SjI+VkJOVlJKUlJCTlJSUkZCQj5ORkI2PjYmPkpaTlJSRkJGJjoCOkIqWl5KYj4+Rj4+LjI2Qi46HjI+QnYmTiYmNjpCWlJeRkpeQkpqYlJW7jZKPjYqLkouPkZSRlJaTk5KSl5aQiIyOmoyPjZOSlpSSk5GTl5qam5yYmpWSko6NjYyLkpSQl5STm5iSlY+Tk5eYmZ6cmpiZlZWWlpaXlKuSlYyVooCRlpqQkZeSkouGkI+EjJOQh4iLio2Ph4uMiYqKh4iJiYqIh4mJj4yPk5OJiYGEiIqCgoyKiIWHiICAg36Be3Z5gIN+gnuBgX1/eoCBe5d4fHiDfXp7d3qAc3JzdN1wcHHWfnDbcXF+cdjT1G9xytbW4XTg1NTY0dLL0MjPzNDT0IDK1tCAv8zLxMbHy7iztLS5sLissbWyrLOvr6yzq66prbiqrKSrqaKjoqevsLC2xrqwqqyknZmupKyqpqusv7Kmpauppqyvradus62sqquqqKednJujrp+im7+onZ2oqbiqqq+ysbO2r7CxqbKzoKepqqqusK/GpqWuqq+lraymrYCvubS8ua+0p62tsbOksLyurbC2qreqrLCtqLGxuMfJuLC3urOxrrWqsb67sb3JxsjExMXDwsTFxb/BysLHyMLBvcW+xMLHyMXFvNTY+dHDxczHyMnPxsTGxsvLyM7FzNPNyty8y8nHz8vRysvNzc7P1NDQw8jFy8zOz8bG0tHWy4DJztTRztTh187MzdHTz9XNzpbM0s3O0NDMzdPV3N3a2Nfh29bT1NfghPHZ39fd3tnX1tXU19Tb2eXh5OXm6efv+eni7/fs6+/u4t3q6+Xx6+3r5e3t9vz98vT7gf+DhPj/gfX49vPz7fLv9fLy+//6+fX8+frz/oD/gfb38/eAgYB4enp4eX6Afu59enx4fIN9fYCBgYKAgoODg4SDhX+AgoaGhYmKi4uOj42KiYiKiomKi4mJjZCJioyIjoyOkJCOj42NkY6SkZaWi5yRkZOPkZCTlpGRlZSWj5GSjo+TkJSUk5CWl5SWkpOak5aVk5StlJOVk5KZk5+Xl5WYl5mUmkqclpOWlY+Ul5eZmZuYl5qamJmal5SXqZmVlZaXlpWXl56hk5aWm5iXlZ6bmJeZpJuUlJmXl5aVk5qZk5Wll5iSkpOTjpSUlJGUlISWgJibmZaUmJWXmJmXlJWQkpCVkbTGl5OXk5WTkZKXk8KVkp2alJmXmZmVmZiZn5qWmJaSmZmXlZWTlZOWm6aXm5qWk5WVkpyZlJWTmZWVl5GPkJOQjI+TjY+blpWSkpiQlZKYkZKRko+RkJOPi46MkZORlY+PlZGPjI2QjIuOjZCNgJCQj5GQjpCOj5CNj5CMkJWSk5iVkpKUkZKTk5SSkJCSlZWTj5SSjZGTl5SUk5WVlo+TlJaRnJ6XoJaWmZaXkJOVmJWYk5iamaSSnpaVl5mXm5ialJialpidm5iawZidnJqXmZ2WmZ2em5ycmpuZmqCfmZOYmqOZmpicnKKdnZ6cgJyen5yenpienJ2fnp+hnpygoJifmpign5ucl5ucnpybop2ampuYl5aWlpeVqpaZkZimlZqdkJGWkZGMiZSSipGWkoyNkZCQkIiMi4mMjYuKjIqJh4WGg4qIjI+ZhYaAgIGEgX+Lg4WBg4aAgIWChoB8f4aFgYN8f359gHp+f3qQgHZ4dn55d3h1eH91dHV343N0dN2NcdxvcXxy4NnccnTO19LUbtPIxtLT1s/Uy9LR0NLRzdnXebnEvb29wcO1sbi3u7S5sri/ubS7tbOxta+uqKy1q6+tsrGsq6WlrqyqsMO6r6qspqGbraOop6KnprWvpKOmpaGmp6WhbK+npaWjgKKjpJqamqOuoaikuq6moaWirZ+anaGipaqho6KfoKGWnp+enJ+jo8Cdn6ilqKGkpZ+ioKmhqKehpZ6jpKeroquzqqaprqa3q6quqaSmoqy0sZ+Yoaanpamvqa6zsaOpr6qvqKilpKSlqKuoq7SxtLWwsK2yq6+vr7Cur6e5v/LEgLCytba0s7eytbOzvLizt7K1uLS1x6q3tLK4t7u4t7Oztbe7urqztbK2t7q3sbXBv8K4tri9urq+x765ubu9v7zCvL2Iv8XAwsTCwMHFxcrMycXEz8bCv8LH0Xnmy8/Jy9DMy8jIx8jHysvTz9PW1dXS19zMx9DX0NDY2dDO293XMt3Z2dXS2dbc4efY1dVs1W9x2eJ05ebn6Ojl497h29zj5ODg3eLi4+Ltdex25+jj7Hh4P3FzcnF0eHl45Hl0eHV5fHZ2eXt4e3p8foB/gICBfHx+gX98gH+AgISFhICAgIOFhIaGhYWJjoiIiIOJh4eJjYSMgI6IjI2UlIiYjI2Pi42Mk5aRj5OSlI+TlZSVmpeZmJWSmZqVl5KRmJGWlpaYuJiVlpORmZOflZaUlZSVkZealZSZmZOZnJqbmZyXlJeWk5OVk5CUo5iXl5mZl5aYmJ2ikZOTl5OSkZiVkpGTn5aQj5aTlpeZmKCelZWvmpuYmJmagJWamZiSk5SVlZaXmpqVk46RjpKXmZqZm5iXlZqVxtWVkZaSlZWUlJiT4JSPm5iTmZeamZSXk5Wbl5SXmZehoaGfn5ydmJicopiampWSk5SRm5aSk5KXlJWalJSXmpWQk5iRkp2Yk4+TmJCWk5qRlJOUkJKSlZKRlZSXm5idl5aggJybl5udmpmcm5yYm5uZnJmZmpeZnJqdn52hqKOlqaaloqShpKSjpqKfnqClpKOeop+aoaSqp6qqqauspaqrrKe1t7G5ra6tqaqkp6uwrLGqr7CsuaKupaarsLG1srOrrK+rrbSxr7DerbW1s6+xtq6vsbOvsbSztrSzu7myp66ugLyvsLG2t7u4t7q3try+vL+/usC8ubq3t7e1tLq7s7m2tL68trextLS2t7a7ubi3ure3trW0trLUs7artLqwtrqtr7Svr6qlsa6jrLCpo6SpqKyroaSkoqWkoaKjoaCfnZ6boqGkp7GdnpaXmZyWk6yZmpaYmpKRlJKTjoiOlpaSgJOOko+PkYiOjoq0houJk4uHiYSIj4ODhIb9gYKD952B+oCAjoH9+PqChuz39PuD+Ovp8u/z6/Lq8e/u7+vj+O2F1uLe2t/j5tbQ19TYzdXLzdfRytLKycfOxcfBxczAxr7EwsDBur7Gxb/D0cW2rrCppaC2rra1sLq5yb63tLe3gLW7ubm0iMS2trKwr6+xpKqrsL2us63Ct7Gqr6y4paKmqaamraCipaOqrZ+mqaqlqKukvZyepqSnnqGko6inr6u1rqaonaKkpqufq7Kop6qwqbmqp6qloamntsHCrqSsq6iio6acoqupn6qzrreysK+rqqqrqqWnsKutrqqppKijgKikp6mnqaCps+2+r6+5sbSyubGxsrK3tbO4rLK9t7nVrbq0r7i0uLCwr7Cxsbe2t62yr7S0trSrsb68vra0srazsLG5tLGytrm8ub63t4S0ubK0s7Oxr7W0u7q3uLe9vLu4ubnFb9S7vLS5vLq4uLq7v77FxszIycrExsLI0cC6PcbPx8jP0cfDz9LJ0MnHwLnGw8vT2dDU2nDab2/S12zV1tjW2tzc2dvb297h3uDb3+Hh3ul063bl4t3ldHOIfgF9/37/fv9+/37Xfgh9fn5+fX5+fYR+BX19fX5+hH0BfpF9AX7EfQF//33JfQF+ln0BfrJ9B359fn59fX6VfQN+fX6EfYJ+AgIEAICCgIKAgpKYiIiPiIKBiYiDiIeEjI2Oj5KUlJegkJOUl5CSkJqWkZCRk5uWmJqdmZiXlJKWmpOTlZSVnJmZmZefnpyepZ6ipaGip6Sgo6CdoZ2foaKkmp6hn5+joKSkpaaipaSdop+emJykoZ6dnZygoZ6cmpiboaCcmp6eoqCgqYClpamqqqanqaSjp6WjoqCfoKCinqKjpp+ho6WtpqqqoK+fpKWeoKCcoaGmo6OlpqKpo6SipKOimp2qm5+jraWirKyop6uvrKynp6aop6qoqaiiop+hpaWlqaemuaepp6Sup6eqrK+uq66yr66opZ+qp6WopqemqaalrqansayprVyxr7KwsK6urrCwsKyoq6upr6ywq66ssq6wrauno6OpqKSjp6GkqaWgnaGcpKKipKCZlpiXmZ6gop+fnZmZmJiUm5utj4+QjZKOlpOQk5qRk5SUkJCQkpWTlJKQkoSQgJOWlpial5aZnZqUlJOSkJaXnZWRlZGRk5KQjYmQjpCRkpOSlZSUkZCQkZKXl5GQjo2UlJaQj5OQlI+RlJKTqZaWlJaXnp6cnpqUmJaRk5GRj5eVmJiXmJmbmJSOlZCRkJuRj5iRp5eVnJuXlpWWk5KWn5ufn5uampeWkpiVlp2gGZqUmpyjm5aTkpqTpKOsnZydmZeTl5WTk5OEmYCWkIySlJqYkZaWlJKZk4WJlYmKioiMkJKSj4uLk4mCiISIjIuLh4iJgol/m4uMkYuJkZGFip2IgoaQgoKAhoKIgIB5eH9/fn9+fn1/foSCgWd3fHeAfnp2fHh7d3N2d29zcXByd95x3N13b21tbWxvbrV94HXc5ubf3NTUy8jJzoDK13RzdMbRxsvKt9G9vLW5w768u7+9u7m0ua6nqrG4s7qorrWyr62tra6xr7GrtbS6vOaso6Kppq6yrLClpKmosq2suqywsKirpLCmpKuoqKarq7GqtamoqqKooKKamZ+mrqm3s7m2trC2vquopqeluqqwsK22tbCtorKwo7W6v4C2uaKvrXbHwLSwsrGqtO23uYa1ubOupKmhsK+mrbW+sb67wLK6wMG6xrmys7XEvsDX0cLBx8zLvsDBvbS9uMHRvL7AwcW+wsTNytLLzs/U1svS1dfDzMjG0szOysHJzdLHzczAvMXGydWDxsrd0d3T1dHN1s3IzcvU1eTX0tfSy4DQ0M/U1Nnj3dXWzNPOz9zQ0NHOycbO0tPM29nk2dPb2t/d3d3x7OvY2tfc2N6E4N/p4u3l4Nvd3ujh3eHi5uHb3+Tu6+3s9+3t9PTs6O3t8Ifu6vr1+Pjy6vyB/fiBgo6BhYKDiP749vHx8O309vPx7vjz/ID69vj8/oT7+//x/AP4+vqAenl8eHmFkHt7gn16e4KFgYSDgIiDhIOFhYKGjH+BgYWAhISQjomKiYuPi4uMkY6OjI6Lj5KNjY2MjJKSk5ORl5WQjpWOk5aTk5eVkpSRj5SRk5WWmpCTl5aWmJOXl5aXlJmZl5iXlpSXm5qYlpWUmZuZmZeXm5+dl5aXmJqZmZ+Am5udnZyXl5malpqampuampuam5aZmJyUlpWXn5eYmpGhk5mfmJubmZ2bnZqZmpqWn5uamZubm5WYoJSYnrGalJqZlJOVmZaWlJWUl5WYmJqalpiVl5qamZqWlqiVl5WVm5iVlpeZmJOWm5qbl5eSm5qZmpeZmp2ZmaOamqCblpdqmZeZl5mWmJicnJyZl5uamKKYm5eZl5qZm5iVk5GSl5eTkZORk5qZl5WYlZqYmJqZlpWVk5KQkZCOj5GRlJKTkZSVo4+Pj46SjZKQjpCXj5KTko+OjZCTkpKSj5OSlJWVmJudnJyXlpeemYSVgJSZmJ+ZlpuWl5mYlZWTmpiYmJeXlZeYl5iWmJqipJyamJaUmJmdmpuemp2Yl5yanLucnJmbm6Kgn6Gel5ycmZyampecmZydnZ2coJ6ZlJuYmpuonp2mnrWinqSjn5+eoZ6bnqWhpKKfnp+enp2joaCkpJ6YnJ6nop+cmqGZpaGrF56bnZmZlpuZmZmanp6cnJqUkJWVmpaPhJRemZaKjpmPjo6MkpKUlZGNjpeNiI2JjJKNjoiLioOKg56KiIyEg4uNhoijhoOFiH+Af4WDioOEfn6DhISIgH6AgX2BgYKUdnt3fXt2dHh1eXZ2d3t0d3V1eHzmdOHhd4VvgHFxtnvXbtDT1NLU1dXT19XW0+J3dHLCysLKysDVx8O8u722tra4t7m2tLm0ra+2ubW2qa2zsLCuqqinrKWnoaystLf0raWjqqivsKqtpaSopa+rqrOlqaqipZ6so6KnpaShpKOnn6egoqKgraqup6umqqyhp6OopaWfprGhoqWhgJ20m6ChoKmrpqWaqKWeq7Kwq6qao6BosqygmZ6gnKTNp6mAqa6rqaWuprGvpqeqsKSrp62gp6qvrL2wsK+vu7CvurSnpKqqrqSnq6ynsa66y7KwsLGxq62vtrG3srCxtbWytr6+tLmytLy7uLewuLe6sbi5srS5uLS7crS1xLfAgLi6ure7tq+zsbS1w725vby2u7u4vrq7wsC9u7K8ur7Fvb/BvbnBv8PEv8rE0cbAyMjPzMvL3dXTx8vJz8zTd8/P1tDZ0c3Ky83Vzs/Q09TTzc3T1tXU09zR0tfW0dLX2dp119fi3eHg3djmc+Pdb3B5bHJwcnni5OTi4+Xl6OfjFOPc49/ndubk5ervfPL1/vDy8/DyLnd0dnN1gYx4d394dHV8fHd6eXZ+enx8foJ+hIx+gICFfoKAi4mCgYGDh4KEhYiFh4CMkIuLi4mKkY6Oj4yTkYyMkouPko+Pk5COko6MkIyPkZWaj5aYl5WYlJaXmJqWm5uXmJWUkpaZl5WVlZOZm5eXk5GVl5aOjpCPk5KTnJmanp+fmZqdl5aZmJaVlZSWlJmWmZqdlpWUmKGXmZuUoZKanpeZm5icmZuZl5iZlZ6Yl4CUl5aXkpijl5yhsZ6ZoJ+bmpuem5qVmJebmZ2ampiUlZGUmJybnpucqpudmpifmJWXlpmYlJmem5qVk42XlZeYlZiYmJSSl5OTm5eVmJuanJycm5ycnp6cmJWZmZWklpiSk5OYmJqamJeUlp2emZmcmJiem5iUmJSalpmcm5aUlYCTk5SWlpOWmJaXlZiWm5yslpeXmJyXnpqYnKKanJ6fm5uanJ+foJ6coZ+jo6SprK+sraipqrSuqaioqKaqqrGqpqqnpaipqKimr62vr7CwrrCxsbGvsLK6vbOwr66ts7S3sLCzrK+nqbG0ttu1tbKzs7y6uLy3sbi2sre0s7C2s4C2uLi6uby7trG4tba2ura1vrbIvLnAv7u7uru4trrBvsHCvr29u7q2vLm5vr+5s7q9xr65tbG6ssC+xbi3uba1sbazsbGytrW0tLOtqrGyubWssbOzsbeyoqe0pqSjoaipra6ppKWzo52loaWrpKagoqCYoJnFn56imZWdn5WayoCZlZmjlJaUmpidlZSOjpSWlZmTj5GSjZKRk+2GjIiRjomHi4mMiomLjoSIhISGif+C+v6JgYCBgIGEhOai/oP0+/j19fDy6/Lu8ez8hoOE2+Xa4eLR69/e1dfh1tTS1dPQzM3UzMXK09fP08LGzMXGwsC9vsW+wLfDwcfG/7SrqICwsLu/usG0s7i1u7e0vrC2urCzrLyxrLKyrauvrrSstq6vsq+8trausKuvs6mvqa+rqqawuaWmrKmoyqqurKirrailnaqmnq6yrKSomKKggMC0pp2io56s2q6yqrO4squiqJ+qpp+jqbOlr6yypa6vtK26p6OgoKymp7m2qaqxsYCzp6ytqqWtqbLBqaanqKejpqmyrbavrrGxtK+zuburtK6wure2s6mvr7WvvcG1t7u8u71vsLK/sbiwsra0vLmxs7K2t8a8t7u8uLq4s7mwsLq3s7GnsrG3wbu9vr22u7m8u7O9ucu4s7u7wLy+v8zn4Lu8ub+6wXHAwMjCz8PBvk3AwcnHx8nKzc/FyMnRzMnH0cbHztLNzdXW1XDOydLNzczLxthv2NJrbHZqbGtrb9XW2tPW19fa29ze1d3Z43Pd293h43Xk5uzd4+Tl6P9+/37/fv9+0X4BgJR+BH1+fX2KfgJ9fo19g37/fQJ9fot9AX7XfQF+w30BfqJ9AX6JfQN+fX2Ifo99AX6FfQF+iH0CAgQAgImGjYuKkI2PlJGOj5WRioiNj42RkI+PkpqXmJyYmJmXlp2NjpCQjZaYl5mYnJ+emZ2ZkpaXmJihqpmeoKGfoqCloqSipqihoqusp6GhoaO0pqKjo6ekpKLAoamop6qrqKetuKSmpKWjoqSgo7KpqKWpop+gn6KjpaWhqq2praqsgKyxq7Soqaqlp6usqaSioqWdoqimp6moqqqurqepqq6qqqWnpqKjoaSao6Whoaanp6ejo6KioaaoqKWjoqajobCjoKuoqqyuqq2ura6urbKwq6ysoqWnqKWmqKmkopygoqWlqrevramqsaysqrKvra2vrq6rq6isqqi7rKuurK6wgLCprrKwr62vsayvr6yxrrC4xbeuqqyysrK0sKWrqaaspaGhprO1o6Klo6Kgn7qemp+ioaSkpKaqpaiknp2cm5adm5mUt5qTlpual5eamqCem5ualJeTkY+Sko+UlZOWm6KoqJ6mpKignZiUlZmPlZORk5GRj5KQkJOOkI2MkpOWgJSPjpWSkpaTj5GQmpOSk5OWl5uTlJSXlpSRjZCJlZmVlZeWmpaWmZqboZaVmJaZlZaWl5uYnZCcoKOcqJ+XmJyalpuZlquUl5ialpeTkZeanaCfn5qbnJaYlZeZnJyinpqZmpqgnp2cnJ6Yn5+cn6GfnJiWmpWZkJOTlJeSlJeUgJCXnJ+YmZ+XnqSaj5Wak4+RjZGTmZGRkJKUj4uLnouQk46Pj5GRioyJi4uOj46JkIykhouKiIiFgIKEf4Z8fX6AgH19goB+fX1+fnt8fH16e4GBg35+eHt5e3h1c3Z0fHp4cd9x23N1dHNvcHBxTnTh4XaI3t7T0tbLycTUxsnUgOTJv8W7xr68wcPGzsvOysvLwcbFxbjArqusqqytp6KwrbCswL60wrKysrerpK2uu7Orqq+xsqewvamstKmrrKyrsbKwqqyyq6aqpaWorKysrrCvtrGspKmnpZqboJWpr6+2ur66ra2roqWfmaGttLOpr7uxr7CvwrW1s7axv9GwgLm5trq8uLi20d68wsTPu7y1scLOvrWzwra7ur3Bu7+9xcHAvMDAvri2uri3vMXJwLm+xMPDxcLDurq/w7u+hMnH0c3txNfWytjT5NbPytLRzcvGwsTQzM3SzMTMx8jJx8bJ0NTM29ra1dTU2MnY1dPNztHPzs3P08nR0NfXztLaPs7Ky9zf2dPY3dLQ1NPNy9HXzdLQ1NTc2eLX2tDV1tLX3dDh19Dj3NDa2eDg4vPu8uzo5ujl5ubh7Orl6OPqhO0+6O7u+Pnz94H87vTp8+qA/PT08vb+goOQioSHioiK/v+D+/T9hICBgYSCgYSGg4uaiIGDgYKDgYKB+fv7hIWAfHh+e3p/fH+CgYCCh4KAf4GEgoaEgoCDh4WFiIaFh4iIkYWJiouKkJGMjYqMkI+NkpKNkZOUkpeekJSVlpSWlZaVk5GSlI+QmZuXk5SUl62ZlpaVmZiYl7CYm5mYmZqZl5yomJiYmZqYmJeZpZ2bmqCampydn5+fnpqfn5yfm5yAnKOdqJ2dnZqin6CenJqan5icoJydnJubm52alpaXmZqcmZuem5yanpedn5qZm5yanJqcnp+bnKCdm5ubnJqZp5uVnZqam52YnJqZmJeWm5qYmp+Xmp2fnZ2dnpmXk5aZmpuerJ+dmJmfm5uZoJ2bmZuampmam56dm6+em52amZuAnJebnp6dm56inp6fnKGdnauvopqXmJ2dnZ6ck5mWk5eVlJKWoKWUlpiVl5eWt5iUl5iVl5WSlJeTl5eUlZaVk5iZlZGtk46RlJGOj5CPlJWSkpOQlpaXl5yalZqalpedpKuspK6rraeknZmanZWamJeZmZiXl5eYmZaXlZSZmZ1wm5iWnJeZn5qZmZigl5iXl5mcoJibm56dn52anJWfoZycnpyinpydn6CkmpufnqCcn52coJ+kl6OnrqKtpZ6go6KgpqSht5+goqahoZ2foqGjp6eloKGjnaCdoKKjo6mknp+gnqWkoqKhpaCjoJyenoSdSaCcoJmdnZ+inJ+gnZianaCYmZ2VnKWak5aZk5GTkJWWm5OTkpSZlZOSpZCUkoyMjI2OiI2JjIiNi4mFjIyphYmKiomHg4eJhpCEhYCHg4OIhICCgX9+fX18e3l4fHt8enh1eHd5dnV0d3x9fn126Xbldnd4d3J0c3RictjVcIHS1dPX3NTV0N/S0dfjzsbPyNLLx8fGxcjDwL29wLrBxcnBw7azsbS1t66puLKuqri1qLSpp6uxqqStr7izq6msr6+nrLqjqK6kp6qtqoCtrayipKmmo6imp6ippaKfoZ6mo6Cfp6utqaytn66ro6anq6mjqamiqKGZnqKho5+nraSjpKKxpKKjpaOxt6GppqGmp6CgnbfNpKmutqaopKa1wLOsqbOpqqussKqqqbCuq6yusrGurrCwrrmzt6+lqa2srK2ssKurr7Wur22xsYC1sNiqt7iturTEube4v8PDxb+4ucG9t7u4tb24uL25s7S7u7S9vbyxsre7sLq4vLi6urm7uLq9t7+5w8G7u8C4tLfDxsC+xMzCwsHCvr3DycPGwcHEysbRx87GzM3O0NjF1snC1NDJz9DV0NDb1dTMx8rLyczNzdHT0dLN0dnY2D/V0dLO1tbR03De09zV3NVy5N/e4eTnc3R+dW1wcnN33+F25d/mdnJycnRyc3V4dH2NeHJ0c3Z1eHp88Orse3qAdnJ3dXV6dnl+fHt+hoJ8en1/fH98e319hIGEiYSFhoaEj36AgYKAiIuIiYiKjYuHioiEiYyMi5KXiY2PkI6SkJWTkZCRk46MlZSOiIqLkbWZlpeWmpiWkq6RlZSTlZiXlp2nmJWUlZWUlZOVopybmZ+ZmJmYmZmZlpKXm5WbmZxInaOdqJybnZmcnZ6alZSWmZGXnpyfoaChn6Cel5aYm52em5+hnJubnpWcnpqYnJyampmbmpuXm56dnJubnZqap5qVoJ2dn6CchKCAn56gnJiZnJKWmJyampudl5WQk5OWl5mzmpqUl5+am5mgnJmZm5qal5qZnJmWpJqYm5eampuTl5yamZaZnpmam5memZqys6CWk5OXmZqenpeenZqfmpiZnqmtnZycmJmZl7uXlZqbm56al5iblpuZlZeXl5aam5qXsp6ZnaCem5yAnZ2joqCfn5qfnp6epKOgpqimqbG8xMe7xMLDurWuqauso6mnpaenpaWpqqyuq66srLGytbaxr7Wys7q0srOvurGysbK0t720tra4trWxq7Cqubu2uLq3vbm3ubu8w7a3uri8trm5ur+9w7bCydDD0cW9vcK9vMG/vNK8v7/Dvr+Aurm9v8PExcW/v8C6vbi6vb+/xsK9vcC/x8bExMHBub67tLq6urm3t7q1urK3uLq9tLa2tLC0vMG2uL2zt72zqq+2rKWppqyutKupqay0q6enwaatrainp6imn6SeoJ2hoZ2YoJ3OlZydnJyZlpialqGWlJaYmpWWnJeRk5GOjYmAjIyKiImPjY+MioaJiYyHh4SGh4yJh4D9gPiBhIaFgYODg5eI/vyCjPT58/L36Ovl+err9P7j1trQ4t3Z29/f4uDi3+Hh3OPk59rj0s/Nzc3LwrvJw767zcq7yL67vMO5sbu6x761tLe5uLC30amxvLGvsriytrm6sbK6tK2vq6uAqqqmpaSoqLKvrK21t7iwsK+esa2kqa2xq6atrKSrpZ2jr62tpqevpqiprL6wq6appK7GpK+spqyspaWixOCqsLnEs7WxsMa9sqmkraOnqauyrrOyubavr6mqqaOlpaiptLS+uLG2ubq8vLm6tLK1uK2saa+ssbHbqri8s765xLmAs661t7a2sqist7OxtrSttrG0vLy6u728tb67ua+wtrisvLm8ub2+u7u2tbeyvbnDxbu3vLKqqre8tra5wra3u7y4t7zBuLq5ubq+uci5wLa+wMDEy7jHua+/ubK4uL69wMzKzsjExMjExMXCx8TEx8HHztPT0M3T0NbV0NBv3dI02dHaz27Vy8jJy9FqbHZwa21xcXXZ2XDW0Ndwb3Bwc3J0dHRyfJJ2cHNwc3J1dXbh3+B1dP9+/37/fv9+534DfX59iH4GgH59fX5+/32ffQF+q30Bfv99i30BfoZ9AX6GfYl+Bn19fn19fZV+BX19fX5+AgIEAICEiImPkpKTk4+NiIqLj4yOjpKRkJWWlp6YmpuhoZ2dm52ZlZaWlJKSlZ2bnKGgnpuenp+eoZ+YnK6do6GknZ+joqWqqqmzq6qsqKamoKKgpqOkqaeorKemrKyupKmuqKmpqq2qrqaop6Oop6OmqbilsqWipqGmo6qqpqqnr66sroCsqqurq6ywq6mlqKmop6SkoaeqpqStq6urrKmpqquvraqqra2hrKugoqSkpqamp6WprKupp66mo6WhpKGqqqWhqKKkqKeppqu6rKuzqayrr66qqaqwrqiqrqusrKWnpKiqqamvsa+rr7Gtr6+2s7GztbO0sbOzsrG3sa6ysq60toCzubW2s62wr629rayvtLSxsrS0r6uoqKu2r7Gpoqmsr6qsqaiqqqOop6amqKepnZujn5+ho6eqpaempZqgnpuXnZuYnJianZqZpummnqGiop6dlpuRlY+QjZGQk5OSlZqdpJ6eo62ropqdnZWWlJ+Yk6SZlZeVlY6VkI+RkpGQkSGQkY+Xk5SUlZWSl5qVnZydnJaXkZWSk5SVmJaVkpaSk4yElYCXnJiZnp6impyampubnKOcnpipnpyfmpyeoZ+cqKGrn6KipKCgn5+am56kqbGipaGfoqadl52ioaWmpqKdpqejpJ6gpqOdmp+aop2bnJ+ZlpaWl5KWmZeSk5KKk5GSk5aYlaeZmJeTlJuWl5OTlpmYk46OkZOPjIqQkZWTkZeSjoCShoOFiIuKkI2Nj4+VhoONhoqKg4eEhYuAhJKdhn6ChYqEgpmRi4WCgIB/gIKCg3x+eXp4dnl5eX16mn177ep34Xp1cHPZcG5x23Tm5Hd2eeHa2eDX0tLW1MvPz8fEx8jOwcjIxcPOyc++trm/xr6zqLiwrKyuq7WvqbivubTL9IC6wrizsK+tp7WqrbWxs7SqsLOsrbakq7CytrOyvLO2tKvHqaWroqSfpaerrqe3tMG8rq6qr6aipJqzsb63usjFuLS1vreln7GvsK+yrrS9tbGstrGwuK+wtcGuwLa+v8K5t8mN7LrHwtO/ubCssrW8vby9v7m2t8K6wr68xsK9tICztbm8tru8vb/AvMG7vr++xca/trq0vcDIxMHGw9HKztrT1tDPzMzPxsfUzb7EysnTxM/L0c/T1tPOyNPsueTX3d7W09jV1M/Qy8XKzd3N09PY2c7SzdLNxtLV19fV3t7Z1NLY2tTX1tTJzNDH1Nji2d7b19TLzNvgidvc2tff3lva2dbX3d3n4Ont+O3o6+Xw8u3v8vXy7/b2/IT19O33+PSA//f6//Z98vn0/YKB9YSCgYGChIaHgoOHgIODhIOGgoWFhoKIiYeGgYH7goSFgoaGhYSFg4KDhoiHgHl7e32IgYKDgIF+gIGEgoKChYSEiIiHj4eIh4uLiIqJi4uLjo2NjYuLjoyKjI2OjZGRlJOWlI+ToJKWl5qWlpmVlpWTkZiUlZeUk5WUl5idmJeamJufmpqfn6GYm56ampubnpudmJmdl52hm56grZunnJuem6CdoJ6dnJqfoJ6fgJ6cnZ6enqKfnpueoqKgoKCeoaOdmp6cmpucmZucnJ6hnpygpJylp56foKCfn5ycmZ2en5+gqaGhpJ2fnairo6Cimpudm5uZnKabmqGXmpqenpmen6alnqGjoKCemJuXmp2dm5+in5yeoJ2en6ifm5qcmpiZnZ+fnqSjnZ+fnZ+hgJ6inZ6dm56hocOinqCinpuen6Cdm5iXm6WfoJmQlpmal5qYmJ2fl5qYmJicmp+VlZyYlpiYmJqWmpmclJmYlpSXl5WZl5WXlJGb0pmQkpOVlJWQl5KYlJaVmJSWl5OWnJ+lo6WqsrGqo6WknJuaopqWqZqWmpmalp2ampycnZ2cgJuZlpyXmpqbnZmcnZien6Ggm5yYnZ2en5+hoKGgo6ChnKKhoJ+goZ+gpKSooaKgoKOipKqkp6G3qaSnoKGipqOgq6Sto6WmpaOlpKOfoaKlqbCjpKKhp6yknqWnpqinpqGcpKKfo56hpqeinaGdop+dn6Wgn5+goZ2eo6Cenp+YKKGfnZuamZaimpual5mel5iUk5afmpaSkpSVkpCPk5GSjoyRkI6TiIaEiYCMiYiMj5aHhpKMj4+HjIiHjYGGkp6HgIOFiIKAmYqGgoCAf4B/gH5/fH17fHt5eHh2eXaWeHno7HbfeXNxdN5xcnTgcN7cb21y3Nna493f1dna1drY1NDT0NjMz8vHwsrDxbmzvcDIxsK0xL27tb62wLevvbK3ssLYsrevra2srICksqinsaytrqessqqttaSpqaitqaSqpqelocOppq6mp6OpqKSknKiiq6qkpaivrqunnrCprKaltrOoqq3Guaaeq6imoqOfo62oo5+opaaupaiuuKWzpqqrr6imtZzcpaypvKqkoKGprrS1sKytpaWmr6mvrqy1sLCprKy0ubK1sYCzsK+ysK6usbG1ubewsrC1uL21s7ezu7W4v7e5tbWztby5vM3DuLu9vMG0t7K3trq7uba0vdS+z7u8vbq3vb6/vL66trm1wrfBvsHDvcK+xMG7xMHDwb7Cv769vcDEw8XFx8DDw8LIxcvDx8XEwb/AzdBzzMzHyM3Ly8vHztPS1lTP1NXXz8jMxMzPy9DP1dLQ1tTedd3d2N7Z1W7Y0tnc2H/c4drfdHPadnVzcnFyc3NwcHRvcXJzcnVyc3Rzb3N2dnZ0dul3eXd1d3l5eHp6e3p9fXyAdXd3e4F8fHx5eXZ5eoB9fHx/f3x/gICGgIKDiIeGh4eLiIaIh4iIh4uRj4+TkpGMj4+Rj5KQiY6ajJCQlY+Qk5GSk5SUmpSTk42NjYqPj5mWl52YmpySkJOUlo+Vm5ibm5uemZmTkpeSmZyXmp6tmKibmJuWnJibmJaYlJqbm59Ynp6goaKfp6GemZqdm5qZmJWcn5ybpKGgn5+bmZuanqGeoaWpnqipnp+goaKgnp2anJ2dnp+jnJqdm56ZoqKdmZ2UmJycnpyfrKCfpp2gnaCfmZuZoJyXmISagJaal5qcm5mdnZuYmpyYmpulnJmbnZybm5+gnp2goJmcnpugoZ6koKOem5yemb2Zlpmampidn56bmJOTlqCanJSPmJydm5+dnJ+hm6Cfn56hnqOYlp6amZqZmpyXmpqck5iXk5OXl5ecmpuhnp2s87Kgo6WlpKSfpZ6ln6OhpaSnIKalp66wuLW2u8bFvbS5uK6urLWqp7aspqqqqqeurKyvhLCArq6stLC0tLS1sbe5s7y9vry3uLK2tba2trm4t7a7tre0vLu6u7u9u7vAwce+wb+/wcDDysPHv+PJxMe/wMPHw7/LxtXFyMrLx8jExLzAwsfO1cfJxsPHysO8wcbEx8bIw7/IyMXJxMbMysS/w7vBvLm7wLq5ubq5uLu8ube5uK6At7S0tLe4tcO1trSvsLaws6+vsL2xrKinqqyopaWsqqyno6mmpKmbmZucnpyim5mbnqiRkqGaoKCbnZqboZSYqbqbkpabnZaVtqKblJGPjY6Nj42Ni4yJi4uKi4uJioaehoH7/oH6h4SBhPyBgoP5gP/8gYCG/vf3//b49Pr69POA7+jk5ePs2uHf4OHt5enXz9Ta4NzWydvS0MvWytLIwNHEysbZ8sTKv726u7uywLS2vba3urK3vbm9wq6zuLO5urjAvL65sdS0rrOtrKSqq62qpLKsubixsrK5tK2pnbSuta6xxMKxr7HBu6qjsbWzq6mkrLawrKWzr6mxpqiqtaCAsKSmp6ulpbev+K25uMu5tKqpra+ysaunqaOjp7avt7WvvLGypqWlqKylqamsrbKws6+xtrW8v762t7W2trqtqaikraiqtLC0r6+wsLWur7+5q7K0tb2vs6+3s7a6uraywuTX28LGwbeytLa1tbm6tr28zbnAvL3Bu8C9w76zubWAubayuLq1r620uLi6u7q0tra0u73Fu768urWztsTIa8jGxMTFwr65tLi8vMK8w8jPx8PIxMrLxcfHycPEx8bNbcrKx9LT0W/i4Ojp4oTa3dXabmnGbmtpampub3FtbXFqbG1sa29tbnBxbnJ1dXVzdeZ1dnVzc3V1dXZ1dHR5eXj/fv9+/37/fuZ+BH19fn2Efgt9fn5+fX59fX5+fv99lH0Bftl9AX67fQF+oH0BfoZ9AX6FfQF+hH0Dfn59nH4BfY9+AgIEAICYjpKQjpORko+NkYmMiYiPj5CQk5OVk5SdnKGjo56hn56cm5qemZeemJ2toaalp6XKqqWlpKCgpKaipKSipamnpKipq6esra2vq6itpaKtqKOlta2tsa2vrqy0sa+yrLSsrq6usKuusampqaemqqmmqayprKqnr7Ousa+usrSvr4Cysa+ur626rqutr7CsraekpKerq6isrLDAtrCysK6rrKaqqKenpqipqqmsqqq5pKmusLKvq62rqKyqqqeloaGipqemqaust6evt6+5sbO2tbOqrayqqq+vrq+ssKyxqq6rr7G0r66ssa6trrCstLO0t7S2u7mws7KusbKzr7W2tIC6u7a2t7e1t7C2tbCtr7TCtLCzr6uoo8GxsLCtrbCuqaiqpqespKmpq6Snr6uhn6GgoJ+jpKWoqaWonaGlop2boKGrpKOkoaGkpKKboZydnpiYnJqUlZKXmZOXlZqZlpubop2cpKSgn5+dmpmVmZyZnJ2ampqVnJiXlpiWmZiUlICSj4+SkZWUlZianJuRuZyln52al5KNlZaVlZSSj42TlZSYlpaUk52em5yZn5qenJ+fpqCmoZ+hmpacmqKfo5+loZ+hpqaloJ+ioqChn6Cio6Wnq62mqqSjqqikpqOmyaGspqSpra23p6iuo6Ofo5uhnaifn5ygmZqjsJacm5eWloCVlZednJiaoJuelpOXn5vHlJWXmZuWnKSWlZKTj42PjJGlkJSSjoyPiIuKkJCLkJCOj4uMi4eHiIaHhomJj4yJioWHhoeFjoGEh32BfYCFhYWDgoF/g3x6dnl2ent4eYF9gn1+eXR5fXV0dtJycXLd2uDZ5YZ/eXlw1mrNbtHX04DSycvHzczGvsW/v8K+xcbBvr+5uLW/yL6zvbrAu7y4tqyyubSwt7W1rrG3t62us7m2rq+ssaeup6KtpaeotrKws7m5ubOxsa6zqaenrqyzs7e4wsC4s7O4raqqtq2pq7Wv0Li5t7GztbG0r6exs63EtLCwvbK/xLG1r7K6tbm4uIC5tcS3wL3CeM7Hxb7Bb8a6u7SwssLEwb6+ysDSwsfKxcbCvvm3trq5vbvExMPHyczTy8vExsrLdcW9u7u9vb2/vsbl28/W3ODW1c/UztPVxs3Hyc/RyMvOz8zR0c/Kx8vX0dbr0N3p79DL0NLQzNbN0ODPzNDL0dDV597f2dLZ3YDh3ODj2+3l09vX1t3i29DM2czU0O3c1tvY1eDf4OHZ2tjV4dzU2NjY4N7n4Ovs7vn88/Hv8/Xu7+/s6vD6++718e7t8f/6/IL6g4D7/ff79/mAgYCF/4KDhYGKgoSFqoCEg4eEhYuJhoSJhYOGhYGAg4WFkIeNiIiKi4qHiouHioCPgIOBf4OCg4GCh4GDgICFg4aEhoeJhYaLioyNjYiNjIuMjo2PjYuQiYuWjI6PkI+8lpaWmZWVmZmWl5eWmZybmJmYmJSZmpmcmZeZlZaenJeapJuZnJqdnJqjn5ydnKCZnJyen52hpp+doJydn56coaSgpKGcoqSeoJ6doaOhoICjoJ6fn52rnpqdn6Kgo5+foKGkoZ6fnJ6qoJyfoKCgoZ6gnZ+gnZ6goqGkoqOwmpqfn6Kgn6GhoKWjoqKinp+go6KfoqGgqJyeo5yhmpqfoJ+ZnZyfnqKjo6Wfop+lnJyan6Cinp6doJ+doKCcoaCfoZ6fpKSfoaGfo6Kjn6ShnoCjo52dn5+doJyjop+foaSspKCgnJuYl7ShnZ6bmpyZmJmbmpqfmZyanZWXnpyVlZubmZibm5qbnJmak5idnJmYnJymnZ6dmZicmpeRl5OUlpOSmJeVlZOam5aZlpuZl5yco6KiqqilpaOgnp6Zm56Znp6bmpuXnZqam5ybnZ+cnoCcnJyfnqKdnZ+ioqCVt56noKGiop+dpKWjo6KhoZ6ipKOno6KioKinpKOfop6hoKGiqaSrpqeoo56ioKWipaKmoJ+ipqWjn52ipKWmpqanp6eqra+nrKalrKyoqqeoy6Ooo6KjqKu1paSvo6KdpJ2ln6eiop+loKOyu5+ioZ6foYCfn6Gnqp2dpJ2jnJidpZ/OlZWZmpuVnKSXl5WWk5OTkJSfj5OUkI6Ri46Lj46Ljo6Pj42Oj46MjYqLioiJjIuGiYSIh4eGj4SFh3+Cf4KFhoSBgH5+gX99fH58f3x3dHt4fnl9eHV4enNyeN91dXbh3OHX24d5dXdx227Vb9bc2IDQy87R09PPyMzFxMbCwsK/wMO9v7zFyMG1u7e6t7avsauxt7KztLOvp6qurqOmqrGxra+ts6y2rqy0rKqqt7Crqq2qqqSlpqaspKSipqKpoqSkra6mp6uup6arsquoqK6n1K2sqaWprKesrKStq5+vpaWnsKuxtKiqpKiur66tq4CvqrWnsKqqebWyq6Srea2nqqimprKzsKinrqnCqbCvrK2rrd+vsbW0uK+2rquqqq+xsa+vs7q9cLqztLK2srKxsLPKv7S3uL+5t7W5usLEvMbAwMTHvr27ubi5uru6t73KvbzVs7vG0728wcXAub+3use5uLy6v77B1crHw77HxoDIyMnMw9DJucHBxMjOzMO+yMPJxNnMyMjEvsjGyMnHyMvJ1tHK0M/N19TXzdHRz9HUzs/P1NvV19fS09je4Njh39va3OTb2W/Ub23b4N3h3ttwcXB15HRzdnJ3cnNzkHB2dXh2dXh2c3F2dHV4eHd3eXh2fXV6eHl8fn18f397foCJeXx6d3t6end3e3V4dnd9fIB/gYCDgICHhoiIiIOHhISFhoeLiomPioyZjo+Pj46ulZOUl5KRlZSPkJCPkZSVk5STlZOZl5KRjo2TkpSdm5idq5uZmZKWk5Kdl5iamJyUlpWWlpSYn5qanpydn56ZnqCcnZuZnqGcn52bnqKfn4CjpKOjpKG5oJmbmpuanJmWl5uhn52ioKOwpqKhoZ+gop+koqOioKKjpaSopaa0np+kpKKemp6fn6ajoJ2clpmcn56coaGhu5yhqJ2onqKkp6Wcop6gnqKhnqCcn5uimp2anqCgmpqXnZuZnJ2bn5+foJyfpKObnp6an5+emqCgnICio52enqCfoJygn5ydnaGyoJycmpmWlLqgnJyZl5ualpebm5ujm5+dnpmdp6KYlp2cmZibmpmbnZiakpibmZaUmZqknZ+ioaOnqKWepqKjpKGhpaShoJ2lpqGmpaqpp6yrs7OyvLu8ubi3s7KsrrGqr62qq6uor6ysrK+wsrGur4Ctq6yysbWzs7a5uruv277OwMC+vLiyuru6uri2trK4urq+u7q6uMHDwcG8wLvAv8LCysTLx8XJwbvBv8jFysbNx8bIzc3KxMPGxsPExMTGx8nN0dTLz8fFzMvHyMbI78PLxsXJztHhysrUx8W/xbvBusO9vbrDvsHa7b3Avrq5u4C6u73CxLm8wrfJu7C3wrz3rK2wsrSutLuwr6uppaSlpKq4p6ywqKWpoqKgo6GanZ6dnZ6ipKOhop+em5ydoZ2Zm5aZmJmXoZaYmZCTjo+Sk5COjY2NkY6OjY+NkY6IhYuHjYeJgoCEiIGBgOaDgIH49/zz/ryMiIiA+4H3gvX78IDr4+Lh4+Xj2+Tf3N/W393Y1trT1M7a4djJ0M7Ry8zJyMHFzsnExsfHv8DGxrq4vcK+tra2vbS/ury/tra2vri3tru6urKys6+0qKyqrKmxqqmqs7WurbS4sK2utq2opa6n0bGwrq2xtK+1tKu1tazBsKqqtq+6vrGyqKmsqqiloIChn7Ckr6ywnLu1sKy2kL22uLKsqbO0r6enr6q7rbW0sLCqqsuopqqqrqmysK2vsbO3s7KvtbzAgMC1tq2wqqmnpKjKtquvsLq0srK2tbq7srWytbrBurq5t7m4ubu4ubzKxMDgsbrF2LW0ub23tLy2u8m9u7+9wL2/ycK+u7O4uYC8ubi9tcO+sLu6vcHFwrq3wbzBvdnEvry5s8G/wsLAwcG9ysG4ure3v7zCucDBw8jMx8rKztDLzMzFxMvR0cfQy8rIztzW2HDYdHDh5eDf29ZsbGpt0mlrbGpxbW5vkGpubG9tbnVycnJ3dHN3dnNydHRxenB1cnN4eXh2eHl1eP9+/37/fv9+836FfYV+BH1+fX7/fYt9AX6FfQF+qX0Bfv99kH0Efn1+foZ9hH4Bfap+AgIEAICVkY+MkZGPj46Qjo2Oj4mOkpqapp2epZCWp5+cmJqamp2anKexpJ+toqmloaaprqmsr6+upaumpae6pampra+xrKepqayxr6+wtLGtqqqkoqWvrbKys7Swr7Cvs7KvrLSvsbSur62tr6msqaanqqypqKmrqLOysbCurrGwsK6tr4StgLOwtK23s7C0tbKnrKurpqqrra6ws7CvsK62uK2wsLO5tLGvrLC4rK+vs7Ovtru7tLayu7Oyr6irrKioq6iorrCtsrKvr7CnrK2tr7Gyuqexpqquq7KuqbCwrrO6uLy4urW/tbe0uLC1trq6vLzCvcDBt7e3u7O5ube+vcC+wMK5UL2/vLu3s7CxsbW1u7mxsayyrKqrr7Kxrq67sa6uq6mmpqeoqaeoqKqnpaehoaSmpKSspaWipaWno6Kin6KjraCfpJ6enp2eoamcm52go6CYhJNDmJSSlpaZl5qeq6GhpaajoZ2mo6KfnZmfmJifoJudnJ2Ylp2anZycm5qZmZWXmpWTmZudm5iVmaKjm5qbnZ6cnp2dmISVgJaWk5OWmJqfpaGko6GgoKWkoKiooKKjpaOfpaCgoqKkpaKjp6WqqqWjpqaenpycmZ6eo6SkqaWkoqKspqqnqaajpaeorK2us6eppqanp6qlpKWhm5+goqCim5ublpmnmpycmqCYlZeVmJKXkJyel6Cgl5ianpqZlpmblJ6ikY+WgJuTlpacjoyQjY6QmpKRk5WPkpORjJKMiY6JiomMiIuKjYmDiIaMhoSGhoSLhn+Fj4OJhoOCgoN+fXZ3d3x5eIB+fHl4e3d1eIp1b9hybnBz4N7QdXPX1Xrg4uLd2dzUz2zZ09TP29XM0L7Bxc3NxcjHysfEwLe9v8G/x8bFvsPFgLW0q7iyr7WyuLe3s7Ouq7Owuba5sba2qaSfo6ipqMq0uL/BubOys6qwq6+sqrK3sr6zv8LRvrnEwLC1vLW2s766vbe0t7W3ssO6rrq0trW2u7+yrLSwuKuzubTOvLvJxr69vbu8wsS5wsrUxMPHQcGtwMHEv8THx8vP1dTCxb66gLy3wL+8vsHFw8PMyM3Yz9DPyc/J0tfXxsjBw8LIxNjP0tff1dfR4O7i0dnP1c/Z08vR1djTztfR2NjO2dTVz8/T1NLc3trX1NLa3d7j5NbTy9Do1OHz5N7V3eDe4dfc2eHc39zc4uLk2tvfz9jXzs7V29fc2POC6erq6Onl6PLeYefY2dzc4Obo5fDs7O3w++7z8vf35+708fH47O308P71g/Pr7/X1/v+Bj/f+gIH7gPb1gYWJhIWBgIaAgoGHgYOFgoaHhIWKi4aDiYCDgIKGh4SLiYuKlJCRjpKNjo6OiY6AjYF/fX+AgIKBhYaFhIWAhIWMi5ONjpKAhZWPi4iMjYyOjY+ZnZSRm5CTkY2PkpiWmp2dnJebmZicq5mcnZ6goZyXmJicn5uXmJ6gn6CinJqZnpyen6CgnZ2enaCdnJqinqCloaKioqehoqSfoaGjoqGgoJ2mo6Ohn5+ioqKgoKGAnp6dn6Kgo52joZ6jpKOepKWmo6WlpKOkpqKgop+oqKCjoqatqqWko6Wrn6Kho6OepKSkoKKeqaamo6Glp6OjqKSjpaWgo6ahn5+ZnZ6dn6GiqpylnqOnpbKooaSjoKOloqeiop6poaShpJ6goKKhoaCmo6Wpn6GgpJ6hoZ+ko6V+o6Slm6Cin6Cgn5yfoKWipqWenp6koJ+foaCdmpmjnZ2enJycnZ2dnpyempubl5uYnZygnZ6loJ6bm52gnpydmZydo5iYmpeXmJaXm6KXl5mdnp6XlpeYmp6cmp6dnp6foamkpKalpKKepaSjoZ+boZqZn5+coJ6inJyhnqChhKCAn5+jp6CgpKakoZ2anaixoqGjpKWmrayupqSmpaWnp6alpqemqKqlp6akpKKnpqKoqKOlqamnpaqmpKanp6ejpKakp6mlpKqrqKipqqapp62rqqypp6WlrautqquppKWlpaepq7SmqKiop6aqpaWnpqGnp6inqaSipKKksJ+ioJ+Ap5+dn5udlpuUn6WbopyXl5ybmJeUmJuUoqeSkZeblpqYoJKRk5GRk5eRkJCQio6RkI2Wk46WkZCOjoqLiYyJhYqIkIeGiYiEjYiBhpaGiIWDgoKEhIN9fX6CfXyJhHx8eH15d3mDeHLidXN2d+Ph1XNx1tyD4ODh3uDe2NVv3dOA0c/Y1tLXw8HDyMW9wcPLy8TDu7++vbq+u7y5v7+4uLG5s7OxrLCusq2wrKqtqbGvr621wbOura6zr6vJr62traWkoKSepqappqWprKmspa6rwamnsqyhqLGmrKqyqaypqK2rsKu8saWvqaeoq7C0pqaqpamdpKuouKywt7KrqKiApqWssaessbqvq7lfsJ+trK+prKyurbK5uKyvrKqvr7O0r7Kzs7Oyt7GyvbKurqquq7S+w7a9tre5uLK+s7W5vbe7uMLNw7q9t7y8w7+5wMTFxL7DucC6tbu7v7u7wsG9wsTCwb++w8TG0NDGxLq+07rD6dTCu8PIyszCx8XGwsZ+w8PGyMrDx83DycvExMrNys3G2XPOy8nFycnP4c3Wy8vMy8/V1dHa19XU1t7R09bb4M/W2dfY2dLS19Xl43zk2+Dj4OLhcn3Y4nV463fm5HRxdHN1dHR4cnNze3V4eHV4eXZ0eHl1cnZzeHZ5fH15fHp7eYF9fn2BfH5+gHt+b4R3eXZ4eHd6d3x7fH1+eXx+hYWUhoiPfIGSioeEhYeEh4SGjpKMi5qMk5GNjpKUkJOWl5iTmJWVl6aRlZSVmZ6ZlJWXmp2YlZSYmJmanJiWmJ6dnp6gnpmVmJaZl5WTm5eanpucnJ2inZ+fm56foISdgJmjoaCdm5ydn52en6Kio6KkqKSooKahnqKhoZmdnqCdoKKlpaWoo6Oin6WonqKjqbOuq6qlpq6jpqWnp5+lp6ikpqOsqKShnaKkoaGmoJ6io6GkpqKhpJ2ipKSnqaqzoKieoaWjq6GaoJ2dn6Wip6KjnqadoJ2hnJ+hoqKjoaejgKapn5+foZuenpuho6SjpqigpKelpaKgnZ2eoaCppJycm6GbmZqenpmWlaGYl5mcn6CjpKSin6Kgop+doJuem56cnKKampeZmp2amZqWmpunmp6loqOioaGlrqGho6irqqOhoaKjp6SjpqSmpqeruLKztbi3t7K8ube1sa2wqauygLOvs7K1rquxra2urq+wsbOys7ixsbW5urm3tbvO0769u77AwMPHxLm2ube3ur26ur6/v8PIxMfFxMPCyMjDyszHys3OzMfNysnLyszNyMfLyczNyMjOz8jJyMnEycjPz87Qy8jFw8zKzMvMy8jKycrNztDZys3LzMvJzsnHx8S/gMTExsXHwcHDvsPevL29u8O6u724urK5sLu/tr+8s7O2u7a1sbS1rcDIqquyubK2tL6rqKyoqKivpaKipZ6ipaWjrKikrKalo6SeoJygnZWbmaCbmZubl52VjJCbjZCOjY6Pk5OTjo+Rl5GPmpOMiYWJhYOGkoaA/oSBhIX+/eyCgIDx95P9/f36+/3384H/8fHs+PPu8djb2+Lc1dbZ4d/e28/V1dLO1dbWzdTWy8e/zMbDwr3DwL65wLy4urjAurq4wsy9uru6wbq60b26vcC5tLG0q7Gus6+tsbawsqaxrNOsrLy1q7C3q66ss6qsqaWpqLGvxbytvLSztLe7wa6qgLSvtqius62zrquwrKSjpaeqs7ipsbS+srDAgbGesLG0rrGwr6+xuLisr6+ss7G4uLOyr7GurLCrr7qwrq+rsKu1vsW1urO0sq6ntaanqa6qrKq1wL6xuLG4try4sLe8v8C/xr7FwLe8ubq5ub27ub7DwcC/ur+8vMPFv7+3vtW9gMXdyb22wMTFwLq4trq3vLm4v8LCub7Btr3Cvb3Ex8HEvtFvysfFw8bHzNPJzsK9v7m7v7+8xsXDw8bOxMTHy9S+xcrHyMjAwsfF1NR219DU29nb2m5709lvct5v1dJtbG5pbGdpb21vcXlzdXNubm9sbXN1c3N5dHh2dnd3c3dwDXNye3Z3d3p0dXN2cXT/fv9+/37/fu5+AX2Efgh9fX1+fn19foh9AX7/fY59AX//fYh9AX6pfQF+h30Kfn59fX5+fX59fa9+AgIEAICelZaRkpOSlpaVk5WWlZKZnJ+hnpmbnZmelp6gnqGenqWkoJ6rp6muqKimpKeurqy0tLOxsbGtq62vtLG0raqvq6vAta2vrbOzsrCqq6ywsLC2t7q2tbS0sbWxs7a3tLG1sLKxsa+yrrGzsLKztLm0tbO0sLDBs7GvsLC0tbSwsICusri1uLa3sbS0sbKzsKuwsLGtr7GytbK0v66xq6uuq6usr6yusrSyubS7t7a4uby9vLi4trO1tLSwsK2srbGzsq61rrKz0K2urrDBsrG4s662xauutLa8srWxsamsrrK4wbW+vcayurW4s7m4vru+w73Cvb66tr2+vL/Du8HEvIC/xMjCvcHAwLe7tbe0t7q1qq+ws7Cys6+usbO0usG8uK6vq6+opKapq6qrqaaqtKyqpqKkpaWqpa2sqaWloaqlpKmopp+loaSfnKegnp2aoaWgnaCcl5iWl5SVlpmcmqSeoJedoJ2hraSmpaKmo6agop2nnZ2bmJmSmZmenJ+eoYCfn56gnaacnKGfnJqYlZ2kpaGcm56dm56WnJ2emZ2bm5qcmpman6Wpp6mopKWlpqWjoqiko6SloKGjoKKjn6SnqqSlpa6toqWmrJ6gop6kpKWpp6qrqaisqqmlqKqqpKOmpaSnqKurrqqsqqernaikpqOkp6mkp6qlpqqqsqipn4Cdop6eoaKgoJmZmp+dnZmfn52tm6abmZmcl7Wcko6Sl5OYmJOWoJWPoJSVlJSXm5SSmJmVkIyNjoiOi5WSjI6IiIuCh4iLhYiRkYCBg4CDg4yLioiKhoaFgn99e4B6int8fH17eXd4eHVyeHR0dHV0cdt1c3R129zUztvM09rY34De1trK2cjM1tHLwtrX1MrFx8fSyMXAxcjUysK/tMe0ttK0ua6zrLKvv7W6trC3ta66tryur62yt6+MurOrr665tLq/t8W6r7SxrrGuubS2vLzJwLa+vLu7uLS2tri3udG2tru7v8jFvbPJsrm2tLe4urm8xL7FvsfBub/Dw9bG44DJvLy5vL+4xr7Bu7Wxsbe6v7rAxMXDx8jHx8i/v8LAwL2/wsHJvMPHwsnb0MvM0dXT09HWytTPy8fQwMbJ2sXL8Z7r1tHj89jI3dHS0d7S0tjU1+3S7eHS0cvX19PZ1NTOz9fj4Ofm5+jb3unh2O7c3Ojp3uXd19vf2ujr6O7j5IDm5eLc29vZ09rX4NrV09Lb1+Tk6Ojz6+Tq6+zp5+bd5tvl3uLo7ufp6O3z9Pj1+Pvv8v6G9fj08/b2lZn07vrz9/r49/uA+//69Pz/h+/7+vqBhISBgYSBgIH/goSEhImEhoiLi4yKjIeIjo+LjY2HiY2LkImQjY2Oj5CNj5GUkjKMg4aDgoODhoaFhIWGhIKIiY2PjImMjYqOiY+QjpCPjpOTkY+YlZqelpWVkpOampednoSdJpmWmZqgnqKcmp6cnaymnp+coKChoZ+ioKOfm5+foqGioaWipJ2ehKGApqSnpqelpqKlpKOkpaKloaGgoaChuaWko6SlqKuopKOfo6elpqeoo6WlpKOko6Gnp6mmp6elqKWmraOppaWop6aoqaWmpamlqaSrqKamp6anpqSnpaKmpqajp6WjpaampKKooqamwaKjoaGtoaCjpKGpxaCip6itp6uop5+jo6SAp62iqaevoqmmpaSko6SipKajqaaqpqKmpqOkp6Clp6Gjpaein6KkpaKmoaSjqKumnqGgpaGkpqKioqSgoqiiopyhoKSfm5yenZydnJyfp6Gin56gop6inqOhn5ucmaGamp6dm5WdmpyYlqCamJqXnaOdnZ2cmJmYm5qbnqGioKiAoaGaoKCdobGioqKeop2jn6KeqJ6fnp2gm6CepqKloqSioqGjoq6ipKinpaShnqOnqaajpKmnpaefqKeop6mkpqisq6elqKqrqqyqqKqpqqqopqyoqKmrpqiqqKmrqKqtrampqK+vpqiptKeprKmvraywra6vra2vraypra+vqaiAqqqmqamsqqqoq6qprKKtqqqoqKytqKutqKerrLyrsaWjpaOipKShop6eoKOkn5mbnZuxmqeampmdmMSdlJSVmpecmpeYo5iSopWVk5CUlY6Mk5WTkZCSk42SjpWUj5GNjZKKjY6Pi4yVmYaHh4WFhoyIh4SGhIWGg4GBfoN7iXyEe4B6eXt6eHV5d3d3eXd13HJucXTg5uHc5tba3tbd2dXdztPGzNPLxb/S0M7GwsvI1cjDubu8yMG7u7nXtrvRur2xt62vrbqxsq+nrKmirquxpaustLy4jsW5sbGusaurrKevqKOorKeqqrGoqq2sv66orq2ura2pqqqxr7HFramrqUmtt7WwqcmtrqytsbKwqaqvqaujr6uor7Cuuqu7r6muq7O1sbavs66vqa2vsbWtrqytp66vsK+yrKuxr7OzsbSyuqivtbGzx7i2hbSAs7m2xsLCwca6urzGsbXbgsm8u8nnwrbGvr+/yL++xcHD1r7Vyb28uMHCwMTDw7y9vsa/wb3DxcHJ2dLD2727w8XB0sjEyMjDz9DJz8XExcfKx8jLzMnNzNbQysrJzsjPzc7N0svGysrNzs7Oy9PM0s7O1dvP0dLW2tfb19jX0NZO4nPZ3NjW1taGf9rW6OHj5OHh43De4NzX4+l65fDr6HRycnJ0eHV1dud0dnV2enZ3eHl4eHZ4dHV6fHl9fXl6fXp+eH58fH5/gX6AgoSBgIF4fHd3ent+fn98foB+fIGDh4qFg4SHhIiDh4mHiYeGjY2LiZSSl5uSkpGPjpSTj5aVlJaWl5OSk5OYlpmTkZiVk6aelpmTmJmZmZebnKKdm5+goJ6fnJ2ZnpmanJycmp6doKCgnqGdoaCgoaSjqKOioqOfn72kn56gnqSkpKCggJ6kqaepqKukp6ejpKSkn6Wkp6Gipaano6iwpKeipKakpaaqpqaqrqutqa+ppaOko6akoaSioKSkpqSmo6Kkpqmppq2lqajHoaKjo6+mp6uppqu9n56ipKihpqSknaKioqWwoKalqp2lo6Sjpqano6Wmoaakp6OgqKekpaiepKifgKSnqqWip6qrpqqkpqGjpqGYm5uhnZ+hnZ2dnpqfpZ6dmKGgpqKipKalpKWgnaOtpKOemZqcmp6ao6OjoKKfp5+foqOinaWjpqKgqaSipKGnrqioqaikpqSmpKaoqayrta6xrLKzs7fMubq4s7ewta2yr8CwsbS0ta60s7extLG0OLO0tbq3ybe1ube2t7i5wsjLxsG+wsC7v7fBwMPBwr7BwcPAvr7CyMvJzMrIycjKzMrJ0s3Ozc/JhMqAzcnNz9HKy8jT0MbLzNfGys7K0NDP0s/T0svJzMrHxcnOzcjKzs7M0M/QzM3LzczL0MTQzMvHyMvMx8zOycrPztjP0MXCxsK/wsO/vrm6u8DAvra3urrStsG0tLS3tOe5rauutLG1tbGzua+rvK2uqKWmqaKgqKusqqipq6SnoqqAqaChnZ2gl5yeoJqdqKuSkZCMjIySkI+PkpGVl5SSko+TipyJi4qKiImIiIaEgYaEhYeKh4b6g4CBgfP37Ob25/L59v768vTi69/o9uzm3fTu6uTc497t3djS2Njo19LQxuTFyeTHzsHIu72/z8LDwL3AurK/uLuvtre/zMfp5MmAvrq4vrW5v7fDurK4t7O2s7mwsrKzwLOvtre4ubeztbW6urrQtrCwrrS9t7Kr0q+zs7O1uLi1ub+3u7O4sKuztbG+rLqwqa6qrLGttqqtpqKamqOqs6uurq+pra6ur7CoqLGusLOytrK5q6+yra+8tq+wsLSytLO5tsW9u7fArKyAqrmjp75yua6vwPK4rL61tLe/tbW9ub7TvdfMv723v767wLu8tLa9xMDFxMPBubrGvbLKu77Jy8LTycDBwrzLysfKwL6+wcG8ur29uL+/yMTDx8PIwsnExMPLxb/FyMrMzMvKzsfKxsXK0MTIxs3NzdDLzs3FydhxzdHOzc7ReXZE1cza0dPU1NnbcN7h39Xf4XbY39rXa2xraWpta21v33F0c3J0bG5vcnJ2dXd1dXp8fH17dXZ6dXhwd3N0dXd4dHN2enb/fv9+/37/fvV+AX2EfsN9AX7/fYh9AX7xfQF+hn2Cfol9AX6GfQF+hH2JfgF9pX4CAgQAgJeWmJaWmZubnJmcn52cmZ6enpuZmpyamqKjpKOhoqGfoKOhoqSnp6qnq6qusbGusre0s7C0tq6wr7Oys7HdsK2sqrm1sbWysbGvtbO2s7S3tLq2t7O0sbW0trq5uLq2sbSzsrGyt7GxtLSyuLe6uLq4tbq8tby4ubCysr63tba0gLC4u7u4ubnCuLm7ub25rrGytbGysrK8t7i9tr+ztbSusbGwsrWws7K2trS0t7/BvL29vMDAurWxtrW3tbS1trm4tbOwsrK/r7W3tLWysrS1tLa9urm0trewsLa6wrSvsrKxsra6u8O6ubi6urq7vMDBv8HBv7y6vLS3ubm7vL6+gMC9yL/Bzc7CuLG4uLKzubi0sK2wrK+wrrWtura/vbi8vLOwsKypq66zsauyrq6tqq2qqbOpqKSmoq+pq6urqKmnpqSloqSkpJ+hoaGgn6agoJ+fnZyen5ebmZmXlZmZlqCeoaukoJ+ho6Hqm6KmpqOlm5ign52bm5uenKigoaKchKGApqadmp+gnJ+dm56joaGgpKCcn56ooKCfoJ2dnJyXoKCenqCio66npqempKimp6qkqaemp6SjsKekq6Wlo6alqK2vqqqnqKO6paWkpqisr6Wir6msrbCtrKmqra6qrKmuqq2rraWnpKato6Ggoqijp6+nq6mspbaqsq6np6qmoKKAq6yjoZ6dm6CdnJqdoJ2cmpucmpiloJycmZKwmJyTmJWRo5eVoJSbnZqWlJiinKaWkZGQkZKQo4+UkZSOkI2JiY2RjYmUiYiLhZ+Oio6Ni4uIio6KjXaDhn+IfoGAgYKCe358fHRydXNzcW9zdHN2lnzs2ufq4tfc4YJv0s/OzMuA1sjNy8/QzNSD583Ux8jFy8nOx87Dv8PAv8O8wLzEuLe3srOyvbPDwLq1tLi2urWsrrCyr66mq6i3uK24sK2tq7KyrK+ytLa4srS2sLKwt7i+vb29v7yBq7e1ubvHvLW/urjFx8XGwMbDwr3EwLzBysLGycfAxcXR1MrBwru/xcGAusDCw7nKu7+2uLvFvb3nxLK64ra+v8bDwMPJzMDCv8HEvcjFxL6/ycLK0NHR0dXZ0cvUyrzPyNPa1dTL09DX1Nbc197T1tzU1uXz9v7Y29vUd+DQ1dHe1NjP0Nna/uvl5OXt6fLr3ODj5uLf4OHr5t713tvj4Ofu4ebb3Orx39tK3djg5Nrb29Db4u3c5uXj4+3s8+f0gP/u8PDy9ert7u7s6O729PT1g/j89vf19PTo9uvs+fD1+4H9/P349vn+//2A//78+fb+8veE+i6EhIGBg4aDgoOEhYiJjI6OkI2PkY2JjImMjYuQkJCPlY2Rj4qOi4uNjZKMlJWTIYiIiIeGiIiHh4OGiIeEhYqLjo2NkJGOjJGSk5GPkpKRkoSWgJmampWXl5mZmpianp2dnJ6gmZuboaGioMijoKCgqKWipqGfoZ6joKOfoKCgpaOlpKejp6Wlp6amp6WlqKinqaiup6eoqKWmpqaipKKgpaiiqqmrpaaor6uop6Wfpamqp6mqtKeprKitrKOoqa6qq6ioraipq6evpaeppqiqqqqtFqirqaurqamqsbGrq6imqqmmp6arqauEqoCsrayqqKqotqKlpaKmpaWnqKenr6qop6mqpKarr7iqpqWjoqKjpqe4qaimqaakpaWnp6arqqmnqKqipqipqaipqqqlrqanr7Gpo56mqKamqaimpKOno6alpKeep6Cmo6CjpaGgoqGen6Klo52loqKjoKSioaiioJ2fnaygoaChnYCgn56cnZqcnJ2anJydnJyhnJ6dnpydnp+boKGioqGkpJ+loaCopJ+foKGgyJqhpqekpp2do6KhoKOlqKW9qKimoKampqSqqaOgp6ikp6SjpqqpqKarqaWpprGqqairqauqqqSrqqinqaqssq6tramprKmpraitra2uq6y4r6uzroCtq62rq7Cwq6uqrau+rq+trq6wsqmms66xsrSxsq6vs7OusKywrK+tsKqtqq2zqqenqq+qr7OsraqsprWrsqynp6umpKOprKGhoKCgpaSfnJ+joJ+cn6CemqignZ6blrCboJmdmZamnJihlJublpOPlJyXnJOQkpKUlZOikZaSloCRkJKOjpCTj4yVioiMhpqMiYyJiIiGiIqGi1Z+gXuEfH+BgoODfoF+fHZ2eXl6d3V2dnV2k3rr4u3s5d/l5ohz3NfV1NLXyM/NzcrGynrYwszCv77FxMa/x8C9v76+wbrAvsW5uLiwrq+4r765sK2qsbG1sKqytLi2tqywrbq5sIC2sa+trrCyqqyysLO0rKuuqampra+1sLCwtLSEqbeysrC/r6ewraqyr66uqrO0srK3sauqr6Gnqqqlqam0tLCqrKqvta+rr7K0qbuusaqrrbKtq9OyqrDYr6+vs7CqrrO8s7OwsbSutbS3tLG6tby6tLSztrq3usS+tsO+w8e/u4C2vbu/v8HDxce/vsbAv8vU2t7CyMvEfdDCxcDIwcK/xsnE28rFwcLIydLOxcrKycPCxcfOyMPVxMLKyM3QxcjAvsfPxsfOzNHWys7Sy9Ta4tDV0cvK08zSxc9v3MrOzc/Vz9LT0tLNztPS09Fu09nX2dzh4djl19Td0dbdcuXm60Hn4+Lm5OZy5eTj4+Xt5Ort6+jkdXZ1dnh6d3Z4d3h5eXp6eXt4e316eHp4e3t8f4CAf4R9gYB/gX9/gYCDgIeHhIB7ent6e36AgYGAgYSDgoGFhomGhYmJiIeNjY6Ni4uMiouOjYyNj5GQjpGRlJSTkJGWlZWVmZuWmZmenJ6ZwpqYl5agm5aZlpWXlZ2cn5yfoaGmoqKen52hnqCkoqGlo6GkpqSmpKihoqOjoqSmp6WmpKOnqqOrqaeho6TCp6inpjigqa2tqqqsuKmrraqtrKGkpaqko6Smraerr6qyqaqrpqqtrKywra+qq6yqp6ixsKimo6Gkp6SiooSngKmop6ioo5+eoqHInqOloaSlp6usrq20rquopqaen6OnuKKdnp2cnp+iorSko6GnpaSlpKinpKinpaKipJyhoqSlpaWkpKCqn6KrsKegmqKin5+lpqOgoKKcn56fp5+ro6ijm56ioKClpqWnqK2rpammpqWhpqSjrqSjoaKguKangKampKalpKOmpKanqqapqaqqqa+pqqiopaOmp6GlpqelpKippK2rrri0s7S5u7njsLW3t7Gzq6qxsrSztra7usS8vLuxubi5ub/Buba8vLi7vL3DysnHxMjDvcG9ycC/v8PAwsHBu8XEwcDCxsnSzszNycnNzMzRy9DPz9DMzNvTgM/Y0dDMzs3N0tPO0M7QzebR09HT1dfYzcjUys7Oz83Ozc7T1dHTz9bR1NHUy83Iy9THxMXIz8vQ1c3Oys3F2c3Z0svM0MrFxM3NwL67u7rAv767vsC7ube4u7q1xLy5uLax0La8sbSxrb6vrrmpsbGrpqKotK2zqqWopqmrqbymgKumqKKioJqanaGdmqaYmZuUq5uWmpaUlJKUl5We3o2RipKJi46OkJCMj4yMhYWGhYWEgYWGhoehiP7n8PTs5vL8o4Dy6+nr7/nq8vPw6uXqifPf59rX2OLf4dri2dPa2NXZz9TW2s7LzsLBxdHD3dXNw77EwcK2tLq+wsPDur+5gMfEt8G3tLi1uryzsrS2t7iys7Oura+0ucPBwsTIxI2vu7W1t8nCusLCuby2tbiwvL+8vMW+uri+r7Kzsq6yrbu8t6+vpKiwraqyubusu6itpqett7Cu2LKkqr6lqquvrKits7Kws7SzvLS7t7evrLSttLSzs7W6wL6+xbmtubC0gLayraiyr7WyuLvCvra4wLi5x9fk4sLKzMaN0cTFwsnCwrq/w77VyMHAwcbCxL+zuLzBvbzBxczGvdO/v8vJ0NPHy8HBzdbHx8jEy9DGysjDz9PUxs7Iw77Iw8e9yW/cycvLzNLIy87NzsfK0c7Py2zM0czMy8/SxtPLzNjK0tlvRN3Z3dnX1dvf43Di4eDW1d3R2d3e2NFtbGpqbW5vcHNyeHV2d3Z2dXF0d3RyeHl8fX2Afnx5f3R3dHN2cnJ3d3t1fXx4/37/fv9+/37dfgGBm36IfYJ+jX0Bfs19AX76fQF+w30BfpF9AX6PfQF+iX0Bfox9rn4CAgQAgKacsJuYnJyfoZ6joaOmpKSkqaKinZ+hoqCkoqKfpJ+hpKWqq6yzrrGwtLKvsLGytbW5tbWztLS2trq3tLS5tLWzt7qytbO5ubq4yLm6trq/v7a4u7TDu724vLu6ubq4tbWxsra007i6vcG+vrq/uby+u7m4trm6u7m4t727uLzBgLSxurSxsq7NuLe0ubm3s8HBtrS1sLC0sLS1v7m8ubq3vby6wru5t7m0tra+uri+vb29vry8wb+8ura0u7q9t729u7u4vL7AwLa8vrq7t767v7+/urm7wLy4u7i8w7e4urW6vLq7v767u766vMDDw76/xb/DwsXAwMLEw8G/wL69gMDGz8TA28C8xra7uLm6vr3Bubm4t7i2tbi4ure8wbi4urWxr66wtLSxsK+usKuyrq6sqaunpKalqqanqqShqKWorKupoqWywqCenaSloZyfnqSfn5+doKGkoZ2YoZmboaOmqqSppKSlp6Srpqejq6unpaGmrqi48KCloZ+8oZ2dgKGjoqGkpqennqShYZ+fo6SoqaulpaKmpaS6oKGfo6Ggp6KfpKWrqqalp6WppKqurKqysaerrK2lpKenpKejqq+srKyuq7GyrKirpqOpqauqqamrsLCzqq6zsK+vramr566urqutq6qsramtqaOjo6ChpKOxt6iqpKikrLOtqqmogKq5o6OnpqamsK2pop6ipqKlpp+anpuZm6Gbm5acnJ6cnZunnZyUmZ/FoZuZmp6WlJqYm5qYmZiTk5GQkZGVk4+LkYuKiYyNi4+Mio+Li5CNiYaJjomLkJGLi4aEhH+EgXx9f3qDe4CGd3N2enJzcmtzeXdxdenl5+7n3XJzcdlqgNTP0NRvcNdx1dfW283M09HKzM7P0MnFy8C8u7y7u73Bs727urq2vMK/1bi4urO3uLOwjbK4sLG0rrGzr6+xwLWyrqupsKqpoautqa6ztqynrq2rrrCzwLW2uHO4r7S9u8m2uv26sbe4tbm7xMfCs8DEy9jIycvGxsXO4unTy8bUgMy/y8vFvcXX0sfAxcfaw8PAts65wbrFycXFysbIy8TLwMC+wcnCxcbBwcW9wszY1/zW3N3W2tLIycHB1szP2tXO1+Df2d7V3t/Z5+DR4Ivm3ube1NvZ19nY0dTf5trm39TW3eTy5uv39ufm3ez/8+3j6evr5uzn4ebm6OLn5tzigOze3d7b3NbW0dHNytiIdvXh6+Tn6fH2++vm7vn58uvl7u/l5Orq8fz0/v+GgICD+fv07/D694P3i/X4/P7x+/j3gf2DgYOAhIH//4D09Pj2+P/9gomDgoOCgoSJhIaMjpKOjYiQi4uRj4+VkJKLkJKQj46Rk5GUlZmUkZaRlpaZgJKLm4qHioqLjYmMiYqJiYuNk4+Qj5GRk5GUkpORlpOWl5icm5qem52bnp2bm5yenp6in6Cenp6gn6OhnqGpqKimqaqjpqSmo6Okt6m1o6apqKGjpqKvqKunqaioqKurrKypq62rv62tr7Guq6iqpaepqKampairq6qqqq6trK6ygKalr6qpq6fRr62rrKuqpra4r66yra6vqquqsaurqamprKyqsqyrqq2rq6mvrauwrq6srqupra+rrKusrKesqK6urayprK2trKSpqqippqunqqqppaSorKunrquwt6qsrKWpq6mpq62qqqupp6irqqSlq6msrK6rqaurqqmnqKangKirsqqnv6urvamsrKuqraqvp6ampqmoqKuoqaOlqKKip6SipaWlp6WhoKGho56lpaWkoqSioKGfo6Cgo56coZ6hpKKgm5+2yZ2dnaKkoJ6fn6Shnp6doKGlpKKfp6CfoqKhpJ+inqGho6CmoqKfpqalpaGkqaanzqGopaXDp6WkgKioqKaqqauppayraaaoqqiqqqynqaivray/qauqr66tsq6rrq6ysq2urq2wqq+wsK20tay0t7exsbe3s7WxtbaxsK+urbO1sK+0sK+1tLW0sLGws7S2sbO3tLO1s66y2bSzs7GzsrCxs7Gzrqutrqutrqy2uayuqq2orbKsqaurgK2+pqaqpaWiqqqqpaOmq6WmpaOgpaKfoKWfnpieoKCdnZ2ooJ6VmZ3DnpmYmJyTkJKQkpOTlpeUlJKRkZKYk5CMlY2NjJCPjpKPi4+LiY6Ni4eJj4mJjYuGhYB/gH6DgH+Cg4CLgICEe3h8gHp6eHJ6fXt1eOzn5+7o5Hd3deNzgNfU1NVubdNsyszN08nJz83FxsnJycXCyL65ur3AvsDEur+/vL23usC72bGys62vsa+rdbHCtLW3sq+wraururCxrq6us7KwqbCysLC1urGvs7Kurq2tt6utr3uxq7C6t8itsOiupq2uq7KvtLe9trq0t7+qrLCsra2wv9u3sK23gK+ntLu0rrTEvbausLHFqq+0sMywtq63t7KytbO1vLe/t7i4uMG8uby9vLqzt7y/ude0ubq6w7+7wLi5xr28w7+4wcfJw8S/xMbB2Mm5xnnGvsXDvcLDx8rFw8PLzsXNy8LFxcbOw8fS18jIwcrWzszGys3Pys7JwsjGycXIyMfKgNXGzc3LzsbIxcnHx9GKht/P0s3Mz9XY4M3IztnZ1M/R293V0tbT1N3S2NlvbW9z3eLe2tzj4HXaedXY2+Db5uLkdeBycXJvc3Lm6nbr7e7p7O7pd3t3eHl5d3d7d3l8fIF+fXh+e3l7e3yKfX97gYOBf36Agn6AgIaAf4SCh4iJgIV9k316f4CDhYOHhYaIh4mKj4qLiImMjY2Qj5COkY2Pj46SkJCVkpKQlZWTkpKTlJSXlZaXl5eZmJyamJifn5+cn6Oam5mdm52eu6u0pKerqaChopuqpaeio6KioKKjpaaho6ilt6WnqKmnpqWopqqtrKyqqayuraqqqK2rqq+2gKelrqmoqafZsa+srquqorCxqKeqqKuuqq+vtq6vraursbCvt7KxsLGsq6erpaKoqKeoqaanqqypp6WlqKSmnaKkoaGfpKamp5+lpaSnpaupr66uqaapqqmmq6ittqanpp6ho6CfoaKgoaOkpKeqqaSkqaWop6mnpaenpaOio6GhgKOosailtqmlrZ+mpKOhpaOnoKKkpamop6qpqqSqraOgqKOhpaeqrqypp6amqaOopKWjoqSlo6akq6anrKikq6mrrauoo6q8zausrbGyr6usq6+sqainqqyvrq2osqmrr6+xtLC0sbS2uLa7tbWyt7e0tbK3wcDD7bnAvLravrq4gL2/v77DwsLAt766hL3CxsfNzs3HxMHGw8PSwsXEycjGy8XCyMrQ0c7OzcvPys7S0M7Y18rU2NvQ0NbX09XR19fV1NTU0tnb1dPX09HX2NnZ1dXV2Nfaz9HW0tDS0czR9dfY2dXW1dLS09DSzsnLzMjIysfT2snNx8zI0dbRzc7OgM/vx8jNxcO/ysXEv73Bx8XGxcC5u7e1try2t7G5vLu5uLfDubittLrkuLGsrK6joqWkp6qqrq6oqqWmpqixqaijq6Kgn6Kin6WgnKKcnKCdmZSYnJWYmpyXlY+PkImOjIyOkI2cjZCXh4OHj4iKiICJjoqEhPnw8vr09IKEgfiDgO/t7veBgfqB9PHw9Obi6ubd3+Pn5OLg5dvU09PR0M7Wys7R0dXR1d7Y+cnKzMXFxL+3dLe/uLvCvr7Dvr27yr28t7e0vbm4rbW4s7G1u62prbGusbS3xby+wY/Bt7m/t8Csst64sba3tLeyur3CucTBwcOxtLOvrbG6zuHDs6mzgKmlsbeyrbnSxb2ztrbHrrCzq8yss6yytrKztK2vtbS2sLGurbStrKuurK2mp7K5t9+7v8PAxL6zs6mntamts7Kotby/uru1urqzzbqtua/WvMPEusPExcvGxMPR0cXLwbi4vsHLv8PLzr6/tcTWz8rAxsjOzNHMxcrHyMPJx8fGgNHJysfExMTFwcG8u8aLgtbIysW/v8bJ1MLAx9XTzcbDzczFv8TEw8zEz81taGptzNPNzMrV1G/RdNDT19rP2tfXcNpzcXJwc3DZ3G3T1NfY29/abXFrbG1wcHV6eHh9fH13c251cnN4eXqOeXt3e3t4eHZ3eXl8fIF9fH97fn9+/37/fv9+jn4Bf/9+6n6GfQV+fn59foR9BH5+fX6qfQF+p30BfvN9AX6+fQJ+gJx9hH6HfQN+fX6IfQJ+fYZ+A319fod9rX4CAgQAgJ2go6Kfm5+em6CooaWop6OmpKWloZafoKOpqqemqKajqqisra2xtLW4uLa2s7K3tbS7ubq7vMG5w762vMK9t77At7m7t7i/vLzAvbm5uLm5xLe+wb68v725uLq8ubm4tbaysbGzuby9u73AwcDEwsK9ub+6ubi5vbi8urq3u7e1gLm6u7u5sbe1t7a2t7y6u7u7vLi4ubi4ubu8vLm8vLq4u727u7u/vr25vb25wMPEwsrGv7y7wr/Ku73CwsPAx87H0MK+wMDAv7/CwrzAvsDCwMjEw8bDvcDAu728u8e+wcPIw83GxMLExcjAvL+8xMPExsbHxMrGyMnNycfKxc3JgM3KycXDw8DEvL25ubq8vLy9vby7vrq0uLq4t7vBvrq9uba0s7Sys7Gzra6vra+wrri2r66uq6Wjoq2sp6ensauptqusqKivpKanqKWkpaKhoa2joZ+hp6Wjo6GaoKGfqKWnraulqqampKWmqqepp7KrqamkoqWmqaWop6mloqOsgK2nqaqnpaOnpKaipKOnq6qsqY+rq6appaWfnqmnqKi5rqmlpqqpqqWoqKqornyrr6utraurrKepqaKoqqilqLO+tLOssrHBt6qnrKiorqyurLW+sbDEtbazsLWtr6+vtbCuq62wr6utrq2xq6qvrKagoqSkraqsq6uora+wrrK3gKasqayqp6Gpraeppq2doKekpJ+hoZ2goaSnoqWjpKajp5+goqGenpyfp52hpLKdl52eqJqZnq2alJeWlJaVkoyOkYqJmJCNjZCSj4+Rk5KUkoyPkIuPkpeHiYeHhYeKg4GDgoCRh319fnx6gXh4gHNyd4Xqe/fy6nhz5tbf5eZ0gN3c19ByctjZ2eLR3+HY0+Hc3tXLzcjGw77CubfAwcbMwM3Fv8O5s7OysbGxqa2qs7S3t7y8uLGzt7auubLAxbq4sLitsK2srKymqay6ta++sbm1tLatubO3uLa/07q4u7a3ucDLurmysbi1ucS7xMLOzcrLxMnHycfLvcrOzbvxgMLMyr7Cxc3OysPRzcLAsb6/ycTKxsfGycfEybvCu77Kwb3Hw8W/yMXC1c3JyszQ2NPP4NbS0dbLyczo09+V9t3i/ejq6eLh6ubl3+Hb4ePd3ePb2eDh09Xbz9bM3d3h2uHi5ODw7uvz8+v4hurq3ujo5/Lp7+jp4eLl5eLh7OfjUt7j29fd1uvVy9nR2dnj6+7o8e/x7PT5j+zu9PH16/7z8O/w9+v5+vz++oOEgoH+gPb3+Of89Pvz9/aC+YD+/v/y9oCJg4GDhYGAgYCCgf6A/YCEhCqChYGHhYWFio6Njq6Plo+Oj5WakJCTlZSSmZmXmJibmJuXlZCQk5GWlZmAiYyOjIqHiouIi5KJjYyOio2OkZKQiZCSlJeamZiZmJacmZ2cmZufnqCfnp6bnaGgn6WgoqShpaCoop6jqKemrK6oqammpamlpaqpqKmmpqWroKSpqKitrqupra2sq62qrqusra6ys7CsqquqqKyrrKimrKmpqK6wr7Sxsa6xrKqArKyusK+pr7Cvrq6tr62ur6+vrq+wr6+wsa+vqq2rq6uts7CwrrGwr62vsK6zs7GttbKurKuxsMCsrrWwr6ywtrO7r6yur6uqqKytp6uqrKypr6uqrKyoq6upq6qps6mqq6ypuauoqKusr6qlp6Wsq6qrra6ssa+wr7GuraymrquAsK6wra2vsLSvsK2tr7Cvrq6sq62uq6eqq6mnqKqnpaipp6epqKeop6mkpqSkpaWgqamlp6eopKKhq6eioqKooqG3o6OipKieo6SloqCioqChr6KgnZ+ko6KlpKGlp6OppaWop6KmpaWjo6Kno6Wkr6ipqaWjpKSmpKimqqmlpq2ArKmprKusqq2qq6erq626q62prbCwq6+srKqrs7CyrsS3sKutsa+xrrOys7C3jrCzr7KysbS2s7a3sbW2srGwuMS3tq20tsS8srK6tLW6t7e0usKysb+3uLa0uLK0s7O4s7Gxs7W0srW1tbezs7m3sa+xsrO6s7Sysa6xs7KvsreAqK6trayppauuq6yruaOjpqWjoqWkoaKjo6WfoZudoJ+jnp+inpubl5uhmZ2hv5uUmJiilJKZrZaRlJSSlpWTjpGVjo2dlJCRlJOQkJCPjo+Nh4qNiI2QlISEgoOBhIqCgoWEgZWJfX5/fnx+foCGeXh9hOd16ufnd3Xq3ODl6ISA4N3b0HBtztHQ1cnQ0MfBysjPy8bLysjKwsO6uMPAxcm8zMO8wLWysrK1s7Wtr6qwsa+xt7u4tbu7ua+2rLO1raulsKmtrK2vsqywsLy3tcCzurW0ta22sLOyr7/TtrW4tLKws76trKqqsq+2vrS3srq6tbStrauwsLSnsLGyps6AsL64rK2wtbKuqrm0rbOmsLC6trq2tbW2tbO4r7W1tsXCvMK8wLi+vLzQw7q6uri8uLTCu7u/w7y8wOLByn3PvsHTwMXEu7vFxcTFyMPGysTAxsTEys7Gx8rByr/JztPN0MnHws3GxM3Lw9FzyMvFzczI0MrQzM7IysvOzMrUzsyAx8rGyc7J183G1M7V09rc2dHV0tPO1Nd4z8/X19rS4dnW1tTZ0trd29zXb3BucNxw3+Hi1ung4dvf2nLdcubj5dzfc3p3dHR2dHN1c3d57nftdXl5enl4enh7eHh4e39+f6B/hHx7fH+FfXt/gYB/hIWCgYGCgYWChIGDhoSJh4iAf4GGhISChYeDhYqEh4aHg4aFiIqHgIiLjZCTj5CRkY+TkZSSkJOWlJeXlpaUlJiWlZyZm52fo56knpaZnZmXnqGcn5+dnqKbnqSioqSlpqavo6irqKiqqqWjpqempKWkqqakpaeqqqmnpqipqa6tr62rtLCvrrCxrK6rq6esqqqArK6vsK6nrausq6qqrKurq62vrK6xsbCxsrOxra6tqaissK2tr7OysKytrKmtrKuprquopaatrcypqrGrqaaqs66+p6OnqaWjpainoqelqayrsq6tsK6prq+srqyrs6iqqqynw6mmpamqrammqKSrqqioqamorKqrqq6pqKmkq6gLqaeopKSmqKulp6SEo4SkgKaopKGnp6alqK2ppqmopaOoqKipqq2oqaelp6ahrKuoqayuqaWmsq6rq6y0q6m5q62sr7iws7S2sq+vraurvKysp6murqutrKWtrquzrq+0sq+zs7W1trW6tLWyura2t7W3u73Bv8TBxcO/vsXGv77AwsHAw8DBub6+wtLIzMqqgMvNx8vHxsLEz8zPzObXz8rN0c/QztPS0s/w1tDSzdHSztLU0tPV0NbX1NHU3e3e3NPd3v/q2NXc1tXc2NrX3vDY2Ofa2tfS1s/R0NDa1tXU2NjY1NfX1dnT09jY0czOz87Uz9HOz87U1dbS1tnEy8rOzMjBx8rExcPSvb/Fw8G9gL27tbi6usC9w7y9wL7Cury9uLOyrrG7rrG12qumq663q62zza6pqaamqaekn6OlnJyqoZ2boJ2am5mbm6CemZ6gm56fpJCQjI6LjJKLi4+Niq+Pg4SGhoWBi42Wh4aKkv+C/fn6hID86/P5/Jj3+PPvg4H19/X15e7s4Nro3uPggNvg3N/f29vQzdbR1NjP2djQ18zFyMrMy87ExL3DwL++w8XCv8fJx7/Kv8nMwb22wLe7ure3trK1sr+5sb2st7Kzt7O/u768uMXdurm+tra0ucy6uLKxtbG8xri9tr7Bu7iwsa6xs7uvuba5qN6turmvtbjCwL23yMK5u6q2sriwgLWvsbGzsrC6q7OxsrqyrLKrsKeyr67Ct66vsbO6uLXFvLq6u7GtsdeyvILItrjHub6+tbXAvLm4t7K1u7K4v8K/yszFxMm7wLjDwsa7wb6/ucnCxM7LwdNxyMjAyMbGz8bMx8jCxMnJyMfTzMXBx8LFycPaxbvJwMfDytPPxcjGbcO+xMlzwsXMztDJ2tDNy83RytTV1NHPa21rbNZr0dPTwdXS2NLY13HVb9rb3tbZc3x2cnNzb2xubG5v3W7abW9sbG1sbm91d3h2e3t5eZ94fHZ2eHqBeXl8fn99gX9+fn19foF+fn1+f3t/fn3/fv9+/36VfgF/mn4Bf/9+vn4Bf4d+B31+fX19fn6FfQF+hH2Cfv99uH0Bfq19AX6rfQF+kn2EfgJ9fop9A359foV9jH4DfX59r34CAgQAgJ6boqWgl5yZl5ugoqOnqKSnp6mqqaakp6SjpKOgq6Ogpaewqq6wwbW1ubm2tba4trO3u728u723vrm1try/vb3CtrzDvby9vsK/uLfEv7vQusLAudW82cK6wri7wMK9v7y5vLm3uMW/wsHAv7/BxMG+vsC8vLy/wcO9v8PAwsS/gLy9vr7EvLq8tbS2uby9vcC9v7+7vry5vr28vL+7vsK9u8O9vsXBu77AvL2+v7zCwcTGwsXBwsXCv8TIys7Sy8zLyMbJxsHBw8TBv7vBvr68v87HxMrIy8HGwczLv73AwcHF0qjOxMXGyMbHxcfIxcvHycfHyMnIycrMzcvNzsvPgMrQzNDIx9DDwcG7ur6/wsDAx8O/ub+7vb290cTEwL29ubu3trq6t662r7Svrq2vsrKzrq2vr6qmoqWmqa2tqa6rsKurrq6kq6aqoqOtqKioqqmrqqqtr6uoqqunpqmnpaShpaSmqKako6muo6aopqKnpZaUo6Wpoaimpa6mqKuogLGpr62vr62tsa6pp6inrLCsqq+vrK6trbWrqKuvrKuro6mprKqtrqq2rq6srbCxwM2vsbOrqaytrKuosq2rsq6qrLGys7OysrGrrK6tta+0t7m4ubq6tba3uLG0s66ys7KzqrG4u7S1tLi1rbK1r6yrrayrqa6vrKiqp6ussK+vgKiksLCqoaWpq6iprKilp7akq6WmqKSnprGjp7Wkn6itqKekpKeipqKjoKCenp2cl5mem5ebnaCcnJeSl5SSnJSTj46SlZKUmJmXlZecmJaWl5OVk4+Sm5GJioiJjIqNh4qLhYKLvIF+f3iAd3aId3V4gH+BhHx7enh6eXXZdeTfgN3g5OXY4uR13d3N0dLc2trb1+DTzc3MydPNxcjByMLHyce7u712v7y4srWysLK9vLS6vb2/vcfAv7u5ubfFyMGzsbzFubK2sbCwp66tr7W7w77AxMPAvLu+tLW1tL6+yM7Ev8G/zc/Evbm+v8LGx9LGyMrMw8bM0MXEzNHUz73IgMPEw8G9zsrY0cXKyMvCys3K5sfAw8HKyMbBwcrJxszLy83MycjIysjJ09XMxs3L2dbU1tnTzNXUytjc2t3c+uLi2+/f6Ozn49vk4+He4NTY2dzc3tzc2t/W2tXd3N/j4uXh5ubq7erq7u3q5+fw947r7e/v5OLl4+Tp5uDr5fPjgNjv5OLg49ba3N3Z2eLi5Pfr8fD28/Hx7PPy8+/s6e/18PP2+frw/4KAhoeHhP759P30+/Hv8fn/+PX19/72/YCB+oaQ9/j8+/z5goGEgf7/gISJh4aJh4OGhYiGi4iLjYqVlI6OjJCWmpuZlZeWnpupnp2Vn52fmJWSlJSWlZWagImIjZCNiIuKh4qOjo+QlI6QkpOWlZSTl5WUlpaWn5iYnJ2jnJybrp2bn5+dnZ6ioZ+ipKempKejqaWio6apqaqup62xrauoqqyrp6q3ram6pqyrqL+txLKttKytsbGusLCvsrCurLWtr6uqqqqsr66rrK2pq6qwr7OvsLOwtLOwdK6xsbO7trW7s6+ws7GurbGvsbOytbayt7Wzs7SytLayrraxsri2sbS0sLGztLG1s7W1srSws7ezsLa0sbGzsLO2uLW2s6+ur7GwsK6ysLGrq7uxrK+usqywr7m/rKmtrayuyLC2ra6wsbCvra6tq6+srqythK+AsbGzsLKvrK+rsK6yr663sLKzsKywsLGvr7Wxr6qvrayqqbesrKupqKWpqKqurqulqqWrqKinqaalpaGjqKmnpaKmpaWmpqKkoqijoqanoKelq6WkrqqopaakpKKipaanpKiopqapqKeloqenqaqnpqSnqqKlp6ekqal1haanqaSAq6ior6mprKuwqa6srK6urrSzrayvrbGzr6ywsa+xs7TEsa6ytbW0ta2vr7GvsbKvurOzsLCztcXMtLe4tbS6urm6ucS6t762sbS5t7m6uLu4tba4t7u2uLm5t7e7t7a3u763urm0t7e1uLG2vL65t7e7uLK3ura0tba1t7a6u7mAtLOvr66wrrCrqbq6sqeqra+vrrasq6y7paqjpqmlp6ivoaKtnZmjqKKjoaKinqGeoJ2enJycm5WUmZaSlZiglpaTkZaWlqCZmpeUl5mTk5OUkpCRl5KOjY6Ji4mHipWMgYJ+gISBhYCCg4B/iMN+fH95gHt8n3t6fYJ+fYF4dnaAdXZ2dt935uHe3uHfz9necNPVycjGy8nHx8fVy8nLycrVzsfGwcTBw8TGvby+gMO9vLW2s7Cytbawsra1u7nDv7+0srCnsbOvpKSwu7KwtrKzuLK1s7K4u7+8vsC7tK6vsq+ytrS8ur6+tK+xsb7Au7Wzubi7urbAsrO1trO1uLWArauwt8vJs7i5vLq2sb+3v7SutbK0r7a8uNm4srS0uLW2r7C3s7e6vr69v769u7u9wcnKwby/vMXBwMDHwb7Ew7rHyMTJxdfGw7vJu8LEwL66w8PHyczFyM7LzM7MyszRys/N0czQ1dHUzczKy8rIys3LysfIztV7yMvP08zQ1NCAzs/QzNXT4c/A0tDS0tfP0NbW0NHU1tLj1tXV19TQ0tDY2dvb2dba3tvd3N/g1t9xbnJycm/a2djk4ejh3+Dj6OLi4OTs4uV0d+Z6hOjo7O7y73p4enbl5XR2e3t6fnx5fHp8en99gH98hoN9fHt8gIKCgH6Af4eElIWEfoaGioUIg4OEhYeFhIeAhH+GiYZ+g4N/gYWGh4aJhIaHiYyLjI2Rj4+RkI6XkI2QkJePj4+fkY+Vl5WWmZuamJyfoqGhpJ+nop2coKGen6SboKShoqChpaObnq+qqbylq6mmuqzKtayzqKqurqmsrq2vrKypsairp6WmqaqwsbCwtLCvr7Oys62vtLG1trKAsLKysbiysLauq6yvr66sr6ywsrK3uLW5uba0tLGysq6ss6yss7Cprq+rq6ysqa6sra+rrKepsKyrsLKurrKtr6+vrK+sqqmsraqopKqmpqWnuK+ssLCzrLOvucCrp6mqqarU57GoqKqqqqurra6ss7Cwr6+wr62sra2urK6tq62AqK+sr6mosqmsrKumqKaqqKeuraunq6mqqKi3q6mopqilqKeqrq6sqK+prqyrqqijoqWkp7CyrKmkqKmrra+oqqSrqKqwsq22s7iysbmzs7Gwrq2sq6+wr6uusK+usrKwr62xsbO3tra3u8G3ubq5tbu6ZZu8vcC5xMTEzsjJysiAzsTIxcfFxcTLx8C+wcDEysjHzc3Kzs3R6dDM0NPR0dPJz8/T0NTU0dzU0s/Q1Nbn/9TY2NTT2dnb29vh3Nvh2tXZ39/g4uDi39vc3dzh2t3f393d4N7Z29zf19zZ1NfY2NjP2d/h29ra3drS1tjU0NDT0dPQ1tbSzs/Oz9HU09KAycTQzca9wsfKx8bKx8fL3MLIv8DBvcHDyrq7yLezwcW+vry7u7e7t7m2tbGwr6ynqK+tqK2wwa6uqaWura26sK+oo6SknZ2dn5ucn6OioKGjn6ShnJ+soJCPiIuOjZGNkZKMi5TCiIaKgoqEhryHhomQi4uPhYOEhIWFg/aC/PWA9fj6/O/8/4H1897h4OXg3dzf8+bi5+jj8OTX1s3TzcvOzMfGyoLNzczGyMTAv8LBs7i5uL6+zcrOxsLDvMrLxbe1xczCvb+4tLyvsayvsbO4t7q9vr67wcbCxMK9xsLJy8C6vLvJzMa/ucHBxsO/y7y+vbyxsLi9tLK+x97RtruAu7q4uLnLxNDEt8G9u7G1u7bLtq+2t766uLG0uba3vLq5t7Wvrqqrqqy1tq6ora24ubq8wry0uLest7i3u7rPvrqywbS7vbu5tru8vby9tbrBw8TDxcHAw7/CvcHAwsTBw7q/wsbOz8/RzMzJxc7TncPHysvFxsnFw8TCvMPB0cOAvNDKycvPxcnLysbCycrK28zNzcvHxMXDyMrNysfEyszIzdDP0MjRaWZrbm1r1dHM3dXazM3S1dva2dfZ39LVbG/Vc37c3OLi4dtyb3Jv29tvc3Nwa21ubXJzdnd6eHp4dX96dHJxdXp+gIB8fXyCfpp+gXiAgIaCf4CDg4SAgIH/fuR+AX//fo9+AoB//37/fop+An1+iX0Bfp19AX7/fcp9AX63fYZ+kn0Ffn59fn6GfYR+gn2wfgICBACApaKgr6ebnaGopqmoqKynqKirp6ynq6WmpLSprqmkrauusbHCsbC3tbTBv7i8wLm+wMC/w8K+uLm6uL+5v8TDw8bI1L27yMDBv8XAvLq9vL63vsG8u8HAvr3BxcXBxcXAtrvAub3CxcPGyMjFw8fIwMK+vL69vb/FwL/Bv7/AxsBwwb67wr+/vr+5u8K+wsfCwcHGyMbDwr3AwLu/w8LCw766x77AzMLCxMjIzNjGxsjFxcO8wsLDwtDDxsXIyc3MysrJyMXJvcXDyMbFxcbJw8TLx8rL0MvNyMvMxsO+v8LCw8nPy8vJysfNycjIxcbGxYTIFsvHx8vN1NDRz9DT1tPTz83SyMfEwcWFw4DEwcnQzMTHwM3HvsW7wcK+wMC8ur28u7q9t7O2sLS0t7O4t7i0tLCrpa6trqyrramur62tqq6uqat9vKGiqaalqqysr6utrqqqr6ypqq6rqKalpqikrauurK6wr6ysqqyuqKeutK6trK6rsqy0rKWmoaesq62usLLDqa6rqqits4CwrbKwr6uxs6zDsbCsqrO5rq6wr6+zsrGxsK65sbO1wrizubK0sLGtrbCvrq6tr6qoqq2usrGusbG2tLOysLG3t7e6wru8usW5uLq8uLi/ubu4trazuLm4uLa3uLPCt7m8vbO1rrG0trO0sLOtrLKtrqytqKysraynpbKpqqqqs4CwqLClp6ekpqarq7DKsKmvr62mpqKkoKOmpaGgmZ2doJ6hnqCip5+fnqGZmZKRkZOQjpGal5iZlpmdnJ+io52bmJeamJSTj5OUjIuNioyKjIaIhYJ/hYWIgYSFlnx5doB+gH18fnx5e3l0d3Vzduvse+vs73vi397f1NrVx8PQ0IDQ18nKzdTO1snVzcrL0NbJx87HubvDw7y4w8K0trqxs7K0tbvDw8PIx8DDurW2vcDDvLW1r7Owvby7vb+3ubu+xcm+wMHMuLu5uru8zMLIx867wL7BwLy+xb7D0MG/w76/xdzP0cjIycbUxsHC0s/H2b3EwMjEwMfN1sXNxMzQzoDJxNLQwb/Dy9HUytXOy8nPzMvQ1M3l083T0dXPzs3KztTXz87N1NXk2djd+d3d4t3l6OPq6vbw7+rv7ujp6+Tf39vk3t/W2t7g3dnf5+Pk4ujo4OLk4+zw6PSF8eDk6/nv6evr4evy8Ofo7efn6enj8OLr6eno5eTd3ufr49/m6HHx8oL2+POB/Pj09PL4he3t8vb3gYuB+vDv9vf3gf75/4CB/oKC+f2ChYGBhPr1+YP//YH/g4CAhIGAg4OChYWHg4CGiIuLj4uNiZmOjI2Plo+Ni5SOkpKXmKaWmZiXm5mYnJydm5yjm5mblZiXmZaWli+UkpCll4yNj5SQkpCQkpGTk5aWm5eblpiUoZidmJSam5+io7OhnqSgn6iloKSmoYSkgKmqp6Omp6OqpauusbG2udmxrbisra2yrqyssK6wq66vq6uysrGvr7Oyr7O0samws66vsbKwsLKxsK+xs6+yrq6vsbCytrSztbO1t7u4uLWyubi5uLu0s7e0tbexsrG1t7e1trK2trO0ubi6ure0xLa6xbe2tbq4u8q4uLi2t7avgLW1trW+tLe2t7S3tbKzs7GvtKuys7W0tLK1t7SxtrCzsrOwsbCxs7CwrK2xsLCzubS0srGvtbKwsa+xsbCzsbGytbGxtLS4tbWxrK+xra+trbSvs7GwsrCvsK+usq+1urewsq66tK2yqq6vqqytq6uurq2ssaypq6eop6mmqqquc62urKikvKqrqaeopKeop6WipqikqYHBpqWrqKWnp6WmoaSlo6Srqaersa2rqKamqKSqqaupqKqopaakq6yrq6+4r6yqrKquqrKtqauprrOysbG0s7+rsbCwsLK3tLKzsrKutLe12ru8uba/wLSztLO1t7WEtoC6trW1v7WwuLW6uLy7vL6+vr28vbm1t7i7vLy5vL3Avr+8ubm+vru9w7q7uMG5u73AvLzDur27ury4vLy8u7u6u7bFu77Dxru9uLq8vbu6trq0sbWztLKyrbO1trawr7uxtbetsbGrtamsraurqqmlpb2ln6WmpqKjn6KeoKWopICknqGfoZ+gnJydopucmp+ZnJWXmZuZlpidmJeWkZKVlJWXmJOTj46Rj4uKh42OhYWGhISFiIWFg4F+g4KHf4ODlXt5doOAg316endydXZzdXJ1eOrreeTh43bX1tfY0tbUysfLzM/RxsXN0c3Qx9HHx8bKzMPAyMO5vcTMwLrDwYC0try1tLW1s7O4uLW6vrnAtbCur7CzrKirqq+st7Wxs7Swsra6wMa5u7zEsraztLC0wri+vcqyt7O1sa6wurS4xbexta2xt8y9uLKzs7C8srGxwsK8y7e8ur21s7e6vbW4sre9vbu3xcS3tba7vr61vbu4tru7urzButK+u8DBw4DAwL+8wcfHwsC/xMTOw7/G18PGyMXMzMbIyM/Ix8THyMXIzsjLy9DX0dHMzNHSz8rT1tTT0NXYy8zQz9LTztd32MnM0dfPzdTb0tXa1c7O09PW2dra6dPW09fY1tbQ0Nrg2tXb2t3cc9bTzm3Z19jb4OV8393g5OJ0fnbk293h4Fbdc+Da33Fz5HV24OJydHJxc97e5Hjv73fsend3e3h4fHt8foB9enl7e359gXx8eoh/f36AhH97e4F8gH+Dg5CChoOChYSChIWDgYOIhISJh4yLjYqKiHGKh4Wjk4SGiZCMjYuKi4aHh4mJj4qQjI6LmJCTjomQkJKVla+RkJaRkJualZmemZ+hoaGmp6WhoqOepJ6lqKenqqzpqp+qoqKiqaWhoqaorKarrampsbCsqaqrq6iur66lrrGprrCwrKytq6qrsLOxtYSygLCxtbGvsbCwtLq4trSytrSzsrOrq7GvsraxsLC2uLm4ubW7vLe4uri1tLCswK+0xrKwsLKxtMGxsrOwr6+ora2urrivs7Kzr7SzsLGysK6zqbCusa6urbGzr660rq+vsqyuq6+wrayopqmqqq2zr6+vrqyyrq6wr7Cwr7Kwr7CzgK6tsLG3srOvr7O2sbSxrrOsr62ssK2rrayqrKiutLOrrqu6r6iupqusp6urp6aqqqqrsqyoraqqq6ymrK+1t7a1r6rIr66rqamkqaqoqqmvsrG3teOvrrWvrbGzsbOusbGur7OwrrG3tLSysbO1srm6vby+w8PAwL3CxL+/ydHIgMbFyMXJw87KxcfFys3IycjLzdjEycXEwsbLyMbLy8zKz9LO/NLT0M3X4tHR1NTV2NXW1dXU2dXW1+bZ1NvY2tfa2tnc293c3N7a2dre4ePi3uPg5OLi3tvb39/b3+fe4N7j3d3f39ra4Nnd3dve297f3uDe3+DZ6dzh4uHV1M/RgNXY19jU2tTT1tHRzs3HysvNzMfF1crS2MrS08vVxMXFwsG/vry/08G8xMTEvb64u7e6vcC8urG0tLSys7Gys7mztLO4s7Wsrq6uqKamrKalo5yeoZ+ip6Wio6Cip6qlpaCkpJWRj4yMio+OkI6LiY6LjoiPj6eIh4WRkJCJhYeHgIWHh4KEgICC//+C+Pn8hPP29fnv9O/h3N/h5ejb3ebq5ezd6N/Z19vd0c/Y0cfL1d3PyNLNu72/t7Kys7K2v8TCzM7IzL65uLq+w8K6v7q+u8LAvcC/t7W6uLvAs7a6v7O8u7y+wNHHysnRuLy6vLu4u8PAxNTCvL66u77Wwby2gLO5uce7u7zOxLrLr7G1vLi4vcLIuL23vcK+vbfDxbW4u8DEwbfAt7KwuLi1u8K6y7i1t7a0sa6trrS9w8C8usC9xru4vNG8v8S/xcW8v7/IwMC/w8PAwsS+u7m+yMDDwMHEw7+6xMjIxb7ExMDFysnO0dHXfdTBwsrTyMTHyb7GgM7KxMXGxMTFxcXrwtLNz9DNz8nM1dzQys/M0s9vz83Ja9PPzM7P03TKxsnLyWl5bM3Fx8zP0G3a2d5vcdxvbdDWbHBwcHHZ1ddv3dlt2XFtbnNwb3JzcXJyc3BwdHFwbXFvdHJ/dXV2dnx3dHF4c3d2e3uHen59e359fH1/gH5/C4WBf4B+gYCCfn59/37/fsp+AX//fv9+tn4HfX1+fX19fv995n0Bfqd9BX59fX1+hn0BfoV9g36GfQt+fX19fn59fn59fYV+CH19fX59fX59vX4CAgQAgKGhoqOkoZ+ioqanpaeqq6msrausp6+qqqutrq+ssLGwsKuvt7C3t8C9v8q9wcjIxMPEwb/CwL27wLy+vcLAw8jGxcnLwsTFy87SwsPAwL63uL2+vr2+usHDyMrLzc3J08W9xsbFysnTytDOz8nJzsrBw8DEwcPDxcPCw8bHydTDgMnDxMXAv8XBur3BwsnGy8XGysrJw8TFwMDCycfNwMTAuMfGx8LFycrKzcvRyszJysvMysXExr/Fw8HFw8rKz83LzcnHw8PCx8bHvcPF0MfT0tPR0M/OzMnHzMnHxb/CyMnIzNHVzMzL0szMzcjOzNDSz8/Lzc3Jy8vJ49TT1NXPgNbU0tTTzsjQzczKzsvLzMvIy83JyczJzcTOwsHHw8C+wMLBwcXFwcC7tLi5ur25t7e+yba1zLKwta+wsq63rbeytrS1sbetr6+qoKamp6epsLSwsK+4rK2tsLCwsq2sqaqqr6qqp6qvsKywqq2qrK6xsbOqra6mtqytrrqzsaysgK2tq62qrK+qrK6tr6yssa+rrqqtrayvqa2prLCusLCvsa6wtLWvrrO8tbCwtrizsLKusa+ut7WzsLO0r7K1u7O7s725tbS1tLu3ubi0vbu9v8LAur27uby6hLm1t726wLu3ubu5ube4t7vDura2u7m1tbOysLGwsri3wLm0sbazgKynpKOop6iqpqGrsKertbuwsqmur66rrK2ysrS1tq6ss7KqqKympKGjpqKeoJ6ho6ejoqCgnZ2apJqcmKOWlpuXmJqbmpmioKGkpKGjoZean5eUppKTjo6LkZCOkZCLjIeHhIaJioabg4iGfXt+fo6Jg42FgX9/eXVz3ePoed6AgN/neON36d3i1uHb1eTT1dfW0N3W19PWzdDIfNPZ3cvDyLa5v8G/wLzEsrjDvri1rry1w7m7vcPOyr3Av8G9v7/Iw7q9urm8tc/Ku7u8wcfTwLq9xru7usq/x8PF0NHK9sLJzMbMxcTIztzaxsXGws/Oyr3KysfCyc3IyczLxsTOgNDJwODNvcbRy8zW38/MzdrCzsXSwcnR0dLTxs7P0tPX09jU2d3m34rv3djV09TO1N/c0dfQ1Nri3N3j2+bm3Ibk4Pf99/n26fDn5fLn7+vp4ODe297l5uDm6+br5PHs6ejr5+jk2+Ll5+ft8PTu/e7t8ezi6eju8PDg6uPt7el2gOTz+fL28evm7OTl3ezs6+38+IWA/oj39+7t8PHz/u36/4OCiPOC/vn7/fP4gIKEgICChoCB/YGGgomOi4qDmIyMhYOC/IOEhoCBg4OEgYP+g4yJi46LjYqOjpCPkZOVkpSSj5CTlpqbn6Ccm5qdn6SipaSipJ2dnJmZmJyanJqcc5CQkZKSkZCTkJOUkZKUlpaZm5udl56Zm5mbm52bnqCho56ip6Cko6mlprCkqa+vrKqpp6arqaiqrausqauqq7Ctrry9srOvsra6r7Cxr6+rrbK1trS2sLOxsrOztLWyxLOttbW1t7S9trq2trOyuLiztLKEtYC2tbSzuLi9x7jAuLm7t7i9vLW5u7+/u7u3uLm8vrq7vbi2tru6wLm7ubTCvr63t7m6ur69wru9ubi4ubq2uLy5u7m1tbO2s7i1s7e0tLK1tbu4urGztLixure4trWztLKysri3traxs7e4tra4vLe0tLu3t7e1ube5u7W2tLa2tIC1tLLNure0s6+xsrK0traxuLm1s7WzsrGysLO1srKysbSvurKxs7CurKyuq62wsK6wraqvra2vrKurssmvr8WurK+oqqqnrairqKupqaeqqKutq6WqqqqoqKytp6enraSlp6mqq66pqKqop6yrqamtsLGssKmrp6utsLG0r66tpYCwq6utwbKwrK2ztLO0tLW3sLGzsra0tLm3tLezuLW2urW5tLa3tLe2tbWytLu6tbS5wry6ub3Cvbu7uby7u8TDwL/DxL7Bwse8wrnFwb69wL/FwMG+ub27vb3Avru+u7u9woa+u77Cv8S+vMHDwsG/vr6+xL25u7/Bvry7vr69u4C7vbi+ubWzuru3tLSyuLW2tbGttLWrrbS7r7CqsbCuq6upq6qqq6ylpa2xqKeroqGeoqWjoaOgo6Sno6Kfn5yemqObn5uinJyfnJybmpaSmJeWl5aWl5aQlJeQj6KPk4yMiIuIhIeIhIaFhISFhoaDlH6DhH17fYCPioOKgHx6doBzdHPk6O553X/e4XTec+HW3tTb1dXfysrKycfSzs7MzsfHvn7Iyc3Aur6wtbzCwcK9w7W4wr+8u7O/ucG4t7m8ysS3uri4sbKxurews7O1trPKxri1trvAyry3u8K6uLXBtL+4u8TGv/u2u724vLm6vsDPybm3u7W/wr6zu7m1sYCxtbO0tbq6uL7CuLfYwLa+xrm3vsS4uL7LuMK5ybK7w8XDvrS3tri4vbq9ury/wsF30cTEwsHBvsLKyb/Evb7Cxr+/wr3Ly8V3ycXS0s3Ry8THwsLOx87N0dDQ0tDS09XO09XT087X1dTW2NXV1MnMztDLy8zMy9rS0tnUz9PS14Dc283b1t3f33bS2drT2dXV1trW19Hb2dXU3dxzbtp12tzY2d/h4enZ4+h2dn/eduPj5OTb33JydHBxcnpzdOR1eHV4gX1+c4h9f3p4ee98fH15eXl6fHp69X+EfXt8fH9/goGAfn9/gX6CgX+BgYKFg4aHhoSFhoeHhYiGhouIigmLi4yMjo2OjY2AhYaGiImJiIuLj4+LjIuLh4iHh4mFjYyQj5KTlJGTlJOTkJSZkZeWnZmapZeboqKgoaKgoqempaSopaWlqqeorKiozsilpaWpq6+ipKeoqaWmq66urq+nrauura2ws7DMtLC2s7K0sbiwtbO1srS8vLW6uLu3t7a2srCxtrW6xraAu7W3t7Oyt7WtsLW3vLq7tra5urq2t7m1tbe8ub+ytbKru7e4s7O1tbW3t7+2uba3ubq5s7S3srOzsbKxtba5t7S4tLSys7S4trWqra6yqra0tbOysLKxsLG1tLGwp6mtrquvs7avrq2zrqyvq7Gvtbiys7G1s7Cwsa+9tbO1tK8HtLe1tLOyrISxgLSztLa0srS3s7Kyr7Gps6eoraurq6ytqq2wrqyvr6uwr62vrq2tt+OztM6wrLCsra+ts6uuqquqr6+2sbe5tqyxsbCtr7S2sLCyuK6wsrW1t7m1tLOytLu4uLm9wcG+wru+u7/DxsfKw8TFvc7Gxcfh0czJyszMyszJy87IycrJgMnHx8rLy87IzMvL0MvPysvPy87R0dHP1Nrb1dba4tnW1Nrd29ja1tvY2OPi4N7j5t/j5fDj6+Lw6eXi4+Hn4uPg2+He4OHl49/h397g57Xd19nf3eLe3OLm5uTg393f593a2uDf3NnX2drb2Nzf3OTe2tfc2dHKx8XLycrKxsPOgNLIzdffz9HIzsrGwMHAv8HExcTAvcTEwMHFv766vcK9uLq4u7u/vLq2uLS1sbu0tbG3sK+wr62tq6mkrKmpq6qrrKulqaylprGlrZ+elJiTj5KSjpGPkI6RlJSQp4yQkYmHiYyfl5CZjouNi4eGg/r//YLrh+n1gPeB/vH47PXvgO385eXl4Nrk3+Di59/i15vn4eTSys28wcjNysvJz7/By8W/vLPCvMnAxMTM2NK+v7u9tbi7ycK9wcLAwbzZ18G8vMHAyLmwtr+6vLnKu8bCxc/Syvm6v8C8xL/AxMnX2sXExb7Ixr2zvL+7uL/EwL/AwLu0usO3ttS+trvIvL3DgM2/wMLNuse/yrW9wsS9urC3uL7GzsfIxMK/v7psxrWzsbS2tbrHy73Ev8DDxsDAwbrHxrxuvbXFvcHFw7zDvb3Mw8nDw8G/vb3Dx8TBxsW/wbzIyMXFx8HHyMLHyM3Lx8fJxdHExc7MyM3M0dLTwcvDzc7MhsnX2tLX1NfT2dXWdcvRzsrF0M1sZ81uy8vHx8rLytLBytBqa3LIbdDQ0dbM0W5wc2xtbXRqa9JscXB1e3h8anxxcmxrbdNtbXBsbG9vdHFz4nV7c29wbnFwdHV1dHd3eHV3dHJzdHZ8fH9/fnt8fYCCf4GBf4J9f4B/gICCgIGAgf9+/37/ftx+AX//fqF+C319fX59fn19fn1+lX0Bfv99jX0Bfpd9AX6/fQF+kn0Efn59fot9BX5+fn1+hn2JfgF9jn4BfYp+AX2wfgICBACAoqahpqCjoqOoq6uxqayrr7Gwrq+ssa+xsa+ysLC2tbeyvbjCuL3Pv97CvrvGvsXDv77FwsLBwsPHwcHGydjOz83LyMzNy9DLyMnIycXGxMG7vb64vrzByMLGxsbQzc7KzM3SzdDIzszNy87KxcfIyMK6xMS9wMPIy8vFx8jAx8WAwsHFwsLFzsXCyMPIxsjKx8vHy8nN0OvLxcnHyMjIy8e9xr7CxcXK0NDP1dDS0dDMzs3Kzc3NxcbCxMTExsXYzM7KxcXDxMjQy8bIzMzNztTT2dPV1M7Ny87PycbJyMvQy87Q1tTS1drT0NLRzdTK0M3SzszLzszQ0c/P1NbW1tiA2eLW2NjU1NPQ09jQ0NTRz8vMzdHLztXLx8nHxr/Dw8XOyMbDx8LExcG8vbi5uLi8uLrJs7O2tbC2t7K0tLe0r7a4u7S0rK6xq6itra6sqa2wsbi4srKxsbKxs7Cxsq6wrK+qrauqsbO1s7Owq62rr6yyr62vsq+wsLCzs66vrLGAurnFtK+rqaanpqmts7KvsrCwqq+tsLGytrazs7O1uLW0uLG0sLGusbexsreus7WxuLGytrq4u6+ys7evs7i3u7zAa724uLm5ucK3uLi/uLy8wsTEvcDBvLXEtLq00MC4tLi5vrq8vLzAvbu/trK4tbe4uba4vry1uLa7ur25traAvLbAsbKuq7Grra23sLKws7SztK6usK+ws7aysrS5qqaxrK2sq6qtq6imp6ekqKWjpKamoaako6ChoaKjnpmXlpSVlqGhqKyqsKmlop6gm5ycmJaTl5ialpKPkJmWlZWPiYqHhI2QnoeEiYF8f4J9jn97foOCfoWJe3h553d37OeA5fDv7H3v9Oh44d/T19HP2NTb4N/Z49vS1dPV2tva0+LR4ci9vbm2uLizucLCw7i9urzEvcXDz83FxbzbxbvEx8rNy8XAzMa9t7m6uLe4wcbGvbzEw8C4uLTBvHLFxsbJysrMydvUydHR0sjMxsTL08jYztPNzsnQ0NbQ0djW0deAyNPCwr/Jys3wyMjR0M3KzcnKy8m/x8nky8XJ3dff4NzV2OLZ4N7c4OLa0d3U1N7W3eDa39fb3tHg3t3p7ufo7/bv8/D38fDu8Onp6url3d7l5d7V2ebj5fzt7ezq8O/m5unm+/jt9f2DgPv7g+Pl5+Xv5urm7++B9fLw9u/u4++A84P68/qA8+ru7fDv7vbz7/T7/ez2iYL3+/j4gP3++f6EhP7/+4OGgoH9gYKDgICLh/79gIWHhYmIj4WHiIqKio2GioaHko6LhYWEh4eDhYSQipGXkY2PkZeVlZeVlJOVlJWWl5mcmpybnZeZoaCdpaSjo6efm5eZnpqbnZydoKGAjpKPlI6SkpKVlpWZkpSVmZybnJ2anp6fn52fnp6ioaKepqGsoaWyp8+qqKWwqa+sq6qvra6srq6wqqmqqbasr66xq7Kysbays7O0t7W4trizt7m1tLGyuLG0srO7uLeztLO3tbixtrS3trq2tba6ubSvt7eytLe6vb64ur66v8CAvby+vLy/xr66wbrAvLy+vMC7v77DxeDAuby5vb3AxsS/xr+9vby/wsG9wLu/wL+9vry6u7y8t7i5vLu5uLbGvL67tbe2trrCu7a4urm5ury7wLm7ure1trm7trO3tbi7trm3u7u6vMC7t7m3tLuzuLW3t7W0t7W2t7Ozt7e1tbeAucG2uLi2t7i2t7uxsbOztLO0tbexs7ixrrOzs7Czr6+2srCtsq+ysq+usKuvr6yuq627qqutrKmsrKmsrbGvqq6xs6yqpaquq6qxsbOwq6yrqa+tqKioqayrr6ywrq+yr7Cws6+us7KysLGtqauts7G5trS0tbS1s7KztbK0r7NuwLzFuba0trW3t7i5vLm2u7y7t7q4ubq6vLu5ubi8u7i3ubW6ubu7v8S+vsO5vMC8wLm8wMTEx8DExcjBwsXCw8LFccPBwsTEx9TEwcLDu729wcLBvr/CwLvHvMO/28fAvMDBxMHCwcHEwsHHv7yEwYC+u7/CwLy/u8C9vbe1tr69x7y8t7W7tri3vbKxrrCys7azsrSvrq+vqampsaWjqqipqKemqamopaWnpKelpKSjo56hnp6bnJ6hp6GhoZ+enZqemZycmZ+amJiXmJWXlJKQjZKZlI+KiIeOjI2MiYWIhYKKjJiEgYeEgoKCfoyEgICBgn1+gYR7eXjkdnbp5ePl4dxz2+Lbctfa0tPNzdHLz9LTz9vUzdDNzczKxL3FvNS+u727u7y7trq/v8G4uba5u7S6usPCwL+4z7uus7W4vLy4tcXBubO1ubSxt7rAvbW1ury6s7KvubJru7y4vL29vbjFvbW6u723u7a2v8q/xoC8u7W2sre3vrq3vMC/yLzGt7mzv77B4bi5v7y5t7i2t7y9s7u90by4usjBwcbBuLfBur++wsXHwr/LxMPLxcnMxMrFxsjBzMnF0NLKytDYzs7M0MzMzs/LzdHS0MzP1dPVzs7T1NLl09XT0tbZ0tja1ebn0tTUbWrV1nHP09fT4IDU1tDX1nzh3uDp5eva5uJ239fabtfX2trg3dng3tzi5eXV23Vw2t3b3HHh4t3jdHTf4d50d3V15XV2dnNzfnnk4nJ2eHR2d311d3h4eXl8dnl3eIJ/fXh6eX1/fXx6h3+Bg359f4GFg4CAf3+AgoGDgoOCg4KEhIiEhYqKhoyJig6Kj4uIh4mOi4yOjY2OjYCEh4SJhYmKipCRjpKLioiLjImJiomPkJKSkZSSkJWTk46ZlaGVnKmfyZ+amKGaoKCcnaShpKKjo6ahoqept7G0tK+tsLCtsKioq62vrbGxsa6ys62urK6zq66urbW0trK0t7m2ubO3tLWztrGvsrW0r6u1tbCytLe6u7a5ure/v4C5ubu4tbi/trG2sLe0tri3vLi6ub/A8ru2uLS2tra7urS5s7W1tbm9u7e7trq6u7a1t7W3ubq2tba3tLOxsMW3u7q4vL69wc3Dt7e4tLKxs7G4s7W1sq+usrOuq6+srbCrrq6zsrOzt7Kvrq6rsquysLOzsrGzsrS2s7KzsrOztIC3wra2tbOzsa6yuLCytri3tra3urS0u6+pq6mopKmprLSyr66xr7Gyrauura+vrrCrscOvsrWzrrS1r7Kwsaymrq+ysLKtsri0srm5u7extLS0uri0s7O2uLe7uLy9vL65vLu9urvBwMC/v7u3u7zDwszJxsnKycfHxcjKx8nIzWXa1N3Ry8fHxcfHycvRz8vP0dHL0s/R09TW1tTT0dXW1dbc2N7b29rd3tXZ4dnc4dzg2Nve4uLn2+Hj6N/j6Ofs6/CP7efp6enq+Obn5+zi5OTr6+rj5+ji2+va4dv06d/b4uPp5YXmgOPo3tjf297e3Nfa4d/d4N7k4uTc2Njf2ebY19TS2dHU093R0c7Q0NLSy8nNyMXIxsLAwMi8usXBxsbGx8nHxMPCwb3Cvry9vb22urm3tba4u8S6uLWxr62oq6iqq6auq6qrqauoqqempaKp3qedmZKSnZmYmZWRk5CNlpmokY6SgI6MjIyHlo2KjpCMioqNhIOD/oGA/PTv9vXzgPP69YDx8ufn4N3h2t/h497s5Nvg3uLf3NvU5dj119HV0tHR0MnM0M/OwsXCw8nCycfRz8zDutTAsrvByMvMysnW0MfBw8W/ury/w8K7ub/AvrOyr7u1gMLEv8PFxsG6xb+zvcHHgMHFvbvFzcPLvby4ubfCx83DwcXBucO1wLK0sL+/wdW3t8C9vr6+vb/Bv7W+u9q4srTJw8bOzsXBzcXFv8HCwru1wby7xr/HyMDEwMTIwM/PydLSx8XG0cLDwMjFxcbLx8rQz8nBwcnIxMDDysPG4MTEw8LEx7/Dy8rp68vP0WtpgM7Kabi+wcLUyc3K1NB71M7M0c7SwtHRb9TN1G3V09bX3NjT2dbP1tfWxM9zbcvNyslnyszGy2lqzM7Pa25ra9Ntb3Btbndz1NVrb3FvcnJ4cXF0cHBwcWpubGx5dHJucG1xcm5vbXtydHZxcXJ2fXx5enp5d3h2dnV3d3l5enp+Fnl5fn15fn5/f4WBf3yAhYGChYODhYT/fv9+/37FfgF//36TfgF/pX4DfX5+hn0Ffn19fX7SfQF+/32NfQV+fn19fop9AX6JfQV+fX19fo99gn6EfQF+hH0Ffn59fX2EfgF9h36Cfc1+AgIEAICjoqKupqqlp6ysqqyoqquwr62usbOwt7O2ubjHwLy3ur28u7+9v7+/wMHEwsPBycTJy8vKxMfBwcTDzdDQy8vKzcrKy8rK087SzcvJy8bEvsDGxcfJ0MvN0szNy87MzcfOz8/Q0M/V0tXWz9PPzcfLw8vKx87Ky8rQycvNx8fHw4DAwMbDxsTJz9HNycrNxczM0MzQzcvLzM7Lzc7Q1NXOy8zHy8nQzNDU1M/W1M/U09vU3+TW2NHRz8vKy9rKyMrMzMnOxcXHyMrJz9HO0dLU1dzY1tXP0NDP0NfRzs/Nz9PW3s/V2djW0dDP0dDV0c/Pz9PS1NLV0NTW19vZ4Nrc4FHl3NzZ2NfY2dff2dfZ2dLU09DNyc7N0M7Lys3JzMnLycnLzczKx8TMvr+7v7/CwMDCwL7Cvri5trq9ubW3wMHPu7/Du7m/ta+srLCtr6+xr7qEtoC0ubOvs7Svr62usq+sq6qltLOutra1v7S7tLS1x7S3t7fItbe4tbOytLOvsK+1tLa1sLGup6mop7KvsbK4tbCzt7W0t66xt7u7t7yytLSyr7Ozs7a3tLavtbe1uLW9ury3tbW1s7i1ubq4usHBubu9ubm9tLW6wMfAxMHGwsbBu4DCusW9vMHDwbe7vb7AxLvIxsPAub2/ubm4vr+2vLi0urbJt7S72L7IxsO6u7u9vLy7t7W3uLi8sLS1trW4uW6wr7K0vry/tbe14beyq66qqqmoo6erp6qrqq+lqa+ppKWpraKopaKcnZuZmKuepKaor66or6mnnp6jnqCcoaKemoCdnpyUmZemm5WVjYuKjYyNjo2KhYiAf4qMfHp9eYWMiYaFhoWBmXjp7evt7e3u63vv8enp493a2t/Y4OPi7Ond297T2MvN4dfK1dXGz87JxsDHsri8v8LHxMbMxcnLxr7AysLMvri8vsHIy9DgyMrNzcnHw7y5uMG9wcjHxMnByoC/s7bCy73G0tMqcc3SydPW3b7Nw+7R0dniy9LR69XSz9DT09/P0vPa3szNzMfIxcnEx8PEv8fLy9LX0cnQzsvHwcHDwtPM1OHd2+Pj3tfX4evY2eD04d3g3/Pp6d3g7OTo6e3g8Orq6/H28Yf48u2B8fXw7uzy8PPg5+r05eXs6IDw6/L2+vKB+O707ung6ebr6//2+ID8/fr68O/u9fL68u739Pb1g/Dz8O3v7/v8//r6g4D6/fz/+/P39Pv2/4yA//78hP2B/4CFgoSGhomFh4OEhIGAgYOEhIuHhIOAg4OGh4iLho2Ni4eJhIiGi4yLjpaSkpGKjYyLkY2OkZqTliuTl5eXnpuelZeWlJmcmp2hnJ6hoaCioaOko6OioJ6jnpqdn5ycnZ6hoqKkgJGPkJqSlJGTl5aVlpOYm5+enZ2goJyinp+koa6opaCjpqSkqKaoqKqrq66urquyrbCwsbKvsq+xtLG2tbSwr6+0srK0tbK7uLq2tre4tri1tbm4tra5tbm+ubq4urq7t7m5uLm3tbi3ur26v7y5trezvLu5wL6+vcG8vL+8v8C/gL69wsHDwsPQysXAwMG5vb29ur++vcDBw76/v8LFycbGycPFv8S8wMHAvcPDv8TCzMHMzr/AvL/Bv8HCzr+9wcDCv8G5uLm6vLrAwb69vbu6vLq7vLi7u7q8wLy5uLa5vMDGub3BwMC9vry+vsG/vLu6vLy8u7q2t7i4uri9tbe7gMK7vLy8u7q7uLy2t7m5tbi3trW0t7W1tLKyt7S2t7ezsrO0s7Oxr7mvtbO0tLWwra+trLKzrK2tsbGvrK2ysbmrr7OurLqvrKutsrGysrOwtbCuraupr6uqrrKxtbCytrWxsrGsubewtrSyu7C3s7KyvbO3t7fItrm7ure3ubm2gLe1uLa5ube5urW9u7nCvLm9wsC7vL++vL62trm8vri9t7q8vLzCwcDDwsHEvcHEwsPAyMfJxcXHx8XJxsfHwsPHyMPCxcPFyMDCwsTIwsLCxcLGw7/Ev8rGxcvP0MXIyMjJy8PQz8nGwcPFwcC/ycvDx8PByMLMv7i6zbzDwL63gLq8wb/Av7u4ubi5u7K0srSztsB7trS0tLm1ua2vruCyrqisqKqoqKOoq6irq6mso6arpqKjp6qgpaSjn6CjoaCtoKCdnZ+dmaCdn5idn52emZucl5KVlpSPkIyfj4qLhYWFiYiIh4eEf4R/f4SHfYKHgouLg4J9enh3oHHj5eDggOLd4d903+Hd4N7Y2NPTytDV1N7j09TVzNTJyeHPvsTEtr3CwMLAybW7vr69wL/Axrq/wLuztsG7yrW1tLa3u72+xra3tre2t7OtsbK5uLjAvbq9usS7srW+w7O4wsE5dL3Du8bI0LS+s9S5uMPPu8C+18XDuri5usO2u9vGyb/BgLy5v8DFwsfBwbrAv7y9xL25v728urm7vbvGvr7Fvbq/vru2uMHLv7zE38fGycnX0NDExtDJy8vSxNHMycnLz8p50crHbc3P0M7M09TVy8/Y3tPU3dPW09bX2NJu1M3Y1dfT09HUz9zX127Z393f2NXU3NXc2tbf3OHje+Pm4uHjgN3d2t/Z1m5t2Nna4N7a3dvh2+N5cuLi3XPdc+RxdnFycnJ1cnVyc3V0cnJ2dnV8eHR0cnJydXN0d3V5enp5fHh7eX18fHyCfn+Ae35/foF8f4CGgYF+gH5+hYOEgIKAgYOFg4eIhYeJioeLi4yPjY6Ni4mOiomLjo+QkpOUk5GTgIaEho+KjYqMkZKOjoqLioyKh4iKjIqTkJKal6yfmpWXmZqanpqcnZ6dnJ+dm5mfnJ6ho6WipaGhoqGnqamoqaqurrCys7K3rK2pqquuraypqa6usLK2sbS4srOys7GzsLa4uLm3trq3t7i0t7S1s7ayvLu2vbm4try2u7+7wMHAgLy7wb6/vL/Myr+6u722ury+ur28u728v7q7uLm9vbu6v7u/vMC4vLy6tLu4tLm5wLrIy7a5tLa1srG4xrOxtbi8u8G6vL2+vrq+vre3trW0uba1tbGzsLCytrKwsa6vsba7q7G2tbKvr62urrKwr6+xtbW3traytbe4ubW5s7S1Ubm1trGxs7GwrbSur7K2tLq7u7q3uri4s66ssayvsLOxsrS1tLKurLmqrq2ysraxrrCztLq1tLWytbaxr7K4t8K0trm1tNG2tLKzure3tbe0vIS3gLW8uba6vrq+ur3CwLy9vLbHxb3FxcHKv8bAwcTSyM3NzeTLzc7NysvO0M3PztTT1tbT1NLKz83M1tHS1NrW09fc29rc0NDU2NnX3tfb393b4ODe3t7c4d3j5+Xk4efm5+Pi5OXi6Obr6ujs8vXv6ezp6uvm6ezw+PHx7/Ps7unigOjf6+Tj6evt4uPj4+Xn3+zm5ubg5efl4uDo6OHk3tri3One2t/14erm4tnd3ODd3tzX1tjY2t7R1NPT09fqqc3LyMfMx8vBxcX5zMnDy8jLy8rDxMrDxcXCxbi7v7iztLq9tby7ubS2uLSywbOzr6+zsKuxra6nqa6sramtsKqigKakoJeal7memp+Xl5ebmZmYmZSQlI+PlZeJiIuGkJGMjIqMi4q2gvr69fb39vv5gfz+9/f28O/q7+Pl5uLp7d7h5tzo3N7w5dbd4dHZ3Nzb1t3FzM3My87Ly9HMz9TUyMvWztq/ube5uMDGzdjIzc/OysnGvLy7vr69x8nGy8LPgL+xs7zDtcDQ0oaXxsi6xcTJrryx0r/Ez+DHxsDYxMO/wcTDzb+988nJurq5s7W2uLO4tLKstbewtL2ysLq9vru6uby0vba6wr2/ysrEwL7Ey7q2vNG+ur6/z8bFu7vFv8jK0sfY08zLys7EfMnDvmrFys3LzdPT1MjL0tjIx87HgM3Iz9TRyGvNxc/OzcrOyc3E1c/SbNHVzc/GxsfRzNHPytPN0M5tx87NztPN0NHT0tFubdfb2dnUztDJ0svVc23W1s5ry2jNZmxmaWppbGpsZ2lpaGdna21tc3BubGtra29vbnNvdXNxb3BrcW5ycnJ0eHN1dm9yc3Fzb3FyfXZ5K3V4eHmAfYB5end2enx5e316fH5+fIB/gIJ/f4CAfoSEgoWKiYiJiYmHh4j/fv9+/37/fpd+AX/ofoh9AX7ZfQKCfuB9BX59fX1+ln0Bfo19AX6QfQF+i32Cfot9CX5+fX19fn1+feR+AgIEAIClqqyrqqurrK6rq6uts7SwsLOxtrK2uLS4trW3try8vb3Dw8HAwcG+w7/CwszIy8nSzMjGx8TExsrKzMrNysfNycfGysjMzc7U0NTK0cnNy9DUztPT0dLU0dDPx8vOzsvNy9HP0s/QzdLLytHP083Q0M3P0dXSz9LWzNDaz9LQxIDGwsXLx8nOycTR0c/OzdTTzNLT19nT0tDV2NfU2uTW197SyM3m5NXY2dba2tnV4dzb1tjT19XR1NjT1MzJz87N0MzS0M3Oy83OzcjL1NLS2NjV1dXPys7Oz9bTz9LZ1trZ4NrX19bW1dPS1tXX1dLT1dfZ1NTT09Pc3Nrf3t7i4oDv7OLh4ODd2+Db297f3trU0dHPytPQ1NHOzszKytPY29XT09PX0s7UysfFx727wsXGw8q/usnCx728xb28xMPDw8vvw79+u7mxtLO1tLSytLSyuLa1r7O1tLWvr7C+sbGssLK1tLSxtbq3uLevurWyubi2trGtsa+0r7O0s7OyroCwsrO0uXO6ta+xsLSyt7XBtLCysra4tbi4tby7wMLAv7e4uLm6tbGxtr69wLm3ssC9vMC9vr68vLm5vbq3ur68u8Niw7y9wIO8vLq+xcnIa8TK0MXFwcLAxMXAw8LBwsXFycLBx8TEwsO8vMDAxcC5uru8uL61vbvFt7e5wsW6yIDAvsDAzMvJusC9vcLEv+O5t6+2t7e1r7q5ys24s7G/tsSytLOur6xosaqrXaetqKirrbKvrK+uq7Wjq6OfoKiloqSmpqeoqK2ttKSjmZ6gmqSloJyWmJ6blpaWo5qZk5GQjYeCq5yOiIeIhH68iYOBgXmCh42Nj4uIiYB4g3vv8IDh3uvw8e+I7Ozq5+Da9ert6ejh6+DZ49LQ69jP4M/T1s7Q0MjBwru6ure3tsC0vL64vr7DwMTMwMPGebzEyM7D4NXUxdPO2MrEzcfExMHg2sDAvr/DvsLLhr/BwcrIaNzb4M3IwsW+xs/g3Nni4OnT09HSzM3NytDj08d4zNDJ0IDHy8jIxMLMzM/L2djZ0MfLy8fOws7Nzs3c1tng8dra29/m4+fm3t3j39LP3Njj4O31++3z5u3s7eLp4uLw8Pr09vn17/vx+vX0+PHw9vvt6fLv9u/3/frvg+np7YX2+Pj29+/x8Pf++fyA/vuGhYCC/oD/+Pn5+vTx7vj9+fr4+ID+g4aEgIiHg4CA+O/8/IOFjIP/i/yB/4uD+YKBg4SGhoKChYOLh4KBg4OGhb6JioONiYuMkJKIioiLioyOoIyIiIqMjZORlZaUkZCRkY+Sk5eVlJqamJaTlpqgqZuflZqbnpygn56jo6GjoaGcn6CeoZ+fnpybmJqbnKadnJ2ioV2QkpSUlJWWmZqWlpWWmp2amp2dop+ipKGkoqGioaako6SpqaiqrK6ssa+wr7Wxs7G5tbKztLKytLe2t7e4uLW4tLGzt7m7ube7uby2vLa8u7++uby6uLi7u7u9uLyEv4C9wL29vL26wbu+wL7Au7u9vsDCxcXCxcjAwcfAxMO6vrq/yMLEyMO6xcPBvLq+vba6vMLEwcPBxMbFwsfTytLYzMPA1c/CwcTBxsXFw87HyMXFwsTFv7/CxMa/vcPBvsC7vr++v77BwcC9wMPAvsG9vb6/vrq+v8DDv7i5u7q8vYDCv728vL6/vbu+v8G+vb+/wcK/vru9u8HAvb+7ub27xsS8vL2/vbzBvLu8vLu8urm6uLW4trm3s7e3tbW5vLy3tLS2urWzxrOxsraysLO0s7O8srHBub62sryyr7SysbG227OxfbKxq66usbGysbKxsLWzsayws7Ozr7G0v7O2sYCzt7q4uLK2t7S2ta63tbG4uLm3tLG4uLq2u7u6u7e3uLq4u71twb26vL/CwMXBycG+wcLDxMC/vLm+u73Av726v8LEysbEw8PHx8vFwb3MyMfKx8nIxsjFxsvJxcfLyMXQaczHyMuZxcTBwsnJyG3EzNDIycTHxcnKx8vMzMvO0IDTysvPy83Ly8XExsXIxsLDxcbFy8DHxc6/v7/ExbvEv73AvsvJyLm9ubq/vrvTt7ezvL+8t7G1tcTIsq2tu7XIsLGuqqqqbbWusGisr6uoqaqsqKSlpaSroaiioKOrp6Kjo6Cfnp2ho6mdnpicnpmjoZuXkZGWlZGSjp2VkoyMjoCMh4SomIuFhISDfqaFhoiIgYWEgoCAfX2AfHJ+d+jw4dzj5ePggeDg39zWzuHS1M/PzejY1uDU0+nXztnFxsrDxMTAvL6+vsG+v77Dub67tLi0ubi7xbq9wYO2uLa8tsu7vbC4s7+2r7y+ury92ty+vr28u7e4vne4urjAvpDP04DUwsC+wbi/wMzDwMbEzMDEwsTAvru6vtG+tGu6uLrCur/Cw8K/yMbEusK+vrWyt7q4wrnAwsK/yMDBxti9v7/DxMLFxcHCycjDv8rFzsjQ1trL0MXMy8/G0svN1dfe1tTTz8jPydHRz9bP0NPWzMrS1d7b4+bk2X3R1NJ02tva2oDf1NbW2NvV2G7d3HRzb3Hecd/c3N7h4OLg6enl5eTf43Jyc290cXFtb9jV291yc3lz4Xrjc+Z9eeCEdHR1dXdzdHd1e3h0dHV1dnWOdnZyfnl5e32AeHl3enp7fIh+e3x+fnyGfH9+fnx9f4B/goKGg3+Af4CBf4KFiZSEh3+Fhh+KhoeHhYmKiIuJjImNjIyQj46OjYyKjI6PlpCPjI6NgIeJjIuMjpCRk5CPjY6Rko2Nj42Rj5KVkpaVlZaUmpmYmZ+em5uenpyfnZ6eo5+hoailo6aopaWnqKempaanpqurqqyurLCtq6+usqmup6ursrSwtri2trm5trewtLW1s7W0urq5t7i0ubK1uLa6tre4tre4ure2ubu0usS8wsO6gLu3u8O8vcC6s76/vr2/xMXAxMPGxsC/vsHBvbq+xr3Iz8G6vdHGvLy7t7y9vbrGvr67urS3ubGzurm7tLS4t7i7ucLEw8bFx8XDvLq9uri8vLu7vLi0uLe5vbmztrm1uLe8t7a3t7m6t7O2tre1tbi5u767ura3tb27ubu2tbm3gL7Bt7W1trKxt7W2uLm4ubi5u7u1vLi6trCyrqmrs7i8t7a2try5t9a2srS1rqyys7W1wre2xby9t7S4sbG2tLO1uvG4t7S5ubK2ubu7vLu7uri8ubextry7vbi8vcy+v7m+wsbFx8LGysfHx7/KxsLJyMrJyMXLys7Kzc3Mzc/PgNHT1djckt7Y0tXY2tbc1+fW1dnc4eHf4N/a49/g4ODf29/j4+bf3Nzd5OTr6eTh8ero6ebo6Ofr6Orx7+vt8/Du/YD28PL3svHx7vH5+veL8fj78PHr7Ort7ejs7Orn6unr4OTq5ejp6OHg5OTp5uHk4+bk6t/m5O3f4uTq7d/sgOTi5OPx7uzZ3djY3t/d9tLRyNHX1NHIycjZ3MnFxdbU8c/S0svLy4jRys2AxcjDv8LEx8PAwb68xbW8tbS2wLu3t7aysK+tsbO5ra+nra+ptbOtp5+gpKKbnJmvoZ+bmpuZlZGjoZyUkY+PiaOTkJCPhoyNj5CSj46QjIGKgvv9gOnp+f3//Jv58vDt5dzx4+bg4N716OXv3t7349zu293i3ePi29nb1tbY09DN0sPKx8HDxcvIydbHxsmJur++xb/iycq/y8LPxL/Ix8bFwuvwv7+6u7y2ucOKu76+x8Wd3uPhw763urC5wdLPy9HM1cPDwL+8vb26wdXFuIG7uLbCgLm/v724sr2+u7K+ubqxsri+v8zCycjHxMzDxsjWwMPCysrIycbAvcXBubS9vcXByM7Qwsa7xcfMx9HPytHN0snJyMjDz8fV1NPX0dDR1srHy8rPy9DW1tKFzc7OcdDV19XTzMvM0NjT1mzTzGtpZWbIadHO09PW0tHN0dPR0dLTgNNrbmxpcHBua23SyNDRa21ybdNy02vTcmvId2dnaGhqZmdranFrZ2doaGtpjm5vandzcXF0d3FxcXNzcnOAcW1vcXFxgHR2dndzcXN2dHd4enZ0eHp5eXZ5fYCJen11enp8eXt7e4GCgoWCg3+BgH6BgICBg4OCh4qJkouIhIeE/37/fsd+AX+/fgF/vH4Bf4R+AX+HfgF/134Ff35+fn/Sfoh9AX6zfQF+nX0BfoV9AYGcfQF+3H0Ffn19fX6MfQN+fX2EfgJ9fo99iX6EfYR+CH1+fX59fn595n4CAgQAgK6zrKyrqK6yrbGyubCysa6utLq4tr3Bs7i6v7i9vsTBxMXIxsbJyczHz87K0MXL0cvJzcnJyM3L0c7N0NLUyczR0MzR09TS1dHT2NLP1trUztjZ1dfU2tbZ2NbW2NPT0OTL09TU0dDR0czOzMzU19DV19rY2+DT1dDR09DY1eDYbNDMy8vNzMrQ0c/N0NzR2tLb4dnc2dnZ19nb3N3e29vX1dnS0tfW2Nze1eDj4Nns5+bZ2uLc39zf2vDh2NHS0dXS1NXW1drX19HU09LX19jX3NjY2dTU0NDV29XZ2Nna2djd29nc19ra1dbb34TcgN7d4eDe3+Ha4uHe5OHj4Obn4uDd39vc4N/b29zb29bX1dXRz9LO087b1NfZ2NfX19PX19PW0MrQzcvOx8rKyMTHxMXByHPJwsLDzsLAxsfGwMLHwbu9vsTJsrS5trq9uMC/ubW3try4tbq5tby3t7KvuLm4vbi3trq2s7i4uLq7JLq9uL64tbW1tLG6tbW2sa2wt7O5ub2+x8j/t7i4u7XLwbS2s4S7gLy7wMDCwcK+w7u8vbe1s7i6tra8vLfFubq3urvBwMC/xr66ur2/wsHCwb+/wcDBvsjKx8LKz85sdW9swsjJyMTGxsi7w8DCwsfFxsnJyN3Fx7+6wMTGwMPDwcPExsXBwMTDv8DHxr7AwsHBwr3AxcaEwsXAw8TPvrq5wrm2tba7gL/OwLi9vre9u7q6tra2r7SrsbGuqa6tp7CwtLGyrrKvsq6xqayutbyiqqarrLK1rqyxqqignJ2gnqScmZmemZyXmJehmZaYlY+UjX+Fg4KDf4B+hJGJgo2Kh42LjY6UiIaLhn+ceut/5uV5eOp7fXzn8+zq5eD86fLo6uHn6urogNnW3dPg1c7W0dLVyMe/tba5vMHBwMC/wcrG2NXKw/K7wrq9wL/Jwbrmw8eJ1dnV08jCwce/w8TJwcLAwcLDw8rIwrvHyMvczNbVzdjLyMHOesbR1NLS1c/O0+TJycrU1c3V2tnQ0dDU0NrP0cfM0s7R19fa/Nng9NrL08jI19HcgOPY4eLi2drk6Nzb6eHh2NrV3+Hb1Nrf5vH48e/38/Ls7u3l7+/w9fT48v71g/n57/by+YHw8/H7/ouMgIP29PX7+4KCgIaB+/T6gYqF/Pj8gYCB+fSE/YOGiYCBhfr8/oL8hv2F/IP+gIKJg4aEhpGEg4eOhoiIhoOEgoCB/P6DaIGE8oCBgoOEh4WDhoaMi4iIhYiHk4qOi42Ji4yKi4iKiY6QkYqTi5GQk5WOj5iSjZKVjouVjpSWkJSVlZSXlZiZmpifoKimqqGmnZ+joqehpKSmoaGvrKWloaSmpKKioaOgo6SipqipWJSYlZeXlZuemJqboJmcm5qZnaKhn6Wpn6anrKerrKyqqqusrKuvrrKtt7SytrC0ubS1trO0tLe3w7u4u7q8srS6ubW6vLu5vLi3vry+w8nCu8C/vr27wb+EwYDEwMTC07zCwsLAwMHEwcPBv8XGvMDCxMXJ0cbIxcfGwMjF1sjEwsTFx8fGycfFwsLJv8S9wMbCxcTGx8bGxsjGx8XJyMbLxsXHxMTGyMHMzMzH2M7Ow8bNy8/KycbTzMXAwr/Av8DBwcHFwMG9w8PExMTCv8K/wMLBwL++wcO9voC7u769vMHBv8C9wMG8vL/DwsC/wsPCxcbFw8S8xMG+wr++vMPDwcDAw8HCxMK+v7/Awb7AvsC8uLq3urS9t7q7vLy8u7e5uLa6t7XAvLi6srW0trW2tLSyuXK6t7e2wba0tre3tLe4t7O0tb68rbCzsLa4sre3srCys7e1sra0smK5tLWysrW4ubu5tre4tbK2tra6ure7t7u6uLm3trW/u7u+uri7wLvBwMLBycvzwMPCw77SzcLDv8XFw8TBvsK/wsPFwMfDyMvGxsTGycbFycrH1czMx8vLzszNytTIxsXGx4TKPsnKzMrJxM3Lx8LIyspsd3Bsx83MzMnLy83EzcrMzNDOz9HRz9zN0czHztHQzMrHxsjKzM3Ix8nGwcLKxr+/hMOAwMHEwYS6uri8vMi8urvIvrq7uba3wLi0uLiyt7KvsK2sraivqK+wsaywrqmtrLCrqaesqayrrqirrrS5pKikp6SmqKGipqOko56io6CimJORlJOVkJKSmpGPkY6NlJCIi4eHh4KDgoePiISTj4uNh4ODiHx6g4B5nHrqfeXndnWA4HR0dNfh3drW0ebU2tHV0Nnk5eLW0tvS3dDEy8TEyMDEwLu8wMPHxLm5tbK7t9LLwrvqt7qzt7q3v7iz37S4ar3AwsO2ub7CuL6/v7m5t7q5v8HGwb+1vr6+yL7Fxb/WwMC2w3S6w8G+vr/Aw8jXv8C8w8G4usPAvLu/xMLKwMaAvr/Dvb2/vr3Ztr/hv7vFwcDLw8bKvsXGwru8wsa9wczJysvMydLY0MjJys3Q083KzszNzM/Uz9bV1dfT0cvUzG3T19HV1t531NnU3N2EgXFz2dfV19Vubm14cNza33J4ddvY3XFxcNrVcthvcHNub3Xf4eJ04njid+R24nBxdnB8cW5wd21sb3pxcnNzcnJzcnTk6Xl3eeF1dXd2d3l3dHZ1enl2dnJzdHt0dnZ5eHl8enx6e3p+gIB5gXmBf4OEfX6Ofnt+gX17hICEhYGDhYOBhYOFh4mFiIeLiYyGjIWHioiKiYuNjY2MmJeNjYuNkJCPj4+Rj5CSj5CQkYCLjoqKi4qTlJGVlpqSlJCLio2Rj42TmI2UmJ2XnZ6fm5ubnZycoKKmoauppKifpKmmp6upq6ywrbWuqqyrrqSnrK2psLa3tLKtrrSwsLe7tbC5ube5trm4ubi2uLq2uLjPtLy9vbm4uru2uLW0vsG4vb+/vsHFubu3uLu5w8LjzxnGw8XExcLCxcTBwMDMv8a/xsrEx8fGxcLBhMCAvcC/wMS/wMTAv8LBt8TEw73UyMe7u7+7v7i5t8e+vLe5u8HAwsXExMfExb7CwL2+vr27v76/wb29urm+v7q7ubq7ubi8ube7ubq8t7e4vbe3t7u7u8DBvbq8tby7ur24ubW7uri2s7W0s7WzsbG1t7u5vLy9u7m9vL20vrW2tbiAury7ur29uLq1sL6+u763t7S2trq7vrvEhsG4tra8srK2uLm3usi+vL3Aycu3ur67v8G9w8G8ubu7wMC9wsHBxsDAuba+wMPJxsPFycXCycvLzc/Nz8vPzcrNzcrJ0s7O0c3Mz9jX3t7f3uPi+9jZ2NvV8eja3tzm5ufp5uLl4+aA5ujk7OXp6uLg3ePn4eLo6+j36unj5ufu7e7t++7r6+/y9vb39/b1+PX18f7++fP6/PmClIaB7PP08/Dx7/Lm7urq7O7r7PDv7P3r7+ji6ezu6eno5uns7u3q6u/r6Ory7uXl5+Xk5ODi5eKV39/b3t3q3NnZ4tTMy8nLz9/Ry9GA0s/X1NLU0tHTzdHJz8/LxcbDusLAxL/Cv8TAxcHFvcLF2ti6wLq5tra4r6+zsrKtqa2vq7GnoqCjoqOdoJ6nnp2ioJ6moZeYl5aXkJCOkZeRipqVkpaVlJScjoyVk4u2hPyE9PiBgfaBhoPx+/r07OX85/Di5t3l7vDu4+Xq4/OA6d7j3t/j19vUzM3R09fXzszIytPN7OXVyvzExrq8wr/Fwb/7wsR2ztLR0sbGxsvAxMHDuri0trW5vcbDwLrHxsXPwcnHwODDwLfFibzIycbGw769wtO6ubvDx7zCysTAwcLFxc2/wri5vbW0uLu72rnH8sjDzcnG0MfKybzAwb1ytbe/x73CzcXDwL65wsa/uLq8wMTEvrvBwcXFy8/M09HP0MnJwtHHbtPZ0NXS13PJ0MvT1IaOa23Lx8bLzmxtbHVu1s3SbXJu0s/Rampoxr1oyGhsb2lrcNPS0GnJasdqy2zUbGx0bG1qbHNqa256b25vhGxta2vR0mxpacNmZmlpa25ta25tcW9sbGlra3RtcG9xcHFycHFvcHF2eHZudG1zc3d5dHeMeHV4eHNweXF2eHR2eHZ1enh5enx4fHyDgIJ7f3d6gH6Cf4KDhIKCjZSHh4KFiImIiouNioyNiYiKiP9+/363fgF//36afoR/tX4Bf/V+Cn1+fX1+fn1+fn67fQF+pH0Bftt9AX6GfQF+hX2EfoV9hX4QfX19fn5+fX19fn5+fX1+fYZ+C319fX59fn1+fX59lX4GfX1+fn595X4CAgQAgLC1tbKxq7yytre7trK2uLLXvcDFvb7HssPEwMHBvsHGxMXFxs/E0c7R0tLQ0tDd1dLQ0czHwtbNzdPS09bU1c7MzczOztLV5tjb2dzU1Nzb2Nzh1t7a4OXm5eDX1urZz8zU2dbX2Nbb2NXT19PY1Nr13dra2NzZ39rb2tTX1NbQgNTY1NbU0IPY1tPW2tnU2djh4N7f4ePc1t3a2ODh2tzd5OTc39zb1tzZ2fvZ3tni9Ozb29/e4uTm2d3g4d3a2NnX19ff3uDe3djY2trW2tnb2dra2NjV1dTY1tbb2Nzg8OLh4+Dn6uDh4eHk6ebh4Nzc5eHY1tvj4Nvh3uHZ3t/igOfp5eDl4Nri4+Pd4uHX29vW1dXR2dbX2tbX2eXZ2OPe4N/Q0tXPzMrJxb++xcTOx8nCwcLLxMbLyMvMx8zOyca/ycHAwMDDxMS+vLrBvL+7wL29vbu9v8HHwb/DyLe2sLS4tLKzt7K3u7m7u7i7wLO8vb6/vri5wLy7wrq6t7W3gL60uLe1uL69ysrFvLzDvsG7wL+6vsC/vsC6vL7Bw8PEysTAu72+t7y5uLvFubvAvr+9wMjCwsXDwby+vcdnwcLDw8DDxcTHxMjMzc5o0MzNaWzMv8LGysjMyMjFyMDEwsTIycvKysLNycPLysjDw8PEv8jGycnL0dXDzsHFwMbAgMXMy8nBx8bCyMbH3c7FzMi/wojDuLvDxb61u7e7v7e6uL69vMC+tra1vrqurrOxq629sbC0rq+xsLCrr66vuLeur6yvsbrAsa+8x62dnqOhoqKeoaOnnZuaoJydnZ6hrp6alJCRhoaKh6KMhYiNh4+CmZCLg42IhYKCfoCDhYDtgO945uZ2fed4f+/p8+/29Ob+8fDy7vjp9uXo4dvU1dTaztPTyMS+ubi6ysbLzcfGyNDLw+LSxNjS3cfEy8HUy8zO0/Pi4X3j0tjPwcDGyLzIydHPzM/RzcnQwsPYy8vP1dLLgdjOz9LU3NTS2dPa293a1NnYyNLUydPYy9Pe59fQgNDh2+jP/9LW3tjl5+7j2u3V2dvS1djS4Xvh5ePj5ufq4+rq1Nnf1dXX4eHg2eXe5O7t8fXw7/Xw8u/v8u/z8/Px9Pj+8v/38PHv7/n4+PWAgP2Oh/j8/vaBhYOEgP/+/v3w+ZD1+IH4hoL/hIKBhYmFh4WEioKBiICIhYmCgoSAXICBgIOEhYqG/4CDgIeLlYeKhIaHgoX2h4OCgP2GhoD/gYOAgoKGioaKj4uNkoyLjI2Jj4SUhoiJiIyPjJChkJ6Pk5GUlZWYlJafnZKVlZSZlJWVmZiVlZWWnJufhKAgoaCeoaCfqKKmpaamp6aor6qkq6ilpqanqr2sqKetrLGAlZiamJqUo5qenqOenKCim7ahoqWgn6iYqaqmqaumqK2rrayvuK22tLW2tbO0tL25uLm5t7WywLq6vLu+vru+uri7vMG/wcLKv7+/xsHBx8a/wMK7v73BxsfGxcHB2MnBvMPHxcLFxcnHx8bIxMjBxdvGxcTFycjNysrJwcTEx8CAxcrIysjFfsvHxcbLx8PHw8nIxsfIzcnDysfGzszFx8fOzsnKysrIzMrJ58bNyMzY18vIzM3P0NDJycvMx8XDwsLFwsjHysjGxMTFx8TFxMXDxcjGx8TGwsXDwsS/wcPRxMPFw8jJw8TCwcXIx8PEwsXMycTDxcvIwcTCxLu+vsEmxMbEw8rHwsjIysHGx8HExMHAwbzBvL2+u7y9x769x8HDwrW6vrqEu4C2tLq3vba4tLS1u7a1uba5uLS7vLm4t8C6ubm4ury8t7Wyubi2tba2tba0t7a4vLayuL2vsq+1ube1tru2uLi5u7y6vsG3u7y8vb25u8jAvsTAv7/Bw8i+v8C9wcfF1M3MxsbLxsvIzMrExcfHx8nFyMbGxcfEzMjIxMnMxMnJyIDK08vMzcrLyszTzs/S0M7KysjRa8vOzs/Mzs/NzsnLzs3NZs3Jzmlv0snOz9XT1NDPz9LN0tPT09XV1NHJ1dPO2tfWz9DQz8nPzM/Pz9PVxtrHysXHwsfMzMnCxcTBxcLB1cS7wb+3vZTHvb7CwL22vLi5ubG0r7Oxr7O0r7GyvoC6sbG2tK+tvrOusautra2uq62trrazq6mmqKirsaamrryqn6Cko6OfmZmZmJSSkZqXmZeZmaeWko6NkoaGioeejIaHi4eRhJqMhnyDfXx3e3p7foB86u536OZzc9hvdd7c4t3h28zh1dnc3erg697h2dPOz8fPwsfHxMS/vbzBy4DIycG4ubjBwb3iyr7Nx864uby3xsXFxcfzy8V2zLzBwby7v8S5vr3Cvbq8wL69wbi6z7/Cw8fAvHjQv8LIxsvAu8K8wsTExMHKxrvCwrK9vLXBzdrDvb3Jv8+648DAxrzDwMbBvtDCyMjDxMjE0X7Nz8nGxsbJxMnRw8jPycnK0oDT1crSy9DSz9PVzs7Uz9HS0dPQ1dTUz9DW29zj2dLR09Xf3N3acG/deXPU2NXOam1sbWvc3uTh091+3+Jz3np03XFtbG9xb3Fvb3dwb3Nxd3V4cnN0c3NzcXJzdHl24W9xbnR3gXZ6dHh4dnnke3d2dOh6fXXsdnl0dXN4eHR4eih1dnl1cXZ3d3x2hXx7fHp8fHh7jHyJfIF9gIB/gn+Bh4iBgYKDiYWHhIUqhoWFh4eKiomJiIqJhoqKio+Ljo+Oj5CQkZaRjpOQjpGRkJOjlJKSlJOXgIqOkI+Pi5uQlJSXk4+Vlo6ql5iZlJObi5uem52enJ6hn6Cgnqedp6SmqKimpqawq6msr62rqbqxr7CsrK2srayssK+wr7S1w7e5ub22uL67uLu7tLm2usDCwL24udC6sa+3u7u6vLvAvb27wLzCvcDWwb69vMC+w7/Bwbu+vcG8gL7DwcPBvqXEwMDEycfCx8LLycbGyM7HwMXCv8bEv8PG0c/LzMnEwcTBwOzCycLI2tLAvr++wMDDu77BxcHBxMfExcPJyMzLycTFxcO+wL2/vsLEw8PAwb3Cvby/vsDC0sLAwb7Exb7Avb2+w8C8vby9yMfAvMHIxb/EwsK2ubi6TLy9ure9t7C1tLWttbezury7ury4wLy7vbm5usO6uMO+xMKztru3tbS1s66vtLO8uL25u73Dvry+ubq5tLy9urq4wby+vr7BwsK9vLqExIDHxsTFw8TEx87Hw8nOvsC7wcfDwsXKw8XHxsrMzM/TydHR0tPUz9HZ0c/Szs/O0dXb0dbY193j3uzp6ODg5eDk4efm4efq6+nq4+Tl6Ojp6vHr6eTo6N/l6Ofs9+zr7OXl5Ojz8PL39fPt7+/5gPb8/P75+v35+vb7/v7+gP/4/ICBh/3x9vn9+/z49fLz6ezt7vDy9fXz6vn07Pf28uzs7+/s9fH18/L3+uv37PPs7+jr7ezp3uHi3+Th4v/o4Ofl2dzu6tDQ1NPRydHO09bS2NXa2tfb2dLR0efeycfJyb2/08bCxcDAwcDBvsLExuXVx8XAvby7wrKxt8a3qauwsYCwrqmnpqajoKCnpKSgpKa2pKGdmp6Sk5eTtJiOj5aSmpCsm5WKk42NiImIiIiKhfv/gf3+goX4gYf38/33+/bj/Ors8PD/7/zs8eji2d3Y4Nfe4Nrb1tLQ09vU1tLIysvW1ND/3c7g1+DFwMW6zMnLzs301dSD1cTMxsDAx8q/yIDEysS/vsDAvMO2t8i8vL7DvbWA0L3Cy8vUy8XJvsLExL+/xMS8yMq6xMG6wtLgy7+8ysPSvOG+vL6vu7vDvb7dxMnIwcLHwM2GxsfCwsPFzMbN18XGxsLAvsXGx77Iw8bJw8fIwsbNy9DS0tPP09HOycvW2Pj+3NXU1NLZ1NTRbIBs13hxztLQyWltbG5r2tfW0MPLdsnQbNJ3ctFtaWZqbWttbGtxaWdzZmppbGZpa2lqa2pqamxybtNqbWpvcX9tcWpvcG1x0nRvbmrTb3Nr2m5wbW5tcXFtcHRwcXZycnV3dntyg3Rzc3J0dnN1hHWAc3dzdnd3e3h7gH90dHJyeANzdXWFegR5e3t+hnweeX19foaChoaGh4iHiIyHhIuLiIqLi4ygi4aIh4aL/36HfgF//37/frh+AX+OfgZ/fn5+f3/AfgF/7H4KfX1+fX1+fn1+frx9AX6cfQF+tX0BfrZ9BX5+fX5+hH2FfoZ9CH59fX59fn59nX4BfY1+AX2EfgV9fn5+feB+AgIEAICytba1s7e3sLa2ur26t7bChMC+wsO9w8PExMjIx8zHyMXK0c3Pz9PV19bd2NfX1dbSzNvT0s7S0NfZ2NjV2NXRztDZ2dDd2uHY2Nzb3d3e2+Hn6eXu44Li4+De2tza2t3k3N7i49ve29zd2trc3Nvc9+zk4dzc4ujo4N3e3tra3oDf2drY3Nfaz9bb2tnb3t3e3dre6N7b3eDf4eXh5uPl3uLe4ODg3t/d2N3h2drd4eDn4eTj2tnf4eHe4ePi2+Dd4Nzb4N7k4eHZ3tza4Nve3uPb2tvW19jS0NbU3NjZ4OPh4N7j5+vo6+bl5unh4Obm4+bh4+fh7ePf4OPk5ufk4wfq5uTk6OXohORW5+Xm5uPe3+Da39vZ2tzb29Tc3/jc1tjV0dPMysrJysnPzs3OxsTDxMDBwMvKzMvMz83Hx8vGxsbHw8XDw8LFxb3AwMTGyMfOyru/urXDwLm+usK6u7qEu4C4vMLFysfLvry7tL/Bv7+/t7nGysW/v77Awb/HvcK8wb3CwMfGxcK8vL69wMLDwby/vr7BwMjHwMbHxszIyMa/vr25w8fIxcHHzcfJwsTCxsbKxMjNysvJ0MzFx8nFxMfGyszNzGpp0WnRadBpZ2VnZWZmzMtnzc/QzsvSzdDR0y7Q0dTMz8rJzcnGx8vLy8/QzMzMysnMyMXMzsbJzM3Kw8XTvsHOzcnL0szLx8HLhcSAw8a7vL+/wcC9w8TDxcLCvb27ura1t7Wzs7CxtLKvtK2xta6ysq/Frri1tLK4wLW1tra0q6ypo6Sfo6Omo6ShmpucnKKdmZ+hnpmVlJuRjIyRlISLjZqMj4yKjouJj46Ih4SCgoODgHx4ent2d3jl6vLx7Ht69n/58vXv7PXs7O+A5+zn4N/b2dXG1c3FwcS/x8jNysrW0dfW3NHe1dTPzdPV2MjS1+LfztHL3drg29zY0NfS0N/Sy+bMxsvR2dvQzcnFw8bHycrR3M/W0tLT2drT1NLW0drU3+Tq1OLX2t3f4OHa4efs2drX0+Dc3NjW4ePd6Ov13enc++nj2+Dey9uA4eH/4Nzh7Orv6eTc0t3b3ODi5eXe5Orq7vPr/Pv5gP377ezy9Pfz+vDw7vf4+/79goD9goCDgIKChfz3+YWChYmHg4OFioaE+vz++4GC+4CDhIeGgYCBiY6DiIOEhIiFhImJiIiFhIWFhIGFgoGDhYGEh4ibjYaFiIaFhYSDgYk1hIaFh4mHgoGIhIWKh4ySiImKjY6Njo+OjYuLhIqEjIuOj5GVmZyYlpSTj46Rk5SWlJuYl5iEli+amZiXmJaUk52anqSsn6Kip6OkpKepqKWrqKqmp6iqqKOnqKqtrampq6ywsK6xroCXmZucmpyemJ2coKOenJumdaOiprGhqKmqqaurq66oqaess7G0tLa4uri+uLe6uLm5tb+7u7e6ur2+vr68wcC+vL7EybzIw8fBv8PCxcbEwMPFxMPLw3THysrJycrJyMvNxsfIycXJyMjLycnMysnJ39fRzsnJz9TTz8vLzMjKzYDPyczMz87QyMvRzMvJzMnLycjL1czNzdDN0NHO0c/Pys7Lzc7OzdDPy9HTys7Qz83X0dPU0M7P0dDLzM3KxcjEysfGycjOy8vGysjGzMbIx8zFx8nIycvFw8fDycPCycrGxcXJyszLy8fHyczFxMrLyc3JzNDJ0cjFxMbFx8bEw17Gw8LDx8fLyMnIxcfFxsbGw8TFwcTAwcHBw8W+xMXTwr6+u7u+ury9vL3AxcK/vbm3uLq3ubm/vL26uby9t7m9u7m6u7i5ubm4u7qztba5ubq8wsC1vLe0xL23u7i+hLmAuri7urq6u767wLm7vLe/v729v7e5xsfFwcHExsfIzMbKxcjDx8THx8jHx8vLyczMzcrHysvMz8rT0cjLzMjPzM/PysrKxsrO0NDP09XP0czPz9TU1tDT1M/Qz9TTztDU0dDS0NLS0M5rac5p0WrSampqbGpsbNbScNLS1tbW29SA1tbY1dbb1tfV1tnV0dHU09DU1M7O0M/LzsvHztDJzs7PzsnK2MTCyMbCwsfDw7+6xLy+wMHEx8i4uLm2uLKusrKusK+ysLKztbS2ubi4trGzs62ssKersaqsrqu/qLCrrKmus6iqqaqqpaeopaeioqGfmZiWj5GVmJyWlpqcmJKAjoyRiYeIj5KGiouWiIuGhIWDf4GCgYB+fn9/gH58eHl6dnV2393h39xycuJ039vj4tzn3N7d2t/Y0tDRzM/E08vEwcS+xsnFwr3CucHByMPWy8fDvMC/wLC7wcvKwMC81cbIw8XCucLKw8nGwtHAu7zAxci8urrHvMDCw8XFy76AxMDBx87QyMjCxMLHvsbK1L/Eu73AwcLEucTL0sHDwLrFx8LCwsvGxs3Q18fSwt/Sz8nNz7/Jz8vzxsLCz83S0s7Lxc3Ky87MzdHIys7Q0dPJ2NTUbNTTzc3T193Y3NPR0dbW297Wbmzab29zcXJydNzW1W5sbW5saWlscXJz4+lN6uJydN5xdHN0c29vb3R3cXRycnF0cXF1dXN0c3N1dnNxc3JxdHRyc3V2fnp0dXd2d3d2dnR5dXV0dXZ3dHR6end9eHx+dnd3d3p4eXmEfU16fnl8fHt5d3p/hIGCg4SAgoSDg4OBhIWEhIWHhoWHhoWHiIaEg4qIi5CYio2NkY2Mio2OjoySkJSQk5SSkI2PkJKXlpKUlpWXmZWVk4COj5GRj5KVjpOSlpiUkZGbeZqZnaaYm5ubnJ6foaahoKCkqaSop6iqrKuyrq6vrq+tqbWwsq+zsre4s6+ssa+vr7S7v7K6tsO7ur+8vb28ub7Cwb3Eu3G6vbu6ubu4t7m/t7m+v7i8vL2/vsDCw8DB183Hw7+/xcnKxMDEw8DCxoDGwMPBxMDEur7GxcXHysnLycfI0cbFxcnEx8fFyMfJxszMz87MyMrHwcTJwsbIycXNxcfGwL7Cx8bEw8jHwsjGx8TDyMXNysrCyMPBxMDCwsfCw8bExcbAwMO/xMC+w8TCv77BxMXDxb69vsG5ucDEw8bBxsa+zMC+vsPDxMTBvoDAvLq6vLu9trW1tLm6vL28u76/uLy2tbe8vLy2vsDDvbq7uba7tra4uby+w7+7u7e4ubu5u7rAu7i1tLe3s7W8u7u8v7y9vL29wMC7vr/DxcbGy8m9xb/A2MrCyMTLxcXGyMjJzMvMzM3S0tfOzc3J0tTT09XMz93g3NfZ2tze24Dl3eLf4t7j4unp6OXf4ubj5+jp5+To6Orv6/Py6u3t7PTv8vHr6Obi6vDz9PL1+O3v6e3s9PX59Pf69vj2/vv2+v77+Pv5/P79+4KA/YD+gP+BgICCgYKB//qL9/b29PH38PP2+/f5//j49Pf48u/y9PT0+Pr09ff08vTw7vT37YDv7O3p4uT33dzm5+Pl6+jo497n3NrX19fi79TW2tfe3Njc3Nja1dfS09PVz9DT0s/OysrMxsLGvb/FvsLFwtzCzMXEv8HBtbSwsLOtr6+usq2xs7OtrqyipKamq6SjpaajnpuYoJiWlZyfjZOTnpWYlZSTkI2QkY6Ni4yNi4yJhYCBhYiDhIX29/749YCA+4H68/n07frx9Pbw9O/o5+fj49jn3tjW2NDY2dLOyNLI09Hc1Ond2dbR1tPTv8jJ0s/BwrvUy8/M0c/HztHM0s/L6cfExcjKzr+8uL62u7y8vb3Ft7+6vcXNzcfMxsfEybzBx8/AycLFxsbN0MnT2t/JyoDCvMfEwr+9x8XCys7Vw86/5dLPyMrotsLGxPLBv8HPzdPT0M7DycXExMPHysLHzM3Ly8HOy81pz8/Ix8rM0czOyMfIzc/U2NJrbNNramxpampszcnKbWpucG9ra2xxbmzOzs7JaGrQbG5ub25paWpucWlraGtqbWxpbG5sbGtrbIBubWxtaWpsbWptcXB8cmxrbWxtbm5vbXJubmtsb29tbnV0cnJwdHdrbGtrbG1ucHNzdHVyd3F3d3Z2dnl7gH17enl1dXp5eXt6fn17eXZ2dXd6e3t8fHp4d358fYGMe35+hIKCgYOEg4GGhoqGiIiKiIOGh4iPioeHiYeKjYiLipB+AX/FfgF//37/fvV+B39/fn9+f36HfwN+fn//fqx+hX0Efn59fv99qH0BfpF9A35+fYd+g32LfoR9A35+ff9+mn4CAgQAgLS3tLm3vr2zt7u0tri3vMPBvsXIx8rMzsrLyMXLzM3Oy8vPzsrL5NPU1dzR09LW1NHR1NLR0tLT0tjZ3d7d29rU19vT1dvf3uHd4+Xz4OLg5uvp6/Lw6uDe497f4uHi3+Hh5ePm4d3a4t/e3tbY4d/c2+Th6uzp9ujn4eLm3t7igN3h3+De39/g39/g3t3n4OPf29za5ebi4N/g5ePo4uXc4eHl4eLi3eDf3t7j4uLk4Nzb3Nzc5uPi2+Xq5eDo4ebk593j6unj4+Li4+Lf2+Li5OXj4uDn4eLh3dnd3N7m6uTu8+vs8fXt7+/p5+jl7+jl5OLn5ePm5uXm5uXn+vDtgO3q7urq7fDy6+/o6+ji6eTi4d3i5eLf4eHi4+De39fY2tbZ19bY1NDMz8/QzM7LyMrJys3QytLO08vQzs3Nzc7MzcrFxMrDxMPFw8XHxsnHx8fEw7u/xr/Av8HAwL/Av8G9wbq7urzEycrLyMTExMC+wcXBxL6/ucC7wbu7ub29Vri7wcSLvsLGw8jEv7+6uru4wMK9wsnIwsDGyNDGxsnJy8nLzMLGxMLCyc/JzMfP0M7PzMzIxGfKaMvSztDQasxmZ2tmyc7NampqadDRyM3NzGlraWtphGiAgtXRzsvHysvQatLVa2vS0dDT0M7IysnQatHT0dHQ09LP0M/N0MvMyMrHycrKx8PGvMbO09HLyMTFxsnKyMXGxMnMzMrTw8nGwsnDw8jGv7u7u7i5uLS7t7S/w7a7uLi4s72/t7u1ubm4ur65srawsK2uqqqprKymqamoq6WepKaAnpihnqCgo5eTjpCQjo6Rj46MkZWTj4+TjI+IipKMhISBg4iBhIF+e358fO/t7OTb6vPt+X//gPb79PPxeuvn6efj6OPl2dTU0M7LxsjIyMfNw8fJ0c3QzM/K2NHt2ODiys/X1M/X4YHx3djb19nS3NbR2Nvh39vSz9Pe29rb2cyAytLb287W4+L83+Xe7djW0NbU29re19va2vTb/+SB3N7Z29LU59ri6uje4uPd3ePj3ubygOrx3uKB5+Hf3tzn5uPY8PDp6Pjh5eHp6Ovn7Orp6eX5+vX3/vb09Pn7+O/y9/uA/v3+/vv79vHw+oT6/fyGhYL9gP34iYaFi4WnjIlBhoKJgoKBhYGChIGAgYOAhIKQi4iGiYaIioqLoIyIh4aKhYWKh4uHiIyGgYeChoWEh4mDjYmKhoeGh4qKjY6Nj5KEjWGGjIWFoZWJi4mJiZGOkZKMjoyIiomJi4yPkZaPma0lYJWLlJCTlpeVlJubm5efnpmfnJuWnJidl52dm6Gfppyjo6fgqaWxqKWrqammqq2qrKmusLuqqbixrK6trq2tq6+yM5udnKChoqScn6GdoaOjpq2opaipq6ussKytq6uwrq+wrq2ytLGyzLy9vcK5ubq8u7m5vIS9gMC9wMHExMHAwbzAxsHDysrDxMTIzeDJyMTIycfK09DOycnPys3Ozc7LycvMy83My8jR0tLTzc7U0MvJz8zT1dLl1NTQ0NXOztHNz87Pzc/Oz87OzcvL087R0M7PztfW0tDP0tXV2dXWztHT09LT1NDV1NPQ1dXT1NPT0tHR1dnUgNTN0dLNyNDLz83Qx83Q0cvMzMzNzs7KzsrMy8vKy9PO0NHPy8zKy87Nx8zPy8vN0s3Nz8zMzszUzcvKyc3Ozc/Rz87MyMnm0MzMys3Ly8/Q0crOx8rHw8nHxcTAw8XCwcPDxcfFxse+wMG9v8DBx8TEwMG/v72+vru8vL6/wb3FgL3Cub27vbu7vr2+vry7wb2/u768u7u8vLq8vru8t7q+ubu6urm6ubq5vby+uLy7u8HAv7++vMDAwb/BxMPFwMW/xcTHwsXCyMrBxsrNp8fJysfLysnNzc7PzNHUzc7S0c3L0NTa0c7Qz8/N0dXL0NHP0NLV0dPP1dXQ0tDT085rgNFsz9TQ09Rs1Gprb2rS1tNsbGxt19jT1tjUbGxrbWtsbm1rjd3Y19fY2tvfb9nWa2vS0tTc3dnV19TWbdbV0tHQ09LQ0c/O0NDS0c7Mzs3NzcnIvsXIysjEwsDBv8PEwcHDwcTFw8HKubu3s7eytby9uLW3ube5ure7t7S8vLG1gLOwsa64t6+1qqysra6yr6mvqaqoq6ipp6yqoqOioKGck5mcl5GbnJ6lo5iVkpOTjo6QjYyMkJCOh4eMhIaFhImHgYB9foF6fXt6eHx3eObm6OLZ4+bd6HjsduTp5ebpeOPg39zV1NTVz8nOx8TDwL/FxcPIw8PCwb/Bvb69xcHYgMTJybO3wLy6wct72cjExsPCv8fCvsbJzs/Kwr+/xsPBw8XAwMfSzb/By8nWxsvL38jJxcjFx8TKwsTDxNnB48Jtv8fDxsLF0sTHy8nExcvFyMnKxMfNacfPwMd60s3KyMPJxMO6z9DMzt3Q0dDU0c7Lzs3KycXPz83M1NLT0tfbgNfP0tfZbdzY19ra3dvd3+J24d/fdnZz4HHh2XVzcnVwjXRwbWtycHJydXNzdHJycXFvcm54dHJzdHJzc3FygnVzdHN2dHZ5dHd2dnl0cndzdnZ0dnVyeXh5d3l5eHx6e3t5eox4enx9eoB7eZSGenp6eHh+fX9/en5+fH1+fX17SXx7fXR9jVZSgn2IgoaHhIKChYOFgIaFhImIiYaIhImEiIiIjIuQiY6QksiTkJeQj5KSk5KVlpSUkJOVqZGPo5iUlpWWlpaUlpqAj5GRlZWYmpCTlZCSlJOXnZqYnZ6foaGjnqGcn6aoqaqop6mopaS+qaqqsamsrrOwr7Cys7OxsrOys7S3uba1t7O4vLW2u76/wcHHydnCwbzAwr/AyMfCurq/ubu+vb69vsDEwsTBvLzExcXGwcPKxcC8xMDKzczdzc7GyMzHyM2Ayc3NzcvLzMvJycrGxtHKy8rIyMbR0MzHxcfJyMzJy8XLztDNzszHy8jHxszNzM/Ny8jFwsXKxcbByczMxs7HzcnLwMXKy8bHyczOzcvFyMXGxsbFyNXKzMvHw8PBwcbGvsXHwcHDx8DAwr68vr7HwsHCwMPAvr/CwMHBvb/SxsGAwL7Cvr7BwcG6v7m8vLvCvsC9t73AvrzAv7+/wL/Buru+vL28vL++v77Dw8O+vry9wcLGyMnCycDBub28vsC/wsHBv7y7wr2/vcC/wcbIycfMzMXHv8TFw8bGx8nIxcbCysnPy8zLy83Ozs/Qz9PT1M/R1M/QzNDM09HW0dTU2dyA1dng5drg5efk6OXk5eXn6OPq7Obr8fDq6O7x9+3q6+3x7vT37vHw7O3z+fr89vn18PHy9vb0gPyD+f77/P2C/4CChoD7//yBgYGA/v33+vz7gIKBgoGCg4OAuP758vHu8fP9gP/+gID69vf+/vj0+fT9gP379/b0+Pn19/f19vGA7+vr6urn6ebf4tfg5+/v6+jk5ODi4Nva2tnj5uXj69zh3Nbb0dLZ19HR1NbU1dXQ1c/L1dfIzszIycTSz8bMxMbDxMPHwbi9tbOytrCysri0rbCxrrGvpKutpZ6npqexq5+dl5qamJibm5mXl5qYkZObk5SRkJSRjY6MjY+Hi4iAhoWIhIb7+/v05fT78fmA+Xzw9PH1+oDy8/Py6+7s7OXg4tzc2tTU19XR1MjLy87L0M7Py9fQ59Td28DG0MfFzdeJ8NLNz8zLxs3Iw87R2djUzsvKz83Jyca/vMPKyLq/ysjZw8jJ4MjLyMnAxMHGwsPFw9fD5MltxcrGxLu/y7uAvsXCvMLJw8PIycLFz2zK0sLJedTIycnH0tHLvs/MxMXWyMrM08/Pzc7Kx8bD09LOztHLysfN0M7HzNHUa9fR0NHPzsnIyM9w1NXVcXBu1WvSyW1pZ2tooHFxb25zbWxnamZnbGxtbm5rbmp1b21tbWptb25weHBsamlsaGpubHKAb3J1bmpxbXFycXNzbXRwcGttamxwcnR1dHSTcnJ1d3N6dHOBfXBwbmxscnBzdHB0dXN2dnR1dHd2enJ7iZKZgHqCfH+Afnx8gH19eH16d3x7fXl9e4B+goJ/g3+Een+Ag7WEgoyFhYqJiYWIioaGhoqLsYuHnoyHiIaFhYWEh4z/fv9+/36HfgF/sX4Df35/hX4Cf36Ef4N+hH+Gfop/iH4Ff35+f3+KfgF//36Tfol9A359foV9AX6qfQF+tX0BfpV9AX6EfQF+qX0Bfop9C359fX1+fn59fn19634CgX/BfgICBACAtLS7uLizt7i8vMO5u7y/usW9wcbGysjLz9DPz9HT1dPR0dDL0dTY19/e3t3Z3NrX3NjZ2d3d29rc4ePk5uXh4d7i6ODf3eXh2+Hn6uDi6ebt6YTx5+Ln5OXg5Ofl6OXm5uLn5Obl4+Lf5eDk397j5+jo6+fi5+7r7uzn6eTg3eiA3+Lj4N/i4Obm5ujr6OPe4OHi29ze4uTn5uHh4+Ti4+bl6Ori5+bo5+fl+3/t5ujl5eTo4+jn4eTk6ebo5ejr6Onq6u/o6efr5+bs6ubk6Obn5ufn5eHj5OTf5enq6unr7vD38/Px9e7v6unq5unt5+no6+ns5+jn6erp7/Ps8/aA9vDs8Ozq6u3u6+3p6Oju7u/l6+ni6OXj3+Pf4ePh4trc1Nnb39nU2M3UzM7O1NPRzszPzc/JzNDL1NHNycvOzNDOzs7KzsjKxMrOy8bIysnJwsHHv8LCw8vAxMPDw8LDvrzAwsK7w8LJyc/FycC9t7zAw8PHw8LGy8TDx764trqAvL6+x83IycLFxMS/vrq8vb/AvsjLysjFwMrIzMvUxsnLzMnNycjDx8rJzM7Kz9Fq2NLT1tRqaWnLysrUastoamtpaW5satHQa2nMZ8jGZWllamxtbtZpamtta9HNz9PQxmhraGpqbG5t2tTS023RaczVa9PR19PUbdHW1NTO2tSA0dPV0MzP0tbN3c7O0NnRz9DSz9HIzMzJ1NLSyMXMv8fGxcPKxMXFy8nG0Me+v7e/v76+y7u7xbu3ury/zsTCwb+9vb+/vLy1uLi1w7a8sbiysa24srOspKuuqqOgtK2xrKKanKGXlZWblJuVlZ+Sko2LjpKPjYyGhYuKkImBgHyAdoh3eHp5e/H19e/x+vP2/PzzfPTs8Ovv7ujt4dnY+NrZoYXNzM/MzL/N1NHd3dnU3tDRydLV5+Xd18/R1MzR2cpz7tXV0cvKx9HG2N/P0svT0cjS2NXV2d7a1trw2cnW3YfU1dr14tTQ0dLW2d3c193b7eXc5+rj6PPu5+zt7emA5vLq5evj2tfk3ejr297f8d7V2tbd4O7q3+3m5Obo8fPx+Ovy7Orl4vL08vvy+/37+oOC+f6AgoKBgIGA/YH+/oD6+4D49vX5gYD7/fqHg4OChIeJi4eIiI2KioeNhP+Dh4GAhIOChYiGiYqPi4mNjoyJjIiEjoiIioWDhoKLjYuAioyJhYOEgYKEgoKHh4qNi4uSlY+OkY2Gj4uLio6Li5CLioWGi4+QjpCVlZOXlpCSjY6Oh4uOkYqMkZGWmZiTjpKRkJKUlpOZmJuaoKahop6ln5ibmpyenqGep6impqKppKSipqekpr+uyqmtqqWqsbSvrau2tbWws7ets7KwtbaAnp6joaGbn5+io6ukpamrp6+oqausrqqtsLCur7KytLOzs7Syt7q/wMTEwsC8v768wb/AwcXFwsLCw8bGx8bDw8HFzcjJx87JxsnMzcbGzcnPyHLQzMrOzdDMztLP0tDS0s/R0NPR0dHP1dHV0M3R1NTU1dLLz9bU1tTR09DPz9eA0NDU0NDRztHR0NDU1NHO09fY0dLV1dXY2NXV2tvY2NvY2dnT19jZ2dfW9nnf2NfW2NfZ09fV0tbW29TRzMvLyszPz9XP0NHU0M7T0tHO0M3Ozc7Q0dDT1NTN0c/OzcvM0NDW0c/N0c3Q0c7U0NLSzM3MzMvR0NLQ0tHMztDJzNCA0s/O09DQz9HRzczJycnMzc3FycrFysnHxcvHysrIzMXKwcTFyMPAx8LJwcLAwsPDwsLGw8a/wcK/xcTBvb7AvsDAwsS/wr7Aur/Dvru+wMDBv77FvsK+wMW6uby+wMDCvr6/wcG4v77BxMfAx8K/vsHExsPGw8LEycjIzczJyM+Azc3Hzc/LzMjOzs/M0NDU1NTTztPW0tHOzNbU19jcz9LT1NPZ1dbS2NbV19fV19ht29fX2dxtbG7V1dbab9VtcG9sa29ubdjacG/dcd3fcHJubm5vbtlsbW5vbtjV2N7e0m5xbm9ubm5t2NXX2G/Yb9fcbtfU2NTUbdPX1dXP3NOA0NHU1dPT1NrS1srIyc/Jx8nNy83GyMjEz87SxcHGub69vLzBu7q7v768xL65ure9vLq6yre1vbWvsbS4xLm4tbKvsLKxsrKur66suKuvp62npqKrpKKblJuenZuYraq0qJ+Wl5uSkZGVkZaQj5eMiYWFh4mIhoaAf4GAhoF6e3mAdYh6e3x4eOrq7e3t8+3u7OnqffPu9e/w7uXk3NXW5dXRoYjEwcjEx77Hy8TJx8O/yL7AvcPG2dPEvLW4vrfAx7914szMx8G9u8O5yM/EyMfNzMTIx8K/v8XEwsbdx7fB0G+/wsfr1cnGycXHxcbFxMnH2tTH0c3FxMzIyM7Qz8qAxMzExczJwsbNxM7SwMbJ49DJz8vPyNTNwcrHxcfK0tLR2M7U0tHNytjX0NPJzczNzWxs1tlub29sbW1t127a3G7b3XHg4+DkdXPf4ON4dXVzc3R2dXFyc3Z0c3J5c+F0d3V0dHJycnNxc3R2c3Fzc3Fwc3FweXR0eXVzd3V6d3eAd3p5d3d6eXl6d3d6eHt7eXl+f3t6fXl0e3h8eXx8fIN9fHd4en19enp9fXx/f3uBfoGCf4CBg359f32Bg4WBf4SFg4WHh4OHhoSDh4iFiIaKiIWIh4qKiouGioyNj42WkZKQk5KOkKOUr5OWlI+UmpuYl5SbmJiTmZ6bn52en6EejY6UkpOOk5OVlZ2TlZealZ6anZ6fop6fpKSipKiqhKtlqqaqq62rsK+urKuur660srKztra0tre4u7u7urm6uLrBu7u4wcG9wsfHu7vAvsPCbMrFw8bDxr7Aw8HDwMTEwcbFyMfHyMXLx8zJyMrNy8rMyMLI0M/R0cvOzMvK1MzN0MvLzcqEzYDR0c/KztDQyMjJycjKysbGx8nHx8vM0NHIzs7Ny8rH/I3Uzc/Pzs7Qy8/Oys3P183OzMzKycnKyM7Iy8zSzszQzcrIy8fJycjIx8XFxsbCx8fIxcTCxMPIwsK/wr2/v77CvsXJxszIysfHwsTCxsfCxcW+w8XEwL3DwcHAwcG+wIC+wcLIyczCxMXBxcLDwMTBxcO/wry/t7y/xsPBx8DHv8C+wsPDwsDHxsi/w8O9w8G/vcDDwMLBwcC9wb7AvsTJx8XJycnLx8PMxcrFy9XKycrKyMXIxMnR1dXO1s7R0tXQ2NPQztHR09LV1NPW3dzd5eDc19/g497l6ebn4enp6oDo6ubp6Onq6O/y8e/r6PHt7/P77PH29/T59PLs8/b3+fny+fyA/ff4+/2AgIL8+/r9gf2Ch4mGhIiEgf/9goH+gfz/gISAgoKDg/6BgoKFgfn3+v/984CDgIOAgoKA+/f4+IH6gPb/gPv4//r5gPj++vr0//Xv7vHv6O3y+Oz56YDn6/bv7PD18PDl5uPe6Ofr4N3l2eDf3Nre1tXW29ra5t3Y3NTZ19LQ4M3L1cnCxcjN29DOzcrGx8nHxMK4ubaxwLG3rLavsa27tLaupq6wrKehta6+rKObnqWdnJuemqCYlZ+YmJSUlZSSkpONjZGQmJKJioaCkYiFh4SD/f39+ID5/vX5+fv5g/z4/Pn5/vf58enp+enk07rU0tjS1MfT2dPZ2NTS39ba1Nvd7uja0MTIz8TM08eY9c7NyMLDw8rF1NrKz8nS0MvU1M/LycvGwMPZwrK/tXDBxsnt1czL0tHU0c/IwMbE1My/yMe9xM7KxsfIx8C5xLy6xcbBwcvDzIDMvcTG6s3GzcnJytjSxNHIxcbIzc7M0MnS0c/MxtfY0tfP1tPRz2tpycljZmZjZ2doz2vS0mnQ0GrR09PWcG3R0Mxua2tqamxvcGxucHRzc3J6ctZqbWlrbmxsbm9tbm9xbm5wbm1tcG5qcm1scGxsbm1ycG9sb29sbHBxcXNxcHVzcXJyb21zdm5vc3JsdXN5c3V2d396eHNxcnR0cHB0dHJ1c3F1cnR2cnR2d3Jyd3d9gYN/e35+en6BgX6DgoKAgoJ9fnp/fXp+foCCg4WAhIWCgX2GgoKBhIWChZiLpoaJhoCDio2Li4uUkIyHiY2GiomIi47SfgF/2H4Bf/9+/36FfgF/hX6Df4R+An9+iH8Ifn5/f35/fn6HfwF+hX+Gfoh/hH4Gf35/fn5/hX4Bf/9+j36LfQF+jn2Cfp59AX+efQJ/fs99BH5+fX2Hfgh9fn19fn19foR9BX5+fX19kX4Bff9+on4CAgQAgLq5ubm6vry6ubm8vMq/vMHEwMrNy8vOytHQ0NLL2NjU1tjY3djZ3dne3+Li4dra3dzd49rg4eXb4+Ph4N/v4ebl5Obk5ujr6enq5+zp5evv9PDw7u3u6+jm6unr6eno5eXo7+/s8uvl5uzp6Onp6+jq6uTo6enu7/Lx8fLr8OrrgOfn5+3l5ujk6OLu7u3q8u7s5uTm4+7n5+bo6uro4+bq5ID86OPs6vDu9/bw7/Pv6vLu9evu8urx8O3s8vH38/n1+Pj5+fTn8ujv7+vt6ejt7e7p7enq5+Lm4+Xl7+/v7env8+v28vb27eno4ejo8fTw7fjr6+3v7O738PLu9fr3gPX29PPy9PLx8/Xv8e3u7+7u8fDt8e7p5OTj4Nzd2dvU3drZ19vd1Nnd2tfT09bZ99LVzs3M1crOys/Kz9TU1tLW2tTP0MnK0tLR1c/MwsjIysfGx8bHxMrJzsrDxcjI0MbFxsbCzcbFxsLhycPBxMLDxsPDwcTGx8rM1MXGwsK9gMLJxsnRztPNzsvHxcHAvLi/vb7K0svNxcnO1c7Nzc3Q09TMz8jKxcrFzM7V0NHR1tbS1Wxvb3ZzbGhreZlqcmtqa2pnaGxpamxqamtpZmZoaGnRatNtbGtra21sa2ppZ2doZmpn0Gza3t/T02tq0m5sbGtra21xbGzX0Wxr1dLMgNFv3NbZ2NbOydDQ1dDV2NXRydLRzsrLx83QzczIzMfDxMPIy8vNysvLze7GwsHBxcG/2be9v7q1t8rOwsHBv8PDwLy6xL26vbvKzbq9wLvDvLe7vra4sKm1sLCttMGqoqejoJ2joaqtn5mll5yUlJCTkJOTj5SVlIqEi4SFjYGASIODgn2BgHzz8318foj0+O/3fn737nt473jreXnz6n/k3d/X1MjM1dbQ1NHY2Nnd2NfR0dDRz9viid7Z0d/X0tTLyMfMzNLU54TXgNXX1dnhjPrS293h6OLe29nbyMrQ7dvm6t2H1dLY19fg1NPU2trf5drt9PuM7ufs//Xl9fbw9f3s5N7k7fH39vHu5+7l4OLq4Ob08+7m7O/s7+7x9ISH9uz2+fPw6/Dy8v339/j0/ob8+JH3+IL7/Pb/gYL+/oD/gYD094KChYaJgImUkoyJkoeHhoeJiI+LjIiHhoODgf2Kh4eKiIuKjo6NkY+QjY6Qi46OiIabj4aHiYuMjIqMkYyFiIuHg4ODhoiKjYyOiI2KiYeJjIuNjouJl5COkIyQkZGLjZORlJSYlZiWmo+KkIuIjo2Pj5mRmJOZlpOWkpKVlpWYlp6kpKSnNK2qpqawoqKjn5+hpauopaalqqWnq6WmrKynraWqramop6mtq7OttLPGtsKwtreyt7myu71Ro6GioqSkpKWlpqqst66rra2qrrCwrq+ts7KwtLC5uLa5u7zCvL3AvsLExsXDvr7BwMDFwMfJzMXNy8rIxtLHycnJy8jJzM/MzMzN0c/M0dDShM+A0tHPzdPU19bY19TT0tbU0tbT0NLX19fY19nX2dfT1tXU19jW1dbW0NnW2NbW2N7W19nR1dDY2NfU2djZ1tfZ19/a2dvb3dvb2d3g2nvo3djf3d7Z397d4OLf29/a3tXW1tHY2dbX2NbY09jW1tTT09fR2dDV19TW0dDS0tPP09SA2NbR1dHPzNPQ0M/N0tfO1dHU1dLS0tDV0tbV1NHb0NLU1NHU3dXY1NnZ19TT0M/O0M7Mz9PO0dDQz87NzcvJzczLyszLzc7RzMzFy8rKycnKwsbJxsTBwcbO98vNx8XDzcPHwsK+wcTCxMDEyMXBwr7Ax8bHx8C/trq8wsHAw8SAw77CwMS/vsLFxc3FwsLDvsrGx8jB28jFw8PCwsXExcbGx8jJy9PIztDSzc7Ry8nOys3K0dHP0tLV1NXd1NDV29TY0tbX2tbY19bX2NjV2tXc2N7Z3Nvi2tva4N/b3G9ycnp1b2tvfZ1udG5tbmxsbnBubm9ub3Bwb25xb27bcN4Ecnpub4VugG9ubnJxdHDfcODg39jdcXDfdXJwbWxsbHFsbNfSbGzY2NLWcdrT1dTV0s3QztLMztHPzMTKysvHyMXIyMTFwcTBwL+9wcPAwb6+wMTYv767ur+7vue5vL22r7HByLy7ure4uLSysbm0sbWzwMOwr7Gssa6qra+pqaKbpaGkoaq2gKego56alZiVnaaUkZuQkIqJhoiIiYiGiYmIhIGGgYKIf32AfXx6e3h36+Z4eXmC6fHn7Xt98Ox5d+h35XV26uF42dDRy8m9xMvKx8jExsbHx8XFv7/BwL3GzHnFwbrGwsDEwMDCxcbHxdfBv8DCxMnK0NWL6cTKycjNyMfHytC7gLq80cHJ0Md6xsPIx8fOwL/Ax8rP0svP0M97w8LK29LBx8fGy9XPzMXFzcrNzMvHyM/Ozc7Tzs3W1tHJy8zJzM7Q1HJ11s3V29fSzdHPz9fS0NXU3HXf3Hvc4HTi4drjcnLb33DfcXHa23V0dHJ0dHt5dXR7c3Nyc3V1eXV3c3R0gHR4d+p/eHV3dXV0dHRydnR2c3N3dHd3dnOCfXd3eHl6eHd5fXp1en17eHh3enp7fHmEen16e3l6enl7fHl7l4F+gHt/foJ6fYGAgH6BgIOCiYGAhYOAhICBgYV+hICHhoSJh4eIh4eGgYaJhoWGiomGh5GKi4+MjIqKj4yLjo2RIo+Ul5OTlpWQlpCUl5WWlJeamJ+YnJyrnaaXnaGdoaWgpaWAkJCSk5aamZiWlZeVoJiVl5qZoKGioqOfp6emqqavrayur6+yrautqKytrrCyra6zs7K4sri3vLS+vbu5tsO4vbu8wb2/wcXFxMPDx8PAxMTIxsjGyczKycnLyMnHxsbExMXNzsvRy8bHz87O0NDRzc/Oys7OztPU1NLS0svT0dKAz8/R1M3P0cvOydLS0c7V1NTQ0dLP187MzM7Pzs3Kz9LPdN/RzNHN0c3V1dPU19PQ19Ta0tTX0tfZ1NTX1dnS1tHSz9DR0srUys/Qzc3KzdDQ083NzM7KxcnIx8XOycjEwsTHvcjBxsfDw8XBxcfNz9DO283LysnExs3Exr/ExsMEwsTDwYTCgMbIxcfFxsbExMbCwsfFxcTEwMPBw8HEvsLAvr7Bxr7Fy8nFw8LGzfnGy8fHxtTCxL2/usHGyMzHy87IwMK9vsfJy8/LzMTIzNLOycvLysTLzNTPztLU09vS0dXX1ebd29rQ7NjU1drb3N/d3Nzf4eLl5u3e5Obo4+fs6Oft5+nkQ+zp6e3o6unn7unq9f308+nr6/Tu8fHx9Pj79Pjx9/H48/r7//n6+P7/+/6AhYSUjIWAg46wgoeCgoSDgoSGg4ODgICEgoCEg4L+gP6Di4GBgYKBgIGBgIKFg4aC/YD///7094CA/4mEg4CAgIKFgID++ICA//vy9If38PLx8+7p8fD18PX49vPp8O/v5uTe4ePh4d7k39va2Nzd2dvX2drh9NvY19bZ1tfuztLRzMPG2uDRz87MzszIw7/Iwby/vMrOuLi5s4C7tbK3u7W6tK68t7axusKvp6yoo6Cko7G5op6qnqGZmZWXl5uZlpycnJeSmJCRk4mHioiIhYaEgPz8goGCjff+8fqAgfv5goD+g/+Bgf/6hPHq7+bh1Nrg3dTTztDOz9LQ0tDR0tLP1tqH0cjAz8nGysbCwsbFx8jbyMfIyczNz4DU3IzwzNXV1trRzMbDyra3utXCysrDgMPBys7Q2MrHxcbFxce8x8/Xh8jBydnLu8XEwcjUysjCxsvM0s3LysfPzcnL0MvJ4t7Wy9TUz8/Rz9NzdtLK1tvW0s3QzMnSzMrNy9Ju0sxvy9Bs09XQ2m9v1NVs1GtqzM5ubW9tbGpvbIBqaXJrbGxqbW5yb3JubW1pa2nOdHFwc3Bxb3Byb3Jwc29ucG1xcnFwiXlyc3NzcnBucHRxbnV3dXJxcXNxcnNwfW5xcG5tcXR0dXhzc5d5eHl2eXl5cHFycG9ubmxwbnRtbHJwcHVzc3Z8dHp5f317gXx7fH16e3mAhIODg4WCfDF9iHx+g4CChIWIhoODhImHh4uHhouLh46KjpGJiYeIiomRjZOPoY6WiI+QjI+QipCR/36ifgF//37/fpN+n38Dfn9+kH8Cfn+FfgN/f36KfwR+fn9/hH4Bf/9+hn6CfYR+hH0Ofn59fX5+fX59fn59fX6ZfQF+mH0BfpN9AX6RfQF+qH2CfpB9B359fX59fX6EfQp+fn19fn1+fn19mn4Bff9+n34CAgQAgMC+ub2+vbm+wb3Ews7CwsLAvcPPzdHS0dXT1dbY193e2uPe39ja2Nzi3+bt5OTl5efp5unn6+fn5+Xm6eft6+3x+/Hy8u/y7+Ts6+Tm6enp8Ozw6ujq6fL97/Pu7+7t7PD08/Py8fXz8fDu6/Dt8PPs7Orq7+3y8Ozy9fPv8fLygPPx7e3p6+3w8+/x9fTv7/Xt7O7z9PH17/Lt6Ons7ens7O/56+j3hPz3+//49fDy8e3x7+zu+YD59PLu9vaZhvyD//ry9PPt7e/r7ezv7uvv9Pn18/Ls6Ojn6Ovs7O/u8fXr7fH0+PH18uzq4Ofo9PL5+4D37fL48/Dx8e/u9vT2gPf69Pj5+Pj4gPPz8/Ly7+vt8vDw7Ort5ujs4t7a4unb2tvf49vj4tve29rc29va19HOzsrKz9fU1tTZ0NXS1dLS1NPU0NfT1NLP0c3PzNLMx8nJxcbFydLNzcnIw8bGxtfe0MzPys/KxMW/v768wMHFw8HHxsLEyMnKy8h808jIML7Fx8vSz8zKycbMy8jOzMzGysl02HbNwsTKx87Uz9TQ09HMzMzP0M/O0GfLzs9s0IRrgG1rbG9sbXBubWtub2tubm5tbm9tbm9ubWxqampsaWpvaWxscW59bmxraWhnZmprbm1rcXd0cW9t1tBq0Wpsb25wcHB1cHBucW3X2NvS1tPc2dnY13HU1dTV1djd2s7Uz9HO0MrSzMfIzcvdzs7P18jRz8vN0tbUxs7JxMjKzdvIgNDTzNDKy8LKxc3KxsjCvb/KvLy8xJbMv7+/wMPAuLm8u7q3rq6yqq24pqWmqa+prKyttqGenqWemJaZmJeYl5KSkZeOi4qDh4SJi4F7fHt+fXrv7e16fuX4fXvzgYSChIJ6e+nneujse7Hx3d/VzNZ9ytLMzNHg19jX1tfQ0drWgNnP2tnd5u/j3NbQ2dLTxsrN1trf2NXSzdHV2Nbd092D2dnd3NnX3NngztTcz9DPzNzY1NTcztXW1NTb2d7f7eTm7uTh4ub28Pbs6u3h6eHt3Ovu+O708fLv8vP48uX6+fPq6e3z8ebh6/P7+/vz7vPx7O/o9Pf08Pn4hoGAgf32gPD49vH6/P/09fv9gYD//4aDhIWBhYGJhoaIhoiOio2LioeJhoyKiYqOi4mAhYOHi4uLiYmOlIuMj5WOkJKUjZCXiIqTjYqIkIyOkYuJiouKh4qIjImJjoiHjpCPiI+MkI+PjYyPj42ckZSTiouPio+SiouMkZSYnJuYlo2SkIyXTJKPlZSZmZiVkpqYlJmamJiUm5qen6Oho6OorKuwpaijpquno6GopqamqKKoqqipq7Goq6musqmup6usrLCqsq+7vLq0u7q5vr/AxNeApKKeoqSkoqmuq6+ttrCysq+trri1tre0t7S2t7m4vLy6wb/Au76+wcbFyMzExMPDxsfGycnMysnLyMvMy87Ky83Wzc7Pz9LPytHVzs/R0tHU0dbT0NLS1+DW2djZ2NnZ2trY2djW2NrZ2NXU2NTY3tjZ2Nnc2d3b1tve29fZ2txa4Nzc3dnY19nc2Nrb29bW3tvc297f3N7f4t/d3+Hi39/g5e7g3+l55t3f4t/f3N/g3eDc1NXccNvZ2dbZ15Fy1nDc2tXX2tjZ29jZ2tnU0dDR1tXW29nW1dbThdKA2NrP0dPU2NTY19fU0dfU2tjc2XDV0NTZ1tXW19bV2dXX1tfS09LS0dFtz9DQz9HPzdDS0NHPzdHN0dbS0crQ1crKzNDSys/PxsrJycrGxsjIxcPFw8PHy8jIxsnBxcHDwcHDw8XBz8nIyMXFwMK+xcG/xMfHycPDxsXHw8XEx8ZcxtndysXGwcfEw8fGx8XFx8rOysjKyMXHysvKyMZ+19LV0NDLzdHPz8/S0dXV1d3b3NbY1nTdetfT1tzc3t7Z3dnd3Nna2uHh4eThcd7e3nPccXBvb3FwcHJvcHSFcAhtb29ycnJ0c4ZygHFxcGxtcW5xcnVxgXFwcG9xcXB0dnhzcXJ2c3FxcuPgc+BwcHFvcXBvdG9vbXBu2Nrd2dvX3dnX2NZx0dPT0dHR1dPK0MvNysrI0s3KycvH2cbDwMW4wsPBw8nRzcLKwr/DwMLMusDDvMC/v7vQvca+ur25t7jDtra2vYPFuLa2gLW2sqyrrqysqaKjpp+krqGlpaWqnp6en6WUkJCVkImKjIyLjo6JiYqPiYyKhIeFhYV9enp5fnx37Ozod3vm83599H+AfX18eHno6Xrk53er59bX0MjTgcTMxsbJ1srJyMjIwsPJxMO2v7u+xtDSwsPBysTKv8LCycvMxsHBvMHMgNDR2M7VeMzKysjIx8zM1L/Dx7u6vb3Tz8vO08XKzcnHyMjMztfNyM3BwsLEzsjRxcfKw8vGzb/LzM/IzcnLzM3Q0dDE19jUzMvP0tPJyc7W29nY0tDW1dLW0djX09DW0mxtbW7b3dff3tzh4uTZ2dvccG/b33V0d3d0d3J2c3NzgHB0eXV5eHd1d3V4eHl3fnt6dXl4eXt4d3Z0eXx1dnh8dnh4fXh6f3Z2fnl3dX16eHt3dnp7eXh7eXx7eX57fH+Af3p8eXx9fHl6fXx6i4CEg35+gn+GiH9+foCChIeGhIR+hIOBiYN/hISHh4WEhIqJh4yPjYyIjImKiIiEhYSHEYuLkYuOi42Rj4yIjo2QkJOPhJQdlpuUl5GVmpSYk5iZmp+an5yioqKcoaKgpKalqLiAlZaTmZmZlZmcl5uYoZudnZybn6ekp6ajp6apq66usbOxt7S0rq6srrGusrixsbK1uLm3u7q9vL/Bv8HEv8G+wMLNxMTFxcjIwMjJwL+/xMPJyM/Lys/M0tvOzsvNycnIzc3Oz8/O0tPR0dDO09DT19DOzc7Rz9TSztLU0c7S09SA19TS0MzP0NPV0tHR1M/R2NXV1NfZ09bU1c/Nzs7R0dHS1+DU09944NTW3NjZ1tfa1tjV0tXdctzY1dPY2Zdz1G/Vz8vT1dPV2NXX2dnV1dDR1NDMzcvJys3Nz9DQzMvM0MPExcjJxcnJx8bAxMPJxs/NcNDLztLNysrIxsHFwsKAwcbBxsjIyMlpxsjJycvJx8rNysrHxMfCxsvGxL/Gz8TFx8rMw8rLxcrKyMfFwsLEwsPHx8bKzs3Mys7Jzs3PzMrJx8XC2s7JzMvPy9DN08/O0NDOzcfK0NDW09fX2dnW6/Le29/c5ODg4d/d2tnc3d/Y1dra2ODn6ero4ozv6OqA5Ozr7vLu6Ofq6e/v6vHv7Ojt7IT+i/bv7vDy9Pnz+fj7/Pf4+P39+/7+gPr7/oL5gIGAgYSCgoWBgoWDg4OEhYKFhIeHh4mGhYSEg4SGhYeGgYKFgIKEh4Sbg4GBgIKDhIiKjYiDhIiDgoGA/vuB/4CBhIOHh4eMhoeDhYH+/f2A9fjz+vf3+feE9PT09/j5/v3v9u3t6Ojj7uPe3+Pg/OLi4eTS3NvY2+Dr6Nnk3Nfb29/u197h2trX1c3o0dvSz87HxcXQwsHAynjOwL68ur25srO4uLq2r6+xqa69q66usLapqqqsxqWhnqOel5idn56hn5iWmaGWmZWKjImKi4SAgYWEiYaD/v79gYHt/YGA/4WHhoeHg4T9+oT4+IDS/err49fff9DVzc3Q3tTT0tPUys/Y0tTI0c/Q19/e0M3K08zOwsPDys3Ry8bGwMXJzc3Xy9iC0c7QzcfDxsPMusDFu7y/vdPNycrSxMrNzcrJw8PCz8bGzb/Hv8LSy9HGxcaAvsfDzL/OztHIzsjJyM3O0s7E1tfOxMTLz9HJyc3T1tbW0M7U1NDTzdbW0MvOzGpoZ2fKyMLKzMrR1drOztTTamnS025sbW5qbmpubGtraG1wbXBwbWptam5sbWxycXBqb25uc3BwcG5zdHFydHdwcXF1cXR7cXV9eXh2fHVydG6AbW9wcG1xcHRwb3RubHJ1c25ycHN0dXR1dnd0gnZ5eHJxdnN4e3Bvb3BwcHNwb29qcXJxfHd0eXl8fHt6d319fH6CgYJ8g4GGgoSCgX6Bg4GFfICChYmHgn+GhYaGi4aMjoyLjZaLj4qOkIaJg4eKjY6KjomQjo6KkZGRkpSTlq//fqd+AX+PfgF/hn4Ef39+f7F+AX+VfgF/834Bf5Z+A39+f5R+Bn9+fn5/frx/BH5+f36Nf4t+AX/AfgGAvH4KfX19fn59fX5+fYd+B319fn19fn6GfQF+rn0Bftp9hH6NfQR+fn19/36+fgICBACAvbu9xL7Cw7++xMG/zMTHy8rHzszN0tbY1tnW2NzZ3dza4uPj4eTw6Onx7u/p6+vl5+zn5uTs6vHx7urr8fD28fX09f36+Pr29vX18vrw8fDy9O3v+PP68vT59PHx8PLz9vT7g/74+ff09fX0+vHy8fLs5/Hv6/Px7/by7fj08PCA6+nr6ezt9PLwjvfz9vP69/f0+fX39vPy8vTy7vHv8PDx8Pv9/fv69/T09/+F9PDy9vb0+Pj0+/L18/T8+P3+/f76+/v49fn08/Hx7vL39fj49/738+3s7uvs7uvr8PLx9fXq+Pn4+/f58fDv9fL1+oCAgPr59/3x8Pb/9feI+faA9fT4+vr6+YD8/v38+fv38/Tw7vDw6ers6efo5OHj5eXg3+Lj497h4uPe2dTa1tfb2drb09bS1dnV4dbR1d3U1dfd3t7X0NbX0dPT0NDM0crKw8nHy8vXzdHRyMXEzs3Vz9HU0c/QztrMxcDQxMbGyMbQzNDQysrV2NHTe+HU0c6Ay9HWz8/W0MjIzHjQz87N2srO0tbOz83HxsvQydHP0M3QadPRztDQ0tBoaWdpbGptcG1qbWprbW9ta2txbW1vb290cHNzcXJ0c3JzdnJxb2xtbXBvbnJ1dm5wcnFtem1rb29tcG1xcHN1cHBzb29xcXJxcXJxcHFwcHFxdHNv2taA1G7W2dTU29va2tjZ2Nnb2tlz2tfVy9LS39HS2MTP1MvO0W3LzdvVz8/Xz8rT0c3P1s/LysrDwszF07/Ny8jSz8nFxsjRy87FwMbIzcrFxM7Lvr/JxL67trq6t6umoqG0pKmrr7CuraSfl6Kdlpa3pZuYkpeVkZCQkJSNi4SGh4aAfHzyen3083qBgIOAgoKBh4WDgHx/eH1/gfN87uzv8Obj3tjW19Pa097i49vf4N7e5NDa3N3d4Nna29zc3d3W1dDSy8HS0tva2tnU2tbe3tzp7ODc4dzi5dPU1NLQwtPO2eTh4tvg3dza293UzN7e4/L+8+/s6ez39veF9vf35u+A6+/t7vb1+IDw7uz48ezn7Ivw7Pbx8u2B+fbx9PSL9f//8fnr6/by+f/6/fz1+/j6+fPx+f72+vfw+vv1gICDg4mIh4iRiIeFiYGPh4eFh4SAiYmNjIyJko+QjoqNg4WDiYeOjY+Pjo2Mh4yNjpWTlpKTj5GOi42RjJONj4+QjpKAkJKPj4qMkIiIiZCNi5CUj5GOlZGUlJWTkJKPj42Sk4uQjpCSkpebkJSYmZ2gnpqYk5KXlZucm5qemJuZnJmfnpufn5+gop6no6KkoaGipLGkpqafpa6mpKalqaenqaqqpqanqquysbOFt7azrq+ssLO4tbO9vsC5vri2t8e+wsGApKOmq6WprKqssa+vtrCxtLGxt7S1ury+ury4ub27vr2/xMXDwcLJxcbLyMrHycvFx8vKysrZ0NPTzs3K0s7TzNHQzdLQ0dTU19HV1dzT1tja3Nfa39ne1Nfd29rb29zb29jddePg3d3b29nZ39ff4eXg3unk3eLf3ODe2uPg3+KA3t7g3t7c3NnWiNrY29nf39/d4N7e393f4OPl5Ojk6OHg3uXi5OPj4d7f4OZ/4+Df4uLc3t/b39ne3tze2eDf3+Le3tvX1dzb3dze3dzc2dfV1t3Y2tjX2dbX1tPV1tjY2tnP2tjZ3Nne2tfY3tfX2W5ubtbX19/W1Nff2Nd02deA2dfb2NXV0mrS09LT09TU1tfT0tXW0dTV09LV0dDR1dLMyM3OzsjLyszKx8fLyMjNycvLxcbGx8vH0sbDyNHFxcXHx8jDwcjNyMrGwsPAxMPHw8jHyMXMwsfKxcTGzcnQycfIyMbHyNjMysbYzM/O0cvQ0NHPysjPzsrMeOnX2dU81NbY0tHX1dHW2Yfe3NjV49XZ39/Y2tnb3OHk3ODf3t3ebt7c2+Di5eRycnFxcW9wdHJwc3FycnVzcnJ3hHIxcXVydXRyc3NycHF0cnVzcXJydHRzdXp4cXFzcm+Ac3J1dnN2cHRxcnZvcXNwcHJwcoRxgHBxcW9vbm9wb93c23Ha2tTP2NnX19bX09bW09Jt0tTRy9PX3s/N2svQ1srKynHBw8/IwcXSyMfOysXDy8PAv8C/vsrFzLvGwb/Fw768vb/Hw8a8t8DBxsO9usK8sbO8urOsp6yrrKiloaOyoKSio6GcnJWSjJORi42rmZKNiYyLgIqMkJCWj42HhoOCennreXrt63d6en19fXt+hoB+fXl7d3p7fOx2497j4dzW0szKycXMws3Q1M7R0s/O0cHJyMjJxsHFxsnSzM7IysPHwLfCwsfDwcG9w8LLx8fS1cvGy8fMz8TIy9HVwcnDyMzJyMTMztDOz9DJws3LydLd0tDNgMrN0tHRg8/X2cvNzs7Nys/Q1GvOy87TzczLz3rUztbT1cx03dnV1tZ53d7g1N7S1NzY3N3W2tnU3N3i4dra4+Pe5uPh6urhdXFxb3Jwb3B6dnd2e3N9dnd1d3Z0eHl5eHZ2e3l8fHl/eHp4fnx/fHp7eXp6dXh4d3t5fXl7eXt3gHZ4end/eHh6fHt9foB+fXp8gHp8e39+fH+AfX97f319fXx8e3x9f36DhH6CgYOGhoqMgoKEgoSFhIOFhIKFg4eGhoWHhIeEh4aKi4qLioqKi4iQjIqMiIiKipaKjY2JkJiRj5KQkpGTlZGTk5KQlJOWlZdzmpyamJuZm52gm5mhC6GknqOfnqO7p6qpgJaWmZ6Ym5uYmZ2ZmKKcnqCenaOipKenqKaop6uyrrSzs7e4t7SzvLWzure5tbm7ubq/vb69xcDFwr+8vcLBx8DExMTJxsTGyMrDxsfNxcfIyc/MztXS1cnO1M/O0tDQztDM1HDb1tXS0dLR0trR1tfZ09Lb2NLa1dLV1M7Z1tfYgNPS1NPT1NjV0JrZ09jW3d7f3+Lg4N3X2NTW1dPV0dbT1NPb2tza2tjX2NrfftvW2dvY19zd29/Y29jY39re2tbZ09XZ2tnc1tbT1NfZ29vY1NLX0dHQ0dXW19nV1tbW1NbTxtDOzM/Mz8vMytDKy85qaWnNzMvTysvR3NXRbszFgMXFys3NzctozM3NzczOzMvOy8nKzMfKzcnHycTEydDRzszPzs/HyMfLyMTBwr2/xMLFyMbIycnRzNnLyMzWzMzMzs7Nx8PLzsvQ0M3SzdDO0cnPzc7O2M/W2tTT1t3b4tzd4OHg4eX56ePg7uLl4ubf5uTk4t/f6/Lt7or/8/HvgO709+7x9/Hr7O6V9PTy7/zw9fv/+Pv39fP4+/L8/vz8/4D++/f7+/7/gIGAgYSAg4iFg4WChISHhIODiYSFhoWEiYSJiYaKi4mIiIqIi4mGiYeJiYaIjIqDg4aFg5qIiY2Ni42GiYeIi4SFh4OChIOEhoeJiomLioiIh4aFgv/9gPqB+fr08P7///749/X2+fn4gfb08env8fvm4/Hg6fTo6OqA293r5eDi8OTg6eXg4eff3NnY1tXf3OTR4NrY49zTzcrI0czQxcLLyM3HvLjEvbK3wsC5sayxsbSvr62vv66uq6yppaScm5WhnpaYu6mhnJibmZiWl5aZj46JiouMgIGB+ICC/vyBhIOEg4KAgo6GhISBhICDhIX/gfr0+/vx6+fe2NjS18rX2eDY39/d3eLQ19jZ2NTNz83M1dPV0NLMz8i9ycnOyMfEvMPBx8rO2dzU0NPP1NfHycjJzrrGvsbKx8K/xsrMzM3QyMHNzMjP2NHPz8zR3NjYi9XY18jLYsfIy8fKztNqx8rI0MnKxcx50cra1NTOdNzVz9HQdtbW1sfRxsrT09vd19rVy9LOz83Fw8nNyNLQzdnZ0W5qamhramhqdG9tbnJrdXBwcXFxbHFwcW1ubXJvcnBsb2lsaG1rhW9kcXFvc3JwdHN2cnRydXNyeHx6g3p6d3VydHNzcXFvcXZvb29zcXBzdnJ0cXVyc3N1dXJ1dHVzeHxydnR2d3Z6enFxc3F0dHNyc3Bxd3d/gH99f3t8eHt5fX98g4SDg4WCioWFhYSAMYZ9fYB+hIyFg4KAhYaKjo2Ni4mIiouRj45ujI6LioyLjpGYkY6XlJKPkYyNk6+UmJnjfgF/pX4Bf6J+AX/DfoN/in4Bf4l+AX/zfgF/jn4Bf5Z+AX+HftV/BH5+fn+PfgF/kH4Bf99+BX1+fn19kn4CfX7ffQF+jH0Bfoh9AX6GfQF+hX0Bfp59/36tfgF/l34CAgQAgMTFwMTCx8TJx8nL1crNztTX1NrWztXU1dfV2d3c3t7g3+Pr8eTt6/Dz8PPt8u7p6ebu5+fp6uzs7fP18/r29ff59vmA9/2A+/X8+/f7/Pb09fTy8vjy/P6A/IiMgYL9/YCBgPuG+fv4/P3++vj28v337u7s9vT3+fz7+/77+vj1gPPu8e7v7/Hx7fuA+Pf7+/nx7/Tv+f3y8Pb28/jz9Pf49/r+goL/gP748e+D+PL2+vz1+fz3gP77+vv4//3+gIT+/v75/Pr6+PL1+Pb8gv/8/fn7+/X08fLv9fHy8/H5gYT9gIH+gIGA//79/fj3+P38g4SAgIL//f2BgvT7g/n8gPb29/38+YCBgoCAhYH//v389vv0+ffq7+zo6ufp6en14tjf4+Dk5eTn5OXe1tHd3t/a293d3dvf3OPd1dTc3NrW4d/i3dfY3dzZ2dnX0tbS1dLJysrPztvR083N0+LY0tDS19PV1tjc6dDSysnVz8rNyczR0s/S1NXS2NzRz83NRNLW1trecN7Z29ja23DO1d3Y0c7T09BpyszPzNJqaM5ra2tsz21ybG1qampsbW9tbW1yb25ta21wbXBwcHRxbm5vcXNyhHSAenZ3dHN3dHF0cm90dXJzkG1wcG1xcG9ydnN1dHFvcnJxd3Z1dnR2d3Z0dHBvcHBxc3Bxc3J0c3Rxbm7f3dpxbnDabtvf5N7g3dvY2dTR0tDS1N121NzZ19/Oz8/R09XIz8zMzM7S0tXX2dDM08fGysrV0M7PztrV1srO0tnLy9mA0tva2dbQzs7Ty8a+vby6v8TJycO9tam0vLW2uLGvq6ainZmWnaKiuLSak5SVnKiakZaPk5OMjJCIiIV9fnx5fISE+oaGm/39h4aIjIyHjYeF+oD08/Ho5tvy3dyD4+Xg3uHk5Nnp1NR63djY5ePo3uXh4tTX0tDQ0tPPztDU3+CA4tjc193c4uPf3uHR1tjY3M/LydLRzt7c7fKP+Onl4t7e5+Pp5enw5u3p6+jk8Ozw7uvs6O7u5PHy+vHz9/Lq9PPt7fXx7/Ls/fvy8eXp5eji7eTs6feHgfr9+fac8P+A9fv5/PL1gvH7+/eB//v7/ICBg4CHhYGEhouOhoqMiYeAh4eNi4eBh4mHiYWMiomKjY6PjpKQk5GNjpGWkpKTkI6OlpWVj5OOk5CRkJGMi5KPkJCOjo2SjJGMjoqNh4aWkI+UlJCMj5SPkp2VmZeblJWSlpOTkI6PkJKRk5aklZacnZuXo6Kfl5aYm5iYnaCfoJ2fnpuanKGfoKGgoKCfm6g4op6jpqmqr6mtqaWjoqaqqaenp6iooaakpaerrK+vsbW3s7ext7HZtrO4urq/xMvIzNzBwsjFxsSAqKimqaarqq6usLS7srOztbe3vbu2vr2+v73Aw8PEwcXIyc7TxcnFyczKzcvQz8vNydLPz9HT0tDQ09LO1dDP0NPR0GrQ1GvV09jX1tvb2dvd3Nzc4Nrg4HDcdHhvct3db3Bv3nbf397h4eLh4eHg7Obg4+Pq5+fo5uXk5+Pj5eSA5uLi4OHg4+Pb9HXh3uLl6OLi5d/l59/d5efn7enp6+jk5eh2deRy5ebj437s4+Pj5uPj5+B04uPi5d/f3uBwdODg39rd293e3N3h3t5x3dfX2dzf39/c39rf29rY09hvb9dtb9tvcXDh4ODe29jY2ddvb2xsb9vc3G9x1tx03t+A3d3b39rWbW1sa2tuatLT1dfT2NTa29PZ2dfZ1tbU0+DRytHQzs/RzM7N0c7Oyc/Q0s/Ozc3NyszM1M3Gx8zLycbLys3KxcTKzMrKy8rHzMvPzsjJycrJ0sjLx8fO3c/Jx8nLyMjJzNDfzNDLzNXS0NHO09TSzdDQ0dLY29bV1tZD3NzY1tVs29ve3ODjdtbY3tzY2t7g3nDd3+Ph53Ny4nN0cnLacnhzc3FxcXNyc3FycXZ0c3RzdHdzdXRzeHZ0c3V1doR0gHN8dnd2dXp3dXZ0cnd4dXegdHd3dHZ0cXN1c3Z2c3J3dHZ5eHV0cnNzdHN0c3JxcHFyb3BycHJyd3Rycd7a1W9ub9ht1tfY1dvX2dLU1dDV1NbS1YLM087L0sPHxcjJycDLyMrLys3Ly83NyMbNxcLExMvFxMXDzMXHvMLHzsDAgMfCy8nMzcTDwsjBvrm4t6+ur7GysKumn6qvq6ipop+cl5WSj46TmJqzuo+Jio2TnJOMlIuNi4OAhoB/fXl7eXZ5fnzwgX+W9PSAf32AfXp9e3rtduTn5Nvd1eXRzHvMzcjKzNHXzdvJynHNxsPLyMnGzMvOxsbExcPCxMG9vsHHgMPGvsG+xsfNzsnKzL3DxsbNxsbFz8/KzcjLz3/Z0tPU0NDWztLNz9LI1NTU0MnRzczMysvM19PL0M7Uzc7T0czT087P2NLNzcza4NjZ0NXS1tLb0tbS24Rx293e3IbU4G/T2Nne1tl02eDf33Tn5eXndXZ2cnd2cnJzd3lydnt3DnZ4dnp4dG92eXl6d3x6hHiAe3l8fYKBgYGAg358fn53eIB9fHh7eH16enx+eHiCfX5+fHt6f3yDgIJ/gHt5gnx8gIB+fH+Bfn+FfIB9gHx/fYGBhIGBgoOEhISIl4eFioiFgoeEhYGDhoeEgoSFhIaEiImHhYmLiYuNjo+Oi4mVkIyPkZSRk46Rj42OjpGVlZUnlJSVlpKTk5eWl5aVkZSXl5ecmJ+bw6CdoaCfoKGmoqfNoaOrpqmmgJSWlpiXnJqbmpucpJuenaKlpKmooqikpaalqK2urKywsbG4v7C2tLm8ur26vbm4ube/u7y9v8HAv8TGw8fGxcXIxsllxspmysTJyMbKysjJysvMy9HM09Ns1XF0bG3S0GpqatJy1dbU19jb2dva2uTf19nV3Nnb29vY2dzY29vZgN3X19jX1dvb0/1z2tja3+He3+Pe5eXb2dvb1tzV19vd3N/ic3Hcb9/f3dx35N3d3+Hd4ePccd/e293a3N3fcHPe3d/b3dzb2dba3t7gc+Lc2dfX19XV1NjX3dzY2M7VbG7RamrRaGlnzs3Q0M/OztDOamtnaGrP0NFqbc3Qbc7MgMTHydDR0Gtra2lobGjPzs/Ox8fGzc7Iz87Ky8fIys3ZzcfQ0M/S0c/OyszHxL7DxcbFyMzN0M3Rz9jPy8vS0c3K0M7SzsnGys3KzM/Py9DP1NHMz9DU1OHa3dvc4/Xp4uDi5uHh4Obp+ePp4+Tu6OTj3uPk5eLm7O/y+f3z8/HwbvX39vb6gP/8//r9/oTt9Pn59fX8/fuA+PX59/2AgPyBgoGB+IGHg4SDhISEhYaCg4OIhoWEg4OIhoiIiY6MiIeHh4iIiYqLi5aOj4yLkY2JjYuHjpCMjciGiYmFiomIjI+NkI+MiYyJio6Ni4yKhIuAjY2Mi4mJiYaFg4OEg4eFgYD/+/mBgYP/gf38/ff6+fnz8u/r8O3v6+6Z6fPv7Pbl5+Pk5OTa4+Hk5ubn4uTj493a4trb3+Pu5uXh2+XX1crN0dzKzNfS2tfW0cXBv8O8u7S1s6uus7e6ubazrbS6sa+yq6ilnpyYmJmfp6i85KCAmZiYnKabkpmRlJKKiY6IiIaChYWBgIWE9YSDnfz/h4eIjIqHioiE+37v8PHo7OL34dmh3+HX3t3h5Nzp19mC4djU39rYz9bR08nQzc/Ly8vFwL7Ey8rMwsG+xMHI0tDT29HW2Njcz8rGzczGzcnS1IPb0tLU0tnf2tnS0tTG0dCA0tDK0s7QzsvMy9HQx87M0czLz9DHzMzLzNLPycvF1NfQ0MjLys/L08zWzdeSbc/Rzct4yNRrztTT1MrIa8TJxsVmzsrMzmhrbGpwbmpsbnFzbG5xbm1ubHNybmpvcnBxbXJwbm5ycXFvcm90dHBxcnVycXN0cHF6d3dydXF1cnMqdHV0c358fX56d3Z4c3Zyc29ybm16dHF2dXFsb3Rwc3t2eHV6dXV0eXl6hHdgeHZ2eIR0c3l5eHN8endzc3R4dnZ9gH+Cf4OCgX+Bg4OChYeIhoWCjoeEhoaGhId/hIKBgX5/goKAgYOGioeLiouKi4mKiYuOjYuQiZCLrpCPlpaXmZqbmpzVkZGYlJaSw34Ef35+f5F+An9+hH8Hfn5/f39+f6V+AX+YfgR/f35/hH4Bf4l+AX+IfoJ/jX4Bf5F+CX9/fn9/fn9/f4l+hX8Ifn5+f39+fn+Ifod/+H4Bf4Z+AX+JfgF/hX4Df39+hH8Bft9/CH5+fn9/f35/kH4Bf+5+Bn1+fn59fYl+An1+iX0Bfot9AX6xfQF+u32CfoR9BH59fX6GfQF+hH0BfoR9/37JfgICBACAx8fByMbHy8rPycrPz87O1eXX0dXV1dfS1dfh29/j2d/g5OXh6+329vby9e/s6en19e/z8vr0+Pn09/z694D2gPf7gv//gPv8/4GA+fz1+/b9/f3/gIGHgf6BhIiDg4SB//76/If/+4H+gP76gYCAhID++/z/gPn5/4OAg/j79vSA8vXz9Pn/+Pj7+oGB9Pbx9/L0+/eA//6Agf32/PuA+/r1+vuAgf399/v06fPw9fr+//v9//z9gYH+gIOCgIOCgf39/IOC/oCA+/+AgIOCgoSD/4D+/fuC/fz9+/z6/P77gYH/+/v//oD+gP+A/fyCgYOFhID8//39/oOBgYSChYOAgP6AgYCDgoL+gYGC/YGEg4WBgID8/YD38vbw7+3w7O7t8+zs5+vo6+vr7Onp6u2AgOvr7O3j4+bi4+LY2djZ3dvh4eLe7uDe49/g3eLg1tXT0M/Q1+HW3NnX09DOzNHRzc3S0M/Rz9XM09jQ1nPS09LV1tvf2NjV2Nja2tFv0daA09fW3dl04HBvcW9ydOXg4OPd2m7V1tNy1GtpaWlobGxwcnRycHJzcHBua29ucW5xcnNyc3l3cHBucHFycHJzdXVzd3V2dXp1dHpzdHN0c3FzdHJ1eXJub21wbm1xcHB0c3N2fHd3d3h2dnN2dXN2dHZ3c3R1dHJzdXFxdXV0cXOAc3NycW9xbm1vbnHi4+fkceDe4Ndy3Nrf2uHg2eDd5eDa1dXU09Xc1djX3t7V2N3b4ODT1dvT0svS0NXJ1dbW09HSytbV09Ta2+Pe4Orr8+ba0tXFv87F0dDay8fCu7a8ura4ur6zsrGkoZ+doKWmpJmWl5Seo5yXmpKLlZWPj46AiIWEfXqBfn+Hg4iHgoiDhoaGjIiKhIKGh4OGm/T17Hvb4Nzf5+vq7eXm4+N739rT29ni3+rr8err4YbT0NHV0dLXzcvb1svU4NfY4uLo4uXh5OHf5eXn08zd3+Xo7vT49/r9+u/s89/Y2+vz8vvo7Ojm7PPxgfzu6ujh7evq7feA8ff69vr57ebp6Oru9fHq94D47+zz6+vw5/Xq8+338vr6/vz9gPWAgoSAh4eHhIaD//yEhIyFgoKCh4eGh4eFiI2Ei4mKmIaKh4eLi4iNio+LlJCRkY2PkY2OnpGVlJaSlZSXm5aam5WYuLKalJSXlpWRl5uSkY2Nj5KRkI6Qjo+AkpCNkZaMk5SSi5KSlZSUkpeUlZmdnJyclZSPkJKJi4uSlJuXlpWcnJ2joKOjqpyUmJ6em6WhoqOenaOjqqGjoaSmpamloqOhoqCiqKqura+srKemqauvrq2qqKmqpKW4p7G0t7e3uLq3u7q5u7y80dq6vMPCxszQzs7Ox8X2w8uAq6uprayvsrC3sbC3t7S1uMbAvL7Bw8O+wcLMxsvMwsbHycnFy8jOzs7K0M7My8rT0s/U1Nzc2dfR1dbW1G3UbdXWbdfYbNXW12xr1tza4d7l4uHicXJ5cd5wcnVxcXRy4OTi5Hnm4XXlc+TfdHJzd3Pl5efqdebj53Z0d+Pm5eSA5url5ejs5ejo6Hh34ubi6+fo7eV06Ol1duzo7vB67+/p6+t2d+nq5ezq4+3p6uvo5+Pk6Oflc3PjcnR0cnVzc+Lj4XRz4XJy4eFwcHBvb3Bw3nDh3991397g3d3d3N3acHHi4ePm43LgceFv3t1vbW9wcHDf4+Pi4HFwb3BvcW+AbdxucG9yb2/XbW5u02tta25sbW3c33Ha2N3Y2Nja1NXU2dXW0tbT0dHT08/R0dFwc8/Rz8/JysvIy83Jz87MzszRz8/L38vJy8jLyc/Qy8/Rz8/O0djL0M/R0dDRz9LQzMvNzMrMzNPO1dnR13XW2NTX1tbZ2NfV2dnc29Zz2d+A2tza3tly225vcXFzc+Hc2+Le4HLk5+F85XJwcXNzdnN0c3Rwb3J0c3Rzc3ZzdXFzdHV0dn59dnh3eHl6d3l5enl2eHZ1dXh2dXt2d3d4eHh5enh6fXh2eXl8eXd3dXR2dnZ4fnh4eHl4eHV3dHJ0cnV3dXV3dnNzdHBxdHZ1cnOAcXJycXBxb29wb3Hd2NbZb9vX1dFw2Nnb1dnTztPR2dPV1tHPzc/Rz9TT2drP0dXT2NnO0tbR0dHQztLE0c7MycbEv8vNx8nLzNbO0eLb4NDGwMS5tcC3wLq8tbKvrKmtqqenqa6ioaCUk5KSlpudnJONjYqSl5GOkIyIkpGLhoaAgH5+d3J5dXZ8en19eH53fH5/gn5/fXd4enh5l+Hj2nPU19HS1tTT1MzO0t5z2NLMzsrOxMvMzcvQzW/KyMvNyMfKwsDMysDAxr6+xsXMxszHy8zKz9DTw77LzdPQ09fa0tXo2dHU4NDMzNXZ1d3Qz8/IzM7NctnKztDM08/P0NiAz9bZ1NfY0dDV1tbV3NbM3HDc1dPd19fb1N3V3Nbe3OHe4d7hc9tydXNucnRzcXNx4N90dXt1c3Nzd3Z1dnZ0d3tzenp7jXl9enl7eHZ4d3l3fnt7fXp9fXt9hnt/f4GBhYCChH1/hH1/n42CgICBgYB8gYV9fXt8foCAfn1+fX6AgYCBg4Z/g4KAfoGBg4SBfoN+fYGDhIWHgoKBhImChoWJiY2Kh4SHh4KGg4OEjIB9g4WHhYqHiYqFh4uMkIuNio6OjJGPjJGPkI6Qk5KVkpKRkpKSlZWZmJqWlZWWkpKklJuZmpmXm5ybnp2eoKOit76fnqGeoKWnqaiqpabUp6+AlZeWmJicnpyjnZugoJ2fo7Kopaioqaikp6ewrbGxqa6wsrSvtbW9vr68wL26ubnCwLvAwcjGyMjCxsvIxWfGZ8rOac7PZ8nJy2Vjw8nGy8jOz8/Tamx2bNVsb3Bsa2xq1NbT13Pc2G/abtzXb25vc3Dh39/hcdza3nJxc9nc2NeA2d3a2tve2Nja23Jy2tva4uHi6uNz5uRycd/W29pw3uHc399xct/h3OLh2ePd4OLk5OHk5uXkcnLfb3BxcnV0c+Pf3XNz33Bw29xubnFwb29u1mvT09Vz293g393g3d7Yb27Y09HSzmfMZ9Bozs1oZmhqamnQ1tTU1G1ramtqbWlZZs9pamptamnOaGlrzmhraWpnZ2fNzmnMyNDNzc3QzM3M083OztTPz9DOzsjIzc51ddHU1tbQ0tTS1NXP0c7N0M/V19fT6NTQ09DS0NnZ09fX09TV2+LV3NyE2oDb4uLd3eDf3t/f5+Hn7ujwgurr4+Tn4+jn6Ojw8/n89YP1/Pb5+v/6hP6BgIGAgYH++Pj/+/2B/v75h/2BgIKEhIeEhoaIhYKGiYeKiYeKiYyHiYiKiouTkouOjY+RlJGUlJSTjo+Mi4qOi4qSiouLjIqJi4yMkJOOjI2KjYqHi4CJio6Pj5GYkI+OjouLiYyIhYaEiI2LjZCRjo2Nh4aGiYaChIOEhISCg4GCg4GD//v6+oH9+fnxgfj29/L27+jv7vj19/j08uzq6+Pp6/Dz5ePl4+Xn297l4ePs7evw4efj3dTQz8fX1tTX19fi2Nnt5u3ZzMPGvbjEu8W/wrm5t4C0tLe4tLS2ua2rq56cm5eYn6CfmpeXlp6joJuelo2TlJGQjISEhoOBh4SDioaEhICCe4KEho2JiYWChIaDgqHx9O6A6e3o5ero5uTf3+DrgOPg2uHe49ng3t/W18+Ix8fK0crLz8TEzcm+wMa5uMLByMPNytHS09nZ3s3I1dfY2IDZ2dzZ2OTd1NTd0M3S4eLe49DPy8bL09J43tDOzcfOxcHH0sjT19HQ0sjFys/S0NzSydFr0MnI0MrJz8bRxM7Hz83W0NPQ0WnLam5wam5vbGhpZ8jGZ2lva2hqam9vb3BxbnF2bXJxcHxrcG9wdnZ1eXd4dHpzdHNvcnNvcHxucYBydHN0cHN4cXR6dXebj3t2dnV1dnJ2e3V2dnd5eXh1dHJwcXJyc3Z7c3l4dG5yc3V2d3Z7d3d5eXp6eHN0cnN3cHNzeXl/fHp3eXh2end5eoF6dHl9f3uEgYOEgIKEhYiDgoKGh4aKhoWHhoWFh4eFh4OCgYGAgoSChoWEgoOIjiOJi5qIjIqNjIuLi4eJi4uPkpSruZiZnpydnJ2XlZWPkLKRmL1+Dn9+f35+f35+f35+fn9/iX6EfwF+h3+Efgh/fn5/fn9+foV/hH4Hf35+fn9/f45+gn+IfgV/fn5/f4R+AX+FfoJ/kX4Df39+h38Kfn5+f39+f39+fod/Bn5/fn5+f4l+gn+Ffgd/fn9+f35+hn+Ffoh/AX6GfwV+f39/fod/A35+f5h+gn+8fgF/j34Bf4d+An9+hn+GfgZ/fn5+f37yf4R+AX+EfgF//36IfgR9fX1+jH0Bfo19AX66fQF+mn0BfpN9An59in6Cff9+z34CAgQAgMzXysnKytDTzc3P0djU1dbb2N/T19bW2djf3+Hj4eHm6Ofo5+jo8v3z9Pfw/O7z+P77+Pf5//iGgP+AgIH9/v77/oD+gYODgoGBgIKAgoP/h/6CgYOAgoKDhoaJiIWHhoiFgYCC/P+AgYGDg4SCgYGBgv7+goH8+YCFiP/7/4D+Nv38//j7/P72/vyB+/r49fv19fr6gYCAg4SCgoOEgoCC/YCAgYCA//z4/Pr4kvn9gYKBgID4hYSCVIGAg4KJgPmAgIWGgoKBhIOEhYaDhIOEgID/goX/goL9/YGBg4KBgoKB/4H/gYGEgoOGhYSkhIOHh4GAgv2Bg4SKh4eGiIWEg4WFg4aCg4iFhIKGgoSFgISDg/74/Pn5+/T08/X48e/r8+/p7PDx9vfu6PH06+/v8e/p6+Xr5evj3ODq7OPe4eLi4ebh5OTj5uDe3Xjb0NDN0tHQ1drWitbXzM3Lz8zQ2dLX2NTZ0dPR0dHT0tHT2NjW2Nnb3NvZ2NrZ3NXV0tXZ3XFub27YcW1ucHN1cXNwCXFucGzQ03BtcIRvJHFxdnNzcnJyc3RxdHR1dXN0c3Jyc3JzdHN1dHV2dXh1d3t2dYR5gHh6d3t5d3d1dXRzc3ZzdnZ1c3RzdHh3d3d4eHZ1dnZ2en97enp5dHl3eHVzdnV1dnZ5dnZ1dX51dHZ1dXNydHFvb21tb3t4dXDdcHJy3N3a3trg5OLh4t/k4tTY1drf3trU1dvd09/n3dvX09nYzM3W2NTZ7dvg2dfb1dHZ4eDdgN327eTd7ezv9urmjtXS193b083LyMnDw8K8t7W2rbKustyopaKhn6WgnaGcm56ko5+gl5Sr15uVl5qIiH56fYaJh5CIjIWJhIqIh4iGjouNiYyMiY/+8PPp1NfS3N/h5+Xk4d3O2O/i1dfW2OTy8fjp5d/f29nWzdHS0s7WxsLJgMvY4OTb4Ofe5trf4ODZ3tvd4uDw6OuG7+3y9u/19O2G+ebm4/j68e3t9fP27fqJjPzxlvf8gYzu+e31+O328ubn9+fx9/H27e7/7ePs8vDr6+34+PL9/fyA+/WA/YOEhYGCgYSGp/+Agv3/h4aEhoaJho2NiIuSjo+NjImOjouSgJSCioyPioqNmZGQkZaXmJeYnZ2dmJSUl5aWmpuWnZuSlpaSjpiWlp2WlpiYko+Qj5ORmZeVlJSSlZOQkJCRi4+RkJGOnp2WlpSXl5iZmZmbnZmYlpuSkpWTlJianZqao6OkpqOjqKShpqKjn5qcn6CdnKGqqaqvpqSop6apqaelOaippqO/oaelqLGxsKqtr6+0tKmrrLGqrLC1t7G4uru+vb3DvL3Gu7y/wMLAxcnMzs3NzsvHyMPIykSxvLKxs7K3urSztLi9ury8wcLJwMbFwsXCxcTFxsPCxsrLysvLyM/gz9DV0uDR09XZ1tXV1t3Yd2zXa2tt2Nnb2N1v24RvWm5vb3Fxc3Thdt5zcnRxc3JydHR2dHFzc3V0cnR26et2dnV1dnd2dXd3d+npeHfp5Xd6fO3p6njt7e3u5+rr7Ojs6nnt6+rp8Ozr7ep2dHR2d3Z5eXp5d3jsd4R4I+/t6e7t6pzq6nV0dHN05nt2d3Z2dXN2dX115nVzdXZyc3R3hHU0cXJxc3Fx4nR14XJy399xcHBvb29xcuR15HNycXBwcnNzlHV0d3dycnTjc3NydnFwcXJwcIRvgHBub3RxcG5wbGxtbm1vb3Dd2tzZ3N7c3Nvb39fV09za19fa2t7d2dTW2c/T0NXTzdHO0s7U0c7R2drSztHQzs7TztPT0tbQ0NB319LT0tbTz9DU1I/Z3NTW09XQ0NXP0tHQ1tDU1dfY2tnX1dzc19ba3eHj4uHi4eTd397h4OFzKW9vb91yb25xdXVxc3FzcnZ05Ol2c3VzdHV2dHBycXJ1dnV2d3R3d3d4hncleHp7ent4eXl3end5fnh3e3p6eXh5eHt5eHl4enl3eXt5ent7eoR7gHh3dnd5eXl7enp6fXl5eHl2fHl9enp8fHp4dXZzcnRzenJzdXRzcXFzcXJ0c3NzfHlzbtltbm/Z2trd2Nvd2dXV1trb0dLP1NfV09DR1tvP19zU09LT3NrQz9LSy87pz9PNys/Ixc3Szs3K2t3X1ebj5ObW0H/DwcbOysG6t7W3gLO2t7Kuq6mhpJ2gxZeYlpeYnZiWmZKQkZaUkZGKip3NlIyPkoGCdnR4eXp4fHd9d3l2f4GCgn2Afn16fn95fujh6ebY19HV1NHU1NPS1c3c9ObW1M/Ly9PQzsrLzMjPz8zFycfHw8i5t7u4wMPFvMPMyNPJzM3LxcrLy9DS39bYgIDb2dfa19nX1X3l0NLN4t3PysvS0NPP13aB28+D1tx1dtTaztXXztre1dXi0tbb0tbOz97Vz9bf3NbW1+Dg2N/e2m/Y1G/bcnJzcXBwdHeZ5HR35OR2dXN0dHV1eHh0d3x6e3h3dHZ5d35/dX19fHh3eoJ8e3x8fn17en+BgH9/FoCEgX+AgH2FgXp9f3x7hIJ/hH9/gIGEf4CCgIaFgoCAf4OEg4SEhoKEg4GDgI+LhIN/gX+Bg4ODhYmFhoaMhYeKiIeJiImGhYiJh4eFhYiHhIyJiYmHiIyPjIiLkI6Mk42NkY+Mjo6Oj5GTk5GwkJSRkZaVlJKWmJmdnZaanKCamp2gnpeampqenqGnoaCmnqCkoqSfoaKlpwmmqK6sq62orK9nm6ScmZuboaOfn5+ipaOko6mrsqiurKapp6qpra+srrS3urq4uLe+zL/BxL/Hu77BxcHBwMLHxW5jyGVlZs7Oz83QZ81oaGhpaGhoaWdpaM1uzmlrbWtramtta21samxtb25rbW/b34VxgHJxcHFycuLkdnTh3XF0duDb3nLh4ePn4ePj5d3i33Ph4dzf5eDj6OV1c3JzcnBycnNxcHLfcXNzdXTm4t3i4uGa4eN0dHRzc+OBdHRzc3JzdnV+c+BycHN0cHBvcnBxcnNwcW9vbm3acHLccG/Z2m5vcG5tbW1s1G3TampqaWlrIWtqkmpobGxoaGvSa2xrb21rbG1qampra2xua2xybmxqbYZpgGhpzMrNzNHV09PT0tfS0c3V1M/Q1NXV1M7L0tTO0dHW1tDU0NXR2dXN0dnZ0s7Q0tLS2NTW2dfc2NraieLd29ne3djZ3NmJ2NrS2Nfc2dzi3OLh4+rl6Ojr7e7s6+nv7ujp8PP2+fn5/Pz/9vj4+vv/g4CAgP6DgIGDhYaBg4KFgIOEgfz/hIGGhYmKioeFiYiHiIiHiIuJjY6PjYuMi4uMjI6RkJGTkZOVk5iVlp2Vk5aTkY+Ojo2Rjo2NiYqKio2Rj5KSj42OjY2PjYyMjpCRkZOQjo6RjI2MjYiOiouIiY2Oj5CNj4yKiYeMg4OEg4OBgoSCgYKAgIGNi4OA/oKEgIP9+fT38vb59vLx8Pb57fHw9fX17eru8fXn6u/k4uDg6urf4Ojo3uH72dvSz9bOzNbc2trW6ejf2u3q7O7f1nTIxsvSz8fCwMDBvcPEwbq0ta2xrK7Eo6Ofn6GmoaCim5iVnqCcnpORptGclJeYiIyEgYOFhYOKgYZ9gH2HiIeJgIaNiouCh4eDivzx+O/c3NTa2tfb2tvb3NHg9+ze3NrT1Nva1dHR0tzT09DKyc3PzdHDwcTDyszNxMjPydTGyMrLxczL0djc6t/git7Z29rT19TQeuHM0s7g3dLPzNnb39bcd4vazHPO2HB60NvO1dPGzMzEw9fI197Y2M/N3MrBgMfMycPFxdDPzNbW0mvQy2rRbW1saWlpa26Yz2hrycpqaGZpaWxrcHJvc3p5fHZ0cXFva3F0ZnBzdHFxc3tzc3N1d3Z1dXp8eHNwb3NzcnN2c3t6dHd4dHF4dHR5cHJydHN0dHR5dnp4dXFzcHNycXR2eHR3d3V2dIJ/eHh2dnZ4bXh1dnd4dXZ1eXJ0dXR1eHh6eXZ8fn9/fn2BfnqBfXx7d3uBhIKBhImGhYqCgYeEhIeFhYWGiIiFoIGEgoCHhoWBhIaGiIeAhIyXjo+RlJKLj42MjYuKkIqKlY6RlZabmJubm5iUlJiVk5aSlpi3fgZ/f35/f3+FfgJ/fot/A35/fpN/gn6Lfw1+fn9/fn5/f39+fn5/i34Bf4l+jH8BfoV/hn4Df35+hX8Bfot/AX6Sfwh+f39+f39+foh/A35/fpB/AX6ef7l+AX+KfgF/qn6EfwF+jX+Cfvh/BH5/f3++fgF/yX7BfQF+iH0Bfo59CX5+fX1+fX1+fqF9BX59fX59iX4FfX5+fX3/fs9+AgIEAIDIzs/OxsfNzdXQxsvR0dDV2tbe4drf3+Di4ebq4+fn7Orp6ejr6vP49vL2+vv5+YCBg4GDg4SGhYOAgYH/gIGFhIGBgoGCg4WGhIWFhIKChYWEhoaLh4WJiYiJjYuHiYmLhoWGh4iFhYOFhYaEg4CAgoH+gICB/f6Bg4KChIb7/W75gIODg4WNgoCDg4WBhYKC//38gIWDhIWHiImKhoWEgoWEhYeGhoaBgf+DhIWChISBg4WCgYSJhoaFg4SEg4mIh4eDiImGh4eDh4eEjIeFhISCgoSAhIeFgoOFgoSDhIKBgP2BgYCAg4aFhoiJh4SEgImEgIOCiIaIh4uLiYeKh4iHiYeGhoaFhIKEhIaGiYaIhYaEgv2AgPv6+ff99vn49fj29Ozl8eno6e/t8fz38+7s6+nx6e3u5+bl3+Pn6uvp7uzq4tfe2dvi29rZ4tzRz8/T1NDT2Nzi2uHS29fP0dLa2tvf3dzd2NzV3dfW09/cgN3g3Nba3trW2dtw33Fw3dvecHFzct5xdW9ucXR0c3FxcnBydHBvcG5wcXJzcXV3d3R0c3Z1eXl6eHh7d3NydHFwcnFxc3R2d3l5eXp4d3d0eHVzc3V3eHZ5end2gXp3dXh5dnl2c3R0dXR1dnZ5enl4dHN2d3p8e3l6e3h7fXx5gHZ5c3NzdXZ3eHd5eHd4eHl6end4dnJwbtRucG5xdHRxcXF03+Dg3t3h3eXh39/f2drW3ePn4drY3dvb1vbs3+Tk7dve4unn5Orb3dvq1OXm5N3m693l6e7u8/T5/ID27ePm5uDd3tHR083LxsbBs6esnqixrKqmpaqfoqOgpaCtgJucnaOemZSXlqq7o6CbmJGTj4iEhoiJhISGhIODhbmQkNCOi5GSlZOLhYb0++vm4N3Z4ejk5tzp59jw8u7c5uP55/CA7ufg5fLd09je3NvZ7OHcz9vZzt7k5Nvj6ufk5tfV1NTe2uLz7/rg4On49//3+ID88Pfs8PL4+ID28vj3gPfy9+/5+oP894H4gfv4gPr38oH89fT+9vGI/fL3hvqI8e739P3n+Pv09f38gf2GhoKKhoKEgIWGiI2Hg4Odg4eOhoeCkpCLi4yPkI6XkZGQlZSVlqCZlJaWlJuSlJWTlJaYlZmeoZ6cnpqXmJmZoKCbnJWZkJiWnZ2bnZ2dmpiUgJSUlZWRlpGQlZiTlJaUlpaUlZGOkpOal5eamZqZmpiZl5uXnJ2hnpugl5mZmJqemJ2anqKkoqOdnqCdsKGfnKKbm52ZoKGkqrOsqaulp6WnqKepqKunqKiuqqusq6211rGtq7KzsrKxqrGysrO0v8HCycG7u77DxsTJ0cTCwsW/DsXGx8TIxsbFyMjNyM3IgKmytriys7m5wLuzuLy8uru/vsXIw8fFxcXCxsnEx8fLzc3MztDP1tvV1NXZ2dnYbm5vbW1ubm9vbmxtbdltb3Jyb3Byb3BwcHFwcnRzcnJ0dHN0c3l2dHd2dXR3dnN2dnl2dHZ3fHd4dnd3d3Z4dnZ6ee94eXnt8Hp6eXl7fOztgOl3d3Z2d312dXd4e3h8enrx7+13e3h6eXp6e317enl4enl6eXl5end58Xt6hXh5eHZ4end3eX57enh3eHl6f314d3N3d3V3eHZ3dnR6dXV0dXN0dHJ1d3VzdHRyc3Jyc3Fy4nR1dHN0dXJzdHV0cXN0dXt2c3d1eXV1cXJxcG9ygHFzcHFxcHF0dHNyc3Fyb3JwcHBxcnDdcXDc3d/c49ze3Nnc3t7c1eLb29rg2tbd2NfV19TU29bZ3djX1dDi1dbW1dfV1tPL08/Q19HS0t7c1dPT2NbR0tXX39fc0dvY0tPS2djY2tnZ29fa19/a2tff3d3g3t3i5uTf4N9x4HFygOHi5XJzc3HfcnVwb3Fyc3NzdHVzdHV2dnV0dHR2d3Z3dHV0dXZ4dXl3dXV3enl4d3p4eHp6en19fXx7e3t8ent8e4B8e3p8fX18fX17eoJ9enh7e3l8fHl6fHx7e3t6e3x9fnt6fXx8fHp3eHp2fH1+fXyBfHx7end2dnN1dXR0gHV2dXRzc3N0dHTldXRycXBwbm5wctnb3Nvd39rg3drc3dnY1Nba3NbU1djZ1s7v5dbf3+3T1tja2tXbzs/O38XV1NLM1dnN09ff3ePk6et12MzEyMnGxsjAwcG9vbi5taylq52gpJ6bmpmhl5qclp2brpSSk5mTko2OjJuqmJaUgI+HiIJ7e3p6enh4fXx5dnWwfH+6gYKEhYaCe3p75PDl4NrWztDUz9DI09XM4+Xn09TP4MnObszIxs/az8XMzsnHx+fMz7/GwbjEx8XBxtDQ1NTKzsfGzszQ4eLu1dXa4dzg2Ndx4dXf1tfV19Rt0szT1tnV2NLa1W/Y1W/Ybt3ZgHDe2tRx3tvc5+DYe9fO0nHUdtbU4d7t0uTi3Nrf33Hdc3NweHRyc29xc3Z8dHN0kHJ2eXNzcH58eXp7fn18hXx6eHp5fHyBfnt9fn2De359fn9+fn2AgYSCgoWCgIGDgIeFgYF8gXuEgoaFgIODhIOEhIOEhYWBhYGAg4SBgYODVYWGhoeEgoOCh4SDhISFhISEh4WJh4mHiYWEiYWGiImLjoiJhYaJiImIhYeJiJqMjYyNio2Mi5COjZKVkI2QjpCPkJKPkJCUkJKTl5OVlpSWncKcmJWEmyegnaGhoJ2aoJ2cop2cnqKkop+kq6KhpKWgpKamp6ypqausrK+rrqxEmZ+hoJmZnJylopqipqimqKyqsbOurqqpqKWpr6uusbS1tra3vLzEycbDwsPDwMFjYmNiY2RlaGdmZWZmymdoa2tpaWuEaoBramtramlpbG1sbm50cG1wb21rb25qbW9xbm1wc3lydHN0dHRzdHFydHTmdHNz4eFyc3JydXji499ydHNzdXtyb3JydXN2dnbn6OZydnR0c3R0dHZ0dHV0eHd4dnZ2dXJz5Xd3iHd4eHR0dXFxc3d1dXNzdXV0end0dHF3d3JzchVtcHBudnNycXFvcXJvcnVzb3BwbW+EbiJv3HBubWprbWtsbm9tamtqa3BraGxpbWtsam1sbGtua21shG2Abm1samxrbGtua2xqa2xq0Gpq0tTV1NvR1NPP0dTU0MjWzczN1M/R2NXT0tHQz9rU2N7h4d7a7Nve3dzf3d3WzdTT09zX2dvn5d7b2+De1dXW1t7V3tLj4Nzf3uXj4+bn5uzp7uv58vLy+PPy8u3s8/r7+fv8gP2Agf78/oCBgoIO/4OHgYCDhYeGhYWGhIWEh4CGh4iLjIuNjI2Ki4qNjJOTkI6OkIyJiYyKiouMjJCSk5OVlZWYl5eXk5eRj4+RlJSTlpaSkJmSj42RkpCUko6Pj5COjY2OkZOUko+Oj46Pj42MjpCMlZSVlJGWjY6OjY2NjIeHhYSEhIWFhIGBgIGBgPqBgoGChIaEhIOC+fr5+ID39/D18u7x9/b29Pb3/PPu8fHy6+L/8eDq6/rl6Orv7+fs29vZ59Lk4uDb4ubb3uDm5Onq7vB44NTKzs/KyMm/v8K/wcDDwLmus6WrsKurqKWqnaCjnKWjup2cmqGcm5iWkqG5nJiTko2QjoaEgoB+eXd7e3p5es6GiLyJiYyMjYCLhoOD7/jr5uLe1trf29zW4eLV7e7v297b9djadtnUz9fn1czQ1M3JzPbT0cfNycDNz83Hy9LQ0M3Cx7/Bz9HV6+fv1NLU39rh1tNs2MzUztjb4Ntw09Dd3dzT1cnNymzOz2zRa9TQbNXRyGvLyMrb1taA2s3Scs9wx8TMy9a/0x/VztDZ2W3XcG9rcm1rbWlscHF2bWpphmhscmtsaXd0hG2Abm2ccXFwc3Jxcnd0cXR1dHx0dnZ3eHp7eHl7fHh1dnNydHZ2fnx5enV5c3l3fHx2d3Z4dnh0dHV3dnR5dHJ1d3R0d3R0dHV4dnR4eX95eHl5eHh5dnd2eHV4eH16d3x1dnd4e4B4e3d6fX9/gHx+fnyRfXx5fXp7fHt+foCEioREgYSAhISGiIeHhIeDhYaJhYWFhIaMtYeDgYaGh4eLho6RkI+MkpKRl4+Mi42QkJCVn5SUl5yYnp2ZmJmUkpOUlZqYnZuvfo1/AX61fwZ+f39/fn6Gf4N+j3+DfpZ/AX64fwF+s38Dfn9/434Hf35/f35+foR/AX7/fwR/f39+in+6fgF/zX6YfQF+rn0Bfoh9AX6KfQ1+fX1+fX59fX59fX1+hn0Hfn19fX59fox9An59nn4Bf/9+wn4CAgQAgNzV0s/NzczP0eT0ztTe2Nba2+Ti5OXl6Ojm7+ny7O/y7+zy7fL29/Hz8PH0//WA/oGCg4OIh4uVioeEhYaEgYKGhIOBgYCEh4eHg4iGhYaIi5WIiYqHiIuPkI+OjI6OjIuLiIaJhoeHhIWHhoaDhYWDgP6CgIOFhYaEhIGDiYL+GIODhYSGg4KDgoOBgISDgoWGhISEhoeGhoSHgImGh4SHh4uNjouLiIWHhYeFhYiKhoeHh4aHiYyFiouKi4iGnZqEiYiGh4iIh4eIhoiGh4aFgYOGh4WEiYaGhYSIhYSBgoH/goeHhIeKhYmIhoeGhYOFhIiFhoWIiIiMjJCSjYqKjIqIhoWJiIWGgoWGhIiKiIiFhYSEg4aEgvz8gPz3+/D58vHr7Pjv6O7r5+3t8+3x8e/t7Ojy8+fk49/m6erw7uvp+erp5eTh39nb3tvb1dna09fc2Nvo3N/f29/b3NjT1tna3dnjgN7f3d7a3NrY2uPb2+rj3N7bc3FwcXJ0dt1wcG1wb3Fzc3Z1dXVxc3NxcXZwdHR4dHN3cnRzgHN0c3R4dnR3dHZ5gXt8eXh4c3J0c3R/cnFydHZ1enx5eXt3d3h3eHh5fXx2enl5dnZ2d3l5d3qCe3x4fH19fHl6ent3eHp6eXh3d3p+fnx9fH+BgYKCfnt6enV7eHp8e314enp3e3d4dnl2dXd0c3PidXV0dXN4dnjsdnbq6+jmgOLm5d7e2t3f5+ro6Obp4+Ha4NnX3t3ke+jt6vDx7+3p5Oje3Obr8/T2gePt/Pn8g4CFgoKC9OLh4Ofg3dXRz9DEw7q4trSwoJyZmaSmp6Olm5mdmJWYlJ2ZoaChn6Gdpq6in6SnopuUkI6KiYKEgoSGhIiIjI+OkpKSjpudn5qPgI/09+rl5d/l5ufk39zu7eLy+urt5d7Z3uXv7unl6eTp6/Do5+Hd3dDVz9TZ09zd4er25+ji287MztjW2+fq7Ofo5e6I5PqD+fyAiPLu9/X3+fb89fn39Pn6goKBgvX0gID0+4D6/fz2g+6Miof7+4L8jYH4gIWA//v07+ft+/z+gPz9goGIg4WHg4GGgIGHkIaBgIWOjZKPjI+LiYaIhoyTho+MlpCOlpOWkZaXl5ealpeSk5eRl5ycl5yYmpuenaCcnJmYm5ugmZaXlJqWoJ6fpZmamZOYlZaZmJaam5udmpyWlpiZlpWZl56Xk5OfmJqXlJ2bmZeXlZ6rnqGho6KhZKajnZmioqGjoqakoKukoLmioZucnaKcnJmfo6SlqKqur7WwrLKuqbSrrqyprKypqqyzrbWxvLPVsLK1sbOrrbGxt7S7uru/v8fDw8PHxM3Iw8TJwMTBusHFx8nN0c/Mx8nLzs9Bvb27u7m6ur2+z9+5vsu/ur6/x8XIyMbIx8PKxMzHyc3Ny87N0dXY1NbW2Nrg1m/db3BwcHJwc3xzcG5xc3FucHSFcxJ0cnFxb3Rzc3N1doB0dXh1dnaEeYB3eHp4eHl4dnl5ent4eHt6enp8fXx573l4ent6fHp6eXuAe/F8e3x6e3h4eXp7enl8e3p8fXx8e3t7eXp7e3t8fXt7eXt6fn19fH18ent6e3p5e316e3p8fHt9f3Z6ent9fHqUl3d+e3h6ent6enp4eXh3dnZzdXd4dnV4d3d2dTV2dHNzdHTod3p5d3h6dHh2dHd1dXR3dnl3eXZ4d3R1c3V3c3R1eHZzcnF1dXR2c3R1cnJzcoRwgHFxdHNy4OHk4uTZ4N3e3N3p4t3h39bd3NzX29za2djS3N3V1NXR1NXW2dTS0uDU1NPT0tLP09bW1NPY2tXY3NTX4NbY29nc2NvY1dna3d7a4YHg4eDh3uDd3d7l3tzp49/j33R0cnFyc3Xjc3RydHN1dHN2dXZ3dHZ3dXV9c3d2Nnd2dXp3enl4eHh2d3d3eXl4d312d3h6fHp7e3t+i35+f39/fH+Afn+Afn5+gIB/gYKBfH+AgIR/gIF/fX+EfHx4fH1+fn19fX97fH5/fX19fn+Bf3t7eHx+fYGBfn1+fnt9eXp6eHl1dnZzdnV1dHd2dXd2d3fpd3Vzc3FzcHHab3Dc4N/e3+Pj397Z2Nfe39zc29/a29nd3Nzh4ON73NzW2djV1M/M08zJ0tXb2+J90drq5OR3c3h1gHZ12MrKx9DKxsC/vsK8vbq4trWxoJuXlpubnJqfl5ucnJmZlpiQlZOTkJOOlaGXk5eWkImDgX55eXZ4d3h6eHd3en19hIeIhY2KioZ9i+Hq39rZ0tTW09HLx9LSy9vf1dzSy8TAxMzKyMfPytLR1s7MyMrWxMjEx8W+xcbH097SgNfY0MfIy9DP1NjZ3t3b2OR9z+By1dhvft3Y3Nrc29re1dnY2t7dcHBtb9XTbm/S23Df3+bid9qBfnfb2m7RcWnIaXBw3t/X19LY5OPk4OBzcXdzdXZycXRwcHd/dXBydnt4fHl3d3Z1dHd4fYx6f3qAeXV7eHt5fn5/gYJ+gX1+gIB9gYKCf4F+gYKFhYiGg4OCgoGEgoOFgYKBiIiKmYSGiISJhISFhoSIiIeIhIWEg4WFhIKGhIuHhIOOiImGg4uIiYqNipCZi4iGiIeIjY2Jho2MiYeIi4mFkIqJoo2Mh4mJjouMipCSkY2PjZCTlpOQlJGMlo6RkpOWl5OUlpyYMZ2ao53EmpubmJ2an6Cfo52fnJqcm6Kfn6CloqqmoaOpo6iopKusq6utsLCuq6yvsbOAp6Win52dnKCitcSiqLasqqursa2urquvramzr7eytri2tru4wMTGw8TBwMHGvGLCYWJjZGZmaXVqaWdoaWhmZ2xqaWhpaGtsbGxpbWxtbG1vemxvcm9vcHJxcHBub3BwcHFwb3RzdHZzdHd2dnN1dnRx4nNxc3Ryc3Jzc3Z7deREc3N1dXd1dnd3eHZ0eHZ2eHl2dnN0dHFwcXJzdnh2d3Z3d3t5eXZ2dHJzdHZ1d3l8eHd0cnFxc3dydnd4eXd1l5Zzd3aEcxBycnNzdnV2c3JwcHJzcXF0hHKAdnR0c3Ny33FxcGtsbmpvbm5wbm5rbm1vbW5rbW1rbm5xdG9tbnFvbWtpbGxrbGhra2hrbGtsbG1sbWxubWvQ1dfV187X0NDO0NrRzM/LxM/R1tPY2tfW1dDb29PV3dzh5OTo5eHg7uDe2tnW19TY3d3c2d7h2+Hm4eLs4N/h3uKA4eji4+bm5+nj7Ibp7u/x8PXy8vP68e779PH39oKCgICBgoX/goKAgoCBg4GHh4iIhYaIhYWOgoeHiYeHi4eKi4yOjYyPj46Qjo+QmJGQj46OioqLjJCwj5CQkpWRlpiVlZmWlpiYl5aXmJeQlpeZl5aVlZaVk5eflpaQk5KSkY+Aj5GUkZSWlpSSkZGTlpWSkpCUl5aZm5aUlpWQlZGUlJKRioqKhoiDg4GFhISGg4CA/4aGhYiGiIWE/4CA/Pv17+/19fT29Pb2/fz8+Pb++Pj09/Hv8vDzgPDy7O7u6Ofi4OXc2uLh5ubledff7efnenN3c3Ny1snLydHKyL+9vsSAvcC8ur++vK+sqKeppqWjqZ6ioJ+amJidmaGfoqChmZ2unpqdmZGNh4OCfn15e3d5e3l9fH2AfoKEhYSRkZWTi4/w9ePg49nd497h29fl5djn6dve187Hyc/Z19XU3NTY2dnRz8jL28LEwsTFvsfFyNXf1NrWyr+/wMXFy9DV2deA1dHVc8TXcdbZcInV0NfU2N3c4Nzf2tTW0GtpaGvLzGxt0Nls1tLSxmzAd3Vy0dVw1nVry2lubNfUzMi/xNDOz87Ra2pxbXBwbG1wbGxze3BqaW5zdHl1c3VycG5ubHGEbnBwdXBudHJ0cHV2dXd6d3l1d3l1e39/e316eXh5dnmAdXR0dXh3fHp6fXl6d35+f5F6eHl0eHV3enp4fHx7fHl7eHl4eHR0eXqBfHl4gnt7eXZ+fXx7e3d/iHt9e3t6e35/e3h/fXt7fX+BgImDgpeBf3l8fIF+fnyDhISEhYWGh4mEgomIh5OJiomIiomEhIaKhoyHk4u9iIqLiIqGh4okiY6NkJCQlZWZlJOUl5OblpGQmJSZmZaZmJeYmZ6enZmanJ6ern4Cf37CfwF+jH8BfuJ/AX63f8x+AX+Rfod/AX7/f41/AX6IfwN+f3+bfgF/kX4Bf4V+hn/MfsJ9CH59fX59fX5+jn2Efgd9fX5+fX1+hH0Pfn1+fn59fX59fn59fn5+i33/fuN+AgIEAIDZ3NTW2OPd2tfV0dHU3Nja4ubo9+7v8IDq6O3o8O71/fr9+fz6+oH7/vX4/fz5g4OCg4SEh4OGiIeIh4mHhYSGhoeGhYWGhomHhoWJiYmLjo6MjY2NjI+MjY+SlZKQjoyPioyMiIuKioiJiouJjIeJhYaDhIWGiYaKioiIh4qGgxiIhoyGh4WGhoOEiIWKi4qIiYeDh4iJioiFiXCIh4mIioqMjpCPkIqOjI2Ji4mNjIuHh4WIio6Si46Njo6Ki46KiIiJhomGi4qKiYyOjouNjImMj4mGiYWGhYWEhYOE/oWEiIiNiI2KiZGLjImNiIqNiYeIioqIi4uJiY6Ni4iJioiJiomIiYmIhIaHhYxvh4aDhoiEhYODhPz3+fX89fnz8+vm7PHw9PP28fDp6+3v8e/v8ufs5eTl5OTr7O3w8+vy7/Pg6eXm4+Hi3NzZ2d3d4u3k29/g3OHi4Nve29zc4t/h6enm3trX2d7c3+Pm6O7o5uJzc3VxdHRyc3JwhHJNcXR0dHp5cW1xc3V3c3V1dnd3eHl5dnZxdHh5eXh2dnd2d3d6en98fXZ5eHh6eHd4fX6Cenp8eXt7eXp8enx+en5+e3p7eXd5dXp6gIeEght/g4KAf398fXp3e3x+h3t9fXt9fnt8e32AgIGEfIB+en1+fX+Afnx+fn18fH1+e3h2d3Z5dnl3dnV5eHh3eXl5kuzp6Ofo7OPmd3p2dnbuee556Xh36N/h3+Dj5+709fqA9/Hv7u3l4ejr7/D16uPl8fn8gf3+g4aIhPfu6eDV1tfQzM7WycrDvbOwsqaknZ2jsKumnp+al5iZlo+RkoCUl5qdq6GssJydoZiTmpKNiYaDg4H+hoeCiImHgYCDgpGkraSbko+Ig4ew6OXW5t/a5Obl9PTv8Obk5Ofu2uPo7Ofo6+fj6PXe2ezj3d7i29nT39fe1Nzh4e36/ubm19Tb3Nbi6PPw6u/u5uv5hfzq6/Ty9e/w7Oz5gPbs+fjzgID/+v3/9vSC7/T7/feWhPr6/oSDg//4hIGHg4KH+v2ChPr6gYD+gYGD/4uFlY6Ij4aKgIyPhIaIjoWJipOPkIuJjJCJiYuJjJGMm5qWmZ2ZlpeYnZabn6uZlpyYlJqqoKGfn5yXnpybnKKhoZubn5ygqZ+do6GfmqGhpaCfm56anoCbnJ+fnqSdp56ioq2enJ+cnJmcnpmYn6CgpJ2hnqSin6Cfpqmrq6mqqqqmoaeioqSjpKWmpKCgo56joKGho6akq6mop6iutLyyr62ytK+9rairs6+xsLSzsbSura6ztra3srS2trOwtrayvb3Dw8bDxcTAysHEx8jFw8HDysfIxwvJ0dHQ09HJ2NXS2UG3vbe6u8nEw8PCwb/Bx8HAxMXE1MfIymvJyc/I0MzQ1NLUzdLS1G7a3tnY3N3XcHBvcnJzdXFyc3FycXNzcnBzc4R1gHZ1dnV1dXh2dnd4d3Z3d3h4enh4eXt+e3l5eXt4e3t6fX5/fHx7fHt9en17fHt7fHt+en19e319gX58f3yAent5ent6e359gH9+fH18en1+f4F+fn1+fn9+fX59fn59f39+fnt+fX17e3l9fn19fXt9foOGfX1+f4B8fYB9e3p7gHl7eHx7eXl7fHx5enl4eX55eHp5eXh4d3h3eOh4d3h5e3d7eHd/eXh1eXV3e3Z2dnl5eHt6dnV3d3V0dnh3d3h3dXh6eHV3dnh2dHV0cnRyd3h1dnN1d+Pg5eTp4ubi4+De4uLg4eDi4N/a393e397g49rf29ra19TZ2Nja3Nbdc9zh0NjX19bX2dnb2Nrb3OHo4Nvf4N7g3dvW2dvd3uLf4OXo5+He4ODk4ODi5ejt6ujldXR1cXN0cnN1dHV3d3h2eHd3e3l2dHd6eXh1dnd5eXp4eXt6fXp7f3x6fHt7fnt5eXl6gH2BfH5+foB/fX6EhIqEgICBgICAgYKCgoGDgoGBg4KBg35/foGKgoKBgX6Bfn59gH+BgH6AgYOQgIKBf4GAf398fn9/gn19fn6Bfn+DgYiAfHl7e3l3d3h6eHV3eXd5dXd3dXR3dHRzc3Fyid/h4eLl6ePndnxzcnDecN1x3HJ06N/i4+Lk4uPj4N9x29XW1oDW0M/T1Njb49rY29/k43Pg4HR1dnHSzcnAtri4tbW5xL7Bvr2ysrGloZaTl6CcmZSZmZibm5eRlJSSkI6QnZSeqpGRl42Hj4eFgn9+ennvenp2eHh5d3l+f4qZmo+Gf357eX2l4uDR2tHM0c7M19nV3NbY2dvjycvLzcjIzc3K0IDZycHVzM3T0c7MxsrCyb7Fzs3b6e3Z2s3Kz9HM0tjh4Nni49nY4HXcz9Lf4efg49zV4XPe1ODd23To3eHd0tFw1Nzm5+GCdeTh4HRzcdzZcWtwbWxx1N1xdd/kdHLicXJx23VvfXdyd3F2bnh5c3Z3enl2eH16eXh1eH14eHp6fjiAeoaBfX1/fXx/foSAg4eRgoCDgX5/i4GCfn9/foOEg4KFhYaCg4SBhIqFhYuKiIWKi42Ih4aIhYSIgIeGi4aOhoeIkYaDiIiJiIqNh4aKjI2SjI6MkY6Ojo2OkY+OjYyPj42Lko6Nj42NjY6MiYiMio6Mj42OkY6TkZCOj5OVopWSk5aYl6CWk5WdmZmXmJmZnZmam6Ccm5yYmp6hn52jo52kn6CboKClp6Oupqmqqqinpqisqq6rrLGwCK+zsau2tLG4gKGln6CfpqWioaGfoKWsqayxsrLFtLW2YbGwta+3tLm+uby2vL/CZsnMwsTIxb9jZGNkZWVoZWdpaWpoa2ppZ2lpa2tqa2trbGtsbXBwb29xcG9wcXJxdHFycnN1cnFycnRxdHVzdnd4dnh4enp9eHl2dnNycnJ1cXV2dXd3e3h2gHh2e3Z4d3l6eHl8eX18fHl6eHV3eHh4dXZ0dnh6e3t9fH17eXl5eHh0eHd5dXd2enl4dXRxc3V5fHZ5ent7d3Z5dnV3dnR2c3h2dXR2d3ZzdXZ0dnp0c3Z0dXV1dnZ0dd5zcHJvcm1wbW17cHFwdG9xdG5sbW9ubXFxb3Byc3FvgHBxcG9wcG5wcW9rbm1vbWxsbWpra3BybW9sbG/X1tnW2dHY1tfS0NPV0tXU1dXV0NfX2t3c3eHc4t/h4uLj5+jn5ebg5eTx2ufo6Obm5+Tl4uTn6ezz6eHj5OPo6uvm6urq6+7q7PT09PDv8ff9+vr7+vn+/Pr5gICCgISFg4WFgIGDg4KCgIOBg4mIhYOGiYmJhoiIiImKiouNi4yHipGQkJGQj5KQjpCSkZiUlY6SkpKVkpGSl5egk5OUlJWWlZaampucmZybmJmdm5uclpmYm6Sdnp6dmJuZl5aXk5aUkZWXmaaVlpeVmpuYmZaYm5udmZeXlpiTlZydpZmWkpORgI2IhoaHhoSHiYaHgoOEhYWKiIiHhoSElv78+PT2/Pf+g4yDgoD/gv+B+4CB+u7v7uvv8fX39fR77efn5+ri3ePj4eLo3dnf5+3rduPecXNzb9HPz8nCwcC5t7rHwsjGwru4uKunn5ygqaShmp+fnaCemZKUmJqcn6Swp7S/mZedgI+GkYiDhIGAf3zxf4B9goB/e3d7eYSVopiTjIqEf4Gk5eXS4drS0s/T5Obe4tnX1tzhx83R1tLS2tbR1uDKw9fMy9LQy8jByMHHvMTOytzw7dbXysXIxsLHytfX0dfWzc/XctfHydbS2NHSzs3Vb9/U3tzQbtnR19vU027K0NjYgNF8bM3KyWhoa9LQcWxva2tvys9qbtDVamnOaGdozG9re3dxc2tyaHNzbG5ucnBzcn14eXZzdXlyb3Btb3Frd3RydHZ0c3V0enZ5fpF5d315dnqIfYB9fXt4fHl3dnp5e3p8f3x/hH57f315dHt9gXx9enp5fHh5e3x8gXuDfYB+gIV4eHx7fHt9f3l4fH1+gnx+fH99e3t5fX58e3t7f39+fIF+f4GBg4eIhYJ/gn6BgIJ/goN/hYN/f4CGipeJg4KHiYifioeIjYuMi4yMiouHiImPjoyNiYqMioeFiouJkpGVkpWTlpWSmJKVlpeWl5ibn5ydmpugoKGlo5ylnp2il34Bf45+AX+Hfv9/s38Bfrt/237/f6J/iH6Ffwd+f35/fn9/i34Bf5J+A39+foR/uX4BfZV+wn0Bfot9AX6FfQF+hn0BfoV9Cn5+fX19fn5+fX2Gfg19fX5+fX1+fn1+fn59/37jfgICBACA2Nzc29fZ2Nje19TZ2t3h5+/t7e/u8e7u8O/w8fPx9vj9/IOA/4H++f73gIGChIODgoWGh4WGh4eJiYqPi4uNjYmLiYyMkYuHiYmLi4yPj4+Mj42NjpGPkI6elpKPmo2RkpCQkIqMjZCLkY2Qjo2Mi4iLi4+Mj4yVjI6LiIiJhoaAioqKiYqKiomTiIiHjJCMj5CQjY6Mi4uKjImHiY2Ki4yOjYyLjY+QlJKTkZCQjY+RjY6Mi4qLiomNjJKQkpCSjYqLiYmJjIqQkZGPjpGNjY2OjIuLiYqKiYiIhoiJiIWHhoaFiYqNjpGPkI6Li4uKi46MjY6Lj4yLjo+QkJOXj44OjIyMjYmMm42LiomJi42EjoCKkoyLioeIiIiGgoKC/fn6+/r8+/v39vX29fHq7Ozr6vXx6Knv8PXv5fnt5uzs7+/q7fqB7ebt6ejr5unl5Obk5ubi4OLc4N/g4d7f4eHb3N/h5OTh6Ozp4+Dc4HBxcnZ3duh/dXTjdHd0c3V9dd91dHZ5c3N0fHp9d3dzc3J0dwF7hHqAdnl2dXVyc3l6eHd3d3V6eHp4enl5fH56gXt/fn19gIN+f4KAf39/e35+fH9/fICBf4B+fHt7fHx8gYCFhIWEh4KCgoOBf4KIgIB/foCBgYKAgYB+fH18f4GCgYOEgYOCgn6GhH+ChYCAgYJ/gn97e3h2foB9f3p6enx8fHp5enaAdXN273zr5HZ0h+Xk6ezs7/B7fXno5eTs4uPx8PmC+IGA9/3y6Ovq5Onu+O/m5ubt+PmAg4X9g4CF/ezt5efWy9HJysXMyMbEvba3uKykpKamra2qoaign6Spo5yWlJaUmZmgo7WvopiUl5WTlI6Hi4eghv2EhIODg4H/+5TygoeAlJaQkIqGioL+7OHf4d/h397d3uzw7vvn59/X0dfm7+no6eTk7eLY3t3o6df34/Td7dzd59va3+3m3tbZ19bb3uPp8+2A9/739vWD/YL+8OLp7fL89/v9+PPx/O757IGAhPf0+4GFnoiJgf6B/4GK+v79jfmBgoeCh4P++veB+PeAhPb6/YGEj4mFj5iPhoSKhYSDgoyJjYmNjJmMi4mQkJCNko+SlpGQlJKTlZmgnZ6enJycn5+gnp2gnqCgoqWjo5+ZoqGXmp2in6OmqqWso52fnJadsKylq6eknJmdoKCko6ain6KnrqKmoqWnqaGeppyloaenpayrqaejmp+hoqJpqaenqaiosKyfq6Omo56koKCkqKiop6SspaempqumqqyvrKu5sbS1trO1srCxrqyuq7O0t7Oyt7WzsLO4tcG9ury5s7a+vLq9wcO/xsfFwsfDwsK/xsjHxcbIyM3M09fb2NfU19PT2NfdgLW7urq5vsHFzMXCxMHAwsXKx8bHxsnIyszNz9LT0tTS1NNraNJs2Nzf23BxcnNxcXF0dXZzc3RzdHN0eHR1dnZzdXR3dnp3dnd2eHd3eXl6dnh3eHl9e3x5j359eoJ4fX5/f357fn6BfoB8f358fn17fn6BfoB+hX2Afn5/gIB/FICAf319fn5+iH19fYGDf4GBgoCBhYI9f31+goCAgYKCgYCBgoGCgIF/fn58fX58fn5/fn99fYB/hIGDgoSAfX5+fn2Ae319fXt7fXh5enp5eHh4e4R8gHp7e3t5fHp6enx7fHt9fH17eXh5eHh6eXp6d3l2dnl5enp7gHh4eHl6e3h6hHt7e3h5eXp5eXh4dn14dnZ1dnV0c3Jzc+Pm5uXk5+Xk4uPi4uHd3ODf4d7n49yZ4+Pi3dXx29Xb3+Hg297oedzW2djW2djc397g4eLi4N7h3ePjRuTk4eLk5OHj5Obm49/n7Orl5OLlc3Nzd3d26X92deR1d3R0dXx35Hd3eHt2dnd/foB9fXt8eXl6e3l4ent4fXx8f3t8gX+EfSZ+gH1/fH59fX+DfYN/goOCgIWHgYKDgYKCg4KFhIWGhIKEg4OEhISDgIKAhIKGhoSDh4CAgIGAgIOQg4SDgoKDg4OBhIWEhIWDhYSDgYKLgYSDgXyJgX1/gHt7fH17fn17e3p3enx3eHd3d3l3dnV0dnNzcHPkdufkeXiE5uLm5OLg33J1c+Pn5/Lq6PLs53XecG/Z3dnV2NvW2tzn39ba2t7k4nFyc9lxgG5z2c3Pysy8tLiws7K6ubq4tbCwsqeenJybn6Cfl6OamZ+ln5eTkpGQkZCSk6eul5CMjIuJiIR+fnyWeeZ1dXZ3eHjz8ovsfn6GhoB/fHp9d+rg1tfX09bU0M3L2eDe69rZ08q/wMzRysvOzM3VzMfLytLUxujM2MfVwMPLwsLJgNjU0cvNzMrOztDS2tdz4erl4t924XHc1c7X2tvi293l3t3c7+Pp2XRwctjU2G9yg3Z4cuJz5HF44ObkfNtubXFvcm/Z29ly2tp23eHic3R6dXB5gnhycXl1dnV4f3p6d3h3f3d1dnl8enl+fICEf36AfH19f4GCgoGAgoCDhIWEgISHhYeDg4aDgoKAiIqEhYiLhoWIioSMhoWHh4WHkY2LjIuIhoSLjI2NjY+Jh4qNkoiKhYeIjYmJj4mRjZCOio+PjY6NiYyQj42Rjo2Mi4qSlIqZk5aVkZWQjpCSkY6PjpSOkpCQlJCTlZeUkZqSlJSXlZeWlpqYmZyanZqcl5qfMpybm5ygnKKenKCdmp2ko6Oko6Ofpqmpp62qqammqamop6qtrLGvsLK2sbKztrS1uri8gKKmpqahpKSlrKimq6qsr7S6t7e3tbaxsbKys7W2t7i4u7thX8RlycfKw2VmZmZkZGNnZ2hnaWprbG1tbmpqbGtoa2lsbG9sbG5tcG9ucXBxbXBucHJ3dXZzjnp2c39zdnd2dXVwc3Z4dnt4fHx7e3p3eXl7eHt5gHl8enp6fHt5QHt7e3p6ent7hXt8fH+BfH18fHp6enl4eHl3eHp+fX5/gIB/fH19fH16enp7fHp7fHl5dnV1d3Z1eHd8e3x8fXiEdYB0d3R4enh4d3h1dXR1dnR1dHZ3d3d4dnl5eHZ4dHRycnFxcHJwcXJvcHNyc3Z0c3Jwcm5ucXJzdHeAc3NzdHZ2cXR9cnFxb3BvcXBwbm9rc3BxcnFxcHBwbm1u2NjZ2trc29zY1tTX19XP1NPZ2OLf2pjh4uXf1v3d2t/i5OLd41vvfubi6Ofk5+Pm5+Tm5ejn5OPn4+vq6+3r7O7v6+3t8O/v7vT5+fT09vyAgYOGhIH/jYCB/4KEg4OFi4T7goCAhIGCgoyJjIiIhYiHiIuNjI2OjomOjYuNioqQhI8/jo+SkpWTlJKSk5aSmZSYlpSTmJyXmJqYmJeYlpubm56dmpydnJ6enZ2cnZuZnZugn5+epJycmpyamJ2snJyahpiAm52cnJyanZydnJ6km52amJSom5eampSUlJWPkY6Ji4yLkpKMi4eHh4uIhoWDhYODgIH9gfv2g4mR//n+/fz+/IGEgPn38/728//8+4L2fXzv8+zm6uzi5ebq4NbX1tzk43FydNVvbXDWzdHN08W9wbi3tL69vr21sK+xqqGfoJ2An5+fmayhoqmtpqKbnJqdoKGnqsbNo5iQjYmKj4uHi4ejgvF+fn6Af3/++YfnfYGOkYyNiIOEfvLh1tPT0NHRz9PT3d7e6tXV0sm+xdLb09Xb1dXe1czOy9TVxeXO4snZxsTKv8DD09DMxMjHxsjJzNHU0XDW3Nja1XXfct7Sv8RdxMTNzdXY1dPW49fdz3Fub9DP0mptd25taMpmyGZtyNDUe89qam5pbWzS0s9v09BuzdDMaGp2bmpzg3dtbXVwcG5udXJ0cXV0f3V0cnd2dXJ0cXN0cG9wbXBxc3l5hXqAfYCBgH+Bf4B+foF/fXt3fX55e31/fn6ChYKHf3x8eHR5gIGBhYKBfHd6fH1/gIJ9fH+DhoGDfX+Agnx6gHiBfIB/fYSFhIKCen1+fn2Cf39+fX2Eg3qSgIODf4aDg4WHiYWGhYqFhoSEh4CEhIWCgIyFiImJh4mIiIyIh4mGi4s2jYiJj42Mi4qNiZKOjpKOhYaMiYmNjpOSl5eVk5qYlZaRlJSSk5ebmp+dn6Sop6inqaSipqOnon4Ef39+f4R+/3/2f5Z+AX+PfgF/qH6GfwV+f39/fod/AX7/f5p/B35/fn5/f3+HfoN/iX4Ef35/f5F+B39/f35/f3+6fgF9hn4EfX1+fYp+vH0BfoV9A359fpF9Bn5+fn19fYZ+Cn1+fX5+fX19fn2Gfgp9fX1+fX1+fX19/37mfgICBACA29/k3uHg39vb3Nze6O/s7/fq7/Ds7/Dx9PX68fb7+P36/oKAgoKDgoOCgoOGgoOEhIOIh4eIh4iJi4yNiYyKi4mLjZOTjo2KjIuNjpKUk5OTlZSSjpCRj5CSkZORmpOVlJWPkpCSkpSTj4+OjpCOj4uPjpGSkpCTlJOQjo2Kh4iAi4qLi4uKjY2LjIyLjouPjo+NjY2OjY2NkI+NkI6Oj5CPjpCRkZGSlpOVlJKSk5KRkZKSjI2TkJOOkZKUko6QkJOSjo6OjZCQj5KVkpCQj4+OjI+NjI2Njo6Lio+Ji4qLi4uMjJOPkZKRkZSTkaCQkI+TkZKQkI+UkZKPlpKWlZWAkpORj46Njo2NjIyLjJCQj46LioqNiYmHiIiHhoaJhYCAgP34/4GCg4CB/oD59/bz8/b1+PX5+fX2+ezx8evz8vP2+Pj58O/u7/Hw7+/u7uzx8urp7Ofi4tzZ4eDi5OHl4uPj5eXk3t7n4+Xn5nZ2dXJ2d+np6HV0c3V0dnV0gHWAdel5enx4dXZ4dnt2dnRzc3V0eHh6enl+gHp3eHh2d3t6d3h7d3p3eHl9ent6f36Bh4B/f32CgYWDgoWCg4SAgYGCgYJ/gX9+fn17gH16e3+AgIOEipKGh4eDhYaFiYaChIaEiIqEhoqFg4OChX6Ag4iFg4KDgYSBg4GChoaIhIOAgIKCgIB9enp7f4B9gYB8eX19f4R+fnt4e3x/foDyd3h3dHfrfXt7fIGA7e556vJ6hfN9+vz1gvz59/bp4+fm6vL05ujygPOChIeJlIWFioL87PLs+9/Oy7vCvLjAv7y+vsO6uravvLOorKyppbW0rqinop+flpWYm5mXl5uYkpCAkI+PjJOChYCAh4CFg4KCgP78gfn+gYD6g4mPhIH+++7q4eXj4uXe4dja4O7y4+bk4tfW3Ybs5e/47PXo5dHR3dny2eHr7uDb3ODm39ja29Xj3trY4Nrg3+Ll6/Hu+e/x7/js6/X28ens9viAg4H+/ffw++2DgoKCg4KDiYGRg5CAgYaFhoaC94GDgYWEhoeEiP30/YX5jfKBgYWFhIaGgoaOjoyMkYuLjYuPi4qOio+Ok5CLkY6Jj5CUjpGWmpWUlZmcm6Cin6muo56eop6gnp2dnJ6enZ+io5ymoqSgoKKsrLKmp6OpoqKen52cnqCgoaGco6OjpaahrKmjoaanp6WAqKipq6SipqCko6WmqquprbGip6OnpqWosaatqaups6mrqKSdoqGlp6aqrK6rpaqrsKutqamvr7O0srmytLS0u7OwrbG0srWzxLu4tLO4udq9vcHHwcLBv8DGxL/Bw8DEycbGycPGxsbEzsnCzsrNz8nOz8/Tz9rS19bR293c3NuAu77Avb29vr/Bw8LBxsjGyNHHzM3Jy8rKzM/SzdLV1NjT12tqaWtsbnBxcHBycXN0dXN3dnV0c3R0dXd5d3p3d3R2d3x8d3l5enl5eXt8e3t7fHx8en19fH6AfH16gn1/gISAgYCDg4aKgYGAgYF/gH2AgISCgoGCg4OBgIKBf4AegoGCgX+AgoGBg4ODhIGEgoOCg4OFhoWGh4WCg4KBhIIpg4SDgoKEgYKBgICAgYCBgoN/gIiCgn+AgoWEgYOChIJ/fn1/gX99fn+EfYB+fHx+fXx9f39/fXyAe318fX19fn2CfX5+fX2AfXuJeXl4fHp8enl5fXp7eH14e3x8en19e3t8fHt8e3t7fH9/fHp3dnd6eXp5eHh3dXV2dHF0dezo6nV0dXJz4nLk5eXg4uTh397i5ebl6uPn6+Xp5OLk5OXn397d3NrY2Nra34De4uPf3+jn5+rl4ujo5ujm5uXm5ejr6+bo7+rq6ep2dXRzdnjt7/B4d3d5eHl5eIN6evF9fH16eXp7e356e3p7fHx8f35+fXt/gnx6foB/gYGAf3+DgYN+gH+DgYB/g4KEjYODhIGFhIeFg4WEhYiGh4aJiIiGhoWGh4eFioaDgoCEhYSFhoiMhoaGg4OEg4aFg4WHhomLhIaLhoeIiYyFhYaJhIKBgYGDf4KAf4CBgn1+fX9/fn99e3t7f352eXp7d3t5en53d3Vyc3V3d3vodXV1c3XoenZ1dIF45up46PF5hOp55+XhdeTm5ebc1tnZ2N7g0NDcat51dnZ3gnRydoBu1MjPy97Gt7essrCutri2trW3ra2mn6uhmZ6gnZ+rqaOampWRlYyMjpCKiYqRj4uJiYiFg4l6e3h3eHF2dHZ2denmdufreXfneHyAd3Tj5NvY0dXT0NPQ0czO1OLl2tza187HyHnQytLZ0tvU0cfEzMfiyMvV2MrCxMjLx8XIyIDG0c7KyM/Lzs3OztXc2+Lc39jh1NHa3tbQ0tvdcHJw4Ojj4e7eeXRzcXFvcHVwaW54b3V0dXRw13FxbnFxcnNyheLb43ngf9d0dnh4dHRybnB4dnV2enZ3eHZ6eHd6d3p5e3t2fnt7f4GFgIKEhoKAgIOGhIiLiI+YiIODhoKEhBmGh4eKiIaFhYaBi4qMioqMko+WjYyKkIyKhYaAiIiKioeNjoyPjoqRjYeFiYqJiIuMio2MjJCKjo6OjY+OjZCTiJCPlJOSkpWLjYqJiJWNk5WTjpWTlJaTlJeVk4+Uk5uWl5WUmZibmpialZWUlpyYlpWanJqcm6qgnZqam5u7np6gpaKjoZ6dpKWipaeho6ikpauoq6urp7CqpK8TrbGzrbGxsrSvu7G0tLG6u7y7vICjpqilpqaoqautrKuxs7Gzu7C0tLGysrS3ub21ury7wLzAYmFkZGVmZ2VkZGRiZGVmZmpqa2xsbm5wcXJtb2xtam1udHRubm5vbW5ucXNycXFzc3Rzdnd2d4d8d3N6dnd3eHN0c3d5fH96e3x+fnt8d3p6fXx9e3t8e3p5enl3dyF6eXt7enl8enl8fHt+e358e3l5eHp6ent9fHx/gIKCgoCEfgp9fn9+f358fX9/hHyAdneEe315fHt8e3h4dXl4dnd4eXx8e3x9end2dHZ0dHd2dXZ4eXp3d3x2eHd4dnZ1cndycnJxcXZ1dIh2d3V5dnZzcnF2dXd0eXN2dHRxdHRyc3R0c3RzcnJydHRxcG1tbnJwcW9wcG9vb3BvbG1u3tvfcHBxbm/cb9va29ba3dyA3N3g4+Hg4NjY3Njh3+Lk5urt5ejp6urn5+fp7e3y8uzo6+fk5+Pi6eru8e7y8fLx9fT28fL69/j6/YCBgYCDgv7+/YCAgYSDhoSDjoOB/oOEh4aEhoeGioeHhoeIiIiMjo+RkJWZk4+RkY2PkJCPkpaUl5OVlZmXlpWYl5qfmZdlmZednKCdm56cnaGeoJ+gnp+bnp2doKCdo5+bmp2enJ+hpKekpKKenp6coZ+dn6GfoqKanKKenp6foJqanKCbmZiZl5qUl5aVmJmblpeUlpSSkI2MjI2Tk4yPjoqEiYeIjoaIhoCEgoCF/YCCgoCB/4WEhYaYh/78gPT8gYv5g/v69H7z8fDw5uHj3+Hl5NTV3m7ednd4eIFwcHVt1MnR0eLOwr+yubWyubq2tLW2rbGup7arn6Sjn6G0trSrqKKdoJaXm52bmZmenpeTj4qIiJSHioSChXyAf4KCgv/2ffP7goL/iIuRhIB98vDj4trb19TV0tLP09ji5t3d3NvRz9OH2tHc5Njh29jIxM3E38HFztbIxMfLzMXAwsK+zcnExc3GysnMxs3RztTQ0c3Xzc3W1s7Gx87QbG5q1dTQzNnPcm9wb25sbnJrcmtwZmppaGtnxmdqaGtqa21rd9DK1G/XjMxqbG1raTNpaWVqcXFycnVxcXFucm9vcm90c3h2cXZ0cHR0dnFydHhxbW9zdnZ7f3yIlX56e4B8f36Ef4B+e3x9fniAfYJ/f4GKhYuBgoCFgH54e317foGBgH97gH9+gH99hoSAfoKDgn+DgoKEgH2Be35/gH+Cg4OIin+DgISDgIKJfYF9fXuFfoGBfnqAf4GDgYKFhYOBh4eOhoeDgYSDh4aEioWGiImMiISBiY2LjouYkY6LjJCQppOQki2Tj5KSkI+SkY2Pko+TmZaWmZWYmJeSmpOOl5idop2gn5+hn6ihpaahp6alo6Oifv9//38Ff39+fn6FfwJ+f8F+hn+Dfot/AX7/f5x/AX6FfwF+hn8Nfn5/fn5/f35/fn5+f45+AoB+iX/Afgh9fX59fX5+fYV+l30BfrZ9g36GfYl+AX+IfgF9iX4HfX19fn1+ff9+6n4CAgQAgOXh4Nvg3eHe3N3e4ubx7PD08/b09/v2+fj9/Pv4+YD+/f3/gYWFiIaIh4iRjoiLi4iIh4eIiIqMjYuLjYuMjYmJjYuNkJGRjo6Pj5CWlJaUk5WVmJSSlZSSl52Xl5eVmZiYmZSVlZSXkZGSkY+RkpKOkpCPkpCRkJORj5KRkI2OgI6NjIyOjpCOj5GQj5CQkpGPk5STkpaVlJKUlJOVkJKVj5GQkI+PkJCSkpaWmJicnJWVlJCPkZOWkJSVnZSSkJGclZWUkpGQkpSVl5KVmpGQkZOTlJOUkpiVkI6PjZGMj42Ojo2QkZGTlZiXlJKWmpiSkZKRj5GQkZSVkZaXmJaZCJaTkpKQk5CRhJCAjZGSlJKRkI+Oi42MiouLiIiKh4WGhoOBgYGDhISEgv2A//37/Pj8+/eAgvz69fP39fX7/P38/Pb38e/r8PX49/n7/Pr2+vDr6+bi5OTh5+fj5e2A6Ozt7n7r7eno6u7w7fV8f3l5fXjsd3h3dnZ1d3p4dXh2dHh6eH1+gXx8e34beXt5e3l7eHyCfnx/gH95fXp7ent8e4J7enx3hHoQfH2AgoGCgX6BgoODhISGhoSFOoaHg3+DhIOCgYF+gYGCgoKEgoGCho7EioaIhYWFhomGiImKhYqFhoSDhICGi4mGh4aHg4iFiIOEiYSEhoCHjoeEhYSAgIJ/gYCFh4eEhoJ/gIN/g4F+fnt7fIB/fXp4fnd6dXd6eX2Afn+BgPf5+vD294D9+4CBgv6D8+bw9e/3/v749fuEh5iPjYuKjY2MiYSB/Pfn4tPGuba95cPAwsrJz9uzstDLtKauqKfJ0NHAp6ienKuin5eXnJ6elICSlY+OjYmJhH+FjJmMiIaGhISIsYWC+vp7e3/0hI6JhPf2+PHw693j6d7h6uLo7+nl79rc297d4t7x7PTv8/Lz7Orm6/Hj5fDn6eTe2+bh39vd5ODd4+/X2ePk4+3n6ePt9ubr5vHi7Yfh6O3s/oD//v/49PmE7f2HhJGXhoSJioCMioiKh4eFk4OC/viGgYGCiIWM/oSGhYD//YCIhIGLgo+TjJONjIyHiZCMi4mIhomIjYmRoZGQkZ6Tk5eXmaKboJuYnKKVoJudl5yfmpufnZ6XpKGcn56doaSloqWnqaannqimqamqq6mjpJycnpuenaeloqKgpKmoqqawqqmmp4CjqamurKqtpqamqKKipaemqaemw6SmpqOpqKqlpK+vsrOws7OusLiuq6+vr7i1srCsq7CtsLSwra6xsrS7u7e3wby3ubayu7S4t7KytsW5ur67l8a7vMbCwMbCyfDbwcfGyMbIy8zvz8zMzMrIyNDQztLM0trd2N7f4d7a2tvg2wPa4N+AyMTCvsC9w8TExcXHx8vIycvLzsvN0MrNztPT1dTXb9zc29hsbm9wcXJycnl2cnV2dnZ1dnZ1d3h4d3d5ent9enl8ent8fX59fH17e398fXx7fX6Afn6BgX+Dh4GAgYCEg4OFgIODg4aCgoOCgYKDg4GFhIWGg4SCg4KBg4WDgoRPhIKCgYODhISFh4WEhYSGhYOFhoWFiImJhoeHhYeEhomEh4aGhYWFhISDhISFhYmHg4SFhIOEhYiEhoWSh4aDg4uDhISChIOBgoGCf4KHgISBUoB/gICGhYB/f36Be359gIB/goGAgoKGg398foF/e3p8fXt9fHx9fnt+fn99gX99fX59gH1+fX19fHp9fX59enp5eXl9fn19e3Z0dXV1d3h1dnaEdH91deV16Onn6uTm4+F0c+bm5efs6Ofq5+jm5eHl4+Lb39/e3d7f4uTg5+Tk6Ofn6Ojo7ezo6fGC6ezr637p7evr7vDt6e53end4e3rweXp5eXp5e4B+fH5+en5/fIl/g39+fYF9f32Af399gIR/fH9/gH2FgIOBgoOCh4KDhYGEhYIHhIWFh4aDhoWHDIiHh4eGiImLiYaKjISJgIiLi4uJiYmGhISIjsWLh4mHhoaGiIWGh4iFi4eIh4eJhYyOjoyLiYiFiYWHg4SHgoSDhISEioWCg4N/f4KBgn+AgH58gHt5e3x4fHp5eXV0dXl7eXZ1fHR4c3V2dXd5dXh5eOnt8Ojs63np53Z1dup34Nbe4dnb3NzV0dt1eYt/gHx5d3h3dXNtbNXSysm+tauosNu1srG1s7fAoaDFtaKapKSkxcXBsZick5CelZOMjI2PjoiNj4qLiIODfXd5eol1dHN0dnh4jnh36uxzcnPedXt2cdbZ3drd1szP1MzM1szQ1dLP2szNzs/KyMbW0dbQ09LTzc3JzdbJx9HNz8jFgMTKycnHx8zMy87hx8jOzs3UzdPR3ebZ3dLbzNV6zdDV0t5u3dzg39vgdtjkd3J7gHFucXJzc3N0cXJzfnJx39p3cXByd3V94HN0dXLk4nN5dXN7cnl7dHl0dXVydX16eXp6eXt7f36Ci39/fYl/gYKChIyDiISBhouAi4eJg4aJgISEh4WFgIyLh4qJiYuNjYmKio6KjIePjI+OjY6Oi4+Kio6JiomQjoqOjI+Qj4+Kko+Li4uIjIqOjYqPi4uPkYyOj5CPj42LqI2PkpGWlJSNi5GQkZGRlpaVlpyUkZOUk56bmJaVlJaVmJuZl5eam5yjnZiWnpuZnZqZoZygoJycN56qoaCioIKooaOoo6CkoKTRv6Kpp6ijpaanxKump6moqayzs7GyrbG0tbG0tLm6t7u8v7y8v8BHraiopaursq+wrq2urrSxsrWytLGztLC0tLm5u7e3X768vsBgZGRmZmdlZnFrZmhpaWppa2xsbnBwbm1vbm1wbW5xcHF0c3OFbh90c3V0dHV2eXd2eHl3en96eXp4fHl4enZ4eXp8ent8hH00fHl8fH2Afn99f3x7fX59e3x7e3p6fX1+fX6Af35/gIF+e31+fHt+f399gIGBhIKEhn+BfoV9H399f3+BgYaFgIB/fX1+fX97fn2GfX14eIN6fHx7fX2EfIB2eX12dnZ4eHl5enqCgXt7e3l8dnd1dnRxc3NydHV5eHZ1en9+enh4d3N1c3N4end6eXh0dnJxcnJydnJzcnNyc3F0dXd1c3JycnN2d3Z2dHJxc29ub3BvcHBwcXBwcG/YbuDj4ubi5uPgdHTh3trZ3Nve5OPm5+nn7enq5Ors7oDt7vP19fD17uvu7e3x8/D19e7w+4z3/Pz8h/n79/b5+/r4/YCEgIGFgf6AgoOEhYWIi4iGh4WDhoeHko+TjYyKjouNjI6NjouRmJOSlZaXlJ6Wl5OTlJWdlZeYlJiXl5eYmZucnJ6dmZ2foKCgn6Ggn6ChoqWmoJufoJ+foKGgo4CkpaKio6Cen6Oq36qlp6OhoJ+hnp+hop+knqCenqCco6OkpKOfn5qcmJqWmZ6Zm5mampuknpqamZOPkpCSkZSVlI+TjIiLjYiMiYaHgYGBg4WEgoKLgYaAgoWEiYqGh4eF////9vf2fvXye3p68Xvp3efq4OPl4trW3HV3jYF7eIB4d3V1cm1t2NbO0ce9tLG55L28u7/AxcmurOTJrqCppabM09jJr7Onn6ygnpiXmZiWk5SRi4mGhouLhouOm4R+fH18fICTg4P+/4CAgv2FjIZ95eXj3+DXzdPZ1dbh3ODl4t/o29vd3dXTzd/c5ODl5efc2M/S2crL19PX0s7Jz4DKx8TGzM3N0OPDw8zMztTT0M7X4dLWzNfGznHBxsjE2G3W1tbPzdFuy9hxa3qAbm1ycXFvbG1qaml3aGjMym1oZmhta3HObW9wbt/dbXNvaXJqc3Vwc25ub2psdHBvcG9vcnF2cneBdXR0f3d3eHh4gHh6dHF2enF9eXt3en99fV+CgYJ6hIB8fn17fX1+e32Ag4GDfIN/gYGBgoN/g31/hH5+fYSBfH97foGBgoCKh4SEhYGDgoWDgIV/f4KFf4CChIaIhoSkgH+AfoSCg4B+hYWFhIGGh4OGj4aEhoWEkISLUoqMiIaLiIWDh4aJj42JiJKQiYyIgoyJi4+Kh4qWjY6SkHWakZGXk5GXlZm/q4+UlJWTlpiYv5yYmZuZmJmdm5iamJ2jpaKoqq6tqaurrKalqaeefgF/hH7/f/9/in8Cfn+IfoJ/qH4Bf4R+AX+JfoZ/AX7/f7h/hn4If35+f39/fn+Lfo1/wX4GfX1+fn59hH7IfQF+hX0BfoZ9A359fZJ+gn2HfgF9hH6Cff9+uX4Bf7B+AgIEAD3g3enp4+fj5d/j5uvu8/Lw+PTx9Pf19vr6///6/vz6/oODgoaIiIyMio2Ljo+Nio6Mi4qIioyLjY+OkZCQhI+AjZCQk5WTkpKVl5iUlpaWlZaWmJeYlpaUlpmZnJiYl5uampial5mbmZiWlZSWlJeXmpiYmJeUkpOVl5SVlJOUk5eXlJWTkJKQj46TkpGRjo6NkZCQlZWTlZaWlpeXlpeWlJSUkpGSkZaYmpmampeYkpWVk5OUkpKVlpaYlJOTlZo1m5eZlZWTlJSWlpWWmZORlJGUlpSSkKGYlZaWl5eWmJSTkJCPkpGVlJWYlpSVlZeWmpWUlpuEloCVl5mam5mZm5ualJOTkY+RkJSSkpWblpWVkZCPjoqNiYaFhoOGiIWFhICBgYaDhYiGhISC//v6+vX0+4iRgPv59Pf59YD6gP378/T18vTz+fz6+v74+vr37O3r8u3k5uTl6O/t6+bsdnV3dnXn6ePseXnreXl+enx6enp5eXd9g2d5eXh3fHl8eXl5enx7fHt6fHt8fHx7ent8fICBiYGBen19fH58e317gX57e3x7e31+fYKBgoWIgICDgoODg4KGhYWJhoeHh4aHiIaGhIWDhYWJhYKGh4mKioyJh4iHh4iJiYaHiYqLhImAiIiIiYaHiYWJi4uIioqFioaHiYuJh4aJiJSGiIOBfYeAgYCDf4SFh4aDgYCGgYODfn+Be3t+e3qCf36AfYh6f3+BgHyBgIB/f/7/gPz1/YWIh4ODgPX09vP08fj1/fiHi5KOjpecmJWen5OLgO/q49rIxb/ByMG7urrAx8TCw3mAt8G2trPBysa9uLGmoqGgsr2lqa6lpZ6mjY2KiIuIhoJ/h4uLh4WDhv6Bg8eD+YH+/IOChIaFhoyLhIL88ezm8fPv6ePp7+7n59/d4ODd4O/a2O/W6Ori3dna3t7hztna39jY4ejo4uLj6vL08eTZ2d/m6t7g/ujf6OXl7PLu9PEe9unv+fj7gYOA//vs94CAg4uFiYqNioSIh4mIhYSBhIiAjIqHiYmNjY2JiImj/4CEh4iJio6Ih4aJiZOQiY2Ni4+OkIyNkI2Nk4+SlZSVl5iZmpmen6GhoaOin5+fnqOinqKfn6KkoaSipqmeo6KmqLKoq6qoraqqqKqoqamtrampo56goqGjoJ+ipqalqLalpamnpqq5rrC1r66sqrGqrKt5qKulpq6ttrGusKympaaoq62sqrC1treztbWlqamwsriwtK6vsLCysqu0sq63r7Kytra3utC7vbm6ubi6ubm5uLq0ubywsrS7tsO/wcfCw8vPx8jLx8jHxs/U0NTS1NTQ3Nva2M3T2NPS1dja3dzg29Ta3OPf2tbc3S/FwcfFwMHAw8HEx8zMy8vJ0c/O0dTS0tbW29zZ39za3nJxbnFwcXNzcnRzdXZ1dIR4WHZ5enl7fHp9fX18fH19e39+gIGBf4CCgoN/gH+AfX1+gYKDg4SCg4WEhYKCgoaGh4aGhYeJiIeFhYaGhYaGh4aJiIiGg4SGhoOFhIWGhoiJhoaGhIaGhoWEiCSHh4aKiIiLi4qKi4qJiImIiImIiYqIh4eFiYmIh4iIhoeDh4aEhYCEhoeFiYaFhoaKiYaHhYaEhISGhIWFh4OChIGEhIKBf42GgYGAgYGAgoCBf4GBg4KEgoGEgX5/f4CAg358f4J+fn5/foCBgoOBgIOEhIB/gH99f31+fXt8gn18fXp7fH18gH15d3h0d3l4eXl3dnV3dHV3dHR2durq6+zp6Ot7gIB27u7q7eznd+d27Ozp6e3m4t/h4OHk6ujw8O7p6erz8ejr6uzv9PHw6u13dnh6d+7t6PF7ee14d3t4enh5eXh6eX2He319fIJ/g39/f35/fn9+fX99fn2AgYGCgoCDg4mCg3yAgoSHhISFg4qJiImJh4SGhoSIiYiMk4eHi4mLiyuLio6KioyIiYiJiYuMi4qJiYiLio6LiIyLjo2Mj4uJiomIiomJh4iKi4yJhIqAi4uMiouMh4qLjIqNjomPiYiJioqIh4mHkoWHhIWBioSFgoSAgoGDgH57en57fX16ent4eX16eYF7eHp2gHR4eHp3c3Z1dHR27u937OfrfH98eHl14eDh4N7Z3djb1XR4fnp5gIJ+e4GBe3Vt0c7Lx7a1r7G1sa+trrC0sKyxeaOAtKOmprfFx7mupJWRlZKirpKSl5GVkJiHiouMi4eDfnd6fHl3d3Z96nR1snfidOHecnFyc3FwdHRwb93b1tHX2NPLycvR09LSzczOzMnN2MfF2r3Ly8nHxMbKyMm4v8DGw8TL087HyMbP1NnWy8DCxMnNxMbf083Y1tjY2NbW09uA0tLg2dlxdW/e39TecnFyeHBycnVxb3Ryc3RycnF1dHR0d3RxcnF1d3d0dXaJ4nNzdnd3dnhyc3F2dYB8d3h2dnp4enl5f318goCChYODgoSDhIKFhYaFhYaGhoeHiI6MiYyKh4mJhoeGi42Fi4yQkJmOkY+Pko6PjY2MjI2QkpCAkY+PkpWTkIqHiYyKi46aj4+SkI6Pno+QkYyOkI6XkZGSkZSPj5KPlI6OkZSQkpKVlJaSkI+UlJWUm5+Vl5aampuXmpiZm52dnJadnJqkm5ucnp2foLafoJuenp2gn6Cgn6CcoaScn6Cjnqaho6ilpaeqpaitrK2rp6uuqq6usLAar7m4ubuytry2tLa2tbi2uLe2u77IxcC/w8KAqaWvr6ywra+sr7Cztba2tLq2srO1sbK3ub/Bv8PCwcVkZGJkZGVnaGdpaGpramlsbGtramxubW9wb3FxcW9wcnJwdHN0c3Jyc3V1dnJ0dHVycHJ0dnd2eHd3enp7eXh4e3p8ent7fX9/gH5+f399fn+Af4GBgH57e39/fX9/gIKAgoSFgoODgIKAgoGEhISCf399gYB/goGAgIKChIWIhoWEg4GCgH5+fYKCgoGCg4GCf4SCgYGAf3t9fX1/fnx8e35/fX9/f319fH17e3t9eHh4dnl7enl5iIR+f31+fXx9enl2dnR1dHd1dnl3dnh4enp8d3V3e3Z1dnh2d3h3d3WAdHZ4eXV2dnRzdXJ1dHN1fHZ1d3NzdHRydnJxb3FvcHBtb29ucHBzcHF0cnFyc+Xi4+Th4OJ4fnLh4d/i5eR36Xfs7Obo6uXm6O3v7/D38vb08enr6/r49Pr49/v9+/v4/oKBg4SA/Pz1/4KC/YCAhIGDgYKDg4WFipKJioiHjYkOjImLi4yOjpCPjpGPkZGGkoCXl6SYmZGXmJqdmZSUk5uZmpqbm5mcnZugn6CjqKCfo6KioqGgpKSkqKaop6empqako6GioqSlqqajpaaqqaisqKeoqaampqShoaWlp6WmpaOhoKCfnZ+jnqOjop+fnpqhnJ2eoJ+dm56bqpibmJiSmY+Qj5OSlpWXko6Mi5GLjYCLh4mIhIKEgoGJh4eLiJOGiomLh4KGhIKBgP/9fvbw84GCf3t8eerq6ufj3d/Y3Nh2e4J+fYSGgn2DhHt0bdLR0M/Bwb2/w7+6uLi9wr+6xZmtxq+wrr7JzMPBvK6ppp6ts5qcoZufmqGPjYuIh4eJh4WIiYV/fXp/83l+qoL7gm/5+oB/gH98e317dXLi4t7c5eXg2NPa4ePe4dja3d/d3uvUzOLF3trY08/Q09DSvsbI0MzM1dnUycrJ09fe29HExsjQ0cjK7dfQ2dXS1NLOzsnMwsHLxchoa2nU1cnRa2tudG5xcXVybW9tb21qa2iEbDRvbGlraWxtbWlpbH3Sam5wbnBxcm5samtrcnBsb29vc3J0cG90c3F4dnZ4d3h3eHd2dXd2hHiAend3eHl8fnx/fX6AgYCDgIOEe3x8fn6HfYB/f4KBg4CAfn9+gYOAhYaDg4aGhICAgYKBgIKNgH+Dg4GFkoiJi4SFhIGHgYSEhYqFhYqIjYmFhoV9fH6Bg4eHhYiMioiDhYt8f3+FhomChoGChIaHhoCFgoCLgYSGhYeHiqOMj4xBjY2LjY2OjY6PiYyOh4qOk42Xk5KXlJWdn5eYmZWWlpSanpqenp+fnKippKOanqGdnqOlpaioq6ajqKmxraWiqKagfv9//3+Qf4d+g3+GfgN/fn+gfoV/hH4Df39+/3/CfwZ+fn9+fn6Gf4p+jn+SfgF/qH4BfYR+BH1+fX2Kfst9g36EfaF+AX3/fu1+AgIEAIDj5e3q7vby7ebq7Ozy9PP49PX3/Pj7//uDgoeBhYCAgoSEhoqMi5KOj42SkJCRj4+Sj42Pj46Nj5CRlZKRlJOWkpSUlJeZl5WWl5mbmZeYm52Zm5uZm5ucmZudnp+gn5yenp6bnJiYl56bk5WUlZecnJybmpeZlJOWlpeYmZmil02WmpeXlpeXm5uTnJiYmJSUkpiWlZWXlZiYlZWVmJaVmJmWmZeXlZOWl5qamZucm5iWm5uWmpeamJuYmJqXmJiXlpiamJWUlZaVlZaWl4SYgJqXlZSUlJiZmJiZmpmYmZmVlpiXmJOWmJublpmXl5qYm52ZmJyamJebnZqanJybnJyam5yamZmbmJaWmJibl5iYlZ6WkY6Ni4mKiYWGh4qLjIaFgoKDhYeLiYmHhIL7+oL9+/yGpP729O/4/PX3/f38/fj19ID5/oGB/IL89/f6gPTu/fTv8Onn5+nw8n19eu96e3no5efo7Hfv73l5eHl5enx6e3p7fIN8fXt5e3x7enp9fX1/fX19fH1+fn9+goGAfnt/fX6DgIGCf4CAgYSChIOBf35/gIGFh4OGhISFh4CFgoKFg4OEh4iJh46NiouJi4mHhYiHiYiLj4uIiIyLgIuNiYqJjo6MjoyKiIqIh4mFh4eJiYyKioWKiYuNkI6QjIuFh4+OiJWKh4uJiIqJhYOKhoaDhXyBgYOFiIaIhIKIgYODgYGAfn6EhYKEgIWLgYB/i4B/gICEfX+Bf4OFh4ODhISKhIqGhID29/H37vH5/IGJjJOKjpWYmZaWkJH+gOvi293JxLy8vrm9v7/BwsC4vby3ubO1tr/Y1Mizq63Dpamoq6ytqqmgnpyVjImKjYuChoSLjIyKi4aFgYGB/YCEh4WAh4GF/IWCgoiCgPf36+Tx8fn++/SF6N/s5O7k6eDj4uve3Onk7uPh2NfZ59vXgeXi3dTc3uvj3eHq5Oz0gOTo6enx3ObijuPW4+jh3+Df5fbt9Pb9g4SHg4uAgoL6+fmHnrebmIuJjZODh4uKhIOEhYSTjoiTiY2MjJCPm4+JhISAhoOIh4OEi4uHiYaLlJWTk5ORkJKMkpOWlpWWlpiWmpqbnpydnqCZnJ+boJ2in5+npqOvp6CipqKkrK2qgKqrrayusLGurK2traumsK63tLKxq6ahn6SjpKalqrCtr6+xra+yr7GvsLGvsK+xsqiopqeop6WvqbCxtri6vce3sK2lqamvp6qysrCyrrCztK2us7O6vLu+vbO3t7C0tbm0tbK3uLS7ur2/vMK6u729vce+vcLDuby6vLfByMO9L8jKysfOz83Ez87Mzs3YztTT0czS0NPc0tPZ0dLc393e4uH24ODY/ODj4eLk3+XegMPEyMDFysbFxMfJzdHPz9DN0NPZ19nb2XBvd3Bzb3BycnJxdHR0d3Z2dnl4eXl4eHt5enx9fHx+fn6Bfn2Af4J/gYKCgoSCgYSDg4OCgIGEhoGDhIOFhoiFhoiHh4iHhYiIiYmLiIqKko6IioiIh4qKiImLiIyJiYuKioiKiZGJWoiKiYmHiImNjYWMiYmIhoeHjIyMi4yLjoyKi4uMi4qLjYqMi42Mi46NjouIiYqIh4aJiYaJhoaGioeHi4iLi4mJiYuJiIeIiIiHiIaGhYWGhoiGhYWEhIeHhISDN4KCgX+BgoOEgYKFhoaCg4GBhIKEhIGBg4F/foKCgICDhIODg4GBgoKCg4aCgH+Bf4F9fn58g3+EfoB8fX14eXp5e3t5enh5eHV2eXd3eHd36ex77+7ufJf28vHs8PDo5urp6uzt6+l55ud0cuJ26Ojt8Ozr+PPx9vDw8PH193x8eet3eXfq6+/y93v08np6ent7fH17e3t8foR/f358fX9+f3+AgIGBf4CBgIKBgoGAg4KDhIOHhYSHhICEhIOFhoeIhIaFiIiHiomIjIyJjIyLjI+HjIqKjYyMi4+Oj42Qj4uLiouLi4mNi46MjpGOi4yQj46RjYyLjo2LjoyLioyMjI+LjYyOjpGOj4qPjI2Njo6QjY+KjZSQiZSKiIqJh4mKhYSMh4iIi4OIhYSChIOGgoCEfX9/fn18e4B4fn98fnt+hHp4d456eHl4e3V3eXd6e315enp7hH2Cf3p24+Te4dfY2dpvdXmAeHuAgoF9fXh62s3IxMq9ubGvsKqtsbGysq+lq6mkp6Klq7PO07+poqGrlZuXmqOYk4+Jj4+KioqMj4uCg35/fXt3enp7e3p57XZ4eXZzd3F24IB0cnN2dHTn593U3tnb3NTRg9HP2tTZz9TIzMrOx8PNydXKzMXDxtPFwHLIxsS/xMnVz8jO0s7U38/OzM3Vv8nHhce/zM/Oz9DR1dvP0cvLbG1vbnhucHLf3tt1j5yDgXVxc3hucnd6c3N0c3F9d3B7cHRyc3h4g3x4dXZyd3Z5doB1dXl4dXd0d359fXt8fHt7eX2AgYKAgoKEgoWDg4WEhISHgoSGhIqHjIqJjouHjoqGiI2KipCSjo6QkY+Sk5WSkJCQj4+NlpGUk5OVk5OTkpWVlJSOj5KOj5KVkpKVkpSRkY+Mjo+TlpGTkZOTk5CVj5KQkpKSmKOYlpaTmZuelmeYm5iWlpSYm56Yl5ubnJ2cn56ZnJuXmpyfnJ+bn6GcoqKio5+inJ6goaGmoaGmp6Gko6Wgp6qknqepq6erqquosa+srKiyqrOzs6yvrKuzrrC5tbW+v7q5vLrLu7u35r/DwcTHw8fAgKenr62xubaysLKztLu6uru5ubm7t7i5t2BfaGBiX2FjZWRkZmZna2lram1sbW5tbXBubW9vbmxubm9zcW9ycnRwcnNzdXZ0dHZ2d3h3dXd5enZ4d3Z6enx5e31/f399en19f31/fX5+hoR9gX9/gIKCgoGCgIOAf4GCgoGDg42CXYGFg4WDhIWHhn+Hg4SFg4OChoSDgoODhoWEhYaHiIWHiISFhIWCgISChISChYeHhYSIhoOGgYKAgoB/gX19fnx9foGAgH5/f39+fn1+fX19e317eXl6fX+Bf319fYR8gHl6enl5dXZ3eXp1eXh5e3p8fXl4e3h2d3t8eXl4eHd3dnV3eXl5eHp3dXV3d3l2eHh2gHp3dXRzcXR1cXJycXJzcHJycnFxcnRzdXV1d+fqe+nl4nWR5eHh3ubr6enu7uzu7ezpeu3ye3rxffTv8PDq6Pfy8/j19vX3/f2Bg4D9gIGEgfr6/fz9gP79goOFhoeHh4WGhYaIkIuNjImLjYuMjI+PkZKRk5OSlZWWl5SZmJiYl5uamZyYl5eXmpydn5ugnZ+fnJ6foKWmoqOhoaGknqKfoaSjpaSoqamnrqyqq6mqqKajp6aqqKywq6inq6mprKempamopqmmpaSnpqaqgKanpaWlqKamoqajp6eqqKqko5yfqaegr6Ogop+doJ+ZmJmXmZaZj5SSlpealZiTkJWKjI2Li4qIhYuMiIuIjpWLiomfiIeGhoqDhIWChYWIgoB/f4d+g398eOnp4eTY2NrccXh7g3p8gYGCfn96fN7RzMnPw8G7vL21uby6vr66gLK2srCxqq6zu9jZyreytcatr6ipqKOenZeen5eOhoWJh4KHhIiFg4CCgIB/fnvyenyBgHt/eXvofHl7fnl46Ojf2uXj6Obf14bRzNnW3dri2NvY2s/J1tHc0tLKyszbzsl91dDOx87T3dXOz9bQ1N7NzczO1sPNzYjPxNPVzszLgMbF0MXIyMloZ2lobmdrbdbW0nCMjX+BdG5yeGhtc3JsbWxsbHtya3Rna2lqbm16cWtnamdubXBubWtvbWhoZWlxcnJydHNzdHBydXZ1dXl3d3R7ent9enp4eXR3eXd+fIGAf4SDgImCfn+Bfn6Dg4B/gIKBgoODgX+AgYKDgomCgIOBhIiEgYB/g4WHiYaJjIeJiYiEg4WDh4aGh4aIiYuLgoSAgYOBgomFioqLjIyNl4qDgn6EhIuHiY6MiYiEh4mIgoOHhoqKiYqMg4eIg4eKjIqNiIqKg4mHiIyLkoyNj4+NlI6Nk5SOjo2QjJOYlJGWlpOUmJqZkJqYl5mXoJykH6Sim56cm6Kamp+amaCjoaOop6ynpp7Rp6qprKymq6SYfv9//3+Zfwh+fn9+fn5/f49+B39+fn9/fn+Qfgd/f39+f39/hX4Df35+/3/Qf4h+jX+/fgF9iH4BfYZ+in0Bfph9AX6WfQF+jn2IfoN9/37/fo9+AgIEAIDt8PPv7+7r7/L1+/j79/j7+Pr7+v6A/oH/g4GAgYOFhYiLj4+QkpKNkZSSlJKSj5COjY2PjpGQkZCSkpOVlpOVlZWUlpaZmZianZ2bmZiZnKCdmKGcnJ+gnp6cmZ2doqGkoqKfnpybmJiUmpiXm52fnKGhmpual5qZmZqfnZyZmICXmpeam5mcm5+anZ+hm5mblpmamZqYmpqZmZeZm52coJ6dn5yanZyYmpmcm56bnpydnZyenJqanJ6cnZudm5ycm5mXl5mZmJiWmpyen52coaOhnJ2Ym5uZmJqamJybm5uYmZiWmZyanJucnqChnp6dnpqemJ2enqCfoJ+eoKCho4ChoZ2en5+foJuZnZiZmpqWl5SXmJSWlJCTjo2NjImJioiKhoeFh4aHhYaflpKQiISCgIGAg4GCgoH9gID/gP7+///7+vb4/YD+gYKGgv7+gP758/zv6+7n5e7t7fF98nvwgnl9eevrd3p5eXd7eXh6enl7e3l9fXx9e31+fn1/gAh8fn5/f359fYR/gIB/f359fXx8gHx9f4KGhIKDiIyHhoeGhYKChIWChYaEh4mIhoaGiImJh4WChIiIjYyPi46Mj4+Mi4iIh4eGiIqKiIyLjIyMjpCOjZCQko+NipGPkIyLiIiGh4qJjImGi4qMj42Nj4uPjJWQjo6Jio2MiYaHjYmOjImHmoSChYOIgIyHh4WIjoKDgoKEioSDiIWHhISDhIaIg4yIh4aIgYB8gH9+goeIi5GNr6SPi4aD//2D/YGBg4aDqI6Si5Whl5GTkPT26dnT0dPEyMvMysPHycO2wLbAxru1srCztLfBv7mtqq2orq6pram4qKCjo6CTi4iKjJGQiI+NiouHiYOEgIL57+31+fn6gYOBgfyA/oP79fj1/YHz+fX1/P3s5uTq4+Xs4fPl2+Hi2d7m39/hlobe3eDg4eHi2t3f3eHk7f3o4tvi6OXk8PDm5vDs6fPi5Ivm4eLi5fX46/H/hImFkIaAgaeHhICB/4SJnoiMi4iPhIaEg4GGhISKi5iTlJeIgIuLkZOQjYeEh5SMioeOhIeGho2NnZeSlZGQkJSUl5eVlJObmZylnqCioqekn6OjoZ+joqSiqayjoZ+lrKSgoqGhpqevqKexsq+yrMOvsLyttK6psbW4trSztK2trKeipKursLG1urWwr7Wwsq6zt7ayr7e2rKqvr625zK2xr67AdLSys7OwuLO4s7Crs6y0r7aura6vtbittbS3vbe5v7aztLe0u7u5tra4ur2+ur3FxcHEzcTFw7+/ysvMwr3Bv73Bw8fJyMXKyMbGz9DU1NDY0M3SyMzNztPb1tvW3dfb3N3c3eHk5enq5ebq5uvx7O/u6+fpP8rNzcbIx8PKzs/V1NbU1dfX2tra3m/dcd5zcXJyc3VzdHV4dnh6eXZ5fHp7enx5eXl6e359gH+AgH9/f4CBgIaCd4WEgYKFhoOCgoKGioeDi4eGiImIiIiFiYiMiouJioiKioyLjYmOjYqNjY6KjY2Ki4yLjoyMi4+NjIqKiYyJiYmHioqNiYuNj4uJjIuOj4+PjZCQjY6Oj42PjY+OjpGPj5KRjpCPkI6PjI2LjIqJi4mKiouMiouKhIyAjYuJi42Ni4qHiYmJiIWEiYuLiImGiIiHhoeHhYeGhoaDhYOChYeEhYWFhoeHhIaGiISJgoaFhISDhYWEhYWHiIaEg4SFhYaHhYWHg4KBgH1/fX+BfX9+foB+fn58enx7enx7enp5dnZ1dYV/gYp6eXd3eHd6d3h6evN6ePF47Ot07O/w8e/v8HjpdHV7d+3vevLv7vfw8ffz8/v39fZ973nsgnh9eu/wenx7e3t8e3p8fHx+fn2AgYCCgIGCgoCCg4CCgoKEgoKDg4GCgIGCgYODhIWEh4SFhYiJh4eHio6Ih4iJiomJi4yIi42LjZCOi4qJio2Ej4CRlJGTkJCNkI2RkI6QjY6Pj46RkpKQlJKPj46Pj46Mj4+Qjo2NlZSVkZGPjpWQkpKUkI6Rj5CSj4+QjY+MmJCNjYqLjo2Kh4eKhoqKiIihiIaGg4WHg4WDhIh/gX+AgIR/fIB/f3t7eXp7fXmAfXx8f3l5dnp5d3l9foCIhLipiYCCe3fm53necG9wc3CJeX14gY2CfHx51djRxsK/wri4vL26tbi+uK65qrK3raanpairsLy9s6SgoZmgn5mdmaSSiYyNi4uMjY+OjYZ+g398fHd7eX1/+e/18vLt6Xd3c3Xkdet86urp5+h34+Tf3+bp3NnV2c/Nz8jhx8XNy8bOzoDLzciTg8HAxMTFxcjAw8bEx8nR2czLxMzPz8fQ0sfI0MnIz8THec7LysnL1NfM0N9ydXOGcm1wlXNzb3HddXWHc3NycXhydnNxbnJwcHNzfnd4gnFwcXd5eHd0cnR9eXh1fHd4d3h8eYV/fH1+f4CEg4SBf358gICFioSGhoWJiICFiYqLiIqKjIuRlY2KiI2SjYqMjo6RkJaOjJSVk5eSpZeWoZOXko6WlpaVlJSXk5eZlZGTlJOVkpOWlJOSmJOVkZaampWRmpmUlZmamKK0l5qamKaZlJOWlpqYnpual5yXnpqgmpiamp6hl52bnZ+cnaGdnJydmp6enZucnqClpAigo6emoaGooISiPKurrKOgpaSjpaipqqmlqKimpq+vsbOus6+vsquxsbC0ubK2tLq2uru9vLu/v77AwLu7v77DycXJysvIx4Crr7GvsrOwtLW1uLS1s7a5uby9ub1euV+8YV9gYGFlZmhqbWtsbmxpbW9ub25wbW5tbWxvbW9ub29wcHFyc3J0dXZ2d3Z6eXd5e3x7eXh5e316dX56en5+fHx7en18gH+Bf4F+gYGDgoR/hYSAhIaFg4iJhIeHhYeFhISIhoWCgjWBg4GDg4OGhYiFh4qMiYeIhoeHhoWDh4aFhoaIh4qHiYiIioiIi4mGhYSFg4aGiIeJiIaIhYSEgIGDgIKAgIGDgoGDhISCgX+Cg4OEgYCCgoF+f3uAgYCBgYB+gYB/gH5/fHp8fHp7eXp8fX57fHt9eX13e3x7fH1+fX1+fn6AfXx5ent8fH17enx5eXp6dnh1eHl0dnV0d3R1dnV1dnRyc3FycnRyc3FxgHp7gnZ0dHN1dHdzdHR1gOV1dex58e/y8vT08fHxee93eYR99Pd89PPy/Pf5/Pj3/Pj3+YH8gPuHgISA/PqAg4SDg4eGhYiIh4mJh4qLiouJio2NjI+RjpCRkZSTk5SVlJeWmJmXmJeYmJmdmZmYnJ6cmpygpZ+eoKCgnZ6gpKCkpaOlp6aioqOkpaimpqaogKytsbCyrrGtr66pqqeoqaqqra6tq66sq6qqq6yqqaysrKmnpa6srausqqmuqKmoqaalqqqrrqmop6OmpLOrqKijo6WjoJ2doJuenJiVwZeWmJaZnJaWkpSbjZCOj5CVj4uOi4qIiYmNj5CMk4+Ni4+Ih4SIhYKBgoOEjIjFsYyEgH157e965nNyc3VzjXx/eH+IgHp7e9bZ0cfDw8m+w8nLx7/Cx8O6x7rEyru2tLC2uLrFxLqvrK+oramfop2unpWZmpuXj4eIiYyMh4iIgoF/gHt+ffLn7PD29/p/gHt67XnygPLv8Ovueejs6evs7NnW0drR09zZ+tTQ19bR1tvVgNTPqZPKyc3NzM7Ry9HU0tPS19vOyMHIysrE0NLFyNHHx9C9xYTKyMbBwMnKwcrccXVvemxmaYtxcW9w3XR1j3BycnJ1b3Jvbm1wbWtubXNxb4VoZ2htcG9saWhqeXFwbHNqa2prbWt5cm5wcXFydnZ4dnV1cnl6fIB7fXx9gn55gH17eXZ4eHt8hIh/f36BhoB9e3x9f36EfXuCgYCCf4aDg5CBhoF/hYiKh4WFhoGChX9/hImKjYuKjYmGhYqFh4SLkZSRjpyQhYGDgX+LnoWKiouYi4eGhIKIgoeFhICKhoyKkImFh4iLjYOIhoiKhoeMhoaIjIuRk5KQkZCOj4uGR4mOj46SmpOSkY6Olp2YkZGVlpSVlpeWlpOWk5GRmpmbnJicmZmdmJqbnaGln6KboZubm56doaipqauqp6Wmpaitqa2rqqenlX4Ef35/fv9//3+ifwV+f39+f4l+An9+hH8Dfn5/jX4Ef35/foR/gn7/f9V/BH5+f36Pf79+h32EfgR9fn1+hX0Bfpl9gn6gfQF+in2MfgF9/37/fo1+AgIEAIDx8O/v7/P09/r7gID/gfn/gIKBg4WFhIOFhYaGiYeGiY2LioqNkJOQk5iak5OSkJCRkI+TlJOTlZmXmZaXlZ6empyamZqdnaCdqZ+fnZubm52fnJ2en6Cdnp+eoaOmp6empaKenJman5WYmJyam6CgoJ2cnp2dmpqcnKCfoqCvnhOZnpyen6CkoaGfn6Ghn56hmZubhp2Am5ycoKCdoJ+hpaSknZ2moKChoZ6dmqCcn5uenqKgoKScnaKlnqKfnZuanZ+dm56boJ6go6KgoaWjnZudmpuZmJubmZqfnZyenZubnZyup6CjoaOfoZ+go56fn6ChoKOkqaWep6SjoaWhoaKgnaCfoJ2bmJebm5ucnZuZnZqZlJSAlZaVlY6NjIyMkI2PjI2NipSajYuLjYqLiYaDg4OBgYOAg4SCgYGA/Pr99fj6gYGCg4aFg4D7+v/++6r7gPPteXp58fR9fXx8hX5+fHt6eXp3e3h8fXl+f4F9fnt/fH98fH1+gIGCgoKAf4V8hoGChYSBhIOEhYWDgYGBhIF/gIGAhYSIiIaLjoaGhYWGiIeEhYqEh4mLi4iLioeJi4mJiImJiIySj4yOjo+PkJGPkIyMiouJjIqNiYqNj4+UkJCPj5GTj42PkpCQmlqNiIqNjZGMjIuLjo6NjY+Pj5GNkJOMlo6Pio6Ni4yNkJGVjZOKh4aIiIqKlYyJiomGhYmFh4OAhIOGioKGhomIiIpsiYeHg4SJgYKH7IiLjo+bl6WjjoX/gvz7+/6FgoSM+oGNkpieoJOJhYX519nO0szQztGOf8jRzMnAu7qzyr+6ta2vsrG1vLu4p6mlo6+sqbKoqqGhoq+5mZONh4ycq5eSkIuJhoeEg/3//fR+enmAgISFgf6A/PR9ffDu8v/8//vz6uft+9/2++rl6ero6+fl3ebf2+Hb5u3n5dvc4dXb3dfc3+Lh4+fw8urn8ePx8vLz7PP45+Lr7ezm5+f465Hw8u7w8vqA+/f2gfv4goSDhIT5g4uJiImIiImJh4eLjomIkI2LkI6HhoyNjJKRk4mckIiNjI9Hi4iKhYeIk5mWkY6RjZOPk52rl5mWoJueoKCgpJ+goqOoqaimp6elpailq6inp6ymq6ekrq+rrrGurrKtsrGvuLG2r72usLCEswG1hLSAsqmrsLWvubG8tra2uLnVtr22tbOusrmzs7nBvrS1sLm1tbm5t7q2tbeytLG7sbaysK+xrbGvr7Wtr6+zsbW1tra4urm7vcC+tri8usDBv8DFwsDExcvFycjIxcDAwru92cDEwMLFw8nLz83HyM2y1M7Jy9DP1tra2tji4N7W2N0L3tza2NnZ4Onq6OmE5wju8vTy7Ozt8C7LzMrKyc3MztHRbG3abtbXa21sbXBxcHBycnN0dnR0dnh3dXR3ent6fYCBfX19hHwYfYKDgoKEh4SGgoGAioWAgoGAgoWFiYaShIiAiYmLjIiHiYuMiouNioyMjYyLi4qLiouMj5OOj46Rj46RkZCMi42Nj42MjYyOjY+Nno2Kjo6Njo2Qjo6MjZCRkJCUjo+PkZOSlJSTkZKRkpKOkI+QkpOVj5CVkZGSkpCQjZKMjouOjJCOjZKKi46QjJCNjoyLjZCOjI6MjouJi4mAiIqOjYqIioiJh4iLiomIi4iHh4eGhoeHm5CKi4mKhoiIiY2JiYiKiYaIiI2Jg4mGhoWIhIWGhYWIiIqIhoSDhYSDhIWDgoWCgn9/gIOEgn59fHt7f3x9fHx7eYKGfnt6e3l6enp5eXp5enx6fHx6eHh57vD28vT1e3l3d3d4d3c/6+3v7+6Q0X3v73t8e/X0fHl4eYF8fnx7fHx+fH99foB8f3+CfoB9gYCFg4KDhISFhYSGhoaKhIyFhYaEgoWDhIaAhYiJi4mHiIuMiIqJh5CWiouKiouOjYyNkY2Oj5GQjZCQjY+TkZGTk5ORkpaTkZGRk5KSlZKSkZGQk5KXlpmTkJOTkZaSkJGRk5WSkpSWlJSjaZWQk5aXnJiXlZOUkpKSkZOSko2PkYyWjY+KjYuKiouMjpGLkoyKiIeFh4eSiIaAhIWDgIWBhIGBf4GDen59gX9+gG19fH16e395eX7PfH1+gIyJn5yDeel35eXk5XZ0dnzhc3x+g4iPf3d0dNzDyL/Cuby5vYBzusK9urKxtKrBtaynoqWrq66zsrCdoJyapqGeppyelI6PlqCNjYeChY2ZhoOCgH58fX6A/v3783qAdnN1d3h6eO7s5HZ24uLo8ujn49/c3+Hp0eTj0tDN0NHQ0tLL1NHIzsrM18/KxMfPwsvNxsfKy8nLztPTzMbSxNDPz8/K0tvKxszQzsjKzN7Te9PU0tDV3XLe2tRy1thxdXNzc9txdnRydXJzdXV0c3d5cnJ3c3N0c21ucXJxdnSAd3GTd3F4eHh5eXx4eXh+goJ+fH99gH6Ah5GBgn+Eg4WGhYaNhYaIh4qMjIqLjI2NkZCVkZCPk46Rjo6VlZKSlJGSlpSZmJihmp2XnJOUlZaUlJOWl5eanp6WlpWXj5aOmpiXl5iXppmimpqXk5malpWYn6CYm5ilm5iZmZiYlphvm5qfnqScnZuam52anZ2boJicm6CeoZ+gn6GjoaGhoqKbnaKfpaaipKikoqWkqqWnp6inpaeqo6THpqqkpquoqquuraqts5y/sa+vs7Cztre3tr27ubS1u729vLy/vsHEw8LFw8PBwMLFyMrFycrMgLS2trm4ubi3t7RbWbRbs7heYV9gYmJgX2JhYWJlY2Voa2tqaWxubmxucnFubm5tbW5tbHBxcXBydXR3dHRzi4J2eXh4e359f32Ifn17ent5fHt3eHt+gH6AgX6BgYKBgoKCg4KDgoWKgYKBhYKCiIiJiYqMjY2Kh4iGiIeKh5iHEoOHhoiIh4uHh4aHio2NjZCJiYSIgImJiomJiYuLhoeGiIuMj4mKkYqJiIiHh4WMh4mHiYeJhoWKgoSHiYSJhoeFhIeIhYOFg4aEhIaEgoKEg39+f3+Afn+Bf31+gH5+gH9+fn99j4Z+gH6AfX98foF8fXt9fnt+f4SAe4F+fn2AfX1+fHt9fH99enl5enp7fH18e357R3p2dnd6enp2dXRzc3h1dnR2dnR7gXl3d3h2d3Z0c3Z3dXV4dnl7eXt8fPf2+/Tz83p4d3d6fXx89PT39/aQzYX+/4ODgP3+hIGAi4WEgoGBgYWDiIiMjoqNjY+Ji4eLiY2Mi4yNjZCSk5WUlZmRnJaWmpiXm5qdnp2cmpyeoJ6amZuem5+fnKWpoqOgoKKlpaOmrKaoqKuqpqqppqerqaqrra6tr7Wxrq+ur66vsa+xr6+trqyvrLCqqa2wrrOurKurra6qqautqauAyJOuqayvr7Stq6qqrq2sq6moqKmkqKultKamoaWjoKChoaOkmqGamJeZl5qZp5aVk5OTkJSQk5CPj5KYjJCQlJKQkoWLiouIio+GhYbSgYGEhpOQqaGGfe977e3q63l3eH7ic3x+gYaUfHZ0deDIzMPIxMjHzZmBwsrDwbu7wLqA1ce/urW4wLq5vry4qKulpK6nn6ecopian6eylo+HgYWQqY+IioOBf359fPT09/iBgYGFg4SDf/f594KC+vX1/fLx6uHe29rkzeTo2tbX2t3a29vS29fP1M3S4dbUzczQwMvLyM3QzcjExMzPx8bRwM3KzdDM2OLPx83OzsrIyNmAyXfGxMPGythy39vUdNHVb3Fwa2/Tb3ZzcXNvb29wb21ydW9tcm5tcHBqaG1tbG9ub2ilb2lwcHFvbm1pamlwc3JubXFvc3BxeYlydXJ7eXx7e3uCenx+foGBfnt5enl7f4CEgoODhoGFgH6Gh4KDg39/g4GFhoSMhYeCiICDg4iAiIqIiomEg4aHgoaKjYeOhY+Kh4aHh5CMnY6OioaJjIeHipKTiomImYuJi4qIiYWEiISHh4+IjIqIh4iFiIiIi4aHhYiGh4aHhomMi4+SlJaPkZaQk5GMiY2KiJCSmpaXl5aTkZOVkZSump6YlpeUl5eZl5KSlnuYl5WWm5eYmpwKnJ2nqKefnaGhoISdEaSqq6isqaikpqmqrK2oq66yin4Gf39+f35+/3//f7J/hn6If4V+Cn9/f35+f39/fn7/f4Z/AYDBfwGAlH8Cfn+EfoR/AX6Kf4l+gn+zfoR9iH4FfX19fn7FfQF+hn0Hfn19fX59fYV+AX3/fuJ+AX+ofgICBACA/Pn5gfn3+fb79/j79/j49f+BiISGiYaFhoeJh4mKjI2Qj4+Qj5CTlJWXlZeXlpeXl5aUk5eVmqGdm5ybm52joKGgoKOgn5+joZ+fn52gnaKfo6SmoaSko6OhoKChrLGlpKiipKSgop2dnJ+foqGooKOgnqCenJubnqKioqGitZ5Co6Oio6SkpqWmoqWkpKOipaOjn6GhoZ6hoqCenaGjo6WkpaajqKWhpKKjoaSypaGjoLSmp6akqqalqqSmq6alpKGkhKKAoKKhpaKipqSjo6KipKChpqKdoKCfnZ6foJ+bn6GeoqOgqKSjp6ekoaCkoKClo6KjpaamqKakpayoo6OioKOfoKWjoJ6in5+hn5+cmpmWl5ualpeSlZeVk5CRkZCSkpKOj4qMj4+Oj4qNjYuIiYqIiYmMiIaEhoWDhIKBgIGAgYCAhIKEhYSFhYKBgPr8/YCBgX99fH9/f35/fHt+fn9+fX2Af39+gH59fn98gICBg4GCgoOChIB/gIB/gn2DgYqAhYeGhYaFg4aHhoiJiIeIiomIi4aLi4mGjIqIhoeJiYuKhoOJiIWIiouOjo+LjIePjIuLjo+PjpCQkY+RkJKSkY+Aj4+Mj4yNi4yNjZCRko+Zj5KSl5iil5KTmZWTkpORkIyNk4+NjpCQkJKQkpGOkZaUk5aVkZSUj4+Mj5CRk5SMkpGNj46Mj5STjo2Rko+LjImIiIaMh4iKi46Ojo2Jh4WGg4iDgYWBgYaJjIyMj5eVkpOG+fv98/z/+vn9hIL9ho2AioOGh4Px9vju3tLKy8nFydGB28jLx8DGvr+4xbm2uLTAsbK0r7axqKumn6anqq+wpKCanJqgm5SRlpubn5SMjo6LioqGh/F77Xh6gIGHhoWEgoGB+/Px7PDrh/jx8+zp6eXl3ufl3t/v6ert5eDi39zf5OXq29rb29Te3PbZ1tOA2+Dl6vTt9Pnn5/Dw5vL58oDy++vo8ffz7eb3+Pj17O/x5e749vX68/KCsoWF8PqCg4mPkY2PioeJiYmMjomLkoaFhJSMiY+Ul5iZlJaKj5GTkY+LjYqFiIqXmZ+TlqCWnZmclpegmpnYnqCln6GqpaKpoqeioaajraitrKmrqbCAqrS3tq6tr6+8tr24uLq2t7W0sLK0trqzsrW1sbS1tru4vLm/vry1vLe5jMTAu8LBu/C3tbm4t7SzsbW5ube0s7u8tre8wcLBxb24ur63t7y7ubmzsK6utLa5vbq1tLq4u7u5ub+7wb69xr2/wcDGvsK9xMPGxcbQxMjAvsLAxMY6xsXLz8vJyczBxcPMz83Q1djV1tnW1dvS29rX2eTc4uHk5unk6uTd2+Li4+bo6Orr9enu8/jz8vPz/YDV0tBsztDU1NnZ2NvX1dbT2G1ybnBycHBycnRzdXV3eHl5eHl6fH1/fn99fn9/f4GCgoKBhYKGjYaEhIODhYmFh4aGiIeFiI6MjIuLio2Lj4yOjpGMjo+OjY2OjY+crZKQm5CSlJGTkJGRkpOVk5aRk5CRk5KRkZCRk5GQj5Clj4CSk5KSkpGSkpSRkpOTkpKUk5SQk5SUk5eXl5WUlZaUlpOUk5OZlZGUkpSSlKWVkJOQnpOTkZCak5GWkJGTkY+PjpGPkJKRkY+PkYyMj46NjoyMjomJj4yJjY2Ni42NjYuGiYuIi4uJkYyLjY2LioqMiomMiYmIiYqJi4mIiY+NiICIhoaHhoiMi4mIioeFh4aHhoaFgoGCgX+Bf4OFgoB8fH9/gICAfX17e35+fn17fX59ent8e3t7f3t8e319e3x7ent9fX18fXp6eXh7e3t8e/Hz8Xl5e3p6e319fXt7eXd8fn59fn6Af39+gX9+gIJ/goGChIOCg4SDhYODhYaFiICEiomRiIuLiYaIh4aIiIaHiIeGio2OjI2Ijo2Ni46NkI6Pj42QkI6Nk5OQkJGQkZGRjpCNlZOSk5SVlZKUlZaVlpaVlpWSk5SSlZWXl5iYl5iYmJSdk5STlpajl5OWnJmYlZeWlpSVm5qYmJqanZ+ZmZaUlZiUkZKRj5OVkpKPkC2PkZGQi4+OjI2KhoeJiYeFh4eGg4aGhYWChoGAf3+BgH9/e3p5e3mAfHt/e3mEfIB7foaFgoR44OLl3OTm4uHjd3blen97c3Z2c9TZ3dfKwr2/vri7wHnMvcC8trixsqrBrKWkoqyip6ypr6acnpmTnJmdoqObm5WUkJaPiYmJjIuOiYOCg4CBg4aH9XvtdnV4dXh6e318e3vw7enl5+CA5N/i4d3d29zX39nPztXR1IDV1dPU0c3M0s/Qx8XFxsDJyunLycbP0tTV29HT1sfJ0tbM0dbNa8rTysPQ1c7MzN/e2NTNz9HN1ODg3dvT0nCidXXW3HBucnV4dnl2c3RycnJ0cHN6cHJvenNwcnV4fnp2enF0d3t5eXl+fXd5e4ODiH5/h36EgoWCgoiCgKiBg4CHg4WOioiQi5CNjZGNlZCUlJKTkZWQmJiYkpOVlJ6YnpmYm5ucm5yYm5qcn5eVl5SUl5aZnpuemp2bmpWblZZrnJqVm5uZxpubn52dmpmVmp2enJqZn6CcnJ+fnp6hnJudoZ2eoKGfoZydmpudnZ+jnpuboJ+fn56dop+koqCooE+ipaapo6ehp6WmpqWto6ikpamoqquqqKqsqKiorKWsqrCysrS1t7S0tbKutK+1tra2vrm7ubu9v7zDv7q6wMDBw8bFyMjNwsXIzs3Lzs/XgLa1t2K4ur29v725u7a2uLa8YGRgYmRhYWNiZGFjZGdpbGxtbW5vcXFxcm9wcXFxcnJycXF1c3eAeHZ2dXV1e3d4eHp9fXx/g4B+fXx6fXp+e3x/gn2AgoGCgYCAgJKrgoOJhIeIhYeGhYSGh4eHjYaKiYqLi4mJh4qMi4qJiZmGgImKiouLioyLjImLi42NjZCOjoqLiouIjI6Oi4uMi4mLiYuMjJKPjI+MjImJmouGiomVjY2LipOMiY2Ii46KiYmHioiJiomIiIeJhYSIhoSEg4SHg4OIhYGDgoF+gIGBgH1/gH2BgYCJg4OFg4KAfoJ/foJ/fn9/gICBf35+gH99gH5/fn99foB+fHt9fHt+fX58enp4eHt7eHp3enx6eXZ2d3d5eXl2d3R0d3h3eHR2eHd1dnd4eXh8eXl6fXx9fnx7ent6eXl7ent7e35/fX599vz+gIOGg4GBg4KCgYKBgIWGhoSDgoSEiIqNjo6PkI2Pjo+QjY6Njo2PjI2PkJCVII+VlJ2SlZiYl5qamp6fn6GhoKCipaWipJ+ko6SipKKmhKUbqKilpKmppaaoqKytr6qsp7Kvrq+ys7Sxs7KyhLGAs7KwsrOws7Gxr6+vrrGztLC5sLGvs7PAsayttLCvr7GwsK6utbGurrKxtriwsK2oqa2rqqyqqKqrqKilpaSkpKWdoaGfoJ2Zm56emJWZmJeVmJiXlpSYlJOTkpOTkZGNjIqKho2Ih42Hg4SEg4KDhY2MiYl97O7x6/P07+zreHaA5Hd8eXF0dXPZ3+Xf0snEyMjDy9KP4crLw7q9tLSwyre1uLjCtbq4sraup6uooqikp6qqoZ+ZnJmhmZSQkJOQlY6IjIqIiIaFh/eA+4CBh4WGg4F/fnx9+vf29PXqhezn6+Xj3NfW0NfW09Da2Nra1tPOzcjJ0tPa0dPU1MzU0vCAz83J0tHRz9LHy9HDx9DUytPZzmvGy8DBy9TQyMLY1s/JwsXKx8/a3NjXzc1uoHBxy9Fsam9zdHF0b2xvb29xc25xdmtqZ3JsaW1xdHh3c3ZtcHR4dnNxc3BpamtycndubnRvdnN2cnN6dHSseHp+enqCfHyCfYB9e355gXuAgICAgYCGgYiJiICAgYCMg4mDgoSDhoaIhYeIio6GhYeIio2JiouGiYWLi4yJj4uMVY+MhoyOi6WKjI2LjImJio2Sj42JiI+Rjo2Pjo6MkImGiY2IiY2Ojo6Jh4OFiImMkI6Jh42LjI2LjJKQlJSTmpOVl5ibkZGJioeKio6ZkJaRkJI9kJOVlpaanpqam5uUmZWal5WWmZycnqGemZ2Wnp+cnqmkpqWoqKilqqSfn6Slpamrra6ur6anqK2rqaustQR+fn5/jX7/f/9/wX+Dfv9/5n+JfgN/f36Hf4x+AX+0fgN9fn2LfoZ9AX61fQF+mH2EfoJ9+34Bf/9+kH4CAgQASPn4+fX1+Pf0+Pn8goGCgYaTi4aJiYeGiYyOkIyNi4uOjpCVlZaXm5iZnJuYmJyampeWl5WcmqChqJ6gpKujp6iloqWirKCooYSjSKSmqaSjo6SjpqKoqKmoqqmoqaSoqaakp6ajoKOgn5+hpaano6iloqOinaCipKWmpKWnpampqaipp6aop6KhoZ+fo6eloqOiooSjYKKioJ+kpaalpqaopaWnpaWmqayip6Wkp7Kop6iipaaqqqWuqKivqaeko6Slpaamqaiop6ioqaekqaelpaeno6Klo6KgnqKjpKKfo6Kjo6SmpqSmpaejpKSkoqajpqamqYSogKmmp6KkpaOmoqWooqSkpaSio6aln5+cnJ2Yn5+cmpmbmpaSlZSXlZKSj4+RkIyOkJCPjo2RjIqPjYqLiImLi4iIiYWHhYmDgYKChIaIio+DhoGAgvuDg4KCgYSAgYGAf4CBfYJ/goGAgX+EgoCAfX5+gYKBg4WEh4SGh4SGh4ODAX+EgoCBhoWGg4GGh4qIhYeGioqKjIiLi42KjIyMiomNjIqIiouLiI6OkY+MiY2Mi42Mj4+OkI+Oj4+NjpSUlZWXlJOTlJGWmZWUkZGSjpSPjpCWkpWXk5ORkpOUmJublJOUmZuZlJqWmZWQjY2OkJCUlJOSk5SdlZKWl5uZmZSSkY+Qk4CSk5SgkpKOm46Rj5GWlJGSkZKQjYyLiouNjI2KioyMj4+Rj46NiYuKg/qBhIWHhoeOjpSWkouIgYL8+/z79vOB9YGCjJCOh4OCgffq5ujh2s3TzNnMzdXNxsPj3tjGwbzAuba6tLy2uL67rKynsK2sr6qrtqihnpWVmZqdnZ2jnICTl5CKkI2ZjIeHhYN/joOCg4OLjYKIh4KKgPqB+Pz3+/eB7+vn5+Pl5uLn5OTn4OXf29je3ePq6eXm3+Lf3OHk4uLk7Ofa4Obz5eDt5e3y8fPn8/7z/4H/+fDu+fPq8frt7Pfu5/b39fXr9oKD+oGEhIL7+v+ChIaIiIeIh4aKjICPiYyOnouGjIuJiIqOjpKUkZSSk5OYkI6GjoeIiYmZlJiYmp2cnZWZlq6cop2gnaOgpKGno6ilra2oo6mttqutrrGnq6+rqbGzsq+xrrS9wb67t7e9t7bAuLe4tLy4u7q2tri1wMG9wr7Bvr2+u7q+w8LBwcO/wMC8ub/Dub25vH+5wLe4s7i3ubjGv8PExb27vcG/w8G9wLq8vLvAwsDCwcG9urq7wMG9wcTF1MXExMHC37/CwMXGxcPMzMTGxszMxcbHxsLJxMjGys3LycvIw8zQ1NbX2NHT0dHi2Nfe2d7g4N/e/eDi5eLf3uDg4ufn7Orv4uzs7O719vD58vf+gM3Pz87P1tjW29zecnBubXB9dXFzdHNydXd4enh4d3h6eXt9fX9/goCBgoJ/gIOBg4GChIKHhYmIjoSHiZCJjY2KiIuJkoiTjI2MjIyNj5CMjY6PkJCMkJCRkpWVlZaRk5OSkpSVlJKUlJOSlJiXlpOWlJOUlZKTlJWUk5KRlJKVJpaWlZWUlJWWlJSUlZWXmpmVlpSUl5iYmpmZlpWYmZiXmJiZl5iYhJVNl5CVlJSXnpeXl4+Uk5WWkpiSkZqTk5CQkpOSkZGTkpKRkpKUkY2TkI6Oj46MjJGQkpCOkJGRjYuNjIuLjI6OjY+Nj4yNjY6MjouLioqEixaMjIuLiImKiYqIi4yIi4qKiYaHiomHhIiAg4mHhIKChIOAfX+AhIKAgH5+gIB+f4KBgH59gX57f357fXx+gYF/fn56fHyAfHt8fH1+fX6Cen18fH/1f357e3p9e319fn9+f3yAf4GAgH99goB/gH+AgIODgYOEg4WEhYaFh4iGiIaJi4qIh4yLjYqIjIuNjIyOi46MjIuHi4yAjIyOj5GPjpKPkJGSkZCLkJGTkpKRlZWTlJOVlJKVlZWXmJWWmpmXl5qXlpealZqgmZiXmJqYnJybm6GbnJ2YmZiYmJmcn6GamZmcnZiUmpidnZyampuamZubmZmZmqGYlpeUlpSTkZKSk5SVlJOQmJGRjpaLjIqKjI2JiYmIhoaAhIWGhoeEhICBg4GCgoKAf399gIB66nl6enx5en9+goSBenl0dOLg3tzb2nXjeHiBgn93dHNy483LzsjDucG8ycDCycC5tM7MybewrLCno6WgpKCkqKmfoZ6lo6Cjn5+qoJyalJGRj4uKio+OiImHg4aEkYR+gX99fId6dnR0eX50eX9/en1033Dc4uLo53jg4djb2dvd2dnV1tPP1NDU09PQz9DOyMjHx8XDxsfFxMjTz8jO1eTTz9bO29XR18vR4NTZbtfSyc3X0M3Q2NHQ187H1tnX2dTXb27QbnJycdzb2W5ucHJ0dXVzcHFyc29ydoVyb3WEcoBzc3d3dHd0dXd/eXp4gXt7fHqDfoGAgISDhH+CgZeFh4GEgouIjoyQjpGNk5KOio2QmJCRlJiSlpqXlJmVlZWWlZmfoqCdmpugnJyjnJydmZ6am5uZmZuYnp6coZ2enJ2fnJucnp2cn6KhoqKfnqGon6GeoZ+knaCcoJ2enqieo3OjpJ+goKWkpqShop6fn5ugoaCgoqGgnp6foaGen6GjrqOkpaWlvKSno6enpaSsrKSlpautqausrKmwqqurqauqqKyvr7Kys7Kvs7GwsLC6s7O2tLi8vby71Ly+wcC/wMHBxMbFyMfLwcjIysrQ1M/Tzc/TgLS3ube5u7u5u7u5X11cW19qZWFkZWRjZmlpa2hpaGlqaWtvb3J0dnR0dHJub3NydHNzdHJ5d3t8gnh6fIB7f358e318hnyFgIOCgICAgYF+fn1/foB+goSEhYaHh4mGiYqJiIuLioiMiomIiYyLjImMjYyMjouMjY6NjIuKi4qMgI2MjI2MjI6OjI2OjY+SlJSQkY+Oj5CQkI+PjY6QkI+MjY2PjpCRjo6Oj5CHjYuKjZmOjo6Hi4yNjoySjIySjY2KiYqMi4uLjYuLiYmIh4WBh4aFh4mJhoWIhoaDgoSEhIOAg4OEg4WGhIKDgoSBgYGCgYOBgoKBgoGAf4CAfn98gH2Af4J/gYN+gICBgH5/gYB9fHx9fHd+fnt7enx7end6eXt7enl3dnh4dnh5eXl4d316d3t6eXp5fH9/fX1+en18gHx7fH1+gIGDiX6Bfn+B+IOEgoWFh4KDgoGBgoSCiIaHhoSDgoiGiIuLj4+SkpCQkZCSj5GSj5KTkZORk5WVgJWUmpmbmJWamp2cm52coaGio6Cio6SjpaWmo6KnpKSjpaaloqenqqmpp6uqqqyssLCus7GxtLSxsra0tbS3tLOztrG3wLW1srK0sLSxr7G4tLi7trWysa+ws7q9sLCwtLayrrSytrWzsLCysrK2trSysLC3r62vrrGurKaopqangKinpqa6paagqJydnJygnZmZmZqamZeXmJiZlpWSk5WTlZSUkY+Oi42Nhv6Cg4OEgoGIhoqMiIB/e3zz9Pbx7uh24HV0e318dHJyc/HY2NvVzcHLx9XMz9TLw8Dj3trAuLW4srS5tru2ubu5rK2qtLKwsq2rs6ejoZuXmZuZmZeegJqOlI6JkIqXjIeKiIeCkIWBgICDh32BgHqBeex57vby9/d/7+ri39rc3tra1c/Nyc7M0dDOy8vOzs3Q0dfV1NjW09HU3NfLzdDWxr3Gv8vLz9bL1t/Q1mvNyMDA0MzGz9nMy8/HwdHPzs3IzmxszWxtbWrQ0NBoaGhpa2xtbGtugG9zbXBzg21qb2xraWpsbXBxbnJwcnR6c3RsdGtqamhxbG9ub3J0dXJ2dIZ5fXl9fIN/gn6BfX97goF7en19hn5+gIV/hIeEgIWEgoCBfYGIioeFgoWMiYmSiYiHgomHioyKi4uFiIeEiIeMjIyOjIqMj4+Mi46LjI6LiY+Yj5OOgJGQlY2RjpOSkpGYjZCMjYiJipCPk5KQkYyNjouQk5KRkZOOjY2Nj4+LjY+So5STk5OVsJOXkpSVkY2VlI+QkpmZk5OVlJKalpmampqXkZSTkZmZnZ2anZmZmZqlnpyhnqKko6Oiu6WnqaimpaWlqayssbG1qrCrqamur6yxrLG3i37/f/9/x38Bfv9/2n8Bfo9/hn4Cf36Jf9B+An1+hX0BfrR9AX6UfQN+fn2EfoN9/37/fox+AgIEAHT5gP369Pr7+fr+hICBg4OGi5OKi4qIjY+Nk5GTko+RlJSTlZeWmJiXl5mblZaen5qam5ydn6GhpKSmqKemp6epp6akpKSjoqWnpaimqKmpqKilp6moqquwrqytraysrquqq6ysqaikpaKkpqapq6urp6apqISkgKWnqKWoqKaprK2tsqiqq6ajoJ+hoqCkpaijpqakp6app6ampqmopqinqKuopqenqKmp16eqq6irrKmopaWkpKqqq62tra6sq6eqqKipraqrrK6wr6+oqKyrq66oqaqrqKmnqaWmpaSlp6WlpqurpqasqKqura2trKuqqamrqaiqgKyrqqmrqqioq6amqaeprKurpqmjpqamp6SjoaGeoaGjoZ6dmZyknJuUlpaVkZaVlpOTkJSVk5SSkpCPi4uNiYiFioqMi4mLiIiJh4yFiIiIi4uOiIiGgoWCgX+AgYF+gIWCg4WEgX+CfIOAf31/gIGAgH6Cg4KDg4WHhoqGiYeGgIWFhoCFhoWGhoaJiYeHhoqLi4yJh4aJiI2NjZWQjI2MkIyKio2Kjo6TkJqVk5KPj46QiouRkZKSk5WSlZSXlJeZmZeZl5WTlpWTk5iblpWVko+OoJCPkpWUk5KTk5KTlZeZn5iamJqZm6SinZyboZqfmpmYl5iYaZeZ25iZnJibgJeXmZqkmJiYlJKWlXCSkZGMjJKSkZeYopOTkZOWlYuKjJKskoyJjZKVlpWZkY6OO4WEg4SDhIOBhIeMkpiSkIyFhP3y8Onk5/b/h4WLi4uTi/vz4ubh3tTY2NXX1tXU327NyNHsxcjIxsXHxr28yLe1tLmsrayorbWqpq+rsKukgKGinZyem56fn5aWlZGPko+Nj467hYeGhIiCh4j/gIyBg4GDgvj0+vj98fju5tvq4ebh4+Tj5+7n34ni3tzb3dje4t7f3+fl5N/mkePj3+Dq4eXs5ejq7e/r7Yjygfb5/YT9//by+Pz8/fTq9fX++vb58/n7+vn5hIeGiYaGhoiJgIT+ioyHiYmNkI+MjYuJi4+Pio6QlJCTko+MkaSUmZWSk5ablpGQkpeUlZWWlJmbmZyeppyhnZylp6qksLKutq2qq7CnqqyqqKipsrGxr7a4uLm8ur+6vcG8xr+9vLvAvMK9uLu8ur65vsHAvbm8wcXHw8K+v77AwsTJyMbDysbDgL+7vsfMwb+/wr6+wMDI08C7wr6/wcjNysXFvsm/vMO+vby8u76/wte8vb/Bwb3HvsXFyMfGxMbCwsbHx8jExcfIx8vTztHMzc3Ky8nLz9HMz83W0s3Q0tDX1NrX1tna29rd4OHb5Nzd4tjZ3+ne4+Di5Onk5Ofm6O3w8u3p7+/uCPb3/Pv8+Pn3gNRu2djU293c3d90cHBvb3BzfHR1dXR3eXd7enx7eXp9fn19f35/f4CAgYSAf4aIhoaHh4eJiYiKiYmLioyMjI6NjIyNjYuMjo+OkI+QkZGRk5CSlZOTlJeWlZeXmJmamJeXmZmXl5aXlpmYl5iXl5iWlZqal5iWlpaXlpWVlZOVgJeYmZ+WmJuYl5aVmJiXmZqcmJmamZuZoJuam5ydnZydm52dm5qZmJiZmcmXmpuYm5yZmZaWlZOYl5aXlZWWlJSRlJOSkpSRlJSZnJqblZSWlpaZk5OTlJGSkZWSk5OSkpOQjo+SkIyMkI2OkI+Pj5CRj5CQkY6Pj5GQjo6Qj42OgJCMjY+Njo+OjYmMiIuMjY6KiYiJiImKioqIiIWHjoWDgIKCg4KEg4SCg4OIhYKCgYOBgX1+gX5+fX9+f319f31+f4CCfH9+fYB/g3+AgH6Cf4B+f4CAfX2Bfn+BgH9+goCHg4OBg4OEgoKBhYWEhYSEhoSJhomIiYmIioaMjo6NgIyMjo6LjIuOj4+RkZCPkIyQjIySkY+RkJORkJCVkZSTl5WdlpWVkpOUl5OTl5iZl5qalpqam5mdnp2cnJubmZ2cmpmdo5qbnp2dnrOhnZ6gnJqam5ycm5ydnJ+anJmbm5ukoZqam6Gdo6Cdm5qbm3ianLGampyYnJaWlZWflJWWgJOSlpaMkpKSj4+SkIuQj6eKioiKjYyCg4WKmouEf4KDhYWChoB/glh8enp8eXp6d3l7foGGgH17dnXj3NnU0NLg6Hl3e3l4fnnc18vNyce+w8PAwsDCwcxwvbi90bCxr62sq62kpa2ioJ6lnJ6gm5+jmZeenKGcmJSRjY+Mi5KUgJeLiYeFhIaEgYB+sHd6enp8d3x99XuHeHhzdHPc3uPl6N/o4d7T49bb2drb2dbZ0cqP2NnV1dPP0M3JyMXIxcTCynvNz8vO083R1tHPz87QztKG2nLX2NZs0NLQ0dPb3NvUzdbT2trT1tLY2NrZ2HB3dHRxb21vcG/cd3hzc29xgHR0cnV0cnZ6e3N1dnl1eHd0cHWKeHt5eXl/hoN+fX+DgIGAgoGGhoWGiI6Fh4KAhoiKhIyPjpaTkpShk5aXlZGRkpiWlZKWmZiYnJqfm56fnKSenZuan5yhnZuenpyhm5+ho6KfoKGhoZ+ioJ+fnqGgoKCfoKempaKgo6esoZ6ggKOgnqChrLSgnqSfoaGmp6akop+uoqGopKOioZ+enp+xm56hpaWhqJ+lpKWmpqSmpaeqrq6tqKeoqKiss6+xrKytrK2rrbCyra2rsK6srq+vtrOzr7CytraytrW1sbm1usG8vb/Iv8C/wsPHxMHEwsPGyMbGw8fKy9DQ1dTT0NHRgLReu7q1t7S0tLZfXF1dXV9iaGJiYmFlZWRpaGlpZ2lsbGtsbm9ycnFxcXJtbnR2dXV2dnZ4eXd7e3t8fHx9fX9+fn5/gH9+gIOBg4CCgoGBgX6Bg4CBgoaEhYeJi42PjIyMjo2KiomLiYuMjI6Njo6NjZGQjo6NjIyNjYuMjIuOgJGRkpaPkpWSkZGRk5WSlJWXk5WVk5WSlpKSkZKTkY+PjY6QjY6QkJGRka+Mj5CMkJOQjoyMi4uQj46Pjo6QkJCNkZCPkJGNjo2OjoyLhYWHh4iMh4iKjImKiYuIiIeFhYWEhISJiIWFiYWFiIaGhYWEhIWFhoWEhYaCgH9/f31+gIF+f4KAgoSFhIGEf4GBgYJ/fnx9e35/f399fXl+iICAfH59e3d6eXx6e3p9fXp5e359fXl6fnt6eXx7fnx8gH5/gICDfYGBfYGAhYGDg4CGhIODhIWHg4OIhYaIhoWDiYWMiYiFh4eJiYuLkZORk5KRlJKWk5WUlZSUlpCVlpiYgJiZnJyanJygoqKkoqCeoZ+koqKopqOmpaikpKOopKimq6epq6urqqussKuqr66vr7Kzsba0t7O2ubi3ubi4trm4tbS6xLe5uri2tsq4tbe6t7W2trW1tLW1tru0uLa4ubnCwLi4uL+6wLq3tLO0tLCyscGwsbSxtq+vrq24rKysgKemqKjXpaanoZ+hoJ6kpcKfn52foqOXl5mdvJ6UkJSVl5aTmZKQkpWIhYOFg4WFgoKEhoqPh4SCfX318e7o5OLp63p2enh4fXrk4tzh29nOzs7Lzc3Q0NyCy8fP9r++vLu6usG4ucOzsayyqayvrLC4rKixrbCpop2bmJuZmJmbgJuPj4yKjI+OjIyKs4WIhYSEe4GA9nyJe3x5ennr6PLz+e/48OfY7dnc19PV1NPa19Kq3tvSz8zHyc7NzNHX1NLM04XX19PT1MnIzMHFyc3R0tia5nXR19ht0tbLys/Kzc/KwsnO2NfNz8XKyc3OzWxvcHBsa2pramjIbm5qampugHBwbnFwb3F0dG1xcnRxdHNvbHCKdXd1cnJ2e3dwb3Bzb29ucHB1dnd5eoF4enZ2fYCCfYOEgIV+en2Le4CBgYCBg4mJiIWJioiGiYaKhYiIho2Jh4iJj4ySjIiIiIeLhYuPj4yIiYuNjouOjIyNj5CPkI+Ni5KQjYyJjJOaj4+SKpWUk5WWp6yTkJSNjY2QkpOSk4+dj4yTkY+PkY6OkJCfio2MkZKOlo2RkISSUpSTl5ucnJuTkZKRkJWcmJmUlZaWmJeXmpuWlpWbmZaWl5WcnaCen56enJmdn6Kep6KjqaGipa+mp6Wmpqqnp6uqrLC1s7Ows7KwtLK1tLKxsbACfn+Ifv9//3//f+h/AYCUfwGAon8BgZJ/iH6Hf49+AX+7fgF9h36VfQF+kH0Bfo99B359fn19fX6WfYp+AX3/fv9+iH4CAgQAgP/9/oGA/oT/goKChIOFiYqNiYyNiYyQk5KTmJaYm5mXlZqbmpybl5iYnJybnaKhoKGgoaCgoaSpq6ipqaqpp6etraamsqekqqqopqmqqampp62sq62wsKywrbCyr7GurrC0srGxrqqsq6uvsbGytLKxsKuvqamqqqerraqsq6urgK6trKypqqippqanp6empaaopqqpqqurrKyurKmrq6qqqKqtrKqtra6urK+rq6qqra6tqq2uqqutr6yrrq2urq6srK6tr66trLGurauqqKyrq6utrKurramqramrrajAq6ysra2sqa6qq6mnqKaop62srK2srq2urqywrq6uq7OsgK2sra+rq7CtrKyrpamqpKSkpaKmpaWmo5+fn5yZlpiamZeelpWUlZOWlpSWlp/qkJGRj4+Qj4+Rjo+Oj4mLjYuKjIyMiomMjIiKh4iHiYWHh4KCgIKDgX+FhoOEhYaDhISFg4eEhIaEh4eEgIKDgoeIhoiHiYqNh4SHgoSEiIiLgImMjo2OjIuNjY2LnYqMj46PkZGQkY+OkpKPkJSSj46RkJCQkZSTk5KRk5SRk5STlpiYl5aTk5qWmJaUl5mZlJSUkJaWlpmXlZWWk5WWlpmWl5WXlpmWkZKVlp+in5+foqGjoaKin56anp6mn5+lp6eenJihoKeampeZmJyaoqacgJ2glpKYk5eUlJKWmJeZmJaZlpeblpORkJCVbI2Rj5CUmJORlpKRj4uJh4aDgJKBgoWJj5KRkZGMhP3w6+nug+n4/oOE+ID9iIqF9uTi29PZ1dna2dfV2dLL0M7HzsXCwMC+xMK+u7y0r6uvsrCvu6qxsKuqp6qioJyen6WblJOWgJ+Wk5WUkouOio2KioiLh4OFhoaIhYqEhoSA/PuBgISA+/Xq6efj3+Dl4ufl5+/v5eLl3OHp3uDl9eff4+Tl5+jn6O3w5+Pf4uTc5t/h6uD19/Ht4+/p9fr6gP39+/79gpLz94SC/v758Pby+vqD94b1ioiGh4iGhIqNh4iOjoqQgKeMloyJkJCPjY6fp5OYmJSUj5WgtpWRk5qRkpSUkpagmJmdm6ahmp6iy6SjpKSmrrifoKapqauwr7a1rsm4rayurrGztru8vL7Gx8C+usHDvMG8vcTBv8LGyL67wrm/vL++vrm7w8fDxsXCxcPJw8LGzMzJy8jIyM7JycvOz8rHgMvDysvHy8fFyNjLxMvLztHN0c3Jw8XCvsC5v73Ew8bLyMHCx8XHxsjcydPczsfJy8jO0NDdzsrIzcvMy8zPz87N1tDKy8zS0NPQ1Nfa39zf1+Dd2dbd3tvb3+Lf4eHf29za2eDg5ePm5Ozr6evr5Obq7+3x9Pr1/vz59/j6/Pz9gN7c3XFw3XPfcnFycnFyc3R3dXh5dnh6e3l4e3p9f39+foKCgIOBf4GDhoeGhomIiImJiomJiYqMjYqLjI+Qjo6Sko2OnJKQkpKQjpCSkZOUlpqXlZWXl5WZmJqbm5ycm5yfnJqbmpmbnJydnpybnJuampmdmZqbmpeam5iZl5eWQpmZmZqZmpqbmpubnJuamZqamJuam5ubnp2fn56foJ+enJ+fnZyenZ6enaCfoJ+dnpybmp2cmJiYm5ybnZqZl5eUlYSWgJeWm5qbmZqXmZiZmJqYlpWWk5OWlJaYlaqWlpWUlJSPlJKSk5GRj5CPlZOSk5KUkpWUlJiVk5KPlZCSkZKTj42QjY2OjouPkI2OjIyLjYyLjYuIiYqLh4aHh4eFi4WFhIaEhoWEh4WN4IGCg4ODhIOChIKCgYR8gYKAf4GCgH9/a4KCgIKAgoCCgIKDgYGAgoKAfoSFgoOCg4GCgoaChoSDg4SGh4aEhoeGi4mIiYiLi5eMjI6Nj46RkJGOj4+Pko+Mj4+Qj5+QkZWSkpGRkpSVlJaVkZOYl5WUlpWVlJWYl5eUlpibmJiZl5qchZ0MpZ6gnZyeoaOenqCchJ4FnJyen5+EohWenpyenaKinZ2dnKKjnp6eoJ+gnp6En4Cioqugn6Snpp2cj56epZqal5iXmpiiopiaopiUmpSYk5KRk5WSkZCPko6QlI6KiIeIjHCHh4WGh4qEgoaDhIJ+fXx7eHeEd3Z6e4CAf36AfXjm3tnT1XrQ3eV1ddpv2nN3dd3R0s3FyMTExsTCwMW+vL69ub60sa6tqrSspqanoYCdm52goKCsmJ6cmJiZnZmYkY+LkIyLiY2RhYSFhYaDhYKBfH58fnt5e3x8fnt+d3l4defmcnFycd/e2+Dh3d7d493e29fc2tTR1dLW2c7P1evRysvJyMjIys3S3s/JyczOzdbU09fK2dfU1s7a1N/d2W7Y1NXX13CCzM5ubtTV0YDP1Nbc2XHVdNR0cnJxcXJwdHZxcnd4c3mOeH53dHh5eHR1g5B2eHp3eXR5g5t7eXyFeHd7ent+h4ODhoKKhoGFi72KiYaGiI2mhoaOj46PlJKYl5Omm5STlJaZmZqdnJydo6afnZyipZ+jnp2fmpqdoqagoaagpJ2goqOenqKinzyjoqGkoqegoKCmpaKkpKanrKinpqenop+in6SlpK2opKiyqKOlpaWopaimqKaqqqmpo6ahpaOkqaahoqiEpli5pq66qKOkpqWrrq+7ramorKutraywrayst7Kwr6+yr7CusrS1uba5sbm3tLC3uri2uLm2uLu/v8HCwMDAxcDCwcXDw8fHxcfKzMjIys7K0dDPz9LT1dbZLbS0tV1dtl+2X11dYF9fYWFjX2JjYGRnamhoa2lrb25sbHFycXV1cnJ0dXR0dIV3gHl3eXl6f4B8fX1+fXt7gIF7fIqBf4SFhIKDg4GBgICFhYSEh4eFioiLjY2PjIuNkI2NjIuKjIyMkJKSkpSSkZGOkY2PkI+NkJKQkZCQj5OSkZKRk5OUk5STlJWUk5SVk5eVl5aVlpWVlJGTk5GPjY6Pjo6RkpKSkZKPkI+Pk5OSgJCSkY6QkZKRkJKQkZKTkZOUk5OQjo2QjY2KiYeIh4iIioqJioyJioyKjI2JmYmKiIiIiYWKiIiHhoeEhIOHhYWGhYeGh4aGiIWFhIKJhISDhYSBgIaDhIWGgoaGgH9+fXx/f3+Bf3x9fX98fH5/gICIfn57fXp9fXx+foLUfH5/Xn19fn59gH6AfoB4fYB/gIODgoB+gYB/goGFhYeFiYmFhoSHiISCh4iFhoeJiIqJi4iMiYiKi4+SkY+Rko+UkpKUlJeYopaVlpSWmJuanJmbnJ2mn5ygoKKgrqChpqSEpYCoqKeqqqanrKyqp6moqqqqraytqqywsq+wsa+ztri4uri4u7a4tbS4vL65uLi0t7e3uLi3uLi2uLi4u7e5uLq3urewr7Gyub+9vb7BwMC+vL27vLvAvsO6ur/Bv7SxiLOzu7Gxr7Gusa+6urCvtqumrKarpqSioqOipaemqaWmq4CloqCfnqG0lpeUlJaZk4+VkJCOioqKiYaFj4WDhYaJiISCg4F+9vHy7PCe5ujsenjjdeZ5fHnl3d7a09XP0tXU0c/UzMnMy8bOw768vLzIwry6uK6po6irq623qa6sqamnq6ejnp2ZnZaTkJKWiImMjZKOkY+Oi4yJjImGhIN/fIB5e3Z6eXjt7Xh4fHz18+rt6OHe3eLZ2NPT2NrW1drW1NfGx83a0c/R1NTS0dHQ1dvU0MzQzcLNxsjQx9nc2N3M1MrX1tNs1c/R0s5tg8zRbm3W1s/DxsPJx2vHbMhsa2pra2ppbW5paG1tanCYb3VvbnNzc3BwhY5wcnNxc29zfICacW9ye3FydXRydHt1dHZzfXhyd3zLe3t6enyEoHt8g4OAf4F+hIJ/jYqDg4WGiYqJjIqIiY6Si4qHjpCKjImJkI+OkJOVi4uRipCKj4+OiYiLioeKiYuQj5aPjpCTkI2MjY+QlZGSk5eZl5aal52bmaedk5WflI2TlJOYlZiVlWyRkpKRko2Uj5SSkJKNiIeOj5GSk5+Rl6WSjo+TkZiZm6eZlpSYmJqYmJuXlZagnJiWl5mXlpOYmZ+koqWbpKKioKSjoJ6gop6hpKakpqekpqSopaemq6yqra6pq6+zsrS3u7W5ta+trauurq8Ifn5+f39+f37/f/9//3/tfwGAp38BgZ5/hX4Mf35+fn9/fn9+f39/0H6CfYR+uX0BfoV9Bn5+fX1+foh9BH59fn3/fv9+kX4CAgQAQoCA/4GBgIGChoSEhIaHiouNio2Pjo+RpZOVlpmboZqXlZaZmpycnaGfoJ2hpaWhoKWipaanqKutrq2trKqvq62qrIWrdK2trqyvsa+wr7C0srOxu7C1uLSxtbCytbSzt7O2tbWxsbCvsLS0tbq0srOzs7Kxr66rsbattq6urayyrq+tsKurp6qrqqqsqrGurLCxrq+prbCwr7Gtr62vuba2rrKxs62wurCurK+ur7OzsLCxr7GxtK+whbKAsbC0s7Gwsa6uq66oq6uvra6sqq6trrCxrqyqq6yyrrOwsa+xrq6rrrOvtLKzsa+wr7Gura+ur66urrCytrywrbCsra+0tLa1s7GwrKqopqWlpqSlrKeqpaSgn5qZmZqZnJqbm5mYlpiamZqXlpaTkJCPjJGYjoyQj5ORlJGQkY6AkI2PjY+Mjo+NjIyQioqKiYWDhIWFhIaJjIyKiImGhoiFhoeFhoSEh46LjIeFh4iJiYqIioqLj42MjYqGhoeHiYyLj46RjpePkJKMi46SkpGSlJOTlZOSlpGTk5SZk5OWlJWUkpSVlJaWlZOUlZWWmpmZmp2cm5yanJuampWWlZgSl5yim5eYmpSZlZSXmZ+dmZiahJaAmJmbnJ2cm6Cio6akpKSqo6Ohn5yfoqKgo6iioKOjo6egnp+hoJ+hoqSho66cnqKYlpeXkpmZmZual6SbmpeVl5WVkZSTjpCTjY6OkZSQkJCJhYKGhoSEhYCFg4eTkouPjYaDgvbo6ebo6err/fj4/Pr/hPjy7Ono3NPZ1NjS1tOA09HEy8O/0Lm8wcLEwr27vLa6tbOysKmxrKWopa6rsKykqaCgmp6dmZWUk5aXk4uDjI2Tj4qMioiHh5SIk4+IiIiFg4SEgYP++/n68/Hs4ODc3YHm5OLs9uvr7eXn8fTx7e7x9fLw7e3o6N3g5Nvd2tvV4OPr5uLn6P70+ILx9Pk3goCCg/+FhYOCgZPzg/7/gYH+/YOJhoWDhYOGgImHhYOQiIyKkIuJjouPkpGNlZOPkJKQj4+SlISTgJSQn5OXk5GelZSSmJypnLGdoqGjqKqkraWgo6ampqOmp6G5rbO3uru8vbi7ubmwvLW4vr/Bv8HCxtDFwcPGxcDBwL/EwsfKz8/WysXEx8fRysnEysjKxsPCxcTAv8TFydLVy8nMysnHy8rLzc/JyMrIzuLZzMrH2srFwcTGycvLb97RzsnGwsLDusXAv8LFw8fHw8rLzMjHxcbM0dLLydHKzcrIzs/Q0tLKy83NzNTQ1NXRztDQ0MzX1tTj3dva2tXY4OLf4ufd4ePn5ejl5+jn5uTo6ufm7vTu6fTt6urs8fTz7fOA/Pv9gIH9g4D+/glub95xcXBycnWEc1Z0dnZ4dnl6e3x9nX98fH9/iIGBgIKEhIaFhYmHiIeKjYyJiY2MjIqMi42Oj42OkJCTkZOSlZWWlZaUlZOTkpWXl5mZm52ampigmJ2empidmpyfnp6fmoSdAZ+EoICfnaGdnJ6goaCgnp2bn6Ocppycmpqem52doZ6enJ2fn5+hoKehnqCfnZ2YnaCfoKKfoaChq6mpoaOio56hrKOioaOenp+emp2fn6CfoJ2enZuampmYmJqZmJeampucoJucm56cnpuZmpiYmJmXlpWXmJuXmZeWlJeVlJKUlpSXlh2WlZSXlZeVlJeXmJeWlZeXmZyUkZWSkpOVlJOSkISRgI+QkY+PjoySi42Ki4uMiYmKi4iJiIiJiImHhoeJiYaEhYaGiIeDhYmDgYSDhYOGg4OFg4OAg4GDg4WIhoWEiIOChYWDg4SEhIGDhYiIiIWGg4SEgoSFhIaEhIeMiYqHhYeKioqLiYqLi5CPkJGRj5CSkpKTkZKQko2Xj5CTkJKXF5iWlJOVl5WVlZOWlJWWl56amJeXl5mYhJkqmpqanJ2dnqCfn56ioZ+fnaChoqOho6Gjo6aqpKKio6CloaGjp6yopaSmhKSApqWko6OgnqKjpKWkpKOqpKSjoqCipaSfoaSenaChoqSdmpydm5uamp2bnq2bnaGYl5iXkpeUkpWTj6GTlJGPkY+OjI2MiIiLiIeGh4iCgoWAfnx/gHx6e3Z6eHuEgXx+fXh4duTb2tbZ1tfX6N7c3drdctfT0dHTy8XKxsnDycWAxcC4vri2xa2usK+uq6enqKWopqWlopujnpqdmZ6an5qVl42MhIeLiYqIhYaHiISEh4WIgXyAfn1+foZ/iYN+fnx3dHNyb2/d297h3+Tn39/c3oDi3tXZ4NbX1tDS1tPUzdXY19XRzc3My8fO0s3QzdDM0NPY0s7Qzt/Z2nPY2982dHBxb9dvbm1ucIHSb9XYbG7V1GxubG1tcG50bHJxcG12cHJwdXFvdnR5fnx3fHd0d3t6eHZ2hHiAen17in+De3qFfnt6f4SWhJSBhIGCh4iFjoiFh4iIiIaLjoipkpaXmZiZmpiZmJmTnZibnp+fnZ+foqqgnqK1pqKioZ+hnaCgpKWup6OkpqOsqKihpaaop6alqKejpqiopquspaSnqaelqKepqKqmpqWlp72wqKemua6ppqenp6RwpLapqqmqqqyspKunpaioqKqopKurraqmpqarra6op66qra2tsbKws7KurrGvrbKtr7Gwr7Kzs7C1s7C3srKxtbC0t7i4ur+4uLq7vsC+wMLCw8LFxcPAxMnFwdDMycnMzM7Jxchpz8/Sam3YcG7Z24BaW7VdXVxeXmJfX2BhYWNiYl9iZGVnaZVtaWhrbHhvbm5vcnJ0dHR2dHRxdHd2dHZ6eXp5ent9f4F/gIKAhICCgIOCg4KDg4aHh4WFhYOEg4WJiIiGj4WKjYuKjoyPkpGRlJCSkZKQkZGQkpSVlJmUk5OVlJSVk5ORlZqUmJOUkoCRlZKTk5aUlZOVl5eWmJadmJaZmpmalJeZl5eYlJWTlJ2bmpOXlZiRk52Uk5KUkZGVlpSWl5STkZOQkpOSkpOSkZOWlpWTlJKTj5GNjYyPjY2Li42Mjo+Rj46Nj5CTjo6Mi4mMioqHiYyJjIqLiIeJh4mIh4qKiomIhoeHiY+Fg4CGgoOEiIeIh4aGhoWEgoGAf39/foR/gn+AgIB+fn+BgIKBgoKAfnx+gIODf4CBfn6Af3p+hH18gIGCf4F9foGAg4OHhIaEhYaEg4OJhIaJioaGiIiJh4aJi4qJiIuJi4yKi4uJi4qLj5aTlpOQk5WVlZeVl5iZnZqbnJmXmZqbnh+fnp+foqC1paWnoaClqKinp6mpqKmmpKqprK6tta6uhK+Arq6ur7CztLS2uLa2t7e3uL69u7u5vLu8vbm7ury9wMbBvsDCvMC8uru+xMG+vsG+vby7u7q6ubq5ub/DxMbEwr/CvsC/v7u+wL66vMG7t7m4uLq0s7W4t7W0s7WytMCvsLSsqqurpqyppqqrp7mrqqWipKKgnZ6cl5ealJOTlZWAkZCQjYmHi4uIh4iChoKEi4iBgoF7fHvx6evl5+Tf3/Lp6Ozo6Xjh3t7c4NjR1dPY097Y2NHJzsXC1Lq8wMC/vbq6vri4sq6vq6ayrKWnoqmlqqeio5qakZaWlJKQjIuNjIiIjY6Ti4aHgoCAf4l+hn94eHh1dnp6d3js6+zw6+yA7N/d19mG3trV3+vi5ebX2NnX2NDV2Nvb19XX0c3GyczIzMnKwsfIzcnGzs7i3Nxz0M/NbWlrbdNvcHBtcIPObdLVbGzT0WpqZ2ZlaGVqY2xpamhybW9tcW1rbmtscG9rcnJvcHR0cW9wcnFxcnR0cYR0dnNye3V0cnZ7kXmOdXhBd3d+f3mBeXR2eXp8e4CAeZCAg4KDgIKEgoeJiYWPiYuPjo6NjY2QmI6NjamTjYyMi46NkZKVlpqRjo6SkpiSkYmEjYCOjZCQjpGTk5KVlo6Kj5COjZGQkpOWlZeZmZ2+p5mWkZyVkYyRk5WWl66ampaUkpKSjJWRkJSSkJKQi5OTlJKOjY2QlJWQkJmUl5aVmJiYnJqWlpWVkZWTmJqamJmamJSZlZWdnJ2dopyho6Sioqafn6Gjoqakp6ioqaWoqqemrRizram3sa6usLK2ta2zXre2tlpbs1xasrEDf39+/3//f/9//3+7f45+AX/Ufot9AX6pfQR+fX19hH4BfYZ+CH1+fX1+fn19/37/fo5+C39+fn5/f35/f35+AgIEADv/gYCChIaJhYaIh4qNjo6RkY+TlpSUlZeUlZ2doJ6fnJqZmpmcm6Gem6ChoKKkpaSmqaqrq6qvr7WytISxgK+zsbGys7GysLm4tri4tcKys7W0tbK1tLW5t7u8uLa5t7m4urq5vLi5t7e5ubm9v7y5t7i2s7G0srKxsrK+tLKyr7Kzs7Gxr6+trquwr7CtsLCwsrKys7Oys7SyrLGxsbOxsbSxsbCysLSzr7KtsLW7t7W4trKzra+wtbaytLW2Dbm2trW1sbKvsrKwtreEroCsrLCxra+xrq6vr62xsLGxs7Ovr62srbK1s7a1t7q7trSzr7CzsrCxsrOwtLe2s7e7tLW2s7W2s7W0sbK1r6ypq6qnqKemqqmkoKKdmpmYmJqbn5eZl5eYm5qZl5mcmZWUlpqTlpeUlZGUlJWTkJCQj42OjY+Rjo+MjZGOj4+Nh1OHhIaJiYqKjY2Njo+LiouMiYmMjI6Lk4mKi5OKi4qNioiJjo6Iio6Njo6JiYeLjZKQjo+Nj5KNlZSTj5CQkZORkpSZmJeXmZqcmJaYmpOUmJmamISVBJeXmJiEmheen5eanZubm5yZl5WUlZWWlZSVlZiXmoScgJmenaOfn6OhnZ6dm5qZoaChpaGhoKOlqKenpaempaaioqCdoaOlp6ipq6qmpKOioqWjpqino6SmoKOinpyinZydoKKjnpqVmpqdnpyamZWTj5WVlZaZlZOQkI6MiZSEjpWGhYSDhoiIjI+Mjo6Fg4T5+Ozt6ebn4ujo8/X7+/fxgPPo6eTd2dfW0tLSzsnBwsfCxcTDxMHDxMTOwL24wbS2t7Oxs66mqKOpqquqpJ6poqeen5qUj5GPlI6Ni5aUjoaFg4aDjo+IhpCMi4qJhYSE/YKEg4H++/Hz7ufq6Ofi297n8vfx7/T09Pj2/Pb6/vvw7vHt4/Hh4eTe49zY3ebvd/P084T8hIWGgf///Pj9hIH8gIL+h5KGhYCB+ff5gICChIOGhoeGhoCEhoOIh4aJjIqNh4qJjYmOkZCvk4mHiYqYmY2WlZeckpGSlJSPjpCQk5yZkpuUlp6dr6yqrK+iqaysrLSurq21pqyysri2vMC8vby+v7a/hL6AycXCwcPIwcPJycXRxMfFxsfNx83M49nHyMzN1dHN1MzI08fQxMfHxMLQ0NPT0dPM2IPQxsnJyM7S3M7U4dbV1tPMwsbIxMHFytHR0NHQyMzIx8fQyMTIysbJx8jKy8jG0c7P0NPPz9vU2NnZ29ra3dnU2dHQ0NHR3NjU2tzU2dk709LX3Nze393i5OTi/e/w6O3n4uPm5ebw7erm4+Lp8O7n8vX39Pf0+P38+oH+/vqH/YCA+v78/fr6/4OA33BvcXJ0dnNzdHN1d3d3eHl4e359fH1/fHuBgISDhYSEhYeFh4eKiIeKi4uLjIuJi42OkJCPkpGVkpSSk5SUkpWVlpiampqVmpmXmpubqpmbnZucmpycnKCeoaKfnqKhoqCgn5+ioaKhoqOioaKloqCgoqCgn6CgoJ6foa6joaGAnqCioaKio6Kiop6joqOjpKOjo6KgoaCgoaOin6KkpKWkpqimp6WmpKeopqejo6SloZ+fn52hnqGgpKSfnp+fn5ydnJybnJudnJygoZydnp+enZ+empubmZmampibm5uam5uXmZeVlJiZlZeVlpmamJeWlZeYmJaXmZiWmJqYlpgSnJaXl5aZmZWWlZKVmJOTkpOShJCAkZCNjI+MiouLioqLjoiLiImJi4iGhoeLioiGiIyGiIiGhoSGh4iHhIWFhYSGhYeJiIiGhYaFhoaHhISDhYaEg4OGhoeIh4WEhoeFh4mKjYqRiouLl4yOjZCNi4yPj4qMkJGTlJGUkJOTlZKSkpCTlZCXlpaTlZaYmpaVlZmZmJpXmpial5ibnpqZl5mbm5ucnJ2cnJ2dn5+foaWmnZ2fnp6foaGjpaSnpqWlpKSipaOkpainpqOopaikpaempaalpKOhqaemqKWloqWlqKWmpKemp6mnpqOhhKKAo6WnpaSjoqKhoZ+foKCdn6Gcn52ZlpyZmZubmpyYlZCUlJWWlJKSj4uHjYuLjI6MiYWFg4OAkH6HjX99e3t8fX1+fnx8fHZ2eefr3t3a1tjU2dTa3Nvb19LVztHQzMvMzMnK0MfDu7i6tre0srKssLCwua6pp7Ckqqilo6ShnJ+Am56bnZyZkpiNjYaMjY2KjIeKhYSGjoyFfXx+gHyHhH98g4B+fXt3dnXhcnBvbdvi3OXm4+Xm5+HV1dvc3dXQ0NHS1tXc0tnc2NDO0tHM2s/R2dTY08/Q09jZ1NVx02xubWrW2Nna3HRv2m1u2HF8b29rb9bV1m1ramppbG5vb3CAb3N1cXNycXFycnVwdXV6eH18fJ6Ienh3doJ+dXp5fH95e32Agn17fXx+hYSBjIKDh4ONiIeIi4KJi4uIjoqLjZmMkpiXmZicnpubm52fmaCfoJ2epaCdnZ+jn6GlpqKsoaOhoKCloaSlu7GmqLSnrammrKajraeyqKurpqWvra6ArKqsprNpraepq6mrrbeoqrSsrK2uqqOprqqnp6usqqmrraesq6ytta6prKymp6anqqyrqLWwrq6wq6iyqq2trq+wsrSxsLWysbOysLiyrbG0sLe5tbS2t7a1srG1tbe338vDvb+8uby+vb3Dw8bGw8PGysXAyMfLx8fGyczMzmkP0NDMdNBqatHW2NrY2t9ygLZdXF5fYWNgYmJiZGZlZGVjYmZoaGpsbmtpbmxvbW9ubm5wcHFxdXNxdHV0dXh4d3l7fH19fH59gn+CgIKDhIKFhIOEhoaHhIuLiYqKiJiGh4iIiIWHhoiMjJCRjo2Rj5GQkpGRlJKSkZKVlJWYm5eVlJWTlJSVlZaUlpehmpiXgJWXl5aWlZeZmpuZnpydmpqamZqbnJ2dnp6bmZKVlZWWlZaZl5eWl5SXlpSWkpWYnJiSmJiXmZOTkZaXkpKUlZeWmJiZlpaVl5eVmpmQjo6Ni46SkY+Sk5GTk5SRkpGQjY6OioyLioqNjoqMiouMjYmIiIeJi4yLioqJhoeJh4WIWIuGh4eGh4eEhoeEh4mEg4GDg4GAf4CBgX+AhIOCgYGAgYKEf4F/fn+CgoGCgoeDgH59gXx/gYCCf4CAgYB/gYKEg4aEhoeFhYKEhoaIiYqHhoWIi4mKioyEjoCMi42OjIyOj5OPmpKUl6SYmZeal5aYnJ2Ym5+foKCanZqdoaShoqSipaeiqqmqp6iop6mmpqisrKusra6xsLK1uLKwsrO0srCxsrW3uLu8vr28u72+tLa7u7y9vr28u7q9vb6+v7+/w8HDxMbEw7/EwsfDxcnIxcbEwr+8xcLCxhLCxcTHyMnGwr/BwcLGw8K/vL6EvwS+v728hLqAu7m7u7q1t7mztLKuq7CsrK6xsrOvqaOloqOioKGhn5yXnZqamp2amJOTkI6MloiUmYqIhYSEg4KDhICBgXt7fvX68vLu6uni5uXr7e3p49vc1tra1tLR0dDT39fTzMnMxsbDv7+6v8LDysK8t76xt7aysbSwqKmipKGgoaCapJlzmZGVko6LjouQjI2NmZWLgH9/gXyEgnx5gnx8enp3eXzyfn58evX57fHt4uDd2dXQ1N/m7eLg4dzd3trb0dTSz8zK0tPK08bFzcrTz8vMztLW0NJ02HFzb2rS0M7R03Bu1Gtu2XB6cGxqcNbV1WtpaGhmZoRnBGVoamqEbYBvbnBpbGpsam9vb6WFcW1ubHhza29vdHdycHBzc25udHR4f3lwfXFxdXOCf36Ag3h/gIB9hH+BgYl7f4B+f36BhYKEhouMiJCOjY2JkY2Kio2Rjo+SlJOwkpSRkI+TjpCPpqCOkaiVmZiUmZONlY6YjpGSkJGcm5uWkZOLmVyVjTaQkJGVmqmanbGgnJqZkouQlZKPkpeZmpmamJGVk5OVnZeUl5iUlpOUlZWTkJuYl5eZlJOdl5qEm02dnpuZnpiZmZaWnZeUmp2Zn5+al5mcnZ+hoqinqKTkxa+nqKScnZ+bnqioqqimp6uvrKWtr7WwsrCxtLKxXLS0sW65XV22uLe3sbK1XQF+/3//f/9//3+9f+B+AX2Efq59An59hH6FfQZ+fn1+fn2GfoN9/36QfgF/+X4If35+fn9+f3+HfgF/AgIEAICChIaJiYqLi4yNjJCTk5SWl5SWlpicmpqanZ+gnJ6gnpqanJ+goqSmoqajoaKopaisrqurrbCwsra3uLe2tLOztbK0sLOysra5ube1uba3u7y7u7y+v77cuba7u728vbu6vr+9wcLBwb+8vby8vcDEvcK+vby4t7G3wLi4ubm2u4C1uLS0tLWytLO5t7W4trWxtrW0tbm7t7a2sbW4ubm5urW1tbazs7W1tLSzsrG0tbe2ur66uby4tbm7uby2t7e2t7e2trS0t7e5t7i7tri2s7KztLGxsLKztLG0s7Sxs7rIs7S0uLW3uru6u7u6vbu7tra4tre3t7W1u7u4wb64toC2tLW4sra0uLe0s7K0srCxtbOvr6yrqammpKWfn5+enaKhoJydmZyemZyamZuZpdiampiZlpaUlpKWkpWUkpGPjpCQk4+NjIuOj5CPkI+PjYmKiY+PkY2QkIyLjIuJjYyNiI2Mio6OjYuMiYqLjIyRjY2OjpCOj46NiYqMjI+Qk4CXmZaTkpOTlJOSlZSTkZKTmpWWm56cmJybmpmYm5uVmZ2dmZmZnJuXmpmZmpqanJ+loqOgoJ2bnJydmZuenJ2dm52cm5+goqGkqaWioaWloqWjop+hn56gnqChoaSlraeprKqppaakrKCipKepr6epqqeqpaelpqWmpqiop6enpICko6CmpKihpKGhoqGioqGfrZ6cmZ6goJuYnZmUm5SSnZGRkpCRi4uZiYyMiYaHiomMi42MiouMioqDhYD16+fv8Ozv8vT3+vbx+fTo5Obk19/e29fX1dTJysfL0M7bzsfNzMjFw8C6ubq7ur++sK6rqamorLCtrK2lnqSjopmYlX2Xj4qTjYyTi4uMjIePi4+RjYqGi4qJhoKDgoODiIeFhYf8/feE3Nnf5dvm7fHw7fHz9vny9/WC+vn1/Pnq6ujv+e/57O7n7O/39/X09/H18/yGg4H/gfyA+4T/+v759P/5+YGIhfyM/4P8//6EhImEgpqHhIiQhISGh4eNioSJgIyOjo2Qk5SRkZyXlpmen6iTjpWcnJeYmJKUkZGak5eXl6WkobOsrq2xqqqxr7S0t7SzsLG1v8Tm7MPGxMe8v7W9v8HO0M/OycrRxtfP3snHzMrMz8rSzM7Ky83Pz9jY09jV1c7NysrLzdDOy8fQz9TP09XRzM3My8zOzc/X09fTXdba1NjTzdzL0NbLyNPwz9HTzMzRyszUys3Pzc7KzM7NzdDMyenR0c3P0czT1NrZ3Nrg2d/e2tjY1NLV1+jc3dne29rb29ve2+Pl5eXm5eni5+/r6ujv7Obo8ero74ToHuvq8+nv9/Tz+fqBgoSHgYGD+/f6gPyChoCAhYGChDRwcnN1dHV1dHV1dXh6eXp7fXt8fH6Bf39+gIGCgISHiYiIioqJiYuNi46Ni4uOi42QkpGThJWAl5eYmZiYl5iamJqZm5yanJ+enZuenJyfoJ6dnaCjo8efnqKipKSlpaSmpqKkpaWkp6anpqamp66kqaanpqWkoKWspaarqaWqpKWipKWmpKWlqKWlpaSmo6inpqapqaelpaGlp6eoqKmmp6eppqaoqaeoqKimqaamo6aopaWopqVdqqqkpqOko6GioaCgn5+ioJ+dnqCeoqKgoKGioJ+enp+fnJ2cnZqcoLWampuemZqcmZiampicm5yZmZqZm5qbmZqcnZqgnZuampqcnpmcmZuamJWWmZaWlpeWk5KRhJCAj5GOjo2Mio6Mi4iLiYyOiYmHh4qJk9aLi4mKiImIiIeJhomHh4iHhomKjYuJiIeIiIaFhoWHiYaGhYmHhoSIiYiJiYiIi4iKh4uLioyMjI2Ojo+Pj46Rj4+OkJOPkpSUkpSYl5eYlpaXlZSWl5iYl5eZmpuZmZqimZqgoJ+anJ2Am5ubn6CbnZ+enJ6foqOfoZ+goKCio6WqpaWioqGhpKitp6erqKaop6iop6inqKeqrquop6qqqKipqKapqaioqKqqqaqqramprKysqq2rtqeoqqursKenpqSopaampqSkoqSjpKKjoKCgnqKfopuenJqbm5qampismZaTl5iak5OAlpCNkIuKl4mJioiIhYWVg4aGgnx8f31/fn98eXl5d3p0d3Xg2dXb2tfa2Nna2dbU29vS0dTUyc/OzMXIxcO6urW6u7nFt7G1s66sraupqqutqqyroKKhoaGfn6GdnZ+VkJCPkIyMjI6GhYuGhouFhYKCf4eAgoF+fXqAgIB9eXgDdXNwhHGAdOHn6YXd3ODn1dvf3tjW2dPY2NTZ23Hb2NXZ1crLytLc0dfR0c3S1tza19jf2NrY2nBtbNVt2W/ac+Le4d/Z4tfTbHFv233Zb9fX1W1ucG1tiHBvdHpycnZ0cnV0dHZ5enx6eHV5fX57eod8e3t9foV4en+GhYSDg36CgoaLg4aAhIGLiYaTjo+MkIiHjImOjpKQkZGSlZygybWcnp2fm56WnKCfqKqnpKOkrqKyqbunpKilpaekq6irqqyrrayyrqmtrq2pq6mqrbCxrqmmr66yrLCyr6ysra+wsq+tsq6yra2xrbGsqcirsbWrqK/Era6vrKyxrK61rbCyrq6qqqtIra6zsq/ItrWxsrKtr7CysLKwtrG3trO0tbKxs7TGuLi1ube2t7e4vLe9vLm4uLW2sr3Bvby7xMG7vcTCwsrFx8fExcTIw8jNhMwUaGdpbmhqbdLN0WzVbXBsbXFvb3E7XF1dYGBiY2NlZWVnaGZmZ2dlZ2hrb25ubW5vbmttb3BvbnFzcnN1eHV4d3V1e3h7foB+foCBgICDgoOEhGWGiIaHhIeHhoqPjouJjImLj4+OjY2Oj4/KjYyRkpSSkpGSlZeVl5eWlJWUl5iZmZynmp6bmpmXlpKYoZudoaCdoJqcmJeXmJWZmqChoaKhoJuem5qbn6Gfnp2YnJ6enp+fm5ycnISbgJmXlpeYm5ubmZyfnJyem5ibm5eZlJaXl5iZmZmXmJqam5eZmZWXlJCPkJOQkZKTlZWTlZSSj4+UtIuLjJCMjpCPjY6Ni4yLjIiJi4mMjIyLio2MiY6MiIiJiIqMiIqIioqIiImKiYiIiYeFhYODgICAgYSEhoaEgYWDgn6AfoGEgIGCgICDg43Mg4J+gH5/foB+gn+CgoOEg4WIio2JiIeFiYqIh4mHioqHiYqPjo6KjpCNjo+Pj5KPkYuQj4+Sk5WWmJmampqYnJmanJ6joKKgn5qcoaCipKSmp6emqKuqq6qprKutqqyut6yssbKvqq+ztLa2u7y1tri2srS2ur26gL2+wMDAv729w76/v8C/wMLEx8HBxcPDxcTGxsXHyMjHy8/NysnMzMnKy8rHy8rIycbJycjIytPKys3Lx8PFw8/CxcbIyc3FxcTBw76/v7++v7+/vr26uri3trO4tLews7GytLaysbCsx6mkoKOkqJ+eoZ2boJ2crpqZlpKRio2agIySkY2IhoeDhIKDgH+AgoGDfoKA9+7p7uvn6Ojq7O3l3uPe09LV1sjQ0dDP1NTUzMzGzM3M1se/xsS+vr67t7i4ure9vbO0saypo6SkoKCim5WZnJqUlJCSiomTjYySiIeDgn6FfoGCgYKBgoB9enZ5eXp7fn17fX3p7+2C2dfdgOXX5Obm5d3e3drb2dnXb9bT1N7f1dXO1NnO1tDU0NLP0szKy9PW29rgc25s0WvUa9Bw2NXd3NPi2NNscG3XfMtnyMfFZmZqaGSPaGVrcmdmamppbWtpaWlnaWtqa3BzdnJyf3BwcHF0e29vcXV1cnR2cXZzc3dwc3JyfX16ioKBgH6Be3uBf4WEh4OCf31/hYiuoIaJiY+IjYmPkZGWl5WRjpGdj6SXppeVmJaWl5OalZaSkZCQjpWUkJmYlpGUkZKWmZmVk5CamZqRlJWSkZWWl5aWlZeem56cnqKdn5mTu5WboZeTnrSanJ2Yl5uVlZqSlJaXlpaZmJeWl5aTtZqbGpmbnJebnJ+eoJyfm52bmZaWlJKUl6adnpughZw5n5mfoJ6hpKSopKusqKSfpqCbnaShoqqoqaqpqaiupqyysK+xsFpZWV1ZW162s7ddtl5gW1tfXFxe/3//f/9//3/Af+V+BH19fX6RfQF+mn0Jfn5+fX59fn1+iH0Kfn5+fX59fn19fcx+AX//frR+h38Ffn5+f36IfwICBACAiIuNjo6QjIuOjo6SlJWXlZiVlZagnJqcnqOko6KjpKCfoaGjpKOpqKqlrqmoraqysrWxs7K0s7S2uLe2t7i6v727vLe6t7m5wrzFvry5vbu+v8G/vb3Awb++vbvAv7++v8HDxMXEw8XCw8HDv77AwL7Dv7y+u7q2t7e6urm/u7yAuru3uLe4tbi2tre5tre1tre3t7S3u727vLnBvsHBvL65vL28ubm4t7i3uLm5uLm+uru8tsHAv7e5w7y7u7y9vLu4ubm6vr6+vbu/v8DDvrq4t7i3t7S1t7O1tsK7uLi9v7u6uby2tbi5vLzAwMC9v7/Avr++vb28v77Fwr++uriAuba7t7W0uLq2uLm1uLi2tbKztLSxsrCuraqooqGgnaCmpqWjo6OfoKCenaSbnKCam6WgnZ+ZmpyanZyXlpaUk5SSk5OQj46QjpKTlpOUlZKOi4yMjY6Ni4+Mj42Nj42Ni4yLi4qKi42Mi4uMio+OkZaQk5OSkJOPj46PjY2PkpWAlZuYlZSSlJOXl5SVlZSVl5eWmZibnJ2joJ6emZian56bn5ydnZ2cnJ2cnp6gnp+hoaOjo6Wjn56go56gpKSlo6ijo6Oio6Whpaamp6ipp6WkpKSipaWkp6anoqSlo6qrq6qrp6Wppqmlr6upqrGur6+yramrqaurqKWpqKqppqyAqaOspaamn6OjoaGlp6SdoaGgpammo6SioKCcnJuXlpaWlJOWlpCQjo6MjZOPj42JiIWHioeGiYeE/oKRgPDz7/f3+Pfz9fb38POB6Pbr5OPn5eXi3d3f3Nra19fS087N0tTNy8PCvLu/wMC8xbeutbCys7Svrqunqp6foKidnZiAmZeWkpOSj5GRjpCKiY6Mj4qKiIqEhIKDhYiFhYWJh4mYi//7+/f26+n16Ofq6uXz6/Tz84H7+fT2+veA3uXm3+/w9fLr7+73gP+C//76gP79gYOB/4KDgP2MgIKJgYD++fj6g4P8/vz3/4aAgPqFi4iDioWChYKCiYyNkZmKiY2AiouPjpCJi46SjpKZmpuZm56dmpearZSbmJWWk4+inJqhpqWjqaOpuayurK6ztrm3uLu5u7rExsbDyerG0cPFwL/GxcPU0dDTycvU1NDT1tDJzsvR0dr20tTOz8/X1dXV3dHm1tXNy87NzdLV09HSz9TT09XU1dLa0tPU2dbZ19qA2d/f3dfU4tLa19nQ1tLUzdHRytLV1dTO0tPT2NbTzc/P1s/V0dPU1dLS0dTV0NPX2Nja2d3d4uDa19vb4t/e39rh497e397f4uTl6Onw6e3o5/Ts8vH29u/u6vDt6ezt7urv7fTy8/n19vj4+v2Cgf+DgoCFgYOAgoWHhYqLh4lgcnR1dnd4dnZ4d3h7e3x+fH99fX6Gg4GCgoSEg4SGiIiJi4yMi4qOjpGNl5GPkY2Tk5eVmJiZl5manJuampqbnp2cnp2gn56epJ6no6CfoZ2foaKhoaGlqqalpKSrq6uqhamAqKmpqaupq6qoqamnq6qqraysqamnqKinq6ioqKmmqaiqqKqpqKmrqKmpqquqqqaoqqupqKetrK6uqaqoqa2trKytrKurq6qpqqqtqainoqqqraiqr6emp6inpqWio6SlqKeloqCioqStrqakpaakpKGio56foKyin5+jpqGgoKMxnZubmpuanZ6enJ+dnp2dnp6dm56coaCdnZubnJqfnJybnZ6ZmpmXmZmYl5SVlpaVlISSgJOQkJGNjpCQjYuPj42OjYqKkouNlY2Nl4+MjoqLjoqMi4eHiImJi4uMjo2Mi4uKi4qMiYuNjIqIiImKiImHioiLiImNi4yMj46Pjo6QkpKPkJGOk5GRlo+Sk5OSlpaXl5qZl5iamZifnJyenp2cnp+dn5+cnZ6cnJ+eoaGhoqGhgKKgoaGjoZ6hoKOkpKSjo6KjpKenqKmop6WlqKmpqKuvqKeqqaipsKqsrayrsKmsqqqrq6yrqqiqqqipqqqtrK6qrK2qr66trK6srK+tr6yyr6yrsKuqqKqnpKmpqqmmoKSkpaOhpqSgp6GioZqdn5+epZ+dmZ6bmZqbmJaXlpWUgJGSkZCRkJCOioyLhoaFhoSEiYKAf318e31/fHp7eXfjdYd12dnU2NfZ1dPV1dbT13TQ3NLLys3NzcjDw8PAv8DAv7y8t7e5u7azrq6rrK6xr62zp6KooqOio56dnZqdk5KQko2Qi4yJiISGhoSHiYSGgH5+f4B8gH+Cfn96eXd4gHJycXFxc4N44ODm5uXf3+ba2tvZ2NvX2NbWct/d3d7k34LL0NPL19Tb2dPY2OFz4nLc3tpu2tZrbGvVbXBs1ndwcXt0c+Lc1NJtb9/d3Nbccm1u1nF0bmx0cnF0cnB2dnZ5gXZ5fHl6fHx9dXZ7fnp9foB/e3uAgIGAgpJ/hoWDgIeFgpOLh4qNi4iNiY2ajo+NjZCTlpOVl5WYlp+goZ2kwKGroKOfoKWkorOppqmipa2rqa2wrqeppqurs8usr6usrLKysbK5qLuwrqurrK6ur7Kwr7CvsrGxsrCzrbKurq+2s7Wzs7G0tbSxr7mssrK1sLe2t7Kzsq2ztra4sbW0ZrS3s7GusLG4sbSytLO1s7S0trWwsrO1tbe3urq9vbm2ubi+u7i8t7zBurm9ur3AwL++u7+4urm8xr/Bv8PDvr+/x8bGyc3OyszIyszM0dLR09DS0Gpp0GpqaG1qbGtsb3Bvc3RxcYBdX2BhYWVjY2ZmZWhoaGppa2lpa3xyb3Bxc3JwcHBxb29xcnR1dXp6fHh/fHp/eoGAg4CCgoOCgoSFhISGh4mMioiKh4uKiYqSjZaRj4yQjpGRkpGQj5OZlZWVkpaUk5SWmZycnZqYmJaZmJyamZudm6Cem52bm5eYl5qam6CenoCen5ucmpyYnJycn6OgoqGgoaCgnqKlpqSjnKKfoqSgoZ6hoaKgoJ6enZqcnZ6goqWgoJ6Yn56fl5qgmJaXmZmam5qbm5ygnp6dmpyam6ahlZOUlZSWlJaYlZaXoZeTkZOVkJCQlI6Oj4+Qj5KRkIyOjo+PkJCQkY+RjpGOi4mHhkCJiI6LioiKjIiKi4mLjIuJhoaHhoWGg4OFhYaFhoeEhYmIhoOFhIKEhoODiYKDh3+Aj4F/gn6Bh4aJioaHiIiHhImAh4aGh4aIh4mHiYuNjIqOj5CPkI+TkZSQkJOQkI+RkZGQkJOWl5eZmpidmpyim5+goqGmpKSjpaSjpaipqbCsqaqoqqmtr6yurq2vs7OztbSzsrG1tLe6ury9vbi1uba7vLy+vr6/wcHEwcHBwL++wcXFxcTGycPBxsbHyM/Ly8uAycrUx83Nzc7Oz83My8zNy83NzM7MzsrLzMzR0dDNzcnHycbKyNHNycrPysnHyMO/xMXIyMXBxMLDv7nAu7TAt7i4sbO1tbfEt7Osr62qrK6opKKhn56cnZ2dnp2dmpeZl5CQj5GQkJWOi4eDgH+AgX5+gIB/94COgO7u5+vs7uyA6+3t7Obnfdrs3NfV2dja1dLS09HPz8/NysbBvsPFwMC7u7q7vsLBvLu1rrWvrqqooKGgnaKVl5ibk5WSlJGSjo+LhoiJhIR/foKEiYWJhYR/f3p5e356e3p5eHyMgO/r6+fg1tTh2t3k5d/h3NjT23Ph29rc4eKJ1Njc0dvW3NeAzdPO0mvSas/S1m7b2W1ua9Ztcm3XeW1tdnBs2tDJympr083LxMtoZGXGanBqaHBrZ2lkY2loaWx0Z2ZpZmhqa21pbHBycHR3eXd0d3l5eHJziW10dHF2c3CAeHR4fHt6f3p/jXx9e3yAhIeGhomGiIOMiYqDiKiIkouOio2RkI2AmZKRlIyPmJiWmp6dlZWQlZObq5eYlJOSl5iXmaaSppeWk5OVlpaWmZiYl5WXlZaal5qXm5aZmqKdnpyfn6Wno56cpZmhoKKco6GempuZk5eZmZmTmJiYnpybl5iZnpabl5qbnpuen6CempqbnJmampuanZqVlJiZoKCfop6hpJ48naCdn6GhoqCiqKCnpamyra+rrqylp6Wsqaeqq6uprquxr7C1srCzr7CwWlqyXFtaXltdXF5gYF9hYV5d/3//f/9//3+9fwR+f39/jX4Bf9d+kn0BfoZ9AX6MfRF+fX59fX1+fX1+fn59fn5+fYZ+hH2CfoV9BH5+fn3/fv9+BX5+f39+j38CAgQAGoqMlJOUkJCRj5GSlZOVl5yYmZubdZ+eoaGjhKWApKqkpKOnpqisqquurqyvr7W4uri2s7m4uru+vrq7u73AwL68u7/BvL+7w8PBxMfEwMLEwsTCwsPGxMTCv7/FxMPCwMTGyMXFycvKy8zIx8vIwsPBv767ubq6ub2/vsC/vr27vb29vL+9vbu+vr+/ury9vb26u7y8v8HCxcPDycaAysTBwcDCvb6+vby+v729vb+/vsHBwr/Cvr/AxMbHwr7Avb/FvLy8vbzBwcO9wcDAwMPDvrq9vrq8vLu9wNLJwsDBvL67ubu9u7y/x77Cw8C/w7/DxcbMx8HAwsTEwsTDxL+/v7u6vLvAurm3trm7vbq4ube1t7SxsrWwsayopJ96nKSlp6Sip8GdnKGbn6Kknp2goKOunJ2cnJ+doZ2anZaUlJWVk5KTkZORkJOWlJSTkZGSjI2OjpGRjo+SkJCRj5eQj4+NjY2QjpCRkI2SkZOUlZaWlZWUlJaVmZORkJGSlJacn52anpiXlZSYm5eYm5iYmpydnp+eoKGIn4CdnZianZyfn5+gnp/FpKSlo6ekp6mnpKWlo6SiqKmurayqqKelpKOkpKSnqamtrautqK2lp6Wpp6mmp6mpq6yrrbCurrGsq66vq62rqq2wrq6xr7Ouq6uur66qrKutsa+sp6ynr6amqaakpqOkp6eqq6iqq6iqpKOkoZ2enZmXmoCeoJmcmJuZmJOSkFhwio+JiISGhoiHh4aBhoCQgv/6+Pv8+vj27+/v9u/m6e7s6efk5+Xj4erg39/q5+HW1M7Iy8jIzc3N1cbCwL66t7m2vry1tbSwrqyqrqqnp6Sdm5+Vn5qbmJWYkZKSkJGQjpCPkomGi4OJhIOEgI2MhYqMiICG/4CB/PmA9fDr5O/s8Pn2+4GC+oH3+Pzz+YKB7Ofz9PP0/P77/P+AhIaCgoP49vTx9/n7hfr6hIV5gP6B+Pn9/PyBg4WAgID///+Gg4ODiYiCgoT9/4CGjI6RlJiVkY6PkZSQkpOplpuflpOVoaWlmZmgnaCvl6uhnJmWnaGinoClqKahpaWjq7Cwrq2xsLa2tLy4tLu3w8LFycrHw8bGxcnHxcjQys3Sz9XU2Nnc1dXV2tLV09XR0dnM0dPX2dvc4d7j1tjR0tTW1dTV2dnT19fc3Nva3tva2tfY2Nra1tje2uDc293a2NXV2tbe3dLY2drV09LQ1s/N09LX29zZ1mDT19DZ1tHQ1NHT19bY2Nrg4dzh397h5eHi4+Ll4t7h4N/e+PTo5eXn5eTn6O3p7uzs6e/x9fb38/Ty8+zv6vDq6efu8oD1+Pf5gYGMhYGBgYODhoWFhYaIjoqKh4iHiYoXc3R5eHl2d3l3eXt+fX6Ah3+Bg4NrhYOFhXmGh4eRjI2Mj46OkI+Qk5ORkpCTlZiXmJicm5udn5+cnp2doaGgn56ho5+in6WjoaOlpKGipaSko6Wlqqmpqaeprq6trKqsrKypqKusrK2vrKy3r6ytra6vr66urqutrqytr66srK6sraytrKupq6ytrKmsrq+wra2thKuArayssa+zrKysrrCvsLCvrq+vrK2urq2srKutqq2rrK6trK2rqKmnqK6oqqqqqKqoqKOnp6inqqqmo6ano6WloqKlurClpKSio6GgoqKgoKGnn6GjoZ+jn5+goKOhnpyfoaKhoaCinp6inp6ioaSenZyam5yem5qbmpucmZaWmJMylZSTk5CMkpKTkI+VrY2NkIqNj5GPjpCQkp6Mj42OjoyPjIqNiIeJi42MjY+Nj4yLjZCFjoCQiYqKiYqLiYqOjIuMjJKNjY6OjpCTkpOVlJCUkpOUlZeWlpeXl5man5ubnJ2bnZueoJ2bo5+gn52iqKOlpqKhoKGhoqOkpqakpaWnqKamp6amo6appqimpaWmp9KurKyoqqeqq6qqrK6urqqtra+ur66wsa+vrq6vra+wra+vrYCtrLCqrKuwrrKwrrCwsK+vsLKxsbSwsLOzsLOwr7Gxraysqq6qqqutrKqmqKenqqmmpKajrKOipqKgn5ycnZ6goZ2dn5ufmJaXk5GTkpCPkZOUjI6LjoyKhoWEi3N+gn99enx6fXt6eXV5c4Z04t3Z2trZ1tfS0dPc2M/T19XQzIDIzcrJyNDExMHJyMO8v7y7vbi2t7e4wLKvrq2pp6ekrKqlo6CenJqXmpaTlJGOjpOJi4eHhoaJhYeFgYKBfoF+gHx5fnl8dnV0cH53cHF0cXDecXLk5XXm4+Db4d3d4tzfcnPfc97f5tridXPX1tvd3Nfe4NrZ2m1vcXByc9/c3IDY2tnaddfVb293bNxx3eDi5d5ub25ucG/j4990cW9udHJwcnbj43FxdXV2en19fXl8fn15enmNfYKEe3h8hIaGf3+Gg4OafY2HhYaGjI2Lh4qNjYmMjY2Ul5WVk5OUmJiWmpeUmJafn6CmqKajpqemqKWmpq6np6qorq2wsLOuroCrrqmtrbGvsbasrq6xsrCzt7K5sbOvrq6wsK6usK+tsbGztbSztbK0trS0tba1sbW4tbq1s7e0srCxt7S7u7K3t7i2trm3v7e2uLS2t7e2tLO5s7m4tbS4uLe5uLi2trq6t7y7u73AvL28ur26tre6uLnYy8C/vsDAvsLCxMDAvjC/v8TFxsbHwcHCxsTKyM/Ly8nMz2rO0dTYbm+CbWhoaGprbWxtbW5xd3JxcHBxc3NWX19lZWZiY2RjZmhramxtcmxtbW2BdnByc3R0c3RzcXlzdHR4eHl8e3x+f31/f4KDhYSDgYWEhIWHhoOFhoeLi4qKio6QjI6LkpOSlJWTkJGUk5STlJOEl2iWlpmYmJeYm56em5qbnJyeoJ+fraKfoKGhoaCgnp+dnqCfoaKioaGjoaGfoJ6fn6Kkpqaio6OjpKGjpaWoq6qqp6app6uko6WkpqOko6Gfn6CfoaKjo6KioKCdn52dnZ6en52bnp2fqISeEJygoJ6anZqbmpydmZeYmpaEmICbraGYlZSRkpGRlJWTkpOakJGTkY+TkJGTlJiVkZCRkpGQj46QjY+Sjo6Rj5ONjIuLjIyOjIqLiIeIh4SGioeKiYqJh4KIiIiGhYqmhISHgISGiIaFhoWHkICDg4WKio+NjI6IhoaIiYmJi4mMiYmLjYuMjY+RlpCUlJOUlJGSlICSkpSTmZWUlZSSk5WUl5qcm6CeoKCgoqGho6Olp6arpqWmqKirq7GzsKyzrq6tq7G2srW4tba4ubm5uLe5uLW4ur3AwL67urm1ur+9wsC+wcDA7MbExMLFwsfIxsbIycnJxcrLz8/Ozs/NzM3Nz9LQ0tTQ0tPR09LXzc/LzsvNy4DMzs7Qz8/P0MzOz8nKzc3LzMjIys3Ly87Lz8jIycvLycTHxMPHxMC6vrnDurq+u7m4tLOxsbO0sbGyqqukoaGdm52enZyfo6SampWYlpaTk5LrtouQi4mFhoOFhIOFgoiCmYL99PDw8/Lx8ero6O7n3+Hm4+Hb2N3X1tTbz87L1IDRzsXFwb3Cv73AwMHKvLm7vLe2tbC3s62tqqakop+hnJeZl5WWm5KWkY+Ni4yHiImGh4aBhIOFg4GFf4R9fHt2iH95fYKAf/l9ffPtduPd29jb29rg2t5wcd5y3d3k3OF5d9nS2tXS0NbY0tLSZ2ppaGtu4OHf3tzX2HXa3XNzv4Bu3HDY1drb0Glra2lpaNPRympnZWRsbGdqaczMZGZsbG1vcm5rZ2hqbGttbYN0fHx0cXJ/gIJ4d314eZhwg3p3dnR5fHt5fX99en18fIGCgH99gIKIiYaMiIKDf4aFiIqOi4iMjYyQjo2PlI2Pko6UlZqanpqbmJmSlZSYl5mflYCXlZeZmZugmqObm5eXmJubmJebm5eam5ydnZqdnJuenJ2en5+cn6KepaCfo5+dm5memqKimZ2cm5eWlpSblZKWlJmdn56cmp6ZnpuYl5ycnqCfnp6dn52Ym5qYmp+bm5ycoJ+goaOgncO2oKGipKOhpaOpp6qqrKerrKutrqaoqCappaypr6mpqa2yXrK0tLRbW2VcWFlZWltdXFxdXl9lYWFgYF9gYJR/AYD/f/9//3//f5p/AoGAkH/kfgZ9fn59fX6KfQR+fn1+hX2Cfot9hn6HfQl+fX1+fn9+fX6FfYZ+g32JfoJ9/37vfgF/hH6XfwICBACAjZCSlpKTkpGTk5WVlJaZlZmal5mcnZ+hpKWmp6anqKipqKeoqqussK6ws7G2t7e4vLq3uLq8vr3Bv8G/wMXBxsK+wMLCw8HEzMrJztDLy8nExcTHw8fFwsjJxsXFxcPBxMPCxMzJyc7Q0dHMz8vKx8fIxMHBw8O/xtHHxcXExcOAw8HDxcPGxcXDxcLBwcLBxL+/vLy9vL/By8jIyMzKyMnNzMjJx8i/wMHCxMTEw8TFvsLDxsnLwsLBxMbFxMO+v8DFxb7AvsXDxMPFwsPBxcbExcPDxsTFwMDBw8XHxcbGw8LFxMbEw8PEwsPCwcPHx8jGysnGxcnHw8nGxsTEwr+AxMHCwL27wLu+vr+9vb+9vrm3trO1tbi7trGtrKikpqWpqqmqqaqloaGcnaKkoaGkoqGfnKCbm5uam5qYmpiXmJiZlpaTlJOSlpaVlpaQk5SPkJKQkJGQkpGSkZKPkJGUlpGUlZOUlJSSk5SSkpqRkpSampeWmZiXm5aWlpiWl5mAnJqdnaCdmp2enZmbm5qdnZ+dnJ2enJ+in6Gfn5+ipKGfnZqhn6KloOOlo6GjpaqorKupqq6rrqypqqmqsK+vs6+trausq66tqKmtsa+usrCyr7KurquurqqorKuusbKwsbS0s7KxsrOwsrGxr6+ztLa0srOysbSwtbKyr6+wsrCAs7Gvs7Krq6mvq6mrrbiprauoo6ansKqoqqqppaKcm6GgoqGenpmbm5STppyPjpCNi4qKiIuNjY6JiIiHgYOBgoCC/4H4+/L29vro6Ojq7fXy6Ojr7/Dq8+7n5OHb19HQxsrKyM3J0L2/vbe5ur+6vcHBsbKyq6inpqesrKmhpJaAm5qbnJaWlJWWl5WSkJORkZGQjYqNjYuIiYyKiIiIhIaMgIH5+oD+gfbu5Ofv8fD5gP2Bhv/99oD6+oH19PL29vD1//r6goOEhIWHhJCA+vf7+Pb8gISAh4iLh4OCgoaBg4GBhYqDgv/09oOChoaFhoiJiJKFh4iKkYqKj4+VmJmAnpiZk5CYnZ6WlpOZl52do56bnqGjo6Cpo6GgnqSpqaqrqqyrpaiqsLHatK+urq2xsrW6wbu/xMbCwcPH0c3NycjJzMvT19TX1NHZ1szV/NnX3dzg1dfW3eDc3d7f5Nzg297g2dzc1drd4eDh4NzZ3ePf3trf4eHm3dze3eHi4+CA3+fg3OHi4ODe3tnh4ezh49vc39zb2dTS0dXU2Nnc29vb49/g5Nnn2d3d4NzZ3Nve49/d4d3h3uTl6ezu6eTo5N7f4+ru7evy6uvy9fPw6+3x7/Lz9vbx8PXw8e/x7e/x+PHziPz2/fr8gISBhIKBgYCDkYOEhYmIh4qKjo2Mjo98c3V2end4eHl7en18e31/fH+CgIGDhISFhoaGh4eJi42QkI+RkpGRlZOUlpSWlZWVmpubnJ2en52gnqCeoKKgpaKio6WlpKOlq6inqquqrKmlp6aqqKupqKusq6usr62sr66trbWxrq6wsbGutbKwr7CxsK+vsrKutMCzsYSwgLGtr7GvsLCvr7Cvrq6vsLOxsrGxs7GxsbaysbG0s7Kyt7Sxs7O1r7CvsbCvr7Cys66xsbOztbCvrq6ura6uqqytsbOtrquwrKysq6mpqKmqqamoqaupq6elpaamp6ampqWlpqaopqWlpqOkoqOkp6amo6SjoJ+jo6ClpKWjo6OgZaKioqGioaaho6GgnZyenZ6dnJ2cnJqcnpmXlpeWlJaTlZORkpKUkZCRjY6PkI6OkZGRkI+Sj5CQj4+NjIyMjY+Pko+Qj5CPj5GRkpKTjpKTjo6Qjo6Ni46MkJKSkZGSlJWQkJKShJSAlZaUlZ2Ul5ecnJqYnJyboZ6foKOfoKChnqKhpaKgoqKioaWmpaenpaKio6Wjp6ilp6epqqytq6mqp62rq6qn46ysqqqtr6uwrqysr6ywrq6vr66xr6+yr6+wsbOzt7awrrG1r6+ysbOxtbGxsLO0s7KzsrO0s7Cys7S0s7O0tbKAtbW2tLS0srKuqqytr7Ovs66tqaipqaeopqWpq6WkoqejoJ+irZ6joJ2YmZmmmZWVlJWUk5CQk5KTkZCQjZCQiIaRj4OAgn9+fH17fX59fXh3eHZxc3Fyb3Ddb9bb1Nrd4tPU0tHR1dHJycvNz8nPysPDwb++vsG5vLy3ure9rrCAsKurrrKsrq+upaKjn5yZmJWWlZOUl4yLiImJhoeHioiHhIGBgoGCgYF9eX17d3V2end2dXRxdHlzduTkdOd25uLe3+bm3+Fy4HJ35OLcc+DjdN3j4uXj3drd1tNra21tb3V1gHTh4ODd2uBxdG5xcXJtbm5wdnFzc3B0eXR15d6A3nJxdXNydH51dHtxdHZ3fHV2eHd7f32FgIN9fYKCgn16en9+g4OHg4CBhoeIh5CJiYqJjJCNj46PkpGKjY+UlbibmZiZmZubm52fmZ2hpKGio6atqKinpqaqqa2vrK2trLOyp7HYsa6ysriws7K4ura2tLS4sLWzuLq0trazs7SAt7S0s7CxuL25t7W4urm9trS2tbm3uLe0u7e0ubu6vLm6tbu6xry+t7e9vL/Bvry8u7m5uLm4t7e9urq+t8u2vbq6u7a3uLq+vb3BvsO/wb69vr68usC/ubvAw8TDwcS/wcbKyMXDxcjHycnJyMbGycnJyM7KztPZ0M9009HW1dcXbG5rbmxrbGxvfG9wcXNxb3FydHVzdXSAYGFiZmNkZGRmZ2pramxva21ua2xtbm9wc3NzdHV2dnd5eHd5enp7f359gH+Cg4ODh4aEhYWFhoSHhomJi46MkY6MjI6PkJCSmZaSlJSSl5WUlpSXlZiWlpqcmpuanJuanpybm6GenJ6goqOhqaahoaOlpKGhpaOfpKmjoqKjpKSApqOlpqOko6OipqWmpqenp6Sko6SmpqipsKurq66sq6uuramrqqykpqSkpKWlpqenoaSkpKaooJ+eoaKhoaCdnZ6jpJ6fnaGgoZ+fnJubnJ2cnZuZnZucmZuanJ2cmZmXlZWXl5qZmJeXlJSTk5SWlpaUlZSRjpKRjpOSk5GQkI2AkpKSkZCPlI+RkJCOjY+Oj4yKioiJiYyQjo2MjIyJi4uNi4iIhYeEg4WDhIaHhoeIh4eFgoeEhoyNj46OjYyLjI2PjI2LjIuKjIyLi4yJj5KQkpaUlJSRk5GTk5STk5WXmZaWl5WXl5iZnp+goamfoaKoqqakqKalq6ipq6+usbOAtbO1srSyr7KzsrG2t7a6u7y6urm6tLe4trq6vsDCwby8vbvEwcPEvvDDxMLEx8vJzszKy83JzczLzs3N0tHR1dDQz83Q0dbY1NPX29PR1tTZ19vW1dLT09DP09TU1tbT1dXV09DOz8/Lzs/PzM7Q0dPQzM7NztHL0c3MycjIx8KAw768wMG8vbvBvbm4usGzuLeyrK2ruKmlpaSloqGenqOiop+al5OWmZKT1buRj5OPjYuJhYWGhYeDhIWFf4F+f3x++Hzv8Oju8Pjo6unn6Ovk19TV19fN2NHLycnGxsPEvL6+u7+7yre5vba1ubuztrWxpqWnpKGenJmZmZeZnZJGk4+Sk46NjI2Ni4iFhISBg4KIiIWKiYR/f4F8e3x8eoCEen3s53fseO3o3dzj4dfbbtVtc9vd13De3XPc4N/k4NfW3NXTbIRqgG1tenDk4+Pb2NZsb25ycnJtamprb2xubGtucWlpzcjHaGdsa2lscGtrdWVmaGhuaWlram1ub3RxdG9tcnV2b29uc3F3eHp1cnN1dXR0fHZ2d3Z8gX+DgoGDgnp9f4J+jH99fX2AhYSHiYqDhoiLiIeIjJKOjoyNj5SUl5eTk5COgJeYkZ3QnZudmp2WmJmfpaCfnJ2gmZ6cn6GbnaCanJ6hoJ6dmpmepaCfnqGioKOcnJ2doKCioJ+mpKClpaOhoqOgpKWvoqWenJ6dnJyYlpaYmJydoJ+enaOenqGZsZmfoKCjoKOho6WfnZ+anJqdnKCkqKersq+no6KkpKSjp6GgNqaopqSipqurrrCuq6imqaqrqq6mqa6yqqphs7C0sLJYW1hbWlpbW11oXl5fYV9eYGFkZGFiYf9//3//f/9/xn8Cfn/ffgV9fX59foh9C359fn59fX1+fX1+in2JfoZ9k36Dff9++n4Bf4V+l38CAgQASpCTkpGTkpSUl5eXlpeZmpeanZmdoqGkpKWnqKakpaWoq6yurauvs7Gxtbi5vL+8wMLCwcHBvL/AvcLBxcbJx8fGxcTGxsnFyMfLhM+AzMnIxsnKysfNzMzJysnEx8LHxsrKzM/U0dPSz9HUzc/Qz8/LycnKxMnJzs7OzcrMy83LzM7MysnHycrIyMXIxsTFwsHDwsbBxMnLzc3KzczNysrLzcnNzsvJycPFx8rKysnJxsfIysvJycbKycbFyMnHxL/DwsXDxcrLx8jHxMOAw8fFxsjJxcnIxMjJyMjKx8bJx8nNysnMxcTEwMPGxsXLxsrMz9HP0M3GxcjJycjJyMXGxcTGw8LEwsDBwcLAwcG+vLe4s7O0tbq2tLKurKmop6uqrKupqaaroqKhsaino6auoqOin5+dn6CdoZ6bm5uZmJuXlpaWl5qanJ2dmZVemJiYlJWXl5eWlZeWlJGSlJKRlZWTlJGRkpKTlZaWkZWUlJWbmJqdo52boJuamJmbmpqcm5+cnZ6fn6GgoKKin6Ggo6KjpKKmn6ChpaWlpqampaWjpaKkpaerqKepqoSrgKiprqyusLCtrKqsq7Crrq+vsLCtsa/9s66zta+0trKzs7Oyq62ur6+zrrCwsLW2s7e4t7e3tb66t7K8t7a3tre5uLSzr6+vsLK0tLK4sra3tbe6trWurqqtrK2trrKtrK6opailo6Ompqimp6KgnZyaoaOgoKCcmpmWWZ6VkZKQgI+Ni4yKj4+MjI2IiIKFhIOCgPqB/P/++/X99PPv6e3u7eje5erp9vvr6+bh3eDU1NHM1tHMzs3Kwsa9ube7urW9ubmzrKejpqeor6yrop6hoKOkm5eWl5eWlZSQjYiJiImMjpGNioiFhoeJi4eRiYqbhIH+/ff7//P18vPr8vb3V4D5gP72/IiDlIH9/Pz39fPy9/v5/YCAhoeJiIeEgYaCg4D4goCEhICDhoOEhPv3/YGAhIKDhIWE9/uAg4SmnZGIjYqDjIuOi42SkJaYoZCRkZWen5icm4SYgJaaoKCioJ+ioKKnpqGhpZ6jn6arq62prq60rqq+tLm3uLaxsbi5vLy+vcDDv7/CxsPBw8bH0MzQ0tvX2Nra2Nzh5OD84N3i49zb2N7c3t7k4+Xo5+fq5eXi5uTh4OHk3eDn5ufp6Ojo5OHf4tvd3+fm6OLd2N3i3d7b193q7tzddd7e4dz55eTe5Oni4Nja1NnV1dze4eXn4N/j4OnZ3Nzf3Nvk3uPt5+/h3Ojp5erm7uvt6+vm6Obq6+vl7erp5/Hw9fXy/fL2+/r7/ff69vb58+/x84CCgfuA/PuA94KHh4iDhoWEhIeIhoSJiomMjI2PkJGRkQl3eXl5enl7e32EfoCBgH+BhoOFiYiIh4eIiYiIiYqMj5CSkI+TlpWUlpiXmZmWmZ2dnp+gnaChn6KgoqKjpKSlpqepp6imqqipq6yrraypqaeqrKyqsLCwr7KzsLSwtLS0s7GytLGys7K0ubO0tra3s7S3uLO3trm5t7aztrW3trW2tLOxsbK0srSxtoC1tba0tLW1ubS2uLe3trS1tLi1tbS2s7i5uLa2srOztre3tra0tLS1t7azsrOxrq6xsrKwrLCur62tr7Gurq6srKqurKytrqqsrKmqq6qoqKemp6eoq6iprKemp6SmqKqnq6eop6mppaenoqOnqainp6aioqKgoqGho6OioqKhn3ifnp2enJ+cm5qanZqamZiZl5eWl5OVlJKTkpeRkZCbkpKOkZmRlJSTk5GSkI6RkI6PkJCRk5CQkZKSlZOUlpeTkZSTlJCQkZGQj4+SlJSTlJeVlJeXlZaVlZeXmJucm5ibmpyanpicnqWhnqOgoKGjo6Gio6KmoqSGo06mp6epqKmmp6enrKiqqaqrq6usra+trK+srq+usq6rra2tr6+wsLCzsLCytLKysbSztbGzsrS2trS5t/W/trm5tLe4tbW1t7mxtLa3t7+Et4C6uLa5ubi4trO9uLWzwLi4t7S0traztLC0tLWzsbCrrqqsrKmpq6qsqKilpaCgn5+ro6KjnJibl5STlpWWlJeVk5GQjpKUkpGRj4yLiGKahYCCgH99fH18gYB8fX94e3R1dHNycN9y4ePf3tjh2tnX0tbW09DHzM7O19rNzMjEv4DDu7y5tr23tbS0tK60rKqqrK2oq6enpKKgnJmZlpmXlpWOjImMjIiJh4aGhISHhYWEhoJ/f359eXh3d3l5eHh2fHZ3h3Z05ePj5evm5+jm3eLi4XTldOjl5nt4g3Tm5Ofm5ePh4d/d3G1scXN0eHd1dHdzcnDXcXB0cm1vcG5xdIDg4OZzcXNxc3V0dN3hb3JxkI19c3d0cHl5gH19gnuAgIt8fHp9g4J9gH59fn+AfoCHiIeHhoiIioyMiIeMiY2Jj4+PjoyOj5OQjJyVm5mdnJiZnp2enJ6eoKSio6irqqioqaqvqqyssa6vsLKusrK0s9a1sra5tbSzuLi5t7m3t4C5t7e9ubm6vLm4uLi8tra7uLi7vLu7ube5u7W3t7y7v7q2tbu9u7y7ubzJ1bu9vru8t9O9vLm/xMHEvcC6vLm3u7q8vsC7ur6+x7m9vb+9vMC6vMK+xr69y8rGxrvAur29vby9vcPDxMHFwcG9xcbJysnZx8vMzMvKyMnGys7NzhrP0Gxsas5p0tRr0m5wb3Ftb3BvcHJzcG9ycoRxBXN1d3d3PWJiY2JjYmRkZ2ppa2xvbmxtcGxvcnFzcnN1dnR0dHZ3enp7eXh8f39/gYODhIWDhomHh4iHg4SGhImJjY6EkYCSkZKTlJOYl5mYl5SWlpWXl5mbnZidnJubnZ6bnpufn6CgoKKmo6Wlo6SnoqOlpqemp6mrqKqpq6qnpqOmpaipqauqqaimqauqq6qtrKqqp6aoqK2prbGwr66rrauurKusramtrq2rq6epqq6urKqppaWmp6mnpaSmpqKgpKalo4CfoqGjoKGlpaOjo6CgnqGfoKGgnqGhn6Khn56dmZeXlZicmpqfmpmZlpaXlpOWkpSUl5iYmJeSkpWXlpSUlJGSk5OWk5OVlJOTlJOQkI+PjouNi4uLjZCNjo6NjoyKiouKi4uIh4aLhYaFk4qJhomQh4mIh4iJjY+OkpGPjo+NjoCQjY6NjpCRj46Pj42LkJOWlJSWlZSTk5WUk5GRk5KTl5mXmJeWmJqcoKKkoaWkpqWqpairtKupr6usrrCysrW2tLm0tLSztLSytLe5uLy8v7y9vbu/ubm5vr6+wsPExMPBxMDCwsXKxsbKysnKzM3OzdLPz9DS0NHR1dXa0tXS0IDS0MzU1Ord297g2tvd2NjY2dvP09TV1uHW2NnZ3dvX2djW19XS39bTz9/V1dXS1NbW0tLNz8/PzsvIxMjCx8fCwcPAwb6/vL67u7m3v7W0t7Crrqmlo6alpKOkoqKgn5qfn5qYmZWVlZSCxJWSlZGRjoqJhomHhIaLhId/gX9/fYB68Hvw8/Lv6fPr6ubi5ePc1crO0M/X3M/QzMzKzsbHxsLLw729v8C7xby3srCtp6yoqaalpKGhoJubmZqYk5WWl5ePjYqKiouMjoiFfn+AgISGhoODgH18fHx7eYB5fI56efDy7/Hy5+bj4dne3Nlv123a1dl2cn9v3t7g3tva2IDY2tfYaWdtbW1xcG9tdXJxcNVubHBvbHFycHBw1s/UamdoaGhpamzNzWZoaoWEc2RoZmNpaG9rbHNrbW57aWxsb3h3cXJxcHBycnFzd3h4dnV3dnh6eXVzeXZ7eH+CgoR/goGEgHqHfX97f396f4aFiomKioyPi4yOj42Jio2NloCSl5memZiWlZKWmp+f1KCenp6ZmJmenaCgpKKhoqCfoqCkoqOgn56eoJmYn6Cip6qoqKOfnZ6ZnJ+ko6ekoJ2lqKSlo6Kosrmop6SjpJ3DpKCaoKOenZiZlpubl5ybnJ2fmpmdnKWXm56gnp6knqKpoqmcmKKjn6Kfpqaqq62rqyypra2rp6uko56lpKmqqberq62trbCtr6qtrqipqataWlmvWrKyWq1bXVxeW4RdD2BhX15hYV9gYGJjY2RjYv9//3//f8l/AYDjfwGAl38Cfn/ffo19Bn59fn19fYR+i32NfgF9in6DfYh+gn3/fvZ+CX9/f35/fn5/fph/AgIEAICRlZWalZiXlpmcoKCgnqCgoKGdoaCkq6alqaqqqqupra6wta+wtba5uLm6vLzAwMHDwcXGx8bCwcXGycXIysvHx8fFzM/QysvN0NLU09POzc7Qz87Pzs3Qz83O0MrMzM/Q1NTY3NbX1dXU1dTS1NTRzs3Nz8/MzM7OzszNy83Qz4DMzs7Q0c/Ozc7MycrKy8nKyMvKxMLFxszP0M7Q0tDRzM3N0NLR0tDNy8vLzMzSzMrKysnOz9LP0NDOy8rKzMzNxsjIy8rP0tjQ2M7OysnKzM7OycnLy8fGy8nKz83Ny8vLzM3Ny87QzcnKycvJzMvN0dLS0dDMzMrNzs/T0dPMzYDNy87IycfGw8HDxMLH0sG8vr67uLm9u7q6ubayrqywr7Gxra2srKeopqWpq6qqqq+rpqako6SgoKGeo6Kgn6CdnZyenZ6gnJ+gmqKfnZybmZmXmZiWlp6Zl5aWlJSVlZSYmpuZl5eZmZaak5WVlZOXmZean52en56in6Gcn6Sen4Cfm5+foqChpKOkpaWlpKSoo6anp6qwqaulpqenp6ipqqmko6epqq2y1bOvsKysrKqsrKuvs7SwsbGvra+xsLK0s7azsa+usrCwt7ezxba2tLWxr7Cxr7GxsrGys7S4tba9vr68vr+8u7u5ur21urm6uru6u7q3trS0tri3u7m8vD66uLi3ucC0sLGrsK2us7Cxr66qrLSsq62oqKaop6CjoZ6ioqKho6CfmpeblJ2UkY+RjIqNmJSRkI2RjY2GhYSCgIOEgv+DgYD7+vfx6ujp5+rr8fDx8/Ps6+7g3dzW2NHS4t3Ry83IzMjFwL2/v8DGx7q0sa+qqaeqrrGrqKWmoqOjpJygn6KhnZWSjIuIiIqOkI2Li4mKhIaIhYiLioiLiIL/g/OM+/Ty6fDw8/3+hIWIgP79iIeJhIOFhP/6gID5gIL9/oKBgYeJi42JiYaFhf79+/2NmYWHi4GEgIKCgIaPkouJi5OEhIeGiIqPnZCYjI+NhpONjo6RlZOam5+ZnpqZm5mbnpuxm5aTkpSipaWjn6alsKiloKi9qKWlr6ursa6vsbKzsbGvtbOxtLq4usTGyMXMxcfK1srExsnLztXLgMrS09LX2t3j5eHk5OXi4uro6OTi6eLn5ero7fHz7u7o7ejz6OTg5fjl5urz6uv07ujo5ODg4+fm6+z27Ojo5OXl6OPg5OTl6OLm+eLi5OLm4uTm5eHh4uTh4dzi4OXn6uPf6t3e5ePh7e7n6+bk7uzk5OTe4+Pr7/bo6efu6ubwM/T18fDx8fjz8u7y7/H09PL5/fr68/f49/Xw+PyAgIOC/4CLhoCEkoWGhYiIhYmIh4aIiISMB42OjpCSkpCAeHt7gHx+fXx9f4GBgICBg4SHhIeHio+JiYyMjY2PjZCRk5aSkpWWmJeYmZqZnJydn56ioqOjoKOnqKmmqKmqqKmqqausrKqsrrCwsa+wrKqur6+ur66usrOztbq3uLi5ubi2t7q1trS2trm4uLm6t7e3uLm7uLi6ubq3ubi6vL2FuTG1trS3trW2t7m3ubm8vLi3ubi7vLm4t7q4u7e5ubm6ubu7uri6ubm5xrq6uLe1uLa4hLaAtbS1t7e4srSxsa+ztru3wLOyrq6vsrOzr66xsKystayssK6uq6mqq6qsqqutqaeop6ioqamrra2sq6qmpqWoqaqsqKqkpaemqKWmpqWlo6SkoaSvoZ6ho6GgoKOgnZybmpmZmJycnJqXl5aXlZeVlZiZlZWVmZaTk5OUlpSVk5AdlJORkZKSkpOUk5WWlZiZlaOYlpaVk5aTlZSTkpuFlYCWl5eVl5iZl5WXmZybnpibnJuanp+cnqCen6KgpaKjnaKnoqWkoqWkpqSlqqipq6uqqamtqaqqqqy6ra+qrKysra+usLCurrGzsbG11bWxs7CysrK1tbK0tLWytbi3t7m5tra4t7u6u7m5vbm4vby4y7q7uLm2uLi7u728vbu7uxi6u7e3vLy8uru9uLe4uLq8try4t7e1tLaFt4C1trGxsLGwsK6sq62yqqmqpaqjoKSioqCfm5ylm5ialpeWmZqUl5SSk5KSkJKQj4qHjIWPhIB/gHx6fIeCf399g3x8d3d0dXR1dXRz3nFvbdrc3d3a2NnW1tTW0tTX29jY2tDKxcDBubrGv7eytbO4t7SurK2ur7O1qaWko56bmoCZmJmWlpGRjpCOjomKhomHhIGEg4aHhoSDgn17fH1+eXt6bXZ3dnV4d3PkdumM7Ovt5ejm5OTgdHV2cuXkfHx9dXV2dObgc3Tbct7fcHJxdnd7e3d5dnNz4Nzd4nuKc3V6cHV1d3d1dnx7dHFzenBydHJ0dXiHdn10dXZxfHl4eYB8fnuBgYN9gX6Bg36AgYCUgoKDgIOMi4uJiIuMlIuMiI6gko2MkYyLkI2OkJKWlZiWnJuYmp+cnaSgoZ6in6Wqua2oqaytr7Srqq6urLCws7i6tba5u7m5v7y8ubi9uLq4uri8v7+7vb2/u8e9vbm90Lq2uMG6vcjCvb69u7u9voC9wsDMw8LGwsPCxcG+wsHCw8DC0Ly8v73BwcPGxsTBwcG9vLa7uL/Bw768xL29wcC+xcjBwb69xsbEw8XBxsXLyczAv73Dwb3ExsXCwsTEz8vKyczLztDNyczPy8zKy8/T0M7U1WtrbW3VanNwam1+b29ub29ucnJzcnNydHNzcwdyc3N2d3l4gGNkZGhkZ2ZlZ2tvb3BwcXFxcG1wb3J4cnJ2d3d3eXd5enp+eXp+f4KCg4SFhIiHiImHi4mKioeKjY6QjI6QkZCRlJKWmJeXmpycmpqYmZiXmpycm5ybm56dnp+joKGipKSlpKeqpaelpqanp6mqq6mpqautr62tr62rp6ekpaemgKWnqaytrKysr6+sr66wra6sr6+sq66tsrKvra2vra+rrK2ur7CysbCvr7CwsNKvrqyrqKytrquqqKinpaWlpqijpqWmpairsqmyp6eioqGipKSenaGhnqCxoqKmo6GenJucnJybn6Cenp6cnJqZmJiampmYmZWUlJiYmZyYmZSWgJaWmZaYmJeVlZaVkpWhj4yOkY+Oj5OQj5CPjoyLioyLjIqIiYiKiYqHh4uPjY6Ok42IiIeJjIyOkI2RkI6OkI2PkJGQkJKRk5SQpJKTk5WVmJaZl5aVm5iYmJeWlJSUk5aanJuZm52foKafpKWlpKmqqKutq62vrrSxtbK2vbi6gLm3urm6ube7uLa6urq7vcPAwsLCw87Awby+v8LExsXGx8TExsjGyc7l0M3Oys3OztPSz9HS0s/S19jY293a2trW2dbV1Nbc293k5OD34N/c3NjX19nW2dja2dze3ePd3OHe3drc39rY2NfZ3NXc2NfX1NPV19fW1NPQz8nJx8vKgMfFw8DEysLAwr7DvLq8urq3trCwvbCrraempqiooqWhnZ2cmpibmZuYlpqTm5OQj5GNioqSioWEhI6Fh4KBfX58enl5dup4d3fs6+rl4N7d19XR0c7R0tXQztLMzc/LzcbF1M/Fv8PBxsbAubSwrauusKWkpKOhn52dnqCam5iYgJKVk5KJjY2Pko+Jh4OCgIGBg4aAfoB/f3l8fGl6fHt6fn58+YD1j+zm49rg3+Hl4HN0c2/e3nl4e3Jxc3Db2G9u027T1Gtra3BxdHNtb2xrbdTS0dV4j25vdGtua2xraWpzcWxqbnRpaWtoaGpwgm10aWlpY21qamlqbWlvb3JtgHNxdXl1dXVzhXRxcG5ve3p6enZ5eYJ7fHh9i315d4B8fYF/foCBgH17d3p6eHyEhYWPjo6Nk46RlqCXkpKVlZielpWcm5eZlpabnZufoaKfnqSgo6Cfp6KnpaajpaaopKahpaGwoaCeo7Win6Sup6mzq6aopKGipKWmqaqyqaWpgKampaiipKmpqaulp7mkpamnqaWlpqWhoaGkoaKcoZ6jpKWhnqWcnaCdnKWmoKSfn6qnoJ+emp6ep6iupKamrKmmq62sqKqtqq6qqKapqq2uraessK2urK6vsqyosLBaWVxasVhgXlhbbF1dXF9fXmJiYWBhYGJiYWJjY2JkZGRi/3//f/9//3/JfwR+f39/0n4Bf4d+BH1+fX6JfYR+gn2Hfgh9fX5+fX59fYx+hH3/fv9+i36EfwF+nX8CAgQAOI+TlZSZlpmZnJ+ioaWopqSjo6Cio6SkqKuurrCvr7Cxsbe2t7a4vLm7uLq6vLzAwsTDwsTFyMnLhMeAy8zP0M3Mzc/S09LOz9PV09XT0tjW1dTU1NLQz9LO0dHQ0dLW09nZ2dvW1tfX19bV19vY1NLS0NDO0NHQz9DRzcvQ0M/O0dDh09jQ0tHS0dDUzs7PzM/Qzs/Qz9HS1M/T09TT1NHR0dXQ0tLSz9DPzM7R0dPT0dDU1dfY2dfWzs+A0NPQ083T0tTU1tfY19fT2s/Mzc7Lzc/R0tLRzszKysjLyszPz8zP0MvP0MrLz8zOzc/N0NDT0s/Mzs3O0dDX0tjW1dXSz9HOy83GxcXHyMrQ3MrLxczAvr68vb25vLu2ubW4u7a2trO0srCxrKysqaypqqyrs6uopKSnpKSioqKAqKWmpqemo6ShpKGioaChn56epKCbmJqalJicmZeXlpiblpibmJmZm5uZmpuanZiXmZqZmp2bnZ+goaGhoqCgoZ6hop6hnqGipaiora2ur6uprauqqaqut6+wra+tq6yorKmrrKytra6tsLSzsbGxsK+xrKyysbOytLSvsK6wsbKAs7S2tbq0tLKys7K1t7i6vLSztrezs7Kzsq+0s7G1trazr7C1uru/vsG+vsHCwr++vL/Bv72/v76/ubi7trzAu729u768vLi3ubiytbWzsK+0tLO0sbCzrKuprKauqqqrpqKkoqOioqCipaKbmZmYl5aUlJGSjo6RjpKQi4uLiIpbiImKhYWEhoSFgoaKhYGB/PX08e7t8e71+YD19unq6N7f5t/a1d/g4dzT1M/MycvCv72/0cW+s7O3tK+osK+praympKilp6Wgn6KepaOenpOTjIeMjJWTkoqJh4SESYaGhIiDjYaBgoaBhImC/+/t+f36gYKJ/ffzg4Hz+4KC/oOAgISBgoeDgISEgYqJipCLioiKhIOIiYmDh4OHi4iCg/2BhYeIjouEioCIio6KjpaMjJOgkJWPjI2Nj5OVmZmfoqCZmZWcm5yal5OSj5aeoKivq7Cso6OgpaatsrGur62yrLWvrq6yrLa7uLq/u7/ExcTEwsDJzcrLzMvPxcnT5dDV2dDY0dHZ3dve3uvq5OXk6uuA8O3o6+zr6/Xq7fn18O78+fPl5obm6IDw8PPu6u3u8u3t7+rn4+jo6ufq6O3q7eXt7enr6fHp7Ozo5uLl7OXn5+mS7Ozi6ebp7eHh9+Po8eXu8Oro6O3t7u7v8vPy9/f09PTv6uvr8e7w6vH16fjz9/T/gvTw8fn6+Pbu8+rs7/P5gfuCgP6B/fj8/fmDgv+BhYWDhoaEhxiDgoSGiIaIh4eKiYqIi42Rj5GVlJOUlJQxeHt9fYF+f3+AgoSChomHhoaGhIeIiomLjo+PkI+RkpOSmJaXlpecmpybnZucnJ+ho4SkaKakqaaop6msq62vraytr7CwsK+ws7SytLKws7GwsLGysrOzuLa6uru5uby5u7q4ube4uby8vby9wL67u7y8vby8vby7vL27ur69u7m5uci7v7m6ury8u8G8vL27vb++v8C+v729uru9hL6Avb3Au7u+v7y+vrq7vry9u7m6vby8u7q4vLm8vb+8vba5t7SztLW2tbe0urSztbeys7KysbCwrq2trq2wsbGwsa+usKyurqqrrqysra2rra2xsq+ur66urqywqaupqaqoqauqqaynqKaop6aqu6Wmo6uio6OhoqCdoJ6cnpueoZ2AnJ2ZmJeXmZeYmZaYlJeamaKZl5SUl5aWlZWUl5STk5WXlJiYnJyenJ2empmZoJ2ZlpmZk5WdmJiZmZyfmpucmZuam5uam5ycn5ydnaCgn6Gen6ChoaKjpaSlp6SnqKWop6iop6moq6qtr62usbCtrKyuuK+vrbCvsLKxtLCxsrOAtbO0tLO2tbKztLS1urW3vbu7ubq6t7m4ury7ubu7usK7ury7vbu8yL2+wbq7vr+9vb7AwL3Cv7zAwcC9ubm6vbu/v7++vr+/wL29vL++vbu8vby8ubi+t7m7tbW1s7O0s66ur66rrKyppaSnpaSkoaGln5+cnpmfm5ydmZiYl5eAlpSRkZORjIuMjIqHhIR/gH19gH6DgX18fHp8e3t7d3h3eHR0cXJ1bmxt2dnb2tjV2NPW2HDX2dLT0srJzsnDvcO+vrixtLOzsbSurq6vxLOqoaCjpKGcop6Xm5eSk5aTlpONjIuEiIeEhoWIhYOEgYWCgn1+fn18e3p6eXV5dn2AeXZ2eHV3e3rz6eTs6ed0dXzl4uF8fOrxe3vrdnRxcnJxdXRxc3Nxd3R2eXR0c3RwcHN1eHJ9cHR3dXN253h6enh5dnNyc3JydHh1d392c3eCcnd0cXN1eHx9f36BhISCg3+DgICAgYCBgYaJioyOjZSUjY6Ji42TlpWRko6RjZSAkpGVlY+Wl5SWmpmdoaWlpqKepKaipKenq6Wosc6tsrWtsq6us7a0tbO/vbi7u8C+br+9ur/Av7/Mu7/Kw76+zMvFurpyv73Bv8C+vMPExcTDx8PCv8PCxcHDwMfFycTKycTEwMW+wsTAwby+wr7BwsSEy8rCxcPExbu6ybu/xr5fxcbDwcDEwsLDw8XGxcjJx8nLyMXIx83JyMLIyb7KxcfFzmrGxMfOz83MydHM0M/NymbGZmjUbNfW2NnWcW7Wam1saW1tbG9ubG5xc3J0c3N3dXV0dHZ4dXV5eHl5e3uAY2ZnZmlmZ2ZqbG9uc3V0c3JwcHFyc3N2d3l3d3d4eXt6fn5+fX+DgYOBg4OEhYiJi4uKiouLi46LjI2NkZCSk5CQkZSYmZuZmpycmpyZmZ6dm5ydnZ6dnKGeo6SjpKOmo6enp6qnqaqrq6qpq6+vrK2ur6+vsLGwr6+vq6mtrauAqqurv7C3r7OytLKytbCwsKyvsLCxtbK0s7Ksr7Cvr7GwsbK1sbK0tLGys7CytbOzsK+usbKysbCtrKWmpqinqaWrq6uqrKyuq6yprqalpaaipKOjo6Sjo6KjpKOkoqKgn5ycnpygoZ2gop6fnZyam5qcnJmYmpiZm5memJuZmZmAl5eamZmcmJiZmpqZnLWWlpKdkZGRj5CQjZGQjpGNkZONjI2KioqLjYuMjYyOi4uNi5CNjYuNkpCSkI+Ok5CRkZOUkZSSlZSVk5OUkpKTnZyZl5qZlJecmpiZl5mZlJaYlpqbnZ6dn6Ggp6Slpqmpqa2qqqysra2vtLS4u7e6u7eAuri7vb6/vsHBwcTAwMXExMXHytjKysbHxsfHxcnFyc3Nz83MzM3R0dDS09LQ1M/P19XU0tPRztLT2t7e3+Dh4OPe3NnZ2trf7uPk6d7e4OHc29rc3Nfd29nc4OHc2tvc393h4OLg3+Hi4d7d297d3dra2dfZ1tXd09bVzsvJx8mAyszHxsjFv8LAv7y6wL67vLe2vLS0sLGpsKmoqaWjpKOjn52ZmZyZlpWWl5WUlJaRk4+MjYiKhoGCgoGEgoOCfnx6end2dXd7dnR05eHi4dzZ2tbZ23HX2c/R0MrM087Kxc3LzcrEyMfHxsi/vLq607uvpKSqqKijqaWeoqGdmptMl5aUj4yPi5CSj5CJjoiEh4SIhoZ/f398e319fHp3d3R+enh4fHh4e3jt4d3l4tpxbnvY19d5eeHpeHfldHFvcG9ucG1rbWxtc3BxdYRtgGlqbW1uaHJobG5taGjJZ2lqaWxpaGlqa2hmamhreWppbXlscWtpaWprb3Bxb3FzdHFzcnh2dXRybW1qb3V2fIF9hYN7enh5e4GFhIGBfYF9g35/f4N+g4N9e3x6foOIiYuJiI+SkpSWl5qTlZ3Wmp+hmJ+XlZmbmJiYpaSfoqCkgKNrpqOfpKioqsSnqLCooqKsqKSdoWSlpqynqKOkq6yuq6esp6WjqKeqpqahp6WopKqqp6ilraeqrKqrp6iuqKqpqIippp+lpaiso6Gznp6jnKOloqGhpqWlpqWmp6Wrq6ysrqmlpqSqpqagqKmhsKyxrrZcq6ior66rq6aurK+uMK2rV6dXV7FasK2tr65cW7JYW1tYWlpYW1lZXF5hX2BgYGNhYmBhY2ZjZGdlZGRlZf9//3//f/9/z3+KfgF/036GfQ1+fn59fX1+fn19fn59oX4BffN+AX+TfgF/rH4Bf7B+AX+OfgZ/fn9/fn+FfgN/f36gfwICBACAl5iam5ycnp2eoqSjoqSlp6iop6qqqautra+xsLSxtL62uLq7uLi6vb/Aur7BwsTBxsTGyczLy8rLzcvLyczMz9DS09bZ3NjS1dLU1tba1tnc2tnd2trc2dnZ2tvY1trZ3N7h4ODg29fZ19fW2NjZ2dXU1tnW19jW09TSztHRz9GA1NLU0tXS1tTO0dPT1dLT0tLX0NXV1NPY3NrW2dza1tXa1tXU1djY1NbV09PU1NXW2dvf3dzY3dzh2NbU0tPT0tTW09XV1tfX2NjY1dnT09TU1dTR0tPSzs3Qys7PzdDS0dLV09XY19PR09TU09TU2djY187T0dPV2Nrc2djW1tKA1NXU0tHQz8jNzcvMysrOzMnIx8XGwsG/v769u7e8uLy5ury8t7W1trOzsq+rrqqqra6wq6mnqaepq6Wlq6ioqqysq6qop6OloZ6eoJ+hoJ2bm6CgmKKbnZ2dmpubmpiZmpmamZqZnpqdnJ2eoJ2coZ6hpKempKWjpaWkoJ+ioKVVpaqpp6qqqa6urKyrrK6vrqywrq+zsbGwrq6tr7CysbGwtLOzsrS1srO1tLS1sbOur7S4uLW2tLKytLGxtbaytre3t7i3uLm7uby+ubu6uLW3tra0tYW3gLy6ur28vby9wMDBv8LEycTGv7+/hPjBxcbLxMK+xr2+vbzAv8HCvby6t7e9ube4t7q8t7a2trWzsrWtqqmlqa6sqaWioqGjpaWlpqelpKKampyXl5eVlpWWlpKVkZCLi4+Li4mKj4iLi4aEgYGBg4WD/vzz8/H1+Pv8+fX38urogPLi3t/l2+Xh4t/g2d7U0dDMxcrCwsC+vrO4u7q2r66tsq+xrKmmpKinpJ+on6KdmJaQkI+RkpqZlJKTko2RjZOIg4KGhYWDgYOCh4OGiYaEhvP3gIb9hoiHhoSFg4CDhoWGiI6RiYOB+4H/8vP0gYWGjI6Nio+Jh4iMh4mEh4q9gJeFhomGjYiGiY6Rj42Pj4yOhpCRkZOPlJyQj46SkI6Ul52irKKdnZudnpacm6CZnp6pqKeytrOwra+mpqqvsq2vqK2xs7GwsK+zsrm1u8i9vL/ExMXOx8vP0v/Ozc3Mz9TS1NPY09La3NTa3Nbe5ejqi+j18PTu8/rr7vH18/T3gPf1gIz18PDz9vjv8e3t8fXv7+7v6vP18Pf68ezs7+br7u3w7+7t8PHv7Pbx9e7s8e3y7erq6OXt8PDu7evx8+3p7/fu6+/y8fTv9/H17vP28PX39/j38/Lv6ujn7ezv7uvt//n0hID3/Pz7+Pb5+PX/+4H+9fr8+YD7/4CAgIGBJv3//ICBg4WHioiLiImIiYiJiouLi4iJjo2PkZCRkpKUl5qbmZqagHx9f4CCgoSChIaIh4eIiYqLiomLi4uMjY+RkpGVkpadlpeZmpeYmp6hoZ2foqSlo6elpaepp6eoqa2srautra+wsLCxsrS0sbWytLW1uLO0t7W0t7W2uLe4uby+vbq8vL28vr28v767v76/v8DAwcG/vsHCwcDCvr2+v73Bwb+/X8LAwL7AvcHAvL/AwcO/wcHBxb7DwsC+wMK/vL7DwsC+wr+/vcDDxMLDw7++wMLAwcLBwr+9ur++wr/AwL/Av76+vrq5uLe4uLi6urm9urm5uLa1s7O1tLKytLCytLKzhLJ5sLGzs7GvsbKxr6+vsrOzs6yxrq+vsrGzsK2sraqtsK+vr66vqq6urKuoqKqopqampaajo6GhoJ+gnqGeoJ2dn6Gcm5yem5ycmpmbmJiZmp2ZmJibmZubl5ecmJeZmpuZm5udnKGfnZ2enZ6dm5qanp2VnZmbnJyanISbgJ2enZydnKCeoaGio6aioqajpKWmpqSlpKeoqaenqamrq6+tq6ysq6+wsbGxs7S0s7GzsbG0sbKysbKztLW2tLa1urm4t7a3tLW3tri6uLu5ur/Av7u+vbu9wL28v765vL2+v8C/wcDBv7+/vsDAwcDBwsTCw8XEwsC+wsHCxMLCbr+/wMHBv8HCyMPEvr++fN+/wsTMwL66w7m5ubi6uru6t7Wyr6+0sK2trbCxq6impKOioaSfnp+cnqGenJmZl5eWlpSSkpOTk4+JiYmFhoWCg4KCgoCEgYF+f4J/f3x9gXp8fHd1cW9ubnBv2trWhNeA2djU1NjW08/ZycbFysLHwsG7vLi8uLi3trG1sbOxsK+jo6Smo52dmZ2bmZSTkpCTkY2GjoaLiIWHh4eFhoaJhoOAgoF+hn6Oe3d3eXd4dnZ4dnd1dXZ2dnre5XV65Xd3d3V0dnR0d3d4dXV+gHZ0dOR48eTm5XV2dXh4d3R3c3SAdHl2d3R2d6aCdHd6eIB6eHZ4eHR0dXd0d3B2d3h2c3l+dXV2fH16fX6BgYiBgoOBhIV+g4OHgoaIko2KkZOQkI6Ri4yPk5aSlI2Qj5GSkpSSmJSalZqkm5qboZ+grJ+lparIp6iop6murrCwtbCxtrWvtbayubu7vGu3w7/Ev8iAzMHAwsHAwcPDwmVqxMHDw8XKxcjHx8bJwsTEycbMy8fIzMjHyMzEyMnHysjIyszLysXLyMnDwsbFx8XDw8PByM3NyMfEyMbAvMHHv77Cx8fHxMrGx8HExsPGyMnKzMvNzMnJyc3LzMnFx9POyXRpycvMy8rLzMrJz8xqzsvO0Mwuac/Ramtsbm7W2dhtbW1sbG5scG9ycnV0dHR1dXVzdHh3d3l3eHh3d3h5e3l7fD1naGpqamlraWtvcXBwcnNzdXRzdXV0dHZ2d3d2eXd5gHt9f4B+f4GEh4aBhYiKjImMi4uMkI6PkJCUk5SShJRok5KVmZydmp6am5ubnJebnp6eoqGjpKOjo6anpqaoqKmprKytrq2rraurrK2tr6+srLCzsbK0sa+xsK2wsa+vsrCxr7Syt7WxtLO0tbK1s7O3r7GysbC1uLaxs7e0srK1tLS0tbm5tbeEuDq5t7a2tbe2trK1sbOtrauqqausrbCtrq6tra6ura2qramoqKeppqWnp6ampaikp6elpaSio6Ogo6WjhKKAo5+dnqCdnp6Wm5manJ+gop+dm5uWmJqZmJmYmJabnJucmJibl5WVlpWWk5OSk5OSkpGSj5GOj5GTjo6QkZCRkpGOj4uKi4uPjY2PkpCTk42Ok5GSlJeZmJqYmpibl5SUlpaYmZiYmJybkpuWmpybmpqYl5eXmp2cnZ+epKKmqayAra+sq6+trrCysrCys7e5u7i4uri7u8C/vsLEw8jHxMPDw8XHx8fKyMnNy8zLysvKzc/Q0NLR1tPQzs/R0NTX19fY1dfT1drc3NbX19bZ39vc4eLd4ODh4eHg4eHj4uPl4uTk4+Dh4ODe3+Hg393d4uHh5uXk4uHg4OHf4eTp4+Uz3t/ef/Pg4ODp3NzY49jW1NDQzs/OycjHxcXKxsO/v8PCv728u7m3tbevrqyoq6yppqOkhKOAoZ+eoKChnpeYmZWWlpOTkpCOiYyHh4ODh4OCf32Ce31+enl4dnZ3eHXm49vb2drb3d3a2dzY0tDczcvK08vRzczIyMHHwsLDwb3Evr++ubWopqmrqaSjn6KeoJuXlZSYmZaRm5SYk5CNiIiFhoeNi4WCgoF9iIGMgHx5end2dXWAeHh6dXV3d3d74eh1eeNzdHNycXRzcXZ4eHV0eXtzb27XceLU1NhvcHBycnBucm1ubXBqamVoapN2aGpraHBpZ2htb25sbW5pbWZtb29vbHF3amlqbWxqbW1wcnhxb3FvdHZvc3FzbXBxe3l3gYSCgX+Benl8gIJ/gXyAgIOCgIGAf4OBh4GFjoWChIiHiZOIjY2PqY+QkZKUmpiZmp6bm5+dlZmXlJugo6Vboamhp5+traGipaioq6+vq1tdpaChpamrqKqoqKitqKeoqqSsrKiusqypqqykqKmmp6ioq66uraqxr7KurLCur6qpqKimqrOxqampra+qqK2yq6aoqadep6Kpo6eipailqKmqrK2rraynpqWnpqimo6aysK1pX7C0tbOwrq+rp6+rWbKtsbOqVqiqVlZWV1issbRcXVxcXF1bXVxeXl9dXV5gYWFgYGNiYWNhYWFiZGZoaWdnaP9//3//f/F/AYDcf+B+BX19fn59kn4CfX6Eff9+hH4Bf5B+gn/hfoJ/i34Bf4V+A39+foV/g36jfwICBAB7nZ6foKCioaGkpKWnpqKlr6mrqaqsqqyrrrCztre0tba7ubq8vcDBwcTHwcTIyMzKz8zO0NLTzdLP09HRz9PR0tjW2Nra2Njb2trZ2tvd293d3N7f3dzb3dfa3eDc3eHh4uPi5+Xh4t7d3d3c4ODf3N3a19zY3NnZ1tbVhNSA19PU19jV19bW1tnY2dna293a2dnY2Nrd3d7d3uDf4N/c2NfY2dnY2dnY2tnY1tjZ29re4Nve3d/k3t/e3dra3dna2tza29ra29rY1dnX1tTZ1tnY1NTT19bV1dHR0dDS09bX2Nre3NrZ1dbX2dvb2d3b29fU2dPV29zi3d/i4dqA3tnb2tfY19PSz83Mzc7MzMvIzMvJx8PCxMHCv8C+vLu6urq8vLu5t7aztbW1sa+vtre0sq2sqqurqaepqq2qpq2zsK2rqqeio6OeoKKenqGdnZybnqGjnqKjoZ6fm5+dnpqbnJ+gnqKeqp6inp+dn6Oho6Slo6WopqappqWnqKmAp6ipra2urayrrq2trK6xsrGztLCxsrS1rrKvr7Kxs7KysrS1tbe4tLSztbW3vbm5ubq3ubi4t7m2trS0tbe4t7e6u8m9vLq8wL+/wLq+ubq4ub25vLm6uby9vr6+v7/AwMDCwcLGhMXFx8rJxsTFx8XHyMfFx8bD9sfEw8bGy8Z/wsXAu7y5v7y4t7a5vba4t7iytLOyraqpqqivqqWipKakpqimo6SpqKempKKgnJiZmJmenJmSkY+RjouMjomJiYyMiomIhYWEhIaHhYKC9PPw8/n+goGB/Pzy7/WE9u3i4ufl3d/Y2NrZzc7LyMzJw8C8waqrvLqztbCyr7Gzr4SngKSkpaWiraSYl5WXkJOVmKCcmJeTkIqRjImFhYOFgf6AgYaKh4qGiYWA/v6AgoiNj4+LhYaJiIiFjpGDhoeChIOFg/v7gvj2gYKHiYmKiIqKiYqFhY6EiIeGiYmJiIqTjoqMiJCUjZCMjoiNjY6Mj5eZlY6VkZSUlZ6moaCgoZmcgKWfm6WrpKWrqa2ssLK1vayvraerubavsbavtryvqrC1s7O4vLy5u7y/vbvGxcXGzM/Tz9TQ0MrO2tXP09nX1t3g4OLk3+ft8vHy6/H59u337u7z8fX6+vf7/P39+/L4+vv+8/b29vX29/36+Pv++fv69vb09u7x7vX6+Pj08YX+fvLv+Pz59PT07e349O3z8+ru7u7v8vLt7+7o6Ovr9vTz8e3r6vH09Pfz8PP1+Pbz9vPv7ejl7vH29Pb5+f729vD3+fvy9/v6+4GA/f38+vuB/oGC///+/oGCgoKDgoaEioeJiYeKiouPko6OjI2Ojo6Pj5OSkpOVmJucm5ybnYCBgYKDhIaFhYiHiIqJhoiTjIyLjI2Oj46Sk5SWl5SWlpuZmpydnp+foqaho6alqKaopamrra6qrqyxr7Cvs7GxtrO1trWzs7a1trW2uLi3tra2t7m5uru/u73Awr+9wcHBwsHEwMDDwMPCwsLExMXDxMPCxcLFw8PBwsPExcXFyIDFxMXEwsPDwsLFxcXGxcbIxsXFxMTExsTDwsTFxsrKx8XDxMbHxsfHxsjHxsPGxMXFxcbBwsHCxcLExMTCw8S/vr2+vLy6uby7vLzAvr67vbq6ube2trm4ubu5ubq3t7W3tbS3uLa1tbKztLW4tbO2tre0s7eys7W0t7S0tbSvs4CwsrGxs7OxsrGwr6+wrq2qp6qpqqilpqilpqKkpKKhoaGfoKCenp6fnJ2enpyenaGjoJ+cnJudnJqYm5qcmZeboaCen5+fnJ+hn6Chnp6gn6CenaCgoJ2gn5+enpyhoKGgoqChoZ+inq2ipaSlpaWqp6ipqKanqqmorKqqrK6xq4CtrrKxsbCvr7O0tbW3ubm3ubm0tre5urW5uLm7urq6u7q7u7y9vLm6uby8vcK+vr/Av8HBwL/CvsHAwcLFxMHBwsPVw8PBwsPExMPBxcDDw8LJxsjHyMbIx8XExcbHx8bGx8TFxn3GxMTGxcLDxMbFxsXFwsPEv+XBvLq5usC9uoC9ubS2sri3s6+trrGrqqamoKKioqChoKGgpZ+cmZucmJmYlZKSlZSUkpGRjYqGhoSGi4iHgoKBg4B9foF9fX6Af3x5dnNzcHBycnBvcNfW0tTX2m5tbdna1NLUd9PKw8LHxb69uru/wry7uLS2t7Kuq62YmaWjnZ6cm5mbnJmRlICUko+KiIqLl5KLi4uMhIWGhYmGgYB/fnp+fnt3fHp7efB4d3d4dHRyd3d26e11c3d5enl3cXN2dXZzfX5zdnd1eHp9eezreOjnd3d4eHd3dnd3dnd0dH91eXh3eXl2dnd+e3l4dXl5dHh3eHJ0dnZzdXp6eXV9enx8fICGgYODhYB/gYaAfoSKh4iOjZGNkJCSmYyQkYuOmZeSk5eRmJuRjZOZlpeWmpiWmJufnZ6loqKip6asqq6rq6eqtLCusLWysra5ubq+ub/Bw8K/ur/Fwr3FwMPHxMXIyMTFxsXGyMPKyczPx8zMzcrKys/Mys3Oy87Mys7Q0szNycvOzM3Ly4B13cnHzc/LysnKx8jQzMnMzMXJysfKycjDxcS/vsDCysnJx8bGxsvMycvIxMjKzczM0M/MzcrHzc/Rzc7OzdLKysfMz9DJzc/Ky2hnzs3PztFq0Wts2NnZ2m5wcW9ubW5qb21wcnFzdHR1d3V2dHV2d3h5eHx7e3p5e318fX1+gGhqaWprbGxra21tb3FwbnF5dXZzdXV0dHN1dnd4eXd5en99f4KChYWFiIuFiYuLj42PjZCRk5WQlJGVlZaVmpiYm5mZmpuZm5+enp2cnZyanp6goqOjo6KloaOoq6eoq6qqrKywrqyvrIStHrCxsbCwsLG1s7i2trS0tLKzsrKzsLG0tbW4t7a2uYS2CLi6uLa1tLW3hLmAuLq5urq5t7m7vL28u7q4ubi3tLi3t7a5ura3tbS2sLGwrq2ssK6vsLKytLKys7Gvq66rqairqa2sqqmoqqmqqqenqKampKWlo6WopqSkoqGio6OhoKKho56coZqanZ2hn6CioZqcmJuZmJqamJqamZqZnJuamJWVlJSQkJGUk5Umk5SUk5GRkY+Sk5GSkJGQk5OUk5KRkpWSko+QkZSTkpCRkZSRkJaGnYCanJyYmJmWl5ubnZycn6CfnJ+fnp2cmZ6eoJ+ioKSloqejtKesqaurrbOytbW0tLW4uLm9vLy+vsG8vb7Ew8TEwsPFxMXExcnKyMrKxcfKzs7I0M/P0tLU0tPT1NTS09TS09XZ2tnf29rb3Nnc3Nva3dre3t/j5+fl5eXn/ujn5AHlhOcM5Ojk5ePi5+Lj4eLhhOWA5OXl5uXl5+Tl5YHm5ebo5+Tj5Obk5uTh3t7f2vre2dXT0dXRzdHLxcbDysnEv76/wr29u7u1uLa1sLCurauwq6ekp6mmp6iloqKlpKOinp6cmpaWlJWdlZGKiYaKh4SHiYSDg4SCf357eXl3d3h4dHN03NzX2Nrbbm1u2NvV09eAf9fPycnQ0cvMxsXGx7y9u7q+vrizrbCXmq2wra+sqKWioqCWmZeTkpGSlJOml42NjZCLj5CSlI+IhYKAfYKCgX1/fHpz5nR1eXl0dXF1dHDh4XBvc3V5eHZxcnV1dXODgXFycWxvbnBu19Zv1tVtb29vbm1sbGxra2dncGVoaGeAaWpoZ2hva2lpZm1vaGxnbGdqa2xoa3BybGZsZmtsbHJ1cXJzdG5xeHRxdnx2dXl2eXh7f4OMfoCAenyEgXx8gX6IioJ+g4aFhIaIiYWDhYaDgomIh4mOjpKPlJKRjY+XlI+VnJycoKGem5yWnKKnp6akp6uon6Who6mmp6qtrK1/rq6sqqSqrK6vpqioqaepqayrqqqurKysp6mrrqmxqa6wr7CsrWrJrayys7Gura6qqrKwrLGwqK6uqaqrq6mqq6amqaivq6mmo6Ghp6mprKqoqqytqKeqqKaopKOqq66pqaqrr6mqpqyvr6musKyrV1aqrK6sr1msV1iurq6yW4RdIlxeW15cXl5cXl1eYGJgYl9hYmFhYWBkY2NjZGdpamlpaWr/f/9//3/ofwGA53+GfoN/hX4Bf8R+AX2KfoJ9l34FfX1+fX3/frh+AX/JfoJ/hX4Ef35/f4R+p38CAgQAgJ6coqGfo6KlpaOlqKSnp6qqrKusrq2urrG1ub24ubi8v72+v8HHyMfKysrMy8vMzM7R0c7R19HW1NLT1dXT0tja3N3c2t3g3t/g4+Lk4uHg4uDk4+Th4t7e4eLl5ejk6Ofp5enp5eHj3t/f3uLm4ePh397e3t/d3t3d2t/b2trcgN3b3N3a2tnX19nd29vb3drb3N3a3N7g3uHg4ePh39zd3Nzf4+He3N3e4N/i4t/i4eDj4eHe4N3h4efk4N7l3uDf4Nzb2tze3N3Y2t3c29rd3d7a2dna3N3W19nZ2tja2tnZ3N/g3t/c29vh29zg4d/h2tjY2tza3N7h4OLi5ePfgODf39va29nV1NHRz87O0NDN0dHNzMzMyMbFxMDDv729vr27vLu8vr26u7m3srKytLaztK+vr6mura2usbKvrbKzsbCtrq2rqqyprKaxpqOhn6OhoqGgoKSko6OgoKCfn56eoqOipaShn6Sjo6SiqaWkpqOkp6uqqKinqqytra6ugK+ytLWzsLKxsrKvr7G0ubaytLW3ube3u7+0s7GztLe7ubm5u7u6uLawuLa2ubq7uLu7vL28u7i5ur+7t7q5ubi8vcLAw7+/wsG/wL/Awb3Ayb/Cv7+7vsDBxMjGxsPDw8XIx8PHxMjHysvJzszLysXIyMfLysrJycjMx8jIyM3IgMbDwr++vr26ubi6ubi6ubu7vMm1sbBxrKytrq2qrKurrK6wqaippaelpqWknp+ZmJiWmZiXl5WWko2Mjo2PjI6TjImIhYWFhoKCh4H+/vz19/v9gID/9vX89vTy8/T07+/o493W2tzU1dDOyszGwcC9vKyoubm4trO0rrKzsa2ugKWspaGfoJ+bnZqZmZuVlpmbmI+RipSQjIuKkIaFio6ChYSEhIeIiYaEhYKBgYD9lpWLg4aAhIOBhYTzgIeNh4iIhIOFg4CAg4qGiYiFh4WIiIiGj4uHi4eJiYaDjoaHjo+QkY+Li4qLjIqPjZCKj4mRlpaSkpKRkpaWnJ2fotSigKekp6qkpKymra2wsbCtuLezq6yrrrC5vrK0x7W4vMC8uL68u7m8wcXEwMm9vL2+xMXJzM3W2dLY09Ta1tff3N3W4t/i5OPn8Ozx8vP09fn49vn9/v79/4D//fv/gYD+/P2Bg4D5+/v4+4CAgIGBgIGCg/7///z08vT3+/z6+/r5gPf5+fz8/fv49vX484eC+PXx8fX3+/n5/fr58fb38/X4+vD39vLs8vj/+v//+fz4/vX5+v3z9P+Dgv+AgP6A/oCA+P/4/vn4/vn9/4KChIOCgoH5//+Bg4GCgoWEgoSDhYeLjYuOjY2OjpCSkJOTkpSVkZSQkpKVlpqbm56enp2gFIOChoWEhoeIiYmMjouNjZCPkI6OhJBnkpaYmpaYmJyfnZ2dn6KhoqSkpaenp6qpq62tqq2wqrGvr7Czs7GxtLe4t7e0t7q5ubi5u7y7urq8u768v7/Bv7/Av8DAwr7CwsLAw8PDwsXDxMTDxcjGyMfHxsTGx8bHx8bGzMbFxYTIO8nHyMfFx8fKyMjHycjKzM3Ly8vMyMrIyMrKycjKycnLz8vIyMrKzcrKycfHyMnKycjFx8XHx8vJxsTKhMI4v7+/wMK/wLy/wcC/vb++vru6uLm7u7a3uru7ubq6uLe3ubq3ube2uLy2t7m6ucO4uLi6u7i3uLeEtiS1s7S0tLO1t7e1tbS0sbCvr6+srq6sqqyuq6mop6Omo6KioqOEomqjo6CioqGfoaKio6Chn6GhnqGgn6CioJ2cn6KioaKkpaOlp6WpoqqioJ+eo6GjoZ+foqKhoZ6goqKjo6Slp6Kko6GfpqWnqaiurKuuqquvsbCura2wsbKztLKztLa2tLK0tLS1tLa6vMS+hLqAvry+yMW9vLy9vb7Cv728vL2+vr+8w8C/vr++vcDBxMXExMLCw8nGxMjHx8XGxsrEx8XDx8jFxcfIysjL0MvPzM3JzM3Ly83LysjJycrMysbKx8zKysrGycnJx8bJxsbJxMTEwsLEvr29vsC8vLq6uLm5u7e2tLKuq6yqqqiotKULo6Ryo6KioqCdnZuEmTiVlJORko6PkI+KjYiHiIeHhoaFhIaDf3+BgYF/gIJ9enh2dXR1cnFzb9vc2dTW2dxvb9vW1NrU04TQgM7Oyce/u8HDv8C5uLW4tbOzsK+emaKjo5+fn5yfnZqVl5CVkI+PkpCMjoqKi42Fg4aGhYCCfoSAfXp8f3d6foJ4enl4d3h3dnV1d3Z0dHThgYB4cnVxdnVzdnXZcXZ6dnh4d3l6end2eH54fHp2eHV3d3d2fXl2eHZ5enl3gXh4gHt8fX16eHl3ent5enRzcnVucnl7eHp5eXp9fIGCg4Odg4aEh4mHiI6KjY2UkI2MlpWTj5CNkI+YnpeZr5mZmpqXlJqZmpibn6Ohoamho6SkqKapp6eurqmuq6y0sbK6t7ayurm7vry/xL/Dw8LBw8bEwsLFxsXFx2TKycvOaGbLgMrLZ2poztLTz89oZ2dnaGdoaWvR0tXZ0MzOzc/Qz9HR0tLRzdLR08/Oy8vNym9q08/KysrJzMrIzMnIxMzMyMvNz8fMy8nEycvOy87Py87N1M3R1NTMzdRsbNNpatFp0Whoy9LQ1dLT1s/Rz2lpamlpamzV29pub2xtbnFxb25tIWxscHJxcnJzdHR3eHZ4eHh6e3l8eX19fn19fXx9fn+Ag4BqaGxraWtrbG1tcHJvcnJ1dHZ1dnh3dnV2eXp8ent7gIOBgoOEiYmJjIyLjY2Nj4+Rk5ORlJmUmZaVlpmYlpaam52cm5qeoZ+fnp+eoJ+foaKipqapp6imp6ioqqmsqa2tr6yysrGws6+xsK+ztrK1s7K0tLS4uLq5ube8t7e2uYC4tre4t7q6uLq7vbq7ury6u7y9u7u8vru9u7u9vLm5urm7v8LAvry8uru5vLq4uru6u7q7uLq3t7W3tLCvtrGysrWys7OztbGxrK2uraysr7Cxrq6tra+vqKmqq6upq6qoqKipqaamo6Ojp6OjpaelraGgnqGjn6CioqKhoaKgmxecnJ2bnZ6dmpqZm5manJycmpuamJWWl4SVEZOXlJOWlpeVlJOUlJOSlJSVhJMnko6QjI+RjpOUk5KWl5SUmp2enZ2enp6foaCjm6OamZmZn56gn56fhKMOnp+goKGho6appqmqqaeEroCssrCwtrO1uby7u7u6vsDAwcPBwcTGxsfFx8jIx8bIys7VzsvMzc7Qz9DZ3NXU1NPS2NzY1dPS0tTV1dPc2tna29zZ3N3f393e3N7h6eXi5+bn5ejp7urt6ebp6efn6ens6e3x6ezp6OTo6ejq7Onp6Ojq7e/s5uvm6+vs7Ont6jvp6OTm4+Dk4OHg397f2dbV1djS0M3LyMbExsLExcO/vr6+v729y7e0tICxsK+vrqqqqaipqqumpaShooSefpmcl5aWkpKPjYyLjYqGiImHhoKBhX99fXx7enp1c3dz4uXk3t/h4nFx4NrZ4trY1dDP0c/T0c/KxMjLxcXAv7u9t7O0r6yalKSnrKyqqaChoJuUlpGWk5GQlZWRlJGPk5ePj5OTj4aFfYJ+fX6CiYB+gYF1eXp5ent3dnJydIRzgOGFg3p1dnJ2dHB1ddZvdHdwcXBtbnBxb25yd3F1cGtsa29vcG90bmtsaGlpZWNsZGVqbW9wbmxsamxubG5qaWdqY2lub2prampqbW1zdXJyhG9ycXZ7eHd7dHV2eXp7eoaEgn2Afn5+goZ9f6GEh4iKhYCDgoOChYmLhoOJfn5+gICFhoyLjJSUj5OOjZWRkZuZmpWdmZucm5+loaSlo6Wmqailo6SkoqKlVKmqqa1YVaqpqFZXVqeorKuuWVhWWFhXWFhYqaqqrKinq62xsa+ysbKytLG1tba0s6+vtK9fW7Wxra6vr7Oxr7SxsKyzs6+vsLCmq6uopamtsq+xs66vNq2xq7CztKysslpZrlZXrlmzW1uvs66xrq60r7CuWVdYWFdYWa2ysltcWltbX11dXlxcW15gXoVfF2FiYWNiYWNlYWViZGRlZWdoZ2ppaWhr/3//f/9//3+afwGAtX+HfoJ/2X4BfYt+AX3/fp1+AX+Efgh/f35+fn9/f4V+iX+afoJ/qX4Kf39+f39+f35/f4p+h3+Dfqt/AgIEAICgoaGioqWmqKmrqqusrK6ur7G1s7S1tbe3ury7u7q9wsXAxcfLys/Pz87OzdDR1NLV1dXQ0dPU2drd2t7h3Njd3dva5+Pm5uXn6Ofn4uLi5OTm6ufl5+fm5enm6evq6enq6uzs6enl5eTk5+bm5uXk4eLl4+Hj4t7g4ODj3uDe3YDe4d/f3t7d3+Lg4eLk5ebk4uPk3uDf4uPg5Obk4OPh4uHf9OLh4uDe4uHi4eDj6enq6Obn5Obm5uXo6ebn6ebl4eLi4N/i4ePl5eTi3d/h4t/j397f3t7f497f3+Hd3d/e3d3g3t3f3+Df4uTg5OTi4IDc3tve4uLl5+jq6Ovq5oDl5+Di3t/f3d3b1tTR0tPSztDMzc3Ozs7MycrHyMPDvsDEwMHExb+6u7u7vLy4urm8ubq3s7Kyr66ytLi3uLS2tbGzsLGyrK+ssLCnqaampKako6ekpqeop6eop6SdoaSgn6KinqSvoqSlpKqjpKimpaWmpaWqq62ppamrra6uroCvtLa1t7m2sbS1vLa1uLaysbW2t7i9uLi7sLK1tbe2uLm+u7u8ure2uLe5u7u9vbm/v72/vb+8vsHCv72/wb/BwcDAwsDAxMXExMbFx8TBwL/Bv729vMDCxMXGxMXFxsbIxcfKxsxpysnJzc/O0tHQzc/Nzs/PzcvM0M7PyMvOzYDKyMbAwsO9vL/BuLi6v7y8v7+4uLa2uLS0tLa0sa+vrq24r62xr6+ppaWhm5+dnJublpiYmpqcl5SUj46MjI+UlZaRjY6Mh4SChYKC+vj39/n6/YL/gIP8+vbx7u3t7vHp5t3b09DQ0M/S09HSz8nIwsWvsMa/v7qztbOysKyvroCoo6afnZugvpyYlZeTm5uTl4+Oj42MjIuMjomJiomLiIaEhoWJiYmLiISFhf+FiYqJjImJiIeEgYWEhIGDhYWHhYCBg/6AhYqLi4uPjouMhoqIhJCNoI6WjIaJh4yHioyLio+QkI6Uj5OLioyRio6TkpWWl5GOlZSUlpidnZ3DqUqkpKurpamqpqqurLKzsrCvs7G1sa6xr7a0xLWutbfC0b6+vr/HwsDFxr7JxcDMyMXL0dTP09PU1NnV3d/c4Onp3+Tl6Obp6e3v+YT2afr3/ICFg4aEhIGB/f6AgoGDgP+BgIKA/4SDhoSGgoeGgoWAgYGBg/uBgoWCgPiBgP39+v37//79/YH9/fv5+/z3+P729fz7/oSFgYL5/P/59fSG9/f6/Pf9+YD3/v///4GB/4KBhIODg4SIRYGCgP/9/fz/gP6AgYGFg4eGiIeIhoSBgYKChYWCgoSGhIaFiIiLjpSQj46OkZCNkpGTlJKUk5KWlJWWmpudnp+go6Oin4CHiIiIh4mKiouNjY6Pj5CRj5KVlJSWlpeWmZuZmZqcoKKdn6CjoqWmqKmpqaurr62wsLGvsLKztra4trm9t7a6u7q2v7u7vL2+v7/Avb69vr7Aw8LBxMXFxMbDxcfFxMTDw8bGxcfFxsfHycrJy8zLycvLyMfJyMfJys3Qzc3LzATNz87MhMuAzszNzs/Pz87O0NPN0M7Pz8zNz83Mz87Nzcvcz8/Pzs3Q0NHOzM3OzM3LzM3JzMrKyMvMysvMycfExcbFw8bGxMbFxMK/wMLEwsXBwL+9vb7BvL28vbm5vLu6ubu6uLu5urm7vLm8u7u6cLm7ubq8uru6urq4urm4uLq0tbS1t7eAurq2tbO0tLOvr6utrK2trK2srKmtqKmlp6qkpKaopaOjoqKjpaKioKShpKWho6ShoKKipaOkoKKjoaSlqKikpqWoqaOlo6SipaKjpqSjpKShoKOioJ2jpqWmqKeiprGkpaeorqiqraysra6ur7O0t7KtsbKztba1t7m4tri6ubaAuLnAvLy/v7y9wcC/vr+9wcW9wsLAv8DBwMTBwcLBwMHDwsPEwsLBv8XFxsjFyMTEyMfGxsnLy83LysnJx8fLzMzLysvMy8rKys/Oz83Nz9HQzs7NzM7OztDNzc/L0W7Py8nMy8nOzM7NzMvLysnHxcjMyMfBw8XEwL+/u72/uriAur+zsK+yrKqrqqWkpKWno6Kio6KfnZ6cmqGXlZiWmJOPkZCKjo6MiYuGhoaHhoaDgoSBgH5+f4OCgXx5enl1c3J0cnPd3NrZ2NfYbthsb9nX1NLOzs3N0crIxcbCv8C/u7y5t7i3tLSxs52eqaamoJ6dn6CempuYlJGXkI6NkbmAko6Ki4eJiYWGhIGDgH+Af32AfX1/fX58fHt7enx6eHl3dHd343h5enh5d3h4d3h1eXh3dHV1d3l4dnh98nh8fHp3d3l5d3lzeHRyfHiGeoF6dXd2end3eHh3fHx7eX15fnZ0dXZxc3V5fYB/enl8e3x8fX9/gZuLhIWMjYuNjoyAjZCMkZGTk5SXlJaSkJOTm5mpm5WamqOunZqamp+am6ChnKalo62pqaqvsK2urq6tsK+2uLW5vry1urzAwcTCxMTLxsbFxsnGyGRmZWdmZ2VmysxnaWlpaM5paWpo0GxpbGlraWtsaGtnaGpqa85paWtqac5ra9XV1NTR0tDQzmpq0tPT09XW0dHUzcrNy81qbGlqzdPVzs3McM7Oz9DN0s5qzNLQz85oadBpaWpoZ2dqamtsaWpp1NHS0tNs1Gtsa21qbWttbGxramttbm1ubmxsbm9vcG5wbm9xdnNyc3R3d3Z5eXt7eXp6eoR+CoB/f3+AgYSFhoVGaGlpa2psbW1tcG9vcXJ0dXR3e3h4enh6eXx9fHt8f4OGgYSFiYiNjo+QjoyOjpGQlJSVk5SVlpmZm5mfop2anZ2amKKgooajgKCgoKOkp6uqqqyrqqmqqKutrq+vsLG0tbS2s7S0tba2tre3trS2uLi2urq3uLq7vbq8urq7vLu6urq7vL++vb6/vsC/vb7Bu729vr+9wMLBwMLCwcDA2MHAwb28vry9u7q8v76/vLy9ury5uba3trS1t7a2srW1tLK0s7O0tLOxDa6usLKwtLKys7KxsbSErYCpqq2rq6qrqaeop6elp6ijpqelpF+kpKSlp6anp6empKShn56gmpyam5yam5uamZuen5+cnZiZl5eYl5eYmZealpeVl5qWlpeYlJGTk5aYmpeXlZWRkpKQk5aUlZiZnJqbmZ2dnaCgoqOgo6GlpZ6fnZ2coJ6foqCho6Wlpaimo4Cdo6akpKenpKm4qautrLCqrLCvsrW3tri9vsK9uL3AwcTFxMTIycfLzszJy8vQy8vNzszM0dDP0dXV193T19rY19PX1tnX1dbX1dfb297g3+Hf2uHf3uLd4d/g5efm5+rt7O3s7Ovs6+ru8O3t7e7u6+np6Ovq6efo6+zt7u/t7YDv7u/x7u3v6vGB8ezq7u3q7uzr6Ofj4+Pi4ODi6eLg2Nrf3NnW1c7Oz8vJy8/CwcDFwcHCwry6urq7uLWytLCurKyrqbGopqmoqqSgoJ2YnJybmJmSk5CQjo6Li4yLiYiHh4mHh4OAg4J9enh5dnXh393a2djZb9ptcNzc2djU1F/V19nT0s3Mx8LDxMDCwLy9ure2r7CXmKejpKKhpaakn5qZmJSPlpCOjpXBlJCLj4uSlI6QioOBfHl9fn+Cf358fH13enp6e358e3t4dXZ13XR0dnV4dnh4dnRxdXRzcIRxgG9rbG3YbHB0cm9vcHBvcm1xb2t0b35vc2xmaGhsa2psa2lubm5qcG1ya2lpaGZob21wcnFraG1sbWxuc3Jyhntzc3t7eHl5dnd6eH5/f31+gX+EgH19eX55joB7gIGLnIaEhIWKhoaKiYKLh4SNiYeMkpKQlJOSkZSQlpmVmqCegJeam5+dn56fn6ekpaanqaeoVFZVVlZYVlarq1ZXV1hWrFZVWFatWlhZV1lWW1xYW1dYWVpbrVpaXVxar1tbs7KxtbK1tLKwW7OztLGysqyusa2ssK+zXF5bXLG1t7KvrGWsqqmuq7GuW66xsa+rVlapV1ZaWFhYW1pZWVVXV7CwQLCurlisVldWWVZZWFpaXFtaV1haW11dW1pbXFtdXFxcXmBkYF9fX2FhX2NjZWVjY2NiZWRkZWZnaWhoaWlqaWj/f/J/AYD/f/V/AYDlf4d+BH9+f3/VfgF9l34Bff9+jX6If4J+hX8BfoR/AX6PfwF+hX8Dfn9/iX4Bf45+hH+GfgF/h34Bf4V+A39/fo1/hX4Cf367fwICBAACpaKEqICrrK6vrK2usrK0t7q5ubm6wbm7v77ExcTBxcfMzs3N0NXU09jV2Nzc3dvd2t3a2drZ3t/l5uHi5OLi4+Hm4uPv5enp6uvr6uno6+ns8e3t7ers7O7t6+/r6+7t8O/x7uvq5+fp6ujq6ujn5+jn6efl5ebk5ubl5unm5Ofm5eLg30Pg4OXk5OTl6u3l5+Pt5efm5+Xs6+nn5efn4+bq5ufn5+bl6e3q6ujq7ezr7Ovr6/fs8unp6Ors7urn5efk5Ojj5+rrhOmA5uTg29/f5eTk4uTj4+Ph4+He3eLg3uLl4eLj5OPj5OXo6OXl4eHi4uHi4+Tn5+nu6+3r7O3q5eTl3d/d3tzb19ra2djW0dbV1M/U03rRzMrHyMXDwsXFw8bCwsHAwL6+vru/xMDAuLm1s7e2uL25vb+6uLe2s7O3uLWzrq6urayAraipr6urqqyoqqupq6irt6KlpKSfo6Kkp6qrrquoramrqaqoqaqura6srq2ora2sr7C5srS3uL62uLa5vri5trm3tba3uLm+5L27vbq4u727vL7Bw8HDxMC8uru6uLu+wMPBxMTDwcHAxMXDxMTBxMG+wMbFxMbGxsrKyMfKyseAyMbFx8fHxsHEw8fHycvKysjLycrLycvNzs7KzMzP09LV19jV1dTR0c/R0tLT0NLOzdTO0MzKytDJwsC9vbq/vsDEwsXFyMW8vr66vru9vLWxsrGwsbKyvLOuqKaioZ+go6OhnJqZmZuXl5STkY6MjY6Sl66ZnJWTkYyLi4SFhYBj+/X3+/35/4CB+Pv5hvf18/Ps5t/h3drb09Pa2trb0NDNxsbCwaqqvLa8vLiytLO1r66rqYihnZ2amJ+hnpubmpyYl5qWnJiRkY+QlI+PkYuMhYeEhYCFhIeChoWDhImOkI2JhIqAiIT5/4SIiYuKjIeHjZmIg4GFiYuJiIiNjIaGh4mGg4qHjIuQjoqPkJGRk5qKjY+SkY6aio2RkpCSjZOTj56ekJKQmJaVlp6gn6WkpqekpaSgoqywpqWprrC0sq+1trG2t7O2tba7ubnLvcLBwMrBw9HNzcfFycvzz87MzcjW1eQ1397e4OPk4+fh5e3r6enp7ers8ff2+f35/f79+/2Dg4aLhIaCgoOEg4aHgf+FhYaDhIOEipOEiVqGhYeEh4GDgYKEgoKEgYCAgf3//YSDhoOEmYL+gP34+v+BgoOBhPqAg4KBgoX9gPv9+/79/P6AgPuC+YL4goCCgYSDhISBg4SCg4aGh4aFhoSHg/6Bg4GBgf+EhRiJh4OHhISDgoOGhoWFh4aGioqLjY2Oj46EjxmSkZKTlZWXmZeYl5iZmpuenp+ipqinp6WlgIqIjIuJiI2Nj5GOkJGSkZOUl5aXl5memJqdnaChoZ6ho6empaWmqqqpra2vsbCxsLOxtbW0tbW4t7u6tre7u77AwcTAwM6+wb+/wcLBwcDCwMHHw8XGxcjIycvJzMjHyMfJyMvKycnJysvNzM7Ozc3Nzs3Ozs3N0M7O0M/P0M/NgNDR0c/Ozs7M0M/Oz9HV2dPU0NnQ0tLSz9bV0tHQ0tTP0dTQ0tLT09TX2dLS0dHS0dDQz9DQ5tHXzc/Ozc3Py8rKysnLzMjLy8zIx8fIxsbDwMTExsXEwsPEw8TDxcLAv8K/vb/Avb6+v76+vr/Av72/vr2+vr6/vr/Avr2+u7y7Jru9u7e4u7W4uLu5uba3tbS0sq6ysbKusLB0r6ysqq2qqamrqqmqhKdWqKWkpaCkqaaopKalpaalpaajpqilpqiop6isrqysp6epqKeopKWrqKqoqqWmpaKjoaWxo6iprKepp6apqamtrayxsLKwsK6vsLO0tra5ubO4ubi7u8OEu4DAuLm6vcG9wL7Cw8PExcLBxN/CwMTEwsTFw8HDxcXCw8XDw8THxcLDw8LEw8bIycrKyMzMysrMys3LycrOzczMzM3Q0dDOz8/NzMrLztHU1c/Qz9DP0NHQ0dDV0NHSztDT0tPPz8/Qz83Pz9LQ0NHNzcvMysrMycrExMjDxcLBwIDFwLy7tbWytbOxsa6wr7Kup6mppaelpaagnaCenZ6bnKOcmpWUkpGOjpCPjImHhoWJhoeGh4aFgoKBgoOeg4R9fHl2dnZydHhy49ze4uHb4G9t0tjXctvZ2dnTz8fHxcTIw8TIw8C7sbOyr7Gwr5qZpp+joaKeo6WjnJmXlnaVk4CUkpCTkY6IiIeIh4aIhYqHgoKBgYV/fYB7fHl8enx4e3t8eHp4dnh6fX97dnh5eXp5d+XpeHt6fHp6eHd8lHt3dnp7e3h2dXp4c3V1d3NxdnJ3dXp5dnl4enl7gXd2enx6eoR5ent9eHhzdnZ3hId6fXp+fHx7goOCiISEhYWHiUmJjZWWjYuNkZOZlpOamZOXl5KTlJSYlpilmqGdnKKZm6elp6GgpKTBq6yrraixrbSyr6+vsLK0t7S6wL2+vsDDwsTGycXGx8XIhMpxZ2VmamVoZ2doamptbWjQa2psamtra291bGxtbm1sbmtsaWtoamtpamxqa2xu2drZb2xta2x+bdhv29bW2mttbWtrzGdqZ2dobM5q0dPS1dXS1mtqzmvPa85rampqa2trbGpqamhpamtsa2tsbG5r0mqEbDrYcG9tbXBubG5sa2xsb3Fubm1ubm9ycXFyc3Jzc3J0dXR3d3h5e3x6fXt8fX5/gH+Af3+Ag4WEh4iJb2hnamtram5vcXNwcXFzc3V2enl5enuGe3x/foKDgoCEh4mKiIiKjo2NkY+Qk5KTk5STlpWVl5eamp6dmpufoKGjoaWhorKjpqWkpqWlpaaqqayyr7CwrrCysLKxs66vsbC0tLe3t7a1t7i4t7q6uYS6Vry7u7u9vL2/v77Av72/v768vLy9vcPCwcLCxcjDxMHKw8PExMHIx8TExMbHw8XJxcTEw8HAwcXAwcHBw8LBwcC/v9XAyr28ubi6u7m4uLq3ubm1tre4hLWAs7KvrLGzt7a1s7SzsLCvsK6sq66sqayuqaqqqqmoqKmqqqippqaoqKipqaippqWkoaCgn6KgnZ6fm52bnp6dnaGho6Ogm5+ampeamoCcmZmWmJaWlpiYlZeTk5WVl5iampaYm5iZlZiampycnZ+doKOgoaGhn6Clp6amoaKlpKJkop2epaKkpKekpqenqaaqtaSpqKuorayusbOztrSwsq6vrrCxs7a8vb++wcC6wMHAxMXQxcjJytHLzc7R1c/Py87NzM7R0dLW8tfX3dvY2trY297e3drZ29nZ2+Lg4OPk5OXh44TkgOPq7Ovt8O/y8Ovs8e/u7+/v8/Px8PL08fHu7O7v8PDp7Ovu7/P09PXz+PHw8Ozu8fHx7vDw8vLv8O/w7u7u6enn5+bl5d/g3Nzj3N7b2Njd1c/MyMnGycfDxsTGx8vHvsC/ubq2trSvrK6tq6yrrLauq6WloJ6amp2dmpiUk5CRgIyLiImJiIaHhoeIrIaJg4SEgYCBeXp8deXc3d/e2d9vb9Xa3HXg4N/h3NXP0tDO0cfFyMLCwbi5trKxsa+Ulqegp6amoKanpZ+dnZxzk5CQjYuPj42HiIqPkJGRjY2HgX+Ag4iEhIZ7fnl6e4B8gX98d3h1dHR1d3hzcHN2eXt6gHbh5nV5eHp4eXZ0eIpxa2ptcHJtamhsa2htcHVyb3NucW1wbWdrbGxtcXhsbG9xcW92bm9wcWpqZm1ubXt8bWxpbGlqZ3BycHl0dXVzdnV0dXt+dnN2enyEgn+GhoCDgXx8fn2BgIGQhImEg4mBgpCPkIuJiYumjIyLjIqWlaSbgJmYl5iWlZiTmqGhoqGho6GipaqmqKijp6inp6pXVVZZVVhXV1haWVlZVqtaWVpZWllYXF5YWllbXFpdW1xZWlhaXFtcXVtaWVmwsK9cXFxaW2tctFy0ra6wV1lZWF2rWFpZWlpes1uytLK0s7GyWFitWq1brFhWVlRWVlhaWVpaOllaWllaWFhZWVxZrlhaWlhYrltaWFlbWVdaVlZaXF5gX11dXFtdX19fYF9fYGBgYWFgYmJiZGVmZmiEZg1oaGhpaGhpamtpaWdn/3//f5t/AYD/f/9/tX+HfgZ/f35+fn+nfgF/tH6Cff9+mn6OfwF+nn+Dfod/An5/hH6FfwF+hn8Cfn+Hfgd/f35/fn9+ln8BfoV/AX65fwICBACAqKepqqqtrq6wsbKwsri2uLm9t7z0vsDAw8PIyMrIycnM19PO0NLY1djb2tzh4ePl4t/d4N/c3uPq6Onm6eno6ujp6unn7u3v7u/w7+3v8fDu8fDy8O7v7+/t8O/z8vHy8/T39PXu8PDt7e3s7erp6urs7O7s6+3w6urq6ezp6umA6urp7Ojo6uro6ejq6Ovt8PDp6unn5ujq6Ovq6+zu7O7w7/Lu8fDu6+7t7e3u7vH08vPx8/Lw7O7r8PLv8O/v7uzt7Ozt6unt6Onn6ezt5+bk5uXk6Ofn5Obn5+js6ujn5uXj5OTp5+fm6evt7/Du7+3p6+3t7fHu9u7y8vD18vNZ8fPu6uXo5+Xl4+Li3tzf4N/d2NzX3dfY2NjW0c/MzMrJycbJyMTCw8fIxMTCw8LEwb67u7i3ubq+vr69v8C8vLq3tri8vr69ube1s7CvrrCnqaqsrKqrqqqFqICqp6mmpqiqqq2tr62trK6wra6tr6itsLCvr7GysLOztLW1tLe8wMTBxMHCyLW8vsa5t7m4u7/CwcK8vL6/vr/BwcHFwb/Bwb68vry/v8HGyMfHxsTGxcTGyMfJyc/Hx8XFxsXExMjHyMjKysfKy8jKzMrIysvHa8jHy8vL0s/Q0YDQztHQz8/R0NTR0dPU19bX2dbW2dfV2NbY2dTX19jV0dHS0dXQzszKx8XEwcHCwMHDx8fLzM/DwcPAur3Au7q2s7KtsrW2s7awqqmko6Sio6GmnqKgnJ6cmZiamZmVk5SXl56dmJmWk4yMioqFgYH5/vf7/YCDgP6A/f36/Pvz8IDh2t7Z29/c2tjc4t/a19bSzdDRv6emtbOytrOxsbe1rqylp6inqqempaGjo6Kdm56amJmkmZaPkY6Oh46Li4qKioaGiYiHjYGEgIKFgoySjpGQhoaIhvmAlIWEhYSFhY2Gh4mDhYGChYOGh4eMjo+Ih4WIhISFjImRjpKOkZGXjoCTkYmNjZCRkZCSjJCVj42OmpeUkpWhjpehmJuYnaWfp6uwqaako6qgnqimqq2vr6uptra8vLq5wb/DvLy/vL6+ycTIz8/IxcbK19PT1dLMy8/T0NTV2ODp3eDj5OLo5+ft6+/u7e7v8Pf+/IKCgv36gP2BgISEhIiGiIWGiIqFhYCFg4qMi46KlYeJi4yHjJCHiYiKioiHh4OFhYGDgoOGg4SDgYeIhYiOiImFiYKA+4L9gISFh4GGg4D/gIH9gIiB/4CAgIL+/oGO/4CChISIh4mEhIiGhYeHiYeJiIuNiIiFhYKDh4KEhIWChoSEhoaFiIiJiISHhomKi4yNjo6OjSKQjo2Rj5KVk5GXlpeZmpebmpyamJiam6GhoaOmqqipqKWoL42KjY6Ljo+PkJGRkJKXlZiYm5ecx5ycnJ2coKCjoaOjpq6sp6mqrqyusLCxs7S1hLZ1u7u5u73Avby6vL2+wcLDxsXEzMXDwsTGx8XFxcTCx8bKy8rKy8rIysnMzcvLzM3Qz9DMz9HQz9DR0dDQz8/S0NLQztHV0NDS0NPR1NLT1dTV09LU0tHS0dLQ09TY2dTW1dPT1NbU1tXV1tbU1NXV2dbX1tbUhNeA2NbX2NXW1NbV19PT0NTV0dHQz87NzczNzczMzsvLyMvNzsvLycrIxsjIycXIxsXGyMfFxcXDwsHBxMPBwcLExMbFxcXEwcPDxMTGxMvBw8PBw7/BvsC7ube6vLu8vLq5t7W2t7e1s7iyt7Kxs7Kwrq+srq2sq6irraurq7Swq6k1pqalqaemp6impqalp6alpairqqyrq6utrq+wr6urrausra6up6ipqqqoqqmoqKaoqKmsqq6Eqw+qrKyurq+xsrW0tbW3sraEuIC7u7u+vr++vb29vsDBvL+9wMy7wsfQxcTFw8TGyMfKxsTHycXGxsPDyMbGyMnHx8nIyMfHycvJycjHy8vKzc7Nzs/YztHP0NDPztDU0tTU1tXR09TQ0dPS0dXW03DS0dXT0tbU09XT0dXU09PV1NfS0dHR1NLT1dLR0c/NzsvOz4DLzMzMysjGx8XGwsDAvr68ure2t7W0sbSysrK0qausq6irrKmppqOinKegopyenZmYlJSUkpSOkIuMioeJiYiGiYmJhYODhIKIhYCCgH13d3Z2c3Fy4uXg5ORydHHdb9zc2d3d2tnNysnExsnGxcPJysW9uLi1sri+r5ucqaalo4CfnZ2ko5mXlJSWlJWUk5GOj46MiYmKh4WHmYmIg4SCgXyAfX19e3x6enx8e393end5fHd/gXx9fHV4fHvpeIl9fH58fXyAent9e3t4eHp0dnZ3eXt6dXV1eHV1c3h1enZ5dXd2e3Z5end5enx7fHp7d3l+eXd3e3p4eH2HeHyCeoB9fIGGgIaGi4SGh4iXjYuSj5KRlJWTkJubnpyYlZuZnJaXmZeYmqSeoaWmn52do6+pqq2opKeqrq6wr7G3vbKztrm5vb2+v73BwMDCwcLIy8loZmfIx2bNaWdraWdqaWpoa21vbGxra29vbnJxfG5vcHBrcHRub25vbm5tbm1ubjBrbW5vcnBxb2xvbmtscm5xcHZwb9lv12xtbW5obGtq1Gts0mtwa9Nra2xt19huhtiEbFRtbW5qa25sa2tqa2lsbG53bm9tbWtqbGtsbnBucW9ub29tb3Bwb2xwbm5vbm9wcXJycXV0cnV1dnh2dHh6e3x+e359f35+f4CChYOBgoOGhYiIiIxdbGpsbGxucHBzdHRydHd1eHh8eX2efn99gICDgoWDg4SHjo2JioyRjpCSkJGUlZWWl5aVm5yZnaCkoqKgo6Skpqamp6Wjq6eoqaqrraytr7CusrK1tLOztLSxs7O0hLU9trq5u7e5urm6u7q8urm5uby8wL6+wsa/v8C9wL7Bv8DBwMPAwcPExcfHyMbHycvMyMnJyMfIycjKycrMzITKgM3IycnGwsPAwMHDwsXHxcXExsbFwcG+wsK+vry8u7u9u7u5trS4tri3ub29t7SztbW0uLi4tbWzsrO0s7Gxsa+ura6wrq2sra6trq2srKuoqqysrK2qrqeopqSnpKalqKSin6GhoKGhoaChoaOlpKOgpJ2in52en52bnJqcmZmZgJeZmpeUlaWampuampqcmZiYmpmcnZ2hoaCgoqOio6Kio6aqrK2sp6epqKampqegoqSmp6apqKqoqKmoqKqoqqmqrrGztra4trW0tbe2tre6trzAwMHBxcXFyMjJysjHyM3Q1NHU09XezNHU3dHQ09PV2d3b3dna3eDd29ze3+LegNvd39/g5OXn6Ons7+3s6ejs6+rt7/Dz9P32+PX29fPz8/f19vX29/H09vT2+Pf1+fr1gfTx9fX1/vv8/fv2+PXz8/b09/Pz9PX39fT08O7v7u3v7vDx6+rn5uLg39/e4dvZ1tTS0M/MysrIyMjLycrIyr7Awr+4uLiysrCtr6q5gLGxrK+qpaSfn5+coJucl5iVkJCNjImMjIyIh4eHhoyJhYeFg318eXl1c3Pj5t/i4nFzcN9v3uHd4+Te3tPP0M3Q0s/NyczNx766ure0u8WwmJmppqampKGhqKagoJuampaYk5KQjY6NjYuNkY+NjpqNiIGCgYJ/g4CAfXuAfoKHgIaFhnt8dXV2cXZ4dHR0cHR6e+d2k3h2dXV3dX52c3NubmprbWdtbGtub29rbGxwbm9udHF1cXJsbW5zb3RybW5tbW5vcHRvcXRrZGdwcW9tcHlnbXFpbmtwdG5zcnVvcnN0fXVxeXR3fIGCg4CKiIqGgn+GhIaAf4F+f4CKhIWKgIuEhYeLlpCOkYyHiIyPkJORlJqelpWYmJebm52ioaelpKWkoqaqplZUVKOjU6hWVVhYV1pYWFZYWltZWFhYXV5eYF1iWVpbW1hcYFxbW1xbXFxdW1xeW1xcXF1cXVtZXl1aXWNfYV9lX1y0XbNbXV1dWV1bWbJYWa9YXlmvWFhYXlmtsFt+tFpaWlhZWFlVV1paWltcXltdXV9lXV5cW1pZWldYV1lXWllaW1lYWVlZXFtdXV5dXl5cXVxdXmJhYGNhYmRiYGVlZWdnZGZlZmZlZmhpbWtpaWttamxraWv/f/9//3/XfwGA+H+FfgV/f39+f91+AX3/fpV+B39/f35+f368fwN+f36Ifwh+f39+f39/foR/BX5+f39+2H8CAgQAgKytrK2ur7CysLG0tbe2ure+v77BosXEx8bIycjKy8rLztLU1tbX2dnb4d/h5+Tm5+jo6unn5+nu7e/x7/Hu8e/u7O3x7/Hx9PTy9fLz9fb39vf49vb4+Pf59/eehvT19Pb2+Pr39fPx8fLz+PqA+fb19vP18fTy7+/07fHx8O7yA/Dt74TugPDx8u/v8fHv8e3w7uvr7e3t7uzs7/Dv8vLy8PLw8fTz9PX28/Pu8fP09/f6+ff18vL29/r6+/n4+ff39fX29vT39vX08vPu7uvn6uvr7O3t7Ozs6+ns6evp5efq6evq7e3t7u7u8vT39PX3+Pj29Pb49vL0+Pf4+vj4+fXz8/PsOeno5ujl5ePl397e4dra29vc3Nzb19LSz87NzszO0s/OzMbGyMTBwsbFwsDAv766wL2+wMK/w72/wIS8Sb3Av7y6ubm1s7Kwr6+vrq2rq6moqqmqrquqra6urausrayur6yurKyvr6+zs7K0s7O0ubq5t7e2tre4tra7vcHAwMPFyMTCwMGFvwPBw7+ExIC/wcPFxcbHyMbJysrCwL/BwMDFx8rMyMfKysfLysvLysnJx8jJyMjKy8fKzMrLzc/NzszLzc/Pz83P0NFp0dDS0tFyaWpp1NXV1tXU2tjY2dvf29rb3tvc3Njc3dva2t7b1tnV1NLQ09XU0tHOysjJyMbEyMnIx8/PzcvHxcDBwIDAvr27ubCxs7S0tri1ubOrpqalqailpKSlpqKioaGcn5agmpudnKGhmpWXlpORjImJiIWE//39+oGB/IKAgP2B+fr17OXf3tzZ3+Th5uDe3+Lb1dbOzMzNvqaovLi0tLGxsrWtq6mrrauprKqopqikopmVmpiWmZiWlpORlJSWlICPkZGMk42Kk5SMjomHhP+BhoWJtoeMi4WHioaBi5aVjImJhIiChoiOhYaHhIj9g4eIj5eIh4WHhYiGio6RlY+OjpSUkZWTkZSUkZCWlZ2VlpSVkpKVlJSclZSWmpienJ6foKano7SztK+qrKisq62srriyta+0uLa7vre5vbm8wWO+xsHAwM/KztLNxs3X0NXY1dbg09PR1dXW0tHT19/h6e788PDr7u7r8fLu8Pb1+Pv9///89/f+gYKFiIuMioyHiY6OkI2LiI2Oko6Ji4mQj4+PkZGRkpCPjJORjImJi4mJh4eEiICHjYuQjI6LiYeJhoODiYWFhoqLhomKh4aFhIWGiYKDg4SFiYT+goaEgoWJiIqOjYiOioqRkpKRjYqJiYmIhoyJiIiFhoiIhoaGiIqKi42MjIqNjY6RmJKRkJORjpKOj4yOlJSSmpaRlpiZmZuamZiYmZycmp+lpKmpqqytrK2rqiKNjo+PkJGSlZOUlZaYlpiYnJycnpCfn6GgoKKhpKamqKmrhKxVra6vtLO0trW2uLq7vr68vb7Avr6/vsG/xMTGx8jKyMnHx8jGycnIysvKycrKy8vNzs3Ozc6IctDR0dDP0dHS09LU1NPT19dt1tLS1dLV09XV1NPW0oTUgNfW1tjW2NfW19jZ1tfY2dfY1NjX1dbY2Nna2dnc3Nrc29rY2trb3dzc3N7a2tjZ29vd293b2tjX19vX5NfW09LU0dHQ0dHR0NHQ0M/P0M7PzsvOz8zNzs3LysnIxsnHycnGyMvJysnJx8bFxcTHx8rGxsfHxsXGyMvIxcXHxMLEgMLCwr++wMK+vr+9v7u7uLm2t7i8uLa3t7a0tLOwrq+ur6+wrq+zsbOyrq+wq6mrrqyqqaqqqqmtq6ysq6quqa2vra6ur7CztbKxsLKtrK2rq62trK2sq6yrrKurrqqrra+ysbCwsa+wsq+ysrS1tLW3t7i6ubq6vb6+vsDAwcPDgMDAxMXHw7/Bw8TDw8HFxsfJx8bHyMXIx8fJyMvLy8jIycrIzM7PzM7Oz83Lzc3P0s3Nz9DO0tLS0dLS09LU1NTS09PQ09bW2Nna2NfU0tbX19fV1dbWbNfW2NfVcGpratfZ2tra2d3a19fY2tbU1tnW19bS1NTS0M/U087QzczJgMbHycXEwsC/vb28uLa6u7i0ubm0sq+vq62srKusrKulpKOinZ6fm6GfmJaVlJaTj46PkJGNjo2OioyGjomIiIaJiYJ+f398e3l3dnd1dOPj5eV1deRzcXDgct/i3trW0M/MycvNyMzHxcbIwLi5tLS0uq2ZnK2ppaShoaOknZqYgJiYmZWVk5GPkZGPiYiOjYqKiIWFhYOHh4aCf39/fH99eoCBe398fn7zfH97fZ57f395e356d3+MiYJ+fXl8dXh5fHZ3eHh653Z5en+Hd3Z1d3R3dHV2eHp1dXZ7e3d6eHh8fXt7f32DfH58fXt5fXl7hH59foB7f3x7f4OGhoCJgImHh4eMjJGSk5KUnJaXlZeam52fmpydmZucmZ+ampupo6WnpJ2lrKerrayvtamrqKyur6ytr7K4t7u/ycHDwcXHxMjJxMbLysvNzs/R0M3N1Wtqa2xtbWxtamxvb3BucHBzcHRzb3JxdnRzcnR0c3RzcXB5d3Nwb3JwcHBxcnJygHFvc3BzcHFvb29ycW9udm9ubW9wa21vbW1sbGxub2trbG1vcm/YbHJvbG5wbm5xcG1zb25zcXJvbGxsbW9ubXFvcG5rbW5vb29wcHFxcnNxcW9xcHBxdnFxcHJycHRzdXJ1enp4fnt2enx9fX5+fnx9f4KBf4KEgoaGhomJiIqKAYuAbm5tbm9wcnRzdHZ1dnV3dnt7fH10gICBgYKCgYOEhIaIioyOjo+RkJGWk5SYlpeZmpmam5qbn6SipaWkp6Wop6empqmoqqmqrautrK2vsbKytLS2tri4trm2uHFetre2tre7vL29vLy9vL3AwmPBv7/Bv8TCxcbFw8fCxMXEw8eAxsXHx8fIx8nKzMnKzM3MzsrMycfHycrKzMzNz9HP0M7OzM7Ly83JycbHxMTBwsTFx8bIyMfHxsfJxtnFw8HBwsHAvr69vbu+wMC/vr66ubaytrm4ury8urq4trW2s7Oyr6+ysLGvsrCvr6+usLCyrq6ur6+vrq6wrKenqaWlqKhQqKqnpqiopaWlpKajo6OlpKWlqaOhoqGhoKKioJ6enJ2cnZucoJ2enpqanZuanJ+dnJudnZ2coZ+goKGfop6hpKOkpaeoq66rqqutqqipp6WEpoCoqKmprKuqrquqrK6tra6vsbKztrS3trW4uLe8vL3AwMHCx8rKycrKy8zNysfLzNDQ0NTY2dfV0tTT1NXX2Nrd2d/e3d7c4OLk5OXl5ePm6Orl5ufp6ejr7PDy6+rs6+vv7/Lz9fb5+fv7+/r8/vn7/fv7/P78+/n3+fz8/Pv9/4D+gP37/v//hoCBgP78+vn39fr39/n7//n39vj19fTx9PTz8PH08Ojp5OPg3uDj4N7b2dbW1tTQzdHQzszPzsnHxcO/v7u5tbOxsaytrq6rq62prqyloqKho6GdmpuampWUkpKLjYWMh4eIhYuLhoKGhoOBe3h4eHZ15eXn43V44oBzcXDfct7h3trY1NPQztXY1NjPzc3NxsHCvsDBx7SdobSxsK6sqaennp+eoKGgl5aVkpCRjYyFgoqKiYuLiIiHhYmJiYiDgoJ9g4ODjZGIh4F9d+V1eHV1mHBzdXB0eXdzfIeDe3d5dnpzdXV3bm5ubXDNam1rcHloZ2hqaW9tb4BxcnRubmxxcW1xcW9zcm9uc3J1c3Rxcm1pbmpsd3Bwbm1qb2xqamxvb2l2dXZ1dXp5fHt8enuEgYKBhIaFhoeDhYiGh4aDhoB9f42HiYyKhIyVkJOVkpSZjY6Lj5SUkZOTlZqYnqCroqGgo6SjqKaho6OhpaeoqquppqatV1hZW0NdXlxdWFlbWltZWltfWl5dWVxbX1xcXF5dXl5eX11lYl9cXF9dXV1eX19eXFldXF9eYF9fX2JgXVxiXVxcXF1ZXF1dhF5PX19bWllaWl1ZrlheXFpbXVpaXFtYXVpbYGBjYF5dXFxdXFxfXV1bV1hYWVhYWVpbW1xdXFtaXl1dYGZfXlxdXVtfXWBcX2NiYWZkYGRmZIRlDWNiZGZmZWhqaGtrbGyFa5R/AYDGf4KAkH8BgP9//3/pfwGAhX+EgO5/hH4If39+f39/fn/OfgF9nn4Bff9+in7UfwF+238CAgQAH6+sr7CysLS1t7i7vb2/wL/AxMXFxsnLz8vO0dTS0tOE1IDV2Nrb4ODp5unr7vHw7u3v7fDv8fjy+Pn29fTx8fPy9vj29fn59/b08/f5+v7+gID+/vv8+Pr79//8+vj9/P3//fj59/b1+fv5+/38/Pn19/j39vPz9fXz9fb19PXz9fL18/Px9fT29vXy8/Hy8PPy8/P08/L38/T09/T39/b3+4D5+ff2+fz8/Pv6+fv2+f7+/P2A9/b3+/r6/fr8/fv8+fX6+fn29/b29vf29PTx7+3v8O7w7+/w7/H49PL07+7w8fD09PDz8vTx9P39/fz+gP///YD+/fj8//36+fj0+Pf08/Ty8ezq6Oro6Orm5eXk4eHf3eDf397b2dfX1dLU14DY1M/Ozs7MycjIy8vHyMbEwr+7vL/BwMDBv7/Avr28vru8vr7IwsC8tbW0s7GwrrGurLarqaurqqytsLGwsK6wr62urK+qsLKwtrq1trq2uba5tba5ub28vb27usC+v8O/xMPDx8jHxsbFxMXDxsTExsbRycvMyc3JzMrKzM7NzyLMy8bDxsTGx8vNzsvNzMzNyc7MzszQzM3NzWfMZ2doz8/PhWiAaWlpatNqampsa2trbnBta21ubGxta2zU1tjZ3N3e3eHhcePg3d/i4eHf3uHi4uLh4Nzb2NXb1NXY2tfVz8zLysjHycvJys3Ly8rHxsnHxcXBvba4tre2t7q7vLi3tbKurKmsq6usqK2qqqShqqefnJ+bnaGbm5mXlZWYlpaRjo6AkYyLjIeEgYKRgIH5/oL+/Pj49u7t4+fn6Onh5enj4eLh393dz8zNyauuwLe2uLOxsrO7qKulpqesp6WqpKCgn6CYnZ+dm5SSkZORkIqSmI6Sk5GUkJCUko6LhoeGhYOFiISIgYWHhYqLioiMi46RiIOHiYSKiY2NjIqOjY+VjYaAiIqNjYmNj4mLiYuTnZmWj5KVlZGTqJ6hnJmUlpWSk5SYnJqllpSXqpeeoJ6cn5+jmaeota+1ubWyr6yutbCvurO2u7u7urzEt7u7ur+9v8TFxM3I0szK0s3MxtfS09bb29zb3NrZ19Tf19vk4eLr6+718vDv8fLz/oH9+4GDgf8SgoGFg4WDgYWHjpCMiYyNkI6ThJUKkZGSlJGQkZKOkoWVgJOSkJmRnZOSkIqOjIyQko6Njo6Nk5KQjIuNjYyKi4eJjIqMiouIi46Oi4uLiouGh4mIiYuLi4eHiImLioqLjI2Ljo6NkZKOkJCMioyGiYiHiYqEiImKiImKi42NjoyKi42LjpWTkJKTk5CUkpSUk5KpzpGYlpiYm5yenZycnJqcEZ6en6Clo6mrqq+vuLGusK6sDI+NkZKTkpWUlpeYmYSaHpufoKChoqSnoqWnqqmoqqytrK2sr6+vsrG3tbe3uoS8gL+/wcHCxcDDxMTFyMfKy8vNzszLzMrKy8zM0M7Nzs1mZ9DRz9HP0dLR29bW09TT1NbU1NbV19fZ2tnY2drZ19XX2djZ19jZ2dna2trb29jc2dzb2tnc293c3dzd3d/c3t3e3t3c3N/d3d3g3t7d29vf3t/d3N7f397d3+Di3t/hgODe33Dc3Nvb2tjZ19jY1tbU0tXU1dHR0NHS1dLS1NLS0dPT0tPQzs7LzdLPztDMzc7NzdHOys3JysXGy8rJx8llycrJZsrLxsjKyMTDwr/Dwb/AwcHDwMC/wb69vru5ury7vLq3uLa1srGxsbOzsrO0tbKxsrKysbGvsLOyrrCuGa+urqyvsbKwrq+urrCwsbGzsbGys723t7SEsDuur62xr663sK+wr62urrGwsbKxs7OztLG1sLa5t73AurvAvb+9wLy9vr7BwMLEw8DFxcbIxcnFxMjHyITHO8nKzMrLy8vTzMzNys7Lz83JyczLzs3Oz8/T0tHQ09PSz9PS09XS1dTV0tbT1NXXa9ZsbGzX19ZtbGxrhGyAbthtbW1ubGxsb3BubG1ubW1vbm/b3Nzc29vb2NrbbtvZ2Nrd29nX1NbX09XT0s/Py8nQxsbIxsPCv7+/wL+8v8C+vL26t7Wyr7CvrrCvrqquq6mkoaChop+gn52amJWXlpaWlZmWl5OSm5aOi4yJiYuHhoSCgH+Bf398enp9eXiAeHVzcnOCcnLg4HLg3t3b3NbVzM/R0NLLy8zHw8PBvbu9s7K3tJqer6anqaWioaCtmp6XmJealJGVkJGTkpGJj42OioWFh4mIiIKFiX9/gYCAf4CCgX9/fX9+fXt9fnt/ent8en19fHt9foGEf3x+f3p8eXt4d3R5d3qBenZ2eHk0eXV4eHV1dHR5gn58eHt+e3Z2iH5/e3p2eX57fn1+gH6PgH5/jH+Dgn99gIGFf4aEjISIjISKgI+TkpKblZabm5qanqSZnp+cnpuanpybop+poqCnpaajr6usrK+xr6+xr6+xsbm1try2tbq6vcXEw8XHxsfPaczNaGppz2hobGpsamlsbHFybmxtb3FucXFxc3V1dnV4dXV2dnN2d3Z3dnZ1dXWBeIB5dXRwdHNzdXd0c3NycXZ2JHVycnNzc3JybnBxcHJvb25ucXBub29vcW5ucHBxcnJybW5vcIRxU3BxbnFxbnBwbXBxcG9xbnBwb3FybnBxcnBxcnJzcnNwb3FzcnJ2dHJyc3VydHN0dXV2jKp4fnx7en1+gICBgIGAgICBgYCDgYSGhYiHkIuJi4uLgHBucXFycHNydHR1dXV2d3h5foCAgYKEhoOFhYiGhoeJioqMjZGSk5WVmpeZmZyfnp6dnZyfn6KopKmpp6eop6ipqKurqamsra+wsK+xsK+xslpbtri1uLe5urnFvr26u7q9wL6+wL7Av8HDwcLCw8PDwcTHx8jGx8jIx8jIx8fIgMfMyc7NzMrNy87Oz83Ozc/MzcvLy8zLzNHP0NHU0tTT0dHV0dHOy8zMysnIyMnKxsjKycjIZcTFxcbIxMbExcXDw8G+w8PEwcPCwcHBvby7urm6vL29v7++vbu8wLu5uLKwsbCwtLSxs7Cwra+ztLOyslmxsa5Xr66qrK2qqKmoMaWpqKipqaqrqKmoqKamqKamp6impqSho6GgoKCio6SkoKGioZ+cnZ6fnZ2dnqCfnJ6EnYCbnaGhoKGgoKOkpKalqKenqau1sbGxra6urqurqqyrq7Orqqqpqamqr7CwsbCzs7O3tLm0ubu5wsa/wcbBxMPFw8XJys7P0dLPzNDP0NLQ1dPW2tzd29nX19na397g4uHp4+Pm4+jl6ufk5efl6Ofo5+fs7O7v9PX18fTy8/Xz+BH3+ff8+Pv9/4D/gIGB//7+gYWAYIGAgf+BgYGEgoGBg4SCgIKEg4OFgoL+/f38+/v8+/7+gP/7+fr8+Pf18/b39Pbz8u3r5+Pr4+Pm5OHf2tfW1tTR1NXR0M/LyMfEwsPBvru2sauwrq+trK6trqurqqmlo4SiY6GeoZ2clpOelIuHiIWGiYODgoGAgYWDg357en15eHh0cW9zi3Fy4eJz4t7d3t3X18/U1dfa0tPVz8rLysfGyLy9wb+jqbyztLWwq6ensZ6jnZ2cm5ORko6Ojo2MgoyOkY6JiYSKgIOIjoGDhYKDhIaJiYV/e3p3dnZ5fHl7c3d3dnp7eXd5d3p9dnV5enZ5dnh2dHF2dHN9cWhoaWtqaW1vbW9sbXN5dnRtb3RxbW56c3dxb21wc3N0cXNzcXxvbnGEbXJwbWtubnJtdXJ8cXd5d3l5eHp+eniAfoGIiYmGhoqBhISCgIWEg4eFhYuHj4uJkY6MiZaSkpOVlZOSk5CRk5KbmZmgm5ugnqGnpKWkpqanrFenolNVU6dVVllYWlhWWltgYV5cXV1eXF9fX2BiYF1dYV5eX2FeYmRjYmJgYGBfamBlYWBiX2NiYWJkYGBfXl5iYmNgYGJkY2JiXl5fXV5bXFxdRWFhX2BgXl9aW1xbXF1cXVlZW1xdXFtbWltXWlpaXF5cX19eXF1ZWllZW11YWVpcWVpaW1tcXVtZW1taXWNhXl9eX1teXYRfIV1yYGVkZGNkZWdmZWRkYmRkZWVmamlub2xvb3Rvbm9ubdF/goDifwGAvH8FgH9/f4D/f8d/CIB/gICAf39/iYABf5KAin8BgOl/A35+f/9+7H4Hf35+f39/fv9/ln8BgKB/AgIEAICyq7O1tra2t7m6uby+wsPHw8XIzs/S0tHY2tbY0NbW19ba2t7i3ODf5Ono7vTw9Pb09PPy9/f6+fz+gP78/PqC+Pb3+vv9/fz7/Pz/+/6DgYKCgYCBgYGAg4KA/4H+/4GA/4CAgP6A/fn9+vv//v39/vv7+/z6+Pr5+/b5+Pv4+oD6+/j4+vr3+vj5+/v7+fn7+fn3+Pn7/Pz9+Pv4/fv6/fv8/Pv4/YD+//+BgP2AgYD/gP7//oH8+/+AgoGBgIH/gP+CgYGBgP7+gP///P35+/f18vTz9ffy9/n19fTy8/Dz9PP09fj5+Pj39/j8/v+BgoKDhISEg4KAgYCAgIGAgYCA//r5+fn38PHu7uzr7e3s6+jm5uLh4+Xl6OTe39zb297c2dnW2NbU1NPQ0tDT0M/KxsTFxL+9v8C/wcLBv769yr2/vL/AwsTIxb67ubaysbSysLGvsqyvr6+stbO0tra1tLW1sbGxs7Sws7K2trq8vLe5t7i6vLu/v7/BwL7Aw0DAxcXGxcbHx8nKxsnJysrLyMrIysnLztHSz9PQ1NTR0c/P0MvOysjJytDNzdHR0tHQztTS09TP09LP0dBoZ2dnhGiAaWlpaGlpaWtramxtbGxtbWxtbm1ubm5tbG1sa2xtbdpwcXLkcuVzcnNxcXFy4uLh4+Pk5OHh3tvc2tjY1NXW2dvX2tXU0s/Oy8zNz8zMzcvKy8rFxsTCv72/vr28u7y6vLy4t7Wzsq+vr66wr7GttK2urKumn6Cgn6SjpKefmpiApZyXlZOVlpOQkJCNhoSGg4SAgIL8+fPy8fDo5ens6+zk3unk4+Lb4N3W08zIxq+7vr27u7a2s7K1ta6qrK6vsKqmoKWkoqimpqKbmZ2bmJSUlJGWl5OVlJOUlJKRlJCMjI2OjpKGhYaKiYyOjo+Wi4qKjZKPjYyLjomSjY2OjY+AkpWQkYyGioiFiYWJh4OBiYePkZCTkI2Rk5OUlpuYl5eYnJGVkpefmpaSnJSXlZean52cnqOjn6WqtKu/tbSvsK6yorezu7u9zMbP4M3IysjFv8a+wr7Exc/WzNDP0dHNzNDM1NTd197g5uLk2+Dr4uPl6ujt8vL07/H28/r6+YJHgoKHiIqJiomKjYqKiIyKjJGSjY2MjpKSlZaWlZWWl5WRj5iZlZGTk5WXlZWQkpKRnpGSkpmXl5OVlZORko+Qj5CRk5CQk42EjnGPi5GNj4+Rj4+NkpSMjo6NjIyLj4yPkpiIjIuLiYmOj4+RlJCUkpKRk5KQko+Pj46Li4iMjI6MjI6PkI+akpCOjpCVlpWUlpiWk5mWl5eXlZeXl5iXnJufnJyenJ+gnp+io6aoqqirrK2ssaywsbGtr4CRjJOUlJaWl5mamZqbnZ6in6ChpaWmpaWprKqsqKysr66xsrO2srOytri3ur66vsDAwsLCxcXHxMbHZMfIystrzs7P0M/Qz87Nz9DS0dNsampqaWlra2pqbGtr12zY2W1s2W1tbttt2trd293e3N3c3dze3+Hf3d/e4Nze3uDf4BDg4d7d39/e4N/g4ODi4eHkhOJd4+Pk5OTg5OLk5OLj4eHh4t/jcuLi43Jw4nJzc+Ry4+TjcuLf3m1tbW5tbtlt2W5tbG1s1dRr1dbV1dTX1NTR1NTW1tHU19HR0c7QztHS0NDP0tDPzs3MzM3NzWdnhGaAZ2ZmZWdlZGRkYmNjxcPDxMbGw8XExMLBwcC/v7y8vru7vL28vLi1trW2t7q6t7i1t7W0srOxsbG0s7Sxr7Gys7CwsrOxsrOysbGwwbO0srW2urzAv7m4trSxr7Owr7GytbO2trezuLW2tLW0s7O2tLS1urq4u7u9u76+v72/v8ApwsLAxMLCxcXGxsnHy8rLycrKycrMyMvLzMzPzc/Ozs7Pz9DQz9LP1NOE0hrT0dTU09PU2NPS1dXV1tbV3Nra3NfZ2NfY2oRtA25tbYRuhG2EbgNvbm2EboVvgG5ub29vcHFx3nBwb91u3W9wcG9wb3De3dzb2NnY19bT0M/Nzc7LycjHxsLEwsTEwcG/wMDAvb26uLS0s7CzsrGurK2rqaempqWnpqOin5ybmJeWlZeYmZaelpaWmJOOjo2KjImKjIWCgIqCf359f4B+fHt7eXRzdXN0cXBy39zagNvb2tPP0tbV1tHL1M3JxsHDwby7uLi2oamqqKamo6OioZ6enZmbmpaWkpGQlZWTk5CQi4eHi4mJhYaHg4SGg4ODgYKDgX+BfXt7fYCCh3x7en18fX1+fYJ7eXl9gH18e3p9eYB6eHl1dnl+enx7eXl3dnh0enh1c3l1e358fXp4gHp7enx9gX98fX+Den17foN/fXyEf4F/gIGCf3+BhYOAhIiPhKOKioiKi5KGlpCVk5WfmZ6qn52in52Znpicl5yeo6qipaaoqKenq6evrrWxsrO5tbWzt8a6vL2/u8DCwsPAwcfGysrIZmZmaWxydG1qbGxsbWxwb29ydHBxcXJ0YXN0dXZ4d3d5eXZ2fn57dnd1d3p3eHV3eHiDd3d2fXl5d3h4dnZ2dHV0dHN2dHR2c3Jyc3RzcHdydHNzcnNydnhxc3Ryc3NydXN1doJvc3JzcHBzc3JydXJ0cnFxcnJwcnGFcwZxdHNzcXGEczV7dXNyc3V4eHV0dHZ2dHh4eHl7enx9fHx6fXt/fn+BgIKCgX+BgYSEhoaHiYqKjoqMjY6Mj1RybXN0dHR1dnd5d3h4e3p9e3x+goODhIOIioeIhImKjo2RkpWXk5WUmJqZnJ+doaKioZ6eoaGko6apVamqqalbq6mqrKyusLGwsrGxrrBbWVpbW1yEXYBfXl68Xru8Xl69YGBhw2LDwsTDxMTDwsLExcbJysnIycnKyMnIy8nJy83LzM7Qz9HP0NHT1tTV2NbU0dDPz9DR0tDU09fW1djV1tTTz9Jp0dDPaGbMaGhnzWbKy8llyMbIZGVkZGNjxGPEZGNjZGTFxWPExsPCvsG+vby+v8PFwRfExsHAvbm6tbe3tLS0trW1tLKxsbOztIVcgFtbWVhWV1ZWVVZVVlasqqqrrK2rrKyurKqqqaeop6ippqSjo6Kko6Klpqanqaejop6goKKio6Kko6Wjo6CdnZ+gnJyfoKCjpKanqKe1qaqoqKmtsLW2s7Oysq6sr6yrq6uuqaysraqysbO1tra2t7q3uLq/wb7BwMPCx8nJxMbFB8bIzMzR0dKE1FPX0dXT1tXY3dzf4Nzf3+Dg4+Hj4OHg4uXm6Ofr6e7t6urq6+7r7+7u8PP59PX5+Pj29vT7+vz++v78+/3/gYGCg4SDg4OCg4OCgoGBgoKBg4OCgoSDSYSEhYSFhISEg4KBgoH9gIGA/4D/gICBgICAgf3+/Pn4+Pf29vPw8O3q6ubl5eXk4OPf4N7a2dbV09LNy8jHxMbFwcK+uraztLSEsguvsbCsq6mnpqSioISegJqomZmal5GKioiGiYeJjYeEg4iHg4B+f4B+eXp5d3BucG9zcXFz4t3Z2dfW0dDU2dfX083Wz87Mx8zLxsO+vbuls7a5t7WwramloqSmoqKgmZiTj42QjIqNi42KiIiNjIuIiImFh4qFg4SChIeHhIeDe3h7fH+He3x7e3l6eXl7gIF3dnd4fHl3eHh8eYF6d3hzdHV6cXJva25sam5pbmpoZWtocHBwc29tcHBub29zcG1tcXdtc3FzeHBranBtb21ucHJwcHFzc3Bydn5ynHp6eHl3fHJ+fIGEh5SNkpuNiZGLioaNiIuFhoaLk4qPjpCPi4yPjZWUmpSVlJqWlpWagKyenp+gn6KkpKSgoqemrKymVlVTVldiZ1lYWlxaW1pdXV5iZl9fXl5gX2BgYGJhYmRkYV9oaWVgYF9fYmBgXV5fXmphY2NsZ2dkZGRiYWFgYV9hYWRiY2ViY2NjYmJeY19hYmNiY2FlZ2BgYF9eXl1hXmJkgFpeXF1ZWVxcWlteLl1gX15fYF9dXVxcXFtaW1laWlxaWlxdXFxhXVxbWltfYF9fYGJgXmJgX2BhYWKEYxxmZWdlZWRjZWVkZWZoamxtbG1tbWxuaWxsbWtvun8BgIR/AYCOf42ADH+Af3+AgH+AgIB/gL9/E4B/f3+AgH+AgIB/gH9/f4B/f3+GgAN/gH+FgAN/f4Clf5KA/3+8f6eAB3+AgIB/gH+HgOd//36vfgF/vH7/f75/AgIEAEy1try7u7y+ury8vcDCx8fIyc3O0tHW2dfZ19fY2dzd2t/h4Obo5Obp6O7y9PL2+fj6+vj4+Pv5/f+AgoGBgP77/Pz7+/3/gYOCgoGDhIRlg4OEgYCBg4KCgoCAgIKAgoGCg4SDgoKBgIOBgoKDg4GAgID9//+AgID+///++/r7+/39gP79+/79/I3/gf/8gIKA//z+gYCB/4CA/v37/YD9gYH///6BgICBgoCChIOEhoWEhoSEgw6EhISDg4SDg4OCgoH//4SBgP3+gICBgP76/Pr3+fr39Pj5/Pj5+vn39vb2+f77/v+A/YGDhISFhYWGhYaHh4eGhYSEgoCBgIGAgPz6+vb08fDt7vDw7u7u6O/l4eLk5unm4+Pg4eHf4OHf2tzg29vY1NrW2NfV0s7KyMrQxMXDwMTBwcDAwcO+v8DBwL/GxsrGdcLAu7m4tLW4trOytLOyr7GwrrK0tbe6t7S3tLK3t7W0ubu8vb2/vL28vLu/wMHDw8PAwMTFxMfIyMfJzcvNz87Q0NHS0c/Oy83Q0NHS1dXh2dPS1dXSz4PS0tDNzsrNz9HR0tTT09XS09Vra21s0mrRaWpnaIRqAmtqhWsNbG1tbm9xcHBvhnFvcIVvA25ubYRuQHNzdXR1dXVzc3Jyc3Jy4nLlc+fp6Oji4Nzc3t3b2NnV3Nvb2djU1NXRztHU0dPSz9DMy8zIysnHwr7Aw7/AwMGEwIC5uLW0sLO5uLi1t7awraypqaemp6eqpqapqKKfnZ2XlpqbmJOUkpGLk4SChIGBiYT9/fjy8fPz7+/v9/Hz6+jq5eXk5drc1dHMycDAxcXDv7ixs7awsK+qrrCwsKyvp62nq6eioKGduJ+fmZWhlY+Sk5OSkJKSlZOXkpCMj5KVloCPjY2LjpGWk46Nh4eMi5WTlpepkI+PjIySjYuNjo2MjYiMi4mLjI2Jjo2Mh4uYk5OVlJOOkY+QlJSSmJiVnJiUl5CVmJqdnZ2XmpqboJ6fn6OlpqqopqStrrKvrbCyt7q6trvTxsbN3MzOwMLFvcPDxcnIyMnPy9LMzsvHytLO3S7d4uDq+e3o9+nh3+To7/ry8PLz+vj/+v78/oCBg4SFhoiHi4iHio6Lj4+PkpWQhI+AkZaZmpuanJycm5qalpaUk5SVlpmYmJiZmZeWnJWWlZifnZqcnpqYmJaVkpiSlZaTlJSUkJKSj5KSkpCUlY+RkJGPjI6TkI+QkpGRk5GTkpOUkZGUkZOVlJSVlZOTk5GPkY+PjI2Nj5KUkZGSkpOVlpWUl5GQjpGSlpillpKUmJgmmZSVl5eXmZiYnZ2en5+fpaGjo6KiqKinqqyrrq+vrrGxs7e1s7NMkZOYlpaZmZmbnZ2fn6OhoaKkpKalqKmpqqqrra+xsbC0tra5u7a3ubm9v8C+wMLCxMbFxsjKyMnKZGVlZWbNz9HT09LR0WhpaGhoaYVqVWtsamtrbGxsbWxtbW9ub25vbm9vbm1ub3FwcnFxcnFxcXLj5ORycnHh4uPi4eHi4+Tlc+bl4uXm5X/mdefmdHh16+jpdXR26HR16OXm5XPkdHTo5+eEdBJ1cnN0cnN0c3N0c3JzcnFwcHCFb1xwb29u29ptbG1s1tdsa21s19ba2dfZ2dfT1dTX0tPV1dTT0dHS09HR0GjMZ2hoaGlpaGdnZ2hpaWloZ2hmZGRkZWRkyMfJyMfFxcLCxMPBwMG9xb27u76/wL27u4S5gLq7urW5vLi2tbK2tLa1tbSzs7O2vLKzs7G1s7SysrS4s7W3t7a2vL3Cvry5tba0sbS3trW0tre3tbe0sra1tbe6t7a5uLi+vLu6vr++v73Bv8PEx8fJx8fGx8jGx8nLzM7Pzs7P1NHPz83QztDT09HS0NDR0dPT1dTe19XU19jXOdOA2dnY19nW2NfY19fZ2tjb2NrbcG5vbtlu229wbm9wcHBvcG9xcG9vb3BxcHBwcXBvb4VxcHBwb4RwCnFxc3NzcnZxc3GEcIRxgHBw3m/eb93d3d7Y19PR0tLRz8/LzcnGw8PCw8XCwMPEwcG8u7u4t7e0tbSzrqysrqmqqaqqqampoqCcmpWYm5udm52dmJWUlJORj5COkYyLjYyHhIOCfnyAgn97fXx8eIZ0c3Vzc3h04uDc2dfX2dPS0tjV19HNz8zJxsS8wLy7gLm3qausqaimoZ6en56inpucmpeVlJeSmZSYlJCOjYeZiYmHhJGIhIWGh4WDg4KEgoWCfnp8f4GFgX9/fX1+gn59fHl7fXmAe3x8j3h4enh2e3d0d3l5eHl2eHd0dXd4dXl5eHV4gn58fXx8en58fX5/fH58en99fH96fX1+gYGFR3+BgYKCgIGDhoiKjIqGg4mKjYyMj5GVmJeUlqeXlZuonqGYmp6an6Gfo6CgoKekrKqsqqqqsKu1srSzuMe9ucS9uLe6vcPNhMSAycbKx8rJymZmZ2hpamxsbmtrbHBvcnNzdHZzcnR1dHR2dnd3eHt7fH19fXp7enl5enp8ent7fX59fIJ6enp7f316ent5eHh3eHV5dXd3dHV2dnR3d3V4d3d1dnd0dXR2dHFydnNydHV0c3RzdXV1dnR0dnR0dXV0dHRyc3RzcnRFc3RycnJ0dnd0c3RzdXd2dnV4dXV0d3d2eIt3dHd6enx3eHx7e358fIB/f4CAgIOBg4KCgYaHh4iJiYqLi4qMjI6RkZCQSnJzeHd2d3d2d3h3eHl9e3t8f4CDgoSHhYeGhoiKjY6OkpSTl5mVl5mZnZ6fnJ+hoKOkoaGgoqCjplRVVVVWrK2vr6+wr7FZW1tbhVkHWltcXVxbW4RdZFxdXF5dX19gYmNjYmJiY2VkZGRlZGRjZGXJzMxnZmbLzc7Nzc3P0NLUa9TU0tTU1HHXbtnWbG5r1dDPaWhr1Gxt2NbV1WzWbW7Z19VqamlpamhpamlpbGppa2lnZ2dmZWZlZGSHZULHx2VkZWTExWNiY2PEwsXDwsTFw8DCwsO9vb28ure1tLS3tbW0WrFaW11cXV1dXFtaW1taWVlYWFZVVVVWVletra+EroCrra2sqamqpauloqGioqOioqamqaqnp6ajn6KnpKWkoqijpqSko6CfoKKloKKioKekpqioqq2oqaupqaqxsrm3tLOvr62qrK+urKyvsbGws7KyuLi6u726uL29vcXEw8HGycjKyczHyMnIx8rLztHT1dTU1tfV1tfY2Nzl4eHk4UHj4+bp6Obl4uLj4uXm7O378u/t8O/u7ITz9PT09vT39/n5+vz7+fv4+/2BgIGA/YD/goSBhIWFhISEg4SEg4SEhISFF4eGhYWYh4WHhoaGh4iHhoWEhIOBiIOFhIOAgoKCgYKBgP6A/oD9/v389vTw7/Dt6ujp5ejl4+Dg3t3d2dbW1tDPy8rLycjIxMPAv7m2uLm1trW1s7GysquppqSfoKOgoZ2fnpmWlZKSj42NjI2JiYyLiIaGh4OBhIaDf4B/fXqLc3F0cnN/duXk3djU1tnV1tXb19nSz8/KxsOAxLvCwcHAvrK0ubq6tbKppqiipKOfoqCbl5WWkZmRlZGMjYyHm4qMioeVi4WHiYiFhIGAg4GGhIB7fX6AgX98fnt7fH57eHp3eX18g3+AgIp8fX16dnx1cXFycW5va21ta2xucGxxb21oaXZvb3Fwb25wbW9vbWtsa2tvcG9zbXBwb25wbnFqbm9wc3BxcXNzdnh2c3F3eXt7e3x+gIKCf4GWh4iQm4+RhoiLhYqJhoiBgoONjZWRlJGOkJaSnJmcmp+soZ2ooJmZnKGlsqajpKOmo6ikp6epVFVVVFRVVVVYVlZYXFpdXl5fYl9fYWBfX4ViCWRlZWVkZGJiY4RhE2JgYWFjZGNjaWNmZWZraGZna2iEZXxiZ2RlZmRlZWRiZGJhZGNjY2VmYmNiZGFeXmJfXmBhYF5gXl5cXF5aW15cW15dXl9fXV9fXVtcW1pYWFlcXl9dXV5dXmBfXl1gXF1aXV9iZHpkYGJkY2NfYGJhYmVjY2dnZ2ZmZmhnaWhoaGpsa2tubW9ubmttbW9ycHBwuX+FgIh/rYAGf39/gICAin8BgIZ/EYB/gH9/gICAf39/gICAf4CAhH8HgH+AgH9/f5+Agn+EgIJ/hICZfwKAf5iA/3+ffwGAkn+EgAN/gH+2gAR/gH+A4n//fut+/3+/fwICBABqvLzBwsPBxcDAwcHDycjM09DT09TV3tva3Nzf3eLj4+Tn5OXp5uvx9fXy9vf5+Pn6/v77+v3//ICBgIODhIKBgYGAgYKDg4OFhYeGh4iGhoOEg4OCgoOFhYWCgYKDhYSEg4OEhYSEhIKDgoSEBImEg4OJggqDgYKAgIGAgvz/hIArgYKBgoD/gP6AgYCBgIKCgoGCgICBgYODhISEhYOEhIOEhYeFhYSGhYaGhoSHBIiJh4WEhjaEhYODg4aFhoWDgoGDgoOCgYCBgIKDgoCBgoCA/4D///7//4CAgIGCg4KCgoSFiIaGh4iJiImEiICHiYeEhYODg4SCgf+A/Pn38vH09fTy8vL17e7s6ujq6+rn6Obn5uTn5uTe5Nzd3tfb293c2dfS1dHP1dHOycjExMTCxcbHxsTBwsPCyczIyMXDwb25uLW0uLu5t7u5tbW5tLOzs7a6vLq9uri6u7u+ub7AvcDDwsDDxMLDw8TFxoDGx8fJyMrKz9PPzcvLzM/R0dTW1NLW09TU1NPW2Nnb1NbW2G1u1tPS1NTU0tDRatNr1tdtbdra2tjXbm9sb29sa2tsbGtra2xsa3JsbGxtbW5ubW9ub29wcG9wcW9wb3Bwb29vbm9vcHBycnZ1dnZ3dnR0cnNzc3R0dnh5d3Z154B14t/d3d/j4ODd4d/j4d3a2NjZ2NbX1tXV2tjUz8zLy8rIycfGzMjHxMnTxr/Dvr29u7zAvL++uru5t7G5rquuqqyzq6morKagn5yXnJqcm5qZlZORk5SMiIWEhGyB/vz98+728/X6+vf99O3r6+bm3uDc2d/d1dO1ucHCv8HAuYC5tbu3trOysa6wq6WipqinpKSipKGcm5y5mZaWkZKRlJSOj5iVlpSUlZOWmZGQlo6Mj4yNkZGFhomLjYyNlJSNj4+UiouNioaRjo+PjouNj42IiImKioiIiY+MkJOSkJiRk5WUk5WVkJOTlJKSmpKUmZudm6Ojn52Zoayhp6eopYCgpKapqauwrqSvr7S1u7+7ub3Fx8bLy8XGx8HEycrM0/DOz9LN0dHRzNDW2On75/Ht6fvu7Orm4+n07On09PH19v6AgoOEgoaDhIOGh4iKi5WQjo6PkZSUlpWWlpGTl5aYmZydnqGgn5+enJqYmpmXmZSUl5ibmJmfnJyZmJmcm4CenZ+fnZucm5mXmZqak5ikm5iXlZeYmpeZl5yVk5KRkpOTkZCRlZWUlZOWlZealpaWkpGVlZGTkpSWk5GRkpGSkpKPj4+Qj5WSlJaUlJSXmJaXl5aTk5OWm5mbl5OTmpeVmJiWmZqXm56dnaChoKGmp6alpqapsK6srrGzsrS0swa1tLe3trpDlJSXl5iYnJmbnZ+go6Gjqqaopqemrqytra2wrrKytLW5uLu8ubm8vb27wMHCwsPDx8nJyszOzGZmZGVmZ2dnaWloaYRqAmtqh2sBbIRtCW5vcG9ubm5vcYRwEnFxcXBwcHJxc3R0c3l1dHV0dYh0NnN0c3N0c3bk6HV1dXZ2d3V3det37Xl6eXl3eHh4d3l3dnd2dnZ1dXV2dXZ1dXV2d3Z1dHV0dYd0gHVycHFycXJwcnBwcHFwcG9ubW1vbm5ubWxtbW9wb21ub21s1mvW1tXX1WpqamlqamlpaGpqbGpqamtramtqa2tramxqaGhnZ2ZnZmXJZcnKyMbGx8fHxMPExsHCwsDAwsLBv8C/vr28vb28ub22uLm1t7a6ube3tLi2s7m2trO1PLS1tbW3t7m5uLa2t7a8v72/v8C/u7m4tbS4uru5u7q4ub66ubq5ur29u768ur3Av8K9wMC9vsHCwcbJyobLCczNz83OztLW1ITTPdTU09bX1tTY1tjZ19bW2Nja1tjY23R03tva293d3NvccN1v3Nxub93c3dzdcXJvcnJwb3FycnFxcXJzcnmGcw5ycXJxcXFycXFyc3JzcodzD3R0dHVzdnR1dHR0cnJxc4VygHRzcnFw3nDY19PT1dfS0c7QzdHOycjGx8fHxcXFwsDDwL66uLm5t7OzsK+yr62rsLmuqK2mo6KenqGen5+en5+gm6WZlpeTkpePjYyQjIeGg32Cf4KBgYF+fXx/gnh2dHN0qXHg4ODa1NjV1NXV09jTzszOy8vFxMC9v724u5+ngK6ppKako6ShpJ+em5ubmZqalZSWlpOQj4+QjYyJiq2IiImHh4aHhoCBiYODgoF/fYGDgIKKgoGEf4CCgXl6fH5+fHt/fnd5eX55ent7d3x7enl3dXd5eHV0dXZ1d3V3fHh6fXt6gXh8fHx9fn59fX5/fn2AeXl9fX98goWEgn6CbomBh4eIiISHhoaHiY6Oho+PkI+UlJGRkpaYmJ6fnZ+fmp+hoqKnt6GjqKSoqaikqq+vvse3vbq1y8LBvru4vcW/vMXGxcXFy2VmaGhnaWdnZmhpam1tdHBvbnBzdXZ3dXZ2c3Z6eXl4eXl6fH18hH0vfH5/fX96e319fnx8goB/fn5+gH5+fX18e3p8fHt7fHx8dXmCfHl5eHp5fHh5eH2Edmd3eHl2dXR2dnV2dHV0d3l2d3h0dHh4dXZ2eHh2dHN0dHV2dnR0dHV0eHZ2dnV0dXh5d3h4eHZ3d3l8eoF4dnh/e3p7fH19fnx+gH9/gYKCgoaHhoWGh4mNi4mLjY+Nj4+OkI+RkpGUSnR0d3h5eX16ent7e359gIWChISEg4qHhoiIi4uPkZOVmJaXmJWWmZubmp6eoKGio6ampaOlp6RTVFNVVVdWVldXVldXWFhYWlpchFslXFtcXFtcW11fX19dXl1eX19fYGJkZGRlZGRlZGZmZWVqZmdoZ4doIWlqaWxra2xsb9XXbGxra2xta25s123WbGxra2prbGxsboRtAW+FbgVsa2ppaoRrDWpra2xrbGtramlqa2iFZiRlZmVkZWdmZmZlZGRmZWZlZGNjY2RmZWRlZWNkxWLEwsDAvl2FXAZbW1tcXF+FXQJaW4VagF9ZVldVVlVWVlevWbKys6+ur66sqainqqSmpqSio6KioqWlqKioqaejoKehpKeipaWnp6WloqWko6mnp6Wlo6WkpKmpra6tq6ysrLS3tbe2trazsrKvr7Gysq+ysbCxuLa3uLm8wL+9vry7v8LEycXKy8nLz8/Mzs/Ly8vJzM/SgNbZ29vc2t3g3d7e4eHk5ebq6+vq7evs7Orn6Ovt8Ovt7fGFhvn39/f5+fj5+oD9gP7/gID//v78/oOEg4aGg4KDhIWFhoaGh4WIhoWFhoaHhYWGhYWGh4aGh4iHiIeIh4eHhoWGhoaFhYSHhYaFhYSCgoCBgYGCgoOFhIKCgP6BgPn38vDx8u3s5+nm6+jk4t/g4N7b2dXRz9PQzsrHxsXCv7+8u8C8vLq+y7uyuK6rqaamqaSko56fnp6ao5aSlI+OlYyKio2LiImIhIqGiYaFhIB/fIGEeXZ1dHR4c+Xm5+DY3NnZ29va39vX1dXQy8LDv73Dw7/Boq24t7S0sqysgKisqaijoJ2amp2XlpmXlpSUkpSRkI+Quo2Ki4eGiIiHgoKJh4eGhoR/gIJ7fYR9fn15d3h3b3F3en57eXx6cXN0e3R3eHZwdnFxcG9ucHNxbWxsbWxramtvbG9wb251bnBwcHFxb2xra2xqbXVvcHJxb21xc3NxbXJ4b3FxcnBugHJ0dnZ4fXxze3l5eH2Af3+Bh4iHjY2KjY2HiYiHh4uVh4mOjJCRkYqNkpGjrqClop2wpaSgnZqfpqGfp6elpaKmU1NTVFNWVFZUVVZWWVhgW1pYWVpdXV5dXl9cX2NiYmJjY2NkZGVlZWRlZGZmYmRgYGRkZWNjZmRmZWdpaWlrgGpramloZ2ZlZGVlZF5jbGdmZmZnZWZjY2NpZGRlZGVlZmNhYGJiYmNgYWBiY19fYFxcYF9aXFxeX15dXF5dXl5dW1xcXl9jYGFhXl5eYWJfYWBgXl1dYGNjbWRiYmZhYGFiY2RlY2ZoZ2hqaWhmaGloaGlrbXNvbnByc3FzcnFzBXFzc3J0t3/MgIJ/iYADf4B/y4ACf4CFf6KAAn+A/3+Xf4KAiX8HgH+Af3+AgIV/xYACf4DbfwKCf/9+5X7/f8V/AgIEAAPUvsGExSrBxcbIyc3R0eHU1NfY1dfe4ePi5ubm6ezn6Onq7/P2+f75/v/8/P2AgP+EgICBgYGCgoSEg4WFhYaGh4aGh4aIioqLiYiHjIWHiYaGhYKEhYWHhoaIhoaHh4eIh4WEhoSGhoaFh4iHh4aFhYOEhIaDhYOEhIaFhoSEhYSEhIKDgYSCgoKDg4OCg4OCgoSFhYSEg4OBgYSFhIWGh4aGhYaIh4iIiImJiIiHiIiJiHWIiouMi4yJioiIhoeGh4eIiYeGhISFg4SEhYSEgoOBgoOCgoODhIOCgYGCgoKDg4SFhoaEhYOEhoaKioyMjI2MjYyMioqJiIeGhoSFg4WDgYKAgf/5+fP4gPj19vLy8fX0+fbx8e7w7uzs6evp6Obo5uPg4N+E3YDc2NfV1dPV1dHPzc3KycvHzsrIx8fGyMTGxMnKx8TCv7y9vbu5vMC8u7m5u7u5uLi4tbi6v7y9v8C+wb7IwL/BxcfJx8fIx8fHysvMx8zNzc7O0NHS1NbT0dPU1djY3NfW19jW1tzY29rd3dra29zb2NnY2NjW2djX2G1vbttvcAVub25ubYVuh20cbm9ubGxsbmxtbm5tbW5wcHJzdXNycXNzcXBxcIRxgHJycnN1dHZ1dnh5enZ1dXR2dXZ1d3l5eHh2dunl5OPl7Ofm5+fm6ejr5uPi4dna29rc3tna3dXm2M7R0czRysvMysrMzs/MyMzIwMXFxcPCwL28vb28ubi0r6+pra+ur6yqpqOenZmWlp6bmJeboZSQk42JhoaDgoD///338u72gPX3+fb49O/o6+Dk3ZDd2tXX0s2118nAu8DBwsC7u7a4trW0ta6spqOhpaKjoaano6OgoZ2bnJuXlJWVkZWYmJeWk5OTlJiXl5OPi4mOk42PkpCUi42LkJGTk5OVmJKOjYyIjYmSkI+NipCKioqQj4uRjZCLjo+OkI+SlJKVmZyVgJKXkJGRlZSTl5eZnZiloJ+aoKOipLOoq6enpqiqpKWqrLKvrrm+vLq7ubS1vr7CwcnOy8nIzczCxs/L0c7Nzs3MzczS2Nnc4O3v8obv8ffx94js9vj5+fqDgPz5/4KEhImFh4aEh4iKioqLjpeVlZaYm5qcmKGfnqWenZ2epKOjgKSmqqOjo6WhnqCenZybnJ2fm52fn56cnKCgo6KhqaShpKKdoJ6dnp6fnZ6en52dn52cm5qXoJeYl5eVlZOUlZaVmZiYl5eamJeZlpWVlZSVlZSTlZWWlJSVl5iWmZmUk5OVlJSXmZucmp2enJ6ZmJeXl5mYmZeXmJWamJqbmJqbIZian5+coKGhoqOopKmmqKmqrbKusrOzt7OytLS4ube3ukK3mZmbm5ydnJ+goaGipqWyqKmrraqssbO0srW2tbi6t7m8vcDDw8LGwsXIxsXGZGTGZGZnZ2lqaWppaWhnaGhqamqEa1FqbG5tbmxtbnJtb3BwcXBucHFwcnJzdHNzcnNyc3JxcnNzdXV1dHV2dnd2dnZ1dnd4dnd2dXV2dnV1dXZ2d3d2d3Z5eHh4enl6eXt7enp6e3uEeoR5HXh4eHp4eHd4eXh4eHd4eHd3dnd3eHZ2dnd3dXdzhHSCdoR1DHJxb3BxcHFwcXBwb4RwgG9vbm9vbm1sbW1tbGxra2tsbGpramtsbG5tbmxsbWxtbW1sbW1samlpaGpoaWhnZ2Znz8rLyM1qzcnLx8bEx8bKycXGxMTEwsHBwsHAvsC/vr6+vbq5u7u7ubq6u7q6u7m4t7i4trq3vLq7u7y6vbm8ur/BwcC/vbu8vLu4u8C9aby7vcDBwL29vru+v8K/wMDCwMTDz8TCwcPFyMbIzM3Nzs/OzcnOz9DR0NLT1dbZ19bY2Njb2dvX19jc2tjf2tvb3dzb2trb3N3f39/g4OLj4uFzc3Ddb3BvcHBxcHFxcXJycnNzc3Rzc4V0gHV3dXZ3d3V1dXZ1dXV2dXV0d3d2dXd2d3Z3d3Z2dXZ3dnV1dXZ3eHV1dnV3dnZ1dXZ1dHNxcuDb2dbX4tfU1dXU19bZ09DNzMbIyMfIx8HCw7/Iw72+vbe6tLOzsK+xsrGwq62qo6eoq6elo6GgoaKin5+dl5aRk5SSk5GRjo2IgIeDgH+GgoF/kJh9fIB6eXd2dHNw39/d3NjT2dbV1dDS08/L0MnMx4jCv7y+u7ikybSsqKupqKWjo5+hoJ2cnZiZlJWTlpCPjJGQjY6Ki4mIioqKiYqJhYWHiYOCgH9+f4KBhISCfn+Af3t8fH2Ce3x5fHt6eHl6f3t7ent4fHiAgH57end6dnRzeXl2e3l6d3l6eHl4ent4e31/fHuBgICAg399fXt8f3uGgH9/hoaCgYyEiIWFg4aHhIaMjpCPjJSXlpeZl5SUmZeYl5+joZ+epKado6mnq6alp6alp6assbCxs7u6uW62ub+9xWy9xcPGxsZoZ8nGy2doaG1pamppV2prbG1rbG52c3JzdHd2eHV9fHyHfn18en17e3t/gn6Af4KCf4KBgH9/gYKDf4GChIKAfoB/gH9+koJ/gH98fnx8fH19fH5+fnx8fnx9fX17g3t8fHt5eoR5YXh6eHd3dXh4d3l4eHl5eHh5d3h6eXp4d3d4eHd6fHh4eHl3dnZ2d3d2eXx8fXl4eXl4enl7e3t9e39+f4B+gH99foCCf4OEg4WFiIaJiImJioyPi4+PjpKOjpCRlZeVlpdBj3d5fHx9fXx+f39/gIKCjIOEhoaDhYmKjYyPkJGUl5SVlpeZnJ2eop6hpKGio1NTplNTVFRVVVRVVVZWVVZXWFiGWUZbXFxeXF5eZlxeX11eXVxdXl5gX2BiYGFiY2RlZWRlZmVnZmZlZ2hnaGhnaGdoaGppamlqa2xsbWxsbm1ubm1ubG9ubW5vhG4Eb25tboVvBHBvb2+EcBlycHBvb29ubm5tbm5tbWxsbG1sbG1tbWtyhGkVZ2loaWhpa2dmZWZoZmdmZ2ZmZGRjhWQPZWZlZGNjYmJgYF5eXl9fhF4wX15gX2BeXV1cXFtcWltbWllYWFdZV1hYV1hZWrWysq+xW6+rrKipp6qprKmjpKOkhKY6qKimo6enpaWmpqWlpqiopqelpqanqKinp6eop6ypsK+urq+vsK6ysLe7u7m4t7W1tLOvsLWysbK1uYS9Vr+9v8DEwcDExsXKydTKysrOz9HNzc/Oz9HT1NfU29/g4eDf3+Hi5uXk5ujp7Ozv6+3v8/Lx+PP08/Ty8fDz8/b1+vn7+/r8/f3+goGB/4GCgIKChISEhYWAhoWGh4aHiImJh4eIioeHiImIhoeIh4mJjIqKiIuLiYmKiouKi4mIh4eHiIiJiIiKiYqFhISDhYSFg4SGhYODgYH/9/bz9frx7u/u7O7u8evn5OTd3t7b2tnS09XP1NLIyMfBxL++vr29v8HBvri6tKqsrK2qp6WhoKGhoZ+fnJeAl5GSko+PjY2LjImKh4WFi4eEgp63fnt/eXd2dnV0cuPi4t3X09rY2NvY2t7b1dnPz8efwsC/wcC9pO66sK2wrauloqmoqKWfn6Kcn5qZmpuVlZGWlpGSjYuIh4eJiYaLi4iKioqFg4CBgoOHhoaGgn5/fn94eXp7gXp8fH58e3mAdnh7eHZ3dnFya3FubGxscW1ra25saW5qb2xucW9vbW9vbW9xc29rb2tpa29ub3JydHRtdW9vbXR0c3B7c3NwcG5xdHJ1eXh4dnV9goB/f317foSFh4WNkIyLh4yNgoaNiY6JiImIhoeHio+PkZSen59km52joKVenaWipKalV1VgpqGlVFVVWVZXV1ZYWFlZWFlaYV1cXV5gYGFeZWRjbmRjYmJlZGRkZmpnaGVnZ2RnZmVkZGZoaWVnZ2hpaGhtbG5san1raGxraWpoZmZlZWVnaWloaWloaGhmZGpkZGVkhWMLZGNmZWVlZGVkYWGHX1BeXmBgYWBfYGFgXmFiX19gYmFfX2BgYF9iZGNmYmFiYmFjZGZmZWVhY2FiY2FkZWNlaWtobGtoaGdqaGxrbW1sb3FucXFxdHFxcnN1dnNzdK5/A4CAf/+A2ICFfwGA/3+bfwSAgIB/zoDef5N+AX//fr9+AX+FfgF/hn4Ff39+fn7/f8V/AgIEAB7Fx8fIx8PExMfLzcrQ2NPa4tvc4d3h5Obq6Ofr7+2E8BDy9ff7/YD+gICAgYKCg4SEhIVGhIaIhIaFhYeIiYmJiIqJiomKi4qMjIuKjIqKioiJh4aIiYiKiYmJiIeFh4iIiIeJioqIiYmJioqLi4qKiYiIh4aIh4mGhYSHhoYEhIWFhIWGBIiHh4aFh26IiIiGhoiJiIaHiIiJiIqKiouMjYyMjY2KiomKiYqOjY2Mi4mJiomJiYiIiYqKi4mKi4mHiImHhYWChISDhIODhZCIhYSEgoSEhYaGhoWHhoaFhoeIioyOkI+Qj4+QjpCOjI6NjIuIh4eFhIWDgoSBgP36+/j59vX2+Pj4+/3++fn18/Hu7u/z7+/s6+bk5ufn5+bm4uLd2tnX19TU08/T0c/Pz87T0NHO0MvLysfEz8nHxsjHw8HAvLu9vr7AwL+/w767vLq7vb6/vsDBwsDEwMTGxsrGyczLzM3Pzs3PzMvM0NLT1NPT2dXX19fY2dbXH9vc397h4Nzb2tzd4eDi393i3+Dg4HFxcXDc3G5ub26EcSRycnJxb3FxcXBwcW9zcW5wcHJxcXFwb3BvcG9vbm5vcXB0dXWEdIB1dHJzdHJxdHR1dnZ3d3h4eXp7eXp7eXl2eHp5eXl6e3t7eXh4eO7s6+zu7/GA7O/q6+3r7Obp5uLl5eDi4N3c2YbqfN3k597T0NPY0dHQ0c3Ny8nCw8G9wMPCvr28v8C/vbe1ta+tq6urqamopqCenpyempuZlJedl5WRkpCOioCHioiFg4KB/fH29/b1+PX87ebm4Ovi3tnT1NPQzrXAz8nFwL2/vr+7t7aytbWwsa2on6Wmsa+op6esq7Sio56bm5idlpmWlpOXlpGUl5STnp6XnqmWjo+Sj5GRio+MkJOXoZOUlpOTjY2QjYyOjpKSk5CPkI6PkYuJi4yPkJGSj4CQkZKVmZyWu5iVl5GOkJCVnJybnJedn56dnqKmpaKloqShpq2nqq6uqbCxs7O1tLiwvL3Cu7zDw8TLzMjEw8XIzr/GzdPU0dPO2Njo+t7k3eHo6/L38/j28e/v+O/3/oCDgIT9hISFhIaJiIeKiIWJjI2OkJKWkZeZlpmbm6ChoYChpqSjnqOipamqra+spqq9pqOkraKfn6CipKWnoqamqqOkp6eopqSkpqSlopydnZ6fn6mkoqGfnZ+dnJ2cmZmZmJmamZiYlpWXl5mcn5ycnp2Zm5uZlpuem52bm52UlZSSlJean5uanZqbmJqYnZ2enp+goauinJ2YnZmbnJyfnCuamZuhoKCgoaSfnp+hnJ2eoqOko6enqqmprK6wtrO1s7i8uLa5ur3AwsDALJudnp+gnZ6go6Wmo6aqpqyyrq+yr7O1t7q3tbm7ub2+vsDAw8XGxmTHZGRkhGUPZmdoaWlqamxva2tqamprhGwkbm1ubW5ubnBvb3BxcXJycXJxcXJzc3R1dHR1dHN1dnV0dHV1hnYFeHl5eHmEeBR3eXl6eHh5eXl4eHl5eXp6eXp6eoR7Gnx9fHx7fHx8e3x8fX18fHx9fXp7e3p7ent7hHoUeXp6e3h4d3h3d3p4eHh5d3d4d3eEdgV3dXRzc4RyD3Rzc3NycnNycXBwcYJzcIRvHm5vbm1ta2xrbGxtbG1ub29vbm9ub3BvcG9ub29ubIVrU2xraWdoaGnQzs/NzsvJy8nKycnLz8fIx8bGxMPDxsPCwcLAwcPDxMHAwLy+vLu8vL26u7q4vLu7uru6vby+vcC9vr28u8fAwMDCwsDAwL++wMHBhMAGxcO/wsDBhMJgw8TFw8fFx8rLy8jKzMvNz9HT0tPQzs/S0tPU09Xb2dzc3d3e3Nze3+De39/d3N3e3t/f4eDh5uPj4+FycnJx4eJzc3Nyc3JxcHFxc3JxdHR0c3N1dHh3dXZ2eHZ2d3d2hHcYeHd3d3h3eXl4d3d4d3l5eHp7eHh5eXl4iHkJd3d4dnd1d3l4hXeAdnRzcnHg3tvb3NvddNnc2tzd29zS08/MzczIycfDwcF50nPEys/Eura3u7S2s7Kvrq2tp6iopKeppqGhnaChn5+cm5uWlZOTk5CQkY+Ih4WDhYKCg36Ah398eXp5eHVzd3VxcG9w39jd4N7b2NPczsjOydDLxsHAv7+8u6avuLKAr6ynqqanpaaloaCemJqbl5KWlZuYkZCOk5Gbi4uKiouLkIqMiYiFioeDhYR/f4uIhI+ahICCg4CBgHt9enx9g458fH16e3Z5e3p6fH1+fn16eHl4e3x4eHl6fHx8e3l4eHd5e3x5pn19gH18fn+AhIF+fnl+f35+fYOGhoWLhoiAg4WLhYmMjIeNjpCQkI+TjJeYnJSVmZaXnJ+fnp+jprKdpKirrKipp6+vwMuzvLK0uLm6u7q+v73CwsrEyMllaGVoyGdoamhpbGtrbm1qbG9vcHJzdXF3d3V3d3d7fHt8gH9/fIB/gIKCg4aGgoedg4GDiYKBgYGCg4OHhIeGi4UmhYiHh4WDg4OCg4B9fn9/gICIg4GBgH+Bf3+AgH19fHt8fn18fXyEewJ8fYR5Wnh6enp4e317e3p6fXh4enh5enyAe3l9e3x5end6eHd3d3h5g316fHh9ent7eX58fHx+goCBgIKGgYGCg4GCg4WGh4eKio2Mi4yOjZKPkJCSlpSTlZaXmpuZlzl6enp7fHl7fH+BgX2AhICFi4aJioiLjI2Rj42SlZOXmZeXmJmanJ1PnU9PT1BRUVNUVFRVVFVUVliEVSRXWFpaWllbWlpaXFxcX15eXV9eX19eX15eX2BfYWFgYGFhYWOEZQRmZ2hnhGgEaWpqaoVrCGptbG1sbG1uhG+EcExvbm5ub29vbm5wb3Bvb3BxcHBxcnJxcXJzc3FycnFwb3BwcHFxcXBwcXFubm1ubWxwbm1sbGpqbGxra2pqaWpqamlqamppaGlnZmZkhmYUaIBqZ2ZmZGRkY2JhYF9hYGBfYF+FYIBeXl1dXlxdW1pcXFtaWVhYV1hZWVhYWlpctbOzsLCurK6urKusrbOnqKioqqqpp6qop6WnpKOmpqeoqqyqrKqpqKmppqipp6uqqaqsq6+vsK6xrq+xsrPBvLy7vLy4trazsbK0s7W4urrDv7y+vb7AwMLCw8bHx8zKzM/Q0c7R04DQ0dLU1tfc29zd4uPk5eTj6OXl5+jq7Ozt7u/x7/Hy8fP0+Pj5+vv6+Pz5+vv7gIGBgP7+gYGCgoKEg4OEhYaFhIaHhoaGiIeMioiIiIuJiYuKiYuKi4qKiYmJiomMjIyLi4yMjYyLjY6Li42MjIuLi4qKi4yNjoyMjYqKhoeJiICHhoeHh4aEgoGA/fr39/j4+on09/P09vP06+3p5eXi3NzY1NPRfuiA0drczsS+wcS+v76+u7q3tK2tq6mrrqyopqOlpaKhnZqalZKPjo2MjY+PioqLiImFhoWAg4qDgXx+e3t3dXl4dnV1dOTa3t7b2dza59zW2tPZ0czFwsS/vIC9oa67t7Wxrq+rrKmmpaKhoZydnpuWm5WampOUkpeWno6PjoqMio2JjIqKhYmGgIKEgYGMioSQlIF4enx4eXp1eHd6fH2AdnV2c3RxcXRxb25ub29ycHFzcW9xaWdpam1vb29tbGxsb3N1c6Z0cnJsaWlrcHd3dXVvcHFubm9zdg12dXx1dm9wc3B1enp1hXqAd3p0fX6CfX2BgYKFiYiFhoiLlYKJjpCQjYuJk5CdqI2SjI6Xmp6hn6Gfnp+dpJygoVFTUFOfU1RWVVdZWVhaWVZZWltbXF1eWV1dW15fYGNkY2NlZGNgZGNlZ2dpamplZ3pmZWdsZ2ZnZ2doaGtna2tva2xvbm9tbGxtbGxqZWZAZmZnZ21pZ2hnZ2ppamtpZWRjYmNlZWRkZGNkZGVmZ2RjY2NgYmBfXmJjY2NhYWVfYGFgYWJjZmFgYmJiYWNgZIRiOWNka2VjZGBlYmNkZWpoZmVlZ2RlZWdraGdoamZmZmdmZ2drbG9ubW5vb3JvcXB0dnZ1d3d4eXl4dqd/AoB//4DigP9/ln+EgIJ/1oCHfwGAk38DgH+Aw3//ftl+hH8Bfv9/yH8CAgQAK8jHyc/LycnKxtHS1Nba29vb3OPf5+Xl5Ozv8/Hy8vf5+/r9/ICBgIKBg4OFhAKFhoaHFomIioiHioqMjI2Mjo6Pjo2Nj5CPj4yEjYCOjIuLjIuLjIqKi4uKi4yMjo6NjI2OjI6MjIuMjIuLiouLiYqKi4uMioiLjIyLiouKi4uJiYiKh4eIiYqKi4qJioqLiouLi4qHiIiJiomKi4qLjIyMjY+Rj5CQj46QjY2Njo2Mjo+Pjo2MjYyMi4qLi4yNjY2Mjo6NjoyLi4mJiAuIiIeHhoaFiIqIiYWIgIqKiomIiYqJiIiMi4+PkZSWlpWUlJOSkZGPkIyKiomLiYiGhoWGhIODgID9/v7//4L+/f+AgP/8/Pr59fX29fLw7urp6uvs6+3s6Onk4Nzd3NrT19fS1NPS0dPT0tHX1NPQz8jJx8XKxsfJycTAvb2+wL7BxcLDxcLBwcG+wcLDdsXDxsbGxcnHy8nLz9HPzdDRz9PQ0NTS1NTW2tnb3Nvb29rZ2Nna2dvd297g4eHi4uLf3N7g4ePi5OTlc3Rzc3Jzct5wcHBucXJydHVzc3NycXJzc3N0c3NycnBycnJ0dHN0dHJycnFxcXJzc3h3dXV4dnZ2dHWEdoB3dnh4eXl6e3x/e318fH16e3d6fH59fn5/f398e3x6eXft8PHv7/H08vDz7u3r7uvp5+jk4uLg4N/e3uPg4+Ti3tvY2dfU1NTa0s3LxsDDwcPBxMzCwMPEwb68uLe1s7C0sa+vsa+ppp6bnqKcoJ6Zl5SUk4+Pj46LioaPg4WCgYD89ffw9vn7/Pj38uvt5+rl3t3Y183NsLzMzsbJxr3ExLq3tbe4sLCvq6ijpKisrK+qrKOqpaWioKKdm52enZmVl5yWmZOXmpeSmZKNj46KiouOlJiWlpWYmZmVlJSbmpOUlJOKjY2LjY+NjouIiIWLioiSi4qNjIyMkpGSlJaYloCYoJGXmYuTkJKVnKKhnZyfqKenoZ+gmJyiqKKknaKjqa2vtbC3s7K2tLK5t7i+wsjFytDHwsTBxcbJxMjK5M/b2tza2tvk6N7l5OXo6e7w6e7s8O3zgYKAgYKEhIiGhoODhYmMiIqRi4qPjJKWk5mdnJiWl5ugo6WkoqSkpqalpYCqq6uvzrKwqq2mp6Snp6mrqKmpp6upqa2sqqmzsayurKmppqWfnp+goqWlpqSkpKCioqOiop2cnp6anJucmpuZnZ6bnZycoaijoJ6gnJyYnKGcm5qblpeXk5afnJ+hp5+gnZmbnJ2enp+hoKCfraignZ2fmaGfnJugn5yhn6KlpCSlo6ShoaCio6irq6mlpaapqqusrrG0uLy9wMC+vr/AwcPGxMgzoJ+gpqKhoaOgqKipqqysra6wtrK7t7e1ury9vLu8v8LDxMbFZGRjZWRlZmZmZ2dnaGlrhWwNbm1ubGxtbW5vb25wcIVxCXJxcnFxcnRzdYV0OnV1dnZ2d3V1dnZ2d3Z3d3l4enl5eHp6e3x7fX18e3t8e3x6eHt8fHt7fHt9fHt9e318fH1+fn9/fn2FfCJ+f4B+gH9/f35+fn1+f359fn+Afn5+fXx9e3t7fHt6e3t8hHsSenp6eXl5eHl3dnR1dHR1dHR1hXQdc3Rzc3FydHFycXFxcHBxcHBvbm9wb29ub29wcHGEcoBxcXFwcHFwcG1sbGtubGxra2pramlqaGnQ0dDPz2nMystmZcvJy8rLyMbGxsTExMPDxMXGxcTEw8TBwL/CwsG9wcO/wcC/vb+/vr7CwsPBwry/v77Cv8HCxMLBv8DBwb/Bw8DAwsHCw8TDxsbIyMbJyMjHysrMy87O0NDO0dPS1VDU1dfW2Nfa2trb29rd3d7g4ePk4+Tl4+Tl5OTj5OXj4eLi4+Pl5+fnc3Rzc3J0dOV1dnZ0dHRzdHV0c3R0dHV1dXZ3d3h4eHd3eHd4eHd4eYR4gHl5enp6fnx5eXx6e3x7fHx9fHx7enx6ent7fHx/enx7e3p4enZ5ent6enl6enp3dnZzcnHf4uPf3+Dh393f29rY2NTQz8/Ny8vIxsXFwsnHyMjHwL69vby5uLi6trGyrqmuq6yqqbOmpKWmpaOin56cm5aZlJKSk5OQjYiGiIyGgImHg4F/f398fH17d3Z0fHFycnLh3t3Y2tza19TSzsvLx8rEwMPAwb6+o6uztK+xsKuvraaioaGhm5udnpqWlZWWk5eSlIyQi4yKiIuHh4iJjIuHiY+GiIOFh4aDioWDh4aEg4SDhIR/fHp8fYB8enl+fHh6fX56fX18fX17fHp4gHt6fn17gXx8fnp7enx6enp9f32Bin+Fh3yDgH9+gYODf36CiIaFgoOFgIOIjoeLhoqNjo2MkIySkI+RkZGXlpibm5yWnaOgoKGgpqepo6WlxqSsq6yrqqy2ubK4tra5ub2/u8LAxMPHaWpnaGdoaGpoaGloa25vbG10b29yb3N2gHJ3ent3dnd5fX5/fn5/foCBgICDhIOEmomJhYqFh4WHhoWFg4SGhYmJiI2MiIaOjIiJiYiJh4aAf4GAg4WFh4WEhIGEhIWEhYGAgoF+gICBf39+gIB9fXx6fYR+fHx/fH57fYF9fHx9fH19e3x/fX58gnt9fHl7fH18fHp7enp6N4iGgH59f3l+fX5/g4F+goCChIKEhIWFh4aHhYiJiYiIio2Oj42Njo+Qk5WVlpWVlpeZmpyenaANend5fnt6e3x6goGBgoSEGIaMiJGOjIqQkZOTlJSXmpqYmZdNTk5PT4RQGFFSUlNUVVZWVVVUVVVXVlZYWVpbW1pbW4RcD11fX19eX19gYGFgYGBhYYRiEWNjYmNlZmdoaGhpamlramtqhWuFbQpubm9vbnBzc3FyhHMrcnNxcnFwcXJycnNzcnFycnFyc3N0cnRzdHV0dHVzdHRzcnJzdXJzc3Nyc4RwDm9ub29vbm1sbWxsbGtshGsKbGtubWxua2praYVoKGloaGhpamhoZ2ZlZGNkY2JhYGFiYV9fYWBiYWFiYWBfXV1dW1tdXF2EW1BcW1taW1tdXFxcWlqysrGxsVuxr61WVqqprKutrKusq6qrqqmoqKmqq6yurbGtrKusraumqq6qrq2sq6+vr7G1s7SytLK3uLq+u7u9vbq2tYS2Wre5t7m6u7y9vby/wcLFxMfIycnMys7M0NLV1NDS1dPW1tnd3uLj6Orp6+ro6Ojm6Ors8O/z9PH09PX29vr8/Pv7/Pr6+/3+/4CAgIGAg4L/goKDgoWGhoeIiISHBIiJiYqEiwyKiImKioyNjI6Pjo+GjgyNkpCNjpGOjo6MjY2FjgGQhY4Nj5OOkY+OjYmKhomKi4aKgIiHh4SCgPz9/fr6/P37+Pr19PLy7+rm5N/c3NnZ2tjU2tPS0tDKx8XEwb/AwMW/urizrbCusa6vuKunqammoqGenJqZlZiTkZCTkpCPiYiLjomMiYWDgYGAfHx9fHl5dn50dXV15+Hh2Nrc3uDg4dzY2dHU0cvLxsK5up2nuLixgLKvqK6uqKemp6WbmZmal5OUlZiXnJiZkJWOjoyLjIqJiouNi4WIjYSGgoSHhYCFgHt9fXp4enp+f3t7eXp6enZzc3l4cnV1dW9wbm1ub3FzcnFxam9rZ25paGxqa2pubW1tcHFwcnhvdnZnbGprbHJ2dHBrcHd0d3Fxcm1wdHdtcW5qb3J6enp8dnp1dHh4d356en6AgoGGioWDg4KIiYyFh4eehpOSk5KOjpOTi5KSlJiYm52XnJqem6BVVFNTUlNSVFNUVVRXWltXWF9ZWVxZX2FdYWNiX11fYWRmZmVkZWVmZWRjZ2hnaYFsbGhsaGpphGsVampram5tbHBwb251dHFyc3FycXBrhGp7a2lpaGhqaGxsbm5va2hpaGRlZWZlZmZpa2doZmRobGdlY2RgYl9gZGFgX2FgYmJhYmViY2JmYWNiXmBhYGFhYGJhYGFqaWRkZGZiZmdnaGxqZmlmZmhnaGdoZmhoaWhsbW1samtsbm5tbW1vb3Fzc3V1dHV1dnd4eXh5pH//gOqAhX8GgH9/f4CA/3+Gf4eAAX/YgN9//37Wfv9/0H8CAgQAMdHP0dTP09PU19fW2dvf4eXi5ezr6ubr7O/19/b2+vj4+/6AgoKDhYWHh4eGhoaHiIiFihuJiYqMjIuMi42Njo+OkI+QkZKTkpGRkY6Nj4+FkCWRkY+Pjo6Oj46Pj5GSkpGRkJCTkJCPjo2Qjo2Mj46NjY2Oj4+OhY0FjIyOjY6FjBOLjIyLjI6Njo6Pj5CPkI+Mi4qKhIsJjY6OkI+PkZOUhJMakZORkpOUk5STk5ORkJCPj4+Ojo6Njo6PkY6Ejw2Ojo+Pj42Mi4mKioqLhIqAjIuLioqKjIuMjo2OjY+OjZCTlZWXmJiYl5aWlZSUk5KPjZCMi46Mi4iHhoWEg4OFhYSDgYKCgoODg4SCgP///fr7+Pv39vSA8PH08+/s7Ozp5ubk5eHe3NjW1M/W19XV1NfT1dvV0dHMycbHyMnHx8fFwsXBwsXCxMfFzcTBwb9kv77CyMTHyMfKy8vOz9DP2M3NzdLR09PX1tXS1NbU2drd3uDe4d/b3d7h4t/g4uTk4+Pk5ubk4nPl5eXm5up2dHV1dXZ1dXZ1c3NzcXNzdHR4dXR0dHN0eXZ0dXV1dHV1dHR0c4d1CHZ0dHV1dnZ3hniAend3dXh3eHl6ent8fH1+fH19fX9+fXx5fX9/f4CBgYKBf39+fXt7e358e/Px8vDu8fDw7evq6+zu6+jn5uLk4enp6fHr5uTh29jW2NbW0tHPzszUyMbFxcnGyMPDx8K/v7u6uLaysLCurq2sq6+ioaesoaGfmpuZpZWTko6OiomAiIqGhYGE//j48ffz9/v4+PDt7+3u5eDf3Ofc0rG/zsnExbzCwcDAv7a2s66xramlqKimqK2sraupp6yrqKWjn5+cnJualZWVkpKWkpiRwJiXlJGTl4yJjY+VkJWUj5SVmZWTlpiSj5OMi42TlI+Nj5GNjIuNk5GNjY6QkJGUjJGAlJKXmpyYnJyXk5KOkpKVlambmKOioqWmpqGnp6qfpaekm5eeoqqstLq3tba0t7Kyu9O9wL7EwsLFxsDCxMHJyc/M0tnY3OLj4OTk6PH27fjx8Pj1+Pz594GB+YH+g4aEhImKhoqIi42LjJGMkJSSk5OXmZyaoqChoJy9oaSpqaqArKytrqytsK6xr7W0sbKusK2rrq6yr7Gura+urq2vtbC0s7OxrqmqqaSjvq2loqWmp6mlp6ijpqSnpaakoaGeoJ2bnp2anZ6cm5+dnp64paGfpZ6gn6CfoZ6dnpyfnaCeoaCgnqKfn52dnJydoJ+bnqCenaSinaGsnJ2coKKhoKIqn6SkpaSloKKjo6GopKmura6tra2vra+0tLK0t7u8wMDCwsTCw8bEycjLOqelpqikpqeprKyrrK2wr7Gvsri4uLW5ubu+v76+wcDBxMdkZWVlZmZnZ2hnaGlpamtsbW1ubW1tbnCEbwdxcHJxcHJxhXMHdHR1dHN0dYR2H3d4eXh4eHl3eHd3d3h4eXh4eXl9fHx7e3x+fn59fn6EfQV+fn19fYR+H31/fn59fn5+f3+AgH+AgH9/fn9+gH+BgYGCgoOEgoKFgBR/gICCgoGBgH9/gH5/gH9+f359foZ9Cnx8fHt7eXl5d3iJdwN2dXSEdYR0EnVzdHJycnNxcXNycnFycG9xc4V0EXV0c3N0c3R0c3FvcW5tcG5uhGxCa2pqa2tqaWhpaGdnZmZnaGfO0M3MzcvPzMvKasfIy8vIxsfJx8TFxsfGxsfFxMK/xMTDw8LEwsPKx8PFw8HBwsPEhMNzv8TBwsXDxMbEzMXExsbGx8nNycrKyMnKyszNz9Dc0dDR1dTW1tna2tbY2tjd297g3d7i4t/k5efo5ejq6+zq6enp6unndunp6urq7XZ1dXR0dnR1dnV2dnd2d3Z2d3l3dnd2dnZ7eXh6ent6e3t6enl4e4R6FXt6fHt6e3t8e3x9fHx9fHx/fX99f4R+hX2Af3x8fX1+fHt7eHt9fHx9fnx8e3l5eHZzc3N1dHPi4uHg3t/e3drX1NTS0s/NzczIycfMysrSysbDwb68u727ubW1srGwuK2sq6utq6ynpqmloaOfoJ+em5iXlJOSkZCViouOkoeGhIGBgIqAf398e3l4d3h1dXJ25eHh2+Db29uA2NTOzMvMzMXAwL/Lwrqfq7WzsbSusq6rp6mio6Cbn56dmZqXk5KVk5WUlZGTko6Mi4qMiouLi4iKjIaHioWKg7aKiIiIi5GIhYaDhX+Af3p8fH97eXx9enp+eX1+goJ8eHl8eHp6foJ/enp8fX9/fnl7fHt9f357fYGBgYOBhIKAgoGWgX6FgYGDhIaDhoaJgImKjIqIjY+Ri4+Rj46Qj5ORkpq3mpmYnJianqCeoaOip6arp6qvrK+ztLGysrS7wrrFv77EwMPGxMNmZ8lpzWlsaWhsa2hrbG5vbW1xbXB0c3RydXV2dHt7fHx5lH1/goKCg4KDhISEhYaJh4yKiYttiIuKiYuKjYmKiYmMi4yLjJGKjIuMi4uJi4yJhpWKh4SGiIeKhoeIhIeGiIeIhoSFg4SBgIKCf4KCgYCCgH9+lIOAf4R/gX+Afn99fX9/gn+BfoF/f3yAfn58fXt9foGAfX9/fXyCgX6Cjn59fYSAK4KBhIODg4WChYaHhIuGiYyLi4uMjY6MjpOSkZKTlpaYl5iZm5mbn56hoKJFf31+gH6AgoKEhIKEhIeHiYaKkI6PjI+PkZSUlJWXlpaYmU1OTU1PT1FRUlFRUVJTU1VVVVdWVVZXWFhXWFhaWltbWlxbhF0OXl5fYF9eX19gYGFhY2SFZRZkZWVmZmhpamlqampubG1sa2ttbGxrhG0Hbm9xcXJycoVzBXV1dXR1hHQKdXVzdHZ2d3Z3doR3M3V0dHR1dHR0dXZ1dnR1dXZ2dXV0dXR1c3N0c3JycXFxb29ubW5tbm5ubW1sbW1sbmxubYZrBGppaWmGahlpamhnZWRjZGJiY2NjYmNhYGFjY2JiYWBghV5hX19fXl1fXFtdXF1bW1tcXFpaXFxbWVhZWFhYV1ZWVVSprKyqrKuura6uX6ytrq6trK6xrqytrKyrqamqq6uqsLGxsrG1srO5tbK2tre3ur2+u7y7u7m9uru+vb7Av8e+voTAgMPIxcfKyczNzc/R0dHX0NDR1dXX19rb3Nvf5ePp6O3v7ezu7Ojt7/H18/f6/P35+fn6/P37gP7+/v/9/4GAgYGBgoGChIODhIWGiIeIiYyJiIiJiYmQjYqMjIyLjIuKi4uKjo6Oj4+Qj5KRkJCPj46QkI+QkZCQko+Qj5KRkpOTAZKEkYCVkZCRkJCOjYuIjY+Pjo6Pjo2Ni4yKh4SEg4WCgPz6+vf09vb18/Dt7evp4+Dg3tvc2N3a2OLZ0s7MxsPCwsLBvb68ube8r66trK2rrainq6Who6CgnpyZlpWSkpGSk5mOj5WZjIyKhoeFj4OAgHx7d3h1d3V1cnfm4eLX3NbY3IDb29fW2Nja087NytLFu5iksbCsr6uwrq6ws6urpJycmZWTmJeVl5uZmpaTj5KQjYyKiYuIiYiIhIWGgICFgIeAsYOCgICDiXx6enl9d3l5dHV1d3NzdXh0c3Vtbm1wb2tpbXFucG1sb21qa25wcHB0aWxtbG9xcW5ydHJvbmtqaoBran9sanFsbW9xdnJ0c3VqcXJva2lvdHl4fX98eXd1eXZ4gJyBgoGFg4OGhoKDhICFhYqGipCMj5OTj4+PkZielp+Zl5yXmZuYmFBQnFOgVFdTUlVVUlRUVldWVllWWVxbXVxfYGFfZGJkY2FrZGVoZmZnZmdoZ2dqaWxrcHBubm5rbWxsbm5yb3Bub3Fvbm5wc29wcHBxcXBxcm9tdW1samtsbG1qa2xobGxubnBua2pnaGVjZWVjZ2hnZWhmZWR1aGVjZ2JlYmNiYmFgYmJlYWVhY2FhX2JhYmFhYGFhY2JfYmJhYGZlY2d4ZGRjZoRqKmdqaWhnaWZpamtpbmlrbmxtbG5wcW9wdHJwcHBxcHJxcnN1dXV4dnl4eqJ//4D5gIp/AYDvfwGAhn/mgNt//37TfgV/f35/fv9/zn8CAgQAKs/Q0tPX19jZ3d7c3ODf5+jq6enr7e/w7/X8+/yAgIGCgYGEhIWEhYaHiISJY4yJi4yOjI6Pjo6Pj5GTkpKTk5KSkZOUlJWUlJWTk5KTkpKTkZKUlJOTkpKSkZCRkpKSk5SUlpaVk5OSk5GRkZCRj4+PkZGQj5CPk5KRkZCQj5CQkZCQj4+Rjo+NjY+Oj56Rk4SRVpmRkpKPjo+PkJCOjpCQk5OVlpaWl5aWlZaTlpWWmJiYmZeXlpWTlJORkJGQkJGSj5KRkY+QkpOSkpKRkZGOjYqLjIyMi42ajo+Li4uMi4yNjI2NkZCShJOAlpeZmpmcm5mamZmYlpaWlJKSk5KPjYyLi4qHhoaGhYaFhYSEhIWFhYSEhIKCgYCAgP7++/v49ff7+/f08/Hv6+rq5+nk6OHg4d7l4t7e29jW1s7Sz9DMy8fJysrIx8fFxsbHxsPGycjIxcXCw8DEwMTEyMbIys3Mz9DQ09HSzNFM0NHU1tba3d7g3t3d3t/h3uHm5uDj4uXj6Ofn5ufodenodOjo53R1dnV1d3Z1d3d4eHZ3dnd0dXl3dXV2dHR3dnh3eHd5end5eXh6eYR3KHl4eXp5eHh4eXl6eXp9enp7en2ke3t7eXl5enl7e3x6fH6Af39/gH+EgIB/gXmCgoOEhYSFhYWCgX+Afnx9fXx8e3z29fT3+Pbz8/Dw7+7o6Obn5+bo5+rq7uvr6+Xg29nc19XU19XT0tPOzMvKzMrLyMXGxcTDwL27t7e0tbW1trCvqKWnoaCgnp2alpWYlJKQkJGOi4yRiIaBgv/8/Pz9+vv38vLx8O7y8YDq5N3e3+HXt8jQ0c3K0MG9vL66uLm4tbe0rrKtr66rr66ppqmmpKajn6KdnZybnJ2am5yjmbiVlo6TkIyQk5KKi4qJipCQkI2RlJWVlZKRlJOQkY2MiIuQiYyPjo+RkJWTlZKRlJWXkJWRkZWXmZKYmZqalpSUk5aWmJydmJibx3+joqGspaSkpKurpqOfpaSnqa6wtra8vLm3vLS7urXAvsfJxcTPzcnPxc/T1dba2OLf2eLm5uPv8fX08oP99vb7/vz+gYODgP6JhYPviouLj4+SkpaZmpmTl5iYm5uam56jpqWpqqWjpqerr62wsLOxsrKvtrm8u8m9t7W1srS2hLJNs7m8uLu6v728v7q2srKyrKusqqmqq66urqmqqamnqaiuqa6qpqSmo6Kiop+foJ+eoaKjoZ+ioaKgq6OioaKfnqCenp6coKChpaCkpqKEoUGioaOhoqGin6GfpKGgpKKioaGkpKelpaelpqWlqKWqpaSkq6mwr7K1tLOxuLi1t7W2vLzBwcLFxcjDxcbNzdDR1CqipaeprKusra+wrq6ysLS1tbS0tbi7vbzAxMLCYmJiY2NkZWVnZ2doamqEaxFta21tb21ub29wcXBxc3Jyc4V0GXV1dnZ2d3Z2dnd2dnh2d3h5eHh5enl5eXqGeUR6ent6e3t9fX5+foB/f4CAgH9+f36BgYGAgIGCgYGBgIB/f4KAgoGDhIOEj4OEgoCBgYeChYWFhIWGh4aFhISDhIKDg4SEFYKCgYCDgoODgoKDgoKBgH+BgH9/gIR+CHt9e3t5eXl6hHkaenp4eHZ2d3Z2dXaCd3h0dHR1dHZ2dHNydHOFdAh1dnZ2dXd2dYR2W3R1dHNxcXJxb25tbm5ubWxsa2praWlpamppaGhnZ2hoaWloZ2fPz83NysjJzMzKycvLysfHycfLyc3JyMrHzcnGxsTDxMfEycjIxsTCxMbGx8bFxMPExsbExsmFyF/Kyc7LzcvNy8vMzs7Q0dDU1NbT19bW2NvZ3eHh4N/e3eDg5N/h6erk6Ons6u3r6+vs73jv7nfv7+53d3h3d3p5d3h3d3h2eHh5dnd7eXh4eXh4enp7eXx6e317e3x7fYV8hH0re3x7e3x8fH19gX9+fn2AsX9/gICAgYKBgoGBf3+BgoGAf4B/gICAf35/dYR/AYCEfoB8fHl5d3V2dnV0dHTm5OPk4+Ph3dvZ19bT09LRzszMyMrIycbHxsTCwL/CvLm3ubW0s7SysbCvsa+wraqqqKajoZ+fnZ+bmpmXmJOSj4yPiomIh4aEgIGDgYB9fXx5d3h9dXRxdOXk4+Lj39za1tbW08/Rz8nFwMHCxLucq7K2tIC0wLGtqaqnpKOfnZydm52YmZWRlZeVkpWTkJGQjY2NjIqKjo6MjY2RibOJiYKHgoCEhoaEhoWFg4WCf3p8fX9/fXt6fHx7fXt9en6Cenp8eXp8en58fXt5e3x9eX57e36BgXp9fX1+fX2AgYKAgIKEfoCAn4SCg4yHhIKBiYmIinOJkI2Njo6NkJGTko+PlI+Xl5Obl56gnpyko6Cln6apqqqrs7SwqrO2tbK6u7/Av2jLxMHFx8XHZmhpZsxuamjcbWxsb25vcHJ0dHVxdHZ1eHh3eHp9gH+Cg4B/goOGiIWGhYeGhoeGi42PjZeQjIyNjI+ShY+AkZGOkI+SkZCTkY6Nj5CMjI2Mi4uJjYyLiYqJiYiJiY2KjYmIhoiFhYWGhISEg4KEhoaEgYOBgoCGg4GBgn9/gYCAgYCBgICCfoGCgIB/f35+foF/gICAf4KAhIKAg4CAgH+BgoWFhYeEhISFh4SIhoeGjImNjIyOjI2Nk5OSlJIRkpeWmZmZm5yfnJ2doaChoKRFe31+gIKCg4OFhoOChoSIiYuKjI2PkZGPk5aTlExMTE5OTk9PUE5PUFFSU1JSU1VTVVZXV1lZWFlaWVpbWlpbW1paWltdhF43YF9gYGJiYmNiY2RlZGZmZ2hoZ2dnZmZnaGhqamtqamtsbW5tbm9ub3BxcnFxcnF0dHRzdHR1dYZ3BHp4eHeEeBGEenx5eHl5fHl6eXd2dnd3d4R2Rnh2d3h4eHl4eHd3dXd2d3Z0dHRzcnJycHFwbm5vbm9vb2xubG5tbW5vbm5tbGxsamppaWtra2prcGxsZ2dmZmVmZWNjYmSEYwliYmNjY2JgYF+EXh1fX2BgYF5eX19dXFxdXV5dXFtbWlpZWVlaWllYV4RWgFdYV1dYr7CwsK+srrCwr6+wsbGurq6qrqywr7Gzsre0s7S1tLa5tLi4u7i6uLu+v769vbq8vb6/vsDDw8LCw8PExMjEx8fJx8rLzs7P0M/S0dTP1dTT1dnY297f4uPm5u3u8+/w9vbt7u/z8vj4+/z9/4D+/oD///+AgYGAgIODU4KEhIWGhIWFh4WHiomIiYqJiYyLi4qMi42PjY+QkJKQj46OjZCQkZGQkI+PkZKTlJWZlZSVlJfVl5WUkpKSk5OVlZeVlZeXlZSTlJOUlJOSkZGFhJEdkpCRkZGPjoqKiIaGh4SEgoH9+/n7+/r49fPw7eqE5IDi4N/a29bW1NHRzcnHxsnEwcDCvru6ubWysK6vrK6sqamnpaOhn56cnZmZmZiZlZWQjY+Li4uKi4qGhYeCf3x7e3l4eYF4d3R25+Xl4uPc29vY2tnZ2N3f2dTOzcnIvZiqs7WxsLeqqamsqqqppJ+dm5aamJuYlJaVko6Qj46QkICMjo2NiYiJiomJipGHuYaGf4N9ent+fXp8e3t6fXp4dHV2dnVzcnJzdHNzcG9rbXBqbHBvcXJvcW9wb3B0dXRtcGtrcHN0bW9vbm5sa2trbGxtcXJra2p8bWlqdnNxbWpvcG5ua3FucHJ3eHt7e3h1dXt4gH97g3+HiISCioqGiVeBhoeHhIiQjo6Ij5GRjpeYmpyaWqKblZiYlZdNTlBNnFZTUdRUU1RWVVZWWFlZWldaXV5gYF9fYGJjY2ZoZWRmZmdoZ2hnaWhoaWZrbnBwdnFubnFvcnSFcUt1dXJzcnVzc3Z0c3Fzc3BwcXBvb29wcHFubm5ta21tcW5xbWxpamhnZmhmZmhoZ2lqamdmaGZoZmpoZ2VnZGNlYmBiYWJkZGZiZWaEYzNkZGRmZGRjZGJkYmViYWNiZGVlaGhrampraWhmZmlma2hpaG5qbWtsb29wb3Z1cXNxcHSEcwt0dXh1dnd7e3x8fpx//4D/gIaA7X8HgH9/gH9/f++A2X//fst+AX+HfoR/AX7/f85/AgIEADLa2tjb2tre4N7c4eLj4ujt6/Hy8/T2+Pj5/ICBgoGDg4SDhYeHiIqLi4yOi42MjY2PkYSSF5OUlJSTlJOUlZWYl5iZm5iamJiWl5eYhJdJmJmXlpWUlJWTlJSWlpaVlZeXnJeVlJOTlJSSkpOUk5STlJSSkpOTk5WTk5KSlJSTlJWUk5OTkpGRkJCQlJSVlZaVlpSVlZSTk4SSbJOTk5SUlJWYmJmbm5yanJycm5ubnZuam5mam5uZl5aTlpaVlJOTkZGSk5KTkpOTlJSUk5OTkJGQkY+Ojo+PjpCPkI6Pj5GRkZKQkpKSlJSWmJmcnJydnZ+dnp6bm5uZmJiWlZWUlJKRkI2Mi4SKF4mHiYqKiYmJiImHh4aHg4SEg4SDgYGBhIB5/Pn19fLy7/Ds7Ono6OXn4+bs397a2dbS1NbU09LRzdDSztDNzcvNys3O0M3MysnIycrHx8nJzM3Nz9LT1tbT1dPT1NPT1tjZ3d3d4d/k4+Th5OPk4uPi5+fp6evp6uzu7+x2dXZ2dXV2dnd2d3d5enp4eXh4d3d3eIR3J3h5eXh5enp6eHl5eXp5eXt6e3l6eXh5enl7e3x7enp6e3t9fH1/f4R9gH6EfX58fXx7fHx+gn5/gYGCg4SFgoGCgoODgoR5hoeHiIiIiYqJhIOBf39+fn1+f4F+f4D8+/z7/fz79/Tw6+np5+nn5enw8fLx7u3o4OXi43je3NrY19fT083JzM7Ny87LzdTDy8rDv8G9ubm7u7i1r6urqaippaKgn5qamJWUgJKSkZGOjJaLkIWFh4OAhIKA+vnw8erp6+ft6eXi1eDe2rvV19TPyM6/u8G8ury6vMC7vLi0r7Crrq+vqKqsqqyvpqKkn6GfnJicn5+bmp2clJeYlZOZl5KVk5CTko+Xk5GXlJKQkZGRkpKPlI6SkpGRlJGRkI6Sk5SQlZaWkZSYgJWUk5eTkI2UkpCUmJmboKCtl5eenZudl6Cbpaajqqafqaywraywsqmorq61r7m3uLq6vLi7wb/Gu77FwczFx8DFysbR1NLj2trh3efi5ezt8/X28vH39/n8/IL9+4D/hYCEhoyNkZONj5SVlpSamJuWmpqZmJ2ioaKnqKSurKuwHKmrqbCxtbe0tbGzsrK4wry/xr+9v7q7ube3uLqFv4DAvrzAwL69u7azsrCtq6+tsbKvr7KwsK2vsrSxrq+tq6uoqaiqqKekpqOkoZ6gn6ako6OlpKSnoZ+jpaCloqChoqKjpKajpqelo6KoqKilpaOjo6Slo6OjpaWlqaiqp6anqaynqqurqqerra6vra6vtLK0tLa0tLe9u7y8vsDBxA7JxcfIy8zJzNHV1NfZ2yKmqKisrK2xsrGwtLW2tLe4trm5urzAwcTExGNkZGNlZWZmhGgMaWprbG5tb25vbnBxhHAdcnNzc3J2c3R1dXd2d3d4d3l4eXh5eXp5eXh4eXuEeTJ7fXp7e3x8fHt8fX2Cfn19fn+AgoB/gYKCg4OCgoGBgoKChIOCgoSFhYSEhIOCgoOEhISFZIiIhoWFhIWFhYaGhoeGh4iIiYmIiIiHhoeGhYeGhoWGhoaFhYWGhYOEg4SEg4KCgoCDg4KBgIB+fn1+fX18fXx8fXx7e3x5enh6eHh4eXl3eHZ3dXZ2eHd3d3V1dHR1dXZ3eHqEeCJ6eXl6eHl4d3Z3dXRzcnJwcXFvb25tbWxtbGtsbW1sa2pphGp0a2hpaWlqaWhoaGdnZ2jPz87Pzc3Ky8nLysrMy8zJy8/Fx8bHx8XJzMnHx8bEyMrIy8nLycnGycrLysrLy83Oz83Mzc3OzczOz9DS09PW1tja29ze3dzf3t3g4OPj5OLi5ebm6ejt7fDx8/Hw8PDx8Hl5enqEeSd6enp5e3x9e3t6enp7e317e3p5enx7ent8ent6e3x9fn19gH6Af4CEfw9+f35/f35/f39+gH5/gYGEgAWBioGDgYWDF4WJhIWFhYSFhoaDgoKEg4KAgnWChIODhYKAfX18enp5d3Z2dnd1dXTn5+fm6OPj393a19fW0tLPzc3R0M7MyMjFwMTCwnW9urm3t7e1t7Oxs7SwrrCtrbikq6qloqOhnp6fn5yalZKRkZCQjIqIh4OFhIKCf359fHl3h3d8dHV3dHJ1dHHe3tnc1tPSzdDNyMW7xsG6m7C0s7CAr7isrbGop6Sgn6KfoKCdmZqUlpWWkpKUlJSZlJCRkJSSj42MjI2JiIyNhomKhoSKiISGhoOEhX+Fgn6Bf317fXx8fXx6fnp+fn59gX17fHl6fHt4fHx8eXp9e3t8goJ9fYJ+e31/gICCg5R9f4aEg4SBhH+FhIOHhX+FiIyKipCAkoyKj46SjZKPkZOTlpKUmpqglpeblqGdoZ2jpqOqqqi5sK+1srm0tbm5v8DAv7/GxsnLzGnNy2fNamVnZmprb3BrbXFyc3F1dHdzdnd2dXh8e3p/gH6GhoaKhIaEiYqLjIqJh4mIipCXkZKWkI+RjpCSkZKRkpSUk5KSk5GQkpMBk4aURJOPkpCSk5GQkZCOioyOkI2MjYyKi4mJiImJiYeJh4iGhIWDh4WEg4SEhIeBf4OEgYSDgoSEgoKBgoCDhIOCgYWEg4KChIA6goCBgYODgoWDhYSDhISIhYeIh4eFiYuMi4mKi5OOj4+Qjo6QlZSTlJSVlpicmZudoaGfoaKjoaGipRV+gICDgoOGhYOBg4OFhImMjJCRkZOFlThLTU1NTk5PTlBPT1BRUlJUVVNVVFVUVlhYWVpaW11dXFxfXFxcW1xcXV5fXmBeX15gYGJiY2RkZYRmKGdoamlqaWppaWhpbGxxbGtra2xtb25ucXJydHR1dXNzdHV1d3Z1dXaEeBp6eXl6e3p6e3p6enx8e3t6eXp6ent7enp5eYd6Rnl5enp6e3t7ent7e3p5eXp3dXVzdHV2dHNyb3JzcnFwcG9vbnBvcHBwb29vbm1sbWttbG5tbGxtbGtraWlmZWVmZWVmZGSFY4BkZGVjYmJhYmFhYWBhYWFgYV9fXl1eXl9gXl5dXFxcW1tZW1xcWlpZWFlYWVhZV1hYWVpaWFhXVldXWLKysbOxsK6tq62srrKztbO3u7O0s7O0s7a5uLm6uri9wL6/vb67vru/wcTCwsPDxMXHxcbJyMzMzM3Q0NLS0dPS0tPU1DTX2Njd3d7h4OTl6ejr8PLz9/T49PXz9fT0+Pv9/4CAgYCAgYGCg4OEg4WGh4WGhYWFhoaJhIeAiYyMjI2OjYyLi4uNjY6Ok5GTk5SSkpKRkZKTlJOSk5KTk5aVl5mYlpaWmJmgmZqWl5aVlpeZn5ubnJuampubl5WWl5aWlJWFlZaWlpWVlpaVkI+NjIuKiYiHh4iEg4H+/v78/vr59fPu6Ofm4uHe2trf3drY09HOx8zMzo/IxcOAwL69ubizrrCxrayuq6y6pKmppKCin5uanJuamJSRkZCPkIyKioqGh4SBf317enp5eIp6gHd2d3RxdHNx3+Dc39nY19DW09LRxtDKwp61uLm1sbipp66rrKynpqigoaGcmJiQkpKTjo+PkJCUj4uMi4+Lh4CEh4eHiYyNh4iIhICAhYN/goN/goB6fnl2enh3dXRzc3R0cnZucW9ubnBwcXNxdHNxa25tb21vc3BvbnJxcHF3dHFxcHBxcXOGaWlvb25xbXFpcW9wdXZscXR3dnN1d29uc3N3c3l1dXZ3eXZ6gIGHfYCFgYqGh4KFiIKGhoOOiYiMjJSQkZSVmZuamZhUnJmZmZpPmJhOm1JOUFFWVVhYU1VYWVlXXFpeW15gX15fY2FfZGNhaGhnbGdnZ2prbW5qamdpZmZrcnFzdnFvcm9xc3Jyc3N1dXNzdHV0c3V3dXZ1hHSAcnBzcXN0cXFzcnJvcnN1c3FycG5vbG1sbWxraWppamhmZ2ZqaWhoamlpbWZkaGlkZ2RiZGRjZGNkYWRlY2FhZmZnZmdmZmVlZmNjYmNiYmVlaWdoaGhqZ2hpaWdkaGpsbGlpaW5qbG1wb3BxdnNzdHNzdXV3c3R0eHl3eXx9fHsCfH6af/+A/4CRgOJ/+oCdfwGAv3//fsp+BX9+fn9+/3/RfwICBAA23eDd3dzd3ODh4uTm5+Xq7vT19vb6+Pv/gYKDg4SFiIaIh4iLi4yOjo+PkY+PkJGRkJOUlZaVhJcOlpWZmZeYmpqbnJ2dmpiEmkibmZmampmYmJiZmJeXmZeWlpeYl5eXmJqZl5eWlZWVlJOUlJWVlpWWlZaWlpeYmJeYlpaWl5eXlZWVlJOTkZCRk5SWmZiZmpmEmBSXl5aZm5iXmJmYl5mYmpqcnZ2dnIWeZJ2dnaCfn56enpybmpmYl5aYlZaWl5eWlpaVl5iYmZiXlZWVk5SSkpGRj5CPkZKSkZGSkZOVlZSWl5eYmJydnqCgn6GnpKGioqGfn56dm5ycm5mYl5WSj46NjY6NjYyMjImLi42EjICLioqKiYqJiYeGiIODg4SAgP/++vr49/L29PTv7+/q6ubm5uLd3t/k2Nfg2NfW1NPV0tTS0M7R0M7N1NDQzMrKyszKzMzQ09TT1tbT19jY29va2tvW29zg4ODn5ubm6ezn6uzq6+rr7PDt8PHx83p7e3p6eXl5e3p6e3p5eHh5ej15enp6e3p6eXl5ent6enp5ent8e3t7ent/enp7e3x9fHt7f3x+fn59f4F/fn5+f4l+fnt/fn5/f3+AgIOChYCAgYGCgoKDhIWFhYaGh4eGh4mFdIiKi4uJiomJiIeEgoKBgYGCgYGCgoCBf4CCfn+A//v29O/t7erp6+nq7fH28PLw6uno5eWB5OXg3Nvc2tbU09XU29bUyszGx8nJxMHDvr66t7WytLKyr6+urampp6GhnZ6bm5STkpKOjZCOi4uAjoiGh4iDg4D+gPv09vHt7+jn4dna2taxyNDN4szKxL/CvrnAu7u9vbzBtbCur6yyrqyrpqampKakoaKgnZ2boqyjnp6ampiYm5qXmpSYlpmXkpKPlI2Tk5iVlpKTmo2OkJCQlJCPlZSalpKRkYyQkJaWl5abl5aXmY6NlI6KlZGAkpSWm5qco5qgop+hn6OhnqmmqaWrqK6vrLK7sa6pqKmus7K1t7u7vb2/vL68vbm+vMLEycnKzNXN2Nna1dbZ393m6O7v7vD5gPz5/fv5/YSCgYKChYaIiYiYlYyak5KWlpeXmZianJ6cm5ufn6SrqrSqrq+1tbGwsLq1tba5trSAtrbAvcDCwcPHxcPAxMHBv77CwsXFx8TFycXKx8LEvbi4tre2trO2tLO1tbOxtra0trW2vLKxsK6rq6ytr66wq6aoqKimpaSmqKmppaaoqaWnqaalpqOko6qop6WpqqaopqepsaipqKSmo6WjoqOlpqipq6yrqqmqsLCrra6srKwlrq6ztLa2uLa2uba5uru/vsDAxMTIycnPzMzQzdDS0tTb19na23uoq6uurbCxtLW3ubm6uLm7vbu9vcG/wsZkZGVlZGVoZmhoaWtqa2xtbm5xcHBxcXBvcXJyc3N0dHV1dHR3eHd3eHh5eXp7enp8e3x7fHp6ent8e3x8fXt7e318fX19f39/fn6Af35/gICBgoKBgoOEhIWEhYSFhYWGhoeEhmSHiIeIhoeIh4eJiIiIiYiIiYaHh4aHiIiJiYiIiouKiouLi4mLiYqJioqKiYiKioqLi4qJiImIh4aGhoWFhYSEhIOEgYKBgYCAf39+fn59fX18e3t8e3x7fHt8e3p5eXh5eHd3hHgjd3Z3d3d2eHl5e3t5eoB9enp7enl5eHh3eHd1c3NycXBvcHCEbwVub3BtboRtcmxtbGtra2pra2pqaWpnaGlqaGnS0c/Qzs7Lzs/QzM3Oy8rIycnJxsnN38rM2czLy8jJzMnNzMzLzMvKyM/Nz83Pz9DS0NDP0tLS0dXV09bX2dvd3d3g3eDg4eDg5eTk5efr6Onr6ens7fD18/X29vZ7e4V6F3t8fHt8fHx7fHx8fX19fn19fn1+fX1+hn0Zfn1+fn1+gn+AgIGCgoF/gISDg4OCgYKEg4SCgI6Cgn+DgoODg4SDhIeGhYaGhYaGhoeHh4aIh4aFhoWFhoaGh4RzhoeHhoSEg4OCgH58fHp7e3t6eHh3dHRzdXZzdHPk4d7d3Nva1tTU0M3Nzs/JysnGxsbExIPFwr25t7m4trW1trW5tLOrq6mpqaqmo6WipKOhn52cmZmUlJKSbI6PjoeGhYaEhn9/f316eXt5eHh5dXNydHBxcN9w39jZ1tDRy8vFwcG/vJuutbXKtLKtqquopKihnp+foaufnJqZl5uYmZaSk5OQk5ORlJKPjYuPlY6JiYiIh4iIh4SIgoWEiISCgn2Be359gYR+c4N6e31+foF/fX9/gX59fXx6fHp+fXx8fnx7fYB6fIOBfYR/fX6AgoCBl32DhIOFg4WCf4aDhoOHhoiIhoyUkJCPjo6PkI2NkJOTlJWYlpeXmZidmZygoqSlpq2lra+wrK+wtbS3uLy6ubi+YsTByMfIymqEaIBpaWpqaHRzbHZwcHJzdHR2dXd6e3p5en18gIeDkIKFhouMiYmIj4yMjJGOjI6NlJSVlJKUlZSUk5iXmJaUlpSVlpeWlpiVmZiXmZaVl5WWlZSTlpWUlpSRj5KSkZORk5mPjo6NjIyMjY6Nj4uHioqJh4aGhoeIh4SEhoSChIaEhFGFhIWFjYiDgoSFgoSCg4WIg4WDgoSAg4KCgoWDg4KEhYWGhYSIh4SGiIeIiYqJi4uNjI+OkJOSk5KRkpKUlJeXmZmXnp2coJ+jpKSlqKWkpKYZgIKDhIKFhIWFhoaHiIeKjZGQkpKVk5WYTYRMEE1RTlBQUFJRUlNTVFRXVlWEVw1ZWlpbW1xdXV5dXV9fhF4yX19gYF9gYWBiYmRiZGVmZ2VnZ2hpaWlraWhoaGlqamtsbmxsbG1ub3BxcHJyc3R2dXaEdU52d3d3eHh4eXt6fHt8fXx8fXx8fX9+f4B+fX59fH1+f359fX5/fX19f317fHp6ent8fHx7fHx9fH17eXh6eHh3d3d2dnR0c3R0dXNzcnKFcQlyc3JxcHBubW+Fbh1vbW1sa2tqaGdnZmdnZmVmZmVlZGZmZmdmZGRpZIRigGFjYmJhYmFgX2BhYGBfYF9eXl1dW1tdWltbW1paWVlYWFhZWFhZWFdXWFRVVVdWWLS1s7Wysq6ys7Wztri1trW2t7e1ub/Qu7zMv76+vb7AvsC+vru/v76/x8HEwcDDxsnJzMzN0NDP0dHO0dLU1tfX2NrX293f3+Hm5ebm6vDvDvL29fj59/j69vj6/P6AhYIFgYGDg4OFhQOGh4eEiAGJhYoHjIuLjIyNjoSPgI2OkpCSk5WWlpWTk5eUlZWUk5WVlJSUlZajmJiVmZeYmZqbm5yenJqamZmZm5uenZ2cnJuamZqZmZqYmJqVgZaXl5eWl5eWlpORjo2Li4uMioqKiIWDgoOEgIGA/fjy7+zq6OPi4d3b3d7h2dnX1NPU0dClzszHwsDAvLe0srOygLeysamqp6amp6Shop+gnJuYlpaVlpOTkpKOj4+Ih4WGhIV+fXx7eHd7e3l7fXd1dXVxcXHgceLb39zY29bW0szLyMScs7q40rSyq6erqamxqaWnpaSlnJeWl5OXkpKPi4+PjI+MiY2Mi4uKkJaOiYqIiYiJiYaDhX6CgoWBfXt2gHt1eHuAfHx5d3xwcHNxcHJua21sdHFwcXJvcG5xbW9vc3FwcHNsbnZzb3ZwbGxsb25vgGxwcGxubXBuaXFuc3F2d3l5dHd+dnRwb29ydXFyc3V1dnd7eXx8fXuBfoKCg4OBgYd/hYaEfoGCiIiNj5SUlZWaT5yZnpmYmE9NS0xME05QUlJSXV1VXlhYWVlYWFpZW12EXoBhX2JoZXNlaGltbWppaXBsa2tta2psbHNwcXFvcXRyc3J2dnZ0dHV0dnZ4d3d6d3t4dnh2dXd2eHh3dHd1dXZ1dHJ1dXN0c3R8cnFxcW9vbm5vb3FsaWtsa2pqaGtsbWxpaWtqaGpsaGhnY2RjaGZjYmVmZGRiY2NpY2RkY2RiZAlkY2NkYmJiY2WEZi1qaWZoaWhoaGloa2xtbG5sbW9tcHBxdHR1dXh2eHl3e3l4e3l8fX1/g359fn+Yf/+A/4CUgN1//4CFgJd/AYDAfwJ+f/9+v34Bf4Z+/3/XfwICBABq5+Dd4OTk4+Pj5uTn5+ju9PL7+Pr8/YGCg4WEhYaIiYqLi4yMjY6Ojo+QkpSQk5OVlpSWmJmampqbmpmbnJ6bnJ6en6Cfn56fnp2enZ6dnJydm5ydnZ2cnJydm52cmpqZmpqbnJubmpmYmISXMZiYmJqamZiYmpmYmpuam5ubnJybm5qamJiWlZeWlZiXmZycm5yam5uam5qbnJucnZuEmh+dnJ6dn5+gn5+hoaKioqGioqKjo6GhoJ6fnZ6dnJydhZsumpiYmZqZmpubmpiXmJaYlpWWlZKUk5OUk5WVlZSVlZeYmZmcm5qcnqCio6SnpoSogKempKOhoZ+hnpybm5mVk5SQj5CQkJGQj5CPjY+PkI6QkJCNjo6Oj42PjIyLiIeFhYSCgoD9/Pv6+vbz7oT17e3r6+jo4+Hg29na29vc29vZ1tjU19fZ29fX2dfW1NXS0dDRz8/R0dXV09bV2dna293e3uHg4uTl5+nq6+vq7OzrCu/z9nx98vTy9PWGfAZ+fHt8fHqEfRx8e3p5en17fH58fX19fHt8gX5/fn18fHx9fX5/hn6AfYCAgIGAgISAf3xwcnLBzN5+goOBgoCDgoKCgIGAgYCCgYKCgoSGg4SFhYWGiIqHh4qKjIyLiIl4i46QjYyLiouJiIeFhYOEhYSEhIWFhIWDhYSCgoKB/vT08/Tv7ezu7vLw+/z18vP56ubo6eXm5OTh3d7Y19fZ19fb59PT0c6Azs3Lx8bGwcG7uLSwsrKwr66rqaimpqKioKGgm5mXl5WQkI+Qj4uNkYqKioeFgYGB+Pn49vXv7eri2djU2rnM0srR0MLAxMXCvb/AxcG+u7ewr7GlpqutsKqsqqqmp6Wlo6WeoaKenqqimJaVmJSYtpmdlZWWlZWTkpSQj5OVl5qAk5eTkZSQkI+SlZGWnZeWlpWUjo6Sj5Sdk5CTm4+SlI6Tk5ORlpeWmZuemqKknKGinqGjnKGjqaysp6epqLGwsbWuqaaqsLO3tbu5t7u8vru8wcDAw8jIyMnJzNLS09fZ19bW2dXZ3eHi9u/08PL39fH4g/6BhIOBhoaKiYqKjY6AlpGUlpaVnJmdnJ+fnqGnoKKjp6iqq7Cytra3uri6vLq3t726u7q7u7y+wMPEyMjKys3Oy8nKyMnLzMzN0MvNy8rLyMXCwcK+v7i9vrq6u7e3uba4uLu5uri2trOxtK6tr7CusrOurKqssK2rq6qtrKypqamtqqyoqKqpqqansqxLqKmnqamnq6msqqisrairqaepqqqpq62urq2vq6yvtbKtsrGxr7Gsr7a0ury9vrm7ub6+wcfFxsTGycfLzc/R1dbV1tfW2uDf3+Ljaa6qqq2xsrO0tru6vLy7vcC8wb/Bw8RjZWVmZWZmaGhpaWpqa2xtbW5vcXJ1cnNyc3RydHV3dnZ2d3Z2eHl6eXl7ent8e3x8fX18fXx9fXx9fn19fXx8e3x7fX1/f4CAgYKBgYGAgYKCg4WEC4WEhoiIiIeIiYiHhogniYuKi4uLiouKi46OjY6MjI2Mi4uLjIyLjIuLjIuMjY2MjIyNj42OhY0IjI6Pj46OjI2EjBCKiomIiYiIh4aGhoSDgoKChYGDgIV/En5+fn1/fn1+fXx7e3x7enp6eIR5DHt6eXp8fX5+fn99f4V+IHx9e3t6e3h2dXV0cnJzcnFxcXBwb25vb25wb3Bvb25uhG0GbGtta2xsh2pBadPQ0dTT0tLMc9TMy8vMy83Nzc/PztDRz87Nzs3Mz83P0NDQzczPz8/Q09PT1NbT0dPT1tfW2djc2drc3uDh4uKF5B3m5+jq7O3t7u/xfHvv8vP293x8e3t7fH18e319fYZ/An59hH8EgH+AgISBDYOBgYF/f4CAgYCBgoCFgoCDhIOEg4ODhoSDgHZ2d9Ta6ICFh4WHhYiGhoeGh4aIh4iHiIiIio2IiIqJiIiKioeHiYiKiomGh3eIjI6JiYaGhoSDgn9+fHx8e3t6enp4eHd4eHZ2dXTk3t/i4dzZ1tXT1M7X2M/NztjIxMXFwcPAwL26vLe1tre1tbi/srOxroCuraupp6ikpqOgn5ycm5qYlpOSkY+QjIqKioiEgX+Afnt8ent6d3h8d3Z2dHJwcXDc3NjX1dDQ0MrFxL3BorO5t7y8sayvrqumpaKjn56fn5ucoJaVmJibk5WTlZSVk5KRlI6PjoyMl5SLiomLiIm2i46GhYWEhIKDg4B/gICCgYB7gX19gX1/fYCBfX+Ffn19fn56e315fYJ9e32Ee36BfoKDhYGBgH5/gYOAhYiDhYWChIaCg4SGhoeBgoeFiYmKkI6MiYqNjI6MkpGRk5OVlJWcmpeZm5ubnp+iqKioqq6wr7G0r7O2trbEu765vcHCwMhpy2ZoZ2VpaGtqampsbYB0b3FycnF2c3d1eHh4e4B7fHyBgYGChYaJiYqMi46Qj46OkpGRkJKTlZiYmZaXlpeXmpubmpuam5ucm5udmZubmpycnZydnpuclZmalpiYlpWYlZaVlpOUkpGQj46RjY2Oj42Rko+Mi4uOiYeHhoiIiIaHh4eGh4SEh4aGhISOh0uDhIOFhYSGhIaDgoWFgoSCgYKDhIODhIWGhomGh4qPioaJiYqKjYmLjoyQkZOUkZOSk5GTlZOVlZebm56dnp6hoqOkpaWnqqmnqas0hYKBg4aHhoaGiYeJiYqMkI+Uk5SWlUxNTU1MTExOTk9QUVFSU1NTVFVWWFpWWFhYWVdaW4RdJF5eXl9gYF5eX15gYWBiYWNjYmNjZWVlZmdnZ2lpampra2tqbIRrM21sbm5tbm9vcXFyc3NzdHN1d3h4d3d4d3Z4eXl7e3t8fXt9fX5+f35+gYB+gH9/gYGAgYSAAoKAhYEGgH+AgH+BhX4KgH9/f4CAf358fYR8BXl5eHd4hnYHdHR0dXR0c4R0anNzc3JxcHJwcXBwcXBub25tbWxsa2poaWdoaWlpamlnaGhpaGhnaGZmZWVlZGVjZGNjYmRiYWFjY2JhY2BfX19eX15dXVxaW1taWFlYWVhZWlpbWVtZWVlXV1ZYWFlbWra0tLW1tLazaL6Et0i4urm7vr67vsDAwcHDwb/CwMHCwsPBwcXFxcbJx8jLzs3O0c/T09DS0dTT1NbY29ze3uDg4eLk5+jp6uvt7vL2+4GE/v/+//6FgEWBg4KBg4OChYWEhoeIiIiJi4qKjIqMjI2MjI2PjY6OjY2PkJGQkZKQkZKSk5OUlpWWlpWWl5eWk4qGheHp/5SZm5qcmp2EnAWenaCdnoScDJ+jn6Cgn52en6Ccm4SdgJuYmIOYnJybm5qampiVk5CPjYyNjIuLi4qIh4WFhYGCgID68PHw8evk4uHe4Nvm6NzY2+DV1NTSzs/LysTAwLm2tbSysrS7r7Cuq6upp6Wjo6ChnJqXk5WWk5OSj46NiouJiIeIh4OBf4B/e3x7fHp4eX14eHd3dHJ1dePk4N7agNXT1M7IyMHEn7S7tLu5rKmsrKilpqmsqaWkoZydoJaWmJibkpORkI2OjY6Pk46QjoyJlI+HhoaLhoi4hoh/foB9fnx6e3h4ent9fnZ5dXN1cHBtbnBrb3ZxcXN0dHBwcWtvcm1scHhub3BscHF1cnN0cG9vcm1ydW5xb2xucGtufG9zdHdydHVyeHd2eHNxbnBycnVzeHV0dXd3dnZ8e3l8gYGEhIGEh4aFh4eHhYeJhYuOj5CflZiUmJydmp9Unk5PTkxPT1JSVFNVVVtVVldXV1xaXVtdXFxfY15fXmFhYWJlZ2tra25tbW9ua2twbW1sbW5vcHJzcnRzc3WEeIR3gHh3eHx4enl5enl6eHl7eHl1eXt5e3l2dXZzdXR2dHV0c3NycXRwbm9xbnJ0b21sbXBtbGxrbW1ta21tbmxraWhqaGdmZm5pZ2ZlZ2dkZWNjYmJmaGVoZmRlZWRjYmNmZmdrZ2hqbWlmamlqamxoaW5rbm9vb2ttbXFxdHd1dHR2Anh4hHoMfH18fn9/goaEgoSDln//gP+AmYCIfwGAxn+CgIV/wICDf8aA2X//fsN+An9+/3/YfwICBABH7Ofp6Ofl5+nq7O/19Pb6+/qAgICCgoSEg4eIiYqNjo+PkI6QkJSQkpKUlJOUlZWXmJicm5yeoKChoJ6enqCfoaOjo6Sko6KEoQmioKCgn5+goaGEoGWhoKGhoaCdnp+enp6fnp2cm5mYmJmbm52cnJ6dnZ+fnp6en5+en56gnqOgnZ2cnJqZmpqbnJ6foJ+foJ2dnJ6en6CgoKKgn5+enZ2gn6GhpKOjoaGjpKOlpaWop6enpqWjpKOko4SgC5+enJ6dnpybm5uahZsRmpmZmJuZmpmZl5eXlpaXl5eEmYObhJwJnqCipqaoqamqhKsTqainqKampaOjoaGemZeVk5OSkoSTR5SSkpKTkpOSk5WUk5KRk5SQkZGPj42LiYiFhIOBgYGA+/L28PHu8O/v7Ovn5ufm4+Pj4ePh39/d3dnh3Nva29rb2dfY2tzXhNYz2dbW2dva3N3d39/g4+Li5uXi5+rs7+7w8vL08fXx9vf4fvp+fX1+fn99fX726O9+gX1/hIAcgX9/f35+f4CAf4CAgH5+f4CAf35+e359fn+AgId/gIGCgoOEgIGBf22tesmjs5C6fIOCgYKCgoODg4KCgoSGhYWGh4eKiYmJi4uKjZGLioiLjo2NjYt3k5CSjo6NjIyKi4qIiIeIiYeHh4iJi4aGhomDhoSDgPz49v329PLv8fL3+fz69PX27+/s6+jo5uXj3uDd2dPX29nZ1tbZ2dbUgNTQy8fHxr+8uba3tri1trezrq2rqKelpKWinpiYlpOUko+MjYyNjYmOiYiGgv77+Pn48fXy7Ojl39ra1bTF0dHUzMnIxsbPw8nZx8S8uq+zr7KpqqetsqympaOnqaulpaOeoqSgnqCdnJqVnJqanZOVmZiWlpqTmZeZmZqZlpeWgJSPj5WRkpCQlJGPmJeVl5eSmZaVlp6qmJeamZSTopiUn5+cmpqZmpecm5yhn6KhoaCmo6qoqq2ura6qrq+wss/Bq6qvsbm6uLe2ury9ur7Ly9TMxcfNzMzMz9nY2tfS2NzY3uXg5uLk7fDw7vTz+fX6g4OEhoaJiYuPkJSSkpaXgJidmpeYmZ2dn6CooaSmp6Soq66ur7TBuLq7vcS7v8HBwb7BwsK+xcDCxcjJz9HS0dHQ0svLy87Qzs7Ozc7Mx8jNyMnMycTDwsnFxMPBw76/urzAu727vsC/vbm6tLW0v7ayvrCys7CwsbOysbC0rbCrra2wqrKxr66ura6wsq+uDaqrq6yvrqyurq+urriErTmurqyvsbGwsK2srrG1s7K1trOytLK0t7q7vMC/v77BwcHNxMbGxsTJyNLW0tPZ09jY1Njc4ODi5etAsa+zs7Sztre5u77BwMLExMJiYmJkZGVkZGdnaGlpamxsbW1vb3NvcXFycnJzdHR2d3d6eHl5ent7e3p7e3x8fIR+CX9+fn9/f35/foR/g4CEfmV/f4CCgoOCg4SEg4OEhIWGh4aHh4iJiImJiIqJioyNjIyMjYyMi4yNjJCOjY6Oj46Njo+Pj5CQkI+PkI+PjpCPj46Ojo+PkJCRkJGSkJGRkY+Qjo6PkpCRkZCSkZCQjo6NjYuMi4SJJYiHhYaEhYSEhYSEhIKCgoGBgYCAgX+AgH9+fn19fH18fH18fHuHfAd9f4CCgYKChoESgH9+fn19e3p6eXh3dXV0dHNzhHIEcHJxcYRwAW+EcAVvbW9vbIRtA2xsa4RshGsP1M/Vz9DOz87Pzs7Oz9HShNEq0tDR0c/RztzT0tHS0dPT0dPV1tTU1djY2tfY2Nra3d7f4eHj5uXm6OjmhOgx5+vu7/Py9vL19PV89nx8fX1+fXp6e+3j7H2CgIGCgYGAgH+AgYKBgoSCg4ODhIODhIWDAYGEhASFhYSEhYWAhoaFhoaChISDcLt+1cjDk8CAioqJiomJiYuKioqLjIyLi4uMi46MjIuMi4qMkIqKiIqNjYyMi3ePjYyIiYiIiIaGhoOCgYGDf399fX6BeXp5fHd5dnVy4d7d493b2tXV1NbV1tLPz9DKycfGxMXCwcC8v727t7m7uri0tba1sa94rqypp6qpp6aioqGfn5ybm5eSkZCPjYyLjImGg4KBf4F/fXl6eXd4c3h0dHRy4eLf4d7W2dbSzsvHw8K+oa+3ub22tLGurrGlp72opKChm6Gdn5WXk5iclZSTkJSTlJCRj4yOj46MjIuMi4eQjo2QhoaKiIWEhoCFhII9gYCAgoB8fYF/gIB/gn9+g4B8fHt4fHp6e4GKfHt/f3x8h4F+hoeDfoB+fn6EgoKGhYiIhoOGgoSDg4WHhYSGgIeKo56Mio+Ok5OSkJCUlpaUl6CepZyXmJ6foaKlrKuvq6mwtbK3uba6tbe7u7u6wcLJxsppaWhpaWpqam1tcG9vcXFxdXFxcnR4eXp7gXt9foB9gYOFhYWIkouNjpCWjpGUlJWTlpiXlZuWmpucmpucnZ6en6Ccm5uen5+foJ+ggJ6bnKGgoaWjoJ6co5ybmpicmpqXmpyYmpeYmZiWlJaUlJOak4+ajY6PjYyLi4uKiY2JjIiJiIeDiYmIiImIiYqLiIiHh4eIiYeFhYWGhoaMh4aHhoeGhIaHhoeIh4eJjI2LioyNi4qNi4yNkJGSlZSTkpWUk52WmZucm56doqOgDaCkoaWkoqaoqqmqrLAUhoOGhYWEh4eHiImMi42PkZFKSUqESy9KTExMTU5PUFFSUVNTVlNVVVZXVldYWFpbW11cXF1eX2BgX2BfYF9gYmJiZGNjY4RkHWZmaGhoaWlqa2pqa2xsa2tsbG1sbW5ub25vb3BxhHMPdHV1dnd3eXp6fHx7e3x8hH0PfnyAf36AgIB/foCAgYKDhIQFhYOCgoOFhAGFhIRGg4KEgICAgYCBgICBgoGBgYCBgH5+fX17e3p6eXd3d3h2dnR2dXZ1dXZ1dXZ1dXZ1dHNycnNxcnFycXBwbm5ubGtramlpaoVpCGhpampramlphGcqZmdmZWVlZGRjY2RkZWVjY2NiYWFgYWFgX19dXFtcW1paWltbWllXWVpYhFkFWFhZWVqFW2Vct7O5tri4ubi6uLq6u77Av7/BwcPDw8LBwr/Rw8LBwsLExMPFyMzJyszQ0dPS09XW1dfY19rZ3N7e3uDg3uLk5unp7e/w8PD19Pn7/YH/gYCBgYGCgICB+OvygYaChYeIiImKioSLHY2Ojo+Ojo6Njo+PkI+PkI2RkZKUlZaVlpaWlZaWhJcRmJSVlpWA3oLHho+C2JWdnZuEnICdnZ2en6Cgn5+foaClpKSjpKOhoqignpyeoJ6enZqAoJ6em5ybm5uZmJeTkpGSlY+OjY2OmImJh4mCg4B/fPTx7vXt6Obg39/g3+Db1tna1tfX1dLPy8nEv8K8uLGytbSzsbK0s7CtrKmmo6WloJ2amZiWmZaXmZWRkY+NjIqKioCHhYGBgH+BgH57e3t6eXZ7d3h5d+zt5+jj2drVzsrHxcLDv5ytt7i8tbOyrq+1rK7MsKukpJmbmZuRlZKYnZaSkY6SkZGMjoyJjY6Ni4qLjYyHjomFhnp6fn18e394fnp7fHx6eXl3c25ucW1vbW5xb293d3R3dnN3c3JyeIFycYBzc29ueXNveHt3dHRycW5zb21ybnBtbm1ycnd2d3d4dXV4dXd2doqLd3R3d3l5d3Z2eXt8e3yEgoqCfoCEhoaFiY6KiomCh4uIipGNkY2OkpOTkJeWnJmcVFFQUE9QT1BTVFdVVFVVVVlYWVpcYGBgX2NdXl9gXV9hYmJjZnBqbGdsbnJoa21ub25wcnFwdHByc3Rzdnd2d3l5fHh2dnd4d3l6ent6d3h8enyAfnx8e4F9fHp3eXV2cnR1cnNydHZ3dnV2dHR0enRwdm9xcXBwcHFxb25ybW9rbWxsZ21sampraWpsbWlrhGZGaGdlZmdoZ2hraGlpaGhmY2RkZWZnaGhpbW9ramxsa2lqaWlsbW1sb29wcHN0dH13eHZ2dXd1e317fYF+gYF/gIKEg4OEh5F//4D/gKKAzH8CgH+JgIN/tYAHf39+fX1+f8aA1n//fsV+/3/afwICBAAc7u3u7+vr6/Lw7/Lz+Pf6/YCCgoSEh4iIioqOjoaQJZKTlJSVlpaZmZqbm5ycmp2dnqKjpqampaWio6Smp6mop6ipqKeEpGmlpKSjpKWkpKanpaSlpaWjpKOko6Gjo6WkoaGgoJ+en6Cen6GgoKChoaGjo6Sjp6WkpKSjoqOkpKKjoqOioqCgoaCipKOho6Oho6Gjo6SlpaSno6GhoaKio6Smpqeop6enqKemp6ipqqqEq1GpqKempKWkpKKhoaCgn5+fnp6fnpyfnZ2eoJ+gnp6cn56dnZycm5ubnJyenaCfn5+enZ+goKGkp6mqqa2tsLGtrq6tq6urqaqpqKWmpKKfnJiElgOUlpWElICTkpWVl5mZl5aWlZaWlpWWlZOQjouKiIyGhIODgP779Pbz9PT18vHt6Orn6Obo5OTk4uDg4d/g4N/e3tvb3Nvb3Nrc3Nza3dve2t3a3dzd3N7h4+Lj4+Tn6eft7/Tx9fXy+vX1+fn8/X5/fn9+fn+Bf83N4NHR5erv2fCAg4OFg0CCgoODg4WEg4KCg4KBg4ODhIKCgIKCgYKDhISEgoKDg4KCg4OCfn3HpMXZjoN33YW6sY9uhIOFhYODhISFh4aGhIckiYmKiYuKjY2LjY6Pj46NjYuJi46PeY+Qj46PjY6Ni4mKi42WhIoUiYqKjIuJhouIiIeIg4CEgP39/fqE/Rn//v769/b08e7r6Ojr6uXj39/d3uHd3N3bhNqA3dbRy8nJycPAwLu8vLq3uLe0sbGwqaupqaSgnZibmpiWlpOOkpCVk46MjZCIgYPx9fX/6+j29u7n4dzb3bfK1tHU0NHSzsfIxcbMwb+2trW0tLWssa+mqaamqKmopKSlpqippKegnaCkmZKemJidoZyds6OdmJaal5udmKOcmJiAlpSSko6OkoqPkpKVmZmZnJiXmJmamp2ZmZeVlpOTlZGSm52fo5+ioJ6ioKGfoJ6fnKKgo6iora6orK2qqrC1r7SsrK+ztbi4z7m5uLe8vb7JzMzKxtDPzs7O1N/W2Nf02tjg5+Tm4+3u8/v78/+FhPz7iYWGiouMj5OUlZmXmZqAm5+goaOipKelpqapp6mur6qqsbG3s7a8urvBu8XDwsbGx8/KycjGycnJzs3Q09XW4NnZ2dPS1dTT19XV09XX0s7P0M7Mz8rLzMjJy8rMx8fFw8HCv8LDv8i+vru6t7i+xb23uLW4uLayubm3trS1srGvr66wsLGyrrK0sLO0s7VLsrKuqrCxsbOzsbKxsbCsq6utrq+vsbS1trWxsLS0trW4t7i3u7i3urm8vry8wMHDxcXExsPGycvNzNHU1NXW1dva3Nze3uLl5efsVbW0tre1tbe9vb2/wMPCwsRjZGNmZWZnZ2lpbGxtbWxsbW1vcXNzc3RydHN1dXZ3d3Z5enp7fH19fX5/fn+AgYGBf3+AgYGCgICAgYKBgoGCg4KBg4OEgQ2CgYOEhYaFh4eIh4aHhIg2ioyKi4uMjIyNjo2Pjo+OkI+PkJCRj5KSkpGTkpOTkpGRkJCSk5KSk5OTlZOTkpGRkpGVk5OThJQDk5OShJODlIWTRZSUk5KSj46OjoyMi4yKiYqJiIiIh4iHh4aEhYODg4WEhYODgoODgoOBgX9/gIB/gH+Afn5/fn1/gH+AgYKDgoGDgoOEgYSCOoGAgIB/fnx8e3l3dnZ1dXZ1dHV0c3N0c3Jxc3JzdHRzcnJxcW9wb29vbW1tbG1tcW1sa2xs2NjU1dKE02nU09DU0tTU1dTW19fX1tnY19jX1dXT09bW2drZ2tva2d3c39zg3eDf4d/i4+bm5+fp6+3s8O7y7vL08/r29fn5+/p+fn1/f3+Agn/Qy93JyeHm8N30gYSEhIKDg4OEhIaGhYWFhoWFhYaEhwaFh4iHh4aFhwGIhIkciomEg82lzeeSiYTPc6fDlXCKi42NjIyMjY6OjoSNCYyNjY+Njo6Qj4aOgI2NjIqMjY56i4yLiYqJioqIhoeHiZCEhIOBgH9+f358eX16enh3cnB0cODg4N3c29jW19bX1NHR0M7MycXFyMfEw8DAvr6/u7u6uLm3tbS2r6ypqaqrp6ampKSjoJ6enJqXl5eRkpCRjYqHg4WEg4GAfnl7eXx7dnV2eXVxdNzegN7gy8bR09LOzcnGxqe0vbq7tri2sausqKm1paWgoKGgoKCYn5+ZmpeYlpaWkpKRkpKSj4+NjY6Si4aPi4qLi4aHm4yHhIKHhIWFgIaCgIGBgH9+fH6CfIGAfn1+e3t+e3p6e319gn+Bfn1/fX+Bfn+Dg4KDf4B9f4SDhoeGhYeEgIiFh4iGh4eDh4iGhIaLiZGMjIuOjo2Ooo+Sk5KXl5adn5+dmaGio6Kipq+orKzEsa+0t7O3srm5ur7AvMhpacjIbmppbGtqa21tbnFxc3JydHV1dnZ5fHt8fn9+gISFgYGHhouIi46OjpKOm5STlpaXnZmZmJibm52hn6ChoaGogKWkpKGipaSjpaSkoqSmo6Gho6Sjp6Oiop2cn52fm56bnJ2em52dmJuVl5WWlJOYm5WRkY6QkZCMj46NjIuMi4uKiomJiImKhYmKh4mKiYyMjYqIjIyLiomHiIiJiYiHh4iIiIeHiYmKi4iIi4yPjY+NjIuOjI2QkJOUk5KUlJSWGpaWmJaZnJ6gn6SlpKakoailp6ipqq2wrq+yY4WEh4iGhoaLioqLjI+OkJJKS0pLSktLS0xMT09QT09PUFBSU1RVVFZWWVhZWVpaW1pcXFxeXl9fX2BhYGBhYmJjY2NkZGVkY2RkZWdoaWlpa2ppa2xsbG1tbmxubW5ubnBxc4RyGXN0dHV3dXZ4eHl6fH18f35/foGAf4CAgH+EgQOCgoSEgwuEhIaIiIaIiIeIhoaHAYmHhh6FhYSEhIWFhoaFhIODgoKCgYGAgH5+fXx5enl5eHeJeAN3d3mEd051dXJzcnNzc3Rzc3FxcG9tbWxtbGtramlqa2pqa2xsa2lrampraGdnZ2ZlZmVmZmVlZ2dnZmVkY2JjY2FjYWBfX11dXF1cXl5eXFtbWluEWoBbW1pbWlpaX1tbW1xcubm2t7a4uLm6u7u6vr/BwcTCwsTEwsHDwsPGxsXFwsLExMbGx8rMzs/U09bT1dPW09bV19ve3+Dg4OHi4ebm7Ovv8vH69PP39vr9gIGBgoGAgIKAycnjz9Dn6O7N74OHiIyLjY6Njo6QkZCQkJGPjpCRkgSTk5SShJSAlZaXmZeXmJiXmJeXlpCP26jY85F1X4xIe62ih56eoJ+en6CgoqSjpKSjo6KjpKWlpqanp6WmpaWlo6KgnZqcnZ6CnJ6enp+en56cmJiYl52Tk5GPjo6MjoyLh4uGhYKCfnyAevLy8e7t6efk4d/c2NjY2dnX08/MzszGxcG/u7uAu7e3trS1tLOxtK2ppqanqKWhoZ2dm5uXmJeUkpGRi42LjIiEgn+BgoGBgH56fHp+fXl5eoB4c3fg4uPoz8nX08/LysfGxJutuLS2s7e5trOwrK69q6mhoqGenZ2VmpmSlJKTlJWUkI+Oj4+Oi4yJiYyRioSKhYKAgHp6m4Z+enqAfXt8fnqBfHh4dnVzc3BwdWxxc3FzdHV2e3h4eHd4dnhzdXNxcm5ub2ttcnR1d3F0b21ycHFxb2xsaG1scXZ1eXlzdnZ0b3J1cHdydHZ5eXl6jXl6eXl+fXyAg4OAfoeFh4aEiZCHioqqjYuQko+RjZOTk5aVj5pRUZqaW1JRU1J4UVJTU1NWVVdXWFtcXWBgY2VjY2JiYF9hYl9eZGRoZWdramptandvbXFwcnl0dHRzdXV1eHZ3eXp7gn59fXh3enl5fHx9fICCfn19fn9/gn9/f3t8fXt+eHp2dnV2dHd3dnx2eHd3dXR4fHVxcXBzc3RydnVzcG9vhG1Ya2xrbG5pbG9rbW5tbW1raGVpamlqaWhpaGloZ2ZmaGdmZWRmaGlqaWlra21rbWxsa25sbW5tb3BvbXBxcnR0dHVzdXZ4enh8fX5/fnyCf4KDg4OEhYKChJB//4D/gKOAy3+JgIp/rICFfwd+fXt7e31/yYDUf/9+wH4Ef39+fv9/2n8CAgQAIe3x8fXy8/L78fT1+PyB/4GEhoeHiIqLjo2Nj5GSk5WVlYSWK5eampudnp+goKKioqOioqOlpqinqauqqKirqq2srq2srKmmqaqpqqmqqKeEpjepqKeoqKeop6inpaWmpqempqalpKSho6OioqSlo6WkpKWnpqmoqaqop6WmpqenpqWmpaWlpKSmhKUopqOlpKSlpqeoqqipqr6uqKaoqKqqqaqsq6yrra2rq6qsq66wsLGwr4atA6yqpYSmgKempaWjo6Sjo6OhoqSjoqKhoKGgoJ+fnp+fnp2dnp2foKGjpKOio6Smp6mqra6vsbS2t7e2tbOxsbCtrKuqqKimpaKhoJ6cmpuYoJqbmZiZl5eYmZqcnJqbmpqYmJaXmJSTkZGPi4mHh4aDhIKB/Pv7+vj5+vfy8vHr6+bn6ObibeXj4+Pi4N/f4eDe4Nra3d7f3+Di3d/d3uHd3ODl5Obm5efk7Ovp6u/t8vH28vX29vj89vh+f4CBgoKDgoGCgH3zr5+WmMXBqI2d/aa+gYmEhYSGhYaFhoeFhYWGhYaFhoaEhYWIhYWIiIaGhYeEhoCFh4WDs5iPttLU5Jel79PJe4Gxe358goeHiImIiYqKiYuMjY6Njo2NjZCRkJKSkpSUkofZtISTlX+SlJOUkY+Ojo2OjIyPjI2Mi4yMi4qLi4qJiYmIiYmFgIOCgoL//4D9/oD/gID9+/f48fTw7O7y7unl5uTj5K/k2tbS0tfb3Hrh3NXSzszIyMTBwcC+vbu7uLW7r6+tq6uspaShnpyanZmZmJKSlJOTlI+Pjoj12s3V3+rxosy34+TauNDGxfS90svO0s7Sy8rBwMDAwbi8t7S5t7CzsbSrrKqsq6Oop6irqaKkpqOhq6iampmbnJ+jo6SpqKGcl5qcloSZgJaYrZWSkpOUk46Sk46TlZmWm5iemJGWmJqcoJ2coJiYlZacnJqlo5yhoaKboKCdpqWop6ahoKaho6KlpayrrK6vrK6vsrSxvrrAwsC9u7zAvbvDyczMzs3R1dLa0dfX2tno497j5ujp6vLv8Yb8/f6AgYGEgoaKiIeOlJKTl5magJ2cnqGioqSjpKOjpqSmqq2xs7a2tri4vb++wMDFxsfIzMrLy8vMzczJys3P0tXd2t7Z4eHf2Njb3ejZ3d3d29vX3dnU19LP0NLT0dHNzdnTysjJxsXExMPDxcPCx8XLxsPBv7y9u7q6vbe2u7u7vbi3trOxsbS0s7GxsrK0s7SzTbSztrSzsrOztLS1tLS2trSwsrKxtLWws7a0tLGvsrS1tLi3trm6urm6urzAwMHDxsnMzc7IyMrIzMzR0dPU1dPW29zh39/i5ejo7PDyKbO3uLu6vLzEvsHDxMZmxmNlZmZmZ2hpa2prbG1ub3BwcHFyc3J0dnV2hXgheXp7fX59fn5+gH+Bg4OCgoOCg4KEg4ODgoKDhISDg4WEhYUChoWEhBCGhoiIiImJiYiHh4iJiYuLhI0Wjo6Nj46PkJKRk5OTlJOTkpSUlZWUlIaVKZaVlZaWl5aYl5iYl5eXlZSVl6ial5aXl5iXlpWXlpeXmJiXmJeYlpaXhJYklJSTkpKRj4+MjY2OjY2LioqJiYmIiIeFhoeHh4aGhYaGhoSDhoIPg4KDgoGBgoCAgIGCg4SFhYYkh4eHhoeFhIODgoGBgIB/fXx5eXp5eHh4dXt2dnV0dnV0dHNzhnQncnNyc3RxcHBxcXBwb29vbG5ubdjY19bT09bV1dnb2NnW2NnX1dfXhdpL2dva2dvX2dvd3d3e4N3g3+Dj3+Dg5OPk5eXm5Ozs6+zv7/Ty9fL19vf7//r8gIGAgIGBg4KDg4OA9bSklZjIwp+HlfymwYSKhYeGhohyh4eHiomJiImIiImJjYqKjIuJiYeJiYqKi4uMi4m3mZK85vv9raXVuMmDhLGBhYSKjo+PkI+RkJGQkZGSkpKTkZGQkpKRkZGQkpGPhd64gY6QfIyOjY6Ni4uMjIyKiYyIh4aEhIOBgIGAf35+fXt6eXRvhHGA3t5u29lt2Gxt1tXT1s7TzMfIysfDwsLBwsOdwbq1srK1tra6tLCurKyqqqelpqWjoqCfnJuflpaWk5KTjoyJhoOBg4GCg358fXp6eHV1d3TXxr7G0dzXk7esy83Kq8O4t96qu7W2ubKyrq6pqaimp6Klo6Kko52enKCZmJiZmZSAl5STlZOOkJCOjJWUi46Pj42Mi4uLjo6KiIOIh4KCgoGCgYShg39+gYOCgIKAe318fXt/fYF8d3x+f3+AfX2BfoF/g4eFgoiFgIOBhICFhoSKiIuKiYWGioaHhIeHi4iFiIuJiouOjImSjpSVlJSUlpqXl52gop+foKOmpaqiqKaAqqq2s6+0tbW3tru6uWfCxMVkZWVoZ2ptamhrb21tcHNzdXNzdHV2eXl9fX2Af3+ChIaGiImIiouQk5KUk5eYmJiamJmamJmbmpqcn6Cio6elqKWrq6qkpaips6aqq6qqqaWqp6OnpqWnqKmmpKCfrqefnZ+goJ+fnZucmJicm6MMnZmXlZOVk5KSlZCPhJIVj42OjYuMj46Ni4qLi42LjIuMi42NhotDioqLjI2Ni4yMiouLh4mNjY6Nio2OjYuMjIyOjo+Oj5CTlpWUk5WWmZqcmZqcm56foqOmpqekpaiprKurrK6wr7K1tiGEiImKiYuLjIqMjY2PS49ISktLSkpLTE1MTE5PUFBRUVKEUwZUVlZXWlqFXAJdX4ReDF9fXmBjY2JjZWRlZIVlBmRmaGhpaYRrB2pqa21tbW6Ebx5wcG9xcXN0c3N0c3N1dHd4eHl6e3t9fX5/gICBgYKEgwGEhIUPhIWFhoaGh4qKi4uMjYuMhIsviouLiYqLkYiJiImJi4uJiYqIiYiKiomIh4iGhYWDg4KBgIB/gIB+fHt4eXl5enqGeV54eXl3eXl4d3Z1dHV1dXR0c3RzcnFwb21ubWxsbGtpampsbG1ub25ta2xra2ppaWhnZ2dmZmVlZWZmZmVlZmZlZGZiZ2JhYF9fXl5eXV1eXlxdXV1bW1pcXVtbW11dhVtxWVxdXbm6u7q6ur6+vcHCv8K/wcLBvsHCw8TDw8XFyMfHycTGycrLy8zOzdLS1drX1tjd293d3uHg6Ojo5+jn6+nt7PDx8vb58vR7fHx/gIGEg4OEg33vnI6Bh8HBn4Fy26C9i5OLjYyOjo6NkJKRkZGEkxCUlZSWlpqWl5qamJqZm5ubhJo8mJW8mZJ3z7mnc2+OeJ1ii8+TlpOcoqKjpKWmp6inqKmqqamrqqmqq6upqamoq6qnmfjJj5+gh5+ioaOihKCAn5uZmZeWlJKSkY6NjY2Mi4mHhYSCfnp8e3t68fB36+l043Fw3t3b3trd1tHR0s7JxcXAvr2KubGsqaqtsbG0sKyqqammp6OgoJ+bnZuamJackJGPjYyNh4aEgoCAg4GCg35+f358fHl5eXXTvLO8ytikerWlyMrAnrmvqNCitq6AsLa0uLWzra2qqKegpJ+eoqGcnJiZkZGSl5eRlpORlJKOkI+NipKPiImIhoOBfn18gH56eXR5e3d5eXh4dneGc25vcnR1c3NzbnBwc3F2dHp0b3N1dXR3dXN4cG9sbXFvb3Z1cHNyc29ycW5yb25ubWxvdXN1cnJwcnJzd3h0c3OAdHVye3d8e3p6eXt/fHp/goWChISGiYWKg4iFi4qWkY2QkJKSkJWRkFOZmpxPT1BST1JUUU9SVlRUV1hZWlhYW11dYWBjYmFjYWFjY2RlZmVlZmVpamhpaW1vcXF0c3N1dHV3d3R1dnZ3eH17fnmAgYF9fYCCjn6BgoGCg3+Eg385gX59fX6Afn58fI2Ifn19fHt4eXh3enh4fHt/fHp4dXR3dHV3end2eXh5d3NycnFwcHFxb21sbGxvhG4Fa21raGaHZ0BoamlnaWpoaWllZmlpaWpqbW5tbG1sbG5vbmxra25ycXFwcnN2eHl3d3h2eXh5eXp5e3l7f4CEg4ODhYWDhYiJjX8CgH//gP+ApYDGf4yAiX8Efn5/f6iAD39/f31+fXx8e3p6e3x+f6CAgn+ogAl/f4B/f4B/gICRfwGAtX+GfgJ8fYh+gn3/fqx+BH9+fn7/f95/AgIEAID3+Pv9+vr6/Pn8//2AgoWGh4eJiY2NjY+Qj5KUlZWWmJiZm5qcnp+goaKho6SkpaimqKenqaqsrK2trqytra6usLCxsrKzr6+vsLGvrq6rqqurqqqqq6qqrKuqqqmqqqqoqKipp6imp6WmpqWlpKampqinqairq6mqqqurqqqnqVarqaqoqKmrqqqpq6yrq6uppqanp6ipqqysra6trKypqqqsrK6urrCwsbCxsbCwsLGwsbS2tLW1tLSzsrGxr7Curayqq6qqqKmopqalpqemp6inpqekpISjgKGhoaKgoKCfoqSlpqmuq6moqqqrra6wsbS3urq6u7y8u7m5trOwr66tq6mqqailo6Kgn52dn5+cmpubnZ+coKChoqGenJybm5qYl5aVk5CPjIyMi4iHhoKCgYD+gf7++vv38vPx7+7v7u/p6Obm4uTj4OTl5Ofh5eTk5ubl5+XlH+Li4+Lh5Ofn6eju7/Dy8vHx9PP19vuA+/v4+X9+gIKEgySEhIWFh/XAnK/mu8KC7uzb3rKJrsyFiYaHiYmKiouLi4qKiIiEiVWIiIeKiYmIiouKi4p84nSAfdjSxcOk+Jrco+mn4vrjna7rvtV3hb2Kjo2MjIyNjI2OjpGSlJORkJKSk5SVlpaXlo6Ct4iBlZeAlpWVlpKRkpKPkY+PhI6AjI2NjI6MjoyKioLIzdfq4/SBg4OCgYGAgICBgoGBgf38/fn5/Pn27uzr6dvNuZrEmMHZpMHf4ODd4dXS0c/Oy8fGyMbDwb6+u7m1s7KwraypqaOenZ2cm5yYkJWSk5KQkpGLg9WsjaHH6dyEsc3JpMSgjuir7Y6mz9LV1tTOxsdYxsi7vba2sq+4ub60tbG1sqqrqKqpqqSmpqKkpKGinqGem56joqeqqaunn5qamJuel52bnJiYmpORvqGZnJ+enaCamZmbnKCbnZqamqCdnqCgn6GfmZidoYSlgKKopKidm5WdnqelpaWqrKmqpqWqp6WiqamnqKuxs7S2u7q7vbmxs7rAvb7BxMrTz8/X0tbb1dfi3+bp6uvo7e3v8e/w+P/+goGHiZGJiYyLiJGUlpeWl5qcnp2ioaKlo52lpKKlpayys7a5vJPYvbvDyMTFyMvKx83R0tjS19PWgNrV2dna2t3f3efl5d/i4ODj5OHi6OTl4OHh4N3i3Nva3t3b2dvW1dDPzc/OzMrJyc3Oy8zMy8rHxMPDw8TDwr/Du7y4vLm8vrq/uba4ubi5tra1tbq1s7azubm1sra4uLq6vLy+u7u5tba1tbu3uru6uLW0tLa2vL64vsW8vby5Jrm6vL/BwsTEx8rLz8/LzdLQ0tPT1NfY1t/f4d/g4ubn6Ojy7vD1Drq5u727vb/CwsTJxmRkhGZAZ2hra2tsbWxub3BwcXJzdHV1dnd3eHh5eXp7e3t9fX9/gYKDg4OEhIWFhoaHhYaFhoaHiIWGh4iJiIiJh4iJioSIgIaGiIeHiIiKi4uKi4uMi42Njo2Oj5CQj5CQkJKSk5OVlZSVlJSVlZaVl5iYmZeYmZubm5iampmam5qZmpubmpqYmZaYmpmZmpmampuam5qZm5qbm5ucm5ybm5qZnJyZmpqZmJiWlZSSlJOTkpGRj4+NjYyLi4qLi4mJiomIiYiJTYiIiIeFhoWFhISEg4SFhYSEiIWEg4SEhIaGh4iJiouKiImKiYiIh4WEg4GCgX99fn19fHt7enl4eHl5d3d4eHh5dXd2dnh3dnZ2dXZ1hXOCcoRxaG9ucG1ubmzYbtnb2d3c2dva2trb2tzY2dvb2dvb2Nzc29zZ3Nzd3t7e4eHi4+Pm5+bl6Ojo6fDw8PLy8/T19Pf3+4D9/fz/gYCBg4ODhIWFhoWFhvCwjqvpwsKB7OPO0KZ+ttCFiYiIhIo3i4uLjIyMi4uKi4qLi4uOjIyKiouJi4t+6XeFg+Hb0saf5JDqjKaTqsu/krj5yemEkdSPkpKTkoSTgJKRk5SWlZWUlZWVlJOUk5STi325gX2RknyRj4+Qjo2Pj42OjIuKiYiIhYaEg4OCgoGAf3vCxcrUyNVwcXFxcG9vbm5vb25ubtfX1dLR0c7LxsfIx72zo4SnhqXIl6C2t7e0uK+vrq2tq6eoqaakop6fnpyZmZeWkpGPj4uIhoWFgISFg32Bfn98eXp5dnG+noSQtNXAea/bzJ68l4jbjMd7lra2trWwrKqvrbClqqanpKCjo6eenpygnJaYlJiYlpOUk5CRkY+RjpKOjY+QjY6Mi46KhoeJiIyNhIeDg3+BhH9+q4eBgYOCgoSAgX+AgIN+f318foJ/f3+AgIKBfnyBbISFhIODgIaDioOEgoiFioaFg4iJiImFhIiFhIaMjIqKjI+Qjo2QkJOVlJCRl5uZm5ucnqKcnqOgpqajpayssLSztrW5uLy9u7m+wb9iYmhpb2prbGtqbnBxcXByc3V2dHl3eXt8d4CAf4KDhoSJgIzHr4+NlpqWmJqcm5ecn56kn6GeoaKfoqGjo6aqp7Csrqyvraytr6yus6+vrKurq6qurK2trayqqamnp6OjoqSjo6GhoaOjoKCen56dm5qamJiXlpSYk5SSlZSUlpOUj42Oj4+Rjo6NjZKOjI2MkJCNi4yNjIyLi4yOjY6Ni46NBY2SjY6PhI4yj4+NkJGNkZePkZCPkpKWmZeXl5aWmJmcnZqcoKGio6OlqKenr62vra2vsbGysrm1tbkPiImLi4mKiouKi4+MR0hKhUuGTQFPhFA/UlNUVlVWWFhZWltbXFxdXF1dXl5fYGBhYWJiY2NkZWZlZmZnZ2hoZ2hoaWtqa2xra2tsa2ttbm5vcnFxcnFyhHMkdHV0dXR1dHZ3d3h4enp8fn6BgYODgYOCg4WFhoaIiYiJh4eHhIkcjI6PkJGQjo6Pjo2NjI2MjY+Ojo6MjYyNjI2Mi4aMhIsRiomKioaGhYSDgoGAgH9/fn2FfBB6e3p5eXl7fXx9fXt6eXh4hHccdXZ1dnRzcnBxcXFwb3JvbmxtbG1ubm9ub25vbIRrC2pqaWloZmdnZmZlhGcdZWZmZWNkY2JgXl9fX2BdX19eX15cW1tbXFxaW1uFXIBdXV1cW11cXV5evGC+wL/Cwb/BwcHCxMPHwsTExMPHx8bLy8vNycvMzM7Pz9HT1dTW2tva3N7e3t3k5ebr6u3t7Ovs6+978/T09318fX9/f4CBg4SEhYXvpHl1g2x0WrGoqaB4V4vSjZCOjY6PkZGTlJSVlZSTlJWWl5iYl5uYmYCYmpuanJuJ/YGQje/k2MGVxVCkYYZwiaKVdIfjw+yHldiiqaioqKmqqqqpqauqra2srK2trayrrKusq6GPxYmNpKWJpKOjpaOjpqWho5+dmpmYl5SUkY+Qjo6NjIuFycrR3tPheHx8e3p4d3Z0c3NxcXHc293a2NnW0svKyca4qoCVe5xxkZp6mbKysrG1raysq6uppKOkoKCfnJ2cmZSUkZCOjYuLh4SDg4OCg4F7f31+fHp8e3dxuZNyhK7TsklviKKSrYVwwWKEV5K9vL29t7SusK6uo6iio56ZnZ6imJeUmZiWmZWamZeTlZWTk5KPj4uOiYeHiIaIhoaGgnx6e4B7f4N7f3t8d3h7dnS0gXh4enh1dnBvbHBxdnJ2c3Jzd3RydHR0d3Vvamtubm5wcG1zb3NubWlua3BsbWtwdHN2dHN3cm1vdnd1dHV3dXJ0dXZ5enp1dXp+ent7fYKHg4OHg4aHhIeQkJSUkpSQkJCRkpGRlpyZT05SUlRRUlNTUYRWgFNVVldZWFtbXV5fW2JiYGJgZGdlZWZowL1pZmxuamptb3BvdHd2end6dnl7d3p4eXd5fHuBf4F/goODhIWCgYWChIOEhISChoSEhoaFgoGBgYOBgoKDgH57enl9fn1+f4CBf317e3p6eXh4fXh6eHp2d3dzdXFucXNydHJycG5yUW5rbWtubmxpamppamhpaWtqa2poaWhpbWlqamhoZmZpa2twcm5yeHBwcG1tbW5wcXBwb3Bxc3V2dXd6eXp5eXp7e3qBgIGAgIKFhYeGjIeHiYx//4D/gKuAAn+AuX8BgIR/jYAIf39/fn19fX6GfQJ+f6CAGn+AgIB/f39+fn18fHt6e3p6ent7fX5+f39/nICCf5+Ahn+OgJJ/A359frB/hn4NfXt7e31+fn19fXt7fP9+rX6lfwGA/3+6fwICBABE+fv6/4D/gP2AgYSDhYmIi4qMjIyOjY6OkpCSlJaWmJmam52foKGkpaWlpqanp6mqqKqrrrG0s7KzsrGxsa+xs7K0t7eEtQyzs7O0srKxsq+xr6+FrgSwr6+vhK0hrKurqaqrqampq6mrqq6trrCsrK2trq2trayrrayqrKyphKsWrK2trK6vrq2tq6qrqqysr66wsK+yr4Wud6+wsbGytLS3tre2t7e4t7a0tLi5uLe4ubi4t7SysbGxsK6ura2urq2urK2qq6mqq6msqqiopqWlo6Ojp6alpKSkpqanqaupr6+wr7GxsrO2uLu9vr/AwMHAv8C9vLq3trSysK6trKypqKakpaSjo6GgoKOjpKSmhKWAo6KgoKCfn5+empmXlJGSkJCOjYqIhoWCgoGBgYCAgfn4+PTw8fLx8Ozp6efo5ubk5Ofo4+Xp6Onp6u3s6+ro6uzr6+zq7fHw9PT19fX39/z49vuAg39/fn9/goSEhoSGh4eGiIeH64WcsruOl+vTmqb89/nefczaw9yEiouKjY8Mjo2Mi42Njo6MjIuMhItxjIyMjYrFsdma6quTfKuQyOOH6N/U2c6Aj+B8rc+8gcGCkJGSj5CPkJGQkJKSlJSVlZaXlpaXl5iXhIy7zbGLmJh/mJiWlpOVlpCOkJSRkYzg74aHj4yKj4+Niop6r9eKk6vAmbbxgoaEgoKCg4SDhYSEgYCA//v69vPs5pznhZ60wt/h/Z3b3uDg3NXV1dTRz8/Ly8jGxsTAvL25ubi2sq+qp6iinqCgo52XlpKRkZKRjpCK7pme15jtl9mU/8TWh4HY/Nn2nL/C0s7M0dLR0crLx8PEwMDAu7m2tbq3trazsLCysbOzq6epp6qioqemo6WgooCppKGtp6Sno5yel5SclpmWmpqamZqanJyampucmJqZlJmWlpWWlZmanJWemqCeqJ2dmZudnaOiqqmnqaispqWgpKOnpauuqaqppqWmpauoq7CysKyvtLm8u7y7ure3s7q4wsDDyczJz9bW3dXa2d7h5d/j6ujm5+vq7vb89vyBhS2FhIaChIeKio2Rl5eYnJuhmp2enKCjqKqpqqqpqquts7S1ubvBxMXFxsnV0caEzDvP0tbl197f3Nzb3eHj6Onn5+zs5+vr7u/u5+jo6+nq6enn6uPk5Ofk4ePf497X2dfW1tjY1s/O0dPR0oTOb8zMysnIyMXFxsHBwb27u726usC9vb/Avr66vL28u7u8vcC7uru3u7q8vr+9vry7u7m1uLW1u7u8v8C/u7y9u7zBw7/BwsHCw8C+vb3CwcjKx87Mzs/OzszQ0tXW2dnb2tzi4+Xh5ubq6+/w9PLy9ES+v73BYsJiw2JkZmVlZ2ZoZ2hpaWtrbG1vbnBwcnN0dHV1d3h3eHl6eXt9fn9+gIF/gIGChYeGhYeHh4mJiImJiImLi4WKFYuLjIuLi4yMjYuLiomJiImKioyMi4eND46Qj5CQkpGSkZSTlpqWloSYEpeXl5iZmZqbm5qcnJ2cnZ6enIedBZ6en56fhJ0Bn4mdTpycnZ2fn5+en5+gn52cnJ2dnZubnJuamZiXlpaXlpWUkpGQj4+Pjo6NjYuLjIqLioqLiYqKh4eHiIeHhoaHh4eGhoWDhoeHhoiHiYmKjISNhIwaiouKiYmIh4aEgoCAf39+fXx7fHx6enl5eXyEex16enp7enp5eHp6eXl4dXZ2dXN0c3NycXBwb3Bub4VubXLb3d/c2t3e3t7c3N7d39/f3tzf4Nzf4uLj4uLl5OTl5efq7Ozr7O7w8fTy9PXz9ff7+fr+gYaBgYGCgoSGhYeGhoiJiIqKie5zfoqjlJTezJOW4NrZ0YXL3cvihoyLjI6Ojo2NjY6Njo+NjYyEjgGNhI6Aire0+Kn3u5+Ap5XV5mOnl46dmnaK5o7F8dmO1oiTlJaUlpWWlpWVlpaYmJmYmZmYmJiXmJeCmMLPsIeUlH6Uk5KRj5GSjouMjoyLhdDdfn+Fgn+Eg4F+f3Wbw4adq76Qn9ZydXRycHBwb29wb21tbGxq09DPzczJx4j+jZ6+wc6A1e19s7a4ubawsrGxrqyrqaqmpaaioJ2dm5uamZeUkY6Qi4iIh4mFg4OBgIF/fnp7d82BitKV45jUovm70YuD2cq1wn6qsry3s7O1tLOrrq+qq6qoqKWhnp2hnZ+em5mZm5ycmZORlJKUjo6SkY+QjpCSjYiNiIaKioiKhYaMhYeAhIWEhISCgoOAfoCCgn+Cgn6DgoGAgH+BgYN+hH+CgIeAgICBgYGEgoaEgoOCh4SGhYiIiYeNjYmKiIWEhYSIh4uMj42JjI6RkZGTlJaVlpOXlZ2anaKhnqCkpaqlqqirrbCrrrS0tLe5uLzBxr7AYmRkZGdlZ2lramtudXJzdXV0eXR3eHZ4e31/fn+BgYKEh4yLjI2MkZKTlJSZo6CYnZudnZ6goq+ipaWioaKkpqmuq6ussa+sr7CztraxsrS2tLWzs7Cyrq+xtbKwsK6yrairqqmqq6qqpaWmp6WloqGhoqGhn5+enpqbnJiZmZeXl5iVlZeEkliRkpCRkpGRkZKTlZGQkI2Pjo6Qj46PjY6PkI+SkJCTkpGRkpGPkZKPj5KUkJCRkJKUlZSVlpqXnJyZnZudn56fnqGipKWmp6mpq7Cwsa+xsLKztbe6ubm7doaHh4pGi0aKRkdJSElLS01MTU1NTk1NTU9NTk9QUVJTVFVWV1ZXWFlZWlxdXl5fYF5eX2FjZWVkZWVlZmZlZ2doaWtraWhpamlpaWpra2ttbG1sbG1ub3BxcnJzcnJzdHV1dnZ1dnd3eHh6eXt6fn6BhIKChISEhSKGh4mKiouMi42MjYyNj4+OkJKSkpOSkZGRkpGTkZKSkJSRhpCEjwWQj5CQj4SOD42Ni4uMi4mFhoaFhIOCgYSAgn+Efhh9fn5/f39+fn98fn17e3p6enh3dnh2dXSEcxxycXBvcXBwbm5ub25vcHBwbm5tbGxramxqampoiGkYaGdmZmdmZWRiYWBiYWFgYF9fXl5dXVxchF0HXFtcXF1cXoRdbltcXF1dYGBhYWJjZcPFx8XDxcfIysfIycjLy8zMzM/Szc/S0tLR0dTT1NbW2t/g4eHh4+Xl5+jq7Ozw8fTx7/J7fnx8fH19f4GBgoKEhoeHiIiG6GM8LT5JbMK0dnOflZCWe8/gzOOJkZGSlpeXhJYGl5mbmZqahZuAnZ2dnpnFrdCb86aKcYptf4dRjYF8h39gbqljiaWqjeWbrKyuq6yrra2sra6usbCxsbKysLCwr6+tk6LT38CYqKiKqKempaSmqKKfoaOfnpft+YyOk46Kjo6Ni4uAnq58kJGfmbPhenx7d3Z1c3NxcnFvbW1ta9XT0c7Nx8OBoVGAXnJtf42mYquvsbOxq66vr6yqp6Ojn6Cin5yYmJOUkpKPjYuIioaDhIWIhIGBfn5+fXx5e3bMe3ibdcyNuXW7jqlwdMyKintTj7G/uri7ubi3sbKtqKiko6SfnJqZnJeWmJaVmJiYmZiSkJOTlo+NjoyHh4WGjIiDioWBhYN+gHsWeoB5fXh6eXx7fHx9e3Z1dHVwcXFrboRse21wcnNvdnJ3dXx0cm5samptbHRybXBscG9vbHFvcG5zdHJzcnByc3J3dHZ1eHVxc3R1dHNzdXd3eHZ6eIF8foaDgIKGhYiChoSJjJCKjY+NjY6MjpCTl5SWTk9OTE5KTFBRUVNUV1ZWWFhdWFpbWVtcX19eX2FgYWNjZ4RmO2tsbW1sb3Zxam9ucHJzdXZ9d3t7eXl6fH1+gX9/gISDgIOEh4qHgYKDhoaIiImHioeHiYmIhYaEh4SChIZ8h4eEgH+AgoGBgICAgYCAfn59e3h5end4eXh4d3dzcnNxcXNzc3RydHRycXBxcXFvbm5qbGlqa2ppamhoaWpoa2lpbWppamppaGlramtvc3BxcnFyc3BtbGtub3N0cXVydnh3eHd5eXx7fX1+fX6AgIF/g4SGhoiHiIaEhYR/BIB/gH//gP+AtYC0f5OABn9/fXx8fYR+BX18fHx9hX+bgAN/fn2FfgV9fXx7e4V6CXt7ent7e3x/f5qAhH+SgIJ/i4ABfoV9A35/f5CAiH8JfHx7fHx7enp+rn8Ufn59fH19fn18enp6e318ent6e3z/fqt+/3/ifwICBAAC/P+EgICBg4OEhIaHi4uOjo+Pj5KRkpKSlpeYmZqZmZqcnp6foqOnqKaoqqqrrLCwsbGytbS2tra1tbS1tra4uri6urm3uLu6urq3tra1tbOzs7GxsrSztLKysrGyr6+ur6+vrq6vra+urqytr6+wsK+vsbGvsbCxsbKxsrCxsLGvsLCvr4SwhbEMsrCysbGys7S1trK0hLIQs7S0tbS0tbe4uLm4ubq7uYS7hr0NvLy6uba2tLKytLOxsYSwCq2urq6tr6+urqyFqYSogKmnpKamqKisra+vsrOztbW2t7i7vr7CxMbHxsXFw8DBwb27ubm2sLCur62tq6mqq6qnp6ampaaop6epqaqrqKqkpaOkpKSioZ+dm5iXlZKTkpCNi4mIhYaDg4OBgYD6+/n49vLx8vvw7urs7uvr6ens7Ojo6ejm7Ors7Ozu7PDwQ+3u8vDy9fj49vn8/v3+gICBiYKChIOCg4SEhYaJiIiLi4qMjYrkvMzGps+u+LnJkrup3PfShK+fvceLjpCSkpGQj4+EkQKSkISPJ46PkJGQk5F9xHbUo5q2iIXOlqLO15eU2seEgNDqy8PGr2+NkpKSk4SSQJSVl5aYl5aXmZqYmpqYmJeBiKuBiHeQmoGLmZqZl5iQvoPHyqKAmNjnxPSHs4vPsqDnvs2v19CB65i9v572goWEhGCFhIWFhYSCgYOBgID/+fTttYD0tLTIqJCz/8vK4+De29nU19TT0tDRzszIysXBx8G8t7Syta6rqaqppKKhn56bmJORkY+Pi4XhnrHD1YK7v9TVubGlpqOy3c6nv9LRzsqFy2rQy8zJxMbAwru8uby/u7q3rrC0r6quqqqnqqqsp6qmpKahoaCepKWko6OhmZ2VlJSUlZGYm5yYm5uhmZ6bmp2am5+cnJqYl5WVkpaWl5mcnqGdop+boKCfoqelpaejp6mmp6GgoaqmqKqqhKiAq6qur62xsLOwsbO0wLe4tbvBurm9urrCxMnO19DQ0NnU1NbZ2dze4+bm6err8fCB//2Bg4SHiIiNho6LkZKVlZiYmZuanJueoKKmp6mqrq6rrK2xtLe/xcPAycrO0dbW2NbV19rV39fb3N7m4Ofn5ejm6Onq9/Tw8PP59PX28e+A8Ozv6uvp7e/x6+vq7ero6ebi4eTg4eLe39nV3Nna2tnb2dfT09HR0MzK0M7Ly8jFyMTDwcDBvcPCw7/BwsDBwsHBvr+/v76+wsHAv8C/wsPAwL29v725vbq7vMDCxMTEw8PEx8bFxcTHxsfIx8XExMHCxsrNz9TP0tLX1dbW1dkU2t3d4d/f4enp6u7u8vPz+Pb49vocwsJhYWBgYWNkZWZmZmhoaWlqa2ttbm5vb3Jzc4R1J3Z3eXl4eXl7fHt+gICAgYSDg4OFh4aIiImJiYqKi4uLjYuNjI2MjISOAo2MhI0Cj46EjRKMjYyNjo6Qj4+PkJCPj4+RkZKEkxmVlpeXmJiampmbmpudnZ2fnZ6dn56goJ+ghaEIoqKjo6Sio6OFogKjoIShCqChoaCgn5+foaGEogmjpKKjoaGgoaCEn1WenZycmZqZmJeZl5WUk5KSkY+QkJCPj5CPj46MjIyNjIqKiYmJiIaHiIiIiYmJiIiJiImJioqKjI6Njo+QkJCPj46NjY2LiYiIiIODgYKAgYB+f39+hHwDe3x9hHwjfX18fnp8e3x8e3l5eHh3dnZ1c3R0cnFxcXJxcnFwb25ub9yE3zDe3uDq4ODd4OHh4eDg4uPg4ePj4+jm6ufn6unt7u/u8PDx9fn69/r8/Pv8f3+BjIWEhCaFh4eIiYqKiYyMjI2PjOOkraqPu6z6uMWJpZjT6M6IuazFyoqNjoSQgI+QkZCQkJGRkJGSkpCQkZGQkpGAzZXluK/Pj534uZeTk2ptmY5wc8nlzMHMt3qSl5eYmpuampmam5yam5ybm5ucm5ubmpqZgZKvgoh0jpeAiJWVlJKTiruLzs6ve5bC0q/aeZt7wKKT1622iKmSUpFroLSe2HJ1dHRzcXBvcHBwgG9tbG1qaGnS0c/Mm3TclI6Zd15567aru7m5traztbGwrqytqaelpqOfpaCdmpeWl5KQjpCPiYeFg4SDgoGAgX5/enjLi5KftG64yN6+pZ+UwsGXuK2Uqr68ubOztLKyra6qq6ioqqmqoqSioqKfoJ2Zmp+bl5iWl5OUlJWQkpCRJJSSk4+MjoyLjIyLh4qGhoeHhoKEg4N+gYCGfoKAgIOBg4eFhISDIoF/gIGBgoOCg4CDgX+CgoKEh4aDhoGDhYSHhYWGi4aIiImEh4CJiImKiIuNkY2Oi4uWkZSTmp6WmJmXmJqZnZ+noaKlqqirq62usK+0trW4vLq/vWXGw2NkZWhoZ2pkaWhtbXBxdHN0dnV3dnh5en1+gICFhYOEhYmLjZKWko6WlZmaoKGioaCipqOto6ampqqlqampq6qtrq+5tbGxs7m1uLm4t4C5t7m2trW3t7i2tri7u7e1s7Cvs7Gys7GxrKqvrK2rqKqpp6SlpKWjnp2hoJ6dnZygn5+cmpqWmJaXlJaWlJWVk5ORk5KTk5KUk5GPkJCSk5CRj4+RkY+TkZKSlZWWlZWUlJSWlZSVlJWTlJWXl5mamZianJ2doJuen6OipKWlqBSoqautrK2us7KytLS2t7e7u727vwKJi4RGD0dISEhJSUlLS0xMTU1NUIRPA1BRUYRSFlNVV1ZWWFhaW1tcXl5fX2JiY2NjZmSHZnNnZ2lramtsbGprbGtra2prbGxtbG5ubW9wcnJzcnN0dHV0dXZ3eHh4eXp6fH19fH1+f4GBgYOFhoWHhoeIioqMi4yMjo6QkJCRkZKSkpOVlZaWl5WWlZSUlJOUlZGTk5KTkpOTkpKSkZKTk5OSkJCRkY+QhI83jo2Li4mHhoWEgoOCgoGEg4GBgICAgYCBgYKAgIB+fX57fHx9fXx9e3p6eHR0c3Jxc3NzcnNycYVyCnN0cnNycXBvb2+EbUtraWlrbGpsa2xqa2lnaGlnZmZkZWNjY2JgYF9gYWBiX19eXl5dXFxcXV5dXl5dX19eXV1cX19iYWJjYmNjw8bGxsXEw8bOycrIy86Ez4DQ08/R09PT2dfb2Njb2uDh4eLl5ebp7O3r7/Pz9PV7e3yJfn1/f3+AgYKEhIWGhomKioyNit6CUUtLWXDCm6Jxf3Kps5dvnoipwI6RlJeXl5iZmZqam5ydnZ2en5+en6CgoKKgiL5rvaKUmXF6nXB1gYNbZIRwW1iOqZGMoLiEqICur6+ysrGxsLGys7O0tLOztLWzs7OxsrCRoMKNlYCdqY6ZqKqpp6ig1pHh3qyInLHNwPqKq3rCp5fKtMyGmpdPfmGiqJ3qenx5eHdzc3JycXFvbW1ubGpq0szHwI5Wq2xqeFxGXpGan7GwsbCwrq+sqqejpaKgn6Gdmp+bmJOSkYCSjo2Mjo6JiIeFhYSBf35/fHx2crt4fYGMUoWboJaBgXuzs3aWh5Srv7+6tLWzsrKwsqyrpqKmpaahop2dnJeZlpGWmpaTk5CRjZCQkYyPjYyOiYqHhYeGhYOCgHmAent8fHx4ent+d3t5fXR4dXJ0b29ybWxra2tsbWpub3Bxc0d0dXJ0cm9wcHBxc3RxcW1vcnN0cXFwdnJzc3Ryb29vcHF0dnJ1dHh0c3JxeHJ0cnt/eXt8eXt9en6BiIKBgYWCg4OGhomKjYSPgI2QjU6Yl01NTk9OT1FMU1FVVldWV1VWV1ZYV1pbXF9fYF9jYmBgYWNlZ2tta2ltbnBwdHNzc3JzeHSCeHp7e396fn19gH2AgICIhoSDhoyIioyKiYqIiYaIiIuMjouLjI6Ni4uJhoaJiIqMioqFgoeDhIWDg4OCf4GChIJ/fYB+PHt7enh8ent4dnZydXR0cnR0c3V1dHNxcXBwbm1vb2xsbGtubmtsamptbm1xb25ubW1ubW1tbG5xcXBxcIVxJ25vcG5vcnR1dXl0dnd6eXt7en1+f3+Bfn5+g4KDhYaHh4eLiImGiIJ//4D/gLuAsH+XgAt/fnx8fXx9fX5+foV9hH4Bf5uAE359fX5+fX19fHx7enp7e3p6e3uEegJ7fpuAgn+NgIV/B4B/fX5/f4CFfw1+f399e3p6eXp7fX9/koCFfwF9h3oCe36tfwl+fn18e3t7fXuEegV8fHp7fP9+qX4Df35+/3/jfwICBAAB/YSBZYKCgoSFiYqJjIyOkI+RkZSVlZaXmJqbnJycm5+foqKkp6iqq6ysr6+wsbGztLW2t7e5ure5urm6u7y/vb++vby9vb6/vb29u7u5uri3uLm4ube5uLi2tba1tba0tbWysrOzsrGyhLE0srKys7O1tbS1t7W0t7a2t7m2tbSxsrK0tLW1trW2tre1trW2tLa4uLm6ube2trW0tri5uIS5bby+vLy9vr7Avr6/vr/CwcLCwcC/v8C7urm4t7e3trW0s7O0srKysbKys7C0ra+zqqqsqKmqqautrayrra6ssbCysrS1t7i5u7u+wcXGx8rNztHP0M3JyMXDwL65uLKxsLGvra+ur6+trqysqquErYCurbCvrq+qqqmpqqmqpqSfnpybmJSUkpKPjoqJioeGhYSCgYD++vz7+/z7+/jz7+7v7/Dx8PDw7fDq6uvt7O7v8vH08/b18vb09Pf4+Pz9/fqAgH+Bg4OEh4mJiomLioiKi4yMjY6Ojo+QkIaPnre2kcj/ocbgiPuR1LaQ6JSZhwer38WLlpWUhZIGlJSUk5KShJMjlJORkH+Eu6qajKH85NV1/dLa2I7cwbTMwMrm+pW1lI2Xl5WElgWZmZmamYSagJybnZ+YhoqXi9Pykae/xZfLo7eWmpyafn2vdb2wu4WDgPWQ3IOu4Yfmm7b71cun9tD7m5GUi5LThoeGiIeFg4SG+a3mhISFgf/7+POttOyjjsiOpcqT6pzh4uHe3dvY1tbV1NTPzsvKx8XEwL25trWzsrGsqKqsoqCdmZqYlZORgI+Ph4Tbp5qLvOzRmfm9urPOxcGLiNn9jY3E1M7Q0dDNz83Kzs3Jxc3HwLu+u7e9sa+urruxr6qpqKauqaapp6Okpq6loKOjoqiioqCenpefl5mZnJyanaChmpucn5yZmZibnpqbmZeWl5eVlJibm52kqqeksLyfpKOio6KipKmugKuqpqekpKOko6irpq2zrquqrLGrsq2st7y+vby5ubu9v767vsq+ysXPz8/UzcfN1djU1drc5Obk6u/z6/Hy//yDg4aHh4WMjY+Tk5WVmJ6anJ6hn5+eoaOoq62ur7Ozt7W2u7vBw8LFxsfLzNDP1drf2d3d3Nrd4OTn7Ofu6/DzgPX27fL19vr8/Pv9/vz48vPy7u3v7fD1+PPy8O/r6erq7ezp6+zo6ejh5OLj5Obi4uLh3NnZ19jW0dTU0dTTzs3KyMjIy8bIxcfHxcfGycfIxsHDxcLEyMXFxMbEw8TFxsnDxcLAvL3AwMO/wMTIx8rIx8jHxsjKyc3NzczJyMbEI8fNzs/MztLT1tjZ2drd3Nzg4+Hj6Ofp6Ons8PP29vr6+/v9WMFiYWFhYmJiZGVoaGdoaGpramxsbm9wcHFzc3V2dnd1eHh7ent9fX9/gYGDg4OEg4SFhoeIiYqKioyNjY6Oj5CPkJCQj5CPj5GPkJGPkJCSkZGQkZCRkJGEkE2RkZGSkZKRj5CTlJSUlZWWlpeZmJmcnJ2dnJ2fn5+hoaGipKSkpaOlpaalpqempaenqKipqKimp6emp6ampaWmpaSkpqakpqWlpaaopYamgKWlo6KjoqOjo6Khn6CdnZ2bmpqamZiXlZWWlJOTkpOSkpGVj5GYj46QjIyMi4uMjIqJiYqJjIqLiouLjIyNjY2OjpCQj5CSkpSTk5KQkI6OjYuIiYWEhISDgYKBgoJ/gH9/f35/f35/f36AgH+Af39+fX59fHp6eHl5eXh1dXV0QXRzcXFzcXFwcG9vb9/e397f4eLk5uLi4+Ti5Obj4+Ti5uHi5Ofo6+3u7fDv8vPx9PPz9/j6/v///ICAgIGDhIOFhIcqiYuKi42OjY6Pj5CQkpGIh4yZnHy6/Z7B2IDwi8J8W5Z0ho+o4cyJkpKThZIxlJOUlJSTlJSUk5SUk5SEgrqroZ+u8vn0h9KUqtttn4qJpKSwyeKMqKOPmJmZmpubnISegJ2dnZ6enZ2en5eIjJiN1PeWsLnOl8/AwpOWl5VycsaAwMeygm9t/LHXfoy8ZLd8reK7k2uIa4tkcm54d7x4dnRzcXBubnDQl7xsa2xq09LRzpGrvJaBqnp9mHnLg7u8u7i4trSxsq+vr6yqqailoqGenJmYlpWTko2KjY+JiYeEgIWDgX9/fn55eMWWhXys0baS3KCdmLa/zYRvveSCgbK/t7a3tbKxraqsqaiksqynpKakoaagnp6dqp6dmJeYlZmVkpSTjpCRlpGLj46Mko2NjI2Lh5CKiYqLh4WGh4eCgoKFhIKDg4aHhIODgoGChIKAhISCgoOEgn6Gk36Dg4SEgIOCg4SIhoaGioeJhoWEhYaCh42Kh4eHioaOioePkJGQkJOWl5qamJiXn5adl56goaejn6KqramorK6zs7S7wcW/wsLHxGRlZmdnZWlpamxtb29xdnN0dnl4eXl7e36AgoKChoaJiImNjpSWlJWXlpianZyhpKeipqampaepqqusDKerqK6ytbq0t7i2uYS4T7y7u7m9vry8vLq6vL26uru8u7m4uLm3tLa3tbi4srOwsLGxrrCwr6ypqaeopaGjo6Kjo6ChoKChoKSdnZqampmamZuYl5aSlZWUlZeVk5GEkkWUlJaTlZSUk5SYlpiUlJaYlpmXl5mXl5iXlZeXl5mZm5uam5+fn52eoaKjpKSkpqioqauurK2ysrSytLS3uLi4u7y/wMEBiIRGFkdHR0lJTExMTUxNTkxOT1BRUlJSU1OEVEpTVlZYWFlaWltcXFxeXl9gYGJkZGVlZWdnZmhqaGlqamxrbW1ubW5tbG5tbm9ubm5vb29xcnN1dHV0dHN0dXV2eXl7fHt8fX5/foSAQ4GCgoOFhoiJiImMi4mNjYyPkZCRkpCSk5WVl5iYmJqampmamZqXmJiXl5eWlZWWlZWVlpaUlpaVlZaYlZaVlJOTkpGEkneQkI6Ni4mIiIaGh4WFhoeGhYSDg4SCg4OCg4KCgYJ9gIR+f4F+fn58fH18eXd3dnR2c3NycXFxcHBxcXJzdHRzc3RzdHJycW9vbm1sbGpramprbGtqa2pra2hpaGdlZGRjYmJhYGJiYWNhYF9eXl1dXF1dXl5gYIReCF1eXF9iYmRkhGM1xsXIyMjKy83PzMzNz9DS1dXV19fb1tbY2trb3Nza3d3h4uHm5ebp6Oru7/Hvenx7fX9/f4GEgyeFhoOFhoeIiYqLi4uNjYR4PDk6K1eSZ4WfZMV6nGBZlmd2d6bizI+GmzmcnJ2en6CgoKGhoqGioqCgi4CzoJJyiufXqmCwiaHNZ41sYHZwe5awcbC4prGysbKzsrO1tbW2traFt3y2t7CcobGg5PqZsrXaqOCyyaapqaeFhbR2sqO6cGBgy5XRdYuyW6d9peKzlnWddpdnbGqDg8F5eHd1dHJvcHHOiLtsbGxp0c/Lx4d5qoFqimlnf2OlebKztLGysa6rq6impqKhoKCdnJyYl5STkpKSkY2KjI+Ih4WDhIKBhICAe3e4gXhtj6OOcLGDf3uaqrxtaaTVfHiuvba1tLKytbGsrKqno7Krp6KhnZeak5GOk56XmpaWl5OYk4+Rj4qNjZiMiIqHhouDgoGAf3qCenl5e3l2eHh5c3R1eHVzc3FzdXFwb21sbW5sa25wbW1wc3Btd4FrcXJyc3NwcHFycHKAcHJxb25tbG5wbHB2cm5vcXVxdnNweXp7eHV0dXR5enp7e4B5gXqBgoGEgHl9hYeDhIaHi4qIjI6Qh4uKko9LTExOTUpPT1JUVVZVVVlVVlhaWFpZWlteXmFhYWVlZ2VlZ2Zqa2lqbGttbm9tcXN2cXR2dXV3eXt8f3t/fYGEhoc9gYSGhYmIiYqLjYuJhYeJiIqNjI2QkpCQj4+MioqLjYuKjY6Mj42HiIeGhomGhoeIhYSFhISDf39+fX5/fYR+YHyCeXh1dnR0dXR2dXV0cHFxb25vbWxrbWxra2xsb2xubm5tbnBvcWtsbnBvcW5vcG9wcXFxc3N0dXNzc3FydnZ2c3Z3eHl6e3p7fHt7foB/gIOCg4GCgoWGh4aIh4iIiQF//4D/gLyArX+bgAJ/fYV8EH19fX59fn18e3p7e31/f3+YgA9/fn5+fX19fHt8enp6e3uIegN7fH+agAp/fn9/fX+Af31/hYAgfnx9fX1/fn18fH59fX18fHt7fHx7enp5eXl6e31/f3+JgIN/hICFfwp8enx9fXt6ent7rX+EfgR9enp8hXoGe3t7fnx9/36qfv9/5H8CAgQAL4CCgoOEhIODhomJi4yPj5GSkpSVl5iam5yeoKGfoKGeoKOlp6eqqq6vsbGzs7W1hLgLu7u7vr2/v7/AwMOExQ/EwsHCw8PFwsPBwMC+vb2Evii9wry9u7q5ubm2ubq6urm4t7m4t7i3tra3uLi5uLe5ubq5vLm5urm6hLhLt7e5uLm4ube5urq6vLu7vLu8ur2+v729vLq6vLi4ury8vLu7vr7Avr/BwMHCwsPExcbFxMXExsTDxMPDwsC+vLu6ubi4uLm4tra0hLM3tLKysLCvrKysra2urq6wr7CwsbCys7WztLi4ur2/wMLEyMvN0NHS19XS0c/Ny8jGxMC6t7e1tIazELSxsLKurrO0tbW0tbe1s7WEsoCxs7CsrKeloqCcm5eXkpGQjYqIh4eFhYOCgYH+gP7+/vv5+vTz8PHx8/Hw8vDx8PDx8PLx9fL29/n4+fj4+/yA//2AgoGBgoSDhISFiImLjI2MjY2Pj5CQk5KRkpSSkZSUkMa55eq/lOCSs4nemLyC/+zOq6XJodjvlZmYl5eXmAGXhJYDl5aWhJeAmJaWlX7L1bLRvaznx3mM/NrLioaNmZvU2eyy8o6Bn56LmJycm5ybm5ycnZ2fnp6goJ+goI6+dtnarHO4tMbHh+mbsLK/jdeksano6qWKhpz/7uPP4ry6386klNqxvKmR9/uGkbKpy6DqiIqKhs7E7+rNr7qC+6u74P7++fSOpvSAnMCinLSgg+CH2ODe29jY29vU09PR0M7ExMG9vLq5u7izs7KurKysqa2joJ+clpmTko+Gv5iUrIm0ra+JybarqdSa3PWklPJ62K/fm9TT1tvV1NPRzsnBxMHBvL+5ubi1tbOwtbawr7CsrKenpqenpKelwqajpJ+en5qcm56hlZuAnZqam5ugn6KhqKOmpaGem5ybmJmemJicmJaTlZabnqWoq6qnqqmoqKOmoqGjpaitrbKwp6SlpqikrbKzraywrrGrscKzrauxtbG4urK5u7vBwr/CxcnQ0NLZ0dXU1NTb3dng5uro5+j2/fXx+PqBgYWHiISGiouNj5GUl5ycnJqAnKGho6Gkpqepq66zs7W2u8C/wsfNz8zM0NHS1dzW4ePn5Obn6Ojl6ezw9PP39vb2/PX5/Pz+gICBgICBgPv++vP08fb2+vr7+Pn9/Pj08vX29fT09/Lv7+zr6+no6+vp5eji4dzZ293Y29na3dvX1NHU0tPU0M/MzMrLys3Nz9BT0szLysrLy9HQzdDMzc7Kx8zLzsnNx8fFxMXIyMjKzc/NydDJ0M3P0dHS0tLNzMjNy8rNzNHR0dLW3Nvc3eHk4eXk4eTs6Ozq7fHz8/j2+f3+//5FYWJiYmRjZGRmZ2hpaWtqbGxsbW1ub3BwcnR1d3d4eXd4e3x9fH59gIGEhIWFhoeIiImJiouKjIyOj4+Qj5GSk5STk5KShJMHkpSTlJSUk4WUJZOXlJSUk5OUk5GUlZaVlJSUlpeXmJiYmZqcnJ6enaCgoaCloqOGpRempqepqKqpqqipqqurrayrrKurqqysrISqBKutqKiEqgmpqaqpqqmpqqmEqlWpqamopqelp6WjoqGgn5+gn52cm5qampmZl5iXlZWVlJSTk5KTk5CRkJCPj46MjYyMjI2NjY6PjY6Pj4+QkJCRkZOTk5SUlJmXlZWUkpGQj46MiYiHhoaAhYWEgoGDgH+Dg4OEgoOFhIKDgoGAgH+Afnx9fHt7e3l5d3d1dnZ1c3NycnFxcG9wcOBx4OHj4uTn5Obk5ubp6Obo5ufm5+fo7Ozy8PLy9PT19Pb4+X38/H+BgYGCg4KDg4WHh4iJiYmKi46OkJGSkZGTlZOUlpeTu6C4vqCH3oofroDFiq1vw5WEaG6gmsntk5aWlZWVlpaWl5aXmJiYmYeYJYPF16bZxKjE0IOCx6CRb21xe3+5yNao4X6FraKMmJ2fn6GhoaKFoYCgoaGgoJ+Nv3TU77WAvMDPxofqotC7wI7UmqiOsLqednB+sJaNe5pxc52Qb2Cnmn1lSm9xRlZ5i62Nz3Z2dXGupsnIuKucbNOQnrvW1tPIcoDCe5edqMSjfeJ2tr27ubi3uLiysLGwr6+nqKWioJ+dnpqVlJSRj46Oi5GJiIiGgICDf39+ebCLnK+EoJGPeqiSiIa1hcLcnY75geenz4y8ubm+tLCvrq6sp6uop6Ojnp+gnp6enJ+gm5qalpaRkJGSko6SkLKUkJSSk5WRkY+QkYWKi4eHiIaHhoeDiISEg4OEhIeGgoODf4CCgoOBgoKDgYWFhISDhYWEhYKGhIODhICDhoWJioaFh4eGgoWGiIWEioqLh4qej4mHjI2Lk5SPlpibnZqZl5mcn56epJ+kpaamrK6qrLG2tLS3xMrEwcTEY2NmZ2lnaGtrbG1tb3J2dXZzdHh5enl9fn+BgYSIiImJjZCPkpWam5iYm5ucn6Ofp6epp6mqrKyprK2wsrGzsmeys7izt7m5uF1dX15fYWHBxcO/wb/CwcTDw8DAw8PAvry+vby6u727urq5uLe1tLW0s7Czr66rqKmqpqelp6mopqSkpqSjpKGgnp2dnZydnJubnJiXl5iYl5uZlZeVl5iWlZiYmpebhJcblpiYl5iZm5mVm5efmZqZmZubnpudmp6dnJ6dhKEboqemp6qsrq2vr66vtrS2tbe5u7q8u73AwcHAgERFRkZHR0hHSUpKS0xNTE1OTk9QUlNTVFRVVVZVVVdVVlhZWllaWVtcXV1fXmBhY2RlZWdnZmhoamtrbGtubm5vbm5ubW5vb3BvcXFxcnFxcXJyc3NzdnR1dXV2eHl3en19fn19fX+BgYKCg4KEhYWHh4eKi42MkI2OkZCRkZKTF5SUlpiXmZiZmJqbnJudnJudnJuZm5qZhZhrm5iYmZqZmJiXmZmZl5iYl5eWlpWVlJSTkpKQko+NjYuMi4uKiYiIh4aGh4eGhYWDgoKDgoSDgoKDgoCBgIGBf359fXt6eXh2d3Z1c3NzcnNzc3R0dHV1dXR0c3V0cnJxcXBvb29tbGxtbW6EbYBramtpaWpnZmdnZmVjY2NiYWJhYWBfXl5dXF5eX19gYGFfYF5fYF9hYWJjY2RjY2NkxWTHycvKzM/MzszQz9PT0tbW2Njc29zd3eDd3t7g4OLg4ubndenodnh4eXt9fX1+f4GBgoSEg4ODhoaIiYuLjI2PjYyPj4qpSE1RRz+ERh9iU5FcjmSdlop1d6WUuO2YnZ2enp+gn56fn6CgoaKjhaSAo6KHtYqSl4N9rKNjb7+djmpmaG9Wi5SfcrN1n87DpLO4ubi5ubi5ubq6vLu7vby8vLql0IX08Llwta++0ZX/q8a7zJniqLKLqKuOcG2CyrWolap9gqWcgHK0mpB5X4+OUVt7e4t30Xd4dnKsosbBpXSUac2Jk7TQ0Musan+5dZGAfnuLaE+saK61s7CwrrCwqaeopqWlnZ2al5aWlZeVkZGQjYuLi4iOhoWFg3+Cf359d6VyboFfd2tyZJGBcG+ccqS1kH7IXJyBt4S4tri9t7KvrKuno6imp6Oim5mXlpWTlJibmZeYlJOOjY6PkIuLibKJhoqFhId/f3+Agnd8fnuAe317fHt7d3t2d3d2dXV2dXNzdXBwcnBvbG1sbWtvb3Bwbm9ubG5tcXJzcnRyc3J2dG5sbGxrZ25ydnFwdXR3dHiFeXRwc3NvdHJvd3d5fHt6enx+goGBhX+Dg4SCh4iDiIyOi4uKlJuQi42OSUlNT09NTlBQUFJSVFdYV1ZTVVkDWlxbhV6AX2NiYmRnaWlqa25va2tubm9xdG92dXd1dnh5enh7fICCgoWEhYWJgoOFhYVFRUZFRkdHjI+Ni4uLjo6RkZKQkJOTkY6Njo6NjpCRj46Oi4uLioiKiomHi4iIhYSEhYGBfn+BgIB+f4F/fX56eHV2dXV2d3d4eHl0cnFwb25ycG4OcXBxcW5rb25xb3NxcW+GcDVxc29rcW12cXJzc3RydHJzcXR0dHZ0eHd2dnh7enp6fX9/goKBg4iDhYKDhIaFiIiKi4qJiP+A/4C+gAJ/gKJ/A4B/f6CAAn99h3yEfQJ+e4V6A3x+f5iADX58fXx7e3p7fHt6enqEe4Z6BHx+f3+VgAN/gH+EfhB9fX+Af39+fn+Af399e3p6hHuMeg17enp6eXl6enp7fH5/hICEfwR+fX+Ah38Mfnt8fn9/fHx8fXt8q38Jfn18fHx7enp7hXoKe3p6fX17e3p8ff9+pH7If4eA/3+XfwICBAAwg4KEhYWEhIeJi42OjpGSlJOXl5eYmpydnqCgoaKio6SlpqeqrK2wsbKztbe5uru9hLwIwL/CwsLFxcaFyUnKysrMy8nIx8XGxcXFxMPDw8LDxMPIwcDAwb6+v76+v76/v7++vb28vLu8vby8vLu6vLy8v729vb6/vcC/v76/vby8u7u8vr2+hL0+vr3AwMHCw8PEwsPAwL2/vr/BwsLDwsPDxMbGxcbFxMTFx8rJy8rLycrJx8fFxcfGxcLDwMG/wL+9vLy7ubeHtjW0s7GysbGwr7CzsbK0tbKzs7a1uLi6vb++wMTFxsnO0tbX2Nra29rX1tPQzcrHw767u7u4uYW4Tbm4trW1tLS1uLm6u7u/vb++vbu6t7a0r6ytrKmooqCdmJiVk5COjImJh4aFg4CA//79+vz5+Pf39O/x9fP28/T08fHw8/T19vf4+vr8hYAUgYKCg4WGh4aGiIiIiYmLjY2PkZOEkmKTk5aWlpiamZmZmpfQrYrGnuze7oGZvbzzgPvUu7Csrcm+l46ZnJubm5ycoZycm5mbmp2cnJ2cmZjehtnK7Mu0zb2npar3xMze6aiMvfWRtdTW4ZuAicl9mZ6fn5+en5+hoYSkgKalpaSZw+Ks5MewjMl46sWKvYiugaGG+uDBpZ3ly6qgsszNo6Sosbqri4WDnp6bjd77iJ22y8qjkYaNir+S16mGk9Wdx6LMyK3x/fqRisCjhMSN0oDt3O7G3Zzn5uLh4N3d2dzW2NHOycfAwcC8vLa2t7OysaynqKakpKSfnZuYgJma8NyW7pnq0Jq9vrK2q6mmrMrg8Nrp0t746ZabxO++3drX0M7IzMXAwMXBvr25u7m1tLOwr66zq6qtqKmpsK2xsrGtqKmioJucoJ+gnZqemp2dlp2fnaChoaWopZ+dmZ2WmJuemqChm5iZmp+ho6elq6ioqqurrqelpaiopaqmgKuwqqWmo6SntayytK2urq2yq6jNs62vvbi2uLi1ur6/yMO8vMDG0sfQ8dfU1NPW4OXk5evu7Orv9PT2/P+CgYSEhoqIh4iOj42SkJWYnKCgo6elpqapqKuwsrO2trm7v8DHw8rS09bT19nd4uPp6e7w7+3x8u/z8fb3+/j5+/n6Mv38gYODhYWDhoSEgoWEhYCA//r//YGChIKBgf//gP+AgIWA/fz69vHx8vH08vHz7+7thOZt5+Ph3dzh3dnc2NjZ2dvWz9LOz9PS0NfV0dHSz8vOzNDR1NnW19HSz9DO0NDNzNTLzMvLy83Hy8/Q0tHU087Mzs3R0NPU09TR0s7Mzs3Q09jZ1dfa2t7d4uPk5Ovs7vPs7e3w8/L1+f7/gYGCg1BjYmNlZWRlZmdoaWppa2xubG9vbm9wcXJzdXZ3eHl6fHx9fX+AgIKDg4WGiImKi4yNjo6OkI+QkJCSkpOUlJWUlZWVlpeXlpWVk5aWl5eXloaXBpmWlZaXlYSWHpiXmJiZmZqampubnJ2dnp+foKGio6WjpKWnqKerqoSrEKyrrK2trq6ura6vr7GvsrGEsAmxsLGwsa+wrq6Er4auTK2urqytrq6vra2rqqmpqKenpqampKOhoqCgn6Cfnp2dnZuamJiXl5eWlpSVlJWVlZOSk5KQj5CPjY2Nj46Pj5CRkpGRkpOSk5WXmJiEmSaYl5eWlJSTkZCMi4uLiYqJiomJiIiGhoWGhISDhYaGhoWHhYWEg4SCgIF/fn9/fX56enl3eHh3dnd1dHV0dHNzcnPl5eXk5+bn6erq5ujq6uzp6uro6err7e/w8vP09PZ8fX1+fn9/gICChIWEhYaHh4iJioqKi4yPj5CSk5SUlZSTlZaXmJibmcWvepyB3tfqg5m1rthtzZl8bmlwmaSdkJiamJeYmZmcOZqZmpqcm52cnJybmpjemN/B16SLmYduaW29j5uxuIhrhsJ8nq2x3aF9hs+BnaKjpKSkpaWmpaelpYSkgKOa0PK///C/jOCE9MuKzI+ndo9u0buPeHLJl2hZYHZ2UlleZm5lTkZHYFlRSGBzQ1pwgoizcnN7eKqQyqB0cbuIuZi0p5bM1tR3do+FbKF90oDovPnS1IS+v7y5uLe2srWxsq6tqKejo6OfnpeYlpKSko6LjIuKioqFg4OAgYPPgL+U7pbgw46ro5CMgX2Bi6nE1sPZyN385ZSMvOGmt7Ozra2rrqulo6aioaCcoaCdn56dnp2gmJiYk5KSlpOWl5aVkpiTkY6OkpCRjYqOi42Mh4qIhoaCg4aHhoWGhIqFhIWFgIWFgoGBgYaFhYiEhoOChIWFh4KChIaHhIeDhoqHgIaIh4aHjYaHiIWFiImNh4Sfi4uNm5OSkpKRl5maoJyZmJmcopieuaSkpqiqsLKurrO4uLnAxcTFxsVjYWRkZ2tpaWlub25wcHN0dXl3eX19fn+DgoOGhoeJioyPkpKWk5acnZ+cn6Cjp6aqqqytr62xtbO1tLe4u7i4ubm4urpfYmFgYGFfYmJiYWRkZmNjxsPGxGNkZGNjZMbIZMZjYmViwcHAv7y8vLu9u7i4tbW1srGys7WxsKysr6yoqaWlpqaopaGjoaCjoZ6joZycnp2anZ2enp+hnJyXmZiampubm5ifhJk6mpyYmp2am5qcnZubnZyenZ6fn6CeoJ+eoJ+goqWmoqSnp6urra6wr7Oztbq2t7i8vr29vcDBYWJiYzBEREZHSEhJSktLTE1MTk5PTU9PT1FSU1RUVVRVVVVWV1hYWFlaWltbXF1eYGFhY2WEZjhoZ2lpamtsbW5ub25vb25vcHBvb3BvcXJzcnJyc3R0dHV1d3R0dXd3eHp7fH59f39/gIGChIWGh4SIfYmKi4yPkI+Pj5CRkZSVlpeYmJmZmpqbnZ6fnZ+fn6CfoaCfn5+enp2dnJ2bnZycnZ6dnZucmpqbm5mbmpiZmZiZl5eWlZSUk5GQjYyNjYyKjIqKiouLiomKioiGhoaFhYaFhIKDgoOEg4KBgIB9fHx7eHd2eHd3dnd3dnV0hHWAdnh4d3Z3dnZ3dXV0cnJwb25sbG1ubm9ub25tbGxramhoZ2VlZGNjY2JkY2RjYmFgYGBfXl5fYWBhYGFiYWFiYmFjYmJjY2NkZGNkycnKyMvKy8zOzsvO0dLW1tra2tvb3N3d3t/e4N/hcnJyc3R0dXV1d3l7enx+f3+AgIGCgoRihYeHh4mJi4yOj4+QkZGRkJKPsX5MVUhlYItcdYN+nFKmh3Zycnmbd46RnKCgoKGhoaSgoKGhpaSnp6anpqSi6Yu6gp2GepCGd3V5vYyRnKF8XmKocIiOlraEg5julLW7vL2EvIC+vsDAwcDBwcG/s9zyyPjDqHukZLu4hrCEpXqfZ8C2nYF2xplxbH2ZnHN3d4GIfmZdYHlzbGKSp1xwiJWRhX5yenecYI97WWefcJWBlISEwNHNaVx4dFuPUZxmlmyNgKx4trazsa+uraqsp6mlo5+empycmpuWl5eSkZCLiImIhYCGhYGAf31/gsiyZZ5lm4Vdg4J5fW5qbXCElaGKm4KNn5F4gqTLn7i2tKuqqKyppKSnoqCemZ2ZlpiWlZaWm5SVlZGPjZGMj5KPkJCWjIqFgYKAf3x5fXl8fXZ8fHp7dnR3d3VycnJ3cXR1dnF1c29rbGxwcG5wbW9sa2psa29sboBxdHNwb2ptcm5sb2xra25sb3JwcnJzdnBth3d0c352cnRzcnl9fYR+enl6foN6fpaBfn+Cg4uNiIiMjoyLj5KQjo+OSEdKS01QT05NUlNSVFNVVldZV1ldXF1eYV9gY2JiZGRlZ2pqb2prcG9vbXFydnp5e3t7ent3enx5fXx/goCGhYeHh4aFg0JEREZGRUdGR0VHSEpHSJCOkI9JSktKSUmSkkmSSkpPS5aWlZSSkZGQkZCOkI6Oj4uKi4uMiIaBgYSBf4J/gIKAgX54end3eXh2fHp3d3h3dHRyc3FydnJzcHBubm1ub29udm9vbW1ub25wdHNzcXJxb29xcXFwcClydHV0d3RzdHN1dnp5dnd5eX18f4CCgYaFh4qFhYSGiIaHiYqMRkVFRP+A/4C+gJ5/qYAOf35+fX17e3x+fn19e3uHegJ8f5aABX9/fXx7jHoNe3t6e3x7enp7fH5/f5SAFH9+fn18fX17fHx+f31+fn9/fXt7l3qCeYV6Enx/gICAf319fn17fH5+f318foR/DX18fX5+fHx9e3p7fH2ofwV+fXx7e5N6BXt9fn19/36ffsV/j4CEf4aABH9/gH+EgPx/hIACAgQAJ4SHh4WFiIqMio6Qj5KTlZWWmZiZm56fn6KjpqampKioqamsq7CztYS3T7m9vb/BwcDAwsTEyMnKysrJysvLzc3N0NDR0M/O0M7Oy8rKysfGyMfHycjGx8jGxsTFxcLExcPDxMLDwsLCw8HDwcDBwMHBwsLCxMLDw8SEw4DCw8PExMHBwMHAwMDBwcHAwsLCw8PFyMTFxsbHxcXFxsfGxsXGxsfHyMrKy8vMzMzJy8vNztHR0dLQzczKycjIyMfIxMXDxcPAv7+9vbu6u7m5t7i6uLW1tbe2tre2t7a2tLS0sKW4vL2+v8LDxMbIy83O09jb3+Dh4eHe3drW04DRzcnJxMLBwL6+vb28vL7Avr28u7u7vL6+v8HExsfHxsTBwb+9ubS0s7KuqqajnpybmZaVkZCOjIqIiIeGgoCAgfz9+fv49fj39fr4+fb4+PT4+fr6+Pj6+YeFgIGBgoOChISFhoiKiouMi42MjZCRkpOUlZaXmZqbmJiampycnCGdnZ+chPLasrT/qtDUv5COkIvu0KCYmLCzzpqcf4+gn5+EoIChoaCgn6Gfn56fnZqIhKa/+Ya+5tSciYy1joWN+uXTz87X9NGznNjriJm2dpCfpKKioqejpaanp6epqKenpLx9mai3dIZ+qIjzipOgmpmkxr7KybGUku2guKm84M6ilomip5uUhoGemZKJgIqBn/2HpJLLg5KQ4sbRpt7wyo7tiYCes5SUk+vfir+XjI/818a1qciy45zk7Ovo5+Tm4N7c3tfQz8vAv768urm3t7e1sq+srKmopqKloJ6cmY7+poWyxsO6rrSyray1sbqptsjwhM/O9s7olY3PmvrU0tDOyc3LyMK7v72+vbu6ureysa2uq6mwrqusq6Wvram7tK2op4CooqGfpKahoJ2ZmZqbmJ+en6SnqaGjoJibnZubnJ2hnKGnn52doaGmpKSlq6aqsrCwq6yqqaexp6qlrq6rq7Gpo7Grp6+ysrGqr7CvsamwsbWxtr3AuLS2vr/AwL7CwMTM0dPY1tna2d3i6OXm6+Tn7e/x9YL7gYSDioiKjIiJjYCUlpKblpibnaCjoaiqqKisqquzt7i5v8DCx83P0dLV19bc4OLm6Ovu8vT29Pn3+/j5+f6A//z9/oCChIeJh4mJh4mGiYeHiIqJhoeFhIWGhoeJh4mHhoSFhIWDgoD+gP/9+fr79/P38/Xz9fbt6+vm5eLj5ODg4OTh3+Ld4ODb313a2tnX19XW1dfV09HU1NHV2tnf293Y1tXT0dLU0tbU0NLQzM/My8zR09fV2NTQz9HU1NfW2NjY1tTV09XT1tnc3eXd297f4+Pl5e3s7e7w8vLx8fX1+Pz/hoKCg4Q4Y2ZmZWVnaWloaWtqbG1vb29xcHFyc3R0dXd5eXp6fX1+f4B/g4SFhoaHh4iLjY6QkZCQkZOSlJSElRuWl5iampmamZqZmZiYmJmYmZqamJiZmJiZmZiFmUabm5mbnJubnJudnJ2en5+goKCioKKkpKanqKepqqqrrK6tra6usLGusbGysbKxs7O0s7W1tLWztLWxsrO0tbW2tbS0s7OzhLRrs7Kys7KzsrKwsbCwsLGvrq+tq6qrqqmpqKeno6SjpKKhoKCfn5+cnJuamJmamZeYmJmYl5eVlJSTkZCQjIOQkZKSkpOUk5SVl5eWmZucnZ2dnJ2cnJyamJiWlJSQj4+OjY6NjYyLjIyKiomEiICJiYmKioqJiYiIhoeHhoSCg4KCgH99fXp6enl4eXd4eHZ2dHV3dnNzdHTn6ejs7Ozw7uzv7e7s7u7r7vDx8vHz9vWEgn19fn+Af4GCgoKEhoaHh4eJiouNjo6Pj5CRk5SXmZeYmZmamZmam52chtfEnZ7cm7PFupWVi3G9oW5iYQhxb4+El42PnYWaAZuEnICdn56en5+fnYt9qMPkhLbCrHRhYYh3bHTCr5uXm6XH0MeL1vybnLt3kaOoqKeorKiqqamoqKmnp6ejwIOpssaAlIyofeZ4kqSnnJrDmXx+bFN6sn1qWWB/ck1HP1laWFVEP1JJQDYwNzVXqlpaaKpvfHnAtciYs76kcspyfY1+fIB+y8GBnnpwZK6WiH55m4fBfbvBwL28uru3t7a3s6+uq6Wko6KenJqYl5WSkI+QjY2MiYyJiIeFfuebhqqytKeVmIh9d399kIWTr9h3t7nowuSYg9Oo37O1tbOtr6yppaCjoKKgn6GhoZ+fnJ2Zl5qYmJeWk5iTj52WkY+Qk4+PjQWQko6PjYSKgIeJhYSHh4iDhoeChoeFg4KCg3+Fi4WDgoSEhoSFhYmGiI2LioaGhYeGj4eKg4iHg4aMh4ORioSHiIeGhIiJiYqDio2RjY+VlZKSlZqampiYmpeaoKOjpqOmqKmtsLSzs7e2ub69vb5kwWNkZGhnamtpa21xcm90cXJ2dnl6eX+BgICBhYODiYuMjZGRkpaam5ybnZ+eoqWnqausrK+wsbG0tLm3uLe6Xr28vb5fX2BhY2JjY2JjYmRjZGRmZ2ZmZWRlZWVmZ2VnZmZlZmVlZGNiw2LDw8HCwsG+v7q7ubq7trW3tbWysrSvrq6vrKmtqKqqp6mmpqSkpKKioqOgn56gUKGfoKShpJ+hnJydm5ubnp2fnZqbm5mbm5uanZ2fnqCgnp6foJ+goKGioqGgoqGko6SmqKmzrKqtrbGusLC2tbW2uby7vLy9vr+/wW1iYmJjGkZISUhJSkxMS0xNTU5PTk5OT05PUVNUVFVVhFYRWFlZWltaXF5dXl9eXmBiZGWEZwZoaWhrbG2EboBwcHFwb3BwcXFycXNzdHN0dXZ1dHZ2dnd3dnZ4d3d4ent6fX19f4B/gYKDhoiIiomJi4qLjY6QkpOTlJSVlpeYmZmcnZ6enZ6eoJ+goKGioqKjo6Oko6SloaGioaKhoqKio6Ggn5+enp2cnZydnZycnJmamZmYmZiYmpmXlZSRjyqPjo6Oi4yLjYuKi4qKiomIiYeHhoeHhoSEhYaFhYSBgX99e3p5dWx5eXmEdxd2dXV2dnV3eHl5eHh3eHZ2dnV0dHNxcoRwE29vbm5tbG1ubGtpaGdmZWRjYmKEYxBiYmBhYGFfX2BgYWFgYWJhhGICY2KEZChiZGZmZGRlZsnLy87Pz9PT0tXV2Njd3tzg4uLh4ODi4ndzcXJyc3V1hHcYeHp7fH1+gICBhIWFhoeHiImLjY+Ojo+QhZOAlJN/s2xRTm9SWXeGdWhoY6WPbGdrfX6VZGl9kqOio6KioaKioqOkpainqKipp6SOeXyQtWmVpppzaGyWf2tztJ2FfH2Kp7iieKzMh7LVjKy+w8LAwMXAw8PExcbHxsbFwdWCnsXVhJVyh3Dmc3iUlI6bwpuUlIBpe7yHeG19opiAcGddd3l2cmFedm1kXVZcVW/EY2Jjp298ebaJfmuTpYpShVhsgHJrcremX3pfU06LdWZbUWlTe2ettLSxsa+wq6qoqqaioqCam5ycm5uXl5eUkY6MjYqJh4SHhIKBf3bRiXZ5dHx0bHt3dnV3cX1sdYiqXYh9nXSBWGB1T7q1trSAsauurqunoqWhop6bm5mXlZaUl5WVmZaUlJGMk42Jm5GMioyNh4WAg4OAf359fX+AfH97ent8fHV3dnBzd3RzdXV2cXR4cnBvc3JzbWxqbGpudHJzb3BvcG53bG5na2xpa3FsaHRsaGttb25rcHN0dnBzdnZyc3Z6dHJ2eHd5eHh8fnt9gIGBgoCBgYKGiIuJiY2Iio+Ojo1KjEhJSE1LTk9NTVBUVFFWVFRWV1hYV1xeXV5gXl9jZGRkZmZnam1vb29wcG1ydXZ4eXp6e3t8fH19gH1+foFChYWGh0NDQ0RFQ0VHRUdERkZHR0pKSUpJSEhJSUpLSktKSklLSoRMJpdMmZmWlZaVk5SRkpGSk42Nj42LiIeHgoKChYSChYKEg3+AfXx8hXobe3h3dnh4dHR1cnRxc3BwcW9vb3FydXVzdHJwhHE2dXZ3dXd1c3NzcnFzc3Z4eHd2eHd5d3h5e3yGfnx/f4B9f3+FhYSFhoeGhoaHh4mLjFFHRUVG/4D/gMGAmH+sgA1+fX19fHx7fH1+fXx7iHoDfH1/lYAGf318fX17hnqDe4d6CHt8e3t7fX9/k4ATf359fn1/f3x7fH18fH19fX59e4R6A3t6e5d6HXt6e36AgIB/fnx7enp8fXt9fn1+f39+fn18fHx7iHoBfqd/BH5+fXuPegF7hXoFe317e33/fpt+An9+vX8BgIR/pYACf4D5f4WAAgIEAEKJjIiJiIiLjIyPkJKTlJaZmJqbm56goaCkpaqnqairrK6wsbO3uLi5vb2+wMTDx8TGxcjIyMvNzc/R1NLR0NHR1NSE1SjU1dbV09LQ0c/PzM/Oz83Py87Ny8nKysnKyMrJx8jHx8jIxsXExMXGhscMxsbIyMnJysnIx8jHhcmAyMjIycjIyMbGyMnJycjJysnLy8zJycnKzcrKycvJysrMzc/Q0dHS0dDQ0dDS0dPV1dTU09LPzs3MzMvMzMnIyMfGxcPBw8G+vr28vL27u7q6u7i3t7i4ubq0rLq5oqS6vL6/wcXHy8zNztDS193j5ebp6ejl4t7c29bUz8zLyMmAxsXEw8PCwsTHyMfExMPCw8PGxsnNz9HS0s/MzMvIxMC8vLm1r6uopKGem5eTko+OjouIiYeFg4KCgoGA/f/++/j/+/77+/z9+/v6/P39/v/9/oGCg4ODhIOFhoqdioyOjY2Oj4+QkZKTlZeXmpqanJyenZ6fn6CgoqKjoqGKpoOA24GU+JDziqiurKOmqZ6mpJeZpb7wir2coKGhoqOjoZ6Nkp6eoKCfnfDJi5eW+O7Ew6ecotmHm4/I1sGGxLKlqt6LraX2yvd4qavekZ+lpqWmqKiqqquqq6qqq5TSeJl9l36JnJ/Xztma6+ew2I+6rIb5sLF1rM/huq6qmIX+krWAwJqBl9ng5pymoYOYq+q7v8zY94PA6Kf0+pWMoqjahK6RyOD5v/vgwvHpxq6Nk471kKGoye/08e/s6ebj4+Dd2dbUzcjFxcPBvLy3trSzr66qq6elo6SeloDdzuXS7OLcxMXHq6yyi8fOwbquu7m3xcCy5pCgipaXkM7OzcrIxceAwr7Fvbm7u7u3uri6tLK4truzsLKupaqts6+xsK6lpKilq6utq6ego6GjoaSlqKeopKmmpaKin5+Zn56fpKSipaKjoqKkqairrquqpquoq7Ktra6vr6iop62lrauqraWrqKitra+2srCysbKyr662sby6trK2uL2/wMPDxszM1dOA1Nve293c2uHu5OPm5+vs7/T8/4KIiYaJi42Njo6Ok5aWmpuen6Okpqepra2sra+yuL67wMDFyc3P1NLb3djg5Onp7PD0+fv+gICCgYCDgYKEhYWEgoOFh4uLjY6NjIqNjYyNjYqLi4yLiouNjYyLjYuIiIeGh4iHhoaEgoGB/vx7/fz+gPv49/by7/Dt6uvo5eTk6Ofp5ujp4+bn5ubn4uDi4N/d3t7c3NrX2tnX2trZ3d7b3d3b3NXX2NXX4NfX1tbT1NbV2dfa3dzY19jY29nZ3Nrc29fZ19TU1Nrg4t/f4eHj4+Lq7uzr7/T09/T29PX2+Pr/goOEhYeHQmdqZmdnZ2lqamxtbm5ub3FwcXJyc3R1dHd5fHt8fH5/gIKDhIaHh4iKiouLjo6RkJOTlJSVlpeXmJmbmpqam5yenoSdhZxWnZ2dnJ2anJydnKKcnpybmpucm5ybnZ2dn56eoKGgoKGhoqOlpaanp6mpqqysrq6wsLGwsrGysbO0tbW1tri3tri4ubq7u7q4t7e2t7e5uLm5u7u4uLiEukC8urq5ubi5t7e2trS1s7KysbCwsK+wr6+ura2rqqmnpqWkpKOhpKKgoJ6enZ6cm5qanJqampmYl5aPh5OTgIGThJQClZaEmE6ZmZueoKCgoaGhnp6cnJyampaVlJKTkZGQkZCQj4+Qj5COjYyMjI2Pjo6Pjo6NjYyLjI2LiYeFhoSDgH9+fHx7enp6eXl5eHZ1dnZ2dHSEdYDq7fDv7/Xx8u/x8fTy9PT29vb39/b3fX5/f4CBgYKDhZCEhYeHh4mKi42OkJCRk5KUlZaYmpucnZ2dn56en5+foIuRa7ppe9dxtYOqkX5ubW1haGZUWGh+56rImpycm52enpyaipCcnaChoqD814uUlermysSqkpHHfIh+q6OraoCMgHd+uHyGdNCb+365q9iVpa2trKytra6srKurqqqrkuWDqoefhoKnsdbHwJTj3qfIjaOFZbdxv4WowJJhU1BFNmtIaXxwV1WIsYdJjoFlT16Yb3myv89um+mZzMl2dIF9snKEdKmtt6W2tKfVp4l4XmVkpmeSc6DBxcG/vbu5uIC4t7Wyr66ppaOjoZ2amZaWlZSSko6PjYuKjIeBccy/4sK5s7Whp6iIhoRYj6CZlo+dnpqtrJ/fi52OoZ6Fubm3s66rrKekq6SgoqCgn6KgoZ2Xm5qamJqbmJSVk5eSk5STjYyOjJCPkZGNh4uKiYeJiImHhYGFhIWEh4WGgYWDg4CFhISFhISCgoSHhYiKh4eEh4WIjIaGh4aIhIaFiYOIh4eJhIqIhYaFhIiGhYmJioyKio6LlJWVk5eWmpqbm5ydoZ+loqCkp6iqq6qvubOztbe7vL3AxMRiZmZkZmhra2xtbXBycnN0dnd6fH1+gIOFhYaIiYyPjZGRlJebnJ6coRWhnaKlqaqsr7Gzs7VcXF1dXV9eX2GFYiRjZGVkZmdmZWRmZmZnaGdnaGloaGlqamppa2loaGdoZ2hmZWaEZH3GxcbExWTCv76+vLq9vbu9vLi1tLWys7Cvr6utra2ur6urq6mopqiopqWkoqWmpaalo6SkoaKjoaKgoaKfoaifnp2dm56gn6GfoaOioKKjo6Wjo6Sho6OhpKWkpKSmsayqqqysrq6usrSzsrW4uby7vr3AwMHAwWFhYWJjZCRJTUhJSUlMTU1OTk9OTk5PTk9PUFNUVVRWV1lXWFhZWltcXV2EX1JhYWJkZmZoZ2lpamlqamxtbnBycXJxcnFzcnJyc3NzdHV1dnZ2d3d4dnl5e3iAeHl5eHd6e3t9fX9/gIKCgoWGhoeJiYyMj4+QkpKUk5SVlpeYhJoGnJ2foKGihKMSpqWlp6anqKmpqKampqWmp6elhKYkpKOio6KgoKCeoJ+gn6Cfnp6enZ2am5qamZmZmJWTkZGRkJCQho4GjIyNjIqKhIkVh4eGh4mHhoaEgoB/eHF7empoenp6hHiCeYR4KXl6fHp6e3p6d3d2dnV0dHNyc3J0cnJxcXBvbm1ubW1qaWdmZWRlZGRlhGQzY2FhYWBgX19hYWFgYWJiY2JjY2NkZGRmZWRnZ2hmZmhnZ2fO0NPR0dfV1tTX2d3d3uDjhOSA4+Rzc3R0dHV1d3d6hHl6fH19f4GBg4SFhYaHh4mKi4yOkJCQkZGTlJWVlpWVgHY+bD1JgVaVZYh5hHp4e211dmVqeYvMe8Keo6WkpaWlo5+Rl6Smqqusqf/Rf2hcpMa4yaqDgL17i36RuJ5ifHFqcaZwdGS1h/6IuMP/rsHIycaAxcbHycjKysvLy8yt7YOsk7GTlbGTs8G8htnYpd2TsJBuzIWleLHCp4J4eWtZq2WGlIlscqezomeVjGlgbqZ5fJiuzG2Uo2iYmmFkb12NW4JvjqO4frShha2CZFU/RkNmQ2ZxkbS4trWzs7CurauopaSjn56dn5+empqVlJGRjo+Ai4yJhoODfndlrp+unZOJind9gm50eE+EjoF8cXp6cnpxXYRVXEtac3mvrrCwr6+wqqeqo6CioJ+am5qbmJWbmZyXl5iUjo+Oko2Nj4+Ih4iEhoWGhYN+hIKCgIB+f318eHx5eHZ2dHVwdXNzdXRycnBwb29wcm5vb2praW1tcHVJb29wcXJtbGpsaW9vcXRvc3BvcG5wdHJxdnZ2eHNxc3B2dnZxc3J1dXZ5e32AfoKAf4GEgoKEgoaQh4aIiYyMjY+RkElNTUtNT4VQNVJUVFRVVlZYWVlZW11eX2FiZGdoZWdlZ2psbW9ucnNvcnV4eHl6fX19fkBAQUA/Qj9BQkNEhEMUREVERUVGRkVHSEhJSklKSktJSUqESxBNS0pLSktMTU1OT09OTk6ZhJh4TZaTlJORkJCOjI6MioeFh4WHhYiIhYeGhIOCf36AfX18fX18fHt5e3l3d3ZzdXVzdXZ0dHFyc3J1enRzcXJwcXRydXN0dnZ0dnd2dHJ0dnZ4eXV3d3Z1dXh/fn1+f4CCgYCEhoSBhIaHioeJh4iJioqMR0ZGR0dH/4D/gMOAln+sgAF+hH0GfHt6fHx7i3oDe31/kYAGf39/fXx7hH0Ke3p6e3t7fH17e4V6AXuEegV8fn5/f5GAG35+fn9/f4B+fHt9e3t9e35/fn17e3p6e319fIZ6Dnl6enp7e3p6e3p6e3t7hXoZe35/gH98e3p6e3x7enp8fn99enp8ent9e4Z6BHl6e32mfwR+fn17lnqEewF8/36cfrV/toCFfwGA8X+GgAICBACAkIyMjY2Njo6Rk5WVl5iZmpyenp+hoqOkqKipq6usr66xs7W3ubu8vcDBw8fHycnKy8vMzc/Q09PV1tjZ2tvW2trZ29rb2djY2dnY1dbW1dbU1NLT0tDQ0M/Qz87Nz87Pzs3My8rJycvIysnIyMrJyszLzM3NzcvOzs7NzMzLy8wBzIXNSMzPzc/PztDPzs7Q0M7Oz9DS09TR0NDR0tLR09LR0NLT1NXV1dbW1dfW19jY2NfY19fX1dbU1NPQ0M/Ozc7Ly8nJyMjGxcLCwoS/eL28u7eqtrShub6+v8GckZGKoL2/usfIysrO0NHV1dne4+js7+/w7+3q6efh3tnW09DLzc3LycrIx8rNzs/P0M3My8rMzc3Q1NfY2NfV1tLU0MzIxsO+urSwqqmkn5uXlZKRkZCOi4qIhYWDgoSCgYH//Pr6gIGCgYaAFf/9/v+AgYGDhIODhIOEhYeKjY6QkYSQD5GRk5WYmJqam56doKCho4WlgKKkpaTfgJWKvKjwn+61wbKvnp+smKSzw7Cbm7XUcLWLlp+jpKOhh/HQqoOKoqKQgI3Umpfqm5DWx9T2pcTN5saC3fLfyq+qq6/IoKGMi5Wr5fDxnJmUi5eqrK6zr66trayrrKKTj4m0e4WfoIzTuIKv5PrZ1L6N7M2/ubuPkOK8gMe4no2Nlaqnr62cqs/wkZbNguvArL6W8I+d8NqtqJiIvIzisL2b1v2v6c/Gk7GNmPbHqbXGu7OvuoTfq7nz9PHv7+vq6OXi3t7a0szJysbEwMK9uLW1sq6sqKepqKme2ZOhgJ3Xybu70LjJtJSej4uwr8Kop6KpvrrG1+Lhm5WGgIe5z9DLycjjw8DAvr+9ub27vba5tb29ubWtrKytrq+0uLSzsrC2q6mtsa2uqKWkoqCmo6OkpqWmoqWlqJ2dnKCioaOfm5+gop6jpKarrqqurqumrayutLGtq7Gvq6yu1LCrraqpqrCtrLW2tLm3tLK4t7iyt7m3v726vbq9u77AgMfM0dbO2dHX1trj4ubm8OTm6OTs6+70+4GBiISIh4mNjo6TkpWXmJuboKCioqSoqaqtsbK1ubm6vsLFyMjKzdPX3OLl5uzz8fXy+fyAgIGCgoKEhYOGhYeIiYeIiYiKjJCTkpOUkpGTkpCRkZGPkZCQj5GTkpKRj5KPjIqHiIiJV4iHh4aEg4KCgICAgf+A/Pr49PPy8Ono6+/r7Ovs7u/t7fDr6+vl6ejn5uLk5Obk5eTi4NrW2d3c3t/h4+Lf4d7b29rk2Nnc2N3Y2Nvd293d4OHe39zb3oTaKNvb3Nva2dvZ3uDm4+bk5OTp5+7y7/Dz8/X2+v38gICBgYWEhoeHioyAaWhoaWlpa2ttb3BwcnJyc3R1dHZ2dnh4e3x9f3+Bg4OEhYaHiImKi42Oj5GRk5OVl5eYmJmanJydnZ6foKGfoqKhoqGhn5+goKChn5+enp+enp2enp2dnp6fnp6dn5+goKGhoaKioqWjpaSkpKWmp6mqq66vsK6ysrO0tLW2tbeAtre3t7m6ur27vLy8vr69vr+/vby9vLy8vry9vr+/vr6/wL+/wMC/wL++vry7u7q5ubi4tra1tbWytLO0tbOysa+traqpp6anp6ampKWloqGhoZ6cm5iOmJeHmZuZmZh4dHNtfpWVj5mYmJeam5qcnJ2goaOkpqWmpqSioqKfnpx4mpmXlJaVlJOTk5KTlJOTkpORkJCPkZKRkZKSkpCQkJKQk5CNiomIhoaEhIGBf318e3t6eXp5eHZ2d3Z2dXZ3dnd58PDy83t7e3l5enp6e3z49fj6fX1/f4CAgYOCg4SEhoeHiYqJioyNj5CRkpOUlZaXmZmcnZ6fhaFdnqCgn+SAi3KmdbRsk2NwZWZfXmpZYHN9aFhZcZqXxIqVnaChoJ+G7M2mg4igoY59jdqiof6dhtC4x7+Ump6xjFqkqJiHcXBxco1qaFRVYX/p6u6ro5ySn7GwsLWwha6AsKaal4+6gYifrJPiuYKj58nJsJVlmKqaeL2cb6FyfGtSQT1GXltvn4KYrfqec5Vin2tXa2OdbHjDu5OUiX+mcKZ4jaH6453JmZpuaXN/0o1ygJCHhYSWda6Um8LCv7+/vLu6ubi1tbOsp6Wjn5yZm5iXlpaVkpGNjI2Njoa7i7WAlJi4p5+ht6KvknF3YluAfYx7fX6EnZejucLInJ2Ig6e5ubOvr9CqqKemoqGcn6GkoaShop+cmZibnJqal5WXko+RkJaQjpGWk5OPjYuKiY6Li4qKiImDhoiMg4ODhoaFh4OAhYWFgoOBgoOHhYiLiIWKiYqPjIiFioeEhomoioaAhoSEhoiKiIuJh4iHhoaLjI2KjY2NkZGQlJOYl5mam52go52un6Oipaipra+4rrK0sbi2t72/YWBlYWNjY2ZoaW1tcHFxc3N3d3l6fYGCgoSIiYuNjIyPkZOWlpeZnJ+hpKWkqK6ssa60tlxcXV1dXmBgX2JhY2NlZWZnZ2doaWkzaGlpaGlqaWhqamxqbGtra2xtbGxsa25sa2tpamloZ2ZmZWVlZGVkZGRlxWLBwMC/wcHBhL1quba0s7OysLGzsLGyrbCwr62pqqmsq6qrq6qnpaepqaemqKemo6akoqOiraOjpKGjoKCipKKjo6SkoqWkpKilpKOjpKSnp6emp6WoqK2srq2urbGwtLa0tLe4ubq8v79hYWJhY2JiYmNlZ4BMS0pLS0tMTE5OT09QT09QUFJSU1RUVVVXV1ZXV1lbW11eX19gYGFhY2NkZmZoaGlqamtrbG1vb3BwcHFxcnBzc3N0dHV0dXV3d3h3eHh5e3t7ent6eXl5e3x7fHt9fX5+gIKCg4SFiYiLjI2Oj5CQkpOTlZaXlpqbm52dnZ6eoBSho6Olpqanqamqq6yurq6vsK+tq4SsG62qqqqrq6qpqaimpKSjoqOjo6SioaKhoaGgn4SdYJyampiWlJKTkpGQkY+RkI+Qj46Ni4yNi4uNjIqJh4R7hIJ0g4SCgoFnX15ZZ3t6dHt6eXh5eXl7eXp8fH19fXx8e3p5eXl3d3Z2dXNyc3NzcXFwb3Bwb25tbGppaGdnZoVlCWNhYGFgYmFgX4RgGWFiYmRkY2NiY2JiZWZmZmdoZ2hnZ2loaWqE0oBrbGxrbG1ubW9w4eDk5XNzdHV1dHR2dXZ4eXt8fH5/f4CBgoSEhYaHh4iJiYuMjo+RkpOUlZWVlJaWlcVkaE5vXoJjmHiIgH92dX9vc3+MeWdogaBvpoyao6anpqSF69Kph4+srZaBhbiGhteEcru12LePnKCyjlqgoZF/a2txdICPbm5cW2V6vcDQrr20p7jNzM7Tz8/P0NHR0cOwq6PakJazvIrivXmT3cC7sp5wutCul62IdrKNmIp0Y19oe3WDp4OUtdOIeKZttYNsemSaWFyvsId5aVx9VXZMXH3AoGyRgXRjZGNqr2hNW2tmX1pnU4x0h7W1tLOzsbCurKqnp4CmoaCgoqCfnJ6ZlZKRjouLh4eHhod+sFlzYHSQgnd1iHSCcFxpV1FzbHlkZGZqeXBzfoF5WnZeY5Wrr62tsdCrqqipqaWenpiZlJaUmpmXlJGTk5KSjY2Pi4mNjZOIh4iKhIWBf35/fYJ/fXx5eHd0d3h8c3NxdXRzc29rb3Byb1xzc3R2dnFycWtobm1vdXJuanJwb3F0i3BubmttbnRyb3Vzb3Jwbm50dnh1eHl2eHNucG5ycHFxdHd6fnuFf4KAgYSEhoePhYWFgYeGiI6QSUhMSUxMTE9QT1JRU4RUI1dXWFlZXFtaXF5fYWRkZWdpamppampsbW9ycXB1eXh8eX19hD8vQEBCQkFDQkNDRENDRENEREZGRUZISElLS0tMTE1LTEtLS0xNTE1NTU9OTk5NTk+FUHROT05PTU1MTphNl5WVkpOSko6Mjo+LiomJi4yKiYyIh4aCg4ODgX5/f4CAf4GAf3t4eXl4d3Z4eHh2dnVzdHR/dHR1c3ZzdHd4dnd1d3d2eXl3d3R0c3R0dnd3d3Z3dnl7gH+BgH9+gX6DhYKDhYWGiImLioRGB0hHR0hHSEn/gP+AxICEf4qAhH+tgAh/f358fHt7e5F6Anx+iIADf35/hoAMf31+fn19fHx8fnx7hHqCe5B6Anx/k4CEfxN+fHx9fHt8enx7e3t6e3t6e3x7i3oJfX98e3x9e3p7hHobe3p7fX5/f35+fHt7enp6fXx6fHt6e3t6fH17iHoDe3t+o38Ffn18fHuZegR7fXx9/36YfrR/wIACf4Dqf4uAAgIEAGaNjo6Rk5SVlpeZmpqbmpubnaChoqOnqKqsr66wrrCys7S3ub2/wsPExsfFycvM0NHR0tTX2NfW2tve4ODh4+Hh4uHj5OTj4d/d39/f3t7e3NrZ2NbX1dbW09LS0dHS0dLS0c7Ozs+FzhPP0NHR0NLT09PS0tDR09LQ0dPUhNOA1NTU09TU09bW1dTV1tbX1dXW1tfZ2tvZ2djZ2dvb2tza2tna297f3uDe3tzd397c397b2tva3NrZ2NfW1tTR0dHPzsvLu8W9sKKoxcWb05u/iLu1kKG+ycCtw6y5pMCGmdqvuMfKzM7P0dXV2Nvf4ujv8fX3+Pn18e7r5uHd2deA1tTQ0dDQ0NLS1NfX2Nnb2djW09TU1tnd3d7f3dze3dzY1tPOycm+ubWvq6ainpqXlZOSkY+Ni4qGhoWDg4SEgoGA/fyAgYGBgoGBgYKBgoKBgoSDhYWHh4iJh4iIhoqNi4fzkJOUk5OYmJibnZ6fn6Cho6Okp6enqZt/grmLi6iAtaTX/OLRyr+5mpGHjJGZnaiws6GSkI2xk5Ctlp2VnJuxzNOw8rytiorK5u3EnayD6sa69uzFlLPVz8XX87+xq6CaipCRk52L9YuUmJuDgobKp3GDsLCwsq+lqq6trq2d0MZ0n52+jJt5hq/5lJKV5MmvtKeyua6mmI+V1eaYk5KAjouJiuaO98TR5oSBnMizprTJ8pWFjfn8/6Omg+2QttKYhLCo8N2EutiuxtG8oKK0pqGwuqLAwIqp9PL08Ovs6ejk4+De2s/Jy8fEwsK9v7Kxsayvqfu7/ZTxiPrRyLSyu8aipZ6w1sO2x6+hr6acp4iNocO/tdLdufXThHm0voCAn8THvMDFwMO+vLm+vbm/wL+7wbu0tbOxrK60tbe0sLCEt6mura+yqaupq6erqaOhpqamoqiioJ2il52gpJ+coaGdoJuioaKjpaSnpqelpaemoaalqqirraurq6asr66spqyrqaewsbO4s6+xsbG2u7++wMK+wry/w8PFx9DUzNMKzPvU29/f6efj4oTmgPD08/X/g4SFhYqKjo2RkZKUlZmbnp2joaWora2vs7S1uL2+wMPEyMvOz9XV4OPl7PD1+Pj8+/3/gIGDg4eIiIqLjIuOjo+Rj5CQjpGSlZeYmJeVlZaTlZSVlJSSlJWUlJWWlJOSkZCOjY2LjYuJi4qHhoaDgoOFhIWFg4GB//z6cPj3+O/09fDx9PXv8e/u8vDx7/Dy9O7w7Ovp6ubo6ebl4+Le4OLj4+no5+Ti39/g297d4t3c3t/h3+De3N/f5OTj4eHj4eDh3d3f4OHg3t/c4OXm5+3v7PDt7vHy8/n6+v2BgYD/gIGDg4SHioqLjI1BaGloamtrbG1vcHJyc3N0dXd4eHl4ent8fYGAgYGDhIaGh4eJioyNjpCQkJKUlpiZmZqbnp+enqChoqOjpKamp6eEpjelpaWkpaWlo6OkoqKhoaCgoKGioaGgoaGhoqOjpKKjpaempqenqKmpqquqra+vsrKztLW3t7e4hbwiu7u8vb2/wMDCw8PCw8PExMTDxMPExMTFxMXFxcTExMPFxIbFgMPEwcG+v7+9u727ubm6uLm4uLi3tra0srCwrqypqJ2loJeLjKalgrGBn3WipH2Mp7GZi5uIkoCWaXioh42YmZqbnJ2fnp+goaKkp6epqqurqqinp6Shn52cm5qXmJiYl5iXl5iXlpWWlZWVk5WVlZaXl5aWlJOUlZWTkpCPjZCKJYiHhYOBgH59fXt7enh4eHd4dnd4dnd5eXl6evT0ent7eXx8fX6Ef4B+f4B/gYGCg4SEhIWFgoWHhoHpi42PjpCVlZaYmpucnZ+goqKjpaWlp5qChK+HgpyhjpyKdmtoYmNKQzxCRE1OWGJiVFBXWH+Dpq+anpacm7fZ2pfywqeIi8Dg6c2jsIbQiZPAsZeEl8K2pqXFinlwZ2FTVFlWXUt4TVpjaHqLk4DTrHmItLOztLGrrrOztLOg08hyoJ23jHqEj5Tpd4uOp4dtbl5jamdiVk9SnatPSEZIS0pNtn3hoonCeV9Ue2NTYXOYc2Zv0dLUioJ0wXOYw4pvjoq5pWWApJClmIlwdIFzcHqId5uefonEw8XCvr+9vbq5tbWyq6eno6CenZyeloCVlpGRicub137EeNCgmpGKl6qEhIeWsqKOlHtyfHNtd11ieaGgn7rIpejYnIKor3qOsLGnqqypqKWkoKWloaSkn5uhm5idnJuYlpaVlpKRknKej5KRkpSOjoyPjJCPiYaJiIeEiYaIh4qDhoWIhoGGiISGgoWCg4SFhYiHiYeHiCiIhIiGiIeIiYmJioWJiYaEgYeIiIWJiYiKh4aKjYyOj4+Oj5GRlpOWhJpin6KdoZzWpKirqrCvrauws7Ozu7u6vMJiYmRjZ2ZpaGxtbnBxdHR2dXl3e36Dg4aJiYmKjIyOkJKVlpmanp6mpqisrK6vsLO0tbddXV5dYGBhY2VmZmloaWtqa2xrbGxtbG2EbA5ta21tbm5ubG1ubW1ub4ZugG1tbGxqZ2loZ2doZ2doaGdoZ2ZkZcfHx8bGx7/CwLu6vLu2trOztrOzsbGztLCxrq+srqusrq2trK6pqaqqqq6sq6imo6OkoaalqKWioqOlpaenpaalp6WlpKaopqWmo6WnqKmop6elqKutrrK0srSztba2t7u7urtfYGDBYWJjCGNjZGVlZmdoREpLSktMTExNTk9RUE9OT05QUlJTU1ZWV1dZWFlZWltcXV5fYWFiY2RlZWRmZ2lqa2tsbW5wbm5wcXJzc3N1dXV2dnd3hXgJeXl6eXp8e3t7hHoie3x8fX1+f3+AgYGDg4SGiYmKi4yOj5GSlJOVl5eYmJqanYSfFqGhoqOkpqepqqqtrq6xsrKxsrOzs7GHsFCur66vra+urK2rqqinp6ipqKmnp6WlpqSjpKOhoKCfn56cm5qZmZiVlZWTk5KTh46IfnF1jYxtlW+JXoB1V2x/dIJ0gnF6a35ZZo9ydnx8fIR7aXp7e3x8fX59fX59fnx7e3t6eXh3d3Z1c3Rzc3JzcXJycG9ubWtqaGZlZWZmZmVkY2JhY2NkYmNiYWFlYmNkZGRjZGNjZGRlZ2dnaGhqaGlpaGlqamtrbNfXbGxram1tbm9wcHFxcnN1dIR2gHd4d3h4dXh6eHTSf4KDgoOIh4eJioqKi42OkJKUlZaVl4x0dpV8fIWLdYeXhn1+e31tZ2Rqamxqb3d7bGVnZIN3gKOQnpmgnq3CxIDnvK+Nj8zl8b+Qmnayd4evoYB6jLOqmqW3f3JvamhfZGdocV+eXGNnZmGXqeqkjp/R0dHUJtHHztXV1tW89eGHpKTOlnqJjp30gIyJuJqDioGOmZGHeG5zusduhGeAZWrFeN+go8FsZW6RdmRsdpJJUmPGx8h8fFmTWIeYaE9vbZyPV3aTh4p3Y0xSYFhSWmNOZ2VQd7i4ube0sq+uqainp6egnqCenZucmZySkZCLjIW9jMl3uWK1jIZ7doKNZ2hicJGDdYFtaHVwZ21RUl59d3GHjmeYgGFdk6Bnga2Ar6esraqrpaKdnpyYnJ2bmZuWkZSTkY6MjY6QjY+QZJSIioiHiYODg4aBhYJ5dHZ2dXR8eXt5fXJ1c3dyb3JzcHNvdnNzdXNwcW5tbGxvb2xvbW9tb3FvcnFrcXJxcm5zcm9qbmxtb25tb3NxdHh5dnh2dHdzdXd2dXd9gXyAe7lcfoKDgoiGhIWJiYiHj4+NjZNKSkpJTU1PTlBQUVFRVFVWVVhWWFpcW1tdXFxcYWJjZmdoaGppa2twb3B0dHd5eXx8fH0+P0A/QUNCREVGREZFREVDRERCRUVHSEiESQ5KSUtLTExMSktMS0xNT4ROGE9PT1BQUVBPUVFQUFBOTU5OTk9PTk1NmISWD5iRlZWQj5CQi42LioyLioSHNYSFgYF/gH5/gYCAfn95eXh4eX18e3l2c3N0cnV0eHRzdHV4eHh3dXRzd3Z4eHh6eHh4dXZ4hXkheHx+gIGDhIGDgoSGhoWJiYiJR0ZFikVGRkdHSEpKSktK/4DRgAt/gICAf35+f359f4iAAX/ggIJ/noABf5mAB3+AgH5+fXuVeh97fX5+f4CAgH9+fn19fn+AgH9/fn5+fX17ent6e3x7hHoBe4x6AXmEegZ7f4B+fX+NgA9/f4B9fX99e359e3t7fHyWegd+fHt6e317h3oCfH6FfxF+fHp7fHt8e3t7enp7enp9e4x6AXydfwZ+fn5/fn2gegJ8faN+AX/yfrN/xYDkfwSAgIB/i4ACAgQABZCTk5iYhJo4m52enp+eoaOlp6epqquusbGztLW1t7i6u77BxMfHyMvMzdHR1NfZ29vc3+Df4eLj5unp7Ozp6u2E7Ffr6uvr6ejn5+fl4+Th393e3t3b29nX1NbX2NrZ2dfV09HT0tLT1dfX1tfW19fX2NfY2NfY2NbX19nb2dnb3Nrb3Nvc293c3dvb2tnZ29nb3d/e4ODd392F34Dg4eLi4+Tk5efp5+bl5ebl5Obj5ODg4d/e3d3c29fW09TU09C6oZWv6/2ekv2Lvs+3hpuakb7N5IWq+4T89OrayPaDx8rLzNHT1tja2dzh5enw9Pn+gICA/vr28e3m4t/c2trZ2NnZ29vc4N/f4OLj5Obh4N7f4+Xn5+jq6enr6Szo6d/d2tPLxL24sKyppKGdmZaUkZGQjYuJhoeGh4WGhYSDgv6AgYKDg4OEhIWDgISFhoaHiIiKioyKhvvRzPqZs6qs7NbdlbuOnqCio6KkpKWmqKqrra+spu7jj6TQwJ+J6Me6u6OelZGN6P/9iIymo7OijpSR5bF2erqfuq3i4X+53PPKp/KHuP3FzMm2obGknMfr4+nl+pCSnaWjtK2XpJOKjIWLg+P3+/aAmrzQgKOqyo+xrqaZhni5ha6zopuMmH9sw5B8/rFoy9/A9sbn0YappKenoZSPiYWIppychqKV+4/J7qanlc/zt5SlqKanpsHpkYn6n5+T6O/YoqGn0YaaprCx45+Mxc3UwbChrp6X07malI6r7uv18e/x8O7r6efh4NzX0czQy8bDtoyAgN6kmI63gYGUnJ2pyLC+sbSetJOgnqivq6qxrqi6orLKj+WKt5+WsrKp18V/rKmYqanAq5iovb2+vrm5ubu5zru/vL28vrOztrS4sa65qauqpaenrKm0sbCzqayvqaihpqenpaqlp6SnqKKjo5+dnaCfoqWjp6inqqqtraurrKisgKmlqqqrqquvrbWvsaytrq+up62uqrK3s7e1tK+xsLa2uLm+w8DFw8PFycjKycrI1Mjf0NTe4uXj5Orx7vTx+fj6/YCDgYiHi4mOkJKVlpmbnKCho6msra6utba2u7m+xMXJzc/N09jd4OLn6+zw+vv8/4GBg4WGh4mKjI+QkJCRgJOUlZaUk5SVl5iZmZyen5yZmJiYmpmZmZqbm5qYlpeYmZiXlZSTkZCPjo2NjYqLioqJiIaHh4aGhYWDgoCAgf77/fz79ff4/fn6+/n2+ff39PL28fLy+PLx7O/t7Ons6+vs6+no6OXn2aa2tu/y0NrV5OHh5OTk4uDf4eLm6OfnIObn5ePm5eXm4+Xl5ejo6ers7e7y8fLz9vb4+Pj6//+BhIIKhIWFhoiMio2OkGFqbGxvb29wcHBxdHV1dnZ3eXp8e3x8fX+AgYOEhoeIiImIiouOkJGSlJWWmZmanJ2enp+io6Ompqepqaqrq6qrrKysq6uqqqurq6qpqainpqinpqalpqalp6Wlo6Wlpaemhag0qqmqqqytra2vr7KztLW2uLq5uru8vL2/wcDAwcLBw8TExcXIx8jIycjIyMrIycrKysvLyoTMfsvKycnKy8vLysnJysrIxsTExMPCwr/Avb6+vby7u7u6uLe1tLOxrpuNd5bI2Xp903eitphxhoaGobG8aI3RasvIwrGhw2aZmpqbnZ6goaGgoKKjpamqrK1XV1etq6qpqKWkoqCfn52dnZydnJucmZiYmZqbnJmZmJmam5uamoSZgJiZm5WUlJGOi4qIhYSEgoGAf318e3p6eXh4eHl5enl6ent7fPd8fX19fH1+f4GBgYCBgYKCg4KEg4WGiYeE9czT/Zmuoqnm09iRt42anJ6fnqGipKSmp6enqaei9eGIr8eJcW2CXVVYR0ZFQTxVbGM6P1NTW0tBSE+zp4iCwKK9gLPv3YC1vbSlp+yaoufk3My8p5J9e6LBvL+/3GZkZ2RhbmZUY1VNUklLRWZ2eXc9WMXckrPSlLi1rZxwmtKKtLqnoJKafXPEjH3Rlme7oZPkoKCPYGZcXF1aUUtFP0NfUk45WE92TYjNgn5VibB2TlhaWFdWdaF4Z86Dgni3wb2QgJGizWtrd319rHN3nYWUiXt0gnJwpZB6eHeZz8DIxMPFxMPBvry3t7KuqqWopaKfmHpsx4+Hd4xocoiJipOLdoGChXOSdICNmJqdmZGKf4hyg55knGmYgn2SlpDDv5XRo5nGsb+gkZutrqysqKWkp6SyoaCdnp2gnJudmpuUkZyPSpOUkpWSlI6Uj4+RiY6Sjo2IiouKh4qGi4iKjIeIhoODgoOEhYaFhIOChYSGh4eHiIaIiYaJiYeGhoeGkYeHhISGhoeFioyJi46JhIt7jYuOjYyLjpCNkpKVl5mam5ucnKiftKqstLOxrKmss7O5uMG+v8JiZGNpZmhmamptcHBzdXV3d3h8foCBgoiKiYyKjZCQk5aXlZidoaOlqaytr7S0tLZcXV5fYGBhYWNlZmdnaGprbW1sa21ub29wb3BxcW9vcHFxc3JyhXESb25vcHFwcHBvcHBwbm1sbGxqhGt8amlqamloZmdlZWRkZMbExsPBu7q8vrm5ube2uLe4t7e6trW1urW0sLKxsK6xsbCxr62trquuo3+KiK6zl6CcqaamqKipqKipqamrqaenp6mopqioqKqoqaqqrK2trrGys7W1tra7ubu8u7y+vl9hYWJiZGRkZWVpZmlpawNKTEuETQNOT0+FUDRRU1RWVlZXV1hZWFpaWltdXl9gYWNlZWVmZmdnaWhpa2xtbm9xcnFzc3R1d3d4eHZ3eXl6hHkBeoV7A3x6eoR8DX1+fn6Af4B/gYGDhISEhy2IiouMjpGTk5SWlpiampqbnZ6foaKjpKSmqKiprK6tr7CwsrO2tre2uLi2treEtWSzs7GvsK+wsbGwsLGwsbCurayrrKyrqamoqKempqWmo6OioJ+dnZ6dm5uYmJiXlYZ2UX2ohEhhrl9/jndecGRrhZGhUmygU6ShmIuFpFR/gH9+gH9/f358fX59fn9+fn4/Pz9+hHwnent7enl5eHd3dnd2dXVxcG5vbm5uamlnZ2hoZ2ZlZWRkY2NjZGBhhGMEZGRjY4ZkE2VmZ2lpaWpqamtramxra2ts1muFbYBub3BxcXFzdHZ2eHd3d3h5fHp43ri52oWbjJLPvMCGqHyJi42OjY+PkZKUlpeYmpmU3M5zlcSQdWiPbWhrYWZpbGqxwrZfY3JvemleZWm4k2VpuZ26ru/XYZ+ktaKp9I2p4szRrJWCd2plj7O0vcLgcG1ubWl5cmBvZF5hWmJcloCmoZxPYNbsiKnjp9LOw7GEmdie09rEtZiqkIPeo5TlpYzm07H/q7WhaoGBiY2IfHRuaGZ/c29ZeGysbaK8hYByosCHYmpqZV5acJBfU9GBgXWypo9vdnuXV15tdXimYWOOeYJ0Y1hmWVOGa1JPS2eRrrm1tLazsa6rrKmqqKekoYCkoZ6ck3Jdsod/cpBaTV9uemyKeIN+gWyBYGhqcHRzdXh3c4Jvf5hchVZ9Zl1xcGSHemKDbmNldqSUhJCipqiopaKen5yrnJ6cm5ibk5KVkpKKh5KHjI2KjIqLh4uGhYeAhIWBf3h6eXp3fHl8enx9eHl3cnBvb25wcnByc3FzcIBxcG5ub21xb21ubWxrbW9ucnBvbW5wcnJtcXBsbnFub3BwbnJxdXZ2dHd5dnl2dXZ4eXx6e3qDe4h/gYeHhoODh4uIjIiOi4yPSEpJTktNS01NTlBRU1RUVlVWWVtcXFteXVxeXF9kZWdqa2lqbW9ubnBwcHR5eHp8Pj4/QEBAQQJBQoVED0ZGR0hHRUZHR0hJSUpLS4RJgEpNTU1OT05OTUxMTk9QUVBQUVNSU1JRUVJSUVNSU1JRUFFRUFBPUE9OTU1OmpiZmZeSkZOWk5OUkpCRj42LiYqGhoOIg4F+gYCAf4GAfn98enl7eXtzUl1bbnFqbmx1c3V3eXt5eHd2dXh5eXt6enl5e3p7fHp6enl8fn+AgYGBG4KBgYOFhYiHiIiLi0ZGRkVFR0dGR0dKSUpKS/+Ax4AWf35/f3x8fn+Af35+gH97e3p6enx8fYd/k4CDgcuAAX+ZgAd/f35+f39+hn+RgAh/fnx+fXt7fIl6g3mKegN7fX6FfxB+fXx7ent+fn99fXx+fn17mHqEeQd6en1+e3x8hYADf31+hIATfn1+gH9/gH97e358ent9e3p6e5B6Bnl6ent7e4x6CXt9f4CAgH99e4R8AXuFegN7fHuOegF9ln8Mfn5/f399fHt7fH17lnoBeYl6B3t7fX17fH3/fpF+sH/NgK1/gn6xf4+AAgIEAICSlZeam5yenp6go6OkpaWmpqiqqquur6+zs7W2uby8vb3CxsfMzc/R0dHU19fc3+Di4+Pn6Onp7Ovt7+/z9fPy9PX29/f39vL18fLx7e7t7ezp6ebm5ePj4eHf397e3+Lf3tzc29vb2tvb3Nvc3N3e3dzd3d3c29ra3Nvc297e34Df4OLk4eHi4uPh4ODf3+Hg4uLk5ebl6Ojn5uXj5eTl5+nr6+zr7ers7u3t7+7t6unr6+3s6uno5uPk4uPi4N/b2tjW1dOWyorr4uuMhcXY5oLBnNvTsojphqaKzNWNxJnqj/WToJjlw6/Z2dve4eLm7PD3/P//hISEgf/++vPr5oDm4t/e39/d39/i4+Tk5OXm5+nq7err6uvu8vP39/b4+vf49u/q4t/Ty8W9tbCtp6Sgm5iWlJKNi4uIioeIiIiGhYODg4KBhISGhYWEhISFhISFhYWHiYqKiouB+/27p57hiqqJ6X3ptNfUw4SioqaoqKipp6iwrK6wr32o7aDgqoCM2sbZuK6ntZ2YoILp4ID494GVoJeZhomC04Oy+IfS45/slsKGsrXu2oKAy+rUwoXpwb2znZWaxITBq4uMnYSDnpqMhPuIj4mC7e7579zGy4Oxr8iVxnd6jZ3JusKyc7auw8TS18i/rY+Eo57tvsLTccfAtaejsbKNiZqlmJWWmoCeyYz8lv6lsoaz9Nmbi5mspZKRg5/koPvk46Kd7P6Yhq615Z6rl5qPt+aUiMn1hs6ltbewsKuzr6uatOGD2bHt8fDw7Onl4t/V1NDSz83A3fSsjMGRkuydvaGa6cO0sbamsaqbgYqSjZ+ZqsOst7GjsMenmbO6lImHpJfUu6yq+YCG/OuRvqq/q7m3oLG5t7q6u7m/wbu+urq2ubazsa+wrq2tq6isra+yr7Otrq+qpqOjpqelpKSgo6SipKSioqKjpqWnpqqqpqmnr62vsK+xr6qtsKqqrKapq6uqs7O0tL2yrqyssLCxtLWztba0tbWys7m5wcLCvr6+w8XNysvOzVLM0s7Z19fb4ubp7ezy8fj3+v3/goGGiImLi4yQkpGVmJedn6GmqKmsrbGxtbe6ur7AycvP0tfb3+Pk6OnrhPb4/oCAgIOEg4iKjY6QkpSWlpeYhJqAnZycnJucnaCioaGin5+enZ6dn5+enp+dnJ2anZuenZmXmJeXlZSSk5CQj4+Ni4yLiomJiomIh4eGhIODgoGAgYKAgIH///+A/f////36+vv6+PX2+fT49/f09PTy7u3phd3gxd+H3cji/8Ki2Yrf5Ono5ubh5OHl5+vv7uzp8+sq5ujm6urn5uDq5+7x8fLw8e7j8db1/fyAgIGBg4WFh4uJiouKi42OkJKRQmxtb3Jyc3R0c3R2dnd5enp7fX5+f4CBgYSFhoeKjIuLioyPjpKTlJaYmJqdnaChoaOjo6aoqqqsrK6urbCwr7CvsISxDrCvsa6wr62ura2tq6uqhKlHqKmpqKioqauqq6ytrq6ura6usLCysrS2t7e5ur29vby9v8HCwsXFxsbHyczIycvLzMzMzczNzs7Ozc7Nzs3P0NDP0dHT0tGH0oDQ0NDOzs7Ny8nHx8bHxcTExMPCwsC/vry8ubi1tbOwfLhpo5Wja22ltLxumoq5mYBgpmGBdriwcKWDwnLFcXt1s52Fo6Gio6SjpKaoq66urVlZWViur66rqKalpKKho6KgoJ+goZ+fnp6dnp6en52fnp6foJ+gn56foKChoZ2bl4CXkpCOi4eGhoSEgoB/fn18enp6eXx6fHx9fHx8fX5+foCAgYCBgICBgoKChISDhIWFhISFe/P1t6OY5ZKskfaG8rPb1L2AnZ6hpKOjpqWmraqrrKuAqOubvYlleWdyTUdBTj4+STNQSzFhYjdGUUxOQ0lHoWqR6ofU6p/tlrVaaoBwusltb46OeWdiooaSiHZsbJNuh3pZUmJHRF5aUEyYU15SSHFpcmpiU2FSj4PgddF9gI2nnIGpqXS6ssDM2tvdw6yPepeY7a+wzXS+fWpaWGRoSUZSW0pFPz5BmWfbVoqdsIeur4tONUBTUUZGQWOwcevCt4J/usCBapGbxoBvXIBfVXObc2l+tm6ce4yKhIJ5goaEebjIcLuWyMnGxcC8ubazra2oqqelmsPVkpKqo5vFjpl+eq5+c3V4coJ8dmJqfIGSk6SukJSGeIeoj4Wfon5xa4Z6rqehn/aH+eOT073In6unkaOopqWlo56jopyfm52anZqYlZKVlJWUkpCQkBeRko6Tjo+QjoyLjI2PjIuLiIqKiYqJh4SFI4OFhIiIhIWCiIWGhoeJiIWHi4aFiYKFiYaEjYiJipGLiYqKhI1xjIqJiYmNjYuKjYqPkJGRkpKXl5qZnJ2enqGdp6emqKyrra2ts7W9vL3AwGJhZWVmZ2dpbG1ucXNydnZ2en19f4KEhImLjIyPkJWWl5ibnqGlp6qsrGC0tbldXl5gYGBjY2VlZWdnaWprbG1ubm1wcXGEcwV1dHR0dYR0GXV0dXV0c3RzcnNxcnJzdHNzdHV1c3Jwb26Eb2ptbm1sa2tqaWhoZ2dmZWVlY2JiYmBgYL+9vF67vby9vLu7u7q5t7i5tbe2tbS0tLKxr61io6aSpm+ppLTLkHqWZaKmqquqrKmtq6yrrK2sq6qzramrqq2tq6ynrq2xs7S2tbW0qrSht7y9hGAPYWFhYmZlZmdnaGpqbG1sBkpLTE1OT4RQHFJRUVJSU1RVVldXWFhYWVhZWlteXl9fYmRkZ2aEZxJpa2psbm5vcHBzdHR0dnZ4eHiFexN8fXx8fX19f31+fnt8fH1/f4B/h4CAgYGCg4aGh4iKi42OjpCRlJSWlpiZmpqcnJ+goKKjpqeoqausrq6ws7aztbe4u7u7vLu7vbu7u7y6uri5ube2tbS1tLS1tra1tbSzsbCwrq6vr66rqaqoqqmnp6elpKSioqKgoJ6cmpqZl2hzYaCQlFVUfYOMWX51mYN5YrVri2x7hZRggF6IWpRcY1x9b2aAf4CAgH9/gIB/f358QUBBP35/f359fH59fHt8e3h4d3d3dXRycXBvbm5ta2ppaGhoZ2hmZWVlZGRkY2JiZGRkZWZlZGZkZGRjY2VnZ2doaWhsa21tbm1tbGxtbm1vb3BvcG9vcXNzc3Z2dnh6hHmAcN3goo6IxXaNdNVyzZ26uqlyjY2PkpGRk5KTnJmam5pvjdaSpnBcg36MamJbaF1hcmO2u2bCt11ob2tsYGVgpWqFyYPJ3YXlhYphgIbOvWlkmaqTflmdgoN7amJpmnaViWpicVVTbWdbVapeZl9Znp2pno98gFuQhOKC7o6SnpmAon2Tr4fd1N3p+v7p28Wji5ug4LrO7YGui310eo6XdnF8hXZuaWtxvYL4dMiXoHWa1LVxWmR0a1pVSF+aYbCws397sppqWXh8nG9jV1tUdJZpY3WlZYlea2pgXVNgY2NXZZ1gnYS0tLKzr66trKynqKWnpaOZpqpucpZ5fbdyhneAbJx5cHF3cH54blddZmRwbX6QeIB8cYKZe2yCgmFVU2tciHlvZphPjHZPiWOKjpydh5ifm5ucm5ien5idmZqUlpOPjYuOi46PjYuMioqJhIeBgIF9e3d3eHh4dnZ1eHh4enh3dHJvcGprbHBwcHJuc29wcG9xcW9xc29ucGtucG9XbnNycHF1b25ubHBubG5tbW5vb29xb21wcHV2d3R0dHd4enl7enl4enZ/fn2AhIaJjIuOjZGOj5GSSklLS0tMTE1OUE5RU1FUVVVXWVpaXF1dX19gX2FihGYdaWttb29wcG8/cnR3PT0+P0A/QUFCQkJDREVFRkeESDlKSkpLSkpLTE1MTE1LS0xMTk5PUE9PUVBQUVBRUlRUU1NVVVZVVFRUU1RUVFNRUlFQUFBRUVBQT0+ETlpNTExMS0tMlpaVSpKTkZCPjIqKiIeGhoeEhYWEgoCAfn18fEN2eWxyJWw/Sl5VQlE/b3V5e3t7d3l1d3d5e3p6eoF+enx8fn56enV9fYGEhIaDg4F4gW+EiIiERoRIAklIhEkFSkpLS0r/gMeABXx7enp6hHsZfX1+f316enp5enp7fH6Af358fn6AgIB/f46AhIHhgIV/C35+fX19fn5/fn5/kIAGfn9/e3t7i3oFeXl6eXmJehZ7e3yAf359fn58e3p6enx+fnt6enp7iHoBe4t6AXmEeod5EXp6enx7foCAf317ent+gICAhH8Nfn+AgH59fXp9fn5+e496CXt7e3p5e3x9fY56Bn5/gIB/fYZ7hnoFe3t6enuLegR7fX9+kX8MfX18fH18fHt7e3x7pXoHe3p6e3x7fP9+jX6qfwSAf39/2YAEf39/gJx/CX58fnx8fX5+fap/k4ACAgQADpqanJ2foJ+go6WlpqaohKsyrq6wsbG1uLi6urvCwMHEx8jLzc/R09XY2t3h4+fn6+3s8vHy8vb19/f4+vz9+/39/v6FgBb/+/v5+fb18/Hv7erq6Onm5uTl4+TiheMk4N7d3+Dh4ODh4uLi5OPi4eTj4uHg4eDi4+Xm5ebl5ufq5ujphOqA6Ofo6evr7e3t7+7v7+7u7e3s7e/v8vPx8PLx8vT19fT07uKV1sno8PDw7u3r7Oro5ePi4ODd29rMorLUnrPQsamC6L7fvIXdqJuoifWBh5HMlJen0LvWuuO/o5ra3uPi5ejs7/X4/YGZ87XmgYWEg//69e7r6uXm5+bm5+jr7e0S7/Ly8fDx8/X19vb4/P+BgoODhIeAhfv27uje1s/Gv7axqqajnZmWkpKPjo2Mi4yMi4uKiYiIh4aFhYeHiYeHiYeIiIeJiYiLgIiMgObQ+5+ttNGT35aGyv2ij7Cyj5Woqqusq6+trq60srO0peHSlJahgNK/pbyQlKeZloX329jh+Nf9iaKfmLejpaCPr8Lzq3h10P6AroXi0KLVl+2si9DIwbW2vrramLShxNCbnZubjamIkZGaipuKk46Jh4aIh+zh5NbpibaYgW1+6vKrm9GmjYmco6iX0Xp4lrrC3JWTk9XWh5nNsKu5t6OC9IuOkoqSkYGMl66Q7c3IyKiRsY//kpiTh4eEjY6Cm6r1m4WHi8KsstuA6enEp6SKibPBtL+otrCGiZyxqZ6jtKqB8Jy+m9rz9vDu7urm4tvX09XLgf2wwb7Kzfq3wJC8kZLCsbuooJOWlJmPkpSYnrKlqbqzoYWYucGmp7jAn6uEjqC8xtbItcXa1O7HmuPDj4SrkKyus7Wyubu9wb6+t7azsLOzubW0t7OAsqqwr7mqsbCrqqqpp6mkpaSkqaamqKelp6SioqKkq6quq6mur6+vrqywr7Gws7Gusq6yrrGsrrC1tLS1tLmxq6msrbStsba4t7W4tLW2u8G9vbvEvcLBwMHEyMvP0NPW09fa19/h6u/z8/v8gPj7gIKFh5mIi4uLj5KUk5WVmJtJnqCno6mssrO1ubm7wMXHztHV2d7g5+nq8fD2+PyFgoKEhYiKjY+SkZOXmJmampyanZ2eoaChoqGipKSmqKeoqaempKOjoqOjooSjKaGhoKCgoZ2cm5ydm5yZmJaWlZSRkI6OjY2OjIyLioyKiIeGhYSEg4SChYNjhIGBgoKDgoGBgf/6+v/5/Pv8/P/87IbdsviHqvStnOSauobektChj/f/3OHl5ev26u708/Xx7vPt7Ozs4enZ27Tb6+vd4PHw88XN75zn8ICBg4aGh4eD8NGEi46Mj46Pk5aXCXBwcXJzdHN0doR4Mnp8fH19f4CBgoKFh4iJiIqQjY6OkJGSk5WXmZudn6Kkpaamp6iorK2ur7OztLOztba3hbWAWlpbWlu0s7Szs7GxsbCvr62tra6rraytrK2sra+wsbKxsbCys7S0tLa3ubq9vby9wcLCwsPFxcjJy8zMzc3P0dTQ0dHS09TU09PT1dXU1dTU1tTW1tbX19jX19jX2dnY19fW19fX1tPSy8KAuK/Fy8rKycjIycjGwsG/vr25t7Zaq46annxul3SWbMCVsp9ryWJedV6qYmpzmnJzd5uLmJCqmYeMo6SnpaenqKmsra9ZZqd5mVZaWVmwrqypqKempqempaSkpaakpKWlpKOkpKWlpaSkpaVSU1NThVQboqGenJeUkY2Lh4eFhISCgYB+fn19fHx7fH19hH6Af3+AgICCg4SDhIWEhYaFhoaFh3uChnrgy/WcqK/Hk9eKe9r5po2tr4mPoKOkpqeqqqyssa+vsKTzy5yVglt/Y0hWNDlKQj84ZVJJTl9DYDpLSUdcUVZVS2l97KyAhtH8nVqMeU+EecSMVG1lX1debKC4g25VcX5LUVFRRmJCS0iAUURXS1VQSEU9PzleV2BbbU/Dj4pxf+zZe2OZqZKInKasnNmHhJq1tNGJfYTL+p2MkWphbmhaQnFFRUs/REQ0e22ge8KJfoJ5TXFMc0NIRj48Pk5VUXCVtXtpaXGVbm6QlsR6XmZOTW94hnlvgX9eY3GEem1whn1aqXuchLbLzMiAxcTAvbm0sbCxqmzNjp+aoJS8laVznoV8hW90Y2NXZGlqaW1rd4aZlZ6moYljd5qmkpmruJOfcXKBmaO3oZKpwcLu1J7pw457s46goaSkoKKjoKGgn5ubmZaYlZeTlJeUlY+Tk5uNkZKOjY2PjpCOj46OkY2LjIuLjYuJh4WFh4YDh4SChYaAhIaFhoWGhIKGg4iHiIaIiIuJiImKkIyJiYyMkYqKjIyKh4qIiYuPkY+RkpeSlpWUlpicn56en6Chpqqmq6mtsLGxuLthu75hYmNkcWZoaGdqbG5vc3N0d3l4fXt+gYeHiIuLjI+TlJiZnJ6go6apqrCvtLW5YV9fYGFjZGZoaWcRaWtrbGxtb29xcXFycnR1dXaFdwp2eHh5eHl4eHl5hXcPdnZ1dnZ4d3Z2eHh3d3VzhHIccXBwcG9vb2xra2pramloaGZlZGNjYmNiYmJhYYZfMV5eX727ur24ubi4ubm3q2afgrVkerqCgsR7kmeoZpqBZ8G9pKarq7C0rq6wrrCurbKEryeosKKkhaOwr6WntbS1lJmvc6uwX2BgY2JiY1+wnGNoamlramttbm8JTUxNTU5OTU9QhFKFVIBWV1hYWVtdXF1dXWNgYWJkZGVnZ2doaWprbG1ucHByc3N2d3d3enp7e3x9fn9+fn59fT8/QD9AgH9/f4CAgIKBgoKAgYGCgYKCg4OEg4SGh4iLioyNj5CSk5OVlpiZnZ6dn6OkpaanqaqtrbCys7S0t7m8uby9v7/AwcDAwcLDw1rDwcDAvb+9vby7urm4uLi5urm4t7W0s7OzsrOupmObkKatrKyrqqiopqWjo6OhoJ6dnJNxc5phb4pjb1WJd4JvWZ5nbo1zzHN1dItnYnOWdHV9im5RV3uAgoCEgS+Cg4NBSWFXbj5BQUGCgoKBgYGAf399e3p6eXl3dnZ1cnBvbm1sa2loZ2czMzIxMoQxH2FjY2RlZWdnaGVmZGNkZGVmaGlqa2pqamxubnBwcG+EcIBvcHBxcHFycnN0dHZ3d3pxd3xxyrPMhJCatHGvbWi60I57l5p6gI+RkpSVmJaXmJ6dnZ+RwaJ+gHVTgHBfcVFWaF5eWbOsrcLRrcdnc2xmf3R8e3CLlsugcHCmz4ldopRtnXWne1qKg3xydX+fu3p+a4ueaHBubGF4WWFdZFVnWYBiYFxeWl1XloyShpVhxJGZgYjjxnZikrymnLfCybb7l4aq1NfmmpSb4PicgJl6e5KTjXTPdnV2aG5tYJOEsInjsqWqhnSXcMNvbmZYUEtVVE1kfrl4ZV5Zh21wkpzEf2ZpUE1tcYFqXW5nREhVZFZKVWhlR4lng2yitbaysLGvroCuq6mnqqBiuoiOgH91mneIXIl1aHRodGdnW2RpamdqaG50gHZ/jIh5X3GTnIF/ipFwelNZZXaBln9lb3pxiHlZfIVxZ2xml5eanZianZuampqWlpWSk5CRjIqOjIyGi4iOfoOCfXx8fHp7d3dzdXdzdXd1dnd1c3JwcHJxcm5tcIBzc3Nxb3FucG9wb21ubHFvcnByc3ZzcG5vcm9ubnFwcmprbG5ubXFubm9wcG9vcHdwdnVzdHV5fHx7fn17fn96fn6ChIaGioxJjI5JSUtMVk1OTUxNTlBPUVFSU1RTWFVZW2BfX2BfX2FiY2dnaWtub3Nzc3Z0dnV2Pz49Pz9BQRFCQ0RCQ0VFRkdHSEdJSEpLSoRLBExLTU6ETTdOTU5OT1BQUFFSUlNSUlJTVFVUVVVXWFZWVVRTVFRVVFNTU1JSU1JSUVFTUlJRUVBPT01OTU1NhEwOSklJSElHRkVFh4WEhoKFg0+BdkNtWXZFVnpZRVwvQDFhRU1ARXB8c3Z4d3p+d3l7enx6e4J/fn5+d31xclZxf391d4J/gF9lez91eUNFRUdISEdEfWxGSktKS0lKS0xMxICFgfyAD39+ent6enp8e3p7ent+fYR6EXl6enp+gH56ent8fn9+fX1/ioAFgYB+gICEgZ6AiYG2gAN/f36EfwF+hH0FfH5/f3+QgAZ+fH59e3uKeod5i3oEfH9/fYR7hHoEfHx8e4Z6g3uYeoV5B3p8fH9+fXuEegF/hoAOf39+f4B/fn59fHx+fnuHegF5h3qEe4R6BXt6enp5inoGe36AgH98hXoBe4d6AXuOegV5enp7fo5/B35/fX17enqEewJ8e6p6CHt7e31+fnt9/34Gfn5+f35+rn/ogI9/EX5/fn1+fHt7e3x8fnt8f31+pX+IgIJ/ioACAgQAQZ6goaKkpaanqKirqqytr7CytLS0tbe3ubu8vsDBxcTHyc7O0tPV19nb3ePl5uvs8fP4+fv9/f7/gIGCgoKDhISFhYYZhYeGhYSDgoGB/vz6+vb18/Du7+vp6+jq6ITnCubm5eTm5OTj5OWE5gTl5OXohOeA6Ofp6+zq7uzu7ezu8e7v8O/v8e/u7/Dw8fP08/X39vf19fT0+Pj4+vj5+/r7+/r7/Pnxz8DvwYex3uj39/b18u/t7e3q5+fk4t/59PWPwu7CyMOFnIqtu/fLnJG+m4GHlYyKrpms6JOJ68rPl/7OgN7m6+vt8vX5/YDGi7DOkK2At4/tgP348/Du7O3s7e3w8PHy9/n5+Pf5/f6BgoODg4aJi42NjpCSk5OQh4P98uvh1s3EvLewqaOfmZeXko+Pjo2NjIyNjYyLi4uMioqIi4uMi4uLiYqKiouMiMuevOq67IOdgK21z4Hk79mTkOK/mpiJ6persbCxsrKxtKWwqZ6Az4eLjezesq6pqqOTi5yJh4SIifHy6+f1kpafwvi9qqaM2q3gmKy25+S5tvzwo6PTja231ojI3cu3tJntioOTlZSPjZOMjIyF+JCNiYSRmJSQoJ6kkIqE9vDZy879hZ+Gmq6xtt6jx8eGlYDaxLG6dpGsrb6TmJPCqs7AraHIpZaAhoyYj5CQjYf38vWHrcHZzIaChe79iY+ViY6G+YGGjrig9Iqr8IzOpuWEyrjT3MfHkJrivLafmJmQlZiajZyVg9DikaST8N389fLv6ejp4d3c18vu9p/q96K+9dPW36So56+ZnZeoppqRkZeak5iampaeqIq1rPqboqWNkbaVhpuAiY2wvtq+qLa81Ofy6u6J04rnlsnbwYWrt7jDt728vL67vLq3t7W4uba4srOvsa+0tLWur6+rqqSiqKuoq6emq6qsq6etqKarrrKwsK2uqquvrKixrK+0tLu1tK+rq6qora+ssLGxrqusq6mysa62u7e7vb7Bwry7vby8ub29wr+AwMLDytLL18vP09ni4uLq6u309fb2+PyA/YKFhYmLi5GSkJOXmJ2Ynp+ioqiqra2zuLm9v77Ex8nS2tvi6efu8PH0+f7/goeFhYaHi4yOkpOVmJydn6KhoaKio6SlpKWmqKipra+vr7Gwrq2uq6qqqqmpqqmpqaqoqampp6SloqALoaGfnp6cnJmYlpeFlBGTkpKQj42LioiHhoiHh4aFhoSFYoaFh4aGhoOEgoKAgIH+/oCAgf787POowva1kaSK+ISPzYjdlf6igOKS9ouD6+/u8vH08/X18/Te5+nNnJSX8oSi5uzloaTR186FxquUiev3hYmIioqRpJGGjZCPkZOUlZmaOnJzdHR1dnZ3d3l7enx+f3+BgoOEhYeHiYqLjI6PkZCSk5WWmJibnJ6goaSmp6qqra+xsbOztLW1XFyEXQNeXl2IXhBdXV1cXF23trW1s7KxsbCyhLA8sbGwsbO0s7W2tbe1tre4uru9v8DBwsPGx8fHycvKzM7PztHR1dTU2OHX2NnY2dvZ2tvb29zd3Nrb3NzdhNxw393e393e397e3dvb3NjOsKHKpnKavMPQz87NzMvKysnHxMTAvLnV1tR9jqp/hIJnlHd4f7/Je3ymimxlc2xsi3yFr3lks5SrecupX6Onqampq6ytrliJbIKaYHR8ZKNZsa+trayrq6qqqaqqqqmqqoSpMqqpVVVVVFRVVldXV1hYWVpaWVVTpJ+cmJSPjImJiIaEhIKBg39+f359fn19fn9/f4GChISChoSHgIaHh4iJiYXNorrgreJ5jXihq8R52+7ReX/duJOUgNaQo6mpq6yurrGjraify4KKbqiYZVxUVEw8OEI3NTM2OFFRT0tWPz9FZo5fUVNGYHPZmq5zo5NdV5qNSUp5WVBTbUxhd2lhmYGsRTlEREFCRUxKTE5If1RQUU1WWlBKUFBSgEdEQnx4YVVXg0ljcYRubm6wjM/ZipaA28+7zniOsJ2kgpCUzZmCaVVKblFKOz9HQUBBQjxweX5ObZComWRPPV5hOUBGP0FAgEROWIx3wWyIunJsSYdXqmamqpqTaWKjf35sZ25obHFzY2pmVXmLZHZsyLnRy8nFv72+t7S0sajLgMyBqLSHoaaGjZiAh6JlUFZUY2FgWVpjamJxe3yBjo9ymIWzeISKfYGtin2SfICepr6Ze5CUt9Xw6/WL0YTZkLnEtYCcoqGqnKCfnp+cnJuWl5SVl5WWk5SRk5CUlJSOjpGOj4yMkZOQkIyLjouMiYaJhoGEhYeGh4aGhYaKh4OJgISEhoSIhYaGhomJiIqIhYeHiYeHiIiHjYuHjZCNjYqLjo6Njo+PkJCUlZmWmJqYnaOcqp+ipaesqamur6+0tLW2uLxgv2JlZmhpaGxram1xc3d1eHl7fICDg4OIjIyOkJCUlpecoKCkp6aqrK6xtbi5X2JgYGJjZWZnamprbW5vE3BxcXJzdHV2dnZ3eHp6enx9fHuEfAJ+fIV7GX18fHt8e3t6e3t6fHp6e3x7enl4d3Rzc3OEcg5xcG9ubm1sa2tpaGZnZoVlBGRkY2OFYlxgYWBhYF9fu7peXV23t6q1eI24gnCBdMRpYZhqrHDKg2DBes5sZa+wr7KvsLCysbGzo6yvmHBrba5ddauvqXJ0nKOdZJmCa2Kts2BjY2RlZodzY2lrbGxtbm5wcTlOT09OT09QUVJTVFNUVVZVV1hYWVpbW11eXl5fX2JhY2RmZmhoaWlpa2ttbm9wcHJzdHV3eHl6ez6FPwpAP0BAQD9AQEBBhUCAP0CAgIGCgYKCgoOFhISGhoeHh4iJi4yNj4+SkpOVl5iZmpyeoKCjqKmqq66vrrGys7S3t7u8vMC/wcPFxMbHxsbHxsbHxsXDw8TDw8LBv7/Bvry9ury/vb28ubi4ta6TiZ5zW3qaoq6tra2sqqipqaelpaOhn7ezsl6LqICAeFxQdlxhbJ2kbWmagH5rfnBicmZxn2JQn36LXZFxRICEhYSDhIOEhEJjO0FdOlJaR3dChISDg4SDg4GAfn18enl5eHZ0cnFwbzc2NTUzMzQ0MzOGMoAxMmVkZmdnZ2hmaGZlZWZlZ2xqamxra2xsbW9wcHBxcXNycXFycnN0c3NydHN1dnZ0somgyJi3X29gg4yeW7q/tWxvxKJ9fXDBfo+VlpiZm5qdkZuVjbR2fXG5q3lxaWtlWVVlVlZaZGu5w7u2wnFucZC3hHR5bKeMtm6PeaehcYBtsKZjZo9kcHeTY4mdi32We7hiW2dnZGVjaGVjZF2jY15cU11jX2BqbHBiXVmimomEi7xpfX2QfHZ0tXrF25iokfzm0eGHnK2ep4iRls2UkoN4dZ+FfGlsdmxqbGtnwL/DbZObx7p8Z2avt2RkZVlXUJRJTE5oXK1lgMF4gl6VXICmcImLd3dbU5Rxcl5XXFVYWVZIWFhKcYVidm6sorm1s7KwsLKuq6unnLGPZIOPanyKanB5aG2MYldgYHBtZ2BfZmtibHNycXyDZ4+FtXyFgm1qimlcb15heIWce2BqaHqIkoJ/S4VecFV7h45tkJmaqJedm5qbl5aUjo6Li4yMjoCKjIiJhIaEgnp6end4dXR3eHV0cnN3dnl4dXl0bnBwcnBxcHFwcHFuaW9rbG9vdG9vbWtub29ycW9vbW5qaGppaW9saG1vbG9tcHJ0cXBvb29ucnFzcXJ2d3uBeIN5eX1/hIJ/hISDiImJiYqMSI1JS0tNTUxPTkxNUFBUUVVUVUdVWFhZWl5fX2FiYWRkZWpsa25wb3NzdHV3eHc8Pj4+P0BCQkJERERFR0dISUlJSktKS0tKS0tMTExNT05OUE9PT1FQUFFSUoVTCFRTVVVXV1dYiFdOWFdWVlZVVVVWVlVUVVRUU1NTUlFQUE9OTU1OTUxNS0tJSklJSUdHRkZERESEhEJCQoB/dHJMY3dhSE9EbTspU0BlSHU8NlQycTs6eHp5hHsnfHx8f253emdFQEJaMEZ3e3dKSGdqSDhfTz40dXxFR0ZHR0VjT0NHhEoES0tNTbiAl4HfgIJ/k4AEf39/foV6HXt9e3p6ent7e3x9e3p6ent/gH16e3x6ent7en1/iYAGgYB+e3x/hIABgZaAkoGvgIR/Bn59fn5/foV9A3x8fYR+gn+NgAR/fn57j3qFeYl6BXl6fX19inoFe3p6enuEegJ8fY16AXmOeoZ5HHp6e3t6enp7e3x+gICAf35+fn9/fnx9e3t/fXuPeg15eXl6ent8e3x7enl5hnoWeXp6ent8foB/fn16enp7e3p7fXt7e496Bnl5enp8fo1/B317fHp6e3uEeoJ7lnoBeZd6CHt7fHp8fHx9/34Efn5/fqx/7YAef3+AgIB/f39+fn59fnx9fXt8e3t9fX59fH57fH5+k38Bfol/AX6Hf4WAA398fYqAAgIEADaTn6Okpqmqq6ytsLCxsbO0trm4uLi7u7++wMPGyc3M0NLU1dfa3N7h5Ofp6+7x8vX7/YCAgYOFhQaGh4eJiouGjICLi4qJiIeGhYSDgoD+/Pv49vPz8vHv7ezt7Ozr7Ozr6uvp6ejo6Onp6+vs7O7v7u/w7e7u8PL08/Ty8/Py9PHx9fX19Pb19vj39vj4+/v8/v77+vr68vT8697Jyt778ef55r3m5Ifph4/z15iqwPX9/Pn49PLx7+7t6uHys/LQ+H+Hgo/Hw/PD6JGaqYO1jsaIztmTkdmX99X14Nmxw6nQ24HCxObu8fP2+PH54qbfnJ+KkeOp8oH/+Pb19fH19fb39fb3+v7+gIKCg4SGiIqMjY6PlJaYnJyeoKGkoJWPioP97eHUx8K8tK2qoZyal5ORkpGOjY6PkI+RjZCPkI6PhI6Aj4+OjY6PjI2L+sWixI7ehZGijPHDv5uK9uCsnd/qsMnkrtWjtrW1rqutoaqCqLDBpre5ybigpaykkJSPgon7jYyF+uj11PiCipzoypHXlPfl6qjrx8qvz/WimsCporOqsd2A6urQt5rG96iGjIKPlJWVjPr4i4uEiYmEjY2ZlY6AoJ2bkZaUjp2E1tbx7pCX4IOJi5WqhLDZ+Hy/j++2t4e27OSNl4aWwtbqvqKqp8PDm5COh4WA/O7zhJOVwIv9tYTwy+nxgvWRi4f9hJif1pqSxpbUw8OaocfIgcSbjdu7r7m7sbGVjJuvqKeujJyZmufqhaLM6Pf/+/f28u3q4t2A262FkIvFzOethNa2zpaAtZOSh5Kyy7KlkJ2KlZuPhoCAhoytrp+fjpqKkqmer9K8q6qnmqy9qbCahbfIzeWMkPf+ma+++ejRpr26v7y5srrGs7S1uri2trSwsa6vrrCtsq6xrbCvrKeqqKurq6quq6urrKytq7Cxrq2usaq4rquAq62ur6uytLS/va6trK+trqqqrK62s62xqquwsK2uw7S2uLu5wr/Avr2/u7m9vcLFysnP1c/T2PrY2Nrb4uzt7u7v9fP1+4CAg4KEiYyLkZOXmpyamZ6foKWorK6usbe7wMDDxs3U2N7g4Obs7PL29/3+goOEiIqMi4+OkZaWmZoKnqCgoaOlpaipqoWrEq+vsLG0t7a2tre1tbWzsrKxsoSxgK+xr66sq6qoqKimp6akpKKgoJ2cm5uZmJiXl5eWlJKQjo2Njo2MjIyKiYmHiYqKi4yNi4uKiYmIh4WDhYeFhoOA0rmY8PDCsN679YOqnv/fhdCE8rTP3sH+w4LA+PP2+fj29/v39svQnLCAm9fQlf6wnKSEiODLkZvA/KjwqteEEYqKiYmp667djsSej42Xg5uBN2xzdnd3eXp6e3x+fn+AgYOEhoaHh4qLjo2Oj5CSlJOWmJmbnJ6goaOlp6mqrK6vsLS1W1tcXV2EXg5fYGBhYGBgYWFhYmFgYIVfgF5eXV1ct7e3tra0tbS0s7KytLS1tba4ubm7ury7vb6/wMPDxsfKzM7O0NDR0dTV19fY2Nvb297e5uDf39/i4eLk5OLj4ePi4ePj4eDg4dra4dTLtLTJ4dfL3MumyMN0wnF3ybuDkqXO1dPS0c/NzMrJyMW8w5zAucJgT2WMjb2eQMRlbXt0npm1d7SyeHSqecWjsbOvi4aIm7VtjpGorK2trq6prZx1qGVmXVmUfaFZs7CwsbGvsK+vrqytra2urVaFVyVYWFlZWVhaW1xeXl5fYGFfWldWU6OdmZOOjIqJh4eDgoGAf3+BhICAgYGBgoCDg4WFhoeHiIiKioqJioyJiof0wJazf7pvco571ZyhiXjixZeUyeS2zt+r1pytrq6nqKqfqIKmpaGZpZSHbk9RU0s6PTowOF46OTRaTV9JbT49TZJpWHdFbmV4nN61imiIoXFnYk1HV1FYe0mCipNgUIm2Z0RDNzw+QUaAP3V7SUxJTUtJVVFZUEZOT01GUVNTZEdeXnR1TlS5SUxOVWd+ps36gMR86ba6h7ziznBmb3RzeYJbRlFQbW1HQUQ8OTdqa3hIWFp7bauPWJOyVWI4ZUM+QIFGXGOTfHqac6KcY0JrbnVYmWdnlYF8h4qEhmtibYF4enpWYWFdfoaAVXevvMbNysfFwr68t7a1lXBwZnBvhopNeWKFdWV3U1FIUXeQfXNfbV5jaGFUU1haZ4qIfHhpeG14lYyiw6ePioZ9hpB8hHRpqcDE3oWCx9SHj5zk0raSoZ6hnp2WnqmampialpSVlJSWlZaUlJGTj5KPkZCPjY+Mj42OjZCLjYqAiYaFgYaGg4WGiYaSiYeGhoaHg4iHho+OhYaIiouMiIiHh4yIhYeFhoqJh4eWjIyJjImMjY+PjpCRkpaWl5eZmJ2inqOnxaelpqWqr6+wsbK4ubm+YWFkY2Vqa2lsbXBzdXV1ent7foCDhYSHjI6SkJGTl5yfoqKipqmprbGzt7g3Xl9hY2RmZmhoaWxsbm5wcXFxc3R1d3l5e3p6e3x+fn9+f4F/f4CBf39/fn9/fn9/gIGBf4B+f4l+c3x8e3l5d3Z2dnV0dHNzcnFxcG9ubWxsamlpaWhoZ2ZnZ2ZmZGVjZGNjY2RkY2FhYl9hXlyYh2+xtJOLpobJcoJto6pirmbLnKO+lc2JY462sbOzsrGzt7S0l51zeFZsnJhpt39wdl9goJdrc5G1dqt4l1+EZQ12toCjapV7a2hvYXFfPkZNT05PUVFSU1NVVVVWV1dYWlpaW1xcX19fYGFiZGRmZ2hpaWtsbG1vcHBwcXJyc3Z3PDw8PT4/P0A/QEFAhUEGQEFBQkFBikACgYKEgwKEhoSHgIiJiYqNj5GSlJWYmJubnZ2goaSmqaytrrGwsbK1t7m6vLy/wMHEwbnIysrKzMzNz8zKysjJycjKycfFxsW/v8Oxo4iHmrmxqLeoh6SbXKBDSJCPZ3WEqK6ura2rqquqqailn6OIopqjVkNoh4u4hapdZW5dfWypZcKzZmOBX52LDMCpoIRkdoOGRmVyhYSGI4V/g3VTXzo8ODFUTGdChYSEhYaEhoSDgX18enp6dzs7OTk4hDcwNjU0NDQzNDMyMjM0MzIyMTFkZWZmZ2doZ2dnZmdpa2trbm5sbW5wcnFzcHNzdHN0hHOEdIB1dnR1c9Gndo5fn1tkdWWqhYZwab6qiIS50qG2x5a3iJiZmZOSlIqSbpGIh4aNi5aDZ21tZ1ZbXFdgr2tvbc3J17jcb2x4uZNvoW23qrOKr5mQdZy8gHqBa2RzbHaeX6iunH5okrx5XGJYYmRoamK1s2VmX2BdWmNkcWljcG9sY4BpaGVzWomMoaZoa65YWltibXaQsPN4u3X4q8mTtPm/a2txeYeTo4Bvfn+cmG9naF9fW7KssGNxdqGR5apvsa2lsV+uZlpUmktbXH1ZVoRvpo96WXOCiFyEVFCMd293eXR1W1JobWNgYktbXmCFjVl0mo+uuLW0t7SxsKunpn5XVoBQZ2qCakpvVnJpWW5XWlVcf5N8c2NwZm54cWVgYmRqjIt/g3R8aWh6bnydhnNubWhzfWZiSzpkamV5UE2XklxUXIeMlYSZlJmXlJCXn5CQkZOQj46NioyIiIaCf396fnp8fH14eHd3dHZ1dnV3dXV0dHF1dHFwcHJueXFvb29wcQxscHBvd3ZtbW5wcHKEbmxybmlsaWlubGlodmxtbG9scG9wcXBxb29zdHh4fHt+f3h6f56Afn9+gYeHhomHiYiJi0ZFSUhJTU5LTk5QUFJQT1JUU1dXWltaXGBgY2FhYmVnaGxram5wcHN1dXZ3PD09P0BBQEFBQkRERkaESBxJS0tNTk5PT05OTk9PTk1OUE9QUFJSU1RUVVRUhFMPVFRWVlhYWFlYWVpZWVpZhFoZWVhXWFdXWFhXWFdXVlZVVVRUU1JQUFBOToRNBkxLS0lJSIRHWEZFRkdFRkRCallMeIFqY3JVeEVjSVxsQl9EYEJfZVBwSDpffnp8fHt6en17fGRTSD8qMlJNMXBWRkg0MlFMLTZLaThcTmpESEhHRkuRX29IY1FIRks/TTyygKGB2IACf4CEfgF/kIAMf316e3p7fHt6enp7hHoNfX58e317en17fX9/fIR6Bnt7enp8f4uACX17e3x9fXx/gZCAmoGsgAN/f36HfRB8e3t8fHx7fH19fn99fn9/i4AFf318e3uLegR5enp6hXmFegl7enp5eXl7e3uEeoJ7h3oEe3p6e416gnmUeoR5iHoSfHt7fX59fH18fn98fXt7en18j3qDeYR6Dnx6e3t6e3l5enl6enp5hHoPfX19f39+enp7enp7fH58knoGeXl6enp9jH8NfXt7enp6fHt6enp7e696hHsGfHt7e3x9/n6sf/SAGX9+f357e3t6enp8fHt6e319fnt8fXx+fXyNfwJ+f4d+hX+IfoJ/hYAHf3p8f4B/foaAAgIEAICNoqilqqytr7Gztba3uLu7u7y+vr/AwsLGyMnM0NHV1tnZ2tze4uTp6+/x8/b6+/6AgYOFhomKioqLiYuMjY6PkJCRkZCQkZGRkJCOjYuLiomHhoOBgP/++/j29PPz8vLy8fHv7ezs7O3u7u/x8PDy8fLy8vPx8/b19/j5+/v9+wP9/fyE+wH5hP0C/P6FgICBgYKCg4H18vrDiZHBl4PJr/SZ6OKGlMrn4ID9tpbU1ZqFsYGBgP78+vr59vTxnKG4nsa1082XwJzZ1I/C5ciIq96AoJDDv9WdhoaZpJOD5anc9oSIkp/q8OLz7Z+2wbXw1qHWrZiD26vZgP38+/n4+fn6+/7/gIKDhYaIiIqLjDaQkpWYnJ6iqquusbW7vr6wpp+ZkYn36NrPxry3saujoJyZlZOSkI+Nj5CVlpSWlJSVlJOTk5KEkYCPjYqKioTGqYOK4dXU0eiP74H9kvrqrK2eto3LwIem+LW6t7Ofgpy3x4PQlPDlwZ2LlpOgppyanI+A8/yJkYacm/3t5dmRu5a26rKt75D9kK3gjrOji5e64cCioouos8Kwws6kloeduYT53Oz3iYeD9PaIj4z/if2ai5+inbGeq4CnlYCHk5yJ8oj0+9eC7OLq9/+KsbLY9cTB7bLLz5OyiYe1xcL88dqupJym0sqUk4WIhIeDjpKxtq6Kgu/xs8yGivP0+fqE/OyMpaeImYXCuJPH+dbN4avO8salmKGiqq6os6my+MyZja/fqoyL6NryipvuzqyL+ffx8Ojr24uEnYDAr6W0ssHqvrajp5yY/+iCnp+nmpOcnIfhjJWNioOSiZ+looWO7P2PlKCprr+khoSEnKe5p5CoycigyZHG0fm/z5OjrKmSvsTCvcCxura2t7m6trOxsKurqKqqqqSwsq+xs62usa6ssKusq6qrqKiqqbCwr66wq66rra+qqq2qr0Ous6+vr62xvKylrKyur6yvr7CwsLWtqqyysLa5sbG1t7W7v76+uLu3uL7Fx8bM2tjTz8/O0dLV3dza4ufu8PL2+PyAhIKAhIqQjo6RlZqdnp6ipKWmqrCwsru9wMXGy8zY1t3e5uru7/L1+/6ChYeJioyQj5CUlpeZnZ2go6enq6qsra6usbCysbG0tLW2uLi6uby9vr+9vLm4tri3uLe3tba1tLOztLCwrayqqaenpqemo6Cfnp2cm5uam5iYl5aVk5GRkZAVkI+OjI6Ojo2Ojo6PkI+Pjo2NjIyKhItTjPGAlvaFzL/PtJ/C/pa6gu/HhdmWk+Xvk8aRqsKnzvf9/vv79fTr59XkmO3LtpGCqrH5z5GK66WhtNKBs+TJlNeFhbiC++KVptHr3NXM4JLc7YQ9Z3d5eHp8fH5/gYKDhIaHhoeIiYqMjY+PkpOTlZeXmpufn6Cho6Wmqaqtrq6xtLW3XFxeXl9gYWFhYmFjY4RkGWNkZGNjZGNjY2JiYmFhYF9dXV1cXLq5uLeEtiq1t7i5urm6ury8vsDBwsTDxcjJy83P0tLT1dbY2drc3d/e4OLi4uTk6e+E6QPn6XaFdWp0dXR1dN3a4rB/h7KVd6ae4oKwxneGtMrMduGQdsfTiHiWbW1s1tXT09DPzcuCiaaJlH6Pr36YdJSQYnmkfH6dyFtnWpaJmWpYY2pgWk+Ib4SbVWx1eqisoaypdYSbjK+ZYIVvYWGmeZlahLQKs7OzsrCxsVlZWIRZgFpaWltcXl9gX2BlZGVmZmhpamRgXltYVaOemJSQjIuJh4SEgoKAgYGBg4GCg4WGhIWFhYeIiIqMjIyNjY6Mi4eIiILFp3N1u7yvp9h83XDcfeb2o6WaqZHJvIWi862yrquZgJewwIDJep3PlHdRV0hKS0E9QDwuW2M3PTVKTmJbgGFdTG9RZJRjX2JJf1WItXV1WWiEj4prS0o3TFJeS11rSUU7Vnk8aUlNUzY1NGBoPUdGf0eCWUpUT0ZPQ09QSUBLWWBNfUl4e1JAcWZxfH9Lc3GUtcbI+9Hn6p6bZ22ksLujkn1RSkpWioRLSz9DPUU+T1Frd3BOQmtcXodlPWJfgF1dMmVrUXJ7e49yqZp0o5h5boVbgaF+YFhgYmtwb394hM+ibVx1nWJGRGNnillxyKOQdcfFwcC6vbJ1dn9yYVdeXG2Ma25hZmFfk39Ra29zaGRqa1mFV1tRTklYW3mChmlwt8VzeYaUmauObmZieYCPemqGsr+Zzo2updGls36QgJONfqGlop2hk56cnJubmpWUlZiWl5aXlZSPlpSTk5OOjpCPjpCLjo+Oj4yKioiMiYeHh4SIhomMiImLiIuJi4iHh4SKlIeEiYiKioaJhoaHh42Hh4iKiIuLhoeLjYyOkZGRjpGSkpebmpeZo6GenqKkpaanq6qorbG0tre4vL1gQ2FiY2Nla3FubG5wc3V3eXx+f3+Ch4eIjY6Qk5OVl6Geo6Knqqytr7G2uF1fYGFiY2ZmaGpsbG1wb3BydHR3dnh6e3uEfUd+gIGCgoOCg4KDhISFhISDg4KEg4SDg4KDgoGBgoSCg4KCgoGBgH+Afnx7enp6eXh4dnd1dHRzcnBvb29ubmxramtqamlpaIZnXGZnZmZlZGNjYmOoXG2dYqF8jXx4h8F/m2SlpWyyene8sXSUaHyOeZW1t7W0tLGzq6mfoGuafnpiWnRxpJBiX510aX6fW3yrkGybYGGMYLqgc3Ois6uul55roKtgRENPUU9RUVFTVFVWV1dYWVlaW1xdXl5fX2BhYWJkZGZnaWlpamxtbnBxc3N0dXd3eDw8PT4+QEFBQUJAQkJCQ0REQ0NDhEIDQ0JCh0FCQkFBQYSEhYWGh4mKioqLi42Oj5KUlpmbnJ6goKKkpaiqrbGytLe4uru9v7/BwcTGx8jKy8rFz9DQ0dHTamloaGdohGd8ZsG+xplsbX9QO0ddnVt0gFZbjZOcX6RnYIeTc1lzWFhXr6+ur6+vrapuYG9ngHGDjFiMbpaSXYeZeVt4qFt0apWSoWJbYXODeGimapSbT008VIOFe4SBVF9dVnNcSmVSRUJyU25DhoeIiIeGhoWDgX4+Pj08PDs6OTg3N4Q2AzU1OIc1gDMyMjEyMmZoZ2hoZ2hpaGhqamtqa2xrbWxucHN0c3R0dHZ2dXd3dnV2dXd1c3BwcWumjF1ZlpiTka5qsWC8cNrKjJOIk3mnn3GMzpecmZWDbIObqGmtaoysgXNha2Jpa15bYmFZsbxocWmCidHHyrx8oXmLuYqIsW7CcIy9c4FvgGqCnKqLaWZRa3aIeIuadWxbdZNgtpWeqGNhXq+uXmZgq1ulaF1rZ2FtXmppW05ZZXBgpmGssYZZoJSeqqdafXmTq7u9+dPr8qSja4Gtma/Qwq1+d3J6qZpmZ11hX2NcZmiCi4ltZ763jp9lar63s6xVmY1YbGpWYFGAbHOSm4F6gI9liKiEY1lfXGFkY3BncreLXExkk2NNUH98mV5loYZ8ZLu5tbWvsqZjVVNkWlVhYW2LZmVWYGBipJhfd3x8bGpwcWSha3ZrZl5nYnl8f2lzvsdrbHJ3eopyVVJUam93X0dbeHdSeVhuj5VnbEBNTUZnkJSVkpWLlZGSkpORjYuKgIyIiYaHhIJ8goB/fn96fHx7eHt1d3d2dnRycXB0cXBwb2xwbW9xbW1vbHBucG1tbmxxhG5pbWtsbWtvbW9xb3Vva2xta25uaWptbmxwcXFvbXBubW90dXV5g395dnd3ent+g4N/hIiIiYmIiotGR0ZHR0dNVFBOT1BRUlJRU1VUR1VYXVxbYWFgYmJjY2pmamlsbnBwcnR2eDw+PT4+QEJBQkNERERHRkdJS0pMS0xNTU5QT1FQUFJRUlFRUFFQUlNTVFRUU1VUhFUMVFRWV1hZWltaXFtchFsnWltbWllZWFhZWVpZWllYWFdXVVVUVVRUUlFQUFBPTk1NTExMS0tKhEmGSFJ5Oj5YQGpYbmBkcJRQY0hlaEhiRkJVaEFePExeTmR8f39+fXp7cW5nUDhNQDQmKTNEVlUyMEs6MDlMJjVPT0RnRERdQn9sUWKIc2RqYWZHYWs+sICmgbiAi4GGgBp/fn59fn+Af3+Af39+fn57fH17fH5/gIGBgYmAFH57e3p6en1+e3t6ent7enp9f317hXoPe3t8e3p6enl6eXl6e3x/h4AEfnx+fYR7BXx+gICBi4CfgauABn9+fXx8fYZ8Dnp8fH5+fH19f35+f39/iIAIf39/fHp7e3uKeoJ5hXqEeYd6DHl6eXp7e3t6ent+e5F6hHkLenp6eXl6enp5enmPegZ5enl5eXqFeYV6DX19fX59fXx7e318fX2XegZ5eXp7fHqEeQ16eXl6enp7fHt7fH9+m3oIeXl5enp6fX6JfwJ+e416gnmJegF5jHqCeZR6A3t7fId7AX38fqt/9IAbf359en57enp6e3p6fHx8enx9e3x7e3x9fn1+jX8CfX6EfQl+fX59fn5+fX6GfRV+f3+AgH+Af399enp+fnt/f4B/f4ACAgQAQuqHlaattLC1tre5ury9wL/DwsPFxsjKzM3P0tPX2dzc3+He4uXn6ezx8vj6/YCBg4WGh4iLjIyOj5CRkpOVlZaWloWXIJaXlZSTkY+OjYyLiIiGhIOCgYD8+/r5+Pn5+fj29fXzhfQS9fb4+Pj5+fv8+vv8/f3/gICAhoIKg4KCgf6CgIKCgoSDbYSEhYaFhPjy2cLim4bqwKnuls29usyPnLqA3sy/vdyeobDXpPyHhoSEg4KB/fv2xoCtmILFlKexg6WSgIvBtuvZr/Dgtuvm+rGKlo/tgISG9+Xtxtfhj8DJ4ITe7sWGgdfdrpLMnZmlsoftgP+EgICBgYGAgYOEh4iKi4yOkJOWmZyepKuyt73Aw8XM2t/kyLuxqZ+Rgu/j1su/ta+qpJ6bl5SSkpCPj5KUl5ucnZyanJyampmYl5WWl5WQkeu4mIXXgtHq8NGfs42os6GO6pOK2cvDl6SVrqnhq7+8oIzbk/7Erc2ju4XF0p+Vk6eWkYCJjomMhIqDiIaI9ff89umajoigsqGLp6S3pbmMl9zJ6qHg4o+OiZ+3raqtqJynppymvaOah4SNkITi39nS+4n9hYeKkJihlaGgoJWPjI+Ngf75gOD+793X6NX09fKZjZCg1Mf73aSzpZ+izMTbt6uxzaSkkKKFyZiNjZKUkpSL8YDwl6TXhv37g52DgYCDiP+I74axopyZjp+38vzUrbix3qTjvKaytaCloa+ttK2coJSA+7j/2/Cf5MfE2/LjtJmR//z69+7m24KxkKGipKmnncTAtLOhg/z/9OiLh5qElKWrqI/0h42E95en2L2XlquUhvL7jJqwtsmwgeyFmZyqp4CbpLCs5aD9+cCKydDJuqS9g8TGwsG8uraztLW1tLOzsK+xr7K3s7CzsK2vqa6rsq6xsrWzubaysKuwr6+vrrSytbGsq6uqqKypq6+urLCsqq+qsqutr6qyrq6xtrKwsrOxr7KysrOvt7S1try+wLq6vbi8wMXJydLT1NjQzdLY0FLY2dvZ4+jr7vTy9f6Bg4eGhIeKh42PlpmZoKOlpKeoqq6vtbu+wcbP0dTY4drk5u/08fP5/4CAhomMj5CRk5OWl5ibnKKlqKmqrLGztLO2tra1hLlJu7u6vcDBwsLExcXFxMK/wL+8vr+9vL27uLy5trWzsrGwraurq6qoqaimo6SioaGfoJ6gnpybm5eXlZSUk5SVlJOTk5SUlJWVlISSWo6Pj5CQjYyPguXRlY+Bv4Ljg7y2qqbIh/jsrN6H1bW596rBz+CX2oKDg4L9rdb35aOcn8CWuufHuo+ryJrxhJ/Bu9Kx6f/Iyb2Mz9qLp+yHkJnDq+bG7LbLhj+rYmx4fYB+gYKDhYWHiIqJjYuLjY6QkpOUlpeYmpyenqGjoaWnqKqsr6+ytLZcXV5eX2BhY2NjZGVmZmdnaGiGZxFoaGdnZmZmZWRjYmFgXl9fX4RegLq6ubm4ubq7vLy8vr7AwcPExcbIy8vNz9HV19ja3d3f4HBwcHJyc3R0dXZ2d3nxeXd4eXh4eXp5enh4eHZ03NrIuNCDcuaxia1vt5iYuXuGkl+ok4WHqIiRj7WH0nBvbW1tbGvU09CmbZiijo58j4BbenZ3g7iktJ+c2bB+wLDMLKhGSkBWNT1Fhn2QeI+gcIaXqWWgqpZjW4aJeV6bcWB5iWGmW7dcW1tbXFxbhlo6W1tcXF1dXl9gYGJlZ2lrbG5ucHV3eW9qZWFeWlWjnpiTj4uJiIeEg4KBgYKBgoGEhYaIiIiJh4uOjYWQgJGRkIuM5LKThdR3vcfHooKJe4ChlYbPloTYuLSZtZCiotiltrKYhM+N+MCd4nmYj6KJWk5GU0E+OTs3ODU5MTEuL1BXX2BVSEA9SVVGOF9ffG6ZdXOHb5SGhIZkOzVIXU1GRDowPENBUG5TSzcxNjszUlteV39JgkRAPkFBQzlCgERIREJETU1Dgnw+XnBcVVloYHp6f2BTUVuJfrnJjm5nY2Oeup1qVVV9S0tAVGmCTERIUFRVVGeklX6FpEJyZzVFMzk5NjRULlZEdXBvZ11se8bVy6SghYhcmXlkc3pgY15maHVwZGtmV6mM0qapWWhTVmmFunlzcsfFxcO8uLBne5dqU1BOT01GZWloaV5GgIiCgFJRX05ebXZxXJVTVEyMX3aulnVugG1jsbhre4mNnoNWq2Nzcnt+c4GSk9SZ7OaogL63w6iJn22joZ+fnJ6dnZubmZeWl5aXmJaXmZOSk5CQkoyPjJCMjI2OjZKRj4yIjIqIh4SJh4qKh4SJgIqIiIqJiYqGhYiEioaHioaNiYeIiIWCg4aFhIaGh4iHjYmLjI+RlZKTlpaZmpualpqZm6CfoKOppKmpqaivsrO1urq9wWJiZGRjZWlobG1xcnB1eHx7f3+BhYWJjY+RlJqcnZ+ooaemrLCvsLS4XFxfYWNlZmZoaWttbnBwc3R1E3V1d3t9fX5/gICAgoKCg4aGhYaEiAGJhIg3iYeJiYeIiIeIiIeFh4eGhISGhoeGhYSEg4KCgX9+fnx8fHp7eXp4dnV1cnJxb25tbm5tbGxra4RqX2loaGlqaGdoaGhkYmRbn4VhbWuIVZZkjpCKkHtk39CPu22irJHNepOcpmidXVxcW7F7mreseHtlfFxzk39yZ3ODZZ5WboaAinOpt5OZi2qxnGh6sFFcf6J4qYupg5FhR2o+Rk5SVVJUVVVWVldYWllcXFxeXl9gYWFiZGRmZ2hoaWtoa25vb3Fzc3V2dzw8PT4+Pz9AQUFCQkNCQ0NEREVFRUREQ0REhUMBQoZBOEJCQUJCQ0OHiImJiYuMjY+Rk5SVmJqcn6Gjpaipq62vs7e5vcDBwsNiYmFjZGVmZ2doaWlpyWpphmsoamtpaWhnZL68rI+ZZFGTcXaka5BnYIJCXnBOlXtqZ4BmanWEaKlbWoVZQ7OysI9bd21udVt2el57bWhihZKXjnanooTEvNCUZmtlo11iZbWlsYmVlmBgdoRFeHFiRDprcltKbElBTFU8ekSJRUSERQREQkA/hT0QOzo6OTk5ODc3NjY1NjY2NYQ2BzMzMjIzMzOHaA5paWlqamtqbGxsbXFzdYR2gHV4enl7enl4dnd4d3JzuI92YphYjJyliGlzbHeNf3C/fHK7qZ98d3mJiLKNn5yCbadxzZ1qtFhzbYGWcWhicGBcV15cY2FnY2hmaMbGzdDBgHZrdoZ3ZYSBl4icfH6ei6Z6q7F5YFVpf3h3d3JocnNodpN+emZiaW5krKullbljgK1XVlVdYmRaY2NlXVVUXVtTpqNUlK6akJOimbiwrXZpZm+ai8i/jHZubnOsx7yNf4WrenpodGuTYVtfZmtoaGu1p4mUvXHVx2BvXGFkZWSrVotSdGlhWlJhb5Orn3BybpBinoBsfIBnaGBpaG9pW19aSopztpSZXYJrbnyDjmZfgGm8uru5s6+oWmRVWl9jZmRadnNtaV9KlaWlo2Zkb1hmdXx7abJnal2oZ3WiiWpugnBgoKlgaXp5i3JKkFJfXGBcVF9sY5Nwn5ZZTXJse2xVaVyUmJeWlpWRj46Ni4qJiIiHiIaHioWDhH99fXZ6d3x5eXp6eHx6dXRxc3Jxb2xxgGxtbWptbG1tb2xsbGppa2pqbmxybGtuaG9tbm9zcW5wcW1ra2lra2pwbG1qbm1wbnBxbXBwcnNyeHh5e3h1eX16gYKDf4SIhoaKh4eMR0dJSklLTkxPTk9PTVFSVVNXV1haWlxeYWFiZWdnaXBqb25ydXJzdXg8Oz0+P0BAQUFCgERDREVER0hJSUlKTU5OTlBRUVFTU1NUVFRSUlNRUVJTVFVWVldVVldUVVZVV1lZWFxcW1tcXF1cW1tbXFxcXV1bWlpaW1xcXVxcW1lYWFZWVlVUU1RTUlJRUVFQT09OTUxLS0tISUlJSkhHSUJpbExIRGRDcUpjXlhWVUJ9bVNzPkJaYE9yQ1NhaD5qQEBAP3lNZGtxQD8vPCo0RTo1MjdCM0koNDYyNjNUYURfXkaRdF9dnFVYUHBkaVZsTFc+AX+sgK2BoYCNgQGAj4EFgICAf36EfAt7ent7fH17fnx8fIV6Bnt8fn+AgIeBhIAHf3t8fHp8foR7FX19e3x6enx+fHp7e3p7enp6eXp6eoZ5Anp8hH8RfXt/fHp6e3t8fHt8fn+AgYCqgaeAHX9/f359fXx7e3t8e319fH1+fX9+fn59fXx+f39/hYCEfwZ7fHt7fXuReoV5i3oKe3x7enp6fHp6e5V6hXkCenmQegN5eXqKeYd6gnuFegF9iXoBe4h6CXt6en19e3p5eYd6A3l6eYh6Bnt8fXx9e5J6AXmFeoV5A3p6fIl/An17jHqEeYl6BXl6enp5iXqCeYd6AXmKegd7enp6e3x7hHz8fql/94AYfn58e319e3p+fHt7e3p7e3x7fHt6fH58hX4Bf4SAB39/f35+fX2HfIR9GHx9fXx8fH19fXt+f396fHt8enp6fHt6foR/AYACAgQAaciIs7S1tbm5u72/wsTExsbJy8zMztDR1NbY2Nva3eLk5+vo8O/y9vf6/f2BgoWGiIiMjpCQkpKTlJeYmZqbnZ2enZ2enp6dnJyamZaVlJOSkY+MioiGhYSEg4OBgoCA/v7+/Pr4+fn7+YT7Bf39+///hYAGgYKDhIWFh4cHiImIiIaEhoSHJ/mF/OfX3OHBzK3AvLST4svJuL7D7+rAtr3Oraepqa+5wYau+cG/h4SMgIqJh4aFgtH9qL61yrqlm4eJhYSloNCqorav4Y+cyqaElNTcitf2gZj8gOfI5dfK967g7NjHwsnbqpy41ZPGsKO7luHcgIGBgoKDgoKCg4WHiIuNjpKWmJyfo6irsrnE0NXX3eLygYKH7NbNv6mbiv3s1cq9tbCqpaKenJGF8s2zD4qVl5uhqbCvpaShoJ2bnISbgJiUg8DSot+m8qWI3P2ntsfY8Lq7zJmh1PzGvIzz6ZbYqsCqpviLmpGOu+b607uksJyTkZyPjYf7goH6/oaGiYf18vTr7oj/9palnpqYq7TT0r6Ki4nXyp2EzKOA/5OcsLiiq6igpqqrqaSnoqCVjIDk++P7/YqDgfmHio+Fj5efgI2LjoiKg/X9hOrq+urVz+bg39TY8/iRqKS6ucfAiPqZ95T0kp+WmZ6uiZCSlY2YnKqilJqJlPCCscORmI+TlJWIg4X/gIHvmaicjP78/Z22zKbMhM3rvLGemp2cj5GqrbOlnZejnJecscuKlvOSlun/mL7ygIODgYD85OSIl97GgKmNiJWLiaWonZKZmf/24eT+gPP48YqOk5uPhZKOjYKlrab1ibnNqKSbjpagk5meoaS1vKSwno2kvrK29tbGppnY1NDF7rfJn8jDwby/vLu0trW4u7e0s7CxsbG1tbO3trSzsbKvsbeyt7Kzsq2vqLGwraupr6uurKuqq6epr62tgK+sramqqq2ura6vsq+trLKvta+zrbKysrO4tLezsrKzsrmxurm9uLe7wcLHysvLys3N0NHS1dvZ3N7n6ujt6vH2+YKDg4KDhoeJjZGZnJ6ipaaoqaywtLO4usHGy9bR2Nnf4uTq7/L2+f2Cg4eLi5CSkpWXl5ubnZ+kqqyur7K1Lba4trm6ur6+wMDAwsLDxMbGxsrIy8vLysnGxMXHxcfFw8LBwMDAvr29uba3tIWzHrGyrq2sqqmnpqampKakoaCfnpyamJiZmJaXmJaWl4SYX5mWmJeXlpOSk5OSwbr3kMzsi528rp7wyezysrSgssDP4KiGmt2Qo5Xr3KzIg4SEhfGxq47Gpr7wxZ6f5v7Ph9WNqr/w3+LOuMCc1IeVw5Hpn4iNj/eCkaOykqabl7f9P55hf4GCgoaGhoiJiouMjY2PkJCRk5SWl5mamp2cn6GjpaikrK2vs7S1trZcXV9gYGFjZWZmZ2ZoaGpqa2trbItrD2ppaGhnZmVjYmJhYWBfX4ZeJru7vb2+vcDBw8LFxsjLzc/O09VsbW5vcHFyc3R0dHV1dnd4eXp6hHwCfXyEfSfne+jVxcnEoqiOp6umdZyIhHN6eqKXaGhygm9ucHV0eoFejcqboW+EcoBxcG5tbWyw14+Qg5h/foNeTU5NamjFkmJ1b6lbZpB8Z1ROUTxLZDxSgUaFe5CJfJ95n6qkmJijqmhUYHFehXx8gWqgnlxdXV5eXl1cXFtcXFtcXF1fYWBhYmNkZmlrbnN2d3l6fkFCRX11cGpjXliqo5qVj4yKiIeGhYV7c9GztoCNh4eIioyOkIyPkJKTlJWWlpWVk459ttKg2qX3oH+9zouWrr7XpK67i4zS6LStf+PZjtOht6Ke6oKPkpOsnKaCc15rXFBISzY2LlIwMF1eLigpJ0VHT1lbO2xiOz84OkBZbYeFbFtdWYJyTV6zTS5YOj5JSjQ5NzdDS1JPSUxCPYA3NC9VbVh1eEZBPGo8OTovMjpGOztFRUlEd3g9YlZfWFFNaGpqaXJ7bkVTVGRldXVIil2Jd3tMVEpJTFpASlBTR09TX1tWXVFcrGWSm1BPQEE7QDs3M1QoKVRUaGdbm5icbIikk7Bok6F0dWRhZmleYXNzeHFpZHBtbnaKnlSDs4BRWYqEX4OtYWRkY2PBrq5ve7uGX0E4QTw9VV9gWmNilox3eIhDeoqGV19ja2BSWVdUSXGDgbtlj5x2cWRfbXVtcHh7fYyTfYqCcY2pm5/UtKeGlOHKy7/ZnKqEoaCfnKCfn5mZmZqcmZiXlZWTkpSUk5eXlpWSkY2Mj42Pjo+PjFWMho+Ni4mGi4iJiYmKiIaHi4mIioiKh4aHh4eEhYWJiIaGioaJg4aChoeGhoqHiYeJiY2MkIuRkpiUlZiYmJmam5qboJ6hoKGkqqqsrrW2tLi4u769hGKAZGZoaWxuc3R0d3l7fX+ChYmIi4yRlJiem56fo6Wmqq2wtLW4Xl9hYmJlZmZoamtvcHBydXh5eXl7fX+BgIKDgoWEhYaGiImKi4yMi42MjYuLjIyMi4yOjY6NjIyLi4uKiYqKiYmLiYmJiIeGhIWEhISCgYB/f399fnx6eHd3dnMEcnJycYRwZW9ubm1sbGxqa2trbGppamloiIKvaIypb32Qb2WwnK6xeHptgJepv4Znj692dWqpmnqQXl5dXad9gW+lhpuke2Vhk5+CU41ecH+WjJSIdn9xmmRykHGlY09YY55aa4CMaYN3hX+3Amo/hFUvV1ZWVldZWVlbW11eXl9gYGFiY2RkZmVnaWlrbWhvb3Bzc3V3dzw9Pj4+P0BCQ0KEQw1EQ0RFRUZGR0ZGRkVGhEUCREOEQoBDQkJCQUFCQkNERERFRUWKi4yPkZKWmJycn6Kkpqmrq7CyW1xfX2FjZGRlZmZnaGhpamtrbG1tbWtpbW1ubm/La8enk5iKbG9niJWOYJSGin2Jjrixh4GIkX55dXNrbWxIaKB5flhcXFtcW1tbWltalrxwenCDc31yXmhoZ4BujTCDXm5poVhqm3tueKSma5y0XnCrV6GLno57imV8d3Jka3SFVUlUYEhUVFJWRHRzREWERoBFRURBQEA+Pj49PT07Ojo5OTc3NzY3NzY4NzgbGho0MzIxNTU0aWlnZ2ZnZ2doaGhpW1idfldacXJ0dXd5e3h7e3x7e3t6e3p7eHRkjph6rHuoeWCOnmt2i6S4k5infIK6yKySbMS3dqWInIiCw2ZpbG9/dH5nZV5yaGNhZ1ZUTICUVFmxumJjZmW/vcPLznbezHB1a2lnfImjoIdnaGeeknJuwXhSomNsfoJsdG5qb3d+fn2Cenhxa2KtvZu0rVxWUptbXVxRVltjVU9UVFZRl51SlZSfnJaVtbqzpai3rWR2c4OCko1dtnu9eb9we3JxdINma21qW2Rlcm9mbWBlsIBkmrNyeGpsZmtpaWi+WVOLZHBoW5qdqHGJlm6KVXyWe31saW1sXV1tbXFlWlVcVldba4VJXZ5UWpeNWnWRWWBgX165p6hfX4t6YlBNWFVTZmtlWmNmqKmfq8Fgr6yhYWZsc2lgaWRgUnR+eKlijJpzZ19XX2lfZWxqbHV4ZW5fUm9rgXJznn1wTF1+eomRuo5+fKCcmZaYlJONjYuNj4yLioiIhoWIhoOFg4GAfH16eXx4eXd4d3V2cnh2dXFrcGxtbG1vbmxub2tqaWdqaGprbGxpaWhsamlpcGxxbm9sb25sam1qbGpraWppbGZsb3SFb4BwcnJxcXRzd3d7gIaEhYKFhoKFhYuMi0hJSElLTE1MTU1QUFBSVFVUV1lZW1pcXGBhY2dlZ2dra2tvcXJ0dXc8PT5AQEJCQUJCQkVEREdISktLS01OTk9OUFFRU1NUVFRVVFVVVlVUVVRVVldYWFhWVlhWWFhYWVlbXF1dXF5dXQxfXl5fYGBgXmBeX1+EXgVfX11eXYRab1lYV1dXVlRUU1JRUFBPTk5PTE5NTUxKSUpKSl9bfEZnd0NPTVlQfGCFiFteUV9mcXBcQ1ZzTUZCcGpOYkBAQD9wT0Y7WEZTTD03N1RYRidGMDw5SkNHRFBUSGY9T2pHZ2xbZGuqWGNvb0NRQ1BTeQF9qoC0gZOAnYECgIGIgAR/fn17k3oFe3x+foCLgQOAf32EegN8fXuFegJ7fId6DHt7enl5enl5enp5eoZ5jHoIe3x7fX+AgIChgYOCh4GOgIN/loABf4R+In1+fn17fH18fX1+f31+fX19fn1+fn+Af4CAgH9/f35/fnuNegV5enp5eYR6hXkDenl5inoLe3t7enp6e3t6enmTeoV5BHp6enmNegN5eXqNeYh6BXl6eXt5lHoDe358iXoEeXp6eYR6C3l5eXp6enx7fn17lXoBe4R6BXl7enx/hICEfwJ+e416hXkEenl5eY16AXmcegh7fHt8fX19fPt+qH/1gA1/f39+f35+fXx6ent9hnoBfYR7CXx7fX99en1+f4SACX9/fn59fHx7e4d8gn2FfIR+BX1+fnx8hHoBeYR6Bn98fnt/fgICBABjnP+2t7m7vcDAw8XHycvNztDQ0tTV19rc3uDi4eTp6+3w8u72/P+AgIGEhYaIi4yNj5GUlZeXmZqbnZ2goKGjpKWlpaalpqOjoqGfnpuamZeVlJKQj4uKiYmHhoaEg4ODhISBhIAg////gP+AgICBgoKDhISFhYWHh4iJiouLjIyLi4yMjI2Fi3WMjPLhxevE24mBodTHv9jNtcK1s7KrmpiiopqboqOpq8mSnKnWnqCa6ZKUlJOSkI6Ni4mFlKz/t6uqsbWYnI+A8/j/kpGPm5ejhYabk4yJgfL2gI+IloyJ7OmGltiFjZCakJ+OsJusqrDIkL/Dr5XVza//gYGEg4CBg4WHiIqNkJSYnZ+iqKyyuMLL2ub1/oCEkpqhqY//9ffKsJSF7tTDl/OFqqmlls+WqqTleIaZmp6qw4HDuLCopaKhoaGjoqGc1c/ew5bfn56arZWX3bnLpMKtpNmAiIG12NCBipL8qvqjyNefyOr3/dXLxL+ru8eyoqufjZCPlICfnojy7Yfy6dfp19bdgev1jaGZnaaWf6+Ypoyxr8Wh+ba/pv3r6oudsbW3rK2yraqwoKGimZ+R+ejk5YKDg4SC9oL/hIeDiYaCiIiD/4j9i4nw9O/qx7fM9s25x9zii4+PkIOLgofl/4eI8K+kpqqanouNh4CDk5CRn6ihmYWi2oCqs42EhYmOgfCB6/WFhoilm4yUnJWbsa/M07WJnvDHopifko6orK+qq6SgsMXOs7DFs4GIhYuT3dv22sqJ5Pvc4KnpwsimwcuWko6FiqCflJKjlpD/joiOgun56uaNkJeE9JS1qsfDxqa0h5qkmp+Wus/NvJGZorCfn8u9wdSesD/exuuXkZmJ9PWDmYSzruSMyMK+vb29tba7tri5urW5uLi9uLm8u727u7e5trK1t7Cws7Kzu7SxsKqvr66xsbKErYCqqaqnrqusqKWnqqyqsLO0s62tq66wuLO3tLWys7avs7O0srGvr7G6uLW4t8TIycnEw8LKyczR1NTW19PX2+Tr7e7+9/iCgYWDiIiIiYqKkZaam6Kmqqyura+0tbe8v8XJzNHT19ng5evy8vf9g4SHh4uOkJOXmJqdnqChoqSpqxevs7W5urq9vr7AwcLEwsPExcfLyszPz4TRMNLS0tHPzMzMzszKycnHxcbFxMC+u7m6uru9ubm3tLKysa+vraqrqqmop6alop6enYScaZqbnJ2dnpyen6Cfnp6dnJybmpiWl4ewwIO/gLqQmZWh472bu62z8Lyj2sSC/o7J+qKtu9Cy8bmw+fTqouyjvJzOsMeZncLFsuKy1ridi6Gwy7iWtvzV0qnUnqSCitr+1oOFkJ/plI/pljuWsYGDhYeJi4qMjY6OkJKSk5SVl5eZnJ2foqOipKioqauspq+0tlxcXV5fYGFkZGVlZ2hpamtsbG5ubopvQHBvb25ubWxramloZ2ZlZGVjY2JhYWBgX15eXl9gX19gYWLFxsllzGdoaWpsbG5vcXJzdHZ3d3d4enp7fX1+f4CEgYCAgYGCguLQtdm5zH1njbGSlp2OfYB1bGpnWVVdWVNWVFdiZI52j4qsgId8vXd4eHd2dHNycXBueJXFfWtobGhuVVRKhHZ/UU5LWVNlUU5bUkY7MEtIKjg7SUhKg41YZ35SV1phWmxgh3B2cWxxXnuehnGnnIS5Xl5fXl5eXF1dXoBdXF1eYGFkZGRmZmhscHN4fIKGQ0RKTFBUSYV+f25lXVilmZBzwGqJiod7rXOUl+vPk4mIio+SI0uSlpWVl5eXmpqZmJTCvdC7kdqhoJengY3Ypa2FppWXtG5waqDMw3mDie6c4J3EzZOp0NLIl4h7dWdzg3FfYU46ODg9SEY0VYBHLEQ6OEpDR0YwVFQvOzM0QqCNW0VNNlVVcUlYYG1VY1BMMDQ8OjcvNz5AQ0g7Pjo1PDNWU1ddQENDQTpnN14vMi0yODU9QUJ9RntCP2FiWltIQVmDYVBeYFk+QEREOkA7Q2yBRkZ2aFxWV0tUSE5JQUFLR0ZTYF5cUnrLq4BURIA/OzoxVi9KTi8yPGRhX2VqYGR2boyOblpvsHJdWWFbWnF0b2pmZWN5k6SSkKaMUFFJSm2SssmskWmqvaetg76fq5abhExIQjg+UVBMVmNdWJJWUFdKeYR+gVhjbWC3dJGGpJmhg5Nmb3lnbGWHpKeVbnN7hHd9rJ+htn6OvaPTi4CGkoT493qLdJiIvXGjoJ+foKKcnZ+cnJ2clpiVlJiTkpSUk5KUkZGPjo6QjY2Pjo6TjIqLhouMioyLi4mIiImGhoeFi4qKiIaHiIeEhoiIh4ODgYODhoKEgoWEhomFiImLi4qNi42UkJGVk5ycmZqWlpecnJ2ho6OlqKWoqq+1tlS2wLy8YV9iYWVlZ2hpaW1wcXF1d3p8f3+BhoeJjo6TlZibnqChpqmrr66xtV1eYF9hY2RmaWlsbm9ydHR2eXp7fX6AgYGEhYWGh4mKiouMjY+RkJCEkh+RkJCRkpKSkJCRkZCPkI+Pjo6Oj42Njo2Njo6Oi4uKhIcehYWEg4SDgoF/fX16eHh3dnR0c3FxcnJxcW5vb3Bwhm5YbWxra155il2MYJZvcGJypYZtgXV0rYN3l4NaoluEvnx8kpyNtY2GrqeecauEi2qTdIBdXnd7b4d4mIBsWGRzkXpqjMmelnakb3tQXqDAoF1icZPZgHKtdDVSclVVVlZXV1dYWVpaXF1fYF9gYmFhY2RlZ2dmZ2pqamxtaW9ydDo6Ozw9PT9AP0BBQUNERYVGEkNGRkdISEdHRkdGR0ZGRkVFRIlDYUFCQkNEREVERERFRkdHSElLTJqdoFKmVFZYWFpbXV9hY2RmaGhpaWprbG1tbm5vb29wb3BvcHBxccKxlbdsWzwuSnNmcZSLeYB6e3+BdnWCf3Rxbm90co5icG+HYGRilV6EX4BdXV1cXFpogqduZWlydG5rcGe7r65kXFVdV2pWWW9qZWVho6VZZmNuYl6ppGFphlBUUFFHVExvX21nZGdJR1dLR2NoW4xHRkdGR0hIRkJCQD8+Pj49PTw6Ojk5Nzc2Nzc3NhwbHBsaGRoyMTA0NTY1aWRfS31JZmhmVXEqJyMlAIBIcnN1eXsaO3t+fXx9fHt8fXt6doyKnpBwqHFpZnZfabyIlnWYh4SkYGZWh62kaHBxuGOreJSXYHCRk5t0a2FeVGyAdWxxY1JSVmBsbmCuqGSyqai3rbS0bMnDanRmZmyGdHxsdFx/gJp5tpSegbmioF9qd3d1aW1ubnJ4bXV1cIB8b72wpJ9fXVhXUp5arFdbV1paVFZXV6NZpFxcp7GuspuVsd+yl5+knmJlZ2VYYVlfpb5oasSRhYKCc3lsbmNYWGVgYG54cWxZgsurkHJpZ2RmY79pvLxiXlt3bWRobWZqfneQj21VW5J2ZGFpZWF0dm9nYVpTX292Z2V5bURKR4BMX46Xs5eDXpqvmaB3mHt9XHN6UlVUTldrZl5ibWVgqGRiamCpr5yYXWBnV5xmgnaRiI5xgV9xfW5uYH+VloZhZm54aGODdHaHXG+ch6llXmFKmJdNXlJ2fpxlm5iYmZmZkI+QiouMjIiKiYiLhoaIh4eFhYKAfnp7fHVzdXR1e4B3d3dzd3ZycnBua2xubmxta2dsZmdmZWlrbWxvb25taGlobGxycHFucG1rbWdpaWppaGdmZ2xqbXFvdnRwb2tra25vb3R3enp9e3p9gISGhouJikdGR0VJSUpLS0pOT1FPU1RXWFlZWlxcXF5eYmJkZWdnaGxtb3Jxc3c9Pj8/QSFCQkNEQ0REREZGRkhKSktMTE5PT1BQUFFSU1RUVVRUVliGVy9WV1hZWlpaWFhZWlpaW1xcXF1eX15fX19gYGJkY2RjYWFhYmFhYF9gYGBfX15eXYRbClpZWFZVVVRTUlCEUV5PT05OTU1MS0pKQVJcPV9EaE1PUVyAX1VoXV6Sa05jZUdsPFWKT01lYVZ4WU53cGpEXzxCN1BGWD8+TEk9QT5NQDAkLjVITT1JdmFkT3hIYVNcq6uISUhPVX1RPV5EAnp/pIDBgQWAgICBgKKBhIAIf35/f359fnyTegh7fH1+f3+AgIuBAoB8hnoHe3p6enl5eY16gnmGegV5eXp6eY16CXt+fHx9fX+AgJ2Bh4KHgYSAAX+FgAZ/f3x8fH+GgAKCgYyAhX+CfoR9HH5+fH1+fn5/fX5+fH5+fn9/f359fn9+fn17fHuTegN5eXqHeQN6eXmFeoJ8iHoHeXp6enl5eZF6hHmFegN5enmJegV5enl6eo15iHoFeXl6enmTegN7fX6HegR5enl5j3oDe3x7mHoFe3t9fn6HfwV+fn59e416AXmEeoR5hHoBeaF6hHsIenp7e3x9fn35fqh/+IAMf3+AfX59fXt6enp7hnoQfHp6e3t9fHt7ent+fn9+foR/A359fYl8AXuHfAF9hH4GfXt9fHx7iHoGfXp6fX5+AgIEAGyYgLW9wMLExcbJy8zR0dLT1tba293e4OXj5+bp7fDz9/j//oKEhoaHiYqMj5CSlJWXmZqcnp+goqOkpKeoqqysrK6trqusqqqqqKelo6Gem5qYlpWTkI+OjYyLioiGhoeHhYWEgoKBgYGCgoGEgoCEhIaGh4iJiouLjY6PkJCQj5CRkpOTkpGRkJGMh4HPr5GCfpfG1Nv0wrO4ubSvu7iqspeDlaWpqaLV68bmg5ORu96I2JqampmZmJeVkpGO16a2prne2Z+Wj92p6PKOh5WOl6KerIeLi4KA8YDb6IaSkvD9gvTb2e7lgYqahY+IkYCoop+OhdWo14+NnJ+uz9mCg4WFhYOChYeIiYySlZqmzMO4tLq9w83e8ICJk5uktcvo6bnCyc7hxYnPtZGHj4fixaylzIn9nOiXruKXnZ+o1JmJt62upqSio6WkpaSkmY/t7uu++sfX1qvuj6PqtqyL+dvknbWMo+btnvqHlY/mjID03fPx5tTCyq+bpaGppJ22urnGxquqppj/4dfP0dXm2Mzo4PXojZWZnvjCn4DCm4qHkqKuhuLkqZbu8YCFl6KeoKaunamjop+ioZmQgeDo9oH8hIGFjIr+iOXy/u3hgfaHkZuRi5Di5uHc2dS8lsnB3e79jaCTloONhu7Y5PWBjICGj4Wfko+Eh42FmIqSj5yRkoaDi4OL+/ni5P/z/ePX3OuBhp6xoJCgkqadq62oq7O80t3Bo6SilZyUk66llI6SqsbEvKuToPyVhOWcjqP0irOm3Y2Hgt3P8/fg3qCIgfaGj/uTkYmSjoyPlKCo++Dw5e6PnaGklJ+moNfQxqSLo4CokfOCpbK4tMuTytW6p5+msMW+nJWswojdk4Le94yXqLOTxd+nvLzBxb++wM+7u76+urW3ubi+ubq7uL2/vby9t7m7uLi5t7S4urm0sra2s7SztbSzsKqrraasqqiqqKGmqLysq7CyvLSxsa2oq661s7S2t7q7tLq3tK+1s661sIC0tri1vr3DyMnFwMDIzszT1dXU29zd6+fx9Iv9gYSDhoWHh4mLjY6Rl5ubn6OorK6xtbe5uL2/x8zR1dnc4Oft8fP5/oKGiIqLjpKUl5qbn6OkpqWqrLG2t7m8vLzAwsTHyMjKy8rMzdDP0dPU1tjY2dzc3Nvb2Nfa2dnW1NTT04DPz8zKyMfFw8PEwsHAv7+/u7m4t7Sysa+trqyrqqiop6WkoqCfoKCfoqOho6Gjo6SlpaGlpaajpKGenJnBj/LMkei1hYaClIiNksmdmYqmmIy4nebUubCrooG1s62RmYTMxNLjtZXLwePRrJriopGC25/b4pKxqpqItoicubG2jxDVpvCOuJylq5C4wYWhgJm3RHtTfoaJioyNjo+QkJOUlZaZmZucnZ2goqGlpKapq6yur7OvW1xeXl9hYmNkZWZoaWprbG1ub3BxcnJydHNzdHNzdHR0hXOAcnFwbm1samlpZ2dmZGRjY2JiYWFgYGBhYGFiYWJjY2RmZ2dpamtsbm9xcnR2d3h5enx9fn9/gICCg4SFhoWFhoaHg313vqCEeISax9fOxot8e3V0aWxfVV5OQlRjamZUgZRw0H2Jepa1b618fHt7e3p5eHZ1dLSNfXCEr4hTRT83nnJqbUFAT0ZRWldmT1BKQz5iNEhKNkVJaXxDhXh/kYFITVVCTUtbdGxna1t+aIdgaHJ0hKShX4RggF5dX19fXl9hYWNodXFsaWxtcHV8gUNGS1BVW2FtblxeYFlgVDtcYGRQW1WkkoaClHDWhsGC1f2HiouPky8PlZ+emZmZm56cnZ6dlYvk4d6x9MHRwJPHma3TnZuK/8jPib99n9fake6NlJzjcN27srWwno2QcV9oZGphV2ZhWV9fgE5SUEdnRzguLzRAOjRGQlVLMjU6P5JpVDp1SDIuNmBOMk1SXEhUVyonLzMtLzY9NUM8PDw6Ojk1Mk5ZZzpzPTo4PjtgNlFaZF1VNmg+Q05IQENRUUlJSko8UlFMZWpuSFNFQTM8PWtfaHJCS0BDPE9GSkFFS0NQREU/TUZIRE1YgFZnpJprX2haX0dEQ0gtNFBrYFpsXW9qdXZ0c3t9jZR/amxtZGhdWmteTktNa4yNj4Vyg89mTGluY4TBbYqAv3t4dba3zNTNsm9QSYBHUIpWVlVZVVBPVmFslX2Tk55jeHt9cnuEfrSuo35pfH1ppFx7h42OoWufqo+AeoeOo516gHiRo3vij3bQ6ICGlpaEqMWRoJ+goJybnqqam5yempeYmpialZSUkJWWlJKRjY+QjZGRkI6Qko+LiY2Mi4uKi4uMioeIiISIiIaIhYCEhZSHhIWEi4SChIN/goOFg4KDhYeIhoqJiIWLjYqQjo+PkY2TkZSXmpiVlpqdm6GioqOoYKeqsrC2t2i+YGJhZGJjZWZpa2ptcHJyc3V5fH6BhYeJiY2PlJibnqChpKerra6ytl1gYWJiY2ZnaWtsb3JzdXR4enx/f4CBgoKEh4mKi4yNjY2QkZSUlZWUlJWUlZeXloSXNJiXl5WUlZWVlJWSkpOTkpKTlJOSkZCQkIyMjIuJiIiHhoaFg4J/f318fHp5d3d2dHV0c3SEcmBzcm9xcnNxcnFvbmyIZLGWaqaOUVxZa1hYYoJdVEdjV0tyVZiWf3N0dWWDgXxyel2MhJOph2qRj6KYdGKfd2lWpXWbpWuEiXxbi2h0i36AdZ17mWKKdHyGbJK2ZoJrfZxETDZRVVVWV1dYWVpbXl5fX2FgYmNkZGZoZ2loaWprbW5ucW04OTo6Ozw9Pj9AQEFBQ0RERkdHSEhHRkVHR0hJSUhJSEiFR21GRkZFRENCQkNCQkFBQUJCQ0NERERFRkdHSElJSkxOT1FTVFZYWVpdXmBiZGZoamtrbW5vcHBwcXFyc3N0c3JzdHRxa2WhiHBfQzxKX2+lgXh8fHpze3dvfG1hdYWHgXKdr451T2BlfItUh2JihWFEYF9fXpJ+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hoqq+q2h47XCvMLAv7u6srm9u7y+uLy9t7m5vby4vcLBwcPCur26u727u7y5uLeusLOwrbO1srKvramwrauurK2qqKenrbG9rrOrsbCwqautrrC1t7S1t7e2srW0srW6tLu0uLW2uMC+xMPAvsG9xs3UzWHV3+Hj5evz9fPv+oCAgoOEhoeIi46NjI6VlZmgp6mss7i6u77AxMvR0tTX3+Po7vL3+YCChomLio+TlpiZnKChpqeqrbCzuLq8wMPExcXIys3R0NHU1NbV19fX2trc3d/ghOOA5OPi4uHg3+Df3NnT09HQz8/My8zIx8fExcXCwb+9vby5t7W0sbCura2rq6iopqOjpKOjpKOlpqaoqqmsq6Spq6qoqKWah5nSkoDFr4WZgIeFhIOOjMyflpybham+hcSzmrmpwc38tOGL8K/dkouq6+TnuPvf5Z+mrbXagL/J1+0brIbjiaaJ/q305IvSn6eSgIKWnKOgpo6Bq6OzQJ16YYmOj4+Qk5OVlZWXmZqcn5+goaKjpqepqquvsVlaW1xZX2BhYmRkZmdoamxtbm9wcXN0dXd5eHhzeXl6enmFeh15eHd2dXRzcXBubWxqaWlnZ2ZlZGRjY2NhYWFiYoRjH2VlZmhoaGprbW9wcnR2eHl6fH5/gYKDhIWGh4eJioqEi4CKiNfjoaXurYW+0cuYi3dwblpgbFhOSUdmdnFnnHVuq4qSZKbGeqG3prOuboCBgH9/fXx6eXefvqWJl3e2qJ85P25gV1xmSDxKUllcZlZSdT9DNjBNLj49bUVsSkNMX1tQSUU9REmHYG1hUIFZW4CeWZSab6WatF9fXmSmm2BfY4BxeV5TRnpzaUp3cHR0gEtZTU9nSFNXWlZUQGhGRTg7OF1BXkVVRV9BUlSlcY9zcHq68KSHhXm4UIaJncPErcDwx8aliJTRx5jT1aLIvZN5vHpxpPNcfLCuebvB8rGopZGEhIP+k8iogX6DhHx+bndmamZoYV9iYGxVW09AQ0ZQOIAuOjdCJjc6NTE7PFFSSjMxhlJJPE03LUVPbU4yQkCGZD9aKCsrLSgvNDQ6NjRCMjAsMTJeUlZhYTpgOzUyL1VYW1JdZWc4aj48PGVhYlBENzdGR0laRVVkPl9QX0A9L1tQMFhnU2xwdD5MUEpISUKBf0pIQT1BPjdtdnqAkoyPi4BEcU1QP0I7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C3x+fnx/fnx9fn9/hH6EfYV8hHsOfHx6e3t7eXt5eXt6enuHegF8hXoCAgQAPrPh7c7Q0dLU1dja3N7e3uDi5OXn6uzu8PL3+v2BgoOFh4uKkJKUlpeZm52ho6aoqa2vsbKztre5uru1vr7AhMKAw8PCwL++vb25trSxraqopKGem5mYl5STkZKQjo6MjIuKiYmHh4eIh4eIiIeIiYmKjI6PkZKSk5WXmJqbnZ6fnp+foJ+foJ6d/42I2uHF7PHzub67ra+ppaSRoIePhLSHi4OEgZTyjY7Ey9+VroSL/ZGN3efypKajlf2Wh/iMmpiAsYWyktP91v3t0s/b2+2GlKCUko6Liv+Lj4eBiYKD//yXgpCXyIydjIGJhoyG+oCOmJDFhZHDioTn9LPC6J+/peGJvIyRiqLXz7GQ3pXH9JPh16i2o/OqyNSK8piVueHqmp/C0pmnjuvUwpSsuIiQ87+TxduQg8usibp8jZ3LhZKAxWikiJWmqbO/tr2lntWBooTOpqDymMWQuIWKlea8wLmj44CHgIHDp6ayk5qgtaSWjpOehpaPjJaTjomGkJGSlpWZkZKfnZKHhNO4r8bJy8jg4Pza4oCUp53VlaOIi4KHgqKE2ePd7P2FioyGiY6OkJiSmZyXipiRhIDz5v717eyA4u/5+On29PqI/IOB9/CBgoL7gOHFyM/P8OXf6P6Eh/mIq/yF+4WPnvCDlojih/6Cl6KYhYD4/YGChYmMjKeGg+2AjYP5h4iZjOLPxMrm6euChJ6Oh42WmJeIhpaflJWOk5iKgYf++PL6goWNjo+XjY6ah/qnorqVgIWBg4mgk5WAvXmwjfqA84G+pZCFg4aIioOA9OuJ48DJwbjSh++AkKelk5OKlJmil5qjipagm66Y3t+ZmZGgtLGKnZ/BtrKzraOelZ+CptbU+faAuKSM9N+UhKq0vLu9vcC7wMTCwsXBv7u7u7m3usC8vcDEv8S/ucTEvMLEw7/AwLu/t7GxsrQXsbKzra+rrqyxqrCvsauusa+xua6xrqyEq4CqrbGvrLKwtLOvrq20srjPub+7u7i7w8THxcTDusC+zdDP09fa2t3k7PH19vT++4CAgoGFiIeMjY+RlZaaoKGorK+xt7i8vcLCyszQ1tzg5+3v9v2BhISHjI2SlZeanJ6ipKatr7O2ubq9wcTHysvMzc/Q0tbY2d3f397e3uDl5YDm6Ojq7Ozt7O3s7O3u7e3s6+bk4ODd2djW1dLRz83OzszLycbDwsC/vLu5uba1tLGyr6+tqqiop6moqamqqKqqq6ytrrOqsbGurbGG8NnO95OAjZCEirXq6PmOnsSYnKyYourZj4CAgI29r4fGps6Gv+njq7Owvdr92Y6hkpraxyOv7JezusWM+6eH1NCb8OqOgNyCjZSqsZT6hoyBj+ycp6Wcmz1tqaaOkJKSlJWWmJmbm5ueoKKjpaanqaursLKzW1tbXV1gXWJkZWZoaWprbm9xcXJ1dnh5e319f39/eYB/hIFDgIGBgH5+fXt7eHd1dHJwb25samloZ2dlZWRlZGNjY2RkZGVmZWZnaWlpa21ucHJ0dXd5e35/gIKEhoiJioyMjY2OjoSQgI+O5IF90ty1xsXCgYF8b3RubWlYZFJVRHJfT09OSFl7SkhylZ5edV1v1HVyrqW6gIJ+dMd3bMZwendmXGCRrK9/aWhVSk5NXTZBTUVHTklBcD46LSowLTBna0s+SU90SU5ANTk2PTtuPkpUSWVLVHZNSqLEjIWhc4N3oWloSk1PgGKBdFhGbElleUdvY0xSRGJDVWlCb0JBTFxiQkROUz1BNl9YWEJOWT5Cb15Ke5hoY6KRb6WCiY+fXGS3g+qHjZaPlJ2anZGEunaLbKqHg81/qX+qcXtrmWpqZE2MV1xierifm6aZoZiNeGNXWF5HV0xJVVNQTktRUUlHRkdFSlhTgEU5MjckHC80NzE/RWFKTS4+S0J/d1A5MSouJUIuPklHUFkwLiolKCouLjQxNDYzLDUyKytYUWVgZGxfamxoV11WVjVXMDFXWTM0NGIyTj1CSUxlX1FRXTEzWjhUZDdcMjlMTjE+M1Q9bTpJU0tDPXeCPjs7ODUzSTAvZEBPS4pKgEdRQFBIQkBNS0ctLkY+QEZPU1NGRlNgXVpVWmJZUlWjnJCRSklVXF5nZGl8bM2Rh5pwVVNFTFJtZZCbgsF7u162XoBsXVFUUk9TTUR3d0htVl1XVG5ThEtXamZbWFNdYnBrc3pqdoJ/jXORk25tZXaHhmBzdpSUk5ylnpqPlGuOgMTE6+t9qI51ybJ2bpGYnpyenKCanJ+bnJ+dnJmbmpeWlpqWl5aalpeUk5iYkZWVlZGTkZGWkYyNjY2LjIyIioeIhomEh4eHgYOE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pqinqaChmpSQobWVnO+sv6a0r5qntKGWh5SPgI6Qs/XVmIKAg4qNqoi6vc7HqMTI4teIiY3Jip3Ipo+7rvm++MC0nfXN7O/e0eHh0d6ChP7+/OX4hfuOmpWNjoD5+oWQi4mOmvzl4N/5gouRgIaHjoWWqs3e27WNmOC/1eec5srjhdjx2sbj34acms23lKuF0eqGyuiYguvRpu2ajq6yxpWB2qb8v4Pu086S8tjrvKeL8IWUgu+VnYjajPHK68nhhHa8mrzK4oqRkYDcz5jyofzKvZKR2uWcuIi+xc7Gs6mtzNLM0/rB5ZSX3dmtgIqPhouCiOjj3/rqgfnygYmRi4ySnJWLg4aJ9e7Wv67CvsPG8u2P9YCAjY2ShZ21mrKRif2GhOP144uLk5yKjoL55/GC9vr78YiHgoKC9+79+P/i7ODY29vv4vLs8PTz6d/t6/X44N+2x87cm5OB/4mSh4rQkY/+ho+RlpiQnailgIeVlZGRh4L369uH/YOJlouPjYeKj66I6PPngYfexrHDwNbg3uH57oWUnamzn5GlkY2PiIaBjpb7gIf+4uT3l6GVhI35jqKhroeFicH39OCAk5KVnZX0vZL9i7GLhuOEi/7x9tr34tzz4Mq0vabM9e+ElaKWlI394v2QmaKdkKSugKKJ/vujrK+MkafFs52Oo7a2rcvL6c2b/JPa45mAqbrzm8WnyffE6LO8xMfHxsTHxcbAw8TCxMC7vsW+v7zAv77DwL3BvL7Bvby8u76+tLOysLSzs7CysbGusK+1srm2ta+ws6+zsLG0sq2orq6psK+yr6+wra6uq6+urrO0tLvEgMHCvLzGv8THxMbFv8TEyc3Pztfa4evr7u/t7/b1/oOBhImKi46RkJSWlpudoKSprLS5ub7AxcbO1NXb5Obq8vX9gYOEh4qPk5eZmp6hpKisr7O1uru9wMLGyszO0NHU1dfY39/j5OXj5OXm6enu7/Dz8/X29vf59/n29/f19O7wgO3r5+Pi393a2tnX39fS0tHPzNDIx8bCwsC/vby4t7WysbGvra+rrKyuqq+rqK6ytLa5urG7vLm4tu6skeCY7cSGlYL0mPHg4qeWm6bLvYzuvKKTmo6Dk7axjo7r5ZXCvbCUlaCl/IrSib6wrLqb5ejxoqbO1ZzP9IadhY6Flb2eEZ787ouA89fg+ffzguCbwv2bWomDdJKSlZSWmZmbnJ2foJ+jpKenqKqsrbCzW1tcXV9gYmNhZWdpamxub3Fzc3Z4en1+foKBhIWGhoaBh4iIh4eHiIeHh4aFhIF/fnt5eHd0cnFvbWtqaWdnZollgGZnaGhoamtsbW9wcnV2eHp9f4GEhomLjY+QkpSUk5OVlJaWl4+Ri4eNnKyLd7KXh214b11ja1xRSFFLPUdObnK2ZkVDREdQd2t7eYOMcWtqg3dWU3CWVmBraleQj8uXi2BRRFNtVVdNQUtJQ1E4O3R0enF5PWY8PzcwMCdRWjM/gD8+RVR8YltVYzY9QTs8PztCUGp4e2RbYpKQi41ghHJ0UHShzKaro2dtXWtgUFdGbHE+W2RAN2VqU3VKPUdFTT83WkRlSzNgk19CcGNqV0Y8Z0VcU5tqf221abmQr564c4nrkKewwXR8fmu1q37IeLyUiW9tqLSBm2h+hol+bV1hgIGKiZrSvuOVmOq7imZkWFZJSm1nY3hwQH1zPkVOQT9ITktGPT0+YFQ/LR4sKSotQ0g4UispNTU4cGxYPksvKTwnKDhIPCwqLjIjJiI/QEguUlRTTDArKSsrVFFcXGxaYVtQTUxXR1RbXGBjWlJaV1hdSUwxP0hXVEo1XjQ2LjJxgDk+ZDQ3NDQ1LjpFSztFR0ZJQ0V3amA8ZTMyODEzNTQ8RmtNfohuPT1VSz1HP0xQTElYUDdFUFxkU0pbUFFSTE1MWF+SU1irjoqZaHFkVV+dY3d1hVxVWWaOgHhLX2Jqb4fssnfJboRiXIhTW495hWt5Z2RoXlI9RDtehH1HVF1NgE5HfHGPX213c2l7h3thqqF1en5eaYKjlYB1i6akoMO/4L+GzXrD0IxymKfXiLmJsdnBy5KYn6CgnZuem5yZnJ6bnJeTl5uWmZaXlpWXlZWXlZmcl5WVkpSTjIyKiYuKiomMioqIiIWIhIiHhIGChIGCf4CCgoB/hIaCh4aGhIWEgISFhYSFhYWHhoaIjYyMi4yRio2Pi5KUlJmYnJ2emp+hpayur7Oztrq4vWBfYWNkZWZpaWxvcXR2eHp8foSHiIuNkZKVmZqdpKeprbC1XF1eYWJmaGlra25vcnV3eXt8f4CBg4SHioyOj5CTlJWWmpqcnZ+foKKjpKSmpqWnp6ioBaipqqmphaYxpKajo6KhoqGhoKGgoaWhn5+fnpygm5qa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laq5wcDRgrqd0ajqhpST8rn1kOavkojZpZPmyM7Vm6e9tL+tp7CvqrO3yLvIi/ufg4D59tf95evhwtXS7oKQjoD7gPLr+YCH+YiLgYbwy8nBr8fFu7fZ093vgOyChYemieCUnMmghIGI/IDxlpmlnJqPhsjM39fYhenx+P6B8u768/7q7ePh1drKvr/h8+v45eTWw9fl5vbo59rn2+/j9IeLm6KrqYiIk4HwiY+Uo5qnwsapmqSRlpOQgebHzuv6/Y6QiYiG7oCbgdq7lobg+OyGwMzY2djM1++BiIyGsLSXjZSMhZeSlJmfl5WunI+JhYLjg/f/jYKKpZujlozSxsjh0N3j48GckoHJj7H69eni6v+F8u7ow8HM0tDTwcrPvdOQipGSgoKCiIja7IudppuQuJ3jyNGapZeojYyGm5Sjo5++urfAxIDQyamaqbHPh7uru5KcvJDX65KPyI+lyc/R0NTOzsvIx8PBoLmyxcLAw8LBwcLDw8bDw8PBwLq8wLu6u7m8ubmxsLSxsairrrK2tbi5tbCwsrOttLCvtK6vrKiqp6unqrCrsK6urbCtrq+zuLqytrW3vr2/wsLAw7+6xMrU0dXZ24De5ubn6O7v8+35gYKDg4iLlpGQlJWWmp6gpaWssri8wMXGyc/S1uHk6uz3+IGDhYiJjY+UmZyipKaqrbCxtry9wMTHyMvP0tTX293g4uPm6e3s7O/v7PHy9fj5+v7+gICAgYGCgYGA//z69/Xz8vHs6ubl4uDf397e3dvZ19TPzX3My8jIxsTCvLS6ube0s7Gnrq2OmqWTmJP5nba9wMTHvMbFwsHMhoHHrvngvpKPmJaMieyPlYaWxo/Gk5aA6/n0kaHxrPObrKyJrf3ls8/EjoTnlPmShc+BkaaM9L6mhM7+rpiL/IOG9oGY/4uA+u+C8ujYhPnf35bu9aGcg1urpIqKi36XmZucnZ6goaGkpaepq66usLKzW1xdXmBiY2VmZGprbW5wdHV2eHp9foCChIeIiYuMjY6OiY6PkJCQkZCPj46OjYuIiISCf317eHZ0cnBubGtqaWhnhmaAZ2dnaGlpamtsbm9xc3V4eXx+gYOFh4mNkJOWmJqcnZydnp2clpmVqJzw/nzwb6R/ipmOgmFtfGdKW1lTZEZuP0NBRkJLY2ZSUY1WWlpLR3VpQktaXFBSdlBwQmldYHiEXzpAQy0pRkxAQ0lESEZQRUxTVlBeV2ZcXjM1ME4vLFeAOjs7SlpxbWtoNTJgOEFpX2h4QmxSZm52T8SBqp6Kgk1QaWZ679m1rnptjFmIWVR9TGJ9fmpXVFY5Q0lCT2I9Z1hiWFBiQDpYUjVSOjtVbFBAZldfVlpbZnFfZ1pccYeXmrdzooWvhLBnd3nGlspztn5pYZ18bauQkZJbXnJnb2CAXGRkYGl1i6u+kfp+X1iolnSUeHhxUllWaTZCPTFhNFpTZzZeOj02Ol48OS0dKCokKT9ATlYsRysuMruMkD06YjwhIylKJ0MxMTguLiknLTxJS0gyR0xMTCpLSVdSUUVOTEtKTT87OExZWGFUV0s/RUxIUUhLRFFOX1RhNjE4PEKARDAzPzdbNjY0OTI4U15JQE5ASUpNQW5XVmVpZjk4MTMyXVA9YElbRFtqXzk7QUdIRj9HUjQ9QD5iZExJTUxIU09SV1pST2tiWllWVJtco6tgVV91a3JjVW5mZm9qdn18YERRcLpyoJ6WiH13h0t5cHFUT1FRRkU1OkE5U05JT09LPDk7QENdaktda2RfiniplZ9/i3l/WVdRZGZ5fXaakpmqts3HpZaejah4qZ+vgYyperTIhImte4OcoaCfoJydnZ2fnJuBnYqYlpWZhJiAmpucnJ2bl5eQkpWSkZKRk5COiImOjIyGhYaHh4SGhYSCgoOEf4OBgYKBg4OChoSHhISGgoSChYSEhIOBg4aGgoWHiIuKi42QkpWXlJianpybn6ChqKqssbi5vLi9YGBgX2JkbGlpbm9xdHd5e3p/g4aJi4+OkpWXmqGjp6mvsVwxXV5hYmVmaWtscHFzdnd5eXyAgIKGiImMj5GSk5aYmpycn6GjpKSnqKepqqusrK2uroZXD1ZWVqysrKuqqqmqp6alpYSkgKWmpaWkpKKfnp6enJyampeUjZCNi4eEhHyBf2dudWZpZ65tfYGCg4N6g4KBgY1dWZCSzMh3SkVKTkpIe0x0WkxsPGdHajldbXhNZpJ9r293cml8uaeDv6R0dqhuqmNell9oemrJlHpep8qMeHSxXFqfVGeVU0mYk1OWlIxapIaGBmC0qm1zWjt9WlJRUkhXWVpbXV5fYF9hYWNlZWdnZ2lqNTY2ODk6PD09Oj4/P0BBQ0RFRkZISUpKTE1NTk9QUVJSTodSgFBQ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hY+UrbCkoJ+Yl4+Phfzf0d2GjIyD+d6A7ujlhoyC4Jb70tbfndXk1MPLwtjynJ+apKezqa2ZiYiUoIOQppuK/Onx8I7y0fTwhI+Lj5r//ons37ukvrujxt/vjIyprfGT4+nk2Ofs/OPdvsLPzLbKy9LS3cPskYuF/YGVlYiG7NzzhJOHqqqckrOhmIaVuI2XkYuhxrejsqqAv6642om5rM/S1Le+kfOBwKCP5Ya58Na518bR0s/NzM3KzMbBiMrlzb3JwsTGxMXHx8THxYXFxMC+u7u8ubu5wbS0sLCurq2qra2vrKyfi661srS0tbe1srWxtK2srbCvr6ysq6iprK2ysrWttre2sreztbe4vbq8uru+vsPIy8yA1dvW2uHk6eX3+O7v/YKBg4aKjZGSlJWWmJycoKCmrrO9vsPExcjO0trk5+ny+P2Eg4aJjpGUmZ+gpKissLS4vMDExMjLztHV1dnb3eDk5efn7fDv9fX39/L5/P2AgYODhISFhoeIh4aHhYWEg4H/+/j29PHu7ezq6ejm5OPh396A3djU1NPR0M3NxqWDrKGzlJuc95Tn/oGr+4acjbTkpcDO1MzU1NLNp9frwJrezL7N747/obOJ+IGZkLueiKeF3u/rzfes2Nzn/KTv3PWSnrzZw/mftJqvotm3qevfvpSVvPynjKC4xaWH8dbkydj1gPH1gery8/z+6YaPi5u3n7+AcKpbbdCEoJCen5+ho6Slp6mrq62wsbOzW1xeX2FiZGZoaWltb3BydXd5fH2AgoWHiYqMjpCRlJWXl5WYmZubm5ycmpmZmJeUko6LiISBgHx6eHVycG5ta2poaWlpaGhnZ2hoaWlramtsbnBxdHV4e32AgYSHi4+SlpmanqCipaWApqmop6ek3YCMmJD5066amoJ6W4FilJKAV1BcXVdkRm5wbFZbTWRWZ0lGc3dWSYN3X2Y2PkxJZ06GTI+NZXNYlU99PTE0Iic+KkZLRk1UUzc2WUwtM2NUTEFWLSksSCsqQjAtNjtZYFRcYz8+WlAzV1VTYGhsSF97k61me6mwrHKAXGeIVoGx7qXtfXBgU4JveVhmiFRcRV9ERkdRaTlIWVRNSVVTSUZPNUFMN1hsQE5rYVVFb2NUVVJQWVBOV2JxfpeX3YzBpKmfdIaVr4KLf7WWi9Gafplyt5a2rntTfHNSUZNXmHmBf3OSnIiAcWBiUE+CPnhsaFRRXWBmYTNVT06AQjpNSkhaWHd1VjI1JSowKSEtP09iXEk+Ojo+jnWba3pVOSYjIC0tJywqMSspJEo1RlhWRVxaLks8QEpOLCwnTEREQUZERj5GQUdJRUo5NUFEN0ZAQ0VHT2NtVFRYLC4uNi8tU1xlNzk2KiwtPEE+QEFCRkRHQXZgVlk6OjozVUaAU05ONzk0V09yU1VhUFhiTDk6NkNOSlJKUlphV2JPRUlRWEFKXlRGioaZnGOki6ieVFxYYGqlp12Rh2tUXFxDW2d2QElxecdjh4N5ZGRndV1YR0hUTDxFQUVDTkBpTEdAbjhFR0BEdWh8Rk5GbXFua4d7bl5rh1RaT0pki393gnhSjn2Pw4O0qsjDvJiueuhwppB4wm+o072nr5KbnZ2cm5+fn5yYa6zYn5OdmJqcm5ucnZqam26amJWTkpGSkJKSmI6NiouKiYqGhoWFf351ZX2Bf4SBgICAgX+DgIKEhoWEgYGBfn6CgYOBg3yDhIODiISHiIqPjpOTlZeXmJuam6CkoKKnrLCvwsO3uMBhYGBhY2VoaWxucHN3d3p6fIGDiYmNjY6RlJaco6WmrbK1Xl5fYWVmaGtub3F0d3l8foCChYWIio2QkpOXmJmbnqChoaaop6urBqytqq+wsYZZAVqEW4JahVkGsa+vrq2riKqAq6qpqammpKWkoqGfn5qDaIR7iG9zc7Ntpp9be7xheWN5nm9/homCiYmIhW6XnYZspp5WfqRFqXFqSW84RD1dRjduO1VgYFDOiYCJlqp0tqvFbXGBqaDCcX5lcXilhX6sppeBconFhW+CiI9vWJF+jm1yh0WGjU+Jmaa1tqNkaGgEdYN0iD4/XDI8cj9QUl1dXV5fX2BiY2RlZmhoaWk1NTc3ODk6PD0+Oz9AQEFDREVGR0hJSkxNT1BQUlJUVFVVUlRUVYRUEVJRUVBPTk5MS0pIR0ZFRUREhEOAQkJBQkJDQ0VFRkdISkxPT1FTVlhaXWBkZ2tucHN3fICEiI2PkpOVlpWUlZORj4yzY4SJcerQkKWjh39Ud1J/gHlYVGdwcYNptryzck4/MEF1QVSOk2dfu8C4yGZmZE9OJTMbNTspLg4dHXJfYGteX6Rcn6eiqa6jYGGvoV9qzrY/qJ2+ZWBitWRgnFdOVVmQlYOHkVlakYxTnJqU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qqfX2su7tPCThLH99qGv2MWf3qrOz8zKy8rIzsO79IKwxK/GxsTFxcXKycfHxsjJyMTEw8DCvLu5uLa5t7KqqoCpqquqqoaExuHQqrO2tby2sLextbGwrq2trKukpqajpKenqaysp66xsK+ts7a7t7y8vbi5wcPIzsvL1dnc4OHj5uPn6u/p+IGAhIWIio2RkpGVlZialZ+mrLO4vsDEyczS2dzl6u73/YGFhomMkpOZnqOmqa2xtLu8wMbJzc/S1SDa2dze4uLl6Ovs8fT3/P+AgIH8gYOEhoeJiYuLjI2NjoSNgIyKiIaBgP//+/j29PLw7uzq6+vo5+bj393b29rZz8HIpJ7MyYyk1cb4hsWBpJWXhLiWhpKZhb/Y1uPk5N7YsuKgsoqckP/5+fmQnojphaK9n6mXqeT539340dDn95armtfnusiE99SXz6aekMaG+cfY1a3dvKbj0/nb6+OUjqiLFtyX09iJh9XU9/PR3t2B8eyGhpmbtLNdg8eta6CXa5+jo6Sjpqipq6ytr7CyWVpbXF5fYWNkZ2hqa2dvcXR2eXx9gIOFiYuNkJGTlpmam56eoZ6ioqKjpVJSUVFRUFCem5aTjouHg4F9e3h1cnBubmxpampqhWmAamlqa2xsbW9wcnV3e318zqmazYCTmZ2ipKetsLKysrO0sqe+ntihmamMxa+tlVKOSW5PW2GEXmtdWWFXSUU9O3xuemhdP09GRWtKSXx7XmZqP1BBf2B2bk1tUGVah2pKLyAgGB0dPj04QUA2Nj5AW0hcOGBIS0hPQEdJSkc/Qk6AVjVcX1NcYVZjOl1QXEtLR2tbS1JqgoePmmVlYXB2e4CIfmSR449xUWCIbHNmRXZpRj5CUzpcNzqAZ1A2YmBPXzdBPD48TGlFRmVPdGtfYFZSTVB4XGFITE1PVlpld559jHyyj35UiXF5YV+Lb31zl3PAd4lsYrtvaKZtZJNOb5iAU5yIh414c22BZ1tdUFlPTEVAXm5DQ2ZodF1NTkJPMz9AMz1GTkxQSTsuKSYkJi0zQ1pnYFE+Tz5cX36HcFAwLCoqJyImKCssNi5KKj9NVmdgVmNfWUJTRzwuT1MqRlMrK0BgTS9FSElFSD01Mi05OjZESVxcTVU3VltcWDI1Ny2AVysyUTlPMjAsLCc1Nj1LQkdBQGljYlhpXDJPLUg5TyhJR1Fc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5e/w9P6Ag4CHh4mNkI+Tl5ibnKOpr7S3v8TFys7S2d7i6O/3/oKEhouPkpedoKWorbK2usDCx87P1NfZ3OHh4+Tn6+/y9fj8/4CCg4OEhIOFiYyNj5CRhJOAkpOUlZSUj7LH+L/j2NP4gYD++/r28/Py7u3r6+vn5ePh3difkPKhkqOAvNij1dHdn4uHg4OQoqK0u77f7sPc8e7p7um0ltW0rfOMkIOHlYmRltecoszFi/nl8+jt/YH56ufl+++BmJSWpMD93YuQi5iirLTC7pbj+b7F5f71vrsco4uPi4WN89jR0/f6h+fx8v73+PaA8JD9o9LGyF6OdcGhZ5aVkpR6pampqqyur1hZWlpaXF1fX2FjZWdpa2xuanJ1d3p9gIKFiIyPkpWXmZueoaKjpqeopVVVVlZWV1dWV1dXVlVSUE6XkY2KhYF9end0cm9ubGdraWhrhWqAa2trbW1vcHN1dnhxrW+Ckamb0ZChpaqusrq9wMDAv72807Peqnq2eZ9ffmejbG9mTVdGiIN3ZFtUT0lAOzpEej9khog/RU1ZUEl8Qz1nbjc3NVBcbFJjUVBDWV9xhUYsKSEeJxs6MDlBUmA0Z2MxLU8tOzErVVk7PUNRRkhGTUiALzE6XV9STzo9XE5PT0dPYX1RXnN4fH+TV2NbXWRhXVtpa15YbmBvNkRNVWlrUEc5bUNNXUFgRGdQWk9DXjY+M0JbO0hWOUNvXEyCb2dqWV5YV4VZVVxiXVZUWl99cbKFvJNeZnWJeVLag2Bft594iXOeg3Fip5OTbI9ei0t5a2qAXV5lYWVmb19LjXd1QUI9bWVlaV5YT0pRV0dUUj08OkQ/MzVLSlxENC4kGyEeKjdYQmhjMzRAO01PU09Yl0UhKCMpKCUoSzAuKStFSl5sXVpaaGNGTklCUDYxKiM3UEMzi0Y1MC43P1RKSUc7LTQ0LDtBSU9RS0dFVjFdXTJaWTCANDIzME8zKSkkJysyU1FLRz50b2lrVVheS0NGNkFGOj48S0u6ooVPQWJOREdBHi4sLj5RPUZJXlNbYlBLXFBHVFhZbWNiYW5mXGVtWId+en9/SFhOS0WIilhDhnZedlhoSVhhlYCSZF5JP1REUzw/NzksPD5OV1Q1Xmo+bzpCUVaAP0JdVGZeVkdbbHR2kpKlh7GwYm9XSktMU09ZbY6YoaCMktWrir2x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jY+RlJaZm5ydnqCemZWOiYL67uTb0srFv7qwl6mekaLZlZ+enJCampeWlpeXmJmchI6I4o+osrLA3tezt7q9xcvT0tTY6YiElIiymKvYk7y0jbCGjv+eptKAw4K6+7PDyrmfmouqmKPI2cDenJmji5CPjo6ThY2Ljezwo5Kdt8O4xrLQpfiQiI6Kiuzx+ujv5vKC84H4/PP38e+PhpiV19TY3NLd59feg/r06NTa8YGSkIHs6efv8OyPlJaVzNbo/4bv+IWOnqm7sa6qt9Hh+4eUxL/Jzs3S0+6AoNn1tfOnkaSI8duB+vG49rGI2qTG4ZeI8uHm46OPiZj7mLmjjp7qxaCtye2Bi6Sj5POfkKy2kfbrmNGb8Nny1rCRhuvBuJSGxfbI4b+8urCbnqqflpCNj4OJgoSA1+Ld6PPHvKi9yd3T1t3IwL6/yqreyMTbsKCpqa21s7fJ3smAsJOf6Pj+9vn194yFio6Bgfnp+ITi8fb2gfqE6t/p5fbr0pH47/T+hYmBkoTW/Ie0qJ6bndLByqe87d/S9tDI4Ofp4+T16erugICDgIHw/ID36Nn4iILkgof5jo/2gIjz5vjp6YTu8IOAg+r46+Hr7daEq6K/vYzs0NDbwbzJ1sWAw/qeqqqkrKSBjoeNk5Glt6GhjJiwpJ6YqaH7/O3z9IyH+5iaqLiJ/IGevpGQlMuUxKDDzr7jxc/M2erf2tPIvrba1fOKmIGAi46Wm4eI/dfg0+X9/omWpJGKiYSH/eeHl52ir7WOpJqZlp6Un7GZqdmqlL7WxpjA8PSx5t7flpaAwcGI0c7R0s7JycnLy8yy5Yy1hNeDwMfJzs3RzM7Ry8vNztDNzsvFw728uLSn3tekm9i8qK/o74KDod7PrLGqsrasq62po56elJijn56dop+do6qipaKqqKytrK6ytby9vrm7vsK+xsbJ0czR1dPZ4eHt6ejv6+vw+P2CgoeGipCAj5KTmpqcnqeqr7W6vL/FytDU2d/g5+/2/IGEiIyQlJqfoaetsbS6vsHGzNPX2tzh4ubp6u3v9Pj9/oGEhYaIiIqKioeJj5GTlZaYmpqcm5ucnJubkoLX3PbNwJyLg7aEg4GA/vv69/b19PT08/Dr4NK2tJOVlbuC5bmmm67Kw8Zrxs/6/PX9gZmwo6+WhozPvdLCxsOiioa3nYKIit+F+Ib8momC7Niw5KWYgZGP//Pn5Y7yzODi8Jm+jYH4ip2MkMi4m5Wbi6S48uWexbaYko35hIuaievv0ejhzOfk5fL/6c6BiePw34L2noc1kbhsa3ytXoN9XJFVVlhYWVpaW1tbXV1eYGJjZWdpa21vcWx1eHt+gIOGio6RlZmdoKSlp1WEVoBYVVpbXFxcXV5fX19eXl1aV1NQTJOOiYWAe3p2dG9jgHpzgKZmamprZW1tbG1tbm9xc3ZndIj2k6iwr73b1aessbbAx9HR0NDTcWxqYXldeHBXjpFOY0ZFlUpQbGVFWZJjb3dqVU5EVlVUYmtWdkBATz5DR0RAPzY/Qk1wfGBPVYBmcmhvYXlyoDgpKyUkLjM/O0hHUjNeN2ddVE5NSDIqPj08PkJFQEhOQEAuWFlXTU5XMTs2KUhJTWNzc1RZWlB7f4WTUJKYU1deXGJbWlRYdHhtOUBZVVdcW1paZERcaExlQztAOV1VMmFaQlc+MVZBTVpDQnl0d3hWT05XlFt0aoBZZqiCX2h9mF5sdG+hvmJJYGRTlJ2Bqny8qsGmhWlhq5uTcFxqkltxUU5QSD1GV1RRS0lJPj85NzRKWVhqckpGOkZMUUhNUT03MC47KlJGRlQvIycjJCgqM0BURo5LTURKT01RU08tJiUnICNDP08xRVBRUy5aM1ZRWFVoYEVCVIBIRU4pKyY1KzRSNC8uNTtAZl9iQ01qU0BcODFHTlFJS1FGTFIwMDIwM1VWLFRLSFsyLD4nKEg0OWg9RoBvb2BXM1FHKSMkND0uLj1GPDVsdHZvPVZBPkIzMTpHMzdlS1NVU1xcPUpHTFJTZnRiYVBdc2hjYHJpm5+KkJJUT4xZV4BmcUl9RFt6TUpNh22qfJNkU3BIST5ATEE/OTw3OFFMYj5JNjQ7O0FJPEBsUl1YYW90QlRoXF5hXmOyoF1obnJ+gl1vZ2xmbWVvjIGX0amKtMipbZTEyZnRzdOMja2ucaWho6Sgnp6eoJ+hjsCV1nuwbp+ioqShoqCfoJ2bm5qbloCYlpGSj46MioLWtZGeyrCPlrOXUU9ll5l7fXt/gnx7e3t4c3dxc3x6eXh9enh9f3t9eX58f359foCCh4mJiYuLjoqQkJSalpubmZ6io62tr7a0trq/wWJiZWRnamlrbHJzdHR8foGGh4iJjY+SlpugoKassLZdXmFkZmhrbm9y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q6+/0uazl/+Dko6Agfvk5trpgoCB+YmC7+/b9+2Qpvnn7vzXzN7g5u7v9P6LgIX29IWB7+Lc9OT4kZmUm7G+1uHk4uTc7pG+n5mNsMfl2oWf1/D4lI6Koa+7gOWV0aPhh7bKlvbage/m043Rn9eol+aEtZ+npq67y8ecu62bmoqOnO6osMfU+rz0u5Dp09SJqNaciJSPsLPu+tW2i//b3dzm8rHpwtfNxMG+tKetu6CbjouB99Hm7eTl4tDIta2sqLyxudDVzsTPvr6wvL62uMOuv66erbW20tHCgMzvgoKI/+3w3uX13erl797U1tvw5+jzgf+C2tHZ4dnw7YaRg+jK3ZKTh4Lp0uv2wqyalKffd9DGqoHIvPnT8erx8fTp/u386oD9hIL1hoPy8ePf5Pjr6vWC9e3x+OTc1Oju34L6hImBg/zlx+Te+uzh6oD07efx5N7QyLS635DlgLb0na+toaadkYuRore0sqaTsZSIlJybm6Ghi+v++4OZiYH1g6KzrZyGhbTNp5z47cvA3Me8ub+6ytrR0tnU2dbRx7vG9/Hs+ZOQnJX88/+H+dvQ1/uamJOFgomG9uD5l5ikrbi8qaSRpI7/hI6cqLXgoaDJw9LGr8rJysios67JgKHiv6PWj7vMzL6mju7MvpGFqYWWhpPM1NTU0tHT1NHP1s3Rz83Kxb+3tKHEo/nuj4qfoLuzgsfU6+T/jqjJza+vptKbi9Sjpafgm5uYk5aanJ+joKClppGkq66ttLi6u7y2t7e6tbm/xcnM0NXR29/m7evs7+zt84OA/4CAhYyJToqPkpeen6Clq66xt7rAxsnQ0tbe4+fw+YCAg4mNkZWZnqKlrbS4vcHFytDU2d3h5Ojs7/D0+PqAgIOGiIqLjY6Oj4+PipWXmpudn6GjpISlgJLHr4jlorObjubSoYS5tPyDgYCA/Pz4+vz8/fjrtJuLsPWQk5GVhvHz+5O9sNG3y8fIxdLP4vjSz7K0h7yX0sbT0P2U+eTd6drc5uHd7b+thoKSvOuw/IPti/6F9dff7urZ1sbWoPTt2vKC8OT48o2ohvP+6beVzcH5pZCR84P4GeHx69XnzPPQzszT0vHm0u7j74erjcWM9q6AY8Cdd56KoYlmqnKVWlpbW1xcXV1eXl5go56cW2ZqbnBxdGx3e36BhYiLj5WZn6JTVFZXWFlbW1xcXVphYWJjZGRmZ2hoaWloZGBbVlFNk46Jg398eXdkfsbF04ilpKKYlZFfebplbW5vcnTOfN+OpL/n0tPshcKcoYCBn7G+vYOAqY9xfW9kkX14gY+zdmBdUmlGWXpDQGVTeHJoRmaBUkNHRXpLR29XVEdFN0JAfV5BNUM5Pj1HP0JCRVFTUGV1iF08SSUqJhsaNy85NUYxMC9WLChAQDJEQTlMVU5OWUM9SEhISUtZYTwwNFdSLSxQTlN1bX9VXFJRYmp+hYaMi4eAjVVyTkpBWGFsYz1JXWt1SEM+RUtNYD5WQ1w3S1Q+YFIxWVdPNlI+Uj84VzpbUlVXX22Jj2ZtaVxjVFhhyWpthZO5jrOGb7+tnWR+onRrfXeIiLbGp4tnup2emJ6hV4NUWkxIRkhISFJoU1BDQDJeP0VOS1FVTEU2ODw7Rzg8TE2AQDs+NDw4Pz84NzglMCUdJSsrOT0vM04tKy1PSU5FRUw6OzU/NTI8QlVPUVIvXjFFRkhOR1pYNkEvRi07NzYrJzsuSFY7NTQ4S3acyl9EQXpqXTVGQ0xIS0ZRRVVML14zMFMwLU5QRklMUkZHRidQTVhxa2xqdGpaMVMuLScpTj2AJTArPjo+Ry5hXltbS0k/NisxTENSMmRKVlRKTkxBP0hTZGNiV0pjSkFLV1pdZGpWjJuYUGJTSYBHX2xoW0pNe5Rtk82wqH9zXE5NSD1DS0I+QDlFQkI9Mz9oZGNuR0NMSG5rdUBwXUxMZk1RV1FVY2a4m7JoZXiDk5+NgWh0VIWARE5ieY7Bi4y0qrGfgpygpq6RnZiwkMKXgqFpkJ+glYVywaimk4TBiqKKeJ6jpaKgoaGgn56gmZyampqWko6Mfp2I3/PFvMy4xpdof4OSiaded4+bfn97oHtvsJWWpdhzdXFvcnV3eXl4eXt9bXp/gH6CgoOHiYiLi42KjY+TlZZjmJqVm56kq6yvs7G0uGRhwmFhZGpmZmprbXN0dHl+gIKFhoqOj5OVmZ+jpq20XVxfY2VnamxucHJ2en1/goWIjI+RlJaXnJ+jpqmtrVhZWVtcXV5gYWFiYmFeZWVmZ2doaWlqhGuAYYd4WJ1tdm9npZZ+YIF9tF1cXF25urq+w8bKx76NeXOU235xcHNlr7K8bIh5kHJ+d3Jtd3OEknZ1bXNPb1t8d4SMpXCxioSGc2tqX1leZ1s9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k4DPg+/XvJKIg5GKm9ujgKiHh5zDsaKzrMPb9pv2tv29karU4rX9s8yA5/Lr06SP/dfay+HQvvHN2s3GtbeioKejoZCF9PiA6PTtzMHJr7G/ubqrq7e8w9GA4e/nvsC1vcXS3rrBxqmUxM3L0tfW9+Lv7fD074fn9t3X+PLk5evjx+jY3ebu3ubFzMrFxtLmg8PahYrWgI+BiOnD1uC6wa6dptd/xqWLw9vn4+/nxs/W2cfb6fz9+PD3/PHr6M/s59vig+rh2/iC7/bj2O/iz+Dx/oeHkoP38+GA1Nfp7/Xp2dzW+uPW3NPLw8fG7p2f7v2msqmTi4SNjpObo5OamoWG+O+F+46YlaSY/YyPhv2H3c3iiYOXmYqKpLPfsuCfxdzGwry/zLrJ1M7Z2N7T29rl7vD5/oyLl42ck4Xn95CH9fXU5IKWkI6LhoTm1uL6nZKRhamrl/+J9dGA+Jfq+pjDsbXW3uvPz9Pa5t3T0qKzsrajpbfP2OiiseGOpNPt3Z2fu+SQho399IXX09bU0M3Sz9LPysrHkeyL/qqBkGvGvrantJS/sK+8xNfk6uSwm4qQjKyIrru6zrmN4qeFmI+Un5eipqOcgKSvsKyzuLe1tLi0uLe8wMXHyMqAzNLS1Nrc5+WS9+rt8Pj59fr/hIeIjI2Ul5qan6Cgp7K1uL7Exs3O1Nvh6+/8gYCGiIuRk5mfoaevtre/xcjO09fb4+fr8PT2+fuBg4eIi4yNi4+RkpSTlJaMnJ6hoqSoqausra6urYW5uZ+OoqOgp4Wak4Li0q/3g4KBgID+/oCAgYC/97id7+/rzt/i0MS+ttWBlp/D0OuhpZizn+Hf18Hp+5KpuJ/i9IubyfLe3MO20tbZgpCwxOnO8veb5czR7eTkiv3k4dfMgevz8IPL/ceAg+jn0oyvqr3f2JibjImiiJCS9+2OiOvv3dnP0ubTxMXL4OTL2PaB+IOnlaODiJuAibh6dquCYL2JqH6jV1xeXV5eX6NouZ10mpOcsI2MaHJ0dm53f4KFiY2RlZyiU1dZW1xeYGFiYmJjZWFoaWpsbG1ucXR4eXt4cmtlXlZQTI2HgYB/e3Rvl9WM26aykl94dpGnbmmK1bKyrbLDx6SNjLWKmp+4zv7Doo+QvPGRrMuAxpKIqrx+n4LX+6i0hUxoWJNhTHJ2dFlQS4hqfEdJaV5gXXtbcFV2hHdMbmNaXW5tZWlvdmt8ejkzQkFJTGRaN1hbTj9HTDg2JCU1PCMuTENJTT9ESTJAQ0hERzlVRUM/QUpGL0VJUGI2NjQwS0BQU1pfc0ZIUUxMTGNnb2tobXmAboRNUENeT1Rccn51a2FxRHt8X147S1k9Wz5QYTdQM142XVdaQzdUOS9IMmdmWEI+Pkx8nsaZWmZISl18bGJ0bIilxX/GktmjepC1v5nQk6pousK4m3RjqYeJdYJpTnRPVklGPkQ8RExQVEU5W100WFdTOzM9Kio1OD40NjY2NTmAPkY/Kzc2PEdKSSstLiAVKzEqKTAuQjY6OT0/QS9FTjYvQT85PUBBMUs/QkFIRE01Q0I9P0hWN2xCNjc8KDQoKz0mP0w5R0U9SHB2iMJRe4NKQUlHNkBFRjhCS1lZWFNcY19YUkJRTEZILEI/NkcuTlpgWGppW15nZDQ1OCtFQjeAKiQwNT88LzY+YVVLT0ZAMzUyTUNCRFRETUQyMTI7QEtWX1RZVkNFdGhCdUtYW2pnpV9lXaRZe2VsSkVTV1BUcIS1qq9ujHBfXFVYWkNFSkFCQUE+Q0VFR0lTVjo9S0NTSkBkcEY9Y19CRy9ERklMTVSWkaS+gHt8bZWReKpRfVqAb1JujWicj5GssbeTkJags7Svsoqblo5yaXJ8g690aKVug63X06CpweeVdXbYy2ykpKako6GioKCempyYdN6H6rJ8poH29urGx4KgemxqbXuHj4qPb2Zrdopxj5ygtIxlonZocWtud251eXRvWXV+fnp9gYCBgoiFh4iMj5CRkpN5lZiYmZyfqahutq6xtby8u7/CZGdmaGltbnBydnd3fIKEhIiMjpOVmp+jqq22XlxgYmRoaWxwcXR4e3yBhYeLj5GTmJqdoaWnqqxYWVtbXV5fXWFjY2VkZWZeaGlqamtsbW5ub29wb1aCeG5gZ2dtd2R4fl6lk3+uXoRdgL7CY2ZootafkNTDv52lo5qNhn+WW2pwkJyyf2JTaVeGiHtvhZBNXHBdjMdum6uRhYJmVGxoZkZRcndkU3SAYHdkboF0cEZ9a2tkY0qCi45PkrqaVFSEf3Beh4ibu6piZm9od2RiXpyJT0hvb15mWmB4ZFFRV2lvWGaBSo5Jam1uA0ZOYTtoaEU3STkYNCo5Jz4rLzAwMDIyWkByY1B+e4SSZFM6Pz9A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6+br1OPBoMO7rbq+qri0x8zGubrb5fHSy7Kw3trfubGonqHCwcXSz+Li2sm/0Ojp7ubo8unm6ffc4fLh8eHb3snS08TJvr6+xcrggIOG7bHP8ICHj+m61PjYsI6rxrnrycCXrMfj4ebRv73V4ujd6/ju+4GGiu/W4tbk4d3h4Ozn8u/5+IG84/yA3+rj/YiGgfHu2uHd7OHrgeD/9PT+gfLWydS4wcrmlLCgl6aml4zy6o2NjZCMio2TjfLs/4qQodSPkpCSi5eCgoTzgOffgYuVif6Fiaq6wdO0ndTEt8m6sq7I2sbG2PPZ19uA0dza4viH+PyYlPfv9oX99ILZ9/mEiojz7Miu0fuSoqKTjoixxpfwhIyXo4yQoLPDwuv14sa0x8vOzNbewr+uvtKir7GusMTA3ICpl72Mo5Wqk6mXy4eVr77R0snV2NTRgM7MzMauzYq5j4a7uceBqqinq4S1wqq2sr3At8TH9PygkIT7kZ2yhuCM55ufu6WVlKWAnJfPtZ2op5insK6xsrOzs7Szub++wMXCwMrS1Nra4Lnt6OXo6vTy+YCDh4mRkZKTlZecoKeprrW4wMXK0dLZ3N/l6vn8gISJjI+UmqGigKmxtrvEyM3T19zh5e3y9fr+gIKFiIuNj5GTkZaYmpubnJ6SoqSnqq6ws7W2tLW1nZO7qbW9yMnwucr6iYSR2MqQ7YOCgP/9/oHRgY2Eg9e539q3u7evqLTH3OP8hKXAt+Gij5moovzK7YGUjID8383c9vO/rs7p58/g8eH4gfySSdK5ydbA1OHv9fqCh/mD69zW2Ibq/I+IsruDnYGsrZiAg6uOrJ2St7uIgq+A2+HS8Pjo1cTfy8rMs7jCq7y92s3Y3YGGkpiPk5aAa2aWXnlkd2KidV1aVV1gYF6mpZj/goSMjZCUl6Op1Itud2ZeZGJpaWNqd4FRVHxfZGVmZmdnZ2hpa2dvcHJzdnd5foNFSGaAZ21vYldLaZTC/mJyeZaqm/7zyq+1tHyFc1mRfJ2MkaWCcWZuUVRmXlGs4YB9p3zerJnjjIedi46AgN+WhoC0j6HYhXVYiGlwTp1/W2JVeWltaJRsa1tCcppKmtRYSTI8w1JCQEdDMDVOMF1RYXBcPTAyOT1IU1oxU1NQNUspPjQyNyAiPz1EPUI/OTs5PSlJSEJLLkdBP01HPkpIRERKS1ov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2N3U7NvPpbi1uMDO1fnLl4CI6tuypOjVr5WqsZ7S1rnG1ejd7+nJyMTY4djt9uz/gI+AheLrzdXX0trc3tvp5vWAgILh2vPt/vj1+ISD9fzq39uA0tzh9Oz0//77hv2C2djBys3v14ehk4eZkvz/8IWU+/fo7oaQlYPzg/KClqSploeIjfXr2Oj72+Pk7fqEiO7o8buvv7HJ3t3f3vbj07ncgffb/4aKg+W76+SA7dnWyNf9iv/974GH4drG7/uAi42Plr62r9T3raiRkpOpqZPyg4SAspWloKy8w8HqgOXAwdDc3ru+zbastcXIt7e2u8rt3vyWpPmqjISQhIuFsKagoNaWuo2+stLW0tLPy8T5tM3gsPPCsrabp7inp6Pru6a0q56Sg/LymPmQ9dz/mrS5iZmRxdzY+MXa9cLI3KH5jZyso6OKq6uxtLGkoLCssrGzt7cHu73Fx83T2IThgOXp7vP0+fiDhYiOkpGSlZmaoaSnrbS8wMHHz9DY2t/l7vb5/oKHipCVmqCjrLS7u8jN19ze4ujt8vb8/4KDhoqOkJOVl5iWnJ+ho6SkppqrrbCztbi6sJeDzZTT+6ORoJKgqqy5lLTpgo6216G44+fTprS3pZfm+4PS1MnLrLWvP6yjtK/F0sjP2eX3kr7S68iB35CNlPqDnsXB9YOAkOjHs7jjtL6zqLu8/ojy3avV0dDIxMzzh/n9safh793o6ITlMpTl4NqshqOpmIa9kar7yc+c/93r/oiO46ScztHEvL7h3M7Nt8Ox2NOwzd737dLZitaWgFdSUo6STXl6cpxwdXdbhpmJeuPx+fP7gf6CjpWYmJqft3ptmmJna2Zjbq1it2uji3uKWopodXhwuZxdk19reX+EiH9bXH1lX3Foi3h9n6yBt5acz3Xct5jg/Z5cgVKqcVlndm9ai2Feb45JR0p/TVhNXlxnjWJj34LS3Myb3mTegNyifXBr3PCQo45WZ19rUaVzlVFncpRxoF9WUU1HP3BoQ2q3cVRNWTcyc2VaM0BVSF1fZltZWWM6Ojc8Mj4+SilQUFRXRk1CPSEeNzErLTY9ODAzPToyOkI8OEVCQTo6NS47QT42QkcrLS8rOzc9PklGW15qbkJGQUVOUVpLQEpOgFdhc0VtOTtASExIQUtMTVNOT1lteUVFVmE9Q1QzRU1SU0ZAPD85Y0EzWVRS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54uG6OuB7fXUgOHqyeb27POG/f/z48TJvcnW5NqAmZmMoory8uzu9uLj6dP4joT27oLxjZ+NpY6Uhub83M7Bqc3FwODg/ICC9PWIpIKV7MbS3cLc4tS21/GAkoaIs+movYTq+PjW2sfP3OHb0tPCxc/M5PDU0t+BpdO+o57j84qVoJ6jrq6erMqtgLO2pqSqtLu56fvYuq7M3NfAt7DAtbHMwsK+v9DS6eX8+YG5oPbp4+Dl9rPn4prAgIeMiM2WxdbQ0syXs5K0z5DdyNO3o4ejp8PGxXvLoZGdk4KM7pKf0/rVwuPp0OiWh7mis6yqi77wlduP9qPrppKHkJ2lm4KK94KsrrCzsra5gLS4vcfO0NPY3d/b3uTq7O7w9v6BhYeJj4+RlJWdnaKor7a+wMPKy9fa2+Xt9PqChoiMk5WYoKCkoZiNnbTB2ePp8PP2/oH66te5wszOhpSZnJyjp6mrrKyuo7KzpfiziqXus7vI+tyJ6dnx9YSMme/Ry8/xjrTbk7zkhoP+/+rhgKaLxJX9x6yOj5aykY2jxLu7xc3egJyqn9+QgorwgYuq4cPC342An5XsiJnNtqans8Gfqramz8vS2ebAxcrj1emB88SknoqMhOuG7ePwhZmtrJqlrtCLqav5hKCHweO9o6f4nI6S+IP/08rO2ujp79O7u7rA58uszenq6NLVj6GrgFVFSFSPilpVe4+Nnd3Wu8TD0NPb4+nm8fLq+oCIj4qOlp6jpISQqLCJe2aLjINgdLyFjqvv9IGwk5CAWVV0a6JsR4uLsJVrmm+ReZuloYaE8+W9v4nlnbChlWKDhXCSUWdTaVpkcE5vjlNcXFpYhnNbWlZsV2WHcJLSgd66XbO3gK2PYnuTcWplYWRYWVpWfmJ9Z3aOUlp9nWdKPntDNEFjSXiAWUdSWkRFRGA0JSs9MDpeY1hKWzAvMjguLysvRCRDRkQtLSwnIhoeLCoqLDc7PS8wND0zOzo4XUM4SzcyLjYyOEJFLitGVC1ALk9LQ0ZKUVdXZDc7OT5DQjxBREFIgFFia3VlYWRubHNCPkZBTlxPSVJbYnVFU1VlPEVRVWxmOFxVVlZZSzlURklQXFN4YWJthpOMfHN4eGpiX2hofJCnbIaHjri7ta6+yKyUg317mMmt5HaHj5Bxa2VaVE83TCIjKSolNTdFRztfOGBhX0xUREU3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iM2B/Pvdhtf2/enduouNjofYn/eGy/iIgKmA3tPjz83Z2OTTzcTP2tHT0dDd/LLwjs+/7YeGo8uxrry4vqyvuIDEuLOZmrrGwNf20sS47ezGu7eyxbzK5OnPu8bkyc3WuqWgoufS1erV4POk9v6mrbGE3trN0sC5gOSvjIK30K+Gk7fSv62qq7HWwMDdppyPkov5iYCmrczxw9Lj2NLN85fBnJ6Ji4Pg3+SBibCr5oue6fSIjYiImPqlpKqsqquzs4C0vLzBy8zQ09XW1Njb4uXj6vD0+4KDhoiKjpCUmp6hpq6ytrjAwcbP09fh5+32///htbCwirDV76KN/4SFsq3IoNLVssKR06KBjP/s5+6Ak/Clt8zW1KXFrYzPl9OmqK2ur62vp/vM5dv5+IP/6ta/0bvS0dPRs5LQ19Xbz8q054CwpZugnNH/k4ebp5ualrakqsLKyOSupaexwK6worr3743SiMiU2Zn7+Ke/zayyqbSytLa6r6yGsrvW2/zP2czh8vWQgs/eh4Xi8YOIj6OWmd3uuY/93dvWirKdk5vh3t+2sfXiqojt39jfzMb0+9bkg9nQzMmspdrg8fnw8PKC7YB2ckVLaVZkVF1xZmy21aierbS7wMLN2t3f2NzGssPyoerr8/yzeHSKbJqki3V0gWyMg/akgpy2zex0U4u1bqrjzML9jrWbY5+Io4XM2YHR7ofvgt6W3Jyac3Osmnlma3hXYnKXWWdJa21+TkM+U05Je2ZmiW1Ua2Rvkpd1bHKPhoDYhcW2nZVnrZveeoeqm2tuf16chn5UdFhIOTVESDxzPkk9RT9VZzdXO2hVXVhdd0MyT11MVEZYTC4tLzIrKyoiMSk6LC8pHhwvICopKCI6S0M2Mz02NDZJTE9KOzw3PCZFNTVSNS4pOVFMREZBPEVRSz1VUU5bNThGNTQ4PUBCUIBWW3JnaGNhaGVpQDxHSlRfVEVJUltaYz5GTVRhaTU/R0k/OT46OzlhSUVITVFRSoWFkZqWi4GBhIqUlZ6uYW91gaC+yt98hXGu1HKCuLeQlsTB9rKYl6Cpn2VgiqB1bFMuJCgpKC06PT08OkJdYmlMPTxCTTNBMDZEPUVbRUU8Q4BDO0Q9VUtTTlIyJyMeLD81PjAwLiUhOS8uNi5KLi0qLTsoODEuMis2MygiFy8sHCEyIiMlIiAaHSImKyci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eXl6kXmDegICBACAjMPP/q2yhIDlgNSzlKiLhIqJ+Ym9naC73oiElIGV6dDY2+f87Pecrq2/qfLa3u6Zs/3K2+Pn2oL3uYbw2LiJgoDg2OX01bynt4Whv4Tp8buejfCU5cua8+uN+oP8hvTtnZev6PX7xYnE2uHclIPv4LOSut65mn6zvMCYsbexWkeAQnzyuprb9Y+1y6eOv9Wby4bkvriopbO1k7yg+8SIlbjen8ent/OC1N+zoMm5rNOgo5SXkJCNl5aI5NXm7IWHjaCUkY2G9PmI6OLr9PbwgoiZ99zaxb3h2vaO3tnN1MfCusO+wrjE7/Hu9trbgeTy9OODhp28vczA0uHqgP6Gh4WAiIH7j4mGh5SSmKmtnZ6iuMnb4t/q+IGSlZSqpqjTxMLBvrK6saekp52TmIyJhIGD8/Lr6t3Y1c7Ow8vK1+nm8Onl697zhY6QgYz79ejp5u/n3s27p6uhmZGVlpCOv73p8IHg1dXPu7W5k4ntgvXq2+Ha0vby4vTW5+jazt7XgIOA/4yJh4qHgoXy9ubr7fbz3t7i48/y7+j7+oSI+fftzODz5NzZzcXL0revs7C8s6m0qLSntZ+ns6qTpZORj4yQ/ImXp5641cKxnZSTnZ23vKWUipmwuLHK1r2Ck5raudqm14yGhY+vg6PM+729rY6IhIagqpeqh//5i4qctK27uMqAwNm8vqSI74iNnJWMhO3GwNa2xeXf6+Db1IHQvuP88+eA+/eNgoiXhtC/19vY8ev76drUxcK/sqmgqa67wr3P2t+GtfD+iOjz9/eChaKxlKjGrsKNk4P0mrGYqLSnwobw/oL8jvXiz9yI2MjlxsbCsoGwlJqf9Zq9npKcu52ess6AzuDP7/X83fWIq5Sbv5OFkoumooz95szMxcfa8diZw8+pm5uQj4uIkPiKmZCMgY6ckbCDl8idj8O+m5qwpIWQhKOqpIfq7qOPeofFxLS5tpf78+jZkcXAtLeqkcDK0sXC3OPh3ezSyrKwxdj1qMf/le+d/uHxr/ayiJSTkIaRnJ6Aoqqop6myrbW7vsPFys/T297k5Onv7u77gIKIi46SlJqiqqq1ubmuqMy/qZObqZuqpayrpaWwsKawr7W0ubrIxr/BwsPMzd3o4uTn4uLf4dnZ56bk4+fr7IOTk5yfqbGxmZnW5c2vm5yeyKGGiJubobOu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ibfA33bUhofxh4P48o2Fp8XU1LH1sf2Y+7T/66md8IfolpeCvd5jqVjWx45WgoDiuOq7u62J9Jash/2QoYOPjJ7PyLqtt7a9r6qah8umhKWql/zGoJzPmY6Ai46muKWXmpyUmouMhf7WzNfvh5CQn6SRgI2BgIHn+vL09vD2goKA6eW7v8H6j4jM2Obj1MS+0tnX4O+GkZ2orKqwq73RycfN1d3i9Pr0jZSKkJicoYCnoqvPy8XLxMfH3ebSur7AyNLP2tvo9oeNoa2ywauxra+urqaumJaVjouGioaFhYX26M/UwrHCwMDIy8vHx9bN2cze08zdg+nq39HPy7izxMK6s5qblZGKkpuLh4W6l+Ph0sqzsLSvnZ2hmZyYlIX39eTo7fDx9fjo6ubUhfWKmYCdlJGGi4/o8ur27+jy9vP46Pvr4ev04uX394Dp7ILn6Oji5OPozs7PybSyscrJtMi0tKinmIiipJ6qnpaZl4+elpWeqrnYy8Ceh52onKmiop2ho5ewobC1r6q6uryxopqZ5t//gcPGmIvHoruVgoSAhZ+goMmHjYmPkqjdusrb1oDH2LrEu6GFmKe9uLSricq/vsnU6MrGyczS69/V2daB+on049njsLTyuLHJ1N3f2/3O1N3YvNC1u7Cwtre4w8uB7tyB5oiRjoOCjZT4ma6knLLj4qSGpcbL8KzKxJGytIWMrszS5dfIkJSCxtS0yLj38dfAqaeOyszr44WNk6PD04DR9vSJl6KNo666xtrAvK+cmJiZmpHVtq6wvdyA1rjL2tunl4KZn5CXkomVjoyGiYOTl7tzsbS+k+mxqbyU96WP8OWrsJKA6PHRhOjW3tTdrP754sLtj8uOkpOesuDk7dn21M3Svs7Dzrqqqu7bt8rS4daA3ICZ8OTFrrqGzoG2kYCdnqGipKWorre/wMXDzdDS0d7f5ubk7PT7/YOIho6SlZ6hm/nC6tGrmZeKgYCRgJGeqKeup620qZyhoZmUpKefpquzs7m9ycXRz9DS19nNx8O+hcnM+IOLkKWVn6WuraGakf6gpZ2O9faEjo2RmYqoraifh++Mstnl/p3C95ijvYCR/7HJh6u1vtfjzpyllvixkpKNrbKCpYXi9IuWxIqImsLGmpftzraQm4SJtKGa9ZXt4LfhxL6T9pz9scath9OBjruh7IT8iqu6rsK55+6R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2tfd84WEhOLh/uTq8YGMmaymscfa3dTXz9LMxsrCucnLzujo/Y6XqbK119rkOvTk5enN1eCAh4eG8c/W1uTl4+Le5viEi5Shqqq8uKy/vbSooKOdi4aAgYiEhob99tXJsrGmq7G6vbeEu4DHw7q6s7ixtMXEw7y/y7qomLCpnZaSiPTw8Ofy9NrXrp+wu7Otqpman5qTl5KRlaOYhIGBgv6EiYeHkPnr2YmBkJ+moJSXkpmMhP38goaCh4qFhoD75/eA+IKIiISHhYKE8/Tj6vrZ1M/az8rIxNvI4cvWwca0ssGyurm1rpqorICqlZiGiJCfucqsjpCQo7a0s46QioeVm6agqaSdm5+26ZL888TV6POv3ZWL4bDErJqLjYuNk46ykpicm5Srv6C1tqar7bCYn4qClrnBs8jOmNfFrsPP6622s8XF7dPAzYqA7tPHrruZjtXZoa6pv9Xf8YDx//vo0ri5u7izr7y5v4DJzN7Xyun8gZmF85CLr5qmnZ2otMa4sq+fqJ6o0/SNjKGwqqyK6OPU+oaFieLXz8iyx8HmmZSrw+C/17mxut6PusrNiZ+foMeqqLPM/pCK8b+llYaA7M/Ev7W5y+fvv7ayoaSfmpWWr6mYiYyhqLG1lIyfnc16vHRmYGfwvbiG94D+8Y2Wpf2ZvYf8iaHJwabKxZ+Die/y7/3Hh3u1pbOuy6GLjcnS1Lu4sbKmsL6+q8/X29j50tnP5fvwgIy6l5OZhLiHkp2YmZ+ipqesr7m8wcbLz9LS2N3g5u3x+f6DhIaLkOWDh5ybl5OXiYqI94CNiYeQj5Wcl5yZnamam6KajYCQkpqcnJ+joaeenqqesqm7s7fBvt3T7IiUk5KpmoyVoamTg+m8mKGHg83ziPiAhZuzo6WZkZmhr8Dd5dWBj5fT842inOai3/f/4PCpqqS6yr+BvbS908Xa9NLi6+uPmP/u5JXLxom/07zZnoWgp/R7wsel75f64pWRteGevfSlhkKEk5yyoZKkl53G0dvY24mVm5u0xuHd4tfxgoubpYr428S3nJehm/TK2sHDmK/P5d3p9u2e/+qB7tXt3c7q2v/YgKyAt5KItte1hWbBhrbKeVxgUlxxYV5ffXpqc3dmU1RnbIh4kFNhUVZaYWiHnm6Mm41+orrG6r3Nu6+AfEtVcmPKf5FwyMq0vqumnqeanX94waGV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s6umk6OUk5aOk5WZk5qYnpuLl46VmZyVlZOGio7/hY2UnICkpbCunqCkkIqNkpKTjpKMiJmThIyQ/IqImouLiouOkYuI8IDu9df22sTV9P32hPz38NrV9uHtgvPm58fFw8bT28XKv7m1z8Crppyhl52Jo7CRpKSlwcPBqpuSia2stcbMv+Hg3pGSrN7Fo6yZkoyQjI2YgYqEgPeIhJWml5mfkICw0baK+NvZ6ZCsxavCre7uxNL317e/tszUiuLzoJiN8uTM2Pa4hLjKm5OUmbTN+f3u++/Z077SzNXN0LzEy9O9xdvl//SNiIKQjZyom5Omoa61yauekaWpiOG+04WYhr/GueGhifGP/ZaLi8rLyLW6jPOjgZe86pyclp6DooLI3ICBj5CSlryppbbmsveE6bbqs5aVgPHZxs/fiZuLzsCppaLJppibppebjouVoK+pm6Oxw5Nqi2BWcqDEyp6DiY2ijoqi6+6ei7qblo6mqcq3nKSehJmQhJiEm3qrg4agsqmO6s+vp5e1rq2swJ2nt9Xs5dPTyrvV59bu9pew7oTJ84DGzaKqlZCWmJqepaWus7S7vrzM1M7Z2+Tt8fn1gP6xjZGIkImIg/+CioL/2dTg2+r9hIOT+/2Dh4yIjYWCg4SHiYiYipCdsqmakoeHhpOcoaWw18nv8vmIi4OFgITpzbaysZLFgIDu3/GG3PiFl4iUoIqFm6q2u9Tex9XjgbbY34D0mYTq07GliPPZ5fXYwo/q88at7ITN+6KXkYLyipnjjKy4jLqbx9T3z8u1tIb+o5WYmrK23Nvj15eAr/Sylpues7C+kea3sb7Q84Xi8Iypu+j3kZOgj4254O/pw8GZ78mqrKKekube0snq8vSs6ubtgPvy9OaG/qC0jZDC4OfQu4CxtJeAmZK2q9Lf9L1xamdtgJFqZFV7amJxY1BVV1xXZWpniVNbYlGGZGiRWGurrHaT0sPNn9LW6IyPZ2qewIGLlMTEYoSpe26+Zp+OpIl2b6yMoqxefVxXZHaHc1l4hYWCb4BUcnFpcUo9akZVamRKeUZZaIBVi1+yzYFeYFNoYIBddGxzP2hzUXpcSFRkQmxiV3xAjWZHa2I6KzkyLysoQUIyTEBKTjs0VD8+QUY8ZlJLSkxJO3FBTEJHPXpKQkBHNjlDST4+O0JDRkJGRk1RV1xTUU9HSzc3Oi9V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trGin4+VnKarw8vm7fTw+5GLgIOG+vmAg+iirYbx9Obh8fLwgIOEiIiAmq2aiITn9eCDj6GpjZWQ/eLugcvFrcDR16uLqpWAie/a0tuC4eaBwq6jn7Oty4WB8oCV+e/m3tTo3c7b2+nz/eTR1uzllIWElqSgpJeUkKCot8vTgv6ViaCG4YCClIDSuq7fos/AjpyflIX14M3LufPI4oKc0fKh4/fdiLaXhYqAqsHYssDD2/aM6MvFiZO7tITLoKHomJK5kYy3i6qRjcrYg9bC+7SS9aejp6Gpsr7B2oN6YqFckqF/ppaDi5elqOOWmceqhYmZpJ2To7TT4J2706/Rzaae+6uZfZeaqYXw7uzLoJqPp6/Du72Uq76zxMi9vbCjpbi7sML2+4rWvLqAwKHtjPayoOuVuImSlJmRkpeeqK60wcbNz9PKpuSt9+/17+Ty7uPj3/Hm5+jg083P6t3i4NzZ3Nzp7PD16ffuhf2F9fP1gYuXoYqC5uz3h42gsN7L1crE0c7EssOi2bSM8vXmr+T17YGTpYSAnK2qo7i06u3X3+Pu+/Di6/Gb/4mA2sfGr6malYuG7pC3o7+OzOzn0Ou+zsLK0/i+95Cnh43/1LqrtLe/tLbbzrmeoaGppLW3uvSB7eawkaTkobaxpLrczZ7F6tLc3uLC9/X/tc34i6CewMi2+Zajrq6f9aiU9cW1sKv66fqljI34tL6X3OaGnpXV/Iu91eHHhueB6q6AlmGGdHKFyrKjj5t3bHRsqnl3YWJWhmRlVlNXY2FkZHN2glZJcW9nX4SaU4phdW54jrr1/v6ukX3ef42v4trv/+lvZ2Z0b2OldrKck6Kfls2ioGhdYYRxsGiSm5adb3pzVExmcnNeh0lSTkV2aFB0nc22qXd9T05ufomvanVySDyAXFhwSGBITlNHUHVda1V8UV9yTFphTmljPjVALFc8OTYuTldOS1wvNU5NRE9KVFZRTJNMTEpIUFVOVlNQVGFjYl1PUltqdmxzenlya2h1d4GBfX2JgIeKh5VWXF1esr54cHV2dXZ0eG53dn56ioackpOcpKuwramvXbNcZF9qdHOAeXpvbmptcm1iZbNgt15fuq+qoqOprrFoX2FgYmhrZ25qaWVkZGNlY2K+tbW0tqmim5aQlZOXl5qZlJOUl5SVkY6NkYmPmqKjpqChnZiVlpCFeNfY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k4CC8/+NkoiSlMOLsKCyyMDGy9jt2oKgjoXVw8jynYnt3uTBmdjb+Jipr7KchNHFwqi4pYqPueuFkOXLgsqJ1K+hgLO8l9mhhYa7roD8h8j77oCx4aOan7PXuNWTt5SuoIzBn+aEpdOGh6rl8eXk8fy4h5+8gG2Ma2BmwoWZz/7yocXx+MHN+englJahjI6Rr8COid+TnMWxpoOel5qKoLWJhIbKsZLq3ICfo76or668xratqby0prGns6jTv+3fzYaTgJL70t7X25yLg8yA2uLk4OXr8/H6gYL/g9uDjt7Q2NbM1MrLytbN3djYz93NwsCrtb7EvtTZysrM0tLh2dfn4eHj7dvk5fLr7eTU4tTI1OKFkJbEq6WhnaCmoZ+Ur9rq9N/MxreOnYWiosKZl5ecyMm1y6ehmYiH3ILx/uzvjLihgKSdo7memZ+ppIK92uyVpr3omOmGjoCwtNe/gLybiYGHj4fshYWbtb/n8OCzrp6YnJqhyMj8jYmN46il0Ju9v6quyf6H3tvbyLyvt93xqrnhiZu1vN2Gk67voa2lka7j26iOwbisjez48s/FwKqwpv/RgZOe9Y2VrL3Vz+Ts7eblgKKuoMN0fI+PeoiCZ2F7dalKYFOIUFBxZ26CcYBlTWpohUpaU2d0bWl0n2KAkYuDm8HZ8YLSxa+22cWnrfHo27ZejnlZkXVtcnRodnSFiXyPhX9ydV5hmrSHl4ygXVZyZHdRjohoZ2RcX1BddkxUhtyBgYGIpVxwm8fkgqJdWzQxgDQ3hVRSinFvckJsb1SFXVdTYFdVUnphU0dBQDtUW0xORVo/coFGSEtOTFVVUFBdpKBbWmBgW19hcnB4f4B+goaPnpKWpKaps6uvvKSvWq9jYLTBZWltcXF3dX16dXh3eXx3eX+ChYyIjZSan6Okr7Wflo2Nh4SFhZaVpmC4YmFfgLi5XrReXFpdXbKtprtkYLelmqCfn5qdq7CrWVteZWNlZW1la2xubHJnY2FhXF2pk56VnJ6ap5+lpJqYm5uclpSOj5GPkJujpJ+bnZuYlpqSjIR74ubs7+fo3di/pqmroZeWjo+Ch4yQjIyFhHuJgXx1e3l4c3t0Z3JwaFxeXGNlgGFxZmpfbGlpY2diYGBYXGBpYldfWV1YW05PPUpESkJITklMj05LSlRMh0VLVV5UU1RaVVVXVlBLRktQ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zofSoanqxMLZ97KwwO6njduO/5KosLbHpr6G77zak7+52ujygaeTto6E2nKpiJ1hWWWvjcXc55HDgLS794r2gpmI/fCCgKiNh8z11oXwh+2IlJT6lYOAvsDBt5aD+aW5trKfpJvAvLmrsMDDs6KnmJKjpLbBuqu6gJ+2u7etpL2B2fjR94iD99q50s3F097Uj87Ksrm4uaqttLK1srrBxMPJwbesq6Wdoa6koaiusKq1ur6+zb3Qx8bNzci62NLJ08W2xdPV7Pv+/v2A1r/Y7eLl5erKl8jGwrC2rqWRqKO7wdLQz+PQ38fw2+GFlIyF94qK9vDvh5+WgI+Nl62si4qro4uus4Xm7djZ5YyOtJX595yJypXWz9re6u/y74efxuP08MKfmY2ZkJKuteX2+4eP+aaWk6Pe7NjL3+Sz/dOxsK/Breeiv8qEk6utwN6cwYfJo8zdiorBl7KR1aiqjfHrxKOzGBuY6/rr+KWzop7bpLDKtdHk6cHPgJySoWiAgLCEnH5oZ2ZyiXNSmWR+f1GNkVGXS0psf3WDgleLbnWFn6Wt23m/fNSh2n/j6++unfrZipnp9KObrl2PiYmKlYFweaCQsL+d5pWKh3e8lIp/b4WbqqStYa6lnn+geYCugIppc6FyknnM8c3Y7Obk/KvDr7jU1XqfVzYwgC4sPdqlUGRzU05QcHVWXE9zVGBkUpOKg42IhHGEg3Fob3t+hYeAhoGChoaFh4yOmJmRmZicoLGnqLGrsrS7rru9xmpyd3jif4iNhIR1eH1+jpSMkJigmZ6nqamhoqanpKmxubnFZm5tdXRxbmhkysa2s62xq6afsmBfZGlpaGlqgGdnbXRsbGNlYmtwb21oYKuvpqigo6tZYm9oenBwfqKkop9SoJqNiIGKjpCYj4V/hZyMe3h2YrO3pqGjnaChoJ6ZkpOTk5mWmJWXn56bm52ajYiDgvL35/Dp7d7byMK+u6qxrJ6Nmo6AgIN6d3t8e3RzdYVrcX9zcW1yalxcXWBogFthXF9aVFJhV1RcXlpUXlpZZV1iZl1wYmdiW1VXXV9uZmdsanl1aoF1fIaBgGpra21zbmNpbWtma2lta2ZqcnNrdGtlYmRtX2xlYWFkXlhfX1pgWXBgZ19jYV5kbWNman1jZ2Zpa2ZjZF5gaGRUUEpHTldMPTs+NGFfX1YqKzUtgC0vWjc9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oaGYnKGTjYCGkKa4paulvrnUgKrd8ev8x7u4xdPk9+nY3O7G3Menlt7OxcLCzceuspmk59KOkZaTlJqakZqZqaGmqaOvsLnQxreqqK+tqbXFs8DN2M/Mxru7k/70gYX5/uzY6N+zoqGru7WphJCQm5yxz+D7987MmYmCg42Pn6SflpCNgIKAjID274uW2J2vyayTia/Ljfba05OV+qjd9oKbhaXy5M/ax8nflKXE/d38/+awlJ6jka7FucOl8YmghITOp4295oH01dfrt5SAzLWxttCPoai98IKWpLvqovzw3+eakqSJg5XGm/m0tJyD/byeThQnjo6S7I6gq8el4KemscG40Nivt4Cqisd6sNTEmYGGd3BzgH1vT1JeV2JsdFNTSlB7Y396h5JvnWt0n86DkaWfmX7U1GluibSudn2x7ueLvKKExauclJaJkZhPj4OYwsWAgJnXsJl/epyzp5m0v7pilI1OUXefZod8e8uEmnBwwrvMxsfb5IKIip3Cx8LT4Orxk104M4AtKaSYr4lgjHBDcIOXvIa2mX2Bd3dwdXV3dnFucm3U0dvU4ODi19flcnDWyMfS4NXUymRjZmllZmttbXFyd32IjImMlaCsrK6uq6espqWssLq8uWdqa3Z0eIOIhImMhIaBgHd4eHBvacbCuLCzsaevqp6gqaCgmp+dp6yzXmdkYIBjZWtrb3mDi5J/fW6woaKUipSSnai3ZHFwaHJzcnp/e3dzcXJtalxZW1dTVlhbVWp6b2RocFqgmJuTlJeWmJaWkIuNj4+dkpScmpycnZyeq6KXnIqBiHp/h4F44urVzcG9wbOikpaKeH53dneAdnd3c3OGen19fnJrcXVXWVRZXYBbW1dYWFZXVlVKVFpbWGNabHhuZWlfbWx+dnRicnV8hoOIf4uHio+YiZOZo4+EhIKWkIyOhod9cXeDfX56gXx8fIN6iIiHioiAfXSOhnF4dXh7fIKXhHt7fYOEfHZ4c4mAfXx7eH1/iYNsY2lqXFxSVE9XU0tVTEpDOkE5NDk7OoA1MzdCPkCFhIOHWU9PUk9FSEE5NzI3O0dHWEFAOj1BXVwxQoiPTGk/QXVXY2xYZ3J+RURxU1pWX0w+V1RCXEhGPGtDRUZIUExJhJRNVpJ+YWVcSU09bD46N0BCTXcxPEllZF5YY2c8RkxbOEQxLiwmK3exb6Fu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38nAxfHygI2tp4/nnIKMr9GMiJ+kd7aDv5+Im7PXupLn69/d28qBw76kc15oUpV0VYOi9dmQdIf2wfqinrCiv478xIzdloX9+oiDi4eOipiJkJCDjIx+g5t/h0lDQUFKTVgzgDFZUE9SlpqZk42WioiCjIOLhYeIjY+Vj4mFh4KC7/CB/IOC/YH794SJ/ff/7PPw3drd9IKDgID5+oiH+5KKjo2WmKunppWSjJKPjpmgsc3l8f6H+eqDl5qWoqSgk42Vk5GLk5mllo+G9/T04ebg6N/T4Nvz8/Hh4O718vLs9Oz0gPHy+4KFhIHv5tTBt7Corrq6xNPYgIWToKCgpaWtqaKwrK2wtLGglI6NkZOhl46Jh4aH+oCD3b28ua2vsK6npZ2XlZeWkZaXmqWwuMLBtbu2qJiWkpOViIOC+fbt5vL58erSyNTDyb+3uce1trOtoqCjnZuVmqWnn6Wjm5emi4+XgJCep6Ogmp2il5icm56foaWlp6euqpyorLGjtamxusTHwrexpri1r6qmrru/xMPAzs/KvcHIw7W1v8fSxtLVzcjEzcjM08vErZierLXEu7jEq7etrLa4q62ioqejnaWVm6GjqayirLSvu66cl6iipp2VnaCVmZySkYb9iYmUlJWKgISJhJD/9+/08/WCj5mgk56brLGiop+QoZalnZmYnp2Cj4qd1sCmjImE6YGDgvLu8vb2gvL1gouE6t3994OSiP/z+Ij5io+IiouthIeCi4qPi4Lw8ICGjZKPlZ6WlZKcm7rEwbC0mveJq7iS6rqnk5Ggqufal4uQkPG9rO6L8K2BgK6OkaLe7MegzIzNhqStmYPP4qSCheGXlprez5GihLiHdq764tK8v8almceK/p7G8o2mf16OSl93sJl+ecGL9obr4f6Ml+mG9tz+vuD5gJ+Fhunbr8W+stOvk6m96ruJ8IPky7CaksCdrKeTgZepsa26zsa/ydW/n6Gcna6tmoLygIqZsKewsba0nrTKh6LPqoyHgv7/l6Gy19G/77nY/cGrluHl0oLR9NvgxcLRh8q6ssHn8dDA2sXhgNimnrDN4M3EyrnV346F+ri0zcqsoJaYivvPqbG1t7yvtLCxoJWGl7TPuZ2ptNLKuMPE0On13trK+ISIiJqvqquK6/778ICHgPfo0oHmtKalho6F7+TDlIDgrKvos+qh+d+4jefs36/Vw+aC6UbOjO3Ooo2QoKWbwcvTsIK7mpSWkv2Tl9/d44Dy4vK3cREVECZ6oaKmrsXq+4uiyuydh6vzmZ/7vuisntWa7cKeoYufxof+hvTY5YHJ9J2bwceKyaiqoarE4Ki6gJJ8zHt+mY+JhXqyqZSjfKqBVldgSlNiV0+NcGtwfGhfX4SKTF6hwZDion6NtN+Ve5qsgch4lGukfLvmyZefopqblYFRcm9dgMzE0c2rga2t27eRj3TSoNmPiJRwcleyemKpfnbp64J8g4OJiJaTnKOjuLm3xu3O54aAjqDH1uN2gHG2lYmG7+ng1tDYy8avs5yTg4J7e3yAeXV0d3R22t107Ht483ns7Hh418LOxNHi1s/M0mVhXVuzvWhowXVzenqCgY+HiH9/fH94c3p7hZulqq9jubZncnFvdXp4cGxybmxobHF5cWtmw8PCtbm1vLiuuLnExsGyrrW0s7OxuLa4gLKxtlthYV+yrqOTko2Eg42GipaVWV1lbm9xd3t/enJ5dnNzdXNrZWJfYl9mX1dVVVZWolVYk4GHioOJjI2LiYJ/f4GAfoKFiIuNk5eXjZudn5OTkZSTh4eE9u7f0NHQyr6pp7WorqCTjpJ+e3Z0bGxvampkanBya2tpX1xhT1JUgFFbXVdYVV9iW15jb3B0d3NucW10cWNxdnhrenJ6gpCUlIyIg5WZlIyIi5SVk5KPoJ6dlJqhn5KQmJyqoauwq6Sgopuan6KnlIeMko+Yh4OOeIeHi5eYjoyDg4iDf4Z4eH19eXVrcnZ0fXNlX2tkZmBZYl5UW1tSVEyHS0ZIQkQ5gDY+QE6Jh4iKi4pJT1VaTE9HSktBPkVCUEpRS0lES0w3QTxOempbSUVBcT8/Q3l5foB7QnZxPEE9ZmB5bT5KRHtweEeIUllPUlNpSk9LUldcV1GMgUJFRUE9Q0VBQT1GSGRvbmxpVG47VV0+TjUuJSo2bKKVXFBETl5uXntEhmpUgHRudmV6Rko2TDE8MkJRVSc8gWJOT4lcVnCcuod1kMSSi7O1lIJyfIdpY2JY1pGg37f84JHXgJCm7s2sj9GQ6XO9naRaX3tLh3meZYqfU25XVMC6hpaMgZt7aX2OtIlenl2fjX5uapRvd3dlWXSHjoaEjHxubXtpU1taWmltVj1mgDtFTUJLUllbUF91VW6Zfl5ZV6qrbneM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Qz4+RId8f46EhoeBgIOAiI2NjYuOjpKRmZOPjIeMhIb7gobt7+Pl8v/z9YP+gYKA+P/9joiPlIiNhomAiZKNi4iEhoqFhoT5g4yQoaOmqLG4ubvDubDJzsHXxtLPxcfEwtHYxNDXzM/Mz8/O5+nw84eIhJKHgoeRgoT6hISD/uiA+vqAh4PvgvTaw7m0rLbD19Pb2+SKnZ+lp6Gem6CXlpqYo6asp6yuoqKcr8bYyoX49OTwzbihqYDfy8zCw765r6+ZoJmbmJWPl6KqusO9srK1r6Gfl5qYoJKPkfyAhPrs8/2A/PX59t/X4+3k59LCvLOhpqaqrKegq6u1oaiqqbWAo66srqGuvrSwr6Wxt7m/wsW0tLGuuLu3wbPHvsPJzs7KycS3tLPAzb7O1dDOysnRxdK+v8fAw8bCyc7Jvbq/w8HIy8PCt7emmJmmrKuqtb+5vaabn6qqq5qhoZ2npqKonZ6zoJyhpq+zvqqirKaoqpuSmpSXj4yIh4iRjpOcnZ6AopyYkI6PhYuNl52ZkJ2Vl5inoK2elZSXnqCtn5qZl42MkKbYyKmNh4WAhIz/iYGD+4GEi4qKjo7+6f/47vX77fHzh/f6jJiKi46XlJqTkZCO+IaGjoOdm5uXm5qdl5Kdo56dqaqqjIicmpGN++bBsLusnNLaj6ugp4/IqJ/8r+CA7fSdrKae25urzLiGpZnbnpK4r7H6v6S6n9KiqMCme4HewYDE1M26o5rD4MO7i86IwHpirIeXW5+ulJeb4JyS8P3yifuA/4ru5fzY1s2B7ofx2vbWiv+YmvGDqr/BvZOA7e+IqpSNpqGWnaKkuMG9v8LXubPNwamwqqiqvZqThfGAh4GHlKC+vayqxND5l43/4MbQ19/Y642rwqWtpJmpn7vBxsHV/s/g2MSqmdiS48PIzYGkg+jYvIKJ79/QxNXt2tTh4MrazNvfrqOvmY/x9/TYyrilm7CqqK+5paudnqjGgofO593y/L27t8LLgcm4ze/54+aHoJ+Wn4Lm8ejb48qAu5qT9aazjt7X8ISIkr7s2N6WgeeYl5SCy5+N8tzg0MvUxO2IrZuHy8ufkaOZqoay5oPBnKvTgPndyu2n7ou9uKeds/e1SyYfRpegpqS09ImWqL+NrN3zkO3bcaq0sNKy1pPiwZmIkPOkl4uA8NywzdjjjcC+9/m6oJCB5v2Ri5WAgcuBsY/Lko6Fj66nmPqCcVxgk2BvboSBgXNpa2WHe2ttgImAkLLFso98vI7f7K+U5PPEmYlwpIWItZLJYl2qpKGTUklqTUiGeYTzkpTJq8KBd5PugYTm6uTq2eZ45+R/eOXR1efj4d7i7u7b94mC+oyfrLm2wNPU3uiGnMDH39KA166NhPLg2OPZ1M3EvrWnoZKHhn+AgYWDjIiGhIOJgoT9gYby6tTY29bMwGfGZW5x2uHVcWxuc2xybnFpcXx1dW5nZ2dkaWzOaGxpdHF0eYKKjpebko2hpJmqmKCel5WSlaGqoq60rKypqKaktrq9xGhta3RtaWtzZWS+Y2RmybaAw8ZjZmO0Yr+yoZiVi5KZqaKkoqNjb25ydnNzdXdwbW5sbW5xbnZ5dHJrdH2GeU6Xlo2WgnZqcV6qoKGfnp+akZKEhoODf4B5fICCi5KSjZSfnpmYlZaTmo2Hhu93eePLx8RmycbPxq2bn6mhopSJiYR3goKMh35vbGluWVphX2l/XWtmamVqeW9qbWp8g4OMiYh7d3Z0d3x4gnqMhIqOlZiZmpuWlpmjrqCusa6rpqu7sMOvrrCnpqKcoamnlZGOi4mLj4qPlJ6RiImOiIB3fYiEiXt4gY2RlISFgnh6enR1b3OAbWltanBucWJdYV9iZ2FfYFxgWFRPTk9VTk1PUIRRgE1PUktSUVlbWFBbTk1JT0hSRkVJSVFPVElGRko/QUFWfXhhTkpJSEhNhkxIR4E/QkJBQkZHe3iHg4KHi4GAgUyKjVNcVVVWXVpdWFlZVZFST01DT0dGQkRFTEdJVFhVV19fYEI8T09JRH54W0dPaluNaU9mSGphgWhWh2R9iKqNgJNZQWNHO045JjAtQCooWFBTg3Rsi4Sm3b+pwoV97tFhfoOAd2hekJKRglyDesiVk+qJ54vi27K0oeOQeqGtlE6GRoFIeHmReYB9Wpxel56zkF2saWWSUHODh4BdTpGaXXNqZ4B2Z2ttboKNh4mFkHJodWdUWlhaWWpRRTpeNzIwgDQ+WFlQVmx0kmNeo4Fpc3p/fJBihaKGjYR2gHWLl5qTpMCbpp+Pe22nd7eVmp5heV+jmIBcY7CZjoeTpp6coaaQnIucm3Z1gW9rq6uskoh/fHeHiYF7h21pY2VohmBjiaedq610cW6Bj2CPg4+zxKiyaHd1bHBVj5aY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r7O4ubW0vMLGwMvEs4Cur7CupZqmnqCnp6a0sLa2wca/1u7v+u33/vX479zr6/SG/4Dn5vX56svW4djR84yQm6WooZKqmoWM9NHPyLrAsbWlqqKgoJ2hnaSrtr28vLm4v7epqJ+flp6ZkJCXlIyQj42DgO358e347/Py5dvZzsy2squgsqzDxM/Sw8vJ1YDO0sjJv8LSxbi1u7rBzNDP0dzczMfT2dbX1cTGxL/ExdTSx8K5u8TBwcHLz87Dvbm7wbfIyMLAvtW3ubKzsrOytbi/vbutt6mri6Gqqba9wbiypZyen6ipm5qSm6ispKypoZ+rlZeisLSloKOpqaqvp5unp6KVkI2OlaacoKippYChm5aSmZeVjpyRjo6UlZ2hn56alZSRl5iZp52eo6WflJiWs4XIpo+GhN/r7oCF+OPjho+CioiJh+/r3uXo+uv2hO/z/IOEiY2MlZiPnJaNjoL6jY+M+o+GkZqhnqKfn5ucpJuZkY6JjJOJlJCPjIWTjZiMqpnL+8O5ro6Mn/WupoC8kvmTiei7os7qmL3EzJfz4ZiboPaxh5G1+Z3GdpeohO6TwMe7ucHStobcoKCC0YKPlKjCbYB6foZzzKSFhbiEiv6CiZiH9Pnj6ICHjOCjv8Las7Swt6rA6pCOg4KL+dbc35GhkIuilpap0bjPw9rez9HVwcvt8ejIub3bm4OCi4CEhIiTlpWyr7/shJyH4ODKub/Fxtzz9IvBwaWVlZWal5ywq7jGwdW3xKy75NujoaOpyteB1qO3zsXdy76lrLu5taWNhYeHq4b+gYDpyMzKvKuvnqCf0ayxurGqtq+pwv2Ssc6P4vP8k77Gvf7nl4Gcr/G/1fv/noyZ/M3a0su/rICtmq7RgZqIqZyGy9eEiYKh0pjHgvCM6r+imYjw4eG5z9jV5fOo/8visXbRs6K+0MHxmpyVzdTKvsOkrLyBi6/I29jix6mX3M3skYquk9PU4JWn75uK+b2O4KabxO7p57PKgdnCpJH+6bCfivnfz8jF6feIyL2Yt5CJ6Nzb0N/v+4Bqd+6u76LSu5m1lXqjfF5/SHlmb250hHt7f4lUnVelcHGGjZegu9CniZuwyIGKr7S9wKecz7hoYo+tromRlHBkaJxvc5uNi4Fydu/w4uHY6N/RxsXT09Tc2tnQ1NLP0tba49/W2dHb4t3U0trk7YCBg5CVpbS1xs7l8O6bt+OC1ICxooLq4tLOz9jKxriypqKbk5KJh4eDfn52b2936eXl7OXa6O7s4tvicm9panBwgvB1cn+AhZeNhXp5c29ucnN6fH95aXR2eHh7enh1d3t9gYWUgoaAf4mCjJGFiYaPiod9jIOOh4l7goqOl5qlpqyklJuZmZiUk4aDh42Rj52dkICJjJCLhH6IgYOKiImSjYuHj5SPn7Ozu6+8vrrBsqKoo6tcrVaTlaWrpZOZnZSIoV5gZW1taWV5b2VuwauurKKqnKCQkoeDhoGCfICIkJWWmZyisKmko5eXj4+GfoCEfXZ1cnJqaMDKw7e7tLe0trW3trunnpiIhXmAe4WHfIODi4CHin2GhYKRhnqAiZSYoaqloq2tn5uiqaWjpp2jpp+joq6rpaqrt8W7u7e5u7GqrKqruKm1sqSflaWIi4mJhoiDgoCBfoB7iIKHb36Ae4CAhoN+eXuDhJCShH52d3l4c3V0c3B3ZGVlamlYVl9kZGVua2NpaWhdXFtcY29kXlxeWoBVU1RUX19fWWhdWFNYVllbWFJMR0BBSElIT0hESE1JP0VBXFJzXE5MTHuDiElMi3VzR0tBSUlKSoGHfYKFlYaOTYiJlVFTVllZXF1VYFtXWlCeXmJalVZHTE9OS1JNUVRVX1ZZT09KSVJNVlRZWFFYT1ZLaWGZzVyOg19XWpBrXoBwW5NdRGVVRWJ2NDY4PCxCQENGTYJsYXSr5ZHKjKiwbc6Gj5qDgoTJeE6RY3Bcn2SfpL/fgamTkpmJ6rGGg5BQVI5DS1RCb3xyg01XWo5mgnuSbmtja19skl9cUU5UiGhzelVkXl10Y1hig2d+d5KaioV4YmV+jItuZ2V7Ry8tN4AvLiwyOz5VVGKMUGVTg4ZvXWFiYHGGhFSSl3dkZ2lta3eDfoySjp+GkoWRtq6BfoKFnKJgmHOElY+hindobn+BgXZhW1xcg12pWVWWgIeCd2pxbnl7qX98dmxkaWhmeq5leopinq+5UnV8drikWUxfdLWEnb6+d2VwqX2Pl5iVi4CSe4qbXGRlZldGiYhQRDlRgnCbXqVok2pMSD9pWlxASktEVFxks6igcG2XY0pVOV+OUWNli4Z9h4h2akw/W3J/z8Gwk3tunZy9kHSMbKyivo+o8XJTpVdFc2CAbVCS7sTTe7eWc1qSg1VKOmpU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oKWptbi2w8bAvL6qrLWqo6GgoqaoqJ2cmp2ipq6noqOopKGXpJiPmqynsLK9vbm0up+emJaPlZqhq7SuwLuws72kkKOiq6mdmI+iqaOdmpqjo5qdl6CcrKGrsK+tgKelnaOlrJ+loZuam5adm6CinpmjmZqMj4+ZlpuhqqehoKS728KdjYuI5v2B/oH9gf2KjY2Og4jr/uvs/oaFjIiMiIaDiIeEjZCXiJWSjJePmpiOiouEj5aboKysnLOkp56fnZGSjIuLmJ+Pj4uOhIONk5Cclqqvs6S1sqzbi625gMn+/7a9q8XDicvT2fbjrKuwhY6I04vs9X2MaW2jzZyIhPGKt7ZTn/rIz9yqmrHXkajCkqGstOSthpB446qhlbGJkfyT5YKC4Yb38Y615MHK8beqp6OstdmQ3cjHysy9ttCppqOZn6Skj6Xc3M7Pw9HT4ezl273XhYCD7YnViIKPgJCVkYiIl62/zfSA+dbQ2L+ywsi4ydXknNHcm+H8/YKNl5yWp7XAu727maKTmomOhPWEjo2Fh4uIlLSmop+hpp2UgeLX2vOu5Ky7tK+wtreoko+NjZextqO6ubHAtrWv4Pb1xI3T0pmHppGb2cCe/IGjy6+n6/KQ5pbvyODo4tPQgNnOzrzNm+XW4OqNvMOqnZia2ZHrwu7JxsXFpZSil+/I1sbK0uPEsd/ZkNvEs5POhKnWgNWp4LDKhvnDwNSQk4XyiLXZw5SG8Yisr6/O2sKEhLasib7XmbLnx6CQtdC0seuW4MGxioD4vaGd88/JyLS5wu3UwZGTg4Tz29zRstjjgGl63YC82piGkal5pJ2Bb0peV0ZcTTpxh3+Ijm6BcZNubn1wY5Z8hYi6hLzFjLScrp2DrtS/uLP/46rpzHvabtHCysDSyWS+xs7L0Nna3eHs5dve7fH28ezj4+bT2Nfb4dng2+Dm6ezj5N/a5Or5gYyElqCwqrS+w9bK5Zeqv9e7gKyX9eXo2dzMzrWwnI2HkYODi4SMhoKNgoWAfH6BeHN3eHp1enx0b3FycnBybHR4c3F0gnpxdW9vc3B0h5CZoKajoZifmqWcoqOYlo6IkZGRhYuRkIuLiHl+en2JgXV2bnd5Znx3dnB1c296cn58gnt6eXV1bWxnXmFlYmllaG1tgGhpYGRcx2fVbXRrbGxvdH6ChZSbl5uflo+QhISPmp6d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npWbnZack5GMiYOAjoKShaSZm52gl5qel6XD7Y6cgKe/yOH9iaGz1PTHkcfi99iMicPKuobcvLmot7OulZHhgsmerZWX29j/har9856ts6yKr+S6ssBikeTf+efh/vPvjpbgteTDqeP7697juanalMq2r7GwpbngxtXe2tDCzdS7qraeoq2nqLmD4eTOztnmhIPg6OLJ8IqaqdDxv5WXgJigmKSbnaSty+bdx7qyr6Gyxdje9/aDicKk8nbr5P6Kh/fC8p6yqK2tsqermpaFhY6YmJqZn5aHhYmHgYLy8OTRu8O5ud2o+ZmwoKOilp2YiIiGjqavzKmolqWzvKqXnMXQk9O6pZWO//Cb2+qrlqWsxLS/1Nju3+Lp1tPFx8LGgMbGxMC4vLK/w8nM1uLh7O6JkYvl3PK6i+LWv8K2/u3qgeLdg8yg9sm865S7gsfA4d7X1rivpZ+lo5mkp7Cus8W/ycTTxtDb4+Cd8/CDlXrbi5yQlfDshventpq/tJqWpseLz7imgOuH5J+HhOLD3tW5yNukwYqM59zR29TGtMLIgGZ/vurejIiBfJSNn4yCk59KhkKLTm5cWYd1bWBCVHptiXhyb/TJdqC9hImRmpOVl352fXxtcX+YidPKw8rCwsbO0ubg2dbg3XLa2uHZ4+fk6+Ll8Ob37Ort4+bl7OPa5t/qeufx+oH1/O/q+PL0g4SGkJ6lpa3BwdjN24Khva2sgJ7y3NHW1tG6rqqXiYD5g4H8goKKiYGIin99cnl7fXd8dX14gYCCdnp9gIKJgX2HgH5/jZKMj5eGjpWjp7dgaWR4dHBzacO+usDCw765sqSsuKuipamonpCYi42JhHR1dHNzfHZxcnJzc3RtYmRobmhqZsTKyWdlYLBfWbSlmaqmgK+mqVJZsry0ubLAX2BhZWhnc3eBgIqAhpWKhn+MjZiSk42PiIN7jZOLf3l4c3R4hYaWlZmcl5aOjZCKmKidjX2GhIqKf3iBe3xycm/acX9/i4h7fIuRj4mKh46Kh42HhX97fHx14uDo2dXDymnQxM/PyMjI5nvVd95ras61tbevgKWvtr22vr62urLOyG5u2G7NzM3Cs7i6ZWO5wsfCu7y7pKGUi4mSkqGdoKCam5CNmJybiZKRh4uBhIZ7e3d+b3J0eH1ucnx2eG9xbYF+gISDhoB/hX+Fgn99dmxydX53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m3oIe3p6ent6eXmmeoR7BHp6enuHegd5enl5enp7hHqDe5x6GXt6ent7fXt9foKFiIGAgYODg4KAf359fHyEewJ6e4R6h3kEenp7eol5AgIEAIDXgLvYvoyHkI3W6NLAvryy9pnZieialLjNpv2c5YClwbnt9LN0i4yD94WHg/3w9veOkZqcpKKUk4X19vmA9/X87OzvgO/+8vDt7evt9Of26Pbn1Of57vWG//j06OXn3e/09IH64+Xoc3dx293c1sPI0G57gYSGi4mUk4qKSVJUUICJfYKGjYKSkZKYk5OVkYqRhYeFjYaGgoWKhI6XnqegoaGanZujoJeVoqa1wLW/vcTg3/CMiIeRj4ualJCThYv+9e7i6ebw4Njc3+jy6eX24eHJwMa/uLS8wr20usS4q6q1vbWip7OsrKWgoZqoo6eZlZaNgoeHh4WGh//r6e/u74CE9vaC9NHt+5vr+4yYoaWjqqunqMDUzsvHvb+2v6m3ub/DyMO6ucjM5cezpKOnrqi1wtDTz+Lo1sLGx8bB0LienZymsbKyno6NhYOBhoyYkZedor3Cy8rZ4Mu4ubOrtK+2t7m+tq+rp5CPiYmLg/f38efi+/j7//7l/YeIiIGEgoCAgYH6+YD45O7q8O7f4eDg3eDR3OTf2N7l69/RxsXT1tPHvb61sq6qnayrrLCytrCup5KQsaWepZeVk5uboZudm52mnqOcl5iYlZakq6qutLGsrqKZpZuZlpGTnKaorqmorrisspqdppaYlouRoK2fnpijpZmYm5+hpqe4sLalrICppKGdmaakpKGsrpeYq5qgnqGmnJ+ZpaehnZibo6ant63A79GYkZaKjYmKhoqEho2IgoSKiZH5/v76gIGOj5uemZWNiYyCjouHjI2KhoqQj5GNh4uGgYyIiYqOlJuRkZemn6GqlISKkIWJjpGFh4mHiJWbm4elpqWanp2dp9nq+oCImpyfqLG+xdrg/4L+iYeHiI2EgenNt5ybm5Sclof75ryYjbLVy7WttdPY2eiJ+oevh8WYm5WntbjFv/290vvItKOI9Pry6djRvNHW6tnc4PXn8vj08+nr3tLLzczIyOLfy8DHsaq+t7Cu2cXI1tLKrISCuM/Pv8LM7Izo0bqumICmn6C5u7e8sbrq3cawqrSz2N3uipOQjZKC9dTS7oWQko2Sj5qoq6qfoqabtKGakv2AiJOgsp+OkYfr2vHq3+PazcXGxuLcgH3DbJKvq5W11JeLoIT6mrShmp+dlpGEgImiqtOzm6/TkqOZo7fDtM3X3cfIyMjHzszV18jEta6rrYCusLq6qaKmnaCaq6zBvdDK2eTvhYWJg4Tj7/C4qYWhhqCYpqunrceS0bKzsrSvrL+2qqufp5aZi4+gn6G0uru0srzExcPAzNDBgO7u84iXrb+Ft1WehsCQgeSF56am1oLEjsu9o4vyiIDKmIWB/+Hd1uDQibusi/vg4cO3u7jCv4CWX5/2hFhjfm2Xo5OBeX9zh22Ia6hVYHuSe72Mk01nqYSkk4WEh39233R5efbq8OyCeXx7gISAhIHx7u945+jw6+nrftvg2uLq6ebq6dzs3vLn2/H77+l05eDk5OTl3OXj5Hfu5PD5gIWA+/f28+Lo/ougq7O3wMXY3d7thZihlID32eHY2sXIuaylmZGPiYOKgoiMk4qJhIODfH+Dh42EhIWAhIaJhHp1foKLlo6Yl5+1s7xqaGVtbGZvaWJkXmO6t7GqrrC7q6OepKqoqKKroaSWlp6ZjIuPkImBgIuEe32Kj4x/g4+JhX92cGlycXh0dHx0amxpYl1hX7i1trG1qoBZoJ1VraTGz3W+u2dtdXh4gYOBf42ZlJOWj5CKkHyKkJOXmpWEgYqJnIp+d3h7f3yMkpyYkpufmYyPk5CMmYh6fX+IjpWYi4CEfHd2eXmCenp/hZeep6mzuqidoJ6WppqgoJ2inJSVmYJ/e3t5d+rm6+jj8e3u4eXR2XN2c2xycYBtcGvNz23SwczHx8KpsrOvrqmiq7KspKquubWsqKOsqaCRh4eBgoSHgI6OjoqLi4SGhXZykIZ9hHd1c3t2fXJvbW5waW9rbW9vbW53fXp8gYR+f3t0fnl/eHR4enx3d3Fscnlvdmpvdm5ubGRnb3xxdXJ5e3BwcnNzdXZ+cnVqboBpaWRjYW5ubmxzc2Zqd2dqYmFgU1VSV1xZUk1QV1ZUX1Zmk3lRTlhRVFNXU1NRVVhPS0xRVWGdoainV1VaWmRlYWFcWWFZY2NhZl9hXWFhY2NgXGViYWdhYl1eZGRaW2JsaW10Zl1iaF9eXmJYWVpRTFBPTTpSYWludnp/g6y2yIBwgYJ/h4ySmaeluF2qXVpYVVVQTZubn5meop2djnnZwJl8aox6aktCP1FPR0oyTTRfY4RaWVNlbm19et3N1uW4lYFjp6ykl5GNgJqouqilpqyYn5+Zm5KYkYmHhIaBe4uLfXSGcWV5b19TcllXYl9fUlRRZ316cHd2j1Z/aFhSQoBRR0RWWVZeWmmckXdgWFlRcnB/UlRRS01AdmBigVNaXVhbVVtkYlxUVlxYcWZgWY1ITVhlcmJZXVGTg52Pj4+Cenl/hcmxiIWxg22DgGeUmVlPW0WHVnRlWmVpY2JYUVdscZR1YXaXYXd0fouMepKcqZujo6akpqGmrqmrpKWqroCuqq+omI6RipKLkJCgnK2tx+P5jo+Sg3/G0N6qn4GhYoZ3jpCIjJVpiGdqbGZmYmlfXV1aZ2BoYWNuaWNvbWpgZXB2eHtwfIV5YLK9yHaLpbyM0JbKn8pbSYZWmXWSxXaseKaWeVyNVk9yTUA7eV5YUlRIPF5mOmNWZVVPXGJydIBpPU5EEA8hUSg3YFRHR0tBPkFOMlgaMCUzH0A1RwsSHAY3DBpAOTgxWjQ3NmZYXFk6Nz09Q0U/QT5wcXY+cnJ5dHFzQWhwZmVpaWVoaltnXG1nW298bWw5amRqa29waXZ4dj90YGhuOT87d3p6dGVtf0hYX2Rma25/gYaQU2RqYICbgoV5dGVnXFdVUVFPTkdJPT46Pzk4Nzo7Nzs/Q0k/Pz84ODk6NzErMjI5Pzc8Oj1JQUEmIyElJSElJCAiICM/Pzs6Oz9HPDk3Oz8+Ozg/NzksLDAvJyctMS0nKTIqISItLywkKDMyMy8sKSUvLjMtLDAqJCcnJSYrK0tAQj9DQoApREouUz9fYjdQUjI3OTUvLywmJTI9ODc5Mzk0OScrLCkrMC4oJi8wPzQuKCorKiUqKC8tKC82MisvNDU0PC0hIiMsMDU5LyctJiQjJiUuJSYmJSwsLiwxNColJyYkLScsLiwxMjAzOC0sLTAxLltaWlNOXWFqbXVldUNDQDg8O4A3OTZnbz56bXVzcnBdYGdlZWpgbHZuZmlteXlyb2hybmVYWV9eYF1bUFpUV1dXWVlaWE5IZmFYXlhXWmRgY1lUT1FVT1ZTU1RQSUVLUExMT1BMTktJVVJZVFJXW15eYFxWWFxTWExUX1tfXlZaYG1eYGBqbWVkZ2hlZGZ0am9hZoBjYFxaWWZjYV5lZ1paalpdWVteV1pWW19cWFVaYmRlbWBvmYJaWWJcX1tbVFNQVFhRTVBVVV2UkZKNRkVMTFRXVFJNSlFIU1JOU09QTlNXV1tbWF5bWF5YV1NWYGVeYGdwaWtyY1xkbGBhYGBUWWBfYGlqZ09iZ2xqbG1tZ397foBBSEM+QUVMUFpXWi1SLCsrKCknJU1NUU1UV1ZcWE2Rim5dWoGLinhydIuKfnxKeEVhO1VVV1RfZGFkWWdBRGRLSks5bHh3cnBtWm94gnBtbXtwfYOAfW50bGJhX2FgXm1wamR6aV9xaV5XeGRkbWpiVE5KYnJwZWNkfFCAc2dgVIBkX2JzdWxnWmCLgnVoaG9ogHJ2SEtGP0AxUjYzRS40NTA0MDQ8PTgyNj43T0RAO1QrMjtIU0U9QjpmXHhubG9kXltdX3RwLSlUKUFZVUdDdU9OXUmLV21cTVJTTks+OTxJUG9OPE9uRltbZ3ZzW2lucl5gW1xcYl1lbmdqW1ZWWVdbW2FhV05UTFBES0tbVGJXYGZoOzw+ODpaYWRCNjlIQUI8RUE7PT45aFVcXFpWUFZPTlBMWlZhWVtjXFZfWVVHSFBTV1tTXWRRR2xfVC0yODkoNBicSwOFACQBAAALHh0xNjYqUD5AalJOUKiWlI2OfFV1YkeFe4Rzam5tcV8KeXp6e4B/gYaAfoZ6HHl7enx6gIB9e32Ag4OFhIGAf35+fHt7e3p7e3uEeol7BHp6enuGegF7k3oBe4p6AXuEeoN7h3qLe4R8s3uMfLt7hnoEe3p6e4R6A3t6euN7jHqJewN6env/erF6hHnGeot7Anx7h3yKe4Z6iXkFenl6enuKeod7rnqCe4d6AXuYeoZ7hHqSewF6iXuNegR8fHp8hHoBe4V6AXm+eoV7hXoDe3t9iHuhegR7enp6hHsRfH2ChYB+gIODhIKAfn19fHyEewN6e3uEeoZ5BHp6e3qJeQICBACAu/O0i+OroseajYOX7fry5taFhpOG0eHUt8Cwj6qzma+YjI6Kho6RkZKUl52lqJygqZufp6mrqJuSgfb4+/f+7vSAgYCGgv+CgoSD+/Lv/Pfu6szGu8HK5t309P6M/Pn6hI6GgoJ+hoN2e991e3x7doGKi4aEgn52doSMkpeWlYmAioWGiomAhoeOkY+PjZaTgY+PkpGgoZyempOLm5anoqSakp6bmZahn6aftLbAycXD3OqBgP+Bh/j1+IX++tjt59z14+fd2s3HyL6/2sq80MPHxsDH1cC9z9HR3trY6ODNw8rAvrm1u+PXu9XLxs68uq2emaGnppWSkJWVkoiLhIaAhoiMio3zjPzw+patqLXR1uPp8Pn48OPYyNPTyeHT1d/PztDY4Ob7+OzYzba8s8G5zOSIipWdnp6Qh+jZ1M/3x8e/yd7U1eXKt5SMkqefraqrrKGxrb/B64f9/Yb2g9zW1cbL1tPh4dnDuZWVmZWVkpKNkI+Ig46C/YKFiYWH/oeAgID+gYCAhP2A+Pb7goGD+PDs6+7z/eXd5NjUz8e5vMG6tKuqtbSqsqmwqbWoqruzr52YlpmUqKSkl5SIm6GjopigoJCWo5aZlJ6Ym6Opp5+ls6mfpJ2bj5SRjpeZoLGro6qvpLGjoZyZnYyTm6Cfo6Kfnp6appWmrqWos7a9tayAqaStqq2lnKWtpbCcmpign6Onp6atpLGjnZ6dlKGzscfc8dLDk5OOjJKUjZSTiIqJgvyIhoaHh4H5h4qCiImGkIuDiIqEgvrvhIGBiYeWnJSglZqSjYOOgf6FioiOlpKgrKOboJmUk4+ZmJmZjJCJ/I2HnJidlZucm6OknLDJ4uuAgYSLlpygpbCytr2wqJ6tscnM2ta7p5eMj4CD/fnk4s6k+IW649DH08XMzOT7iJGXkob8naCgv8LEuq2uvrnBydHQ0ePYz87Ou7m6xMzDz9jv7/j98/Px/oP99uTT1c3lzci+vqespbSuu9fe3u/fs43HwJKTnZ2rudPcuqicuI2AiaOmx7OWrba0wcetp6e5vcvV8oKPg4aKhO7Y6/Sa4IGJgf6Fj56an5KVn4uKiIn99P+Bjv2C9Pry3Pjv3uDk4dXY4umJiWmwzcfcw7OukuSJ2+PJvtTJxffv7YWHgoCAgdXRx9LUwMjGxdbS2+jj4Nvayb+4vcLFvsnQxay2p7OAsLa4raChppultL29y9HMzNPa3+TrfZGXkoSQl45/h6+epKiio4vyqZeYhpCXk5qgpp6foJ+YmJGTlZ6nwMjKvbe/uM2+ztHa3Or89f6EgZ/C7qabo76HmNiYvNvaw73D9ZzMqpDo3YX73a+Uk4X668LNvNK5oKKvt5SAp66zv7aAhbKUhcCGea2DeG6AxdbLuahoZW5jmrSikZyMbXh6bqCKhIB8d3t5d3h4fIOOlYuMkH19hIiOkYyLfvXz8uno2ON4en15c+t7e3x149PZ5+ju6dbZ0dTb697s5ut93N3acXx5d3+Ci4yChfyBiIiIhpOgp6qqrauoprrN2+jt8OKA3NTS0cy9taysopONiY2JeoaGiIiRj4+Miod/i4mYjY2FfIiQi4SQhoZ/jIyTm5udrbZjXrReYLCvsGC4t6S0saW2p6ejopiYk4qOnpaMm5mgnp+ho4eDmJaWpaCdq6aamaCdo56Zmq6gh5SPi5WLkYmBfYmNint5c3V1cWpraWaAZWZqZ3HUe+vi4nyEgIqcoammqqyvrKajnqilmKOYn6+kpKShpaa0ta6elYGMg46Jk6NfXWVsbnBoZbSnpaW6m56Tna+oq7ionYF9gZCFj4mKiYSMi5idvm7T03HVc8HAv7CwtbC1uLmspZGOjISAdHh8e359c3x67XR5fnZ14XeAcHHYa2tpbdFt2Nvmc3BtxbirqKqstZ+co5+mpqWTlpWKgnV2fn56hYCHgYp+f42Eg3Z2cnhxgX9/eHxxeXxycGdvbmRufHJvanRucXt+eHR5gn16f3x+d3hxa3BsbnpxZGptZHBqbmlzdWhxd3F0c3FwbnFtdmp3dm5wdnd8dGuAZmZucHZta3J+dX10eHN3cm1qaGFjXmRdXFtZVlxnY2l5i3ZuUlVXW19fXGBeWVxaU6NYWl5iXlyxZGhjZWRka2dgamhqas/Eb2hjZl9pal5kWVtaWlhkXcRla2pmY15mamdkaGttbGRoYF9gV1xanVVIVExNSlRbaXZ8fZGnv8qAdHl8goF9e4B+gYqEf3uGhpCNmpqNiIaCiXl42c2xrZdwn1F7b19QXEpOS1FZLzI4OjVfUFdZc3x4dGxwe3p9hoiJhJSJg4KLe4CFkpiOkpOloaOomZ+Zq1murqCRmZOfjYl2e2RfW2JWVmFgWmhjRzdwdFNRVFRcYHl/YFNOakOAPU9PaFdBW2VmdHhcVlVhXmdthEhVR0ZJQm9gcX1feU1ZTJBLUFhTV0pQXE1OTE6KhZBGUYpFgYyCcZWNeYGBf3iBhZVyxZ3H+Ovtt52RgNBtpqiPhpCMia6ssGhua2dlZaagmaeso6WkpaablJ6hpbC3s7CvsbC4qbm/uKi4sL+AuLu6p5ubn5Ohq6qosLa2t8XS2+35hJedmImaopaHjKCUmKCNinK0dWtpW2ZhXWJlYV5cXWBjaWprbG5v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jaCeoKOipaGqpqOdjZmMkoSLipeVmZyXo56aqKKuqaqzwMSxwa6AmaanoJmao6SfpcCzpJ+qpKKysKyytrinrqakpqu2xMXnx8ChlJKhkoyRiZKSioX7g4qB/42Ag/KCjoqHiP6DhoSIhIuG9fzz5PKChpOalpehlomcnpGVioOEj5mVmJCfoKKgl5GChIKTlp+gn5aJlIuMkpGco5CChouaorGwyuWA8O3ohoeNk5WOkoqSkZySkpKNk4mTkJSZmJGRiPLn2cW2lZG5y763vcLM2OCImJmqnp6knqaqqaWUk4qUmbG1uczNz9PC2NXRycjFw93Nz9jQzd307vH7+v2B6Pna3tfi5/mI+eTa2+/e5OHzgYL/7NeB0Mm9tM3W2+Xv28i3wrqAudPO1Me81dbDqqKZm6PFzdnX74KRiYD1/4L/iZCev87i6tvkg4yOj5SSmJGC7ung2+Pr4cLEx8DK4OXa2u+CjZefiIeJea3Hqo+ThfSB+oGC/IOE4/Tj6fTt4ujRy9TX6/b7gfz28vb15+/n5unq7e7r39rMys7JwsXKwLayucGAt7m2xcfQ0NrY6NbU4trW19bAytTg1NLU0crLxbeqra2eqruxs6+2ua2qp6eopKCnrLC+t7Gqt7zBu8HMztve19PU1+Pa2+/wgO/+9vWKmLLN4Yaqt2O6a5XGy5/dybiP4cy8tqSbhYmHjYKBgfXt7Maztrvr/IOKh/P65d7M3u6AuXKLjbGYkpGIgvuEh4SEedZuZWtyb2vLcHOBgIiShYF7f31/e4J5fXd0dXZycdHUa25wcnl4f396c9nceeXp3PN89PR5fH5/f36Df3/28YSA/ID5//fwfvV/h4qLgYB+h4GBgISHioSCiIuBhoeJiYuYnKuttLCuuLvEys7TzcSAxcTBs7GyrKOek5SVlo+UhpCIiJORj4+RhYB/gICDg4V/hoaOj5qfoKOnqqSgpqmirai8rqiemZiclYqNiZCdm46AhYqGjYuakpalo5yNh5GOk5SSnJmZm5+prauloKuqrq+tq6GYmpaRjYWAgoeEg4iYnqKfpI2HkImYmJOIe3SAdXh0bXaOkpSYlJqkr8Fjam14bWtiWl22sa9hs1lcYGBgZGRjZHBiaWVpb3JpXbFnZ2+CdXNzcnuBgIFrt7VcY3J0dGhmb3Z2d3xvsJiGlaCruK2woKSox8zFydFxd3FxcW3GtcRou7efpKmnopCJgX17enuBgX2A+HyDe3t1dniActbSbNfHzmlnyMnX29XFxl9gYLKxtK+uq7G1p5+SkY+Lh3VxbW5ucYJ/e3+GhHV3dXFvc3F2iYt7eXFzdnN+eHN6dHNueHR1bG5yeG5ydniBg357eH99eXR2Y3Jtbm5oaGVta2hrY21tc2lvbnRwcHJsd3Jue3mCc3N7goJzgHGAY3JzdHN0f4V8f5SKfXV9cmVqZV9jamlga2RhaGxwdm6FcGxXVFhrY11hXGRnY1+1YWphw25lZbdia2tpacBoa2dsanBx0+LdydFpaGlqX11hVExkb2p0a2locXpub2hycHR1aWljYV1lYF9hYFZRV1FRUlFYXVRIWGN0gYuFoLqAzM/Hb25vcnZvcWxwb3lzcW9tdW58f4aNjoaFetC+r5WAZF6GaF1YVlRYWlA1PjlKRUdOTllhY2NYWlRgXnVzeYKBfH1qgXh+eXx8f5SJj5SGh5Oln6CjpKlXnbSco5yXm6hVnIp8f458e2pzP0CEhIJZkIl+d4uTkZigiXdqbGWAaHt1d2lgf39xWlJGRU9qam5rgElXS0N3dThqQExdWGd8f3F0QkdGREdITktDdXR0cYF+d1hcXl1hfIZ9dYlKTlVeSVBbofHIwaaSfuJ24W9t1mtrsbussLq7t8nEvsjM0djZbdvc5OHd086/ubWzvsHEw83LycjGsr/Fu7e8x8mAvrqyvLzAvMLI1M7N1c7M0dPDzdvj08rFwL3CvrKooqSNjpWCgnx7fXh3e4WCenp4dXd9eHh7i5GVlJienKOpnpqbnK2lorO0YbzPz9h5hJqwx36t1IL6hp++v5fYx7CR6sq1qIl4XGBdZmFhYbitqolyc3qlrVpeXKqvoKGXscCAYT1HPktDP0A5NWU3PDs7NVszLTQ5NCtFJyYwLTU9Mi8qLy4yMjw4Pzs3OTcxL09UKy8yNj09Q0VAOWNmPmtqYHU6cXA3Ojo7Pz4+OThgWTYvYDNgamdjOmc3P0FCOTg5Qj07Nzo8PTc2PUQ5Pj5BQ0RLT1pZXltYX2NmZ2lrZV+AYV9eVVVWUkhEPD9CSEdOREs/Nz07ODk8NDM1ODc6OTkxMy4zMDc8OTs+Pzw7P0A4PjdFOzgvKissKSIlJCs6OjUvMzcwMi41LS86OjYuJywoKCkmLy4tMDQ6OzkxLTk3OTk1NS0qLjAtLSomKi4qJis0Njg2PCooMy4+Pzo0KieAKS8sKS9ARENAOj5GR0cgISAnIyQhHCA8NzgiOx0dHRsaHBwaGh8WGRUYHB8bFSUbGRoiHBkYFxkcHB8XIicUGSAfHhcWGh4eHyEdKyQYJjA4QTk4KCglNTQtLC4cIB0dHh0uJjEdMzMpLzU6PDMxMC0qKCguMzI1aDlAOTo2ODuANV1gNGlqekJAdHF7fnl1fT5DRH5+gXh3d4KKgHlqaGJeYVpcYWVgYm1lWl1jYFRZWVVWV1ZaaGZXVFFaXFllXVZbVlFMVVBSSkhITURITFBZX1pbWmBfW1hbUF5ZW1tWV1BXVVNVUlxaYVdgYWZjY2dibWZgbWlwZGJqd3ppeWiAV2RlYV9iZmphX3dzZV1kXlRcWldaX15SXFZVWmFpc26EbWpVUlZlXlZZUFRUTk2TUV1UoltNS3tASEdFSIVJTUxQTlJTnKqomZ9RT1JUTk1XUElgaV9lWVNRV2JZXVdiYGNkXWFcXltlZGBgXllYZWFjY19kZFlNWF9rcnVqfo2Ai4JzRENDR0lBQjc6OD85ODg3QjxISk9TVE9PTY2LiHpvXF2HgX+Fh4eHi39JUkxaVFNZWGJmZGJQTURMSFtXWmVmYmBRaWNpY2NgYXFlZWpdXmd6cm1wcng/b4RtfXBqb39CemxhZHVpbWJyQUKDd3BNenduan5+fIGJfm5mbmiAb4aEhXZofHZkUU9MUl93c3Jmcj1GOjNeVydELDNALjpMUkQ/JSknJystNDIqTU5QVWRoYkdHSEhMYGRaUVsxNDg7KCswDBcsKiM6OWM7czw9gUNFb3RjZ2tqZnFlX2dna3JwOG9ucXZ5eIR+eXdwdHFsYV9aW2BeT11kXFFRWFuAUlNQYmtwc3d2empkaV9aWVRCSVVcUUtPUFJXVEpDQEY9RVBIR0NFRz9CSVFQSkdJS1NgXV1fa2xsX1xfW2BkW1pbW2ldWGZjN2FoXVgxMTc8PSIkIhUcCAcGBQYKEBAULC8xNjE0KTI1Pjo8PnZ2eWRUVFh4eTw9OmRuZmZcb3UBeol7AXqFewF6hnsBepZ7gnqKewN6enuEegN7enqJewZ6ent7enuEegJ7ev97tnuJfAV7e3t8e5J8AXuNfIJ7jXyRe4Z8BHt7e3ySewF6iHsIenp7enp6e3uHeoN7/3qdegl5enp6eXp6enmFegF5h3qFebl6mXuIeoh5rXoBe4h6AXuJegZ7e3p6enuheoR7B3p6e3p7e3uGeol7kXqHew99fX18fHt7ent6e3t6e3uPegF75HoBe4R6hXsFfHx8fXyFfYR8jXuJeoN7h3oCAgQAgIuMjYqLkYz7gYWKioaC8/Pu+4CGkZeakp2arLaksbKejYmMj5GWl5mYnZOVl5Gdq7GrppydpZ+gk4uMkZGNi4eFj5WUnqGZpJaXkYuMi4J9fIGCgnuBgIWDkZGQjZCSlpyXlpmTiYqDioeGk5yMlpKcpJuZlIyLjZiLj42LkYeFgICAg4+Ti4+KkI2NlqCjqKqinJGYlpmcrKquvrrAtq22qay5tKm4wr/Nz9DY2NbZy6+7xtDP1c/DyM64sLCvp5+eqbOyqr28vMC/wL+8ucbK5v3y5Nnv/PHh4uyIgvaAguvz6OXo5uvv+Pf35/bt9fjuh/Xo5N7Ot8C2vs6/v7G/gKqxvMzJzMrOyNXn8viEg4b/9fv65/SAhvr4gIuWlJGJ/YH9g4eNra+7vtXAlpKgpaSmrr/d3se2rsbAoZuKhYWRqpmisLKhnouA/vLvgNe1t7Guw9THyMzGxcrFycja6PiEgf+A/ff6293CtKevq7SglJWXlZWijY6UlJaViI6NgI2IiIP+/PaBgPjk4uXs7PKG//rw6+rm6uvv4NzP2dLb1MW/tK+4pKOnuZujpaimuJuXm5qfnqadrJ6fmJObpp+ep5qXmZOOjI6ZmJKYq7KrqqeurKykqpirppqpr6afsqSkl6OjnpyVh4iJkJenoJWblaOmpLWao6Clp7SvpaOqgKSdrqyho5+gr662sK+2q6uqqKCmp6+0pZ6hp6WjqbOsoKCko6mpopyeoqempJ6UlJSHiJWRjY+Vh4qJg4H/g5SKiIH8iu3v8+f2hJKfpJ+gpqCgn6mfsZOZoqquwMOuwMavs5+Yg4+Rn5+ztKObjYuRmp6gqKGWgYORpai8ys/xgPX09IHs9oiKko+ChYmPiY6L/oP38/eJkYqPjpWOhoaI+una66Pw3dLp3OHu85WNoqCqo6WgoaGkoKGko6arq7C3rLGztcnNwMHEubW8ycjL5d3d297o3uPy8/36/dfc4+fm5fHo7+rs6f/yzdjs7IDx+OTWzMTRyc2/vsjLycS9gNDMyOHr6/X1hID89e7shOv78/SFmJmYq6ynrLWjmJeI8/zr4+z9hISHjoCFh+fTycHA5cW8xs+mm/S8h/+IjprX9d2xo8Wkl5WKjJadk4qHio+NhYiOkZWRjJqOm4aJl5SOjY+RjoSBgvDx8feAgYH6+ePPzNTj3+DTy8O4s7rGgNjP1d7W1t3n1NrWyszHy83Qz77d2+DR4trK38fAw7W1sK23urvDwsvDyLuyt7i7wMjGxtHQys7FxM/X5fyCiv6BiIOVpJ2alpyUhYmBi5q8w9v7hZ2pssfmi5SKr6aeheTLwtDVu7WonJuZlI2Nh4GChYaHipGMj5aKioyNhYqIgHd1d3h5gILve4KCgn995Obe43F1fYKDfYiDi5WDkJaOhIaIiYSBfHt4enR5fniFkZKKhHt9hYOHfXx+f4F7enl7foB/gIOBjYOEgICCiYaHhoyMjYWCfXp5gX9/fYCAgIN/gYeHg4iGkIuKk5aJko+dpJ+jop2hrLevurq8xb7CgL20vL23qqyjpZ+Ym5uVlJSNiIKJioeKkoyKlY6PjYWSjJCdoZalrKSno52jo6Cpl4aQmKGhqJ2SlJaGhYuOh3tyc3RrY3Z8g4iLjoqLipSesL23qKGuubGhpapiYbNfXqquqa62t8G9xsS+s7u2vMO6acW7v8Czn6CTkZ2SkYmXgIeOl6OhpqSpp6+9wcFoaGrLvsC9qa9bYbe4XmdsaWVfsV22X2BlfX6Dg5SEZmJydnJ0dH+Ulod+fJGQdXJlX2FreG11foB2dWZhwrizYKKIjIiImKien6SfoqahpaexusNoYsBhu7m/rLOmm5OinaKVjImLioWIe3p8gISCdHlzgHBra2rKvb1gYcW7w8PDvLpjtrSurKmtsbm+srCcnpaYkIF2bmlvaGx3jHR8enh1gm9ucG91cnZud25ybGRtdHR1fnJzeXV0b213cm9ueXh2dnR5f3t1f3F4cmdwdm9ld2prY21wc3dzcm9wcnmAfm91cX6Bfo53gXZ6e4B8cm52gHRtfIF5eXV3gX2DgoGEe3x2cm9wdIJ/dHNwdnh0c3lqX2FnZXBzcGxrbnN1c3RtbmtlY25tamhvZ2lvbGnMbXtxcWzNd8nOz8TLaW9wcWZiY1xeY3NrgWlrbXR0eHdpc3dweWpwZmxobF9paWBZVVddZGRjZGFbSlJl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+ObW1NLJt8PV293O2tvJvbrAq7mVpo+Wm6qnoamvsbqAvLG+v6+2u7i0sL/AwMLHzcO/x9rl84qWlZaan6ONmJeXn5mSk42jv7e7wLCbh5GZlI+Zr7nGubfAubKurKqiioabn661uKyzrKKoppqbio3t3MzW3fjd3uTr7cXDxdbM09bS19re5drc593Yy7WkrrWbnKmgoKablJ+elI2LiY2AiP2AhIWGhISAiPX09vaA/4OCgPPg4ubl6/Tl4dTT2Nvcy7y5saGlq62jp6qfpKGlnqGeoaacop2ppKqko6+vq6qhrpqTkpmXoqWqoaqutq+xnqeOk5WcpLCwr6yusrC1qp6ZppuQmZagnqWuo7GprbOyqKeur6KfoKCmsKq2ubKAqaanpZ2bmaCgnq6nrrautru4w7zQusCwrrOjqaWxr7KWnKGhoqicrKmutKmlnp+YlY+MkY+InpycjpOLiouKlpiTjoeLgvuB+4uSj5aUoLCpqq2ysqKZm5aYqqi6tKiys7CtpKObsLGyt7qyq6GZlJWfqK6nsKaVsbC8uLnM0oOAgISEgOyD/oeMioaBiIeEioSDhYGEgIOIioaNioz96/Dn9OHW5t/YzLOwqqiUl56xr7Owp6KwrqOYnqSzsa6qt7aorrjBvbixnqadopOWsbTHzMXg4N3d3eXd2d7f2c3U0Nja09bX193d1dvg0d/Rw7rO0tTK2dnX1MfCxNK/wr+AwsXH397k6fuIh4SBg4n57u3h8YeRoKSqyMLL2uHq9veChvrx+uPWxMzMu8S5xcjGxsLX2tjI3L251eXKwrW/1tzi4N7M0a+rqqCuoZuXmpyXmpyblaCdpJqbnZmWjYSFgoOGkoyPiIqJ+f39/4KMiYWNiYuOkIKHgvT17MTG1taA19nV1cbSwb29vb7g5e/o5Nrf1eTn6ubt7u3d5MraxtTOxMnQyN/j3s/P1cnI3NDP2OHq5uHj4On58/H7jo2SkJetqce0wrKqsKiqtbzMyuHT84WUmq+5xN3l5NCek5H338i/rKSlmZyVkpyQjYyHiI2F/oSA+YD7gPKCgYKGhIeAdHl3fHiGiIiEh4iNipGHiZCTiJ6CiIWMi5WTioCNiY2Hh4yHg4GEh4V9gX6DfYiJg4OGe3Z6bnp6fn51dnV4eHV5dnV6fHd7e4ODg3+BhYyNkIySk5iUlpqTk46SnZmbk5KLkZCNlI6RlI2Wk5aYn52bmaKgnp+en62xtre9x72Atby5wbmppKWimomZipKakJiPlJycnaaaoo+UkJeVmqOkr7a9xLm+raqtpKK0m6q2sZ2PhYiTl56SmpyMjpCSjpSalZehqZupsLyoq6uqoK6wpqm2smVvY1tYqVeYmJmZqJ6anZ2Xi5KiqamhrbGloJqjkp6Ck4CHho6Ge4OEh46AkIiVlYmUnJybk5qbmZ2ipaCZnqiqrV5nZWlscHNgZWJlbm5tb2l2h359hHhoWGVtamtwfYOKfn2DgHx6e3hwY2Jvd4CChHqAfXZ9fHNyZmesnJKcn7SfpKSys5eWnKedpKSdpKipsq6rtrKvp56TnKeTkpqVj46Ffn5/eXFxcXOAbMdjZ2hlZmFeasHKxb1gt15cWaqfoKiqs7uztKGZnZycjXx9cl5kbHd6foZ8fnl4dHhzeoF3e3F2cXVsaHN0dn10gHNxcHNucHJ2cnh7gH6Bcn9sbnF4enp1dHBydXR3cGtseHRueXSCeHuAdXl5eH+Aen2GiH96c3Z3eHJ9fHiAdHJ5gHl4dnx9eISBg4mCiYqIj4mfjJCKjZCEjYeMh4BkaG1xdX97ioeKlYyLjI6HgIB1dHJqenp8bnZvb25teHVyb2VuaMtr0HN4b2xobHNpZ2x5fXJudHBqeXZ+d3J5dnp5bW9ocnFua25ramNkXmBjZ2xhaGFVcXeEhYeKh2GAYGprZ7xmvmZqZ2RiZ2psbWxsbm1vaWtub2puc3fg2eTV59PC0L++qYp/dGhLTExUVFlYV15vb2phaG96cXFndXJmY2xycG1xZW9vdGZqgYaVmpWsrKeloqWamZ+dnZmenKiknqOgm52YjYyOg5eSjYmcoaCSnJeUkIN1coBvdXdQeoSElpGUmqZZWVVTVV6spKifrGRqcnJuh4KFkJuhp6VeXKuttpuYio6UiIt/iomHhoecnJuToYqHoauYmo6Qn6CjoqaioYyNjIOWjImJiImEhYCAiIiMg4aHhIaAe3p2d3mDgoqHiITt7eTjc3x+e4J8f4B8b3Jt197hy87Xz8i/trWywbu6v7myx8TAwMO8xLrK0M3Dvb/Et72zwqy9tKqnr6e2sq2fn6ustci8s7GzrqSnr7TC0dHR1Hd3eHR4h4KYipWJiJCNlqGpubfHvNZ0hCyRrLrC09vU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gu3a9fmAgf+AgIOI+/z4gvyK+/7y9/j5+PP37eje1tnJwMTX7Obd5e/w3dje1tzGzrzEubvCuK6tqLSutqeuq6y1ubetn5aUkZ6eqaqxuLC6v8Xa4+Xh2Ono49zS1t7X0sy50OHk4urp8Nna3N3c2+rtzcDJzeKG//+IgObbgM7Qx8zg5fT8jPv2/vz08fqFiouSnp2ppdD0iYqNnKGfnZqE+O6BjIuTlI6JjYiKhvnz9/v7gv+I7fb+9+TX1eX2+/eB//7+6u/a48zQwcPUt7i4vLzGvsW8srW3s7etqbW4sKynnIqHioWKg4aPlJSdnJ6fn6WqqKeRkYjz/eb0gPH17dvf1ePZ4d7g2+bl/d/W0cnh4trb3+Hc1tu80NXZ3OXq2+js//f39+rZ3+Hc8vOBiIqFhomKioaBgImWmJ+np6ujo5uip6G0udDBxLO+xNPa8YKIiYWPnY2Vgezg5ebSzse4wLKvsK+lnpaalpaZmJeQjob56IDygYT/goeUgHV7fYF6jY6JhImHiomLiISDhImLf4CDhI+Hi4eCjY6NlI2ShYuLhn6AfnqDinp0eXZ1bnlwdnV5dWxtdG97g3z6hoh9hoR/f4h/f4WVh4eGiI2MlKOZlpSVmJmRkoqOio6Wl5eSkJeLnZiQk5Oam5iZnKKipKSksqmosrq+ucnAgLiwqKqnrK+bqqipr6G0t7PFuLPDvKWspqOyu7GptLmwrqC3sq6mq5uUjZqXjJiPkI2NmIuIlZmWpKSqnZaNm66oqZ6jq6eWqbC0pKCepJ+rWVu1r7y3t7Ctp66ro55WWbCpnaeoq56coaSXlpSPkH1zeHPjeYGPjomGgYZ5goSPgIaCi4WNjIuIhIuSnY2Tlo+FkZSem6+gWmNucnFtZ2lgVWR1a2dsW2R4d29lamZnZnl0aHF1gXp1b3aAi4V9e3ZoZLxgYF5bXKmnvbxfYWddXK6urLClmJeloZ6bmJ2YkqOgnqKgpKmnsby9tcC5sbC/tKSdlpKQjI+FgnVwcNtygNPZz2a8X19fXKuutbm/uLS2tbO5saevrLGoqKiwqaebpZuSlJWOkY17eX1/goKNhXl1eXx8e3BzcXFzZ3Fsb3FxdHd4d3d6d3V2fHR3f3t9enB6fXZ2c31xcXN1dnx7gn97c3NxdWtpbmlzdXJ3fX+Cg36DfX1+em+AeXl1gnp2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wczJ1MWwtqattce6zdjUy9LR3d3039fY5+nT3eTe4tzc5trc2dDRw823w8m6tL7Fy8bKx8S+0dTUz+Dd3Ofm49rZ1eDd5/Df9oGHh4KOifDa1unY0Nra+/79g/+AgYaD8ePr2Ly4xLipqZybnpqil5iSlZGNkpGbl5KhpKmApqqkn6Ken5SdoaeyqKaprr/P1drfhKWgrbGwq5mPjY+PpayhlpebmISCg4qTnaiL9urthJOtprG4rrewq7K6lI2J2/D5jZaQj/Xr8fT18PP14+XZ69nX4dTY4N3Y4vHR697d08/W1NPd2dvr+vP0+fz46efS187Su7i6oZublI6A+YKA/vf79ICChvjr9Pf28/f6//r28PDp3N3X1d/Z3N304ufV4+Dg7dTZ2Me/yLrJpaGqo6Kkmp+apKWosqucnpmOnKWQmJqimJmgl6Kkt6uomY+YkZKOk6WysqqaoZ+gopyXoaWikJecoaettMW0xbO2r6einJyPk56gnq+osa6Ao6imppySkZ6mqKaknJyswsTCxruzrbe9r6ecmqOdqqCjl6arpaynpaiqobCvrqSml5uYmaaitKqjp5mempyUnaKZkYuUmYmRnq+nnaWusq+srLKkoay6pbjAx8PG1dPL2rm6urSnqqOnq66traWbmay3s7ytrK2ut8DCvsvTxeWA6/iA+eTt+vb46erp8f/z/oOBgPr3/ob87+3t6eXk5Nrd4tLQ1MRcgLPMw9zg5+3v+/Hf69jWy8DAtK2ytb22n62lsqiuuqqjoqOYp6OlrKmotLq8qqezztvn4Oj77Ofe2r7Jx7jO0+vl5evn4dTf3tzP2c/M1Njh5uPt8YOC9/CA8OfYz/SLl5qTiYmOjJiPlpqfoqW2xLfekKOsm7CxsaWXl4Hs5e77h4SFjIaMhYSIh4eIlIuJg4uEg+j178nP2srP3e7t9e32+e7tgfr7gY6HhYD36d3M0ryrraqkmqGrrr3Hxc/JzcbGwcK4qa2qtbSfnKefrrO5tK2em5aMkpWAlY6Tko6MhoiCgYH8+YKDgoL8ipGQjpCUj4mTjILz7ufp5uTm+O3+gIiHiYiDgf6QioiUiIuDgoqKiIWHjIuPlJimlJaMh4aYm6OZoKGjr7nL2M3i3PCB/YCFgv/y5+XfydXJubq2vLvArKmmpZaWnZKdmpWRkI6KmZqimpaSj5OAenR5e4B+fnx+g4iBf36Ahn6Ehnl5fH5+gpCJgX19hoCFgPH8hHx+eHd7enp5d3Nvdnx5enV4eYCFeHl7eoSJiIeNho+Gf4B8hIF7hYN8fn+BgYiSiY+RloyVlZSTjoiKlJuTmZ6YlaGhl5OZm6eenJuXkpOon6Gro6ysqam1u7mAur64t767wbCeoZGXlp6Soqqpqa2vs6++pKShq66aoKWhpKGgopmVkI2Si5aHkZuRjJqcn5qZlpGKmpmel6qrqLK2sKilm5+VnJ6Sp1leXVhgW6ufn7OnoKeis7SyXLJZWl5cqqmyrZqZo5aHioCBhn6HfXt7goGAgX2Ce3iGjI+AjpGJgoaEh36DhYeOhISJhpeoq7OsY3FqdHd4dWplY2NganBsY2pualxaXGBlbHVnt7e5YWh2bnR6dH16eH2BaWRgmqmpYGZhYKqoqKyflpSUiZSOo5ebnZSZnpyapK6XqaKjnZqfnJmgoqa2w7q7uLy8r7OpqaSpk42Wgn6CgnyA1W9q08W8uFxfZb+3u73Bub3Bw7+0r66ysrG2ta+lo52yp6+iqqKYoZGan5iTnJKcgHqCfnx8dHNsdXRwdXFnbnNtd4FwdHaAdXl9eHx6h4KAdHGBeHdzcXd/fHluc3R1enh2eX18Z2traGxudYR4hnh+fHh3dXltb3p3cn12fnyAdnl8hXpycXqDg4CBfYKLmJuXkYqFg5SclY+KjIqAhHl5bnqDf4SLh46Mg42MhnuAcnh0dH99jYN/gHV3cnJndXZpZ2FqbmJpc39+cnN+hn19gH12cXd/bnh8f35/jIyDi3h7fXx3e291dnh7fXVtbn1/foJxcXBxfIaFgImHdJiAoLdevK61xcDKub2/vcjCwWVmZ8zIzW3Q0MvV29fX0MK/zL6+yLl8g6O3ssbDycq9xLeXp5eWjYmKhH2Cg42Ebnhsc2pwenJxdXp1f3x7g4GDipGTgn+Hm56jnaKsoqemq6GqqJqkm6qgoK2np6axur2svbampaelraiqq19fubyAwcGuoMFudXVraGlzdHl0c3d+fX2KloukZXBzZHZ5fHZwcGK7t7nIbGprb2lsZ2ZqZmhnb2dnYmxpasHKx6msrqOhqLW5ubbDysHKbdXUa3FsZ2G+u7OpqZqNjo6OgYeTkZ+no6SXnpuamZyXjJGQmJyPhYZ9e35/e31yeHhy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naCZlpWknri8vravp6e3v6ypn6KXnaibo6y6ta+uqba3r6+yu767nJ+ZjZahrK2qs6GcsLKzrqSropWYm6KmubCyoqKpqp+dopiVmZydrKO1s8PAyN/Uzs3Gy8PDsbSyuKuqpauurZqhs6q8prjAv8K6xL7F1tHkgPju6OXZ18/k39jd2eTg3ub49e32+unm4M/V2dXY4N3h383Ktr7QYPPJ0tfW3tvl1OrbzeDaztbO6tG/xsHHwb/Ft7GioK2qsrehm7eqs7q4sLqsv6u3urjO1eDY4tjg1c62wbjByN7d8ur17+nW1dW8w8jL19Th8/WDi4iFiPn0gPDo4+WDi5ebnZOWn52lqLSpvdTf8Ons84SZmp6in5aVgN/W0tvk18PByMrW18jO0eD1iIqHgImIjo6J++vQz8rF09Pbyt/jgejp5uLb1+Dj2Ojo8Obm3tjTzM7Fva+gn6m3uMW7vcPO0Mzd6NXQxdbMyMfP0+TdzMK/tKiinJaVgJSYm6Ccl6OfmJOTjoyXopSSl5qbmJ+WnpGWjI2Bg4OGi4+TlJ+cnqOpr6Szqp2VkZWHnKGjqJ6jtJyZp7iopaKfnZmWiP+IkIiFjpCPl5+drbzDvMfw7/Hp8vnv89ni2s/Hyse+tLm8uLOlsKacobWqqqSnlo2Oi42EjY2Qg/6GgHFvcW59enp3eHt8f3p9e4CEgIN5enjy6nqAen56e4CDgoOIhYGBe3l2goaFhoB5d4F2fXl4cGt1bXB0e3p6g4J9gX6CgYR9gYaHh4yLdm95e4KFk4iHjZaEkJeYl5+Yl6yun6qppqydoaedoqadm5mLl56krrCzr7e1tLa+yMeogLWsq7CpoZial6Ccq6ijqba2sJ6luLy1t6WcmYyUkH+Fjo+Fg4eGkIiLk4yTmpCNkYqKm5WZm5J/lJCNlI2moJ6XnKynoqOmpZuak5ytratbWlxdtba7s6ShoZqQpaSxpaeys6Wfn5yLkImCgYHt8Ht9gXx2fHd2e318hnd3g4iOgI6Fin15d4mNjIF9i42Hh4mKmJ2lp66zXF1ZXmhmcH1tZGdlW2NtamlmWrCiY3V1aFuutr5jaGltaV5jcYR7a2x0dGZZX1tZYWJeXaiopaSlopiVrKOqobWnqKWknKCSoJmLl5ablpeUlpyQmaCgnK+ws621sKupnJGclJOMhYODgHBz083DubTDYmS3r7bDwcG+ZGfJwMK/tqyxuLStpa+or6+zpqeun1eqoZ2dl6GeraWssY+amoaAgYh6enZ8eHRtdW10eIB7enaGfoN/eHJ0gX51fn5/fW5samt0dHZzcnFvcHJocmdtbW92hoWAgY+DgoaFeHRzcn57g358fXKHgHV6eXt0dH18fH96fH6KfY2QkoaCfn2QloeLj5iOkZWGiIeSkIyOjJaYjoWFi4iGcXV0bHN/hoiFjXx3h4SJfnh4a19fYmlvgnqCeXZ9gnx7hH14gn98gnd9dXx7gpKGh4F+iIF/e4J+in57eXt8fG50gHiAbXt/gIOAhH6Ch32TgKinq7CtrKW2saqrrbaytr7Iw8DAxLu6vLe/xsrN08vMy8G/r7O8buWzusvLzMzQs8G1oLGtpKmgu5+LjoiPiIWKfHVqanR3fodzdYuAh4yNjpaPpJGXmI6dnKehp6CwraycoJuhnq6isai2uby7wsq5vsDIx73I0NNucGhkZb2/gMnKy8ZvdX6BgXx+hYOFiIuAjJWZpJ+os19rZWZubmtrXaKboaqzsKGfqa+3urSxsrnCaWpnYWhrcXNxzcGurKmsubbDr7G2ZbCwubi1tLm2qrSyvbi3s6yqo6KclouCh4+YmpyWk5Sfn5yuuaamn6impJ6enqCXioKDgX19fHt9gH19fIB9eYWFgoKEf4GLl4+PjI+JgYV8f3V9dnVtb2tqbXBydHp1d3l3e3WBfnh1dHhugIWIin+AjH17hpGCfXp1dXN1b91yeXNwe4GFk5WRlaKnoa3FwsXCydPKzL/FwruztrKomJudkoyCiYN9gI6Lj4qYiYSGf4B0eHh5bdRvgC4qKyg0MjItLzE0NDA0MzY4NjkwMC5dTi4yLDAuMDQ2MzQ6Nzc4NTMzPDw6PTg0OkM8RUA9NTA7NDk+QT04PT03Ozo+P0A4OTw8PUVGNC44Oj08Rjg3PEQ1QEZGQkc9OUdJPEJAOz0vMjo2PkhBQkA1PEJGT1BRTlJOS0pNU1NAgEpGSE1KRDs7ODw5QT88P0VHRDU6SktGRjs1NC84Oi84QkA2MzU1PTc4PTU5QDk3OjIwOzQ3OTMkNzMwMi09ODUxNEA5MzQ1My4tKC86PDkfHB0cMTM7NjAwMCskMCovIyMqLSYkJiYaIiEeISM8QSUpMC0pLysoLjEvNyspMDQ0gDErMCkqKDk+PDArNjYvMC8sMS8tMDQ4HhwXGhsYHSIcFRcUDhMYFxcWER8WFR8fGxYpLzMbHRsaFw8QGCEcExMaHRcRGBcXHBwaGSgqLCwvLSUhMCouKDkvMTEwKiwhLCgeKSsuKyooKi4kKS4sJzIxMS0yLzAxLSw9PkI/Ozw+gC80Xl9aW1tzPEBwZWlzb29uPUOGfIB6bWJmamtmXWdaX2BjXWZyaT98e319eH13gHN6hWV2emVdYWVZXlxfX1tUWVFXWWFZVkxZUVNPTElMWVhNUlBUVUtOTlBXWFZPS0dDRUY+SENMUFZda2pjXWtiY2duZGdpbXRtc21nZF1ugF1mZGRiZGtqaWxkYV9pWWlubWVkYF9tcV5eXWFaYGdcYGBpZFpXU1pZU05UY2dpVVdUS1BYXV9ZYFFKWFheWFZbVExPVFxgdW5yZF9jYldRV1FOW11hbWNqZGhmantxcnFueXR1b3ZyfnBqY2ZobF9jcmp0YG1ydXd0e3Z8g3mAgIuAeHl1b2+GgXt7dXlvam5+fn6Gj4eIhX+FiIeHhYGDhHx+cn2PirZuZWxqaXF5an11Ynd1a3NuiXFnbGZrZF5jVU9DRFJZYmxaW29jZmVkY2dfdF5nZ19vcHhyeHGAenhrcWtvandsd3B/gYN5eXhncXN7fG91dHA6Pjg1OWRmgGxucmg/QkVGQTw9Qj9DR0lAR0tKTEVCRCIlIB4fHBkcFyUjKzU5NSolKyoxLyklJCsyHh8eGx8hJCUkPjgsLCsrMzE5LTQ5JTo6Pjw5Njk5Mj09Qz49PDc3NTY2NCojJSoxMDQuLDI7PT5KTkA+Mzs4NjI1Nzw6My4xMC0sLS0wgDM2ODg0LTUyLSwtKSkzOzQ1NTk2MTYuMSszLjEpLCwsMDI0MzozMzQ1OTU/OTEtKi0kNDc7PTQ3RDc2Qko/PTk1MzExKU0rMSklLS8tMS4nJy8uJS0/Ozo1NTw1Ni42NDIuNDQvKTE4NzYxPDYwMj43OjhEOzk9OTsyODg5LlYvlnuCev97uHuEfJp7gnqoe5N8gnuFfIN7l3y3e4Z6gnuHeoJ7lHoBe/96/3oBfNJ6hXuGepR7iXyRe4l8jHsBfP17AXq4ewJ6ewICBACAhYiOkJGWj4ySooyLlZKXj5SYoKSZk6OdmpeWnJiZmpSUm5eal5eVoJ2copKbj5GPoqClop2ZlIuIjY2Oh4+Vl5SfqKObm56gm52mrratr7u7sLKrrKmmsrGmxrm+yNLH1dHY4trl297WxcTV3O3j3dzW39fC0bSqtrm1v8O4uMWAsK63r7KrpqjEwMLEy8zd1cO6uKenvq+un7CxtcHJvbO9sqyoqKylq6+1t7y8xMW1u7fczM7My8HHwse5uMXGyb/S2+ja4t/o2eTh2eHm9/jq6vr74dbW1dPv6dDJwdPEuKu2saWnk4j9goWHhY+Ph4WRkZmUkY+WlpaboKOzwbSAoJeWm5GSm6WhnKelq67AzuHx19Hn6O7p9Ofx8oWGj5aMpoqBjpL/kIj88ouHkpKG8dbTyNXt9/eEjqetzbCuq6Wdlo+E+f7r5/n+7/n+gO/tguvq9eDS0tTg29Xi9Obc7ObZ2dLK1eHg49ja0NjN1+Dk4tDg+PPs/OnazLquo6OAk46H/vyB9PqDh/by9/+FhoWI/Onp4uDZ5Nvd3/T0+4Xu6fHe6N3p7OPm5u3h/ID9++bv2NLm0dfX4tbcu7+6o66dkpGVj4+VnqKcqKWhm5yknp+aoZyhqKOlqqmmp6ipoKOlmaajnZ2nu6y4sbu2ubK1sJygnZOZl5acoaSmsJ6ApqansKyXqKegnqGrop+nrbqyqZytqba2tKqhqa2iobWtoa+7raefoKOwq7e8t6qkoJuco6axstazrKqrpKqur62xr6Weq7mwurWqpaafn6KopK6rsrW8xMfI3dXZ5dzi4NHOye7EvMnAs7O6sq+wqayytq+wtsHCx7/HzNHNwd+A5Ojh1tHd3Nra397e0uDOz9fk1+LZ39jR0cC7u8zR0svRzcGqqcFdtL65vL7Bzc3Z2sTNwbW3u660tLW9vsPLvsXDx8C5vMK6srCYrKqlqKvBwrnKx8K91eHp3OLf3dLUy8XE1NLg3u/g6dvg0cvN3+Ht4Nvd3uHu6env6ev1hIOAg4mKjZOUmZ2rrq+irKW2wcbK2Ovq3MXV84eRk4+PkY+F7vD18O3ovbinmZaWm6Czr7rI0tHP1s7Qz9Pb6NjR3MnBxcbP5uz57OfZ3tHY0NfS2s7VztLUx8rHvcPFsqCes8aztrnDwcO4ucTBtsDBxMHFw9fO393Z3N3j0snDurWAq7m/wry/ureqqqSko6elna+prJmktbG0qLK3sqWppqWiraynqqmqraikpKCinI+Sk5qpo7jBsaicmp6dqLe5tLi/va+pqa6srZ+UlpujqbLQy9rw9/mA+Pf79ImM4fLY4eTO0NfFv7G7srOuqamrrqOjl5+dnZKXlY2Zk4OIhoWAdXV2d3l8dnh9jX2AhIGGfn+ChYp+fImEhoB6fnV4fH2DjIyNhIB5gH2AhnyFfH10fXd8enl8e3l0d3V2cnl8fXqAiYN5fH18dnmGiI+HgoeHfYGBhYOCjYh/mI2QmZ+UnZueray5ucC7qqWtrbGjmZaXoZ+bqZqaqKussrOppKqAm5eel5uUjIylnJqWmZSinpGMjIKMn5aVhI+Jho2PhXmBfnt5foZ7foGDg4WEiYp+fXqTio+Nk4+VkpSLiJWboJepsbiqpZqgi5eZlaKkra2hore7rq2opaG0qZePkKSYlY+RjoiJg3/2fn9+eH6EfHOBfHt2dHJ1d3h6fYCLm5WAh4OEiH9/h4+JhY2JjoyUmqq0npyrsq+ipJOZm1ZbXWNfb15WX2SrYF2ooF1bYWNeq6KgmKCxt7JeZHR1jHd6fHhzb2letresq7m8sbKyV6WhWqKlsKCVlpiioJmerKKTn5uRj4iCipOUm5acmqOeprKxr6GuvLy0v6+tn5KJhYaAe3dvy8dhs7BdYbKusbxiYmRoxsC/u7y4vrG1sr68xmi8s7mqr6i2tq6rpKiarFqup5yonp+wop+Xp5KTcnp9c4iCe3V9c3R9ho2Gkod8dHZ8dXp4e3d5fXRwcnZ0dHZ3b3R4bnx5c3Z7hnyHgY2LjYeHgHV3dG90cXl7e4CCi36Afnl+g35ygIN+e4CHf3t8f4qFdnF+d4eHhYCDi42Ef46EeoeWj4yEgYOFe39/enJ4dnR5f4CFiKeHgH57cnZ1enZ3cm1lcX13gIB7eHd3e4KIhoiGh4WFiYaGko+Ql4+XjIWDf5mEgI2GfX6FfHRxbG93eXFxdH59g32Bh4iEeZSAnKejo6SsrailqKuyq8C0t7bBsbGorqqsubOyucfIv7e6vLCiobtsuLu2vcG9xMHFwa21pJ+goJOUlZSamZ6jmJ2Uko2EhI6Jg4h8iYmJjpKppZ6ppZ2Xo62yoaikpKCno56cpqStqrOqtq28t7vD0s7SxMjJyc3Xzc3OwMLGZmeAZm5xc3d5e36JjpKLkoeTkZCRmKaooJGeq11dYGJna2lgqq2zsLO4m5eSjIeQ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p7KpqLG2prCvpammp6mlqbKnsLvDtrO2s6ixpZiSl6KdraG1paGAoKKlqqCirLWvqr2lo6SmsrGtqaSoqbC3sbifmqKsrrGqrbGwpZqXkpyktMSzwcWwq6Oorr+wu6WztaKjoqOopaagq6urtMO4wL+tq766sb3Avq22urrCv8bA0c7X3eHR3M3KzcbAvr22rLKzxsnEvLGztqy6tcnKvrrCzcS8w9mA4OHVvMLNwMvN1NziydrJzuLY1t3Z0tDUwL2xqLe3sry7uLqwpbSqoquwnp6lnaunpKuvt7avqru4oqGorqaps8DEv8fOxs27ua6osLC/v8PBwsjEx7y7wsvF19XK3tnW1uvg3efg5/n55+7339fD2NrTzsO6wNTRyt7p4fr9gISAh5WjnJedr6K7s7K7zcLBw8rBy+DtgIbx0euPjY6PlZWTkZGNkYyH8eXU4Mmjnp+kqrO2ubG2pKGpp7Kurrenqqe3wtDR1d7j5uLcz9bR09zr7+jp2u/v4drf1tPKw7q9s7u5xsu/wb/BtsPGxr26ycjN0OHP3OfY1dLQvre7q6yArrO/ta+zqaqnqKWZmKWdmJiZnZyhq7DDyMzP4NzJtMK0trfDtMHAyMe6sLa9trbD0cS3sb7V1cnMysLEz8zQxMHUy8XMw7/Ex7y9t7K+yNPs5uPy7IL6+YGKjIL/9fbt693UydDKvb7Ewreyrbiwt7SrmZ+hlpOZoJOdnJmWjP+AdXV6d3Jydn2Bg4B/goKGhISNhoh/gYCAhnl8fXl1eYF7f3+AhId7fn2GhIaAgnt/d3h2fHp3eHlycXZxdXl8enpxe3l+gHl8f3Rxg4B+eXh6fH+Eg4iLjYuEeYl/goKGhXd7j5Wel56ao5Oek5aUjo+TlJiXmpmaqqiio6acq6GAraWwr5uhmJCelpSXk5eNioKGfXuMh4+GhoODh4KKhouJhYiGgX18f3yIh4aFgol/hYKEeoGAhoCChYJ/eoWVk6WpsK2fmIWWn6qjoJ2iopmgpqu2pbGmj4GLi46VlJ6en6CNhISKipSPjIaGgHrye3h/enZ3dnd0fIGJn46SlIeAiIuRk5CElI2MhoeNk5SaoaKjm6WdrpaTioyXU1VXZ1peYl1oZmlsYVZkYmBWV1eutaignZqdraiiumJ8fHNwa2dgXl6xt2C2qq2spp+ZlpuaiouTjIuLj5mOq5aNk5iXjot7hYiDgIiOlp6qqa6fp6OzsK6yv8rByL2xpaWIiYGAfnt7dXhxxcG+v7iirK63W11fvb+7tr+2urO5trmswsC8ur29rK+tsLKyn5+sqa5Yp6KapqCnnq6bpqyznYWUhJGOenp/eXF5hIqGfoB8fn1+fICFfYOAdnVscn94d4OKfYqLgoeCfn92d4B3fIqVioiKhoCJfHVucYJ+hH+RhIGAfnh9gXR3e4Z+eYqBgoJ/hIB/d3h7fIKJhYuChIySkIl9gYWGg36DfYWBhIt5gIN/eXN4fYd8h3OFjHp4dHh5gYJ6goB/gYiBhYh8fYqKhpOUl4aMj46OiYx/iImPkJKGjIGFi4uQlZaPgoF+hX9+d3Bzc215cYSFfHyDj4V7f5GAm6aglZ+mm5iam6Cwob2zvcm+ta6nl5qmn6alqbayqaukoZ6YkKfop7K4s7K4rr6vq7KtramflqejlJacoZqTm6Kbi5OTh5aPkpCWl5GZnpqdnq2prJycnKSkq6igrKWin6efmqilrLW3rrjQy8W8zs3Hw7yusMC0rb3EuM/Mam2AcX2GgXyCj4OZlZGXpZSPio6Jj56pWl6pjqBhY2ZpaWlmZGdkaGRkuK6qtKiTjo+VlZSTk5CVkJGZm6SVk52NlJSdpaulnqSmq6usp66trLG7u7O2qrq+sqqto5+gnZackZOSmJmMjouNho6Xm5SVoJ6gnaWVnJyRk4+TjoyQg4WAhoWPioaOh4aFiol+gY6LhYOCiICBiIiPlJaap6SXipSOjpCdlJydoZePiIqRjpCdp6OXkJ+zraeoo5mXnZibkZGjoZyjmZOZmpWfnJympK24sK64uGbIy2lwbmO+srWytq+vqa6mlpaVmI6KiZmJkpKKe4KFfHx6fXN3dnVyc9aALiswKygqLTM3OjU1Nzc8OjpBOjwwMjAwNy0xMzMwMjczNTY3PD4zNjc8Nzg0NjM+OTs8Pzo4OTk1OD04OTk6ODcvNzY7PDg+QjYxQD87ODo7PTo6NTc4OTs3Lj8zNzY5NywvPkFDNzw3PzU/PUJCP0BCQERAQj49R0VAQEE4Qj6ASEVPUEFEPDE6MzEyMjkyMi0vKCU0MzgyMS4tLyw1NDw8ODo3MSwuMjA8Ojk2MzsyOTQ3KzAxODQ2NzIsJSw1Mjo7QUE7NScyNTs1NTU6OTEwMjQ3MUA3Jx4kJysrKCwpKyodGhshIiwpKiouLjBeMS83Mi4xLy8qMC8zQTI0NCiAJysvMzInODU1LzI2NzU3Ozc2LTMuOS0tJigtGhoZIBcWFRAWFhgbFxEaFxYQEhQvPTw7Ozc3PTAoMxkjHxwbGhcVFxkwOiE8MzY1My8tKy8vJCYuKykoKzIqOSwkJykpJSUdJislIycqLzU5NzgqLSgwLCsrNDs3Pz8+QUc6Pz6APjw8OT49amxzenhpa2xxNjc4b3JsaHFka2Roa2tbbmdfXmRoYGtucHByYGJydH1CeHJtdXF3bHpsdICNd2NwY2pkUlJUTkZMU1pZU1hXXFlbWVpcVFlYUlFLUV5ZWF1fUFhZUVROUVNSWmJaYm1xZmBkY2BtamViZHBsdGl2amiAZWFkaGJnaXVsZ3ZnZGNeZGNeWFhcX2JpZGlbWmBlZmFbYGVlXlZYUVhZYm9lb3RqZFpdYGZWYEpbY1FQUFhdY2NdZmViZ29qcHFiX2llW2JhYlNfZmpva25jZmRnaXFqdGpwdXN5fHx5cHFtd3R1c25zc21zaHl3bWxvfXVvdH6Af4N4bXd8c3h6fYCHdIh6e4qBfH55cXeCdXRsaHJqYmhpam1qZXr0a2hmWlRXUmJYVl9ZXmVdWG5sWVpdX1ZSWmBdVmJoZHNoaWFdW1xgZW11c4B+emxoaHBxfHlue3NxcYB6eIN3dn16anWJf3dncm9tamVcYW9hV2RrXm9uODkcO0RMRUBBSz5MR0JGT0NBP0I8PUhOKixKNj0lIoQfgB4dICAmJidFPDY5MSEfICcpKikqJysoKjI0PDMyOywvLTU7PjozNTY5ODcxNjMyNz5CPD80P0E4MzYzNDQzMTUtMC0zNCswMDMvNzw8My83Njg0PjI3OjIzMjc0MzsxNDU2PTgwNCwpKCkpHyMwLCgoKTAqLDIwNTc2N0JCOjQ+YDk4OD83OztAOzczNzw3NT5EPjQvOkhDQEE/OjtBP0Q7PEhHQEY/PUJFQUQ+ODk0OkI7Nz48Ij89ICQlHzw6PDo9NTQxNTQtMztBOzc1QTY+PjsxPEI9PDtBNjo7OTUyUv9773sBeqx7lHyLe4p8A3t7fL97iXqDe516AXv/evd6AXvXepd7BXx8e3t7jXz/e697A3x7e4R8o3sBegICBACAn6SXmpOeoJ2gl5OWl5mXlJmVl5KRj5CLj5SSiZGNi4eJgpCQlZWQjpWeoZudj5yfqbOuq7ClmpiB9YqYm46BhYWPlZakq6ylpqqpo6iwqa2kr6G0pqW0rK6vnZ6SnaKut626v8DKu7m+vLzCyNDH1c++1cfLx7XDuL62tcGvubWAt7G8uqy2uKitt7y3t7nEurTBycC6u7yyuLW718S9sL/EvNHLy9HV0+DbycPAu8GzuLvCv7i5vL65u9C8ytXIw7eltay9utjN4enV1cvJzMy6sqimrZytpq2vwLjEwr65oZecoquenJmUjoiLhIWHgvqCg4uOkpCcjI6inZGflJ2Al4+Vi5CYn6ytqLnCyMS20Nvk6e357PD76/D78oCK7f6FnqSrpLOUhJWxrqmjjfHm4eLj5MnE07nG6pKMh4eAhIDp3t3kysSztqimnKGXqqyqs7XAsKOot8O/xcrq1tfTzs7JysDg2cvW4e/w9fro5Obq0uzc4d3GvLq4sbKzuK+Am6CXmpWNhoiGhYfz8oH+gIuG9ujc3NTc3N3a4+bf6eb1/PTq9u/75/Dk3N3W4OXz/PHy8u7x2uTY0NPH0My7v7K5s8TEuLSqnKysrMDDzr+3o6Wkm6GmoKiZpqimoqyrq6Glm6WnmJulr6anrrSqrqGlrKSfpJ+boZ2wr6qxqq2ApKeapJ+Wn6Cirbi/r6SjrK6rpZqYorCgs7Wtqqewq52lpp6apZmVmaa6uLmxsrGhpqqto6umq7KllZSWkJOVpKOpqqWut8TMvMjCtr3BvL3Cuqitrb3H0dPN3MnT0dTQxcG2ucK0urGvqrW6wbi/tqWuqLW8s8O/raihpLmxssKAzsm9trHHsbjBx8zOzLzGysK/xcvTzsq8t6Wuo6Snt62zraGym5ORr62pq5mMlpGWlpScqKGusrW2tr24t7nK0MzHx8LL0tDJy8bJxMnOw9HPzNDVvbq7xcPIy83U3tPDxcDMz9XP2+vg0tTY1cvNzMvMzMbQ0ufy4uLx8f+A/PyAiZOQi42Wmpums8C4srXBvc3U09Hlz8S+1e2IjpulpaCprpmWivrw7/r9hYPyxrGsoKGgn6ialKGoqq+7wKycjYuUo7zN6ezk3Nzc1tTR2tvd5+7t5d/o6emB9fv09e774N3i1MbK1NXEx7y3tbyzurizv8fI0N3h1uLs59vUy8yAxb/EwsbOz8C0t7SjnJ+hoZyioaaqrqmss7O6raGgobCdoZaZm6OcpJWYmpiapKKqpamyr7CrsLTM3/fr9+nv58zs7oDr7ePi0Li00snU79ng8O/q7ODp0Mfn4d7Zx9Lh0MTMzsjCwbrCw8bKwru8vbLDuq2qnZybpqKhmZujoZcHho6DhX+GiYSHgIuMjomCh4GDg4CAgHZ5e3VyenV1d3dufX18fnl1e4KGf35xeXZ5f3p9hoN/g3DTdX1/cGJmZ25wbnd6eHZ1eXp0foR7f3d/e4mDhY+KiIV1enF4e4KFeICEiJOQjpibnJ+hnZSak4eekZqckKCcoZyap5OblpqTnZuNlZWEiIyMgIeKiZGMhpCRioODhYCCf4SXjYd9hoqIk4+Qk5ORmpWMj46NmomLioqEenp9gICBk4GImpOWlYmZi5KDkYaUnJOXlJGTlIaJiYuPf4mHi4eVi5GUlJaGgHyDh4CJiImIgYJ9fIOA7X97eXyDfod8fo6Ie4aAioN/hXl+hYWNiYKLgJGXlIaanpudpraurrKinqWWUFaLmFNlZ29pc1tOXXJvbWpdo6KeoaSkko2jkJWta2JdW1RcWaWipbCinpSYjZKIh32JiISOjZeHenyHkoySmrCfnpeNjomFfJiUiZKcn6Chqp+hqrOct6uws6Sgnp2SkI6TiXZ9dXt3cm5uaWtrgLm5YLRYYFywra2ytLmztrO2tKepqbzCt66zpLOlraqlop6hoaasoKOmqbCfqqOdoJWgoZidmaanrrekmpOGjpCGi4aJf4B2goiAhoR5f3J8f315hYKGe4WAjJCBgISHfXp/iYWIhIOKhX6BfHV4fJCJiI2GjH+AeIJ/fYCGhYqSgJGEf3d8gXx0bWlue22CiIeOj5SPfX2BfHiHfXt+hI+FgHh8fHl/goJ4e3uDjYuFhYJ6e3uIhIWGg4iLj46EjImBhYuPlJqci4qIlJKVjoaNg4uMkJaKiIKCjoqTjY2EhoWGdn14bXdvdnlwfnxzdG50gHZ2g5CYkZicrp2cm5iXgJ6jna2ysK60sLaoqaGhmKaip6u1pqmll6iTjqm2sq27sqaurLKxr7S3rLSuqqelp6Olo6+1rqCdmZydnpqYmqOcnqWfqLCwur6up6enoKejoqizpJuak56ao5yntrGtssXHvMXEv8LAt8DA0dLCxM7N22zW0nOAgXp5gYJ9gYiSgI+K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y7jAvMPCzMXO1dDawMDJuL28ucG2pa22pqWnn56jmpufp6qeoamenayquaalp6OrqayvobGjo6adpqKupICXnqOinZajo6qqs6ywqq+qrrOnqKSzrq2uubSjsamkrKimqaSvqaylubWuu7OwqqKcnZ6enp2iqJ+QnJeOoJeZoqKZj6+msrazqqurr723ucq9s7i1uLu+1MHPw8nPw8bO1cTK1L6+sra1w77Ivrm7uq+3uLOutrmysKiosrOyv4DHxbeppK2suMLJz9bTv7q4tbO7yL7Lz8S8tbKqqaCup6arm6OUmJiUo6KVm6CfnqGgpKWjrLfIx9TAwb++tMTEzL/Ix9LWzNfb18vS2szF1MLd1dDQxcO8u8DIzsTEuL/CycPLyc3T2NHK1dfH4s3e7+705vfq8YWHhYKKkpqRl4ChpKKcjYycoqCklp2jqaq0vcnSzcS4vMbq/ZSKhJmbn7GvqZuWguDq6/j/he7nv62eoZ+bmJ2Qm6Grr7Ovo6Gbk5aUs7e8rrq9ury6vcnW4tnc6O7m4Obe6uvn5uPdyL+7tqOoq7u+x83MxLu0sbG3r8C8wcS6w8y/xL/BuLm4t4C7vLy0sr2/vr/EwcTDxsi/uLGmpamjpq2qqKKflpOTlJKLj4+DkpKMk5WZlpeTmqSYlpyakoianqPCycDKvMm4sbStzrjHzb/IusbMzMHb1Nzf4d/bzNLKwNjU6PPa1OzV1cy/tbe1r7O0zdvcwdHGusK5r66zsq6srJ+lqKWjooCLkn+Hg4SOhIN3fYiHgIl5f357fnl4eHdzfHl5dHiBc3d3hoiAhoyHjI+MiHN4fIaKfHqEhoR9g396g4KIeHqCeH+Ggn11g357bmxub3RzfnqHeXl/jJaDhJuKhIV7fYd8f3N+fIZ6goeRjomQjJCKi5mZlZmbnpqUlI+ZlZSJgoCHjouDj5CJjYiTlI6IjpCOmoqJoIqIjn96gYmMipmNk6GdnKacrqmnoJiNmZqanZull6Kck4yCk5GWmJCSqZWijod7fX2Aj5GIko2TgYeBloZ/b3V8i4SQmYqQjIyUjYqRlZCNi5CEgoWDhYiHg3vufXp4eoiIg5eUioyHe4eOkICJhYmQi46NjIyHi5GVpJypp1xbop+eqLSoV2NWj5eRjJaVTl1hYFZZYmdgaVtXoZmljZCWk5mZlIuUqrKfnKGqqK+ak56gn6Wij5uJgYSBjIF7c3t4e3p3dHyKk6SfraStr6KPlJ6VkZCQlZObV5yZnqqtsLS6paeXmJ+XkIWRioCDgYR7dHRsbWp1bnFry8HCvLu3s7i0wMGysa61YKqxtbqqraStpqumqKOxsq+knp2jp6Gloaeju7G3rpylpqifpbOrwLi+tbixs6appJuhh4aRiImOjpGDd4GGfH2AfX2DdnyBi42DiIqEgIuJlYiEiIaIiYmIfYd4e4FzgHiGgYB0eoB+eHZ9gYyKkYSFgoJ4e3hvc3B8eXx+io+FkoqGiH5/gX+Qi4uCjYaAjIWDgoN/foB5e3+FiIV/iYF6jH+Ciod/eZaKj46LfHh4dYF+gpKQhoiDhoSBjX2FfYOHeYGFh4OGlIaIfIB8hHyBeHR1dm91eHV0e3x5eHFzenp4hICSmo6PjpGRk5SPk52hoKCgpKOlp5WYnZuXmKCjp6Kpop6klqCTnZyhq6ufqbS0ucbMys3JxMXQwsqys66yq7e3vKajnp+clqSoqqqusqeir6bCvrm5qaukoKajpZqajpGSk4+Qk5mip6eps8G4y8HP3dji2eXY2nN1cmtwdXZrc4B9hYaAcXF6f3p9cXd9g4KGio2RioeDjZestGRdWWhkaHR0c2hpXqmysrrBaL/Ao5aLh4iGhYmBh4eKi42LhoiGgoaAkpCOfIiMjJSYm6Sqr6Okq7eyqa+lqKeimp+di42NjoKHjJSRk5mbko+Mio6QhZCNjY6MkJeTkYqQhYiHjICVmp6cm5+hm5uenJ+eoKGel5STjYyJi4mIioWDfn99fYF4dndtcnJucnd3e3t3fIJ0dXt7eHODiIaYnZCWj5aIh4qMopKXm42VjJqeoJmmnaGdo6CcmaGemq+rtbejmbGkp6SXjY6NhoSDl56hkpqYkpGLgX6DhH59f3J6fn+DhYA9QzA0MDM8MjQqMT4+OEE0OTg0NTExMS4pNDQzMTQ7LjEvPDs1Nzs2Oj06OSYpMD9GODpERUU9Pzo2PTxCNztDOTw/OjcxQUFBNC8uLC8vOTZENTQ3QUUzNEo8OTs1O0U9QjQ/Oz8wODtBQDhAQUQ+P0lHQUNEREI8PDxEQ0I3MYA2OzcwOTkzNi86PDcyODo4QTY0RTY3PDIuNTw7N0E2OkZBPkM8RUNFQTs1QEFBRENMQUlEPTkxPT5AQDs6SjtENjIsLy8vNTMrNTM7LzQuOzAsISQnMiYuNSYrKCwzLisyMi0tKi4oKSwvMzg6OzVhNTIvLjc0Ljw5LzMuJC0xNYAwLTA6NjY2NDIrLjIyOTI6NiMgNC0qMzkxGyIbKDAvKi8rExobGhUWGx0XHBUVJyk5LTU8Nzs5MSkvPEAtJyktLTEkIzA0Nzw7KzcnICQlMCgjHCIhJiUiHyUtLzYxOC81OC8mLDUxLi8uMzE1HzEvLjU3Nzo8LC8lKTQ0NTFAPoA6Oj87OTw2NzQ/O0E9cGhnY2JhXl1aZGNcYmVvO11fX19WWlJgXWZgYFtlaGtoaG5xdm1pYmJdcm1xa2Ntb25kYGlhc2dtZWtocGpydG94YWBqXVtZV1tNRFBYT1NbVFNXSUtMUE5ITFNSVGRpeGdlaGdoZmhrYG1fYGhgbGJxaoBcYWNhX1thZGpnb2RoZmZbX15XWVRfW11caGthbWdkZF5eXlxsZmVbZl9aZWRjZGZhYWJdWVhcX1tWYFtVaWBjaGJbUWtfaGpoX1tZVmFcW2hkWWBgZGRneGZtY2VnX2drcGxxfnZ5bnNxeHB2a2pucWpucGpmbm9saV9kamlmbIBxcWZpaXBzd3l2dHZ2b2xqbWt0gG53gHx4cXJwbmVsZGNrX2heYV5dYVxQV11eZGtubWtmaW96dYBoa2RhWGJfYlVYWF1eV2BgX1pfaFxcb2qGgHx5a2piYmpudGppXWFla2NmaGtrbGVfaWxhcWBrc252bHpwdEBCPzg/Q0U4OoBBRkdCNTI6Pzk+NT5CQj09PkNGREI7PkFNTiwmISglJCgmJh8gHjY/OTk5HzM4LCkmJyoqKi4nMDI5Oj4+OTo4MjYyQUE/MTg5NTYyMTU4OzU3PUQ/OToyNzY2Mzc5MDAxNCcsMTo4Oj0+OjYxLi0tJDEvMjQvMzozMzI4NDg5O4BBQkI8ODo5NTMyMC8rLS8tKioqJyklJiQkJiQnJSgqLTAoJSYbISAcIicqLCsmKy4gICUkIRwlKCo4OjM3MjwyMjY3SjtBRjxDPERFRThAOTs5PDo4NDw4MT88REU6NUpBRUI6MjI0MTE0SE9SQ0pIREdGQkFKTEZEQjU8Pz09PP978HsBeqF7gnyGe4N8hnuMfL97AXyfe496AXv/ev963Hqje4x8hXsBfP971XsCAgQAgJ2Xj5iLjI+Om56QkpSNkI+WlJaOk42omZSgoJmooqKbpJitsqysoKq4sMm2ubOkxMfCq6OiprK1rJ2fuLint7W1tLOzurSgvsCzsL2qp62rp7asr6WiprGlrrC/qJqprqqknZuVmZWWko+Oj5eeopqytq6qubrGvceywbi1u7q3gLO5urGqtKynqbG1squzwsXPyMjJytXK18/O3uLo8Nji3M/U2t3Z0uDV0NDS2+rV9Nvk4Oz7gv/yzsrczdLJwsrDweLY1NPT1tLc0uzNy8zZ5b7NzsS5wbrPxMi9urvGwb/AuLeztqyhl5CRjY2LgIiJkZabpa+vr62lqaaco5yegJidqKmppai1usLBydni1PDf5ebs3vyIivKMjYTw5dTV29zn7Ofe19na6Ov21Na1x8a6wb25wLSmwMLLz+HT3vrl0c/Sy9DH0NTN2Luvqquwu7ess6+zqrKnpqako7fBvLrMucLL0sfV0uDc1tHfz8fV0cvJ0NTu7+7o5NDU3L+8gMGtoqWnnJqcnIyLg4eOhoaD8drg2ePh397f1eLw9Pv+gO3o6Obl8drp6dvk4ezt1O3m3ubn4+Ph8+3u6evh5eTc0NDWxbu5uMfOz9PW2eDY3+XOwMqwsq63v7CurqSsqKKnoJKXnZ6knpmfq6amoayor6itrKimrKitoae7ysKxgLCqrKiaoKSmqrG0t6yjrrq+wrq2oqqcoqGxuaizq6ecn6anm5+Ym5mUn6WtoKKWlpSYoZqhkp+moIuLm4eIj4yToJaVj6KrtKKhnKersbqysK+4srm+vb7FxcrLzMzT18fNwr7Avre2rrq9v7jGwMC5tLW2sa+8rrCyq7Svqae2gLq1t6Sip7O8w8fKy8q5m6+vub+4v8G7trWqo6SfpJakoJKRjJaYk5mOoZqZn7equcvPw827vMfSwLC0tbmxsauutLO4ycHE0svP0dTe0MXX1+HTyMvHxs3SxLvU3dbI0uXq5NjX2+jh2tHa3+rz+f2Igfz4g4aIk5KRk4+VnJuagKeknpOalJefoaCckpugoZ6rt724t8rC0e/xhICDjpOInLGjlJKB/O/0gP79293ju6iomI+UmpWZl6ykpqKhmpeboqOtqKyqsJ+WlZqTpaals7O6y9PU1tjX1tjg7djb3drOvsbAu7e4xLu3vrW5t7Oxsbi8zL/LzMe+sa2qpaamgLCpt620w8HMx8K9vrmtsaqwu7SxvMG8trCwrq6fmKCbk5OpmZ2ViYyJiYSQkJKXnqShq7Cfn5Kal5iRmZ+pqqmdnJKZm5qVmI+PmJiknaOsvcrKy8W7xsW8uLPBvrqwsKWsq7azqZ6emqCfrq+mp7ips6unn5aUmJ+lo6GknJqUgIaEfYd+foCBh4p8eXx1eHN8e312enWHdXR2dXGAen17gniLkIeIgIaTjaeUmZOBmJuWhoeFjJualYWCko1+h4WHhoiKjYp4jYZ8eId4dXx8fI6Ij4uGipOGiYyVgG95fXZxbW5pcnF0dHZ4e4GAhX6Mi4mDjJOckp2KmZKTlpWSgI+UkIiBjYN/hYqNi4SKkJKYi4OIho2IlI+NnKGnrZqknZCSmpqal6GWkpaWnaycsJqcl6KxX7WukY6hk5aTjJaUjaadlpOWmZGalKaPkJKdpoqcnZyaoKGypqWYkY+UjouMiZGPlZSMhoKFhIWEeYGDhIOAg4eGiIaFjI2FioaIgH+Ij46KhIKLjpGOlp+on7KpqZ+di6RaXahhZF2loZGPk5GZmJKOjpOTn56mkZJ/kpOHj4uQmZGKnJmgn6iaorikmZiWkpaQm6WmsZqQioeLlZCEiYaKgIqGhoeHg4yQh36LfIOIkomWkp6Xk4+hmpepqqKfoqO5uLm4taWqsZaUgJyMgYSIgH5/f3FxZ2huZGVnxrbBt7e5tauuoqauqKipWqmiq6qmtaOusaqxrq+yna6pn6Wlp6Wkvbi+vLy1t7OtrLTCw763try4r62io6aZn6ealqCTj4KJinZ0cWt6e3aCgnyAhYuNg4CEiIaEgISFiXqDg4B9hHyAeXmNmpiPgI2GiIJ6f4CJi4+UkYR8e4F+enN0Zm1nc3WCjYKNioZ1dHx7cnlzeXVtc3h8dXZtcnJ1fXd5b3yFgHh4hnZ8fnuBi4F9c4CBg3RvZ3R5e4N9e4GFgH+KhIKCfn1/hISOmI2Yko2PjYiCf4mHinuFe3x1cHF1cXJ+dHl+d399fH+NgJSVl46NkJ6dmpqYnqOdhpqZoaSXmJWRk5iZm6KjrJurqJyZlpyknq2gs6+ttMjE0+rs3eTJwMbHt6mxr7Wws6ytqpucp5qdqaOqsbS4s6y4vs/EuMC5sbCvnpOlrqadpK+vp5iboaysrK23xc3W3+B6d+zkc3NxdXZ2eHV9f3t6gH9/e3B4dXZ7fHl2c32GiX6IjY2HhJCNmLGtYF5gaGtdanlyampfubGzWbC1oae1mIuGenJ6fn1+eYqDgYOHhIWGh4F+eXp5hH56e396hoN9ioWJm6GcnqGfnp6irqGfpaaajZeSjoqKlpGLk42OjouIhYmKlo2QkoyGfH5+fYB/gIWAiICFkpCYlpKQk5iPlpiZoqOam6CckYmNjY6FhYqKiYaWiYR4b3R2e3qEf359gYSDjJCGiH+Ig4J6fX6FgoN6fHd9gX98e3h0fHqDgoCFjJKSko6Mk5aMiouXlpOGiYOKjpmak4uHf4SAjo2Hg5GDiIGBenRxcnd8eXh8enx7gD86LzUrLDAyPEI3OTo1NzQ6NzcxNC9AMzE2NjI+ODs3PTNDQjo5LTA9O09DRj8zSk9NQkNCSFJORjk4SUk9SEVGQ0JBREE0S0lAO0c3NDg1NEE6PzYzN0E4PURRPzE9Qz47NzczOTY7Ozo8PEFCRjxJR0I6QEJJQUk6RT8/Pzw6gDk+PDcyOzMwMjc5NzI4Pj9BNi8zMTk2Q0FBSUhKSj1HQjk8QUA/O0U9OT5ARVJIWEZIQERJJklENTNEPEA7Nz87N0lBPjg2Ni84NkU3NzhARTI+Pz48PzxIPDwyMTE3NDM0MTc0OTo0MTA0Njo6MDY2NjIvLjIvLiwqMC8qMS4ygCwxNjYxKSgyMTEtLjE1Lzw2OjM1KDYfIDEfHxwxMS4vMCwuLisqKSYhJSQqHCIaMDcxOzs8PzYrOjY2MzcoLjouJyotLjUwOT06QjEpJSUoMi4lKiotJi0pKSgoIigpJB4pHicrMiwzLzYyKyczKygzMywpKSk3NTY5OzQ5RTc4gEM5NDo/ODg7QDk9Nzs/NzY3ZVhkW2BiXmJoYWxwaWZhMllVXmJfb1liZFxjY2pwYXRxam1raGNgdW5xcnNvdHFpZGlxbmRdXWpta25rcHVrbnNkXWRUVElSWUlJS0dQUUtRTkVGRkpOSk1WYmJkYWtqcGZrbGpna2VlXF9vfHdtgGxna2ZeYmBnaGtxc2ZeYWZmaWRlVFpOVlJebGFsbGlYWV9cU1pWW1ZPV1xiXV5XXl1fZV1cTllgXlVXZ1leYl9ia19XS1dZXVFOSVNZXmVcW19iX2RtaGxxbmxsbGhye252cXByc29tbHl5fXF7d3VtZ2RmYWFwZ2tuZGppbXB3gHZwcmxrbnp5d3dxcnpzXnZ4fod8ent2dXt1cHJvcV9rZ15gXGZmYWVWY11cYHNweYqKen5oZXB5bWBpZWhhXVNTVExPWVFSW1RWWFxnYF90hJGFfH94cnFwZV5ze3ZnbXp7cmhpb3VuZl9nbHN1d3M/OGtmNjYzODg4OzlAREFAgERDQDc8ODc7PDw6OEBBPDA5Pz88OkM8Q1JKKCQjKCggJSwnISIdPz09HTM1Iyo6KykpIx4nLCsvLkE7Ojs+Ozs9QkFAPDs3PDEoJyghLSwqNDAvOjozMjMzMjU6QTc4Ojs1LTYzLywtOzc1PDU0MComKC4xPTQ3ODQvKCwuLzEwgDYuMygrMzA5My4qKCkhJycqMzEtLzItJiIoLTEsLDIwLCk1KikhGyIkKScuKSYnKSknMDMrLiMsKSkfIyUtLzIpLCswMjEvMC0rMzI3MywvNjs7Ojg0Oz00Ly43NzcvNTA4OkNEPTo7OUBAS0tFQk1ETUpLRkE+PkFCPj1CPTw5/3u2ewF84HsGfHx7fHx89XuPegF7/3r/etF6BHt7enqme4x8BHt7e3z/e9d7AgIEAICTjIaQio2RjaGWk4eJkJeai46KlJSWm6Kgo6amm6WhpK6jqrfAwb69ucG0tbe3tbu6npyiqKOkpaOum5Cius69vrnFucS0u7e3rbXHusivsa6ppqmloZ+np5uKlqygm6uwqaapm4mSkoiQmoyOjYuGjIuSmpmapra6srmpsaqnqIC3q7e2tq62srSrtK7CzMzIysvHydbh3rexr72/x8WsucXBzrPFycbIx8S60MjFy8LMz9nd3vbzgP/v5sSjnaaywMnR5tjM2/T98PPr4Nfb4Nfa09DLy9LHzsHDvcSro5ibnKaln5iSlpCQl46OlJqek5uaoZ6hoqWhrJ+XlaSlnoCbk5uYobimoLO1wsHO183y7OTh7uvy6Pb78Nfo4PLh4N7U4uTuhP6B8faBh4Pmx93o0c/RvMzOvNjIyMLFurHG09Lj2Nno5eTw59/Yx7W+t6+ywb3I1dC+wsCws7m2rbW40dPb4tPKudHR29PMx7a6w9fe2tnW2OjZy7ixtaObroCjqqusm56YlYeVkpWXl46UkIb7+vLl6vPt+Pn27/Px+e7w5N/Pztfg5t7R4Pn1+IL39+fs8+/27+/k7uLa1N3Y297b1Me8vKm5xbO9xszR3uza3tzT1NDFxM7SvsHDwtK5oaKcmpabp6mppaCioKGok5eZoqygn5uioZmhtLK7rICuqaKlrZ6sp8O+wsOvrsK5wMK7sqeirKiioqmimZqVk4uSlqCUnqqpn62go5mblaOgmKmrr6GdoJqSjZeTjY6Kh5mVmJSanpiRorO2vMrIxK61pZuuq8C1ubbN18XTxsi7ra2uqJ+rqKCwt7u7try8taqxr6muo6mss7u/tquorYC6zLemnqGps8S+08zBuqOssq6prq+zw7uzraShsJ6ho6KejISSiI2NmqCspqywq6q0udPPx9PF0M24vcy/trSrqLWstr63ys7IxsnK0s7Iw9Ta0M/T0NDh1crc3d/l5/GNh4CB6uT76eDn8oGEjIyNkpKWnKCkn6emtsa4p6+xtICpo5OVkZGSlKGso7C3vsO2xsK9wdbn/fqGjID5842RlY2hmZGMhIOB+O/zgPnm6NfOuKaZlZKOjo+Mjo6Sl5iampWZnp2gq6evoZ6lo5y6sri3s7Ozur7DycDFy9LJzOXw3+vj5OLc39fSy8O6ubu3srS2s7Wts7a7t727u720t4CrqLK2qrKzubSorra7vbfFxczL0MbOzcjJvse1uKyxrL+7wbOxrqebi4WFhqCUjKOyrqaooKmjnJyakpSlra2pmJiemJSckaCVk4+Toqmxsbe7tsDEvri1srGvpK6vn6SfnIyNkIqKkY2GgoD/+4SFhYSOj5GNi5SRjouOjJKNjoCAe3aBfH1/fo2IgXh3e4OEeHp1e3Z1dnZ0dnh5dX57gId8fYiKh4eFg4+CgoWGhYqQeniDgoGGg4WPdml1iZeJi4aQiY+Ch4GAd32QiZSBg4ODgoyMi4qVkYRyeYZyaXNzbm1yaWFpa2pyf3V2c3Rtcm94fHh4fIeKhoyEjYmMi4CTiY+JiYKHhIyEioiZoaCfnpeNjZScnYOAgImLmJWAjpiSmIWRlJaXlJCNmpGPkYaRkZebnqytXLqwsZF2dHuIk5uhr52QnK2soqKgn5qinpaWlZSXnqmlqqGjnKCOhXt8fYaGg4F8hIGFiYSHjJGTiIuFhn9/gYaCiYN6d4aHgIB9en93fpB9eYyJlJGYm5KwrqafnJSdlqizp5igj52RjJCHkI+YVqRVn6VZXVuZf5imlZSWi5WWi6CVmJWWi4WVoJ+qnpuloJ+vr62om5GVjoaIlZKZoJ6PmpqPlJySh4iHlpqco5qNfpOSlZCJj4OJlqeqqKioq76zq5+bnIl+jICCh4SHdYCCg3aDeHZzcWdrbGXI2M2+xMS3u7WyqaSfpqGon6yloKuvtKujrreopVilrKWrtLK1sbOvvbu5tb20sba2vLu4vbK7waupoqSjoq2epauorKienqKfjYuNj5qFeH17eXV7goGEfXd8fH2DcXJreYF2eHp6enZ2hISLg4CFgH5/i4CJhZyVk457f42Bgnp1cmVkdHd1e39/d3p4cWhzdHxvcnt5cXxxenV4c4B9c4F+f3Z3eHd4dn17eXhzcoV/gX2Be3Fsb3V4eoCDhHaHgHeEh5SHgn2NlYSQhpiOhZKUi4CIenN+f4SBe4OCfHZ7f3+Gf4KBgYN/dniAgYCNn4+IhYaOj5WPm5uVnIqTmpmRkpCOoJuVmJKWrJ2fnp+ZjImVlJicoqixrrS9wsTS1uvj1NjKzMSvt7uxqaqkpqyXnZ+RnqWeoqmyure5rL/IvbW9raq3p5+rqK2qs7tqZ2Jdraq7sqy2w2ludXh7f36AgoCCeXt7jZuQhIWFiYB7e3J1dXhzdX6Be4ePlJaJlI2FgZCcrKtcZF6+vW1tbGFvaGVjXl9fta2yYb6ytKiejYR6e3x+fX59e3l6gYWJiICBfXx9hYSLf32GgXaLgYaGhYqMjpCUlI6QkpuXma63q7Ovrqedn5mZk4+Li4+Pi4qMhoeFiYuPh4iHiIeDiIB6fIOGeICCioiGj5aemo6cnKCgrKClo5qUjpaKj4uLh5qQk4qJhoN8dHJ1e35+doORjIiIiJOPh4eDf32HiYV/c3h/fX6GfYh/e3x8h4mHhYmKhI6TkYmKi5CRhI6LeYCBhHZ6e3Z6fXtzb27WzmlqaGZtbW5saWxtaWptb3d2eIBBNi4xKi0uLkE9OTIxNj0/MzUxNzMzNDU0Njk7Nz08PD82Njo7Ojk6O0U6PD48O0JHNzpERUNFP0BINys5SlZIS0VMREpAR0VGPUNQR1A9Pj49OkE8PDpDRTkrNkY2MDs9OTk/Ny84Ozg+SDw+PTo0ODU6PTUxMzs8OTw0PDY2N4BANz47OTQ3MzgxNDJBQ0E/PDgxMjpESTo7PUE/QjsrN0NBSjZBQT09Ozg3Rj09QzpEQkNAPEE7IUM+RTUkIys1Oz5ASz4yOUVGQkZHR0RJREBAQT9ARk1HS0NEP0U4MSotKzQ1MjEyOzk7PTk3Oz8/NDgwMCsrLTEuNi4oJzc5M4AxKSwmKzknIS4sMy4zNi9GR0M8OjI4Lzk+Ny00LDs1MjQrMS81ITsdLioYGRonGjNCODk+NEBCNkg+PDc2KiUwNjM3NTdCPj5HQz46MiwyLScpMS82PT0wNzQnKSwnHiEhLjA0OTMtIC4yNjErLyIlKzU3MjAvMD44NTI1OjItOoA1OTM4KjM0Ni09Ozw+Pzc8OjRmcmleZ2tqeXyBeXJqaF9iWWBZVV5gZFtQXGxmbD5xem50eHJzbGxlcW5pZ25mY2lpa2ZgZVlocmFmZ21ucnpqb3FrbmleXmZjUlRaV2VSRElIRT1FS0xTU1FaX2BqW15bZmxhYWBgXlpcaWlyaYBramVmcWJnYnhycnJgZHNnamlkY1tWY2FZXmZkXWJgWVFYV2BYX2dnXWZaYlxcWGViWGNgXFRVWlxfXWhnZGRdVmNcXFVXVExIUVhbYWtraVtoXVVlZXRqaGRzfGt4b391bHV3cGlyamVwdXh1cHl5cmtucmtvZ2loamxpY2NoaoByfWtkX2BnaHJteHRvcmZzeXh0d3Jzgnt4d25tfmlnZmViV1NfV1hWWFhiW19kZ2dwcoR6bnhueHdjbHJlW1ZMTFdIT1VLV1xUUVFYY2BmYnmEeXd7b214amRzc3d2dntLRT47amp5bWBjajc7Pjw7OjU1ODg8NTc3R1JLRUlIS4BAQDk+PDw3Nj1CO0JGRUM8R0M9OkRJTkgpKyZKRisqKCIpJiMiICEkPzQyHTYwODU1LSkjJykxNTo6Ozk5PkJGRkFCQT8+Qz9CNDE3Mik+Njs5MzMxMjQ0NjAyMjQuMD9EPkE/QT87Pzw+OzcxLjAuKiwzMjQyNDM1LjExNTg1OoAtLDI1KC4uNTIqLjEyLigwMDQzOTQ6OTU1MTkuMi4uKjgxMy8wLS4qJSIlJysnHigxKigpLDczLzAqIiAqLzAxJy45NjM7MkE5Nzk5QUI8Njk6Nj1CQTk7Ozs6Lzc3LDc6QDQ4Ozc4PTw2NTRoZTY3NzQ+P0A8OT07NjM4OUA+Pf97uHsBfO17CHx7fHt7fHx85XudegF7/3r/erJ6hHuHeq17BXx8fHt7i3wEe3t7fP97wXuCepJ7AgIEAICWm5ubnJuil5iQjpilmo+VkoKBi5SXnqKhqLK6sa2srayopqisurm1ucC3zMfQu+LFt56fvqekp6OivrKpurC7tLO2qbSsrZuWkZGNkpecn6Snnpuap66orqSlqq2urauvrKSflY2U/vj/hP+IgPz89+mAiIuIjJKdop6YoKKjtIC8sKu8uM/Buqymo6mvqa6qw8PLzM7Ova+turOyvMzEtri8trzHxq6vsLisvsq8v7S2vcbJwL7AyNrF5NnB37jHyMnD2cC1rc7I1trAxMW5ys/MzcK6tMXCu7e7sKSenaWdmpuVmJaSj5CVkpeUnKCZlJWNlKaxr5ycm5OIiY78ioCCi5adpqOana6ft7ywsqeqqLDJ6vfw6d/Vy7q/1Pfd0MzT0t/Y3efljZSij6Dp19HV9e740cqzlY+nqbSusq6/z8Wip7Syu7rE19vQwsjNyMvPucDK3cvNvMKxnqOcnJ2qrqaow8jF0NbS2d/Mw8G+xsPezNLt6d7eyMm/vLq6zYCwu7+7vLKjnKOprbGqoZyqqZGKgfXv5/f8gIL59O3x9ufl3tTT19zo94L29oD6+/j/gIb//vT+gffp5+Da1eHi7d7b2dLOycvBwcO0vc7L3ODf9Ori0dPOzdDizeTr497R0cTI08/Jy8KrpK2klIOTkpSjpaqio5eUm52wtbe1r4CspqOxrqWsp7GkrrmupbW+xb++v7q3pLCysK6lr52tpqyqqLW3sLWrnJ2np6SelZySlZaZoqWbmKiNm5SUi5SfoZCVkKCuqa28x8nEzdvdzsLFtLG5tLjFxcHDvbi7xMbEv8G6rKOopLCwwci1ure7vKqvsqCsoqassbCvqKypt4C6srG3qrWvq7y+uri0raKgrrOjobe0scOwrq2mp6qnrau0npaSkomQjaGlnqOvrae7raOuwdXY1eLKt7+7tbOnpqnBu7LKzMvHydTL4+bl6uXs4eLh4Onk5OTy9Prx8ICE8O7t5uf894aPlZuemqaxuK3GvMbGtK+rm5Sco7i2sICnvLGcoZWiqrzBy9bKy7exur291YWWm5mOg/j3/IuMiIOUivv118/I9oSDgfLr4urg2M3LvrKokYqTl5ORlZ6jmI2PjqGmq62mn6Oisae3tbi3uK6wq7W4ubm3sLa7s8i608/O1ezm5fXm4+fQ0c/a1MbW0dLZ0tPUzMjBsLqurFOjrrCso6ipsqWgo6i4rsS6t7O+rrjLwsLMzc7azNPZ1uTM0M3OzMq5samppKy7r76xqaKpq7CvpqiwmK22vLWoo5yglp+bnqCcpJycmr+1xLyxwIS+KbiupaSkqLOvq5qVlv6DiIuJgOuH6N/s7vuKjZKMhYWNmZeUhoqZnJKUgH2DfXx8eoJ+fn19hIuBe318cG9xeHV0b3N2eoZ/e3h+fHh2eniEg4KJkYeVj5OCoY+FdnqHg4OBgYCUhX6HfYV/fH9zfnuGenVxbmptcHV4foB4eXuDioWNhoaDg3l1cXVybWxlZWqyvsRr1XRu3d/VxWttb2xrcH5/fHZ9e4KNgJOKgoyLnJKPh4SEi5GLjImVj5CRkpOIgIGPjImQnZSGjImGj5OViIeHj4SNl4qKf4CBh4qFhIePnouom4umh5aYmJKlin11jYuZnoyRlouSlYyKhoiMmZ6Yl5qRh4eDjYmAgnx9e3p5f4iFjoyRmpaPi4GAipKRfIB/eW9xdtF0gGZyd4CFhX2DkIOVmIeIfHt6e4mfoZqQi5GKf4OMo5KLjJSRmpKVmZRdYWpdbZCEfoahorOamIp0coaJlIuLh5OclHp6hoaOjpuus6qipaWjp6WOkZWgk5GKkop8iXx4c35+d3uVl4yRkYmOlYeDjISTk6yco7u6s7anrKOfnJWkgIeRmJWUj4R+hYiNkId8eIeMfXp02szAwbtaW66inKSrpq+ps7SwsbS1YLu5YLm8srZeY7a4rLxgurS5trOvtrW7sK+4u8DDxr23uaihrKOmpqe3s7Spq6yvrbqhraurpZyhl5qinJWRjIF9g4B3aHNxcHh9hHp9eG90dX19gYCCgIGAgIeLgoWIkIeOlYqAiIeFeHN3c3hufIOFfnmFdYV8fHx2f354gHRraXR1dHJrcmpvcHN6fXZyhHGAeH58hY6RhIV/jZODhZOShoSHi4+HeYB6eH6BhIyKiYeIgYSJkpCLkY6Aen50fXyGi3h8dneBdXqCeYF7gH6CgHh1fYCGgIuLiJeTm5SNlpeRlJKUjouan46ImI+SopCTlJSZmZmYlqGNh4mQg5CKnZuZm6qvscS1rrrE1NjO1sKzrqqnpZugn6CZjZucn5+ntKq9wMXGwMq+vry0t7Kzrbi+v7fAZmjEwb24t8G1YmhtdXx2go2QiJuTl5WGgn91cnmBkZGRgImdk3uAdHp/iYmQmpaajYuPjIOPVFxgXlxdtb7Ea2phWWdhsbWin5itWlhYpqKepKCalJaTioZ6dnyAe3x+io+FenhwfoGDiYqBhIiOgYqBg4WGgIR9homHiYuJkpSLmIyel5qcqaSjr6SnqJ6fn6uhjZeMi5aRkpmRjYp9h4GCgHqFhYeBhoeThYWEiJWOmpSQi5SMkp6SjY6NkpiKkZWToZCYnKGko5WUjYyKkZ2RmIyFf4eIj4+NjpR9iIuOhHl/gYmHjouKh32GgH16mY2YkIOKiYqPlJKMh4mGiI6LiH1+fttvc3p3ctl71cbJw81ub3JtZmlxeXl1aWx4fXd5gDc7My8tLDMxNDIyO0M6NTc5LSwuNjU1MzQ3PEU/Ozs8OTczMjA5ODc+RT1KQ0U2UEQ/MjRCPjw6OTdOQ0BMRUxHREQ4QT9KQ0I+OzQ0Njg3Oz0zNjQ+Qz5DOjo7Pzg3Nz49OTgzNDpZY2o6bT44cG9oWjQ1NC4rLjk7NzI3NDU+gEU7ND88S0I/NzQ1O0A7OzY+Ojs8PEE6NTdDPjg7QTsxOzo4QERFODU3PDE7Qzk9NDc5PDozLi80PjFHQjdKLzs7OjhINSsmPDxITD5CRDxBREBBPj5BSk1IRUdAOzo3Qj0zNTExMjQzOT86QD5DR0E5NSooMzo5KC4vKyQoL0cxgCMrLDMzMScoMCQ2Ny4vKCgnKDRBPTcvLTEwJyozRzk0Mzg2OzY5ODEhIiYeJi8qKS5CQU08PzcmJDY3PTQvKTQ7NR8iMDE2MTY+QTkxODc3OTopLzU9NjQtMikeJx8dHCcoIyU2OTM0MzAxNCsmLCQvLD0uMEE9NzUuNTM0NzhEgDA4Ozw+PTcyO0JITEU7NkJGOzo3aGRibHU8P350am9sX2FZXF5ZWFlaMl9iOHB6dXlAR35+bnk+dWttbmpncXFzZ2VpZmltbmloa11gcWlwcGt7cnBiZGNoandecHFtamBiXWJoYV9cWU9QWVtXSllbXWZqcGdqY1dcXGVmamhogGhmZGxwZWZlbWNsdmlgaGhpX1xkY2NYYWVpZ2FwYXJlZGFXY2llbmdbWGBiX1lTXFRbWlhZW1ZVaFVlXmJdYmdmV1lRW2BUVWRoYWBodHluZGpfXWRjZXFydXRzbG93fHZzdnVsZ2xmb2x6gGxyb293bGxyZG1mZ2dramNgZWdxgHRsaHRtdG1kb3RsbGtsaW16gnZygHZ3g3R0cWtuamNjYmtdV1haT1dPXFtWWGNmZHRmXGJre356hnNoaWVjYFJSVl5WTV5dXlhZYldqbnV5doJ3enpzeXV3cn5/fHR2QUN4cm5ra3ZtOj5AQEI5P0ZEOkdBRUY6OTgwLjU9TkxLgEZXUT1BNjk8RENGS0ZHOjpAQDlAJiwsLCopTVBRLSwnIyonREg+PDs9HhsZJygoMjU0Mzg6NTYvLzdAPDw+SkxCOTk0Q0VFSUU8Pj9EO0U+QT89NzQrMzMzNDMtMzIqMyc1LzM2PTw9SENITEE9OUE5LDk3OEA6OTozMzMsNjM0gCw3NjUwMjE6LSgnKTAqNC0rJy4lKzcuLTAvNDcsMTUwOyswMjM0My8vLjAwNj0yNiwjHyYrMTMxMzokMTQ5MywyND85Pzo8PztFREE+WkhORTlAPj1BREI9NjY0NDw+PzpAQmc3O0E/OmdDZ1lgXGI7PUE8NDU8Q0E7LjI+QTs553sHenp6e3p7e4R6/3uNewF6qXuFfOd7hXqCe456BHt6enuEeoJ7hHoBe/96/3qpeoJ7h3qse4Z8g3uGfIZ7g3z/e7h7AXqFewJ6e4V6kHsCAgQAgJGRlJqZl5KPkoqChpaTnZaelJiSnp2fpJupq7mmraegpbKutcm1vrjHtLa/xr+/q7bAr5ydmZ2bkpadnqqeoqSgpamdmqeYl5SIhIOHjJKgnZuen6Chn6emq6qrrLivtLG0taqnnZWSjYuFh4iB/vP98oGAi4yRoJ+VmJSbmKKkgKaanpqWp7C4sbGwpaajqKussLm1usexqJ+dnbHCwr+4u8C9uMW6ta6sqLmxvLTCzcu+ws7Iwra8ucfBvcDJxMTe2t7uyrmvraWssa6gq7C0ub64r7SwqaKZlJWep6Okn5ebm6OdmIeQlpebnJiVm42OnJiRkZmUiI+Pko2MhYaJgIKRn5iluLOwqra0yM+8sLOvstTEy8jy1NnzxrrC07u2ucW7t7Opq8Dl8fjqz8zHyNnr2+LnyrSkmqGfrp+spKnFv660tKvRy7yos7ejorC2rrmrsLTAvcGso6yjp7Gutq6ck4+NnqKot67JucatsLG+xcu1weblxtzZ29jYucfSgNfPy8jmwrW+t724t7Ktm5OUi4WGhY6Rif/7gYOEg4GFgvTi1Mrc2N3e3d/e8+zs9/T09vr3gPr5/eXk4O7Z5uLr4ePZ1OXSztXIxri+wdDS5Nvo2Mq+y9HZ5NLG2trk4dzm4+Ld7unn4uLd5NfAu7qompqTk5eSiImOnqm3qrmwgK2worCpq6auo6ivrKC2t7i5wL2+wq+wrq6usKqqtLSesrrCt62ttbW5sKuyrZ2ampOVlpCgoJ6dnpOSnJSTj5+eoqGnoK+no7vGwL3DycG8urmsnaWgq6eqpq++t8/Sy8m/u6ixtpuqra+8xLynqa60n6eipLm4q7i2xMKuq7a3gLisuLGoq62zv7W5sK+upKGlsayqssLVwreytqicr7Gkoq6tqKeXioiNmpOSlaOnpq+wp6eywsXY6NbZ0r64vK20tcC7ucrX0tHSzdfZ0M3n29HY3uDW6N7xgoDx+4Du4fTz+vn6iYyeq7KruKalqMbV0c3LvLSwpKKWoJ+bmZ6cgJ2Wn5ilr7m3uNDg4L+8sbC/ztv+jIyRj4iIipCQlJKLiYqI/4T//Ovt8O38+vnn4eHd38bHsqybhIiUmZebn6KYj5OTjZeZm5+SnJ+hnKerq6issK6zvLezr6qemaKTqa63tbe/t8a4vsHT09nl5tzYzeDa4tXPzsbCvritqaahgKKnn5ugo6Whm5mZkJCQk5SamqeimJOYpK2vvtXl29/Vz8PAw7LH3sGupKWtqbG6xN/Qyc3GxrO+wLOwtam1p5qbmJeYnpacmZaalqCgoKOin6+5try/u7CsqqSfnpqenJOKjoSRkoSI/vaC9Onp7PuAiYaMiomHjpuUko+QlJCPgHd+foWHhIB+gXhxcn15hX+Dfnx0eXd1d3V+gY1+hXx2eYR+hZB/hn6JfHuDioODc3+Lf3d0eYB6c3V1dH9xc3Frb21kY3h0dnRsZ2hpaWx3dHNydHp8fYaFiYaCf4N4d3FzdmxtZ2FkZGVmbG9t3M7Wy2tnbm5vfX51d3R6doKCgIN7fnZ2gIWLh4iLh4qJiouCfoR+g5CBfXV2fIual4uIioyKh5CDh398fYl/hX6Fioh5f4mGhn6GhZWSjJGak5Wppaeyjn52eHV/iIqAjIeAhYuIg4mPj46HhH+EjIeFhIF8foZ/emx1e3+EhYKCiHyAioV9fYB8b3RxdXJxaW1ugGNxfHJ8i4OAe4aGl5yMfX17eJKEhH2ZiJWqj4WHkHx5eYR7dnJqa36irLSolJOPkKOxpKmsmYuCfIKDkn+Efn2Ti3uAgXmYlIqDkJeJhoyOiYh9foKIhY+HhI2GioZ+gnlpZWNneXyAi36Sh5F8hoKRmZuIl7e8pbW1vLe2m6GmgKednJ24nIqUio+NjImHenyDgH14dHZ2asbAYGBaVVFVVq+qtLfHxb+7ubizvayrrqSsrLKxXrK7w7m8usSxubS7tbawtMe5u76xs62psLSwvLe+trKpsra7wrCksK6xraOurK+qurStpKKhq6GTlJaEdXBvcnRzbmZren2IfYZ+gH9/c356fXqEfYaNiXyNioaBhIKAgXN0dXh7enl/iIVygIWMgHZze3p7dHB3eG5ydG5xdW54e3t4e3d3e3x/eYWGioSLho9+fpCThICFh4GDhoV+dXt6hH17eYCLgZGPjIeAgXd/iHB0dnJ2e3RhY213a3JucYF/cXd2goB2gYqJgIx/iouKjZCWo56hnJmbk4mGj4eBiJqvpaCfo5qPnqCTkKOlo6qkk5WUnZSVl6WwrLKypaizxMrR3MfLuKGgpZijop+bl56loqKnp7e4tsHWycDBurajqp2vX120vmK8tMTL1MzHbWZtdnp2hX14e5Wcm5ualY+Lgn51gIGCf4SBgH51f3aAh4qBf4+cnoiCfn6OmZ2qWVhbXVpdYGJgZF9aWFtZrFuytKqppJ2qpaOUkpSVnoiPhIN9bnN5fHd4fYB7cXVxbXV1fYV7f4uGe4WDf35+f317gYF9f4N9foN3iIyRjI6OipqNkZOZmJ6kqqeilKqcn5mUlZGRjoqEhYODgIWKgYGDg4R+c3Zyam1wb3N6eoeJgnuBhoGCiZSelZaOjYqMlYqar5iIf4KLjI+VlqqYmp2Zm5KWl4uHiICJfnd8gYGEiYCEgn9/fYGBgIF/foiMiYiNkoyNkIqGhH6CgX15fHN8dm9z2d144tbMxc9obW1xcHFvdYN2dXBvdXJzgDEzMDM0MzExODAqLTc0OzY6NDMuNTY2OTY+QEo+Qj86OUE6O0I2PjxKPz5ESEFBNUBNQjc1OD05MjU7PEpAQ0RBRD81MT87P0M7NzU1NDQ8NzMxNDQ2NT89QT89PEI4Ojc8QDg7OjQ6Ozo5PT47dWhvZjcyNjI1Pj43OTY6NT0+gD83OTEuNzo7Njg4Njk5PDw3NTgzNkE5ODM2N0FMRz8/QENCP0U9Pjo6OUI5OzQ8QEE2OT88OTE5N0E9OTxBOz1KRklRPDMxNTQ/R0c6Qj85PEBBPUVNS0pCPTg+R0FAPjgzNTo1MicwNTU3NTIxNScqNS8nKi4rICclKSgoIygqgB8pLyUtODEtJjAuPEM5Li8rK0EwLitBMjdIMysxOi0qKzUuKysmJS4+P0A4LjIwMkBHP0VHQDgyLjY2QTM2MTBBOi4xMChBPTMqMzcrKS4yKywkJyowLTUtKS0oKysqMy8lISAiLzAvNyo5LTgjKicwNTMhKDw7JS8xODg7LDc/gEM6NjlMPTVBQk1NTUhENzM3NDIyMTY7NGRoOD47NzM0NGRaWlpmYFpXVlZWZ2VwenZ9foJ6QHZ7gXJycXppcW9yamlgX3FkZm5naGBgaGxrdWx0amFWYWZwd2ZabGxxb2dvbm9pdXJrZGhte3dxdHtuZGBgY2RjWlJXZGZvY25mgGZmW2ZgYVhiWmFqamBybmVhYl9jaFtfX2Nna2lwfnxkcHF2bGdmb3JyaGNoZFdZW1ZbW1NcXFxaXlpbYWBgWV9dXVpgV15RUWNpXl1kbWlobnBnXGRfZmFhYGVyaXt8eHFqamFtdmBpa2ZtcmpbYGl0ZmxoZnVxX2VibGpgZW9zgHlpc3FsbGlueXZ3b3Byb25veHNvcoCRgX17fm5mc3FlZHF1c3NoV1VSWlFSVmJuanFxZGBmcXWBj3qAcl1aXk1VV1dRTFVeXFtdWmdqZGyCcmhrbGxea2F0QT1ydj5rXWlqbmdnPDhARUhCST84N0lKR0ZEQT06NTUuNzc6OT8/gD43QDc9QUI7PEZPTTk2MzM/R0ZMJygqKigrLC0sLiwpKiwsUitSUUhDNi03MzcvMDQ3PjE5NDc4KzI6QDo8QkM/Nzw6OEA/Q0c6QUVDPUhHRD87PDc0NzQxMjMrKSwgLi41MDAvKDcuMzlCREVGRj86MEM9QDs4NzQzMjEvMDAxgDM5MS8yMjQwJyQiGhscHSAkJS4rIxwhKCkpLjU7NDUsLCYjKiIuPzYsJysyMDEzMj8wMTY1ODM6PTQuLyg0LS0yNzo8PzU6PT1BQkVEQz88O0JFQ0FERD8/QDw4NzM8Pj0+QzxHQzk+b3BCeGhkYGo3Pj5DQT87QEk9OzY1ODUx7nuEev97/3uoe4J6h3uUegF7/3r/eqV6BXt7enp7h3qve498Ant8/3vEewN6enuFepB7AgIEAICHkIeLm5iYj5CPhIKHiJOSmpybkJiUoKKfnKOdoqyrp66sucWwpaiurKetprSupKqzsK2jmq+Yi5KRkKGimKiioaGsoqCgm5WUkZCTlJqXo6Okn6OepKSro6Sls8G/wcXHzMbHt6qooJWZioCD+YWJi4qDio2MiZaOjISOiYmEjICXoJSfsrC5uayzo6akn5mamqOnpa2vqaWSio+pr7qwt7y3pK6fsZ2krJyep6mswLbHxMy8x7PFwL7CtbmtwMK6vb/Gv7OyvKuqs62jq7jIwLWzoJeTlJyao6O2qKGfn5CLioiLkZeOmJeYqa6dqKaZmpidpJaYnZqbl52SipqelYCanKuesqKlvLe4uLyooLO3wLi8maSmsbrBi7zdxMrFyMa1uK+4ttva/YDo4by0qai6xMO/xcy3pqqco5+emZGKk52dp82yycrMvLqtpLWXppKUmZmZoKaooKWEhountqytp5qjoqmsrLGqqaalnqevusji49Lf5O7s6Nzd4ejn24DY08/by8jHu8HKsKmmraWZlYuOioqMkJSPlpWMiY+UjI+GgPDl4djX5OLlgObY6+n14vPq5evs8vD77uLdxNri7+rt5ePw5t3s2MHEytXp3ebX5NzR1s/X1+XAv9/l6uXd3dnl3enn7+7m5+/r3s7IxcnCxrzFyMXGt7G5vrHFxoCvrqm6p6u6rbe3wayrr7G3vL+4vLitpaOfkaKltaitsayktrC+srKvo6unqLeln5eSkI+VmaGhq6Wco5eWnJOdo4ujoqGpnKitvrLAvtTPxdDHuKaYmrOml6i/sbnEu8PDvK+rp6Obn62xwcm3wKazs7Gor7i8urvDu7e5tbS8tYC7qLe2srOcr627wMnDu8O9r66urq/Dwca8vMDFxMrGxsC4w8ywoaSSlqGenqW5uK6qucPBu8HB3MvF1tTMvsy5trjHzMnV3Nvm1tHT1dHd5eTb3Ofo3d7g7fmEiI2Ljv+Ih4SJmJqbqbbAvsTKyMfFurm4u7y2p56gnaGRlZqaiYCPlZmdsLewrbzAx8vAs8LP2euFjY2VkImOh4eMjI+Sh4uOkIeFgoKF/e319vf+gPTk2M/O1rCol4mKlIuJlpaXlpKPk5OXlZeTlZeYmJuYnJykw7i1ubS+wr2yt7q5t7CvtbO3s7Kyq52wp7vAtse5tbu7vsHKzci0sbW9rbWxroCruK+zq7Sura+pqqemmZWfmpebnI+Dmpuapqu0s62zsbGloKeyucS6qaGssbytzb7O1dDY4NfO0NLD1MrCubmmqJaemqKSlIyGjZGNk5OYlJupqKqwsLuvsqWssaqdo6Shl5KRmJ+YkZCMiPyJ+f6Lh4+Hh42KkZyXnKCemY+Dj4Bxe3F1goCAenuBcnBxcXp9gYSFeXt3fHx6dnt1fYN+eXt1hJN/en+HhYGBeoN9eHmEhYV9dId0aXBuaHd4andwaWlybXB1dXJvb25xcXRyeHh7c3t9gX+HfXp3gIF3d3R1eXd6cmppamdtZ2Zpy21wcHVudXh2cHpzcGhwbGxocIB4gHV2hX6AgHmFe4aJioB/e3+CgYmMi4V1am+AhIqEiI+KeX90gHR6gXV0e3t3hXyHho+BjYCOiIWJfoF7jpCOjYqPhnt8g3R2fnpzeoSOhYCAdG9xdHx6gH+Jg4CAgnt4dnZ5e394e3l6g4qCi42GiomQk4iGjIaKfoR1bHx/eIB5fYd7h3d3hoeLjo2FfoeFi3x8XGVmb3N/W3+WgomGioZ7f3l+fJeSr1mmqZKNhoaXnp2VlZ6JfoV6goB/e3hudn55fpR/kJSVjY+Jg5d9inl3d3Rxc3l+f4RxcXJ7f3d4dGt2dXt6fIR/iIuKhY6Qlp6ssqayu8XFw7etq7GnnYCcmZWhlZuakY+UeXJ2fX58fn2Bf3t5eHRtcGxlXV5fWF1aWbW1tLGturq/armgraeqnrCmpaapsa3CvKyumqqtubS0tLjEv73GuKSlprLGr7y0urSxsrC5ucGhnLi4t6ycoaSrpbGtqqilqba4tamio6efqKKqtbatmoqIh3aCiIB1dnmFd3yGgI2Sn4yLjoaCfnp4fXl2bm1tX2dte3F4eXFve3V/dXVvaG5rb35ycWpoaGhucXh5f396hHp/hn+IjnmLi4iKdoWHjnyGf4iGfYmHgnpze4+CcXyOgYOJg46Gg3h7d3dycHp6fYJwdWRzeHpwd397fX6AfHyDgoWNh4CPfoyQlJeEl5ehpKefl5mShoSEiIyhoqubmZygnKalpKmpusi2sLWmqLKopqW7ubGsu8O/usPA0sG2wLOnmamaoJ+orKOioaClnp2qsbPL2NTKysrFuLWzur1hZGdnbcpvc3R0fXtzeX+DhYeOjIqMg4qPl5qWiYKCgod9gomId4B7e4GEkJOBc3x9g4uHe4uXnKZaXVpgXFhdXVteXF1dVFZZWlZXWF1gtqGdn5yfUp+VjoqKlH97eXBzeXJyfH6AfndwcGxvbHV0d3p/e3x6eHd5iX56fHqFhoaBiI2Ni4OEhH+DgISFgniHgIuQi5iPjY2Ni42Yn5qKi42Uho2Kh4CFjoaKhYuDfn59fXt5cWp0eHmBi4V2gX52en1/gHyBgoiBfYWQkZWMf3mDiI6AnZCgo6GkrqSjpamdqZ6blZeMj4WHgYp/hoR+goR7f4B/eX2HhoeMiI2Gin6FiYR4gYODfHZxd3h0cHZ3duZ51dRzanFsaW5tbXVvbXBxc2lmdoAvMycoNDEzMDQ4Li0wMDg5PkA+MjUxNjk3Mjc0Oz8/OTo1PkU1LjM9PTs9OUI7MzZBREU+OU08Mzo4N0dIPkxHQ0FGPjw8PTw+Pz4/Ozw2Ojo8NTw5Pz9FPzw6Qkc9PDw9QkFCOjQ2ODc+OTY4aDo6OjszOTs3Mz43NS84MzMvN4A+RTo7RkA/PDY9Mjo8PTc1Mjc4Nz5BQT8yLC49QEU8QUVCNDwyQjY7QDMwNDMxQDhDQUg6QjVCPDs/NzozQkNCQT9EPjY5Qjg8RkI8P0ZLQjw+ODY2OEA/R0RQS0hIRj03MzIxMjQuMi4uNDkvNzgtLi0wMiooLiwyKjEpIjI2MIAwMTcuOCorODQ0NzcxLTk5QDc4GCAgKy8zKDA9LzI0OjozNzE1MEE4RyI6PC0uKio4PDo1OkY7NDwxOTY4MzIpLzUwM0EwPj49NDQtKDkhLyIiJSUlJy0vLzAeICIuNS4wLSYtKzEuLDArLzAwKS4vLzE7Oy00Nj07OzQ2OUA9OYA5OTdAO0FHQ0hQPjo8Qj84NjI0MjAxNTY2PD06NDc6Mzk2NWplX1RMV1lhP2pddHR7coN6cnNxeXaGfm5rVGNoc21wbGp0cW15c19gYmx8aXJlbmdgY2BqbnpaV3R1dWxeYmRvaXFwb25td4SIin18f4V8h4SLlZOLem5vcGR1eoBlY2JsXl9jW2Vpdmhpb2hkYFpUX19fW1taT1tfbWVua2Veamh3b3NwaGxnaHJhXlZTVFJTUlpbZGRhamFmaV5jY01gYF5hVGNlb2FpZHJwZXR0a2BYW21gUF5uZWp0cnt1cmhraWhhYmpnbHBdZlVlamxkbHBtbGdpZWNoaWpycIB7anh3c3FZamx8goR7dHl0aWVnamt7en5xcXZ3dHt2d3dzgY10ZmhaV2BbWVxzdGtod352c3dzhnlpd29kV2VUVlZeXlVXW1xnXVpfY2FveHRra3RyZ2puen9CQ0NCRXNAPzw7Q0E8RElMSkdMR0NFPkBCQ0ZDOTY3ODwwNT0+MoA2Nzs6Q0k+OT8/QkA5LTxFR0wpKigrKCUqKyotLC4wLDAyMi4rKSkpSDc2ODU8IDs1MS8zPzAxNDA1Pjk1P0JCQUA8Pj0/PEA8Oz9DP0NDQUBBUEQ+OjY8PDsyNDg4NCwrLSotKisvLCQ2Lzw9ND0xLjAvMTM8Qz8wMTI5LDQxMIAwOzQ5Njo1MTIvMC0rIx4lJiUqLigdKCgkKSosKSQmJCghHCYtLzUyKicxNTgrPS45OTc6Rj49PUA4QTk4Nz04PjY8Nz4zOzw2PUM7QEE+OT1EQkNGRElCRjpCRj82QERGQz8+REZAOj4/PnVAZWc9Nj45OT88PEQ8Oj08OjErNO17AXr/e6x7AXyPewF8+XuIegF7/3r/erB6hXsBerJ7lnyGewF8/3u9ewR6e3p6kXsCAgQAgIaGk5Wam5qTlpCOjZCIjoqNk5mWm6W3u7W/rKekpLSytbWrtLO2qq6xuLe7tKWhmqast7CpsKmYm5igmpeSjp2SjJOjnqKhjpCPmJOZmKKhr6WZpKCroa6srrS2vca8ztHUxsvPv7ysqJyXmZWKhoKBjIn8h4qIiIaDhfyIgv2IgImHhoiitb6umqGimIeDio+NkIaDmqCqqq2pnKessam4qq2nqKWsm6Kolp2YkpqZmJ6pqK6xuNXEwLKknIWHkJ6coZuxs7CopZOYlZuYqaOgk4eFiIiOj46Xl5mZg/z1iISBjo+Ji5GNiI+QnpyompyYm5OboqWnpZ2dmpCWoKSogLS0srO4sbLK2rqsoZ2cramtrL3DsLSztrTBrrmqtbit1uCpqb6mx8798onquba2trO+28vOx6+ropmms56ckIiIiYqbnqu2v6qrr6SKlpamp63BuLCzsqm3rbm0xbmgl5qwrqymn6GpvbGnnZaVkIyor8fGvsC9uravsK7FycO5gMvT2cXY37y4raSgo8G8pKKemoqPkZWWpZmXlZyamqCnnp2VgPf894Dz++vw/+Xuh/Tu8uff5Nze3+He2drN2dvi4vDf4ePYzNnmzsXZy9Pc59zm3tnY1+fj7c7R7Ons7O7v6uzo4OTr1uTT4d/WzsvS18O9xs/Fw9bR3NXI0cXQgMvNvL24w9q8vLm6wcG6s6Wutbuzr6qcnpeXnKeztLK5xLm/xL+1vryxrberrqOlnpGMjJiZraOnpKCWn5OQm5mWp7KmnKyut7THvcW7wcjGysW9uLCorqyusaefqbC0v7GmoaGknJuvxbW/xLC1sqWYp7G0sLjHxtDKu72wxb29gLK7sarAxb/X2d7FyunX6NHDxtbLz9HFyNXZzNLY2MTMucbBsbeso52puauxrrq1sbS/vMO0us/fwqrEzNTJvKahqsHaztDi5fzM1tbc3czf4uHf2+Hn4PDz84eNjY6XiImKhYeOl6KisMa9wrqtt6WioaGnqq+wsqekoJ6Qj5WTgJabprCvpqitpbK+ray6xc/a8vT+i42QkI2GkJaWlZWGgoiGgYL++oaCgfr48/br3uHQybSqoJuUkZWOk46Ti5OUkJGOjo6Gh4+Oj46MkZifn56lsbKsuLSvrKKnorO1yM/Fyr+9rrCil5GKn6OmoJueoKSeoa23ubC5t7/Bsra0gK+0sbKutq2wtbi8s6ypmZ2XkZ+cm6qqqqanqZ+mnqObm5ymp5ubm56goKeUoJ+foKKkpaSmq6u9xby7tsK+sKevtaqdm6Sjl4eIi46RnZaTkJyfmKGlqqWkoayuq6CjqKGalJCZnJaVnKGio6CWnKWjlZWVoqGlpJ+lpKmdlJaMgGtwe3+DgX94e3d0eHtxeXt4foR6eHqDgHyIe3d5eH6Ag4B/iIeLe3x6fn2Gg3p7c3+Di4N7f3lpbm59cG5sZXJpYWd1cHZ1anFxdG5wb3RxgHVrd3R8cn14eXp5fHxye3yBcHiCdnl0dnBudXNsamlrenbhdXdxcG5scNd1b9txgHFzcW6HlZB/cHZ3d2trdXt2d29shIaPi4d3cXV7hoCOh4mCgnl5a291Z2tpZ2praWpzcXJzepKCfnVtbl1ianV0cm5/fnt2eW1xcHRwf3t6c2ttcG5zdHN6eXt4aMnJcW9ueXp2dnp0cXR3hYmTi4yJiIOHj5CYk4yEg3d6hIWKgJGRjI2Nh4aUoop/dnd1fXZ3b3+GdHh3dHR7cHdreXt0l51zc4hxhYuunFqae4CGjIqUr5mYlICEg3+OlIB7cmlrbGh2en+Gj4OHjYx4goSNiYqXjYWGg3+Qi5mRnoxyamyAg4J8cXJ6iX99ent6d3aOk6irqKikn5iKhoGQkouBgI+WnpGhqYWCcWpnaomEeHyAh3+Fg397hHl3cnZva21vamxqZMbJxmbBxre5vqq0ZrGtsqyoqp+in6Wno6OdpKKqqrWprq+poq+/s7K5sLOyubC1qq2np7i2vZ+lva+vrKyqqbGwq7Gumqibp6ysp6ausKOhpqqnpK+ss6eYoJGdTZyklpCOk6OOj5WTkpeQjHx+hYV6dHVscHBvcHZ5dGpqcGpudHNvdnVwb3l0em9ybGFbXGhpeXh6fn93fXdxd3d1gol7cXp1f3+ShJJ/hH6AfXp7e3uCgIKFeXBydXaCc2tsc3RucHmMfX+AcHJ0a2V4gIB5doWEhoZ5gn6Tjo2HkYmGoKait7i8o6K4pbCYjYqfk52ln6SxsaOpsLGhrKO0tKexrKairLehp5uoqKept7nAs8DY48GmtbS0p5+SlpuluqmmtLnNp7W6xsvG3tqA2NHFw8a4wr64YmZjaXd0eH18eXyDf3h/iYaLh36FfHd5fXyBhYGDfn1/gnx+gX9/f4iOioJ+gXV/iH2AjZeVmqimp1hZW1xaV1pbWFZXTk1UVlJVqKhcWFehn5qclo+VjYp8eHNvbW9yb3Vvcm5ycnBta2ZoXmZvcHFxbnF1dXWAdHd+gHmDg4B+dn12gYKOlI6SkIt+gnx6d3B/gIV9eX9/gX98hpCQiJKRl5uJi4aDhoOEhImDhYmQlI2HiHuBf3yIiIqSi4SBenxyd3F5cHR2foF5dnV5fXZ/bnh3e32BgYeEg4aHl5ySj4uVlImAi5OLhISSkIl7foF/foR8dHEveXlzdnuAgoB+iIWAdnaAfnlzbnJycG56g4OGf29zeXdpbW93eXdzbW5tdW1rbmiAKisxMzY0NDA2NDQ6PTQ5NjQ3OjAvMj08OEA1MzU2P0FCPzk/P0I4Ojs/PkRANzYyPUNLRUBIRTc8PEhBQD89SkQ7PEdAQ0Q3PT1BPDs4PTlEPDI+O0Y8SEVDQkFCQDM8PkI1OkM6PDY5NTY/Pzs5NTQ/O2Q5OjY2NDI3ZTo2ajqAOTo5NUhSTkE1Ojk3KikyNzAxJyM4OkFAQjo1O0BEPEQ7Ojk9OT82PEIyNC0oKioqLTY2Nzg9UkVDPDk4Ki42QD05NkVDQj1BOT48QTpFQD43NDg9PUJAPUVDR0k7a2c9NS42MywtMiwnJycyNDoyMS0tJigyMzg2My4uJikyMzeAPDo1NDYxMkFNODEqLi45NTUwPj8uMTE0NDotMCcxMixJTy0vQi48PE5CJ0EqLzU2MjhJPT9BNTo3M0BEODgzLCwrKDAwNDk/MjQ4NCEoJy4rLTozLjAxKzkzPjhDOCQeIDEzNC4nJy06MSwoKSciHywqNTIuLy4tKyUlJDU4MSmANDxCOUVLOj45Nzc8Uks8Ojk7MTQ0MzI9Nzg1PTo5PkI/RUQ8cXFoNF1jWmRxZXdKenh7dW9yZmpqb3Fsa2JpaG1sd2hrbWZdaXxsaXFrbG5zaW1lZGBfcXF8X2R8cG9sa2tqcnFob25ba2Nxd3l0doKFd3uEjYuEjImRgnV9cXuAdnpta2pwgmZnZ2p2enZuW15gXlpZXVhcXV5iZmxqY2NrZGhvcWtzc2tla2NoXWFeVE5OVFFeWVtfY1thW1RZVU9ZYVhQWVhiX3Blb2BjZWNpZ2RjYl5iYWNoXlpiaW57a2RlbG5pZW59aGppWF5hWVVocXJnY3BucXBnb2p7dHeAcnx1bn+AdYuKk3t4kn6HcmlofnZ7gXl8iIx9f4WFcXxzgn90eW1jW2BrWl9VZWdlaXt7fXJ7j5d3WWZpal1SREVNV2hZV2hvglhhYGdsY3d2d29oanBqen15Q0ZAQ0xDQ0U+O0BBQTxASERFRDxAODU1ODc9QkBDOzk4NzM1OThANzc+QT47PEA5P0U2MztEREdSS0kmJygqKyotLiwtLywuMjIuLVJOKiUkQT86Pzw0OzQzKywqLC4yNjU8Njo1O4Q9gDw9MzY9Ozw6NTpBQ0RBQEZDNz88OjkxMy01Mjs/Nz04NSktKikoIzMyNCokKCkqKCcxOjw0Ozg+QTQ4NDI4Nzc3PDU1Nzs9NjExJysoJi4sLjYyMzEtLyUoISUbHiAmKiMgIScqJzAiKigpJiYlKigrLzA9QTkzMz9AODI9RUE4PDhCREE0Nz07P0lBPj1ERUBDR0VFREBJR0E6PEVGRD49RUVAPENISElCNztDQzc7PUhHR0Q9Pj9BODU2LfN7AXqHewR6e3t63nuCest7AXz5ewR6enp7h3oBe/96/3qserp7kXwFe3t8fHz/e9d7AgIEAICRjpGzpZiYqpaamY2Ij4uOkpGRk5abqKiqr6uupaeop7isoqanq66utbG5trakl5WdoJ+enpmimpSQlZONiYmJnJ+jjZKZkYmciZGbk5SRlqmroqOeo6iqq6qts7rExdHNztPKysTCtaCjnpmgnZ2aj4mZooyQjYmIjIeGgPXh9oD4+PXy+4SMlIyHgo2HiZSUm6Wwnp+fmpKhop24rLqdpK7Jwbq6oYWNjo2moJyjp6ipo6OdlYqTp6OnqpaZkpiXlZucp6SaoaScrpmYmZiKiICEiYKLkpmViYf5++Ta4fP9gY2OiouPkJuyuLWepZqWjo6NkI+PhIODj5KVlpiaooCjo7Gwo5yWmZexv6yws8nJ1a/Ev729xsHE+ITb59Xh4ae4uqO7ssW3x83Sw6++rbPEws3It7SZkJeftqCnnYuRiYeRnqe1ubenvMa9r7KywMLGyr/Ux7W1pLClpqeYqrelq6qYoaG8w92yuK60oqaisLLAucK+sKiktraxsajHy4C7w72+vq+mpbSuwbiusru5rrS1oJ6mpKWkpaSota29rqiaoIaD/oePiYT77evb9O/v8uTb287F187NztfTx8LI2NXX293j59TBxMO7xcvn3Nvj1ePs/ens0tfR1+Tn5uPZ2tHS1NLJ6eTe0+fm4dzY0tTQzcjEvbvUysnF19vT24Dg5dTWzMnFy83OzMPMysO9wr28wrq1raObl5ylrK+xqauvrMC+u8K9xbu+pqmmpI+alZqWqa2joJaXl5qXmZ6Ym6CloLC2t7SxvbC4sLu9uL25raWvoZyyqqaptbqyuLiyqLqtrq6nqq+2t8a8ur2vnarCwr3M39PJ09fHx8C+uoC7urKsts3P0Mja0dTT0tPQxtvV5tfd6czW38vV7M7N1svO4ra0u8W0uLa7u728uru+xcC+xMPb1cLJz9XWxsq7ucK/yM/LzdvZ2+Ll3tzX2eTq3eLk697n/4eFiI6HhoCQhYGOmJmYqK2pn6KlnqOgo5WTmJKWnqmroo2GiZWeqICpt7ucoZubn5edrLDD2NPh4Obl+IWJh4yOkJWXmZSHhv6Ghf7zgO+H/Pbq4+fi18/Kx7evpaCWlYqKh4yMioWNhYaJi5KKi4aEhIOKkYuQn7Kfpa2onaaspKGfoaWlpK+zpLnC0snJxLSnp6WZmJidoJekqaKlqKmnop6hoKGiqICqsrOtq7Gyp7Szr7a1wry1r6+4tLeqrq2rrbCvqqKirKGhpJqMl5Wpn6+km6Kemp2pnpqZkY+Rk5mkpaemopKFgo+OlZqWko+NhoKEgoyQjo+Xn5eXnZqjqKKfoaippq2plpSPlKORk5eWm5ulpKKsopiZk6OnrKCoraSpmJ2nmYBqa3KUh3x5iXh+eXRxcnB2eHl8eHp4gHl6fXZ8dnV4eYZ+en+AgoSBhYOJiYd7cnF2end3dW1ybWdnbW9tamhjcm1pXGVxbmVxY2t2b3JqbX58dXdxdnd3dnVzenp/foWAgYR+hIOHgnN5dXJ2dHVybWx4g3B0bmtsb21uacu6yoDP1M3F1GtrcGpkX3VwdIJ/goiOfoB8eXJ3dHCAd4d0e4eglYyKclpgYmB3dXB2enp5dXNsY1hebWlvdGlva25rZ2dud3dxc3pufGZqbG9pa2pwdGpwcnt3bWzKz72us8HDZnR4eX1+foOPlJKCjYeHh4aIh4aHe3p8gIWFhISDiYCDgoeEc21lZmZ+inl+gYyJkWt/enh7hH1+olmSnZScnXJ9fGt/eoZ6hoePhXiIf4mbm6qjlJN6dH2Bl4KHfnFzbWpscHaBf4N5j5uWi4+KlJebm5aonpGRgY2EgH5xe4V7goR3gn+SkZ99i4WPg4aCkY2gnquqopqTl4+BeHCDhoB7hoOFjIJ5dHdzfXZydYSGfouHfYCDgn58fHt+iH6JeHFsc2JivmRtZGXJu7mnvLCtr6CenpaUpZ2fo6uon5SOnpOPlZunsa2hqq6rrK3ItrK2prG9y7KxmJ6bo6+xtbOurq6wsrCmt6ylm6alp5qXl5+gra6qq6m9q6KcqamhrICttaadlpORm6Cmp5qhm5GKjIKEh36Cd29rZWpycW9rYF5mYHBvbHFteXh7a3Vwb11mXGNjcXd2dHJ5eHh6eX52d3t8c3+DfoCBi4GIfoB+e4B+dXF8dnCDd3FveHhtc3RsZ3dwdXd0dXt5c311dH10aHSGgXJ9kIR8iZCDiZCVjICPjoJ/kquxtKu2p6urqaWglJ6XpZmksp6lqpmgsJeZoqCrwqGotsS2t7Kwramlp6yvubu5wszj18PEwb+1paugo6qhp7Cpq7m2ucPLycrO1NrWzMvG0sa/0WxlanFtb3N9d3Z5fX51fn98cnR8dXl6eWtsb291fIWIf29vbn9/hICCiJB8hoOBgnJzfHuMmZWdm56ao1ZXVldRU1ZXWlZPUJdSU6GdUp1Zp6WblZiPiYiIi4V/e3pxc2xtbXR0cW50bGppam1nZ2ZnaGdtbWpsdIN1eX9/eICDg3x3enVwcHiAcICMkZCVmZSKi4V3d3d5gX6IioSFhIeBe3d5eXl2e4B6gYB8fYWIfYuKhIiIj4mHhIWWlZqNiIF6cnh3d3N2enR1d3FocHCDeYiAeoaBeX2Ed3R3b212dHV7d3h3dm9ranRzdXl7fH5/eHZ2cXJxbWxzd3FxdnaAgXx4dnt7eoGDdHRtdHxtb3V2eXl9eHZ9d29wa3V4eWtxc2tuYGdybIAvKyxEOC0tPjM6OjUyNTI0NjQyLzEyOzk7PzlAOjg5OEE3MjQ0OTs6Pz5GR0k+NTY9QkFBQD1EPzk3P0FAPj89T0tFNjxFQzlIOEBHPDkxMkJCOz05QUJEREE/QkA/OkA8PkM9QUBBPTA2NjY6Ojw3MC87RDY7NzU2OTg6NmhecIB2d3FocTo5OzYyKzwzNT04OT9FODo4NS84OTdIQEw3O0JTSkZJPSsxMy8/ODE2Oj4/PT87MiguPDg+QTc9PEFBPD1DR0U/QEhATj5CRUM4NTM6QTxHSk9MQ0F5gnBhZWtiMDc0MTEvLjE7PkAyOzc0MTAxMDIzKiwtMDQzLy8wNIAvLjUzJiAcHh81QDA0OUVDSis9NjQ1Ozc5TCg7Qj5HSCg3OyxAPEQ3QD5BOCo5LzVFQUxJPkMyLjU5TDpAOjI2MS0vMjM+Oz0wP0U/MjIvNTU3Ny89ODEzLTo0NDMmMDgtMjMlLSk3Nz0nLyw2Ki0nMCYyLTYyLCspNDMuKyU4PYAyODY6QTgzMj09S0Q5PEZHQEdDNDM2NDU5Ozs/SEFOQj48RTc3Zjg+NzhwaWxjgXp5gG5tbGZgc21wc3lzal5fbmZhY2ZudWteZ2dlZ2l/cGlsXmZxgGttWl9ZX2trbGhjZGFjZ2Fab2djYXByem1ucHZzgYSEhX+ShIB4g4R6gIB+gXRycHJudXd4enuFhHpwcWRha2NpZFtZVl1iZmVkWFZbVmZmZW5rd3FxX2VgXk5WTlVQXWJdW1lfXmBfXF5VU1NVTlhcWltaZl5lXmVkYWpqYl1nXlZpYV5ebnVrdXhrZXVvb3FoZmhnYGlgYmtiWWZ7eGhtf3BpdYBwdnZ6d4B9fnRvd4iGiH2Ke318eXRwZXJue2x3hm51fWxxg2Voc3J7kmhobnVkZGBhYl5eYWVqend0eoCSiHRyc3JtX2RWWGRYXGFXWGdmZm1vbGhoZ29xZ2pre25tgUE7PkI9PTxGPDU7Pz45REVDOTc8Njo7Oi4wMjM2PENEPSwpLDg6PYA5PEIwOj9CRzw7QTtFTEhOSkxFSygoJyopKi0tLy4rL1wyMl1VK00sS0dCP0JAOzc3ODQyMjQvNTAzMzo5NjI5MjU5PUZCQT47NzY7PDg9RVRERklDOT9APTcyMjAtKzI3JzQ5OzU8QkA6PzwtLSsrLiguMy8wNTk3MzAzMjMwM4A0OTczMjc6MDo5NDY2PDcyLi45OD01NDMwLDEuKyUlJyImKCYdJCQ0LDk0LTcxKCgtJCQmIyMqKywyLS4uLiYjJTAxNzs6PD4+OTc3NDo5NjlBR0NBQj1HS0dBQENDQEdJPD05QEk6OTw8QD5CQD5GQTo6N0RFRzs/Qjw+MDY+Nf17iHrVe4d6uXsBfP97i3sBeoR7/3r/erF6u3uMfAh7fHx7e3x7fP972XsCAgQAgJqbk6SnoqaipaKTmI6MkJWRj46gpKSlqqWnra2irbamuKmjsZ6aoJ2er6eeo5mRlJWUl5mRjo6Ll4iPjZWNioWHkZ+clZKIio6Qj5aWkZuYmZmPn5eYm5qps669wdDJz9DP1MbPwMjBsKmnoZuZm5ibkZGPkJSIlpeMiIf89PPpgPTm7/WEhI+Mh4eEhYKQmpaZnJumq7Snq8C/wMK0qpWVoJ+Ui5WmoqGan52qrqCemJ2TnpyTkpCVmZmbi5OVl6iQj4mkoqKdpaiwqqSkppOIjvmHg4qDi42K/PH228rRytzU+IP8g4qckpaZkISOkMGVhYGCgYLp8omEiIaAhoaOgJCcurOjraCos7Cvvbm8q6m4w83NzNLY3tLc1NflxcS2tbCktbOpsq2wvrKrrbijsrvSv6+pop6SgfaHj56dk5WRkZaZm5+oqp+ipLmxt6arqqWpqbqytrGuqZiik5OkqbivsqukoZ2tt7WltLihr72qo7WfssLBq7uyprfLxsawgMDBzcPL0c/a3ce4rrrAwcjFvr+0ysbCtq3ArKeur6+ik46GgYmOjoyJgvbh09Xk5u7dvsXNy8Pc08a1vb2xv7fJytfOwMbL0Ly8xLXRwLbAx83HzcrWzNbL0NDZ8fHq5+Dk4uHb1s/QyMLW3NLn19PMxr+7wMPR087W4trS4uDggODS3trcx8e4wcnQyNbIxsnTw8K9vse6sqSas7vEvLy9yL7KzdrIzMbCuLOxqq6dopumoZizq6+trqqhlJGOm7CoqbnL0MDCxcHFsLi0ub26usa3r7Cco6Wir7WltbKorKaoqqmvtq+rusvB193HyLvCwLmsvrzMxc3cytTDyLu8gLG0u7jD0d3bz9LGw8bR097a5erc39fb5dvj+/rt5tzl6Nzg0re4uaWxtLOvwMK8vcLS1dbSwNDRx8TR0d3S0MrKw8LZ2tnX3uff4evu+O7y8+jv9vrr94aEjZafmJCSi5Sdoqemm6Sbn5GRkJmPmJKLiZCIipKdnY6RiYSQjpGZgJ6hoJiNk5mboqWwr7/H1tzu9P6IioeCi4eIjYmKiO6K6Pr0797j4/Xw3+Xd2uni0MK9saaioJaVjo2LlYmKiY+OjpiOiImEgoiLi42UiYuXn5unqLKqr62nnqObp6miqrewsbO9tbimq6idpJ6npKOop5+ZlJCZj5qbmJydoqS0gLWzsaynpailqaqwrrO0tsC6qri6vauypLKzpJ+loKatqaqnpZibkp6ZlYyRjpKUlpGVkZSYmZWboKWqqKCYkZiMlJqRm4iIiYiLh4aJioeIkZWQlpeio6ikpqCkorKrpauloI+ElZWOlpiWl6aur6uvoaGkp7Cpp6qboaKYmo2NgHNzbn6EgYB9fXltc21ucHh3dHN/gHt4enNzeXlyeIF2h3t+jH18fnl7iX96e3VvcXF0dXVwbGpodGRpaG1nZWBianJwaWplZWdoa29uaHBqcXFmeXFyc254fHF+eYaAhIKEiHuGfYeJf3t7eXFxc25ybm5ucXVndnNqZ2nCvby0gMC4wL9rampmZWBeZ2V4h4eHioOMjIyFhI+Hi4V9fHB1goF0aWhzbmpnaGh6e3J1c3hudHFkXVtbZWhtYmhra3pnZ2d8endvdHR5cGtvdW1sedp3b25kbG1tycXQvrC4rby31nHidH2NhYaHf3aBgrKLe3h3e3rd3nxzd3VvdXJ3gHiBk419gXF5f3h7hYCEcm50eH99fIeNkIeLjZGdiIh7fHZmdHBncG93f3l0dX9vgoikmIeHgXdzZcFocHh3b3J1eHZybm52enZ+g5WRmImQi4aChJKOlJSVj32DcW92eIZ/iYeCfnmHiop9j5aGkp6Pi56PqbKvl52LeX2KhoN1gISJlYuSlZKcnYp8c4CCgIODgYJ+lJCMh3+Ofnd7enxyZ2hiYmhoaWhnZcezoqCnnaSXf4uWl5atpZyTmp+SloeXkJqOi5CYqZeWpJinlI+Nk5yWm5+rmqKUlpWerKidnJSaoqytsrCqn5uopZ2xn56gn6WprKm0r6SkpqSYq6uogKegraGpmZSQnKKnl6SYj5Kci4iGhpaIhndrgoWHeHNucGdpaHVpc3V7d3Z6d3lobWVtaWF3c3t2gIN/cXJsc4N5dnyHjH59hYaPgYmDgYN9eYN3eH9zd3ZtcnVmcW5nbmlvcG95enZuc3xufYN4f3+OlIp9hX6NgoaahZCFjouLgISFhoWVpK6xp6ymnZuooKGepKyjqqSrtKypururqaGrta68tKixu7G9vbStubKsrq/Bw8K/tsjGu7S1srWrq66zp6O4trG0vMbAxM/Q1tPX08bNzMrBz2xoam51dHBxcXZ7fHl4cHVwcGZoZG5lcW5naG5oam93c2loaGZtdnV7gH+CiIN8gIB8dXJ7gI2PnZmioqNWWVhVWFNTVlNSUItUjZ+gnZWXlaKclZmMiI+QhoGHhHl6fnBxbm5udmtrbG9ta3BqZmdhZWpoa25vaGxxdXB7fIeBhYSBeXlxdHRrdIB5d3qCgId+h4Z+gXd7dnR+goGCgn+Ee356d3l8fX6IgIWChIB/gYJ/hIGFhouEh5CIeoyQlIeKgo2SioOJhoiIgH15dG5uZ3Fua2lxdHd2fHRxc3R4d3Bvb3B0dnVycHdrcXlxdmloa3J2dHR0cmpkZ2plbW93c3t2end6eIaBe4R/fHFncnFpbnBqaG91dnN4b3J3dX92c3hpcHNvb2VmgDk3Lzs/Ozs6PjsyOjQzNTw5MzE7PTo5PTg7QEM6Pj02QDQ1QTUzODQ4R0A/RD44Ozw+QEQ/QEE+SDc9PEQ/Pzo9RkxLQ0RAPj5AQkZCOUA4OjwxRT9BQz9KTUFJQ0lAQkJDST5IPkZFPDk7PDY1ODAzLzExOD0xQD83NjppZWligGldY2E6ODk1NjMvNTE+RT8+PzlCQkM6PEhFTUxHQzc4QEA0LTFAPjo2NzRCPzc4Nz02QEA3MjAxNzk+MTs/QVI+OzxMSUY+RklSS0ZJSTo0Pm5EQkhARUZHeHiGdmhtWl1MYTFXLDE+NTU1MCg0Nk04MC0tMC9JTTEoLCkiJiMpgCkwQjwwNCoxODQ2Pjk/MCwzNz05OUBESUBBQEJGNzgxNTQsOzo0PDk+RDw2NTwrOD9SRzk5NzMzKlAwOEA9Njk3OTk3NDU9QDg4OEE8Pi4zMC0sKjMvMjI0NCo0KCUqKzUtNDEqJiEtMC4jLDQnLzcqJTEkNTs7LTcwJS84OTgqgDU4QTk/Q0JNU0hCO0JDREpOSko8SkQ/OzhJPTg9PkE4Mjc0Njw+Pz49PXptYGN0cXluVmFramN7dWleY2NWW1FiYG5kW19ja1hYYVlrW1ZYW2FbX11oXWVcX15nd3NlX1dcX2dna2poX1xubmZ8bG5ycHF3fn+Ohnl/hH9xgHt4gHJmdG57bmxmcHV7doV9d3mCbmppanhuaFtUbG94bWllbGFmZ3NmcHBzbGhrZ2laYVliXVBiW2FbY2ZfU1NNUV5TUFdkaVxfZ2ZuXmhiZGZiYm5jY2ldYWNeZWldbmpnb2lwcnJ2enJnanNgbnBjbGp9gnhrb2JxZmt/bHdudHB0gG5wc295g4uNf4R5bmpzbG5qcXpvc251fnR0hYV2cGp1gHeAdWFjaV5nZmNdZ2ZjZGh9gX16cIF+cmlraW9mY2NmXVZnZF5hZ3RoaW9sbmdqamVsbGtgaDk1NzxEQT0/O0FEQkA/Nj05OjEzLzkxOjgvLzUsLjM6OjAzMCw0OTc5gDk3PDo2QERDQD5EQklGTUhNTlAqKyooLSoqLi4wMVU4XWZmYFRTTVRNQ0g/O0VFOjY4Ni4yNi4yMDM0PTIyMjc2OUE/P0I8P0I9PkBBOT5DRj9FQkhBRENAOjszODovNj4xLSwxLjYvOTs2OjE0MC0yNDAxMS81LTYzMjU0NjU9gDs4NzY1NDYyNDA1MjUyNTo0KTQ3OzAyLjk9NS0wLTEyLjAtKyQkHykmJCIpKy0rLSYnKioxMS8wLzI1NTQzMTgvNDs2QTIzODo+Ojg7OzUzOz45Pj1EP0ZDR0JCPUlBOkVCQzozQUA2PDw3NDxCREJHOzxCQUg+PD8yOj05OjAw/HuIes17AXqHe4p6Ant6kXuCesF7AXrwe/96/3qverx7i3wCe3z/e+F7AgIEAICbkZSXoJ6WjqGQlI2Eg4eKjpGSmKadpqCfoaGip5+fn5qTjI2KgoiGlJiWlZ+Rj5GdmJGYloyFioyPio6KkIeLg4qVjIaPjY+QiYaKmI2WlJibkqCZkY2YmKOio6u+tb7HxMC+qqitqqmtsaWqq6OcmJeenp6UlI6Li4CA8eju64D4/4GKhY+JjIaDkYmNnJSF+4CPmKWkpp2usqutuKqinqqyurKos7aqv6SYoqatoqGVlpWOmJuOjoyPlYqGj5yNnpmUkpCWnaOZmpyOkJGUjP6E9u/++P+IhpCMjIX/he7h6/iBgpCShoCTnJyUj4yIjoPv6ujf4czY4eiCjZmgs4C3rLLRxrKinZ2xsbK4qbe3orG/wrekoJ6klae5sbCgpryvp7u2wr65ubesq6WklaCes6+1tq6rqq2mrqinoJ2imJacoKadlZGKgYWGlaSspaS9trC2qai2p6Kdsq+sqa2ytrmopKeytNStt8i1r62er7i+na+rnZSNqKmq1t3dzYDQucHS5tfSzs/Iw8zJx73KzbfHu8TNyb2jq5iNkoyYk4qC+//7gf3769vX1szR5+ff69fZ0NrMxby1qrGsusXAxdXc1MO+t7Orq6qstLKyvsOztrmxtry4srO4o7rHzNPKz9TP5eDJx9njztTU39Pb39/d29TVztjZ5ujl6+Xr8oDi2tHO3s/Iy8nK2NLcw9rN0MbFyczMyb2wtqvAzcrO2NXf89fq5tbBzMHBureynqSwr7ulwtO+yq+vrKWWm6jFsr7HyM/My87J1dXTx86+p6+6vLiqnaGbp6Wxt7TAtK+traavq6q0scTU0cbIxc63xsbJu8vKzc7V0tHS1srT04DHzdK+wLy5ytPY0cfc6+/z9P/r7OLf3N/k6P3z4vfd4+LU2tHKwMzAyM7Cvr3Lyri3t8PRy7vB08XBw8/TytPM2c/H1dDX5P763trj9+v57vn4/PaGhIuXnpuloJmTmJalrqahq5WZkZKEh5GTkJeVkpWVmJ+YmZuWkpiHhoaLk4CfpqWPkJianZuqq6y1x8vS1uDf4dLc4u/2/YD/gvrs99TX1c/TzNTX2tPc49zQ1MS3saqno6KfnJOYm5aTk46RlpKPiIGHiISMkJKRjoyJkpKcpqWqsaqloZ+boKWinKa1tbWwpqCUko2Qi5iVmqOinJeZkpKSipCQjp+ZlJmXq4ClpqynsKmuqqWhqqWtraioqa2zrqmhn6yipqK1w8W8w9LNxbu1ta6wqZiUhoKCioKVkJmUkJGXl5WZmJmXk4yTmKSmmJiei4iGjYyEg5GWoaetqqqeobyhoKuqpqKqrJ2fmqKiq7O4vqOxuKWvt7aysqWspaultLOnrK2pm6SdloBwaWlveXhuZ3pwdHFqaGZrcHBvd4N4fnRzcW5vc2tvdXRwb3Nza3FucnNuaW9qam14dGpvbmVhaWxsaWpobGVoXmNqZmFraGtnY2NmdGlvbHJwaHt1b2xycndycnGCeX2EhIKAcnF3eXqAh31+gXpua21vcHdua2lrbGRmv7a4tYC9xWRraHJpamVeamdrfH1y1W51f4OBg3mAg353g3lwbXqCiYR5hIFyhmddbHJ7eXpub2phZmRYV1phZmFZYW5hbm1tamZkZWtiZWZbZm58e+Jzz77HvcJpZ3FvcnDZc8u6wshpb3t+d3F6f4J3d3h1fHPW0tzW4MbNz8xtc3x7joCMg4Scj4NzcG5/fYCFeYSDbXJ+gXdqaWVuYG97eHhtdIN2boF8hoN+fHdxcW1xaHh3iYSChoN/gYJ7gHt3c3Fybm1wdHJxaWxpZ2lteoiPhYOak42QhIeTh4N8iISAe36DiY5/f3+Fgpx6iZmOiYh8iZGYhJ2blop9iYB6lpyXioCMg4mVoJeSkpOPiouIhXmDi3uIf4aKiINwe29oamFqZFxbtsDBY77AvLW+vbCrtamcno+SjZuanpiak5SRmZePkpidk4qGhY+NjpGRioWGhYl1e318g4qJh46Wg5ihoqaalJWVoaCWkp+vnZ+gqJqgpqmpsK2ppayiq6WgpJykroCkp6egraCWmZqdqJ6llJ6aopWWmZaakoqEg32MkYeEg3yGlYCWmIx9ioSGhIN9a3N9cn9qfZGEiYCGiIN4d3+Ue31/eoGCg4KCjI+Og4Z4Zmx5g4d9fX5yenJ0eHN9dHNydXB3eHF9eIaRiXuCgI1+jo6PgYSBhYWLkIyNk4WTkYCMkZGFiYySp6+0r52oraaho6qipaeprbS0tcC5p7mnrrWuurS4scG7w8S4r6u0ta2oqrXCuLW3xby2tLm4r7Wzw7KmsaWqt8/MtbS918fUycbDybtnaXB0enJ5d3ZxdHV8gHh1fXB0bm5iZGtsaG1wa2pranNubnFvbHNoZmZscQN9h4WEdoBzb3t7fIKTlJaTlIuRhJCYm5qgUZ1OkoaUhZGamZ2VmJeVkpOZloeMgXl3dHBvcGxsanBzcHBwbXJ3dXJrZGhlYmhqa2tmZWJsaXR7en+Ign59fHN2d29lcIB+eX1xdHJ3dXdzeHN0d3Vxc3l9f4F7fndyg3t6fXyGfn+BfIuJi3mJhoGEf4WBfn98g4yKiYODkIaIgYmQh3x9hISDfoCEgIWEc3FoZ2huanJsc2xnbHBwbW5qbWprZ21xeHhsbHdnaW93dG9qb3B4eX5+f3V1iXN0f4F6dHyDd315fXt+gYSIc3+DdXd+fXl6c3x7fnyDf3Z8e3xyeXJtgDoyMjQ6OjMsQDg9OzMxMDM3NzU5RDtBOzs8Oz5BODg7Ni8tMTIuNTM9QkBASEA9P0lGQERDPDc8Pz46PDpAOT04PkVGP0hHR0M/Pj5IOTw3Ozw1SEU/OkJARUA8OkY5O0BBQEE1NTs7OTtBOTs+OS8tMDY3QDg2NDQ3MDRhXWFegGNmNDo2Pzg6NjE7ODtIRThcLzI6PTw+NkBEQUBLQTs2PkVKRT5LTkJVOC45O0I9PzY7PDhCQjQ0NTtBODU/SD5GREZCQEBBSUNIST5DQklFfEaBd4d+fkZDTEpNS4xMdFxdWy4vOTgvKTQ4PjY3ODc/Nl5XX1ddRExHRigrMC49gDsyNkpAOS4rKzo2NzswODcjLDg7NyssLTQmLjYzMCYuPTQxQUFJSUdGQzs5MzYtODVCOTc6NjY4PTo+Ojk0NTk0NDg8OTkzNTIsKSkwNDYuKTs4MTMrLjcuLy06NzIuLTEzNisoKC4rPSMuOjMvLCIsMTUiNjQxLCQ2MzFKUUw/gD00OkNMRUVHS0dER0RCOUZOQk1DQ0RAPzM/NS8uKTQvKyxbamo6cXRuZnB1bHCGfXF4Y2Vfa2dpZmRcXllgX1dYYWdhVlRSWFdYWl1ZVFZZXlFYWFRdYl9cYWNQYmhkZVlXWFpoZVtWYm9eYGJrXWhxcXN9e3t6fnSEgXp9cnZ8gGtlZWR2b2tvc3eAe4FygHyGdnd7eHx3bWdpYG52cG5wbHaFb4OEem56c3NycGhYYWtibFNkdmhsYmZlYlVVWnBZW15cYmJlaGdyd3hwdWZUW2Zsb2NhZV1oZWpwb3h0dXN3c3p4dXpxfIF2ZmdoeGl8fn1xcmlsbHF1c3J7bHd2gG9zdmhtbW5+gYJ2YW50cHBze3J1cXNzd3VzfHRmd2x2fXeBdXBlbmVtbWJeWmVnX1xicH1ya3B9cmtqbmtkaWVzaFxkWF1mfXtgWl5uXGZaXV5jVTIyNTg9Nj49PTo+PENGPTlDNDs4OzIzNzc0OTw1NDMzOjY2OTYzOzAwMjQ2gDxAQjU4P0NBPEhHRUdOS0pERkFHPEhPUFNXLlwvXVViVVdbVFJLS0tJQ0ZNRz5DOTIxMC0vMS8vLjM2MzIwLDM6PD08OUNBQEVDREVAPDpAO0JHREdOSURBPzk7PTctMz44MS8mKScsKzAvNjEyNzcxMDQ0NjcvNC8rPTYyNTI9gDU1NjM/Oz88NS40LTMxMC0qLjMxLyopNzAzLzY9ODEwNzczLi0wLjM0KSgiICEmICgmMC0tMjg4MjQvMTA0LzY9Rkc7PEI1ODtDQDYzPTxDRUlJS0FBUD9BS01FPERGOT45P0FGS05NO0RKPEBISkdHPUVARD9EQzk/Pz83Pzw3/HuGeo57AXq/ewJ6e4V6hnsCenuEeo97iXr/e6h7BHp6env/ev96sHrGewN8e3z/e+R7AgIEAICmn6Goo6GWkZKYmI+SjJGOjZaRj4+NlpmOkJGbkY6RjI6GgoaAiIyMlpaZmp2hlZWZkYiTlJGEgYCFiouOkIiIh5SSnpuXnpSLjoyIoIeJh4qEg4yGjJCKiaKap6mztbnFu767sbGlsZ+qpqWunZeWjY2NhouMk4iMj/Xz+f/544Di7oCLjIuZmpiQmZCNjpGOlZ2Wo5KSlpKZnJOpxM3FwtHCwrC0xru4sqSZoZejkJalqKulo6KompiXjYb5iJKKlpKKko6NnJWTk5iVjZCN/ICE/oKD/oGCiIKBhPj4hf717YONh4iNhIH8hY2Rh4GA6/Hl3+je2Oje//6FiJGbmoCor56dqaOlo6/HyqmWmKPI5ezn58yxvLmeu6K8pKugm5Wjm46WmqKftby1o6GjqKfAw8G5wcTCztKtnZOUlpSPkpKVh4aGjpiTi42OkZ6sraGnpLCtqrjAs6aqua2hm6OrnJCqr6O7sba8wtLI1Mbp3tG0qq2WmJWqnKa1q7i3nYCiucHb1dLn4NXEt7mvs8TR38/Uu73Euq+yr62foZ+Vl56SjIH17+vX19rF3MTS3t/s4t3cys7Nw8O+vL3C1Nvn4+fi7N7FxrCtnqGfqbOtr7vBysO9uri2t6yjnKGfqKWqrrC60cK2zdTU5dLc493d1/Dnz9PTztXe6/Hv+vvm4IDj2tHaz8PN1MXS2NHFxMu4v8azwM3F0tLT1c7k3tDc29vo5NLDvdHO0cjFx8XXyMLFy9nI0NrMz8jDuKqenqW5x7/Lx9Td2dPX1NHJwrrDs7Gyq6m3pLGwu7LCucW6ucjAvbG1ubO2vMzI0sDKz9vKyrvN2tzV0M/d5/Dt7t/b4oDS3c3FxMPK0tzj2dPc1drr6v/36ezV2uHohPLu7+Tq4d7t8+nn+fD/5NjKyb/Tx8XHubHDxcTH19LZ29bX0d/b0s7e3+Xt6e7m59bj74D99f6AiomQlZ6jpbCwo52hoJacmZmQi4z4g5WOnKCVlJGVjI6RlJORmqWmo5aVmpOMk4CPlJaRlpigoa21ua+90Njd5+fy+fn884KBioP7geze2te/y8zc1dvV2NPX3Nvc1cWwrZ6gmJmfm5SgnqSipZifl5KMkIL6+YSEgZKNkZaRkJCKj5acpKmfmZKUmaefpa27tbSxurqomJialqSoopyWk42FgoCC94yEjPiGiI6TpoCttbK7rqyzqqelvLGusrqrs7KstquZnqCdn6ClpJupq6mtoKqcqq22p5+mm5uilJGYjIaI/YeNjpmel46IgIqHjYyRhYeKgILy9PeGh4SQjZmSnZeapq2tnZyss6iXoay0m5ymqrS3np6lqLezqqyysKm2p7GqoZ2hqKevoqyqrYB0dXV8e3lpZGdtcW9ycHRycHVwbW1pb25jYmNrZWJqbXBqa29pbm1sbWlnaWluZmdrZl1qb25iYmFlamdoaWJhXGVhbGhkbGhmaGllgWdoaGljXmFdZ2liYXZsd3B4eHiEfYF+en5zgXF5enh/c29sY2dnYWpqbWFmaqixsr+8rICotGNsbmtzcW1ja2lnanB0e4N8h3Jucm5wbmh0iI2HhZSLiXp5in13d2pibmt3bHR/gXxvZmFnWmBiX1yiWWJca2lhZl5daGJeX2VjY2tw0mpryGJguVxfZWJiaMTFbM29t2hwbHF1bm3ObXZ0bWxsytLJwc/Gw8zG2dZtbW9ycoB7g3Nwfnp6dYCWmn1xcHOPl5COi3ZodHVkf2uEdHpxa2RsZl5paHFwf4B4amlwdHSIh4V5g4yOoqCEdmtra2pjaWprXl5fY2xnZmdsbXmAgHd6d4WEhJafmIyPkox9eX+IeHCFinuLgIGCipmYppm5rqeTkJiIjYaMfn6Dd4J/bIBziY6knJWjnZaOhoaAgoaHjYCIeoKGgHh5eXhwcG9qbnZuZ161sq2osreuvaWpqZ+ilZCWipecmJqfoKOvvLm7r6SZn5qEiYiNf4yOiY2DfIGBjo+Lj5WQl5OLgomGjYyRjImUnY2DkpSZpYyUnZeZmrm0p6inoaGipKCgqKucnYClpKOinZidpZWdpZuUkZKIkJaMl5yWm5aWko+knIuWjYiRiXxwdYiKkIyMkY+ZiIGEhJGHjJaLi42OiYSBfoCQk4eKgYmOioKMhoiGgXuJenh+fH6OgImChnl+eH52dIV7f3N1fnZ8gZGFinuGjpqQkIOSnpyZm5SfpqOdmIuNkYCOl4uOi5CdprG3qKCkmZikoLOvpambpqq0ZrmysqmzrK67wbCrt7LEta+sr6u6tLe6sbDAuLu7vbe6urO1s7y2rJ6jn52npa6rs6mzwmrOwsVka2drcXp6eoJ9cXB0dXZ9fH10cnTGZGxhZ2RiZGZwaGtucW9scnt6eGlnbGhnboBvd3hzd3N1dXyAhHuHlZmfpqSnq6ysoVNQVU6TS4uGh4yBj5GflZqTk5GPkpOQjYN3dW1xbnN1c252cnh1dnJ5dnZxd2zOxmloYm1mZ2lmZmZlbXN4gIV8eHNycnlubHCDfnx8goBpZGVnZ3J3cmxsbG1vcHJ24Hx0eNd2eXt/hICEhICFfoONgoWBkoqJiZCEioeDjIN1fH59goKGg3V8eneAd4B6g4OIfnSAfX2Dd3N2a2hoxGdqZ29zbGpmYWdkY2NpX2ZvbG/S09FrZ2JoZGxlbWtxeHt6bG9+hHlwe4mVdnN5eoKDcHR2eIZ9c3V7e3WDeoB9dHFyeXZ/dHx6eoA+PD9EQ0M2MTY8Pjk+Oj08OkA5NjUzPT84OztDOTQ2NTQtLTIuNjk7Pz4/Q0VIQENFQDdERUQ5NTM4Pj1AQzo4Nz86RkI+R0NCRUVBVjw9Ojo2NTo4QUI9N0Y9QztAPTtEPUBAPkE4QzI4ODk/NTMxKy8xKzU1OS8yNUdQVGRjUYBLVTI4ODc+Pz42PDs4OT07P0Q6QTEyNTM4NTI/UldRTlpUUUVIWU5ISDgwOjQ+MTlHS0tDQD5DODw9OzVcOD46RkQ/RD89SUVDSEtGQ0REekFHiUVEhUJESUJAQnZ2QnVeUDI2LTAxKypLKzY5MzQ1X2ZdV2RYVF9UZV4uLjAyMoA7PzEtODQ1LjhJTDMjIiY9REFDRTcrNzonPy5EMzguLCYuLSczNTs6R0dBMzM6OzhIRD40O0NFTUc9MyoqLS8rMjU4LissMDg1MS4uKzA0NCktLTY0Mj1COzI0OjcpKTE6LCQ3Nys2LSoqLzg2PTRMSUQ2NjkqLyw1LTE8NUM/K4AtOz1MRUVQT0xGP0E4O0NJU0tTRENDPDY7PT41NjYxNkI+PDhtbWtjam5ofGdye3V6bWJmWWBhXmFhYmBlb210bWtlbWZSVlRXSlNXVVdRTVNVY19aX2NeZF9YUllUWVNXUlBbZ1lPX2FkbVlgZl9kaIeDeXt8eXZ0fn55f35oZIBnYGNtbm13g3mDhXVvcnVtdnpvd3lxd29vb2p+emx0b252cmRYXHF0eXFwd3eAcGxubn1scXdvbG5tZ2BYVlhlal5iXGhvb2pybnJwamVvYWFlYGFwZG1rc2x0cntydYh/hXl8gH15eINydGJocYF3fHB8ioN3dWx3fn16eXJ1e4BxeWpra253fIWIcGdrYGNzboJ+cXJhamtwRHFqbGRvaWx6f25oc2t7bGNgY15tZmZoYV5uaWxudm5wcmptaXRtZFpfWVhjXWNdYFBUWzNhUlkuNC8yNDo5OUI/NjU4ODc6ODkyNTpZMDw0Ojg2ODpANzc4Ozk2Oz88OjI0Ozk0NoA0Oz89QUFEQ0dKTUZKT0xJS0pQV1peViwrLyxVLVRTWV1NVlJWS0xGSEZERENEQjsxMSktKy8yMCsyLzU0NTE5Nzs8SEKFhUtJQEpDQ0I+PTs2Oz9BSk9FQTw8PEI3MzQ+NS8vNTYlICElJjI4My8vLS0rLC4yUjYtM0wxMTI0OYA5PDhBOz1EOjgxQDcyNT0xNTIuNi4iJiknKy0wLiQsKSYtJC0mMTQ6MSkxLy82KygtJicpSiwxMzk9NzQxMDc1NzY7MjlAPUFzdHQ9Ojc8NTwzPj1AR0pLPj9NUUM2PEVNNjVARE5NOTw/QVBKQUVLSUFLQUdBODU1OzlAOEFDQ/p7iHq6ewF6knsHent7ent7eoZ7Bnp6e3p6eod7AXqGe4t6/3uqe/969noBe7N6BHt6enqVewF6sHuEfAJ7fKh7gnqvewV6e3t7erl7AXqTe4N6tXsCAgQAgJ+do6GrpZqHh4iCgYiNlYqOj5GMj4aMk4+NlJGRlpqSjo6Mif2C/4eSm5+enpuamp6UkZeZkY6LhoCKk5WSj46JjZCMiIaRiIKE/oeOiYqGk4aH/4OGi4yLipOhraOlpbnEt7Cqtrenr6Soq6Cjp6iptaaimpWYkZScmIyMjY+HgIWFiIaGgImWlp6al5SijYCBg4CNiaCPjJ6hqLCwsaSto56tr5elqbGknKCdmqWSl5qTlJ2anqmipJqWj5Oin5GUlI6RlJaUkIyQi4uNjZGGkYqEh5GNiYOBgoOC7vCDgYKGjpGFgfCG+ICfiIyHg4Hq4fDg3ezl64T6/4SJgJaUgI6kta6ss66jsLiv6bi1qrHNhovau66uqZycr8GxsrizsKWfpKCYoLe7vLOvoKSsnLy6yMbJu5yMk5etp6Cfnpmil5minpuhoZWdqJ+Rhf6DjpydlKCRoqSck6Knraiolqq0qKarpLisrK+pvLOupKqdrKeft7avs7vfzcuvpbq5gKbCwL2hr8DFtbq5xcqyscvV6d3MwLvGw7y5raWfnpielIL5hIuFh/3s3dzd2dnx64H53fj/+f+Cg/7+7Ovj2dnd6ufp2NDRysCtwrO4uK67t7e5vrbBvcW7p6q1t7Oyq6erra7EucfW3Nfa4+Tg6eXy7PDg79fT5uLs5viBiYD9gPvw4+Xi4uTc5ujnxc3O3cLSx8a6zdTX18HXztDI2t7X5ODw7OLe4s7Sx9zQ09DFx8DNzb3GzcfPv8u6ta/JwszR2OHT1dfT09PEzcvRzsrLy8rMvLaqu7TJyMrIzMrExcnWyM2yxLjFztDR2+b47fXQxs/FytPUz9fX5uvr6eb4gOXs7uHy4OPi4dje4+7f4uzkgvr29Ozv9PiC/YP1+uvq7eWE+fbr5t/U0MbMwsvAw8TIy83P0czS19/T7+zg2tzWy77L1dzl5ubq8/38/v2EgouioqizrLKtpqqqr6ShlZibk5KQhZSYmJCPkpKMjZaVlJeWlZ+yrKihm6Gel5SYNZ2Zlpebo6ayubawus/P49nb3+Hm5vbd1N/4jvGC8N7d0rfT5eTp5N3a3dnOx9G+yLCnnpeehJaAo6qxtrKuqJeHjIiA+YOBh4T9hICDhYmNiImOkZKWnJ2YmqGio6qqureysaq0sLSwuLG8urSwoZaMhYeLgIOTiI+IiImRk5ymtryts6a4trevqp2gop6inq6qpK+psa6hmqCltq6qn6Owoaafn6GkoZ+VkZmXkpmXlJePlpSUmJZHhov8/fuAg4qNkIWDgPT09Or18fT+hYWJjpSSkZiWjouJhpSQiYmRnp+WmpGWkqagn6GfmZintrK2qKaonJ+Vn6mljqGen6iAc29xcHt5b2FibGhqcHV2amxrbGhvZWhuaWRpZWRka2lnam1wzWXCZGVmZ2ZlZWZobGZgaG1pZ2pmXWNlaGZlZWFmaWVkZGliXWLBaW9oaGVwZGS4Xl1gXV1ZYm13cG9vgId8dnJ6f3F5b3V5cXZ6dniEc3NybnFsb3dxaGZpbWWAYGJjYWJbX2llaWhpZXtuaGhtaG9oemtpd3p8f3+Bd312cHx+Z3N1e3FrcHF0fW1xcWpmbGNla2RqZmRiZnJwZGdoZWZnZmVjXmRgX2Flb2dvZV1cY19eWlldXVy2uWdmYmRtcGxt0HTUbIRtb2xqacfF1MPFzMbJcdHUaG1hbGmAYHF+enl9eHF4fXiWg4V+gJBcXY53dXt2cHF+jX54fHh0bmVqcmhxhIGAd3drc4Bzj4yUi42EZ19nboZ/eXl4c3pva29qamxvZ3F5dm9lu2BmcW9ocml+fnhte3qDfX5vgYd+dnp0gnd3e3eKiIiChnqDenCEgXyChaKSjXVxhISAd5OVlHd7hIZ3g4aSln15ioeJhXx6eoaHf3txbmprZ21lWbRjaWZmw7+1v8K5r7qwXq+XrbawuWFixcu6wLiwqquup6WSiY2RjoCcj4uRhIyFfoaLiJWcpJ+WkpeVjY+HgoGFhpOEkJaYlpWXm5mdm6mfram3qKe3sLSltV1iW7eAs6uloqGimpOdmKSWmpudhpaIj4aUnpygkKOeoJujn5CTjJiblp2jlZyWq5+inJGQiI+Rgo2Qj4+CjIJ+fZeQmJidoI2TlY2KjHp/gYiDgIeGh5KDg32LgJOMhIaEgH6DgomAgWuDgo6ck42QlaSdr5aRm5CTnaCZpJ+kopigm6GAlZWWlaGbrK6wq7CwuqilqaJarqOgoJ+rtF+1Yqurmp6im2Kxs6mrqqakpK+usrCyrrK1tbG2sbS3vK3Ew8G+wbmvnKCep6urrbXByNDU1Gxkanl0d4OBg4B6eniEenh1dntzbW1kcHNxZGRhY2Jjb29sb25ucHx3cmtnbmxnaWyAcnNwcnR2dn+Fg36FlJSjn56dnpyYoZiUlp9XkE6Tj5KRgJKhnZycmJKXj4WDinuFdnJwa3h2dHR0eH1+f3p6eHJudXRv02xma2fFZmNiZGdoZmZrb3BzeX95eHp6dXd4hYB+gHmBeX95fnd+fnh1bWpnaG52cHGAeHt3eHR5d3qAfIqPgIiEkY+Th4N7goCAhHuHgnuCf4eHfnqAgYyEfnV4iX1/enh2ent6dnd9enR5eHN3bnRvb3FvYmnExcdmYmRlZ19maM/Qzb/FxMjLZ2JiZWtram9saGlsaXZxa2tyfn9zdGhrZ3l3dnV2a2d2fn2DeX6Be3xyd4F6bHl2e3yAOjY8OkRCOi0vODM0PUFEOTo5OTM4Mjc9Ozk+PDk4OzcyMzY3XjFdNDtAREdIR0dKTEZBR0pCQEA6MTo/QkA8OjU6PT08PEdAPUJ8REU8PDdCOjxvOjs8OjcxOEBIPDo1REpEQEBLTT1FNDk7MTQ2NDY/NTc1MzYwMjo3MDE2OjGAKywuLS0oLDg4PTs+Ok1ANzc6ND45Szs3QkNITElMQ0pEQEtLO0RGSkE6PT8/RzU7Pzw8REBCSkNGPj03PUtIQUNEQUJGRUVFRExIRkVDSEJMSUVCS0lIRERHRUJ2c0A8NTU7PDQyUTJNKUAuNDMzM1pYZldfZl9hPGNiLjImNTOAKjhCOjU2NCszPDZNPT00NUMvLUE3NTo3Ly88SUA6Ozg3My43PDM4R0ZGPz81OkIySkZLSUpFMSovMj84MjY7OEM6OT86ODg4MDc8ODAkOB0hKywnMSg7OTMnMTE3MTIlOEI7NTQtNywqKyYzMDIsMikyLyg7NzQ3OVJIRTQyQECAMUJBQCw2QEM6Q0FMTz05SU1TUEdBO0RFREdAOzU2MDgzKVk3Pz5Ad3Nsdnx4d4yBSoVldXpvdUBDhot4eGpfW15raW9fW15gXE5nXVxfVV9ZVl1eWGJnbGdeXmZnYWFYVlNVVmJTXmZpaGlsbGdpZ3tvfXyKd3iCdoF1gUJHPXeAdWppcXd+gX6LiIVqcHZ+bIF0eGlxdXFvXnNvcW9+fXR7dH59dnp/c3pwhnt+eW5xa3N1Zm5vb3BjbGFdWG5jaWpscmBnbGptb2Nqb3RwaWxqa3RkY15vZ316dnp4cXJ5fIuGiXKIen+Fd25zd4uLnYR+hHd0dXVpdHF3eHmAgouAeXl1b4N4hYWDeXZyeWlocm1DgHhybGpvcTxwPGpsYGJlX0BucGRkYV9bWmRiZmRmZGhtbGpybnJzdWZ5dG1pbGddTE5PVlhSUVZZW1xbWi8pLTozNT06Ozo4OTlCODczNDs1NDYvPEBBOTk3Ojc3Pzw5Ojg3OEA7ODU2Pz87PDyAQEA+QkJFRUpMSENIUk1UTEhHS1BRXFVUWGA2VC5VVFpZSFRYUU1JR0RIRT07RDg/MS0pJC8tKysuMzg8Pjk5ODMzPkFAgkZCR0B1QDw6PUBAOjk8PT9BRkpEREdFPz05QTgzNC02NDc1OjpCQz88NjArKS42LSw4MTMwMS4zMDGAMz9ENz83RENEOTUuMTMyMys0LygsKS8vJiIqKjUwKyQnNCotKCYmKSkpJCYtLCkwMC81MTo5Oj87MDZcZGs2Njs8PjY6OnJ1dGx0b3B0OTQ1Njw5Nz8/Pj9BQUtDOjU6RUY9Qjo+OkdEQEFDOTdGTk5QREZIPTw0OEI9Ljs7P0KkewN6e3qjewF6iHsBev97iXuCeoh7A3p7eod7iHoDe3p6lnuCfLt7AXrTewF6hHuJegF7hnqCe8N6g3v/epN6AXuHegN7enuGegF7rHrLewN8e3yoewF6hHsBevB7g3qIe4h6snsCAgQAgISGlpOJkJSQiouO9f6Ij4iJiIiGiYGDgvb5i5SUko+HkIuSlYiMjZaamZegpZ+ZmZ2dl5qalI2djZKLkIuNg4+Lh5GFhpWJ/YWKgIGDg4OFhYeJioOBh4qPkJOepaqrqK/PuLeys6+or7Svnqq9rKGvqrm0sKqrrKaopJuWnraZgJ+WlKSkk5mMqraYiYuWkYSPiPjzio2Oi5WWh46Tko+UnqyQkJOMhoWQmpSWkKKej5SqnZyZnaShmp+hlZSLio2QkoaIh4qIi4qEhIyFgoWEiIL7gv6Hg4OKiIH09vT+hJCHjpOQiIqHhon9h4n1+u/j4crJudHS6Ov3hYaB/IqXgJCgoaWuvsGuorvAv6ympbOvwtLPuaeTi5GSkKy7sqiVr+PN28nH3Lq4scC0qKintK3BurC5qaSlh5y1x8GbnJL+hI2iqJOhqLOqs6GWkoSMmquqway1nJ2YkJqilqGSlZCVkZKmpp+jnZeenZigl5Obm77Tp6usrKS0pKGnodDNgOjZw8W0vbGdoKa6r7eur6Sor7G9yNXu4OHDyMKorKumlY6NjJuXkIX68ubs6ObrgIiHhYuChoKIg+718NDY5d3z8oDs7N7Ly8i+xbjAzMjT6OHCwMbDv8C4pamprLG/uLO2tbjIvs3V1tbQ2d/k8Obl9efz8uPs8N7r6PH6+/2EgPXs8vjw4OTb7+ng29fT3tre2tfR4Orw/+bs6d7N1svT0c/g0svI1c/NwMLMt6yywL+5vbrCw8+7u7+2ucWyn7a5ztLY29vd497Z0czJys3d4tHJysDFx8nSztLJxMHFxMq/ube5wr7R2+Ts4NvLyNXKxsPQ2eHg4ezsgvuC/v7ygPHu8/fs1On67vP1/PD5/fn3gYH65d7m8Pj59Pf8+ujs3tfd49zZ18/S19PWy8q/wMjNzsjUzM7O3Nrg8PTi5eng39779oCDhoKAioaOjZGZmpaytqKiqqOeq6mqpaKclpaVlJ+Zl46KioeGiJOQjo2Dio6inquvp6yytLeio5+dgJ+fq66itrO1tLq7xtrU2d3S1M7u4dbc19rn4dzl19TQ0cjJ1dnQ4tLX18XHxs3FwaaYjYeDjI+VoZ+osLnBt6+hlZqSiYmLi5eMjpSIiYuVmI2Sjo6Pi5KQjpKboKenrKu3wLnBtra7q7i5w7S+uaidl52UoZSJl5efnZWel5afgKado6enqKSpnZ6epKCnnKKlq621tLawwLC3sL3LvaeXtaempKirnaWeqqGZn5+jor+vuayzrp6opZWQioqF/YiQjZKH/4T39f7v/PvogP6GgoGKiYGRj4mF//Hs//aAiIiChYH5iI6KkI6NkY+GipKQjpCMhoyOj5qXj4OJgoKIgGRmbmliaWxqZW1uwMZrbGVlY2RiaWNkYriyYmdpZWhmbmpwcWVlY2xrZmBna2ZiY2hnY2hsbWl4a25obGpqYGpjZW9jZG5fsV5kXWFlZWNmZGRlZV5cXF5jX2JucHNzbm+Md3d1eHpyeX56bXqQgHV+fIR/fnl7f3p+eXFqcIFpgGxkZHFyZGZZcXtnYmVzc2lya7yyZmtvanF0Y2lqa2VpcH1kYmZjXFtmcGpsanh2aWp+bWpjYmdgXWVnYmJaXWFka2NjYWVhYF1YWmRfXmBhY1ywWatdV1tkZF60vrq8Y2hbYWZlY2hpa3DJam7CycPHyLK1rL++zczObGxmvGNvgGFua254hoR0bnyEhHZybntzeIWKeXFmZGptZ3yDdW9jcZODin+AlYR+dX1za2xwfHqJhHyGfHV8ZXmPm5Jvc2uxXWV1dGRtc3x4gHNvb2Foc3x6jHyIeXl4cHl5dnx1eHF3cWt3d3FxbGxzdXV7cnB3c4+fdnZ8fniHenZ7dJmVgKqhlZeGjHllaG+Cd4J3eG9yc3J1fYignZ2KlIx6fX15amZoaXh1bGG5t7C4ta6tW2BdXGVeYWFnYrC6uaaqs6Oxp1mlpZiKk5WPm5KUmZSYoJiBhIuQlZaVi5GMkJKalY2Ih4uTh5WZlZuQlZ2kqKKmrKW0trC+vq6yrLW1tLVegKejqq2so6KesKSjmpucn52glZeTmJ6epZagpKKbpKGnnp2kl5CVpKOmnaCklouPnZmOjYiMi5OBgYSAh5OBcoiMmZiboJqZm5aNg4F/f4OSlYmGi4SNjoqWjoiGiYiOkJWMiIOAh4OOlpCbj46IipiRi4OHkZaWnKekW6dTp6WUgJ2bobGikaixrbCwuK2ysq6sWV2xn5icpa2up6uyqqCnnZmmrKunqKClq62yqqunqK61ura7uL27ysXG2NrKzcq7ubXEt19gY2Bfa2hvbnJycG2DgXV4fHVweXl8e3p2cnBybXZzdG9ua2VlYmZkZWllamp3cnZ4c3F1dHprbnBvgHJyenxyf32Cg4aDhY+OkJiUl5WlmpGcmJyfl5KXj5KTl5OWnaOZqJ6fnImIiIh/g3RubGxsd3h5gHp+gYWKhX52cHVybW1sbHNsbGtlYGJrbGVtbXB0cnV2dXh8fX94fX6Jj4yRg4KFd3p5fnR8e21qaHBsfHNtfX6Dg4GHe3d8gHlyfH2CjIeEgn98hoqLgIODhIaKiI2Ilo2PipKgkoB4koqHhomIen55gXlzd3p6d41/iX2AfG56e3R0cW5rwmVpZ2piv2fGydS+xMOzY8BmZGJnaF9vb2xs1szA0cdma2xmZ2W6Zmxmbmtpb2pjZGpqaGpqZ3Bwc3V0amVlY2dngC8yPTgvNDYyLzU5WWlAQjw7NzQvNDEzNGRlPEE/OTgzOjY8PzQ1N0NFQT5IS0dCREdKRklJRkFNP0I9Q0JEOD84Nz41OUc9c0RLQ0RCPz0+PUFFR0I/Pz9BOjpBQkBAOTtXRUhHSkc8Pz85LDZFOC03NkA+Pjs8Pjg9OzQvN0cwgDIqKzk8MDIoQUo4NjdFRDg/OFVQOD5DP0NGOT07PDk9SFg/PUM8NTI7Qjo9OUhFOj5TR0hGSE1IP0RHPj85Oj5CR0BCQUJDRkdHR0xDPkBFTEqORX1GPkFMSkJ2fXVzPEEzNzg0LC4sLDBLLTRTYGFlaFRZTl9cZ2ZlNjQuTS88gDA8Ojc6QT0vKTxBQjQxLzs1NUBAPjMoJSssKD5HQTwtOExFSj5BTz49OkQ7NTc5PztHQz1HQTxDLDpESz8tNjVTMDhHRzY8PkU/QDYxLSIoLzk2RTc/MDIwKC4vKjErLyszLis4NzAuKScrKyouJiQqJkFMMDI4PDRFOjQ4M0xIgFZPSEo/ST0sLjNEPUhCQz1BQDw9PkFTT1NKU00+QUBBODQ7PUxLQzttbm55dnB1QEZCP0U9Pz1EQXB7d1paXlBiYjtucmldZmdfZ19gY11kcWxXWV5hZWRlW2NgZmhvZ2FcWlpiVWVlY2lfYmlucW92f3qPjoGQjHR/fICDg31BgG5lb3t/d319k4V9cXBwdnV6cnduc3h0emd0endwfXaAenV+cmlufXx/dHiBcWJmcmxjZV1jZXFeXmNfZXFeTmBgamdpbGhsdXNyb3BubW96fm9paGJtb215eHl8eXaAgIR/fnt6g3d8gHd9dXJwdIN+eG50fHt5eIB5Q3xAhIV3gHl0d4V7a4GOhIR5e21xeHp7REmNdGtwdXd3b3J7dWtyZWJqa2lkYVlgZWdvaWdlZ21yd3F4c3dzfHZ2g4NxdHNkX1toWjAuLSgmLyotLDIyMCw/PDE0OjUyPjs9Ozc0MzI1ND8+Qj08Pjw7Nz06NzkyNjVAOT4+NjU9QEg+QUI+gD9BSk1CTkhKSElITVVRUFRMTk5iWlNXVFhbV1RYUVNVVlBPUVFES0JFRj0/PkI8PTErKScmLjAzOzg+REdMR0A3NDw5OUBAQkpDQ0ZBPUBHRz1EQUNGQkZEQkRHRkU9PDlAQj0/NTg6LTM4PjQ+PTItKzMvPzIpNjg8OTY6LyswgDApNDg9Qz08NTIvNzU6MDIxLy4wKSsmMCgtLDRAOCohNzEvLjExJiomLikkKSosKj00QTtBQDdCQzs5Njg3VzE5Nzw2ZTlrcn5sdnNiPW8/OTU7OzRDQkFBgHhteWw1OTg0NTVhPEA4QT08Qz42OD89Ojw6Njo5Ojw5MSotKzAyi3uCeot7gnqtewF6ynuCer57A3p7eoZ7hHqLewN6e3uNegR7e3t6wnsBeud7h3qKe4l6AXu8egF7+noDe3p7lHqCe7J6/3v6ewF6hXsCenuHegJ7eop7hXqGewF6m3sCAgQAgIWKmI2SioyOjYyF8Pb4/PWIi4eDhfP8iIGHkJKak5SJjJGWjo+PlpmdnJujpKCXlpahnqCel42Qj4+MlPiDhomIg4qD/fL98fWFhZWCgImGkoeMlJKKj52WlaCVp6GborO/tLazs7XCtcWkpaGuq5mnoLakp6uhqLCys7ausKmxgJ+anZiYnKWiqKeWlpyflZGF+/D3+oWIgYb0/oSB94mEjZaKjpmRjo+Slpiak46KjZGWmZSZnJ+io56ZjIeNgYmNiYuIi4WBgYmEgfv48vz9g4X/goeCgv2B/fKN9OXphp6ThI2FkJGUmYiNjICJ74vw7v3e6/Ph9fiEi5efpqCkgKKgqaGQlbGWk5yYk5adnKKwvsWsoaieoqqik6qtkZuPkbaku7q5wsLQrKmqq6axuK3DxrWppoyBjaXR18u+qbSnjJaZlZCXkpOgopyKhYO0taWcs7yd/ZCKhuX7hpySnZGO/oiSlZabmK3FrrKjmfiLmaacmrfIprKgl5qzwLi5gMbIt7fLxM26uKqeo5uaopyhmZmWpL/Ezc/EzKyqoqGWgYj7hIGPm5+QhoD05t/8/fqCgICBhID88OTn6vD384yNi4qAgfH13/DRydHOxsXJ5ODJxL7Au8K6tKKoqrK8qLu5vre7wtHP2tbc4Nri7uTr9ODw6ubl3+br5eSE/vGAgPv68fbp4unu8Pb37uHl6e335/z2/PPw5Nvb2d375+Hc3+Hf1NvPytXJxMnOv8fRycXMzba/xrusr7i1srnF2dPb0crW2tXf3trH0LfRxcnAzNXO1t7S1djc3ODBx8TM2c7Z1uLe69ze9PDm8vn18PHU1NXX6OTk3eDr9fX87vPogODo6OXn5+Xu9e2F/O/r5t7p6Pb6gPP47fT08vPm4dzX397S49zk1tLQ18/Oxci9ucHLz8fM0MfN2eju6PTo5Pfqgv+LmJKTlZWWnZaVnJCanZqjrqajoqmqqK+upJKSmJaXlpSJjICK/YOIiYuDiomSnKSip6yopr24sLiwqaangKmns7a7u7u+u8nS1dPJ4NHY1M3j19jc3djb0NXP0c7b5dnc2erg6ePgzcfCvsnDu6einJiYnaGdoaSlr73At7qxm5KVkZeVjpWXmp2Zl5KWmZCQl4+LjYiQjpKOkJydlqCotsq6trbBuLe9vsG4rbKlucfApaScoqWhi4iRl5iigKyqsLqqsK6srJ6hnYahlqSjvcjX18XCuMGxxdm4r7SlmaCjsK+xnKKfqaCaoqKhocC1tby5s62/pKOhnJmOnZmgmZeKgfrvgY6Bifz57vfmgYWIhYeLi5GYkYP6+vP09/308Pz+//+Hh4qD/vje6e/w5+Tv3ev95feH+PaAgP+AgGVnbmVpZmtvb3NuzcrNx7hkZ2Zna8bGaF9eZGRsZWtjY2htaGhtcHR0b2ltbGliYmZuaG1rZ2Foa2xna65aYWNhX2Rfu6+2raxfYHJiX2RdZVVZYmFbXWhiXWlmc3BpbXiCd3h3dXeCd4BsbGx9e2t0b31xcnZsdnh9f4Z9gn6DgHZzcm1sb3ZwdXZna292cG5iuKyvrFxgWV6stmFgvGdja3FkZm1mYmFnbHBxa2ZgY2FnZV5fXFtcX2FhWl1iWF5iYmNjZWJhXWRgW7Gtrb25X1+1WFpVVrVcvbBjvKqnYXBlWlxYZWlueWxwbmNrumjO0ePE0djBzMtoaXF3enF2gHFxeXFkZnljZGVnZ2lua2pyd3ppY21vdn11Y3d1W2lhYoJwfXt5goSVd3VxcXB9hoSSkYR4d19WYHKboJKFeYV4YWhoYF1iXWN0eHhrZWCJi3ZtgIR0wHBrZq66Z3l0fXdzz2tzdnBvbHyMfIF4crVpdX51boCFb3hqZWV7hHt7gIqRiIuUiYd4cm1pbm5rc21uaGVdZHV1gIaEjHh5dnVuXGbGaWZwc3RoYWC+uai+u69aXl9fY2HDurK4tr2/t2dmXFdTVJyomamYk52ZmJmbrqWUkIqamZubm4qLkJafjJuTk4mFhpKNkZSYmpaiqaGnqJeloaOmpbC1sbZnwK9dgK+yrLGrqaWpo5ukrKaoqqqunbKpqaKjlpKVlZzGpqCfmZyYlZ2bl6Kin56elJWdmpWanI2Sl49/foaHgISIlpWakI2YnZignZmEjHeOgoqBh5GKjI2Eg4OQkpSFkZKXpJWal5mQmo+PppuTnaCcn6GNkoyNnJiYlZakqqeprLGigJ6eoaOioaGjq6VetqyppKGqp7azWqKlm6asq7Knn56bpKSfr6uxp6SosKyqpaykoauzvLO1u7G2wcnHvsy+usi7ZsZpcGlrb3FxenRzenB0dnZ8gnp5dHh3cnl2c2xxeXh4dHJpbWFrvl9mZmhia2hscXJxe3t3c4J6eYF7dXFxgHBxen2DgYCDfoOJiIZ+j4eQjY+ilY+QlZaZk5uYmZSYnpOWmaigqaKckImJhoqBfXBsamZocHRzdnl5f4iKiIaEc29vb3VuaG1tbG9xaGhraWNocG9zdG11cXZydnx6dHuBh5aJgn+Jfnx/f4J6cnRtgo+NeXp2e39+cnd8fXp5gH53foR6h4aEioODhniIf4iCjpWfnI+Qh5GGlqWLhYuBfIKEi4eDcXt9jIWChYF6d5GBgIWAenKBbXF3eHZteHN4cnRtacvFaHFkaMPGvcSnYGFfYGNoZm15eG3U19Xe2eLTx9LLzcVoaGhfwbmirLC0qqe5rsDKsrxmtMNiaM5lgDAyOTIzMDM3Nzs5Z3F4dWg6ODQwMlphODQ0OjpANjcvMTY7NzY5P0RIRUFISEU/QUNQS01KQztAQkNCS287QEA9OT86c2l3dHtGRVRCPkVASD1DTUxDRExEOkM7RUA5PEZRR0dFRkNMQUUyMTA8Ois0MUI3ODwxNjg6PEI8QT1CgDYyNDExNDw3PDwuNDpEPz41X1NbXzc+OTpodT47dEE+Rk1BQk1DPzs8PkBDQD05Pz9GRUJIR0dIR0dGPD5EOUFFRUZFSUVHRU9OTJCLhpCNSEmORklCQoZFh3hDe2RdO0s+MTMsNDM1Oy80Ni87Xj10c4NmcHZfaWY0NDpAQz1CgD49QzgoKjskJy0wLjA1MjM6PkEyKzQ1O0E6LkVINDovLkQ0Pzw6QENSNzUzNTI7QT5JSUQ+RTQrMTpOTUA2M0I+MDo+NzU4MzVAPTkrJCBFRDYxPkI2RTEwKjU+JjQvOTMzUCszMy0pJTNCNDkzLTEnMDoyKzxGNDw0MC5BRz05gEZJQUJNRkg+Pjs4Pzw6QT1AOjgvNkNASE1MVEREPz45LTdpOzZCR0c9OTp1cGJ/e3M8Ozo5PTx8dm5yb21nXDg5NTU1OWt1a3hjXmliX15gc29fWldiY2VkZFhdYmhxXW1laFxYWGVdY2ZoaWNrcGp1eGp9eHR2bXB4dHNIhnE+gHV2dYODhYyVjoeFgnh8fX2Ccol+gnh1aGFoanWri4eFg4F8dXt3dYF9eXx/cnB2cGttcF9kbGdZXGdkXGFmcm9zZ2BpbWdyc3BkcF90aG1hZWtgYmZiZGh2eoBtcWxxfXJ/g46Jl4V8jntve32BiI58gnt8in56b253d3N5eIF2gG1ubnF3e3yCiHxFfXFtbW15e4uNRXh6b3V9fIF7c21ocW5kcmxxZWBka2draWtoZW91fXBvdWhqb3Z0aHNjXmtaMVoxODExMjMyODAuMyswMjA2PDY2NDg4Njs6OS4yPTw+PD85QTY/ZjY7ODkwNjM3Ozw7QkM/N0RAQUpGQ0JCgEJFUFRVUExLQ0dQUVJMWVJWUVJkWVVVWFdXUFNPT0xRVk5PTlZLTUtHREBAP0Q/Oi4rKCQnLzM0ODo7QkhKREE9MzM2O0RCPkRDQUNFP0BGRj9CR0BBQjlBPEE8PkNANTk8PUc7NjU/NTQ6Oj44LzIqPkpJNjcyOj49LC40NDAygDgzPEM4Qj88PjM0NCQ2LDQtODtBPTAwJy4nNkIwLjQtKC4xOTczJCsrNTAuMS8sKkA2O0FEQz5NODw/Pj01Qj5FQUI8N2dgOUQ7QnR3cnlgOTo3NTY6Nz5IRz1ydHBybnRpY3R0eHM+Pj42b2hSX2RnXVltYW12XGQ3VlwwMmIwi3uFeoV7gnqlewF6h3uFesh7hHqEewV6ent7eq17hXoDe3t6hHsIent6ent6enqPewJ6e4l63XsGent7e3p6hnsBeox7AXqyewF6iHuGeoZ7iHqGe7d6BHt6env/eot6AXuJegF7rHoCe3qlewF6/3vde4J6hHuFeot7jHqEe456B3t6ent7ensCAgQAgIOMk4+OkI+XkZCPhoiNk4mBiIeHgoGKhIKHgYyMjI+Ji5GalJucsqejqKSmo56hmZCVk46Pk4qLi4yHhPj9hYaB94Lv+vr+hIaFh4+OioiDiY2MkYuBiJOSnZuYnZSToKCbm5SPl5een6yjqK+ZqKats6ydqqqPmaK0qZ6VpaGXgJelo6KQiI+XnKOZl4yVkp2ShvyA8vL56NjX5N3f393X3ebg4eqFi5ORj46MhYeHkpSfoKGbo7Ssr5eVi4qNiZGRkImHg/3v/P/2hYGGhoqNgv7+jYry9PP38PD119Luh42TlZKIjIKHieXw8eSA6unTztrS8vj4h4qLjpGRnpuTgICKh4mOiYyOhpqfkZinqJyqw7ign6SQlp+rsbG1k4qYmajDztTLz8fEtaGkprKztreuoaCtoKfJyNK+x8q5n6KRmoeUko+Ri52B6PeOjZCLk56ho6eJjpCNkYiXlpSThomJhp62sb6/t7O9trW7yJSbu7GmspWxnLK0qKCno6+wgKydjpGUma2mm5uUnpWemqmopqiyubi8sqibrJ6hmJyMhoTpg+SNi5eQlI6M7O6LhomQhoL++uuD//GF497n2uvw+oKUhISC/oDy8+zc393j0tvgyszEq7Wno6vErtbi39Xc2t3j6+Th2OLQ3eXm7Pbh8/Tx9d3Y4t/W6t7k7fX+gIGB74Dl2OTn8fnw5ujq6ebm3+Lt9fz/8/Hq4OT35+Tg8O2H5tbJwMfBvM7FwrjCyc/IxMLGxLG6trS4v8Cs1dTj1+vc5d/c19TLyeHU2s/L0eHg3+LV8v7u2eq8xbbGv8ba3Ojt6+LX4+no/fnk6OjL1ubt5eLp6vLk+fP/gfLxgO/r5uLe5N3o+fmCgIPv3c3Z1uz85Ojg39nv6uPf7OPe5uTY0dXY4t/i4dXNxrm0yb+9zsnU1dLM1N7l0/Ty/YSOgouKi4qVlqqem5+YkJOcl5Gbkq2doKOcnqGclZOMl4+TkYyIjYWEhoSMjIqIko+doKKan52nsrS3v6+wr6WfgJyhqK66w7m5vc7C19G9wMy7uLu+x9bn1NXe3tjI1trj5uLJ2ufh9evh18fHuL2/taShnqSmnZ6cpaqrr73Rz9jIrZ+im5aglZiaoJuWnZiSi4qFg4f0h4X3+IaHjpiUl6CYoLSroaqoqK6krquqo7iopbKxqLHPqJ6NjZGHlpyggKGqo52dqq6ytLq0oqmip7W5vb67usLAvLeyxcSyuqucmKSoprOnnLKnmY6xtquyqbWuobCusJyko6emraqZqLSslpyQmqSUiY2FkIj9h42KjZSTkJqVnZuVmZOJjIWHhpaWkJCSj4r0/Pz7gYeClJOFi4X9+oWUiZKVhPT2guqCgG5ydG9tbW5zbGtsY2VobWNcZmVnZ2RqYmFmXmZtaWtpZWdtZWxvhHdwc2xtbGhtamZraWlobGJgX2RiXLS6Ymdjvma3vrizX2JfXmRhW1hUWVlWW1lWXGFeZGFgZF5hbW5sbGlob2xycXRxdHdqdXJ4gXlvfnxkb3aHfXtzgoB3gHWAfntoYmhwdXpwbmFqaXNsYrdcqqy5oJCVoZukqaOirbKura5kZWdmZGZiXV5ZYmBmYWJbXG5qbmFjW1xfX2NmamdpZcy9vrqpWlNWWF9hXLazZ2Ompqqxr7K7pZ+vYWFgX1dTXVplb8DNyMNsxcO2rru40NHKa21paGhlbmhhgFNcW15jYGJiXWhrX2Rzdmt2iX9rbHNka3J5fHl7X1tpZneNkZSLkY+NfnFydoCCh4+IenmEd3iUlZOHj5CEdXlsdl9oZmFiXW1YpLpub2xhaG5zeXxnb3d1eG94e3V4cHV0dHyIhYWEe3qDg4SNlm9viH5vcVluX3B2cGhva3V1gHVubm9raHNsX2NfZl9lZHh0dXd7enh4cXBvgnp5b29jX2C0Zq5vaG5rbGpru7ttZWJlYWK9uKpcrqZeoqSrnqeqqldoV1ZTpVOgpKCWoaSnlpmcj5KWiJGPi4+ahp6nmpSRjI2TlpWYkaGRnKWmprCbpKOkqZ6grK+htqylp6mxgFhaq1ysp6morKSdn6eusrGuoJ+loauxpKOinKW2qKKip6limo2Mg5CVlKWcn42XnKGVlJSbl46YmpegoZ2CpJ6mlqaZoZ6cnJmTjqSTlZCOkqKol5KHl5uYkJyHnJOgnZijnqGgnZqTnZ6gtaqboZyHj5SVjoiQlpiMpJymWKOmgKOgnqCcn5ueralbVlukl4ubnLK+qqidoJuppqKboZuToqSbmZ+iqqmwsa2jpKCgs6ysurO4t62stLe1n8K9xWhyZm9samlycYB1dHh0bnR4c213a4N2dXZtbXNvbnFsd25ya2hmZ2FiY2JsbGtpbWx3eHhvdG9ze3p9hHp8fXlzgG50dnd7f3d6gI6Dj4N1fIqCfoGCiI6elJypq6qcoqCkn5qLmaaeraCWkIOGfXx+eG5ua3R5c3FweXl+d3yChomCc2tvbnF5cnd3em5qa2tnZGVlZ2vRcnHV0W9yd3hydnhwcX99d3x4dXlveHl2c4V7eYSGeoORe3lsc3ludnV0gG1ybWRmeX2CiJGLgIl+hJCSjo2LiI6Ni4SCkpGFkYV8eIGAgIp/eYqGeXCLjoWHfYmBdX56eGlzcnh5fHhrd4GAdn17iJF8bmtmbmjAZWhhYmRfYm1md3VyenZvdHBybndyaWlraWeyubSyW2JgcXFnbGbGy253bnZ3as/Pcs9xgDE0ODUyNTQ7NTg6NTtARz81OjY1MzI6MzM3LzU2MzMxMzg/OD09T0dFSkNIR0NIQztEREFBRT09PkJDP3d7QkI9cT9reXd7RklHRkxIQ0I+RElGS0dBRktFSEI+QTg5QkA9Pjg2Pjk9Oz42OTwsNzI1PzcxPz4nLjM/ODYwPz81gDM7PD0uKS4zNzozNS05O0Y9NWAzYmt7a1peb2ZpbmZmcXVxbXFEREM/PD4+Oj06RERMSEpHSFhUVEVIP0FEQEdIS0dIRYmBiIuIUElMS05PR42RVlF/gIKLg4OHal5sPz4/PTUvMyszOVBhZmRBdXVnXmhheHZrOTk1NDMzPTw1gCsxLCsrKCgnJDI4LjRAQTY+SkIxMzosMThBRkxQOTc/OkJLSUdESUhIOS0tMTg3O0I+NzpEPUJZVVBAREU7MDkyPy47OjY4MkIqPUw2MzIpLzY6QEMvNDc0NSs0NS8wKCwtKzVCPT48My82Njg+RicrQkA0OCY8MEBEPTU7Nz4/gD82Mzc4OkY+Mzg0PDY9OkhCQT9BQ0FCPj49TUVFPTwxMjJZOFNBOkE9Q0FDZ2hFPTw+ODhsbWE7dG5CZF5hUlxhaztNPjw5bjdmbGddZmpvYmZsW2BiVl9cW2BxXXaBdnFubGdrbWtoYXFfanFubHdlcnd9hHN0e3ZugnZ0eXh/gEBCgEqMh42NjIV/foKDgnp3a25zdXyAdHFxbXmUh4KBioZRe29vanR3coJ3dWBlam9laGVsZ2BpbGtycnNdfXuBbnlrbmdnaWxqZ4BubmVfYG1tZGJdcn13b31kcGRwa2p8foaJioFzeHV1i4F4g4Rwd36FenB3dXVmdWx4Qnt/gHd2dXl5f32Cj4VEPkFzaWFxd42afXpxcmt/fHl4fnNqdXNnYGJnbWtxb2piY1teb2tqd3B1cWZgZGZhS2RbXTI4KTAsKiguLTkvLC8rJi00MSs1LEE2NzkyNTo0NDQxPzo/PDw9Qjw9PD1DPz05PDhCP0A4PDg7QUFFTEZJTUtIgEZNTU1QUUZHRk9GWFVLUl9VUVZXVldeU1ZdXFlNUE9VVFRHUVlQWFNOTUdJQ0A/OC4tKzI2LSwsNzc8NztDRUdANC40NTc+Nz0+Qjs8QkNDQEA/QEF3RD9uaDs8PkA5ODgwLzw3Mjo3NDgwOTczLj8zMTw/NkBIOzcrLTEnMTIzgC81MCkqODo9P0Q+MjkxNkJDPzs2MDMwLigoNzYuPDErKTIyND0yLDs2KR85PzY5MDs3Mj8/QzU9Oz4+QD0yQEtKPURBS1REO0A9Rj9rPkE9PkA7O0E4RURCSEM7Pzk6OEI/Oz1AQj5bZl9fMjg0RUY7QT1zdD9IPENDNF5fOFk1vHsHenp7e3t6e4R6y3sCenuReqB7hXqHewR6ent7inqKe4R6AXuJetN7gnrWewN6e3qHe4J6hnsHenp6e3p6e4d6hXsCenu3egR7e3p7nnoBe9p6AXuMeoN7snr/e5d7BXp7e3p68nsBepp7hHqIe4J6hnsFenp7ensCAgQAgJWWmZCMhpefl5WJ/4uKg4yGiIuDgvOFiISOkp+Um6CNl5mfpKKjsaSjp6yoo6qfm5WYnI+Gl52Kko2Ik4uNgoWLgYiIiYiGiIeEj4eOiYiNkY6WlIyLgP/y+IeVm5CIjo+IioiAgYWCgpCYs7unqqWdo6aen5qxp6qjmaWcmpycgJmSkKmsn5mbmaifmpeXnY2jmvz59erj3erW18rQ28bNztPPydXj9IWSjYSMgYuK7oeCiYyYlpGZoZqOk4ePjIqKh/mB9/Hxg4eRj4aB/oGF9oCBgf3+++zk2uiC/oyF+v2No5+ao6mGhe/2693T29rZ5fPggYaBj4eSiYSA9YyLgJejoKHDwcjEssCxrqipyrinlJqWoaahnKGgppb9i4WH/oGEkpSiwb7Pr7C6r7G1xca+ramajpmit9rL5drGwa2imqKepJaZh4aZ/ICRmomlxc2upYTtgPb6jpGWra2Si4iVkY2Yj5SJip23o5mV95ajprOrp6e2p5iYoJyaoa2ygK+Wm5OJh/yQlZeSn5unqZ2Vs56np7C3p6ecoJeWnpaUm5eQjYD+ipeVh5KOgYiE4Pn5h4yLk4mTj4uUloOIioCHi//58/3v74Du1tvB4O/i4+jr4tfUv9O5rrCwq8zEq7rH1dfZ09nk0cDLyN3i7uTW5tzX387XzdjU1t30+/yDgPiA9+Ln7fXq9vv29vbt++3r6fH4/oCB/fnw3ODn5tze5eDi6OHSzczLy9PJxtLVzcjRy9rMxL62uMPBzcfM2uDg1tbT09zGzN3i4N7k3+Lf7enl8fje7vbgzdTQxcC9xdrb3Ovk7Obq6efr8+ny8eTf5+rw7ujv5++L+4OBgIrzgPjs7d/S3Nrq6eXs5uHf1b/e5dDa3tjUztW4zdHVyd/U1s/QxsvGy9fW3NnFxKyxxMXLud3T3+jn7PPz84WJiICJkZivpqKmrauruLannZydko+ShaOTkJ6SkY6Ol4eKj42Nj5eHgIaGjoiIk4+Km5ulpqSqoqOrs6+wu7OprJuWgJWfq7Oysq28x9fOx9zNx7u1u7W/yNTT0tLZ2+ba69zq6c7G2Nfa29XKxb++uq2ut6SjpZSgnJ6aoqCrtr7KwsvIuba7q6ehnZyfn5uWlpWWlI2IiICD9Pb57vmBh4qHj6CXmZ2ajJWamZ2jpbGus7awqpWZrqmxrambhI6KgZGcgKa6s6yfpZeinZiVmJOho6igpKqztr66r7KopKevrqekkJ6mp6Ook5KRk5aYoqiqpa+voZiKpZqWqJykoK+zqq6fjpyGipixpKahpaiikZSYmp2ntKStuK+pm6OalZORkZiZmpmYl4uPkIuYlo+Sj4eJh4WCgIH9hZOWkYeEjY+MgHd4em1nYHB8eXhwznBwaGtmaG1nZ8JqaGFoY2xkZmtgaWludW1yfXBub3RubXZvb2pvdGdfcXFjaGRhZmNiXGBmXmFgXVtYWlhXXFZdWFhZWlVXWllcWbOqqV5pbmViaGpjY2JdYmdmY21yg4Z3e3Vvd3dxc2t9cnVtZnNwb3h7gHdybn1+cmlsa3hxaWVhbFpyaqimpJiUiZaGg3uElIKLkJSZlJ6ruGVtZ19gVV1cn19XV1VcVFBbaGdaYFZcXFhbY7tnxsK4W1pfXldUqFperVxbWaqnqaeopLBjvGZaq6VbcWhncXlfZbvFv722wLu8vsS0ZGRgamNtZF9cqmZkgG94c3OMhouKd4B1cWpxjoR2Z2xocHVybnJydWabWVNUm1NWX19phIKQeXyGf4CCkZGKgH5uYmpzgpOMmpOGjX54dHx2e2xuY2R2xWRzeWh8kJWDfGa+a8XIb3BvhIJvbG54eHF4cXJlZHSDdXFvtXV+f4d+dHJ8b11iZ2NgZWxxgHFhbGpiYbBoaGdfZ2Vtb2xlhHR3cXuAc3l0enRzd29qcW5tbGPIanNzYmltY2lnqrm6Zmhlb2RlYVxgY1deYlpiYqymoaebnFalmJqEpLGkpaOimpSShZuPiI2PiZ6Sd4CAioqKhpapmZWjnrK0urGjq52Xn5SknKmknZ+nqKdYgKNYr5aqtraqsKqkq7KrvrOuqaiqpVFVoqeflJyhopuen56dmpWTjZCenqKenJ2jop2mpbKoo6OdnaWlqpuYoKGckZORlJ+LkZ+knJaYlJiXop+hpqiUnqCUio+UmZSUnaqhnKGXnpqkp6Gttqq2tKGapKGioZijmphdpFdXV12hgKikq6Wcn5iipKCnm5iYkn6frpyrr6SgmqOJlpybj6KWl5SXlqCcpbe0urykppWbqq+xor20vL+6urizsWRlZ2FpbnKEeXN0d3V0gYF4cXV2cHFzZn1vZ3FpbG5vd2xub2tqaG1mYGVmamFnbmtpdnR6eXV7dXV4fHh2gH17gHdwgGxxeHx5e3d/hYqBe42EgHl3fnmDhZCUl5yfoaibqpulpI+Lm5udmpCFgnp+fnN0fW5rb19ta21wdG91enqCgoeGenR7cXNvc3d5fHdxbWtpZmZlZ2Zv0dfbztJtbGpma3RtcHJya3NzbW5ubHd0eoKAf21we3d8fH55b3p2bXJ1gHd/eWxmc2l1dnR0enqGiIuAe36DhI6MhouCfoKIiYKCdIOJioSFc3R3eH59f4F8dX1+dG1idm5rfHN9eYCHf4N6cH9xcHyOhYJ9f395bGxtcGxyfnZ/ioOBeoSCfn57dHVxcm1ucGRmamVwcW5sbmRmZ2dpam7Mbnd1cmhkcXNygDw9PzUvKzlCPT43Yj9BOkM7Oj01NWE7OzY9OkE2OTsvOTs+RDw/SkFCRUpHRUtDQjtESj44SUtASEZFTklGPDxCPEFBQ0BBR0ZFSUJHQ0RFSEVJSkhKRYp9cz9JSTw4PT86PTo0OTk4NTo5RUg2NzAqMzQxMy0/NjcvKTMvLjM1gDEsLD1BNzAvLzkxLi4vOy1AO1lcZmRpZ3RmZltga1pjaWxqY2tyfUVMRj5EO0VFbkdDRUVQR0JOVFFFSUBGRkRHTZFPk4qDRUlTWFFNlUxNhkhLTJCMjYaCdn5Hgkg7Z186SUE/RUgwNFpra2lpdnBxbnVePDs1PzY/NC8rSTM0gD1GPjlJRUZHPEhDQT5FW1FBMDIwNjs5Mjo9RD9eOzg2VSknLCgvREBJNTY/ODY3RUZEQUM5NTxETFVLTkY7RDo3OEE/RDw+MzNBViw4PS1AUFNGPytILlJXNDQ1RUAsKCk1NzE6NDUpKDM9NDAuOjY+QEg/OD5JQTU5PjkzNTs/gEAxOjk1NV4/QEI8Q0BHRT41Tz5APUNHPEJBSUZGS0A8RD8+PTRnOkJAMjo+NDs7U2VlOjs6RDxCQT5DQzM4OTE7QHJxbXNmYzhiVFhIZXVrbnBzamZkWGpdWmNmY31xVV5eaGRkXmt5aWRuaHx4fHNodW5uem17cHl1cXJ/gn9EgHlCh3KGkJKHi4ODjpCFj3lzcnd9fD5Bf4N9cXyFhHp9goCDhYB8eHmBfX92cXBwbGZubHZqZmdjZnBwdm1veHl0Z2RiYWhUXm94dHFza25pb2tqbnJjbXRpYmpvb2hkbn14d4B6g4GEg3uCi32NkIN9h4KKiHyGenZKe0JCRUx9gIB6fHlwdnWEgXl7cG9taFd4hXWCgHp7dHplcXR6bnxvc2prZGhgZXRtbnFgY1NdbHBwX3dscnJtaGRbVzIzMyotMDNANjEwMzMzPD00LTAzLS4zK0E3MzowNDQ1PzQ3Ozo8PUdAPEBBQzs/RUA7RUBFQj9EP0BCRkJGU09KUEdEgEFITlJQTUZMTVFKSV9bW1VUWlJWUFVVVldbWl5TWk5VVkhEUFNTUlBMTEhIRTs3PC8vNCY0MDI0OTM5PTo/PUE/NjQ7NDc0Njo9Pj07Oz1AQUFDRUBFd3N3anE9Pjw3O0A6OTo2LTU2MzY4Njw3OTw5NyYtPTpAQUA6KjMuJy80gDtFPzcwNys1MiwrLzE9P0I4MS8wLDAuJysnJyw2OTQ1KTY8Pzo8KikrKy4wNDY0MTc6NDAnPjg2RTxGP0RIQUY7MD8wMUFUTU5KTlBMQkJFSEZLUkNFTElGP0dDPj08OT9AREVHS0BAQzxIR0RCQjo4OTo6OzxpOkVDPjc0PD44i3sBeol7AXrBe4N6uHuVeoh7AXqSewV6e3p6eoZ7B3p7e3p7e3uHegZ7ent7enqIe4t6iXsBep57BXp7e3t6qXsBeop7BHp7enqVewF6l3sBep57AXqJe4N6kHuGegF7tnoDe3p7k3qCe+J6Ant6hHu9ev97m3uFev97nHsBeol7AgIEAICJj5eWpYqGmYWKkIiVjfb9hfDn7uuA6fHnko6QjZKjnZ2TpKegqaSsqa2rpaGpkpKblI+gn5mPiIaIi4f/+4KA+uj14+vr5fqC9YeEhYmGi4+SmI+MmYj15ub8hZmH3eqAio2Nh/2HgYCGg4yPn52coqKpq6KMk5yxqpucoZ6Tn4CgtaqbpZ+goqOop8S4pZ2InaGT+efp2dy5qriyvsLGtbvMvL2+2+r8h42Qh4CIgvOCiZ2NjqKRk5yTi4aRk5eWkIeDh4Lz7YGHjIb+8vHi7+/u8vvv7u719vX19/6IgI796O/h8vWAgYb89OzazdTTx8fM1fX0gYr6j6OWmYaH/4D18Z2Yq6Swt664qZmJjY+UrJ6UtaSnrbOcrbitheaHgYmSnYyg58PK18a+q7K4qqq7vrCfgPyh3LW+nbDSxbqrpaGRl62ksJWRkpeWhoORkp+clJGKhvfp4+Dt8ZKJgo+JjIGSiJSKifSIjZKZlJeVnZSH/PmZnJufmp+eoKWhqICmko6YkI6RppeIgIOFkZujrK6yq6OpoZugm5qPm5SChYyMlpyXlJ63lIP9g4X/+oaFjYSAg/7yjZCLkJCjtZuJjYiWipaejYT06+3w4fjv2c/g3ePc2cPX3by/xcHZ1sHDytvV2dnBycO8x9Dl3vro7eLm4tvKv8DQy9TR5/Xm6oDs7eHn7+Hv7fmAgPHl7ejw7/Xy7/Hr5/Lj39Ta2t7w6u3v6efgy7/H28nOyNbQ0c7N09bLysXFw7O5tsbDzN3i2dXX1Nzu3N7d2+Xi8uzq4d/c2u/x7/CE/d/m7tbM2+fl8ffl5/bj0Ojk5eHc8dnl3uHn7vSAhP/4hvf29vCF/4Dz6+7c2uTl7enp4ebV1MXAzMjSxdfe2eDCurm9wtC/x9PIzcLFy9DSzN/P09K9xsbU0czl2+vq5Of4iYKFi4qVkJSXp6arr7Sit52moJuhnJWSiPf38fb/iZ2elqGQj42MioOFg4WOjI2JhpSQkpygoaWopaOpoqa2q6+rrKOfnoCjqaeop6e0xc3Y2sbYysW8urzIzs7R3drS2NXP3OLv+PzisODY097Yyc/Kx8CxsLO3rp6Tl5WalZmfoai4urW7w8fBvLGrsbS0qLO0qqCdn4iFhoKC+vHs94HygImCjImOlZaVh5OdnJaYoai2payuoKSWjamjma2rpaCooYeKkICVlpSnpqessLO1ra6hmZSVk5mmsbGupaqbnaSorKmrr6mdqYaIj5milpKPlo+RqqChnpqYkoWYipaWmpShn5+jopiTm5uhoKOknJOSlIqWsZ2dqrawv723r7mqqJ2Tn7GxqqW8qpGVlZWfnZmJkqSelYuMjIuJh4KOlZCYn52LiIBobnFzfmhoe2dudWx4cszPcMvCz8txxcO4cWdnZml2cnJlcHZwdnF5dnh4b254aGp0bWl4dW5lY2FiYl+3sl1fvrK/sLa5s7VVnVxZW11ZW1lWWFJUaGCpnpysXHBjpLNlamhkX6xeXmBma3JxfHx4f3+EhXtlZmt5dGpwfHxxeYB8i4JvenRwcnJzbol9bWdYa3BnoZCSiot0bnl2g4eJfIKVjJGVqLO6XV9fVU9dV5tVVl9OUF5WWmZjWFFXWF1gX2BfaGOyp1VaX1iqoKObpKykpK+ysqyysrKvsrllXGq0p6qdqbBeZWvLyMK3rLa0qaWjpLSqWV6hZHlsc19fqXWhmWpmeXGBhnuEdGRWXF5jd2tigHJ4foJsd39zT4VXU1piZlprloSJlo+HeYWJfX2MjoR2W69zoYCFb3mWj4mAfXhlaHtxgG5udXt8cGx0cnNwamhmasOysayzs3JoZHRyc2t2anJlXKdeX2dtaG1xdGxdqJiEZYBkamZobGZsbV5kb2RiY3drXFVVVmBqeH+HjYV9fnRvd3R4cXpzX19nZ3R6d3V6inFhv2hqxLtkXmRgXF69rGNkXVlZa3VkV11YYFZia19bpp+oqJ63tJ+ZqKOnopyLnq2NkJiSnZeBgYKPi4uRhIyFipSVoZ6qpKebn5+ako+OoYCbnZSepZuhpq2ho6ifn52nU1Shl6Cbo6aqpqivpquzpKGanZiXoZyeqKCgoZWKkaeWmZSimJudmaKonZ2gpKujpaOuoJ+op5qWmpigsKGlpaGknaqlnJ+en6GuqaKbVaWPmKaalaWwqq+sm5yrlpGrpq6po7SgopyloaOiVFeimoBcpqywoVWqpaarnJuamp2doZ+flZSJh5CUoJilqaGmlI6Pl5uomaCnm6GWl6Gjp6K0qrKyn66tubuzw7/NwL6+xWljZmpocmxra3p1eX+DcoZwdXVyenJxbWW6w7zGzGx3enB3bHBwbnNoZmViaGdnZWZyam5zdXd1dXVyd3J1hYB+gIB+c3FwcXh2eXJvdX2AhIp/jYF9enV5gIWFh5OXlZybkpiapauvnXidmZWckIGDfYKAeHt+g3lrZGhqbmxwc3F2f39+e4GBfn17eoCEhneAfnhwcXRiY2dka9HN0NBs0G5wa2xmanBycmpvcW9iYmlreGtyd3N4bGh8cmdxeIB2fIR+b21xdHJtent4fXqBgnt9d3Nxcm9vdnt8e3h/eHqBgoSFhYuMhY1zc3J5f3hwcHZtbYF3eHd0c3FmfHF9e394fHp4e3x4cnx+hYKDhHt0c3Jrc4d2cn+Mj5qSh4KLgYJ3cXiBf3FsgnFiaGlteHRyZGl5dXNrbXBzc29sdQd3dHh+fmxogC0zNzdCLy4+LDM7OEdDcHtFb2dxbkJxb2FHPDk1N0M/PzZESEJGQElIS0xHR049PUVAPU1MR0E/QUZJR4N9QT98dIJ0goaFkkeAS0hGSEREQ0RHQEFVTIZ9c4BDUUFZYj1HRUM9aTo2NTY1NzM5NDE2OD1COysuMz87MTI7OS81gDdEPzI8NjIyLzMvR0A4NSc8QTxYWWNlbVdSX19la29gantpaWl6g4RDSEhCPk1JgUlLVEZIVEtOVVFGPkZHTU9MSEdMSIB6RUtTU6Kal4aLjYaJlpeXkpWQi4WDhEg8R21fX1NgYDI3PXB0dmtnc2thVlNTaGA0OFc4Rjw/LzFYgFBJPzVDO0RKRE9EOzE2NztHOy9KOT9ESDZEUE0zUj44OTw8KzdOR0hQSkI0OzwyNkVNSUAsUkJoTEk0OUtCPTQ1NykwRkBKOzxAREI4Mjw6PDo3NjU3YFJQRktMOzAqNjU1LTcvOC4nPyYoLjMsMjY9OC9QRj5AQUBARD89QDlAgEAyN0Q9PD9SSj44OTpCRUxLTk9EQEM9O0VGTEdSUDs4PjlDRj89Q1M7LFc2OmxpPTg+ODM3bmJARDw5NkNLOSsyLzw0QU0/OmBZX2JcdHRgXWxrb2xnW2x6X2dwbHx3X2Bda2FhaF1kXmNoanpxfHB2bHV6eHJubn14em98gnN2gHh+dn2KgYaGjEVJj4aKfnx4enZ4fHd+ioB+d359foiAhIyLkJOFfYOWgoB1f3FtamhwdGhnZmhsZGdmcmtudnRoZGRjaXZnbXBudXB8d3FuamlmcWxqZDt0YGp6bWVzfXZ+fW91iXhth4KHgnuRfIN8hICHg0RHf3RIfICFekSFgHt4f3N1enuAfn54fHd1bWt0d31wd3x5fWloZ253gWxxfm91aGZoaGdcamBkaFdnZnd0anx0fGxiXmA2LzEyMDgwLiw2MjY5PjFALTMxLTYyMjIuUldRVloyPkE7RTk8PTo/Nzk7PURGREFAST49Pj9CQkNDQkdBRFNKTk5NSEdIgEtOS05KSExST09SSVtVVlVSUlRSTU5VWFdcW1RXVFhcXVA0UlBNU1FMUE1OST88OD05LyoxMjUzNjc0Nz49Ozg9Pjw6NzY7PkE0Pj87NzpENzlBPkSAe3l+Q3hAQz5BOjxAPT0xOEA/NDQ4OUAwNjgxNywoQTcuOj86O0Q+Li8ygDg0MD8+OT06PDw1NzIwLzAtLDAyLywmKSEkKy0yNTU7PjlALS0vNToyLSwzKyo8MzY0MjQzKUA1QD1BNzw8PEJCPTc9PURER0xFPz9CPENWRkJKT0lOT0xHUUdGOzM7R0hAP1hNQEZFRk5JQzM3RkI7NDk9Pz89OkNGQEZKRzcvjnsDenp7hHoEe3p6eqN7BHp6e3uIegJ7eo17hHoFe3t7enqFewF6rXuVeod7AXqVe4J6hHuSeoN7hnqDe416A3t7eoZ7g3qbewF6l3sBeqB7hnqMewF6inuCerV7BXp7e3p6hnuCepF7wXqCe8x6AXudegV7e3p6e4R6AXu7epl7hXr9e4R6Ant6/3umewICBACAoKCXjaOomZublpmin/z3//756+7zh/2Ag4mIkpaKi42DiZ2dkaGgrLGxo7epsJ2lpKiVnqKTlJiKgoSOjIeIiZCB/+zy6vr6hPb5+YaEioiDjI2RloLz8dXi84ONi4CD2IiZiI2JiYic/YyPjIukm6aOpJuK9f2LpZCXkpKflI6AjZKPn6eWnKWdnaGmnqKstK+mo5SQ/O7/7oDayde6us3IzcDG2+yC+YOA/PXh1eTq6/+CiYWTkJCWjI+NmZKYloyFg4Dt2ej8hYf/7d7f6vP26O7h5+3i6cnpzOvhgIeAnY2Yh4ORgPT169/e5eDd1M/f9+j5hYODg+7+/YGEh5CAkYmThISB+4n+jJqYhZmRkZSUmJTKlpWXk4aP/uvo9oOOkJGbrsG6zJ+TmZykqJqwmI2Gl6WRg4SZrbrPxM7Ku6qhm5urmJKIi4eUhfXq8oOSn7mwmZ6Oh+vy5+KAioCEhYeBjpqinJmtmqmjmZqcq5eTlKixpJ2PloWHg4OJqKKAlqqlmqaZpZyVifuFhI2TmaaYjoGdrKakoZCNjI6Ig42Ihpido5aYmZeUiIOD+YCChoKCl5GOh5+xsbi0maCO94qDjI2clYrw8Oz/+4P58OXk3drk0cTPxtnQydLL3eDm19Lm3tzXyczUwMTKzuTc5uzn6N/v29/a1NTX6oKB6eeA49Xe4tfR1dXk3PHt+Pft5urx7fDz6OLh4NfF2uje2+Pl4t7i1MzAvcbQydHRyM/Rzc7L0s3J08bCv7K7yc7L09LMzsvOy7nEztTf2e3l7fLw4t3t6fL9gPGAiuPKy+Dj09ve1tbX0dTaz9TZ0czL0NPL4O/v+vf9gv6A8vX8//qA9vLU3tPm5ufg2erc2cvI2NzZxcTVzODa0cTKx8nSzNDH08zEw8PNy97P0d3f2ero7ujk8OGAgIGJjo2emJuRlJGVkZ+coqqYlpOXl6Wen5qSiIaKio6TjpWomJmWopCGgoOGho2BhouDh4+WlJCVoqGkpqepo52nqqepn5yVlp+AnKepoqSysL3F0dPV0tHdxMbS09zRxNjNxMvP0+Df2eLe15u8u7/LzNHK1dvPwLe6vaawmqmel5ehlKOkrq62y8q8s7u8r668wr67wLmpn5eLj4+UioeH8vyA9O7/gP/7/4iIgIaSkJaZjpOQlJmjmpmcjIWHjI2QlYqKl6SnrqeAmJGOqJqOpKaorp2ZkpWjnJykp6Wlq6CepKKorp2Si5iVlJ+MkLGrkZaWlZefnqSdn5yXjJmWmJ6dm56po6OftamZoKKdm6CdmJeTkJCZsqiepJ+YlqawtqytsaempLippbStmbyoo5SWkqGimpaSm5WZm42Li/yFiIL5hJCThpqAe3lwan1/cXVybnN6fMnIy9LQvcHMcs9lY2NgZWxhYmdeZHZzZnRudXl5boF3gXJ8fH5veHltbGtdWFxlZ2RnZWpfvKyurbuxV56lolxYW1pSVVFTXVOiqpGbplxkZl1hmWZxYGJgXmJ4vXN2cG5/cXpnd3dnuLtpfmhuamt2cWyAbHJxeoNvbnFtaWtzam14f3p1c2Nen5KemViRhpWBf42SnJCYp61erlpVrqORjZmcm6ZNUU9aW19kWlpUXFNbWllZXGC0p7C0XV6wo5meqq20raqgrbStu6DBoL2qYmNccmRsYmFyY77Fwbi7yMG8srGzw66sWVJOT4ibnVZYV12AYldfXWFeumKqWWVkVW1oZ2lnaGKOaGpsaVxhopONlFReX2RueoOAjnJqbnJ2dGt/aWJca3ZkWVlpdoOSj5yYjoF5c3Z+bmhjZWRxY7eytWFsdYeAcXdqZa6zrKllbWhucW5rcXZ7dHOHdXt1bWlqfGdhY3J1bGZcYlhbWFled3KAZnd1c3dqbmVgVp5TU1hianhwa191fnl2eWtqa2piXWdmZHd8gXZ5dXRsYF5ht15fYV1ebmhkXG14dnh5Zm9jsGZcY2ZwZV2foaK3r1+wq6Snp6itoZKZj6eZlpyVpKKoloyclpSRiouUi4+Tlaibqaegnpmkmaaempyao1RXmJuAmpOjp6CamZ6rm6WXpJ+al52inKCnpaqvsKebrbGim6CgpKOjnZ+Rj52mlZ6ai5SXj4+Oj46OnpqkpZaeo6CUm5OSmJmdnJGdqayypKqioqiflJadmqOmUJhVWpyTnLO9qquspKKgl52rnqWxpqCgnp+coaafoZ2kVqxYqKqqrayApq2Ol5SkpKahmaOYloeLmJyekpSnoK+rpJygo6Cppqqep6agoZ6jn6qfoquxscLBzMbAyb9saWlrbW56dHVsbGprZnNweol3eHFxb3hwcm5rZWRpbnF0bXB9c3BtfXFqamxsaHBlZ2liYmpubGlscW9ucnZ5c3B4fXx9dHJsbHKAbXV0bXF7en+AhYaJhYWLdHWDi5KNhJSMg42LjZSSj5eamG2Mi5GZkpOHi5KMhIGGjXZ5Z3Vxam53a3VydXF3hYaBf4qKg3+KjoZ+g352cGxjbm90b2xrztVu2NXVZ8nDy2ttZmlybnJyZWtoa211bnB0a2hpcG5xdnB1gIuNkIeAenFtgnFodHR2empqZ2x3dXR4eHZ2fnx9hoaMjH51bnZ6eX9xdpCIc3d0dnV7eHpyd3h0b3p1e3+BfH+FeXp1f3tzd354d3t3cnBsaWZrfnVwdHBudX2DhHx8gHl4doZ2cnhsXHRpbWZsb35+dW5rbm1xbmltb9B3eHTbcnh3anSAPj00Kz5CNTc2MzpGSWxueH10YmRvRXs8OTw3Oj40NTkzOUpHOEI8Q0lKQFRJUUBGREU1QUM6PkA4NTtFRkFBQEc/fnJ7eYiISYKEfEZAREI7QD5BS0KEj3V4f0RGRDo8U0VRQEA8OjtMYz48My07LzcpOTouSE4wRDE2LzE4MSyAKi8wOkAuLzIuKy83MDM9RD89Pzc2XVxubEBqaHJiYWtuc2Bod4JNiEhHkIl7d4mOjp5KTUpPTU9RRUZASkNMTUtMT06MeoSRTlOil4qIj42TjIuCkpqTnHiXcolyQ0A4TT9CODlIOG50bWt0gIKAcGtrd2FqODQzMk1ZVy0vLzOANzA5MjY2aDplOkZIOk1FREM/PjhaOjw8OzNAbGhlajs9OTg7RE5KVToxNTk9OzRHNzIuPko4LSs2PURNRUxIQDg2NTtEPDczNTRCNFhNTi01PEhDO0I6N1teVU4zNy8xMjEvNDg5Mi8+MDY0MS4zQzQxND9EQj06PjY5NjQ5T0mAPkpKSFBFTEVDPGw8Oj1ARVBHPzJES0VFSkRFR0pCPEM8NkRFRzo9PD87MTE1Yzc7Pjo7TEQ+NEZTUlRTQEQ3VjgxOTtGPjZRU1FkYDhhX1tiYmVrYVVhV3BqanNue3x+bl9yamdoZGZvaGdnaXVlbW5tcXSHfImDfHh2f0RFdnaAdG5+g396eoCLfJGLmI6AcnJ1b3J2cnV8gX10iZOFeH6BhIWPioqFgoqRfX94ZmluaGppaWRfbGRqa19oc3ZpbGVfYmRoY1VlcnV8cntzd3tyaGVqaHByOGo8RHJmaYGIdnh8dXN4c3aCdXuDeHV4e4B7gouBgn5/QoRDfYCAgoCAeX1ga2t8f4R/d4J3dWhqeX15a2p0b4B8dm9xc3N4bnFncnNubGZrZGpZW2FlYnJrcmpiaVw7NjU1MjE8NzgxMjAyLDUyOkIxMi0uMDkzNjEuKisxMzQ3MDVDOjo5RTozMzc6OkQ+QkQ8PUNHQz08QkNFSEtPSERMTUtQSEdDQ0qARUpKRUlVT1FNTU5QU1dgTE1VV1pTTFhTTVNSUVRQS1JUUS1IRkhOTFFLT1FJPzpARTM4Kzs3MDY8LzcxMy81QEA6OEBBODU/RD87Q0I+Pj87RUlMR0RDdnxBfHqFQYB3eUBAODhAQENENzw4OTpANzY6MS0vNDM2OTIzPEZKTUWAOTAtRTMnNDQ0OSwrKi45NjM0Mi4rMSwpLy82OjEsJzE1NDsuMkZALjQzNTc8ODgwMzIwLTk3QEREPkBFPD87S0Q7QUQ+PEE9Pj06NjQ7TUdDRkA6OkFITEVHS0JAPk1AP0lFOVdRUkZMSlNRSD05PTg6PTc5O2lAQj1vPkRANDyNe4h6Ant6q3uGegR7enp6inuFeoV7AXqIewF6i3uCep57hHoBe4x6BHt6e3uIepJ7hHqCe5N6inuOeoR7g3qKewN6e3qSe4R6rXuDeol7hHquewF6onsBepF7AXqHe4V6AXuveoJ72XoEe3p7e516A3t6e7p6/3ujewp6ent6enp7enp6/3uWewV6e3t7eoV7AgIEAIC3oaigmZiRjZGIkI/+gf2Eiuvv7/mI/YSAiZOToI2Ok5WZnZmjmqywrLiqoJ+Sn6uoq6qpk5yjn5SBjYWEl7KhiIaQgYH3i4X/gOqBgYGWlpabkZ+RkoSBiYqCj4mUgY6Sk5KM+fOE94SCgIKHgYqAjYmYn5CRjJGboJWgn6CiloCWjZSNjZmlopyho6GYqKKpy8mzrayqk5SJ/vzw+IaD9uzu2c/d5srogufo487i5uz1//uEi4uEio2SkYWQmZeSj4iEg/rx+vL9/evh4enz+efX1tja19XaweDu7/D+i4KB/4WKkoqQheHn2Nvw9YLp+POLiIqKjZKPjYKDio+Yo4CWipGPiu3W2ODj6+3gzdLQ0OWoo5T6//qC/4H17uyJlp6qmKO5266xr5yNiY6IkKOToZyMiYeVrZipuLa3s8W+raiUt7CcheDg++fr/Pnx8YWJi5CHioWD+4OB+YSQk4yRmaKZlZOVq56VoKeYlofs/oGao5GBj4iRgYeRj6C1o4CamJeMk5KOg4T3/YSIi5eC9IGHk5KWhf+FhIiEjfSKlY6JhYaGg4uQlZOJhp6egPqBgYKT/pOVkZSenpWMgoX7g4+ShYyFg4H79Pvw9/uGgt/n7drUxL/AycfNxs3p5urx6ujn7uPb3NLYxr7M0NPU4d/y49nby9HT18zS4ejq4IDYz9zkzM7P0dHl5fL26/bx9v/47fXt3NXT3dm8zNrY3d3p3c/d2svO0tHSyc7Ez87j1tXOzMPSwLyzva69ysTIzM/Z08rFyMbN09LR2OPo6fbj1ubY1Onc2OTo0drL08/L1tTZ1tnZ1crL0dDR08Tb19bd7/Dv8v777/Xt6eDo54Dx5tHb1OLf5+zj6ebS28jW4drh1M/P1tfUx8HK1dXP3s7IxrnCvMnDwtfd8e7m7Pn5hfWC/oSHkI+GgI2IjJGVl5OcnaKUnJuQi5GSopuRj4f/g5SdmJeblaSlsJSThIaEgoKI84CCg4eHi5uVmJKZnJudk5ial5aeoaSVmp+aooCmnqSZopybrLC6x8rV49/T1eDl6eLW39TUzcq51dXQ0c25mLHIxsnS6tvWxMjCv7m8pp+Rj42Tm5mSqKOmqKi1uLC7usDCvsLCybmxsqajmZaUj5OIhvz4+Inz+vv7/Pjs/IGDh42Hh4mHjIWKhoeJjoOHiZWVkpSckYmRl5CLm4CpoKe8qpaZp7WloqGWlJqWra2zm4yWmKeWmIj+l4uCi4WKlZSToJKLhZCTm5qSlomGiZOKipeUkJOdiZmkpLOfmaugnauZoJGbnKCrprGos6OssKacr6yoppubo5aRqKCio5qXnJWWjoyToaarj5yDkpaYk4iKkIaJiYyFjIutsoCIeHx3c3RraXBlbnHNa9t3esfGwch0zWtobG9teWhrbm9zc2twZ3J6eH5zamphbn16f4GBbHN4d2tgaWZmc4p6ZmVxY2GzYlmrW6RcXlpoaGJiWGddZFlcX11UXVljVWJkY2FZmaFbsWRlZmhpZGxfbGhxdWdmYWJucWdwcW90aoBuanZvbHh5b2dram1nc252kY17dG9tWllUoaiit2RerJ6klY6Yn4CWV5CZm42hoJqbno1MVllXX2VlXU5SWVlUVVRUV7CqsJ+pq56WlqOqrqumoqitqqaulbG5vbXDa2BfuWJnbWZsYqq1rrXV0nG9xrZnXVhTVFhSUklNWF5pcIBqX2RlYqKOgoN/goN/d36BiJl3dGmrrKhVq1KVi4VSXWdzZ3aEknh9enVqZWdeYG9fb2xfX1lld2VxfoCDgo+Pgn1rk5B+Z6inxK20wr+2s2NjY2liY2BdrVxbrF5rb21zeIN6dXJzgHZtcXdsZ1ykqVZpbVtQXFhdUVRXV2Z4a4BlY2RcZGFeVlWVmlJTVWBQlVRbaGRlVJxaW2FgZaNcYWBfYGNgXWVqcWxmYnNzW7BcXV1psWppZmVqamVfWl+6YmtsYWdfXmC8t8e5wcVrZ7G1vaurm5ibqaatpqi8srCxopqZno+NjYqVjYaVlJian5WnnJSZkZibn5CSm6KmnICYkJueiY2NkJOgmJqekp6dmZmYlaWrpKCjqq6WnqSfoZ+yopGmrJScpKKimZmHk46djI+HiIidlZmVn5Kao5aUl5uioZyWnp2fnJmSi5WZnaSakZySk6CQlJ+llqGbpqGepKCgnqOjn52apqqtsqWxpKCbopyXnKipoKmpoZqio4CjpI6UkpmYm6SfpaSUloaRnZijnpycn6KknJakrKamtKCgopinpa2loa65ysnBysvSbMJiw2RocXFoYm9pa25xcGdwc3Vqd3NrZmpqd29oaWa9Y3F6c3J0bXh6gW1qYWdpZ2ZpuGNfXV5eYm1qa2hucG1tZ2twbm95eHhtcHNscoB4b3dwd3Z0e3h7hYKEiYB2eoWQkJCIkIuKiod7kY6Lj42Da4SYlZGRoJCNfYSGhYSNenJnaGludHVrenZ0dHN8gX2Iio6Oi4aHjoR9g3h3b21xbnJsbMfLxGq7xsLHz8zH0mxsbHBnbW1tdXBzbmxtcmZoaW5vbW90b2x1eXRsd4CEeH6PemdncHxxb3NtbG5tfHl8amFudYl7fnPRe3Frbm5xd3Z3fnJva3B1eXVxd25tbnRtaHN2dXl/dHuCgIpzanxzcn9vcmVra3J6dH93e3B4e3Juf4B9enN1em1renJxbWdnamxwbGt0gIWLbnxkc3Z3c2pweHV3eHtxbmmEhoBNO0E9Ojs0MjcvOT5qOHVDR2VpanRKfEE6PkI+STk4PEFDQz5BND9DQUpGQEI4QElCR0ZHND1FR0E4Q0A+SVlLOj1IPj5zRkOJSXtHRT5MS0RHP1BGT0VMU1JJUEVNOkVJS0pDZ2Y7aDs4NjU0LDAkLyo2PjExLCw2OjE3ODU5LYAvKjUuLTc5MSsxMjcxPTQ5Tkg+P0BDODs3Zm5reUdEe3N8bWl3gGiATX2Eh3mTnZ2eppBKU01ITk5OSDhCTU9NTUtKSZOMlImUnZKMipKZmJCFgYqSjYmPboWLhn+ISTw6bTo+RT1CPWFoZnCKjE50fnJDOzs1NTs2MykpLzM6QYA5MDc5O19XUl5eYmNbTU5PTVxST0VkaGQ1cjtsamE8QEJHN0FOWkVLSEE0MTMsMkA4R0Y+PTc+RzM5Q0FAPEhHPDspTkxCM0tScF5hcWhZVTQzNDo0NzU0XTMxVTA3ODM1OUI6NjM3QToyOD83NSxGUi5ARTYvOjk9MjQ4NkRUR4BBPkQ+RkREOz1tdkBAPUQ0XzY7RENGNVo5PkRCSmxAQz02MjIuKzM4QT84N0lJNmk5OjtHZ0RDPz1GRUA7NDdnN0BCOD04NjZmXWdYXmA5N1dibmVlVlRWYWFrZ2+Eent8bm1udG1vcnB9bmNwbmtnbWh/enZ6dHt5emppdHp/dIBuaXd/bnZ3fHuGiI+ThIh6dnV0cn1/dW9yfH9pdYB5fH+TiH+YmoqOk5GQgoJtcm5/c3RqaGJwYWFbZlZeaWBbXGJnZmNdY2dwcG9sZ252dXtxZW1iYWtgZW92bHhteHNsdHJycn2CfHp3fYB+gXKEfHp4g355fYaFfIeDfHV6fIB/e2Rsa3V0eoR9hIJvdWZ1gXeAeG1vcnd2bmlye3RueWpsbmVtZ3FmXGBnbWpeZGRnOFswXzE0PDktJzEsLTAzMiwyMjQqNTQuKi0tOjQuMC1SLjtCOTc5Mz9ASjo7Mzc3ODg9aD09PTw5OkQ+QDpARkVFP0RJRkZNTUtDR0xESoBNQEpETUtHTEVGTU5TW1ROUFVaWlZQV1RWVFFCVFBMUVFHMERST0tOXVRQQURCPz9HNzEqLC0yNzUsNjAtLCozNjE9PEFEQT9ASD89RD9AP0BFRElDQ3d5dEJwd3Z+hIF9hUJDQ0U9QD89RD0+NzQzNysvMTg4NTg+NC02OjUtOoBFOj9TPissNT8zMjQwMDIwPjk6JxsmLDsuMihBNS4nLSstMzM0Oi8rJy8zOTgyNi4tLjUwLTc7OzxCNTtDQUs6MkU/Pkg4PTA6O0FIRE9FSkBJSkA5RkhHRDw+Qzc1REBBQj9BSUtOSEhOWl5iQkkuODs/PDM5Qz5CQ0Y/PjlPT4x7BXp7ent7hHoCe3quewZ6e3t6e3qZewR6ent6sXuEeoJ7iXoBe4p6kXuaegR7e3t6hnuGegR7enp6k3uNegx7e3t6enp7ent6enqpe4l6iHsEent7epN7gnqYe4J6hXsBeoZ7AXqFewF6kXsBeoR7AXqKewF6iHuGeoJ7/3riegR7ent6nHsBepJ7AXrxewR6enp7iHq5ewF65HsCAgQAgJCim6CYlI2LioiLg4b+i4SShYWFg42MioyG+f2NivWCiZKTop+itLjHsaOcmoucjZSZoLjEvrGjopOMhIeTl52OlJCJlqCdko2FmZmKipKVkIqTlo/p5PHygIKGiIn4iYD3+f6BhYDf2PntgImIoY+CioOHi4mQpKOjprOopKG1gJ2Roqamna2poZ6rr5SUpaOdn66gsLbBzcG+wq64nJ+gk5CD3+T2/oKF+/3u7u3y9+7xhIWHhYmFho+Tj4+Ul5eVjomF8/L16e/57PD59fDp2trX0t7W7ujv6Pbw//CNg4qbiPTu6enSztnNtqbBtbzg7IqLkJWUnJ2bl5qdnpGVgJ2Qmv3ZudL9++v+jISHh/XugoKYk5SLkZOJh4P774eF+P+KlZSfk4D98vDu94SLj5Kkp56am4qfrLO+u7ejlY2OkYWLkoDr04GP+/D29PTi7fL7kImLn5KHiofx6tvO5e35gIqYlpqYk5mak4GYnLKgpbeAjYKaloj6hIeOn4iIgI6Ahf/ziISDiIOC7viLioyJg4CBjJeIhIeFgviFi4+Mi4iLiYmGjZOUlZWMjvyHgYKNiPmB/4OLjpOSj4+SjYqLhIyMioKOipCQl4iKkZyFkIH84Ovj5Ov74/jq7Pf32uru5unt697n1NzW0cPc2djj5d7a2NfS0tPO1MDf4N3PgNDJ1srKx9fg3Ofe3Onc2drr7O366+3i28ne5tXd7Orn3+LV1MvGxLzZ39fAxc/PzdfTz8nExdLNw8i8vb+/xcS8ucPFusLCytbT0NnW2+He3+fg5tHM19rb3N3cz9/V2uHi4u7h4Nnc2dzo2t3Pyb/JtMTK1d/l7dbp5OPn6PLugOrOytPS1+Xw5ubi1dbd39vX3N/d7+nv7+Dm2tbi5tjo39LRys/O5vSGgvmFj4SF/o2NkpCBgoiRnpaIioeIj4uDjY6LlIqNj5KLkI2IhIKEjY6QkJeMiJGRiJKBgviBgYGAgYiIiYuNkJqVl5qYoZmeo5+YmqKgoZ2knJianpupgKWkmKGdpKG5tbbBxMPG1dLa5ePl3t3c49XKusjL0c3W1s3AqcnGyNrY0r/PzbW9sq+dmZqjoqWYk5uvnZWXmpi1rcPKx8DBy8zKvr27qqKjrp2bloSA9Pr78eLo4eDo4N3m6u39hISHhImFgYeIhJGSgoyRhoGBhZiZkIqFi4WSgJirpaOop5ycoJyitKupk5ufmpqNgID4gYeLkI+VhoqNh42Y/4yJhIOOmZeZk5OOjIqOm6Oxuq2gnZGVlZGetaWsopqcnZuRtampoqWks8Gyqa64rKGcoq2pnZqgm5KZkpqgjIaKh4uIgfyZjJWElI+Hj4uPiIiOjJCelJST/fuWgGp1b3JtbWprbGxvZ27Tc3B/cG1ybHZycXFrv75tabZhZm1ocm9wfIKRe2xnZ11wZ2xyeI+Yj4N1dG1kYWNvc3ltcm9nbW9tY15abG9kZGltZ15nbWqpn6yiU1NVWFytY1ulpKVUWlaTkrWuX2ZnfWxlamNoaGdpd3Nxc3xxaGZ3gGdkdn+BdXx1a2l5f2psd3Vsa3ZqeICLlo6PlYSOdXh7bGhblpKgpVNWnp+WmJidmI6STk1SUFdVV11dVVNVWVleXV1aop2jlpulmJuanqamqK6srbaos7S4tLy5wLBoX2V3Z7u2tLqsrMG9p523o5mysWJdXV9aYWFjYGRtb2VogHFnbrOWeYuknIuWWFRbXKGdVVRlX15UW11VU1GWiFFTnaVhamZtZVWvoqKgqFtZW11sc25tcGNzeXmEgYV5b2dpa1xia2CuoWZyw7e7u7musrW5aGJic2lhZmS0r6WftbnAZGhxcnVwbnRvalltbIRvcX9QVVNsal6uWVhea1RTgFpLUZ6XWlNTVFBNkJtYWFpbV1ZXXV9QTVVVVqhcXl9bYGJmZmFiZmVmZGZeY7BiXF9nXqdXq1teX2FhX2FlY2JmYmpqamFpZGhncWVqbHVia1+6qrSssbPGr8O0sby5n66wpKWoo5WfjZiWkYKXl5mhopyampqYlZaOkYCWm5aOgJWPm4+RjJSbmJyQipeJh4ONjYyblqKdmI2Xo5KPmpqcmaWXkY6PhoOgp6SUmJialJOOi4CAiZebmZ2Sk5KSl5yTj5aZj5SanaSgmZmQk5eYm6SfpZCMmJeZnZ2dmaWbnaCgo62op6KipaCno6agoJecj5iVnJ2fpJCfnqKjn6WjgKCVk5SVkp2fmp2cmJifpJyXmZubrKerqZ6mn6Ovr6i4sqSon6uktLlmYL1rdG1y2XRzdG9fZGt1g3psbWlpbWlkam1tcGdtam9oa2pnY2Jrc3F1c3VvbnRxZ3FeYrhhZGZkZWhqaWVlY2hiYmdmb2RoamlnZnNzeXuAd3Buc2x5gHl4cHpzeXGBfHqFiImJjIaKiImNjYqKkYiHfISGg4WMjo2EdI6Mi5OTi32Li32Be3xuYGJqbnNvbnSEeG5ydnOKf4yRiYaLkJOPgICDd3N1f3Ryb2dowNHTxL/Ku7rDvLzFxMbLZ2VpaW9vbHNzbHNyZW1xZ2RjZ3Z6dnJxdm90gHqHgH1/fnBtbWdsf3l4amtuZmVeWFq2YmZla251a3FvaW1zvnFuaWpwdnN2c3VxcW1qcHZ9hoKBhX6HgHd7jXp9eHJ1dXRtg3RxaGtqgI9+eH+HfHh3e4KAenl+dm9zanB0ZGJlZ2xuatCCdHtreXVvd3N4cW50cHOCend2xMBzgDM9NjgyMy8wMDI4MzxtQT1LOzk9OkVDQ0I8ZWI+OlYxOD87RT47RUlVSUJBQzlIOz1AQVFaVE1DSEM+OjpERExCSklCSlFQTUlEVFVFQEdLQztGTk59fZKPR0VDQUJ0RUB5d3k+QjxcWHZpOTk0RDQrMCsyMC4xOzg4OUI5Mi49gCsmNTw8Mjk1Li48QS0vOjk0Mz41P0JJTUlJT0lVRkxTTE5FcXaLkklMiY+IjpajopaaUFBTTE5LSExNRkVKT09QT1FQkYyRg4iWiZKWlZqUkJSQkZmLkImKgId/hnNGPEJWQW1oZWheX3ByYlp3aF12dEM/QD86QD07Njg8PjU7gEI7RWlXQ1p0c2dtQj1BP2xkOTlLREI5QERAQkN5Zj05XFk1PDlBPjRoWlhSWDQ2NztKTUZDRDVDREJKRkc5MCkqLyQvODFURjpGaltjXVtQV1thPjc4Sj43OjldWEtDVVZaLzI6Nzg0Mzk2MiM2NkY7RFMxNjRKRz1vOjtBSzc4gD8zPHlxRT46Pz4+cHdDPj09OTo4QUM0MTk7PG49QkI7OTY3NC4tMzg9PkI9P2g+ODtFPmc2Zjc6OT09Oz5BPTo9OD8+PTY9ODs2PTAyMzoqNSxeVWNgZml4YXZpaHRxWWtrZWtxdG9+dISAd2V2b21zenl8f4GAfHpxcF13eHRrgHJtfXN0c36DgYaDgJF+dGtyb219dX54dGl2gXFxhIKEhJCDg398eHORmpZ/f316dXZyb2RiZnBoY2VaWltbYWJYVVtgWFxdZnFuam9obndwc352c2VeZWlxcnN3cXhvcnFzeYR9hYGDiIKOiIp+fHR+cHh1e3+Ch3GDgYCAen99gHlpZWhpaHV7dXp5dHZ/hIB9eXdzfXV9fXB6cGx1cmd0cWVrY2thb3I+NmI3PTI0WTQ0NjMkKTA5QzouLy0tNDApMDIuMSosLTMvMTIvKikwNTQ5ODszMjg4MDosMVwzNTY1NztAQ0RFQkU/P0E+RTo/QkJBQk9OUk9SS0dHTUhSgE9KQUtITkdTS0ZMT09TWFRWV1lbV1VTWVJPRElLR0dRVFJKOk9NSlJTTkFIRjg8NzotJCgxNzo0MDZBMycoKSY6MUBDPz4/REhFOjxDOztCTkVGRj08anp9cHB5c3mBfH6Hg4aGQD0/PUFAPEJAOD48LTc6Mi4sMEBBOTMxODA3gD1KQ0FFQzc0NS8zQj09LTE0LS0kHBs1HyQlKS01KzAwKy4zQC4rJycvNjU5Nzk0NDEuNTpDSkZFRT1COjQ8UEJKQz1BQkE6UUZEOz09TltMRktPRkBAQ0dGQD9FQDk+OUBIOjk+REpJRYNaS1E/SkE5PTg+NjQ+Oz9NSEhHY1w+jXsBeox7BXp6e3t6tnuEeoV7CXp7e3p6ent7e4R6uHuEeoJ7iXqSe5p6hXuPepF7iHqEe4J6i3sGenp7e3p6hnuFepl7BHp6e3uJeoh7h3qXewF6iXuCeoZ7gnqOewF6kXsBeoV7A3p7epx7/3rXegN7e3qEewF6rXsBevh7j3qxewF6jHsBesV7AXqTewN6ensCAgQAgJaQkJaLkZaSiICCgYSFiYWMlYyCjI2UlZSPj5CfiYKDhvr5k5CboLK7rKmkm5uWl52bnZy9sqGZnouNkZOXiKCHiYyKmqOciIaE8oiE94iSivyRkJmHioaIh/yChYX+lpPo6PvvgYHy+4H/goKGi/+KhZeIhIPtg5GH7PqOm5CdgKidl66qoamgqp6lsJKbo56ckI+TlZGao5eUopuVlpKQhYePgIKB9oLt7ODp9eTx+vaJhZGLi42LjIGIh4WDiYKHiIWC6vPe2uTo5fHk7fbq2trSzN7d2Nn48vTv4t/v7Ov0++fd4ubi9NXCvMe529Tjg4aMjY+QopiqpauMjpCZgIelkYSon6SjkKWopKOUnIL08IKH+vL16N74+tv+iZWYmp+erqqakISMjYyVmZmWlZCYnJSaqZ2cjpeVr5mOg4mLlp2Pj5OR+PqKhJmSg/6Tk5yao7SMhICHmpGCi4aB9vn/7oKKiYaQ/u3vgYWJipCrm6X3huiAio7W/aWtppqFgISLlo2C/YGIh4uQho+Tkp2npZ6kl6Sko6qnoaanp6Kgqaqupp2NjpCOhpKNk5CQi/n9goSG/5OOkJeRl5WLhY+IlYSF6oWAg4iJjY+Ui4uEiYOEh4Pt8+vv8vPZ5/Hw8vXs8oD9+vH76uHl2OLw9PKB+Ofh/Ofc4un159bS1uLogOHb5N/j39XRysrX19rZ0MTX4fTt7uLg0NPU1MbV3N/f19DR4sjBzcLNz8XPxcjItb67trq1ur7NzM3FtLfCu7Kuusi/zcjQzsHHwcbd2d3c6+3q7ePi4NHS3N7h6eL48/Xo0cfR09Ph797q5OnJysK3pbzJ3vXu9vTu5+nl6ejLgOTLzc/MyMrS5OXS19DUxs7h6Ovq+/f65+nu5dzl+/Pq7/Hm5vqD/YGMiPKXh/6D9YGChP+CiICImZmC+P32/vuEiICKkIiEh4OEiPiTkIuFkaOfiYySk5WRkIL4+oL+hoeLkIOE/YCJlo6ZkJOUj5WeoKOcqaGnoaeim5aWm6GcgJeblJucpaiypbG8wcTR4uLw6Ojn6N/Y3te7obWzvcTMycC3mLPA1NHIu7zFrbCvr6+iwq64rKKsmp+wsK2osKyzvL3Ey7u9x87RxrrBopqinZqPjI+C/YDz+fjg3ezN1uns7O31gfT9gIGA/IGEg4eGiYeQjoL29oDo+/7+hIKXgK+ipZ2bl6SmqqOqn52eoKGnlpKXio6Mh42akpCMk4qRi4WIioymmZafoZ2gkpaSjpCMjJSappqem5CdoZCTnJ2im6iWgpOVnqCemZmRkZGQmKKln6GWoqWcnJWTnZ+cj5Wmj5CLgPiB9vXx9en4j42OkYKJg4OBgYuKio+PgYaKgHFraGliaHBua2NlYmhpa2p0fHBqbm52eHduamt5ZV5eX66samhxcX2Fc3Nvam5sbXFubm6LhHhwd2hmbG5yZnxqbGtmb3FrWVpapWFdpFxkW59iYGtZWllVUZNOU1msbGiQkaqmX2Gztl26XV9mZ7ZlX25iYmKzaHVpsLBgZFdfgGtmZn99dnhwd219hW93fHZvZmNmZWJmbmVjdG5samVlXV1nW1lXpFWWk46bppaenJpYUlxVVFZTVEpQT01LUklSVlZVlaSVk5yfmqGTm6ago6qprsC2s7fBvbm2pqKrpKWwva6nsLu90LqroLCgtqGrYFxdXVtbbF9tamtXXWJpgGB5ZFt1cHBkVWRmZmtgbFilmVVWlIySjIiloYWhVltfY2dqeXNjXFBWV1lhZGlkYltaYVxicmxrYGlqfWxqX2Rnb3dsbXJtubZlYXdzaNB+eoJ+gJFtZGBnd25jamNkw8LLw2ttbGZqtaSfVl1fXmZ5Zm+VU5NXY2qbtnh+dmdWNlBTXVVOl0xWVVlfVFhYWWNwa2JoVWFfXWptb3VxamdjbXN+fXhqbW5vZG5naGZoYrW7W11bp4RggF9lZmFbZmJzZmm8bWVlZWNnaXFpa2RpY2RnZbS8sbW3tZqntrO4va+pWq2po7SnpK+bmp+ioliln5qxn5KQlqGVioaOn6OgmZ6aoJ+WkI6JlZKYlI2EjZCWjI2NkIWNhY2Gj5aZkoiHhpiBhoyJkpSLl46Nk4aJh4Z/gYqNm5+ggJSFipCRiYaPl5KfnKGklZiRkqCVmZWdoJ6dl5uek5Sbmpmck6Ccop2TkaGnoqWvl5qdqZOgnpWKmpqnraChopyfop2spo+fmaKfpJ+em6mom6imopOTn6Geo7e0u7CytbKurry4rK6so6W9ZMNha2i+eW/acdlsbmvIZmllaXl7gGnHzsnOyGhqYW1zbGppZGJjt3Fwbmx2hoFqa3FzdHJxZ8DHY75nZmtvZGfDXmJnYmliaWlkZGpoa2hzcnh3gHlzbmtwdHNydnR1dXl6gXV5fn98iIyNlZKUlJaLho2Of21/e4GHi4uIfWN6g5aRh32Dinl9d3Z5cIh2gXxzfnF2gIJ9d3Z8dH+Hg4qQh4mPkpiKhZB1c3l4eW1pcWnJacvGxL+6yrK2xci+ubtlwMlnamzabnFsb2xvbnRyaMHDZbjP0dVuaXSIent2dXB4dHFpbmhobXFveGdja2NlYWFjbWdnY21obG1mamdrfnBsc3V3enB3b2pxZ2hvdIF9iYN5RoqKeHR7d3x1gXRkdHFyb2xpbGdvb21ydHltdW53fHV4dG51dnFmbHpmaGNbtWXIy8nNv8xzcXN1Z25tbG9vdnNzcnBjZ2iAOzUxMysyOjc0LTEwNDU3ND5EOjU9PkNHRD89Pk05MTM0VFI8NTk7RktDSEhIS0hHSUM/OlNMREBLPkBFREY3Szk+Pj1KUExASUh/TURsQEc8YkREUkZKTExGdz08PXRNTGVnf3lEQnJyO3U5Nzk3VjMtOzIvLkkwPDRMTjI1KS+ANi8tPj01NzM6MkJGND5EQDsyMjY1MTU+NzdIRkVFQ0RBRlNLS0qOSH19epCgmKippF1TXVZRUUxHPENCQ0RMRk9SUU+Ijn94f4qIlIuSmpKPlpWWpZqLhpONiYNzbnJtbHN6aFxgZ2yDbmdkdmqDbnZFQEJCPT1JPUdBQjI5PEWAOVA/N1NTWE1DVVZUVklQQHZtQUR1bXJqZ4SEaX5BRkM/QD5JRjs4MDc3NTc8Pjw8Oz5DPUFKQD0vNTVEMS0kKSs1OjM1PzxdXjkyQTwwW0FARkZLWj86NDpHPjE6NDVfXWJXMjU0MjdVRUIqMTMyOkxDSmE7YT1FQlRmS1BMQzeANT1IRj9zNz8+REtBR0VBRU1JQ0k8RkE+SEtLTk1KR0RKSk5IQjQ5PkE5RkJEREdAb3g7PjxqQkA9PztBPzk1PThHOTtgPjY1NjEzMjYuLykvLC81NVpmYmZtblNga2ZqbmJnPXh5eoqDho55e399d0B7dXaUg3h2eoN2amZufYOAf3iAfIKFgXt6eIN+hH5yYmpvenZ7eHpvdGxzZ3GAhIR/fXyUd3R8doOIg46DfoBtbmpmXl9mZGpsa19PVFtcVFFaYFxoYmduZGtoanlyeHB4fXl0cHJwZ291dnt9b355fHVrZnV/fYeXfoeIkHZ/e3NndHSDinx8fXt/gn6Ce2aAem10dHlycXJ/f295dndrcoJ+eHeAdntvcXRyaGh2b2FkZl9fcj5zNjo0TT0wVy9TLS8uUiovLDI/QDBWXltjXTAyKDA0ListKysrSzk2Mi82RkMuMDY5PDw9Ml9pNmY5OD9EPEF7PUNIPkQ6Pjs1Nz8/QT5JSU5LUUxIRkdPUU6ASEZDSElMTVFDRUpNSlRXVlhUVlZWTk1UVks8S0hMTFFUUUszRUlSUEtDRko7PDc3NCg/Mj45MDksMj45Mi4zKjE6ODpDOTpARUg+O0YyNUJES0E/Rz1wPXF0fnp7k3V4iod7eXxEe4RDQkOBQUM8Pjk4Nzw6MFVXMk1eXl8zLDyAUUNGQD46Q0A9OTwzMjY3Nz0tKjEoKSYjJjAqKykxKzEwKSwqKzwvKjAzNj02PTg2OTAwOD1IQ01HPUtJOjtBQUdBT0EuPzxBQ0NBQz5CQD5CRUlAQztDRDs+OjdBQkE3PU48QD06c0KChIKCcXhIQkRCNTs3NTY2PD0/QkI0NTehe4J6p3sIent7ent7e3qIewd6e3t7ent7hHoGe3t6ent6hHsBeoZ7Bnp7e3t6eqp7Anp7iXqTe616n3sEenp7e4l6qnuCeoV7AXqQe4R6hXuDeoh7CHp7ent7e3p6insBeqp7Bnp6e3t7eo57AXqQe456AXuMegF7/3q1eg97ent7e3p7e3p7ent7e3qHe4V6i3sBeo97BHp6e3qGewF68HsCenuNegd7enp7e3t6insDenp7hHrpewJ6e4Z6knsCAgQAgJGFhoeHh4iRjYiMmYmBl5CUmp2Nk5+klZuXn4yklZidoo+Ti4qSnqW3va6kn5qNj56gmbihi4qM/4CGk5iA/on99YeGho2Kho+Fm4mGlo+QkImNjoaKjp6Tio2J/ImAhomK9ODm4fb6kJv86/aHgoiSj5KHho2OmJCXg4GYhJCPgJWWiISHgaKZmZ2Gk52soIqHi4OEhYOHh46IioyGjYiQkoaIiIeBh4uQgenngfP8g4v/gvvq/4SFioiRh4P4+4CCgIL67urs9/Xw8+Pl5/P19N3Ry9TMz9vpgID3//ne2dDt1+fx9+7s6tzgy9/S2eDt+oaYk5mBiP6A/4GEiZejgJqVjomSiPz+gIOTlPT97+fm7P6LioWDg4v37fH6g5eQlJmQiuqOn6GPjIaLkYSOkpWbn5qml42gqMC3r6Ozu7SgmJWWkpP/kIaWnaKclpSI8+jxhoSJgoaTjJOQjYiSkYaK84b95oWHj42E/Ybu/+786Pr7jYmNhIT78IP56+TgEPuBioOHhYKCgvr8/YWQiYqEi4CJm5+UpaelraKbr66Zpp+0opyboKiXr46C/vjn7vmBhouTo5iI9u/rhfWRkpGIh4r36vH954KPkIyNhpKLgonv8fDi2Obl4++BgoyK/N3ygof+8uP44d3u/Pn46fb1gOjw2uj38+ru1u3b59vX2Nnc18/Y0tjc6uff393q5+bl64Dn7OPd4OLI0Nnp6NvT1dPOy8TP0dW3x8m6tLi6v7SvuMO6vMC/xMy+vrfK2dHf4snEwsnAuLu7vMLK1cbGxsHQ2cvW2MvK2d3b4eLs7e7c0NHJytbT2tXU1sbCwL24ycnX6uHb09zi4tzU59vr4NPN29zk4uzj5ujj5+vl5uHi2YDg4Ofq+/H6iImIhoWE+4KBg5CJi4KMiIyFh4SMkZCHmY6Oh4GKjv72h5KLipGE9/nr5Oz5gYGJlI6RjZWYk46Ki4b+gfyF7/Hu/oCEi46HkIiHlpegnJmjmJekv7i4xsTAs6mlnJWSio6WlJOamZuspaO2wcHJy8PNxNTR393f44DP09LGzKurwsC4w8LIurmfxbnE28bAv7SluLyzs7LHw7u5sb+qqsarrKKfsr3LycPNsrm6usK7triYjaKhlpSbkpCLg4Lv5tfiw9fU2+v69/iC8eXv7fD7g4KEhYuPjZCWhoaGiZDtg/LxgaeclJuakZylo6ihrbSanJqmmJuRj2yPlJWbl5KUhP2D+Yr15oGIhJWrlI2fnp6alpmEh4mEipapiY+Ng4KJiYaVk5eLlZ+hnqqWopWLkY6PkZSdnaSxpaOhrrKxnp2Vk4+UmYyKiPf29vX1/e/f/t/u/IuK+oHm49/3/v7l++/zgoiAcGNiYF9jZmloZWh0Z2Fwb3Z3d2psd35xd3J6an1vc3Z4bW9qa3B1dYGCdG1paF1hbm5ohXRkZGi2XWBtcly0ZcG/amdjZ2VhXllpWFNgWldWUlVXUVFRX1dRWFaaWlRbYmOqlp+bq7JocbWmrWNdYWppa19fZmh1bnVhWmpUWVeAYGVaW2BadGpmaFdodYd7Z2NnXl5eWlhXW1dZXFldV15fVFhaWVFYXGJWm5xaqKpWXJ1NlIOTTU1RUFdPS4yUTVBQVKikpamxqqaglJmYqamsoaGquq6xsbFhXrO7tZ+YkamYqK63sLW6ur6yxrizp6itWmhna1Rep1GkUlFYZWmAZWJaVlpYnJxMT1tbjpyUioiIkk1NTkxUX56UnKRWZF5iZWJfjmFra1hZU1deUFdbYGBhXGRdVmRtgHp1cH2Ih3pzdnp6ecJrYmlwdXFqbma0qrZlY2dhZHBsc29xa3h3bnLPc9nHbmppZFumW5qkmqOPmppWV1xXXLOhWqmUioiAmE9VS05OTE9Tn5mbVVtZWVxhW1dNWFVJW2JkcGhic3Jhcm19cGxsc35ujWhftrGeo6xYW15jcmtgq6umX69naWhlZW3IxMfSumFwcGlqZm5nYGaxr7Kqn6ytrrNfYWhot6WtXmS2rqe6paKssammmaiqWqOpkJuhnpWbj6icrqKAn5uWmpePl5KWkJKQkYqLnpaTlpGJkISGjYp7g4mbn46OlI+Ig4CCjY96jpSMhIuMjoiBhJKLi42MkZaGh36OmJCcn5ORkpqTiouMjI6XpZWTlpKdopKcm5ONlZeUkI2XmqOenKKioKiio6KfqaClpaCbpJ2hqpyWkZqmqp6Zp5eAoqGbm6errqSroKCqp66xqayprKy4uLy9xbOzY2BeX1terVpZXmllZmBqa3Boa2dtb29peHF0bmluccCzZG5mZXBjvMvFvczTaGVncWlraG5yb2xoamrTatRvysrEx2NjZGlfZWFhaWpwb212cW5zg396hoeGg39/fHVvZ2pxcHKAdXNzfXd1gX57foF8hoaOhZCRk5SChIR/iXJziYR9iIKHf3pkhnaCloOGiH92hIN7e3SCfXp+fYp8fZZ/fHRweIGJh4WPfYSDhI6HhIdzbnp4bmptaGtoaG3ExsTQvdHMxsnFtrRhv7rGxsjQamtpZ2xxcHF3ZWNlaG25a83OcYyAf3V2cWZscmtrYmtzXWBmb2ZrZmlsbG5xaGNkWrRkvWvAs2JlYmp5a2FxcXJ0dnhqbm1pcXyKcHVxam1wc256dXpteIOFeYRueG1ka2dpamhtbXR+eHV1g4eHe3pvbWVqb2Rna8fKysvIyryvx7S+w25vxWnAvcDU19jAz8LCZWmAQDEwLi0xNDk1MzdANS85OT5AQDU6Q0o+RERMPVFCQ0VIOzw2MjU6P0hSTUlMST0+SEU8UkE0OD9qNz1HSjdqP3JzRENFS0ZESUNXRz9HQj88Njk7Njo9T0lBREFpQDtBSEp7a3ZyfYNPV4RxcEA5OkA+QTY0OjhBO0IwLDwoLSuAMTQpKCslOzQzNSY2QlFINjQ5MTQ2MjQ1Ozk9Pjk/O0NHP0RKSkJJS1BFfYZQmqNSWZxMlIWPSUhIRk1GRYSRS01MS5CFfX6DgYqSio+OmZmZjY2SoZGIg4NHQ3yGgG5pZHljbGxvZWhubXlwiICBdnp7PkpISjQ9ZzJiMC4zP0aAQUA6Nzs+cHE7QExOdX91bmpocz4/PTtBS3pzeHg+SUA/QDgyOjQ/QTQ2LzI3KzY8RkpMRk1BN0NGUEpANT9HRDo2OT8+QWM8Nj0/QT41OTRVT1w5NDkyNEA6PTo7NEE/NjlcNl5MMzI2NC1WNlZgXGhYaWs/PkI8PXFcOGZWT1GAYzQ7OD9BPj9Be3p8SE1HQkJFQT05RUM1QUZFS0Q/UE46RUBLPDg5Qk9BXj84cGtYYWw5PT9EU0s9YmFdO2VAQ0E+PEFwamxzWi47ODEwLDUvLDJSVl1aVmJjYGIzNDg4WkxcOUB0cnGKgn+Kk42DcH5/R4CKc4CFgHd4aoV3iIGAgX9+gXx5gnqAfIJ+fXVzf3Z2dHd3fXR5fHlobXOHjH+Ei4iCfXV7hId0jJCEdXdxb2JZWWZhYGBhZ2tYW1JhbGVwcGVjYGdmYGNmZmhvfmhnbmhvdWVtcW5rd357dHB5dXx1cnd5eoSDg317gHR4d3Rzf3h9iHt0bXeCgnZtfXCAgX52dYOChH1/dHR5dX6Gg4l/fXZ8dnd4gW5wPzs4NTI0XTQyND04NSw0MjYvMC0yNjUtOTU3My83O1pOLzk0MTkvU1tSSlJaLisuODEzMDY6NzU0NjZsN248ZGZlbjc4OkE4QT0+SUhKRkFIQT1BT0pFT1BSTUtMS0hJREpSTkyATEZFT0pHU1FNS05NVlRaS1NRVVZIS05MVEFDUk5ETEtSTEs3T0JHU0ZHSEA4QkE6NDA9NzM1Mj8xNEo4NS0qMjc+PDhCMzw7PEZCQEMzMkVHQz9FQkM/PUN1e3WIdIN8fIOFfX1Ff3eAent+QD47ODs8OjtBMDEyOUBUOF9fOFSAST9DPzZAR0JDO0FILzAzPDA0LCotKiwxLSstJUUrTDJOQCcqJS05KiIxMzk9P0I0ODgxOUJSOkI/Njc5OjZCQEQ4Q0tMQ0w7SkM8RUJEQz5DQkdSSUVBSUtLPj03NzI7Qjk8P3B5f4GIj4N3i3F2dURAaTxhW11ucXJfc2trOTu4ewF6hXsEent6epp7AXqFe4Z6BXt7enp6vXsMenp7enp7e3p7enp6h3uCeoR7lnqCe5d6hnsDent6i3uCeoR7h3qGe4R6h3sBeqF7AXqJe4N6j3sEent6eoV7Anp7h3qFewN6enuFeoh7g3qhe4V6h3sFenp6e3qGe4V6inuJeoR7BXp6ent7jXoBe/96qXqGewF6mHuCeoZ7hnqOewR6e3p7hHr7e4x6AXuGeo57BHp7enqeewZ6e3p7enrCe4x6BHt7enuKeoJ7AgIEAICG//qC/PWVjYuFj5CDjY2RoqOynp2wp5ihmKuUnpSXqbylv7Goqs2usrKwpZWOk5WeoZ+un5Oek5eKjJKSmYyD8YKRkZuWioGL/fHy8fmPkZKKmqKdm52mjpiJk/zziIeCkI6B9/OBhPD++fCC6t3FxNzRgJWT+Iugiunm8PCAiYCCgYmJlJOcj7eciJSZmYr74fGChoyOhYeFhICE+4SIgYyJjYiG/vP/hICCgYmFgYODgPX/8/7z7Ov7+PiC+Pb2/v3x6OXl9OLs4+Xe4fP7/vfs++71/PX1ioGC7unw697Y5N3j5fze3urk5MzVz8ja6/H0/faA5uaGj5iXkueCgYDx8IKSkI2J9fHjgPL++ICDgeqEhIKLjffv49nV2+WJk5Sfpqa8zbCYn9Gpm5+ej5GIm5qqvK6enpeMiIqbna2ekIv4jP/n74OEk4yIi6KNlPT8/N/+7OyGkI6HoJ6qqZmGgY6NjoycoKWhlpOJh4mMiZiBgPTp+/Hw8e+EkJDzgoCTnp2kjpyIgObs9feDgYODjJeimoyVm5mcq6Gsi5mhkomXnqSeo6CRlqaWh5WI84KKi4mLkZCCiO+E9uaAjIaE/+qC5eXl4Yb8gobx9of/ioiD+/P4/+uDh+qCkJ2blO7n9e2DhvyD9/j0+YKE+/yCgu7c29/U0OmB9Pbo7u3u6IDh6NPa0MfIy9Td3+jt3uzu+vDt9Ob04OTr9ezm5tnc2NLD19LGvM7Axr++yrS5tLaxs8O9triyw7zLv7Kxu7/DwM/O09rSycS/w7GvtcXHxcLIyMvOzdPIyNfi0dfn5O3s8c7OysC/yOHs4t3f1dTXy8vax8nR4OXT1dPj5Onn5IDf1dLWzce/3ub37fLr9evq6/Xq7YD29Pv5goiMiPyQjo2eio+MjZGDioSBg/HxipKYlJWRjJKSkY2Phf/3/oGGgff67fLv5IWLhYqK9IGJhYCBjIyJhYeC6//5gIWHjoWOjYqLjJeTnp6VpJ6fo6q4wdS7sK6gnZibnY6Qi42WkICOmKasprW8xMjJxL/GwtnQ49Xk4NvY08XLs7W6vMTFvta8v63Cy9XH1sTBwbC0pa2xt727r62upaaqq7elpKOrq7C0sKiinKmxt7SymJaRnqabmZaXjo6Ngu/59fDj297o/YT46ffl8fHo9vTt4P2AgPuGj5GIhYmDhoWFgOPa+YCIjpaXi4uKkZanmpaXlomIjJedrLKxnqOem46G7PP6jIP3iYCBh5yZlZOXhIuUhYeTipGlpZyLlpKVio+NnJyKivqQm6WfqKSmmpWKjJKRkZuVk46co6SjnKWelJuUlIaB8YLx5vHo5uDc3/bl4/rm7+X9+Ojt5ePb7unriILn5oBjvrxfuLFvaWVfbG9hcWxxhH+Dc29+eW51b4BrcGZkcYJwiIB6fZx9f3x7dGhgZmVpa2p2a2VwaW1fYmhpcmpjwWlyb29kXFZmtqejnJlZVFZRXGhmYGFsVl9XY6GkX11bZ2hcsrBeXKGppKNbqqeVlKmZXWtpqGR1ZKSdpJ9WXxdaXGVkbWlsXHljUWFobmK5orlkZ2hlU4RRgFWfU1VPWlpgX12nnadUUVNRWFVNTU9Mk6CZpZuTjZuSi0uQkpekp6afmZyomaSanJWTnqSorJ+no6a3urtlYGKvrbevoZ6hpauuw6uuvcHFtLy4qK6yr6uwrVqVkVZcYl9dkFVUpKZZZ2hjXJuTg0yJj5FNUFCES0lFTVCJkoyKgJOXoF1hX2NoanmJc2BhjmpgaWheXFdiX2pxZl1eXVZXWmxue3BkYK1pw7G+ZWRqYVxgeWhzuszOs9XDxW91dG9/foiKeWpqdnFzcXt+f3xwbmNgYGJfaFVUnp23srjAtGJpZppVXWBfYlRfT0qDjpykWFVYV11la2NUV1dWW2llgHFXZG1hWGZqcGpvb2Rse25fa1+pXWVjXmFkZVxhqV6sn1pkX162r2S1vLu5c9dtdMnIbMZjYFyrpqu3qmFkpl9pdHNvrqW7q11eqFinqamvWlqmoVZbpp6joZmOnFednZefo6qhm6GLk5GJjJGZmJWdnYaUnqahn5yKlXyDipCOgI6Sj5SNjICGgndxgn6FgoiUfX1/hIOJl42NkIaTkZ6Sh4aOkpWOjouQmpCKiIuQh4uQmpuWipOPkI2Vl4yRmqCPkpqTmpiok5ykoJeXp6ien6OhqKmfpa2ZlJiho5mcnq2orKGcmJifqKGim6y1va60rbKppaetpa1iwMHJxWNmgGdhrmhlZHJeYV9eZlxiY2drwcZydHd0c29tb3JwbG1lwsDHZGlmvsW3wb66cXNobm2xXmZlYmZwcGxrbWvH2t1tb2ltYGhmYmVocmlycWx5dnt8foOIjoCAgHd6dnV7a2lmbHFua3J+f3Z9fnx/f318hoaTipiNlY2Gf355hXd5gH6Cg4d+j4F/bn1+hXuJhoaIfHtudHZ+f4B4fIB7f39+h3Rvb3J0eX57dXBncnqCgoVwcW5ydWxqaXFucnVvy9re1s3AvMHPZr+3wrXAvbjJzM3J3HBu0W5wbmRjaGRobG5uzsXZdHV2cmdmZGZocGRgYmJdXWFtc3+GhnB6bnFpZWSsvcBpYrxkXWFhb25lYmdaZHNlbHdrcIB9cWt5c3ducGt4emlwzXqAh3uAeXpvbmVkbGpqbm1qYHBydnpxeXRrcm5uZGPAacXI0sHDtKSot6Sjt6WwqsDAusHDx7/TxLtuYqmogDZkYTJbVkE4NDA6PTI9NzlIQ0g8OkhHPkVCUz9EOjdCUD5RST8+VkBCRkxMRj9FQ0RDQUg8ND47QTY8Q0VMQz5wQUpGSkY/PU6Ge311dEhEQztDS0pHSVdDTUNNc2xFQT9LTEF+fUZGeH97eENybFhSaVg9R0VcO0s8WFJbVzA0gC4uNDQ9OTkuRzcnNj5FPm5abD1DRUI2Nzk5OjtpOj44RkdOUE+KgYlGQkdJUU9LS0xLjJuXopmQh5SOjUqPj5GZmpOIgXyHd4aJkImIkZKVloeSjJKalJJOREVzc397cXBzcHBse19hcXV+b4B8c3mBe3V4cTpZWDc7Pz47TjU2gGdmOERFQj5lZl48dHx6QURBaD07N0BBbXVuamtmZ0FCPkBAP0pXQzIzWT00PT85OjVEQUxVSkBBQDg2OEE/SDowLlE7altoPD1ANSsuQTA8T2JqVHJkZD1DPzZERU1PQTU0QDs6NkFESEY8Pzk5Oz87RDU1XlxwamxxZTtEQ1w2gD9AP0Q+TD46Ym17fEdDQz5BSVBIO0BBQUNOSlE3RUs8MjxARDw/PjQ9TUM3QjpcN0NGQkRGRDtAYjtoXDlCPDptZj5obWxmRXg7PV5dN14zMC1QTFFgVjc5VTM7Qj87TUZcVzc7ajtzfIONSkuKh0hMhX2DhHp1f0iEgXqGiYqFgIGEcHd0cHR4foJ/iIt0g4WIgn2AdYRvfYWJhoCBfYF+g3mFgXdtfHB1c3uIcW9ubWZlbmFeX1dkZHJoXF5obHBrcGtxd2tkZWZrYWRoc3huaXRubWxsbGZueoN1d395fnWBa3N8enN4jIt+e354goR8hZF6dHV9fGxtbXl4gH14gHh2eYN+eW+Bg41+gnuGgYGFhXl4Q3p2fHc9QD82Ujo3NUQ1ODQ1OSwxLi4wTU40Njk3MjAwNDk7OTw1Y2BmNTw3YWdXWE9EMTQsMzNKLTY0LzA6Ozk5OzlhcG43OzlAOEBAOz08RT9IR0FNRklHRUlPU0hHSkRJRkdOQkRCRkxIgEFGT1FJT1JRVFNNSlBNV0xVTVdSUVFSUVlMTk1NS0xGWE1OQUxKTUJLR0dIQD8zNzQ2NTIrLzMwNDk6RTMwLS8xNjo7NjIsNTpAP0ExNTU/RUA/PUY+QUQ/cYaMjINzc3mKRoN7iHeDgHWEg3pwhkI+azo9OzEwNjY7PUE8ZFlxgD9ARUM5Ozs9QUk7NzY1LSwsNjlBQ0Q0PTU5MS9DTlQ0LlEvJigpNTQtKTAnMz8yNT0vNEE/NTFAOj01NjNCRDY9Z0dMT0RJR0xERD4+RUNARUNBOkdHREU9RkA5QT1ANzdnP29uf3h8eG90hnJugGlvY3dzZmllZlxoX1o+NU9RBnt6ent6erp7AXqIe4V6jnuCeoZ7BHp6e3uEegF7hnoHe3t7ent7e4R6kXuDeop7AXqIe4N6inuKegF7m3qDe5p6A3t6eoV7BXp7e3p6hXsLenp6e3p6ent7e3qFe4d6pHsFent6enqJe4d6nXuHegR7e3t6iXuEeqJ7AXqJewR6e3p6hHsDenp7hHoLe3p7e3p6e3p7e3uFegN7e3qFe4R6BHt7enuEegZ7e3p6e3uHegF7/3qcegF7hHqEewF6jnuCeo17Bnp6ent7e4Z6hXsBeot7g3r+e4l6AXuMegN7e3qLe4N6nHsGenp6e3t6n3sBep97Anp7mXoEe3t6egICBACAkJSEgoDj/fr9lJWHioSG/YyGoaKZpJeFhJaemZydlpWhm6KRpKqhq7a7uKSbh5OWoKeexKaZnZeM/JaWkPHr7IKLiZOek5mPjv2A/P2EkICHhI+PjJSRgZGMhZ6TgYSPhZX/gYiAgZWNg/Ls+uT6lo/6693+h/Ln8ISB//78hoiAgIybiPuAlJWLh5CE/ufp5+rd5P+BlpSNlpeLh4eLipeRgO3l5+vi9P75gfOD/fPn5PeA7vP38OLZ7d7d6//37+Hi6drb6+3w8OzV6+L1+PT1gP76i4L8+o6AhYL/6ufk2drY3uT08vjz6ebs/Pfz+vfd5ePg5ojqiI2Pg43u2tyA1t7Y7d/5m42HhvSCieDs3Nf2iYKKkpKK8NLd5e3wjY+alY6WqLusmZqhp6yhjZCYoLm3r6mtsbOpna+4sr+rp6izpZH6lJaFh4qKhZSMk47vgPr19/Dr9vr9gYyMkfyG9vmMgu3p///7iYr04+Lw8oGVipeLg4eUmIWJjYL3hZWAnL2lr6epoJ+ZmKORkYeLiIqKg4OWkqueu6GShoaGi4aEhpiQgpyatp+VhoWKlZWUkIKMg4Lx+YD5g/iB6OLk6fXW2tnhgYKH+e39iPCB8PSAgIGHi4eDiIjwg4nu9oLf1uf/jYX4hPnz4eT0+veBgv/s4NbJ0Nz1genx9oDx7d2A++rs6eDs2M7T0PHx+fvn3env8vL16+3b3+Tj2s/Py9DW0trPzMvS0cfKuLypnJalt7+6tre3wsK9zLe/qri8ysrE0MfHvsO8ubaxtLHFt8O5vsPFx8DF0dbU5e3f5/Dv6N7TxcfO1NPj4Nnf39vXz9Ha2N3V4/Xf1eDY4/fy8veA6N3a0dLR2O/2/YKD/oL+hISDgIX9/IDx+oaLh46KjoiLkYqOiZ2YkY+Mi4CDoJCQjI+TiIb6hvmG/frm4e/18faDhveWjo2Lm5mNgIj++/X4+PTh+/jz/faB+IqHl5KUk5ORk6u/pLmhlZGbpqipv7rCvaisqpycpqCUmYyLkI2AjZebsLK/ubXDusLNwbq9xdDT0dPX5tTOyK+7try6usHEx8W1wMvW3NrZ0c7G3b61tLextbe2saefpKS3yrmosKKprKmqpKKmtKels6+yrruzoaSfm5ybl42LiYH49vft9fft8+v28+rv7/Hn2d3w+92HhYiPmYqQnpWMhoX12uaA7YWOiYOLgISBkY6VlJOIi5WOm4iRko2HhoCCgYiH/IOE+ID0+YiOjpyIkouEh5aFgpGVk6SQkpSQjZGXjZ2hlJmSkZqbs6Oeo5qcloeFiZqaqKWloqupiYySlpaFl5WPioWKi/31hoWA8Or+i4r36u2AiYmLj+j21efz9v6A8v+AcXNpY2CktrO2b3JrbG1wzXVrfXtwem9eYW92b29uZ2Rvam5neH53f4WMhnNpV19iZmpnh29rbmphoGNlY6alpl9mZGlyYmdeZK1asatWXkpPTlVXVVhXTVxVVnBkWFdeVWSqVFxUU2NeVJ2fqpmuaWawpJq4ZKykrGFhtrGoWV2AWGh6ZbVXZV9STFRQnJGWm5+WnbJXZF5XXF5ZVldaW2ReU5WSmJuToKKjVJpYraGXlaRXnqCkmY6FmIiBjaGXk46Sn5KToqaipqmXrqGsq6OlWrKzVlWfml5YXF/BsLCqpaCepKWvrba4srG7x767u7eZn5uanGKfYGRlV1+UgoaAiI+KnJCia1xVVJFQVYONf36UUUdNVVhYnY2dpamgXFhfWVhicIV5Z2ZsbnJtWmFsc4R+cWRnb3BsY3WCf419eHeAd2izbnFjY2NiYGtnbWmrY8XCw768vr3AYWhqbsZry9F5a8rG2MzEaWSnk42bmVJhWWJXU1dnbGBiZFWdVmWAbYNvdW5kV1hbX2pgYFleXV9cUEhUTmdfgGlhWVtdZWFgZXdsXm9sfW1lWlZaZWtua11kWFSWoFe2ZLZlsrCttL6nq6mwZmpuysDWdNJwycFiXlheYGBcYmm8aHPAxWmol6WyYl2qXLSyqbG1satVVKuhm56TmZ2rWZeXolWgpJeAsaSlp6i0oqKomqmZoKGQjZCUkpCRgoV5hY2TlZGTjYuSjJCEgoGGjIWKh5OFe3qGkJaMhIaKkpKPmomNeYCCjYqCi4iNhYyNjo+NlI2gk56KjpGOiYiIj5OOk5iKi5CRk5OWlZ6np6CtoZ6orKywpaKrpKWcprKhm6WcpK+qoKKAlZaip6Wvr7K2sFtcsVeoV1VWWWLC0GrCxGNjX2FcZF9faWVoZXRybGppZ19fem5uaW1wamnIbdFv1tC7ucDDvLxhYrB2b25xgHlrYGK1ubnCxsW2zc/L289r0G9qcmxsa29ranuNd4l5d3SAh4aBjYeKh3mAhHh3fXlxc2tsbm6AanB0hIKGfHeCd3+KhIOCiI6OhYOAhXt8em54d4KFhIuKiIFyeH6Jjo6Si4uGmIR/foF+gYWFg314fHmDiX1ucmhwc290cHR8jYSAiIF/eX95a21qbHFwcGpoa2fN0tHOy87BwMHExL67vszDyNPg79B4cm5pb19ibmloaGvSvsGAw2pta2RrY2hiamlraWVdXWdjc2NsbWhlYV5hYmRovF1hxGK9yW5taXReYWBfZ3drZ25yb35ucnlybXB0ZnF4bXVwbnd3iXlxd250cGBgYnFqeHFrbnR3Z2hvdHJibm5paGtzeOTcdnNouK25ZGSuoKheZ2pucr7Pr8HIxNBlt8mAP0E4NjZRZFtdQEE5PDo8YDsvP0I8SUIzNUVMQkJANTQ+NzkvOjw2PkZSVEhENT1AREc+WkQ+Q0A6WUNHRmloaj5FQkZOQ0dBTH5GjohFTjtBPUFDQUVCOEZAPlZNPT9GPE13PEM7O0xIPnBwemF1SkZ0aGF/SG9jaj8+bGhfMzSALTpJNl0tOjkyLTUyXlNdYmNdZ4BATEhBR0hCPDs+PkdFPW1zfoJ9ioqER4FMlo+FhZtSlJygl42AkYB7iJ+bloySlIJ5fYSIkZmImYuSjYWFSJCSSkeBekpBRESLfH6Cfnp0dm93bnJxbGx6iYaFh4ZobGdlZEFjPT9ANT5aT1aAW2FcZ1drTkE8PmtAR2VzZmN3Qjg+RUhFd2h1e3hqQDs+NS82QE9IOztBQUM/LTVBSFhRS0NESktGO0lPSVNFQUNORzpZQEU5Ozk1MD02OzlONWxsa25pbWprNDk7PGU7a21FOF9ZbGZjOjpgVFNiYzVEPEM1MTVBRDo8QTVgN0CAQ08/RkNFQENESFVJS0NIREVBNS07NUtCXUhANjk8QDs5OUpAMkRCUEM7MjA0QUZJRzxFQD5pcD13QW4/Y11fZXBdYV5lQUJEc2FtPmM6Y2EzMS0xNTQxOD1lO0RmaztTR1VqQDtoPXh5eIKLjIlERId5dHdsc36KSX57hEiDgneAk4KBgYKOfnuDeY+FjYt8dHl/e3l/dHtzf4eOiYGEfH2JhIt+fHqAfnV8eIV4amdyeHZsYF1gamhodWZqXGhrdXJrcW1uY2dpaWlmbGd5bXNnbnNzcGtqeHt3f4R0dHp2cW5vbnmDhH6Mfnh+gYKGf32Igod6gY96cXVpcIGBfoSAenh+fX+Ag42Jh0NDfEB9REQ/PEFyeD1rcTw8NjgwNjAwOTU3MkI9NjMxMSkqQzY0MTU2MDJcOW1AdnJhXWVqZ2c3NlVBNjQyPToxKjBYXl1kaGZWbm1ocWk4aD88R0NFQkNAPUhZR1VIREBHTUhBS0hLSj5GTENETUtGTERGS0qAQ0ZGU1FVT0tTRktYUEpJTFFQTEtNV1NXV0xRSEpGQ0lNUE5DR0pQUU5SS0hETj45NTc0Mzc5OTYyNjU/QjotMiw1Ozk7NTY6RT07QUFEQk5KQ0Q/QkVDQj09QUCAhoaCho6CgoGFgHl5eYJ2cXiFjmtFPzw7QTA0RUE8P0J4Y2qAaDxBPjhBNzw3Pjw6NzQsLjUxPi03ODQxLiktLjEzVy4zYjFaXjc2Mz0nKyoqMDsvKjI2NUQxNTs1Mzc9M0JIPkQ7OT8+UkI/SEFISTo4OkdBS0VAPkJDMzU9QT8vPT05NzY+QHFwQkM+bWZ4RENvYmY8RENGSWl5VmJpZW00VmSFe4R6hnsBeql7B3p7e3t6enqJewR6e3p6lXsBeod7hXqCe4R6CXt6enp7e3p6eoZ7AXqHe4h6jnuIegN7enuFegF7nnoHe3p6e3t6eoR7mnoCe3qFe4l6hHsDent7hXqGe4Z6pnsBeot7Anp7iHqEewZ6e3p6e3uFeoJ7hXqNewF6t3sHenp7ent6e4l6C3t7e3p6ent6e3p6iXsGent7enp7hHoEe3t6e4d6gnuIegV7enp6e/96jnoFe3t6e3qFewV6ent6epx7BHp7enuIegN7e3qJe4x6Ant6/3uCe5V6jHuEep17B3p7e3p7enq/ew16ent7e3p6ent7enp6hXuHegN7enoCAgQAgIiJgISP94KE/Y6chYeGlYuNnqCSl5WLg4qIjYiHipyKj56NjoyVmZSopamcuaKwlaWoqauqprWqra+Vi6OAg/rngoaRopuciomRk5mVgYmQkJCNioyLivf4+YP3kob//Pn/kJeO/uDL5Pv584Tm5faP+PX66P377uPz94GJgI6YgICJjITw+Y2TlJebkIWOiIPu8+iDhY+Rk4eI/YLz+PmIh4mJh4OCmIyNg4+FgIL9/IT+8+jz9Pfy/P7p5+nl18jV49LZ5OX184Hu4ub4g4aFiYKC+vn+/P/x8oaDhPPo3tbe1+Xc3czX6PCA6Pf8+YDu2tLo+IKRhI+Ohe7/7+vfgNzf6+uC8vr5+/f77vr5//qKgfuG+4GB8vbr8tvv//CF+IaamLSYj4qG84KDgI6OjZGdnpyKg4aMl5OShp6mmYWGif3l/oaEm5OOjIeKlIv76+r8+PDhgIf98IOOhoLq/Pno5dbs4e/p9v+H6eeOjI2klJGRjYiSi4SGiJGOiY3lgPyBmpSYn6mcn5Gdh5ijhvCKhI2Unpmmr6CPioaB6eKAh4+Fiaunj4uHhJePjpCelpKMjpaSn56ZjYuE9P6C9ev94+Dj09f24Ob/34D73PTz9/2DgIKCio//9oDy7YCG6+Xy/YWG9fmJguzp6vb+gZGF3uzr5OLc7/r0hYWCgoH3gPH48vbZ6tvP0OPq5Oj57vf88u/08PXs4uTW5ufp5tzJxMHOzsXMzMnNzLe2tremsLrAxsjJ0NLWy7zBvbm2wbrKw8bCu7i2x7nHyLvB0sa/v8G4wL+4w8fPzcrV29nc5d/YyNHT2N7o6ezn8Ozq6+/r5+nx4eXt5Nzi3u+D8oKDgPrj5ebp5e73gYWJi5OOkZKPkJGQlYqDgoeUkvyRp5eXmpOHhpGOi4KTk4eLlI6HiIqLhIGE/JWLheP+8Pz/hYP/hIP/kYuBhI+ckYqRgICB+YDi1+v97vjm9PPvhJSDgY2Hkpu4uqGpjpaZpq20wL/OxqeYnqaipKeiko6Ji42SgJOSoa+rsbC4vb/Ly8G5xMvP8NzA0M+8vrO2u8G+xbvBvM/Qv7zO1NjUzs3cxsO4sbyrqaqsvaukqJukpamtrKCcoq+0oamrrLG9ucC/wau8taScnJ6Ym5SUkouKgfzz74D46P2A9ur+6+jY583X4/X5hoWLl5aZl5KF7fjr4d73gPjxgYH8jvuA9vX4h5GNiIKMgoSBh4Hx8N7z+Y2IjfiHhvvz6ury6ZGS//7s7oKFiZGU9Y6ijJiOl6ueopSUopaZkJCVlJuhnqqYkYr2lKCztLGquK+pnpqZkIaTi56iiJSIhpGLpaabl6GKh4eSl4H63/ny8d/v3+yA/fHzg4yEgGpoYV1kolZXpF5tXFxjc2dse3VoaGdcV2BgZ2RiY3ZnbHtqb251dnB9fH5yiXF6XWVna3BzcHpobm5dWnFYXrGjYF1gbGJhV1pgYmVcTFNUU1JNT1ZVVZScnFWdYVqqp6OlZGdkt5uFmaSYl1SMkqNjrKanmq2ooJelrVZeU11kgFVkbGazuGVlY2NlXVRcWlmcpJlcW2JiY1hcrV+xsrRgYWFhX1hTZVZTTVlUUlOgn1iqpZymoqCao6aRj5SPiYCQnIuTnpqkp1qim52sWFpZXllYqaaknJyQlFleZLKtp52gnaWiopKfsLJdp6yrsVqnlY6hsltkV15fV5WkmZWJgIqLlplYmqCemZGaipSZnJJWS4JJhUVMkJ+fqZuttaJYnVRmZX5qYVlVllBPTlleYWhzcGxUTU5VY2NjXG51bFxdYq+dtV9dbmhiYF1icGq7sbXGvrOiXmCsnVxoZGW1zsq/tKO5tLy3v8dmo5VaVFJoW19hXlxpZF5gX2ReXl+XgKdYaGBiXl5aaGJrV2RpWZlaU1hbYV1mbGFWWFxaoKBdYWlcW3ZrW1pZV2dhXWFxamZiYmZjbW5va2tlvMZmv7zJtbjGtbHMsrLIrmbTus7Ewr5iXl9dZ2zAtWC4tGBmqp6orFhZoKViYrCvrrS1V2Rcj6SmnqCVmqKXUVJTV1qvgK60tbektqupq6+plJObjZaTj4+SjI+EfYd7jZOZnZaJioqOjoGGhYeLjoKJiIyAiI6OkIuIkZCVk4uSjYyFjYmTjIyLhIJ8joaQkYmJmpOSjpSQlJCMkI+TjoiZnpiXn5yYj5idoKKkpaGfqKCgpaGepKOsnqKppJ+jnqhcmlNQgJqQm6aurLSxXFhdXGNfYGBeXV9lbGlnYmdsZ6Vhcmpqb2pgYGtnZV1oa2Nha2lhZGpuaGZozndybLrHvM3HZWK3XF6vbWpjaHJ6bmRkVFtgx2q6ssbXycy6xse/a3RqanFtdn2NkX2CbHV1f4OGjYqSi3pzeoOBh46GdXFsbGtygHJtdn52fXp2eHl/gYN7gYSFnY53hYF2dm1udHh7hH6IhJGPfHWGjJOTk5Gfj4mFgIZ6eXl9jH94fHN7eXl4cmZgY3F1aXF4eoGNh4yJinaCeXFpcHVwc2pqaWRmZdHMy27QwtFnw73SyMe+zrvHx9HPamRkaWRnZ2dfsMvIx8TLgMy6YmPAcMlqx8HCZm1mY1xkXmJgZmS+w7C/v2xgZbJhYsXEur28r2xpsrGls2JkaGttrGp/a3h0coFycmNpdmxzbW10dHZ4c31wbGiybHJ+dnNteX15cm9uamVzanyAZXFraHZ0iId8dntjYWFvdGLEq8jHxrPDsr5ow7rDaHFrgDg4NDY/YDQyWjpHNDU2Qzc4QT41Ojs2MTk8QDg0Mj8xNkExNjI2ODRBQ0pAWEZQN0BDRUpJR1JESks8O1I6QHZjPz0+SUNBOkFIS1FJPERFREE5OT8/Pmdxc0J4UUWCeHJyR0tHfmVUa3lzb0FgZHJJdnB5a4B5bGBqbTM5LTU2gCo3PjdaXTY4OTs/OTE6Oztja2VDREpLST9Cdz9vdHVCREZIS0dEWEpHPktIRkmQklKflYyWk5KOlpuIiJGRin2HlHp6hHmGlFKUiYiVSkpJS0dHi4yPiIh5dkRFS4aCf3d6cnhvbVxjcnZBcHyAhkZ9al5vfUBJPkBBPWN1amhdgF5cYl85X2hnaWhzaHV4f3VGPWk9cDxBfYuGjXV+gWo6Wy88Oks6ODY3XjAvLDQ3OT5JS0k3MTM7Qz48MUJIPC80O2FRZzc3SEE7ODI3Qz1oYWd6d2xgPT9pWjY+OThZcXRmX1FmXmZha3I9X1U6NzdIPkE+OjhBOzY6OkI+QEJkgG84R0BCRkhDTEVNOklNQGZCPEBESkRPU0c7PD07X1o3PD81NlNRQD06NkU/Oj5KREE9PkVCTlBOR0Y+bXU8a2NzYGZyZ2iDZ2Z1WDZyWG5pZ2U1MDEwOUBpZjhsZzk+X1ZhbDk4X2dDQ3p6foiORFJHY3d8d3t4foeDR0dISUmLgIeLhop7kIiEhY6QhIOPg4iEgHp7eX12dX91iIqOkIV0eHiEhXh+fnp6fG11d3lrdHd1dW9pbm91cWlwaWxqdnF/d3d0aWRfcGZxcGZpeG5oanBxe3d1e3x9e3F8gHl5fXl0bXmDiImNi4R+gHl4fX18hIaRgYGJgnp4b3VDd0dIgId4fIGJgoeIREBCP0RBQ0NEPz0/Qjs5Nz5HQFU1RDk5PTgvLzc1Mio1ODAxOzcvLzQ4MC0xYUE/PF5rZ3VzPDpoNDRcPTctLjY9NzE2KjE0ajteUGh6ampZYWBfPEc9O0E6P0NQVUZMOEA+SUlKTUpSTj86P0lJTlVSSUtLTkxQgEpDSlJNUVBPT0xOUlJLUVFQZFZEU1RTV09RUlFOT0ZNSVhYR0FMUFJOTUpRRUM+PUA1Ni8xQTUwOC83Njc4NCooLjxCMzc5OD1GREpMT0FORkA6P0ZBRD0/PzxAPoF+gEqMfpFJg3WLgX91h3B5eX13Pjc3QD0/QEQ7ZoB0bWpzgG1cMzRnRXY/dm1uO0A5My40LjEuNTNcX05dYD00OFc1NmZiV1JSRDY0TUpCTTAxNTg5QDFDLjo0NEU6OzA2RT1EPj9EQEFDP0tDQUFiP0RLREI+SkpFPTs9OTJAOEpPNUA4Mzw5TU1HR1FAP0FNUkB5Y4F+fW13ZXA9bmFkNj02hXsEent7erZ7gnqWewd6enp7ent7hHqDe4d6BXt6enp7inqJe4J6inuDeod7BXp7enp6j3sDenp7l3oBe4R6hnuHeoN7jXoBe4R6AXuFeoZ7iXoBe4t6B3t7ent6e3uIegJ7eoh7AXqYe4N6inuHegR7e3p6hHuMegN7enqSe4J6jnsBeo17gnqcewN6enuNegF7hnqGewd6ent6ent7hHoGe3t6ent7hXqDe4l6hXv9egR7ent7iHqTewF6mXsEent7e4V6Bnt7ent7eox7Anp7inr/ewp7e3p6ent6enp7jHqJe4h6CXt7ent6e3p6eot7hXoGe3t7ent7hnqCe4R6hXsBepl7AXqje4l6B3t6enp7e3sCAgQAgJePi/+HgoWH+4CGipKD8/eEmJWTm6HyiIWHnpWdjI2FjYyLi42TmJKOgo+LmKScnIuIjYyNj42ip6Cqm5OSnIeN/IOGmp2TjIODhYmE8+6FhoyflY6Sjo6JiYj9g4T074D+/f6MhPfr0PL3/ZCNi4SGiPX98vqF9PaB+IuE+YqVgJuEgIDk/vzz+4illI6E/vXs9Onp6+H8jIyM/4P03fL6g4KFiIWDi4yN+fXmh4P/4fjz8fj3hIOBk4eKg/Lo4uXw6tjR4ujp8+rz3Nz0/oSChoiIkZSKj4qIhfLe5O3h2dPX1efZ4OXg7/iA/ovo5ezazcfi18TR1Mb78vHR2+jugIGL9fL++vyDmJeNh4SB9/2Nh4mHjZeCioH2/IP7iZabkY+NlpCMgKuuqrKuxamWkoiKlZ6QlouLkJOUmp6ckIWDgYeGhv2PlJOQi6aplpmHhfnx8+v5hdfe94GD6OPR4eHWys3Y2uDQ4NXd5uv+/vTtgevo8Pr/+JOEkpeL/eTqgIaGsaCVlJivsK+ahpaVlJaMn5uPkY2Og4eThYX0hJGSnouOjZabiouelZGXoZmil5iOlJ+ZnJyYnYn/i4+M9YH46uXj5tvnz9za2drg2tnr5vv29fD/gIDm5uDo+OHM0OX4g4uN/oCJ9+To7v+Agff86OLl0c/Nx+Lg6+z5+PXugOnq3dzRyMjc4Of58ePo3ujr6fD66Ozm3N7Z2OXm7Ovj2+fc19DO0M3XwsW8xcOhqaertr3Ky8HGubS5tb62vcPF1MLSwre9x8vNvr3CztHHxr+vuLy4ycPI1t7d/erk4+Lk197TzdHa1O3m9fPz4uju5+Pf5OXq6O/2/YODhIWDgP7z3urb7uPf8Pf8jo6WjpGSkpSPk5KZkpWTjYiHmZWMjoyIi4SAioSFiYeKh4uAh5aI/ITi7+zw5tbz9fr2goOGhYrz9vDz8YSDio2A9eHh7erc2uL3g4D8l5CNh5SIjqCqqaq4s7qio6KyvL2ptK21rKqgmpiYpKKSjYiGgI6ggJuioqCvr6qstrTM1sfOxtHp3tDIytS7uq+/xc/Ky8rNzcnCsaXJxtXb18LBvrqlpaWboKivt7CqoqSqo6enoJ2imrO5rriztcC/yLrB2LjDwLKfnJGOmpOLi4yHgvru94OC7fvu5fDt7OfU4dvOy+Xn+oGFi4iNjIn+ivby/OnrgPX2+/uK8O3r2ezs+v2HhvOAgpCMifjr4Ovp9oKCgYSBgubb3PLQ8Im2nZ6MgouJho6N/YSUiY2Un5uWmqqjo5qYkYWTnq+cqKOUnZWVm6umsrKxybS4m6mupJePkZWUlZGUnp6QlI3474WCgoqOoZaG3u/f6uTY4PuQkoSDgP33gHBpZrVeXF5dqVRaYGVdra9gc2tnbG2SXF1ienR4bGxla25qaWxzc25qYWpmc3drak9LUVNVVlhqa2VvX1hcaltjsVxebG1hXVZYW2BYlYdKTlFhW1lbV1tXV1WiU1SXlVKnrqplYbOkjKCboWBaXllbYq+4tL5gsa1arWBZqFxmgHBfYWKyyMawuGJ2aGJaraijrJqcm46mY19ir1umk6WkWVlXXFtUW1hZmZaNWleniZ2alJiVUE9LWlFWVZ6UlJynnpCLmaOhqKGvoJuwrVdWWVtbYmBVVlBPUZadqbCnqKKjoq+ho6GgrrRewWeipa+fk5GnnoeOiXqnnZx7iJGYgFZipKCpoJ9TZmNZU01KhYlTUE9QVlxMVlCbq163aW5wY15fZGFiT21ybnFufmplYV5kaHBfX1VWV1teZWhqYVVWWGFgXq1lZ2ZmXnZ9bm5hYLi2vLa/ZJCUp1tjsLClubKnk5KYoaebraGkp6SoppmPUJWQmKOroWRaYWNWo5WdgF1ZeWRXUlZwfH1tW2ZhX2FcamVWU1JXTldjV1WZUl1haFhYVmJlVFNhWVZdZGFtZGtkaG9nZmZmcGCwZmtmr2G5srSxr6Sym6Wsr7S7ubO/vczDx8bPaGm4sbC0vaiTj5qrWFpeolNdp5mdoaxXUpqjlJehlpmXjp2XnZ6qr62odqiqoqajnp+3uK+5pI+TipOSjZSdjJGJgYqIh4+Sl46Gg4eFhYGBgoSLfIWDjI51e3Z0enuEhnqCgIGNiY6FjYuIk4CPg3mCj5iYjY6Nk56RjpCLjo6LkIeEioWJopeQlZuhnKSdmJedlZ6Yop2impmgn5ubn5yEn4CkUFJRU1KiqJ+xprqvpbGwsGJkaWZoamxuaW1tcmpua2ZhY3Z0amxnYWFeWWJcXmFgYltdU1xpZMVvvdTNz76tvsDKwGRjYl5krrO4yMhwaW9uZb61t8jHt7m+zm5p1Hx2c251am9/iI6KkI6PdHRwfYuLfYF9hX9/eXN4eoOCcIBtampncHx3endzfnx1b3Vzg4aCgn+HlJOEfn+JeHVtd3mAfIGEj5KSjn1tioaNk5SFi4+OhIF7cXB1eoSBfXuAhHp7eHNqb2J4e3B4c3qHh45+gZJ3hIF4bG1oZ3d0bW5uaWfIwMptb83Ww7e/v72+tcLDvbHHx8hjZGNcZGNjtoBqv8PVw7jFwsLHb8DAwa66tL29ZWOuW1toZmS0sKixrbhiYF5kYme9tq7Go7JhgnBsY2JqamhvarljdGhvd3dyaW11cnNpbm1ldH2HdHp2aXFpaWpzb3t/eoh/gW54fXdwbW9wcW5ocHd7dHZwyrZlXVtfZHlwaaq8rbSml5uxaQZqXV9cuLWAPTY2XTY2ODZcLjQ4PzZZWzA7NTQ6P0k0NDdKQEI0My40NzM0NDk6NjIrOTVFSkFBLSszNDQ1OEpNSlJDPDxJO0Z3PT1JS0JBP0BES0Z0Zjs/PUhAPUI9Q0FDRYRHRXtwPHZ1bkQ/cmZQaGlwSEFDPj9FdIF8h0R3cDlpOjFTMTeAQDAzNVpwaFljOE1EQDt3dHF6b3FxYHhIRUh6P3BhcnVDQkJHR0BKSUl0dG5MS5V8k5KKkI5NTElXT1BPlo+OkZeSgHWAhIKOi5uKhJSMRkVHSUxVVk1PSUdFcG14gnl3cnJtd2ZpZWJvdT+GTHV4g3dsaH52XmZlVoJ4dVpja22AQEZrZW1maDZJSUE+OzpqbUVFQkNIUEJKQ4CDRXpCRkU2LzE5NTUoSU9NTUlQRD88NzxBSDs9ODk4ODk9QUI5LjEzOzo3Xz9BPz02TE8/PzU2aWdxbnxEVlprPEBnY1ZubmZUVlleZFdnXF9nZXBvYVc3YVxeaGlhQjpDR0F/b3OAQz1XRj48P1FXVUc3QkBCREFQSDs6OkE5QU1APWY4QUJKNzo6Rkw9PUpCPUNIQ0xDR0BFTkZFRUNMPGdARD9kOWpoa2htZXFZY19cXGJdW2dib2dlX2k3OWBiY2t0YU1NWWw6PT9nNkFyanB3hEI9b3Zoa3hvdnpufX2Ego6OiIKAfX51fH57f5iVkZ2QfYJ9g4B8fYN0eXZ1f31+h4uPg3x4f4KDe319eH1rb3F8f2RwZWNoaGtqX2dhYWpia2hwcG98anhqX2ZyeHhqaWtzeWxvcW52eHF6cGxxbW+KfHV2eH17h4WFhYl/iHl+eX12eoOCf4OFgIKBgH6APj9BQkOAg4FygXSGe3R+fnpFQ0hCQURBQTw+PURAREQ/OTdCQDc6NzU2Mi01MDEzNTo1OS8zPzdjOldqZWxdTl1ia2U3OTo3PFtbVFhTMSw0ODJgW11tbFxYXW07M2lEQkE8Qzo8SU5QTFJVWENCP0pST0FHQ0xJSUVAQ0ZPT0NFR0dBS1WATU9JSVZVUUtPSlBUTlBMUl1cUkxPWFRVTldWVU1KS1FQVVNGOVBLT1BTR0pNSkFAOzMxMTU+PDc1ODszNjYxLTYtREg8Pjc4QkFJPkJTP0pIRDs9OzxKRD0+Pz8/e3aFSUuBjn5yd3h6eWx9e3BneHR3OTo9OUBAQ3NJd3OCcGiAbmtqcEVvcXZncWlvbTo1VS4uOzk4YF5TWlhfMzIwNDM4W1JLXDZEKkQ1MiwsNDMxODVRLjsuMzs9OjM4REBAODw5MT9GUUFKST5DPTs4QTxGRkJRR0o1Q0tHQD0+Pj88NTxCQjo/PGhhPzo3PD9UTURngHR9cWBickZENTQwV08Ee3t7eoR7AXqFe4J6hnsBeqp7AXqLe4J6jHsLent7enp7enp6e3uGeoZ7hHoIe3p6e3p7e3qGe4V6hXuJegV7e3t6e4R6iXsFenp6e3uHeod7knqMe5B6A3t6e5N6gnuFeod7gnqJewR6ent6qHsBeot7hXoGe3p6ent7lXoBe4Z6hXuDepx7AXqdewZ6e3t7enuWeoJ7inoGe3t7ent7hXqCe/96jXqFe4t6p3sCenuKeoV7hXqFe4l6A3t7ev97hXsFenp6e3uQeod7Anp7iXoBe4h6A3t7eoV7hnqGe4Z6i3sBerR7gnqIe4h6hXuCegICBACAlI+Kip6Pl4qVk6GOjYD+iIiRmY2Rkon8gYSbjYyWnouNh4Cbo6mQk5aIloSVrJ25r5eKkK2hiaCrk5ahqpuYifXugYeWk6imj4yZkJaT/vSGkZefpJmTjID9jOXzhv2KlJuIkpKTj4qBlIOMk5qZjI2Dhf377f7vjovu+IeD/PyA/viLhoSLhomJkJuP//318YP0hYGGgIWFiIf/gIT5ioGQiY6Tj5GTnI+LkoeL+fbz8/DwgYGAgoWE//vt8+Dh9YHs6eXv5eDi69jV1+jtgpGUjIiBgoH09/P54NzZ5dnT18jQ2t7Z4eDr9eDm9fDYz97Ou9fM5ejY1c7Nyc7Pz+WA3viF9+iEhoCFi4L1l4KNkJiKkpGNj4n38vb8gvqDnJCXopumpJyQppWbn5iJhIWHgYKEjZmOjKez0+vvxcSvmqOglpCNh4P4/fyCiP3k+//n8+js1uTP4vjm+ITy6e2Cl4X9+Y+Ag5H+8er9+4H7g4eBgYbx0OSAm5GfoZGF+oGAhIyimJOVop6WlKGbnaWvpKOSg4WQ+vyF/oOEg4KBjJSKjI2Om5apn5iMgYGMlZmRgY2LjYj/j5GJkIyOj4vv4Obf5Nfh5Pb16OXP4+ji1NPm5fj4+vDw6efgydXj2+Twg4iB8Y+Eg4bn4OPj/ufs3Obc3/HZydHY3t/w+O/19+uA8Ong7NzQyt7c5/Dr6Nre2+He6ePj3IDRzM/d3N7r99zH5eTUysXEuMLJ0czSwre5trvGyNDH19XDxc7DxMDHwMnGurS6u7C5wr60x87Q0M7DtrTHu7rJ1d3i4tzi9uzr1ujS4Ofi2+Lq4vj27/z5gO/s5t/g4unj4enxg4KAjIqAhILx9/j5/oD0ipKLiZORnZaTnI6QnJqVnpqehYmPjYmHl4yKhv2J7/r0+oWAgPyDg/n/5+jk9Pn74uTm8fD9/IGHhvyEhP74ifv+goD45PDv4uPs+oGG/4mOlomMhIOHiZyWoaSjl6Kkqqy1q62eoaevrKuio6Oioo2Lh/2Ik6iApKaknaGgqLG7uLW3t7/Ez/LO1MXLz7m8wc3Jz8XIwMXAvrmxoMLJ3dPDr7ursq2hrquikqOnsqCmqJ+pr6yempqjorKhtb7JyM65q7O8tq6vppmbmZ2ckYyRh4WCgYeEiIfz8fLf6Ovq6djOzsvN39z0+4aGiouNkIeKhYL79vKA5fWA9/bu5s/Tz8vh3feNg/Ts9emCgoCB+P3y9P/6kYLs6ufw9PeUqaaZg4CEhYKLioqSiJSJgIiWnqCooZ2uoJ6QnJ2coaKhpZmbn6aToK3m2MGpr7iopK2ln5yYiY2Yj4qPrJOcppeOnKKWjZ6fi/fp9OXl+vP7j5uMhYuQmZWAbmpoZHJnbF1nZW1mZF67ZGdtcGVnYlynV1t2aWp0fGhmXVNpdn5rcXJkbVhse2Z4a1VNV3NpVWVsVVZea2JlXK2yYGFqZXp5ZWBsZGhkm4hNWFtjbWNfXVWlXpmlXq1eY2lcYmVtamZgaVZaX2JhWVtYXLW3r723a2SqqV1braiArKpnYGFpZGRlZm9mrq+ppVuoWldaVFdZWVqrU1itYlhnXl9jX1teY1paYlhdn5+foaCeVk9NUFVVpqifrJyarlubmpupn5ufpZmdnKmpWmVjWlpUVlOXm5uomJ2boZycnZObn6efnp2mqpidoqGUjZuNeoyBj46Bg4GAhISHhJOAi6JYoI9UV1VZX1mhZlFZWmFUWlZUVVGWkaS0XrVhb2Fka2ducnBkdWZpbWNSTE9TUFhXYnFkYXN4jJ6jhIx+aW9uaWVjYFukr69aYrWfubujr6emlZyKl6iWpVypqK9jdGOzqmhdYXLKtai6rVOqWFtWW1+cgo1SaGNrbF5Sm0+AUVhnW1FTXmBbW2VfYGtya29dUVNbmJ1UpllZVlRQVF1VWVtaZFtnYl9WTlBbZWtjV2dmZ2SxY2RdYV1kZWOpqLqzv66vrcC5pq6ZrbS1nZeqq7q8xr6/wcG7rra4paeiWl1WnGRaWlyUkZSUsqWomqyiqbyilZSTkIuWnpukpp+ApqKbqJ6ZlaqepKSYloyRkJOLk42OilWBgoiXlZObm4J3j5GIe3l5b3h7g4aLgoOGgoCKiIyHlpONl6OXmY+XiY6JfHeBhX2MnJSKm5ubnJuTio2ajoiNk5KTkpOar6amm6uaoqaclJifkqaflaCfU56mo6GgnJ+clZyjWVVOVVWAU1Wir7Kzu12qXmJaV2NkcGtqdWlodXJtdW90YGVrb2hic2VhYLJkp7Gpr2BaWrBgYcLRv8PG0M/Lsa+0xsXNyGNiYLJeYMC+asDHaWjKvcXFubzL1W1z2nV0e21sZGZrboB6f313bXJ3fH2IgYN1d3l9enpyd3l6fGpnYrxoc4SAf352c3ByeHx/eXV1eHh+g5SIjoaJjHVycnd0eHF5dX+Cg3x2ZHuBjYeCd4mBiYp9ioV4bHR2fG9yenF5eXZrZmpvb3pueoWPkJOBd36Gf3d5cmpwbG91a2hvZmJgYGBeZ2vGxsSytr6/u7iytriwtbC6t2FdXF5iY1xiYWDAwLaArLhgtbK0s66xsqq2ssRuY7Kgq6RdWl1dsbOrqbWvb2S0ur27vrpuf3pzXlxhYWBoZmRtZ3VnX2NrcnF4bWt8cnFlbnJ0enx6fHF1dXlqcneckHtudoB2eIB+f3x4Z2p0aWlugXV5fG5ja21jWmttXqSZppuYpZ2gXWlbV2Boc3OAODMzM0Q8QjQ9PUM9OjJeNDM0OTU4OTdZLzFGODZARzc4MitBRks5PUA2QC5BT0BTSTMqNE9JOU1TPTs/ST9CPXN4REVNSllYSURNRkpIaVg3QD9FS0E+PDdsRWd1Rn5FR0o6Oz1FQkA7SDpCR0tJPT85PXd4c393SUJjWzQvVlWAWlo/OTpAOTg5PEQ+Z25xckN9RkJGPkFBQkJ4Oj16Rz5NRkpPT0tPVkpHTUVKgYaNlZeXVE9LTFBPmJSImIiKmVGKiIKKgH+HlISDgId9RE9QSktHTEqHiYaOdnRzfnJwcWJnaW1mZmZxd2p1gIJzbHptWXBkd3ltaWRiYmRlYnCAaHlDcV07Pjk+Rj1qSjZAQklBR0VDREBvbHuCQXM7RTg4PTlAQD42S0FHRjwuKy4zLjMyOUc6N0tNWGJjTFZLPUZGQT4+OjdfZ2k5PmtYam1daWZuXGdWZnRdaztjW2E8Tj1pY0E3PEyBcWd8bjVpODk4PkNoU1o2SUVPT0hCdz6APD9MRD0+SEY9OkY/PUdOSVFCNzlAX2c5ckBAPDk3PEM8PkA/SkZUUExEOztDSUpBMkA/Q0FsQUI7PzxFR0JqYGpjcGBmant1ZWVQYWZlUk5cWWVjZV1hZWpqYG1xYWNgOj43XUQ7O0BjZWtxjXx+bnpyeY53b3RwdHSBiYSKiX6AgnpyhoF7fJGEkJaSj4GHgH12e3R2b0hrbHOEg4KLiW9fe4N9dHZ3aGtrampxaGhuaWt3dndufHhwcXpscGx1bHJvZWJpbGRteHNlcHBwb25rZWt7cWttdHZ1cnJ1iIJ8a3xue4eDe36CdIR6cn1+RHx/eHh3dHp5cXR4QT46RUaAREJ3foSFi0V1REc9N0JCSkdCRzs6RURDTEpPODs9PDUwQDc1M104U1tUXjk0NGQ1NmhyXmNmcHNwVVBUYmBoaDQ1NFowMFdTMlBbNDZrYGtrX2JrdTxAbT8/Rz0+Njc5OEVASElJQUdISkhQSkw/QENKR0lCR0pNUkNERH1GTlmAUlJNTVFSVVVSTEpJS0xRU2FUWVFWWlBTU1lUVUpMR05PU01HOEpOVk1HPEhCR0hASkY7LDI1PS8zOC81NzgvLDE6OkQ0PEJHSUw9NDxFQz5APjk+PEBEOjpDPTw6Ojw8RkqAgYBqbHF1cmtqbm1obGJuazs6P0FFS0FIRUF9fnWAZm86bWltbWZqZ2FmYGtAOGBSXlk3Njc0XF9UUFlSPjNQUlBPUEgzQT03JykxMzI7ODY+NkE0KS86PztBOTdHPj8zPT8/Q0RCRjw9QUQxNzhWTj4yN0A4PkdHSERBMzdDOTY5RjpCRjszPUE3MUNJQG5oeG1nb2NkPkg5MTY7PzyOewF6iHsBeqh7gnqMe4J6iXsGent6ent6lHuFegZ7e3p6e3uEeop7hHoCe3qIewR6e3t6j3uGeoZ7h3oBe416iHusegN7enqGewF6i3uEegJ7eqp7BXp6ent7j3oJe3p6ent7e3p6hHuFegJ7eoV7g3qHewF6lnsEenp7epx7AXqIe6J6BHt7e3qEe656AXvYegF7i3qHe4V6Ant6nHsCenuEegZ7e3t6e3uPeg17e3t6e3t6ent6ent7iHoDe3t6pHsBeuV7kXqKe4V6AXuLeoJ7hHqEe4Z6gnuGesp7iHqIewICBACAh4qLi5GHkIWThq6T+faVnr+3pqC6t6mWj5WmopD8kY2ElYyfjZGKk62pnJGNpp2ktK2kpamZj6edqbKurJ6hj4XvhYaEgI6Ggvn2hY6Nl4WCkZCNjIyYm5GPh4ePj4GNlIeLjI6LhYeEkY+djJCYhIX8kIiQiOXi44D+iI6EhomAioaUiI+D84n9i4qLhfzt94OJhP79+f6EgYCNi4f9gt7LzeLf64Du8tzt7YKH7PDm2+Xm6v/06YKFgPHd5d7k5fj16uzk19no2dPa8YWEi4mQiIGGiob76/CD5uTc5ezo3Nn55eXn//aBiPPx/Pjs3tjZ7dTh/IDt3uPOzsfP1+eAg62YoZeOlYmLhuObmJCimpaQoKqktLKc+vWCgYeUpJeioKirsqycvbW0qa3M2aKiopuenqSjko2MpM7Gra7Eq6OH/vT3goeCmomN/vLv4ePi+On519Dl9feF8+fz/omBkPKMlJKNj+/4zNHd3IL2+/yG+f2E+fj3g+Txg4b4+++AgoeM/oiTkJSTo6KgmZmgoaermpehnIyQiYuKjIaGg/uAgPWLiYT87+38/4r8h4WHiI+Kh4mC/oSJnoj6iIL0+oKEgur67uL95eng6NvX1t3V3eXm4tfZxr63vrrN0efz59bK44P3+Prq9fj8/v/06ID+9t/f0Nrj8+zp7OXt7OaA9fP78vXVxNDT7dzf7NTc2ejt8OTwgYDm2Njw5viGifjl7O/i0tfOzr7Gxbm+u7u2vbzIwMrXy9zT3tHQyMDAzc7Yz8+zs7+1rcSzxMrBwbPExMHIydrX3dbk6+P28Obu2d/c2cjg2dTs3/L58Or03+Hl4vHv7/yA+fv+jYSKkIaAgYKE9v/36vLzhJKQk4eaoZ6olqWVk4qNjZKblZOPko6LlIaRgff59ubt7omIg4mR9fjv6ePR0vmD6+yD7P+JiIqGjYSHkImChvzk/oiB7+7a3vD4hJabmY6Yl/2O/4iIlaKdnpKMmKWZppOXn6qqpayxrp6gmpWejIyKhISJkZaAkZycoqScqK+40sbWv8K/u8bGvbe2v7mxsK63u76yra6sp56hoLHDw764sKqYoZqSm5iKmLGnrKinrK+xp6eTjJObmK2otbS7x9LBtLi1q6afnpmPk5WQjJCHkZeVk42K/oHu/urm7tPq4dbdzd7S6un7hfj59/Hw+vP7h4SFh4OA+PHUzMm/urvaw8nL0ebV4OLc5d3u+/Pn8+/+homGm4n8+OT2mIqVk6aI9ePp+IKAhoSHkYyMlKadmJmboJ2fmZ2anpqSnp+hjqChpZWoyMSzvbi/tqeszrOznZaepbGll5uZrKWupqKqwMahop2inoaJ9unwg5OKiYyGio2RkIaAaGdoZGZfaF1oYIForKtsdJOJdXGIgHZlYWh8eW6/b2xibWN3a2lhanpzY1hVaWJpdGxlZ2xeWGddaWpobGVuZ1+uY19dWGVjXq2rYWlmbVhPXVpVWVllbmVkXF9lYlhkaWBmY2dlXV1bYlllVlpkVVeqZ2JuZamsqV3AY2diZGOAZmZxZXBmtWrBaWZpX6mdn1teWLOurrNcVlRhXVyzX56Vl6ahp1ijn4qZl1dcnqGblp2blKSWilNXVqegq6CinKOjnqObkZSonJSVpV1cXV5lXlpcYV2toaJbnp6ZnaOhl5iznJ+hralYWpmbl5iPkJKVrIuQoU6MhZCDhX6Hi4qAUXNfaWNcY1xgXZZvaF1iW2BZZm9nb2pdlZpZWV5ocWNtbHh+h4Jsh399b3GElWlsa2ZlaHNzZ2NfbouAanCHeXZhtaioV1xVbmFmuLWxqquovqy4lo2cpaZbqKSyvWdgaKdiZ2hqbbzPo6Wvol2rrqZcqKZUop6bVpWcWF6opZCATlRXlVNeWVxaamliXVtkaG1vZF9jX1ZaV1tcW1NTUJxOTZNXV1GTioeWnVecWlpaXWRjYGdetV5da1edVVeotmFoacDFvbLEprShq5ual6GhqLm9wra6qaGWopeno7e3pJWInF2tqqSYqqi1vMG2smbOx7KpnZ2dppqXmpWbmJGAnJyoobKciZWVpZCSoZGYl6Kdm4yOT1CMhYeYiZdSVZaOlpqXiIiEhXV6eXN3eX98hIGIfn+KfIyMoJqflI6PlZGbk5OChpSVk6ORn5yRloeTmpWVkZ6Sk4WNko+gopqilJqZmIqcmpapnLCtqKOqmZ+wpK6nn6xYp6y2ZllYW1eAWF1fsLiyqrO2YWZgXVJnbmt4ZnNmY11bXWJqZmhkaWdjbVljWq60uKepq19aU1lkpba5u7ispsZns7NmvcVoZGFZYFpeZmZmbc2513Rvz9K8vcrTanh8em51d8d32nRzeIB6fGtocnpxeWp0eoKAeHh8d210d3V/bm1mYWNnbm+AbHFsc3FrdnN5iH2IeHd1dH6Ce3VzfXdubGpyc3Zwbnd7eGxqYWh0dHR6d31ze3lzenhqbXtwc3Bxc3h/eH1sZmxsZXRwfH2Glp2Nf4J/endxb2ljZ2xsamxmbWxoaGNiv2W90Lu8wKi6s661oq6bpqqzXrWzrbK0t6qwXlpgYl+AvL+tq66rqLDHrrGsscOzvb+ur6OwtLCfqKOwW1xZcGK2vrS8fW1wa3hfqaCvv2VhZmJndXFtdIF1bmxtc2xuZ2tqcHBqcnJyYXJyc2Nyiot6gn2CfHOBnYqLfHZ7goyAdHZxgXl/dXB1goZjZGBoZ1Rcp6CqXmtiXWFaX2drb2iALSwwMDUxPDM9OVA/XVc6QFRLPjtOSUIzMTdJRjtcPz42QTlNPTs0PExLQDU3SkBFSkI6QElBQFFIUVBMTERLR0N1SEVDPkpLSIB5RUtKTzo1QTw3ODdCSEFDQUZOSkFKTUFCPD8/Oj0+SEJPPD9HNjptSkdSS3Rxazx1OTsyMzKANTZDOkM6WztiOjk9N2ZibEBFQICCgodGQT9KRUSESHRpbX96h0iEf2p3eEhNhYuGgoyLjaKPgktQTY6FlIyRh46NhIZ8c3qMgHd6iUpFR0hOSEVHTk2OiIlMfn14fX97b2uCa2preXM/Q294gIR8eXp6i290h0N1aXBiYlxgYl+AOVRCSkU+Rz9BQFtISEFGQEI8SFBJUE1BYGg8Oj1DSTk9OEFGSkg4T0lLQEJQXkJHRj48PklLQD04QVZMOkBSR0U1YFpdND44TkFEdHBrX1xYal1sVFNodXQ/bF1ocD84QV89QUFCR3SGYmhzZj9vbGI7aGs5cHF1Q3B3PUF3eW6APUBCbDxFPkA8SEhCOjc9QUlMQ0BEQztBQEZGRjw8OnE8PHBHRUJ5b29+hEl7REA+PkE/PEM6bTs+Tj9wPjttcD1AQW53cGd7YGdXX1FPTFZRVWFjYltjWlpaZ19ua31/cWFVaENybWtecHWDio2AeUiSj4B8eHp3g3t7gX2DfXSAfnmEg5eCc318kH6FkHuBeYKFgnR7REZ8dHaPgIxNTH5veIKBeIB/f25vZ11gYGVlbWx4bm1yY21pd25uaGRmcXB7dXhkaHVwaHtoc3BmZlVkamhub31ydGdwdHB+fnh/bnZ3eG+HgnqJeoeDfXiAcHJ9cn15cn9Eenp9RjxARUOAREdHfYV/cn17RUxEPjFAQkFINkQ5ODQ5PERLR0Y/PTo2PS85L1tkZVZYWzk1LzQ7VGNjY2FTUG05WFQ1V2g5ODUvNS4wNTExNVpLaz06aWtcXGltOERIRDxERmZGbz06QEhFSj49RkxARjdAR09PRkhMS0NJTUpUSUtHQ0JCRkiARElDS0tDTUlMV09ZTU5MSVFWUEhHU1FLS0tQT0xCQERHSUBBPEJMS0dIREY7QD05PTsuLzwyNjExMzY5NDsuKzM2MkA5QD1CUVVJP0RBPj4+QD45PUI/Oz02PUA8PDo9eUR8kn16gmR5dW56Z3RicW13P3B1dXmAiHd/RTs9PzuAb3JdXGFgXmaBaGddXmlVYGVYXFhlbGdVWFReLy8qPjJYWU9UQjI0MT8sR0RUZjc0ODQ2Pzk0OUc9NzQ0OjQ3MTY3QEE6Q0RDM0VCQi84S0k6QkFFQDpFVk1PQz1CR1FFOj05SEBGPztCUlY2OTlESDtDdW5uPEc8ODozNTo6ODCMe4J6j3sBeqN7AXqHe4J6pnsBeoR7BXp6ent6i3sDent6hHsGenp6e3t7hHqGewJ6e4Z6AXuFeoJ7inqDe5J6insEenp6e456gnuMegF7iXqKewF6jXuCeqh7g3qGe456AXuEegR7e3t6hXuGehd7enp6e3p6e3p6ent6ent7enp6e3t7ept7B3p7e3p7e3uFegJ7eol7AXqEewh6e3t6ent7e6F6AXuLegF7pHqCe4Z6gnvYegR7enp6iHuGepx7hnqFe4h6Bnt6ent6eot7BXp6ent7hnqHewN6e3r/ewV7e3t6e5B6AXuIeoV7m3qFe4R6hnuEesR7g3qLewICBACAiYX8iYKF84eEgoj9jIWFk5CPmo6lm7SmoJKUlZ+EkZScjpWfqp+0ucqis5+Mrp+mnrGloJyZmfuWkpKLkJORiYbf4oaEkYiSjvqHj42QkIaAg4eVioulnoX0+4iAg4mLiYiEgomGh4DhhIaYhIGDgoP7+IbxioPw3+DuhIeChoWA+vf88/rp8O78gYSIj4OIhJyMgoeGgISBgoSJ/PyRjIOE5+ry8/zq6+zk1YP/+/Tk4eju7vDk3PeBgYD78evt8e3i7oSH/vzy5Ozo+/qChoCJhIDs/Ov77+rn2+Ds7+n03tbZ5tTnhISBjIGDi+zO2tzm+dX10uTm8eDo2M73lZGAl5WXjp2E8v31hpOPgYmOlpScmo+Yk5mWnaukn4+myK2moamjobelqae1+vrZgMfRnqiwuMmsk5GWoc3l1rfQpZ6PlpmEiJyMkIv/3/CI+4eOi/7x8Y6Q9v3+/oDo99/p4eLyg/yD9/vh6ub4/4uKioH2ho6TifHn9IHt+veDiZGAhouZnJ2ckIqJk6qXm6GXj4uC6eyClpOOjoGAhImLiYL7g/L4gYaFhvP8+/v16f2DhI2KgpKIhIn29oH87oiIioTs7PWC6ufu3NnX3N3m9d/w59be1dLHybi+uLbB0svU2Obn8PPu9+/v/fT08+f//YPk3svZyNvW5uSD/YCGhfGA69/U1LbL2NDY6eDU5era4PHw5ujl5vbw2N7b1+bq8fft9/Dn2efU1su+yb/KxcHC0M3ExsrQv8HOxc/Azrm1xLnDzcW9x8eupre2vbW6ys7MvsDR1cvf3djm+eH47+jy6N/SyNri4ube3+vz69rT3eT09+qF/4GJh4yApJaalJKAjIiC/oKBipWOkIeRqpCWnKOfmaSY9Ibr+P6GjYn1/42Ok4eQ9Pz79ef77/X59oOGgPDr6vTd5/b5+vyFgpeHh4SDi4eOi4WIh4b37N7S0uP3gIXwl5KQjIKCiICCh4KJnZiQhIqHhJOMkZSTkJWXnZmglpSelZaTh4yQiI2RkpWAl6Gkpqulr7G2xbWupaqytLeztb+6u7qmp6O1u7zGurjFv7i8m5qdraOhqq6qpaKVmJKVkZ6ipauoqKebmJKTlI+VnKewr6uusKifmKGiopabk5OHkpKNmZyboqWjlIuQhfTj2erw+On49vPg4ufl4eP9//nv7/Lw9f6Aio6J+v2AgOvj6OPawcfBwMG8tsDb1dDY2fH/6uzt+ensifmBhOz53dvp9YuCiISB8ufo8urp3fj0+4qHmpSWmJyos66ll5WYn6GdoaCKhYqVoZibsaKMncC3xLS7sZualZazqaarlaCqjYOmq6KeoJ6unZWOko6ShYiTqo+YnZOamJqLlpCAYF24Y15jsGNjX2GyY2Rnenp4gXJ/cYN3cGZrcXxlb291Zmhzfm59e4Jgal9Rb2NpZXRtbmtkZIxXV1VRWl9jXlybnV5aZFxmYaJbZGFlZFtUVVpiW1x1clueoF5VWF9hX2FdWF1cX1qYWlhlUU5TU1mvsGOxaGS1pJ2xYWBfYl+Atrm9tcu1vLm8XmFjZVpgW3FjV2BgXWFeXl5jqKlqamNosaqvqKacmZuWhliwsKyjoqalmpWMhZxSVFWrpJ+hnZibp11gtrOqnaCbqKJVWFVfWlmetKO4s66ompibnJ2qnZueqpemWVxYYVddYqSNlZmcp4SadoaKl5GZjY2wbmWAZWFgWmdWm6akXWRdUFNVXFtiX1JXUFJWYnR2dWV2indzb3x2dIZwc3h8mp2JU4eNbHV8hpN5ZWFmaImYhmqNcnBpb29eXWxdY2Gul6xowW50cs2ztG5qqbGxtV6wwK23qqizYLplv8q2tKy7sl5cXVGaWGFkXZiRnliapaZZXV6AUFBcYGFmVlVRWm1aYWhfWllVjJBPW1xXWkxKSU5UUE2aUpOaT1JRVJKamZeTk6daXmZiWWVaWmKqqVmsml5fZGCvtMNmurvDqKykqKmvva26t7Gwrqigo5aampebppyWmaalqq+ut6uxwbOyr5+wumKoraSooKWan5daq1ZcW6KAoJuZmYOZm5CSl5SPoaKPlp+Wi46GjaKjkZuVi5aTmqCep6WgkJiFjoZ6hoGGhYKAj4d5fH+Ien+Qj5qLmICAiX6FjoaJmKCQjJiWm46Nn52Vi4uSlpCXk4qVnYmemZqsqqiinKqsqqujpLO4tayor7C8s6RftVtjXl5QbmJlXVuAW1xYsFxVYGpiZFphdltdYWloZ3RuqWCntblhami0u2hnaFtnq7LAwLXIvLu3p1hbVp+kqaqcqra1ubhfX21gXFpaZGRsa2xzc3Pa1Me+vsnSam2/eXJwcGpxeHJ1dm1ygnt0aGdlZG9pbW1ubGtsb2x0bG10bXFrYGRlZG1yc2+AbXJscHNweXJ3g3x6dXV2dHZzdoB6f39ua2h1c3h8dHaAe3p9XmBlcWxudnt5dnducmprZ3Nya3JxcXVya3BubmtucXeBe32Fi4aAenx8eGttZWpibW5pcW9wcnFsZmFnZ8G2t8TFxbK3srSnqLGqmZ2wrbWqqbKwrapUW2BesrmAYry8wby1oqulpKagnaW1pp6fmKyxl5ueqZ6hYKZaYrDBr7S+xm9iZWFhtq+zwLiyrMHCzG9peHFqa294fXhyZWRtcHBra2haVV5nbmZrfGxTYHt5iIGJgHNzcHGNhYOFcXyGamF9gXZydHSDc2xiZGJmWV9nemBlZ2BjZWpca2mALytVNDI3YD49Oz9qOjY0QkE8RzlGPE5EPTU6PUYyOz9FOj1HTT9LSlI8SkI4UkNGPUpCRkVIT2tJRUI4PUFDQUNqbERBS0NQSnlFTEZIRz05OjxANDNKSjZdZ0Q+QEdJRUU8NTo7PzxiQEFPODM2Nj14eEmAUEp+aV9rOzg0ODeAaG56coBpamBkMzc8QTpCQFVIP0hJRUdEREZJc3JMTUdLfHqBgYR8fIB6bEqOjouChZCUlZaGfI1ISEaMiIWHhYJ/iExPlZaRhIiBi4FCRkNPS0uClH+OiYiIf4N/eHN/cG1weGNsPT88RDxETYFqc3p+iWuBYXJ4f3Z+bWiGVkuASURFQE0+andxRUtDNzw9REJJRzs+Nzc3PkxKRzlERkI8N0M+PU4/RkhLUFZTMlNZP0ZPVmZMOzk+QVlZRzhTQD43QD80Nkk7Qj9qUmZBbj5BPmhYXklMdXp3eDtjbV1oYmdzQX5Ef4pzb2l4bj06Oi9YOUNJQ21re0Jpa2k7QUSAOTpDRkZIOzo3P1E8PUQ+ODs3VVgxPT05PzY2NjxCPz16Q3d9QURDRneAgH93c39ERUlDO0c6OUFqbDxzY0FCQDpgYGs8Z2t5YmBYWllea1tpY1xiXVxZYFhlZGNpdWpoa3Z1eHx1dmtwgXl+fHCGjkyBiH+Lg4h4gHhLj0hMTIKAfnhzeWp+h35/iYaAjIp3d357cHRyd4yOe4OCe4WEhYmCiYmIfY6Bhn5ucmdtaWhreHdxc3J2YmBtZ25fcmJlcGRqcmtodnllXmxrbWFicG5qYGFrcWlva2NueGl/en2Kgn90bIGJh4Z7eIKIgXdyenmBfG9EfT9IREM3UEZLREWAREVAfkM9RU5ERDg+UDU2OEA8O0hEWjxkdHlARkFgYTg3OjA6U1xrZ11sZmpqYTc5M1pbXWBRXGZjYlswLz4yMS4uNTM4NTE5ODdpaFtVVF1rODpZRj89OjM4QD1AQjxAUUlDOj0+O0I6Pj8+PT5BREJJQ0ZOSk9MREpJRUhIREKAQEdESU5LUEZFTklHRUpOT09MTFNMUlRHR0VQTU1RSklTVFFVPTxAS0NBRkpGQkI4OTMzLDQ2NDk4Nzs2MTUzNjI3OT5EPT1ARUE8OUBDRT1DPkI8REY/Q0E+QURAOTZAQHpzdYWGh3F6eHhucnt3aWp/fXxzd4WDg30+RUVAdHuAQ3hzeXdwXWZhZGRaU1hlXVpaVnB4YWNhZlteOVcwNldnUlNZXDgtLystUU1TYFpUUWNkZTozQDo1NDQ8Pzs3LS04Oz46Pj4uKzI5PzY5SDskMUtJVE1QS0FDPT1TS0ZGN0JKMidDSD46Oz1JQDw6QUNKPUNJWT5CRTxAPkEyPDYHe3t6e3t7eoR7AXqrewF6iXuCeoZ7AXqPe4J6jXsBeoh7Bnp6e3p7e4R6hXuJepJ7gnqEe4p6AXuMeoN7iHqCe4h6hnuTeod7kXqIe4N6pHsBfI97AXyMew16enp7ent7e3p6ent7hHoBe4d6A3t6e4d6hHsBeoR7B3p6ent6enqVe4J6jHsEent6eoR7h3qJewV6ent6eoR7BHp6enuregF7iXoFe3p7e3v1egJ7eo17AXqRewp6e3p6ent7e3p6hXuKeoN7inqPe4d6A3t7ev97jnuZeoR7A3p6e5p6BHt6e3uGeoV7inrMewICBACAjJyPiPr984L7goHr64aAjpL/+omDhYyfo42Hi4yEgI6PipeblqGiq6iTnJiRlJOAhpCPjZaakoqPl5Woo5yBgIaC9v2ElJGKhZSRnpOEjfWE94CEiYSVl5Ga8er6hoSFgoeI/ISB8eiBh//68v+L9fzt3PH13Nb88PPm2+Ha6vqAlf6L//7qhfGHhoD2/oCLgP+DiYqKhoDw/P2Kh/yUjYT53e/r5PLU1t/c3oDx5u377/P37+j57+/45vT7/oeBgfn4+oCPi/z1+vSDhoaFgfeGhIf16N/c5ebh3Ofn6uPn8ubn++XqsrfKudDf7vuM6dj34urUzdvogYbz8vfp09uA9uiA2tG9rLra2v/8/IHj8ujvj52VkZWflouetbCRrJuOk5aMhur7h46Y0MaYk4mHjJWK/YKA+IGKjrKdj5eQiZGVkY+SiY6EgoKChY6SiI3/l5Cbl5eNh/T16f+Zhuzw+/qG/fmD+f7k84SFi5SKlYiMg4CB7fHi+4CBg4OGgKCAh5GZnZaPiLWriJ6kqaWrkJyOivqZnqOcoomI+oeBh4iA7fDv/42JiIqLgoL4jIqOhPyC/IOHiIj+iYyT+Pjx9v315+/s9PPc8uTn6u6IiYL/7uLX1eDd2sfPvMfN0c7g2+bw2dfm8uXV+vv5iob35di/r7LOzffn9PT19fHs1tWA0uPi4uLJ59bS2u7g5tTm+frx8OnU/fDn6OHb2POC6v3o6vT87fLr2d7T0tna2Njc1NfOyd3P1tDMxr3Jw8PL0cq9vsnDy72rrqWqpb7RxdLcycHP09jk2efv/PH48+7p7e7mgPf8+enr4eTz6d3k6oCF+PzogYb2iIb6iI6Shv+AgOz1gYKBgouKhpCcoI6SkJWHjJX+44H7gYOD/4L7hfXxg4WA9c727fju6+/z6oLmhIX68vbz9IDk4uLv8fqIh/T6gvmJgvTq5ufd7Pb044aHlJSRkIWChYCAgYmDh4iTj5OQjI2RhpONk5WLkJORkpOQn5Wfh4OXkYOLipOTjZWAnJqcpqalrrG2tri5wMC/yb25sK6wnaCitbKqzMHExr65srSvm5mgqKCmrayYnpSam56WmJ+anaCcmJugmZWQjJKPmqWblpGGk4aRm5KenI+Mi4qEjZiSmJ6mpKeaj4SJgfTs2e79gPmLgP797+vog+Lt8IKG9Pf5+/6Ah4WGgvqA6eDe+9/s1dXN0tLRysPNytHS0djj1uTi6uTl5+z18Pb3h+fcz4WMs5KT/ef3gIzh0dn9gIiKhP+SlZWgn7ClrcytrrSso5iLmKGVmo+GhJ6XjYyJlpSmspySh4KLo6avrKe9pJGin5yao56mnpOLlIaLkJOjq6ein6ScmJWds5CAXW5rYra5r121YV6rsWZicHfLyW9mYmV1dWBaYmVgXGhkXGZoY21sb21YX19XWV9OVV9gXWFgV1FYZV9xcmtYW2Bfrqxda2diXWZgaV9VYKRcqlpcXlpnbmt1qKO0XVpZVFpcpFZUm5FTWaSalKBbmqqoocDCsLDOwsGwo6SlrrSAdMlsw8yzZLVkYmC9u19oWqtYXF9dXFmkrrBiXrFxa2W/priyprCSkZySkVinnZ+voaCbin2RiYiQhZWjrV5YV5+dnFFfXqWlqp9XVldaV6VdW2CnoJqVoJ+Zl6CanJifsqquw7nAhYeSe42do7lrqJmzmp2Dd32GUFeUl6eci5GApplYjop9cnuXl66joFGFlI6MWWJZVVhoZWF7kIxqeGxeZGZeXJqmXWpum4tpZVxZYWhbpVROmVNYV2pjV2ZiXmlvbmxwYmNSUlFRW2pvbHLMgHp8enlsZri0pr96a7m9xb1mu7Rgub6ptGFhYWNYYE5ST1BRlJiFmFdZXV5dV26AUVRYXVJST3FtTl1dXlxkUV5VVJNdYGVcZlRWllBJTlFOkZOSnFdTVldaU1SYW1pcVp9Wn1RVWFmqYWNssbSttr29sr+5xMayv7OvrK5kaGLHubenqbOpq5qglKGgnpacmqOpmJScpJeHq6qsZGKyqaaSjZKkpLykqKOmpaeii46AipqgoaeZrp2Rj52WpZKdqqiZmpOAqaKanZyVk6tcoq+in6aunaGYiJOIiJaTlJSTjpCEgI2Fj46QkIyYkIuRkoyAhZCVoJyOlYiQiZyroqirmY2XlpyfmKKlrKCin6GdoaOeWK2wraStqrLAtKuwsmNnsa2XVlymYl6qW1xlW6yAWaizYWFcXWBbVVljaFxhYmReYmm0n1ywW1xbsV+9Z7q6Z2hkwpvHwce7tauqmlWTWlqprK60vmq4uri5trRhX62zYsBsZr+8urizwMfFsWtqdHNzcmhob2xubnFxc3B3cG5samVpYGxtdHNqcGxnaGRgbmx7Z2RxbF9maHBtZ26AcGxreHx7fXZ7e3t8gn16gnl4c3V2aGxqd3Jof3p6fnt7dnt1YF5na2drcXFlbmtzdnZsa3JrbXJvbG54bGxrZWhobXNwbW1qeG1uc2pycGhhYF9ZYmpla3J4c3NmX1lhZMXCtsDHX65hV7G5saysX5uloVpfmaOfnZdNU1ZaW7qAsLSzxay6painqqejnpacmaWio6iqlqOioZ6hoqStrbS5bruwo2pqhnBzx7bJZ3CrnZq7YWppYLtoaWluantzeJR5dHd0a2JdZWpiZVpTUGdhWVhXZ2RzfmxlXllhdnZ8enSIenCBf3t1eHF1cGRdZVteYWVydXRua3BoZmNogGCALj07NmJlYjtuPz5lXzozPUNlYTs1NDlHRzMwNjk1Mj8/N0JFPENFSkc5Q0Q/P0EvMjk6OUJIR0RJUUhTUEg2PERDd3VCTk1MSlJKVUk8RW1Acz49PTpGSEVNZWh/RURCPUFBazs8b2lDSYd7a29AZHRzb46TfnWJe3hsX2BbY2aASHNDdHlgOVo4NTVqcT5KQHc/RUpLSkd/i45OR3tSTUiFcoqIhZJ1doF5eU2NgoSUh4qLg3+Pg4CEc3+HkU5LSYuMikRRUIeFjIJJSEdGQ39LS1KQioJ4gYB5d4F9fHR8joeKno2QVlZhSlxte4xSfnSQfoNpYmlxREl6e4p7bXCAiH1Ic29jVV1yboJ1cjtYZFtZP0U4MjM9NzBDUk4zMzUqMTUvMEpaNz9AWkc3NzQ1PUQ6YDIuWzQ4OEpAMz42LjU5OjpBOUI1NTIvNkBCOz1dQT5EREdDQXJyYnRPQWRpdXRCenU/d3xncT8/QEI4Py82NDY7bXZoeT46PD0+O02AOj1BRTs6NVJOM0FAPz5GNUM7OmA/QUY+Szs+bT46QENBeXt8gktGRkdKRER4SERFPGc4aDs8Pj5tQUJKdHpzcnRrW2VkcHNlcmJhXls5PDZvZ2deXmxpbGFuYGxtbGJra3N5ZmNrcGVTdnuEUE2Ngn5vam2DhJt/h4SHio2Jc3WAbnp8gYp8mYd9f42Ah3WAhYF5e3tulZCHh4J/fJVSiJWDgYyZj5mWhI6Aen52cnV5dn94doR3fHFuaWBpZ2dwdnFjZnBudG1dYFhdVWd1am94Z19scXR3bXN3gXh8eH52dndvQYOJhnqBeYCNgHR2eEVIdnVdOD1rREBuPD9KQXiAQG92QkI+PkE9NzxERDg8O0A4PEZuWzxxPT49bztrOl1WNTk2Y0JqY2tfXVxfVjVRODloZWNqbz1fXVZZW182NlpcMl05MlZbWFlYZmlmUzk4QT89PTQ0OTY5O0FBQj9GQkNBQkFHPUM/Q0E5Pz8+Pz88S01ZSkhUT0FDQkhFP0aARkA/Sk1LTUZIR0dGTU1MV1FPSktNQUNDUE1CVU5QU09OTVJSQT5GS0NGSUc6PTY4ODcwMDs4PD89PD9FOjg1MDQzOD44NDIqOC0vNzE9QT8/QkE7QEdAQkZGQUM4MC03OnV2boSORX5LQYOIfXh1R2RycUFKdIKDhHs9Qz4/PnyAcHNzinOCamtpbGtkXldaWmdgX2dnV2VhX1tbWVdcWl5hPVxRQjY3TTs9W0tdMjxNQUBdLzk4MVs5ODU2MD83OlI9PURBPDg0QUc7PzQtK0M8NTIuOzdDT0A8My4yQ0BEQTxLPzJCQz85PjtBQDs7SEFGRkdQTkpEQUQ+OTY5SjCEewl6enp7ent7enqEe4J6r3uCeot7A3p7eoh7g3qGewd6e3t6ent7hHoBe5F6EXt6e3p6ent6e3t7enp7e3t6hnsJenp6e3t6e3t7i3oBe5F6CXt7e3p6ent7e4R6hXsEent7e5t6AXuJeoJ7iHoBe4p6AXuEepN7gnqMewR6e3t6mXsBeod7hHqCe4R6BHt6enuEeot7hHqaewF6h3sBeoV7hHqHewF6hHsDent6hHsEent7e5F6g3uceoJ7r3oBe8V6AXuMegt7e3p6ent7ent7eoR7BHp7enqRexB6ent6e3t7ent6e3p6e3t7inoEe3p7e4V6AXuGegh7e3p6e3p7e4l6/3uTe4V6BHt6e3uFegZ7enp6e3uFeoV7onoEe3p6eoV7BXp6ent7hHqEewF6yHsCAgQAgIyNjZWVhY7/ger18+7zj5SKlouK+oCAkJmJ4YeMiJGGnYWHk6SlpJ+bopWloqOcjZ6pqa2MioaEhIOBmq6K8Jjx8vT4+eKGi42Khp2mjYSCiISF/PHx4trq6O3xhYWAlo6HgIKFhfb69ffuhYHv+fLt7YP54vL39OuNhYqF5ND7gIHs/YP99oP7hYiFkIyJkpKQge2A/4L7+PTw7e347+Hb1MnP09Xd1uvy6+bb39Lm1eDW5t/V7tje9O3Z19jb/4D+goT58v2Dg4WAhYWK/oaF/vr46+jg4Nbg6efk4+bo1+/i4trUy8OTuMzNtNe+t6yfs7ff4+SBgeWD58e8pM3CgODIyL3CycjI5NXk0Nzq7faAmJiNj56YlZGYjpOis4ySnJWMh4f2gouVjvD4+euC+ouMhIGBjKmaiY6LlKKto6iRiZGJjoWPhf6N+uHOw73O8vPT3uCD8/b474KQgYCAg4aBgPjs8Pvz8faHjIuNjYeMk4uGhfvx3Yqhp5mXlYeGgKCOjJiLi4+ikYOOoJWUwKmMn5qLoJ2hjo+YjYeAgPvu5e2AhouRjof46+37/f6Ehov1gISMkJSEio+N+IH09fD23vb4iYmAgoiDh4P///b58O/i7+Xj2/ro6dTFzNPU0trbzMvMyt/uxNbvgf3t287m4M/X59L0xujy2t3Z8erngO3474GB1dDR3drw6/vy7OTu8+nr3vbl2t3X3dzf1d7j4/X76+ne5+fk2tnf+tjg7+Xm3d7n5un+19nPxL3Fv9PMyc/Avcu/t7KxuMDB4sTK1+vP6eHd7/Lv6/Ty8vPy4Ovw6YCB9/f6/IP28f3r6+qAg4iVjIT3/oaMlJegh5qKgO7m6Pn+/YqFg4CLiYuJmIb8hoKB9P326eT8g/jwkYT7+ZyYnPjv/oH294H2/oD87YOGhPeC+YD0//3j5/Te7Ovk6fSA5IH9/PT17f+Eh4KRkZ2UkJWHh4KJ/IqQk4iJlZKblZqfnZCMh4iIiY+Qk5mhl5GHjYKFioqJio6Gj5GXgKmjnqKzr6qvt77AtrnCvr+4taamq5mUnKKZrcrVwL20t6eytrOXo7Cgqp+aoJmWmqGcn5qhlaSrqJSnj4WUj5eilpaUk4+OlJahpZuWm5KQko2LiIqCgoiPmZmnlpaTioqB7uPm7/n5g4D86/Pm7fPn7+jv5e3s+erv7ff09u3cgOPV39fa0d3t5e3g3d3KxcfH0sjK2tTj2dXs5/H2gvyH8Ojuz+P8iYaSmpeSi4aDg/WFjZeTgtPc94aWi42PiJKakrakqY6rjIyVipWbgIeYoqCbhoaOi4mVjZCSjY+IrZeqmbKjsZGmqaGttZ2QiZGSh5GC/I+ojpWvsaenl5r8gGNlaGxtXWe0X6iuqqqtaXBnc2posFpYZ3BlnWFmYWVabllYYXBsaF5bX1FiZGVlXWhycHFVV1pbW1tUbH1fom2msLKvtZlcY2ZgWWdtWFJXX1happmmn6C7uLq6ZF5XaWJcVVJRUZqjoaiiWlWboJmcpWC4pLPAuqxsZmZjq5rAgGfAy2jPwGPBZmZjbmViaGdnW6RevF+5s6qnoqKvp5qWk4mUnp+mpLW5rKGXoJikk6GSm5GEmYyJmpeCf4GIqlOqVlaflaJUU1VPU1NWmVRWqbCxpKegoZmdnpyRkJeejqunqaOgno9lgJCKd5F/fXZtfX+XlY1OToVPh3JtXHlwgIp1cW1xeHh1kIaSg5CepKtbaWVYVmRfY2JtaW15h2VocGpfWlmbU1tpZq63ta9etWdjW1VWYH93XV5YW2Vxa3VqZW5mal9nW6xgn5ePi5SnxsWvq7Bpu7q5rF5sYmFiZGZjYbixsbatqKZbX1tYVVFSWVVTVJ+ag1Zuc2lnZFhXgGpWVF5QTlRlVktVYlhScF9KXl5TYmBjUFJeWVhTVaablZRQVFdcV1ablZuop6ZXVl2VS0xSV1xUXGdnrlqin5+lk6yyYmNcXWRla2bIwLm7rq6lsKmooLWurp+TkpiYlZ2glZWOjZyjfZChW7KmlpCop5+mrqC4haOslJOTo5iUgJShn11hnJWVmpCZlqmhnpucnZGShZ6VkZWXoJ6hl52fn62wopuOkZeUi4qXqouWoZiajomIjJGfjJaSjY2TjZqWkpiOlaKcmZmWmqCdu6CiorOOoZWXnKKknqOko6arn6atqV1gtK+zsl2vsr6xtatcW1hkYFqtsVtbXWBrV2tjgKqvuMXHuWFbVVFbWlxebFupX11asLy2qq2/Yru4c2i/uHJxc7yvxWO5tF2nrFqzqGNmY7hgs1yzvMGorLursLCtrbtjqGG/uq6zqLxlZ2FwbHRubHJjaGhuym9wdWhncGpsaWpra2dlZ21vbW1oZWhwa2xlbmZmZ2FhYWhlamltgHp4cnaDe3d2fH1+d3iBfnx8fXR6fGpjZWddbYWRgYF8f291dnFbbHhte3BwdnBwdHhwcWhwaHF3c2FxYldpZW53bWxpamppb254dWxmaF9eYF5dXmNdXmFmbW1yZGdmYGdmwMDAx8K7XlmvpbGpr62iraerpKmkrJ2Uk5yiqaqngLK1vrKvpKWwsLWmqrGinKGZoZGTnI2akIqdlZebVKZdoqe2nKe/aGNtd3JpZmFeXalbZWlnWYqXqV5rYGFjYmpxaoR0eF5uX19nXmZoUVVka2xqW11hZGJtZ2lqYmJZdWN2a4Z8kHOChHh+hXRmYGlpXWhbrGV7X2N4eHJ1aXKtgDEzNz0/Mj1nOmh1cWxrQ0Q+Rz8+XzAuO0I4STg+O0A5TDg5RFBOSkI/RztMTkxKPUVMSk02PURGRUM6TFg+ZU9zfHl2fWNETFBLQ1JYQz4/R0REe3J3amh5c3Z6R0M+UEpDPDs8PnR+e4B3RDtkamVueUuPfIuQhHVKRkdEZ1h9gEN1gUF8bDZjODs6R0JDSUtJPm1EjEiMhn+Ae3mCe3BwbmRyenyEfI2Ui4eCiX+OfIl/ioV/j319ioNsZmpvkUaNSk2HgIlHRkhCR0ZIfEdJkJaakJGHhn+Eh4R8eXp8b46Ih4B8dGc8V2FfUG5hYmRbbnOIgXlDQWtAbFpVRmdhgIBtaWdqcm1rgHB5ZnF7d3o9S0c3NUE6ODM0LzM9SSwxOzcwLzNYMjhCPVFUVVAyZ0E+NS4tNE5HMzcxNUBJP0c7Mzw3PjhDOW1BY1ZIOjxJXl5LT1Y9cHJwZjlGOTg5PEFBP3t3eH96dXI/Qz48OzU4Qj89P3p2Yz5MTUVDRTw7gE0+PEM4NzpKPDI6Rj04VUY1RkQ5RkNGNjpHRUM8QX50c3ZCSUxRTEuEfYGMjIhEREdlNDk/REk8QUpHbTlpaWVrU2ZnOzs1ODw6PzltaGRoYWVhbmhqZ394fW5jY2tqZ3FzZ2VhYW54VGmATZSEdGyFhHuDkYOcZoaQe3t7i395gHeAfExRgoOFjoWLgJCKhXt+gnh8dI+HgoN/hoKBeHp7eIWIf4SAjZaXjIqPn3yJkIaJf3t7enyFbHVwaGh0bn98dXhsa3RnYFtcYmhlgmdobX1ec2prcHJybXN5en+Ednx+dENGgX6BgUV7d4BxcnBAPjxGPzltdUBBQ0VOOUxEgG5wc39/dj85MzE7ODg4RTNcODc4a3ZxZGN5P3NvS0FxZ0pLTWhccDdfWjFVXjNoWTo8O2U2YTRfaW1WXG1gZ2ZeV10zSzJhYldcVWc3ODJAPUhAPkU2OTY9aUFFST08RkRGREtOTkZAPEBBPz89PkJMRklETUdHS0dGQ0dAQj9DgE5KQ0RQTEhITE1NREVNS0xLTUZMT0RARUg+SFpgUVBMUEdQVFE6Rk9BS0JARD07Oz84OzU6Mz9FQzZGNSs4MTg+MzMyMjIxNDM9OzU1OTY4Pj8/QUI7OTo6PDg/MzY5NDs8cXV/iYmGSEKCdX50eXdrdXN3c318hn52dnt7d3VvgHV4hX19dHd/eIFranFfWGBgZFVaZFhnXVZqYWJhNWM6XFxoSlNjODM6RD42NC8tLUwsNTs6LTU+Ty46LCkrKzI2MEg+RTJIOTtDPUlLMTZDSklEMjQ7PTxIQkJDOTguRzZDN0tATjdISUVNU0U7O0ZKQUk9akNSOTxOTEFCNjtIh3sCenuFeoZ7AXqFewF6o3sCenuGeo17iXqKe4V6gnuFegF7hnqEewt6enp7enp7enp7eop7BHp7enupegd7ent7enp6h3sDent7pnoEe3t6e5Z6lXsBeoR7hHoCe3qYewJ6e4t6AXuEeol7h3qLe4N6pnuEeoZ7hnoEe3t7eol7Anp7h3qIe596AXuXeoJ73nqCe4R6AXuGeoZ7gnqIe4Z6insEent7e4Z6HXt6ent7enp7e3t6enp7enp7enp7enp7e3t6e3p7jHoDe3p7hnqNewF6/3uJe4Z6gnuzegN7enuGeop7AXqFe4N6u3sBeop7AXoCAgQAgIyB/IOGjZWKh+Hu6+GE7ov16ebw7omHhIeGgoX+j4uCl4+LlJWinZmSq5egjaSPg4ualoyPjviG7fP194mEgYeD9f6IgviOiouPoJiUh+zo0fbp/YqK+ergzt/o7YD+/IeGh4WHkIv27unohYDy5eHZ3unW6/zymoqJiZOP9//5gP+CmYaEg/7c4P/99IKGiIOD/vyAg/3g4sbM6+3z4ejUw8DU187LycjKws3P09ve3Oju8PD5h+76/vb15+Tr3Nnd5v+UjY2Eifn494SMgf769Ojt4d3b4drn+8/y7eXt4cDa3sS3v768ucrDtcK+sba0qpnJ2cjG3dLi3cvNs7W4gMbRwavL8Nfj5djx1smCiI+OkJOfnbekl6ipnaChkImOnZOUkZOEg4eamqSXlY+PgZuSk4+TlpiYmJGYnZ2Sj4uJh4idi5CKj5ORh5aRm6KrlLmeh4Dz3OLqhImG84PyjIKIhoeHjIWCg4WOj5WTop+jqrSfjo6Pg4b5+I2J+viIgJyXhZGE9YiNiuzo+P+VrpCmsLqjw4yTk4uJlYyJjo+AhYSFiJKA+uXngPWGhIWHgoGGg4yPh4aEg/+E+IaG+Pvt6u6EmZePjYyHh4uHhIOD5ubm3tnL4+7u6tjW2tne1MDFub7Qxd/c5OT55Nzf6Nbq9szRzc3T4/P/7fTj7e7WgN/q5dflz9Xq7+3v5/nw9/T05u/e2+jh4djc4+v0gPft/fPt/vrs8+DX4M3QzMvU4s/E0uLi6unk5NXUxLS6tMjHr8/J1uTMycHHwcfS1fL33/Xm9+ft8/aAgvv+8fjt9u7m9fr785KC9PX5g4X59PPr84eQj4n89oGAh4yQhIiYgJGE7vHmjoaCgYeRmYn9k4KEiISD5eDr74eBg/j3h+DX8fzn5/Ht9/XngZP0j4SA/YWNgIKAgvHu9//m2+2Ah//18fHu8e/28vjuiI2RkY2PlJuPlqagiIWHiomLkKCXmJSQjouNkoiBhYOHjo+ZlJKXlZSXkIP1goiNhZeWnp2YgKerraerrrC2vdPHws3At7S3uqixsZyXoJ2htbimnZmmnZuqor+dmp2ip5WalY6SlZ+Rkpibj6OWh5GNjpCei6GdnJWYlIyNhoKJnI2Oj5OOk4+MjIr9ioqVkKKfmJWSnZCH6eHk7YGAgYT98fv09YD08eXb2+qA9PGB5Oba3crTgN7c3ODo3t7f4f/z8+LZz73Bw9fR29jk1+Lk4uHU1uDd69LV2uXtgIbrhIiPh4uSm46WjZeQg4b0jJeQgIOXjoWPlI2cmZiC+PaDgZKL8PeFmpyXiomH+fzt7+z39oKEmpWOi5aYoYeMkpKhhJ6Ci4KJhfaLjZGdh4+aoJydm5KLgGFZrlxiZ3BlY6CwrqxnwHTLvrrCuG1qYWFhXFuuYl5XZmBbYmFqZF1ZblhhVGVaV11ubmJjZK9fo6yqsGNaV2BcrLVfXatjYV1damBcU5GakbWotF5gq6WtpbK6vmG0rFdUVFFTXl6oqaqwZV6wopqZoamUoLCjbFtZW2Njrr64gMJkd2FiYbWbobu6t11iZV1fvrthYr6jo4SHnZyfjpOKg4ajqJybm5aYiI6NkpyemKijlpWaWZ6opaCflZKSio2JkKVjXF1XXJyXkExWTZ2mpaOqoqKipZujsIulnJmilHyYm4WAi4mKhI6FfoeCeXl4bl5/inZthHuEenF6YWZmgG92ZlBni3mDi4iijINbX2RhXlxkYXhmY3iAe39+bWlteW9qY2hXVl1vbnRtb25uYnlrbGlqa3N5cW1zb25nZmVlYmJyY2NeYGNhV2dhbniBao53YFqvlZ2lYWZis2O1bWRpYl9hYVpXWFdfX15baWNla3ZlV1ldU1eellpYmpZXgGVfTVdIe0lVU4eBkI5SZU1fYmlYak9WUk9MVFBQVVVOVllZXGFSmYqKT5RSUFFWT1JSUFVVT1FUWbViuGVltLapoKRcbGhhW1tgXGVlX15fpaCqn5uMmquusKKko6CilIKIgoaPhpaLipKajYqSmZett5mglpWVl52ok5uSkpeEgI2YnJOnlZSlo5qXjKKdpaWhl6CQjJiRkYqPlpuhWK6fsKeanp+UmI+Mk4SOjYqPnYN3eIeEjY+TlJShnZOdl6Wiip6Yo7OkoqGpn52cm6+2ma+crJiin6JWWaSimZ+Yo5+drrW3tm5kuLK0Y2K3uq6kp1xiZmKzqVtVVVhcU19vgGlkt764cGVeV1pja2S7bF1cW1lYmJytum9oaMbIcLavxtHBusK6wLirXm6rZWBatWJqXmNjZbm6wc2woa9fZL25tb7CwcHCuLmzZWhvbGVmbnRtd4Z/a2RkaGViZ3Vvb21naWZla2ZkaWttcnBzaGRoZmdta2K1YWdoYXFwdG5qgG9zdHJ8f3x4e4d9e4B6cmx0eGl0c2NgZ2JmeIB2cWxxZVxkXXNcZGp0em1ycm1wdn5ubW9wYWxnXWJjZmduYXFpbGpvbmpua2lteGllZWRfY2NkZWi1ZGNsZ3dya21odm1mtLCvt2FdXFywrbKtsFikraGYn6pdp6FTi5eUnZKigK6uraqspp6go72tsKOblYiJjaGcoJOajJSUk5iTlqGdq4+WnKSsXl+bW1xkYGBkZ1pfVmBfVFifXWVhVlxwa19maFtmZmdZsKpaWGFZlp9Ya2xlVldWpbixtbG3sFtba2ZfXmtxfmhpb254YHZdYlpjXLBpa213XmFoa2lqbWVfgDgvWjI3PEM6OldqbWZBcUVzZ2FmYD86Njg7OTptQj43Rj47Q0NOR0I+UUJKP01ANjlGRj5ESYBLdXdxckI8OkJBd3xDQnVJR0ZHU0xLQG1yZIp4hEhJfHR0ZXR8g0aHfkE+Pzw/Skp9e3Z6S0F2a2hueYNygIl7VEZCRUtJcn93gHw/Uj89O2lNWHN2dD5CRUBAfYFFSo14eWNsg4KDcXZuZWaCg3ZycGxya3h6gY2Lg5GRh4iMTYKJhIB+dHV8dnhzdYlVT1BJT4WCgkhQR42Rj4ySioiGhn+JlXKOhnyEemiIjXRoa2VkW2FdWGRlXWRlXlBye2ZdcWVwa2FqVV5igHN9a1ZohXB3d22CaVxDQ0RAPjtCPEk7MD49Nzo5LywzQjs8OUA2MjZEQD41NTU4NE5CQzs7OkNEPzxBQUI9ODY3NjZJOz47P0E9Mj85PUJDM0w/Li5aSE5XODs4XjhdQTg9OTo+QTw5OzlBQUA9TEZIUVpLP0JIQUJuY0A9Z2g+gEpIOUI3WjZBPl9ebGk9TjhLUFJDLzdAPz08RDw9QkE6Q0ZJUFZHiXh3RYNJR0dGP0A+PENDPT89QHpAbj8/bHBiWVo3Qz86NDM0Mzk4NDQ2W19taWpgcX99f3NycnR3bV9kXWBtYHFpbHSCdnF1e3SKlnh/fX9/gYqUg4+Ghop0gHmBgHiKd32OjomHeIyHjYiEeoV6eYWEg3x+hImMTpKCkoR3hIyGkY6MlYmLgHp/iXNqbn14fnx2c25yaGBvbH18ZXdvdn5nY19nY2VmaIKDbYR0h291b287P3V4c313gHhveXx+e1BEeXV2QD5yc29oa0BHSUN0cEA7PD8/NzxLgEM7Y2toSj43MjdARzxpRjc5Ozk9Xl5qcUY/P3FyQ15VbHZpaW5mbGBNL0BUOzc0ZztDNzo5O2VlbHdbUGE6QHFnX2BiZGNmYWBYNjk9Ojc5QUc/SFRPPjc5PTw8QExFSEpFR0VFS0U/QUNBQkBGQD9EQ0dMTEaCR0tKP01LTUhCgERFRkRLTk1NUFxTTlNMRkRMUkZTU0dFTEhKVlhOSUdPRkFJRFY8PkFFRzxBPzk7Pkc4ODs9NkU/Nj0+Pj5ALz43ODU4NjM2MS4zPTEzNTk5QEFBQUJtPjs/NUI+Nzk5REFAbXB4fkVERUN/e4B8fj91eXJqcYJMgoFFb3dycWRvgHd5fX2AeHBta4R2dmxqX1VaXGpna19qXGJgYGFXWWJdaVBUVVldNjdNMzI1MTM2PDE1LTg2LDFSNj45LC09OSszNiw6PUE2bnE8OkU9W182R0hBNjk5Z3Fsb2x1ajc2RkA5NT5ASjQ2PUBKMUcxOzY/O2hCQ0VMNTpAQj0+PzYzA3t7eoZ7hHoDe3p7hXqHewF6mXsCenuEeoV7BXp6e3t6iHuGeoJ7h3oDe3p6h3uEeoJ7inqGe4R6hXuGeoV7BHp6e3ugegF7jXqFewZ6enp7e3u/esh7hHoGe3t7ent6mnsGenp7e3p6hnsEent7e4R6h3sBfJF7BXp6ent6jnsFent6e3uFeo17zXoBe7p6gnuMegd7e3p6ent7hXqEe4J6inuDeoh7AXqGe4R6Bnt7e3p6e4t6B3t7ent7e3qGe4d6gnuLeqx7AXreewF6jHuEeoR7hXoBe4Z6BHt6enusegN7e3qOewF6j3uCeoR7gnqHe4d6lXsBeo17AgIEAICUjIODiY+Qi4n8gILk9eqGjfnj4MaB8O6Dg/eWiIyD5Y6hgIiPlpGKiJOinIqQiomSh4SC+ISP6f/784j/g/+FhoaDh4WCiY7+jZOVnIqCgPTT3+mGiv/yhfHu4eX58fKJh/3t7/6I8uDQ0v708vfj3N7T2d3t5eLk2dvp4ODr44Do8YiBgpWC9+CCiIyAgPz29+jt5+ni397czOPk7fbv4dra08zOydjY29vJzsGzu8bl3OfS2+Xu7erz3+rv8ef22drzioWVgoDp4+vo4vr18uTg2s7R4N/j64D57PPq3ePLx7u4rsbG1dHNyuji28PQztDVzuaB1cDq5OzV5dXf0IDJ2tHY9O7v5/Ti++Ha7vz99/uQpKKXk4+dqKalj4qjoqyen6CRi4yYlo2Lm6Grm6iXkIOEg4b7hJKXl52klaWpiKCPjJuIhoCJjIuCpKmhoKq0r5mXk5qL+I2YmJ6ajaiYw4uI+IyLhv2NiYuAgo6Ok5Wim4+C/P6D/veLh4L784Dy8/6EgPvx9PuHhYeGlqmJj/3l9ICei4SLiIaRiY6RhvuVhZDri/nm7/D08oeC9OTw9YL89fqN7t7L3tX57+Li+Pj+ipOUgYmJiP34+/j29evp4uTk3+vJ0tva3+zx4dXU1czN3tLc6+Pp6dzv/YDr6N3p6++A9fiA+uqAg/f15IDZ3vDN0MXC5PTe2N3t5ujr5ur7/ID99PTo4+Dg2tDO0N718Onk19/l3dXUv7bHzLq8ysHW6erj6evPvb+1wL++vsTZy8/Nwczd0bO6xb/DzuDiyNfm1d3yzeHqg/z9hf7x+ezxgej4gYiKh//ohoj68fyJiYWAhP2MjJSmrpiFhYCZk4qKjJONif+fjI+LiouIgITy5fDnhP2JiYPy4Pnr3e3v4OLf6fH54t35koj7hYSUjYmGi4/99v7/3vv6iZD87tvn+Ojz79zy/4yLiI2wlJuWlJyRkoqBiouDkoqUjZmTo5aYpJWLkYiIkZCYk5GSnpCPi5SPioKMkYSQj5iopoCxr7Ono5mdvr3Cz9zmx7a4sK6ejo+EhYyWnKCYkZKToKurpaypn5KiopqZko2Lh4SNl5KWi5ekmIuKkZWol5SUlpKIj4uDgIuMg4+ZoKKim5WUlJiap5CUm52boJ2knZ2OjoXvh/747+7r6YD5/veD/Pj99O39goCC/+PZzMvHz4DH4d3RztDV3Nrd8eXr7NjG0+LL4tnn2eHc3ufh2Njq6/HWy+Tc1NDd8c7p8vjxgKT7/oWFg4aJg4iKg4CDhfD/jp+UlpKE/OvZ9P+DgIOCg46VjIGAgfHt+fuBi5iL/YaBg4L3gYj0hJCIm6OPl5ORkoD2h5KXkI6QmpeWkI+ppoBiWVJTXmVqZ2W3XmGmt7Jsdcy1ro9it6pdYKpoXV5Wk2FwU1dgZmFZWF9pZ1tfYWJoYl5apllmmqyxo1+vWKpdWlpXWFpXXWOyZWRjY1NPT6CGj5xdYLGmZb65sK+5qaVbVpyLkKlgsqqhpsq3sa6WjomCjI6em5aWkZihoKy2roC4v2VeX2xXppRZX2ZZWrGvsKmwqailoqOfjZ6cnZ6Wj5GVl5OVkJ2YmZiGjol+gISckJWFlJyopJqijpOXl5KniImeXFlnVVaWkpyUjainrJygnpGXn5+ioFiklJqWiox+fXR1cZCUoZiTiqaemIWVkZCWiplagmyOhot0iHp/coBpcGptf3+FeoqDmYaEk52enJ1dcXBhXmJ0goKAaVh3d31xb2tbVl9uaWRjb3iAcoBuYllbWV+1Y21vbGluYm54XnNlYG1eXFZhZmNefH55dX+Ggm5xbnNntmhybXRsYXRujmVjtmVjWqNbV1xPUFtcWlxoZFpToaVRm5RYV1WimoCQjpBJRoeFj5hWUVFLVF1FTol7hEVeTUNLS0ZRTlNZUppkVWWSV5SFjpCRj1NQl4maoFadm51enZSJnJu9sqShtba5ZWhkT1RbYcLFz8LBtKainZugmKCGkp6ip7S5ppiWlZCOmo6Nk5GalIybqlWYnI6eo55YoZ1PnYxRVZudkICLlbCVn5uQrbeYgIWWkZaZlJadnE6WlJiOio+Sko6OiZemnZCMhIiTlY+VjYeWmIeFi4GMkpGMkpeFfoSGm5+Yl5qml56el6a7sZOUlYyPmqCkjJqlmZuriJaXWKanWaeguq66bLrHaWpqZrufYGWqoqVbXV5dXqpaUlhib2ddXIBrZ2BhZGtkYq9uYGRjZ2lmW1+pmqWfYLtpa2a+s829rbu+sbS0ucHFraC2a2CqXlxsZl9fZWm6vsbKrsC5Z2u6tKe1wLnHwKq9zm9ra2l+am1kaHBqa2lka21haWFnYGxpc2lrdWtnbmpsdnRza2ZkcGRmZG9rZF1maV1lYmdvaYBycnZ4dm5vhH57gYmMemtsbG1mYWNaWl5mbHNuaWpobXFtYmlmYVxwc3Bzb2xtaGNrcWdqY2ZoZVpYYGV0ZmRobGpibW5qa29vZmtxeG9tZ2FjZ2pte2RodHRxb2lvam1jZWK2acW9srWwr2O1salRmJ6joKa3X1xerpubm6GjqYChsayblZmYnZufq6eysqiXoKybq5+ql5eVm6CcmJmnpaeQiJqUl5Kbro2mr7WuWnGjoVFQT09UU1tfXV1jZLS1ZHNjaWpdtKaUsL1fXV1YVVldVUxPVKCksrFaYm1hqllVV1iwXGa2X2phbXVkbGpoalyyaXN3a2hlbGppZWR8dYA5MiorMzo/PDxtPUBicWZCSnxiX0Y7ZF44O2hJQEE4WUJQNz1FSUU9O0RSTkNFQkBCPDg2aT9Pcn96a0JzPHZCQkNAQkQ/REh2REVHSj08PXhia3ZKS4R5TYmGfX6OgIFLSX9rbINLg3ZscJSCf31sa2tncHKAeHFwanN7eHiBc4B0d0I5OkUzXFE7Qko9QX9+fnJ4dXl2en17coSDh4iCfX+Agnp4bnhzdnxyfHhwc3eQhot5gYKIgXV+bHV3fnuLcW2BTUtYSEt/f4qCfJWVmIqNjH99g4GDgkiMgYmDeHtucmhqX3ZzfG9lYYJ9fmt7eXh5bHhIZlZ3c3tjdWt2aoBqdW5wgX19cnxugGphaG9qYWA9TEo9ODM+Q0E/KSI8P0c+QD4xLzhFQDYxOD1EOktEOzQ0MzZhNj9EPz5BOEJJL0M3OEg8PDc/PzkxSEY7Nj1CQjQ5NzwxTjM7OkE7MkNBWD08bUNEPWs+PUE1Mz8+PkBLRT87dn8+cGU+PDx0cIBrb3U+OnBocHRBPj85Qks2PWRQWzFIPDQ9QDtEQENFPW9MQklxS4B0gIOFgExHgnWEgkiEe3xNdmlXYlh2bWBecHF2Q0ZBLjM1OG1rc2ppY15iYmx3dYNmb3d6fYiNfG5ubWdgb2FkbGx2cWx8i0V3e3CAg4JKg35CgnRGS4iJe4BydoxwdnBuipaBb3SDfX59d3uHhkWHiYqEf4CCfXhwb3iHfXd7dICQkIySgHWCg29udGl1eXh0eHtmWltccHFubnR/cnVvYWp8cVhbXVdbZXFzXWp3aWp5VWdrRYCCSIJ4hnF3SHOER0tMSn1jQ0d4cXNBQ0E+QG8/OD1HT0g+OoBIQTY2OkM+PWpPQURBQkJBOT5qZG9pQHhERUFxY3lsYG1vZWlkaW9uT0JeQjdeNzRDOzY1Oz5lZm1xVWxnQEVwa1tobmhzalViaz06NzVMPUI9PkU/QT02QEE6Rj5EPUlIUElLU0pERkBARkRGPz9ATENJSldWUkZMTkBFQkVLRYBKSEtJR0FDWVRRV1xgUEVJR0tIRUlCQkZNTU5HQUJETVRTSlFKPzZDREBBPTs7OTU9Qj0/OT1CQDg4PUBNOTQ2OTcwOzo2Njo5LzM5QTw9PDc6PkRHVT9ARUNAQTxBP0I4PTtsR4N8eoGAhFCQkYU/c3N1cG6AQ0FFg3NydXNzeYBthYF2cXV0dXBueG56eGlbZHBbb2VyZGdgYWhjWlppaGlUTl5YVlJWY0BSWF9dMUlaWi8uLCwxMTo8ODg8PmRiN0Q3PUM7dG5gd4FCPjs2Mzk+NzI2OmlrfHw/Rk09ZDYyNDRiNTxeMzs2RUw7QkJAQTNaOUFFOzk5QT48ODdNSol7CHp7e3p6ent7hHoGe3p6e3t6hHsBepR7A3p7e4R6BHt6e3qJewF6h3uEegV7e3p6e4d6gnuEegF7m3qFe4J6hXu1eoV7kXoBe5t6AXuceot7AXyYewF6oXsBeot7BXp7e3t6jXsIenp7enp7e3uFeoJ7hHqIe4N6jHsGent7e3p7hnqCe4R6BXt6enp7jHqHe6R6AXuGegh7enp7enp7e5d6AXvGegR7enp7hXoDe3p6hHsHenp7e3p6eoV7AXqQewF6iXuEegV7ent7e5B6A3t7eoh7h3qCe4t6/3uaewJ6e4Z6BXt6enp7hnqDe7V6BHt7enqMe4J6hnuFeot7hHqEewF6hHsEent7eot7AXqNewICBACA/IWBkImOmomKhfyJ6tvm6ufZ2PLR+PGGjoCF/ISMj4P7m4Py8IKCjf6DjYKFgoL6kJyJl5Kak4iLioqOi6CVkIySnJ+Yko+MjpWclZWYlpibh4CQhej7gYmDg/rv+4aVhv2D+IHy4fHr3OXY4eDp1N3b4fDeytDc4dbgzc3V3/CA+dfth4OPjYjx+O7u283s4dXL5uLJ4/zc19rPz+Lqge79+ezSzd/Xx9ba19XW5+Xv5ufo6Nvo6eb++Ov7/P7z8t7h9f3w9/j85tbj49rf0dbV4tvaztjU2OLyjIiHhYDt2dbI6ungycu0usfe0dDGzsfQu9TUtL3I2uGAhIL36OSA3enZ4M7UysbdzOPdzuaE9eOHmqOOk4uNlpOlvL2exMOnhpWqvbS5n6WvppOPkIiF+/z1goyDiImeoJKchIeVmY+Uh4qci4H8+vL4/ICEg4yWlJGVjpijoJiYlYyIhZOWm5SRj4WUlIf+iIaGhv6FhPL/jJyblPqC5efm5PH27uGA8feKiYONjYuN9Pv7hIyK+vaRg4iB+4uNmIaXhvuNmI/5kP+FjI31+Pb46O3t7dvPy+3a4eDl8uTy4/mB9eXu+fWFhIj7/ev5jIuJgICBhfnQ2N/k4ere3t3a4NHT+MHU2M3M4enn4+XY2tzh6PLz5uju5unrg/X6/4eCgpL26NyA3tLl3+H0/YHp3+D27urm8fPy/fvj0s/gydTm6t3f3ebg4ffu74Dn3uXXzb2vurvJwtDRzMrn09LWz9G+xsPHv7aty7vJzsTK09jo89e+1MDTxbTV18nb9d/3gYaGgPn8gPKG/PXp8fmHhoCDlZWAgobs5uLr/4SCkZmYnpOWlpOAj5OWkpGJgoaIkJeIgo+Pg4D08+WDgoH38fOI6/Dq6uDW3ODi3vXs/oCDho2O/IGCg4KMkImLhIGLkISUiomRjYT76e319OH8jIWPjI+GpquiloiNjpiKjZWHhouSjomXlY2Gi5WL9YOOgoCIhpKWlJubmJeJhYiElqGVoaGmx7uAtampmaOenZ6dn6iqrbSzraiYlYuJi4eTnZ+bjYaFkpaXlpufoZmPmpKUjYiLjY2UlY6LjpOZlJSYk5eep6KckKCZoY6Oj/KQmIyZoZ2ru623u6Slm6ekrJ2alJSQko+VmIKD+oDv7erw8fP3/fPo7e7k3+Dr3uDk9ezYydHDu72AysbP2NbU19Dc24Hz8fvj29XX5eTI0t7d3O/t//bm5NLfw73O3MbZ1/rj4eDn5YPx5vGDgoOKj4WGiO6Cg4uQlZD/i4L03oDp3dyB//WXnZGPjaGkk4mKkYDn/v6KioeQi5Dy8vWE+On8ioeT+f6LjeX38ouTjJ2MiZmEhZ7ug4mAqllYamVseGtqZb5osam8xsm+uMedsqNUWlBTn1pjaGC3dGCssmJhaK9YX1RYVlutaHBcZmBoZFhWVlZbW2ldXFlgaG1qY2VmaG1yZmJgXGNnWFNjWpuyX2pmZryvsFtnVphTm1WfmK6zorKmp6SljJOMjqKPgYiUm5aojpGir7+A07O9aGVnX1uUmpmgkYmpn5KNpaGJpbygmaKVlaakWqOtrKWRkqWhjZydmZiVop6fkJSTmZafpJ6sppuen6alrJegs7Wssq+7qJimpZyikZeapZuWjJWTk4yWWFBRUUyVjYt+n6CciY19gYyekYmEjoaPhZWPcnmCj5NVVlidi4eAfYFzeGZyb2mBdIaBdIhTlolVYmlTV1debGp5hoVfg4JtTlppeHeBdHV/d2ZnZ2Bep6SeVFtWXWN6f3NxWltlamRoXFxrXFOnq6esrVdXVVtkY2RnZnF8eXJxc2djXmRlaWNgXVhlYlSaVFNTVJxVV5SeWGdnZJ5UgIKHhJmhlomAkpJTUE1XWlxcnqGZUFRPi4lbT1ZPk05NV0VTSoZSYF2XXJlPU1WRko+UiYiKjIWAepWGjY2RnpGlmrFdrp+qsq5gXV+loYyVWltaVlxdYq+LkZaYl5iIkJKTnZWWu4eUl42Jl6GclZSKjYqOlp2impygmqOaWp2WmlVOUmKakYeAjIegm6CxsluajZGhlZKMl5aYoaSOfH6PeoicnZmYlp6YmqWYlVKMiZuPkY2FkJahlZ2WioSXiYaLhJB/j5egmZOLpJOhpJedqrC2waCInI6bl4WipZicspiuV1xZU5mkV61mvsG2vr9lYFpdamlYWFumopqov15ZYmViaGFnbGiAZWxsa3FqY2ZoanBmYnFzaF6vrZVbWVmvr7Nrt8LBwrars7e3s8e6x2FgXmJhr15hZGRoaWZmX2JobF9rXlxjYl67sbvEvqrEbGZrZ2hdcnVvZV5lZ3JoaWtfWltiXltqaGRgZnVxx294aWZpYmtrZWlnZ2VeYGJfbnRobWdngHaAd3FxZXFycm5raGxtaW9xa3BmaGdmZWFmbnRxamRjbWpoZGVpamhkc21xamdlZWRoamNcX2NiaGhrYWRudG1pX25seGtrb71wenB1dW5yeXB3em5zcYJ+fXN1cWxkZmNrb19nzWnCwbzFxcTJx7yupKGal6Cup6WjsKSVi52XlpKAmJafoaCgl4+YjlOjqrKppKGfs66VnJ6ZmKSjsaygoJanlI2cpZOclbWmo6OwrWWto6pXV1daYFhcX6ReXmJnbGi4ZWO9qWq3p6ljwbVtb15VVGVqX1ddbWGpu7NgW1ddW2Kkq7Blwau+Z2RuvsNxc7G6tmxyb3ttZnZiYXimXV+AYDMuOzQ5RTo8PXNDaGBsc3dtaXxYcF81PDQ4Zj1DRT50UD9ydEJCSXg+SUFEQEFxREs7RUNOTENAPTs+PEtDQ0FHTVJNRUVESUpMRENFSFBVRkJTS3eJSFJLTY6EjEpXR35GfUR4bH59bX1zeHd5Z3JvdYl0Zmt1e3SCa2lve4aAj296RUJHPzxkb3N8a2R/dWZedXBed5B1b3pxc4OJT42al5J4c4B5Z3iAgYSCj42MgYeEiHt9fnF9eHWGiI6Li3N2g4h/hYmTg3uMi4WMfoODjYF7anNwbW56TUhISER9cnFkh4V+ampWWWV8cW1lcGlyZXdyVWBodHhHRkeAeXiAd4R3emJpYVxtXGpjUl47Y1A4RUc3NzEzNy42QUEpQ0o8ISw6RUFMQERMRDQ1NjE0XFtYMTcvNTRCSEFAMTQ9QDtANTpMPzlxbmNkXy0qJCoxMjM2MzxCPzg1NzEwLTc7QT08OzZEQzhkOjs7PGg8PF5mO0pKTYBGZmJiXm13cGSAcnZFQz9GRUhHcnl1PkJCc21JO0I8cUJEUD9KP29CTkpySHc/REl6fXuAdXV7gHVua4V3fnp+i3J+a3c9b2JvfXpFQkRuaVVeOzk3MjU2OmRHU15tdH54gH+BiHdymWFxdWplcHpzbm5lamludHp6cXeAfIWBTYJ3eUQ/RVeEeW2Ab2Z5cnWFikh8dnyOgnxyd3N1gIZ2Z258aHGBhnt7dn1zcn91ekd+gJGHiYB0e3uFeIB8cWqAcWtrZWpWY2xybWdhd2l2c2NkbXN9hmlVa15sZFJub2NpfWB7P0VFQXuCQ3pIf39zfX9HQTs8R0g5PkR5dW50gj04QEI+RT1CSD+AOj09PEE6NTo9Q0o9OEdIPjttbWJAPjxwZmlFaXFscGdeZGlrZnZlaS8tLTM0WTM1OTc8PTg4MzM7QDZEOjtDQTx0aXF6c19zQjlAOzovRktFPDI3OEI5PUQ6OT1FQj5PTUZARU5JeERLQD5APUVIRk1PUFFMTE5KVltOUU1JWkyASURIQEpISkpISUxLRkpKRUlBR0hKTEpNUlJLQj0/S01NSElMSEI7R0JGQT5AQT5CQTo2ODw/RUREPT1DSUE6MkE9Szw7QFs8Qzg8PTg7Qj5ESkBFQU9JTUREQT85PDtBRTU8eEBwcHCAgomRko1/dnhubHV9dXRxgHptZXlva2aAbGpze3p7eGtyZz91e4BvbWlqeHdgZ2xoYmlpdm5kZlxsWFZiZ05XTGdWUFFgY0NoXmY1MzI2PTg9Qmc/PD5BRUBqPjx0YURuYWBBe29KTT44OUlRSEFFUkp4h4BEPTk+PENkZmpAcVtqPz1HbHFHSFxmYD9EQEo8OEc0NEpWNzwBeol7Anp7i3qEewF6hHsJent7enp7e3t6hnsBeqV7gnqEewp6enp7e3t6e3p7nnqFe5Z6AXu7eoV7m3qDe5F6A3t6eox7AXySe4N6lHuFepx7AXqEewV6e3t6eoR7Anp7inqHewh6enp7e3t6eoR7AXqGewp6e3t7ent6e3t7lXoBe4V6g3uEeod7pnoEe3p6eoR7inoBe516AXu0eoR7BXp6e3p7hXqJe4V6m3sKenp6e3t7enp6e416hXsBepN7h3qeewF633sBepx7Anp7pXoBe6N6BHt6enqIewF6hnsMent7enp7enp6e3p6jHuDeoZ7EXp6ent6enp7e3t6ent7enp6insDent7AgIEAID9j5SZi5GXjor6+vT86+DYz7273O+Ci4eC9oKFlaOf/Ifvg/yE9veMi4yKio6NiJSlpqSwo6CWmZiyoZqakIybiISWk5CVj5mUqK6Rl5uJk5GBhYKC4/H1gYWMioCCh5OLhv+EhPDyzuzn/ejy2Ojf3dPHyM/Lz9zc2NXM29De9ID+6Pr+hPeFhf6ByuDk2/Tn3+Xw/vvl9u/e1svJ3Ojh3dza1NvV3tbl29fb3vDz6t7M3djT0NjY4vT57fH27+3n2+/m2uDW1LezuMG/xcnE1dLn1+fe4eju6/WCi4D+7N/ZxdLZ4OHVx7nY0+Dl9vLiu+P/h4H/5OjW5IL/2O/b4YCBjY+LkJiU/4rr9fmCgpD9hIiFn52b+OP0nO+FzNXipKCWrruamZmlvrepoY6GiPWCgv2BhIakmJunmJHAnp+uoaCYtZyHjIyA/omUkpWeraPGs5GKoqenkY6ajJyL9oOXmpKMnJanjoKEg/+G+POAlqeZm5WG+vj6/dLzioL18oD/goWE9YGFipCHgfGAjviDhor1+/Dh8YGEhYX4+YP264n59/2A2/aHjoORhevp4dDj+NrS4t7h6Nfh+OD9+vf8++vv9IT354KVhoWE+oeE6+j27/rs6ujw3sXZy8/s3tjI297s6uXw9OXN5eTvgIf95v2CjY6BgvuE++Tr4tbVz4Dd8uj7/+n1h+Pw9f6F/PGC/oSE9ez55ubsgOT838/X1d7V4NHUyM20ytnLtbe3m7S3v83Dw8rMztXT2s/OxMXKzd7k2Nzmy8zS4uiD5svTzc/DydfS3N3j4OyNhISKhImH9vr++vro54KMjaSXjoKB5uz3iYL0goiGlpiYjZGPl4CaopmUjvOOh4KSi46Kiuvt/e//4NDo5ujy6eXq8uj45vT59/3g7/Lxg4OK/IONiIiGi4qCg/f2hP+F+IaWhJCFiYP89/qGgomH9oP8/4P+hZClq56RjIuBgoGCjKSUmI6OkIiJ/oeChI+RkpaSjYyQl5aZkZSGiJCPj5Gfp56pr4C5uq+svbmxqamopKynpauznKqamZqToqKQmZuZkJeboqegpJ+YnISQkZaUl5iRmpaPlZqRlJWhmJqVn6KmpJaWkYaJj5eVjZWRnK6oqLu5sb28raKUmY+NlIuMjoSJho6Zlo2Lko2O9f/05oD9+ODo2env5OXj7tvs7NrDws/Ew4DBztLc1tjW4d/y+vfr7ujTx8XA08vd1NPY4OTL1cPIwLa4ucC1xt7s8d3e2snR2tTN6O/++YKHg4GJ9f7n4u31gP7h5/P1jJCJjJeL/v+FjYuTnp6Li4+lopOFioeK+oSHkYvt7IKO/YKTjI3u/YX56ufti5SUoZWHlpqer4yCgYCcW19nXWVwa2q/v7LAtrGvrZ2ZrLRfZFpTllFSYW5wsWStYLZfp6RgXFpXVVlZVmV0dnZ+cm5gZGF8b2dpYVtoWFRgX19lYm1peX1hZ2pVYGBSWFlcnrCvYWZsal9bXWVZU59XXq6yk7Koua2xmaaVloh/goqFjpmenZyPno6ZrYC7p7a6Ya1cW6ZUeIyUiaSUiYuPn6KUp6aXlImDlJuNj5GTjpeVmYuRhYKIlausopeAlJSUl6ChnqqjmKGmoqShmLKuo62joo6Ki5OSlZmRoJqrnKWgnZ+gkJROVEqaio6VgpKZoqCXjYWln6eturCiepy4YVmul5iLlFemhI15eIBFTk5ITVlYj1aJj41KSFGJS0xLY2Rfn5WobqZaiIyVZ2JddIFlZWh0g4F0bV5ZYK9eXLVaXWB5bGx1aV+IaWl3cG5lgGlVXV5Yt2VvbG11fXSKelxednx2a2VuYm1hn1hra2Nda2d1XVRYW7NeraxXZXNgX2FXoaGbonSSWlGKiICRS09NjUxQVllPSoVIU4pMTVKRnpiGj0hJSkWBhEWFe1GZmJ9OfpBNUkhTR31/g4GcrpaMmJKSnJSWoo6qoaOpqZyjp1iciExeU1NVo1dXl4+YlqCSkpGbjoKdlJiypp6NmpqioZyipZiFlJSeV12unKdZYF5WVJ1XppSkopGRiICSp52rsZaaVIiVo7VhsJtXoFdXn5KahoaRU5Col5GWlJ2VmoqQg4Rwh5aRio2OdoiCg4h7eXuAhIuKm5CZlpqYmqSllZmji42bq7BnsJGXkI2Fi46MmZOYnKJkXFdcU1tZpLK/xsm2r19hYHRoYl5Zn6avYFyvW1xXYWRjWmNkZoBnbmRnarlxamRqZWlmbLm5wbS/nYyfoqu3sbG2vbbFsrrBwMGwwL6+ZV9jqldiY2Nma2dfY7K1Zr5mtWBqWmVaYV61sbdiX2ZmvWbGxmbAYml3fHFoZ2lhXlpVW25gZWRmamtszG9sbXRubG9pZWVnaWZmYWdeZGRgXl5rcWdwdYB8f3BsfH15dHNwaW1naG94Z3Jnamhjbm9jbHFxam5tcXNqa2pibFlnaW9sbmxkamdkaWljY2VqYGlkanNvZlldV1JaZG1waXFvdoN5eIN/doB/dW1qcGtqbWlqaV9iYmVvamNnbm1wvci7rWG8taGjlaarp6Wmr5uppp+KlaigmICRm56inp+XmZakoqKgn6Sbl5WUo52un5aXnKGRoZmjpqGfoaSQmaiwsZ+foJintKehtKy0rFVXUk5XlaSTj6KyZMm1wczBb25la3VovLBaXFJZX2BXXWl4c2haYF1eqlpcZ2Kmp11pr1poY2izxWvKubG9dXt+hnVia2trd11RToBbODk7LzVAPD5scm13bmhoZldQZ3E9Qjs1XjU0Qk9Na0FrQYBEdHJFQ0E/QEQ/PEZNTU5TT1JHS0RXS0ZIRUNPPz5MS0hLQ0lBTlA9RkxATU9FS0pMeoiIS0xST0VFSVNLRoNHSoGCYX95iX6Eb350dmxmaXFtc3t/fntsd2JsfYCBbHl8QnQ+QH5DX3R6cIV4aWpte39tfn5xb2hneYR6e4B/eX56fnV+dXN6g5eWi31qgXx9e357dn16cXl/e3t1aoB6dYB6fm5scnx4f4N7ioOUhId7dnl4a3NCS0KJfXt7ZnN8gntwYlRvanJ1hX1uS2l9RD56am1ibEF6WWRVW4A5RUY9P0ZEakBgZmY1MzlWMDAvQ0Q8VEJMNVcwSE5WNzUvQUkxMDE9SkpEQTIwNl02NmkzNDRKPTxJPTFTOTlGQT86VUY3QUE2Zzk/ODg8QzlNQiorQEI9NzE0LDctOyg4PDUxP0BOPDQ5PHhBbms2Qk4/QUdCgYN9gFNsRT5qaoB5QUREe0FDR0lAPG07RXE+QUNteXNkdT9DRUB3ekF0ZEV7eYA9XXNCSkFOQnR0dG6Jln5yfH2Dhnp7gWR6cHB4eWxzeEBqWDVEOjo7bT08XFhkZXdwdHaEd2mAc2+IeXNhb3J5d3N5eWtbampxQEWBcoZJU1JJRnpEfm57eGttZIBugXaHi294Q2l6hZZQi3ZCdkJDe3WCcnKASHWNdWhucHZud2p2b3VlfoyGfHt8Y3JrbXNlXmBjZmdmb2FmZWdrbX+DdXqEZ2VwgIZRhGhvaWhaXmBdamRkYWlIQ0JHQUlGdXd9gIRybD1AQFFGQz8+cHmASEFvOjs3QUJDOj49Plc8QDU2O1tEPjpDPkM/RGVneW5/Z1VmaG50bW9weWx4ZW1zdnhkcW1lNC4ySik0NTU4PDozNllfPWs9ZztHN0I5PTptamw9Nzw4XDVmaTdkNj9JSkE2OD2ENoA+UkRIRUZIR0mDSUVESUZHS0dCQkVJSU1LU0pOUEtHR09SQ0hMUlVJRVFSUE9PT0pORkZKUUBMRktNSlZWRkpJR0BGSE5RSEpIQEUzPkFHRUZGQERCPUBBPD1ARDo+Oz9IRj4yNjMrNDtBQTtAPkNMQUJLSERNTkZBPEI7Oj06OoA9NDc3PElGQEJKRUZrfnNwSIqMd3dpeHx2cXF9aXZ5cmBtfnRqY290fXl7cnNvenp5dnh3bmlpY3NreGlgXV1iUFtUX2BeXmFgS01bYWFPUVNQZXFnXW5obWY2Ozo6RHOFbmp2eT98YWl3b0ZHPkJOQnJsPEE5Q0tMQkRLWFdNQCVGPz1hNTZBPl1gO0NpOUlGSm5+RH1lWV1BRUZPQzQ/QkNSOjIwAXqIe4x6hHsBeoV7CHp7ent6e3p6rnuDeop7A3p7e596Bnt6e3t6e9B6g3uWeoJ7hXoBe4V6h3sJent6enp7e3t6hnsGenp6e3t8k3sEent7epZ7AXqUewF6jHsEent6eod7hnoJe3t6enp7e3t6hnsHent7ent7e4V6hHsMenp7enp7enp6e3p6hXuYegN7enqFewN6e3ueegV7e3p6eoV7Anp7jnoBe4R6B3t6ent6e3uGegF7sXoBe456h3uHeoh7Bnp6ent7eo97AXqIe5p6BHt7e3qJewZ6ent6e3qHe4N6hHsGent6ent6lXsBev97g3uEegF7yXqFe4Z6AXuFeoZ7gnqQewF6hHsFenp7e3qEewN6enuEeo17AgIEAICLmKyvq5+YiZKRluqE/PX//ea20ICGhJ6RjZSjpY6RiYqFiIKQpJSvprGbloyMhY2fqKauo56omZiftZ6kiYKH/PiEgoqHoJiwoaOvtpmeioCA+Ib75tXX187uhpCCgIqCiYGB+/Dx7+nt9uHt3c3x6PP16O3f1eTfxcPw3tvw74DozNnS3O7g7u3u4PHn6eXb6+js9ure5+Hm29jT6fb56/Pr8NXb5d6A7/Xu7vDy9d7K3tLOzerg5fHy99/28/v/hYuH8NvY0L3KzM3gzcbZ5uL96v7x6ezh8O3i8OTX4d7bzLa2u7q9xNPV0s3P+o2ZmZmJjJmV9PWB1ueBkI2knIC4r6fAr6qllp+Qj5eatcGuo5+dl46ar6a1nK7JuvKqmq6qrq2gpZioqKWsuqmiloyLkZackqaZiZKUoairn6qqmpWQi4SKiYz6hP2LjZubnJ6dpaqquKKilZSdkJGMlZuYlZ2Ll5KUhIPo8vuE+dTb+e6Di4CC//KGjIKHko+HkYCFhP795/H/9YKB9O7k9fra6/qB8fH39IeNjJCMg4H25PT9i/aE//n/h4ePi4P78Ord4rvB4NfF49TU94GE/Pfz//Tzgf748t+F7u/4gPj5+/SFg5KL/fL87uLo0dfE2NHn0ujd69bg5+j27vyOjYH37PiHhYaIg4OIhPXy+vHv7IDygPOMgPbz+/GBhfn15ujn+oOL8tna39Hc1ub52Nnh1+Tw4ufg283WxMbF2762sKuls7nKyM7W18fY1tXVztXXwr/Au9K+2MbMx9TSz9/U6N/I6YL269nT0uHy9/bp84CJioX87YD/+4qMlJScm4eA+fr9koX8l5eZm5yal4+SioCTmqahmYSIhIqHif6H7ery9vX86eD4/Pjw9O/3+t7r8uv++/T46/rpgvP+iIuCjpePiY6HgeuMlYyI/uyEhYX6+//z/IGDgIeJ+4GGipORlaGfl4D/jYSChPyBiY6MjI+HjoeHloqSk4+Wj5qUlZaTmpmSlJKShpGfopyfpaCktYCttLK0raS1rLSvsaqppZ20k5yiopyfnKKepKenraavppqiq6qdl4iZjZiQjJWUkpibnZKWmpqdnZKWm5yjnJeYm7Ggl5OTiI+Ukp2fpKifnbauopeLkY+Si4qKkImGjZCmlZWKiYWEiIeH9PuA9/yFiuLr5ujy5urq8+Lc2NzV04Da1dXI09HF1+Hm4vTx7Onaz9XZwb7Rzs/W3Nbi2+HNv8LFu7LN1+Dg8ujpzdTNvb3A2PT18urq8IXv6frw2NDX2+P9hf2UiZONhO3j7OH4hP+FiIeEiYuK+5SMg4f3gpmJhYmPnono+PqPmJaEiYKBhoyOppONj52Ni5CYnZ+QkIBaZHR5d21qZW9uc6hdraq3uauGn2NkX3FeXF5pbFxiXl9fYFljcF92bnhjX1ZVUlxsd3R1bWVsZGBqg211XFdcpqBYWmFcdmt6bGt3f2drWFFWq2CypZ+lppy2Z25eWFxSVlJSn5+ioqSjrJ+pnJGto6alm6GUlKKbiYehmpelpICrlqasuMGur6Whk6OhoZ6Tl5GQmJGPl5KakI+HoKipna6mp42PjoROk6GepqioqI6CmZaalq6imZycn5Clrbe/ZGxnsaakpp+qqKeumpKiraS2p7WspKaXnpd/h3hzfYeMiHt9goCEjJabnJaYuGZramNbW2ZilpFPeIZPYlxxZoB1bGZ3amxoX2pcV11edHxwa2doZFhje3GAaHSHfpl0Z3l2eHNpa2N0d3p8gHJoYmBcZm10bn1zY2llbW50a3V4bGhiXFVUVVqfWa5hZHFxbnBtbnN0f250Z2NrXmJdaHBxbnViamRjVleVprVgt5WUrqNXXFZasKRcZFVTWlVKVoBNT5qah42alk9Ql42GlZh+jZhTnJiYi0tPTk5NSkqTho+bVYlNkpCaU1VZVU2Rj5GSmnuEnJKDnI2JmE1Pj42Wo6ClW66onYVXjouQUJWaoJhTVGJdn5qjlpSejZeNnpmtlaOZoY+YnqCzp7doaFmpoqdgXFlfWFVdWJ6epJqbmICcU5xdUZeXnp1WWKyomZmZp1hfnIaLkYOVkJ2qj42RiJCcjpGPjHyGd3Z+noaEh4F7h4GLiomPlIeVmpqak5uWgH1/f46Dmo2al52akp+UnZCAnVipoo2MkJ6ts6yVlE5YWFepol68sF9eYmJnZVdSorGzaWCoYmBfXmFkZWZpZIBtcnt3cmBiXWNiZ8NoubrBu7u5opGepailqquyu6azurDBubK3qrq2Za6tYl9ZaHNtampjXJtdaWBerZ5bXFqmpa6hr11gX2dpv2NmZmxobXl4cV+6aWRfXrJXW19hX2ZgZ2ZndWtva2dnXWZjZGRgamlhZ2RiWmBqbGdpb21vgIB3eHR2dG5/dndtamNkZGJ6XmVra2JoZGZqbXFzeHF3bmRqdHVtbGFyaXFpaGxnYmFkY1ReYWNnbGRkYmNiWlVZXXFqZ2drYmhsbHRydHRqanx2cGpjaWpsZGJiZmBfam2BcXFoZ2VlZmVntrdgsbVaYp+jqq6vn6WprqGkqq6spICkoqOaoqCRmZqXj5yeoKSdn6aumJamnZqbn5ihnKqfm6ajm4yZnqWcq6eumKKllo6Xqbavp4+IkFGMhZiWh4aar73ScdFyanFtab+xtp+vWZlQU1RVXWBepGdgWV6iWm5eXWRre2uwtbRmbW9hZmFiY2RofWpnaXViYWBlaHBdXoA1PkpKRTw4MzxAR145aWh1dmhFXEBBPVA+P0RNUD9CP0FAQDxHVEVYUlpIRkA+PEROV1dXUkxSR0JHXElQPz1CeHdFREc/T0RPQT1KVEJMQDxDiVKYjYKCgW2GTVVHREpBSEREfnd4dnd9jX+Lf2+NhImKf4R5doJ9amZ7c2d1coBuXGhpd4ZxeXp+coV/g391eHJxeHNrc3B2bm5ofoqLfoyDiHF3eHFFf4uIjo2RkXdthHh5dIZ7dnl4f25/gH5/QklHeXN2fHSDiIqTgHiJmJClkpiIfHxwfXpwfnFqcXV4cWNiZV9fZWxvbWlmg0dJRj82OURFZ2U4UFQwPDVGPoBLRUNTR0hEPUg7OD49T1NGQT5BOzA3RjpCLjlHQFc7NEdERkE2OTA/RUVHTkVEQEA+Q0ZHPktENjs7QEBDNz5AODY1NjQ5PUJoOWQ3NT48PDo7Oj8/Sjw/ODY8MzUvNjw9O0M3QUBDOjxhcoBGf1xYb2Q3PTpAhHpGSj49Q0A4Q4A9QICFdXuGgERDfHRufohvfIBCdnV4ckRKTEtJRUJ9a3d/R20+cnF7REVKRkKAfoCBh2pyjYR3inx0gD9AbmxzhX2AR4F7cV1BZWNnOGZnZ2A3OEhFd3WFeniAa2xebWh4Z3pxfGdrbm59boBNTkB+eoZQTktMRD9HQnR2fHNxbIBuPG1GPnBxeXhESYmGcG5te0RLeGlweGx9d4SVcmxwaXB8c3l4enF7bm91lHp1dGxiaWVsZGVpa1xsamhqZ3FxXl5fX3FidmVxb3h0bXltd2tWcEF6d2JhYnF+hYBsbTlBQTxuaD58cT89RERKTEA+eoWETUNrQUJERkdKR0JBOYA/QUlFQTI4NT08QHM+ZmNscHF3altsdnhxdXh7gGZtb2Z2d292a3doOldTMjAqNj46ODg1MUgzPjk4ZF08Pj1saXBmcTw9OT9AazY6PUA8QEtIQDBgPDk4OW47QUVHRktHS0VETUVIREFFQElISUhGUVJNV1JRSU5VVU5OT0pIVoBQUU1PSUVUT1NNTUZFQz9SOEJLT0pSTk9NSUhHSkRNRz9GUFNIRjtKQklDQUdFQEFDQjc+QUJDRz09PT9COTI1NklBPTpANjs/PUI9QD84N0lFQT44Pj9BODY0ODMxOj5RRkY9PTk4OTw+a3U/eHxASGdtcnN3bHJzfnB2fYB+c4Bzc3RsenxueYN6coGBf353dXx9aGRwZmFgX1hhWmhbVF5fWE1bXGJXZWFpVGRsX1pbbnx1cWNib0V2b4V8ZFtjaG97QXNEPEVBPW9ocWN8QnE9Pz8/QkNDdFBJQkJnO0w8PEFHV0lpbWxDSUo8QT47Ojo9UT47PUo5Nzg8Qkk5O4t7Anp7h3qse4J6kHsCenuHeol7w3oBe5h6g3ureoh7BXp6e3p6zXsDent6nnsEenp6e4V6hHuCeop7hnqCe4h6AXuEeod7hHoGe3p7enp6hXuOeoJ7hnoBe4R6BXt6enp7hHqEe5d6Bnt7e3p6eoh7h3oEe3p7e4R6gnuGeoJ7wHoBe4t6hHsFenp7enqIewZ6enp7e3qVewJ6e5t6A3t6eop7AXqEewV6ent7e4V6hXsBeop7AXqEewF6/3uRewd6ent6ent7x3oBe4p6Ant6hXuFegJ7eod7AXqEewF6iHuDepd7AgIEAICGjpmUmZSanZGHj4D+h5CXoJruzOft8u3+h46ToZyHloSPjIOJiYSNk6esmpP68e2KkZOalo6SkJOfp5mQlpGBhYKJionxlJ+xna2gnI2PiIL85+7m59LSz8vT9YH62+Dj9++B9u3u6OTt9O3b3u6AgIHpxNfewdvy0cfZz9jV5YDb1trUx6bZ2NjZ5+nf1eDW493k5vT5g/Xt5NfT6OPr4+DSzMPF1NTj6Onp6e3v2fPl6t3L3Ork4viA69bz7+Dv8+3+gOb57Onj4+bbxNHR5Nvn6ezf5OXS2Obm5ODLv9je27a6ycvRxL7Ru+zv64WVlZPxho2MjaGirbaKiIuqroC1qo2kj/eI8IOmuMu/uae0ppiymKC1pp+nm5CUmrejpZCPlI2Znayhk4+L7YyUq5yghI6ViJGXjpaZmq6yxp+cmJmUloP56fXp8vmAhYeHgoTsgYyMjoeVjPyMg4+El4+QhIOGgoWIhoqEhoKD+faB3+Xo6fOA9/jwh/yYjoyUl4CNhomSj4CLjo3w3M7g1O7o0OPt5+f3/db0ipaNhYWAgoGIjPj4gPKEgueGgoGEgvzv28qzwbS5yMra7vH0gIP98fXr7/KA//2Gg4qG/IP87/Pz6uvz5Pns7Nnv4eTk4dLg0dbt++rd+YD0+5KQ9v337t7T5vn3+P3whoeA+4GCgID4/e2E/oeGh5qF7f/t0uv03uL98tzo6931/v/y+/Ly8IuI+vDt6dnUyczCvtDCsb/Atca/w9zb2cvS08vPycrPuca/vsfVycC/vM3W3+bx6ez9/oP62t7f3+vu3vKDgIuemIn3gfuGgIaHhpWMjouKj5CBrJWFl6GYpIydlZeXjxecmaOOgIL8//v24u7q0cfd4fP28YX08YTygPjl9O37gfj7hvbw6ff+/uT9jPmA/oSHiI7+jIuJgYqOgv6HhYL8g47xgIH85v2EjJKLh4eblIyD+oGBhYb9gYeMiouQi42dkpCSh4iWlJORlZiQi4yNiYmEjYiJkZygnpmWkqWqsrW9tLWqvLCfpaOmqqmom52aopeUmJ2km62ygKuzsqmrpqCfoZSR9YiXk4uVlZWYmqSTlZmcmJKWmp2juq60oJqFiP+RmoeKhoyVlZygopaXpp6fl6CloaagzLWkk4iVhqiho4mJi4GDipOFgYeDjZaH/oLl7OXZ8PH66uzV1NHS4uXS4NfBz9bc8vTs3OLl08i3v8DF39LJ0dTSZeDYxce3vMK6w8DQ1NLY3Obe59DPytDz8uqB7Nzd3tjx6czXxcTHxbng5feF6uzg0+rT1PLn3u/2gvSA+4v8gvuB/4aFjI3+hoGZ/vn76oeQhfmR85CRgfiTg4CPiZOWoY+HhoeAgFtfZWFoZG53bGVsW7NcZG1zcrKZuLe8sLBbXFxnaFdnWmRiWVhXUFhcb3JjX5uXm15kZGdnXl5cX252aGFoYlZcVlxbWpxsdnhjcG5uXl9aU6SToJ2llJKVkZ3BZ8WmpqOyqlqrp6Sfn6i2sKGmsWJiYKqPmZ6LnbGTh4mBh3+KgI2NmJ2ad6KYiYWUl46KmZCVjIuKmKZapqCWjo2gnKelrquikpGTjZqcn56fpaiSrKGrqpqmsqGcqFeXh6KinqmysL1hrLy3ta66vbGWm5mnnaaoqp2iqZeYnJKEf3Bsh4+NcHKBf4Z+fI9+rLSqYmxqY5pYW1pZZmdyeVRWWnh8gH1wWWVUlFSSU3J+in15a3tzZnpkaXx1b31uYWJnfG9vWVpiWmtscWxhYVyaYWV5bm5XZGpibHBrcW9sd3SCZWdoaWVmWJuMoJqltFpgZGJcYJxWYWJgWmldpV9VYFZqaGxjZmVgYWBbXlZdWluzs12XnpyToFqtqaJdomVYU1lagFROVF9bTldeX5mMfY6Bk4x2hpCXmaSjd5FVWlJLSklLSlVZkYtHhUxKg1BJR0ZGiI2LhXiMf4aPiYaLhoZFR5aRmpqem0+Zjk9NUVGVTpSOkouMkpiNpZmal7Clq7Cqn6aSkKGqnJSsWK+za2qurqmklIaXqbOxtKZeW1KgT1JSgJ6jl1anXFpbbFiduq2UqamKj6WbhI+QhKKqq52jmJOQWVaUi4yMgIF7fnt/i4WAjYuEjIKCk4+Vio6WkJWRjYt1gn6IkqSWkY6IkJSan6ial6ypV6OEg4uPm5yTnVZNWWZjV5xTq19XW1lXY1taWlteYVp5Z1VeZlxhT19bY2pegGplbF5UWLG2t7yuxsa1rbuwtKqcVJeWn6CmrreruLC7YLO0Y7GppLS6tJuxZKpZsF5cYGKsX1xbVl9iWatdWlekWWKeWFyypLthaG9qZmd+dm9mv2FkaWa5W1tfWVxeXF1tZmVmYmRsaGJdYGRjYmZoYV1cY2BiYm5xbmppZ3R0gHZ1eXB2coaAb3Nwb3Z2d29uanFkX2VnbGh2fXd9eXNzbmtrcWlnqmFuaV5kYV1cX2pcYGpubGZqbGpqdWpqW19SXLVqbmFkYmdxcHNyc2Znc2trZmtrZmhkhXVtYl9xaYZ/f2dmamFhaHFiXmJaYmdbslqqsaqYpqKnn6OcnJ6jgKyupK+lkpqbmqagl5GYo5mdjZacp7uwoqalorGwoqiepqCVoJWbopmVnaabq52el5u1qJhXlIOIh4OWjHmLiJOiqqTFvr5mtLmuqsCpmauWgY6WUpJSpGKvWq5arlhZYF+vYmJ+zMPBsGdsY7Ztq2doWatsXFtqZGxxd2JcXVlVgDQ4PDU3MzpCOzlDN2w5P0hNS2ZVb3B8bnE+Q0RPTzxMQEhFPkBCPEJFV1hNSnRyc0ZLSUpKQkE+PUhSR0VQT0JFQEdGQ25MT005QkRIPUNCQIR5iIiVgoCAb3WPS4xxdXeKgkiEfntzdX+NiXl/iU1OToVreYNug5p9cHJnZ19ngGBeZmpmSXJubG+Ch4B+iX+BdXJwfoVKgnt1bGh7eYOBh4B6b3F3c3+Af3+Bho14jYWQhHaBi3x0g0d8aIKCdnl4cIBDc4WCg4KNkop2gYGPho6Pjn9/f3Bze313dGZhc3Z2XF1pZWZaUmFQd3drPkdDPE4xNTY3QkFJTSsqK0FBgEQ+MT8zWTZXNE1TXVBMP0lDNkMzNUQ9NjszKiwzSj5BMTI4Lzs8Q0A5OjZSPEFUTE44RUY6P0A6PTw5Q0FHMzIzNzc9NWNbbGNobzU4OjgyM1AwOzo4M0A4XDcsNis6Nzs1ODo5PT48QDlAPD50dT5YYWFca0B+fHZGdUtBPUNFgEA8QkxJPUZLTX1wZ3tvhYFpc3h4eoeHYX9MUkxGRUJDQUdJcGs4ZTs5YkI9PT8/eoB8emuBeH+IgX5+dXE5O3t2fHh7dDtxajw6QEB0PXFpa2Zma3ZtgXh5cod6d3p2bXRobYKNfm+CQHp3Tk58gIJ+cWh5iYmEgnRGRDx1PD08gG1wYDxyP0BBU0V5koZsf39kbYN7anV5b4qTk4iMfXd3S0d9dXqAdnZydXJ2h312gXltdGRdbWhsYGZqYGZiZGVRXlpfaXhsZGRkcXd+gYV4d4WCRH5iY2lqc3JrdUE5Qk9LPWY4ckE5Ozk5RkBDRUdJS0BbSjpETUhQPExEREM3gENASjsyNm11dXZkdHBiVGZoc3BtP2tqcW9zeHpqcmpyOWhwQXNtZ3FxZ0dXNU4rVDAwMzZTMjAxLjk/N24/PjppO0NjODxvW244PEI8NzhMSEI4Zjg6QEJ0PkRKSEhIQkJPRkVFQEJLSERCREtIRUpOSEZHTUdGR1FTT0lEQUtOgFFQVE1PSllRQkVDREhMTEFGRFBIR05RUkpRUEpPTUlJRkRESkJBZD5MSD9FQz48PkU4O0NHRT49PDo+TUdJPDwsNGVFSDw/Oz9DPjs9PDE0QDo7OEFGQ0M+VUc/NTBBO1ZTVD8+QjY1PUQ4Nz05Qkk+dz5xendsfnuCdXhqam1sgHd/dIF9bHZ7f4uGfXV7gHRzZGlscH5vYF5bW2ViWl9WYGNaY1hZXFJSWWZec2tsZWN9dWdAbWJqc3KDfmVwZWdnZ1hycHM/aWxnZ35sZH5tXm12QXJBeUl+RIBCej08QUBtPztTeXJsXj9EPWpIZ0RBMltFNzRCPUVHTT44ODYyjHsBeoV7h3qUe4N6lXsBeot7i3oBe4Z6AXuLeoN7pHoBe6J6AXuJegF7qnqEewF6knsDent6o3sBepl7hnqGewF6h3sBepN7A3p6e4V6Bnt6enp7eo57kHqKewd6ent6e3t6hXuOeoJ7hnoDe3p6hHsCenuaegV7enp7e4x6DHt7e3p7e3t6enp7eoV7lnqCe7F6AXuJeoZ7A3p7eqB7jnoBe4t6BHt6enuIegR7ent6hHsBeod7DXp7e3t6e3t6e3t6enqKewF6hHsBes17AXqbewF6rnsCenvBegF7kXoBe4x6Cnt6e3p7ent6e3qEewR6e3t7hHoKe3t7ent6e3t7eo17AgIEAICRm6GRlrCxmYeF5YCRkorq4fD+2MvY4vjzhZCWlPuIiIGIiIXngIOBg4iRg42Eg4KQj4iLl6Cklpaio6eYiI+KkKCMhIuCkJ+dk6+mi4aGi4D644CDhv/j0sfi7u/8+PHwgYD8/fr66vLl8/D01Nnj1N3WztDGvdDT2Nnu7+LOxoDQ0dHNyrP539Ha0c7JxsO8ytnd5efv3cnBxsnG2crMwMy/wcTY0MDO1ubd2N7q4OLj39XP2OHo6/eA6oDt++bg6u/g0tzi2tXR5Ovj3+Lh4+Dl5eTTz97i5t7a0dvb3eLLstbZ24WPj4mLkfrr2u3/mameubSojYCB+IOC+ZKSi4CtmbOnsqWmq6Oiko6SypyjkpqaoKf7yLKgury/pay8srioqramr66gkZqRj/Xr4vuepv3w9N+C5YKdnKqourqjtpqTj4yM4viQm52mrqiYiIL2g4SLnpWjlICCgImWtaSUi5yWmsmJlJCbl4CB++/v7IKUi+7rie7i+vfq/PaF9YCAkIisoI2LkZCIgPT7+uDa4M/I0umAhN//ifmChIKAioyQm4iCg4CI6f2FhP6G9ePwgInb18nT0d/8//n394Dy7u3w6eDg5+/w7fP18/Dy9IDv7/nx5Nv3/4H08/rx7/Ln8939+fXq9e7og4jg4Oje6ern8+f69+nr/vCD/IP4+4CDiJKFhZKkmY6I7O3k6+zc2+rh7OTz6en9g/X8/fP5gfqEg/b99PD68urNysjP6eDS1cvfvcbE48rAuMDHy9PSzNLOxNfLwMnGvdHa3eb1gfD1hu6B7/3r583j1YGEiYuRn4uNi5eRgoP8gfqI/4OGj5WLloKCjIWVoo+Uj5f1jYCI9IL994f7he/r49zj2eDt7/T//oz48++DgPjy2tuGg4b/+4+FiY/1gIOJ7e/S4N3s8vPwgI6C/YqLifDz+Pn+//uGloaF/ISQlZqIkZCem5GD+Pzx8ez0/v7p84SGi5SUk5ecnJCTkp2ilpOUkYuPiISBh4iJi5CUmZahr6usqICqxqqzrLissqihoK+lqaacn5+cm5WVm6GYoKevnqOxsqyfo6KepImLmZiaoY+Uk52bjo+NhIePmqmUjZ2mnZCckY6UmIqEmJSVmamdu6KUioKNj5eblpaenprxnJObmpmPip2QlIWGiqCQj4qIkYSNhIP33uHi5uTig+7vzufv6IDc5dbc1dPOzd7Y4eHo5+/YyMzWwNfW0crXz8nZ6NHbsby1xMK7ztHM3OHm6M/a1c7S1d7k2d7c5Mu7xcTNvrK2vsjZ7OLz7c7Z0MLPzd/IzNvr6fPi7fv1hPrt7eT7h4Pv7+3yh4iNgvyLk4XqgYSPpquYq5aA542KmIuHj5OjhoBlbG1hY3l+c2VjoVhnZWCkorfKrKCorrWnWV1fXp5bX11mY16VUVBMTVNfVV9XVVRfXVhZZmx0Z2dzcXJnXWJiZ29cTllRYXJpX3NoWVdVWVCfjlZZW7CWioGdp7C9uK+nXFmvrayqm5+SpaOvmqCupqqopqGSiZCRlZKhpJmEeoCMlJednIC6mYGIgoOEh4p/iY+Ki46bjoaEipWbrKChmKWgnJmqm4aTkqGSj5qjmaGjn5+bn6ernp9Tklems6WXnKOWiI6UlZaUq66mpKCjoZukoKOVkpykq6Wgk5eamZyNeZSNjFNdX1tfaLSpm6mta3VqfXdqUklOmlZXqWdmXIB2YXZnbGNkbmpqW1pcgmZtX2RjaGyulYd8hIqIcHKBdHtwcXpvdHRoV15ZWZWOlKtwdaKWn5VdpGJ3dHt0fXtmeWFdW1hVd4tYZWZudnJmWlefV1hfa2Z2aFRVUFdlgHJnZXlzc4hfaV9oZ1RVrqegmlZoX6OlZqicsKqZo5dTk4BNXlp6b15ZXmJbVqmyp4iAgXNwfZFPT3SNUolMT01LVFVbZVROTExQfY1MTZpVnIyXU1uLkIOHhJKZj4l/fUiPlZ+moJGLiIqHgoaIgoGHkU2MkZ+UhX+ZnVWoqLCzrKqcpIyppKWbpqGmXmamo6uiqaOZopyvraGerJ1ZplWgnoBRVV9SVWNyaWBbmaOhpaWQiJiIj4yajIuiU5SWl4qMToxMTI2Yj46cm5J+goOLqqOXnJKbhIqLrpuTjZSZmJ2Vh46NkKOflaGfk5+jn6WqWJylVpRVl6GcmYSXj1hYWFtgbFxcXGxmV1mmU55XnlRcZGthZVhQVExZYE1YWmuwbIBnp1ipol6sXaenp6aytrXBuK6uo1iPjotUVq+toJ1jXl6ro2JZXWSnWl5krbCerKu3tbCpWWNXp15fXaKlsLO0srJfal1erl5pbXBgZ2VzdGhdtr64ucHGycSprFlaWWNeXmNlY15fXGlqXVxlZ2VsZ2JeZWFfYGRnamRqe3Z7d4Bzhmlwa3hze3dzcXx2eHdubG1pZV5eZWpjbXJ4bGt0dm5kcXR0fmRoc3BvcmFhX2ZmXF1fWWBkbnpmWmZtY1lpX15naWFcbmtsb31ubXJpZGBmZWtnYmBlZGCcZV5obnFoYXRpamFjZHlsaGRiZ1xjXGK1q7CwrqOcXJygjKOxsoCpraqtqaminaiYl46UlaSQjZGcjqaloZuhm5ajuKq3l6OVnJiJlJyQnaOkooydmYuMiY6Oho2Pooh9i4OHfHyElqatu6yzr5mpqZ+tq6qKhoeTkp+SnayqX7Olp5upXVqgqqy4aWtxY7ZmbWGuYWJsenlneWtZoWdfb2NdZWtzXYA7QEAzMkJIPjY4WzhGR0FjX2x9Y1dib3dqPURIR3BCREFJRkRqQEA8PEFLQ05HRkNKR0FBTFBSQ0FNT1NNRExKTVdJP0dASlJHOktDODk9Rj+BdElNU52He2mAgH2DfnNxREKJiomEdH1vgoGLcnaFe4N/fn92cHl6gH6MjXljWIBiZ2tvcViUeGx3dnx7gIB0eH14en2GeWlmbXR2h3x8doF4dniMgG56foqAfYOQhIiMhX16fn5/dnpBbkSAjHtpa25kXWRtcXJwhIWBgH+DhYOMiIV0cHZ+hH58cHV5e35tWHRubEFKSUNES350ZGtqP0M6SUZCMCgsVS4sUTQzLIBDNEhARD9AREJBNDQzTDk/Mzc2NTRfTEAzN0pLOD1IQEk+PkI4QkhAMzo3OVhSU2xQVm5fYlM5VjRGPkI3QD8vQjIyNDg6R109RURKTUlANTNeNjU7RkFPQzExKjE6S0M6OEpIQ1A7RUJLSzk6dm5mXTdDPmRpSHNqf3prdm1AcIA7SkZgVkhFSE9JQ4eRjnx5f3JrcoJGRmWDToBIS0VCSkdMUkA5ODg+XXNAP31GgHKASFN5fXR4doSQiH9zbT95fISFfm5taW9paG5wamdrcTxrcHtzZl53fUN9enx9eXhremSDfX5xd3FzREpxcHlyfnx5hH2LhHV0gXBCdz90cIA4PUQ4O0dXUU1KeIGAg4R1cYh4f3iBdXSMSoCGhXl5Q3dBQXeEhYqYl411dnZ5mpOBgnV5XGJhgW1lXF9kZGtmXGRiYXNrYWtpY3R6d32CRXd7QGk/bnlzcFxqYT48Oz1BTj4/QE9LPDxtOm1CdkBHS1FFRzo1PDhHUkFIRU1oRoBDYjhraUJ3RnRwaWJobWh2eHV7fEdsa2lBQ4N+bGREPTpiXz83PUNhNTY4VVZDUlRiZGFaMjswWjg9PGVueXp7d3U/Sjs9azlCREY2PjpIRz4zYWtnZ214foJzfURFREpFQkZJSUJFQk5OQT5GRkNLRkNASERDQkVHSEFIU09RTYBLW0ZPSVRKTUZAP0ZFTE5FSUxNTkhKUVRLTU5QQEBJSUQ7RUlKUjxCTUxLTT4/PUNCOTo6NDg6P0U1LDxFPDNANTM+QDk2SUVEQ0s7PT40MCszNT9CPjw/PjdiNzE6Q0ZBO0xERjo7PFFCPz09RDtDPUF2bHJzeXFsR3FzW3GAfIByfHh9fYB6d4Z3eXJ6eoVxbG92ZXhwal9hV1FbbGFrUF5XYV9TWl1RXmFlaVpvbmJiYmpqZHB1hW5jbWtyYVpcYWpseXB5dGBub2p3dntiYmd2eYN0e3p6SYx/gHN+RT9mampuQUFENmA9RTxeOTtFUFBAUUY0V0E7Rzo1O0BINYp7AXqEe4p6hHsBeoZ7AXqsewV6ent7e4t6gnvWegN7enupeoZ7hXqJewR6e3t6r3uEeoJ7hHoCe3qOe4J6iXsBept7hHoGe3t7enp7h3oCe3qLe4p6Bnt7enp7eo17C3p6e3t6e3p6ent7i3oBe5F6AXuIegF7kHqCe496BXt6e3p6inuPegF7hXoEe3p7e6x6Bnt6ent6e4d6jXsFent6e3qQewp6e3t6e3p6e3p7jHoGe3p6ent7hHoFe3t7enqEewR6e3t7iXoHe3t7ent7e4d6hHsBeot7inr/e5d7h3oBe956AXuFeoJ7hHqEewV6e3t7eol7AXqJewICBACA/OyGjoieg+H+g+vT3Nv1+I2P7vXy1e/63erx/YT2ioOA4dbp8O7m6vr19N72/f38+4aLiomXhZmgl6SUj4WXnYuUmZ+VioeDhIyTkIGDipCSguqAhYWA/ILk7uTp7PPo5uP27u/w8fPg7evp29Hg3ODZzrm/vLiyxs3F8O7b3d6A1dLHx8nV4Mray8XCt7i6r7/GyNzMx9HKvcPFy9DO1d/jwdbI3dPN0dbb1/Xg+fv26vPn9uPh74WBkPz6i4r22vn3//r23eff3OTd4O309/rl393G1NvOz8bPzs/O093d3dKBkI6qmpLs8IaajObd2ufyg5GZq5ehqIX1kImIlZGArJCNipKdjY2UlYmYmaukpqGNiZSSn9KytqKpvpy5r4+Kj5mcnpiVnJKPkJ+Wjoj+h4LZ9v7zgYyQkaaytLXfmJ+vsbyijYmMlObm8P+EioSEioyMkZudj5CHgPmQi5aQgv6H+eSB7oKSlo3+hP36k4WMg/fe8v6F7/Dw4/n699uA0/+Ag4KHjpKF/OzY0dzSxMLt+uKA9IKBgoH3hoD6+/yLho2WlYiBgoWE9fH0kJKThOny2NHP4IHh/oCB/f+GhPeB4M3P1Of1+Pv45vD27YHz6+Xz/PX7iouWi4qKj4zs8u7+/IL/+oeCif/36eLc5/Tm7/n57uDv+4X5noyQqvmAhISFgfSQnoLs7OHl6O3W5vPo3e7w9ID9i/js5ujm7tnm6fLY6ejl+OjZ2dDM0tfCvMi+tL2ysK/Fysu6ws/CysDFv87Qx9fRztze3ubr/Oz65d/48fOA/Pjygf6Jgo+RnZ2OlJGKjoeak4qehvqA8I2Fg4j//IqEjIWJhIKBi46A7+fw+YGEg4PfzsjO2Nre5YX5/46Lg4iEnoyG9N/hgYaKm5CEgZGFjoDq+eXm0t3f4f6C6o+XjIWA//b+8On39PT/ho2K/YeLk5ClhY6Mj46J+vvz6uPR7vXs6+P48/2GhoWFiI2MjImIj42YlIiJioiGjIeMjZGSpqianJitra6ArbqzsbeyqKqkmpePkJ2WmpKVlZmKlpKUl5iXk5WWr6q0o6Kkn6L+6IyWk42TlJiUlJacioSGj5aRgoSYk4+Ek5qfj42VkP6DnZKsmpidmpGXoKKdoau0n6WqoJ2xlY+YiZGXh4L0+IL+gYqKg4KD/YXu6+zo+d34+4H64/Hm3e6A5/nz6+Xdysvj1Nzh2N3U0c7K0c/WxMe3xcO8vMa509HO4s++xMW+w9jOz9njxtLFyczIzdzk1dzPyb/T6OrszNPc3LzOxda2vNrQyNL14NXn/dr04PPvgYb569LogYaKkYLw/O7+/v6Fh4yXjviGgpeRgZGbj/yFjIeLhvj59f+As59eZWNzXqG5XqKKiYmkq2NkqrCukKiriZSSnVSfYl9foZKZmpSGiZuZnYqcn5qclVVaWV5pWWtuaXRkYVtqcGFhZGdhVldWWF1iX1ZbXGRlVY5RV1lWolSMnZmkrbWuqJ6tp6WsqrWgqqyonJqloa6moJSRjIV1goR3mpyJkZWAlpuVl5icnH+Fdnh8e4aNgo+OhpWFiZibkZKRmZSSmZ+ok6GSpZWRkpCTg52NoKKonqyltaOapFtWXZ6iXmSzkqyrqqOjj5WSlqWhpKytrauamZmKmZ+WmpOamZePmKOgoplZXlpwY1+Rllx3bKGflpidVl9jcF1kbFGJV1RSYF+AdWFiWF9oU1VfXlVmaHhzdXBaVVpcX5qEinZ3gl91dFZSXWZnb2hial5YVmFbV1OdVVF+k6CdWGlranZ8fXqbYmh2eoluVVFVYJCVn6paXVlYXV5fYGdoV1hUTZFbWWZgXMBov6xfpV1qaWeyW62vZltqYrWgsrdiqKSimaysqpCAha1VVE1NT1BNk5GNjJWNd3CTm4JKiExMTkmDSkeMkJBTTlJWWlBOUVNTk5OdY2pkW5ucioWEkViNn0lIgYFHTI9UlY2PjZGViIaFdX2IiFKUjoeVmY6TUFFdWVpeaWahpKGonlKloFlVYrGupp+Yoq+lp7m4saCprFmbZ1ZXbIyAUVRYVaJpdV2koJijqbCboaGViJCVkkqUVpGIhImFjHiAhI1+jo6Noox+gH56iZGCg5SHg46BgIOWjpSHjZiRl4yTkqWjn7Knoq6moaGirZqmlIeclZdQpqekW7lkWmFkZmRWXFlbX1lqZVxsWJpUoGFaWFekl1NLUElNUFNYYWSAn5mcsF9kaWuzpaCmrbWtr2SuqV5WTVJSbGBgsKGkXVxdaFxSU2Nea2O5z8PJwMbEvshfpWRnXVdRpZmrnZuurqu7YGRjsF1ia2d2XGRfYWNhrLGsrKqmxs2/uKu8tLlgXlhaWlpcYV5eY19pZ19hY2FdYGBjZWxreXtpZ2Z3dniAdHlybXJxbXRzcnBranNsbF5hYmVZZ2VpbXBqZGFbbGxybHN4eH68qGdtaF5gX2BbXl5nWFdeZnBnWFZjXFhSY2lwYmJqZ69Ya2N5aWhxa2VqbnJqaW5vZGttZGR3Z2ZxZGlwYlywsly1W2FjW1hZplqmprCyvJ+rpVWejaKal6mAorCwrKmmnp+wpaijmqCYlZ2ZoKivnqOUnp2YmKOXraqjr56Sk5ONkJ+VlpyjjZWLioqDh5GTipeQi4WVpqWlkZuuq4uUkKGKm721pqe4mYiNp5KlkqScWWG0pI+bVVhbYlSjuKvDxcFnaWtzcLxjZHRpXWlxZbFgZ2NnX62yq7SAcF45PDdEMU1qO2xgY2V6fUlHbHNwVXJ4VWNpcz5tRUJDbWFxeXpxdIiKinmOkY2LgEdJQ0VLOEVHQ1FHSENRVUVJT1FLQkA5ODxAPzg/RlFTSHlHTlBLkEtzgXd5eoF4eXeHhYiLiIx1f4SBcm94dH97c2pucHBlc3dsi4lwc3KAbG1obG95fmd2bHJ2c4CDdX14coFzc4N8dHp4fH17f4mOcn50in56fYGEe5WBlJSUh5CDkn9zeUI+SXd5TFCIZn13fHp6a3Z0c31zdX+Gh4d5e31ren91eW91eHZvdHx2d2tCRkFVSURbZEJYTmpeVFBKKzI2RTg+RC1EMCsnMzGARTg5NT1FMTQ7OTA8OkVAQD0uKSspM1FARzpDTjZKSC4qMDc3PDk5Qzo0Mz02Mi9fOTZMYGhkNz47OUJFQz5UJy88Q1JGOjlATGZmaXI8QDw7QEA/P0ZHOjw5MlU6Nj87NndDemY7XTdERURzPnZ2STlAO21hd4JHdnRzZ3l+gm+AZoxERD4/QkNBenRwcX19bWyNk3tFfEdIS0d/SEN9gn9IRERHSD04Oj0/cHJ+VFRNTICJdnBygE6AkkVDeXVCRX5Je29xcXl+c3NyYWdxb0R3c2tydm1tPj9JQ0RGUU91dneCeT5+dkQ/S4F8d3JteYd6g5OPiHV+hUZ0U0JEVWWAOjs9OWlIU0J0eHV+h415hYuEeIGHg0KHUYR4dnd0eGRtbXlleH6BlYV4eXVweYBxa3lvZWpgWlpsaWpZXGRdY1dfXGxtaHdubXt5eHx+iXaCbmF2bW88fHh1P4JKPkRFSEg6Q0A+QzxKRUBQQHNBe09IRUR3bD88Qz9EREJAQ0SAYV5ldkNGTE94ZVxfZG9nakd7fExFPEA/VEZEeWtsQUA+SDwxMUE6QzhebmNmWWJjYnA1VDxCOTUzcGt8c298eG13PUA8Zjk8REJNNkA7PT47Y2dobmtigoN7eXCEf4REQz8/QUFAQjs8QDtJSEBCRENCR0dHRklHU1NERENRTk+ATlNRT1NQSUpFQT05PEhCRDs/Q0o/T01OTkxGPjo3R0ZLQkVLTFBxZkZNSj9AQUE8PjxFNjM4PkU8LS06NTEpOD5FOz1HRGozRDpLOztCPjU4PUI/REpLP0NCNjVGOTtGPkdMQTppbDlwOUBAOzo7az1lZHFzh26CgEV8aHlvbHyAdIiHgoF/dXaLf4GAfIJ5d3lxcnN0YGNTWllUUlxSZWNhdGNWWFNNUF1TVmNvYG9jY2ZkaXV4bHhzb2N3iYeFbnF+ellpZHNbZ4V9cXiLdWdwi3aJdIJ2Q0qLfml4QUJDRz1ygXWCfnE6PD9KRm4+P01CNEBJP2Y6Qj9COmZpaHSCeoV7A3p6e4Z6gnuKegV7ent7e5B6oXsBeoR7Anp73noHe3t7enp7e6R6hnsFenp7e3uFeoh7AXqaewF8mXsDent7hHqTe4R6jnsBeoV7Bnp7enp7eoR7BHp7enqEe4R6AXuKeod7i3oCe3qEewZ6e3t6enqKe4N6hHuGegt7enp7e3p6e3t6e416AXuHeoh7hXoGe3p6e3t7j3oCe3qEewF6hHsEent7e456A3t6e716Bnt6enp7epF7A3p7eoR7gnqKe4R6hHuIegN7enqIe4N6i3uJegJ7eoV7iXoEe3t7eot7jnrHe4J6nnsBep57BHp6e3qGewJ6e4h6AXvdeoJ7hHqFe4Z6hXsBeoh7AXqFe4R6AgIEAIDS74DFyPaA1+Tz4c7Bu8HD5o+F8vnriP/W+4WhioKJ94SOhPyT+/vr5Pf13OTk1e7+7YKKh+z1gpSSkoL0goSAgoSGiISG/vf69YCEgYWLjY2EgPr3/Pjy4ePp4Ozp8fSF94Dx8O/p7evu+urg1ODd1M7Rxc/IvbjCy83a6tPY4IDltrzKzuHs19/3yce9tLuzr6esrrbA0MC7w73Pwrmvs8i2s7TAys/R2N3i/OTt6Njf3c/i4eeHhv37goH/gPzrgvP78ZCG5dbb0ujt6/rl5uTw6sPPzMLe2NLZwbvHy9DY2dj5hf6RjJ33/eri5fPd8vvq9OCAkaG6qrevlJSlloCShfSFiouQj4SaivH5i4qUjZGd052mnZCsoKKYooaQlKuljIuLgoiVl6Kpr7i5qr+hnIqHxYr6hoOloaGVkJWhi7Gpn4KKmY+CgOfX1Nv6gYKMnYqanoaHiozphYKEiIqF9eSB7v2Kl6uck4mGjIb85+bp5//thYD56erz/uv09IDe/YGB/YaR9IDf5IHn6NvR0Oj9++336vWA8e7ygf/t44aAkYqD+oSE9oadgNLy0bjL2PeA7vSSgd/7ivrz74D58u3e5ejq3+f1+/r8+fHf3trf4+Hv4eX5gYHy9+f06NzY5/yFkIWKk/L3+vD5g/n5gf7v8PPl2eXphIeRjIj/hoCMkYf57uHy/+r33czTy83P0NXp7vH79vr8+t/h1OL1+/vu8fzz7O7T0tbX2Njx2c3X08Szs9HGtdbf79DG08jH29nQz8XO7N3v7fHn4+X4gILr4eDr5N/n6unj9eyDgoKRkvny9PWFjJOkpIyB/4Tk6/ju7eL0jIOS9vvu7eyG+oCG9oiB+OH86uXY09LV1Nni5ff6h4CHiYWDl4+EhfSRrKGjnIiIlIKM6/Ds/vuEh/ri+vD0goOCg4X99vfv6uT2+4GDj42Vm5aSjouUk5qTi42GiOrt5/j5/f/87vHxgIqGiIiNiIX0+Y6NhZKRj4yKhIWA+oT/hZCSmZ2hm6KorYC1sLmpsaaop5almoaJk5iem5iXm6umr8WoqbWWjJOgo66jnKKdnJ77io6hjYuNi4qKnJaIif/7gIGBk5aemKygnZWCkaGZmaGYm4yQk42VmKWtoqGrsJSUnZ2flJCHmI+InJCCh/fygPbu8vPu+PT7/oGA+O706vP39uru7O3p7YDf6uLd6d7X0M3G0tLc3dHU19rIydTEv7C5rrS4zrrAu8LC0dzXys/Nyd3X28zGxsW1vcbM2tXEvcTHxM3S4NfK0tzIvsjT1MGux87AwNPa9OPe5Obrgvr06Obl2u/c3/WA84D/gYGA9/X0+oeGgfuAgYSIj5OD8faG9I2D4ur664CMqFmCh69fnq28rZuPiZCSrWthqaiZXqJ3mVJsWVZfrVxmWqBil5WGhZibiY2Lf5WekVJYV4iTUWFiYVKYWVtaXltcW1lbqqewrltfXV9gXVxVT6CipaOag4eQi56gqq5hrFaeoKGjsrC5yLKgmp+am5min6SZjH5+gnyClIGIk4CggYqdoKmjiIOVd319fY2BfnFycn6No5mSkY+ik42Ji6aelpWcl5qUlJGQpJecmJKcn5uppKBfXrKtWFSmV62YXKSjnmRhp5ebjZ6lp7Ofm5iYlHuIhYCakoySgHyNkZCdopKbUYtYVWicr6SppbOZq7WorJdZY3GFdn94X2FwZIBeUIZLT09TWVNmXKGpZWNsZWRmhmdwbWZ/cXBkalBbYXd5YV5hWFxmY2ltbnZ8cYZwaFhTZ1mnZGaKg31uZmlyXX1yaU1TX1tPT5KMi5q3XlxjcWFrbVVVV1mATUtLTlJWo51fsrtkbXxpZmBbY2CymZ6jobmoX1iokpWbopScmoCHpVNQlE9ZiU6CjFWOk4R0coKRkIOMgo5Qlo+STJKAeE9MXVdQk1BQkVNlVIWjineGjZ5PiY1bToCXVIx/eUaQjpKKlZWUg4iIh4OEgoWBiYqOkYqSg4uZUlWiq6GyrJ+YoqxXYlZfY6KqrKCpWaisXLSrr7SnmZyPUVBWUk6TUIBYXluin5qks5+sn5SSi4qEgIGHhIaNipOenIeQfoicn5uUk6OgmZ2IhIWCgH2UhoWYm5OCgpqLe5edrJGMmJOSpKCZm4+YrqKur66ilpikVVeZj5CenZmotLeyyLxnXlxkY5uVl6BXXWBvcF1Vq1uYnq6ppaCval5qpKSYm59fqoBZnVdWqZm7sLCysrS2sKOlnp+aWE5UV1ZXb2lfXahfdWxrZVVcZ11pr7e4yMlucc+0waqiU09SVFampaqkpqOxsVxbXVplZWJlYl1saWtjXV5XXaCnq7/DxsXFu7++YWJaV1dbWFmnrGNiW2VmZ2FgXV1WrmG3YmtqcGxsZ2pxc4B1cHZqcGdtbmd6cV9gZWdpZGNjZ3VyeYlxdHpgVlZjaXRva3Nta22bWmJ0Y2BfXFpYZV5VWaerXFtUY2drYXFoZWBUY29rbHBobFxdX1tgYm5wbWpvbWFocXFyaWVfb2tkdW1eXqqhWa+st7iws6WmqVlXs7O0qrGuqpygn6KiqICZoJ+cqqiop6ikrKOsqp6coKCRmKScnZSekpedq5qcmpuWoKqllJmPh6GXnY6NjYt6foCBjIuGhIuRk5ucpKCWo66ejZOkqJyMpq2ZlpyVpJOQlJijX7WwsayooLKVlKZUoVu3XWBiu7y+wWhrY79jY2Joa25hs7pksmlemaWupIBVbDpJSmw6WWl8d29oZmxrgVBEcXJoSHtRbT1SQDtCeERMRX9ShIN1dIuQfoWDb4CFdkJFQlpeNUVITD94RkdDQj9AQUJEfHV9dz0/QEdOUFJMSIyNkY2Jd3WAc3lyd3dIgUN9hoWCi4OJnIZzbnRwbm1wbXl2cGtvdnZ6iHBxdYB8WV1uc4F/a3KIb3h4e4h8d2pqa3F7j3x0eXWHfndwd4d5cG93e4B/hIaImYmOhnp/f3WCeXdJRX98P0GGR494R3p7cktHd3F2a3l9fIh1dXh+el5pZWF9cm54ZF1scGxxbmJtOF8/PElreXN0b3ZcZWZZXEcyPENRREg+Kik5MoAyLEgwNzc3OzJCOFpdOzg9NjU2TjM5NjFHQUU7QSw1N0hELS0wLDhAPUBBP0NLQVJCPjU0QjhmPjxWUEk8NTlAL01EPS85SEQ+PWtiXmV9PjpATD1HSzk8QEJYNjIxNTk8c2pEeX1ARlVIRUI/SER3X15hYntwR0KBcnN3fnJ9f4BuiERCekFLckFrc0Z2fnVqanqIh3uHf4lMjoSERYN0bEdDUUhAcz4+a0BSQGSFcmBxdYpFeH9USXaNToJ0bD16dnZsdXd3anF0dXNybW9ma2hvcGpwY2RyP0B1fnSHfnFodH0+Rz5FSnaDhn2JSYWHSId/hYyEeoF5R0RLRT5zP4BESkNvbmZ0hniLfHBzbW9ub3R+e3yBfIaPjnuDb3qKi4N2eIaDgIl1d3d2c219b2l5enRjYntpVW13f2FZY15cbmljZFphem16e391bG54QUJwZ2p0cGlydXNmd2w9NzQ+QF9fZWs+QENPUUA8fkh1e4yGf3J9TkZQgYuAg3hIeIBDb0E+fWiKf3tybmttcGtyd4KASUBCQz88UUtAQG5DWU1KQTA1QTZAW1tYaGk7QXNgcmNdMzM1NzlvbnVwbWZ2czs6PTZBQz1APztHRkhBPD44P2hzdoeNjIuMfn6BQ0U9OjtAPD1ucERAN0NGRkRFRUZAgEZ8QkdESUlMSEhNTYBQTFVOVEpNST5ORTQ4QEJGQkBBQ1BOVGNNUFdBNzpFSFFIREpFR0xkP0dVRUFCPz07REA3OGdsOjcwPUFEOkhBQDwzQ1BKSU1DRTY3OjU5N0FFQkFIRjg7QkA/NjIvQT48T0c7P2xqPXJxc3Ryd2xtbTg2bnB6eIWIhXZ4dnp9g4B2gX54hYKBf4J9hn+LiXp4fXxpbHRlZFdaT1ZaaV1eXF5aZW1oWV5ZVGhhaF5jam1dYmdnbmxiYGx0cnt7g3pwd4N1Z3J/gHJlgIJva29ugnh4gYSKToyFgH+AeYxvaXY5aj17QEBBcmxrbkJEPnhBPz5AREk9bG8/akQ6V2RxbAd6ent6enp7inoJe3t6enp7enp6hXsGent7e3p7jXoFe3t7enqFewF6iXuEeol7jXoDe3p703oQe3t6ent7ent6ent6enp7e556BXt6e3t7jHqNewF6iHuCeql7AXqTe4V6i3sBeoZ7BXp6e3p6iXuHeoJ7inoKe3t6e3t6e3p6e4x6CHt6enp7enp6hXsHent7ent7e4d6DHt6ent7enp7enp6e5l6gnuJeoV7hXoEe3p6e4h6hXsBeoR7zHqCe4x6hXuEeod7Anp7h3qDe4V6Bnt6e3p7e496insBeop7hXqCe4V6hXuIepJ7i3qIe4J6i3sDent6sXsBeo17gnquewN6enuJeoJ74noBe4p6B3t6e3p7e3uEegR7e3t6h3sGenp7ent7hHoCAgQAgPne4e3Y2M3j+O3m1svD0NLn9+fe5vD0+vzvkoqFkPz+/f2ChoPzgYD3+/T/hvzt74GJgouAgvODgYCB/IWDhYiMj5OD95r0goWEgYLv8YqA6snS1tbe5eDkhfLfztfU5fHs7+XV19O9ydzHydPb1MrMv9K/xsq+vMO5y9fR0c/IgM/QwsrR5vDT1NbW0bjEpbWqtLS6wMi/xMDIyM/AuL/CysLOvr7Y2Ojy2NPUgeDn3NTY5eji/ICGgvjm2+KA3Oz06vXw5/WE+un46PDyhIKB/Prv4vjs6Ong0uHTv8PU0sjL1dHj+4T++ID/7+b6iYqRpYn/gOGEhZuVm6CfurOZgK2xsrmlr6OhmYf+8ob2jJSmrZ+UmKCTpameh5Cyn5SYnpOSjPSJlrO7y8O7vbzQnrOjnaito5+HlZ+ZkoX27ueD9fCRj4+PkIuBhIWM8ff+h4uP8ZCH7NHh9t/7g+ODhJiUn5SFkI6MlJWRkI6Bg4+K+u74hP2FgYSAhfno1tzbgNPq7Pf49+buh/bj+tb13OTm7eSA9ebt7Pbg/PT1h4WJipTj+ILn+/fr9/f89uWC9uPmhYCGmZiLl4GO/IDy8ID59/SA3s3Z3uXj6/f88Pry6e/r7/v5iNj57uPo6/Tp4uv5+/GAgoD8/oCC7/+O9oH45+bk17/Z4eWBio+Ii4SBgIL94oDh5Ofj3+XPzs6/u8fE1OXh/PDq6trx5d/V7Or09+36gOrm6ff48+/p8OTezs3Azc/S0PPQzcvGt9HHv8/R09LRz9qA+/Dr6eLg3erm3+fo7uLb397i5unkgpGGjIqH/IKFg/eCjZGGhfqQjvPh5ujv6/6CiYj/gPXo8fjxgIH2lYba5urq7Nre+O3q69jH2oWLiJaGhob2jZmE/4aGjZGOhoKAgIeIhv6C9oT95OXk6fSG+oCB8Pjy/fbq/viCg4eEi4OJho2MpI+Pj4WDhfH15e7ogOLY5vL7/v6HgYaPjY6Dg/X4iPaA/oeGhvT/goyG+/qGh5KbmJuXn6GngKuurKqloqiYlqasp6OVkZqZk46Ukp+moZeLkJacqqmhnZWWmKGpl/6FjoOJhIqQkpCQj4uQiIuLiP/jg/WDkZiLlJuJjZOUnKOloZuSk5yNmJ2aqZqpoZmdr56cnpOPmJuUk6OgloWD8/nq5uL65fyA/YKM++3p9+j2+/7r7evbgNnT3c7Z3NXKv73C0NLJ1tjr8uHi0cDRxrjGt77AwsLE19TUxsjIx9bL5d7JtLS0y9PZ0dPW3c/Q0cjNxb3o3t3i5dXY2tPRyc/Bx8HR3ejq4djX2uP94+LXx760tcHP5+v2/IWEhIb+/IGEgv7wjoTqgP7eiYGA+oWAh4He3e7/gLacm6SQkIKZsqmjm5CGkZaqta6qqbGvq6eZZV5bZa2xr7BYWFaZVVOoqaSzXaeXnFNYTlVMTJVWVlhaqlpZXGFlYWJSnGmaVVVXWGCytWRannyFhImPl5SWXamYjJmXqbStn5OBfX53h5uRkJalnJGdlayio6KQg4F1foWAfoJ+gIuNho+Wn514bneAh3qNdYF1fXh8hI+QmJSZlZuIgYaFkZOej4qen6aokYuOW5+gnpmfq7CmsVZbVqWalJ1YipOcmKCopbBhsp+rm52dXl5gvLGgkqCTkpSNi5iEb3SFgn+FiX1/jUyMkU6jmpusYGBkemKzW5hcXG9na29pfnhjgHBwbnBibmdqZ1ikmlafX2JydmleYWlfdHlwW2J9bmRocWZlYaBebYF/hX95fn2SbHx0cnt9cWtbZ3d2b2KupZlXm5JaWFdVWVROUlValp6pW2BmoGdfm3mHl4CWTntNTmZncGpiZWRkaWVmZmVaX2ZcppuhWKFSUVJMVZWGe4OBgH6Yl6ShnY6RV5WEm3qWgISFhndDiXuDhZSCpJmTUk1SVV+GllGInpyQlpKWlotWqpaVWlRUYF5SWElTiUaCgUqOk5tXkYOLioqDg4eKfYyPkJqWm5yaVnqZlZKbo7Oroqm1rpxUUVOgoFFUk6ZgnFSmm56koIyhpZZUVFNOT0xMgFGlklaVl5ifnKOam5eKhIV4g4WAnouFin+Wj4yDlpGcnJefVpqVl6ihnZqUmJaNgYZ9iYyRj6yPi4mEe5SLiZqZmJaTjZlfsqqmnZeQj5uYkZucoZucn6OrrKuoXmdcX1lXmU1PUZdRXWNZWaBiYqyeo6OlpLRdZGKzWaeeoqOagFWiZVuTpa+1uautxr7FxLOkpGNlWmNWVlehYm9brFhVXmBgW15bYmlpZcVlvWzXv7u1sKZZmE9Plp6Zq6WfuLFaYGVeY15fXWhmd2ZnZFpcX625rrCmXKGcp7XDyMZoXF9iX2JYWamzabthv2JfX6uyV2BesrNhY2luZ2RhZGlvgG1ubWtpZm9jYnV6dXNmYGhkYFtfXmtwbWZaWV9hZmpoZmZub3V7a6VZY1lfWlteXVlVVFRZV1pcWa6aXqlYXmRTWmJSWF1gZmtva2hhYmpgZGVjb2N2b2xue3Jub2lkaG5iYm9sZ1dWoq2mrK+/prVZrFdktaCerZqlqq6gp6ebgKGXpZimpqigmZqeop+Um5ahp5uglZCgmpCaiJaTlpWerba4r66ooaaUqJqGc3l2iI6YjY2Olo+Ol5SenJSxpKGfpZaam5eYmJ+OkYmTmJmVlY2OkJaun6+7q5+ZlZ2murW+wGReYGTAxmVoYcGqZFyfWLiXY1xetGJfZWGmpK7AgHphY3BfX1Fkf3h4dG9qdniHinVsa3R4e3hvUEpFTHZ9foFERUR4Q0KDi42eVJd/fkRHP0Q5OGc9Oz5CfkRBREhKSEo9clJ0QkNDQ0aAhVJNiWt1dnh/gn17TIV2Z2xre4mIgnxwbGhbZHRraGx6b2JqZndxeXxyb29ldXx2cnFpgHJzaHF6holrZ296f3KHcHtxeXN3eH58eXN7e4N3cXN4f3qBdHCEg4yRe3d3T4iKgnd2fH52i0dLRX92dH9Icnl5cXd3dINKjHqHeH18SUZGjIZ5bX11d3huZ3hoVFVlY19gY1JSYTZhYzh3cW16Q0A+SjVfME42NUM5OTk0RkMzgENKR01CS0RIQzdsXzllPD1ISTw0Nj0zRUlDMzdMPzU2PzU0MkcvPEpITUlAREVTOUlCP0pPRT8wOUpHQjllY1k4YVxBQEFBR0Q7P0FFaGtvOz5DYEZEcVlneWR0OlA2NUxNUktCQj8/REJFRkM7P0g/bGRtO3E9P0I/R3lrYGpsgGl+fYqHhHV3SHlnfGGBcnt/gHFBf3WDh5J/lYZ/RkJISldzfUNqe3dsdnV4d21EhXZ7T0hIVFBGTkBNf0BxbT1yd3pFcGhydnh0cHR0aXNzcXx0d3p4RFh1b2lzeIZ8cnaFfms6PD56g0VIfZFQfkOAcnd7eWh9gHhFR0ZBQj8/gEGDakFvb251d4F5fXpua3BqeX53kX11eW6JgH5zioCGhHqESH15fY2Kf3p0d3NyaGhhamtubYVoZGZcUmZfXGtsamdlXmNCfHVycGpoZnFrY2xqb2RkZWdsaWdkPUU7Pjo4ZTY4OWo4Q0g9PG9JS4JzeXt6cXs/SUmKSId5fH1xgEF5UUZodoOGhW5nenB4e21lbElLQUo8PD1rR1JAdT06QEA9NTUyNjs7NWE0YDt3aWpqamY6YTQ0Ym5qe3dqfHY7PEA7Pjk8OkJDUkNFQzs9QG9/d4F6RW9kb3iAf35DOTxBQUM5OmxwQ2s3ckFCRXyKRk9MhoJGRUlNSUlGSExRgFBQUE5KRk0/PExQTEtAO0ZEQj9DRFFVVE1BQkhKUFFLSEFGR09XTHRBS0FFPj9DQUA8Ozo+O0BAPG9VOVwzPEIzPEY4QEVDRkdJR0I6O0E1Oj07RTpIPjs8Sj47PTg2PUU8PExKRzs8cYFydHmJcn88cTdDcWVsf3F+gIFzeX1ygHhygnN/gIJ8dXZ8f39ydm+BhXR4aVxtZFRfU1tcYFtcaGlqX15cWWVZb2dZT1pddXZ7cHBvdmxsdXJ3dGuKgIJ/hXl+gX59eH9vcGZvcnl5fnt8eXuPfYSEd3FrZ2lwf3Z/gUVCQ0Z8ej4+PHhnRUBlOnZTQz0+dEI/RD5fXmuBmnqEe4R6Bnt7e3p7e4R6BHt6enqGewF6hHsBeoh7A3p7eoV7BHp6e3uJegF70noBe4l6g3uEegF7iHoBe4Z6g3uWegR7enp7hHqFewN6e3qUewR6ent6lnsBeph7Bnp6ent6eop7CXp6ent7e3p7e4Z6Ant6k3sFenp6e3qFe416AXuKegF7iXqFewN6enuJegR7enp6iXsJent6ent6enp7knoBe416DHt7e3p6e3t6ent6e4l6iHsDenp7n3oBe6J6AXuVeoZ7BXp7e3t6hXsDent7h3oFe3t7enuFegR7ent7jnqHewV6e3t7eox7BHp7enuGegR7ent7iHqRe4V6AXuHeoh7EHp6e3p7ent7e3p6e3t7enqxewF6kXsEenp7eqt7iHoEe3p7e+96hHsRenp7e3t6ent7ent6ent7e3qEe4R6AgIEAIDd4dPUwMvM7PqDiIGBztXP0ura19jr+vT3+4KCgoqAgY2khPv9hIP58oHn/YONg/r9+oaN/Oj5if77jIWRhIWBhYqDh/Ty8IKL9vn29P3v3NrW1tbM3+TXyN3X2d7Y2dDV397X6e7g29jn9uHh2NvZ3MvEztHBv9jb1djDv8S5xoDR0MO8rMLL4ODa3eDTw6+2oqerrr20tLq0vcbU19TIyMnJ2tnS0cTJ5d7f1tPm4uHh6Pry/PHo7+/YzNjezNHu+Pn919Xi94WEgoLu5/bn5ubu49XS14n8/ISFm4T9j42Ag5Gnk46F6uTd5oDj84WPnaGYhMzq84qIiJGcooSEkICGlKO4tLy+s7ulnpujl5aVr7+viqmxp6+ii6XKqquln5+Zq8XGp5eTpamkpJ2mmIKQjpSQi+2d74CQkI+g9+7474WGj5CDgv779On/h42EhZWblZSO7ZOB2tvqjYWHl5KWhJ3n74qQjpKKiIiHgYyLjPuTjfX58uP49PHn8Ozv8oDq8/b+iuzf+O3w3IDZ7fbu5+j4+oH8697l94HmhfWC6/T034D7iY+M/frz/9zh7OfU9YiMgPyIjouNjZCMioyLg4H+itjM1+ng1d7u4eb28uT09ePg3/vm5uvi5NTj18Da6+H39IHxgvf19YWEg4KFg4P87enX4vv4g5ua9/r46IDu9+bz4OPq7Orb1MnDwrHVzNrf48vU3t3g65L25YuE8fL0+/Lg3N6C+oT354Hr7Mm2ybLFvdzdhO7Y34LtxqWpu8XFx87A3YDp5N/j4Ofg7O/j0uPY2+rj09Dm6dz68IiHh4nq/vzj8P/8/IGA9vLs5M/Y1u3s0+fi+e7m3+b4+oCB/oHz3/7s2uHj2tLe+ID38/CA/PyHhY/5kJeKg4mGg46Jhu6Rh/+DhIaJj4707OTR7/2BhPuEh4OHioWSg/zz+vyAgI+Lj4KCh4Pv6O309/vz9fmA4IKN+fr/7/OBioqBlZGEhoLy0If7/oL49/f4++P58vX+gPiEiZGSmKKhn4Cdn6Ssn5yUi5WYk4qDj4qQlY+Gh4aWmoyHiJSWkaWkoJyTnpqEk5WA/oWC+YSLjIyEhoqDi4qHg4P7hoyLiY2InJWIh4qQjYGEkrifqZiVl5ylpKetwq2prLWnoJebpaSZkJaokZKZk/z62s7m5ej2jZSGgvD54+zv9OXl7ejm4IDj39PX2NLRvr25tcXQxtPn8vLg083UvbTDvc3O4tbVzsbF1MHAyr7VzrzDxMfRzsW+stTN3uLV1s+/0M7I0Nzr1uLG6tW8w8PM0cTNz+HY1s3M0dDQydfkzdTMpsfAu9DV7oaDiPXx5d7y+Ib16Oz38vzz+e+B6/XsgoOVnI3+2oCMjoOGe4WHpbNgZF5fjJOUm66blZCfqJ6bnVJUVFpQVl94WqitXFmln1aTsVxlXa6yr1xjrZ2xZLKtZV9rX19cYWNbXaCWkVJYlp+gqbKmlpOJh4qFm56TfpGLkpeTlo6SmI2Ejo+Dhombs52WlZmPnZWYpqWVi5yclJKAenx3hYCSl4qIeoeFlZKNk5uShXJzYmltcoN9h42GiouSlJCHi4yOn5uRjYiLpp6Zjoycl5mbn62oqZuTm5yHeomYhIOfpKSpjpOes2BaWFaTiqOiqa2yn4qBgVibmFJSZFOnYWJaW2dzYltQgIJ8iVaUolpga3BrWYKapWZjYWl1eFxYYIBQV2FsaXB1cX5qZ2NsY2BidoJ2VW52bXRpWG6PcnJsam1ndomHdm1mc3RvbWpwaVpkYWVkYaFtplpoa2RvnI+XklNTXF5PS5aVkomiWGBZWmhrZmZhnWpaj42WXVNSX1xjVWWdqWFlZWZbXV5cV2FfX59fWZmWjYaWkY2BiIiKjYCDj5OeWIyEoJGOfk98jZaMhIOMiUmQhIKKl1CIVJBMgo2LfkyQU11cnpyXn4aOlZJ/mlhZTpJQU1FSUFJRTlJVUFSsYpCHipeKfH6Lf4OXmZGhpZOPj6qSjpaTnZWspZaos6myqlqpV6ejm1NSTUtPT1KnpKiap7alWGZjlJaYjoCbqJyll5SZnZyWnZeTj36RgIiEiXR2f4SNmGifjltUk46Rk5aOjZVesl6mkVWVmYN3h3WEe5WSXJ2JklqmhW97iI+PkJGIoVyhnJCVjpOQm5+bjZqaoKqpoZ20uam6pF5bWlmVqrOepqynq1hYrKyuqJiclKmplKeftaWdlJKgnIBTo1Kcmb65rrW0qJaetl20t7BeuLFcWWGbX2VeVFtZW2RiY6twZsdpaGpwdnXNybuluLlVVZROUE9SVVVhWre3u8tjW2VkZV1kamS5sKq2urqysa9Zl1lfrLO9ucFmbGZaaGRZXmG9pW7IyWS7ube1t6Gwpqu2YLljZWRgY2ZnZ4BjaXF0bWddV2JqZGJdZWBjYV5VU1NfYlpTV11fWGZoaGhmdHBbZ2VPoVhVollcXF5UVFdQV1hWVFaoXWRfWlhTYl5WWFlgXlFTX3xnc2ReYWZwa2xwgXFxdX53b2dudHFkVVxxWl5pY6u5opu0qqirYmlbWKesmaWnraKkq6ummoCZlYyQl5GYkpeUlZ+jlZ+kq62elpqml5Wjmp+dp5iZoZikuKiprZimoIiKiomVlo2BepOLlZiNkJSKlZaUlaWym62Vt6WOko6MiX+GhZWKioqMkZKVkaC4rLKqh6alpbSswW1kX7Kwpqa1wGzBsKywrLOvt6pir7asYF1vdWKri4BpcGluYGVgdYBGTEpOdXx6folvZ2Nzhn+AhEVEQkQ5PEVbQXl/RUV9eUV3k01VSoKAfkNIdmh6SXx5S0VRRUZDRklDR3pzckZNe3x3gIyFf312dnh0iYyAbn12eXlxdW10f3h0hYZ1cm58lX92c3Rtdm9rfYBwa4KFf4Ryb3Bnb4B6f3NvZHJygoN9h4+LgnB0aG1wcntyeXhudn2Ehn9zeHl4g4F2cmpth3+AeHmLhYJ8fIeCg398goVyZnR/a2uBgIOJam12ik1KRUZ8d4qDh4uPfGldXEZ2cT0/Tz12RkU8P0hTRUA6W1xZZj9haDc6P0I9ND9aZT06NjtCRCsrNYAqNj9NSUxPTFRGR0JKQz49TVNILENMR0xAMkJWSEdFQkM9RVNQQjk0PTs9PTpAOi46OD49O1JCVjI/Qj1LZWRwajs+REc8O3p8d2mFREc+PERIRENDZUpEaWt2ST48RkJHPEVhbEJEREY8P0A+PUZDRG1FQnF1cm+Ae3dmamVqboBpdHmFSnZshHd1ZUBmeoJ/eHN7dj+AfoGJmEp0R3ZCdYOBc0R7R0tHdnd2eGlye3tphU5ORIJITUtLSElFQkNDOz18SmllboF1a2tzZmd2d3OGiXh0cIVtaG5rdGt+dWV0gXR+dkWAR42LhktLQT5AP0CDfIB1gJCFSVhXfH2BdIB9inuHenV3eXt1e3Z0c2R7bnt4e2FhbHB2hF2Qf1VNhoCBh4d9eHxPkkl7aD9vfGZab1tmW3JrR3JiaER5YEpTYmlnYmFTakJxbWVraW1mcXZwYm9pbnRzZmN0e25/a0A7OTxdc3xrcHt3czs9dnqAfG1zaXt4X3dyiH10bW9/eoBBgEBxaomAdHp3a1hdfEF8g35FhnxBPkdmR09HPkVAPkU/PFpGPXI8Ozo+Q0ZxcW5edX05OWM2NzU6OzlGOW1tbHY9Nz4+PzQ6Qj5taGdzeXp1en9EbkVKeX1+b3M/Qj40Q0M9QEJ7WkR2eEGAhYmMj3yNhYGIRH5FRkVBRUlKSYBGS1FTTEg/OEFIQUA8RD9ERUI7PD9OU0pDRkxNRFFOTEhCS0k7Sk08eUVBdkBERUU+PEE5QD89OjprPEM+Ojo3RkI5OjtBPzEzP2FGTkI7PD9HQUFDUEBBREtEPDQ6R0U9NTtPOjxGQmZ3YFp1cnN1R0w9O2tzZnV5f3Fze3l6doB2d29xdXB4cnd1dn+Dc3Z6hYVyamtxYV1oYGtpdmhlY1RXZ1lXXVNoaVpeX2d2eG9jWHJpc3Vrb3ZseHRub36McoFpi39veXh8emluaXVtb3J2eXZ2b36MdXx3VnRxbnprfktISIKAc2p1eEV5b3F/eH11eHBFdHtvQD1LT0FzYol6hHuNeol7F3p6e3t6ent6ent7e3p6ent7enp6e3p6insFenp6e3v+eoR7i3oDe3p6hHsBeol7hHoDe3p6hnuDer57A3p7eoV7hHqGe4V6iXsGent7enp6iHuCeox7A3p7e5B6AXuGegF7iHoBe4V6BXt6e3p7hHoFe3p7e3uKegR7e3t6jHsCenuhegZ7ent6enqHe4d6g3ueegV7enp7e4h6Bnt6e3p6e4p6BXt6enp7i3oBe5d6hHuIeoJ7k3oDe3p7i3oLe3p6ent6ent7e3qKewR6e3t6hnuGegN7e3qIe4R6iXuJegR7ent7hXqJewZ6ent6enuKegJ7erB7BHp7e3qNewF6rnuIeoR77nqDe4Z6AXuJegR7enp6hXuCegICBACAiIXy4b/W0bWkwsXYyNDU08vT2+3s4ODlwMeD9fOAhoGK9YD79vXqgYGWgY6Djvrz8/6Ei4Hw94Dw6vnp4+vXvt7h7e/k4fj+hfqA+uTk5MzT5dnO0cfEvMvHycnb1+TP7efm8uDc8+TQ4fX3++bl3dHP7NLj3MjH0sXBxMW5ztCA08Skppq+qdng3d3u7oXn19DMwsi81NOAxMLNzNLM1NHd7e7b5ITp+/jy+93V4dbn2eLp3urg5u7v6tfa7dXg6PHj4MTR2ubz6PiA/oCH9vP139nOz9z/jPLh85mbiZmSgujihPv1/vj19/GDhfOEifnpk6aIh/eG1KuWkbGgjYqAhpK1q6CHhv6FjJujkof9/5Ohm5mdnZOWo7iTipeRkZKeoI+GhYaJnJOpqqCMhv6GkvmAhZ363PKH8ILzgPKAhYiQsKOC6+/vgP+JgPT/hIP+74iJ6emOgIOPi5CXuqaZioyGhIiBgYWfio6Lk4mKhaGFnIP28IDgztXrhvKB7+GA5NPb4NvTxvTZx8Lk7OyKhev3gPHs7/vu3NbL0uPtg4j+gtz3ioaA+Nqlgu7/6Oz38ZL15ITx9Pb1+IL07+fq1/KG+fLk59Xk3eng6enm8urogfru7vro3svR39zg39HJ1PXt8vHpgvLt6erzgYCIjYX+8YDr2+T084SJivL79PKA3/He2+PW4Ojs3eHW087M2uT7hPDe4OHr6M3S49vo9+rf8Orb1dXT19Pj4oD/9oLRssO73dnb2tfMwdHQ0cDBnq3AwsvAx9j67ujk4OHx4+Xn4Nvo2uzo6dvT29rh3vX7hfKAhIH/7u7s5vWE7NfZ7+LY39rXyNHO6tLS3OPu+fiA+YOA9vvyhICA//qDjYmNkJCE/4yXiIOOgYmSkIeGgYmEhouAhIT8iIyKkJCGjPj14+Hh7f3/hIuFhpKHk42DiYeHhoSNm5X+gfb4+Pb56d7x7+r8gIWLh+7o/veDg4eTkpGWmIGFl4eKg/ju9+Ts9YL7+/LY2OjY/4iHho2Um5SAnpGYk4+Mj4WMjZCKkJeNiY35iYqEiYaCgomNio2OjZWQj4aB+oaFgOTl/fSDh/b9ioP1+/zy9er3/Oz07Ib9g4uLj4uPjouPjJSXipOWpKSho6StmaClo7e8sp+Zn5+VkpaMhZGIjJKD+dHP0+fjjoP6gPTo8vH38vPy5+Po3c2A0NjSyNDSz9HWybvM29XQ1tHf1sXLvtLY0tHY1eXg4eDK0dHEw8+/0s7g39/U1MnGwsTW3uHp1cjOvr7Aw8fH18jEvtTI1t/TydDGwdndydXByuDR09TUycHa4MDaydDi2+Lb3vH039Th5PTr5oP18/ju3/OBhpWIhoeLnpeQiIuAXlifj3WWlnlwj46ilZuam5SXmqyqoKCdeXlXn5hRV1ZgrFqysK6gWFhuV2FbaLCtrLBXXU+OnlKbnbCjpq2Qe5mdpqablqaiVJ5SqZ+goY+UloV9gnl3cYN/hIaVkJmGnJaOl4uIoZ6PoLexrJ6akYqOsJiooIuJmIuCg3tsf4aAj4lyeXGOcZSWj4+gpV2ZiYaCfIZ8kpZehoOKiIyEi4WMoaKQm2CjtrOttp+WoZmolZyglJuNkZaXlo2ZqYqJjZyYmX6Pl6i2n6hTplRfrK2wnZmLgIGeWZWKm2hnWWVfVpKLU6acpJqYmphZXqhfYKucaHZfX6hjooBnYXZlU1GASlVvZF5OUJ1WYG93ZVeZmVpmYF5jZlxhb4tiU2BcYGVycmNaW19pem12cWdYV6dbZqpWWnK2nLJkrGKrWKdTU1FYb2dOiYqUUqRdVqGvW1inmF1fnqJoXF9oYF5geWdfV11bXWNbWl9wXGJeZFxeWGlTZFGbmVOOe3+VVZBSlIyAkX6HkY2If7CRfHWQmJVaV4iSSoCAhI2KgHt0gIyRUFWWTnqJT05LlX9mVJKikZGclmKThFOPlZWTmlCTlo2Pg5pZqaOYoYqajpiJjoqJj4iHUJGLiZSPiXuDlZKgppeWnraurauiYaqnnpeXT0lSU1CfoVuuo6mwpVlVV5CZkpSAiaKLh5SBj5uhmauhnpORkoiaT5CKj5qqo42QnJOaopWNmZGJhYeKj46dnFeqpFqMdYiDoZudm5aNh5Oco5WZfomZl5SBjZivppuUkYyXkpaYmZekm6uyt6yjrqqsoa6uXqRXW1m3qqiel6BcoZSasqifo6OejZaMpJGMl56eqqKAo1dWorSyZmNhxLpgZWJhYmdZsml0YlxjVFZcWVFUUlpXXWJXX2C4ZWlmbWpjbsTDt7OppamfT1JPT1dTYVtbYV9gW1Nba2WwYrq+wL24raa2sKqtVFlbW6evycJlZGNpZV9malhecGNoZL+4u7CxrFqusK+nprmjvV5YWVtgZmGAaWJqZGRcWFNYW15XXWBYVVeaWVhQVE9LTVVXWFpYWGBdYV5asWFiXJyfrqleX6auYVqkqbGqraawtqixo16hWVhXW1dVV1VYVFxdUlpdaGZjZWhwZW5zc36AeGZia25nZ2RbVl1ZYWdevpqcn6qiZlutVqehqKewrLKvqqiuopSAlJqXjpSTk5aak4iRpKOZmpalmZKelamsqKGdmqWdoqiXpamem5+KmZSinp+RlZKSiIePjY+UiH+UhImSmJygr5+claSWnJqVioV8eYaTho+AjKOYl5ybmZatt5q0o6u5rrSoo62yn5yxsbuwoVyon6ikl6hbYXBlYmZpe3dtYGWAQkF3blp4dVRJZml9doKEhnt3doWBeYCBZWhNhntAQj9IfUaIhYF0QkRXRlFJU4N7fIRESDxreEBzdoh6fodxXHRzeXt2cYaKSYhBhnt8g3d7gXNud29tZnRucG55cHlrhYN/i4F7i4BseJOQinx8cmtwi3qJgnFwfHJxc3RnenyAhHxkal97YYSGf36Rl1eShYOEfoR4io1XdHF+e393eXeAjIp4fU6BkI+Lk4B7h3yIeHt8cXl1foaIg3V+jnJzcoB9fGFxeo2ZgYhHkUhRk5aVgHZkV157Rmtda0xIOkM/OGBbO3dsc21oa2o9QGw+PWNQPEk1M1c1X0k1L0U6LC2AKzdSSUMyMVszO0tRRz5oZ0BIPzw+QTtASVxANT86PkBKRzsyMjEzPzU/QDkxMmM7RGc1O05zWWs/aT5lN2w5Pj5FWlM5YF9qPnpDPm14QTxrYT8+Wl5FPUFKREREWEk+Nz07PkM9O0BPPkM/REJDP1A+TkB4fkiBbGt9Q2s7amOAa15odnVyZ5R3ZV50dnZGRm52PWhpcX+CenlsbnZ0RUyMSnOCSUhCeF5PQ3CEcnaCflN8b0Z9gn59hEV/fnJsXG5DfHxzf3OFeoVzdnJzd3R1SH54d31xaVdba2l0eGlpbYmBfXl5TImNiYeRTEVLSUN/fUiGfoaPiEtISXZ9enqAboVyc4NweX2Bd4V6eHBwdXCFRXRnZ3CDg3N5iYCJjX12hYWBe3l4eXF3cD98eEZoU2dje3N1b2tjXmhxdmxwV2FwbWlWXGmCenFraWp0bG1wcGx1ZXZ2eW1lb3J2bHh4QWc8QUCGfXdwam9BcGNvh3txdnNvXmVhe2hlbnd7g3mAeEI+cX95SERCgnZARkNGSE1Ae0pRQj1HOkBJR0BBPUE8PT8zOThoOzw4Ozo1QG50bnBvb3VwNzk1MzkzQDc0PTo6NzE2RD9gOW90eHp6cG1+eneBQUZIRXFxhnhAPzxFQT1ITzxCUkJFQ4GAjX6EhUiMj4x6doRtiUQ/P0JGS0WATEVLRkU/Pjc7P0E7Q0U+PT9sREVDSERAP0ZHRkdEQ0hFRD89fUtNSHZ3hH5GR3uBSUFydHlxcmt1d2dyZkBrPj8+Qz8+Pzs9OD9DNj4/RkA7OztCNjo8P0hNRzc2PkE7PD44Nj03PUI4clJWX3FtTEB0OW1lbnN/fIN9dniBdm6AcXl3a3BydXuCeXB8h4N2c29+bWBoWm91b2xwbHlycXJYYGFZVWBTZGZ4c3Rscm5rYV5pa290Z2FzZGpucXJyfmxpZHx1gYiCeXpuaHV5bXdncoh6dXR4cmd8gmiCc3qIfoR6eIeLd296eYF3cEZ7en90ZG8/RVJIREVGVE5GPUOCe5h6A3t6eoR7Anp7hHqHe4R6Bnt7e3p6e5B6A3t6e8J6AXuJegF7jXoBe6R6BHt6e3uJegR7enp6hnsDenp7h3oHe3t6e3t6eoR7AXqQewF6hnuCep57EHp7e3p7e3t6enp7ent6e3qHexF6enp7ent7enp7e3p6e3t6ep57A3p6e4R6A3t6e5B6BXt7enp7i3oNe3t6e3p6e3t7enp7e4Z6BHt6enuFegF7hnoBe496AXuUegF7hXqFewN6enuFeoN7lnoBe5h6BHt6enuxegV7ent7e4Z6AXuVegp7e3p6ent7e3p6h3sBepN7AXqHe4h6kXsCenuLeoR7hHqOe4Z6AXuIeph7AXqSewR6e3t7hHoGe3t6ent7i3oCe3qqe4Z6BHt7env6egF7hnqMewICBACAgYHuzvrw3e7S3Onx+I3p4dvf0N3U1tnN3NHZ5djV1tjVxtbm/oDl4/bw6oCI7+Ld5ero7fjv6ers48vJ087R09Dm5oSBgv2C8fbv8/Tm19HJzdvo08vGwtfY3NjH0dLb3dnHxru3wsC2wrnuhurg6dTJvsDEybizvL2ut7i7zciA76yin6Gomq+wxsDHs9Pj6tTHwcu7xb/Pxc/Z5NjN0djf3djf6vTo6fLm3dfi68Pc/O3u4d3c5+nf3ujb9OyA4Pvi++Xdzt3h0svJzs3V7YLj9PSGgZ6dnZPe7PDp7O+B+/vz9/+XjYWGhoiVkYD2mu3N7KCZjIn2g4C3loSAkPOA+Yfw/PWDkvuTnJSC94mEipukucKzma+W07CNkY2eoKCF/JWZrqyhk5eZkYmFi5mB/o3zkJKQ+J+YqYL19uSj/OTng/GChYqBlJ2RgI6bgIv++4D+jICDg4eEjZymnJaxr4WHhe7s/oaPiJ6Uhuf124Dt4dbfge3uhIn+gYuJhoWA5uTegfLy28DT1LvT3vfji+nt+fqA6frs+PXs3/jo+On1gIvn7+zSwtXV69nUyO3r29nj3+Tw94CCgeuA7ICH8/n/lPHz0N3d2fv16fXz+feB+/rq99fSyc/Kztzf3sjJ2/Dv+O78+/vu/PT9gf///4P8+oL+/vD2hYGLj4f7+/SA/Pr46tHd1uXm4OqC8Pzy9Ons6+zY5N/m49zR3sTS1NzY4+Dh0+zX74L/8vPn6uvPxtDQ7O/gw8vItrjEyKKtvcK6v9q+u9Lm8erj5+Hvztzu7c7P1eTv5tve3+Pn8Pfyg+D35/zr8Oz34dXg1NDk7tnj29rQwcDc5OPv/vyH9oiAgIaE94mTpIKFjaGunI+Mh/qD8NrS3/GGi5OKiI2GhZWFjIiG9IOA+YaB/9/P29XY18nm1fX9hYqDhoWCgoiDgY+Uh4/zjOv1+dvm5+fs8PXzgPj2/4H6/YH69IL4g4ePiaKbjpOLhomHhoH8hoLz6+rq1enz8fz57f/6hIeHkpGAi5SHiI2TlZGGgoaD7u3phfnh9YSJhYqPkZeTj4eLlYqIgez+gP7x2cfR5oTo7oiL/oKA+f2B9YGDgPT74Pj0jIWQlY2alIeLj4mRmZqesaqiqqSml5ORmKekrp+hmJmSlJKOj5KO/ImCivXcx7vH5ePv9+v1goGF/oDg+u/ridSA3Nzi4dHZ4drd073V1snHzN7c2cjQy8zQzdLKyd3W58XKubi+ydHn5eDo3+Hi09TJwr/Cxc/c2tDJzsnJzMe9w8zNwc3c19TQzdHHusnHzdDFy7/M08S8usnO1tHox9fVyMe9t7fJysrX0Or4/PSB9YKD2eLN3977gfLY9u6H/e6AWVaagK+snaeQnaavvGqkm5qejp2WkpeGjoKNmo+VmKSmmaGuwV6hmKqgnVhgpZaXo6WjpaWen6GmppKRnpSNjo2ioVpWVqNTlJqQmqGXlJWRjpCagXh0cYeLk5CFjJSblpJ+fXd2ho2FjoizYqeepJaTjpienJGLk49+gHh2g4GApHJydnyCcH9+kYyTfZKdnoh9e4Z5hH+Mf4aNmpCDhImMioeLmKGTkqOdmJuiroegu6qmlIqDiYmKiJSSqZ5Xi5iEmo2NiJ2mkI6Mi4KKn1qfsq9eVGhjYlx/k5+boZxVpKOdo65pXVRTUldmZlqwcqWMn3ZvY1+tW1aDY1FKWo6AjFCKkIhPXJhmbWhXm1lPT1decHlvX29dh29RVVRkamtXpWltgH14a2trZGBXWmtYrmOfZGprt3l1gGKvr597qo6CSohMSk9MXWhgVWRxXGWzsFy1al5fXWFcY21uX1Zua1BdYamjqVlcU2hhW5Wjh1CNhoCTWZydWVukVV1cWlqAjoiDVJKYiXKDhW2GkKqWYo2EhoJEdoyHlZSPh5+UoYyUT1t/iYpwZnuBnoqNgaOhlo2UjouRlE5PT4tPklBVl5miZZ6lhY6JfZaPfYWLkpNQmpqPnoeFg4uJi5mhn5CQnKmqqJqnp6mcppicT5SVkU2YoFqzurG0YV1eYVqWlI6AlJaZkHyHgpKRk5tdqq+kpZmck52PnJ2np5mRmYGNi5KRl4+Wh5uMpFy5qaOamY+EeYSLqK2jio+JfH+KlHmCkpmOjZ19eYiUoJWNjYeahJOpspWYnqmtqZ+go6erqKihWpGxp7ywtKytnpSeko6fqaKyqKWajIybnpeXpq1fsV2AUVVTllxqe1lWXWt2Z19fX7hgsaKao6xeXF1OUFJPUmRZZF5fsWFgvmhiwqibq62ytaSxkaadU1RSWFhaW2JeWWRrWmKqa67L07K6tq2qrrWxWqylrleqrVq1tWO4YmNmYHBtZWljZGlrZ2C/ZGCuqq60nq+4tb+1rrisWVhaYFyAVlxPUFZbXV1WV11eqqmmYLCaqFhZVFZZXGBeXVZWX1ZUUpqwXLy1nY6aqWSop2BirVlarK9bq1tfWqu3nqudXlBWV09aWE9RVU9YXV5gbWZjbm5xaGVmbnp5f25uZmhiZGBeYGBeo1lXZbOmmo+dsJ+oq5inWVdctF6hubKtY5mAoqCprpeYn5aXkIaZnZyUl6KfmpGmnqSpqKegmqmisZebjo2TmpqopZqfmZ2gm6Sdj42GgYqWlYyMkY2QkZKSmp+fkJSfmJGUlI6GeoB/iIyIlIyfopOPkJuaoZioiZ6flpaGeneEiY6cla23sp1TkFFXhY+AkJS2YbahwLVls6KAPz1uV4B+cXdgbHZ/j1aHhIKFc4B2cnltenN6hXV2dXx6anaBlkd0cYeCgEpShHJveHx8gIF5eHyAgm9tfnZzc2p6d0dFR4pKhYd1fIN4dnl2eHqGdnNxbX58fXdlaXJ+gIJxcW1qc3NmamSOToB4gHd4cYCHhnlzd3dtcHFzgHqAnGlmZ2twYHFwgYCJdpCdoIyBeoN1fHeCdIOKlYh6eH+CfHp+h41/e4d/e3yJmHKHoYmBcG13goJ8eIB6jYNJbn1rg3V4cYSJdXBwdG50iU+HkohFOUpFRT9QZW1pamQ4b3Z0fIhPQTo5OT1JSD12UmVMXk5GPDleNDBUPjMwQGKAZDtjamA3P11BRkQ3aEM6Oz9BTlNJO0s/UE43OjZESEYyVDs8SUVBNjg5NjMxNkk6cEBeQkZFbEJHTztqd2tefWNdOGc7OTo2REk/NkBLPEdybjlrPzM3OT88QktLPTVHRjI8P2xtc0BDOkxFPm6AaUN3cGl4SX57RUZ6PEJAP0GAaWhmRX2Cc2N0dmN3e5F6UHZtcXA7aHx6jZGCcoR2hnaBRlR6g4RnV2puhnJzZ4eEd291cnN/gEVGRG5Abj1BbW13T3R7ZHJvZoJ3ZG9yeHtFg31xf2FdWV9eZXZ6emZmb39/fG+AhYyGkouVTImHgEF9f0aJkISGS0hMUEx9gH6AhoqOiHeAdYB5dn5Lg4h/g36DeX5nb3F6gHx3hG9+e39+hIKIeo16jE2TfntzenttY3Byh4d9ZGlnXV9qcVZebHFoZ3NWT11odGpjY19sVGJzel1fZHN7enR4eYCBfXxzQF17boR5gnh+cWVxaGV3gXN/eHdwZWN4eXFyfYFKg0eAPEA7Y0BKWTo3PEpUR0A/QXxEeGVcYXBDRExBQkdCQU0/RT08aDo4cEA6cl1VbHJ7gXKDZnxyPDw3Ozc3Nzw5NkBENz1aPllrd2Jvb21uc3x2PnRwfj92eT57dkB0QUFFQVBOSEtEREpKSEGBSkeEhYuSf42Nh4uAfIqDRERGTEmAQkc7Oj9DRUM8PUJCdHJwSH9rfkZLR0pMTlJNS0RGTkVEP3KISZuUfGp0gU5/fU1NgEBAdXY+bjw/O3B+Z3RtRjpBQjtFQjg6PDY+QkBCSkA6Q0BCOjQzOkFCSDs+OTo3Ojk2Oj47XjY1RHJoY1xsfG1ycGBsPDxDf0RthX9+TW+AfXuFhW5wenV5dGp8e3NpZ3RwaVxsZGlwcHNwbX55g2ZjUlBUWVpsaWRsZGlwbnVsYWBfXWJvbmpqbWhmZmRiaG5vYm+AgYOFh4R3ZmpocHNvem98f29qaGppc219YnN4cnFmYGNzenyAdYWLin1De0dLbnNibm+MSX9ngXZGd2uCe4t6AXuXegF7hXqCe5Z6BXt7e3p7pHoBe9F6AXuQegR7enp6hnuGegF7hXqJewV6e3p6eoR7AXqHewl6ent6enp7e3qEewF6lHsBeo57B3p7ent7e3qEewl6enp7enp6e3qMewR6ent6kHuDeoZ7BHp6enuEegZ7enp7e3qFewR6enp7i3oBe4R6AXuMeoJ7lHoMe3t7ent6e3t6enp7jXoBe5t6CHt6enp7enp7hHqFe456AXucegF7t3oBe5x6Ant6hHsBeox7Anp7hXqNewZ6e3t6e3uMeo57Anp7i3oMe3p6ent6ent6ent6jnsDent7jXqRewd6enp7enp6j3sDenp7hnoPe3p6e3t6e3t6ent6e3t7hXqnewR6e3t7i3oFe3t7enuEegF773oEe3p7e4Z6AXuEegN7enoCAgQAgO3w597Vytbd1t388PCE993NydXT0tW3x+7z1fDf4enm0cS/2uLY2MnPwsjL6drm1uXr6d329PP28Nrw5ePU1uPw2/H5gers4OTw49/Sz9nR38PBv8jZ1c3JycfC0dHN0eHgzrzAqsjPztTD1Nvd1d/f0r/CsqWlvLa3tq22urjHgNGfsJ+MsrSzr7a/sbHK39PQ2NXXzeHg0NHR4Nzh3+3p+ujj6O3k2+Xy3c7FxenK49Xe6u/19+zo8fXZ3+mBh/r1+OD3+tXgz8vRzMfHt6i3z8/hyNP8go6YmoeOhOHjzeTk6/L76+vm+fPi2+Du5PL3wP+mge3S+JKDjPmJjfTkgISViq6UlKOpxon76/GEjZakqKWcgoGVgIGBlbSnpKCFk439loaRk4aQh5+B9YL2kJ2VmJKGgYKNlJ6dm6yWgYDr9Pb0+IODkp6omISChfXS94WTkYySiY+no5+MgouduKuOhej9+vv9hf+Xi4aIz4WK7vXp393q2YGAhI+Pgof2gOvr19ni1t3Xx8rT8ODU6Pru5NvX8eri+fHq+ODf5uro4uv96ffjgNrU5eT36u30gOb38u3d5ez1iIiG6eni5/Dn7ID19evc6eHc3fH28f2H9vr89v/55+Xa2enS2dDExL3b1ODw2efb0+zu7/v794SC8/mEh/j8/v/89/H2+4PsgOb8/f/j5tnq9N7k4ev/9P3/lIL/7fL57eTQ3trV5uH17+vm3ePc29DR2dPWwsy0t8LMucPPztrn2NLRw82uxL62ts/gxsbS3OPW8d3ZyM/S0tTOusTe6ejW3enm54uMjYCC6OrSyMjN5tjS1NXd2NTT2Nf27/SG6e+CgYf4+IKGgIOKhoCOgJOOhoefsZWLjo2XjYn6i4ibiYKOj4OB84KPh4iKkoWDiYPl/e3W19zSzNHZ2OP9gYuLjI6DhYqCi5mLhIX/gJP8gfuD9+Lg7f37gfuEgYKF94eD7v/+/OuTiYiWl5WJhIiAhfyKhvv4gPmA+N7k8fnriYOK4+329I2OgJOPkYuPhpGIgv6FhIaB8pXs6Oj0goKDg4GKjISGgfz55+PT2NDb487BzLm03uTg4PyDgv78+PHv/vr+gO/1+YKGk4eGi5CGj4qQjpmirLOptLuwnKCYoZypvbfApJ6RjYuQmpOQoZWVlpCI6/vy0tft6YLz8/yG++n47uLg3ufhgODbzdO6y83d5eDPzdjLybrAwNLEx8bJwsnMycfN0tXD1L3S1dze5+7r6u6A29LWwb+7xc7az8nZ19Dc1tLUvtPL39DLwczGudDMzbK/0b7RvLOqxbXF18XRy9TR5s/Av77KvLy1ucXIv8bH0M/Uy97Z8/zz7uzw08Xh6+2A/P/vgJeVj4uGhpadmaC5q7NfrZmRjZqXlJZ9h6ipkKmYnaill4mDnaKRloWNhouLq5mfk56fmYqbnKSlo5SrqayWl6ermaiqWp6ik5ujkIyFgZCOmoF4dnmJh4B7fHx3h4eHjZ6fjn1+bpCbnJyMlZSbkpafmYiVin19lo6MiXd4d3WBgJFshHlwko6HfH2Fd3CFl4J+hn9+eIiIe36Cj4+Tj5eSnIeHipaXkZ+1no+IgqmGnJGVop+inYuHkZqOmqZcXp6TlYOqvJusm5ablIqKc2h1i4mZgYOcTFVeY1diXJ6hi6Kcn6Wum5aPn5qOio2hn6eoeqp3V5d6nGJRVo9UVox/gE1ZTmdRU2BpiFaZi4xOUVRaWl9bTFNmU1FMWXFnZWVZaWi3cmJma2FqY3VZq1umaWplZ2VaWVpjaXFxanVkUVKUm5iXk01MXGdwZlhYWqJ7mlloZ2ZuY2l7eG5aTk9cb2ZQUJCsrKusV6NnYFxahFdTf4eDhY2Vi1VSVFtbUVihgJqThYiMhoyHen2Jp5WMn62ahHZwgIJ7ko2OnImIjpGQkZmniZSES3h5iZCcl56cU5ilnZiJiZCWWFlYlpiQl6KWmVWgnpWCkIN4eomNiZNTjJWXlaSgk5aMjZ6NlZKMi4egnaOvmKCRhZaOipCPi05RnKpeZLa2tLWxp6Gmo1WRgIylq6ySj4KOm42XlqKvoqWfZFSqoKy1rqqVoZiVppyuqqiinaScnZWWnZWUfn5scHmCdYKLjZunmpyhk6CCkoh/fJGgipCdp6yYrY6KfIaLlKCYhomfpqCPkJaTkVlaXFZdqLGalpugsZuak5Wempaanpizo5pakpNTUVustGJjgF5iWlJiVWpkW1tufGJbXV5mWlaeXlluXVNcWU1Kh1BcVlpcZFtcYV6ftqqboqymo6evoaSrUlpVV1lQVV9XXmtiW1ywXW+0YcBmvamnqr26XK5cVFRWpF1fq8W/u6VoWlZeYWRbXGNhZrtpY7WuWq1as52mtLiuY1xioKGjoV1ZgF9YV1JVTVpWUqddW2BbrHKprKerXFpXVVVcYFxdWKyqmpyPmJmmsqCSnoiCp6ikorRcW6usqaaltampV56hnVJOWExJUFlSWVVZVmFlamhea3VzZ3RtcmpwfHiEbWphYWBja2VfaWNjZmhjrcCylpSjolmorrVjwLHEv7Sys7a0gLavp7OQlpOfnJiKiJKRkICLj6GXpKOpoKekopqcnqCSoIqcn6GYoKKenapdl5mklJSOjpObj4WVnJKbmpWdjZ2PnJCLiZWOh52NlH2EloKThoaDnoyWpZOZkJaJmYd3eHuIfYF8foyPhIqIkomIgIR/l5+VlZicjIWlsLpkvrifgG9vbWtlY3Bya3CGeYVNkIV7dn58dHVfapCVeY18f4iHdWZjf4Z3f3J8cnRzkHt9cHt+eWp6e4KEgXWMh499foiKeIaHSoOJen+IdXZvand2hHNtanCDg319fHRrc25uc4WKe25wYHh+fXpueHV3cXeBgHF8dGdne3N0dGhwdHJ/gIpjd2pce3d0bXB8dXSQo46JjIB8doSGend+iYKBfISEkoGAg4mEeH+SfnRtbZJzhHV0fHmCiYOBh4p2fINJTX93e2mNmnyMeW5yb2xuYFlnenN9YWJ6ODxBQjVBO2BmVnBzeoGIbWpmdnRtZmh3cXNwRW5PNVhAXT8zOVw8QmhcgD1JP1hDQ0tRZjprY2k/QkNFQkU+MTZIOTgxPFBEQD4vPDhUPTA1OjA3MkYxYDdjQ0hCR0Y9Ozs/QkxIQk5DNDlncXRxbjk4RUtPRTs6QHNQZzxFPDU3LzZJS0c3LjA+TUUzNVx5end6PnNLRUJDVUVHcn53eHyDcEI8PEA/Nz5zgHNwZ2pyb3RyaW18l390gYt+bGRhdXlxioiJlXt1dXZ0eYOXf4p5R21ufHyHg4uKSYCKhoJ3eX2AS0pJeXhtcXxsbkB2dG9mdG1pa3yCe4JLfn56dYB4amlgZHhrdHRtbWl/d3uGcn95eIyNj5eTjU1KipBPUo+OiZGTioiSjk6DgH6Xn56IhHJ7hHN5c3uKf4aCVESAbXJ8enxyhH17hnWDfnx9fYqEhnx4eW9xX2ZcYW14Z2hta3J8c3V2bHpfcWphXW54Xl9pcXdne2NgUFZcYW1lVVp0gHtucHNwbUVERz9Dd4FsZ21tf2xnZ2x2d3d2e3aOfXdDbm9APkN3f0ZFgEBEOzNCNklFOjlMWkI8P0NNQz5oQTxNQDtHRjw7aj5IPz08QTg1PDtcdGldZHRxc3iDenyEPUM9PDwwMzw1P05EPD1sNEJcM2o+d2lpcISEQXhBOz1AcUFAbIR8fWtJPjpDR0xCQUhCRXxKSImHSZJNmYGAgIBuRUNJbHV/gExJgEtDQz1BOkZAO3NCQURAdld0d3V9SEdGRENMT0lMSY+QhoZ9h4aVnIh5gGhlhoF+eIJCQXV1c2xsfHJ2Pm5wcTw8RTo5PUQ7Qjs+OkNFSks+RUpEOEQ8QDo+SEVQPzs0NjU7RD47Qj08QEE+bIeCbWt3cz9ua3JCfXCFgHh6eoF+gIB+d4BlcHKDhoF1dn94dGBmZXBhZ2VqZm5wcGxvcnJhalBhZWRbYmdna3ZGcXJ+a2diY2dvZFtrcmp1cmtyYHBmdmpraHZ1cIiAhmxxgW6Acm1hc2VreGxzam1keG5iZGdyaXJvc4OGe353fXd4bXhzi5GBfn+CbF91fYJHh4RzjXoBe7Z6AXv4eoJ7l3qHe5Z6DXt7enp6e3t7ent7enqKe4N6lXsBeol7A3p7epF7hXqJe4N6knuFegJ7eod7h3qHe6d6AXuIegF7iHqDe4d6AXuMegF7n3oGe3t6ent7iXoBe5J6gnvKeoV7lHoIe3p6e3t7enqVewF6iXsBeop7jXqOewd6e3t6e3p7hnoCe3qEewN6e3uFeot7CHp7e3p6e3p7hnqDe4R6i3sBeoR7Anp7hHqKe5N6gnuIegR7enp6rHuHegV7enp6e7R6AXvQegR7enp6AgIEAIDl3tjY6O3v5Nnc6trG0cC3scfZ0NfRuMXs0cfY6/Td0vja19nd0c3NyMDCysXQ29fi9vX8hYP47v/q2ODl4NbW1d7Vysjh5eno58rk2NnFwsnTytPSz8bV0cHCw87N09fo3brAtL3AusHqysXGt8PCr7W+s7mnpK6ur6W2osDAvYDKt7Cyq7e7oqW0tru8ybentsPJ1NfW8ufm4Nvc0svYz9ze1ubl6Nrp4/rh3dn6y8rb7PLg2s/k4OXz49jFwOT3hPz67/n1iOjX1dDl1NvDx+jthPr97OSAg4eO8e7p++uztLe68dDb4YbR6eDc1uOG7tbzgIz8jJDqkY2Vl4j7gID17eiB/oCBg4uLk46KgI+XlYeFh/njg4ujvZuYppaoj4qMkae5p5SWkZCFhIKG/fqFl4/+6/XzqpKdmJqFifWE9Pz9+4X6he3s4NTn5dnviouGmoiSkPyNgefW7oqOp6iQkfDw6Onz9oGPooyFjdvh9IGDg/X48d2Ok4mF/oaF5oDX7e/k7ujx39znydnp59rX0czQ2d/xgIeF9+bPzL+6xNbY1tfo3vzZ4+3bxc2+vs7r/OLy9Pn8+PPe6+H6/czUztHX1ePYveDx8tzp+uvl0dXm7fDf7PLm4N7fx9LLqrjHycLOyczf4Obj9fni5evpg/mAmIqB7uvx7fHw7YXk34Dr5t/h59rU4t7l9/WOhIGEgf2SgeXz7fbl3efM3/nu9v7s9NbQ1sv00svK3OnKzNbTzMvPzuHf3Nvu6sS53eHe39TNtbzSxcXMzc3XzLfJzdTG1tjazOfu6NzO8/eNhoT5+vDt2MzPwOTizsXQyN/b1e/yjoORlaOOi4qEhoSIgYCMgoTyhfD/j4aGkZmUh/3r8vmBg4eSkImD7eyBiYCIk4SEkZOA9fCA6e7y4e3h2tnXzujp+4KDiJKUj42LjoD4jIaE7PT594TtguLv0+jo9PaBhIOCifr/hPiCiYWJjoiHhoeEgoT39aKB9/fnh4b58ur03evi3YOTkJKIgvuLjoCIjI+KkYmDjYKHlJCOkIuV/fuDgoCIhIWHgpL9+oGD7d3a2t7W0ci/wLi7u77C1oH/gfeG+/KWg+Xf8uHf0Pbygv2LiYWHiJCSlZunjp6ipKavraKYkI6ir7GpqZ61mY6OkoaDif6EhY2PjpeKiYP+hu339M7E6vf67+jp4ero4IDh4Nnc1OLn4Prp58bevsXJrr7HwsHN1sjCvtTLu7rEusHP19Pu4uPV38jn4dza0tLMzMW/xdPN0MfH0dLZ29Xm29/L1L+7xca4s6+8sa24xqiztrmwx+DT3uLW2t7axNPLw7+/wb/HysKztbzKzMTY19bg4dng6uvhyt7e5uvq24CMhIGFmaKkmpSWo5iQmIl+eoiWjpWPfYqljoWVoq6Xj7qYlJefkJGMi4iJj4yXl5GYnZmdT1Chm7qplZ6inZCdmaKYiICTlqGoo4KYkJGCgoeOg4V+fXqPjICCf4WAh4uck3h+dX2Bf4etj4aHfYqKf4mUj5SEiJKTkYSKcIJ9eYCFd32Cg5KXd3d/fICAgG9dbHh7gYKBmouHjYqUkYqQi5OQiZCRnImal66YlouzgIWXr7alk3uIh5Cnm5R/epajVZydm7O4brScnpWhkI94eZWYWKKahnlISlFcmZ2huKt4eHx9sIqWml2FoZeOkaJip5WmVmKeVVN8Uk1XWk2NSoCIf3lHhkRESE5TV1RRSFNYWU1PVKKVW2R3h2VeaFtrXFlgZXaEdmxva2ZcXWBitLFdaWGhjJWRbl5nZGZUWJRSjpGPj0uNT4WGgXyVjomYXFxbbWBvcMJuYJ+FklNSYmFSVYuTkZOZmU5bcF9eY5GKjEhISYmVkYReYVhSmFNRh4B/kJWKkIuUgYKReI6ipJmTh3d0eHqJSlJPiXx0eXJ0fZCQhHV6cohwg5OOf42FhZGtrYeRi4+VmpmHm5Wtr4CKgoqQkJ+VeJOdlHmFloqLgIORl5uLlaOfm5mbiY2LcHyKlpCXk5SYlJaIk5eChJCQV6tbcGVdpqKnpKKfnVuPjoCclYyVnIuJk5CVoqJiVlZXUqFiV5uusLyrnKaIk6qdpLaruaWioZW9mI6JmqF+gIuJgYOFgpKNjJCttZKMrqajopWOf4aflZqdm5ibkYCTkZaLk5STgpilo5uNp6pfVlafnpuej5GekrS2oZWclaadmKecXVNfYWxbWlxbX19pYYBqWVuhXau4bF5ZYGhkXa2cpa1aXGJycGZenZlRWE9VX1JWYmVYpJtXnpujm6impq6po7Koq1RTUl9jYWJiY1mtZWJhoamppl6lYqW1na+stbVcXFdXXKqsW6xdYWBfYVtbW1xcXWK9vnxgrquQWVusqay+orqwrWhxa25bU51ZXIBaWllVWVZXZFldZV5aW1lmoapcWFdcVFVVUGClolZYn5GOjpeUlpSPk4+Qj4+NnWO+WqlZpZxnXJuRoJKIfZqUUZhWUk5PVFdZW2BrVV9iZGVtbWZkYGBxeXhwbGN3ZmBiaWBcYa5bWF9jX2lgXl2yX6y4vJ+Tt8S/vLezs7+2toC3sa2sk5aTiJqSlX6aiI6TfpGXmJ2lsKWfmK+jk42UhYaJi46lmZ2RmYKgm5WcmJePkoeEjJuNjoiJlZOVmpGdkJSJlYOAiZGGfX2Je3uJlYCOkpiMna+anJ+UkIyBbXhycXByfX+LkYd5e31/fW96en2HiYuVorCrma2qqambgIBwaWVndn1/dW5ueG1lc2tkY3N+enpxXm+Ld212go94bZh1dH2Fd3l5enZ2e3iBgXqBg3+AQ0KFf5eLe4SNhXiCfIF4aWV+g4iLimyEfn5wcHN+dnt5eneKiXt6d3pwdXiGgWlwbXZ2cHSVfHh0aHVyZ3R8eYJydX19eXB5Znt4c4B+bm1wbnx/Z2t0eoODi3plb3dzdXdyiX96fHqCfHZ/fIiFfoiIjHeEgpeBfXWabW93g4d3dG19e4CSh3toY3yERn98e42PWIt0dW9+cnhmaYODS4eAaVo1NzlCY2Zof3hRV15ikGNma0RbendwcX9Pf2t7P0hvPjtfPDpBSEB2QoB5cW1Df0A+P0JDRkA9N0FDRTk5O29fPUNRXD44PzNBMS4yNEFJQjxAPDgvMDU6bm8+S0VxX29sTUJIRUU5QGM8Y2Robj5zQ21qXVZrYV1uRkI+Sjg/PmQ/N11NXzs7Skk7QWhxcnR4dzxGVERDTXFze0JERH+IgnBNTkM8bD4+aYBleH5yenZ9bXJ/an2Ihnx1a2RlbHKCRU1MiX1wcWlob35/eXF5b4ZoeId+cH91dX+ZmXmFg4iMioRuf3qOkmRvZmpycHx0XHaEgmt4iXt3a253dnlocHlybG52aXF1XWlze21xbG17fYZ/kpmGh4uHTo5LXVJLiIKPkY6MjVF+e4CHhH+LlIiAiYOChX5PRkdJRoRQQWRwb4B6eYx1gpV8eoN3hnZ5g32kgXRtfodpbXd3c3FtaHhxb3GJiWZhgX9+f3JpWF1xZmVoZmVsYU1dYGZbZmttZH2GhHhlfnpHPT1ydnl6bGl0Y4ODbmhwaXx1bYB6TERQT1lGQUE+QEJNQ4BLOzpcPGh8TkE9Q0pFP3JncHg8Oz9KSEI+ZWU5QTtBSz0/Skw/bWc9aWdtZ3N0dn94c4Z9gT46Nz4/Ojg5PDNlQj8/XWJiYjtlQ2x/ZHp4f3o/Pjw8QnF1P3Q/QUBBQj0+PUFBQUR+elNCeHxzTE+XkY6Tc3pvaUVOSk1EQXpISoBFRUZBR0NASz9BSURBRURTeoFGQ0NKRUZGQlKFgkhMioSEiJONj4d+fnd0cnBpdEuJP3U/cGxMQWhgb2dkWXx2QnhGQj09P0FBQUNMNT0/Pz5ERT08ODhFS0tFQj1QPzg3Pzg1Pms5NT5CPkxJSUqJR3Z9e1dMcoB+e3V2doJ6eoB8eXp9bXZ5c4uBhGyIb3N0XWlqZGNqdGhiXnRrXVxjWFlaW1prWlxSYVR3eXeBfnhrbGFaYnNoa2VlcXJ3eGx6bHFjcmdpc31zbnB6amZ0fmVubmxgcYV4fn9vbW9sX3FwbW5zd3iHjYJ0cnJ4dWd1d3mAfnl+iY6GcIKBg4V6ZbR6gnv/eot6AXuFegF7i3oBe4R6hHuNegF7hnoHe3p6ent7eoh7B3p7enp6e3qPe4J6mHsFenp7e3uEeod7Anp7hHoDe3p7iHqHewZ6e3t6enqGe4Z6hnsGenp6e3t7hHqEewN6e3uXeoN713oCe3qEe4d6AXuOeoV7A3p7e8l6g3uTepB7BHp7enqHe4R6h3uCeop7A3p6e416insEent7e4R6A3t6e4d6hXsEenp7eox7CXp6e3t6enp7e4h6hnsBepJ7gnqJewR6ent7kHoJe3p7ent6ent7iHoCe3qjewF6iXsCenv/epB6AgIEAIDN1dHW3uLY2uXu58PVz8K+rsrDzOHYzMzMvc3J0dvv89/R2Prh2uLh7Nrd3c/GzOL3g4L6/vrj4tTi6f/s6+vl29Lm3efo/PP28tPh49DU6OjVxtTTyb3Uyr7C1rzWw8PT3Njiy9jOvqzd29vVwNC5trSmqbG5urzBsLi8v9zi4oDNs73BxLC4v7e5vMLM4t3o38/N0dXs5ejh1tHi3dnMx83Zy9Di1+HVydbh6/nv596A7vOKg4Pw1dzZ4dTAx9v6+/2G/Ib97ICFhYODgPLv2u7e44CHkf379eWF+fPm34He1uTf6O/6l/vt8PWngYfl2eDkyc7h4/H1i4ya7OHi2YD47f7xiI/8+vLulpaUjZOTiYCWkpekkI2NjauHiZSZmJOkqZuVoPPygISJi5KYqqWNi4GSlpSA6O6GkqCsnZKF6POKk4P58/vn6O7w5+bk9ISDg4WB2O6MhP75jpCSm5SJmqCnnu7U6/DuhIKG6Nbt6uyAgoqS9vONgvr26e/y7ID+//75g4HT2cXb2OTj0svo5Nzg9vXx9vWCg+zr59XAvLG5xdrjxufl7+TW1dS9xcHNy9nm0+r08Pr019jM7NfEyMHN1ObZzdXV0b3K2tvb7oCD8u3d4e/s/vvm9N3Iv8K4yNHv3ubt/ffq9fvu5Obv8NzY3d/ayOfU59vb6uXj54D+5OPx7u706+Po5uLm7++N5vXV0deBhe/W2s/d3ufm9v2Cj+2/zMbG2N3k3sjr2sjKxtrc3+ro0cbM0MHPy8/cyMbF2t7Tva2/wcXMyMHJ49nW1+T97Njb6PHL7uqDgYL1+uXk1MXQ3vPY0IHv2d/y6fL/gomRnJSYl4aDgObs4oCAiImB/P6UgoCFi4KKmof66vCMnYufk4fn/PaC+omSkfz6jIuOipSIgfr2/Ojg5Nnb0OL7/PqFlIePi5iEi/38kPv/io/2/oD//ODr1uPx+fuDg/z6goGH/v2BgYOC8oeNjoH3g4GMhPP27/n0/PuBgf+A/oP17/WPivuLjIeIiYCBhYOCgYKUhIaEg4qSkoyE6oCPi4L87Of2hJqPnYGL/N/d393b6IaBzMHA2NvY3+Pvh//66/L65Ony8NnhhvLigoOFiIPo9IuBi4uSlpWXlIyekIyPjo+drKeikp2du5akpJ2aiIeKgfn0/PmCgoOE9uz0s6uOhIONjY6Bgvv/9IDR6tPP2unc4engz8G7ubTQsby/v6uu1sXO1uPg473Sy9TFwsLO0ce9xc+1ysrDxcHBuMDGv7/U0b7P2NrQ1MPO4ePS1NLOyMarr7S2wbituba5tq6svLjT1uLY7vXr0ujhzcrIvLvSy9PXu8Daw9TSzcnKz9TZ4NXc1OnK3NzR14BwdXJ8iJWLkaCup4qln5OOf5GGj6GYj46LeYqKjpyxsJ6QmbqooqypspiWkYZ7e5GjUU+WkZeOj4ibm7anrbS0rKOwm5uZqqito4KMin2Cl5iLfYmEgXiNfnF4i3WNfnuFjYyThZKIfm+Zl5WOf5SAhoyEiYqNi4qQf4SCgpWSjYB4Z3mBhHd+hXt5eoKHlZCZk4iGh4OUlZWPjIukqKOSkpGYhX+Oh4yBe4aSmqKinZlcpqpgV1SPeHd4hn9udYSaoJxYpWC4smJjX1hWU5eTg5GChlFVXJeVjodcrK6pol6fmZ6ap6WeZpaPl55vV1+WiJWTeoCPiZOZWFZig3Z3dICMhpSLVFuUkYiLZWNkXWBeVEZYW2NzZmhoaIFWWGBnZ2Rzdmxmc7G3YWNnZWhsendhYVliZ2ZRiIdPXmZrZmNZjpVXWk6QiJB8h4+Xlp2Xp15hYWNhm6tlXK2kYWBdZV1TZGt0b5d7jY2SWV1lqJScjIFFSVFbiI1aUpaShYWIhICQk5eVUleDiHiKh5KPgnyWkIeDlZGFiIpKTHt7eHJtcm57eXx/Ynl6iYeIjIx4fHqIgYaPcX6Gho+ThIp6mot+g36Fh5iMeHx/e2p5jIyPpFpcpaGSkJ+bqqqXo4+CfoB9i4+ikZGNmpOBjpWNiJGcpqCgpaiijJ+MnY+RopaQlICkjoqalJOWjIyVmpmVm5lej6GFhIpbXqiTlouVkZmSpbNhbrWQnJaUo6WooomklYOAgJSboq2qk4eNmIeamZullIqMpa2hlIqXmZqbkYuQo5WWjZyyoI2aqbiOr6FYVFOWnpuim5ejrr+npWa5o52nmZWaTlVdZ1xjZVtdXaGploBXXFhSoq1tXFpZXFRabF6lmJtdal11al+WqqhXp11lZKKmYV1gXWBTUpucrqelqKSfkZyspJxTYVZbW2paX7S7Z7C4YmOip1SqsZqomqutqqlUUqmnVVhdq7JeXV9dqWFjZVmrXV9tZrayo6GboKVdXbhbtWDAv8ZwZ7ZiW1dXWoBWW1hWVFRoXF5aWVtfXFZPjlRkYVy0qKOsXGlhZlFcpY2Oko+NnV5chIuMpaWdnZmcWqSdlJafjZifloGTYJ+PU1NWWFaOl1lQV1VXXF9gXlZqYl9hY2Frc29rW2JfdFtqbGtoV1leVqeqsbJhXmBhqKCohIRwaWZra2lZW7S2t4CXqpmWlZ6NkpWTiIOBioafhpeVloaIraKnq7qztI2gl5qLipGbopuMj5J5gIODioqRj5SYlY2eloSSk5KJj4GIkpOCg4yOipWEhpCSk4yEh4yZkIWBi4KVk5+VpKeWe4eBcnN5dneMhYiJdHqQgpCEe3p4doOIi4qXk6mKl497fYBkZ2ZqcnhscHqBeFpxdG5rYnRncYBza2xsYG9pa3eJiHVmbpKCfomLmYOFgnlvbX2LRUJ6dnxzeGt/hJyQlJyalo2ah4yHlY+Tj3GDg3N3ioh+c4KCfHSLhnl6jHODcm53gH+Heol+cmGGiIh9bYJscHhudX6Af3+Bb3d2d4yNioB0YXB3e2tvdXBwd4GFlo2QgnVtcHGDgoN7dHKHhoR6fIOLe3mJgoR5cHiBh4uLg3pJe35LSk+Nc3NzfXRiZ3aLjotOj1GThkpLSEREQ3+AdYd7d0VHS3Nwals/bm1paEVycnx1fHdvS2dib3hVRVGFeIWEZ2ZwZ2tsQURQb2pva4CEfId9TVSFfnJsT0xLREdDOy88PUJOQkFBP0syMDc7OTM+QTk1Q2BjNzk8Ojw/S0s9QDpHTVBAaGM3QUdOSEI6XGQ/Qzxwa3lla3V7dnx2fENAPDozQ1Q5N2hgP0E/SEM4SVFWTW1YaWtrP0JIe29+dnE/Q0xUentNRHx4bnN0dICEg4N/RUlscWd+fouEcmuDenJxhYN+gYNHSnl7enRscGt0eIWGY3ZyfXp3fYV1fn2JgIWKb3yCfIaCcHVlgnRmamNqantxYGRoaFdmeXZ3iElJfnhsa3ZvgIR4iHduaWxkbW2BcHJ4ioZ2ho+IgISNkYGAgoaEcYR5iHp+kYWDiYCci42cmJmYiYWIg355fX5Ueo5uZmRDR31wfHqIhYt4foNGVIVsgICBkZCQjHSPf21sa3l0d4CAbmdtdmd6dXWAbmZjeX1wYVNgY2RoYFRYbF9eWmyGeW15hIteeGc9Oz1yfXqAd257g5J1bEuCbXB9dXmCQkhNUEdLS0FBQWt1ZYA+QjoxYGlNPj0/Qjk9TT9pXmNASj5RTEJod3Q9cEJKSG9wSEdKRkxAP3JvgHd2fHp0aHSGeG06Rjg8OkY4PnF4SHB3QUJhZDNseGR1ZXd8enY7Onh4P0BFdntDQEJBbUNJS0F+RENPR3t6b3V6hIlMSo9EfUB5dHtJRXZFREJERYA+Q0E9PTxNQEI+PUNKSkdAZz1LSEKBdXF6RVNLUURPkoSEh4eBjVdRbXBwiIl+e3V1RHdxZ2x2ZXB2blppRnVkPz9ERkNob0M4Pjs8Pz8/PDNEPTg4ODdCSEdENj48UDdCRENCNjc7MmBkbnZHRktKd21rU1JEPj1GR0Y8Pnl6fIBZcGRhaXpweYKCeHJwdm+EZ3BsZVNQdWhucoJ9f1xvam1aVVxkZF9XXmpaaWxsdG5tZGVkXltsaV9xenlzdmZueXtqbXR6dX9vcnx8f3d0fXR2bWNibml+eHxufYiBboOEdHR3bm6HgoeIb3OId4J6cnN0cHh8fneBeo9wgH1rb7F6gnv9egZ7enp7e3uMegV7ent6eoZ7hnqDe4R6AXuEegF7h3oBe4R6g3uKeoN7iHqCe4R6nHuCeo97gnqHewV6ent7e4t6hXsGenp7e3p6inuFeoN7hXqEewR6ent7inqCe5J6gnu2eoJ7vXoBe4V6gnuKeoJ7unqDe4t6AXuHeop7g3qEe4J6iXuDeoZ7Cnp6ent6e3t7enqHe416iHsKenp7enp7e3p6e4l6CXt7enp7e3t6eoR7AXqEewF6hHuHegx7e3p7ent6enp7e3qVewF6hHuEeoZ7h3qCe4l6AXuLegN7enqFe4J6oXuEeoR7g3qKe/96hHoCAgQAgM7Yzd3U1d7YvPPHu7XAvr3FubW4z9XOwbq60sTQ0M3k0szU2trq6f7859PQ2eOA5YGJiP/55uni7uzr7N3axdTV/ebOx8rQ8OiC79nf+N3y2Ozc29DVwtHCxsvJ0NDQ6O3w5fLaz8zLvb3N1M/S0M/Ux7yttrW+srioq73H28zXgNvb0NLW0sDIyM7Q7unz8+DZ2eLV1e/v4OHP1NjEx9HW4+fa3t3JxcDMys3p3t/13uru7/aA8YXu99jbz8zE1u3o3+TSwfbsxefkjouBhIiF8+LPxMjSztri54WAj/2IiPTe4fSKg9ru9OvQht+CiIyA7f7e9oXAuorcgpSHiY+SgIf2hIDv+/b13MvT0OmGlbSc9JGEn8CYmvTc/uqMmpWVqbOXm5Ghjv379v/9iLGKhfTI4v7k9uvu+oCKnZOL/PuMm6qitKWKj4WAgPuD8oCEgYOBgvv+9pGHk5eI/v+Nnvj4gYT88s7W793R/OjohuGAi42Tiv306PHv+en0gIGBgIKDgISSgdLRzNTh2Orj19zk39/d3uWBgIiDhIGA9ejYx8myzoTegf778dXSzcfUzcXq3uXV2+Tf2tDHurPV1ruwtLjTx8bDytHezs7hgdzn7ozshfDk5efp1M/i67jAy7fGuczN4/P44vPs7fLk7+Ls5d/S2NTT4+Ds4evi3N7LgNzd3Pf67+374+v8/d7Y2u/X8vT58PLV6+vZ19TX5ufl8u/u3snxzsLI5OvTyO7Ry+Li4erziIjy1t7QyMvGx8fCxsHH0c7ZxM/Q3NDU/f3f4IHhz+PX6Ofd29vk4fWDg4SC/Pb33OPb5c/N5Ob09/Tw7Pb2gP+KgoiXioaAgor8gNzr/o2BgoHygYeMk4iBhYSAiZyblIyCgOzqhoH9iJWPh4T/+vDs6uPz9e/b3s/X1Obh6PWIhYmK/YyIhYyTj42Jgvzr9O79/uzq5/fk5PXr3Obx9YCCge6K+of+gfvp8+bj8/Py+Ojw+v/z//D/7/j9hYaC94D5lYH1g4ePiYOIgIyFh4qDhob6hP2IgYuI/vDwgvb+6fKA3u6B6/z9gof65vCC8YD/hITGyODP4vf33dXn5Ofd49fS6oH3io2UhP/+ivXn/f2LiISDk4+NlZmij4+HmJOcp4qMi5mLkIWJh4yMlY2Lkf6JhIiD74H76oT94fPV8N7e/fX7gfmEgIf3gPXu3drm3tXW+9XazcW5yMLCxLa5wL21x7+z4M/PzNjBsr66v8XTyK+6zNLOyczOw8PLzNHGw9DQwcu/vMLJvLrH2dbO1tS2vLSztKy7u7W9sbK+t7e2ysDQyN/t2t7j2uLYw7a60M3b2NjCzMyy2c7BwbnExM7c2cPX9uDi8ubigHJ9dImJj5uchLuRiYmTkJSaiYGBl5qViYB7kYSJhYWakIqTlJeknq6xmoOFkZpYj1BWVJqSipuQmZWUnJmcipiWu6WMgIuUqJpWloiWrZGihJSBgn+Fdox6e4B7enZ3iY+Uj6WTjoqOe3WChoGHjo6bmI+DiYaMg4h5eoeNlXh+gH+CfoOOjHyCgIKDoJ6ko5SSkJeJgJWaiYmEkJeMj5CUnJiKiY1+enaFeXyWiIyplp2dl5tUm1SGjnuKg4N4gJKLg4yCeLCpiKSfZ2BQT1VPjYp9dHN3bnd+glRSZ7VnZLCblahnXI2cn52GXpFZWFhOiZh/jU1kcVNzTF5QUVBXgEl+SUl+jZGPfHR7dY1WZXdihF1RcIx1fLqkuqJkbWNicHhjZl9vY7G2t7u1XnpcV5p6lrOfpI2NlU1XZl9XnaJYYmpjbGdOVU9PT5tVmVNaWVxgZMTCtGtfaWlbqaVhb6SjWVylnHl4lI+RuqeiX4dMUFBXUpaRi5OSl4aTT05NgE5LSUxZUXt9fIWSjp6ShouLg4J8enZDQ0pJRURCgoZ8dXxmdU10SJWZlYWHioCHfHeSgIdzeH6ChIOEfm+OkHVpb2+GgX91d3l/cnOMVIiRmFyVVJWIkJGUgH2LmHJ/jISQgoyJlZychpWMj5yPnJahoaGWmJSOkYuViJmWkZeIgJKOhJyWj4iZiY2jo4uNj5yJm5ilm6GMnZ2QjouPnJ6jsqytpZW3loqOpa6XiaaLhJGXl56mYWCiiJOQkZWZl5aQjYOMl5epnaShppWNsrGSkVyZjqSdrbasq6aml51RUE1OoKGun62pua+tv7q6rqeglp6ZUaVcVFpnW1tXWWWygJagpV9YXF60YF9eZ1lTWlhUYHBsZ2BXV5uVWlWmWmdeWFetrauopJilopyTmY+fn6iamJ1XUlRVnFlWU1lfWF5bVaaSk46coZuiorGnqK6ilpmcoVVRVZdcqWC3Yb6psqGaqK2stKmzubqorJ2sna2wXV5cuWO8c1ypW19lXllagF9XWF1YXF+0X7JfVlpVl4qNVKGuoadak51Uk56cUVWZjJVTllGlV1l7h6ScsMK/oo+bmpqZnpWHoVmgXF9gUqClYaqbqahcVU5OXV1YWl1nW15WZWNsc1pdWWZbX1daV1hXX1ZUXZtbWWRgrWO0omGxl6qXtqiow7nBY7ZjXmK2gK+jl5ijmZCUrI+WjoeGlpCUmo2QmJiRoJOMt6mmmqWNen5+iIuXkXh9j4+CfoWLiYqanqaclZiSiZGGgIKJe3N4jYuNmpl9hYeJjoeNiIOEe4SRhoJ8ioSRj6KomJaSh4uAcGhxhoiUjYpzfHxoiXltdG52fIONkHyOrJWXppCIgGFrZHNwcnl6YY5jWVdjZWp0Zl1hcnRzaWNjeGhrZ2d7b2t2fH6KhZiZhXFve4JJc0JHRn11a3p2goOFi4OIdoGDppF6cXV7joBMhn6JnoWRdYZ3enyAb4R6f4ODhHt7iIiMhJSEf3t+bmx+gn18fHp/e3Jqc3Z+dXhqbXd7h3N2gHl6dXmCfmtycHd7lpKalIF7d3twbYOJenlxeoFxdHqAi4h6eX5ybGp5b3SJd3mLdXx+fYlLilCDjHN8c3JqdouEeoFyY5KIYnZxUEtBR0xLiYl5cW1tYWZoYz45SHpISYFyd4hPRGZxbnBdRW9ISk9IgJN0eEJgYj9SPU5FSEpRgEZ5Rkd5ioqGcWBiW2tBSjpEWkE1TWJMUm5bc1k8RDk4REk3OzRDOmVpamtlN086OWlUcI15g25qbDc9R0I8Z2xASU9KU1E9SEA+QYFIg0dJRUVCPnNwZEI7RUY6ampDUXJ1Q0d/eFZWcGNfhHR2SWc9Q0RKRX18dn9+gnB9REJDgERCP0JORmdoanaGhJOEfIB8d3ZydHRCQ0lIR0ZEhomCeX5rflF5SI2JgWxxenmGfnqXiIt4fIB8eHBtaFx8gmZaXV52bmxmaGx0Zmh+R3J5fEx1RHltc3F1ZWh5iWNuem12ZG5reoaKdoV+f46BjYmTkJCEiISAhYKJeomFf4V6gIyKhaGblIyTgYKQjXN0eIZzioWMfHtien54gYKEjX96fnR2cWuWfHF1h455a4hvaHh5cnF0RkZ2ZXFvb3Z1cnVwbmNobWh0Y2xrc2dlhoZmZENoXnJtgYqAfXR0Z3E9PkBCjYyWf4l9h3RrfHJzcnBydIF/RotKQkZQREE7PkyAgGRqZzw1Nzx1QkZGTUA6Pjs0PU1KSUM+QnNtRj52QUtEPTx7gH56enODfHdudWp5e4N1cnZAOjw8Yjs4NDo/O0A+OXBfYl1oa2Nrb4R4eoNzZGdpcD07PmtEdER7QYBwfnZ0hIiFiHqAhYV2gnmMgo6STElBd0B4TzxuPkJJQ0FDgEU/QEM/REV7RHxFQEhHgXd3RoGKd4BHbXZDcXt9Q0eAdXtGfkSMTE1ibIV7jp2VdmRvb29rc2pedkN2RkhIPXh9TIBzgYNKQzw5RUI8PT9IOz42Qj9FSTQ3NUI6PzY7ODk7Qzw7QF45NkA+a0aBcUd5Xm9UbV1ZdXJ5P3RDP0N5gHFsZWl4dHF4mH2If3l1hHt7emllamRcaF5YhHdyanRgU1dUYmZsZU5UaHFpZWtyaWZxbnFmZW9tbHlycHV4a2RneHBteH9mcW90fXh7dnR8am52a2ppeXR6bHZ7cXV9f4uFc2hofn+Mh4VveXpmhXdrcWpucHd/e2RyjHZ4iHl0rnoFe3p7e3uWegF76noDe3p7k3qGe4p6Bnt7e3p7e4R6gnuFegJ7eoR7hHqEewF6h3sDent7iXoFe3t8e3qGe4R6i3uFeoR7iXqFe4J6i3sDent6hnuDeoV7CHp6e3t6ent7inoCe3qFe4h6iXuQeod7h3oDe3p7pnoHe3p6ent6e+F6gnuaegF7jHqEe5J6Ant6iXuEeoR7AXqQewV6ent7eoV7knqEewF6iXuSegl7e3t6e3p7enuUegl7e3t6e3p7e3qNewN6e3qEewR6enp7hHoSe3p6e3p6ent7enp6e3p7ent7kXoCe3qEewN6enuEeqB7AXqEewV6e3p6e4p6BXt6e3t7/3qCegICBACAxc2+t7u3xc2+2b/PzsS3zMfQxdHT29HR08/Tz8jdxb/BusrL3fLu9Onr3dLB5ejI+vb+6ubw5Nrx4t7U59G3z+Hi4MnD2uHr79/n4Nrh3NbG2t7i6+LK09/Bvc3Mx9HY6erm6Obi5MnJwsXe3tXu3+fb1MKrsbmvur2xusfNuMuA1uXb/eLUvdbLzMTPxOTq2Nba3ebX3uHW4Nrf2NPKyMLBy9DZ0tPUxM/X4t/l5dfd4dnQzNTg8fn05v+H/urg18vL3tLMw8XZ6PPj9eHU8d3g69vZ392Jo5qM+N3a1tO/xubRycrO3dvm29HI/ezcy8vK4uPMzuThgPGHiIiQgomA+4Tm5urs5tni/YiEk5r1hZaH7LiB+Nni/PaLkY+crYqLk4qIh5+dvNWjqI+BiYiD/+nf2tHc6+mDhYKAifnl9Pn2jvSbp5Sjjvje54Xd4Nv/jYHp//+CiIGOgoLl4N3X3ejs6oT/69/j2M7E2db9gfCDhYeOnIH3h+7y7JSLj+SA7vX4+YDly9zQ39TQwdzXwcfMzOWXhIb0loL99e7j18/JxsTg/tnw3dnQwru92NOqrt7Z/47m7drexc2/v8PBsra53NfRxL7Jv7+8udrW1NSAgYGA6dbVztjd3PTQ0svd2vLX7/fk6N/65OyK6tzV193e49rC1uDv6+/p7ufny9iA29vk6eTj7fHn8uTr9+n7/9vi4ej3hP/+8ebu8Nzw9+vw7+XgydnWzcvV88fW5dLI0tnN2fr16dHc2ubV5d7Zx87BsLHPw9LLzNTVz7/s99vq8eHp49Hc4r3W7O3v+fLv+fLjge/g1Ofe3eb36O7l5v746NXYyt7z95CYj42TjICA4++Bjv2VkO/5gYGA9oKHivKHiZKVmobq6+jzhPz7/Yrt9fjm2eDfyNGChtvu7fHi7ezp54L+g4iBiYP7gYqCgoiG/IOEhP72goaA9e7r84SBh6aA+uva64v59O3Y1tbq3eHe3NHd4fX+hYP7homD8P6GhoPwhoqWjf+F9oX7+oOAgun6+4D29fvn7OLq6+3t5dPq5Obp9ury4+jy8/CEhYPwhIL6+fqFgOG+1crMztPGzdns5dzp4ufd4PiLkoLl69zj2Mns/ZOOkY2Eh4+fnpSSjIydjoKJiYmKiomSkZONiJaamomKjZHs/YjX3cna7o2H9eTf9ojs9Pv39/vx9+WA9Pn2gIfw29HZ2NnH3ebR0dy/yszPzMjQvbG7tL7HwMPK59LE5MvJvMjM183Qyb/Axry1tsW7vMTF377B2LXO0tfb0tTPzMm4tsC/uL/JwbOgpq+itMbGxsrF2eXa1ubt/4Ts0rXXzNTI2dXI08/ayMW1qrG3yMnaz9fg6Pfw0MOAhot/f4iIkpyOpo6epqGTpZ2WhY2KkYuIiIWJh4WaiISJh5OLmKihpqCdkYt9mpV4p56nm5Ocno+ilpCRpo16j6SjoYqAj5GaloWRj5GimJR6i4yMj4t7hZJwbHx4cX99i42JjI+MkH58c3SLhYChjp+Zl4p3foJ9g4h5eYB+ZG+AfpGHp5qNeJGJioORhqSqmJaUm5+LkJSBiIaIjJCKi4qIj4yPio2Nf4yJk5aVl5GWmpSIiImPmJyRg5tWpJSIhHh3i4OBeoKYn6OUnoV8l359hnd5f31UZl9Tk4OMlZiEiqSMg4F/jJCclYyBtqSKeHZ0iYlubYSBTYxRUE5WRU+AiU9/i4+RjXqCmFJNXmOMUF1RjHNUo46euK9jZ15mclRVYVxcW2xqgJZ3e2hdZWFdrqCTjoaLkYhOUE5OV5WIlpigWpBbZVVjUo95gVSBg4OkYV2owL9gYVhgVliemZycmp2bmlajiX+JlpWPnpKtT4NHRUlTYkyYV5GXkWFaW4gokZGWmU+NeId+jYGAcouKeHR4coNgTEp2VUGEgHZzbG1yb2h6lHB9c4R8gIekmnFxlIWhWHuHe4R1g317goFyc3aWj42BeIF6e39/m5mRg1JNTU2IeHx4gIKEmHV9eo6Npoqbo4mHe5WBkl+dlI+TmqCjkXmIi5GKjYualpZ9hoaFiY6GiJGWi5SRkpiPn6SKjouVnlaoppiVn6GRoKWhqq2qopOgnZOQmaqKgJGdjYyPoZSguLakjI6NmI2eo6CXpJmDhaCPm5qYoqGdg7C2lZ+wnq+to6y3j6azraCgl5OYk5BWoZ+hsK+1vMa1taqnt66gjo6Dkp2dXmdhYmNeUomUVGOub26qtVlSUJZVXF6cYV9lam9bn56Vm1iko6Zel6WrnJiioIaVWFuMgJ+fqKGkoZiTVqFRV1JVUZxTW1lXWliaTk1NlJlaYF+wraakXVtcdlaqm5Wqbb64sqOhn7atraemmqaru7ZeXJ5YXFebn1RTV6lhZG9lrlmnYLKuXF+lsq5Zq7G7q66gpJuYlIqAm5acqrWstJ6bn5WNUVRRkFVQmpabVFWTfJuXgJqYm4iLkaSbmKihpJ2drmFnVpqjlZ+QgpulX1hbW1NUWWVkX15bXmxgV1xbXV1dW2FgZF9aY2ZkU1ZZYJq8Z5utk56zaWGwpqa6bLKqsq2prq21qK2ppVddoYuMkYqPf5GajY2agY+PkpGTm4mDko2ToZqZlaiTiZuRlYuSk5WEVIeBfIWPkJKVn46Mj5OrjouadIyJh4qIkY6PinyEk5KLiY2KgW95h3SEk4iGiYuZm5GOmpynVJeFcpWRk4mXl4iRh416dm9pb3aBe4R6goqcsq6RhYBqdGlmbmtyfGx7ZG90bWV1cG5fa2t0b2xua29nZHdkXmRjcW58ioOKg4N5cmmGgmOQhYt7cXh4dY+HhYSWgm1+kpWTf3aChYmIeIaDiZePg2t7e32Fg3eGlXh1h4V9hoOPj42PkY+QeXZsb4N9dIl7iICAeWlxenN2eGtqcXReaYB4i4OgkoFne3JxanZth4l2cW9zem12fm94eX5+fXZ7e3uAfoB7fHluf32LiICDdn2Cf3Z4e3+KjYd6jU6RhHt8d3eJfXVnZnJ2e3B+cXCLe36LfX19ckpYTkJpVl5mbGJsiHVxaWZrYmllXFmNgnVwc3KJhGdhb2c9bUJEQ0w/SIB6RnF3e395Zm54QDpGSV01QDpeRTlrVmB3b0BAQEZQMTA6NTU0RURQX09UQjc6ODhxb2xuanN3bj8/PTxIdGVvdXhHbktWSFhHe2x2THZ2co5RR3GAfD5AOT83OWNiamttcXJxQXpjWWJpaWd3b4pAaTk5OkFNPHlIeoB7UExKb4B7f4eMSIBqdnCAe312jIt6cXBod1hIR3lbQ4eFfX14eXx5d4qie4Nxc21jZHWXlG9vloaeV3yDdXtodW5rcG5hYmN/eHRoXmhhYGFifXd0a0M/QT9xZWtkcHV3i2puaXpziGl7gnFxaYJwfVOLg32DiIuShG+Ch42GhoGPiolzfoCEhImOhYiPjYGKhoSIfYqMdXx6f4JGhIZ7fY6Tg5SOf358d3Jsg4eCfoWScXR+bWhueGlwhYFzYGlqeW1+f3pyfHJdXXVjbGdncnFvW4CEY2d0ZXZ1cHmBW253dXB3dXiGg4BOjYR8hnx3d4BsbWpshYh+cHZqdH14SU1FRUlJP4BmcD1CbEZEZndBPz1yQkVEZUA/RUdMPWpvaXA/c3JxRGV1gnp3gIBpdkhJan56hICCfHRuPnE5Pjc9OWc3PDs4PDxmNzc2YmE8QkF9e3x/R0REXD58bmR2TYF6dmdsbIeAh4aCeH6Aj4tIRn5LUEuCgkQ+PWxCSFNJez91Rn+CRIBEcnt7QXp7hXuAeIGChYqEeI2GhYePhop1eYB3dkVFQ3hFQX95f0hIeWaDf4SAfGhoa3twbXx5fHRwfUhOQHJ/dYJwX3SCS0RFRDw6PEZEQkE9P0tCODs6Ozs7Oj88Qjs2REVFNzg6PlV0RF5xXWl4Rj1nW1p1RmxvdXRyeXR3Y4BwcXE+Rntsb3x5gHCBiHt6iG53b3BrZ25eWmhjaXZwcXSEbGR4Z2lhaW55bnFqZ2ttZ2Rkb2VmbnSReXyPaH57eXpxdnZ9eG10gH92dHd3cVtjbV9vfnp7d2t2eHFseoSVToh2YomEiHuGgXJ+eINxcGpjZ213cnttcG9/jopvZ/96vHoBe5p6hHueegJ7eoZ7Anp7iHqEewd6e3t7ent7hXqWe4h6hXuFegJ7eoV7BHp6enuEegV7e3p6eoZ7iHoBe4p6Ant6hnsIent6enp7e3uFegF7j3oGe3t7ent7mnoBe5t6hHuXegF7qXoBe816AXuVeod7EXp6e3t6e3t6ent7e3p7e3t6hnuEegV7enp6e4l6gnuJegJ7eoV7AXqGewl6e3t7enp7e3uEeoV7hHoBe5B6DHt7ent7e3p6e3t7eoR7DHp7ent6ent7enp6e5h6C3t7e3p7e3p6ent7k3qDe4h6onsDenp7hXqCe4R6AXuMeoJ73HoBe556AgIEAIDMzdnJwMnBwLa+xanSz9nYy9LN4Nvm1N3rycLa4Na9xr7Dy+fc8Nzi0t/h1+ni7s/k4t/u5uLazdbh6trn0sXEx+DOy9fgzdTq6PLUzdPKu8LQwtHG3uXN1uTTvr+qusbH1dnQ0sjKydbb0tnv6YHjzsPOwrG3sru1ysXBxLzC4oDUxu/pwbXEt7nPxMfHytS1vMrR3N7Z3enn4d7f3czH1NPC0NfazeHGz6a/0ef0ztHZ1uHk2/z05eeAkPPu6eXSvrnErcDS1NHZz97m+N/R2dvi487t+P2BifXtgu/j18z0/dnh2e/li4Tqi5KRgu/929nb3OXe2dziyOeAjY78jYCXk4ju3M/b5/z68omGroKH5uyNhPyAif7/hIaGkIiPlJKPjJGF/Pvt9/nziInzhIf7hIOJ8/GEoKuvmYDl4dXzi5Xu29fv8vLr1trZy9zc7I2J+eDY94Hy8Ofa19HQvtzH0r++6Yjw5dTXx8PT3NL1g4SKhYKK8I+Bgvz84Off7oDczt3h3Njx3ePc1d7L1+bZ3NzliIbrgfb47/b/69Pq1NTQx8Le2uHY0tDOztjSw7C80cnQ2u708v3X2tnA28zM1vvn6YfMzNLPw8TAxczU6oGB7eP339Hh6dvu8OXkztXmztf+7P6Hj4fw6+P89NzW2efo08rr+/Dq7eqA0svB1YDd7fbygPGG5PLy6drb5vDy1NLw5vKD/fns5Onf4unf6fj7+ufl683My9Ha39TX29TIxMTM0NbAusO6q67N0dbe2NbQs8K/5M7g3t7a2vfx3cLT4PXj4uvo3drf4OLo4vnu8ObhyLi4s8na2e+DgYmUh/3t493b4Of2iYSRoY2PgYDr/IWMlIr2g/n5/ID3jpP06fvmiYH/iIDl6uOEi4SA+vL3+e/98vzk3ujm5ID8gPjp49nU/PeD7u/15PaB9Oz6hfHq7/zx8t7X6eLX1Ob3gYCA9P/y/er5guXi5dTOydbS3Nzm4tzX8oGGjYKLioyGhYuHh4uSmI+Tk5uTgYD/9ID29ujY8fPt1ebS39Tf49vg097mx+Li7Pv37/r/goGJiIKIioOFg4KJ5Ni9r83CtMrT2d/T09TT4vODhYuA5M7m4+/eyILwgo+oj5SknqWqo5SQkI79hYaPjoqJhZeKioT/h56bk5SiipGJ9dzj6+uBiYT68oeJ/IeCiYb69fPu84D+hYiM/ePDxsnO2NfZ3eHv49nVz8/S1dTSwrq9s7nAwsDHxruyuL+4vMrMycLUvs3KvbW/t76/w7nMxMLFtrq3ycTBv8m4tc/KxMjCw8TFx66lrcHIydLSwNLX083Ezc3S3MbJxfPm3tLl4dvevcXPybmztLbCzNXN3PeA5Obuz4CKh5SQi5SRj4OMlHmgnqKilZGLmJOajpCaf3uOk5F+hYOJkKOYo4mLhI+Qh5uVmYCWlJGmmJWYho2SopWmkoqHiKCLgoiRgIKMi5mGjJiPgIaMfoV5j457gox8b3Jhcnh0g4h/gXl8foiFeXeIhFCKfXyKhXl9eIB4iX5zc2htioB/dp+ceXB4bXCEeYOEiJV9gYaSj4iBf4KAf3+Pj4OBjox+h42ViaCFkF5xfo6SeH+Ih5icjqObjoZNXJSTlZeIdXGCaHyRlI2UiI+aoYhzeHiBhHCLlJVKT4uHT5GQi4Wrr42Qh5GGU1OOXGJjVZWlhIOChIV/e36AaH5IU1KFUoBcXVeSg3aAhpydklhXd1JWgoFWTZJQWqyxXlxbYVJXXF5eYGVdqqGUoaqqX2GmWluoXVtjqZxTZG5zYk2If3aZWGOPf3OFhH54a3J2a3l5kWBcqJGFo1ibm5KIg4WEeJWBkIGAp2SjloyShYCKh4CXUVJVUE9VjFxPT5WVf4SAkYB9eIaGiYqckZCMh5B9hJWOh4uRWFeNS4aEdXyHeW2BcXZ1b2Z7d392c3aAhpaRhXN6iHqBh5aalaKDj5mDnIeDiauSkluBg4+PgISBgoSIlFFOh3mLenKCi4CRko2UiY2gh4umkpxSV1OLjpCvqpeTmKKii4Obn4+GhYpTgXx1g4CAjpaLT5FWi5udmY2NlqGjgoCVj5dSnp2QjpWOl5+Soba2tqijqI2Pj5WbqJugoqKSlZGbmaGIf4N8a22QkJefnJ6biZeSs52hm5ONhZ6binKPnLqoqbGzpqWtrKaonaqeoJ6noZuinKeyqrFgW2BrXaiclZOUmaK1Z19mcmFlVICPolRcaGGxY7ywqFGQWV6Tj7CeZF6zYludmoxWXVZUn5aan5inpq2TlZKJhVGfVquem46KraVYnqOpm7BdsKu3YaeipK6mqJeRoJyJhJekV1hVm6egpaK4Y7CytaWgl6anr6mytq6nvVxbXU5UVFhTVVxYWFpfaWVjY2tmWlalmSOgnZKKpLC0nbSbo5SWkIeOg5CZfZeWnq+nmKKgUVFYWFJaWYRTgFyRkIB8lYd3i5GXoJWanZqbo1VSU02LiqOksp6KX6NSVWdSVmReZ25tY19iXqBYV2BgXV5dbWZlXbZebmlcWmpXYWGumayqp19hWq2qZGi3YVlgXaejpqKmqVZXXqaXgoyMkpiTkZSRlJGKiIaJkJWYm5CMl4+QmJmRkpaQiZafWZKRloqCf5SAk5KJh5iPl5SSjJyRiodzeHWDgHx7hHx1i4yNkZCJiIyWeXeBjYyOlJSDk5iUkYyUkpSXhYWCqqKajZuUjItzdnt4cG5ydX+BgXeBmVWbn6OPgGVodW9sdHFxZmlsUHBtdHVqaWN1bnduc35oZXR4cl1jYmlwg3iBam1kcHRthIKLcIaCfYl8dndwfoeWhpSCend8mIaAiIt1dYSDjHZ5hYFzeYN0gXWLkH+HkIJ3eml5fHeDiICEfoCAiIN5eYZ+Sntvbn19c3h3g3WFfnJuaXCKgH94n516bG9kaHtvd3h5fmJhZmxxcG9zenp7foiLg4SSj3x/foF2iXeFW252f4VsdX9+hop+lox6dURSiIiFiX5vb3xncnx6bm5ia3WIfXR+gYiIcoSJikJEcGY+bmxoZo6UeoF1gG8+OVw+RElBe5iBhIiDgnluamlQZz5ISHNGgE5PR3NmWmFneXFiPDpRNjpRVz84aDlAcHI/Pz9GODo7NjQ1OjZoZ11ueXdDQ2U4Omg8PUd6cz1IT1VLPG1jXnlJUHBlYXd9e3BkbXFmc3B/Uk2IbmF7Q3JxZV5aW11Ub15rXVl7S3dwaW9kY21tZnlAP0E6OUFqTENGi4t2fXWFgHt1h4mIhpiHhoaBi36Ekol+fX5OToJJiYl2f4h8c42AhYF9doiDhHpzcHB1hoB5bXaIe3p+iYqEjXB7gmyGc3F4mH9+TWNkbW1hZmRnaXB7REBvZXhpYnF8c4OFfIF0eIZtcYx2gEVIQ2lra4uKeXmBi5F9d5Wbin17f058eXSBgICHi4FLj1WJmJiQhISKkItsaXx0eUOBhHd6hX+GinV9iomLgIGQe3p3dnyEdnd7eGpmYmlocF9cZ2FTU3Fyd4F/fXlia2N/a3RycG5sgn1kSV1qgG5yfoF2cnZ2d3t0ioKJhot/dXRpcHRpcDw2P0pBe3Vxcm9udH1GPkNORE5BgG58PkJJQGxCf4GAPm5IS29lfGdFPnZFQXFyYj9FPDlqZWx6dYaGknZ5c2lkQXlBg3RqX1t2cEBtb3pqdz92cHxGeHV5g3ZvXVhqa2Bdc4JEQ0FyfXV5a31DbW5yZmZjdXiHho+QiYWZS0lKPkdITERBQz5BQ0dSTUxIUEtEQoZ9gH16al55gH9th3KAeIGDe4R5hY1vgX6Gj4R7hYVFQ0hHQUVEPz9BRlGAhnl0jn1mb3Jwd2twc3N1fUJAQTplYXl3g29YRXtAQ1M/QUtFS1BPRkRHRGs7Nz08OTc2RT5AOW09Tkk/PUs4P0BsWW5ubEFCO2phP0NuPjxCQ3h2eXJzgHM8PUV2bmJvdHyDf3t+foSCfXVsbm1tb3FoZ3Jrb3V1cXFwamNscmhncGpnZndmdnFlXGdjbGhtbYN+e3tnb216eHBteXZxhX+AgHlzcnZ8YVtkc3d3f39ueHt0dW94foOIdG9rlI2FeoZ9eHplbXRwamhqbnt+fnF0gUV1eXto7noBe8p6gnucegV7e3p6e4t6A3t7eoR7jXoEe3t7eoR7iHqFewl6ent7ent7enqMe4Z6C3t7ent7ent7e3p6hnuEeoJ7jnqCe4R6AXuOegF7inqGewR6e3t7mXoEe3t6e616AXuLeoJ7lHqDe5J6AXuIegN7enuOegF71nqFe4h6h3uCeoR7CXp7enp6e3p7e4R6CHt7ent7enp6hHuNegN7enuHegF7hXoFe3p6enuOeoN7hnoBe496lnueeox7kXqEe4d6Ant6jnsBeot7AXqJe4V6CHt7e3p6e3t6hHuGeoN793oBe4R6AgIEAIDG0ufMz9nIx8zAwaudrdPS2eLW5O3h3eDhwcvg2NS7wcOqts/P2Ovi4e3r4unt7MnS19LRxtHZ18DG0Nfb1Nbe6Oji6oDp29TS4eLK14HPw7S9x7nH3uXNz9Xn4tLO4NjR0MfVzszBycXY6vD17vfe5dbc1tXIzcbd48PC29zc7IDXydnUys3K28XAuri6xMGtu8XLzdrX5t/n1dfP19jKwbrL3+jZ1cve/d7e/+bp8+Xf4uHn3P73joHl8Prm4NzPy87L0t7igfjz3uHzjpj8+srRxMvByOLj6vb/hoaP/4qZg/yAiJKEm4H1go6WnqaXkbqmke/X1dfc4/qFg5z8hICB8P71lJGPkpSb3NeFhIqMooPqgoH7hJiSi4GHhYqTn5KUgYGNgIHx6t7w9u2BgILuhpaB+f2GkZWjjIbT2v6Kj4z07/iF89jKysu+urzCxsfEy8XJ8MTP1MfEyNDl1fnV1NDtgITg0NDpzcbKwNaCmqSnloXt5IDk8PTo3uTd34Da4eLw1tHR6Ofl6fbm//WAg4H294Dy/oH+5OHl8Pvy3NTXztXc2e/LyuDV1dnKxL2v0M/Ex9rd0ujk3oHZwNzh5eWA49rGxq7Ht8HMwcnE09zi/uXb6Ov6gIaFh/2D+P3+hIGLjYuInfnz+Ozv5NLm5efhz+PZ5ff6iYuC6+bs74CAgfvz6Ojf6fiA9vHz/OP24d/x8P+O/unb0efv5ujngfDl5+Li38m2urrq0dLVyNDBzbvD3ffT4v6C0b3K2djUtLGssNvo3uTWxq60vcrG1tjjz+bP1OLn0+Hc9PHs6eXb49nMztHV3vHf2+Xp9IbngoX98ufT8N3d6/WM7oSLi4DZ1eLk5vr//f2J8uL4hvn77vrn8+rdh/n9ieng2ez27eno5OXa2uPow+P49YaD/YXv6Pf4hYCP+4f5/t7y5d7jgO/f29rX3tHc4MyF5urrgvKMhfLxiO+K5e318e2D+NfDzNfh5O/1+fKDiv6Khej9+oKMgoCSjIuLnouDioaEiICOgvPp5+bd0dnZ4dXMz9DXwMHZ2dzv7P6EjYOJjY6Fg4OLjIb394Dy5NvHtOjY0IPx+/vt4e7y4+z294Lv5NTf6/jw/IiCg5Ccn5yOmJiMj4KLjLCIjJmMi4uLipWTjYGA/5CFjISNmIr449fN0e/u/IqE/O7p6oGJg/fY7+3n9YDkiPr99t7Z0fjk0tTY49nfz9/h4uTx17bLu6ystrmqxMPNtLCmnLC2x8LGzMzOvr7Nt7Oko7WpsaK1v7a2r7S5wb7ExN7m7/nm9N/QytvjzsS/ssrF1NHZz9TS3M+2wcvHuK/Ewt7m2crMxcrMz9jO18bCx77B1dTJxfnkv9fHzYCKj56LjZiJjpKKj31xg6GgpKCOmKCQlJqVeoWWkJSHjZV+hZSSlp6QkJmYkJ2looCGioWMhJamooiEiY+XkZWdnJuUlFaejIR+h415jmGVjHyBhnR9jJaDg4WSk39/mI2DhHqIf3xzenWAiIiNhJB6jIeRi5CDiIWam3ZyhIaHlYCHfo2HhoWAk315eXt9h4Jye32FgIeGkYSIeH6FkpiLe3J/jJOKi4aWtIyHp4aGlIqIjI+Uhp2gYFGFipWKjI6LjI+LjpekX7azmpKcWFuVlG93cHVub4SAiYuUU1ReolhlUJJJUVZOYE2PTl1fYGxfVXdqXI13d3V6e4xNSmGPUIBNi5yTX11dWWBpenhVVFtbaUyCR0aOUGZkYVlhWlpeZ2BjV1deVVSdm4+bnJdUVFeXXGxbralYXFxnWFeEiqpfXl2Vjo5Pi3Jqcnpyc3FvdW1mcnF3loCGjYB8gYmdkLKRk4+qXWCVhYSch4KDeIhTYXBuX1SQiVKIjIp9cnd3eIB1f4CNfH5+nZiOipSClJBQUVKZlUyJi0SHdXR5f4uBcG1ybG9zcoVkY3V1fYp+dnBnjYuAfZGPgZaPi1mNepGPk5JMgYBzfHGMfYyYhYh6fICCmX50foiXT1NSVJhQlpaXT0xTVVZSZ5yUnJKZlISaoaGah5qKi5KOT1NPi4WHi4BNT5uWjoyDjZtTnZGVo42hi4iUlZ9aoZOHhpWbl5WTWaCZoKCkpJB/goasm5qhl6KNnISLnbOMnrJbhXJ/kZKReHl2gKmyo7KfjHV6fIeIlpiyoLujqLO3nKSfsamim5mYoJ2ZnKKqp7apoqSorF+ZWluso6OVs6OfqbJmpF5kZICSk52iorO+wL1nqpSjW5ifm7GisKqbY7WzYZuRiJ2qoZ2kpaGZnJ+bfIuallNSpFylpa6xYFplql+stJesoJieXJ+SlJKWopiho4VbmJeVVZhbV52hXKVloKqwrbRmxauep7C0sbCvqJxWXaFbW5Snp1hgV1hlX2Bcal1ZY11YW4BgVaShq7Wsn5+ampKGjZOXgYKPjIqZkaJUWlFVXF1WV1ZgXlibnVOhmpWOfKiVj2Grsrqso62ukZGTkk+Ul5Sfrburrl1WUlRdXFlQXWBUWVRZWnVaXGddW1xiYGtrZldctWtgYlldYlamn5mdn7i1t2RjuayoplthXaqNrK+quYCkYq+xr56dm8OqlJCRmYyNfo+Sk5eslHmQgnh8hop/l5WhjYuBeo2JmZGNjI2Pho6klpOGg5GEhnuMlY6MgICBh4GEgZCUnKmeqaCdlqGqlYyIgJONm5iTh4eHkol5ipmQg4CTjaKjm4yMgoOGgoR5gHV4fHZ5i4R4daWVfZqMk4BqdINydX9yd3twcFxOW3d1dnJmdHx0e316YmpzbW1dYmxaYnJvcXptbXR2dIOQk3V7e3V2a3iFiHZ5gIKFfICIiIyHi1GQgHt5gYVvfFSAd2p1fW96ipN+gIKWl4eHnZCGhX2Kg4J4f3mAiIaHfYZzgn6Ghoh+hoWXmntzgoaHkYCEfo6Jg4F5indycXR4fHRhaGlubHV3hXuCeYKFkpeNf3d/iYt7e3WIqISAmHl8in9+goSNfpCKVUl4gIp8fIB9gYd/g4aFS42FamZxRU6FjW16dHdqZ3p0enyARERLfUhUQno9REhATzpqOUZJSFlRSmVaUYFsZWRkYG89OU1xP4A7a3ptSEdFQkhNTEs7O0BCTTdfNzZrO05LR0BDPT1BQjg6LjA7NDZla2ZzdWs5ODpgQFFAf4NFRUNKPj9eaYpPUU56dnpHfmliaHBnZ2VlampkbGRkfmJjZVhWWWB0botwcW2CSEhoXmeDb25tYmxCSVNRRT1qZ0JyfoR7cnh2eIB2foKQfXt7kYuEgYl/kIlKSkmGhEN+iUaKdXV4f42Henp9eH+BfoxoaHhvcnpuamhhh4Rzb4F+cH94dE13ZHZ5fn5Fc2tcYVJpW2p2am1laG5vhGxkbHJ+QkVFR39Fg4WERUJFRUVBT3BweXN/eWuCiYyNgJaJi5KKS1BNi42Hh4BHRoaEh4uEipRMkY2ToIqVf3qAfYFKe3BlaX+JhIF6SHpwd3iBiHVoaGiOfXx+cnxocllcbIJfco5LZVZmeXx/ZmNdX32AcXtuZFReZm1rbWt2ZXljbHqAanBpfHhxb3BvenVxd3t9e4Rzamllaj5aOz5yam5jfW5qcXlHZkFNT4Bvb3d5dH2FiIdOeWh4RnF0a31tenNlSICCS3VpYHF5cW50fH95fIB+Ym55d0RCgkp6c3t8QTtHcUJ2fF90a2RsRn1ycnJvcWVtblpKdHl6RXdKQ3BzRG9IZm10dHdKjndrd4SOkJSWj4BFSYFNSnWCfEBHPj1KQkRBTEA9S0ZGSYBORHl0fYN7dH5+g3xyenyBbG2AfXeFgIxHT0VIUVBFRkVLSEVzfEaKg4mEc6CIdkyAf350cICEbXJ0cz9wbmhxfoZyc0Q/P0JLS0Y9SEtDRz5EQlY7O0I2NDU5OUNBPzQ2bkY9Qzk+RDdsZV9iZXt2c0E/cmdlZT5IQ31igIN8ioBySXyAfnB2dqCPeHV0enN5bYCDgYWPeF90aGBka29id3aBbGhcVWRgcGtucHRyaWt7aGJUVWhaYltueXZ2am9yenNzcISLmaOSn4yAeYWKdm1vaHp2g4GEfHl2gHZldoZ/b2l2cIiMiHxzZ2xzd390fXZ5fHp9jYZ1Zox5XXZpb8R6AXuIegF76XqCe416AXuFeoJ7jXoIe3t7ent7e3qGewF6inuHegl7e3t6e3t6enqGe4J6hnsEent7epF7hnoJe3t7ent7e3p6hnsKenp6e3t7enp6e556gnuJeoZ7A3p6e5d6CXt7e3p6e3p6e6N6AXuGegF7lXqEewV6e3p6eod7kXqDe4R6gnuHegF7i3oBe4l6AXuZegF7tHoEe3p7e4l6BXt6e3t7iXoFe3p6enuIegR7enp7knoEe3t6e4R6BXt7e3p7h3oBe4p6DXt6enp7ent7enp7enuFegF7i3oIe3t6e3t6enqRe5Z6jHsDenp7iHoBe4t6AXuIep17AXqHe4h6gnuEeoN7h3oBe/56AgIEAIDS2NbMwcrCwbelur+q4N3R0OOA4MnZ1NLW4uXb1NfJxrywqczWzdjp3d7izcrJ0sTDxMPL3tvK3dPh5+zm297Q29rc0+Db1+XK2NnYyMzV0ruwwbLB0+rqytrr8ObXxMbFuba2r6qspq3C3OP58ILw6Ord283L47ve28C/yNrV2oDZzcvJvb2zpLK5v8qev73FyNLi2+nr2dHYwcvFzs/M1Nrc6erR9fLwgPPs9ICE++Tz4NTj8e3p2Ob0/YiUlYfz6+fa1dfg7Yj+6f+CkbKenYiA/OHk0YHiiIOHg6SGj/j/g/D9lIWZkpiLkZOSn5eOgfGC6eLX4ID67Pb+l5eIgYCSp4H1lp6LjfaD9PPrioGFnrK3ma2rj4eNgZaOlY+GiYyQjIuGi4aVj4SI6+jv84CTm4zr8erchY+moKmNg+zWhYD+hIPq2vTv6cjKw+Tg1uTh0cjQxNrQvczo1crKxsnP1NXR8Jj3/oiU6N7N0tL1gIaAhIL77d37+4KLiYv784Dh8Nvr4b/J2Nrz4O/s+evvge3a2fPo3e3v6PXz+v3r6oLY49Hh19i+zNfZ5Mzb6NCvu7m4wsbo2d7U4P3q1MjCxuHiyN7T5N7E28zPwLzKzM/k1N73+NbpgY/2/YL19ID7+P2B9fn9gOyA9fr4/tr38NXZ3OLi6/nw7/Xy+oX1hoCKg4P37+r92IT40OyJgfSA7PH5//7/7Orh1tfi19vm6PiPg/n66fDVwdDR0+Lcz72zu77P1PL149/IyrLK0MfFv87X/YDmg+bRx8Kxu9PSz+HnjYqK3c/+gpmH+Prr3+zo6vTq4drNu8rJ2Obw8s7qhoT9+fb+/9/Z5veDhYz6goDr5+nl7eL4hJLu6OLiju3ogvL79vPf4dLZ4eT/7ur/9eLi7u/p5d/j0On3/vaHhPzu5dP/goOGg4aD/ePk9ejy4d7gg+Ph5tne4tz49PGA/vDr4OjY18LZ8PqD28+G+If81Ozq2vDt4fP9g/uC6O3m5/eFh4iIkfyelpCSiYiCjoCimIaM8eTRytjP0tzW3c/T0NfczuDl74OBgIaBioeHjIqMg4L/9/Xi1NDMx9bWy/jv+PHm797k3ub7goXo69+Ag/fU/oKDmImOjZOcoY6Rhvfv9YKBhY6FmI2Nio6Njo6KmImGgIeCiYOA39jh6+SH4vORifDl94GoipyF9uvv64Di7enW/Nrv2dvX2MzT6dfg4dPZ1ODn1uPKvMGsrqylpbGloqaqm6uzvcnHwsDPzdHBrLi8xb6+rZe9wrG3rry8utHBx97n6OjWvNzcxNfF2Mu2yMTOxsLGzszZ2trfxcLCs7evyMXPytPa4NLSytzfwsrKxtXE3+PY2+vYvMDHzICVkpCKf4aBgHZtg4ZyrKWWk6BalYaWlZSTn6CVipCPjo6EfZuhk5Kcjo6ThYiIin9+goGMnZ6PnpCUl5uajpGCjo6ViJOKg4l5go+VipCfl3x0gnR/ip+ZeoaSoJiMgIWFfXp6dHBwbGx2gHyOgEmCgIyFg3l8lXOTlHx8gJCIi4COg4B8d3hqX3B6hJBkfXyAgoackqGrkY6UfYWIjYyGhYiJlJyKtK6jWJyVmlJUno6fiX+KjoWEdYKJk09TVF2spbGflpObp2S3pLZdZXZiYE5HloOKeVF+UU9STmhVXpqYTX+FU0hZVWFTWl5caGNXS4xPiYh8hlKTgYGGU1JJR4BYa0yOYWxeXqBjqaebYlhYaXp8YnNwWFZgV2pmbGBXWl9hYGFaXFlnZVxjnZeSkk1dZGCdo5qLVVhoZW9eWKKSW1ShU1KThqKbmHp7d5SNhI2GdWtybIJ3boCYiH1+fYWKlZWTrm6oql5lk4Z1eHiVU1lUWlimk4WcmU1SUFCJgoB3in6LgWRsfICXhpWYoJWeVpaEfI+Bdn+FfIOEipB7gExwe2x6cnNcbHF5hnmLl4lrenhzd3qchYt+i6eah396f5mWepGCl5p/mo2SfHx9dHWCcniKiWx9SVWHjE2Qk0+bl5lMio6OSIZMkp6eqYelnn+BhY6Lj5aIhYiFjk2KUoBWUVOXlJCie1Ocdo1VUZpSkJWZnJmXjZKJkJGelpeXl5hcU6Orq7yjjZ2YmKGmmod7h3+OjqaknpqKkHuNmZCJfoiNr1iaWp2QiYyDh56XmJ6lZWJdkIOuV2tcqamim6ussLy0tK6nl6Geo6euqoSXXFmrrq+1uaWgqrldXGWyXYChn6Kjrqi5Ym+po5+daKegX7G6uL+rq5+kqam8rK3BuKGfqKWjnpOViI+ZoplYWq6hno62W1laVVlVoJCSpZmsoJ+nZKunrZ2cnZCgnpxUqaWcm6ycm4+er7JhnJJowWvLprWxpa6rn6SrXalan6KYm6BUU1dXXJxoY2BkYFtTXIBtZFlir66dm6KPjpmOmoyMjZCQg5aYo1pYVFlRXFhYWFZaT1Kkn6SelpGQjJSQg6ylqKSao5KclJivWlmVnphcWrGXvVxWYVRUUFRYWk5VUpeToFdUVltUYl5dWWJjY2JfaFpcVFlXXFVXl4+jr6dopbBuaLSotVtvWmdVo6SuroCjr6yYvZ6voaaZl42PoI6PjYWNiJahk6GUi5KKko2LjJOHiYuTiJeXnKKZj5Kcm6KYhJSZoJmYhXGRknyBd3+AeoV6eIqVlaCajLK1k5uNpJaAj4aLhYKGioqNjJOfj46SgIiDlZSYkZGXm4uJfIGDaXJ1c4Byh4V5f5OJfX+KkIB9f315cHp4enBjcG5XhX5wbXlIe2t+fXl1f35vZWtqampiXHh7a2x4b3J4bHJ4f3VydHZ/jIp4j4aLio6Fe4J2g4aLh5WOhotze4WKe4GOi3Zwfm97hZmTd4GRn5eMgoKBd3N3dHN1cXJ6hX2IeUV+f4yFiYCAmXaSkXhyc4WAg4CIg4SEf3xuY291fYdedG5xc3WGfoyYhoGId4GCi4yIioqJkpR6npeSUI+GiElKi32SgXqIjYJ8anJ4g0ZLSUyQkZuIgX+BiVGOeoxGT2JLVkdFkXx8aENuSkdIRF5LUoKDQW5yRztJQ0lBRURIU09LQXVEcnBkbEF2YWFpREQ7NoBDUzdeRVBCQWg/aWtjQzo5RFJSQVFPPTtAOUlCRzwzNDU5ODk2NzdHSENKdG1najpHTUdsenhsRUdPTldJRHlpR0F9Q0J4cYyGh2ppZYF6coB+cWZsZHVkU154Z15hY2huc29qgVJ6gUtUeHJnbGqERkdBR0WCdmd/ej9GRUeAgIB6j4eVjnB3goCVfIeLk4aOToZ0c4d9d4GFe4OBho57gE52g3Z/e35ndHVzeWdzgHNbbnFqbnKNen5ygJ+PgHNvcImEaHhpenZfd250aGhwamx4aXKDgWNwQkt3ekF8gUaJhIhBdnl2Omo9c4CDh2+QjneAiJKTk5iFgIeGj0uAR4BIQkOAhYWVb0qIZHxPSotKg4eHi4SAcHRrb3SFf4F+eXdMQn2Ih5eCa3hydX6AeGdfZ15oZXt6cXBjbFtzgXx2bG9uh0JsQm1lYmVdYnVtaW5zS0hEaF6CQlNEdntzboCAgYiAgXx0ZG1ma3Bxb01hPz51dnuEhGxncHs/PUaARYB2d3x8gnaGSVV7dm9rT3RrQnV8eH9ucWtyenqLeHaFfGpreoGBfnV3a3N7f3ZERIJxaV2DQEBCPEA/dF9jdmx9d3mCT4Z6em9vcGmAfH5GiIN9cn5vaVdndHdCY1pNj1KSdIqMhJKRg4mLSoFGe4N5dndAP0JCRGlLR0NJRkRASIBXT0FJgHxyd4V1d4F5hHV0dnp/c4KFj01JR0pEUU1LTEpMQkOBgIeDe3+BfoZ9a4t9e3VseG96cnWJSEdwcWxCP3ZcgT47SD1BP0NIS0BHQ3Zwdz46ODozPTk4Nj4+Pz87Rjs9OD07QTs/ZF1teW5JYWpKRG5hcTxQP08/e3+Gi4CAhn1riW6GfIJ/gHJ0hXV+fXiBeoeLeIR1aHBlaWRiYm5jY2ZpXWhma3RuZWp0d3psV2RodHBvX01xdWFnWmhqaHdscIaSlpqNe5eXdoFxhHZnenmCe3d6f4GIi4+YhICBa3BnfHl9e4CFhXZ4c4KFbXh9eIZ6j45+eIV4ZWhwdpJ6AXvbegF7uXoGe3p6ent7jXqEe4h6BHt6enqHe4R6Ant6h3sFenp7enqNewJ6e4R6AXuEeod7AXqEewV6e3p6ep57hHqEe4R6h3sHenp7e3p7e6B6BXt6ent7hnqFe4V6hHuSegF7j3oBe7d6Ent7enp7enp7enp6e3p6ent6e5N6Ant6hHuFegh7enp6e3t6e5F6gnuhegN7enuLegl7e3t6enp7e3uVeoJ7iXoFe3t7enuHeoJ7hHoEe3p6e5x6gnuFeoZ7iXoBe4p6AXuLegZ7enp7enuKegN7enuFeoV7AXqMe5N6jXuWegp7e3p6ent7enp6jHuDepd7hXoIe3p6e3t6enqFe/96hXoCAgQAgL/HysnJybi2ybu9v7ifrb3K3drd19zfzdjY6+b96L3Fza+mvenB4Nnt3tbdz7e9xNfVzN3k38XT1Mnl9NrW7NO8vsjS3dDIws/Avb3OyNLJtrG9ztOn2uLy8OHQxNPIt8CwvbawsqulqrfA1OHc6+jY+ujZ5t3hz9q+yM/OxMnWgNbPxsivxMbKx7y+zL/GyMLYz8nEsdbi4sOqs7XHzMjZ1t7/gfnr7tXh8+zN+er0/Pf674D67eyE7PT5+Yro89rezLXLvNnj+vTW0+H9h/TugfuD5eLVhezl6uvY5Pn/+4OA9fTiiYr66Pbmg4Dhh/KF4fr9gvr3yOmDhIyG+v2EgIWLmYjohIaKmsaojpSRoob2iqSho4mPgf7p4oOMgqKTlJyhlY2dl6+gmILp7OfphoeAjIX048z19/P61t3i3c7O1Pn1hY+Fh+vIldvo8NLuhIuE1eHL+tzN4e7m7OLN3cjE0Nv2jYmB9vHT0cjm8PuA9dvz29nW0e7sjZyeh4GCgIDr7fLlyM7g7v3g+4P18v3nz9fGzNve4Onj3vqA7oDZ2dXg1+DM6Pbh5trq2ujV4u/lxuXX3Pvm4t3m19XJv8rQx9DPw8TPzNrW09XP3PuA9YLr/v319t/a1OaaiIeCgf2MgYL+/IL87e3u9Nzo3+aD5uTu/Pzj5+f46/qFl4mQgI30/OXn6NTV+YSJiYmD+P3z9vGAg/nx6+re2dbN4tze+YWUhPj68tnV4OPZ6cO7sKm2yOjZ6obn27vVycnY3eLy5+Djgo+Dw83W1dOC69Th9YOK4unh8PWAgf+C9v2E+vT04d3b4+HlyM3rgYHr+P+DhIeDhIKS99/u/YGH/4D8gPfO/oKGgIiD/PTi7e/s5YSK8uzn8OHu3ejnioeMi4iAgPeB9end1NjR7/+MguCD9eHQzNrwhYOHioOA8Nb9ivbn+fbh1dPF1+TX85uC9oj/+Pj75u38gYeQhe7469j394Tc9/Dm3d3l7P7009vZ4ezy9//274SPiYiTl5SWjoyCgISJ+Ojb4tjshoDy/4bp8eTs8/Hv3eLn8/uKjoeXhoD8jP/76IiEgvmC38jC0tLS2N3d6d3qz+Dh8vv7+fb0hYbv5Oz4/YeNh5KQjZiWmZuMhZ6D/4GIgoKFioeI9YeKhoSIhoeL/PiFiJeR74f6/Y6MmZWMjv+ElYmBhvOC5srJgMXZ5OP4++rMwdDlzNXV4Nrb19Hi1djBvK2yr7Gsr7G9tLSmnKCNoKevt8O8x+HYwq7CvbOmpautprrDu7SqsLS9vLHEzcfIyMPJztPf0sfIzbjH0c3BvMTUztjIy8TBvLbBvMnAz9HEycPQ28nTyM7Ozc26zc7S6Mu1xMzQur61gIGEhYGAemxsgHuGkIh7hImRm5OWkZSZjJGNmpOjlHqFmYd9kbaOnZKmmJCaj3Z7hZiWjJyhlXiGh32WqZCRpYt1cXqCjYOCfIh8fYOSjZuNdG13iI1ik52jmop4bn16cHdugH13fnVwbnN1gImEj4yBqI97h4OJgIl0eH95bm57gH19dXNldXB2d292iHh/gniKgX57bJSjon1kaW+AhHyJhoWgUZ+WmYaYp5+BqZSYnZOOiE2TiItUkZWQkVOHmY+Yk3Z/cIiPqaeCfYabUIl9SYxMeXZmSXl4enpyfJOUjklIh4p9UlKNgJCAUE16VIxNgJGVT5STZoRPTVFLe3lBgEVLXU96UFZYZ4psV11aalCNV21pbVdeWLGhnV5iWXNnaGtsX1xrZ3puaFecoZOOVVVPW1ifmH+jnJuig4uPkoeBhaacVmNbXZuCZIuTl3mRU1lVeox+rJKDj5uTj4l9kXx9ho+tY2NesaqPj36WmJpOkn6bh4qGhJ2UW2dkTUdKgEuHlZyOd3uHkJ6CnVafn6uXfId2eIaIgYuDeo9Lhktybmh5c4Fuh5J+e3KDfo9/jpmRc4+CiaiPj4eVj46Ad4SFfYB9dXV/foeEf4WBj6hUnFOQnpmRmn50bXhdT09NTZVZTkuMikeCen2Ej36Jh5BTjImNoKCGiIiZiI5OW1BdgFuftJ+ho4qFpFpaV1RNlJiRkoxLTY6PmaKemJeQopKOmVJfVJ2qp5iYqaugrJKJgXeBhJ+PnVmfk3iOh4OSl5ikkYeKVmVdf4qRkZNZopOdqF1knJybpadVWK1crLNfuLe5r7GyurK0jpOrW1udqq5bXV9bXFpqrpSrxV9juWDDgLeMr1RXUllVrqaYpKKln2Nru7Ouwq62pKidYF9hY2NfXr5lvLSpmJiVmaFZUYhWqZuTj5ipX1xbXldUnYipYKebqrSonp6SnqGUomtWoVutq6u1paStW1xlXqStpJaysWKfsqmimJehobWzjJGZoqCbmaChnFdeWFZgY2ZrYFtQgFNdqqilr6K2YlqgoFaRloqbnp+hkJGYoKRcXVhoWVSlXqefklxZWq5dnIN+hYJ/ipOSopami5uVm6GhnpyeXGCno6qwslxcVllTTVJTUVRPTWdZrVljXVpcX11fqmNkYF5dW2Bhp6RXWWZinV+vrWdka21iZ7RcaltPVplbpZKWgI6cn5qoraGMgo2ii5GOlIqNjIyWkZqJiIWNjZWRkZOYjpKLhY6HlJabmpaGkZ6elIifn5aCf4V9dI2VioV4eoKGgHeDiYyMjYyWnaWzo5eRk3mEiIaAfoWUkJSNnJeUjYSOi5uQoKKVmI+Qk4GIcnl3dHVoc3h9jXFfanaGcXtzgHF4eXRzcWVmeG9ydGlVXGRweneBf4iOfn54g3qIeV9qe2hfb5BqeXCGenOCfWlyfY6Kgo+Vim6BhXiOm3x5k3xtcn2GkYN7dIB2d3eEgY2Ec3B9kJRkjpKblYqAdYN9bHBlc29weHNwcXN2fX55hYR7pY6CkYiMfoJxd3t1cHB+gIKDf4JzgHyAfXN2hnl9e3OFfHVyY4ubmX1rc3mKjYOPhoSbTZaNjnmFkYVkjHqAhoSHgkmShX9KeoKGhE15h3uCgGh3ZHd/kIlqZm2ESXltQIFHcm5eNHNvd3VpcoSBdz08b3BkRUZ0bHxxQ0NqSntCb4CBRX14TWY9PUE9Z2g3gDo/TT9fQENDTWJIPEI/TTpgO01JTDpBPHZnYjw+NEc9O0BCOjlIRlZNSz90d29uREE5RUN7c1+Gh39+X2dweGxlZYF1Qk5HTHpnR252eFpwRExIY3BiiWtZZHJsampkdWVkaW6ETk5KjYtwcmZ8f4JBdmR/bnBsZnxwSFJUQ0JHgEmGkZiLcXV/hY50i0+UlqKRdYFzdYSGg4qAeY1Igkhvcm19fYV1kZqIg3F6bXVoeISEa4iAiqaOh3+HgX51anZ7dHVwZGJsa3NvbHNud5JJiEl9ioiAhG1kXmtVRUZEQn9PREB6eT1uaGhsd2Vtb3tJfoWOoqSLjIiRhYpJUUVLgEZ2iX2KjXdxi0xPTkxGhoWAg3pBQXVwd3t5d399kIB4gEVPQ36Nh3hzfX14hGxoYlplan9rdENtZk5lYGV6g4mWfmpoQEpBUV1maGk8emZveUZKa2tqdnc9PnI9cnRBgX5/dXZ7hoGFZmV8RUFpeHxCQkI8PT1MdF5vgEBCeUCEgIBfiEVIP0ZBgXhnc21vaEZMfHNvgHB5bXNvS0lKSUU9PHRBfnlwZWlnc3pGPFxBe2ldXGRyQT4+QTw5ZlV6SYB4ipOGenVneHlrfFVAd0Z/fn6DcXR3PUBKQnJ/dWuJhkt2j5GOhIKGg42HaHB3hIWEf4J4b0BFQDxFSEpMRUQ6gD5Ie3dwdm+GT0qKklCBgnF9fn9/cHeBiYxNT0pZS0aMU5CFdEhGR4lLg3JxfHducnBoc2x8aHhyfIB+enZxRUl4cHR3dj5AO0E+PUZHSExDQFNDfTxDPTo8Pzw+aj9APTk6OT9CbGs7PEdBXUBwbkM+Q0M9Q289TEE2P25GfGdrgGRtbmx5gnxqZXeMb3RyeXN4enuIg4ZzcGhsbG5paWtwaGxjXGBVYGJoam5ga3l3aFtzbmdaWl9aVGx1amVYX2lzb2d3gIGFhoGJiomUhnx6gGx6gn94c3qJi5CJkoqKgHJ6doJ2g4J5gXp/hneBc3p5eXtqeH2Bk3dganZ+anBn/3qkegF7j3oFe3p6enuEegF7kHoKe3p6e3p7enp6e4l6B3t7enp6e3uEegp7e3p7ent6enp7hHqEe4J6hXsBeot7AXqHe4N6kHuEeoV7kHqEewN6enuFeoN7knqDe4h6AXuJeod7i3oBe496A3t6e7B6A3t6e4l6hXsHent7e3p6e4l6AXuLeoV7iHqFe4V6gnuMeoN7knoBe416g3uFegF7hHqCe4V6B3t7ent6enuMegV7e3p6eod7hHoEe3t6e4R6hXuHeoJ7iXqHewJ6e4h6BHt7enuGeoZ7BHp6enuMegR7e3p7h3qEe4Z6AXuUeo17hnoFe3t6enuMeoZ7Cnp7enp6e3t7enuVeoJ7hXqOewF6iHsBeoh7gnqEewR6e3p6hnsBeoV7Anp7/3qEegICBACAx7y/v8HLy97XusfIv8zVz9Pg4uHp8N7H5trc7+vi2ce6uL3H8ubc4eXp5N3c4MiqtL/PxtvpgdPIz83a29XVz8jYzcLGytXYx9XGw8m80dnMtbrHybq9y9LX0r2zubqsubGyq62svLWqrcXS4Onm0N3UyszTysC3uK6Xq7e4x8aA0r7MysC7zcyyt7DOscS9wd3WxNvDvtS/vL3T187Ewdfv2Mi/yMLR29zU1M7RiOrs6efv8v6JhYmDgOry+Nvq6NbswsbJ5Mvd3N7X0vqyiuLv9uzdg4Hf9uDZ2dXK2MLiovz6kY6P++vg38u6t8/d2PHH4fuDiYiN7u/3g+31iJCAmaOFpJbq5PD9hoH58vOH7oSDjIiUiYKB+ISDn5WOgJGYhoeViY6Z+ff8gITnhfuAhYKDi/rc59/s2N/k65ea5N3t4vbwz83UyqvB+IyD8vSUhvbV3tT0kYeB8fLgv8zgy9aC7PH5heDg+93g5uny/ObvgPjw9YOHhZiFh/WNk/GA7Oro5+Dg8ffk8+Lx8OXsifrK5N7X09ba2dTK2eDo2s/Lwr/V5/7h5t3s4vXX7fGB8Lq7zMfU1YDe5uXl3dnUzczQzeHS1+PO2snT5+zd9tv19ebw287S2enwi5SMoYv41vDm7tny+ImAiIH8i/fk+Y3zgOHi4Ovw/YLti4WA9uyA5+zl7+Ln1t71hYqIhP6AjIuB+v2Iifj06crN1dng6pSPoZySlPj25tzx8+nl5NfFzcXT7vnyg/H50+Tr9/zc9O/g4ejd5ur37+3v6YDo5O3Yy+Pj1uL59+76hYL+gIKE9fPo9+Dd6u37gZb8gonzgI6HiYeDgor474H4+YuL9+2Agvbl+P+FhYn3hIH56snMgPPo7+jh8YDv+oGEh5aD+fno7/P/79jU29fW7Ovl2YK70dzh9oGD/oGAkof86IGJgof56Ojb2NLZ5+Xi+fj5gYH8hPbu+PyJ9IPu6uzT1OTv8eHu5eXp3fH2+Oj16PDS5e+K/fj1+Imal5upkYyHhIWA+Pjv7Oj87Orr3efs/+Dj8Nvi3fb5h4OBg4WEgP+OgYOCjZGJlYv/jf3m09Dc5vXp7fPz0c7Z7tyBgvv28fLm7drc6ob9+oKFhIGPmZWFiYSFjYDu/oGEj4KLhoiQlZOTi6ugo6aOhoCHjJiPmpmI//WUhYLw74vtgISQhPjw8+WA4+ntgf7w4MTHx8vi38/Y0M7T0cvHyq+unJqmp6m0wb+9uKyfnaCYqKayu77Ix82+tMO+ycjMx7O4usbP09DMys3e08jU0M3L2ezTw8C/w8zK1MTF0dTU0MzW1srSxrG6uKiuuLm+vrTBwcfRwcLXz93DtayzvsPAu77LucC5wr2AkICBe3d4d5KMfJOZjqOmlJWZlJSan4t7j4OElI6MjoSHi42VvK6dnqaspqGfpIhveYGKeYiSV4V/hYWLi4iHgHyKhH2GiYqKeIN3e35zgYaAbnF8fnF6jpWVjHBgZGhmeHmAenl2fnJiXm56iJOQfo+Lf3+Cf3p3eHVneH95f4GAg3N+d3Rwe3xuc3WWeI2EfJePfJeIiaCKeHeDg395dZGrkoZ7f3l8iJKLh4GIX5aZj4qQkpxXUlhUUZKamISNioShdnR6i3GCgoV/dpJqUnuOmJKDVE9wgG5tdHh2gXCEYYmDUlJWlY6BgWxiZ3uMip11h6FWXFtikI+VUX+ASFCAV11GZ1x/g4yaUUyThI1TiVJUXl5rY1xerV1YbGRiWGhuXl1mXGFso52hVFiSWKVTU01OVJSAjIaQhISGk2tyn5OekaCYfH+Mh2yArWFXmptkWqKAin+aZV1XoqONb32OeohYmpuhV4uOp46Fg4OIkX6OUZ2ZnFpiYHNeXZZZWoiAiYyQlI+Ml5d/iXqKi4KJVpNlhXt2dn6GhoF3fIGEdGtmXl5vgpiFgHp+dYh5kphapHRveG95eVCBiYqRjoqGgHt/eIZ9hJSFkn+QoZ6KmHiNkIqYg3aAg4eJUlpVaFqef5iNjXeJiE5HTEqSV4yBlFSLToKPi5CNk0t8UE5Mk5WAm6mptaipkJKkVlNRUJRMVFRIh4lOTZCcn5KVnqWjnmNcZmJhZaaropyxr6WfoJCFh3+Cm6ObVZykg5CbnKaKoZWLjY+FiZGkn5+molmnp6+hnrCxoa+8t6qwXFuwWV5ira6nuKCfrq2yWmuhVVudVWNcXl9aVV6knFu1tWVktrGAZrmfpqNUUFSaVVixpI+YZ8C6uLGztV6qr1dbYGtgwcOxu8HItKGdop2Zp6ainGGGnaepvWBht1pWZ1mgilNcWWKyp6qgmI2Tnp6bq6mxWly0X6ykqKNdoFidn6OPkZqip5mlnqGjm7O6ua62q7WSk5lcpKKcoFpnZGhzYl9YVleAmaCboam8sLKmko6QnYmMm42SkrCvX11YW1xaVahfVFJPWFxVX1qlY7SejouQl6CXnqOnhYKMnIpVVqeim5+YppehrWCxpldaU0pUWVZIS0xQXViqwF9bYFRfWVtgYl5hW3NwdXZhXVRYXWZgbXFgsqVpXlunrGmoWllfVZ2cpKCAnKGbVqmhlIOLi5GnoIyWjouPlYyJkoF+d3iDg4eLmJORkImBgoyAi4SLjYyVj5aNjJKKlZKYloWHjJSanpeKhImSjoKTkpKNnLSdko+NjJaSmoWEioeHg4OQiYaTin+Lin6FkI+TkIWLioyPhYCHfohxZV5fa3FrZ2hzaXl2hISAfW9zcG1zcoeAanZ1bHl9b3R8fYOPmYl4jHx6hXt0cmdnaG10mIp6fISMiIaHjHhgaXSEdoaOUoJ/goCFf3Z4cW5/fnR8fH2CcH5zd4B3hYqDcXaIiXd9jZORjHlsb3BkcW5vanBygnxua3uAhY2HdomDeX+EfnZucGxZZm1sdHeAfXCCf3p2hIVwc3OSeYqAfJSKd4t6fpSAen+PkouBd42hiX1ze3h8gYN3cGhvUn+Gf36KipJQSk1HRnuAg2x3d3SUbm50hW17eHh1Z4JNSmh9h31tR0Nhd2dnb3FtdmNzVHRrQ0RGc2tqb2NcXm+AeYZhcoVHTElNbG1zPmlrPUGARkk0TkZdYGl1PTtzamtAYTo3PDlDPTg7az06S0E9MkFFODhDOkRPeHl8PT9iQYBCQjs8Q3hnc3KAb21qbVJbeXJ9cnxxWV1oYkxfh01Cbm5ORn9ha2B2TEM/dnltVWV1ZW5KfYOESHF4k3t7fn6Dh253Q395ekVJRlFAQWxGTniAeX2DhX59h4dxgHOHj4qVXaFvjYN7dn2CgH11eoGGdG9uY2BxfZSHhH2Bc39oeXxNjWZqeHSDf1OEioSHgXxzbm10b31xeIZ2f2t2iIRwgmd9gn6NfG5ycHN0Rk1HXE6GbIeAg25+ekQ+QDx4SG9meEh9SoCOjY+KjEV1SkVAdG6AcXp4iYOIdHWER0dGRohJUlNIf35GRnyFg3R5hIuJhlRMVU9KTnyAenSFg398gHVqcWtvgIR3QXB4V2RyfYx0jIF1cnZnamx8dXB2bz1yb3ZnZHN0Y3B+d2xzPTxzO0BEeHt4iHp3goCIRVN8QER0P0pERUNBPkRybUF8ekNDfXOARn1pen9CP0N0QkKFdV5jSYiBfnd2e0J5gEJHS1ZIiIRxeYGOfm1rc3NvfHpyZ0RQYWdugEJCfDs4SD1rWTtFQ0yIfoB1bmVud3Nvf3d9Pz99RHZxfXlGc0NwdX5qb3uChnqHiI2MgJOVlIaNhI9yfH1Ie3VvdUNNTE5XSkdDQEKAcnVvcnaIgYaCd3yBj3Zye2psaISJTUtHSkxJRYxSR0lHUlNKUEuBTo6EdXmBhIl+ent+XVtneGVDRX54cndygHF1fkR3cDxAPTlHTk5CQ0FCSD92g0I/QjhCPT9DQz09NUlITk8/OzY5PEE8R0s+cmZIQD5zdUx1Pz9JP3R2fHaAcG9rPnpybF9nbXCCf3F5c3R9gHl2eWVkW1tmZGVqdW5vbWdeX2VbZ19mbWtxa3FkYWthbmp0cV5hanR4f3hwbHB8eHCCgYOAjaKLe3p4eoWCjX59g4J+eXuLi4eVg3eBem1zeXd7dm14eX2EgIGOhY54amFmcXh1cHB6bXZud3G2egF79noBe4d6hXuTeoJ7hXqCe4p6Bnt6ent7e456hHsGenp6e3p6h3uEegd7e3p6ent6iHsBeo57CHp6ent7ent6hXuJeoJ7jXoGe3t6ent7hXqDe4h6BXt6enp7i3oEe3p6eoZ7A3p7e5B6AXufegF7h3oBe6J6hXuIeoR7CHp7enp6e3p7hnoFe3p7e3uLeoR7AXqEewR6ent7iXqGe5F6AXuVegF7jXoGe3t6e3t7iXoGe3t6e3t6iHsKenp7enp7e3p6e4R6Bnt7e3p7e4R6AXuGegN7enqFe5B6AXuFegN7e3qEe4J6hHuNegR7e3p7hHoDe3p7mHoBe4R6inuVeod7AXqJewJ6e5B6gnuJegN7enqNe4J6mnsJenp7e3t6ent6hHuHegF7/HoCAgQAgMfMwL7Gzs3X49HZ1+fGyNrb1eDh5NLPxcPg0uXa3tO5pLe3zOPe0+Hd3OXT1MXpybq4s6fM2tjIwcXAysHI1NHSwbTHysjS5tnYysnF1NPG2c22y7CstLywv7K3tr23vMi5w7iss72xtcHRxd/Q0cjLydDMycezuLCtrq+zusbJgNzT3NzS0t/ehem7v8/HwsHf1tDR2eTc0eDn7cvR0MXGvdTZz+v35dzS3dbh187049zH2PXK1O//+vP5gfv3ip2HiYbX2tXS49zO1Mfp2OHo4Pr7g+f/i6Sn/dnYt+CB9Pvj+YGLlpiYiOXX3Nzog+Do0+7v6oeHiIyHgvv79fyLgImU+vzt3Nrk2uft8Pju75OXn56HkpSZiPyAiIWJ+4eOnYuNi46BhODg2dDW2/+Jh4j+gIKEkJeM7u/R+vf58vvv6/zr6c3Wysqzy9Dbg/Kg6ejr7/LuiYf89dXQxdvT39Dqhfrl0vbYy+TliI+SkI2B9uzW2u7ZhoGC8v2FkYnvgO/h4u333+r36ufk2c7X9IDx29nU0snI09TYys7p59zWwOHZ1tXs4MzN3N7/6PGK5rSqu8LC4YWL59LYzdjMz9bk5tPMxtzW0NDj2Mbv0czv5+rg5tXU3fuehoCTjovu6fb/+Pnxh4aPj5iChfWN9ImH3t7Y9+zm5P6E3YD568zZgPH094Pg6dTk8YT8+4eJi4Xr4vP7hI2N+9jPyMrigYz1gYiLko+BgfnugoH+6uLf19Xh1cnJ74D6g+3m2tvZ0dvR6IL3h+n27f2B+e7lw8rU2sTE5Ory7+789v34+Ynh3uX66/bz2efX5PX18pyA/JaRgoCGh4uJ9+GE8oH6/e31gImH7Nbp/viHjIaGhIqB+ITw3fr84fSEgYqLiImKjYX46Pb6hvza3MTA18nG5d/87ubh7fz7kIGD9YeC8YD89/KB2u386unY4trkgPnf7IDk9ISD5dnt95Lk4djY4ur+7fT17tLY183k64Ht4eHV5uv/8dzd3vfogfWIi4GJ/vqKgIaE5/L4+9Xo5+Dj3+X3+fPk2c7Y4Oze9oT+gIX6/vmIiOmNkoCaiJCDgtPC6d3z7N7l7e7q9fHwi4Pn2dvk5cLd1u3x8oGIioaLkIWD6urf9vL06oCN+PqEoqKhnKHOmZKhmqmSgIHkgPGDgI388eiC/ID/hY+QiJaoj4n6gJWKgPeNiIuE+uzZ0dHG1sjFy8HJz9bYuresoZ6gj52tubrLurmur6memp+hsLGwwr+1tay9p8O9tbGvwLDMyMPI0+by2tfa0L2+xrbM0bm4v7zRyc3TwLTGwtjP09HFxsy0ssa/xLKsxMe+uqu4wMnG0MvNxK6lprmpvbfFt7S1w8O+gJCRg3d/fHyJlY6horOamqilmJ+gnoyHfXePgpGKkpGAdIaDlKOcj5eSj5yJjH6ahXl4cWR+iIuDf4SEiHp6g39/cmp7goGHj3+GfoB7i4Zzg3ljeWZkb3docmtnZnBtdoR6jIN6hYp4cW95aoJ2f3qEioyGfod0fnV2e3p5eIN/gIuAg3t4dH1/Vo5qeIeNioOmmI+QnKqknJ6jnXyAfHqAdYyOe5OhioN9jYmUjIGkj4RrfZp7g5mppJyiVqWhX2hcXVmCgW5qf35yeW2IdHuDfZeZU4efXG53p4uPb4xVnZuKkklPV1teVY2Bj5SdXpmdhpmXkVNYWVtVTYyKfHtJgEhQeomEeHyKfoqNh4WBhVlhbmZZY2RnXqNUWlZUmldfcWJhXl9VWZCMhX6Kj7FeW1adTktOW2JakJR5k46Sn66inKaPkHJ7eHxwjpGYXqRjmJqdpKqhXl6rpYSCcoh9jHuWWp6GbZJ1bIaNW19eW1lQmpmNl7KibGRktLdcZliLgIyCh5Sji5idjIeGfm9yi0mCb3Vze3t/hYWCdHOKinpyWnJucHaNhXF0fXuVh5JZjG1sfXtxiU5SemtycX1zd3yGiXhvan9+eoCQiHqefHaOh4WBiXh4hZleTUNQT095eYuSi4V6R0RMT1ZIToVTi1JNd31+pJWQiZlPdkyUi3KJgKuwtmOippCZn1aWkE5PWVKOhI+PS1BRl4OHipGnXl+VSU5QWVlRUaGaWFSqmJ2el5KeiHyAo1apXKKZko+SiI1+jU6QUYCNjZtSpZqRf4yXpJiSsLG2q6u2rrO2tWSenJ6vo6ylkZ+QnKyllVtOlGFfUlBaWlhapJBeq1uxsaOxgGVlooyerJxZXVZbWl9bsWGrn7S1pateXGJgXV5dYl+0przDasKhpI+OopGTr6q/s6ihqbSqZllVoF5YmlmsqaVci52rmJiQmZWjX7CaqFuerV1YmZCmuHGqo52Vm5+vnKisrJWlqaO5umO5rauioZqpm4SJh56NUZZYXFRan6JdgFlan6u2t5Sml4yEe3yXmpqXj42bn6abq16zWFunp6NXVYhXXE1hV2FaW5CGppeppJKco6WcqKWhXl2gk5OcnoCdkaOooFFWVVJXWE1MgoiMq7S4qV9rsKdWbWhpZWeEV1NkZnZoXF2ZWqdbWWSvoZtYp1usWV9gVWNwWVaVUGNbgJpcVFdToJiUk5ORoJCKinyBg4uNfIGFgYaGdXuEiouXjZCKjouHfoF9jIWEkIqBhIOWjaick4+Ol4iajIJ9f5CfjI2ZmIuOkYOboYiJkIiViImXiXyHgI6Gi4qDhpF+eo2Mi354kpSMin2Fio2Fh4GEfmlmZHVlc293am1zhYmHcHh4bmdtbm52f3B6d4JrbXp6dX2FjYJ/dnGHe4d8g31rXmtnd4iDdYB6eoh6gHWRe3Fwa197hoqCgoN9fm1qdXR4a2d2e3d9hHmDe39+ioZ8jH9qgGlncXdseHFxbnRudHxygXp2gIp+eXyJeo6AhHyEhIB8gGxyZ2lraGhrdXOBfIaBe3iAgFSMbHuMi4eBnI2BfomXlI2ao6OGiYZ/hHiKi3qPm4V3cH92fnVtkIN6aHuRcG9+iYWBhUeHg01USUtNd3xubX15bHFkg292dnGLjUx2gUhSWIFsdFp7TIeDcHg6PkRGRkBtZnZ4gk98f22CfYB3RUZHSEE7c3BkYzw7Q2NtbGNlcGNucm5qX10+P0M6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+vDi6+vx1ujh2vXx+/jq597Z4OnxgPz66uPq3eTp5eve4Ov79PHr/piJkY6Ag4CLge7ZzNr66u3p8/nv9fPn4uHi1+vk6/n8+/frgfaGj5GHg/r+hJGT9PbW09DY3d/QwuDq5eX4hob8//Tq18rZ093V6v2A/PGB/4CA9Izm7/aC4/Lx/fT494eJlZ2bm5yysZablf2BhvyIhPn49IKShoSImIeOg4qUooeClJOdnYCJi4aKgYLj2Mriz9PK2sPEzc3Dxr2qoqGepaKqvbq4trfBury6nJajp6qor6u0tLXBxLuxubawtsHDvr+60M/R5Me8rK2ssrzDwMe5srKqsr7FxaOxw8G6y9HHwMOztsPTu7K2xcXIurGpr7+9tq+1rKmtmaSsxtK3rrmsr7m4woCah4KJk4+Lj5SQn4KKk6mgs7uun5N/endxdYOUfImBjn9/iZOUj5GAiHeShpR8i4N/f5uchZB6enCHiouQdnKFfGFphnOOhYeCfnl8d4B/gW13a3WOh31ubFhkYGp4cXt+jHN9c3yHiIB0bl52eYWFfnmGhJGKdoiBd4GCfH2FgYB7hoF5dHhsa2dqfYyMj46OjH97k4WDf62WgpB8jJWKiISIgnqLnZ2NpbCmo5eaho+fdH+RiIqclqKUoZ2WipqblpBQVlaEh3WAc3t7g4pVi2eEipWFkYWHUVhVoI+eq1xfX5FVVlVxVZ+MiZCbho+UZZaYU5eOg4N+dWNWVWRmT4BTS02QUZOVqmZhXVlaW1pnY1qjkqlgWFukW0xSUFdgZZRdUlVfelaAi4eGiZCIml6no5hWU15XaGFTj5VZWWFVkpSeoluOToeAgHaAe21vho6Aj49cXK2PhJOilIWXmZKOf5CFS4BHiVFZZmFdam6kj6anoJKXoaGorqKekJaacYCRjI6GT5+gl452goxNj4Z4f3p8hXyDhFqEgXl3jI+IeHN9dH+GgnxkfHV+kIZud3tvblxueFhUT1BHeW1ucHdzg4SGhoCAfIGCeoR3hHiNiH1/g4WCfXuYk355fEtJfGx5dINVSkVDhIxKklJNT5BxWYeKhJSMiIpOekZESEpMl4BXrmuunKyRi56pVFRVl4mJh4ugkpCKkIOHf4WUnoRzhXV5SUdKS0lQUFpZUIlapaeboJqYf46DkU+UVJ9SVZSBcIaHmouHn4qmXKSlopyug4eSqqOvoJWWo6JhXKpiWZ6VVIKjpJqeo52YVFFNV05XWV1nZlxaWqeUoV1ZWFaiYYBgs7Wntl5dY19cYaakmaedoqGqtL5graa3X61YXKWusLvAamtnqKaonLWtpquWtp2xtWOcmJqUpprCsKy+vZmTnYyRnJ+joY+UtKqkqLWqnLOzsaudo6e2naqonrOsramio56cq7K9Zc29r6y0qqKcmZqNlKCvo5yRoWVXYV1TVIBbVZ2Ri5Srm5WMk5KJkZKMj5CblKOdoamqqqGQUpdWXWBWUpWZUl9ioqmWl5GWmZmJfpeblJKhWVepsaijlYeVj5mTpLBWqZpSoE5NlV2UqLxmsLq5ua6ppFpXYGdlaGt/fWhsaLRcYLJhXK6spFlmWFdgaVlfVFRcZU9KXFthZYBTWFFYUVWPjISgkJiQm4ODiIWEiYyFiJCQkoaGjYB8d3qDhIqRfXmDgYN6gXt/gIKQkZWNkpWQlJeblI+Cj4iHnYmDe4KFi4uJipOHgIR7hY+Wl3J6iIV9hYJ7eIR9g4+dgXRygIWNgYR7go2OhISMf358aG5xho10aHRvd4iKmIB7aWVrc3JvcHVueVxgZXlyg5KJgIBzcnFtdoWVeYJ2emllbHh8e3ptc2eGfY14hXl3dI2OfY99enOIhYKDZWR8e2VyjnmKfX9+fXV5eoSHiHJ2bXqRiX9vcmBoZHB5cnh5h3B6d4ORlI2Cfmt8e4B9eG55f4eCb353cHp3dXZ7doB1goB8d3x0cWpse4eHhoaJg3Vwg3d6eKeXh5eDk5mOjYqKgXiHlpJ/kpqNhnyHdoaadH+OgHqDdX9xen14boSHfHdGTE6AhniGeHhzfoVQfV56f4l4f3FvQUdDemx9jU1QUHVDQUJXQX1va3V/a3J0U3uAR4B6b21mWk1FQ05MOoA9OTpoPW1sfkxIREA/PjtFPzZgUWdAOT51Rzk7ODxDR2FBPEBJaUpwenNtZ2lgb0V7eXdGQkdDVVFGdnhHSFBFeXeAgUhuPm1lbWNsa15cb3dkbm5JSIVmXnB+cWZ3fHh5bH91QnRBe0pQU01HUlR4aoCAf3N0fHx/g3h4dICMbICNhoV5RY2OiId4h5VUnpeLjIOCiHV1cU91fHl1io6Ken2Ie4WFg35ke3Z9in1iam9mZlhna05NSU1FdmlpbXFvg4WFhH17eHp4bXRibWF2dG51e4B+d3GNhW9qbUNAbVxoZnhNRUA+eINCfkZAQHJYSXJ7e42Ef4JIbUA+QD88b4BAfE56bn5tan2JREZLh3p/gIOVi4uHjoOJfoGQlnxugXB1RkFDQj5EQ0pIQGhHg4J4g356YHBjcUB0RH9GSoJ1Y3d3iXNnd2J6RHh4c3GBVVdje3aBd2tpcmw+OmtDQXt5SGyLjoWGiIV+R0dCS0VNS0pORz08P3NrdkY+PD1wRIBDeHdue0BCSkZFTIB/dIF6gn6IjpNIfnaDR4JCRnp9eH6BS01KcnB1aoF3cXNfeGRxfEZycXVremiEcG18gWZmb2VteXl8d2ZqiX53eIZ7aH17eHJobnKDb31+eI+LlZCHh3x5hYKHSYuFfHuHfXx/fX5uc3yNhXp0iVZIUEs/QIBGQHJlWmV/c3V1gIR8f3pwamVuaHt5go2SlI59SH9KT1FIQ3V2QUxPgot2enZ2eHVfUWluZmh0QkJ/iIKDd25+d35weoA8dW5Bh0dJi1R6g4xLfYiJj4R/d0A6P0A4ODlLTD5DQGU0Om4/O25waTxIOzpCTD9GOj5DSzUvPD5FSIA4PDk/Oz9ra2qJe4J8h3J1fXh2e3dsbnJ0d29xfXRvaG53dnZ8X1pmaGlla2FjZGVvcWxnbGxpbXN+eXFkdG9xhW9rZWlrcHV6eX5ybHBlbXl/f2BqeHRueXx4dHxvc3uKdGlnd3V3a2tgaXd7dHmGfHt5YmpuhYx4bnZrbXZzfP96w3qDe4l6AXuJeoN7hHoEe3t7eoV7iHoEe3p6e4Z6iXsFent6enqKewd6enp7e3t6h3sBeoZ7iHoEe3p6eod7gnqEe4R6A3t6e416gnuOegR7ent6h3uVegF7h3oBe4p6AXudeoV7onqCe4V6hHsKenp7ent7e3p7e4d6Ant6hXsEent6e4d6g3uVeop7Anp7inoGe3p7ent7i3oBe5B6CHt7ent7enp7iHqNe4N6hHsDent7hHqGe4p6CHt6enp7ent7hXqDe416AXu0egF7knqIe5p6Ant6hXsFenp7e3uPeoJ7jHoNe3p6e3p7e3p7enp6e4d6jHsJent7ent7enp6mHv6egICBACA1NzZ1dLc2tzX2dLOtp+6pbXA4t3o7d3d3Ofh28/KzMu7ztjB4+DPzdDS0sjQ2tPQw8TPzcvIwbChwsLH4/Xb6url0dPDwrq6yrq+x8rKwsXFsMLX0trMxcrJyMW+wsDXu8DOzdLQ19bax8S8ycXFzL65wbzJw8C/1sLW28O4ubiAwb/N0MHZ8drGzsvi0NTDyMrH6vLY4tfv3+fv/PLm6dfg5+Lg3uHi7+bl3N/i3uDv6urfzdrY08bi39/T0NjV0d7Turi61NnZ6ePx7+T+k4Dc0MnTy8fK49fY3dLPxMDA1ufQ4P6+huPd4v7838fg1t/x3vz28d7m6bq28paJkaSAppyZjvOHmJemqZeVjZKLuq2Wj5OO9/v/hOrx9/qGiIuE8/Hr6u7o7vaCjJKUg/eIjPOA84GG/oD65fGBiobl1OPl8enq3YH79MvYysSztb7Q5dXs6u7a2M/k4t/o2ePi4uLe6vTx9f6D/YuIkoyIl4Ty7+vb3/Lt6uzz5ubdy86A3+bp6/71/Yf65fz79IDl2OHp3O/n3NDbvubo55L58+XtzOX194L69drH1+LW0crGy8Ozwr3Ugu/y5OzL28/Rz+vj6t/dpcbDu73K4tXH5+Ho6+fU1NHU4e7+hYaOm/L0j4f48eb8/u7djPfr6t3R64Dn0Nvf6Pzp5+Ph5PmNiZGAivSD4Pbv2+Hx8vKE/vv7hePL1M3H3eHdxc3O193i/O3o+PyB9P+DifyKg4KAhvLz7vjn6fWA8vTu/fDq6+/2htXr7+Hl5+HV9ono6Nq2zMa1z/f39OD49NuHgOn1h4SNhIPh7Ozn/fHn/oqDhJCSl+jrhvKjlYyDioPw/vv97o+AgP377vj8hPeIgu775+Dk4Ozj8+3h8uvp7ID2+ubX2tLS7IuJhYDai9XUyL3i0s7S7Pn0ztzQyczVzr/U08PByMrT2NPr39/gz/vk5ufv6erj9uv48uXq5dHf9u/ngef17Oba/u3j9fbz4fTz5NvY4N7k0tHW3Oj+6+n5hoH5+4OA7ujz38XL64SE+faBgor36e718PX50s6C/f78/5Df+/7xgfGDh4eD69vX4Nnf0dDjgoz6hY7+9oD7+Nrh5+bj4+TW6IH8/Pfs9uzu9/WDg4SLg+Tr5vrq9OfU8vaImZ2VrKeJh4r+/PH6hoKG84PxhISIg4WAgYehh5KViIqA+ICAiYf7hYOD/ILh3ubW4r/BwM7QzMGxqKSoqKWtsLa5srqwvJ+jqKijqLa3t8K1vq61tr+3wsnCubzExL/I09HVzM/Q3dDGxLy5v66wtLynubm+ycO1sMPJ0MzAtqq8t9vI2LfEtru4uMbFw767sbSepq+rnKCgoKyrpbi+tLa/uciAnJqQiIGFhY6KlZaSh3qUfIiKoZWWkICCgY2KhIGFh4h8iY+Cm5OJg39+fXZ+kJGRiYaIhHx/gHZuioF/jpN6g4GAdYB6g3x4hHV4eXh4d3t/c36PiIyBfoGEioN1fn2Ve4SKhoOChIaJb21odXeEkYuNloqNhnVufGt9hnNtdXOAd3V/e259jHVobXWViJiJhIp/mJ2AhX6WhYySnpSHk4iRm5WTkJKappqblJedkIqUjoqDdoSJhICdl5mKiYyEgYd3Y2VgcnV3goWUjoGcYlGBd3iHfXZ3hnt+ioiMgX2Aj5d9jKVrWpKMlLGymoSckZOlh5+in42aoXJjjllJTF6AX1lbVI5VZGZvdWVkWl9Wd2pdV2RhpKynVIOJjY5NVVpaq6SYkZGRkpNNVltdUZJRVZVQnVtcqVWkjJZUWlSEfIOMm5CIgE+annuJhIFxbG98joGdn6OTk4uflIqQgIeNiIN9iIeBhIhHhUxMVFJSX1OYlY6Ch5WQjo2WkIuDdXKAgISEipyWm1ORd4qKh0dya3B8coOEgXZ7Z4GAf1uVloyOaHiHjE+Xk31rdXp1cW55hIJ6g3mPV5GMeXlga2FqbIN7h31/cXl5eIKPoY11hnt+goJ4gYKKjoeMR0NKWHl/WVSVi4Oal4t9WZaOjYN+lVKReH+GiZuIiIKAe45VVFqAWJxWlq6mjY+gqJdTm5SSTnxrcmtodn2Ab4CFk5ycqJWFjYxLg45KUY1PR0lJU5iclaGIhJJNj5GSp56bmJycVHyLjYeMioqCn1+dnpR8kYp8kK+soouho4ZaVJOfWlhgWFaOnJiWr6KWrV9ST1VSWXyEU5dxYlpSW1ebqqihlmSAWbCroq+1YbFkXaqulpaYkp6Wn6CapZ+cmlOho5mToZ6huG5nXlWDW4SDe3ijmZikvs/EpKiemZ6nqpqmrZeRj46Nj4ecj5eXjMGooaaunpqRmpGhn5umrZ2kvq2hXp2opqidw7uuv8fGpbeyqaSfpZ6ijIqOk5yqj4ONT1Kco1aAmZmikXyAmFlXnJVPUVqimqKmpKepi4hbtLKvo2F+maGYV5xbX15aopaWqqOmnpinX2ivXWKmo1WuvqKkq6mbnqOaql2vp6SanZman59WWGFraLe/uMWvsZmBlZRTYWpme3xjYme6t6asXldbpFmrYF5jYF1XWVxtV2FiVFlQmFGAWFaXVlRarlmWlZqPm4J8fYKJhYB3foONkomNh4F8eH99kHqCi4l+gIeCf4t6gnZ9f4qKlJaPh46Uko2Sl4+ThpGWqqCamI6Ii32Ag42Ai4aJlY+Df4SCiH1vamZzcpGElHaEeX15gY6Skpack5GChIqJdnl5dn13cHp/fICQiJiAhYN7cWpvbXNyeXt4bGB4ZG90iYCIh3h8fYyKhX18fHdocndpiIN6eXh7gHiBjoeCeXd8eXZ7fXBmfnRwgYdxgISIgoyDh3lyfXF0e31/fH5+anqMhYl+eX+Gh392fX2Tdn6JhImNkJSVfXtzfXp/hX54fnl9fHRxgnOFjHVsbGeAbW15eG2Akn5ucXSNfoh7d3pwiI90fHWRhY6WnZOIk4mSlo+KioyQm4+LgoSJgYGQj4+IeHt9dWyBdnVsbHBwbndvYGRjeHh4hIWSkIOcX1CEd3R/dWloeGhmbGZsZWRoeINsd4hXQ3Z0doqMd2N4bXCCZ3p6dGRyek9Gc0g5PUyASkVGOl46RkZOVEZGPUA2UkU8OEFAZmxuP2RrbGw7QENCeXp1b3l7eno+Q0RBNmM5QG08ckNFgkN/a3BBRUFoZXF2h394bkKCgWV0bmxdWl5rdWR9gIFxcmuBenN5a3N6d3Rvcm1scnZAd0NAR0JASj5zcnNqbXp5eHiBf4KAdXWAgoB6eYiCikuMfZWYlE6Cd3mCdoaCfHF+aoWGhV6an5CWbnmJiUuPiHdodXpzbWpzfXlwem6BUIaEdHlkbmNmZ4F7hX16Y29sZWlzgnFedGtwdndtcnJ1eHR5PTlAS2BjSkZ8dG+GhXhvUIiBfGxpfUh/bXuFiJiDfXRua3ZGQUaAQ3BAaoN8aWt8g3tGhoWIS3tpcGxndXx+cHyAjZGQo5CBjZNKgotESnxDOjw8Q3Z5coVybndAcnZ4jYKCgoSFRmJwc21uaGRcdUhzdmxZbmJTaYaDfWp7el1AOF1sQEJNSEd3hYF9koV+kFBIRk1OU3F2RXNURz84RER4gH17bEyAQX92bHl8RX5KR4GNfoCCgIyCi4p9hHx4dEKBg3dxeXFvgVJLRT5bSGJkXVh7a2VrgY+Hc3lvbG9vaVhla2BeY2VqcGl4am1rXot2cnmBdXFqcmh1dG95f3J5lIl+TYCNh4x8mo10gIWDbH9+e3h5g32CbmxqbXyLdHB5QkJ6fEOAcm96aFJWb0RGfntDR02Ddnl+foSIcG5OmpqYkVdshoh4Q3FBRUdCdnJ1hH6Bcm16RUp5Q0h5dT19i3R8iIqEhIV3f0V9eHNven2FjYlLRkhPTYSMipmEiXRZaWAzOT03SUo5OT9wbmNqPDg7aDxwQT5EPz06PUFQO0REODw0ZDeAQD9sPTxAfUJyeIN+jHBvcXmAfndrbm92d290cnN0cHZzgmlocWxjaHJvbntpb2RqZ21mcHRsY2h0d3JzeHB1bHR4jIJ5eXR0eWprbHRnc250f3draHBxd3RqZWJybo15iWp2aHFrbnt6c3Z7dHdtdYCDcHN0dHx4c4CFenqBeIP/es96gnuWegF7lXqIewF6kHsEenp6e4R6hHuIeoV7EHp7e3p7ent7ent6enp7e3uIegF7oXoCe3qHe5Z6AXuFegF7jnoBe4h6AXuQegF7jnoBe5R6hHsEenp7e4d6AXuGegF7jHqEewJ6e4h6BXt6enp7k3oGe3p6e3t6hXuHegF7iXoBe4l6AXuPegR7e3p6hXuIeoZ7BHp6e3qGe4V6gnuFegR7ent7j3oBe4h6hHsCenu1egF7nXoFe3t6enuHegd7e3p6e3t7iXoBe4R6AXuEegJ7eoR7iXoIe3t6e3t6enuLegF7iXqFe4p6iXuEegZ7e3t6e3qPewp6e3t7ent7e3p7+HoCAgQAgMfK0L7EzOTT3szOwqykpKWppsnM2djX2tXY2tHVytLZ29nMxcrP0djR09bd0N7Qy9/n6NHRysWsvby82trr4NG4yMK7squztsDHvsW5uce5vL7EzMS8zcm7ur2wwL/Gu7bE1uDv1eXa2NLRwcO+wsS0yMDk7MrNzsbO4PTExtDNgNLZ1tvi3N/YzdjH2fHYyc2/0szn5Pvz7OXp5/Pz6fDo/O/cyMjU5fH68Nfsg++Ci/b63Nbi69/i0cDCv9bV0ezg2d/P0crd+t7s69PS1feF+o3j0Mzj897X2vjx9+COlOnVwbqirtPQuuHW9pjt3NfX4PT3g5X16N/V/er7pIOTgJmSk4r+hYePnI2Rh4OBhp/BnJqUjJWK+/T+iI6JhYSA+uX78uzd+ePx+ZCYrZ6LnKDj5efs7oD79e/v9PbgwsjZ+YH+/ebd8dTGz9XNys7K193s5+n12Ozn29XKzurz7t/d0PiGg4Py9en26+3c0NrK3O/x8vX7+evv4eTr4+rogOzf64SMiP2JgfWE8NuE/uPY5+Pa6ubsxMuJiMne0N7U2N3q7vfs1/T85tjX5ujY0s3DysS807uwvcPS2c/N0+P1hObe5vDgxLu3try81dvo29XPutLBvebl4OyF/dve0d3p2/Tb4fHs3OP75oCR5PTUytLW3eDZ4uHt6/uBhIeFgPXd6urh7trn94P28YDq/uni2PHl8fv66PX4hOrv5MvJzs3QzOXi3vHm9/70/fbqhP+E+oaEgev48fDk7emAgYLqhPXp9IDK0YON1OLg08/X19rm3+ft8enfhIPzjpGD/92FgOLm5t3P5YH3hYqQh4n3gJCglaeO9e397eT++vDvgISQhfWF++308fXt5eLZ39vP2+3j4d7m3dnf3+nwyt3miPvm1+Dd1YPn8uTs383Fydnj6LzDzsvJ79bn5M7Lsa/BzsLP09TY48/m8ubrgIH6g/eE+e3ozNHO3MrGytXW0tfk0drz4tbd6N/ugOzb5uXe4Pjr8vGHi+707IaF7oX+gPnt9u/49vaG+O+A2Nv859/a59bS0s3V//Pk7oPyh/b9gOfL4Pbk2On15+LdyNHP3O/y9vzy/vPt7PvSy8q3uPqJ5+fq+PGA5euBjY/5/oSK/u/q9Nri1vj++4KPjJqenKOLlZmNlY6IjpSK+IX6gY2Ejfn99oGDm4OZlo+E9fX/gIKKiIjt9fzx4+DgzefBssa7obWxn5+ppbGzqqqxtK+zubmnr6m1qK2dtbOoqrO3ucG4rbDCzr6/yMbGz87Az7i/tsi/uMm+tse9sbXCvcDPyc3CuMPFxsnSwMPDurvOttHA1cLHu8bLw8PEvL6ppJqipLDHqLCns6mntbqvqbXMgI+Rk3+ChpyKkYSMhnx9hIWBeZKRnIuGhn+Fi3+JhYiPkYqAgoWEjI58eHqBfZKJipygnIyOiYp1fnVwgXyIhHtnd3p3b2tscH+GfIV+eoR5fn6CiX92gn1vdHlweYJ/cXJ7hoqUeoeDhoCDd3t+kZWImpCjn352dG12g5lxcYB7gH2Ae3yDenx3cn9zi6SYj49+inyRhpiOhn2Dg5OXiZWWppWFeHh8kJmgl3+WV5NOVY2TeHySmZCRfnN7e4yKgpKAdXxsaWV2k4SUlX15fJBMjlaGe3uOmIN6fJmVmIJdZJaQfXldYXh6Y4iAnm+ZiXt5hJSUWGynmpeNqo6TYENRgFRPV1GdVltlcGNpYllaWWJxZGNgW2JboZWVUlZRT1BOn5Sjm5yLnIaMlFxibGFUZm2Zl5abnlOpopicop+Qd3uAnlGYjHx0iXt0gYyLhIB2hIaZnJ+tl6ilmY18fI2TkH99dZhTTkyGh4CPjI+CfYl+i5KWlJOWkIaJg4uOhY+HgIR3gExTT5FPSIRLhHtUnISAkYt/k5STY2dQU2N1aHhoaG92f4yGeJShh3lygIR6fXl0hIB3jm5iYmRtcWhkanSETIB3iJ2WfXd6dnlvfXuFf32BeJGEg6GViYlQj3N0dIWViJqFho6Mg4mfkldmlqeBdnh7f3xzfX+IiI9KSU1QgJaQpKmdpYmUn1aVk0+FmISAdY2DjpGNfoqTU5OclHt6gX+Cf4+JfY+Bj5mWoJiRVqBTllJRT4qak5WMkI1PTU6HUJqcqlyNk11lio+GhYGKkpqflJmbnZmPW1iaWltRn4dZWZeaopmIlVSXT1RZUFKRTFdjXW1ampy4pJqsnpaZgFVcU55hvLO7s7Otl5GMlJCGkp+am5OdmJScnJ+igpKaYbWejZSMhFiUkYqQiIB9hZisr42Ql5aVtqS3saWki4aXm4+WmJWZoI+ntqGnW1edUpBUo6KpkqKaoZCFhJOOh5Spm6bGtqSyuputXKuhsq+in7KfpKNdYZumm1lTjVOdgJ6UnJWemZVWnJZThoylm5qVnJKPj5GZt7Ken1aXVZSfU5B+maydkaeyqqmpl6Sbp7GysK+fp6WfpcCfnJ+MiLhfoZ2hp59TiY9SW16joVhgrauvuKWrnLu9t1tnYmptZ3BcYGJcYl5bX2VcoVWhVVxWY6utqllUZU5jYllRmZmlgFRaV1iYqLmwqaSllLSRhJWJcoV/c3aCh5WOgXt6dnd6hYd7gn6Ec3hugHtub3V9gYd9eHaCiH2BiYaHkIqCk32MhZiUkqWThZGJgH+KhImVjZGEeYWHgHuEeH15c3iDbox7kX+Ff42QioyRkJKKj4F6eoGVfoN8hnpyfIN3d4KZgHt5fGVmanxueXB5d21vdHRuZXp6h316f3qGjYGFfn5/f3lsbXd8g4h7en6Gf45/eYaJiH+FgYNtdW5nd3SAfXhrgYWCeG9vbHmAdn95doFzc3J5gHhwe3hudnpxfoWFeXd9iYqXgIyIiICCdHVweXdreW+Ii3N0dXB7iZ1ycHhxgHJ1cHF6cnZ1a3Rod4x/d3ZpdGh/e5GJhn+EhZGQg5GRo5CAc3V8jJSZi3GDToVJUo+Vf3+HioGFc2JmZHJxboByc4Nzcm16k4GSlYCAgZZOkFaBc29+iXFiYn50eGhPWIaDcWxTWnR1X3prg1h4ZlxaX21lOkZnXV9aeW9zTTZDgEM/RDtqOzxDS0BEPzk4OD5HRkhFQEhBdXN8REc/PTw5cWJzdoBzhnN3dEZITUI5SVFvc3Z7ekGGgHqAg4FzW2RuiEiLhnZtgWxncnp3cHBodnWAgYOOdIiHfndpboKHhHFqXndDQD9rb2d0bHFkXmpfbnV5eHd6d3B3dYGKhZONgIt5e0VLSIFHRYJKh3lQlX93h4N7jY+Mam9SVW+CeYl2eH6CiIt+b42ahHdzg4h8gH95hH10hmtgZGlzdmxma3eFS311gZGJb2RlYWJbamt1cG5yZnptaIN4b21BdVlXV2p9dIh3eoWCenmLeklUfY1ybXZ8hIB1fHZ9eX0+PT0+gHJmdHpxeV9ndkJ7fUVzh3Z0bYeBjIyJeoeMToaOiHRzfH2DfYuHeIZ1fH13fnJqQ3Q+cT4/P2+EiIuCh4FGQz9lPHFxf0ReZUdMZ3BqZ2JkanR5cXp/f3hsQ0JyR0lCh3JPTX+FioBvfkd/RUhNSEp9PkRJQU4/bHuYiHqJfHJ4gERLP3BHg3yHg4qKfnx5gX91fIiCf3aAdm95fICDZG91TYlyY2pkXERucW52bGBZX218fGNobnBsf2x4cGdqVVZkbmNqamZna1tyfW53RUV9QXBDf319aXh5f3Nsbnt4cn2Re4KZgW93eWZ5QXdwf4F2dId4fXlJT3qIgElDb0N8gHdtd3B2cW9DeXRCaXCNgHtzgHh2d3qDoZ2IikyFS4KIRXJdc4Z8a4KPh4ODcXdveH9+fX1ve3Vtco1tbnZqa5VMd3R3enI/Z3FET0+EgURGeHl/jX+Ido+MgT9FPT89OkIzPD87QT07P0Q+azttO0E7RnBzc0A+TDlKR0E2Y2ZxgDpBPz5od4mDgYGGeph4an53Y3d2Z2hxcXlwZGJlaGpueHlsb2twY2hgcm9lY2dwcXVrYWJweWprc3Z1eXFldWJuZXl0boJ4cn95cG93cnV/eoB0aHV4dXN7bHFxbnGBaoZ2iHJ5b3h7b2pvcHNtc2puc3qPdn55g3x4gYN3bXWG/3qtegR7ent7n3oDe3p7jHqCe4x6AXuHeoJ7h3qHewF6knuDeoZ7inqHe4V6AXuLegF7oXqDe5x6C3t7e3p7e3p7enp7i3qCe6Z6AXuZegF7kHqCe456hHuJegR7enp7jXoBe5R6B3t6e3p7e3uHeg17e3t6e3p6ent6ent7j3oKe3t6e3t7enp7e4Z6Ant6hXsBeoZ7iXoFe3t7enubegF7hnoBe6V6Bnt7ent6e5h6AXuKegl7e3p6ent7enuIegR7enp7kHoGe3p7enp7n3oBe4V6Cnt6ent7e3p6e3uKepF7A3p7eoR7g3qIe4N6hHv8egICBACAzsjHydnZ19zayLmyrK2km6exu8XO3dbW2cbEyMrIycbLyd/ExcbTzN/Q2t7l59fT2t3nysGwtq24uba2wsS3t7PJwcC/tqyztbCmpL60x7yzqbC6vsDVyMLExrvUvLfCudO7ztrY49XZ18fizcPXzrvC0+Plytbc2ebujt3g29+A3tHczcbc5eDo2dji3eHH3MbW5/Xr3trU0+fz8fj09dfe3uji2Ojzg4j38e7p3PH05MLH1NXe+9PmzsrQ1cvZz+rm6+Lv5tHs5OPGsrPPv9byiO7n88jE4Jj7g4+F87voguTdztHRgNnfxczp+rvxj4iKrqzA0bKtgO2Dg4SXg4eAg+33nJGC+fP4hYelkYyMlPKJjoWF8vuJhf7s7/eIkojv8f3v893i3ez5gJiUqJ63l5WC58ny6Pv24rnt5YzZzv2OhoGPloj/3+by1eXM4N/c+fOD6+/k28TKzMy81MLBzc/X3Nrj6Pb9h4Xw8dbYhOjj7f2Jo4aF8eyF6PHv7O2A2+OHkJv91uPn3e7o65jj59jy2tri8Pzi3uzkyrrHu8TX0+Th2+bg5ODl3/T17+fh29LMz8LGvrXGss3Dz83e6vfkytKnu77BusXXydjcy9HU5ufR5szMwdfW+uyCzMTk8+vh1dnp893HsbPCvNHIz4Ty4uDi5tnU1NXngP3w9YCAhPjm3d/1+Or9jOf64t6G0d3d1uLt4PCC/NTL39jU08vR7orf4O3N3Ojf6t7f3+n6///2gPOE5e+C9+/y6fH6//jw2PDm8db+9tTD2+fq2tng6+fo4vGBx+Lm6vDn//P1+Pf45OnZ2eX25M/ygYiDh4iMh4qQk4uH6/iNi/Dr6OqAgezxgYbo5vDl7unh5uXR2M/L19LW2NXP2t/m3PDu3eWP/O/v6+3biIiG/fbn3+Ly6/LeycvUw9Xi6NPDxtPE2tDZ1cbAx9rj3Ojo1N6Dgv/43Nvaz8a/uL23yNXp4uvlxszQubbR29rY1NLi5vHl8uPu7IGB+Pns9fTxgoOMhoqAiPXv9fvp+YGI7d7Q1erRxOyT4tnFvrjW3OLr6/aGiPqAhvnx8MfqgPPxg/vX38DW8OrG0fLr6YH/hNnZztn7zuXe0Nrj84D2+/379YaToauW8+314tfh6+CN/JOnsqa5vYSMjYqdsvaFiv/99uSF6N/i8YKLiIGC+4OUh+//9P6AiaOM9ff66+Xu6ubZ3NzYvMK+oa2fs7y+tKGpn6Cfk6uqu7GutKKpqKe4sbGvsKyrwLi7urm5uLW4uczDy9jd0LjNydLKysC/rK60u8TCr7myvsTQyc3J0cLMwdG7zNLMvM/fxcHEy8TBsLrAurOhoqamrbi21bW0rsG9x9C9xMKAjoSAhI6Lio+Sh357e4J+eH6Bg4aJjoeFiXl6e3+Cfnt/fpKChYCOhYx4f4WSn5SRlpKVgYB4fXR/f3Z1fntvcG58eXuAfHBydnJkZXtyhoJ/dHp+eHmJfXuAgneNe2x6dY52gYR9gHN4eW+NfniOhXiEkJyVdHl5dIOGWYKFg4+Ai4CNeHOBhoKRg4iWk5+KnIeNlZqKgHhydJCen6aipo+TjJWJe4qVUVmal5CXipmdkGpseniBnHmPgX2DioeMgIqEiHaBf3GMio50ZWV1YnGMVY+Jn3RwhFqPTVlRjmaOUYuEfn1/VIWFbnuZpGeQWE1QbWuAknt5V6BWU01WREeARHiRbGlZq6OhV1dqXl1aXIhUWlRYoLBiX7iimJZRWVKMl6WcnoSAfIqXTF1abGByXWNZooyom6WchmaWh1eIfqZnWlNbYlWUeYaQd45xgoF8k5FUkJmWkH6JiIZ1inNzeHiAiYeOjJWTUk+NkICMYJ2TlZlUak5LiYhTi5OVlZGAgIRSXGSZfYWGeYuDjGSIkI+njYqMlJZ1dIZ8ZVpoXmR2cX13cHp7gX6Bd4WCfXyBhn51fnN5bmhyYXVpbWp8iJKIdIFkfoePhZSki5CMc3d8kJaJo4uMe4yCoItRbWyJmpSMeYOUnI2EdHSDe42ChFeahoKAgXV2fXyOUpuTmlOAWqmekJOoqYyaWYWch4RUbn55eImSh5ZUoYF3iYV/e3Z+mV6XmKSAipCCioWIj5yqsrOfUZhUhpZWnZyakZigpZ+ZiKOZppC4q4l7kpGbjImRnZ2bnapcgJeSkZOFlYmPk5Wkk5iTlZ6snICcU1hTV1hcWl1iZltTjqNiYqmflpuAV5KWVl+mrbWrrKSYnaaRl5GNkpCWk46PlJOWipiYio5hppeWk5WCWlhSk4yChIyjpbWhlJGWi56nsp+Rl6WVpZiel4WAhpumpbGukpRVUpqVf4mRkZCSjYp9g4eYlKGfjZ6kkZerqqSek5yurrWnsJugnlRWq66rtaynXFdZUlWAVZiUnaWUoVVdnJOPlKWShKBqnpKFhXyTl5uZmJtWWKRXX7Ozs4+vYqmsXrCPnICSqaaFi6KbmFa0Zqiro6zFlqaai5KbpFWdoqWfnFZcZG5iqKS3raGrt6VsuGdyf3iGil5iYF1reJpVWqitopdjnZSboldeWlJUoFRgVZOqprOAYXljnqCnoJqopqCTnZ+cg4qNb3dsfIiQjniDd3ZzbH18joN+gGxpaWt2cHF0dXd2g3Z8fHx7fnx+gJePlaOlloaZlqCXlY2GdHB3e4iNgIp8hYiIf4R7g3N8coRxgId/dYOUfICFjISAcn2KiYmBhoJ8fYKDn3l5dIF/h5OAiIaAgXRvbnRybnV6dXF2eIB9dXl6en1/hX2Agnh5eXh2b2hrbX1udHSDfYd4f4SNkYF/hYWOfHp0eHB5fHFscm9la2yCgoSGf3Fyd3BiY3x0iH1zZmxzcXODenl/gHWQfHGAd455f4WFinx+fXGKeXGEd2tweYmJcHp8eYmNWYOAeoCAgHJ8amZ0e3yDcnWAfId0hnN7goqAe3V0dY2Yk5ONlICHg4+FeYaPT1WQiYGEfIyRi290gX+CmXqHcmpzeXd9cYSFjX+HgnOLhodxZmt7a3SLU4eAjmVgcUZ4QUpBdlR+TYd+cnJyTXl+Z2x/h0xsQzg1RkFMU0RFMF87QD1JOj2AOFxpSkY4aGNoOzxQQ0E+QV08Qzw+bHVGRYx3cG0/RUBmbXh3gGtsbHd6OUQ/Sz5PRExEgHCIgpGHclWDeUxyaYxVTkhSWU2HbnyFan1ldnZyhH5Kf4WAfWt0d3ZneWZmbGttb2lubHFyQT9rcWBtTYF7fYFIWUNAdXhOhJOYnJiAhoRRVl2McHuDe46HjWCFhoGWgHyEkJB6eI6IdWl6b3ODfIWEfYF7gHt8dYeKh4WJjIR9g3d3cGl4ZXltc218iZSHc31fdXp4bXeBb3Z4ZWlrf4Jyh3FvYXBognFDV1Rxg353Z21/i3x1ZWh2coF4eE+Ug4OHhnl1cmx6RHxzd0SAS4h7cnSDhGt3RmqCcnJNaHVvbX+Ie4xOlXdtgH17enJ5lV6Wk6J+hYh2dWtta3OAiY2AQnlHboFPjoyJfn6EgnlxYXxzfmeHgGJYb3B3aGRtenh+gJBOZ3x4eHtxhHqChYSKeX95eYKUf2N6QUZBRUZIQ0JER0BAc4dTUYN9eoKASXl3Q0l2eoaBiIaBiJJ9iIN4f3t9e3ZzeX6EeIaDcXJOgXBwbW1cRkRAeXVsaWt8e4Vya2ptY3V1fWpaYnNldG51cmFaXG1zcn57ZWlDQ354Y294eHV1cnNpcnaGgouGdIKAcHJ+endtYmh3d4B1gW91dEFDf4F+gHl2QkNIREWAQ3Rzd3xrd0FJd21scoNxZINfi4J2eHCGi4yMi49QUIpGTIaBgWB8SoSHTI5scllqgHxgaX95dUKDTHR2cH2cdYR3Z2tweEF8iI6NhUdJS1FGcW+De3F8iHNNeENHSUFHRjM9QT9NWmo9QX2AdWhJaV1iajpEQz5AdkFKQWd4b3uAQ1dHanJ/fHaFhX91gIKCb3qCaXNlcnd6clpeWFlaWW1tgHhzeGNlZGVva2tnaG1teWtraGtpamhrcYF0eYOJe2l4dn1zdnJzZWVrb3h6bXlweX6Aen93fGx0bH1rd3xya3uIcXZ4f3hyXGh0b3BqcXBxdn5/nX18eYiFipN8gHv7egF7q3qCe6h6AXuGegl7ent7e3p6enuFegF7iHqKewF6h3sIenp7e3t6enqHewF6hHsEenp7e4R6g3uKeol7inoEe3p6eoZ7jHoBe5V6gnuEegF7hHqEewN6enuHeoN7iHoBe856AXuTegF7inoGe3p6ent7iHoBe4R6AXuIegF7inoBe5B6Bnt6e3p6e516AXuVeox7BHp6e3uEegV7enp7e5t6AXuGeoN7o3qCe6R6gnuGeoZ7hnqCe4h6AXuLegV7e3p7e4V6BHt6enuMegN7enuMegF7hXqFe4h6Ant6jHsDent7hHoBe4R6hXsEent7e4R6g3v9egICBACAy8rT1tPX1NnUyce5vb6+vcPFxsa1wc/D08rDw9nZ0dXVuca8wsXP2tPT0tHCtNTY9NPm3dPaxLimp6ixv+vNxcm8ur22u721trGzwLS/wMDJurOzwc3L0NzUwcXGr7bC0cnH0drWzLa5x8bd48zNwdza1c/k5cPVz8je7dLP09KAxtLcx6vO3N7k79rX4ebe1P3Cz9be09LY7uT6/fOD+fDo2d7h2eX4jPLb/+vm7vTxgfrr8tXcxb3s8+TXz8nPyOP8gf7dyde7+vvkya/F0O7s/YON2d3ExMre3bzR1MqD3d/atMfH29yC/L7Wv+Dy/f6Mk4ern9bn0JuK8IaD8fuA+vOMivvs7ubk4+mA5O/f+IWVi4PY3eXo7+T1ksuNgfvu8+rc7+zlgoGA9P+NkLey2fSD2dCJ4drZ5tvGs9jg5t/96+7cw4n3yc7Z3eHv39/+hpnz7+LKu8a3vdHSyNXK8ITT38jIztz07/Tpx83P2/P6/YeHk/2SjeHsge/28+2A9IaCqIeF8uKA/oyA6PLh3+HtgYXk3enr5c/Aw83Syc3T2NnVy8K5u8jK9vjx/+Lh5tXQ2MXMxMfKxMLPyb3Qzcq9tKGformrx9T55svNvrfC5tmD2rzYgcbT9u725+LV2cvP0N/Hya+0sLmxxtHIxuvuk/jt6N3W2vP2gurT4O6A5ejt5faH+IXwhfPu5vPoxtmX9eyE9Pf+4vWF+uLkhNLn8/mG+vnXz8XbhYaH+9f89/Xg4fOD8fHQ4+fz9oL4koj11tHb6/Tr6t3i9d/ny9no48vs6YH6ytbrgI7wgY2eiYrp4uPjx+zn6eL2+4uH+faAgZasm42Fhvv464bh7/iA7u3llIrt/efr393X1NDByMvLz9fZyOvk1vHd3eLf5+Pb+vD/9vbb/ejs94jl4dnc187HxcbQ0d7c3tnJvcfS2NPY19vS1dzB4e/o0vDv8evh8MC7vsO91eLOz9bv4e3x2tHVxrfLy8zY0MbT4ujs8uvl8e/z4Onm5e/oh4X9gvSA/PqYhoaFi6X2hPjo+oPslvv3+9fb2M/p5eLk0vP10NPl3efy8ent/oKD99vAzbG0x4Xi5Ovy7oCJjoWA09bG3ODW4Nvk/N338/H15IHwiISB9IeD4I2RgY6OhYaNiKKllImKl5aPlIqCgIyKgOr0gejj5Or08f+C6ICNhef6+veA6/2EgoaE8uDq0OTfy8nWxrfIu7e+sMPBtam1pZmcqrqxpamtsamor6yyvLy4w76yrKOrrrS+ysDDwcLYzsbZwMTG0NPPzLfBsL26rqu3s7LE0Me+ucHCwLesxbautb/Nv8fFwbrSuL3O4qu8yrCuuqqyub2ztrW9say5w9y4xNCAgYCHjoyWkZWVjYp9hISEgYaFg4NyeIR6kImFgZibjZCReYN8gYCJjoaFhol+eI+To4SWk4+bjIR1cnJ2fZyAeoF4en1vd3hyd29zgXaAhIiMf3V2gIh+go6JfYeMdHB4gXx8hIuHfGhqeHiRln+CeJCLiX6PkG94eHOAkH17g4yAhI6bg2SBiYmQl4aGj5qUjrd7iIiHfXRzi4agqqFcrqqbhYqLgYyXVpF+nY+OlJGKUJKFjnh+b2qMkoGCg4aJgYqYR4h1ZnaCp52Jc1tpc4WAjUlTfYBucHV/gGpyd29ShImIbHl5iINRnWiAbYiSlZNTWExqYYKOhV5Nf0lJfomAkJRgYbCZn5WLjpVXj6KRoFdjWlSLlaSurZ+qY49dUpuZopiLlI18SkpJipBXWHVrdpFVjotfloqEjolyZIaNjoCckJSDb12bfICIjpSVfHqTTmOZnpqKfYh8e4eGdHxzmViGjHh6gIyinqOXeoGIkKipolNOVoBTVXyKVJielIyAhFJPb1RVmIJMkVJDeo2Eh5KeVlaEdnp1cWRiYWdwZWxwd3ZuaGdfYWhjhIJ5hm92g317hnB2bHBsbmx1b19rZmlkZVpgaoN1i5i7oH58aWJ2l4pblXePW3l9opmhlJSEiHp/fJF7gWx1c3t0gol9c42JXoqDfXl6hZ+mW5yGjZOAjJSbmKVbo1SCTIeKhJSMb35fjYxUl5eagp1Zp5SRVnuJlJ1fsbKXjH+SW15eq42vq66RjKVboqSGk5Ofm1KdYlqnj5GbrLGmopScspSch5Kcm4qgpmCyf4aQT1uISE9gUVSOkZikj7KoppSfo1tXqatWVmZ2Y1hTWKGnnFyWnaOAlYyEZF2juKmvoKGUlJSFjJOUlp+hi6aikaSQj5GUlpCDqJejmZiAo4+PnFeVl5Seo5yTlZKUl6Gdnp+Ni5OcnZqYlpSVmamYt8a3layfmpGOpouQlZ6apqiIhIidl6atoKWrnZajmJmllYOXoqWkp56WpKmrnqOfmqefYFmkU5aAnppkVFRRVWiNTZGLnFOXZZ+dpYaSkoWgnpeXhaKmi5Wooq++tKqrtFlapo9+jnh8kF2jn5ygnlhibGdkpKWOlpiVnpqdqImenpymmVaZWVZSpV9hpmdrXmhpZF9hXXBwY11dZmJcYFdUVGFfV52rXKuro620rbVam1dcVIymra6AoLFaWFxbppiki52ZioqViXmLfXd5coWIhXeFd21tfoeCeXl7fHFqdHR8gX95gIB3dWlwcnZ/i4KFh5CilY2eho+SmJuajn2EeYJ/dHF+fX6JkIF2b3x6em1jd2lkanGBd318e3mVgH2Lnmx7jXl/jISGg4GBhoGMgXd/hpx2gImAdHB3enN7c3d6eXp3goaJhYeAe3Rka3lyhn55dYiGeXh5Y21la297gn2Af4J3cIaMnn6QjIaTgn1vbmltcpBzcn11eoJ0eXdxfHN4g3V4ent/cWpsdX95gpGOgIeKc3N+h4F+ho6NgW5venmNkXt5bYR+eHKFimx1dHGBjnt3en6Adn2EblRudHh6fW9tdIF9d59odHl/fHZ3jYmZl4tOl5iNf4WFfIaOUo57moyHjYyGTpKGk3+EcGeBhnd4d3uCeIecTJF8bXRum5eLemZ3f46GjEdNc3psbW13eFpmb2tQiI6NbXh0f3lMl2p0XWppaWY2OC5ANklUUDkxVjc4ZXCAcW5JQnJdZGBcYWg/ZHZidUJOR0FcY250dW96TGpIO3NudnVxgYBzRUI/cW5AP1VOUWxBcXRSh3x3gH1pWXR3e3CJgod3YlaQbXJ6e4CDb26HR1eAgnxuZXJobXx6anBmhkxydV9iZm6DgIV3XWNrdo+Oi0ZCS3NJTHCDTpOalY2Ai1FNaVFQkX5MlVVIgJGFgoeQTk57cXd4dW1ucXmBcnh5fXp2cGtlaHBpi4iAkn2EkYmHjHl+eHt9fnyDe2l1c3VsbGBlan9ren+fim5rXVdmhHlMe2J0SWFlhH2EeXhvd2xyc4h1fGl0cXlygol/d5aWZJuVioB6fY+QTX1qb3eAcHV5eIRHd0FiPW93doV+ZHJTenpJg4WDa4dOkYOIVH2Olp9bqLKUh3iFT05MgmKCf4VvbIRLholwfn+KhkWAUEeAamhygod7eGpzh3F7Y217fGmChVCWZ299RlJ9QkpZS0t3eH6GcY+FgHB8fEdEgXw/PEhURz8+R4WOgk97iJqAin1rUEl3ioKKf4aAgod6fYJ+fIGBbIiDdpGBfoGDgn1yi3d9dnJcfGlrfkl6eXJ5f3ZsbWlqbHdxcnFhXmpydXFwbGxpaG5adoV7YHh0d3ZxhWhsc3t7jJR4eHiGfYaFen+Ce3aAeXp9altpc3x+gHlyf4WHdHZza3NvR0WARXSAeXlRQkI/Q1ZxP3Zygkh+WIuPl3mDg3iUkoqLeZKTcnJ7dHuEfHRzgENFfW9gbVpdakV7d3N1cD5HTUlGb3ZndXRud3N1hWl+hIKJfER0QTo3aUFDa0tQQ0pJQTs7M0NFPTxAS0tHS0FCQ05NQniAQ3RsZGp0b3s/akJIQ2d4enmAang/P0RGhHWAbX56bGx5cmd8dXFyaXV2cWBqXlNWand0bXF1eW1na2hvd3dudnVuaV1iZWpwemtvcnOCd218Z25wfYB9dGhwZnN0a2h0c3WEjn5zaXJzcmpkeW1obXN/dHt4eHiReXh/jWFwfm56hnuAgH96f32FfHZ+g5hwdn7/ep56AXuJegF7iHoBe5F6AXuEegF7inqCe4t6AXuIegF7iHqKewN6e3uEeoJ7h3oBe4R6hHuHeoR7iHoFe3t7enqEewZ6ent6enuQegF7inqCe456AXuRegl7e3t6e3t6enuFeoV7Bnp6e3p7e4Z6gnu/egV7enp6e5p6AXuIegF7iXoFe3p7enuHegR7enp7hXoFe3p6enuEegF7hnqDe4h6AXuHegR7ent7lHoBe4R6A3t7eoV7i3oEe3t6eoh7BHp6enuGeoJ7pnoBe896B3t7ent6enqGewh6e3p6ent6e5h6gnuHegF7hXqFe5B6CXt6e3t7ent7eph7A3p6e4d6BXt6e3t7hnqEe/p6AgIEAIC7ycHJz+rEz8bIv8K9xse9t77DucGivL/Qw9XV1N7z08/IzMrh59TMzcfArb7d19rhxt7YzODD0NTBvcK44b/Exba4r7a+wbq41NG+u77RyNCts7zCyMfC17/RvMGmr666wMnNzdPGtL++qMzHvrrL19/SzdDb1NPay97b2NjJ0IDb4t3t2ePc1OHi6OHf59PGy87W2dL0iOHi9Ob394CAgebX0ufu+4SgqY30hPbn9oOF4urSzd/Z1dzj3M/YztDq5f7y89TGwbqyuKe+9NrQxLDU6/ba7NnmvbO1ztLEsuDlzOntybTP6vv1u8zo2Jvaw6GMioiToaXAtrGLm5OBh4CKjoqG8dHqiICKg/L1gviKirKF8OLRxvHd0sfdjob8j5fipoaEhPGAjYH81r7Z8vD8387O1t27r7vA0dzW2tfmxcLQ6PqM8c/e98zYzeDn4ebr3NrV0tHRv8rDxsK+u9H3+NjQuM/Pz+L5k9/q59vf3tzc8OHq4uTi5erwg4by44Du5+7u2/Pj4IWElo+F8OPd2d/s4O7yge/tzMzS4t/b3tvoy8PO0sTK2OmAgoD03u7k3NHL0eDg3ujW08PGvcjDxLXFvrintKqlzNnx/NLHytLIvbymuMPM5PTg8tjfy8TYws3u0LmsiqeqssnI1NbTz+n3ie/f5/L83cHt3Ovm4YDw8oL28/2Fg/v46Pbk4dfj6//w5vPz99jL6+/46N3dvr7U3u7898/gzeD5hYSRgIT67dvi7dPb9NfS2+r6hfGYiN3SzMXUvrzCx87b5/TV5/Tl3PzT75Dm2O3k8u/zg5mC/Obe4NvKz83f8fOGgfny9fzh7P+I/Pj19v+CjPH474Dw/IyQhOz/7fba3fLhz+PXys/W0MvQ0fDW2Onr2NzihdbZ8tjs+Pb8/vr69+Ha0/TFzdfJyd/k+ePj++zB2dbe4OLv8uft673sg4Ht7/Xh0dLNnriyoZyapbXG2+bc7uLN2cjRzNO+yMrk0cHT1s3t3djl6+zo2of19IL/79rxgIDo9YHw9O7p2+n26ev1huDV2dvd1rjhx/7w1eHP0s2zuc7Fy+bk4+Ps4eTn0sWzxbzSz8LL5Pz9+POC9vLU6Oju3/by7uDm7fLygYOBjYSJlYmG/PjslIaAioOGjYSHk6KXmI2DkJKQhZSZgof3//f06N7jiYaQhID4/PSMgOvm94D5gY2Whv/f9OTY5P/919DPw8DJyr2kn5KboqWip6KmrbGvs7G3r7K4w7fCw8a7vrO3q6Syqau1utLIw8/ax9PTx9vQyc3YycrEyOHTuMC7wMDP0sHGy8W/s7a5rb2/xb6+vc7Hs62jq8zZ1cTCtrfCq7Cmp7Cprq+oqbrDycnBxYCAi3+FjamGk42Th4mIjo6He4SGgIZthIaWhpSTlJ+tl5KKi4iXn4qEhX94a3ybk5OUe4yIhJh9jJSBe350l3uBiXd6dXl/f3BrhYh9cHKFe39pbXF6fHp2kXSLfIJqdXV6dnx/eoN4bHRyYYB5dnWDjpeEe3yHgXuGeImCgoBzgYCNmJelk5mJgZCQmJOSnZGIjYaQi3yVUnh6koqfoFhWWpSFh5WVn1JeYVCES5SJllFXkJF4b4R9eICGhX+GfYCVj6SUmIR5cm5pYlBdhm9rX1FtgY98jH+QaF1jfIF4bZ2ghpqedmV8k5iXZnWLfGaRe2NVUFJcaWqBdnBOXVpMU4BaZGNiroueYFRcV5uhWKVaWHVUmZeLhq2flIyaaF6iW1+OZ05KR3lDVEaNbmZ+mJKYfXN3g41xaHN2h4uJkJCYc219jpxdooKPoniFf4qPi42QgoCAg4iQfYuCgHduaX2lppKJd4uHfY+gY4mamZOSkIeFjn2DeX6BhomTUlaSiICNjpWYgZSEeEhEVEpFe3h8hZCXkJSNTYeCX2hugX14f3yFbWhwc2Zwen1IR0BwZnx/fXNwcH96eoF4dmtsZW5na1xsa25neXFnh4uYm3VpcIB5cndmdX6Hlp2RnYaKgneAdIKahXRnTWprb4J9gYF6c4qYV416g5KdfmmSg5KNh4CSlFOhnZ9WUo2Hh56UkYSOlZmIhZSbpod6mpeajoGDamuAjqKzs4+ejZuvXV1pWFijmIKHmoKJqZSQmaOuXKFmXJOPjpGhioqPjpWjoK2LlZ+Yj6yLoWiXhJCCi4aFS2NRoJaVm6ScnZelqaVeV6SorLGUl6leoJ2gpK9YYJuXj4CLklZdVZWpn6+UnqyjlKOVipSakYqOj66VmKepm56aX4eLnomap6iuq6SipJSZkryPmZ2LhpGTn4mNrZyBoZ+gppuoqaiys4m8amOtqaqVho2PdJSShH1ydX2Hlp+YrqiWqJaamJyHl5mzo42UkIOjlpGboaGhlGGsqFq2rp6tWoCfqFeZmoyHf46ZlJqlW56PkZaRhmyKdKefiJKHiINweIqFkaqipKGmmKGhko2FlI6jloSFlKKppKNdr6qVpZ2cjKWtrp6kppmOTU5TYFhaX1hYorCmaV9aXFhcW1RUX29oZ1xVYWJeVmNpV1qjrJ+hnZyWZGBjWFWem5BWTpCSoICiVF1lXK2Trp2Oma6sjYeMhISMjH1samRsdnx9hH19foB8e3iAdHF6gnZ+hIeBiX6BdW99enmCgJCFiJOej5qWjqKSjI+UhISBg52TeH14e3eEjH+HkImFdmtpYHh3d3J0bHp8c3Fvc4+YlYGIf4eRgH1zcn13d3t0cH2IiIuGiYBsc2ptb4tqd3R9dHt9h4mCd3t6b3dddHqJeomGgoeReHRweHeJlIV+g310Y3WTiYqKb4J+eZV/kp2Lf35vknJ7iHyAeHp+gHRvg4d6bW+Bc3ZfZ3F8g4SBmHyQfYJteXh9foSKh42AdHt6Z4qBeHWCh4t6dXaFgX+GeomDgX9vd4CBhH+LeX1va3Vyfnp4hn10enZ9fnKQUXd6k4WRkU1NUIR3dIGAhkVSV0uBSYuFkUxShoh1cX55cXR3eHB6c3mPjqeYk3tycG5ralxsl4J6bFlwfYRufXSGZV5mfYN7b6Cli56bc2Byh4+VaHB6aEhdVUE0MC4yOztOSEQvPT41OYA9RENCcVVpRTxEPmduP3A/QU8/bmxhW4BzamBuSEqBTExpTUBERHZDUD94U0VbdHJ9Z2RueH9qYmhpeX11dnV/YV5ugZFUj3F+iG12cHqAent7cG5rbHB3ZXZxc2xjXm+OjHp2Y3p0Z3SCT2l5fHx/gHZ1emt0bG50eoGJTlGJfoCIhI2LeoyAd0tIWE9Jgn17fIWJf4aCSIF/YWp0jIuHiYCFbmpydGp1gIVLS0aBeI6RkIaAg42LiY6EgnV2bXdzdml7en5ygnRnf4GNj2leZHBrZGhaaXB0g4NzfWlvZWBsZXKPfnFrU29vc4aCiIeAepCaWJOAhZGZeWCGdIN2b4B2dEOAf35EQnBsb4eBgXaEiIlxbXmBkHRlhoaOhX6Fb3CCjZyqrIaUgouWT0xVR0qIg3FxgWhuindxeYGKSHtQSG1sa218aWRpaG50d4RjbXt0boxuiVqBcoR4hH99R15NlIiEjJGEgXqFhoNKQnd3d3xmaXdAbXB6hJRJU4aKiYCJjlBTSHaIfYlweouDeY+Ee3+CeG9xcox4gJGQg4iEVnVzfWh4gYGGhX2BhXZzbJZrdHdkYGtueWZnhXZceHZ0eW54eHZ5d0x7SERzdHxvZ3BxVHR2aGJbYWt2foB2hX1yhXaDhoh0gYGSfGRraWJ+cm93fX95Z0l7eEOLg3SFRYBzf0R1d29sZneAd32HTH51foqMhW+Seayegoh7eXFaXWdgaHt2dXR7dX6Ac25jc2t7cGBibnt7cW5BeXhkd3Z4boKDgHF8gHd2QD9BST8/Qzo7b3xzT0U+Qjs9OzIxOklDRDw5R0hFPUhOPUN0hHp4cWdfQkBFPTx3enRIP2tmcYBwOEFKRIVzinptdIWIbWlycHaEhnhkW1JYW1hXW1leZmtscG94bW1wd2tydnZxeXR2amNvZWJqaHhycHd/b3l3cIV2c3yFdHBrcI6JcHhzeXaChXV2gX9+cm1vZnt4eXN3cX5/dG9rbYOLh3N3c32OfH5zc3t3eXl0cn2DhYF4eP96l3oBe4Z6g3uGeoR7B3p7enp6e3u9epZ7g3qEewR6ent6hHuJegN7e3qHewR6e3t7m3oBe6R6AXuReoJ7inqFe4l6AXuTeoN7xnoBe456Bnt6enp7e6N6hXuNegR7ent7lXoBe4d6g3uLeoJ7h3oBe4V6gnuFeoN7mnoBe6l6gnusegR7enp7hHoEe3p6e4p6AXuregF7j3qJe4N6l3uHeoV7BXp6ent7hHqEe/t6AgIEAICqt7e/vMa/t7O8prG6wMbGr7nAvsa1tc/CyMvb39zcyNzR4cXI3NDO2c7Nw9jV2+Dl4Ozrzt7f4b6+3NHFx8DAwr+8ucK9v8G8u8LO1MLQxdHEztLW2cTKy8q2tsXJvby7xtHDtbC4ub/Vtbu7xsbO19PFrsDJ4dnv0c/i4fjQ8oDj09zQ3vf16PLi7t3s4uDn1Ovq5NTh2+LY4f/1/Yj2+Nno1u3i9YCSiY+EhYLq6IHzx8bL1uDw5uXa0N/Vv9Htgc/k2Ni80MnOxbK0u8fS1b7I3NnL1MPiysa7i9XK0uW6xsjYgurOvLXLhpqM/YGElIyZm/Th49/+opaFkoL2pICGgo35ipXmgYiH7Y+kr4GMjafv4uTt2MrP2La6y9Pd/PuC94D68uyCmYf96YDy8//29ce8ysm7ztb33vft5OPgh5He2N3F3MjUz9Ph3djf8uLU7ufs4+WA2ePs2N/i5dDT7t/XubnHyr6F3of+6fHW2+ft/YODhoOIjvT69vDqgIDo7NzY2unbzPWHjYyLhPLq4dXh4fCB1Nvd3dPa3/zw4tTQwcfYy8jb7ff0+vLovuXj1NbCyMjY2uDPu8rCrLy4qq2/s660r7evvb7Xv7PRus3DwM27wrm+493Fydq/vdDWycbCuK3Fs7a6y8LS3+H/7oP4/ffu8+f07+j14dnv94Do6vTi7vH1gvqCgOeE5dXt8+/s1u/27v/544Hm0Ob2zOL31uXn5NjBs8bI04Dg3/vz38bP49/W3eLJ4Ozm9OfzzcbKz87V19Da3t/9/4mB7ury6PX65eXl092I9Ork6uL9h4zw09rZ1dLX6enr+YCF8Ozzg4aJhoSC/fKBg+fu6YD4h/uKh+vy44P0+/jUyNbKwbzI2tvR18/w6P6B/Pr0gPfv++zy9+n329zZ2dnb1svF2djGw+Tb7Nnf1d7AqrrMy9bOw7rX0rjN09jc4+X8hdPgus/k7MjDyM3Hz+Hk49jM6s7k07W4uK7Lt7W+39/1+fPz8OP70ODm5/H08uHhgICJ6/fx4ufp2+Tg5tbQzezpg478+vz2+4r868bHws3Gwr3c0szg7u7y7ffx0K/Au8Lg7/jq9YDm3ePn6ez/ivvk6+2J++yD7YaFjIiFlouHi4SFkYmRk+6A+4GAjYmMt5+Vi5eThIKAgIDwgIGQmN7Z3cXk3+fjgYCB+OTd3dTj6ID7+dqAheTn9fDw5e/e0er7zcLjz8y3pK6zr7G4sLapvrq6vb7GusrNvq+9vLqzrK2opa+xr7a7ys/Q1dfk3N/exc/C1dHSwMO6sej9wNvExtnBvbasr6mkrLy7ssHKxcS6rrKllpKWnZ62t7O5uLbHwci6t7GouJ2krrW4rbW9voB6gn2Bf4qJgICHeH6ChoeIbHuDhoyFhZyOjoyaoJiai56WpYuLl4eCi319d4aGjI+WkZWQeYaLkHN1koqBh4WKjot9eX5xcXFsa3N7e3SBd392enqBgW90e3pwcH+BenZxe4V9cHF5enqVeXx5iIWJkIR3WGlzi4WagX+Nipx4l4CQgIyCjKGgk56Xo46hl46WhJOVjXuGf4R7hKCXolugo4eTiJqNlk5YUlZOUE6Ji0+dfXp0en+Lh4qBfYuAcYKUUHKIgol0goOKeGJhYGdyeGFqf3lscGKGa2ZjUXlwe5Bqe3+PWp+AaVxtUF9ZlU9OX1pqbKCNi4KSZFlOWVCbcoBdW2WsYWWRVFlYlGJ0fVBWVGKJhIuXi4SOmoB/iYuQoaZVmE6UiHhFV0aAgFKeoqifm3ZufXlqf4anka6glJaPWl2HgYt5loqTioiOhn6EmIx/l5SYlZlbkZ2mjJKLkoGEo52McW57d29XhVmnlJ+AhIWHiEhHSkpVW5CZlpCLUoCOlouJhoyCa4JLT0pLSoN9f3d9gZFOdXt1e3FxeZiVj4F5cXR9c297iYyFh4B5XYaIgoV3enV8eHZqYHJsXGlgWFdrYmVvbHduenaKc2F7ZntycXt0f3d9oZN9foltcoGCfIJ8d3GAcnRze3R4gX+Yhk6RmJSKkYiRjouYiIebpYCXnKeSmpOQToRFRH1PfWyFioaOhpymn6qfikt9cYGQcoShi5uhoJiCd4iIk1+inLawm4OGm5OLk56Em6Ocp6Oui4qNk5aZlZGcmpm2rV5Thn+LgI+ikZaYg4dWlYd/hIefV12dhZugmZSXoqKnrlpjqqGnVFFVVVpasaVTV4+RgYCHSoBOUouTjVijtLCTj6WYjYeMl5KChHqWiqBTpqGaTpSQm42XnJani42Lj5akm5KRpZ2NhaKbpY+ZlJuGdoWYmJyShIGhm4eep6OkoZ+yYJmrkKzAw5uQjpOKkaKjn52Us5Snmn6KnpW0m5OPnpKlqKSqppethI2XmKCnp5uXV4BbmZqVi4iKgoyCk4mDgZ2TVl+fnJ+XmFmfknqBgYuNioWjn5ypsq6sp6yrjnqLh5Ckqa6epVihmqKppaCzXaKTmJJaqaJhpVtUWFJTZ15bXlZWYF1iZqxduWReaGRlgG5mXWZiWVZUVlacVVRhZpCTm46gmpqOTk1NlIyMiISVmYClpohUXJOSqqepobCgkqu3h3iUgYB1Z3Z9e32CeXxzg4WDgX2DcXh7b2R1eXl2dXx3doGDf4KDjo2JlpCclJ2hjpmHk5KUh4mIf7O9fI55fpN+iIuEh4BzcXl2aHV6dHZwZmZhXlpqc3KDgnp/fXyKjI+AgHlxf2txfISLgIqOjoBrc2xxbXNxaGZxY2t1fYSDbXl9eoB2doyEgn6LkIJ/a390i3R7jYSDj4B+dIF+gIOGgoyLdYqSlnh7lIl/gn6GjIp/foN4e4J8eHyBfHN/c3t0fX6GiXuChIN0cH+FfXl2hZCLf36DgoKXeH12fn6Eh31yWWhyiYSXfHyNhpVuioB7Z3RncIaFd390f2x/dXSBcYGGgXF9fH1yfp2RmFWSk3qFdoV2e0BNR01JTEqIi06Yd3JudnqGe3drbn54b4ieWICRhIZte3uBd2dpbHiBg292hn9ycmF5aWdjQHt5hZh1g4KOV5d0XVNnS1dPgz86RD5JS2hVVU1bRDwxOzRnT4A/Pkh3SE5nQEVDaEdXVTk+QEhoZG17a2JpeFtda252i41HgEOBem4/UkBwaUJ5eoiGim1pc29kcnOOgJSGeXhuSU1xcYFwiXiAeXqAeHJ3hHVmeHJ0b3FFaXiFcXh2fXBzkYV7YF5nZVZHZkeEdIJqb3Fzcz09P0BLUoKKiYOATICDjIB7foV4Z4ROU1BRT46Hg3h9folKcXdxdGtyfZuXk4R8bnB7cW58iY6LkIqIbJWbkpaDioKLi4t9bn50YW1mX190bnN6dHlucmx/ZVVtWW1naXRteG9zk4JrbHNdXW91cXR1cnGHe3p5gXt/h4WdilCRlZSJjYOLhn+Jd3SDiYB3e4V1g316Q3M9P3NJc2eBhX5+boKOi5aNf0h5bYGOb4Ghh5eamo54b3x2e1CDgJyWhG5vgnlvdoFleoB1fHuEZmVtcXV3dm11cnGJhEpBbGx5cH+Of4GGc3pOin96fX6TT1OLeIqNhHx8gX58f0FJend/QD5AP0JCg31CSHuGhICST4NMTHZ9dUqEkY1xcYh9d3J4hIFvcGiHgJNLkY2FRYJ6gXV/hXqJamxsdHmAeXBvgHpkW3Rtemp0dIBuXWp3cnNlVlBoYkpfaWlqamx/R2p6YH2XnXxzdXdzdH+DgXpyknaMh2t1fW+MdW9rfHWGhYGGgnSHXWhvb3qCg3Z2RoBJeYB9cnB3cn11gXVvaoJ7TFaTk5mSlViaiW9zb3d1a155b2Nvd3V3eH+AaVhoYmZ4f4B1eEFuaG10cnOFSXxvcmpDd3FHc0ZBRj9ATUNAQTk4RENJS3ZAfkM8RT9AWU1GP0lGPjs6PDtoOztDSWVka1tsZGheODg5cXJzcWlzcyB8dVc9Rm52iYeIfot9c42ddmuKfXprWGBjX15lXmNdcoR0XHtteHdpXG1wbmtpcWpkbW5maGt0dHN3dH51en1td2t6eXttb2ljmalwh3N5jHd5e3V8eG1td3Zsd3x0dW5namdiYW91boB9cnFwbX9/hnl8dnKCa3F+g4V4goWC/3qeegF7iHqHewN6enuQegF7mnoBe4h6AXuFegR7e3t6hnuFeoV7AXqEewh6e3t6e3t7eod7j3oMe3p7enp6e3t7enp7k3qCe5V6AXuRegN7enuIeoZ7hXoBe4l6hXuHegF723oBe5V6Bnt6e3t6e416AXuRegF7oHqCe4t6AXuGeoJ7i3oFe3t6enqGewR6ent7hHoIe3p7e3p6enuSegV7enp6e696AXuveoJ7j3qCe4V6AXudegF7h3oBe4R6BXt6ent6j3sDent6kHsBeoR7iHqDe4p6gnv7egICBACArru7qrm9w6ipsa61rbK4r7O8yNDDqqzByNnPxsjBw8HPzdXO28nN1N/f7ObS1dfo4uPr5cba5N/I4M3Bw77AtaGqn56rtbjS0MW40cfW1NvcysTB1NbOzcPKx8uxq7quqbmwsqyyy7Cnqa2lucbAxLuqzdPJy8vSvMnHxb3Py4CA3brN1dfe6Ovh7uPY2eTj2eTty8bU5vGB7P2Dgunt2dTMw7nAvNfy9Yb5+PCG9Ors6PXu3/H+6/3519XS2sK09/Du8uvbxb7O1M+9wMbYuMO9xrnCra+jsb2/4NHU2tHI0s7A0sG1v430++P47OPn2Nzk6oPk3t3m7O+Rlo6i9JCA993f2/H46Pz+5Mjj9+fl+Pbx/v+EiPuOgN3v2tbL0sXGx+D5hPr3j4KElI/24+PPyNLDzNLC1vft6/v/+fnv4fT7/eXSwNCzu7XGzejy39TjyNDphZSO5OP39YCG+ujd3/j25tjP09L+1eb09uTd7ezpy9n8j+X49PLx5tjjh4SA9/rt6vyGytT0g4eBkunu8Obu89ja2s/T4O7i0MHezcbV6dPKz9/b5N7e4ubi29jU2dDR0OPt1tvMytfHzdncyMnBxLq+wbK1yc/N1uPpzMff38rPu8W+xtrazuPYyr/Fx7rGur+6wqWxuLzF4+Xd4Nre1Ovx9+Dc0Nzc3Yn19OaA0crl9YH6h/Lv+PL7goeJnJeE7Ozl5/Ln+P/3/vSB9fOQ3uLs5YDw58fT6tjN3fuC7dS76u3N0tvasr280siowr++xdTJwtDe5uLi4uDm/4H86+qF/t7Nw8zU2eri6uHx/vHt6vThysTW09vr4PqL8/3sgpybkZCCk4vk0Pn86uaA8fTzhoL68vT9+PGG8evYur/R0u/f7+30kYH1+4Pp5ODr9e389YWD/dPV1M3O6NHNysvc4Mjj3Ojd18+7trK/wMvA19DP6fjoyczA0t393trl8sXPvMXh1s3UyMTM0dPd7Ovi4vnfsp21pcO608nUzdLu5d/m3N3l5dvc1dzj6/KA9eTQ3NvY1Nbh3Yji4NTV94GFioCDko2C9u/IwoHv7eDi69DZ8ub47ers3cGxq7m4x8TO0t/jyc3P2uTq49Db6fX28/r4hYOVhIaChIeHgYaQjY6JkYyKk/v5h4+K7PWE9O//g4eGmoeNjoD6if3i4+bG2dHk5erm9vjz6tbZ7eKAz/KAh/L/goaIj/r3+4eK8/O+2L3Dt8HJub/JxsPDzcPEysDEs7e+wsbNsrWyvquonaihrLa8ycTY3+Lh1djH3sTEu73Ix7W8w8vL08/Ov8XMwLezq6+in52swrS0sqy7sbSmnZWSo5CgtrK3wcPE2cvJvNTA0Le0urG/trWwtK6AeIF7b3p+hnV0f4OHfoOCeHuEiZaMfYCUmaWXi5CGhoaWlJuTnouHipKOl5GEhIKMh4OJhXGIkZF+lId9gXt+c2BnWltmbGZ1d3FugnmHhoiLfXd1gH1zcGp2e4FvboBzbnduaF9lf2VeZmlgc4J8f3Rifn50dXV7a3t/gHqNiF6AnHiIjIuQlZOPoJaOm6GfkpaUdW94h49RiptRUY2Th4aCfnt9doeYkFKOi4ZTjomLiZqTiqGmj5+WeHt9f29mmpeOj46HdXiLko56fHyKbnt0em53X1xTXWdmg3RzgH53iIV5h3Zpb1yOj3qVkIuRhYqTnl6Yk46MiIZYXFZqk16Apo6PiZqbk6Snj3iXppmRnpmPl5hQVJlbVpSnl5GJjXx5d4GQTo2LV0lKVVaRhImDeXhmcnhuhqymoK6wqKScjpqgp5aEdox0eneAgZWbg3uOd4CYXWlil5OgoFBVlouHi6Gml4V7fHuhfpGgpJWPmZmPcneVV3yOjoqDe3F4T1KAlZ2Zmqhac3WJSklCT3Z5fnp/hXF0dHN4h42Ac2qDdWt4kYR7e4R7fH2Cio2Fe3x/kY2JhpaWeHxqaHNjZ3R6bG5naGVyfm9ygH53gouLd3aPj4CDd4J9hJmRh5aOf3+Cg3eDdXl0eF5lbW11jY2GiYWIfIyQlIGBc39/f1eam5WAhYGXmlKZUpGJkYmMS0xJV1dKiI2JiZeNkqGUmpZQkpRZh5GjnFuqooSOo5KFlLFcoY14pbGRk6KphI+Nn5BzjIGJj56VjpCaopaRjoSClEqMfXxToIeAeX2FjJiLk4OUoJaTl6KejoiWlpSglKNfpa2WVGNgXmJYbWigiqqjjYyAj4Z9SkmSi4+clptYo6KZgICOiJ6HkY2UXlCYnVmZkomTnZGemFZYr4qPkJGWuZyYk5SZnX6ViJKIiYd7e36Qkp2Pn5SWsMO7n6ORnp60mpmsu5Sfj5aqnpSek5GXnJ6jraqfmrWnjYWejaSUo4yRiY6fmpWbkpSYlZGPiY2XmZqAoY52gIB6dHyGh1+YlIaKpVZaW09RWlJOkYl2elejqJ2erZqhtKmypaSln4yEg5GTmZCTlpyejpOTnqOmo4+UmJyamaOjW1doXF1ZXF9fW15jYF1aYV9haLO8aG9rr6dfqaClU1RSYlZdX1KdWJqAh5B6iYydnZeSmpiYnIqQqJ+Ag6RWWpqqVVxgZaippFZem59wjHyGgImWgYePh4GBiX6FiHyCd3d3enyCbHZwg3d9dH96hIuNlIqUl5yekpSFnoqRlJako5WSmJ6Yl5GMf4ePhIJ+en1zbW92iX2BeXF7c3Zqampndmp0fHp3f4GElY6MgZWAjXV0dHJ9e3p4eHiAaXNuYWxsdF1caGxwbnV6bG90eoF1ZGd8hZWFen5ybml2cXx3iXt+h5SOmJB8e3qHhYGHgmyGlJSBmop8gXx9c2RrYmdydnKDgoB7jn6Jg4WIfHp3hYqDgHaAfn5ucYBzc4R7fnd8lXlwdXRocn52eW1bfX98foCId4KGgnaBe1SAhmR0dHR4e3hwfHNqdHl9eYKGbGZxg41NgpNOTYaKeXx6dnFvZXOEgkmBhINRjYaCgIyFfJGZhZKJbHBxeGxkmpyWmJGJd3SFi4t3e3+OcX13gHN/amhZY2xtiHh3hIN/kI+AjHlrZlB+gXGFfHV2a21yeEdxaGRgXVlAQjxKZ0WAeGhvbH+De4qMdl94h3pyfntyenxBRHdJQ3GCdHJscmVnZXF+Q3t4TEFAS0l4a3NwbXVtenpufJOGgo+KgYB5a3d+in53a39nbWl3eo6UgHOBZ2h2RVBIaGV2eD5Fe3R2d4WJfnFtbmyNaXaFiXt5iIl/Y2eEUHCEgn55cmRsSkyAhY+HhpZSYmqFSkxIVYOLjoiOk3p3dXB2eX12c26Ie3J5jHxzd4J6fn2BiI+KhoqNm5OQi5+hiI+BgIt4d35/cHFqbmx5gHJzf3tyd3p2X1xyd2tvZHFscoR9cYN5bmtwc2t1bXV1fmZtc3R6kJGJjIWHe4iOknp5bXh2dlCJhXqAamV4fESARn15hICGREdGVFRGe395fY6GjpyQk5FNiIZNcnuOilOZk3d8inhuepVQi3Vkj5Z1doSJZG1oeGpPZF9mcYB5cnZ+in98enNzhkF6bm1LkX51b3V/g5GEi3uLkoaDhZOQgX2MiYSPgo1RiYx3RVROSEk+Uk96bJOYjpCAl5CETEiJgISKfn5If4aEbHKCfZJ8gXaBVUuMjk+BeXB3fW99eUhIjmltbW5wi3FvbW50dVhtZXFpam9mZml7d4BsdWJecn9xWmBXZGp/aWt+jWd0aXGHfXR8d3N1e3uAjI2EhKCPdGp9b4Nzg21waWx+fHh/c3N3dW9sbHF4fn6AhnVkbm1ranF9gFaFf3NxkEtNTkRHT0pIhXtmZ0yIi313hXBxgneBdXd9d2ZgYWxqbGVobHN2YmRkbnR5eGlwdXl1cnZyQT5PQ0RBQkFCP0JHRUQ+REFCSHJ4QkpHbG1AcmtxOzs5Rz1BRDlsP3BbYWlSXVtsamdlcnV3d2dsfHGAWHI6P2t7QUlOU4uMiUdPhIhhfnF3b3N4ZWhuaWZmcGpwdGpyamxydXR4ZGhhc2ZtZW1ncXNzeW14fX19b3JifW52dnaEgXJxd4aChYKAcHqCeXh0cnVraGt3inx7dG14dHhucG1pd2dtendyd3l5joSBdox5iHNydnR+d3RxcW3/egF7l3oFe3p6e3uMegV7enp6e7p6AXuLegF7hnqEewJ6e5R6BXt7ent7i3oDe3p6hXuoeoN7hHqCe5h6AXuIeoJ7hXoEe3p6eoR773oBe4d6A3t6e4V6hnuLegR7enp7hHoBe4l6AXufegV7enp6e5p6BHt6enqIe4l6gnuGegF7jHoFe3t6enuIeoJ743oBe4V6iHuEegF7qHqTewt6ent7e3p6e3p6eoh7Anp7lXoEe3t6eoR7BXp6ent78XoCAgQAgL24ya+xtKeflaObubCzusS1xMWikJ2WnKSxt8jLyMfO09nZ1M/Kx727zM3n3MTIy9jX6N7m49jezbnDwre3ubCepK+yx8W6zN/VyNq+wMvUwczIxsLAwNDGzM7Ywbm6tLy5r7Olo6GkqK+rrbjRtM27v8fV6dy3wMfExLrDzOj/gOzv9OHe8drj1uTTysDY5tnp/ePz6Onl6oWDgoeMhOHZ3eiC5P/38/SHgf75gICA9enW196L2NjR8PXl2NnaxtjI94Hv3tLezu/x5OvO0dLLydi+xLaysqfKyNHVyNXM0M/XtKe1yeiZ2f7zgYjp95WJ3+PlwsTI2NDb6OXmgM3mgOTn9+fb2ry8yMbj4uj4g4bq5dP87uLvi4fk9P72/fTc9vHsg4+Di4+Li/r14enTusDMxMXL2+uQiujziYeL3ICRiJbu1s2+wsjNxuHNy97Ky8DB5/qH/9fb6erm5Ybr49bL/YjA5fTn4+bX3NPmyd7Is8DU6I7/gOvl7uOGpvD0gO+IioHz+vDx8PX9/fr2/vbs8erj4eLjzNfMvbbBurq5wcDQ0djUzu3v2NHXwcq6tK+rwsrH0MfU9t2LhuG6ucLWzMXVyczK29nEzez6isG56PW8zNPi5uSB5t3n4MK609e9t83O8cq/xbK2ytbbxtPW6ubu7/Li7en5+IaNhO7kgPX2j4P68ffq9ubd7ICGjouE7/H9+9ni7PT3jJCP//HpzsjS3+Hk64jp2dDP0970+c6+1+Xywcm5pb/DtbPRvcS6wOXht8fLyNTT2PLz8fWAgP74iPfr7+fZ5tvg4+z35fny+/jw3NnX08/o3t7i69/26eXu9u6F1fKM9eOF6ujegMzR0tHn4+vp3+Pz4oH92crXwt3o6/P8/oH4i+f26OWAgPfh2ufg4NbNyMTNxcrSxdLD3tbT6OTh4NnRsdrFubC4wsrOwsfv4NDS39fq9OrgzNe/0cbIysfOydXY1+Tn6ITw8PDd897CwbPj3tDC3MWzxcG/09fa6+Pt+Pfn6OuBgPn86Ofk1tfx49fd1s28sK/b1dv55vTm+d7WwavWxsvP2d/p7Pbz8uvt2sC9ubTLxM3S09Hb3NLdyMzI0M7d/O7/6fjx+Ov8+4aMhPb5jIOE94GChI2JlYjq9YqShcDazuLU6uuMiZGQgpmPl4b85+zp7oqDgvT38pmE+tvr95aMgOXb4vWEhZeAw9Hl5OTj/t7007/J7d/Fx7y6y8K0vq7F0728vK65vNTLyMK608Cmr5+lp7LEw8DQzdTL08XWwcrGqMTBw7uzv8PQ2N3e4tvRwbu2r7GxmaiqpK+zrqmprKWooqWnkZObpMSnt73Sx8nr3N/Jw7S/t7G+zLW7tMGpgIJ7hXFyd3BoYm9tg3x9f4R6jIdyaHl2f4KIhY6Mg4CIlaKio5uTiH95goOXjHd9gImHk4SJi3+KfnB7fXRzdGlcYmZne3hufYqBeYlzeoaKfoJ+fHl0cHxye4WMfHl7d3t2bWlgX11eZW9qaHGJbH1paGpxiHxhb4B/gniAgZqvgJqan46Ln42VkqGNh4WUn5KcpY+ckY2JilNOTFBXUImIi5ZWkKGamZZTT5qQTUxJjImCh5Fhf392jJeGfHmEc4F0lU+emYiYjKippaiOkZGKiJd5fmxoZVhuanJ0b3t4eoGFal5qeZRne498SVKEmGRciZOZfYGFkoiMmJSYWXySgJGOn4qFf2xveHOSlZuoXF6emYurmo+YW1iZmaSZopyFmZCHTFVIUFRSU5SUhpB/Z2x1cnF0gZJjYJigXVlbgk1aUWGSgHhxdX+DgY53eIt6g3p6pLhkvJWZp6GWjFaLhId7pVZtg5KGg4SAjoqkjqGFaW51gU+IRoB8gXVMXoWQgJdbYFifm5GKgX+IiIWKk4qEj4V+gIGBa3l6dXR5cW5oaWt8eoCCgJiYf32HeIF4cXR0hZGFg3F0iGxSUH5dYmh5dXKBdn57j41yepqpXHVtm6V1go6dnZlZk4qUjXd9lZyGfIqLnX91eWdpe4OCbnV7h4GDhod6iIaUl1NXUYyGgJ6XXlCSgYmDkYZ6hUlOU1FJfo2doX+EiIqIT1BSi4iHd3mDkJSYoWOjjoSCgI2foXlthpSqf4iBcYaIfneTfIF6gaSjgoiKgop/gZOLhoVISZCNVZiSmpR/joSJjJGdkp+XoJ+dlJaRkZCglpaRk4+fjo2QmJFciappsJdbl5GJgHp4d3+Pk5mRhYiWgFOkiYORfpGXkpKbnFOjYpeqn5BQUZqKhZiWnpWTj4qXlJqdkJiGm5SKoZWUkpCKcJeLg4CLkpSWkJi/sqOmrZ2lqqCXiZiLoZmdn5eYk56koaeopV6no6GRrp+Ni4K2rJ2KoZGClZKHjo6RmZGWoKGUlZBSgJych4mFfH+ZjouZmZB8dGyMiomhkZyLoJCIfXWilpqfnp2pp6qioqClm4uLkI6noqSfnJSdnJihj5KJjImUq5qhjZmVpqGtrFxhW6usYFlamlBQUFtaaV6isWJsYIujmKKRm5dcWF5eTmNbY1WcjJCVmVtZW6Ooo2dWoIGPomxogKOZmqRZWmZQb36RmZWRrZCmh3iCpqGPlY2Mm4x3f2l+kH56e3F2doyEgXl2jH9wgG1vdX6RjoaRio6JjoOPgomIc5OZm5SRk5akoZ6hopuOgX2CeH2Ccnx8eX2Khn57f3N1cn1/aG5zdItxeXmLgH6ek5yFg3d+eG17g3B1dX9ugHZveWVlZl5TS1hXbGhqcHFqeXdcVGVjbXZ+eoR/cmltcnd4eXh2c29ueXyVi3iAg4yFkoOLj4uUjIKPin99e3JmbnN1h4F0fIyGfY55foaLfoV/fn17eIF1fICGeXl8d4KBeX51c29yc3p0cHGFaXpkYWl1j4Vqd4SBhHd7eI+jFI6KkH17jHZ7dH9uZmFvf3WCj3yKhIKAUEtKT1VMeHd+h01+j4KAfkZBhYFERkaBfnV4f1Z2c2l9i35xdYFvenCPR4uDc4F5mZ6Wm4GEhX9+jnB5bWtuYXp3fXtyfXyBi5RzaG52g1dmeG8/Rm9+Uk11fYJsa2x1a2dua2xBV2xtcIR2c3RhY21pgYCDjUtLfXloiHhscUKAQV9ueHKBgHCGf3c/RjtDRkZHfHtvemlYYXFzd3mGjVZPfH5LSEpgPUpBUXdva2lvdHZzgW1vgnByaGSFkk6JZmp7eHNuR3JvamKKS196iHt3dm13dIx3jXZcYGl1SXxBc293bURWeH6BUFNKh4h8fn1+hoqMjZmQiJGIf318fmqAcWpna3Z0d3Vzb315fHt4k5d/gYt8h4F8gX+QmJGOfoSXfVdUgmNlbYB4dIJ1e3iIhGpvjJdSYFd/i2FtdoaHhE1+doB7Z2iCj3lxg4SUfXV3ZGp9hIVwd3uJhYaLi3uGgIiDSEtFdGZ8d01Df3d/eIZ+b3pESU9MRnZ/jJR3gIKAhodOUFGHfnpqanaEhYmOWJF7bmpue5CUbWB3hpRpb2tecXFkWnVgZFxjh4VpcHNtdnF1hH14czw9d3NJhYSPjX2LgISChY+Bi4WPjY+JjYmMiJeLh3x7dYR0c3V9ckZhgFSThFOSko2Cg359iISJhHt8iG9HiXFofG+DiIR/goiASZNZhpSFdUFAeGZlenh9c3BpZXNwdXlvd2Z2a2FyamtsaWtXfXJqYGVoZV5TV3lsYGRvZHB4bmthb2BzbnF0bnFten55goWDTomIh32VhG9rZZOIe2uAc2N6d295dXR7c3mAfnN1cUOAhnl6eXN8mIqEioV9a15YfXh4kICIeo2Ae3FmWoJxdnZ1dX54eXR3d353Z2lsa310c3NxbXZ2b3Zla2RnZnKJd4NpcWt3cYB6QUVAd3tHQEBqODk6Q0FMQWZyQ09FVW5mc2JqZz87QEAxREBHPXFlZ2hpPjs9aWtrS0B6X2d0UUprYGFwQURUQ1hkc3hzboRtgmlia4uHc3Robm9+dWhwXnOEcW5uZmlqfndxbWd9cmNxYGBgZnNvanhyd3B6anRmbXBefoCDe3J1dH59fYOJhX91c3hxd3xqdnh2fIN9dnB3bnJxfIBrbXBtiGt1dIR5eZeGkHt8cnx2boCHbXNyemf/epl6hnuEegF7hXoHe3t6ent7e4V6AXuNegF7pHoKe3p6ent7enp7e4x6AXuQeoJ7h3qDe4l6h3uNegh7e3p6e3t7eoR7knoBe4d6AXuFegF7kXoDe3p7hHoIe3t6enp7e3u2eoJ7kXoBe4p6AXuieoN7hHqCe4h6hXuJeoN7inoBe6d6BXt7enp7onoHe3p6e3p6e496AXuLegN7enuEeoJ7v3oBe556AXvIegl7e3t6ent7e3qHewV6ent7e4d6iXuFegh7e3t6enp7e4R6gnuEeoR7+HoCAgQAgJicm5ulqKCwpZqiu7KytcDJyb+1rLCcqr2rtL67x93E1MjSxsDJ0bmppbipqLK+y8ze1t3b1M/NydHEy8DFzc3LytfM09zVvMy/zL+8tq+8wrTG1sDE2cHIxcC5rLG5tb7FvKq8taa8tbOcr7ispKvEx9bQ69bSzsnf1LLDz7nvgNfU5dXC2Pjn+uvi1ILogO/5guLy1uDn+YGB/euE9dPS0NDXzufY54WG3+mE7In48PPu6O/23+rLyfDw0eDr6drnjp/835LS5fmAh/r02tjIs6qwq8PP9c3M5NDQ7t6F8MPEt7bWxYDYn4uJ+eXok/uAgJOB5Nrl34nw+N3fhvPpgOX31bGuqK6z2enk7Nrw7vnn9N3Z39Hi6v6WmqyK/IT6nJqrpIqH9fKG+Obu89zItf+A4+n78u2KjPaOgPaW/IOdtZOMhtjg28W1x8Dv6NbRvsvs4tTQztPS0vHh49jb4s/Ext3F3urR4+nl4/Df8crCycu/xNjEzdjO2OX/8v7ugIP04O/r+YPs/IyB6f7p5+Lt3NqG/f7mx9O5urTDxtDOzLHCxNTSxb3HyczIubjAv8bT0L6tp7fd0v2Gkoja48rZg+jr3M7z9Pzm99L14cq4zuf3297Y3+XY0+TnysHFxODPwbvH28/JxsDU3+v/6vHSz9fcy8/O2tHm3erX1YjsgOnw5/X+6t7Y8deAhoWGjIL25/TdytXf4OTc/YqD/Pvv6Ojr7N/e6oHW6uDZ4u7i8t3a8+Pj29jPztTQscTjvOTw+N3m4NDI6I3b1ujr+4CUhYHn6Ovz5O/q2tjlxM7T2dvX2t/w5+bh4d/a6uHp5env6Nzn9fPr1uXy8NvE2tHRgMfQ08nS2Obk39ne3tDf2NLR0Ojj7/7n6e7l9OPm1u2A/pWB/erLytzd0dbA2MzFwsHCzc7e6Ozp4Nexyr7IuLi0xMjOxMje0d7Wz9jh2tzUz9rbzsa318TayLzP++fg+IDu5Ib42uHTxcfM27/Fy8S/1NXh0fDd5vX+gf7h2eX7gO6B5eXNz9fw29TGztHIvdDG0+6N7/H65+LY7s7Gxr2+yM3V4uWG/vP0597Yv7rS3/SF6uHTy83S1s3c4c/u/ejo8+eAgIOK4+OI9Pf3/oKXi5GNg4uNheXbhvfb1NK42MeAlZKbkYuVgIP7+4j56PHi8Kf+jZSHi5qN7+Pc7I36gNj6houRgI2AwMf63eKD++X70sG5xrS2pKmut8bL27C+x9SwvbC9xtLY5eHV3Lu47NTKz9DL0NDbyMK3r7q4vMfMu8nYsL20t73H4+HQ4ODQy6+rsaajtbKrq5+lrp+ttLu1o6SkubOkzsnHwsbErK62uL/Dt8HCx8W4t8u7n6upgHB1cnR3dm5+bmVsgHV0dH2GjYSCf45/ipuJiYaDi5R9j4mSjY6WnoZ4Z3tqbG96hoeVjZCLhX19e391fXmAh4R+eoB4hYuHeo2EjoJ+fHSDjHyFlnt8iXp/fHp0a25yb3V5cl5uaWBwbGxeb3l1a214eX5zh3l0c3mSjnSHj36ugJqSoJB/kq2ZoZiMgVSQUZWaUouWgYWMmFBOl4dSnYKHhYWNh5WJnWFfj5FUileYlJiUkpqbhJZwbZORdIiQg3iDWGOikWCIlqlZX6ymk46AX11iYnOAn3Zuf2tuiXdPjW9xa2iFcE2IZE5OjoKIX59SUWJWkY2YlWSiqoyMW5SEgISPdltdWWNrjZuYm4Wdnaibq5WOkYGOkKVpa3daolKaZ2RyaU9Ng4BMiIKNmpKBa6lTi4ualJVeYKFgUpdil01ke19XUnB8hHdyhXucloV9a36enZSTkZ2ZlbWXi357hXxxdYVtgohwf4OEh5iUrIh8f3xsb35ud4N5gIWcj5+SgFqplqOan1OHlFVLfpSGi4mUhX5Tl5uDanhob2t+g4yEd2Fsa3d4bmp1c3V6cnB2dIOYl4pzZGV9a4dLVk5vf2x5U4mMf3eYl52FmXihjIJyh5uvkpeSmJyRiJaZe3eCg6COenJ9jIN8dW9/g4mUgohzcnaBe4ODkoOWiZF7d1SNgImOgoyRhH55jHNJS0hHSkSAgpWQgYF7dXNvhk5KjJGPjpWhp5uYnFeElYyLj5uRmIOBmo2YkpCPlJycf4edco2TnYOPjoJ7l1+EfYyOl01dUk+GjJSbkJaJg4KSdISIlpiWkpOdl5mQkZSIk5CUjZabjISNlZqZiaOxsZmFk4SBgH6Dgn6DjJmKhn6FhYCPjYqKg5WOlqSMiIyNnZCWh5BQnWFRqZqDiZybjpKCnZqTkIyMj4+Zn5yXi4VlhoCRhoqGj4yQiI6nnq2loaOkm5iPjpqgmJOFo46gjYOSs5+XsFmjlV+ukaCWi4mSo5GVmpiLnZqji56Ii5KcUqWMgYuegJBSiI11fYObi4qEiZCEeoR6hpxgn52snaOYtqGbnpiYl5ieop1bqZ2loJyWiIeZo7BfnpaQi5KbnZGXloGSopWRm5FSUllgoKZiqKmkolBaUFZVTFhfW5eUZLynpqKJoIpaaGNqYFliUFKbmVSbl6ShtoPCbm5fX2hckoiHmGKvgIyiVldXSlVLbnqplplcrpmriYF9j4WIfIGAiZGXo32Kl6Z/hXh4eIGJl5OLkHh3ro+Ei4+Kj46ai4Z9dn99hpGUhJilgomEiIqWtaeSm5qGgW92g3h0hoR5emt3fm58goh/c3Z7iYh7m5WShIeHcG51eH6Cdn56fnduaXx1ZnB7gGdqZWZnZVplWFJaa2VlaGx4eXBsbHdqepF+gH11eoBicWZvbnF8inRqX3ZoZ2x3gX+LhYeFio2RkZiLjoWKjYuIh42Ik5mNeo2Dinx7eHGAiXmDkHl8iXh/fHVvZ2pycXyFf26Be2+Be3tndHVrZGRxcn51jX9+fYCTjW95fG6cgIqDlIFxgZeFjYZ5bkt7R4OISHSCcHmHmVJRnIlTmHd7fICKgZB/jlJPdHtMfFCNhIiGh5GTgZFqZoyJbHuEeW17UlaKeEt0gpNMUpWVh4Z2W1hcXmx4mHRvgnF0koNVknyAdXOKcUJlUD4/c2psTXtBQlFFeXF5dE55fGdoSHxxgHKEclpdWV5ifoiDiHaKi4+AjHVwdWNucH5MSFJDeT5zUUxXUjs4XmA/cWlzf3ZmVpJKfH6Oi4VRUINMQHpQbjlNXklISGl3gHFpdmyKhHNrW2Z8fXNvbHNvb4t1cWdmbmZdYXRgd39oeX14e4yEnH11eXdpbXtpc3ludXuPg5KCgFGUfYd/h0h1iVFJfpeIj4yWhH1RkZN8ZHNpbmyBh5SPgWZycn5+dHB7e32Cenp/foublYd3bXWTfppSWU1teWd4VIqOf3eXlJmDlHSYgHBdboWXfX9+g4d6cX+Ea2l0dpOIeHR7iYF6cGl7gIWRfINvdXqFfYB7g3SCc3xoX0dygHJ8fImRhn51i3JHRkNFSkR+e46GeoGCfHpwh01IiYmGho6WnpCOkVF4g3h1f4l/iXRwiX2Benl6gImHaXGIYnt/h3B8eW1og1Z4dYCCikRSRkRveYSPhpCJgH+Pc36AhoSBfn2LiY6Iio6CjYiIgIeIfHN4gX58aYGTmYh3jISFgIaLi4OEiI+Ee3J6eW55eHR3eIqEjph9gIWIl4eKeX5Cfk4/gnZfY3R0Z2ldeHNtbGdoamRsbW1oZWBKbWx7b2xmamJfWVduZnVtaW1wampmanN4b21hf22Bbmd3koB5jEeDfVGRf46Bd3V6h3N4gYB6jIeNdYhxcHV4PnxpY22AgHhIfYFtdICYhIF1eHxyY3Btd45ZjoeQfn9xj3t2eXZ0dHV3dG5EfnaBfXh2ZmZ1f4tNfHRuaG1ydXF4d2d7hXJvdmY7OD5Ea3ZMfX14dztHPkZFPERJQV9aRH9vbW5ddmNET0dKQT1ENDZoZz1saG9nc1R7RUY7PklAYVlXakp6gFdrOD5DOklEX2iOdXVGgHGEaGhmc2xvYGdqcn2DjmhzgIpla2JmaXB1f393fGZnn4Z7fHlxeHeCdXFnZGhiaXZ+cYiXb3ZucHF7lol4iIt9e2xxfXNte3dvdWtzfW13f4Z9bnR3hX5rhX97cnZ3YmVubnV6cYCBhoR9eId+anJ2/3qNegZ7ent6enuGegV7e3p6e4p6B3t7enp7enuTegp7e3p6e3p6ent7k3oBe4d6hXsFenp6e3qEe4R6AXuEegF7m3qEewN6e3qGewN6enuIegF7hXoIe3t6e3t6e3qGe7t6AXuFegV7enp7e4h6AXumeoN7hHoBe7x6AXuLeoZ7i3qCe4p6AXugegF7hXqEe816BHt6e3u9egR7enp7lnoBe4Z6AXuRegF7kXoBe4t6AXuReoR7A3p6e4R6iXsDenp7h3qJewN6enuFegJ7eoZ7hHoEe3p6eoZ7hXoBe/J6AgIEAICinqSWlqm2p6Sytr+62LPKxLK3sbCmtJaqptjXytHGzsa3ttDDys7t4NfIu7m1wMjQ79Ta4eLWzK7JucfBsre+v9zT29LnwrTFuLuytMKirLyzur3DwcXBvLO9r6+/0tPSy7vZzMW4t6ivqLXAtqnEycjW7eHZ3tv3gP/u3dqNgIDh6fvV0/7rh/zo5cfrgYeG6vvx5OHd4+Pw7O7e3uHa1cjS29fU1dn11c3Oivne4Om71Ibi5/Pr5frz6fTh5ODk2uvp9fny0qW1wr/L8/347eLN3+Tez4Xu587LwdvT19vMzd+A2u2ClJGNj+fYwMXVgpaagu6GhIn96PXT1dXu/4CDjYvp8evOxNr49eLJz+Hf6tfJ2N/48of78eyiif2D/oeBjpKTkoH/j4/u9/Pi0fmtj4eBiKmwjJSCg4WEjpGUioOEjfza1MbF1tKzycDQzdbB18jFyd7Tz8jc5evV593Jsr7U1N3d2NzKsrzAw7uvrLu6tdf5k93K1tDW0djUzYDN0Nfq5Of119nTyeXY2vvR2sj26NHF4crp0Me+ucTR3unl0tLj1OPw4uHQ1dDLusHFyMWkzMzH1L/l/e7k5tbe4eHTytvb3uzt4+TQ2tS4tbPHxda6u6+yycHFy9zOw9XIwszH1NW/wMrTyfmNi/Hc1+fm3cvV3NfX8/j/kIDv4IDV6/Hj+vjy0+3l4+Pk9vjk9+bQ3Nbs9fPm3Of3gfTu4t3k4+Tb4/fX0c/f5t3T4Mn27oDy4eTr8ITqztPg3Ork6+Dz6Ozh8NrVytjm/IGBg/aB/d7v/PHv6trb3uLe4ePn5u7b4un65+jZ2ufz9+Xn8fTj4v7w79ft3ti4zcfc6IDQ2N3R6OfZ487V7uXm3+Xq/tvfgvL5gufr2dPW2eb+iP2Ggvzn7dDKybTNxcXOu7zGvdrT2+Du9+3b1tPMtaOrsrfAucHNzsHAxMfD0MrUxsbW0NC/ue/T0MrC0+rt9fr4+OT3iJfY29DPytjO08K3rMLHysLb6ujm4vmF69vq5YDj7vLR0tfw3vDK0L3MxcvXztPanOPykfzU283O1fHk1dDB4oaL/oX89eLZ2c7K08i1vdPHx6rEgObh5urx7IWDiviLgIL/gvzw8enugP37+4iJi4KFgPPr5IeOitza6u+blJKOjqCcqo75gYuGgOjv7vSQgYuM8vGi/+iFioP4jYDo7oGDh4KC5Nzxhu2DgID12+PjzMvNy87SydXP5Lyqtbq1rqamtM7M1e/j3dK5tbHHx8bMybbLy7y5usHBvcbQt7rh1LGxs6+qxMTfurTDxbu4sKysrLW9tJyvvbaWp7KnsZmTobW5uaLa2N7Jua20t6+xya+2pbO+wcG1vrKooIB5cnNua3eAdGx2en92jW2Bf3R3ent2gmVzbJSFfoBzeYBzfpyXnp+3p5aGfHxzf4aMpYmKkJGGfGeBdIJ+cm9ub4l+iYScfHSHe4B7e4lqd4Z7gIWEg4N5d3J5bGl1g4SCfW6LeXRrbmRsaXN8dWl8fHp+i39/gICmVqygjYhhVoCRmaiAf6eTWKOSim+UVFlYlailmZiOlY6ZkJWJkZmWkXyEh4J4fIGkhnx4WZ+Bf4Rie1SIiZSMgI6KhpORlZCRh4+Lm6CmjWl5hYKFpKqfkH5tf4N9d1iZjnVwZnlze4F3eY1TgpBSXlpYWoyDcXN6U2VnU5hZV12nlZx+f32Nm4BPVFGEjop1dIuoqZR8gZGToJCDjY2ck1SWk5JmWKBSmFFMVlxfW0qPWFWGj5CIgKF4YFtSV3h/YGFSU1RRWmFkWFNSVpeAgHh4jY95jomVjZF8jIF8h52UjoSTl5R8j4yDbXuFfn9+e3xqYWxzfXlvbXVxaYOaWnt1f36Hh5KTkICWlpemlpGaeXh5dY6BiKV+hHOcjn1zi3iLe3t1cniBho+FcG58cHuIf4B2hoB5b3Z6hINlhoB2fGJ9lId9hXZ9f39zb3l2e4qGf4d2h4NzbXJ/fox2d21wgXx9f4yDfI+Ee4F5goBucHeAeqNdWZZ/go6FeHF7h4eFnZucXlGQhIB8jZOGlZWVeI+Lf315i4x8hXtsfHyRiH50bnSITIuKgYSQmZqXmaaKg3+MlJKKmYGpm1SWjI2XnlyhiI+Vk5qTl42al6GYp5GJeYCIl01OUptWsJKeqZyPh31+ho2OjpiXkZuFi5epk5OLhI6copCaqKWOhZ6Xm4ukm5J0hoCQlYCGhYd9jI+Gi3p/lYWOhZKdroOCUY6UUoySiYWIkJipWqFWU56UoI6Li3mLh4+YhY2UjaOZl5aXnoyEg4yPhHeGhYGEen6JlYySmJ6MkoWNgIOVkpSNisGjnpWJlqmrq62npo6gW2CPlY6SkKGcoZaOhJKNiXmKko2GgJBRk4WSj4CKj5Z6fIGdjaaHkoKQhYmQiZCScpujZayQlZGWmrSnmpGBm1xgp1eipJ2epJ+hqJyIjp+Sl4KaabOknJucl1hXW51YT1eoVqijpaSjUpmWkk1MUk1VVqamqGhuZp6dqa5ybWdfW2lldGGkV2BcWaGjrLJwZGtqp55smoVPVlCaY4CZmlVYXFZVioylYaZgWlijjJedioyRjo6Pg5KLoIR6i5SSiH11eomBhp6Rk4+ChXyKiYuRjXqLinx9foKIiY6WhYitpoSDhX1yhoaad3GAgnp7dnh2dnyEfW93ioFicn95f3BxeYeJh22emZOBd21xdG9wiXJ5Z3B0e3Zue3pvc4BuZWdfWmZvYFllbHBrhGRycmNnaW5sd2B0b5eHenpqa21eYoB7goShkod9dXJseYCFnYKHj5eRinSUhZCKe3t8fZWJkIagfXCCdn9+gY9yfIl6fX2AfH11cmpxZmJxg4eHhXyXj4l9gnV8dXp9c2V2dXF2hXt5fHufUqSTgHpaT4CFiZp1dJN/S4d5cVd7R0tNfYyGfH58i4qZlp2Pk5eRiXuJjYZ7fYKef21sU5J1dHVRbUt5gpWOg5OLfoWChH59dXx2h4+TgmR1f3t9mJqPg3VhcXVuZk+PiXd1boF+goZ8fYxTfoVHTUhGSXNsWVddQExNOmpCQkiCcXtkZmNyfYA/REJncW5bVW2Ji3tnbH19h3Rlb3OHfkqBeXVTSoBCekA7Q0VFQTJlRUdteXtxaIhmTUtFSV9iT1JFRUdFS1FSRT8+RXlwdXN2ioZugHiAe31ldG1pboJ4b2Z1enxsgHJoU2Fyc3p7eHlmWmhwd3VtbXZ0bIWZWHpweXZ+e4ODfoB9fYCOhYSNb3JyaoJ4fZx5gXGaiHVnfm2IgoR/dniDh4qFdXWFeoSQhomAkYqFeoGCioFjiYmDi2+JoI5/gnJ4foB0b3h3fImIf4Nyf3hhWV1ub39qbWRmdWxpa3lyboOEgo6JkYt2dHV4cJhXVIp5fpGPhXuEiYF7jouJUkZ+dIBsg46EmJmYdImAdnNxhot9i4BvgoKTjod9dnuMTYyEfoKQmJmQlKGDd254fn11g2+XikqCcnJ7iFCPeoCGhYyDgXaBgImFlIJ/cnyCj0dFRHtFinB/j4WCgHR4goiHiI+LhYx3foueiY6FfoeRk4KKkpN9d5CEhXWSi4lxh4GVmICOjo1+i4yChXV5jXx8cH2HnX5+To6RTIWNg4GEiI+aT4hHQntwd2ZlZlZoZm53ZGpuZXhsaGZtdmtiZ3F4bF9nZ2NjV1thaV9hZ29kbmVrYGNxbG1kYZWBfXdveomHh4d9fXGIUlKCiHx8eYWBh351boCBgG99gXdqYXFAbml2doB4hJF8fYajj59/gG16bGxwbHByWn2JWZN0dHB1epOKfHVnfklMhUaAhX56gHx+hXxpcIJ6eGJ2Vo6EgHyAfEhFR3VDOjxzPXVvcnF1Pnh3dkBBRT9EQHJsakdNR2pzg4JXU01FPEdCSzpbNj88O2RobHBJPkI/Z2hQdGM+Qz1wSYBlZztARUJEcXGFTHhGQT14ZXSCcXF3dHR5cn13kG1hcXl2bGRgZXRsbYF5e3hpb2p5enl8eWt+fGxsbHF2dXuDcXWYlHFzc2tecG6DY2V7fnp8d3dzbW5xb2Zwf3haanp1fm5udIF+eVuKhYZ3bGducGpqgmx4anqDi4Z6gntta/l6AXuEeoJ7h3oBe4V6g3ubegF7hnoBe6N6AXuMegN7enqFe4V6hHsEent7e4h6g3uUegl7enp6e3t6e3qHewN6e3uGepR7snoBe/V6gnuOeoJ7nnoBe5V6AXuFegF7lHoFe3t7envBegR7enp7iHoEe3p7e8F6gnuVegF7l3oEe3p6e4x6BHt7enuQegF7hnoJe3t7ent7e3p7hXoEe3p6eoZ7Bnp6ent7e4R6iXsBeoR7hHqEewx6ent6ent7e3p7enqFewh6enp7ent7e/F6AgIEAICsupSowL7Ct9D7xMzFhfnBvb2+pKmomKfBusLAyMPDusDAsp61xsS5tL7Nxsa+vs/c49XOw7i6u62/0L7Cwce1udu6trrFw8S8wcjIucC9wra4s8zGu8Df1ru6uK6suMLl39DF2dO/y7jCyMLRzOHXx+mGjI+B6ur/7+Pl297o44Dk1un85dHe7+Xd4dHQhIWKjoqE++/q7fLZ29S7xbats7TIzNLO1NXIz+DI0IX94uLW59niw+KAg9vi8efj7fPl0OPd3cq9ybWqq6rLqru83evd6vH+ypL2z+T17PzS3erwgN3R28jBzt/i9fjuxr6vrNqDgt/JgqOK/4r4jZ7+9ICGj46QmKKK/4qNmuiU4d3hg4LKv83miPDd7YWGg/bl9vLe9o2Vk4uTlILr44Ht5YiHkJ2WjYr68tveytHW49SC4t7M8+b96efm28bU2tHj1vLgy87L0r3MtN344fHlgv/55YmR597c4OTk5sO+y8OesMG81+D81dLn3M/H1+DD1oDY3dzn9Ovk7u+Z9PHg09HVxL3J1djb9OPBsMTg8uLo8Ozc5eXl0tu/zuLw4d3d09bQwdW4zOLTz9Hi6e/e1dre5uWC/4Dz8e/13Nbbzr6ozIXO3PDl0qq5v7zOz8vCwry2trvDt8HHwNfg0cjS4dTh6OrfyMO4z87l5+3v8fXg3oDqhO3o29DT4ODn3evi7M3Qwcjc5NXX1ujo8ffN6uzn5ODYv+TX5vCS6ePt/+naz8XRz9qKhYn56f/01uPc49XN3NnUwMTe1u3j5OLm+vno5uLX09Hc7vjq4+Lj1dDW3u/pzdrL19ve2dfV1eTl9Pzc7OrmgYXu7+Lj0sXIzM3Q0oDT4uDd4uPH+O/v7O/m9+vY9eTy9d3t7uLk8YLx3tzt9vb1gdXm7NrI1Lu5tbu9tr3dw9HIy9rW5ubf08vJtse5v8PJzMDI2MTb0ODP3OXR1Oj649fGvdfO2tvDy9fl8vKEjYH0//j+89P93MvaxLi/rMK2srjFzNTj5/7s/ungyYDa+Nzj19nGvNW2w8jY0veD8fja1NfR7O++w8TV3eXf6oPH4tL4hIHp18a+xs3ht8fJ2N7Gv7PF0+rO9OmA9f7VzOX26P2H9on33PGDlIqDhIqLkJKVn4Xv+vnt5+bw3PLz8oOJh4r/g/fxioiSjYqKgPTrgoyIgoKSjI35+fiL+YDo7u+HioqJ/qPug4z9iIeA/e/N2dTI28jcusDVwqScv8q00NG4zM7HzdfP1dbPzbTjzMHM4snVz7qyurm0udHRxuP0up+is8LAxLvG0cfWz6y3qqu1vr68wqWqsZiqtrWdmaCjt7+uscvmzd/Rws3Suaipu6Ows6qvrKqxtr27m4B9i2d7h39+bn6jc3p2WaR4dXZ2ZGJmWGV4c3p3f39+doOOgnWQn5iMh4SNgoZ6e4qco5iPfXNwcWVzfXJ3d3lpbItycXmFgHhze4GDdXl2fHJ1c4uJfX2OhnJxbmRianCGfm9pe3lqdGRsd3eEhpuFb4NLTU9Fe4mzoZqZjImUkICQgZKdjXyHk5CKiHt4UVBUWFZUnZuVmZmGhIV0hXt0dnGAf4J+hYeAhZN2dU+WfYJ9kH6PeItMTH6GkIeNlJ+UhpeXmYR2g3Jvb3KManBrgIF3go2XcmGWb4KNhpdueoaLS4F2gW5obIGFoaWjf3lnY4pWVI+AWXFhsl+hX2uQf4BITkxUXWlUnVZXYIthiIyXXF6If4ecWJeDjlVUTo2FlZN/k1ZeYVhgYU6LhE6LhVNTWmNfW12ioouGcnl/kYVYj4d8pJGfhYaDgnafm5SjmauZg4eHi3+Pe5+4n6STWKafjFtkkImFioyIj3V3hoVmcndvgYWYd3iFgXpveoFtgYCJjZGYpZeKjIpgl5OJg4KDdW5zgIF8lIttY3GDk4WBgH5xeHt5b3Vfbn6Ti4mIfoB6c41tgZB8bWlxd31zc3yChYJMjUeBgIeMd3qAeW9ec1V9ipuRgWFrcXCBgHx3e3p0cW9zZmx0cYqUioKHj4OHkZGEbm5kfYSXkZCNj5N8fYCGUY6KfnN5hYSJd4F0gm10aXGAjX57cICCkYtkhYWChYR9bY+JlZ5kl5GesqGWj4WMhIhcV1meka+hhpWNlIN+iY6JdXmSiaGZk42So6KYm5eOkYWQnqWNhIWDenZ6gpeOe4l7iZSWkY6HgYyJl6GJko+NVFaZo5mglIGCg4iQjoCEiYmFg4ZxkpWRk5eUpZiCoIOPkHyQl4yVpFmbioKJjpORVYWbrqaaq42IfoeGgIuokZyOh46BioWKgYOFgJaHj4uKiHyDkYCVi5mJk5iDhZenl5GGgqSZnJqFjJinq6dZXlGZqqWyqo21lIedi3+Hd4R0bG1tZmp0fJaGmId+bYB/nYaOiI2Bdo1zg4SSiapaqK+VkpqSqbCGhIiXm6GhplyBnIquXlumnpiXoaS0h5KQmZuJjISSnbCLno9PkJl9gJefjp1SmVqmjZVRXVROTE1OWFpfbFOVq7KqpaStlqaholpgXmGxXbSybmluZWRiWqqnX2ZiXFphWVedm6FerYCfmJBTU1FRj2+XXWm4Y2FarqKEkImBk4OYeYahlXt2mKONo5+Ck5CJkaGXm5uVk3aciHp+kHuJhHBveXh2g5mZjqewgGdpd4qGgnp8hH2PiG12am1+hIJ/h3J0gGp2goFzb3yAjYl2bn2VhYuEfIyPfXBwgG17dmlrZl5kbnx7bIBye1lrdnFzY3ieb3FqU5loZWhrW2FoW2uCe4F4enRyaHF1aF54iYR5cXWEfX51d4aRlYqCdG92e3SIl4eKhod1eZN5dXuFgn14eoOHe4SEiX6AeoqEd3mOh29sal9faHONi354jI17hXV7gn2GgpR/aX1KTlFGfIWpk4qHdnSAfoCBd4iUgG5zfnlyb2NkS0pQUU9JiIOFjZOBhIh5inpxc3SGhoeBhIh7foRpa0qFbm1le3OGdY1PTn6BiX2Ag4l9b315fnBsf3R3eXiNamdfc3dpb3R5WE6EY36MiJp2go+XUoV6gmpfYm9wh4mHZmFQTG9FP2RRQFNHfUd8TFl5bIA9QT9ARkw8ckJETm9ObXN7TE1nYGyESYFxe0pIQ3lwgn5rdT1CQTtITUBwckeBe0tLU11YUlKRkX18bHBwf3FLcmlbfnaNen2BfGyGg3qIf5F8aW9sb2FsVnSNd3xzS4V5ZkhTd3Z2fIB9gW1vfnxgbnVxhYmYeXiGgXpxfoZwgYCDgoKKl4qAhIVbh4F3cHJ3bGdveXp2jIRuaHiImomGhYFze36Cd4FqeImZjoqJgYWEfI1uhpiKf3l+g4FvaW92f35KjEeCgoWKeXuBeWxYaE5wfI+He1xpa2dxbm5rdHh+gIKJd3V3cIGFfHV7hHmCi5GJeHVsfXyKhYOEg4Rwc4B7TYqMgXd+hoSHd3xxg292a3ODjYCAdIWEjIplhoR/hISDdZiLlZleiHuFmot/enB5cXVRSkyGgpyWgI2Ej4F5gIJ6aGyEe5OHhH5/jIt9fHl1dW+AjpWFgH9/enZ7hJaKc39xfYaIf3x4dYF/jpyIlI+GT0+GioaNgnd9f4aNjYCLlY+HgYJogoqEhoiDj4RvjHuHjH6Pj4WNllKSgn+Fi4yGSm96hXtyfmdnY21ya3CLdHtrZGpkbW9vamxza3tqb2xpZ1xgcGF0bHprd3xkY3SCcmxjY4J8gX5jaXSAhYFFS0KBkoqZlHmig3SNfHV9boB0bW5vampsbH9vgHVtYIB0lIGNhIuAcoNmbm11a4ZHhIxzcXx5lJdtam59gYWFiU1nfWWHTEiCfnh1g4qbbnp3gYJta2Jwe4xwh3ZEfX9kYXF2Ym88b0aAaW49SEE7P0RGTk5QWD5icnVxbnB/bXx4dD5DQEFwOGNeQUBIQkNCOmxmPEA9OjpFP0Jyc3pJgYBxbGdBQkJEdl11Rkx8Q0E9e3djcWxlc2Z8Y22GeWBZe4VvgoFnd3Nqcn94gH13dl6HdWlugnOCfmprdHRxe4yJe5ijel5hcIB5dW1weXaJhW14cHF7fXdueW9xemNten1vbHh7iYRtYm6Ec3pyan2Cb2Nkdmd5em91c2tvdn9+Z416AXvkeoR7l3qGe5l6AXuJeoJ7nnoBe4p6AXuQeg57e3p6e3t7ent6e3t6eod7C3p7e3t6e3p6ent7hHoHe3p6ent7e4Z6h3sFenp7enqHe4l6AXueegZ7enp6e3ulegF7uHoDe3p7i3oBe7B6AXulegF7i3qDe716gnulegF7h3oBe716g3usegF7kHoBe4R6gnuVegF7iHoGe3p7enp6jHuLeoR7BHp7enqHe4J6iHsEenp6e4R6hHsJent6e3t6e3t78HoCAgQAgLHGtbvT0dDF0dXE5M7EzMO/urfL0MKfrrm8zdPm5dLPxMSzoaGvu7ayv8HOwcHDz9Dt79PPzLa0vMTY077KyMzI0tqwu7+vzcrJ1NDPs9nPuMXU6uvRzcq5w7m0uLS/09br1dnez8PGxsvG0dbhyMa+xdr/gfrw5uHZy9nL3czHgPHq3+z9g/rshPvx5NXq8umEgef0jJKR+9zf2NXU1se6tsLI/OPg0r/AzL3R1Nzk7tri5MW0uc7g1oL55tmChfuA4+bJssCsoJ+es76SuM+YloiA7ePb4uWB9v7m5/j37NLgyOXm19H06NTC3NrAvL+tqdXt7Y7o8fPo69rW5tXzgITz/4Tx/OPb2tz88cuIgdPF2ubq6Ofi5uaBhoGKhqiT29r5mbGjp6+mhoON99bQvsnVzdzI2dTJy8zKx7+2vOrh8svt6oGKgIz52M+A+vPi78nM2PX44s3viOLZ9eHp8oLi8YDz3tbm4u7y/t63wrOwsrfm1eHd2snS4+bi38zQgNDZ1+zi6enk3YWDg4Db2t3Bu8LTw9ji0+fK1fLp+IeCivXr7c/L6t7Y3ero6fjt3ePbxsrUz9PJzcLE0Nvf29fa8+br8O734ODXz9bLwazC0ubX6se0ya/Cw8fCzb/Ssqmhrrq9zMnIytnIycTJ0d/Z5/Db+OLUxd3v7enW7OrggPCG6tXczczV3+Lq+YGF/fbu793l38/BuNf929zt7ejd3OrMydTc0dXd+NLn2MnAuszV1M/T0fbe1dnZ6Nv61tLY1N/Y8uHSwt3p9ezz6tbSy8C81e3U39rh0tjL5dXL1OPFzt/g4ujXx8jW1Ofm+O7u5u2C9fDp6tXN0tjf1szOgNPm8eXp9M3h59rV0oLh28fU6Pbg8tfc0cTr5/Tx6uzp5+zp/OP83OzRw73Gv7/CvNPG19HEzcvIw6+1qbS1scbBmNnUxu/Ow8PHzNPa2/Ty0Ybk59XGzMnh7OHg/P6A/uH4+eGB5cnW6eLUu9O0vMO/vNHDtrm9srW61dny5tzVgM3R4fbO4trM0be0yNDH1vTe68zhvbjFvr++1czLz87H2MXLvs3i6OLX0trQur7WttLuzNfR1NDKwOSA9vT+8PuQ6vb9hPP5+fTj4uyQi4mLmI+Nj5OBkfuCjIDx6/bg3/jhgJHtkIeOiIKNjJyEhYKA7t7f+vCP5fX3iv/o7dbmgN2Al/aKipCTiojwgYCBgvfx++Xi09TPxcHKwbfNt6/H2L3Y1c/z7+Po4dvgw7LWtuvMt8XO4NbOy7PMwsfC3Le6wMvJsLPUzNPOxsDHyMjBsLS6rsrAvrOw18/EtcG8q7OsrLe5wqOuysvd6Mvdysi0rKy4t8i3sKKVq7C0sLXHgHeEdHqGf3tvdHdsindsdnNubGqAfHNXYWhseXuNj399gYd+d3qFkoqBhX+Df3l7io6uspGMg2loc3mOiHWBfXx1gYpoeoRxioaDiYKDaoZ2Zm5+lpyCgoBvd2pobGlygnyIdXN2bWRlZWxte4SQgHtuc4OZTpiTh4iCe4V4hHdygJaPhZChU56NU5iOh3iIjoZPTIGKWFtbkXp5e3mAgHNlYGVoiIiOhHVzgGx5eIOHlIqTknpwdYGNfkuTjYdUWqZXk5p/aXlqX2NhbXJNY3NcXFRKgXl9h4lPjZJ3d4mJf2x6bIGLeXyakX9zhYh3dXJoaZKgo2een6OemoeCkXaJgEqDikyOoIyNj5GrnoVcWJGFl6KjnJKIioZSVlJXVW9deXiMXHFtc3psTktYmXh0aHR+d4FufnlxdXRva2dkbJWUp4OUh0xTSlOUdHZSpaKann1+iqKql4mpY5qPoo2Zn1WJkVKaiYOVjpebqI1pfG5saWuKfoaCg3Z/jpGKiXiAgH+LiJqPl5CFelBMTkyHhop1cGt/coCGfYlveYuAfkdDToF4eGdnfnNvcnyDiKGZiIWHeH2QhoNqZFRTXnB9enh3iHV9gX6Lenl1eYd+e2p8h5eIlnxrf2d7gIN6hXmNcWleZWlrc3F1e4yChYGCiIuEi459loV3cH+MhoR5jo+FgJVTjHl+cG53eHV5fEFEgoGEj3+FhnZmX3WYfICPh4V+gJB8fJGYi46XspGknY6KgpCUkoeHgqaLhYaEk4Whe3l9f4yIno+CcYeRlY6Xl4uOi4F9jaCFiYGFe4B3jX1zfoJueISJlqCPgYCEg5GMlpKRjaBdpqGYl4V+g4yOi4SBgH6Fh4KKk3OKkIiAfVePjHqGlJ+InYONg3ufmZ2bkIuGiI6OrJm+p7qciYOMhoOGgpeMmJCBioOAfXB6b3+GgZGOYZeQfqCEfoKJj5GXkqShgFuVmY2Cioyep5iSsbRZsJafp5BboI2VpJ6TdYx3f4GAe4p0aWZhUVRVcHWMh39zgG52hp6CmJGBinJvfYV9iquerJSrjoiRj46GnpWMj4mDi4CFf4WVnJiQkp6YgoKWeZKqkJyjqJySg5VUoaKnnZpbkqexV52cmJCFhotXUE9PXFpYXGJUYJpRXFOcm6uYl6uUV2KTX1pjYF5oZXFcXFdXnpCds69rnqGhW6OPnYmZgJNXZJFTUldaWFmfW1hXVp2WoYyLgIaEg32MioibhX+YooyimIyqoZCRkpGbh3qSdJ+Db3l8j4qFgHGIfH9/mHh7gomMcnOPiYuDfHd/gIN+cnR3aomDfXR2joN/c3p+cn14gY2Mk3Z5ioWRlIKWjo+DfXyFfIx2cGFXZmxwdHePgGp3Z2x7d3ZocXpvjHhxenJvbmyDhoBkcXt9iIKQi3l1dXptZmd0gHdueXqFf3t8hoWgoYSAfm1vfYWblYKNiIeFjJZwfoV1j4eEj42NdZOFc3yIlZd8entveGljZWJqfnuPf4WJgnh5d3pzen6Id25hZXiQTJSQhYh+dH5tdWplgImEe4aSS4h3R4F3c2yBh4NMRWx1Sk9QhnV7gIOLin5ybnN2joqKgnZ3gW57dXd6f251fG9tcXuFeUmLfXFGSotIeYNtXG5iX2pqeH1XaG5RT0Q8Z19fa3BFhZN8fZKQhHF/a4CBbmqHfmtgdnpqamdbV3aAe01qb3Nyd3Bxg26CgEV0ekBvfmxucXOOgmxPTHlrfoqOiYR8fntMUEtOTF1PaGVyR1ZPUlxXQUNSk3Z1ZW96cH1seHd2e317d2tkZoN3iF5wbEJNRVSMamZGi4h9gmJlcYSIdWiFUX1wg3N/hEdweUWDdnWHhI6ToYpnem5ubXSVh46JhHiBk5WSkYKEgICFgI+Ijod+dEpEREBxdX5tZml5and7coNxgJOHhEtFUIaChnV1kIN8f4iIi6KYiIuMen2Ph4p4d2dob3h6dHF1hnd/hIOPfX15eoV5cVxpc4Bzg21hc15ucnBodGyCb3NudoB+gnl4eIF2enZ5gIuFkJSCmIh4boCMhIN2goF8gIpPin+Henl8fnt8fEJHiYmIkoSLjHxqZHeUd3qMhYJ9fpKAfo2UhYKFmXqQiXt4b36Fg3d0cZGCfYSCkIKfe3d3d4B+lIV4Z3uFiIGFg3d4dnFtfpV9goCHfYF6kYR9h4x2fYaHkJSBcnN7fIqGmJKRh5FTkIiBiHt3gImMh4GBgIKMi4CGkXKHjYJ8dlB8emp3i5eDm4OIf3OSi5SQi4WAgIJ8j3aOd4tyZWNvbm5wa3pzgHRlamhnZldlXG50bHZyTndwYoNvaW10enp/eIN8YEdydmxlcHOJlYB6j4dEh3OCi3lPiXWElpCHboZvdnl4dIZ3bWpkVlVQYmB5e3dygGx0hJt6kY16f2RbZ25mbol7hnSMdHF6dnhwiIB5e3lyfW9wY2h2f3p2eIaDbm+CZHmNbnt+gnhxY31Ig4GJfXpJcoCFQXZ7endqZ29EPT5BUFBOT1NASGk5RD10dIVuaXthPENXOzY6NzQ9PEc6Pzw9bWBkeHVNanZ6Soh4hm98gG9CTms9QERJSUp7R0RDQHJwe21uZWdmZmJxcGyBbWh+hnCEenCQiXx+fX6Ib2J/YYxvYGluhIB9d2h+eH19knB0gIeKb2+NhoiAdm11eYOGeXyAcIZ9d2xtioJ8cHZ6cX93fYSChGZld3KDhXGEfHttZ2dxcot7d21kcnV0cnKF9HoBe5B6BHt6enuHegd7e3p6e3t7onoIe3p6ent7enuOeoR7hXoBe5x6AXuKegR7enp7iXqCe4p6h3uDeol7mXqEewR6enp7jHoBe4Z6BHt6enuleoR7kXqDe+B6AXuKeoJ75XoBe5h6AXu0egF7j3oBe4x6AXuFegF71XoBe4V6BXt6enp7h3qLewR6e3t7h3oDe3t6jHuFegV7enp6e4Z6A3t7eoZ7AXqEe/F6AgIEAIDk3+Lb/9fZ2LzIzt7h1cbZ7OLIxca5tre7v7K90s/LzMa5sr2ywMzNr7S5wsu7yNbP2N3Wz9ja0dLIwMXY5+nk3dnfttLTsr67t8zTw8rl5Nbe3trR29vVy8a9uLnEvr7Ly9Tv/erv3dzH2snE+97t+sjR7vn21d7m1sjIxNHT4oD+5tLJ0ZWS+4X54fXXhoT84Ofr8veNlYzo/PXIuLnFzNno0+vUzsLLycri7eqGjYX9gM21sNbJy7zb1tzl6PqWjIKMiunU4Km2qbbQusTroYiIi4PUysfSxt3549fW5PbR6+v84fLO9Nu32+LZs8bBx9DL5+P4ye3s4uLhwM25xYDVz+jj5O3q5fPQusbH2b2yuLm4vLm4zN766O3r/97v9dzt9oWQiPjx1d+EgNbO38++zdDdscXPwczR293b3N3p++nZ2IKK/JOG6fOBgdHR2Njz2d3o3dnT8erHxMrFzMrV0fDRzcnE1ebs3Ma0wNjIxdLN1Mnd4tPw49/K6eXf5YDp4uDk94GKhvX6+eDb7NjczdrB0dLrg+rz5Nje6/CKjIqF6NnI0Ojj5uH96fXzz97f4NDCxMm7yMjH0tXXzODK3dLQ79769eHi5dPa1c3LwMjEzNfN5dzHwMjP0tffxrG4pq6xuMDuz82/sK2zuMfQ1OPg4ef28snD1ubg3+vp9IDm4t/V6dDi1uqA3ery4vT079vm6dby/tna6+n46/Pe5+Lry8vU2sjg4YCR487UusXe0cjh+dbb0Mj44ebZu9TT0NzQ1tfR2c7g6vfk3MDOyLyvsMW8r8DOy8rZxcnI08HY19PU5NXV2cvV0O/d3trf69rZ4Nrn7ezY583e1dLb14Do+un88I3n9vmD4tjHx9bV3NK/wPrr18ext7XM5d/R5O709prb1u70yt/MzdTX1d3e0Lq3rby6v8a4rcLBxc7TvbW9tcDAyL3I1OXQurXk9s/8/t/WzsrK19nc8e6F7Zbp9d7f8dnSz7zExsrIuLbB2LXyg9LJ0fqG4NKEgOTo04DM0tb1zLe2ysfH5tTj4N7UzM3A3tG6ycjB08jZ7ePj+OyH5ujm8IHi5uO/wKm4vr/L5sTTyM3RgIDl6fHj9fT08Pf+iYD27Ij84OeA++Tohujx8vX1iYD4hYGC6PLy+eyA+4ON8oyvia+MjpOogIaIkfb7ku+LjeH37tH6z8zQ6oDX3vnljPiVmoaH/eDa697j5uDx2djVsc3N1qqir6u9yrW9wrzB0efx4OTi0su7tr/KzLvAuc3JztDNv72+wKzPwMPNxLTI1NfQy8O7xrbC187T0b65pq2nusK9vrvKwsfCzcfiw9DJx8fFyNHO1bysrLC7vMjBrr/BrazBw8TP5oCcl5CKp4GBgWhzeYmEcmR0g35pam1lZmZrb2RuhISDhIV+e4qEkJ2cfn6Ahop4gYuGjpOOipCWjYyCdneGj42CfHqGa4eQb3l+d4OKeniJi4aLi4aBgYKCfHZtbWptZ2dnZm6Fj4GDc3Nuh3t5sZCZoXeDl6Gljpahk4N9dXyAjoCtmYmAh2ZiolWXg5RyT02Mdn2EiZFXXVV+jYpoX2Jsc32IdYx4dm1zcG5+hoBPU0qQTnNqaYh7e2+Df4mLj6RnW1FbW4+BlGVyaHODbHCMY09UWVR7d3F5cIWZhnl4iZN3jo+YhY14lYVtk5eQbX10eoaEmpitf5mZlJCLa39kaoB4b32BhY6NkJyEeoGFlX9zd3V2e3VzgpCkkpKKmHmKkHeIjU5ZVJSQeIJTTXpzg31qdHqFaXV8am1udXJ0d36RpZyRi1pbm11TiZhUVHx8gXuSfIqTh42Jo5qCf354fHuBfpOBfHt0hJCQg3Fkbol6eHtzeHKFi4WjlIt3ioN/hICJhoKEllBTToWLi3h7kYaKgIxxfn6NUYmNf3F0eXhHQ0E5ZmVeaIB+g32ai5GSdoSAhn5wd4BsbGVcYWJlZ35whXxsiHOLinh8hHd8ent6b3RvcnlzjId0cHt8fYWJe3N6am9qam6SfIJ9d3l9fIaEgYqDhYyfmXlxfIuIipCOlYCEgYF8k3qMfIVKb3Z5aHt9gHF7f3eNlHJ1hoeYjo16g3+Jd3iKkIKaomFpq5qeho2gkIWZrYqLfneiiJGIa4GBeIZ6gH15gHqLlJ+Si3mLioN7fo6DdoGLhYGMe3x4gnB9gH+AlIuLkIOOgZuFjYqWoomLj4iRm5iJmoSTiYWOhYCVpIqYkVqKmJtRioB0fI6Qmo92drCjlIZyeXWGn5SBjpiYnG2Nkra9lKaPkJORkZiakH9/dIN9foF4bXt9h46Rf3J4dIF+gnyIkqKJdGmUnXmgp42HhoeIkpSWqqZgonCXl4aRp5KTkYCLj4uIfYCGlXWwXH5yc41NdXFOS32Dc4Bvdn2gfmxwgH57loeXkIuKh4h8n5J8ioyAh3mFk42OoZddlpyXplmco6eGiXF6e3yIpIaUj5mcY2WpqauhqKWaj5SiX1OmnliehoJLlIKGU46dl56dW1myZGBipaikqp5Yqllfnlx1TnJfY2Z2UlZRWY+YYKBeZY2lm4Cfenl7lYCKiZeBUoNVXFBUp5eSnpeWl46ikpiVfpqYo4J9h4WZpY+WlYiJkKCejZaYjox9d3+HjHp9dYN/hIKDfYCBgW+OfoKNgnaLk5KFgnlye3B6i4GGgHN2a3RyhIN8e3eEgoiIjo2dhZGLh4WDgIOFj392e3+LkZySeoqEc26DhIiQpICRi4iBm3l9eWNzfYqJe29+j4x4eHx1eHh+gHR5h4OAfnlsaXhwf4yKbHB2f4VzfIaBio+JhYuRi4yBeH2PmJOLh4WSco+Ucnt8doiNfICWmJCRjIV/g4WEgXhtaWduaGxzdX+Yo5aVhIF7jHpyon1/iGBrgZCVf4mZiHt3bXBzf4Cah3pydFxYjEqGcYdtTk2PdXRzc3ZJUU10io90b3B8gIiQe4x2dG93d3SDiYJKTkR/Q2VgY4F0dGN4dHt8eotZTUFMTXx1jWN0aneKcnCHXEdHSUVfWlloYHiOgXBxgoxrgYGMd4Fpg3ZghY+HZnhtcXl0hn6NXXJxbG5zXW9eaoB3bX98eoF/gYxwZm5zg3Fpbm1tcmtpeYeYi4uFk3WDim98gkdNRHNyZHdRT4KAjYJsdXiBY3B5a291fHp0eHqDj4FsZUZKek5IcX9IR2NjaWV5Z3J8cXRvjIVubGpmamlta4NwcG9re4eIgHNndI+Bf4F8gHOFjYOgko14joqIiYCKgX16jUtPSoKGgm9vgnh/d4RuenmHToaOiH2Ag39JRUM/eHpyfJGLjYOah5GTeISGjIBye4NzeXJobGxtaX5uhHtwjHqPjn2Ci36Cfnp2aG1kZnFshH9vaXJycXl+cW19cXh3eHiWfX51bnF1eIOGhZKJio+flnpzeoV/f4aEkYCGhIiFnIOTfYVIbXh+b4GDgnR9gniQnX6AiYSSi4h2f3uKfHyIjHiIiFFXkISNeYKWiXyLn3t9dG+XgId8Y3l3coBzenl1e3F+iJKEfm17enZzdIZ+c3qFgHqGfIB+i3yJiIWCkoqJjHyGfZeGjY6dqZCIhnl9h4V4iXeNhoKMhoCQmoCMg1N/jpZQhntqanp9hoBrb6idi31pbmuBmJJ/io+Pi1xrZ4SNaHlna3N3eH6DfGtqX2hlaWtiWWlueH1/bWFpZnFveXB7hpJ9al+BhmOGiXRvc3Z3goN7ioRLf1t3e2x2jn15fHB/hIB/cnJzgF6SUHhzcotIaWRGR36IeoBzeICfemhocmtke2l1b2xqbHBpjYNufXxze297iXx9j4NRenhygEZ6gohscl5maGdthGRxaHFzSk17fH10goN+cXF5Rz53dkmCamc/emdoRXKAfIF8R0F8RUFDbHN1eWc7bTo9WDVJLUAwNTtNMjo7RGJqQmhBSGN/e2mJZmVleYBrbHtjQWVFSUBDgHNwfHFyc2p7bG1nUGpsd1xYYWJ3gWtzc2dncYCCdHx/dnNmYWt0eWlsZ3hzenl7dXyBhHSTf36KgXWJko6Be29ocWZzi4SMi358bHJpd3tzc298eoODiYmafoZ6c3FucHZ4gm9hZWh0eouGcoSDcmyBgH+ImP96hnoEe3t6e4R6gnuGeoN7lXoFe3t7enuNeoV7i3qFe896g3uEeoJ7mHoJe3t6e3t6ent7uHqDe456AXuHeoR753oBe596gnvaegV7enp6e5l6AXu5egN7enuTegF7hHoFe3p6e3ukegF7hHoBe5B6gnuKeg17e3p6e3p6ent6enp7hXoGe3t6e3t7hXoIe3p7e3p7e3yJewZ6ent6e3uNegJ7eoR79noCAgQAgNTk4Ofq3NLBuNPHybjN0tjb7tHIrrO0zLW80djX18/NvdPHu7nJ1M7Q2brX3cq2xM27z87j7sDQz9i+xNnW7+HX39zIzr+3w9XWx77Ax9jRyNDMw8HOv728xrq1vdLQzNLJ0bzAzevSwcXR6/Xt8evr1vaYjYOBt+HZvbnqi4GDgPTu6N7XgO7fhpSL9/v0g/3Z4N7e5PTV8v+A+evY29DU19LS3OHSydrJ4Pr24+fo+fX329C5u8e1tbLQ7+vxuMuJ+YDk9pe7uc/OzMjD+/Dh7IykhIff3t3M276q0M3b2Org4uSA5OC31sTT3Mu2x7/MuM7GyMvy29T44ce4wcDdgNzz/IDk5PfYzcm5xMGptbO4trK+wMDTys6+y8zV4dzj7+3oxK+70b/A1rW/t9S/xMLT1cnMxt3L39Lv4tnP7u2P8OjzgOz5/YiIhon8kYv+lofqhonv1/TO07HLtMTN18rD17fBxsrNuMG3rbO2yNTx+NHI5YHu5u/Uz8/i7d/1gPHr6PP35vCGjZi+lIzl39jjy9zhsLa0yt/e4Ir964X2hP/u38/c2NDe4ebX1MfX8dLz++XU09rPvM3d29bc2t7M2tjT1+bZ39/TwMfEysDUydXW4eHtwsfK08zJy7TApLatsbu1zuLrz8LEwsPByObu6enSzLu/xdjl4/LXzOHXgObU39bx84zy4ffV7Pvw6Ojh3NTT9N7k4ejF3drXz9/x9uPj3cvTzdjd9tvfuKy2s8nkxtLXydvb5/Dd1czHyMPEzs/h4Ov494bs8fziy7rCurWesbzCzcLBt8fHy7/K2czc3N7e1+PX1NXQ09Hay9/l6u/p2u2C1dvb39PGzN7XgNvl6OXXjoDb19zw49nJ2c7J1cW/vbfE2sXF2NXU1s/m5ff4+OXXytPc7djDyeHe7enf2MTGw7eysLewyMXHyq6xsbCiqbvH487GxtHbws3I1tnj69/P6dvMyOHi5P33l4Ls8N7d1c+6vrutx7K0ws7jzc3g+djn4Ifj0vPk3cjSgMrCtLy5vM7U74Lu2tnY4P3n3NbPv8fru9nj0+3m2OXp9PCA5uve2tTg3e7Lv7ayuc3u5tm+wavZz9bW0u+B7fXw39rv+YTu6vTX+PqIgYqHgfr8i5iR/JSGh/L13dnzkpOclZb9+5CLk5ePt5aJhJSmkJ2Gio3hgJSI8f7u18rrgNHw4t/rgISVhITy39PW3/X13OTc0Nu1wMzhwtbBt8vEyb+pq8blje3p3dXUzsXDxt7KytXMy8O72dHGzcHKp8G9vMG21NbT3tHK0tXKx9HU4eL528CuvsPEvrWzr7e4vsPP0cbO1N3M1c/L087a2MG9trXAvabNyby+yMrNy83UgIeSioyQhn93dI+HhXB5eXx8kH15ZW5wgmVpd3x+hoWFfpaIgoOPmJOQl3aOi4BxgJCCmY2ipXyIipB3fIuGloV7g4V5hX51e4qPfnd7e4R9dnlubm54b3Jxd25rbX97d3ZudGZlbYh2aW58lp+boI+LdIxbWldae6Gcf3SZXVRWgKWcmpOOVJaFUVhRiYWBTJJ2f4OBiJJ4kZpQoJGGiHt/gXx6hIyBe4R2gpaOgIKCmJyljIhzdHxramBwjoaGZHdWoleWomtxaoWFg3puopN8g1ptU1iIg4VvfV91c3aCgpmRjpBUjYJfgnJ8hn1ue2+CdId+gIyplIyylnhmcmh5gHaKi0d8gIx+eXl0fn9reXF0b2p2eXWFd3xocGpweHV5goSFbV1jc2ZtfWRrZXtsdXB+f3NwbYRze26Ad2pkhYddlJ+oWpibi0tHSlKkYmGrYVCIU1qbk7SUlXWEbHV7iIB6hnB4eXyFc3RvZmZieH6Zj3Jsgk2LjJJ6cnR9gniOgIuEiZKSgoNJS1FtU1CDgoCNfIuTaWZgb3x2dFGPfkd6Q4N0b2l6eXN9fod+fXWAkXCNkoF6enpoWGNoZ2Z0d4Z2g3dwdIB0eHlxYWljb2J5bHVzf32FXmJqc21wdWZ2Z3hydHZpfIaVgXV6eHRvcISHgYJxbWNmbHuIh5Z8c4V3gINzfXmUmF6Yip12f4aDhIN/fXR0moSKjJJ1kI6He4GMj36EgXyKiJCXsJ2ffXOAeo6nhIqJe4WBjpZ/e3V0dnBveHiHg4uXmFSHk6CLf3iEhoJsd3+BiH57c4OFhXd/i3V/enp+eouDhIaAgIGJe4iPkZCLfpdYgY2OkYd2eod/gISPiYd6XFCBe4OUioV+kIqIloZ/gniClH+AkI6RkYialqGkppqSjZuktZ+JiZ2Zo52blIaGiHx4eIF6i4qTlnuCfXlvdH6EloJ/f4ONdXt2gYKLk4uAoZaHhZiZmqydY1SKjoiMjZWKj4t7knZ7g5CdiISQon2Jf1CIepqJgWhygHJrY25wdIOJm1aXhIOFjqmakpOTiZC3iJyciZqJhpSboqlboKWflpqprr6fi3xvdYWfmpOCiHqkm5yZkahZk5aThoudpFuZmKB8l5hUS05MTZifXm5hpGddX6aokYqkY2hwbWyro2BZYGJddF5UTlxsWWRTVlmGUGRam6KciH+hgI6hlI6RTFBcUlOelo2Ll66ul5+dlKCDjpCiipyIf5eWno96c4KVXZGTjYiMiYZ8g5WDgpCIhH9zi4F/hX19aIKBgIR8k5ePlIF6hIh9f4eKkpKjjXtvfoWKi4aDgYODg4aNkYeGjZKIi394gYCLiXx8e36Hg3KOhHV2gIWKhYqIgHyFfoGGfnltaomFg3GChYeJoIyGcHp+knp+joyKhn96bX1yb256h4B9hGqEhXxygZOClYWYmm9+g4x1e4uIl4mDioyBi4F3foyQf3Z6f4qBe4B0b3F5cHR4fnNvcYF/fYB6gnR5hJyIeHp+kpiQjX12YHlTUk9Tb5WNc2iDT0ZHgIWBgX51S4FwR1FJfYCBTJRzdnJudX1jgIxJmY2Eh36ChYB+g4V2cH1xgZKOfXt2h4KEc3NkZ25fX1lrhnt6V2lIgkp9iFpgXXt9f3pxopJ9e0pZRUpucXlkdllyanB6eIiAfH5JfXZYem56hn1ufXGCb35wa3CGcGuNe2ZZZGF0gG+AhUNydYRybG1kb3VhcG92cW15eHWFen5rc3B4gXyAhH9/aFdaamBoeWZwbYZ2enSBgHJrZHpte2+CdGRhfnlSfXyGSXp7ckA9PkN+TU2ETEBpQkZ1bo91gGh+aHV6gHRseWFpbXR9a3BvZ2lofYKck3VpfUqJh455dXWAiHyMgIqBhouMe31ISk5oS0ZxcHN/boCKY2Zic4N+e1WViE6DRo+CgHiLi4OLi4t7eXN/j3KPkYJ9gYZ8a3V8eXN3e4N2gnt0eod9goeCc354fG98a29teXmDX2ZueHN0d2d1Znhyc3ZrfICPfXN5fHl3fJSYlZF8d21tcoCKiZN8c4N6gIp6hYOYllqQfo5ue4WBhIN9e3NzmoSSlZt6koyHfIWSk4OIgnaDfoOHnoqRcmhzcIKXdXp8bnp5g4x3dW9xd3NweXaBen6Gg0h1gY1+eHN9hIhvfIWFiH14bXl+g3mElYOPi4qLhpKKiIWCgYWPfIqRjYh8bH1KaXV4fnlwd4eCgISEe3dqVUt4eYCOgnlre3RwfXJudG97j3p3iIWFiICPiZSUkH9xZ3F8indiZ355hYKEfXBzc2lmZGhfcHR7f2pxcHJqbXmCj3l2eHyHbnRsdnZ9gHhyjoR7fImDgYx+UURyend5eX1xeHluhWttdH2JeXiJlXaCfk+BcJCEfm55gHdvZW5ra3V0iEuBamhmb4t+eHp5d32kdYqKe4x7coCDi4pLeX12a3KEiZ+GdWlfYm6EfXVjZ1aBcWxlYHVBb3h6amd0ekVwb3pedHNDOj88O3R8S1lPe1BERXp8Zl1wRkZMSEdoYz84PT07Sz85OENSQko5PEBcO05Hf4iCcGaDgGx6b2ZoOT9JQkV/eXFzepGMdn59b3dYY2l5ZHZiW3Rwd25ZVGN6T3d6cm5ybmtkbH9wb354d3Bqh39+h4CDa4WBfX9ziYh+g3Jsdn10cnyHlJSnk3todHp9fHRva3BzeXuGi39+hYh6fXFrdnJ9empram15emmHgnd4f4aFgYOA83qEe4Z6g3uFegp7enp7e3t6enp7inoBe6Z6Bnt6e3p6e4t6hHuGegF7iHoBe556AXu8egh7enp6e3p6eoR7CXp7e3p7e3p7e6B6AXuReoZ7jnoGe3p6e3p75XoBe8F6AXutegF7jnqCe9h6gnuXegF7kHoBe5h6AXuaegF7h3oBe4Z6hXsJenp7e3t6e3t7hXqFe4J6kHsEent7e4t6hXucegF72XoCAgQAgMba4N3a3NDa0r/L1Mqxy7u6zs7Ixbe2zr60ydDVwsC/xNbJzdfd5fHQ2efPx8G9y9DNzM/Fyb7Hv8bQ09fOzerc29TE2dHHuc/Jyc/VzNrSwbzJycjJub7Evbq0s7ax4OzRvuzXzNzV47vHyNbV2eHT3d377er764X/ytHZ7Oz4gOvQr+nRy+Tl8/6C8/KPgPnq2cTV7OHZzuXs8vru/93I1tPd1dDHwtfd+drXztLZ2vTw1dzG4NWzutC9xKbM0u/n3NTix+7vzufl4uLe3M/14OnsmKHf4M/Fi93/7N/z2cjRxcnBy8W9xdK9rMTd1cnSxc3H4Nzk7ZPqlJeroOnmgMP8+O+zsbO7ycrNuaitpLnYwbDCy77Exr7R4Ork5djfz9HM99LVx8rXzLrGy8vAxc3O2OHn2+zM7eqC9Iz5jPfp5/375NLeyOLy8/v6gIaEoZDgl5KC7MnHzMO7p7zAwtDW07/F2ubbudDTwr/FvtO368Cx6NXc4tXV7OLw5tjRgOHp9/WDg4P6gIaC7+fQgtvV09bTwbvJ0unb2ODugvj7//zt8OLixc/L4r7c0Of3+t3v/o3u2unj7+3o687q3OjHyc/C09fT0MfPxcXJycnAxdXU1M7OwMXb2srzysmzpqOrury6ws/iz9DXz9LY3t7o7/DPz8fL1dvWyMbEv8fKgM/nvdDe5+jk8+XagY6B9uDw0dTC1t7o9O/e6uzz8f/68/r349DF0Mq01tjVzMTG0tXg9u7X1dTG59W9zsrFwa692dzh5Nzk6/DW4t3Szca6wamqs7/S09jCu+DS783N3tnU1MnN1+Dd3M6/0dXLwOPn19fWxdPh/uvr4NLW1dLdgOvY0dDR2dK0xcjc/tro4NbJ0MfEuru4wau+z9PZ1c/R1fXh5e3r8+DT7M7C0t/q2O+4xMK5urq2tLPCt6uws52Rpq24wsPCxsDCvLijnMzFxdTh2dnd1N3a3NPt3Yb+jo3x9drhyL7MvdG3tri7trm/29re8N/d0LrTtqzEucrcgN7QxcfRvLvF4fvk5NjQ0c/k59rgzuzR2+Lc84eB7Onsxd3i1uLd29Pc0sesu7etxLi9vb++x8PJy9Tr6vDg3vPoz9T29PX+i4P0gIWE+oqNgY+hkobf6O2Gg/L19P+Nlp+FmYWEi5OVqJOKm4Lsi46Hh4+Sjuni9f2D5vLn69PtgODi4trq3/WH9+vk2tzL2dDa3+XXytHd7dDo1djawL/W083Gx9TV6tzh2+ft4cjTzcS8xsraz8DP7N7X3t/Uz8rMx8/D0ejw2Mzgw8XOy8/X2NfrzMbQvrO4uaqytLqgtszjxNDT3eXDzrfK0N7i1sS+wLq8sq2mt8fFv72/zdLLgHiEiIKAgnqMhnaEjXxgeGphfISAgHR1h3lugIOIfn1/iZiPk5uWmaB9go1+dXRzfouMjYyHiIKMhoiOk5aKhJWCgX50iYqCeIuCgIOEd314ZmJscXJwZGptZ2hpam1oj5R7aZF+dId/jG5/f4+Pj5ODi4afkY+glVuxgISJm5urgKqUebGbkqaeoaBQkZBaUZ6VhnqGnJKKe4+bmqGWon9rend/eXx0coSKnX96cHN5epeVgox6jIdoaXptdFd2d46FgoCLeaOfgJaPjYuGgnOOdXt+WWWChnxuVn2lhnyFc2t6cnp1fHdueodyXnOHgHF8dn11j42UnGWZZWd3b4eDgGOWjIxfX2NvfX6FcGJnZHCGc2VweW5taGNteH11d254bnd0l319cHiEe2p1eHJxeHp7iY+OgJFtgYJJg06NVpiTlqiglICGaYORj5KTS09OYlByWVxVpYuIjoqBbHJ0dYSDfnB0ho+Ma3h5amVhYXVkhmpjjnp9f3FziYKMi356gIiQmppTUlGVSU9MhH5xUYGBgYB8a2Zyeo12anF9Rn1/g4d8g3d9anV5kHWMgI6akHyHk1SCc4N/hX55fmaNfpBtcXBicXJsamVsYGNnb3FwdYOCgndyYGJ0dml7dHlraGVseXNobniNe3x/eXV6fHmFk5V0dm9rdHp1bWxraHJ0gHuTb4KNkpWKkYJ4TVRIh3iJdXlvgouTnJSGj5aZj5OLg4qMgnx1gYBsipGKgXV3f4OMopyHiYN1joNsfH55eGVwiImOkYmQl5t+jYqBgIN7iHJxdH6QjZV9d5uLmXp4hHh0cWhwe4iIiX9zfYJ5aYWRg4qOf4qUppSNh31+enqAgI6Efn2FjIVreHWDn3ySj4+LmI+MhIJ+h3SDkpWYko6Lh6iSk52gppyTrpGGkZebi5pxfIB6fn16fH2Of3WAhnBlcnN5e3hzd3F3dG5aV310dICNio2WkZiRlIigkWGnWVeNm5Kfj4eWiqGFgoCCen1+lJOXoo6NgWqIaV5tX2d7gH9zaXJ9bXOAmK6cnpeTlI6lo5qjmLGZoqSWpVxTmZWefpeZlZqWjZKdopyHlY17joCFfX5+h4WKjZGpoqeXlKOhj5Otp6amWFaUU1lUkVRVS1hrZVmHj49STo2Wl6hfYW1YY1dWXWFlb1xUX0yJUlRPTVJaVIaHmKlYlJuZmIafgJqWl4yYh5hTnJubmqWTn5admZyRiI6ap4mck5ediYylopyQgoSCjH6HhI+elYKLkYqChoqXhHqFm42PmJiNioSGg46Bi5qjjYKUfoOFgX+DgYKXgoCKhX+GhXuCgIJugJOif4iFjpV5hGx2fYiOiX59g4WHeXFlbXt4c3F2foR6WXOAhH9/gXmGgnOEi4JshXd2j5aOi3x/lIZ6jYuOfXh1eIJ6fomIj5d0eop8dnV2gI6NjIqFhX2GgoWIiYyDf5WIiYN7kI2DeImFg4aIeoF6aWZucXBuZXF6hHKAc2+Xn4d1oZGFk4mVcXh2gn98e2p0cY+GhZySWap2d3ODhI+HdV6QfnuOhomNRX2AVEqSiXdpd4+DfXCFjZOflaKGc4KDjYaHfHWHjqCDe25sbm2DfWt3ZXNxV1xxZWlLaWh/eHp3f2eKiG+KiouLhYNzkHFzck5RbHJtY0l7oIOAe4VzaHZsb2htamd3hXRid4qFc3pwd22AeXt4SW1NUFdTbWxShYKDVlheaXV2fGpgZmZ6j35vfYJ3eXNseYSIgIF3gHN1cY1vb2Frf3lpdnx7eXp5dICGgW9/W3J0RHZGfEx9dneHfXRiZ1FreHh5dzw/PVBAUkhJQ4Rtbnd0bFqAZ2tvfn16a3GCjIhmeH5wbW5qe2aIaVuGd36Ac3eNg5CLe3ODiZGMS0pHgEJIRnx2a0t5d3d2d21reYGOfXV/ik6LjIiLhYuBinmDhZl6jIGOmI+DjJRTg3eJio+MhYhsjX+ObXR0aHh7eHl2f3R3eXl2bWt2dnVvcGNpfoFxh3iAe25pZmx3cmlvd4t6foaDgYmNjJSfnHt7dnB2fHl1c3VxeXyFnXWJlJWViox7dEtTRYJ0hXF3bIKLl5+aiJKVmpGWj4aPkYJ1b3l0YH+GhoJ2dn99gZKIc3RyaIB4ZHV3dXdiaoGAf352fYKFboKCe3x+c4Jyb3F8jomPeXGQiZyAhYWXjoyHeXyHkI+LfXB8gHlmhI59fX9qcX2RhH97cnlycnuKfXRyc3x3Xm9vf5hxfHZyandzd3d4eYVveIKFh4F6dnKVgX6EgYV5codrX2t2fHCBW2tvamxraGdmc2hmcXhnX21yenx6enhwdHRxX1mBdHJ5gHd4goCEg4V3hniAToZJSnR/doFxaXhvjnV2dXtwb26CfoSOgYB5Z4NlW25lcYSGeG1udmJhaHqMeXhuam5rg4V+iH+ZgomMgJRTS4eFi2l+fXh/e3F6goOAbnhxZHhqbGdpZm1namVld3F0aWl9fWtofnh6f0dDcT1CQnFCQjlGV1FGY2tyREBvc3GAfERDTTlDNzhAREVMPjlDNmFBQz49QkU+Wl5ugUR1eXp8bYZ/fX5we2t+RYB/eXyAc3tzdXV5bWBkb35idW1xcl5hd3RxamBnZ3VncGx3g3hmcXRuZ2x0hXVpeJKDgo6Rh4J8e3R+c4GQlX10iXF4fXx3gISGmYmFjH95eXFmbmwjb1xwg5Nxf4CPlneAZ3F2fIF4bGp0eIJ6d2t3hH9ybnB5fXb4egF7kXoFe3p6e3vEeoJ7hHoBe556Ant6hHu8egV7ent6e456hXsEent7e656C3t7e3p7e3t6enp7jnoBe5V6AXvXeoN7/3rQegR7ent7unqCe6p6B3t7ent7e3qHewV6enp7e4R6j3sBeod7hHoBe416AXv4egICBACAwL7HxMnpt7m+zcrfu726wcOxr6ums7m7udzNycDAura7uM3D1MvV497j4uXOy8m/zcO9zcHJ1Njj6dDYwr+82c7Q1bzb9s7BurnPyczC0LvHw9K+1dHQ09DJwcK6vsjd2uLF3dW50tjD57i9urLBw8XYgYXz+PHh64DTzMnN1uuA1Leu7srK39TE1Mfj9IH45/vl2tXjzM/f5NrdgoGA+vPez9nQz9jYvt7s2MvEw9PkxtT3gYSRj4LujPH18NjU6urlztvjzMjg09Le3tHRvtvh4eyGkYWUg4Ci3MvI0IaAztXb5da2ur604dq714nay8OtrdTb4e7yjvqlxPusioeAhPji3sq9usTG0cjFxcC1r7nNu8vO38vP2dXh3t7IxMPGze2b8ePr1OD67NTU09DGytbtg4rm3uyMlPeXiYaG+u/F3ufa4fqVhYfx/vLWysbk4+mWgIX6ybrPpbrS3LTt6r7Px9LU4e+zytXE2NrSytPdx8Lr18TM2+nsyobc2N6A3/Ps8Pf9g/fv5vP/4MjB1u3zyr20trrP2LTS3oL7gpKSmYP6h9DAytHP0+fGwsfg3t/x44Tb7fv76faD3drW0Nbb2NO1uca8ycjWydjY1czT0NPKydG5xMK8tLOntaqbpa+lnLWzwO3N2czJzcbFzMjb19HSw9/d39jXvbPGwseAzMvBusrczsvZy9rK5+bw1ODQ3cHVxdDJxuTM+uPJ0+bu6+rQxcvYtcLI1+bq7d/hyt7myPbu3tPcvcXVuLOyw8Tc2d7bz+/g5uLW5Nra1MjLtbC7ycjTvr7Uv8DG09Xd59bFvsvEx9XT0+Xd2c/G1ObEwvfc2OT04eDo2unu5d+A49bn3szX5NPX3Nbr94SCgNrfzcm1wLfHtcbJ1eTe2Nz299703N3Y8tHvzsa96OLU29K1ta6pu7OwxMHBuL25nqamrbWxxdTcxb/WyLzIxsm428vY1d7p4ePl0dDO3omEgOTY+dbIub6ytbC5oaXCwLu7ysreybvE7uLNv8XX1tmA283Zuayv3vGJ6s3Svr3G3drY4PrV6t3iz9/v9O7n5OHW2u7S5e76/9rFxbDY07TX0dXSzL7Gy9Pgh/3o2/b36ID44fjj5/z07++bk6yK/oP8/qTz1s3U4Ons/+v6mYOWjYGCia+kj5WWhonxgIX26ITth4GGi5iRoIiLmeuA4PqA9vfm3PbqxrzOzMza1c/a0MvVuOCzrsvV0dHQ18Tj0s3XzcTh6+3P5N/67OXcwuDa0tze1tTLvrrC19LS3dLi0sDCrcO/t8rMyNe6wdbY1O7NydDCw6y2srS0sLS0tLO0zMvP4eDeheLYy87RytXYnKawssas0N/auMXDv9rMzMWAdXJ4dHmXcHZ7iIiecHFtcWxkZGNgcHR2cY1+eXZ6eHZ+eIqBjXuBjIWKjJKAfIB4hIJ6g3V8hYmbo5Gci4uElIWChXSSsYqEe3iHh4F3gXN4cXZjc2prd3ZybG1iZWt+eoJqgn1nhYx9onh4enaEhIKKVFeenpqPmVWJf3+HjaCAkXxvqYuImYl0eGp/iUqShJSNgX6IeHaBgXp9U1JToJaMfIaAhIiGa4WPe21qbXWDbHiXUVZhXVCHUouRi3p6jI2NeoiNfnmVh4WLh3h2YnR0cnlKVU5bT05pf3VufFNPcnyBkoxwc3Rqj4Jff1p7a21eZomTmJ+eXKBpg6NzVVKAT41ycmlfXm5ygHt6eHZuaWuBbXZ1g25pbm5xcnZrbnF5f5FlkIaShI6hjXV6e3p1dn+TVVeHgI1YXo9iU1NWn5h9kpqLjqJnVliXo5yDcGt+fH9cTFGkgnaIZneMkWufn3N8dn+CiJxhcntve3Z2c32GeHWYgWttd4SIcVqLiZaAlKeZm5ucUJCAfouZf29ofZWif3NpZWh1dlhrcUaAQUxMVUOFT3JteoOEhph5eXiHh4WPek53eX5/c4BKc3t8dn6IgX5iYmZgaGlwZ3R7eXV9eHp0cntjbnFtaWtjamNaY2xkV2docpd5gXJub2lnbGd+eXNyYnt7eXV4amNzc3iAfX1xanuGeHd4bIR/jo+PdIN2d2h/cnd0cIx5ootxdIaMh4V3eH2ObnV2hIiJiHyAb36DbJOPgoKNdn6RdnNwfXqNh42Me5uDioB6h4SKjISQfniDjI2RgX+UgYOEjY+PkX5qa3x3e4R+hJaHgnhwfItwcKKGgpKgk4yPgImGgHuAhH6Mh3d9i35/hHZ+hktJSnmIgod5hnuHeoOBjZmSkpWpp5CijpGMpIajhX12n5eNk5BzdnFsenN1h4WAeoOCZnBsc3hweIKHbm+Lf3SIgoJvkH2JiJKemp2ml5aPl1lPS39+opGLgo6EhnqCa2uEhXt1hIaSfm9wkIZ0anV2dHmAgHqQeW5zmKhhqI+Vh4WKnJ2VnrydraOsnqOxsbCnpqWSlaCCj5qfq5SKj4Chk3SWiYmKgXJ6gIKXXbSdlaWllFOej66gnaufkotfX25UlVCWnW2fiIePmp6hrJKcZFJkYFlXXnh1XmVhVFOOT1eZk1WPUk5RWGJdbVlbZ5BKhaKAoaGUjaKUfHSGipCjo52mnZeZgKd8co+Ri4mOk4OlmpWdkoaXnJpzhYSfko6Hco+OiZKUiIaCeHd6jYuRnJOgk4aJeIqFfpCRjJd9fYqIiaOIh4Z5iXV+foaKiY2IhYCAjoiHlY2NWZGDeXh/foeKYGd1fYpuio+BY3Nwbox/gHeAcW52dHyccXR5homdd3p8goV8f3tzgYSHgp2LgXt7dW1wZ3ltfG55hH+FiY1/gIJ9jYqDi3yAh4WSnYqRfoF8koWDhnOQsIh/eXmJiIV4gXJ5cHdidWxue3p2cXtydoCUjZV8kpB2i5OAo3N0cWlwbGp0Sk+Ok5KKllWDdmxweImAdmRdln1/kIBpb2B0gUaHe42BeHeEcXSCg3d6UE9Pm5CEd4R+g4qHb4yZgXBqam57Ym6IRkpVT0FwSX+Gfm5vgoOAbH2EcmqEeXqFiXt8aH95cHRESkBOQ0NTdWxodVJMbXiAkY1wdHZvlotshluCb2tYW3uAgYZ8SnlOYnBVQkGAQnttb2dcXmtteXR0dndzcneOeIOBjnp1enl/fYBydXZ9fo1fhHZ/dH2ShXJ4eXVqaG6AR0lwZ3FKTW5QQkRGfXVYb3hpa3lNQUNufHlkV1BhXWBKPECFZ2J4WWl7gFuKjWRvaXR1f5NbcHxxgIB6dn2Dcm2Qfm1xfY6QcVGIgImAiJ2Oj42OSIR3d4SWe2lgdo2ad3Jub3WAfWBzeEmHRE1MVUSHTXRzeoeIipl/e3aEh4KKeUx4hI6OgYxNdXZ1cXqFhIFpa3JuenqCeYSDf3V3c3ZxcXtndHt0bnBpc29mbHRrXGpnb493hXt7gHx7gXqNhXx6aYGBgHp+aWJxcHOAenpwbX+MgX+BcoR5hISIcIB1emV7cnx4eJJ+pJBzd4WLh4RvaW59X2hse4eMi3+Db3p6XoN9cnN9aHGEa2lqeHOGfoB7bIdyeHRygn+EgnqDdW91g4mQgYCdfYKEjIyRlId1coJ9foeAhY+BgXhseYttapl6cn6OgXyBc4B/eXaAfXaCemh0gnN4fnR+g0dERGdxa3Blcmx7a3JxeYN8enqLjHmJc3Rvg2qBZlxYgn52f39qbmhjb2lpdnFxb3h6ZW9ueHtyeoKBaWeDenOEgoJuiXV6eYOPio2TgHlzdklBQG1sjnlyaHFob2lzYGN5dmxpdXiKdWNnjYV0bHV7fICAhHuKa15cd4VNf2hsXmBpgYB7hJ9/joKIeYGSko+GhoJzcXxgb32ElH91fnKQgWKCdXR1cGBnaWt1Sol1boOHdz9zY3ttbn50bGJKTFtEdz93gV1+Z2hweHx/hmhzTTtKRj89P0tTQUlHPTxmPUJwaUFnPjc6QExGVkZJU3A5a4SAhYV4cIh9YlxtcHKFgXqAeHFzX4ZaUW1wZ2Rna1Z0amVtZ2F1fHxfcXCJe3RsWHFwbnh7dHZyaGZtf3t+jISXi35+aXd1b4KDgJBxc4OEgJqIiYl/i3V7e354dnl4eHVzg3x6iYWHVoh4cHJ6eYaJWGFwd4twkZmRb3t0cIt9fHbyeoJ7hXoBe5N6AXuNeoN7lXqFewJ6e5l6h3uEeoJ7jXoBe4p6Ant6h3uiegF7j3oIe3t6enp7e3qEe4h6g3uJeoN7pnoBe4l6AXuVegJ7eoV7Anp7j3oBe4Z6AXv/etJ6g3vOeoN7p3oBe7B6AXuGegF7iXqEewV6e3p6e4p6jnsHent7enp7eop7Anp76HoBe5l6AgIEAIDd3fz21NetrLK+xcSylZShqJ+nq6GgqKC2vcrQysXWsre5xejEs8LHxuPr3+Dj39DU2szX4s3Px9HSz9bI1sbHzNnMuMrQyM/TwrzQ2dHaxNfWxtLi9uTY7PvIx9LL0My70a621MTR2/PozM/Gy8PIusLO4d7m6fOC7N3Y4L3i04Dq8/7v8uPo9Nbc+N/m/ofk8/jl1tfWxt3v4NXi7Yb39ODU287Z6OrMuLO7vb+y3d7M7/P2k5CLgf3xg4yJiIuIgtW71OPZ2cDK8dnWyNTX1ejg0vuJhZWTiaTdxPnL0MKqtau0s5udmKq6vsfg9srQxNS20OLg+OLlhYGIkImEioCFipmphOjT0M/L1b3f68vS2OLn9OyC94H38urk3tTbz9be5vDn8YyHi4SE+u7c4/2C64mDiP7fkYeA94D66/bv6NP38vyAhIWM9/j94Onzg5GfkJmE/su8x7OxyLfe8o3xw8vA2u3q4tXu3cv0guPa4MzR0dvYxtfZ3MqkpMXO54DazMi/0OzugPL1gOrf2Ibn+vrV48yyycTAv9HK0P6A//3r3Nnm37/W/t3M08Hi78/n5dug19r36/fr/P3t5v389e3PyLq50tvU1ODZ3NrX1NPP1sC6v63IvrK0uK62sLClr6urocLf3/y8xsrIysjMz9zP08/N29vW0crGv8XU4IDCv7fKwt/f0cfPx7/S3dXb0crAxLy9xMfMxNvj3tza9OXZ4OHm38bKzOfU8Prt5efc8NXI3uPlvMe/z8rOvNDAx87NwNfW3Nrc3Nnh2NXMxLy+xcDBxNLGx7vIwr/J1NHR18HMy77Jz9LRzcvPz9HV1tPcy9HN19Xgxszk4dnX1YDg8enizsfPyd3b0eT04o+M593s1s7MwtrAvdL08Prwgur1g4SH7enwgY/n4sf/zdrAway4xMW2taCyu7PQxsGOm5icsq25yM7F1NnJ4N7CusHSv8PAy8bh0t/a3bnK0uj668jKwL67t7evsr2rtaWerqmxsebdwL7BvK3MyuDfxoDVytnS1tLO2P7v2s21zMzN0M3bz+Hd4+zRxsTu64P05/HQ28LT1+TIyrmuutPG1bup1cvXw7/UmYiLhv6LloCLg/CCtJaRkYb964j5iP/sgvT04+vYmZKDm4uGhZDxhIr/i/zz7YX//ZDx7P2SkoWCjPSEjqOdoq/RqYuUhPz9gIDh7Y2E6dzqzNPIy9yFgvLf37uy6cy8xerhzMni1/He0c3Izs3Vz9X7hInv59zn18vh29fjzby7scbSzb/SxsS+tr6xwaXFz8/Kz+rY1tjdy8zhysTBucjTxaWop7OqoqvIr7bQ1dvu6MfO0NbW3KClvLGuraSpqq2zzdrR09rn4oCFh6Wfg4dqanWBh4l3YGBpZmNpbmRmbGN4e4OKhIKRc3h3haSBcnp+eJKYioiJiHuGjoKLknx/fomPj5SJloaDg49/cIWKg4eKeXOGiYCOfYaCcX+KmYN8iZFkb3h0dXBjdFlkfnKCiJ2NeXl2e3d4cXmDl5OUkJxVj42MlHiajICboqKRj4SJlXZ6lHt+jlB5hIh/cXJ0bYCQh3+HlVqenI2CinuEkJd+amlub25jhop+nqOXXFROSYx/SlNRUlpTT31meImBhHB1l4KAdn56coB3bJBTUFxaVW+EbaJ1eHZkc2pzdF9cWGZwbXGGmXJ2coJwhpmasZeXXlZdYVpQVIBLTlpoSoBxdHVxemmEinR9gIOPjYBKh0R/fnp4eHeDen+Li5CJj1lXWlhYo5ODiKBWjVhTV6SJYVVTnVCYiZeOiHydnaVUU1ZfnqGljZOXVV1kV2FVrYN8gW5oe2uMo2anenpwhpCLiXiKd26KSn54hneDho2GdIKEhXtgX4CKooCVh4F4gZaNTYOFR3pzdVKIoqeAjHRYbGhpXmxqboxEhYN2am5/fWuFqJOAiXmWk3SFfnRkdXqNeoR6iIp/f5iio56Eempkbnhzc3d1fn58fHh1f3Bsb2B8dmttbWNkX2NcZWZiVXGNiZ5iamxoaWVla35wdHZteXl0aWhnZWx/kIB0dGV1aoF8cmdvdW9+hn2AenJrcmtrb3V0bYCIhH56lIZ8iYuakoCCf46Aj5KCe3Z1jHZ0jJeWcXhyend9boJ4gIOFeI+Chnp5eH2CgIWCfnl8fnZ3eX55enCAfnSAi4N+g3B8gHiChYmMjIqJfn18d3aGeIaIkZGdhISTjoWBfICAjIeHfnmDf4uGdoKOfFNVhn2VjI+RjKKKgY2rpaikWpCXVlNXlZCYV2WXl4Gzh5p/gmx6hIN3dl9vdmyLiYVYaGVpe3Nxe351g4p2kZJ3bnOEdHyBjo2olZ6ZnHaCgYuXkHeCgIWFiY6Ff4h1fG5reW12aot7a2h1b2BucH6EcICAfZKNkI6IhKieko+Jm5ibmY6XkJ+Wn7Sfk5a+tWS5paeIlX+PnKeUmpKEjqKOmXtoj4OKenSFY1dXVZ1bY1BZVZVTfWFbYFaUhVKPVZuNUZeYkJyVcWZUYFNSU12gWl2uXqKenFuysmWalKVkZVhVXJhQWWViZXGFbVpmWKuvWICUm19WlYybfoOAhJljZbqtqId/rI16hJyRfoOWlq2jlJGOkoeOg3+WU1iXk42ckoyim5aojHd8cYCNjIOWjo+JhIt9jXaRmZaNiqGRjpKVgX+Tf4CAfI6elXt+foN+c3qTfH+Rjo6fl3d+fYGGj11idG9ycmhqZF9edXlucXeGg4CChKKihYdnZWx0f3xxW11pcXJ8f3V0eW+DhY6Phn6KZmlmc5V0ZXF0cYmPgX+BgHaBioSOlICDgIySkZaHlYGBg46Ab4GEfH+Dc2yBioKPfYd+anV/j3x9jphreICAhIJzim55kX+Kjp6RdnZzenJyanN8kIuOjZtUjIN9fF+Dd4CEkZiJjIWJk3Byim91hEtwfoZ+c3R1b4SUiH2DjFSWj352gHiEkph9a2tubmteeHxuiY2IVExGQn1zQklHSlFMSXJccX53eGhwkIGBdoKBeH1uYX9IQ01JRlpvX4Ztd3VjcGl0eGJjYnWBf4WdqHx5bnhgcX58kXh4TEZJTUZAQ4A9Qk9dRHhwc3RyfWuKk3yHio6Pl4xPlU2Rj4mAfHmGfISOiIR6gU9LTUpLkod4e49MfU1DRXhfSkI/dTtxZHJwbF19f4VDQUJIdHR7ZWxzQUZOQ0s/hWVeal5bbF55jFeKX2VddoOAf22CdGqKS351f3B5e4SDc4OFintaWHV9lICNgn1yeo2ISIKHSH54d02DmJ54iHhofXp5dH9zdJFFhIZ4bXGEhXKIrZuFhnOPi26EfXFidHyQho+Gk5CCfI2Tko97dWloeIN+f4SChoSAfnl3gHBtcWR+em9xdGpwbnFncG9nWHCIhJxodXx/hYGChJJ/f3hte3p0Z2ZjYWd6hoBxdG2DfJKMf3N5enB8hXt+eHZucm1vcXZ4cYeLhXx4kXxzfHyFgGxuboBzh5CBenl1iGxkeIKEZ3BxfXl+bYBydnl4a353eW5xdHqCfoB4cm5xdXBzf4uFhnyFgnuBjImJkX+FhnqBg4iMiYSDfnp6eXeCcHl3fH6Kc3WGgnhxcoB5h4R/dXB7eIWEeYGHcExLb2Z8dXd7eo52bXiQho2LTHuGTUtLeXJ0RE90cWSWdIl1eGdzeXhqZ1JfZmCAfoBaa2hreW1sdXJqdoBzjYxzbXSDc3V4goCSgIiBfV9qbHqIemJraG1tbnVycn1td2picGNqXoBzYl1ral5xcHqAboB/d4V5eHNoZIJ5bWddc3R7fXqEe4uBhJB3bGyUjE+QgIVlb1xvfYx7gXdueYt0gGRUenJ7aGFxUEdIQnlIUEFIQWU5XkVCSEFzZkJvRHxwQHZ1a3ZrW1VGU0VBPj90QkR7R3hyckR9g093coJTVEZARGs5QEtKTFVVUERORYiMSIB7gFBIenF/aHBrbYFUVZaJg2Vej3BfZ4B1YmN0boJ3bGhob2lxamt/SEx8dW96cGd+eXiMemptZniCfXKDeXl0bXZpeWKBiIR/gZWAf4SJdnyRfnx9doOPiHBzdHx1bnOMbW6EiImck3F6eX1/h1RdcW1ydGpsaGhpfH5vcXaCg/h6AXuVegF7jnoBe5Z6hHuCeod7k3qGe596jHuQegN7enuOeoV7hXoMe3p7e3t6ent7e3p7iXqEe4Z6hnuKegF7jXoBe5l6CHt6ent6enp7j3oBe5R6AXv/ett6gnuPegt7enp7e3t6enp7e/N6AXuZeoR7AXqFewF6hnsIenp7ent6enuFeoh7D3p7e3p7enp6e3p6e3p6eoV7AXqLewd6ent6ent7iHqCe5p6gnvWegICBACA2ufn59/qpaTJ09HVy8Sfp7uytr2imaalqq20u57Jyc22wq/E69bRy7e71NHL1Njk6tfc6ufn1b7IztDDwsjA/dzSw9fO1NHYzMzb3MbJ4eba2+COguntzdfqvqe9u9XMx8K4sb+zwbnS0d3o3YfvzbPExMDAxuj27tTg6N3NysiAt6uG85KM6uvB0O3T0dnU187v2b/Mw7q93unn2fea9439iuDy94LXzLjFwLvCvOHV+eXS5IaFiZb6/fHo7e2j8f3B2+H87umWofbwztjW2OTu49Dni4PlgYidl5GF2cbEuLvDvMm8qKWsqrq61e/I6dzSy8DJq7qtysrmr+CIg46AhpWWmZiJ797U09/bucTT3OjW5vr/i5OSgvr33N3n3+fT8uzZ8OjohoaAgYyEiID88/yF6fHjgoWCierg4Pjq9fmS1ubr9/r3gfX09NjkgPzv+fDx69zKwLrHx9nc5PL00cWB3u3g5ffn8/XqjoSB/+bt19jW2dXu7szAuqatwbuAwL6zvsPU3d7i1N/Q5PKP4+Dy6PW/zsPL3dS6wsPt5/yQ9re738TQ8PX054C9xrXR5O+NiYLZ4Ojz5PPn69rp6PXhyMW4x9TMy9PW2NLRxbvMzMrCpqzCxb+/w8S6vLSrrLGaq8Tl2cXEwdPEvtC8xMrQ4NbT4PP58YOe17zQ5MmAxM3d397XzsnFxMnCzdvn49DYxMzP2OfX6Pnw8Ovb19roxtzX9JCF9/Py5erk9ur16t7u3O3j4bfNu7bKyMHCx8jBttTXxsfT3trg6d7i5tzUxtnDtLnH0svRxtLNyc7Y0dTU1MfBx7W5uKmuudDH9NfdxrvW1tjE0tnu2dTb38qA0+3u483G2+Le1OHa7+vr7IP02tDFxMbT2uDw//Xc69zhztTQ9tL/h/OE7/Tj5sq4trS3yMa9tKaqsb2uwbCsqqSTk6muu77Iwc7I4NbPqq2mysO1tsfP2dXJvbrOzNPp+8fByL7EyMa8r6uvp6ujpaifo6WvtKW2wMG8zNXT08yAzd/bzNjt2efN2MLNs7jJzMzPurva5vju49jd5M/s29ne4dHQ2sTCvcq1rqK5zLayvd/v3OHV1pri9fmclpni//7u54WFkJ+UmomI+4CGgvTf+vn7/f308vWA+42LkJSL/Y2EhISA/ZH9k4Da+e3+8fSIjJrAmIea483e0NbgguaA9uTq4dfV1M715P/c8Pv579nRyfvOxb7SxtjL5ff5hZH74uPPzMvk+ICG9dHZ3vHm39TOxaqts6W3s7mwsLu9prbGzcmm1tjExrjEwMbV383O1s/O1ca7urW3r7G2urXT0t63xM7Q3ejG297Aws/EsrW3t7O/udLI08rS4eTc3OCAipeXnJaeXF2Ag4OIf3lfZXFvdn5mYXJzeXp+hWyNjpJ+gXWJqZKLgnFwhYF9hIeSk4CEjIaBcmFwfYuHkJiJvZ6ThZSKjoiJd3iMjnh2jY6Df4NcToGFcn6VcGFuaHh2cW5mZ3Vuf3eIfYGCdk+Hc2JxdXdxcY6elX2Vm4+BgXqAZVxUjFlWhoxrepiAfX98fW6Ke2hubWtpgYqOgp9qnV2bVYCPllKDf217eXh9e52SsaOMjlRRVFuLjo2JholzjJZccXOUi4djaZaZeX16foKIfGx9VlCET1ZjXlhPemltZ2ltbXxwYGJkYGlngJp3kYmEgXN9Y29heXqTY41dU1qAUF1YXF5Ti4R/gIyJZ26AiZF7iZSST1VRRIOCbXWEhY58lY57iYaKWFdQT1lQU06YjZVTgIR1SlFRWpOLhpuLlJhheYqQnqGbUpSSl4SZWq6dn5qenI6GfHmDgoyNkp2ggnJQgoyAgJiIj5CMW1BQo5CZi5STlo2qqol+f2txh4KAhH1xd3aCiYeAcXhnfJNbh42ek5dmbGZqbm5fZ2ODdopUflRaeWd3kpSTiVBue2R+jI1UTklzen+IeoV+i36RlKeUfHdkcXVtbW5vdG9xbGJycnVxWF10d3Jsb21kY15dYGdXZ3iXi3RuZ3hrZHdmbnV+jYN6gYqMg0lgcmd+kIGAfHuAgn5ybGxqaG5peIGIiX2DcHRyd4N4gZONj4l4eH6LboSBomJZnpuZkpGJlYiPh3yHeY6Kh2d/cWx+e3d6gH14cIuMfXt/h36BhHuGi4KGgI5+bmxwfHJ3cYF6d4KGfX6BiH18gXeAgXp9gpB/n3x9cXKSlZyGjI6fjIWOj36Ae4WIg21uhomGd31vhH+EilSgjZGIi46Xm52isaWMnJGQg4yHoYKmWppYl6CUmH9wbnB1iIWAdGlrcnlugHd1d3dpa359f3h5cHp4kI6NbG1mioR5fYqNlZSFf3+OiIeUm3d2g3yGhYF6cW52dHlwcG9fZWtycmJlaGdfaXR5fXeAeo6RgYadkZmIlYOShYyOkZKPe3ySma2sqqOstqO6q6qjopiYoI2Oi5mLgXCCjXZxeZWijJOIiGOPmqJqZmuTqp2KhlNVX2dbX1JTjUpSU6SQoJ+kqauln5xUnVZbY2Ndql1TVVNMmV2UV02CnZipnZpZW2J+YFFjiXyQhouWXJqApJecjYKCgHiYkKiIpbKtqJSOh7eJgn+RhpSOo7WxZW2tmZ+JiIaXq1pjtJGYm6ymoJWUjnd8hHKAfIV/foWHdIOQl452oKGMi32IhoqVlX+CiYSCjYeAe3Z8d3t6fXmYkp55goSGkZx7j451fI6CcnV4dnN5bnxvfnZ5hI2GgoeAgpCPmJWdXFt6e31/d3FYYXR2f4lzbX19hYCAf2GAe3tmaF9xl4WCemtsg3x4foOMkX6GkY+RhXGBjZWGiIt8rJKFeoqDi4KDb26Ag3ByjI6DgH1YS3l8aXSLbWV3d4mGfnx0c310gHeGgICBd1KNdmZ6fXt1d5Kgk3aFiXpsbWuAV1JSiVhXh4hlb4txcHFtb2WHeGVvbGtshY2NfI9ei1SKUHqLmVSFfG58dm9vbIZ7l4l4hE9LTlN8goJ9fYBxh49XbHGMgHlZW4qPdHt9gYWEcl1pSkJmPERSUExJd2puZmlsbHtxYWdsand2iZ13jH11cWRvWmlfenqRYIVRSEuAQEpJTU9GenV1e4yKa3mKkpmCjJWZUlhYS5GReHyGhIp7l4p1hIKDV01DREpCR0OHgIhLdXdmPz8+QmNdYnZpdnxRYnd7iImDRXp4fGR6Sot6hHt9enNsZmJuanJzeH+AYldEbHtub4R0eHl1TkVEinZ+b3Z2e3eUlXZubFhedHGAeXl1eXmHkIyHeYBxhZVZgIKRipJqeHV6hYdyeXSYhJBTgVddfm99mqGelFZ5gGBxe3pJSUhyfY2WiI6GiHiFhpWGcG9kcnt1d3t7f3p6cWd3dXZ0XWN4fXl1e3pzdXJucHVibXuSiHJxboB6eI57goSIkYmCgYmIfUVbZVpwhXaAd3yHj5CFfnt1cHVteYKLin+Kd3t5foh8g5SRkYt5eXuEZ3Zsg1JKgoSDf4N+joGJfnF5anx2eWB5c3GDf3h6enVvZn9/cG1xe3V7f3V4eW9vaXlvaHKAjIGGfYyHhY2Rh4iGhnp4fG92d25tc4V7nX+CcG6IiIp1f4OSfXR9fnSAdYSHgm5viI+MgIh3iH16eUmFb3JpbHF6fX2EjoJugHZ5cHt1iWaERnVDb3dzfGxlaGxxgn50aFtaYmtkeXFzdXNjYW5qbWtrZHBzi4qKbG9rj4Nyc3yChIFyaml2cHWJl3RxfHN7fHlza2txb3VtbGpYWlxkYVJWX2Nfa3RzdHCAcYaDbnKAb3Vha1hkV2Bqc3Z4Y2V4fIuCenF6gW+Hent5enBzgXR5eYl5dGZ2fWVeZX2LeHxxb05xeX1WU1Z5jYdzaT4/Rk1GT0RFdkBHRYJtfnl+hImGhIFGhEtLT01HfkpBREQ9eE16SD5ofneCcWxCREdfSjxNaF9vaHB8TX+AjX6CeHBua2aHfpZ5jZSQhnBoYo9kX11uY29keIN9SE51Zm5eYGF0iUpSkm96eot/fHNxbl5ka2BvbHNta3FyX2x4f3lejJB9g3aCe3+Ii3N1f3x6iIB5d3R1bHF3enaSjZNufIWEkZd1jYltcoN2a3F4dnl9coN3gHV1f4R9eoDTeoJ7mHoBe5R6BHt6e3uXegl7ent6e3p6enuOeoR7hnoBe4h6gnuLegN7e3qGe6B6iXuPeoR7jnqIewd6enp7enp6hHuHegF7hnoBe4V6AXuTegF7iXqDe596AXuRegF7inoBe4Z6g3vEeoJ7qnqCe+l6AXuWegN7env/eo56B3t6enp7e3uFeoh7BHp7e3uKegJ7eoV7AXqFewV6e3p7e4Z6h3uGegF7n3qCe4h6gnvWegICBACAtbrP1OPgubbP6sbMysTDxfPXydqzsLGhp7/Es662r6+6xby52MOws7nE2rfR5PDi4P3m4PPn38HQ4djHxNDNydDr29LTztfo2d/di9rIudnLx93ix/f04OTv8Mm4xMrQ1sDFtK6vq6vcyNPh+4aB2cXLv8vFxdCE9//h3unZ0cWAwsjh2t3+8eb27evp1cqwubrGtMHOvM7U093y4oGD/f/xgP7g7vnf3NO+tbnbzc/dxs/M94eIiInwgvDpzMzW1/iG5+iSje/04+uB2MTK0ePg39vX3obl6c+Pq4WHhNzNwKrCtbK5vr67vL+8rrzk9e/u4cDE0NDKp77IwNvAyf+A7PSDmIup/fzkwc7ZusjErcPH1enozL3o5+7x8Nfm5tyA9O/i/uLFzNr8hveN/vOKhIWO++Tn+7Xyh+Hu4u7mgIDv0c7V1MbM2dfIzOLq/+PT2/r/jdTO+dvg19fF3NTXxeXv6uzo3tHl+O7m842I+4Dm3dTd/ID65NG+ydmAxvSA17q9vtHK0+bg39Hg+prl28XN0r63ydDuv7q8xNfZ49Xi3Ojk3dzd6IX73Onf8dzi8fyNlfaKg9nd4/TR8v/k3ObSwMjMyeHY3dfW28vLyLnB0MzHwbLDtaSturemtaehqKehmpultbnFub6wrre9tsKvzM7Q1ICFmJn13dzd6u+A4OHe1tXW3tve0t7Uysfd2t/byM/N2dXg4NzCvs3ZzcfG3dDUysuOke3t7Obd6MvIwdLa9+DMyNHXwcLNx8vdyc3LwsPX1M3TyMzZ4d/h4u6E1NfNtcLEvMXMv8DI0M/ezNjIwMG8wcXPtLjCsNbR0NC7z4jb5dDYztrs+uza5tKA3Ofc2czX0NTbz+Xo7IPv4ert4+Xv3t7lyeXs/9XP8+TjxsTI2uDmh4uBydHPztDIsMfAwcTPs7zIw76/ubq4wMuelLKwwbLCuMiwy8fDtKypys+7zMrPytfNs5e6xruxxsK9xbzFyrqwtKqmnK6yqbe8rrSs0Nje+ry7u9bEwsKA1MvL3uPR3N7JxbnWq73Eo7vDxtLO1uDv3NTb5+7z7vOBguPohcfCrr6/xtO1wsPCs7HT0cvXy8/h8YSJifzz+Nzd6IqJjfPv7vSEgv79jvv1+omdjoCC/oP8hfWX/4OFhoqCiJCRlJORhvbc4e/h8Prg/oOJ+YGL4+rV3ej65NOAi/3vg/aC2e/61Pvv6/z4goLixsjWj9LB29PM+OPthZz1usjiw93n/JjtzvLi3tjf2dfUxb6ym6+rtLzFyNC3xcPd38PGucHZrr3Bw9Lf39bQ1dLQ08rMvr7AvcLCytPC1ujt2MrY1c3SydrY2tTszLKgr6itqLy9uMrQ3Ovq2cuAb3eGipmRa2iClnd6fHR4eYSKfI9ucXRscYiJgX6FgH+IkIaEoIp5e4CGknCCjpWKh6GMgpKCfWR1i4mAfZKTipOtloyJhoWPfoqPYY+BdIyBdYGDgpOWio+coIFwdXqChXJ4cHBwcW2Te3+EnVJRgnR4cIBzbXZVnKCKhY+CgHOAcHqPhoqonJSkmZ6fjYNvdG54aXZ7a3J2bHWHelBSpqukV66UoKeWlo19d3qYjIyWgYh+l1FMS0t3TIiKcXR+e5dWjYNZUoOGfIBPc2Npc4B+gIJ8flODg25XcU9TUX57dWB0a2Rpb3Vta2xnVmJ+jI+UjnV7h4Z9XXR7dYtzdJ2AgoRFVktojZWBaHR+YWtnVGRodH9+ZVh+hIqRkn2LioFQl4h9lX5mc3mUVZVbmo5VS01UiXl/loOXWJOflZ2TU1KSd3R9gXBygH1vcoSQp5KHjqatYIWDpo6Pi4d4h3x8bomMiIqCd21/j4mHkFhWnE6LiIGOqVeslYN3goZTfJuAg25wcH53eoJ5dGhyjF+MhniBh3BjanN+XldeYG1wdGt6dnmDfIB+g1KSfIh+jn2DjpJUW4ZRTHB5iZ59oq+XkqCLc3JvaXdzdW5wc2dvc2dte3p0bmJ3bmBmdXBea2BdYWNiW1ljbm96a25iX2ZraHNlhYiFg1NRZGaNfIeFlJ6AkYqGe3Vye3p8dIJ3dnOGhId+cX15hYGIgX1paXuNfnd3i319dXBbX5GRmJWMk3lya3uAm4h6dH+EbWx4cXeLfH5+d3mJhYCAdnmHhYOLi5JXh4aAbXFsbXZ6cHR3fHyHdIZ/fIB/gISPfoePe5iJhX5rf1qSoYuTgo2bopSCkoOAho6DfniFgYKFd391dkh7domQjJyolJKXfJmcrYp+oZiZf4KLmZiYX11Vd4SDgoR+aX14e3iBZGxvb2twbn1/iph0aYODjHR6cn1mfX5+cmtrjZJ9joiOi5WNeGV+iXdqcnBobmlxd29yeHJzanR1c356cHVrh5GWo3JqZ3tvcnCAg4GCkpaGk46ChXydgZWVfYyPjJiQjpyvp5mos7m1s7BbXKKnYY6HeIWCg5BweHh6a2yIi4OLfH6MlVVVU5SMlHpyelBQU4iJjo5KSZGWWJeUm1loWU1MllGgU5pkolBUVE5GSlFPVVNVTZKAg5iPpKeYtFpco1ZcjJKEjZWlkX2AWZ+TUZJRfZKbfaCRkqSjWFmZg4aMWpSJopqZwKatZnW0hZOpjaSmtW6nh6aUlJGcmpaWjYd5ZXdxeYKLj5N9kI2nqI2Qh4+mgouHg5CZlpCLj4uIjYKIhIWAeX18gIl7jZ6gj4GOh3+BeYqKkpKnhnRqeXh9dX56eH+AiJmUhn+AZW2AiZqTbWeAj3N1d3B1eYqVip99fXxwdIOAcm1ya2hzeXFwjnxucXV+jmd4gox/fpyPhpuRjXGClZGAeoiHfIOaiIGEf4GMdX6BXIJxZYJ4bHp/e4WHe3+PmIFzfoeSlYCDd3Byb2yRfnyDl1BRgXmEfIqAeH1WmZh/d350dGuAZ2+FfYCZiXyNf4CDdWxeaWt1aHd9anV6cHmJeEtPl5aLTJh/jZiFhn5wamuFeHV/anVyk09LR0ZvRn9/amt0co5Qh4BWT4GGeYFPdGVpb3l3dHBoaUVqaldKYUNKSXV4c190bWZqbnhzdnp4ZWyEi4OBel5of4N9YnmBd4ttbJGAdW48Sj9XeYN2ZneDa3l4ZHZ7iJKQcmSGiY6QjXWBgHpNlouCmYJsdHWLTYNQhn1JQ0dOhHFzhWR3RGh0bHJvRkh/aWlucmVpcnJmanqHmYJ1f5GPTGhmiHR9c21hdGpqXXmBfoB4bmh6hH14fU1JhUJzcm96mVCchnFja25EZoaAd2hub3x3fYeAfHF8lmCJfnB7g3FpdXuGb2hrbXl6gXeAe36KhYSDi1acg5GFk4CBhYJJUXtKR3J4hJR5lJ6GgIdzYWlra4B+hIB/hnp+fG90f3x7dmx9d2pygH5sfHNwc3RvZmBmb256bnZvcnuAfIV0j5CJf01LWVp+bnV2hI2AgYWGhoiGi4mHe4h5cm+EhY6KeoB6h3+GgYBqa3qMe3R0iHp3bGRSVIB/iIWAiXFqZHR3j3trbHqAcXJ7cniJdnh6dHeIgnl3bW96dnFyb3VGa3F0Z3Z6d36DeHqBhoaRf4+EfX57foCGcXZ7Z4d+fXlqfVeMl4KHdHyIj4BygXaAfYh/fniGgoaIeoZ7ekl4bnl8dYGLfXuBaICCjW1jhn+Aam1zendzR0VAWWZscHRzYHRucXJ6X2VtbmtvbXV5gY5lWG9rdmduZnRlfX19b2dpio54hH6Be4J8Z1JugHhxfXducmpxd2xtdW5tZHB0b3d0ZWNWb3h6iGNfYHNhZGaAenl1gYNudm9eYFZ2WGxtVm1ycn53dn2Keml1f4SCf4BERXZ/T3R1anp5fotpbmpnWll0eHF8b3SAik5PTYmCiHFpbkpKTHR2eXtCQIKITYF4ekVURT0/gkeLSIRYiURHRUM4PENCQ0VIP3NfXm5jc3lshkNHf0JJb3ZocX2NdmSAS4V4RXtEY3uHZYuAfY2LTU2DbG5xSnRleW9ph3F6R1eBVF90W3R7i1iBZoFzc3B1d3R3cXJpWG5qcniAf4Fod3CJjHN8eYaieIF+doGKiH96fn5+hHqBe3x7eoGCh499jJiZiX6PiH2AdoF+gYOXfW1lcHJ2bnd4cHV0eouHeXPLegF7iHoBe5h6gnuIegF7pHoGe3t6enp7knqEewJ6e4d6BXt6ent7hHoBe4p6BHt6enqFe6R6hHuaegF7iXoFe3p7enqEe4R6A3t6e4V6gnuTegF7mHoEe3t6e4V6AXuGegF7j3oBe5p6AXuJegV7e3p7e796hHuseoJ7qHoBe6J6AXuZegF7mXqDe/Z6BXt7enp7lXqDe4Z6g3uEegh7e3p6e3p6eoV7B3p7ent6e3qMe4l6BXt7ent7iHoGe3p6e3p7iXqCe4R6AXuIeoJ7iHoBe9d6AgIEAIDQ2uXh7dHCwtLYzcW3x9Pb0r3Iy8W2uLmxxrS6scHKsLrIrty0oaixyczW1N3s9fKB7u703+TVy9Tcxry1u8PTy8qE19zu8fL49Ybt8vLMvL/H7+T41e7o7/La4dXLv8XW2tHQzsTL4szS3NXZ8PLw9cnV3ePQ2/Ds69Gz247c4YDe49HT2/7w9ISGj97s49W0rbbSttnO092T9PKF/PT23MrS0s/Yy7nO0sPdydbaz9na3enx8eD4hIqAi9zMwcrX6//r+73SwtjTyLfO1+71+Ozi+uvbvbasp9HZ0oSTiYiT+MO5zLKvu82+sLWtpqat6+zn7efwwt+Ru8e4trTE2YDjw73f8+fgycDd3vDK2cHKvdLi5fHq6ev+g4iNgYr67eKC8OXb2cyyssrI0+7o8ubs4tz4/NDv2NP38Nna2YPX3eDStcbL0Lzf1eXYt7a7xs25zNXRwtbq2f6A7tze2/DU5ujqifDh39PV3OPc8NjH4efU5cnqybzIxs7Q1+XHtYCqu6q/5d7m5dji2+7g/p7c08/QtKvAyIHRzr68usvi2uLu697X5I7a7PT44PHw1/z57vHq+/rp+YiO9fSJsbGurb+wvMfZ1s7KyszgzdbS1MzTzLi3wcrGqrC/s6q3qaeyr5mywNrpqLOyv7ixurW0vLHH1OiG85yH2djb3enm4IDHxdDPzMzM1uTW0dzU3eXm3MvE0+Tx8/H03enJ2dbl28DZ2bnN4NDt6oXz+NrYzdrUz9Pc4tjawMvWx7bCw7+/1tnIw7zEwsXOz8/X1YDg3dLU18C3wL/Dwr3GvM3QzM7E0s/RyMbJxM3Fy8zS1czD0Mvk09LM3fPx69iB5en0xoDEy+3cz9DQ0dvV39rG6+j26+Lz5d3g4d7ThfHr5YHy8frx09PS4+OLqIrs1tvIzM7Au77G2tjd09HIyLOtq7Gnu63CrLTJs7eus7W3v7e1sLa9zMWt0r29yMa5rbDO1t7MyNDJr7jMxaWqnqOpr7mwp6+7s6/U2un/htX44tbV3YDV3sXXysD2+err17/XysLM28vb2d7w3sHv26/ZzuLa+uvV6fXuh/vo2rvU29fSzbKmysyC4+rI0NiMmfLx9fje+PPp8fL43efw3uH07ob+54abnbKJhISRhP77gYKGk4uYkaGGypmOlpWHjJiKkf6G9O3t7urz94H54f378efczYCJ7dbU1ZeGjJLq04jW8c7T5srBvLi+t8rL1ffb09/Y9OjDvNHKz/Dk8evY9+Tw0+TU1eHFx+Wpsr6/wsPFzb/W0sjGyM7fsK2gvsLHydPm1tfb28e7wsS4rbfGwr+4t7Wqsc3FzcrRytbBztjR8//g6ciur7bDxtLDxsnU0+ri64B9houJnH1vboKCdW5jcYKNg3WAh4J4foB8jIGHfouPeoCLdZx7aG1ygHx8dXqHjI1Qi4uNfHxva3WFd3RueH+Rh4VaiYmWm6WsplydnqKGdnB5opehhZqQjJCDkol+d32HiYSJioCJmoR9gHR4hIiIlGt7iYl3gpeRjnlff1mChoCFiXt9h6iYolteZpipn5J1a2+HboyBfYZhjYlRnJWahniCh4eRhXaKjH2VhY+RiIyOlJOOhnR+REdFUHRsbXWDlaeUnmR3a3JsZ15weZCXmoyLo5qGcW1gW3p8akpWUFVinnlygWZlb3VoX2RdXlxgm5+XnZemdopMb3NsZmNve4B/Yll1ioZ/bmuFgpJyeWdrXWt3cn55eHiRSEpPREqLg31PjoZ2dG1eXXBvdouDj4KHf3eNjm2QiJC3rpWQjVmHhYN4X3J4emOGg5CEbm5we35te4eFdomWgp5SjX6Af496h4KEVYp7e3R2g4yEkX5uhI9/kXmZfneDf4eLiZmAcoBhcGJyjIWOiXB7cX1ti0xzcnd9amBpbUVvbmZjXWd4bXeEioWBjVp8ioqIc4aIcJeRhYiAi4d6i09VkpNcaGhla3ppbnaEem5nZWd2aXJucW1ycGJhbXh5Y2d3b2NtYF5iYVVod4+cXmhjbmlibmxxe3GHkqJcnGpWe3eAipaOj4B2cnx2cnd/iJKEfoZ5iJSVj352e4SNkpCQgIZufHh+gXKBg2VyfGuDg1Gan4WFeoF7d3mCh39/aXJ7bmRweHd5j5CAeW93eHJ/gIJ/fUyIgn2HiHVwdnN6e3N7dIGBfHtyhIeKhHyAgYOBhomKkYJ5eXCLg4WBlqefk4JUjZSmfYB0cJKDeIGAf4aChXlpjoqblY2im5GTjoZ3VpOMi1KVl52ZgIaDkZhgemCaho9/h4x/eXh+ioKEfnl1f3JveYF3h3+Re3+ReXpvdXZ2fnd7dXd/jIZymISLkZWBcmyHiop0dn10X2h8eGVxaXJ0dXpyanF/fXiWlqGjVoWhhn19f4B4hXJ7cnCYm5WVioCelYuUmo2ZkpOekYKupYKlmaWfr5+MnqunYLSjmH6QlpSKg25khophnJd2dXhXZIeEi4h4j4qIkZKTe4CFeXuOiE+WhVJjZG5OS0tUTZSTUldWXVJYTVdEeFhSWlVKUFxTXp9Xm5mbnpWfoFOYgZ+ZlJCOf4BgnYmChGNYW2CQflh+lHmBlYJ+fnqAgJOUn8upm6adtKiKhZqSlrWnp6OQpZSfhZaHiJ1/h51qdHt6gYWHinyUkZSVm6O1hYF2i4uMkZmmk5OVk4R9iYqCdXJ9eHVtb29ma4iAhYGIhIx6hZKNrrWcpox2dnqCfoZ9enZ8fpGIkIB1f4qPnoJxcYKAdm5fbXuGgXaCioh9gH55hXJzanN2X2ZyYolwYmlxg3+CdneAhYNLhoqPgoZ9eoaTg3pwc3eGfXlVgoGPk5aclVKNk5d6bmxxnI6ZdoyHhIx/j4eAeoGMkouKioGHmYF9f3Z/kJWSoHuNlJWCh5mPim5QblB5gIB+gnV1fJeDgklLUXeLiIFtZ2+IbYh7eIFejYlRlZKSeWlxdXR/dmd6fXGHdXyAcXZ7gYWLhHV9RERETnRqZGdyhpeHlF1xandwbWFwd4yRkYB3jIV3XVlSS2ZoWkBJRUtYjWxreF9gbHVqZWtiXl5diIqCgoeVd49ZfIB0bmlyfoCBYFd1iYV/cGh/gY1xfnF5cYKQjJaOiISVSkpOQEZ+enRLjol/g4BpYHBwc4V+h4CJgniKi2iEc3CNhnZ6dU13dnRvWGhscl19doh/aGtueXttdXpxYnB7aodGdmtvbn5qeXl8UIBxc2pqdXtygGtdcXhrfGeIb2hxbHNzcH9pXoBWaF1tiIGKh3SCf5CCoFl+enyBbGNtc0B/f3VwanWHe3+KjYiEjlmCkZSRfoyKb4+Ec3R0gH51hktPhohPXF9dXmpcZGx7d3Fvb3GEeIF8gXt/e25tdn+DcHSEfHB6b3B0cF9xfZGcX2pndXVyfXqBin6Nk5pUh11KaGhweImGhoBzdYSHipCRl5qGgYt9iJOUjoJ5f4eRkIuNgodxgX1/fm5+f2NxeWp+gU6QlH18b3lvbG54fnh+bHaDeW12e3p1iox/eHJ4dW99fn13cENxbGdxdmtqeHh9fHR4cYCAfYB1h4iJg3t+fH56enp+h313fniTiYl/jZuUhXJLe4KUcIBucJaHeoGEhIiEhXlkhX2GfnWFf3Z7dW5jS31zcEN/g5iLdHhzfnhJXkt1andrc3xwampxf3p7dnFveGtnbXVsfHKAbXGEcnRpb3Jxend3cXZ+ioNsjnh+iId0Z2eDi498fYV7Y2l7d2RxanB1dnxvZmpxaV95doCJTHOOeWxzd4BzgWpxZl+Cgnl4aV94bGNxe29+eXuFdV6GdlN2Z3Vug3VkeYiGU6CVinKFjod7clpQbG9RgoBkZ2pQXIF9hYRzi4h/hYOEbXl/c3OFf0mGc0ZSUlo/PkFMRIWARkhHTUNLQks3Y0ZBSEQ5PkY9RnJAcHJ0eHWBg0aAbouHg3hwY4BQfmtnZ1NITE5wZUxthGx0h3NsamVpZHVzfJp8bndqhHhaV2xlaIZ2eXZnenB+aHNtc4RueZJib3Z0d3l5eGl6c29zd4WZbW1ieHd2eoORgYSKjHxyfX95bXOEg4F6enVpbYp/hH+FgY11e4R7mqCOm4JucXR8eoFzb2xwdYZ/ibB6AXuRegF7h3oBe7J6AXuKeoN7jXoEe3p6e5t6hHuheoV7uHqFewR6enp7nHoBe5t6AXuJegF7qXoBe4h6AXuOegF7kXoFe3t6enu4egR7ent7sHoBe6N6AXutegF7nXoFe3p6enuJeoN7z3oBe6t6AXuNegF7hXqCe5J6A3t6eol7gnqTewJ6e4d6AXuIegF7hHqEewN6env0egICBACA3Oru8P3i2MK70cW9w9TFv8DNxL69uciqsLu3vNLS4erbysnu683GycnS2u7Z4+nt6eDi3eXe39LV3u/DysrDzNHb7N3q2+Tg5Nu/19/j38W+wszH18/i0da9zPXnzePJ0NXo2cPRx622zszd4dzZ2tXHwcjIxsjW3N3nz9iVgdyA0cvS28319uz5gITuzNrGvcG7p7XS5oqC6dri7/Dt/tzY4eTc2uf29dTG1tvIwtC8zNbV+uL1193p+obm6+Ha7/rW8ujY8Mzs3+LL08ztzObVzfDU5+fIpcbX6tfmhPSN/PzIvLi7uNKB9NLL1eXCxLi3ysy8vKakpLy0pJitys+A2M7IxdTJ9fLx1d2I8eDS3d3Y59jl0Njt/Ovn8ISEgISB9u7+6M+/vLG+0MfNyrvK9vP+197I2NzW2um079fTv8PN5N7/+OXd9+jcytDl1dG7t7C4vdDVsMzY1t/s3ODWxPPe2b+5zdDA0NHV3eLVxcvEycG6x9PQx97e9/+W54uAk+TN1en7/f3s7eDl5P3X28/Hv7a80OTR8uqI3cbNysXa5PXt5cXMzt7R4NPZ7dXl6YCImIOM4vyE5NGJ9birvrW0vOzh6ufHy8TI2LbBx9C3utHSvb25s7O4uq3BvK+nyqmxtZOst7e4tMOturOrsr6sz8DZ4dHp3M/U19XP09OA1cq+ycDc0crO1tXf7t3ugdPW487i5e73//ni39bTzsa529vI6fPy5uLo4uzn48/T2drY1tnNy8THzcG+urrBscjIusnQw7PKvcXAvcje4PHe7se7xMXS0trNx8bO0cjCwtnU1cfN4s7GyeDM0czUzMnK0cC+zsTFztfQ2/TX5MyA0Pfs8MjX09DM2t727If8+fL3+92FgPX32IDu5+bf5uTf1b67rNbX4aOX6tXFycTIzczE3fP+2eLEucC8qbq1ucDFwq64ua2nurykr7/FzNDN2drnyNHN1cvP1brJz9bOxb66zLC8vsC4q6SgqqCtzK3FzaWdm6Szw83V9/OV8M6A3dzSzODV6NXc9YiI3fTg++zv7+/P4Ma90NHg9eCC1NH4+4by3fmM8Mi0usj23NjCtsvLp7jK+bOIjYD70Nfg8fSB9fD+2eHq9dzg/4qG8/j0jYKj9oaDhYbwh+/lgIWHl6WHl6H2jYWH94uJ9PXom+3e3pXwhIKFg4Ly3djW4PWAgob004GaqfTt2Mrb7NLKs9a6yKuep7fL6MnJy6+C3crb49/k2uHN44D49fWQgPXmxc3a5uvT47S8wcG91djHzc68x8PH09jVx8jj08rawdLM2IHNvsXSy83ItcPXzrvKrsXI6d/t3N3c1cvQ09nLubu5ra3JvczL5OfX+N3e9OeAiZWUl6yJfWhnd2phbn1xb3GIfn2Cg493e4KAhJSSnKWSg4OnpYaBgn1/gJV+iY+YkYSGgYV6enFygphvc3l1fIOMopKbkJybnp2Imp6fnIN8fH1zgIOWhYl4gaSahZyFjJGhloCHemlwfnyKjYOEiIZ2dX15dnWDhYWJcHZhUIeAeXyEi36knpOaUFSTfox9eH54anqRpGRemoqJkY+Hm36Di5ePjJafmn54hY2FgIx6iY2Np4WTc3d/kVWEkI2GlJx/mIh5lnSLfYl8gHmegJR9fJl+kJx9XHB9g214SINRlJR2b2ttbYFPmn1+hpRycmprf4l5e2hiYHhtXVJec3WAdWlfXGhjiIeHdXtVjnptc3BudWVzYWp/kHx3fkhERERFiZCShHBmZFtofnV4dGVwmpqhgIV2h5CSl6h0po6JcW52i4OeloN8m4h4aHKIentvZ2BscYSNbYaJg4KMeX92apeEf21nd3xteX6ChoiAc3Zzf3lygZGMhJKRpKppmmCAZpmChJKgqZ2Kg3RycINodnd3dnN4go1xh4VRfmtxamh2gIuPhnZ5dYBzem55iG96gUhPVktSfZNRhHtcn3BodnFycJGFjopwdHBxf2d2eHxlXnBtW19hYWRqcGZxcGFZdlxla09jaWxta3lmcWtlbXZig3WHj36PfHFzeXt4en+Ahnpyem+Kh4GDiX5/h3iPVHyGk3WGgoeLj4+BgHt9enRkgH5yjpKRhYCJipGNhnZ6gYaEgIJ4cnNyeGtsaGlwY3Z2bHV9dm5/eHx5bniIh4+Cl3VvfH6KjpCCe32CgHdubIiFhnt/iIB/h52WlIiLgHt5g3d5hn9+g4mDjaWLmYOAgqKTlHeIgH58h4CMf1CIkJqoro1fV5uYdk+TjZSTnJmXlYB9bZKTn3txqJmEiYOHgHtsfYuVeIh1bXl9bIB9hIWNi3p7fHVwfXtqbnuAf4V/ioiZfIyOnZSZnYCKk5OFfXBteWFqaW1vaGZlbWNxjnSJkXFpZGl0gISAmpRglHGAfnlxaXt5hnZ5klZYjKWatq2wsKyLmoKCl5urvqFilomprF+omLZppYNtbnanh4RzbYB/ZnSCmmtSW1OfcnN2g4NIiImYfISNkHt9lFVTlJiNVE1jiE9MTVCLVpGJTU5MUFhDUl2FVlRUlFRVkZiNaZSMjWefWVhXU1SWgYB/jJmAVleYelJncqWdi4GRmoR8aohyhnBhaX2ZtJmjqIhutZmhrKmnmKOIl1ajmpFaT5eOfIOTm6GKmXZ/gXx4iol6ho2FkpGSm5uWh4ylkYiRgIuDk1yGfYiSh4F8a3WEd215ZXZ+nZanlpmSjoqPkpiOfHx8dneLfId/jo56koGEmI2AeISIi5+DemhqfXVrcoFybW+EenqBgI5yd3p0dIJ8hYt3a22Rln9+goKEh5Z+hImPiX6GgIaAhXqAkKd9goF4e3+IloWNgIuKiodyhIuOjHx3en92fnyOfn5wfqWZgpmFi5Khkn2Lf253hXyFiYWEhYZ7eX99eXR/gH+BYWRTSHaAamtzeGmJgW51PUJ1ZnpydX14aXWEkFVVjIKIlJKPnn18hYmBf4mXk3ZygYh2cXZgcXh8m3yIZ213jlKCh4Z+j5R1jn5vim+Ie4Z5f3aVdIZvZ4JqfYBlS2NyfGp0RHxLiIZoZF5eXXBDhW5wdYJnZl1fc3hub2VlaIF6aV1neHmAd2leWmNfh4SDcXhSi313gIKCkYSTfX+Pln1xcj87OTw9gYuUjn5zbmZzh3d7eGxzmpiff4Nvdnp3fY5fjnt8aWpwhXyPint3k4V8b3eOg4NzcGZraHd6V2tzbW59cHJrX419eGVfbnJjbHFxdHhvY2pmb2pkcYF8cn14hYZRc0aAUH1tdIGRnJeGhH6AgZV2fn57eXJ1fot2kIpUhm53enaFkqCaj3x9fId6g3d8iGpzcT5DTEVNeY1Le3BRhV9baGVmZYh+iYhxeHV4inF8f4RvaYB/b3FxdHqBh3uJh3dxkXF5f2B0eXh2cn9wfnlzfYZzkXyKineGeW5zdnl6gIOAjomAi4Wfk4iIioWNlIWXU4CGlXiLioqKj5J+enh7dnZogH1ziYuJgH+JjJKRind5eXl1cXRtbXJ0f3R1cHF5an1+dX2Fem17d3l1aG95eH5yhWdmdHiDg4V3cXV+gHp0cY2Jin6DjYF6fY6DhIGIgYB/h3p8hXt7gYN5fpJ2hXKAdJOOlnyPiYaAiYKPfU2AgoWQlnhRS4J9XkN9dXl6hYWGhXFvYoB9f2NZg3Vob250cm5fdYaTcoJuZ3F1ZXh0eX+Hg3R6fHVuenllaneAf4J8ioaUdX14hn6AhWx5h42GgXZxf2t1dHl5bWhkbWRxjW+DhF5VTk9bZGZmg39TgmeAe3tvZ3l0fGhpfklKboByi4GChIRqe2ZhcW13i2xFX1p6gEZ/c5ZZknZlZnOefnhnYnNyWGZvglpFS0aHZWpxgYRIh4ONcHqFh3Z6k1RTk5SETUNQb0NDREZ0S3l1Q0RBRU45SFJqSUVFdUVDbHJnTnRvb1eESkhKSEmFc3Brd4OASUVyWD9MWH93Z2Z7hXVzZIJse2RQVWN7jnBydllPfWhvdHNzZG9aaT97eXJNQ4N4Z29+h457h2t1eXFsfHpqcXNkcm9yfH58cXePfXaFcoN4hVeAc3+Jg3+Ac3+OgHN/ZnV5l46ejo+Mgnl8gYaAdXl6cneMfYV7h4Vuh3V4jX79eoJ7inqCe4t6gnuiegF7onoDe3p7iHoBe6J6AXuQeoV73HoEe3p7e5l6AXuWeoV7Bnp6e3p6e9N6AXv9egF7hnoGe3t6enp7jnqCe9N6AXuMeoJ7kXoBe4R6BXt6enp7kHqEe4Z6AXuKegl7e3p6ent7e3qEewR6e3p6iHsQent7e3p7e3p6ent6enp7eoV7hnoHe3t6ent7e5Z6AXuKegZ7enp6e3ukegF7rXoCAgQAgOnh0d+B7NP82dzGx77K3+PH0cLPwrSzraakrb7d2+3zzbe2tsLex8Da6u3r6+bg4/Hp6ePx7OHV2ubiwcS1wcXGxNPh6eLe59DDx8fUzdzi4/zN0s3VvsDGwLjQ5OTu9dnridW11NbPy9bSybXZ5OOU0cu2wsepo67L3MjXhunTgNfCvM7p54Dx4PX5g8/ov+LMo6ejrrO/hIiA9u/u8f7z39ne1r66tsHFxNHm187Lxsiv0dTVyNTf5+yD3uvo4/Xd+uTM2sPX4+v38uLDz8K6xrHJz/T/6dyHjOTDzM/C0MvX0cXc9Japrob6gYKC6eTKt8jIpKfMsqvOv7jJ48ragNLd6OTj18/I3+2Lm6GWg+fg5Jjb+I6A6+uF/4qIiYTp5+Hp6N2/tsW60Mm9xLapr8Pi29jT39DM+evQu8yzs7u9y+/hzdPP2unW0N/DyMbKxcLBztDNxcmA7eyB9NSA3NbXzL/X2rC+ztbm1MC/sp692eXMwLbQ5tXX1PuE8PHvgPX62NDe4trr5+PS1+385tvT3NrY6PLa1diyhISChsvF4urz7eLU1N6/wNbZ1uvWmfb/k4uWiPbh7Yze8tznt8XTzeD+iuHPzbfCt76uqsTHrK3HycjGz8bEsamqtaufnaCaq9G8tM63qLS5vsi/qLurtfbW0qvD+e/YzM7Nur/WgMTOyL/W5cvKx9bbwL/H0cvXuM6+yurI1dnw7+nhx9fd1NjYxM2Eiunh9OLk4+PV2NTY5s/KzNG/xs7CuMTGzcXFy7/HyMq2xcHFvru7xNHT4eDJwsy+xrzGvMm91c7Jzs7Tz8vLz8jFzsLNysLR39PN083Fx9HLwdPR1rrm+ODggNLkz9jGzdPJzOXohubx8fvw7fv26eDm+8Hj1tXy4tzXyNfGyKzL1eP2hN3cxsG/z83Lx9Ta9OGxvLK41MHS2sfXxsW6w6KbubC9r7DFycnJ2MzT1L64rL3Uu7i3t7m9xM6/vbm6rqytpaulqK6px+7W7ePjwr/D4drc4tn5gfPygOne08W+3ujqy9fRyeTm3+bu49LZhvvU6YjMjo774u7xg/KAkeiE/+bKwtPGz9Xx4t3Y7tG3xMvKxr7ggYKQhoPf6dDi49HM24L0/IaB6u3u8/Xs7fv8h4X9hdfs0O//g5enoaKf6eLf5ueag4Hw7tbi2uTb9+Ln79Hd4ejXgtySgI+che37u5Py6eni6tb/0cLQ1c/5zMrd1v/9y+Ds+/WH6uPph4eD4tzM3+L0gfTV397TyuHLtLDH587LwsvX1b/X3t/c3uHl4NzY3NrS1dbKyM32+OLYuLzDy97Ixr23u8fCvLvK6d7E5OLcycXAqaLIxKasuM3Oy93bjYvi+/LegJKKdYRRiXKTdnhlY2RtgIx0inyQhHd3cGlnbXubnKezjH98gIechHuIj46KhYB8fYh+gX6JgXx4e4uMcHJufYSGgZGbn5uYopGMjIyXmaGemqh+gX+JeHF1cGp6kpKfpZCjYpByh4R/eX52dGJ6h4xkgX1udXpkYGmEjnd9VIp1gHVjYHOPjVKXgZCXT3aOcY5+YmhocHR7XlxRk4aGiZqVioaPhG5qaGxycH+Yjo6NjYpxhoWFfImPkY5Ug5eVl6GQo5V8hnF9h5KnqpZ/jH5wfmt8gJynjoBWXohmbXNtfX6MjX2MmmNta1KZUlZXmpN9bHl+YGJ/amF9bWZsemVwgGVvd3d4cGpnfoZXaGxgUId4dlhheEtAcXhKik9NTUh1d3WCf3ZhW2hjeXZrdWlbZHmVjYmGkImJsKiSf45vbHFvfJqQgYV5g5aCd4Nwcm1zdXd0hoeCdHpTkItOjW5IcHB4c2qFhmNwfoiZhXRwZVdogox6bGaBlYaFhqZcqrC2gLjAl4SNj4aQj4Z0c4aRgHt6g4SFkZh6dXdyUE9RVHFwho2Ujn93eIBhZnZ2doRwYY2QWFBbT4d7kV6Po5eheYeYjJ20Yo14c2RrZGlhZHp5Y2V2dW9sc2xsYV9hbWVaV1hQXX1nXnFgVl1ianRsXnBiaaiOhmV2oZF8dXl9cHiKgHZ5cGZ5hnV7eIuQb2dze3uGb39vdY5pcnSHiYOCanqCe31+bXJTVox/koaGioZ3enl8hXpxc3hwdX5vaXF1e3Jwdm93d3pufn+Fgnp6gYiGlZR9doZ1enJ8cHhwhXt4fH6HgYGDh4SJlIuSiIOMk4qGf3x3foWHfIaDi3WXqJKUgIGTfIJ3g4h/g5OFT3yAgZCLip6hnpaVrICejYigmZWOhZaEhW+KkZ6wYJiXh4F9ioR8d4OJo5Rue250jHmGiYCMgIaFh2xqhXqHeXeFg32AiYKLkoGCeYqok5KNhYKDgoZ1cGtpYmJlY21qcXVrfZ2Kmpachn5/k5CMkIOaUJSOgIp+dmloh5OSdoF5b4iLipSho5OXXKqBlFqCamewo6mlW6FWY51fuqGPhpSAh3+TioiCjIBreIGAd3GGS0pSRkd0inqNgnl4iFWXnVRQi42QlZiNjZeUT1CeVoGZfpacTVldW15dgIWIi49lU0+XmIaOiJCHnIKMknqBgod7UIBfgGBqWJekhGahmZeOloOhfXF6hIKqg4GUkrm2mq+1w8BmqKKnYmJkppmFl5WdUpiBiI+Mi6GPfHCFn4B6dHeBgXSIk5aZm52clpOOi4WBiYp9e4OrrJ6Sfn6Bf4p8enR1eYR/gH+Kn5Z7kpSPhIaGd3CQjnV1e4SEeYF+VFN4kZGEgIh/bnpNgm2Td3ptaWZvg4hzg3WHfnJ0bGZiZ3OOi5efd2tqcYGbhn6QlZaTi4KAfoh/hIKNiIeBhpqdfn5ydnl4c36IjoiFkIJ+fHiEgpKUj6OBhICMeXV2dXKCmZWgqI+gXoRqhYWFg4qDfWyEi4pjgX5sdntdV2B6gWZsSn9wgHBfXWl+e0R0X3B3Q2OAaIuBZWprbnJ1WVpSmJGRlqKYhnyBfGhsbHR3dX6Of3p1bnBad3l5cn2Dh4ZPeoiLg457kox7hXB9hZCio41xfW9jcVtuc46Vf3hQVn9jampkc295dWt0gFVdVkN+Q0dJg4FtYnJ1XGB9aGN/cm54hnB+gHB2fn94cmtoeYFPW2BWSH56f153jVVJfHpFekM/PjphanCEiIBxbXhugXpzgXVqcIagkYF4e21vlo98bn9mZ2tmb42BcHJsdYZ6dYJuc3Fzb29nb21pW2BFd3REf2NEampzcGaAgV9ue4aUgG5sYFJogIl0Z2B6jX13c4xLhYiLgJGeem16f3uIiYF1eZCahn17hISDipJ4dnlcSkZETm9xipaflol/fodranl7eIJuXIKBUUlTR3ptgFF9i36KaHODeYqkWYh6em14cXtwboB/Z2Z5enh3f36DeHh4hnxxb3FldZWBdYZyZG1yfIaEdYZ6gLuWimNvmYp6d3d7cn6RgIOKgneLmIGBfYyWfHWBh4GJb31sdpNvdnmLioaIcoCJgYB9b29QVYl8koqNjox8eXBye2xobHJqdYF1b3l9g3t6gHh+gH9vfX+Df3VydX17h4Z0cIF5fXV8b3RthHt6goOOjIyMj4aFjoSLg3+LlouIg4F9hoqFdIB7gGaKmoOGgHmNfIR5hIuAgI6BS3F0coB4d42UiYF4jV6CcmyFgIB+eI18gGZ/g4mUUX16aWVkcm5mX2x1jX5ebGhuineBhniEeX16gmtqg3aDc217fHZ6hn+HiXVxZ3iQfHd0bnV5fYZ4cnF0bmxxbHJsbnJqfp+ImI+Mb2RkdXNvdmmBQnl6gIB4cGRkgImHa3VvZn57eoKLhHZ3TYxnckZZTE5+b3JzQnU/S3ZNm4p5c4F2gHiLhoR/h3lkbXFwaGF4SEhUSEl1iHaFfnVxfU6MkU1Nio2RmJiIfoN+R0aLS2x8YnqAQU5RUFRXamlpaGpMPTxwcGN1cHl2j3eAj3N4enxpQ2hOgExSQ2xzXUp3b3BygHGVc2pyenWZbWh2cpGIZ3Z4goJJb2tzSElKd21ecnJ8RH1janJzc4p+b2iAm353bW94dWJydHd3eH+AfH15fH13gYV6dn2jp5aMen+EhJCCfnh1eoF7eXiAl4hqhIB7b3F1amqRj3V6gouJfHx2Uk9xiYp6hHoBe916AXuNegF7jHoBe4h6AXuEegF7i3qDe6B6AXudeoJ7jHqEewR6e3t7nHqFewx6enp7enp7e3p6e3qEe7l6B3t6ent6enudegF7nHqFe5F6A3t6eoR7BHp6enuKegF74HqCe+R6AXudegF703oBe5Z6CHt6enp7ent7hHoGe3p7e3p7lXqFe4h6BXt6ent7iXoEe3t6e4V6hnuFeoN7kHoCe3qEewR6ent7mHoHe3p6ent7e4Z6AXvNeoJ7hHoCAgQAgNjvy9jp2sjg7+G6xru30czTycHT89jR07m7ycrE2fOB4MvG0Nni3dDg1eOC/u3z6PDt9+jf39vV39PCs7y4trnJ0tXg2OHz4NjW0cvJ0MvX0vvh2c3B1rqywbzBzNbIyb3k6dDU29Xdz9TKvLGztKez7cbDwLS54tbF6O7qiv2AgMfbz8fe3Y35/oKGif2J6Ofm3uXTwbi2t87i19rV2N/X2ubez7a9rrfN2dTn8Pzv+fDQ2/jZ2MrJwsng4+Lk48PG2vv46snIy9vFtsXI0NiyltC5vcjQuuKN8+jp4OfZ4+P3htbk9Pnu6ePT5e7a0dm5v8y14PSezLS+2PTx39ragODi6+HhusK5tKisw+PvgYGD+OyC6/KJ+oGVg5qUkJL09/Pl3MzX0r7GvLzCuK+YvbLOy9bVzPPi/uvh3eDKwrrUxN7j6uPT19jo3sLE0unL1u2BgoHyiIeC4vn67Ivr4dbVxtTP4Ovx6fnUsKyit6DIybuft6m6xMe8ntXoxMjYgNnKy/SK/uX9ydPZ5tj62dX7hvLz+fLa2de/4tLr2YHAzsjU0cjU+NLQ3ebc6PWCh/aGgIyU9e2EjYiN/vCL0JjzgPv1+uHTxMDKvrXPv9DKwtX16MfKx7q2u8C4tbGkuaOhrcvKtKmup9nFtq7Eu9DN0ujR4MXCyd3W0se+wcjGgLu0urzDwbm719LIxsTA09TNv+v0tc7g3N/u/oLm5Nvn0sjGu7/G0Nfk8/Dc19rZwMvR1eTYz8G7xsq+xsO6x93S2dC9x8bDxba8x8bHzN7U1ebgzMjGw9e+ttHJ2c/UydHL08/HusjNzsazub7W7tDP0OHaxtDS6tHO0sXoz8rOgOXXyMjUxM7S0dzk9dvj7ujt/vXc0uba/f2J2PDs6Pb2ze3RwsvA6v7Qycfdx8rAtMjU0dTE3s3JxNXJzsrF1cHYzLm/uZ+jo7vOtL/RxsDHxN3x8MzFwJe6qMGorrXE1tPQt66yr6mrrZmzssLYzNzgzMTcxLWnvObwgoP1/OLsgOnl0M6zuMm8uM7O2ubVz9zPyMPUweHa+drB45iS+eSA9YiMhYH7+9fYycjx3NG/3e32zru49unV4djkgPyCgfz3/Pna1cG0q5+krtTogN7o74WLh//4hJKGiYDx3/P9kJ6nmoXx5sba3OOAj+f+6O7g2NPCuM/Y29nPycTF/undgPCT9tbf59jZ3sbIvcDz6LfBv8Tz5+C12oWNzb3g3vL68+zb5ffsg/PQwemK8+y+38zFxcXjxq/H4cO5yNjYzs3Sztvgz9fVzurZ3tbS1MnJ2c/T1LjGv+C92c66w72vuLi7r73B6bm6w9O0x8G2sMCvs7Sxu8bNyuL7h4iA8IaAgIWbfIqYg3OHk4NhaGRfdnR9fHGHpJCIiXJ1gH10iJ9VjHx6goqQi36HdYBMj3uAeH55jIqAh4SDjYd7b3l4eXiDiYWKho2djoqPjYqJkoaTjKmPhHtxhm1meG51go6CfXqYm4WFh3uIe3twZmFeYV1unoCBfHJ2mYZwjI6CVJdNgGd8dnSEgVqWkUlNUZhVipKSipeDdW1sb4KShoR7eYR9houLfGRkV15sdnaMnamnrqqJjaqLi3t+dH2UjpCVk3iHmLSomHh0dYV2Z3B6ipFqaohvbXF0YIBVhnl4cXx1goWhYomQnp6TjYqAkp+Ti5FvbndmiplrfGZqf5eGcW5tgG1wd3N3WWRiYFZacY2TUk5MiXZEdHxNgkVVRlhWVFaFj5B6b2Nxc2l2b2xwamVSdXONjZqelryitZuQjpN9em6Jf5udop+FhISXjHV5hJF+hJZQVVSgXWFelJ2WhFKEeHV2a3x9iJGVjJ6CZmVhdWKFiHphcWVzfXxwWYmfgYuYgJWHgJxYnoieeH6Bj4abfHyeVJSWmpF3foVuhnqPgFVygHl/eHWCnXhydHl1hI9LVpNVTlZdiIJMU1JXlpdcgmypWKmgooV6a2t1a2F8bX1ya3qVj21vbWJhaHJrZmRcb1tZYn52X1NaUnRwZmJ3dYaGh5+Gj3dxeoiEgndydn13gG1hZWRvamVsiHtrZGhoeH6CepiaY3h+d3iCikl+f3WDenNzcHF4fIaLmpmHfYN+a3h6eYl7dGpocnZsdG1ibX1wdnFkc3d6gHV7goKBgYp/go2Jenp0dIZwZn1xfnd8eYWJkJOLg4yNjIdxc4CMpYSKf5CGdYCDmoaDioGeioWJgJaIdnaFdoCFh4yMm4F/fnuFl5aMiJ6SqKZchJmbnKaqi56OhIuCpLmTiomeiot7a3aAd3triHp5d5OAiH57iX6UjYKQiXB4d4aVd32IenWCf5eprZCPjW6NfpZ8foKRmI2DcWVlYl9iaVxvbnyIeIaOfH2ZiHlqeJmdVlWipImNgIeEdHpmb4F0dIB/got6fImJiYuWhKGVrI17lGpnrptWolpcVFKenn2EeHmfjXxxj6Kqhnl5rKOVmI2MT5dJRYGDk5aDjIN4aWFiaoWRUIONj1RWU5mZUl5RUkuUgpOdXGRpYlKRi3iLj5ZXYYydjZSHhYV2an+HiId/eXV4pZSEgJlkooeRnpadoYeBc3GUjWhubnShl49ymF9pmoyoobGwo6CRna6pYbCKeppho5xyjHt1eHuWfGR/mXlufYqHe32EhI6ZjJSOiKGSmYyChXZwgoKJjHiMgpR6ioJydnh3g4SNiJCOrIaEiJZ+j4V7eINwcnVweH6Ef4qYUlNNilJSgH6Wcn6Ne2yAjYRnb2die3Z8dmyCnYaBg2lrdHBpfJJRg3Z2gY6YkIWPfolRm4KJgImDmZCFi4mFjYl7cHl2dHF6gHp/eH6QhoKEgH9/hnyGgqiRi4F4jXJqfnZ8h4+Be3ORknh8g3yMhId8c3BubGBvn313cmdmhnZkeXlwTY9LgGh8d3F9dVB3cDk+RIJNgI+Uk6CQfHZ0eYyhkpOJiIyChoyJfGluYGZ3fHSBi5CJjohweJmAgHN1cXuQiouKhWtxg5yUh2xucYJyYmt0f4JbV3djZWxwYIJXh3p5b3RteXaLVHd6iId+c3JpeoqBfoNmaHBef4lacFthc4uAcG1xgHJ3goCEZ3RvaV1baHyARkRFg3pIgotTiURPPElFQkRoeoF4d3F9fHJ9e3Z9dGxbhH+Rj46KepiBln10e4BwbmN7boiDhYJyc3yNhGpteIducHw9PDtyREhGcoKCeU59dXN1bHh1gImMhJV7Xl1cb1+DhHNYaF9veHdqUXqLbnJ9gHxvaYZOjHqObHR8joWdfHaWU5KOj4dteoNvfXGAdUtsenmDfHWAmHVzeX10fYNETH1JRU5VfXlITkxPgX1PaluSTZmWmYV9dXeCdnCIdYJ3b3+UlXd5dXBveIB5dnVrfGllco2KcmVrY4eDeXONipycnbGSm3pzfYuHiX93fIWBgHdwdHN+e3J1kYeAfnx6hoaEd5CQYnZ7dXuIkk+IiH6Ngnh1cW92foWLn5yNhI2Eam9xcn12cGpoeHxzeXdteIqAhHtre3t8gXd/hYF+d4F1eISBdHl4eYtzZ350hYCFgIuMkJCKfomKjYRtcn6TrIyQiJiOeoKFlIOAg3eRfHZ7gI+HenuMfYmLhoeDkXVzdnh+kI1/dIdwh4JOaX5+fouPdoOAd4B1lKJ4b2+EcXFmWWVrZG5eeGpqaIJ0fXRtfG2BfHR/fGp1coKSdHZ+cWpzcISVlH15eFh5aoBoaW9+iYaDdm50dHBwc2Bva3iFdYGHcXCCcGBRXX+CSEWBjXeBgH98bXdmbHtta3h6gIZ0dIB6dXV9bId9imtXcVRQgGw/dkVKREWOlHh/dXKYhXRohpigfXBsmY19f3R1RohFQn1+io16hHxvYFpcYnmGSHZ/hk5RSoWBRlFGS0SBb32CT1daVEd9eV9vcHVFTmx5a3l1dnhsZHiFiYZ7dm9qlIBwgH5UhmlveXF1dmRnX2OHgmBnZWmQgnZXd0xUdGZ+eIqNfHpueIWETohnXHxOg4RcdWptdHiZhW+JoXtqcXt4a2pwbniCcXp3dpCDi4Z+gnVzhIGJj3qKh52Dj4R0e3t2goGJf4aAoXV0eIRse3NsbXxyeH17goSDeYCNTU9JhlJOn3oBe4t6AXvRegN7enuGegh7enp7e3t6e8J6AXuHegF7iXoBe5N6AXuXegp7e3t6ent6ent6h3uzegd7e3t6e3t7hHoBe6Z6AXuMegF7jHoBe496A3t7eoR7gnqEewd6ent6e3p72XoBe/16AXvgeoJ7n3oGe3t6ent6hHuWegR7ent7jnoJe3p6ent7e3p6hXuEeoV7hnqCe5V6AXuWeoJ7jHoBe4R6AXvOegZ7e3t6e3sCAgQAgOLs39zl5b7d6t64zM7LwdTO0MfS4Nrf0dPTzb/B2eqB4rfRt7y/0M7Vz9bp59ne0tzX1uHv/dTX3Ojc39jAy8jZ5vj37PPM3NC5vNLTx729wdvn087b0Lu5ycPMvsPBucDctdLl+/fz68jJwsXIvsCxrLa9naKkscS2v7zK4/XWgNHO07zC093h4OX9/eXg6MPg7M7Q2efFuL210Li0uq/Rzubh6d7WytPU78no6on/3ezTgoPY1tLNv7fV2dXt1Nff1dbbwcHO14LXuMnI6ffHytOU1eL40/mD5N3p2+/3gPv0ie/nwMvU5orP1dDTsbmqt8G3uM7p28e6tfDT6v6FgP6Wg/ToutjExsDCwczP7If47vPy5u3s8IWChI2Skq2Xm5SSi5T8hYPKwM/ssa3d1cPJrL3J2cnE25KDgNzpk+rVuYT8zeTvgfXJvb/FztDU3+j3i6ywof3S2NnU4dnTz8LLz9XCwtztzs/m27q4t7Lavp6ixNiG/dnE2ODez83+gLmzwcbAxc+/uc2//e2D5uSFgYf/7P3u3orYytPJxtqAy8W55OXe6Ofi4d7K55OHiv348uHmys3whODo6Pjy1IHWtdvrur3CvLK+w7/f0862xdvh8drk8Ly3tK2eoKCepaWhq6qyurm6u8K+q7m3qLa1n7G0paGoprqxtbu1vqzEgMPLvPnIxczW2Na9u7a3wdbNu8qDvtTQ5vf3g4SC/PX93ubj3OHP097i7ufg3d3p4tLv8Nnd0s7Ozc+8vLOuuMvZzc3RzcDIwLvJw7/L1uTV5+TQ7MrO0fbrycnL69THzcvNztPLwdjXzMrI3b7d2NjfyN7u3ePn39HFwb64u9jbgNbLycLDzc3VxMjZ2t3Z2dv82ePK0frg1ZXb29jyydfw3u7Hx8TC38vIw8DVyc7Ay9LE2tXSxbm/wrHP29S9zN/Nu8rTy6Gcp7DexMfEwL+/yN/15+zR2L7ZsM21scS5tLvBvru/tKy1yL7Dvsfdyd/p0NfSyszI3rLH0dfPz/f3gNjKwdXQv9Lb2dbr48/GxcvS3c/Ju82+xLHCz4uV9c3Pg+/u/u/q1tzaz9vBxNPSy9v1wL629MHa8PTzksHRzOzm3NTp287n3tTa5MzF9ePg+oiqqf3+hIPu7/Xt+IGRkI3w3ejq0bS2wfuA5tj/2tzc68/Zz9Dg5+nf5+rv1YCGgIHthNfR0unn083HteHZ0rjO+5Xpx4bg14eH2svU2oD0gYnl/eLF5ov2zNnX3+fQ082108fM2Ma6ssfb5sTWycLE1cjbw8y1vcbM0MnO2s/Ewc/Hy9LNz7u6z9rR0cu+yauvwc3CydTH0tPNz8jFxqukqq2uusDP0N6AifXT6uPPgIeRgYGJhGJ6iH5eb3ZybIB8h3yIkYqQg4GGg3d6k6BamHCMdXh7iH+Cc3N/gHJ2bnhzdYSMnHl/go2IiYJteXiFkp6elZx/kIp5eoqPh3l5dpCXg3+Ifmdpc213bXJ1coGZdoaUmZGQiW1ubXFvaHNubXyIa29weIF0dGt0jZ+DgHp7g25yg4qHe3+SjoF/iXCKmYmPkaCAdndsgmthX1dwaoOChXpzb3Z7lHqanGK6maKOX2CWkIyDdW+KjYmdiIaVi5CWgHV7flN2WmlsjJdocHtbe4aUco5KeXF2aIGQTZ2iX6KWcnh9iVd7hoiSe4Ftd3twcIGXjnxvcqF/iJFNgIpUR4V9YHxxdHR1dnt3j1STiYuKf4WHiFJOUFVZV3FZXVtaUlKASE53c3+ca2SOjomSeIuUm4yDk15VUImWa5uIhVilgI+eV6F1ZGtzfH+HkpqbWXJ4baF+g393gntzb2x7gIF2dIeVeX2SinF1d3abiW1sg5Fdr4hyjI6ThIy8gHNvenpwdX1zcIFtrppUhohVUFiikqSTiV+DeoF3coJUe3lxj4+GjYSFg3lmfVRSV5qYlo2NeneOT3+Eh52eg1mNcJWncG5sZlphZmiMgHxncH9+jHR+iGFfZGNbXmFiZ2ZdYVtcX11fZGdpW2loXmZlVmluX1xhXW1naW5ocWN2gHB1ZppqZmx4eXdjYWFhZnt9cXtXcH50g4yHSkxJh4eWeoKEgYNyd4KJl46OgYOQinqRjn99dnZ7d3dsbmRfaXR+c3R4d3B7dXB7dXR8iJSBkpF+lnl7gKGYeHN2jn1ygoOIiomDfIyCgYeBm4OglpWYeYuYiI6Sk4uHgoJ8eo+TgIV3dXJyfYGNfoWQkId/fYGefox+iK6ajGiQioecfYadkJ97f399l4WEenqQgoR0eX1wgX2Ad3F3fG6Gk4h0gJOCdo2bk2xrdXmhiIaBd3JxeIqekZaBjXqQdZB+dIJ6cnV6cmxvZmFne3R4doCQfZCch4yRiouGlHCEj4+Dg6SjgIV5dIiIepGZm5GklYB4e32HloyEeYh5f2t6g19qq46JXJ2ZpJWNeX6Cd4VwdXp8eIideHpztIWbpqmkZm52b4qEfneLiYOhnpqcpJGCoo2DkFFvbZKST1OPkp6bn1VgW1SGfpCYiHNxcqJTiXuagIGEkH+HgH2IiYd4gYSOd1FbgFWSVIF8gp6dlJGIdJOMhnJ+pWieeFmSj19hnY2RllmnV16XrJWEn2Cjfoh/hI56fXlmfnZ6h3FtaHiJlneCdm91fnmThY1+ho+Pin57h4J7e4yEho6LkYCEio5/fX13hW50hI6CjJyJkpqVlYyLinFna3BpcHJ+foBOVJJ1iIN7gIKNg4CHg2F7ioNldXlzbX96gHJ7hn+IfH2BfnJ3kJ5Zl3CJdXuAjYWHeHmLjH2GgIuFh5OXo32CgY6IjIZxenaDjJeTiI50i4V0b4aLgXRzc5CbiICLgGxwgnyFdnd3b3iMY3aHlZSWknd4d4F/dHtzbnuCZmllbHhrZ1xlgJV+gHh5gmppeXpuY2V4eG9temaDlIWMkKGFgYJ7lIB3eWyBe46KiIB7dXqAl3aRj1ied31tS0t6fn98dG+Mj46fhoSRg4aOdW1xdkdyWmlujZZnbXJRdX6PcopNfnZ9bIKPSY6MUot/YmhweE9mbm95ZWxhbXRpbn+SiHVlY413fYNHgHxNQX55Xn90dnd0bW9ne0uIhYuNg4mLh01GREVFP1VCS05TTE14Q0pwcHyabGmUk4iMb3x9fW1hb0RHQ217WH1qZ0eCYHOETZJpXmJpcXJxd3N2QlVWT3leaG9vf3pxbmh3fnxtbICNc3eLgmhta2qOd1lXb39Qmnlme4CCd36vgG5td3dxdn9zbXlqqJNPenhLSVCPe4yAflp+b3NpZ3JJb3JoiImBh4GCgHtpf05LToN+fXl+cXGKTnl6eIiLb057YoeZbW9ycWZrcXKRgXpobn59kX+JlXFzdnJoa21rcnNqcG9xdXJzdHh6b3yAeIOEdomIdm91a311eX9zfXGEgH+GdKd2c3iDiYt5eXd4e4qCc3dLantxfo2JTE9OkJOghYyNhYZ2fYeNnJWYjpaek3mRiXN0a21ydntxcm1pdIOQgYGFhHqDfHaDe3N3f4d1iYp6k3mAh6ecfHh8loR5h4mQlJaLf42FfoF+mX+fnJmZfY6Wio6Pi4J6dnJqa4SLgId6fHt/jpGYg3+Fgnpvc3qUcHpoa412Z1FxcG2KZXKMgI9weHh4k354bmt8b3BiaW1fb25waF9mal96iH9ndoZ1bYOOiGNka3KXg393cGtpb4GWi5B3gWyEYnxnX3BubHZ+enZ7cmdmdWxtZnCAbH2Kdn1/d3Zseldoc3hubZGXgH5xaoB+bYCJiYOZjHRscXF3g3lwZnVnZlRgbk5WiWRhRXp4hH2Acnp/d4RvdXx8doiZc3Fnlmp7hYWCVlpnYX54cWuBfXWQjIWDiXlvkoB6iUxoZoCBREh7fYB8gkZSUUt2bYKHeF9cWohHcWKAZ210g3J+eHmJjot7gYOCakdMgEV3SWpka4aEdXJtXH97dmFwllyIYUpxckxNeW1wc0iJSFB9j3dmgE2EZXJqcHtpbWxffniDkn93cXyJjGt5a2NocWmAbXVncHuBgn1+iYJ8e4iBhI2Ij35+g4qAgYB6h29zhZCAhY15foF8fnuAh3Nze4J+h4SLhYBLT4hrgH50n3oBe/96jnoBe4R6gnuUegF7iXoBe4V6AXuGegR7enp7hnoBe5V6BHt6e3uMegF7iHqNewN6e3uRegp7e3t6ent6ent7hHoBe4t6hHufegF7lnoGe3p6e3t7hXoBe4Z6AXuNeoN7iHoBe4Z6AXvTegF7hnqDe/t6AXv/egl6enp7e3p6enuaegF7lXoHe3t7enp7e4V6hHuJegF7k3oFe3t7enuPegh7enp7enp7e4R6BHt6e3uFegF70XqCe4V6AgIEAICzuajIr76+x+Tq4Onq1dvay7m+wL64zL7GxK22sr7H18qwzMnH1dLl2t3N0c/Y2czU1tnP7PCC5dvUzsnN0brNyeXe8uzWzM28wsPb7ea7t8PU1+LIxsnIz7HOzruyu8rKzKi0xtLp0MnLyb6yycDCwMqqoqynpqvFsM24xujr7oDLwa+xve3azsrUydfl+NTQ39yvsLnMu8vFwNPb0t7e3Njk4ev3gezn2tjL1szg3+eA/PLs1tLByLe+vL7cytzJyb7e2srX2+bp+IL17srT3NrYgfnZ29Ti7/jd5ObR0+7oyczK99LP/NDY3svu5qiXwuG9xtmL5+vqwcu82+fb7YD32/P5gtjA0svPzM/WyNfy/ZaD+YP48drdxt/n+5apspyC4tDygJibifrV0dTu4b7S6b7F24mJrpSbjYeAjv+KgMqqyNjOu9rYzPD4yObu2oPJ3vro19LExMXI2Mq+vMfJw7nFwczO19TSiPfZ0tff1dbH7YWC6vHl7Mi4w7i5u4DTzM28tK/E1ta4v9H+kuDx7emE5/f+iuPoz8Xr6/LkjIqB9N/PvO/6+IWQgOqLgoTklu/f5tHG1tXQ8PLl4d3jybDVx87XwsO7w8y/y7rIzr3H3NHKy8rizLy8tKiysaGhq6exwLm8vbm6uLSysay5tqajqKmvrKKvrJ+yrbu6y4Ds5ObixcTP1s60u73DxLK4r7y00MvQ18zT5OH+ydjq2+To7ICI6PD1597g2NTVy+fX6OfR3PTf2+Hey7rDy87W0dbi4b7FusLDvbW3xarG6PTV1vbnx8jTz9fLxevm48vSx8fC1czQ4Oi42OS0utHIwcvd1ePj4NDE1MrEu8LY2oDa2+D23/3m4cvb2+HOzcfUz8K/zOGHjuyDxN7tie3g+vjz497Y0sbJx8S3usnVytDNzsrN48Ww1LvL2b+0wby7wbmwxbi3qrSsv7vH6c+5wLvI9PLW0/O4wq7InZ+x28S+q7azs620s7bFv8TSvLuy0cbSyL3EuLq+uLDF0MvTh4DZy9jV0cm9urzgsc3P2u3ey8ze+u3gzrjX24Ln+dG6+M/k4Ovk9YDm19nZysTJ3s26ubm81vjt4+byzsjh9fbw59rwwLbT0+Do49no+PCI8vzb8uTY8uPsjoqB+YaG7dPS0+373sXG7seM3IOA8ebi29zLy8Ldw+za5PT64oTjnIDu2u6ii/HwgI2q58XOi8WsxsqX++PygIvx9umEp47s9ubk7/fiz9Pagov18trP0tHOorzg+863xcu4w+Tdyufk08vg2NHJzrm5ycfDy9DZ2tHlvtza4uPVy8TS383LrL20uM7OxsfUyN3Bw8LAxMnQwbejt7m4vsPMyOLFwbe+qoB8fWeGaXJpboWJgYmQe4KFeHFwdHBvg3uFhnJ3fIOKk4huhoODjoSXi4NydHB0enV7fX96ipNSjoN/gHl4gHGBfJOOnJeCc3VteXmKnpZubHeGgIp4eXl4hml8dWdcYXByeF5mfISZgXh3d25jc2tzdX1oZnRxc3qKdIdwdZKUlIBybV9ia5eHeW57c3qLooiHmpVydXmGeoRzaHV3Zmtuamlzd4GNSoiGfnp0f3WLi5RTqaWml5F9fWxwbG6RhpOHjX+Yj4KJhIeDiEV6fWNsc3VtSYhub299hox0eXtueZWOf4KAooSAont/iX2fmGlce5BucoFWi5GSb3tvg4V1hICCaX6IR3BlfHmAf4SHdn+UmF1Pk1GamoOJcIiKmF5rblxKd2Z7Q1xjU5qEio+bkXeTpISRpGllfmloWFNMWZtZV4Zwi5qNeJiYh6KmeJWUiVt+jaykkoaAgYB9jH5sa3N4d2t5d3x3eHZzVpmGiZGhnJyNq15anJ6Ri3hvgHRzdYCJfHdpXFZmgYJqb4SrZo2hm5lblZ+nW5WYfHKRjJeMW1pUopSIdJ2aj0tVR35SSU6AZJODiXxyfndxhoqChoaWg2yQf3x9aWpjaHFocmZzdWdue3RpY2F1Y11iYlxnbGJkZl9nc2ZqamRhX2BgXVpkYVVYXl5hYFhlZFxraHJ0f4CZhoaCaWt3hnxpZmlwcGNmY2tjfnyAhXl9hn6UYWt/eYmRlFJWiY+Rg3p/enh4c4t8h4FsdYV2dnd3amFpb3R2dHeIg2p3dX+EhH2AinCAlqGCfJ+ZgYCOkJJ+eJOOjHyEf394hnx+iZtzk6N8fo2BcXJ+eISIiYV/jYWFeXqLioCLjImZgp+KhXmFipKAgXqJg3dyf45cYpxUeYqYWpiNqKKdi4mFgHp8gIB5fYmQhIiDgHp7kHhrinmJloJ2f3l5fHFthHVybXZtfnyEoo14fHV+oZyJiat8hnqadnSAnol/cHlybGNqbXWAenyNe3pzj4OJgnx+d3Z5c2x5f3mAYYCLgouKioZ9fn2Ybn53gpqMgIKYqp+Rf2qChlSToYRvnnuLhoWEkVCEfYWHeHV6jX9zdHd7lbWupKGlfnKBkYiFenOUcXGLh5Wjop2vtaNemJt3hX14k4iTXVlTolpdm354dombgnR5lnhhjlRPjYWFhIaEgHySgJqJjZqXhFCDZoCShZNpWZWYVWZ4noKOYIFsfXtooYeLS1iQmZdaeWCZpZGRoKWViI6RWmCjl4N5fn96XXKSrIRseoNsc5ONcoiCem+Bg4OFint9iIaEg4eMjYSWdpKXoaGajoaOjnp/ZXJrbYGHgoeYkaeSl5WOlJOWg3djb3Brc3Z+eJaAeneCc4Bxd2eCZ3Fqa4OKgYSFcXh5bWRlZmRjenN+f210eISJl4hvhYKBkIaXi4R2fH6AiIKIiIyGlJpXlYWBgXx/hnKBeo6ElY96c39ze3iLmpRsanWIh5aBgH99iHCFg3JlbHh2d1Zdc3yVgHyBgHlxgXV5eYBoZHNta29/aHVcYYKHi4BsaV1gapWDcWNrY2p6i3JzhYhpcHeMgI5/doOIe4aGfXp/gH+LS4eCf3xzfG96dXVChISIfHtwdW1zdHGThJGDiHmQh3uEf4WGi0eBgmZweHdwSY1ycG1+hop0d3Vlb4iAdHhwkHRvkmlweGuKhlpQdIlrb31Th4uNa3NofoFyf4B9ZnyJSXZrgXyFgXx9aW+DjFVLjU2VlYB/Z3h1ekpXW1BHeWZ4P1BTRXxsdoCVj3eNnnl+h1FMWlBPQ0E7SXxIRWZQaHptWnl8boiJYH1+cUxlco6HeW9lbG5yg3hsaXJ3dmx2cnVzdHNuUIpycXWDe3prilFOiY2FhXFkcmlscICIfX5pYV9xiIhtb4CfXH6KhYFOdn2BSoSMb2F6d4N4UFJNkYZ6aI6QiUtVR3pPRElxUn90fHJreXRrgoV9gH6Ne2WLgIKEcXNsb3Zudmh1dmhwfntzcW6FdG1xbWhxc2lscWpzf3Z4e3FydXd3eXiEgnVzc3FycGd2cWl6dHx7hICbj5CMdXWAj4l1eX6Ehnh4b3RnfHuChXd8iIGZanaMhJCTlFFXiZGTh4qMh4OGeY55hX9pcoN1dHt6b2NvdX6Cg4WVkXiAfYeMiX99gmZ2iZR3eJqVfn+Ni4p0b4yLiX2IgoiBkYGCiphskKJ5f5ODdHmFeoWGgXdsfHd3bnKDhoCFhoGTg6WRjICHiI55dnKEfXJwc3dMT3Q+U2Z0SHZvjoyLfYGBgHp6fHZqZ3J2aG1pZmNofmZWd2d4iXZtdXV1eG1qf3BuZm9ldnN6lX5oa2RtkIx4d5Zmb2B/W1xrlIB6cHt0cGhsaW11bWx3aWpjf3Z+dnB1aGdqZFxtdmtwWYB7cH2Be3JqbHCQaXt2gpWDcnKClIt/a1NsdEyBkXFahGRza25xhEl9eoCEdnZ5jH91dHN2i6CYjIqOZ19zgXl0bGeEZ2R2cn+IgXuIkIhSi5R1gHlyjH2EVVFHiU1PgGxoan6SdWJhfVtKcEVAcm1yeYF8fnqSfJZ9fIR/aUNsToB0aXlQR3R3QkxWe2VvTmtXaGlgkHNzQk13fnlJYk9+i3t8jY97bHF0S1GIfWhjaGppTmuOroVueX5naoJ7Znx0aWJ0dXZ4f3FygoB9f4KGh3yNcY6Sm5mTiXuBiHh9aXt1eIuNfoCMhJR7f3t3g4eQiIR1hYl+gn59c410bmx2Z7Z6AXvuegF7inoBe5l6AXuHegF7onoBe456AXuMegR7e3p7iHqFe4N6hHuMeol7A3p7e496AXuZegF7iXqCe5d6AXuEegV7enp6e4h6g3uHegl7e3t6e3t7envxeoJ78HoIe3t6e3p6enviegF7mnoBe4t6AXunegF7iXoGe3t7ent7i3oEe3p7e5B6EXt6e3p6ent7enp7e3t6enp7hHoMe3p6ent7enp6e3t7inqCe9Z6AgIEAIC9tMi9sMuytdDp4O/r2OD327O/vsG9tsO5t667wbfG1OHXvcbJ2tTe2tfc28j05drLwcG/wMba4tvUxM7O2cPY0dn73cjBxMXCura6z9PhwcXU2c7G1sTHyL/Uuba1w8DY+OvAvrrU0tStyKKeqrvAqrm9saWZqaCkuL+qtcrz/oDMwL3Aztfe6uOB59ff6t/k18nSssbE49Hy6+zb09LO1djS39Hv+eTVxcvFysXP1+Ld3Nbn5Nbdz7KrsbO4q8TY3M3S7eKU8OHlgefgjYWD9I6IhPKMgd7/gunk4+bXyu3K27yrwbq0iYO1tb2krLGir8vOrOTTusLlkYr7h+ff5oDkhNX78+rq5crWzdbXzMnD9JKVipHn+dDIs62qtKa3vMTF9PuEkpr35/n7/vT79MnE18TOobvJ+4nk4++OlIWp4urug+Xbvby92eq4v4rj5tzUrr22tbOgrcO8vs3Rw8LC3NrM09Df1Nvw4+TJu+nZ4tjXy9fMsrbZxq+rqLW9tYC7x8HH1Lvl39i6vdiCh+XNz8iurNvb7PHs2fLt/+vz3NnR4ILs9JG/8NyG9+yA/YP6g5CJ94CEg+yBkI2GgYX22K7C9uDBwdnP0NDTx7vFt7e7z8TAxae2oa6voLWvnZ2kp6Swtae2uLvRwbu7sKepuLqprbO3pryurbWwyMbJ24CD+tzjxMbRw8G/xam3p6utsLSw38zU6Obq7PP5/YLl6djg+OPu8O3Wwdzg1+fTyd3k0NzV4eDl39fSurrBw8Xj3Ofg6N3Xw8PGycPXycPR1fPM9NPjzsbMscfL3eLZ4uHUyM7X1dbS39a3xdPPucHXzsPT0vf92sW9vMTGw9bU5oDNz7/u8+fHzs3X5e/X17qtstbK2/rxhOXl4OLI3ejh79rV0sHExL3Mv7S+xtve29PHwb2708e5ua3Atb65wrK3x87HytG8vcjBxcWwwMrLxrzOvNDI2IfesLeglZSYjJ2dpKW3rKyyoq++18vExsKzyba7sbG3xODk37+/uba40YC/yby+tsy+ys3av6+8w9bp297e3vj47dnm0OTs8drWxLnIx9rS4uz56+jl6Nu7ubvDsK67vtL23+rs6vGF+PiG6N+JtMvW4P/+++nk9oTx4su8xNG95OrzhN7q7eXegdzg5YSA9/6NgOqJhoP0gvjW2c7hzr3p1+Pqzczf/Yfao4CG/e/C4Ozz692AzezS3cPK17/rkvKDiv+Vg/XosvLL//Hg0Nbayeb3hoCA6PfR4/jlzOCB/9jY19PAvb/g2s7Q4NfY5dnMqJ+2sMa2x+PXz8jO1dXRy+vn0tPg6sa7uq+2vcPCyOjl0sS/uMe8q9jE09C3t7C2vsDLxuXty7ayuYB5cH95anppaXySjJiXgouhi3B7f4OCe4mBgXV/hnWAjJSLdX+GkYqTjYSGgXCVi4R2c3V4dHmHkYR9dH+CiXiMhYmkhHdxb3FtZ2FpfHd9X2Nzd3Z0hXRxb2l0Y2FfaGyDnpRnZWF+f4FiemBaZm94aXJ/eXNndW1rfn9oa3ukqoBza2ttcoCIkYdSin+KmJGhlouVeoV/jnyThn5uY15dZ2ljdnOQnoyDd314gHR+iZaMjI+hmIaPgmljZWhwZXuPmIqPpJZflYCASXtyUExIgFNMTYdWTn6bUIeFfn55cZB3kHdpe311X1hsbXdmbnRkanx/X4B3Ym2JYFqkWoyCiYCDSnCUiYONk3qFgoqGeXVqi1hbUVuEnHt4amhjamBua2tpk5BPWmWajaGpraOnpYJ9joCJZXmFqVqPjJNcZFZ3ipSZWp+ghYWInaRucl+LjoqBY3BubGdcZnluc4WEe3d4jox+g3yCbneEe4B0dKSZraShk5mJbGqFcl1ZWWJoYoBnb2ZqdV2ChYBoa4VXX5aHh4hycJmXoqmfhZeTnI2Zg4WHmVmbomBkjXNKfGxBgUiMTFhUmFNWUIlKVlJLSU+VgWR4pohlXXlvdHiAdWl3aGJicWpiZFBcS1lfV2toW2FmZFxlaFlnaWZ3aGJgWVRXYmlgamxyX3RkY2lhcGlse4BPj3aAZGp5cGtlZ1hsYmJoaGZii3t8kY2NiIyMiEp7hH2IqJWamJF9an5+d4Fva4aCc314fH18em9uXmFpbXCDf4qFiIOCc3N4f3qNgHyBgJp3moOUhn6EbHp3goZ/iot+dnl9fX17iIZufImFbm2Ad2pydZehg3l9foaEe4qIlYCKj36lsqeFiImJlJ+IhWlcYIB3h6OdWZOSjZN8j5+ZoZKLhnl8e3iDfHR+hpeUj4d4cm5thX1zdWx6dHpzd2xrdn58gYZ3fIaAgYFncnp8eG19cIN9k2SheYFvZ2huYnJsbmh1bmtwYW99jn59gHlqfG5zbnJ8g5ucl3d2cHF2kIB+hXp7doR+iYuRdF5jaHeJf4qMjqupl4GJc4aOkYOBcmh3cn96hIeOhYeHjYptbnJ9cW55fZO3n6uroqJYmJBQeHZTXXuLmbWyraGbqVujiG1fanZpjZWhWo+cmI+KVoaLj1ZTnZ5bUY9ZVFWbWKaJjIOTh3aej5Kefn+NoVaAbYBWnolohJGcl5JaiKKQlXx8gmWEVoNIUZVgVZ+Wf5l3nJSFfoGIe5ehVlNRh5B1g5SDdo1Xr4uNi5B3dm6PgG1tfnt+kZWUdGp9cnxugZaJgX2GjZKRkK+rkJGZn4F5dGpxd3d2epaYin+EiZmNg6mRlo91cGdocm53dZaghHFweoB0cYV6a3toYneLg42IdX+UgGJrbG5uaXhzdGp2fnSAkJaMcnqAjYmRjYKIhXifl5WIhYeLhIKOlod/dYGEjniNhYqjg3ZzdXt4bWVre3qCZmt7gH15iXh3dnOAcWxnb3KDnpBlZGGAhYlqgWlpdYGHeIGLgXppc2lpeHVaXG6WmYBrZGVpcYGHjH5Of293gnuHfXaDa3p3joCXjo6AeHl1d3ZsdmSBj3xxa3Juc2dqbXRsbW6AfHF+empoa215bYGNkHp8kIRaiX2ASHt0UklFd0pDRHdKR3mWT4eBe3lyaIZmfGRWaWpkU09nZXJjbXBka31+XnV2YWeAWlSUUn91e4B0RmeOiIeOkXiCfH9+bmheg1RYUVqCnHx2ZV5XWlBgX2drko5MUVaAdYOKkI6XmXl2iXZ6U2NtjEpuaGpDSj5WYm11SH6AZmVrgIRSWVFxc3NvVWVqbmpban90eIqId3BwhoN0eXF6aXSAc3NhW4N4h319doJ+aW2Ne2hhXWpyZoBtdm1sd2WIiYBnZnxQVYZ0dXNhXYN9io+OdYJ/inqGcnZ5ilKOkFJhh3NLfW5AfER+RVFPjE1RTINFUE1KSE+Yhmd7qY9sZX91dXJ9dGl6bGlte3ZxdFxqW2luZHl1ZmpxcWp0d2hzc2+CdHNybGhqd31wdXd7an9xb3dufHV2g4BSmH+JbnWFfX17gW19cHR3eHVtkH2Bk4mJh4mJh0p7hH6Hp4+WlJF8bISLh5WBeI6HdX12ent/e3ZzZGZwcXSLiJCKjYmHenuCiYKRgHd8dpJwlnyOg3qAZnBrdXh0goaAeIGHhIF7h4NndYaDcHWKgnV4dpScfG5ub3Z1bXt6jIB9fm6WpJ5+g4aEkJ2Lg21iZIB6h5mKTHRxbW9ccX56gnNvcmpxdHR+c2hvc4F9enJmYV9je3JpbmV4cXhzdmtteH18foJxcnp0dnZdaG5yb2Z0ZnZzg1qKZGlbUlRbVWhmbmt4bmxuXGZzhnR0eHBgdGVoY2dudYmRkXFva2hvhIBwdW1vZnJufoGMcl5jbXuIeoJ+fJeWg216anqCiHt1aGFvanh3hYyUjIyJjIhsam97cGtydIKeg46Jfn9IfHdEaGlLU2t4hJmWkIF5i02MfmpfandmhIuRUHZ/fnZxSnR2dUhFgH9GP2xHQ0J3RolygHqRioGqnaCgf3qDjEtrXoBKjX1YcH6Hf3ZLcY56gGpobFNwTG88RYBUSIp9Z35hioR4cXR3aIGHSERDcHhic4d4aoJRn4CBf4BnY2B9c2VidnZ6iYiGamZ4cXxreo6BeXZ6gIWKi6injYGFlHh1dGtze358fZuaiXl4eop9dKCLl5mGhH58hHt+dZOcfGtqcv96inoBe7t6FHt6enp7enp7e3t6e3t7ent7enp7jnqCe5B6BHt7enuEegF7j3qEe496g3uRegR7enp6hHsEenp6e4l6AXu+eoJ7lXoWe3p6e3p6ent6ent6e3p7e3t6e3t7eoZ7wXoBe5x6AXv4egF7v3oBe+V6B3t6ent6enuKegF7inoBe4V6EHt6enp7e3p6e3t6e3t7enuPegR7ent7iHoBe4l6Cnt6e3t6e3t6enuLeoN7iHoBe8x6AgIEAICvqry3yrq0r7TE4ITtxdXi1rq9use0xuK8sqKenbXRxrjAzsfL0bC4ydPL19XM1Mq+0MrE2uba593o5dDHxbbG09T82bS6xseywaaxscayxNG/wMTFyMi5xL3j0rqwurTG4drCxsrDuNCly7a2rsuxqrCxsrmkrqyktamwuMrp/4DhvtvQup2+9+rm7vni1u3s5uLd1be/zvuOjoqPhuTggILWzd7Ak+a8uK+3u9fd4d3o6N/f5cfPjOS5sbzCvLXDssHM4IP3/4aKgffxiZ+F0e6Chv+W1NHxp+/PweO+0OHRusXu4Mv28IDFnKOoqa+n1cPQ0Y681uTMydLjwbjj1YDOwcTM3OH038/v4/LV6YWJhPGBoZb0hITTyca32rmvsKaqzez57P3v5YXQ28/IzdPAr72s0b74jorq9eOIjITm/v6SkYWG4cbTtcbY15SK8tPIwbLBr7e0ur3AxL28va+sudXj4c/Q0urq3+zV3Pnf1cjAttHRvL26qbTQpKi4vYDMvbvE2Ib27NS2u8KCj+7Lw8jPy9zg3fuRjIaH9O3xzNnY0djG0cfV7/TngI6Tg4CAgIKJgPGD9ur4997c/5HPv7HR3ePm2eb12v/v0dDJrcG+ssfkx8O9xbTKqKOzwbSytrGywbS1s7XGub7Iw8ezwb+/ysTR0uXQr7i1tsPPt4DO3fvq3tfp19bAq5aco6TDxMG319TN1Nni0dHY283U3OXVhebX0cO6y87V14fp0uDbxs/w18/Z2tjCysO8wcnV4t6F9evc4N7Y38O1x8PXzdG/6NnIw8jT0cnJw8/OxPTf6N7LxtPh7N3R3tzXvsfRuMWE++D869rT2MTT3ezf6YDeyr+13f/00M/z+9PM0b7F0M7S4P3394Pz6drp/e3m5+HRuLrSvtDQxNbBxuTj5djIt73R08K7t73DvreumqmusLO6qqioz67Uv9O6vKbR2tX3+sLe6ebh0OrJyrueoaenkZacu7TIu8G70dvWzuPZxLXA4N/559ff38/RrcG9wYDKwrWxrbOovc7Ott7agOuAhczO1ePx8dDZ6/TY5t3W1N3Tz/LZ34CB28jFu7rFv76zu73Hxt2y1O//h4SBjYSGgfXczdaKk/jU3OHx5/bj4NmxxsPH4OP+7NjM7NnVhfXs9YH7+pWYiJePhOnN7vS+38Lr08PTz5jX+vj08fTkioCF9Prah/n68pzy29/e5M3Gw7rB4Pjr5sX2i/zm9Irn//L/5dDD4d7z8OLn/oTtzv7rydz/19S+w6qvusjr6sy6h/XczdbIqKnAvbvB4tfV3cvj4e/z093q2+mBg/jH3MnY2Nzl4czq5dutqLWysa/Ju7e8v7nBvfHmz+3k1sKsuYB6dIJ8hnFmW11nglOScX2OhnN1doZ0gp6CeWZiY3GFeHF0goCLlXV8j5KKkpGGiX1vfXRxhYyGkYSPkoJ6d296h4WihWZueYBudlpiYXFdanRhX2dwdHBmcm2Od2FWXl1uiYhzenl7c4Bdg3Vyb4JvaWlvdX9sdHVxf3J1eomku4CXfZWGb1x0opWQkJmJgZadnJiYlXyBiqZcW1FVSXFyTE54c4pwYZFxa2VrcISNjo+UlYyWnH2CXZVxb3yAdWx6bHuCk1ieoFFURoN4SVxJaohQVZxhcnqadJyCc5F1g5aGd32ainyYlVR5WmRqaGlefmVxcFRZbndtbnqKbmKEe4BvYF9qdX2XiXyWkJh3f0xRSIRNaWGWVlaBgH1uiW5mZFdWbIGKgaCdm2KXoZCHio15a3dnhXirZF+Sl4RTVk6En6FiYltdkoGOcneDfVpTj4B0cGd0YmhmZ2tpcW5scGRkcISLi3Vvbn+AdIl+ja2gnpGHepCMdXRvW2N9WFpocIB+bmdwgVeYlH5obnRZaa2SjJielqCeladiWVJPiYeNcn+HhImAhXZ1gX5xQk9TREJHSk1XUZJSmZKYjX6Am2F6dWeDiYeDdYGLdJSKcXJuV2pkV2p/ZWNeYlxuWVVicmdkZGRjcWZqaGl2a2pybG5dZ2lrc2pxcIFvU2BgX2dtW4BrepKDenSGe3xsY01RVFRyd3Vwi4V9iYyThoWDhHV5fIJ6V4+Hfnlyf3x/fVSKeIV5aHGLdnl/g39udW9obnJ5goBUkIh5hoKDkHtwf3+MgX9xkop8d3+FhHp2cHl5cJR/i4NxbHN+in95hIWCcXF6aHJTl4qiloaDiICMlqKQk4CKiYN6o8mvj46rq4iChXh/hoSBiqaioFiemIyesaafn5iJcnSJeImFgpJ/g6CdmI9+b3OGhnZzb3Z5fXlxXXB2dnyHdXVvmXaRgpR+fmyPl4ypq3mOnJyckKqRl451e3x6ZGZofniFeICBi4+IhZeQgW53l5i1oJKXmIyPanp3gICLhnZ2dnhuf4eCa4J7TopQVHBzeImVlnF2hI93hIOBf4Z+eZZ+hE9SgndycnJ9d3ZvdnZ8gZVujqm2X1hPVkxNSZSGeH9YYqSIjJWgkJuLgHhZc3aBnZeyoo19kYKDWZuUmVGcomRlVWRcV5qDmZ94knujloeVj2iKpKCZmJqQWYBSl5Z7UZaYk2eYiY+TmoZ/fXNzjZ2QiWyWV5yIjlGBkoeShXRuipCgmZKTo1SUd56KcIGmiop4fG5tcneVlG9fU6CPfpKNcW2CgHh8lImIjXmTl6OljpuvpZ9TTpdsgHSDfH2Hh3eXnpl1eIR9enWMfnVzb2hsaY6GbYyLf3hxgIBzc4J5hHJmWVxpgVGJaHWEfmttanpoeZl9d2hnaHyRiXt8hH+Hj3BygoN8hoqDjYh+j4OBkZOJlYeUloiCgXWDi4ejh2hugI18gmNqZ3djb3ppanZ6fXlsd3GUgWtjbm19l5F4fHx6dopliXt7dYl1bG1ydH5scnFqdGRiY3GNpICDaoh+aldvlYmFhIt1aX2Df3t7d15hbI5SU05TSnl7TE1xaHNdV4BeW1dcXW5uaWhxcGl1gWhvUopucH6HgXZ/cHd3g02GiEhOQn14SVdDWnJBRoJTYWqLaZJ5a4dsd4ZwXWN+c2eIik9zWGNoamdggGhxb0NddHxycXmJbWGCdoBoWlpibnR9eWuFfoJncURJRXtJZF2NUVN2cmtde2ReX1lbcoSMfoyAfVF+inx4gYVzaG5cdGaPU094gW1KTUJrfn1MTEZIdWx7ZWlvbFFHd2xjYFpvY21qcnZ2eXZzcWRia36GgmxnZXd2a3locIp7dmplYn6BcnZ0ZXGGXWJxdICBcmhreVaUj3lhZGtMV491cnyIgo2HfpFRTEdCenqBanuChIZ8gnNzgn5zRE9RQj9CRUhSTIZMjYKHfXF2kl55d2uFjYuEdYKLdZCIcHFvWXBsYHiRdnJudm6BZmNxf3V0d3N0gHR1cGx6cHB5dXtpdnd4fXd/gI9/ZW9wcHd7Z4B4g5uPhoCXiYp9dV5iaWqEiYV4j4qCh4qSg4KBgnZ6eoJ3V4mAeXVvgIGKil2bg5CAbXSOdXZ9gH5ueHNtdHqCi4ZWkYl4hIGBj3xvgH6LgIBxkYh5dHuEf3BrY21uaJKCkIl3dXyEi350gYSCcnZ/bHdUl4efkX12eW97iJWJkICGd3BsjKafg4Gbn355fHR/iIWDiJqPi0uCemx7joSBhH5yYGZ7a3x3cIFucI6LiX5tYWV8f3Ftb3d+f3t2YXN4enqDb2pigWF9bH5mZVN3f3WSlWR9iIeBcYRtdW1fam5uXmNjeXJ+cnZ1fYB4d4qDcmJphYmol4ePjX+AYHBtc4B8dWhlX19YaXZ0YXhzSoFKTmVmbXyIh2ZvgIpyg4J7eoV4cIx5fUxPgXR0cHJ9d3VucnF1eY1ieo+ZTUY/Rz5BP3pwam9OV49wdnuIfYp7d3NXc3J2jIecj3Zmd2hlTYR/hkaBfk9RQ1FLQ3Jdd39fgW2Xj4WVjmaBkoiAfIJ3T4BOi5JySYiHgleJfIGFkHtuaV9hfIl9dll7R39xd0RrgHmFeWpjenuJgHl6jEiAZY2BZneXenlpaFdZYGaGhmVVS5KHe4mDZmd6dHBzkIKAfmqEhZCUfYqdj41KRodlf3SFhIWNiXWSmJBoanZwbmqDeHV6fXiAfaWVd5OMgHdseot6AXv/eo16hXsEenp7e4R6AXuRegF7jHoVe3p6e3t7enp7e3t6ent7ent6enp7j3oBe4t6AXuZegp7e3t6e3t7ent7kXoBe416C3t7enp6e3t7enp6hHuHeoJ7tnoBe4Z6gnuKeoR7j3qKewJ6e4d6AXvjegF7iXoBe5V6AXuvegF7pHoBe/V6BHt6e3uVeoJ7knqHe4R6gnuXegd7enp6e3p6hnuMegF7h3oKe3t6enp7enp6e5B6BXt6enp7jnoBe5N6AXuZeoJ7pHoCAgQAgLC7vLa1trLO0NPZ4ea31cC4pp+Oio642LqptaiXnrzDyMHPycfLw7e/2MfLyM7Vx8vPvbviw8jZ3ODk79HDstHSx8/izM7CtK+vvLmvtr3Oxra3o6Wxuqu7wMHd187eva2pu7rRubOtzr64wL23vaKmoaKWrrCqpZKbqbO+zN/vgOXiwtXyx7XA1crHz87VhOL67d3bvt3ug/aC+4eE8vWSlNvk2/Xe7evo/YTU6Nzu2N32gd/9/KHr/fnk77K6pKu0r7DB9oGA9Pfv8NuAgfju5u3e1MTDwbKh6MnGx7Xg287LzdW7z+rq4tzUwL27tK+uxMzb3vr18uHe7ufm7/SBgP/17/Hs7+3R3tfC4Nn94fHp7e/57+jd7tXhhuP15s3XxsDI2ejZoPLI2/bHqLrMwOyqwM7Rxt3jztry7eLvz9Da3frx3tGFgdWywcPKgfbsj5D90b3EtKrAwqudmZCcp7DHydDC2c7V5f35guf0+NvGvsizwL+5zbu8wtTEzcDFgLffzNvZ5vn688rK6/Xk7t/Tyr/J+Jjn6Ov82oTj183D59/azdng3enbiYaPjIWHgZWR/d7l6ejS6N7k2NTcgcjEtdXQz9DRgu3i3fS+t8G798O71OXAzcO5vcO3trjDwcvKybe5vq6wv8G6wsnd2sfHyc7c2eHU2NvKtse8wM7DgNf19sG6xdXpzL7Ku7KvsLK7y+LO28DJy+DNwc/PydLN4rzIzsjIx+D22NbPzt3N0tjk1ePuz+bWytDFutHi2tnX0uT2+d/Z4t/V1sfBv7bR0tvh3t3V2eLUxLzG1tHjx+Da59/u/+Hl7/H35IHy4OPL1dn21+aM6ejc2tP38fDsgOuwpr/d2sy7wePv3b/l283Iwcvc8Nztq+jZ2ebm0cjDyrKdr8fOzdHO5MrQzsfOzsm6062tuK2csbykrrCqs5+xucbDs7O618u707bMztPw6eDZzMvlgvHwj+rv9cvosLKhmamXuba3zcvJ2Na7ttflysO0vLvDzMa/v77MxMqxgJq4nZqxt7bFyr7J2/rh1OyB7+La5+/93dvU1+Lx2vXc2crF0srY09W7zs7E1M7FxrS1zcfF1s/l+PWIiYaE/4OA3NTt6IjNgtfb7eHi5uvh4NmD3eTX4tjo//3/+Y+VhfH3ionc2vLm2YTMzrbv3tC8y8S1ubnD6N/EyteH/9uigPj98NaA/+DliNPay7bAzNDByMvb1/GC5+Lr1ojj6P+L2ojV3Nze5tfi1O+FgPjx1fXi4O+C793fubzC3/br57CA1YLk3u7p4NTHw8/dyt/d5+CEgfDX2d7M3efU6Nvl4emH8tPYhtfM+uHQ0Me0tsfUx7/RurbH3My9ztrCs6WfgHiBfXh0bmRzcm54gohqgXh2a2VZVVh7moZzeW5eXHV3enJ+fX2CfXR+koOEfoKLe36AcXGReH6OjYqRnIB2bYmKfoaSfXpuZWhodXRpaGlzaGJjVFllalxmZ2J4bWh4Y1xdb2yDbWtjf3RvfXd1emdsaHBqfn55dWRrcniCipehgJiVdYSbdWJrfXJtc3Z6UY+lpZiggJyjW6JUmVVQipFgYX6HhJeFkIuIoFaBkoqcjY6eUoyjom6Xop6RpXZ/bHV6c3J+n1FOi4qBg29ER42EfISAfXRxeWxynYaCgXWalomIiI56iqCako2Gd3p6cmpmcXN9fY2IhXx+ko+OlJZSgJ2NhIuEiZeCj4BxjX+XgYyDhYWXj45/j3yOV4+fln6DdGdodn55aZ19oMWUc4GPgJpmd4GBeo2QeoKTjYeTdHN2fZiRiX5ZV4lscnh/UpWPXV+niXp5ZmN0dWFdWVRhb3aJiIx8i31/jJ+eVpq0uqSUjpN/hIB0f2lhXm1ia2ZtgGSMdYR/goyOhWVliZCGlpSOjYuRrmmcmpqheU14c25lioyIfYeNgoR2SkZKR0BFRFpWi3eFjYt/kICHfHt+UHJxaIiBfnJwS398eJhqaXZ1oHVthI5qdmlhaGtlZGZtcHdscGJjZ19ibG1rb3GFh3l4e4aRh41/fnxrWGZbYmljgHSOi2JhbHuUeWduaGVkZmpscYd3g3B9gJB8dX99eod/kHJ8gXt5eJCfg354dYN4cHaIeIGFdYV5cHptY3OBeHRzcX6HjXt5foB4gXZydWuEgoqJiYiAfoh7a11mdnSCa4B1fXJ3g2dteIWJfE2RfX5xd3SQdHxUhpGHiIChmZiSgJZuaX6Ym4d3fJWfmH+elY6Lf4aRnYaRcJGIj6OwnZGJiXFaaHt/fICCloGFhn+EhoN3iW5yf3lpfoVwenVxfGVvdoN9bG17lYd+nIWSl5q2pJ+Ug4ObXJ2hYqChsJK0hIR6bXxmhHh0fn97jJOBfZSfh31vdXZ9iH18fHuDe35zgGiJcXCDgoKNi3qAiZ6CeIlMi352gouaeXRzeYaWiaOKj4J+in6MfX5pe3ZyhH53eG5uf3x+joebqaJXU01LikhKgoGclFt7VYGFmY+IjZGDgYBXhpaNnZahr6ejnV5iVJyjYFuOi5uclFyNlHummZN7goR6en2Bmo15d39YoX9ugKGom4JQoYmQWoiOiHuAjIuAgX+Gg5BUioqOgVqHjaRXf1eAhomSnZGYj6ZbVqqbh6GVj5xVnpSNeH17jp+Yh1VNelWLj6mkl4V8dIGMfZGPm5VbW6+iqKycn5iDkoWTj5dWlHl4UHh2qpuam5WAeoWJenKEb2hxgXdmdH90bGNmgHmHh4B7dWx7d3Z7gINlfXNxaWVXV1V7moV1e3NjZ3+Gh3uCenp8dWp1h3l9foWNg4OJeHucfX6Ojo6Uo4h+cIyNgouXhYOAe3dyfn1ub3R/c2ptXmRucmNvcnGIfnmNdm9xgn+Te3Zuin53g4GAhG9ybnFnfn94cmBkbGpudoGIgH59ZXWPbmFpenFrbWZkQ3CAfXJ3W3SCS4VHhUxIg4pZWG1zan1xfHl5kE1re2xzZmt+QmqChVx+iIl8mnN+cHuAdGtzlkpGf4B3eGVBQ4R5bHJvbWVjbGFfk3t1dGODgG9vcG1cbIaGgoGAdXR3bmhkb3F8fpOOioCBj4uIi45LgI1+d3x2fYZueW1fdW2Fcn18gYWXkIx8jXWET3+RiXR+cm5wfn9yYo5th6iBbHuHeYdgbXV1b3t8aHSFfXyFZ2dvdYuAcmZKSXFZXGJqSIF3UVSPeHJ4aWZ8fmpgXFReZWp8e4BueWppdYWDRnqMlYFzb3pvfH15jnl3eIZ1fnN1gGmRdn56ho+QiWhjg4l9h4R9eXqDnl+Ih4yVbURwcnBtkpGMgYiLg4Z2S0dLSEJIRFhXkn6FhoNygnR7c3R4THJyaoeAfXBsSX93c5ZpaHVzqHdvh5hwfHBrc3dubnF5eoJ9f3Bwd2xrdnZzd3qQk4ODhY2Uj5SGhox/a3xxc3dxgICVk2xrcoabgHJ/dXFydnuAhZWDkX6DgZB7b3Z2dIF7j3F/gXp2c4qcgIF+fox+fX2Le4WIdIN4cHxwaXyNhYKAe4aMjXZyeXx2gXRwcGV6dn2BgIB+go59alxib2+BbIZ+iXyGkXJ2f4aNgE+VgYJ0e3uUen5VhI1/fnOWjImDgIteV26HiXlra4OPh26RjYeDe4WQm4GHaX5vcYCJeHNwdGFPYXZ7d3l2h3B2d3F1d3RpfmZqdXBleoVze3pzgWl1e4Z6Z2VrgnFkfWNucXaQh4J8bW2CT4B9TXl9i3WXcHJrY3BdfXNue315h4d0boSMeXRqcXV9hX15d3F6dHdmgFZ2XVhkZGd1dmhveJJ4antGf3Frd3+Mb3BwdIOUhpqEiHpxf3qGen5sfHt0h4J5eW1te3NyfW99ioRJRkJAdj5Ba26BelBsS3F3joOBhIh8eHRQd4J5hXiFlJCHfkxMP3d/TUxzc4SCfVBucliGfHhpdXdzd3yDnIhsaG1Lh2lcgIiVjXVGjXJ1TG53b2Rre3hqbWp1cX9Jd3V3Z01wc4hNc090e36AhHV5bIFKRYl/bIh7d4JKiXhxV1tdcoeCektIcUx9gJSSiHtxbnuDcIB7h35NTIx/h4d3hYd0hHqKg49Wk3V5UnpypJGNjIJwbXyEeHaOeXOGmIZzgo17b2Zm/3qPegF7iHoKe3p7ent7enp7e4l6AXuHegV7enp6e456gnuFeoJ7inoBe6Z6AXuaegF7i3oBe596gnuFegV7enp7e5l6AXupegF7hXoBe416iXuMegF7iHoBe/96pXoBe4l6AXugegF7vnoEe3p6e7Z6AXupeoR7A3p7e4R6A3t6e4p6AXuKegd7e3t6ent7hXoBe5J6BHt6enuEegV7enp6e416AXuEegd7enp6e3p7iXqCe4d6AXuLegN7enuPeoJ7jXoFe3p6enuaegICBACAr8fAyte6vLTY1NbV1tLLtbSjlImgq7K/wrHFu7O0zI/Rv8C3sczB1L3bxtHG1tzOzcvCubu9x9jS697o6v3kzsa+v83kz8rAo6mosbK1wsvHzbbL1dbOvLa94OjP5/Dc0L+4uLizysrKvr6nusOwsr2/s7C1tcPKsrHBtra70emA9N7M0eTs6oHexM/S3cz6+Ovdw9XG2NXn4erm8Orr/vuAiOv+g/aBi4jy+uaChIvw8ev558/pkc6KjILa9OHLp7avvb/aouXS/vbv4//15PX08cTRxbqsubzbu8PSwsbR2Ovh79zI4vWF5vvExL7Ey9PMwN+B2NrU3Of15f+Cm4aAo5aihImUkuyG+oKKhIeE6/Xn9vf37f2Km4qb/Ivk2+zy69TL1Mrg2dqowKvUyrC80OXh/ZD17se0wNjo9eDl0e/Zysq41Y+Kg83YgYGajP2TivPT7cqrw9O2ta63ua+u1vLj0vXl3efzhujk5tvj1Lvav7W2try1wrzFysvRy8GAwbm6zK3C6vHW3PDszNbJs7rGwcnDy9Dw8dzQz8bY0NXWytXG3ebh8PLsgpaMnpqQhfzPxsbJyc3X4OOG3se109HXz8/A0cyH8fTg3+rjwaauvNDNtL/KsLvC5urp0tv188fFxsa4tb7K1trJx8S2uLzFysDL89nX3NXCv9DQ2sSAx9vUt8nFvtS0u8fgz7mwp67FwMHV39TNhMzQxsbA0uHHvai4vLK22+3Myt733/nx4e3N1eLX1Nno28i9vszR1O3l6ejngeX359rS0cLL8NfI2tzH0Nzq2tj+2NiB8vfs2uv05+b37fH38drYzdTg6dvo+YvuzYbbzNjX2oD62taA083Wv9DY5NC/yunf2/jX1NTZ0+TM0NjfxoTy68bBt67Lqa24ydDDyLLGvs/Q2dbY3tLazb++tMm7uLSoqLS2q6OntbC6zLLLzurDwvaC3uzm8ODLwtLh7/z64+3zysW2sqGlq6GitKy7q7q608+1oNTYtbmru7vPy7jRxsrJ2c6AucDby9bczbnI3L/Swby02d3gzdfZ48vP3Obq7Mi+2s3FyMrJ19bl1tnkysLe1rm1r7W/ubvC3d398oeJj4Xp8ob1+f+B8vDr5dmE8s3T1tHWxdbr5Ong94L1hYvr/YiKn5mngNfL08qAzKS0wMXJ5tK9ur/VxbTP0dG37o+IgZeAi+fu3e7l3fXv9+rl5ePe2OHh1tTz69DoxNbo8drm9OHvkubahu7Uz7aL/oSC+OXjg/Pf9vj+5d/b3tb1idbk58/hg9zr4tLdzbK218rT4uPk7ev56PHn3+fp5+LR0dzQ2Mjb2/OFytHS5OLP0re+x9K9yMTC3NTNwrStrq2mo6+AcYV8gYttaWB5bXZ5d3t3a25lXFZsdXmKjXmDdGxmeFp+cHZycIh+inSMd39xfH91cnZycXBnbXpxhH6NmK6ejIR7d3uGcXl6YF1fX2JldXp3d2h8i45/cWluiItxgIZ4dGxqbG5siIODfXxnc31paXR1cHJ7f4+Yf3+KfHx7j6aAq5yNj6OoolqXeHt8j4KorqSehJSClISPiY+Ijo6ToKFTW4+eT45MVVOPm4dUU1yVmZWpnYabZYVdYVSOqZaJbnl0goKUepx9nol+b4R/eIaEg2VwaGZjcXeSe4CLgIGOkaKUmIp6iZ9XmKuBg3t+gYd6a4VRc3NteoeSjKhRZFSAallgSkxWWYtSjk1ZTU9PhpGKnp2dk5tUZFpnnluKfIaOgm9oa2uLjJpyjHGalHl7jJWGlFubl3docIePlH9+cYuDdnZnfmNgWnyDVVZpXqZnX5+DmnhjfYRqaWVtaV9hhp6Qg6eUjpefXJeTmpWdkn+VfXRxa2xfZlxdYmdtbWeAbGlpeF1og4xwc4SBZXNzbXqNio6FiYafkHx1dW1+cXR8cnhxiJKJkYyBRU5JUlBIRIdubXN5fHyIjY9biG1fdHd+goFudHJRjo18gYmRdWJpeY2IdHmBaGtxi4yGcHWPk2FjaWpeYGtyfIJybWhiaWxze252lXl3eW9fXmprc2KAZHVuWW5xboZhZXKGdGRiXGBwbmt5gXt3V3qDeXp4hpB8dmd1f3h5l56Adn6NfZeRf4t4gYSFgoOOgndqZXN3do2Ehn+ATH+Qh316fnaBoYt2hH5tb3d/bWyMaWlGh4+HfIeLfX6Fd3Z+fGtubHZ8gnqEjFGDbVWBdoJ/hFOhiIWAg3+OgYiOkoNzgJWUj6eGi4uMiJd8fICMdFekpIWCeXCDY2ZvfYN4fWyDfIOEjIWEjIOIfXR5doV/fHpwcHx/dGhvfnd/jXqNkKuJhKhbl5+WoZR+c3+RmqqsnaSqkZGIhXl7f2twf3qLgI6JopyEapyfeXxreHaJhniPg4SElIuAfIWdjpaYiXV/jHSDc2hjf4OEdHx8hHBxdIKIi29qinh5enh2fXmCeX+MeXSSh3BqZWdya3B6kIugmVRTVk6BiE+XmqNVnJeSinxRjm51dXN5b3+SjpqNnFaiWVuPnVdXaWNvT4J6iJBgk3uOj46Vq452eX+PiH6RkIpvl19YT2KAWYuMg5GNh5iToZaTmJiWjpibj4ufmYKXdIWbn4iQoIqWYY6JXJ+JiHBpvmNfr5WUVJuHnqaypZaanJGfV3uFemdsVYWSkoeVi3V4kIOFkJCLkJSnn7ayqqiml4+EgpCDjn+Mh5RSdX2Clp6OjXR7g4hzeHVwiH96dWliaGxlZnWAdIyDiI9wbF95cHd4d3t7bXFpXlRpb3WEiHaCeHBwgmKIdnhwboR8iXOKc3xxfoB5en16fHtzdoJ3iYOUnrOikIqBgYaYgomPcXBwb25wgYeBf2+Di4x+cW1zkJd/kpyNjYWBgYB5jYiGfn1rfId3eIKBdnJ4eIOHbmt3aWVkdomAj4BwdY2XlVSJb3FveGWIjH54YnFmdW99dXt5fX+Dj4xGTXWAQ35ETUuCh3JHREZvcHGJeGBzU2VMTT5+m5KLcHxxfXyMaIJpiHtzZ353a3p6d1ZjYF1ZaW+IcnSCc3F4dYd7f3JjeI1PhZdvcG50e390Z4FOdHVtd4GLhJhLW0qAW09XQkFNTnJDdD5FPkA/anp1iYyMg4pMWU1Zh1F/eYeTjX95eXSOhohlgm+Th29xf4t+g06LgmRYZoCJj31/cox/cmxabFJQSmZpR0hZToZVUIZzj3Fcd4JoZ2RqZl1beox+cIt5b3h9SnV0enR/eWeEb29ycHhzfnd+gYKHgXeAeXJud1xrh450eIeEZ3RuZXCBfYJ2eXiWjHhvcXOGfoaPg4h8jpCEioV6Q01IUlJLSI90c3h3c3B0eoBSgWlif4KHhH9rbWhJgn1udYKFbl1ncoWDb3F6ZGxzkZKKd36Wl25wc3VqaG5zfIBxbm5pb3R9g3aAnYOBhoJzc4B8hHOAdYB/aHt9epJvc36Ug3Z0cXaFfn2NlYh/XHt9cXJufIp7eGx7gXp8m6GEf4qYiZyYiJB4gIJ+eXyMhXtyc4WGhpuPin99SHaKgnh1eG50l39senlqcHiEeHORbmxIhpGJfYyUhoSNf36Efmpra3eAh4CKlFWHb1V7bXVydEyTe3WAdnSDdICGjHdiboaDgJh9hIiUkJ+BgoOLcFOTjm5ubWV+XmVueX9zdGB2cHl8g3x5f3h/d29zcIJ9f3lsa3h6cWhqdm1zfWZ0codmYHNDcX55hX5uZ3OAhIiJdX6JdXhxcWlucmJmc3GCeIN9j4hxXYuRdHpvfn+LgW+BcnJ0gniAZ3GHeX2AcWBtfGZ1amNceoCCb3p7g29zfIWLk3pzjIB7enV2gX2GfoaRfXiXjnVybG1yaWpte3KEekVFSUBpckSCgYpJiIiGgnVMh2pydXJ1ZnKCdXtpekaGS1F5iEhFVVBZQW5td35XgV5vc3R7lX9qbXmOhXqNg3tcfU1JQVKATHd8cXx1bn56hX15f4ODeYGFdnSJg2l+XGl8h292gXWAU3l3UopyblZTlExLjHd2SYVxgYeMe25ucWl7R2ZzcGJqUX6JhnuIf2lshHh3fXlxc3eHe46HgYqQiYV6eoh6h3eFfpJRbXd8kJiHhGtyfIFueXl2lI+Mg3RscHBoanifegF753oBe5h6D3t7enp7ent7e3p6ent7e4d6BXt6e3t7inoBe6J6AXuLegF7iHqKewN6e3qFe4h6hHsCenuXegF7kXoFe3t7enqEewN6e3uXegF7wHqHe4p6AXuLegF70HoBe6x6AXuWegF7lnoEe3p6e4V6AXucegF7s3oBe+x6hHsHenp7enp6e4V6AXuNegZ7ent7enqGe4R6AXuTeoV7oHoEe3p6e4R6CHt6e3t6enp7i3oBe4V6AXuiegF7mnoCAgQAgLiuu8HDw8TJ38TKysfe0sq/va+dsMS4psqqp6+ruMPPx9rJ0LfHvMvWz9TV0tTb8c/P09nD1+TW1Oni297oz9eutcC60MbPwryjn6+mucLSw8vA7O7YwL6wscrAtcnOtbu6w8fLu8jN14Ls3trEurmsq57Qx7jh5dTEvKOql5a4gOLRsM/d4u3g2dbc8OW574uDxsLN1dHK2NXS1enJ3fLw2deDgO/c1ubn9eLT9ebr9ID359Pi6+vq5oOrtb2IhayrqMnf2fLltcjt5ur2kOqCi5GHgeHvhuLjqpWvtazM0ODc7M67vJ6wm63Fu8Lz2c+4ztnu5vTe3O/m2tvo89PagL3Egf+G++HU94iB74P18+CB/ofq1efY2+7L34nQ3YL1h+nn6ciqrqLHobLB58284uqNic7Oycy/5vPez8XMy9/F0aq2sqq5xubyioqQwtbM0se0v7au7Ojctb3EzczBysT6iPvb8oL499jTycystqq116Wuqailw8DG18rX0tbbgMPBzMy9uMzS6PT97PHX39vV0uLZ1tfsqYrj0Pj4hcDI1dfN1vrU3/j724CAi4LykfuH8/Pg2ePegvDg/PHkz8/U58TFy865v9XVsLivtqa3r52K2c741c7dzIHqxcnv9unm0/mQ4eHJ3N3I1s7Q1cS8zsXKzLu7vsfFxMTHxczSgO/dtrq2x7u/sri6wrK0rMPCs7u8zNWzxtPCx8PIwLrO2Lm8xb2zpcLFv8TY4s3V28Lw9fPoyc3b3c7NzsvP1e3e4ubk7vPL2tXmzcTFzOHSwNnt+e/r6YGB6d7w/enw9vXq9evu5tnV6NzT09qE7uGC+enw9oTs2N/r6eD/6d7lgMW73MbTy83HyeDu+oH49eXW29bf0d3T08eA3srbgMzIxbW9xtHXzsPP2s/Oy+HZ1tDJxLyyvbrWr62vt7Grqq2mqbG1wfDK0ujk4evR8tvg5d3ZwtXZydng/oTvzdezyb6+yrjOs6qjv4jBtaevuqaevrqxprq+vbm/xsO/xs3FgNPJxOH56MjT097a8ODdvNzF0dbl0M7Cw87J0czczMTFt7TexMjKztDF8Pb9/tjLuMOzxsG0trHr5dP8hoyE4YSK5OP7goLs5fXm5eWK7+fk5try1Mvh3O/72PTh6IWA8dbxg/DXuMHN3a+zp+3Eyej37+Pc1djV5Ma/vMjzmvOCgIfN5tLk7f6C9IL+h/v+gPzzgPPI383S3cTc8d7N6tzY74TP6/Tm7s+/trm+ztDW3uLJ1uiE0MjZi9Pc04S80NnT+tXS4Ni+xcrY1uXR3t7lge/o7/H+4er+7YvqjPPPzuPZ8uD9/se93NXu3r7JyMfItMjX0+jTzru7oaysqai3gHxxe3t7dnl6iWxzcWx6cmxoa2Nccox8dJRzbnFnbnR8cHxye2hycX+LgoGAeXqAmXdyeHtteYF3cYF9gYqXgpFpcHFqeXZ6cnJmZG5mb3iDdXdpjI13ZGhhZn1uYXV4ZGlsb3B2a3p9hFihk5ODd3ZraWGQinykqpyIfWlrVlV1gJyMbouTk5aLgnt8ko5rmmJbioaUlJGCioN9fItxhZmYhYZUT5GAe4SOm4l7npGXnlKWjoedrrGtoF1mb3dZXmhnaoOZkamfcXiSgXuDVHRHTllSUYKSWZafcGN7g3eKjJaQn3xwcFZnWGl6c3qegntkcXiMg4lydYyGfYCTmn+HgGhpR5FLh3Jti1FMjlCUlIRTpFiPf5J+f451h1Z0fkqKTWp/fGZUXlV8YniEq412jpdnUnFybWlfjqGLf3l9fIp3gVxpZV5se5mkXWVrg5iSlo13enZzqaKRbXV+hoN6hH+rXKSMnFemp5GKg4Zvd2t0k2RtZmBabGZpbWdzb3mDgHF2fH5sYGZsdn2BeHhib3N5fJCNiYmYb1V/bZGSU2Zqdnt1hKiEhp+ad0ZGUEV5VYpKgYJ5eYySW6GOpJSHdnJ2jXNxdnNkZ3p5X2xncGl4bGxZf3icfnWGdlWXb3GTl4R7apJZfIBzgIJ0gnd4f3NqdXJ0cGJgZ21ramppZ2xxgI1/X2Rnf3d7a21sdGdrYnFuYGVkb31pfYd3fXZ4dGt3fWhtc3NuZX2Be32Ji3mAfGmXmJ+Re4CEjHx5dG9ydouBg4iIlJl6iIeXgX58hpOFbXqGjoB3eEVGdmx8kICGjpOHj4aLgW9ugnRuanFNhXdOkoiRlU+Dc3yJko6kjYWMT3RwlIaPgX93b4OPmk2Yl41/gnyFeoWDi4JdloOQWYR/d2Zwc3uDfHJ+iYF5eImCfH96dnJ1g4OegH57hoB7dHVsbHRyg66KkaOjoauNqJaElYB8jYyFkZewXKaImICZj4+ajp+He3GGX4iAeICKcGaGhnhqeX14cniHg36GjIiTi4ikuqqJkYqMi5iHgWiCb3p/jH1+d3R2dXZxfHJpbGNnh3Z8e3p8eJ+jp6uAdGNsYHNzbnFxqaGOqVhaT3xNVYuRo1VRhIKVgnp5UoN7fIaCnoCKhZORoqyGmIGGUEyNe5BPj4BteYiWeoJ6roWMnqOVj4uEj5KWgXx4hKNrnVRbf496iY6ZS4VMk1GXnlOhnVekf4+Ei5WClKaTgJmJgpRXfJmln6iMgXt+hZOXlJiYh5GfYZSQnGmgppNiboOFeqCHho+RfXuBiYmfjZOTk1abky2Znrmmus2tZZ5lrIuMmIqhjqmlfHCMhp6RcX9/gn5teoh/kICAdXJda29qbICAg3mEhIJ9fHmGaXBubH55dXBxZ1xugnNpiW5obmdweIV6hXd/bHd0gYl9d3Vvc3qVd3d8gnV+g3p0hYGHkp2Lmnd9hHyMgoR8eWtqeG17gop6fXKanIRub2huhnpvgoZ1fICFh4d5gHx+UpiOk4R9fXJuYYJ6Z4WJeWxnVlpJSmqAi3lYcXh6gXdwbnCCd052T0VfW2hva2Z0bmttfWV5iolydEtHhXp2ho6bhXCOfHp+QX55aXmFiIeBRVRibFRacXNziJmPno9gaYN0cnlLZD1GTEhHb3xMgIpeUGlyZ3x6gHyKbGFkUGFRXm1jZoxybVtqcYWAiHV0iIF6fI2Qd3iAWFtBhkZ6Y111Rj9xQXZ4aUaTUId8kX1/jG19UWp2R4ZPYImLeGdwZopte4enkX2Qi1ZKaGdmZV+HnYd9eHp+jXuIaHNqXmhvg4dNVFpzh4CEfGhpZ2WcmIZlbHN8dW10bpdTk3eGSo2NdnNvc19pX2uMYm1taWeBfYOKgImAhoyAdXl/fm1mcXWCjI6Agmx1dnt/j4iFf41pUHdnjZZWdYCRkYqRrYSBk4lsQUBKR31TjE2Jhnt1fXpKgHGLgn52eYKXe3l6dmVrfnhca2VsX2xhYVF1bYxycIV4VphucZOUgXxqj1h/hXJ/gHJ+dn2HfXeGgIWCcm9zeXp8fn94en+Am4dmam6Gf4V1eHd+cXtzh4JyeHmGkXSFjnp8dXhxZ3iCb3iBgHpshYZ+hJOYhYqGcJeWnIx2d36Kf359foaInZGSkIuSlHKBgY58eXd7iX5lc4KOg32AS0p4b3+QfYSQk4qTjo6HdnSJenJudk+KfVKUipCUUYRwdYGGgpqIf4aAbG2TgIl6emtjdHyDQoOIgHqEgot/iISHek6FcYFRfXp5bHN8gYd7bXJ8c3BwgHt1dHFta2p1dZBycnR6d3NxdGttcWtylm9xe3h2f2B6bXF2eYFqd3dqdHaNSYVxgG6Ee3uJfI53bWd+XYB3bnaCb2aFh3twfn54bXJ8enJ4enSAe3Fqg5aGZnFwd3WEendifW95f45+fnd4gHyBfYyAdXpub4h8gH17f3udoqaviX1weml4cmlmXox/aoVFSUFiQkpzdYdJSX1+kn95d059dHN5dIZyaXpzhY9re2duRUJ4ZX5GgHZib4OTbm5ll3J5jJKGfXt7h4iNc2hiaoNZhEiATWt/aXR5g0BwQYFJiJFNk4tMjml4am13YXKFeWV7bGp8R2ODjIaOdWpjY2d0eHV4emlveEdqZW1HcXtvTlpwc2uUf3yFh3V1eoJ9jHZ5dXVGeW5yeJF7jJ+KVYNWlnuBjH2Ugp2abWSEf5+Qbnl3d3Jfc4V+l4uOf3pldXRwc4fpegF7pXqCe5F6gnuMegF7iHoGe3p6ent7jnoCe3qFewN6enuregN7enuEegp7e3p7enp6e3p7iHoHe3p6e3p7e496gnuXeoN7lXoFe3p6enuweoJ7hHoBe4x6hHsEent6e4Z6AXuYeoJ7h3oBe4l6AXvyeoJ7lHoEe3p6e4R6AXuWegF7jHoFe3p6enu8egF7jnoBe9F6C3t7e3p7e3p6ent7hnoBe5B6Bnt7enp6e5p6BHt6e3uGegt7ent6e3p6e3p6e496AXuSegl7enp6e3p6enuTegF7iXoDe3p7o3oCAgQAgLiswcniztPR07zEvMC7ycnA09K5pqmylaSrxcS6wdbC8MXHx7HJwMnVyNjSytDP4djP2M/G+42C++jiuN3JvNfSxr20vsXRw8jJxL7BybK5wNS4tNTNwdnRy83gwMu8w7GxtMLQzdHP3//vg+nPucjQvcDcw7/a18jG07qxtbm0gL2+y93q7Y2X+/PR9enb/PedgfzX9vjXy9bk2uzi0sjd0bK3087F1ePL3szIvdvOy8XW0M3W3c/w4cO94LDo2s2ruc6+0umB6Krp4+mFhIDi9uiKgrHB59npu7a8w9zc1c/L39/Juri0s7m0tuKc9/jTud7Sv+nw9snh3OHd3trfgNbT9omU5Onc1/Xw9P2fj+nf74LR3N3hgt7d/YnQ0cvLycnS3N3Fs6epvs7FvM+qts3Rzc7g4O3J4cbOy8/Eqby6vbXHyb6/0tDRzL3OsL+6t6mzu4/E7LfH4svoguXz8PXr2M++39raz8jT35+fmpWltqGnub6hs7nFxc7V4s/tgNPd6eXTyc7X9erR4Orl39/EyrfOzNfI3Nne9pP739Lc0NPqjfHazOvM3Ojrhf7zgvjV9Nnf8YaFg5CIiYXu29r8yOLQzdfI09LQ1N3Wz7qu5YCEvs3n9fG1w9mvu9/q8+/x2OD49oD4g76/zLC5uLu1vb3U2Oi/z9Pj4sTJ2d30gPXcub67ur/AxLmrwLu2s7m/tryzxc/LxtDB1eHTtLazxL2ir7TAtdnH9O2C697k5eTr5uXq3tDiwbzK3M7V4+bZ5vLp8uPW0dPZyb3CwtPFzNnu6/zo5+70z9rf2Ojf1+bg4fv4/tDG6+7Z1sbk+fDp5u3y7en839bl7drugOHVgMzlg7/Z/YXZytTNz9ng5uPq7uHs9Ozn5crTzOrUyMLDusjC0dne5tvO2d/2gubQ17u3u8DAyMm4uby2xL3Ev9bLwaatt83MyOzP1cbe5/79gtyI29zEyNjZ3eveztrkzMvBtrKxs7zQv8a9naKYrp2Wvbi2t8TGvMLIxM3VucbEgMzNzNP44M729ePZ8unr5t/g3MPU09HNw9fL0Me/xdHCt66+ssHAvrvDwsTn59nYwNnV0tLM0dr01s3i7+za5/yAk5L/39bWwtPX4+L5jOX69/bwgtvr7+zZ2NLTy+v9jP6H8+TOyczGxMfSuL/H4uPk4/j25IH66cjJwsLR7e/tgPOA9OXv9Pfj4YKa8//r9dnRybenztPFzN7e9PHQ+djN1+6P5+jGzfPHuNHFy7Tu59Pf2ej51bmC2NDfhJDq3fSKgu37+LzOvryqusbM6IHw+ePd0tLe1u3yypv21ODYw8TCz+HvzsjR59DX57m7uLjIytPBz8/Tt7auqay1squ0gHdoeXaKd3x6fGdza2pncXRyhIt3a295Ym1whIJ0dYd1mHF2emuBeoGKe4Z9b3Rwgn16gHl1nVpPloF6XoV9d5KMhHhvc3eGfXl3dG91hXJxcHtiX3dxaHt2d3yLanRpb2FmZW12dXt1hqeZWZSCbn+LeoKdjI2rp5SOknlubXFwgHt8iJahmmBnmIlsjYR9mptnV6mMp6yLfYWOhY2KgHiMgmRmdnJneH1sfXV1bIR8enWIh4mXopq4n4ZzhmGShn5iaXtugZZXmGOZi4VNTUp6iYVZU2N2mpakfH1+hZqVjoR8j4x1aGZmZWxmapFpmp15ZYB4aoqIkG+Fg4mLko2PgIB5h05XcHRsaoSDj5ltX5OLnVZ5f3+AT3Z8l1VvcWhoYFtndnJeVlRedYN4c35bYHR6dnGDgI9yh3N8eYN5Y21rbWh5eWtpeHl4eXCIcH2AgnR3fFF+pG57loOkXJ+qpKidi4R8lpGSjYWSnGZlX1tkcVpjbnFXYmdsaG11gnKRgICSmpaAcm54kYlrd4KCgIR5f3GEg415gn2Hm1+dhHV6cHWFVJF7dpV9iY6LTYh/SopukXmAk1dXUlxVVVGIeniXbop7dXlrcXV1fIaCf3FsnFpYbHmKkYxebH9hZYmOkYeEbXeMikuVUWNrdl1hYGdiaWqChZFjd3eDgWZodXuSgJZ9Ymdna3F0dm9hcm1sa2psZGlebHt6dH52ipWFa2tmd2xSXmVvaIJ5oZVTjoSFh4iUjIuQinqRdW52gnV2fYB1foiAioF4e4ONgnx9gop+f4OPhJN+e3+GZmtwcYF8dYqDgpyenmxhhYBsbGKCmpWOiY2Vk42efnKAiXWKUImFgHuGVWx/mVOIe4R6fX2ChoiMjIGHi4SHiHSCgqCHfnRtZGtscnh+hH1teH2VUo17gG9wcnh9h4l4fIJ2g3qBd4Z6cmBncoWHhJ2Kj4aUma+tV5NhmJyGkZycnrKij5ylmJmVhoeFgIOMgIeJanRxhnNlhXx1b3R4dHl9f4yOeo6LgI2MhourlICkn4iBmI2RkISIhnOAgH57b350eHJtc4F2cW59c4B8d3V4dnGJjHl2YXl1d3t+hIqkioGMl5KAi5tPXF+ji4aEbnd2fniLUHWJkJGMT4yao6aRi4KBe5SbWJhSloyAe4OBfYaVe36HoJaQg5eShFCmmYKGh4aSqaeegKFUmoiVmJuCflBhlaCUoIqFhXhtjpSMj6SfsaSDoX1zeY9cjI96g6R9c4uChHaknouYlJ68nYRlnpWiYGaUk6BdVZShnnaJe3pofoqNo16hpJORjZWjoLa2h3W3lKCdiYSEjpOUe3Z7inyHkWlpa2pzc3pyfHyEb3Fpa3N+dm13gH9yhISVfX15emV0a29ufXx6jI92ZWZvWGVtgIZ1eot6n3d5emp/en6GdHtwZm1rfnt6fXp2nFhPl4R/bZmRjaGXin90eH2Je3l9fHh9jXp3d4NraYF6b4N+fYSUeIZ9hXh7eYGJg4F4gZiKUo2BdIONeHmNd3KJh3d2f2liZGdlgG9udX6Ef1JahnxfgHNkeXlNQoFlh4xzbHaDeoeFfHWIfGJleHhvfod0hHp0bYN1cWt6dG94fnSRf2hdeFiOjYZudYd5hpNQhkuBdndHR0RxfnJQRk5cgXyJYWNocISBeW9rfnxoXl9dXWJbWnhbhIRmXXl1aIeJjm+CgIeIj4iEgHNvfktSamtkYHRvc3lXTHV0i052g4OET3t7lVNxdG5tZ2Nxf4N2b2p1j56ViZBjZHV6cmp0dIdvhnh/f4R8anV1c2x5dWdqdnt5eHCDbnp/fnJ2eU14mmJpfmV+SHiJi5SMfnduhX9+dG58iVtdXVtneGRrd3hhbnh/eoGGknuYgICPlZJ+dHWAnZR0foWFhIZ3fW5+fH9se3Z5iVqZg3uDfYKWWZuAdItve39+SYyCTpV5lXp9iEtHQ0xGS0l9eXugeJeIf4FyeHdxd4d+d2hijk9OYnCCj5FndYlnZoaKjIJ/anKIikyTT2VtfmZyc311e3qOjJJnd3mLi3BxfICXgJyEaXFyeoGFhXpreXR3eHp+dX52hYyDe4ByhZOFbGtsgntjcXd9cYh6mpNRjYOJi4+VjIyOgnCGb2x2hoCFjY+EjpCHj4F1dnyEd29xcHlubXKAeYh3eIGIaHN5eIiCe4+IiKCfn25ki4dzcGeGnJSKiYeRi4eZe297hXOJToeAgHeKVGl+k052aXNpbHN3fn6JkImRmZGQjXV+dpF9dXV1dH1+h4aEg3ZgaXGMTod5fGhmaW9xd3psbnhxe3N3cYF4b11gaHp6dIZvcWNrcYaDRXJRfH5ocHx8fJCCdICOfn98c3VzcHSDe4WFZG5nfG5ohoF/d3l4enyAfYSDZXRwgHN2cneWf22Ni3ZvhICIiICIhHSGiIeFfI6DiH99f4p8cmp0bnt5c3V8eHiTloOCboV+e3t5eXyPcmZxenZkbHo+SUuEc3d+bHR0fHaFTm5/goB3Q215foZ3d29tZX2ATIRKh4N3cHJ1c3aAZWdwioSAeI2JgE2fjW5vbWx2jY6HgIpLind/f4BmXkFReYuEkn96dmdaeHZqa3l0ioRkf19YX3JNdnllcZFsYHRpa1yGfWt2cnaPcFlNbmp4SlFwdIZPSYaVkGp+bWpYa3RzhEx7fm1pY2h2dZCWbmKYd4V+cG5veYKFbGtxhXeIkmhnaWtzdYB4gYaOdnVpaXJ5cnF+tnqCe7N6AXuaeoJ7iHqCe7R6AXuFegh7e3t6enp7e5l6AXuVeoJ7iHoGe3t6enp7hHoFe3p6envAegF7vnoBe4d6AXuIegR7enp7hnqHe5R6gnuTegN7envCegF70XoBe4R6BXt6enp7pXoBe6N6A3t6e+16g3uKegF7hXoBe4t6A3t6e5N6AXuLegF7h3qCe5d6AXuUegt7enp6e3t6enp7e4x6AXuLegF7pXoCAgQAgLy7tLa2wbuyv7y3p6q2t7CzxNnCvcXFqaelu7+vw+X8gLG5vcrNzt/y4M/dzr/Yzt/Tvrqns8yB6dfLuM7awtj30ryvt62zvbWwsrXB29TQyra4wrrGx83N0/zb1drUxbivsrfBy8fJ4/Py4ODEutG0qamepL3LwrHFrr+1zMfNgMjOzMzfyOv2gebh4+Pl+o/Ywe7X3oTk4dTL2dHLxc/ajfOEkevQ2+bWpsLZgOTa4+PDysrW0Lvg7MHMv8THgt7DtKqisMfasLi8zu+DjMLdz8aH3rS6vOffyvHr5NLgz73a7+2Ax8GA3+OUkqOgpefCwsPGw8zP2s7Q3d7LxIPigNbM+4WD8ob59eP/6fHazsPT3IHs4uuB8Nva1Obmw8vGzMPL0Mm+t7u7tdeys7ipn6O51efh1NPX2cfStbKwpay0wLXT+OfP2+T+zcPKyLXAvsOz1tPXrLTXgdj0z+Px+oLSxtHZt77RwrHLxp+elp+ltpasrLK108zh++7L69rDgNXd9tzOytrf9efu5/D+597O1sTHxcvDz83j7tnQucPZ7ojY1s3k8dPciIT8gYmA3NnGx8/a+oTjz9XX9IT1h/Db3Ofd2dXZ6fX249LL6r6itsy4tbGtrL+wt7baxueHi4PFyMzM2eDj1rvDxMi378nH0bXS2MnQ4vD29OfK5vTtgOryyL2+xsHAvMzEsbG9rcW6wMHG1tnNycvU6OnKrbfE0LC3nq/Az8jM/trl5tHb9drh3NyB7bHw383N0tDL3On69fPs7dXXz86B5cvOzcrJy9DS3t3OzsnW2OTt69fo2uTn3vTmif3F3fbf3cPR3+nY2unz7fjr4s/S3en7jOjWgM/RzNPy6NyF3eby2tzv6vvf4ubq0t7d1OjZ0dHLytLXw7S56NnFzdni7MjIxby9tbCyrqqutLS2vse9u7bKx9vRtdjN5I3Dw+XKyrns6djj8/rr6bjWzej+yLy4tL6/ss29xLvBsbisobqx1qmfv8PYq8i7ya61vMHHgvLd276+gM3gzKq3uMG3xMvVycixw9fT2Nzu7t/90tXN1cPSz8/Pu8W+qKq3wb6uw8fR1szPw9LUwdW/t7/D68Kzt9rZ/fT//pWT6ury7NDNzsLfzc2K5tjT6uHsgP33gcnN6fqC9YT+8Ozk5OP4y9nkgeDY3evW/ICJhYf+9rOtx+XHx9jkgObp9PCM3vPk9O/m4NPVxLfGysvS2d7u+MTi4cG+xOO60dj434DO7NLph+fz2NaB9Ovcw9j/xc7N2+Pk74Db5u2F9dbQ6PPZys7Krs3A6IXg6NLf6dvs3uHn4dHa7eOB/uTLxd3pyMHN4L6+zc7Fx8LG1c3Ivr29tKGfsJOupK7DgIN+d3BvdndsdnR1aGZubmxtepB+d3+Ab2tnfIBzgKS4X3V/hIuJipekjXqFd2d4dYd9bGxcX3BMhnpvY3mFc4ywj3txdG1ygXhwcnR6j4R9eGlnbWBnaHFye6aHg4+HemxmZWpzgH59laKdjI97coVqY2VcaYKWjHuOcnhugHl+gHmBhIKQdZaaUYaCh4eRomKFe6WPk16YkYZ8in96c3mAVpBQV39oeYR8VW+FVYx+goJyhI+gmoips4OFc29wT35nWVRQX3KAYF9lcIlMVGJ2cW5di2tzdqCairKqoIeTe2yAkIpRbmZMf4RiYnJwd5Vzc3R0cXJuenN0gYF8eVeOgH1wiExIg0uEiX2UiJWBd3J6g02KgIpRknh4c357YWplXl9kZl9fXGNfXn1eYWlgW1dme4mKfn+BgnaEc3FwZ2ZtdGuIqJZ+hpClfnZ+eWx9gIJwkoyLYWV9UoCbfYmgrl2MfoiRcXWIeW6Kh2dnZm90fl9zbm1pe3J+iYJpin1ygIiUsJCBdXl+i3x6eYGLfn11f3B4c3x1fX2Ql4iAcHiGm1qAeXqGioSAVE6SSE1Idndtb3F4l1KFc3t+k0+KUol3eoN/e3d9jp+cjX59l29ca4RxbmpnZXFjZ2V5aIZRWE5eaWxsdn6GeWBsbm1fk3FxfmeCh3h7jpWVkYNsgY+IgIuQbWhteXt4eIB4aG13ZnVnbW1xgod9dnqGlpp7XmFpdl5iUWN1gXyDsoyWkH2DlHd9cXNJgVeTi3t8gHhye4GMhoGAhXV5dYFapIqQkoqEgX93e3lsaWFvb3J4fG16b36DfJaNYKJvhZl/emZ4gY+Gi5CYlJmRiHd4goiXWot8gHd7en6Yi4FRfYWUgYCFfI14eoOIcX5+eot/f4B4eYWNdWVslX9qbnh+iWlyeXR6cHd5bWhyeXp2gYx7d299doJ7YIJ+k193eJR/fXiYmZCYqKmoqHqOkqzFgnZybHV4c4p8hnt+dIB2bH94k29miIqZaoV7hWVrcG91VJ2Nk4GDgJCejGhzc3RtdnyJfnppeYZ+e4CHiniSbHJueG19eHyAd4B6amt2e3ZlbXBxeW5zanyCd4x9eX+HrIZxco2FppumnlxejpigmX91eXCFcndaiHhwh4OLT6SmVnx8k5xQkVKdi4qLiY+mgYybV46HjJOEp1dcWl2xp3V3jqeMi5abgJeYmI1Yg5WDi5OKh4GIf3KCi4qTl5qsvY6pp4SBfptvfYCdhk13lICPV5OhjI5Yop+NgZKxipOUnKerrlyMlpdWno+NnKeKgYd9a4V7pGCXnYSUm4+pn5+hnIqWp59gu5eAd4aKb2p3hnFzfHtzeXR1gIB6cG95dmdleWV+dXuPgH98d3V0eXVqdXJyZmhxdG50gJN/d3t5ZmRkdH1sepqtW2x1eISDhpGii3aAcmZ6d4qCb29fY3hUmIuBdpCag5u/mYN3emxue3BqbXB4k4eAeGtudWhwbnV0e6OGhZGNgnt1eXt/ioB5jJeTh4p4do12bWxeYXJ+dWd7ZXBpfHZ3gHF1cm94XX2ESHNwcG5wfUZgUnhlbk18gHVufHd1cXZ/VI9QWIZ1h4yEXXSAUId7g4RudHeBfGuFjmlrYmhyUot3a2VgbXmBW1leaINHT1tvaGJVfVxhZI6HdJmSiHeHb15ygHxGX1xCbm1QTlZYXXZgZGhsanBudW5ufH13c1SJgHhwjE9JgUl/gHJ/c4BrZWJzgVKUi5NWmH18d4WGbnh0b3F1eHR2cXl8fp13eH1vY1pmcnl3bnN4fHWEdXNyaGhsc2p/m4lwd36QcW97eW58fX9riYOAVVhwS3WOb3qLmlJ5b3uEZml8bGJ+fWBhX2hufF5ycHBvhoKSnZV2lYNygIiUsJGGf4iNnouJhIiUh4R5hHR5dHhqcGx9h4B7aHWMpF+Jf3l9emZ0T0yLSkxGfX90cnR5jUp2Z21wik2JU5CBhI2HgHuCkp2Xh3pyimZWY3ltbGdjaHltcm19aoRLUkhZZWhte4iMgWp3fX5zpIJ/h26EhnZ3iJCSkoRrgZCJgIyRb2pxgoaIhpCDbXN/coFyeX6EkpaLgnyFlJt5XWV1iHB7aniDjYGDrYeOiHZ/kXp/dHVKgVGMhnl6f4F8hY+alYuIh3R1cXZSkHt+fHd1dHRxe3psbGd0c3uChnSEeYWKf5eKWpxrhJqDf2t/hpCChYqRjJGJgG9vfoeZWYp9gHmBfIKaiHZKbHF9bnJ8eo9/g46Se4WGgJGDfnx4fIuUgnB5ooxzdHt9g2Nrcm91bW1yaWhtc3JweYV4bmZzb3x1XYF7k2J1cYdtZlx4dmtxhoeLi2BydpKZbWRgW2hrZHxvdGpvZW9nZHVvkmtmhIuZbIN8h3B1fHt6UpWEg2dpgHmHeFpnaG5mcXaEdnlldoaDg4yZoI2rgYmCi3qIf4KCc3lxZGZwd3Zpc3h+hnl8coGBcIJvaG1yk25cXHNtiHyAfkhOdIOQj3lxcWl9aGZRemljd29xQ4uPS2pkeoZFe0eFdnh5e4GYbnd/SnFsdH9skEpQUFKbkFlYboZpbHh+gHuChH1Pb4Bwd311cm56b2RydnF2eHiDj2V7fmRhXnhTY2iGd0ZrhXJ/TYCIdHRLiYFyYWyKYmlobXh9gEVkc3lJi31+kJ2Cd3hsWW5hgkttdFxsc2yDeXuBhHuDkIhRnoJvZ3p8YV1sf2xzfoB3fHRzfoB8cniBfGljdV1xaHGJoHoBe5Z6AXvQegF7hnoBe4V6AXuKegR7ent7iHoBe5F6AXuNeoJ7hHoBe5F6Bnt6ent6eoV7j3oBe4R6BHt7enuLegV7enp6e8J6AXuGegF7wHoBe4V6CHt6e3t6e3t7h3oBe4V6A3t6e596g3vTegF7lHoBe5t6AXuXegF7iXoBe716AXu0egF7x3qCe4t6AXuGegR7enp7hHoDe3p7inoBe4Z6hHuOegF7n3oBe4R6AXuEegF7jXoFe3p6enuNegF7j3oBe6F6AgIEAIC5vrOxyKTDycO+v8yprrHAsre7sMCpvbiyvaGppb/D4fXgyczCy8vO7Pbz3ci908q8w8LAs7/E4+rnw9Pf4Ni+s7TUvL6/xs/DyKzIzdW/u7yyq7jBxbKovL3r1dLg5u7dtKepq8jY1sfEx9Tq2rKTxMShi4yfnarC1MG6ysG6zoDWzMPIxL/U+PLa7e/I2IuxhPqYut3689zL0b+0nKWgo5yvyNqtw83j6NfQytnRxMjM8vrZ2drU0IeT/dHl4Pfmw7S5rrm35cqm2+uEzMPk6sXH78+r0bjMvKfL+Iy/29213KqvtMTf9+aAloiKiJiXluqWiM3mzbijydia3LXM6YD2vtjj/c/P5ov75e/3wdCDj4TvzOzW38+H7ICB+/be0ebu9MnMw8XBpau4wsPK3NbJsajHyPvgiurS3cK7rMCTlKixwMDi54Ha56XErbDIvMG5vLnK0ce+2vH0ua+0jqSosaitnZ2ntqmbtqyppZ6mr6yTiq6lr9PN2fniwszDqYC5vbjO6Nrl+Ov25fn/7djzldTSurzw29ba89bX49LHzuzpvdjJzv3TiJ2VjduA3PPv18y+yef11N3c1dLe7N/n9v75goKC+uf9jdnPwtHEwdDX1vOwu8fHu8LDw8riiIj51ubb3tbQ1sjIwcC8w9KwxOzE0tbm0fLq3dbUzcfV0IDp3szIxb7NtLO+vL3Nz8LAycjYhNPe0M2yxsnS1M7R1eS+0sm/xr7E2Nf51NHI29nAytrp5M23y+zbwdHK3OLy7/Xu7u3249Xc2sLQwbqvxbi0ydfX7uDaw9HY8fbe1dzwg+zn3PTU3fDpx9rTysrV0svG18zv3+Xe3O2FjunW2IDY07zD48G78O3esMXK2t/l6e/59/Hh1M/3jd/pzdvQ4crIyeDSzdTh2eHdzdPNzMS1qLbAsaixtLu/ucfNzNbNwsW6usvCwcCyuuXH38HX1NfRxc+vxcLXgrDSssHFzd3iy8rIu8TG1cO+xbmsrKafuLSvwLS6ub7P3+H16fPLuIDJ4MCztbm4t7O72PbCoNzn5/j79+fO7tbU4Njn8sPAxsrKx8DS2NDDs73K1ryxtsbA0+DRtqGvwredpKjMgeD27oSJkO2H59bRuNLZ6tnv4Ofv897ghujy9Ovp3u6Alfv00vD15+b4q/nu7Y/s/vD6/IaWhPWP0La5xLzWyeK954DTztTx9dfj5e/n9Ojr3tjO6MvJ5YfQzOjZ28O+wsr80ObTz9PP4cOM0//g5N7Z84X16c3CxamBzt7T2sfyg4CBhof12MvnqNXHysS10eXp+vjR5uDi3+Dfg4Dn5ejsgJKGy9Hg2MW41evw6dLQxsLO0cDSt8vBwre1tcOsq6q5xoB7fW9nclpyeXd0e4hobW18bHFzanloeHp1gWlsanqAmaaSf4V8g4KDnaOZgmxkdXBvdXRuZm5viI2IY3J/g3lxcHaXgYJ+hIqAhm6HiY93cG1jXGJkZldUaXCYhX2Fio9/YVxeW3WBfnFydoCWhmVHb3JWQ0ddY22Ck3psc2lkdoB9eXZ8cmyBmo94iIxmdVRxTpllaomspY+EiHxuW2RgX11qe4JdbneFjHhsbHpxZ292k6WMj5SRjV9krHyEfY+BZV1fWF9hjXNVfodMbGR6hWx4noBpkn2MfmyGpGNwh4RmhVVaWmV7jIBMX1ZXVmRlYohhWHSKcWJRdoZbiWp9k4CXbHp4j2ptfliYhZCcaXNQV02Ia4Zzfm07fUdIh4VyaHR1elhgZWxqUVhebW14k4d8b2mGgbKQX5R8hXJxbohYXGt3fnmamViJmGWBcHGHfoF8f3mJi3tviZqhaWJqSmBjbGZtYV5peGdbcWplZmVtc3JcU3BlZ3l1epB6ZnhyYoB3eXR/kXt6hHZ8boKKeGyJXHJzYmaVhoGGo4yKlH5xepOUb4p6eaKAWWlaVnNHc4eGdnJocYuYeXd1b2p5g3l/hYR8RkdJioOZXYB3dYR5e4WGfY1eZ3FxaW9vb3SJWlePcX56fXNxe29ramdjaHVYbJt0hYSagqKcjYyLg3mEgICSiHp4enaBbG1+gXqFhnVrbWt6SneIfXZkdnN4eXJxdohmc2lqdHB5jZCkf3hvfXZhaneDfGtYcZB/aXtveHiDhIiDhYuajIKVlYiVioV6jH5zenp9inxxWmJgcntoY2N3S4SCfJ6AgpWOb4KBfXqDhoF9ioajlJGHf45RVYF1e4B5gXN2i29pjo2BXXB3hoiKhYaJjIR5c3aZXYyXeYmHlH16f419dXl/dXh2bX1/gIN/dHuGenR0eYOGeYKGgoR3cXFuboF+e3podJmBiXaSjJOPjZR2hoiZYHOPdYJ8gI+YgoF/cnd6iHRvfnVxd3BseWxmcGNpZm99hoijmaODc4CBlXtvc3Rwa2Zqf5trUIOKipuelIduhW9td3WHmm50fIqKhn+OlI2AbXR/gnBpc4SBn6iYh3N+ioBpaW2JV4+ek05VVINPiIKDa4CLm4iakpmhmoGAUIaPloqSjJVQXpeQdpeUjI2ad5uPk1+Ro5KWl1VeVqRnkHmAi4mUjpmDo4COiomam3uDgH9/ioKOhYqDqJKRqWOWjqygnomFjIm4kp2Ee392hGthfqKSlY2Qo1mkl4V7hXBim6mZoYahUFBSU1ehjoabZ42Egn92jp+dqKKGlo6PlJmZXVicpKekVmdYbHSAfHJofo6PiHl2cXKBf3CBa3lxeHRzdYh4eHaCjoB2em9rel12fHZzeIJnbXF+c3h5b3xndXVvfWBpZXl8l6eTf4B2f3+Am6OahG5nenRweXhzbHZ6mKSeeIqcoI6FgoCXfnhxeIB2fWl/gIl2cXFqY2hqbF5Wa3KYg3yGjJePdG9vbYOKgm9vcoCUi3Bag4RlUE1cXmd6jHdrdGtmdYB5c21wY1xxjYdxgYRcY0haP3FPRGaJiX52e3BqXWdkZWJwg4xneICJkX90coJ6cnyAjZ2GhIF4cEpUkWt7fpaLdW10bXdtk3VQd39KamV7iW12mnlegm+Cclx4mlxkenpceU5VVmN4hHNCT0REQE1QUHNVUHCHc2JQbnpNfF5wh4CQaHh1jGpqeFSRf4aPXm5OV1KUfJaEjHdBg0lKkJKEfoKChGZ0eX5/anR7iIeJkoV7Z15vZoR1UoR0gXJtaHtUVWRsbmuFh015iFt6bG6Ge355enWBhm1keo2RZWFpUGVpcGhsXVxmdmRYcWxqbWtye3xnXXltcYuFi6GJcH93ZYB5fXeIn42OmYqPfI6QgnSQY3l8aW6Wf3h6kXt8g3ZufpiddY95dJluTVlTTHFIe5OUhHhqcYiNcXNva2p9iYOJkZSMS0xMk4mbXYN5b3tubnR3dIdbZnV9dXp3cW+DUk+CaHt6goCEi4B+fnt1gIlqeqN7hYGTe5aRh4iGfnJ9d4CLhXd5foOTfn2Jhn+LjX90dXaIUoSRg3picXR7e3Z5hZ1+koqEiH+CjoqadW5peXVncXyIgW5Waol8ZHZzf4SSkZeKioqTgnSEg3SDd3Foe2xlcXd7i4B7Y2xvgot2cHSIUYmBdZR2fJCMbYF+enZ/fnVwfXWUh4qAd4NMUXttdYB4hHV6knFnjYh3T2Rte4KKjJCWl5CAd3iVVYGPeIeHl4J8g5SFe3+FeX57coGAg4B1anWAcmxvc3h8c3h5dnxwa2xqbH56d3ZgZYZsclpybnZ0doJne3uNV2V8YGpob3+Hd3l4bHJ1hHZ0fnNscGloenh1hHh9dHiFjIugkpBzZoB1inBkam1ubWdnfpxuUoSOjp2jnpN8moWDjIWRm3FyeIB9d3GDiIJ2Z257gnFnbn14kpeGdGFufHNeXl5zSniCcT5FRmxHfHR0YXV9inSCe4iKg21tR3J6gXR8cH9FUIJ7Y4eJfHiFaYJ1dE9zhHV7e0ZQSItZd19ib2p0bHZigYBvcXSGhmt3dXZ3g3iAfHxxjnRyhE1vaIF2dWJhZWKQbHxqZ29peWJLb499fXZ3h0uFd2RUXUlOb3prdWB7QUNHR0yMfHOLWIB0cm1db355gn1hcWxucnh4TkyCj5OTTF5RYmh1cWled4iPjXh2cHJ7em6AbXx4fXZzcYBtaGZ2hf96j3oFe3t7enuleoJ7kXoBe5B6AXuMeoh7A3p7e4d6AXuMegF7hnqDe4Z6BHt6e3ubegF7j3oBe8h6AXuXeoR7Anp7lXoHe3t7enp6e5R6gnuzegF7zXoBe5l6gnucegF7vnoBe+R6CXt6enp7e3t6e496AXuHeoJ7iHoFe3p6enuFegV7e3t6e556AXuSegF7h3oBe4Z6AXuGeoV7lnqCe4R6g3ufegICBACA0dPNv823ycS5vK+xr6Syw7G6tcHDxbrAt62qlai0qsC5tsfNtb3CurPO4cXptLS8vbXOwa/q5OPI29Dazd2tvc25y9XAp6rlybfD07+4pam7s6yvtM3HtbTB74vf5+avu7C8w7CsxeDPyMfGscm+sayVrLykrM321ObWw9DG0smA0tXWv7/Mzb25g/PS6tLy9ICGg//ng+XH1dbEvqGmrKqnr8TaxtT86/3ZhNvx5bngxvflx8XT5+Pi/+rQgOy5w9/O1MPO38PItdXfvb/M2tOtt+zcupbl88qupNXw1NTTw+v0zdnajfPcs8PB4ePxioeLmZLfyuHcx+rF/dnay+aA0Mn33pPs4YDZ6ObY2OXu2b/PzvzEvPu9tMPW597Vya686I/n3urkyMvAu8KzvK+owq/Etrvvo4Db7+rhur/FvOzm6MGDi5XBtL7Jhcu2n5uZq7/GvL/CxdPexry3upaToauzvribvamXl6qnrrOto6mkn7C8wODVyNzM0rjH2+SA7+/R46br0vHu3/f32dPw8crez7m90snS3c7o49f/x9uG7/DT19vW3u/o3Pno8P+njYHtzODS5cXv6O7O9vabhPOKgvXi7+X36ujp6fCA9eW44/TMv8TA1Pn4+5SSm6n/39/l2dfgzcWyzLu7yt7e1NvGxOnC3Nrh4+vd39nX1uaAytPR2MnFr7i9yMC50MXFvsDr4efe3cLT08/C0+Pi3OGJ2svKyMjUw82/xce6wc/e1s3o2tXY5uXs39nZ8fbx+PWFlPWDgN7o0ey2vLS3w729wdrXwMnY34XGzN7Tzdfx/Ora4Ofh1tHYzsTP1fPMzoP/i9iD+vnb6+/m14Tp6tyA4uTS0MLEuc/cyLnQ0eXh4NrG3svK0sfJ5uvcw+Hm5fbY7cvq6uHW4eno8dXFxb7DyZGtx9DPvcK9u7iyu7+5wMLPvse7zdq95NjVztvz/NXC37LKpaKvzb/Jw8rG1svJ987YytPUrr+5v+Llw6qkpKyotratttnd7dLG99HIw86AwtvTys7XyNO3ytf38cjNzd3r6Pfk29LU0NfOvcjS0Ma80NPQ3YvYxp+0y87FxcLC2dnpyca/xtfpwbW/2dDU+veD/pWC59nI0q/D6cu6uvnx3MfBz9eI0taF5uyM8/rs29a/0dTY2IDk34KE+faG+/WBjof15MnEsr264+7sz4WAg4DS/P7OzuDg6oGBion4/+zZ4dqC9JHv39/Nycnyjezj5oDc2Nvv9Nbty+aA5ern+tDDvMnmydXv19XS3ePX9vj6x7nH2cnQ29Lassrf3eXt4dbm5N/vzZ3w3uH9hpmBid7Z0eHWxubRw9TCxcKvs73L2svBxbe7vbnAtr66ws+Ah4p+cHhkdXRucnBwdGl2h3N7c35/gXp+dXFtXWtzanxybnt/Z29yaWV8i3OQZmJsb2h8bl6PgXliaGFvdI1keot4i5iEbmqgf3B8inp1Y2VxaGNobYB9a2RymF+JjI1hZ15kalpZboJ1c3JxYHpxZllLWmlUYIeli5GBaG1lbGeAb3R5bW55f21kU5Z8kHugoFNZVqWNUolteX5xb1xfZGRkZ3iAb3iai52BVIGRhF1/cZCIf4CCjIWCm4ttS4peZHZuc2pzhm9zWnZ9YmJscm1SX42IcGebpodzZY+ignxvZIaLaXZ6VZWGYnRzkpGeXFlZZF6GdoeGdpJ8qIyUgpmAf3ifgkKPi1eCkI+FgYiOdl9rbJdiXZpiV2V0g3p0blRadE1vbHZ6aG5pZGpdbWBhfHGEdnqqeliHm5aNbnV5cZ6ao35bYnaDbXuGZIpyaGdneYuTiIqHiY+UeGtlZUpLWWJseXRagG5ZWWZjZmxqY2ljYW94epOCdYFwc2JziZOApKaDkEaIaYF4bIOEa26Ij3SMgW90hIKIjn6akYKjcoFWk5aDh4eJlJ+QfpF/hI1oV0uGa392iGuJhohng3xPRXhLRH1sfHeIgX6ChY9MkYpqjp15cW9qdJmSkltZYW+YdHqAfnuCc3Fgc2Zianl6cnhzdJp0ioaLkJKIiYmEhJWAfIR8f3Z3ZGx1fnV0kX54bW6NgoeAgGp4enhob3t7dn9We3Nxd3SBdYB8fnttbnF5cGp/c2tve3+Ddm94jYyGjotPW41QT4CRg5pudm50fXd3dIV9aHJ6e09hYHBlX2d6hHt3gY6Pj4uVjoaHjKWBgFepW4BVnJ2Fk5KJeE6LjIaAkJaIiXyAcHuEdWd2fImJi4h7jHhzeGlpf4t/aHyHjZmAlXmSi4J2f4OCi3p4dnqAiVlofYGHdXt+goB4foN6e3yDfH16jJmBmJWOhoujt5GAl32Tc298lYaSjI+KjYB9nH6GfYKBY3d3dZiggGtkZmZfaGdlaH2DkndsoIF1doCAco2EfoGGc3pgb3iUi2pucHyLhpKFd3FwbXBuYnB4enl5ipCOnWiaiWd1hoZ+fn59lpilio2HiJ2thnqBlYJ/mZVOjVRGf310e2F0kYN7fb+ojXt1foNbe3pRiotZk5uTgoBqgISMjFiNhE9PmJdVnJ1WX1iro4iDeISFnqmrlF+AW1V9mZhvbnp2hExLVlicoZiSl5Bbrm6soJyMhoejYZ+SklOLh4qho4ufgptWm5qdtYyFgo6jjJyvnJiNk46LpaevhX+Qm4+Qlo+Xd4SWkJSZkYqYl5Sfg26clJevXWZZXIl7dISAepqIeYBvcHJjbXSAlId7enJ1dnB7dHp3fIuAhoqCdX1odnVvc3Bye3KAk32EeYB9e3B2bWpmWGdvZnpzbnx+aXJ2bWmCkXeVbm92e3SHd2icj4d1f3aEiqJ0iJR8iJF7YF6UeWt0e29tX2JuamhtcIN+aGFvkViCiIljbmhyd2tleIl8eHd4aIR/eXBhcX9kaYmni5WKeIB6hHqAgYODcGxzeGdeT5Fyf2aDhkNFQoJuQ3Jjd35yd2ZscnFwbXmDcniXg5F3T3uNjGmNeot6enuCiIF7jXtnQ4dkan94gHaCj3V3YHt/ZmRvdXBVYY6GaFyPnXtkWoWaeXNsX4CEZnJzSoZ3U2Nhe3mCTEtLVlJ+c4F8bYVsj3l4cIKAb26NekGHgU2AjZGCgYuUg3KAhaJ5dKxvZG16ioaFgmlrflF2doKHeoJ+eoN4gHBqfGdyYmCFXUl0hoSAX2VoYIF+g2ZNU19rXmt2Wn5uYmRmeIeOgIB9gIeQeW5pbFRVZGhweXNZfGtYWWtscnt2b3d0coCHh6OShZB/fmx5kJeAo6aElFGVd46Hd4uIa2uFjHKIgG9zf3Z8gnCPhnWZbINUmqCKgn98hJCAbYJ4fY5pWU+McoZ7g22Kho5xlo9STIdSSoFvf3qGgICIhopKjoFeg5d3bW9wfJ6XkVZRWGCCaHF9gYWSiIV1iHp4fo2Mg4R5d5lwgnx/gIJ5end0dYmAdIF+h32BcHuDin14lIiDeXqbjpGGiHJ+fnlsdH6AgI1hko+Oj4iQgId8enRna3R+eXOIfHd6gX+Fd251kZKNlZRTXI5PTXqJe5BncGhrdm9vboR+anN9hVVsb3x0bnSIlIN6f4aHgX6HfXeAhqJ+fFOdVnZRkpN4hH93akmFhoCAjI+DhHh6a3iBbmR3fZGPko58kH13e21uh46AbICIjpuCkXePiX5zfoKEk395en2Bh1drgoWLfIF9gH51eXpwcnR6cnRwgo54j4l+cXWFlHFlfmuDY2Fth3eBeHt3enBwkneHfoeFY3Vzd5aee2loam9tdXNuc4iImXxtm3lvaHCAaIR/fIKJfYRnc32akm1zdICPjZuOgn9+fH96anV6d3Fsdnh1hFmAc1VkdnlycWxsg4KNcnNucoeWdWtugG5sgnpAc0c8bW5nb1pti3dqaaGSd2RfaHJVcW9LenVPfYN6b29hdHqFg1F9cURDfHtGfXpDS0WJhnNwZG9vhouMcEuASkdmg4diZXNwfUhFT1CLj4V6gHdHjlSIfXdnYmN5S3ZtcUNzcniRkHyQc4NLhoiJnXdrYmt+Z3KGc3BlaWtqiIuZc2x7h31+hHqAXWd4cHV7cmh4dnKBZ12Be32WUFpKT29mY3d2b5KDeIJwcHBgY217jX93fHZ4d3B6cnFwdonbegF7rXoBe4Z6Bnt7e3p6e5R6AXuHegF7iXoBe5h6AXuQegF7iHqFe5B6BHt6enuaegF7k3qCe4x6g3uEegF7uHoBe5t6AXuOeoN7jHoFe3t6e3uKegF7jXqEe8F6AXugegV7e3p7e5J6AXuXegV7ent6e4d6AXv/eql6AXubegR7ent7kXoHe3p6e3p6e4p6DXt6ent7enp7enp7e3uLeoN7iHqEe4Z6A3t6e4d6BXt6enp7iXoBe6t6AXuEeoR7n3oCAgQAgNjw3s/LxsnMxr6pr620rZK5pMHV0ebKtc25uLDEraSxuLrBvdDLyqy3xbOzycK+qs7jzcHQ1+zx0c/JurvHvr+wtLnC0ujW28m7rL3HuczGq9Ckt+HY7fPT4ezO8OrvxrOmtNyzt/Tu8emxuLCppp+fj6/Es763s7zMuse5razAgNjVz6bQ4+XOvrbIvdnW58WFlvzY5PDe3tXNtriqt5qep7e0sNjcysnX3eHStO7XxujMm9LX7tHDzNvl9vz83+WgxOK4uda7wb3Fu7fBx7e0sb7Kq6O7s87JoLzW39SB6/ja7vrmzuP1g87N0tj67Ov2i4SPjYKBh4DDv5qDmaKOgJzrhoWIgo7R3sbH19X69vjf1tS6xtHh49HBwbjBsqGWt8rX38bg7Pf81bS9xcy8nb/M0bWnvaOHjfXngvHX4O/1+diO2qLk6rLO1eXR38i/uOfu3OGuu8zGyM7Evb60q6Wot7i0vKijr5+utayuqK21wb21s7fM0sbPw8LJ0IHygNnRi8zH1eHd4v2I5Nfu9d284NrDysCxxNi1vcvO4+vr5NqJ1MTG3Nnc29ba4f7skf77gt/q5YLp3dXlwszc5ezvhofynIGV+v7Lzrfl3765zYz2yre5usS01vv15OLh6Pns4NrH28u0scfAusS/2+3o3OL06Nra5+Lf6+TH1s/XgNTHwba6wrWvvruyu7/KycvX08LQxMvX09nO7dC+1+LbzNDU1L+8ury8wMLFtLzNx9PU7uLUy7/E2IT0y+Tz5u3t7ID+jf320dDKyMfDw8W/v7LQ1cG4vsDVsLTzx9fH+5WA5uXX1NHYwtO/weHuxefl7Ondm+vx0NjZ9dXS8NrNgOnizOP/ybTIwsHS0d7m0NHS1dvJzMPK2e3Y2M7Py9Dc8+Ha29nHzL3MyeTQxbm8usikqra6w7vHr6qrt7+5u7W5vbKzpbDDw9XHvsHHyMWxx8Smx7+noLbEx8a8xtC6sbzCtrmxuba8wrXRxt6upqiztrHQw8bUwszQys3G09nmgOr26c3L0s3Dtr2/5oDs/LmxsbO5x7G9v8TF3+DTydC3w8rm1Lzp28LesYX/1qLEvrGosq3Cs7rx/uDe483Q3+KIjoCDj4qLj97Ix+nd1uHPuMXCutfs68+U1tvn8YCH9ffOzN3hy8XS0Nra2PTn++rp2djr0uXdz8LAudvp0YbygJX/2drX2+7q3P37/vqIivLs3enpzOT989jTxNK/1c7Sk9/y0NTR2efe4cbZzc7ix9q98+PO2bvfg/Hl09Hc0cLq+Pnc1MPU09bR27jG4s/Q4ebZt8XSvdTTub3V1fCI9dzm3eSHhOvm0LHVv7qltsDN3ezOvq+rvsK5wcvW0cfCgIOZhHtxcXR+gHpugIaNf2eLcoWTiZ2Fb4J2dnGCcHB7gH58d4N8el9lbl5fc3BtXX+Sf3J8eYiKbWtpYWV4cntwdHZ5ip2OkoF2bYOFfY2FaopjbI6EmZ2Djpt+nJSbc2RWYHpVXIiChYRdZWFfY11USF1sXGloY2x6anFhVVBegHFzc1twiY10ZF1za4F/kXpYZqSFiI+BgHlwY2libVpdZ3NzaomLeXZ3goWBZKJ/ZoeCXouHlXdqbG5vfICJdXdZaYtmZoBrbWdtYF5lbV5fXmt3XVttYXh4eWyAhHZLfopzgIV3aHqTVHZ8e3+ZjoiMUEtVVFBRWlZ5eHBZb3JfgGGMUlBRTVp3fmdpd2+Jgn9xZ2hZZ3GGiHZkZFxjV01DY3t6eGBxgo2XcVplcXxuWXWKknhrgXheXZyXWqWQlqWioYJXlGuSoWyJiJ6IlIV7dZ2jlZ5wfouFg4d9dXRrYV5hcHFscmJeZ1pmbGBhYmVocm1mY2F1dWpyaWd3glqfgI2FVXdwent1d45PeHGJnI5xmZN8enBnfZJxdXt3h4mLhIBVhHl9lJeTjISKi52FW5uZUX6Hg0yAdW54W2Zxenl4Q0RvU0FUi5dyeGiSiWVhcVaPbGJkZHNkgqGViIB8gY2DeXlsfnJiY3FtZ2tlfo6MiY+ilIWAh4WDjINygH6EgIuBeWx0fW9pdnx3f399dm97dmt4b3eBgYR4kHBedHp4a3F1c2xxdnh3fXx2YWVwaHJwi4F2b2dyglaYdYeOg4KFf0iSWZiQd3x2dHVwcXdyem2HjXp5eHeDXVuLY2pfillIhI6FiouQgZOAe4+cdoiDh4R5ZJCiiIuIn3x1j39xgImRgZq1hWl4cHF4f4WNgIODhYdxbWdocoJ2d21zdX2NoJmVlZJ+gnd/eZOHgnV+fIhhYG9rcG95bWdteIB9enR+f3VybHiDg4eBd3R3dXlsg4Vwj4VvaH2DgX+BhYhvbXR0aW5ra253gXiPhZ1xaWhua2B5cXN8bXd2cXhye32EgIeYkH1+hn53Z25tj1KUomljZ2dpcmBoZ2trgYR+dX5vgIKgi3yon4uifma8l3WKhHx2f4GWh428xKSbnIV9iIFWVUZKU1JSWIF5gJyLh5OHeoV8cIOSlXxde4GGh0lPhY13eZGVhH2Eg4eBgpmPoZidlpSlkp+ShH1/eperm2izgHO5lYp+hJGKe6CdoqVeX5yYkp2ahJ62s5+glJ2Ll4uIZoWUdX17hZuTlX6QhoOVfI1ypJWHk3ecZbGllIqPgXeZra+elYOSjZKSlXmCoo6OoJ+UdISKdYyJdn6TjZ1do4mPhotYVpKZh26Nd3BfaXN+jqWOfm5ygX1zfYSKgXZ1gIWdiX54c3V+f3xseYGJfWWDbnyIgI13ZXlwcG9/bGhze3Z5coF+fmJqd2lof3x6aY6ijX6Ihpabf358dnuNgod3dXZ3hJSCiHZuYHF7coJ2YYVjbpGHmJh8hZF6lo+UcmVcaIVgZJOLjopibGxtcm5nW3GAbHV0bnmIeoJ2aWZ0gIaGgV56kJV7amByZnhveWFHU4FmcHlxenlyYm5reGRob3dyaoaHcm5ygIR9ZKGEbId4VoOClXtxdXh4e3yOeH5Nd5l2cYlxbmhuZWVtdWNkYWx7Yl5vZnx5X2uBhnlMhI53hI5+bYCVUW5ycnOFend8R0JOTUdHTkhdVlFCUldJgEt0RUZIRFBreGlvfnmUkJCIgoJyf4eXl4RxcGt2a2JZdIOFhWx6g5Cbfm57hox9Y3yHh2RUalxWV46GUpB5eoiBfGZDY054gVdvdox6iHdwcZihlJtseYuHipCIfoF6bWhkc29pcGFgaV5venJzcnd9iIJ7dnSIiHuAcGp0fFSTgH97Unp3hImBgZZRd2uElYhvmJN6eWtZanxganJvgISIg4RZh3V3i4mDenB5fpGHXKSfVIeOiFCLfniFa3aBiIeFS0p4V0ZUipBweGeNiWtjcFaVcmdpanZng52PfXdyd4V/e31ziYFwcIB8dXhviJeRi4+gkn97fnp2f3picmx3gH54dW95h316h4R2foKIhISTj4OQgoeJgoJ3kHZnf4uKf42UkYOBgH95e3RtWF1saHl5lYqCdmltfVCPcIePhoiMhkmXWJaNdXpyc3dzcXVuc2aCiXdzdnqKaGuccXhslltJhIh5e36EdIR2do6ad46Lj4yAaJGbgIB7jW9qhndvgIiGdY2qe2V1bGt2eoKMgYiKkJJ5dmpufIx9e3Z4dXmElYiFhIRyeG53dpOCfXN9fIlnanp8hYWOe3R1fYB4cmlwb2VkXmp4eoN4bmhsZmZXbm1cf3dlYXZ3cm5tdn1qZ3BzbHJtb3F6fXKKfZBraGt1dm+GfX2GcXl3cXRrdXuCgIiXjnh4g4B5aG1vklSTo25obXBzfWp0cXR0iIh9cXdibm6EcF+FemiAXFSdf2RybGdgZ2h5aW2bpoyIiXNwenNLSz0+REZIT3ZudpCBeouBcHZoXW6Bg21TcnV7fENIeIJqboaOfHd+en1wbIJ3hXt9dHaJeoqCdG1tZ4CLd1GLgFyYenNxfI2JeZqUk5NTUoSBeYB+Z4CTjnh2anVmcmlqVHCDanBxfIiEhXJ9dHSEa3dbinVgbk9wTIh/cGpya2GFmJuFfGt9eHx6fmNph3Nwf35zV2hwXXFwX2l9eopRjXF1bHBLSX+JemaFc2pXYWZzgpd/dGdpd3Zsdn2AenN1/3qEegF7jHqCe656AXuYegF7hHoBe4l6AXuIeoh7gnqGewF6hXuvegZ7e3t6enuGeoR7uHoFe3p6enuHegF7l3oBe4x6CHt6ent6enp7inoGe3t6e3t7inoBe+d6AXuIegN7enubeoJ7knoBe/96mHoBe516BHt6enuSeoh7kHoBe4R6gnufegN7enuMeoJ7kXoBe5d6AXulegF7hXqCe5p6AgIEAIDM49Pdy9nf3dO9ysSysrS63sDex7nUwMbU3Marw7apvtrRxsjU39n7v8vRv7XGt7CsucvCvMfQ1s/DzbvR1NfQys3CvMjN7Mi4prCuup7Ku7jBtLTI7t3Dv4SZ9fXl5tu1uNHc2diJ+4OG2Nja1dC6upqYp761vbeVtK+zuMmxtIDE4s7I4eyD8IGS8NTp8+fU3/jh0+/08v7k7t7FqLDHuJjHoK7CxMTPw7TH28nY1tzT2caE89u6wc3Xvtn6+tzoi4GA76S1s7G3sMis1Me7u8jAvcOsvcrVzNvewtjHu8fUwtXoydbZ1aPc2bvF1ca50s/Z5Pfm3dnegZKM2ersyoDmj/D4goaC5bzKzOHNw+DY4tzT48fY2Nbp8+/76uDmu9LN1cG70c3A1erFwMG+scva6ue/oq+qy+7i4+PZwtHTjpKV2rXEgujo+s7dytfc2e7+4M3En6qqucCrxKqxyra/qL3Lxa6wnb2eoqu2nrK+wMC2t8TBtL66vsTCuNjQxYC+sbyru/G7uMrbvMjM27K2ssHdzs/g0t/43+TD5+LY1czB49XUw8HJts7G6vSA5/Ld0Li91NTd4c7f0crb6OXv8Ofv/oeKjILmwbzFv8O/trrRzLbL1sXv7e+O8//78u/u99vaxMO7wMvFuMrX1OfL5MjUysTX2dPj4fHS1O7o94DkzNTUwc61qLm2wLCrqr/k4My1vr7JzsTQ9+fq0cvl29HJxc/Fur/Jy8LLurvGxuWEiZCg3NXfvrb817rSytvd4Of4++vd69bY3c7Fw8+8wrqyqKiroLe8vqe+3bvP2uTk4url2eHf2czS1uDh6OfL4Ov13/Dt6tTI2YuC2+bs44D21d/tgbu1yb/K4Nrky9nJ1M/Cz7XHxNTy4OHZ19PIwb7azsjIyePg4eXIu66sqMHItsG6xLOxr6GnsbOvrK7Ft8Ozzc/Fy9DM1Mi+yMPX9MLnu6q7tLWjvL2wws2ypbSkrLCtrMenxcDFtsrPxLS4wtbHw8nRwcjX49G7xs/EzIC5uInfzO/a187JxNr16N7by8XNtMvPu8q+4Ony4dXPzsLP3NnBxtjF07O4g9b9sqCxtsCvt7S9z8jF4YfcxdPdzduFhKOJ/YDu8ejf4bqy0dzs5Nnb+NHqg83b2vKDhPrx9ezv89HPyerX2tvm3fLf1M3R0bm/xsTF1cLz3trygYD4/MfO3fX55/rp29jnh+PGzuLL54jor6TtxcPB1sfV5P3m+d2xvczg1dzZv8zT6+HG2NLzzMHS78rN5fb05MrU6dT80bjQz87PubyvzbrIyt3k3tXH44P17tSqz9b1utbalPLWl+qA38TWg9rZ0dG84fHs8ObaxcrKz+Ph6M3RuoB4inmEcH+EiIN1h4qChIKKqIyfin6QfYKQl4Rvh3dxhJiIeHh7g36VZGxyXl5sZF1bZ3Vva21yc25mbmJzdXp3eH9+e4J+kYVmXGpxgWqPfnN9cG9+m45zcVhml5WEiIBjaHuEe3xVklBQc3J3e4BpaVBKVmdkbmFHYFhYW2hWVIBkhXNzhZJUjk5cg257hn10gJaDe4+TkJ6Kk5B9aXCKgWOFaG57e3uFdmp8k3qDfHp5hXpaoYtoaW1xW2+GhG17UE1Qk1psamNnXm9RbmRaXGxobnRkcX2FfY6KeId1aGl6ZnSAX3J2ekt/g2RvenFebmp0eIyJhIiVXXRtm7GpioCaUJuZT1FNgVxpcIl3aYJ3e3Rwfmd4eHh9hYCNenOAYHZ9iHJrgHVqfY9raG1rZIGUop56YGVbdZGNjpaMeoqSaHBpjXyETYSNn4eYiJaZkaKymIqAYmxse31tgmxyiHd8aHmCfmpwX3VZXmVmUmRqZ2ZeXmdmXGRiaXZycZePhoCBc3pnc6BubX2Mc4GEk2xwbnqVhYSHdn2eiplzlJGDhH1zjYaHeoCHeIyEm5ZViZWIe2VmfHh8f2x3ZltsenZ+em5ve0RJTUmCamtxbmxhVldpYlBncWGDgoVUgo6GgH18h3F1aGhlaHZwZXB3cYFrhXN9eHWGgneEfodrc5OQooCTg4uLe4h3a3Z4gHVyb3uVinZlaGlzdm11kn19Z2J0bmRiYWtraXJ5gniCbWduZnxITVdndXB/amOkgGl7b317hIOTlYuEioGDiXlycnt0eHp3b3Nya3l9eGBvi2lwfIKAfIOIe4eQjoaMkpiZoJt+iI+YgpGTmIZ1fmBUeYCIg4CVgZOfWHdugHV8jIyVfY2JmZGBhG13cHeIe3pydHd1cHGQiX98gZWUkJR9cWllYXZ4aXFxdWZqamVncnVxbWx4cnZpgod/gIiCinlzd3GIq32jfneIgYJzjYZ4iZV2aHtwd3h1cIZogoJ/bYWUiHd5gI59dXp/cG55hnhpfYx8eYBlY1uCd5qCf3Ztan2Ui4WEfHuAbYGAaHNsh4ibk4iAgnuFkY98g5eLmIOIY5+/gXB+hY6EioaMmpCKo2OVdYCBfX5KR2BUnFGZlYyKk31zg4mYkImNnHeMVG99dodKTZGNlZCYnoOBgJ+NioydkKaako6Mk3+BgoCCkIWnmpu1X4C1r39+jJyii56Ufn6QWo5ygpaCnFyffHWnioqFmISQm6uUpI5ncYGWioyJc32AlIl5hIKihYeWuZeYqq2mlXyJmo6vinSNj46Xh4F4mX+MiZyck4d7kFWdlItxj5y0gpOQZ52AZJNRh2+BVouIgH9uj5ydpJ+ThoqHiJqXmHx9aoB+k4KJdn6BgXpqenpzd3V7mX2Pe2+BcHaGjnlmfm9mepGBdXV6hoOjbnyHcnKCenBufIZ9d3h9gX53fXKGioyHh4Z+eH54iXFjWF9jcVyCcG16bmt/m4puZk5aj458fHleY3mBfX5WkVBOdXZ8goh4eWRgb4B6gXRbcm1wcH9tbYB6moWAjJJUiklUemZveW9qdYNuZ3t8eoyAgnx1Zm2IgmeJZ214dnB4aFtrgm58e3p7gG5SlYVqbXZ/a4GUkHR9T09SnWZ4dWxvZHpefndsbnZtbnBha3l/eIeIdod0aGx7aXiHa3eAgVSFhmlufHRicW53f46CdnF2RVJPcId+a4B9Jn2CRUpJgGZ2gJaCdYuEi4iDkH2NiIOHiYaShoKOcIKAi3Zyhn9yg5V5en57bYKRnJlvVFlObYmCh4x+aHV2UlhPZFdjPGdxh3GCdYGEf5KlkoV9Y21ufoF0h296k3x9ZXZ6dGNqXXVgZnR5Z3uFhIN7fIaBd4B5eXt0bYqDeYBwZnFldKZybn2McHt9jGRoZ3OKfnl/a3OPeoZkhIV+gX12kIiIf4CBa3drg4NIg5ePhHN1iYSKjHmEdmx9iIGIhXh3gEdMUEuCa21wbm5nXF51cGB3gHGQjY1XhIyFe3t+jXqCeHt4f42FdoGIfo52jXiCfnqIhnqCeYBkaIR/koCFd4GEdod7cH17gXNvcIGjno6AhISLiXx7moOCdXKJhX57fISAeX+Dh3yBamZxaH9NUVtigXuGa2affGh/dIGAiYaRkYR6fnV7gnRwcHhvc3FuZWtrZnuBf2t9lnR9hYmFfoJ/b3d7eXB0foqOmpmAkJufjZmXloZyeFhNdHuJh4CUfYuXVW5oeW53ioiOdoV/kYl8f2hyb3yUh4qGhoZ9dG6HgHV3eJGSkpeAcWhkYXV5bHZ3fXJ3eG5zfHx2cW58dHdphIh9foaBiHdxdmp8mmqLbGV1cnVpgHttfIhrXm9ncnp8dolngXx5an+JfXJ6hZmHe3x+cW92gnFfbXtxc4BhYFd+c5eHgnlzcYackIqHfHuBbIGCanRrhoaUiX5zcGhxenZhanZqeWFnT4akaVhpbXRobmlvf3l3kFiFanJ3cHRCQFZKiESBhIKBiGxjeX+EgXV1gmV7TGd5dYZKS4yHj4uUnoB/fpyIgXyGeot6b21we2htcm1tdmmHdXGHR4CEhFhec4uSgJGDbmp7TnxicH9shEp8X1Z9YmVjdml1f5N+j3pUYHSFgoSDam52hntlcGyBZmVzi2drfoWAdmJxg3add15zeHiAcW5mfWhzcYKBe29hdUmCfHNfgYeeaHp5WYZtV4dKhGyAVYmAdGxWeYaHj4yGeH56fI6Ni3h6b8d6AXuReoJ7i3oEe3p7e5x6BHt6e3upegF7jHqDe7Z6g3uFegZ8enp7e3u5egd7e3t6e3t75noBe5Z6hHuSegF71XqEe8Z6gnuIegF7/XoBe6h6AXuPegF7hnqEewJ6e5B6AXuEeoJ7oHoBe416AXuGegR7ent7vnoBe4p6Cnt6ent6e3p6enuVegICBACA1/fawr3TxuPq1sbEuJqvvdDj2N7Sydm/zMbAvMnNtMbM18/T2cLI19H72dGxr6SaoKWnsdnM1tjX1szMyM/IzMbCytXv+NrxyczcwZqm67u5qrLOy9XeycK58/Hy5LrZw73GydbGnYaezqG2zcnJ3vGS9s3K0s+8u7Cgrpm4u6iAnL2+x8Lc5PL3iYiA8t2B7+Hv8t3l9f/+8obfx8rXwsarsrCyyMq3y9LRsMK80+iB2PrIzdPUzey/58fO4ffu3cOv1/DHtaaoma7XvunW0sPGvNb767/QyLrAwNL938Ksnaa3qLyxqrOUkrSboquyq7LSooWL4c28sPfO+czS27WAub/F19a+yPr83tXM5dfKzMzuydHD2d7hv7zR4t/mzcHBzM3a5enY0MLo5eDWwrHJtcz79ta/v8TE0+zRx/ncwdXUwsHM3P308vztiufGy97RzoDZsJmhnbfRsrmxusTuwr20x8KuusHYvKWtssHCvsu6ycjRwbmztbq4wK23uMaAwsq+vsP5t7bIxdzL0c7Dxa+nx7nE1L7WiIfh1eKG0OXMzsrIwL/ZyuzW1dnW5cuuo6ayrba+ubi52Myz0f376u6R6fD+h/fhybqzwcHs1tiEzczD0/XS1c3q/Pb43PHeyd/LxMbAvK2qws7Q5tLf3PPP0cXBz9zm3Nng2eqE5cmA5dDDwLK7vcDEvMHD0PnWwsPDvtPg1L26wsr34tvi29rd3+qI8OPSzNbFzNSGguXY7JbvoOLj1cLFvL3J4NHIzLnT4+DZ2eXjysfi4cLAw8jVybe5wb61x7C9q7e129bY2+r41tfWgsjB4tjh7t7R4djh8vnw/vfZwdDW4dHX34WA79ve8cbK3Ira0ubj2cm+zOPS0Nm7s8bS5NLW39PK1MO1y8/Mz9LSwtnSyaSxsKSuq6qtzNi7n6udoaSPnKqvtLKy54fDxOy9wb+80L7DxvDg2qqyqau6v7m71LHgtKOWqaSzrq67t76+ycbNqra4sLXE19zZy9HR8uC8vcbAysGArcLGv77O3si/tautv+rb08nE2vbg8uTk6Ofi8eXa2c7K0tbA0OCBxNbOv8efxb3Z2am2u7i1pJ3QzNbFtLC0usHYxcjp2tXm4u726+fht+XUzYKEp9Dl9faM/IXq+Orq4uHRuLyiuMfy39/W17rU2c7fv8zP49vJ7+XM29fr6ICA5fnAgIWolYaNkovw7ozw5uTT6vqQ9+Cig4boxszZvLjs4+DOtL/Z4NmD1LW518DTzMHb0dq+6s/I6/rjg+nNwO7i69qlxeHSzMa30M/H0dnfiP/x5czg5c7Azdj1gNjM1+bp08e18PWIytPT6O/wiJOK6+Hj0cW2wrnR4dfP0L+Ai6WIdWx/dZGZiYGIhWp6h5Sik5aJfY13hoB7doKCcICDjIGHhm5wfHWUdXFbWVNPVFlZYoNvdnNxcGd1bnt6goB7g4mYl4SQbXKLg2Btq3d0aW98cnaBbmljmpqcj3CHeXR3d35zZVJob05feXZ1f4NYjGdlcHFiY15TXE5laVggTWhoc2t+iI2SU1JLj31PkYOPj4GKkpygllOPgYmWiIyEboB9e2uAhYJodm93hUh2oHyGiIBxjlx4aWp5jIZ8aVl8knFhXF9RYoBnh3NuY2VddJeMa3t5b3Z2h6uLcl9SWGVYYlhQXUZJa09UWmFUVnFLTVSIf3Jxqpa3kJugfnhwfIaCaHWkoIF9cn5wZWZogWlxbYCFgWRjc35+hXp1dHh0fICLlYZ9cI2Ih3twZHhqfa+sjnhydW95k36ErJmBjY17eYKLpZmcpptekHZ7jYKBWY5pWV9edoZ0fHR+ibGGgHeBe2d0dIttWl1bZmVgZVtmZW9mXmBjaWlwZW9vfXqCdnuArHFwgXqNgIF/e39xaId3f4JsflhWgn2SWoKUgIiKioCBgZmKpYqJiIaUg2teXWhlam5saW2AcFdrj4dzclBsd4pNin95c3B6eJZ+ekxqZ2FxknZ4cpChmJZ5inVed2lmbmpsX11xdXN+bHFsfmZlYmBtdHpubnRugFCIdpaHf31xeHx6fnJ4fIOhfW1raWd3hoBxbnJ4moF2e3JrbGp1T4CEg3d8i3+LjF5bjn2JWoRlfoR/dnp1eIich392ZXZ+gnZ6gYVycImJcXN8gZOKfYOIiH6Re31veG2Denx5fY91dnlWeHSPjJSdjIGOgISVlJGenoRzfH6GdXqHVpSJipdyd4VbhoOUloqBeYeah4WJbmZzeoJybXZsbnhsZHd9e4B+g4R4jYSAYG5tYGZfX2J9i3Jla2RpbVpdZmdnameWXH+Bm3Vzc2t9a2t0nZOSanxycXqAfHmOdqV9bV1xa3p1d394gIOTjJd5hoh3eoKOjId8hYKklnZ0e3F2a1pocW9td4Vya19XW2yYi4d9eoqljZeAfIR8fZCPioeAfH+DbYB/k16Jm5mQlnWQi6OkeISKhoBwbJmUl4NyZmtqc4Jub4R4eYmKlJmNiX5ijImBUVFod4ublVSNS3iDf4mIjH5scGFxf56MjIeJdI+Zj6CCi46ckoGgl3yIiJ6mWp+rgVRXbmFRWl9WiYpWh3t+boOOUpaQYk9VlX+LlIB7pZqPe1xlbISPhFt/ZGuCb31/dpWSmo2wmIqksZBVlntylpOhmXKQo5OPjIOWloyOi5RhppOEeYuVjH2BiaRWk4aMj457bl2LlVt1en6PmZZZW2CdkZaFfnF4comYiISFeoCLp4p2cHxwipOCeHt2XWp2g5GEh3pxhXGBfnh0fn1qe32Ee4GFcXaFfqGFgGtrZ2RrcW50k32Ae3x8cn5+ioeNiH2AiZmVgIlrb35yUmGib3Nqb3x4gYx2bmSVk41+XXhpZGlqc2ddTmRxU2aAfHeBhleRc3eEiHl9d211ZH6DcIBjgX2EeIOKi4tOTUZ/bUeHeoCBcXuAiY+MSX11gI6DiW9ubm99emd4endcaWZ2g0d3oXyAgHVqh1+AcXaEm4+DcGOBlXhrYmZYaopwkH99dG5kdpiLZ3l0anByg6KFb2FUXWxhb2deaE5NcVNXXGNYV2xLTlF9bFxSg26Land+YoBgPWhyb1hllZiCgXiJfnNzeZV8gnuQkYppZnaCgYh5eHqBfoWTmoyCd5eTk4Z9codzf6OcfWlna2hzjXFwmYJpdnZiYWt2jYWEjoNSfmRqfXd7Vo5rXWVje4t3em96hKuAdW93cmNzeJB3aXN2g4SCh3qGhY2CeHd5eXR1ZGpnc4Bpc2pyd6NycoF7kX1+fXZ8bmmGeHt7ZHZRT3RtgVN4jH+Ih4R9fpSHooqAf3+MeGJdYHFweX59eHSIdl90mI96elFsdolOh3pybGt1eZqGglF2dHF+nYGBd4+dlJB4h3hnhHd4hICDdHSHiIWPe4B+jXNyb297hYZ4cndufVB/bICLfXZ3cHiAhYeAhYmPpYd+gIJ/kKCWhYKBh6yRh4+FgoSFj1mZkIF/jH2CgVhVhHaJWodkhYmBc3hycYGXhXx1ZXZ4dWxvcXdpbouNc3J6eYV+bnN6fXiMen90eXOIf4B9gZBycXFRamJ7eIOUh3yNho+fqKWvqIp4fn6FcXaCVYCThIGPbHJ+VXt6jZCHfXaEmIaFim1meYSPfXmGfnyCdGZ3e3h7fn10jYeGY3FwZGhiYmN9i3VocWlwc2Blbm5tbmiTWHp7l3h6eXGAbGRmiH15VWhkaHN6dXKFa512aFxxbHx6fIJ4e3uCf4ZodHhsbXeGhoN5fnqWh2RiaWJua4BcbHRsbHiFdG5kYWV3n5OLgXqKo4mUfXh+d3eIhYB4bmtwcFlmdUpjdXNsclV1cYiLYWtuamVXU4B+hnRnYGVocYNubHtvbn17hoqBe3ZYg310SURXX3WGgkuBR3N/fomGi39vdmZ5gJ2KiH1/ZX2DdohqdniIgW+MgGJoZHJ3QIBreE48QVlQRE5TTX19T3txc2BvczxlX0E3PW9hbXpjXod/eGdQV3J8d1J7YWN9a3dxZXxvcmeIcWB7iXBDfWldgHeHfVJwiX57eXCIg3t+eXtUiXdrX3J8c2hyfJJNfnF4fX9waVuWnld6foKOjIVMTlKNhI5/eGpxaoGPf31/eeZ6g3uIegF7l3oGe3t7enp7inoBe5V6AXvDeoJ7jHoBe8l6AXuGegF7xXoGe3t6enp7o3oFe3p6enuKegF7rHoBe6V6AXuIegh7e3p6ent6e7J6AXuYegF7h3oBe7t6AXviegF7q3qDe4R6A3t6e6N6BHt6enqIewN6enuGeoZ7j3oBe5J6AXuUegF7i3oBe4p6AXuGeoN7jnoCAgQAgO/t9Na+1tvY5M/Z3u/Dta7nxdfl8Nzv0N7QxM7o0s7LzMXJvb7g4eDUieTb3+u2vb2yxci4u8O9tbfK0d3b29y1uNfP1Njd2PzT0ru1u9a2vcjJqpq5tLe7q6+wwLXLz93k+PPd0I7wh4CbvdW4t7GwzezKr7K1ybCnqq6ivqzCgNuGw+PZ09fU1cPwi5vh3s7kgP3Nxr3JxIPfxpq3ormjorKuvNfW3Mjj2MXY1tmp8s/qibS0wODmvaDE4/zY34/TwtPxx7avsr/b6dD1goLfvcCGzIPEvam3wLGyttbn7765yKStrrShpKOxtMTI3Pvnvbiy3O7XwaHXm6OyutHTgP3RxsK7xdHCxePoy/7i69G50czM0cGztLC1wsvV1f/VvKG45dzG0crT3eTAxdfO/snE0cfR4tf9/MbogePZueHaw7fO29HHsrijsc+1x7nOtsjZsKSjnrW9u6Kvu6uyu9K+wb68rqyxqqe1r7fA08rTy97K2b7P28O4r7+nprihgMPXq5qps6+6z9Db6Nq8tM6lsbzF29rh8+bn7IGHhZWA04S7ub/Gu7uwrr3FzsKro6KyvbTdu7DYz8Sz1MrM6uaA94OD8oHr3766wMHD0s/pwcLL1+vq78jg2M/q8d3X3NPM2szEwMvJvLrMxtTP4dvazMy0tc7y3OPO2uvu9NmsgMKh/r+8z7Whs6uxxtTNx7m9wcPL49GzxMzU/Nzl59Pr5u2Rn4D75OrR0NzMx9Pd0N/ezvjJ2dzRyM66ubrK2MrG7cLR4Nrn+8XT/enI5ezR3eHdzsvn6dO4oJnG3MXTyMTX4N7j197X3trm6uPO0svV2tTL3Pba0trW6Z3c1dXZgOGE4+HezuXr2cPn4e7W2+XQuMzn7dbV2NvV4c/g7t/UydbPzce+vsbhycGtvK+bpKimtri6xrmYjJjEv7O0stDD3c/JxcHOtb3TxsXQxuu3y/DY5sS3vcW6wtm6us7Dt7evw7O7wd7FwLqzxNbl287Cvs7DuLO5wtTb38jd2sq9gLGvvb3L183Mz8bAyqzW3tTcwsjMyMbT3Onn7/jw4NDf69fu9OPW99DhyaC5n6LHw6z888yxr6vLvs7h2sjG4uvUvsnFvLnG2ejo5eqA+OGA6cmFm/r99ImK3d/h6eDwgYqM98+71eTg6eXZ1Pjd3tbow96A9ef3i4qK1+T6hYrygOLn1ZGCi5Xv3fXy7Zik6oLcz4KWvayg29fKy+Ld4ID76ebW49LFyuDw5d3PyffHmraD2cmpv+HL3IbCw/LKztjd5/jwyMjiycK/rMDf7+bw54iM/PTG0Ni6x8ysgOHI59rjxeSn79C41d+27fXL09bW1NC3xMa7vMjIzeLQzvzkgJiQlnVecXp4iHuLnK+NgXyjgo+VloKUf4p/eICceoOAgnh7dHGNhoVzUnVucn1aXl9ba3NqcHdyaGh5f4WChohnaIyJjIeLiriAgW9sd5d2eIJ6XEleW11fTlFWYVpxdIGJnJN/d1uFUE5AWnZeYltZdYxvVFtfb11ZXV5UaFxpgHtRa4Z9d3t2eWaOVmSGgnuRVad4dGd1bFKKeWB5bIdxa3Rte4yJkICbjnyJhIBfkHCNVmFiaomPaV9jdoxye1l2ZXWUcGVkaHKJjXOLS0l2XV1QcFR1cmd2d2xtb4WOlXJtfVthYmVVVVNdW2Zqf5WFYmBdhKOTg2ygbG53fYyGgKOEdm5jbHtsbIeIcIR7hXVoe3x9iHRmZ1tWXmJpapd+eWJyk4RwdHB7h41ycYB1p3NygX6IkoeopniXV5KNd52ZiHuKjYR0Y2hVYXhhcmV4ZHN5XVZbX3KBfWt2gnd5gI17fXNwY2NkXFdhW1pgb2ZrYXBib11vf2tlZW5gX3FdgICUa19sc2tvf32CkYFsa4Rib3uCkYF+kImRmFZaWWhYj1x+foOMf4Fzc3+GjoFxaWd1gXeVb2GCeWtddW1xiYNMiktKhEmHg25vd3h9hX+Ta2drdYGKkW+Ff3aJlHpydmxodmxoZG1tZF5pY3FseXR2amZRVWyGanBdZXZ9hnFUgGptom5ufGtdb2dreIF3a1teZGVtin5oeXyFp4eLi3aCgIFVZEmTgIx+gZGGgIiPgYyEdptxfoJ+dnxscXN9i3lzkGh0enR8kWFpkoFohpB+j5KTi42qsJ2DaGCGkW1wa2d3goeLf4yKj4eZn5OAgnuBgX53iJZ8dn16jkl/gH+JgI5YlY2IeIiWhG6MkJ+Rma2Vg46goYeGfn93fWh8jHtyanx4dHFwcnSOf3xqdm1aW1hYY2RqeHZZWGCIhHVwaH55jn98f3x+aGp+bGtzaJNlfJ+MnoV9goV9gpJ0dYR7cHNugXeCjaSKhoJ6hpGgl4x5dYZ3bG12f5GUk3eCgX1ygGlre3uFj4aChHZtdmCJlI6ciouPi4KAgoaEjZiUiHuOmIKYn5CKrJKmk3iOd3WXkXe+tZB1c22QgZSmopCGoqSOcXd1bGh0hI6Tio5LjXtNiG1TZqCfkldYfoOChnSCSVFUknNmf4qHioF6e5qPkpCkgZ1bq5yfXV1ago2lXGGigJSVg2FUUFiQgZaPi2BphEd2bU1aXmhmf3x3fZGMjVOpmZmJk4V7e42TiH1ta5NvZ2tWkYVre5aAjFV0eZp+gZCTmqmmjZKum5WWgYqiqpydkFlZnpx4gYd3goVrXJSBlYuKcoBhf25feYBnl6GAh4yJi4Vsdnx2c31/iJmIhaqUgJGNk3RdbHV1g3SEkaN/b2eSc36JjHmOeYV8c3qScXd2eW1ybGyMh4d6V4N9hZFtcnNzf4V5fIB2bGx6fYOFiIhqa4eHiomGg6t9fWtja4ZtdYF/YlZxbnJ4Z2hma15qanF3iYFxa1aEUU1JY4Boa2pohqCFbXN3i3dxdHJne259gJBdepWLgYV8eWKLUl98d3CGT552dWp5dFaNeGJ4b4V3cnhzgpCKjHqShXSBf39fk3mWU2hmbIeMa1VofZJ6g1l/cX2Zdm1oaHGJkHOMTEp4XV9Oc1N1dWd1d2traoCJj2xpelxiY2VXV1VhX2lugpV+ZWZgfZF+aVB+UFZeZnZvgI9yZl1WXmxjZ4KHcYyCinlrgIKDjoB0c2djaWxxbpJ7eGV2lot5fHiAkJV5eoR9p3p5gXp5fXOSlWqHT4V+ZYV5Zl1veHhtX2RTYXlmbWJyYnR+YVlhZXiFf2txd25xeIVwc21rYWdsaGd3dXl/kYiOgZCAjXaImYN3cXZhXmtVgG6CXlFhbWttgX+HkoNsaINicn2Dj358jIGDhElPUGBShlh5d4KMfX1sanR7hnprZ2p3hHyceGuIe25ddWtrgXhFfUNDeEV/fGlrdXeBiYWVcXB1f4mPk3GGfnOJlYB3gXp2g3t6eYB+dXB5cn95iISDeXpkaX+YentkanyCiHBRgGlcmmtremphdm50hpGGf3R4gYaRqJd6iIuNrpOXmIGRj4xWZk6ahY+Af4l5dn+Fd4N+cZlzgYaBfIJvdXWAi3p0jmNudnB1jWBplIFogYhxe398cnSRmot2Y1+GlXmAdnCCiYWHeIN7f3uKkId7f32HjYyGmKiMhIqEk0p+f3yDgIhRgX9/cYaUgGyFiJKEj6OPeYWVmIGFhIuGk3+SoIuAcoJ9d3dycXiTg4Bwf3RkZmZkc3J0g31eWGOJhXd0bYJ6j4B8fHyEcXeKeHR7aopaa4hyhWxka3NwdYZsc4R8cnNxg3uCiZ+Cf3hve4SMhXxycYR4bGtxd4SGiWl1dXNzgGlncnJ9iH5+f3dzfmWMl46ZgoKEfnV0dnx3fYiGeWt7hnKHi3lxjXKFdFxxYGGBf2ilmnlhYFp3bH6MhXVyjZN+ZXJzbm53gouNgoJFhXhMhWZKWouNiFJVfIKBhnaESVNYmnprh5OOl4yBfJeHhoCUcYlRlYeNVVROZGp/Q0ZugGFmWEpCPUd3aoB6d1Vbbz9nXUFPSE1PX11YXnNvcEWFeHhveGxjaXyHgXtsZpFsXGFIfWxQYXxncEhcYIFmaXN2d4mEaW+OgX6DcH+UmIqHe01NhIJibnZpdnpiUYhzh3t7ZXRYf3Fngottm510d3p3eHZicnZva3Rzeol1dZyNqXoBe7x6BHt6e3uXegF7iXqCe4R6AXuGegF7lXoFe3p6enuGegF7hXoBe416CHt7enp6e3p75noBe996hXsCenueegZ7ent7enu7egF7oHqDe9Z6AXuFegF7/3rLeg17enp7enp7e3p6ent7hnqDe5F6DHt6enp7e3t6enp7e4R6hHuFegt7e3p7enp7e3p7e4d6AXuQegN7enuHegF7l3qCe4l6AXuHegF7m3oCAgQAgILg4N/JxsTZzcTj8evMw9XG1tLNus7OvszVysrC3tne7+HkytP56czy6vmSrfD1vL/Hwdu5vr67uMbm7eLRxNC8vbavrfr91fDrzcu7us7J3Njh+97FvMC/wMvLvr3Fy8nn/4CByuvX9oHW3Obqtru/rdLqwcLBtKOulKOTnbvKgMrVxIPwgdLIzbOvr8La3LLVzOnJv7vPqcXWxrq31MG4taqovKOx0tbB1NzYw9C/sL7V97u+pMXCx76oobvb8ZK66bvitL3C1tHQhfrg08314P/M2cbPy8q+taGuu/bv4+Pyx9bVz9OB0qm26LbXj/TN84f+g4T5iua9xcStpcScgPD3l4r6joPa293ZgYuIjIeS/8zZ1KOpoJqix9b28ebcrK7x4dClyumB5ebEq87hpK/Y1MvN3YS5hvnR+czR08vUw9PFrLi2t6ms0sXIv7rH0fXBpK+5z9PksI/Oq6WjprCuuL2+qMTBuLvAur3Cy7+9xMbP2c7SxsW+wdu72NrZgNrEyszk7L/Aq8a4vc/PzYLHuLK9y9La6OyG3ea8yvaC1//uv7K1w7uy1uO3ua65psPQ2cTIp7K8xt289tDm7PiUnY6H8vjmxMbT1MjL0ILUxMrT2fuF+fv18vX3/evW9+TJw722z7epoLm8wsTCwM7tzc/X6ISzvcS8rNLavczfgMaR0MHFqaO9tp2nubyssru9ub7Hw8zK3c3L1NPfyYXv7emMg77U6tPU3OvGvtjd18vf6d7X4oPT3MrS3tXPy+fS4OXt8Nve5vrlgOLehO3GwsXi5t7z797P5NXN1u7l6IeM0NPl3d/X7vyF8fTYxavG0s6+xtnP08OyvrKzvdHigOXd5N7o4ePf4eTm5+jZ7eTp297Kuc7FwMLVy8zFv8nKsLi+/7+4ur3Dvr66ra2wv6+ur6q1qqqOlaiyt8CxscuztMayvb3PxcnTy8rd0eDhs5fy46ywvc/T2OXMw+Hg28bH1da94oilt6+2wcfI087h17y19tLdztLNx8POys/YgMjGwsS7vr7E18jMwre6tcTEs73LuK3VyMjV1sDbzOPe983U2+7U2MXr67P1m5yeq7/UxdPSs66hrcfIrs/dgfLg5c+6sbO4xezk7u3w3PD2/ObR4e/phPPNyt/V1s7Jx83b2OTb8v7l9Pzu4YDw7dHyje312vThg6mN5+aC4+frgOr87Y2N2OT36f38/YmL2+fh9ISI9dfsjODS14f97ePh3NjL/e3evIjVzMuqs9zPzcnGvdLB2+Dw39/F4ODV2s7l0ffhu8jTsruzrs7Y7Ofu5eDk9f7B3eLPwKOsr+DRvufx7+ad0c3E4/fy0uvn183g19C9ucXIwsO3r8Lm5NmCgFeLiYVubWh6c22SpaeOhZ2KlYyCcHp4b3mCenp5j42Vo5CUe36Xg26Hf45bdoyQX2NpbohvcXVxaXWNkoB6cXpqamdgX56beZeQen52coaAkoyNo4JqX19eXmpsYWRxeHiPpVJUcYd0iUpqaXF5TVRZUHKRbHBvaFtmVGBTWXB3gHV8cFWUUXp3f2trboOcmnGPg5h4b2yBYn2RhH18koV6cmJfcl5qhot0hJCOfIRyZGt+mmZpVXRwdWtXUF90iFd2gmF/XGJldnF5W62Sg3iXf5VwfXB8gIJ8cmFteaWekpGcd4OCfX1TfFlhhVt0WoxojFOZT1SiYJt6hIFvaX5sgJmiZVqYWlOCfn1+UlxUW1hjp36Oi2FiWVBPZnKIg36GZmuqm4tgcoVOh4txX4GTXmONiXl8hVSAWamDqoSOkYiQf4p+anFvcGVnf3FyamhyeZ1qVmJuhIWfbFSYdXJwcHp4fX14Y3pyaGdsY2Fkb2VfZGRwe3R5cnFpcIRtjo2SgJSIkJivrouIdYmBhIqEfU9yaGVtdHN6iZFhkp53faNUhLenf29ygH10j5ZyeW9+bICLjHuBX2VqcYNqnHiLkZxgZllRjpeMen2KjIaFglaAcG9ucIlKhoiAf4WGjHxxin9oamZie2lgXXFwb3JtZGqIbmtxgFBbX2VdUmpyW2VzgGFWc2xzYVxxcVlhbW1aWV5eWGJnaHF0i3l7goCLclKKioNSTl10iXR3fY5wan+Gg3aEiHtxe1J2eXB6i4B6d4dydHd8e2lueot4SXx7UIpsb3aYop2sqJWDiXRmaXlyek9Ub3eHeYB+j5pVlJWCcmV/j4x3doN3fHFlb2VqeoqZgJmSkIWPhISChYqOkpiRqKCjmp6OfIh+dHKBeHlxbG5xXmVrg3Rwcm99d3dxZmRlcF9jZmdzb3JaZnJ8hIZ1bIBwanVocXJ2bm90bWh4aXd0YGChmXB0eIeOjJV4dJKSiIGDkZJ7o15ec3F3eHt4f3qKinVtsomZjImDgICJg4KFgHZyb3d0fH+Kl4SDem92eYqLgIOIdG2Jfn2KjXySgY2InXJ4g5OGi4KiqIKtb25zf4+klqmkh4F0fpKRc46VWqSRk390bWtoc42Ag36AcIGJj4J0hJGLVp94aXxucnJzcnqAen52iJZ/iI2IhVGanImqaKqujp+DU3dYhYhSi5KVgJWglF5cgYubk52bnFZXeoOElFNZmoCQXYqChV2tnZWUkY+ArqGRcV59enVcZYiAgX17dod1hoqSiIBthYV/hoaelb6yk5qriI+GfJCUoZaaj5CQl59meX93emdxcJeEcY2WmIpnb3Bmgpqeg5mWi3+Qi4R2bXuEeX15dYSjopVagFCBgH9qZ2J1bmSFlpN4aX1te3hxYXF1bXqGe3tzhYOKlYKFbXSTg2+HgpBacJOYbHJ+gJp/fntxaXWJjoOBd4JwcGpiYZ2beI6Kdnhxbn13gX6DnYp7dnd3dn17bmpvcG9/kEhJY3pyj0p0eIWMYmtvZYajgYSFfXJ7Zm9hZoCFgIKJeFKbVIB6f2dnaHeRj22LhamDfXmMb4WTgnx6kIWAe2xqgGt3jo95g42LfIR2bHOHp3JxW3l1enFhWWh5h1NrhGmKbXFtenV6VKCJfHiUeI5oc2h1e4B8cmJrc5iShH+HaHl7en1RelZefVRmTnlYeEd8QkWCTnZdaGlXUmZcgH+BUUdzSkZub3N0S1BLUE1TknWIjmxvaF5gdHqOhXp/YWWbkIdjdIpTkZR4ZYaUXWKDfnBxd0lvTIltjm55e3J5andqWGBhZl5kfnJzamh0e55xXWdyiougcVSPaWZmZ3BsdXZzYn56c3eAenx/iX95fXuFjoWIgX9zdINngnt9gHlrcnqVmHV0ZHdwc316d1Bza2x0fXh5g4VUfotpbpBMeK6le251gX5xio1ma2NzZ3+MkIKIZ2pvcH5hjml3en9OU0dCdIB3anKEioOEhFqDdHd4epJOjo2HhImNlIV3lox3eXd3k3xwa4F8fH97dH6bgYKIkFdrbW1iU211XWRygGFPb2lyYV54eWRufH5maHJ4doWMio+OnIiDi4yVfVeZl45PTWl+kn+AgpBuaHt+e3F/gnlxd0h0enJ5ioB6eIx0dnZ6eGRlcoVxRnV2TYJfXmF6g3uNjYB0hHZscYiBg1JWcnOAdn14h5VTj5GBc2R+i4x9hJOIjYByeW1ueIGMgI6KiYKMgIB9gIeKjo2Bk46QiI+AcoSDfoCRi5CJhIiDam5zin15e32HhIWDdXR4hHZ1dnaAeXhdZXV7f4Jzbn9tandrc3aDfoKHfHeBbXFmT06Gg1thZ3d/fYVubI2LhHl8jJF+ol5leXJ0dndyeXiKjXhyn4SPf355dXN4dXuGgHl1cXZwdHN8i3+EfnV6eYaEdnl9aGB7b2t1dmaAcYF6lGlud4NzcWaFiHCSX2Nnb36OfYyHaWJYYXh4WXV9To1+hXNpaG5vfJiJiYGCb4GKkod4g46HUZV0boJ3fX16dXyHg4l/l6WGjpCFekqLinmaYZmceYVtQ19HZGhDam9sgGt2aUdIX2l8d4WHiUxNaG9vekZLgGRzTmlgZUyNgHh4dHNqkol9ZFV2enliZ4mAfXhxanVid3yHeXNhd3hubmqBc5mJam6Bam9sZnyEj4eIfHl6hpFedn51dV9ra459bIWKjIVfb3FngZicf5SOe299eXVoZnN8cHBoYW+LioJRAXuqeoJ7tnqCe4R6AXuZegN7enu5eoJ7iXoBe5x6AXuGegp7enp6e3p7e3p7h3oIe3p6e3t6e3uEeoZ7l3oBe416g3vWegF7iXoBe4V6AXugeoR7inoBe4Z6AXufegF7i3oBe5x6Bnt6enp7e5J6AXuTegR7enp7knqCe4h6AXvmegF7lHoBe9d6AXuXegF7lXoBe4R6AXuFegZ7e3t6enuGeoJ7h3qCe4R6Cnt7enp6e3p6enuLegF7vHoBe5p6AXsCAgQAgOfs37HQyc7Dzrje2+Gz4Ynds6u1odLAutDpurjG5d/U+tvVwdHjiYDm4oSWl5GV/9nP14Hizde3qsO8xqzUyc65vZ6itNbDtdnlw9DAwdO3sdDwhZyJ2djI07K3tLKvy8XD1M3LzsDP1c/t1+j55b3J1OGzpZmro5Wel6qru8a9gL7Nz9Dw4c6EjcWoq/vSw77j0sSzpJyptLy169fcvq+mqK25qp6Fs8a/r5+rsKewtcDBxrO3r8bqwsa2ory4obWUiImN183Oy9void/PubnQxtfSzsPFvau6uLO7/4vuiY6i/MGP79eWobaOi42Kn6L8pf/nvJ+g6Yj148fWuMnzgPve+vib/YH4/r+dweGB6/Xt0aulsr6msLi5r7DT4M7p1evl1Minv+DW6Mrbv7yuo7C5wOT65YqAiIGE19u82PfU386/yMLApKKns7bFnqe6ycSrrq6mub710qeqnJ6ywri05ta1q7rKw9XOz9bY4461ytfLwry1uNLP0ufb8uDogM7H3rzZ2ePx5uja3/fu1v3z1+PnyN/T+vLNuODN1NLW5OPUtrO5v9HkhtK2wce1wMjlt7nR07a50trl8fH26v7wgoOI3dHJ1MHI2czW2Nba0Nfj/une4tHN3tbs9uqEgNzD18bE6Z2moLezrrmwu9LY4uP11/HCy8DGrMfp6L/4gPrXt8jCu8XexrDB08jV0qy9tL7Nv8G5vcfl5d7DwtTUv8jaydrU9dnbzd/V19/Sxtnp8Njh8JDj7Ofg0Nq8yMrSzc/S5+zpmff0+fyC+8rZ3d/jgYyL3vaCjYvo6+n394SE5crdz9nYhJH26+TUxc/QzuLp0tTFtb6+wL3d3MSqgObP6f3m8OXZ3dTl8PXt5tjj4dLS1MXGtrXGu8Grzdm1vbLHxsy8xMfIwr6wtKaqp6ajnaa4n7KflK3CvrGZwsLBxsyztK69vsDHz+jjxsa7rK343ty6qKbms7LNvdLMq6TA5N+5ybSssbS/69C9tbfY2MrBu62ar8i/u7LJtaf5gJOpn7Pdray7xrbNuaXJyMWkr7+/tNvC3snX1da63PbpivDn5/TiwbOnqLLG3Luwr77Cx66zv8W1s8fQ3caI5YDj5Ozi197CvdLr5OrygNzf983QzvGA383Lzs7R3uHY7vHUwtve/fPx64Pq5tfqusnikfPT2env/fPskIvz6YLmgOmGhujljt6Iioyhhoad4OmAhP3wguvZ5eDfzs7h/Ybn4efx4dfd086Aucy/qqy+3Ly6ssHU1euEwu6HzbvLtrqC2/rG0M3RwLiN2sLa1tXd1+Tk2uHcztbbxr/LvNnmkYHi7+bg3NbR2NvnztHe2NDKq7PYxcu+rb+fm67rwsKDgKWfjGR4eX96h3SWmpt1mmCWcmtxYIN2dISdcXOBnJGJqIR6aHGCT0l8fUtaYFtel3NtdE2IepFwZHt0fWR/dX5xdFxbZ4JtaWx4Y3FrbYRxaoSbV2dVd3JrdltdXFxbb29uenV2cmBnbWqDcX+Hd1VgZ3VTS0hZVk5UUGJmc3pwgG54eXWPgHhVYn9pcL+WgHWSf29hUk5aZW1uopaZg3lub294aFtGbnx3aV5obWRrbXJtcWNlXW2Qa29oWG5sWWZfVVVYfHZ5eoSLVoRxYWF1b358fHd6dWx4dnB3pFqRV11tpn1kpZdrb3lfWFNTY2KKZJCPcWprklyjooiWfoyzgLCXqqdtqVessHxffphXoKuqlHNxd39jaWdfU1Ryf3GNgZWNiIFjdIh3f2d2ZmpeXGdxeJOqkl1PU1JWiY55j6KMloJwfnVwW19lbXB5WmFveHNeZWlmen60l3F1bHR8hHl0nINiWmVqZHFtaGtve09cbn11cGtoaYB3eYeAl4uagIOIqZOurLjCp6GUkJmLcZCCa3mAanptlJR7daKPmJKSn5aMcmpydoCNVoBsdoBzgYmccm1+dWJkeoKTo6ClnKiWUlNZjYWBlYGLmYyTjIiHe4CJq5SJh3hudm+Bh3tOSXZldnBxk05bW3JrZXJpcIKCiYaNc4Zpb2tuV2l+d1eGgIpsWWhoZWx+cWRvdmZtc1hpXmh2aGxla3OTkY94cIOAbG96bXh2knx7cHxxdHp1cX2MjXp+iGF9hYODeoBpcnFzZWVjc3V0XoiKkZhQmGh2e36EUV5fjJ1XXFqEf3mCjU9RjH2Le4SCUlqPj4yBeYWJh5ubgIN4a3R2enyXlIVugJx6kaCLjoJ6fnODj5eenZahnI2NhH99bGh1amtffIVmbGNvcXhrcm9zdm9iZ1tfVlNRT11sXnZpZ32Ninpcd25ycHZkZWRoampxdo6Ocndya2+3pKCCcG+qenmMfo2IbGR+oZx5i3drcXB3knpqYWmEioR/e3JnfZKJg3SKdGWvgFJpXW+XaWp+iXmTg22Ki4ZueIWEepiAlX+NjI50jKKNWY+Nk5+WfnZuc36MpIN7d4mMkXV6g4p3doiRmYFbm1mSl5+fjpJ6coKRgnx+Q2x4lWxucI9OfHNsdG9xe4KBlph/bHl4iYSAgk+KkYyefY2jbLCMgYeLmY2EWlWKiFGMgJBXV4yJWoFVVlZnUlNlhIZQVKGSU418ioSHeHuQplubmKGroZqgmZZZfIp7a2d2i29vZHSDhJtYeppbhnqKfotnqsOXopyckIRnlYCSjYeIf4J3aXFzcH2Ie3SFfJajZVSMk4iCe3x4gIigiIqTkYuIbG2NfIaCcoxyc4fFnZZngI2OfVdra3JreGWDhIJde011V1VcTXZvb4CZbWpxh397mnlyZHKGU0x+eEdUWVVZlXd1glSYh5h0ZHxyemSGfYZ6fWNfbIZvanN/aHNsbYVyaX+SUWJUgH96h2lpY2BaamhlcG1ubl9sdnGKfIuVhmh1fYlrZmRzdmluZXFze4F1gHJ8eXiSgnlSW3RhZamFc3SOgntzaF9sd3pyn5OXgnd1fHyGem9WeoaBcmVudG10doCChHFtY3GTc3hzZH12YmxZUElQeXt4dn+IVH90aGt8coF9fHV4dGt3dW1xlVB8SU5ZhGFNe3BTVllJRD9CTUppTHNoVFFRaEN7f2x4ZnWZgJV2g3tSf0SNlWhQaoBMjJWShXBxfox1e3p0Z2WBhnGDeY2Gg35mcoR+iXCBbnBkYGxvcIWTfExCRURKeIFvhZZ2gW9gbm5wWV1lbnN5W2NyfHtqb3JsfoKxk2xtYGNwdW5ql4RmYnF8dIN/fICAiVFmeIV/e3pzdIyAf4h9j3+KgG9wi3SOkJeekYuCgZCIb5aOeoqRdn9zlpNxaI5+iIuQmpKGbmpzdoOQV3pmcHppdX2Xc3aJgmhndXeCjIiKe4p5REZMdXFtfW97jYONiIWEdnqBn4h/gXNteXOJkodQToNxg32Aol5nZn10bXNqcoSDjJCYe49yd29xV2qBeFmHgI10YHB0dHyPgXB+iHZ8g2h9dYKThIV6foSelZJ6doWGdnmEdH14lX6AcXxzd31za32Dg3B3gUl4goOBe4VueHh9b2xoeHZwWYGEjJFOkV9oamttQ05Mb4NLVFWCf36Kj05PiXJ/cnt4TFiOioh+dH2BgZqfiJGFeH58eXaOh3hlgJh7kJ6JjoJ7gHWHk5iXk4mPkoOHgoKIfn+OiYp3lJx5fnOAf4d5f36BhIF3fHF2b29taHKAboBtaH2MhXNYeHBzdX1rbWx1dnd8f5aQcHBmXV2gkI5vXl+baWd7cIJ7YFpzmpZ+jntyeHd+mIBuZ26CiH95dWthcYN5c2h7aGKwgFhqXm+PZGJyfXCMfGmKhoJlbHVzZ4Vrg2p2c3Vge5N/VIR9f4qBZl5ZX3CAmHtycH1/fmVocHNiY3B2e2dLfEl2eoeMf4hzb4OUh4KGSHiGpHx/gqNXinxzenRyeYB9kJR8bnyCloyFgU1/gXiJZXaOY5x4bnN0fnJlSUVvbDxogGpEQ2dkRl5FSUlcSUhXc3dJTI6CSXtrenFuY2h4jUl9eH+Mf3uFgoRCcol5aWh3im5qX2l4eoxOaIVPdGl3Z21UfYlncm1ybGVKfGx5dXJ2cnh1bXh5cniBdHGCdY6VXE2Ci4R6dXZ1foOWfHyDf3x2XWGDcnl2Zn1iYHCifnxZj3oBe5Z6BHt7enqFe4R6AXuTegF7i3qDe7F6gnu3eoR7hnoBe5J6Cnt6e3t7enp7enqJewN6e3qEewJ6e4t6A3t6e4Z6AXuoeoV7tHoBe7l6AXuXeoN7mnqCe9Z6AXuQegF7hHoBe4Z6CHt7e3p6e3t7hXqCe4Z6gnv/erd6AXucegN7enuNegF7h3oBe5N6AXuHegF7iHoNe3t6ent6ent7enp7eod7B3p6e3t6enuJegF7iXoBe456BHt6enuFegF7iHoBe5V6gnueegF7AgIEAIDc7Nbc6erm3dfHsc3KyfPZhuumt7i3zdDc6/zxx8zWz+rdwbvMwufu/4PriLecipSiid3e1Mm3qJ+60tvjyLS8tbmQsamyzcfBra++wrjP0q2urpmds+Ll4ePNz8K/vauyra6sp8nE0/nn58/V6f/hv9m0t7m5vuCVn5+xo5yguYCjxL644aynyrywnL/Jqa2pxOu9vqCqmpmUl6Ovxb2ruq2ttKKdlaixt8zMz8KjoMPBwamrpJ+81tTCp8jYpqGg0oP+8ObD65vczc7a4Kq/6tLHy9mC3dTE0769v9r13u7y+tanzI3Utd3v65CFjfGGip6dxIyZgPzo6f3XzLSpgIDz1NqK+uLo9NvK1cnX+Ku+oJm3rL+82ra8uL6uvs7lkfCG9ca5wfm9uufB4sy4m6223bHN0Nnq9IP85Ofb7Neossze+tvGrqmyp7+6rLC7pLW3sKO6wLCozrK6rrKnmK7L3OrT0rSzz/fgzsfS19S30b+tusXMxMDEx83ow8DHt4DMwsbG3eDBw9je1viDhZ+MhPnk0MHQ64DRttK/rrXEtsy5ytj33fD02+e70eqG0rrI3PaA1ovqwebu8IPs6Ojj9PWH4tXh6eDM4tPd7oHd1vqC3MDK8+jv8uvx8enk7IXV2YLe6sXBwMCiqZ+xv4KFgPWJjPbq7YbS38j0wra/xoDjtsLozcTAwezysq/MrbG2x7zBy83Sz8vQ1+HVwNzg38bFyMXZ7szV1dPSz8bIz9uDkPnl2N3h5cyxv9LawM7Yz+bn34Dt34WKhoSDk/bT4ejnzd7P3tTijoSSgoWDhJD81NjX4P3z4+j18uHPzMXN5eDU0snU1NTbvM3W0cbLwoDBucXXv/3v5tPXy9CC+ujd2eHg5OXDrq3E1bSvtsrF0Ku94LOxr9nRtrCmk5+WmpeSlJiRp6GroqHE1rSbrNHKyrfExsO9tcWir5jPyqa6taKqr9rZvZ+9wL20w9HdzMGovdrAr560raWvxuz81urNs8q8vqy/1bO1uLiklpmgmYCOu5ior7emp6yzz7uyt8u0o7LBzdndzsfA0erX9dbr8YT77OHd7Nzbube2zNiusdbay9G8u6qlrsXDwNPx6Pj25ufXhvHXvczY3tPRv87R1de2qrnOxcrJ4eLaqbrb8eGD4tnV1cHJ5uTz39ft4ejR1PLQ0oHli9XPgZnuhfLxi4Dw/Ov2h8/J3IOB9+v29/bix9DThPbv0+3xv8vG547j5PyBhtTpjeDf7bS3sdSks7G4o7nPsqu4wrS+1cnU09Xd7/3a4tjd4ODP+4XD5dTLzvbr6dbq2c7a27zT0b/Qybi/q8Pd1+ja4uvz4sfYzcHYx7icutTp0OHwwp6npKjKw4CXnYWDjpGSjYyBd4uJh6mTXJxjbW9sgYaOoayhfX6Ke5mIbmVtaYGLlU+GUXlfT1dmUXh/fnp1a2F5iZWZeGVoaWpJZlxlfnlpV1lhZmB4fFxla1ldaIiFfXlqcWRiZFliWlxZVXNocot8fGRgboRsVGlKUldZYYlUXl9zaWZmeoBle3hwlGdliIB2ZoyVc3NtfphxcVZdVFdUWGRxhH1pdmlpa1xZVGBla3x+g3ZhXnd1blpbVFJpgn5vWXOHWVdTdE2Sin1fiVx3cXaEilZnjHdpcn9Pi4J5h3Nxd4WSgZigo4xnjFKOcYublVxRVo5RU2JielBdTZqQlK2MhHhwXYCoj5NeqpKYoYyBioGSsXKGcW6Fd4R9hG9wZ2VTXGV6VZFZon5xeZ90c5lyhnNiTFRdg2F8fYaYnVWmkIt/l4pgaH2Km4NzY15pYW1qY2t1aXh2a11ycWBfgm52b398a3qSn56AeWJgcpV/dGxtbmpVaVhMWmp0cnF1dHyNb3N3b4CEhY6TpqaKgYyHfpZNTmZVTYx9cWV0iU57aId6bnuKfIhueH+bh5qjiJJvgJZflHyDk6RVg1mTcJKfoF6hm52ZqaFajn+Ij4t3i36IlFOIgaBUgWVojH5/fXeFhoeAkVZ/gVGGknNtdHljZ19teFxaU5pZWpWJj1NyemaPYFteZYB+WmaEb2xtbJaWX2B4XFpYZmBkcXN3dHByfYiDb4ydmYJ8enaIl3J3eHBzbmdpdINSY52Hfn6IiHFZaHuBbHyDeYeLfUt/cU5PTExLWolneYKEb393hXyGXFBYR0dGSlydeoWLkJ+QhIeNh4J1eH2SqKOVkYGGgHqAbIKQi4CIfYB1bXWBbJKMhXN0bXlXpZmOkpqbn5+Dbmd5imVcZXRteVdvjGdmZ4l9a2heUFxUWU9NT1JOY2JsZ2qFkHZeaYN6gW57f3p6bHxZZVWKh2V4cmFnbpueiWuKioN8ipegkIdwiZ2DcWFuY1lfepejhZB4ZH15gHCEnHl0dHVlYGFqXYBSel1tdX5wcHN1j3lyepF5aXaEjpSWioN7i6GPn4CMjFGZkomNppaWfH17ipt1epqglJSEf29qbHx7eIihma6rm52NXqePdnp9fXp2ZGpoaW9ZV2R3a2xmeoV+WWSIlYpTh4CBgW1xhICOhoeil62im72TmVmOV3lwTWOPUI2MVICKj4iXU3JqeklLjoOPjpF/bnl+WaefiaSvgY2JpmygoLBfYZWma6eernp5aoVdampuYnSLeG57gHZ5iIKOiIyVprSSoJyhqaaXuV+DnYx4cYuGg3mMf3Z+g3KKi4CSjHp8aXeLgpWHjZmhl4CLhX2QhHhgfI+gj5mnh293eH+XjYB/h25uent9eHttYHNua4ZwSn9LWmBie4OJmKKSb259dG17a2RvaoOMjkh4R2RUSVFcT32Cg3x2aFt0hI2TfnF4dndTa2JofHxtXV1jZ154fWJqcmFkcJGNhoNzd2hoaF5mYWJdV3Vteo2Fi3dygJiCb4FkbXR1e6Bnbmt4bGRmdIBje3RvkGZhgXpwYoSMb3Jygpx+fmlsZmhlZG15hoFuf3d6fHBuZG9yeYyKjX1lYXqBg3R2bGVyhIN1Y4edc3RsilSYh35hiFVzbG13gFFjh3FqcX1LhoB4hG9vdISJcoKIiXZVcz99ZHqEfkxBQ24+QExMWTlHO3dwepl7cWpfU4CObm5If2t4iXp3f3qHpWd4Y2OBd4uHmH1+eXZianB9UoRPjXBpdKN5e512i3psVmBpil91c3uEhEePf355k4lhaH2JmX9xYl1pZXl1aXJ9bXyAeGmAgHJriXF3amxkV2N3hZJ5dGJmfaKOhn2DhIFqfmpbZ3J7dXR5d3+Qbm9waYB5d36Alpl8c4F7c4hGSVxQSYl6bmNxhk12YH91a3mHeohveYOiipqehoxpfZNah3R5iJxTgFiRb42RkVOPh4iCko1QeG98hYJ1jX+KlVKFe5lReV9hhHp+fnqDiIWBjlR8fFCElHdwdHtjZVtnbU5ORoZRVZCIj1Z2f2qRZmJocICQbXyZhYF+fKOfbW6CZmhsfHV7iIiIgn5/fYB/boSPj398e3iLmXV8fnV1cmpsc35RX5N8cnR6fmlVZHuDbnyGfI+Qf06DckxMSkdHU4Fib3Z5Y3JncWl0VEtURkdIUGCifIKChJeNgYORk4h6fHuHmZKLi3+Jh4KJcYeVjoCDdIBtbXmGbZaKhXd5c3pVoZKKiJKVnKGHe3mNoH15fpCIkWt+m3R3c5qOeXZwZHBpcWxram1neHJ4bW+Ik3JXZIF6fm16fXx7cnxfaFeJhWRzb1xiZ5CRd1pzdGxkdH+Ne3RfcpF+d2x8eG5zh6CliZF5Z4F/gW56jHBraWZXU1VlY4Baf2Rzd3xoZmlrh3Zud4pxYGp0eoCBc2xic4l1iWx5ekiIfnZ5kIGBa25zhJJubY2Of4BsaFpUW2xsaHaNgpSRfoByUZOCa3N4enh2ZXBzeYNxcYOWi4eBjZWIX2iIkoNQgn19gWpsfXmFd3SHe4p8epVzfkp7SGdcQVN4RnZyRIBudGp2Q1pXbUVHiYGLi4x9a3ByUZSJc4qQZnFwjFmAf4tJS2l6VIF/hGhyZ4Zhb21vYG5/aWFsdWpsfXZ8c3F1g45qdXFvdnVtiE5rhnxrZ4V/fnCJe3J8fWp9f3WFf25tW22CfYl8gImSiG98cmt/b2ZPboKSgIiVdVlhYWeAdpB6AXuPegF7iHoCe3qHe/96kHoBe4V6AXuMegF7kHoBe4V6BHt7e3qIe4h6BXt6enp7m3oDe3p7lnoBe9N6hXuGegF7lXoBe4V6A3t6e4V6AXuGegF7inoFe3p6enuNegR7enp7i3oKe3t7ent7enp6e7h6gnuSegN7enqGe4t6iHusegF7/3qUegF7onoBe5x6AXuTegx7ent6ent7ent6enuEegZ7enp6e3uJegF7iXoJe3p6ent7enp7pHoBe7V6AgIEAIDGwfyA5YaM2cTLz66xwsvthYXhsKSpusTOvtPPrLm6usDTubKqjJXI2tjn26eI3+mHmYz+587OyqC4zceB5srTpaKQqZyinpuZqrrS3bTx0Lesm6aLprLT1//fu9Hn49+ttsDM3MqzrrXh4My4sKK8x/bB5eK8r7WvwauyraG5uIC9ysja28S6zYqwoOK8s8bNz8G948PLvtSoj5yXmKasraSfvqinpJOblrK60Oip2Mq/2PfUr8zJ5IXwuq+jtsu2ssTd9N/Jra+6xMS86Mm7ztru0rHe2c/JvbizxsXcxMnK3u340tPHubTSv9/bkOO/x43kzbnQ47rC1+zry7fZ4YCDzNrW4vXOpLHGu8iit5qwnK23tarZurjT0NLUys7Jgufl3di1xta+pp2usuKwrsDR5Mi7t9nVxsjP2M/ftaOnqNHv/de0tKuzq7fLucLHybrHw8C1z7O82s7f19zDsr2kuLe81tLL5Z7b+qWuy7anwL6rq729ocO2t8TUzb3UxoDLy8W/rrWqvuTat8Ls/4Dp5Yft9uLl29TTvrG/wqurqrG5m7vb1uvx8OzV4NHPxvbe1+Tn34uR/fHo7YuA1tLZxsO1x9je3NbGu8rJ+9vF2+zOx93xgvTz7f3u7OLYz9vRztrm5s3Zw6qjs9Lj6vTr0czb7M3H2+KTws7H1NHJ74D71NvQw8PL2MeutMLj3/i2x7yxycjIzdbR4tTU2eHevc69w8fd6M3hycq4vuDZvc3sjez238/Qzsrfy7/Vw+/rztzn6urk4O3T1+DY+PLi79/d1fbk0src3eT1gYH9goCAz/Ld6p2I/+by7Ore0tvZ7u/jgc3X29jQwb7V57vCyIDA5NfK3dbOwb2/rNjz78zKyNPGydfUrLq7s6q2xtPQsLDJzb+4tcOwuKmlipmGh4mMipaDiYCSn6OmyNHErM3dhvLx8Nnz5sS+s8W7yc/My6ChipPX9cjl1sPOtNjEo6uvrL+op7jcuqazyNLIws7ZwsGpx6XB5tKxt7mxn5+jn4DPxq6sxL+UkrSdmqrDuszVztTi5tPfu8zOw6u91sjK28/N1sbb0MTUw9WsssDD3NHLzc291MS+n6TGw83e8NTa39bQ79DX9uPgx9344szW1c7Ftb23x7/R2430ydy+6cXG0+Pu7fjW3sbAx7zV4uCB8/Hw9u72rOT9g5r584KEg4CG2+qSifqJiIDe98/g8ubx3+f6rv7r7N3X/+bq1/LWgKCD9P2S9M3L17i7u6anuba3u9evwsaHkL+Am43P39fW1OD33e3OyuzVu9/Nxb3S6erav+aDkuPiycrf8tzGyMKusMSnrLjf2Nne8ta1s8itxtPOyte5v56+yZ6boImOnICMfqdSiFVehnaBimtrcn6XVVODY15gcYCLfYmFanRyb3iHa2NbRUZrdnd6cWpTe4JUZmCml4aHg1xxg3ZVjn2GYGRVal1hXFZRYm9/hF2Te2ReVWNIXGiDg6SOZ3qLjIhYW2Zye2tUUVqBgGlXTEFVXX5Ud3ZaTFxgb2BpaV5vbIBueXJ/gnNvhVxwZJ52bXyBf2tniGtxZ39dTFdXWmVraWJac11cXVBUUWVxhZxpj39ndJB4X3R0jFSVbWNaaXdiVWJxhXJbT1FaY2Vojnlre4WWf2mQkoeGeXVwgX6Mdnp4iZqolJaJfnmUfY+DXJRwb1aLd22DkHN+lKmminqYnIBagoqKlaOFYnKCd4Bme2B0Znh9em6VdHCHfn9/dnNuUIeLiYdtfZB6Y1pkaJNmX2t2hW5gWHhzaXB8g4CObl5fXX6bqoVpal9oYGl6bn6CgXN+enhvg2Rti4GSkKCLd31meHZ1gn90h2aVm01NY1JJXl5RVGxtWnt1c36Jf3SDe4B+g4B/cnRtdpKCZWqEl0uBe1CLmYCDend5bWFzemhpa3F4YXF/doWIipCEjoeIhKuYkZaUhlhZkYeAkGFdlZOWgXpwfYiMiYZ1bXZxkXlpeoxzcYKSVJaPhZGKioiEhJWQjpqnpIWLdmFaZX+UmaeghIONont2h4ZBY21obXBliICTcntwZmdyfnJga3GGg6Blc25ldGlrdXh0g3V9gYuLcYN1fH+Qln2Le3pnbId/aHaTXpWch3JybmuAbGh9cJ+Ue4GJioeDe4t2foZ9oZmHkISCfpiFdnKIgIOQS0qLSU5Oc5ySmm5Vn4eNh5KMgoqNmJqPVHB1goSAdXmWs3uCgYB2gG5neHp7cHFuXoShpoyKi5aOiZSSbXRyaWJoeH90WllvbmNeWl9UXltbSlxMT05TTlZKUEhWZWxogoyEboSTZKagmYCWjXBtaHx1g4WBflteSlOQrICWjICMfJ+Mcnh4dH9mYm2Oa1tjcG1bYmt/cHFhgmR8nolxe4B8bWplXICFe2ltg4BfW3tpaXaHfImNgH+Oj32Mc3+AfGh7inl8iIGFjYSbm4ubjZtycX19lYmJjoiAmo6IaHCOiIqUp4qOjoeFnH+IqZ2TdoSRd2Z1eXZqXmRhcG5zeleNY3RskXh5gYeNjZyCjXp3hniPlJ5dqa2mr5eacYWZTmOYklFRT4BSfYlfWp1ZVk+Dk3ODjYWOgIWZb6KQl5GMtpyfjaCIVGhYp7lqs5mTnoGBfWZkcGpud49vgINTV39acGSIjYmMjqG1oriama+fh6aOgnmImpuJaHpNWoWCcoCirY90dHFnbH1jZW2Mi5OVrJR2dIJsfouHfox3fmiFmnl8gnBxdYByZolEcEhPcGVvdlhXXWF5RkduUE1Ua32KfYyGanF2dVWHbmdfSktvdnFzZFZIbXVMXVuejn2CflZsgXVTkoOKaGpZb2JnYV9WaHF+f1iOeGZeWmpUbXeOhp2EXHCDhIZZZHB8iHdlYmmPkH5pYVZrdZNqjIxwY3Z0g21ycWJ0a4BwfHSAgHZtfE1oXpVwanyEgXV2ln6CeJRuWF9fYWtvcG1rg3J1dGNkYXV8jJ9pjoF3i6eMcYWBklWYdnJrgpiEeoaVnoVrVFBXYWNjh3Vmb3WHcVV5e3NzaGdme3yHbW9qeoOOfX50bGqHcIBxS25VWUZzZFlyeV5ogJaVeWiGioBOZWpnbXpmTGJ1b3xhdVxuYXJ9e3KTfnyPioiHe3l0T4OBhYJtfIl6aWFubJRpY217jXJkXHVvYmZ0gH2LbmFgYH2WpYNqa2FrZXOBdIGHiXyMioV4jHBxiH2JfoVvX2VRZmhsfYF8jmaGqGhthHRrfXdiYHBuVXJsaHR/dWp4dIB0enl6bXJsdZOBYmSAkUd8eVCJm4KCdm9wZmN5f2tsbnN4X3WIgI6OjY2AioaBeZuIhI+Rg1dYin52gFhUg4GGeHRneIaNiYR2cnt2lIBvfIxxbn+MUJCMhJGJioaCfpCLiJKgooeNdl9XXnOAhZGIcXSGoH99kpVJcHZvdHVskoCjgoh+dXV9h3xqcneLh6Bwgn53in98f4B4g3mAhYqIcIFydn2QlH+KenVjaoaAaHaVXZKTfmloY2N7amZ9cKCZf4iRkJCJgo93foJ4lo1+hHp2c495amJ2c32NS0qPTVBObZCBh2JQloCJi5OIeoB/ioqFUHF9i5CJfX6Vp3J0c4BugXRte317c3NzYYajpoqGh5aPjZyfgIyJf3mCkZiOb26DhXt5dH1weHBxYG9gZWduaXJkaVxlbXJtg4N0XnOFVpSVlIOdk3Z2bX93hYuIh19hTVWNoXaHfW91aIl6Ymptan1tb4CkgnN7hoRvcneFfX1yh21+mIZqbm5rYWNkYICMhHN0i4VjXnpmY3B/c35+c3N9f25+YXBzb1ptf3Jzf3h3fnKIh3uKf5FwcXt7jX12dG9ke3BrUlh3dHiDkXN2dnBthGt1kYaBbX+Re2x/hoeAdoB+jYSJiV2SZnRoi29xeYGHiZd6hG9ob2BxdntMiIiEinp7W2l6P1F9eURGRIBIa3FRSoNMS0h6knOEj4SNfXuKYJF9f3hxl4CFcoFoQVJDeYNKhnN3hHd5eGFjcWltc4Zpe4FPVHpWY09ydW5qZ3SEcIJpZoF7aot5b2h4iIh6YHVLV4WCcHiQln9ramVZYHJbXmaFfoKFmoFjYnJgdH97c39nblZzhGVmallaWwd6enp7ent7iXqCe456AXuLegd7e3p6e3t7iXoBe8t6AXuvegF7mnoBe5l6BXt7e3p7jnoBe556AXvIeoJ7pHoEe3p6e6N6gnuEeoJ7mHoBe9t6AXupegZ7e3p7e3uEeoJ7jHoBe8x6AXv/epl6AXuVegF7hnoHe3p6e3t6eoR7CHp6e3t6e3t7inoBe4t6Bnt7e3p6e5R6g3uYeoJ7qnoCAgQAgMvJ9oPh48rs0L+tq6iyyuWS/+y5oLy2tcTL1cG9qMbDvcq6r8K8rsLM0MO70dnbzNTP8IfjxsHc4MKBgOTY1d7Jq6O/sbaspbubzsXhr6rizceoqLuWpazRxrfGybLEq7zFtLOmpKLui4jYycPOyLi63eHP+dDAvrrNrrqGna+/gN7Pzce8tbW316Ovy7q0tMDFyMu8xqu4n6a2t83Eu7zLorG6xM/BqYeap6Cip8qLhNLOxNiuy8bA0Z60naGvp6OsvciM8MK8xcfivcjwgczMwoi6oP3g74Hvz8KA6fbdwby91s3T5LOjqLuhpqW80eyyttPN1tnY0c/k99Tp1M3igOLYytLP3unwxu/Wt7a4pqyfq6vCuq7gx9nz2u+vzM3G2//bxLOtqaiqwc66wq3ByNLLw+vbhevf6N3U1Kyus625s9D5it3KytC/tbe9rrnSxLq+xYHFwMrZ1Mm/zsHBvqunurDB1s/C0ffGmJ+lpLTQvri+2/r55snz1N73zNK1gLPBzrydkJOos7rEzZ728NrJ4Pz7yeHWy/TXzMulsqa1tM3e4snY0Mra3NTj5tHC2uDY7eHr5tTI0tDK5tTQxauew8Tp7sXCxbW7suTTzc3e5vPj7pPg3uvn8Pbd7tH+ioHr4b+u0bjCtqzE3fDjz7/e3NO9z6/Frpfw+OTiz5f2gM/GpLDDzr+7wLe5ub+/u8feycS/x87FzMHh5eTz3s62vtLEw8DSufXy7NTX2d3Htcve2L+3wOPu49LT/OHw2+G6xdna1dbUyMK9vsze9YHf1s/h9vP59Nfl7vP17vWAgerg/Nr0gYDy6eDqi/6OifHc0M3w1NPj7+jX1tfizLfHgMbJ1Nnn3OTbzb63xdDb3tbd08vYusTJzL2tuqbC9dHEwMG2wbzAx8XBrKalopySjo6JiY6NgI+fm5uum8CbsM7o8YDSxczKrbGrv8fKzNLFtbCblZi7ycPHu6erlKedppmgr6a3uLHYwNThtNXJwOa9pZ2OpZiZpqGpnLObqquogLm0tajEvZ+YobKiqbfBydrJv97m48e2r7Wptau5rrLJw73O4OLh0OXSvb++t8rJxbuyzcHft6y2orHY0dTYxcvh6cPMxN701s7S3eLVy8nV1tDCzLC1wsnIg9/E4sTbyNXG39fxhOrm0dHFvei/4f/ynYT0gIr3ge/tj/Hw2v6EgP/f9Yn39/z29evb0trKjff45Onq8PaC2oGD/ILoyPzr2vXY4ITJuteH5MG4pLy5z83KrbSjvsqloND514Ti1ePk5eLnhMvfys/E2sm/u9SG+eTh5v7puMC0vdH30svI2uvy2sfnuMjRwczMz8+91crY2cvJy7riuqyqoqmji6KlgJmSr1mUkX6YfnNnZWBmeZFiqph0YHRvb3h8gXBxXnl3dYNyZnNvXmx2eWtkeoWHdXtzkliId3eSj3RWVY6HjJSAa2eDdHZjXnFYgHmXZF2Hd21UWWRNVl94cGVrcF1xXmtyY2hZWFOIUU56a2JpZ11benVskmxgZGp3YGxFWWZwgId7d3FsZWZujWFug3NuanB0cXNnclptW2FxdId/d3aBXmpxc3x0Y0JTXVlbXmxYWHl3bHVUaWtrfVJjVVllW1RZZGlUg1hTWlxwW2mFTHB1cFV2Yp+KmFmmjYBan6aSgHyFnJqfsn5ran5gYVptfpZkaoV7hYmIhICWpIibioaWgJaNgIuKl5+khaSIb3RzY2tibmyAemuUfIyabYddcXBtgaGDcGhmWVlfdIBqclxqbHp4dJSFUod4hYB6fVdbYl5pZ32bWYh1dIBsZ2t2aG+EeW9zdlFvanF+gXx6joqJhXJue2pziH9veZx1SEdKSVZpY1xphqSqm4Soh5OmgIVsgHF9jnhgU1dhZmdrbV6IhXVrg6SkdH91cpiBfH9faF5oZnmFiXaEfXl8e3iHkHx2j5KJlYeRiXdrdnNui315cF5YeHyenXt0dmdtZo59dneJk6KNkF5/eoZ/h493iXObWFOVj3drhXB2amJzhpOKe2yHioVzgWN5YUqPlIN+cVSMgGpjWmZdbGprdWpqZ21ua3GFdXJvb3FlZFlvfIKSin9xdIZ4fHR7YJibln6DjZiCcomak3hpcImNf3FxmoKUiItucoODgYR6d3BubnqKm1ODd22BlJCTkHR/h4aHf4RIT4R8k3uRTk2SjoGKWJtcWZ6GeXSVc2+EmpaJjZCWgG56gHhzc3qBfYKDeHBtfIiWlY+YmZCXgYN/eGlcaFdxoX52cXBgaGhnbGVoXltgYF5ZVFROUFlUTFdmZGNxZIdncYunq2KEcXRyV1lVanN5d3xzYV9NTVNxgYCEfmprVWhgZV9lc2hyb2d+ZnN9X3JnXoVnVlJIXFJWZWJvaXtrdG5sgH14d22FfGdfY3BlaHF+hI9+dpCUj4F3cnl0fHZ4cHB+eneJmJqcjaCUf315dYSBfHh1iIGdgHZ3aW+Mgn+FdXyRmXt6eJGkiICFk5N/cW97f4V3gWVlbXFwUod2kXePe4d9koiZVoyLen59eKF4mLGhalujUlicUI+OXJmYh6VVgKB7jVGOj56cm5iKh454YJiZf4CCh5ZTg1hbsViceaeaiqyOmVuQhZllp4JyXW5rdHt/aG9mgZBsZZCnpFuSipihqZ2lZJWpkJOFloJ4dIRWkoOAhp2QcHZve4uviImKk5ybg3eIbXuEdoSJj5KHmpSemo2Gin2fhHuBfYJ/aYKBgIJ6lE19emyIdGteWVJVYXRTj35bTGZnbnqBiHd3ZIOFgZB/coB5aHN4d2Zfb3p5ZWlkh1F7a26FgWlMTYeJlJ+Od2+GdXZlYXNdf3eNXVl+c25cZHFYX2Z6bl1gZFRnWWx4bHBhX1uFUU2Cd3B6em9wi4h9on5xdniHbHRNXGpvgIuCendsaWpti19rf29ub3h9fYN3hGp4Zmp0dId+d3WBYnN8g4iEdVRlb2hlZGdZW4eLhotsf3h0g110aXGEfnZ3foNgmmlcX1twV2KBSmtuZ0xjToFuekuGb2VOi5J7amhvgoKNmnJka35eYVpodolZXXdveH16cm6DlHuMfXiOgIJ4Z3BqdIOMc5qBbXR2aG5mc3SFgHWcg5GeVIRrgXxwf5t+cG5uZWxyf4l0gGdwcXp4b4p/T4V2gn15fV9mc2pyanuXVIJwcX1xbm13bnWGfHV8gVZ5c3N9e3FmcGhpZVhZbmRzj4x+lbGLXmBmZXCCdmlsgZaSg3GPd4Oad3djgGl4inpiV1xob29yc16Mhndqg6WicnxuZIp2doBjbmdvbYCLkH2JhoKGhX6IjXlzjI+IlYqPintveHNsiXt6dF5egIGkpYZ9em52bZJ+dnWCi5WDh1lzcH12foZvgW+TUlGTjHJnhG9vYllrfouEdWeChoh5im2CbVSZmoiCcU+NgHRwVl9ufnh4gXR3dHd5dn+UhYSAgoR4dml7g4WTiYBzfJB/gXp/Z5iYkXp/hY15aYGUjnNjaYKGeGxumoOViY5veImIiIuFfnZ1dX6QnVN+cGV2iYSEgGVxfX+DfoNIUIV5inOGR0mGg3aEVJRZVpiAdnSZfXyQppuMjY6UgWx5gHp6f4OHfoGEenRxf4uXl46UkYqVgo2UkYJ3g3GIto+Ce3dpdnh4gHl7bWxxcm5oZ2hjZW5rXmZyb2l0XndYYnqNmFSAc3t8YGJbb3h9f4R+bmpYVlp1hoKAd2VpVmlhZ15idHB+gHyVe4WQcYR9cpR/bGxgcGBdZV9nXW1caWltgH96fnCGfGljaHVobnR6e4BvZXyDf29iYGlmb2lwaWh1dG18iYuMfZGDc3NwbXl1b2ZdbmaAZlpgVWCAenp9bHGBi2hta4GQc2twfod9d3eEiJKKmH58goB8VoZthmyAb3luf3OKUoaDc3FsYoZdepODWk2FQ0d9QHBwToGFdpdNgJFzgUt7fIiGhYR8eYFzXJWWeHl4eYFGbElMk0yDYIp7aoRnbD1lXnZRjnFoVGlpcHp/aHNnhI1lXn6HZkt2Z25zeHB2S2x+bnh1i3luaHdOgnRzeZGJanBmb3yccm1tfIqOem52X252aHN2eXlugHyKi4J+fm2NcmtwbXRtV2xoBHp6enuMegF7onoBe4Z6gnuseoJ7xHqCe5N6AXuJeg97enp6e3t7enp6e3p6enu+egF7mnoBe456AXuPegF7t3oBe8d6AXuKeoJ7nXoGe3p6ent7ynoBe496gnuFeoJ7hHoEe3p7e9N6AXv/epd6AXuLegF7i3oKe3t6e3t6e3p6e4R6BXt6enp7inoBe4d6Bnt6e3t6e4h6BXt6enp7knqCe4d6AXuKegF7sHoCAgQAgOLq4enm4t/v49eCgN3o2u2AnMnPqb2+wbO6vbO5obGzvcastLbEv8TFssa+u9zk3sHax9XJuLfG3oP10svIv6+7ubjA3cShqqW9q9bYzcKMgMC398fDnKqzss7KxsC+vcuwtbannKG2v8mksrbFssLf38rL1uaa8L+xuqihr77DgMHO3c3SweqluoLQvsbP4NH1hc+2ubvSoKiz0+zc6ebS1buuvcbdwbnJvLOspKaq0dHPo5ybstCGrau9p5Kp4qalucvngufW3d/t6e3y4Obi7L3s6+mHhM2mgePV1r7JzIDtwbbD18aRlKKhxOLAzMTFuNvR0MTAxOHxzfPn1tH/gOXWv9vIw7Cy/femyru7urSsq7DAxtHO4MDBo7a6ycDI1uXWyL++uMfayLitmtvP3dPV0+H029/a0OTMubzSzs3Szby77+r25d/mhvrXqMjc66XS4tjWxu7bwfvx5trGyMr3vcbbzd7JocnApJuzq7a5r8bR6NrQgYqE1N64u8SGgJDc0sDHy9Wrs8W809TKydDMxuXxiuzg07PDz8y1xdm30fD/hdrPurvY18PW3ry82crL4ODuvta6xaG7trDAztHK2+f99OPb18HPvL+D6dTQ8YLu9YTZ7/vj6O7m9fTt1d/v17K1tbfBxsTW19Pq3b3c5MLDu8i1nsbw64CTnpyMgOX10qynycW0rrSruNDVza3K1tfPx8XHvMzY4ZuB+9bCtt2F1svo/PTr9/+D8MPctri6zM25tNKw09nd1c7Cy8u7vs7Fx6y+rqC6mcXqk4vg19LOx+rc4v6Loofe6oCHm/vi5Ovb1tTr3eDb5+fw/enrgoD77+TVzsrd1sa7xsPGgMbGzMPY2MDbyMXEx8jW6cLEwb7MwM7Bvc25orC+vq23pMW3tcfIzsDVvKbBx6ihpoahkoSHh4qRmZqqpbGywdasurOyu8rLudLG5dDNzNS/tbSxnJWS3tGkxcaztq+hnJ+rpZqOoZmrsL+1tLCtk5DEiJ2qqKSwr7OntMfB0dfGgLOzrautqqyivb2t2arAq7e/heHy38q7oq/GsremqKy/tLLc6M/D1JD4wK/A7O27yePPi+PJzsC+tKe3vrrMy97Yz9nVzt3v2MfSyNXR0Mu5uL7HucnI3dfl1tHWvrfWuM3O2uXx9u/j3MHAwNDcg/jp8Nvi6uDe8sGNkfz8gfLXgInK7vDSyM3d49rZy9S19P3h4dbT2u6+4un05vzP6fuB6ICI543dw4SJ4cjXteOt54LUyr2xyMmzotOyzOLj79Hj5uKHjY7twb/Ou8G8v9bf3ODp9ZaC5+HP69v+7+HByu6K2brDxNe7s66zvL3evLy51NDaycbE3syqsLSnmqDIgJ+fj42LiIuTg3hQTZZ3b4NIZm99YHF4fHF6fHB5Y3NzfYRma2p1cHh3anZyb42UiG6DdIJ5bXB/kVmggnl3cmp0c294k31jaGF2YomMfW1YT2pjnnR2U2FoZIF2cGpqa3ZhZGVYUlZsdn1aX2FoVmJzb1xkb3xgj3FlbWJeanNzgG56gnR3aY5xg1J6amlyfG+NS29fXmeDWGVtjKGQmpqFh2xgaWuDcGlxZFtXUVdcgHp6W1dSXHJUWltqXUpglFxXYml6RXlsb3R9fYKFc3d6g36Hi5RSUI5vVZWPk4OPk16ujYGXpZZdX2decoxwfHF2b46Ff3VydpGagJ+aioa0gJ6Ndo2BfHd0pKJtdG50c25qZ2l0dnx6i3VyW2lwemtrd4d6c3BpZHGKemZbS3lqdm14eIehiI6KfopvYmR6e3x/dmZlj4qYj42OV6GHZXyRn2B+hH12aYR7aZ6dm5aHg4qvfHmIe4N0TnFqS0NVUFtbV22Bk4SDXGZdiZVzd3pegGiUinR5eoFXXm1nenh1cnp3dZafXpOLfGFwgoVvhZJygpWdUnh1ZmiBgnSHl3d2jn99ioeSY3ZbaE5mZV5yeYJ4gIudloR8dWNxYmFRhndwkFGKkU94hpZ/g4WAjI2Id4KVh2x0cXV/f32GhICPhmmCjXFxbHlqVXCVjU1aZF9QgHOCalJOdW9mZGpjcHyCel12f35xZ2ZnW2VzfGVRo415aYpZgG+DkoyFjphSlnOEanJ4iYl3dIlohIiKeHVveHhucoF8f2t2aVxzVHmZaV6GeXV0aod6gJhVaFB0d0JJXI1ycoZ9enqRkpCPm5ObmoqFSUiSint2dHmFgHhxd3Z2gHR1dnKGi3OOd3NvbHOFlnZ8fHWBf4l7cX1pV2hzdWVtX35saHl1eGN6Z1FtdFtbX0leUklNT1BWXF5kY21veopmdnFpbnh2aHhuh3NwbnRnX2JkVVJOkoFafX5vcW9mYWRwbmJVYl1raHFnZV5aSEaDQ1ZhYl1la3BncIN/j5SFgHBsZmdrbm5me35wjmx7a3N1WJCai3x1ZHSPgoh5eHuJfYClrJWIkmKqd19tlZRldZGFX5aBhnx2bmFvdW6AgJGMhY6MhI6bg3aBeYN+e3xubniEdn9/jomSg4GGfXaVeImEjpWdnZiMhm5xbnZ+UpiIkYWNlImNn3dcYKOfUZF6gFdtjZB/c36LjIiCeYFgmpqCfnRudox7i5SnoKyEmKdXj1Zcm2CWh15jn4eQb5hllFeKhXlyg4RyXpBshZiPk3iSl5hdZGWYdnOFdXRtcIiLiY6YmmZWmpGDnpKvm450dp1kjm94fIdybm13f4OkfoSBmI+Sg4F/mpF7h4l+dnaQgImFdXRwbXB6cGhGQVtmW20+WF5oUGVveHN9gXiBbX+CiI5vc3N7eHp4ZXFtZ4aGeGBxZ3JoXmFuf1GUfnp/gXiBfnV9lH9obG2BaIiFdWRSTW5roH1/XmprY3hpYlxeY3JiaGhcVVlsdHpZYGNvYG2BfGZrdoRTlnZscWRia3Z0gHSAhXV1a4VjbkdvYmVwfnSST3tub3WIYWhrg5WGjox8gW9qdXuVf3mDc2plXmBli4mPcGtncYFZZGd4bV9+tX15hImTUYh2c3J+en+Bb29sdWtmZGo9OmhQQHFtc2JtdU+PamJ3gXJKVGBYcoludmtuZYV7enJucImUd5ePe3SggIVwWG1dU19dhYZdbGt0dHBtam55fYaBj3l0X2twfW9udYV6dXJvcYmjkXpxX497hn6De4edhYmDeo15bHCIhoaJf25qkYmRhH+GVJl/XnaIlWKFi4F6bIFwW4qAeHJmZW2UanGEfI6BYYaAZmB0cXl2bHmDjHxyTFZQeIRla25OgFeNhHZ/ho5kbHdufnhwbHVycJSdW4J4b1dqe35sgZFyf5OeVIF5amqEhneJkXNyhnp6j46Yb39mblBoZ2Z4hI2KlJ2wqJuQiHmFc25Uj3lyjk+Hi0tvfI53fYKBkZWPfomah2hvam10dHJ8e3iIgWqClYCFg4+AaYGhk01YYF1OgHiNel9ch39zcnZxd36FgGuEkpaKhIB+cHZ/hmhUo4l6cpVfjXyNlpCFjZZQlG+BY2lufnxpY3VWdHV7cnJtdnpzeIaAhXN/cmR5XXiTXlV6b2tpY31vcIVKWUZnckJJXpeAfZGGf3mLiYSAkIuVmImFS0qVjX52d3yMiX91fn1+gH2Ag32RkXaSfXp0cXOHnHl/e3aFhZWNhJB+aniDgW10ZH5ubYCAhnOMd2J/h2tocFp0aGBlY2JlbGptZ2xrdIlnc25sdH58a3tuiXl2eYB2bm9uX1lUk4dggIBwcW1jX2Nta2JabGp6e4l9eHVxYmCWXW54dW1vbGpaYGxsfYqAgHBydHJycXJoeXlukmp4Z2tsUX+Ie2tjVmiDd3lsa2x5cnGRlYF1fkyWbVtojIxcaX9uVH5rb2lpY1ttdm+Ae4x/dn5/fIWPdWlxbHt4fIB6fIaTgoyLmpKYh4GEdm2Hant2e4GKi4V8fGhqZW1xSYZ3f3J6gXR1gVhHToOISIVxgFRnhIZyZ3B+gXp3bnZak5eAe3JpbnpgfYGOgo9pd4JCa0BDaUFqXj9Ogmx1XH5Zgkx9d3RvgohyXolec4JzdVVubmxGT1SBZWyBdHRqaHt7dXeEiVtOi4p2jIGahXZgZ4hVf2hxc39nY19la2uHY2ZlfnuBcnBvjIJpdXxtYmB4inoIe3t7enp6e3unegF7lXqCe6V6AXuQeoN7h3oBe6V6AXuMegF7jHqJe4Z6AXulegV7e3p6e796AXuseoN7hXqCe5N6AXuOegF7qHoBe4R6BHt6enumeoV7m3qCe4V6AXuIegF7oXqCe4l6CHt7e3p6e3t7kXqCe/96n3oBe5V6AXuKegF7u3oBe4p6CHt7enp7enp7lXoBe4h6Cnt6e3t6e3p6e3uHegF7knqDe456gnuLegF7nnoCAgQAgNjj4Pfw19Xj7Nbs/u+Cj5OM3P7pusnk07u/vJ6zvsS6sLKztYHz38PCvsm51LbC3OOxzsbGxNHf4OHEtam4pK7Bt6aswcXBw6+rvMLn3MeIi9/GhLfErbyosc721LbI07y8w6OlpazQ+vjPzdPR6+DH/ua31MTR4NTIr8HJ2e6+gMjMx7jDq7z2g+C0rM/80rer2ty9wcq+vK2ryOj8gO7+hoiE5/G+xdzd4MK/uLTC4ci+yLmlqp7gua20zJKQr8qcu73x5Mfj9NHb24Dl1NLIt7WygYv3+d/I1JeP3NHA7Jbw6cbE6su2qszP4NzY3eDFubvByL67vMPm0dHhgsaxgOD5ubqzsYCc7N7EqcXussPJ5PGB69rP9uzZy9DMzdvPvMPZzdbz497Cr7KnpbfE0b/UytPf2ca44dPCscnVz7+/wa6lvcjNw9bs0+jW64KR7tbM6t7Dztbe0Ibk087c7NXHs7awn7qtn62npqy8qrO7wN7N2e3j8ILsyczvvNuagP3q0f315tne08XU4+nHv7vKydDt2dfuy9jUwcLAzNPPy7Pb5tK54uzZvdLL2s/x/9nW6eToz9GG2rvvzM3L2MzM0dfayNHl1sK6zuTq4vbogvnQ19/U8/ji8ePt7N3Ly/rGucfAxr/H2NPk4tXf3+Di0Ne6tMCjwLfH0YGHidnOgMnLutmqtcexnJ+nvKzSxuXNy8vPwLbDwbXL2e3M2sjDr7K7xOro48zc4vOM693FuLqp5NLCvdHG6fDl2vDU6di+ub7BtbXAvrCnucqElPS6uL7L1fXo/O+EmfrZ5d6KhuLw8su31uTg447a2djs4urxlYuDhubw7Y6SieS/t7/NgLy9vdrW38vgw8avrMDgz+nNvrHHxOjTwNDHxsXFxbq+wM3tw9fWyMW1wLKono3Qx66nm4uPgJeSnLHRqsfHz7yyq7Syt7HF4PX89e/83MXBwqy7raWfpueorsDIvcezzaWpnZWHl5+Wmpu9pZShm5WoqKSjoqvH1NirlcG7rLa0gMfTuMHfzd/c0Mu+1eLnuaahvOfTwcOyoKWhpq+ruaa1t7HZ3+DHuoH2wrCqxtO1w8/Axb21yL7V6s3b0K7L18nL0OPevs/bwOTR8uzN18zU1qqrtMnXztLc4dTc5MrHuLe6xvLb1uHg4c7F0/b66oro7ebd4fOA+Oj4hOfa6eu8gOTL3tzeyrSD1cnB2Miz3IHT4NHOwrTT1dOB1vmN94OGnPSJ8r+S2drMwc3K2MnKmZS/tburz8/s3eXz9+nf6Obr5/mFkPrcrMTUv7+t4oGB5q+Aw8fd7OLJ5+/k18O1zdPqxsLLv7y0vbrEvsLSxNXa74Dn29zv/NPGmr6xsLvigJGaiJiWf32Gh3SAiXtDTFFKb4qGZnGPg3B4dl93gol6cGxiYE+NfWlucHhujXB/kJ5wi4aGhYyXjYdtYllpXGZ5dmx1hIJ3d2dhdYCokXVaV3xqVWl0YGxeZ4KgfmZyemRob1VXWF18npd4cHVzhXxkkHtXbV9qdXZxYXV/jpx1gH1/e254YG+gV4xqYHmge2RXdXtiYmxjZVpYdI2dUI+aUlNUhYpbYXR1d19eW19ui3ZweWdbXFKObVxieE1NZ3xSZ2aPfWJ2fGFrdkp7cm9pXWFgU1udoJJvh2hhjoqCqWyopYmJpYdxZIWAhIaHkIp6dXN3d3FrbHSShIKTXHxvgJy3d3l0blx1oZF1XXWXX2ptg4pOh31zmZN+dXZtbXdyZmh7eH2XiYlyYFtRUl5pb1xpYm99gXdriX5yZHZ7d3BxcGFccnZ3c4OKeImBjVBdlIJykopxeIKLhV+XhoqWoIx/c3FpXnFjVF5bVFZhU1xfY4J2iJuXpWGogYaocolmgKeWfZyfkoKEdmt4h4tzbXF9f4qgioSYeYWDdXZ2h5CNfmWFiXlkhY19a4OBiIGhr4yGkouMb2xTeV6YdXhze3BsbnV7bHWKf29seIyTip2HU55ycXhtiYd1hnuDhH92d5x1b3x3enh+h4CMiHx/gICCeYRua3ZgcWZuck5PVXRngGVlXoNVYG9eUlxleGyKeYt1dnJzYldiYVduf5d2iHh2aGlsbIeBeWBweY1dl4x6dn5tnYp9cnhviId6cop2iXxsZ2tvamttamJcbXlWa59qZml0do2AkIpOY493eXFLSnZ8gGhceYeKlWCHhYCPf4SLWVNHTHF0eFFZVYtvaGZugGNnboiHk4GWen5nYm6FdIl4cmd3d5Z8ZnNrbG9zd21vcHqTanl0ZWBbal9cWE6Le2pnV09RSFdRXGuDYXZ4e25oYWxqaGJvfJGUjIeVeWhra11uZGBaX5ReZXN5doBzjGpyZmBSW2RaWlt5Z1hiW1FhXldXVVtwgIpkWYaFdHx2gIWGbHeMfYuIgn11ipqac2JedZKCeHxyZ2xsc3t1hHaEhYGmqquQg1uxg25rhZBve4h8fHJrfXSGmYCLhGJ8gnt8gI+IaX6MdYx6koh0fHV4gWNobX+KfH+BhoKQnYSFd3V1fqKLgYuNiH92gJ+ailOBh4SAiZpWq5qpXJeNkZBpgIZyfoKHdWZYgnl1iHhkh1WAiHt2bGaCiYRZkqpjpllbbKJfpYJco6CShot/gnRzY2JvbHJohoOhjpKanZeRl5eYkZtSX6WMY3KBc29klldRi4FTbXKKnpeAnZ6SinptgIicfnd/enNtc3N+fYKSg5KSplmTi46cqI6GZIR+e4KggIKId4N/ZmRwdWZ0fm09RkpDYXhyVmWFf254eWN9iZOIfHhyb1aXjHd2c3dshWVwfYVYc21tbXeCfHxqY19wZ3GCe2xvfIR+gHRwe3+dj3JYVYF0WHWCbXJcYHGJalJlcmBrc1tcWlxykoxwaGxsgXlhintYcGRwfYF6ZXiCj59zgIB+dmhwXmeSUIBhWnScgG1dfoZvcXttcGNdc4eUSYSLTVBQipRna4CIiHBsaWt1lH96iHlvbmCWc2RqgltggZhuhH+gi2t+g2VudkyAdXFqYFtJREZ1dWVEXVBKbGdiglmBe2dohW5eWXl3gICBhoR0bW5ydG5oaG6KeniESWVbgH6MV1hSSUdihnheTmqNXWxxiZFNiH10l418dXVxc391Z2p8eH+dlpiFd3Zua3J+hHN8cHyGhXdsjIV4aX+Ji4GDg25ic3Nzbn2Hcn93g0xVinpvjoVsc3Z5cFJ7Zmd1g3RsZGlmYXxzaHVzcHeFdn5+e456fYZ5hEyGaXKTaYJfgJ6PeJudlYmMf3R+jo9ybHB8fIadg3aJaXh0bHJ0hYuJf2mLkoBoi5KFcoV+hXyeqomKlYuLcXBUd1uVc3t5hnyAg4qThI+ei3t0gYyRiZmIU5pxcHNnhIRxgXeBhoN7faJ4bHZwcW1yfHaDfnR7fYKGgJB/gIx0hXR2dU1OUnFmgGhvaohhcYJyY212hXWQgY98h4WNfnN6d2uAi6GEkoSAcnR3eJWTh257gZJblIl1bXVnln5yaW1jf4R8dIx7kId2cnZ6dXd8eW5mc3tRXZRkYmZvc4l5hX9JXIRseHNPUYmSj3Nkf4mHjVt8fXmKe4CGV1JIT3l4e1RdWpF2cHV+gHJzeI+PlIecf4JrZ3OLeI96cWd8f5+KdoN8en1/gHNydX6Zc4WEd3JremxqZ1iYi3p4bGVoW2tkbXuNaXx+gndwanJwbWRufJGUjoyZgG5zd2d3bWhhZJZka3l/eoN0iWZsZWJZZ3Fqa26LeWt1cWl6eXNwZ2h1e35XSXNyZ3FvgICHc3mOf5CHfXh2ipaXblxTaIR1bGxiWWRmbXh0hG96e3ORk5N7bkOVdGJgdoFibXdqbGJcbmp/kn6LhWF5fXRydoiDanuDZ31siIZ3hIKHj3R2fJOciIWGhXqHlXx6bWpqb5eBdIB/fXNmcJGRgU96f3hyeYVIinqHSXhzgoZkgIVvenx/b15RfHJvgnRjiFWAhXhwYldxd25KcIlSgEVGVXpGdlVAcnJpY2llbWNmW1psanNqhYWfiYiJh35ycWpoY3FATo18W3CBcW1diUxGcFtEVl95iYNqhYyCemdbcHiRc3F4cGplamdvamx1Z3V4ikp7cneIkn14WXlxcHWWjXqEe5N6AXuqegV7e3p6e7R6AXuUegZ7enp7e3uoegF7hnoKe3t7enp7fHt7e4R6AXucegF7iHqCe4t6AXu6eoJ7inoBe516AXuGegF7t3oBe5h6AXuqeoN7rXoBe6B6gnuKeoJ7hHqCe4l6AXuHeoR7Bnp6ent7e/96rXoBe8d6AXuGegV7enp6e4x6AXuHegF7iXoNe3p6e3p7e3t6e3p6e4l6gnuSeoJ7iXoFe3t6e3ufegF7jXoCAgQAgOPy7vXs59zXz8LI0f74k5idgcKJwOHg68vboZ2fqq7PtbK6z9DJ3ca9t7etpJmtyuG9udfHwdTDur7Nvqyvxbm3xcLIwLrEuq+hoJ7HweuY0raWodHJyKujxOyG29nQ6eDXzc3Ovr2678vB6fKH9oeB+PaE+eTh2v3bsbC9xs26gLKqqK7JzeeU6crI3tbWw8HJubiruZ+7rcO5wNXr7bSXvN77+fT80+6+ubyytqu6ub2qqKimqsbLtbXE18ugqKSjoqWRrL/E24m8+4T+4dHDq73Gw8C5+5jLltTc077Wu+vp9f2H0dTZj4Xk6ODchOHmw87Ds77F17juq//n7MvYgM7v4dfN7eeC5vaY9s7WwLy30+z7p+/s09Dmw+vFzOvg39PUx7KvuL3AsKSfw8nO14DWpMPs3rra0dff/efn1OH4sbrJ47rGyNLXzt7b5eTq9NDZ49uE7PLohIXy5dvW0MzI5KuTl6C4lJ2pnK67u7jIrNmus8/t9Ofj2+HK14XTgP+Dg9/08+G2ysvM2r6tx77D4Ly31OeDlfDTy8O8yOXd2PGG6d/N3+vN1uHM18rYyLm68ur65o+Q9+jf0u3i/NXGzsbOvd3o0tPN1cu4wYb04dDE1M7Z7eP01d3U6OHO6Pfyt73T1uHn29Pa4+3x3uCW06GqvqmuubC5vdSA8uLHgMDSzbe1sKywrK62s7e9qai9rLnGt8G049XZx9LN18a2wq26y7/p6su22/eA9unLuJ2bqqeovMXG4uPc1NLg08e9vqS2q6zLtbDLv+Dw79zf6ODw5ef17fzv0crNzPODmofn6fDS0tvo4OTL1v/w7v339faXiIiI4Prn3dTZyc67gMTPx9Hnys7Fs+S2sMXJ3PHi0sO3t8DYvL/Bt73Mx83T19jnwdLCucDHvrKvnrvdyb+evqeHgZKSk6rItbDJ4M7Jp66wtrTC4ePn1uDPxsHAqb+htJ2ZoamQlp+bpaCfopygn5SQnpKJi46NjoyUppurtr/pwLm/zd7RxavBp7/RgNrLz8nS6cSit7+x4tvGr5+RorTSwa/Evau5wLicnaG3uMnAtsi6tKezq6G/y9W1uMnMxcHQ9M6xsb3jtLLJ4t/a9uLSxN/f3/nwgNTD0dDIxrfFvszLwuPSv7vJ08/GxLrP7fbd4Ou47/rH9u/0iJWVh/eB5dn+j4vq/eva7N3QgM/Byr3Ls77bzbm4xLnN09TUyMrPyr66yInw3uX44ZmTjvagnN/N2tjUxNrQ6OfK9t23ts7NwNv15+Py7Of9kor66vPgtPHP3M3KvsTCvtPf4M7i0tzp4daA9fX7zsy/4dTW2cCxs7KnqrS/sbKvv8fe/ejd0eHa2cqsrse/1NDrgJOZjY+FfXJuZFtYYYd6UlZdR3dPbYmOloCKXltfZGyEbWhofXdvhHBvcHRxbWNzhpl2cYl3c4l5cm56a1xjd3ZwfHN2bGRpZmFZWFp/dJpsgGdQW4F7el9UcY9Senp2joaEgX+Gd3Nyon5xh41Qi1BHh4NNjnt9fJuBX2BtcXtugGlgXGN7e45hjnR0hH+Cb251ZmJZY0pkWWxhZHWChlFMXXyckpGUcIleXV9aYFxvb2pZXWFcYHqDaGd0h39WW1lXV1hEVV9da0xxg0mOeHJtW3B6fXVvnV54VYeCe26GcZygqbRklpqbaV+RioiHWYqUe4R0aXJ2hGiWfqeTmnyIgISklY6GoZVXmqJqnnmCaWRfc4WSbIiIdXmKco1scYZ9f3qBeWRcZm1wY1ROZmFkbEhzS2qOf2WGf4GNpo+LgJSiZ299k2x1eXx/c3x7gYCJkHRwd3VMg4uKWluqnJuOi4aHo2lUWWB0UlpiUl9qaGNqU3tYXXaWpJiUi5R/h1R/gKdXVoedmohhcHR5h25fd3BzjW9ofZBTXYx4eXl6g5qSiJ1YjH5qd4Rteoh8iH2LfGpllYOSg1ZZkYd/dImBkXdmbWhvY32JfYKGiYNuclqUfmdYYl1qfXWHcnZ1hYN2ipmRZGx7foaNfXN1eICEd3hbdldgdGdweGtwbn5Pk3lrgGR0bV5iX11jY2Vwa251ZmZ0ZGVoWl9QeHB2Z3R1fXhocV5kbmWJiWxaeJtUoJt/dWVjc3ByfXx3ioJ2amdxaWVlalpoaGp+a2N2aX+LhXN0eXSBeHmGfZCFb2psZ4RIWkpxeIJtcYCOkJF6hJ6EeX57cmxORERCZ4eBdnWDcHZigG55fIKbhouEdqBsZXFxfZCBcGFYW2V2YWdqYGZ0cHN2f4OOb4RwaWl0a11dUGyLeHVbeGVLSFNMTV92ZGB6i4B7XWRmaGRvhYOEdX1xbmlrXG9cblpUWmRPV19YYllgZGBmaF5cZ1xXXFtbXVpcZ1ljaW6TcGlugJeJfmt/a3+JgI59fHd9k3ZecHhvmpiCbV5UYnKIfHKCgXWEkYpvbXSLhZOJf458emx0bWB6f4lrcYKGgX6KrolzcnebbW2ClZeUppN5aoGBhJ+aVHpmb25jaF5sZnl6cox5bnB8hYJ8e3KBm6CGhI5mlqJ4npCHT1lYUI5MhH+hYF2VppaPm5KAgIN4eXN/anCMfWpsdm55f36AdnV7dnJuf2CnmZusimxlXZ9tbpWDk46HeIZ7jol0nolsc5CPhpmxopabj4uYW1KPjJaId5p6g3+AcXZycYSJin+Wg4+emI1app6deHtsioOLkXtyfHNsbHOBdHNtdnyMoo6Ad4eBhXpkaYJ+jIuhgIOHfXtzb2ZlYFxcY4Z0TU1SO2A7TGl1gniJY2RrdH+Yf3h4iYN5i3lxb29pYlVleoZhW3NjYHdtY2V3bWFrgoB7hnt6bmhucGllZ2WHfpRhhnNcaZiSiGZWboRKZmVke3l7eHmAcmxokG1dcndGekc/e3dGgXFzc5d8WV5tcn1ugGthW19yc4NXf2dpenuAdHV+cHFpdF12antvcX+Kh1hBXHuYko+VcoloZ2hhaWNxc3JjZmprboSHbWx3i4hkbmhpaWhUZG5qb0xqg0qVfXVxX3J4dWled0dXN2Fvb2J0Y4iKjJZSc3R1VE11cnFzUHyJcH5yanNzeWSMZJGAfWFsgGWCdWdhdmpCdHxTh2l3YmFheIiWaYmIdniIcY5tc4p8fnmDfGpmdHyAdWljfnqBhlKDVnWXiGqHgomTrZqXipmsc3qFm3N5en1/cnp5fXyCiW1pbGZDcnNtSEmDdnJrbGxxk2JRW2iBYGhxY3J8fHd9YX5bXG6DjIJ8dX5tdUtugJJNTX2UlIppenyBkHNgeHJ2jGxnfI1PWYVscXN0fpaQhpxXkYd5iJZ9h5B+iYGOfmxrmoyZiFpaj4N7dIqDmH10fHyHfJuij5OOjoBrbVWSgW1ibGRygHeHcXZ0iol7k5+WY2h2eoKJfXJ2eYKHfIJdgGNwiX6FiXh5c3tNjXdrgGd/gXN3dHN5dnh/fH+EdniJen6HeXxrkYeJeYWCiYd7hnR8h32em3xogZxTopx9bltaZ2Bfa2xogXx2cXB+dnR2d2ZzdHiLd26AcoeTkHt7gHmIf4GJgJGFbmdpZ4ZMYFKAh5B8fYiRjYpveJZ+d4CAe3ZUS0tIbYuEf3+GfYZ0gHyFgoefiY+IeqJvaHV3hpmJeWxnaXKIcHd5bXWBe3l7g4KOcYp8eXiHgXJyZX+ZhoJphnVbWGFaX3KFcW2FlouJa3Bvb2hwhIF/cHtxcG5yZnxjdWNgZ3FeZm1kbmVnamRmaGFib2ljZ2lpbGlvfm94fn+eeG5tdIZ5a1ZrWW55gIJ4eXZ9lXldcnlxmpWDbV5QXWx/cmRycWd5hX9mZGV5d4R4b39vbF9qZVx1eYFkaHZ2cGt8mXtqa3WWb2x+jYuEl4l9cYeBe5KPT3ZqeoF6fXF6coB+cYt8bm17gnx0dGx6j5J6d35WiJJokomCTFZVS4NGenWTWFaAjX13ioJ4gH1zeHKBbHOMf21ud299g4J/cG9zal9aalSGcnWGZFFPR3ZOVW9gcW1sYW1leXZljXxjboqLgZaqk4OBcm95Rz5paXNqWYFveHV4bG9lXmdsbmN4anWGfXNMioqPbHBnhX+Jj3pvc2xkZGh0ZWRYY2d4kXxxZ3h0d3BaYXhzg3+TjnqGe7t6AXuLegF7kXoHe3p7e3p6e5N6AXuXegF7qHoEe3t6e4t6BHt7fHuJegZ7enp6e3uEegF7i3oBe4x6BHt6enuJegF7m3oBe6R6Bnt6enp7e6N6BXt6ent7k3qCe4p6AXuTeoJ7lnoBe6F6AXuLegF7rnoBe7J6g3uSeoR7/3rUegF7o3qEewd6e3p6ent7n3oBe4V6Bnt7e3p7e5p6gnuEegF7k3oBe6d6AgIEAIDZ7vn4gYTQytG0sru3vrngmM7Ax9j5/te7usKkl56otsPLzJXL0dHI3badub+2wLK9vtbFzcu5t7++xMiopqrTzM24vdH/gee9vLWlna66r8Cml4+jpKyVp9zrgIDt+N/V1/Xb3NPN3taq3fzW4YaX7/Xn/ubL2uiI9bvIsqDIrICqqaavuc2jkpjE3f/G6LSmvLq9qMW8sqq4sLDA6ebo2sKxxNbDy+rZ4M3Wuqy98+e5wMKtsbSxmLC1ttKrusrHs7Cp0/zZ0bvHk+rQ0tHZuLC1vresrsHKxYa8hv3vzt3lv8j9vtPXwdTblq/6593Ex9Dt1MfvjOTm74WY5P/Y+4CE3djaycjAvbiFj8qM/ezPscPFwtbev9byxfXt68i8t9Lq4trEycGvrLepxMK5tsHsz8PDx8jP1cfUxc2/w8PYz93GxOC5wcG9x8qwjqatx8HS/vj3k4TPgPzfzIDJwsCjkt6tqMDCy9i1tcTH3da+2KCTm6HGzc/Zs77ax4/+i4DUiZe6vrbGw7PN0sO3vMa8uMW1tcbg//rb1NO21e/HzMLW4u/PydvAv8DWxtHatay3t7HKydDT++rV08eE4OnP6uTj9IXX3dnPvruvx8Tc6fyBztLOzend5+DV4ujh4NjYvKqqzODozdPG0uPj0cDLwNC1urm0tLzDysXA6+TGrICotratrq6vtK+4pcTQtL6wssHDurTBzeDdyO2JudnIxsi8v63IzcS+usXr74Hiza/Hprmwz8jAw9vGwMfGucPawNW5tra0q7i4z8LQtNLV3uri0d3u17TCydbazOb0+PmGgYKGhpGAgo2H8u+B4+eC9eDw+oWCg/Pk3OvfwceGyIDE2Nz83dHGwqzXya2uusff1tvKw7q3t6SfrLK8tsXD1NrP79jG1c+9q6iqpqamrNC0zM6+i5ynnZ7E0b+zuM/KuLfBwL7Qu8HY4tTQ4bfG0rrDrpuWkJKRk4iNl5iro5GLlp3Zlcecg4yGlI2UnK+lpLfB7NfS1s/j4/TDztG404DIydnnyLXy7NjIwu3Qw8C5qrm4wtHXysrIwsW+u7S3q6PCw7izqLWtpaiwpqC0v+Hdu6nK183AtKS9orGlvMHylIWC3tDa1/jv1N68sr/E2c/Q0dXY49Kg+uPM1/DJ2uvc59Lg3vOI+ef98pyW/Jup+/HU2ciGguiNg/ry4u7xzYDVscXazsXbv8zOwoHVv8DIz87a6/nK2PLx8vCB5Mfw9YDn3NzLzLPF1+LE1+Hn0/nXybmruNPh3Nzv6P7h8PP/9+THx+jFuLG1w6682M/CycfLz9XR3efY5+zi7YzJztrNvqS4s62jpayrrqymtcO01t/b3uTs283Oua/Usc/a6ICFlI+JSElkYWVRUFhWWlh0WXt3fHuYood0dntlXGBpcnyFf3F0f4N3kG9edHpzfW92dIl3eXZiXmZlb3JZWVqBeX5ucH+hUYZjZ2RaVWNvaHlfVExfYWRVYYmQS0mBj316f5mDhISCkIRbf5RxeExZfIF2j39pe5NboHF7cF2Aa4BnaGRpcH9sYWNuh51wjlxSZGFlVnBnX1hoV1Vfe3VvallLYnZtdpCCgXR6YlVnoJdma3JdYV9gT2VudI1lbX94Zl1UdI10bFxkWH5kZGVtV1lmcXBmZXR1c050TY+JcYOPc36pdouQgJWjcH+pl5J3eIKahHGSWoWGjlRpjqWFqYBcjoiIfXpxbXBXYnpfqZF3X2htaoCJanyUbpaQjHJiYXeLhHhobmdeX2JaeG1gWF1+X1hfa3mEiXV7dXhsdXyTiI50e5Brc3NudHloTWBjd3R/nZONW09sTqCHfFuJgn1iU5RfWW9wc35jY2huhX9vf1FFT1d2gIGObnKSf2KpXYCFV1ZlbWNsa111fG9iZW5nYW9lYnKJo56FhYRkgJZ6g3aHiZJyZ3poa2x7b3p9XldhY2J2cnJ0npJ9eHBThopugHd4iFF1gIB4c2xofHOFjJ1Pam9qZHtyenVreoKBhoKKbGBheomPdnZrcoGAcGRtaHZpbG1qaXRyc2xoiIZrV4BYY2pmampobWpzZ4CJb3pubHNwZl1nc4B+ZohRYn1sb3BlZVdrbmdiXWiMk1KKfmd7X3Fvhn5wcIZqYWRgV110aIBtaXBxY2xqfm5zV21vdXx3bHWCclpobnyCcoOLiIdJREVLS1ZJTFVPf3xDbWs/dl9laT9AP3RrZnVwXGFNZIBmfIShi4N9fWuOhWVocXaEe3trZVxcXVNSYGhvaXRxeX53kX5ueXZnV1ZWUVZYX4BwjIl+UGBkVFVze2ZbZnpzaGlyb2p9ammBhnp6i2t6hnN8bF9cVlhVV09QWFxwZVlWX2SeY5FoUltXY19maXRrY29ymIV4fX2RlKR4hIVxiIB5domYeWugmoJ3dZ6IeXVuYnJyd4uSi4uKi5CJiIOIfXKLjYF9c31waWltZFpqcImCaFt3f3t0bGBzWmZba2uVY1VPenWDgpyQc3hbWGRkdGtrbnJ8h3Zil4RzgZd2hpqMkn2LiJxbm4CLhF1Zi15rjox2fXdeXJ5kWqidlaGnhoCOcYWViH6McXyAcVaDcXR6goKJl6J/h52an6BYjnCUmVCPiY6Fg3F/i491ho6Uh6mShnpxeJCRkIyajpN9gI+bl4x4fqCAd29xgWt1jIuDgoKDhoWBiZCIlZmIkVpydoV8cFtubmtjaG1vdXFncnpphY2IiZOXhH1+cG2ScoiZnYB1hIB3QERfYWtfYWlnZmRwTFxXV1hwgHRudX5sZm53f4aJg15yeHt1hmdUZ2thbV9jYXZoampbWmFhbnRcX2SLf4BubXyWS4Flb21kY3KDd4hyZmF2dnZhaZCPR0NzgXFvdpN7enZueG9FaXxcZkJQbHNpgHBba31OhVtpYVR4ZIBmZWJjaXdbU1ljfZh1l2pidnd8bIZ/dWx4amhzkISAdmNUaX1xeYt+gXV4Y1tsnJ1zeoFudXVyW291dY1seoyGd21lgpyDd2VpUn9tbm12Y2JpcG1fW2VmY0NWQYJ+anuFam+VZXZ8bH2BXGaLfXhnanWPeWuKUH1+g0dceYpqi4BNcm5wZl9YV1lHUWZRkoFtXWhsaHmCYW+GYoeCfWlfX3WIgnpteXRscn1ykYJ3bnKScGltcn2GinuEe4B3g4aajpF5eo9pcnJudXhkSlxcbGdzjYZ8TkJRPXxnWkdmYmZQRIlbWHF2fohraXB3i4d5ilhNVVt5f4CHZGmDcFaUUIBwT1VkcGl8e2yEiXprbHRrZnJmZ3SLopyCfX9mg5p9hHeFiZZ5dIZ1eoCTf4qLa2FscWp9eHp7nY57d3JRiZJ6j4eIl1SHjImDeXNld22Ci59RcXZxboR9gnxxfYiEiYaNb2FefIyTd3pvdYSFd2x4eIl7gYmCgoZ+enBohoNpWYBbbHl5f3+BhX2FcYiPd4V6foqLgXh9hZGQepdYcYp8gYV7fm6Dgnt0a3SVm1eSgWd6XmxleW9jYn1nZGpqZG2Gdox2c3t7bXl3jHyAYnp5foJ+cnqJd11ucoCHdYqXlJhTUFFUUVlLS1JMendEcnRGhHF3fEhHRnt0cH5+ZWxUdYB4iYqrkYqGg3CQiGhpdHyLgH5wcWtqbl5caXB8eIJ5hIp/lYR5hn9zaWdoZ2xsbYx9lZOGW2hrX2OBiXRqc4aDd3mAeXKCa2h+gnN2hWR0gHJ/bmRkY2VkZl9gZmZzZ1lUXWOfaZZxXGVgbGduc3x1cHp7nIV2eXCBf45kbXJfeYBwcH2LbWKTi3lvcpqEeHRuXm1ucICDent5en95dG9xZV54enFvZnJpZGdsZFpqb4iBZ1ZzgX58c2qAZ3Njcm6SX1JPgoKMiJyNcXVgYnFzg358fX+Ch3Nfk35sfJl1hJyMkHWBe4xQhHJ9dldUg1dli4R0eGtUUIlYS4yBfYuTeYCDaH2TiICOcXt/b1GAcHV6fXqAio1kaoB8fHxGcVp9g0d+dnlyb15qc3pfbHd9c5V9dmpibIOJgn2FdHlkaW92dGxaY4VtaGRnc15meG9kZGNhYGFha3RreHxzhVJpcoN7cl90cW1jZmdmaF9XYGpadn54en6Gdm90Z2SIZ32LjYR6gnuKeoR7j3oBe6J6AXuUeoJ7kXqCe4h6AXuNeoN7wHoBe496g3uOeoJ7inoGe3p6ent7hHoBe4h6BHt7envIegh7e3p7enp6e6B6Bnt6e3p7e7l6AXuHegF7jHoBe8l6AXuQegF7s3qKewZ6ent6enuEeoN7h3oBe/96wnqDe5R6AXuOegF7hHoFe3t6e3uFegV7e3p7e5F6AXuPegF7hHoBe7t6AXujegICBACA14H3+P7x78u7vsauucKu8YWcr8aSjO3Nx7SVp5amq7fh7tzFr8HAta6qpq2iqL6sqMnf8viAh8zB1MzRvLO+tMHQyMzH1ejs5c2lndz7vruzrJ+aubCun7PN8+rSx7SY9Pfeucrd4s/R25SX3/WPjNzs6PX29O7w7v/48d7e46aAur+6rJ20scTH9OL1+4WG5dTTvp2rq6ytssvJydbsgrylrbHg1NLNy66ln72+urXBybTF1rW0x5+2tZ+rtLe0pqeoq7X8yoi1wbGrwLnLt7m8vrmwscu7xLu01peDgsWV3Mart7C30MTol/7Tztfg3rXS393W6IPIzdHZ4u//3+qA8d/qz9vezsPhj5SFiffbvMnBs8+8stK92u7byburysnM0c/Bxd3MysG/uNetx7miqJ+ps8C2wMTDvrK8v8LG7+nGqMzG4bG3v7WupYubtMHGztP89IXR1tDK3dnn1OzOuLe2ubfW6PXf6dKsv/q5qPHhqaPSy9LNrM7AqrW53OGAv57Rxr/P18rNy9vCu7q5w7jFurbJ5cjK6ufA1sTS08/I0dPRxpCH6LW0xcnCucjB0c3g2uTf1cW4z9DR4dbYzOT43+PT5tPJzsPTvcW9ubnN29Tg1eXm8ODY0cHCwMbd2ID8/+jR7trc1+PRxc7LwbXWy8jf1bTDvb+2vdHqxdGAz7Spory5rq+uwa3K2abAuc7i1rKvsMK7xsTXxMDFzra/07a9zdbb0d/R0NzX7OHFx9m+4/GD7M3i4M/M1cre0L7B28bSvbS6us6lp7ityMbNxcHRxsW79OnV88XQ5+Px6Oba9t+PnYnlxcXQ7PXs4tnU8fH13Ort5/KE+OX0juKA29zwmPXzyLrSwrqopL/H0dPN1+vXwrrIxLPBuLq6w7m90LvBxsTFvKOlzd3TvLW+zrrGyKmcq6mpwcHEsrGv2sTEwq+yv7Svvdm5ztvF1eG1sqORgv+Aho2Klqy2o7uutKWdtre6opKYh4iUpKq5t7O21u3e0d3c7/6G4erQ09SA2N6I8N+/2fDl482JzNLRtqHGy8rG2Mjg1c3gw8PI08ueuca4v7q8y8O8vKrMgorOubHCx8KvtLa0s6y0qL+9v9rq08zo08bHuqrE69Piz93P7Obk5OeB8+jaydrewMzY0cTd5N72/OPl9NP294nRi5KK7fzygon1gYLt5IP38uuA08jL09by1+Lg99X8j9DHzMDL+dT67PT9+uzg4umA5NXpy9TUz8S6vKrK09/cw9PJ09DD28/W2NPX5fTq8NbQzdvWwM7QyLnOtrO6ray0wNT+4ILx4OXr9OLVy9/T2srNv93Dp7Guq7Gxp8Ds1MnOw87Q3e7mjfHS1caz1MDD29OAj1qgm5uGhGNXWWFNVlxLfkldaoBZU4l2dmlSYVVhZ22PnYVvXXF1a2ZraW9iY3ZiW3eKoKZWVXJqend9bm17c3yBeHlzfYeOg3FXUomla2NfWlRSa2lkWWd8n4t2bF1ciZeDZXSKjHt9imNjgpBZU3R/gIuPjY2QlKSioJeWnm2AfYF5bmJvaXh5loCOkkxKfHJvZVRfX2RhZXJraWx7RVxRV1+MioaAe15ZT2NfYWFuc2Bzf2BcdVZqZFRcY2VmW1tbXGidbEtUXFFSX11pWF9nbm5pZoBwcGVbclVFRnFffW5hbWltgXidbquHhJGXlHKMmJSNnF18fn+CipOgh5OAm4iUf4qKeG6MXGJTV5l2X2peUGZaVG5if5GBdmhcb290eHZscIB0cmlmXHpVZVhGS0JSWWNbanJ2dG12dHB6n5h4Y4mElmxtdGlkXklXb3uAhIKjnlh5fn56joaZiKGEbXJub3CLmJyLkn9dbqZpWpaLWVWAdoOBYoFzaHBzjZSAdV12b2t2fXZ2eoFvZWJjaF1mZGV8jHh4lJd0iHiHfXhqc3N3aVhOfFldb3V0bXdpdW55eYWIfnZtfnZ1f3ZtZXaJeHhzgnd1d3KAcHRwa2Z1eW91bHZ1fnF0a2VoaHGFg1KkqJN+lIyHgYl/anJua2B2dG6Hf2Z0a25ha3ePcHeAfmJeX3R1aWdnemJ7g1h0b4SOiGhjXmleaGd7bW92e2trfmFjc3R/dYN2eIJ5iYBrbYNumaZepXuOhXFna2RyamJqhHKAbmZqZXpWVGJacW5zb214bHBkk4h0jWZsfn6GfHlwjXxdbV6YenN7jpB/cWtme3x+cHh8dX9LiH2IU4GAe3yMYJKVfXCJeHFeXnR0eXhyeIl6amd4dWh3bXJ0fHJxgG10dnV4bVlZfId6Z2NrfG96gGVeZWBhcm1rYWFgiXd5dGVlb2ZhcIRqeoV3hI9ubmNVTZNKTlRPWmtyZntyeWtmeHh/bmBoX2Bud3V3c2dpg5OFeYCBlqhdm6WNkIiAg4ZakX9mfpuQj3pefYaHdWqMkoyMk4qgk5Cgh4OFloxmfYp8g3x/iX95dWd5UFiAbmh5f3hkZ2tpY11lXXNtcYaRd2yEeHN6bVpxkHR4Z3BjgXl5gIFNiIJ3Z3iAanWDfXCEjIWWnYOLlXaTiFGHT1VRhZWTVl+lVViZj1admJKAfnN5goWbfouHmnulZYJ8g4aQt5i2q6u0qp2OjpFRkH+Qen+BfXNna110foeJdo6FkpCFmYmNh4KCipuOl358fIaIcn+KhHeHeX5/eHN9ipK0j1abio2WqZqMgpCFiHh6d5J7YHBrZ2tqZHaghnp9cXZ2gY6NXJJ5enlmint/oZCAcUl/enxwcFpWYnBfaGxcgEFMUGJBPGtkbGhXaGBtdXubo4x2X25za2ZmY2RWWGxWTmd7jJFLTnFpdHB2aWp1cHuAdHNydoSJgnRcWYqkdnBxbWZlgHlyZnGClYNuY1RRfI58Ym18fmtsbVNPZXdKR19xcn2BfHp8foeEgHt9hVmAbXRyaVxsZXJujHmHj0tKfnV3bF1rbW9rb352dXiDTGRZX2WKhH56dmBdU2trbGpzemd7hG1qfGF4cFxpcnVzaWtqanChdUZdZFtda2hzY2x0dm9pYnViZFpRZ08/P2VVcmhaZmFnemyNYphuanZ7e1t1gX97jE5sbnBwdX2KbHeAfmp3Ym1rXVRuTFJHTpN3ZXhxYnhpYHNjeod6a19VbG5zeXhrb4F6fXp7c5FsgHBZXVJdYWpianB0cXB5fn6Cp6F+Z4iClWpqcmdhWUJPZW1zdm6KhkliY2NecGl6aoduXGNkZmeFlZ2OlYBecahxZJ6PXVmDd4OBYXprXmZogoeAaVRubWp5hYCCgot3a2dob2ZwbXCKlX5+lJV1joCPiYN5gIN9a1lPhWFoe4OGfIVzfHV8eYGEe3Bnend7hoB8c4aXioqBj4B8gHiEc3ZwaWh2f3d+dH9+hXt6dGxvbnaJhFGcnYpzi4OFgIl+bnl4eG6LiYKelnuDdXRmbXuTdoGAhWxsb4iIfn57iG+FkGR/d4eXkHJtanVsdnaGe3yFjH2CkHNzgIOMg5CEg46FlIpxcX5oiZRSi2h8eWxsdXKEfnd6lYOTf3V5dotmY3BofHl7eXaBdnZsn5KAnXB3i4uSh4N5kXhYZliJbGhyjZWHfHZxiIiLeYSBeoRNkIOSVIqAh4aQYZaWhneRf3tjX3N0eXdzgJWHdHKBf299dXZyenN1gG92fH19dWRmiZaMd2txgnR+gmhgZ2ZrfXl3a2tqkYKDfW1td2xjcINmd4R0go1xc2leWLJaYWViaHN3aXxzeW1ogIOHeG10Z2d1f4GBe3FwhZOBbnJvfpBQgIt6gXeAcndTinljdYyBf21TcHp9aVl4fXl3gHiRhICOdW9zf3JPZXVrdW9zgnt2dWh6T1Z8a2Z1fHxsdHh5dG1xZHVta3+KeHaUh32AcWJ4l3+Ie4Z3joN+g4FMiYV7boCIcHyLg3OBhHeJjXN5gWyIhE93TFFOfomES1CNR0d5dUqKiIiAc2twe4SegYuJnnyiXHx2d290mHaNgYOMhHdycnxEeHCCbnV6dmpeYE9lbHF0YHZweX1zjXt/fHRwdYB0eWJfXWptYHF3dGZ7bG9wZmFgZGWBZT9zY2dzfHdwZnVleW9zcIx6YW9raGlkXm6TeW50aG9wfYiBV4RsbGdWeGlshXQCenuOeoZ7n3qCe6h6AXuKegZ7e3p6e3udeoJ7j3oBe6d6AXuUeoV7iXoBe4x6AXuSeoR7yHoBe6t6AXujeoJ7unoBe9N6AXuveoN7knoFe3p6enuEegF70XoBeaR6AXuHegF7iHoBe6F6gnunegF7lnqFewt6enp7e3p7e3p6e496AXuQegF7tHoBe4l6AXuYegF7inoCAgQAgOfy25L44PWzvrChqsnTyKey0oaZ06uDnoGwrLS8zLi60tKrtLu2q6utm5mtsr394+ibkuC+wNvp5YHU5Nnq38nBt7S10bPP7/bhw7P7zra3oruwqr6xwbfI28zz47vmj4zUyLa70dG/z9jZgI3UvsqT/NHQ9c3O3JijwpT7gdnAgNCsr7q42bDK8urz4vvn9+TE6dGzqLmguLrV48zT1dq1rqq1uaWzwqy02e3Z8s+Bhc+4v9axrrGjoKqssJ29yMLzvLjH2vrI1cTNx7OpprG2v62bwK6nmqKo09veg+Ls7+fWuL/OuLbHuNfEydbr7djk9ebm6+z044Pm39jq7ejygNfo6+6rx8Gvt8u4tbanhe2/kpmdu66226muyefBu7TEs6rhz9ffxrmpq6nIt8LBtLevvNDKt8XIzNXo2NOtsqe9raLJ6s3HtK+tzp2Usb3a0LbA6sDMv4SGsrTEx87Y2dO6vc7AvcnRgIPc1YGKgYn368q73ODM2MbKuLO4wbfJgMvPzbe53fe8yMTZrLTItaq92M60yLW9v7zOy9r21tvf8dXg1dDq1tfj2d7Xv6++vPj11NLe9d7YyL7G17u7r62/ytHXzuTHzdfV08/c3OHD0snd0djH0dDO2NnT3NHF4e3by+S3sLi/zsPeybHexdHgv62208bQ1+rZvLvhhfLNgLLFn5iQmKStvsfB0NyyxM7V1NfR55rc3szk6dLVx77Cz9TGvsLSg4j/1dvf0/PnzMTG14Lm5+fc3ePXvsXM2dHftKqpp6WrrqikqLGwuMXDtr/AttLRys7hwcTO0dne7ODY5pWQ/fb06PDc99rTwdfXzNPN19/L4/Px7+PauMjYgMPR8P/h/9i6qr6ypbngyb6+1MfN19jW1LG3ubm6rqy0vqyuvtTY0cOtv9Xe3vHU0NfGvKunsrCyr8PBuqmgt+TN2d/bx9PEv7GysbG2ub3HvsGXiqWPgoGNj6K3rpyZlo+nk5GOlpukmpGTnZ6bm7TsgPDygeb349DA3L6wo8HOgM7D+ODaw8jTgfzJ+L3i5Ke+sLG4usnM3ebo3b3A1dn7iebD0NrY0Me4qqin5LTav8DDqJysr8Oruri6zun11cC4r7yxuMjHx766s7XRstbr0Nrfi/Dj7If20snW3NDGvLu80cDCgqig6sjxg8vH7cXugv3G5uOA2t3xzvHz8OaCgNz62ejh4urk4+SBg5Pzgfn43cyk8tTU9pD40Obw5PLo3rLOu9uO+sbCiNri6NO/1d7KxsjnzvXc1tPZ0/vS07zD48XOw7isqKSqsc3bwdvcgfqDgPHR8OaE4dr8rbnTx73Ds6ust7nHyru70Nrf59K+t8zX0d/J1tjf19HS4fDugJiahV6WfZNYZVlNWXZ7cFRXcFFklG1OZU5fWmRteWtogYVka3BtamlkVlZlZG6tlJVuY4JeY3yGh1N9j4OTi3h3b2xqgGB2lZOGbWWpgGxoVmtfW2phamJvfW2Iel2HW1uAeWhsf4BweYB9Tlt7aW5fmnNykm9xf2JvgmKoVpCAgJFxcH54j2t9mo2NgJOBj39qjXlhXGpZaml8hnFubnBVVFBaYVdkbVlbeYdzhmtNUW5aY3lbXGNaXGdnalZteXCXZWJqdoRtc2hval1UTmBndGpcg21kV1pcdXVyRWt1e3psXWZ2YmBsZoBscH6Ul36JnI6MjY+UhFOIgXuRmJWfgIeYmphed21cZnZpZ2heU5NrQkREYFddgVlhfJNyZGJoXlmGdXuCenFjYl90ZGloXF1SWW9zaXl9e36Uh4NqcWd5bGOGnX99aGRkgmBYbXeOh293n3uCd1dbb3B5f4eLjoZxcn50dHx8UVJ+b0tWTVWNiG9jf4J6hnN3amhsa2d3gHl4c2huhJ5tc3F7XmJsXlNgeG9dbV5jZWZ0c4OXfoB6h3B3ZmJzaW13cHJzZ2Fyb5+bfHh6ioF/dnF1hm9tXlhmbnJ2coxzeYCBgXqIhotyfXOEe31ucm5scW9tcWpjeoh7cIZgW2hsdm2Cclh/bHh9alxlgHaCgpODYF17UY9vgGR6W1tVYGdvenhzfYNWYWlucHZzhmR4fWuDhnV6a2Rrdn1zbnV/Wl6ogH+BdY2DbmxzhFSUlpKFgoR3X2FpcG5/XVlbX2FiZV5aWl9cZnBqXmlqYXBybWx2XmNrbnR7hnpzf2VfoZ6gmJuKooh7Z3d2bG1rb3ZjdIaEh3pyVGJ2SGptgpJ8lYFyaHdqWmaCa2JgcmhueXZ3eWFudnmAc3F4fm1seIaGfGtcZXV5eo13eYV/enFveXVxb3t5cF1XZ454hYeGcn92coRpgG9zeH93ellSZlZMSVVVY3FpX1xaVWhcWFtma3BrYmNraWRfbJROjo9Ri56VhnuSdm1nfoJ+cJp/eGhre1CnfrKBp6hvh3t8gH6Ii5iZmpF1cYCJpl6Od4WNjouCdGhmZY52jHl+gG1kcHB8bHZua3uSnXpoZ1pmXF9ycnJsZl9ggHRWdIBhZmtLgXmCU5FwZ3N3amZnZ2yAc3RXe22WeJlUeXiSb5BSpHGPlliNjqKAmpuVi1F9l3yIgICEhYKATE5gmFCgrJWLbKmUkq9mq4eboZKhkIlhd2d+VJJuaVGDi5mGeZOXiISEm4WehXp1gHmjen1ucpN7ioh+enRxen6YNKaMqqBgslteqoymnWCXkK1ycoeAfoNwaG10fIiLeXOFhoaLeGRdcX19iHN/gZKDhYKTqp+Ad3djSHdmck5dXFppioyBYFplQEtkSzhMQVpfbHWEdnSLjGlvcmxnamZVUlxZYZuEh2NcgWJlfIWCTXKBeouDd3NraWl9X2+FiINxbqqOfHpkbmtoeG10a3iFcYh1V3tRU3JuYGN0c2ZtbmpBTWZUXlGKaWmKZmduUVtnT4ZHdWaAemBmcGuCXXCJfH5ziHSCdmKKeGhndWV1dYmPfnx/f2RhXmdsYG51XWB/iHOGb01OcF5rgWhscWpnbGlwXneGfqZxbHJ9eUdxY25tYFlXa3N7bFx+amBRVFJraWhBY210cWdYYnRmZ29ofmllboCDbXeMfYCEhYt8Tn10a32CfIaAa3yAgUpjYFRicmRkZ15TlHVTV1hwZ2uJYGV9jm5kZGxjW4V0fYZ9cmhqaH9vdXJlZ1xkdnZpc3RydId+fWhuaHxxZoWegoBsaGiFX1NnbH97YGaHZ2thS05YVmBia3V5dGNncWdncnNMS3BjRlBLUo6JcmqEiYCNenxsaWprZHWAdXVwZ2+OqnqDf41qbnppXWiCdml+cHl7doOAjqGJjo2ehYt3cYZ4eIB5fHxxZ3Rvn5p6dHWGfHZsZ3OGc3NnZnJ8gYR9lHeAiYqGfouGiXF+domEiXt/fn1+fHl9cmeBj4N2kGlkcXWDeox5Yol3hYZ4bHSNh5OOnIZkY35RkXaAboNoamdvd4CFfnmHkmlzfIB/gXqIYHyBeIqQg4Z7dXuKkIZ+gIhcYKh/f4V7k4xxbm98TIWGgnd1enNiaXeDhZZ0bnB1dXR0bWdobGZxenRlcnNofHx3dn5nbHV4gIWPgHeDY1yXkpOGiXyVf3hofX55enV+g299joyNgn1fb4CAdHyQm4CUg3Foe3BgbIxzaGV1bnV/goWHaXN6e31ubHd+a258j5OHeGlwf4SFlnp4hH13bW18d3d4hYR5aGFymIOPko15hnh0ampoam5wc3p2fF5Yb2hdW2Zmd4F4bGloYnZmZmp0d3tyZGJpZ2RicJpPjYhLfoyEcmqIcGdfeXiAeGmYfHRjX2tHj2iUZoiNW25pa3Bvd3qGio2Ea2NzeJBSe2V2gYKBfG9lZWOOd4ZydnlkYHF1g3aFe3aDl5t9aWZdcGpse3t6c3FtcYhrhZJzdnlMhHiBU5B0b4CHe3Z1c3GBcGtOXFyAZo9PeHeSb41PmGZ8gUt1dIBgeYF4dEqAb4tufHx+gn9+fUtNW41Kj454bU2Cb26MVYVnfYZ7jYR+W3RnflGLZ15Jb3F9al94e25rcYhyjnNpY2pjiWdoWV56ZXR0bmppYWVkdX5hdWhEez8/c114dEhycYpUY3h0bXRmX2BjZ3N1ZmNyeHyEc2Nfc3x3gmtxcnttbGx4iYAEenp6e456h3uYeoJ7hnoBe6V6gnuKegZ7e3p6enuHeoR7Anp7sHqCe5Z6AXuVegF7m3oBe5V6AXvIeoJ7j3oEe3t6eoR7/3qOegF7l3oBe5B6gnuLegF7r3qCe/96jXoEe3p6e5N6AXuXegF7jHoBe6V6BXt6enp7jXoHe3t7enp6e4V6AXuEegF7iHoBe4p6BXt7e3p7iXoBe4x6BXt6enp7pnoEe3p7e4R6BXt6enp7o3oCAgQAgOf12/H9+uLIzc7Kx8G9t7643eyYspbtj6fD1O6B5tKrzYXEkaSvpqacla2s0NfS/YGPgYb9iJncyOvX29+B3Mi7wLmiw8HBs9ji0MKN1aywsriwnMDFxa+j1dnr4vDzgo3+kr6ynrW+yNfYzYKF2d/MwcD47ri3zvfFrZbs59q2gLSpvdja+tq09MW+pb6Djfbs+oazk6uptJKc2/bu2frYxcK/tJ+tw9K1stzp7tzz38/Ayo/prbKgt7iks7rky8zmxsrMvcjQ9u7/mYXgxaHEwrWtx73y3sDnncTd8vv+juzn1tnN48HEwdPW0tne5+uC79XX6c3Z5dW29uDk68/lgIGA2YCE2NLFwL2rt72it8G7t8PX2rLB3K2yv7jJzqyZurLE1dK+wr2tqJey3+b338OwuMrHvdvSxcLH5tfUn4aipb3R37Ouq7fQ3q6qubW5udLJpsist9P/q7PM3uSz4+bZ2M/81dvX6sPNgPH74dTTvbvJ1e7Yxq6woLPMvrC4gL/Aw8Sw2cmjx8a3u7HRwrvR69y9rLazucLDyM/g1MHg3MS+z9zm9OHo6cK5wrC91OHI0drj37yyzs7G1djHyLG6vL7S38XP1ePe89Lq4t3R0uHM0O61xMrXydHX2NzU1u7s3tvFwrW4ytPl19DMydHWrKalrdLW1M+1wMPZ1dPQgLPH0qiupbO+srq4sdix0NTt9PPs3OCD9cve58zGsrzGtqrCwqmvxvzy2s/Y1d7hsLC0ubS5wLja1sPCwc7Zzde4tbnJra3cyq62ur6wwLa0wcrEy8bDssrb/dTN4ufU4+jh3e33z+L39OjIyMrXu9/TytXNzcrh5fzn8oTk08PMgNXl6fT56NCz07ijsr3e2sS3yMW+zMzQ0OTVrL+zsrq4taiur8q5vbm6r+XPwr/SwcG4vMHKus7UzPHMzsbL2+TX2OLu7P+Fyb7XuaqoqbCwuc/EvbjEvq6lrrSxr5ypnI2ZhYGSkbLAsrmiqaqkpLjDube5ycnSwa+zop+vkq7MgMPK2d3ZvcfL8NDO0sHX6sKzyNbFube6yb7Ox7XE3OWB+Ybh0uHj2tbHy7TWuL62v4bEsKisp6mpr6Cqv9bVy7bEp7vVyL++v8K+rL6xorjKztLY8vrS//zO0NHCwtLlyb3DydLR2vDl+c7T68faydns6uvszt3h6Ozp5fLui5KAgOXf5eDn8t/8hIT1/Ofb2on2y8nE1dXS0Nvh4u6C8enq3Lu5wNrN3viL3crp2OXGw+3Q3ezxzdjWyuTe6/KGwsPQt6GsqKS6paSktrm12fXVgoWH/dPO5vDx0szN/cDEy7rIwK2rwsbJ3tGBztrD1MXYwcjAxeSghIfw9cno+ejcgJWch5WamYVtcHFvbmpmX2JadX1acFmUT2Ffa4ZPiXtbell9T2FtamthXHBpg4J6mktYTVGXVF15aYp7foZVjoN9fn1sgHJqXoOOeHRbhGNmbHVqU2xoZ1hRenmEd4WLTFijYW1jVmlwdoN7ak1RcnNcXWGRjFxheJl2b2Gbm5N1gHBocomEnnxbhmJVQ1VIUIWBj1JiTF1fZEhMdo2BbIduYmJiW1Feb31mY3+JjYCXhHZncV6RXWZabm9ZanKXeniSeX5/a3F0mIyXYFOGa1Fydm1rhXqtk3mQU215iIeNU316cXp4iW52d4eEf4eLkYpRkHR0f2x6gHZbkIKJk3SFgFBOdEhLcW9laGhdam9ZZWpjV2Jtd1hlgVxibWl8gmVVZWBlc3FrcnBmZlptjo2gim5cYnBrZ39/eHuAmIqFXkdfZHmPkWxnYmp7iWhne3p+hJKKZ4JpcISsaGuCjZZ0mqCVjIOsh4l9iXB2T5CZgnNvYmNyfpmEdV9kVGiBb2NvgHZ2eHttjoFgeXdpamB0Z2BsiH5rYGJZWmFmanN8bFlzcl5caGxzg25zdVtXX1RgeYx6gIeNjHJnfXl0gIdyc19oZ2d4g291d4KElnmOiop+f496g5Zkb3J9bHF3dXlzdI+IfnxrZ15hcXeHfnVycX2DYFpdZoKIfHdfYGFtbW9xgGJ8iGVrY2tzb3RqYHhWa2t7e3l2bXVKiWJ0fWlnXGd0Zl90dWZqiLiumYaJhIyLY2lueHJ2gXiOiXZuaXF7bnliXWRxWl6AclhgZGhdcGphandtcGhhV2qYlnhxhYd0fnt8eY2Vdoynq5qAfYGJc4iDeXxxbml5eIhzfEh3amBngHB4eIaQhnhninJlcHqPg3FeZ2lkdHN4eo+Han5xd357d251dYp6e3FwYpB4b2p9dHp6foaQh5OUh6SAe291g4d7fIWYk6hYd3GIcGJlaGtqbYB4c2t3dWdiaG1tal1qXVFdTU1cYYSTh490d3JqYW5vY2ZqeHuCdWVtZGZuWGt+gHV3f4B2YV9jh3d+hniQo4F1iZGIeHZ6hHqFeWRoeYJNklKDe4uOjoyCiXWYeIB6gV+DcGxvZGNpa1tecIWBd2h4XWuDcmlvbm9sX29fTFliYmpugI5pk5lwb29fZHWMc2xwdnp2fY2FmXd7kHSHdH6PkZCOdoaHjo+LhpGHUVpKgH96gH5+iHSQT0mLko+IiVuwgH99houGhY2OjJZTj42LgmNian93g55eiXiTi5x/f5+IkJuafYF7dYSCiZBVcXqIeWh0enmIdnN6jYyLprqhYWRisI6EmJ6kiHt3pHN2g3WDemdsgISDj4VQd4Bte2p7Zm5lboRgUlOblnOJnpuMgHF4Y296f3JjbnZ/gH97c3BkdW5LU0dfO1Ndb4xRjoBgfFZ7TF5pZGRdV2hheXdwjkdSRkuLTlRyY3psbnNLgHdxcW9ddGxoXoCIdXBZiW93fHd4Y3t2cmJZfnyGeH+ARk+NVWNbTWBrbnZuX0ZHaG1bXmGQildZaoJdXU97fHNagFdTYXl5kXNWf11RQVJBS356i01lU2ZobVJYgZqOepZ6b21rYVNgbndfYHmBhHyRf3NlbliGXWRdc3ZicXmcgIKfg4eCa2tqiX2OWU6BaFd7enBqf3KdhGt7Q1xqeXV7S3Bwa3h4inF2dIB9c3N4fXdGf2xvfm94fnNVgnJ0el9wgENDY0JFaGdhZmlda3NgbnVxbXiJkG54jWRmb2h3gGpheHN1gHpucXBoaFdlg4aWhG5dYG5oZHh0amluhnt4U0JXXnSHi2ZiXml9i2dgb25wd4V+WXNaXnGZVVZseX9ggoqDfHScen51f2JrSoaQg3l2Zmt5gpuKfGhrW22EdGdygHd3eHpvkIVkf3xub2R7bGZykIl6b3RvcHd7g4qRgGyHhHBveXyFkn+DiXFrc2RtgYx2eHqDgmpheXd4hIh2d2VtbW1/inZ2d4GAk3aLiIeAg5GCkKZzf4SPen+CgIB3e5SOg4NxbmZoe4WShn9/gIuPa2dnb5GZjYJlaGp2cnZ5gGiAjm53cXiBfIB2bopnfX6TlJCEdHZJiGl7h3l5cHmJe3OJh3Nzjbqwk4KHhY2OYWVobGZrc2yAfGtrbXmHfYx1cXaEcG+NfWJrb3BlenBncH12eXBpW2t7k3Zwg4d1gHx7eYuPan2XmYxzdHyKdY6Lgoh8dnKGhpaDjU+HeGlzgHyIipedkn5piXRjbneShnJhbHBrenp9gJWOboFydH5+eW1zdIl8fXh5aZh/c215aW5pbHB6dYKHgZ9+fXV9ipKHhY6dma5be3KGa11fYmZnc4mDgXmNinx2eoF+fG56b2FuXFllZIKLfYBqbmtpYm50a2xreHt+b2NpZ2lxWGx9gHJye3txW1hdfWxrcGJ2i2ZccXxzZmVsdWx2bVpcbXJCfUVtZ3h+gIF5gG+RdHtzeFt+a2lwamtvdmVndYeBcmFtVWiHe3N1dnl4bHtwXm53d3t+jpdumqB3eXtvc4Odgnd3fHlydYJ7i2p1jXSGdoORlZCKb3p9hIN9d4F6RlNFgHBrb3F2fmyGSkaEioJ5eU+MZ2ViamlmZGxtbn5If32BemJiaX1xeY1ScmF3bXtkYoBvd4iHaG1nXm9vdXdHWF5sYFNjZ2FyYFtebGhfc4BlQUNEe15Zbn2BamZplWVncGN2b1paa2xqdWpBYnBhdGl5ZWpgYnVQRUiAf1twg3lqk3qGewR6enp7hHoBe456hHsDent7hnoBe456AXuSegR7e3p7iXqCe4x6gnuRegZ7e3p6enugegF7lnqCe5N6AXuQegF7j3oFe3t6e3vmegF7/3qregF7wXoBe6J6AXvKegF72HoDe3p7jnoBe8x6g3uIeoJ7hXoBe4x6AXuLegF7lHoBe5J6g3uXegF7i3qDe4d6AgIEAIC/hpv12t2R7+2Ex829u7q8sNby2puE3ePiyaG0oLXAssncvK62t7aus764u9LSwbLhgOr3+I/76efg7cvL2NjG4cKfpcLJs7nd18C6q8++rbrGlqnMtKG5ubDn3ci2rrK7t8avqqa7z/fTx4rt7+nTt8X9/NXOt8XnwcTt7MTXvICxsdO90ISJ1drO3s3F7vf0/dnbsKSpp7OXpa+z2OTf3NfV1sXL2NXIuba1xsnZguTlxr3ehM/Aw7WUl7CmrLy70OXx4K66x9zh0dDn5eLO9MPAur3R6piMhf7g7+qe8YqgmZmKwqW18ebAzsbBvbPM88DI3uHj1d6+r8KwxMnIxoDP3NOA2+/kuKOvqKewsLKXobKorLu9wb+7xby+zsGoq6WfsbS3tbiiwsmoq73p8fb24K+7wsTR3fr17IHuyay6xtDVrLi6r8LWzfa9xObEp+DSqLCevbnBxs3br8m2pcjOy8XF5uXz/4Hr58vQ4N/oyr6wvLW8tOzi19LBuKCrpoCyvby/1LrK1sDFybysgt62xN/KwLW4oMq4x8PN7eOy7djR1+bx2cvI09XOxci+w8jK09qB7PXSwLHNx727zNHXzM/W19DdzNbW2vDX69PHyvSC5O3s5tPJucrQ79Pp2tfq2tHU0ObU2fjn1/PXxc2/1Kepp77syuPdytTP2P62zIDAw76zut2pnbm2tsTH0tnP6u2ByNXF1tXGzNDUwcK8qqy2u725uLO7wcvW5Nzd1ebM3OO6w7/Cucm6usPcwszCycW0rLmvq7O8wMLLw9/IttnwxMvNzruwzfbd4NLortnY2tPT5+7xhNqA28TFvcPRy9fKwb7J4NTo5uPX5+THy4DKz4CE+uLDycKzndLPvNzQupbBuN3Vw9bLzcXA2d7Ox9HUuaOturu9ttHaycXCxuHTwcrDzdbG2M/20srX29vQyr6zrLO+z7SmorGsv7GqvdXl0q66u8Cqt9Csu7KroJqenZainp+nurOxq6Gus6KyxMeruai3paKZp5uxwbzZ0IDP4e7ezLfR29Xi3PjW3cmxtN/hyMbDsMrXyN/S1tDh5uvj793TzcbGn6KWqbDCpJmYt6al0Zmao6WrwLrIy8zI5cayhfTGvcrNt7iqrp6o09Hc+6jozs7byMXPz8e40ILh2cTP5tTI9fPl3s/b49CIi//85efbkO6Dl4fy44bd1IDSxtf0+Jv8/YDr9PmF6Mvv0si3wsvd1dTJ3Nbb8dPJz8jI1bLM4NH06PnPvdLX2djg29Lm5eLJ3c7s+urUiPK+tcSirLOxrZi07OL4+oGB5I3+6+Dw5+Xo4LTGx+Xq4KLQr9L8wL6Bzrri1+fYwbSjtMfJzvbK8oT89I7V1vHJ14BzWGmXcXBUgH9JYWVdXltZTm2Ea1tLdn9/b1RjVmd1ZnuLd2dud3hwcXVybXd1Zld9ToiUk1ySg4N+kHuFmJ6RrZBtboaHaWuOiHRxYoZ3ZHWAV2OAaVZkYleFfnJoZWp0b3liW1xtgJx5blaDiYFyV2ScmXpxZHOMbWuQjmx/boBmaYFyfVRZgHxscWZefouKkXt9ZGBhYGhQWltednNqbGhpa15renpvZmVlb3GAVIeDamF+VXNobWNTUmZbY3Fvg42ZjF9tdYuOe3aLjY18nnVya2x4kWdZVZx/h4Fkh1JmYmNbeW98pqB+hHx6em99nXB0gYOCe4FsYXFleoKAeoCAh3pNfoyFY1lkYmdydHFXXGBXV2JjZmpodGxtf3ZjZmJYYl9bX2FSdYJlanyXoqGei1tpcHF9h6Oal1Oef2h1gYuRb3N0bXmKfKN1fqGGcquXb3Flfnl/hpGed4x7a4SEfHd0k5CdpVSXk3h7jZGUcGFXX1tfYJGKgn90a1lgXoBmam1vhG96i3N0dWRWTHpaZYBwbmhiTW5fa2VuhnlQhnFmaX2PfXJtcm5nXV5cYmlvdnlNjZmCdGd/fnJuf3+Acm5zdHODcnt6gI57jnhtbIxOfYZ8em1nW2RrhW6Bd3eIenN2doZ1eI6AcYx1ZXFoe1taW2+Rcn94ZGxob59edIBueHRrcpRoX3VxcndzeXRqe31LZHJpfHpycXZ5aWZgWVxobnJycXB2foSHlomKf4l4g5Rvd32CeoZ0c3iJcnVrdGldVmFcV1tjZ2lwa4BsW3eMaHB1eWthfqSJjX6QVnl2enN3jZmjW5FZk32Den2Jfol4bWhsemt3dnJtfXdgYoBja0hLi3lldHZrWoeLdI6Ga0xpZIF+cYJ4e3d1jY6EfYeMdmRqcnFvaHZ9cGtocYuFgY+OmKaTm5CxkISJjI2EfXFlYGdzfmZaV2NeaWJfboSThml0d4FpdolpeHFnXVdcXltqaG98j4qGfW90cmBneoBmdGFuZmNea11veXWQjICIkpF7aVlqdnV8fpJ5f3NiZ5CTh4N/b4WMe4t3dWx6fYF5iIB7eX1/ZWtleX2MdGlle2lpjVtZYWJmdm94eHdyjW9hV5t6dH97ZWxiZlRZdm94lHOBaWp1ZWJsbGVZcFCCfW54hXtsjYl7e3GAhXhZXaOgiYZ8WotOXlaUiFaDfIB5b3+XmWSfqFWWoapen4Ogj4RyeX+UiYV8i4eNoIN7f3t7hmmDl4emo7KIeI2OjY6Tj4CTjoZvd3SLnpODXp15eIJvc3t+e2p3oKSuulxbpmq5ppOnoZeWk2x8eJOZjm6Fa42vgH1chnSVgI6BcmlZaHh5fqJ2mFKVmVmDhJ2Fi4BSQ09rTlZFcHlIbXRvcXBrX3WCakxBaXN0aFJiVmZ2Z3iCa1xhZ2hiY2hkYmxrXU5xRHmGg1GDdnRsfGhvgId7l3xbX3t8Xl5+fW1tZIN5aniFXWuIb1tqa2SOh3pqYmRsZ3NdWVdpeZFwYk16gHltVGScnX1zZG+HZGJugmh6aIBfYXlrdk1TeHNobWhge4mIh3+BbGdqa3BaZGZpgoV8fXd3d2dwgoFyaGdha2x7T3hvXVd0THVpcGhYWGtiZXFyhpCZi19rcYCFdW1+f4F4nnZ0bXB1hlxQS4drdXFYc0VVUVFLYlddkI5xd2xmZVhqh15leX1/d3xmWmpZa3RxbIBwdm9FcoB8XVJfW15rb25XX2xkaHR2eHp0fnR0hn1pcW9mdHBsa2hafYNhX2mBhouOf1JfaWp0fJOIf0aCbFRgb3yHY2ppX218b5Vna4lvW5GFY2ZddHB2eYKGYHFiVm1zbWlngH2Jjkd6d19idXyEZ1pSYF1jZJqSiYV6cl9oZIBrcXNyiHF9kHp4fW9gUoRkbYd4enZ1YYRzgH2Cm4xhm4h9f4+fiX11fH55cHNvdXd6fHpJh5J+cGJ/fHZxgYKEdnV6fHiHeH98gI15h3JobJBRiZuTk4OBdXuCmH+PgIGQgn1+fY5+gpySfpqFeYZ9kG1xcYOqjJiLc3lzdplhd4B1eXpwdZhtaHx4d4B+gIN2iIpPa3ZpeXZwcn2Cd3p2b3GBhoaEgoCFiIuMl4iIf4d3gZBpc3d8cX5tb3qNeoN9in5xa3ptZWhvc3V5cop0YX2WcHZ4fGpedpyDh3iJUnh0dG5wfoWNUHxQg3N5dn2LhZKDeXR8joCOiod+j41zcYBwelNWm4dweHdtW4aIdIuBZ0ZqZIOCc4Z5f3hzjY6FgI2QemlvfX19c4GDcGpjZXlxZ3Rveot9iYOmhnyEiYiBfHBnY2l3hm9iXmhlcGZhbYmbkXR+ho96i5x+j4d+dG5xcGhwa2pygHx5cmhxd2lxiIdud2V1amZhbmR2fXOFgoCAjY97a1ppb2x3dYdrc2ZSVnt9b25tYXiBcYVxa2Jtb3BndnBvbnR4XWNfcHN+ZVpXbmFkjl9faGxyf3V7fHVrg2VcU5l8d4OEc3lvdmRngnyDmWyKdXmEdHOAgXpqgVeOhXN4hXNjh4F1dGx7gHRWWJeSe3dpUXpGWFCGeU10bYBrY2yChViFiEZ8gYlPgGeCbWNWXWByamdebW14j3NscnF0fmF4iHeQipJzYXNzcXR6eG2Be3FdY1xufnBgSXxdWWhWXWhrZVJfgoSJjkVDcE2HdGV4c21zdFVkYX+FelhpVHaXaGVQb119cH9zZV5SYHFyc41kg0V8e0xpaoJkaQp6e3t6enp7enp7inqCe5t6BXt6enp7tXoBe496AXuJeoJ7p3oBe4V6AXuheoN7hHoCe3qFewN6e3ucegF7t3oBe6x6AXukegF7pXoBe516AXvAegF71HoDe3p7mHqCe/96wHoBe496AXuLegF7j3qCe4V6CHt6e3t7enp7h3oIe3p6e3p6enuwegF7j3oEe3t6e456AXuGegF7kHoEe3p6e4V6AgIEAIDO9fiW5qK3+IHR1NbMvsjIwtbz546B29u74MO0qszw4vLyybG32r6loafU5/fr4cfN1MbggoL07vzR+Nu5vrGrpqigj5/Gq5vW0rrGuLi2tbnTsqexwK+xx8XZtMOssriitb28qcvCs73Ct8yEgvvr0Nne1OPU3czs9/CYge/4zoDQtqjw89Pd5ujl2M7Gx8bGv9W+y9vPtcWsuo+gr8DCu+DIztaxrZ7JtLOxsMbb4omH6MHyhuuCzL2qtrueqbLE3cyvw7C3u9LKxrzExsjIxuKqu87AuuXagoiF9ImQ+oeH1syUlIuayPvp07XF177Bx8m+5f7ezMnCwLrX2r2sv4DOy/K3tsLE0723qbrE1/2E6tvn08/b2Mm7s7C7v6uinru6qsvAwrO889mznbvn1czH6sykucvZ2cfj38TIwsi3ttrOysjDzf7S04fM6vb03OjPoJ+qvdK+x8/JwrLEvrnBrbfI18Sw1ufVz4TthPaJjY/nvMfI1NDn3b+osKmsrICzuLOxqLe9tq/R4Myty9bcws7Zwc7Vyd/p1bq+xbaltbzEtNLNx6rPx8PRx9PGz9bQ0efYz9jbyeDN06a44uHU0ufs1NXQ3NnZ1Mzd0+XHxMyuwLfVybCmtrbB3b/Y2MfN17m7sL/Dw9bKv7yyt7G1v7mvqbTEzMzEruDY3NvG0ICAu7vhv6i1uJ65wsPG79jF9JiG0eno07nB8d65uKukudCeqbK2w7690tXb19nm+fv4gtu7y7mxuMXJtMfTytexy9r5t7Osv8zPxszJzMjAvLPX4+DzzsDJv9DGzM3SusHH5u/64eHr/Nnn0cjGzNLHzsfS38zHx8rlxtba1dLWz4DNz9bNysjN2967s6yzsNXY37DAv8rf0d/ByOPyxtbI4OXXy8nBxq60wsHT0NPY3dbQx7rT3+/g1tT93tDr3/zp2Le2rKy6t7q++ua8197w+8314rekmZahtICcrp+ooJqanKemuq6cj6Khq5adqrS6t5CMm5mroKygrq63o9fAu4DFt9e/w8rc0M7O4sjRy8Shv8rOw8KwwMG94de+0dTQ2Mbp++Tcws3ZyKmvkrCQiJiRrqy1yKyspq6+zL7H2uzo7b2x1PGE0LOss7SZuMjYxq/U/+rDg97DzsDLybm8x9fe6b3Qz9/P29rT1sbe7OKF9/Tn++6A/e3x6fnn2Na3tYCxydP6k43d7ubf3NDBwMfDoaevh93y0NzPxNPk94Tm39vk18mL4eHi9crbw9vbz9Hl2ujb7vvYzZHa0dr28eWyqqqgmaa1wr7IuYOA6v34gYHu3+jp8OPF1rfb1uDmv+a+vt7fxsi9up7ey87VxqiUpq/M1oKAuMC9zODY3+vn9ICAo6prh0dTjklna21mWmNhXGyEelNKeX5niXRnYXyWipeTe2VxlIVrZWWPkJWFeGJqbmaFVVKSkKd8nYp0fHNvc3NqVWCDaVqOgG98bHJucHiLbmFocmBicnB/ZGxfY2xdcHp5ZIJ5a3FzaHVUTo6BaXN6cIB1gG2FjYhcS4aRdIB1ZVOWmnaBhn95aWVfYmFnZn5sgZiOeIRseE1WXWRgWnBlZ3RbWU5xYWJgW26Jkl9bkWyZWZJSfHBncXZcYmJqhnhncmVsbYF5d3N0e3x6ephodX9vaop6TFJPiVNZlVNQcGlhZVtafa6iiW97inFwdHRqipx+bWpoZl98gm1ib4B2dJJcWGFldGdfW2p6h6dXlIGJdnSBfHRqamp0fW1kX35zWnhtbGNpn4tnVnOain59nYFeb3iAeGmEgmxzeoN1c46Kf3d0fqmCglmBmqShjJODXF1ldIl5f4aCf3GFgHp9aG57jHxoiJeJfViPU5tXWFaAYGdrdXKFeWZXW1lfX4BmaWlpYm9tZ151f3BVcHl+Ym17bHmAa32HdF5jZ1tMWmFpXXVxaVFxcGt2am5kaXJvboV4dX+Gd4l7g1lgh4J+eImOeXl2hICCfHR9doZ2c3ptfXeHgWdaZGBpgWJ5gXZ8hW1yaHF0dH1yZGNfY11jbWpeWmhydG9nUXpzcXRiboBQaW6Pd2JvcVlzgHZ5kXZnhlxMa4aEfml2oY5tbGRbbHxWYGtxd3ZxhoiEgHuKmpuhVYt0hHpzeH58ZHJ4bnRZcHuaXV9caXF1bHBvdnVtaF+DiYicfHN/eoZ/g4iKcnN5lJuplZuhq5Gcg3p8foR6gXp+h3t0cXKCYWlrbm9ycYBtbnFsbGt2f4FpYGFrZ4qLjWFucX2ShpB1epKbdoF4iZGGfX13e2RndHOCf4OKjo2OjoOco7CfkIaojX+WiqmajG5rXVxmZmZppJFug42gn3qej29lYF9lc1thb2RpaGRiYm1uf3ZnYnBzemJpcHt9d1dSYVplXGNea25/a5V/doB5a4Zvc3iEd3l3jG1zb2pRaXmCe35te3lyin1ka2Zia2CAjX5+cYCMhnJ5YnthWmVfdGtzfWdlYGRufXJ3hJaQmXBnhaFYgGxqcXBSZHKDdGB9oIhkUnpkbmJnZllfaXiAiGR0doR3goF8f3CGkY1YoZ6QnpJQmZWSk6KUi4hyb4Bkc3mVXFZ6ko+MjYqFhYeDbG9xXpWtjJOJfo6crlydkZGYjINilpSWqoGLdoyIfoCMgYV9i5R6cF6Ad4Whn5puamllX2t9gICGdGBZprm3X12llJmYpJN1hWyLh5CWd599fJmkiouBgGqVhoWIf19OYnCNlFtYcnZvfYmEipGaoYBbdXpMYjA/d0NlcXp1anFuaXOBc0tDbXBbf29hWnSPgouEaVdignNeWVuAipGEdl9laF14TEiAfItnhHFaYlxbX2NfTFdxXVB+dWVwZ2ptbHKHbGBnb2Fic3OCaHVkaGxbanRxXXt0Z2xtXWlJRYJ2ZXN8doV3gGt/g31SRX2IbIBuYlKPl3d+hIB8b2xnZ2hpZnlqfY+FcX1seVNdZ3JvaYB0dn9lY1Z1Y2JcVWN5fkxJd1eCTIFKb2tjcHNdZGlzi3plbl9gYHFqa2Vnb3Fzc5JlcH5qYoNzR0xJfU1QhUpGX1ZTVU1KZJGGclllc11eY2ZhhJp+bmxqaGN9gmxhcoB6ep5hW2FldmRdVmJve5hPh3uFeHuJiIF2c3R9hXZyc4+CbYl9em1ynYZlU26Ne2lng2xSaHJ6c2Z8eGFoaW5hXnl2a2Zibp90clBsgYJ/bXdqS09aaoJzeX92cGJ3c2xzYWdxfnBbdn1vZEp3R4ZNTk5yWWVreX2Th3RkamVqbIByc3JzbHd4dGuAjH5jeoCEZ3OBdIOKgZadjXd6e2xeaWx2aISAeWKBfXyIfYJ0e4J8d4l8eIGGd4Z6g2Bri4iBfI2Pe3x4g4CCfXiCeId0dXxqfX2Sj3VtfXWBlXeJjH+CjXN2bXd9fouDdHRxd3d6hYJ6dn+Nk42BZoyEf3xncoBRaG2SeWt3f2Z9h39+k3pqh1pJaX5+dmNwnJFydnNtgpFyeoSJj4uCkpCMhXuHlpSXUIBnenVtcnx8a3uDfIZrho6ucHFrd3+CeHx4fXxxaFuAh4GRb2VxbHd0eHx8YmdqgoiTfn+Kl4KOfHR7gomBjIeQmYqEhYeaeoWFg4SEgIB6fYN+fH6EiYtuZmVva4qMh1pma3mOgo5zfZage4Z9kZuRiYeBiW9zfnaDe3x+fXNya194gpiJgICgiHmRh6CTg2loYGNvc3R2qZd0houam3qhlXZraGxygV9te250b21qa3BwgHdmXG1vdmVocXp8elxca2VzZmphbGx+bph9cIBzZHtiaG16amxqfmNqamVKYW94bm9fb3BshXtiZ2NcYlJse2xuYHB+fGdyWnNYUF1Xb2hyg3BwbHSCinl3iJaOkmdhgZlVgG9veHdfd4WSgm+IpoxqVIRyfW94d2ZreYmPmXF+eYRye3h0eGyFko9Xm5WDjn1Fhnx+gI6DenphYYBWYmiEU05leXVzc21qaWpjTE5QSG6DaHFnXnCFl1CFfoGIf3VWhH9/km59aX12bG55cXdsdHpcU0peV2F7d3pXVFZTTltrbmtxYVJKiZGKSEV3aXF2gXRfbVZvanR4W3tZW3l9ZmpmZVF5dnp+dFpIVmB6fEtGV11WYm9pbnN0ewR6enp7hHoBe4t6gnueeoJ7tnqCe416gnuyegh7e3p6ent6e6F6C3t7e3p7e3p7e3p6hHuoegF7unoBe6B6B3t6e3p7e3v/eo96AXuQeoJ7nnoBe/96qHoBe+t6AXuPegF7mXoBe4V6AXuOeoJ7jXoBe4l6AXuGegF7k3oBe5F6B3t7enp6e3ujeoJ7inoCAgQAgNXY1OeKh4zU1uX84dm/vMDD1sbQu9Lr5MHO48fGyYL23+Hi77q6wJacuL2uuLvewrSysbrF/o2C44Dz4Nvu/NG4vKS5pLa3uLO3p8KsuMa4rIfmtrKru72yzrSmwu64x6iOlLOvxbjLt664tb7Kioq8vtTI1OOetLPJ5ffj9Iv7gJHAr+DY28nH0uTb2O7zz7LC98G0nrvBsqOqsL6tx7yz4srBs7qzvq62r7HAyc/p2oDlwdeD/sjByb/Z0tG2rKK0sdHLruT84o/WybvEv729x93P08qrt7vk58O+yeTd9szcgfnm8v7HjOGZt7bR2vfU2c3W6su4ucfAt8TCra2tgMWy3LfD6f3ZxNDAvcLW+Ojn2O+F0+/46pLn3MKvqJ+kv6uywLK1u7Krv8OtlrTPvcTFsb3hwMbF3s7azsrW1d7FxrO3vM7T1e7lvsinxsi20NvOudDGyMnK0sbJ08/r2umyxbm4vcC90LTC1djgxqjAxs/PzLG6w8/Byr/Fv8G6gM/MxLatsb61uLbJyrSzzee6xLOswtfM6NS4ur/TsbWstrW2tb22rs+8vLy1yK640trJ1Mar+vfE4sjJvcbS08m22sLS0c3P0ODY2+XnzuLPqp6coaSW3LGvs8bTzNfExsTDvLe+ws7UzMLpwLa7vMG6sJyloLK/y9S51NPe5s7wgMP4w9jW19LDrLqonLfMxtuF8svh5YDbwNXLw7Kro6Oqp5KqssC1u8C/yf/b3fW7gO3MqKChmZ2nqbmgutCptt7869nIrrLRt7rHwsnEz8zSw73j//vB5If13bu+2d23utOC/+jS5evj3fO/0MvO18rAxcLL1eD24vyF0eTH39bhgNHRz823vczHxcq/ucbd0tXFpKjIwdi7rL+6vsy/wMbT2uz8wr/K3MzL0OXo2fPB19jhw8HFvNO/0crTx+f21ePvz8Gwrrm0trHG8djC0dnPwKm7uqOehpazsaG1rqCfjaOrubG5s6qzxJmVnrqurrOpqLO7qLTLq4ylqLi73sC4gLazvMXVwtHn1eKAnKmK28rX472judXE48j74ODd54WJ1c3f5t/Rx8/GuaGhoZaNn4+doJ7AybC7rMOsq6K20OHE3bzM5by1uru/qpmb093cyMPYv8/Q3Mm1xcnN4+XT0srMyM6949Le2+fh8ujm34PPyuyA/eLl8/v17/bZx7bEgMDckZ6KkoLe6M3g0OHYttjLzt/0gPTS2dnKz+j38djUh4SP8v/Lwryxob7RyMK60cne0YDz7uW+v723ob25sqqioqupoKm6wci15IPa1d7Q0sHdxdfSyMi6ptqL987JhYPT6NTX58DEycvRv+G4p6qpv67bhNOqu7vP2fbTyMvPgIeLhZBTUVBnZm6HcWxbWF5heGt2ZXyZmniBlnp5eFWchYaNkW11f1hdc3lpa2iMcGJhYGNplVZLf02QhomksIt7fmp5aHVuc3FzZXZscXZrZluebGRgamhfeWdab45pemZQU2Zdb2p6ZltmYGhuVFVgZHdveItQXFpqfol8hVOVgFhqXY2HgXZuc3xycoWJblhqnGxqXHh+bF5iZ29caltUeGhhVmJdaF5mZGx3g4WfkFeVcIFVpHVxeHGHf31lYFxuaXlrX4KbjFiCd2pzb3N1fJGGiH9kbnCSlXJobH13jmp5TpqQmKl8XpZpb2+EiJx7fnN0gGpZXW9oZXV3a2tsgHZkfltge494anNraXSFo5KOfJJVfZmek2CUi3ZmZl5ieWRnbmNlaGVfdnlrV3CHdHp4ZXCXe355iHd+eXmEh5V+gnNzdYWAfYyBY21adXtpfIJ3YnVpdXp7iIOFjYmjjZtmcmlqcnZ1hHJ+ko6Od1hpZ3Fyc1hka3Ztb2ZnaW1lgHNxb2hgZm9lZ2Jyc1xgdY5haV5dbH9yjHpeX2JvWV1bY2NoY21lX39saGFaZ1Zcc3ppcGVVlpdshG1tY255enVgfGh1eHR2d4N+f4iOfJCDZmBjZmRcmm1sa3iBd4J0dHVyZmdpb3J4c2eHZWJoZ2dgXktPT1ljZ29TbGx1f2mLgGiZcX+KjIt8ZHRnVGp3bn9NiWV/hk2HdIyEf3NoYV1fXUpdY25nbXNrcJV0c4x8TZN8aGNkYWRnaGxUZ3hZX4GjkoFyX2V9Z2l0cnd1gXh7aGKBl5dph1meiG5xiohmZHhQmoV2ipKWkaNufnt7hnt0enp+gIOKdopKaHRhc214gHBraGpYWnJ1cnhubHKDfX1xXGJ+eIp2ZXBqb3xua298gZOfcGt3gnd6gZadkax9j5yqjouIgpt+iX6KeY6XgY+WfG5fXmlnY2JznIJoeIaCdWd6emZiT1xzdmh6cWFfVWttdnB8fnV/i2diZ4F8fX9tZ3B9Z3CKc1Vrbn+DnIF2gHNub3Z/bHeKfIVSYWtae3B9kHFhd497kXWXeXVydktPbWd4g317eYaKhHN0dmxjZ1tiX1hqcF9pVWpbXVZogpR8lXiFkmlnb3mAdWZgjouFb2qBanBsc2lZZ2xuhod4eXJzb3RkgHF6eouHmZKUkFiBd49Ol4GAjJaUkZqDc2pzgG2CXGVWW06Hk4GQiZyTdI+IiZmnWLCNlJOIkKW1sZmVYVxlqrKCfXdwYHeDd3Jnd3SCdVGXiohjcnBfU2twcGxnZ3JxYm55gIZzoF2Rj5qUl4ikhJWOhYJyZpBgp395XFmEmJSesIeOkImHdZl6ZWVrh3iaYZZqcWuBg5yCe4J+gHFwaXJDQUJUXGaIeHdoZGptgW51YHWLimtzh29ub0+Te3h+gl5mcE1RZm5jZmV/amBcWFlhhkxCcUV6bXCHl3ptb2BtX2tlaWdoWmthaXZsY1SUa2NcaGpifGlfc45tfWdRVGlhc2t6a19nY2ZrTk9dY3hyfZBYY2Bqe4V6g1KPgFRoWoeBfG9obXx1douKblhmlGlnWnR6bWRqcHdmeGxihXVuYmtmb2NlX15kaGh+cEZ0VmtHjmtscm+JgYBpZF1wa3BlW3qKfk57aWBoZWlpc4d9g3ZeaHCSk3Rqb4N6j2h0SId4e4pdSXhRVFdtdYpudXJ4hnJiZ3lyb3t8b2xtgHhohV9hfI10YGtiW2Nyjn98bYNMco+ZlFuWjn9ycWx3jXt/hnl5eHBneHhkTmR4aG5sV2WKbnFyhHR5cHJ6doNvcmNkZXFvbXxxUVpEW15RY2thUmdgbHJ0fHJ0fXuTgZNibmVla2xnb1lleHl9ak9kZHF1dFxqeIV8gHZ7eHpzgIGAfXVtb3hvcGp5e2Vne5ZodGhmdouCmop0dnqGanFobnBycHNraIZ5eHdxgGtziI13f3BYmphxi3t+c3uBf3dhfmp6fnx5eoiDg4uJeo2BZV1jbG5joXp7fIuQhpB+fXp3bWxvcXuDgHeWdXJ4fH16eGdvaXeCgoZpfHp/g26NgGydc4mPlZKBbHlzZHiFeoVRjml5fUd8bYWChHl1cnJ4d2Z7gpCHjoyAgKJ7colvSY53ZGBkYWdscHdfc4JgZ4mol4h7aG6IdHeCfoF8hnt8Z1+BlY1celKNemdthIRhY3lRm4V2h4iNjKFvfn2EkIR9hYWKjpWikKRYf493iYOIgHt5dnlpcYeEfoB2cnqLgYBwWmB7d4ZyY3RxdIJ0cneEjJuqeniEj4aEhpSUhJlldn6Lb290dpB4h4GKeIuVeoSMdmxjZG1tb2l4n4ZxfImFfG+Bg3BqWWd9fnCEe2toWG1yeHJ+enN4g2BbYHd3gIJ0dH2FbHWOc1hwdIOClnhrgGlhY2l0X2t5bHRGUFdObmNwgGJQYnhnfmeMcW1nbUNJXlZmcW5qanh9eGlnaWFXYVRfXlhzfW16aX9ubGJvhJF2hmx4jXBudHiCd2xpl5SOfneKc4B7gXZldXh6iYt+f3p8d3llf292c4J/lpCUkFZ+dItMjHNveIKAf4dyZFpogGN0UFVNUEJseGh1bYB5WnJoaXeERYNlbWxibYeUkXl0UE5TkJlxamZhVm95bmdca2VvYkJ1a2RDS1FEOlFUVVNRVl9eUl1mbHBeiVF5cXtycWR+ZXVuZWNYT3NNgF5XRERhcmt0hGNpbnF2Z4doWlpedWiDUXtVXVpscpBzanBshHqDe5d6AXuXegR7e3p7l3oBe5x6gnuOegN7enuvegV7enp6e5N6AXuZegF7hXoDe3p7qHoBe4R6AXv/evh6AXuEegF7mHqCe6d6AXuJegF7l3oBe/96kXqEe5B6gnvPegV7enp6e456hXuNegF7i3qDe5B6AXuEeoJ7kXoBe496Bnt6enp7e5N6AXuLegICBACA2uLmh5Gd89S50P7U2LXf4Le0wsC56OG05NvW2+bHnI+A89rp39K7p8fsure+spyjtsTB2viB9/Hx75v1+NaAxMuBysC1sZ+47Myoysu+ubytsMTiv8SBsqzNhOi2y+DbpbWr2K+Voq6xtrWhwr7R29Hu78S40Li4vsm7w9/Qu9uA68bCyeLI0MDQ7tnj4MnLwbKysqqlm7LE1MC92uXPw6SosMCtr6ipqLa3vdmA1dWA/PLn6tLL4NrU8df0jbXDuL61tLHxvbWuxc7Gq7ChqKTTz+bW5N+0mK/9q6SelrK7yOXV04WC2KiX0Ozu5ryzw66wscTL1ri9u9XDyuHq3/aA+ufV2M/e3uD3gO7VqJzL2tfU0+nMt9Dc7tHO67Cmtqeloauyv9q+wq+izLazu7OVyL6ruLa2u8ff3czV17OhuNfHv8O2osDDwvTt69rTwMzb0dv3g+zD17e1xeLUyOy42NTLuLnBvrqzsMbLxdLdoqDFzc/W7M3Jwb/DpLbAxcaAytjXxLm3r8a6uLe6sZieqbG2pLW+w7e7p67Vw7Gstaaqtseps6epzMTBwKm3sLq7wr+6t7O0vLzGwuWwzdjO4NnPwcvP0tTC44Hn98253NSxqazT866qq6y8vcDNzci+ytvEw73FvNC7vMS+1tvD08m+uLi0qdqsvb2+8N3j8OuA99r12t7Hu7K7qqa0zsLRy9DSx73a2eDCvKeun5yhoJ6lxautsb67wM3VwtPb9rHG0da7vsS5qKe+v8O9wMbY0+7v197CtK2nr7/Mxb64y83a1eTp4tXy+NnZxNH639u3teSG2+Ld69Xu1bjDz93O2sDKztLQyvyN9fDb2t3az7+A3eLK07u6w8PascnCxMTMw8fDtLfS0Lm9zd/f2rG5vezN5u3Y4vLp6+jJstHq07//z8rKw7CquLS8rbnH0c3AyM3Lur6xpau2qd/53L63qbK3m6CvtpqWlZCTmp+ZtKywqsW/s8DKxrDAx5mdqJrBmqirrKanorCjlZ64x8m9r72A0czC+8/S1sXX0NTt8NLFx+PKxrTkiNO92+2Kl+vH6KXq8+/V2dPi6rzFwrm2uMvAwrGoqqmdpbHRuqSkor6/ssS8w8msvbqYprXGpaKwuLnF1fz38Ynt373b4MrRy+HoztvR2M29xMbO3/SDioHio+TW6u3c7oLs9oCE8cCu0N+AzYaWk4LQhdfZx8jq2t3z2dvc3oONlMi609jO/Lnukc7J3szY5dGxoKyguMbTxcHXxef16N7Z6cvckrCRmqmnyuCnkcTaxbWxzd7Sjpa2ubajqqaurbXAvuGsusTH5Nvehvft/f/Rxd7b297a2de2qsuEuM3VxbOyvcWzw9/f3OKAhIeIU1hjgmtVaZFtb1d6d1hWaWJjjZBrl4+Okp1+Z2BTm4GNhoN0aIemeHN1YktSZW5jdpNLi4WJlWKeq5Nbgotdh3xsa152moFjgYFybnNoa32Ob3RKXVZxT4pfeouLX2tjhl9OWmBjZmNVcGx3fXeTmHFlgXBrbHBnaoNzYn6Aim9pcotueGlyjHeBgGtyaV5hY2NbV2d9h3Jzh5GAbVFUXXJcXVpbW2RnbY1ajo9dsaCMj3Vxg313k3+ZYHJtbXJqbXSecWhdb3dxXGNWWVaAgZGCiIZiTWOOXFtXU2pveo2DiVxZk3VnkKGdk3JqdGJgYG52fWdtco1/gZWdkpuAl31nY1xodH2VVJiDY1eFi4uFfY1wWXB2jHd8n2NecGdhWWNndo5ubmBcgG1tdXVdi3pnc3BrbHeMiXp/gmRYbo2JgIJ2Y3Z3cZ6Xjn94anR6bnaHS4FreWJnepmMhKVyh4BzZmhta2tlY3h9doORXVh0fn2El3x2cW5rVF5ma2eAa3Z1ZVtcVWVkZ2ZrY0xUWl9dT2JqcWhoVFl9a2BeZ19fanleZllggHRwaFZgV2BhamViXlldZWJrbYdacXd0gnhzZHB4eX1uhlONmndskIx0b3OZtHVxbmtxb292eHZveoh0dnR6cYZ0b3FsiI1yf3ZsYmBcU3hOXVxejXeAhImAlISmiZF9cmtvYFpofHWGf356bGd/go52dmVqX15eYFxfdFpbXGdjZW5zW2lwiEpga3JhX2ljV1lnZmljZW54epSUfIh1bGJeY298bWtncHJ4dISIin+dqIqKdHyefnZPR3JRdoOCmIOahGh0eol7i3J6e3p5cpRQgHppZmlxZ12AeYRvc2FeanKOZnpybnB1bXFzZ22Bf3FudYaMjGNkZoltfoJ1fYyDiIdwYH+YiHu1kZKShXl0fnZ3aXR/g3ptfIF8bmheVFdfVIOghG1tYmttWF9qclpXWFZYXmdfb2VnYnhzZ32MhXR/iWFia1x/XWZpbGxrZXJqW2N3h42DdYCAjo16q4F+hXaDfXyPkXVtb4h5eW2aYYVugo9PV31eeFWDkZB6fYKMmH2HjYmCiJOIhGteXFhRVmJ6ZlldXHh5coB5gIFiZmlQX2+Hamd1fnp3gJ2UilGKhGJ4eWh2dYqVfId5gnZmamhseotPVEt/YIZ7h4t8iEyHkE5Rl3JlhZOAgVhjYFJ4VIiPgISrm5uvlZaVl1xmbYl/kpWMrHeka4aCmouapY53Z2tibn6FdHOFdpOcj4eDkHiJY2tUYHB0kKJvZ4KRe21qiJSMZXCFhoZ3e3p+fYOGgp1veIeElo+TW6KXqqyGd52dmZSPkpVyZolkeY+Uh3dzd31veZKOkYyAbG9wRUdNb1tRZpJxdV9+fV9baGJfg4Behnx3e4RrWVREgm52cW9kV3KTbWxwYk1VZmthdI1HgHt+g1SCindOdHtUfHJkYlZrjnJXc3NpaGthZHN/bHJHXlpxSYdme4mFXWtjh2BQXWNmZmVWbmtzdnSMl3NqgnJxc3RpbIR1YnuAiWxocIZvd2hzjnqGhG5ya2BkZWVdW21+inh5jZKGeF9lb4FrbGVkYGVlY3dJb3NIin50eGVme3p2jXiPWWNtbXNpXVWRcGZccHRqWl5VWleAfZCAi4tmV3CUZmZiXm91e4d4dUtGY1dKZ3l3dFlaaltgY3N4gGltbod2eIuVi5eAmIJvbGJtdnuLTY11VUx2gIB9eo50YXiElIKDomtqg3lxbHZ8hZyChHVsinBqbWhRe3FdbW1qbXWIgnN2fmFRZX57bm5jUWdmXIV9dGJZTFZdVl51Rnxnd15ga4h9dJJmg4B0ZmduamRaVmlrZHCATEhkc3Z/k319e3l6ZHN6fnuAfoqIeW5uZHhzdXV7dl5iaW1wYGpzfHRzY2uPf29veWxrdYJobGJriIJ9e2p1bXZ3f3l0cGtvdHJ8eZZieH15iH52aHh9enpqhlCHlHBhgX1kXmaJpm9tcXWAg4CLioZ8gpJ9gHx/e5SBgIB9mJuFkIuDgH96cZ5venRul4CBhISAk4GfiZGBd3B2aGR0h36OhYWAcWqCgpF4eGhxaWxyc3R6lHl7fYmDgYeMc3t8lFFnc3poZ3JsXmFycHVtbHN6epGPfYd1cWppb36LfHhze3h8eISDfW+MmHp5aXeggHtZU4FUeoSBkoCahWlxe4yCjHSBiIuLhrJioJmGg4SFd2yAiJSBh3V1gYagdIl/fHx+dnV1Z26IhHl7hJeYlWprbJJ2ipKAi5yRl5FzXniPeWabeHZ2bmtveHN4bHR6fHVkb3NyaGpkXWRvY5Krj3h2bHd7Zm97g2lnZ2BhZ21kcmdnY3t0Z3SBemt1g1tdaF2CX2txcHFva3luXWV6iZCHcnGAeXNjkm90dmZzbW5+gmthY3traVuDVHJfcn5LUnJUa0dveXdjZ2l2hGdydXJudYF6eGZbW1xXX2+Jd2hoZXh2bXZtcnRfcHRdbHmOb2t5gH59hKCblFaWj26EhnB6eY+WfY6Bi3xrbWhrdodOUEp8XYd5iIpzfERyd0JGfVlLanqAa0tWUUZiR290Z2uLfXuLb3Bub0dQVGBWaW1oh1uAU2RmfHB9iXdfVV5XaXh+bmZzY3qDdGpka1VlTko4RlVXcYFST2NtY1hadIN4V19pa2lYWldZWmNmY4FWW2JcbmNkQ3lwgIVmW31/goJ5enxjV3NSZ3t9b19eZGhaZ4B6encGenp6e3t7mHqDe5R6AXuEegh7enp6e3p6e5R6BXt6enp70XoEe3p6e4x6gnuEegJ7fKB6hXugegF7ynoBe/R6AXv/epl6AXuUegF7/3qeegF7hHoGe3t6enp7tXoBe5V6BXt7e3p7hnoFe3p6e3uGeoR7Anp7jHqDe4h6AXuaegF7iHoBe4h6gnuTegF7kHoBe456AgIEAIDCwImL7Nvm2MvgitnShYeMhtbFrcm4xN3J4OPP+tWLgZyi5/Odgsyhutjeu8fAo6K71ffy0tCQ+434iuXj+pLO2Yf99dHIvMXR1bi3xMjD2fLH1cWur7Smm6yv0u3GvL6znqbZore8uLuwvqnR0LSqraSxp7OpsLe3s6b/4ubb0oDMw7/H7onBttiCh4v+j+3ZztXNqZOzvbHWwK29tLy4qqG+xbemrbnF7NOuuMPNydbH/ebw8dGHicm8r6LJwauXo6zs7YHF4cG02sPcoqbQ79XT38fj4LWwzODHw7ixt8i9tsGxt7zO2ub6gOLVnKWkq7S/s8vFqKmv0c3R0eTEtYDUzbzZzMTX2L3Ovq6bl5r4zMeyyr2upM3Sz7/Duaqhraefraqt2Nnmw6XAxcCzq87OyKG7ob6frp6XsLfHt7TFp8zbxdbi0u3Fz9Tlz8fEsLG9u7TG0bXJsbmuvsTMx7u3uqO5rqu9yLq5obC80uza8OvJ54ek1cnEzbKvtbTK0oDIzsi8t6uow5iltrCypbfJyM7KycXEvOGwz9HCzau4u6Wxr52YoKbV0L20urOyw76/z+GzuLOctdrU09PhjdOA38Ds1NPQuLrw2e775rHQy8eioMXYqaPM1dnFuuPKtcnZvMG0mMS3y8a6rcbT39ja8vbXw9XJ0tG/3+j31euA8IDa0bPNz8u6wq+tt7vKw8zZyt/Xwt3RrqutqbitnauZmqGysMKptq+0vMu/6dvCsra83c+/xbPjvMLW5OPtgOvCwd3w7NzN0tG+uLK1t9PW5Obd4NPOxdC90sDR0tjN9uHWg97psN7Ly8jA1szL3sPDv73MzsnN0NfS2+Xt7s7P3oDh0cfVwMCxtrCx2M7gzODTxcjF19PI0sasyODd2cLK2t3d6f3r+YH53rzpwdzh7PGyzsSwqqWx0MTHxcDRzMi8u6mZqKugrJyox8WzqqKcpKKppLDEsL+5t7Snrq6/s6+vrqK4r7rj1NDY4NDF0rO4sa2olJ+isqqdl6O8tba5y4DRyc7SusHKxcLCztzBzbOwxuHJwc7M9fDH0Off2c/QubXy3vvU0ePozMrP5u/fzsm/zvX4wcLMw8e3ra23laehkKCsrK2vq6auorupnaGpwMqR4O329oPuwtfU1dvL38mkmLnN0sfMzcbJ4dvX2fLu8+7s6OTz7oSE/d/U9dTw6IDggvLu5uf+7Na/4/XT0Nj94dvn7N7kxLfIweXFurS+wrTF/4Orne/QpaO3ysCz4te4vsuyxN7mxM+6pKOun6XV08zt1tPexcHB68XBh4+ZmqOfn6mit8O4yci5tsLm2s+84vD49IXS1Ofn2tbI0a3X84LIse/2tLesq6yxyIW+7oB6dVdajX6Fc2uNVnNoT1RXVH52aH1zf5qJm5yHpYJZUWdpipBmU3pYcYuObnhqUE5lf52VenhcnlydWIiGoF98jFymoIN7cXyDhWlwfn17kquAjoJoZmdZTVtihY50cHJuWWB8W2t2cHNodWCHhGheX1hkV2FcZmxoYlWpiYF9dYBwcGx4n114eoNPVlikXJiKgYWBZVRsb2SEbVxnYW9oYFdwdWFSWWVvjXthaHR8fYp8rZSdn4FZXXFmXVF4c19PXGabmFVrhWRZfWl/UVmFooyJknmKgVxUaXdoZF1ZX2xqZXFobW+BkaCxWZKJVFpaW2VuZ3xyXGBpioF9f5FyYIBza1txaGF4fmmCcWVWU1Kid29bbWBSUHF3eXF2bGBcZV9eamVhgoacfF90dnRnYH+Ih2eAZ4BdZltSZmp+bWt7YYCKeIiYipt3gIOQg3p5Z2Zua2Z1eGNwYGlibnd/fHJtcFxuYl9uenFwXm11h56Nm5R8lV1vgXRxdFhXWVZmbIBlamVfWlJMZkZVY2BiVmhwbWtoZGRmY4dgentpdmBwcVtpaVxVWWGKhW5ka19eamNkboBaXFlHWndwcHOEWoJSkHSThIJ9bGmWiJWjk2mFgoVnZoSUa2R6hH9xZoN2Z3yMdnhuXHtyfHpsX3F+iIB6lpx0YnFtcnBifYuZd49RkoCEfGh3e3dobVtZYmNubm10anV2aYB8ZmduaXpxYWVUU1dmYGxSXVJUW2ZYfHNcT1Jad3BmcWSLbm59hoOKTYhpa32NjYN5dXZnY2BeXXd3gYR4gHl4doR0hnJ9fntwkXtvTnyGWIR4enVvhIJ+kHV4dG91dmpsaWhka3V6e2dqdYB6d3J9bXNjaV9fgnKBb35ya25xfX97h3phcYiDd2Bqdnx4gpeNl1GgjnKbmJedpKx3lIt2bWt3j35/fHZ+eHhycGFTW2BVYlVddnlnXllVXllhXGd3Z3p8fnZqcW91bGhwb2ZybHOYiIGMk4N5f2dycW1rW2NkcmxkYGt7b251kYCYjY+Nd3Rzc3BwdH9udGRjeJR6dYF9oZ15doV+eHFtYGKdjaOEh5iXhYyXqrCllIR5gJmZZGVva2pgYGh3WWNiU2RtaGdoY1lgYn9tYmRqfYBkkJKTjVGOZnh7gIh8i3pdTml6fHNycmpqfHl3douHiYSEgn+PjFJUnIJ4mYGckICMVJiRiYqmmIx4mK2Oio+ukoyWnZSZgHmJg6eIgH2DiHaFsV6Hb6WMaWRvf3ZokIJrbnhjdJKfgY15YmFsXWSJhXqbjoeWgXuBpISIZ2JvdHxzdHt1hYx7hoF6eYKYkIBth5ejpFuCi56eh4eDiWGEoFx/a6SveHdua25xh1l4loBiW0ZJc2VwX15dT29jSU1STXJqXHBncId0g4Bsh2dKQFRUbXJSRGhIYnqBa3dsVVVqgZiNc3FRhk2DSnNyhk9rd1GTknlxZmxxdlxiampme5Vvf3VjZGhbU15ffYVvbW5qWF52W2pxam5hb1+CgWthZV1sYWtkam9qXk6Se3dvaYBkYl9ohk9nVXJETFCVVoyBeoKBZlZvdGqLemp3cX14c2uChnNkanN8mIJkZWxvcnlpkXyHiW5NUmtpYluBeWdYY2uRkVJrfmBZeGZ7TVV6l4eEkHeMiGJfdYR2dnBscoB6c3ZlYmFrdoKTSXlxRU9UWGh2boF3YmVsiX9/gJFzY4B4cmJ3bWV3fGh9cWNTT06VcW5fcWldW4OIinuCem1rc25vfHVthoyhh2yChXxtZH5+e1tzYHpga2BWaWx7ZmNxWHN8Z3SAdH9eZ2pzZl1cTlJdXl1vc2ByYmdgbHR7d21qcF5vZWVwdmhjUFhdb4NxfXdmg1Rlfnp6f2hobGp7gYB6fnpxbGJeeVZlcm1yZ3Z+eHl2cHB1cpZwjY17gmx2dmNwdGVbYmqTjnhye3Byfnp5hZNrbGtZb5CKiIaOXH5OgGOCdXVzZGOTg5Cai2B5d3laXXuQaGF+i4x+cJB/boCNd3pxXHt2h4V6a36LkoyGoamMfYyGjo56jo+WeIhMjICDfmyCiYh6fGtrdXh8eHp/coB8aX57Zmdxc4iDc35tbXOEgJF1gnV1eoN0l4RuXF5kgHltdWaKcnOCjomPTYRkZHeIiIF7fYFzcm5sa4KBiIh8gHhzbXpsgG59g4V6noiAVYaRYY9+f3p0ioWDmICBfn+JjYOHhoqEh5GTknp9h4CPh4KShYx/hHh2l4SQeoV4bXR4h4t/jH5of5iUh25yfoF8h5iOmVGeh2mKcIWHj5ZngHpraGt4kYOGgnd9eXVtbF9VY2hgbGNrhIh4cGplb2lxbXaFc4OBgHdobmpwZGBlZV5raHCUhH6Kk4N6f2lxb25sW2VlcGtlYWt9c25ufIB7cHJzYWRlZGJkanFgallZaoVtbHNykZFvcoR9d29qWVaJeYxrant7aG54jZOIenRrdo6TZmh0dXlwb3eCYGpmVF5kYmhxb2dubIp5amlsfoNalp2dllSRan1/gIV7jnteUnGCf3V2dW1sf316eI6LioSCenaCd0VFgWxff2WDe4B2SIOAeXqVhnhogpZ3cHOOdG54f3V5XlloY4RsZGBlaV5oikdeVYZyWldmeW9ihXdeW11DUGlxVWFTQURQRU5wbmeHenmIcGprjnBzW1RaX2JYVVpSYWlbaWdaVlpwaVtMZXB4ekZgaX2Edndxell4jVFvXZGZY2ZdWVpfdUljfAR6ent7hXoEe3t6eoR7jXqEewR6ent7kHoMe3p7ent6enp7enp7w3oJe3p7ent7e3p7qHqCe4x6AXulegF7/3qKeoJ7x3oDe3p7vnoBe756AXujegF7xHoBe4R6AXv/eqZ6AXuEegF7oHqCe4h6AXuheoN7pnoBe5h6AXuLegF7i3oDe3t6AgIEAIDw7e7to7LE7MSom+r/+o2BycK+u8Gz0t+61cv1xPn2u8mCgoya5I7u4s/Hv8e9sayvycfcyMLP4OWHnILR/O3m0fWC1sX5uLPAuLuzx97Hw/bs38Otu8a7pKfgr6a/zuLNpIu01aOx1MW+v7rIure9naKhlaaVkLijqazHyc6tv4DeycjR5vyGz4CC8ePn4u/e48/cv7/d0POGpaqur9WYr7zJu8S6qbC/xrq0wtLt9P/i4dDx1Li2m6ilqZmgraeWorbF5s2zoKerp8DF6O/d1NTtwdzS2MygyPuF4sq+vbu5trW3yM7s7vLdzumtlpyntcvQ2NzYxKSorqy4vNnc5oDktcXLz8DIwqe9u8i/sNW91u7o077gycfOwuPQp46nvOHY5dLM0uL/gNHEv7O8s9rdsJCioK2Xm6aZuKnCpsCxuNG3rLW3zvHT3ff60Pbu5sPE28+up6+gsbzCzLu8vc7JwcrCrKWuqru2p6yfm7bQ3Nro98a5wLbR6N7lu7e1woDExr6dsMCx9oyfqaukzbK0u67Arq+strO9y7WppaKkrdKytMWmqLjCsr6vqK2twryxyt25t7iXwq7N3Ob5h/aK08yA74eH09jf08PW1Mm3sKSvwMC/ubCvy7u0x9zO1+HYtdKIgdGsws3H0NzQw//S4Pfg29/Q2Nzt7dra0OLz3IDW4b7Dva/AvLq8p6q1rafH2t7S0N3Vva+iqa6irqWnrb7ExsDDvbq9wd3k69rj29/E0MfAu9/G5eDhyL3W5dDCwd7ivcLOzLuksbe/uMCsyNSB39blgu6N7rvuiefWxOjN1+HJx/C+usK71tXOy8ayytnG1dzLwMS9z97i4tfh6IDj0ca4uMDAwcWvyNjF19fi7ubf2e7MycTAus6MhMS0zt2/5PiL69rVzrnIrOfm4OzExcXMtra+yMS/u76ow7qzrLC/raGppLO6mqavo6+jnpur0rutr7yrnqirqpytzbG4uL21pJ+2xMPWrry/1brCr7bH182otcPEtcrLo7m1uYC4v6ubmqi7yL+2y9/AsrPDw8Kurr24wITi0NDYtb2n1sHVw7nHvsrIz9TGwsXAn7T16+Hp5s7Lz9Oon5+YkpeNlZycmLzOuKKdl6Cjo6GYmq/WstDHu9X4xMXP1OfQxq6rn7PW3dHWvuDp3OLd8tTW1eLY4ff5+IqJ7rze0/7e3IDj49334O7svMTayIXI1Mrl+ujd0ujL38vDyMGB6Nyow+Lgpp/diIfh5MzSss3O0uXzhZGXhYGg3cmusp6hr7TOyb25r8/19+iA4qyvuKO0psPMq6eerLS2utnk3tHdytbL1P2GkqLnsrjKvcjAxdzLveC/q4aUna66rairs73a/4ChnpqbW2Vyjm5rY4SVlFdOcGlrZW9mfotug32bcZ6RZ3BRU11rm3CnloR5cnl1Zl9kfHuIcWNtenxUX05ym5OPfqNZioOwe3OBfX11g5J5dqibi3VocXpsW16RYV12hZ2GY1BvimFtjnxzdG51ZmJoTlNRS1pQUHBfYWV9e4JneICPgoGKmaZWdkxMiX6DfpKFjH2Ic3CKfZBSZ1BWV3dJYGZuYmddUVRkcWRhbICXna2Wl4Wmh2dkTlVVXlFTXV1LVGV1k3heUVRYUWJjiJaIgYSedIh6fm5MaZVSfmhjZGJhX2BjcnuUnKCMfo1cR0lTXnN8hpGLfGVqcGhteImKiIB/WWNmaWFuZ1FpaXZrYX1kd42LcF15cGlvapCAV0JYZoV8inx7gJKoUnZwb2RnY4uMbFBiYm1dYGdZcWV2YnZqc4tuZ3N1gaCDlK6piq6oonx7kIZsZWpcaHd5e21tbnlybHVzXFZgXW1nXWNbWnWNlJCepnpnal9yhHaFX1pdaIBudGlNXm1enj5PWmRegGtscGFrXltYW1ljcF9ZWFVUWnllZXZeYXR8bHRnZGZldGteb35hYF9LaFxzgoqaVplYhX9SlVZVfHyGe2t5gX1wbWl1hoaCe3FrfGhhcH11e4iAZXpWU39gb3VwdH5zY6BrfI94cnRtdHuSkH15bnqDc4Byel5lYldoYmJnWFlhWlNuf3twbHt1bmxkZG1jbF1WV19hWVdWUE9RVGhvdWdva3BcZWRiX4VwkYeHcGeDjXZsa4KDZWp6cWBQWmJjX2tXbHVNgXyHVJZfnWmWXYx7aoVsdoBvb5JxbXJrgYZ6dnBkfoh1hIZ4aGJdcXt4dWl3eYB7c25jYm1ycG9hdnhpdXiDk5SOiaOIg3xza3paUWtecXtfg5dbjX+Bf3KFiaiopq2JjIuPc3J8hH11c3pidW5taGl1YlddV2JqTlldWWRcWFRkhnhxcIF3bHNzdW13iWhxbnBkVlJqdHKEYnN5jnl9dHmQoJRudYJ9bH2BaHx6foCBi3pmY258hXRte4hyaW54fYFucn12e1uWfnmCY2tVhHmRgXKBgIuIkJeNhYJ8XmiejIKJhG9ueX5bVlxZV1lRVlxaVG57Z1hYWWZqb25hW2uJan5yaYGWb3F/g5KBemhnWmyHin5/aYWIe395h21vbXZtd4iJilNUiWWFgKCFg4CFh4CSjZadcX6Tg1qEioSXsKCUi6CNoo6JjIJen5dieI+IaWSIVlmTlIB8ZHV4dYCSUVtiTk1ohXhmbVxhaGyDfHZtZomuq6BboG91e2t9c5Ceg31xeoGDiqGmnomUh5CAhKNYYnKWbXWDdHx2e4x8cpV3ZV1eZnSAeXN2dn2Xs4CPioaFSFRlgGVYWIKQlVNMcGhmZGtje4Zpem+JZYyCU1pCREpSe0lueHBtcHlzaGNofniDbWFocnRJVERjhH55bo9Ndm2TZ19tZWRdanhjYY+Dd2ZcbXhtXmGRZV91f5B5XUlkfl1ohXVtb2t1amlxW2JlXnBhXntkYF9zcHNXYIB6bm54hpdQbEdGgnt9eId9gnSBbGqEeIpQZl1kZ4dacnp/dXlxZGV1fW9nbnuKjZd+e2yLd2VjVV5gaVtjcG1ZXWx2lH5mWFtdVGBffYl5dXOMaHxyd2pLZ5JQgG5rbnBycW1sc3WEh4p7bX5PP0VTX3eEjpWQgGhtb2ludImHiYCBXWdscWd0b1pwb3lsX3ZgbYF9bmOAc3Z5dZGFYk9lc5GIlYiFiZimT3hzdmtvZ4uMakxdXmtdYWtddGp5Ym5kaXtgWV9kcopufJGJbI2DfF5jeHVnZm5jbnl3eWtqa3dycXt3Y19mXmtmVlVLR193e3mLmG1haWZ7j4KPamVmcIB2e3FWaHpvs1Rlb3Bqi3JvbmZ0ZmlqbG54hXBmaGdmaohvbnlhY3eDcXtxbG1te3drgpJ2dnRad2mCjJKfVZRTdW1JiU9MdHmFfHGAgnlrZmBqfoB+fHBxhnlygpOJjJaHa4FYU31hdH1+gIp9bKh1hpyNiIuDjpOim4N/dH+Je4B/iWp2eG1/d3R3aGpxbGN+ioN0cH14b2tmbHhwfnR2eoeLhoKAeXR0cYOKjH2Bd31mcG5lYoRyk4mNeGuIjnZtbYSIbHWIgnZlcHl6dXxleH5QgnqET4pWjWGOWol4aop1gpF/f6h9fIJ5jIx9e3psh5OFmZ6RgoB3h42Lh4COj4CPgnt0dYKIiIh3jpJ9gn6AiYaEf5h/gH17dohhV3JjdXxig5RYiHp7dWRrYpCMh5NzdniEcXmFj4qFgoVqgHVxaWp5aWBoZHZ9Ym5xaXFoYltqjHxycoJ1aGppamFsf2Rsa3BmWVNqdXOFZW9zhW9zaW6CkYtrd4J+cIGEaHxzcYBudWVVU15ncGJea3lmXWBrcHZna3Frb1aLeHZ7XmZRemx8bV5qaHBueIB3c29sUl2Pg3mAfGtseoNlY2llYmdeYWRfWnaCc2RkZXB0dnNoZHKKa4Z3bIGVbWlzd4p7eGdmXnCLj4OGbYeMf4N+jnFzcnpscH14c0VEbEhlYol0dIB2eXKFfYSMYm2Cb01wdm+BlYR5cYVsfmtmZ2FKenhLXHNqT1BrRkp4gHFxXm9vZ256P0VFNTRIXVZJUEZNWF53b2VcU26OiYJNiV9mbF5pWm94XFZPWF9fZXh9dGRvYGdYWXhBSldvS1RmXWRgaX9zaYdvXlROU2NuZl9kZ2yGo4l6B3t7enp6e3uReoR7Anp7knqDe4Z6AXvDegR7ent7jnqCe8B6AXvNegF7/3qVegl7ent6ent6e3udeoJ77HoLe3p6ent6e3p6enu/eoJ7h3oBe4Z6AXvregF733qCe5J6AXuPegF7hnoFe3t6e3uKeoZ7kXoBe5p6g3uOeoJ7inoCAgQAgLPGu83EzeDOsajcyK2gyY+J5sa7wrTB5Lexq8/aiIf/4cnE2cjdlpr5ycbl4de3qrq6uMuz0qKjvr6b2L6D6drtgJOR4ITfyr+iq7K7y8bI8e6J99TB1u25tLu2v7TFwKqLhaG8n5y+v7DavsasrKqfpK2onrq3w/KP9rq63Nq/gL2mwrvP37yz6ebBqrC5yci/xs7JzsvpzIXzuNPBuaKkxbLDxKyan6Wono2asMastqOXltenx6ansaKmpaCmn56YstbXxsrJvu3Xu8b5/4D96I/Zg/a2sarSw8i4wsfHxdDQvbXd4dfc34SS+cqy/77X9uLv2d/lsp+vwcDZtL/GgMG7v93W29TFuKyutK+x5eThi4zh3uXg882/vLu2t9DM2Mq20tLY0tbDt7y+urm72cy0nJmovJ6xxIugl5yjopyY26jBv9HQ0cS2pKzMzKeJl66nra/DpqeeqKK+wNPc3/Hl2Pb14Mu0pK+bqrGzvufYzcK8r6yzsKyyu7atusTKgMnJ87asuM26l5zBzK6rn56mqrexq67FoaGnqKick6DSybCpraiysa/CwbPCs7K1u8bV3NDR1crEvrLi3t7729e33d3j4NXK2cW9vaq9q6bGyLO087jBurS0usvS5d/Pyc7L1vPc1Me/1s3T58fK2d7l6u/q1s3bx9bDzt7V1PyEgO7Jw8Kxsqu4y8vCu67Dq7PQ38i3zMO5pKGimquqnrWzrbGyurO3sLrF2NTs3eTo6+DalODGyczl2NXO5b+xvcTAzsm90bi6v7K3tb3BxMLIwsvP0N/+7tGN84yN+M/D6ILp38za3dHCwsHL2MrK4tDEw77X3su3trbP3O3/6O7ggMjT0NPfydfp1snj7+DM0OPu5unY3NSrutO9vLHAxbu0s8/Z8+vRy8rEzrq+wczphsnKvci4sLivsaOqtLS6r8TJ097KpLjNsae3rrCmmqqemZajj5yhqJ+cqKqusKvF1tOusq+hkbLSxdq+wL/GrqKjqLSosKC8qZSbtba+vLK8gKiNjpKcrrSvoam+1c+9yLO2rqunpbOpm6/F2MC+s6zSwdbFx723obrQz6+zqKGmt8Di0cTGvKmwxLS3ppypqKehn6KzzerFu7qro6WZxq2dqrLv4t2sxcHSwrq0vLy0s7S52+LziuHh5MnU1drl4ezzyufOkpP4h/aAh+Xp9OfhgNTczNCv67fNtcng0vHu1ujs9uXP4djEs8bV2veA0NOK8pyc6cP78veM8t7h0+i8jLaLsb7o6rfvx7igpKOwp8e15MyysP7u8d/Su7fW2by1vK+kk6icrtP/4tC5xcvm5eST8O2R98jLrqXEwO3KuavMycXa8YqGsKS+pZ2cq67OgHeEeIuAg5iGb2ePf2hcfGJelXppb2VqjWRgW3R/VVCafWdndnB/bneigHqYlIxrXW5zbnlwd1BRZGded2hTh3eGTl9ef1KHeXRebnV5gnp7nZhbn3pldIBZXGVrdXKGiHhbVmqHbGV7eGiOdHNgXltUVV5dT29rcJlZkmFheX9qgGtbd3KFj3FnlpJwXGFrfXpyeIF3e3ONdE6QZnxsZlZbeWR0eGBOTlVaUURNYHVaZVVISn5Yd1pZWkxRUU9WVVNNZISFdHN5bpd/X2aQnE2Qil2EWJtkW1RxaXRiZmxqam9yZmB9e3d6gU9ajmBWoGJ3kIOSgoiSa2JteW2HZWlugGRgY313fnxya2Jma2dtm5qQX2COhoqHl3BhY2RfYHl8hHhjdmxzd31vaGdpZGBhg3VqWVxmdmJ1h1RlYGFoZmBen26Ef46Ji4N5bnWVlHJTX3Ztc2+DY2RgaWN3c4KBgI6Ef6OcjHtrXmpZa3N1fqudi4N2ZWBjXV9ja2xocnuCgISDp2pea4BrTFJ3gWRhWVddWmZgXWF2WFVUVlZMSFF7dWBZWldoZ2V4eGtzZ2tsbHOAhXd1dG5nZ12OlpOvkIlzjYePiIF2fHFsa191aWiBgnRxoWlwbGFaYW92h4R6eXt4gJyFgHJsfnNygF9ga2Rwb3RwZWFtYm9jaXZua4xMgIJrZmdcXl1ldXNsYVNnVFx3hHJpfHl3aGRmXmxlV2JiWFhUV1NTS1Fbamd/dXp+hXx6YINxeXySiIF4jG1eaXFseXJrfmppa2FlZGlrcXFybnN6fYujknpZjFJWh2ZZdkd2cGRrcGtmb3KAjoWAloZ+eXGDiHhlX1xrcHyId4N7gGZxc3N6a3mCcml8h3Vlan2UkpeNlpVzfpF4d2pxdmpjXW99kIl4dHd3f3h+go2ta46NfYt1bHFpa1xhZGdmYnB4go16W2+Ba2Vxam1kX2xiWlVlVWBkaGVmcm1ubmd4hoNrb2hYR2R8dYlyeneCb2xwdYFxcGJsYVNdd3R6eHF6gHBgY2dvfoB3aG58joFyfGpxbHFvbHx1ZnWBi3ZzaWGEeI2BgHp4a3uRknh6amJkcXmPfXFybFlgdGdrYFtoZWRiYGFxgZt2amljYWZeinJia2+bj4ZfdnSBdnRwdHpxb2pnhYeVUoaKiHB2dXZ7dYGHYXhlVlqNUopKUX6Jlo2LgIKJfYJnmnF+bIKTiZ+ah5SdqZSHl5iFdoKLjqJSeHdZkmlkjm6ckpdVkn1+d4VlUnpYeIqXmnCjg3ljaGZtZH1skoFucLCen4yCc3OXoY6LmI6IgZF9gZWxj4F9f4GUlpNgl5dkm3d/aWF6fKeJdWWDfoGPqGNddnCIeHFueXmZgGRwZHFmbH1vWlR8b1hPb1dUgm5hamFsi2ZgV213S0qIbVpaYlxsMxxybm6Oi4FjWWxuanlddE5PXWBMbWJLfW55RlZScEt3aGFOXGJnbWNlg3tKh21icoZgYGhobWx8e2xTTWB8Y11xcWWPdndoamhhZG9sYHdydpZRhVpednZjgGVYc2x6h2xhhYVpWVhfb29nbHNscGmEbE2NaH5yb19mgnKChXFiY2lsZFRZaHZfaFxOT3xbeFlcYlddXVxnZGRbbIeBb25xao57ZmqLk0eDeU9ySIFXU09uanBja3Bxb3d8bmaCfnh4fEhRf1VMgFhviYKOgoaSZ11peG+GZm1wgGZiZYB7gH5zcGlsbmhniIh+UVJ+foKAlnVqa25ra4KAiH5tgXR6foBzampqZmRkg3loVVZld2JyhFJiXFxgXFNPg1hpa3p2eXFqXWJ+gmJIVWxpcHGCZGliZmBwa3d1dIOAd5qainhnWWJPW15eaJKDeHRwZGFpaWpudHNpb3Z6gHx7mmtjc4Z2WmGBiHBuZF9jZHBqaW6CZ2drbWlfVl+Jg3JqZWFvbWl5e3B4bW5ucXiIj4OEh4N7eW2UlIujg3hgfX6KhX91gXVvcWN2Z2R/gG9vpm96c2xqc4KHlpGCfHt2fpaEfHNtgn9+jGpqd299gYqHfHeJfIZzdX11boxNgIh1cnlwdXJ5ioV9dGd8aWyBinZqfnt5a2xva356b4GDe358gXp6b3J4hX6NgoKBhnh3Wnppb3WPhYN/lHVlbHRveXNqgG9weHBzdn19gH58dHh8fIeZh25Uh1FVjW9niVOUkYOKjYJ4e3qCj316lImFhIGan4pzb3CBg42Who+JgHeChIKJfImUhXyTnop4d4GRjIuAhoRoc4x3eW53eW5pY3mClYx5c3V0emxtb3iSW3h6bn9xcHpxdGlucXBwaXZ7g5KDYneGcGp3cnNpYnBlXVZmWGNlaGVmbWxsa2RzgIFmbWtdS2V8c4Fuc3F6ZmJlbXdpaV1rXlJddHR2dWtwgGJOUFRdbW5mWlxreXRncWNsa3BubHpxYnF/jXhzamCAdIV5d25rXG2FiG5tYFpcanOLe3Fxbl9qfHJ5b2VxbmxnYmFwf5x6cHFqaG9qkXdnbnOilohdcG18a2VgaHBnZ2dsiYqaVYqOi3R9e3yBfImOZnlhUlN7RXE9Q2Nre3V1gG11am5XhWNvXnKCeJCLd4OJkHtse3ViUl5nan9BWlpGcFVSbVB6dHtJfmxvbXxXQ1k/VF1kZ0h0X1lKTk9ZVW9ff21cWZWGin91Y157gWtncWphWGVXXHCKbWRaYGByc25Oc3BLclRcSkhgY5F0ZFVvam16jVBLYF5yY11dZWR/j3qCe4x6gnuHegJ7fIx6AXuFegx7enp7enp6e3t7enuMegF7pnoBe556AXu1egZ7enp7enuVeoJ7pnqCe/967XoBe7J6AXulegR7ent7hHoBe856AXv/erZ6AXuOegd7e3p7ent7oXoHe3p6e3p7e4V6AXuGeoV7rXoEe3p6e5B6gnuJegICBACAu8DTx+Tm/cCrwru+9MLe99jA77e8rb+1qqKxzdG9uv7Z1b3lz8C7sLirqOLYxMS5rZ6n243/tLyrucLg/f7o4NzbqIrbyeDp5di6qLGxvN/s7+eH9NzByPfy4+Xr6IPrq6WXpLK1ybvs4Ka0yri/yLPVy76ooaKhpbHVxMTeza+Am5CbpJykzcq4+b+5vaGzscr+xLK9u7+AztfJyOPTtamlxceUmZ2aioaShZKMkYyNlsPe8fPa4brEsLmppbCoq77i9eDf0sOvuOvCxcnJ3ffp7Oz/79HdqP335fnv0NDJz9zmgdjhxrrEz8nCxMDO2+LJ3NG9uMLEqKKts7W8w8+A293igen+wMfDu77Kw7TY8Oby38bPxbvL3MSjrq2xxbepqMLTl7XKvrja0sDqvcK/r/TZxL2kqbOSkqmfmaW0wc7e6/LLvLaosr2nrsS9wMaKj6ejt6egnZeMkaarx9HU7NzXkOjQxrrC0MnCwc2+07KusLeytqu1vra3tLSuq7iA7N/60bvPt7SnuK2j0828sc/r0LWZqtm2l56roa6zuuK5uLfEpMS8uriywsfg0MXItcmutsjS4c24zMDK4dbO2sbg3unlwMfExcWqoreqt8q7rrvDt7XG0Le4yfDk1sjEyLe/yMLRyLG6s8HDx8bN19LS2uTd09a44IDb1dDQ0uaA1L6yvLezudPNz8nKwMvM0d782tyxvMWsnKSko6KcrLTAysa+uLzJwsPg1c/H4dPf4oCYgdLH+djJ57K0rb22ubjIxr7FsK761LK1vs7evdazzurlgvn354b7jYi+ys3Z9PPtg/LRz+HhysbH8uq5qqKfsrKuur3Nx8Li2Nvq19CA4+bW3t7RwsjDv9bR2NLP69/TwLu+xryytbq2z8vFramzu97d2d7Zz8rV5K60tr/IydDHwKutr6i+yMKvt7/Gz8bY6d+8xtrUtbzEvMjFzsTQoKigoZ+bnJCTq66qrbClpoeMm5yTpJynprGzwa2WoKevubTDtsastr6wt7G1s7mAopeRmbGdlJGWrsLPz7PYwL2znameop20spe1t8THz8nFycHLg7SkqJijlpmVkpumnM64s8a+tcWpqaWdrqqhl56bo5u6scrOwbWZvbzqtqumu6OD1bHY1NnH19y9vLe6w7XDzc3kwuTu68y5ydDDwdDJvanGgoKB7fHXz+imhe2Ay67Jz8bMrba1s7akq8y70OyAzbvYtrmzyeft4ebw5d3d9p7b6uWE7dbI69vgv9HS+4zPgK6e5uvDzbqltt+5vsfMwOPK2tDf+oLKzfa1q6iZga+u1Mm08+To1czAp7POwdXEw6r4ncC1lqeq1/fGwaynpc/xvta9qKysm52goKuAhYGPgJiWqnFgc2pwlnGIpYt5nWxxZWtlX1tqgH9xaaeEgXGQhHd2bXVsaZeOfXtyaFtikGGwaGlXY2d6k46Cf3x9a1h+c4iLjINrZWxsd5WbkYdUjHZgZo6Le4GGjlijb2tia3l6iHicj1pfbl9kaVtxb2taXWBeZ2uOf3+ZjnCAY1pkZ1pbd3pqn2trbldlZ32rd2RucHVYf4Z+e46BaF9geHpSVVpXSkpRRE1DTEpITneJlop2g2BoVmJVU11eYnOOm4Z/dGhaYIxnb29xhJaIh4WVhXR+Vp+Xh5WLc3FwdoKQVYeLdWp2em9laWd1g5F+jIZ7dHp+aGVhX2Rrb3WAf4OIU5Gocnl1a250bF9/lYuWiW11bmd2imxRWlxecGpYVG9/TWd4bGJ9enCXb3h0aKeLe3VfaHZZVm5oY213hJecq7ibiod1fol2fI+KjJRUVGZkcmhjYlxTVWJfeH15kX97YIlybGRsenl2fIV8kXdxd3pwb2FqeW1pam1nZXGAq5+3i3SDb2ZZaV5Ve3poYneUfmlSX4ZmTVNcV2JiZ4lnZGJsV3VwbmxneXaKe3JzXnZobXh/j3lpdW+BkYp2gHeDgYuFaWtlZ2NUS2VfboNybnd4a2l4eGFcao2DenN1eGpyfnaCfGpyZmpoZ2JlaGdjaHBwZm5Zf059eXFvc4aAdV9TW1hWWG1obmhsY2lqanmdfH9kcnxoWmBfXlpSV1xhZF9XVFVdWFltaWhjeW57f05iUHp2oIB3lmRiYGxoa2dycmtzY16dfF9kbHiLcYFjdo2HUYuIek2NUk9cZGRreXl2R4JocoWJeHqCpJlyZF5aamZeaGVvaWV+dXmHdHCAgIl9g4F4aG9rZ3hsbWlmfnl3bXF7iX97e3x4ioiDcG5ucJGOiImGf3qHn3J/hImTlJSKfWVmZF9veHNgaG53f3mIl5BveY2GbHF5dX5+hoGLY2pqbG5qbGVldXh6fXluaU1PV1VIWU9dX2tugnReY211fHiBdYJndH1obmdpZ3GAY2JeaYZ1Z2Nmd4OIiG+PeXZsYGtjamiDgmB6d4B9fHN2e3J5WXBqbWNuZmhgWF5lW39rZXNvZ3VeYWJcbGljWF5eYl1yZnd8cmtTc3mjeXBoc2xWfWCAen51ho10dnNydWx5fXuGZ4KDgmlmcXVucHpvZlVyWVlWmZ+MgphyWpqAeGSAiH2Fb3R2dHZsaoV1h6NZfnKKc3FyhaGnl5ahk4yIm2yFk41Wl4Z6k4SLanh4oGB6Wn9umaSEkHxmdZx4foKFdI55hn6Gn1aBireFiY6BaIl9mohuoJOckIaAb3ePgZB+f2yzYH96YGptlrOBdWVeXX2ed499cHqBdXZ1dHmAamh0Z3l9j15OYlxif2J4kXhojGRrXmpkYFpkdXJlXYxxbGB5bGNjXmJdXY2FdnVrY1hag1WfXV9PW2FzioZ4cmprXU1yaHh6fnVgVl1dZoCDf3hNgHBdZYqHeXp5e0qJXllQWGhtfm6Rh1ljc2hxeW2EhH1pZmJfYGN9bm+FemKAWFJfZVhAYndhjWNiYExWV22SaFlmZ2xNe4N8e42FbGdphYplanFtYVxjVl1SWVNSVHaBiIF1f2FrXGlfXmllZXCFkoKBdmtgZItlaGdqeIZ6eXWFeWVwSoWCdYSDa25qcH6JUHp7Zl5pbGNaXltndIJyfndtZ29yXVtbWlxjZWuAc3Z6TYWYaXBwaW1xZ1p0hnyGemZycWt+k3ldZGRmeG5kZXuEUGl4bGJ7dWmPanFtX5KAcnFhbXdcWmthWFtjanV9ipN7b2tfanhmbH97gYhQVG1vhHlxcGdbXGZjd3h0jH97Xo12bWZsd3RtbXVsf2ViZ21namBseXRvbm9kY2iAlYybfG18b2xleW9lioZxZnKKfnNdapJyWV1lX29vcpNxcWx1X4B5dHFtfHiIe3JyX3JiaXyFl4Byf3uDkIZ1eWx7d4OBaG9sbm1bVGpdbX9ya3Z5cHB8gGxqeZySh3t9fnB5hH2KhHF7dHl1dW90dnRze4aBeYBtjlSBe3NvdIiAfWxjb25tb4R+g35/dn1+e4WhgX9kc31uZm5xc3FueYGJjol/eX2CeXeKgnp0gnWAgE1cS3FulH11lWtqZ3ZvcW95d214ameqjXB2fomZfIxmd4uFT4iEeU2VV1VpdHV9kJGPVJh7gJCTf36EoZp1aWVjdnl1fXmDfXeNg4aUh4WAkpaLko2Fd354coZ+fXlxiIN+cXF3gXRxdXp3ioV8aGhqb5CPiYqHfXiCkGJsbnR+goR8dGJnamV4g35qb3R7g3qEkolrdYeAZm15c3t+hoCGXmZpamtnaV5ebnJwc3JpaU1SXl1UYlplY2xsfW1XXWVrb2lzbX1kbnhnbmltbHOAX1lUXnlmV1NVY3B1d2J+bXBwY25ocGyDgWV8eIN6e3JydW52V2tiaF9tZGddWV5mW4FtZnRxaXlkZ2pmdnVuZWpoamN3anqDeXFce3+hfHBmcGhRe1t4dndpd35scG1xe3J+gH6Na4SHh29mcXZwcXdqW0ljTk1JgYRtYHddTIeAZlJrc2pwX2VlY2RaWnFhcIpLZlpvV1RQYHp9cHB6c2xqe1lqd2xDd25ogXZ9ZWpohkthPl9QbHlga15PX4ZnbW9xYnhncWt1j0xwc5ZnaGtdSGdddWdSgXN2aGRgTlRlV2RYWUiMQmFcRlNWfppyaFhRT2uJX3VkWl9mW19iXGK0egF7jXqCe496AXuKegF7pnqCe5B6AXvLegF7n3oBe996AXv/epZ6AXu3eoN7oHoIe3p6ent6e3uHegF7/3rBegF7rXqCe6F6g3uFeoJ7knoBe5B6BXt6enp7inoFe3p7e3uTegF7tnoCAgQAgM/Oq7n02dbTwffSyeHz3d67vvbCpbzLzdHByMjZyOHX08nUqq+/tp2mk8TKr52yzqrTmay+wc7hts67x9zn29TY3Jefh9jN2uHaxbrDq7a/rsjOyNHQ38nd3cna2/Hz+NPW0JqcncfF2sbIy7K12cDMw7+8ybyxtsW9vqS6v5nZgJeemparmpa1vsC/5MzrusbRj5yR8c7Gub3kvMzq08StlpmpqpaemJaQgf2TjJKPlKumu8y30/X16Onctb7YnZm3tM7H1PvY3cy/ysvNweP+hfXOgYnHtqu6rcHlz72yytTP0MzNzseF1IDygYDh2dbcutDP18aixKifoamotMKvgOXR1trPwrWvrcW/49PM2dvlvaG0oq2sn6K5p7qvpJ2cmLGZsaiirdLd96bF7Mjt58G7uL+/qqatlZ6robu0yPHK3t/ay8GjvKe4rLzNucHu7qWpsKOfnJuhmJunr6vAzYCK/8Oso63Kz8nE0N/osL21tNi+yc+Zyo6vnaizq6WygKS029C9xZmXlrKxuKuvuq64s6y1mKm5ra+fvtDEvvPdgPfy47DDzM+/v9i9xs3b0+Hd2P3Rw766rLvQxbDF0Nfd48G72s+xs6OVlJ/VqaaotcbWxcezqKetsbDX1Mq6s7m7qsa2m7Cmqq+ytry31NjUzMHJ1ru6yNr/6PLs69bTgMbNurm+ycPHy9HK0NjayvfR8OnN2ra0oaevq7mrv7e1usvBsbbCw73Gscbfvsq5uL7Z29vzgPfl38XPubG2wbbc56+nsLLO1ce5tc3RycGP2ODr4NPb0dXj2e6F4YHl6e708crB2rPS/ffSst3ZwcO40q2qo7mgwcbG0dLO2NPogOXz6NvOwsjFzbbFt9rvwb/R3bvNrqqfprG8tM/GxLmxu8DUztTGycXFydPS6ry6xdjJy7Kq0dK8v8PHuae0ubi3sKvBuOHN88fArMTX+sCuyLGexa+kq4ibnJC0oqGspaOno6CQlaa0obKwk4eMkJmlnaWruresttW1u8q3wrG4gMusnJ6XwLS7psC6zryLqNjVwJ3CsrSwrb7Xv7iuvsnGz8Wtq7i307aikYSPnKuPkpCv0rzDxb6isbGqoaalm4eSmpamrZKXu7vEpY6fqJujwMu/t7y5zeLt0eDAwM63s7SZsrfmtr/E4tDI7YGD4+jU3Le4zs3n9/DsuN/tg9PGgMfJgMzV3Py6hqulnMjD7erRl+zMvMzD6oru0+GGlvDtgIWL4/GCh8rSxOPlzsLQzLnftL286pDi1PjetdbKzdLz3t7j7cDG0M/RoruMjLaou7HPz9/O4ZWB9ebw+smQmcDS7c7u7smwnsmuqLarv7/TwKmuuIu2n6ickY6xnr+ygJWSanSoh4B9baB8eo6gjJJzd6V1X3V7gIJ+gX+Mf5ONhoWPbXGAeWRtXIqRdWRzjmySW22DiIuacYdyd4OLf3Z7gWJoWoV0fYODdGtxYWlzY3d+enhyeGR8fm6Fhp+fqZGOjWJmaJGKoIiEgmZqiG12ZWBgYmNcYnhrb19zeWKAgFZaVFFfU01lb25vk32haXF6XmdbinJyZGeNc4CZgXRjT1BdX05XU1NRR4tWTU9NSFdSZ3RbdJWUhYaCanCBVVNpY3Fuept3enJqc25vZnaVU5qBWWB8aFtlW2uLdWdecH57gH56dHFVgk6WUU6LgYKRcoSLnIpphnFpYGJfaHBegIh5gIF5c2hmZXl5kYWAj5CXcVttWmdlXl5wYnBmXFdYU2hOXFZTYH+QpmF6mnmfnHx4eHx3ZmNpWmNtYnt1iLKUnZ6XiYNsfG9/dYSNe4GqqWdocWJhXl1hWlxnZWRvdVBYp3BZUV55fXVxgIyRYW5maIlweX1NfmpsWmh2b2x4gG17nI16f1VPSl9eYVZbZWJqaGRtWGRxYWRTbXtzbJKCT5mPglp0e4J2e45zeICGgIV8fZp4bXN0aXWKgG6AhY+RkXRohHpdXlJJSlSGYFxdZHR7a2lbWFNXXFyDgnlyb3R4aYF4Y3RubmtmZmJfdHVvZ15kcF1hbn2YhYp/fm5zgGtxXlxiaWdraW9mb3Z1apFvhoFxhWtrWVljX2hgbGNaWWBcU1pjZmNtWW6DY21dW1tvc3SISY2AemRwXVthaGWEiVxaYGJ2fnJiX3V3cW1agYmSin5+d3iDeoNMd0h2eXmCgmdjfGaCqaiGbZCPeXlyhmdmY3NfdnZxdnBudXSGgIKMhXh0bnNzeWVzYHWQY2V0gWp9aG1scnuDfIuEhHR0dX2PiIt/fXp5fo2Opnx7h5WCgmxdf3trbnR1aV5ob2pvbWd5dJWJo312Zn6Oqnlrf2hagnVvdF1wdmaOf3qAdW1waV5SU15pXm9xW1ddaHB4bG1ve3Rsc4hqbXlpbWdtgIVwYWVhiXd7an91gXJLYoiBcVl1b3F1eISYgXdudXhyfHNeXWhsiXBjVVFZY2xUWFRpgm51d21aZWxoYGdpX09VXllnaU5Vbm91W1BaX1VdeIBrZWpndoiQfoptc3ltbGpTYmyUa3d2jHtujExMgId/gmxrhoWZnpycc5CYWoZ2gHh5WoWMlLd3YWxiXn53mpSAaZJ3Y3ttklqNeIFSX4eFSk1RfItOVHJ9dpWSg3SEf3aNam5vmmOViqaLaIZ8en2XiIWAh2VpeH+FY35lZod5in2Tj52GlWhWmo2bonZVZ3iMp4uoqoh0apJ6dId+j4iahGtqc1xxYmllYmGHc5J/gHx7V1+Oc25qV4dnY3SDdHpeZZFnVWxzd3x2d3F8dYl/eXWCXl9sa1xjVH+Ic2FthWKATVpsbXCAWnFmbHqAdW9vb1VWS3VteoF+bWNrXWRuYHR4c3V1fWl9e2t6eImHkHl4dE9WWXt2jXt8eWRpi3WAcXFxfXBkanlqalluc1p7gFZeXVpnWlRoamhnfmV+UFthTFRNdmFgW2OJc36bh3prXWFzdmZwbG1pWa5lWVlTUl9abHVfcYmEend2Ymp+VldpZHFseZd5f29jamRjWmd+RXpmSE5sZFdfVWJ9aVdRY29scWtmXlZDYT13PzxvZ2l4WWxxfm5Scl5ZWFxZZGpagINxeXt0a15eXnJxhnt0gYKHaFVnW2hlX15wYG9nXlxdWnBZZ2FfZ32CklRxkHKQi2xqaXBvYmBqWGFqXnBpepl3f316bWpZbmV2bHeCcHqlp2x1fnZycHJvZWVpaWNrcUtTnWtYUl53fHJsd4KDVWFaXXxncHVNe2NsWWZwZWJpgF5tioF1flpZWG5raFpcY19jYWNqV2V2a2pWaHVuaIt7S5iQhWB5gIJ5f5R2dXl9dXlyc41vZ2xuZnWLgG2ChIqKjHJqgnxmZltQU1+NY2Jjb3yId3lsY2BkamuNjIF1dHd4a4N4ZHZvcnNxcG1rfn14b2pwemRnc4GahYZ9fW10gHB7bGt1gXx+foh9g4aFe599ko14hm9vY2l1dYF6iYN/gYyGeX+Hh3yBa3uMbXNiZGV9f36UTpiKh3SDcG11eneXmmtkbW2Ej4JzcIWGfXRdg4iTiX1+d3iGgIxPe0t7f4GNjHVwhm2Msa+Jb5SOe4F7lHN0cIBpgYOAh4CAiIuigJ2gk4J5cXl8hXGCcISZaWh1gWp+Z2tka3N7dYV7em1rb3iLgoZ6enZ4eoOAlGhkcH5tbltRdXlpb3h8cmRucmxsZV1rZod4kXBrYnSFoXdqfGNWf3FnalRkZ1uDc3J6cGxxb2pcXGdzY3NzXFRZYWVtY2RjbmplbYhtc4BwcWZqgH9mWlxZf21zYnhtemxEWoF+dVx7dnt+e4afhXhsc3dyenBaXGpriHRkWVNbZG1VV1Fnf2txdG1Zam9ta3J0bl1jamVwc1VYcm50ZFpmZ1tgdXtsZGZebH+Hc4BmbXVqa25YZWyUb3V1jHpvh0lKfoV5e19dcm+BhoSCWHiBT3dmgGNhS2lwd5RkTVtVU21phH1pVHFaS11SbkRsU188R2lmOjxBZ3ZBR11rZX58cGpzb156W1pZcUV8c4x1VG1kZGuDdnRze1xeaXF2VWdTVWtdZ150b3lmcFBCc2l2fFU9S1BjemN7f2JRSW1aWGxjdG+BbFhZYExeUFdQSkltWnZmwnqDe7l6AXuSeoN7lnoBeaV6BXt6ent7knoGe3p7ent783qCe5Z6AXunegF7/3qVegF7mXoBe4t6A3t6e/96/3qQeoJ7j3oBe4R6AXuFegF7iHoBe4Z6D3t6enp7e3p6e3t7enp7e4l6AXuFegF7lXqCe4l6gnuFeoJ7lXoBe4p6AgIEAIDNwNi+rrLV3eXZ2c/n9ODM3qDCtZuzyPOA2M3N1du82fL08r6lo7ClmqyljpOvur68uJWasK7SycHH0K66sKa/sMLL3Ojm4f7U2Lq+xbe/wMO5y73Eqr65pJyn1OLmzdH65rimrqitxN3k54LQ09+Jp4Hg94n418Wxr7a609nQwICiuaG7vraCosSx5sqys8i/ydaC1MXa56uxwq2z1viAz6+io6Okl5GSiIqfsq6WkpKBm6O1nLjf2YPv+/3xuMewtsLhs9TU4dGuttLW4c3d2+qU9InuvbCnqKytsOH658bg3uCK2qy43dHy7YaSlI7bqM/G4Na/1c6swcnj2825y4DpxL7Wwb64uZ6dsqfD17Kumo2NkoyYqJeapKmonafHt7KmoKixq7GirsDP1cPDvqqktdPYv7u0rqSrn6KepqDLraOotPy9pLSlr6qjoqKw4NDBtq2nr8G8n5Wqmq+kr7nqwb3FrqKoucXJ0Ku0sKr95MDfr9HY07/fybq5tK2spYCfpLG5jpKPkLbGvdzRm7mqnJajusrKwdDUvMavycvtgPTY4PPs5tzq8tnYv87Tytrp7cz15urCyMiuwa6rt7y/ncjTtLW1pKGmqcHJz7fC1bfFwb2xsKWtwsG+uM3gytfQwOWKq56xo6SovLS3t77CtszpmPn/hfP3iv2Uh//YxYC1vbm8sLvJytrR0dTTzszJzt7jzd7NrqG5qqOvsrOzsKqvta6qqbKmqrO+qsO+s66Vw4HP3LvY+dznzcW0s8rCuLmxw7axx9DRwcrM28no3uTq2dnI1NTZ0dPV4OPf0vbb4ZHU5OTK0frk5dHRzLe4wtmnrbSysL3Jwsve09Hk0IDe8fDkzsbRzMjIuc3Rx77O0tPBxrytr6G7w8HEvsS7rr67vMzd48rAv8auw8POyMC3zOzQsri5v7LSysXx597SvLCyxLSE7+3j08O9xs/LytKxqKGVmZOXkY+FjJ2gnJ+dmZenoq+2qqWQm5+LjJ2XrbDPv8C5mKOgqb25t8Lau4CsrZuiqrzHva7Etb6qm6rCvcO/2q23yLG0qq7BrMaxpJato63HxLiorpeQmqm5laWltL+4vcDFwcqwqpqKqZmzrKeusaeYq7Dq1cOdm7W1uMzG48/GyMndzNDIyr2+tcK7qaDM99fKvcrKwNPm4PHh2eTY8OfS0M3d0+GS/u7cw4C5ssTT3d/WxbSzqq3F2aWM//3s67/O3fPgy+vd8rbjlIvVmNXmhtrKyOeDgOLF2d/gyrzb2IHr3cCU+MrTz9PW64veg9y9u/TD0Kaku+O+uM3Nvd/I5Mm++crCgtTRxIf30+mFiMq1prG8rsen1dy23/fZv8eovrarpKqknZK0z4CMfo9zWlpze311d3WHlH90hnFoYk1hcZ9Xj4WHgoxxhpaamnNcYGxlXW1qU1dvd3l8d1hhdXORhH6DkmlxZ111a3qEjZWKg5ZzfGhocWZvcW9reGtuWmtlVk5WeYmSf4eyoX5yeXN0g5ubmVV6en9Sa0yDllWYgHNlXmZuhYp+boBYa1RkZWA5WHJkmodsbHdxeYVVe22EkWJidGRogZJNc1dKT1RXT09QSk1dbGdVT0s3TFFjSlyAfU6ImZSUYXFhZ2+FYXx9j4JnaX6DinZ/gIxnomCXc2dgX2JhYYylkXOHiJJjkGVvj4Wbk1diZGCNaY6JnpyDlIlqe32PgXdjcYCHaGd5cm9tdWFkeWmFnHx4ZVZdYFhjcWNkaGtlWF10ZWBYV1tnX15RWW19iH+DemleaoCJcXBuamNnYWdmcWqSdmtudbqAaXhoc2tjYWNunId4aGFXZnd0XlptYG5ja3GXdW91WlFWanV3fmFqZFyek3GKYHl/eWaQeGxubmtwbYBqb3iAXFtUVXB7dIyCT2VaSUlWanN3cYB+bHdkfn2YVZ2AhZORiYCIloaIcH2Ee4iWm4CkmZ15hntpiHFscG51VXiEZ2hrXVZWV2txeWNsfWJybGReXVZaZ2plYnWCdoJ8cY1YZ11tY2ZmdGloamloXWqAXIaISX18To9YUpp6cIBkbGlmW19ranNxb25sbWtqa3qFdY6AaF1rYF1oaGRiYV1jZ2FhYmtlaHB5Zn55bGZNclJ5fF1ykHN/aWRcX3JvZ2lhbmdjcXZxZWtsdm+Mho6Nhol5gnp+dm5yeXh1Z4RvdlVwf39seZ6VmYSLhnp9gphpa25oZHF3cnZ+dnF9aoB7jY+JeG98enl6cYGCdGt2fH53fHl4fXiKj4uKg4p6cHRxbn+OjXZvbndkeoOPiHxxgZuBYGRgZVlxa2OHfXtvYFlcbmZWpJ2WhHVveHt4c3ldW1tXXVxoZGVcXnB1bHBtZmFpaHR4cHRmcnRiYGxmdXaRg4d7XWRXX3BpZ26FcIBpa11la3h/dmh4ZmxeU2F3dHt9km98iX54bnF/anthV0xeV2F6enNiaFZTWlxWUWFha3JtcHN0cnloZVZKY1duZ2ZqaWFOXWSgkn9aVmppbXJpe3RwbWl6cHRudXBxanNtZ2CIooR3Z3Jwa3yOiZmRhoyBlZF+fn6LhJJiqJqIdIBraYGRlZePhHZwa3KAj3ZgqaaVl2x8iJ6LeZeJmnyKYFx/Y4CUW4l+eJlXU45zgoWAb190cEmCeXVfnHuCe31+k1SHVIJpZ5FwgmNne56Ad4eJeZZ6i3FuqYN7WYB8dVmlh5tbYIV4cH+HeYt2oqF4nriTd311e3Rqam9xbWSBk4B0aHZeRkpka21mamJzfmpjcltcV0ZZZZBRg3h3bXlneouOkG5aWGljXmxoUVVqbm1tZkhLXV9zdG91g2RqZVluZG93foiBeo9xfGlobmdubnJxfG5zX3FpWU9XdYGGdHqdi2ZbY19jcoyOj051dn5NXUV4gkqJc2tcV15ngYp7bYBccmBubWlDWm9gjXpbV2BZX2dGYVZqdlBZa2JqhZpTgm9lbHJzbWhpYF5vfHdkXVlIW15qT116dUmClIuNYm5dYGqAYnt7iXVYWm5xeWlvb3lYhlB9ZV1aWV1bW36JeWBvbXNQb0lQbmR3b0FESkVfR2dleXlmdm9Wa3CGe3BeboCFZWN1bmtla1dabmF6j3FrW09WW1VhaV1cY2lnXWR/c3BnXV9paWdYXnB8g3JzbV9WYXZ/b25sZl9gV1xaYVx/ZllcZ6ByYG1hbGRgX19olYV4bmtodoyLbmV1XmtbYWiPbmpwW1NabXh4fFxiWVOQhWN6VW92dGOGdmdpaGJnZIBhaXB8W15XVnB5cYV6TmVdTUxXaHR1bnp2ZW9gdnCCSIpzeYyJhYKOm4qJbnZ9b3Z+fWSEfYNqe3ZqhHFrcnJ3V3yKbm1xZF5fX3V8f2ZxgWp2cGxoaGFldnl0boGLe4qCd5Veb2Z3b3N1hHd1dXVxZXCFXomISH58SopTTZV4b4BrdXR1b3mFgoyIh4SBf3x6eIGGdYqDcGl/dnR/gX9/fnl+g35+en1xcHZ+Znt1aGVNc1F8gWV9mYORf3txc4mDfHpvenJse4OAdXt6gXWQjI+MhYd5gX2AeXZ4enp2a4d0fFt6iYdyfaWVmIWJgHB3gZdrcXdzb3yGg4uXjoaTfYCImpyQenF9e3p9doWJe3GAhIV5fXdwcmt9hoOCe4N2a3R1dIOSkXdtcHdfc3V/dWpfcIhxVl1gYlx1b2mLgn90YVlZZV9SlYyGd29mcHR1c3haW1tXXFpnY2JaX21xbHBrZWNva3d+c3RncHFfXGdebG6CdHZuVWBXXmxpaG2BbYBiYlJWXGlxa15yZG1jVWF6d4F5kHOAkH17c3aAa3pjVktdV2N/f3hqcGJeZWhkWmhmbXNrbHFxb3loZ19VcGd8dHJ3dWlWYGSYjoFeWW1wdXlwfXRsaGl5bm9scWxsZ3NxZl6EnIJzaHV0bXuNiJeNe4F1iIFra2dzaXVUjINzXIBTT2Jyd3l3b19cVFpndFxLgIFvcU9ea3toWG9mcVtrSUlkUGd2R2VaW3ZERHtpd31+bV9zbkR0a1ZOgmNqZGRqf0ZxR25cXYZndFdVZoNoYW5tXnZcbFlWg19WRmNgVD52YHNFSGZdVGBmV2tWe4BcgJR3ZWlgaGRfXFxbXVJoeZF6AXuGegF70noKe3p6ent7e3p6e516AXuLegF7mXoBe5h6A3t6e496AXuHeoR7/3qxegF7w3oBe496Cnt6ent6ent6e3u1egF7r3oBe+J6AXv/erZ6AXuSeoJ7jXoJe3p7e3p7enp7hHqCe4l6BXt6ent7h3oDe3p7l3oKe3p6ent6enp7e5B6AXuKegICBACAt+rLtMqrtcjf6tvO8O31o4mu69OfmZClwKquu7ypq92h/M/TwZWjwb6/tZyn6uS0uNLHuL24sL61uLi3r6Kpn5WfndvL7NHB1ePb3ufbrLS1rLaxva2svKCfmtG9srm0rcHW7rXPitHpyru1ur+j0omc+OXfqua8raiat7bEtr6ArMDEtcu+ubG/xMTNv8fS3vXf1vjg6eW6v7mxwrq65MLPw8XPsLeop62ckqikrbKx7auxucPJ3ebaxrni1MXEsLGky8rgwL3T07zDuN6+9dfhyrCur5yut73AzrPfgNuE2NXk4tzazs7giPfMvq/Jqrvq6/r7htyzv8nMt9bSy7mAtK+nu6G/uemjmZrL5sWNioGNkI2WkZCQj5eqqqGltJqjmJe1zpyky9qpoqOtpJ6iopKryKff88eo1LLDyL/DsK2mrcjIqK6spaWiooyQkbLfzt7Fysiwu7O+wb2vtrXE4Ni4obSvs8a+0cGcsbHPz9PP37i7s8HDwd/R4N3Vx9yA1K6t3paij52mrczq1M7KrKO3rcLI0MDcx6yfvd3OscDIsr+tyu/w++HAz8rQyershIbxjYzv0fKBl6WhpKiuqqu2vNm8nKevpayejbjC0MetrbKss7Cdobu4zN3G+PPX2q+/raumurOls6axwsbMzuPT8Yjl2d7cxIb40cvL2+aArra4ubS+urvEzb7HzdjcxeOC+d3TzKiyubalopuhm5idtbS3m6OsqaamobmztOzBu7/kr8rUyr22s7+4tse8xb2/vquttMbetb7I3Niu4N3cxt/Q2dHl5tXl9f32zMLQyc7exvLvzNDb4bvNybzGzsK2nKCntbe6w8e30sva2MKA9evugdLJ7NTSv7/K0sfKz93NxNDCx8K3uq68xayytaa4yrrHwsTH4NjTucrP7MPU0r/P9M3QxMy+1N3WzYiUj+K9t72tvszi++LW09vpv6i8ycussrammZeYlZagr6WjoK2wnr2XmJutkJWlmqWdmJyvusHN37yjopalsKbAws2Asbi2u834u4rGsrnIr6u9ucSzrsSxvqKMk7bDvcPL2NKozdTOwLShn6OYlqSmnZ6ivra1usfny6/BuZiUiaSirK6sjIGMmKiszMK7pJ27t9i7tP/cs8OxsamxwMPFoamyrqbCu8TJwea+u7TKztj87OWB94Lp0cG5geza2826tKmAsLbN1eDd4rC44dLch6qL2+DW1cvo88Xi24T995Kf5vaB25KC5ZOysaurssbbw9LLzOe8z7fhgOeo1eTd3q3H79XT0tbRu7jm4cCxrbS7u7bNz6HD3/nZwszRybTSzb+G48TJzfrKva7isKGQtdfytbv28rrUgO3akKyqwcy7jfiAeq2NdYNnb32Mlod7lZCWb1l3hnhUUktieWtudnJfXIFkl3N+cU5bdHd7dV5lmZhrbIB4b3lxZnVucXRwbWFiWFNfX49/kHhkc4R7fIeGY21qZm9mbGFaaVNVVod4am5raHaKm22EYYWbgnZvcnVYfFlnmYSCcY9uYltTbG58am6AYHF2YG1lZGBlbG1+bnN5gpd/c4l5iIRjaGRgaWFgh2hzbnaCaXJmZWxgWGpjbXJwn2Nra2dmeoJyZ2GQhHd3aGlgenmMdm1/h3V6a4VnknyNfmtqal5veHhzfWGFTnpQfX+TkImHgYGTXaKEeG2Dan+urry9Z5l1foKBbISAcmWAX1pTZVJybqRmYWWTspJfWlFYYWBoY2RiXV1qZltfbVxkWVpzhU5SdINeWFdhYV5nalxvgVqNonRdg2t7gX+GdnRtb4SGaGtmYGNkZUxQT2uTgYx0c3NbZWJud3Zpcm13lYVlTlVRV2VmdGxQY2SBgYmFk3ByanV1c5KCjYt/doiAg2RplVtkU15gY3iQfnl2YV1qZHR0dmV7cFpPb4h8an6Fc3tlepiSnYhwemxtZ4eFT1acY2OjjKxaWWhgY2RnYWFmaoRsUVtfWFxOQl1kbWdSVVhWXl5RT2NhbX5pkpJ/fGFvZ2poeXNocGRodXRzcoByhk17cXV2ZFGUfHp9jZyAaGtmYVtlX2JnbmJobXh8Y3pOlH+ChW5xcm5eWVpbVFhccHF4Y2pybWlpZnp0c6V4b3GKX3J9cGhdXGNiYXBocm1sa1tcYGp7V11pc3dVhYOEbod8gHuCgnJ/hYyGamVvaGt7cZadgoaMnnmNhXh/fnRpWmFsdHFycnRkeHF8dmOAl4yTVIF2lYSKeneAh3t8f4l8eIF4hYeGioKLkHh5d2xxh3F7dHFzin95anuBm3SEg298nHx9bnFkeoV2cVNgXolsa3NpdYiaq5GEf4eUdmJ2f4Roc3hjYGBhYGVugXNxcHl5bYdmZ2dzWmJwZnFsbG14foGIl3RgW1NhbGp+eYSAb3FzeIWieFdxY2dwXlpraHVraHxte2ZUV297c3R6gX1Yd355cGpdYmdgYmxtYV5jdWtpaXeVeWR1dVhRR2NlbmxsUEhRU2Bjf3RvXlhyZoNqY5V+V19TWFZbZmttVFxmZWB3bGx0boxtaGd5e4SjmIlTmlSYfnVyXKeXl419eGyAcXiQmKGgpHd8mIuXXXleh46Dg3uToHSRh1imn2VrjZlThmBVlWZrbmdsboCOeH54dY1odWB9TY1SgJKQj2d+pYeBfYJ9bmuKj3dnaGx0dG58e1Z4mq6Pd36EfWl8eG5VkHZ/ibuJfnGcdWldf6O0eX+vpmZ4To2AYGhgfYZ4abiAZo92YWdUXmx7hHdnf3d7UkpieW1OTUZfem5udG5gX39elnqBcEtadnh6cltji5Fman50Y2pkWmdgaG9uZ15hWlVcXIt7jXRkdYV3eIqHZm9saHFrdGdibVdVVH9yZ2tqYm19jGN5WnuNem9na29XeVJbinRwXHdgVVFLY2x7ZW2AYnd9ZXNtamNla2t0YWNmbH5oXHVncnNZY2Jjb21ul3uViZOfgIp8eH9waHtyen15o2pwcG1sen5rYFyEfXJ0ZGJUbW2DamV0cl5eUm5Xe258bVtcXFlla2poclh1RWxGa2h3d3Jwamp3ToVrYFNoUGGKipWWUXNZZnByZX98cWeAYVxYZ1NuZpFaUFZ5kXxTUk1XXl5kZGNfW1xsbGJkcmRsYF5yglFVdoRgWlpmZGBnZ1VkclaAimxVeGBscW1vYF1XXnl+ZWhjYmVlZE9SVG+Xh5B6fHhpdWx4enVlamVxh3lfSlRUXWtqdWlKWFZwb3Rue11hXGlqaYVxe3luZ3SAcl5ikF1nVl5hY3WIdW5oU01fXG1xdGZ4aVJJY3puW2xyYmpbc5WUoZB1f29uZHx2Q0Z7T06Fc5VRWGxma25vaWhscYpzXGdsZGhcT2tve3VfYWNhbGxdXHFveodxmZeGg2h1bHBugX5wem1ve3h1c39vhUt7cnR1YU2MdHN1hpeAbHZ1d3J9eXl/g3d8f4SHbIJOlYB+gWlzenpxcXB1bm5xhoSLcnV5cGxpYm9raJlxaHCNYnqHe3Ntbnp2doV+iYOBfmxvcn2NanF6h4Rgj5CNdY2Chn+Kh3iHjZCGaGRtZ2p9c5eaen6Gj2yCfG51eXNtXGVygX1+gIR0iH+LhnKAppaWVH90nImOgoSLjn5/hY1/eoZ9hYJ/hX+HinF2dmlxhnN6dHBziH92YG5xiGBubFhmhGhpYmNcbHRnYkhTUHZfXWNZanmJm4J9d3uLcGBwc3xmcXhoZWZmZmhxfW9ua3V4bIhkZmZzXWZza3RtZ2VxdXN3hGdZVE5baWR3b3WAZGhmZW+FWkhkXmVyZF5ubnxyboJzgGZTVnB6bnByenJNbnVwa2NXXWJhY21xaGRqe29sanWWeWN3c1pXUG5zfXp7XVVbXGVogHhxYVlya4dqZ5iCWWFYWlZea2xrT1hmZmB3bnN4b49xbml3eoKYh3lKiEmCal1XTIR0dmtcV0mATVJmbXd6fVZad2t2TF9LZGlgXFdueVNpYUJ3c0RLaHM+YktDc0RRVFBTVGN1Zm9qbIFkcF13RXFIbnlycUxfgG5qZ21rYF99gGlcWFpgX1pnaEdngIxyXGFkXlFkXU9AblhhaJFqYViEYVRKZ4KUWl6Ie01gQXxxWEVXcn5uUJyPeoN7jnoBe8V6AXuJegZ7e3p6envnegN7enuJegF7i3oBe/96u3oJe3t6e3t6enp7unoBe4V6AXuXegF78XoBe7l6g3vGeoJ76XoDe3p7hHoBe5N6g3uKeg17enp7e3p6e3p7e3p7kHoBe6h6AXuVegV7enp7e4R6Ant6AgIEAIDP3eyNpObE+cTF3ufV6NDTpIzj87m/s77j+sHJs67CyMHN9NTeycvRxtK2wru91uLGzb+zvbS1xcWrnJ+jnpCYlKCe0ujhyr+2vNDnjvCKx7W9rLO4wqOnppWe2LK9zL+yrprKs+GAgrSvtqefoJrCgPrNwoGC87S7wLfau87i4IC1ssfX3cqFhd7Ozd3b6OTwkJD50ejl8+fv74n/0b63yra6+b+toqChzJyOpYummqG21LnGgvPNyqm0srOouLarrqaxo8rKu8DRuNvV0OHXv7bGqry7q8C/x66hvNnS197r7ILPwqmZrKupobjEw4bav7nI3O3etpfY7OPF387BwIC9yd/LoaKvycOSi5apqZSTjZSdmp2P8oKLkJWpvci9oZmNnaGNlJm8qqimlo+coZ6Vk5Was8TIyLixzNPWvLO+qbK20oLZzLGjrq/Cr6at2cPK54LV3N67xszVva6/zLi7p7Wns7K25s7Z6dz19e7f9eLq2OG8xri/vL68vozcxoC4/ZLOp4+ooZywzNXazc3NrrOloaOru7WsvLOttbCnpZ+ss7asm6GsvcjczNXL6drvgdzo9tfGvsqjq52mqpuhoKmsq7GytLC3tqK8u7DDraSlvKuisa2ttbPb2NzR2sjRv8nCsaixwsCxxrrD1sLb8MDSz8O82rqsvbPYzL7JzYC1tMSuubinvsOywNjdz8TT0t/Y0b7lq7mxqbqnlrOsprC9u66rnqOmpqaovbyrsNfBo6av6OnG6NDJrsjnu7rGssK9t7+/0duvrc7e2aHbvtGo/LG1y9Pd0dLX1OnTzM3dxdXf0ebPvMjG0NLV0sq2tK+fxqW8qaq0wsPGwMu8wYDcuNfo4N7p2ePZw8W5qrXDxMnYzsO9zdO+wK+koL3Cw8irsra4wbrg3crC0dng49j63fOG+9zEwNzl1dLK2dPUxcq9try2qNjausCrqPXOzdjm0MKxrKWjpaSfra+npZaClqag44yVpLKBpJyVn72orKWbsa+6ppam07G+sruyroDS1bmtuIWihs6+z8W1s7avw6SWoZmEkIyQr4+gwc28w87Y2djF1Kixpdywnpylu7fBvqywqaW/raSpsbGhqZahpqqloqe3maSep5acnK/B5sfT1OTJ0LrCxcbLsba/u7/JxevLurfB1sjLwsrFwcmG+pPT0In1zcvS3dremtK4qYDJzM3Q2efRxYnyjeaAr4Li3M/D3/aR3N7i387n2d+onIra4OLKxbfrvLbExcOzr7+xucjLwOfTmJz06eb8mcXLx7a2vs3fzdfWy8nAxtLNzY3Vp8Hn0+fNzrvh79fKtdnLzdDd9NzCq7K+jp2xqLfBx+C6x+fh7d39/ajUzobzxYCBjJpedpRyp3R4kpWBjXZ/bliEk2dwY2+ZrICDbWd2dG1xkXmDdHZ4dH9qc2xqgnxydGxja2loen5mX2hybV9iX2Vbgo+IdF9WVWB7T4BUb2ZyaGxwdl5hX1JaiWdtfXZsall4ZolVVmdncmFcYFd6WKZ7blBSlmFjZF94ZXR9eYBYWGRxcWhRU31vb4d7hH2FVlWFZ3uAioWOj1qddmlndmFknWxgW1xejWNWbFNpXlxnfGBpUZFwdV1ka3BtdXNqamJoWYaFd3qKc41/doWCamZ1XWlpYXZ3fWtfe5CIh4iWllh8cGRZamhnYXV8e1+VgYGQn62heV+Sn5F5hnVlZIBfaHhpTFJge31YVVpoblpcVltrbW1gnFZbWl9xfoh+ZFtPVl5MTlFsXltYTU9bY11RUVVXbXp8d2lff4WOfHR8bG1tgluNgWlaZ296bGlvmoWEoVqEiYtlb3eBcGBvfm1yWmJUVVFWemt1g4CamZmKoo+Yh5RveG95fXdxclmKeYBpmmOBX0pcWVNdc36Gf3yBaW1iW1pZZWJaYl1gZ2dlZGBucHJuW19kbW54Y25uh3eJTYCJlIR6eoRiZ1lcXlJWWV9kYmhqaWRgYlFqZFdoVkxPXldSXmFeYGWBgYNzfW9vZWlqX1pkdXdpdW5zgXGBkGp5enJtjHBjcGaJfGhvdYBiYWtcaGxcbW9cZXR2amBwcX16gHqfbHNta3hlVWpkX2dycGhrYWhpZ21ufXxraodvTk1QfYBeeWhiUWqDY2VzZW9nZW1qc3pUVW98e1SHb35ammBgcXV7dHZ8eo9+enuIc4CMgZaDcXd8hIiJhoBybWxef2h3aWtreHJyaXRpcYCHaH+LhICPhpOOfH92amt2dn6MgHl4iJWDiXtwbYKDgIRsb3J0fHGPhnRte4WKjH6fh5pWnoJtZXqEdHJrd3Z4bXRpaXNyaY+OcHZnYpx7e3+Gc2xkYVpgZ2pkdnp5dGVSYGhgmlFabXpUfHZqcYRxcW5jc3F8aWJzlHiCdHxzb4CLinZweFhtUXNkcmphY2dje2RaZV5QWFRUb1NheIJudX2BhYp6iGV2cKSBamdqeHZ6eGxtaGN2amFpbWpaXUtUWF5aVVpmSVROV05WU1xne2hoa3tpdGBoaWxuW2FoZWdwbJFyZGt0f3V3eHt1dHxZnGN9gF6igICOm5yjfZp6a4CEi4+TmKWUiGeyaKRbgVmQi312kqdqi4yOiXmKjYluaFmJkZmMhnSre3iBfX9vaHVkbXt6bYx8aGeWkpKpa3l+e2lnbHeFdoKAe3x3fId8gV+AVnCWh5aDiHiWlnxwY4Z+fIGNo5WBbHR7aV1wa3Z+hJZudIiJkIGYlFV+dlWbdYByeoZPYnddkWNpfoBuc19oXUpwfFVgVGGKnnd6ZWJzc2txj3qCbnV8eIVud3Fsf1BobWVZX11eam1YUlhfXlZdWmFZhJGIc2dhYW1+S3xPbWV0a290fGRmY1VahmZteXFkYE9sYIRQUGBjbV5ZXFZ1UJdwYUZGgVlaWlpuZ3V7doBbXWhzdGxSVH1wcYJyeG90TEpxVGZsd3aEhlWYe3V3h3qAtIZ2bm1um29gdVlxZWZ1iG10UpJycVlfZGdfaGdgYVplWHp2ZGFrWnFrZG1sXFhlU2JoZXd4e2ZXa3x1dXaCgU5yaV1UZWReVmhsaE16ZWh2gI2CXkl3h31sfm9iY4BhanxsTFBcdnZRUFlqbl1cV2BpZ2hdlFBVVFpseH53Yl1UXmRSUlNrXl9gV1VdYl5UVFhccHt2al1Xb3N1Zl9pVlpedE59dWBaZm15bWpyloSFn1mChYtnbnZ+bF9qe2hsWWFTWFVbfW1wenGDgX1vhXR6b3xga2JrcGtmaEyBcYBkhFJ/ZFNhWlRZanB2bWhsVl9bWFZZZV1RWldZY2JdWFRiaWxoWl9mc3iHcHZwgm97Q2pzfnJsc4FocWRrbF9iYWZraW91dG9yb2B3c2h4Z19hcGZhbm5qa2+Li4x8hnx9c3l6bmdxgX9zgHR3g29/jmd3eXBriG5jb2eNgnJ7goBydYJzfH9uf35qc4WFdWlzcnh0d26KZHR1cIRxZHx3c3mCg3l6aWxramplc3JiZYJuVVlfkJNyjHx2ZX2Wc3aEdn92c314gopiZYCNiFqQe4lin2prfIOHfoCEgJOAfHqGb3uIfZF/bHR1en+BgHttbW5ggWt5bW5ygoGCe4d5foCSbHyGf3yLhJWTgYd9cHOCfoSNg3x0iJKCiXpta4aKg4hvc3R0eW+Igm1hbHF1dmeHbYFKjHBhXHZ9b2xncXBwZ29kYmpsZIiKbXlnX5p4eX6DdG9qaWNocXJre358dWZTYWpjl1RbbntVeHBqcYVvbGZaZGBpXFdpi2x0am9nYYB9fGhdY0ZQP2dgdnZxdXlzimxhaWFRWFFTbFFccXdlaHF2eHpsdlZmY5d2ZmZtfXt+fGppYl5zZF1kb21gaVpmaG1raGp0VFlRW1JbWWZxhm5wcX1ocmJpa29xXWNraW50bpJ0ZWhzgHl5eHt2c3dPiVFjX0qCZ2dzf32EZXpeT4BlZmlscX9tY1SPWIJIXEVrZVdQaHtSZWFgXUlZXFhLS0Bjb3pwaliHXFhhXmFdW2teZ3JwYXpkQExzbG14SFhfYFRTV2RxZm1sZmdhZG5tcFBqRV17a3hpalt3fmlcT2tkY2dwhHhoWGZuSVNjYGdoanVYX3N4gnqSgVJ7dFGNaQV6enp7e4t6gnudegF7nHoDe3p7l3qCe4h6Bnt6enp7e5B6gnuIeoJ7iHoBe5l6AXutegF7i3oBe6d6AXmtegF7jnoBe6l6AXuEegF7rnoBe/96/3oEenp6e9F6g3vnegZ7ent6enuHegF7i3oHe3p7ent7e4Z6AXuGeoV7lnqCe4R6AXuSegF7mXoBe5J6A3t6egICBACAzc73goK5sr+zscmJh4WJhqCKhZ7N3t7B5InY1Me/zdro6N+Ei+uB6NbU1b7DqJnRur7L3caA7O6Lhq6po7W2wq+zlq7Bt62xr7fP1Ib/8+rnvNPT8tW52vmYrNPY6e+6paSvrsbj4N/QrMDU16abqq2Z4NrVueje69nCuLq3zPGAuLinrri6xef80bi5w9Hq3tHt7dTh3fqVl9775sKYp7C9vJT1sJSYtLOhl5Sd0amsrsTH2u7KspqxoZuNvsXWzqKopJvks5+zubvU1cfL0dq2naGfq7Gpsa6nq6XE4ti+paSdsq6roZiSj42GsKKspaKhqq+ttZ2u0vTT3vG7ssCAxruyrKbXzK2lya+moqWelYuOlIibhoCGjYT3hJGfm56enrOelZ6joqWpp6KmlpeknKW0sMHFxLOjtbG1oaeXzbDOws/Yycmtot7WyLm8tuevvazE1Ly2xb3s7uTrtbKyt6uaqrKozdnun4Pz19na3MHPzdPDtbGfnZWPr82hguuA2t2bqqWdx9280ujbwcLAvrSqtbvMuaOxxruapam2o5Sin56sqJ2oqqOvvsPDwtjF49bZvMawp7itprezvrW4sLyvuLe6zMG8sbCxtK6wrZ2trKmftsjNu8PO1sLN1tPK1cLOyLywyMPAs8jb2sWrzcLd4eLUzsDU2Ma/0Pq0rLCArK6t17K8tuCuzMfAtMPMwcK/wb7Dxr++t7WssKzDv7XEyLvGuqWkqJrEsqOxwbjJqZmyyujm3trb1cLi4tbWytfm28O0p6rlx77Ix8yvwdTfq5qlr7jZtp+nrKGl1tTs6cHBvcfJ28/EvrKzs8XaxtXHuO+xsMSpqsLFq7yyv9GA0beund/uw8Db5dLNxa22ztvO9sjP2d/QssGio7K7sbWxsrK3w8S+xdDPwr7I3eTj2+HJ0dXGxsq3usW6w8i+29q/rbislZOUoKmxnL/BtvLHv6+ttqinmZmio8LBnq2kma2jo7CYnZ2Dhqmco4yal5Scjo2Pj46eoaq3r7WqvKqAgtGml6zCwrmVyMLVwcPDs62Uhfqrraq0tc6TqKy/xcLNwbK8rqGorKirsLacpqKv6ejX0dK6ycXIvra3t8m3vJ+asMuwtrK0kqWmrri9v7y+ycTS1rzLwsvNxuLY5Nv+ysnb5ILf0t3Qx8G9trnJ/9TFttXw6tjQ9ufQ47XTxsGAtrW1vrC2ucXVr7jZzc/Ix9fsg8fQwO7IufOT9K2RypHl5Nnmztb37MK7nrCxqr7N0MS8wd7Yy8jg1MnW6oPcsNO9vdXZ1dLHxcLJxeTmg8rAy7+06r3EvNG9wMrlg7vIwbvT3+nTz8Cm/6/Fva2w1uXGz4GO68TL0cOuws/BxcuAdnedWFpuZG5eYXVXUlBRUmdTUm91iIpynWGMiHhsc3iHf3ZOVoNNgXd8gm51W1J6bWl0gnZNlqJrZnh2cHx6gHB0Wmt6amVdUFNkcVCfhoSIY3d7nolxkKdYaYeImZlsW1ZbWm6GgYB0Wmx9hGBTYmFjh3x2eYqDinpmX19caISAV1hLUFdcZ4aWcFpgY2l8c2qAfWZwcJFiZIGklHVPWmJraWSYYUlQZmxeWVddi2JgWGRqgZJzXlBsYVxRen2FfFlhY1urd2Btam97d25wfX5qVFVOWmNcZmJcYlx2jYdyXmFdcmxvZ2BaWFhXem12cnR4foKCjXaBnbiTmaFoW2mAZ1tWUVF9dF5agW1kYm1oYVdaZGBuWlZYXU6IRkxUVFdWWmxWRUhQVFlcXVleVFRfXmVpXmdqbWJXaWlsXWNUhmqAc3d7b3RbUoiCdm91cJ1vd2t8inNob2eLk4+RaWVmbmJTXF5TcHWLZE6XgICJjHaHh4uAcXBeXlZPZX1lU4yAgYFMXVpOb4VlcX97amxtcGliZ215aVhicGdKWmFrW1NhX11paFpgXVRfbm1pZXZhfHV/aHNmZXZrYmtnbWNlWmJbYF9gc25rX2BhZmFdXVJgYFdRZXJzYGFxdGdsd3RrcWZtamZgdHBwaoCRknxkgHiKjI5/fnCBgHJsfaJkYmeAY2NdkGFwbpRof3FpWmVrZ21qb254fnt5cG9oamB1cml2e295dWJiY1yCc2RueW12WEdWZn+Acmxsal56em1zbniDcmNZS0p2X2BlZG9Za3+JW1BZY2uGZ1ZfYllejY6gmG5wcn9/jYN2c2ttbXyOfId8dKJycX5nZ3uBbXdnaXeAeWxiWY+dd26Fjn9+e25uhYmFo3Z+j5WMdY9ub4CFgIN7fHV4gXx2en99bmdyf4SBen9tcHZta25fYWtlb3BshIRyZGplVVNTVl1kVmlpY51zZ1hbaF9hW2JqbIR+YGliVGVjaHZlbHBaX4B1eGZsaWFkWVZVWFdjaW5+e350hXaAYZZxZneLh3tWenJ+bW5waGhYTItucW54eI9fcHGDg4GDeWt3a2FncnF0fX1qbWNrkI+Be4Jqc3RzcGllZW5aYUZBUWZYXVtcQVNZYGdrbGNhY1xoZ1NfXmtvbIWCi4OUbGd0d0l4bHh2eHh3cnOHt4h6bIackIiIsaaQqX+fkImAgYaEjX+FiJWhfYOfko+CeoudXHuFdqB+b5RfnnNcdWKRlZSdj5i1roB6Y3BwaXaCgXdtbYV9b2uEe3eCmFmGYn5ub4KIg4mBgn2DeZOUVnlzfnZtpHh8dYV4eXqQVnJ8d3GKlaCJgnZeqX12cmdmhJJ5fVNelG5zdmZTZm1dZm6AaWeHTU5aVF1QU2VKRkA/QlFCQVhecnRjjl6KhHRqc3WBfXdNUn1LgXd8fmlwV05oaGhvd2VHgINQSl5iXmlteGptVmVzZmJjXmFxeEKQg4WIZnl8nYx1i5Zaa4qHipNwXl5iYHKHhYJ1X3GCiWVYZmBZeW5dWHl3fXBhXGBfa4iAX2FUWF5jbYmVb1lcXF9vZFhqZlFaWndSVnqflX1gbneCgWuweF5leX9vZmJmlXFxb3x+iZR7aFZoWVZOdHqHgFtfXVOPZ1ZjXF1qaWFkbXFdS05MXmpkbmpkZltwhX9oVFRUa2hqZV9YV1RRcmRrYF5iZWlkbFZhe5d5go5hWGeAa2JfWFR/eGRgdm1rbXNvZ2BlamJwXlhYW02LTFRcXWJiY3NcTVFYWl5iYl9jWltnYGNrZnBxb19RX15hUFVGdFZqYWtxaGpWUYSBd3J4dZtxfG6Ah3JqcGSHi4WHX15faVxOWl1TcHaJX0mHbGtxc2Bub3ZrZmdXV1FIXHBZS4EMentSY2Rad4dkbXhvhF+AWVVdZ3dpWF5oYkdUW2VXTV5dXWlnXGNmX2p4dW1mdWF2bHBZZVpecm5te3uEeHludWpvbHCDenhtb3J2b21sXGpoYVlvfH5sa3p7bHF7fHd/dHx7eHGCf31zhJGMd1x2bn+Cg3RxZXN2amZ3nmVmb290b7FxenyfcoZ7dmVtcGeAaWZraXd6d3h0dW9ya4B+dICDeIJ7aWVrZIdzZXB6cHdcTmZ1kJCHgIJ9bomKe4J8iJKEc2tcXIpzcnd0eGV4jpZoXmlyfJp8Z2xuYWGKiZqSZ2pseHmHfHNuZGhpdYRzgXhwnW5uf2hogIh1fXFzf3ppXE6CkG1qhZKHhn9ydYmAiICac3aEi4h0iGlod4B4eXVzbG52c25wdnRiWV5qcGtlZ1phbGNobWRkcGVvbGp9fWtdZF1RUFFVXGVUZWdhmXRnW2BvZWVdYmpthoJmbGZca2hteGZpaVVbfHN4Z25nW1xRTElJSVlgZnRvdmh1YVB3VUxccXBoS3J0g3d7f3aAdWRYnHNxbHFyhlhmaXZ4c3duYGleVFplY2lydWRpYWqPjH10eGNtbG1rZmhsemxxVlJmfGpsZWJGWWBmbHJ2bGtsZm5tXGZibW1rf3uGgJZraXh9Snhvend3d3Rub36nfGtZbYB1aWmMhHaNaIZ5b2JiXGJUXFxqellgfHBuZVtwaHVFV11Sd1ZIYUJsVD5QSm1ycn1xd4+NaWRPVVVTX2xtXldWamFUUWhkYW2ATHBQbVxbbW1ucmlqZ21nendGY19nYlmHYGBaal1gZHZFVmFdVmt3gWxoYFGSYGtoX110fWRpRk99Y2tyaFlsdGFlZwV6enp7e4Z6iXuFegF7iXoEe3t6e456BXt6ent7knoBe6R6BXt6ent7oXqCe4l6AXv3egF5znqCe5J6gnv/ev96/3qEegF7knoBedB6AXutegF7h3oGe3p7e3p7nXoBe5B6AXuOegF7jHoBe4h6gnuLegICBACAv7DmlOm92eHXrt3f7o2UgPOBgYDZxsS/uKuzp7uypKTHudvZyNL6rafx7MXCsqCus6qsvLLFsr7l0cr76cq7z962uc280sLWorurut/qiPzxz7Skra22vZqhlo/BttTCxqytuLG8vqrstvrLucXIsq3NwtXAtdaBiOy5vpyh9a6AxNPFxM7Vztbj2cS+wdvr2r3L6Iv+7+uOro2I7tC4ubi0o8XdwJ6WoaynoJimsaWysLm7q52YpqG2m5uZxZiky7Gpkbysq8/Qwc3o2NHGvJOctaOopqy207/ApJzAzaSQnbukrqKnrp6WkJSWkZSWurKzsdHF28Ozu+/Yy8jVutSA48PNx7rE47/Ot6WiqbGlno2bppSSo5aemJuUpaystKum2c+ktZ+rpaigvJ2KsK+njZKaqLm4tbTJgobq4erWzPLftM3LyLG43tqwwbuzvriwsbfV0s7Lxsfi3d+NuazMxa+Uq7zS6YHh39LDy8LE0MXG8821t6enopygrrDQ3d+AxdimobPXxr+ZtcnPvLvCsqmcrqi71M/M2MTZtMTIoa+fkZmro5+ip62wwsagt8K7xubFvsPKwsi0tcalsqm4u77Ex77NvLi5r8S7ubCusq+6tKqiu72trMXHzM3vxKi01MO0vb22uMrGucXQwreuzbj4jOe4vL7d4r+6wJePoqqAwcXDu7zd2bestr+/vb7Asr+2xMDTv72tp52wr7i/wN/ZysW+tquytqWtqKebppewsZLH69bR08rS5MiuycW/wcPW077e17XTr7XB6M69yfP/1aSup6SgprXAwMbQ2cLGyKC5tsncx9G4wMjRvcDAtsfItLGvpqPG1rSwsK66wtOAxsOxqL3Pw8bFxsHAv6ystrjU38zN9fDg082wur27uLbBubmyoObbu7vBu7+8zvDlyrW9wMu7yLe0srGstcK/xrqsuriqqLuGoqSemKOoqZ+elpWjoZKTk5mhra6wnK/IsJacmJWbmZ6JhaGvr4GWnZ2enJOelrOlr6Gzq5qqv62Ap7a1raCorLiboK7kus3SxtvAqc2ls7PN0sDQ4MvP2MvW17WumqDDtaeZo7qs8cius7TO1r7+5MLHx5rKwejh07vGzL2/wbKlsMG1pMTBv83DzMnQyNbF18Oyt+HA14LlxMflku6B5Nnb6er1yr3H4eXC6d3Z6szT4Omv08bIrr+Ayrm92cXDwcrczrW20cWm6d3j/c3py4HW79WWhIfQvurvvbr5wLnL9PLU4dby0tTP3fTLzd7S3M7W0Lu6z8i1oqCso6+tu7y7tcDPvMDcgunIyNu4w8G1xse/rLG4manVucfX0OLJy4mB+P6Y3rq58/S82K3V0re6lpyruLa0q7iAY1aKXpZviJmNbJaNmGFgU5pSVFWJf398fHF8bn5vX155ZYKEe4apfnmho316alljaF5kc215ZXGcjISwpIqAjpd4eol3h3eHVWNTW3qFV5eMdF5PW2RpcldiW1J4boFzclhVXVleYFKFYo5uZ3Z3Z155cnpnXHNQVothY0VLilCAZW1gXWFsaW16cF5jZHqLemJsf1SQhoRZdllYk3ZeYWFfTmuCb1NPXGlnZVdeZlljX2dqX1VUY2BxWlZUeFBae25pXYNzZ3pxYG2Dd3h1d1dfc2VrZWVphmllVU9udldJWnVjbGJpb2JaWFtgVV1ggn2AfZeOoox4eqOQhn+EaH6AiGlybWNviW6Cc2NhZW1mXk1YZVlYY1dgXV1VYWVgamBahXxWY05UVFZPbldJcWxjTVNXXmplXFllTlWPjpmEhaWacIWAg2pvjYdkbm5ocGxrZ2iBeHRuYmZ4eXpRXVRzcFhDVl1uf0h0b2dgaWNpdnF2pYBwcmlqZF5haGh7hoeAbXpRTmN+cXRPZnR2aWhwZmdjcGdwhIR7gHF8ZnJ2VmNeVVpnX2BgXF9damhJWGdgaYlvanR5eIFrcH5fZ15kZmRmZmJvZmhrY3Rpa2FaYFxoY1lTaWZaVWJoam2KY0xWb2RVXWJgYXJxanOAcWlheGehYZdnanOQlHBqakc9S1KAZWttZWiJh2lbXWRqaWdsYm9mcWl8bXBgXFhlY2hqa4J5bmxqamNqcmhxbm1hZ1ppZEpyiH1yc2xyiXJidnJxdHSHfW2QgV12U1xlgmtibpSheVJdWVhRVmNub3WAjn2DiGR0c4OQfYBsdX6DbnNybHd7bm5vammImXJsbGZtc4SAgHhlX3WCe3p4eHJ0c2NibHSOln9/qKWTjYt3f35+enh/dHRsW42HbGlwampqd5iKd2ZucHdrdGRhXl5eaHd0fndsd3RjaHhMYmFaWF1fYl1aTk9eYVdYWF5jbGtrWmyFbVRdW1lkY3BiWnN9fldmamxrZFthXHhte2uAe2t/j32Ab3p7cm1xdoBhZWyccn2Be5R7a5BvenaKjoCPlISKkYiKjnVuXGB8eHFjbXtxqnldWFlydmOdh3BwclB4cIyDeGVwdWpqbGBUXm1kUmpqanhsb2hsYGldcWVYW4Nme1GEZ2h8U3xHb3J2io2ac3B9kJJxl4iCkXqElqN0l46Se4uAkoaJnpKMjZimmoeHo5NuqqGktYaafliGmINlWVyFfpymgYKygXeFpaWHl42ni4uKk7J8d35wcGRrbGBogH1vYWJqZm5reHx7dniGdHaMV5t9fY1ue3VogIZ+bXF4XmyPd4aVjZR8f1xSnJ1igmVgl5lsg1p7emVnTU5eZGFcVl6AXE94Tn1acH91Vnluek1MQHdBRUZ1cHB0e3V9b35yYV1wYHh4bnKSZV2MjG9xZldiZl9jbWVuXGOCcWuSiXNuf49ub3ltfXGDWGdbZ4aJTpWPe2daY2xxdVZfVU9xbIJ1dF9bY2JoaVqKZ5F1bnd4bmJ8cHVmWGtJTX5aXkhPhFiAanRoZmp0b3J5b1lZWGp2YUtVZkV3bmxOZVBRkX1udXZ2ZoOXg2Nga3h2cGdweW92eICAcmNgbmd1XVpYeFNbfGtlWnhrX3FqWWd7b2xmZEpVal5mZ2twinZyXlZ0fVpKVW9famJpcmVeWltdUlZXdW5wa4F4hnNjZpF8dHF5YXiAgWhwamBpgmx+a2Jnb3pya1xocGJgbGJpY2NaZ2pka19bhHpVYU1WWFpUcF1ReXVsUVRZYGtlWVRhSEt/eoBxbo+HZnt6d2Jphn5gcHNwd3Jxb3KJgHx1aGh7e3tTXlVycFlDV2N0hkx+eW5haGJocWltmXdpbmRnZV1fZWZ9iYiAc4BZWGmEeHNSZXN3aWhtYl5SXVlrf3pwd2l2XWhuUWFdVlxsZWNlaGxreHVTYGpiZoRoYWlvcXxxe41ueW53eHR2d258cHFzbYF2dW5ocGl0bWNbc29iYG10dHOSa1FgeXJlb3Jvb4F9cXmAbmNbb2CUXI9iZmyIjnNscFNMWmKAc3l7cXOTk3hqcHV5c3FxZW9lbmuAcHJnZmFsbHN2do2Bd3V0bWRreG95c3ZscWFva1J8j4B4fXZ7jXxpgHh3eHWFe2qMf114WGJrinNpeaGqh2VvbWtmaXV8eXyBiXZ6flpsbX6MeXxocXd8bHBtZnN4a2lrZGOAj29tbmtxd4qAf3NfXG+Aen18fHd3cmNja22Ch3VxlZOHg4FrdHV5dXN7b25lVYeCZmVrYmFeaox+ZVRbYGxib2JkX19cZW9tdG9ibWxgaH1OZmdkX2NlZV1aTk5dYlhYWmFmdHV1Z3mSd11iX1pkYmxeWnN8fFVkZ2ZjXlZaUmtkb2R1bV1qeGeAWWFgV1NaX21RVmWVdIWMiJyDcpNud3CAgXB+gnF1fHN6fGFbS1Fua2JWY3hvrn1hW1xvcFyPfGVkZ0dybYqFgGlyd25ub2NYYW9lVnR1doBzdW5yZ25gdGpcY4Nid0x+YWJ4UXdDa210hoeTcW57joxoh3VxgWZvfolceXJyXWmAbl1fcWNfX2l3bV1fd2hHeW9vfVdnTj5YaVhLQUVbVXV/X2OSZVtohIhreWuDa2tqdIpgXWdYXVNcXVJYcW9gU1deV1xYZWlkX2NwXV1zS4JqZ3VYaGNbbW5iVVhcRVFxWmFtaW9cX0dBgINTbllSfoJZbklpbFxkTFFha2ljWVsEenp6e4l6B3t7e3p7e3uTeoJ7pXoBe6h6gnuaegR7enp6hHv/ep96gnuhegF7inoBe/96i3oBe/96/3rtegF7hHoDe3p7sHoHe3p6ent7e7F6AXuYegV7e3p6e4d6AXuMegICBACAuMbSkuzWrbmJ4f7z3uS+8fqMiOvhwaK8v8m/2dXMs6CvjZ2+t7rFg++dvLq81bmvoruvs6HE0uXMgtLWgeXYwuPXipTu8OaJ0LKjrbLpgPXzxcq76YDLsKG2rpu1yLjkgYHlwffRy7uH1Lqo2bavraiSn8Dh0tGEooXz1Lfr18CAx9SBvrvbi8vF4rqB7/Xs5+zKz4uO6szk9/TMvLTEuri74ra9s5mYmKCnrZ6yq6Svve/9rJuXqKugl52Zsa+rrsjE3buvzsHbw9jg+tDCs7iwr6uptLe7tMXFpbq4rqechaCotbOlrq2etI6lnqGUp62qtcnAjN/v0uvU1Mri6PiAnZPg4e7diPzEtaLByLeoua6unZyXp5+mrLuzmquzzcDB2IPOv6yfsJaixZaVqbiaj4qjvae51tfjkI7T5ffTx4DFvMDOycGuucW+x7DBzMXLv7qzuba9x+7U1dvUxaHWxKCtx93t4Ond6fLW8oXh2IDOvaGuorCvkpWgo5Wrp7KAurCsr7rCloyTqdjRwsi4sZWrtK+nxLzGybvNy8fGusa/l7Kuna+nuajM1Lehqbi6v6bLqsC7t66qtsnH0NCvxr/J1Nn14s7F0dDLwqWrrau4ybzFur2rtsXd6s7QztnAxrm7sb2t1MS3xMDHvaezy9Py5ufc+KD48tq/vLm3praAusOpv8q2qa+0t7a9ubnDwb7Dv9jRuMartbi2vcW6v+bS18y4ydG2s66pscGlnq60ncvhueb8uqrEyd60t72z1sfS3tzvz8zJztzX+MPYxM/KxMK5vaOhpaK3zuHCw6ups6WyssnQxs22r8nWzsvV0MbI2NLc6c/h2L+uqKaqwNGAyMC2scLN1cvVycG7trGxtajQys3P1/mCjdLHwNDAv7WwrrKZovSI3MrL2szHut3/w7vCxti5xrLH2MXJr7e72ce9pLKxo6GhrK2horisprutnKGxsayGlZiRjJqht5+9tLi1k5uQjYPk6+7z7uSMm5WYlJ2xs6StpbTDybmupaOAoJSPldTYuMvE2MzRzt/yz8uypJ+Up52jq7LFu7u509TF1suop76rrK+sxLKSnLPIrqG3+9LNuqGgmaSotbmzuLa3vdvq7MbAwsKyzcvC3+bh38m8w8a4zsOvtMG5u9TTxuXr+fPM3Orn1d/dy7zR1O3HxNTRwMfEx7ux5ui+ycSA2Nrq4s/h9N2U4M3rxcrh2MbG/NqDvMzN5vOT/tvexcTW1s/TyMLQycjWvsLP5ePU7+vx2OXN1N/J27yYs722treuqb3Tyse4ssDTtK6wguDly8mntK+wtd3N3YLNz+2vsNbw4d/7i7rd6/eg1M/AjoPCgOX68N7dxq2dyNXDvb6AXWl0V5WAXW5ki7Odh5Fuj5ZVVIqEbFZqbnlyi4V7Y1pmS1h1b3F2WJlpc3F1i29nW3BjZldzgY12VICKXKOUf5qQVWGZlI1be2heYVyKUpiKZmpiik95Zl1walhmeWeIT0p+ZpVvaF1RemNWh3FsbWZUX3CDenVOYU6JbleDcWGAaHBMXlt5WGhngFtMh4mDeX1jZlNZhXOMoaV+dGtxamZmg1hgXEdJTllgZldpXlhebpegX1RUYWhZUFJPYmFfZHp6kXRqh3OGaHqGmXpvYmxpcnFwdXJzaXRuVWhnXVVOPVJYY2NbZGJVa0xhWVlRYmtqc4J8Z5mnhZ+JhXiOkqGAa2KMiZyKVaJyX1FjalhUYVZZUFZUZWBlanVtVmRmd2NkdE5sXk9IWktZe1JRYnNeV1FgbFdohIaCXFlygJNxc1FxaGl3eHFja3lze2VzenN5a2ZcYWBkaottcn13ak+BdFNhc3+MeX5wfYBuhU1/eE12bltpXW5uVVhfXlBbWGGAY11dYG1rSEFGWIB+dn11cl9ye3dsin5+e2+Cfnl0bHNwS2JjVGVhcWKAjW5YW2RiaFR0WG9qbGFcZXVvdnZXZl5jcHWKfW9jb25raFRWXmNvem1yaGhXW2mAjXJ2cIBqbWVkWWpdf25kdHV3bl1peX6TiY2DmG+hnohsaWVhTliAWl1NXWtaVmFqbm93b29xbGlsaYR7Z29bXWFhaW9lbYt0eXBmc4BlZmJfY29aU2FnUXqHZ46iZFdud4pmZWtkhnZ/g4mceXFvcYF7lWZ6a3d2bG1kaVdVWFlwhpV/gGpnb15paYF/dnVjWXB4bWl2cGpxiIqNnIaTl31samhpeH+AenhtbXqCi32EeW5vbWZma2ODfHl9hqpaZYyCe4aBg3lxbXBYW6BciXl6hX15aYipbmZubXZfbV5wfXN5aXFwi3x2X2lnYmNhamleY3hqaHdsWlxscm5SWmRfXWRsdl93b21sUl5VVFCJj5mdnJNhaWRmYmd1fHB6cYOPmIp/dnCAbmNjaqaef4x+in2Df4qagINyZWZhcmhwdn6Nf3+Bk5OClJJxcIBxd3pyiHRbYW18YFZpnn1+cGBdXGlqc3FmaWVkaIOPimtob3Vqf3tuioqAfW5maWhbdmtcYXVxcIJ5bISIk45pc4OCdoV/dGx/hJR2cn56and0cWxpn6B7hoGAl5qpnpKbq5RolYGQfn2Qgnl9ro9adoCFmqRps5mbgn6Ki4qIenSBfXqDdHqGmpCFm5aRfX5sb3lqfmZNaHRycm5na3+VhYR2cXqIbmpqVpGUfnxhb2hnbYt8hFJ8hp1pao6rmJGmYYeIjpdrf3ttW1RsTn6Shn96aFhNcnxqZWWAVF1nSH1qTVpOTI9/a25ZbXNCQ3RzY1Fqc4N9k42DZ1lhRVFpZGRrUotdcHF1i3RtY3dsbVlueH5mR2x1Tot/a4Z5P0uAgX5PcWNcY2GKTpeNbm9mjlGDbGBwalZhcWB+Skh5ZZZ0bV9ReGNWgW9rbWhTXnCDd29IWkmDa1aAcmaAa3RLXl14UmNfdU5BcXRtYmROUkZMd2WAlZ99eHF/d3Z8nHF5cVhbXGl1dmp7dHF1gq62c2NfcHNkWVpWZmRfYndziGtkfW+DanaAlXZpXGVmbWdrcnV4b396Xm9uY1xURFxkb29kbmtbbk1jWFdOXGNiaXNtVYCKbYFva2N2eIWAWFFzcIB1R5RuZFtze21ncmZqYGJfbmpvcXpvVWJhbmFhcUlqXVBLX09dgFZWaHVdVVJkbFdgeHd2VE1ibXxfXERgWWBtbmpjbXp0fGl3fXd7cGxjZmhvcY9yd4F6blKEdlNecoKPf4R4goVwiE+Ce054bl9uZXZ2YmRqaltqaXCAd25ucH2AXFNWZIeCeHpuaFRmbGlgfXB2cmZzbWxpZW9rTGRoXG1oe2qIk3dhY25qblh2WW1qb2lqeoqHkY9vfHB0eXyVgnRtenx8dmNnbG96hnl9cXJgZXKIknh8dYNxd3BxaXttkXxvfHp3aVZhcXSLgoR8kGeYmYl1b3BuXWiAanBgc39taHJ5end6cm90b2xwb42GcnplaWxob3lvd5d6fHVqeYVtcnFscoNtZnJ0XoSQao6eZlpveIlmaG5nh3V8gYOUc2tscH58lmZ6b3t7dHZxd2RkZmV4i5eBfWhkbGBtboeKgIJtZHl+dXB4cmtvgH2Ck3+QkHZqZ2VodoGAe3ZuanmCioGNgnt6d3Bvb2B4bGlob41MVXZwbX55e3NtZWdQU5RUeGpqc2djVnaVXFNaW2tVZlhtdmxyY2hshHp1X2hoZWlocG9maHlrZXNqWFxscW9QWWNaV2FnemN8dnZyWWJbWlOOlpykoJZia2JiXGJxcWNwYnF+hnZtY16AXFFMT4F/ZnVtfXiBgIyagIJyaGZhbmJnaW96bWppeHhrfntcXHBiaGxpfXFYYW98YFVjiXJzaFVTVmJncXJpbGRiZ3+Kh2lncXNmeXlviYuCg3FobGxheW1cYXBranltYXl8gntcZnV1bXt6b2d0eoVnYGxrXGVkYl5bi4pobWiAeHaBc2hwgGpNcGJ0X1txYllZgmVCSlZWaXFOgWxtXFxqbG9xaGRwbW93ZmRtfXJleXNyXmNPVmBWaVZAXGdmZWVgXnCAc3JkXWRxWVFMQ3V7Y2ROX1hZYXxqcUdlaXxRUGyBcGp5SGNobHdWamteUElbQm58dnFvYVRMcXtnYlsEenp6e4R6gnuEegV7enp7e5R6A3t6e496BHt6enuFegZ7e3p6enuGegF7hnoBe4p6gnuGegF7jnqDe4h6BXt6enp7hHoBe4d6gnvgegF7inqCe4R6AXubegF7lnqCe4V6AXusegR7enp7/3qGegF7/3qheoJ7jXoBe8F6hnn/ept6AXuLegF7hXoBe7N6AXuMegF7inoNe3t6enp7enp6e3t6e416AgIEAICN8N7SyOC4uMSTi+Pc1ODW6PLj2P2LzL67ssnPx7rAuNmmmLTBpL703Ma+x8XTtcWxrb6hsO7y+8bAvqq3ydjOkdLi6Y3y1tmmr72usrXM3vbl29CryoPeqamqtLSywOicqYL1juvk9L2jqq+7qL29zqGA5PXF8OCIguf95e3Uy4DGptLjxc+sq/zLiJ2ThIGG9svoio36gIq1l/y9xc29ydze98rFtaKMgomfqqCnlpartaWQqIyBlaSpr7jRz8K1ss+5u8/Mz8u2sri8vrWmxcmsp6en3Nu6ydvEsci3rZ2VoqSqpJ+ro7DWvbu/ybbX4bXgur6xwOHTt661tKzDv4DX14CS5eS/ycLK0c+4wJyloJ6hsqOpo5+kq6G6q6rCzMbLxsy+uqWsrq2qlpuapJacm7a/qsTY3oHj6tOrx62nq7Gvy8rg0cLH093DqsXKyLq7w6+hoqqrucnM2cbBwsHKxt3Gxs+75dLIt7OxpLOvs7iqop6ilpShn6GupqmotYC5sLGwqaCdlYuWrKyfmqKupKCunpuao6i5ury6wMixvrKvyLqbs6ivp7O4rZ+zwbWpu8SkpKCgvc/CvLC+wLe9wNnZz8/nzc6/sc23p63V7s3Pzez3wdHEzczIxsrPxLm2pLe6pqOox7KsoLrArbvT0L7J177T39K4srfAuKWppoCpuKeytKq7sMG6o7WtqrbHy8HK1sTLzr+zuLCxsJ2zy8nUysbWxr3GyrbRxJ6fvMWlwcmu3/zjur2/xby7t6rb2cPb5aWzz7XCt9WF18vLs7O/urK1vLvDvby9tsC7ta6wva/dybLX7dzBwtLCx8nOw8C+wZzEwLi9r7+3qcDI4YDvzr+2zsrLvLa8t7auurOvsLvCvb7R19jPyL7PwNO2w8DGzs7U2evW09bHy9XDws7HyrfH0cy5vbe9zNfAxrC6vr7AwbC2wKKssKWis6ins6CkjqqgjpKahZyJk5Odl6SsrrqcnraimYuD1eX3ou2Foo+ox5yhpZ2mpaqpjsP2zYDQ7LKyvb6qwsLL2Mna0ayoq5+klJSxvNjY2cC+yLi8sLCoz7zMzMjAp8O8oZ2Z8Nqzob+akaCinZOhn7C/srK1p7q+vuHGt66vztPS48XC1cPHq8TG09+zsqy0rrmsxbe95Obj387W94np0Mfb1NnC1drIzrjp2dbm9ebr1+HRyIDw7e/M3vPIzvzq0sLc2+Dh4OLm0YrR6MPV24GCx9Om6L2+uLbCxsm0pcC9nLq50dHHuvLP1cjh6tPxv7OlxKW3s6ulpL3As8m2pcC2+4jWzMm4sLDDqbyx1K+b1bfG9N31gfDV5P/x/IPTuP2Jhb7c/8jlo+TY397fr5i72Nve1oBakIaCe5Fvcn1fXomAeHtzgol/dZdYcmVpZHiDe3F5cJBhU214XXOkh3Vvc3N5YnBfXm5bYZ2bp35+fWl2hI6IZYSUm2CkipJma3NiZWx4jqOQhntXcU+FYmFnb21pcIlaZEiOWomDnW1cYWNuZXVwgXJRhYVnf3JLSH2Ne4NpaYBiSG14YmpsZ41pT2JbTU9Tkm6EVFuaUVt+aqptcnJja315iGhkW0s/OUFWX1VcTE5lamRQa1FJWmRoaGd7eWtpZ4x3eoSAfnRiWl1fZGFcf4x0bGVfhYNlcH5pWmtfVktHWl9jYl5rXmmKdHd2e22JlGySbnFsdZaHcW1ycWuBfYCPhVZkjX5eZ2NlbWlZXUhPSkxRZFheXVxeZ1tuYVxvdmxsaWtiX1JdXl1bTVhaZlpgW2dtW3F/gU2Bj3hYbV5bXmRgeHyTgXR6hpJ3Y3Z5dWZlalZMT1VXYmhufGtlZmh0c492cn1pkHxyYF9eUWBfZGxhXFxaUlFfXV5iWFhXXoBhWF9eWVFSUUpTaWtdXF5raWp1aGZgZGJ0cXNzcHNncmZhdGhOZVlgXmpxaVpufGticX1dW1dabnxrZlxlZltcX3F0cG2JeXVsYHVmWlx/mnd3coiVZXBkbW1ub25zbGhjVWFlWFhac2RiWnBxYW6BfG15hnOClIpza25valpcWoBbY1tkZF9vZXVuXG9nZ3J8fnB2eGhwcWVaX1xma1ptd3F4bGx5aWRtdWR5dFRVam9QZWZTeJR4V2BocWxobGSLhHB/jFZdb2FsaYFZhHd1YV5iYVZbZGJramttaHR1dG1ucmKDdVl2gHBXYGxgZW1wa2ZseFuBf3B0anVuZHd7jYCYgnlwgn95bGlvamheaGJfY2txbGt8g4aFhnyMfItwdnN0gYGEh56NjZKGipaBgYp/g253f3NkZmJqeIV5gWt2fH15emdvdFtjZF1dbWRoc2dsVm9sXWZqWGZaW1lhW2FoaHVfX3JhX1tYka6+haJYcFxwjmlqa2Jsbm1sVIO4ioCJr3l8hH5peHBwd2x8c19gaGFnXV5zd5GNjXlxfHNyaGRfi32HioWGcoOAY2VgmIxqW3hoVmFkY1RiYGl0Zl9dUF5jY4FuY1hadn2AmH16gmlrWGlteYlsamBlZHNkeGtvjIuAf250lVuMdGV8dndkdn10eWeWi4mapZWeiI+IfYCmoqaJm6eBhaiWhXWQkZaUnJmdimOIon6RmWFokpx4sIaIgX2GhoVzY3d7XXZ2jYmDcKCGg3SKkoCWcmdeeF9sbGhmaX+GfJmEcINzp1qHfIN+c3OEZHFsiWlpiGxwlIGbVZ+Hk66osld/c6ZdWWiBn3KDZ4uIlZSUalZtgYODfoBGb2hjXnBTWGdJS3RtY2Vba3FoZohRbGZsbYGKgnl6c41hVGhxW2+ahHJxdnV8aXdmZXRfZJaSlm5oZVNcZm1kUGNteU+HdYBdaXNlZmlyhZeMhHxdd0qEYmBmcGxlan9QV0B+UoF8k2pbY2ZwaHhzgmBIgYRne29FRn2QgYhwcYBqTXB6ZGhPV3VYP1BLQEBDc1RqRkqAQ05oW5hudHpveI2MlHt5bWJTT1lsdm1yZmZ6hXplfmBYZ3Fvbm16dmllYn9wdYF9e3JiWVxdY2Bbc31oZ2djh41zgY93ZnRlXFBNYGZpZWJpXWWBaWtrbWN7iWSKaW9mcJCAa2RmY15vbIB3dElWeG5SYGNteHhtdWJqZmZtdW5zcGlqbV9vYl9udW9vbG5mYlZiYWNhVV5faFtZV2VpWWx3eUhzd2VMX1NPU1tbdXeKfXV5go56aHx9e25uc15SVV1aZW1xfWxnZ2hzcItzdH5tlYV7amlrYnJxdXtwa21sZGNwa2luZGZmboBxa3JybmlpZWFid3ZqZmZuZWFuYWFcW1doZWRkZ2xdaWJgd29UZl1kYWxya1xtem1ndH9eW1pfd4t+d296fHNycYF/eHOJenpvZ39uY2aIn319eo+aa3dtdHVzdnZ6cW5qYG1zZmNkfmpmW2tpWGN0b2Jufm59jYh4c3l9eGpraIBrdGt2dW17cYF2YnRsbHiCgnV7g3Z8fnBlamNpa1pteniAeXiAb2x2gm+FgWRkeHxgdHJZfZZ7Wl9ncGtoamOKhW+DjVZdb19rZXxWfXJzYWBoZ2FocXF4eHZ6c399e3R0e2qRgWeEkIJqcHxucnZ5b2tsdVR5emtyZnRuZ3l6ioCWf3ZuhISBeHR7eHRseHFraW1uZF9scHBubGN1anxia2hoc3V4e415dntqbHpoaHRpbV1odWteYWFkcX1vdWJsdXd3eWpzf2VsbmRjdGlqdWdoVGxmV11gUWBTWVdhX2l0d4NrbH5taGJgm6+9gKFbcFtthGFhYFdeXl9eSG+ZeIB4mGVndHJgcGlweHGAemZpbWRoW1tsb4h9gW1ocGViWFROdGhxdnN1Y3V1X2FdkIlpW3FdTlxgW01eX2l2aWRlVWJoZ4ZyZWBhdnt8j3lyfWpsWWppdoZna2ZsZm9ebl9ifHltbV1kgk5/al50cHVkdHltcmGLfHeDjYGEc3VuZICNgoNkc4NdX4BwZlducHV2eHZ5ZUxecFVlakdJZnBRg2RmZWRuc3NiV21tTWFecWxfT3ZIXlFmcWN1WlZSbFhjZmNfX3F0bH5lUmRVgUdqZGpmW11tVGJedlhYcFZad2d4QnZibYJ6hUNeUH1KSFZtiGBtVnh3gYSDW0pfcG5uZAF7iHqCe4p6AXuqegV7enp6e5F6AXuJegV7e3t6e4x6gnuFeoJ7jHoEe3t6eoZ7Bnp6ent7eoR76HqCe7R6AXv/ev96lHoBe/96n3oFeXl5ennEegF7tHoBe6t6AXuFeoJ7l3oBe5l6AXuMegF7hnoBe4Z6Bnt6e3p7e4V6AXuMegICBACAjs+frMrQxbzDrce3xdXT4OyB2YaIioTc0czq0bXJyMetvLO7tbm7sLy77MWau6LGlJCXr63Gtrfm28fOyoHp4NLN2+nUx8LAvNO5o6y76q28vq2/3cK6qbOrq6yxt6+2tL3P09/+7PvlsNbixaSeip/BqaO6tL7Yht3Oydzf2OWAx6Spwbm716Dy5uOT5/L/7uLTtaCmlsCrwefc1/vRs73N2o/4vbCgoqSUh5GJlpiXqLCemZz0mabOs7K6vK6uusHHzr22wdPew6fdvLO/2djSqsmdpcjKwcXGzLO9s8KnsbK2sKa1sMXDw7iVg9XM1snAw8Czu6bDmaLNnbmws7CA0dvhhN/b0M6xsMKkm62lnaKNocqQpY2blKqQqKi0trPFv6yyyryuqrrItZmrtJmUpLOpq6TKzsK8mtXs3YD89NS6mKusk5Kgrp+juqq3urvBv7jM1u+1xau5wN3sy9LNvby6xc/h8czXzKmvs6imsqmwq5ucrarRwra4qqqirrSAuKeZm56ekv/u9Irt6PmCiaO1sJqUmJKvwd27vsPI/sfBxNKsoKmmj5Kzxszo37O7s76qqsCqvuu9pLSutrm4v8zDysXI++vCxLjI0dDV2czUwdPd3b24yry/0KuC0Na0wrK3vZyTubWsq5uoq6izsqCjoKevq7Kws6iwsaOxrLSAsLKtsr2xqLegpKWdqLe+0NHLwsnFwtW9udfBrbnSy9rJzM3Py8nBvseeqq2Yosq1osjdxdzl5dm5s8G3wLq36sjKsbq7rsfDwsTG3PHL4bzLzs+5yNDQwbi5rbzOw8mpra2qpb3K1NaytcLRxMy6vqvAysWpw9jYo7PB08rU5tyA3tXCxrjG2b2/yMfkycG5wae8x87HvsC5zaS+7uLAw8u+2OiTgP+Cy8Tn0qyusa2pvMK7rbCeobu6v8Wys8PCycKYn7yhqqqqxbCwtrS8tNGXmaWejYuXjpOVjpSBo4KrpKKopISarI7smJ6tlIuBobSd/5GKlbOss7qysabFubKArqKvs8HFoq3IzuOI0L7Zxqyks6m5z9vguLXA867ZpKW7z9LPwK+srL6qp6y3w/S7usWst5qoq7GjsrK0vrWvqb/Gq7m0pru8ury8x720tq6rsMazw8fApqzZu7Wptfju8oGjmOa/7ufs9NzM197d+Irh4tXM0dTCwcPK1+bTsLGA0r/pzbfTxbfjjKXL1/LTv87t98Pb5PvUv73W+v/F2ebPvLW4pKns38OE2tKx5uLmv8vl2sO/h+HK0MW6tda3yPbDtsOnlqW7uY/n7tvXuebUxMu+47q1psS8yb+83dy77ZTh7veNgI/87faH/8C34OXr6/TZ98vk2b+vsM3VydyAWoFndn+Ae3l/aHxocntxeH5HbEpOUUl1cnGTgW57fX5md2tzbG5rX2domXlUdF2EVFFZbWx9bGiPhXh+glmZjH59i5mEe3Bsb4dzYmdzlGR3cmBqh29jWGdiZ2pudGxtYmp5eX+ViqCNZIaXhGdhTVx7Y1RlYGV3SXNpZ3R3doiAcFJWamhke2OSiIdgi5OnnI6EaVRbSW9db5eMhaN3WF1lb1KKXVNKUFNHQEhBUFJSZW5gXGCIXWJyX19gYFtfaXeDiHx3hZOUdFZ9Yl9yjI6IZ4pcXXR5cnBvcV1jXGVQV1pfXFRoY3J0dG5wVXtxeWxkZmRbZlNwZm+DWXBqcG+Ah42UW5OHgn1kYWxQS11XT1JEVXdMYU5dVmtVaGVxcGZzaFlXbV9WWGp3Z01aYVBPZ3RlY1lwdGFgRXuWgVKspIZwVGlqWVxpdWVsf3FxbmxsaWNteJNeblFZYoCObnFwZmZocXiNmnyCdVRZXVNQWVRdW01SX12Bd25vXFxSX2OAZFpTV1tYTYV5hk6Ego5JUGZ9dmVbXFNmfYlramlxo3NucHtiWGJeSU1rfYeZm3N3c3hmZ3Njc5JuWWVfZWNgYm9lbGNolIhiZ11qd3uDiH+Fc4GJi21odWtxg2JafYZpdGVlZVNObWViZFplZ2ZublxcVV1nYGdma2JqbF9oYmSAXWNhYmxiYHRjZ2RgZW95h4p/cnNoX3NmZYByYWuBdYBwbWtzcW5oZHJSXV5RV3djT2tyYXJ1enBaXG9jbGZklHVzYG1xX3R3dHZ3hZVuf2NoY2hWZGxpYV5mYHKGgYVrbWxjXG93f3lZWGZxaWtna19ud3dec4yKXmxyhXt/hoOAiYZ8iHSJlXNwdXKFb2tpdWFseH5yb3JthWJ4qZ9+fHxse4lbTJlPeXucinF2dnNwe4F5aWhaW21vdHptb3x+hX1aW3BYZGFkd2Zlb256eZVjZnFqYF5oYGNmYmRScE1vY15lY0peclqOY299ZWFWdIVvn2BUV3Bna3ZlYldyb2mAZl1sbnZ8YGh+fodWe2mAcmZhaWl5iImMZ2FojmuDVlhoeYyNgG5udYhwaWt2g6Z2cX9reFxqZW5fa2RiamhhXnB3Y3BpX21nZ2hlbWhobmdjaHdhbHBxWmONcnRrbKmZlU5rYoReh4KBjXZjbXh1kFGBjXt3gINucXZ9jJqHb2+Aj4GhiXqJfnCKWFt9hpV/c4SdqHyQmrKNf4CXv7qRp6+Uhn97ZmaWjnJThHtjkZCTdICZjX91XZOAhXtvbolwgqqCfY51ZXGEfmiRlpGOc5CGfYZ0mXVwYXl0fXZ2i4hxm2mOmKJjVWOjkaFfsHJmhoyUjJV8n3qPhnRkZX2BdX6ARlhNUV5gX2BoVWRXYGdeZW0/X0VLT0l6dniVgW18fHpicmlwam5vZW5vknpZcWKFXFhdbW14a2WFdmNmaENvaVxbaHdsZ2FkaIFyX2RxkGRxa1tkgG1nXGxmamprb2lrYWhzdnyNgpODYXiLfWZhUV55ZltrZWp8Snlwb3+Bf5KAdldXaGRgcVd8cmtOcXqLgXNtWEdORGZaapKLiqWAZGp1f1aXcmhiZWddVV1XZmppeoNzb3SqbWx2aGZoaGBfZ3B3enBqdoOLcVl9Ylxpg4N+ZIFcYXmEe3x7gWxya3ZgaWluZltqYWlpZl5bSG5rdW9pbW1na1pzX159WGtkZ2KAdHp9THlwcHJgZHVfX3FtaGpbbX5jdmFuZ3JcamNraGFval5ddGRbXWx4aVVjaVdOYGlZWVJnb15aP22AcEaQi3lsVmpoWFxseGVrf3B0cW5xbWZxfJNjbVVdZYOSdHp2a2pqcnuMnYGHfV9mbmdmb2lxb2Jmc26Phnh4Z2Vha3GAcmhiZ21tZLCnql2fnqNTVmd0bmBYWVFicIFdWVlhlWdmbnxjWmRjT09pd4CPkHF2dn5raXZmdpR1YG1qcG5sbnlxcGVnj4NhaGJwfYWKj4OHeYiNj3ZvgXR8iWtahYludmxublpScGxnZltjY2JsaVhZVlxnYGdqcWt2eGt3b3OAa29sbndta39wcm5nbHV9jI2GeoB7dYVzcIVyXWd/dYFxbnB3dXNxcH9danBgZYNuXHd7aXuAhHhfYXFobmlrmHd0YWluYHBvbG9wfYtsfWFpZ21ebnh2bGltZ3iHgYRqamljYniFkY5sbnmDdnlvcWFxc3NacIqIXWdvgHd8gXuAgYF4gHCCkXh0f32Sfnh1gGd0fXxuZ2hhclBjkIZtbW9hbn1XR4xIZ2eIdVtgYV5cZ2xmWFtPUmVqbXFlZnF0eHRSVm1XZWZrgW1tdnR/epdnZnBoWlliWVxdWF5Pb1J0bGpxb1hsfWahbHaBa2JVcIFpl1pPUWNdYGpbWE1mYF6AWlReYWxuVV1vcnpKcmR9b19cZWZ2hYaKZGBmjWx9UFBicn1/c2VlanppZGx2gqV3c4NrdVtsam9dbGppc25oZ3R7ZW9tYm1qa3FudmxpaWNiaHtlc3RzX2OLcXBnZ5yJgkNZUWxMdXV2hHBib3h2ikx3fnJxeH5ra291hZN+ZGCAe2mFbF9vYlJuREVfa3hmWWV5gVlrdIdqW1tzlZdsfodwZGNkV1uMhm5RgHJTeXV3UVdwXFBJQWhbYFtWWXRhcZhzbnhiU1prYUpWcGtsWHtxbXBmiGplVmtla2RjdG1SdFJqcHdIO0l3bHpKjFlPbXR5dH1phmZ6dWVWVWprX2GEe416Ant6hHumegF7unoBe456BXt6enp7lnoBe5J6AXm1eoJ7i3qCe4l6AXu5egF7yXoHeXl5enl5ec56AXv/esd6BHt7envBegF5iXoBeZh6AXuQegF7xHqDe4x6AXuYegN7enudegF7jHoBe5J6gnuWegt7enp6e3t7enp6e5R6AgIEAIDeluTaxKKaoa+jx+He99fn6eHd5Pf74t72yq+0vba4u7Oqq66voI2UpazVuJHd2bu1pZuRmsq2pMu6oc7kg6rih9je4NjJztvBuLavqafC0Mvv1/LtxdejscPCtayauq+wqLq8vt3YsNLCs8DJpq+RjKenhKWknb/h4crPzdXU4oDr1svcztrT1fLk9+jb5cbC7s/d7r+hpau4u6imr9/Uu7iuw8bBv7HYurq7lZ3CxLTPuqOwoIWThJKWlZyassS5xL/Lu7Kgqrm6qK7ApaOvt6uytbKuv6mzq5+urtHBqaCzysWwqca1nL/PwdnF4cPPzrvHx72vsKmmuKySrM/Fp4Cpu7C4wZy91smusLy7q6SerpGamKWdhJCTs7XpwsHD293ovaHBxKKhrrrD06efo62y2MLtwdXOyrKewM3e7rTCpKecmZWDlqivq7XAwb27tLGptsDCxMT226+3x9nL8s/Hs8zRyMC6u7irqK+1sKXSwLC3qJ2ysr69uKCjmomHi4CVjYebrp+MgJSGgoOOkpWgqqamppSnqra2vsvE3Mzn3L7Ize6rw9Ovmp2ip5a6w6uiq562qqq2qq6opKrNuauzq6yurLjMwOfL09CxucfNxsjQwsC4vLjCt7e4tbnOxrzDxbi+xsy9xKusqaamo6Surqa/v6Woq7PDobOlt7qmsYC1s7qyuLWZuqynp6mlp5+nrMO6v7O8u7G2t7Gxt77H18LDyMrXvri4ubirusO/3LXA0NrM1tjl5sGouL6ztru2wM+7vr220crK2szZ2OrZyuLo5cC+xsm7tcbBwa63xr6ys7vKpqazrqGTqLquurq5tsLJna3vx9/Ersfav7zz3IDOzrvFq7W2sK+5xLqz5dLGyMDKvLSXk8PF1c+7wcjNwtLw5d738oDB3trGqrW/xcmzrMHU3ra4sMTSxLnFtb3Mwbmwr7Sozr7Av7LY0cKytaycpKChrpmRrqexr46fkKLowqOcqpaNj6GwvZuWpZ6pp6yKlpanrbOtvbLO07uxq4CVkJqfrrKvydbH0MvBsbqdnJedpKyxt+HkyMzdwqXBzru7nMfx2bGqypW5y7W7rd7kp6SxvbCwsrG3trWTjaCfuPLDsLCepLGguLq+pqO9vJ2po6+km5airqmpl5myzs/k2PLXt9mMh93k0MDT7d3a1c7rxuCOtrzY6vjm3Me+p4DHucPPueXn6IzQsNPmyszIt93p0IaH79TF0N/cxMnS2uLqqb27p7va6Ijb5drt4ODYyOLK0rzo1oOY6MrW0L+v3Zir59Gqqajk48DFwO7kxa3H6tHS1sLhy83c1+3HvdvVvN743Ifgzubg4YPVvNby5KWIjPbYt+PV17+10+u6toCOY5OSgmdmcHlqf5SLmHN7eXJwdIeRgIWbd2ZxeXNzd3FsbG9yZFBSXWSKcEyNi3RqXVRPVXdgV3ZvXIGVWHaSXIWDjYZ4fIZsZmZjXFpwdnKXf42NbXdVYnh6a2RYcWliXWxoaIGBYYB1anmHaXNVUW1mUllZUGhwcmJnZmxugoCRfXOAcn54eJaLmZKJknl3nYaRl29RVltgZldSV3x5X1xSZGJiXFVzYmNoRE9xb2F8bV5mXktVRU9PTVNSZ3dwe3yKeW9bX2NnXWJuV1llbWRsbWNibllmYVlgYX1sVkxabWdWUG1jTXCAdYxziW14clxgY1xSUExSYVtEXHpzV4Bea2FndFNxh3dgXmNiUk1JV0FJTVlXRU9Tb26ccGloeHmHXklncFZUYG17h1pUVV9hf2uUbX13clpLZ2+FmGlyW1xTUk0+T19sbXmGiX95bWNdZWltcXChhltidn5sjG9nXnFuZGVhZWFUUldbVU9wZVhjV1NgZXZ3dGBcV0dERoBNSUldbGJOSVlLRkZOTk5YYF9dZFdfXGZpbnltgHGNgmJxd51edohrY2ZqcF55hXJscWR9bW5yZ2xoYWeDb2BiXFtdWmh6cZd9g39jbHuDfoCJfH1zeW91a21sZWRxcmloal1ibnRlbVteYWBeYGFsa2V5fWhoa3KEX21bamlYWoBjYWhiaWdYbmVjZmxpZmBhX2tiZl9na2ZtbWdla3V8jnx5d3J5ZmVlaGlhcHZtiV5ocXNmb292d19PYmdfYWlka3NfYWRcbmtsf3V7e3p8coOIiWdncHNqaHV3emx1hn51dnZ/Y2ZsY1JGV2NVW2BeX2tvT2ObfpNzZneEbG2fiYB7fHJ9aHBsY1thbmJZin1xdGZzbmVPT3x9kYV2d3d4a3qUhoOTkE9siIl7aHR/gYZxa32JkXJybXuHfnZ7cXOBeG1kY2RfgHN3e26VjoN2eG1fYWJlcGFefHaBhmBxYXO1iWdfZ1RJT2R7impoeHR+c3pUX11lYmNYZFxvdmZgaIBUUV9gam9of31tcHVrW2lTXFthbHJzco2KbGhyVkthbmRpXYShkHBxiVRwg3J8apGZZmNze25tb21xbWtPTl9gdayFcXFeY2tZaGRiVVhualJhYGZcUk5ZYl9hV1VngYGPhJVxVGxVUnN6ZFRnhHl3dXWWcYJWYGV+i5aLgWxkWIB4cHmGeJKallyBZYSXe3x+aIqQfFdUkn92hY+Uh4yUnZ+mbnh1YW+HkVmGjoWVlJiWjKWLmYGll19yooOQh3RkhE1emIVqanGgmnh9eqeiiXSMpY+KjXaLcXSCiZx2bY2NdpSqlF+Ne4+Hg1N7ZXSKgGVVVJB3X4R/g29ogpNwbIBqTW1xY0pIVF9UbIF8imp0d29tdomQf4SYdmRsdG9ucW5pamxsY1ZYY2SGcE5zc3NvY1xVXHpnWnFkTmV2QFVqRWBmcXFpbHhkY2hnYGFydnCUe4yKanRSYXN3bGRYd3BrYm9sbH55Xnx1Z3aCa3FZU2liS1teVW5+gXF2c3d0hoCReXB7b3ZtaoJ3fnlyeF9dfW19h2RQVl5nb2NhZYmDbGlfbm5vaGJ+bXJ2U1+ChXqRg3N8clxnWGBfXF9aZm9ncHF6bmdVXGNoX2NuWFhjbGNoamZreGNxaV9qZoN3ZF1tgXtmXHNlTGhyZ3hne2Z1d2hxdnJmY2BjcmtSZ4V7XoBgal5jbU5ofnNgY2lsZGJgblhiZHBtWWJlenSXbmdnc3SBYExla1RXX2p1flpUVVtgeWaGYm5qZ1NHY3CGlmZuW1xcW1lLXGpza3aDh313bGZfaGtwdG+VgVhebX5vknRrX3V0bWpqbmtfXmRsaWOJfG14bGZ2d4SGgW5rZllWWYBgXV9ygnljXW1cVlZcXVtlaGdmal1iXmdlZmpgbmF7b1RmcJVacYVnWl5iZ1ppd2tocWZ7bW90am5nXmR/bWFkYF5fW2VyaI11e3hfaXWAdXiFeHNteHF8dHd2cnSCfXRzdWdsdXdpcl5eX1xaWVphYVpwcWJkanKJbn5tfYFvb4B1cHNvdnNgd21raW5sa2lvbXx0eXJ8e3FxbmNfY2lwgHBxeHqBcG9wc3BqfYN8l217gH5yenqAgGZXbHFpbndzeH9vbnBqeXNyhHqAfIZ8cYOEhmlpdHVraHZ3e2p0gHtxcXaFZ215cGNZbHlpcXFvbHJyUWKYfZJ0Ym15Y2WSf4B1fXmBbnNwaWFpdGVgjH52eG14bWRMRm9ufHRjZmtsYnKKfXmIhElfenlqVmNrb3JeV2VxeFtcV2hyaGNqYmd0cGljYmZihHl5e26SjYR3e3NkZ2Via1tWbWVvc1JkV2qmhWlhbF1WXG+Ajm1odW14cnJQW1lgXFpQWFVpcGNcXYBMSlZaZ21le3tvcHNlVWROVFJXYWVmaIiGa2l4XUxha2BiT3WRhGRielBqfm53aJGWZWJudG5tbmtwcG5VUWNkeKuHdHVjZm5ebmxsYF1zcVdlYWhfV1ZibGtnW1hjd3aBcX9dQ1hJRmt0YlZth3x3dXOMZ3ZLVVlygpGHfmlhUIBsYWZuX3p+e09rVXODaWlrUnF1YEhFdGVcaHJ3aW5zfX6IWWdlVmiDilZ9gHKCdnduXndcZE5sYURQb15sal5UdEhXgnJYU1V2c1hnZIaCcF95k4F5fWyAbW96fZBqXXFrVm54ZUZmW2plZEBfTltwaVZHRnhhTWtobFhQZ3VTTAJ6e7t6BHt7enuyegF7/3r/ev96/3q2egF7/3oEenp6e7x6gnuNegF7knoBe4t6gnuTegF7jnqCe6t6AXuFegF7hXqDe4x6AgIEAIDLzei+0OHZzsSA6+X4gIT83cK2wNPHvrq/x7SmrZ6opZ6kqba7oqa1xaHIz6+srbqyqq+1vp7RpZyWmKjE0L66sL2xivHU3uay6tWiq66ow8Tj5+XxhNO6n6Dfy6mio52OzNHA2MnHxby5w8WpqqGcnqKBvt27s7/BtcDGpb+7mYCzrsr949TQy7WmobPAzM/Eyc3S1Muso7yzqdjLp620t7PGztLHwrrwyrqqj4aZo6Wylo+UmI6Km5Koq6qon7rC7tnfvdDPxZ2+t6qosc64vrGruaqhtKWmmqOmmrLk7L2ztb69mbmIvrTHxc3kvdf8x7jDybuvprCena62j5qcn4CbqpWbqZq4vPTTwL+uoJzH4pyNjJS5xcbAza3HvLa+u9TQmq25qpmTobrSytTazZzM0oXZw7u/0uSvv7e+xpSZpqqVopudqbG3sLSuy82npbObst/BtLC46ubazb7HzM2hnrWvuLe1tLitnZOmk5aiqqann6a9uKe7opWZpJOH/oCylI2MipuNkZeeoKy1qZydoJugqbLGtaq2vczUzMrT5e/d7PizuK+ooJ2+uKS8wr2nnLC/oby8tbCrqsD/paO5sq7Mzs3Dxq3F3d+7sbWu07i4uMqqva2ttsq8y7y5vb2+wbm4v7ehpaShnKKim6CvprO+08zG3be8ubSx0LSstICwrsGwrbqztamssrWwrsCsvribmK7BtcfNxLafpq67ss7AvbutsK6xu8Suu7u9sqmyuMDKzbrP3uTItrO4rarBsNfMzszIuMHTybfS793TzOPa78XG0sHKydPFxMjJ29fCwMG1qp+Vo7zApq6rrcTNxrifmLGooam81NHGx9b22IDKwbK7vrPZxMW7yt/QzbuzxcuvvcS4u8Kxur++xsTKxMbZ3uni6ePl8dXhwb/Q7Me2usfl8dnN39ib3qu6srixwcGtqry2triypqi8t7jBrqOglpafl5KctbKrnp2Ej525zaien4qfqamosKWlpKOinZusvLipvdPFzeaA8NvXyYCqsaPaubi1q8zJxq+lv6acmqCrtaSjrLO1prauvrGdpczBw8Gv27rPx47Bx+rmyLWwiabB2cKvqL65u7Gjop6ciJumpb/fw66ipbWxu8nEwaOfq7iypKato7WwqaC/xNLNz++1scXY5vH7+cbG0cTSycTTydLrydTS7fflz/y3y4DI0OLe/urh09Xg0JLVxbjaucuKhebmmOjOz9HAvNHV4/PHyNi2uuWvvd/G3O7L8ubi7s/e1cHQ1tH89euG5N/Qzpu2x6CazrTi1djUtp/L0sLKv7PJwb7i18m92s/P1tzTzc7u9IeE29TT1dXc/oSQ5oyHi/HvqO7Br8DD4PfBz4CFi5mJoaygkohZl4qVS02Oc11YX3Fua2l0eW9qc2VvbGRmanN3XFpiclB4gmZjZnJpX2Foc1iDXFZXWmN6h3lyZnFeW5V7hY5dk35SWV5ddXF5gH6KUXJiTVWKel9cW1REeoBsgHBub2hmbnViZF9aYGNVgIZlW2JdVWBmS2BdSYBjYXqjkYF5eWhdXGt4goZ+gIGDgHVYTWBbU31qUFJYWVRka2tiXWGOc2FXRD5QVllsWFNaW1BNW1FbW1dVTmVvkoODa4eFfVRtaFxZYXhnamBcZWBfb2RiU11gV2eSlm5iY2plSmJTbWd1dHyQaX2gbltlZ2BZT1xRU2dsR1BVWIBUYUlSXU5jaZl8amRWTEdngUxBQ0pnd3p5gmR/dW90b4SEVGRtYVdQW3WNen6FeGx3d1V9aWRmd4hgbmFudk1RWFpNV1RWam97bnB0iI5qaHdba5NtYVpfiYd+eGhsc3RTVmljaWxqbW1iU0tbTU1XXltbU1lmYlhrWEtQW05FfIByVFRTVF9PU1RbYGt0a1pfXVhbXl5oYF5pa3FwbW5yfYd1h5lgbmhlX16DgHCGjIRvZXWCa4F8dXFoZ3arX1xuZ2J8fHpzdmJ8kZx6dXp0kHx5d4BpdmlqbX9zenBtb3FtbWNlbGZWWVxeXWNcW19qYW1xg3x8jmlwbGlgemBaXYBfX3JjX2xscmhoampmYHRkdHRbV2h3bXx+d25gaXqEeY18b2tiZmZncHNgc3BtX1RdYV1kaFZpd3xsZmBnXFprXHRsbGtjVV5vbmJ3k4d6dod9k2tpdGVtbnhrb3Z6iIl2d3dvZV9WXnJ0XGVkYHN8dGtTTmlfWWB1jIx9eoidf4B5cmRoZmB7Z2lgandqaF1ba3Zda25lanBob3FzfXl6bW57eYR8goCEkX6OeneGoIJwcXyTm4B3gX5jhV5vbnJufH9qZnJ0cHJsYWN2cnF6aV1cUFNeWlVie4ODeHNaYW2BjGZcXUxcW2BfZ2VnZ21wa2Rwd3BVXGxhbIlJh3p8c4BcZV+YeHlzaXl1b1pWaVlWVlhkcGVlaGhqV19WYVtLU3p6ioh8poedkFp+gJyWdGdiRFd3iHNfW2ttaWFZYWBhUGFraHmRdWFcW2JaZXR1cFdYY21jXWJnWmJeX1Zqc3t1dItdWWh4hImSil5kbl9sY2Z7dX2Tcnx5jJKDbptld4B3goyJnpCLfnuHeV18bF9+Xm1UTYSGYpB/gYJ/g5aYo7CRlZ2Bg6Fwdo94ipt4o5SVooyfmYWYmIuxoJJUg4B3dVRyg2ZkiHabkI2Ld2GKjoKNf3KDdXCLg3NpfXNxgo2HgoWgqmNdi4aEhoaLqVRbhFRQUYiHVoxxX25xjKV7iIBobnNld4N4bGNHe3aERUmHc2BbZXh1cG51emxma2BoZmFkaHNzXV9qclR3gGVhZnJtZ2lteFuCXFRQUlZpc2VfV19PTH9seHlbjH9aX2Vjd3WFgXyFT3BgS1OLfGFgYVlJfoV5jXx6e3Z0en1sbWRdXGBIaIJrZW9uanZ9YndxWYBuZ36kjX91cGBTT11rdXZsbXN6eXNdVWlkXYR2X19kZF9ucnNrZWeMdGlgUU5ianB/a2lwcmpkcGFmZV5aUWVujn5+Zn5+d1RvbWFeZHtpcGVjb2djc2hrYWlsX3KZn3pxcHhuU2ZOZ15oZWt9WnGPbmBtdGxoYW5jZ3qBW2Noa4BjbFtZZVRka5eBcGpiXVl9lWFYWWGBiomDiWx+c21want4UGBoWU1JVGuAcHNzZFhmaUlxYF5gcoFdbmd1fVFYY2deZ2NpeHp+cHNxhohkYWtSX4VoX1pdgoZ8dmpud3dTVGdja29tc3VsYFptX2BtcWxtZWt6dWl7Z1pfal1WnIB8YWJgYW5dYWRoa3WAcmNmaGRjZGZuZGBoZWlnYFxcbXZrgZVgbWVfU1FvcGF2e3dkXGlzYHh3cWpiX2+hWVlqZWB2dHFoa1duhItsZ2lmgnBvbHllcmZocIV4gXVxcnRub2dobWRSV1laWV9aVlpkWWNre3l3imt4eXt0j3VtbYBsanhpZXFxd2xtbm9saoB0hoVtaHeGe4V+b2BRV2RvZ4BzbnBpbmtvdnlpfXt3aF5maGVsbl1wfoJ2b2pya2x/cIqBgoB6aG18eW1/lYh6dIJ3jWlpd2dxc31vcXR3goR0dXlzamhlcYeLc3hxb4GIgHRaVGpfWV9xgXtsbHyUd4BzdGpwdW6OenhtdYF0cmdmcn1ha3FoZmxfY2VlbW5wZ2h0c312fnp5h3KCaWl4lnZkY2+EiXJncXBTckxcXGJfbnViYG5tbnRxZGZ9e36EdGlnWltgWVRdbnFrYV1JVGN7i2piZFhmaWlmcG1qa29ua19pcGxSXWhea4ZHhXd5cIBcY1qQdHRvaH16cltSZFRPTU5baWBfZGhrXGNbYVxLUHNvc3Bmi26BeU9ze5WUeWxqTWF9jntpY3N1dW5ma2dnVWVtan2Td2ViY2xka3Z6eGBfaXJqYWVqX2xoaV1rcHZtaHVJR1ZmcHqHgmNqc2ZvZWRyanOJbHFsgIl7aJFaaYBob3hyh3lwYl9qYU5mWU5qS1ZEO2RoS3JjZWllZXp2f4Zqb3djaYljbYhve4JjhHNudmFyZ1VkZV59cms/ZmdjZk1icFZTbVp9bWxwYk93fXB4cml8cW6Ffm5leW1mc3duZV9xeEtGZWJkaWpxhkNJZUJBQW5rQW9aTVteeI9jbol6Bnt6enp7e7R6AXuRegF7nHqCe/J6AXvIegR7enp7zHoBef96/3q7egF7wXoBe616AXvhegF7hnoFe3t6enulegF7qHqCe4d6Bnt7ent7e4x6AgIEAIDCwMPK8ILt3+/a5N7fht/x1se9uMTLv8HLv6uomJuapLeotLW5nrChqpuhopuRtK3E3NjAorbPs7OlmaSw66C4wdbO2cjX54vU09jlzr27u7W2z9zf/N2ztIDnyqKqrbWitsS6vsjNtMewx8Llw6WmrsGSjdTpv5SenY2PkqqqoYCKpbi5w7fCspiup5etrL3I0czW7IH1wNLuyN+P4OjRrrjFt7y+xtO/r5ObmpaHnq6snqukm6GZnqSYp6SWo6O5va2iqLXDiNTOxam5tbi+wMi7uLuvpJidnqaiuZq/wL61s66XiKvL5q6w17CAvcKowt/Kx7mYiKSkqJ2trbSmnICiqaawrJKjlIGsrq6rrqGaoJqMn7LD366xmqKzqqW6w8O2oqm9sbCeq66spK7Bsu3Z/+rp2qWomvj0y6emlpOfoMCglp6WmaGkoav9zpuxsKSkramitLnEvb/T/YW7uLeyoZ6wuayqqaqimq+lpIqVl5iiq6SlrbOtsqOuzLyyoICqop+YsqubjaPXgL2cp6ern5mcl6ixtMbS2Pzgzt2+0eTp7ezMr5egiqSeuNHj7LSnuZmyv7re3MD4vsa4y72u1LCnsbWtsL7b0r+ntauTqq2dnbW1vbKzv8GzuLy+vNTKvra4u6youri/sL2kqKqwoq61yMnesb3KtaObnqiOn4CZpaSbuLa1x82zrautsse2u7ilsa/F9L63lqanjrS9qMyvw8iktKmdpa+3rMrQvau2q8C5vMbQ+8nLtbm4rLbEs6+8sravsrO8s77Fvb3Iu7nO6JDHtMC4tL7EucrDysvGw7+1mI+jiK2GmqWxu7qxxKmmtbWjna3A4+HY1MS8u4DNubGnk6W/u8C6yOXuwLqsyMirtLOrr7a5rcbW+MrR3s61vbfQ0ODo4N/Kuby00su8r8/Ox9LN+uC6rJywqKutpq6qprWvqautxMO8s8C0m6Wbm5yfmZqkvYCNjp6Ump2mrK6noJWlt7yrmpihrcK+qbLSzbe7pMy+2eb23dPGsICtrLjJrcKyqbiso8q7zcLP3eTn1cjHwcjCtKq6o5qsvavEyOKAu8DIscDAk+24nrW4tLfi1ui8vtjCrraWmaKWkp2emayyuqWfrLq4usnh3Mm6t7i4ssjJu724v8fB1ePDusfI2NDp6uLn8Nvq8NS8uMWA1vfrzL/D5d3gyND88oDdyc/Pxd3O5sia677U4fLe3+TugrnP88r5iI315ZaQ7Mjg1dK2r7Ob2dzG2NrMgYPl49zfhvf38uuEnO3UwcXH37Wpq6CTmr/A28X96NzYz7jN0sa1sLrd3tjMxsDt4uLM19L145D65erj2djm7YSLppKGi5/u1LympK6tsci7yYCChIyRw2qzo66Wk4aFU3qMcmZfWWNsZGp2cGZpXF5eZXRkamxrUWFWX1VdYlxQaWF4iYhzXXGIbm9kW2Funltsbnxyf3iDmF6AhIiVe2tlY1xacXh7kXBVXlSXelpiZWRUZGxiYmtwXGpbbXGNcFZWX3ReXICOZkZPUEFCRlpeVYBCWHJweGdzZlNoYlZqaneAhoKIklOTX2+GZnZSeX9rUVprYWVmbX1tXk5TV1BCV19fVl9XUVhPUFJGU1JKUlJvd2dhZnF4WIB0a1dgXmJobHJrcHlwZVhUVl1ZbVFqamZeXFhGPFp5jF5jgmRSb3Bfc4lxb2RKOlBRVU5dWFxUToBVXFtjYUtXSVJdW1pVWU5ITEk8UWFqfGFoVlhnY15veHlwXmJwZmxaY2hlYGFmXY6AnYuEe1RVTqeniWNhVlFkYHtfVVpUVGBpZG7Ik2BybmBdZGFcbHJ0bmx6oVJgY2ZgVlNkaGRmamdhVmZjX0hRVFZeX1lWW2BZXFBYcGhhVoBgXl9ZbWRUTF6BVXheZmNoYVhaVFtdYG5yepmGeoJlcoOEiYNlVkZTSF9dcYeWn3dtemJ1gXmVkXWjc39zgnVjimdhbG9na3qPiXdld3BecHRoZnV3em1sc3Rna2poaYJ2aGBkal9dbW11a3VdX2JnXWRrfoCYbHqIeGhgX2lSX4BbZWRddm5qfoRwZmNpbX9ycnBiZmV2m3Z4Y3NwX32Ca4lxf35ZZWFaYWpxa4eCblpjWmRcYmx4m2xzaGlhWmh1Z2BoXWRZWV1kYXF2cXJ1bGp2kVptXmlhXmdvbHd1eH58eHFwWVhnUm9PXml1eHNufGNhcXVnYG18mZSLgHd0cYCDbmZdTVtpYWNgZoCGZmRZbGtYZGNdYmlxZXyNqH6AinZkaGF1doeSio9+cnNriYB1ZYF/eoSCqpN3bmJ2dHR0bXJtanRvaWdqfX58cnlzXmdiWFxdX11ohFRja3VmZWtvcWxhWU1UXWVgU1VgZ398amuJfWltU2tdcXiFeHRrYoBkYWp6a39vY2teVXFib2l8ipKWg314a3JtXVFbSUdYZFh0gJlXd3+Jc3l6XKFzU2VlX2CHeY1jaHhvYWlPWGVXVFtdWWlnb1lWXWJjaHR/fHBrbG1rZ3l5b21ocG1oeIVrY2tvenqRkYmJjXeBgG5aWmlSfJ6VfHN0ioCDdX6onICPhouNgpKElnhck2+Ah5mEhoqWUV9wjHCaVluaj2Vem36Vj4h0a2hSg4Ruf39zUlWQlo+TYKqnn5VWao16bHF0h2ZfZWJbXnl6kH+4npOQiXOCi4BsaG+IhXl0b2aShoh4iZCklWCch5KJgoKTnVZbbFxOUV2CeWhcW2FlbYJ5ioBnZ2pwlVKNf4p4fHR3TXWIc2llY2tza214cGNlWl5faXptdXd2XW1hZ1lfYlhNZmBygIBuXHCHbWxiWlxnklBdYWxncmp1iEp8f4iXgG5pZ2Fhc3l3iGtPVkyNe2Fqbm9gcHhxbnZ3ZndsfH6afmljZ3VLVIOKYFJbYFZZXG5wZoBRZHd2e2pvZFBiW01hYWxwc296iE+PZ3mRcX9Vio53W2BtYGVpcYFyY1VdYl9VbHh3bnpxaW1kY2JUX1xRV1Vrc2ZgYm1zUHF3cmBramxyd350dntxZ1xdX2RfcFlydHRtamZTR2R5iVtZc1hJZGldeJF8fXReUWlqbmd2cnJrY4BmbGZpalBYTE9iY2JgZ11aXltRZnVJhnV5ZGVxaWJwdnNnWFtoXV5RXGBfWl9lV4JthXl4bU9STJeXhGlsYFpvbIxuYmpnaG5uZGurh1doaWBeZWRfcXJza2l1mE5dYWZgV1ZmbGdoa2tjW3BraVNfYWJqb2hlaG1maV1jfHVuYoBqaW1ofXRkWmuQWH1jamZra2JkXmZkYmZna4JzZm5SYHF3fX5rYVJdUGNaY3WChmVda1FjcGuDfmORZHNnd2pbhGJZYmRbXm+GfWpZamBNYWhdW25xeGtweXptc3RycYd7bmhsb15cbWtxZ29YWlpcUFhgdHqVbn6ThXhxdHtja4BlbGhddm5sgYdxaGdtdYl9gX9vcW6AoXp1WGNfSWNtXHtofIRlcWlgZWx0b4WFcF9oXWdhY2x1kGx2a25raHaId3B7cXZraGlvand8cHByaWNwiFNqX2ZjYWtxbXd0dXh3dHFwW15xXn1cbXiBgn51gGhldHRlWmV1joh+eHFtboB+cWtiVmV1bW1pbYOKaGddcnRhbWxiYmZlWGl2jWpseWhYX1ttb4CHgYNxZmlifnZsXHVybHZwmIFlWU5gYWNlYWhlYW5lYmZoe3x6c395Z3JsYmJgXFpjfEdTVWBXW2RudHRqYVdgbHRrW11jaHt4aW2HemtuVWpecnqLgHtxZIBlYmh8a31uY2thWG9eZ2Bsd3yAcm1uZnBuZFhkT05bZFdscYdKZWt1Y290W5t3W25zbm6ShZZzcX13a3VfZXBiXWRmYG9vdF5ZX2ZobXmHhHlycnJvanp5cG9qcG1kd4FnWl5gbW2Cf3uBh3R7fW5bV2FFbouAaFted29yZW2Wj4B/cXFvZHdldFhEdlVlbHppaml3PUJUblN1Q0l1aUxFcFpvbWpaWVdHdnplcG1iRUZtbWZnRXRxa2U+UGpgVF1kd1tWXF5WUGFgdmqYin94dmZ3gndpY2eAfHJuamCFeHdka2l/cEp3Z25sZGRzdUJIV0tBRFJuZFZQU1dZXnJkcIV6AXuHegF7uXoBe5F6AXuYeoJ7oHoBe4Z6AXuqegF7pXoBe5t6AXuMegF7xnoBe616AXv/esl6AXv/etF6AXuGegF7xnoBe5Z6AXuJegF7hXoGe3t6ent7j3qCe4R6AXuEeoJ7qnoBe4h6h3uLegICBACAy76449nO6uzZ54OP9fTp9NrT28y9xbCrtaydoZ2Xkp68xqO6ta2grMGSmpyfkO/x2c2up5Cm0tGTnJLMhda1q6Sswb7QzOnY0srG3t3Py+m8zte+qrHe4fnnhLWipLSumqKotb/Pt7q1s9ezxcmlmJq3kIa6v7K1pJycoJ2Um6iAqZiRza64maiim6Cursq3qeTKxNLchYLp2tPIvemK58a+wausuKCmtsSstauymMXCqaipnbCpute3tqyfpZiVmqGlnZ6vrNLLtKu4s9LDuMC7xbmanZ67osumssy+qLqyvsewnrOqsc7Dwcuyvf//5ebjweCplJGvlJyVpbCloaGArqqxyZywuJu8vry5q6qmoMbIoJ6upZ2ivKeexrOwqM3AtM7UtMDb3MDKy+TFuLmt2Mqr1qyYlIqkpJySjpKUmaOto52bnJ2bupew5L7IzPaNoZTIr8HQzLqxzsXJxr62rKumqai0p62xsLi2uLibqLPIq7Sjo6agk5ShlqaorquAsbisr8zTxIijubfWzbO2sai3qaqprMOysM398dfQzPfd1M7R0b7Cwa60kbSu2d3C1radjpmWqZ+yt8bH5tm5m6u1rb3v2d2A8ce6qbK1nZicp5istLawstfW08m4sLrLx8PIu8O6u9K6y7/AubnQt7zEvsy1uOHO1bamjJOhoK2AsKm0pK66tbrFp7ywvcPDtLastaimr7zIy7nMram9ubCpnKKzpaWprLrEzsbO3NC/urKruLvb/PbUycu7r9a8vc6/rrSsvLXSybS5vKKSx8XCztLs1LPDvsaus7jHv6fAwLu4xbm6qpCXkpOgtaO6vbSuqbGpnaaqsKy6tbq4ycOAv8rFwLuvsL3Dv8PCubq3x7y5u8GsrsKlqK+1xdbfw9S7ub3JrsnW4MXX8s6xzLGgq6rCqbGzwejW2ui/yL7C1rWinpirrKGwrbqxr6Wtpaa0qaShlJWwsJWqn5WSo5WZmKS6uKCOoLzb0rOyrbinjo6Los6ysKLY4tvTycO6sseAzs7Hz8fUypqgpKG3xdTEzcjJwb7O5NnexKu8srKtrsjL74C8zsHHw8TQy7a8s6qnsqe1u8nKxq+jkZ2NqpqhnpWQm5ihqa/CsbzKwr/f68u/rrOwq66rosjNw8PAytfKyNfY6d/544fQzcTDvcW3tdPv2s3E8MvX5dHk3vbrwNWAy8mwrtrRiPWA7tfck4L19+SE59TEhYLq3tbewtuOt5nP0L3Ztay9x82+ivHNxezk2+Xh3+Xa2NTL3oXsvr3N7/LUwKTq3dS4p77S6+PMt8vEr7nD18O9yMzO287K19DG1s7xiOWK39/a5c/KyeqF9JiA/Pb/r9vArK6Yl5mqpbaAi399oJmJmpeLkVJbjIeDh21ueW5pcWNodGthZGNaVFVxd1lraGdgbH5RXV5iT6uulINkXE9ijIhQWFN4T3pfWFZccnGHgZ2Jg3x4hoB0cIhhc31uX2OIkKSZW25iZHFmVVtdZmp4ZmleYX1kc3NXT1BpWVRvcmlnWVRTV1dNVF+AXVFKfmBpTF9ZV19tbod1ZpZ9cnJ2S0V5cW9nY5FbhW9nbFdaYFFTYnBbXlpdSXJvWFdWT1tWYnheXVNMVE5NV1hYVVNcW3VtWlFjXnZoXm1rgXtaYmF7XYNbYXFsVmZbZmtZSVxZYHNxb3hnbKShkY+Pb4tdSUVgSk9NW2NaV1eAaWJmflNmaUxobGhjUVNSTmptS05YTkVKXlFFaVxWVHFmYX+Ca3mSlXd+f5p6bWZdfXNdf2NSTUdeXlRQTlhZWl9qXVhaXV1igVhokXWDfppaZF52ZXZ/em9pgn55dG5oXF9XWV5uYWpsampnaGpQV2B3YWhaW2RfVVVeU15eZWGAY2llYXd4bDpTZWqHgGpwamJrYFxdXXdpZnuil3tvcZyCenJzdmNnZlliSm1qjZF+jXZmXWphbmJvb3Z0j4NoUWNtaniumZ1is4aCeYCEbGdteGdxdHBraoWIhXpsZW18d25yZW9mZXhldWhmX15wXmJmYG1baJB/iXBjTk9bW2aAaWJrXmlycHOCZ3hncnVza25lbmFnbHSAgHWGanOFhHBoW2JsXF5eX2VvdXBueHNlZV1PXFx5m4ptbG9hXXZnandpYWFdaWR/eWdwdV1Sd3RweHiMeF9oZGZVXGNuZlhwbmtqend9c19pY2JqeWl8fnRsaG9rXWRna2h2dHFneXiAdHlubWtgW2hyb3FnXWFkdm1tdHhnanxjamtyfpGZgI5zcnF5YHSAjXOKqYRtiHVlcm6GcXp6hKSYn66FjISFl3ZmYV9rb2V0c352dmlvYGBqZFpYTFJvclNrY2BebVxgXWJobVpKWGqDfGZoZ3NlU1VVZ41rYVF6f3x3c3FtaH+AhoR9hICNhVVZWlVlbnVsdXN3cnSFmIiNdFlkVVRTVmlznVlwgHJ7f4GHhnN8c2FbY1toanN0c2NbS1dNYltmYlhRWldcXmBrZGl0b2qEiW5qXWFdXWRlWXd5cHBncXttZ3B1iIGXhlN6dGpubG1hXHmUhHtynXqEln2MipqTcoSAgYZybZaMXKJNjHp7V0uFintKfWthUlCJi4CLepRlgGeIjnyPbmhzeHN7UYtsZ4mGeomKjJGJioJ3iFaSbGx6kZR8a1iTlZN5bH6Jn5qJdIB3XmZrfnFufoJ9inp0enxzhHmaV4Vdj4uIinpzcpBTkl9RmZKbaYVuYWRSU1hnanmAe29nh31sfHtvdkRQgIB/hnNzfnJrcmRkb2ZdYGBYVl16gmN1cWxibHtSV1daR42Xg3pjXlNli4lSWFFwSHZcVE9XaWh5dpGBfXZzhIV5eIhkcnhjVVl5fI6KUWZgZHVuYGlve3+HdHRtb4lxgHxoXl1zVU98eXFxZmJlaWlfZnGAbFxWiW1yVWJcV1tnaoBvXYd1cnZ/T0uJd3FpZ4xSfW1pblldalthbnhpbGhvW4SDbWtqYm1ndYlubV9ZXlZTXF9eWVZeXHZ0Zl9yboh2a3ZzgndaYGJ2YIBiaHh2Z3NqcnhoVWllan91cHJhZZeViYyPdZBnWFdwW2JfbHBjYmCAbmdqe1hnalJtcHJsXmNiXXp6XWFwZl9leWpcf29kYHpqYHZ2X2uDi291eIx2aWNYd2xXdl1QUU1hY19YWF5haXF9c2ptcGxrgmBxknN4colTVVVsXXF3dGhieHRzb2pmWl5XWl9wYmxwb3JvcXJZYGl9Zm5iYGViW11oXmhob2yAa3FvbYKHekticnWRjHR0bWdxZmVkZnxpX22JeF5VV35ub2xzem50cGRnT2Zig4JteGFRSlVPWk5dYGlqh39kUGJqYW6eholVlm9rYmpqVlRcZlhla2lkZ4eHgnprZGx4cmxyZ3BkY3hlc2hmYGFzXmJoY3JhbZmLnoZ4Ymdxb3WAcmpuX2ZvbXJ/ZHNlcXl7cnZveG1tb3iBeml3XGFzcWVkXmd1aWhmZGpxe3NxfXVpZVpOWlhzkYVrbnRraId5fIt6b3JteHGLhXF5fGNTeXdwd3eLe2JrZ2tcYmZxaFVsa2hreHeAfWpya2x3hnGFhXtxaW1oW2JlaWRua2tkdXGAcXlubm5lZHF4cW5kWVxebmRmbHFiYnRbXl1fanuBanlhYGRuV257hnCBn3plf21bZ2R7ZGhncJCEiJJvdnB4iG5hXFhnZV1saHJtbGNrYmRvaWBdTlBpa1FkW1lYZ1teX2RtblpNXXGGe2Zra3NnVFRVYoVsZ1mAg397dHZwaXyAhIZ8hoKPiFlbXFdjanBkaWVmYWJyhnh+blplWllYW25ylU9lcmRrbnF7fnB8dmhlb2h2eIGEgm5jUl1VbWZual9ZY2Blamp1anB4cW2HjnZzaWtmZGtoXHt5b2tgbHdpYmhsf3mPgU95d25zbW9jW3KEbmVbgWFmdmJwcoJ3XG8xbm5aUnVsSn86aV9kST9wc2Q7Y09HPz5oZl5mVWpLTkxgZVxtU1FXX2JpR3pbV3VvYYRrS2FhWlNfQHFTVmV8hG9eTXt4cl1RaHmNhXxseHJcYWl7bWZydnR9bmVqZFxsZX9KbktsZ2ZvZF9bcURzST1wbntXcmJbYFFTVmNhbYp6gnuwegF7nXoBe5d6gnuheoJ7hnoBe/96s3qDe/R6AXv/ev963HoBe8R6AXueehF7ent6enp7e3p6ent6enp7e4Z6g3uJeoJ7j3oBe6h6A3t6e4h6CHt6e3t6enp7inoCAgQAgM+7urvnzMTKve6/jevr0tnH1uDHy866q6a2uZ6alYuGnrTQo6akobXIucG1sPmI87OilKWVnqCdnJa1vMKsvanKzdHRv7TLxdXRv7zSxdjz47G2xsi2vrSstrCwuKGqoJ6QobLNvrOatcS2nq2ulpmhrtXSqc3ipr3Etq7DvKKbgJ2cnrTCwvrKpZmfqqWjpqK6tMPDy9LkydjKo9n+07evwaqUktC2tbaxsK2soKaqyb6yuL2wo87Iv7CsrKaUn6Shsa21vaSUoKiywNG8rqTE0LWsqq+UnaS0qMGerLjKsc28pbPDtcnUusnKtLStu7Owr7/DsKeRoMestLK2tsbfgOPMv7WYqKe6oL7glLKvsLu94auvt6m5uqmoqa+oqbWywdHY1o3f8L3ewcO5s7XMvLuws9O7uKWgi4qRk42WnZ+emK+trZ2mnaGhtb2618fF3t69oaS+t6u5tcS/08+7sKKZp5GapKugsseWldaDoa/F4b2woKOnr6OrqcHOrauzP//l9e7rw6GQmLGzoKSzr7nj1Mq7vqy4srXG98iw1NPy49zqxtC+vry2prTX5oG3wLeenqCSmK24wcnT4fTbroSogLL9r67PxLOtl6KgnZOkmJabr6uy1tnRsr61qtHZyMbBucrgysXA08ezwMfGy8TCztLHs7GdnJuclK2wwra2uL+0ucitttOir6q2ury1wrbBubHO6NnDv77H0M/Ixquxt6yzoaiztrS/r8TFs9i8u76zyvLI3dPV1c6/yLK0mbCvgMPHysS+3J+en5KIiJSlrri4u6zBt7Kpn6azpMjJtZyLmKmmoKCmrrKqlam0op2kraSnlKGyqL3Au7auu8Gmq7u/r8nE+uvOx8TAusvGxbvKzLe4qJ2mubiwtLCpsq+uprWjp7Gwm7K2vcfApam4rriyra+2xL23y9Ownqm6uqujgKahubyhqKahm7KXp62hp7Gtq6rDwK+ntaKil5uZjouUpqS75q+on5ibo6a0n4ymuMO6vryiuNPpxbWwqr7AwtTFrrjXzrG2ssvLz8fU1bzE25n4x8vS0s/Fvqes0NHX4bW8u6a0ubXfqKqUr7K4qZ2wra+uqJ2Xp6eemLios5SZgJekp6app8S8t87Z1caysamrwc3Dv8CyqbTD09vH3sjBus/Nzc/2xMjN0M3EzsnK0cLG6q273fjjyNLVytXJysHfj4WG5Orc4ufg0OHUy++AvdTf1t/J0cDMw9Lj2efB3fK9tMKvgf3ni9LU8+PNz8jH5c+7y4fFytXDyL7AxuL6PcfI3sGz2azA97qzua2Yj6bFs7Cz5NvT8N3GvtHT/O/r5+uL/ev179r33ufHzqXL4tyEuvb1v8S2oKGyr72AjXl8gaiHdnhvln1Tg310dV9zgWx6emtkX3B3X1xWTEZYaoZbXVxcboF1fXZzi1CocmNVY1VfX1lTTGtua2FqW3h7e31uaX51fHptanpzi6ibcG57eml6e25ybW94ZGtiX1RfaIJzaVJodWxbaGVPT1VffnpZd4JXaXBkYHRsVU6AUlNUZnN0p4JfWFpnYl9eWmtocGdnbntncGdNept/ZmBwXkhEe2NlaF9hXFpKSk5rYllaYVdPdXFoXVxeVktSUE1YU1phVUpSWVZfbWFdVXB/bXBud1dXVVxTZ0tbYHJfcGVSWmZhcndkcXFkYVplYVtfaGxcU0NScF5mZmlqdouAlIJ1a09eWGhNaYZXU1BXZGGBUFZeT1lZT1NSVVNXZWZyh5KIVYebb4pydnBsZ3xwbmZifnBuXF1MT1RWUFdXVVVRZ2ZnXGZkY2F0e3yahoOWmHteXm5oYmtndnSFgHNqX1pjVFtkamRzgVJUhlRZYG+LamBUVFpkW2RhcnxdXWSApoiSj5BrS0FLYGRXXWxob5OGeWhqX2lhZHOlemODgZyMhpJueWRhY1xRX4GPWHF7eWVkaGFidHuBh46Yp4xhWldbX2msaWuOh3p6anV5c2p8b2dod3FyjYt+ZW5lXHuFd3JqZHSHdG9odm1bZGdqbGxrdHRvZGVXV1haVWFjcGKAbW9wbW56ZGuIXmxocXJva3lrdWlmfZODc3VzeoKFg4BnbGtfYlRZX2BlbmBsaldxXlxcVmiNanRseHh1bXJoZ1Zoanl8f3x7mmVob2dbVFxobHFub2h0a2FbVmBnXYKNeWJTYnRzZ2RncXhvW3R8bmhpdGZrX214b3h4c2xha26AWF5lal1sZJCFbWxmZmdxaWxnd3pqbmdiboGBfX55cnd0bmZyY2dwb1xwd3d+dV5ecWJramhqcX16e4+SeGx2goN1am1meHliaWRiYXJWX2BXXl5XV1pualhYZ2VkXWBiXltiaWR0l2JaUk5UY2x8aVludn1ubWlTZYSjf3FwaX+AgIeeknl9kHxmaWJ1c3p8iY10fI5voXBzd3FwZ1xPV3N7hZJtcW1dam9qjl1fT15fYllTYFtfYF1VUmFoXFp7cHdaXV1nYl9eWGthY3eBdm1bXVZZbn51b2pkYGdvfX1thWppZHpzdHmbbXR2dnBpbm1wdmhsjnlsg5qHbnx+fYSAfH96kWpXW5CRgIaIg3WCenORTmh8jYeOgYd9i4SMmpGZeYmbbWVrWkyZilN1epWLeHp3dZWBcIBdeX2Ed3ptcHeKl290j3JulGuFun53eGdVTV53Z2NfhH19opB/eIOCo5mcmZtgqZSWjHmahpSAg2CBlY9bc6ysfYBzYWNwcHyAfGlmZoVsXl9VeWJKeHdxdmh4hXN+f3BlYXN7ZGJcVFFjdYxmamdmdIV3d3Vthk6PcWVYZlhgYVtZU2xvb2JqWW9xcXZmYXNueXptantygZqOZ2dta19ramFnZGl0Y25mZVxqc4p6cFp0g3pqeXVjYGZyjYhrgohgdHt2cIF7ZV6AYV5fcXx5pH5eVFZjYWBhWWlodG5zeIRxdWpOd5J7ZGJwX0lHgGtucGdoZmZbXGF9d25wdGpgioN4aWVnX1JaW1hlYWduXVBaYWFtfXFpX3WCa2lob1ZZWmRbcVdmbn9qcG1aX2lldHpmbm9gXlllZ2lrd3xtZ1dmhG5ycG5pdIiAjnxwZlFgXGxYcpJXYGBlb22HXmZsY3BxZWdmamZmbWpvdYd8SXyDaoJqbmlnZXltbGBcdmxuYWFTWWBiXGJlY2Fec3J2bXNuamZ4d3OMeniMinJYXXBqYGlmdnCBfW9nXVljVFtmamV1gFhZhFBbZHSNbGZbWl5lXmdkc3lbWV6Ah4CNjJByVk1YbnBhYmxoZoR5d3ByZm5iYWuTak9ran94eYpvfXB0c2tdZX2DTmNoZVNRVU5OYGdtdH6Lm4VaV1hYWmOiZWSDeGxqWmViXlpsX1dZZ2FlgYJ4XmthWX2EdG9pZHKDcWplc2taZGtub25sd315bnNmaGxtaHZ2gW+AcnFya2t2YmyJX2lncnRzb3pxeG9rgZiMenVudn+CgYNscnVscGRnaWlrd2RvcFx9YV1bVGWGZG9od3p4c3pwcV5ub3+Bgnx4l2NlaWJUT1ZhZmxrb2Z2a2VcVV1kWHZ+bVlNW25wc3J3fYN7aHp/cGpucmdqXmx4bnd4c2tibW+AWl1mbGN0bpWJcGxlZGJuZ2ljc3hkY1tTWmpoY2VkX2ZlYl5sXWJsalhqcXB4cFlbbWBnZV9fZHBtbXyCaFtpdHZrYmJecHBeYV9cWGxTXmBZX2RaWFlsaltYZF1aU1pcVlhdZ15sjWFcV1NZaGx4alptdnpvcW1ZbYagfnFtZHSAdHyOg210inpjZ2BwbW1qcXFaY3hfjGVtdHV1bWZaYnuAh49pbWlXZ21tlWZtYHJ0fHFqeXN2eW5lX252bWmHeoFiY2FoZGFhXnVxbX6HfnVjZV1fc4B5dW5oYGRpd3lqempoY3d2d3ybc3l2dnNrbWllZllWclVNZHpqVWBjYGiAYmZgdFFJS3RzaHB1b2ZyaWJ/RFJkcGhtXmNYYFtjcGxyV2p8VVJYTEaLe0tjZoBzYGBdWnZjVWJLXGBqYGRaXGFsd1ZacltXeV5xnXJyd2tZUWN7Z11WdW5uinpoYWpphX99eXhLhHV8d2N+ZnRkZURfbmlGU4qIZGxkV1plY22KeoJ7oXoBef963noBe5h6AXvIegF7v3oBe/96/3roegF74XoBe416g3uLegF7lXoEe3p6e4x6AXusegF7jnoBe4t6AgIEAIDE29zG4vHW1t/zmfqN5dnDvse4vbKlx7e3tMSkn6eWiYvItMGlq6iywb3Zzrao+Z2jpqu1q6CdqNC2tLbTyK/Tvbbwi+PKycq8y8jGor6Gh83jtLa4u8GysLe6urenn6OcraWfn6esmKenq5+drp2h4b/gv8vAtrOhoJuVpKaqooCcmZyKiJyyxMWwr52Fj4axwKust5/v6bilqLK1o77IwuKvnbHnxMS/s8HOvbm6sb7XvrTHwaa5tKi1tqWtm5uVl5unprvEwO3OtsDd0rerssO0qarAyK2rtrHEya7QwrC2sa69tLe0v621p6/Guq7BsaW6ub29x87BuLjL7tGtu4DRwaevtqGhtKShq7DKu5yVv7vEprSxp7fGqN66xt67q6m1yLO8r6Knopupq6vdspSnsqe2ssmnz86OlJKgtKWcm6Gqr5+8tri3trKzoLKzkabFvra4v7mgo7WYqb66tZuopqqosqOup7Gysq6nprGjpaOitaHCw7a0qK+9xtOxpoDSwMaYldOrpaGtrbbCtdfP6rTAvLGyvq+xudq4xNHPtMK0r6yzq6Kwvcayu+reor6eqZKesp2WoMnV99OqyL6LnaK93YO8trKumP2InqOUnpyjpqaqq8G5uMTLx8nB0crk2bu8wtDQw7PFuLK1r7LEt6rFvtOkr5apoJ+xuKartYC4y7inwrixrMDIuMeysb/RtrW3ury/t9O90b/Jr5WmorW2s667vqKxoqO3p8bCwLXGrLOkrNCstMPBt62evbm4sLS6tMbctqyqqKqlr7O1u7mxw7m9vKm1uLGbt9TP4tW9t6eTp6qomJqnopiYnqOrmrGdm6ysrK+7s7i2rZ+0toCfv8u9yMnQ59TLvs/Lv7C8usHG2K+zoZaqprzMvb3Fw8XLzcO8tKasyt6ov8HFuaXGtKuot66tn5ainpWapqqwoKSroJ+gsqexp5eFjo+Yk6erqbzFpqq3qamUl52FjZOfo6a8rayjpK2Nl/eEnrSrra7K28CyvqWmzvbAra6rsoCsmbieoL7IzL+3uMLG2fHOu8G3u97QwMTE5OjRyurCxMrEw6mwvrTK1dHPxMissbi7s6aiqJybpZGdgayrqrG1vbqfoamyrM+4tam2vsfLzdvPsa3T2cS7vMDDvsPWzcX+x6eYsbavrq+zxNLYzOyB/f+I2Mbqhurd8p2FgYbp6YDSzdK/65bAx8nm49va0rD3q8zVudbh2dXV07/RtPnGycrMxcGvy7jZ17/kycfLt7/F266+yqe9zID3yOD/5b+57pjazMaFqsHt3aKmqquqvqWascPe7qnEydPH9Ia9rcPk7OPS5+f44oL11sm8uO3LtMeIqIOzq4rSzLy6v7WjvIB7kZF/m5x/fIOUWoZTgHxqYXJxdnRpiHl9doVkXGNRQ0R3aXhdZmRsgHuPjHBjpFRYXFpfWlRVXYlsb3ONfGOEbWeVV4V4dIF5i4yFYHNZW3uLbGtydXx2cnp8fntoYWdfcWZfX2drWGdoZWJbZFNSg2p/Z25kYV5PUkxMYGJlYoBbXF5MR1ZlenRkX1JARz5kcF9gZU6UjmhYW2doW2x2co1kUVuSbGtoVmRuX1tfWmZ9amZ8dl90bGRsaVlfUVBITlJaXmlsaoJyXF50Z1pSY3VtYmJ2eFpXXltoaFJ1aF5bWlhjYGNdZlpeVVtqYFpnWFFcX2RiaXVmYmN3lHlZY4B8bllgZFRVZFhXX2FyZE1JamVoUl9XTFtnUYVeaohnXVpjc2BpXFFYV09XWFh/X09haV1qY3dcfH1LVlRbcGRdWFlcW1RsbXR0cG9xYnNuUWN/fXh6hH5taW9VX2plYlJaXmFkbmVpaW9ybmxlZXBeX1pbalRwdGprY2ZyfYVnXoCBcHVmY3pVT09aW2RqX4V5mGBoZFtca2FhaYhnbnx+ZnlnWlVbUUxZZmtVWoqGU29bZVhqeWdhZoeNoYFieHJHWV12lF18e318bLJhdn5tdnFycHBvb3xwbHV6d3p1gHyUiW9xdYSEeWh4aGJkW1xsYlduZnpXX01aVFRmaldaYIBiemVac2llYG94bXhmZnOLcGpramxzZoFrgHV4Yk1dWWxvbWJobVVfUFFeV25oamN2YWNdZYVnZ3R6b2hddXd0b3J5cYSTc2tpaG5tdHh2eHhveGtubl1jYmFQbop/lYx2aVlLYmtpXGN0eWtnc3J5aXVhXWdtbG98c3VwZFttaYBTZ3JpdXJ6jH1rYWtpaV9pbnN6jWhtYF1zcIaPhoKNhYmLiX11a2FfeotjdXZ3cGCGbGFncG5wYFtta2RseHyCcHJ2aGZib2VuZVlMWVddVmlxaG94YGZyZ2dgZm1WWVxraWZ4bGlfXmlSXItOYHRqaWyBkHhteWFehLR9bWtncIBtZoVzcIiJhHhoZWlsgJt+anNvdpiFbHFyi411bYppaHFvbFxiaWN1fXt0aWxXV11cV1VTVE1NWE1YUWtjZ290eHReXWRqYIBnYVNdZmdnbHpwVlZ6hHJoam9ybHB+enendV9UaG5mZmZicHqAdoxSnZxSeGeATot9kmpUU1uYmoCOgYh0o2x5gIWXkoyFgWKhYHeAcISUi4aQj3uQcap+gH6Ed3Zkg3GUiXaYfYGHcHqHoHSBjW15g1WfgISfkXNsm2WLg35gX3KflV5hZGhrfmFWY3aNm1t3dX92kVZyZnyYn5WBkYSLdk6Td3BpbqiHdIRhdl1xbGeOjoF/gnVmdoBwg4BthopsbHB9SX5PeXVmX21rcW1kg3N2dIRrZm1gVFiMe4hrcW5ygX2Oh3BlpmFmbGtxal1ZYIVtb21/c155Y1yJUHlpaHNte356XnNPUXN+YWFlZ2xnYmZpb3NlZGpneHFpaW5vXmxtbmpkcWJiln6UfYV8c3FfYV1ba29ybIBoZGdXUV9ygXxsZFhHTUVncmZncF2blXBdX2hqXWtycIZiU2GUdnZ1ZHN/b25xbHqRfXiJg2l8dWtzdGRrXl1YXF9lZWxpZYN1ZWqBc2BYZHZrYmJ0eWFeaGZydF9/cWZoZ2VtZ29rc2RkWmFwZ2BwZV9rbG9weIFybWt7lHhaZIB6cV5lbF5dbmVibmx8c1pScWtwXWtoYXF9ZJFueY5zZ2Bnd2FpW1FXWE9WWVt/ZlRkaV9oaXlhfoRUXl1ldWpjX2NoaGF5eHh1c3JyYG5qS1lubWdsdXBfX2lRXW5taFVgYmVlbmRnZWxvbGhfX2tbXVdXaFNtb2FkXWJocnlcU4BqZG5dXX9fXV9paW5wZYR4lF5qamZndGRgYntgZnFxXXJrZWRsZ19pdXpkZo2BTmJPVklYaVdUWn2EmnlccmtHV1hsiFRwaGtpW49SY2ZXY2BjYWJjZHNqaHF4dHRueHSMgGhscX6AeWp5amVmYWJxZl14colmcmFxbGp5e2doaoBne2ZZcWRgW2x1ZnFiY3OKcm5ydnl9co14i36CbVZkX3N4dW11d15jVFZmWm9paWJ0XWFYX31fYnB3cGlfeXhzb292cH+PcGlqam1tdnZ1dnNrd21zc2JpaGFRaIV5i4FuZVpMYWtwam97e3Btd3V9b31oZHJ0dXWBdnl3aF1saIBTZ3VteXmClIRyY2tnZlxnaW53i2JiVU9iXGx3bGx1c3h/gHZwZ15feIdecG9xaVx+amJmb2tqXFhqY1xibXN5aGxvYl9da2BpX1JDTkxUTWBmYGhvWmJwaWphZmdRVllkZmN2aWliY2tVYJJSY3ZsaW2Ekn50f2pnhKd2amtnbIBoXXtqaYCCfG5iXmFkc4tvW2NfaIZ5Zmxuiox1b4xrZ25rZ1VbY11wfH58dntnaW9za2dlZl5fZ1hjUnRvcXV6fHdgX2VsZ4RvbV9mb3N3e4h+X119gm9lZ2tsaWx6enKZb1tUaXFpbGxodHp7cYFLjINCZFNhOmpcbU5AQEZ2d4BsY2pYg1xfZmx/fHh2cleRVGhwXGt2b2dta1dmToFcYGBkXVxMald0c2B+Z2puXGNsglplcFNfa0mGYW+Gel9Ud05nX11JRViHh1VdZWdqd11XanmKkFdwbnNqfEZbUGF5gHJhcmx0YkN/YFdOUXhlVWFJV0hWVFR6fXJzdmlcaop6A3t6e7Z6AXuKeoJ7/3r/erV6gnu/egF7hXoBef96/3qiegF5znoBe7R6C3t6ent6enp7enp6hHuHegF7snoDe3p7hnoFe3p6enuWegF7i3oBe4l6Bnt7e3p6e4h6AgIEAIDA4PDfvezjg4TdyuiMgtvY1cCkvbetw7qzw62Ylp2Ym4680rC5xLCjsrfE07e2p6ampa6/1rWkp9zCqqHUyLbX8Lzm+IX5zb2muLO3x7Xp4/C0urm7qrSwz6SRu7Gxx6Snp5+Un5WrrZ+torbUr76htsfFrra4tKSxtJ7NxKifuYCioZ2VpqCqtcLWz8Gfm4WtzcjB+cegi5iZ0reiwqTegMrnz+rEzt3MqaukrrfEtbbbnbKpoYSZtqq7samfrYyRjaWWqcLp1u2C59zQ57ypq7TT2dWsuO7gxNPBw67Jy5+PorG0u7rH07Chl5O2vMW3rLLO1MXEvsrAyLbD0OPWrIC6tLTLvrComZWpvq2wvbetrLe/t7Gtr7avnq+2v7izpbC1t6+4tqulnZSan6SwlaueiOydqrXPr5i4qqOoo62WmrKto5XVq73SzN3x2cHSv66yuLaurby9o7nRrKCtmZiXmKO+u6ulta+0saWzwPzAuqGcj4+zv6PHxbmnwL24tYCv3JyS6b+3sre3o6O80725u6y7u6rO17/Jzea+usrNva+1tLm1t7m1gua4vcS3tLWhqcKToqOYpKGquaKixqOdu6ysqqOgnJumqYWZopmMj6q/n7G2rbawt7zAw7XSx7ewtbG1xcO0q7q0qb28wLXWtrLTy97Pwa6Wq7vP6b2ipICfz8G4wtzUx6e5mqOwo6SorKG8rbGxy9SlgIaOlp6joZ6Ypq/Ax6fMr6qus8K9w7+7vremqLS8raWrva673J+wwc+upLa3ys/BwMPGubrIucary8Wzr6zAqrWln6avsbDEuqCYlq6qqbGuv6SUqqeqs6+orqqlsbKov7WlnZ6nq4Cxtb7N3+Lf2su4xaO/x6m7v8y10ZamvLmztd3Bvq+xp6Wyq6upnZ+lnLuzxNGwpKqvr6qmtM28qKqmk5asubywqrS1n7axuMLFuqiPpqSYnp+7r6Syo6Ckn6qjq8WPmIaNj6DXmaerhY+QgYPWrpmQi5GOn6ycr8GntuTrw7enqoClqbyxqKWbnKqmo7a5wc7qxLewwriNmZaqxs+/wr3Kvr65pa2qsLy72dq0m8a1yMrEu8yxu8no8eLKwq+4saa9ubChsa+1q7TBqam9w7q7wMK6wLie0K+6rquxtb27vcK5sqKhoJ2ivpGkrMDq5P3SkJXsjs7Xg/Dq7ZeunvHx+4Dx2sHF2OHK+ITRv77Nraq8qbGxqq/fw4juhb+/ytbp1s/WwMDC1c/Ix8eys8C6wbW96cDAxrLFzPHe0eO60ZiXs9+76czQq66zgL+nucrY3brinYGAz7qyuMXAxdzSwojY9/by6NzM1JiNzrSy1KXAqMbH64GqpL67rNXazrqRrYBzjJ+QdJWGUlOEb4VXUIeFfXJhdXpwf3p3fXBXVVlWV0lwiWl1hHRndXeBj3BqVlRbXGV1jHZsbJ2AcGiLeGZ/kGuQpF6pgntlcGpla1yHhoxecHR4bnV0lGpXfnFsf2RkamVYY1lpaWFrZHiPa3RVa3ZrWF9eX1JeaVeHg2ZdfYBfXVhRXlVbZm+GfnFZUj5jf3t3onNXR1FThG5ceVaMVXaPeI5seYV3WFdUXWJoX2R+V2twZUtgdmt0amNaZUlMR1dNXW+JeohLdWpkgF5TWmeDg35ZZ5OBZG9laVhubk9GVFtZYGBndlpRSUVcYmlbVFtwdm1saXZvbmJsdox6WIBlYmRxa2NbTExdblxcaGdgXmNpZWJbWWFZS11ibmliWWdoamBlYFtUTEJCTFNfS1xYQ2hTYmyBYE9qYVthXWNOTmBZVk2IYnB+do2biHJ/c2dtdHVvb3+Da3iOb19rWFVPUlt1c2xjdnN4cWxygJB8dV9XTkpnc1t8fG9fdHBraIBcf19Wg2NTTlZZTVFjdmZjaF1qZ1h5gWhwdpRtZ3V3amJiYmdgXFxTTn9YXWtfX2VRY3pXY2dfZ2ZvdWVjf15bcGdtbm1rZ2t2fVdlbmleYXJ/ZHR2bG1pa29vcGN+c2dhZGBmcW5jXGZfU19gX1ZyWFh1c4J4cWFQZXCKnG1TVYBThHRqdY2FfF9wWFtmWVdaZFp2ZmdqfYNdPj9HTVdfWVdUXmNoaVVxW11gYmlmbWpkamddY211ZmNpd2l3lF9oeX5lWWhneIBzc3eBd3qDdH1qgXtqaWZ4YGVcXGBkZWp6dFxXWXFqa3N0hG5gc29qc2xkaGtndXZkdm1kX2NmZoBpaHF/iouGf3RncFhveWBscXlph1FieXd0eKSJgXN2bW11bGpoWl5gWnNyf4VnXmJoYlpZZ39uWl9gVVltfYJ2cnZ3YnRtcnt/eXBefHhoa3GUf2x9Z2ZkYGdkcYdYWlNYW2aXYG1vTFJVREeadVxVU1hXYWhYaH1ib5mje3VtcoBydoyDe3JjaXlxZ29scoObeG1qfnlMUU5fdXpmaGd3ZmllXGRdYm5tkIpjTnVocXNrZnFbYXWTm5R/e251cGp/em9hbWpvYWhsWVZrcWZra2peWlVHb1RcV1dhZWpobm9paV5iZGZphllnbXiPipRwVl+KWXN7UY2Eh2F3aZaXo4CcjXl1i5N+pFqEeXaCa2R3ZmdoaGSOdlqnXnV1eYGVg3eBbHFxiIB/foZ4fIiChnl/ont6fWl0eZuGeZJ4ilxffJ10mYKIbm5wWnpldYKLjGySbVhVemRbZHJvdYmDcluDlJOPgndpdmxjemZljGaBZn+BnFdpZX50ao2ShXdUUoBogIp4X310R0hyYXlQSXp6dWlacHZuf3p4gnllYmZlZVp9jm92f25jbnWCknZzYWFlZm18kHZnZJF4aWKEdmR3gVp6jVCRdXBkbmtqcmaLiIBcaGtrX2NgfVhHcGtpfmNmbWtga19ubmVsZ3iIcHhgeIiAcXdzcGJpcl6LhG5lgoBraGVdaV5obXeJgHNdVkRmgoF9rHxbSlFRgWlZdFOCUXGQeZB2gZGEZGZjbXZ9cnSHY3N0aE9lfXR9dW9nc1hZVF5SXmyDd4dLfXRuhmJVWFyBgX5eaY6CaXVqbl51eFhPX2ZjaW54h2hdVlRrcXhrZGl8gnV2cHtxc2JqdIV1VIBgYGd2dm9nW1xrfmlodG5kYmdtbGlnZ25pXG1yfHZsYGhna2Rua2ZfXFBOVl9oVWJbSXZca3KFZldwbGVpZWxUVmZgXVWIZ3GAd4mYhWx5bWBiaWtmaHR1Xm6EZlpnVVNQVFx0cGdgcG9va2NpcX5ybFdQSUZga1JzdmdWaGRfXIBTc1FQh21hXmdoXF5rfWxnaFtqal16g2xvdY1qX2ttZmVqaXFub3NtWJFoZmxeW19MWG5LWFxXX19nb15fgGBedGlra2NjX19qbkxdYllPWGp7X2xya2toam5ydGR9cWVgYmFpdnNpY29nXWtra2J9YWB9e4yCfGxccX6RpXdcWYBUem1gan95b1doUFRiWl5kbWWBc3Z2i5RvTU5WXWVrZGJfZWdsbVZ0XF5lZ2tnbmtlaGdbX2lwX11kcmRwi1tjcnhgVmVldHxzc3aAdHV+b3dhe3VoZmZ4YmZcWWBmZWd1cFtXWHFvdHt5h3JmenNzfHlwdHRteXttgXVpYmNkY4BkYWd1ho2KhHlscVZrdl5oa3FhfklacG9paox1cGJlXl9taWlmWmFkXHNxfYxlXGFoY19daIJzX2NkVFdpeHxwa3BxW2pma3R4cGJPZGFUWmF+cGBuYWJiY2tmc4VWWFFWXGeUX2ptTlRVR0mWdVxXVlxea3JjcoBrdJmiem9mZoBhaHt1b25jY29tY2tnZ3WNbWNidnFHT05geIBsb297amxoXGVeZHBvkI9sWIF1gIB6dH1kan2YnZd9eGtzcGh/e29hbW1xaXF4ZWJzdmtvc3ZsaGJOdVhcU1BVW2NmbXJua15iZ2ttiWBscHaIgYpmSU1vRVldP3BpaEpaUnR1foB+cWBgdH5pk09yaGp3Y11tXl9gXVVyXEWCR1NVXWR5aGBrWl1hd3Bsa25eYWpkamBmiWVjaFdhaIt3bYBldUpMYHlWdmJnT1BbS2dZb36Oim2OVVBPc2NdZG9pb392YUprf394b2RYX1ZSZlBRblBhTmVkeUpUVGxrZIaPgG5NSod6B3t7enp6e3u2egF72noBe6h6AXuHegF74XoBebt6AXmTeoJ7pHoBe/96/3r/esp6DXt7ent6ent6enp7e3uLegF7jnoDe3p7r3oBe4h6g3uKegF7iHqCe4p6AXuLegICBACA6r/aiLCuwczX0eTuh/3b1+DIrpquoKGRgIS3r/qSo52szr6qs62gsLK8yre3xL+4q7DPw7S7tby+w7esq7vI2cvZisbp69a4ranEys+00fm9xqyusp6/taOalrzRqp6Cj46xpqykqqmYlp2bx7Cto8bTyobGpru5zqutsN3KxcuApKyunrCqxMTorIaWyPTf6tHH5vPV1qGcr57Gv6aa1Kqdotv1trvn3cXHv6i77LnKxLKkmpKVkY+Mo9yopamPj6uOqsektOvK5tXZ+dqqzcePh9r4kIrez764urq3yOqinLX6uM2woK21rLG/z9LOz9TS38a0zNOym6ufydv3trmAvcrIx6WprbKjpKCbnae0qKeys7LJsLmpp6GYoa2kpZugp56ns7Cro52SlZWmnqeWkaSbqrXFzaKhmJ+qqJCbkqalwbCxu6XG3uLh7P3p1cy8trW8vLKyubeuoqWsv6GhrLqslaihs8qqmpqfrri84crR0quznaa7pZWTmba12b2AkrKzr87Lx77HxaWen6rBwqq2rLS6zdXEyc24xM+/xay5uMW2vr7E2MbL2dne3Nblz8HFmqfKs7HIs7y/tb2Un7avpY2dl6CVnZD7gZCCi6K5oqabuK+7sqy6ycO5rb2vv62/vr7BzK+kqsLRzubtwdnEwMq5uLmnwK66zMqdnZ6AnJquvq27u6yun5WtpK6ts8eprKqvi6K9npCPmKWOmZ2sprXIv86loLa8tqyqtsG9y7CqpK+ytKyuu8bJv9bCvMW2uJ2nr7jfzcy4wbO0qrvVs52pq7O5sr/IrK6ms7qqrKfEq7arqrusrpeQsp2Wm62zoqmqp6ChrKi2xLOpoaeApqfAyMrM1NK23MC1wLO0vL/G3au50MrGsbijoKistZ+xsKOVkpyvxsCnobKwq6ueoq2xsLPM+uutsrScstKtoaK5rrjDoaWqq67OlZmyr8G/wqausK20pr6ioLGSiJqUhp6+qa20uZuKjIiSjZiPoZaPm6idnZyeobzW5cGotrOArLvIwKOdpaO9y7CssK6rtb7Jys60s8Luj6i4rsanq6m7mr/Gm6yzu8DSqqOusqeXqqKeprO1wMO4vMCGvLS658i2kaOroKy2t7y2vMHAxtTawrfNusXP4MzGq7S1u8jP0bm4qqiiq8OepKqttrPUyODF7YXWxtPoz8S8zdDCucCAssXAtM3j79na4uLaxrrSoLStta+/ycLZwPPQ4YL/6tfS6evi8tDX5sDHya6dquHAqO+92LmruPr0+szn3uaeyrPFu8rcx8m7oLPAu8K/1eO6vM7UyMO+vdXHzbPN2cWnj+/llIaG0Mvf/8Su0tXWzba+1Ozcw8iGy665yeHNyuWAj2WGW2RfaXKEfo2PVKiPjYuAbWF0amhXS0x3bIBSZ2FuiX1mbmhXZGVrdoZmdG5nY2uFdmRvbXF0dG1lXmVzgXGBWnOXnYRsY192dXZgdJxodm1ydGSBdl9YVnqGYmBLVFNtY2djZWVZVFxegmdrWnWBclRsUmdedllYYYV2doCAXGNkVGRdcG2Gc1JbbY98jXdvkJp/g1RRY1iBeGZYi2JXW4+YXmSGeWhnYFRhiWh0bWljXFdZWFpTY5ViYGhUVWxOXHRVXYZsgG1ujnZRa2tZWoeaWFyEdmRcW1xieJlUUGScWWtUSFdgWVxja3Fvam1rdmNbbHRfUF9Ze4ikaGyAbnhzbVZdXV9VW1hVVV9vamlubm2CZWphYVtVWWNeXldhZV1mZWFbU1FJSEdVUF1NTVtUXWt4fFVbVV1iX01VSldUa15bYlNuiZiWobKfh4B0b2tycmxzeXlxaWxtd15gZnBiU2BfZXxmV1RXXm51kniBgGJtVF94ZVlZW3BpiG+ASWJhX3p4cWduak9OUFpwblVcUlRfbX1weYBtcHhra19oY25fZmRmdGFhamdzcHWFdnZ7V2CDbW1/b3F0anRUYXRxaldlYGlha2ClVWJXW3CAZmZdcmhtZF9ncmliXWhgaF1sbGhsdGFXWWp0boaIY3hqZnVjZWpddWp0iINXVlmAV1tqemp2b19ZTUddVWBdY35bXlxhRFlwVk5IUVtIU1NdYGZyandaU2JmX1tea3Jvd2dhX2xtcGhud4KDeo5/doFyc11nbHOWiYRygXR4b4CWdV1kZmhuZmtyY2ZcamxhaWB6ZHFvbnhtbFpUc2BbW2ZoXmRjY2NodnF8hm9mX2aAaGh5e3N1f35ghmxkc2djbXJ6kGVyiYV9anRjYmlsd2R0b19QT1dnc3RdXWpoY2VcX2VlZGV/pJFgaG9ZbYVoXmFyZm13WF1fZWiJW2V7fI2Gh2ptbmZpWG5bXWxWT2BeUGaCbW9yc1hOU1JYV2JaZlpUXGVbWlNVWXOMnHtmdXSAcoOLi3hxdHaPnIFwcXRsdHp/iZBza3miUmZ2bYFpamh1V3p9XWVscneFZGBpbF1OX1lUWWNodHRrbnJjdWtznX9wTlliV11oZmtnam5laoCBa2R5Z2xsd3BuWWJja3t/gm9ya21ldJNmbHJydnOGd4lpiVF1bHuLfHh2ipCCfImAdYR5aIKRl4eFhomId3WNYnFtcm93gnyKfamEk1mjloF7j42SooWQo4GKjXdlb519YqVui2ZdaKaks4ONhpJfi3qBeIGLentyYHiCfYF/kaBzcXmEeHJvbH1wcF5yfnFtW4uFYlVVdnCDq3Bhh4yPgWZkf4yFcXRZeWBrdIh8eISAgFtxTFFMV2BraXZ6SY54eHpzZFxuaWpdUlSAeZpcbmhyjX5pcW1fa2tzf2l0f3l0b3SIeWpyb3N1eXFmXmd0emhuRWWJkH1raWiBhINsfqN0eGtsbVhvZFRQTGt7X19KVFdzaXFpa2teWWBfgGpqXXqDd1F1XG9meV5eZHx3dX2AXmVnW2lidnGIak5VbY+AjH55mKWMj2FcbGJ+fGtYhGRXXpKaaXCSh3Nzbl5pkm99eHJrY1xdWlxYap1xa29bWW1PXXFVXYJsgnR0jXZSZmhRVoCNSlB/cmZjZWhsf55dVGSWW2tXTVthWmFsdX15dn18hnNoeoBoVF9VcYKYXWGAY3FwblpjZWphZWJdXmZxZWNmZmh9ZW9nZmNdZG5nZ2BnbWdxdXJrY2FYVVRiW2FSVGRZZG18gGNnYGZqZVFYTVxZbmFhZlZvhpKNlaKIdGxgXFtgZWFmbW5nYGRncl1iaXVnVmNhaX9oWFNWXGhqg2traFFYR1NoWlBQUGRhfmqASGBlZYKBe3B4eF1bWmN5dVxjW15ncoF1eXtqanZoZ19nZXNocHB0gW1rc3B0cG97a2ZsSlR5aWl9bXJ1bndXZHZyalZjYGdfaV+cUl1RVnGCamZddWxxaWFrd3BkW2VfaV1ucm91f2lgY3SAeYyPa3xubnptbXFnf3SBkY5gXFuAVVRkbVxlYVJTTUpiWmRjaIBkaWlwVGqDamNaZHBaX11lYWhyanZaUmJnYV1ebXJue2ZhXWdoaV9lb3l7coR1bHdqaVNeY2yOgH5tfHJyaX2ScltiZ2pxanB4ZWZcbG1gaGJ7Zm5qaXhub15ZemhiY210b3h5dm5ueXR+i3RpXmGAYF5wdHF2fHpdg2xmdWpnbG5zh11ofXl0YWpXWGFlb15vcGNVVV91g4BnY29pY2JZXGNnZWV+p5RlbW9aa4VqX2F1am96XF5gZWWBU1dqand1d11iYl1lWHBcXnJZUWFbTWR/bW5ydV1QUlFZW2heaWBZYm1kZmNfX3aLlnJbamqAZXR8e2VfZGJ7i3Boamppb3V8hIlyb3+oVmp6boJnaGNzVnp/XmdzeoKQcGx5em1dbWNdXWdrdHRqam1acWlumH9vUF9qYmt2dHpyc3ZucoOIbmZ5aXF0fnJqVV1eZHF6f25zbnBpdY1lanBwc29/cX1ceUhoXWl8bGVjcnZqYWqAW2ljV2t8g3Jxdnl7bmuBWGhjaWNocGdyZIhmbUaBdWZhc3d4iXB7hmtzdF9PVndkUYdablVQWo+Rl3B8fIdRdWJnX2ZuYGJeUWZyc3l4jpdwdYCJeHFwbXxydGZ2eWhVTXVwU0hFXF9ylGJTdHp3a1VUZnFsX2JMa1lncoZybngEenp6e4h6AXuPegF5jnoBe5Z6AXuwegF7lXqDe8R6Bnt7enp7e/969XoBef96/3ryegF7t3oBe6h6AXvCegd7e3p6e3t7kXoBe4h6AgIEAIDbysjv1Me2ws6B38aEiNvm17i5pqGhmqmIjqOPj6GjlaKfo6q/u6e2q7y/gLC1sLimusC2v7i0tLO5u8DFxu6L2cK3u6am08rHwrios8bD26mr59C9utXMvr+n2ujYx6eanMy4qMfSmbKUpL2xqKiosMfJ3ezBtM2dvrCNnLHLuYCUiZ6anZ+qtb7CwNLRzM/W0t7z08K4ts68qd2zq6CstKK22qrpwca7vLu7uLvK2MXku7SlmZuNkpeLp8C717CwyrSfmZWqs8Hy+4b6zbK66OXctcOvwLyzvM+uurG9sb/p3PnGxMSx0OauuL/OyL7Ih+jyv7m/r56xq6qrpbegrYC0vrS7vKGXlZKUhZifqbqzsMbfuKmwtKCztKS0r6miqpWEmZuotqWfm5qio6Sbq6arn76inYCArLOlwaqoosWrqq3axcvBxNbKhpHMwdrKwufYl7LMo6G3p7C4k5GWnbK9wsS8xqHEqp/W3s7m0vOA5NnToI3t0c+gqaa0r6OfnoCWo6yjorjM6MWZrqevu7y6taGhrbnXz7vG6LS3tL27xL7d+cG2z7O3wOfX1eXb2OPE2eLRxtLKyIHlwKWmpaq2nq6lqaOTnoqVgfiGiYWIjI+Tk4qUmp6kqqSxwtqrtLrOuri5vMy7vaiywMzDwrTLzsXHt6aozayVtq7c0J6anoConqm9xau1yru/srvBtL7Mtq/Dm6Kpk6ucjp2ZlKKXm6SSqMCrq7zHsKymprS/u766s6asq6vFypi168yzx7nVxPe4x8GkrL/AybfAr7KytKysuMW+x8+9vbu1nJWpqaSmoKXUqq+sr6iwtLajnJCVo5+SoZmolZXCup6wlZKJl4CcobbEw729xcOyq6efm5ymrLS+trXArLOjqKKZj5ejj6rF1MOqn6y7r56Upamfo5uYp7uyvK68t7CzqLCnsbW4tMvKuri0nK2yr6uJk6SfpbPGta6rsr6ps7KypJybk5aOlpOJnbCfopKQl5CllKKhtKq7wLGunZatsdzpt5ibuYC6rLayoKatnqirsKW8uKqPpsS2usrZ5Na1x7q4srS3qcextZmLus6trre0sq+ttbWusKrAxMLR1Lism5qpqKvIzbqxva2uur+3x7etr769wNDT3YDs6PLWvrOuw76tvsKxpbjAr7rF+aCesr7Mwda04/DY4Nu/us7c1s3CzsTSloCcrcPBwtbb3L7E0eHrtKCouKzaytrRxty9yuvEyK22rMW7tcq6gdTBrLX/wo/Fs9Sd8rbQy8PJ4O3HpLvV1sHLwLfBtMLi/szFzcfg0sHM2Zycq8m2rODLvLXLxcWwuYvG9evd7djDzcK/x+eAxujy09/kl4TNqMuotLLIxLjHwICSfX+minhgaXlSgWlPVo+RiHF2bGxsZXRXWG1TVmxuYGxlbHCBfGd2bXd6W2tuY2hbbXNqcXBraGFjY2JqZ5BegWxqc2J/h3t9dmZWXG9wi2FrqJF+eI2Ba2xYh6CRfGFXWH9tYnmBU2pRYHJmW11ZYm9pfIhjXW9LbWZLXml/eoBXSlhRT01WW15fX2xxbW97eYaZf2xmaHtvYJRrZVlfaVVegGl9ZWRcZV5eX2N1iHmSc3NsYWFVXWJVa4B5knJyi3ZfV1BWV2CDjU2Ja1pkjJKJantgaGdbYnlbZFtqX2uGg5tvZ2ZXdYlbZWl0bWFpUYGLZGNnXVVkYWBiX2tTYIBlbGJkaFZPT0xSSlpeZ3t5cYCTbmBhYVRmZlxpZWdfZ1pJWlxmblhRT0xRUVNNXl5iVW1TTjU1W2Nae2JeXoVqZWaLdHJsbXdwVV13dZCCfZuUWW+IZ2d9bXd3WVJZXnJ7gHp0fVtwXVWFinyGf51TiYF7ZFuQfn5XZGFvaFpaWYBTXGJbWWx3inROW1ZcamhlYE1MUmF/e2lvil1hZXJ1f3iSrHJnfVdVVG9lZHh1dIFqhZSJgYt/e1KUbFteW2VtVmReZmZbZVlmU51WWlZcXl1aXVdbXV5hY1pean1YX2R2Z2ZmaHRoaldcYmtjZFlscm91althg2pWd22dlWZgZIBrZmt4gGdqgHBqYGZnV19tX1tuVF5jU2RZSldOT15ZXWFXZndiYGhxW1xYW2puanFzal9qamh9hlh0qoNwgHmMfqhufXhdXW9zfnB2aXN0cm1scXdydXxrbGhrV1FfYWFnYmKTZGhmYl9ka3JiW1FVYV5PYFhoVViCemNzXl1SV4BbXW19eHJ0eHRiWVtXU1RhYmp3bGx7a3FkamRbT1RfUWd3gXBeVF9jYVVSYGReY15ZXW5pbmRtZWRmXWFcY2hpZ3d3bGpmV2VscXFVX21pa3SDbmlma3NeYGRlXmBhWVxYX19VZHBkY1tcYVtrX2lndGZ1fWtpWFNnaIuecl5khICGeXh1cHF6cHp+fnGAfHBVY3ppcHqEjoNpfXJwbnB3ZYJub1pKcIRmY2lpYmRgZGJbW1hsb3WDh3FkVltoYmF2eWleZFVXYWlfcmVdW2tpaW1ocUyOj5eAb2ZcbWRfcXdtY3KBcXqHumttg4yUgo9rkpeDiINrYniGgnpyfXaFVoBea3l0eIeHi3Fzg5GYcmFte2uajJiLg5R9h6N9gGtzZHt2cY99Xp+Pf4i/jVyJkJJZo2x+dm94jaSEZ3mLg3B1cHJ8dXaLqHR0goCVj4KSnmJXXnlmXIN2a2h5dnlka1pznIx/hnJmb2trdpZWcomNc4CFX1l7XntibG1/eHaCfYCDcWuMcWFOWGVGbFdDSHZ4dGFpYWJlYnBVWnBdYHByZ3JucnODf257cHh8VnN2bXNmd3xye3ZvbWpsbmtwcJVZemJdZFdpgn2CgXpud4eGmGxpm4t4cIFzYWRWf5aOfWJYW4R0aH6HWG1VY3ZqYWJdZXNwh5JuZnhUcmlQX26Ae4BaTV1ZVVVfY2VlY3Bybm54e4eag3Rsb4BzZZRwbFtmbltliGGHcHBobmhrbXGBjoGQdG9qYGRaYGNWbYJ5kHN0iHFcVVBYWWGCi0yIb15khIF5Xm1ca25nbIFjbmVtYGiAfJBnX1xRb4FYZGt3c2huVYyVbmxvZllmYmBdXWdPW4BhbGZscmRcWlpcT15gZXNrY3KFZV5jaFxvcml0cXJsd2ZUZmp3fmliXlxgXV9ZZWNlWXFdWUE/Y2tke2pnZIFoYmB+aG1pa3ZrUlpxa31ua4R6RVhwU1RmW2ZrUUxSWW14fHZudFZuWlSCiXqFeI5Kd2poRkF0amtMXFpqZ11eW4BWYWlkY3R+jHlUY2Fmd3h0bFlXXGmEgHJ7lW5xcnRweXKMpXNqf15cX3htaXhwbHVccoF7coF6eE6PalxhYGlzXGdgYWJaY1ppVqJaXFhcY2NiYlpgZGNnaGBlc4FaXmZ3aGZrbXxvcWBncHdua2N1dXJ5cGJmiHNghHidlmVeX4BlXGRxeF9ie2hnYGhsX2p5amd8YWluXW5lWWdhYG5kZWRUYHBdXmhwXFxXWWdta3F1bWJqa2h8gFRtoHpncWp5cJpmdXFYW21xfWx0aG1tb2dnbXNyeH1ucGtsV1BeYV1fWlqEX2RjZGJpcHZqZVxibWlbbGV1YmOOhW13X1tQVIBaXW19eHNzeXZmX2JfXFlkYmp0amdvYGdbXlhUTVJgU2yBjnxpYHB3b19ZZ2tgZV9cYXZvc257dXR4bHNqb3N0b4CDc3BtXGtzdHJUW2ZgYm18Z2BcYW5bYmVpYWJiWVpWXV9VYm1kZVtXXFhrX2tpeG18g3NwX1dmZIOValNZcoB0aGpoYWhvYmxvbmRxbGNPXXVobXqFkIFleG1saGlsW3dna1pMc4twcnp4dXdzdnFpaGFxdHaDhG5iVFdlY2R8hHNocWNmcnhvfHBkZHNvdHdvd0yOjpJ7a2NdbWdecXZoXW56cHWEtmlofYOQfIdjiI14fXlkXHF9dnJodm96TYBUYXJubX5/gmhseouPalhgbV6KeoV3b31nb4lma1hdUmZfW3FiTX5xYGaPZDteV2lBflZlZGJqdpJ3WmuBeGhuZ2RqX19whl1gb3GKg3mFlGJhaYZvZ5CDdnSDgHxkZU1ggHRpc2JVWldaY3xIZX+FaHN2V0FoU29ZZmh7c2l2cIl6BXt6ent7nXoBe5N6AXuFegF73XoBe6Z6AXulegF72HqCe6R6Bnt6enp7e8N6AXuRegF5/3r/ev96i3oBe9F6AXuIegF7sHoBe4x6AXuGeoJ7i3oCAgQAgNHRpJvUvK3D3eTWsPDy7MKzttK8sKyZxKKmpKPLkaWLiKulr7iv2Lq10a/Lr67AlYfx49jV0sXGo6OxqL/I5ta9ydPNuLfbrMq5urSuo7rXwqGmxNu41Mq2vrS+0bmqsLKgqLu4xsaorKequ7q0w6Snq6PLnNi/xre5wa23pK30gMett7e2sd/S9onb0djMzN7Uysy7q7KxwcbaosS0sbm9sLfS1+vb0Mq7ubq1xKqz2+ufnb6mvaKambasoq6exaynsbfHycTa7+3Yu8PKwsXLvpiAo6O4xLjEudKE3dfZgOXm8+Pd9YDM3LOr1djn5LLI2KiMzb7Fz8S0tqytra62gL/Jurm8vLC5nKOam7Gvorasv6WUqKy+qrTGsLGwmZ6WpbKbts2urq6vq6KZmaKxtaaoqa68pqbCsZyTm4yp0Zy0xNfR48O3taK1xa2eudO/y8THzdTQz8mVoZ+SreS8rr3Kxsb07f7Wte/419jn7tzk3tiAzsfNoKXDqq6ppp6XgLmin5ud1OHKwaq0uc2prq2ssrLB7vPSwZimureqsqKtnPWhsa25pLPF1PPilKTyhrnpz8zbwq2q2L27r7m5r8PIpJadmpakp56cn5SmlIiVkZCTnpygm52WsLmyspnCsrCvsre6ucG3s7vAwM7Kw9XY1cKswsS/vqCNkrCyppCOgKKhrKmzqrSuqLSvrrjEtKS5rcjUsbqokqGbn66ZoaSPoKSzqritws7GpKWepqylqrCsq8LBssG+tMDAutbc1LvJ7M3Mg8yxnrOp2uGbyMeqxdvo2cfBuc/Ww7+4q8auq73CwM2zsLCYnKioq5+Wiqawr6Onqrackrm9tr2joKnDgLe9wOG2v6us9rqkm5eHkaOjsa+nrNW6w6Cxtr62qJaaw8iyxrq2t7+qs6K0y8u9uqmtxKyusMG6qbKsqbK+wsG6zLGwoKetqrGwl+aHlaS8wbOwp67Arai4q5ylp52NkpiVnbShm5L+/Y2PlqCcqcbLmaDCwqaln5mhnb/KgN36gOye2qGXp6agpp6dn6CKjPuDl6y7xsmt1e7CrqzItr7F3ce8uL291ruysranqKilpamqq7PAwdTTx7Sol6OzxMLLwLvOva/WrKzPxKmuw6G8uszTy7vQ3uS+ybeyxKyxwMnLw7KyubjPtL7MwLO5vb7IysHGztbv34LUusCgqbaugLi3usalnNzdlpa/mOmsra6rtri5rKatu6+43tS+vcDd84u8xMnNjI62uuG5tb+dzr7Rn6eiobPMvtrLv8nLu7+yyMnJs6a9vcjd/+nHp7fQzsC8v6SwyLq0qru3srPE+M7m0dPv8fSA+7HU89fa1eXnjYLZ1cDI477FuK2uy/XjgIWBaWqDaltriJCCXpufn3lscYl9dXJmjGpubGmOWWpWUWxqb3Vxlntzh2qBZmZxV1yhkYJ8e3N5WVpnW3N5joRxeoF9Z2eHWHdnaGBbV3CVgWhphZd0hX5wdmdsgXFnbGxbXmxpdnBVV1NTX1xZZk9QSURkYHNbXlpka19tWGSngHleY19bU35wklZ1bnpxc4V4dHdmWV5kcHSIW3ZpYl9jV1lubH52bmhhYmVibVttkaBaW3tidV1VXHVqY2lgiXFsdHJ6dWt8hYZ0YXF7en+Gd1ZBWlZoc2ZwZn5Mg4CAVY6Pl3t2ikpueltVenyKflNmbmlUal5reG9kZV5fYFxkgGlwYF5laWFsVl9aXW1tZXhueWJSX2FpW2d8aGlrW19WX2NTa3lXVldYVEtHS1RfaFlhXmFqV1h1alpSV0pklVZpd4aBkndsaFdldGJXan9we3Z5hJCMiohaamdXa510Z3B5cnOWk5R0XI+Vd29/i3l+enNPd290UVJyXV5aVlBMgGhYV1VZh5GBdFxjanxbYGBeXWFpjZh0akhOZmlbZFZdV55jbWhuUltka4BzU119SlqKg4OSf2hqkXNsZGttY3BzXE5XWVVhZ15dXFhmWVJZVlZXYl9gXV9bcHNoaVR2aWVmaG9oYm1lXWRiXGhoXmpvdWxdcnZ1fmhbYH6CcllVgGBia2JpYGNbVFtWV19sYlhrYH6Ia3FbTF9cYGpga29YZWVtXWJZaXt3WFhTXmVfZWlmZHl1YnBuY2xtZoSLjG98mn98W31hUmhcjJdSfYNido+ZhHRsZHR2ZGdkX3hjYXN3c3ljY2lSV15gamVdT2FiYFRYYmlSTHJ7dn1lZGuBgHd6ephob2Bfo2taVFZHTmFdbW1gZoxydVZpcXhyYE9Ud3dgbGRhX2FWX1VjdXpzc2Vle2ptb31zZ25pZmpxdXRqdmNhVVpgYW1zYYRZaHSHhXduaGZ0Z15sYlxma2JXXWRhaH1uZ2OtrmJiY2ZoboiLWWGGf2RhXVxjXH6HVJSsgJxekmZfcHNueW5qamdTWphMWWpzfXlhhqJ+bGqDc3l3jnhqZ2dmeWJcWmRYXFxWWl1bW2NscYGBdGNZTVdgamhtZ2V0Z2N+Y2F/cllfclNjXm10bFZga3ZjbmtpemtteH+GfnNueHiNb3aDeW91dnZ8fW1xdHuHekR6aHVYYnBvgHt2d4JnZJWWWFuBX6dwcHFzeX6FcWpyfXB4l4h3dnaNo2Jue3p7YGVrco9tcXd1iXaMY2xkXmuGfJKEd397cnhrfHp/bWZ9fYWPppd7YnWFf3N0dVlfbWBZU2hlYmZ5qH+PenORjZJPnGOEmnl6doiPV1WBgGt0kHZ7dGdqhKmYgHR0S1BvWE1ddoB0U46OkG5lbId7c3FnjW1wcHCQXmxZVm5rb3RukHlyhG6IcXF/V1eimYiEgXh8XltmXHB1i35sb3ZzZWmKZoZ8gntyanuZhGlogZFxfnhqc2lvgG5pbW5eZHVzgnpfXVlZY2BfalZbVVV0YoRsbWdsbmFsW2SegHdgZWVjW4FzjlN0bHdub4J+eHxqXWJmcHaIXXhuZWVsYGR+eo2Fe3RraWxqcmFxkppeXX5kd19YXHRuaXBkh3FrcW91cWt5hIR1ZnB0bnR4bFJEYF1rdm54boRGhHx2TYGAhnFqeT5fblZUdniEflRmb2RUa15oc2dgYVlbXlphgGhwZ2hvdG50YGVaXGloXm1kb1pMXmRvY22BcnR2ZGhibHRie4traGdpY1lQU1xmbVxgXGFsXV95cGJaXU5jiFdmcn55hnBkX1BhcV5PYHJncGhnb3p4dm1HVlNFWYVjVmBqZGWEhotwVouSdXJ+hHF1bmpHZ2BlSE5sXWNiZF5ZgHhmaGVoj4uDdmBpcIBmbGljYmVvj5t7clVcc3dqcWNlW45iaGRrUltja3ttTlV0Q058cnKAcV1giG9qZm1waXV8YlNYWldja2RjYmBwYlpjYGBgamdnYmNdcXdtalR0aWdnaXNwbndxa3Vybnh4cH19gXRleHh3gGheY3+GdFpVgF9gaGFmXmRfV2RgYGVwZVxuZoCKb3NjUWZmbHZocGxUYGBnW2NZaXdxVllXY2tmam5sa397aHVxZWppYH1/e2BxkXd1V3lgUmdeipdVen9jdYyVhHZyanmBcXJtY3tlYm9zbHNhYWZSWWRoc21nW3F2dGtuc3deV3p+e35lYWJ0gG1zdJRqdGRmp3RjXV5NVGZjcnJlaIpvc1JfZGxoW0xTd3xpeHBwcnVlbF9tfn93dmhqgG5wdIV8b3Zuam12enpzgHBuYmhxc3t7Zo1WXml6e29pYF5rYF5sYVtkaGJWWV5ZYnRmYFucoVlZXmRnb4eMYGeIgGJfW1ddV3R6SX6QgIRVg2JcbW1lcGpmaGVSV5NKWWlxeXZff5ZzYmF5Z2pthnVoZmpthHFrb3htcG5lZmdkYmZtcoGCdWVbUFlldHN8dHCBdW2JbWmEdFpfdFpvanh+dWBscXljcWpnemZpdHl9dm1udnWKbXiFdWtycm9vbV9jaG96aEF2ZnNYYGtlgG5ra3JZVICCSk1xVZZoZ2lnbnR5ZmBmbWNphHZnZWR8i1BbZmVmUVNZW3NWVVhYbV5xTlZQTFpzaoByaHNxaG5hdW9wYVZmZ2p0joFuXW6AfnR1eF9oem1qZXVyaml0k2h0YV1xc3hBekpvinJ0dIaHUUtzb1xle2ptZl1ZcJKBBHp6e3ureoJ7w3oBe5R6AXvRegV7enp6e4Z6AXuLeoJ7/3oBe7h6BHt7env/epV6AXv/eod6AXmbeoJ5lHoBe5F6AXnneoJ7qHoBe4R6gnuGegF7uHoBe4l6gnuNegICBACA9drR2LC+q5uk19HQ1Mm9p8HVqPPCrrm5tLy3p7+cmZ+Lj7ifxbW2w8T/v7CmpcPG99evtcDOw8S1jpumuLfn0sHQ0svK2MDC1NOimc27qJ+vppmit6DJuMXIzanPuZqpnaiNpbOwxsaGnbKosMDHyuD69dXBu7nS4bannsKotb+AwJ6mnamov8/Y4dTFxauyvLK7raGimY+UpK2hu7TI0NWQ6b2uysbW4cS0pqm+yte+x9aon663rLq4qLObnIegpa2yu73R2dTQ54WDz9bAwKuyr6ulprvSzL/xwPenyb/c0v33+PCCk4/4/NW3u/3o39jT2c68zayhsKrGhNC6wcWAw6antNzlqtW9oqyOoczCqayss62vq6+1pqqcrbinpqiwwqLIybWxr7yiqJ6cq6SkrcPZxK3NsK+3tpqXmaCdpdDe1u2A9POH6dy/w8PL/L/P2uKH6MnxzcDBzMLL6+vXhZ6mhriagNm+wL3chunY9IaP3ebUxcakqp+dlbCxqqWAo6u1np6Yn5ShsrrLuqWotbbIwsDc3O/BvLWzpq+2gsyvtsy4w8WtttOApICD+KrB18/Jytar0MvDsayhrqytoKCan5CFj5+nj5CTk5WdjZeMk4WOkZyjpKKftqusnrq8vcK4r7m3w93LyNnT0cKvr7a34Ma7wK2XiYyJo8G4mrOAoKKnpKynrbG0t7KysbO5x7W/u8Hdx7qdkpyPqKS0sLu8wMHDuL6zt8C/rrCqr7Cup6e4rca0vb+5zL/F3OCFwIDc0MTo58SmsbXH9bu6xLCytra/0Mba3PaC6u/Nu6u9urCxr7W5r62ht9fLrJ+Smpi0qaqlss2wrb+lrLS2paqAtLfDybWxsbHBuLOnp52Ji5qWnKemvq6qq6iypp2HoZqbqJybldK1q6qvnrzNs7O+w6bH0J+VmqahqqylnqOorMO+uLSio56mtaSM5PD8ire6pKG4raaqnaObk5Kil5uQjYuEt6Wji4aHioWgoqWjsamwqpugnaGtjpyns8m95IaAr4DIpJCxqp6onJq3oJuzlaGut7vX1cLjy866w6myrbLAxMfFvq+tta2kpbvS0urV3fTTuMTKycnBvMWywb+zpa/Evqypr7uss7CnqKi6vbS1u8+5qcSqtr25v823nqOprbK+u8bFxL+0vLa2t7/Ev9fgt5j18Me3vrjcz8nCt8iAvci0wa/DwrbO0L24xbm4qbfYr8PBxa/Ar7e/wsbPx8nVwMbP7tTd+O7F6fXT0MTaws6uq8G7tavH1fa/xNuf1a+34K6rlsezsaWwtr+zqquqs7HAqrWuzMfb7NHb8fOK9q6r1s7LgIj6z9Tbyc6z34yB6Yb05Mm3v8G8zLuQiu2ApIiDi2ZzY1RhioiGioJ8a3qNbaV/bXl+eH5+aYFlYGZWWoBmgnhzgYCrd2xjZYOBt5JvcHaDeXptTVlfbWqVe2p5gXt3hnFsg4NdWod5bGd0amNrgWiJdHp2fl58bE9ZTVlGXWxecnpEVGZbXmRqaHCBhG5gYFtxiGdeW3xldn6AgVpgVVtUZXByfW9iZFJcZWJqYVlbU01SX21gdXSFgYpcjGdUaWl2eWViVVVnb35tdH1dWWRvYW5wY25cYE5obnZ6fHqGgX56llxVgYx/gG1taGheXWhycG2TcZR7g3OCbomIiX5JW1aKmXpiZKWLgHtzeG9ibVZPYFt2VoNydXiAdl5cZYiPYYpyX2tXYo+HcnBoaWFnYWFqY2tgaWpjX15gaUxtalpaXGdRV09MXFdYYXeHdWSEZmVubVZTVWFiZ4qQiI9QjolShH5rbnJ5omx/h5BZjG6Pc2Zvd297mZ2IVWVlSmlfRmxeYV59UYZ5klJafYd9bWtSWVFSTGBhXFiAWGdyW1hRWFBXZGt6b1tbZmBtbGl8d35kYlxbVlxlTHdnboJsc29XWnJJZUhKh0pccnNzcn9fg399ZmNaZWZqW1taWkxFTFVdS0lNTlJbVV9XWlFYV19iZWVfcV9iWHBwbXFkXmJcY3ttbHZrbGVaW2JohH5zfG5fVFZTZYJ4WW6AXl9gWmJbYWBhYVpXWVdfbl5qZ3STfnBcWGRaaWZvaW1rbXBuaWxla3ZyX2ZiamxrZ2NuYGpdY2JfbWFjd39ShFF/eXCUkmxQXWR0m2xremhoaWVmcmVra3xCd3xpW1dobWhnYGZoY2NgeJSIa1pOVVJmXl1UYnpfXW1aYGpqYGSAZWNuc2BgZ2N1aWZdXVNJTV1bX2Vog3FqZ2FsYFpFXllZYFdSUXdoXl5kVmx9Z215e15/imJaXmVkb2xnYF9iY3RuamhbXV5nfHFfkZysXoeIcWqAdm9yZ2tlYmF0aG1iYl9ZhHJzW1daWldqZmxseW5zZ1ddVl1mTFlgZ3pvl1yAalGGaVt+eWhzbGVzYltuVFhgZGl7gm+LfoV2fWZxcG9+fn56cl9eYFVMT2N5epB6g5h6Y296enZxb3RpdW5kVFxvaV1YXWhfampeYWNsbmBeZHVgUWZWYWVhaHtrWl5kcXGAfYJ9fndyeXZ0bm1vaX+DeWSeoHtxdG6RiIN9c4iAfodzf3SGhXmIjYF8gH16cH+YdIeEh3SAc3d6fH+HfHZ/bXN9kXuDoZp1kpqFgnyVf4pxbn+Ef3SFiKV5e5JsgmFqlGhlV4FwdGt2gINwX1paYmVwWFtXdG+AlX2GnKRqumlff3Z1T1yde4WOgH9gflZMh1eXkXttc3htgXBrYZ6AiHNvdlplWk5bgYB8fnVvX3GEY517a3d8dnt8an5iXmNUWHhgenZ0gH6ke3NtboWFmItsbXR/dHVpT1phbGuSfGpzeHV2inl2jY9pZ4+Dc216bmRsg2yHcHR0d111ZE9dVWJTa3dseH9JWWlbXWVpa3aHiXRmZmN6jWtcVnRhbXWAd1dcVlxabHd5g3RnaFZdaWpzamFlXldaZ3RnenmDhIpJiWxbbnOAhXFpXV5sc4BtdYBeW2l2bHh6bXNjZlJpbnV0d3N+fHx4jVFNdX14e2poZGVgYXB+fHeVc4dnfWx5aYN8dGQ4RER0gm9cYJKIfn95fnZoc1xUYV5zTntxdHaAdl9hb4+VaIl0Y2NQW392YWBdYFhhX2RsZGxldHlvbHBzf2CBfWxoaHFbYlhUYltVWGh3amOBZmNpa1lXWWNhZYKFe4NLhn9JendpbGxvl2dxdXtOfGN+YVRdZmBlfH5oQk5LOFJPPGBUWVh0ToByh0tQc310ZWdRXVVWUmtuaGKAY3R8ZGNbZFxibnaBdWFeZVxnaWp9gYdzcmtrZ2xzSXlqanhmcG9aWWpDVkNFfEJRY2Zna3lehIB+aWdga2xxYmFeYFJMVmBpWFdbXWJqY25jZ1lgYGdqaWhjdGVlW3Jyb3FoZmlnboZ6e4Z8enFlZWpuhYF3fXBhWF1bboh8XHOAZGZoYmhiZ2hqamdnaGNpcWBnZW6NfHNgXGVecG51bW1ra2pnY2dia3ZzZGxscXRybmx2aHdob21mcmZneHdNckt4cmmPknBWYmx9pXR0fW5ucGtvfXJ7eoxMiI51Y15xcWlmYGluamlmepWQeWpgZ2V6c3NrdIZsaHVeY2tqXF+AZGJtcGBhZWN1b25oZ19TVWNeYWVke2xlYl5rZmFNZmBfaWBeW314bmxvYHqIcHJ+gGWFj2VhaHR0fnt0bWtyc4N/eHVoaml2hXlkmpykVnh3Y19tY1xfWV9ZWFptY2leXVZPeWpmUE9TVVJnZ2tsdGtyZldcVVlhRVFVWWhfh0+AXkV1XVJ0dmZ0b217ZmBzWFliZmh5gG6Kd3xscVthX2JydXl4c2dpb2diZHqQj56IjqCAZGx1dXNubHZqd3NtXmuBeGxqam9lbG1gY2Nuc2hibH5pW21bZm1pa3tsYG5pb292dXl3dG5sdG5ram5uZHN1YVOLjGxjaWWHgH92bHuAb3lmcWRzcGV0fXBscW9tY2+HZ3dydWFvY2ltcXaAd3J6aGt1hGx1jX5edn1nZ2B8anReWGltaFtrcYxnaXhZcVddgGJfT3VkZ15oa3FjWFhbZ2x3YGVfd3OEkn2Dk5RWkU5KYFtaQEt/ZHJ8c3lie1NIe02FfnBlaGtgb1pTToSSegF7/3qOegF7qHqCe5F6AXuIeoN7k3oBe8h6BHt6enuLegF7jHqHe4V6Bnt6enp7e6x6AXuKeoR7/3qSeoN7mHoBe/F6g3mxegN7envweoJ7ynoBe6B6AXuGeoJ7iHoEe3t6e4l6A3t7egICBACAgeXd1ry2s6+trru5wsXb28Gxuc3HzbWvzer8qpmki4PYzbaXsbXEtKnGuLavpKmrtb3hpLHPtaSp2dvEx9bnzs/c1Ki7wOGgvryeiprcwqOdqJCfreeztLnEmNvawKuwyYmYsq2pprKkpLjl3siyuPvw5MjVxsTmjrLsx8S0q6eAp6S/lb/NoqKvweS1rsCvqsiqn4qOkpeO9ICMlfuduNnBz/yB3ur22ebJvK6ut6rCqr66n8HOqLbX6si4tbqvqqCTrMfTnfrrg7jK1OfUw7+1vNi/uL792K+skbXO4t3e3tv14P6HiZWqlfvUwvPy4t3eh/OA99fPzOfPycemq7eAxobZsZqhrrGxqZev6c7LqaiXpZ6arbrBtb/VxrnCwamepK+4qLOwrbKps7TBvbS2rbakwrfFt5ekmrOpvtrDs7TVzMzM4N3c69LTx86jta22/e7U5+f7guvLgo72ssf8lJGGmYacloqO1drc3Pbk3YOc5PD078q+sqmlmLagzM2Aqo+GmpeNj6WLpb6vq7rIxb3Fzb+rtLGllZaWoq3TztLKtsrr3cDI1cjG7t7ix/++xMXXwb3WwrXHw6GpuayctqqZq7CbppS5sJ+lmKGijpmdkpWksZmipqy7pKq2tLS/w7enoai4t8G2ta66paOvua273rWqtaKYmqmgmaWkqqKApJecpaS3tsLDvrvBwsXBx8vR3eDFu8rdp5+apLC9z8iwt6mmsrWyr7GooKGir7Ktp6CxsraurLC6y9jF7+2O6Mq739q0wcOmr6ipo6auoaurtszSzL27ws/U0dbGyePixMbCx8i5uMKqkpidmfr5hqCwuMOysMPOx7murrO0o6OAoK3Mz8XE0tG9uaCalZ6SjZOSl7C0xujkrrK4mrfPmrWurqeUmaGiqaaXoa+q0NCrr5+op5KQmI+SkZ6onpuNo6OelpuzpbSluqWNl5mKjqy+uqexnpmgl5SjlYuYkaCcoKOcoauMkJSRi5ehn5yYoqrmvKqxwLOuw6+hyc3Mm6yAqaWWkJSrmo6Pqp6ikIiapauwtLazy9rHvrCpxa2snrS5u9TFsq+tr6Wmm52iuLS+wb3N39O5zcjOxNHzxMCqwra3srOpusjBzMCywba3ua2cnsO/vaalrsq8qbetqKawpKGtrbm+wc7FwtbMwtrj4Ozo3YD2/tLL19LAyNeu1dOAva63sqPBz9vOw7itpLK8n665r56ema+9nL3J1tzHu7S72LaszLLN3JuOh9CxzL+kqaq5r8zevK255OCDkb/MqqKruq68sdKwrLux6+3Mup2drLTGvsq+u6bLg9n2pLXomprC8pLQg43vu8rhzPy9q9fz8/TRxcvRyMfCz8H+ge2AVY6HgWdiZV9jZ21qc3iPimxiaXt0emloh6e8b2NuVFCUinlfbnd/d3GJendzanBscHeVXmd+Z1lfh4p4e4OLfIaQiGRzdpFWcXVdTFyeg21qbV9rdqd0bmtwZH13YFdedFRHXV1fW2JUWm6Rf25gYpKCf2hsZFh+XXqWfHZvZ2KAYlxuSGluTEpUZH9aV2phXXliWUpQU1xUhEtVXJBkepF6fJZPfomXd4p1aFhcaVxzXm1oTW15XGiLnoNzdH10cWJXbZaPW6CXVnKIl6KSgXxxbnxjXWWZfmJmUW9+h3l3eHSGcYlITVhrX517bZaThn57UY9OkHp1eI97d3pdZm+Ae1yJYlBXXmNkZFZwo46VeHNgaWNhcnV4a3OHcmJtalVPUV9oW2BbWF9XXV9pY11cXWlhfnN6bVVhWWxgc453ZmeBdXJvfHt4gm5taGtNWFNckId0g4GKSX9gRU+Ia3yWXFpUXVBiWU9UbG91b4p+eE9mg5eemndvZmBbT2lVgH+AYEpDVlNJTVpEWmteV2RrbWNrdGdRXVpUSkVDUl2DfX95a3qahWZvfW9vi3p3YZJdaW2Ba2V6amBvalVicGRXaWFSY2dSWkxrXlFbT1tcUF5lXF9rdmJoamx0X19sa2lzd2xeW1xmZ3Nqa2JoWVpib2VylXNlaFtXWWhfXGVobWKAZ1hYYlxnZWpoZGZsamllcXN2hI16c4SWZV1VW2d4jINsdG1obWhgWl5aVlZaanBsYVdoaGdfXWBnb3plj41eiG9ihIVpbm9WZ2FmWmFnYWVfX3FwbFlOWGFnYWlcZIJ/b3Jyc29gYnBkU1lcV4N/QlNeZGxdXG96cmRfYWlnXGGAXWV8d2pkam9gXk5RT1VSSlBTVmpyfpuUaW5zWG17WG9pZmBPVF1bW1tRVmFcfYFjZ1lna1ldY1laXWVvYl9OX1tUUFNpXmxme2tZZmhgZICQkHqBbGVuZWNpXlZjWmZkaWxkanVdYGBlYWhuZ19dX2aNcmdnamlhcmdifH+DVWOAZGRcXV9wXFBTZ2JhUk5hbm1oaWtuf4p6dm5qgm1tZnl5eZB+bWVhXFRTS01SZ2RqcGZ2hXRidnJ4bnqdbmxVZl1bWV1ZboV6e25jc21vcWRTT2toZlJWYXp2aXdxamhzZWFsaG94e4V8eIl/dImOj56Uh1CeqYaDkpKDjZZ1lpmAgHF0bV98h5SFgHlxZ3V9Z3d/dWdjX3F8YnyAjZF+c2lriWpkf2Z5h2dfVXhnfXlscHZ+bYCQfXB6nJRUYGp6YV5jcGJvbJFxb3VvqKOEeV9bYV1oXmxnaVV5WY2tZXWpXl16ml1+WmapbnmMd5xhUn6ZmqKAdXt6bHFzfW2aUZWAToeAfmhkaWVoa3RvdHeNim9kbHx1fGxqg5mpbGFjUEuFeHJYanR+eXOOgoSAd3p2dnmVXmZ6ZFVYfoRvcHuCdH2Gf15ueJJddnpnVmOfiHRxdmNsdKBuZWFlV3FuYFtmflVZcHFwaG5iZ3eRhHRjaJqNf2tvZFp7UFyGbm1nY1+AYmBxUHB2VlZebodjX3JpaIZyaVhgZWxgnFRfZ6Jrf5eChp9QiJakgpJ6bF9ibmR4aHZyWXeDaniXqo6Af4B2cmNaaH2FWJGNT2t9h4x7bm5pa4BpZGmWf2pvWnOFi390b2hwXms3OD5OSoBqZImKfnx8UJFOk393d416bXFaYGqAeFGHaldgaGpta1RniX2BZmFSXFdYa3V7cnyShXeCgGxhZm52Z25nYmZcX19kXFdVUlxTcGx1blZjW21fcINyZGiBeHNzfXd0f21tZmlKWVZejIRygX+AQm9UO0FpP0trRUI7QTdGQz1CV11laIJzb0hceYWJhmtnY15aUmxdhoWAalZQZF9UWGdSaHhpZWtzcGVrdGxZaWllWlVRXmeFdnlyYm6KfWZud2xrhHVrWIBTXmNyZmV6bGZycl1od25gcGhZam9ea1l5bGBmX2dnXGptZGRtemVpa21yYWRvb2x2eW5hYWNubXpzdGpwXWBqdGt2l3ptcmRgZXRsaHFxdWyAcGVkbmhzcXVxbW5zbm1rdHh5iIp4dIWYaWJdY2x5h31mbWdkbGlkYGZlYGBkdXd1a2R2dHZub3ByeIFrjodYfmhcfnxga29YZWJnYGRqYWhma3t7e2xmbHZ6d35tb4aEb3Bwc3BlaXpsWWJpZ6qvWm14fYNxbH2Ge2xkZG5vX2CAW2F4d25ocXRkZVZXVFpXU1xcYHF0fpiQYWRoVGt7V3BqbGlaYWtrbGldYGxoiItvcWZzd2Vocmxxb3Z+cWxfb21oY2l/cIB2inVfaGRVVGp3dGNqW1VbV1liW1NeWmZjZGVdZXBXXFxeW2BoZF9cX2SJbV5dXllVZVxXb2xsRVSAWFhQUVdrXldZb2hpW1ZnbWtrampqfIV2bmVheGVlW25xc4V5aWhoaGJjXWFne3N3eW97iHRhdHF2b32bdHNfc2xuaWpjcYFzeG9kdG5yd2ZVUm9ualVXYn1zZXBta2pxZmZzcXR4d3tybHduYnV7eIJ7cEaLkXNyfn9xfIZlh4WAb2BkXlJrdYF1c25oYXR5ZXB4cGJeWWhzWHF2g4h4a2VlfF5XbVZmcUFKRWVUaGtfY2VsXnJ9Z1pifHZGUFtqUk5UYVtoZYVmZm1ml5p+cFtZZGdza3dvcGB8VYegWmSHSEdhekVcRE6HW2uDb5NfUHmOj5J0anBxZmlrc2KJSYwBe9l6AXuGegF7lnqCe556BXl6enp5hnoBe6R6AXuceoV7iHoDe3p7jHoBe9V6CXt6ent7ent7eol7h3qCe/96zXoBe656gnn/ev96hnoBe7R6g3uQeoJ7nXoBe4l6BHt6e3uWegJ7egICBACA1NO1s7S9zsW2sLnCxdD2ktjD1+m2uNjJtbaqpabH2MK4sKbOu8rCt53DwbLK7LSyvNb7lcfKwrrGuqO89M/S4cOMhd+40ry8tbqXmKKlwZqZkZ20v7m9z/iAjLrUw8bQz9KxnJyppJ2aoqGrx8bDw73ArsXc0MXBwLG6v7CAjo2ArJivlqS5tqzX7N/QuMzVz8zSlYiTjomG+oaTjI+QvKrByN+3j8/kzfvZo66xucnv5eXNqaq9p620u7Owpaygoc7mmJeWopHLgvGAhfrj1MS98J2i+MWtsaimlrHPvaarnYf39s/T3N7x/9+5sayptMmTk/zpgoDr5tiviaWqk5aAttTjw7+wur27ttDGtKezp7C8sbijlcy9rbKotrm/06iZmKmvrLGo17qpotawsr6+zrCmi4ilqqOwqr+x1u7VsaDZ6f+B2OLe4NnU1sa+p5+7vMTL2djm6tDDssWMhIDn38K7wLzK9vfVtr2X08LSg6iO27e9waqzrK/Bn7GYnY6Am4+Aqo+OlLq1rbC6r7y3yb2ttMjh2dnRxKOrrcTFyIS62anOv8n32tXk3cTBwee42Krc9cC5rri14M/U19C/zLe7udLRubezuKijpp+roa23tKOhnaiklZmwuLW/vq7MwMm5xb/MytnEvq7AtNe90bKd78ifua+wpKqknaKkts6Asq6ytKi+u7XL0MCwy8nJxMPDvrbJsKvCvb6ew6CfmKyoqaPLuMWpsbetq5eir6antLSqo66qoJuyt87AqLSxsrS6xsbE08GxpaScrrWrtae/sLW1uLnDzs6tvr/PvMO5t6mks52uyKbGsIODt5qYk56vssHEt7vDu8OzrqnErLqAp6mqubilu7OvoJ+3sLiqpKigl5izp6ixvaOvw7yzuLi0sbK8pqmvopmVmZG1kqG/kpmij4ynrqOkkYmUm5aNlpWaqaamtK/Cq6ufnaGHk5y0tb22pqGyw6yUlpaimqekmaSOkZmWm5KBkZiThp+MjbDX1ePLrKmxqrLIxdDoxqiAlL24u73AwMW8qKSKiZCby7SlucnKrqSpjIuZqbK7y9/Hv9LBube9xMW6rp6lr7W6x7qfrq6/vNqqvci7vrvA0M3BsrS9rp+xub2tr5yy2NOztL7Hx8vHrq+ms7W4i464oZ6jtcLGzMzE0c62yLq2zvni3tXP8evZzr3AssrZ57OAwbOrwMDez7nSybi91da8vbitmZShrqusrqyvr6yorcCYy6ayz7zamsG2xMOqwMKurp2ws7+3rcC/2e/ohPPJtbG3u7ezuLO4sqOyua24v8SnusfJxsnCuL7Wz+DXtbC0q67C5pXK9/TlxLPOrcKzq6Pq5c/Zw8jIy7/I69rWw9+AhINoZmJsfHJnYWdscXqXYHtpe41eYIB2Z2tjYWOEkH1xamSHdoN8eGKLgnGBoGhfZHqeVmptZmFuZlpvqYWCkHZeWY1piHN1bXRaXWRpf2VoYml4fHp3fZdOVnNyYmdxa25ZTFBgXFBJT0tRam1vbWNjWHCBdmdpbGhze21JVlWAdV5wVVxoZFVyhn50ZHeGgISMV09eWVZTlVNhVVRYeGh4d4ZjXXGFbZB8UllYXm+Ni4hzU1ZoVltgbWdoYmhiZIWbWVVXaFeHWqlcZLycgW1lk2ZmkG1XY11eSV59bVZTaVKEgWZncnSJnIhpW1dSWHFfXpuKT1KVkIZmTGZqVVeAcomSbGRXXF1bXndzZV1uZ3B2bHBgUXdtX2JYYmNre1RJSVRYWFtVgmxbVoZoZXJvgGheS0dgaWVqZndoiJ+EZlqMlZ9NaXRvbm5vc2toUUxiZm5tdnR+g3BlVWhbVVWXjnRxcGx0mph6YGJldWZzT29dfGJlaVReW2BuU2JOTkiAVU8/bE9PUnJrYF5mYWdne3BXY3KEfnlxaVNaY3l8gFqIjllyY26SfHWGf2lqa4hmfluEmWpiW2FdhXF2eH93f3B1boN/Zl1cXVNQVVBeWWNtal1bWGFdUFRhZWFpaltwZGhbZl5lanpraltoYYdyfGNRn3hTaWNoYWxoY2Jha3aAYl1dXlZmaF5sbF5TaGdpam5vcGx/bGZ4e3lde19jW29mZV6CbHRYXmNaXlBaamJebGliWl1cWFdpcIByYG1sbG5we35/hXRkXFxXXmVcZ1tqXV9bYWJkcXJVYGd4aXNqbGdkbFpldFlyZUZHeltSS1NeXmtnYmVuam5lYlt0Y3CAYmZjbWxbbGVdTkpeWmVeW2VgWl17cGxzfWZvfndsdHJsZ2lxW2ZrYFhVVUttS113TFNfTk9rbmhtWVBYWlhSU09QX1tdZ2Z5am9nZWdZYmh8f4iDdGyDj3peY2ZzZnBwY25fYm1pamBTXmRiVmlaVm6Ge4aBaGJpY2x/foqmhmyAWXt1dXp7dX5zYmNUVWBkjXRleIaJdGdwWFlncm95h5OBeYJwbmRub3FmXlFZYmpxem5YZmR1cIphcnpvamdrd3ZwZGJvZFxsdXhqaFJnioZqbHR7dHRvXmFfa3Z7V1uHbGlqdIF+fHt2gYNrdmdke6CHgXZwmJSDenBzb3uNmnaAgHdtioOaiXSFfW90iIhzeHZwXFpicG9tcm9wcm5rbXphhGdxh3SNa3Vpdnlie4Bwc2Z2dIR6bXt2i52YWp1zYFppb21teXZ7cWNvcWZoaW5QYnJ7dHNsX2WBgo+Oc3J6bWp8mWR3lpWQcmJ9ZHZnXFGSlXJ4Xmlxb2VxnY+Rd5CAeHphYmNwgnlvaXB0dXyaXXttf5Bna4l/cHNsaWuEkIJxamN+cYB9emeBe3aHpHFnaX2bVGdnYFpjXVNmkHZ3gmpTT4Bjg3Jwa3JdYW1xh2psYmVyd3JtboJETWFtYWp4e4BsXmJwbmNaXVhgeHp2dm9rXnSEeGlpbWZwd2tJWFeAc2NzXmVwaVx2h390ZHSCgIiPYFloZmNeqVxrYmBcf2x9fY9pV3yQeZB+WGBiZ3eXlJOBY2p6bXJ5g359c3dtbImZWlVWY1N5TotMUpuId2ljiFtfk3VibWpsWG6HdlxUXkpzZktKT1BfdWxWVVVSWG9UVpSITU+Rj4dpTWVlV1mAcoiSdm1jZ2RoaHlzZ1xqYWdwZWpcT3t0aXFqeX6DlGxfXWhraWhhjW9bU3peXGhkc11UQkJZY1xlYXNniJuGYlWDjZtLbHVwdHV4fHBuWVNoaHBwenR7fmdbS1hMQEF1bFNQUE9YgINnTlFYZ19rRF1UeGFkaFdhX2BrU2JRVU+AYFpLdFlZX392bG11bGxpd2taYnOGgYB5cFxeZnd3d0xpgVltZXKXf3WBemNhYn1Yc1N0j2pmYWhkjHx/gn5zeWlsZn5+aWNjZ15aYVtoY293dWZgX2hiVVlnbGx1dmR7b3RpdWxydIN0cWNvZot4hWxZpoRedG90bHRybW5seYiAcm5ubmR0dGt5e2tgdXNta2poaGd6a2Z6e3xjhmlrZHRpaGKDcXpgZ29oa1pldGtre3hvZWxsZmJydYd2YWhkYmVpdHZ3gnFlX2FcaW9mcGR3am5sc3V4gIFjbnSBbnVsbGRhaltoeF12a1FVh2pjX214eIF9cHF2bnFqZF10YW6AYGVjb2xYaGNdUU9jXmljY25oYmN9cG5vdl1md29kbGtnZGlyYGlyaF5aW1R1VmmCWmVyYGB9hoGGcmVsbWlhZGFicGtqcm+EdHRqaGdSWV9vb3RvY1trd2xbX2VtZ3FvZGpXXGZiZl1SX2RhVmRVUWR2bXZwXVhcV1xsa3OLcFmASmpobHN5d4F4bGxdXGFmjXhodYKDbWVtVFNga2x0fot3cHpsZWJudHVvaFtmb3F3gXJcZ2Fvb4hfcHtwb21xgX95amlzaFtrcXFhYU9mh4FrbHJ2b21sXmBbaXF4V1qAa2psd4F9e3t0fHVeZFZQYH1rZmBdfoBwamFjXmx8iWGAaWBaZWWFdmR3c2luhIdydnJsWVZdZ2VjZmJjZmVjZHFQeVxkd2Z+V2hbZmhacnZqb2FvbXlrXmljc4J7Sn9eVVRgZ2hmb25xaFtmaWFnaGxTZ3h+e3lxZmx+f4aBaGVlVlJabkhceH56ZFt4ZHhqYVWGiW5zYGlvbmJskX13YXyPegF7o3oBe416gnuWeoN7vHoBeYt6AXujegR7ent7hnqCe4x6gnuPegZ7e3p6e3vNegF7l3qDe4x6B3t6enp7e3uteoJ7/3r/ev96/3oEenp6e4Z6AXuUegF7p3oBe5l6Ant6AgIEAIDY3c7RusLQta+7tMLX3sfOx+Su4K67wLGlq8qhnovK4MKtoJu3sqKb2t3Al7K9ra2szNPt7fn+4dbRvMzUytvi7d6429bYt7KpubaMkJyjo56bprG80NnJrc/ym4Xy2PSHwaytq6qjpLiuurC1q7C338O4ufzkztPJsqalppu2roCcnaixqb3IrdPF3e+8x87qv8im0riYo6GaoZWPjI6rq67Bu8i1usTa4tXp0sbHwbbz7NDmxLbIuqepu7ezpZuasePWk4SqsLbAoJ3I2OrKvd7g6uT+rKSos7unw8u4qbPH45H82v2N1cCz3by8p6SXnqbC8tzRwcakkJG/rb2LvYC8y/LRytfMr4XSyrmxtbbGttLUwbvEusi8vbCqv7Wgp5WIkaLywa6cpa+Rqs3Q0764t6y8urWqw8y+wbLZz9Ta0e/z3M3W1cW5uNjnu7W2trnAyMC949fdyMvWz9nb2tjRubG+5+TSvam2wc3FtN6V9+S8saSqsrK1saafjqahuYDCn6mrmZ+wt7+2trXCtLy+08W7vMzE2unVvay3u7LX5cChp6K/0enQ5bKsnsXswLW/srvGvZ6QhqKu1PnmqrLCs823ta/Bvc7RxMXU1LWkwKOknbCboZ6dur7Zu6SkwOTDram7wcLDz8SkpqysrLq7vbW8vKmeoaqepbCztauiq4DPs6rE1dnBzuOxsM6yz8O/wc22oqOt2K6rm52zlpuKpqSru9WfmrCvr6Shnaiws6CjrbSsrrG+trvNzqywrbbBvrzEytzTwbSwr6S3pp6ara23r6/Hs7u9sqezvLvU0rikobnrwbSlkcTZsLewpJ2foaLM1cO4vL6uxM20psXau4CvrKq+ybPrw6+6ps/gsanDwsamucebk4aarqfHobLEuLmnuLi2pqSyoqSfpqnF3LWrvL+omqCz4J6DmJqVi4KDkJCrpJimrL2xu6ykrbW4orjPxZ/Fz6qmnI+cnaGampKclI2Sn5yWo7KRmJKtrp2Jnpufsaa2ub/FubC0scbDrYCsvLPArrSfm6ixkp+ruKqXrLO5q56fq7ax0aauraGqwqy7yLqqr7its6OuvZOjqrCdrrusrZq355qgvsK8uL/d0LzIr6/Aubi1t7KwoLDR7OTWz8TkxsjJr82snK6gqqqyvKfHws/ZzL3Uvb3gkOLE0NXTxcDGyNLMvcaztLbByIDNtbKYuuzMoOnKyK/Fhsy5v6arsaqtpLK1nrG3uLWtoJikorfz4u765+rHysrNvLWup664tbmlvc3W5+WBxbzL0bjKw7uyqqqin7bCx93by9HNvd3w2MWvvMC90L/I5ufBy9nozb7J4IWzub+ppqyrtrnQtci6zNPQvsyU38nFz4CEfHFzX2d0XlliV2J2emNoX39uek9cYl9YYIJdWk+GnYBtYl52cF5YnJ+CWnB4ZVxbcHiGhoyRe3NwX3N8eYeMno9viYiNc3FpcXBXXW1ycnBrbXh9iIx5W3ePXkx+aYpQaVxeW1tTUGRYY1tgVVhhfWNWVo2DcXp2ZFtiZ2F7c4BkZWtvZW94V3Nme4ZjbHqadn9fhW1UXltbYVtXUlFsZ2ZwZHNkZGh5gHiPeGlpZluOh2p2ZFtoXk1WaGRkWFZZcaOUV0hlaG16WFZ9iJd2YXx8hIScW1daZXJhg4pvX2RuglSJbZdje2xnh2NhWVJGTVFokIFzbHFUR0x4ZXVKdoByeJVxaHNpWkZva2NgZGuAdY6Od3V4bnhpallSX1lJUEQ9RlinemtYY3FVZ4mJi3hvcWZxcXFngYR6gHCOfnyJgJmYfHB6fWpjXnV8YFleYmVsc2digXN2ZWlvcHqAhIWBcm55oJqGcV9lbHdpWXpgkYBfXFBXXGBoaVxRQ1pTaYB0VWNkVltncnltaWtxZGl1f25pZWxjeol+bV9rcGqHm3NaW1Jpc4h0hl5YT22EcGdxaXd9dFlKQ1ZbfpyNYWZyZn9nZWBuZnJtZWNycFdRbFpbWWlZXFhZc3WJalhVb49uWllna2loc29XV1hbYG9zcmt4emZdX2dfZmtsbWFYXYB5X1VndHZhanJTVm9eenByeH9tXl9pjGdkVFNnU1dLYV5lcoVTTVxbW1hXVmBraVZXXWRaWV1sZGp6elthYmp1b25yeYx/a15fYVVhWFNSYFxiXWB0YmZqZF1kcm2BhXFlZ3uwhHZkTniMa21nXlhWVVVzem5lZmlecX9yZYSScIBkX1tufWqSb1phUXSEX1hwc3xleYlmXk9jcmyKYHGBd3Fhb29uY1pkWVdSV1Vth2JYZmxcU1xrmmFNXlxUT0hITk1nYFdianlxfnJpcXt9ZnyPjWiNk3ZyamFlaWlhYV1nYF1bamZkbX5kaWJwbmNTZWFhcWhxcXl+c294b4SAaYBjb211anJbWWdyXWl2hW9ga3uCdGZka3Vxlm1sY1xicmBqcGJaXGdfY1xqfVZlbHFjb3hqbVpxnlpgeX1ybHGLem99Z2Fxa25wcm9tXWaAlZOIgnqWfn15ZIFkWmlfaWlueWF5doaLhHeLeG2JXYhvfXx2ZWNrbnNzanNoZWh1f4CFdmpVcZ+CXJB7eGh4Xn9veWdqcGdxa3Z9Z3aAgHdwYFZeXWqbio6ahYtsd32GcGpsZWl3fYJqeYGJmpZXc2p1fGl5cG1ub3NnY3R7eYqBcXdzZIWahHZmdHp3iXyGnJt5gY2YeGR6ll1obXRgXFtYWVNqWWtianN2aoBjmIJ5fIB8e3B0ZG56aWVuZ3F/g29zaYRshFxtdnFqcJBqZ1WJmn9tY11ybmBckJF7ZHh/bWNfcXiGhoqLdGtkVmVuaHh7inxfd3d9aWhgbnJaXmlrbGplZGtud3loTml+U0iAcI1Mb2dta2hfWWticGlvaGxxh2phYpCGcXx1ZFxgZmF7c4BjZWtuZm53WHBmd39faHiZeYVmjnpibWppbmdlYV98eHaAc35ubnKAg3+TfXBxbmmbl36DfHeLgXF0gnt1aGBgc46GV0pkZ2pzWVZ2gYtuYXt8hIWaY2JrdoRzjZB0X2Fndkt3WHVWVUdHa1JbWFNKVVpznI6CeoBjWFh7a3dPdYBzeZd8dH9zUkV5c2lhYWR0Z3t9a2pybnt1em5sfXdpb2FTWGSoemlWX2dIVHJzdmdiZltlZ2ZgeHpydmuKeHZ+d5KTenB3dmljYnyDaWNnaWtwdmtkfm1vWFtjYmlwcW9oWlRegoByYlJWXWlhWHZWiHtgXVFYXWFoZVZNRF5cdICAX21vYGRvdn50c25yZ2ppdGRgYmxogJODc2FscW6ElXBbXldreIp4il1US2mEamNsZHF6c1xRT2NpiqmYbG15bIRta2Vya3h3b2+Bf2VdeGFiXmxcXVlbcXKGbV1cc5J0Y2NyeHV2hn1jY2RkaHV4dmx3eWdcYGxkbHR4em1kaoCHb2N3hIdwfH9fYHpjem1qa3NiVltqkXJyZ2l9ZWlab2pvfIteV2hnaGRjYGdycFxgaW1jZmp4b3F7fFteXGJsaWlvd4uCcWdobGFwZV1ca2lwaGl9bXZ0bmZtd3CDiXRkY3akgXNoVX6RcnNybGlqaWaAiXtxcHJjdH1uYXuMbIBiXVlte2mSb1tkVXWEYFx2eH5pfotnYFFgbGR9WmVyaGdXZ2pqYWFsYl9cYGB4kG5ldHptYWl9rHlkc3FoYVlYXVlwZ1xiaHpyem5laXBuVmd6dFV5f2NhW1RcYmVgYV5oX1laamZjbXtjaGFxa19RYVxea19nZGdsY15mYHBrWoBYZWVuZXJiZHF3YnB5hnNgbXV6bWJka3VviWduamNpemVyeWxhZW9maWJvgVppbnBha3NpaFhwlVdfeHtxbHKJe3J+ZWJza25ubmtpVl50iod8dm+Fb3FvWXZaVGpibG94hG6De4WHeWl4YllzTHJaZGRhV1dgZWtqYWpfX2JvdIB3aF1IYolvS39sbl9xV31tdGRpbmVrZW9zXWx3e3VvZFlhYG2ZiI2VgYJlc3d8amlqZWt3eHdfbXR1fntIXFRhaV1vbWpoZ2tjXm1zb313aHJ0Z4OYgndmcnVxemxwf4Vma3N8YFZth1Rnb3hiXl5YXFhuXm5ncXh5anNYhG9pcpJ6AXvIegZ7e3p6env/egd6ent6enp7oHqCe+Z6AXv/ev96/3r/egJ6e596AXuuegF7q3oBe5J6AXuEegICBACA4ufM1s29yLa3t7DJ6NzasbCx9NWuvsLnwaO3pq6q2sqL0Mu6saOXnY+YhoiQqpuuxNTIw9zH48vL3d/+//2F6t/U8OjJ1Lq3t6yrlaebpqKZnbLL4MPU37PE18vw8OvYw7vYwrnFpaC7tLKrvbu0wPbT2oDn3snEyKmnlp2dmqWA/uy6tKrn1Pie99bAwtTV6r7S0PHv7cy0prCetaeiq6epzsm4qb3Lv8HEyMuEidOo+Lu4rcu5stDd19DJtLKymKiroZmXo6q0sbi+vNfg2sfKtOTArrOZgYqTnr3St7LJqrTBzs7308Oh+9uVnbKTq829t8CzrbW1u6GeuJneu7OAtr3P7rCnyaeM0c/JusrZxr3i0dzZ2sG8x7q+qbOlqLKeoI+Xnq6+q8mEuruAxaWfpqjH6cC8rMKvpKi6wbnG3+DL5v/WyLCxvrHAzb2qs7u2ts2ssdTC4dvLvPOy7MW2wLmz2rbFxq+1u5upray0orW0r7HIx77p3MqXpaipua2ArbahqbKqpbKuy8LRus7Yycvh2NTe8vfo68rlzM21y4LZwbe1tK2pr+yuoZyvxrWxpa+4s6mopqWPiJWnnJKNsKeuq7C/x7e5xYDZwsatrLK2raSnpJ6hqq+rqrGsraWwwMS8saG6va2qpKGuoqCsnqq5o6WnoKaiqLHAsMS7v6+Awai1wq3JvbzcurensrfCtaq2q6SxraKcqJ/ArKmspqamp7W3r5i2qKSkmqaorrG4tKmesrW7yMa3tLeyurautr2/ztXGzNSzoaumtamgoKy/ubCxrbC5rKGrrbO5sbXBxMPOwKSkiYuYmrK+say0tryztM/szb3Cu8Wy4NPP06+AobO7zt3HsterrcDk1ru+rbK2vOHJo8fHmp2hoKWmtLLGqbXCpqqor7KopLytsKituK/Rs7qjkZyLhpifmJ6VmLKwzLOuqa2/ydjFydLh0czCwYbH3b6os8atoJiLjfSBmJ6bop+bsq+bnpCFgIOQkZGRp7Kts7/IysDJza6wwLyAwaWeqaWoxd2IwpantKy/n5yXnZ2hu8C8zNy7vKaYt6apzcqvsaGapqe3p7un2Lywnqq3wNKxsqewv9XGv7y0wb+ttLesrbbIzLS5x6Shl7e83N7X29vb1uzApZ20tKzBvNTc3Nzn2dzazd7tzdTe3d7Yy8fhyt6Dz77GvdnXxMGAuKbRz7y8uaG1zLy2zLahzMqnlKGspYmWio+mrraVvMavoaOOpsu7u8DNtMnIxbmpubnExbjiyLr5x9LBwbbF6MetweLNuLa6rZ6xwL7X7M+G/oqS3tnAs7ulmtXftra41MS/zNTCuuTXuNbAvMHB28uzxba7upPTusOuwMip0OaAe4hzcWphbGJjY1lpiHtyUk5PinZVZ3GYeGByY2ZhiYBYf3xtZ2BYYFZeT01RYFVfc3hsYnhmfG5qdnqWnJ1UiYeGo51+iHV3dnJxY3JocHNmZ3yQm4GLiVhda16BiYJ6a2uBbWF0VE9nX2FWZGZdZJFnbER3dGpweGVhWWBjYWuAmYd2bWCDdotcg2tYX21zg2h/eZagp39pYGZbblxZaWRfenJqXG96a2tvcm1NUGtKhVhXUG1hXXmAd3N3aGxsVl9hWlJQXWFqYGhoYHR5dGVrXIhoXmRQPUpZYHqVdGRtUVtrdnOZgmxMj3xGTFlDWHlrYm9jYGlob1xbdVyWe3OAdXZ+mlpUa1lLcXJvZHKAcmuGd4WFhm9ob2RkVltRVFxPVkhPV2h5aIRienxgfFtWX2R7m3dxZntvZ2hxenB+l5CAk6J6c2JealpqeWhXX2ZlbH9ZWndkhHxuYJWDn31yfYB9oH6MinR7fVhmZmJsXG1sZmaAgHiciHtUYFtaZVyAZWpaYGVhX2dleHOEb32Ie3d9d291hJCFjHKLd35vjGKReGxpZ1tZXI5eVlRmdmdjXGZpYlpeXV5MR1RjXVVSbWJnZGZtclxcZEt1YGtaX2lsY2FoXVlibHFqYGNcWlBaaGpkXVFmamJgYF1kX11nWmNwXGFhXF9fYmtzanRqbFuAaFFaZlVuY2l/ZmZWYWdxZ2JuZV9taVpQV1JsYWJnX1xaV15gXU9nXlZaVmNiZ2hubGZcbG1ufntuZ2Zjb21obXJygYd7fo5oWmVdaF5WVWBuZmFhXWRuY1xpbW1vZ3B4fn2Mhm5oUVBbXXR6aWBkYWFZXnWPc2x2dIFymoyIim2AZHFxfX9rWnNZWGaBcmNnYml0dpZ/Xnt+WFxgYF5hb2x5Y2x0YWVfZmNXVWRZV1RZYmSGandrW2leWGNmW2RaWmNgfGJhWVpndINvdHyMfnx0dWaGnHlld4l0ZWJYW5VOYmdmaWdhc3VkbGNaU1JfYV1Yam1nb3l+hH6GinNtd3WAfmZiZGRpg5RkiVlqfHKNaWplZ2RleH1+kZZzdGRab2JlhYFpa15ZZGZzantsl35yZWp4gI5vbWRwfpCDe3dvd29eZmdmbHJ+fmhqfGJfU3Buiop6foCGgJZ2Yl1uaGRxbYOBgICRgYZ/cYOYdIGFh4Z4a2h/b4BPdGhydIqHfXqAeW+OkH1+eGJtgnRxhGtXfYNrWmdwcV1qX2J1f4hljI92ZmtVZIRzb3KAZnVwbGRhbGd1fnObgnevfH5wcm59nHpda4p5aHB4bWR1fnF/j3NNkVJohIZ0bHZkX5ugd3Jyh3txfYFkYo1+Z4VwbG5ng3hcaGJkYlZ4aHJheoJlhIaAeYN1eW9ocWlscGh6l4uFYFtelIRjdH+dhW6BbnRsjopZhoRyamVcYlxkVlVbbGFrfH90bHxrgGxpb3CIiohJeXd2kIxwfXBwbmpsYWxiZmZeYHCDj3V9e09YZ1x9hX14cG6BcGNyWlVvam1odnZqcJp0eUmGfmxucmNjW2VqaXGApZR9dWmKfJBeiXJgZHN4im2Df5mmrol1bnVqfm9qeXJwjoJ3Z3eAc3J0c3BGTHJchHJ0bYd7dpGYkIiEcG9sV2JjYV1daW5zaW9vZ3l6c2JpWohtaXBcSldlbICWeGptUlZjaV92ZFE5eG5HT2BIW3xwa3lvbXZ0emhmeV6Ldm6AcXJ6j1tZb05Hd3h1aXB9bmZ/c4CFiHl3hHuDdHtvcnpqaVpdYWpzYHZVZ2xSbVVRWFxzkHNwaH1xaWhydmx0h4FxhpRxaVhYZ1psemxgam5ucopiYXVjfHFhUXlZe2BZYmRhgGZ1emltb1FfZGVtXm9waWZ3d22JgnVNWFpda2aAbnVjaW5raG9rfHWEbnd+cG12bmpzh5WMkneLdnxuhFqLeHJua2RiaJZjWFRlc2loYWtzcWdraWtbVWFzcGZfdGdramt4f25wfVWMeX9tbnR2bGZrYV5lb3RtaG9pamFreXp0bWFydWpqZGFoYF5pXWdyXmJlX2ZjaXJ8c4F3eGqAdmJrdWR+cXWLcG1aZGdvY11qZGJ0dGhibWmGe3p9cm1pZm5xbFlwZmFkXmZlaWZwbGVaam9yhH9vZ2Zia2ZfZGtteoJ4gIxuY25qeGtjYm19dXBwa3F5bGdwcGxva3N5fXyKgmxqVFBaYnyFdmxwbnNrb4WYfHJ4dIBxm42Jh2aAXGlrfIJyYHhdXWWAeWZsZW12fJ6FZICCWlxfXVtbZ2NxXGdyXWVjbGxgXm5jY2NqcXKUe4p2Z3dwanR4a3BoZG5pgmlmWllrdIRydHiDeHBlZFNwgWpYZXpsYWFXW5VOYmZiZWRgcnRjbGNbVlZhYV5YaW9la3V4e3J2d2RhbGuAdF5eZ2tuiY5diV5ufXOHaGljZWBfcXNzh5F3d2tleWtrh4Nra2BbZGZ3bX1rlntuYGZzfolsbGJreo6AeXRud3Nla2xqam95emRrd11ZTGZle3hqbG96dYhoVFJmZmFzco2SjYaTgIF0X2l3WF9gZGNZUFJmWmxGZ15maYGCdnSAbmF/gGxqZlJfc2djd2VTenxmV2ZtbFhkWVxsdn5igohzaGpXZoF0cXR/Z3dvbGJdbWp4f3WXfm6bamtdW1Rkg2ZQYH1xZGlxZVxsdm17hmlHiEdEfH1zaXJgWYuCZmFieG1mcnJcXYiAaIJua2xmenRjb2pxb1J5anFicXhieoKgegF7nXoBe7R6AXuMeoJ5hnoBe6N6gnvZeoJ7oHoEe3p6e696AXvCegF7qXoBe/96/3qKegF7i3oBeal6AXvuegF72HoEe3p7e6J6AXuJegICBACA0fDm+enQxLm9vLDC89bHudvKvrSvvbe2tZ+dkJelw7fQ1M3Xw6vnh6mT85CWhZicip7Q1brN1c/Nz9zH1N3k1drbyNOwuKauqre4qaKRmpmdrLS3zc3j08zLw8LhsaDxt/771e7vuqStrMPF1cHG2dbN59rL1dWxs5X1sbzWwISAgYPxjZuyv8DK3fz429HO2M6Mj+by8/HJwtSyxb+usa+LtL+muMnQzNXEzcyFnse5iJX81era0Mi/4/n7ytnbuqSUkpKmsLG+v8DU0+Lp3/XZwbi7uuaNuI6Vtb7BnbOhq7S+uMLMyLKpnqKrnc65sMWprqukpLnCn5mapZ2LsZKAocKBj9m2wczq5eTI18fKvrvDztDGxb3Zv7/SvZq1yLzApaGgpazDubbDtLS+vNW2sZyPw+DKoKGbnpmturW7uKjEz8C/xcK5lKCho5ihrMSdt9PKwL/M2taP94XD2bTE3b3Itb/Erbm0r9jEs66sq7O0z5GV0LKTjJSZjIKapq+A6M2rzrehmqq2tLjFwL7M1u313+GFjIr9ktnAxMmypq6eqLvDrKirxsKwxZuevdjRrZ/KnsPgrbSxna6zt5mTnK6dlpuyv72+obHIxcG4tKahpretraGrxMjwqKiXmp2doKeco6eoiautnq+jqbuxrKemtqahprGttq+0sqqcsLWAz6G2zce219LJwK+zw7m2qq+llp+lqaGinaOnrKOvoJ+nq6S2s7Oquayzs6qnqsS+taqqr7C9wc3DtsC5tLucpquttLm4xc7byN/OxcK8uLaenqKkram6vrO1sbSutcO7ss2hrLOWlKCPkJ/H6tGroKnC2Ovm0sS7vKq41se0sb+AwsizvLfIrK6vr5+ntqO1t7+zpKSxkZSqq7WisK6qpK+pt8zvmqG0s7qYnaSarLjDxszo0peXoLCTkZCQi4CQkqKklqiqsbO6srbG08jGt6+qseG4q5aZkKimndulov6ap6KjlLSylKKYmpmgkpmRk4mhmKyvtKazq6G0rLS7rbKAqJeKqK2mmaK4qaepp6GTmpSYpKaeqaukt5yYoqSqrq+ilJyop6ajn6mgqLS4k6yrtq2hl6agppuZm8WzoqmkqKausrmysLnPv8fIwNW8tbKpv6/D4NDqit3MtrGsrrHJhuPe0uDo3ODb5+LMz+T3/9bX4+v3/Pr419ndyNryyOCA5LbBsLiwvK2s1Jmzq6rFp6uVpbGsnpiWiY6rm++UqZ6dnpyQwKmar6++x7qzr6y+7cPNwLPC0+Lz78nEz77QgtbDzcCxtrG0sOeq1dj9iIGGhvzw49zMzsy3u8Pjy7Pf49zT/M3m1c69zc6+3M6nqaSlvbS1trvSwpycuazIvdqAe5B+j4V2cmxwbGBtmndmWnRnXllbaWtmbF5aVFheenCCh4KLfGmlZ21XjVpfS1pYS1Z7eGBpdG5ma3ZocoOQgIaGdolnb2ZxcH16b29gaGRlbGtsgHSHdm5tY2WFX1Gcf6ypgpSRZlNaX3J6hmppcnFld21keINfZWGYbHWPf0+AT0+HUFdmcGlseo2UfHFyfHZdYYiVlo5qa3pjcm5gZ2lJanBaZ3FzbnRob2xTYG5hUl6ddIB2cG9nhpWbdIaFbFhNTEtfZWBlXVpsc4aNfJB+Z1xeXoA/Y0xSbHJ0VGpbXGJrZWxxamBXUFJYTXNeW3VXXF1WWXB1WVlaZF5NaVGAXXFOU3RdYmmFgIFufHF3cXJ8gYd3c26Ib2t9bUpec21uXFZWW2Z5c3CAd3d+do1vaVRKeJOIYGFfX1htcmxxdGh6g3V1eXJsTFhcYlleaIBacox6cW97i35bl1t2kGyCnYmVeYKHbHd3a5aEbWZkYWdqfGJmh29WS01SS0RWX2KAmIJmh3FbWWVnZWVubmx6f46Jbm5GSE6LWYFxdIJvaG5fYHV9ZF9idHRjdlVcdYqGZlyBW3OKXmdkUmZyeWJdY3NjWV1sdHBrTl1wbGtiZWNlYXJnbGFmfIGlXl5OT1JRU1hRVlpeQ2BhVWRVW2tkXV5daV5aYGdob2lubmRYYWCAcEtZamRXdHBjXFRdbWdqY2xhU1xfYVxeVVZcYVhhU09SWVNfZGZhb2RrbGFfYHdwbGFmaGlyeYV+cHdvbWNTYWNgZGlnbnh+b4B1bGpqaWdUWFxlbGd0enRycHNvdH14bYRga2tVUWJYVl5+n4ZhVldne4eBcWlnbWNykIV0b3eAdnhmcG5/aWRkX1NbaltucnxzZ2p4Xl10dnZhZ15eWmFcZnibVltpam9OUVRLWmpydYWekF9kanxnaF9bUklUVlxbTl1dYF9iXmVveW9xZmJXXopoX05YVG9uYZxmZ5RgbWpmWnN3WmVfZmZpXV9XXVdpYnBzeGx4dGt7b3iAdHiAb1pOaGhgWF5xZ2dyc3BhZFhbZmpmcXBhb15ZYF9kaGlfW2NoZWhranBqcX6FYnl3hHxxZHRrbWdnaYh6Y2ZfW1pkanNsbH2SgYSCeYhrZ2ZgcV5qhHOMV3pxY2FgY2l+WYl/cYGEfYGAj419fI6anXBtcnqDh4yKd32Geo6gf5eAonqAcnV0eGxog1ZqY11yXGJWYWtnYmBeVldtZZVib2ZiZmdagWldbmx8gG9sZl9mh2uCd3B+ipKgm3p2gGd2VYBzgG9hampkZ5pxhoupXE5RUI+JgYB5hYZ1fYaniHCblouEn3mSgHdud3lvj39cXVpWZ15fYmiDelVXcV1wZoOAc42Cj4Jyb21ycGdzoYBxY31waGNlc3Vzem1oX2VnfneEhYKHeGibWGxdmWBkVGVjUlt/fGVudmxlaHVncH6KfISEc4FgamNuZ3FuaWhaX11gaWhldGx8cWloYGGAXVKMaJORd4mHZ1lhZ3h/inV5hoJ3h392f39gYl6XanWPhFWAVVWWWmJtdG9yf5KReXBwdXJVW4SRlJFzdYVvgn9zenpZeXxncn19eHpxdm9KWXhtSFOqhpCFf3pxipSOc4OEcmNXV1ZpcWxwaWZ3eIaMfY6DbGJiZY5MbVZcdn58X3ViXV5hXGRmYVdWU1dcUnZoZHdcYmVgY3qAZWZnb2VSbleAYHFITnNjaGyGgoNxfG1xaWlxd3tzdHKNfYGVhmZ8i4WDbWVhZGx6cWx6a2tybYVvaVhSfpyQa2tjXlhqcmtubl53fmxsb29rS1ZbYllfaXtWaoByZmJsd25Ngk1ngWB0jXiDbXN4aG9sYoh5aWNfX2NleVpcfmpUSkpQS0ZaY2SAlIFrjHhkXmlva2lwbWt0eoaDaWxHSk2LV31scHpxa3ZpbX6EamVneHRjd1VYcYmHaF+HZ36Va3NvXGx1fWdhZ3VmX2NzfHh2XW1/f310dGtnYnNpbmZrf4WkaW1fZGVlaG1mbXByU3BxZHFkZnRrZmJeaV1aXmZob2xzdW1gbW+Af1hmdnBkf3xvZl5jcGZnXWlhWGVudG9ybXBzeXF5amZoa2VvcnJrdWtxbmFcWWxoZFlcYGJtdYF4bHVsa2VWYWZla29ud4CLe4+De3V0cGxcXWBlbm13e3J1dHlxeH94cIVeZ2pVUV9YXWWFo5BuZmZ2iJGLfXVxd2p3koR1cXuAfH5qc2+AamhpaFpjb15tcXlyZ2l1W1ttbXBdZF5ZVFpVYXKSTVlrb3ZWW19WYnN5d4OZiF9kaXhtbWdmXFFfX2ZlWmhnaGdsaG56g3h4bGRaX4drZFNYUmhoX5ljY41dbGdhVXF1WmhjaGptYWZeZFtuZnVzdGVvZ15tZWx2a2+AaVlQaWtkW2F0aGdxc29hYlhZYmNdZmpjcWZlbm51dXRoXmFpZmVnZWtobHh8X3Z0f3htZXBrb2dna4x+amxlZWVsbXdvbHmKen98cYNoYl9VZVJddWR7TXBnWFpcX2V8Uo2Fc36CeXpzenBZWGdzeVNSX2lzen5+a25yZ3mJbIGAi2hwZWpocGNgfFJnYV1yXWRYZnFsZWFfVldtZZZkcWtnbGpghnBkcW56fnBtZV9nhGuAeG95hY+ckWpgaFJiR2phbWVZYmFeX4tde4KgVUtMS4N5cXNud3hrcniKd1+Bgnl0jG2Gc3FqdXZognhdYGFhcmhrbHOMgF5dcltqYXGnegR7enp5tXoBe5h6AXuIegF5jnqCe5l6Bnt7enp7e9B6gnvVegN7enuXeoJ7n3oFe3t7env/ev96x3oBefh6AXuIegF7u3oBeaJ6AXuKegR7enp6hHuuegICBACA0uSVgvLWz9DQ0q65x8C/stn01snBo6utpp2Wg5mjs5+cpq6xt6TRqaqkmJGXo56dn6KRpL257tHDwcfAxcj+gvfo1tTpxcHBv8XNxcK7wtrv1unruKzCxoOBp56htcfP94rk38/Jub+1sLyx6c281MTEv8PU2MHVvLjPw8/Q99yAxbyroszktL+6qp22vrHG0cmHuI6L8ILP4sC7v8CznaKNh6Gvo7DB0Pa8ycG9w9DK3Y2FhYPm2sXl5LW5xdrR3J7HqbHI5s3Z6NnczcHK5Ijw8c3EzNeftaOtqK63p+61nqe3vtC6rau6xsS9ubS+qJu3zbm6yrvPz7OpqKKlpLCA48ewuLzSzLTC38/X08DZxb6pg9bS2sW6uLmqqajG0MHdvau0rKKNrratsqmcqqqumZeJho/3ipKQmaitvsnGvcjktt2xo7e5r63KzM+b1sisqcTv09W8vNr/uszJyanSwpqap7KzoZmjnKugob+porCvzf7JupeK/oSVl5+ajY6AmsfVhq6wsrK1qqS5xNO62tTR2s7Y1cPHyp6doq6kqNDVycOwzbSzwqCpusiossK9p62mlqa4tKihs8O2vau30qualZWfmdKxnZessre8tLmblaSsqp+npqWlpqqSpJ6lq6arpLGto52irai2wa+suKeqnamzoaqft5yf1OrN0sOAubbBtretxsCxnNTGoKuflKSYp6OTm5KipLOqsZqbsKqcrqKlubOsxMu9ybW4p52hnZSvuKCyu7Ssv9LGuavDt7nDwcfAta+2q6K8xMKuqbGtvsG0o6mssbm4rqWzrqOqwc6sqsO2nJmSnZOiwqqbp7LCxsDUv8W5trW2rK2enbWAw6arwrayppegvqOaobSMnJavvLmpnZ6ko56HnJ+ltLXLyNC/yMy/n6aVjJunrb+zs8bIp5uOlJCYmZGjuqmZk7PGsp2uuLeut76rvbu5raaemaWYk4+VjZOXlaGQsJGUj5ito6K5zqmZnJKQoqeXmJm0tc3AvsHFva+jprm+rsSA37ufoaursbaepqigoKKRhpORmq2imZibrpqbm6usk7S0trisr7eYk5+aoqqak6q2w83fu6SlnZ+Zj56doaennKSysb/DsKa51bm5pq61tbG0ws7x59zV3M/Nvq+tq7Tg9tzTydHBzcbbytnTus/i0OvBwsfR7fjq39/t1Ozx+NKAica4s7m1wLq4x7a+rL6os76ps76jkY6JgpmWgp+i/pKXjIr3m6KHjoyhr7Opop+7qcr1sqqzpsvG29nDvcbP5+DR6NK9trS6ttK8y+D44M7Ryc/P0dnH3NG4ybmcsaO08+D047+EuszHysq+qazbp5yrqaitqamlxMCp+bGru8eAeYdkV52DhY6RlWpveWxlWnWKcmljUV1hZF5ZTFplc2BbZGlodWOTam5uaWFlbmZhYWBMWWpjlHZsZ2hhZG2UTpOIfoKae3+Bg4eUiYN5gZSniqClc2V1eFNSXFlcbH2Bp12UjXt3anBua3JolHhod2ViXGJ2fWNyXmB8coCGs5qAjIBsX4iXbnJtXE9kbV5xdG5VgFFUhlB2imxrdHVpWFxJQVJeV19oc45gbWVhZmxmelJTVFKNfWmHiGBpdYeAilB4W11whGhzgnRzZWFtgVF+fVxibnNJXFRfXGZ0Zq5sVlljZnVjWlhkbGtfW11vWkpfdGhodGx/emtjYl9iXWyAmnpjZmd7cmBqg3l/gHKKe3JxWYl+gnNmZ2RVVFRtdmx7a1pjYV1NanNpc2lbY2BbT1FJS09+TVRVXGBjbXN5bnmNaY5vZnNyaGWBg4JQgHVgW3KWentmaYWjZ3yBgGWMgGJjbXV2ZmBoX2xlZHpiTlledZ10a1BJh0hUUVRUTVKAW3+HVGRramppYVxrcoRnenFla2VxbV9jZk1XWWdfZY+VgXhne2Nkd1tlcn5mb312ZWpgUFtsZ15aaXl2fXN7knJhXlllXIhqVExhaWtzZG1dYGlpYFpiZGNhX2JMWVdcYFpfWmViW1VWYVxmc2FiaV9kWmZvYWlhclVTeoRsbF2AWFVeVVtUbmNXRH5tUGFYSltXY2FQV09XU2NbX0xQXldPXFRUZmhjb3Zoc2lvZWBoamJ5hGt5fHNueot4cmF0cGxycXR0ZWFkXVdrc29eWmNgaHFsWl5gZW5uZ2RtZ2FodoVnaHxsV1NOW1Fkg2hSVmBzeXCBbW5kaGdvZmddWWmAeFlbdWdnYFVfeGRaY3NPYV97hYZ2aWdsbGFQXldgaGV5bndqdHVuVl5RTFlibIJxdIyMb2VdYWFqaWJnfGhXUGJvXk5eYWFYZmtZZmRkXlhPTVxUU1FXVVxiXGdWb1dYUldnXV51imlcY15ebm5bX15ydY2DgYSHf3Zvc3+IdoOAnHRaX2JeY21dZ2pdYmNaUlpcaHtoXFpcb1VZVmRiUWxsaGtgZ3ZgXWVeY2lZU2Vue4WbeGRhXl1cVF1gYWZjU1tiZnZ7cW1+lX2Ab29xbWZmb3iWjoB2fnNza2BaXGaNnoR6cXhoaWZ6b3xzYnKHfZRvaWpug4JvZ2t7aYKHmH+AaIRycnp4f3l1gXN4aXZha3lrb3dhVVdSUGViVXRyqmRrXl2dZmxQU01eamxkXVtzYoCea2lvZYR/johrZm96k4l6jnpsZWZya4d7h5ekjHl9d319fod4jn1rf3JecGNvrZGnjWdOYm5xd3t0Z2qWXlJhXltfW1lZdW5XqG1pcXWAcHpWT5J/fYOGiWlseXFsYn6TfHZyXmhrbm1oWGltemtla3RzemyVa2xrZF5iamRgYVxOXGxmknhtaWpkam2VTpSLgYKMcXB3d3mDgH52eoiXgY6OaV1sdFFQXVlZY3FxkVOFf3FvZWxqaHNtnYZ4h3hyamt8hWpzYGB3bXmApZCAgnlpYoSPcXdwYFJjaltsa2RLbUtOhEt2jXN0foB5a21bVWZyaXB7g5dvd2pobG9sg1NWVE6Gd2Z+gV9kcH95gVBtX2N5knmBjH6Bd3F9iVSHhGhtdnpXZF1naHSBcax0XWBkZXdoX15pcXBpamt3YFFhdWxsenOEgnNpZ2JjYGyAm3tkZmd+dWBshHuDgXCEcWlMTXp2g3Z0d39zeniUmI+ZhW91b2dTbXRtcWhfamdoWltSUVeNU1tcZGhqcnZyZm+EXn9gWmltYV12eXtOf3NdV2qKb2lYWG+LV2htcVuCe2BgbHR2aWJoXGlgYnlmVWFicYtyalRLh0dVWF9gVleAXYOIVGdsbGxuZFxqb31fcWlgZl9ubGJqcVhgZHFqcJSVgntofGRmdFdfa3Zdan14aXJqXmp3dWxndX59gnZ7j3BhX11qZZF5Ylptdnd9b3RcXGVqZV5nZ2loZ2xXZmRqb2lvZ3JtZF9kbGZzfGhmcmJlWmRuYWljeFxeiZd8f3CAZ2RsYmhfeW1fTIZ0WGdgU2Rfbm9hamJub4J4fmpreXFkb2Njc29ocXRob2NnW1NZWU9nb1lpbmdicIBvZ1lwaWpxcXR2aWVtZV93f3tmY2pqdX1yX2hxcndzaGNycWtve4Vna3xsVlJPWlJnhnJjaHCAhHqLd3dwcnF5cHFlYXGAgGNmgXNwZ1tng2tfaXhVZGF2fH1wZmZtbGVQXVZbY19waXFlbnNuWmRWUmFnb4d4dY6RdGtjaGZ0dm55jnhnYXZ/b11tcGxlcXhnc25uaWNaVWFZW1ZYUlZbV2JSa1VYUllqYGJ4iWxgZ2NkdHViZWJ1dId6dXZ6cWhfZXF8cYKAmnJXWl9fZm5cZmtjZGRYTllZX3JoXV9je2FoZ3RzYXhza2tiandiX2ReZ2xeWm56ho+gg3JuaGdlXmtqa25sX2VucYSGd2p3j3l3Z2VnZF9caHGLg3dudGttX1dXWGSKn4x/c3hnamFrW2JVR1RnXnRXWFphe39waWt5Zn6EkXiAWHRnaG9veHZ0f3J4aHdhanZobndgVFVQUGNdUW5xsm1vZGOsbnNXWVRkbW5kW1xzYnyWZ2ZuY311g3xiWF1ieXNlemthXmJqZIBxeoucgXBybnFxcXtrfXJidWpUY1VglIKTgWFIXGlrcHNsYWOOYFhnZWRqaWppgXZdnmdjbG8Eenp7e7t6AXuYeoJ7h3oBe696hHsCenuaeoR7mnoBe8F6gnuiegF5wnoBeYp6AXv/ev96/3r/egF7nXoBeYR6AXnBegF7mnoCAgQAgMvd0NrUyO60oKen0rnAycHlkoDY0ra7yquZm6iXi4SMoJKcpLOzpqqhmY6WkZSaubGdm6Detbu8sMK04MLHxrjc5NHV3c+6sbHN3cq1mZ2fxa3A4MyZnqC5sZ6Vr8HL1daL6ru4vLWitZmisbDB0L67yNTT2cK9rcbzw7LJw4CpgKL+gZacjZWdpbKdxauszoThy+Px6Y+D7MnEzMTe2MXAr7W5tqi0wMf75+7pxuHqy9Xxi5WH09bV0+Lh1e3p6v2ot7jQxbfR7ILsqKOq5Pvah/PFzfH7vLStnaupnoqBqb2+xavCudGvpLWjndPW3JW9yb7Lyb20s9/AqJynsKm0gLexu7y5w+HZ1NXa8Mm8y+rMyMi1vryWn6SWkZeelZ6roJSiwa7Vn6Xlm7Svq8C8qqCkvKuqs56hqrGvs6GltsTEvbrEzdTj7vLixr2yu8qOiY7u/IPZwq3g0s6uqZ/DxbXBoZ6VmKKlqKeQlbTHyre9q7mim5yLnZWEnZWfopOggK26v6670822v7u4w8fBvNDV5efN1dfRy+bT5cK/07+93rm8x7a3vrapz77SytS7qrnDuKudvK6surGgqLK0yOCBoZKtqqe0obCkl7W+xq28srWblZi1vLatpJiUipCdw9q+xq6W4cukk4eDjIaWnZ2dhJCTnp6uv7LKqqW0uc/BgL/DzLO0vLiyqpqhqZmzsaOipbDGr52ekpyWoaufm66rpbittre/w8nnycKl0pytoaiYobPDp7O6xrnHwMPLv8nI3sTKwLrCw8/K5OXPrM221eC5x8G3rrq8zra6wK24uLDAu7q1xdy3mZmXn8zmxqWlquWwsaXloq6ooKujnJylgKy5rbKvsayliZujrKSfm56bnZ6sprGnp5yQiJKlmqeXysK1wce3xq2gjIiGnbWR5qiyoJufjIqTh4mEjJSZnZSkqY+al42nqrail6OZo6GpoI+aj5Geprm7tJ6qn6mmppedm6mvtLXEvMizjaG8vaeqrrO5u7q8usu1t6i/1cq1gLKuubHMudW4t8PJrpqolZWdjoqwub+enaGhn46enba0oqynrKqempCip6efoamvpaGfjqCip7eso56boKyro56euruzuay+nJ2kmLK7n77ExL+/wtHCvNrSuNHKtqq2xdTMxMrL4fHb8ubL0s7age6JirfV3N/z1M3pocvjhPrlgMawqqujo56wq7ywsbOxp9Divb+isaOmlo6enb2eu6KnmKiWjvuImqOPtLG2vKCur7PQy7qrvsS7urXYgOrBtt/t4tLEvLCkramr0ujP0MHDsbOAh/fDvLm4w77I062zw8vq6I3j6Ob0vcvCqY+vz8S8qK3Nvbu4w7CvrZ/i1rq+gIaVgJCOh6JzX2Ngg2trcGaBWU57e2dvgGxiZnZhV1FXZFdeZHV2bXNrZF5lYWBhenFdVleIZGhoWmVTfWFjYFd3gnZ+iIBxbGyLoI13ZWlqj3eFopRgY2d9b1tOaHqDioxkmnJvc3BfZ1NecGxxd2Rfa3Ftb1xfWG+TdGh8fVVwgGuXSlxgUFNZXGVPdlxUdVB+aYGHglpSi3BqdHGFgHBrWF5dW1Niam+eiJSIa4GHbnSHV19Sc3h7fYaFeJGJjp9SWlhuZFhriFKMT0NGd5R4VY9hbZGXW1taUF5eV0tGW2ZlbV1rZXVWSVhNT356e0xocGp9dW1naJN9YVpmcGpxgG9qcXFqcox/enp7j3BpcZB4dn1qdG9LTVNGREdMQUxbUkZVbGGJVVeWT2ppZXh0YFldcGlka15ZXWVoa2Nncnp2b2lyeH+Mm5+YgXVma3ZfXV+Uo1aBb1+Gg4FpaF57fnF8Y1xXWmRkaGVTVmdyblhYT19VTU5BU1FDWlRZWlNegGlydmZtg3xqcGppdXZ0bXd+gHhia2RiXnhvhWpqgHBujWxufm1tbmljhnWMgIxxY21zaV1RaVtXYl9WXWxxgppcYlhtZ2VxYmxfU2dtdGFsY2NWW11tcGpmW1JPRktWfpN3f2pUnYRjUktHTkxYYV5dTFRXX1tjb19xWFBbZHNjgGRkbFVXWltaUkJKVlBmZllWWWN6Z1VWTVVNVlxXUVpTUWFbYl9jaXCRe3NfhVtoYG5eZnR6ZWtwemt0cHJ0Z3NsfGZrZl5eYGtqf4NxWHVhe4xmcWdjYHFwe2hscGRqZWFwbmlpepN3WldUW4CcgGBiZpJfZWKlYGlcWGJdWFdegGJqY2hjY11YRVpmcWxpaGppamh1a3Rqal9UTlRiWGJOfXFocHhreWZYTUlIXnpvg3SBcG1zYlxkVltUVlVWWVFbXUlQT0lcYGtcUGFXXl5mXU5ZVFZhaH5+eGJsYGVkZ1dXVWFocXB5cnloS1xxcl9nanN1e4B9e417gnGAjoRygHBsbGR7bIRqa3V/bV5rXV5lVlV0gIFZVFdXU0pVTFtaUGBeZWtnaWJsZ2ZjZGdmY2BeUGJlZnNtaWNgZWphV1FSbXRrb2h8YWRqY3d7X3h9e3Nwc3tpYHhzZIB8a2FufYl9c3R0hZN+mY12enV4TIhSUVdudnuIb2qFZmR8S5iNgHlraG1oa2NxaXRsa2liV3yObmtUX1tiUk1ZWnpie2VoXGpdVY5RZGlUdHV2eGFlZGqMi3prd4B4cmyFVJNtZIiYkIV8d2phaWNljKWPi3l6a2hVW6p8eXZ3f3Z6iGppcXSLj1yIkpSfcoJ9ZU5skIZ3X196aWRibWFnaVqXjHR7gG95c4OFf5pqWF1aeGhrbmiCVE6Df21zg3Jtb3puX1hfa11kand4a2xjXVdeXV1fdW9ZU1OCY2xsX2pbgWxxb2qDin+Chntva2t/koFxXF5gfGt5lYlcXmF8cFtTaHiBgX5WimVkZ2ZbZ1licm91fmxqdHt1dWNjW2yIbGB1eU5XgGuaTl5iVVhgYWhSeFxUcUx5ZHd/fFFNiHRwfn6Yl4uIdX16dWp0eoCnkpaDbYOEaG5+T1hMbXJ0c314bYB7fJBUYWB3cGh7k1eZYlxah5qBT41pdZKZaGpnX2xqZVdSa3VwdWBuanxfVmlhYouBgEdrcm18fnRubpJ9ZV1ncGlygHBpb29oc4yCfn6ElnZpcY52dntre39jbnRta3BzaXB8cmFphHCVYWObV2tqZ3t4aGJkdGtocWVjaG1ra2BhbHJvZ19jaG16iIuCcGdbYmpUUFF/ikltXU1ycHFcXFZ2fXSCZWJaX2lpaWNRVWdzdGZpXmhYUlVKWlRJXlhhZVllgHB6fmtyhoBvdWtpcnFsY2xvc25aZmRlZIB2iG1uhnl0kHFyfWxqbmhhfGt9coBwZnF4cWpifGtocnFkb3l5iJlbaF9zcnB/dH1uYnl8fmhvaGhYWlxudHFsZFlXTlNeg5J5gGxYmYdnWU9MVVFcZGJfTFVYYV9od2uAZWFtdYh2gHZ0e2NjZWdhW09ZY1tvbmFfZHGKemptY25pc3hvanJqZ3JnaWVlaW+IdG5Xfk5bUl1MUl9mU1tjbGJqZ2puZnJxgWxya2dobnh3kZOAZINshJd0fnZwanl8iXR4fGtxb2t5dm1odY50WVdTW3+fh2pscp5ucGibaHJnZG5qZmZugHJ7cXRsa2djUWVsdXBta25sbWp2bHRsa2BXT1dlWWJOem9lbXVse21iVlFSY3prjXKCdnJ3aWNqYmhhZ2dmaGJra1ZeXFRobHlsYG9mbmtyaldeWFtkZXR1bWFpXmRlaVtgYGt0fHyCeYNtUGJ2dmNpaG1vc3ZzcH5uc2h5iH9ugGpnZ11xZoJub3h/a11sXl9lWVl2golkX2NmYlpnXmlmV2RfZWtlZF1lYmFlZWpubG1oWmxub3tzbWhnbHNtZFxde4R7fXCAZmpqXm5wVW5xcWtqbnZmYHpwX3h1Z1tndoJ6cHFwgY53iXheYFtcPGtFRUtibHWEbWmDZGN6S5WIgHRpaW1oamd1cHtycXFtX4ORcXNcZF5iVFNfXHdjfmxzZHFjXKBXZWlWc3F2eWRpZmeBg3hrc3pzcWx+TIJbUHB9dGtoaGBYYV9ih52Hf3FzYVxLT5NvamlrdnR3fl9iaW2FiVF9hYOMZHd0Yk5ggH95aWqEeHVye2xtbl+Pg2lpkXqCe856AXucegR7e3p5jXoBe4V6gnubeoN7k3oBe4d6AXueegF733oGe3t7enp763oBe/967noCe3n/er96BHt6e3uIegR7enp7pnoBeZZ6AXuWeoJ7j3oBe5x6AgIEAIDs7MWour/Iur7TusO+3Ons6fSa7sm4oJuombPG2K+gmpehtqaZmZigtLi7qKmwxdL4zsC4w7ixssSysffE0svEyry+vLWtwqu5ybnVn7+nh5aRpLKsppWekZSfoqGtq7i6u9fz7ebPnp+hlrCwrbDH3MrH7tnEtsHLpMHX4MCS1ICFhJ651LvL79W72NS2scDJxeHBxOeHjtK+suXVxbKmpa20sLq3pM3I2ODP25Ld17TNx+LU79zj29fazcfJz97iwtnYy9u/o67I4LTE1NrXxbfSu8vm6sOvo5yjoammp6ey3cvTxcq4sam2rr7Hzrerwse9zL3qxt/TzI370/buuYC7rMDb/+GG09Xv097P7YPYrbDRw8ann56hpY2qqaOysa+7tby6sLTQmJiSi5KmtdK7r7mkr6qmo6Wtr7KhtMfMsdHfv8K5wLW9s7y7tMXV1rnk8IeE1sDbu9XOjpab5r+hn6WwlJOtp7TKkLi0xdunmaevucK5rqyXmLSqr6uvxIC6oaOn2+PI9OjRwdrbxrbBz+3/y8LS1Mfp49a9w+DSx8HBwMW1ubq4pberxcXwwamqqbGrprXCtby+qre0rrKnt8zIxNrWtaeYsr6wmMSvtrO3zuPUr7SpsLSmmaecrba2s6qalq+nlZGSnaiXo5OcrJyZo5CXmK+/3s6xo6rS1IC5s7S4squsua6ztaS+m5iZm6HWvaulnpSXnquwq6eet8OuwLq10bOzuLi3ptnPxaiZl52ep6ernpydk566x8jGusW/1M3Nw8e0r7a2xs66vMLUzMfKyrXCvraptrq8vra3w8fAw8zR7cPJroqmwd7Ela3AtJOsxrSyoqGusbq4s4DAwMmvp5eZtY+QnqSqk5imqq6vrquSqM+up56TnZ+aqrCmt7q8s8Gvn6anrJmfloKgu6qxpZKiqIuEk5ufvrGjva+TorGgmJqdiJamqLOhpI+JiJydlK3bx729qqSvwa2gmKqlnLLA2rOso5GcoJ+sqa60tcrEvbjDs6emrsC1voDUysXFu+rI1tnGr7GZpJ+Nn5WOmZuN/4iWi5uzr620oZ+Qq5qZmKqzwaetsLfByLabm6W2qKa7vbCerKizj5idtrW2sKnE18KrsbG3taDWxa6z2K221+LW3NPk07y4rLinpcXU4NXe1MTWyc3l39zQh6Ps0faCh/Hn85vu7PbUxICxsqquqIuXrLPAyM+338HXw8W7jMO5p6mjpLi1orWvobKqpZWVkKKfpLmvpKqdrq6ugeK6pLCltrOo1eKA7M2+2tbL0di8xL2xo6KovJTVtMC8wdXiu9TM0Lavp7/Rtqqt9K+A6vWxp6Ps27WssquwxvTRtq/Bz7iemKCrraKtyICmo4JndnmEdnaBanBqg4yGgo1ijnJmWVpqYXuOoHxuZWJsf2tgX1tic3Z2a2ptgY+1iH9ze29oanJaW45ibmJfZ1lgYmRjeWx/lIKaa4l4XGdjcIF5bl1gUVVdYF9mZnV3dpGpopaBWl5hUmFlYWBwf21qjX9rXmt0Um+HjXdfhIBIR11viXGBm4VvhIRoW2luaIFjXHpUXXNkWpCKdl5XVlZaUlxaT2lqeH90fV1/eFxxaYV7mIeUjoWEfXJ0d32CYnZ1bH5pTFZ2ilxsdn2Cc2BzVWKBhWRQSk9eXmJZV1VehW5yZm9fW1FdVmBiaFxXcHZndWaPb4N1c1WMe5aRZoBqXW2FoIpXenyQeoR7mViEXlx9cXhVUE1SVEBWVEtWVVdgYWBiX2SCT1FKSEpdaX5sYmlZZGBhZGZubmxbaHp8aX2Gbm9sb2d1a3JtZXOIiXKKk1ZSfnSEbY2FTlRYmHdfX2RtWFt1aXWCTnFueINNRE9VV1teX19PUWdeZmdleYBzX15hh41zl457bnmBbWNud4OHVU9aYFt/g39tcYmEenVxbXRiZmprXXNqgH2hdWBcXF9YVF9rYWZnU2NpamdjcIF+d4uLa15NYmlgUnVhZWJke5CIX15UXWBTSVdNXmhnZV5VUGZfVVJUYGlaZ1lhb15XY1JUVGZxiHhdUlV2eIBgXWFlW1ZXY1hfY1dyVlhWWVyNdWFfWFJRVV5gXFpVZWtcZWVhe2NhYWNnWIuFd2ReX2JmcXB2amNgVl1vd3hzY2hkcnFuY2VaWWRmcoFuZWl1a2twcGFtZmVaYWloZmFmcG1ocnp/nXmGbVNuhqeHWGt6dVpqh3FuYWNmZ2lmXoBvb3xoX1ZWbUtQW2FoVVtocHVxb2xabZRxbGZYXl5WYWVbamxrYmthVl5gZVhhYU5siniAdmZ1eFtUY2Ngd2ZYaVxGUF1STlBVRFJiZnRiZVJKSVtaVWmQf3l4ZFtidWdcUl5YU2hzjGZgWkxZWVliYmZrb4WDe3R6bWhtdINxboB+d3Vxbo1thI58aGZXZGNVZlxMVFFGbjxIQE5cV1VeVVZOal9kZHBzgmlwb3F8iXhgWWFuZV9vc2hbZmRuSVJTY2ZoZWWBj39sdnuCfWeOfGNkfVxfgIyCiYGNfnBtaXVoY3qCiXuDeXJ6dHWLhIBvT2eDb5NOUoqIlWmOj5aCdYBqbmxxcFhdbXB2fYFlgGZ6aG1gOmtpW1pZWXBwYnFsYHBpYlZTUGJcYnpvY2dYYmhwWZl6ZXZodG9jgotTi2tkg4N9gYp0gXpxamtyh12Tcnl0doiLcY6Dh25qYneGZ1tfnndWmqFpYluhlnRraWBjcqN7Y19sfG1YUltnbmhug4CPjXRfamt0amx3YWZlfoeEgo5glHdoXmBsaX6NmXxuZmNqfnBjYl1eaWtsYGFmeYWrgHNsdWdobHZlZJZxgHVyeW50cWxid2l5iHSBYXdqVF5aaHp0a1xjW2FpbGpqaHBtaoCXlYx5VFZXS11jYWBwgnFukn5rX2VrTmR4gW5UeYBJSF1thHKDnIZzgIhpYG5zbYBmYX1NVXhtZZaUhHNwcHN4cHl3aYB/iYh7fll9dFhrZnxvgnSAf3x4b2xwcn6Da397c4FuV2N+kmd0foOGdGZ3YW+KkHJfWVxqbXJsaWRtkXh7bXZvb2VwaHJ0c2dgd4B3g3KIdoV8e1KLepKOZYBnWml9mYhVeXuRf4iAm1WFY2WAd39pZWludmN+fXV+fXl7d3Rxa2+EVllUTlNmcIRzaG9faGVkZWdsa2hVYXR2ZXh/aGllaF5nYGZkXmp5emR9g0tHZ1xwV3JzRVBWmHtnam54YWF4aXOBUnBsdodXUV1jZmtpZGFVWnJqbmlneIB1Y2NkjpZ9npJ+bXh+aFxkbXh9U05cZGKHioNudIqDe3VycXRjY2dlWmlic3GSbV5eYmlmYXF7dHt7ZXFzcnNreYqDfpCQcWhbbXVvXX9obWtsf42IZWdgaWtfV2Vbbnd4dXBhXHJpXV1eaXNibFxmdmNda1pgYniBm45wYWSJh4BtZmdrYVxeamFqb2J5YF5cXmOTgHJvaWVma3d6dG9mcnlncGtjd19bXVthVYZ7cFhRUVNVXmFkW1haUFlxfYN/c3p1hoKCc3dnZXFyfIZzb3eEeXeAgXB6dHJocHZ6eG5ud3VucnZ8mHV7Zk9qhaKEWGt+e2Jzi3l2aGdxdXt3b4B8fIdxaFxfeVZYYmVqV11qb3JvbWlXa49xbmdcYmJZZGlebW5waXJoXWNjaVddXk5phXh7dGhydF5ZZ21qfXBleWxXY3JnYWRrW2t9fol4e2ZcWGZmYG6NfnZ3aGFmenBjWmdgW3B7kW5qZVdiYF5oZmhsb4F+c290aGRnb3xqZoBybW5sZ4hsgox9a21daWpfbWNVXFlRh0tXUl9sZWJrYWNVZlpcXGlpdWFmaG57iXtnZnB+cGl2e29hb212VVtecnR0cW+Pm4Vvc3h7dl6CbllbeFZZe4mCioKQhHNsZG5hXHF3f3F5cWJqYWF2bmZXQlhtWX1FSX1+i2GCg4l5bIBhZmdualhhcniBhYtyjnOEcXZsRnBsX1tdXm9uZHNyaXVvaF1dWWZhZ3xya25jbW9xUY11YG9nd3FjgIVPhGRZbWlkanNkcHFuZ2RsgleNaW5naXmBZ350eWlnYHODaFxdkGlOjZhlXlWOhGlhZF1jeayMc257jXtmYGRral9fcZJ6AXvregF7lnqCe5V6AXvNegF7i3oBe4d6AXvEeoJ7/3r/ev96xHoBedl6C3t7enp6e3t6enp7tnoBe4p6AXuleoJ7nHoCAgQAgPjl8r+8v9bL0cft7feGg/nJ/N7erK2nkZWpqKesybKesKylur2apqaxtba1rLTa69aercO5wr+/tsTNzeHiytXa286it7GqubvBup+wvLLHytCptLq7m52Yy6qpn5+vvsHKv8r3gYffu6+qq6mwy/zh2LbU78+7tbCuzcvNw4yBCMPKvs7At8bKhMiAzoPNyJ6P2Ny+tIGyq628oozLw8K/vamwvqnYiey/xoTp0cSoh+a0q7C7zO37/NTHxdzr54jk4Nbysp+rwfHY0+nUzK/Fy8ut0vHnuaabn7HT2MSmq7q2pcHHyqXL99DAray9ubKds67DzdDs2dPI1OiDjvnt3+ThyMnl4Nvx6NiAxtTDysbBwbvGvbK8vL+wq6Gepqy7vsW4rbSpk5uQkp6cpKOyvbmxvLOup7CgoZScqI2brri6tKalr6a2ubKjqqifoqrE0oW3ydDU5efhu7qtvM/Awca3npyWpqW5tqavp6KelaPbzNXOzL+8sKirpK2xrre4q7Wrw8C518PllpCA08bR29ni5dO1wsjLxc/Fuc7QvcS/ycO6r7Wsnqy8scG6v8XPxMTFwK62sJqotbm0uauhuMbUtZuozqe2sLG5qaShsqK3qsOYucW9t7WtwpeorbLB0uLBnZ+elpydmoqbrqSslpyhmqump6aipaG3ureWoba/uLilqa2qncG0ramAr6mioqPQ656RlZuolKCeprPBuLq5trm9vsmyr6ups6+7s7O1paCtr6Sgr6iir6y+zurFx8PVxLu9zMrEz828qbCwvKukr8PL17vdwq651ce0uLy3srW5tsm5vNDazL+lrreopouPscW9ra+poZmSq6OksbiursG1vLWxrZyZq5SAjaCnqqW3uLSjoLq7vKqtrKejuqSYsMqB2cy0mLqmkqGWlI2Pl42jnpudm/6g75KsoqKRmZ+5iImDk5GLl5SPiJKgoKiroJalmIqkwbzE4LW+pLOcnJmcopuWmrDAvJOStNSYo6aursPT08mwv8XKqrbRzsrQ4t/M1uS359zQt7CApqOvpqKgmJail46Xk5aToYiSlZmdhJKdncW9p6eiwdXOzeW0sK+mpZ+gqp6tr9SH/NGznbS7v863r7CxqKWsxLqSg7Cc1djNqr3EydXM3tC2rbKsp667s7a9xMfLzdHB1uzW493t2PeH4fSG8fnqv7zGu7/GzLyrr5ubnZSXm410maybpa24wbnJqaihoK+bqKi9rJ25tqCK1sSir7K4tqStpLjCpq2T8ae1oZmuvdPW5r7K1N3a84TVxryEwK+xy8KqpJ+Jn7m2s7XGuLK3rKqfrbSxr7676cm6o7HG5azc2Z2Zrb7HxbGmoqihoqanv7mxn7KArpqic3F4kIaEepeXmVJQjmOOfXxbXV5SXWtrc3KRemVxbGZzeVdjYGxvcnVodJmmmmJvfXd6d3NlcH10gohxfIKAdFNsamFwdoOAanyGg5WZm3N4dXhUVlCEZmliZHJ7eX5we6ZZWoxvaGpnZWh5o4t+YH2Sd2ZfWVl0enp4YFiAfn11e3Rtd3t3eXp4eVRvbWtdeHdeXFFaVltmVUB0bGpiYFBUYVB4Voxja0yIcm1ZXZRnYmxygJ6lp3pta32HhVR+goKtZlVVbqGJhJd7cllqbm5QboqLZVJKTFt5fWtQT1tbTGRmbktpjWphVVdkY11RY1xwfniFcmxfb4BPWpGAf3h8dmFifnt7jol5bntrcHBscGpxZl1mZ2tfWlRQV2BubnptW2ZcS1JGR1hTVlZgaWVgamdjY2xcXVRfZUtTYmluaFtaZV1obGhdYmFcYWx8hllsfIWGkJOSb2xdaXBvcnlqW2Jeamx8cl1kWlVSSEt7cXZwal9iXFhaWGVoYmiAaF1nYXRtY3Niglxaemxyenl/fmxTYWhvaXZ3bnx9a3JxfXVsZmtjV2N2a29tbXd9aGtuaWRqX0tYZWdgY1lZd36Ea1NhgGFqX1pdU05JWlBgW3ROaHBnYGRcb0lcXmJyfY5vVVZVUlpeXVJmenB2YmRrYWdhXVpVVlBhZWRJT1+AZF1ZTFNVV1JsY11aYV5aWliGpmJYWl1nWWdeY2ZwZGVmZmVoa3FcYV5aZGBsaGRqYV9obl9aamReZF5udpNtbmh6a2NndnFtc3FnYHBwcmBYXWxtclV5Z1ZccmtlZGBcWV5pZHdsb4KRhXdjbHhsb1tdeoyBb3BrY1tVZmBeZWiAWlttZm9ua2hbXW5cU2FmZGBvb3JlYXp8eGhpZF5YZlRMXXRQgnpoVG9jVl9ZXFhfZ2Bycm5ubcRwmGR1amVVWFt0SExEU1JOW1lWUFpmZ3N0YVZiWEpgcnF5km9zXWlWVU9QT01PVGZzbklGZYBMVl1oaHeCgX1nd399Y2qBf3yAeoOJd4SOZX+PhnNoYWBqY1teU0lSRUNKSEZEUkBNS09VSlliYYSDdHFmcYiJjqN1aWhoaF9gaWduaYxdq4lkVW12dn9tZmpwa2pzioFcY3NdjI5/Wmlubnp3iXxuZmtmZGl0bWxwdHZ0dnluf5mDjIybgZtTgpVUk52TZWNwaHKAfIN5bnhoZ2hfX2NUWmBTV1xoaWJtV1lRVWBVYGZ7a2B8dGZTl4Fob3FzcmNrYXF8aXFZpWp3ZFptdYWCj3B1c3Z0iVWBdXJdeW1xjoFuaWZUZ3lvaml3aWRrZWZfaXBqZnZwl3xxW2p5l3GHhlZWaHR2eGZfWVlXXmRld25pXW+AmoaGYmRqf3d1bYuIjU1NjGqVholnam1gaX1+goebgWp1cWh1d1dgXGJjZmdfaoqWi1xldW5ydHZpdX14h4x3foaGfF1xbWNwdH11YXWCeIiKjW54eHhiZWCRcW5mYm11cXNob5ROTHdcWFpbWmBylIOAY3qIb2RfWlducnRwWlOAfHx0eW5odHdzcm1tck9ta2BYfoFoZlBqam9/bFiQkI6EhHBzfWqLXphydUuGbmZOUIdgXWZxf5umpn1wb4GLiVSBgX2TZFdbcp+LiJiCfGNwc3BWdpSTcmJcXnGOknxhYGxpXXR1fF19on1yZ2h1c29ib2h2hYGLeHJjboBNV4+AfnR4dmJmgn9/lJB/cHxtc29xcW97enSBho+CenNudHqIgoh3aHFoV15VVV9cX2BqcWxja2hlY2laW1FbYUZNXWlqZVdbaWBucGpgZGNaXGZ0eU5bZmtxeXd4W1xVZ3JxeYJzY2Vea2V1cV5tZWFcVFuGfoR+dWdnYF5iYGhqYmmAaF1nY3h0bHtphFpXdmlrcXBydmlTX2huand2bnx8bXZye3ZwaW9lWWZ4aG9sbXaAbW51cm92a1VlcnZwcWZieXqEb1llg2RvZmRpYFpTZF1sZHpTa3VrZWpjdFNiZWp5hZV4WVtbV15jY1dsfnJ6ZmlvZm1paWdlaWN0d3VYXm6AdG9pW19hXlp0bGlmaWVfXlyHnmdiZGdyZHNuc3iDenl0cnFvbG9aXFhbaGl0bWtrYFtlZlpXZmNcZV1ue513e3eHd25xf3lydXRqZHJ1fGhiZ3h7f2GGeGhviX91dW9raW1wa3VnbH+He29candqbVdZd4d/cXNya2ZhcmtqcXKAaWl3am1qaGheX25cVGRnaGVycnBhXXV3dGlqaGRibl5VZHpSh35rWHJlVWJZWVZaX11ua2hqZ7RtlWJ2bGZYXmR+U1dSY2JebW5rZnB6fIaGcWRwY1hoenZ6kXF2YW1hXlxeYVtdY3R9eFdUc41ZYmRqaHiGiIZveXl2Wl53dXGAcn+EcH+NZ4WVj355dHJ7cGZjW1VfVVNZVVZUY1FcWllcS1hdWXV2a2dbZ32DiZ55c3Jxcmppb2lxbYxapoltXXJ8gIp3bG90b21xgHFPVmhWf4NzUGBobn94i4Fxa25qaW1zbGtucW9raWxebYRxenuJdIZIdINLhoyDW1xlYWmAcnt1bHNpaGpkZ2tdZ29gZGVwcmxzW1pRWGNWXmZ7b2iCfGtZnIZtc3Z3dmpzaXZ/cHZioWdxZF1weIOAiGdsam5tf01yZmRUb2Vrh35uaGJPXm1lY2RxZWJqZGRdaW1oZ3d0mH5xW2VtiFN8g1tYa3N6fXBqZmtqbG5qeG9mWGONeoJ71nqCe5d6gnuNegV7enp7e4R6AXuQegV7enp6e4R6AXuPegF7vHqCe9N6AXu3eoJ7/3r/epl6AXuVegF5/3oDenp7knoBe6V6BHt6envLegV7enp6e6J6AXuVegICBACA7b3H1rDQ8rvFuczk4/6MzOzo6LaNpaeQoKColpyMk5WlrLfA3szKu9Dh27iejrXUzau4trHHxtC9tsPL6NLZ5NbQxp+0tbSz1eG6nqu0qc3GjYuM5OG5trCux7KqssKrqrTc08WAmtq4laG1ytbU0NTe3MbDzsO4q9Dy7PKzrKmAr6SgssLOyN2KnZzZ0NnI79DQz9L8hr+1vcmvrLurrb6xvcu5zsu198zPxcPXvqeXsZqMxb7DvMTWx77U3N7Y0cXXvdSvkIuawsbGwtrgyMKsvMKfqbHG19nU1LGvxMC60dve1uDPz7K9xLy4pYaipJGqp7G6qqOQntPBva23yYSA042QhNvM2uzcs+PT7+3u5IfO17m7vqrkuaGlr66imsOvsLTEoLTDzb2bhJOfm6u+xK2/qa+2q52kk72Um6zSvtPnzrjIv8G+wLKyq667zripzf3GzdfS6vTUr+z4srC0oqStoLepnZKWl6i0qJqZmrnN3MnJu77UzM2+v8fEtrWArqrSvMm6stTG2trky9DL7vj/1byut8S1v8K748HDwrrn/9K+tLepoJ6vx6G7vK20tMbCxbmytLDChrSrp6u4hJfJvuG39ufi4NTMq77Awrysu8elssjmqbDHsqO7q6apramasqGXlpyZlJCdho+H9IeampOhoaqpra6op7m3raCAv7mnu762tLvCnp+uqqyfmJq20b26jYSZpsmqopmnrKa7z7m5n6emtqqusKWloLW7uq6xrrK2pJCssrbEzdW0vdrXt7Wwv9zJxrjf7deuqJ+sscC/wbTX19np1cW2wsbDsKywt62xt8TBtL+nmaqvppqVkrCqqp6qyLyspY2vuKuApdjHvbfJvrHGytfBtqGesMixvMWxsrinsq23o5K1qKamj6Or1ffItKWulqyvko33jqfCl4qEj72QhIaaoKKZ/4qYkpWcjoiQk46PpKKTlZuVlJCUkoKYk+m71+DEr6eoo5OVl5iWqqiZmJGdlYygoKWusb/EuLm2vc7KvsbIu7uAuq23vrrLsbbTqJONlqCcq6mfrracipGhmZiTpaKioKKkmZWbnqK7y9S+0eGCzM/E3dzFubyrnaDJ3cTb5Lyrsbvn2ufIxLS1r8fEta+Z0NLa1sG3mrG+tcrKyuXHxbm7u7bCxcq8x8zKpc3ez9TW5u3s9/roidHijYLNv7e31baAqqnIsLKml5W5s5OPmqaxmaeppqCan6KrpbCuq6W4wNGrqbKxuc+isqSpr6Keoam7ubK0tras56y4uL6+z4736si6zs3ngerj17y8rqiRqcnmy9PTo6TU3PjLwbukraWRmanU5a/gv8HCs7i16PXM1bm2rq68s7XAtqrI19PzsrOAmW55iWqKom16bXmPhpVWgJaEil9EWl1RZWdvZGleZWt0eICGnIqHeYmZmnheT3KQiGFsbGl0cnppX2lqfXJ2hHlzbkxnaGhoj6B5ZW95b4iBZ2pmm59ycnRzinNqbnVgX2WJgnZUapF4W2RodYKAfoCAfG1rcmlfVHygk5RwbnCAcmxlcX+FfItaa2l+cHVmhnBydHelXXFobnthYGlYV2JVXWlYaGRVkWtxb3KLdGBQaGlYcm9wa3J9cGdyd3p7enV/aoNkS0dVe4B+dYmNenJfaWdJUVdqc3NxcFNRYmJdc3h8eHttaE5dZ2JbTjZOTkBVWlxfU1FES3lnYlRaaVCAb1FUSmxeb4F2e3tuj4+MgE5wfWZmZlqQa1ZcY2FTSWlcXl5zT2d3iX5YQUlSSlZhaVlrW15kXVZdU3lVWmiIdYCLdWR6eHdwb2ZiWV5thnBmhbSBiouJmqaFZ5ubZGBjVl1zZmlnZ11YVGFtXk1OTWd4fWttYmV1bGtlbHNuZmeAYlt0XmVaWXZrc254Y2log4SObldPXGVeaGxnimxsbGOMo3toZGZeVFFneFRoY1ViZnRzcGZhYFtuVGBaW15rXG9+dJFqnZGPh351VWJjZl5SZW5VYXSQWWNzZldwX15bXlpMZldQTlVWUFBgT1VTjkxZWFFZVVxbYF5ZWmdmXlKAZGZUYWFdW2dwT05dXWNZVFh1kYN/VktdZ4VsY1dgY15qe2ZoWFxbbF1eY1leWGhycmlqbWdqXEthZmdsdXthan57Y2JpdINsbmOImoNiZWBnY3BraVh0cG5+cWtYX2ZpXFhYXltibYB+c4JuZHRxZVhVWXl0d2Zth31rYktqbWOAX4R8bm56dGp+hYp2Y1JPZXtpcHdmaXBia2p0ZFV4aWZnTVxliJ18ZmJoV2duXFmiYHqUcGVha5NoXWFxcmxjlVFfWF1dVlBWWVVWZGNWVlhNS0dKTDpJXI9pg45wX11hXVBRTkxLWVlNSkhSSEFMSktXWmpya25laHh5bXl3dHCAa2BncG5+ZmqFYU9OVF5aZV9VZWlRQkZYVE1LVFZXW2BiXFxnaG9/hop0ipFagop9kJB2dXpsYF+LnYCOkGxlb3uhlJ55dGRkX3l6dXNiko+Sh3RsUGdyZ3h8e4p4dWdqamNzc3lseHx6WXyGeXt4hYqUpq6fYYmdbGGCa2FkgGeAYGR+cXlwZGGAemBZX2NsV2NmYlhTXWBiYGdlZ2hyd4dlYm1obYBdal5iaFxZXmBwc2ptcHBsnmpta3JsdVyYjXVrd3KBTIaLhXZ1a2JNZIGXgoyMZmiRmK2Fd3RgaF5PU2KKmGSKZ2lxbXJzpauDi3JsZGBvamtvaV95hIOoaG6Ahl9re1p1j2NvY29/fIlPaXWLhm1TaXFkdnmFd3xtbW12dnl+kIB8bnaDhmlTR2qFgGNwcWp2dn9vZm5uhHmAi4aAellsamlrjpp0XGd1boaBXGBemZZ0cnN1inJpbnNdWFx3b2NEVnZlTVVdanR3dnd6em1scmpjWHmKiYhubW6AcmtlbXR4coJTYFpycHpqi3V4foGjXHVweYZtbXpqcH9weodzg35tpICAdnGFb15PZVRPdG9ybnaDeXB7f359fneBb4RoUk9ZfoKAeYmMdW5eZ2lRWmV5h4eFg2ZldnJrgIaJf4R7emRwfXlxYk5mZlZqbXBzZmJRWYFvaFxhb1CAdVNWTnhse4mCZ4d4kpOQh0t4fGpvdW2bfHB2e3htZIBeXXCCYXWGkoZnT1RbVmJtcmNzY2ZtZltdUXBRUl57a3mGbF5wcnJvcGlrYmhwg25ifKFscHJtfoFkT4KIYGRqX2V4cHVwZ1pZVWJvZ15hYXqFiHp4a219dHRsa25rZWSAXFZxY21lY35vdnR8aGllfoCDaFdQW2dgbHNujW9tcGyQpoZ3cXRrYl90g1hpYVVhZ3Fxb2lnbGl+XnZvaWNlT1lxaIRljIWHhX95W2puc2ldcHhda3yWYmp/b2R8bWxscGxgdWZcWV9hW1hqVVlUkVBgYVxkZm9yd3ZycX16b2CAcXFhbWtnYml0VVhmaGpjXFt2i4F/Wk9jcJR8c2pzd3F9knZxW1lWZV1iaF5iW2pycGVnaGZqXE1la2pyfYVrc4iEZmNjbH9qbGB+jHthaWdycHlzcWKAfH6RiIFxeH58bGRjZmFjbXhzaXdoYW5uYllaXHp0d2t0jIRxalV0eGuAZIl3aWVwbmV0eH9tX1FRZ35sc3tnaXNmb213Zlp+bGpqVGNpjZ59Z2BkUmRlUlKQVm6JY1xbY4tjWVttb2deklFgW19hW1ZgZWRoeXprcHNoZl5fXkxWWpNzi5N8aWdtaFtcXl9gb29iYFtlW1JdXWBqanZ7cnJpbHh1aXNwamiAZ15lcW9/a3aYdmVhZm1pdnBjcHZfUFZmYl1caWZiYmhpYV5iY2d2fIBug4xVf4p/kY97en9vZGSImoGQk3BqdYCkmKKDfGpqZnt8c25bhYKFgHBoT2RtY3F0doh6d25ydG99f4Fyf354VXZ+b29pd3mAjJOHUHKFW1BwXlZadmCAWmB+cXl1Z2aJhGllbXF4ZGtsaF5VXF9iYWZkYmVzeY5raG9wdIlmc2dscmZmaWt6fHZ1dnZynmxva3NqclGFgmxkb2l6R36FfG9xaGNTZ4CSeoCEYmCDi6B/d3ViaWRaX26TmWuDZ2hsZm9zmZt+hHNxaGl3c3Z7dmyEioOVW1+OeoN7wHqDe5F6gnuheoN7inoBe5t6gnvMegV7ent7e4V6AXuGegF7j3qCe/96mHoBe4V6gnuuegF5/3rAegF5j3oBeZd6AXvUegF7yHoFe3p6e3vDegF7h3oBe7p6AgIEAICsvMHausLAxLXCwM7psNnfhKOFqJ6ZkKCckJ+eraKiqbezub66ws+8zc/qwrDO0OKxsKyzqYLZytDHw9zu3+bby8PGwrm80rquxMjFtrO/ztqB6u/PgcOE0L66z9rEzaHFt4CQgbm3sc+3v7zEsM7Z9uHBor+5vriurPHCsK6lpYCNk6CgnaO4xuHP3tz/3P3shtnJycHK4bG/yqWhtKeksd+Gv82sr7SE2u/YvMi6ra2y0OrNqqi6wsrOtK20w7PU2dS/x8+lq6zLy+DG9uXO3NfDuaDHztPakuLEvdbGv8fUxd/54r7g3dDKw76mpJ6cpsPI06W0ssywm6msw73BxIDd69rn383LzMLBtL7J1s/M2tCryOHN4uHdtqm1qaqvpN3FqpKQkbOrmM2spa6gr7KdlKKxsLi+pbjP06eku8q7x7Kxqp6tpsKxt7Crvbyswbi1zMHTwszl0qedje3lxomRq8O1kpWSj5ucn6enr5qvwr+4xsvYwsfKy77Q38fCu4C9uPbS14TP2b/DzNnY5dnX1svHwbDDyb+9vLfBw7/GwN3E0s61qKWgprPQo6TEwau6t9DCv76ssbLEtLK/qtWA39yA1tuPgc6C9basur+3r7CqssKvw4jTuMGjm6WhoLCkqq69oqacrcGnrqWbnbGjl4yTlpiyopeaoa+ir7e5q4Cqq7C7uKWruKyktpCeo5ujn5qml7uclo+RlKyeq7Cqoai2rs23oJmipaSkrL6wrqy4t7Kip7a3t6ixp660x6WvutS4q73CuOCvs9D10sK9xbG+uMDArrra4tW6vL/Axbu5t6rAsay0vr+in6GhoZKbpbaxqamkkaGwmZeklLG0poCbsMe8xsCwp6irutPQ3Lesu8bAwLzFqanNuLKqnaOYs7WOkKCYnaqpmJGPo6OZi5KampzN0dbBo4+WmoiYz6iHjZuTi5WamZWgoaKqo6akl6raoKafj5uYprmfyrasp6iSl5qekqGio6+ck5mgkpublJuaqq7Kq7vCxsjKy8Ovt4C/wLq2n5miopqTmpmoppynr7idpJGWmp6Yopi/p5iPmpGPh4+eqr2pr6O93t3w6t7lg9nAqKKxqrK2rLG7tLGxsNuCoJrGurystrSwsa20u7vAyPzN3NPIy8Xv0Lq20L+vvq/V2cC3xcDTzOeF2sjS4enrgYGG7pD1/Nyxw7i9soCx1OPNqai566Ogi4ubpKSvqKmknKayx8GwqK25pZiQpKagnbqqp6KqtMm+tq7DtKWotL/IwsDF48uus8r6jv2N5o/mu7a4ts707tzsyaq4rrnDqrC3xtrD4cS/vaaim46MotfP68m7xtaxqLDL9bzejenLs7Kys8bPz9DKuranroBja3OIbHZ7f3aDfIWYY4qJWnVXYV9YUWRkWmVobmdscHpydHl1fYZxfYObdGaDhZZmY1haU0x2a2xtcoiRhY6HdG5vbGdrfWlidX19bmhyh5BZpK2OYIVcg3FugY98eld1aFVnXHp5b4Fncm91YHiAl4tnSWNfYV9UTY1rYmdqa4BcZW5saGt0fIx4gH2WcY5+UG9nZmJyjWRzfWFfbVxUXYVVZG5UVllTfJN/Z3RnXV1hf5qBYlxqbnV8aGBldGiEi3xve4RiZmh8fJB+sJqAi4FtX0docnV4WHFbVm1kW15mYYOZgV57cW5ta2xLSkZJTWdoek9fZXldT1ZaamdoZYB3hHaCdGZnamVjVFtpdG1sdm1TcIh0i42IY1hfWFdVS35pWklKUXFrWIdfW2FPW1pGQElaXGBtXG2Lj2JfcIByemVjX1dmYnhmaGFbbXFleXRzhHyOeIOahlxWX5uee0VPZX5yW2RbUFtbX2FYW09jbmxjcHN+a3BwdWl0eGZoYYBnaJ96c0thaVVeZ3Nvd2xoa2RlYVRkbGdlZ2ZxcGpzb5F5hIJrZFtWWGJ9VFNvY1NeWG5mZWlcXmF2Yl9wY5Vfn5hbiIRjWYJXnGZgaWZeXl1RV2dacV2Aa3VeVV1XW2ZZXmBrWF1YZHZhZWFaWmpdTkVMSk5gTkZLUFpUYWhuYoBhX2NtaVRaaF9ZZkROVlJhYlpoYYdoYVpaXm5hZmdmXGJwZoNsWlNZWlVVW29oZ2RtcGtgX2hpaV5hVVlba1NfY3hmZnZ6dplkYoGjgXZrcWR2dHBsW2J5eXBeX19hZ2FhZF5vZWFndntmaW5paFtmbnxyYl9bT2BzX1ZgTmtvZoBdcYZ8gnxva3FzfI+KjmtgZGplZmRxX2OHeHZuYWhcc3BRU2diZG9uYl1Za2tjWGNoamuoq7OTc2FmbVxmk3FRVWReWGBiYFxlYVtgWFhVSVZ9SFBNQkxJWGhYgnRpYmVTW1lbTlhZVl5KRkZKQ0dKRUtLXmR9Ym90d3t7gXtsdIB7eXBrV1VdWlpZYF1tX1peZ25dZVBOUFJQW1FyYFVTYVlbVl1maXVmal5yi4mblYuSVoh1Z2RyZW1zZWNpanJxcI9ba2J3c3RpdHh1dnBydHJ2eqh3hXl2fHmehW5mf3Fia2CBiXJpdnOBf5dfjX6IkpidV1RalmalspNmdHJ0a4BtipeEZ2t4pWViVFJgYmJsZGdgWF9vgnptY2p4aVtRY2JYVmxbWFVXXnJoY190cWdlaG5vbW52j3JfZHWYVpZUglqEZWBmaHeTkoefg2Z2bHV5YmZufo9/l350dWBXUEdGWoV8mn1yeoJmX2SBqnKUY5h/aWpmZW90fYJ4bGhdZIBXXWJyXWpucWp4bnKGVXZ4T2RMZ2ZiYnV1cH9+hXx7e4R5dnp1eoFsc3aOa155gY9pamNpY1SDdndycIKMgIh/cG1xcGxvfWtleYJ/cG53gYtQmqGJWGpVhXZ1hY96dlRrXEpYTmhpYG5aYF5kV2x3joVoUmhjZGFdWZJvaG1tboBeZG1nXl9oc39qcHCKbop9TXBrbGl3i2l6hWVkdWhodZtYfYlucXZflqSMcHZoXFteeZJ9Y2ByeIGLd25zf2+KjoByf4VlZ2l9fox6o5GAj4Z0aVFzf4SFYINtZXZsZGhxbYidh2qNh4OCh4RkZWJjaYCCk2l1d4dsW2JleHN2dYCIk4SNg3h3eHFtX2Vze3VzeXJWc4t9kJOSd292b3FxaJqBcV9eYIJ5ZpBuaXBjbm9bU1toaG53ZnKDiGFcbHlqcl5bV1BdX3hqbWdic3JkeHRve3F8bHKBakhFT4uXfE1Zcol7YmlhWWBgY2pnb2F0gH50fXqCbnR3e2x2fmppYoBjYJR1cEhjallmc352e25qbGZqal1veG9vcW53eHN7eZeAkZJ9dWtiZHCKW1dtYFJfXHJucXpzdXiOdWxyXYFQhIJMd3ZRS3NNj2BbbHBmZ2dcYXFkd12Fb3pkXWVgZXJpbXJ5ZGlhbX1nbGpiYG5jV1JYWFtwYlphZ3JseH2AcIBta21zb19lcWtndlVeYlxmY1xnXoJoY2FlbYF0fH57cHN8cIpxX1deYmBgZ3pva2dvbmlcX2psbGFnW15kd1pmantkXmhpYYJXWXSXeW5pcmh6eXhzYmmChoBtbnJ1fXV0cmh1Z2RmbnJcYmlkYllibX10Z2VjWGl7aGVyXXZ2a4Bgbn5wc29iXF5han5+g2NaZG1paWRvW1+AdnJvZ25kfH1dYXVqa3NxYVpUZWFZTldbXV58ipiKaVxhZFZhjW1QVWNfWWFmZmZxcW52b29wZHGXYmlhVFxVZHRhhXdrZWpfZmhuYmxwbXZiXF9kXGBhWl1baG6Da3d7enl8fnZlbYB0dHFwXl9ramdmb29+c2pyfIBpblhbXGNeZ1t9bGJeamFgW2NmZ3FlbWJ2jIqWkYiQVot4a2t3anJyZWZucnl0cI5YZ191cHFmcXVvb2lrbGxudJxwgXdwd3WYgWxognlremyNlXlvd3N+eI5VemhudHh6RUNJeFKIkHRSX2BmX4BffIl6Y2duk2tpW19ucHB6bmxmXF9qenNpYGRuZFtVbGxmY3tsamdrcIN4dG+AeG1scXZ6ent/lHhgZHWTUIhMeE55Xl1lZnSQkYeagWZ2bHN5ZGhqdoJzkHd1emliX1VVZ4iBl391gYlrY2d/n3GGTox8bnJubHZ+hINyZWBUWpB6g3uiegF7m3oHe3p6ent7e4p6g3uregF7kHoBe4V6AXuwegF7/3oEenp6e6l6AXu5egp7enp7enp7e3p7jXoBe/96/3rcegF7kHqDe6d6AXuGegV7e3t6e8V6BXt6e3p7rnoBe496AgIEAIDJ0bnLzaWfxcXBqbeuorS6x8WH3rm2oqagrbLSt9TYvKW8tbbM3de7vtPW1vjgwLSktZ+sucfh59q/vMnb7++J8MzG1824sKuovLiztKmvvaaeytbM19/TzLXAvcGxv6ytprLC4+Dc+ubU0ra9w83NwueEzdDT0cO8uLb+8su3pYCqlqa9yseovNnc6tPw2MyZ6LzR0NrF7uS/pLmjsaGit83Sxd/Yx8TB1ZTY2LzR0brBw6S2qZylqtjY2e/ZzsuxxNy7yrmduLu5s6y2vsbD0+LGxNLA0OuD0+P+2OzkwNbhtLe76L3Tyau1wsHBsrXYub6roKemuODPxcm/wr7HxIDUzL29w8vMyrjVx7PPzNDHu6+sscjk1762xLm/zMOwqp2ZoJqUnJaTmKG5u6q3pqa3wNLDqKqtqbC4pcnK163P78jE0bvOtNXO2cDAr8n94fSF9Nroy9a9utrWx97erqKbtq2tscu0nquola2io9y3tb21rqqxs9f83tjIv7yyuYDW1cbGzdnG1MHshorX1OLg09bKt6auqsDAtdHSzrDDx7+tpaWhlqqot6SzsbvW5tbIwcLAt7+7vLqupqOsrb3YzbzA0N661eXow8W2qqnByqycpbexsYrvsqi2ubSmtbStq6+VjJWklZqFr/aoraqyrr60naOfnZealqauu7S0v4C6qqylpb++r7WwyMifnLOpoa+iqZuZhp7InqGmmZqoqauzvbW2l4ull5meoaqlraydpLKyr7a9zcWusb7Ay97SsLvExs7Txt7By9G2trK5o7C5t7uqnq/AwL+5t8LFtrvHssSzoaSwvqyzqaadnY+Un7PCsqavpKarsrinrq2+qICTuK21tK+xqKKcrrnPxsCxsqqwpKesuaGixriepZ+PlpugkqCxo5SSkJSgqqOxlp+ypo6gqaSuq5CekP+Dk7adlp2enZ6or6ymr5yisrq4vrS+tLGrnqmjuMbH1sCxqKKNh4D8h5Kgs7jUspaNkIGcr5ycppiuqr7OvcDIybCmrYC40MG6ppOEoI+OnJWXiZqbl8+8g4+bgo6jnZSYkZeVipeGlImbma2isLW4q8C81cjWwLqztbOyq6yfpKWsrLu/rLm6vrWyq7bLzsq1sre+r7/RwcbM1uPXvr/c0q3E3rCpzc/Cz8jCr9jfzdLFxbeInIeI54Kcg+Xf4+Kywr7exYDQ1s3Px7yvvaqqmp+fqKqZmpusrae1udHEqq64vbShmaqpqqKsoZWmvMG/wry7xcmxr7nHxMjt5tbKy97T38zBwMnTzbmvx7vn5r67wq7Epp+ho6S8x8KCw8TAyLSvt5aUsKq4z8TJy8XAt7DvmbLuwqu61NXQvcXV1tzCqrusw4B5gWmCh2Zmjod9Z21jWmdqdHVaim9lVVpaaGePbI2UeV5rZ2d8i4ZqboOJiqWNcWNVYk1UYnGCiH5mZW9/lZZYkXJvgXxuZ2VkdnFrcWZsfGRbf4R4fouBgW13cHFmb2ZiWWx2lJSQrZuNg2hoaHR5b49Tb3F0bF9fXF2gnHpuZIBrWWN5hH1fbn+AjXOGc2hfiGV9eYt6rKN/ZnpjaVpXZHR1Z3t5ZmNedV5/hnCHhXN9fF9wZldcWn+BiZ6GfXpnf5dxemZPY2pjYFplbHNwgZF3d3xlbYFJZ3WKa3t2XHKFWltgiGR9cVtfamtoW153Wl9QRk9OXYJzc3xvdnR5cYCEeG5qaGltbF11ZVVwbGxpX1ZZYHSPh29ncmhrdnNjX1ZWW1VNUk9PWV50dFleT01ZZXNlUVNYWWJqXHx+hWOCoXh1hG+IbYaBiW9vZoGxna5lr5ibeYNseYuCgJ6ia2VccWhqb4p5YmdhT2RfYHpfYWxjXl1gYoCnjn1vZGBaY4B1em1sdXdjaVd8TEtoYnRxcHdsX1ReXWxtYXp/fmB1fHFkYWBcU2ZncFhlXWNxfHJqZGNkWWZkaWpiX2BlZHaSgXJ2ipRxhJufe31wY2aAfltMV2VZV1WRX1pqbWlfbW5jZGtTS1RhVVlEZaVdZGFjXWlgTU5LSUZKRVNcaWZpdIBvYWJbXG1nXGFgd3hOTmVfXXBlbGJhS16FXmRpXFhiXlxrenNzW01hUlJUV2FZYmJVWmprZmxxgnZjZWpibHpuV2JtbXZ9cpB7jI9wbWZuW2FnZHBjVlxmZmhhW2NsYmVwWmtlW2JweGx0bGpiY1dXW2l1bGJpWllZXF9XXV5xYoBQdW95eHJ1bmpjcniNhH9wbmhqXlxhalRVdGZYYlhLU1NWUGBwZVpYWlxnb2t2YWx3b1pnb253c2BvY65ZZYpxanJ5dXF5fHVrcllXYWNjaV5lWFZWTldOX2t2hHpuaGdYUk2STFBXZml+Y0tERj5ZaFdZYVZmZHOBdHiBf2heaYB2hnt4aFpQZVdaZ1xdUlhdWot2RE9VQUpbWldaVFpbVWFVW1JgXm1eYWRkW2lfdGd5cHNuaWZoaGhZWFxqa3JvX2pmcG5qYGV2e3hnZ3J2anqDdHN0fIl9bGyKhGJ3hmFZd3dpend0ZJOhlZaKg3hibV5bklVuWZiWk5Bkd3WSe4CEh4CIg3ZteWpqX19iZmdXV1toaV5uc4eAZWx1f3VjXGRkYV5mWE1ccnd4eHNwe39oaHF5b3CJd3JscoBxeGdhYWx3cF9ac2uYkmhiZ1h1YGBhY2F5g4BbenZzd2ppcE5Sal9odGhudXBzb2mda2mffWhyiIR8Zm99goxyXWpdcoBsbFdsb1ZZfnhvWl9YTVthcXRWkHhzaHFwf4SjhqKlhWt5cm9+jINra3p9f5mHdGlfb11lb3uGhnpgWmNyiI1WjnNwg4BxaWZmdXVwdGxzgGddfIR4gI2IiXh+endpdGVgWmVwi4Z/mYN3dGBfY25waYhNbnJ5dWpmYminooR1aoBwXmh4f3VaaXl4f2l+b2ZWgmF3eIR3lZh6ZHtlbmRmd4aIfI6IdnNtgF+Agml8e2p0dV90b2NrbJGWmaqTiYRxg5l1gHBYbG9mY2FtdoB8h5yBgoVweo5NdH+PdX96YHB+X2VskHOIgWpufYJ/b3GPdn5vY2xocJCAe31zfHuAfoCThHl0dnp9d2mAc2N9enp2amBfZHmUjXZufHZ7hol7eHJwdnBnamZkanGFg251aGZzfox/am5ycXh6ZX58fl55lHBrd2p/bISBi3l5a4CrlKBam4OIb3VdXXVyb4yQamhleHN1d498ZG1mV29sbY9vcX1waGNlZoSqjn5waGdjZoB2eGxqcG9aYlN6TE5xbX56cnZvZ1xnZHN0Z3yBgWV6hnxua25uZ3h1e2RwZWl2f3VycHF2bnx+gX1waGdoY22Ab2JmeYBjc4SHaW9lXmB+hGRWYW1iYVORZWFxc3FmdXlvcXdeVV1pXmFNb61pa2dpZnRpWVpbXFpgXW52hYB+hIB9bG5nZXh1anByhYthX3RsY29lbWhoVm2UcHh+bmp0cWx6hH98YlBlW1pdYGljaWlbX29xbnR5iH9sb3Zxe4R4XmhtaHF1an5ocnhkZ2ZxXmRqaXJlWV9sbXBpaHB4bW95ZXZuX2Nuc2dtZmVcXVRXXm16cmpwZ2hqa29kamt/boBZem5zbWlrYVpUZG2FfnhtamRlWFddaFZadm9iamRXYGFoYG9+cWJfXFtkamRuV15pZFBbZWZsa1ppXJ1RXn9pYmpwb2t2f3t1f2xse35/hnp/cGxoXWRXZXN7hn5xamxhXVivX2VygIKYemFZW1FqeWZmbWBybH2Id3p/fWVaZYBugnt7bWBXbWFldW5wZ3Bxa5iBTFRcSFNnZWJkX2VnYm1fZFhmZHFlZmprY3Fnd25+cnd2cW9ybm1eXV9sbnZyZG9rcm9rX2JxdXRkZW5xZXJ6b3F0fYd5Z2qKhGJ3jWllgYR5ioR+a46ZiId6b2JRVUtHcURYRnh1c3JQYGN/aoBzenZ8eW9mdWtuZWdqcHNjX19paF5nbHx0XWFqc3BhX2xtbGlwaF9wgIOAf3x4gIVubXN8dnqWhntzdYNzeGdhYWlvaVlVbGiWlXBrc2N7Z2NjZWJ2fXdVd3V1e29tdFVZcmx3hHuBhHx7cmiQWWaSf2x1hoJ7aGt2fIpxWmRWaJJ6AXusegF7snoBe5x6AXuZegF7rXoBe/l6AXu4eoJ7ynoBe/965XoBead6AXn/eox6hHsEent7e+B6AXuVegF7knoCAgQAgNy2qayns8K73+bs0M67wsmzvtXVxcXEpM3g5vj01qbJur3CzM/l5+PmkdTU6t3gqamwtNbh49zp6+zl/PPL2dfUw7Trv8+C5sS7r6SztbW0x9C+t76bvbDDta6qq6ahmp+qu7eys7jIzNXlkcDd49LG1Mzv9dLWzLu/zNaExra0gKefn7y0wbWzv8zw5Na/xobfg9bBzNza0ZuSqbOppLnI5djXgtj6z9LbuOny6KXnxLjS6ZafoNDk0sGrscjSuabKs63Fyaammamsq6awuq+mx7fVgPy9vdfV/L6KgvvXyM3Bvs2n0IHPwZS/r6u3v8XSx7nDy8rI0bS0vrSboKGlgKm8tauwutO4pdj44bSvr6yysaOuq8C45pH63sPPsLTKtKS0vbayq6Gbn7a0pKipubaqqKy8vbq3veunt83GxL/K3tXW3sXUsr7GycbSjNjY3cbI3tja79jZ6N/h58CwioO3obmjnqK32bXH3rOfmKC5tbuzs7auuM+8pLW4ycnPgLfJ1Mm9ttnRx4nq0OTc7ujTztrl0sG1wK660fGsl7DH0qOjpaSSmpmYlKzHv9TTxbu4x8HAu8DFxK7F4+is3cjzzMvU3baqxc3S5MizsrSrrqijpayara6nrq6fsqunqKOQm5iWkYuEjoK15omQqZzC9+KutcLGyMGcoqzAsK2pgKmtq6bFuLmqrsfBwLmyr6Wbm5yenJ2nprGgoaChr7CmqMWqr6iika6ipqi9xNrNt6WtqqC+zLHNv8rD2dLY3YWJ/PPS4uTm5+HTwK+yrKmus8HCva2/wLnL1My7z7q3wdG1wtrauKyurK6ikoifn6CVq8zav727u6+tpbS/0NLGgM27wrCtqqOrraCToMDMuM7EyLOlpau1sbjPz8O7sJyaoaaYm5WUrbe9w6+wqrCumqyysKezocCvn46pjo2FkYqbnqmmnqCfppKlqamxy6+8qr/I5Ni2tL2/y8/AwbG6o5ukl5GUr7WnrKKor6qqio+Ok6Okn6+ey661sLq+sLGqgKmytriuq5uNmqSIgYWKiZScsbWO+4+PlpSUjIaNk5ibk42K/JqWmaCrvrG2x9DSycGzpqa1tpanq6imvNKhsLSywMK2qry4wcS3s7+yq5+xvsvF1bHI47W0uMnQ29jDyb/QxsO3uceuqbXE2uDOrtnl/v7K6pOA5MrBwrO3xPLZgOvEvtLqwqqvpKmxqKidopiyq5CNnqycoaPGtMHK5LiwtrzJ6Ijk18G/rq7GtMG84sXMzr+9z9vO1dza5O/bxb+2zufNwLKgtcPWvrjCzMKuvLGxs6y/yOLi57zLpMHVusPBuNf/w9PJz/HGrqGksLG2x+Ti5djN1cG4xLG0p7XkgI9vaW5vc36EqKKbf39yeXxtd42Lg394Y4Wbnq6cg1p5aWZrdHyKjIWGWXR7kISHUlFWW3Z/e3iEh4mBlpBrfoGDdGiceI5iqYSCeGdycm1kdXtta3VVcGR4a2FhYVVVVFBYZmZqa3F6gYSHXWeIinxwfnCWmnR7b1xgb31ScmRpgGVgX3t1gHRtcnqbjXlkbE+CVopzgpeYkV9Va3VmXGVuiHl1TnONZ2tzV4eTj1WUd3CLkUxUUXWKfWtdaH6Hb111Y1lralBSSFdaWlxmbmhcc192TZVeW2llfWxMSph4am5jZn1fjF5/bkpwXlddYWRtZmFralxleWZxdnJcXl5ggGJoZltbY3hgT3yWfVRQSUlPUEhSTV1aiGCbgm19X2R3X1NkbmpoY1lXXGxnWFdTXFVLSEhZYV5eZZVZaHx2d3B8in5/jniHZW1pa3WCYoyPloaHlYR+joCGoamlqol1Zl56Y3RhXF9ukWp5kGhTU2FuYmRjZWdmboNrVF9idHB1gGVxeGxfV3dwaFV7ZXd1ioRrY3CAbGFga2Jsf6FhTWN8imJkZ2ZYXVdMSVdmY3d6amJfamlqanFzcFtqho5YjXejgYCJlG5lgoGBj31ub29qbmJaWmFTZmZlbHBhdG9qbWZXY19bV1VOVElvmklQYVJrmYNRV1leYV9BRlRqYWFjgGNiY1p6ZmJXWnBsamRaYVtVV1JXWl1jW2ZUUlFTWlxYYHlrcHBpW2ddYWF1e4yBa1xjX1Nrc1l3X2dkbmtwc0xOk5R7g4CJj4t7cWxzZmBfZ3Z7cV5xdXWAgnNieGlkaG9ea4KDaGJmanBnVU1gYF5UZHuAZ2BiZl9aUFZfbHJugHFnb2NkYGF1e29fZoONfIx+gGxgYGNpY2x9fHJtX05NU1pXYFpbcXuEh3Nxa3JxYmx0cWl4aXx0bF91X15UXlljZm9wZWZjZ1ZjZV9icVphUV1ieG5WVFlfdH10fHF7ZmJoX1pdb3FjZF1ob2xsVVZRUl9dW25bhWhqa3l8a29tgG93d3hvcGVZY2pUUFBXVVpba3JRi1VTV1ZXUUpSWl5fXFRWlWBSTlFcaV1cYm1rcnNpV1dtd1piX11geYBRY2Rfam9rZWllampkZ3JvbWFscXt0f2J1jGNjaHyEh4JweGx5cnFobXdlYWx9lJWFbJKquLyLr3FfnoB3cGJocZyFgJNwa4OXdWRoY2t0aWpgZVxxb1RTYGxcYGJ+a3uHnnNqbW92k1uSg3JvZWqBandykHl7em1reoNydHZqb3lvZGJeboZwaGVWZHCAZ2Jqd25caWVobmx5gZeQmniCXnmCY2hjWnykdIJ4gIl7Y11fam1wfJSNl4l1eGhjcWFgWGeRgHlaVFpbYWxyk5SPcXNkaW9jbYCGfnx8Z4qdpLGljGV9bm1vdXuIhYCCU291h4KIW1lcY3mBe3R5fHtyh4Rke4KGem6geopbmIR9dWlyc3NqdXtub3hhenCCdWxsa2FfXFdea2NlaW54goCHWGSAhnhqdmyTmXh+dWlufohVfnBygG9ra4J4fnJwdHiOhXRiaUl6UH5sd4uKgltUaXFmXmt3joF8UXuYdnmAZI+XkViReG+Jk1VhYYWbkYNzeoyReWiCbmFzdl1gVmdpa2l2f3ZsgXSJU6Vxb3p3i2lMR4huZW9rb4FjhlJ9dVd7bGdweHyJgn2BgHZ+jXh+gXxobGtwgHF5dWpsc4VuXoiijWdkYGFmZFxjYHBslF2ej36LdHmLem5/hoB8d25obX56bG5vfHhsbG59gX55e55jb3x2dW51gHR2hXeHb3l5eoCDXH+Ah3d2hXt3h3V4homOkXZpXFd8bXxqZGZyjGt8lm1aWWZ0ampnaGlnbYJsV2NpenJ0gGVyeGtfU29oYlF3ZXZziINva3mKdmtpcGdxhKFkU2uGlW9vcnNlaGJaV2Z2b3+AcWxufHx7eICEgG98kJBWg2uMa2hxfWBednVxhnZnampnb2hiYmhbbm1qcXVneXNyd3JjbmpmY19ZX1N6o1JWZld2oY1hZ251eHhdY3CHenl2gHNzcWmCdHVrbYSAenRsc29oZ2RnaW10cH9uaGVlcHJtcoJtcnBpX29naGV4eYt+allfX1Vve2OCbnl1f3+IhVJSl5N4fHuCjIZ0Z2JrZmRmcIB7cGBxdnaBfXNjemxobnVian9+Z2FmZW1mVUtdXVxWaYGKcWtucW1uZ255g4N5gHdobGFgXlxqbWBSWnmCcYJ3emdZWV5nYm1+gHdyaVpaYWljaGNfcXZ+f2trY2dkVF1lYlpnXXNsZVtvWVpSXVpmaXNza2xrcmJyeHR4inJ8aXR2jH9lY2VoeYJ3e3J5amlxaGVqgIR4eXB2fXl6Y2NhY2xsZnRgjm5wanByYGVkgGZucnhydW1hbnhmZWVmYWRld3xbm1xcYGBfWFNZY2pqZ15eqGleWFxjcmppc3x3d3ZvX2F2e15nZmRleYFTZWllb3RvZ25pbW1oanZzcGNvdH12hWJ0imJka32Fi4d5f3WCfHx1dn5rYmt3hIRzW3WJl5ZvillNhGxkYFRbZY51gIZpZ3uTcmFoZW12bm1jaGJzbVJPW2VWWlt3ZnaCn3FpcXd8l1uWi355bW6AbXZxjnp+fnR0f4R1en94eoFyZ2hkcoVvZ2RWY29/aWdue3VjcGpqbmp0e46IjnB6X3yJcHNwa4ulhZOHi5Z/aWdsdnZvdIiCh35wdWNeb2FeUlyAqXoBe5x6AXukegF7kHoBe5J6A3t6e5F6AXuvegF7hnqDe4l6AXuxegF7tHoBe5F6gnuoegF7/3q2eoJ7/3rUegF5jnoBedF6gnutegF723oCAgQAgNftwsm1o6qqw7vbhZfZvNDP3NXPwbXSvcLT6uDRysLc5srLge7m5OX2+ejw/fb4tsfM2IKE14SLiJTQ59XVvrK+xMzGxtu+gNPBwq3CzM3d4dnpz83r3bC5rbzDzsSyq7KunpySmJqwu8K47ci9wbOhqLm0xvHX2s3NubzLuqWtgKelwdXYsaWtwOTx78/z7NnUuOzMyb3FpqbO47va1dXJ2+TU6uHg3eP9vLbGu/LUx8Wwwa2+zs3WvK+ckK7atqu/opaztLvDwauzsL+807OejpCZpNW9ydbQ64uD18bC3r7G3uHI0qmkmp6wyb64xsywvd7OvLS+xOOyx6yasKGzgKfS0L3O4sLFvKbFuLGys723vru/wMLr6er03treycjatbK7u7Czs5Wkoqqsqrq1raCxrLGwxq/g4dKivMTT29vYvszPyL3P0s+ytqmZsLzIyMnR39Hb5uPfxsfhxJquppvSpJOXvcrGwsjP5OXa2c29ubLQy8HDw9zPpqWmu8XKgM3CwMTCy9Pv+pja8OLT3tKtv87IxsW/zdTJt8a7rry0rKaUrKimpJqTop29t762sbi5uNPEwb/d0tvf4bqxw9W9ucrDwsfR3eDe393l2saw3c3Mpbqkt7mxsbSrsKuyp6GQk6ihnZ2SlpOq5pWltKXHhNittLe82Neoxr7DrKqigK3NjairuKWzs6mou6+wq6msnqOin6mhorKwpKvIv6KwqaacpJOin7Wcr7PczeLMvrqspp6ls72/vL7L09fT3PXo6/f14/3m87vBwLSvraGdn8HFw6K9vMG7ucqvwdG6wNjSy9Hr1M/HsLarraigoqu0o6rRrL7CwsfI0MG/urvCgNTIwLGos7+xsqrDtJ6qq7O/w62ss7Crq8HOwLK8tLawnLOumKmzxtaRhPOM38/EzMzJz8axssG/rpjA0ZijmoqUmZOj+ZacoIytmpyRpJTIvq6qvLWvnZynsrCfnI+mj4uMmpqdu66lmJ2lsbmzppunsqOioqTQtLC3tayyqry4gMK/rbjQ2LujpKGPj4eOpZSQlYqEjZipjqKekqOKjZWLlY2JjJaUlputo6+zvLezzc+5rqOjraW4tquzu6y2wbe+q760q6+pvLXI09O3n6i3wMjE3vSywKqntbm/3dnYwbWxpa60ut/VuqShwoDFqc3X+sbC8beF0bu8vbWwobHAgNLP2MjV1se9tr2rnr2tn5Wagvu2kp6XlJ6+rsHPubHTzb7ky+XOtcG5vcK+q7is0czPzdnb4svRytzYxsfJ5sjNvMfJur6tsLbJyri5vrrCx761uLa6t9bUyLbY09iC7MmlpLrHjb2zrrOnpqWVqL+twPKCz87h3/THzN7Ps7vmgJGngH9zamxqg3mVXGGKdYiIloyGfW6Ab3SCl5F5c2uEjWxuTY6DfoGWmoeZqJWTVmFldU1Pbk5VU2Fuf3FvYlxqc39+hJ9+YZaEhG9+gISUkImbgHyYj2BrYnF6gnNfWGRiUk5ITVBgZW5mlnFqcGVXXWlhc5Z4dGZnWmRyZlNagFZWcYWLal1icIeRlnSPint6aZx9fniBZWaJl3KKfXhve35re3Nxb3yPYF9zbaiDd3VfaldneX2Hc2RXTmiQa2ZyWklfXF1pZ1dhYWtnfWBNQT9BSGpUVVhUck1KbFxgg297j5p8j2ZbUFJicGNdaXVdaHVoWlllc5JqeGNUZFZjgFp2dGFsfWVnWklfWFFSUVpYXVxeXl6FjZCRe3d4a2t9XF1nbWdzcFNeXF1dXGZgWUpVT1NWbFuDh39VanB6f4aGcoGEfHF6eXZdYVRLYW17d3eElIB/hYWIdH+limZ5ineOYVRXd3t3dHZ/iYSDiXVkZ2mEfXV4e45/WVRYanJ0gHh0cndycnKGkWBxhX1vfHVWanlzb2djbHVrYHVtYm5rZGJUaWZfXlJMV1FmXmZfX2loZHljW1dsZ2x8gl5Wa4B1cYeFh317gH97fXqMgHRpkoeIYG5ZaG1pbG9pbGpya2RYXG5qZmRbW1hokFBbZFNrUXNRVFhWdXhTbWpuYGJYgGWBY1lfZlBfY1xda1lZWVpdUFVXU1lPSlRSSE9pZlFdW15baFliXGlSY2SNfZJ0bGteWlRYW15cXVxmcXNobX95gYiCe52Gk19jaGFdXVZWWXJ2e2R4dnd0cYBncn5qbYB7dn6SfHVvY2pfX1tWV19kWV5/XGdpZmVobWJjYmJkgG9nY15YY29qb2h+c1pscHSEhHRtcWhgXW51bmV1aG9lVGhmVmBodoFEVZ1Zh3ZteXR3fHBkaHVzaVh+k19qYldgY2NwmmJmalpwYmBXYVN7a1pXY1xZT1BfbW1jZV90X1lcZWNmem1oWl5ncXlwZV1od2ZlZWuWd3V6dnBzZXJvgHR2anePnn9tamhcW1FPXlFSW1RTWmhzVF9fVmFRVFpVXVlOTFNQUFJXSVBWZGdldXttaWRqcGFyb2RnamNrcm1wZHpwZnFncG+AlI53Ymp2eX53iZpgbFxcbXF0lpSRd2xlWGVrdpyReWhmhl2DZH6HoXB0o4xdiXd5e29pW2t0gIF+gnd+gXRvaHJpW3VuYlpkUZiCXWVdWmJ8a3eEdG6FfGqJd5B/ZHNudH56aHRohHd8fIOEhnN5c3x3YF1gemFoYm50b3JfZWh0d2JcY2Zsc25lbm91dpOLfWqFe3pTlXRXV2x5Y25kYmdfZmZUY3Zhdp9OdHeLhZVnc41+Y26cgH+JaWdcWl1gdnCIVFZ9Znl5hX9+d2yBdHiHnJiGfnSGjXFzTYl/e36PkoKOlJSTXmhreE1NbUlOTllugXh1a2dzeIN9gpV+V499emp2e32RjIiahH+ZjmZ1cICGj31qY29sYV5YXmFscHZnlHZvcGVYXWljc5N+fG5wZWt3bmFrgGVlgZGZdWhufpKYnHiTjXx2ZJN4d3F6XFx9jmmAd3VxfoV2iISFg4yebWp7b5+BdnJgbWBzhYmTgndqYXaYdnKAZldsbXB5eGZvbXx8jnNjXF9lbYhzdHRwgUtGbWFjfGlzhYl1h2xlW15se3JrdYFueYp9cGt1gJ15iHZpeWl4gG+OjHeAkXl6b15zcGxucHd1d3NxbnGWmJWZh4KEen+Pc3aBhH+CgGNsaGxsbHp4cmVwa3FxgnGVlo1gcHJ5fISCb3uBfnmFjItycmdebnZ7eHh5f3F3gISBbHKMeltqc2aHY15jgIJ7eXh8h4N+hnZoaWmAd3R2eYqBW1phcXZ6gH90cHJraWp+i1pugHpvfXRXa3t2cmtocXhwZ3xyaXVya2pacnVycWZeZl51bXNraHJ3dIt9dnSLhoWPjGZUX2xgXnJtbmltd3x5d3ODe3RpjH5+ZXVjc3ZxcnVucm94c21eYnZwbmxkZWFymFljbFt0V4JfZGxukZZwj4yOfXpvgHqUYW1xd2Vzd3Fzg3JxcXR4aXFxa3FmY3JxaW2CeGFsamplbWFsZ3NeammNfZF3bWlcXFdeYGZoam11fYB5gpKKi46He5qHkmFobWllZFtcY4B/gWd3dXRrandeZ3ZlaH95cXmKdnFqX2xkZGBbXGdwY2eIZnV4dHd7gXh6fHp7gIR0a19WYW1kZ2B3alJfY2d1d2djZmJcXXB+eG58cXdtXnFuXWVpc20zTIdPdGVfaGNnbGRWXGpsY1N4hVhjXVVeY2N0pWlvc2N9bW1ibF2HemdibGRgU1NgbW1iYFpwXVxjcHB1jX54aW1zfYZ8c2lyfm9tamuRc25zcWdoXWlpgHFzanaPmod2d3RjYllaa2BiaWFbYmx2WmdlWWZUVl1caGJXVFtZW19nWWFncXFxf4N2cGhscWZ3dWdqamNrbmpvZn50bnNqb2p9j4p3ZG17foR9kKBlcGFhc3d5mJiXf3JrYWtudZOGa1pXb09sU2dxhFpcgmpOeGdsa2FdVGFmgHNzd255em5saHJpX3dyaWJoVp5+XmNdW2F7a3uGdW+Ig3KSfpWFb311eoB7aXBkfnN3eoeJjn2Ge397aWlrhGxwZnF2cHJhZGh3e2liZmZudnJob25vbISCemqBe35VnH5iZnmAXHt3d31zcmxZZHVibo5FbG6Ef5BkbIVzV2CEi3qCe5Z6AXuPegN7e3qEe416AXv/epN6gnv/eoR6gnuoegF753oBe5B6AXv/eqV6BXt8e3p7mHoBef96qHoBe4h6gnubegF5y3oBe4Z6AXuNegF7jHoCAgQAgNmF2u/ku6ynrJ+iosDa4Nvczc7H2tLZxLrJ3//Zj+ni5sbFu7zSw8vT3d6IkeSO4dzRv8/09ub5gZuE7Ofh0szH08XL1b/Ax8nCzca6usvGxuft+YC8trappsbHvMXBsLCio5eiqKOqoaSts7OjqqfFs66764Dj35L51rybp7a/gLmtppKNl5qTjqrVxb/I8tvf7/Hy2t67u8DK1cjX0dzZ4O/j4cfRybvHvcnTwMvT8NeytLKjlsnWvLC3ppmlkqqWrbLLz8LCvaqqq7i9xrSjiqKvwLnC0IOPjofs9NHkitrGw9SsoKSZnJ+rycbRsrjZ0cPw9uf99svArKOnrqibgLrUyLSxqbC6sKKssc3i/uC+tqa/yb6jrcTjm9Xk5Mrit763qZyfm6qrpai2srG/uNrNp7W2x7nL4v7Dxt/n1s+3vtLDr6a9x8Syq6KroJ+rutDSgYeJge/Ywb740Larm6eps9Xavbjb1MDEydHm7NDW4s7Tp7W0tLi0s7S0srvDgLXUv8+2wb7Nz5DE4eny4MvBybu9yra6pbGo64bdtr67qrS2sKyjpqGisKe1oa2kpcTE0MPDx87F19Da+9W9t7zFv6ne18/d5trWytzMuL7TvrjPvru1tMDHvbKzp72voJuYl5yek5icpKOhm6GWpZ+v1+rGsKKkxMvOwLKnsMGdgLzh1Z6drLe3p7G2nq2lpaOcmrSzp6Otu7Gvs7O/vcXIysKer7yttLOnuLO2s7Kyu7WsrbSpo6qwvsG7u73TytHQ39fCxcLK9dnNwqaTqqWTnJm4wLTLm7ywr7razNTc2dTjtMLS2crNvM3EyK+3tra6wr6+t6+xp77KyMvk0cDQgMrHuay6ybnBy+j2yZumrbrF2MTdzbiVmcHPwrGuwtbQxLy9uMfLv8Xu39vm5vmBiIju59a9tdPfua/L1cDKsqOatr+glIuho42cnJeqxauarqawlL/TvbDs4bau14+Mmaejt6GUnaKhpriyrregpa6xrKekqMC8pre3xtTOzMzcgLHHyre7xbWvrqyXopOPnJGGjISMjpetopuiqJqRkJCIjYulpJmXt6+1qaqfp6e/urWrm6Wtr7C9ssPJx8TB39G3qKqZmqmqucjF2c+kprTM6s65q5qSrKWp6eO5sbC7vLXCna20o6/rgvbrw8HIr7bKuLqxz+yA5v+7sLXatrbJgL6yv77Q6brCsrOttrWzqpmnuJaUn5SLiJyxta6rqKuqqsnKx6O63a6vpZqmw7q4r8HW08nLv7rG1MXDysvCzMfH1bG6taijs6y1sa61rbWzxuLSwMO/vuHqzsLGz9HHhMqjrre637u2x7ewn6Gkl7GmsffZxc+Jys3Lv726xbfFgIVakaedenBrcWJlYHWSl42NgoF5i4SGdnB9jq2GXpWLjm1rXl97ZW52f3pXYYFSfnZqW2uPlYaWUmxakYl+cnFyfHZ7hX+Chod/hIN0bnd0b4iMmlNta2pgX3t2aG1rXmJWWU1VWlRaT1BUXWFVX1lzYlxjh01/eleVd2ZLUlxcgFtTTkE+SUdFOkxzZF9ojXp+kpeWhItrbHF5hHaAdXx1fIt/fWVsZ1pjXWhzYW14joBdYF9XT3l7b2dpX1JcSVdEVVpsbmJkYVZZW2hrdGBQPElMVU9RZEVOTUiBinGEVnx1fYtjWF5XVFVgdnJ8amp/bGeLnZimm3BvXFdXXFdNgGVza1hSTFheUURLTWJzjmxTUkldZ2BMVGiCQXB9gm2JYGdkW05QS1tfW1tjZWNtZ4R4VWBhbmBuhaNucIaSgoBucoVzXlZrc2peWFFaVVZec4eBUltcVZqEcnKlhHBxZWlkbYyXdnKNf2tweXiGf2l4i3V9XWtsY29oZWJiYGhwgGuGcn9mbGZ8dlpjeIKMfmxhb2dndWVrW2Vfl1iNaWxoV1tfYWFYXVVTXVplVl9TT2xma11ZYmtlcmt2nX1pZHKEf2KJfXN5g3NwXW9pZm2Fb22GdHJnZHF7dG1uYndxY19hYWNsX2ZmbGtgW1lOVUlQbXhbSjw9W2RkXVRKWW1PgGmUhVJRYGVhWWVsWGFVUlNMSV5hUVBYYFRPTk1bXGZscm1RY29iZmFaZmRqaGZmbWxjXGJaTk5QV1xcW1lwZ2lndG1cXV1plH5wbVZDV1JKUlVwdmh7XoFwbXeQfX6Cgn+NaHR8g3N2Z3JqbV5nZF9iaWRmXlpdVmRwbG6EdmZygHBnXFdidWdxf5WwhFRfZm12hHaRg2xMUG59cGJba3twaWNpZHF2aW+IgHd+f41KUlGFfW9eVnWCZGWAj36IdGtmg4ZvYVloZ1NdXVpqe2RVZFxhR2Z3aWOPjmliiU1NWWZic2NaYmNkY3Nwa3hdZWtwbWlobIB8aHdzf4N8dXeJgGd8gXB4fnhzcmhRVlZVaWJbZFlgXV9tYFleY11VV1tWWFNnYVlUZlpeUVROWFlzc2xqZWxvbG51ZnWBf4CAiYx3ZWphXmlpeICAlYliZm5/n4BrX1JMY11jnpRvZmZub2Z0VWdyYnCuYLWrg36BaGx8aWRheZJVkapqYmuTc3OBgHVoc3aCmG5xaWpnaWtqYVZiZVtaY1RMSlptbmReY2dlZH6Af2aCnnh8cWpzg3ZwbHaMhHd2bmdrcmFgbWxlcW1ve1xpaFxcamVqYmBiWFxbcYt6cHVycpSdhHp5fXx0VXlcYmRnjGlqfXBrXFxgVGdbYaSHbXVYdnt7bGtsfHJ7gHJLdoeGaWJhaWJnY3WNjoOCdnZyg36CdGx6jrGJX5OIi21sYWF1ZW11endSWIJRhn1zZHONkYKISGBUjYd+dHZ2fnp5gnl4fX96fnpsZnBtZ32EklN1dnJpaYSDeH98cXZrcGVrbmhuY2Rmam9iamJ3Zl9mhkx7d0+MdWtYYWppgGhkYlZSXltZT2CEc2hukX59jpORgIRnZm95g3WDeoWAiJGIg292cWlxanV8anV+k4JlaGxmXoiMf3h+cWRsW2hXZml6fXR3dGZnZ3d+jH1vXHB2gXl4gE5XU0uGhW9/T3FtdoxvZmljXl5oe3iBbml8c3OYppupoX+Cc29yeHJmgH+RhXFrZG50Z1lhZHyLpIhuaFxrcWpWXW6FTX6KkH6UcXd0bGFgW2tuZ2dwcHF7do6FYm5xfG98i6Z0dIqRf3xscIR3aGZ9hYF3c2x1aWNkdn56TE9QSYh4amuYfWdnW2Bjco+ZgHuUhG90d3eBe2V0iHV9X25qY2tqbG9tZ3B5gHKIc4BgZmFwcVVdcn2KfW9odG1we2htWmNdkVSHaXFxZG50c3JtcWlmb2hzZGthYHx8iXt6f4Z9hXd8mHdlXWFrZk1zbGZsdmhmWGplZGuAcnKIc3NubHZ+dm5xaXt0amVlZWptZGtvc3JqZWZbZFljhJR2Z1pde4aKhHlwfY5ugIWqmWtqeIJ+dHyEb3hvcHJqZ3p5bW13f3JubWl1c3uCg31fbnpxdXRqdW5xbWtrcGljZGtlXFpaY2ZjY2J5dHd2g3lnamt3oIF2cV9QY2FYXlx3e22BX3lpY2l+bHB4fHmIYW13fXB1aXZydmRvbm1wdnN1bWpqYXSCgYigj3+KgIJ2bmRve2lwfZisf1BbYGdve22EeWVJUHSGfW9pe42Ee3R2b31/bW+Cem50dYFFSkh3c2hUT2l1W1pyfWx4aGJffIFuZWFwcV5oaGZ3iXFgcGdpTmx+bGaUkGxpjFNTX3BugG5lbnF1d4N+d4JqcHR3cm5pbH15Y29qdXx3c3SHgGd7gHB4gHp3dXBeZWJic2tjal5kYGFxZGFlaF9YXF1UVlRpZ19edGttYWFZY2eBfnVwZGdvc3J2aHR9e3V1gINyZGxjY2xrdXp4kINfYWx9mYBtY1dSamNpoZp2cXJ6eXF8XmxzY2iTT5SIaGhqU1hoVlNSaIFMhZhhWV+Ea2t0gGtibm99kGVsaGhma2psZlxmaV9cZVdST1xvb2ZhY2ZiY3p8e2N5knN4bWdvfnBqYG2BfG9yb213f3Jwe3ZteXZ9hGZua15damZsZmJkW19ec4l8c3NxboqQfHVydnx5Vn9ja3F5mXt/jH13ZmVlWmxdXZN9aHFSb3NzZmZndGltAnp7m3oBe416BHt7enuJeoN7mXoBe6B6BHt6enveeoR7hHoBe/N6hHuyegF7kXoBe/96/3qUeoN7/3q4egF7jXoBe+h6AXuWegF7iXoCAgQAgNju1s7Vw7SnoKGUmKa6tr+3vsCvpr7uyrK7sNiE+aqP69DDwcPatIKGgNjbgdusqb63v8vU1Kms4e6B2eHeiPm8zsbFxs67wsy2tcnukZWK/o3q/uDTyMnAtZaqy8ignp6pp6uvobC4tMe6taGhpbOruqzwkIfi4aHZ38OysLHAgMjLx7mrqaSfjq22st2T1NPfjfn60uK3yMnIyrr51Oz12vXv3ODg29PQ7ejaztrLxsO0ra2rudWIxbO3y62quq+nrcngvqKytLS7sZ2nu6ShqLq4x9Ln0fnbh4Dx2+a5s66hqKejk5OKj4u98bC64cy2tdTGxtT838uupa6vo6WmgL3QxrCqubPHv7ezw8S37vHr2cOyxdXFraalvLDGvsSysLynqLCypbeura2rvcmvrKTO4+fz1brGxcr9gurY+/vUxLuystGzrr+1lLGzpLPTjrqO3YDxyoDv39vT0szJ1pOT+sC9ybGxrrnPw72/vtmH78HDwbO8sbioorrLzcnEgPLWtbDJyNzQ0vDW7fL84dPM0LiturzCxdTAu7/Bysq2ur7YxreypZ+nraOs6623rrHRysXHvMbMzOmKgNiuxMvc4OLi0tbj8uvMwr/P88LIwKinp620xN61r5+jqrbEsrGzq6Glp7mlu6OqmZappq6gucK3rbiqtqyvvbe0tbWngLK+t5+iqau9t7quqq6xpK2bo6+ssLvAwr/Dv8nDrsrs2da4q6issrW4ta+orqShl6CysKGhh6uvqbvHusHG2cnL1s64wsq60cfFqarEzbqspaqxp6aqtcfMl42uyc+E48/jx87RycHAtaq0vcPAzr+/0MOtsbXC0sy+zLbbz8HKgMizq7nLyNLh28HSub+1uLjG0suotMnPxLa2uMS1vruyu7u1paO9s5WgqpibhaKbsq6s0bittcK8rpCg2arew8KxurexsaKXpJ2VmJWgrJiPl6OgmIqXpMDDtqe1oZiYoYmYo5+9ppWYnqW2wsy4prKwlJqvmpy3r7zCxtq3ztbogL680cCuxNLHqZeXnoT2hoyNhJKel6atoKainpmVjJSflJiQla2msqGquKudpKCnoJylqZ6orcHP1sPCxra8sqSgoKKjmKPmysDd29XYrqiopqScsLezrpyexdPc1dzf4aO0prPByNm0zrLN3MS9xuTSxMfchoLn7+O0vtLd08KpgJ+Rvb6qraCopp6ap7WqpJSdoZOakLq+uLq2wsK/rLTBos+oqb+3wqy5vbOpt7W90MDKuMvN0MDR4cm0w77StaS4vLixo5u4t7i3w73MsqTH2ufJz9LC1tjlzezPw8XBwsztyLS9xMHBs7Oyr5mbmN6+uMfS1+jv6NDIxbCywOPVgISZi4yQgHZsZ2VVWl1vcXlweHZrZXiZe2dqYoNVl25aiWxiXF1yVFRYTnV5Tn1SUmJYYW92dX54fJFSfoWAVqFrfXx3d4R4eoR0a3qaYGJZm1SHpJCHfHduYExfhX5WVlVcWFlcVFxjXWtjXlBQU19XXVCSXVCBhHGGj2xYUFFggGdqb2VXWFVOP1VhW4NTfnmEYJqfd4hfbm9tbl+UeIyafJmOen5+d21siYR3bHtybWxbVVRSYnlab1xgcGBXZVpTWHCDZlNdYGBnYVVea1hVUV5ZXmR3aZR4VEl5aHVeYmJXYmFkU1RNTUp0n3FsjHtmZYh9fISjgXNcTFNUSk1NgGBpX0xKVlJgVVNRX11YhoqFdmdZZ3FmU1FTZltvZmldW2RTU1hVUmNaX15ZaHNfWlJzgombfWJtcXWlVJaKpK6LeG1nY3pfXGhqUGZiV2OIZI1jj1KYelKVkYeHh3x6gWJhqXpxeWViYWZ1ZmRmZ3dQf19qaV1mXWdbU2l1c3FtgJKDaGZ8eYd4dI95io+agnF1emFbZ2xxcHdoYWJib2tZYV5vZlxdVlljX1dfm2BoWV98fG1rYWpwcY5cU31cdH2JiI2IdnmAi39rZ2R2kWhsa1peZWdqeI9uaV1cZHB7bW9wamFoa3pmemRjVVBZVllKWF9USlBFTUpMWFRRWF1YgGBtaVlaYV9oZ2tiX15dUl1OUVdTT1RYW1VXU1pbUXGKfYJtYWVkY2JjXFdSWlddVFtlYU1INlVUT2BnWGhseWdpcmlaY2xidW5xWVVse2thXV5oW2Vwc3p+W1V1kItZiXGHdntzaGVpX1RcYmdib2ZldWpSVFxjbmpgbVyAeGdsgGlYU2NxcHqIgXCJb3RwbnJ7g3NYX3JyYlZaXW1fa2lhbHJwYF53aVFZX09SQVhQZ15geWVdYW9ud19ajmeWgoBweXVycmRZYVxVWVpjbFhRV19eVkZPWW9sZFpeVFFWWk5bZGB2Xk9RVFZodoZxY25rW2FxY2R5Z21ydYdoen6QgGtuhXVlb3tzYlVdaFSnW2JiWWJpYGVsYGZjXF5cV2JtYGBZVmJbZFBYY11WYl1iZGdub19oaHaGjIGCiHmAfGhka2ZiW2OfgnSFiImNbWloZGFYZ2pnaVhYeYGNhImRjFRiWWRud4dpg26Ij4B3eo+DcHKBUk5/i39ZYniEf3VfgFtReXtnb2JsbGFdanVqX1NZXFVWTWt1a2lpaWpnXWptVH5ZYHl0eWh5gnp0enBwf298bn2ChG9+i25WXVxsW1Nnb21tYlxxcGxoc295aVh3i5V6fH1yiY+ZgJh+cHJtbXeVdWJnb2pyZ2ppZVdZVplxZm15gJCZjnh2eGRkbIKFgHWBdHeAdXNtb21gYmR3dnZtcnNqZHqfhG11b5BaomtVjXJpY2JwWE1UTH19TYVcW2phZ3F3dWljdYlOfYSCV6Fsenh4d4NzdXlnXWiCUFJLi06CmImDgH97bVtvj4toaml0b3N6b3d+eoZ9d2JjZW9iZ1aKVUpzdWJ/iG9dWl5wgHp9fnVoamVgUGdxbI1hgn6GXJudeohodnV3dmaTfI2WgJaOeoCCf3h3lJCBdoF5d3ZnYWJkcIdceWpxgXBndWxkaICRcFxna2x0bmFuf29ub4B/iIuUhKSJWE+Db3JeZGljbm5xYWJYWVR6lW5rhHJlZ4h+f4uvlop2a3V1bHBuWH+KfmhmcWt4bWhpd3Ztmp6YiHNibnZrW1ZXamd5dXpta3NnZ21rY3JoamhkcHxraGKBjZKgh251dHWfUY2BnKiId21rbYh2eIiGZ3tzZGiBV3ZYgUyKZkSEf4B6c3V8Wl6lf3qBcm9qb3ppZGdmdk+AZHJzZm5nc2dhdH5/hH+hi2tld3aAcGuGcoaMloN3eH5pZnJxdHN7bWhsbXt6a3J1ioBycWpmbWtkap9ye21xjI6AfXF3dnKKVUx3VGRncXF0cWNlcH52ZGJdbYhqcXRmaW9wcXyPbmhcYIBrdYR2d3VvaGxwfGx9aWxfW2ZjaFxweG9ncWdybnOBfXyDh4GFkIZydHh0gYKCfXl4eW96bnJ5c3J4fn93fHZ5dWSAn46RfG5ucnd7fHRsZGxlZ19lcGxbV0NjYlllal1obHxrcHtzZnB4boN7eWJccX90a2lsdmVqcG51d1RMaoCBfVF/Z39xeHZrZ29oX2ducm6BdnWEeGBjaHB8fHSEd5aOfoR7aGJve3iBjol4i25vaWhrdn10Vl9ydGhhaG16bnl2cXp+e21sgnRaYGZUV0NcU2dcXHFgU1dhYGVTTXpXhHN1a3Z0eH1xZW9qY2dob3dkWl9mYVhIUl11c2phaYBdWV5jVmhyb4VxZGZsbXuEjndocm5cYXFhXW9iaG5ygmR5fo5tcYh8bnqKgm1fZW1brmJnZ15kZ2Frb2NnZl9gWlFZY1lbV1lnY29dZHBqZG9qbWtpbW9kaml5hoZ5d3pucm5gYWZnZV1kl3twfoCAh2tqbWpoXm1vbG1dXH+JkoCMkJaVXWthanF0f2J2Xm5zZV5keXBfaHRKRnuDeFZcbXx6cVtZT3Z7aW9jbW5kYG12bmhbYWJZWVJvdXFtam1xbmJub1iAX2V6dXlqeH12bXNqanhocWJzfX1se4t5ZW1qfGlgcHZxc2ljeHRsaXRwemdadYWOdnl4cICAiXWQfSRwcXByfpx+b3V8eIF4dnVwYGFclHJob3N4h42JdnV4ZGJldHOcegR7ent7h3oGe3t7enp7iXoJe3t6ent6enp7jnoFe3t7enuiegV7e3p6e5R6BXt6enp7pXoBe6F6gnvgegF7lHoIe3t7ent6enuIeoJ7jnoBe8l6gnv/eqN6AXvZeoJ703oBeeZ6gnv/eot6AgIEAIDf39DWwaGeo5+uta+xsrC6v763v7G3+efYu8/j9oSW8ObyxsjXu6/H2NfQwszJurS2s9iy5PPg09XZwPPPw8+M6tnDxMGwsM7Z5fDAzeCAgouC19rV2Ka7tLS1laGpmp2jn6u1rrOop5y+p56qnp6ktK2+3crE1tLE3oftzcHL0IC7t8/vwb29tJu3wb6+672D9qqK9NLSy9HS5tzm7un37+T6huqI6c+99ujPtcPe6+706+/pxdKvyri/u7K5qrS6xLrO3emFyNDCrKeut9S1srK5td7XztLq54vdk4PxuJ+8rL+wrKmttJKYor/Fpq6dmqC4rrSqoKerrqWrqbCoqoCorrq+ubm6scXWwcy7xODo59vd+dvDs6WkqbnJ+8XCus+uvLvEq6enqKmyurG1opKKq7nLyqi2wNPCzce2uNrh2s/JsqGkm66ul6C1x8/e+tz09f7e8ezfgfrqz9O9w9nKw8zFy9XLm6m2sMHO+de5lKfHzdy5saO3tcC4sru3uIDk08/F1LzCrcDF4Mvg5tzdwbWr2cC+uLnGu7q8zcGuvMDAvr3Ava2ajJ+dl5yss8Omlai3t8HX4MWAxd/a64rc6s7W1szNxc7Hu8K+09KvuLqsprjAuMSltbOlr8K/u7jBtJqhmbGeoLWrrquss6imsbXArMS4vLq1pqCyr6CgsIDFpq64p5+mucKsta+6v7yyvMKxprWsrrm01YDF06m71Nnav6Ctp7/ArK25tKOpoaOwqLGamrrKsaKstMXOwdjf3ePUyLfN9c/JxsG4x87Ivr+uqKalrsW7o7K0xLnopomixa/IwrO6vcfKxrHG0NHT0c/Es6fIydvTyL+6uef7y4C7rqG+zvSG+M3G0Lq+xru5u8fVraTBp6SoqbHCxM3RxcO9u7yqsam0oqGhwsWQnay6ocOxuLywwunI0vjr4ry9pbu/oraej5WVj62tsZemoLW6p6agtqiitbaqpaOzurS1sqef4rGWoq2OnL6oipmnm6a9pqeru7G5srm1qb/L3IDDxs+xnarCq6SbloaGjJqXoY6gqayfqafCwqSnk5qzvbCjpaO7sKupqJGYoKOWnZSOoaail7Otra660MvDxKKTraupqsGH7uXa39W+u7nCtrmgopy5tK6ptse9ubnI1+G80My/vdCS7N7Z6M7Fu+TwjPHT2dPe0+XNxN7Pxb27xIDTt+mB28Gyop6nnaKhlZueioqOlJWotMvU0Lm/sK7J6Lm8r7epsri2v82zq6663be7xLPC4r7Kw8fNutbEzdrm8a6gtcOmmZ2Znre/y7+0wLnL0ODb1OPQ5dfN2sLW5NPN3MPOzMDGwtinqKadqqnZ4sqtxPzz9Ojf1tC8nqvl8YCNiXmDgGtmZWBrbGdvbmdscXZyeXFypJiLcH2RmVJglISOcG96ZF1wgHxwY3BqYF5dWndchpyEdHR8aaF4a3dhm4p9fXdoaIKGk5RkbYBNU11WiI6Lkmh/dnFvUl9kUFFST1hgWVpUV0xrWVJbUFBVX1ZhgXBqfnhvhVqMbmZsdIBiYXuZb2hrXkldZ2NkfmhVmnlemHd0bW5vf3mDjIeWkIWgW5Zdj3dmkox2XWiDiZCTiIqCZnxhdWt1cGhvXWVucmZzf45Vd3pwYmBlboZoX1leWYJ9fXGCeExxWFOXaVV0ZXtzcm1zellbXXZ8X2RaXWFxYFxTUlxcYFJXVFxXVYBSU11gXV1fVGFwWWNTXHN5e3V4mXtiW1BTV2Vvn25qYHJWYGFrU1FVV1VfY1tfU0hDWm98e1ttdYZ0hX1ubY2TjIR+cV9kXGpmUFVmcniDoY+qpaCLoZWKUZ+Ug4p5fIRraXRvc3xuSVhnXGVnhW1dQUxmYmtRUk5bV2NgYmhnY4CIdHRufG10Znh5g219gnyAbmNdhmxmYGdsY19bZllIUllfX2Vtb2VaTV9bV11rbnddT19pZnCBhmxQc42Gj1iDloCGgHFtZ3FxY2NieoBhaGtgWm52am5TXl5bX21raWt4b1xhXXlpbYBxbmlpaFlNUVNWR1hRVVJYTklaW05UY4B7X2hxX1hdbHFeZmFlZF1RWV5RSVBLS1hXckpgbU9jfouOelxiXW1nXFdaUlBXVFxsZWpSTF9pWVNXYHFzZoCHf4R4bmF8on52dG9sd3pzcHRoYmNfZHp3a3ByfHehalRrgl5vbGJoaGRhWlRocXN1bGxmVk5kZnRvaF5aXH6QcIBhV1BmboNOlHV2gW9xfXt9gI2VbmJ5W1ZTVl9vcHZ8bm1oampaYFllVlRWbXJETFthUWpaYV1aao51fZ6WkXF0Y3Z3YXRgVVpYU2pzd19qY3RzZmNcamJfcXBtY1pmbWVnWllWkG5YZ3JTXXpmTl1rYWh5ZGdqdW9uam5tXnBweoBlaHBaTVZsYF9eXlVUXGlla1llbHVna2h6c2duX2N6hnZmYmNxY1leXlNXWVxcb2FWYmRdT25rbGxzio2MiGpebW9taYBinpWMjId2cnZ8bWxWVVJpbGpjbHVwb2pxfINdcHFpaH1hk4qJlXtzZYWOXI54eXN7dINqYHJqZ2ViboB8X5dZiHNlW1lhWWBgVVpcSUtRVlVga4CBgGprX156j2psYGVhb3FyfotybW5xkGhrbGFzlnF3cXB3Y35qc4KMmGBcb3poXWBcY3Jwemxlc218fIqGgoqBkYaAjHKCi3hqdl9vcGhtbItfZWNZYFqGj29YbqKYlI+HgnxnSVSNmYB/fXJ9fG1pbWt3e3Z3cWlwc3FscmtqpY+AaXaJk1BYkISPb299ZF5zg352aHRxaGVlYnthhZmCdXV8apx5anhdl4V1dW9gX3F0fH1TX21DRk9KeoGFi2yBenZ0XGlzYWVoZnJ9eXtwcmiJdGtxYl5hal9le2xlc2xjelKJc252fIBraXyVc290aVVpb25qjmVPk21Yk3Z3dHd4i4SOlIyUjoaUUIdRg21hi4dzXWqGjpSai46FbX5ofHB7enJ3ZWpxeW97ipVZfoN6bGlpcY14dXd6eKCemZCZkFV+UlGRdGR/c4h/fn6Hj2poY3V3YWdcXF9wZ2lnZ3Fxd291cnp1c4BwcXl7eHZ2anR/anZmboGIh4KEmHxlXlZZXWl5o3l5cIFkcHN7ZGBkZWNudGxwY1lTaXeGhmdycn1seXNmZYCJiYKAeW1za3h3YWZ1fYGHl36PiYx9iIJ4SpGDdXxtcH5xcHt1eIV7WWZzZ21th29gRlVxc3xmZWNycX12dXt3coCVg350hHR1ZHNxeWZ5g4GIdWpkj3dyamtzamlreW5eaW51c3d9fXNlW2xoYGdydn5mWGlzbHJ+gWZMZHhvektse2VvbWVjYGpoW1tac31ndHtzbHd8c3leaGZhanp6eHmDdWBkX3ZlaXlwcW1ucWVcZW14aH13f32CeHWIhHV5hoCYeH+GdW5xgId1fnuFhoF5g4l4cHdxcHhyi1d4hmF0j5iXg2lzb4J9dHBzZ15rbHOAenxgWXB1YVldYmxvY3eAfYR8dWiBpYF7e3lye355dnpuZmlmZ3lxY2lpdm2TZlBlfmFzc2dydHNzbWN5hIOEe3tyY1pzdIN/eHRxcpqshoByaF5ud45PkHJ0e2Zrc2xwcnuFYFduWFRXXWh4eoSJfHl2d3lqbWdwYV5gd3pOV2RrWHJeYV9XZYNrcYyFgGZrYXZ5aoFvYmZnYXd+gGZwaHl2ZWNdbGNfcG5qY19sc3Bzb29po35lcXxfaINuU2BsYGRyYWNkbmdoZWtqYXd+i4B3f4hxYGl+b2xnZV1bYWxna1tqb3RkaGV0a2BnWVxveWxfX2ByZl9mamJnaWlkbWFbamdfUm5qaWdrgIF8fGFWamtrZ3hUjoZ+gH1wa2x0a2xbW1lwcW5lbHhzc3J7hYxnenlsaHVVf3Jrc2BaUnF7TnttbGtzcIBrYnFqaGdjboB8Y5RYjnpwZ2ZuZm1rYmloVlRZW1tlcISFgmxwZWSAlXFzam9ocnFzfolxaWpshWBjZ15yj292dXh9a4F2f4qUnWZhdH5oXmFdZXRyemxkc3CCgIWCe4J4hXp3h3SDkX9yfmp3fHV5eI5pa2pgZWOMknRdbJqUlpKLhn5qTVKAhp16gnugegF7jnqEe6h6AXuUegR7ent7j3oDe3p7oHoBe5N6BHt6e3v7egF74HoBe4R6AXvbegF763oBe/96v3oBe6B6AXuJegF7knoBe/x6AgIEAIDty6Sy0Lmmt7jBz7qrvrzQvdHRt6vvzbq6tdfb1e/v4u7kvoe9pqO3t7mxyMDDvsnDy8/DzrLczcO2sb7DuL/NvdmC7dzEp7PQydqL1t3V24f00MvTxLWptbavt7Gxq7GbnaCgn5eqrqSkn6WqqKWht7u809+Agorg+/LC39zMxIDFzszN5L7Mv7C9wtvk48G/4Y+G5rS+w9jCxLSuqbOC5cLc2IWN18PC4eXgxNnPurepvdW4qrnA2+Hd6rTJyL6zuqXLyLvEub/NzaidusvTpe7Nv77Eu6eyrqSYmaiZmJSyk5SOhpa2nKPWsqi+uM7V0LeqtrW4s7W0zd3UuK6hsoC4tLTEzsvK2PGNgNi73/LL7eC83MS5ucOstq/N1OLD2NrC9ueyqaenp6arqaqZoJeIjZOpvsTI5ezQ0tLO5dLQn6atspSJobfUxZyn0s3D5OaF7d696e/bvMi8zZHAwMLNzdLF1ee0t8/w58qvybSnsMm7r8fIv8nDxrerwcPayoC1x8Dey+HoxcK9zcXI0MGtur/G1+zTur7L6cfXy8PIxb/Fsq6dopqiobavsb2zqKCxla7TwdvZ2+iJ7vba6YzDubji2c65xta+rLKtqqWkpayrtaisrqalsp+dpbe0wLbGzK+lqKyyqazXuLKctrKksNCqs7SzqqyoqK6jpbGdnICcl6Cfm56vrKirs6mjyMCws7a2wtC3q56y2NDu0KSlrZucsqqsrqaiqKm1u7fBqKK1oqCeoLO0rLTBsMLT0t/o0M/cx8O4vs7Az8S9usW9sci0q6Ogt7zJxLq1ytfanJbLrKyyurm3qa6nnrnMxs3Pw76/ydrYv8bV28XEur3E2IC+tbfE1rOz2PjI3Oqev7OysaayusPTtbimtsPBvsC/vrSzt7Spwbesqqm1q9SrkYeQjbCpucG0tNq/vdHLxLmfwdHDuamTqKKYjpaoqa+hnJyfr6mnraOToq+0q56ysZufiYyRuKmEn6ShpLzDnqKdmqmkmsaxqr7B087V8Nbr7IDNzdjTvJmxzrSooZqcn5eWnpGat7e5wbS77r2wn62/vaypqra7oJaUm6idlpyMp6ahnJ+roqyvuLK8u76roqWqmK6ooNfg7+qC5s7MwsLIys/Moq+rscXlyLO+0tzU5MvO3cvX8uzZ8+rjiJSkgYrX667f2/Kq2NrF0tPQxtqC6oDksqvQwcvFt6OtpqeYqaKhtbCirbLFzsvUyMzQyLywvanCw7u2u8e1xse8wL/B3b+7w721ubfIu7u/w7G6vbzM86SjmJucl6GfrJuisLW/v8Oxt7LN0sjCwcPG29zbhoHy4cvQ08Ct0cC1q7Okq6eruNbU5ODIx8bpgvLPzL/j6YCTgGRuhXJidHBufG1cbWl7b4mIbmmhgW5uZoCMh5aaj5iQblpwYGJzdXZqdnFuZm9pbG1kdFmDdGtkYG9xZ2x8cYVUnohsU155dolchYmGjmK1lo+ShXxrc3FlamBhXGZTUFZXVU5fYFZTUVthXFxSXl9fc4BOUVV5k5NrjYt6coByfnqElWhwZldiZHqFf2difVtUhlheYm9iZ15aVmNWk2uJhlxjgW1mfoR8ZnZrYlxPYHhfVGVxipOSn3CBeW1mZ1BwbmVqZm55fVxQZ3V5Wpd+Z2hqXk5aXlpOTVlKS05yXF9bUlxzXGSQbmV6e42PhGhXXl1fXmRicYR2XldOWIBeUlNeaGVnc4FUSnJfgZJskIRoj3BiX2dVWlVsdH5meH5nmYpdU1JUV1liXltRVU9CS1BmfX+BmaF8e4B6mJCSYm1xcFpRY3KKdk5Qd2prhIdYm49lkJaBaHVteV5qaXGEeXVlbYBWWW6LhG1TYVVKUGheUmViWF5ZYltTY2Z/eIBnfG6EcoCMc3Nue3h5f21cYmJpfZJ4YmJlimNuZ2VkYl9mWlxVXVlkY3VrbXhvZWBsU2mCc4qIjZlflZqHmWB1ZmOHgX5tc3prXGRjXlpeXWBea1xdXlpZZltaXnBrenCChmlgZmhvZmWObGZSZWNQWG5OU1JTSk5QU1hQU1pNT4BTTVtdWllnYVpdY1lUamRSVlhWXmdUSkVVdHGPeFRaZllVZl1cYltUWFljZF1tWFprXGBcXGhlVFdpYnGCfYOLfH2KcnZueIh6h3lzcHdxZHVnaGdhcnJ9dnVviY+MWVmJbW1rb2plVl1ZT2FvZ2ptZFxfaHZxW2R3dGJkXl5oeIBhWl5se2NifZh3ho1VcXR3eXF3gIqUbmZVXnNsamtsbGZlaWVWaWBcW1xqXIVhRT1FQ11WZWtiYoVvbX17d3BaeoZ6cGhTZWFXTlZrb3RnYmRlc2xpal5SXGVuY1lnaFhbTU5Ze21NZGhjZnh3Xl5dXGhnYI93b3x3fG5xg216d4BiY3dxX0hefW1mZ2JkaF1bZl1mfnt3fnF3oX5zYW98eWxpaHJ3ZltXXmJcXGRXaGNfWVhjXmhrb2l4dHhqZGNrYGdnXIiPopVVmIKBeHh4fHx8XGZjaHuXeWVteoN4hm9sgnB6mI99j4yIV2FuTld5hGh/dYJsa3FibnJzaoRTkICOXF18b3NvZ1VeVllKVFFQXVxWXV5pdG91bG1taWNcY1ZuaGVmbnZpgYB0enx2iW9nb3BpaGV1ZmZpbGBmbWp/qF5iWGBmY2plbVxgbWx0cXNgZmR9hH16ent4gXx1UE6KeWpxeWtffXJsXmRWXVtfan50hIZwcXCPU5hzcGaDgoCKemJth3hufXx8h3Znd3F9bX+CaGOVeWZlX3eBfI2OhZKIZ0tsXWF0dnlvfnd2bXRucnRpdl2Edm9pYm9yZ2t3anhJjXhgSlRqZXNObnRrc0+SfnuEe3FocnRtcWtubnlnZW1sbGt6fHNwanFzbWldaWpqd3lGSExwjJBsjIh4cYBvdW94imhza11qZXuFfGRhfFZSil9rcH9yeG1oY21WknKFflRZdmVjfIJ4ZXVwZGRYZoFkWmlziI+Ml2+BfHJqbll5dm5wa3F+gWRZcH6GXqaShISIfmx1dm1iYmxhYGF+aXBtZnOJcHWYdmp7e4uPiW9lc3d0cnZ2ip+XgHlyeYB+dHJ6hH58g4hTSnVgfolohH1mgWxjY2xdZWF6hY95jI50oY5pX19hZ258eXhpbmhWWVdpfX6AkZNycnNvi4aJYmxycl1VaHaOfFlfgHRvhYFOhnhXgIx2YGxlclFlZm9/eHxweYxiZ3yVkHtgal5RWXFsZnt7c399gndtgYebjIB1gHWMfYiRenZyf3p4f21fam57jZOGbmx0lHF/eHV0c3F4bm9laWJqZnRtbXdyaGFrVGp8cX92dn1OeH9sfVJnXVqAenNkaXJiV2BkZGZrbXJsc2lua2Vibl9dZXd0gnmIi2tgZWdsZGOHbWlYbWtbZ4FrdnZ3bnR3eoF4eIBvboBuZW9vbG56dXR3fXRzjIl6f4N9goVyZlxrjIeehmBkbmNldnJzeHBsbm54d3F8bXSHc3JubHVvXV9rXmt3cnyCdHeGcnNrc4F1gXZzcnp1a31vbWpgbnF9dHFrgYqMW1qIcnZ1enRyZW1qYHB+d3p8cWpqdYF/aXOGiXx9dXV9joB0bGt3g2VifJdxfIROZWRmZl1kbHWAYl9TYnVyc3V1dG1tc3Bmd3BpZmZ2a5NxVkxUUGxfa29jYYJpZ3RxbGdVdIV7dXNhcm1mXGR5eX1vaWpndGxpa2BUXmhybGRzdmVqXmRpiXtYcHFvbn18YV5bW2ZmXIRyanl6gnuEm4ugn4CEhJSOeV91kn51b2lnaGBgaVxkenNxdWltkXNpWmh0b2JfXmptYVxeZWtmY2xdb2pkX2BlXmZpbWZwcHBkYGRpXWViVXuAkIVNh3Z0cG9zfYGDZnBtcICQdWFreoJ4h3V2i3qBlYt3gndrQU1XQkdialZrZnZdYmpga25wZ4VVlICTY2WFfIR+eGZwaGpcZ2NjbWphZmZyfXp+d3V5dGxnb2B2cm5rcHhqfnxyd3dyiWpiaGpjZmV6b3B0dWpxd3aKqmRnXWNpZGhjZVddamtyc3Zka2l/fnRycnV2gn96UE+Of3N5fXFognhxZmlfZWRnb4J6iYx4eHiSUph0bWJ9gKN6AXueegF7iHoBe4R6AXukeoN7mXqCe4t6AXuEeoJ7qnoBe7l6gnvGegF7inoBe9x6AXuEegF7/3r/ev96jHoBe6F6hXsHenp7enp6e4h6AXvfeoJ7mXoBe4Z6AgIEAIC8ybe/tsX/xMW6t7rGtbK/x8vaxdT4ifzG1OTQ1tvdz9LXvLypmJjBhtKwxtPSwsfDysqyus21uMK/us3MwtrEwbi/lu/it7/MyNzVzOn22eHo74Sq2+z23cCusqanu7C4pKqZmqignaefpq+zrqmXjbfrlPD/+cfD1MTFzdvZ4YDUys7g2cnW84jK0dCAg8fR8I6O9dje4t/PqbiwpLHZ4fL3kuH28O/v9Oe+uLbPzsmCz8qG8uXY3/nCh+Dpz8PGzM3S0snP1NDLxMDKwNPq3s/Uw8OpjZqjrpygp7Ozq5qWjofek5Grvp6vp5ShsrGyusPEusS2y9bc1bytqa6ksoCurLm40dza2+b27oHtjLvW7fnUxtDh1NDEvMrL08za6fjjzN/CraK5pcr0xrWrrqadlZiSmKmxuvDe1bzCycnBu6eprNDJ3drX1MnH4IuSjOXd29HJzdnW1vXg9/PVwbnZ3+DMwsO8tMzQw8Squr68w7msxbvGtMG8uK28z7mwx4DHr+qvrbvK47+7ydLXurG4qLCxvLO1u8Hb/uLp7O+76dTGqLvZ2OTDwK+pwcCon5Kksa+z4fj4+OzT2cfGy8S/oaynm53fy7aro52Qp62yt7Otq7q5uK+ystKyqbG+tLu5qKCjn6OpqcjLsbO8vaqaqqu1uLynqbKlrauuqqqknYComKz9z6Oho7SmsJigrbq2sLLBz8m5tMbBzPu5lpmCkJqzs6SstKSeoqu2rbqyuLGkm5ykrJqzsbXA1d3exdDZ2bvYwMO8xbaxvrO5w7WimKiftriUo7nAs663t6efkaSwu7GtoqmsqrO/yMHHwbGytrKt1dzK19vI9u/LsLuiqoCmsLG1wNO61rH9z4PdzcnDybXCx7inmLnErsDBwca4urS1tcC1trS0tavE1sK72cKflaGvpbGttauqr9DEu7u5taukqKKYt6WbjJeipK2hnJ2lpZ+qpa2WlJWnoJeipZyVnLSbjKSPlLOwna2yo4uQsN2tn6aotbvCx9rF3dq8qIC8wdSmn5iWm6W3vq+toJSwmaunys7IzczDwdKqwMato8jAoZ+hp6SZmImJk5CNlJWpqbSzrs3KwLfFwr+3rLbBsKnJy8S+0dPP0b2/xdPb1MXO47aznLW2s7e/wMzNy7vGsse8uLrDwtfj5OCArPrPxdjg48yI0sLEz9vu+dnn54DGu8y1stPWz7uZk5mllpSfp6ScpbK2gcLhz8y70c/Cr660t7ewusvCx8jJ2MHKwr60ur6zvrHEwrO+xcK7vamio6+qo6Kwl6mnm5q3xqeps7DBtNPS2c7HxeHX0unL3unl5ObGvbi81MfVtLWZm6CdqLbKz4X0vL/r7Pbg3dy7tIBzgXN7cnm0cHNpYWBqY2JwdnmGdYmmXal2gY1/hIaLgoWJeXdlWVp9WIljc359b3FvdntjcYFqa3JsaX5/dId3bmZpYpaNZXR+eo2IgJ2nipWeqmOAl6OoiHBkZV5gdGp1YWJPTVtUU1xVXGZnXlZFPF+JWoWXlmppenJ5f4yHjICDfn6Ef2x5i0xvcnNQUGlshlZYjXJ1entvT1xWSVR/f4OMW4CTjY2MjodlYFxxbmxOcnBXnpqPk7CCYJqegXFxc2xxb2ZtcXJuY2JuZ3iNiHR9bm9gTFVcZFJUWWZlZ1paWlR1WVhqeGJya1VaZWBfYGhoXWZYaXV5d2dbVFxUYIBZUVZVZnVxcnmIhEqIVV50hZBzZWx3bWtkXmdob25+ip2OdoJuWE5iT26KalpWYV5XVlpQT1ZZaJWDfXF4f35+eWxydJiJkIV6dW9vhVRST3xzdXdwc3p2fpWJmZd4ZV18i5F1Y19ZVGNlW2NNX2JeZVtWaWJtWF1VUUdSbGJZbYBvWY5eY3WCl3Rtd36AaWRpXWBfYVlZXWJ0jXJ4goRZgXBpVGqOkaeDhXVug4RrYVRmaWpum6ilppmEin12dXBwWl9bU1OPgG1mZGFRYGFnaV5VVmFiYV9hX4VlW2NrZ29qXlhbXWJraYeKampwcF5OVlRbXmFOTFRLVlVYWVtXV4BqW220iWFaXGFVXEtRV15YUVZhbGVZVmhncZdoUlE/T1ZpYVpmZ1lXWGBmXWVhZ2ZfWFVaZVNlXmFldICMdYCKg2uFeH12f3Ryg3h7gGpZUGFbc29PXHN9cWxxb2VdVGBpcmdmX2JgXWBncm10cWNkZGBafYJtb3dnkJByXGVaXYBVX19ja3xphWWsg1uRioSAhnWAgXRlVnJ+ZnRxbG9hZVxcYGhdZGNeWlRufGxohXBNSUxZVF5dYltbYn96dHBwcWVdYmBXc2JbSlRdZGxdW2JoZ2JvaG5bWVZnYFpfY1xYXXZjV2VVWG9rWGloZVRZd6B2ZmlobnJyanRgc3RbRoBVYndTTklKTVtueG5taV91YG1nhoqAhIyEfpBqe39sZoiFa21pbWpeWFFTVU5IUVBlaXBsY3h5c2d1dXJqZHZ9aGB5eWxicXt/iHh5fIKKhHeBmHdzYnZ4dHd7f39+eWVsWW9mZGdtcYyVlYxSbJ9+c4GGh3JVempobXWLlIKOkIB3an1iY4SFgGpRSk1XSUlPVFZTWmRlTWiFcmtcbm5lWFhgXlxZYnBqcXRzgW53dnJmaGpjcGZzbF9qcmxkaVtZXGlhY21/bHp2ZF54g2Jka2h0aIKBin90cIJ7d4xvfo6JgoluZmhpfnOAaGpTU1NMWGh5d1abZGKOh52MhIRraoBoeXR8dX6udndzcHJ3aWFscXd+bX6XU5Rlbn1wdXl9eXyCcnRkWFp9WYlkdH5+bnNvd3xlb4JsbXRwa3l1andmYFlaUoR6WGRuaXtvaIGKdH+DkVRriJWfhHFmbGdmeXJ6cHRnZXRta3JsdHp7dGtaTmqIUoSRkGtrdm90fIeBg4B5c3J7eGt2ik52dXxST2lviVFTjXZ8goJ3XWlkXGmMkJSUWH6Rh4qLjoZlYFtyc3BMdHBSk42Fi590WIuTenBudXJ5enV7gH98cW54cYSWlYaViI19anR5gG9ydX15eG1ubGaecG19iHB/d2Nncmxrc3+AdXttfYqOhXlxbXRteIByanBteYN7enyCfT95SFNpfIZvY25+dnRva3d3gH2MmKueiJaIdGuCbIylg3JqcGpkYFxPTlZZYop8dWlwdnt5dWdrbIqAiYN7eXN0iVFNS393cm9obnZ4f4uBio1zYFx6jJJ4cHFqZHJzaWxZam9rc2lkeHeIdn95dGp5kYJ1h4CGa5lraXmCl3FqeIOEcm9yZGtrcGpscXKEnoOLlJdslYF1X3CPjJJ4fG1nfX5sYldjZl1fgYyIiHtnb2dnbWlrVlxWTk+JfGxmY2JXam1xc21ub3x4dnFyc5Fza3R9eIF6aWFgXmFqaYGEa252eGhZZWp0eXxpaXFodHN5d3t3dICBc4C/lnZtb3ZvdWVsd315dXiEjYJxbnt2fah1X2FQX2d9dm51d2pnaG52bXd1enhzb2ttcmBvZ2Zpc3h+anJ5c2J7cXpxeGtodWpwd2ldVmdjd3RSXnV7b2ltbmRfVWBxfnJza3FvbG92fnh+fG9xcWtmiI16fYNzoaKCcXhnboBnb25wdH9lfVybdVOBeHNzd2dydWlZS2VwXW5tbHFlaGJjZ3ZrbmxtbWR+jHx8nIhhW2FrZGtna2NfY3xzbW1ubmdjZmdif2xlVmFrb3ZnZGZraGFua3BfXVtsaGFqb2lla4dzYm5dYXh3ZnR1bVpbd6F1ZWprcnl/fIx6lJd7aYB3f5RvbWVlZ297f3JyaWF0X2pjfX90eXx2cIBecHNhWnlzXl9jZ2dfXFRXXlhTWVdpaHBvZXh3cGp1cHFqZXN8amB0cGRbaW1tc2hqcHeBgXmCl3RvXm9vamxxc3R2dGdwYnRrZ2puaXx9fXFEWIRpXnN3fGtPc2ZlaXGDinuIi4ByaH1ma4mMi3heWVxlV1heYmJdYGhqT2+KfHdpenx0ZWNqaWdiaHRtcnJyfmx0cnBlaGxka2NxcGRweHdydWZjZG5qam9+aHZyY1x1f2NiamdyaYKAhntxcIF6epB4hJGSiIp1bGtrfHWAbGtYWFpUYHB+fVadbWqOhpmOiYNmYZZ6AXuRegF7mnoBe496gnueegF7lHoLe3p6ent7enp6e3uPegF7jXoEe3p6e4Z6AXuoegF5p3oDe3p7v3qDe/96/3q9egF7/3rheoJ7h3oBe6B6AXvdegF7i3oCAgQAgNi0sNrwx9PAxsDBwsm3yNHDzs3E4MzRvs3pgYX81cnf4besw7itrNTEu8PJsbPO46281MXI1M3CzsnJ1L/RjcvCz9nX1ce1rqq3usrM0M/T2fWE99yEiYHb6fP85rjXy7+4t7avsJullp6mvrudsJmWpaacuc6Ay9jPx7vAzNnMgNrn4vfu4POI08ixwuLitMnp6djSydTJw9bFr7K+x+rQ6tXm2omDhPnm0YmRvrWwu67Fv7j61eHi0s/WtcbKzLe+ubq+xrTOxrm+xNLKus/Ku6jJxsSfpJmdq6qvna2Sj6Ogg6CXrpmQpJ+ptLK7t8vf0ru+5MWsyITyy9XHuLa5gKC+tqG3u+Xeje/e3dDDyejwycPZur/SzMTCxrrBxsyyp6qwpqu4u7mOqbSjoKq72qu3wc7S18/gp/z65MXAy6yyrbObvdfDwcnG0OSHg+jsy7vBwq/EyM7U0dXX8cS964bq8NXCx8fszc2vzMbMysmyvMKwsry1wcG0xb/EzLm/gMawr6K0rbv14cPHpLqmtcm6sLS1trbE1tr41OjR1tLjgfi3qL3Oq67O48ax15Olmq2spcjs2NqzucrUzfTLoJanpq2wop2gpZuYk5mmsKu8uK2wqLG5sZeNoZKlub2zp6emqrjGrKWwqLitqZ+mrrjArK6wsrSvzbqqqZyXn5yqgKq7xqqao6uwhcnFvcG6usDAyri6w8uN0pC4yqWZkqiWqKOss8GpqaqypK2hp7SwwKWkpKaisLK3xcjeyfj31sW4s9C5xrCrrcHKsbixpY3BzL+wqZmnvJqwsqKZoqCmjJ2foamfrcaztL7DyMW5xbKjrcDLrK+3yMLSv66ru9vFgK2uuaemraDQwM7Aj+PayMLAv9PBtqKzrbm7srO4s86wq6WjqLa9w8TCvL28raynmJGfqaiwta29taysqrS0tqu9u6ifmKS7qqOOkY2OlqefqrSklZ6suaCRoqCXm6yhqquqnaWZqKGBrJafk6KvloaUmZagrMa1vMm1wsfDxMm6gLS1tLyzs7e/27nMwbOxuMKvu7bAyL7Gv7/DyMawoq6qs6u0v6yXmoaJlKCpoJK6qrewurq7zdvZxb7DucCjubi1t9TQyNPrurvAwr3TwLq/sdTLtb+rr7evsq23urWxtcS/yd/W0tfDt7i4w86/vsDdsp62v4bp7NTd2ez46PjwgNO88szLyb26x7+uprOirau2p6Gwv7y7wr/OycXA0s3Msrmrv8K7z8LIxOvKxM7fzOPWu7/Ar8m6vr/M2Mi9tr/PxdCvt6WjyLKzl42subDAvb7IwuPZ48jn18zNvsbG1eja38vKwMWyu8jBtLylmaXAxcnaptPbsvjO0unKu8K2gI1wcZmjeoNvc21qa3dpc3VueIJzjXd4bHyVVFusgYGVlnFnfnRtcZSFfoWLc3iUpHKAln6EjYV3hXp6gW16XXh2hYyGg3hoY15mY3Nyd3WChZ9bqpRbaGCPoayznnaRgnhybW1mZVRZUFBXam1Ya1FFTkpEW3FMcoN4bGRwe4l4gIGFgJGQiZpaf3Nfa4mEW2qEhHVuZG9jYHRmV1pkZH1wh3SIgWJaW6GHalJVVlNWYVx0d3S3laCbj5KUbXt6d2BjXFxgZ1twaVtcYHBoXW9waFd3eHdYXlJOXFxkXW1XWGhoUG5kcl9UZFtfYmJmXGp7cV9efWFMZlOZcHZnY2BngE5iVkZcYIR/WIt/fXRqcYWKbmNxXV9vbWhjZGBkbXBdVFZZTlFeYWE+XmthXmp2jV5la3N2eHOET5iTinp7hm53bnNZdYZ3cnNucXpMR3t/Z1lXU0liZGloZ3BuhmlnklaHiXZrcWyIZmJJYmZtbnBbZGZcaG9iZ2NSWllgZFdmgG5hXlpoZnKnnIGHZHZocHtnXWBhZGNrcXCNbIJscGqBUZhlY3eGa3GPoIlwklRjV2hpZIGgiIpmaXF8dph4VUxcWWFoYVxiZ1hXVFNYXlZkY1NaWWNtZlJMXFNfcHJsYmFiZ3GAbGlvaHJlZFpfZGhtWlhaWV1Wc2dcYVZUXVdlgGl6g2hbYGRoWnhwam9iXWZfZ1VYZHZYd0hrfl5TS2paZV1lZnFmY2BnYGddZGhhb11jXltWW1phbm6AbpeUgHVqaH9reWtoaHiFcHtvYk96hXhnYlZfblRpbmJcX2JmUF5gYGFYYnRlaG10eHVoc15VXGp3XFtjdG15alxWZYx8gGNmcl9eZFh8bnRqYoyNgYCAgpaDd2NwZ3Bwa2ptZX1jXFhWVVpbYWJiX2FlXmNeTUlVWltiaF1oZl1hX2xtdWp7dmliXmN5aWNTU1NWXW1jbXdlV15ncllLWlRQVGVaaGtsYWleaGJIb1xlYG15ZVdhaGRqb39vcHhkaW5mYmlaF1NXXWZhYmhthm6Cfnd0eX9scnF7jYqLhH+AgXRrdXF+cnqBb2BkUlRTW11WTXJmcWpyaWh1fHpranFpc1hnZmJbVHBjdpZsbnd5b4dzbnJjhYZve3B4gHx7dn15bGVjbmdziYmJi31zdHN8g3R4e5BqWWpuWpOPeH56i5iLm5F1ZaB4eXZrbnh2ZV1oWmFkZ11ZaHVtaW1mcWplY2BwbmpXZFVla2d1b3Nuknx1fINvh4NudHNdbWZrcnyGd29sdIB3hHB+dHKbhYdtXm91Z2tpanZxjYeMdpCBeHxuc3J+jn1+bm9mcWNtdnJpcV9VW3J0doNVe4VkpXyEnoBxcm6AgWlojpl3hHV+d3NvdmZzd2twenCGdHNha35JTZJwb4KBYl1vaWRpin52d3tnaHyKYm2Ed32EfW56cXFzYGxMZ2Nvenl1a2BcV2BcZWZranF0jE6Ufk5YVIeUnaKSdo6MhoOEgnl4aG5kaW+FiHCBZlleW1dpeUp4hHlvZm15hXOAeX57iYd9jlN/eWh2lY9jdY+PgHtyfHRvgnFma3J7koOTg4+EX1ZVmnxkSUtOS05ZWm9uaZmBiIZ3d3pcaW90YmpnbHF4an54a2tvf3lwgoR8bo+VmX+HeXR/fIF2h29uf3xiem+BcGZ2cHV7dnt1hI6CcXGReV9yVaSEjoJ9eX+AZ3tvXWxtiINThnZzaWJqgIVqZHNgY3Vzb3B0cXeDh3hydndtb3h5d1JxfXJtdH6QZWpqb3Bxb35LioqGdHR7ZmxnalZxgXd0dm5xeEtGfIFuZGZkV2xxeHZweHOAY12BT4CHfXV6eJN4dlxxcXd3fWx3em94gn2GhnqEgIWHdn+Ah3d5cnlvdaOXgIVgcWJufG1pbXF1cnyEhJ18jXl8d4hQmWxleYdrbomdhXGJU11QXFxWbIh1dVdbY25rj3NUTV5cYmdfWV9mXV5ZW2NraXp5a3Ryf4mBa2R1ZnWFiYBzcGtsdIBrZW1mcmpoYmhrcnhqbG5ucm6JfnV6cW51cnqAfYiLb2NrcXNXhIB9hoB8gn2Fc3V8hFSFWHWHbGJadGh4cHR4gm9ram9mbmhwd3B9bXJvcmZqZ2xyb35rjodxZ11ab19sYV9kcnhiaV9WRnmJe2tmXGZ0WG9wZFtiY2hVZmtrbmZugXd3fICBeWx1YltmeIhubXSCe4l4a2ZxlYaAb3B8bGlrX4Fxd2lag4B1c3NzhnhpVmRcZmhlZ21pgWZiXl9gaWpsbW1pbXBrcGxdV2RsaW90aHFrYWNdZmhuaHd0aGJfaX9uaFhbWlticWhyempZYm12X1JhXFhcbmZyd3Zud213c1h9aXNrdX1mVV9oZWxwgnZ8h3iBhoCDjYGAfH1+hHx7fYOae46Fe3V2fGpvaXF8dXZvb29wcWVeZ2ZxZ210ZVtfUldZX2NgV3hlcm12bmx5f3ppbHZscVtsamhjXXFlboNgYmlpY3VmZ2xhfn5pdGhvd3BwZ2xpX1tcamVwgoF+f25hYGFrdGZoanteUWRqUomIdHl2hY6Dk4uAcmKcd3x9cnZ/e2pkb2BoaG5kXmp0b21wb3x3cW5/fnlkbV1rbmp0bnJqiXVtd4FvhX9scHJicGdqb32If3hzeYR9hnN/dHCSgX9mWm1xZ29tcHxziIWMdpCHfYB2enmFkoF+bm5lcGVwe3Zqbl9WX3Z5eYRYe4Jopn6BlXZrb22aeoJ7onoBe5N6Bnt6ent7e556AXuQegF7nHoIe3t7enp6e3vMegF7j3oBe8N6gnuSegF7v3oBe+d6AXuNegF79HoBe/966noBe/96i3oCAgQAgODR3tLjt7vGvMWzusPAw8Szsbu7z6y/yM/25sPV1s3ugt7BxbK774OL6sHaybKvr7nEv8Lt29nV4tmxxub4477Hz6a9vene8uDMy76qvdn11tnVzM2Ahd/h0OTRr6m7wrTEurWzwqeosaqnmaGfrries6GsxLS5tuTGvrfa3djtgOHh3ernzuWAgdTR19Lq7fLs0NTuyNe/vrq8vL2438fU3dDEvdCdko2Q/fWs1bPCyM/MwYPg4evxwePZvr+tvLWrnKG3yebXvdDT0vuI5cTFq5uclo6Nlo+JkJ6al5aQmKbFy6mRmKecm8bJ1KrS18rek9bP4MbKxND5xsa7rqypgJ6bqaKktObW2/rMzdXFyNPP3fT3/Ib8g53e9dWBurfZvKWrqpeVrZqYo7K4vcvt28a6xd/XwOfi3La5vbWlppiem6+jqa6fqKmppa3HxbrKh7PAxcPLxsTC1M/Kz8/UgZ6BmPfDv8TQz9PKy9nkz9avo9TGwLikv8G+wMG6x8y6gLmyxr+vrKOdoMvOtqvBu6+4tr+4vMe2utDy0sbKv9HPuK62xMK7u7jJra3XqrG7p52ap6/C24Dg2s+/wbmsoaO+4tenk5+dmqeeo6Cko6uzoZ+oraKnusC6uKepu7nAuqynraarrKm+s66SsayhqcXEqLHKtrayq7exoaCamamjgJuUlJCptsPiocHD49bDtbzBs6eYutasr7O5u7umn6+Xn56jq8yttKSaorSorauru66ttqy4wMznzr/SzbfCys/ducLXv7G/usTIuLipqqe0qa+4qaaZqcXEybKilpyzsq+rrKimsKepws3o762ssKunsMngw77FvMbJ0ty3orjJgNHbzca0t7a/zNjPtszM4Mvh1d/JvrKkprS3uLXItKizxcO5t7bD0NzOzbG1v7nAvMzcs8y+j6qtrLCuoqKhqKGfmLrJwo+Gp5iGhqOYnqGRpbWvsKvAs6OWloyVlpqJmZifl6GXkZqWr8fcxMChkJKSnp+ertbOpa7GuLbGyrSrgK6lorirp6+pucXFq7XEy727y7G6scvSwrOxp6+hrcSur6CtpZGAqKSlp5mdopqXpaezt7/OgeHz1dXFzdLWx9nMxcnOy87Bt7S7xry5uri1wa+jssirs5ufsMvax8HY3MLHy8rc1djDsba8q8CzvcbAvr3QwMjRwPKB+/iF29rTgNjIuM/Jx8fXwbGaqLOvscHXwKKxwrS9ysa7wsHCxsThtpeYobe30Nq629i2xM3Nxe7R1NzVvs/Mw7fCwsPIzr6jxsS2pZ+hopCO9ZyxpKebwcG6q7W1xLrJxc+1u8vZyNjQ6NnTyNrbz9u6r6SfvPDN0MfMycHPxcnRxc6zt9DagI6EkpGmenl9cnVnbXRxd3tnYGRthl9zfYSolHeIh4WjWY92eGhypV1hnniTg29xanGAeHqhjYeEkYhmdZWllHN7gFxrapOHlYl6cWZYan2dg4KCg4RYXZefjqOPcWt8fm95bWdgbldXZWFlW2FZYWJOWUpRY1VYUXxkYlt9gnaHgHt3c4WIdZZZWH9zdm2Dg4eEbXiSbHxranBybGthf2Nre21qYnBfVllXkpB4eFVnc3V1blSSn66ugKWbeXtsdW1iVFBbZnxtT2FrbI1Pe2BjVU5RTD88RD9AS1taV1lYYXCJjHFVXGZUTmd4gU5td259VHVrfmVoXWuYa3BtZGJdgFBKVU9OXoyChryBhIx8e312hJOTnFOZTl58jG9NWVt6YE9XV0pJYFVXX2ptbXeYhm9ncoh6Zo6OimhpbG1faFtiX29gZWlYXlpUTlJnYldmSVNdXVVXV1hZZ19aYGZwTWdTbZ9oYWVvaXBqbnaCc4VhWoh5cGZWaG1kYmBWYWVfgGFdcXJramRjZIuMd3GAe251b3ZqaGxaXW+OdGZraHZ3ZF1hanFtcHCAaWiSaXJ7a1xcYWZzg0x6dXBocGZYUFJzmY5jVVxaVl1UWFhXVFljVVJXX1ZZanJsZ1laaGx0cGNgZWJqa2x5bGdNZWFUXWtuVlttXWJdXWloYF1aXW5ogGRdW1NpdHuTcnBsh3xsXWZlWVJKZ31bWFplZmhdX2tXZGViYnxbamRcYXJpZ2Zmb2Bfa15oZ22AcWh3bVlnb3SBXW+Pe2Vwb35+bWhkamh2aW9zYV5SZH98eWhbVFpsaGpmZGdlbGRkdHeVoGNiamFeZHeMZ2dtY2t1foVoXG6BgIagin5qb2VkaHNoU2ZtdmyBeIZxaWJVWGVpa2l4Z11hd3NkYl1haXlvc15fZ2BpZXaGYXhwR1xiZGtnYWNkZmRiXX+OhlpRb2BPTmZgZGZUZHRoaWV0aV9VWlFZW2BPYWBpYGhiWWBgdImYiYVqX2BiamdjcJKJYGRxXVZjaFVVgFpUU2NWV2BhbHh9am15gX16lYSGepqdh3d+cXhqcohtamZxcGBTdGhiYlJUW1hVYFpdZW1xT4WZfIFydXV5bHRnY2VpbYF0a2hmc2tscGpnd21ne453gGlte42Wh36QkXR9eHmHf4V1anJ5bH53fYF8eHaDd4OIdJ9WoplXfHl0gHpxYnh0cHaKd2lRYWllZniIc1tmd2hsc2liZWBjaWyNbFNSV2dkeX1lgX1iaW9zaZV5eoN8Z3t6c2lubXN4fnNegYl4a2ZqcF9hnWx8bm9deHZsXmhwfHKFgIZobXqCb3t2gn16cYWHf41tZ11acKF7d3N8eXWDeoCDdHhja4GKgIV8h4OTdHd+cXVobXRtb3RoYmVneFZjaGiHeWFubmqHS3ZjZFZgik9Uh2J5cF9fX2lzbHCTg3x3gHdZaYeTgWVsbVNiYIV7ioJzZ1lKWm2Gb3Fvb3RNU4SMg5qNc3OFioGOhH15hW1ufXh9dnt0entocmRod2lqZIxzbGSBgXWGgHhzb36AcYtTU4R8gn6RkpWQd4KYdn9ybHFycHVuiHp/jX92bHVZUFNMf4BjaUlWXmNlYEyChZGWa4Z+aGtgcGljWltpeY+CZ3h/fqBYjnZ+cm1ycWdpdG1qcX15c3RudX2PlH1mb3twaYCPlG2IjYKLU4Z5hnF1bHqifYaCendxgGZgaGBeapKFhqd3dnpqamxkcYGEj0yTTFiGm4VWd3iUf21yc2didmhlbHV3cnuYhXBob39wXYODfF9ka21kaVxjYXNob3FhZ2JZU1ltbWRvTllpcnF0b3BteW1maWpvSVpHWpJoaXJ9e312d4ONe4ppY5KJg31ugoaCgoJ8h4p7gHl4iol+enFtaoqGbml+eG52cXVwc3xqcICbfnB5dIaDb2VqdXhzcm97ZGGDY2pxY1lVWVtmdERwcG1lbmphWVx3lolmWGBgXGdhZWBjZW94Z2dzgHl2h42HgG5vfX6FgHBnaWdra2x7b2pVcGteZXV2YWp6bHBsa3h7b29rbH95gHNsZl1teYGVbXp5lpCEeH+Bd29kgJJvb3J8eHdwcXthaWpsbYVpcmRbY3Rrb3Byemppd216gH+Mdml1aldka213V2V8ZVdkY3F2ZmFXWVVlXmhyaGVaa4F+fW9lXmJ1dXZzcnJudW5ufoGWpmtqb2hka4KUd3d9dH6Jk5l1ZXSCgIeWiIJwdG9xdoF0YXF0fXKDeIVya2NWWWVpa2t7a15idnNpZ2RobnpydmNocWlybYCQa4N5TmVoam9sYGFiZmJgW3qIglpUb2NSU25ma2tZaXlubWp7b2ZcXlNaX2RXaWtya3RuZWpmfJGklI5wYl9gaWppd5WRbXSJenmFjHlygHZybHxqbHV0f4mLdHR8fXNug3J4aYSJdGdvZGlfaX9kYVpoY1RKbWdjZFdYWlhaaWVkZm91TISagIF2fn9/cn5ycXBxb35wZ15haV9iaGFfbmVkdYhxeGNkb3yBcmx/gWdvbW9+d3tqW2RnW2ljbHBtbWx/c32DcZlQmZRSdnRugHVrYXh2dHqLeGpUZGtna3qJdl1ndWhveHJuc29xeHugd1RQU2Fed3thf3tdZG1waZJ5eX54ZnZ2cWtxc3d7gHRigId6b2hrb2Bfnmp3a21dd3htYGxxfnWAfoVrcX+HdHx2hHx8coGBdoFpY1tYbJt5d3V4dnV/dnx/cnZlbHyEoHoBe4Z6gnuseoJ7sHqCe5x6hHsDenp7h3oBe5h6AXukegF7o3oIe3p7e3p6enuzegF7jnqEe9N6AXvRegF7/3r/eq56AXvDegR7enp7zXoBebV6AgIEAIDVycizrrGsrbnAtaevtKy6xsa0wtHShcS5ytbNz8nL0/3mybSz2M+YmO7m+YuR1c7F4cTEzNC8ydji3bDB4PLq4MOvtM/92Obr0e7s8fTLx8jj08XFx7vAz66P8Ki0tM3Dwbm8r6WnqKOmqqa7qriys7q/w8nRvsi1yMrI9Njtn4CD5tHZ2YSDkIfa3NnE1u/o17vR/dXJz8LFxdLKwtjs3t7RwMLTydjkhfKNgvPj/e3k3se+zt7Q2eLJxMHI09jFv8m/wurt9tDYzNX62KO8ubuxr56ikJubn6efp6q1xZWznam2mpGlsamsuMa90Mi4u8HH3M/Py7yg0NTAr8amoIC5t7ums83S5tTKw9Pw18+2wczn3f+L95ifk5HYurS3trCvvL3A3e3GyNnYvrjH3MevobTJv8PAuL2ttbCpsre5sK6Wmp6kq6Omsqi2vrHcx4G4tMmtqKq8t8ncz9zX6fPz17m0y8rF0ci+uNz9w8bVw7Osq7ez37vItLK1wsjBx4DLzMDMwLKgspadpKKUsKu7tLW1tsfLvbnFwsPF1t3ExLjGwcOftLS/yK6lrpaTjKelu63D04HSu8PEusS3r6+wpbixuqSXoKGeoaqck5minqSrtqCnn5uUl52skpPI49OpxLKrn6CqtcKwnKSkrJ2/y6+qtLK91eDXv6CKqZqZkoCdn57j2qCttNrcscvMta2oo7vCx8ngubyxx7m8saCWmZyXm8m6vam5rr2zs7W6uaqlt8fJxcDGvMTA0K6+x8jfgsn/gNbO0b3Iu7arwMG2uLrCvLe2pLDT0aGjoKqoq6GlxLLBxbynv7eyss+9op6iuLSjrLa8t7u6tcHY0tzFvICm0enj49LQzNjl39LA093a1+eP4vzsyamzvMrRqM68usS5vrzFw8u9tLaxvb27sra3tqK528XRvqqzrqutuamPgZacrMSzm4qQkI2YmKGolJK6xL2wsKeYmo2Mj5yQl6OMkJ6YjJmdsbrW1bOZqJ6khJONlJOxtrW3t7i9ycrBrYC0s5iWqqCaoqytsrXGrauxsraqvcLiw6KepqSksMLPmJGcqZSSorjY4rKfu6uxtZunpLq6xs+8t9bZtLna1MvHyMy8vLy6t7u6u7bE0Nr1xbqztLrGxsu5sqawytyC48rQ1MW14+jT0sS7sMHcy8zWtbvcz7+3zfDg9vSCgPnz44Db4NbUyMrAz8GxsaGjubC22M2vrsG/yMG94NHGwcng4umtnp/K07y7sN7Xv8bCwdTGvcTP2szn2dvQw8TKu62svty0sKqlopKhhJPJs6aqsarFt7y8zcK1sbi6yMm1utHc0su9ztTcwd7O0Ma+v6+yyraQjeTBzeHDr7/k//TGwYCIg4d5dXZtcnZ1aWJrbmVqcnhkcn9+YHduf4aCf3h9iLCWgWplhoJqYpSImltifHVuknl/hYZ2fIyRiGFvjqOZknxoZ4CrhJGchpmTmZ16c3SPgXZ+gXaCmHl1tW5waHlpbmNnX1xgaGdkZl1pXGZhXmVgYmJhVWJRaGpslHeDYIBOgHJ/hFtXY1yJhIFoeZCQgGZ7oYF4fXJ3eH5xZHiHdntzZ2p7cnyIVIhVTId8lYt9dm5pfpKKmJyKi4aHiYRyYmRVXn+HlG9vaHOSc01kYGFaW0xTSVNVVV1TYWN1h1ZrW2ZvWUxaYVRXXm1gcm1dXmRne2pkYFZEdHpnX3ZbVIBoY2dSYW96jXpxcYOehIBocHuRgJxXjlhjWVdtVE9TVFJWZWZph5VxdYOLcWd2hXBcVmd7dHp6cndpbmpmcXh+dXZcXFthY1pWXFBXW1RxZUleWWVMQkBKRFdpY25ofoyPgGhmeXNscWdgWniecnB8cWxucXVtj2hsX11bYmZeZYBra2h6dmxgc1xham5geHN+cm5raHN2ZWFna25sdn1kY2BqZmpRZGh6jG1od1ZTTWVdZ1hhb01tWmJjXmZbU1VbV3NueGVYW1pVV2FZUVJXUFNaYUxVU1VPUlRiSUt+nINjfG9mX2JqcH1mVltYX1BocFtWW11ne3+AbltNZ1xeWYBgYmKgkmBobpGUZ32Eb2VeVmNpaGd5W11XYl9qYl1XWVhXYYhzcF1xcoB6dHJ2cmdhbHBvc29xYGRhcVdobWt/TXCgVIKBhnR9c25mcXBocXiBdnV2Z3KKh15fWF9eXVZeem5+gHllfnRvcIlyW1pacm5dY2tqYmVkYG6CfIN1a4BghJmVmYODfoGLfmtcZ25vb35ZeZeFa1RgaXZ7Wn5tbHhqZWNrandoYWNbZ2diW1paWEZjgnF5Zl5sbGtueWtXTmNqeYt/aV5gX11lZm5zX1p7fnNraF9aYVlaW2RaXWVTWWFeVltfc3uUlX5lcWlwV2NZXldtaWVoaGhrcW9nV4BdXEZGVVVUXGVcYmqBcXiCgX52ipKvl3RrcXRtbXqFXVpqfmdibnmUmXBgcWNlak9aWmNkcndqZ4B5VVpyaGJjYmpiZ2dkY2JmbWt3gImkg35teH6MiYt4cGBpgo5bj3Z9f3Vpj5KFiYBzbnyNhoaQdnqWiYByf5mIlpdST5GIfIBzfHNxb3FtgXloaFldbmZsj4JoYmtja2xmeW5iYWuCh5BjXVx4hW9rXIR9aGxpa3x1aG51f3ONgXx0a2x2bWFhcpFyc3Z0d2Z0WGSRfXFwdWp7dHp8h4J4cXRyf3xscH6EgHtmeYCFco+DgXFkZ1the2lUWY9ygI1sXWqBlJZ0coB+dXltbGxmaXF0amBobGRqcHRiaHJtUGVbZm5samFjbZB6aVhUc3BaVYV4hExQb2lif2dtdXlpcH6BeVtmfYl/fGpbXHCYdYKJcoV+goVjYmR9cmdsc212iHVlm3V8eYp/g3uCd3R3fnx8fHOBeISAfIF/f358bHhnfHlzkHeDXoBNfnB6fVRVXVmIiYFsepCLfGV6nYB4fHRzeX53cISOiImBdnN+dXqBSX5OR3xzin91cGZcbn50hIRyb292fX5yZ25kao2UoXyAeYOah2V+gYeDfnJ5bXl9fIV6gn2Jkmh+cn2FcGd1fHJ0e4d8joF0dHZ3iHh0cWlVgod3cYpwaYCAeXpkb3qBjnpsaHiOdm9cYm6Cdo1PilVeWFmAbW5wdHN2h4OFm6eChZCUeW54hXFcU2J0bnN1bnJnb25seIGIgYRra2xvb2VfZltjZlx3bE1nZXRgXl5mXm59cndsf4yNfmdkcG5teHNuZoGgeHmFeHJ0eH97oH2GdnR0fYB5goCKjISNhXhsf2hrbm1edG56bm1ucH+BdG91dnh1hI10c212cXVabG55h25sb1hVTmJdZ1libUhqXGhtanFnYWRpY3t1f21gZWRgZXJpXmFqaXF2fGt1cnFqbXB6YmGWsZx1iXt0aGhweYRwZWtobl93fWtnamp0hY2KeWdZdmpsZ4Bsbmuhkmdydpabd4yTgXhxa36CgYOWeYB2gHuAdG1jY2BaY4h3dWN3c3p0cnF1eHBqc3p5fX2CcHBmcVVnbG2DTXKaTnl1dmRtY15YZ2hfZmtzbm5xanaOjWdoZW1tb2lwinuFg31tgXl0dYt5YWBkfnxtc3t4bm5wb32PhYd3b4BihpuSkIGBfIaRi3Zmc3l5dYBUfJeJb1Zga3mCX4Fyb3ltbGlubHRnX2Nga2xrZmVnZlVui3iAbGRvbGhpdWhTS11ic4Z8Z1peXFhkY2twXll7gHZsa2ZfZVxbXWheZG5cYGpnY2dsf4WdnIJqdm90XGVfZ2J4eHh7fH+Di4uDcYB2dl5baWdocHZtbG5/bnJ8dXVpd32bhWVfZmlobXiBVVBbbWBcZ3aPj2lbcGVscVdjYGdlc39xaoB/YGSAeXJ1c3dvdHJvaGZmZWFweHyYd3Bpc3uCfH9tZlpkd31RhWxydWpdgoN0d2xjXmx9enuDbXGLgnttfpeFj5ZOSoiBdYBseXJxcHVxhntsaVtdbmlwj4dtZnVvenx1i4R4doKZlJxpXlh3hGhkUoN+Z25pZ3lvZGlyfW6GfHt0bXB3b2VkdY5yc3RydGhxWWSPeHBxdG2BdHV1hX10cXR0fnxucn+GfnZhb3V8a4J3eHFjY1xfdGdKU45xe4ZpXWqDl5VybpZ6AXuQegd7e3p6ent7rHoBe6R6gnuEeoR7n3oEe3p7e+l6Ant6hHu1egF75HoBe/96i3oEe3p6e856AXv/est6AXudeoJ79XqCe4x6AgIEAICD7Lz9/8zotqzDuMXQyMfK1dLCy/Xb9N66wNXHq7Omr7DMwcHIg4aDk52ThObAwtzy2NnZtKv00PTc3ci9rqOjwrShsLjPxdfLusbl19vN2uft4ry0tKXV5LCvucLMv8fG2MWpq7LHtbK1q7Szq6iru8jN0Mm/wLzFtbSwuefq+oDi2/391tPR4eTv/fTr0+T8ztLJ4NHI0dnT09HcyOHr6OeL18q7ucfk5ef96fz82PPyhuPf1sfbg9HC5+7xgPvX3dvU7JKJ08Wx2vq8vKG39t+vp5mqpaeoqrGtuby/tqKZmMOm3NGhzOm3vN+wwcDIt7fc0cbO2ru3pt3QvL/P4YCs1LXW392A/ebfzbCht83fxMLM3tTMy8HV2t7J1sfO1MrGy9vQv7CosL/Q0cK/vJuTlomzwri/tqi4qKafpqm0pqueoa6tnKekusrE0LCxv9zdzdTQ1tG30un18drWgfv/5sPQvdLBubev0LO42s/R18TKxtXH58LLuNLQ7s7g44DP3dDCzca2nLWao6KzpKqsw83GtK230bq5u9HXy9rOyr/At7e1tK22rbOap6ikpKbO3/Tm6qad4+LPx7OprbGboqS5uqKhk5aew6+ZqbquurG4uLCboaGuurKupZmclJm7rK2kraOlpKC4sLa2srbHwKGjzNSEi5vpxZ+amp+WoYCeoqWwsbKtsryilqWyuKaktr+9w8vQzMCwvrmwsr/BuqefqreflaCpvsGiyLaysLK2sLjSzcLBxMTMzry8yrassrzEw76C9/LSq8/JwLGwwcC1wM+8u7KrubCUjae7scCxusCjoLOvuKqyy8SppaComaqcpaOWnayjwuPy6NTJvoDE0vH33MvJvLfPvvPX7u3o9eH17N/f5uC83sq/v83BysPKup6/ubXAwbq8xsWkr8K5sqq81tXJv7isvszPq5+TjJizrr2oo6qkpIudl5mewqC3rZ6anKyZlpOXopqgmZSQmKKLh5esnY2Lk5GJlZONi52MmaOTmKWcq5+wrKK6t4CppLTPxrinrLGjoKqmtKnGr6qkscW+qZSWq7a3tJ2ZnZuoj/2DkanhuY6ku73BrqijpLeotLfAv7HB1dXVx8/Axr3Fqqu5vrK3uLvNxs/Ov7Khsr7Cyr3Ht6yjtb66trq6zcLS7Ynh1seyt76+xsq3xszy5OTj17rMyby85uX054Dh0dPXodDC1tK4r5qWuL7AwMHGtMO8vcfPyMTBu7iss6yqqrXguLrz0tfYw8rO0NC7va+wsMC+2tPLw7nLt8TPu7mloqG7taqzjZafnKemr6Kfm6OxsbK7x7S7vL+0vsrNy7/Hv62U6rzM6eva3cS2vtDV0dDpjIXZtK2kzO/Z2oBconi3uoylcmuDdH2DeHyDin5seKCPm4tweYuFcXRpcXaGgXp4VFZUXGRbUYttcIibi46NbmSoiaWOjnxvX1hYdGldZm1/eY1/c3yXhpSGk52qnn12enKnuHl0eH2EcHVwhoJqbnGAbGVjXGdramNcZWtsa2peXFppXlxWVnyCk4CBhKmnfHx+kJKWnZKNdICXdH10h3l3fIJ9hXyCaoKPgoVaf3ZnZ3KGhH+He5KRdImHTX59fXmLXop/m5KSUpyAiI2GkFxTbWJTd5BgYktkoopmYlRdVlNXWWNeaGtwbF9WUHZbfnVJbo1la41eamduXlpxbGFte1tTS4x8am95g4BteVx6hH5Nm4qId2VXaX+IbG1ygXxwbGFwcHNebl9maGFbZ39yX1hVYW2ChHV0clpSUUhtem94cWp2a2tnamt1aW1kZG1lUVpUY2lmc1tga4OCcG9pdGlUZHZ/fW1vTpefiWx5bHppaGpec19feniCjXyBfpKDp316Y3pwhWx+fYBvenFpdXZwW3Ngamt9bGxvf4l8bF9hcltbYXJ1ZXNpaWRqZGBiZGRvbXhfa2dcVlR0fY1/fldQcXJfYFNRWF5QXF1xa1hdUFRdgWhPYWliZ1tgZGJTV1dkbGVmXVRbV1l4a2pkamZnYl1wZmplYmFtalFQcHdSWGWNcVRTVVpUYIBcYGVubGxvdHxgVF1na1hUYWRdWGFhW1tWaF1WYHF1e2heYnNjWFphdoBojXx6dGxmYmyCdmhsdXJybWFte2VVW2h3cm5VnqmOZ4Z+eG9udG9ofpCAgXJteXBXTlpoW2dib3NcX3RzeWpzjIJjYFxkWmteZmdWWmZbcIqakXp2bIB2f52dhHRrYV5vYZF8jYyDi3qJhHt7hoJri3xwbXtrdG99blh2dW52cmdob3BVW2RcU05efX90bnBpe4+Vb2RcWWJ+e456eH12cVtnYWJkfGNzcF9bW2RXWVdZYl1mXlhXW2ZRUmV0Z1taYmJfamxmYmtZYGVYWltVXVNhWE9cW4BVWWiDemVYX2lhW2Fda2KEeXdwfpKLfm5rcnJ0dGVlbmx1WZdNVWmpfE9acW1wZl5bXnFgbG5sY1VebGlnYWZaaGNrV1tnZl5pbnCBhImHeXJlb3p8f3h6b2RcbXhzcHNyfXJ6jViJgX1pcnV2gYRzfHyUh4eBeGNyb2Nig4WOg4B+b292YHVqhINrY1NRZmdtamdqW2JbWl1hW15gXFlSU1ZZW2SObWyXeXx+bnN0dnlnamFgX2xohXZqZFtuX3F9aXNiZWiIgXiEY2x3bnFyd2liXV9ucXF2gWxxd3dobXp5d3B4b2BVjmFpg4d4f2deaHeCfHmKXliCXFVOdJWHjIBUmG6nqoOZbmZ7b3qBdnZ8gXVkbYh9h3xgZ3dyXmJVW11ta2hmSElIUVlPRnVcX3OFd3l4XleRc4x6em5jWFNSbV9QV19wa3lrZG2DcXpudYGKhGhkameZqnNzfISLfYaEmJV+g4aVgXh5dn2EgX16g4mHiId9enN6bGljZIWJmICFgJqafX2AkJOTlIuGdoOWcnpygnh1fYd8fnuCc4eQh49ciIR0bHOEf3l+coWDaXp8R3Vvbml4U3hqhYSKTJR8gIWDj1dPcGpfiaR4e2R4q5x5dm59enuBgYV/iIqKgnJpZYZ2mZBpiqR8g6F3gX6AcG6DenJ4g2ZhWZOFdXuFlIBwhWeEi4ROm4iBc19RYniCaWpxf3lzdXGBiY9/kYOKjoeCiZeOe25kbnqJiXp4dFxUU0hpdGpwbWd1a29xeX6IfIBzcXdzXmdgbXRueWFibIiHc3l2f3xqfo+Wj3p0S5GZhGl0bntra2tke2hog3+EjoCHhpuOq4aFcYmFmoCTlICIkYh+iIV7ZHlkb3OAc3FwgIqAcmZrgGxucoCDdYWCf3d6cnJzdXN/eH1nc25kXlp4gZCAfUg9dXxwcmZgZGlbZWl9emdoXGR0l4BmdX57hHh6fXdqcHB6gnp+dGhtaGqId3Jwdm5wbmqCfoR+enqBfWRkfYNXXGeWemFgZWhkb4BsbnN7d3h7fodwZW15fmxoc3t3dX6Cfnp3h3twb3p8gG5mZ3RkV2Bpe4Nng3R0bmlrbXqNf3Bwd3V4dWZremteZm93cm1QlZ6AW3duZl5ibWxne4h5fXNvfXlkXGl4cYJ5goJnaHh1eWxzioNmZWRtZ3hqdXRkZ3JrfpagkXlxaYBze5WWgXVwbGt4bZmEl5aOj36JgXV5gXtnhXxzc4FyenN+b1dzb2tzcGdsdndcYm1mYVtrhYV9dXdvfo+Ob2ZcV19zcoNxb3RsZ1JhW15hfmN3dGRgYW1eYF5iamduZ2JdYmpWVWh6bWFia2xnb25lYW5danFjZGpibWV1cGV0coBnanuViXRkanJraG1rdWh/bWpjcYR9b2NlcXV1c2JgaGZwV5dQV2ORcE5edHJxZmNfYXRncnFxbV9md3p8d3xwe3d9Z2pycWJmaGdzdX17dG9hbXZ3fHN2bWNebXVtaGpncmdzh1OBdnFfZmpqd3treHiRhYiDemh0bWFefH2HfIB3bnB0WXNpf35qYlZVbnB4eHV6aHFsb3R5c3R4c3BpamdlYWuWbWqZeXp9a291en5qbl1bXGpphXlua2V0ZnaEbHNkZGaDfXB7X2hxbnNwdmpkX2NvcHByemhsdHdpb3l3cmhtZlVJhFxmgIR6fWdfZnWAfXqGWVR8XFhTdZKCgwF7pHqHe/V6AXuPegF7hXoBe4V6AXuGeoJ7unoBe4V6AXvXegF71nqCe756g3vNegF7/3reegF5wXoBe556AXvhegF7j3qCe4h6AgIEAIDu2rDbyc3Fv7/M7v3kys/J7IXYyfbW7+/U59GkvMGunryyudWI8+eEio2hh/fmz8y94eOqu/zYva/Fxaz19MO3uru2n+bHrsXGtcbUwLXEgPfWtcO2nJ283Kmwu7i0pqaqu7C7wNDiwbK6v7SywLimr6260dW7ubjAv720scvsmYD9y9XT1tHPvt/ViP3m2+bt49vYzM++w9a698vYwe7e0oXk69zgx8rp/er5hvb7/+nihOTa3OTm4N3Iw9ym1ejKgYfw38fJ28y6wMOyy7ap0K6tr5fEzoOYsum7ttfWvZOIoJucrOLQpavIr7TRzKTR173J+fHt1OPPta7pyLesn4C5x9u71NOyxszLxcy6yOuGwLbCvujp4N7cybrRxry/vMCvqLy6qrHBzp/CuN2ro5Wal5SNqrbTs6WirJ6TnZeVmJGSoaalq7e8w9G/3YCA+e/f8uPo/fHS09vW4tjKzN/f4uDGvZ+Yr8O+pbG+5u7u18XUvtvV6LydssDGyNjU2IDX18jCxM/KwMyxp62y1rqxq8XPy87bwsrBtbm5wKzFu7azsbS8prPHtqKSm7G00srjgYeKg+zU0b/J08bUsrGgoKqtsbKkoJGSmpyno7zFvLK9ub60rrazr7u2pJCjrLWyrrKsq6i5nprToLLKvKfS2rOqsba6s9TRvsvHvLy1t4DCpZyVkI2inrGstdGxr623tLe3scG+ys3CtKeuwOu7t6+ipaeoqaKupKGmq6Oqppqpsra2sbGzuMStpbq+47nBpaaxrKq918m/rcTJvLe1wLmsq6mws623mo2QpqyyqsDHz8G1rqq0qbHPyK6Vnqqjx+C9spyQnpCfq7q3u8HHxYDgxevlwtzTsra4q7K0xdzJ3ta8rsHUpKXFqrvH1NDJtru3r6rBxbK8vbrHy8HJy7u/t7eutbHVtqOdq6W107eooLOtsbi7qqy7sKaXrKGet9LGt7CpnKe8o5+pva6WoKmyppugnp6bmov97P/Gq4WNpaWlv6KlrrOprrKry7+0u4CxwcK0vKWVoKitqa2srK7Dt7ywp7CsqqmVqZyhv6SImpunrJKgkJirq7bTysa0rbKxtJ+o4t/Dxbyw0dHcw8Sx0L2nscLHu6TAyeC+saakoqGz+YbKusWyvrLDuLPDydq5v9jNhIbb0MCUscKxu8S+w8vg19DPzL/68c/HvuH7yYDFusbIvcPFuL+1v8LJtqOvu7a5v8PG5Nvm8dbGxc2/uLa9srW9sr7P3ObQ3NHAtrvGr625w768trXTwsjNs7vArqOkv6Gjm6yzmIubn8+/sJ+vvLXYvr7VwsHFvbvHxtfAzM/i38j93cDE4IGT5tHCvMe909TZ/Z/00cjM3err4ICikGyQf4KAfnyKpa2ciIeAmFaHd6GHpp6IoIpofoBtYHhscIBUjIBMS1BfTYh+bGhmgodcbaSIcmV4c2KjpXtvcHNzYJ6AbH+BcoeYhXqKXLCVfo+FbW2IrXR2gHZ1aGlseWtzdoGScWVxd29ud25VWlRfcXFkZl9kXlhXWnKQY4Cfb32Ah398YnpwVZWAg4iKgIqFeHZrc4JrpoKBYox/cVSDk4iMdXKFkICESYeGiHZ4S4OEhIyPjZB9gJ5pjZx/VViUfWVkcGdeYm1ggW9lj2VhYEpoc0tYWY1oaIaIdU1FWE9OW5CDWV51YmaAeFBxdFxpmI+Ndoh6YluTe29lWIBteYZrg4Fkc3t7e4VveZ1YZmBkY4qBeHJ1W0pkW1RZV2BVUWhsXWR6h1t1cYpiWlFTTkxLZXmZeGhiZV1SW1xeXlRWZGReXGFlaX9rglFQm5GCj4KAkYpvcXRyfG9jY32EiYdwZ1BSZG5qWGVthZyZi3iJfI+Hkm1WanBsa3VwdIB0eGxnbHh0a3VnZm53mntxan+BeHeCaWdmWl1cW01mYmBgYGVzXmx9cFxOVGNgenOGT1JOSntnY1Reb22AZWVTUF1iZ2RdXE5NU1ZbV25wZmJqZWhbV19eWmltWk9hbHJvbXBraWd1XVqHWGN2ald6gmBYWlxhV3BwYWlnaW1paYB6XlpYUkteXGdfaIJkamZkW11eVl5aY2BfXFRdY5JwbW1kY11eY2RuYWRtd3BvbF1oamlpZWplZ3VcV25tk256W1tfWV51kIJ8Z36Cc29veXhmZGNudXR8YE9PX2JgWWdtdmdlYWFvZXGSinFbY2ZffJN3cl5UZVtkcXhybXRwcYCEcJeWcYd2WldYTlJYZX1vh4BqXnKFWVx5W2h1hHp6bXl2dHCDfWxxcGtzdmhsaV9lWlxZYmB9Y1pVYl9whGtiX25scHZ6bnR7c25idm5pe4eLfnFqXmV0Xl1mdGhZYWpxamFnZ2dqaWGnm62Qd1ZbbWpgbGFhZmVfYWRgfW9jbIBjdXJmcltMVlleXGNna21/eoJ9eYN8dG5fa2BnhW9XZmdsbFRlWFtiY2yBf3ZnZG9vbmFkj4hwZllQb2xwW2JWbmJVXnF1blt3fJd8bWFiY15ytV2BcH1wd3B2cGpxdohudIV5UliHf3JJY25odHp2eH6OhIF7dGWQhGVjXoCTbIBqYnFvaHBybm5ob3Z9alpfZ2RpbGxqe290e2RWVWFaXV5jYWlwZGt5gYl2gXVmX2lxYF9nbm1saWB5aW52XmJkWldegmtwan2GamBvb5WDbmJtdGqFb3KJeHV4cWxyeohxf4CRh2iPdl1hekpaf3Vxb3htfXl7nHmfdm1xgImSkICZiGeIfIF9eHSAmqOUgoB0hk13bI56kod1iHlbbm5iVGleY3JKf3dGREtYSH57bGpmgoVaZ5mBa2Fybl6am3NpZ2toVY11ZHNxX21yYFhnSI16aHpwW196m2pvfX1/cnV7in2IiJCih36Fh4F9ioJudHF+k5aCfnZ6d3FtaXmQXoCZdIKBhn57Y3VsTZCBfoOHfX58cXNrcYBtmXZ8ZYuBdVKLk4WIcG2Bh3p9RH56fWlsR3t5eISFg4l0c4xiiJR7UFKOf3BxgHdrbHZqhHZwl3d5eGOGk1RffaeEgZqZh2FZa2VodZiUcXeNeHqOh2OCgmlzmJSSfY6CbWmagndvYoB4fYl0i4pteX16e4Vvd5pYaWNrbJCPjIyPem2JgHt+e4F0cIF/cXWDimR9d5FlXVRVVVNRaXiTd2ppcWpkbm1tbmVlcXFraG1vcYJuf09Nk4qBkYaFmpN8gYaCi39van5+f4BrY1BUaXRxYG12jqKgk4KQg5aNnXxmeH16eYOAhICIjIR5e4R9d4JuanF8oIR5coiMhoaTe3p3amtqbmB8eXd3dXmHcH2MfWpcYnFugXmFTE9NSYN1cmNreXWCbG9iX2hudnx3dWNjbHF5c4OEfnqEfH10cnx5cX18alxueoF8dXp4d3SEbGmccYGUhnGPlHRsamluZX6Ab3p4eX58gICScm9qY2Bzb3tyfpV3e3F2cHB0b3p0e317eXBzdZpxcG9mZ2Rma2tzaGdtcmdmY1ZhZ21zcXBoZ3RfWm9wlnF9ZWZrZmZ1jHtzY3h6a2dncHBiYmNzeHZ+aV5fcXh5dIGEint2bGhtY2+PinFcZWxoh6CJhG9kcWdueX54cHRvbYB8ZoqIaoV7YmBhWFxib4d7joVuYXaJWVt3XGx8ioJ9bHhzb2t/e2pxc212eWxzdGttZmdiaGiFbGVjbWp4j3twaXhwdXuBdXl+c2tfb2hjd4WKgXdvZGt8ZmVseGxdZW93bmRpaWxra2GpoLSWe1pfcG1mdWtudHNubnJujIF1fYB0gH91gWxgaG5xbXFvbm15b3Vubnt3dG9gbWRqhXJeb21zcFdlW15nZ26FgXlpY2xwd2dplY53bmNefn6Ec39wiXxqcYCBdFx1dIhwY1xgYV9xp1SBdIJ4fXN6c2t0eIdscH9zT1B8cmhEYWtmcnl5en+Oh4J+dmmPg2RhXX2ObYBuaHd3cHV6dnVpbniEc2Npcm11e317joOLmYN0cn1ycXB4dXqBb3OAiIp1gHNjWmhzXF5sdXRzbWiAcnd/aXFxZmJng2tuaHV8Y1ZkZop8cGRudmqDb3OKeXp9dXB2eIRweXWBemGGc2Bhe0lXgHRwa3FrfHt+mliRd3Bzf4uRjZF6AXuSegN7enqFe6N6AXuvegF7inoBe5V6AXuKegF7hXoBe456gnuUeoJ7t3oBe716gnvieoR7/3r/erZ6g3nnegF7kHqCe/96hnqCe4p6AXuIegICBACAzMjEy8/B0NTE2NbXz8vJzNrKr76LitTNzMvDs7OusMPl0MO91NXS5NTYhOnX29bIv7XMxLfTzK+65M/GxNi+pa/GyM/PisuZpb/H7uLnwrO1tsXoxreqwsmynavCx8PH0MvHzdbQ3cm1ub2wv8q6vsy3xb21vMK7yL/CzcfR1M6AurrBuNTk3svI0dri09nFz9DWzsK+x87RwNbbwO/hw8384IaK/9Hs4YeMnpz68fDX3dzl++W6t6u9xdHOvLjplIH06e7p397Bwsjtrbiuiemw3NTgzb28z8S3w8nJyKWdsryyu8S8sLO7xqyyzOit34ba3N2C5czVx8m3sbbC1LyAvcG5q+uT+oPW1O3txPPt5tbV0N/RyuTBrbmzwrS+uLi/wMG4rZyalqGnpq62qp2PkI6ipcm3wsPStZmfoY+PkZiMm66qq6+ks+KSu5LU4eLJ6vTo6Ojp5uXz7evKvsPi1cbG5a+elrG3n6q3o5+norvJvbyzsbTav7THvMLF3duA49TPwbus4MW4x4HV2fDHwM7Dy8W2wMzHv769ua2xxLynqau2xbizqqKro5+yr6u7zs/f1fGDhPTJusOmnaimqJqiqK2qq6KllJO/wqyypbi9u8G5v7W0sqW8uqSmrrqorMW/0rC3oqPC0srZz9DW6sK6xMHBu8rcwcO/vdHVtIqAuLqgjJ/SsM6so8CxprKxvcO4vLazvce6wr61r7q21ru7qJqPn6eto5eko5aes7CmnbKsysjBvKatrLWzxsPQya+vqa+ro6q2u8bG57i/trGusqmhm5+un5OOl6S2p6uprNG3v8DEutPL0cHBnaabnZ2dqrzVpaqzpq+wsbvIwcKAztjQxteEhraswsjDytHDzuvl08bKw8eDvru3tq+8u6SipsXHsb3NyL/Av7i7xdPIwberoqivvKmmp6isxNDdysS/vM3NwLzCyK+8xLCqqKGbs8rGsqWqtL6Znp6cpauvr66cpqKVoJuTjaOjqrS2q6qmq5aPiqKjopeSpKWusaWAucCqr8CnkpubqZuzxcbMx621rbWro62vnJCNl5ublIyal5OnrLCuo7C9x8O9qaayw6mwrau4yse2ttS3vsfNxrmnsczvw7i1r7C+s7GnsbCrsdjZx7e2zsOst6+0y9fHvNfdzcTj9sbjz9bq1NrVysXa1LDIwc7a5tHV5O7f39qA6+LT1snO2tTStKvDysrHxtnFtrSetb64w9Pi19DKwru7v7y9vsO8xtLT7dnc6MbRxLS6wMrAx7bfwsK61rzOyLqhq6mdoZypq4iItLmwuau3rsOqtM/AucTCwtG81MD4ztLY9cvX49fmkvOm58CyuLqwyNLX3e/UzeHz1NTH8f2AfHR5hIZ5hIt7kZSTi4aAgIh2XXBgXIGAgIB4cHFvb3+YhnZwfXh4hnd8UId5e3RuameAfXaRi3J2mIWAgZV4YWl7fIR/YYFbaYSEpp2ig3t7fIqriYJ4j5uBaXaJiH9+hH57fYmGjYBwcXJmb3ZnaHNjcWxiYWdicWhrcXOAgniAaGdtaYKHfG5ra3OEcYFpd3eDgXl0eYyFcIeOdpuMZWuZe1BKjG98dkdNXFqTiop0e3+JpZJ0bF50fYKEcm6SX1CPhIF+d3plbXWbYmxjXZhZgnZ+bmFjdWllc3x+hmBXZ3JweIB0Z2psdVxkeZFTh1R+f35Oi3eEeX9sZWdzgWqAbWpdVYtUoVJ7e5KUb5aShnh3bHRsZ31eT1hTYVVdWlthZWxoYlJTTVlgXmNqY1dNTkpZWXlmcnWGcl1iZFJRVFxRWmZgY2hbZYZdelt4hIh1lZqQjYmAe32Wk4ZtYWN6bmpwkmJUTWZqWmFpWVdcV3GFenhvZmeHbGBoX2hvgYOAh3BrYmNbfmlhc1CAgJt3fIt9gnZiZ3JqYmRkY1hbamNQTlBgbmVmXVZgWldnX1djdXN4aoBKTIptZ3VcU15TUUtTW2VhYVxeUU1wc1xjWWJgXmNcZlteZFt0c1pdaHBjaH96kGpyYGF+iH2Ed3N7jGJcZ19gXXB6ZmhlaH2Fa1yAcnZgUFt8YYppWGxjXWRocXFgY1taZ29fYl9aXGlfem9uXltRV1phYmFqaGBrf3tsX3Rtgn52c2JlZXNrdXmIe2txZmNZVWNyeoOEo3R8bGNfY2FeV1loXFRQXGJqVlhWWHJbZGRpZX54gXyAYWVTVVhaZneNZW94a3V0cHN6d3aAfYB4bXlQUlxUa3Judnduc5CJeG94d4ZffXd3dW12dWtscIWGbnZ/fXJwZ11faXdqaFxXU1xjb1tdXFtgcXuAb2xnaXB3b3B9fm56fGtoY1tVaYF/b2Bnb3pcYGBcYmduamtbY2BWZmNhW25ydXl7b2xrb2FbU2tkYVdSYWBnaVuAbXxrbXdeTFZZalppdniCgXSAd3huZmxyZVxXXF1fXFVhXlpqa25qXmZudXJ3Y2JwfWdlY1xhcXNiXnJaX2NraWRXXnibeHBqam96a3FtcnRvcY6Rf3NuhH1ocWpofYV1a4GEbWqElG2Fcn2OfoeCgHyKgmR5cHN1e2xzf4V0eHaAf397hHp9gn99amJwd3h1d4d0Z2VWaXBnb3N3bmNeV1VXYWRpbmxtd4B/k3+BjHR9dmhubntycmaGcWxrg2p0cGZYY2VeaGSAhWJhkJCAgG9waHBbYXNpZG1vbIJwiXOte3qAoXh8fnGAX5ZtmHdnaWddcXt+hI9xaXqLdHx4mqCAgHl6hIZ3goh3h4uNhHp2eH5pVmZRTW9wcXVwaGlkZnSNem1odnJ1gXN7T4N0eXNta2d9enCJhG1ykn14eodzYWd1cnRuUG1OVmxsh4GCaGNhZG2Kcm5rgY58Z3SIjIaGiomHjZmSk4V3fHxyfYd5f5CAjYZ/g4h+iXt8gn2Fhn2AbG1ybICEeGpnbnSBdYFtdHV5eHJrc4SCboJ/aIyDaXOVgFNQk3R9dkZLWVaKf31lb3eBmI5ybWJ6g4uKenSRWFSYjoyKhIFsc3qdaXRsXZ9pj4qSg3V5iYF/jo+OlnNseoF8gYqFe32Ahm92h5Rfi1SDgoFOjHyGgYV2cHSAjXaAenVnX5NWn1F9e5OTdZuckomNh4+Gf5h9bnh0g3V8enl8fYJ7bmBbVWBoaXB4b2RbWlhkYn1rdnqMeWpwdGRkZmthZ3Fsa29iaoNYbVVyf4Bsi5OKhYOBh4mbm5B2amqBc2lpiGFWTmlxY2pwY2RsaICRiIN6dHWTem58eH+CkpCAlH91bW1mkH1wf1GKi6N9f4+Ij4Z3eYN8dHNzb2dpfXloaGV2hXx8cmpxaGJxaWVvfn6Ee49OToxxbXxmXWJdYV9pcnt7fHt9bmuLjXZ6bHh8e4B4gXFyc2h9fWprd4BxcoaCkXd5aGiEkY6ajoqWn3JueG9uaXyFcXRxdoiTel6Ag4VwYWySd5mAcHx1b3Z5f4N1eXFueYRzdXNsbHZshG9tY11TYWdramVva2JodG1fVWpken14eWdmZXJufH+Lg29zcnRqZGtzd3x9nXB2aGRgZGBgW15waWNibnWAb3NydpJ1d3V2bH94fHR4X2ddYmVncoKccX2IeIF8dnV4cm6Ac3ZuY3NNTVpUanRxeoB3eJKKeG92dYBgd3JzcW12dWlnboaGb3qBfHJxbGVocn5zcWVeWmFodGRqbW1yh5WbjoeAfIaMgX+JiHN7gG5qZ2BacYmGdGVsc35gZGVhaW1wb25eZ2ZdamdhWm1vdnt+c3NydmReV29saGBcbGx0dmyAfol3eIVuXGZndmZyfXuBfW56dXlvZ292amVha2ttbGZvaGFxc3d1aGptcm5xYmNxgGpubWdrfXxvaoBrdXqEgXxudYuqgHZtZ2l1amtja2xrb42Uhnx4iINxeHJtfYFyaHx/a2N8jGV+bHeJeYSAgn+RiWuCeH9+fGxygId2eniAh4iBiX+EioODb2Z0eHh1e5KBc3Bdc3pyfoOLgXdzbWxweXV2d3d1gIiGmX9/hmt4cGNsbnx2e2yTe3Zyi3eAfnVlcHBnbWd7e1tYhIV2e21ya3hjbH92cXh4eIhzhHGjdnd+mXN4fG57UoxajHJna2tgcXt+hJB4dYORfoN7mKGUeoJ7lHoBe5p6AXvceoJ7hHqEe5N6gnuNegF7oXoFe3p6enuQegN7envCeoN7vXoBe6p6gnvIegF7/3qGeoJ7kHoBe/96/3rTegN7enuUegICBACAxtbGu93TvMrP3re68cjU9NG9ttzuz9zh3eHV9qmzuqvj8cLGxKi02vqB5tHe3dbe2qvE68280L28zdaWgfLhhv7It87H2MO6xdrH4uLV9NDPyaqqqcPBv93ZwsOgut/x7c64qOGX+b3c2MbFxbvExc2/z9DOwLasx7u4v8auwcuAz8rsg93N09DSwMzN29fWxL7BwMTJ6/jHxeTf0+HT1u/j3efm5eDV9/uHjo6S8725wcvKy8LH0ru3wMPA1MusxdbE1trOx9nXwMzc7MzA2Mu6yqXV/snAwcm+sJqqya2orbaVq8uqvMHBx9fHxtXHqKyx77vK1f+3wM7F36mcmbKA0LSlo7zXg9W5uNPm0ODW2s7DwMXDv6uztcHc08Cynpyrzbyglp6Wrq2ipZ+4raKor8O/ra+oraiwoqiQnZmPjI+borW9wLS/s8TsgYeF1/S/1Pvz8djE1uDT4s+7xcXBxeC4waim2vbpqqGosMSno72irLSpu76alJ3O0OLf2+yAuMnMuO3u08TQ6ObqzMrRwb7J0MzR1Na5ubjAw7+t0NCvtLzBu623sZzFjqm1rcW1ubq4zM3Lwbe2tamrsqWxrJGmsKqqsKakoJulvrW+wsK7wK+6uaaXrK2yw7W3ra+yqb62qaOuuLG5upyxytzCqbOut7G7ucTGwMrJs7ShsoSAl622rZqsysmjpae0wca008/DvrC8sMTBwLK8t7Oxw87EtKGdpJadoaero6iorbKqpMO6xqmmtbCko6Obp6DCrba3wbqwvaWywLmyw7WyrrPAsKuqpKWkn5eYp7ihobis0cyqvMa8wrvEy/zD7uLdvbKnlbOwpKGssrSsrKOsp62AxM/m6vvi2dvWz+DSzMbEw6q6yraxtKWcoKqzucO5vK+hq62+trbEvrDLusHU7cn3h9DJwcPPyMGqq7a1ssHBycqytrm5v7O8wq+2wLm6pbW8qLC1wKK2/tevwr6+vMy6rrqnq6KSkI+Sm5ifoJiWnaCXnZibmaOmo6GjlqGrscaAwr3OtbyzrLTXrbCovL2ysrbQzsa9vrKmmoyQoqKfqKSUoJyloqrY5svH0Lq9sayprZmtsaevtb7GuLy2xb7Hs8HCxd6+vMG6yNu7qKe3o6WrsbS3ub3Jw+7jwsLAvLzC0dLS0L/cy8rT39zT1eT12MrP3N3Pu7PS6vbv5NfZ3vKA5enY4tvb3uTT5OPGsMTezsnR2ca+x8a7tLS7w7e3u8Swv8Ozur65ybzM58zO2c7Rx8zFvb+9t7CxsMa3w7S0s7CdqKGpoJ6rppyep6O/vrjAsbGzsbrXwLTHvsC8rLHG1Lm7ssTWz5KlsMTJyOXT/uj06NTVyoDOysW9zdPi4tuAeoh2bpmNdX6Jn31/pn6Ko4NyZ4iXgpCPjZSFmGBkaV6Omm9vaFFZe5NNhXB7eXmChF92nYF3jXl6iJB1XaSNWad8boF7joB5hJCAlJWKpImQiWxucYiJiqaef3xec5SopYVuYpVoqnKGfm1pbGp2eoNvd3h6bmlfdGtrcHJfcnyAe3uUU4t+em1ya3VzfXyIbG5zd32GlauEc5iZjpl/fJV7cXh1cXJvfnpIUlZYk2psb292fXt6f2ppaWpuf3heeX5qenl2bXt4b3qJlYJ2g3Jlb1B2mWxpanVzY1Jge2ZmcHlVbXhkb3BwdYFvb3pvUm5zj19ugaBoc4N+i2NYVmqAgmVXWm1+Vo5tbIWWhZWEhXVwamdrZ1RaYm+BeWpZTkxefndfVFtZamhbX1ZtYVJZYHNtY2hkamNpXV5RXVlSUFReY2xvcmJrXXKUU1ZWhaVvgJ2UmHxren1xfnZkZWRkZ3tbYllcja2ZWFFdboFhYHhiaG9leXhWSlF2d4GBg5KAYW5sWYqJdGpxiXyAb295bWp0c3J4eYBjZWBiYl9QcXtdXWJnY1poY1B6SmRtXm9ZWlRWZ2pza2NnZFJYYVhhXURUYFtcYldVV1JfaV9kZ2VgY1VgZFdOYF5oeGNiYmlqZ3p1aGRrdnB6dVlofYVzWWRdZVtjX2trZ3R3aG9jdGCAW213aVhgen5aX15ja3VigHpwaV9rXXFzcl1hWl5gb3lwaVpXX05UWmFtbW9ueYF7coV+h2tlb2tiYWNdaV54ZnFtc3JweVlhbW9zhHx2ZmRuX1ZVUFBTUE1OWmhUVWtdfXRSYGNZX19ndqFvko2Nc2tgUWdlX2Jrc3hwbGVuZ2SAd3+Pj5uHe3p0cYB6enx5fmlwf3Vta2BYVFxrc352endsdHB7bW51al5zZ2l6j2yOTHVuZWp2bWtWX2ZlYGprdHVjY2JgY11mbGNmanF1Y21zY211e15rs4ticmxsZ3twandnbmhbX11eZGJqb2lpcG9obGVpYWlpYmJjV2NtbYWAfHiJeYB4a2mKYWFhcXNqbm6LioN8f3FpXVZcY2Jga3BiZ2FkX2OIjHR2g21ramtmaFhgY15naGtqXGBcZmRyX2xtcoxybHFygpR7bWh4aWpudnl+eHSFgKSPdXVyb294hIB7dmmFdXV3gn15fIudhXp7hIN2ZV90fIF/e3N4doeAf4V2fHyAjJCCh4t2ZnmHfnmBhnNtdHVyam1xdmxjZGxgamteY2xrd29+jX19hXl8cnt3dHt3b2tnZXdtcmBgZGBTXVlkYGZ3dG5ud3SPhnh2ZGFcW2B5YV9waWlpXGN9iWxva3eMh05fbHt4dI1+nYqLh3h9cldxaW5nc3uHjYiAgIx5cJWJc3+ImHJymnqBlX5sW3CBcHp8eXxwg1leZVqIlWtsaFBYdYtKgW96fHmAgV5zlXxwgHFxfYRkU5J/TY9sYHFugHBob35rg4B4jW5tcVpfYXN2e5OOdnJbcJGnpYl5ZZFfn3GJiHp5fXmDjJOGk5SShnxzhHt4foNuen+AfHeYVox/f3JwZW9xfH+Gc3Fzb3d7iZ+AdZGMf4V7epCCdHx7fHlxgn9IUVRXi1tda2xveHh6hnR4fn98jX9men5ugo18doSAd4OLl4R5hH5zf2CFoICAgIqHe2l2jndydHxgdIF1f4B+gYh5eoJ1WWZnkmVvfptrdomFlG1fX3OAjW9gZXeIVYhxcYyYi6GTmYuJiYSCgnF6f4aWlId2amV1lYpxY2tjdHNna2J1aVtgZnlzaXBtcG11bXFkb2lhXmBsbnl6fWlrX3CSUVFQepVga4uKlH5we314hHxramhkZ3xdZlpckbWhZF5seoxubYhxe4N6jI9qXmKIiJWXl6mAdoJ9aJCSfXN9k4+UgH+HeHJ7f3+IiZF0cmxvcG9igpByc3R3dGx2c2KIW3R7boBtb2VjcXR4dG5xb2FnbmVwbFdqeHV7hnx3c254gnd4e3p5fm11dGdecnN9inh1cnZ3cYJ8bmpygHmEgGd8kpB9bXNrdWpvbHh3cX2AcXRnemGAZXaAdmZyj45weXh1e4RzjYuCe3KBcYOCgWptZmhod4B0aV5iaVdfZ3J6dHFrcXZtYnNtd2BfbWxkZ2hib2mDcHt6f398hWhteHNwfHRvZWhyZGBfW1xhY2Nnc39tb4Z6mI9sd3hubWhseqV1k5CQeXRuX3dzbW92enx1cGhsYV2AbHF/f4x7cW9sa3p0dXVzdV9odWdjZVlSU15tc351eHZpb3B5cHF4bmN4bW2AlnaWUH14c3eFf35qcnx9eIiJkpJ6eHl3enN6e25xdnZ7aXN3aXR6fmRytpFsfXh4d4p7cXxrbmhbW1pcZGJqbGhnbW5mamRmY2xta2prX2lydouAiIWVg42HeXiUbW1vgoJ0dHGJioaEhXp1a2Fqd3ZyfH1wdXBzbWuMjnVxemttbW5pa11qbWdtb3N2bHFrdXWEdYKEhZp/d3Z3hZR3ZmVzYWNocnuChYGLhaWWfH54dXR7hH53cF55aGlte3Vwc4GRf3N0goh8amV7f4SBfnd8e4+AhYl4hISGkZWEiYt3anyNfniCjHl0fH99cnN2e25na3ZndHZlbndvemx7jnt9hXZ2a3ZwcXp6cWpoaX1ze2psc29jaGNtZml5dGtqcW2Ggnp9cHNtanKGcGl4cHJuYWV8h21sZG5+d0ZVYnJ3d4t+mouRinyAdlN5dX10foaPlI6pegF7kXoFe3t6enulegF7nXoBe6V6hHvDeoJ7lHoBe8R6g3v/erJ6AXv/erF6AXv/ev96x3oBe4l6AgIEAIDp49S63vTHw8DD37XG3s3V2sy5w+Lm8oGA393j1ra8vrfLxrvW4IrS7eri++nEwtDeybHO1szi4ujc1eL4/tPr3OLe09zv3N67zs7MwMzU1+LS7r7S6sS1xoiBp6+dvLnQ5+zFyeTx2L/L3suywsy9vrqlq7zEsr2+w8bE1raysoDG366vzefi39zUqoHYvbvO0cjW5Nvpgs+819jSysXCxdfe8eLtg46ij/OBg+vr1cDHyLzAub+8rLO2qqm9ycv848u6xcTCyri6xLvBrcbOw8Hg1dnJxMCxt7OfqZOs3diqn5nCv8y+rqvAxNLTx9fd/fPAsL7JzcKunJu/u6awwYDLp5zT3Mfat8XPxrOaoaDDv7Kzq7eyqramnJaam6ievb2ztqumrrrEwd7fm620ta+xqKmjlZGbmaStmpSWmaS0tqKdu8G0y8zFvNTX1820ydDRycjT4pPh64fY5t3Fw7vB68mpstXj6c+Ujaa0tLmu0b+/rLCrxr27t7W/tsjFtoC7u8myra64s8n96/Diu8DK1MrJxLjAyNbG0d/PytL2787KvZiWo6+7u8K4stHeqbG+s6q2vq/EycfGtqCntqChnreqrbK0o5qowrizrbvAq7q4sbzCuKWmrq6ttayxsbGn0ru0zLmyr7bAraKttNy7srunnK+3uMCst7Cuoa+1voC0tcGdqbSvqZ6vr7HK1MvhzNrJva6xwsa4sqy/v7i/v8Kssrq1oq23tKyklJmyrKOvsamesKe7rqujm5iVlLmsrLGzxce1rKKjr7jNy7yhu7C0qsm0qLawsKWtpp+kr7Gosq+7usi/xdfc3+Xix8e3qLSdqLKvubS+tb2zs8PIs4DC3fnf4Njk/Pzby8LNy8C5qbegoJKio664xuzxwr26rKaZm6CtvsymrqyqwNTDx9Oyuq+yubysra2xtq62vr6yubrMx9TSsb21oJunr5+qvL6/pLLNub/WxL+zu8DIt8LAtKOgnZuftquZnp2VoKmwsbappKGjm6+jnZ2bqqWjuIC8tL3Dur/EuL3XuKqirK+vuc+wvrS2wr2smo+LmpSMpJy5sfHn6se1tKiknrK2oKymmKWksK+qtcrHx7q5prews77G2cvOv7K+z8G7rquyvKijqaKmr7aysbKzsrG7ubavuMfWu8ezs7/Bw9L/29Hf18HM0cTQ2ufS0++B/+bt6oD18vrv7OTdyOvcxsLNxbezwL3MwrnNx9/Y1crWycu5yc/SyL26u7yrs7vCyt/byLq/68quq7LE1LqsqKStysnM4sy+rb6yv7KtqZinp7m9v7uxo8Sjs6rBtKnExeXOztbLybWxuPnyzcfK0dHXyeDl0uO72rW0tM7Z3Nba5MvC2ICRiYJrkqmFhoWJoHmFlYKLjnlzeI2Qm1halpKYiG1sdGx8c2V1fE9vfIB6koFmaXmFdmJ4g4CSkI6Efo+fo4KRgYmJgoqjkY12gIOBeoaOkZqPqoWSpY53gmNcaWxZcXKJoZ54c4iReGRvhXpodXpucXNdY3FyYGt0dHZzgGpnbIB+lGhoe5SMgnt0Z0x3YGd5fHOClJWhWpB6g4qEeG5tanJ3g3WASU1XTYNGSYuXiHh0eXV/cXBwYF5iXFlpcXqehnNrdHBubWhudmtsYHJraGl0cX1vbGhgZ2BUYFRolIxkXVl/fYh4Zl5wdX59cX6Bp5xrXW57hHlqWlRzcWRtfICFYFeIkoGJbYyMf2hVXF12b19jWFxaWGRVTUdRVWBafHtwdG9pbnyAfJeYVGNoZmJjXFlRSUZOTVlgUVJXWGFscWBbcHZpeHx1bIOFgHZec3d8d3d8hlaFkFyChHZiZlxfhWtUWXaIlHxMRlhdX25wlH18aXBpgHRsaWVxaHp2ZIBeW21aU1NaVmGJgIx9YGZsenBwal5kZm9mc4J2cHGQlXZwY0VKVmNtbndnZH6AUVZgWlVfYFRpcm11aVVdaVNRTGFWWF1kXFdhdWhaU2JnUl9bWGFhW05PUVFRXFleWVhSdWRbbl9eXWJqWlJaXYRkXGZXTF9na3Fka2plX2drcoBrbX1cYnhmXVttY2N6eHWKfId7bWBjdG5nZmBqZ15rcnFfZXBqWWNsbGdjXmd5cWp8gnlsdGt5bmtiW1pXV3xtY2dodHNqZV5hbHOJg3VhdWtpW21XTlNRUUpVTUhNWV5WX1piYGpiZXeCg4aHbW5kVmJRXWZmcXN5dHlubn58a4B1iqOJhYCLnZ6LgnaGhndxZHFiaF9qaHF3f6ywfX5+amNdXV9nb3lUWVdWZ3tobHZcZVpbZWdZWFpfY11haWphaG15c399YmtjVVFaYVZaaW9uWGd9bG2FdGxiZ3J4a3Z5b2JfX15ienVma3Ftc3l4eHhpZWZkXG5hV1Vca2FheoB4dX6AcnNzZ2iIbmJeZmhocYFwgXl6gHltW1NTZ19UZWF3bZ2TknZpaF9bW2xuYGRiWmZkaGJZX2xqaWJnU15YX2hxf3aBdmd0iHx5b21zfXZ0eW9ydndpZWZpZGRwcGdeZXODbXxxbXRzc4KihH6IfXB2eHF5gYl2cns/inyLioCSiYyLjIR6cI2Id3V9eXVzfHR/dnKCfI2Khn2GeX1yhImKfnJxbGhgZW52fIqHeXFyk3xmZWl8jXlqZmBme3Z0g3RpX3JufHRycGNucHp6dGxeU2tOW1NmWFtxdpaGjJGHjHd2fb2yh3p5gYKFdX+IfI5qiGdnaHuAend9jHdwgYCWjoNtj6ODgH2Cl3J/j4WFf25manh7gUdIe3+IfGJkamR0cGJ1f1FzhIWBmIVqa3iEdGR6g32OjYh7dYCRknCAc3x8dYCYh4VodXRvZGl2eoB4jmd6kXBhb1RMXWRacG+Ako5vcIWSfWt2ioBvfYd8gYNucHyAcHp/g355hnJub4CBlmpug5iKgHpyXkl0Y2x8gXV+iY2XUop9jI2Dd3Jza3N1hn2FTFBWTYBHSYiOg3NvcG14bHZ2bHBzY2Nwe3+YhXNyeHd4d3J6fnFyZnhycXSHfIp+en59gntweWZ0nJd3b2uLiJKDbWZyeIOEd4CFpZpzZnSChn50ZmJ8fHF8iYCTbWKQmouVcoaNhnVhb3KKiHZ6dnZwcX5ybWNocHlvi4d7enRudYCCgJWVV2RramhpZGNeV1RdXWpwYmBlaXN+gnBpfYBwf4F6bn99dmxYbHV7dXd+iE+IklN8gXhlZV5jhmxWXX2SnYleW2lvc4B7n4qPf4aCmIuHgH2HfImFdYBycH9rYmNoZHGViJGIanB4h359dmltcXtwfoqCen+Ymn54bFBWZXN8fIR1coKJYWh0bmZvcmJ0fnuDeGRqdGJkZX9xc3d9dnB6iXpvaHN3ZHRycHt7c2dnbG9veHF0cG9liHdvhHZzc3uCdG92dpp8bnVmWGlxcXdncHBwanJ2fYB1eYRmbn50cW6AgYCLiIabjZeMfG50h4B1c2t0b2dvdHlqbHRxZ3N8fHVuY2h5a2NvcWdcaGN0bG1oZWZiYIR7d3t8iIN3cWpnbHKGgnJfdmtvZHhmX2lpa2VwaWdufH94enR2cXhvcH+FhISIc3pxZnJga3V3gX2CeHxta3h1YYBtgJd9enV8jI14bmZ0dGVhVF5QVUxYWGVueqepe3d4a2ZeXl9lcXtZXl1fcIBxdH5pc2twd3lrbG92e3Z6hIJ2fn+Lh5GSdH91ZF5nbF9ldHl6Y3GFdXyRgX1xdXqCdHx/dmdjYWFme3RiZWhiam9vcXRqZmVkXm5jXV1hbmlqgYCGhI2Rg4iMgoafhnVscnFueYt3h4KFjIl9bmNjdW9keHWMfKaYlHZnZmBdXG50Zm1oXWZmbmpjanZzcGlwYW5scneAjH+GfnF3hHh2bmpxfG9scm5yen10cXRwampzc2xiZ3OAaXFjXWFjY3OYdGx5cGNrb2l2gYx7eIFCjHuKioCUjpSQkYmCcI6KdG95d3NxeXF+c26CfZCIiH+IeHlrfISEenNycW5fZW1yeIV/bWNnjXthYGZ7kXhmZGFng3+DlIJ3boB0fHNvb2Jubnp9eHVqYH1bZ2B0ZWJ1d5N+gYZ8gG1pbqOaem1vdnV6cH2If5Bwhmlsb4WHhIaLloR9iZd6gnuNegF7snqCe696gnuKegF7jnqEewN6e3v/eqt6BHt6env/ev96/3r/eqV6AXv/eoV6AgIEAIDl29LFvr+7tcDEvbGvqbTBx9vOzdzT2ejq6M/hy8TEwMO+0bbD0dbY7OXN1Nbe1N3vytDIs8bfgO7g4dv+idHw1+nu0tX5xtTGtcLT39HXztLk2MXOzs7T6/z94tqE49XVzti6vsSv/4X18N3PyMTXu9Pj0ubczuDXu7GsqKepo4CdqbW7urqz5vns1eaquNfs09LRyr7LzNe96P3U4Nbi0dLDv8Ti9++D+fLv4OjazM3Kwa+usqedxsCim6K0v83q/+TMybrByLe0w8u6vOTNv+WA6Mj56bmkprmgrqW54Ly1oafEv7jBusni1+Dl3M76gd/Iw7a/vbynnJ+otL6vvoC4oJ63wry2wsDdr6SlvauhqLGtp7autbbBu9XY09bu1ce0v8W1y8zGurClss7b383Vzampt665oaazj5GBj5OPkZujtbzCwM/WzM7Zz8m3uqvH5u3n6eXOtbXEn7vu7f3C/MnH7t/U4LKnm7iss7+unsW3rbC3oJabqL3HtJqu+4CRt7u9vrzCzdXOvu7u59DzvL/UyObLxt/w6ti+vcnggsO6t8CutMza8urk1u3Uu8/WtbCpvLC1r7avwbOhvq+tsMCir6qqna6su7+qubrB3MvSxr/Ax7u1sKavraGnlrCjnpWZmKCTn6a5s6agtbux1smeo52bqaSqqqmks6akuYCorqq3qaifoq60prHJw7C5ubm2qKqzsL+6sqedorbMvrbCw6uqrK6spJaZn5eEjKjQvpSlnZKUjpeNj5SXo6Crr7i8u7uut7Sxv7jWt9jNusTBs7SzpZ+hrby8qL62v7SsyLGyt7/Kw9PCzsjcrrTEycDV2a2nxMLAt6uypK750oDQ5vnz69XJzOTcy7nFxMGmp6ehpLW06dfc1uXp0cu2qqjGyK64yty1vrzBtLSpp9jFqZukt7zYuMLE0MewqMK7qLHG0Lixt7a7yLK/3b7IgdvXws/issGpubi3s8vFsZ6zrLWgq5+XobKkqLPPtLrHy8W6xLLCrKahx7mwsa+4uYC9wbu6vayckrCgoqOpnaiMkZGmkYeJp6aWlJijlpevt6/Fure4vrGqoq64sbGnrqihpJynv8jP08XOzcTWvbTj597Zw7bMwMC9w8G9vbamoaCuoZmxu7uttbexp7S9ubaw2rfY3cS1wcfQ2tzIy77GydXhyc/n0MTFzLvC/4SI74DYxsPFxs7CyMjj3+nJwri+oo65trzBwcbHzd3Xu7fHwsLcwcjEurTCw7KvorSxq83X6N7q8Ojg3rjEw8rAyvfszM6wsrW+y8HCvrmqpZyhrqarsMnAwczOvbqyz+jQyb+uyLzg25Oaw767tKnbucrJyM7Rw8GxpsXLw8vg597i4ICOg354fYaAfIOFenF0bnJ4d416eol+jKWtpoqXhH16eXZwf2RveHt3iH9xenyBe4KSd3l2YnGHUpaFhX+TTnOQhY+QgYObeIJ6cn+MmIiSipGej4GKiIeGlaKrm5NZkHp1dXhcYWhYn1Sel4F1d3WJcYeXfY6MeIaBbWVoamhvaoBjaWZpZmRYgZGEb4BNV3eKeXeBfHeKfpF7kJyCi4KCdHBiXl5xfntJiIKAfZCNgHyDf3NsaGNZdXFcUlNeaHaJoo16dWZqdmhpfXhpb4RpZnpFdmuXkGRUWWlaZ11qiHFlXWN5bmptY2yBdHx/dGmQSX1saGJvcXNkW1phcXppd4ByWFJvdXNyen+rc2Nie2tjXWBXXGZZYGJxbIGHh4iej4BweYNyhIWBdGdlbX2NkHqFelZTXVliT1ZYRktDTlBMTFJea29ybHZ/cnaAdXJiZlFpi5eYnpR/aWBwVGaLfY1flW1mjYV7gFxUS2VkcHdhU312a2drWVFRV2l0alNklIBDZWlsaF9eZmdhVXyDeW+ZZGN2aoNwbYSQi3hhWmeBUmxkaHJfZ32Hm4+HgaCGbXuCW1ZSXldiX2ZgcmJZZltXVmlWaWhmWWZibm5YZWVqfGtqXllaWE5MT09dYFBWSGVaUU9PUVRLVV1sZVdUY2dbgXVRUFBSXFlaX1tXZWBZc4BkaGd1bGhqaG1yaGNybmNraGljVltnX3JqYVpWV2Z7bWx1dGFhYmZjWlBXW1ZOVW2MgGBvb2NhU1pUVlpYZF9nZmppa2xcYWBjenqUcpCCcXZvZmNhU0lFUFxfS1dXYltUZ1hYXWd0b31td3KFYGd4e3B/gGFfenhycWJrY22vjYCFlKGal4J1d42IfXB9fXtmZmZjaXd8rpuclaSki4Z5bWl5d2Rpe4hdYV9oX15SUoBtVUtVZGyKa3eBjX9rZoB6ZXF+gm1nbWprd2NsiW5wUXp2YWyDXWJWZGJmYnx3ZVxzcXprem5mb31vcHePeHh/g391fnSEcGddeGhkbW11c4Byb2lobGNZUGhWV15mXWhPVFZoWU9QZ2RVW2FoW1lqb2l4cG5wc2pkYWZtaWpmamllaWJkcHN3eGx5eHJ2XlqDiHx3cmp5b3Ruc3J2eXtydXeDc2x9gnlmcG9lY3F3cW5qkHCSi3Ntf4KHiI99f3N9e4OId3yRfm9tbmFjjEpQiIB2a2xubXdxdXiKj5WDfnZ7aFp/enqAfYWDhYyKc298cnGPfH56dHF5c2FbVmhmYnyBkIqYlo2Lh2x4d3xxdpaQdnliY2huend3eHJoZFdcZFpfYnhraHJ2ZGligJ+LhXxthnual213e3RpYV2LbYF/gYKEdmxgVnB2bnCGhnmChICMhH94eH18en5/dHBxaGp0dH9tanRqdIWMiXWIe3FwbHBugml1gIJ/kIZzfH2BeoCQcHNuVmh/T5B8e3OJRWaJeoiMfH+bdoJ1ZW15gXR6c3mHfW5zcnV1hpKZh4NPe25wb3ZfZGZZk1CVlIiBhIGUeo2diZeQfoyEdG9vb3B1coBrcHBwbWtghIx/bX1PV3KKfXt+dXaEfIt9mqSKl46JdWxkYWJ4gXlJi4eAe4uMfXh5dWxpbGZgf3xdVllkbXSBjop8eW1vfHJ0gIFyd4xya4hKgHSbmXltc4F2eniDmoZ9bHGEfXd6b3iIfoiJfXCVTYF0cGh0dXhrZGVre4N0goB/ZmB8hH+BiYWkfHFyiYJ5c3htb35xdnqFfoyUlZOllIJzeoV4hoR+cmhibH+QloOOhmRkbmtzYGltW19WYWReXWRufHp4dHyBdHR9dXlvcF10j5KSlo98aWRvUF+AeoZflnNxk46Di2pkW3h5god0aJOIfn6DcWdpcISNfWhxmoBQc3h4d3BvdnhxZYiUinmibWt+c4l2cIaRj3xlYm+GUnJqanVpb4KLmo2IhZ+JeomQcGxpdnB0bnRtf3RpdW1rbH5pe3h3bXt1fn5odHZ7joCDe3p7em9sbGp4fGxyYnpwZ2NkZGpfanF+d2llc3lsin5YWVhZZWRnb21qdm5nfYBtcW94bnBydIKJf4KQhnd9fH11aWp2c4eAcGdiYnCEenR5fGtrb3V3bV1laWBSWGyEeFhmZVtdV2JeYmhmc292d31/gIFvbmtse3mJb46Dc3x1a2tvZmNhbXh4ZXZ4hH11hnNwcnh/c35yeXaCZGh6fHOHjW9uiYZ9eGlsYGWeeoBxgZKMiXFman95bmFubmpUVFFQU11fjoKIhZmahYN1aGR2d2Vsf41kZmRsZmdgX4+AamJodXmSdYCHkohzboF9a3OBi3Rvc3Bzfmp0kHF2VYOBa3mNZW9gbnBxbIB+bmB3cnpodGlhaXZmZ2+LcXN5fXpyfnGAb2dfemxqcHF/gICEhoeKjYZ7boNwb3J5cHtgYGByY1xgdnJkaHB4bm19gHaCfHh1dmtlYWpyb3BucWxjZ2Jlc3V6fW54enZ9amSLkomDe3OBcnNvdHNyc3ZtbnCAbmh2fH1weXZuaXF3dG9pj2yLhWhgcHBzdntsbV5oZnB6am6DdWlsdGRkjEpPhYBxY2JoanVsdHiQjpR8dW11ZFN2cHN8en97fYF7YmFtYWF8aG9va2p6emZhVGdiW3d6ioORj4eIhGd6en9yd5uYgoNqaW94h4KCgXxwbF9jbGRoboN5cHl4aGtkgZmDenBfdGqEgVtgamddWFaDanh4d3uBd3JnXnd9dnySk4iMibd6AXuFegF7n3oBe4p6AXu+egF7qXoBe556AXv/eq96AXv/ev96sXoBe/96rnqCe+Z6gnuZegICBACAj+jU1Mm3w9nQveHXzra5tcHj18zH1uzg0Mu2v7u+vbW+497Pxc7UybrP1+fn6tHM4+Hn4PjX2t3y6eXa3Ni81+7V0r3Gx8/P3NjFzcHQ3ejLyNPNzdLf1tDd3/6CgpL52sPGv7zY2/CB2ubZ5MDEzZnFgdfYyLOzr7OlqrWilKCAnq/D0MuyweX77t7l5t3/4ffn0rWivKmjy4XVyOLc3d3J34uTi+rU3OLd2NCztr24spuaq6qikt7Q17vZvri74f3VyeGK673Fw7q4tabKy8/l+frw096vndLgtLyYvsTNw6isrp63p5qawub/1Mfd1tLS7NS8zrqzpqK2uq++wcmAyNPJu7vBpKGwvqWuwKamiJKeusSxpbPJ0Or45cbFuNPKxLi3yLu1mqyxrqu/sa/R166msbqgqZ6dorSqo6KUnZyst7rNxODXyMK1prGit73FxcLSz7TG2LbxzoSz7db4mJiH3cuztqmelbu+xLSinKvJyMm9x7G3pKizqq65vqaAlLnhxsXLs6mdtMzU2dTJ6NDI3dPj4c3f6cjj1tnI083SzMi9s+HZ5MSdtcrDqKSssa6nsMa1oLObyLGurJuqqrTwnJWenpimr5+kocHBzNHLz+bbyLbCs7SzrKa0t6Kpp6SqsaSinbu5w766ubKht7a1pK2vtKmspq6lrqmyrL+At7Kot66asbG1uLKgrrK1v6Ssqq6nt8Gssq+yqKmfvamrxbXBrquhtLKwo5qdm62fpJqoqLSfnJWTjZqWj5GpvbW8r7C0q7zGztC8x86AxbSwpLK2qqOenqyxta6svLe7qayqv8GswLK5s8LT4te7rb7CvMHGo6C+u8G6tcrEtbmAsrXL2d7SysG6vK226OfYuNLTspO61NjS3vP0gdOuubOusLi9x87Os9bCxLaroqyyo7bfwKXD1dHf3NzIp6muraOqutS+raeoq9PH097h6NvZ1MPd5rrR09bln52foqekoJ6WobCstaKioMbFy7y9v7uxxdjU0dbQwL2yrsDBvMCA17ShmrGwpKSknZeYnZiNjpqsoZycpqGRpLusraWws7CZrayio6mwsrS1rp+frJ+vtLqxyuHd3MG6wLPP2MjJ9NG4zdzXvr7Eyb7KxM+5tK2usKecn7DAxL+uoa6ytaSlr6ysu8HDwry7wMHPxcO3rbC6v87q3efr4s3Fwb3P2NaAwru5x9XIvaimubm/xcKrsbezspqg18Orwce8vMfS3tLG2N/i1d7K9t3WwqS3ybjZ19PW1Nre4fTv8Ovv8tTOyc/AsbDJv7KrqKSloa2Ymqeux7S5y8G7yMXTyN7b5tTFycDCwNHM3/bMuq6vubbT+cnFxdXL3tDkuL6zu8HDgIiAYp+OjX1vgIyGeZeOiXNwaHCJgX16hpeZg4VyeXZ0dGlwiIR3c3l/cmp6e4WHiHRtgoCBeopxcXWHgYB6gn9pgpB9d2hyeH2Aiox/hn6Ql5aCf4aDf32Hh4SMiJ9RT1aVe2dlZWF2gphOfY+DkXR8fk1zVoR+dmRoanJpcXxqWF+AXmp1dG1WYoGSg2x8f3+fhpOOgGlhenBkelOCeomCg4F1f1JZUYJxbXJ0eXpsaWtwcWNgbGteUIuGg3KHcGlqgaZ/cIVXkmZxdmhkZFVscHN2goaLeYddVH2NbWpNZ2x1b2FdYVdsY1pZfJmngG2BeXZ5jHdpeW1tY2Bxc2h3d4CAe393a2p1YGJuf2xteWBeS1FVaHBiU1prb4WQfWlrYIB5d2pwf3hxX210cG16amaCgFpQVV5PV05KT1xSUFJKTktbYl9yaop3bGZZTVRHXGJvcW99fGd7jGOWcUxgmXqVYF1TgnljY1lQS2Vrb2VWVmh/dXZ0g2lqWFpmXFxeZVSASnCWcXB2XU5DVGFoa2pid2tre3KEfWl9hWqFfXtqb25wdHJrZ5OKlXlWaHdzWVNZWlpQW29gUmpWgGJaWU5dYGqiVlJdWlJhaVdZUmlobXFrZ3VkV05dWF5eWVNka1VZWVleY1VUUmdlbmppaGRWYmVqXGJeZltfW1lQW1tgY22AamtjdHBkdn2Ad2hha2NkdlxbWlpSZHFfZ2VhWWJbdF1cd3B1Y2RaamdjV1BXUmNeZl1jaHZraGNhWF1YVVVndWxuY2VlXG52e3xue4ZhgWtnWmVfVE5NUFtgXFdZZWNgUFVUZ2dUaFhiXXCGk4hxYnB2dn6HYF50dHJycId8b3CAa29+jZOKf3ZzdWdwoaKPcouQbFV1io+KkKGrV4Rpb21oZ2lsb3ZyXXJkaWJXUF1lVmWLbVVxh4CQnpaAYWlvb2hueIdyY1xcYH5vdXyEgnZxbGF4iWZ0eHqIU1VXYGRiZGVfZnFvd2RjYYKFh36Bhn5yf4CBfYiCd29iXmx0cXeAkXFjW2xqY2dqYFpWXVtRVF1rX1lbaWtbaHNmZ2Jrb2hVZmllZWxtaWttZV5fZ2JudXhnd4qJiXBsa1txfGhjh2tedXx3Z2Zoc217eYmFiIaCfXJsZ26AgnhpYGxwdWZoZmNpf4mBd251f3+HfHpyam5+f4OQipOWk35wa2R2eXSAZWZodoB7dGNneHl6gIRyfYJ7e2ZsnotzgYN9eoWGlIqAkZGRiZGBo4qFd1tsf26Fg4aGgH+Eg4yOkIqPj313dX1vYGFwbGJfXVxdWmNUV2FneWZmdnFocmp3dYiMnop8gn17eIF8j6R8cGVmcW+Ltnx7eYN2joCUaWlaXV9kV1yAYJiJinxwfYmAcoyIfmtrYGiAd3Btf46Ld3lncW5ubGFph4Z3dHp+cWR1eIWGiHFuhIKDeIhraWqAeHNweXdhgZmHgnF4fX99g4JzeW5+iohvbXd1dHV6dW93dI5ISFKMcmJkZmFye5BOgZOLlnl/gVV2VIiDe2xub3JudoBxZWyAanJ9fHRdY36MfGt0d3eZgI2Hd2RgeXBifleNhZCKg354gExUU4VzcXVzd3hsaGpqalpbbG1iVpGNiniOc2djeZV8cIVRkGt2dWxqZll0cn6EiI2OgY1xZZGhfn9ke36EfWZkaGJ1amFggJ2jh3SFfXx9k35ygnZ3cW2AgXeEhJCAjZCGfH2FcXR8hnN9inRyZWZshYV1aW9/gpeikHx9cI6BfnB2hHtzYW9zcW14bGyEh2VcZG5lb2Rla3psZ2ddYmBudXGBdoqBfHltY2dab3V+fnqHh3KCjmWUcktbjnaNVVdRhoJxcWhfV3J5gXdoaX2Vjo+Kknp9bnJ7cXFzfGmAXn+dfXuDbGBUZXN4e3hwinl4hnuKf2t/hGmEe3tvc291dHJsZ5KQknRYaHh9aGpxdXZpc4V2aHlkhW9sa19tbniyZ2Nta2Vwd2dnYnl3f4SAgZWJfXN+cnV2c3KBgWxwb2xzd2lpZnx6gn17e3ZpeHd4anBudmxzcHVuc3J2c3qAc21od3Nogo2Qj4B6ioeDi29xcnJseYJ0fHp1aXFqgmpog3d8cXVpd3h4bWJoYGtia2JqaXNoZ2hoZG1qZ2h8i4GAc3R1bX2DgoBwfIZXfWtoYW9yaGNiZ3N3eXF3hH98amxqeHdkdmZrY3CCkYdwY3J0dH6JaGR6eXp2cYJ3ZmWAXGBwfn50aGRgY1hhj4l4XnB0VkBccnx5gJOcT3deZ2llZ25xdXx1Ynlpcm1lYG94aHaXeWF+kI2coJ2Lam9zb2hud4t2ZFxcYIN0fISJjIB8eGuDkWx9gIWVXmBhZ21rbGljanRtdWFfXHp9f3V4fHVqeH99eYF+dHNpZ3p/gI2Apol8dIWDe4B/dnBuc29kaG95bGZpdXdodIN4e3N7fXhhcG9sbHF0cHFxbGJiaWNxdHRmdoiGg29rbWF4gGpniXVnf4h/amhrdWt3doN5e3x9e3FoaHF/gXlsYW50eWlnaGJkdH12bF5lcG10Z2ZdU1RgY22BfIWGh3RpZmNzc2uAXFxca3ZvbFpdc3N3entkbXRvcV5ijHhgdHZsaXFwfXNpfoKBdoFvmYWFd1xyhXCKhIaCeXl9gIuOk4yOjoB6eoV4Zmh9fHJtampqZnBbXmlviHJzgXdrdG15dYaEj3hsbmhoZ3BtfYxxZ15eZGJ7nXh4eIN4jIOXcXNlZGpvUFcBe9t6g3uJegF7iXoBe6Z6AXuIeoN7n3oBe/96l3oBe4R6g3v/eut6AXvNegF7/3r/euR6gnsCAgQAgNa/uLOvrrO5xcvh5Nq+tru7wr/Hys7Q/+HW18vQyceytLuUr8Ta0dje9+Du7NXS5e3rh5aJgfLj28q8w8jdy/bRx7HI2L/ZwryvuLOyscLDwMTtiIry59zV3erq8fuah87Cy7+4vsrP1t2C6+fTytLC1NrElKW1vMK2nqWan6WngLDOxtXt+IH59oPe2svFvdSG98zU+KGYsrGwr7q5hOrg6++M9tjW1/rr0bq7t666urCvn6CkppGgwL210uLV28nJv6zGw8/g0s3MycT859br7evkx6/Dtq7yz8Goq8Gxu8HVr87GwdPRxLe/u67ph5Tq58rc5NPAv5mY2M/t5/HtgIH96O/ZuK2Rqb60rLKzrKaho6a0qsHN4evk29bMxqnqx+3hxLOxxLSwqLPDxsTEsde7p5e9vMzFrqettLqtv8XH28bJzs7W3921xcO5rMa9zbamwsHF0cbL2cr7xsDH0tLg5sm/zce0paWRsLu8vcrd47astcbDu7m+trWqx9XBgMW3tMCp2MXBzZiov7jFwM220djeztPb99/g89/cuba1z83Pv7nS1sKxmZObmJ6ihaWrr6inr6muyaOknbu1srOlrJ2opKSvpKGcoLO5vbrY3tXs49a4wa6xsq/BycK6w7rBubbX48rW4tnO0eC1oa2xsaWtqLOopqOfrKKtqbOrgKuqr66qqay0rpuqoqGgoauknKObnbavt7CXn7W2zq+nqbm7pbK3sam8rqWzraOoq8eznaCVlJSirZeWn56nqaGgsKusvbiwwr/ByLrU0b2nlJuUj5eRpbi5tK+qq7KwprG1qLKys7e5qb3IyrfU6sa3vuvWu626vcuzsKy8v6rkgKq/1uGC7tS/r8Oq3tSvvNG5xNfAtLzf7vjw84Ldp6Sym7rm39nR3cS3uLW3rKSnn6q9v7OwsrfCrrC7wL6pp6m3vbTGz726zd/S5Ong1NHl7ebUyse7yLyvwL+sq6KluLiot62ruLCmtN37yrbJv8jDx9zkvMrW07i3tcS7urO6gMegmpagqJqNlJGTkZygoJyWna6dmI6ZlqifnK7T2LqtqqalutnUvqu2trCpv6+2tcO6v7q+qr24yv3awePww9TU09jc0Nre18C2sLWxwLe5ubqlopafvKCwyLqtuqWptLq+vsW6u6auwP3nz83iw8zRyMa/xNLP/YSE0L7m1tP6gObw1cHB1sy8s6yuopytr7Gxt7y8r7Cltr28w7+yq8nv2ODKxcS7tce/1NaAz8TAycDQ3NTp79u11dnjm+fN1/Pevraxraqwt7GmlJWjjqWxrri/v7zCzOHH39vK1/Hw7tXL1eqPhr7IyLrEvMfFx7/HxMjO0cvAvcfCuKettq20gIyAdnJzdH2DiY2WlZF8dXN1fHiAgYmIuJeNhnyAen9sbXNTaHiJgoWEmoGGgG5pc3Z5SVJKR4WBe3Bmam1/a45zbGJ3gHWLf3lwdXRucX59enaOVViZj4F3gIeJiYxbTHBrdXFpbXd5eoBRnpGAeYRzfYN0TmJyd392ZW5gXl9dgGh/eIWTjUmLkE55dXRzbIZXl36Hp2hZaWptZ2hlUo6LjYVUknp2c5J6dmtwbGB0c3BtX2FdZVZle31rg5OAd3F3bl90cHOGhn58c3KSiIOIf4KDdWlwammchXNeWmhhZm+BZ4GDd4qSh3l7bmCKVV+MiXF+iXprbVFRg3yamaCggFiolJ6LbWNVaG9rZ2hqYVtfX2JkW292iY2IfHRta1aQd52SgXNwf3JvZ3CBfnNxXHxkUkNgXmZjUEtNVVlSYWlod2pvcGtwcW9QZWReUWNecF5Pb25teW9uc2OMX11ueoCJj3NodHZpXGBSa3Fvb3eFi2lkbHZya25wa2pbcndkgGZfYHdjjWxjbj1LXlllYGpVcHV9bHOBmYF7g3ZsU1lYcXB1bWSDiGhhT0xRUFRVOlddXFpaXlleeVNUUGplZGddY1VfWltoYVxTVWJoY1tqcG1/em9cYlJWWlxpb2Rhb2loXVdzeGZxfXlwdIJgVFxhXFpsXGFYWlNNUEdQU11XgFNUamxmZ3B1dmdxX2FjWVtUU1lMTmNcaWVMV2lpg2ZcXGlsXWppZ2BtXlJgX1pdXW9jWV1SVVljdWJiZl9hZl9cbGZjbm5nd3V0emqFi3NlUVFFRExFU15gXV5hYWVgWWZjV15dYmZrXW10c2WFnHdsdZeJcWVweYd0cXJ9fmicgGZ3jZRWmYh5boBsnZNxdYZ0fo16cHmXprGlpVeMYWNrVmuCfHxvhW9lYWdpYFhXVF1pcWlpaGp4YWt3c3ZlYmh2fnR4hXZ2gpKCjI6GfnaEh35vbGtlcmZfamtcWlZbZ25lcW1ob2xrd5+3inWGfHt5d4uTanmGg29vb3t4eHB4gIpnYGJsb2FVWlZXVF9oZmFaXm5kXlRZVWRXWGiKkHJqaGRjc5iSfmtvcWxogHV3cXVqcHJ6ZHNsdpuAaIaKZHh2a3R8bnR9f2dmZnR7iX18fIZvZFphe19uhnVsfW1xcG5zb3Z5gHFveKiZiIuYgYqNh4uFiIuAo1hainiRgICfgI+WgnN1iYR6eHJ7cW98eXh8gIWHeXhsfoOEioJ1boahjpF+foJ7dYV7iYxWhnhwfXqDkYaPjn1ddXZ6VH1vc45/ZmNgXl9lbWdiVlhgT2NuaXF0b2xwd4pxgoN1fpqcm4mBh5JfV252eW11b3RyeXV9eXiAf3lyb3tzaVtja2dxgI+Cd3JtcHp+hYmQkIl0bWdpbmlzdICBs5GIhHqCenxjZmtJZHmIfoCBlYKLhW9rdnp7SlNLRX50bmRcZW2Jc5p6cWN7hXeNfHNobWliYnBydHCLUlKOg3Noc3p7fIFYS2todm5kaHF0d31NnZOCdn5vfIF1V2dzd3x1ZXJqbG5tgHOIfoSMh0WDiEpzbmppW3FPkHmBnGNWaXJ2b25oUI+RkIZNiX14dJB8cmhxbWBzdGtsXmBibVxngYJ1h5N6cWpzbFttaXGEgHp+cnOWhoaQgIaFd2x8e3qol39ybndtcnKAYoWIfo2WiHR3b2CEUFmJhXJ+in1xdVtajoWkoaupgF61n6GSeXFkeX93c36AeXJ5dXZ5b4KLnaCZjIR8d16XfJ+QgHVyfnVuZ259fHR0ZIRxZlp8fomDbmhqcndren99in2Eh4GFiIdqgYF6b355indph4WBh3l0dmSHYmFvdHqFiXJse4B8cXRkfICAgoiWn352fIqGgYCCe3hrhIl5gHlwcINwl356gk5ecm56c3xkfYCEdHiFmYB8hnx1XF9fc3F3b2eDiXJpW11naG1tVXN7d3JzdnF2imdlX3Vyc3Zwd2dsaGp3cGxiZXN1cm2EjouemYx4fnB1d3WCh356hoCBd3GLlIONlpOKjpp3anN1dGt2b3dvbmpmb2draW9ngF9da3JvdHuDiXuMenqAeXpxbXJqbHx0e3lmcIN/k3hwbXd/cHh2dnN9b2Z3c2xwboBvYmVbXmFufm1vdXR4fXZyf3h0f3xzgHx6f3CHiXRlWFtXWGJdbnp6dXh6eHl1bHh2ZG9qbGpoWmp1emqHmXNqcZiLdGdxdoJxbGl1c12QgFttf4dMhnVqX3VgjYVkaHlfa3lhUlt6ipWPk1GBV11pWW+MhYR5iHdubHB1bmlpZGhxd3BxcnR5aXF9e31pZGZzenB1gXJwe4p9io6GfnqKjYl7eHdxfXFqeHtsaGBkcXZqdW5qbmljb5OlfGl5cHJtbX6DZHODgXBwcH18fHWAgI5waGx7gHRqcm1ubXV8enVrb31xaGBpZ3RnZnecnHxxb25wgKCYgnF3dW1meW5zbXVscHBzYHBsep+AaIOKaXx6dHyBdnuBf2ZiY2twgXp7d3praF5ne2JrfXVteWlrdHNzcnd0dWVnbpuQfn+LcHZ3bW5nbnNrjk1NdGaJe32cgIiMdmdqfnhraWNsY2Bwbmxtcnl4aWthb3FwdW9gVnKLdXhpaW5qZ3lwhIhXg3p4hH2FjYaRkXpaeHp8UYl9g56PcmxoZ2hyfnhyY2JuVmp0bnh8d3J0d4lufn1scoqMiXpxdIFOQmNscGdtaG5udXF5d3Z/f3t2cX11alxkbmhvsnqEe516gnuJeoJ7inoBe5t6BHt6enuGegF7jHoBe4R6AXvLeoJ7kHoBe/96/3r/eoZ6AXuWegF7/3rdeoJ7sXoBe496AXuoeoJ7mnoCAgQAgMG/vLO+vMbZ5tzS4vXYxc7W0c/Mw8G+0N/jx8CvvMHK5MjHztHz+ImVjoLp1NXp2eb55Nfi4tzU19Ha2c/XzsbQw8S4pbirpqehs6y2xdLBt77J0OPf/pCN49re5tPx07y+yba2y9jZ6PbegezJ0dDu8ODPysTSy8C2rrOvwLuygL/N19bh39zb5fLh2PPbw83c7dzTzL+vmaCyt7m6na/Ezs3Bzejgzubl3cvEzsbhwLO0tquQ/5Ousbm0t9b75+DEt7qvxsvd3snSxMvJuL6Cj9fEtry7orLLuLSrusHfkZqYqYiyw6qfsbCpsbimoqmxp6i0rbCuxKKmzZyQkYiJgIeSs8W/ubSppaWywq+qq764t7vAxeWRgu/K1dS3tp62sMfFwKeVq6CjqaytrrK1sKu3wL+5trmptMPiyb6zvbG12Obe5eDUw7W1xquwrrKpoKadsLzdzdfi5M7q7vDy3cepx6Wmop2xwKytuaibtNryzr7Hxr7D69fG2uTK18TLgNjf5bDG2sTJ2dnPwbSsubOyvcHKv6++trrAu7m8q7C7rLjD1drT27ScnZqhrL+1raKzrae0trKnqebGsKagurW4yN3fxsG0s7axsbi9vbzE1M6+vsXKxr69xse8zra7wLrJ1cPfy7e6ztfJwsixvrOruLexrb62t6SvtrvLy66rgKu4tKKyp7S5pp+elJqvsa6rnaGqsbG+qqunk5Kfsru5vdC3wL6ysbq7v7u1tK2tr6Seo8aqqLOyprWxo6SknrKwvqeXlp+7vciyop2grZiVka2VmrDEwsC2pKipq7GirL60t7i4u7qpr8TCzpXJyb+ytLTJt7rT78vDucC8qbnBgMTN6+Ta5byvstjJvbfKvLy7orjt68HO3uLi4+HEurmuucq4vNLFy7XCvb67tKywsKmmoLSyxc7pzsuzw8u4rLLbxZSsxcK1rt7Gw8bT19bI3eHY1drNz8fFvsq5tsDAtbq8zrisnqCvsrLK1rjEzdTP0MDC0qWToqq3t7GkjaOXgJqWlJmWoaOxp56lnKuhm5KYipieraOspaOjq7LHqJSZl5aSsq6op5qjrKjTvMK9u66xsLq2orizts/V5de3yc3fws3Y0b7F1sO0pLKztMLM2Mm3ssjP1+WznqSwpLK0qqa4trS7wrajoq/BztzVz8Ozu7XGxczR2uPetba9wr3IgM3f4tDN3L+ztKugsaeyu77FtsTDxcDCwL62uMbAxc3KyNPQ1dvUzNe9tbjAxLK/x8Wzsra0oLLD8oXF4s/L4dDHxLeppLW2r6KXnMDVpaWkpa6zt7fV8NjQyM64sdfY4+7Oxt2A5dPFyLzCu8y5opypwMnMvsW8wLu4ure8t6/GgIF9eXF5eH+Pmo2FkqGIfIKLhX9/dnNufouIenVlcnJ5kXl4fHyQklFZU0d6ZmN0bXaGe3SAg311d3B1dHF8eHF7dHZ0ZXNtaWtod3B0eX1uZ3R5eYeDmFZUgXd5gHSJdmhlb2VmdYCEk5iJVZ6EioCPkYyAfXqKgHVsZm1qeHRogHJ9fXh8eHt8hpSEg5aLdnmJmY2NgnBmWV5maGRkUV5qaW1qdoWGcYCNjn91eXuOeG5ud2RQlFhocnBub4WajYhvZmlbaHqLk3t/dnFyamxMVYJ0cXBnYWZzc2leamiLX2VgdF1teGpmd3RrbW1gX2lvZWNrZ2tuhWdpimBWVU1NgElPZXVvamdbaWFhcmRcXG5kY2hsboVNUY1vdXlmaFVqaYF/gWxZbWNkaGpramlrY1phZ2ZbVVVJUV96ZFpVXFJWa3Jvc3VpXVFQYEZLUVVMQ0lAVGB1bXJ6fW17f4SBd29ae2FiXVpyfXBzfW5fdZOkgG9zdW91m4Z4iZR8h3h8gH6KjVlrfm95hYRyYlRNWVdaZm17blxjXWJrX1hXSlBhWV5kcoB/h2NPUVNWWW9nXVRgWVFeXlxTUolsXVdVaGRhdY2UenVoaWtiXV1bYl5jbWtjYWNkZF5bYGVjeFteXV5ydmV+a1tbbnhua25gcWRgbmNrc3dmZlheX1lmZVFSgFBbW1RmXmt5aWZpXV1maGNfUFNbYmZxW1pdR0dWYGNmbHxjaGZjZGRlamxnYVldY1tSU3BgXm9wZnRwY2dqYGxsdmJSVFxzcYBuXFlZY1ZTUGhQUWNwa3BlVltbXmRZZ3FkZmZrcGxeX3B2gmp2d3BmbHCCbnKKp3x7cnh0a3d/gIGHoZmNmnRscpeIfHeBe3h0XmmTjW92jpOaoJt8cWZcZXRkZ3xydGZvbGhmX1heXmFjWmdecXyXfHNjaHNpYGaOe1FjdHhuaJJ5dXN8fXpvgHpydnluc2VpZHFnYmlqZGVtfWhiVl9vc3WJjXR4foF+eW5zg15QYGh2enZsW3JpgHBsZGZcYWBrZF9lXm5mYFxjU1tcZV1lYl5cYGeAaFZXVVZVb2hpaV1pbmyMdHVxbmlva3JxYGtmZ36BintgbWp8YWl4dmdod21qXW5vbneJmIR6bomSmah1Y2dzaW1vbWltcG1vfXltbHJ9h5WVk4t4fXqLjpKSkZKMcHV7gHl4gHqLkIWDj3tvcm5pgHeDjZKTfYODioaIgoB2eISCgoqJiJCHi42Hgo58eXuCf2h0enViZGtoVF5rkEpifXRvhHZrb2FZV2RmY1tUW3mMZGVmYmtpaWt+loJ7dHliYXyAi5J2dIRQh35ycW10cH9yYFpnfIaHen94fHZxc3F4dHCCgHZ1b2RraXSEj4F3hpZ+cniBenR2bWtoe4yGd3Ffb3B0lXBtc3SMkVBaVkp+b25+dX6OfXB6e3hybmhwcGx3dXB6cnNvYnVuamtkcmpzd3xnW2hvcIKCmlhUd21xe26JcV9ea11cbXd5h4+BUpqAg3yTloh+eniDeXJsbXJzgH92gH2BgHd4dXV2fIp7eIdyX2l+j4WGgXFsYGRucGdiUWNvdnltd4eDb4KNjIJ7fH+Wd250e2xan1htd3txboGUh4RvZWhYaHSEind8cHBtaW1NVYN4eXZ5bHOBeXJqdnCMYF5bYVpygnFvfHdwfnRlaG5yZmVtbG5whGxtimJZVk9UgFJZcIJ8dXFpd3F2g3R1doV8enx+fpZXWp6Bg4VydGBzcomHhHBebmdpbG5ydXZ8eHF6goB5dXludoSbg3ZveGxtgYWAhYyGeXBxgGdtc3dtY2ddcXuNgICBgm9/g4aHfXJZdV1gYGN6iH2Gj35xhqKzkX6Ag36GqpWHmqCIkIOFgIuZn2p9kYGJl5aIfG5kcW1ten+IeGRtamxvZ2JiVltpYGZreYSAh2hbYGhwdIqGfHJ+eXB9fnptaJR9bmhmeHV2jJ+jioV8fH1zbXBwdXR7h4J5en2ChH16fH54jXV6enuNkX+Whnd5jJWNhIV5hndwf3h2eYd8fGx0eHaCf2lkgGBmZl5xaXiHfYGCdHN7goGCcW92fYKJdHR3ZGRye359gpN1eHl0c3N4f3tzcnB2fG9jYn9ubX5+dISDdHV4cX+BjXdlZGl+e4ZyYV1dZVhZWXZfZXiLh4p/bnRzcnZoc35wcGxscG1fYW5weV9xc2tkaXGEcHGHnXt1bG9qXWpwgHZ9k42Ai2piaIt9cm12dG9rV2SJgWJqfoGGjY92bWhib35tcYd9f3B7dXNya2RpbGppXmtjc3+ahIN1fol4a2+XgFNkfXltZpJ4dHiChoN4ioiChIqCg3d5doV7d3x5cXN5h29mVlpobG6AhGxwd3l3cmZreVZJWWRwc25kVGplgG9ram1ocnKAeXJ1bn15dG92ZGprdWxzcW9tcHWLdGJnZGRie3Rwb11naWODb3RxcWlta3JxY3FoZnuAin1kc3GAZW58e2treW1mWWhrbnaDkYV6cYSLkJVuYGVsYWlvbGlycGtvenJkZW97gIuLh3ZkZmFucHJ4e311V1pga2drgG+Dh3h2g2teYV1Zcmp0e4KGcnx6gXV3c3Rta3dydH16eIB1eXt5doRvbXJ7fWd1gHtmZm9pU1xtm1JukYiCloh9f3NsaXl4cmhgZIedamloY25paWl8lnx1bHBWUW50g4ttaX1MfXdtbWlwbHxuWVBfdX6Cc3lyc2xmaWluamV5p3qEe6x6gnuSegF7yXoBeZl6gnuOeoV7snqCe/96/3rWegF7/3r/es56AXuqegF7m3oCAgQAgLGurrfHwLvAycjK4eDW2Oz30sa7xsu8wM/J6v2HloDg19XQy9HnjpWYs+XMys3X1r/q8uzp29fZ793s8s3iidemq8HIqqWem6fCycewu8DN5+HDxOPNxenY49PPwsXIxsrHz93CyuTT6+n1/sHF0s7Rx8/Qxs3PxN7OvrO1r7bGgMzPxL7Dub7G6uPHvuzkzffkx8Wzt622wsK3w7ST5OLUzN3T3+Lc7uTDsq7GxOyBvZqepZ+inrWkqrK3uMDd19DMxczIy8i+tbnBscXGvsbRsqOwoq+jmrrEy7WcwNXQ1uXUxMOmqKOqn5aTlYiKnpWat6estquuopigk4mVmpyZgLGqt67DwLjN6J+2ws74zLi7xNjq94eCgt7lzMvKrKijmq2QmoqdtdWzw7ynqabCrKeon6mmmqKRi6jV5sfV2djPy9jz8ePdx7i6zNfW2dHt4Me50t7ZxsbL4uvKzuLu/MLb3bWnr7qnrrW2xK2z1t3DuMDEvLi1uba5vLbbzNPJgOuH8ObQxMLCy7i/r7urvsO0x8bAt72srMK3urC5ysHFtbi8yNDiyLq+r6DTuciytaysrbOtoKqywvDeyq65urTK7t/Tyrq6wLy7u7/Axr6zwuPrysjDx8jPx7q0wsjZw+ji1dDAxL+4tLq8uKi1sLO6tK2rpqGkt6m2rMnTx7bGgMXCuLW2np2dppydpJ2enJ2lmqCorqOmlKShoaSwsrfO5tLKvKytsa+4qqujqKe0vLCfk6qvq6+5rK+hm6Spq6atpbO3laSotKi3q5mN0KyYk6iJh7q4v62spqymprG6r7ezr7O/rb23tLzVwOvIxc/Mx7zJ2fjozLKhnLG/xcPCgMvW3dbbwMG4wtHk07uxxtPT4cbF6+Dz/veA7d/U3MHMu7+quMrSur6/z8a7ta2tvMvDsbqouaqypq6xvvGV48KwvbbDwL3L4dzOurrO08zAydLG1NTNwcy+ws25y9PDx8W3tKeunKytubvDytjAy7a5ubi9vsXKtpLCwp6xoq2mgLfMvLi7w8m6tKWqo5uUj5ygiIiVmaGSqbCiqbCqsJ+an6Wlr7PL0sfExs2woJa0q7a6qquvxLysq7y42MHJv8nQ0MbA2dezs7i7wLaytbTJvrm9vb7h28vAsLSxvLanoqSUr53EuaqSqpuVmLm6wM/Fzquvv8LV6buzvMHKu8DDgKuzv8vQurSswcCsrrOxu8G/q6ilhcu9v76yz9+5u8LR1Mq7rrPOw72vyL/GtczV3IHCwLSywNzP3NzE0tb71dHjz8a2pbC3vqShsdLl3snJvLTAzNXX3eDrw8fBy9jZ9tfV1eHW1dbLu8PT0sK+2dfP09Plxr2yxNDc3tbN2cnEgHpycniFgHd7gn6AiYqDh5aagHJtd3tudn94kZhVXE+Ffn96dYCLWWBgcINuaWpzdmCGiIeGfoKFk4eVl3eKWIBdYnR8ZWVjX2N5d3Bkc3mCk4t0dop5cIt/h3h0ampqaWhqdoZ1gpWElJqnrIF8gnuAfISIfIKFeIF0b2pua3B4gHR1cG9yY2FsjpF2cJqKc5qMfn5paGRrcHFmaGpZg3xxcoV6hoh6j492ZmJwcY5QclpfYF9jXGpiYGxscG6CgXt2eHNrd21nYF9rXHF3dnFyYFpraF1fX19/gW9Vbnd5f4J6dHFgXGJmZFZTUUZIWF9fd2RkbWZvZl5iWE9YXl1ZgGdeZ15wb2p8nV9qbnmpg2xrcIGTmFdTVIWJdnl5ZGNkX29YYVVmeZt6h4BtbWV4XFRRR09ORE49OVFze11ka2ZgW2eBi351XFRVZXR0dG2Fd2BTa3h1YmJogYZvcHyIjV90eWNicH9wcnt8iXN1kZqEdnx7cnBwbWtrammHen93gJtioZd+dHFxb2JpWmlba3lif4JwdXJhXGZZXldaZ11jVl5ham6GdGVmXFKBZ3NeXlBTV1tWTFZZaJOBaFViaGB1moZ8b19iYmFjXmViaGBWY4OHZl5bXWJraF5aaXB2YYKAeHVnY1xbXWlxbFlfYGZna2VaZWdaalxpXG9vYE9ggGNfVFlrWVVWYFpbYVpdVVFWUllaXlhcUFhTVFRbW2Fvi3RtXlBMUVlhVVxUW1hhamZcT11eWl5wZGVgXGJnaWRmXW9wUl9ia2FqYVVOm3VdUmVHRGxwcVtYVV9aWmRuZGhiYGVwX2thXmmCbY9vbXZybWVzhqKdhnBeWm57fnd4gHqBjYKMcXRxdYSVi3BneoKDiG9thYGQmp9Yoo6DiGt2amxeZ3N6ZWNjcG1maWVlc35wYmhXZlZhW2Rka5RhkXdpeXaEfoKPpqONeXaGioJzfX9ugHtzaG5gY2pbbXBocXNucWNsVmJicXJ1gId2hnV6fXh9f4SKel6LkG1+bXBugHyMgHyAh4p8d2draGJdWGNoU1BYWVxQYmthY2VgY1xbXmJebHOKkYmFi4hpWVJuZG5yamlmd3FlYXNsiW51ZG1zd3Jqf35ka25tcGhja2h7e3J3fHqgopWHd311dnJlYmhfcFuAdmlXcGpnYnp8fIyMmXR5iYyepn1xc3h+dHp/gGltb31+cm9tfH1vc318houGdnh0YIZ2d3VphpNvcHqFh312cHKFfnVsgXd/coOMjlZ2cmNbZ31zfH1reXmUd3KCdHFmWGNqdWBfaoSPjX99c2hseHqAgYOGaWlncoCFmIF/g4qGh4l/dn+Mhn19lJGLj5Gbg35zhI2UmZOOmIuHgGJeX2l5c25zeXZ3g4d+hZGWeWxmdHhrc3p0jZVRWUp5cm9pZHOCUVtZa4Jva252e2GLkYiBcHFyf3SEjGyAUXVYYn2Ga2llYGeCf3dgbXB6lIxzdo12bYl7hXVvZGVoZ2RjbXxicYVzhYaRmHN0gXp/fYSEeX15bXtwaWVxdXl/gHl3cXBvXl5oiINkV4J7ZId9b3Nhamxzen5sal9aioeBe4R6gYV4jIx2aGR0e5dTeF9iamtsYHNqbHFzdnSDf3p2dXFobWReXFlhWmhxc29vXVtya25uaW+GhnRedH95fX5ycHVnam92bWZobVpUaWNmdmRmb2huZV5jWVFcX2BhgHZtd3CBf3eEnW56gomvkX97gIuaoVtYWpKZhomPdnRxbXxlbWBvg6WFkYp2d3KHcm5vaG9tZG1dWnOYm3t+f3l0cYCbp5mRenJ1hpWVlY6nnIFzh4+Jd3Z4iI1yc4GPl2R8gmhmc4J0eYGDk4GGoqmWhYiGgHt5eX17eHiXjI6CgJVZpJ6LhoWCg3N7bnxwgIx7i5GFf35taHNpaV9kc21xY2lsdHSJeG5ybmqZg5CBgnl7enp2a3V2g6iVf2t1e3WJrJuRh3R0eHl8fH99gHZteZmggoF8fHyCf3dygImRe5uYkI2CgXt6f4WDfm9zd3d7eXZvcXFvf3Z/dYqKemh2gHZvYWRzaGpsdW1zenN1a2pyb3R0dXN3anFtcHZ/fX+LoIiCcF5aY2tzY2lna2lzf3xxZXVxbXKBdndua3Z6d3F4cYCDZGtsdmp0aVtSjXdiXXBYWoaHh3Nxb3ZtanJ8cXVsZmZsWmVaV2B6a45xbHNubWZxhJ6ag2taVWZzdHFzgHd+h3yHbW5sdIGSim9nen+AgmtnfXmIjpVRlouGjnSBc3VncX6Fb25ufnhxcm1teIByYmdYaF9sanNxeZ9nlndpd295dXaCmZaAcXCBiYF1foFzhYR9dHtucH1sgoh3fn53eWtxXWpncnN3fINve2hucW9zdXyAblR/hGJwY2djgHOIgYCBio6CfXR9eHFtaXV5Y15naW1hc3pzdXpydm1oam9seXqKiH16foFnW1ZtaHB0bG9reXRpZ3NviXJ4a3R3e3ZsfH9laW1tcWlhZGR4enp6eXaRjoZ8bnRscG5hY2lba1Z5bmVTamRgXnVzcH58h19jbWt5hGJYW2BoX2VrgFRdYnJzYl1abm5fYmtseoN8a2ZjWXxsb2xdfY5qaHKBhHlvamp9c2xjd254aXyJjld1c2Zhb42Di4x5hoSehX+SiYZ6anV8h25repejnIWBdWpueXh6enp7W1hVYnF2j3h2eYJ6eoB4a3SDfW9vi4Z9fX2Ja2FVZ3J4fnl0fnVxnHqDe4d6hHuUegF73HqCe5F6AXvmeoN76XoBe/96/3qZegF7o3oBe/9613oBe5t6AXvPegICBACAqKytrrTAytLR4ejehffVyb++w8PGzsjZ28z249/9y6qturzP5MrNj4v2ysLP4uja2/n37ufg79ze3+fj1szv4+G8v7O+qrqB58StvLCxvNDg2cnIy+vDucPI28e+vcrPyMHR9ODp/96/yMbGydrHvbfFxtLnz87PxN34v7fDucGAxL60w8fFsbXp1MnF//PV0srZ19S9u8PB2s/C09/Tz9zw/NPX5pLo3cCzwMbQy7bEtpm1tc64p66uwru1zM3hx7S1xr6/zb/IxsOup6q9wsW8sbSwqpar1ai7raSwxMfI1t7prby3r7Cmm5qWk5ifk5ucnqOkrculsamgmKGqoJeArb2vq6OltautrbjCwcKyubbT1cm8q5atqa2hjoeFoJCVkY+Mk52lrZGAkKeotbKstKWorLittJ6e6PLw8M7ArLva0NfJzb6/vMvK0dzaytDk3NLZysvi1c3D0s3A6IrQr+Du3NbpxcK7tbSpsru2pbKzt+K3pbSSrLHCs77Fxr6AvL/WyMjGvrjSt6y4yL2zyL7XxaW4q56twMTHrb+9u7zH2b67wsjKx8+Hhd6ZoaianKK0tbK5zda/tLPJyMnb7ObbtbGgq6KqprPAysbGyLi6ub/E0rKkuefWwLK5xL/NyMe8v8LHt8mxtL6wuL+7tq+2qqe2sKO3sbStucPEw7uAuLW62LKeqcK2uaiPnKiutq2+s5+fpqesnpWMo7CtorXR197j7tO9xMvWr52io5eup5mXj6Gkm6i1qqqgq6acm7W+sL6yqKO7sqydn5a9oaGerJq+sqWjl6Gpv6+rqbLM4ce4wMG6vsO6i6/7yNbKy83Dub23yebP34rWp526t+mAs7vBvbvW4tvd1r69tsisu7+5ucTItq28zMm0tODbyc7AurXF1cHAtLumqrOwvLm72LWsta+3q6m20Lmeo7XDxM2yoNe8wMq5v8TO19bOxsbB2s3W/NK7yda/wsatqLnJv7Wwr7G5xqusqbm8rq+rs7a9wMSuqaitsJiuuaynlqGAqp2Xqq3O0cfKrJq707SjpbKNpo6eo7uosrmtm7W3qrC9xcbKws/Qxtbx1tPQrqukpqaytrehv7ObpK6+usPFv6/F06y/r73EvbvGv93e0sbAtsbDvMe8y7e2xrGlpaeotruxtsvQtae8wbqfosW2tLe+paCys621rbW/z9K5wteAv8TJxcvJycq4tbrCy764tbiqt7m1n6izrcXOybvT5NTp6srHzcS3t7izrrO6yNzo0bnT08S7wcfLzrqxt8i3oa29uL3Aw8u1tbq9vbC9zsHBscfk6si+68jP29bh7uvw6OTWx8TW6dnZ4+ju2MXO3fTb597n4+Xc1sfM3dPdxcCAfn99fn+LjI+Ql5ePV6CKgHd1enZ2f3yKiXuWjI2WfmdrcnKAj3l0WVaQa2V2h4uFiKSklJWNmImIiY6Je3GMiYpycmZxYGxRkXhueG1udoiWkYSDgZp4aHJ0fmpjY3J5d3N8mIaVr5yAhoB6eYd/eHJ/foSVenV0b4WdcW14bGuAbm1ncnVtX2eRfHt2mY18fHuEf31tY29vfHFqdHxuboGOl3x7iF6RhnNibXN4dWZ1blVrb35uaGVpdnNpeHqPeWdfampkcmlpamZaWV9tbHF3bHRmW1xfgGxzbF5ecHVtcYeHV2hjY2hhVlZOTlNcXWVhXGNkc5NveHBnYGZzY1yAanRlYVtbaGNkY292dXRjaWiBgH1zZVRraGddUE9PZ1phX11ZYWZpa1dHU2VjaGNbY1JVWGJUV0Q+gIqDhGxfTlVwY2dhaFlaWWhlanh4aG56enB8bG+AcW1neHFsj1ZvVXuJfXeXgIGAenVtd4F/bXJxdZlwYWxRZ3B+aW5vdnKAdHKAd3Jvamt3XFlmenNqf4KHeWhsY1ZZaWVfSl5ZV1ZlemNlZWdqa3dSTndHT1dNSUxZWFNcb35jV1hubW99ioF1VU8+R0JNSVVibW5vbVhWVFxeak0/UoJzZVphbWNqZWVeYmJgWV9VYmxiY2NiY11jYFxkbWZuZmRdamhjXlITUFlfdFZMWGljaV9OVl9la15jZYRggF9YUUVRXVVHWW9ranV/aVZfbHtdUVRaUWhdTlBNXVlTW2RgZVlkY1VPY25lcmddWWtoZVpdWXddX2JuW3RqWlVNVlptXlRUWnCDb2dvcGZqa2FegZpldWhobmheYVtsgnB/WXxcW3JtlmNnbnxsgomGhIFxcGh1Y2xvZ2x0dmVfgGp6dWRkkIh4d2diX2ZzY1tgZVVdaGt4cXKJZlxlYWpjXWRyYkpSYWx2fmZajXF2gHaDgo2PiH58e3aIdX2geGNta2Fjal5YaHN0b2praG12ZmxwgIR8e3p6eHt9gWtoZGttWWl3dXNpcn1ybn5/lZGEh2xed41vZml0VGVPX2J0gGNka2FXbHBgZ3OAfn5/jZKGmaOUkYhpZmNmYWhwb158cVxgZHBocXh3ZnJ5WG5ibG9ra3VvioaEdm1ufH95iXmKhIGFcmVnZmp0enR6iYVxanuCfm1thXV2dX5ubnuAdoB5enp9f2x6jXt/gHt8cnJ5dHd9h42Aen6Ad31+fGxxaXNleXx1Z3WBd4qOdnh5dW1zeHNwcnV/kp+LeYqGdGpvcHJ4Y1dca2BTXGtrbWtudGRla3FyZHGCeHlicoeGa2OEa2xvbXSGgIeFg3ZpaHmGeX2IipCBcHqFn4iSjpiYmJWTjIudm6OOjYBfZGNmaHd9hIGJi4JUmYF2amlxbW93c4B/cI2BgIhsT1NdXmt+a2lTUY9sZHWKjX9/l5eJhHmBdHR1fHl2dJOQk3h3bXtqelqremZzaGp2jJePfnx8l3ZkbnB+ZFpbbXNvZmyLdYKVgGZwb2xre3RtaXh2e4tsamhfdo1lYXJqaoBucGhvbmtWVXxhYl6GdWFla3h8hG5ofH2Ie3R+gnt7hIuSd3iCWo2HcF9wgIB5bYB1X3d2hHlscGx8e3N9fo15aGBrY11kXWBaXUxNVmRianJtdm1sZWqKbnVrZGBvdGxxeoJhfHp9fnZweW9panZrc2pnb252jW94cWhia3ZpZ4B6iHp6dXR/eXp4gIaFhHh+e5OSjYZ2ZHt5em9hX192Z2xoZWBna290YlNjdXV8eXR8b3N4gnZ6Y16coZeXhHprdIp/gXqBdHh2hoGGlZKFiZSUi5WEgI58dW58dnGXW3ZchJKHgZuEhIB9e3V/i4d2e3mBqH1qeF10fIx7goGHgoCAf46CgH56fIZvaneIf3eLipiIc39vXmV1bnBcbWpnaXaFa2twc3R1glVXk2Vwe3Bucn58dX6Qm4F4dYeEhZCfmI5xbWBpY21reIaLiYiHc3V0e3yEaFtum4t+cnWAd4ODg3x/gH93gHOEhnd4e3h3bXRwcHt9dYN8f3V/g3x0aIBlaW+CZFxqfXp8c2NxeHp+cnl9d3h2dnhvaWBwfnppdYeEhImQeGRod4htYWVpX3VtZWdkcnBlbnhxc2pycmtpen9zfXJoZHZybGFjYIJsb296aYZ9cW5lbnB7cGZma4CPc2hxcWZpaFtTZotjcGdocGtiZV9vgG57U3FTUWhlkYBiZGpwaYSMiYeFdnRufWZvdmtscHBhWmZ1c2hokYyAgHRxa3OAcGtrcmFqcm93cHKGaGFsaXNsaXODc1xjc3l+g21di3J0e291d3+Ignp0cW5+cHqhe2hxdGpweWxpd4GAeHNyb3N6Y2dqeXxxcm9wcXZ5fGVfW2BfTlxoY2BYY4BrY2N2eZKUiYtyZYKYenJ5hGV4ZXV1h3N3gHZpfH5xeIGJhYWAi46AkJmLiX9paGdqZ292eGZ+cl9obnlyen19bHR5XG9ha25qanVrg4OCeHJte3tyfXWBdXF4bGZkYmVyfXVzfnpjX3d+dmVogG9tZmlWU2FlXWRbXmNrbVdjfYBpa21qbmhrcmdobXeBcmxucGlzdG1bY2lbdHlwYnWEeZCTeXuAe3BydGxnam92iZWBbYSEcWl1fYKLd2xvfHFganl6fHt9hnNze4CBcHWIfHtjcomGZ1d8YWNlYGl3dXx6fWteYHKEeX2Hio55ZW13jnN5dHp3d3FwaGh9f4V1c4x6AXuaeoJ7nnoBe916AXv/erl6AXvKeoJ7/3q/eoJ7jnoBe4l6AXv/ev96/noCAgQAgMW9wbyxxMzD0tDa5tLWt8L6/oSZ9dbd++7Z1+ri1sTMzt7w5d3g293L4vDh4unp1Njh4+j38//v8Zfwx9326ejNvqvBycnH4L2zw6y+yOLaqZ2xvb7AyLXFxcPCvbzN87y4vP/z4MXrt9HM3sjNxNrozuLz5N/Fybe65cG4zdTOgOzJv8DR9+2/zd7Q4/3Sy+Lp69Di5cXU29XR2c3V09nt2t3f7M3L7MvGwc7svNqprOO/0smxvrm0q5ylv4SdmpyhxbWytrXFv8C6tbLAxNbAvaiks5nCyrattaq7zcjB1ebO16quveCtspaUm52ol5Sijv6KmqDQu+O/ra+4yrm0gK3Ix62rsLT9xsrCvKyvrq22tq62pJWOmJ2TjPyCk7armaaZpqKRlsvHrqm2t9jt9YPhzcvzgta0naS95/7Zvc3Uu8LNys7L0LrAtruyv8nMztre69Xd57itrbnIudLLw8fPya2Vyd7JycCatfTZ572+zLq6vb63u8zdzLmtrb/hgNS1u8S51svItL3Kubu2wci6kpaVkKqzssTKss/HxMvGy8fDwbaxxMvEy7OooqzBvr3Rupuhrq23s93eivDl3/Tvy8u/xKeruLyrp8y5tMO4sLa91OPg67jGsrC1usC/usDJytLGzLPCwbOuuq23ybSysKensrWvpayoori2vsm1gLzKwLXJzMG4rKeysZ6htqyxqq+smKCWnJ2OpqeitbTLytPbzdzj5/bczqioprSUoKCboKObkZmksq2moJSbprOvv6vGy8Gwn6CtuLKvy66ur6ayu5mVo67dn7+lpML7iIzh59G/taGxwrXJ49/q0s60v9Hi4YPYybrhicLBtsPkgNbSgvnc5+3c39jxvabZ55edp625yNnG19u+vq2spKGsurW51sbUtrOhk5SirrW1ub6ttLSnssa/w77IxLm63OTc1M3Bw8CstK6qtLOxxcnE0d/l5dLByLW6wbC5nJysvsHEybS5pbeuvrC2o7C7v8DAxc/Fzs7Xz8eyqrGplaKhgMSrs7jKr8Th3M6yuLKzrJyyzuK7zcLCwczfyMbAxLmttNq5pKa5usHOybi7usa/vbWyqre0rrSupJmasbu4webmsKWgx8a9y9bNyOHNvLfCv7u8wMjJsq7StrKqpqmTr7uuqLmxtOf8hN65uL6/uMS5vbO+0ti9tLe1s6+zt8vhgMzM0snNx7i6w7S+vbm5s7vEwMPBurfBvbOzy8O8x83f06TQz9DA182ysrWss7XBsrS9xdK9xr7IwLvJzMy/qbK2wb3Bxsa5wreqsL/Bt6/EzuXYydrj7Yne4sbJ4PHw7dra8Pn30ZrX0M/T3NLR2cO3ysy9zs3Qx7+wu82+ysLHgIuDiX51hYZ9iYqMlYGBYXCeolRjlnyDnpiKiZeSiXZ+gIydk4qMiIh+kZaHh5CRgIiOkZefmKKWmV6UcIGRio54bmZ1e36ClHxwfG19gqCXcGNxd3p5emd2dG1wcG58mGljbaCZioGhdIF+kIOKgZadfIiRiol0dmhvj3ZreH13gJN4cG18n5hseoB1h6KBfouHhHeBg2t2d3NweGtxd36JfH+BkHp2j4F1bnuLZ3leZZB0f3lpdm5sXV1ibU9TVFdOaGpaWV9kYmVeYmBucYN6eWFdXFZ3bW5jbWRpbnFtboV3cVNTZ4RjYUpKTFNcVVplUY5VYGuPgqJ8b3B6hHh0gGh9dmNhY2SXcG9oalxeXl5oZ2JpW1BJVFlSSYJGWnxxZHNoc25fXYyKa2FpZn+OmlWCb2yVUIBjTktefIloUGJtV1pgXmNgaFlfXV5VY3R7doCGkH6GlGlgYGl5aHp4bXB4bltKdIt2fn1bdaePlndwhnh2dXdxcHyGfG9kZGyTgIxvbnBneHJxaGtuZ21oc3xyV1dQUl1ZX2tyWGdfXWRbY2FcW1pXYmpkb1tTTFVjZGd1X0RFUVJYVHd5VIR8eouEaGVZWUJIV1tNSm1YU2VcUVNZanZ6hVptYF5gY2hiXGBpZ25hYlBjXFpWYlhca1haWlZcYmVmYWRbVGZpY2tagF5mZFZlaWhoXFlfYlhbaV9kYWJgVWRSVFREX1xTWFZmY2VsWmVxe454b1VZXmlLVFZTWFhUU1ZZZmJZUklQVmJgbFtucmteUVVgbmpmf2poZ1trflZQV1yDTWNMTmGTUlJ3emxfXE1YZ1prenyHbWpWW2h6fVCAdGWBUmlnXmmDgHJwSJB5g4h5fHmPZVWGjlFVYWlseoZ3hYBmaVxWVFJZY11eeWt2Z2VaUU5YZW1wc3ZiaWxgZm5mZWNnamJohJGNhHpwbnFha2tma2tmdXl1gY2Oj4BwdWhrdGZwWVlib3N7f3B0ZnZtfHF7anJ2e397fIh3f4SJhYJ2cHp9bXt8gJ6ChIWOeIORjoBkaWpua15wjJ96jH99eYSSfXNsbmVjbY90Z2h7goOLh3p/f4h7dWppY3N6dnhvYlVTaHd0fpqTZ2FXd3FmcIB1coZ6bGFvb2xyeX1+dXOFdXRpZ2xbd392cnxydJWmWJB2dHuDf4Z5fHB5jpaCfoJ+fXNubn6WgIOFin59d2dlaWVzfn2BfICJhpGNhHx/e3R3hndtdHN8cEpiaHBsgn5ra3FpcHWFeHZ+hY98gnmCenV5fHlrXF9hbWppb3FpdG1iZ3B1amR6epOAc4CIiVN6c11gcoKFhnVvhY6Lblp4c3J3gnx6hHJrfoN4hoqIhnt1e4p/i4aKgHpzeG9idn11g4OFjnt6WGWWm1JdjHJ4k4yAfouDd2VsbHmLgHZ5eoB2iZCEg4uMe4CGgoWMipOHjVeIZH2RkZSAdml6f4KEmH5vemh6gJmPYVVmb3Bxc1xra2NnaGR0kVxTW4iEb2OFXWxrgnF6c4ePbn6Lg4JlZVlhhWlkc3h3gJV3amV1k31TX2daaH1hZXR4fXOBj3uHhoSDhXuEfnyGdn18iXR1iXpvcIGOZ4NjaqGAhYJ0fHtwZWNpcUhSVFlQbGVWUlVcUlhSVVpiZXV4dWRfb2KBdW5kZGJqbW9qbn1vdWNtgqR6fmlxbXJ5bmx0YK9lcXOSgaOBcXN8iX5+gHiRjX18f4O8j5GGh3h7enqCfnl9cGNcZWtkXKNWZoJ2aHRrdXJkZpqSe3N+fZSkr12ci4apWZR2ZGh7mKSHbHuEcnZ/enx2fGxycXRseoePi5SWmYWLlW5oZnF/cIKAeH6EemVRe5F6gYBie7CZo396k4B+fYB8gY+ZjYF3eISigJV6e311hH9/dXR8c3l0e4F8X19bWmlpZ3Z+ZHZzcHdudHBsaWRhc3hxgHRva3aHh4iXg2Zpdnd8dJKVXZyWmKajiYl/gm50gIN0cJF8dYZ9cHJ3ipeXn3KBdHJ2fIKBfYGMioyDhHR/gXx9fnFyfmpta2txfYB9dnpxbIB9fYRqgG51cmRwcnJ2cG51dmZreXJ4d3l2a3NjamtbdHVudXSFgH6DcnuBh5SCd2BmandcZmdkZ2pqZmpwenBoYltkbHh2hGx7fXZqXV9seHVzjHp6e3KBi2dibHOZZX1jY3OdWFiChXRlX0xZaVxvgX2FbmpaXm6Ag1GBcmR/TmBeVWKCgHRxSY57g4mAhIOWcF+SmVVYZGtvf4x7iodscGRhXVpha2htiHyHdHFjWFdjbHJydXpqcnRpb3lzdnV8em9yjJONhHlvcHBgZ2JeZGVkdHZwfIaKi35xdWlqcmZwWl1ncXR8gHJ2aHdve3B4aHB1eXp1eIJyeHqAeXRlX2loWWZogIdscnqLdIOUkoJscXJ2c2d4lauImZGOiI+gjIiDhHt0epyAbm99g4OHgnV5e4l9eHJ0cn17d3x0aV9edIB8g5qSbWRZeXJlbHlubIN7cmtzc3Bucnd5bGd6a21kY2pXcnxwbHxva4ecUIZubnR3cnltbmFoeoJqZm9oZV5cXXOEgGtveW5ta11fZV9sc3FxbHF8dn95cm51cmtuhHlvd3iCdU1qdn13i4VtbHJlaW18b291eoRyeHN+enqCh4Z6bXF1g4SDhoV9h3prcH1+cWl/g5KAb4CGilR6cVhYcYCEhnJvho2FZkt0bm9xfXNweGBTZ2dZZ2tqZV1UX3FmdHJ5knqCe6V6AXv/egR6enp7tXoBeah6AXmUegF7hHoBe/96hnoBe/96pHqCe5R6AXuEegF7h3oBe/965noBe+56AXuOegF7mXoCAgQAgPPqhd+/ysra3tzf29XK2Njk9eTTyezLydX43NLnzsq8sKCvsIr02t7gvtvz7fLd5sHL3fn49veGhOn29urezs3Csq/Fvs+/vb/H1MXAt7/BwM24vMjMzLe1tLq2yMzfkOiw2/3gz9T/geXXzMHW4Nfk9NTAwc/P3cfTvsfDwdPDgNru5+jc3YCg4ujj4ObL1PXw5crTwsO2tq681dLZ1ujf8NbQ4tDBvee6wcDdi9iwr7zCw7iqoOjjwJS4v/LSnLKsqrrTytXd3NPMs77I0sC9x87Rt77O0c7VvMayxcHOvvTqg+PVrciknaWfoJqgloyanp6UoJGOkKfPq7GjmpaYgJWjpJyosrXJubK5wrW+ttKBzrKuop+TqJqEmJiJpcyqiJCbp5efmK+wp+CP2ePUutrosMr428Tg28uqu8j37PHJ0NrS54TSwtLBu6appbG1qrGyqLfgwqKyubm74vnuzdPUus3V4dS/vrewtLnf8fiN29Krqq3Brcy2uLOutbC6gLvEwq+/ucG0scXW77K8qLv2sZi3vrGwv73DwcvR0M7HxcXR5dfU7LXBv6ynl621o52xpZ+4xLzAxtCyytPS0cnKsLLIyLq9zbXBz9KxxcfGwc25trK61N29qauosKS2wMG3vcbUur62taujtL2zuqu6s7mtprCsq6meo7G4qrO2gKy/y7nAyr7EuLG+sbKssb26tq2vxLuYoaqjr6Stsru+xcfLzMfGzdq3sLC8mJyepJmOsaaXm6iWnaSmp6+pu728s7W3vb/G4rvGzr2iq66ivLeiprenmqe8nsLl7+jo8On9gfjr4OLav8zN98zR3t3W1O3N1uLb2uHY1cfTzNTBgMzUgunGzra6qrTYvrGvr7KnwbHBt764r6+5vbOvqLLDr6Sur7Gwq7GvnKGquMC8tKy7mrS/tbvYx7vL1dLBxM3T1d/WxbOhlp2rs77Pxb7Zy+Pc3b+sxcy4r7m1say8xcqosbSusrzIv7jGu7CuqcC/t7/PwcrCs7/R2rm5tbyngK2vwsrQzL3R1NDIvsu1rKyhvrW3sbK1trPDyci/x8O7sqC0q7LEsqissra/y7G2vse1qLK5o6Oto6mhkKKgnZ/DwsrKu73AuNW0w8O3taSzvMS7v7XGpratvqmqtJqetbSstKmpuNP41trz99fk7/qG5YHTt7u1wNXJvrGtrLnGgMra4MrBzePVxM3jx763t7mnqLLLxaW6v77H6IfJrLSypqK5zsfV68+7zcjIrZi5x768vLu/ys/Cv8jCxs29v7zBuLSxr7e7wbTAurm/xLquvrvM0+j/6Ma3xNbP4NbIxcbJzdTAub6zwsjBw8vP0sbG1Nns8O35gNzQ8/Ph6ePmgKGcXJB0gX+RjpGRi4V7iIaRnpKBe5V7eoSejYeagHp2bGJpZVKSgoiMbYWamJyKkniAjJ6emZlQTo2VkY2Ie317cG+FfYZ4cnh/ioKCe3+AgYt0fISIindzcXhwenuKX49hhJ2KfHugWJyRh4OQlYSNnIJxdYKFj3yBbHJycoB3gIqYjpKJi1Njh5GOiY58fZaQfmd0aWZZWFFaanB6c4J6hnh1i3p3eIdscmd+VoZnYG1wc3BeWJKQfllyfqGKX2FeYmZ1dnR+fnZyYHB2gXR1c4F4Y3V0f4eCeHFdZmpoYZaNR4FtVmZTSUtMSU1PT09XV11dXVxSXWOLbHJoX1tbgFRcW1BYXV1qXVNYYVtfWHFOcFdaU1ZNZ1tJW1pOaZBwV15lbF1gXG1vaJxoio6FaoeSXnCQdGJ7eWRMV12Ee35bZG9ueUtpXHJmYlZaVl9mYWRmYW2Qdl1sbGxvjp6Vd31/ZXKBkIN0c2xmcHeSoaFbjoZgW2h+bINybGNkaGFkgGp2e2ZwbmphYHeCmV1rVmqsb1t5cWlmZWRqZm1tbGphWFtnfGxqjVleWU9TR1tiUUlXSEJUXlhcYWVLXmtoa2RlUFFjX09TX0tRYWdMX2JjX2hXVllmh45pVFdUXFFgZl9aV1lrVFhVU0pNXVxYWFBeW2BcWl5YXV9TVWBhXl9fgFRha2JjbGVnYGJvYmJgbHFwbmZibm1TX2FXXlNdXV5XWlxeXFtXXmxcW1tuUVhXV05HaWBTUltPV1pWV1tTZ21lXmFiY2dvjGVveGhXZm1hcm9aWlxXTFxnSGB+fnp3fXiDRIR5c3ZtXm1xom9zeXlubIlue4J8fYN0enOBe35ogHJ4T4psc2BjWGB7al1cXWRbcmZva3JsZ2hvbmRfV2B1YVddYWBiX2JiVFlkcHR0dG5+V2ZqXmR2aWFwfHdlbH+GjZeNeGpXT1pfY2lwamR2dIiDgHBkd35taGxraGdydnZfZ21oa3iCfHuGe3d2bn9/dHuEeIJ9cH+Xon6AgIVygHZ3iouOhHSDhYJ8eod3b2tmfnd+dHZ3dWp0c3RweXhwaV50aXeCc21zd3p9h25wd395cHl6YmNqXWJhVmVgWVp9fHp2bXBvZn5YamteYFNjaG9lb3GLaHxzdmhocWBleHpyeHVra4CYfn2Un4uSoLBgnViLdHlzfo+Lhnx5dXl9gHuGjYB7hZOCdnySc3J5gop4e36TiXGDhoCBlll+ZWpmV1NkdG94iHBnenyDcWR9iIF/f32GjpiMhYqBhIh7eXV4cGliXWhsbWJsZmZrcW1kc3GBhpakjWZUYG5tfG1nbG5wd4FxaW9mdXZzdHh5e3FxfIKPkZShUYh9mJeKkZSWgJWRV4Rjb3CCg4aHgX1zgYKPnpKBeJZ4eYOgi4WPc2tmVkxYVUmIfIGDaIGTkZJ9hGlwf4uPipFMSYaMiYqKgIJ9bm+Hfoh3cXV7hXhya3F0cX1ka3R0d2hmanNqcnSBWYBOc4V2ZmWITIyDe3SBhnqFkHRiZXZ9h3B5Z2lqbYFygIaUioNzeEVWaW5mZHNeYHx9cmiAdnZtbWNqf31+doB6hnZwhXlsaYFsdGqCUoZmZnJyfHlnY5eZhGB5gZuKX2NhYmJrZmpvb2xmVmBlb2xuc4CDdYB+gIWBbmxZXmFhV4WERYl9anpjY2pvaWltaGRqbnRzdmddYm6Od351b2xxgG16fHN/hISTg3l5gHd9d49ajnN0a2xfcmZSYWFWcJJ3X2lxem1xbH57daJol5yWgJ2qe4qhjoCXk4Rrd36jn5x4fYaBjFF8boR3dWluaG1tY2dpY3GTfGJwdXN3l6KYfouPdYCMl4h7fHdzeoOdqK5nnJFvaXCHdIyBfXZ2e3R9gH+CgnF7enZtan+HnGd1YnKrcGJ/e3Ryc3F2cnp7enhybG94i358l2dybWRpYHR8b2p5bGV4gnx/gIdtf4qIi4eJd3uLi32BjXh9jpR1iIeIgot7d3iAmp+Bb3V3gXWDioZ9f3+LeYB9dnRxfHhxcGd2cXhycnt1d3ZoaXV3bnNygGJvd25wd2xxcHB6bWxrdYGCgnx3gX5ja3FscmNtcXVxdXV0bmttc31mZGN1Xmdna2FYeXBmZ3FjanFsa3Bsfn94bm90enV4k3WCi31qd35xhYFqcHx0aXN9YXeWl5KRk4qSSY6FfYB2Y250n3B1e3lwcIpxfIR/f4d4eGx2bm9egGduSIFncWBlXmiFdWppaG5nfW11cHNvaGhvcmhiW2J6amFqcG9yb3N0ZGVseH17eXF+Xm91bHSHe3R/iIFscICFiZOHd2dUSlJXXGVva2d3cIR9eWdbbnVkX2RmZWZ0dXhdam1pbnuEfHaCdnFuaX14cHV/cnhvYnCFjGpta3FhgGRmdXl/f3eGioeCf419d3Nrhn6Gf4GDgXiFiomFj4+HfW5+dX2GdW1xd3yAiXJ4gol8cHyDcG90ZmpnXGdjX2SBgH95bGtqYnpdbW1lZl9ub3Rpb2t9YG5ocWNocVdddnlxdm1pbICMc3iNmIKIkphPikx/aGtkbn13dGtnYWhwgG94e29seoV3anaIc25vcXRfYWeBeV1yeHV5klh9Y2xuYF5xgnqFlnpsfXt+Z1h2g3t5e3mAjJaIg4iChIl+hHyEf3lxbHl+fnJ9d3J0e3NmdWt7f4yYhF9NWWdnem1lbG9zeYRxaXBmdXZvbnFwcGFeZ2p0enyJRG5khIV7gIOHA3p6e6F6AXuSeoJ7pnoBe4h6AXudeoJ7pHoBe7V6AXutegF7mnoBe5l6AXuqegF7/3ryegF7n3oBe/967noDe3p7qHoBe9t6AXuIegICBACAgNDK1OXg0Mfa28/Y4t3mzuXsw8jh2tnd0N3Iucm20t/izsC4wsXM3ofSzNLl5tKDhcj67oP229jz3cC0mbK9xdXPytXg1LS+t8jAvr/S17ylhNm5raeor6K3vK+/xMTvg+bw7YfLzt7y2drP2dW/xoml8b/M9orXt5+vuLi0yMmA0cz6lofm+IeE8t3Z6tj7hsinoa+xuL25v7q6vNLp9+mtutPX5Nm41NTMzcXHup6zoqKkw6O2wqyvmqKnnZOim7jO9ubHzcbOusO31te/pbnNy7i7yuHV2sTH0dv09v7qzMvEusPK1beltrO4tbuypJuhno+ZkZSSmp6mrriflKSAqru5pamw0qK2wri5uru1kYSPm6avl8DFs4iUmJTT5K2hmqOJjKa2qLyqpqqirqSvxcjBvbKmrqnI1sDAysvO07bQ4uPb1+jGyMu1vsTL8N2K2s3Z1L/AubG5srLI2rqzwL6qz86a1LrSwMi/u9bOs7SzpK2/39PKwLOrrbfDwryAzMm1rsfOrsPJw7TKx8SjnM/JpqiovMvBxsnHw8/EyrnMy9XrzNjt18S4vrW0nrzAvKywt7zK0sXKt8vS18/dxKmnvs3J4d7az8bEr8zRv7aspKCsqquerqqyp6Sboqezt7+/r7q7u62l1rS7scu0taitsa6sqrSvuqq2tbSmrq+AtMLKv7/I0eHJya2gnLigsLmtr66+urW7tbW1rb3Dz7i/2cDAzcK5tMKwn5LIp6igp6+Xr86Up6Wkrbi3ubjCsLGtrKquzMPFxMW6taSeqqeltby1wLbAvcO2zvSVy77o/eHL3Nvc8szL58bW3t3w49TWyM7bhOni9uXVysHAuq6Au/SBlsvAuOzS0s26qJ6Xp7Cyna+rv9qzz8rCt6ixrsDBu7G3rKqzt8mvtbGfsL+5vLS7ssa0sK+wxcnJ2cLIzNPV1vL40c7Bt9LI1OLL2MbDt8O+q73T08nLxcjIusTOyL/Ewreur6OrpJ+un6auyuDS1MjTy8nK0tLJysjT07yAv6+1wNzQyLvEvs7Fwb+xpJqora2rrqWnscS7vcS0r6OZ+aa3wayxqLG0tdPIvdG6sq+jtrOtsbO9uKCaraqxlKHDzNbCstrFur3KwbCfpby3v77Ax8biy7HSy7e5rb+ys5OWlrnJrMbU5d/TycDc6dzPtpGdsbCtpqWnqrCsuqmAs7K6xNLQz8jGwLSusNrKsbS5qrmvubKws8TBxb228ruqqKGats23tKuyqquns7q/wr/DytS7vqOimaGkqaq3wsrBwLSpzN68xripsbGwqpWcrrLBvK+oq7jM1MTByMbJ0Nvk2sXAua/C09TJ5Obf7+zZg+z05Nz1h5b63+Dk2+eAUoSDiJKXiIKOkoeMkYuVfo2QcnaLhoSLgY2AdH9xhpWWhXtydHZ5ilmEgoaTlYVYVXmej1GTfHyRfnBqVmx3gI2CfYaSjG9+eIl8eHeGkHhlY5t6b2tsc2l5fG94dXOTU4qRklV1fY2hl5SHh4Byd1pwoHyFpF2Faldka25wenmAgoCkaFSLoWBaoI2Lk4CaTmFRTlVUWF5ZXVtgXW19h3xSXnZ9lIhogn1wbW54alNhU1dadFtme3FsXmlvY1tbWHR6npFrbm5yZ2pphIhvX2t4dmRqcYSLi319fXyTmJOReW5mZGdudVxLVFVaX2VjYFRRWFFSVFVWTVddYG9bUF6AYGpmVFdbd01cY1xdYWJbPzVBSldhT3B8bklTVlGLoXBoZG5WVmpzZnZmXl9VXVBYbGxlYldOVE1mcFlWYWdrbFJmdHB0codpbXFgaXJ7nohTiYOUknx7bmRpXmJ6jHFsd29lhYZfdnWFeHx+eod9cGRiXGpyjpCKg25nYGh6fHCAeXlmZnuCZ2xxamBycnNZVoB6YGRmb3dzamxpZ3FoaVthYmN3YW56al1XYFpaR19fYFNWWlhhZFdhUWhrc2l0W0RBT2BZaGBdXVxcTGRkVE9NT1BhWVhJVFJdVVJJUE9cXFxgS1RXWE1FdVhiVV1XVlFUWlVXWl9da11raWRYYl+AXGxuaG5sb4VxcmFaW2dTZ3FiYl5nYF5jZGFdWWlrcVZZaFNVX1xZWWRZT0x9W19bYGNEZYNMXFZQV2JjXVtmVldaW1dadWlnZmpiY1dUXltebHBna2BhXmVabopZWkp1hG5caGVjdVpif2NzfX2MgHl6bW96VI2IoJJ/d2hoYVWAX5NRYXFmYop4eXdpW1ROXW1vXmhkc4dme4F+dWZoY3FzbWFjVlhjanpiZ2tddIR8em5tZXJgXVlYZmhugGhvdn+DhJqdeHFjX3dpd35reGxuanJsZHmNioF9fHp+aXR8cW9zcm5rb2dxbnB/bnByg5SIhn2GgYOHjZCJioaTkn6AgXJ1fpSFgXd/d4V+d3lvZ2JtdHZwdG5scX92dXZoaF9VmmJygW55bnJ0b4qCd5CAenRjeW1oaGx9eGBWZ2VtVVxzeodxZIVtY2ZvaltKT2Jfa3J1gou2jGaFhX18bIR5d1hXVHiLZm5zfXtuaGmFk4iEclNdcHNvamhlaXR3gG+Ac3FyeIGBg3p2bGVna5CAb3WCfIaAhX16eYaBfXFrm3FjYVtTaHhlamNlY2FebXd/hImQmKOOinFvaG9xd3N8g4R2d2peeIZqcGVYXVpdWUhMWl9qZldSU15vcmRiYmRoa3mAeHBua2Nzfn10iIx8iIpxTYeHfHmNUGGSgoKIhJGAS3RxdoOEdnCBhX2CioqTgJOWc3iLh4iMgo9/coBvhpaTfXBnaWlsfkx6eX+NjnxWU3CVhkuLeHmOf29oUWp3gYyAd32EgWNwaXdvaGZ1e2JMSYBnX11eaV1ydmt1cWuPTX6CgklhbYKTh4N6fXdmbE5gk257nVuEYkpaaGxpcW+AdHCWXUlvgk1HfGxqbWN7QV5RUF1ocXNqb2pmYXB9inxRWXZ8iYBkhX1wcW10aFRiVFtdeWNqg3Z2Z29wZF5cW3N3jHxdXF1nWFpUbHBhUl90dm5ydYGCiXJzd3WJioh9aWNjZXF6g2dhb3N2e4R+eGttcmpuZGdkX2tzeIVtYnSAfIiIdnuBlnGBhnt5fH11WE9ZYWp0Y4KKe1pjaGWUqYB2cntjY3aCeIt8d310eW12jI6Kh3tweXOMlYF9hISFhWp8iYaEgZJ4en1scnh9n4pWiYOQjX6Ad3J2cG9/jHVxenZujI01hIKTjZSRj5qUhHx6cXaFm5iRhXNzcXeEiX+Ai4hxbYWJcnh6c2l5enphXIeAY292fIN9dHZ0cnxzd2x2dHiLdoGPf3BrdG9vYHl9fXJ1eneChnqEb4KIjomUgW1pdomEmJSRjYqGdYyNgXl0c3B9eXhueHd/dnVxd3eCiICGdniAgHhtkn6HeoFzb2drbm1tcXp3hHSAeXRob3GAcX1/d3Z6fIdydmVgYXJhcntydHaAdnBybmloaXd4gWpuf2lncmxnaXNoXlqJZ29wdXdgfZZic29rcn6AfHZ8a2tta2dphX5/e3x3enJud3V1goeAhHh9f4Z5h6JkcGCHm4dyf3t2h2huiGt7hH+Ke3R0amt0TIOBl4p6cGJjXlGAWoxKV19WVYN2enpvYltUZHJxX21ndoVhd3t4cmZpZXN3dm92b253fYx0dndldYF6f3d4cn9xbWtodnV3hXF4foGDhpmeeXFkXHRmdn5ufGxrZGpkWGp/e3NzcG91ZnJ7bGltbWlmbmhwa2t5bG1ufo+AfHN6dXR0enp0c3B8e2qAbmRpcoqEgoCDfYuDfH1zbWZyenx5fXl2eomFho2AfXRplW58hnaAeHt8dpCLgJWDf35ugnp1dHaBeWFabWxzWWN5e31sYoVybXB3cGVZW2xob3Fyen+fh2iGgXR3boR2dVdaV3eFZG90dnRuaGF4hXtyWkNQY2dlYFxYW2VqdGKAaGVlbHVzdHFvZ15eYY15YWNrYm1kamNiZHl5fHBqn3RmaGNcdIlwcGdnYmBca3R4eXyDjJyHh25uaXBzfHuDio5/emxhhJd1fHFjaWdqZU1QXmJtZlNOUV1vcmJeYGRrcYCIgXZycGN2gH1wgoZ1gHxgRnR3amV9SVeBbnB2c4EBe6d6AXuGegZ7e3p6enucegF7jnoFe3p6enuLeoJ7hHoBe4x6Bnt7enp7e4Z6AXv/esF6AXuUegF8/3r3egF7mHoBe4x6gnv/ev968HoBe4V6gnuGegICBACA28fT4enIwdnh1cDc3eTP3eq6ssbF0LfEq6avtLispc+2sLmus8HH2M/m4uTkgfHc0drtydzl0tLTw9X00NjX5ca4ur6/yMrS2d/R2dfBwcOwu72tl52kp7KyuMnPzd/Sx8z50tzax8PSwdP45dPLysS5xMfZ4dzIxcC1vbzQ2cuAzvuIg4zT6+nb1+D8oMC/tujv2ujdw8TO0svR7fzf24zTxMC9uL+00emA8c7ex8fI5Me7qaGTkYaJjJSYk6Kuq8Xe3uX+s52crMHDzNm/1syzopql0dzPys7c1NvP/O/7652K9N7Q2MzHx8rCubmpmKSkn6Ojo6CSobmypZ+inaiAmqCppsCzqcHE0663sK+1nKukqK2epqiuq5SDopy1p5Wiua2OkrTVy6eanKCXqJecsbKukZi3sKSbmqaypsXk69To/NjRyszb1dfL7brLz8C1sLGrtLSduIa82Mu8t7O4sK7J68mes8+00r3Wr7e5oKmqnsDZzt3Ft7PLxri3qKmAvsHCw83KyMTItrqipq21qsjJwKyrs7rLxM3Yzc3W1t/h0dzs5c/c08zByr+0rai+wMa+t8HBx87Bx8fNx9fl09TJzdnj/fb05dTKy9C8tbauu6S4sqigpJ6qqKG4uLKnrs3TobWyt7yvw7u/tLm/t8CloJenr6W0uqytoLSyvsCAtsXNxcC81cq9u7uxtKidutbAt7q9sbTHzd3S0Nm+ssve0MW+vb64urScqamjmaK5rq231uO1s6qho7SxrLK9ucK/r7G1wb7Uv665rKqlnZmjtbS0rLC1z8jZ6urevLewxNrMyOfk08bj8PHy7+np8OnU18HY8+Pk29bFxMi9xdaAyujlwqS90d/o7evb996spLLmrqi1pMTHub/Suri3wsDAubqtrrHEx9XD0sakmKCyoLO5w87Bx7zB2tfKx7Swws2zqM/Kx8bLv8rNvKqlsLiopLnOv9Hd69bc09jOyLi2prDJsJ2bxb+z1c7b1eLf09HSys203dzj0cTTwL3T17uAprrHvsDFyMS5u7q3vrrNya+5t6ymq6eurrGlpKOcqKWf3puGoJeRoqigrcLKzb7Dqa2wrLvRxsSxrbS2rLeooJebraKrvKKnqqyoraOswLWlrbrDt721sLjL0cyxqLrCzNLMzLTFxa7D7t6F9+be2NDPyLSswsTDqaastaOsq7GAna68xcvPzcfIubfCxb7CubW/z8a+vLe9sNW/s6uvn7O7qMLC1r6qsaWus7i9zcK5s7XQua2jsp+eo6KhpJ+Nn66spK+509bRt7rQwLrBwaa4vq6bsMTIytDe2bqsvq+st9XXysHN09TI2NbN38jL3tHb4uXh/e7y/Pzn1tns8YKAhXmEkJN9d4iOf3CGhYh4g4poXnR3hXJ+bmx7eH90b5B8eoR6dn6Hi4idl5aXUpqIfoKPcHyEd3h6c3+Xf4eDk4B4fHl6g4CCh4uHjIp6en1scXhxY2Zvbnd2dH6Gfo97b22Nc4CGfXl8aniViH16gIF1fH2HiYl5eHVwd3B+iX+AhqJVUVuMmJyTkpinbXt3bI2Me352YV9paGdpf4p5d09uZWRoZGddeItNlX6Od211jHhvYVlYXFVbYWhpY2N1aXiNjIqYX0pOWW1zfoNvg3lgVE9TdIWDe4OFf311kpOikltZmYJxd3BjY2ZkXWFZUFVQVFpWW1pTUWlgUlJUVF2ATE5RTl5SSl5ga05WU1FRQEtITlVLVlpfYE9DXVx1aFljgHFUTnCKelxSVFJMU0VJV1dVOkBZV05JRU5SRl1yfW2DknJqZmlycnxzkW1+gHNtb2pgZmZSZ1Fthn5rb2l3b219ooFeZ4F0i3ibeHpyYmlbUnSRhZB8cnmHf3BtYm2AeXZycnt6dHl1YmFUVlxrY3p0Z1dXXmFoY2Vza2xwcXl6aXV7bVtpZFtYZmJdXFViX2JZVV9jYmVZYGFpY3BuYlxOVF5vkoV/c2lgXmNUU1hTX01hYFZLUEtTT0pcW11LWGVnQVNQVldSWlliWltYXWdSUklVXFhha1xcU2NaamqAYWtya2pwg3JwbWVcZWBUZoBvbWZpWFZhZHdvbHdfVm13Z1tTVV1ZZWRRW1hWUlhvZGFmfYFeXlhNUlxUUFleXWJkWVpkcGp7YlZbVFtaUE1TYWRjXV1dbWJmbnNvUVBHVmVZWnh0YlZse4GBiIKBiYVwc2F4lYuJhYJybXFrdH6Ab4eFZ05idICJiIt7modfW2iIZWFva4mNe4SQfXxucWdhX2ldXFtjb4d8jYdnW2BuYG50eXxra2FkeXNmZVpddH5mX358dnV2bXV4bF5XZGxhXHGCdYSLmn+MgoWAenB2Ym2Hb2VljYV8m5OVmJ6ZjISHgIhznJmgjYGNfnmNjnWAZHaDfHl8eXhxdnVucGx4fGNqamRdYGNnaWlfX19aXF1djllKYVxUY2ljbX+Hi3yBaW1tZHCKf3xpZmlpY2pdVlZaZllba1NWVVxbXlFTY15ZZnODc2tiYG2AjodycXl8i5KNhWdrcl9nhHNLioJ+fXuAd2lmfH2AbG11gGhsbXaAaHN8goODe3V4bGtzd3F9eHh/i4ODgX6EeJiGeHJvXmpuX3F2j3pobGFma3B8iYB2cXSOf3Rtf3BxdXR1cmpcaG5pY2tziIN/YmR3Z2JpY1BZYFRGU2VsaXJ9fGNWYVlZX3x8cWt2e3t2gHtzgXJvf3J5gH99kouOlZqGgX+QmVOAdmhzgINrY3eAcGN/fod5hpNsZH2AjHmFb2l2d3xsZIdwa3JnaHF5e3aHh4aFR4h7c3yObXqEeHuAd4GVeHd3hnFobGtqcnJ2fH12eXVkYmVWXmtgUVdhYm1sb32GfY12ZmWHbnp7bGlyX2qKgHZvc3Fpc3WEiol3d3hucGZ1fm6AcItHQ0tseHx0cnaCU1lcXpOcjY6Ib299eHFxgIx7eE5tZ2BiY2hbd4xLj3mNd253jnx4aF5aW1ZdY2VlYWBvZnN8dnuCTURBSlpYZHFgbnFbVE9TcHp3bnN8dHJpfn6HgUdMj311e3NyeXx7dnlvZGxrbXV1cnBiZIGBdHN0cHqAam1xboN3bYOGj21zcHByX3Bpb3Zqc3Z6emhXcXCGeWhzjH9jYYOflHdtb3Bpc2Vse3x6YGV9e29lYWpwY3qQloOSnX10b253eH93km58g3dub25pcXFecFJxh4J1dXB8dXSHpYhsepKJnJOri5CKe4J5cIiblJuEdHWGiH92aXSAhIaEfoGAeX19bGxbXGRxaYCAfGdpcnB3cnV/dXZ9goyPfoiShXKAe3VxfXpzc2t7e4F5c3x7fH1yenuBfoqNg4J4gIiWr6Wnm46GiI6Aen52hHGDgXlvd3J7enWAgoR2gpKQb394gIF+hH6Jfn57eX9paV9uc218gXRxaHJufH2AeIKHfHl0h3p0bmdha2hec459fXp+bmpzdYF5eIZvYXeJfnFoZ2tncnNkcGtoZGl/eXyCmaJ/fHRqcYB4c3l9eX18cHF4gn2Ne3B1a3J0cG50g4N/d3l5i36DkpSKaWRcbHxycY2HdWmDj5GQj4eEjYhzdV50jYGAfXZmX2RgaHaAZ4V/YUdabHZ9goV8mIhjYXCLZWFvZX6AcHqHd3dvdnNxcXxwcnJ7hJiMloxvZm15Z3N7g4x+fXFxg350dGlqfYNtZIF/fXp7cHZ3bF9aZ21hWmx9bX2EjXaBdXdxb2ZqVl98aF1eh4F2kYiLi5WShH97cXVhhoiJeHJ9b2p9gm+AXm99eXh8foB3e3t2eXaFh213dnFsb3J1dXducHRrbW5rjGhac2tibnBocYKLkYaNeHp7cnyPhYJvb3VyaXBiWlZZZVtfcVtgYWdmbV1gcGlhaHJ/d3JqaXOEkYpuanmEkZOOim94eWJqiXdKh4aBe3NwaFtYcHR4Y2Zwd2JjYmmAWWRwdnd4dG9xZGJtb2Zwamtxf3JsZmNrXYNxaWdsYXJ5Znp+ln9tbmBjaXF6hXtzcHONe3Bqe2trcXJxdW5gb3lya3SAl5OLbW6BcGtya1JcZlhIVmp0b3mDgmNUY1tZYYCEdnB5goJ5g31ygm1pe2pxdnNyioKGj5F5bm+Ejk2tegF71HqDe4d6hHuPegF7iXoBe7d6gnv1egF7/3r/ev96/3qWegF7/3qUegF7AgIEAIDT2MTi8eH00+PLvbrC0brEy7fDyLjSvcXDsLP6q6qxq7C1vM7VzP2K0YDq5Pbbze7s49/Zu7nBycvd9OXLzMvCnsXOzc7DztPO2+Lfztjx98S8oq6gucjJysvW29fQ+fzMzdjd2eng5+vq7unf1+jM3erX2cXDwcnb0tDb4IHkz4CBiJLD1tG+wrOzztbe8t3Qi4Tf4fvOv8DRzt3p9vb93b69trzCvLK8y8bDx9u2vbyttqulp7WjlI2Kk5OioK/Sy8fpyrSnqLjBz9z7ie/P3N+0t7y8wsvazr/o5dDMw+PZ3dCMhtvVgeK5tru4tKOcpLeelaunrZesub29p5mUo4Ctrau2vcO0sbnCtLiruL28uK2uoKihmqKopJyVp6aYpKOhqauf7cS3saq9zcDhvsO1x7a1yb2zrauwpqurwc3h0+H43t3v2MqN5fTvw8W0yNnuzs6CxujK4ePb6dDAvqiapLq7tbPMuairsq2nlZ+inZqwsMO1zK6wsbe6va/A04DIv7rV1tLDxcW2vcbh4IqvsK64urizurrIv8LRw8nF09TK1vDv5fjc0dzEt7yrs8a7tMuH9c3L08/Fz9vg0NTf0s/E0uPf2dTJzcazuKmxxOTAqrC8rLCwsbDAr6K9z72zt8W+s7KkuLCvwbqws6OomqKqpaeotqevpamuqK7DuYCwwrvGtcG1vce8n8Lk8dnY8M3Qt7S/xbnU2dfl8unL4N3T0MWwvcS+sqKgp5ybpLS/08fJ08fBv726sK66vsC6srvGu8C1sbeyrpiorJ2hs83Ks7i5sLXA3+L+gu3Y2dXcmMOrx7zGvbjT3+Db4vDn2evw7fT159Hb2NPh9dnb+oD50d32zPDd0s7BybWwq8Cin6aWoaGfo6O4vcO9wLC8vbfKwbG3tK640O/qzcS+uauno7C0sbvJxtPQ2se8vLHHzsvDs7G7rbK+vMa+t7W8xb6zu66enZ6cvL20t728u8e7x7uzreHfzLOyqqy4x7PBxsS50vSOl+zVyrfG38OvyIDFzr/IxNTby9fPzce5vbu0ssO0tri9vrrFuqWtoLGqm4qBmpqlsLfEr7Sip6+2u8y/vLu9sq+xpKuuraqenaaoo7GwtbGnqLq8urqxrK+8vcCwubKnv8fOtLbbsbvMw8vW1dDHv8KytsPG2tPPzs3QvsrZyrrVzsXPzMuW+ouMjYCPpKGzuM25q7m5t73JvKu4vcCrvr3LxL/A39TOx8i8xKOsq7Svo6SkobXM0ce1srysrpuTlK2zrpSTmaCwpKWdrrK1vLHKxcXCuc26ubasvsHE1fvV08y/zdvLwtbHtsPL083AxMvd2Ove2dHWucW8ucba3N/r79vb5tLH0uPS34CBiXiOmoyag5B5bmxwd2VtdGZsdmqCdHxzc3m5dHJ7dXp+h5SViK5hh1idlKCFeZOSiYeBZGduc3iJnJJ8gn11WHmAhIR/iYyEj5GMf4GTmnx6am9ldYGAgoeQlId+lJJxcoGKgYl6f36BhYWIh494iJaHhXZ1cXiPg4GNlViYf4BPV2eCkYx8hHpyfoGBkIN2UEx1eYxrW2BrYGh5hYKOel5iYmRlYlxhcXBweoNjbWpmamJmcoNxaGNnbWlwc3KLiH2Td2lUWWVwgJCgVpZ2g4NmaWZqdoCOgHGQiXpye5CEgYBgWHt3UnddWmNhX1VRU2BNSlhbXlNZZWNoWE1KVYBdWFFYWF5PSlBVSlFHVldaWFNVT1tbVl5mX1ZSXl5SW1xcYV9VnXJhX1lnc2R8YGVdc2JgcGNYVFBRSE1NYGx5anGJeHeLfW9ZkKGhe3tqd4WZe3JMcJV5h4aIl3Zzc11ZYXB5c3OJbmlnbG5lXF1ZV01iY3Ztf2BianpzdWh2h4CGc26EenRtb3htb3GNj1ZcYFphYmBeYlxhWFxoW11cbWhlbn5+cXpiYm9bV19RWmxcUF9MhWZqbWlcZGlrWmRoXVlQYnR5cWdeXVxQUkxSY4VqUldmV1lYVVNaTURSb19TXl9dUlFIVktMWFdbX1BPTlVeXFtaYldaV1NVVFRgYIBbaWVsYXBscm9nUmyRoo+TmHJ0XltgZVNjZmR1gHtieYB1bV9OWmJkX1laY1ZSW2dvgm5wdmlcXF5dV1phYWFgW19oYGZcVF1cWklYV0hMXnl0YWVhWFxmf4GVR3RgYGNwWVtHWFVmYl14e393eYWDe4WEg4uRgHiBgH2Rn4iIn4CddHiRbZCEeHRpc2VkZnxhXmdgbGpsc3SCfX93cmZtZGBnXVJhZmFrgaWmiX5/eGtiXWVlWl9pY25rcGZkal5wdHJ0ZmRsXV5nam9qZGVudXBtdW5fY2ZkhYh9f4iIh42BjH53caSkj3Z0bW56iXd+iIR4jKhlaaGJgHSGnn5ug4B+gnJ9eYeNe4d/e3Vpa21oZnVra3Btc214a1piU2BaWUlCVlJdaXSFcHBiaW5xcYJzbm10amRmV2FmaGJUUWBhXmdncGxiXHFzbW1eW2JxbXBkbGRWbm96bXCRbXqGfIaMhIV9d3VdWWdre3Vua3N6bXaCeW2KhYGMio1lp1tdWIBaamp0dIl3bHVyaG53bF5sc3xtfnuJfnh9nIuKhYZ9g2VpY2hlXF1gX3GFiX1vbHludWVcXnN4dF1fZWhza2pidXZ1emt+d3RrZHBeYV9UZGdleJl1dm9jbXlwZnhrYG9xe3Rvc3qJiZyNhoJ5bXNsaHKChIqUm4eHj355f42EkIBveGN7h3yEb31mYGBncmVveWhxfW6Fdn13bXGva2hwZmhscXx9cZRUckyMiJF6bouSiYyDaWZtcXOEmYtxcmthRGl1eXhtc3Rrd3d0aWp9g2NiVl9Vanx9gISQk4mAnJx4dYCDfIV4eHd8gX98eIdwgpSIiHVydX+Sgn2IjFCBZ4BES1Vmb21faF9YZnBzfYF8TEyBhZl6aWt0Y2l8iYeNd1tdYWRmYFtdZ2RoeH9hamdncGhqdIN5bmdiZmVoaG2CdmZ/YlROTlZgZ3aKSH5qeHthaF5faXJ+b2N8fWlfX3JtZW5STHt0SoBta3V0eWllbXpmZHh1eGRsfoWKe2lgbYB5c250eH1taXB1ZmpgcHV2dXFxaXVzbnZ+enBte3ptdXV1fHput49/fHOCj4GWfYJ6j35/joF1c3FzZ2xoeIKKen6SgHyMem5Xj6CjfH9wf4ycg3tNdI94iYmMl4SAfWpkbH2Be3uThX6DiomFeXyAeXN+fIJzhGRmZnNyfHaAioCJgXyThnpxcnlzdXiOklhpbm10d3Jvcm1yaGp2bnJxgXp2f5CTjZOAfol1cXppcoBwZHRWnX+Ah4F3foaIeoGIfnp1hJWWkoyEhIFxcm1zhqiKc3aFeICAgnyGfnB8lo2AiYiGf3xyhnh7g36Ag3JxaG51dHBzeG11bW9va2t5doBwgHyAcnx0d3x0Wm+NnpScq4iHcG51fGt6fXuGkYt1jo2DfXJhbnh3cmlsc2dncYKKmYuRmYyEhIWBeXmBhomFf4SKfYJ5cndzcWNxd2dpepaWhImHe3p8kZWnVI15fH+LZHRcbWhzbWmBiImBg42KfYWEhouPgHeAfnmFlXx+mICVbHKKZot/dXFncWNjZnpiXWJaYmFjaWh0c315eW52cXF7dmp5e3d8kLK3nI+KgHJqZm9ya3J8eIKAg3Rwcmd3fXx7a2p1ZmdwcHRva2p0eXJrb2RVVFZUcHJpaHFydH5xeGxkYpeZh3FvZWZwfmlvdnFofJteZZZ/d2t9knhpfoB7f3F7d4SLfImFgHtvcnZycX92e4CAhH+HfGt2aXZval5Wa2RqcnyIdXVocHd7fpCDgHx9cWxwZnF1c25fWmVmYWprc3JqZXV6eXZsaW59eHhxenFkenuBdHmbcX2Jf4eRjYt+eHxmYm5xf3hwam56b3Z/bmKDgn+Jh4dbjE9NSYBJWlhnaH5qX2hpY2dwY1ZjanBba2l3bWVpint8eX13g2hycXlzZWNhXGx/g3VmaHVmbV9WW3N8emZma298cW9jdnh5fnGMhIJ6bXtoamVZZ2prfp56e3Njb39wZXpsXG1yfXVscHqNjp6Ohn93ZGpjXGZ4eoKMk319gm5lbXx0fqd6A3t6e9N6Bnt6ent7e416gnu6egF7lnoFe3t6envgegF7i3oBe7l6AXukegF7/3qoegF7hXoBe/96lHqCe/96hnoBef96hHoCAgQAgO/vgevu2NjS0ubg3NnPwMXTz8nZx7Wyzs25w+XZy83P6Mi4wLu7yNjR0tzVwdrH1ODl1eK/08r6gYGC3setraqqqK26ucm/gPiD7oL/8OL62vr61dPHwLvH2vnkzMi80ujQ29vj4tzU2tuJ6cXDwb7Cys+80OSR4dXd2dzi0+jsgPr4iefg+u3Css/Ax9Pc+/j48dnY4cfG0+uNoYKGgtvKrKy/uLy/0djHvZ/d4cu5pLG7pqOWtK2qpbKmrJyrv8m/s826qbm6ubzp8PrT1uvXvMrilIHt19PQxL3Rw9POy8rLxtza8uDV2bu7xL66q7jNrKSsqrGsoLu+s6yfmaingKSnm7S0sLnKwbrM+MHGw9q+rrS1pKucoLC4pqGZmJujp7LAt7/X19PLzb66vc3MxMrPz9GtsrrAxq6oqbLU09j40c68uMzVyLHc0aylsa7h18PJz8nE67ezx8/Hwri0sJqqqbrPuNnNrqyiqp6fkpqdj6uor82/vsPu+Mu8pbK8gO7nvru+1drHvt7mxs/ez8O6r7CcsbG6srW5xL3A3PCFhoOD5+v0yNHazdrY+di/vMzMxMTj6t7QzNLU1c/PydHLw7TO+PPZwtvBt723sKOr0svFurWvsbe2uaunsLrT273Auaqkoqijp621vsa5rqeWnaOupKafqK+zvLm3xtDDgLCwubC/xLnOx8m9vcX+v+rgyqvQx8PG2eHB0dDK28XBudDjztzXtNLAv7GilZCetLjUyN7Nw8vAy8+1sq+ypaCnvrfAx7qppbqumJSVp7zFy8e0trvRyMHT2uri38uqx+v7scbTqqq5q8i+zMza1MLJ0d/O4vPa2Nn86djDvbbIgML0jt/s29rk6c7UuLyopp6mp6OYj5anwLO5ysfMw7rFvqyqvLy2xquotbi80cW/qbG3vsTOxcDPv7Tk2M3Eyr2+w724rampv7HA59G9ydjEuLK4r7qwtqyxvamltbvEutfiw83IwKHdqbjMssTAyL/DztC+xKnB3dO8rsre1Mz0gPD2487A086+xNPTwsG5sauvwcy7wMG4xbysoaSWrcG2srPDsL28ubafmJSVk6ibtMTIwb6utKinutPGq52brqnTuLW0qb2joa+ypKSupKuorbjNvr7EyM2xoJzEy8XDu8vm+dbAxru0vcrMvbXCvrKuvbO6xaH9yLK6v8awudeugLq3s7Czpq68ubCtoZ+wq6qts9C4scC6pqivtLCwtMLTyMvFwLy7paequbeouLTG3fvrtq2ovq+traCpm6ChlpuioKCpqaS7uLewr8K2rLXBurfNxdPX0tHFwcCuvMHA087VycXFwtPKtsLQ2cvN2MfH9ebp3YGN8PLk49fd1snpgJSbVZWXf4KAgY6GgYFzZm59fneGem9ug4R2g5yWiouPnoZ2gYF+iZSNi5iMe5J8hI6Wh4tvfHOaUlJWi3ZjZmZpZ219fIN5VaBPlFCfm5CagZGKfIF+fXh/jKCOenVrgZB5fnt8enJrcXZRiGxqaGZrdHRrfpFijoGIiY6Tg5OdgLOuXp2dtap8aH5ucYCElI+Jg3B0dGVkbHpRWUZKTnRjUFJlYmZpeYN0a1WDhXBnYGhybHBkgnt8gId9fG5tf42AboB2YXFwbXKdnJ6DgZSFcHyJXlGhioN/dXF+coSBeXt9e5GNmImVfGtocGtnXmFuVU5aVWFbV2dqXFtVUFxdgF1YS1lUTlJdVlBgiFxgXXReU1xgVVxVVmFpWVZRT1JVWl5tYmVzeG9seGttc35zbXZ5eHRWV15jaldNTVNwdHmTdW5cX3OAdWKFe1hQXlmMhm5ze3FphlxcbXZ1aWdmY1lubHmRdJeFYWpXX1ZRUFBVSVpYXXdvcm6Qp4dyW2RqgJeQZmpvdnZubYqWeHuCdGBaWFpLXWBiWmBmaFpXbHtJS0hKeXt+Wl1gWWpsi2xVU19gWV93eXBjXWJkZF9fYGdjXVBkiH1pWnNfV1xbVUxSdWpnXVlUWFlSWEtBR1JgcVdjWEpJSE5LTVNXXmppZF5MV2BlXV9WU1tZY1lWa3FfgFVaYltobmR+endud3ircJqSeUxnYVpcbXdZXlRRYVFRS2qJdHh5XXlna2RcU1JbaGZ8cYRvYWleZGxZVlJaUU9SZGFqZ1tRT2RXSENDTmJwc2taXGF3dmx6fot/fG1PY3yITWNxWVlfWHFre3iFc2dqb3hygo1/fH2cinpnZ19tgGuZW4WSgIGHiGt3ZWlfYl1laGtoYmJuhHl6gYF9b11hX1JVY2Jic11abXF3iX57Z21ucGxwZ15rYl2AdWhhbGZrc2thWU9LWVFgemxia39ybm10cH19gHt/jXhxf4eNg5qbe4SBemCVaGOBbX15iH5/io5/iWuCnZJ/cISTiYKRgJqkk35xgHdqbHh+c3NtZ2RoeYJ3dnZrcm1iWl1NXm5nX2V3aXRsaWtgWVZTUGFUZ3l7cG1gaFtdbH12YVtaZ2GMcnRvaIBmYWhpXWBoXmFdW2FtYmNqcnhhVVdzd3h5cHmUooFucGtkZ252aGVvaGJhbWpzd22tf210gId5hqGDgIqEf3l7b3R/e25nXFpmX15jbIFta3h4Y2NoaWhpbn2JgIB9enh2XFtdaGVeb2+BkqicdWtsf3N0cmdtZWhjWV5iXlxiXltyam9oZHVpX2JqZmJ0a3yCfntvbm5fam1reXZ6cHFxc4B7ZW59hHd+hnh4mo2Oh1FclJKJhICCenaTgIaOUIqJdnZ1doB8e3xwZG5/fneHeGxrgYNvfZuSg4F/j3JfZ2dmcHx2eId/bYd1fYmUgohlc2SPTU1QgWlSUExLSU9fXmZcSIdDe0OEfXqLcYd/cnt2dnF6jaSSf3ZrgJB7f3l3cWljaWlLfF1dYGJncXVrhJdkj4SHhIWDdICCgJOSUIiImotsX3ZmbH6EioeMjXuAhXZwcn9PVUhNSnNgT1NhX2Jmc3lqZU5+f2deXWt3b29jh4GAeYJ4c2JmdHpsXWxiWWVhX16Bhohvc4x6Z3SCVUiLenFwZ2ZyX3BrZWRnbH6DjH1jfnZ0fnx7cHWHbWh0cn10aXp/fHp1bXh3gHlzZXNvam56cmt4nXJ5eY99dH1/d351doONfHpycW9xc3SCeXyLj4uGjICDhY+IgouPj5F3eX6CiXRqZ22FiI2egXlnZnqDeGOIgGBaZmKQjHR5gHh0j2dndn58enh4dGh3eYiYg6KbfYR4hHp6c3J6antvb3xwcGyLmYByZnR6gJyXdnh9hH9xb4qZgIOLg3Bua3BidXV2bnJ4fHJvhJBRVVRVkpWXd3t/e4uKpopzcHh5c3mUlo6AfIGDgX58eoWBe2+Gpp6JeI97cnh2bWRrj4mJf3x4e317gHJseHqLmoWMhXZ5enx1d3yCipGMhXxobnR7eHZybXJ2gHh0goZ2gGtyfHOBgnSGfXx2e3qocJ6Yh2B8dnN5iI5xd3Vzgm9tZXuRf4qNco17fnZuaWdzgn+Yj6GMh5CFi5F+e3d+cnF4jIeRj39xbYF2ZF9ea4COlY19f4SXj4SSk5qMiHpddZSgZHeDamdtZYB1gn6Ie25zeYB2h5WGhIGhjX9qaF9sgGuSVX6IenyEh2x2Z21iZF5kZmVeWF1pgHZ1f4SHfnJ4dWpteXl5iXJvgoGFlYmFcXZ4fn2Ee3OBeHCUhndud3BzeXBpY1tabGFvi31vfI19d3BzanFtcmprdWNZZm13cIaKbHd0blaHV1VyX21pdm1vfHxudl11kIhxYnuOhoCWgKGgkHxtf3lrb3t+c3ZzbmpufomChYh9g3twZ21gdIN8dXWCc314dXZoYV9fXnBkeoWHfnxudWlsfo2FcGlpdW2RdndzbYFsaXJ1a254bG5ubnF/d3Z8goZoXV5/gHp8c32Zq4dxdG9rbXR4aWNrZ19bbWt0eWSmeGdvd3ppc41vgHNsZmJmW2FsaV5aU1FhW1dbYXpkX2tpVlhfYWBfZ3iJgoaEg4aFaGZocm1hbWt5iJuPbmdpf3J1dmx0bXJtYGVsaGVqaGJ5cXNrZndqXmNraGR3bn2De3ZpZmVVYWVldXN4bmxtboF6YW18g3R5f3BujoaEf09Yj4d9dm9xbGmEA3p6e7V6g3uMegV7ent6e556AXuLegF7i3oBe5Z6hXu1eoJ7knoBe/96uHqEe/964HoBe+V6gnv/eox6AXv/eoJ7iXoCAgQAgPjog4j81MXY4ebn3dPKvr3NzMvIzc3AyMXUvsHDwK29tKCfn9D84/Lm4tLN1c3z4djg8fXIxM73loL6g+fyv//YpKSwv77K0ff61tfG6eTByJeOxN/Z2vPDt7i7u7m60snIy+bkk+iBgYXjgO+E7+DJy8bJwsu609vZ0eHK0eXlgOzr0cXH1Mq8sfXR4PyChPOJ6MzV2Nj0lobV3c7agIne18S9w8bEwNbPt7/mgeG2n5GIlZqOlpucmKaVmbmtyufe4/vhwL+ws8aEhfPIr7O5t77M18LLx83Frb+8t6O7ubzAwb/J2bKp18/HycfKvdPJsLzZwbW7sa+vsKykqZyNgIecjZObpKi9zszQ1c7I3tTKxcO0u8G0uMOmv8zYyaGoqcq+t8rY2cq5wc3WwMGvup+oz+bNwbOstLLHubrG1M/IzbuiutzJ0MbAtLfLt8uI2au8uNfGzNPZ4tS9vKCllZmgpqeuscrFvqy0x6CoopadtbOnu8nZ2IGeqf/ezcTUgL21t8LAsb3M4NfQrbe60IryntanoLCpoqmzuNHQztra1uPzx83Uz9DU0tze2trczc3IxNHJ3NK4usTKzL2yqqm60tTBv7u+t7W3uLGptMCzucmxwLmyt8G5xMvJy73KxLu7stGzqK2UkKOr1MKynaSgrqeMsLuwqbK3zLu6s7GzgL/Ds7S9vsfh08fX0sG7x8/EwLXU0c3JxNTRyMvPysnUuM3P3+ji2cu+zsG+urbF7K2msrzKzdDn3sPOysexvMa+zMzTxrTEu7Strqq2w9fy4riurLvf1MO8uMPA0M6lqbS3ubK9vqKvvbfHzcDG1NDl98bDwtLHyL/GvMWzxdjEgMW/0oDW/pHKtcrOtayqsJqdr6yinJaqsKa0zdjd0sewwszfzerfvquwvLm4wLi0vrzDv8vRuLO319bKw7vM1dK5ydDQsLG1uLHF2NPIuNPGuK+0tMy8y9fbsKewvcvTsr736vKK2dLw84PI09DJwLWvw7a6vsSkx8fI14Xt5u3VgMHV3uDCu7TF2MvNw8LJwr3ByM7Gw7u4xL/Kt7a7utm+ysTGw8++vb6zyr2xpabBtL3I0725rKu40b6qn6S5u7bQtLq0srS8qKaVpaugpqeru7Cosa69x8e1td/XvL/D5szg6tPJ1L2ttrW60s3Itaa4zte7p66xq7u8q6efoqeqgKqem63GyMCysLe3ucHAv7u2x7zDsqyirKahq62dq7a/wq25zczAusC2t6GltcOyura9nqarurKsrrmuo5enyL2zqa2rtbK+rbCop7e2ra+tu9Dk0dTf3dvUwq2ytcWztLfJ1ry8yL3H0+aWpPbS5crN28/k0o2Dzvvu2cnU0NvpgJSDT1SWcmd5hIaJgHhua2Z4dXh2e3x1enyJfIOGhnWGgWxua5a8pK2knJKPj4aik4iPl552cneYXFKbUpScd6aKZmZwfn6DhqSlfntwg4Bud11be5CJjKh3bG9wcnJyhnh2coB8UoBGSUp9SIhPiXxvcW9zbnhrg4mGgI1+iJuWgJugjISCiH1vYZaAhppQTYxViHJxd3CDUklve3N3S1V8d2Rpam9uboKGa26MT4lqVVFUXmdeYmxtbHVmZ3dxi6CYmLSagHxqcYFXV6B3Zmlwb3V8eHR8eHxxYW5uZmBma3NqdXJ9hGlcioF0dnl7bnFkVFuEaF9iYl5bW2JeX1dNgEpVSk1HS01ZZ2Zrc3FtgnpxbmpeZGliYmVQYG6BbU1OVGtpZnKDf3NicHd/cHBcZ05Udot3bFxUXFxqYF5ueXhrbmVQYIJwcGxiWWF1aHpdh1plYndpb3F8hntkaFNbTVNmZmJmaHl1a1pjcVNVUkxOYlxRZ3F7hVdqa6WOfHCCgGJZYmdvZWNnhH15XmVicVmlXHhRTFpSS1ZdYHZva3p3cHd6WFliXF1iXl9jZmxsYmFdWmBYaGBOUFtiZF5XT0xXY2VWWFhaWVtcXVxUV2RaYGRYYFNUWVtTVmFWWUhSV1BXWXdgV11EQVJaeGxjVWFebGhRZmteW1ZgcF9bVlpXgGJoYF9kZXGXjH6OhnpzeIJ0aF90YWBeXGZhW2BbT1JcSF1lgZeShXBnem1pbGx6nmVbYWZucXKKgmRkZ2VWX2RjdXR3bWJvY11ZW1ZdY3OKfFdRU2GCd2dgYGlodXddXWdtbGdvcFRicHB8fW1yg32OmmtmYGtnaWlpYGVWZHFjgGljc093m11qWm1xYFtfamBmdnNrZmFubGJneYGJfXJZYWd+cZSKbVtgbHB0gHRubWttZ2trWFhdeHRqX1pveHhkc3ZwVFZYWlNlc3FvZH53dG10do1/k5uedmtyeYSNb3OllJRZgn6do1uCjI+EhXR1hX6ChopqjIV9h1aXipR/gGp5hohqY2BxgXuAdXJ5dHJ2fIZ8d2xpdG93Z2JlYXpmbm1uboB1cm9nfXxzZGh5aWx3g3JqXmBkdHBlX2V3dnSMbndvcnh+bm9aX19aX11cZ15RVllibHFgXomJb2Zsj3yLj352f2pdaWpreoJ6aWR0hI54aHFwZ3F0Z2ZiaWx1gHt1b36Tj4x4c3NzdXt8e3dvem91ZV9YYFxYXmBSXmVzeGd0g4F1cHNqbFhbY3Bgam12WmNsdXFrcXhzZ1prg3ltY2VfZmNlXmBZWW1qXl9ib4abh4mQi4Z6cFtfZHFmZWV2fm5rcm1zeohhaJeCkHt6hnyPgVlUfJ+Vg3V6dYWMgIJ1SU6LaVlteHp/fHZsaGh6d3ZyentydnWBb3d9e2Z5cFVUT32ij4+GgXh3em6PgHR8jJFjXmWDT0aER3x9VoppQ0RQYGJpb4uNampcdHJfaFJOaoKCh597am1tbG1rfHBpZnhxRXVBRERyRYRPi3tuc3J3b3hrgomHe4ZydoSIgImMe3N3gHxrW4p+gpJLTJNXj4CFh3yJUEtzeGt0SlF8cl5lZ2poZn+BY2Z+R3pjV1FRWmNbX2VhYWtaWWtkeI2IhJyLb2tdYGxNSoJkW11iYWhvZ19qampjVGJfU0lRV1VYZWRzdV9WhYl7fIiHfIF4ZXabgXl6dW5sd315e25fgGBvYWBfZGVwfXt7goF+lpGOjY2BjJKLjJB2ipWkkW9vcIaGgY2alYp4gomThYRxfGhvjqKTiHpzeXqGfHiDiYd9fnVebop8f3ZvZ25/cIJdjml1cYp5fH6GkIl2e2dxZGdydnl4eYyJh3eDlXx8eW9xhHxvdXh6fVBgYp2Kfn2SgHlvcXp8cnF0ioeCaHRveldlOZBqZnVvZ3J6fZSQjJmUjJGPc3R8eHyGgYOJjZKQhoF4d4B6jIRtbnqAg3pzamd1hYd0dG9vbnFwcHBnbHtzeYB1f3V0fXt1fIN9hHh9hH+GhJyFfoZrZXd+n5aDc3p3gnxkfId7enZ5jX15dXRtgHqBeX18eoCYjHuJiH1zd3xzb2yJe315eIR+dXt8c3aBanmBlaKbkn96jIKChYqYu392fYKMkZCsqIiMjYp5g4mFk5SakYWUiIJ5eHB6g5WpnHt1d4GdkoB6dXx4g4JiYm10c215d15wgH2IinqBj4aZoXVvbX16fXt6cXZkcH5vgG5pdUx7l1ZsXnB2aGNncWVodG5nY2BubmZvf4mVj4dye36RhqyfgWxye32BiX55fXp9eYCDb3BzjIqAdG6Bh4FpeIB9ZmpvdGp6hoF7bYl9d29vbX1ufYiIY1thaXd9YmeXhYhRdHGPlFJ1fX92dWpoeHBzdHpef357hVaZi5WBgG18iIhpYF1tf3mAdnN6dXN3foiDgnp4f3qEd3WAd5J5gHt+fYx/fHtzh4V8cnSEd3yIkYB+cnJ1hIF2cHaIhoKVeXxzdXmCdXdqc3JqcnBud3BnbGlze3ppY42QdG1wkHyOkn11f2hba2xtfH94ZVtugo11Z29uY2dqWllTVFZcgGJcVmJ1c25hX2JkZ2xsbWlldWpuXVpRWVVUXWJWY2p5fmx7j4t8eX93e2Nlb3pmbm50WF9pdXVxdH15bFpqhYF2bXBtdnN1ampiYnRvY2FgbYebhIaMiYN2aFNWXGxfYGBye2dlbWpteYZYVYd4hnBsdW18b0tJaId+aF1jYHN5BHp6e3u0egR7e3p7lXqCe5J6CXt6e3t7ent6e596BHt7enuGeoJ7hHqCe416AXuceoJ7/3qFegF7qHqDe5R6A3t7fP968noEe3p6e916AXuEegF7kXoBe/9673qCe4l6gnuJegICBACA++jr+Pr1/tfS2czE0LKwudbL6enhvcO8ucG+uMPJwsC/t6ex0M7P0szFuLW0zN+AgtDQyffqg4yPhM7k+dvCzNfu1crDutLFvc2/v82/2cnZy9m+prnA3M27s7GprsfZ0sbU9I7An/j9+veA/N/aydrfz9DXwczSy9XazMzp1caA4N/SwsjPv7/Lw8a6sKup4N3Er8DCzr7VzLze397n6eHZ183PzMG1z9mCiv3Su6+UlpaUm6u9squip7TH7b+ztLq12Mm0qqKnsK2xxqiousvG5d/Uq6zd4dfVvsbd7PLc48nAv7W9waqnuMXHvr63kvrGzdnkybrO18/HxrGhl6qAw52MmLu8vMbR29PVyMnF1NjEyre4wLm+trK+wby6r7CjsszK2dS8xsq+zs2+t723wMu8vci9pZ+yt6y4uryrpby2u8TMtbfDwsHJy8HHxbK7vMrQzLrLyOjxhdzIoaqcpJqZsaqrsb/GqK24sLHFw7C6uMrN+IGA/9zY0ruxwaCAqbO7vtPTv7PRyMK8v9D5o8zDq6+cmpmVm6a3ub3Ow7/JwbzCycbCztPZzuHj18vTwL/Cx7+/vLWrrbWuxbq4q6u+wMjCs6mtt7nAr62tq6ysx8HHwM6/w8bYy765y9DFsKubmp6XmaCep7G/v6WvrqKcqqi0uaSupL6srqqwsqSAtLq3vL+8y8zM1M/Q1MLBsKy3z9DBx9nOxcjOz8m8xt7Q6eHj7Ov1z7LEvsvOxcXAurXFwsHBysvKzsHHvMm+tqzC182kosLLvaKps6+3466vxs/Y3M62sKe5wry0ucXAuLfJvryxorSN//nTyMrM2t/cysWxvLi+vMDYz7re392At8Kvz+ru59XMxLevv7akp56Xk6Owmr6voKOpora0wc3aoq6839vKurGuxMPCyLq8z9HBtLe9vMPJzt3fzsjV0NPEvLitusjSztvZ4dPa2+TIxLazw8HP17uutc/hyMjJ4cu8t8DL2u7TwaOysK6yvL++yLrE6OHa3eTj1sfFztGAy+L17c/D0s7X0tK9vLe4w77DtrO1urG7s7nFtrG8zd3bz82A2sXI1s+5taqlyL3Ms7mtpb3Eu6+or6OprrStqKi5sK6ut7O5sKacmpysoqa5rbC1sLnK0+PCsb7GxrrT29nbubWvrrPS0L/JzcvDwdeqrLKvsKSXjYuXqrisr9OA4tzHuc+ytLq2yM/ezNXCtbrKvrmzuLu/s7WyrrO8wrm8tb+8s7m2vb6mpLKtppeZi56RoKSpq6q1t66uqLzEurG0ube1ws7NrLLFxr+zqsf67NjCurSptb6tp6SkrrbA1NzOy9DLysDRysS+xsXPtL/Y4MK+sdmA9PHZ19bVxeaAlIKDkJOSmn9+gnl0f2tncYqCm5mYe396eYOCgIuRj4qGgneAm5mcnJSMf3t4g5BRUXp2cZGJTVZYToCWp5SAiZKgiYN+dYZ+dX91dHpzgHJ9doRzYXB0i4NzcHFtc4SKg3F2ilVwW4yHhoZLl31+coCFdXZ8bHh9eoWIe3mTg3iAkJWHdXp9a299eHRoXFpejoZxX25sc2ZxbWeDhYmNj4eAhHR6emtnfoVVVp+Eb2VaW1hfZG+BeXBsc3mErYRzcnl9l4xzamdtcmlteWBlc4aCl46CZmOLjYyGdnWSmpaRjXd0c2ptdl9icHR7dHFiUItiZ3l+bFxxgXdveGdcUmSAfFdIVGRmZ2ttdGlqYGJbbG9cYVRTXFpgWFVfYmVeVlVMXW1ygn9rdXVqeHZra29pc39tbXJoVE1ZW1NbZGlgWG9jX2RoV1xqbW93fXR7emdpZ3VzcmVzcYWMUHVjT15UWVlcaV1YXWNoT1VcWmdodWdmZW92mE9Pn4NxZ1pWZ02AUlRfZWp3cVxvZ2RkaoGYZYFmVlpNTE5KUFhiZmR0Z19mXllbYF9ZZGJhYGxqZFxoWVpdXlpYWlhVVVlQYFVUTU5gY2pkVlBTYWFnWldYWFdSZVpeUFRYWFZhU0lQV1tfTEdGS05QVltVXGNzbFtpZVtXZV5zZ1BZWGpbX1heXFaAXWBdY2hjdHZ4gXp/hnl+ZVxjcXFkYG5iVF1eW19ZXWpecXF7iIudfWJ4cmt1aW5sXV1sb3JobnBva2BlWF5XWFJfdXFRUWx2aFVWXllfglFPX2RjbGtaV01cY11aaHBua2yBfX5xanVjt6iHgYWEi4+DcGdXYlpgX2JybV99gHuAXWNVbYCIgXNtc29idHFiY19bW2RvX35sX15fVWBaZXJ+TFFefoN4b2lkcm9yeGhjb2tkXl1mXmVucYCGeGlzbm5jY2NZYm50cYB+hnyBiJZ/dG5tenyMj3VpbH+KdXN3inhsZWt4hpiFc15rZWVtfX+AgnZ5l5aCiIyMgnd1e36AdYyZjnRveXZ/e31vcm9zf36Aenl1eG9xbW56bWZmd4WBc3VThXV7jIl0dmxngHeDaWxeWW1xamJhbGVrbXNraWp0aWxudnZ2ZmBdXlldV1ptYGNtZmx3eoVrXWl0d2x3foCBY2FfYmiDhHOCiIJ8eI5qb3FucWpdVlNaaXNpbpCAm6CPhZeBfYB4iZCTh455aWx2cnJsb2xyZmReWl9nbWVqZ29uamxscHRfXWhmXlFTSllNWF9fZWJqb2pnZXh/eGltamlkbHZwWFxtb2xhXXOil4h1cGtjandnYGBfZmpveXt1cXh2d2x2c21rdXd9aXKGiHVzbIdRmZSCgHt6cIaAiHV5h4uNk3Z0dm5reGJga4N+kZGTdHhxb3Z0c36DfXRwZVhgf3x/gHNtX11ca3lFQ11YVnZtPUdIPF53inZhaXJ+bWpmX3RuY2xgYWpebmBtZnRnVWVthnxraWhfY3V/eGhugk1WToCBgIJIjnd7coWIeX2EdoSFfIGGdHWKeGqAfoJ5cHZ3aG57c21jXFtekZCBbHh2d291amF/goSFhnt3fXBycWJedH5OTItwZFxQUE1XW2p4bGReYWRukXJlY2ZogXhhWlZXXlRWYFBXY3F0hn1tVVZ2e3x7a2iEi4V6eGpna2BjaVdgc3Z5f3ptV5xxfouThW+FkoaIkH9wZHWAkG1dZ3x9fYCDjYGBe4B+jZaEj319ioeOhH+Ih4V9eHVodoeNnZqEjJCEjouDf4J6g46DgomFcGx7eXB5foR6b4J4dHqAcXN/fnt/hHyFhHJ2doGDf3KAf5SeWpN/aXNoamhpfHNsdXyIc3aAgoqOk35/fICFmE5NlXxxa2Jkd2CAaG94eIGKgnKFe3h0doyeXlh4cHRkZGZmbneEh4SUhoCHf3l8goB7hYWFhpGNiICKfHx9f3l4enZwcHZtf3RxZ2d3eIF6bGJjcHJ6b2prampmenN5bXZ5enOGfG13g4aFdW9qbnBxd398f4WRjHmAfXBveHKDgXF7d4t0dnZ8eW6AcXNydn11gYB9gnZ2fXR3ZF5nen50c4R/cnx/d3dyfJKImpOYoJumiHGGh4eQjpKRhIGOkYuFj46LjYKJe4J5eXSDlI1rboqVhnF1f3p+oXFyg4mEiId0cWh2enJrc3h2cXOGgYByanZfsqeKjJORmp6Uf3lpc3F3d3mJgXKRlpCAbXJjfZGXkYR7fHZsfnlpamZgXGZxY4JyZmdsYm5pdIGPXWVzk5WIfHVvgH9/hHVygYF7dnZ8eH6GhpWYh3eAfIB3dHNpcn6DgIuGkIGGiZd9eWpncHF9g2daXXB5aWhvg3VnYmx3hpeIcltlXl5kcnZ3e29zkpGBh42MgXd1eXuAd4uZknlwd3J5cndscXF1gX1/eXh3fHJ1cHKBd3N2hpKMf4JYkYCCko99fnVxjoOOd31zbn2CfHZ1gHh7fIB5dHSAdXR0fH2Ee3RvbWdsZm6DcXJ7c3iBgotsX2hwdGt8f3p7XlpWWWKAf3ODhX52b4ZhZmppbmZYTUlOW2JWW3mAhIZ2bn1pZmdgcHmDdHtpXmBsamljZ2dsYmFeXGNveHN6dn17cnV2gIJqZnNvYlVYTmBTYGZqb2t0fXd0boGLg3NzcHBudH54X2V6eHNmYHmon4t4bWVcY2xdU1JQVl1kcndybnV3d2x7dnBvdXZ8ZWt/g2xoX3lKjopycGtpYnuveoJ7hXqEe6p6g3uEegF7vXqCe8R6AXvqegF7m3qCe5d6gnv/eth6AXv/ert6AXv/etR6AXuIegICBACA28/T2c7P1cO1vLzGxLnk4f7b0Mrl27bDuL27yLnUq8DLz8+7tbnExsbN1c7Y6djciYmH5N6BgfyHgPmcjc+4p7XK4LbNxrzN39rN0eHevLbZ1czZsZ+krqSgw720qcXZt7jRxe7w6ujb8NO5yObe48ayycPKxtbk1sjEvMDphIGAvcD45d3b1725wMCdhp6WhoycucPAurCE/NTd4uzd7cLFyri0rrXJzdvbkLqZ4aKtn53E1rLMy7K5zrilnJqfrqmgtK6un5GSrba9uLvMzcXc6vLbqbDJyMbLycm/ttzvzbbjtbzK07ekorTDwsXJztWG4vHQxsbDtP2soaeqlJiAnKO0rb6vsre8vs7dv5KayMjk0tfCvLGzop6oqJ2mta2omJOqxsO0taqtpanMytzCxLKyoK6kqKOrs6+UqquossWnp73Nu625uLW/v8OktcHAtba7ysG6zN3i4sHI5MrBrre0v7LDwNDRwbi0wbKwwL/IyPLxgN7p+Niuw8m3vbeArqy2rq+ytqmvrrjXyMCysbO5p6y6pqnAu9fM0Li1u8DNvMXA4LbGxMHIwdHd0e/o0KKzrb64yrKyrbfCrqeuyMrCzb20y7C3urS2v8XIzM7O2M7Q1tvWy8rByc7QrL7HssKvqKHB38a7xLGor7u7vLK2qay7pKGnrKCcoK2rqrOAtbC6uL67w8jMyO3Mzr+yxrfFxMbEurjFw8jOx8TK0bi+xN/o99jTw8a+vdPR0dLK3snfy8DUzcXc3sXV0sfHxrG1s7e3sbK/r7LEwaultbzbz8nNrMrDwMbFyszBwcC9zsfi4dXBvMrIwb/f89jOvsTDrLCts7DNxbm9w8G8ubOAu8vQtba4t7S3tLattLStrZaRrJympqWzsL/S1eLY3/eRz7Gzu8TJw73Ayb++wt3Szbepqqyzs7i2t7rO9dO/n7G/wrvDwLvg7+Pj1snIycnOyL64sr25rrq5zdnEvKyqv8nGwNjd4cLI1czAvcbS0MXQvrOyx9XDxsa/u9fI1deA2trcx9/S49nZw73Lu7DCx8/aza2prbG1qKG5raPxzeXM47/Y1Lemrb+rvayfo56qt768vb+8u6GaqqW6scK9uryzn6K6xMSyt8DFqZ2SmK28v8DDubC0utDy8vvb2sS8wdDR4fjPksDCyKvJzrvDu7TFwsG+xMvGp5+3y77CwreAxcrLvsXArsnErqWsn67Dzbu5vLzHwcC6vKSyws3L08e4v7jBvL+0t7Kru6qlmZqOiI2Lj5mhx7Cmqri4u6Wrrraxr56xwce2xcTG1eTX3IHe4tLCrtLUysC7trS2v8jjxsvIwMzG29O+zcHGxsXFxtTR3Ofh2PWFgeTliuz73OOAgnt/hXuBhndqcXF7eHKUl66PiIKelXiFeoSCjIOfe4uUnpyMgoGGhoOGiomOmIeIVllXjIRQUp5VT5hkXH9yZneLnH2QiH2JkYh4d4aBa2mBgHqGZFhgbmZihHpqY3eBa2h0Z399fYN/mH5mb4iEiG9gcm9ycn+KenBubG6UWVaAd3agj4mKiHJscXBZS1pOQ0VXbnRzbGZdnoaTj5OHmX53d2xlYmZ4eX98XX5jj2NoXWaCknWHiXZ5g3BoZGFjdXluem1sZlldbnZ3cniGiIOVnpORX2Z4fH6DfXpvY3+Zb2qOanCAiHZjXWZ2dHZqYWlHd4pqY2FiYJpYWWFfUlWAVV1nZGtiY2FkZWl0XUpHZGFxbG9fXFVZTUhPTklSYV1USkNVb25kZV9jX16Af49xcGBgUVlSWVZZYWZPX2BYYW9WU2R2ZV9sbmtycW9VZnN0ZmlscGdkbH6CfmJqhnVxYG9veGNsYHBkZmNgaWBla3iBd5eQTX2DkXlRX2BUWleAVlJUUFNUXFdYU1t3bGVdXl9jUlhgT1Rua4F3e2dlaXJ3a2plelJeW1hcVWFwaYaEckpbWGVea1RSU1hiVE9QaGVdbVxXcVpeXl5maGhlZWVneGdjZlxfYlpYXV5kTVBaX2NRWFN2mYNzdmlfXWZ2d2ZmYWRwZFtdXldVV2ZZXF6Aa2NkYmNkaWhvcJx7emlfcmJwcnBpYV5kYl5gY1ZecFpcYHJ1jnh3bHZwa3RzaXFxbWl6dHBuamp3b2BvbWNjWVZcYF1bVlRjVlpualdQXWR9bGVpTGBXU1xhaW1kY2dpc3iSkop6eoqIhYKhsJmLenZ1YFxYWVhyaFZiYmBjYmGAb3t4ZGVlY2BjY3FraWtoaFlWa2BnZGFuaGx9e4yAhZJZb1VbZWZsZmVpbWhhYnt2dmddXmNoZGVdX11umHZlSVxmamdwaGd+jIV/d290dXd+fHRybnh0b3h8kJB/dGZidnp4a3yCinV4hn90b3eDgXyCdmtugJGDhYV6cYJ1f36Ae3Z3ZHltgoGDdG58cmp6foeUknJrbnV3cWl7bmSWeY56iG2Gg25oa3podGZdZF9lbmxra2xvdWVhamR0b4B6bm5tYGFxcG5lcoKCZFpSVmNtcnR6dGprb32WkZ6HgG1pbnV1hJJ5Wm50emaFin+FfXSAeXh5foGAbGeAi31/f2+Ae4SBeIF8coqHcWhuXmlzeGlqbG93cnVxcV5oc3l0enBfaGZyb3JlZ2NfaWBcWFxWUFNQUlpge2xmZG9wbV1fYmhfW1FebHFhZ2pudYJ6fkyDg3xtYH59c2xoYFtgZW+CbXJ1b3p6iX5seGxwdnJxcHd0f4mDfZRRT4aGVo2ag4mAeHF3gXiBg3FkaGh3cWmOkJ2DgHqYi2l3a3JwfHOQaXd/hoFvYmFoaGRlaGZteGVmRkhDZFo9QHhBPXdQS2NWS1tyhmN5cGd0fXdmZHVxVVNucGx2U0dRXVVSdm9hWXSAZ2VuXHZvbXZyhm5ZaIOCiHJogoGCfoaUh3lzamiETUuAbW6QhIOLh29pc3FbSlxWTlFfdn58dGRRlYGJhYFzh3Btb2JbWl5yb3JvTFpTflZYUFZ0e2R1eGZkbldRT1BQWlxVX1lYUkNIV19fYGt0cm99jH93UVhmbHF6dG5lVnF+Y1+DZ2d2fWlgWGN2eHlya3NOiph4c25taZ5oaG9uYmOAaXOCeYF3enJ4en6GcVZWgH+UjJB/fXd6a2hwbmdxfnhwZFxxi4uAf3d+dnOTkqGKjX19bHZzfHh+g4VrfHt1fo1zb32LeW95e3d+fHxic319b3J1fHR0gZWXlXd+l4SAcXt8hXqBeoeDhoF7hH5+hoeJhaObVYWLknhXZ2hlcHGAb21zcXF0enJzbXGKgHdxdniAcHaAdHeOjKSbn4iFipGVh4WBmG18fHt+dYCRi6qnk2p6dYJ7iHJvbXJ8amVof393hHNsg2xwcG91eXt7fX16iH5+hX2Agnhzf35+anV8e4NzdXWRs56SmYR6eoONh3l5dHWAcm94fXZ1cnlxdHaAfHBxcG9wd3B1c5R3dmdhd2l1dnd1bW10d3h6f3N5iXd7gJSVpo6Mf4qBho+Tk5mUm5GhnYqRjoiVk36JiIJ8enF0eHh3cHF/dHiKhHJte4Oci4OFaXxzcXh8gIF0dHd1gIKYlIt8fIyLioWhrZmSiYqHc3RxdnKMg3J3d3Z3eHSAf4qJd3p7d3d5c3tzdHdxcF9fdGZtbGx4cnWEg5KHkJ1chWxxeHp9dXByd3Juc5GPjHhsb3N3dndyc29/pol7YG94eHJ7dnaNmJCIf3V2eXl/fnRvanNvaXJxhYl3bWNhdnt3a3+FjXR2gn1vaXF/f3iAcmhpe4l7fX13cIR5hYWAgn5+aXtuf3t9bml5cGl4fYOQjG1pbXF1cGl+dW6lipuHlXuTj3lwc4Fxf3BmcW53goF6fH+CiXl0fHaDe4mDfHt3a2x+hYN4go6NcWhiZnF7fHp8dW1tb3qTkJ2EfmZiZ3JueodwVWpuclt5gXmBeXB8dnZ2eoB+Zl1xem1ubmCAcHVvaG9sX3RvXFNWSFRgZ1peYWRraGlmaVdib3p4gXdrdXSCgIN3d3RueGxmYGRcVlhVV2Boint1c319fGxvc3tzcWJvfYBrcnFxfYl9f0uBhHVnV3p4bWNdV1VZY22Ea3FyanV2iHxodWpvcW9tbnJueYF9d41KSnp4UYCOeX6weg17e3t6ent7ent7ent7wXqCe5d6AXuSeoN7xHoBe5t6gnvmegF7/3r/eqx6AXv/esZ6AXvoegF7p3oFe3t6enuEegICBACA4t/mgNfAuKC3w762uL+4tKugoLvHwrezvMW1uc3LzcnIzPTx07mwsM/H9YLc3u7ni6Gl/4GP7ez68uTrgdjp07yio8PI0N7wiOXljpjQz9bs7uz60tbdytPY96GbpcOKyprMz8utwcXB0Nvg1Mvht8CztcfRzsLKzsvX+ILQsc6AntqvguS+wrC1q6uwoayYk4KNpKm0s6iwoa+ti+/FysXI0tvCubvB6+Ljj4S+v9C8yNvEtMPe2dni2rCxp52Pm6mklauss6qcm6rG08vSzMHC57ugj+CAzKy1wO3c08nGxrewuuvly4DOvs2/xbG+y9P72dPk1L+6woXJssCzmpyAoaq1s7mws7G+tcXI483JwNLSwsbHr6i3vKuyrKG2xritp6WsnKurn6imtKu3qZ65xcK3qqugqrm4t7arxcSytKS0taKzuMK+zryzsey7q6m4rru/uce2rLTQyM/BsaetmJ69vMjRzMPKyqut08W+uLOtqLnr+8DM1PfiyM/++sGAqrSyubG4s7G3ucuysr6rs8LHvcmzu7uypZyyuLq20sXDxtHU1KCxxbrm3uXu4JyQ08vFtrbSx8m516eqp7PCzd7m4OTS0tjHxsnJvr7Ax9Xa5t3i8OblysLfxsS/rLTYt6ypvb3HubetsqOon6e1oKSvsbqtsKmjoZmnnp+ptLiAs6i408jN0szEwbTeysXBvbi+zL6s3/LRzdjMybO70MrT1dHV3eP52cnLwM/Hy8O4087CycHK09HF27+sxL3Byr2zrbOxxsCxqbu+s7yuv8jA0r/PvsHHwLbB2f3xuaLLydza1s3OsbzLxMrMzMzHr6epp52bkJiorazBztG+srKAsK+tsq+orrS8vratpp6hqMDHt7++sbjBucTZ3dXN2srP5oCwt7rHzMXD4OTDrrvAub3KwKahwcfKucLGzrbCzuLRv9TR6uD68NveycO/w8W/vcPP2tveycHO6uf/6u3OzMHBzOHe59nWzNDTycbSw9+ktrLEt7fKy9Li6tHQ1N2A4e6A1cLMwcfCsre0tr+yu8TG6dG43dfWvsvPu9Ta0cfAt8ayw7aunbq6t6ano623ub65rqexrK26wKijqrGuqsfbybjCvKiyvuHItKy7vsu/x8LCubKmqqe1t7/Gub/ExcjQzt/y08HU1bS5srCzqq27ztS9vsLJx8S2sbC8v9OA09LIsqytsKmtqZmkorDIyMbBt67OydTSwMmwvrmsrrS1tbS9vLvJrK6opqCPk5iiv72llJSqqJqPx7mqp7ixxMO6taysoKWqtsG7tbu3vdTB4fnjzM38/uvR1Obmg5Tc7c+8xMPNwb67w7m7ub63ur3d1tnM0ufqys7j4uDayNCAiIiOUoByb11wfHR1foOBfXlwb4aNi4F9gI6Cf4+PkIqKj7WtlYB5douIpVeKh4uFVGNojklXhIyXlY+XVJGhl4NtbYSAg4qVWI2SX2iBe4KWmJWhgo6ReoGFkFpVWWtPdFdobXFidnpzfYeFfneMam9hX2hzc251gH2HqVuHaYWAaodpWZR7gG5xa3BuYWpaVklQXWVxc2t1aXBtYKiGgX6BgIVybGxvkY2WYVR3eYR5jJeCdoOVl5STlXR1amNcbnpuW297e3NkYm5/j4WLiYB/jG1ZTI5TfWBua5eFdnBoamRXapKRhV6Acnh2eF5bW2WBcWl2a1hYaklvZXVmWlqAWmRrZGhjY2FpaGlndm1oZXl7bXBvWFVjZlhZUkpeaGBXU1VZTFlYUFpZYltiVE9oc3BmWmNaYXV1dnJnfHZlZFRkZ1ZkaG9pcmRcYZhvamh2ZW1tZ2lfVV91b3NpXldcSFBzbnl9cGZqZFRad3BxaF5gWF+Lk2NscJF+ZGmUjVuAR11XWVJcUlVgX3BgYXBlaGxsZGxZXmFZUElZY2lif3N0c3t8fExca16DfoiLeGtfeXRpXVx0amlYdklKRlBgaX6HhYZ4dX1qZWdjYGNnbnh4g396hXZqamN6a2BVUFJmWltQX2h3ZWdhYVRiXVZoZGlmZHVqaGpeWE1ZWllkZmqAY1twgHlxfG9nZ12Jcm1oZl5ncmFReY1wbGxmZlBWZ2RqbWlsc36TeHF1ZW1mYGNcbHFdamZla3BgaWFMX19nZ2dfXVtYaF1USFNjWlxSXmdmd2NuX11jXVNWa4yHYU9ta396gHh/ZXKBfISIiIeDaGBnZF1XUFZhV1xqcHdxZmaAYmdtbWdcX2VycGpnYFlaXXR7a21sYWltaG99fm9oe2xugkpPU1pqcWdieHxkWGtxc3uFemVdeHRqWWBna19xeIx9ZXV3jH+Rhnh+bmxtbXFtaHN8h4eNfHaDnZigkZN6enNyfo2JkIqKg4qKgH2FfZ1neHeLenaGiIeSmHt2eX6Af4pMdWl4b3d7dHl2dnlsdXp6nIZtjouNdoKBcoWAe3dxZ3poc21kV3Bub2BjYGlydnduZGRxbGx3g2toaGRna4OOdWRwcmJze5x+bGZpa3xvdnRycXRnaGVrZ2hoXmVwcnF8e4aRe26DhWtzb25xbG93iYhvcHiBfn14d3h8eoyAiIV7aWZnaWNmYlReXWd+gX12aGWEgIOAbnVneXVra29taWZvb29+ZWFcWVRJTFNddXZkWlloZ1tOeG9jYG1te3htZ1xfU1NWX2BbU1dUWGpZdYd6ZmaNkYFqbn2BTFl3hHBhZ2lwbm1tbmRubXJubm2HfoN1hIuPc3GEg4R9c32AenmDTHVmX0tdaGVlbXdzb2hdW3V/fW9pbHZnZnZ2dnFtcpWKb1ZNTGFfe0NjX2dfQU5PZDRAYml2dnd8RHSIfWhPUm9scnuFT3t+TVBnZGuBhICHcH2Bb3h/jlVTWG5NYElaXmBQYmhjbXt9enaTc3pua3WAgnyEhoGEnVJ5XnyAU3JeV5F7f290cXFvZ3NlYFZbaWxxcW1vXmVZU4pudG1wcnhpZGVnhHp7Q0BoanRpeIdtXm6Ag3x3b1hgWktAUV1QQFBbXFFHR1FofHRzdGVqdVRFOXpHbVNgYoh2a2NdZl5YZImEeVF9bHJzcV1eYGqLe25+cl1bb0h4bH11Y2SAaniDeXp4d254d3x5i4F/fJCVh4qJdXJ+hHV4cmd7iIJ4dHJ2aXR1bnh5g3iBdm2FkpCFen93gpaRkoyEl5OAgHF9gW56fYV+h3pxcaF6cG96bHd4dnxyZ2+Gf4N4bWZsWGB9fIeQg32Cg3RxjYSJf3VwaHKbonR4fJeFdHqgpH2Aant4e3d/dXF6d4V0c35yd4KEfYl1foN+dm2Ahoh+lImIiJKTk2N3iHuel6KnlmZnkY+GdneQhYNxjWBhXml4gZKbmJ2Mi498eHl7dnh9hZCNlZOSpJCIg32Ng35waG6Ic25leICPgYh/gHR9c3B6cHRzcX93d3d0dW53dW90d32Ad2p6jIJ7hHtxb2aKe3VycWx0fm9ki56IiIqEg2tyg36FiIOFjJirkIaDgoaHiImCl5WEk4OEkYx7jX5oenl6fHdvbm9ufnZvaHV+d3hteYKBj36LeHd9d2tvgaCdc15/fYyGiICFbXuLhIyPjpGKdG91d3FvaXF9dnWCi419c3aAc3l7enx3dnuEg3x3b2dobIKFe394bHN4cXiFiIB7j4SFl1RiZmh1e3FthoxzZ3h/fYSNgW1ngYJ+bnh/iHWAg5OFcIGDlYiXjn+DdHFwcnNwbHR7hoOJeHN+mJShjpF3eXRye4qHjYeGf4aHfHiEepdgcnCDdXGBhYaSmH93en6Ago5NeWx2bHRxanBtcHVocHZ1lYBph4GDb3x+coiNhH57dYl4hn5zZnx7eGxvbXaBg4WAd3WBfHuEkHh1eHd0dIqUg3WAfm98gZuGeHJ4cX9zfXh1b2xfYmBnYmFgWGBnZ2pzdn+IcmSBgWNrZ2dubXF5jI11dHp+d3dwbWtvbXyAenZtXFhYXFlaVkVOTFlubmthVFBubXd2ZGtabWxlZmxraml1dnqKcnFpZV9TWGFrg4ZxY2R1dWdYhXhraXp7i4mAeGxtY2RncHVtZGZeYXNfeYp7YGGHiXhjZ3V6SFNzg25fZmlxaWpoa2JsaGxmZWJ6dHpteoSFaGZ6enlxZW4Eenp6e6d6AXuEegZ7e3t6e3uGegF7i3oFe3p6e3uSeoN7mnoIe3p6ent6enuXegF7jnqCe6p6AXuQegF7kXoBe/96tXqCe/9683oBe996AXv/ett6gnuhegICBACA/u7i1er96qi3hLHivKuwtrift7y8w8TD1c64tNrq8oOJ6c7R0cXZ7ITJ6efTjPSFsKCYgYGJ5+TVxMWstK3AxrfJvrjB0dTf3OHU9f69wL3pzOGQjfHm3MK3w87iyebKgYWUl7TZ5fXaysOyrrC3ueWB3K6ts8fNxrvFr8KWpbqAuq2kqK2zpbTAx7KilYqbn5+xwLSbnqC2tMSgr8i1tbrEw8bJ5vuG2crCsKmyvb+54MnHvbK80uboxK6zrqSFndO7srCpoKa7vLO+1MnSr7C60tC9xujv/MWt3fbY6IqH3dTTydmD9IXh3t/W8dHg3ePc2fyG5dPPw8XN4urCqaiAr6mx5/Tpu8zlusTH4c/N1d7M0cjOxLLEt7Gvxbyxvsm5r7W8pqefrbHMrpq1xbyyva+2yr67wcyyp5/Aurm+rqSoq6fEzsTrxq2VpLatv8XJvKyptbbP34HXw8/W0pifsq3RvcG8vcS5zcm/z7+4p7e6tMa+w8O/ydbM44XfwruApbnBsrG0tLu5ubO/vK7Cy9TAsq3Ev7vBrcLHv8W/z9bQ0NTd5sjcy8rj8/Lp44Dt5czB1erb18/E0L+ssre5tsrEyNjc4uHYwL2wubK3tr7d2cbRwtLd09Dd0rm7vLzQyrXBvbuntqqprKmikZ+5tJqdqrS3paenppyxq5yqp7mArbOrpLe9zt2yvtDXu6umtaWms63B4oC3sLPJzMTW1Nrd0NHM1ue9yN7UvsbHzrqxxrTEtqm0u724vryps7iyv7u9uczTwsDGtsi/usG0t87Ouby/sMjQ0tbi+dnX6NO4s8Dj67G2u+fcpLSzrK/Bp7e7r6afrKOfp6q8ub38z6iAq66yoKLZoLGwtbGguLG7u8S5vL260MfS7NLLwtnFvdK0gY/kybfLwdzHxMKwu7uhsrrcv73Eysm/ub7Fzs/CssPW0MXa1dDq4O3j6N3f1cHBy9DS4/e0zcbK5droxbzC3szHxcjc0r69x8LAtdm0ubCmpqGouK65vr7Qw83a5fqA6NbV1MbEtrOy0NLLyci2vLvExsPEsbC6try4ztPTwcHDt7u5v7GnnKasrqatqLi1wL20qK+yr66jpJqftriutK6ks7TBr7C2qq+wrLGnn7jvsLKsq6+2ra67ubm329TLztLL5NfGsrirqLfCqL/Exr62wc/Ctrq8uL60t7elps+Att3TzLy9sq6UmrKovMDN49jNy7rDy+O/taawsubArrDBx+y/xcOzo5qnsra94ePVsa2roqmhmpqkn5+Yq7+qrq+yvLzHvKKdnaWzs8LIv9rt8e6C6dXY1OPp3OTb+/TRwMC7s77TytTq6tbQyMG5t8bCxrzKwMfN1MzS6dGG+YGAp5+Xj6KxqHuIZoStjXt+goRvgX9/goCAjYN0cpOfrWBhoY6MioCNm1t4kYx3VI5ObmRcTUpTjY+GgH5rdW16fXGBc295iY2Vjo+Bo6RtcGySfI5gXZqPh3Jlbnh/bYN3SUxRVWmBjpSHenZra2NpZ4pOgF5ganyBf3mDcYRha3qAe3Jrb3R4b3yKinNlXFhgZWJufXRjbXOAe5F0e4t7fnt3eHt4j55QjoRwaWZqdoN/nY2OfnV9kJidiXZ4cXBabZh5d35wZ3CHg3WAjIOTbGtsfYF1c4qUm31iiZiDiVZSjXJ4cYJVpVmLh4WGkG5yaXFucYZGeWhoaWV1iZlzZ2eAX2Rmh5iLY3KFZ2NseHh4goF1dXFzcGJxZ2JZbWVbZXFjWGBjUVZTYWd8Yk1lc2pjcGNuiH1+fIduZVx2cXF5aFlYVlJjZ2WIc19QYG5leICDdGFYX1txiFaCdnh9dkZNY2CDbXBrZWBgaWNre2xlXWVvandrbWRfYm9qelN8ZlaARVZpXFNUV1pmaWZsbF9vdX5uZFtuamRhUVxlYWlmdnx5c3mBjG6HdXGKlpuOg02Og2tdb4Jybmhha2FRVFpaWWljZHB1e3Z0Yl5eaFxgXmp6e29zZW9xbnd9cF1XVGVsYV9rWlpOY1haX15aUGFsZl9iZGhzYWNlYVFlXFheW2mAYGdcWGhsfIpka3+AZldSXk9OVVFceUdOR0lYWlVlYmZyZ2tocoRjaX91YGZtaGZdaF9mW1JVXF5YUl5TWmNfaGRoYHR0XGFiW11aXV9RWmh0XWBeUWFqYGh9lG1reG9fXmaJlFlcZZKKXm1va253X25yZlpUX1xRWmBsb3Kuj2mAbm1pZGSQWGRiZWBRY15lZm9rbGxkdmxufXBtZHlkWWVNS1eBZlVcV3FpbGxkb3Jfa3OQbGNwe3tmXF1pcXNjVWR5dGt1cHKGgYV8gn14cmNlaXJ2hpdnenl/m46Te292lImFgoWTiXh5goB/eZt2f3hxcGpyfnh4eX2IeHl+jJ2Al4OAgHJya2Zkfn57e31yeXSBfX9+c3F4dXdziYmIfHh4aHBvb2hdWGFlamJjYG1rdnt4a25vcHBqal9ddn5zcXFnZmx4aWl0a2VmYGFaUmSaaWliZGdycXB5dHNsgXp1fIJ7k4l5ZmdeYXB5YnR7fnlzgId7b21ra3Zubm5mbZOAeI2FgnRxaWVRVGhbbXJ+lI6Hinl9g5F0cmpxcZx2aGx7gKJ2fXxrX1dmcnZ7kZKIaWdmWl1YU1JdWVtUZXdhYmFmbm96aFRQUFJaVl9iWmR1eHhCcGFobnp/c353lYtqWlxYUmBybHiChHdzbm1iZW9vcGRwa252fXd4jHxVn1WAj4V9eYyZjF1oVGiTd2Nsbm9Zb25tcm9udmpWUHOAjUxKeGZjYFVkbENKY1xJPGA5UU1IOzxEdHRrZGNTX1pobWBxYFpidHiAeXtrioxSVlF1YnZUU4iCfmxjbXd+aHxmMTxBRVlygYh7b3BlaGdwb5ZVjmpsc4SDe3h6ZHNNVWaAbWdhZmtzbn2HhXRsYl9rbmpwfXZnaWx6anNVX3ZkZGFjZWtthZJEd25kW1VYY2tthnN1Z15mcHVyZV5gVlI+TWhZVVZORVBmYlpoeGx9UlZUXGdeXnSBiWpUeIhveEhLhHF2ZnJKjk6CfHt/gWp0bXV1doxJemtoZ15zipx+b2uAaXV4nKedc36PcnJ6hIGBjZGDiISJhnuKgXx2iH90f4p/dnyAb3NvfYSdgmyDk4uDjoOJoJORlJ6GfHSRjY6VhXp6eHGChYSjhGxcaXhufoeLfm5mbW6Dk1mIfYGHglhecW6Ne355dnNyf3+Di314dHuBdoN6gXh1e4mAjlyUeHOAZ3qHenJ1dnt+fnd9fW+Bho18cWqAgH+CcoKMgoeDk5aPiIyTmn+Vh4ifrbKnnFGqo4p7jZ+Pi4Z9hHlobHNzb353d4WKkIqHcGxteG5zdoGRkYOOgI+OipCUg3dvZHJ/d295a3BnfXN7dnRuY3GAdmptcneAcXRzdmp7dWpzbnuAcnhtZ3Z6iJVweIyNdmllcmRjbWVzkFZrZGZ3enKCf4ONf4J9hZl2f5ePfn6LiYR8jH2Ig3R1hYJ5d31ncHNtdHF0bX+Bb3N3cHRydHhocIOPdnp5anmAdnyNm36EkYdzcn+ZqW1xdqGZa3d5dHqEbn1/dGxpdnNpc3aBhYe1lXaAf319dXGgb3l2e3VleXF4e4F4eXtzg3yGmYqCeYp1a3piU16LdGNsaIV3dnVpdHdmdH6ae3N+iIl6dXR8fH9yZXSFf3R8d3iOiZKJiYN+dmprcnl8jJdne3h/lYmQdGpxjYR+fHqHgXFxend6cpJtdXBpamVteW9wcnaDdnyDjpuAlICBf3NzaGNieHt4d3ZrcG12dnp5a2lycHRziY2PgH1/dn5+gXpwanN2eHFzbnt5g4eFeHx9fX10dGxtho6AfnVsb3Z/b2xwbWppZ2pjW2yca2piYmZtZ2h0cG5ofXVvdXhyhnpvW11TVml3X3F5gH56hYqAdnR2bXVpaGdcXnqAZYF4cWNkXVpHTGBWZmhwg311c2Jpb3tgXVliZJNuXWFzfaN6goFxZFxteX6DmpyRcnNya21nYV9pZGReb4Fsbm1weXuGdmJdW1xlYGhsZnSFiIFJeGVqbXyCdH55mJBuWlxXUF5ya3mIiX16cGtdXGhmZ1tnYWFnbmlrfWpNikqJegF7lXqCe4d6AXuEegJ7eod7nXqCe4p6A3t8e496AXu0egF7t3qCe4V6A3t6e4x6AXvjegF7o3oBe7F6AXvnegF7/3qLeoJ7/3r/erJ6AXupegN7ensCAgQAgNjNx9LQ5Obi4d24q5idpZauv8C+1NDL0dPZ1NnxkIz6xtfYy+3Vu+zY4N7a2eqH8vbr2czk2c3Gx8zBu8jb09Lp7r7NyOna7vDn4vLp5Pz42N39/+rX5dv8srvP3Nba6sTGw8jOz9PEsqaqu6+v8OTTqrSpsKSwub/HyM/Jva6ggKGurKmgrbq7u5CAk52etLOiopuWmKC2samgrbamoayppqq8r667sLnTwrXk0KCdq6TGz7m8y8S4vrawoajJ5q7Y4cDx/KqmtMC2tr2cusm/zdnBvrS9y8/m4Nvv3Njbx6/U1OXr1M/lguzVyIDwgYPks9nr3s/Fzb/h9u7Y3rWrgMCuyPPry8y+zMu39N/zgIPYrcDHyvTmkd69tcenrqGwztWpnJ2ZpJ2lr6KtqLTFuKKctaustc7UwLm9tqK2nqOpqLbMrrqtwqOopaWip7S1tLu2u9HJxtHZz7ep4b7Lxs+x3J+h0LrIwbbBxK2rsr2knpyrrKiutrO3y+LVs7mrgK+5rLuvt6+1wLnL39nWysi4xtvfyM3dz9LIrbW4y72rxuje7NTVjoDujqKD/ISC9vHj3/P70sbj6dnp6NDBs7nF1NXJxtDZyM7Y1cCutMDExdLEutPNv829vsu/xM7autS7sbLOxMi6sK2tqqiepqOtmqSsurC8tbO9sKCkurCogMCtpKmsrqSrp7myxrKhmpqttryrtLjJw8nIxsLH39PR1bm+zc7CuaHH1PTXxse9uK28ybe50sS4tbW7uMLAr7Wyv7nGxcS4ucDV3dnZ2eG/ycrn89rP1N/v2N3U2ujt2Mu8u7HMw628sKyvnqyvvaq1yZWYq76/qLK2zdernImegK6xqL6xrKy4rauqtLy4sa7FxsHDucLOxcnW3uHa5ZeMgu/18f3PzMbC0bi0yM/aycjD0fbP3di6v7G0ur3QzMrC2da+vMjV08rFxb+/yLqvzL3Ly8fHyMnJ0MbJ7e7N0da+ur3FsLC3t7fBwcLCu7+0tKGlqqWtsaWtvdzKz9XYgNDGx93S072vvLfFucbGyNXRv73Av8nCtbu4qKiuuse5y8DE0LycoKaltNqyprvGxK64vLSupKKjq6GWl6aipZmbnaWv29O8tqWln6idnZSRoZmZssO6w7quu7i/t7y8tLrl0szSxLjFs7+wv7y0sbC1uZ2luLGin5OZqratvLa3gLKuoq7C3dPX4uGmrr67xdHUxcjQzcW4s9fK38W6xc2+xL68sq+lraermJehpOTfzMu7w7/H0bDFucOuoK7BxsO2u7rE08GxqpywurqzxtHV5YTw69bj0tfk8unr+drQ287Ltbi/ua/MzNnV2eL23t7S2Pb1/vHp9OLbz9jQzMvNgI2EhY+NoaeoqaSFdmlqcWN4iYiCmZKOlJaXlJmvZ2Kzh5STjaWQeKCVmY6EgopWlpqRhXqMhHhzcnh2cX+MiIqboXaDfpCNnaGXk6GamaakhYelo5iBi4CYY25zf3+ElHl7d3d9gYR9b2Znb2BcmJGJZnBnaWFve4GMk5mThXtxgHJ8em9reISDhVtOXmJkdHdqa2dpbnKDgH51fYJ4cHFsaWZvZWZzcXOBemyWhmlndHGRmICAhnx4hHlzZ3Kat3SKlo+xuXd1gYR9eH5jeHtzeYV4cmFncoGckISYe3Z2b2BvfISOfoKKUYl7ckiAREVuVG90bGZhaWd/m5WNg3BsgG9meo2PdG5pcXJXj3yPUFeBXGBqcZOJYIVmXnNcXlRjeH9jU1ZVXlxfZVZaXGN4cVxZbmlnb46SfHd/dl9vWVtZVGJzYWhebldaW19dYmhlY2xnaHdybnl+dmZekmx3d35qlV1ee21tZ2Real9eZm5eTlNaWFRXWFdXbIl4V11VgFFbTWFcYFZeZmN4h4OCdXJmcYB7Z2t5bW5nU2Bha2RZcYyFk3uAX06PXG1TlU1MjYh5dIqSbmOAjYOQk39uXWFod3JpanV7bnB4fnFcYnJ0bHZtY3RwWFplX2piYmZxW2hVWVpsaG1pXVteYGBcaFZgYGxoc213bGlvYFFTZWFZgGxgWV9kZFpeX21qfGdZVFBfZGhXXmBsZWhlZGBnfXBudFlebXBhW0lqdIxzcWpwY1xoc2RkeWxcXFFeZHJvYGJfdV9uamFSXGBveHh7eYFoaG12gGpjYG5tZnNmb3yDfHFnWFl0a11vZWFgVWFmel9qgVNTXWhsWWJph4hoW0hggHBya3Vta2BlXFxbY2tsZGFtbWRkXWVvamlyeX55gFJQRnaAhIVrbGpoeWVecXmBb3Bse6Z6fXdocF1YVldlbmpjc3ViZW5zcmxraWVgY1lRaVtpbGxrb3N5g4KFmqKMi5qFgoeNdnV6fHh9fXx8dnx4em5vc211eGlweZeCgIKCgHtwbntydmRaa2l1aXR2eoGAcnN2dIF9bXVwZGhocnpxgXh9hnRcXWRncJduZm98f2p1cW5pZ25xc2lhX2ZlbF9daG9tjYZvbF9jX15ZVEpKUk5SbHp0fXRtgXl/dX15a2yXfnqCenODb3Jjb3NvbWZtcldgcGpfXlJQWWZpeHJ2gHZ8cHR9kYKIjo1eY3V0foaGfHqDgoB5epCJnIZ8h42Ag3Z6amZdYV5jU1NfY5uaiop3dm50f2RvanFhVmFwdnRpbGt1gnJiW09dZGNbZ2llcUV3dWNvY2Ztdm93h3FqdGpnWlplXVZ1cnx3e4GRfHtweI2QkYyHj4V/dX97fH2AgHZvbnt5jo6Pj4hnWk5RVEpebW1oeXNvc3NzbnKGT0yKW2hlXXFcRG1hZl5ZV2JDc3tzaGB1cGRgYGVkXm6DeHmLlGRybIB5iIp/eYR9eYeDZ2qMj4RzfnGGU15pd3p5jGxsZmdvdHtzZFxhbWFdmJWRbXdsb2RxeHp+f4R8dGthgGFraWdjanJ2gFxOYWhpeHNnbWtjZWlwZ2NdZ2dbUVNQT1NgVlhfW2FtaFt+cVNSWlZzeWdjZFdSYF5WR1FtfFBmcGOEglJTYGZgY2JQXmVcXmpgXU9UYm+Cf3KBbWJjYVVvcnd7b3GCSX5uaD56RUZxVHB3al9bY2J1iI2IinRqgHFyiaCXgXdxdHdjmoGWUVSGYWd2hZyYW5V6cohvcGt4kJ98bG5vd3R6gXR7eH+XjXh0iIF/h6KrmJSZkn2Ld3l5doKOeX1ygWlramxqbnZ1c3x0dYN6doGHgXJqmniCgop3XmpoiHR5dG93gnRtdn50aGhxcW11dG9viJ6Ob3ZrVm17b3x1enF5gnmFkYqIfn1xfZKTfoCRio+GcHx/i4J1jaSXooiOZVmjYmxcrVlZpqSXk6mvjIKfqJypq5eFdXuCmIt8fYKHe3qBioBsdIOOi5SHhZWWhH2AgHV7e3prfWpoZXl1f391e3l4eHOAb3dveniAe4N4d4ByZmh5dG1/cmpvcnNobG57d4d0Z2NicHd8anBygHt/fn14fpWKiY5ydoWHeXNfho+ojouFhn55hJGJhpWRgHtyfnWEfG1tbnlteHVyZWxueYF/hYSJeYCCi5aGe3mFhX6AiHqGlpyRhYN7dJGFeIZ+e3psdHiJdHuMZWV0gYRxdnyYnIFzXW99gXuGfntzeW5ubnR6eXFugoF7fHZ9hnx/iY6TjZJdWE+Kmpyffn14dYRwan2Gi319fY6yjZOKd31qaGlqd3x4cX19a254fn95eHl1cXVoXndqd3dzb3BydH+AenuNknt7hHR1eH1nZm5wcHd4eHVucm9xZmpubHN1aW11k3+AhoV9cnF/eXxtYGxoc2h0dXd9fG1wdHF5dmpwbmVnbnuCeouIjZiHb3N6eoOlgXZ+hoZ0gYF9eneAen52amp1cnZlYWVqbZKIc3JgYmJlX1xTU11WVm9+dXp0a3iAcntzeXVlY4h2c3puZXNjaV9qa2trZWx0XGh8c2VhVFNeZ2RtZmpoalxiboR6fIKBVFxubnd+eWxqcG5qYmJ+d4VwaXV8eHx0dmhlYGZlbF1daGuloJGOfHp1fIVqeXV+bmJugIeDdnp5gY98a2BUYGdnYHB0cX9Ngn1ocWFlcHomdXuLc2p5a2laWmZeWXd1gnx+gI14dWpviIqPgHuBeHNpcmtpamydeoJ7j3oBe/96unoIe3p6ent6e3ueeoJ7h3oBe8p6AXvDegl7e3p7e3t6e3v/evB6g3v/ev96snoBe656AgIEAIDVzLzd6O3ZyNu/w7meqrivrbW/vdnSy9nbzb/ButLOzeLc1szf4Ob129LU0drmhZSK7PPx7N/jybi6zcTQ0tjS8d/V3NXQ49vd0cvPz9ncwsHVhvmB6cjJu6yxutjc0s3f1cHBvc+1r83KxcW2nq3A2pisq6/Av97NvsK2usa2u4DKv7K7sKOb2KHFt6qWw7GdmoqJi6GgoZyQl6S5tMzL1M2yqKOjpbCpoqadnZ+lrbm/w8e91tHQtr64rKmap6Xbzdni1rGnpaezuKqyrM7d/+vJucHP1c3G1ez26dbEtK+4xfv5gu/g397w49qVhd3hzbXM0+DW0s7a+piGyqquq4C2tKmpscbgz8y9t8q/7Pryyaysx7DKytTF6crNpZ+gl7HDqqWppK+nnbnLwLnZppKq9Na6nJuenpmtq6Kty7mlr8y9sq6nr7Ovraq2tM/K1cK+r/G8weiyt8Crs7W5lsbPsZiQpKq2rbOtqsG9ucSwq6Kwuq+fpaXE2YHmuai0zYC9rqavsqaxwND22+jr1cvCwcnfya60uuLezrW9vMnIvcG+wrCtxM7LycrV4vj01NbHyMDGzMfPwNjgxLnH1MuvqrG/uru5yNjVx8GusbatrbjO89zDtMDAurC8sLPNycbJ4L7Gw9bWyKqbo5+jqqupqqqtpbC3qbGyv7Kus66lo4CxvLW5xcrJxbO1uaSOs+itr7m6tbWuo62vrb+4tre5rqiesbjEycfCtbKfq7O3t7y3vr27sLi/say+wb6zva6yrra0y8DH59DO2Ofc0tbk1cHF0NHZzMzTzuPQvMnFvbC4vbKypbSxqp+UmaGrrauku+LOuLestrKtucWvo7etlICYp6+lrba1sbSzrMPFxsa9y7rDtrK7s7a+xt/g5oCE18re3OTg4drDv9TY2MvMu97Y2PrV0si+193HtLGyrMHQyM3Cv83kzMPAv7zFtq+svMTAusO3x9jAwdnExszEzs/Pz8O3vMTN49rWz7ewrL21o6Tb0cHAqrG8vcLLzL/T1IDTztXW8cHCv8DNyrvDwMm6zdbFvsjQtsjK0crDw7rFx8bGzuOyp6evpq24rKemt7G5trGtoputp5istq+0l5yUpuzDmcDku72yrpGFprSlppqKiIObp6Wyqby3urG3sbCxr7Krt7qtr6GYla7CtMTPsaehsK22wq6lmZ6Wmq29wICsscm+tLW8vsa+vrK2ubS3srW4uLW4uLrLubutn7Olo6imqraxlZWjo6Gqqqm3rbjGvMTFyq7KzMStsq3C4tbKvK2poJqfn6OyvLi1rrvFztTN28jMvrzGy9Hn4eTKwsrAzc/avcfzya7LzcbWyb+7v8XNy8/hhvr+hJCB6/D054CCe3aXpa2dkaqPmI9ygIp9eICHhJ+Wkpmej4aJhZeVlaWhl5WmnZ2pkYaHfISMVGBYiI+UiYeJeWpvfHmChIaCnY+FioWFkIeOiIOFhIqUf3uKWqRSmXx7bmRsdIyNg3uIgnR9fYZzb4N7enxuYG9+kVVpa3aIi6mXjYuCjJmKjoCajXyIfG1ooHGNgXVljHVla2FiYnV1cW5ma4GOeIyQjoBuaGlram5qZmVgZmxweIaOh4V/kI2PeXl1b3JjbIKalqennIJ5e3R6enB5cH+Po5F/c3J3c3yHjJefj3ZoX2JWaZGRUJGLgXuEiHtRSGdwXVtiYG9sbW59mWNUf2Job4BnbF9aZG6Ac21nWWthgJqTcFlQYllpcHVshmtwYFdTV2ZxZmNcYGhiWW92cG2GX1JkooVsVFNcZFxsa2VrgmldY3poWVNPUldZWVZfY390fGlpWoJgZohdY25fa3F7WYaRdVpQYGZrZGJcW2lnaHNjWVVXal1OVVVqfk+MZUxbdIBwVVBbXVhdbXeTe4WRdWxlZG2BaVhZX391alZaX21vam9ucmRjd3x8fn+HjJ6WcXJmXmBnb2JsaX99bmhzf3phWmZuY2ZhbXt8bmZbYWNeWmBlloJoV15ZUFRaTlZsYVxmcV5oZHF4bVdOV1VaX2RpX1ptZWhrXGJkcmFgZGFWVIBhb2dsdX11b2BeZFM/Yo1eXGRmZWVeVFtcWGliX19fVFBGVF1na2hoX1lJV2BhbGdqaW9qXWlqXFtiaWxvd2tqZ25jb21leXNyd4J7dHV/fGVmZ2VoW1VYWl9dUGZnX1lpcWhgWF1YV1NNTU9dX19kcJJ6bXRiamdrdX5rYXRtVYBaaW5nam1vaGtnX3F0dXZpdGVtYF9qXl1iY2dxd0dKbmJzdHl1fHpmYW10c2hkX4Z/dZl3dG9kcm9fUlJOSVxqa3lsZm6JeW1rZWRtXlRTXmpmYWdmdoN0d419hYR7i4yMi3xubXB1jIiGemllZHZuX2SVloB/bHB4dHN6eGx4eYB9dX+CmGxsbGt0cWVrbHtsfoJybXV8Z3R2eXl0cGhxdHVxepBpY2RsaHB2bG5pbnV0d3JmXldueWpye3h6XWBebJ5+XXySd3hwbFZNYWhfWlRJRUJZZGFtanl7g3p4c3d3b3Fsc3RsbWFdXm13ZXmCamBda2RrdmdhWWJZV2R1goBzdIqCfX5/eXlwcWhucm5xcHFyd3d4eXqHfHxyZ3BjW2BjYnBsU1JfYVpcW15sZWx1cXl5cmJ5fnNcXVlrgH11aGBfWFNXWVpkaGRiW2JmbnJvdWZmXVpeXmN7d3toZWljdHB6aW6Xc15xd3OEeG5ram1ybWt+To+QS1VKhIyTi4BnYVt8ipSAcYZtdGtPWmRaVl1mYn5zbnZ4aFtdWGlmZnhwaWRxa2x7YVpcVl9nQ1BJbXmDeHh5ZVVbbGhydXp0i39wdXFveXF0a2NmY2tzYV5wS45Dg2ZmXlZgaICBdm97dmlxcHtlYXl0cnVpWmx/lVZpa3KAgJWEeXx1fYRxd4CGf252aF5ej2KGgXNdgHJkZ1hZV2NdWFNITVxoWm5xdm5bVFJRUllUUFBKT1ZYW2Zuam1ga2ZlWV9XUFBDTFBsZXJ2aVlWVlJbZFdjWGt9gnNlXF1jZGhvfISGfGdUTVJRZoeHR4l+dm95em9DQ2ZwYllgYmtjZGZ0ilRPcl9jYYBfbWNgY3V+dGdlXW5lhp2UclxOZGBvdn98kXqBbmZlZHWFfHt2e4V6c4iSkI6Uem2AppmHb25zdnSHhX6Fnol9gZaLe3ZtcHJvb2t0dY+EiHRyZJNscJVrcHloc3iCY5GYgGVca21za2xnaX57e4V3cnN2hHdqb3GIlVqofWlxh4B/bWlycmpyf4ihipGahXpubneMdmlveJyXjXZ6fomMiIyKj4B+lJmam5SenKefi5CChYKJkImPjJ+hkIqSnpuEeImFeHt3fI2NgHxydnt1fIeNtaOQgI2Ed3eBa3OGd2l2hG5zcIGFg3NrenJ3fICEeHJ+dnl6a3Byfm5scWxiX4Bsd3J0foV+eGpobV1Jb5ptbXZ5eHZwZ3N1coN8eXp5bmhebHV/goKCdnJhb3h4fHt+f4KLf4WLfXuAhn19gnJzc3VwfnVxiHp1eIF7d32Fg3d8fH+GfXZ7e4F7bHx/e3F6gH98cHt5eXNubnB3dnN0hJ2KfoNzfXp9hY2Bdo6EZoBqdnpxcHV1c3Z1coiMjIp+iHV8cXJ/dXV9fYSJj1RXin6NjpKPkY17d4SKiXx5dpmSiqqMiIN5ioZ0ZmJfWGd0dIB0cXuThHp8eXaAcGZja3hzb3FteYFzdYh7f3xzgX+AfnRoaWxuhYN/d2hlZHRwYGWXkn5+bHB4dXl9fnF+foCAeYGBmXBxcG93dmlvb3ptfYJ1cnh8aXd4e3x4eHR8f4KFjqF/eHZ/dX6DeHhxdXuBf312bWeAg257hn6FZWhibal9WHuZeXp1clhRa3ZnZV9UT0pgbGhvanp5enJ0cG9vZWRfZWZeYlZQTmJwXnB8aF9aa2lzfWtkV11VV2VyeoBnan5zaGlqaW5naF5laWRmYmJkZWJmaWh2a2thWGZXU1hbXGtrVlRgZWJoZ2t5c3mBfoSAfGqBgndhY2N0joiAc2poX1pfYWJscW1oY2lrc3dwdWZlW1lhYWeBfX1paG1ldnR/Zm+YdFhyd3B+bmJdXF9jXl5uR4B+RE1Dcnp9dK56g3uhegN7envpegF7pXoBe4d6gnuMeoJ7/noBe/96/3qkeoJ7/3r/etl6Bnt6ent7e4R6AgIEAIDeyM7o39G+1dm/uMS+uMfu1NXJx8u+xuTi6cPr5tfexb+63dPV1df18+/Iy+Xy1OX12/fj4NDa4d3pxrqxydrU9OTg3MnPzNXXysy6sbzRtMbW4L7Gv8e8tbPEyb+yxYW4vrurscDZw72vsLavtaust5ydr7yxsKmqtsC8yLCvvoDTwrSlnaWdlZ6m08jKy6ipra6fo62msaeyuMKvpL20qrS6uqqxsayyspWZprayvrnIwLvN3cO5wtTe19nKwcrKxN7qqZ2esLmdnKKw2M3Ipr3RxMXCwLCmvszJ1uiFlOrAxNvd7PPfzM7YhIiM+vDPwK7g78nN1NnAzMa1uL7Jr4C3rquuxbHL29PU5Mrj8YmC18Xc+9e3wsPBx9z0y6Gcop+/touNmaKhoZWb5ZqisJeRhZGln5ell5uhuK26ubCstLq9uayptLe3tZyuyNSA7Ky9xtbBvMPSxrecnoSCi42SqrDOtqyoqqK+uqmyxMGupa6uoZWOlaKwuLC2vaCv2YC9pK+trLe+uMDR0Nbo2srF1NjQz9HU3d7i0c64x7yptK+3wLbLwrmxq6uorbS0ubrAyLvBuMTHx7++rLKmrr69uLOqtqu1vsW4u7/Du7rBrsC6z9jTyMLDx763wL/Lz87C/uDGwsu4ubSolJGWkpWsscrov6upt7uxuLOqsbedkYCpuMXIwqensL3Px8iyo5WTmqqslpeXmri7t9TDtbWqq6y5rqWzs7DArKq+xcS9vse4x83Lxaa/wrmxvrW7uLTHrK+nwMPf4+XXzNzY1ce7ybm+x8vLzcXUzNDc08bXxq6rvJ2Yp6yqt7ifn62spqu2rbe1xcadorTCu7y/sr+0noCUla6/xrW4t7u1ua6toLSpqauxuMCxwL26t8fP1NXY5NjNz8PLyc3Owca+s7i4wrfFwsrD0dnY38zFu+Hn2bmhnbXCu7rY2c29vbmqtKe4uK+1r7S0u7+1uLjB0cnh2sjR0MjEt8bCwL251uLN4uLOzsPHxc7SwdXj7PjRxdvMw4DBztbV0M7Ky8C/xszY1sXBt7bBurqv1L3AzsvDyeTVzrbJxrzGu62yvsGwo6elvMa4tbWklJi2rJSjr6imoqimo62kp7SxoZ2/uK6jrLSnoqecj4eGk6CnpbKys62hnqyoqrXCvq62x8Kss6ihrd3Rt8Kpvry3u6y+p8O9qaq4toCzzsa80smssbmzw8PJx8vFr6u4vsO+v6qso5egoqevn5qsr7y5vb2ysK+zqqGhpa2utb7CwqTLur3EvLC3w7GxuKm0tKy5vsm4uKvFzcbCxt2+wsPDw9Pk39nZudbm4uPq+urQybW61tbf5tHZ2t3W4dTQus7L2oOI2uLf6+ng5nV/cXaRjoV0hYh9fIaBgo6qlpmQkpWLj6epqYyuqp2fjYOAk5WQiYmem5RzdYWOeYaXhZmPjX6GiImOdW1pfoyFo5aSjX2EhoqPh4yAe4KMeoiYn36AfIB3dXV/gnppd1p2eXptbXqOgHhxbnNrbmhqdGFldoeEfYCMlo+ZeXeHn459cGlybmdveKKPi4x3fH98b218eHx2hpCVfnWEdWt2entzcW9vcnZgZW99fI2CioCBi5SDe3uOl5aTfXyNjY6arHdxdn5+Z2ludoeGf1x2hHt3anJybHl8gHyKVV+IZnR3fYyQg3Rnb05OT4R8aF1VcHVjaXV4Z4BzcWhxc4J2Z21kY3hicXx2gYZshodRTndteIZzYGBnY2t7iXFaUVlebWhRTldgXV5VVolSWWpQT0JHXlxXY1tbX25mcmteVltiZFxSTVlYWVxOXXF7UopZZ255bGpuf3xxWWBKTFNUWGdshG5hXF9XbWxcYXJrXVlgX0pEPEVQWoBZUVpnUlmHa1pbWV5dZF9od3Z6h3pvZnF0cW9vc3l9fXZwYGtiVF9bZW9ld3FpaF5gYGNsaGRmZGlkZWJjanRlYlljV2BvdGpnYGJVX2FjYF9hZGRbaVxlV2R7dm1nYV1cWFlcamxmU4xtW2FsWV5YU0VGTkxMXml8m3tjY3FvaoBvaGZpb1VLXW54e3tgW2JvfnV4YlRMSlBZX05NUFFna2h8cGBeVlVUXlVOWlpXYVJPX2hpZmxuZ2x8dG9XaXNxYHFndWxse19fWWFwiIB/fmx1eHNqWmhiXG1cXFVTW1tibHBuhHRhYHZYUlZcVWNlV1ddVVdcZmdvZ296WlxpdYBzdXhwe2tiWVZlbnlsbWtsZ2xhZlxuZWFeY2huYGliXVpna21yc3ZsYGNbYmJmZ1tnYFhcWl1SZGBlXGhtaHZjWVF2fnZcTEhcbWlogIl8cHFrXl9VYmFZYldeYmltZGdqb311h4h2fn93c2hza2lmYHeCbIeLd3h2fHqAfnWGk4Caq4d7jHx1c4CEgnd1c3JvbnV5g4J0b2xqdm5oYnpob3d6cXaBgXpka2xpc3VxcX6DdWBla3p8e3Z6ZFBXbGxkdHRnZ2RqZ2x4ZGV0b15ednBqY3V1ZWdjXlRKR1RhYl5wcnBxbmxzbnV9hoV4eoF9bXFsaXOYg214ZXNzbnRmc4BheHZrcIR6cYmJgo6JcHN6dn17fXqAfG9reoGFg4Jyd2pgZWhqb2RdZW9va21tYl9iZWFaWVtgYWZrbm1XeGZpa2VaXWtbYmhfZWVkb3N+b3BlfH90bnGFam1rbGp0hXlydVp0gYCBhZOJeXJiaH6BgYt6hIWJhIp+dWFsbnZNTAd0fXp9gX+CgG5dYXx5b1tsbF1bZF1caIhydGlsb2RngYCBXX94bm9dVFBpZ2VfYHZ2cVBTanJda4Fthnh3a3N5en5kWlVpeHSQgXx0Z2xtcXVrbV9aYWxZaHqEaW9scWdiYG9ya1xtUmlra1tdaoByamNlbWZqZWRwWFZlcmhoaGp6dnl/ZWd6gIp5bGRgZGBbZnGPgIWLcHB4c19ZZWBiVWFsb1pSY1pUXWFeVldXWV9dR0xXYlxoYm1lXmRrWVhba3hucWJZZGVjdX9UUVZdYFBRXWF2dmdLYW9oZV9iX1tkZGlwdkhLemBpbnKAinxpXmZGQ0SBgWxjWXZ8Y2Rvc2JqZVlidH1kgFtnYmZzYWx1bH2GbYiMVlF5b3uJdWBgZGp1gpN7Y11nZ3p8Zmdvenh0bW2lanqBaGZZXXNybn1zdXuPhJWPg31/gn97cGt2dXRxXmt8hVWSYm95hnl1d4uFe2VpU1RaW2FwdIl1a2ZpY3hza3SFg3V0fIBqY11oc35+b3WBaHGTgHpmbm1vcnhze4WBh5OGd3B9gYCBhIyWmpuSjH6MhHaAeoKLgpaUj4p/hH6BhYCBgoKThop/h4iQhYV9hXyIl5uNjYB+cHp0fHd1eYB+doZ6i4GPnJ6Wk4uHgH6BeYiFfGaeg3V2fnByc3NjZmpqanmAkrGLc3B9e3R6cm5ydlxPgGJzfn99Y2FodYN+gm1fWVhfaW1dXF5feH19kYR2dGxsa3ZsZXFybntqaHuFh39+gHiDkJeReI2Rj36HfYJ+eIhwcGd2fI+IiH9uc3hybWZycHeCen15eHt8goiIgZaFc3KEbXF3eXWCg3Jze3VxcHp2fHV5hm1wfoeEiYqDjIBxgGdkc3uGe4GChIGDeHdtfnV0cnd+g3aBf3h3h4yMj4+Tin1/eYB+gH9zfnhvdHB0aXl3f3mFioOMfXBlho2EalZRZXVycoqSi4CAeW5waHZ4cXhucHBzcmZpanB+dYKAb3Z2cXBmdG5qaGN7hHCHh3R1c3x9hIV3iZKWnIB4i353gHaAhYJ7enZ2c3N6gIiGeHRvb3t1cWqEcXeAhYCHk4mFb3l9d4KJeoCEiXtmb3SDgoOAfWpWXXt5a3R4bmlmcW5xeGpqdnBiYHt3cmt7fXFzb2liWVNeaGpkcXBvamNgbGZseH95aGt0cGBnYFpljHtlb19tbGZvZXZie3tsbXtygGl9eXJ9emBkaWVwcnVydW9dWmxyeXV0YmRaUVpbXmRZVGBnbGtsa2ZobXRtZWVma21zen5/ZYRzd3lzZ2p1Z2txaG1wbXV2gG5wY3t9cmxvgmhpZ2ZkcYF4cnVVdISDg4uZjHdyX2R8fYKLd399fnh/cmtWZGJtSEhpc29yd3J033oBe/16gnuLeoN7oXqCe756AXv/ev96/3r/ev96rXqCe4d6AgIEAIDKvM/D0MfJy8a5vrisr8jQz97dy9XFvdLlyqrSgOLJvdXQzdrf4dzj6+XfwuHp7NLVvdaA5uzNt8W/1amgtsLOyrvGybiquN7jndW1pd+ytLKvrKS3v8nCrK21rry8vbvV2se4pbPGx83GxcK8sZyiwNbMr6unmpKfoae7qK67pICYpKyhnq6xoqSxr8GvpLTR1sLby9DNuZydpMrHvrC3trqpwK6hoJqgqrGuq620zcbBxc2/2NfLwLirw7vF38q3zsa9oJepq5ecyNiB6cyxpqKrrLKtupamrLe91snh8aywyMu6vMXJwczD8ZrG9bW+5KjIw7zC3ebHvqyutbfCp4C7sKGHhpex9ZXc49bLzur40Lez1uK+1cvGvMvrxaSTnJy4n5iVpJiZloyMpKiYioSLjpKan5+unKvCud/Pu7q2tsC2qLOvtaq1xMaypLK/xLa5vrqoo62hspOippuSoKvayrjhxLS9t62vqKOltbvFsqOrnKGiqq2rrLGyvbGyvoC3trbCy762ub/Hw9XY2NPZzdupzL7MxLm9v8jKvburrqquprKyp6y7vLm3wLCqrqu2u7K+q7Wyr8W7wr6uqLWzu8m3v8fGzMjCxs7Xyc3SwbbE2dzs083F4cDGvMjLvbvP593WysXJ0aW7spOcnpijxuq/ua/Rx8Srs76hrrWwr4Cytq+5uqutoa66urm1qp6kn5mnmZ6qo5urrr+iqLOpn62usb60r6qytri4xNXFwMO6xLXAyMnKvre4vMG4trS9tcm30r2/zd3MztPXyc7MysHWv77Ku8/Fw8jMwMCypZmLlaSOqaOjn6uyoqKys7q9r52xua/DtLW5vrOmr7S2soCZqLGonqKxsKqivrisoKKrmaWprsOrsai1vdLY2fHX2c3Uz8e/t7jDxrm5ycrOwLS3zOXVzdLXwcvOytLT2szGxcGvrbG3r7KxvMS7qpydtsjIv7q8ureuraq1wMfRysvb4uHe1N/n6obi4PTj19/jztPHg+vFyca2w8e9ub/N0oDV4eHIq7LTxKPO8ubZ3dfKuKLEuL3H0MPHvLOqrr62n6emsay3n5qfoODFqq2psrzYvamflbGljpWVnLKpqrihrqifpKSktbu+urGklpaMjpKkvruQiJygn66fmpOwoZuzqbu0rry9sqWms7bCubK1w7eyu7mus7K4yc20ranFx4C9wK2apK2zpri2sLO7u8LErKzEvKuorqGipJ+npqmtpKqso6SemLK8qJms9M3Aq6+tpaCusK+0wMi2sq+xtaqoscHGr6m0srGqpKnEtrSuprO4vcm6xrzBwbW0vcTM1sHI5M/R4dXl7t3d1s3c5fHw3eji19zP2Mzu49K+xcTD1ICDdomBjYeJiYaCh4ByeY6QlJ+ekZmLf4yjjW2QV5uAe4mEfIeFhn+CjYqIcIqQkHl/bIJUkpN/bHd2h2Rcb3qAg3N+g3VufaCocJp7cKR8fHt7eHKGiYh+Z2Vubnt+fnyMloh4anWFi4aAfn94cmJohJaNdHZ1bWtvcXOHeH6Gc4BlcHp1cHqCdHJ3doSBdYOVmIGYjpCQhnJxdZOPfXJ9fnx0gXBoZmVsc3p0c3V9jX+Dgop7kYp9gXhve3iIoY95h4mKd3B1dGJqjZlVnYlrYWVubWltfmZva3VyfnqRlV9qdmtkY29zZ2hqiV93hlVhgVJfYGNrgo92bGFxc3R9c4B3dWNMTVRjkGCLi3t3dXuPbmNebndndGhnYW+BZlhRUmBqVlhaX1hbVEtKW11RSkVHTkxPUltpW2p6cIp5bWpgWmFaUFxbYVVea25gWmVudGVlaGhZVlxXZlFeYltSZGmPg3CSfGpyYlldWlJQXWhsYlNVRkBJS1dWVVZbaGBnaYBiYmdxb2pfXV1dYGx4fHZ3anZQaV9qZlpeXmdqY2VZXFteWWRjWVxycG9re2hhXF1ga2RnWlxeX29mcHBaVldeYnBdZWhrYmRfY2p0am1zamBhanODeG1le1xsW1tkWFlmc21qXmZqdUxkYEdVXFVZd5RtamJ8d3FfZ2xZYmdjYYBlZWFoaWBeUmBrampnXFRcVk9XTFBaVlBZW2ZLT1dRSFJSU15YVVRZXV9cZXFiX2ViaGNtdHhvbmhnaG9qaWl2ZHdobl9nZm9nbW93aHZubGV5Y2NgVWBeWWVqZWhjYFRMU2ZTa1hURVNXVFVjXWhvYFhralprY2JiZ2JdYWlraIBQYWNgWF9xbmVefXpwZWZqWGNfYXFcX1ZbXmhwcYd2fHFzZ15VVFdmallcaGRlWU1QXXdmWmJlWGNmY2pydWdlZWNZXmd0bGxncnNnVlBYZmxta2prb2xlZ2JscXZ4c257fHt5cHd6fFJ8doR5a3l7am9vWppzfHxwfIB5b3OEe4CCjY93Y2OJd2GEl5OOkop9blxzcWx1eHJ3bmdiXmhgWVpaXmJrV1xjZJ6EbWBidHiHfmxkWm5jUlllbnFiaHFha2lkY15hbXR5eGpoY2lkYF1tgnxZS1piYWhcXFlya2uBdYF+fYGIgXJsd3+JfHl2gXFqdHRpaWp0foBqZGSBiICAinVfaXF2Z3Jua3F3d3d6Z26Ef3FxemxsbmdwcXh5bW5sZGZfV2hvWUpbmXRrWWBeWFRdX15lcXFnZGFjZVhXYm5zY19mbGxpZWh/b2prX2hrbHRrbmhpamFkZW90fWlwiXZ6h36KkH98d3OBhZaQfoWAe3hzfG+FhndnbXJ0iIBxYnNreXN0cGtiZlxOU2hqb3d2aXFjWWh8ZEVlQnJWUWBfW2VmaGRmcW9sU3F5emNqWXFOf4JpV2Bick9FWWRrb1xrbFtSYIGKUHpbUIJeYWNiYl5wdnxzXVtiYG5ubGx6gXZnWWVyfXt2dHRrYVBTbYB3YGJfVU5SVVpxYm9sXoBUY2hhX294aGNtcYN4bHqNiHKGeXdxZE5OUWxqX1VfXVxTXFBLSklQWGBaVVNabGJgW2JVaGJYXFdMWllfd2dWY2JoW1JUVEpSfIRNkndeVFVhXl5gb1ZdW15lb2Z4fVRhbGJeXW1wYWNhfk9chldmhVlkaGVtfIlzaGBjZ293YYBobl1KRU5fhliDiHx1eoSUdGVfbXlpcmtra3WDbmJZYGp0YWtuem9xa19gcXNpZl5dZWdobXSEdoaYjqmWh4qCfoR+bXZzdWhwfn1vZm95fW9vcHFkZWpjcVtpbWNaaW2Qhnmch3eAdGtwamZod4CFfXJ6bWhvc3t5dHdxe3N5gYB3cnJ+hnpxcHF0dIKKiYSIfoxkf3iIg3d7f4qMh4d8fHp7d4SEe32Pj4yIlIB6enp7j4GId356fYyIk499fYOIiI+Bg4WJf319f4eTiIiThoOEjZKflo6KnYKNhYaJf3+DjoSCent/h2N/fWNvcWlvjquCfHKNhXxrb3dham5pZYBpa2dsbWRkWGZwcXJzamNtaWFrYWRvaWJuc35laXNrYGprbHZwb212enp2gY6BfH56hYCUlpqVjo2Ig4h9f3qGdoh4g3V2cn1wcnF6b317e3OLfXl6cn97dYSKgYF8eGtjaHhph3lwY291cW96dHl7cWl4emt+enl4e3ZxdHt9eYBldIN1a3GAfnlykY6Fe32Ab3l5fI54e3B1eYePkKWVmYuNhHx0cnaBg3F0f3x+dGptepOFd4CEdX18en6BhHd0cm5kZW55dXh2foJ3a2Vod3+Ae3d1dHFoZmFqcHR2cGx3end4b3Z5e097eYp/doJ/b3FsUpR0fXxweXpybXF/e4B9h4lyWmaDdWGDoJqUk4p+cWN9eHmIh31/eHJvb3lrWl5cZWdwWWBkaJyGbWZpd3yLgG9iWG9mV2Fpb3Zrb3dndnJoaWlqd3+EgHNwa29nZWd1ioViVmVoZW9gWlVxZWF3b4B4dHh5b2BZZGx4bGhlcmZiaGdgYmJtfoFpX155goB4fWZRWl9mW2lkX2BnZm1zX2J3cGFhamBhY1tkYmVnX2NjXVxVUWVuXU9fkX13ZW1rZmRtb25zf4F2dXFxcmtocHx/bWZtcW9qaGuDcW5qXGJjZW5hZl1gYVhbX2pxemdvinZ4gniDhXZ0cmt3fIyFdHt4cnJpcmR8fW9dYGZmepx6AXuWegF7/3qYegF7n3qCe5t6AXv/ev96/3riegF7inoBe/96/3qOegICBACA6qixw9LP4M+4r8DQzMu6sLnR2s/V4vLc0MvFwdzy6O7W1MLc3NnOzd3z3L3a1NXO38nP8trHvbm6wcLK3ujY28i+v8GtjMKY87rAmp2Ulqiys7ernpuuv7WsqKSrrK6soKGwqamnqLK5wsnGx8zPyOnHtrvAzrWqpZ2QloyTmbiAsre6raSnsK2loYmNlpCswrjLzdLJwrqrp66xu7m+ztrTrKqkoKGdqqKetbW008vQy8HBvYHVsKmw19TPube2uLKxn5KfnZ2VlKHHv6a088W2o52nmJC3u7e+u6i42OenprbGuLScqMfElKfDq5zIuaOvu66jq5SIoq7El6+psKaAraOgm46cpePozLzDw7nPx7+z09fOn7fCt+Xbq7m3t8XNv8XFlpGQko+eq6KlqaWZkpeak4ySn6enp7u4vr6+sqy7qKOyvcOgqKWotcewrLSvscOxr66qorGpq7ahpaSqlKao0tTSubiyuLm9v7q6w7aroqWjtbSvq6+0vb67v6yAtsLA0LnGuaipsbe94tDFwbijsdb94ezGtb+zq7azsquwq7GmtrK2wb+2tKqnpri3462mrqm4pLK6uLayvLXAz9LNxcDF1erUzsPayuvi193X3OH3+drSyNDJuK/Dzri3wM/E2tDBt7qks7zArbaptrKftaeqqrW6pqednJSQk6GAraCcpKK1zs68va6loKWzo6OXmJyUoZ+upae60L3At73DtLSuu7vEwsTCtK+1vKGfo8C3sr3Lwqu2uca8vLS3ura0utTNuMPa28XMwcK7q6apx72wr7W8zbq7wbGfrpGEjI2an5qVsqjsraOuoKjCxqi4xrK2scOupraxqsDKxb6ArJ+hnaOfpaSst7anoaCjtrKvrbK8ua3Apqq1rrLFyMTKzrq3srK2zc7GzMvAtcvDv8vLwbi+wL3Ax8rv3tDGu7bAvrynusW3wb+xtKy0obu9v77Mu7+4trCwutXMtMPCxsnW1dHT4OmA5Nrn+eDOz8zH0sfSz8rM1o+M1u7S08iA27iu4eLStr+ns8HO3dfV1tDDvbK7scLJwrfGr6etqLTHy8i3pKu1pLG80M25u7O0ytC4r52rrKejoZ+fp7att7Caq6+ts6SemIyJnqmnk5GKpLK0rZurr52Uh42pucnJt7PCtqKWo6mgo6PDrqy/xcjJv8Gvq6qvq6u1sbW0v76A1b+lmJuioaKnpbO/08HCu7Sur7m5ppCesKSXlo2cnqCaoaScnbPJvLO8xcvDucWuxuS4sKWlq6qio6m2oK6msravmo6jpKimo6ywv8G/q6qtnqCnr6ensa+ioa6loKe/xMPHzr3F2c3JuczU8NjN0t/X0c7c6dbZ7c/My9bS2+mAp2hyhJKOoJF6doeWj49+dX6PlYqRnqaQiIR+d4mdlJmDf26FhIB0doKWhWWCf4F8i3uIqZJ/cHJwdnd9kZaPlYiAhYV3XoZytoaNbGtjZHmCgIl7bGh2gnhyb2htbHFwbGhwaGprbXh8gYeDh4+KiqOMf4yPkXdtbWpiZ2BlaIWAg4qKe3Z3fHFqbFhcYVtsdnaHh5CQlIl5dHZzeYCCkZmVcnFwam5udW1tfYSAjoeQjHt8d1GJbm1viZWYhXpxcXJ4cGdoaWVkZGqBfWh1qIJ7bF9pbWeLiYKDe2Z7kpRkZGFqaGJSYG9qS2F3XkxxaV5eYVhWXlRIXWR4ZXBscHGAcWllXVtbXYiOgXJucGtsaV9henFpUGhkX4eDW2dgb3qDgXhzWlNRU1BTYVdZW1ZRTk1STUVJWmVlaHRvbWh0YVxmWVNcaXFWXVpccH1oYWdiY25gX2diW2tfYWhYXFthU19jhH98ZGBcX1tgZmViaFlQS1BPWFhWVltcYmJlamCAZW1ufmtxZVdUW1xkf21gW1hIT3ShgY1uWWFcVlpXVVBUUFZSYmRoa2tfZGRgXGJtjV9dW1piUGBiXlxXXVdeZWxpZV9lcnRwYmVvYYB4b3t+d36Fj3pxaGpoXFhgXlZQYWJac3JgZmZTXmhvXmVga2dYbGJlZXR6bGxgX1lXWmSAbmFdZGBxkY18fW1gW15rXFtSUlFLUlBdVVVjcV9hWFtdVFJMVVVfXF1fVVJWXEZGTGRjYGtvZ1lhZGpnYl1eY1lZW3dvZGh9dV9tY2VdVlBTcmFcWFRaZVxeaV5XalNOU1ZdXlxVZ1R9WU1jWlxtd1tqdmZlYGlaU19eV2lub26AYFRcXGVgZmptdG1iYF1kcnFrYmFkYFhoUFZeWV1scGVxdGFZUlZcaW9mYGNdV2ZZVWBnYVliYllYZW6RfG5iWltrb29ecHlqc29lbV5cR19kZ2dwaWtoZ19iZHdtXm1uZ211cmhteHhHf3qBknx0e3l6fnZ7eXWBil5ek6mAg4OAj3Jpl56EdXRpc3WCj42Ki4N4bGpuYm5wcnB7bF5lXXF5fn1rYGBoaHB1h4d0aGh5hX12cmZrbWhjYmtqaGdlbGlZZnNzbGFdWlhgbHNwYWddb3h+cmVybFtXTlJkeY6OgoKSg3Fodnhvd3WKdHSIjIiMfX5tZmhpZ2ZubHBvdnmAlIFqYmVsaGdva3V7ioCDeHFoZ3FyZFpldHNnamNra25pdHRnZ3aKeXB2gIB8bnNbb4tlXVhXXl9XWVxlVGNgbG5oWlJgYWNjX2dqdXd3ZGRnW15laGFeY2JXVmJaWF1vdnl9g3V6iX53aHV9l4B3eYZ+eHR+jn6Ek3x4eISDjp+AkE9Za3l6h3VeV2ZzbWxcUFhpcGZveoRyaWFaUmV2cHRhX1JpamhcYGuBclFvamxmdGRxk31sWlpYXV1jdn5xdGlgY2RWQWNZj2hsT1FJTGBra3VpXFdndGlgW1VaWWBfWVdeVldYW2VpbG5qanBvbIJwZXN3eF5VVlRNUkpRVnWAcHd4bGZpbWlmY09UWE9gbWl4c3dydGpaUFNUXGFfaXNsSk1OS05OV09KV1pZbmlrZVlVUjxiS0lMYW1qXlNRUE9YVEtJSUpLUVJtcVphj29qVlJfXVd4dm90blVoeoJaWVhkYGRTXHFoSFplWU50bmBmaGFcYlROXGV3XGlpa2OAYGVeXlVWWXyFe29wb253cm1jgXZqTmlpZ5KRYWpvfoiSjoOBcWpqa2lvfHJydHBvamZsZ11id4CBgpCKi4aRfneCcmx1gIpqb25qeoVwaXBubXtranJxaXVrbHBfY2FmW2lukI2Pd3V0eXV1fHp7hXhwa3Jwenp1c3h5fXl7gHKAen57g3R/c2llbnF4koF0bmlYZY2wnaiMe4N7dHt5eXN4d316jIuMjo19hn94eISJo4B4eHJ8anl7fXt0fHqEjZWHiH2AkZSHhIORhaGUjpqUk5igqZeSjpKNgH2Mhnt9gn1yg4Z5en1ocYGHdn11gHxsgHV3dYGEdHFmZF5aXGSAbmRhZWN0ko6CgnVtbXB9c3Nqa2xlbml2bG58jHh6cXR2a2lmcG95d3l8cWxweGJiaYGCg4iSi3qEiIqAfHl6f3Zyc42Kd36KhnF5cHdxaGZrgnp2c3R7i3t+i35zgmxlaGtxdHR0hXKSamR3bG1+hGd6iHh1cXtuanZzanyCg4CAcWRra3Jtd3yGjIqAgH+DjYqDeXh8eHCEb3V6c3eHioGMj3t1bXB4hIaAfoV9dYJ0cnyCfHZ8eHF0fIOjj4F1bGp3eXpoeIF1gn52e3BxYHl8fXyGe3hzcmhpbHxyYG5uaGx2dGxzfXxIfHqElYF2dHR1fHt/e3B+g1NPfZp4cW6AhmhhjI+Ia3Bnb3eFmJCPjIiEeXR7c4WEfnuEd29wZW94fnhsXGBoZm95g4V0bm14g353dWRtb2lpa3BtbHRtc3Jkc3p5fHJsZ2Vpd3x4Z2hgdH9/dGh8emVcT1Voeo+MfHaEeWZYZmlfZl90XVxwd3V3bHJkYGFiXl5lY2hlbW2Ah3diWlxhXFtgXGZwgXR0aWNdYWdoWElTZWFYW1RjXF5ZYWBWWWqAb2dud3d2anBfc45ua2JkbGxmZ2lyY29seHhzZV5paGhoZW1rcnJyXl1gVFdgZl9dZWZVVWFXVFhudHR3fmxxgG9pW2hyiW9kZnRrZ2VygG9xhWxoaXN0fY3HegF77HoBe/96/3r/ev96t3oBe5B6gnv/ev96h3oCAgQAgPfOvLPUz8vBtMi6vtC+uby4wsXY5+3c09zV4cnH3NDR5uzX0NTnz83W5sSJy8rUxsbO5PDptbTFy7PDw9/T5In2zsnhrqizvtnb0a6gnaCcysHFu7nDsbvDtK2VlailtLO2s62Xp7Pc287L3L7Is760yL7Nx6qxqpWYqKykoZWVgKOpvdC0ppeJmI6Tl87Iuri0u7irqMHV186/vNLSvNDPxbe1pq+ur7nCw9HCvsfOzsbQxt7VxaqtoZ+buL+67dOfl7DEqaSdkp62sKmcrciup7OniZGgn56UnN3FmZibocnGwb26p626pLa+vb6xpqWmoqSXorSUoaekpf6OkpuXgJyPjZ+QlpW4sKCfxMO6r8K9x8jQ1N3Vu8PAxt3N186ut7q0hvmspqCfjI+wrp2clIuJl9ulg4yNkZibqaOjoKG0vr6pi6GnpqKfnqu7wqCira61s66rnOHMtLHBtqSeq66il6/Hv8vNxNC/tb607t7MtMeqtKyssbmssba1xdK+gK+7xcbBxcDVv66Ytcbfgcu5yPHU38zo3LmsusnFvbGnsLavr7zJ0M7BvrHArp26r7WvrrCzt7C0tbrIjufU3Ozh1cvQyODdydHj29/n7tvSzsrEx8u9yNrV0by9s8G6sbu5u7Skqr2yu6+zv7+xxcnIv73azreypZ2prq2noq2nC6Oml5uhsrGytaqehKGAg4iZpJ+stqymp7q92cm7t7O4trvNwbjOw7TAu7+6u7zMzayurqbAsrKzsLXFxMfEuL7KyL7FyMnS5OrY3LC8rq6hnrKpp7G6u72yoqKenJ6SkZaLkJ2Qm56Ywc/Sva+tvde+uaepy8Ktt7fV0MHKwMW8rayhsa2moqqqq6unn7aAwbipo72wnZ2drrKwtM/XzM+vp6Cqsre4qbK3s6SryeDD4MO1s6Glt8C5sdTo39zZ1rCuusDPyrnAwrbBubGwsqypt8fHyczV19nH5+7Iq7q8t7y6p7fEz+nv6ODT29XY+Pvs8eHf0c3IxNjV1PXW0tfRs8K8o620ybmqw8GztciA08zTyMq7uaPGvsC9wLizqb6xyqubp7fNxLWxzcvExNLHprG9qqWjqqqss6mioqu8ua6koqeVmpiZmqqjlpjyjJWKlaieoJSZop+bqcXOyMa6u52koJyrtq2trp+Xn6TFxqWasKm9q6+mqLG4s6yRp7KrqKSXkYeSjqGblpqmsb1xsLiqq7Ovraelr6Kpqq2opqCqqaSrqq2xqqujoquwrZ6nweDMv7jEurGjrre3rK2rqpqdnJOTop2GkaOstsS3qbGxqbOqt77Dvr/HzL2wr6igsr7IwKmvvL/Ew7674ujd5dbXxL/HyMW9t7uyw9fO1/KAtZGBdpaRk4d7jYGFkYN6enaAgZGcoZaJipCSe3iCfXqJlIF1c39ybn6IcWB+gYl/foaVnZxub36FdYKInJajZ7qUkaV7dX6JoKariXp6dnKViIqFg42AgIp5bWBeZ2lxb3V9eGJudpmZjYibgY17hXyIgYeGcHhyZWpzfXp0aGaAcn6OnHlpYFdlWVlYgX1yc257f3Z3jJmWjn6BmI9+kZGFenZwe3N2hYmKmouEj5OTjIJ/k5mHdW5lanCChHWhj2NleIJxamliZ3Z1bmZ1iXl0dmxnanZ3bGFpqo1fYGBkgXNzcG9faGRbdIKCemliZmhYWVNebl9gaV5iqlFaX1+AYlpYYF5bU2dlXmBwdWxbYGBuendwfYlna2tzjIGBd25xcnZWoW5jYV5OTmZiVVBLR0dLe1w/RE1UW2BrYlpUXGxzc1tIX2dmYlxXZHR6W1xkaW5lZFxRlXxmYXFqXVpkYFZPZHptb3FjbGFaXFiHeWlUY01WU1BYXlRWWVhmfW+AZ3BxdHB0a3poWEdda4FQdmRwi3yKcYyCY1tpdmhkXFJcYFhWXmpral9eWm9gTGVkYWBjWWNlWl5fX25agWtwdXJnYGpkcHBdZntveX6FdHRzcGRhZ19xfXV0Y3FoZl5ZX2JdW1FZZ2tpYWRvbWNxeXhzb4l/a2lgXWRpa2RgbWSAYmJUV1ttcHBvaF9gX1tcP0RRVk5YXlFPUF5dcmRWT01QT1ZnXVZmYlZeXGJcXlxlaVBXVlBfTVhYU1ZnY2lvYmp9cXBrdnJ5goJ1gVxhYV1XUF1SWVxjX11dWVpZYV9ZXWJZVFpVXlRRaXV5a2BVZIVzb15ge3RgaWmAeW1zcnSAbWJgWGdnX11lZ2lmYlpygHpjW3JoVFNUYmNbYXV7bXNeVEhPWl9aU1dYVk9Wa4JpfW1gXFJXYWVhZYCJf4R5b1tZXmNpcV9ibmFqWlVcV1RRXWdnbHN6e4Byg4NmYGpwaGlmWGBygH6Okol8e3mAnqKZnpCEfXt7eYaIka6GhpCAjG15e2tud4N7dX56cG+FiYOGd3luaFVsanNzdHVnZXN4gWpXYXB7cnFpgIF6d3Z2bm9vZ2psbmlocWdoaGtva2teY2VaZFxbW2hjYGaiWGBeZnttbWNmcGlfaYKMhH97fGx2eHKAgn19fHBob26FiXBoenCAbnBoaW5zbmhYaXSAbGdjXVdOVFhpaGZqdHiAcXFta3JuaGJjbmp2dXh3fXZ/fXV2dnl9dHBkZnFxaWBjgZeCeW95cWhYYWpvZ2ZrYlxfXFVXZGNSXWpweoV3Z29vZ29mbXFzcnF6fm5kZWBabXaAd2NnbnJzdXFplpWMkoaCc3FzenZya2xpdo+GlqmAl21hWHh0b2RYa1lda1tTUlBXVmh0d25laGdsWVRfWVtreWdfYW1fXWdyXFJoZ3BlY2p4gHtPT15hUV1gdG56Topna4BYU15uiImLbF5fX1yAdnhxcHtrb3VnXExKVVRhYWRpYUtWXXRzamx6ZW1bZWJva3RvWmJdUFJdZ2RdUFGAYGx/i3FjVktZUU1JcnBoZ2Fra19dbXV4cGFgcGZbaWdeVVZOWlZWYWVicmVia2psYl1UZGxiTklBRUleXlh9bklLWl1SUE9OTl1jXFJgeWdfZWFWWmRkW1FcjXVNTlNdemxvanJiaWlbdnt9e29la2xiX1poeGRtbmRmnU5WXFaAVldSYV1XTmFcWF50d25obW95fn53goZtc3eAm4mKh4B/g4dbuox6eXhnaIN/b2pjY2NkmnVSWmZtcneAeHNtcoSGh3VfdHl1bmhjbnyEZWZtcnd1bmhdmYhxbXx1aGZyb2VccoyAhYp9hHhwcWualIRvf2Zzb2x1e3J1d3J9jn+AdIGDhHp+eIZ2aFhxgJhQiHeEm42biKGZfHWFkIaBeW95f3p7hJGRj4J+fYd+bYmFen6BdXl7dXZ1eYRdk4aNkpB+gIJ9kY56h5+VnaGfkZSNjHx9f3eHl5SQgoqHiXt4fn1zb2FtfHp9c3WCgXaHjY2IhJ2UfnhtaG5wcWplcmmAZ2ldXmN0dnd7c2pwcnFwV1xrcWp1fG1oaXd5jX9vaWZpZ2x9dG5+em52eHx2eXmEiGtxcm6AdXx9fH6Hg4qLgIePiISFjY2OmJiNlXV7fXZyb3hzdnuFg4Z+d3l4enlzdHdtaHJvd3NpfH+GfnVodY98e25wiYRyenmRin2Bg4KAfnR1bX5/eHh+goSAe3OJk4t5dIl/a2pqd3p0eIqQhItwZVtlcnZxaHF2dm54i52GmYZ5cmlpc3dxbYaWkJKIgmxqcnuEhXl4g3eBdXJ1dXJseIKFgYaIh415johuZWpvaWtoWF5ueYGKjIV7f3p/l6CUnpaHeG5wZ25peJ99cXyAel5wbltrcHx6b319dG6EioeRhYR6dmaAfH18fIFza3lyf2VRX29/dXBriYB8fYOCbGxsZ2tqbWtqdHFwbm96d3RocXVlbWltbnl0cXKvYmljbYJ1d2hocW5mcIaQiX93eGRqaF5scmlmaF9TWFhwdVpSZl5sX2RdYGdsZl5PZG6AZmBaUk1HT1JhXlhaZGl1ZmVbWF5bWVZXYlphXmJiZ2JsbGZqamprY2NZW2VnYFZad41/dW15dnBhaXN1bW1uZ15hYFZWY2FRXGVpdH52YWZkW2RaYGVqaWlyemtgYV1WanN9cVxeZGVlZGJZgoN4eXBoW1lgYWBaVVdUYHdwfpCregF7k3oBe/96vHoBead6AXvqegF7rHoBe/96/3r/epR6AXn/erN6AgIEAID5lu/Iycz/gvPX6vfv1dXFwc7B09Xy5dnRyM/O09jQx8XR0/DQ0tTVztPa5NC7yt3awca4wqOtqKmSpLbcxMvP0IrHsqCbn7Kwr6ikp7C6rbOxq7rGx9fQscPIwraXqsWwqq+yscfIy9Tg1ciqu7W0w7m9vbvBv7mip6ymxc6yq4CfoJSdnaKsoaqqm5qgnaGfsM348M3Fs7/R1cnW4eLi3+PbtqyxusLKucrKtbK2o6jmg9jUyL/DxpeSxcWy3L641InTv66jn7C6r7GwuL+1raael4qblqCg+o++x7C2rbDE4M3auq6wucamp6OrrZ6l5JafmZqPiI6QnJeSlaChrICmlpabj5qis7GtkqKnrKeipa+3ts3Vsqyzt8K5srGQjrPTvs7NrZLJpZGdkZ2xk4+vmZLCn6WsvZmRj5WUlZSOnr7u68KtsZ2mp6iZorCzur++zbq/vrjHtr/BtauqwLW3tK+fpKucq7XQva/Cw+Dj3MPFt7uwubm1r6+yxL/OvYC+srq+wru407iuobjJt877x8O0r7OpucDBt7a8urGnpb26vrvP8PvZxca+wrGnubSwr6u2wri1v7/T2NnW5O+C9ercysW4ydK2sLG/u8W3sbC0xMTLzMfAtb3Kv7GusLSprLzC7czDtLOvtLunvLSzr6qqrZmbmZeRn7GhoKiclICanJyUp6ScqKGelZ+opZiQm5aamq7Gy93RrsPY18Ow1dDT1eDPzr+4vcO+xb60xd3Qv7y/q6PEvK6tp8G7vL7Uvru908LAxci+zs7BuLOvn5uZnayprLCxsqGanrKXiaSWipKgmpWcmqCwrb/Qy7e5yrGqvO/J06Ccr7+9ztK0pYChoIeJgKK1sKCTnquqvNaSl6iipaOnwb2rrLWz2tSyp7S+raGorbPFsqmkrr3Rx8W1oqKoubrKy8jO0dbU6PnMxMGzvLO8ur/Nvr7ArMm9wMO5tLi7ycDCy8rI3LOtxMy2trKqr73K5eL6gO3m6N309eHm1dTbx73Z86WE/LHG1YDZ9MuuqJ+mqr6wvczP1Lm6v7mqxMDIxJ+jrbPDsa++tqmko7bD1e7rzbjFwMbKzs+5qZufuLmsqamaqKuYn66pppSVmZebmZaLloyLg4z1i6KNn6unpY2rs6Cwra6qtcCtvKquoaemnaestbyqm6uen6qnobCampuhqqmakpGXkICPgfyFiJ6hqqWhqJeLmaCjnqmdo6mlrJmpsaq2wr61o5+aqLWys6uzsKupqKeooKOxuLerqK2qnZunqLKzp6Odl4+aoKGsnY6Zm7Cyq6ysraOclpqrt7Gss7G3sqmlus3W3dHfgLm5u9POw8nJ0srIy769ws+/y9TW1+Pm1NW/1YC1cqyDg4exX6mOnKWehoN5dYF2hIOYlYqBfn59f4N+d3KAhZyChIeGfYeLmIh2gpeRfoJ9g3F6dnllcoOmj5KYnWCRgHR0d4mMiHp6eYCLgYV6fYSHh5eMc4CBfXRdco50a2psaoCDiI+Yk4tsfHVxe3h8g4KIhn9weHl0i5KAd4BtaWJoaG94aWpmXV1eW11idJC1q4yEb32RlomSnJ+bmZWWfXSAh5KVipSPgoJ9c26eXJqel4mKkW1sjoNslH58kFyOg3RubXp+d3l7h4p+fW9ma2Vub21lnFl7hHJ1bm92joiQcWtqcX5yc3Jub2dwnlNiX15YXFdeW1pmXmtncYBsYWBcW2NgZ2tmVFpbZV1KTlZranFxZmFha3NwaG1OTW6KgYiBZFd3Z1FZTldhTUthVFFwWFxicFhXV1teXFdNWn2hmnloa1hhZ2hZXmttcXdvfG1ubWdzbnB4al1gc2hpYl5UWllPVVdwXUtaYHh6cFteU1xUXl1cVlVabGVzZXRrZnZxcG5rfWthU15wXnyhcWxeW15TXmNoZGFmY1hSU2ZiYmRwipJvXWFhZVlKX2VbX11gdGplamp5e3lsc35Denp2Y15QZWNVS1RcWWtVWldhbnBrcnVrZWVzbmtoYGFcXmxvlYV+ZHJqa3FhbWtqZ2JiZIRTgFBcaV5bY1tTV1hXUV5eWGFcXVZYYl1QSlBNTUxdbW53cVFhcHBgTWlobG96a2daWWJkYWliW2qBc2NiZ1RKXmFYUEpiXFhlc2VlZnRlaGloZWllaWdeX1hRUFFZVl9dXV1MUWFrW1ZjYFZfa1lNVldOW1ZkdXNnYXlhXGuZeIBXgFZicG+EimpeWVhFSFtgc3JdVmBwbIOnXGFxaGpfYHJvY2FkX3l6aV1fZl5WVl1fZ1hTVFlpe3FuY1JTXmlqdIKEfXx+gYiPeWxjVltYXlxiamBeXFZhWFxjW1tbZnBva3htbnpkZ3p9a2RiXWFyd3uOpVCOho6FjpmPi4F0fHFlgH+SZ1SmY3aGi6SDbW1oa2t8gIeCjo56dHtyYm93enZTVmdyf3Zxe3dtbWJ0fImfi3dpd3JxeXpxbG1gXW12eHFtYGpxYmlsYVxSVFhaWVZZUFZOT1JesmFqXHOBeXRecndqc2traXeBcHx0ent/gnV4eoONfGx6bWZucm57ZmVjgGdvbmBVUFlXWU+bUlVmbXNwaXBlXWhxdWx0aG5vbXRlcXVveomIgXZxZ3eGgH1zend2bm1tbWRibHNzbGt0bWJcZmt2cmxlZFtVXGFkal9TWVlrcm5wc3hvZWJhb3dvam1rcXJlYHKDj5SOk1FwcG+CfnN2d3txbnVoaWl2aHWDCH1/kJWKjHeLgItTfltZYYNBdWRpc2xaV05LVkpXWXBtZl5bXmBjZV9YVmRshGpucG5lanJ+cF5qfnZjZF1jTlNSVUJOX3trbnJyP21gVFVZa3BtYmBgZ3RobmVnbnJwf3VcaWxoX0hbd19YVVRRYWJobXhxak9iYF9nYmRqbXJvalpgYVx0gGxkgFxaU11cZGxbW1ZMTFBOUVNgeJ6Pb2pYYnFuYWxycnFxcm9cVV1hamxhbWpfXFlNSXNDaW5pXmBlREVnYE5vW2BwSmlhVlNXXWJjZWBtd2ZjWlVYUWBaXVeOTmhrZGVoam+JgItza21zgnJtcW9wZHWiWmdiZl1gZGJhX2JbZWZvgGRiXV1dYF9oZGJXW2FkYlZaZG5udHhrZWl2hoN8d19kh5+WnpODc5R8a3VrdH5nY3xtaY1scXOCbm1pbGtqZl9qj66okIKFb29xdWhueHl8gHiFdHh4cn15e39yaGx+d314d21vcGJpbYh2ZHJ2jY6EcXVscmx4eXZxcHeGfYt7gIF2h4SBfHaHdG1icIFxirOJh3l2em56e311c3t6cGtug4OGgomlrZSAgYSBfm+HjHd8fnyHe3t9eoaIgXmHk02TjJB+eXSGhHRyfIOChnB2dHl+fHmFiXx6f4yGf4B1dm1ueXiYiYl1fHd7gHWGhISBfX6Bb25tbGZxfnBudm5ngGlra2Nwbmhybm9pbXp3aWJpZWZmdoiHjYVleIaFd2KBgYSHkYOBc3B4e3aBd217k4h5en5rYoB/d3lygXp5hJCGfX6KgoaCiH2FhYiEhIV7cnBve3qAf4KCdHJ+iHZsfXpudH9waXZzbHZrb3+GeXKEb2p6qYiSaGVvfnmNlHlugGhqV1tldIWBcGdxg4OWs292g3l3cXKAfnJycmuHkHZpbXlyaWlvdYFzbXJ2gpqNiXxqa3N+fYeLiYSFi42WoId9dW1zbnp4gIqBfn11hnt7f3V1cXd/enyAeXeCb2p7fmtoYFlbaGt5g6BPi4iJfIuYiIt/dXZjW217T0aYU2J0gHuPfGBdYWNjeneAh5COdXJ7dGt5gISAYGZud4N7eYV8bWhhbniInI56aHx5dH2BfnFsXFpxenhyb2Ftemltcm1mW15ka2llamNuZ2Znc8lufmp6h355ZHV6aHZxb2l0f2x0anBrbGlbX2Fnc2hcaFlTWlxaZVNTVVpmZ1tTUVdUgFVLlE5OXGFnZ2RrXVJcX2BaYlVaXlhdTVdbWmVycWxcWFBhbWtqY25samJgYWFbXWZvbmdnbmdbVmBib21lX15XTlZcXmdcT1NTZmxoaGdpYldUVWJsZmNoZ2tsXlltfoqRiY5QZWJhc3BlZmRnXFpfUVJUYVRgaWdqeHhta1tuAnp7hXoBe7l6AXvvegF7j3oBe5Z6AXn/eud6AXv/esV6AXvlegF7j3qCe9J6AXmzegF54XoBe5t6AgIEAICz3szMz8i+5NjWzdzzuq+rtbvAusrS7tWrqaKxtsjQvLe5zNbYzs7IwdHXx8e8x8WtwreutpiwpbWysq3gz8bb0Liql6acobKrs8G1pLzLw7jNyLrGvbS+wM3Y2c69ysbAvsW6tLe6wcbGq6qnp8DFybiTpMTTwr+2uLC1tLmxr4Cho6q4wNXGrqqpl6Gjpq6mpqW2zfiso52sy9Lc9Neyp62loKimr6ytrqGcrq24rKTB0q+3ycPCppii7Y3Yvfi84PnaspulrLzAvruvr77P3tamoLGjsqSYlKKtsLW1ubO/xbarsLeyr7ugopTryrivqJqtoZudhJGWoaOEj5GRpoC0qqasn5mtvaOVoZidrbbD0drEv8PHyL3Ct5+ns7akkpOanZWlo5OQh4mWmIGNrY6PwbPPpLrL1tOm+of+haaDprGVjZiOhpCSio2OqqrOws3s07eu5Lzc4MzIuM+M6MvBpZ+foqOZpKu8y8TCyLbBy9razb6+tcKzrrS0u6qmx4C7u8fDwsDGvq+roqKqnaq4xri5sbGxnpybqrO2usXX9f+Jjt7b4+PkysK5sKi+uLO4trW+sLOvtq+8xdfl6uLu8d/PtrnDwsK8srTArKiuqLi+t9Lo2MjO27y8zcCuramuwc/Nu8Gwsqyzyb+1rqS8ycCvm42OkqWRmpmSk4mNkYCSh5GSmpyYoaWgmpympJyLjqukpKqfm6Ojtb/Av6irrru+xLuurrWzu8PFure9vLK3v8TNx8TCzsLBwbzFwsLAtru+w9Gwtsa5wcTFpazAqKmlq56gqKiuqZqZhouZlZGhopeXnbOurqGsxLC4ury3v7SqsLG9uLq/s6erpqaom4CEiaCXmJiSqZ2el5aWmbKxtJeXpa29vLSzqrLBsr7Es7K3qZqeqKWkpbStpLG9xMHQu8HHwcfIz8zNysfG0KnFwr6owreotazFzdnHuLG8wMfJvaa4u7isvLzBwK2vxLqtxMa0srDSyrrA1+XygIWD7evl7PbXy+Lb7t26wNHP1IDJvJ+zspqku7ytvLGxsKy3sJSMuKq1sKKvl5aemZSoq7CXpLjMzNDG07+ko7K3xry9ta6fm7S3opatnpqwraGeqLqsoaqUlpGYmJCLopyKgs3dzdq9nZmwxMrnzL+6rrnCsqqtoauWmp2knbXPvcyomZian6KjlZOciZmFhoL2+oDygIeChYqbiv2UpKydoqSTnJycmq6qp6arqqOrxL65pKy8sLe4tr2xqq+orq2fp6Wdm52Wl6Oftailn6Glp56lq6Cqs7q4tK+rs7u5s9HiuKWoq7ivsayjqK2goqOqoqqvtrq+q62ussTIvr+6yrettcK6wcfLx8vA1ffX0dvQw4B1nYmHioF9oZOThJCjcW9rcXd8eYCJnZFqZmZwc36Dc2xtf46PgYB+fIaNhoeBjYx5joJ5gGV1bXNzbXKjmpGjoYyEcnlyd4V+hZOIepCYjX2Qjn6JfHZ+fYaOlYV7fn95dXxwbnBze36BbWpmaXqChXpdboWai4J7d3J7fH11d4BvbXF7gZSCc3JsW2NnY25rbWx4haxta2JrgI+bqZBvZnJuand4f3+Dg3lzgYKGcmyJn4SMl5CVfnJ1omKVfqiBk6medWN1fIyOjo+KhomdnZt3e4J/hnFlYmJqbnh1dWxuf3Zmbnd0bYRybGKKlYR+dF9ybGdpW2FnZWpeWV9da4B3d29nYGZob11UX1NSaG1vdHt4dnNuc3d5althbm1lVFNVXFtiXlJWTFNVWEZJYExPeHB9X3CBjI5ml1eaUWtQZm5eV11US1BQSlJUaWiCdXuVgWpllnWMhXVxZHZVi3ZvU09PUFVMVllgaGFeYFJhaXVwZVZaVmFYVlxgYVVXcYBmZm9waWlxcGFbVVZkVmBtbmJiXFhYS05OV11cXGZwiZhTUIR5fn13Zl9cWVFiY2ZnZ2RsZmtjYl5mcXuBe3p8dnBvVVtqZl5iVltfTldTU2VqZX6ReW56gmdmdXRsZWBldIR8bXFpaWFpgXZtZV10gXdoVktLUGNRWlpXVU5RVIBTS1JTWlxZYmNfXV1mY1xJS2JcW19STE9QX2hmZFJVVl9fZmJXWFtcY3F0YmFoZFtaXmNrbGdpc2pmZFhmW2VnW2ZtY3ZXXWhkZGVlWl9uZlVhX1JOWFpcW1BPSVtaXV5eYVlaYWtcYldab19iZWdpcGVgYWZ1bXJ5bGJoY15gUoA9Q1tVWVxadmtqZmNhX352eVxYXmd1enVtX2FybHFuXmVrXVJTWFJWWWJbU15nbm19b3J2cHiEhn17fXptemJxbGdXbFpRWFNqb3ZsWFBUYmRsWlJmZmBfa2lrb2xsfXxldntpaWdqZ2ZqdYSNU1ZOlJSGkZGAdoeCkYttcX2AhYB+dltvdWZyenyDjXd4dnN7b11QaGdtaFpbVlxiYGBqcXNjbnmFh4J1d2daV2hrbG1nbXBqXmhzb2ZxYmFwbmleYHBlYmdcXlRUXlpRX2BaWJCQh46BZ19zhYuikoR7cXqHeHN2cXtudXp+d4OWhZd5aWNlZWlvYmNoW2ZTVk6UkYCJSlJVV19wYq5ocnhmaW5mcnNxcIF7eHd5enR+kouMc3iFfoGGfIuHe3tzdnZoa29nZWVeXWNfbmZrZWVvc2htbmNscXx7eGhma3dxb4eadWJmY25kZWVgZGxiY2FrZG1xeHV4ZmRiZnR2cHFte2pgbHZwfX2KhoZ5jbCUj56NgYBObl1aXVhRbmRhV2BvRD89RUxPTFZhdWdHRkZQVmJoWFVZb4CAc3Jvbnl7c3RveXlldGlgZU9gVVxYU1WCdm2CfmxlWGRfZHBrcoBxYnd9cGBxbl1qXFdhYGp0d2pgYmBcWmNYUlJVXGJlVlRUUmFna19EV2+AcWtmZF9naGtiZIBbXGBucYByYmFeUFxaVlpUU01bapJSTEFKYWpygW1UTFRPR09RVlVaWlNPW1tcTkZbcFpga2hpVUpOdkduWoBdbn51VklbWmdycGtnZ2t4fnxdYGlha1tVVFNYXGRqcWdmdmtlbnZ2a4RtaGGGjX6Ed2Z3cG9uX2tpam9bWVtebYB0e3FpZmNncV5UZ1Vab3J9hI6Bd3V2f3uDeGp4hYJ0a3Bxc3N5dnNzY2Rtc19jeGBijoWYc4CQlJd0sWGsWHFXdH1pY2hjXWRkWltgeHSPgoachW1ol3iOi4SBb31WkoF9ZWNlZmxia21zeXR0eGl2foqFfHB2cn9zcnp8f3FvhoB9foWFe3qAfm9sZWVwZnOAhXp6dXV4aGhmb3l3eICIoa9hXpaNlpSZhHl4dHOGiI2ChomHf39+fHR5gYqPlJGVkomIc3WFhIF+doCBcXBpbH6AfoudiIOKkXV2hH97dGtveYd/cHVwdG12jYR+eXOGlI1+b2NjaHxqcW5qamRobIBrYWlqb3BsdXp2dnWAfXRgYXp0cnhoYGRhcHl2cV1iY29xdXNmZ21vd4SHeHh/e3Fyd4CKioaHkYqJhnqCe4WDen58do50d4qChYSIeX2QhnV9fnJ1fYGCf3V2and2eXd+gHV2doB0fXJzgXFucHp+hHl1dneFfn+EeG50bm1vYYBMUmpkamlognR2c3BtaoaBhGlob3N9gX51Z2l7dHh6dHp+b2Rqc29xd35zbXiAh4OPgoeMgoaMkIqKiod9i3GFgX5rgndrc22FjJeKdm5wfH+IcWV0cXFsdHZyenNvgnlmeXxlZFtmYlhhc4GOTlBNjouIkIt2aXh0e29XXW1ucYBsZE9fa15ncnp7hXp5d3N8cl1Yc291b2ZqYF1nZGNtdHNga3eDg4N4fW1aXHJzdnd1dHNnXGp2cmZ1Y2N1dm1iZ3twb3ducWdrdW9qfHpvZpyfmp6Ja2V8kJSqloh+cXZ9cGloYmtcYF5gXGl8aHtkVk9PUVdbU1RdUF5OUEmLjoCGSVFSVV5tXJpcZWlaW15SW1tZWWppY2NkY1pgcWtpWGBuamtxanZuY2NcYGNaY2dhYGFcWWBaaV9gW1thYldeXFJbY2xsaFtZX2pnY36NaldcW2VdYF9XXmZbXFpjWmBobm1yXFtTWGZsYGJea1dOV15XZGNsZmZbcIVya3NoWv96vHoBe/56A3l6eaB6AXvDeoJ7/3r/esx6g3v/eo96g3mHegF593oCAgQAgMDQk9HO7Mu5rqe7zsa7t7Swq8C2qsi6s7e8va65vq6uubzHw7GqmI+aqLG2rrGmoZu0uaO9y7mzvLOqq6utnouXoaixwqGfpKGbn6Gips/Urb3fzMfGz8y/vL3S2Li9wdXCvMzf2LyzurmytL6+v7SppK3B1tLc1dPGw7TIw7asgLaupq2+uKyprJ6kpp2bn7O9ub/l/s7M187Ev9bYx6KpwcOmt6qOjoySjo2aqKmoqqWbpauutaGSoZ+tsunu18C9usXa0ruusqSprKOwssS/2ry43fr01rfq6MCksLnAuLq6sKqho5mJjoiQkJOkrKe5u5Wbo7O64K2jpKKqmZaYgJeSmqOqkqa1z8HHvs7AxLfHz7WmqLO8s7KuqaaaqLqHjpGWmImQmo6JjZ6ahYyahLHJvaONiI+kw7nI14W5yZ+JlpOXloGIlJujqae1pqKusqq0trrBuMDOztS+4u7W2fX23LCbo6Wgm5mttcS9tbq+6urZtcXXxbKprquztrbBgLbT1cPAwMHS2t7BuripvcC9qbDHyrCnt73P3uLIxtW7vcb08ezh4snIw7uxqbiiu7e0sqyppriutMfM39HUzNbVztbJw7q4ubqzsLzJxbnj2t7W2M/IvcTLvc+3w7a4urjEzr7FrZScr7rEzLzF18yyo6iooqKdlaDFwqusraefgKahl5mUkqW2qKilrqSorZyisamrrLK6x9zimpa2v7Ohmq+xqaWuuLSyubO3tr6vsam1vcm1usm7tLXKzsTQ0b6/t7O+ra2rtLSmta+jprG6p7Olppeip6CynZORgI+ik6KtwLKpp8PBtbW4vLSpt8ipoK2qsLOnoLKrrKinla6mgJijoZ+Si4ibo5+dn6Cfm6e/p6SpqKapwOLa1uTe3MzBv5+Xm6qjmJ6qpqKqtLrFr7TEz8nBtZ2rpLK5qp+0saaooq6yvr6wpqG1nqOuwbCsqr22p7uupai/qaKUoKu4wa+6tZevytzn1+f69u33jfbq7+Hb6eLf5+Xv7dng2MPBgMvF37GvreHtu620ubahq66onJikrZubs7y0rKassaStr7Wnq7q0trLDydKwsKaorrOxs6Skqbejqp2npJuwqK2+wbjCsqGpqrOqpJibp6yTmam5ubG8pK26tLS2trCtrpuhtK6gp5mXo5+em56npKqwmpKSlZeWmaCS8fL16/uNgJqaoZuRj5WZl43+8eT+k6Kysayntq2en6iglZKQlpuxvamYmJCTlaKnqbK4t6Wpp6atp5uduLGvoaOroJaio6aqs7W5s8LMxLy4sr3FsbOzuL67vLKrrK+5trK1tLLBrZ+dp6yxwb3Q4MXL0Mzh+8e9zcDLx9ba4OPu8+Lsy7fIgH+LX4iJpI2Ac2t3hX9xdnVscX58bIB1dnh1d3R9f3FzfoKHhnhtXlpgb3N6cXFnZF93emN/inl2gHpwc3JxaFdhbXeDl3Zzd3VrbXBub5SadICVjIGEioiBe3uOjnl4fZB8c3+OiHdtdHJtb3d7fXFtaG6Ako2Vj4yLgn2LiXltgHNzaXGEgXJvdGNjZF9hZXN4dICjvI2Cj46FhZGIfGdugIh3h3hjZGVrZWh0dnR5fn52enx9iHtvdmpueq2smn53f36RnYZ/fXaAiHyBhIqJnpKHrMutl36vk3ZrbXB5b3F3a2ZiYlpQW1pbXFx1eXSGgGFvcn6PqXxocH14aF9ggGJiZGRiWWdrgn55cHhzemVtbmFaYmZkZ21paGZcZnZNVFRWW1JRWlFUVGldSlNYRW+PeWhYUVRlg3yHnWqHjmpYYlheYE1PWl1iZGZ3ZWJmZFxiZWdwanJ/eXRie494e4qXgVxNVlhWU09dY2tgV1taf4R2Vl1sYlZSWVhgYmFogGKEgGplYWV3f4VwbGxgcXJuXGFsb1xaZGdwe35ocnVrYGaIkYd9fGhrYmNfTlpVb2hfZF5gZGtbZHZ5gnJqa3RlZ4JxamFjXWFdU19obWSPjYWGgnpuX2lwaG1jZWRxbG13eWtsW0dQX2x3fnFwiYNvYGNmYF5aVF1+gGtpamJbgF9aUlFMSlxpXltdZlpgYFFUX1pYXF9lb4OOTkllbmRWTV1gWVZfZmJfY2VmY21hY1hgZHBeYW9jW1xucm11dW9kYWFjVVZWWFxUVVVUXGFsYV1eVkxQVlNjWFFKRVtdWGdmd2plY3FuYWlkZWJUYHJgV15bYGBcVmRfX1tgVWtpgFhjYmJUUlJkbGJeXllaWWN0YWBpbmhjdZSYlJqNjoR1blVPTlhTUVRcVVNbX2VxZml5gHZ6clleYGtvXVlpZV1hXmBla2peWlJbUEtRWlRRU15eXGZeW19uX1dYY253fWlwaVJlcnJ9fYOUjYOZV4mOkYqCjIiJjYWXmoOJhHJzgHx6hWpwd6qleHaBf3hrb3tsZF5eYl1WaG1oaGdqcmpudXFvcHZsbWRwaXJgXlxaWV9ga2hvbHBednVyZmFybXJ/fHh9cm1ucXRpYFtiZGVWX2t0eG52ZnJ+dnl7fnh3cmFoeHpud21yfHV0cnJ1c3d8cGdhYl5faHFkoKSimZ9bgGZlamlcYGZrbGW3rqnCcHZ3e3R1hYB1eYF7b2lobm6Ej3tramRram9yeYeKiHd5dXF7cmZqf31+cG5zaWFobG1udIKFgYyNg3dsaHJ1a2tsb3BsbWllam91e3R2dXeBc2tocHV4iYCRn4uLjYigpnhzgHV+fIqNjoeXpZegh3mFgFVhPVtedF5QRD5LVlBKSEhDRlRRRFlST1RXWVdiZlpbZWt0dWdhUU5WY2xyamliX1hubFZrc2RhaGFcYmRlXE1XZGt1hGhiZF5UVFZQUXN3UV1yZlxfZmReVlhsb1haXm1dVWNxb2JYYl9XWF9jZFtVUFZpdnZ9eXZwZ2JubmVYgF5cV15vbmNhZVlcWFBLSldeXV95hmdjcWhfYG5rYU5UYmNWZFNAQUNJQ0NLUU5OU1FKTVFVXlNGT0ZIU4KGdFlSXGB1fWNbXVhZYl5bX2hofnFqgZuMeGiDdV9UXml0Y2ZrZGZfYFlLVlVZWFlrdneGf2Ftd4CKmnduc3l5ZWFjgGFnamhpXmlvioKDeYOFg3F8gHBeZGpwdHR2eHlzfothbnRydWtpdW9vaXZyY2x0WH6YinRgWV1shX2QqFiKkW5daGJoalZaaG1xdHB7bGhsa2NnaWpybXaCfX9ykJ+CgY+XhWpbZmlnY2BwcnlybXN5nZ+Pb3eIgHRyeHZ8f36GgHubl4N+fH2MlJeAenlxhIaBcHaEhnFseH6NmJ6JjpZ/fYmkppublY2Jf3+AdoV/m42Jj4GCgo9+gYyNlIWIg4p9g5WLhXl+en50coKGiXeko56ZlYd7cnt8cHhvdXB/eHV+fXJ2ZVJbbnmDi4CBmJKAcXN1b21nZG+OjXh4eXRsgHJvZmdhXW98cXFzfXF1dWRncGtpa21xeoySVlBpcWxdVmlsZWNveHd2fH1/fId7fnV+gY98gpGDeXqNj4iPkoZ+eG94bnR0doBwdXBwd36KgX57eG94fX6Le3p0aXl9dX6CkoF7c4SCf4J+enlqdoZ0a3NvdXNsZHZvb2xvYHZygGNubW1gXlxvdW9tbWlmYmp9aWhwdnNve5yblZyVmZGDe2hkZnRvbHF3dHF0eX6JeXyPk4eGemNoanZ6a2x/e3J4dHyEjIp9dG59aWZrdG1naHRtZm5pZmV7aGBfZXN7fW11bFJjbnV+fIqYjImWU46OkIh/h4CAfnmBgnF7dGFigG5qeF1mb4aNdXN6gHprbHdtaGJma2NdcHp0bmxwem1vd3dydXxxcW15dX9maW5mY2xuc2xycHNjeXV0Z2R4cnuHhYKIf3qAhIZ7dGxxenxqcHl/g32Eb3WCfH6Ae3VybVldamxhZ1xgaWNZUlJWVVdcUk5MTUxPV2FWh46SjphYgGNjamlcXGNlZFudjYCSVV5hZl9eamVZXGVgV1FPWFZqcl1OTUpQU1xiaXR8d2NlYmBpYVhdc29wY2JkW1JYWlxbYmtubHV5cGZdWmNmWllYXmBdX1pXW15mamVmZGVuYFlXXWFlcmh6hnBwcW17g1pSXVFZWGVoa2d2fXB3W09bA3p6e/96/3q7egF7/3r/ev96tHoBe/96jHqFeYt6hHnyegICBACAy8HCubXOysbAvcLS19i+wLXMvsCz1sbArsnLrquyqrKqn6GflJKZl5WbnZP/gImXl5SasJaMlZKRg4iEgIeWlouhtaWdprizso/AxqakrcrEzb2rrMO9ycHQx8rXxbG4zffeusP6yrq9sLfIrKmvprHBsK+wvcjGu9LMsrO3uLSAvLK0tMnNvs3oz8zX0byvqa+6u8DGuKy3t9LNuLS/sKatoqipoqelo5qgucemjZOcpae708Gumq+9s62ssKu/sZagrZm8pKCVmZensqSiraOrv76prcC1yefY3czCtLLHqrvPtrOeqIqElbPJpZeYn5+enqibnYeGoZqKj6qskI+AlYyjnaOgnqK0tsOhubOfsayooqWuxbmplpSRkJGXmJGJh46PkIOGhZWYoNm4jIahzcKenImCiZicr5aZlISGhKSiiYqRk6Wxr56ets+2pK23wcOtuNfByLm11bTGwLXFz9LIwsjbpqatrq3FycPchIjj6cKxtbSsvNTbwrXCv7eAu8S4pLCtyvroz9fV2eDl/rSctcHBxsW3vsjAwrS/1bm9uLqyuMTDqLm5uKa4vZ+rsbC1raqwsqqmqKm4qbewt861ra2su7DFwaSztr3Ku8Cvucq2ucHWyMbR1Pbky7m6rbjL2ru+s7zBxcrLyMu3xczIwKigp7W4vbOzq5+spqiAqrG4urCqvr64p6ez0sGwsrPMusK5tr/FvLWgn6S3sJ2lpqahq6ersLq8u7e8v72trLHIzq+et6+nt72uurq+usi4+YDex7Krr6Ctq6KbnqSYsqmjpbDDsbWokqCon5Gjm6CjsaabqbS7ubzE0LSotrert7enpaGqqqyinJycrqiAmpqovqWZnKC4tbKmtLajnJyeq7DAz8u70OLZ1d3c0b+nqKmnvr65rJ+mr7GlrrKhpqKmp5aNkJ+5w8Cuoay6oaKvsamwppmcr5+qyOGtm5qyp6XJtZ6mq6elpZCWpLDBurCtt9+qsYTM6eXJ0tLkwrS/wb3X0sW+yMnL6N/w18yAxMrF07G4wdKuuKiRnbitn5iNiI+mkZTH3M28r7msnKGwqqiqq6TJpafl58bCtLC5xbiqvrWfmau1qpmopL6hmqmyqbC7s62ytbSxpaCeno6JnLTEwbOgmpuit7Ovqa+hm5+loKuij5+yuaacn7OnsambnZeSmpuLkJOblJeQjImAi4mKkIaLjpOKgof7gZufjpSXo6avw8K8sKGgoJqNhYCPi5GhmYuerqmfo76hj4uGioSIlJKeoqOmoKSmqKqxsbero63Ezse9tJqgpbe/ua+vvsm9vbywsrbBxca4s8m5pp6arqaTlKWiq7yqpsC5raK/vrW+vb6xsM/VzOTc19GAl4aHfn6Yk5KOhoqZmZmEhH2NhYSBnJCKf5mXg36EeoR8c29tYmFjYFxiY1iQSlFeXlxhc1tRWllXTVNMSlNhYldoeW9rcXx8gGCNkXNxdYeAhXpwc4iJkIyRiIuVfm93h6eWeoGrf3d2b3WAa2dxZWl2Z2dpdomIgJWPdXJ3eXGAe3l6fpGVfoiWh4OOi35zbHKChIOFdXOAfoqHcnZ/dnR6bXiAfnl/eXB6h415aHF7fX2MoZmMe4eJfXh2fnd/b1xib2eFc2hobXJ+enJtdmp4iot/eIR/h5KHkn9zamp5Z3+CdGpdYVFQXXmGbGVmbm9ma3pubmJgcWZbanp6YlmAYltoXlVhYlxpc3JXYF9aYFRNUFpgdmdaVFNVXlhfWldSUlVRWU5MTFpjZ6V6Uk5lf31qZVVWW2RjcmFhW1hbVW9oWFNYXmx5dGFdbo91aGpxdndjbIl0em5oel5lX1xrdHVuZWuAVlphZmV5e3F+T1N+gl5RXF1WXXJ6Z11nZVyAY2xhTltfcqCKb3R2fIOImWBMX2hmbG5kbG5nX1tlcmJiVVtQT1taSVtcX09calNeYFxlYF5laFhbWV5kVF5fY21bXltXY2FtZE9VUVZfaGFTY21hX2lyZWhseI+DZV9gWGJzgWNnX2hpb3RucHFhcHdzcFpRWWNma2FkXlNcWVuAWmFnamBbbWhoWVxofnBlaGh0b3h2cXl8cGtaXV1oY1NZVVJRV1lcXmRoYmRpZ2pXVFxtdFdKX1lWZmpfZ2Nma3RqpVCHclxTUlBUUFBTUFROZlhbWGJuX2NdTFteVlRXWl9YYFROYGRoaGxvcmBZZV9YYV5TVFJaX15XUlVYbGiAXFliclpQVVhoZmFYaWpZVldfbGt0hIR3h5GKjJGLfW5cWVlccHBoXVJaX1tZYmRUXFZaZFdNT111gnlpXWd4Y2VpbGNoXFZSWVNXZHhXSEtaVWF5alljY19ibWBja3V/dGhpcIVsbVFxgYFqZ3l4XVteZWNudmFebHd1g4ONgnaAcnlzfHN5g4hpe3hhYntzbl5RVFFXVFZ8jnt1bnJuXmJxcGNrbmF6Xl2GeWluY2ZnamNheHptanBrcHR7anpoYGp0Z2h3cHR1dXNxY15jYlNQXnZ/em9cW19meXJtaXRsbWttbnVvYG6GlHxuboB1gHprbmpqcW5bW11rZmdkZGOAY2BdYllgYWlhXF+0XXJ1aXF1fHqAj4uJg3Z5dnJmX1hiX2V1b19wg393e491ZWBZYl1gZ2Rzd3d7dHl/fnx+fH12b3aFkYx9d11iZXZ4c2pocnZzb21laXB2e351b4eBeW5uf3tqant8hZGDf4+IfXaOjYaNh4d5bZGclaumo5uAZllaTk5kYF9aU1hmaWhUVEtfWFhTbmNeVGpvXV1iYWhkXV9gWltgXFteXleRSE1ZWVhdblhPV1ZWTlJOSFBcW01dbGBbXmloZ0hxc1ROUmJcX1VFR1taY19oX2JsWk1UaIN2X2uQa2NjXWJsV1RZT1JhUlNSX2lnXW9rWFZcXVuAYl9iZ3t/cXqKdm5xa2JZVVZcXF9mWVNbXGpqXFxlWlNeUldeW11hXFFVZGpSQ0ZMTlBic2laTlxbUExQV1NWSz1EUEZgUkpHSktbUkpHUEtYZ2pYWGRjanFpeGVkYF5lWG14cGRWWkhHWHF6ZFdibGxgZ3Vsa2JjbWVfaXp5Yl6AZWN2ZmJraWN2fIBna3Vpa2RhYWRnfnNtYWRnbm95c21pbndwdWhmZnp9equLaGV5jolybltXW2hodWVmZV9dWm5tXFleZXeBfm9oeJd7am91eXxrcYt4fXNugmZvbmt5f3xya3KKZGlyeHiQk4iSWV6bo4Bye3lweImSg3uFf3mAf4Z8anV4jLWiiY6KjZSYqnNecnx/hYV9hoqHhICLloOMhIB2eIKCdH9+fniBkHqIhYeQhoOLkYOBe3qAbn97e4F3dXJxd3eDgWVtc3V9endufIZ3dHeDfHp6gJSKcm1wYmx9i25yaXV2eX99foJ0gouEfmhfZnB1eG9xamBqZ2mAaW50dmxoeHNzZWh0i3xtb29+dX56d3x+c25cXmBrZlZdXVtaY2ZqcHqAfYCFhYd1cnuPlnhpgXt1hop8hYGDhIuBpFibjX1weG90bWtqbG5pfHFzd4SQhIyCb4GDdm1ycHJyem1ncXh8goaIjHdrdXNsd3doZ2VucXBoYmVndXKAaGZwfmZcYWZ5d3FkcnNjX15lcW94h4Z6ipaWm6CWjIZxcHFzh4qAeW9zdnNweXhrdW5udGVaWmuCjYZ5cHaIdHeGhoKGeXByfW50hZZwXFxvZWp/cmVqbWxoc19kb3aDfG5qdYttcFR4jodwcnuAa2Rla2lydWBZZGdpe3mBdm2AbW9ocml1fYVrenBcYXVtZ1xXUldfW1uGm4iCdYB8bGd1dXByd2qFZGaTjnh8cHl7eXFtgn5ua29tb3J9bH5xZm56bXJ/eXl7f3p3a2tta15daYGHf3VjX19keHNvaW9kY2BjYGRbTFptemRXUl5SXllMS0lLVlZFRktaV1pYWliAW11cXVVWVl5TTVGUTmFkU1dWW1xib25sZVhYWVZNR0JMSU5eVklXZmReY3pkVlJNU09OVFBcX11gW19kY2FkY2ZfWGBweHFkXkZKUF5lYVlZZW1nZmRZXWFoamtgWG1nWlFQYVlHR1ZYYW5dW2tjVk5gX1hgXlpNRWZtaHpycWusegF5/3r/esR6gnv/etN6AXv/eqJ6g3v/eqR6AXn0egICBACA4snU18Gxx7q5tcPH6dfLtK+0ta6orcG8qaSvpKqtoaGXmKOokZqflZuYlZGTkJ2lnqKeqp2TkqSktryxrr2kmo+QnKWVkY6Sorz20tXGs7KyubS3rbS2x9Tpwr+p8dKipJ2hoaOrsa6zoqewrba61dTUtrW7trPMx6Szw8u3s66AoqWzpaPE1qeNlLm2uMTOsrmwxL63lpOcmqekoanPuriuoKOsu9Ddvbm+rZqNkJ6XoLrHmo+dqbC5xca03pfM2sXR58bMyrCuoKa+ra+qrqG2w7S64Onc8qv+3c7Gr7a1qaqqnrS3y6WnrLSusLGopbK2mpmjoZmPqKKG/42OoKKAjImXqq65urOlnJ+hvKmeuru1vbrSsYOQi4eQiYSD8oqIhYD6joqPhvj244mIkJP6rvqnk4qNkp+IkaaLh4aQupeNjYyVjoiPmaWfoPHHyL/CvNDn1dDx4/zT2tfgy7Oyws/BvrG1oq+eur+mk5CQoLrgvqCen5idrLS5urjOuKKAnKfh58797dbOz9nX/+zS+v3Q2ce/64DF5M/P19G8p7fIyrizrKykpq6woaiuqKqnuMvAuLGpqrG4oKC0vLatqrOlqp+qtKO0rLi5vcXIur/DsL3BwcnHztPMxtPcx7u9yuTQydjPyenIw9TTx8WvqKadj5ahoKicp6Wgn6Gep7OArKiqtayqtqu0sbOutsfHwLXD1sTIqarU0MLE0MDBzc62oKCgnaOrr6yvr7W7sKimm6Ont6elsrq2v7essrOur7GzsLm9rayvvqqluLGYoquytsvBsrXJ7cG7sqamtbuqs8GxrLu/q6yrprezsLGvq6fCv7eys7CprLq1uauhpraAtbCvsrqnrbCysLnCt7CopK7G4dO3zuLPtbPipo/GtaOena/BycSvmpmblYearaahqqWZlaqjnbq5paynpJOYo5+fnZaal6SmmpubqKi2sKWzmJ+ynJmwoZmIq6WRkqOprKSgr6y3tPbm0sK2vrTHuKe3us/Pxd7uv6/U+P734cyAsrywtquqpbaumYSFiqu2p5GXpLLZpJ6cs9W/tKucipC4w8PAqaytsKinxby9t7exs8DOubOoqZ+jw7uaoKKts9Xmw8G2r6WooKWbpKCdnKSgo66uwcnCwrG+rqSptbOknpqKh5OYmJqhsJOZj4ucvcWzqJyXkpqPioTKsqi2p6CAj/vh44qKiYuKgfD6/YfwhpKVmqOfpLGzu8S9wcS5pqeVi5ibmKqqq6OhpqGdmJGXmpuOqKOglpWMlZWZoLC6r66xuMfQxsvBy8XIuqi6zeTm6vnK4ezX18yz2uXMw7Cmramsram3uLeqsMbcy9TBtcPKwcfH7dzGz9zWzdPPyN6AoYuTkIF1hoF9eIWGn5aRe3V6fXp2e4yIdXWDdn1+d3JqanZ7ZWh0aGxnYlxaW2ZrZWhmcWRaWGdncnFxbXhmXFhbaW9jYF1eanytk5WMgH2Bg3yAcniAlJ21jIp0sZZub2doZ21ydnV2aGtwam1tiY6BeHiFd3iLi2Z1hIZ7d3WAam9/bmSBfmlaZYN8foeVgYN6iYeDbWppYWZnZ22Ofnt1cn6BjJqmjIeHfHJocIB2eI2ZdnF6gIGFjZKAml2JmoaTsZWfmoOBeH2HeHd1c29/jYOHmKSWnGmolXx7bGxtaWljYWtyfWdoa3FrdHRudHtzYWl3cG9ocnFau2NgbW6AXFlhZ2JudW1eVVtYXVdUbmRiZm+IaUZGTU5bWFFSklVUVk+UVlZYUJeSi1hZWlmRc6duX1RZZXBcX29YVVJij2laV1ZgVVFYYWtkaKqJjIiJf4qdioKdlKeCgn19bFdTZnNnZmBhT1ZKY25dT1FOW2qMbVJNSkRGUFteWlVrW0uAR1CDjHSbjXxyd4B4l4Fpg4Jma2NhiE5nfW1scm5dRlBcY1lVUVBJS1NaT1ZXVllXZW5kXVpVXF9qU1ZfbGJdWlpQW09TWU1UTF1ZUllXWWJeVVxeW2JkX2RiWnB0XldVXXBkYGpmZ4BnYW1vZ2hbWFdSS1BXV1xWX15ZWltXXGyAYl5jamNgamBmZWdkanh5c2l4g3V3WVd/dGtteGxtdXhkTEtLR09YWVhbXV1mYFdWTlZaZFdWYGdjamNeZGVhYmFkYWxqWldcZldVYllQWltja3NxZGR3jWdiZVlhaWtkYHFpX2VpW2FbVmRjYGBdW1ZwaGFgYWJaWmRhZ1dUWWeAYllXXGVVWVlcXmZvZGJfXmN5mY1sgIp0YGiLZVVxZ1RQVWVveHNiUE5NSkdUYF1aX15XUmFiYH56YWhmaVliaWdkYFhbWlpVUk9HTldhXlJZUEtiVVZrW1tXfnBeWF9pYmFiZV5YVo+AbWxaXl9mXFNWZ3FxcXqIaWp3j52Wi3aAZm9mZ25paXJpXlRVWm59dV1ZZnGCW1laaYhwbmJYTU9zhH5yZ2hhY2FdbmJdYmhram11cHBzdXFseXlvdWlmcIiPfn9zbWpvb3FkbGljY2pmZWpten52eGl0bGVteXZoZGZfXmJpaGpxeWFoY15rh4+EenBqZ29rZlyVe3KFfHaAarimomNhYGFiWaSutmOxY2trb3Z3fYmJjJKMkZSHdndoYW1vaHt5fnd2fHVxbWhyc29jf356bmlnbm5tdoKIgH+FipSYio+Ae3Z5bWBtfo+Ol6CAlZ2NkYtxkJiLe3FvdXN1e3iCg4F9e42jkJWIfYqOiI+Qqp2JjJyWj5CRjZ2Ac2FnZVVLXFVUUVtfc25nUk5SVlNQVWVjVFRlW2NmYmNcYWlvXWVuY2hkYFtcWWVpZWpodmVbWWdmcG9sZ3JdU05SW2JTTUhHUWOLdndqW1dWWlNVS05SYW6BYF5LgGxNUktOTVRcX2JhVFldV1pdeHl1W1lgWlpsaklUZGdcWleAUFZjWlRtbVBHTmRfX2duXFtXaGVfSEZKR0xOTlJvaGRZVV9ndYOIbWdpXFBGR1FLUGNoRkBKUE1SXmRYYi1ecWRwinB2cl9ZUFdXTlBPUk1cZ1phc310eE6IdmptYFhZV1hbV2JtclxkZWlkaGlwbnZqXGJvaWtnbGxbtWBcbHOAYF5sdHN8g3VqYmhobmxreXV7fYKSclFXX1tqa19lvWpna2fKcG5uZ8LBrGRgZGy6h7N4aV5eZ21aY3BZVlVmiGxkYF5kWVVfanNuca6PlIuMhZGjkY6lm6aKjoyLeGRjd4J2c21sXWZed4FvY2RkcoKhg2xraWRlbHN2cW2AcmaAYmuYpIyzqZaLjZaOrpl+l5h8gnl4nViAmIiLko59Z3KDiHt0cnBscXh9cHh4eYB/jJySh4J9h4mQdnN8gn95cnBrc2Vtb2RvaXhtcHhwbnJ4bnl4dXd3c3t0aXuAcGtqcIV2cXp6eZd6dYODfX5vbGljWFxkYmhhaGhgYmJgZXOAaWVob2hmbWJqa2xqcH59c2l2hHR3W1p+dW5veG9xeXxrVlZVVF1qbm1zdniCenJxa3V4gXR0gIiFioB5foB7ent9eICCfHaAhHhwf3BlbW94f4OEen+Sq4iCf3N/goR1dYR4dn19cG9sZ3d0c3NwbWqHgHh4dHZwcnt3fG5rcXuAdGlpbnRmamtqZ295cW1pZWl/mI1xhY16anGTZlyEemhnan6Jko9+ZmNiX1plc3ZwdHFpZXR2cI6KeH98fG1zfIB8e3Bycnp3aWhdY292cGZsWlhqX11xZ2FbfXNkXmRzbmVnbWJiY5uVfXJna2pxbGNkcnd3c3uIZ190jpeNh3KAaGpcX2ZnY3FrW1FLV2p8blpdbXWNZmVieJeAemxmXV57jIyDdHZyc2xrfnFucXd/f32Efnt1eXVvfn1xdGxoc5OWfH9zbmxuanBkaWVhYmllZW5xfHt0d2l1a2RpdHJlXVtPSk5UUVBVW0RKREJPZWldVUxHRExKSUJ9ZV1xa2eAWZqEiFZWV1RTSIOKkk+GTFRUVlpWXGVlaW9qcHVqW1xPSFNWUGBhZV9eZWBdWVdbW1pOaWVhVlJOVFFQV2NoY2BkaHd7b3RmZF5gVkhTZHR0eoVmen9ydW5Uc3lvX1NPVVFVVlJbW1lTUmZ6aW1eU15fV11fe21aXW1lYGRhX27/eol6gnuzegF7mnoBe6J6AXmiegF5hHoBeYR6g3mEegF55noBe/96/3qGeoJ7/3rleoN5hnoFeXl5ennxegICBACA4NrTwraxwtu5vMzDzsrExbSmlIaYmZ2tnouJnpidl4adlamhko6Ym5iglpKQioyNhI6VopWaqaSusquljZaPnaaop6acspaYmp29r622srzVy7+3s6q6rremno6CjJWUnaSbm5WRnaSyrrm3s6OltLKrrL3FvsbHt7OxxOrwvreAsqKvp7+prbDH2ciupLq6xcO7tb/Ks72fiY+foLiprZaXkJ+tqrLFn6aXobHBuLO8o7zJxrHEgs2trKK+xcG65NO6sLS0p8y1ssXEu7S7r7yyw8zNycrX9/Pt5cC12svEsLKqrcnLzrmzsryutLOxuc7uuJGWoaKxv56D8/egmJeAjo2Prryxo6CVpq/QtLCuoLewrLKuvankg5mXk46MjISNj5ONo5qVkZyI/oSSmaWC6vr1ipakq4+MkZychoqauLGsn42Up5uVka+oo5Kir6+ViJqesre7yby/t8nQzMXBwri1sMnsvZmkp6ainKOfmJqdmaCXoZuZp66zsbmxy/+AgPqG7d3U5LrJ0bq4r87Y083GuMbeqYK+ztnGxbKepcDEzcq/tLK0sq6cnqOhobS2vb+tpZyduLqtuba8zM/15c+0qqyruMC+2MLBy8fX4snDxrrM0cLAtrGxvMLAsa65vrrM2bukqdXhz8i/w8S8vamhn5y8v7+3tbCttLGzuNiA8M2xxsXCvMzg17iswr/C8ae7vta6mpebtLPohYvDtYawn6iboqWtscTDsrOupra3uKSxo5PbyaCpprq3uq+0r7vDtLSopLfDw7i6xrSxq7bGv7XFt760trilq52ooq21wtLn69fQurjCxcy9pqi0wcjTwce9u8C6pZ/KuK6bsvaAzKKgoKu4uri4sL25t7DNz7+5utTFvLCtvLPWz8GlnJ6yu8bDwLSspaOgkpGSk5ichZeSjo6dmpyaoKqnpKGjnJWVn4yZpaqpo62qrK+xp6q1q6mjt66cmJyVnJqcmp2To6agrcvi5dvqvaeuqKuio6avpcK7q7PJyLXQ7uD3z7iAlLW7va29rKy1painorbNzsHFuMHcu46UnqCjp56llJGpqayvsq2vraqtrbfQ3cKypa7Cy7KsvLejoqantqWgr7qntqqxq6admpWip62usa+mqJ+ptbLLycSypqi3paClpJqalpKinI6NlZafqcDHra+6o5+klZ2dlZCfpKWerLKAqZyQh4T++o6aieuG9/2LkIv14+niiKqgm6awxrWpp6GcmpKZnpyWnpidpLK4sp6anKarvLGsh4OLhZmatbu6v6mmzr6zrMG1pqOcoK6gtLy7wL/E5uzUyMja4c23v7ammqSrq7W5tbu2wLi2uLetrLC4vMG7vLvQ59HCt7HFxeOAmJiUgXp3hp2AhI+IkI+FjnxxYlpoa25/cWJgdHB2b1xxan1yYlxmaWZqX11aVVpbUFRZa15gbWl0dHBoVl5ZY2xucGNjcVxgZWiBd3mAeIKglod9e3aCeYZ6dGleZGlncXhwbWtkaG57c3t6dmtveHBqbYCKgIaFdXVvg6OziISAf2pwZXpsbHiMm4x7coeIk5KIhpaZgYJsV15sa35ue21tYWtzcHyNZnNreoudk4iJd5Wknouia518eXKDhIV6lI9+doGHfJaHg5KVh4F+eIV9i5WRi4mPlZ2ijnFxlYJ/c21uaoCBgHJucHBncm5ze42Yd15kbG93eGlTo6NoY2CAXFlZam1iXl1TX2Z+YV5fW2ljYWdmeWt6S2NlYmdeWlhcW19cbGVhW2JZl1VkZ25NiJSQVGR0dV9faGpnTldjgoBvZlVaamJaW3hybV9nampaUV5cZWdqeWtvZXB1b2hjYllZV2uQZ0pSTlJRTldaWVpeXF9UV09JT1RUVl5ZbJmATp1Zkn53hF5oblpaVWtzamZcUFxtX0VTYmxcXUo8Q19kaWdcVVVTVldLTVBNUWVkZmtbUUtNYGFXXV1fbG+Pg25dU1ZQXGNeaGJgY19pcmJZX1xkYlVZTElLVVtbUE5YWFdqdF1NUnt/c21mam1palhUVVJocGpqZWVkamhnb4qAmn1nenh1cH+LhG1lcnJ3nGRydYdzV1RXamiXXF6GfltcTk5GS1BZXG1tXFxYUmFjZFZhVkqGc1JVU2FdYlxgW2VuYmFWVmVwcGJrdF9dX2ZubGRnY2VfXmFQV1BWUlheb3uMj356aWdvcXVoVVZcaG90a29lYWFeTUhvXFdFVpCAb09MSlNeZ2JiXWpoaWN8gG9nZHRhYGBgZVtzcmtTTVNkZ3N1cmhgWVlaVFJSU1ZZRVJRT1FhZFtYXWlqaWxoY15ZYFJbW15eWFlVXGFeVlRpWldebWtYW2JpZ2ZgWFhSWWFbYXR9f3x9Y1pTVVhRUk9XXmVnXFNpd3V5hYWPf2OAUWhwb2x8amRtYmdob26EjYp+bX6LaEpWWVdZYFpZUlFfZWpmX2VjX2BjZ2l0eW5nZmdydWRneYBvZ2Bfd3RqbHNqcWtydHJpaGh0cXJvdXZpamJqd3OEgH1vZWd0amt2cWVjYGJ7cmFgZmtzeISLd3J/bm11bHJ3b25+hIJ1fXyAd21iX2HBwGtzZq9nwMhqbmm5pq2hYnVvbXJ/j4Z7fXtycWlub3Brdm1vc4CIgW5qa3iCkoaFY19nYnN0hYyLkXdxj39zbXhuZGJaYG1fcXJydXV4lZqJf3+UmI99hoJ4cX6EiZOUkZyYoZORk4yIgoeNjpGKi4aUppiLgHiKiqGAb29oWE9NXnVZXGlkamliaFpPQjtLTVBfVUdGW1heWkxiW2xjV1NeY2FmW1hWUlZXTFBXZVdZY2FqbmtlTlJMVV1fX1dRX0pKS0tkVlddV154bV5WVE9bUlpQTEM6QEZFUFdUVlBLU1lkXmZnY1ZVXVVMUWFpYWVlWFhVaIWRbGeAY1FWTV5PTlVpeWtZT19hbWtkYGxxXF1JOkJMT2dYW09STFheWWJxUFhPW2NuZmJlTGJvbVtoSmpOTklWV1pWc25fXWJjXnFkYGRmXltcVGBcZG1nY2NpcnmAdF9hfmlsYl9jX3p2dGlsaGZhZWhxd4ePb1RYZWl1cWJSnJloW2CAX1xhcnhwbmZebHWMcG93bHh0eX11hHaWXXR1d3dsb2xvbXdwg3l2bnduwWRucHdbp7OtXGh6e2JdYWtrVFtnfH11b2Flc2dgY4F9eGdvcnVlV2ZncXN5iHh7coCGgXt2dW1ua3+ieVxlZmpqZGtsam1xbW9mbGVjam1sa3VvgKqAV7JdppWLlnZ+hXR2cIWMgnlwZG6CalBoeINzcl9RXnh7gX91bW5wc3Voa29vc4iJjpKAc2xthYR3fXt+i4ynm4d1a2tocXZ0gnlxe3yBh3Zwe3aAe3RwZGJobXNwZGNubml8g21bY46QhIB3fn96e2llYl52eXZxbGlmbGlscYqAmX1neHpybnqIg25kdHR2mmJwcoFvWFVXaGWOU1d1blVkWFtUWl9obH5/cnVzbXx+fW98cGKlkW10cYB9f3l+eIOMfXtzdYOSjYaHj3l1c3yEgnV2dHl1c3JmaGFnZWlvfIeZnIyHdXV+g4l8Z2pwf4WLfoF5eHx5Z2KMeHBcbKWAhWRiYGhzd3Fxa3dycWuDh3dwb3xqa2lma2V+hHtjXmR3e4aJiH12bWtvZWJqbm1sWGdiYWNxdG9wdX6AfX6DgHp1fWx5fn93c3FrdnRybGdzZWFmdG9gYGppa3BqYmdhZGtoboOWlZCZeGVmY2pkZmJmZXJuYFlvem55ioWMemKAU2ZpaWd3ZmdzYWplam2Bh4R7eoKUdVZgZ2hobGRmYWhxb3V2dHZ3dHRzeHuIkIF4eH+DhHJ0gYJybWJjd3BnbXNob2VqamZhXlplZWZhZ2ZaXFdkb25/fHdrYWJuY11mYVZUUE9hWEZGSktTVmJrVVNeSkZMQ01QS0tbY2RaY2aAYltRTU6ZmFdgVIlSj5RSVE+DcHZrR1xVT1ZhcmNZWlVRUEtTVllUXFZYXGZsZlZUVWBoeHFsS0dNSVdWZmxqblhScV9UTlpPRkRAQ09CU1dWV1hden5rYWFvdGlYXVhQSFJXXmNmYm5pcmZiZF9ZVFdbW2BcXFhneWpdVE5eX3P/erd6AXvEeoJ5mnoBeZJ6AXmFeoN5zXoDe3p7knqCe/96hXqFe/96/3rnegx5eXp6enl6eXl6enqEeet6AgIEAIDQy8bQx73K5dS7rKy0uLCjr7KanKWikpqiq7CQjYaRkZGSjpiTioqWnJ+yqqeynJugrd/BmY2Oqaihq9qrsaSrpZ2UoaqipqqltKussLGspbHBw6axoJ6iqqqbkIuchfKAi5eUk5aPk5WIpKWfnaiUlp6mxrjCubazwq6ekKy3tIC4srfLzdOkt6GhvcDHrJ2ZkpqetLOosaijramel5aRl5+WmKKqp8ayoKOmu7uz0M2uvsPb1sy4x7u9qcq/s6+vp6itn565rqa+saqsp8K6wdXi1M25ucjD0uDevp/X2bu3r7Sztqe7s6+ypaOdnbKzvMuinqKdp8WnkImBh4KSl4CZkJCmpKOvrqGdrLahn62ptMW5q52mj4H2i5eJjP2Hg/+FiYSEkaK1lpSJhpeprpiM+fSMi/eFgfKPxc6vl4eMxK6XoY+Zoa6qprakj5efur6gk5aSlpyxsq+pqbGut7e1paO01djNrqWxnJmYoaauqaKema2jrrGuuNe6usDF34De2dPAubvCwbqxr7i8tby5w9bBwsXazNbEzs3EtbzT9OHNrbO4xtHDwMG/t73GraylqJmcnqmipKesua+1vcjOvKqisLLF7sLBvszMxsHLwcHGwrXAs7G7tbC5sJ2JlIm4oay+tK+ww8S/vrvAzsq5samhq7jAwMDU6evez83g/IDh0Le0t8C0qKSpo5uoqqSknZaIkZOVo6iepsK3wr67ub+unZWpo7WtvLqlqZ2tprKsrbeqqLS7tbKoqK+yu8K/xLWyqqKjs7SrtcHMztS+yby4vMuysrewv6OVmaKYl6e50OnNzsGurKimrbOutLjDuMHCuMK/usO9xsPM2bqdr4DBlI+drr2kmZOvsLGzrLiyyN/nzdTg0tXR4rOmsautwc69qLKsuLjLuZ2npqGmno+Ml6CWjI2Lrc3I0sysraKVmKafn6GmwL6wqqKeppu4p7rAq6iuuLKciZaepKa5tMPMzb2/1s/AxL2y1MWysqWplpmSkaCnvNXG1OHiws+0tICetbS2qKOnsLK3saGqrbyqvrzhvqulrZ2gmaKtoK2tp6+traestMG3q66tsre8x8a4qp+ypbS3s6mmq7HHxamkvLa0sMS8rJ+grriqo6e0xL23trK+ucK8tKCmubazqZqSjZmanamjmYuWn5ibjY6Zla6utKursZ2FkqismZybm4CgqaydqaCUkYmPj4qTl42HjImOl4by9vmPmq27rqqmopmjmJ2QiomJi5yNi4+KnI6YoqKgrcXNrZCbsLK+vc2vsrW+rqOissS+xcrXxe7l2dHL2LK9sq6rp66+rr7AxbGYk5minpScnrC2sLOzsbSlr6+tu6m/wMHXytXcx56ys4CSkI6VjYiWraCHe3+FiIB3gYNsa3d0ZWhxentlYF1kZWRlXmtiVVllbXB8fHR6ZGZmcp+KZltecXJvdp90eGt4cmtjbndub3BpdnByeXt7cX+LknuFcXN5fn51bmp+aLlgZG1nX2ZjZWhifnhqZG5gYGprg3mAdnFwhHpvY3d9fYB8eH2PkY9rfGhqhYqZem1vb3V1hoF4fXVzeHFkb3JqZ2phYG1xapCFbnl7jo2DnqGGlJm1spuGjoSHc46Hdm1wZ2x0aml7eW+Gfnd7a4aGjJqqnJWBenl4iJKNfWOVjXh0bXFpbWZ0bGlsXVxaXm9zdHtqZ21na3tlXFRSV01eY4BmXllkXllnZV1dYWpZUmBib39zZ11mWkaSWmpXYLdZVKZWWFVVXmmCYF9aU26DemJWmZNYV5dUTotblZp0W0lWhXJgZ1lfZHBqZnZoVl9mdHRbVFpWUVJbXF9aWVtUVlVUSklYd354WVNTUE5QWFtiYlxbV2lhbG5nZ31kYGZqg4CAdXFlY2dva2BXU1tbVVlbY3VfXV1qWl5WX1tYTldui3hnT1RWZHBkZGNmZGl4W1xWVktLUVdVVldbYFdaYGdsXUxFUlZkh2RhYmttaWJqYmVmaFleVlddXFleVkU0OjtURUxaV1ZVZ2ViYF1icnJhW1hTXGdvcXKFnJ2UgYCTpoCPe3BxcXZvY2FnYl5raGhlYFpPV1pbYmZZXXBnamtmZWhbSUFQTlxXZ2VVWFBbV19aW2FXVGNmYF5XVlleZmprbWVjXFVTZGJfZG95eHpmb15dYXFfYWdhbFRHSVJLSlRkd41yaWNVUlZQWl9WXV5rXGReVF9dWmFaYl5kbVpIV4BnQD1KWGlTSktkYmBhWGRbZHJ7bnt+cG9tgV1XYl1ccHtoWmRfZ2mAcVleXlpbVk1LWWFYVU9JZ4aHjY92cmpZW2piXVlZdWhaWFVTU0pdXmluYmJrb25kXmJramdwb3qGhHJzfHBrZV9oe2tiY1hZRUpQQ0xWWnZ+i4V/cW9sY4Bcam1yZWViaGtxcGZya21qgnmJbmlTWVZaU1ReWmBfYWVgZWNgX3FlXWFob21qbm9taGBjXGZrbm5yd3J9g3x3fHJzdXx3cW1sdYF+cXR3iIGCh36Bf4SAdWRnd3l2b2RgXmlnaHBvcGdqbWRtZ2ZsZnh6fnd8gndncISJe318eoB7gX5wfG5sb3B3fHN8fnRydHBzdme2uLlpbH+KfHZxbGVwam5pZ2JjYXFhYmdga2hve3p4hJuhhWdtgYCKhpZ4ent8b2NeaXlxdHmHdJiQg4CBjmp7dHZ4eYGQg5GWoZF2eHuEfnV/gYuSiImMiod+goB9iHuDgYaak5+ejGh5fIBqaGNqY11qgXNeUlVbXFdQW19LTVhVSU5XYmVOTkpSUlRXUlxVTVFfZ2p1cWhtWlpaZIx8WE9RZmRfaIxma15nYlhPWGFVVlZQXFVWWl1bVFxpbFVdTlFUWVpOR0VYSH1ES1NOSVJLTVBJYVpRTVNIRUxNYlhjWFdXZ11PRVVhX4BfW15ramlHWEdHW2JsUkdGQ0hKWFdRWlNQWVZITFFPUFRNS1RbV3ZqVFhWZmdgcW9YY2h9dWVYYVtZSmBcUk1PTVZXTE1bV1BdWVJWT2RkcHeDdG5aU1ZWbHJ2Z01uc2RhXmFdZVhkZF5hVlRQV25vcHdhXmNgaXxeVVJRUkpUXYBpXl5qaGV2c21qcndlZHN4fo6NfnByY1eybnhqdNBmZ81oZ2ZjbnuRcHFrZnmJh25jtrRmYKRdV5Vek5l+aFdgk35pcmZtcXpzb4F3Y2tzgn5qZGViYmVvcnhxbnFqbm5qX1xuj5WOcGhpZGRncXF2cmtpZXhwd315fph8dnmAkoCQiIF3dnl/enJranFyam1sc4ZvbWx4a3BncGxpXWZ8mYd6YGdre4d9e31/foeUenp0dGhnbnVwcHB3fXJ2fIWIdWRdaW19nXx6d4CGgnuCeXp9fnFza2tubWlvaVlIR0lsXGN1b2tqent4dXR5hYR1bWhfZXB1dnWFmJiOfX6NnICHeWxra3BpXlxkYl9ramllX1hPV1teZmpdYnRscXJvbnNmVUxcWGhleHZnbWVyb3dxcntwb32BenpycnV7gYaFhXx5cmttgH+DhY+TlJJ9gXJxc35sbXNteF9SVFtUVWByhp6EfHdoZWhham9mbG96bXd2b3p4doB5gXyBiXNfboB/VVJgcn5oXllwb25waHNsdYOKeoSFen18lGtmdG5yho58bnl0eHuVg2l1eXRybWNicHhtZ2Rjg56dpZ+Lj4V1dIF+fnt6koh2b3BoaWJ1and6aWlwd3NqYGh1enaChomUl4SFko+AgXp4kIJ1dmxuV1haUVZeZ4SCjo6JcnJqY4BibmltYmFganBycmhtbWpmfXiRe3NgaGdqZ2pxaXBvd393dXJ1dIp8c3Z3f313fX98c21xYmp0dG1ueHN+gHFsem9raXJnYFpcZm9mXWFlcnBsbWhxb3NvZ1RXaGxmXFJMSlRUVmBcV0lLTUZMQ0BEQFFXXVZVWE5ASmBlWFpaWoBdZGVXZVtWWFVZW1RcXlZSVVNYW02Bh4NQU2NuXllUTkdQSExGREVFR1ZJS1BJUUxRW1pYYX2DZ0tTZGZvbXlhYGBgUkpDS1hSV1xqV3lzZmFgbE1bUlBQTlZjV2RocWFHRklSTkdQUl1mXmBhX15TWFdVYFFaW15yanV1ZEFVVd96AXn/erl6AXmEegR5enp5kHoIeXl6enl6enn/eqZ6gnv/ev96/3q4eoN56HoCAgQAgNbNx6/e2drtyLWik7q1n6SjsKTIr5OVnaeelpmMlJ+Lj5Wgsb+rlqijqqyprbGimIWXpaSVkpummZqboaCmpZueoKaep6acnqejsa+wsrO2ppyluauYmJqPj46ZmZmjopiNlI2SobGSgpKUp52Rsrmmr7Gus6Klnp+ktrS2t728gLqBzrWxvrCmsLq8x7u1s6qjn5uVl6Gfsayzs6CLg5SdlpOkop+apNC4nZWnqquvwrK3x+fj3Nrq5dC4sLixsrWzt8DZ5dmytq/Bt87HqLLEs97v27zItrfKvbCXutSmmrvYua6stcamqKmxraSh3LS1sJmEh5aprZqF9IKQurCEgICEkJmntK6oq6W6xruznp6erKymqdS8p6aniP2B84CD+oOKkYaVmpGYoKaSkJmQkpGE/42D4uX8j4qstJuE94uam5aes5OXmaCm1enPq7KBpNGulZOgnpeWqKefqbi/r7/Ev6ysqqaZnZ+qpaOhqrChsLS2sa28vrehlae5xMXOgLGyw9PHv7SnpKarrrfCv7vHu8nJt6+4vMrQ/P7T1NPRwqyawPSQ37Slrrizt7yxqKWusKauop6lprGsq7C4wL2rrq+wurO0xdjVyLq9xdDOvMLEwsi8sKutrLG2ray7tayswcfKu7rAwrjDv7G6yce2uramo7a8sq+qr7+st+PogPC5iJej06+UnJ+PjpOMh5qXiY2Pl6Oho5yprK22sqWbnZ2io6uiprawpaORnKKarLq3vrGnqLe6xt/R3O/MxLGxsKWTo6m/sKWuv9uXoZbqwL7VwbO1u93g5ODcvLOzuLjBvbG4rKe1tsCys6uqp6uim7qtsL3G08+yp6irnKKtgLiwraiioJebqLeytq++ztPi1/X7gZOD2cS1p6Obp7Svqaqwq7Oxq7KrtrW9u6yqr6WmnaSalJm0wqCitqmks62fm6arsKSot72rqam2vbyvsNPvt8rUvqacp5GWoJWjvayzvMPn4tbBrpCjnaWor6GfmoeSp6ewvcDCxcvCw7G3gI+Spq63sKKkqsG2pbGqs7Gup92HupSxtaGflamjrLKlhpqerMLFy6u7tKelpaS9ysqxt666wc3RwrqgpN69sLGqq7OxsqS3sqiPnqiblqewsqmko6+2sbLG5ca2sKmlr52ZkoWWpKqjo5WalY6MnaKlq7StraaZpJyRmqnKpZ6XgIiHgY+XkqCompabk4yA5/CMivyB+IiDiIuHipGQiIykm6Gutamik5mYnp+crZ6cqKaXjZCQkZ+hnqKgobGwrrK0vcG7wr6+yc7HxdO67PPTxvHa1c67xMHAv72/wsm6sJyWiYiYmZSVo8Cwp5mfmqeko6WvxtbJwsjNw7zIu7PPgJSNiXSenp6ukH9vYIOBbHJvfHKZgWVmcnpuZGpcXmlWWl9reIVuXW9scnNzeHxtZlZkcXFjYmp1aWtrc253bmVoaXBsdHZubHhxf3t8eXqAd212hoJ3d3RvcG14dXeBd21ibmpwfoNkT1tjdm1idn5ucXJudWhsbHJ7g36AgYGGgIFZjHlzf3RveYSEj4eFioFyc3FwcXVyfHOBhndfUVxqZF9wa2hmc6WRdGl2eHuDjn+Mo7anoJqnpZCGd3JtbnF5goycnpp6e3yNho6HdHuEfqSxn4V7dHaBcm9dgY9gYXyacWdpdXxmYGJlY2FliXFpY1lPUVlmZFpQkVNbd2tUgFJTVltjZmdgZGNzcnRlVVZXZ2hnapWGb3J/XKBTrVdUoVdcXFVnY15jZ29pX2htYVpNpl1QfoakW1RuemRNgUlVXFVfcFlZWmBmj5+NbXBGZollTU5dWk5GVExOWWhlVV9fXVRVUlBJUlJbWFhYYGNXZWZraGZyc25cUmBqcGtzgFxdbHlvZlpUU1VYWl5lYWBrYGhmWFVYXGVqlZFvbG9uYVE/X5BbgVpRWWRoYWVaUlJcY1xmWVRaWl5XVVhdYWRXWFZbYVxdbIB5bWFnbnV1ZmtsbXNrXltfX2BoXl1pYVpZbHN5a2pscmlxcWNtf35zd3VlZXZ8dnNwd4VxfJ2jgKV3T15og21cYmRZW2JbV2hkV1paYWpiZFxoYWFlY1pRU1NYVllTVmFgWFhHT1RLWGJhaFxVWGVoc4h8iJd9dGBkYlhKWGBtXlNZZIJtdGmYcm+Cb19eZIGEg4R/Z19cX1xhYltlWFNaX2dbW1JPTlJIQ1tPT1lhaWRTTU5USEtXgGJYWFdWWFBRWGhmZmBmbXB/fJeUSVNLeWhbVE5HUl9cX19jXWFiX2hgaGRnaFtbX15fXGBSS1NteWFmdm1pc3FlXGJhY1xcZ21gXVhhYnBbXIeicn6LfXRsdVxaWlVhc2Rtc3eKi35lYlRWV15obVxWWE1PW2BebnuAc3V1cW5pgFRUZW93a19bZ3lyYm9oYmZqZohQbktbXldTS1dWYF1WR1FVZnp2b19iZFpfZWBvc3Joc29yfYOFe3Zqcpt1bnd0c3JpcGFudXVjbnVxdH2Ff3Ruc4WIe3qLpI58dnBtdWZhXVlodndvb2hzc2libXiAiJKFhXpvdXhxe4uui4N3gHNpZ3V8dX9/dG91b3Jrw854edtw0nNpaGliZGxmYWFwbXF6gHRrYmhnb3Nwem5uenhtYGRpbXh9enx3eH+Cfnx3eH11eHN0eIF9fYRqnKB9eZ2FhIZxgX2Bh4SIjJSSiHV2bW98f3t9h52Ohn1/fYaHg3+EnaWakZibkouSg3yQgGtjX010dnaDalpKPmBeS1VQWlV0X01MVl5XUVNJUFdHS1BfbHdhUmdkamtrbnBgWUpVX1xSUFliWFdWXFphXFJRUlpVW1lUT1hTX1tdW1xfWE9VZl9QUFJPUE9aWFhfW1RKVFFXY2xRPUhNX1JIW2JQU1hSWE1PTFFZY15gYWNjgF9CZ1hUXlBHTVhZZV5bW1NJR0VGSE9NWVNeXlVFPEdQTEpYV1ZRW4NqUkpUUlBTX1Rba4B3cG97dWNYT09LT1hcYGp4fnxcWlxpZXFmU2BjWHyId11cU1lhWVVGYG9OTmiAYl5dY25cVVpfXllhiW1kX1BDQ1JiYVFIhUxTdV5JgE9RVltob3NrcHB7gH1wZWZnd3l+fqmQfIOQbsFny2NgwWpqaWNwcGpvdH12b3RybWhZvGhdl5m7aWF7gGZWn1ppa2NtfmhsaW5ymamYe4BVd5p1YmRwbWZhbWhrdX99b3h6d21ubGlhZ2hybm5udn1sd3R2c3OAgnppXW57gn2BgGpreYR9dm1mZmhnam52c3V6cnl2ZmNkZ29xmJRzdHZ2a1tMbJ1ilW9lbnd4eH5zaWhyeHB7bmpwb3Vvb3R5fn1vb2xwdnJxfpGKgHV7goyKe3+AgIZ9cGxub252a2t3cGZmdn2EdXR2eXB6fG10g4F2fHNlY3J4cm1pbXtqdpecgJtxTFlieWZUWl9YV19YVWNeUlVXXmlkZl5pZmdvbmRcYGBmZmdgYnBta2hWYWdfcHp3f3NqbXt9hp2RnaqSinp7fXNkcnqKfHBzfphobW2og4CTgnR0eZSWlZaQeG9tcG95d3B6amRrbnZoZ15fX2RcWXNpanZ9hH9sZmdrYWVygHxwcGxoZ19ga3p3eXJ6gIORiaGfT1lTh3dvZmVeZW1tbnB2b3J3dHp4hH2AfHJ0eXZ2c3tvam+Ik3p+k42HkIp8d36CfnV4fYR7cGx8eHxpapGjdoKIgHhxf2lqbWhygHWAhoyrqpqGfF9pa3F7gG5nY1dZY2VoeYGBfn96dnFugF1eZnF6cGVkcoB2cHJwaGdqZ41af1tsdWtnY2tnbm9lWWVnc4iHf3BycWdpb2p2e3tvd3Z6foOJf3djbZpxaG1nZ2tjZlJdX11MWWBZV2FsZVlVV2RrYmJxi3lkYVtdZFNRTENTYGJZWU9VUUVATVRZYGhdXFVMUU9HUWKDZmBXgFFLSFZcVWBhVlRYU1FJgolVVZpPmlROTUxITFNOR0ZVUFJcYVdQRktKUFNQXVBQWlhOQkZJS1ZXVVdVVFpcW1pWWVxWWFJTW2NeXWRNfYFgWH1nY2FOW1dbX11iZGtnXk9MQ0VRU09RWXBiWlJTUVdWVFNYbndtZ2prY11lVlNm/3oDenp7+HoBeaB6Bnl6eXp6eZF6Bnl6enl5eYZ6AXnmegF7/3qneoN7xXqDe/x6AXv6egd5eXp6eXp563oCAgQAgPzbv9DezcSxn5+hprTL0cjN1b7CvLm2uI2IkKGknqmOjYuGhYmUiZSkmKeqnKSoqqugl6KorLa4oKmypqGmq6GLkZakvqqpl4uPqr2qraein5KRmJOQkYigloaNjpOdq5uZjpifl6CShsWypLKtqo+erKGgnZabkpmhr6e3xdbZgN7Y2sq/tq/Lqqq3uL2oqaGYjpWSkqOpp6Kimqymmpyeo6aZjpOYoZW9wrayp6aZmKy1wr6+wM7O6tLVxLamqKm6vuPfzNPdzcm5yLm2v8PFtrrdxMi/raequ6WowL2eoJ6lqKWvuq+kqcLLwaitpIWmr7C7rbC9x6+PjZSkkL69gI6OpKOkraWmtLm1xL67nZWamZSJqbq1lo2BgYOKhO3uhIuD/IOIjJOWmJimjYqhk4eCg4aKhI2Jif6AiYT5g4aLkquzoZyVj5OTn6q5rq2vtLOklZqKlZOBgISVoqunm5qhpqukoqWup6GUoK2zs7Wqp6Gfr6DEwKydnJebxbeygLe0tcO6wbu2r6rCtbCwtLrBvMe4uLC4t6/Cyszh6tHKyLjIhL6c7cCsqqqsq6equKCuvbKtrrK3ube2sKmwsKuqtqLLvrCfnI6WsbKqsb+7sre+3dS+o5WhpLaurbivr660wbaupcS+w6u8t7q6va6hop6QmaCco6Wa482kr7G0gMGpqqOikZWQjJiep5WQlZCFi5GYmZKWn52jqbWikKSnube7r7K5qLe9oaCxo5WflJiXn6Omsq68usW8sbGrss3IwJuXm56aprK3rrq9zsW0tL2u4eCur7fS/NvUzr2uqZyZq8Lzvam2uLywtLCqtM+8v8zTzsvTyrKluq6gmJyfgJyjpLajp6yyraKfnrCntMTRvpy687aVpaihnbC4x7Khmaq5xdjCqaKgq7O9v7Shsp2gn5qZl6G4tKGcqq+xp56ioZyvpa2zs7bDtLGytcrTyKW7wri6uZ+TlZCTmKOrt8m4q6uakJullpugpqWbxeunl5Obqs2+qZWlqbSxw6ivgKCDnZ6uqZ/Cp6Kyyr7L49/Jv8LHuaWxuqOlj5SgnM7PrKKpuLGovb6lqKyqq6WjnK29v7GToZyiqbOnk5ShwdbIz7i1tcCkp66zt66qtLSgprCzsaKpoKWsrKWnrqyakuGWoaKRi5GWoJqoqJqZpKexr6GZkZmUmI2RhZCE/I+FgP6FiZaUmJuWipKclJmVjpmTn5aL/PyGg4T/jpmWkpGXmaWimpuNlp+svcq2ta+np6ilmYGRvNK6vru9try2w8u7wsPAxNLb3OHYvL67u9DX09/MxsW3vrK7uL+1tL22sqeurqqfoqOur5qZqq+ilaCeko6qsMrDsbjO2uHt1MrOgK+dhZOjlpB/cHBvcn2VmpaXoYWQjYeGg1dUWWxxa3JgW11WVllgU11wZHFzZW1tcXJoYGtwc32BbnN7cXJ1eXFgY2x0ind1YVhccod8g4N7d25rc29ubGZ6b19iYmZyg3d0Z2tuZ2tiWJF8cXh0cVdkam9va2VqX2dtenJ/h5OVgJqYmoqFf3ePdXSChot5d3FtaGpmZHF8fHt1aXRwaGlnaGhgVl9ocWWKkICAdXBmcIOFioWAhpOPp5mOenJfYGl/g5qUg5CdkpCEgX18hIiKgoWjioB7bGFjcmhwfnNjZmZjYWZydnJoaHN5dWtsXkdZZHCCcW5xdWpaWF9rVHN7gF5cZmlka2BhbG9vbnNxU1BTVlVLaHZ0aFdSV1ZYVKacU1hZolBUXFthYV9tX1hraFpST1ZYUE9TVJpESEKESUhHSF9uW1dVU1ZWXWZ0aGZmbm9iUU9BS009OzlDTlhbU1RPVVdSVVtjWFNMV15gYGJaW1lbZlZwc2FVVFFVfWtqgGZhYWpjamVlXVlqX19kYWloZGdfYFxdXVlocXSHkHxsZ1VrUXxmj2lZWFZYWlpbZlZhcWpiY2ZpamljXVVbXFZVYFR0aV1OTEFJYGRdZW5rZmpwkopxWlBbXmxiYWxjYWFkcGxkY3x2fml5bnN2dm9kZGFTWF9bYWJZoY9nbnF2gIBkamhoV1tdWmJrc2ZkZWFXW15jZV9iZmdhanBdTFlebGhqXlpfUFpgSU1bUUlSSk9RVFhfaGdvdX5vZWhjZ4R8clNKTlBSWmRiX2ppdnFhYGhcfodaXGF2mX11b2dfXVNSX3KbblViY2BWW1lTXXBfYGpuY2FpZ1lQYllOSEtOgE1XV2ZXW19nZVdRTVZRX3F+Y0phiFxDT1NOR1VgcmNUSlRfcYp1W1JOUl5maWNVYlVWT0lLSE5obFpUYWppZmBdVk9iW2FnZ214Z2FgbX6DfmR2eHB4f2teYFpXWGZka31zZ2FRTVFaYF5dZmNlg5xmX1peZYNyZl5qYmlvb2lqgGdKWWFoX1tqXFlncHV+gYJ4d2tqYFpcX05XRklPUHZzX15fZ2tkamlbWl5fZGJgW2dxdGpZXlxjYmZgVF9nc4WEi351b3xnbG58h4F9hol8eXh+eHB7c3t8eHBxdXZiXJxmcnBgYGx0dnB7dm9yf36EfXt6d4J4d25uZ3Ns1np1gNlwcXl3fIF4bnF6c3dxb3d4goB21tR2bmrLaXFraGdtb3h1a2ZdZGp0ho17fHhyc3Zya1hkipyJiX6GhId7hIuAhYR+gYuRjpGCcnZta36Oh5OBhYF4g3uHhYuGhJCJhHuMjIZ8gYaRinV1jI6GeYWAdm6FjZyah4ujrrW6o5iTgIFyXGx+cGhYSktKTl1wdXN0f2dtaWpsaUJAR1ldWWNQTU9MTVJVSFRlWmZpW2NfYWBUTFRaX21uWl9lXFhbXVZER01WaltYRj5CV2pbYF1YVU1NVFFQUEpbUUZHR0xVZ1paUVhcVlpPRHlkWV5YV0FLUlFOS0VKQUhPXVZhZnVzgHh1c2FbUk1hTExaWV5LSURBPEBAQU5VVFJRS1dWUFJSVVhRR0pNUUZpbFpYTUg+QVFVW1hWXmdidHFpWVNFS09cYnhzZnF2cW9naF1eamdlXmF6ZWFbUEVIWlBTX19PU1VUVFxhZWRbXWtzb2BpWUNUW15xY2VrcWBQUFRfTGNqgFhYZWVocmtvdoJ4fnp4Yl5laGRaeoiAbWNcYWFpZruqXWhnu1tgYWZsbmt4aWR6bmReW15kXGJfYr5ZXFKYVFhbXnZ8bWhmZWhma3SAdnZ5gIJ3ZmVZZmZUVVVfa3h4b21pbW9oa3N6cWpianR3eX1zcm5scWF7fGxgX1pdgnR1gHRxcXpzeHNxbGh9cnJ2dH19d3txcWlraGNvd3uKkH1xaltwVHhnoHdraWpubm1udmZxgXhzc3V4eXd0b2hwcmtrdGeKfnBgXlJbcnRtdn17dHh/npZ/aFtmaHhubXhuamltenNqZH15fmh3cXV0d21iYl9TWF9aYWBWmopkbW91gH9namlmVVlYVmFrcmVkY2JWWlxkZ2JobGtocXhlUmBmdXN5b2twX2tzXmFvY1liWl9hZWlteHqEiJaLgYSBh6Sbjm9pamtpcX1+eIOEkot8eoN2mZ5ydXqOsZiRi4F1dGdkb4OremNxcXBlaWZibYV0eIOFfHp/gG9leHJnYmdogGRrbHlna212cmdgXWZjbXyKdlxznnFYZ2tmXmVtfnBjWmRxh56Icm1ma3R/gntseGtta2ZlYGqChXh2gomEgHd6dGp7dHl6f4eMeXhyeIiRg2d3eHJ3gm9laGVlZ3Jtd4yAd3BmXWRya2RudnJxj6RwZWNjaYd6bWBsbHV0eXBzgHVaYWdzaml6bWlxgYGKkomAe3R5d3BtcmJpWFteXYeGamxveHZvdm9kYWVnaWdoXGZzeGxYYVtgX2RcTFVibYB7f21nZnJYVllgaGVfZmZXV1teV09XT1ZeXVZYYmFPR35RWllLSVJbXVlgXFJTXFpeWlZTTlVNTUdKRVFHiVNPgJJPUVlaXWBXTU9WUFRQT1hYYl5Tko1OTEmNTFRPTUxRUlxZUE5CSE1Xa2xeXllRUlJQSjpGaHlnZ11hXmBUXWNXXFpVV2FlZWZdSk5KSldoZnBfYF9VXldgX2ZeXGZfWVFcXFZNUVRhW0pJXmNUSlNQR0JVXGhoWltwfX6IdGtr/3r/eqB6Bnl5enp6eZV6BXl6enp55XqDe/96/3r/etp6BHl6enmTegZ5eXp6ennmegICBACA54LZ5La0qpmgvcqrrKu22LytoKGak4qA+5SZkoWHk4uTi42GiI6SpKOXpLLYw7Gts6ianqSdnqSHiJ2jqK+zs7uuyaWpqZGOkoGRmZiioKOfoKaUmKi1qbS6sK6Rl6WsnKCaobak0aqPhYqcqKOlqKGSlZmZpaKgrKOlq5+quseA97+gr6+flZCBj5yfqq6imYueqbfDvLeeoqWVk76tpZiRmJ2bpLDGt6yqrLOpp6Wtv7S1pZWXt6Kqt769utnq1dvdysG9v8/Zr7u6vbu2r5+nuMHBwLW2srOrqa/Y+76rr7KascCkkaWapq+5v6CXlJCgrKKau8zbuKWt2dnP4LmAjJmrq6aTgJeqtb/U4sK8tLSrmYyTqL24mZuVkYCEh4GOmoeKlaKlqa+muY6AkZqjlpeTkYmUkpiMgYe+4rG1n4WMo56OkZiKhYCDlKbAsaWip62upaCflKGhn5iZopqHh4imoZ/am8XXs5yQkKrAtKqflJmUmLHKubKvs6+vtsKAvqytvbirqKmktcW+sLTG0NTLw8W9qKiwwbu0wLvCvK+swNrb3dLCub3GxcTFwLKys7CZnJ+xurSwqLG9xdLUzru35rnDq6e0tJ2hppu1yqW1x8fBxriUjYyetLS0srfGta2rtK+yp7GupaqorLCxuK2nlpymzbipnKKkmJidoaKAsMfogdnIno2CjYSXkYeXjYiQlqmeop2frrfSwpugs7CptK6irKirpLOqlYuNk4+Sg5Gwt6e7spCilZOXoaanq6+2opWVop6Toaumqaapsszix6Sjp7O1s6Sam6SapL6wqrHAwN3Ju7GxsLexsqyz2NqrrLOuq6y2tbq3uqitoJSAlJChnJqcqZyPjJmdioueoJyklZ2rs6+toJakpp6gpKu9p6u3xMG8vqiXpLKlmquqtqqxq7aqm42Pm5qjoqGbpqmnnpOjqqOirLSssMfVwNPcu8LFu7zBsKePh4uXoKSgnZyWp5iYm4yZqJWtrKS2srWbnqGrmJ+QmJuppaatm62AvpGTm5WrpbS6qrGnm5qrxbWksbW7tri0sKKonqihtOPs0cmvqaKXo6aWoaGenZejqqutpZujqKenprPIvLqtsNLW3c7Eu7W4tLrCu7O3u7avm7Wwq6WjoZSqpKWqoZyXnp+pnZiptaKYmJ2PjIyano2OiY6G+ICGhpKL+fiOkvyA74GLiIKBh4qKmpiNio+Xk5GywZ+JiaOdjo6QhPmIlJ+uws61qbmzvK6mpaqtpaymnp2mx9vV7O7P38S+xsPBw8TN19fI3uLZ18PC0cnR3evU6c/W1ruqrqScr72qsayfm5mcnqOgq5udm5yio6WurK2wqqbN7+bCr6+7t8CvoLWApV+fqYJ7eGxyjJB5dXeDoYt+c3JraGBTpmFnY1ZWXVddU1RQTlNWYmVZZnKRfm9tcWtgZGtnZm5bXHBwd36DhI16iHyEgXJ0eGpzd3d5dn92entxbniDeYeMg35nbHl8bm9pcH5unHZaU1hlc21ucWlgX2Rlb25td2tpbWZwgYiAo4JpdXlsZGNVYmtud4F3amJweoKPg4pqa21jYod0ZltVXGdobHWJf3Vzdnhwc3V4f355bWFhgnFucX14c4qdkJiPfn11fI2ccnV8gHt6cWNuf4h9eXNsa25ucHSNr4dxdG5edYJsZWhdYGd7hWVaU0xYcGxjdHd8c2tykpWFiHWAWGNlb2RVRVNganV+iHl2amtjW05TZXuAaGFnY09LWFVbXFZbYGZqbnNufVRJXmlnZWldV1RgWVpSTVN/mWZqWURHVVFJS0tHSEdKV2l4bGBdYmprZl5ZTltcWlVUWk9ARExmYVeCYnSLcFVJSF1zZFtNRk1PVWuEcWRhY2JjaHSAb15haGJYU1VTY25qX2RzfYd/dXVqWVRcbmxpc3BxamRido+Kj4Jyam1zdXFxbGJlaWhYW11qcG5mX2Zwd4WIgHFtmWpxW1lgX1BTWE9ld1xqdH14e3BQTlBcbm5vbHB9bmVnb2tyZ2xrY2ljZ29vdGtkV1tihHJjW11gWVpeYGOAcYKcVo5+YlZSWFNiXlpkWVJaWmpgYlxdZm6Gc01QXV1aZl9YYV1gW2peTkxRVVJUSFNtdWV0b1BeV1VUVl1eX2FkUkVGT0tDUVtWV1ZYXHCAaU5OUlteYFdTVF1UW3NhYWlzdIx6a2JiXWFcWldbe3RQTlVTVlhdXWNmYVZbUEkcTEpYW1dWYFJHQUxTSUdUU01VSU1YZF5ZTEFVVoRQgFtPV2BlY2JiUkdQXVhNXFdYVV9VXlJJPkRQUFhZXVVbXVlUSldcWl9jZl9ff4V1jJx3e3p5fod5a1lRUVtjY2BhX1leWVhVVWRkXWptbnNucWRlZWFXXVdgYmRka2ZfcoFWVF5YXl9famNnXVdYWWVrW2VfYWRtZFpWWVdYV2mEgJCGgGVgWVVZV09XV1RTUl1iYmBaVl9oZGtpanttcG9wg4yZioV4aW1zeHt8fIOHgoZ3g3x5dG9ybHx5dHZuZmNucG9tb4GJenZ4emtjZGxzaG1scWrKa3FygHbLxnV71shtdXRvaG1sbXh7cW1zeXVxk5p6Z2iFf3FvbmW+Z2x0YH2Gjnx4iIGHdW5tcHRwdG9obHWPpJusqY+hjIKGf39/goaNiH6KioOAcHN5dYWJnIGTg4mIcWdqZWV3f3F+enJycnZ8gH+KfHh1eH+Ah4+KiY6GhKO7tpGAg5CNj39tf4B4SHiCYVpUSE5ob1ZWVmF9amBWV1BNSj55S1BNQ0RMSE5DR0FARUZSVUpXZoNzYl1gVk5QVE5NVUFAT1BXXGBiaVxoWl5hUVBTRU9UVFpWXVhYWlJRW2RbaG5lY1BWY2lZWlRaaVuEYkxHTFdgW1daT0RESUtVVVRbUE5UTVdjZoByW0ZOUEZBPjA5QENMUktCOkhSWmRaYEpOUkpMbGFVTEhPVlJUXnJkWVVVVkxLS0xUU09EOjhSR0pOWlpYb3ludW5fYVxhcn9dXF1hY1pOQ0xaY19eWFBOVFNTVXKSbF5gW1BkbFlQWFRYYW11XVFLQEZYVlBobXhiXWSAgnZ0X4BOWmFnYFhLW2h6e4OKe3h0eXNpWF5xg4Bra25tW11oX2JnY2lwcXJ1fnmHXVFmcnRvcWtoX2pmal9ZZo+tgoFtVmB3bWNjY1taWl5odod8cnB3e317d3FmcHVxbnF2alpeYnt2bphtiJyCaF5ddId5cWVdZWNkeIp2aWdramx2foB4aWt1c2tlZmR0hH1zeIiTm5WLiH5pZ2p7dXF7d3x0bGl9mJGViXpzd31/fn98cnV9eWdobHd6eHJscn2CkZWMe3anc3pkYmxrXWBoYXeHbHmDioSEeFlTVWJ2c3RzdIByaWpxbHJmbGpkaWRnb25zbGdbX2iDdGZdXmNdX2NmZoB0hZxSin9jWVNZVmRfWmNbVV1hbmVpZmhxd5GBWV1samZybGRtaWpkc2lZVlthX2FWZIKLeoyHZndubG5zenp+f4RxZGVvamFue3N2dHZ5i5uFampueXt7cGxud210jXp1e4aCoIl5bmxrb2ttZ2qLhWBfaWdpanN2foB8bXFlW4BeWmhpZWVvYlVQWV9UVGFhWmJYX214dHFgUWRnYmVjYXFkZ3B8e3d1ZFplc25icnJ3cntxem9lW2FranBvbmtzc29lXmxwcXJwdXRvipJ8kJp5fHt2g4qAdWFcWmNnZ2dmZ2BsZ2FkYGdqZnN3dnx3eWhrZ2dZZV1la3Jycm5ofoCRaWBoZGxwcn10cWhkYWlzc2VsbnN5hHRvZ2xnZ2F0k6GPjXFtZF5hWlVbWFhVUV5iXl5ZVFhiYWRgZXJjZWNld4CKd29lW15dYF9dXWVmXmBQYF1XUk9RSVlWWVtUT0xXWFtUVmRuYlpeYFBJSE1SR0hGSUR9QkhMV1GFgVFXk4CESFBPS0lMTU5aW1BLUVVRTmlvV0lLY1tOS0pDekZMVV9obmBYZ2FmWFJRU1ZPUk5ITVZugXmHhnB9aWBjX1tbXV9lYFNhY1pXR0pTUF9ndWFxY2ZmU0lMSElYYlVgWlRRUFJVV1ZdUE1IS1BPUllXV1lSUWqAf2JSVmFfYlZJWAJ6e5Z6AXn/ev96z3oBe/96nXoBe/96/3rzegF5hXoGeXl6enl5m3oBeeN6AgIEAIDE0Lm5sqKbnKu1r8G6s7CqqZqmrrCrtLOlnJuJiYaLiIL2hoShnKOptau4tM3XwJmZmqeUkpaRjZWai4WHjpClpJ2YoJqWmYyNoaqWk6Wmp6Wnxq2rqqWXk52cqa25qaSmqZiLn4qjm4L5jaqzq6yrqKWio6OnpKmqpKCZoLGu1oDLwqibwKCstqOaipGmlpufr6e5rLmkrKSmsrexuKudqqaut7m1zN63vrCsxtS6yb63wcSpqamltr2z0NO/s8K9w8TD1dDHs7zDq7vEvress7S4tcLB1NjJwqyxwc3Bt6acq6edqZmZi5mpop+Xif+9++3Hw8LDwKugm5+hq6GewoDUxMihoIvrj6Cx+dzVtq+ytq+vp6uXkJeUlpWNj4SXjqGgqKGprMLOuJ6Bg5KWmY6dmP7/+/2OqZ+RhPuJjZmViZiZmaSbk5unsLSbrLibmZeHjZbBv6K2nouF/ID89fOXmJOJiZ+3n5KWiIaTlqu+tJ2YoaGhmJeZubmorby9rYCttqmpqbWssq+loqWsubGyq7KnpqG706WmoKWim5+isamov7uznZmYn6mys8G0oJuZqq2+uKifqbC1rq6rqbixs7ytmay4v8O0sr+3vtjat7u7tcvKuLCnq5+yt8OsqKmpr6OfpKu0rqinnZOjrqyip6+ooI+Wi4+NkYqEh5KSnoCZqK+TioWSkoz++YWMioaGjqO3x7fM1MuXm6Kfm52bnJekstTGsZmZmZOUkpmYkvf29JChqJCdkqGorq7IwKeovpmXpaausbG6sKufkJupqaGkoqu2rKqnoq6+t7S3vbi6pbC+utLgxMiurLPTiPDEuL+1u7y8u8XFxMS/v6+moYCcqJ6Pj5KKg46ymJSntZydkI6ZnaO7wNmhn6WosLS5tbGpp6+2vsG9u6assaadpZ+bqrWprJ2myLats8q8t6etoaOanJCYpKKqpaOwtq3Azruvs8S0oKOikZSWo6Ofp6ObpK+lnKyslLOhkbGyqLGntJOPn7O5sa+vnqGclZeTl4ChhZCTlaehoJqLk6evlKOqoKatsKC7rampqKWuv8bJ3+zjv6abk6Wwu7uvrLeyxODyyMHHv7y/w7WprbS+zsa8s8DJucPJvK7Grq6wurGpq6+upK7BuqOmsbOnpZ+tsZylo6OxpamvtqqckoyFgoOMhouQlYnk5/yA3dvYgI+P/4Df74iA7e+A+u73kZWdmZufopqdsLW7uaSdko2Kjo2Wlq2tpY6dpq25rqaews62lI+iqsGvtrvDsbC1u7a4ubOru87I3fzc3/ru99rg0b6408y7zePLxdTBzcWjtsa3srqnsrW9rJ2imqurn6SstLqppq62wby6tau0p6e2y5SrxICLjH6BfW1rbXuEfIqGgYJ/fG93gYJ+iId3a2RZV1hbVlGTU1NpYWlrb294eIyYg2NhZG9fXWNeXWRsYmJkam2DhIWAh3t3dW1vhIx6dH5+fXp6l3x4eHZpY3Fwe3uDdG5wc2VZalxybVWbWm13b3Nzcmxsbmxva2ttbmtkZ3V0nICUjnZqiG17gG9pXmx8bnNzeXuHe4RvcnN2fHptdGZaZ2p1foF6kqR+gW9wjJl8h358gYZtcHdqcHlzhIR5d4F6eX16hIh/bXd4Z3R/gXJsdnR2bnqBiJGEgXN3fYyKfWlfbW9tb29sU1pgY2hlU42AuaKLkYV5bF1fYmRpY15beYCJhX1lZlGBUlptooh+a2Zmb2dpZGFXUldkYl9hXU1YXm9pbGl0cX2LeFlLS1RTXVpgV5iSiY1XZlhHSYhMSEpEQFJUUlpST1RdZ3VicHtiX1pKUFd/f2d1YE1FfkKAdnJMUE5OUWV1WkxNSExbW2p2Z1VRU1RTTE9Tc3FdXWhoWoBbZ1pdW2NZW1NPTVNYZWBhYWRfYV92kGFdWFxXVFhhbmpnfHdzXllXWmNrZGpoXF5aZGt3cmVfZ25raWloZ3RubHRmV2dzd39vbXJpc4SDZWZpZ3Z4a2VfYFdrbHVlYmVmamBgZWx0b2dtYllpdXJqb3NwaVleVFZUV1RQUVpZZIBicHhcU05ZW1iYkk9VU05OUmFygG6GjoJQU1tUU1ZZV1ZhdJaKdV5eX1hWUV5eV4mEg1ViaFJcUFhcXWV5bFRYa01KVFVbW1xfVlNOQkhTUU9RUllmXVxaVFxua21vdXFwYGdzc4SQen5mYmF9Uo5rY2pdY2BhYGlnZGJkZ11YWIBYYltOT0xCQUpoU09cZFNUSUhQV1lqao1bV1lXWVRZWVlOTFFaXWlmZVVZW1NMTkxMVFtSVUtScGVeZYJyalleU1pUVE9ZXF1fWlhobmJ8i3dpbX94aWpkV1peYmZeYmRcamtjX2RnZ3pmYG97dHFrdGFZZXB6cXN0Zl1gWlhbXYBnUlNZYV5gVlFMU2BnWVhWW11iXlBiY11XVlldbXB3jImKdF5RS1tncmlkZGpkco2jgXh5d295fHFrcXFycnNzcnV4cHh7eWp8Z29yenZ2e35/fpCQh3N1foF6eHF8gm5wcXB8cXN8h4RzbGtpZmdtaGZscWqvtMVkrayuanZ0zYCsuXBpwsZt2svOdHV7dXl+gXmCjpGZl4F3bmZiZ2Vpa3Z4cVxnam54dnFsjJyEZV9tc4N4fIGJeXp+hH5+enNudn98ip+Gh6CPloqAeGRid3Jod4Z0dIZ8jYtogJWFf4t6gYuThnl8eIN5en6IkpWGgoqQnZKNhXuCeXqHmkY2kIBkZ1ldWElGR1hbVV9cWFhTU0ZPVFlXXmFSSUU8PD1BPjtrPT1STlZXXFpmY3d9aEtJSE9DQENAPENKPz5ARktcXFxYW1RPT0tKXWVTTlpbXFdYdFxZWllQTFhYY2VuYlxdYVVOXlBiXUd+S19nXF9fYFxbXFdZVldZWVRKSlZVd4Bva1VLZExXW0pEO0RSR0lLUlJbU1tMUlNaYmNbYllRXVpdY2pkc4hkZVVRaXJZZV1XWltFRkxHTFdSaGpdV2BZWV9ia25mV2FfS1tlYVNLVFRcVmRmaHBsZ1VXYXBuZldNW15WWlhURk5UV1VUSXdnkoJtdm1nXU5OU1NTT0xFYoB8e3VbWU5/Ul1wooN6aWZqc3F2bmhZVFZbYmNgYFZmaG9obnN+foaOfWZUU11cYmFrZKqoqKlhc2teXK5kZm1kV2Ztbndsam11f4t3hI1vamheZ26Oj3yQeWRap1asp6ZnaWVhY3eKcWJmW15qbX2MfmljZ2hnX19jfnpnZ3ByaYBqeGpsaXFpbmxnZm1yf3p5eHpzc2+GmnBuaWxnY2VreHJxhIB9Z2NiaXB4c3t3aGdibnSAfG9pc3p7eHZzcH10c3hoVmhzeoFxb3pvdouJa21vbHx+b2pjZVttb3hoY2dlZ15eY2t0bWNqX1dnc3JqcXh3cGFjWl9dXVtYWmVjbYBseIFkWlVeYFyjm1JXVVFSVWRzfXCDhn5TVVpWV1laV1dldpiMe2ZnZ2FiYGxrZqajoWZzfmNuZG52eH+TinN2imtmc3N7e3l9cm9nW2JvbWptbXmHf317c3qJhYSGi4WDcHZ/fo2ahYlwaWqIWKB8dHptdXV4eoGAfnx7fHBqaIBncGpbXVxTUFhyYFtnclxfVVdeZWl6dpBkZGlpamhubGhhYWVpbXZzc2RqbGRhaGRkb3lwcmlyjX12e5GBfWxxYmdiZF5ramRrZ2Fub2V+inZoa3t3b25rXGZkY2dgZ2lfbm9vZm1zaXRlYXN7c3VueWJbY3J5e3x7cGtrY15jaYByYGBkbGtvZ2JcXml1ZWlmZWhta2F2eXFmaGdue3h/lJSXeGNYUmJtdWtpZWdjcoiaf3Z0bmtucWlhZGZlZWNkZWdoYGdpYlJmUlVSWFVUV1lbVmRrZFBSW2FXVU5cYVNZWFhnXF1ka2RWT0xIR0dNR0NKUEpscIBAZWZpRlNSioBtek5HgYNJkYiNXWtbVlpeYFZbZmpxcVxVTkZDREJHSFZWUD9KT1NfV1FMaHVjRD9OUmJVW11kVlVYXFdWVE9JT1hSX3RdYHZjaVpWTTw7TUg/TVxPUF9ZZmhJYnNkX2lZXWNqXVBRTVZMSUtTWlxRTlNbZWBaVExTTU5bbzcvZaF6AXnHegF5/3qFegF5l3oBeat6hHmFegF5n3oFeXp5eXn/eqp6gnmkeoN5u3oBe/96/3qJehV5eXl6eXl5enp6eXl5enp5eXp5eXnzegN7e3oCAgQAgLHY2bGyo5Caoqq50ba+qayah5OagoiXpauqqpqWlIT9g5++rsahx6CvvLy6v7qwoKm96tq3sZyesK6UkJ+ckYyapZuUkY6IlJKap6aopaCooaWcsKqmpaaSpbGjoqmXhISQi4uJg4Obq6idk5KPlpaSkKCoqZ2YmaainZ+gk5e5gLqysaCWqNS/lJeUmaWysKyfr7Oqo5+kpq+mnKCrnae8vLexsrivxNbNx+Lb5fbMwcCynZ20ur6/wba1qK2yuLrEt8C5ubO4urrMw77Kh865rqmiuveq1bvJvcTCubqztauqoaSRhqqiqJmLl56bqMjn5s7Avrq2ubKus7SssKq4gMHAxrOyoZWirKW0xLmuxbyvpaGhoai/grfSrpKSkZCShJuywLaclpKXn6KUkJuztsGmkI2B94Keu66epK2Xi4mVmK6Vkq+up5ibsZyOj4LyjoiKkYiHoZ2n+taQgpyei4DygoaTl5OFhPCJk5mZlouNk5ikqK+mnqihm5yeop2fgJ+prZ+vpb66n6aooaWropiNk5arqaO1pZShpouNjo2eq7OhmYyJiI2Rk5qfn5+otb/KpaGlmpWem6Slo5ykqLSjoaWmtLqww8q4xa/DuqemoJutuKy2pKuirbbBu7fDuKqqpKeWmbGzqp2YnKaooKCclKWNkYyQnauN9oL7/oOUgJ2XmKCThrLMiIP5+4OIkoSJlqfBjP+ppqKoubaoo5ehm6LMvqmqsqyZh5KYnoiPmJWQlqKvqJuoxKiVjZWfr7bFtqyxt62yuMG9rK+8q6qzobS2sLOtoqqhna2qsri/4unc5uC8uLGumqq3vLOvqbCwss3Kv8PJxtTWwcSvqJqQgIqSt4mo8bKhn6uYlZqkoLSps43g9K+hl5Wfsriyq6yzpaantLe+s7WxsrbCxa2YtLO1qaGZrN7F1bm3xLaooZiioIyDjJCfmKWZqLKuqLG6vcff3qWah4OHk6ivqK6fo4mTjpaNg4aIub7OupWksbWnk5K3ra2qqpybpJWbtKmbgJ2TrauZrKOmnZSJjqOVoaq1sa6nobGxpKKjpZ+qramzqaGgqZOaoKyfrrStoaywudH4zKysrLu6t7WeoqWz2+fSvbO90MnAxLCoqLTU1L65r6SzurvDxs7Gu7uvoZuwu7mtsbay2rmPqqOjp6Gbk46IiZGFjJaOgoT1hZCSg4KJgIGIhIb9/Yv6io+NkKKmqqWvlZiWm5iZl5qblcKcmrGhnp+dr6Wgq762wLasrY+MjIyQiJ2inZqXqaurrLrDvq+yqaOz5dri6oP13env9vj46drt3fSA94eBgYTZwsC1uLuxt7eysrGntK6zt7zKu8vNy9uB4vqChN/WvrbfvLevgH2elXuCcGRxeYSRn4mMfIF4Y21yYGVyfH9+eWpoZ1ekVGyFe5Nwkml2gYF+hoN5aXGHsah7em5vgYFraHp3bGl3gXh0enVtdHN1f4CBgnp/d3p1iYR7dXBicXdscXZjVldjW15cV1BmcW9iYF5cZGJhXm9wdG1oanJoZGZoYWWFgIZ8eG1nfp2Pa2tsbX2Cg31xgIR4c25uam1hWFlkXGd6fXZvdXdygZKPi6CUnriSiYVzZGt4dXh9fWpuaG1rb3F3bHJzdHFqcHSAhXyFbZh2bWVhe659k3yGgoV/f3pyd21ubGpkZXFmY1lXZWpha42inZiSgHVoaG91en5ucGpygH2DfHZ7b2Rja2Zvd2pkeXNqY2JkYGN9VHWafF9gW1RSVGZ4f3hkW1VVWl1VUVtnbXhiS0tFfkVdeF9OUmhbTEVHSmJUVG5nZllXalpVWlGWW1hUV09QYmRrsI1OQ1xiUEN5QkZQWVtQUoxNUlhZXVVVWFZdX1xYU1pTUFNVXVVVgFFYWU5WVGdiUVZbVFdaVk9ESE5eXFlnX1dhZlJVVFFeaXJlXVNQUFRZW1tjZGRqdICHZ2NmXVdbW2RnaGdwc31ramxvdX5ygYh5gG+AdmZlXl5qcWhxYWRbYmp0bGp0bGNjXWJTVWZrZVlVWWJlX19ZUl9NUk5TXGdQhEqGjUlWgF5bYGRcTHiPUk2LjkpPVEtMVGV4ZbJoY19leXBkYFdiWGCHfGlnb2xbTFNVV0xRVlRPV2BrZlpifGNSSU9XYmJvYlpfY1haYWdnWVthXFlbUV9ZU1dZUldUVWFhZm5whpGSmZZ6d3BrWF9rbWZlWV5aWW5pX2JpaHN5Z2tcWlRNgEZRcFVqkmNWV2FWUlNbWGtmak91hmhbWE9NVVhXUlBRS01OW2BqY2dhXmBrZlZQYFpZUU1JVn9vgGlmb2RZU0tVVUZFTkxaUl5VYm1oZG95fIOaoG1gT0xTXWhvamhmZFNcV11QSVNdfoaSfGNrcXdpY2B0bW9tdGtnZV9mdnBjgGBfamRlZmBeUVBNTllWV1dkbWJeUmBiXldXW1ZaZGBnYVBTYlJRVFtXZWdjXWhnb4akhm1qYnR2cWxdYmRrgoZ7d3JxgH9ze3FrZXSUnIh/fnmBi42am5aMh4V4cWx8hYeAgIN8polbdHZ+hoF+eXZzb3pmaW9kXmC5aHNzZmhwgGhsZ2vL23TXd3p6eIqQlYmOd3dyfXNzd3N6cZhxc3t1dHJtfW9ocoB+hX1xc1lWWFtiXXFzbG1pd3VycXuKhHl2cGl0mo+VmVafiZKRmZSWjoCRhJpUol1ZW12ShIJ+hIJ6gIiIhYqBg4GLjZGdjJmbnZ5SpbdiY6SfiYKphX16gFNubFRaSj9IUVpnc2BjVFhOPUZMO0BMWV1cWUpISDhlNUtlW3BQck9ZY2JhZmBYSVFje3NWV0hJXFlHRFJQRkNPWVBMUExFTU1PWlhbWVNbVFlSZWReWllMXWVbX2ZaUFVeVFVQSUNWX1xST1BPVlVQTlxhZFxWU1pQTE5PSU5ogGhcVVBKXHprSUhJSllgXVlQXV1UU1NWUllOSU5aT1VgYmBbW19aaHVvbH1wfI5rYV1MPkZZVlldX1JWTE1PUlNcVVpXXlpaU1hmZl5hRGxWVE1IX4BPdGRsZ2tqaWVhY1tgWVZQTF1ZV0tJUFpZWWt7eHRzY1tTUFVdYWBOU05agGhwbmdmY15ebGNucmZhd3BrZ2dtYWF5TmqIdl5cWVZdWGJxfX1qZl5cYWRgW2V1d35vXVpTnlVsgW9jZ3ptY2FpaXtoa4aBfnJyg3Jpb2e5aWRkbWdoeXeByqloWnJ0ZFiqWl5na2xhY7Rjam1tb2VmaWpwcG5qZW9nY2Vma2JigGBmamFsaX95ZWluamxyb2heY2h3dXCAcmZwdGBjZGJveH9wZ1xXV1tgY2Zubm5zfomSbmlvZmJoaG9wb2x0dX5uamxudYF0g4p5gWx+c2NhWlpncGZuW15WXWdxaml0a2FgW2FRVGhqZVpWWmVoY2RgXGpbXVlfaXVcnligqVdkgG1pbHBmV3yNWVSam09TWk9TWWp8WaNsaWVtfHRqZlxlXWWKgnFwdnNiVFpgZFdeZGNfZnR+eW11j3dlXWVseHqLgHd7fnV3f4aFdXd+d3R5bX16c3p2b3Rvbnl3fYKGkZ2irKWIgnt2ZW59fnZzaG1tboWBd3l+fYqMeX9taWFYgFNfglxpmXJjYmpcWV5nZXhydk5dg29kYFhaY2lnYF5hWllYZmp0bG9tam56d2lgdHNyaWdlcZ2IlHt2gXVqYVZfYlRUWlNeWmdaY2tnZW13eoOTnG9hT0xVXWRpZ2dlYlFYWF5PSFBScYKSel1mcHhrXVlta21xd21sbmRpeHRsgGZld29qcW5tYGBYVGRnaGhydG1pYWxxcGVhZ19laF9sYVBUXlFTV15bZmdkXGBha36XgmZiWm1rY2JSWFpgcnVrZmFhaWhcYlNKRldxcmFdWVRbZ2dwcXBnYGBZUk1cY2ViZ2higGpDV1VYXltaVVVTUlZHR0xFP0N5R1BRREVLgEZLSUuJk0+PUVVUU2Noa2JmU1VTXFRUVFJUTXNPUF1VU1JMWE1IUF1bYVlPUzs7Ozw/PUpOSkhFUE1KSVNdV0pGPzpEZ19jZjlnVl5gY19iW01cT145b0E+QENlWlhUWFhQVVlbWF1UV1RcXF5lWWRlZmIzbXxBQW9sWVVyWFFPn3oBef96rnoBe4d6AXvBegF7nXoBeZl6AXmRegF5h3oBef96kXoEeXp5eYx6gnmIegF77nqCe416gnv/euZ6AXmKegR5eXp5wHoBe4x6Ant6hHuYegV7enp7e4h6AgIEAICkp7TBt724u8XKtLK4v72om6uzvrLbvJqeqq2mp7mqpsyGoYOwpqOjuLnBvMPO2sW6qLuom6LexKSblZGLk5OXj4iNmZaUl5ebmJukopeVlKOvp6qxqJ6toJKIipSM+v6OjZWJg/aClIyPhYOMlKSch4iO/oaUopKHiIn8lImUkICD/vuCg4uZmYyHnKmntKaXnqi90cjG1d/bxbCwr6qgqLOjrqCsvr20usa5sbbD6ffVw8b84ce/tbmkpqCnrqyusK2noaezvtTOw9Xbwri9ta2noKiqqqzGwre2t6OdqrWwqa6tmJett8CupJ+YpamptqOcl6i50s3D2sa5xMzz7oDewtDHr5iXt6KZoL67qK+2zbqpmpm2vrfguJKMgfX/lKyenquxpqWVkKGnmJeco7GzlYadpIiPko+Miqefk5CSo62RjpaUj46HkqWUiImIiYSIkJCVk5CKjY2gjoX3+o6SjpGIhIqNh4z8/4uLnor/g4SHjJOWk5qaoKWdoqivrYCoqaynp5qYnpSZqKCdmI2Xn5+ioqOenJORmKKbnpCTip2jiYiGg4eAhouUmZysq5mPnp6sn5WMmZ6YnZiRoausmpmOkJWgo56rpaSvrL67rrKinZ6jxcbGrJOako2msKaTkZuwpKqkopWUk5iWkZKSiYurwJCgq6GSjfL99YD99oCF/IWEgpOGl5eQmpegpKeK7KiDhZCOk5aRoKuhnpSfnJKfn7O6ubG0npGWkJeRoJOKjpuXkIqCipmcl5yhlJekrLC3rrOnnK20x+LPvr3M49bGq6WWk6W1qKCclYubmIuJgY6clZqep6apo5+oqauturXM2eHZzdXXwb7Bt6qYkYCSq73L2oXgiYj9mpWcnJ+ap7GThpOTqrq4q62stre+3b+1vLWfo7rMtLexuKiZqrCspI2VnaGYsdzBwK+rpq2ji4WDkJ6fqa61ppmjorKzpaq1saqupaSOj5iclp2Ql5yej5KTlJCEjZ+rqK6pnK+rmpaRoZ+Zjv6EjJmbn6Gjk4CXnaqurp+Wmq+YhoiLl46boJeZn5OorqGYoKGumJugpZ6fn5yZm7awrZyRmqmqq7SxsaSWmpqjnaChsLWuqLi7rrC5tKKttLzNwbHAsbfYgdXEuamwsb+0u8HFz9SsmqWyusW8uLSkm6Onrausq6qKjIyjmIOIhZGE/ouUjIqKhoD0g/uGkZuN+/jthIKHj5WD6O7s9YqPjY+KjoqEi4+XkJGcrKyqqaGxrrOpoaivvKeTmq6wtKetuLK2rbO6wcjL1ba5rqC4xc3T0Nbh6ubZ3d3g09mLnbiniKGB7PHwysbTzdHNwdbCp7DdzcGyt9bHrZqcrLvRurG/uMCroZaYnYBvc36Hf4uGipSZhIGFjoh2bHp/joKmjnF1f4B0doZ5dJRnclp+dG9rfn+Fg4iRnoyDc4J2aXOEiXdzb25sc3V2cGpsdnRyc3R2dnp/hn16doKJgn6AfXSAc2VcXmVdpKpgXF9XVJlUZVtfV1RdY3BnWlxiqVlhbGBXWlqfX1VYWYBVrKpYWl9sa19cbXV3hXdrb36OnIyFkZuWfXBwbGFbXnJnb19nb3twdX14eYGLprSckIqwnIh+dXNkaWBkaWloZ2hlYGNleYyIg5KPhH+AdmxraWhsbXOJin1+hWhfcHl3dXh5dG1xdXpzamtkZWZuZmRjaWZwfnp5mY18foifnICQhYOGemVmfGJgZnJxZWpyfXVpYV5te3qQfWRdTZCSVWpoZnN1aWpYU15hW1ReXWZnUkZXWUlNTk5OSVRSTlhYZGVOTlhZWFJLV2dUSkxTVFNZX19eWFRSVldpV06Bg1JXVl1SS05IREyNmFdYY06FQUhMUVpWT1JPVVpTVVZZXYBZV1pTUUlLUUxTWlhYVU5SV1RUUlRUVE9PWGBbZFdbVWRqUlBNTE1JT1VeZWZ2enBicW58bl5WYWRhZmNaZm1sZF5ZXVxobGpva2lxa3h0b25jYWJmhIiGb1tgWFNjc2xdWmV1am9naF1dXWBfXVxaUlJvgFxlc2dbWZOelUmUioBNlFBPTVhPXF1XWVtkaGtQf2tERU5NUVJOXGZfWlJaWFNaXnB3dm9yYFZaVFdTYVtUVV5aVE1HTVhbVVdaTU1YW11iWV9RSFNWZX1rWlxqf3doWVNGQlJdVU9NSURTVE1OSFJfVFtdZWBeXFVXVFJTWlZocXlxanFzaG10b2BTS4BIWmRxhlWSXFygUUtVW1hWW2FTS1ZVX2RbVVlWW1lofmllbGdVWmt3YmZYX19QWFpaT0NLUFRNZYhxc2NfXWJZR0NIT1ZaY2JsXlVeYnFyam14eHJ1bmhdW11fZF5bXGVoW1xYV1hVYmh1bHh0Z3JvZGdiaWReWahZYGBnbGlqV4BZZWdpcWNXWGJUTE5JWlVUV1pYXFVjYV5WXF1nVFReZFtZU1ZXW25nYVdRWWJlam9paFhYW1dZVlxdZW1vanh2XFpoc21vdH2IgHuEb3ScYpqBdnh4eoqGkJOWoal2aHSBg4uLjYx7cnh7e3d9gIZraG+FfGlta3NhsmdyaWxsaYC6ZcRpdXtxxMnEcW90e4BwxcjAxWpobWxmaWhjaG1ya2xwfnt2dm94c3lyaW5zgXJgZXJ1e3F3gH2EfISLkZedpIV9b2V6g4eMg4iUmZSMjouMfoNcbIJ4ZXdeq62xkYuTkpiZk6GWfYOtnpWIiqeVfHBvfoeZhHqJhIl5b2hlaoBNUVpkXGVhZG5yX1peZGBRRVFTYVZtWkVLU1RNUF5VUnBRWkZaT0xKW1tfXWRweWlhUWFTSU9naFNLSUhHTUtQS0RGTkxLSUxLS09WW1RRT1xnYWBlYVtnYlVPUVlUkpZWUltOSYRIV1BVTUhPU2BZTk1QhEVNVEtER0h8S0FHRoBAfng+PkJLS0E+T1VVYlZKTVtsenJuen5+bmFbU0xKTVtTWkxQWF5XV11YW1xifYpwaGuLdGdeV1hISUNKT1BUVFNPTE9QW25rZHVvXllfXFFPSUhHTlVpaV5iaVRNWWJjZGZpYVhkbGxiXlxYXlRPS0VFSEhVZl5cem1aWmJ6e4B1bG5xYlNdb1tYXWliWmFqfXJnYmBpdHB9a1hWSYSKU2tmX2hwbG5kXWBmYV5mZ3BzV1FjaFZaX1xZU2NlXWRqeH5qZWpoamdea4BtYGBlaGlucG1vb3BtdGt7amSvsmZoZm5jYGVgXl+xtmhqd2WwV1xgZGxoZGVhZm1mamttcIBta25pZ19gZ2JpcW5saGBkamhsbm5tcGppcnhwdGNmX290XlxZV1hSWV5kZ2l4e3BhcHB+b2VbZmpmaWdea3JvZWJbXmFqbGlvamhybnx0b3FlYmRngYR+Z1NZUExda2NUUl1uY2hhYldXV1pbWVtbVVd1h2FsfHNjXqGtpFOonoBXp1lXVV9WYmReYWFoa25ThGxJSlFTVVhVY3BnYVtkX1phZXh/gnh7aV5hW15baGFaXGdkXltTWmhsZmlsYWFucXV6c3lrYm5ygZmGdnaCmY9+bWhcWmt4cW1rZWFvbGBfWmVyam9yd3Jva2ZqaWdnbmp9ho6FfIeNen6EfG5gVoBUZ3B9jFSWW1ulW1ZdYF9bYmhWT1xaZ2xmX2NfZ2Zzf2xqcm1aX3J/bXBkbWteaWxrZFheZGVab5V/f3FtaGpjU01SVllbZGZsW1BZX2hxaGtvcXRyamRYV1NUWVhWVl9gVFtTU1JKT1ttZm9pYHBuYGFVXl9aWaFXYGZpamNpXoBcZm9xeGdiZmxiVlJRZ2FkZGRiY2BqaWdiZGRvWFpcW1hWUlNQVWhjX1ZMUV5gYGViY1dSU1FSTlZRWGViW2pkRkZSW1NTVF5qYFVbS1FxR25bUlFVWGdhZ21weH1USFNeYWdmZ2haUFlbX1tdXl1GRUxjXUtPS1A/dEhSSkpKSIB7Q35ETldNh4N6SUhLUldId3p7fEZHSkpFSEZDR0lPSktQX11ZWE9WUFNLREhOWUo7Qk9TWE1QV1dZUVpdYGVoblVOQzlKUVZZUlZfZF5WVlNQSEs4QUxHOU8+cXB0XlheXGFjX2pjS1B3amFWWXRlT0FFUlxrWVJfXWFSSkNCR6F6g3u6eoJ5hXoBeY16AXmHegF5hXqCef96m3qCeb56gnmKeoJ5hHoBef96i3oIeXl5enl5enmOegF59HoEe3p7e+56AXnXegF7qXoBeYZ6A3l6eYR6g3mGeoR5wHqEewN8e3ulegICBACAqrW4tsGl4tSlqaefob2ws8z84tO3rqGUpqCVrq+ttLC7xsG1ubKswMi0squ5ycGutbzPpZKMkISLh42WnqWsopieoJeVjIiQm6OD/4qip5mak42ZlKCkp5D2jZ+wmouJh4Hn3PSJivn4g4WGk52Ni5aX+oKGiI6Rnpu0uaaC/4yAho2WhI2Ki5ifrbi3x8jCrqiel6OnrLO6v8zGz7m0sJ+puK+2uMG+xbCxoaioq6GnvMLT2My+wdDSyb2XnMC6xb2nnqe1wbOotdSJ7LbLwLKzsrm2sqe9wrynvqe0rKWrl5mShZWts8DhyoDr/qrchf7Ew7a/zuPg27elqsC5r6mAuL68wq6sq6qzobbCuaWus6KisLecl6S11+jSuamnlqG1ytPM0r+W/IGImYeJq6+stJmor52ji4+gi4GGl5SWjIidkY2EgOfyjZOJh42RiIGH/oWMh4WDjYmA8f7l7IKMjJWSh/HpgpONjZOXn5mbiufe/pOaopyOiomQmKitrK6Aq6WjnZyRhI+UlJ+UmaKYjZGTnZCPk5KglpyblZaHj5q0oYKPkYiHgYaUiJiTmKGspK6xs5KcrL+k6YCA84GPi4uSpKenq622ubjAur/Jv62nnamytbvDz7KIhIOcub20r6qxwK2iqZ2nrLmurZ+dlaa9tNC/r6CLi4b4hYKCgI2Ah4aPkISLiJOTj5OYj4OB7N6BhYuQjIiOlJ2qp6Wbnqi3tJia08KnsqmglZ2XnZmknaqgn6SkppyanZaRlI+dnZuhvsaqrK6nvrzDysnO9/3u6dvDq5+lrqeVjZ6pmZKJkYyIlKasorDCv7musLSusqq5zbmxpq64oKzJy8e4vbOArquXpLu0qsTMrbCbnqeyo5+Vm7XTybyxqqy0pLGu7smxuKrAsrzNv6WZlJymn5SUjpKPlqCys7+xt8K4rri4tZaMkJWtr7rKz6mhn5eYmpuopKOuqZialJ2ukZChkpWbm52trKappqGkraejipaYkIOPnLCan6eL+46rqZiYpqeAmaGUtqaalKWrnIuLkZ2VnaaZlrKgj6SupqqwoaK+p6eem5mblMHEk5qtnZiYm6SkmpeamqOTjJePjZWksJyvrrXP9vfXqqmrtcHBwL3DxMLJvrivraSwsaOkraq1v7+gpa2jpKWpoJqkmbCzurmxjoahpLegkoORif6HlZeeioSAgpCYl4aDg4WKh4Ty+4SB/ev46fL6gYWBh4aBiJqblKSooq2mo6e3qp6fpKauqKmrq7vOrbWwraipv72zo6qrraGZrLCrvdrTvq+irMDDwbi+xra7vLm4scXOycK+wcnUzrfOo5+Sk5+XpLjr9f/p0cOnmJyvv7i9vLmwtbanu7GAeYSFhIx0nZt4fnhxc4V+gJe0qZ2FfHJncGthcnZyeXWCkIp7gHhziot3d3OBlYd+hJOTfGtmbWVpam5xeX2FgH9/gHd1b295gYlowGl5f3Z3b210cXd0fGiuZXWCbVZcXFqVi55YV5+gVFhZZGxjYmlonFBYXFpibGaAiXpYqGGAWGFnW2JgX2dpd4CDlpqUfXNhXWVrbHB6foqCj3h2eGhtdGlvdHl6gHh3bnBtb2t2hICRlot/go+ZkoBeX3t3en5pXGBndWlkdY5hrH6Lg3p9fn15c3KBi4V2kHRzc3J0aGtlYHB1e4acikqOkmmdYayMjIh8ipCVkHltcn59eG+Acn95gnx+gHx9bYaBd2xzcWRhbXtoVl93k6GbinpuXmRzjpuRn5Bej0tQV0lMZ2lnaU9XZFVcS0tYT0hKVEpISU5bWU1HRnyRWmFWVFhWT0hRmFRbWFdUWFJIj5qIj1FVUFNQTY6FTFlQT1NYYF5dU4J1h1FZYl1SSkhMUFhaWV2AWVZWUlJKQEZLS1VTWl5SSk5SWVJQVlZdVFpXWVZQWGB4Zk5aXFFRTFFWTlxcZGt5cXmAgGNugJh6m1hWmVNeWVhfbmpnbXJyc3J2dHiAd2ljXmdvcXl9hm5LRERacXNuaGdsdWljZWFmbndvbWJjWWd6dIl8b2JRU0+SU05PTFWAUFJaW1JVVmJiW15hW1BPinpLTlFUUk5RUlthYGNZW2Rvblpel4NnbWhhWWRaXltlYW5jY2ViYl5ZW1NLR0RLSEZJXmJLTlBKXl9kbGVujpOIg3diUU9RWlpOSlllWVJKUlBKVGRpW2VxamRZWVpWWlBYZ1lWUltmUV15eHJjZFyAXl9VY25jXXZ+Z2ZYWV1pX15bX2l+cm9nYV5jW2Ziindocmd3Y2h5blpTUlhbUUtLSEtMUVNnaHJlbXFwbnJ0c2FST1ZmaHF/iGNfX1tbX19oa21yc2JlXGBsXlZhWVpnaGVsaGtscm5pcG5nWl5gV1Vea3dkZG9grWBubmNib2aAW2hdZ21jXWhjV1BYV15eXmRdXW5gUmBmZGlmXmB0XmZdVFBQSXFxT09iWFNXVlxgWFVaXGVXUFFKTlZjal1zdnaInZ+Ibm5vdIODfYGKhYSOhn92cXB+e3BygoCQlZRzeIF8fXl6c3J7b39+ioB8XVx5hJZ/dGl3cdRueXd1Z2aAZnR6eGxlZ2hsbGnCzHFx3MzPxMzMaWxlamdfZHdzc3uCfoR/fXuIem1tb3J2c3N0c3yLd4B6eHZ5jY+AdHx7fnNod398jqWdi3pud4WGhXl7e21yeXN0anyNjIWCho+XkYGXfXhsbnhxfZG9w9G3oJR7a298iYOIiIZ8g4Z5h3+AUl5fXWNNeXNPUU9ISl1WV2+NfnFdUkc9SUQ8TFJNU09baWJVWFVRZGlYVFBcaWFYY2xwWEhDSUFEQ0dIUFNbU09UU0xJQ0FLVF0/cUFSV09STk5YWWJlaVaPWGRxXk1OTkyEfJBSUo+OS05OVllPTVJRez9HTEtOVU9iaV1Df0yAQkZLQUhGRU5RXGRlc3ZxYVpNSU9SVV5nZmxoc2BeXU9UWlFWWV1bYVhZS0pGR0VRY2BtcmtgZW90b2JERmRgZWNQR0hNWk1MWXRMf1lrZlxhX1dTWVRibGRXcVtdWVpbVVhRT1pnbXOFdj9zhl56RYZma2NcaXNtaVhKTVRTUk+AVF5gZl9gbWloYnNxaV1naFxdaXZoVVxugoyJf3NoXF9rg4Z7hXxdk1BXV0lRcHFtblpfZ11lVVdjW1JRW1ZYVVlpZGRfWZSkZ2xkY2ZrYlpiv2lybGdlbGlivMKtr19nY2diW6qiXGxkY2dpcW1tZKabrWVrdm9kXFtfY21wb3GAbWxrZ2VeVFpfYGlkaG9lXWNlbmVla2x0bHBwb2xkanGJc1ZhYVhZU1hiWWZiaW93cnh6fGJqeodyllVUmFJdWVdcbGtobHBzdHN6d3d9dWpmX2hwbnR5g2xIQkJWb3JraGRpcGRdYFliZW5rZ11dWGV3dIZ7cGRUVFCXVVBSUFqAVFZcXVRXVmBgXF9jW05NiXpLT1NVUU9UWF5naWZbXWVwblhbjYBmbWdiWWRcYl9paHRqa25sbmZkZmBZWVZeXFpec3ZfYmRecnN5fneAoKqhnpJ/b2xyfnhoYXB8bmphaGRdaHd8bniFgXhra21qbWJsfnBqY214X26OjIZycWWAZWRXYnFqY3yEcW9eYGZvZmNdYG2Cd3JnYmJpYGhkh3drc2t3Z3B/dWJcXGJoXlZZV1lWW2Bwb3ludHt2b3V6e2dWT1NlaHF9f19bW1JWXVtfYmpua1hdVlVfT05gVFNeXmNoZmdlZ11hamZeUFlcVE5VW2tcYG1boVppZ1tWZmWAWl9cbGtgXm1lXlpaWmdqam9lZndoWmVqa3JtY2N2Yl5TUExKRGVoRUlbUk5NT1dYUE5TWWBORklBRE5WXVJmZmJxgYNtUFBOUV1eWVteWltjWFVPTUhXVUxNWVhma2dJTlhUVVNUTk5ZUGFgamdfQD5WXmxaU0pXUpJOVFFSRUOAQ05TUUVAQkBERUR5g0lIi4OEd3t7QUZBRkM9RVNRUF1gW2hfWlplWExMTVBTTUxNS1VlT1ZST0xNZGRXS01OT0Q6SU1HVWhjVUhARlRYWkxPUEVLTUhHQVFdW1RTVltjXE1gSEQ5PEI8SFh/g41/al9KP0RRW1ldXV1YWF5RXlnLegF5jXoBeYh6B3l5eXp6eXmJegF5i3oBect6AXugegV5eXp6e7d6AXmeeoJ5iXoBeYh6hHmGeoJ5inqDecZ6BHl6enm9egF5lHqCef966HoBef96Anp5kXoEeXl6eoZ563oCAgQAgKGbiZ6mpbjDubanmpavub/AtMrbxq3ZgdnGybetoq6vw6Shqr7i06+in5+nrJeImaeZnp2dnJH18oiIi42dpKu4p5+kq4yWmZORq430jpeXnJadmpCMhYWG8oCBhviA+PH/hoqVnaWelZGbkpyXlpuUpZyDgZmYrZOStImIocjegMivpZ+ts6udnJaSkaa4uMXDs7u0wPXowMaztritsdLmwtLOzMW/urixq6SunZGfnaCfrcGtp7O2wrSupqXCvbrP1NDFx8jAsK6jssS3x73Ay8LXzbjHx7CinMfg14zZsKWinam2jpy3saqrrLe+8YzPuaWWvcfPwMSwqqKws7fGgNu5sbKnra2v1MT5u8i5vbiJ9ZamoIWKpLrYm6OipbCyvayilaGR/IKDjomTrLCwnZqan6CinJqboKWUg56spqmolpCK7/2A5PKNlZ2kiIOVnJuVmZvt5trb8YWNjIfpgICDjIqQnZWZko6JiIqeqrCmhvqEhJWdnpudq5Oam6efgJuWmpialI6Mjpeml5OSjJKCmpeRjJaWj//78P6GjYaUjZaSjYCBio6eop+foJ6Yn5err5yal/3/lIr73db/9fX/hYqOkaGrrLOsu8e/tZujk562t7a8wozur5uenqGekoiRmqSkrsGwrbGprrm3rJegqbOtq6GpsJSXk4OC8P+LgI+OkJGXkpudm46E/YKCipeSkqKkoYmQkpeooZOjuMa7sa+hwaOHtJ+ilKqmn5+psrqnuNXgwKqrrqOeoauuvr2ytK61sr63stDMy+XIu7/AvMXY0LCdl5ejk5afpq6ts6uotqiiwrOmrampx825ub2qo6eztKWkq6qxzbLBwsHHgLatrKq0rqSfpKissK2bna3Bu7CghIuSnYqNpJKMmJ2ZlpOamqyoko6vr7q+rKSqr6izzcC2laCnpqytqqWvvbK8qaKelJGai4+Xs+DPq5KhmZyUhJGTmpukq6mfmJ6jnIiVlZ6oqKu2sJyou7CqnouAipmYl5f99YCVp6OPmqe2gLW5psG4o7Smr7WumJqjpqWgmZiipaW3trSpqaKspLi4pZmkpqiXmouOnKSsuraspKmnmY6OgJaTrauYk5app6ywuLnAxcy/tb7JzszPw7PAxbm4srKwqa2omaq2ubm1qrGyt62rpq26tqOsq7KtmJ+kn5idhJOdkpSdoJqNlpGQgJWUkoWIgoOMjpiMjJCYop+ViY+IgYeJiYWQg/ODkYmhlpebr6uztp6ctb2hoMrBppyWrK2vpaKVoKqntqyqoK+op6aknpObqKCnsq22urG6wczb8czDtbKmo6axsbakr6eeoayssJ2wrraqsMK61M+snZ6aq7KwwLuwqrKyqKqxgHFrXW10coWOiYNxZmR4gIWIfIycjHCYX52LjHx2bHZ4inRveI+ooYF2cnWBhHFkc3x2eXd3dWuysmJsbG+AiI2Si4OBhW12dW1xiXK/bnVtc3J9dnBpY2FltmNka7Vbq6SlX2Jsb3NuZ2RvaXNtaW9reHBVUmdldmJlgl1bdpmxgJZ+dWt1e29qa2xoZnR/f4yJfYN8hLWgfId1foN4eo6Zf4eJioWJhoV/dGpxaF9iYmRlcoB3cnqEin11a2uBg4WSi4N/eIR/bmpleYd8h4mLlIeRjYOJjIF4dJGho2aggHZxdICDYGh9eXV1d3l7q2OOhXNqd4SEe3dzdm95fIiFgJB8dW12e36FpofBhYOBhndUm1ZlalFPY4KaYXF3d3N3emxoXmVco01IUVFYZnNrXVlWVlNVWVRYXGFSS2JsZmNiWVpZjplKhItcZG52WVhiZmFeZGaQiX9/j01TUU2JTk5QUU1QWF1iXllQTEpcZWpgSIlJRlBUU1FRX05UU1lUgEpHT1FUUkpLTVZfUU1KRE1EWlhUUFtcV5uclJlRUlBeVWBcWVBRWFxna2doZ2FdX1pnbF5dXpadYVaahH+lm5iiVlZbXWhub3JzeX10cllcUllpa21zd2CcZ1ldXmBeVlBVW2Rmb4JzcHRwdXt8bl1lbnl1cWdweGJfYFRSlaJdgF9hY2NmYWxzbFtRmlFSWWRhXmxqYlJXWVlmXFNjeIN0bm9hfoRnclxgUWlpYGBra3tieZKbdV9gZFhUUlpaZmRWWVhbWWRaV2tiZ3pkWl5hYmh9eVxQT1BVTk9ZX2dncGhmdV9ac2RXW1dWb3VfXWVVUllkYlhVXVhdcl5pcXd6gGlfXF9qa2ZgYl9gaWZbZHR5al9ZRE1QWU9TZFZTWFtZV1JUUmBeTU9ram9wY19mZ2ZuiXJpUFdjY2ZnZWRvhnt4aGFWUFFXTE1ZcqKZdlhjYWNeU2BkaGVmdW1lX2ZsZ1lbWWRta3Z4cGJsgnhvZFVRWmZhXVubpVxkb2tcZ3Z5gHl9bXJ7cX10cHR3Z2dpbWtjYV9nY2t9b3JtZ2NpZnBvbVxeXF1SUklLVmBpd3NrYGBmW09RSVxba2RSU1hiZG51eXl5dX13d4WPkI+Yi32LjoGEfYJ9dXZ1aXeDiIyKhIuIin1/foSKfXR8foB7bXJ2cGx1Y3R/dXR9f31yenRygHR2dW90cHBzdYJzcnZ+h4eAdXt1b3Nycm52bcVqbml8bmttfnt9iHFwhIx3dpmPeHBofH1/dXNsd316gX97dIB5fX14cmlwc3V7hXyIiH6FhY2XqZaHfH1xbXB6en9scm5nc3yAhnKCgImFgYuKnZh5cXJseoJ/jId/fH5+eXh/gEpGOkdOTFtkX1tOQkBQWl5hWWZyYUlsRXNjYlVPRk5RYktKTmJ7dVpQTlBaXE1BTFVNUE9NS0JhYTk9PUBPVlpjWVJUVkBISENHX0lySE5MU1FdWVNRT05RkVBQWJhQlJCVVFZgY2JfV1JYUlxXVVlVYltBPU5MWktMY0RCWXeJgHReW1RhY1lQT01LTFhiZ29pYGdkaox6YWpbYmVeYHJ8Z3BsbGVkY2BZTENLQkBHRERGU2BZUltgaWBbVFZqaWh3cWhiX2ZkU1FGU2BbaGhscGFscGJqb15TVG16fkV3ZV1XXmxvTldoZWhnaHFqf0RqXlNFVGFkVVNRUEVKTFpjgG9YV1BWXWdvjHimcnVwd21LilZjaVJOX3qFV2dwb3BzdGVjWFxRj0pJVlpaZHFwZF1ZXl5YW1lcYmdbVGdsZmlqX15fpLBYl55ibXV+ZWJudW9tcHeyq52aql5mZWGoWllbXV1iZWZvamVfXFpsdndwV6ZcWmZqaWdncmFmZ25pgGBcY2JkYVpaW2NuYmFeW2FYbmtnZW1uabq6tLxjZWJwaHJsaVxcYWRvcm1tbGhkaGNydmhmZ6eoZFqgiYKpnJmkWFteYGtwcXVyeH5zcFlcUFdoaGlvc16bY1VWWFlaUEpPVV1eZ3psaWtobnNyZ1heaHNvbmVtdFtaW09Ojp1YgFpbW1tfWWJmYVVLjkpLUltaV2RjXUtRVFhjW1Nid4V0bWxcdmtXblpcTmNjXF1panVid5Gdemdpa2BbXGNkcHBjZWNoZnBraX95fpODd3x/f4iZk3RlY2VtZWlyeX9/iH97inRwiHVobGlogYZzcnlnZGpydWtnb2pugmx5enx8gGxjYGNtbmhjZmppcG1gaHV7b2RZQ0pOV0tOYFNQV1taV1JXWGRiUVR2dHt6bWpucG92i3htVV1mZWlnZWVxg3l5Zl5XUVJVSEtXcJmUd1deXWRYS1ZaXVdZZWBgWVxgXlJXUV1mYWhraFxkdG5pXVBHTFRTVVSIjVBYY11MU2JvgG5sYnR2Z3dxbG53aWJqdXFqY2NsZ3CAbmxvbGJpY21nYFNaWFhKRT9BS1dhbWpiW1peU0lOSFZSYlpJSk9YWmBlaWReWmBYV2NoZmVtYVFcX1VVTlNRTk1MQlBaYGRiXGBcXlVXV11oXFJbXWJeTlRZVU9VQ1BcUlFaXVtRVlBLgE5PUEhKRkVISVRIR0tRWVlSSVBMR0xNTUlSSYBJUEpdT05OXVpeZ09MYWpPTnFoT0dBU1RUSUY+R09LU09NSFNOUE1KQzo/RURJUUpXVk5WV19mcmFYUFBIQ0ZMUVJBSUA7RExMUUJPTlRMS1dVZGBGP0I+SU9QW1dRTlJTTlBVl3oBe596gnmTegF5jHoJeXp6enl6eXl5+3oBe5F6AXuhegF5lHoBeZ16BXl5enl5jHqFeYR6AXmTegF5pXqEeZp6BHl5enqHeZZ6AXuleoJ5jHoBeZh6gnv/etF6gnn/eqR6AXnkegICBACArqyerKaYqKrGxcW2q6Kwx8u9ube1s66tuMXYw66trrKvp56ipa+vrKWhvLukoKKco5ial5CEi6OpnYqAjpexq8a8rJ+Yl5WUmpONj4WCgpKKkJGSmZmPi5KKjIrw4uPlgYKZ1qmKkpGDiZecl6izsampmY2DlYualZyWkouUmrKA0s7P18TPwcGqorSvnpGQvb+hqbPAra2giYiWlaClq6Cjpam7s6WforCsrK27r6mvpaWprquenJmhlZWkq6Cps8LHxcPHwL3Qxry0yra0qaTC0M68qqmhmouQnaCinZyorKGRm6qZmqqgqZyLorLKyMa9vL3j5K+1tqGho7/Wlb2A3ZTd3L6zsJujxM2vx8qwlJqipsS1koynq4yKjYePlqmnra2qpZqWiYWMkImMkaeJ/YiTlYybvMvAv7+5sZq7z8e7l4z7hoeIioqMmq2gk5uKk4+ChPX22d37i42GjYmLl46RmpqjrNvWp5OQho2Qq72wq6adlqSxvKWrsbKopLGApZmYnKOZnoyIjJCRjYyQnJqZlYePkYONgoKEgO7+hv2SlImKjJmjkpT1+eT+mpqalJeNk4yNo6ipq5iYkpyFlpiYiJGRk5mroaGVlp+nmMC0r6SikJSXrK6XmoySmp+eoY2HiZCRlq2bi5iYjoSzvayjrbOzt76nmY6RgoLw84GAkI32gZWNiYuLh4+NmZWQk5OSoaCfl5aTkZeUhIqLiOyypaWst7KimJ6npqmuurrHz8vAyc3RxMS3qqGlqbS1vM26s6WhprO6v767xsK2r5eVqsnIysa4sZiNkZKNiJaZj4qcpKSYnZSYraartLbTxcGpopqqtbW0wr+/r66uqpqAloyQna6mqJiXk/qJlI+ZoJ2ampqJiIWK/YGRif2Ci5eZnJqRh5KQn7ezwdTNubevs8LCsYn99o2QjoqMj5GZnaudkYqKkJ2poa2mk4+WmJyao5iSkZefu66nmJOSkIuBioSQjYqIjamfmqm0p7K3rqKqq6SVjIP+hMPPxsCxnJ2ArbzDv4igvLi/vqqVkKKdkI+UlJiSj5qZrJmOnKGkqqWnoJ6kqZqOqry8ra+uu7mtuqqfkZCPjZeWnKG1uLqqs7Sop6uzw8nAtLGeoba/qJSqtrG1scC7kq+ysrq4rbSvn5mfnam0p9OvoJeZlpiUjpGRlpqko5SIiImXm5+Ph4aAkIWLiZGijPLngfn895CYjYmrr6GjoI+Ag/+Aho2Pj46OkpKckp6knqKinZadyLOmtLe4sJysoJSclY2eq5yUl5mhm5iYpKSer6y8qq2um56vo6+1sq68vLafm5+ypJectLebnKmmq76+trC4xbKtqrSxqMGvvbGut8GqqafDsbeAfHduenVsd3qSj49/dW13jImEgH5+eHN2d4WUjXd1dnl3c251dIaEg393kYl6d3t1fXR4dWxhaIGGdWdeaXOKiZqbi4N5dnNudXFrbWZkZnVvdHV5goN6cnNpZWOpoaWwZGF4n4Vscm5gYmt0coGHhHx6a2NWbWBrZ3BrZmRlZ3mAjIiSmpKgmYV0bXx3a2Jjjo9ocnmDdHVrXl5lYmNoamBkaXKHgXdtbnl3fn2AcmtwamtweHdta2luYGJvd3Bzd36CgH6FgHiKhoJ3iHuAd26JjJGFeHF1dGloanFxbGp5d3RoaXRnanFoaWBRX2x+fIeIhIWVk2tvZF5ga4GVYoSAmGWdkod6fG93lqOCjJl9ZF1pa3l7Y1Voa1ZUVVllZmhobXFzb2BbWVJQVVRVU2tShEdMTkROcXt6en15dWF/kpWDV1OWVVdXW1tban9xX2peYF1PTImJd36aWFpWUk9OWFNXYWNoaYiOZ1paVFNUZm9lYF9WTllnb1pWYl9bWWmAYVNRUFdNUEZGTFFQSklQW1lbVktSWE5WS0pJR4WWUKZjY1lZWmRpWlyMi32SYGJiXF9WWlFRYWBiY1ZUUFRJVlhYTldZW19tZmZdWWNpXX12b2dkVVhbbG9cXlNXXWFiZ1RPU1haX3BkWGJfVk96h3NpcXh3dYBuY1leUlGVmFCAXFqZUF5cV1xbV1tXYGBdXFlaZ2RiW1pVU1NQQUhkZ6dxYWVseHVkVVxlZWdtgHmEioB0foSFdXNmWVZbXmVhZm9dV0tISlFbYWBjbW5eW0hHV3J1dnRpYlFJUU9PSVNVSUhYXWBUV01OW1VaXWJ8bW1aVU1aY2Blc3d3aGJgW1KAUVBVYGxmYldXWJdWW1FTWVdbW15PUVFWl01YTIpJTVRRT01GRUxKVW5sfZKHd3dub3p1aEmIh09TVFFTW11hXmZaTEhPVlliZnBlW1dVWmBeZ2ViYGJpdXVvW1tYWVhTU05ZW1BTVmZmY213dHZ6dGxyeWtYUU2sY5OYiYl8cGKAboSJdU5ujomEfHBjZXBmXllhZGFbV2lgY2RYZGppcGRnZ2JgXVJNYWxzZGdse3tvdGVaVFBOUl1hYltpcnhscXRub3B3goV9dnZqbnqOfGd2gH5+fIaEYHd5dIWIfYJ7b297d32GfaiHeG5uaHFsZ2pobG9wcWZlZ2h3e3xxbHCAe290cnWGd9HNceHQznmBc22NkIqPjH1wc9hscHRycG5ubG52bnR6cXNxamJnjX5xgImEhXWDfHV2cmx+iHtzd3uAeXZ5foF/iIWRf4GCbmt8cXh9fHmAfH1raG18bWVsfoJobn15fpOSkImLlYZ7d4WDe5WBj4N+iZB6dnWOfoOAUU5GTUpBTE9mZGRXT0dRZWNeWlZUUExOUV1wY09PT1JTS0dLTFdWVVJNYl9QTlBMUUhLSEI4PVJXSDsyPENYV2VkVU1GRkRCSkdCREFBQ1BNU1VZYmRdWlxVVVGNhIKHTUxehmpSV1ZLT1ZbWWRnZFtXS0Q8TUVPSVFMR0ZJTl+AcXR8gHR8dGlbVWNeU0pKaGxOVlliV1pPPj9ISUpOUkhJS1BiYFRIRlBQVVhfUkpQS01UWlZOTUxUSEtYXVRXXWFjYmFpY15tY1tSZ1pfVExeaW5hVVFPTENKSktRTU1cXVtVVmFUU1xYYVRFVVtiZGllZ2NxcU5MQj8+RVVlP1SAZUx3bGFfYVxfeoFud35qUlFeZXZzXlFhZk5GTlReYGVmamtsZ1pWUE1RWFpWUGZTkk5UVE5WcHl3eXx6eGaAjYqBXVaeWVxdYWBhbX1zZ25jZ2NYVJmfkpizYmRgYV9gZ15dZ2RtcIqNa2JjXV5hdH93cW5kXmp5gm5sdnRua3iAcGRiYmhdX1RSV11fXFxjb2tsZ1tgZVpiWFhZWKe3YcBzdGdoaXR6bnGrqZaqampoY2VeY1xcbW9wcGJgXGFUYmNlWWFhY2h1bGtfXGVpXX52cGRgUFJUZmlUVUtRWWBeY1BJTFRUWm1fU2BcU011gXFocXd3d35sXldaTUyLjEuAVlSMSlhTT1FRT1NQWlpWV1VWY2FhWFhVU1VRQ0lYXaFvX2Jncm5gVFpiYGNpdnaAhn50e4GDdndrYVxhZ25tdH1saV1cXmdyeHd7hINzbllYbIiLjouAemliaWhnYmptYFxqcHFjZVpda2RqcHOPgH9qY11qcnFzgoCAcmpoY1yAWVVZY3JtalxaWZdWXFJWXl1eXF5QUE5SkUpVS4pITFVUVFJLSlRTYHd0hJSMfn51dHx2akmDfkxNT01PVFdcXmRZTktQVFpkZWxkYlxaWF9hZV9ZWlldbWhkXFtSUVJPU0lTU0pKTGFfX2ZtbHFybWBiZmFRSEKQUnqFenZlVlGAYnBybUZgfIB6cmtlX25qZFtiYmRiWmpfY19caGdma2JfXVtcWk5EVmNoWl9lcW9oc2JVTkxLT1paW1VfaG9iY2ZeXFpeY2FbU1NHSFFdTjxJUE5RUFpZO05RTmFgVl1XTE5YU1dfVn9hVU1PSlFNSEtLUFVYWEpGR0lUWGBSTE2AV0tPTFBcTX15RoZ9eEpSSEVjZV9hYVFHSpJKT1RSUlBQT1FaT1VaT1FORz9Fa1hLV19ZVUZSS0RGQjxLVUtDRUhLRUJCSE1KV1RhVVVZRENTSE1RUlBWVFFBPkFRQTk8TFM8PUhLTVpeVU9TXE9GRU1PSV5SWlFOVF1KSUhgUVbceoR5/3qfeoR7rnoBeZN6AXmQeoV5wnoEeXl6eYl6hHnQegZ5eXp6enmaeoJ763oBeY16BXl6enp5mHqCecB6AXn/epB6Bnl5enl5eYx6AXnmegICBACAtbC9sLOjn626xNDVxbO+rrK+vb/Fybq2r63q37DF4L2rrqKfq6Wzx8+7kaelo6qUmqG2p5ilt8fItqOeqJmQoLagn5ePlJ+jmJ2RkYiTkoaYl5OdtcunlI7594WChOzR6PiEjfiPnYX6/4CRnY+jw8KhoqOnr52bkouNlJ2alJ2Am5+Mj4aFkKbBtLb29MGkqc2gh5aRg4v+94qPnKWrqpqVnZuZprS0pKKks8nP8bealZWSm5+SlaKknqCqsLi4ybyzraavr6en0OPIx7WxrK2rq6OWnrrArqmss7S5x66qloiUmImfn6OosLCwpamzsammparhrpCcvNK2tLC9v7qAwtPXx6ejqZ6NnbjR7t2ZjaKzn5+wqJ6rv6ydqrXFubK5srWpko+FioyVlpqOjdDJj5Gfp4ubtcKxrsffs4aJjZKPkJaWh4GUnKORkoL4hoiOiv/v6P6Lj5aVgYCAjJ2fpZaIu+2F0aWkvdC5rZyYioyUqLm+rKOamb7axaWWkIuAm5CHnZ+iscC12eXT17SelIuRnbahj5GZpJmLhoWKhpymk/WMrKiPkIqVmamgtZmVkfiBgYiOh4SDnJ6dpaaim5qbjJKeorG+uqyYn6WSgo6SmqiUjImqhsSOkZ6nj4mRg4KNoKKysrSenJ6dko+nraOpqa+zsb6xtKOcmZ2co6OAmqCopp+YrLamnaW0s6rGyJKVkpaZmJOVi4GClJegnZ6UhI2KkpOOjIqfrbHCvLvY9+/RtKmqqrevrLW8o5yinLCzu73Bu7y2v72/wtCysqKioKeanZullJKgoZ+dppSXpJ2Hi42dp5Sbp6qnn+bJgp2eppmms7HEz9XJy8i2paSAoqGnm5ygmIaJiYmYjY+DgoiQg4SAh5mbkIqGiJejpJaZnZGFj42Sm56gucK0nqiqlp6w1/XXqqajtrSpo6yplJSeipGhqqOnt7mel5WLjZaWi4aKk6CgmI+IkpiVk4GGnI6BkJ+hpLywo5msvb7AusC0qp2djIeFjJeclq2urKGAoqSUlJOC+I2PtZeWlYP8gYmKjYWKjJGSpLeklpyXlpyssaaepLuyt7i0vsXRy7zDw7Ojpa+po5qakZ+qt76vqbSfoZ6hna6s156bmKSwv8rVqrTArJyQjJOOlZGfsaynybu5s8Skr7aurLKuoJelpKiooaKknZGSmJaYioqMgIeAiZOSjYj+54OTh+/04uP2hoOIlZCNoKiblpORiIOEg7CniJ6OhISLmJSlmeqAjJGVoa6lrqmmmp6ppaGkopaSk56npZiYlJmaoa+spaCno52gm6eoo6mqsKuaoaGeoMCyuKuasrGsx7/CqbG1tbnFxcC3s8G+wcHCsKq9z7a9vsGAd3OBdXdoZXJ8g4qThHmBdHeDhYiJjIR/enSrm3uMqoh3eHB0dm5/l5aQZnZ0en5qanWJfnJ+kKKikoB6gHVwfI17eG5vd4OBe4B2d253eXB6e3R/lqyHc2/FwmlpZ7OiusxtcMZxfmnAwF5mb2Nyl5B3d3R2f3RuaF9eXGFgW2WAZ2hjZVpWXHSSh47Z1pZ4eah4XGllX2O2pVpcZGlucGlsc3BteIGAdHZyfIyLqHdnZ2JibHFmZmlyb296gX17hHtwbW1zbmltj56Gjoh4c25qcnZmaIaXioB8jISLln11aF5hX11udG5xbnVqXmVpbXJta2qMZlVacYd4dHKBiYWAgoiQgW5xcG1jaoChr6JnWGVjaWRqa2lte21jc36RiX9zcXBsXl1QU1xlW19cWo+MWFJaWkVRZW9kaIOhc01UWV9cXmRdTEpaZ29gY1OaVVpgX6eUjZtTU1RWSEdJU2BjZ1pJbpBVi2tndoFwZV9eUFFUYmxsYllPTm2IdllSSUKAS0VAU1lfam5je4OBjHVjWk9VYnpoUVFeX1tPTE9UU2JuWoVPbGpVV1FbXmdne2NaWYxKTVBVTUpIW19ZYWJeWlhaTlBaXWhyc2ZWXmJTSVNWXmpcUlBrXHxWWmVrWlFbTU1WZ257eXhmZWVhWVdqb2htanFzb35wdWVgXl5fZGOAXmFpaF1YbXxqYGd3cGqGiFRYVllcW1dXT0ZHVFVgXGZaR0tNU1VQTktVZGh6eHiPp6GKcWVlZW1kXmJiUkxRTF5dZ2RuY2JeZmJjaHdeYFBSUlpPUlFbTk1WV1dYX1RYZllLTUxWXEtQVltYTI2OVFFNWVBeZWN1g4CCh4l9bGiAYmNkWltiYVdVT0pVUFVNUVZdUlRPU2JhUUxITFNZVklOUUo9QkJMWVxec39sWmFhUVhpkaWPcmtqfn92bGxoVlVaT1lgYmFqentlV1FLUFRYW1lYXWVeW1ZMV19aW1JTYVJRW2NqZ3t4amJ5g4SGhImAdGJiWFJdZmZlYHl9iW6AaW5fU1dZsGlkdGNnbVykUlpeYVpbWF1ganRoX11aXl5qb2lhYW1oaWllcHiHhXuBhG5eXmhoYlxfXWxwb3dsb3dobGxvaXRzmGVjXm15hZKaeIKLem1iW15eZWJueXV1j4OCgI53iJCGhImGfnJ9fHl7dXt2c2xpbGtya3B1bG6AcHl8enTfw2dyab3OxMvbd3V3gHp0ho2AfXl0bmpqbIuAZnhqXlxjb215bKJXYWRmbnZufXl0cm97fXx/e3Vycn6GiHp6eX1/f5GOioqFend5cXh8eH13gXloc3FtboqDg4BugoaClZSTf4aMg4OLi4mDgYeGi4yJeXKCjn5+fIiASUZTRkg7OENNVVxiV0pSRkhTVFVWWVFNSEN0Z0xdeFxNT0hLUElYbG1lP05MUFVDRU9gVUhTYnFwX01ITUI8RldGRDw7QU9QS1JLTEhQUktWWFRdcoViVVGOjU1NSn1rfYxNUYtTXkmEhEFKUUVRbWlVVVFRWVBMR0JDREtKRkyATExGSEE+RVx1cnCUkXRaWG5VQUtHQkZ7bz0/SE1RT0hIUU5JT1ZVTVBPW2lphFlISEhGTlFJS1JYVlNcYmJhZl9UVFJVVEtKaHNpaV5STElKUVBFSFtmXFtYWlxiaFlTS0dKTEdTWV5lZ2ViUlVbWVZSU05rTjk9UGNWUkxVXV2AYmJmWEpSU1ZMU2eFkIJQRVJYWlhgYmJncWhcY3WKhXRwcXFpWVlNUlVcWl9dWYJ/WVdgYU1bcHVkZn2WcE1WWF1ZVl5dTktcZm9jZFWfVVxiYrGdmKlbW11iU1NVXWdsb2RScohOhW5re4R1bGNhV1pfbnl5b2ZeXXyTgmhhWFOAXlZOYWVrc3x0jZGOlH5qYFlfbYR0YGJtcmtfWlxgXm55ZqBff3tlZWFsb3V0iXFraKxaWltgWVRTZmllbG1raWlqXWBqa3N4eWpcX2RWS1VYX2paUk5pVnNQU15jUUlVR0dNYGZzc3FgX2FeVVVobGVqaHJybXlwcGJaWVpbYF6AV1xjYlhSZHBiW2FxbmiEhlZaWVteXFhbUkpMV1liX2RaSExLUFJMSUdTX2R2dHWJjZWJdGlrbHZvaW9wX1pgWm1sdnR8cnFsdHR0eopyeGhra3VpbGx2aWhyb3Fvdmdpd2ZYWVllbFpeZWtnW5puUF1ZY1hmbmx9i4qLjo6Bc3CAa21vY2NnZFhXUk9bVlpSVVhfVVZSVGNjVlFNTlVcWk1TWVFFS0xVYGNmd31xYGZkUllrkaCIbGdjcnNrZWhhUlRZT1pfZGFpdn1qXVdNU1pZV1RTVF1aVE9OWlxVWFBTYFJPV2BiZHd0aF5vfXt+d3ZsY1lYSkRKUk9RUWZkaFuAXF1OSlFLk1tZZllgZ1abU1laW1NaW1xdZW5kYF9UWFtnY2BZXWliYF5eZmx+gHV2emtdW2RiW1hdVl5iY2hdXmRXWVlZUllQcENAO0pVX2huT1ZbTUE4NTs6Qz9IVFRRa2JiXm5ZZGtcWl9fV1BdX19gWFxYVk9QVlNXTk9TS0+AUllaVVCUfkFJQneDeoGPT0tOVFBMXWBXU1BQSkZISm5lS15PRUNHUE5YTWE3PT4/RExET0pIQkFLS0tNS0JAPUhOTUNDQkpLTF5dWFdVTUpLRElNSE5MUUw8RUM/PVRKTUo5SEtJWVdWQElOSklQT05KSVJPVFVTRD9MW0tOTVXZegV5eXp6eoR5CHp6eXp6enl5rXqCef96tXoBeYR6hHmPegF7vHoBeY56AXmlegF7/3qXeoJ7/3qXegF5h3oBefZ6BXl5enp6hXmcegF51HoCAgQAgLaxu6eeoaSlqLO5urjDt7y8ubW3vsTP0ti9s7G3v8PArKeqpp2fr6uiqainnJmmraCemKGyuMLP6bu/wLa4vKijkp2Qio+Un5mZpqynrKqjsaOSj5+gjJKRh4WUopqY9/v8ipyVnaWMjomOl6mzp6esl6Odn5uenJeRioKD3NfQgLmPjYypvYuG+P3e+YqmkpuU8u3y3/WQn5OPhJmjoZOUpJ6Vm6iinZ62sKmppYeRiYyXmpuXj4ufwt7OqJ6jp66qpJyss7bDwsvNx5mipKKtsKiYi5WajoiSlZqZruT+u4qPmf2KjY+isa2no6Ogpa62o6myqqOTq9n7yry1vrTQgMHt9/PxzsOvppmpoqCgjJ+6v9XIrZyLnp+Xn6SdlvfF+Jein6ONlJuRnaGnrqu+sq+hnp6im5aPh42Np6yhqKq7t5WOloKho4T1gIiEgYWSiYeMjP3zgYuIjpKZmZKRmKGQl5eKpa6xvba3vq+pnKe1sZeSo5qeoJKXobCzmpiRgIyUoamrwLSskPz9k5Seif6BiYykmoD/lJmIhf2Ci5KHhpOOkKCgrbK5mouYmY3v9/f7goL37/6IgI2eqKOalJOPkIuHh4uQmaCnoJ2WkZGA/YGJjpi818Wjk5ufjIKGh5SQj46QkJ64uKWutraYn6K3z+vIubSkm6mlsb7k4dW/gLTW8q6D/4WIj4uRhZKlqqWXkpqVkZiekY+blJWdq66Uk4aSmpSDiYWEioeTl4qYqp+nmo6Tmp6lnpemlYqOpbfIzL7ItbO+tKSjpKakrLG0nKGirKeioZ6tqaSbm5yjp6KdoqKpqqejmZect7vT8NKOnZqXqrGxwsi4p52akJaHgJGTjJOdmIuC8+/z+ICEhYyPkJKThYaOlZOQj5STmZeRn7GlpJiUmaSinJuUkJiemqGgsKmtu7eywL25r6yckpaNioeWoZeaoqGjm4qIioeJ/5GpwLOtsK6wqqahoYfyhpKA/fqMjZCQi4nv1IGbob7GqpeXj4WDg5Sdn6uejKSugKqrrKqekZmWh5aagoj8iY6PgIyRm5yIg5ucrp2Zv8e0k6CUjIOSo6+0x8rCvsG5o6m6tq+kqa2zraugppuswLy4pZ6do5aQnp2kpKSurL2pm5KZioyKm7nDv8qzn6Gvw7qwvL7Gw8qsnZmbm6aqpJiUj4+SkJCWjZSVnqCSkpKJgIiNhv/3gPzj2eD74/L3/Ijy7/fi+aOWk5SZjImRlZKTpqqOj4ySm4r8hIiMoq+0qZukpqakn52qu769s6ynra6opqimn5ytoKu5rbC1pp6WmJCdmJqdkJKSnZCYsqa6vrbT3t66r663urbCrqOqrbLLsqOpv/TowM/Ir6up69uxgHd4f3Foa2trbXN4dHeFd3d5enZ4gYWPkZt8a2x2gYqGcm5vcGpue35zeH54b214fXJ0cHiCh5OkuI6QnIyNkYZ+dXlybXV3gXt3g4iChYeAjol5dn6CcXR2bm14hH12xsfJcoZ+gYdubGhrcYKLgXuBbnd1cWpoYV9YVlNYn6ShgIVgWWB3kWNhrLaYpWJ9bXJqrq+srLJqcGBXT2R0d3JxenVrbnh2c26Be3ZzblRbV1xrb2xoW11ykqyddWpycnNwcWlzc3uDhouLlWlma2VtenljV2p0ZVxncG5qfZqvhGFdYalfY2ZteW1oZGRiY3iEa21vZmJbbJa8kIF9h4abgIijpaWul4B3e25sbGhhVV92b4CCaFlWZV1YXGZqXpV0lVlfXWNcY2JYZG9tcHeLeXNpYltUTk9JQUZJY2xlcHSGf11aZlZtbVCSTVRVV1dcU1FTVZWNS1ZRUk5SVVNTV1pRWFZPYmRka3F4gG5jV2JubVxUYVZWWUtQWGRiT1BLgEdNVVlZaWVjToODUk5TQXM7RE1lW0SMVl5OS45KVFdPT1dWWGhmbnR4XU5XWE15goSJR0mKg5BQSVNkbGljXFhVU05LS09SWGRqY1xcWVlLjkpSVl9+loNoVmFiUUlNSlRTUFBWVF1zd2VrcXBbX2Jxh5qCcGlgV2JjcHWWjXV8gHGNpnFKiUxOV1JZUFlrbG1dV2FaVVpeVFJcU1RZYmdTVUlPW1hKTk1LUEtRW1BXZFpeVEhLUFRZUkpSRj4+UmJydWdqW1pmXlFPUFRRWF5iUFRWXFpXWVhmY2BcV1xhXlpUVFRZW1tXTE5TZWeBnnpBTUlKW2dtgI98bGRgVldQgFxeW2NsYVFEeXyLkk1RVVdaXF1eTERJT1BIRURAR0ZFTVhQWFVWVWBZVFJUTVFVVF1fbmduf3t0iYmBcWxfVllRVFBTXF1jZ2ZnWUpMR0dNoGZzh3Zpcm9vanFnYlKWVVdPoYtQVFRdWFCKhE9hZ4OLdGRZVE5KWWhiaG9sYHx9gHR0c2ZaX25rYWFlUVmpXF5bT1tiaWRST19daGZfdYR4WFhUUUdSWmJtfX1zdHh5am5+eXJiZW50b25mcmx1end1a2Rkb2ZjaWhwbnF6eXxwZFheWFxaaIGPi5V/bm53g399hIaQjpZ6cXF3dnp+e3V5bWtrbXBzbHd2gHlra25ogGt0cNTCZcu5ucjgy9PU1nPL2drS6ZGHe3p6bGhucW1ygYNvb21veWe2X2NldX+Bd211eHV1cm52h4OIgX1/en+CfoSGhoGKgYyZjY6RgXxxcWx3dXZ1am1rcGlziYKVlJGwtbabko2XmZakjn+KhouiiHqAkL6vj5mQfXRyppp4gEpLUUU+P0FDQ0lNTE9aT05OUE5QV1xlZ3FSRkRNV19cS0hKS0hKWFZPVFZRRkRLUUtIQ0tVWWRzg11fZlhXW09IP0Q8Oz9DTUhGUVhWWVpXYlxST1hbS1BPSEdSXlhWiYuJTFtTVltHRkRJUWFoX1xfTldUUUtLREQ/PDg8en9+gGtHQkVacktIfoZue0leUVhTgX58d39NVEZAOElTVU5PVkxCRU5OSkxbVVJSTjU/PkFNUFBNREVXcIJ5WlJVVlZTVE5XWVthX2lqZ0RCREJMVVBDOT9HPjtDQkdJVWp+YENESHxGR1FfbWFbW1VUVGBqWFlXTkxCUnaQc2JbX19zgGt/fXt7dGhiY1haWFNMQVBhYG10WFBOXVpVWmFcWZJ2jFNgX2JXXl1WYmdrcXKGbWhjYl9cWFhTSUtKYWhganF9eFtXXk9na06PT1dXVVdeVlRXWJ6WT1ZSVVRYXFhZXmJYXV5Xamlob3F5gnFqXmNubltYZlxeYFRbZXJxXl5YgFJaY2hpeHVwW5mXXVpeTIZETVZrZE6iY2xgXK9bYGJaWmNhYnFweX+CalxoaV6ZoqKlVValnqlbVV9vd3NuZ2RhX1tYV1haXmdsZmJdWVlJjUhQVFt0iXhgUFtbTkZKSFJPTlBVVF5ydmdscW9cYWFvhZWBb2tgVmFga3OPiXR3ZGyEm2tEfERGTkxUSVNiZmdZVVxYU1peUlFbVVVaZHFVV01SW1dITUpKUExTWk9ZZ15kW09SWFxjXVZfUklKX3CChnZ9bGx3b2FhZGhmbnV6aW5weHdwcW58d3BqZGZsamhjZmeEaoBcW19zdoyegk1ZVFNjbXSGkYJza2pgY1hhYl5pcmdXS4uMmKFVWV1fYGJjY1NPVVtYUExLRk9PTllkYGFbWltlXVhYWFFTVlRdXGdiaHp0bHl5d2plWFJXUlZQVmJdYmhrbmFPTVFLTptib31za21qb2xvY2VTlVJXTZuITlBSVoBSToZ1SVljdHhkVlJKQT1DUk9UX1pMXWdiYl9ZVFJbXFVRVElVnFNZWklSWGJjT0pbVV1eYHh8cFZYTElBTFRcZHF0bGlsbmFjcW9rXV1iZ2VlXWNcY2dkY1dPT1tQTVNOTktPVlZeT0Y6QDg6N0JaaGhxW0tNUl5aWGFkbW13XIBUUVRSVFhUTlJLTVFUVVZOVlpgX1RRU0tNVVGXiUiPfHmAm4eQkpNRipOWjKBtYVlWVkpITU9NUF9hT1BOU1tKfEBCRFRcX1FHTVFNSkZDS1ZUWVJNS01NTUpPUE5MVk9ZZVtbXVBKQUE9R0RHRTs9PEE4P1FIWFZVbGtrV09KURhQTFdFPUFDRVhHO0JQeW9TXlREQEF1ZUnfeoN5pnqEeYV6hXnNegF5unqDeah6AXmKeoJ5s3qCeYR6AXmGegF5hHoBeZJ6hHkFenp5eXmZegF5tHoBeeh6AXuZeoR5xHoBeY16Bnl6enp5eYZ6gnmhegF59XoDeXl6iXkBeoV5k3oBedd6AgIEAIDIsqagrq6mnpaLkZano56dprahmaGppquvrrClqq632s+8v6+WkaS0vaqotri0tqSopKqspqOxtrmytqmTm7vFwq2qkYWThY6ToZCTk6CYo6K72dKqxLKSjZCQnpOLlIyLjp6imqSbio6JhZGaop/H27zWycbA6qevq6eqoay9toCfopmglYr4iIqF+YKKjvrphYSLi5ikmZyfjISMhoqYnKOblKChjKbE4ubQtZWLk5GElKGonpiZmaC2sbTBv6ymnaK0q6mswMW6qbC+uqeTj5WtoZmFjZqbnqOfppmUo5yMmoyPlYmDj6ail5GOjo2C/KK4w8+2pbK5uL3CrMC0rYCos52fr6iym/n4iqifkZiZrbPJz8L3yq6MxdrLwqSbnqPFq6CZiZyIkI+Tnp+nnpOFhZSgo67flZOdnpagoZmdsaSQko+QmJ2WhIifqaajo5aEgpOXh/SRo5qPipirxKaToY+NkaCpq6OjrrjNu7urmZOWqaWsppqelo2OkJCZlICcpaCTiZWUiomHkJCPj4WRnJympKujoZmfmZKajYGEgYaE94KNlpqqoJWfmJOVkYGJgoX4h4yEhYOOmZOSipOPj4WLiouLl6KdipObmI6JgP+B+YWCg4+FhIuRoJiNjYuOh4b25/yEhImNi5GRo5GRiZGOjY2Ej4P9haawxbiYlVOQkYmPmKCemJKWhpOjnJubmJutpqSiqbKrnqGqnpiWqrymlpONipOJ9ICJjpeZk6CboJ+Vjo6Qj5apoJWtqLWyuMXHxb/R0sjGyce6r666srPBuYSogJCMmaadpqmrqJilrqqqqq20nKC+t62T+sGigouHi4qWloaMiZiHif3ujqu1r6Wk9d2A8vjt/IOLjJGLj4yHk5aepqCkmqyjrKuRjouDjImUlZ+jmZCjo5qUl56hn6iuq5qdoaGdjoiJkJ2glIqGhYP6gqOmo6uK8O/3jJKcmJ2kgKOim5aai5CQivfhi7+plYWGhZCB6oSUoqimmJaakZ+opqezr6Sho5qgqaCprK+koJGUkJmLi4eVkYeFprCvkJWfpKusubSjmZSShYz/+ISJqq+vq7KurKWuoI+PiqCypJ2ZnaaqqKSmsL62raKimpaOhZagqKmpvLy7pKORlJmvgM2Rubm3u8DAubG0sKyemLK5sJuRmpmkn46Wl6Gbn7nKtZz26YOQkY+GkJyahf/x94L6/4CBg/r57PTijI/6+/iKi/bsgvz4hpOPm4ySlqGpq5qEhouGhI6RmJ6gnJaWoJaWl6Cso5iNi5OXkpGhmaSqp6OjmpSRkJOJjJ6gmZqaKaOqpaSysKufop2kvbWvrcCvrqOZmaeprbeil5OTm5qVmKConY+em7iugJqEd3OBfnxwaVlbYW9va2xzg3FrcXh4fH97dXFxdH+al4eHeGFfc3uGc3F/g3h9b3NvdHhzcH6GjIOMgWtzkZiXiX5tYnJmcXeId3p7gXd/f5KqqoqdjHJucHJ8eXB4cGxrfn55h31ucW5ncHN3dJioipeJi4iqd3p+endueo6IgHV7cnBrYqhfX1yuXWFmvbZmbXFqcnpnamlgX2hkZ3F0e3NwgXlfbpmxoZR4YFllZ1xncGxpbWxucn96gImFdHFtcnlvb3F/hn95dXp3aV9iandramFja291eHByZ1xsbF5gWGNoYVNYYmJaVVBQUEydX3N3gnRod3Z7eoFxgH13gHR4X15rcHNek5hRZmRYW19qa25+eKKEdF6DfXR8bGJocZBpWlRTalhdVltqbHJrY1FIV2dkaodMUFZVUFteW2d+bGBfWVlhamZNTl5lZGVpW0tHUFZNflBgWlNQVl9yXVBgTkxPWmZqZV9kboN+gW9dWFZkYmhgW1xSSkpISlFLEFBXV01GTVFHRkBFRkZKRk+EV4BdV1tXXltaYFJITEtTTo5LU1xcamNbZ2NeYFxQVExNiUhPSEpIT1xYWVNZVlRNUVFQU1xoZFVcZWJZU0uRSY9RT1BaU1NZXmhjW1RWWFBQkYWXUE5UVlVZXGVcWFJZV1VUTlhNkFBvepCHZGJaWlhbYmZlX1tcT1liX1xcXVhlY4BgXWBoY1lbY1tTVl5rW1RQSktRSnxBRUtPTk1XUlhYUUdBQkRKW1JIWllkYGNtbmtocmtqZ2xrY19eZmNndG9eYGFgTUtQV0xVV1pbTlphYWFeYGRMUGBbVFuYbVU9SUpLTVZWTVBNWE5Sn45ZdX1wam2Me0WPj4iUTFJSVEhHRIBBSUhMUEtNR09HSktBRkVBREBKSlRZVEtcXlhVWGFoZ290c2VoZ2hiVVBPWmZiU1JTTkqKRldoZGVLiJabVVtcV2NmX2RiWVdQWltRkYtXem9aUFRPUkyUTlljbW5kX1xYYWx2dnRxaW5ybm1wa3BqaWlvZGpeXF9ZVWNgT1FwgIB9Ylxma2pre3pqXlxaTFCTj0pSbGhqaG9lY19tZ1lWUmNyZl9eYWRrbGxydXdybmdpY2RjWmRsc3h4jY+Fa2hZWmJxlmp/gn6IjI2GfIB+d25rgYZ9a2Jvb3x2a3Z3gX+CmKySeLyzZnJ0bGNndXBiw7rEaM7Ta25y1NfP2sR4dWzJysdwdtbMcdDGZ3Jrc2RpbnqBhXFdXmNdXGhob3N3c25ydWpoaXB4b2VeXGFmZmRsaHd9eXp8cm5ra21mbH6BeXp9g4iGhpOWioGEgIqclpSPm4+Og3x8iIyOloJ6dHaBe3d2e4V6a3dxjoGAalZLSFRUUEY/MzY5RkdFQ0lXSENKT09TV1ZOSUpNWHFtYGBSPj1RWFpOTFZZT1VHSUlLUEpIU1teW1tRPEFZXl1QSDguPDI8Q1FER0hPSlJTZXh4XW5gSUlJSlNPSE1ISEZWWVVeWkxMSUNMUldWc4JkcGlpaIVWWlxYWFBZammAVVpVVlBKgklIRYFGTFCPiU5SVVJYYVFST0ZES0pLUE9UTUpVUz1LboF9cFlHQktJQEtVU1FQUlRYZGFlbmpZWFJXX1ZUUFplX1VPUk9GPzxDUkdCOj5IS0pQUlFDO0xMQ0dETE1IRk9ZVlFOR0lGP4NXY2dzZFRkYmVmbFtjYV6AXF9KR05XYlB8gUhVUkhKT1pZYG1ohnRlSVFCRnFjX2ZvfmVfW1RnV1tVWmNncGhgUUdTY2VsjlVYXVtVW11aYnNmXFtWWF9kXkpNXmZmaGlaTEpVW1GJVmRgV1VeZ3xlVmVUU1ZgbW5oZGRpeHl9bVxYV2NlaGJfYlpSUVBSWVaAXGJjWVNYXFNRTVNWU1dQWmNgZGVpY2ZhZ2RjaF5VWVheW6ZXX2dpd3JqcW1pamZaYFlZpFddVlhVXWlmaGJqZmRaXl1aXGNualtiaWZdVk2USpVOS0xUTU1SWGJcVlJSVE5QkoedUlJZXV1iYWxiX1hdXV5cUltOk1BsdYJ5XFuAVVRNUFlbWVNPUERPWFVSU1VWX11bWV5kYFdZYVlTVV5uXldTTU1UToFFS1FXWFZfW2JiWlFLTE9TZ1tRZWJua3F8f3x5hH17eHx+dW5ueXh6h4JwcnRyXVlhaGBnaGlnWWVtbW1rbXBZXXJrYmGmeF1HUVFSVlxeVFdVXlJWpJWAYHyEfHV3o49QoKSbpVVaW1xVV1VTWldaXlhaVmFZXF1MTUtGSUZOT1leVUtZXFZRUlldYGVnZFZZXF1YTUlLWGReVFBTTk2SSmBraG9Tj5qcVFdfXGJjYWZgWFxUXFtRjoRTeWtYTFBMUkmLSVRfXF5XVVNPVFtgXl9gWlxbVVqAX1xgW11eYVZdUFFSUVFfWUxLZXl0W1thZWNib3JrXlVTTE6GgUVKXmFiXGJbWFVgWkxJRldpXFNPUFZeXl5kYGVgWlRUTk5MRE9WWVhXbG5nUU9ARUdWc1heYV5na2plWltdVU1MYmdgUEZRUV1TRkxNVlZcdop3Yop7SlNWUkqAUF5XSpGMlE+coU9RU5ucj52IWViRmJJVWpqUU5mSTVVOVUdLT1dfX007PD88OkVESk1OSURGSUA/P0ZNRDoyMDY5OThBP0lRT0pLREE9PT03OUdMRUNFS1BLSVRTRz5CPUNSTUxIUUZEPjY1PUBESj04NDY/Pjs9Q0pDOURAWlD/eod6Cnl6enp5enp6eXngegF5l3qCeY56hHu6egF5y3oBeZB6AXmcegN5enmQeoN5knoBea96AXnEegF7kHqCeYZ6A3l5eoR5unoBeYZ6g3mPeoJ5iXoBebd6gnmzegF7oHqCeYl6CXl5eXp5eXp6eoV5DHp6eXl5enp5eXp5eeJ6AgIEAICSkpyTjZiVkpCTpKOrmpielpbV3+m5vbGtqJuUpbnP6szOsrW8xbmtsKyorbGmpp6VmJ2stZ2jsa+yvaaZlJaUoKuqlZuOkIaFipCTkpeqqJ+ZtsC0mZ2purKjlIqHgI2OlJSDipuyrqylo66jp7irpJq03N/DucO2yLuuqtDZrICjlY+MmqOEi46D8v+O/vqHho+PiJyomYeGio2ToKmytKOjl5+Q9/mUraqUjpmTkYqMlp6fmpmjuKCYp7G3vcC+zMCys7Wrr6a2zd7FsKuzvbq8tqq2srmfoqeny7+yt6uioo2FhZGQjo+cpJ2hkJH5/qWjpqq9zrSjo7aunpOZmYCft9C1rJDw9fLe1YCNk5Cmws7V5r2b7J2h4sS5rJihnKu7uKGnnonc3PmOm4WPvK/0lIWHkI+FmKufk4SIjZSUlpqRiY2Un5qZlZ6dl6O1vriala2km5iUl5ujpp+glpOQmpWal5OQiJSYqJ2dlZ6llo+Lj56isLOrpqqkm56xpoCkqaahkY+QjYiToJ2arLW2taedlZmRioaRo6GhnaKKhoyLjY6Uhfvx+IKRifyLmY+F5/GFg4mKg4SAiYuMlpKQjYqWiv2AjYuKi42OlY+MlJr8gJaShOn2i5OXhoCChYuUkZiSlo2Imp+knbimpZ2Tjp+YiJakn5KOjujHnY6OmYChu5+ZoaGem5ujnZqlt56hpqGZlpOXnaymoaepqaGTkoiMk4+bkI+RkIyJkIuQlZqcm5GRkJGUnJSelpKir8HNxbe9u8S9uKqjqKyrqqWjormxrOGCx7Ouqa+gmKCemqGopbGuqJqsrKSousbbu5uhsaatno6EiYudnpyUkanBtICtpqiYlYP+/fv+hoqVlpiblJqtp5ugmo+QkJypraSimpaIkJqdlpGUo5mNj5CVi4SAh46gpKqpopmXn66jl5qjpKOPgv/6g4f79ISG//+Kj46XlqGon66nkZqflIaJm5OCgIqon5iWlYyAgoSVrbu1rq63tLLHyLS+yriomY+Pl4CRnpehoZ+RiIqFm4b/jYiTnJ2hoqidlaGXmq6888uXn56MjYf8kY2bnKCtraOhpJ2imJCUpq2inaqutbKsr6aorL22sa2pmqKanJ6Vlp6jqqShoK6ip6utuK2opJ+fmpKHmaCpmrGso52im6GYnKObmZ6ir7u1hoaPlImOnIuLiYCD+4OHh4iIkpCYioWqh4q1lpaNio6OiJSOiYyKhoeH//D5jIqUo7CqmJqioqOooKifmZ6imomXnZ2slJSVk5Kdn5eUnaCqpa+utsKXh/GksKCisrWtnpqcqK+xqJ+bpbSyqq2spqGZkpeOl6CSj6CXkZmdoZaMmZSioJybqrK3kIBxcHpybHRyb21udXZ7bGttZGSstLSBgnZxbmRZaH6Vr5OVe3uHjIJ1dnVze311d29obmx5hm50gH+IlIB0cHd3gI2Kd3lsaWRlZmxxc3SHhX12jZaTeH+LlJGFd2xoZm9zdnpqbnyMhH94d3p2eYR+eXCKq62WiI+GjYyAeJ6mg4B3bGVjcXdeZGZes8BxycBnZWxpY3KAc2dnZ2lueYCJjYB9bG5msKxqfXZcX2pmZl5ZXGhwa2dxgWpodHiAhouNk4V1dXdkanKDhI19eHmAjYV+hXuAe4V0dHl4j4N/g3xraV9aYGZgV1ZeaWVhWFCSllxfX2F4hXRiY25oXFpeY4BpeYlqY1V6enpzbD5MT1FjfX19hHtjnG9qlHpxZ1tqZHSJhWdjXE6AhaBbZVRkhnyiZ1FMVVdNVFtWVkxKTFNVWF9bWVheYFtbXWVfVlxpbWpWUl9gVVJSWFxdXVtfU1BJUlFVWFNTTFJfYWJkW1tdWFdVV2NocHJoYmBfU1llZIBeZGJXTUhFSEVRWlxVYWdpZ11XUVVPS0dOW11eWmFMSU9OTlFYTIuJkU5fVphXZFpPhZFSVVlaVVhTWF1bX1xaV1RYVJBKU1JOTVJVW1VSWl6OS2BZTX+DUlxfTkpOTlNcWWFhY1xRYWZsZnVsa2ddWGRiVV9tZVtYWYmJaVdaYoBtg2pha2ZkY2JjZWJpdmJeaGJZWlhYYGxpYmhoaGJVUkpOVVNXTFBQTUtJTUpMUVRWVUxKSktNUk1RS0hSWGRsZ2BkYmxmY1pWWFlaW1dWV2lkYYlTc2FbWl9TUFZTUFVbVV9aV0lWWFJVZXaJcFVbaWJmXlFIUFFgZWZeWXGIeoB4bWpjX0+NjIyJSkxST05OR01XU0VKSEFDP0NITUxRT0s6QkpNSkhMW1NQT1JWTktIUFRia29uZWBiaXNqYWdpZmdbT5CJRkJ3fENHg5dbYFVcXGFoZWdqYWNeVlNWYl1XUk9vbWVhXlNMU1Ndanp6dnd1eHiCkoJ/f3FrX1ldYYBbZF1hXlxfWltYXVSnXVVha2lzdG1ua3NnZW6BupleYmNWUkmOWlNjYlljZF9dXVdhX1ZabHJpYmdudXVtbGdwe4Fwb3FuYG1rcXVlY2hwdHpybnppbm5yenJwam5raWJecnmAcYWDenVxcHNtcnp0cXqBjp2NZWZxb2hscWhta4Bqx2hqaGJkbWpwZ2WDa26We351dXx7d4J8dHJvbnBtycTIc2tyfIF7a2txc3V6dHtvaG90a1tob3KAaGVmZ2hxdW1tc3iAe4V/iJBtXaN4h3t9ipGId3R2goyOiHt2f5KTkZKTjoR/enx+fIF3doJ6dnuBg3dyfHl7fX15iYyPcIBDQ01HQ0tKRkVHUE5TREZFPj5la3RSW1FOSj82Q1ZrfWxuVlZhZ19UVVVSWl1RUktCRERQWEJHUlFYY09FQEVETVVTRkc7OjU1NjxBQ0VUVU9KX2hjTk9aY2FXTkZERFBSU1NJTFZmY2BXU1RPUl5ZVlBjf4FsZGtia2VcVnh/YoBaUk9MVl5IUFNLipVam5BOTFNTTFlkWEtNUFJQVVtjY1dVSk1GdHFJXVpISlNMSkVBRk9TUFBYZlVSXmFnb3JyfGxeW1dMVFZhYmhbWFhcZmJZXFNaW2JRUVlXaFtaYFdOUkxGSVNTTU5TYltdU0mEjFhUVFtteGlZW2JcUEdPUoBWY3FYTkJpbG5kZDdCR0hYbXByeGhLhFRMfW9pZFhkYXF/eF9jYVB+gpdXYk9agnmXYlJMU1VPVmBdW1BNTVJTVlpSUVNZXFhXV11cVl1pcW5bV2RjWlpZXmNnZmJjWVZQV1JWVlJVTlRcYmFjW11gWVdWWWRnb25oYmFgWV9paIBkaWVeU1BPUlFdamhjbXR2dWtkXmFbV1RbZ2lpZGlVUlpYW2JkW6upsVxqYrNjbmZfpK9hYmVmYWRfZGtoa2hkY19kX6RTXVxbWlxeZF5ZYWOYTl9bTYKHUlpfUE5RUFZeW2JeYltTY2hwanpvb2lgWmZjVmBtZV1WV4SBY1JTXIBld19XYFtYVVVXV1dcaFZVXl5UVFNVXmtmYWdnaGJWU01SW1heU1VWUlFOUk5QVFdaWFBQUVJTVlNWUU1YXmtzbWRqaXVwbmReYWVlaGRiZXZyb5ZXhHJtanBjXGNdWmBmYGtoY1RhYltea3uLdl1hbmdsZFdRVlZlamtkX3SMf4B+dnRtaluop6enVlpgYWFjXGRtZFVZVk9SUVZdX1xdWFNDSU9UUExOW1FKTE9RR0JAR0tXXF9eV1NVXGZjX2JiZGVZT5SOR0eAgktQkJ9cX1VbX2RnY2hpXWBhW1NVYFtSTU1rZl1cWVFKUFFXanFqaWpsb293fG9va2VgVU5MXIBVWVJWVVRST1BLUkmSVVFbX2BhZGNgXmhaWGNznYNYXVhMT0eAUEpZVU9bVlJRUk9VUkpOW2NaVl5fY15ZX1lgaGxdW1tWT1ZVWFtTUFRYWlpWVGJSVVVZX1dSTU9PTkhDU1ddUF9dWFRUU1VRVVpVUVheZ3RnQkRPUExSXVJVU4BQlE5SUU1OVlNXUE5tUlZ7YGNZWl5eWWRgWVhaV1VUl5KSWFBSXGFaTEpQVE9UTVJHQUhLQzdBR0hSQD4/Pj5HSkRCSExUUFpVWmRBMVBIVEZIVFlOQT9ASU5PSD44QE9QTExMSEA6NTk4ODw0Mz45Nz1DRDw4Q0JHSElLV1leQf96i3oFeXl6eXmWeoJ5yHqCeZV6hXmJegV7e3p7e456g3mGegF59XoHeXl5enp6eYR6gnmRegF5jHoBeYR6gnmiegF52XoBe7F6hHm6egp5eXp6eXl6enl5vnoBeZd6AXncegF5nXqDeat6AXmyegICBACAqautqrDaso+Vrq6enaWZoaaakaSKgoD6uqehmpejsLOpu6quvcC3u7i8vMnUqoaOpqmurZ2no7Gss7isq7CtlpSQmov+mJGOmaGYk66/udHAq6GRifb5ipOkppuTi5mbuayvt8rEvaqnl6WzrcnJqJqZo6CZhJy6tL23qqeqoJWAjaOUipKSioH5+vyCioH+iJ+amYqA8YSPhpacm5m7yeTNvZqUnZSYnZSQg4aao6OXmpiXl5+joJaYp6qzsaqnnqzB1dHGt7Hczca2oaeglqOtqJqqp5+Qk6avrqex2ejr/+naxM/Ko5ytp6CutK2plaGfm6zEyrSkoJ+lt8Gdj5mAkpydnZuQ/oaOmJ6wuc3fzrC0seqG0djw2rKBkpeVmpulsaujmZedkoD8hPL7g4iE/fb0hJCdkoaLk4iNiJWUioCIipKTjYuAhaWbmJSboKKosauxrKStr7CluaOVo6ipq5iRmpuoqqqcl56eoJCIjJSanKqppZySjISWoJmRkouAk4uPioebk4+Gg4qdnZmXsaOck4OMjpSZnaihkoqGhYaGhIqI/vSXj/7+8IuDj4iA9/yF9v77gfuHhYOKgPX1/e2BjI2Sivb5gP7vgYOI8+iCj5exq4+HjZqNhY6GnJqZl5yRkoieg4eXmZWmmo6G7vWE+/uNko6MjpKC8vr8ioqAj4yWkoqMj/+IkZSalpeXkomImJ+TjJaml5ual5eYnJiWnKSnoJ+lpqCPj4eWiIiWmJGTkJyhmqKcobOxuLa3ubOzrK6wvLOtq6azsq2knpehsru8sKidnp6amY+bqam1ura2w7ayuLvCvLWtqZiUrKmaqZ2G94WSm5qSqqGUhpKAjY+MiYSRmaOSi5KZnLe4o6impaqaj5qgpJ6lnKKqucO0m5KcnqSamJaHjJGQk4ny9YeNj6i0q56bqaaal4qWkpmSm6WoppyLhOXr6vaHkJmWjIWMm6mhmKOij4CLipCShomJlZ2fq6uZkIiemZubop6ToZ2WpbC3wNzMv5mGge+A6YKdrK2enpOCg5+V+I2QkomTkp6QjoOOi5KWmIqTiY2FhIn47u6Aho2QmpiUnqjnjcCur6qonJuyp6Wzp6SosrWspZuooZ+npJ+ajoqIkp2gj5ahop6Zlp2hlqGcnpaHi6mkqtXGraO0sqGej5OcmJuTlpiYmJGUkJCXkouHjIeA/4r27IiUgoSOlJKVjYuCjpCPj4T67+rmgv77h/zo/oSJkoeFhYuHhZSTl5icnaGemKymp6WpoKehnKWlmZucppudpKWmqpuamJKSipKXlpKXmp6WlZyhoJimsq2xtbC4v7Kso6jAzNLXv72yqKSgq6aapKCgo5+ko5+prqujpKeAgX6Ee4KkgGRofn9uc3NmcHJpXGxeW1uwgG9sYF9udn51fXVxf4B6f32Bf4eOdVRccXR+fnV8eYqHhox9f4mAbm1rb2fAeXJudnhxaoSSkaGYhH5uZrm4cX2Qjod+c3p3i31/hZaUj4GCdHyGhJicgnNzeHFqVm+Oh4+NgX2AeW2AZXNtY2ttaGPHysdjamG8Znx2bmhds2BrXm10c3OXorCmlXJrcGNeY15eVFlqbWlfZmRjYWtqaGZhbXB6e3NrZGuGn5SBfX6chX90a3ZxZmlycmlxbmpjZ3J2bmx6mq+vr6WYi5aWcGFzbWx0cmtnYWFaWmV2iHRnX11idX5hVF6AX2VnV1hVkEpQY2ptdIOLinFpZ4pXg4ihlG5DTlNWXmRve3pzZ15bUlOsV52fUlhVn6GnVlllXVJSUEJHTllYSkFIUVhYUlVNSVtUVVRcXltgZmBiYlpiZGVfdGRVXmFkZlhRWFhna2xhXmBjZ1hPVVtiZGxoamNdV1BgZFlSUkyAUEZJREBNSkY/PUVOVlNTbWBbVUdOTlRVWWBcTkhIR0VIRUxMioVaUYySglNMVlROnZtUm6enVapaWldbU5mcnI5PWFthWpmfU6eWUFFTjYRQXGN2bldOU1hKSE9KWltcXmBaXVhmUVVlZGVzZ2NbmKNYpqxiZmVdX2VWoauuY2CAZGRnaV5dYKRYW2JjYmFeWVRQWWBYUlNiWFldWFhcX2BcYGVrZGBlZWJUUk5YTEpSUE1QTFNYTlhTU2FfamdkaV1bV1daYltaWVZhXl5WUElSZm5zaV9TVFRRTkVNWFlha2hkb2dma2t5cGhhWU5PZF1WYFVIhEpUW1hTbmhhUlyAVVRRS0ZMU1lQSEpOTmBdUFNTVFpKQ0hHRURPUVZdanRfT01WWGBbWFlKTlRUVlKJiU1WWG14dGlocm1kX1RcXWdhaXFrYFxNQXB5e5JWXFpYTklRWmBjZmpcUU9XV1tnXlFUaGtrcHFfWlhdVlNbZ2ZeYltWX3B2fX6Sd1dOR3iAdkdhbmRXYF9RVWFWo1xeW1ZdYGpdXFpiXGJhWFlnWVdRUVeRg4ZSU1lgY1ZSYWyIXXlucnNtYWZ+b2h2bWtsbndxcmtwaWdwcWZmZWFcX2hrXmd2dnNvZm5sYWpkaWRbXnl2f6ufiXyJin1+c3B0bXNvcXF1cWxwbm94c3NycG6AyWi+sWZwY2Rubm9xaWVfZGdoaGbFurm1ZMvNa8S4021xdGxpZ2xnZXBzeXZybnVvanpxcm9xbHRtZW1uZGlodGhncXd2fG9zbGJkXWBnaGludHZtcnZ9enqAkYyNk46YoZKQh4edqqywn5WNhoB8gH52fHp7fHl4eXyBhIN5enyAU1NYUVl1Uz5BVVlHSktBSEtBOUMxNj94XE9MQ0FPV11UXVNQXl5YW1hcW2JsTzA1R0xTUEhQTlpXWl1TVFlVRENARjxmSEM+REg/OU9dXWpkUk5CQmtzTFZnZF1WTlNTaV5jZHBsZ1lXTVdhXm5wV0xLUE1GNEphYGpoXl1hW1GATVxUTlZXU06XmptMUkqQUGVfVk5Eg0lRQ0xPUFFrcnp9blJKTkVGTUlHPUNSWFRLT1BRUFdVVFNPWFtmZ15YU1xuf3duZmN6ZmJbUllTTVBSV09WVFBJS1peU1JfeYaGd2lzcoKEYlZjZGNoa2VeWllWU11ye2tjWlpbaHRTTVKAUlhWTElFgkdOWGNwb3eAfmdlXoNPdneNhGdDUFFTWV9tdnNoXVpeVU2dUpOTTVFPmZedVFdgWE1OTkVMUFdWSUJHTFNSTVFKR1hTVlJaXl9jZmJmZVxiYmZhd2NYYWJkZFdSV1diZWZbXGFlZlhRVlxjY2loZ2FcVU5dYFlQUUxdUUxQTEhYUU1EREtXX1xdeWxmX1FYWV9hY21nWlRSU1JUVFxcrKRpYquvoWJcaGVeubdjv7++YcVoZ2ZsX7O0s6daY2RqYqmuWrOdVFZXkYdPWWBycF1OVF1QTlVThGCAZF5gW2hWWmttanhtZl+gpFqsrGNnZV9hZVaepKNdWl9dYF9VV1iQTlNZWlhYVlBMSVRbUk5QYFZZWlZZWVxcWl9la2ZkamhkVFROWExKUlJOUEtTV05XUlNhX2pnZWdfXVpdX2hiY19dZ2djXFlUW292e3BmW11dWllPWWNja3mAcmx2bGhtbXlybGRdU1VoZF1nW06NT1liX1pzbGZaZV5fXllUW2JpYFliZWeBeWZpZGZuXVVcWlhVXFpgZWx0ZlVRVlhbVFJVR0pOTEtGdHVDSEhbaWVaWmRlYFxRWltkYWpwa2JdUEeAhIWZWVtZW1ROUlpiYWFnYlZPVFJTXlSATlBcYGJoa1lVVFtVVFFaXFBYVEtQXmVrcYFrTkE7bGs+U19WSlFPQUVTSYNMUlFKTk5US0tFT0tOTUtETURMSENJgnx3R0hPUFFMSFJedU9rYF9cWlBUal5aYlRPUlhjX11VWVNTWVtUVE9NSk9ZWktRWltZWE9UVUxVT1FLQkSAX1xkhXtnW2ZkWVxWVVxVWlVWVlhVTlFMTFRPUE9PUJRQjYJPVUtLVlpYWlNRSlNVVVVRm5CNiVSjnVSXip5RVFVOTkpPSkdSUlRTT0tPSUVSTExISENIRD5IRj9BQUhBQUlNTFBGRkE5OTQ4PUA+QURHQUFESUZCSFNMTlFMVFkfTUtDRlZiZWhaUUpDPz5BQDtBP0FFQ0VHSVFUVUpLUJR6g3utegF5kHqCebF6B3l5eXp6enmGegF58HoBeY16AXuUegp5enl5enp6eXl58XoHeXl6enl5eYV6CHl5enl5eXp5hXqEeYV6Cnl5enl5enp6eXmfegV5eXp5eYd6g3mJegF57XoBebl6gnmYeoR5snqCeYt6AXmWeoN5inoBe896BHl6eXmQeoR5B3p5eXp5eXnhegICBACAkZ6XlZuWnJ2KjouPlqqgl4+PmKCfo6uwq6aTmqGdl5eYq6avr6qmsa2ol5WhoamrsbexrJ+dlpqUmJmXorTCwqaUmKChprGftsvCm5SThPzwgIunsZqKjaK8xMvJxamXgoHgnqeTlpb/5ZHC2MHMxdzBs5iUjpaqqY6WiYialIuAko6Q/u/+gIaCg42Kk46bo5eek/mAi4uIgvuGopqZm52tqaelqpqZsKKXmaWjoqGZlZShmqGdoq2uraybm52roJ6vvsOqrcPR0sa5wJ+Zn5WG/oufraSTm6KpsKuvu7TFs6yO4aKpmpWhmqGjpKCVmrCpr7Sy2batppONjJyYkZqAn435mrGbnZWlppeVnqWwkoTyi7/J0O68lYqJhYeIgoKRuruRjvyEjoT4gOf28P3y/oWI8viFnJSF5ISx2MWVjpu4npqkkZKdiIyUqZeZoqaUhYWWo8OsnLC31r6uopicopaor5qbl6GgoZqYhoaLmpKDi5CI/4eIlIqKjZGOjZiAnZKOlICiu7Onj5OSl6aaoKesnJWXl5aWl42KiIeOhIaMkIiWkZCJjIiEjIrt4+3p5fLu4eyC7eD77PGNi5KRgu7tg/iC9d7k8oCBgPP5+ISAho+Riv/y/viEi4KErpCDj4uVk4uMiYSJjomJhIeC9fOA/oSJjYuKgICEipeLiYuAgZCJipaPjoGCj4+Qk5CWmJeelaOdoZ6bo5yPjJahmZSUk560sba4r5aUj4SCiI+gqamwtLaipbW2tsPCvKKoq5+cmqGvraagnpeQhrnPys/N3ujttJeSl5CLiYyRlauutK+vtburqri4n5WXmqmpqp6gnpmZm5eamI+UpaiwtcuAzsOWk5aVjJqWl5WalpebmpaSmaGxsK+mlZurvba0rq+qqK650YLanfX4nZmYjJasqrOwnZiNi42Wo6GgpJCIgoiJk7XQu5qbkJmG593/i4eMl5afpZaVkZuViIKCh4b+jZmKhYWUkZOb6eiMopiZmpWXmo+ckIWGjo2Vo5+PhIuAlpCdnJSptJONiY2M9u6DiZCIkZmiopmGjJKOipiarKyRlJqLh4WF94CKmI6ap5eKlaiio6ivt62hl5qir6uts66ioaijlJOdkY+KhImFi5GTkqClm5aijZCKjZyeoaS8qqSkq5iZl5SNiouSkZCO+Pb/hYiIg4GFiImOioaB74CAhYuRlJeOhPH1gYmRjYH2gJGbhoeMhY6imYX6gfj684SJiIaNjJWG+YGJi5GUk5uYkZafppeYm5agrKWfnpOWkZGlwbm2pqizqpyej5ymq7O1sqSjs72uvsKwrq6uvsrh0Ly9tburqaWZlpeyr5uSi56a/fmOjZSYoZ6UlZuknZqAcHp1cXRvdnZjZmVoa392a2Nna3Bxcnt+dGphaGxpaGVldHF4dHlxdXNyYGBvb210fIF/fXJuamljaGxpdoWRkXhqb3N2e4Z6kaideHNxZcS9Z3GKloRva3yRmZ2XlIJwX2GjeIJwcnLDpWuVppWal6aVhnFtaXODg2hoYWBua2SAa21yzcXRZ2hlZW5tdW55eHF3a6tYYmFeWq5jeG9vcXV/eXVvamBjeGtmZWpjaW9iYl1mXmJmanFxbW5jYl9nYmVtdXtuc3p9hYWBh2phXllWo1Zhcm5kaGxpbXFxg3+BeXpemmtvXltdYGtgX15bYGNhZmlplXFoX1BJSVVWUFqAY1OUVl9bYVloZ2NbW2BlV0d+SW96hZVzVk1OSUVLS1BbhIliYqZTV0qYV5ijnZyWoVZboqdYZF5Vg01sh3xcV2J/XlxpVlZeTlVYYFVSWFxQQkBMWG9fUWJviHhhYFtfY1dnal5gXmFmaGViVlZZZF1RVFpUmVNSVlBTVFxaWGCAYVxSUUNbcGxfSEhLTmBYYGZpW1dXUldVV1BLS0dKQ0NJS0dQTlJTVlJRWVePh4yHgI2Lgo1SkYahlJpcXGNjWJiTU6FWmYiPm1JPUJSWmFRRVVtfVZyTn4pQWktKWVFMWVhhYllYVlJXXltUT1VQlJNQnFNYXVZUS05RW2RaWFmAUF1XWGJYVkxNWFlZXFtfXlxjWmNhYl9dZF5UVFtfXl1fXWJ1dHZ5bVNRTkJDR0pYYV5laGtYWWVnZ3JxaldZXlVSTVRiY1tVUkxHQGyBfnyBkZSabFFQVE9JRklLT2Jja2Znam1dX25rVlFUWl5kY1hYVVNSUk1VWlJaaG1wboSAgXBQTU9NSE5JSUdLTU1VVFNPUVJTXFxcT1FdamNjYmRoY2h/lWmsZIaMZWRjWV9wcnhya2ZcW1xgamtqbGBYU1pVXXSOf2JhVmBPg3+TTkxRW1teXFldVlhWU05QV12vWWReXVNeXlxgj41UV1RgYGJhW01XSkRJTEhQXl5WS0yAWVhiXlViaFxYWVZQmZ5UVlxaY2pwcWJfY2VhXmNwiIRhYGdZV1JToVRaamVlZltUXGdkY2p8hnttaGxweXN3fXNnZW1zaGhrY2BeWF5aYmtoZHBya2l5a25pZ3Bvb3CEdXFwd2pubXFua2ltbGxquMDDZWRlZmRkY2NnZ2Vju2iAa252eHxwZbW0XmRpaWS4YXN7Z2RkYGJpW2G6YLy+smZqamZzcnpuzmttbXBwcXpxbnF4f25sbWlvdm9qaV1hWltwh4CBcW9+d2lqX2Zye4eKhHh2iZaIl52KhoeIk6bCrJyel52OjouCfICcl4Z6dYSE0cp0cXl9h4B0dH6De3eASVJOTFBLUVFAQ0BERldORDs8QUdFR09VS0Q9QkZDQkBBTktRUFBLUE9MPzxJSEhNUldVVUpIREdBRUdETlxkXko9QERGSlFGWmthRUA/N2tpPEhgZlhHR1hsdHZvbFlMOTpeVGBOUE15X0Rnemlva3VkWUlIRk9eXkdLQ0RSUUyAU1RZnJadT1BNTFVWW1RdX1ZgV4VCRkNAPXtFVk9QUlRdWVZTVExPYFRQUFdNUVVPUU5VTlJVWF9eXl1RUlNaUlBXY2xcXGJmbW1ma1FLR0FAf0NLWlZNWVxXXF1aZmRkQ0JEg1xhVVJUW2BXW1dUWV9bYmRkiW5mXlRIRlJNTVaAXEuCTlhQWFlnZF5dXVpeUEJ2Rmp2eYRlT0hJSEdIRUtXgoZdXZVNVUuPT4iZkpGNllJYmKBWYllNe0hoiHxaVF14XlljUFFVS1JSV09NUldMQT9KVGtbTV5mfG1fXVhbYlVnbVtcW2BiYl5bTlBUYVlNUVhTllFQWVFTVVtYVl2AZFlSU0decnBlUVFTVmVdYmlsYF5eWV5dXldTVlJWUFFXW1ZfYGBgZGJjamu1rbWvqLOvo6tirKC1qK9mZGxsX6ahXLBcqJebplZTU5ubmVFOUVdZUZSKloxQWE5NWlRQXltiZF5gXlxhY2BdW15WoqBWqFtgYlxaU1RXX2ZcWFiATllTU15UU0dHUVFRVVJXVlRcUlxaXFhWXlpQUFZcWVhbW2J2dXZ4blhVUUVFSkpXXFtiZmlXVmNkZW1sZ1VYWlFOSlFdXlhTUUtGPml9eXt/kZKdbVNTWFNPTU9TV2dpb2pna29eX21rWVRXWmFlZ11dWVdWV1NZXVdfbXR6e5KAjX1dW15gW2VhYl5fX11kZWJgZGJma2djVllncGptbG1qZGVyfT94XX57V1VWTVNkZGhiWlZLS01VYWJgZFVPUFdUWHCJfGJlW2BSi4CRUVJUXVpcW1VXVFtYUUlJTFCYUF5STElTVlRYhoNNV1BTV1RRUUZMPTQ6Pz1DVFJIPUWATkdQT0ZRWUpGRUVCeHlBSU9KT1RYX1FJUFFKSFBYaWZRU1ZIS0pJh0dMXE9SWEtES1VSVVVhZmNYUVddZWBeX1hQUl1hVU9TTUxLR05JTlZVVWFjWlZiU1VRUVlZWVttXlpXW1FVVFlST09QT09Oh5GUTU5OTElJSEhKSUZCeEaASk9VWF1US4WISE5VWU6RTmFrVVJTT1NXS02PSYuMgktRT0xVVFpPi0lKSU1QS1NLR0hNUkNDQ0BFSUVAQDU5NTNDWVNTRURPSDw9NDtDSlNVUUpIVl9SXmBQTExMVmN6bFxdV1tPUE5FP0NaV0hAPU1MbWhBQUlNV1NKTFFXUVDPeoJ5kXoBeYV6gnmZeoN5jXoBeYV6AXm3egF5jnqDe596AXmOegF5k3oGeXp6enl6hnkEenp5eYR6AXm7egF5tnqJeQF6hXmFegV5eXp5eoR5Bnp6enl5eYZ6hHmWegR5eXp5/3qzegV7enp5eaF6g3mRegF5iXqCeaF6gnmZegF5x3qDeYx6AXmIeoJ5hXoBeYl6B3t6eXp5eXmIegF5ynqCeYx6AgIEAICmm5+jopaMj42Oh4aLjI+GpKmsoaummISDmMOtraSoq6GrpKmvrKm9r6ycopimnp+tqp+Vi/2I/4ahlJqwxcTEycbIuLS7wcu1qp6LmpWep7bzlPrZx8S4u6KjnJWbhvWAgo2dobe+uqWZjoGImKykk4mKi6G6sK+kl4rz8+XziICEgpCNjYP37YiU/YKFgfGAiZmelpGgkaCrpqCpnJ2dpK61yLSmoaaYmJCNlZGUk5+hqq2sobS4qbC0trO6rqCltK6wt7O9yM3Ir6qxxdTBnYmMqqmjmpKOopmNq7KenK2UstzNpKOeqamkqZiZkIaOtqmxubuwtbGxmKi8rbKqmYCTmKKcmImC+fmBg+Dn7PSEgoSKh4aD++/9jZGDgIaMhqjKpo2G4e7oipGkqqCYg9/2joaG+uju9oeSlKOTlqaS/YiDl6GqqqWeqZqPk6W72t3U2ICE0auol6Cwq6OVoqmkmZuqpJqSkJWWk5yNjo2EhYOJg4mRhIOFgfz/gYSIi4CLkPyCh4qGlIaI9/yMkpSTkZSXqJiSiIGHk5eShOXR2YONkIySkIeHioOLhpGB9f6JgvDg3tbh3fSCjIaA+4OOm5GD6MXU5fOC69jn9vuDjIT1h4qG+PiJj5OTkpSTlKKopq+1n4X3/f6AhICE7er5jIqE+IaXlIyEg/iBi4OFq4DV56uYlouJjP2IiYySmYSHiZCTlI6Wnp+bioqRi4OGiZSgmZqTkZiboqWZkJCDiZ2xqqWnoJ6cjYOTkIePi5CRi4eTm6WgmaGrra+usba8z7+2sKabmKOViY2OkJektrXAw7uztKaroautq7e6urOutKqgl4SBgIGEjoqFhZOYloCOlpaTmI2E9Pn6+vz3lqKhkIyUm6GinJ2aqaWhmouJk5SPlpGWm5ekpJOYmJ2foqWWkImLhYKGh4mZmo+SiI6JiZCThIaKjp6VhomOmpmYoZaSl5STlZuakoeShICFi4mRjpOaoqaenYTj7YKQjJGNlqino5eVhYuQlpeNk4CBkICRkIuRlJ60k5CRkpuC5/uBiYOJlo+AgIPq/f30gob47vSEkJmemI6NhP+MmY6VjZKM9v2IkKOvsLWmp6qenqSmpZiTmqeonYqKjpeOkI2DhouNlImGjaGemo+NlJaXpK+oqqWgpK+2pZ6QjoSCj46VjYuZkJKLhYmB5f6DioCHjYCPlIONkpaK/ff5/oaEiP+JlYOE/vmFg4OFgICKiIX78eeDgpGdkYOJhIP3hYuTjviBk5majYubl5mgl42hpZGcqrayrcSzr6uamaGqp6Cbl6WuuMW8u8HHw8XCrLGqt6Wpt6mgqqaak5CgoqOeipGZpaCQhYmDh5abk5yShIiXq4B/eHt/e3Bqb21qY2RnaGlgfIGHd35zaFJRaoxwdGltbmhyZm51cG19b25iZV9saWdxbmlkXqlds19yZmuEkpKSnpqajIqRnKiWjIFxfnh5g5HAcMWsm5mUkoSAeHF2Y7NdYW13d4iPjXZvZVxkdIZ+a2NiY3OMgoN6bWCss6axaYBmZW1sbmbFxW55ymdkYLJeY3B3bGV3aXN3eXJ9cnNvbXZ4jHtua29kXlNUXl1cWWNobG90c3N3Z3F4eHB2bmdhaGZzeWxvdYCDcmx1gYh6ZVhUbmxwYV5ZYFpUanloYGZbcY2JbWNeZGdrY1RTSkZMYV9obWdla2FjUl1lXmphVYBVWmRaTU5IgoRFTXx6d4JHSUlLRUBDhoSQUVRGREZETXCJcVtSipaPWVtvfHRrWYyWW1VQmZWak01ZXGpXVVlMi1JOXmZsbGlhaFxVWmRzj5CGhE5YgWNfUltmY15UYmxpYml1bWJaXF9hY2RbZGBVWFpfV1tiVlVTT5OSTUxOUYBVV5VLTlhQVktMhYhNUlJRTk1SXVdUT0lPWFtXTIFpb0tTV1VZU0xKTkdNTFBHjJVWU5iMj4SJiKBUWlRNmVJaamFVim99kptWnIKVnqFTXlWaV1ZUl5RTVVlaWFxZWmdtbnh6Yk+QlptOUUxPjImYVVVQllJfWVNLTY9OWFFSc4CUoHFiX1VTUpNRUVRXYE9RU1hZW1VZY2RgUlFXUktOUVtmYl1XUllaXF5UTkxDSlpoYl1gX1pYTUVOUEdNSkxOSEZQU1pWTlNeYWFhZWxyhnp0bmJYVlxOR0tMSk5YaG54fHNscGFjXmlrZW1ubWZgZ19WU0RDQkRES0ZAQEtRUYBMU05LSkI7a29xdHt+WGJeTURNUVpdUlFOWFlcWkxLWWBbXltcXl9rbmFlZWRnaW9hY2BgU1JUU1dlaGJkW2FbW1NXTFFXX2lfVVpbXV1eZ15YV1ZcYGNWVFJVT09bZFdcY21oanBraFOTjUdMU11dY21raFxURk9OUlVMTklJUoBQUFRZWlphWFpeXFlJlaFRVlJeaGJbV1mjta6rWFyztLRbYGppa2JeWbRjbmZqWFZSkJtTWGp2gIp5dn93c29ydG1pbXFzbmZpam9namddY2ZpbmViY3JqaGZqcXFyfYJ7enRxdHuAdHFmZWFndXNybmxraXFqaGhcqcVoamJiZoBpcGBtc3ZuyMLGxmllZ7xkcV9bsqtiYmBgXV5kYmCzpahhZHGAdmRqZGO7YWtzbsNleIF7cWt3c3R7cmd4dmJqfYiFf5GAfnhpZ3Bzd3Bsa3iAi5SUk5qgoKGdiImEk4KHi4R7hoB0dm59f395aW90fHZwZmpiZ3V+dHZyZml3hoBSTVBVUUhDRkVDPT4/QUE3UFVYTFJKQC8tP11LTkRGSUNJQkhOTElZTUtCRj9NSkVSUEY+Nl81YDRGOz9SYGBhamhqXFtjbHZiW1JETkhLU1+HT496cXFpaFxdV1JXRnU9QEZQUF1jY05GPjY8Sl1VRT4+QE5iWltVSkJ1fXOBT4BNTFJQU0uMh09YjUlJSIpKTlVXTktbT1lcW1VfVVNRUllecGBVU1lQTEFCSkpNTFRXW15gXmBmWV9iamFmXFNOV1dialpcYmtuWldham5jUkdAU1pYTFFOU05HVl5VT1xTa4N/YltYW2FjWlFRR0NKX1xhZ2VhbGJmV1tjXmFeVIBRU1tRSERBg4ZDSnd7cHVARkdFQ0NFg3qDSk9HRklGSm+LclxUipCFVVxqcWhkVICNWVJQlImTkktWVmRRUVhKgUtGV2FkYl1VWlRPUldke350eEhTeltXSVBcWVNKV2FgW19sZVpTUlhYWVxVWlZOT1BYUlZdUVJUTpWUTU9RVIBWV5VLTlRNVUxOi5NUWFlZV1ZaZVtYUkxRWVtXTYZzfFJbY19lYVpaXVlgXmRasLxsZb2ws6isqcBjbGNcs11lcWZdmHuHl55UmICOlZpNVE6PTk9Njo1QVFVWWFdXWGVra3R3Y1KWnKBRVFBUkIyaV1hVoldhXVZPUJZOVk5Pa4CMkmhYVk9NTYVISEpPVEdIS1BTVU9VX2BcUVFXUkpMUVplYmJbWF5fYGFWUU5DSFZjXFZYVVFPRTtFRT9FREZHQT5HSVBKQ0dSVVdYXmlzgndybmRbWF9TSUxNS09ZaG12enRsbmBjW2VoYmltbGReZF5XVEVDQ0dJUExIS1VbW4BbaGFeXVRMiImJjpKUY21pVk5UWGFlXVxXYF9hW0pIU1dTWVdZWlhiYlZbW1paXF9RUk5NRUZJSk1bXlVYVFlSUE1RSk5WXmRdVFhUWmReZl5XVVFTWF9YVk5QSENPVlBYV1pZXWJeXEyDfEFISU5OUFleWkxCNUFCSEZBRDw6SoBIQUFKSklSRkdKSUk4bng/RUJKUEtCQ0RzhYB6QEeEgIFHUVZVWFRTTJNRXlFSRkZAbXdARlZdZGtgXmdeXWBeWFBKUFhfWlFST1RPVFROT1NTWlNRVWVcWVFTWlxaYWdiYl5bYWZqXVtSUEtNWlVVUFBSUVdRUFBIepBNUEZJS4BNUURPVFlQjomNkE9NUZNQW0pJkIxPT05QS0tST0yIe3lGRlJdU0RKRUR8QUdNSXc+TlNQRkJNSkhNRDpHRDU7R1JRS1lNSkU4Nz5CQz87OUZMUllbWl9hYWJeTlBKWEpPU0tGUEtBQjpGS0lFNz1BS0U/Njs2O0VOSUxFOjxJWbV6A3l6eZp6AXuMegF5m3qEeYd6CXl5enp5enp6efh6BHl5enqEeYd6g3mMeoN5h3oFeXl6enqEeYh6AXmSeoJ7pXqCeYZ6AXmHeoJ5kXqDeY56BHl5enqHeYR6AXmFeoV5AXqFeQl6enp5enp6eXmPeoN5hHoHeXl5enp6eYZ6AXmNegF5/nqGedx6gnmieoJ5iXqEeQV6enl5eYh6AXmHeoJ5xnqCeYx6hHkEenp6eYR6gnmJeoN5iXoBeYR6AXnQegICBACAko2YnZCKhf3/iZiinIOUsL/cxLussqKVhIaUlJuSl6qknpWXpJualYqBipedmpOSlZqmraihs66zuLG1trG+4cmwoI+gl6Cirby6vJudpKawp6WenKCRh5KIkJqRkZihnq6wp6WmqJWYl52anpf4jZiJl5yjqL6TiI6NhoGEgPCA+Ofn/IyOhoGMk6WooqqWkJKbkIeQmqChqLnF2/HmyLesvcydnqWup5WWi4qJjI2O/4iWnaKmqqyupaSemp+nq7K8w8fNz7ispbGuyLqVjp2ssqepvZeatZicvbCzubOus8e+p5SCl66dpKyroq29tr+/rLnd2dbLtbW7ybGvqp+An6Wmq5+G+4SHiITs84qRiYaNj5mPmpH8+oGLjo6Mk5qP9/iD+o+Xi5Gbl5SZk5qekqWpnJycl42IiYiKlY+SmJOZlY2flYaI+oyPlJmTj4qZpMqDwa6npaytoam87Mygqa+qn6aekpCKk4+GjpWZmY2N7/Lr/YLt5fj05vKDjYaA9PuB8/j7//rm2uX09YaAgOP6gPyMlIP1+/HczMzP24GEiI6ampqB/YWVjIaChPLx1s3mgIKQhI6A+O2C69DG5erXhpGOkoqA7/mCjZWPh/T48Ojh/YOPl5+cnqq5yLe9zN3MqJCJi4Xxguvn9/KDi4H28oWNh/mDlJOHgpaZiJWAmqCfnpqLgoeEgYKFj5GYlJybnJKRjZKMkZGRl5CVnaGUm5+hmpqVlpagq5yJi5WVlZCrk5mYn5yUmZ6cqqSqsLStqKWcpKuiraKWn5eTl6KitM+rnJijpKelmo+UoaWgqMi+qZWSpamzudHg0LrBqaerpq+xmIyPkZydm7HKgvqAmoWOnJSF9YOKmpqWl5mgoaaVi4uVoaSYp6OLiICFg4qMhoqOj42DhILv8vP2g4eFhICNhYKPg4WWnYyOnJKOk5L9g4SGh4yIgoOWm5WHj5yuuaiUi5ihjo2UkZOKhYaQ9fqIlJasmp6LgY2ej4iEl6qppZKFnoiJl5aklJaIjYuAh5ajk5WepJiSjZKSl4X+ho+KioeA8Pb45ez38dfn4uiChpCLg4eNhPiIj5KPhODZ8JKNioyYnqaknamekJWKjZiXnKCeopeZmpuH/oiG/4iIhoaGjv2F/4L/hpKSmJ+jno+OlYWFg4eak4iJjpGS//OGgPSC/vyC/PLy8ImVkpKAioqJgIaNlJKPj4uDh46NiPuCgevs/f/7go2Ki4WKiq+ik4WMiYyQlY79gP37gJGEiZOelIiA+PiIjJ2hnZKWlZWZp7e9raenopCLoLPBxNPAz+3bzc3Vy9TUzdy6sLGkoLqnkp2WlqCZoaGUjqCYjoelnYyFhY+C+YCA8vCAi5CAamhwdGtiXaytXmt1b1hkfIqfjYN0dmpeTk9cW2BaX29saGJkb2ZoY1lWWmFoZWFhaWptd3ZxfXyCiIGHioOQuqKAd2p2bXl5hpOTjnV3f4aNhHt6bnNlXGVfZ3FkY2hvdoOBfn+GhXNzbm1pa2aeX2ldam93f5ZvZGhqZ2JkX7KAu7C2xm5vaGNpbnyAgINybm1waV9ncHJ2fouUoK+jjHlxfodlZ2pvbFxdUlNTVFVWllFcX2Rpb3JyaWlpa2ZjZXKAgHp7fXFnZnBvgHVbWmt3dXBwfGJdZmZkdnmCenBwbHZ/dFxLWWVlbmlmZmt5cHF0aniKloN1bG5ueWlnZFyAXmBmalRJjkxJSUeHjEpVS0xSUlZKU1SOj0lRVFRRT1dWnJ5On1ZlWV5qX1hhY2VpXmprYF5hXVxWVUtOWVZYVk1UV1hnXE9Oj1RaWFlXVVVeaYBSdmppaG9tXmR2noReaHtwbHRxZ2dgYmJdZGlpbmBfnZ+ZrFaXk6OWg45OUk6Ah4hHgoaPkJKGe4WXmFBJSniJR4lPVEd8f39ybXJ1g1JVW15qamlTpFNjXlpVVpGWf3R8SExXT1JKl45Qknt1l5qGWmRmamBbp69dZm9sY6iuqKGXsVxocXhwcX2RmoiImKiXc1xUU1KRSHx/ioZKUkqMgkpSS4hGWVRNT2BmVV+AYGpqZ2VZT1FSTUtRWltgYWZhY1pYUVVUVFVXXVVVYGNaYWJmX1tWVlZeaFhKTVVWV1NWVVdXWlpTVlhVX1xjZmRiW1lXXl9bXldPVk5LTVZTZX1iU1JcXmBeWE1QX2NgZ4Z8aVhUYWFnaX2Nf29zZWJoYmhoU0ZKT1lZXGt4Up2AUkFIVVNIhUxWXl5WVVRZXmBRRkRNVVhUZ2NTTElOTVFPTU9TUU1VWFWUjZWfWGBdWVVcVVJaUFVqcF5lcWhdWluRTlNbW1taWFddYF9UUmh2a2RZV1laU1VRVFtaVlJYmatYX2B2ZWRbUlNdU1JTZnNuaVtPV01NVVZhVVNTV06ASlZnXWRhWlxgX2NaWlazWWBZW2Nar6mnnqmwq5mspLNoamxgW1peWqpibXNoX56TmmFdXF5sbnFxcYN6aWhmZmtpcHl5fHJrcXJpzGxpv2ZnYWFgabRjtVqxWmBmbXl+fW1maVxaV1trZl5fY2dqvrVlZL9iurVdt7Gyr2hwbXCAaWRnWlxfYmRiaGhfZWloZsFhXaWjr7OsW2RlZmNpa46BdWdrZWdncGrCZMPHZ3RnanN9c2lgubhrcHt/e3B1d3l4hJKVgnt9eWRfdIiWmJuRo8G2o6Wqpauqo7KNiIqBe49/bnp0eHd6gn96b3p7cWmCfnJkZmtlv2NkurhiamyAQT5HS0A7NmJjN0FKRDE5TlpvYFdJS0I3Kyw4OD46PkxLRj9BS0RFQDUxNTxBPzs6PkBJTElGUU9TW1RYWldignJWTEFMR0xQXGZiXktNUVZfWVZYU1NIP0Y/R05CQURKT1xXVlZbXk5QTU5KS0ViPUU6RUhPV21LQ0lNSENEP3SAfHV4hk1PSUZPVWRoZWZTTlFYUEhPVVZaYWtwfIqAbl5WZG1PU1ZbV0lLREVFR0dGeUJMUFZYXV9gV1dTU09PU2FubmlqbVpSUVtaa15JSlZaXFpaaVlTXFtUYGBqaWRlZm10ZVBET1xeYV9iYGRsaG1vYnCDjYN4bXFudWhhYVuAXF5dYE9Cf0dMSkV8h0lNRklQTlJMVVaSiERLUFFRU1hQkJRNnllkWFliW1VZWF1jWGRpX1lfWFROT0dIUU1MTEhNT01aUUZHekZJSUtNTExRWG5GY1xdX2NhU1ZliXFOVmVgXWViWFdSVlVPWVxdYlhYjY6KmE6JhpePfohNU06Ah4tJi5GbnJqNgIWVlVBOUIWZUJlWW0+NkI6Aen6Fj1RWW15paGdXrFtpZWNgYqywmY+bWVpnX2NYsqdbo4+KqauaY2xsbWNcp61aYWhgVpOVjIeBlk5YXWVgZG56hHl8h5OIa1lTVVKSTYiHj41LUUqQjFFUTpBLW1lRUV9jUlyAXmRkYFxSSkxLR0VJUlRaWF1aW1RUT1JRVlZXXFZWX2FXXV9iW1lVVVRbZlVFRk9PUElTS0xMUE1HSk1LUUxVWFdVUU9LU1VPVVBIUEhHTFRSZX1eTkxXWFxaVU1RX2BcYn5zYlBMWlpgYnSCeGlvXVpfXWFeTkRJT1pZXW2AV6uAXUtSXVlNj09WYWFZWVhcYGNVS0lRWFxWZmFNSkZKSUxNSUpMSUVJTUqEfn+CSVBPS0hPS0lSSE5fYlRaal1WU1WJTFBXVVdYU1BYYWJTVWhyamJRUVldVVJPTVBQUExVh4lJU1RpWV1VSElUTUZJU1laWUs8RTxBSUhRSEZBQ0CAPEBMR01KRUZHRkhFRz+BQElFRkhAeHmBcXmBe2h2d4JLTVJNSEZLTJFTWVpVTXhpdUtHRklSU1dWUl9ZTU9NUFRUWFpYXFJPWF1TmE9NkVFTUVBNVY5Ok0uTS1FUWGNmZlZSVUtMSk1dV0xLUFJTkolNSopKi4ZHi4SEgU5WVFWAUExNQkZJTU1NUlBHTlFRUJFJR3x+h4uGSE9QUk5UU3NmWlFRS01PUEh9PXl5Pkg9Q0dRSkI6bmo/RE5QS0JEQ0NETVlbS0VFQTEtPUxXWmBXZXpvZ2RnYmdkZHFTT1BJRlRLPkdDREZFSktGP0lJQj1TT0U6O0E9bzo6ZGQ3PkGHeoJ55XoBeZB6hXmsegF51XoBeYR6gnmKeoJ5iHoEeXl6eaN6AXmKegF7nnqEeQF6hnkGenp6eXl6inkKenp6eXl6eXp6eoh5iHoBeYZ6hXmGegN5eXqGeYZ6gnmFeoZ5k3oCeXqEeQl6enp5eXp6enn/eoh6AXuHegF5onqEeZR6AXmeeoJ5rHoBeYZ6i3mIegF5hXqDeZp6BHl6enmGegV5enl6eZV6CXl5enp5enl5eoR5lHoDeXp6hXmRegR5enl5iXqCecB6CHl6enl5enp6AgIEAIDG1LitooejkJCSkI6NlJ2anKXMvoeJlKKYi4aEhY+Up6u4w7jAwra2tZqHi5igppOHlbW9tbKmm5GZm6CXud/X09zHvKmNiZyfnaujp5yg4v3Euq2oo6CftsXKpZeK8oCFhI+Lmamjn5qUobXAt7CvopOhmvbc74KMo5eRlouHioCD+vb/g4GPlZaQk5ePjZOIjpaNi//9h5mxwrrDxdjGu6ekpaGcn4uGhYOCi5qmmZWNpre1wsi+ta+cj5yclpWXnq60s7q8wbunsbm8vrKsu7G4uqepqaWXm6eop7KroJuckpCw07q4uLSxt66nsujVwJeMnrm+y8WwrMH6xbCnnICTkouNk5KIm6aZk4uKjoL/8omE/ISEgfuHhIuJlYqJhI6KiIWCgIeLjpWSlo2RnZien56ejpylnZiP/4CJ+fiFkbLdwqyRnLKdjI+QkJqYqa+ttNbPysu/uK+elpmdko2VlaiTlJadl+eMkYCHgYSEioOF8+T/9d3g5NHm+PPxgID48Obv7ejw+e/i+9/h7eqBgoSBiIn6/P6DgoaEkYD2+/738e79+NjL2ozb8Zvs6OLPzNf779bNzszFyMHR0tCd3oHRtpezr432iJKXk46HifT+8PT87oORl66jh4WMiYyaqaCdj4Pw0czI5Pv77tvj1u6LlIySi4qNjJmWi42XnoCjorGtn6OWqKmUi4qRhYSRlpqXi4aOkoyUjpKUnZiboKCUsMq2p6myuLeqsqeqnqOcko6PmamxqbK0paWpop+gnqfNwKuflJabmJ6mn5ien6Slq6Khop+loqelnaKnvLbJ0MTG2/HQvbuzu6zViITMzMGxp5mRlKG13t/Vzce0qoCRk5CNioD8hZGfk5CNiomfqayglJWYjpGTjYH2//n8//32/fHy4+TugYKG/fuUr7evpZaJ/v+Nl5KA9PqEhoeH9ujXlYf98/uMlJOOgvaOkpSloI6ZoJaNnpaXjv6GhIqMjZKMlpSNjpKbqJmcsaixx7Kcn6i4pZmTmKGcl5SSj4CHk5uSiY6gm42FgPmA9OmDk/7f4M3V3NLm4u/q4urh2OiB+fPoh4uN/4L/9YSkgouclJSZl4yblZajpI6H6PCFiImOj4iNhvr5iZKRk4n3hpeRhoDugoKFhIGFg/z/gP7w74GEhIaA8YaOi4b7+ImSjIqHhPbv/+zk5YaIkJiciIDk2oOXhIyYkYOCgYaIhICCiI2K+/qE/fzs+P+Jl5+XpZiZloqCiZarqI6HiIiHlZaJgIH4io6KjYuWlp6jm6KqqaGgn6mns6+zq6mmpKyjnaOxsr7J076rqdDw5bmno6SnpKGkrqanpJygqKWUjoejn5uQi4WBjZCNh5abm6iss4ChroqEemJ7aGdoZmVjanBucXeZjF1eaXBkW1lVVl1jcnN9hYSHhX6AfWFRVmBja1tPYX2EgX5za2Jsb3BmlcezpamWjoJfWmppa3ZscGdrqMCQiXpza25vhYyReHBio1leXWRfaHVvaWhncYGKgn18cGRsaqmXpV5ogXRycmlla4Bjt7W6YF1ham1rbGpjYmVaYWlkZLSxXWx/h4GKiZaHfW9sa2RiYFJMTEtLUVpjX1pSZXV5hIaAeXdrXmJdVVVaXWRtanB6gnplcG96gX56gnZ+fGtwaGJhYmpzc3dqYlhUVl12j3JpeHt0c3Fsb5SDcVZPW3N1fnNfYnKdd2VdVYBQTEtQTlFUYGNYU1FXVEWQh1FNgkRFRItMTlVTWlJSSUtRWlROT1NVWl5eXlNYZWFnZ2VjUVliW1tXmElOho5PWHGLd25aZX1oWl5cXWNfa29wepONjId8eHNmZGJiWlhfX2xeZGhubJpjaFdgXWFhaGJgqZqtm4OGiHmQmJqWT4CYjoiRj4mNlI19k3qDkY9SU1NQVFGSkpRLS09MVUmLjJSQlJaoqZGGlGWz1HimoZqKgI2lnY2DhH53dmx6c29jh0eRg2d/e1+sX2pzcm5kabjCt73DtWBxfZOHZ2huZ2t4iIJ4ZVucgHZth5KSfXRvaX5OUk5NS0tMSlRSTE9ZXoBeWmxrYGNbZWNUTkxTS0lTVllXTUlLUE1VUVRXXVldYGFZcIh5bWtyd3ZobWZnXF5YUU5OVl9rZmxwYWFjYFlZVmJ8cF5WTk9VVFdfWVFZWV5fY11bXV5jYWRlYGVpfHWEhHx7i56DcnJzd2qBUFCChHRpYlpWV1xwi46JhH9zaoBSU1tWVEqQTFZlV1BPSUpWWV5STFFUTE5RTkaAf4SJi4uHkpGShnyDTE5WoqJjen58c2Zapa9iaWhZqqdXUlNUmZSQa1iem6RcXl1XTY5WWVZgYVldYFtTXFpfXKNSTlhhW1tWXl9VWWFhZl5nd3R+inZhYmptZF1WWVxYWlxZU4BMVWNZVFVVVVZXVp1KkpxcYaaOlJKRm5yjoKqwp66pprtqyrWrZGlhr2DDvGmGYWZ1bGlraGBvbHF2dWtnsbhiamtnamNqacvIbG5sc2nGb3xzZF6yYmBhYV5jX6+wV6mkrF9hZWZcp1xiYVqiol9mY2BhXaWnr6KioGJgZGxvX4Cek11rW2BmZFpYU1NXVFFTW2FhsqtdtriqpLtkcn13gHRybmRfZ3uOiG5pamhnc3JrYGG9a3BxdXF3dH2AeXyHjIB9f4qKko6Rh4WBgIh3c3mIipKdp5eFfqPBsI9/eHl6enZ3goOEgX+DiIZ4dWqCfnlwbGRia2xraHR9e4SHkIBpdFdTSzVJPDw8PTw6QERDRUtqXzQ2PUY/NjQxMzk+S0xWX1xgXVRVVDwtMDk8QTctO1FXVVJKQzpCQ0Q9WW9sa2tdYFY8OklMTVlRVUtPhZdtZFROR0dIXGJoUkxBaDxAP0dDTFhRS0lIT1plXlVWTkRLSm5eaz1GXFBOTkZDR4BBdnd/Q0JKUFRNTk5JS1BGTFJMSoV8QFBhamZrbHlrZFdYVFBNTUI/Pj4/REpSTkpFWGRkb3JtZmRWSk5IREdMTFNZVlxgZGNQVVdgZmZgZV1mZF1mXFhWUFRdX2ddWFFKSE1jfGRdampjaGdiY4B2a1BFUmtzfHBfZG+QdGBXVYBPTEdKTElJWGFTTkxNS0J/ekpIekBDQ4tMSk9MVU9QS01NUU9NTlVZWl1YWVBUXFhhYl5bTFVdVlRNhUJHfH9FTGSAbmJPV2pbUFJPS1JPWmFjbYB3c25jYmBYVlNTSkhOTVhMUFNZWH1UWEhQTFBQVVFQjoOUinV2e2yAi4yLTICRi4WPj4ySmZaKooyUoZtXV1VSVlWanqBUVFZUXU+ZnKGdoKOzrpeIlGSluXCenJqMhZKsqZeNjYuGhX2LhYJoiEmQgWh8dl6oXmNoZmJZXKGonqKpoVZkbYBzW1xhXWFtenVvYVeagHhxjZiSh316dIZUWFNVU1JUU11dU1RdYIBjX29sYGNZZmVUTUpPSEdQVFdVSkZJT01TUFNXXlpdYGBWbYZ2Z2VtcW1fZl9gVFRMRkNDSlVeWF5eU1RVU01MS1dsYlVPR0hNTE5TT0lQUFVUWldUVFJWVVhYU1hdbWZ4dm9vfo92aGdlZ1hbNz1wdGhdWFBNTlVnhYmFgHttZIBNTlNOTUWHSVFdU05NSktXXGBUTlFTSUtNSUJ6e36Af311fXqJfXJ6RURJiYxXbXBuZltQkpVVXFtOl5hSTE9Qko2GZVOYkZdVXV1XTpVZWVVcW1ZgYFxRWVRXVZxQTFNVUVNPVFlPVlpXXVddamdocGJSU1ddVVBMTE1NSkpFQoA8P0ZEQUJDQkBBPm43anBCSX1oamVkamx+eIGGeH15e4xNkIeHT1FLjFCelFBrTVFcUlBSUUpYUlNaWEtJeYBHTlJQUUxQTpGNT1JVXlWXUV5bT02PUE1OTktQTZCTSY6DhUxOT1BLik1SVVCQjlBVUlBPS4OChnh3cklITVRXRoBsZERQQ0lPTkNDP0FEPzw+RktKhH5HiYh9fI5MWGBaY1ZUT0dCSFZmX0pGR0VFTEtDOTlpP0JCRkFGREpMRkpTVUxJSVNTWldcUE5LS1BHREhUVVpkb1tMSmd/c1dLRUZGRURFTk1QTUlMUU9FQjlMSkY/OzUzOjw7N0BHSE9TW956AXmVeoN5inqDeZB6gnn5egl5eXp6eXp6enmiegV5enp5eal6AXmKeox5AXqPeYZ6g3mGeot5hHqSeQN6enuGegF5h3qGeZB6jHn6egN7fHuXegF5lHqNeQV6enp5eYd6gnmEeoJ5hHoIeXl5enp5eXmFegF5jnoBea56Bnl6eXl6epB5C3p5eXl6enp5enl5kXqCeYh6gnmFegF5hXoBeYd6Bnl5enl5eYV6AXmEeoJ5hnqGeYZ6gnmRegN5eXqFeZh6AXnMegICBACAuJmMkZyepZ2dm5iVkYeJiJGbm6eH6u39goSOkpCmmpWqsr27zLa3tKqon5CopLfGvK+6wry7trKso6Kyt7Wqra6rqrq5treutbGztLyIo8WIk6qgjImSj4+Tl5WlpayelKKkk4ybnqqxnJuqrqOVlIqHi4SE/YOKjY2Lg/aFkJqAkpSJk6CflpSWmq+xpJOMlZeVloGMm6intKmhpqmfpZ6SmYuJhoaOn6q8yrGXkJSusK2wtb7Av7GqqKOhmpWoq8vv0sXCvbrCzeCxnZ+xqrWxtLOpqaqyy7+lk5qXjZ2uoaejpaa2urO0t7asxsS1tbuow9DBsa6ZlY6NjpWSkoyAjoiHiZKQgJGdoI2MmZOVjZSVkZGMhIj67ISShoiNiYKDi/T3++P07ICSkp+6xo+RpqeSjYmnlZybmomFkIKEhoOImZyUkZyblYiM+e6ElqSaoJe1n6CgqaitienW8pCUhI2cmZ2uoJiopZyPgO3ziIeIj5OQjouNiYD4gIuZqp6AhIr/8IGAg+7j8Y+DgPHxgoPs2tLf9Pn5hIT08enx8vGB7ISR/OLvkpWRjZSH4e389//l2tPMxNbc7vjo1Ma3uM3V3v6OoqKYhPH9iZmdnZKNg/Ph6P+BgoOJ//3/g4OGjZqYkJKA8+rn7/mJk4aRkIiBiYmMjZOPj52lnJKQ7YmAuJWHkq+rnKaUlomGiZH8iYWKkZucoqy8wMbJu6vOx8zb1bu0r6yro6ilm52cl46MiIONlaKdl5mjoaje7cuwpJ2dpJ2lt5iZpq61qJiXlZufr7y1tLS0s7q+wLm2qrW1tMO2sL7As73G18C274OQ5YmolpKNlZCiqqWztq6pop+AqZaUg//u94eWpZuioZ2imJeXn6CjmJifqZ+bn6CgmI2GjYKDhoT26oOLmau6xKyQi4SQ/4WKh4OIi4qCkZCAgN/ohPmGgJCViPSMk4uLiI+Gg4qRjZOgppWcmIaOgPrs/IaLlYuHmZKQipaRl5KSh4+bm5u1yaqjmY2ak4Tv+PuAgJWWiv78hI6DhoSJop+ZlJObj4Xox8HMytff0NzY+/Dh0+Pb6/D58YOG+dbp2ej/gPz2gvfu7/WFmpiNh4OIjIiUioWJjY2KhoWCgoX64ezt7ID7iILz/vCDj/z7/+v3iISC/u7ugYD7gY2eg/aJjIWQ/v36goGPi4uOjIeLj5GAlpOkl56Zmpual5iktrS7u7K7oZugo6immpScjo6Vn5aIgISEiZKlpqSckpKLiY2bqKmzzMKqna6ysKqlrbWsnqGvqKGgsLywpqeuqqujp6quvLKhn6q5ubqzqZeIj5SfnJuVmp+jraSQi5eYnKKxvqKYmpqfpaiqo6egprLJyMyAnXhudH5/gnt7dHFxbWVnY2xweIBfnaqpVlRZYGBrZWFyfoKCk399fHdwa2B2cn2CfHR9gnuAeXZzbWp5e3dtb29taXdyc3Vwd3V4eX1edIhTWXZwXlpkYVxgZWRub3JlYG1vYFhob3h5amhydG9mZV1cYFpYpFVcX2BcXaZZZG2AaGdka3Z0bGhqboKEd2VjbG5tbFpeaHVveXFqbW9qb2lgXldTTk9VZnB+gnJfVVptcnFzdoGGkIR4bGlqXFpmY3uTgYGFfXt/gJByaXB4dX95dnhwanKNkYFvXFpaVFlhZnJsZ2hvdX6DfndyhXhucHRrgoh+amNOSEhJSkxITUmATURHSVBTTV1gU01SYl5ZVVpbWVxSRUmFe0ZaUFNTS0dJR32OlXyIh0ZYWGNxdlVTYmZUU05nVFhUV1BPW05MTUxSYGRgV19iZVhYkZJUYWljZF96Z29wend2YJmKpWVpW2Fsa3GAdG+AhH1uXamyZmRnaW9samZpY1qjVV1odmyAVFqlmFBRT5OKmF9QUZKNUVeVg3qHmqWhVVWmmI2Wk5RQjE9YmIGQXF9gX2dZkZ+tqq2Zlo6IgZCXqLGhjYJ1dIqRmrZjdHVuX7S7aHNwcmloYLOmrLxmZGRtxL/AXmBnbYJ3bWtcraKiqKpeYlZhX1xRVVVWVV1WVGBlYFxYgFGAhV5PV21wZG1cXlNMUFOTVFFVXGdka3SBg4iKgHePiZKXlH11dG9uZmliWVpZVk5LSUdSVmBdV1heX2aZo4dxZV1eZmFdcVhWYGRmYVRUUVdYZ3hzd3J7doB7fnZyZm1udIR8dYOCd3+EknpujUxOh1dlW1ROVFNiZ2l3eW9raGUZb2BaU56Hik9bZlpeV1NYUFJTW1leU1VdYYVWgFJPTVZNTExOkY1VXWlzgIt5YFtWW6VZXFhZW1tWUlpbU1qam1eeW1ZhYVWVWGBZV1RbVlZYVVVaYGZdZV5SVEqWk51RVF1WUmJfWFBcXGFgYFdXYmhsd3xuaGFWXVROkJuTS1ZWVp6cRklPWVxbZ2pvcGtvZ1ynlo+Xm6WpnKyrgL27sq+1r7m4ubRhYbeita29yWG5slurnZ6nXnh4bWJiaXFwfGxtcmpnZWRrbW1rwa61ubtpz2xjtr62ZGzAvLettGNcWrGmqV5bsVxfaVmhW19YYKenolVTW1pXXF1cW2RmZmBtZG5pam1qaGhxgX59iICCbWtvcXR0bGlxZmZuYnJwZl5iX19rfYB/dmhva2NufYqLlqypjoGPj4uIg4ySkYGEjomEhJWekISGiYqJf4KMjJOOgoKLm5uYkYZ3Z2pweHZzbW91eoB7aGBwbnN+iYZ4cnB0d3+EhYKIf4qSqqqzgGVJPUVPUFJNTUlGQ0E7PDpARUlTNFFYWS8wODs5RT47SFRZVmZUU09HQj8zQ0BMUUlGTVFOUEtMSURDUFNRSU9OTEpWUlFTT1VTVVZZQUtdNz5VT0A8RENDR0pJU1RYTEROUEM9SEtUV0hIUFNPR0ZAP0E8O2w4PkNDPz9vPklRgExNSU5VUkpKTFJoal1NSFFTT1BCRU9ZWFxVUVVVUFVUTU1HREFBSFZeaGZbTkhMXl9cXWFrb3RpYFZRUEpIVFBnfWtmaWNiZmZzWVFXXlxkXl5nYFlic3dqW01QTkhQVlVdV1dbYmhpbW1qZHVoX2RpX3N4dWVdSENHRkhNRENDgEpEQkRJSEBRWlBGTFZPTEdNUFBTSD9FgnlHVEtMTUlFR0p6hYt1hYVJW1hgbXNRTl1eTk5HX05TT1JIR05EREZHSlRUTkpTVldJSnx7RlBWT1NQa1xjYGdiYEt3bYlVWkxRXVtcbF9ZZWRgVkiBh1BOUVVbWVZTWFFKjUxTYG5lgE9Xm45NUVGWjp5kVVafmFVbopGOmqqwqllYqZyTnJygVpZSXJ+Gk15iYmFoXZilsq2vmZSKg32Ijp+qnIp/c3OFjJSxYW1val6vsmFpaGphYVmsnJ2qW1pbYLCur1lZYGNvbGhnWaeenqOmW2NYYF5aU1haXFxhXF5nbWdhXYpUgH5eUVpwb2NqWltPSUtPik5JTVRcWmBoc3d6fHRqgXqDiIRwaGZjY1xeW1NTUk9IRUI/SE1TTUpMU1NZiI52Y1hRUVhUUmJKS1VZXFRIRUBFR1NjXl9iaGJraWpjYVRcXmFyaWFsa2BmbHVjWm80KFNFVElFQUdFUlVVYGNbWFRUgFxOTEOCcHhGUl5UV1JQVk1OT1dWW1BSV2BWVFZVVVFJSFFJSUhHgXlKUVxodH9vVVFMUo1LT05PUlVUUVpYT1OPkVOZV1BdX1eXWmNfXFdZUVJZWVdbX2NZWl9TVkqRhY1NT1VPT2FaUEVTVlhTT0NHU1dYZG1iXFRJTkpCb3RygDxDQEJ7ezc7PkZLR1haW1dTWlNIfGtlaG58gXSAepCNg36FfIiPk4lJSpV9kYiHmEyRiEV/dHR9Sl5bT0dGTVRSXVBTWlRQTk1QUE9OjH+Mko5Pm1NNjpqRUFaXlpOKklRNTJGHik1LkkxQW0yOUVZOV5WUjEhHTEpHSklFRk1OgE5JVk1TTU1QTUpLVWJjZnFnbFhVWVpeXlZSWk5NU1dSSkFEQ0NNW1xXU0ZIQz5GUFdYY3FqWlJdXVtWUFpfW0xPWVNNTFpnWE5SVFJUS1BUVl5XTEtVYWJiWlNGOTw/RkRFP0FGSU9IOzVAQEVOVVVIQ0NERk5RUU9RTVVccHF4lXqDebV6gnumegF5hnoBef96m3qCeYl6hnmjeoJ5jnqDeY96gnmLegF5h3oPeXl6enp5eXl6enp5eXp6h3mCeoZ5B3p5enp5eXmGepd5hXqCeYd6hHmEeoN5iXqFeZN6AXmPegF53noEe3x7e5N6g3mfeoJ5i3oBeYx6BHl5enmFegF5lHqDeZx6g3mEeoJ5jnqUeYJ6hnkEenl5eoR5lXqFeQl6eXp6eXl5enqFeQl6enp5eXl6enmEegF5hHqDef96jHoCAgQAgLvErJqjqrql+qKoqKeT9e3oip6biZaamY2Tvvio08emp52cqZDRurrb7pGCzba5w9P04dqotbbBu6uyrrClpK+zucDR1dfswrSno5CfxNrSroeMk4qRmJKKloqRnaaUl5aYoJOWj4+bo7KhiY6JiI6TloCLiIyanKSu0N3KwZSFgJWQkJOkqZqYoKSsoZaWipSfm5ial5inlZmovL+4sLGgqJb/gIDw7fj6hPv8/P6KlJajo5+hqrS2ta61rKinl5yqua24tq7A3c2rpJmfnqW3rrbDvK+rsbusl4qlsbK0sqiltbq+vKmtppWal7KtpKCgnZOel6SpweCakJOUm6SUgJKUjP+MlpihmJebk4eFiaGfj5GUoK2okZOKj5OIhYKEhYqFiP79iI2RlZydko6Sl4yWoZeZkaOloainm6WgnpiSjpKRkZiTjo+SiY6K8PXe+P2J+Oj2/fT68u7T5PKAhY6HjqOmnpSPi4mLiouSiISAgoKAh4qD9O6F8vmBma+5gLW1uKOYjIOEgY38g/rx4t3a3fHg2dnx2dDS1t3q44H39vL/h4uCpaCGhoaFjY6Oho+IiYqN+Y6Xj4H47uvk/YiG6P/4+o6E94Lr5v6C9OXm7PPx7oH78vHn7N/e7NjX3NfS4vPy9oSLgf75hoLx/vmJlIiHjoqUnJqwv7molIKBgIOEjJWSiZORko+LioqDg5OdnZ6rpba2sKeptczMyrzQ8bydoJSSjJ2hmZWQlJabloyHhP+Njp2qs62jp7Win5+ersmjvNPHsrnQ4LCckYWLlpCLlpqkqqqfoKOlnpapqai0pruxopySipmhpbCysbGlpKmkqaiytri0s5+hopOlgK26uquZiIqOmKW0saSZmZWUk5GLma6tn6astb2WoJqPmJaVlZaZjYuWnIuE+4H9h42Tlofw4+35gI6gk4jw4fT+hYmJjZOQhIOA+Pzw6e2JhYP9o5GUlYaOhIaIioKD/4KCgP+Ei4qYo5aMmI+PmKKlnZqho5+ajoCD+YGIj46JgJmZlYTy74GRko+QlZ2OlaGniYiLj//4gO3j1dje0NfW3+Ld5+3x49vn84yUi4yFhYaLlZudlIuGlJL47Onp9Oz9kZGLioaG+IaRjpKO793xhoL8hJCHgoDm8ILp2fjv7+eOjPrx/IaH/4CFiouChIePi4uKgISMkJmRio2LlZ6egJeWlZGUoJeRq6m1lZ2hlJ6lnouPkJ2loI2FhYDwgo6HjpaakpORmP/1i4uHioucqbK8ubaqpa22ts3Itb2xpJmWpbzQzcW7u7Galpmgm6CpqKSanZ+iopOjqamoo5ael5KXmJqcjo7+iLnHrJiVlYyPm62jqbG8rqamtLavrrW4gJymkXuAiZiS3H6Ag4dysaimZYB3Z3F5eW1tkcJ9iYl+ioB2fmuZiIWgtWpbk3l2gYedk5ZsdXaBfG92c3Jqanh1eYWVmZuthHduaVtojqOdfVheZFlgaWVcYlddY2VcXV9kZWBkX15kbXBsXGBaV1tfWlFaWVxnbHF1kJWEfGFYgGdkZGZzfHFrc3uFfHJuZWtybWZiY2RuYmhyhoZ+enlqcmOZS0qNh4yOTZSTlp9ZX19pb3B0e4F+iIKBdmxrXF5nfHeDfXKCmY53eW1xc3eCdX2Gg4WFeoB9Z1dsb25vdXZ0fX15eHB/eWRpY3FqaWNhYVxjXV1igJpXTFFRVF9SgFROTIlKWGJtXllcV09SVWVoV1daYmdjUlVTUFZQT0xLS1FDRZKOTFFZW1pfVlBQU0tTXlVXVGBfXFtaVWRnZmBeWF1gX2NiXV1fWV9Zj5SDo6ZZmo+WnpmpqauZpK9aX2tlbYOJfnFqZ2FjY2ptZGFaX2NgZ2Zdp6hhsbphd4yUgI2Hg3ZoXlJYUFidUJmQh4WHi5qOhYqgi4SMlpyjnFmvpqCnW1tRb25WT09RVVdZUFpUVFdcnFphYFCimJWSpV1dmqautGRhxWOysb5ju7GzvcO9uWLFubirsaikr6GcoZyWoayrt2JlXLCoVlKUnJ9bXVJTWVZZXWF1ioNzW0dKgEVJUFhYT1lbWVJRUVJLR1dhZWRtaXV2c2psc4WHhH2NonpiY1tcWWZqY11XWltiXFRPTZZWV2FudHBmaHNiXltYZ39gc4R4anF+hmtfVk1UX1lWX2JoZ2dobXBya2Z3dHN7bHl2bmlgWWJoa3VycG5qbW1nbWt2dnd2emhnaF1vgHqGhXdhTUxNVV1rZl1aWFNRUU5IU2NhWFleZW1VXV9WW1pcYGVkVlVXX1ZUnlOgV1teZFmckZSfVF1qYVymoKqoWF5iYmBfVFJRm56QjpRcWFCSbFpXXVpZUlNQU1BToVNSTZpSVlliZF9YYlxfZW5tamxzaGNlV05QkkpWZGBagGRdWFWWkkpOW2JpcW1eaHKCZ19sb8LHaL2zqKuwqKykqbK4u7m2qp2otmZnaXJqbG5wc3VwbmhicHCzpaSrtrC/dHZzcGtqvGJtbHJzyLzJbWjBZndtaGWxt2G0p72wqp9jYqyosWFktVphYl5WV1hZV1lZT1VdYGZfVlpWXGVmgF9kY19lb2VgdnJ/Ymx1bG91bl5gYWpwa19ZWlWZVV9cYmZsa2Zxb7OsY2dgZGV1g4+Qko+CeoKMj66olp2OgXl1hqC1rZ+XmYt2dHh4cXqCg352fH+DhHaDioiEfm9ucWtxcHFzaWq/YIyjh3xxcmtwd4eDf5GZjYeGkpWHkJqcgF9jVEZLU11aiElNTE0/WVlVN0pGOkFGRj1BWXtLUlZNTkpBPjtdVFFmb0M7XklIUVZpYmZCSEdRTkJIR0dAQU1LT1lqb3SFYVVOTEBNb4J+YT9FSkJHT0lARz1CSExFRURGR0FFPz9FSlVLPEA8Oj5CQTlAPkJPUldacnlvaEc8gElFRkhXYVVRWFxnXFNQSFBTT0tIRkhSRk1Xa2xmYmJWX1J8Pj1xbG9xP3l5eoFHS0xXXV1gZGdiZmRmX1pWSUxVYlxmZVtoeW9bXFJXWFplX2dsa21pYGdlVEdbYGBeYV9aZmtoaGBnYlJYU19VVVJQUEpSUVRXbYVMQ0hISUtFgElFQnZBSE5ZUlBOR0FBQlBRSEpNUVZUSExMTlFKR0JFRkpCRIR/R0tSV1haUUxKTUdMVE9XT1pVVVZUTltYV1FQTVBRT1FOS0pMR09LdHZvioxNhHuDjY6dmpqLjpVMTlhVW2lxaVxWVU9PTlJVTUtITU9MU1RMiYhSkplQZHV+gHl0cWddV01UTVWbUJyZkY+RlaSWjpClkouUnJ+xp1uyqqSsXl5Tb29ZVFVWW1hbU1xVVFZZoFxhXE6blJCKmlZVkJ2ip2BasluioK9er6Kiq7Otqly3rK6oqqGgq56eopyYpbSxvGRnYbmvWlegq6ddYldZXlxgZGd3ioNzXEpMgEdKUVtYUFZWVVBNTExGRFNaW11mYmpsaWRoboB/eHODkG9cXFRUUGFhWlNOUlFWU0pFQn1KTFZgZGJaW2VUUE5MWG5QYnJoWWBrcFpMSD5DTUdDS0tTWFRSVVdXU0xaWVdgVGJdVVBIQk5SVV5dW1lYVVVQVFFYW11bXk9QUUhagGNwbmBQPz9BSlRhXldSUU5NTUhDT19eVVleZnBVXl9YWlhWWFtbUE5PVkxKh0mLS05QV0yGf4iWUFpmXFWZkpyeU1dZWl5cVFNUpqudlJNZWVifbVtYXFZZV1xYWVRTnVBRSJRRVVhcXFhTXVRTVFpdW1pfVVZaTkRDe0NKUU1JgFNMRD91eD1ASlBZX1lOV11nT0tXV5GYUYyFf4GFfX54gIOIjIaEgHuAhU5SUVxUU1RWXGFbV1BMV1iMgnl3gHyJV1hVU1JXlExTUllZloiQT06XUV5UT06GkU+Mf5SLioJWVI2Kk1BSlUtQUU9IS01PT1BPRUpQUlhSSUpGS1BQgEtNSkhMU0xFWlZiRk5VS1JWT0JHSlRbV0lDRD5vPkhDSk9WUk5UVHxzRkdBRERPW2ZoZmBVTlVbWmtpWmVbTkZEUWd7dWliZFhGQkZHQ0tTUk1FTUxPUENPVVVUTkNBQTxAQEJDOjtlM1pqUkdBPzk9Qk9NTVhdU09PWVlRU1leh3oBe4Z6g3mLeol7hXqCe/Z6A3l6eoR5AXqEedV6AXmgeoJ5p3qFeQF6i3mZegV5eXp5eY56Anl6knkBeoR5knoBeYR6hXmCeoR5CHp6eXp5eXl6h3kBepF5Cnp6enl5enp5eXnDegF5+HoDeXp5hXqEeYV6hHmJeoV5BHp6enmMegV5enp6eZZ6AXmJeoJ5j3oDeXl6knmQeod5hnoBeYV6Bnl5eXp6eYV6A3l5eoZ5CHp6eXl5enp5s3oBeYp6gnm+egF5mHoCAgQAgLSqmpmmnrG/qYuDl5+QhoeIkqSGoKWsr6aoq+Di2Ozq276yv8DBwcG9vcTMz9LQyL2ghIz0usTJv7mwqqWepquxu8G5vrGlpKqur7rM08/Jy9XJvbfNvbGmoK6soZqWlJufpKmanaGalpajsaicmZCfmp+64djn89y+trOpnY2EgImNnZqTiImVnpuZpbGyoZmipcWorK2iqJuGjaCdmqWpqpWWmJD9goqJjP3384OHgYuOnJinpaSem6iiqLS2rKKxq6ShvM7EqLGkp5+PnavA1du/oqmxpZuil5mgpL3S1bWpnZaloKClpqGbjouTkpWQgvb/hpaeys+DguOKraergKesr52Zi5CUkJWfoqGhjI6Ok5eqvrijnJSUmqaroY+VkYyDiJaQhYyVmpuKiJSgm6qnlZmek4mLjYeLnrq9r6CluJWChYPv/Yqdoc/sodqBjpiPhI2SiPzwhoOC7PyHh4H+gfHr7dbyjpeYlIbv5vP5g4iUi/qDhIWGkJSWkJGPgI2LjI357/uGiZCYlIn3+O/4hYP1hID16Nji5Pf57PT5+fSF/4GHipiaj5L684CKifqCkYaHkZaMkYz8/ImAhobw6/mPjoiI8vL5hfPp1Nvl19njg4T18PmDh42Nh/H+hoSEhoWGg4qFhZKPgoGB5/T5gITv3+368vD7ge/zh4aAgIOAhI2NmqOUi/7z8+v0jYWPo6Gan6OnoqWSkpedmI6SpLqcpqmhnJuYlJaQiIyHipCKiIKNl5ycn6CUkI+PnqGVjYSOmZuXjfOLlZeS/fiHhZeShfP2jIqEkZSdp6uekZKXkpeQkYyUj5yrs7ilprq8qpuYopiVmqqsoJqiqqqvH66pprnApamloJCUpKSfmaairrzZ0MCroJ61wJmKkpSEk4CQoZmXmJmcjpGXnZ6SjpORh4OAio2OjoOIhPSLgfyGjYyF//mDhoTw7vff5Or7/4OHi/To/oyKhI+GiY2HiIyDiIuSjIuLiYKQmI2JhYaHi46YkO/l5NLf6Ozn8fqGnKCVh/LV4f3c2ub2hY6I/PCHhPGDgIyVjeHI0N309oD474D4gpeH9ODjgo6bmfrs5+3t1u74h52QhYP4gIWE6df5gIaD8YKAge7zhYqJi4Lv7OTlgoqOjYH38oGDiYyGgoSPiYmEi4n/gIyakYeQkIqXmY6IhpGDh5CIh5CKhZihx9PDs7KmpJmgpKCTjo6dmpSMjo+RkJqclZaSg4eWlZqYlV2Fg+zt2OTp8/f+gomKg5CTopidtLjAwaipr6uWjI6RnrOhmZualqOysp6joK2moZ2po6OcmZ6lp5+XoZ6bo6CinYCKlKCroZyZoKCcm66toZqrtbS3vK+urqSnueeAmIx5eo2Bkp6HbWZzeW9naGhugmZ9f4CGfnp2lZ+kraOXg32DhIaAhIV/g42UlpSMhGlaX6Z/iY6EgHlzb2tob3eDhoKJfnRzen2Aip+mpJ2eqp+TjqeUhXhxfH5yaWZmaWlvdWdqb2pjY255cWZjXGtiYneVj5WYj4SAeHhoWVKAV11saWdgYnFzdHR7hYJyZ25ukXZ5gG1vZFJbaGdkam9uWlpYUZFMUFRXpqOYT1FRXmJzbHl3eHJqdWxweXt1c350a2d+j4Fzf3N6dmRvfIqcoZB8eHlzbnRnZ2ZmfJmjiHhoYmtodoR4c25bVVVhXFdOnadSWWOPjEVIhk9qZmuAYWdvXlpRW2FbW19kZWhbWVlcYG57e2ZgW1tiZWBgV15UT05MUFBOUFBUWUhGV2BWXllOVlZMS0hNS01bcHRvZ3GDYFBRUZWcWm10pL5zglNgZWFZYGVboZ5bV1ikvGZmXrVcqKKmmrlvc3RtYKKgoqdgZmxptmJjXlpjb3Rsa2qAZ2VnY6eZo1RaZGdlWZiYlJ1VVp9XVqKaiZCRo6qco6impV22XWVndHRrZqWaTlVTkkpSSkxRVFBRUZaZTklUVJSSoV9hXFmnpKdcpp6Lkp6VlKRgYK2vumJlaGRfsLphX15eXl9lZWJjamVVVVONnaBUVY9/j52OhZJMhoxWUUeASUhMVFdfaGRalImJhIpVTVdsaWJmZmxsa1tYXmJjWlxrfWRsamJiY2JeXlZQVU9QV1BOSE9bX15iZVdQU1RcXVRSS1dhYl5RilVdXlqWlVVVZGFWm55eXlVhZWdzemtgZGRfYl5dVltcaHR5emxqdXZnXFliWVdfbm9mZGtvcH6AbGtrdWtlZWRgUVNjYltXYVxocYJ8cWBYWm93WUpPU1RdW1pZXlheZWdtYmNobGpjXmNfWFdUXmBgZGBkYKhbVrRfYVxWqaxaW1WYmaKXkpagmlRXVpKLmlRTT1lTVFdVVFdTV1VcVlRWVVJaYV5dVFNVYGJcWIyJjX5/f5WaoaKAVWFiXlmVe26Li46grllgXrOuY1+2ZF9sem6smZ6nwMNgs7LCaHVkr6KiXW56bLi6tb6/pbK2YnNsZF6tW2Bco5y4X2dowWhna73AamlrbWfLysO8aGtpal6nr2BdYmZjYmNqYmBaXlaqVV9raWBiYltjYVhUTVFOVVtZWWBcV2mAboaNgXVxZmxkaXFuYVxZZWVjXWFlam5zdW1sZ1teZGpsbGdeXaWlkZuhsK+2X2ZnY21uenF3iIePknt7gH9wYmRmcYd2bXJydH6Mi3h8d4R7cXF7dnl0bnZ7enRtcnJxeHN4dVxnb3qGfnt5gYF9fYyMgXqGkZOWlJCRkoiNnsWAWVBCRFVOXGRQOzZFSD41OTk9TTdLTE9TTEhHZWxvdmhhU0hRUVNPUVNPVFhbXVxYTzc5OWVPV1tUUUtHRENES1BdXFxjWlNSWVxha36Gg35/ioB1b4J0ZlxVXVxVS0hGSUtQVkpNUkxIS1VhW09LQ1BHSVt4cnZ3bmRhXVhKPjeAPEBOS0lERE5SVFRcaGRUTVJQbFdaYVFUSjxFU09NV11dSkpJRHc9QkZHgoJ5QkdEUlJcVF9cXFhWYFRYXmVcVmJbVVJkb2VWYlpfWElSYG95fHdlXF5aWWNUUlJRY3Z7Z2BYUFdUWF9dWldIQERIREI5cX5DSE9zcjY4cEVZT1OAU1ZYUExARElGS05OUlNFQ0NFSVZiY1NNS01YYFpXTFFNSUZGT01GSUxOUUREUVhRWVRKUE9HRUVHQ0RRZGhhVFpoTUJFRH2DSl1gjKRkcEtXXVpSWFpRk49TUFGUqltYUZxMjIeLfZdcYGFcUoiDhIlOUllVklBPTUtTW15bW12AWVdYVZOGkUxQVlhWUIyRkJZSUplUVZ+dkpiWqambpa2lo1y1XmZpdHZtaa2mVlpXm1BXT09SVFBVUJOUTUdQUJGQnFtcWVmfmpxWm5SCiJKLjJlYWKCirVxfZWFcp69dXFteX2BhZGBiamVYWVmZpadYWpiMmKSYkptQkpZVUkuATEtPV1ZfZV5UkIaDfH5NR1FiX1pcXGFcW1BOUlVUTk5dcFZfW1RUVVVRUUpFSURGS0VEQEVOU1JVV0tGR0dOT0dFP0pTVVBEbEVMTklzcEFATkxCdHlIRkFKTFFbY1NIS0xHS0VGQEVETlhcXlFUW1lNREFKQkBHWFpTUFZaWmKAVlZXZV5WWVZVSU1bWVRRWlZjbYN9cWJbXG11XExRVVNWVVRSWFJWW1lbVFZYW1pSTlNSUFBPWFpYWVRWVptVT6NXWFdSo6dbXl2jnJ2QkZulm1JUVJaMnlpbVl5XWFpWVVRPVVhbU05OT01VWVNMSEhKUE9OTXx4d2ducX97gIOASFJQTUh+aF1zbneOjklTTo6FTUqQTUlXZFqBcXeBmJlLjouUUFxLhYCBSVVhWZSQjIyNeomLTFtTTEeER05Kf3KKSE1Ok09PVJeaVFFSVFCbmI6JT1VVVkqDiUpLUFNQTlFYUlJNUEuOR1FbWE9TU0xWU0tJREhCR01JSExHQlKAWG90aV5aUFJJTVNQRUBASUhHQkRFSktRUUxLSUFFTlJUVVBGRnZ3Ymtxent8QklJQ0tKVUxRYmJrbFVXW1hJPD4+SF1MQ0VFRU5YV0lOSVROR0VPTE1HQ0lOT0lDSEZESUZKRzA5P0hRSkhHS0tKSFVWTUROVlZUVVVZV1FVYn6yeoJ78XoBeYR6g3nFeoJ5iHoBe8l6gnmGegF5iHoMeXl6enp5eXp6enl6hXmFeoR5hHoBeY56g3mGeoR5BXp6eXp6jHkCenmHegZ5eXp6enmJeoJ5hHqDeYR6BHl5eXqIeQV6enl5eYV6gnmPegV5eXl6eod5A3p5eYx6hXm6egF5hHqCeYV6gnnnegR5enp5hHoFeXl6enqIeQZ6enp5eXmeeop5hXqIeQh6enp5eXp6eYV6hnkKenl5eXp6enl5eYR6iHmFehB5enp6eXl5enp6eXp6enl5hXqEeYV6gnmNegF5vHqIedN6AgIEAICFsaugnqWvw7KtpJyBiJ+UlJ6tnpuhpaOWmo6Ljo2RkJWdm56uq6SYg/7igtOMiYvZtq3WmayPuKi2qrLHq7C+rqKVhoeDhISKlJanmpygrK2iu8+7v762qKqnrrS2rJubrpeVlp6vppiapaeyt6qkrKiboauywbGrqqytnp2Zo4CvsaaXjoyLlZinsbe4r7ijn7+sk5GVl46KkJuysbClp5+5ubChnaafhYiLjezl0eiPl5GWnK+prbeypZeZqKSUoJH/hpGbp5iPhoKKjpWVnJKaoZ2PiZaXj4mdqa+fq7Kcn5SUpJKXoqemjJejhvaGhJGXm5CNjJuir8yS5Li6sICspKqZn6qdkpGao6aqpZ6UpqyvtL+/rZmgs6WfqK6jiveUmo6Tl46GiYSRpp6ilIaEj5uci4mSjpaQjoLhyb3M7YOA8N/W1ube6Y+TqZ2fq6y0taeajoOD/4aGhIDx/OTi7OvQ3OHU0+Xl3+bdxtLf3uPa4/v56+HvhJaUg/+Fh4CKipGD/oiB/4CBh4H7/pawvbG9nYqE7eLf89HB4Yumn5ufpKCbnqKXi5KR/vmE9uTvh4Dq5uHy1OHq9evw/omEifWAnLTP3dCkkoH93u7t09Xq8OuCh/Hn/IL38vmB/fiFlJuTj42Oio6VmqCwsba8qo6Mg4Xw8O/y5+7h7u//g4CUn5GOl5aamIuMhe/7i4+Vs87n2LmkoJ+iwMqghJucoJ6qoqqru8W7jYCIjpCWlaCalpOLgouSnJ6al46HhIqUoa+to6qup5+UkpOblIPv/4OQlZmQhoqHhI2JkpqViYOBjYWAgYihu8HP0LustMTlzq+noZ+amJidosOnq6qknoCjpqGhmaSjn6mkpqiipK+ppaKbpaOlmYqIhYeHg4CJlY+elpeaiIWDjZ6jpJyMjIyDg4SFjJCW+enl8oqNge/e7oHn8Pn1+O7a1tvn7/r8hoOAi5OZlIiIkoyCgpWOhoWJgPeBipWJiIWLh4eEhvyD+vH4h46HhfGA9e7i4Nzm74DvlLPCs4bqhZeC8fX0+vbp6ej1/v/v7+X43N3a8uz07PLw+oKI8PmRlo6WjoqMhfuB+e6JjO/d8+//5vvq5urS7YLq8IL3g5CA8vHthPj6h4aLh4H/7v2LjYeDjJaZkIaQioWG/Y6UhYaEiIuWk5aXjIr/gIKGg4uKkpibk4+Mj4Chq56blZiVm6m7tKyfl5GJgYaZmIyDgZGRjYyPjY+Mj4mHhYKAioKC/IOFg4KEjomLioiNiouFg4umu8nFu6u1wLqnnaCZm6+bkpicnJ+lqbOvqaWonJyiqJ6nnKmzq6eysLSonaq4vKmimo+DnLbR3dS/rp+anZWSmdXfs4623YBtlHx1dnmBk4eCe3NbYnZucHJ+dnV7e3JwcmJfYGFoY2lwbHCAfHRqV6CFUZNhYWedfXaWbHtqgnaEeYKcg4SPh3pwZmJgYV1iaG1/cnR2gYN3lKaRk4qHent8f4OBf3ZtdWloZ2hzbmFiZWhwdmplamddZnF5h3h0dHJ3amhnboB6gXdqZGFcZ2h0fIKDfYl0a4Z6X1xhZl9YWmV0c29nZ2Bzd3RmZG9rVVRUU4eIfpNjaWZudYd3doN8c2lqenVncmKmV15kcWllY2BjYGVlcHF4dGVZXGdqY1lgbXdpdHxubmNecWl4fXd5YWNoUZJQUWJraVhYWWBjc45fk3mBd4ByaXBjZ3FpZWBia3J2dG1kbnaCfoF8cWFneGtmaWdeU5ZZWFVbVE9MS0ZQWlhZTklLTk9MRkVNSFFQT0NxYlVcdkpPmYh9gYuJk2Voe3d4goSHiHlqYFhaqllVV1eirZubqK2SnZuNkZSfoqKikpmrmpeSma2po5anYXBuXrReXIBcYGldq1tZtFpZXVOcq2yIk4aQcF5SkIyLlX5zlGN+dnF0eXNrcHNmXmBgn5tVoJCaW1CNioGNeIOGiIGMmlJPU5FMXniOl5BxYlerkaKij5aoqaRdZLSpulywrbNcsrJhbXRyamZjYWNmZ2l2dX+Dc1lPSUyFgXx6dXpteHiFRoBRVU1MWFhZWVBNSHyKTlFYco2llXlpYmNmgYZlTWNmbm1wbHV0hJCDWE1TWVtiYWpjYF5XT1RZY2ZjYlpUUVdgaXJycXF1dG1gYWJrZlWVolZeZmpgV1hTUVhWW2NeVE5MVk5MTVBheX6PkHptdX6UinFpZmdkYF1eY4RraGJiYIBjZWNkXGdoY2xnaWhhXmVfWVlUXFtgWk1JRkpNSkhQXFNeWl5kWllYXmhycGtgX11XWF1dXWJtuaKdql9cWqaUnlSUnaimpZeLjJakq6agVlRUXGJsYlJOVVJPUF5cWlZZU5lTWmBYWVVbV1laW6NSo52nWFVRUJ9XpY+BlJybmoCgY3SBeFaMT1tVp6mooaGUj6S6urq4uqi1qaulury/uL+3tl5mtbxqbGNmZWFkX7RdvblrbrekrbK9qbeopaaSolqstGO6ZGtnv8K+abu2Yl9jY2PIx8lqaGNeY2xsaVxjWlldsmdrW1tVU1RcWl1hWVqnU1JVUVNRVlxkXFhVXIBrdGpmYGViYWp0cHBmY15bVltra2FXUl5hX2RmZmxqb2pnZWJfZ2BesVdeXFpcZmFfXGBjYWJeX2Z+k5mVj4SQkJCDen13en5ua29ucnJ3e4SAe3h+c3N3fHB3bnqEgXt+f4N9coCOkXxxbGRZdZCprqmTgXZzc3FydKuyi2mOr4BFXEtFRkpQX1RRSkQvM0Q8P0JNRURHSEM9PDMyMjI4NDk+PD9LSEU8LVA7KF5BPUFoUEhgRlBIVk1XTVdtVlhjWlJJQD8+QT5ETFBgVVhaY2RacH5tcmllW1xaXWNhXVRPW09PTVBdVElKT1FYXFNPU1FHS1RZZVdUVFRYS0lHToBXW1ZLRkVBTExWXmJhXWlVTWlgR0VITEVAQk5eXVlSVVBmaGRWVVtXRklHSXd4bHtPU1BWWmlgYm1kW1JTYVlOXEt6PkFHVVBKRkFGRUtKUlJaVkpCRlRUTEVLUlxPVV5XXFFMWktWYlxfTE9SO2c5PEhRWElDQUVIWW1Le2RhV4BbVVhOVFhOSEdNVFZZWFRKUlhiXWNhWEtRYFtbZmNUS4BPWFJSUExIR0NMV1JVTEVGSEpIQEBGQkpLSTxiVEpRakFCfHJqbHZ1flZXaGRncHN6gHJlW1JSnlRRUlCXoY6Nl5iCj4x/foGIiIqHeH+NgYF7gZKOhXmFTFlZT5ZOTYBOUVdOlVFPoE9QUkuQl11wfXd8ZVhPi4aDjXZriV12cnB1eXVvcHNnX2FfoaFWopafXFSRi4WTfomKjYiOnFVPVJNNYHmMlYxuYVOojpubiY2en5lWX6ufrFaloqhXqKldZ29saGRjYmZpa218f4WGeGFZUVSVj46OhIp+iYWPS4BWXFNQWVdYWE9LR3Z9SElPZnqNgm1bVVVWbXJVQlVVW1xgXGVmdHpwUENHTE1WVl1WVFJMREpOWFpXVk5HQ0pRWWRiX2BiYVxQUFFYUkR0hEJJUVNKQUM/PEJARExJPjs6Qjs4OT1NYWRxcV9UW2R4b1tTUE9NTEpLTW1YV1RVU4BbXFhbVGBgW2NeXmFbWmNgW1tZYmFjW05JRUlJTUdMVk9aVlZZT01NUltfXVlPTk1KUFNUVFdgmYiJmFZVU5eIkU+Nm6iorqSVj5KboqilVVBQW2FoYFZWX1pVVmNcW1hXT5xVWl5TVFJZVVZTUZJMlIuPSk5NS5BOlIh6hIOChYCJVmp5bk2CSFNJkaGlko2Ce4eVlJeVkH6PhIR9kZGVj5WOkkxRjI5RVE9WUktOSYlIk5FUVYx/i4qUhJOGg4R2hkuJjU2OTVVPj5CRVZiRTkpOTU2bkJRSUEtHTVVXVEtSSkhKjVRYTE1KSkxUUVFTTEqEQkJDQEFBRUlPSUVCR4BVW1FORkhHSlJYVVRLSEM/OT1LS0I5NUJEQkZISE1LT0tKSUdFTEVJiEBGRkJETUdFQ0VGREVBQ0lgcH94a2FqaWdYTVBKTlZGQkZISkxPUltYVFBTSkpNVkhQR09WU01UU1dQSFheX05HQzsxSF91d3NmUkdDQ0BAQnR4VzpWcgF7qHoHeXl6ent7e4R6g3v1eoR5knoBeat6AXmMegF7pHoBeZt6hXmCeod5jnoBeYR6nHmEegF5hnoEeXp6eYR6gnmIeod5jnoIeXl6eXl5enqLeQR6enp5iXqJeQx6enl5eXp5eXl6eXmVeop5jHqCecF6gnnneoR5B3p6enl5eXqNeZN6AXmLegV5enl5eYR6Anl6iHmFegR5enp6mXkEenp5eYh6Bnl6eXl6eox5Dnp5eXp5enp6eXl5enl5hXqDeY16AXmNegF5tXoBedZ6AXsCAgQAgN2/rKCQmJiVjo6Xo6mcj4eGjaDCr4iToa2ln5qNnJaSqKmlnJOnp56aiP2Ehpi2uaywub68v7/Gt7asnI6OjZeQ/vP1gaCF2MSwraaimo+bpJ+lo56cnZ2fl42Nlp6bmZuZkJyenp6mrrW5xLy8xMC0v9LKxsW6vrefko+Mo9DqgNDAzMjGv7ShoaaorrvFvqy1v7GmlIyTmZeiqpygqZ6WjI+Ph4aKi46JgYKKh5OclpaF+ISqv72wr5yZloeIjpCIiKmono6Dg5CLhYaF9OiAkqSjlISD74uQkqKpmouIhOrh5e716d7sgY6HlJScp7WYjYyJi5OOlp2nxMvU8dHPgL2xwuHSyKafmK+vkvuGjZCOlae1vPr5tqmmg/714ICGk5KQm5SRmpmMj52ipZKUl5aIhYaenJyZjpCYpp6kioWB8Ovf0c3b6ubm9YWNmq/BzayXmZaalpuVkIj8hoL5/oLygf/98IqChoiTh/Hx69vh6vL9iPv4j4uEkJWSioaMgJCYmqeWm5H3/Ifz/IKAgIqV9/H27uHs6d/m3fPx5IGaoqelp5mSgoGU9eqCiIiOi/+I+fqFj5aQkYyQnZGDgPSEj5uPiomHi4WJjYaGgf2Fi4P77/eEgPz6gICHh4KFiYaLj5KSjoL88oqWpsK2lo+A49Lf0tbX5en29u7qgI2TgIyEiZWeno+PkYKCh/+Cg4eTn7nDvbehmaKZlJqTj5mSj5uWhoyUlo6KhYyLjIyGio6VlZmWoKaZl5SGgP2Bg/7u9YGWrcG2q6uikI2SnJSTl5aMjZOKoKeLiJyxuqugkIqTkZyeqaqytby1o6CgoJ+fpKuzn6GSiI6PkpmcpKKbgJiUn5qTkJOSmY6LjYGMjP+GhIOEg4eQ+uTvgImIhYiS/ujlgIWTk6OzqJ6SnZSL/+eG/dDT2Ob08/qHkJShtrqiheno6vvx3srG3fOGjIuTi4WB8ID9g/78hIeOioiChIWEj4iJ+ez1+PDv+vv2if2Bge/2ho6E8PTr94mFguvpgPmH7+2B+/yHn6CD69rw8eDl+PTl+OfW5Oz26e/48Obh1Nrb6+Hv+IKboaqvtbiYi4KCiYD4+YGFkZGTk4mDi4eKjpCTkov5hIeLkYeGhoOBgIWFhoeCi5SPhYeJkJ6QiIyDgY2WlZSKkZOgmZ+QmZieoIyUm52Rh4mLj5aWnpiTgJ+cn6Wjr8GwqaaalZSalJOVnKKnpKqeoaqnm5aSnJKWl5qZlYqEkJeS/f39hYKEiIiFhoLz/vyGlaCquba5oqCqnJmXlZuQhZGTl5Cfra6mraqtp6ursK2oo6Gfo6eoq7y+rbGquM7NzbupopKOsbexs8i7sbOmpJuqn6Kurb3zgLediHtrcnJsY2VveX91ZlxdZXqVfWJrdIN7cWteZ2Rib3FwaWNxdm9pWqNXWmqFiYCHkJOSk5KajomAcWZlYnRuubCuXXZoqZiIg357c2dzfnp/fXl1eXh5cmtobXRvamxqX2dnYmVpcHR3gXh8hH15gI+KiIZ9g4BqWV1abJOsgJiPlo6LhXtrbnJ2fH+GhXR6hHtwXlVWW1pka15fZl1ZVVlVV1dWU1VTUVdfWmZxbm9grVp4jYd+f21wbVpXY2ReXXd0bmJfZGpkX1tdtrJlanBoZl9fpV9eXWxxZFteWZuUkJSipaKmVl5YX2BobnZmY19WWGBdYmx5j5CYuZuWgIh9iqGYknZybHt7abVdYmVlbYCOjKysd25wU5mVfkdLWmFkZFpUWVhNUVxaWlFRT1BMTklST01QSlBXZl9mUk9Gfn9ya29+j46Tnlpod4meqolwcGttZWlmYl6pXFSkq1mkW7SwqmZfYWJkXKilnpKcrKeuYK+oY15YYmZfXFtfgGRwdoNvc2agpV+nr1lZWmNsqpiem4+ZkYaFh5ubilRwd396fGlgXFFYl5NTV1VVUY5LjY9QV1xZWFZYZVxQTpZUXGddWl1bX1tfXlheWK9hZFy3rbthYL7DZGVra2drb2tyd313bWTArGRvf5SJbGVVinmEent5fYCQjYmDTFZagFdMUVtjYVZZWUtMUZZNUVReboSMh4NuZG5pY2VfX2djYWtnWFxmaGFdWV1aW15aV15mZGdlam9mZmBVT59RVKacolZqfoyCfXdrWlhZXlpaXFhSVFlWa2tSUmFscWpeUEtQUFlbZ2lwcXl2ZmNmYWNhZ252YmFaVFlUWWNkZ2hggFxea2RXVFdWVUxKSkFJSIRGRUpJR0pQiHqGSExNTVNfoZeVVVhkY3KGfnNla2Zhs6Vgs5Sbn6y7prNham9ycX50XqScl5+bkIeMn6JZXWFrYVdUl0qNSImQTk5XXFxXV1hTW1pVlI6ampObq6yjWp9RVZ2VU1hUnqKbmlNWX6ScgKtXiotWop9VaG1bo4+nopiZpLazuKmhsK+srLa9tbWtnKCgp5mjtWJwdHt5dnxoYVpZYV+7wmNlcG5ub2VgaGNjY2ZmZGCuW15gZGNgYmJhX19eYmBcZW5vaGdnZ21hW1xWVFtkXlxWXF9vaHFgY11dXU9XXmRfVVlYW15cYF1WgF1cYmhod4V6dnNqZWZqY2BhZmpwbnFsdn96cGtncWtmZmhtbWVhbnh0vsTFYmBhYmBhXFehra5aYmtzh4aDc3V5cW1oa2tmXWNpa2d1f396gYCDgYiKgYeCdnl8e35+fo+SgoWDjqCcnYyBe25sjJOMjJ+Qh42AfHaFeX+Jh5rIgHdnU0g8QkJAOjlAS01EOTM0O0tmUDc/SVRMRUE1PTo4Q0NCPTZBRD89LEwqLDlRVU1TXF5dYmNtY2FYSkFCQEtGeHFvO05MenFiXVZUTkVOVlRbWlhWWlpeWE9NUVZTTU5LQ0tMSk1SWl9haWFjaWRcY3RtampfYV5KPT4+Tmt/gHRwfXFrZl5RUlZYXWFnY1RYX1hQQz5AR0dOV01RV1FOSEpJSElLSU1QSk1STFVaV1ZKhkdkdnNoZVZWVUdHUFBIQldWVUtGR0pFQUBBd3ZHS1JQTEdIdkpJQ1BWS0NDRXx2a21xcHF5P0hGS0tTVl1PSUlIRkhAQ01YanB5jXNugGhjaHt1blZSTFdXSng/R0lJTVpnYnl/WFJTPXR1Zj5AS1BRW1NMT09IS1VUU0pMSktGRkNNSEdKREpQXlleSUU+cnJnYWBsgH19h01WYnOBinZiYV9lYWhkX1qiWVOhpladVqmmm11VWVhdVZaQh3qDkI6UUpaSWVRNVVpUT0tPgFRdYW5dYliMjlGPmVFRU1hhnI+VkomSioGEhJWbjFNpb3ZydGVdVk5Xl5RVV1VXU49MjY5PWF9YWlhbZ15UUZtYYGheW1xZXFhcW1VYVqZYXFSlm6daV6utXFthYF1kaGNpbXJwaF61pmFrfZCEa2RUjX2JfHx9gYKPjYiFS1JUgFFKT1heXFJQUEVGSodERklRXnB3c25cVVxWUVVSU1hRUVtYS09ZWlNQS1FOT1FNTVNaV1tYXmJaVlJHQYVFR4yBh0hYaXlwaWRZSUZHTUhIS0hBQkhEWFtCQ1FeZVxQQDtAP0dJUlRbXmhlVlJUUlFRVlxgUFFJQkhGS1VWW11YgFRVYVlRUFNSVk1LTkdSU5VOS01MS0tQinyDR0tKS01VjoB/SUtXU2BxZ19RVlNRl41RknR4foybjphUXWBjZW9rWqCcnq2olYSFl6NdXFlgWldUmU+fVaCiVlldW11YVFVXXVlUkomWmJWerKCWVplOToqIUFZSk5iSmFFRVJGLgJZNentLjplSXmFRlY2dkIWFj5iSlYmCi4iLjZKXk5OJe4GBiX6Gkk9eX2RoaGtUS0VHTUuXmkxPW1hVV1BQVFBQUVRVVFGMSElKT01JSUlLTExJTUxGTVZYTk5OT1lNRklDQktVUFFLUVJgWGNSVVNTUkNJTlFLREZFSEtIS0ZAgEdESE1OW2hcWFRMRkRIQT9CR0pQUFRNVl5aUk9KUk1MS05QUEhET1dThIiKRUJFRkRFREB0f4BETVZdbGpqVVVaUEtGR0hBOkFER0NOVVRRV1RYVFlVUldTSktMTVBQU2FkVVlUX3Bub11QTUA+WV5WWWpcVVhMS0VTSExVVWKDqnoBeZZ6Bnl5eXp6e+t6AXmaeoJ5h3oBeYl6iHmkegF5jnqDeaN6inmQegt5enp5eXp5enl5eYZ6iHkDenl5kHoFeXl6eXmFeo15i3qCeYV6BHl6eXmLegF5jnoLeXp6enl5eXp6eXmOeoJ5iHqMeY96AXmvegZ5enp5eXnNegF5h3qDeYZ6g3mMegN5eXqIeYh6inmHegZ5enl6eXmMeol5CXp5enp5eXp6eoR5DHp6enl5eXp5eXp5eYR6nHmNeoJ5kHoBeeJ6g3mIeoN5yXoCAgQAgIP0+IGKna6bioSD+fb29ff4/5Snr6ikoZ+grbaerNO0rLS0p6KUjKK1xL6onp2lqaipmI6SkqqputTQv87CucGnmZiir7/b2dnUw6uqqK+1n4bv9YeVrrnGv728uK2pmZCbnZeZl6OppbW0pZylraunp6/E6f7iwa2dj4aOlp+hgKOXjamN172yxsTM9o6I37Ogn62+qaWepKK2vLKtwamSjoqMh4mA+4uKkJSOnKKusrGxp5qirrOciYeCgJGUjpOgr6yXkYuPkqOdlZCMhJKdjpiS9faBjpqltKybm4mLlbu/lqCU79LO4er7jJmftaWUkJuVmJuopJSQl5KaoaaYgJiXk46MkZqYnKWvqI2Hqq+QgP78+JCvsKmtmoOBhYODgYGJi4uXioaMn5CNxLmrnpeek4KFh5adl6GikYHqytbf3vSHi4OD94GPnrjRtLO6sJ2CkP/4i4P/gfqCg4WB/I+Xj4Pt4+zr4tPd1eiA48/R7+n29fKCoLG3qKSnsqCIgIeCiYuNk4+ampqPhPj17/Xv9uDOzdXX4erw8PeA6+r08evq5uHcg4H8g4uMj5WOmJuTmJuS/+mKi5aTnJqZkKKw2q6vopWHhIr+94H9+efk4NfU4+72iIyPmJmJgoCJiof9/oOCgYj3g4f3go2LhIL/gob89eDi2ebw5evq/4SEgIaGiIuIioWBiY2Kh+TzgYeIgISEjI6I+IaH+P78hYiMiZShko2QiYiBgYqEiImHhY+XiIP3houQioD7/YGLlZ2gmoaPqr+9tsWmkIeTkJiViIqdrquikpWVl46OipKam5aTnKWvsLKpqqqsqJ6RgoCHjZONh42Mk5OSio2Xl56WgIuUiIDz8ZKVkZKbkoyUkIX9+ev+/oKD8eri8/rs/4yEhoiEgYyLk5iqtbWwkYWCiYSHg/7u6fmCkIf+/oScl5SUh4P58eHQ4eHqg5CVlZWZj4yGi4qJgvKChYD6iI+Lio2HjYaIgenw/IDg2uf++YGHiIWPmZT94+DghIGEi/3zgPDu5e7sgoeFgvf9gYKE6+L38+zf7fCB6eTuhPb49Ovl6eTt1N3n9YGDhIuUmaOdkZCRkJCL+vP77/aK/v2JjomM/omIkJaJgoCJk46ZioWLifz+7u/9hIeBhomGhomOkZCSnZiVqK2nrK6vuKeZmY2OjZSOlpyeo6mupZeWmpiTgJ2cmp+dp6ywr6mZkYyTmp6VnJiTk5uWmZyloJ6SkpGFgY6N6Ozs/IOJhY+TkpKKjIWAgoSB9OLu+IKWiYmViIbv9PD+/YOMj52ppKOxqaauoKOktLmytquksLasqp6dnJadmqOrsa6qtLGOk5iko5+goqSlpKaZkZGPm5eMlZSLgF+rq1lhcX5tX1xbq6mjoqmpsGl8gnx8fHl4hIVxfpqDfYWGenZsY3mPm5N6bXB6eHh5bWVlZXt8i6GZjaGckpZ8cnN8iZm0srGqnYeEg4SJdl+jqGFtg4+clI+LhnhxZFxkYl5gXmltZ3d0amJocHBsbnOEo66cg3BgVEpQWV1egF1bV2hNhH1yf4aOsWdioXNhX2ZzYV5aXVxtcm1ugnBhXFZWTlJQnllcYWRhdHyJh393bGJue31wXldYVmNmZmJufHlra2lucHxybm1ybG9vX2xttrheYWp2g39sZV9ibY6ObHVssI+CjZmqYWVndm5saWljZm9+c2dpa2RobXNogGRgZmNhZ3BxdXiBfm9tgYNtYMXBuGl/dm98b1lVWFhTT1BaX15jU0tPX1FPfHBgWVdUT0ZPT1dUTldbUER4YWlxd4lNVlBNklNfZn+MgIiVhnFZZrGpXVamVJpSVFVUq2JoY1iXjZqZkYKLhJJVhnF5lpWhnZ5bcn2FfHx4e2hZgFhRVlpfamJrb21mWZ2goKWbo5GBgIKGjpibnqNVk5adnpqVjYZ/UFCdUldXWmRgZmhgaWlbk4FVVF1aYl5eV2lwi2xyamBWVFqopVevq52Sk4yMnKesam9teHxpYmJra2fDxWZlYmavXmOwXmpmYFmuWl6qnImIhJCYjZGOo1RUgFZVVFlTV1NRWVxYVYiYU1hYUVNUXF1YnlhYnaOgV1tfXmdyZlxbV1lRUlZVWVtbWWJoWladU15gWlKhnFFcYWZlW01Wb4GAd35qVU1VVV5ZTk9eamVcU1ZWV1JVUlhgYV1YXWRtbm9nZ2NlY1tUS01RWF5VT1RQV1laUldgYGRggFZZSkeFglNXUVRbUU9VUEKHh36JjU5MhICBkJ6WqV9aXF1aWGdlaGx+g4SIb2JfY2JmYrarrKpdamK3tFtvc3NuYVqoqKKYqa6kWmdubXBxY19YVE5LR4hMTVGlXGFdWlhTWFVYU5Weo1KYlZ2hmVFXXFpeYmCrmZmaV01ea7efgKWhjJObVldUUp2sX11dqZutpJ6hsaxgrKysW6+8sqesr6atl5mepFldX19kZWZiWFlcYGBfsK21rbFhs7ZmamdrtF9dYmZYVFVdZmNoYl9hX7K5ramxXGBYXGBhZGJnbmdlaWdtcnZydXRydWRaYFhbW2FeYGFeW15kXlZYX2FhgGloaGVncG9xcm1gW1lgaG5nbmljYWhjZmdsZmdhZmNcYXJur6+uvmFhXGhtbGtfX1pXWVpXn5GjsFxwZmNtZWCjqKCln1RbYGp1dXJ2dnJ3bnh6iZWGh4B9iYmCgHZzdG51dHmEiIOBi4NobnF9fXpxdHp8e3huZ2locG5nb3BmgDplZTY9SldHPDg2Y2JbWl5eZUFPVVBPTUtIUFFBS2NOS09PRkE5MUNUYVpGPT5GSEZLQDs9PU5PWmpoXm5pYGZRS0pSXnCLiYmEemNjYmZrV0NrbkFKXWVybmlnZ19aTkhQTkpMSVNWUGJgU0tPVFJOUVdngYN3ZldJPjY7QUdIgEhDPkcuYGVYYmdthk1HdlRGRk1ZS0pIUU5gaWNebl9QTElNR0tJjVBPUlRPXGNubmpkXFFZY2ZZSkdJRlFRS0ZQXl5QTklLTFhTTkpOSE1OQk5QgH5BSk9TX11OSkVLVmtrT1NKc11WZHOES1BRX1lVUlhUUlNaU0pLTUxRVldKgEpKS0dIS1JPUVJWWEtIXGNPQ4V+eEZWUlFZTz8+REVEQkNJSExXSEBDVUtJeGxcVVBOSkNISlBOSVJWS0FwWWJnaXZDS0ZFg0lXXXKAdXl9amBOW6CaV0+bUphSUlNSoV1lX1WTi5aVinuAdoRNemlviYWNi4lOZXF3bm1tcV5QgFBJTk9TWlRdYF9XTo+Qj5WPmYl5eYKFjZednqVUmJuho56alpGLVlWlVFlaWmBbYmRcZGZamolZWGFfZmFiWWt0kW5xamFVUliinVOlopWOjYSCjpaeYGNiam9gWFlhXluusl1dXGCnW16nWWViW1aoVVqmnYeHf4iPh4qJm1BQgFFRU1VPU09MUlVRT36NTFFQSEtLUlNPi01OiIyKS09SUFhjVU5RTE1HR0pITE1OTFRcTkqFR05STUJ+eT9ITFJRSj1GXm5sZm9dS0NNTFZORUdSXFhQRkdGRkFDQEZOUEtJUllfYGNbWlZYVk5GPT1CSE9JQ0hFTlFUTE9YWF1agE1QRkSBglVbWlpgV1RYU0ePkIeVlk9Oh4SAiI+CklNOUE9MSlVSVlpjYmVrWFNNT01PTJOIhopLWVKZmE9hZ2xnXFmqpZeIl5+eWGBeXWJnX1xWWVlYUplWVFKjXmJcWl1ZXFRWUZOboVSblJKcmFFUVlJZY2Ook5OVVkxWXqSUgJWRf4eOTlJTTpSeVFdanY6dk42Om5JSlI+MTJSdlYqOlImPe4CEiUpMTkxQUFZRR0VHSktLiYWOhYlOjo5PUlBWkk9LTlFHQ0ZLUUtQSEZJR4eLhISNSk1ITE5MTUxQVVNRVlNXXmNfYmNlaFhQU0lKSlFNUFNQTlFVTURGSkpIgE9PTkpJUlFSU05BOzlAR01GTEhDQUlERkdNSktESEY/QU5LcnNyfkFDQElNTE1DR0E9QEE+cmV0f0FSSUZPR0Rwc21zcTxDRk9YVlVZWVVYT1VWZGpjY1hUXF5XVUtISEJIR0xUWFVTW1c+REdRT0xKTU9RUVBIQ0NCS0dBR0k+A3p5eYh6h3nAeoJ5sHoBe4d6gnuYegF5rHqCeZB6hnmneoN5qHqGeYR6AXmMegd5eXp6eXp5hHoBeYR6iXkBeoh5lnqQeQF6iXkDenp5jHqCeZJ6A3l5eop5i3qCeYR6BHl6enmFegN5enqLeY56gnmJegZ5enp5eXmXegF5hXqCech6gnmKeoV5gnqHeZV6hHkFenp6eXmHeod5jXoFeXp6enmKegR5eXl6hXmHeoR5hHqHeYR6BXl5enp6iHkFenl5eXqMeY56hXkDenl5hHoBeY96hXnNeoR5jnqEeYd6hXm7egICBACAlqarpZqeoK20qJ6Ri5edlqK5xLTByrufnJqPko6bsb3L0tPa2saij4uWk5mfpq+2sr/Tx6iUj5Wcq6OusqitqqKcpp2YmK/M1cTP9dW4ua2emI2boqqfg5Opq6ehoK+0saetsMC9xfXsyamToJaQlZ+en5+gnZecpKWMiof/8fqA/oSRlYmWmp2qv9PM18/Y18W1r6iclZCSmIuGiYiQkZeWoYb4gIGKg+ff5uH6hIqXrLvuiYL/4NKui5aSjJWnpavKxrasoqOWkZuNi5+vrpuLgPmIgY6kqZqguZ2Xlpejrrezr6ylqpiQkZuYmp+bnJmNk5GYxL2nmpabl6qxurqAqai2q7q+uqGShvzhzr6+2vSJkJqK++f5i4aB89/s8vL3/4GF/fyOl5GWmpiVgfSBgoSCjo+BgYeIkZ6YkfvFu73Oh6aH7q+M5uTv+oLu9IP15tvf7P6E//Ln6vaCiYuIipSRjIPx/u7MzMznmLP53ezn8O3t+ICCgffy8YCYm4uAioPo7/7o9ffy9Ozn+PuC+Pft9/eJk4iEiYiEhpCDgoeOivL4hoWA/vzl6vf4gob87Pzn6/v/goOCh5KRj5OE+4SG+ID88fP58oGCgOflgo7+7YCTjomOkI6L/YWUi4OD8fGF9u3t6eTb9ISJg4nv4tvg4Orj+YSEhoXi+u7u5YKAguzygImH/YeFiYeAgYeBh4iHiYaGjI6OlI+QjYuDjo6A+fb//vf064GGiP+JmJGSmZ2Yjo2TkJOUkI+MkZudk5KXkZaYoZeXpa+WlZ2TkY6RnZ6amZujnqSemI2TmJqSmJyemJygqpKSmpOPiIOFi5mRhIDx8u/7j6Gkm4SUjIWAiICDi4qTo6SgnImA+YKRkImDhunuiJWI9YP/5vLz3t3v7e3y/Ib6goeIhIaIkJmWif7qgoqMl5mD8fWBgeHEyODf7eTkzszW/4+hjobu+YWPkZSgn5OQkouMkpGNjIX1/4WB+IPxgfvo5dvxgID8//Pg9o2Mifjs3tmFg/6Ih/+A+un4hPr9hv/v5+nkzN3gzMTN0M3S7IWD8/6Ji5OS+Ozb2djr84X/9feJg4iIjYaCjZeVm5eQg4WHi+3o85OQi4aFgoSDhoKFioSEj4yIhoyJhIyGi46Qi4iFj42Jl5iPh4uRkJOrqpSYl5CNi4qMipWdnJ6dkYuLkpaSh4yToLKAqbOuqKasrKefp6GqsLq7t72to6CcnJSblIuPhoqEkpKXnJ+Vio+Qio6Vj4yBhIb6gYqKhYiIjYmNkYyKj5+co5iTkpKGlKipsK6emKCktbqkk6nR6uO6qK+hkpSYmKGdm5OSpaydmaGZp7erk5uak4mVmJubl4+JjZemopaTiIiAcXyDfXF0doGHfHVsZnJ2bHaOnY2Vl414dXJqa2Vse4ybpKKpp5F1ZmNvaGl2en6IiJOfkXpqZGhve3N/gXd6dm9teWtpbISgqpihxqiRjoN1bmBqbnZqT1ttcm9oaHR0dG50dIKAhayqjXJeaWFbXmNiYF1bVlRWWl1KSUWIf4CAhEdRVVBdYWRxg42EjIyOiXptamJXUVBTWVRQUE5VVFhZZ1KSTUxVU5ORlIqWT1Jcan6tZVy/qZqAYW5oYWVwc3eUlImFe3Jua3Zxb3h6dWllX7xlXWR4f21tgW9paGhzgYiHhYJ4dmRcYmtkYGJgaW5mYWJrnY9xcG90b3d5hYWAdXiOhYyQkntuY7SgnZ6cpcBvdX911L+6XVRWqZuhoqGipFVZq61maltbX1pWRoJGREVETE1FS1JRVVxVUIhcVFhpUmxiq3lXgYGNk1CRmFSekYqOlahaoJSKjpVQVldTVV5cWVKUnY1wcHOMa4eiipOJj5GQllBTUZ+gnVZsbl6AWVOPlJ+JkY2IjYqDjpZTmpmRnaBcZ19bXV1dYGhbW2FoYqGjWVVPo5+NkpeZUlWhkZeKkZ+lVVNSWmJmY2ZZrFpYo1eonZminFRWVpiTV2Ouo1dmZGBnaWlmumNvZmFjtLRktq2pop2Uq1tjXFaXkouPjpCLnlBTVFeMnZGQhVCAUIeLS1NRk09OU1FKS1BKUVFSUk5QWFpfXllbWVVQWlpQnZ6npp6al1VaXKpcZGJiaWxmW1ZeXVxeW1hUV2FjW1pdV1heY1xhaWxcV15XVVJUYmFfYGFoZmhhWExSVFNQV1hcW2BlclxdY15XT0hFR1VRSEeHi4mVWWdqYUtUTEqAT0dJUE1VY2liXVFKjE1WWFNPTYaPVmFYn1irk5mckZeqpKOnqlmqXGNkYWJlbHNxasOsX2VpcW1ZpKdbXaKBfoyQp6GlmZWTuWx2X2OjqlxiXlleY1xcW1VcaGljX1WUoVVQnFefVaGTm5ujUlOjoJyTmlhYWKiknJhVU7NpZbWAs56dVauxWaGVkJSXh5KUh3+EhIaVrGBht8BjYmVptqebnJmvsGGpnZ1cVltZW1FNVFtZYF9eWF1cX5ucpWNiZGNkYWJcWlVWWFJRXF1aVl5eWmJeYmhpY15aYFtXYWRmXWRqZ2d2dGVpZ2BbWVdYUVRYWFlZVFRUW11ZUFJWWGOAYm9sbGtsdHFpdG10eYB/d392Z2dnaGVrZ2JmXWBbZ2VqbG5nW2BiYGRuaGVcX2CzXmRlYWdnZ2Jla2VgaHdwcWVeXGFXaHl4fX9tYXBzhI55Z32lurWNe4NxYmRpZ2ppbGRkeH9xbHVwfol8bnV0bWVtbXF0bmliZmx4enJvY2WASFVYU0dJSlRZT0c/PEJEPkdXY1ZeYFtGQ0E5OzU6R1VeZWZrallBMzA5NTY+QkdQUVlnXkg7ODxCTklWWlJXVU9NWk1ISmF3f3F5lHxmZFtQS0NMUFZSO0ZWWFhVV2FiY1tgYG1qbpCKcldFUUhFSU5MTEpJREFER0Y5NzRjW1+AYTU7PjdBQ0RNWWRfaWlubWNaWVJKR0VFSUNAREFHR1BMV0V7QD9HQ3FtcGd1QERNWmaFTkqdj4VtUllPRUdVV1lqaWNdVlNNSVBKSlRZVUxGQHpEP0dVWUtNX1BOUVBWW2VjYmFZWkxJTldOTU9NVVZNUlJVemxXVVJVVGBjZ2SAWV5uYWtubVlORHZkXVpdbINOVFpNh3h5QDk7c2lud4CEikdKiYhTWk9RVFBOQXlBQURBR0lER0tKTlVQS35VUFJiTGBUk2dLb3SBiEmFj1CXi4KDiZRQm46IiY9NU1VTVVxaV1CSmox0dHSMZX2XfoV8hYKCikhKSIuKiEpeYFKAT0l/hJGAh4eEiIV9iY5MkZGJk5ZVX1hWWFdXW2VYWl5lYaKoXVtWrKqYnaOjWFysnqOUnaurWFZUW2RlYmldq1lXpFmnmpmgl1RUU5aUWGWrnFVlYVthZWRgsF1pYFxesatdq6aknpmOpFlfV1OPiIOGhYqGlkxOT1B/lo2Lf02AS36CRk1Lh0lHS0lDRUtFSUpKSUVFTVBRUk1PTUlES0tBfn2Fg3x9dURJSoZJU1BQVltUTEpRUE5RT01JTlVWT09TTU5TWFFSW2JPS1BJR0VIVVVTVFdeWVtTTUNHSUpFSUxOTE9TXUhKUExJQj4+QlFNQ0N6f3uFUV5eVURQSkmAUEtPVlJYYWVeW1BKjk5ZW1NOT4aKUFpRik2WgIiIenyNioeIiUeISk1OTE5QVl1eUpOAS1JVXVlKiZFQUYxxdIWHl46Lf4CEp19lTlKQmFNZXV1laWJhX1dYYGRhXVacp1VOl1adU5qRop2eUVOkoZiLlVlaW6qhmJVXUadcWayAqJWUUJ+jUp6UjJCSf46UgnV6eHWEnFVUoahVVFlcmo+BhYOUk1GUiolSSU1KTEM/RUtITUxMRUpKTXx8glRST0tPTU9KTUpHSUJCTE5LRkhJRUtHS1BTUE1LU09JVFRQSU1PTE9fXVFRT0pGRERFQUhOSkpKREJCSEpGPkFESVSAT1hVU1JUWlVNVU9UVl9kWl1USEdGRkNLR0FFPT86RUJHSkxGPUFDQkVNSEdAQ0N5QEVIQ0hHSERFSUVCR1VQU0pGQ0c+TFtbX2JRSVRXY2dWSlp7jIZkVV1NQUJFRElGSENDUlVMSVFLWWJYSk9PSUJJSk1PSkVAQ0dUU0tHPT79eoR5onoBeYR6hXmGeoJ7nXoBebd6h3mEegZ5eXl6enqHeQR6enl5iHoBeY56hXkGenp7enp6hHkEenl5eoZ5AXqFeYl6h3mCeoh5Bnp6enl5eYZ6jHkBeoV5jnoFeXl6enqGeYJ6h3mJegV5enp5eoV5CXp6enl5enp5eYh6AXmFegN5eXqHeYR6iHmEeoV5CHp6eXl6enp5mnqHeQR6enp5yHqEeZR6AXmGegd5eXp6enl6i3kCenmKeoJ5hnoEeXl6eox5hHqCeZB6CHl5enp5enl6hXmCeoV5g3qEeQV6enl6eoR5BHp5eXqPeQR6enl5hHqHeQR6eXl5kXqDefJ6AXnQegICBACAhouFhPbwjoiQlZ6to4qiysWvko+jt7WqsbK4s6WfqrC7nbXLxMO9s7CurqykpJqNhqfHyrulq7Oio6OxvcfRy8LU1sTt0ueOifqBrKPoyayopautp4iMkJWak5Sbp62roJ6blKaxs7evrKCRp6ippKyipqGnsKOmpJyhn6S4sLeAxtCIsqWYlYfUq5mVmaWmppuIioX/+4GPlJOTjIOE94KVk46Ql42UlpSLiYuVnI+OgvOC8fX8+4ONlqW0153Ao46F9bKWloyJmJ2gopGf27WZnpKPs86tk5OXj42RjIqQoZmsraassKmmnZGTlJSL/IKOkI+pscStjISVk5aVjoiAl6Cfna+nm5KMkZSPhvmB/fv09v2BjpOLmqiznqPR3aaG+P6MjY6Ni/+DiISPlpCXhfeFkIf984SMiouKgoiG/t/WzNTZ79zo4M/unq2B187q/IDr6+jo5fL06/Pq9P+LgpCUjIbn6fyBhYT14ODi2+vo+/D4goqNlZ2lopeWm5iAjY2Oi4L/6f2MmpGK9vCGh4qHge3e6927v8HG09zb5/WB+v2VsKWgmJD8gYSJhYD+3cW88LTIvK2di4eBhfLy7+rY4e3v5uWAhoT064GHh4D3hYyE7vny74KQgvnu7s7F0NjZ3ff85enu8vDo/ISD+vXu4cPD3N3QzrnH4vju8fyA9feCgfT99O2Bk4zv+4eAg/zzgoyLioiFiIGGhoaI+vqA94CIkJeUjYyLhpSdn4+Mho6VlJ+3n5Gfn5GUjY6WqqioyMXEramjq6SosqilmpaVm5qdoJCQlJyqlo6H/omDk6qlq6y0saeVkZWGhP2G84OLh4KFhIaPn46FgoeEhYSA+PyCiYmNjYX+9fqJiIiHlY6JhJChnJSUh4Dp+o6jrbWpm5qMgvr68eT+gYuMhYqNmJqRjYTsgv7y4OX2gfLp3ODz7dTW6ICQnJuqop6RkpahrrKtoq2oo5OVkIySiYaAhIuWj/35io6C7ePp5ufv6tzM1+fp9PqLkY+lhv7249qA6/SC6+PygYeMgevy5OCGjY+Pkovs7PPw9O7k+4mKiIGMiezw+f+EgfmOj4qFgPmChIOJjoyCoY2Si4aBjYqWpY2DgoGA9IDx8IaAh4uMlYiBiJWfl5iNk5GOkJibiO3+7/z2+46Lmq67ppWdmpmTl5eWmJifkJCXlZyalJyUkZqAj4mQmpSfp6mrpJujk4qLg4+ZmpKJiICMlJGQm7CwtLqvrZiWlJSRjpmjnJ+fp8K4pZ+ZnaGRho6GgYL76efr4vqVkZKEg5+pppiVlqCioK+ysaqlr7Stm5OTlJydmJ6cl46UkpOcpqewubqytL7Ax8expKKajIKDkZOGh4SEgoOAY2lfXLCvZ2NqcHiFemh8nZiDamt9ioyIiImRi352foKKboSalYuGf358fXtzcWZbVnWSlop2eoFzd3V5iaCnoJqlpZS2o7ZtcMZjjYiqkXh1c3VzalZcXmBjYF5hcHd3bm9uand+fH1xbmJPX2BhW2FYXVphamFjYl1hXWB0bnWAgoxeem9lYlqMaFdSWGRlYl1QT02bnVBaWlhZVE1SmFFhYV5ia2NmZ2NVT05XYVpZUpJNkpeip1ZgaHiIn3OQfW5oxYxyb2hpfH94b2F4tJyBf29riaOEY11nY11gXl9kcmt9fnd1d3BzbF9cWVhVpFphYGB1e45/aWJyb2pjYV2AaXR3eYZ7c21odG9mZc9w1tDHychpeH95hIiDa3ittHpXpadaYWNjYrNaVEtTWVJNRYtKUEmHe0ZPUlRRR0pHhGloa3J0hnuGe2uFW2lLcmiAjkuLh4yNjp+ejo2FjZNSTFtbU1CHipVMU1KOfoKGgo+QopmcVF1gY2lwbmZjZWaAXV1bXlahjJVXZWBVkI5SUVRRSoqEj4JnbG1wfYWDjp1UoKhnhn98c22yW2BfXVmykHlrlX+ekH9yYVxXWJ2foaCTm52qpaRdZWKwpmBlYVy4aGxitcG8umV1Zr+zs5WQmJuanrCwnaKrraugsV5gr6ijmX59jpGHgW97j5+XmaSAm5pTUpaemZVSYFuIk1RNUJeSTlVUVlNQVE5RUE5TlZZOk05UWV1aU1FRT19nZ1xaWF1hX2x+aV1nZlxcV1ldamhngoWFb2VmcWpwfXNvZGBcYGFgY1VWWl9qXFVOkVFLWm9qbW13dGxaUVVOUZVTjkxOTEhIRkZNW01HRkpGSEiAhopFTlBUVE2UjJBRTlBRYFxYVF1saGRhV1acrmh6gIN5bWxlXbGup5+wV2drY2ltdHVvZludWKuonqKkVJyZkZqwsJeQmFdncXSCdXJpamZmbG5uanFtbWRpaGFdUVVRUlpiXamiWWBbqZqTkpSVk4mAf4WRpqheb3Z6V7CzpJ2Asq5XnaC3XVlbWaSln51dY2hsa2Kgq7ixu7aks2BaXF5taayuucNhWqddX1xZU59TVVFTVVFFYVhjX1tXYl9oamFdWVtguGGvp1tSVlZUUlNSV2FpYWRdYWFgZGtsW5SfkZubpGJgbX+JbmBlYV9ZYWdgYmdqVVRXU1VTT15bV2CAVlBWYFpiZmlpYl1mWFRUTFllaWZeVlJdZWJgbH96e4qAf21saGJdYGhuaG5sb398cG9tam1nYmtkYmO/rKeon7RuaWtfXniAe2poZ25xbnx/gHtxfIR4a2RgYmhpY2lta2NqZmRueHiHiYiJkZqbnpuCent0ZVpbZ2xeYGBhXl6APEA4N2VcOjg+QklVTT1QZmJPOjxKVlhSU1FVUUhBR0tUPE5fW1ZTTU1LTUxFRTwyLkljZ11KT1dJS0lQXG1xa2dydWZ5a3lGRohIV091bFhWVVhYUkBFSEtMSUlKU1hbVFZWUl5kZWdfXlJBU1ZUTVJKTElOVU1RT0pOSktfVFZrXWhEVE1GQT5oUUVBRlBSU05CQD12dj5HSEdHQz1Bdz9MTEtKUEpPT01DQEFITkVEP3I+c3eChENHS1hnfVlmV1BJjmZQTUdGUlVTUEVXhmpTV09KY3hdREJHQ0JGQ0FDT0lbYVdVW1ddV0mER4CAQklQT15hb2NOR1NTUk9MR1FaXFlnX1hTTFNPR0WISo6NiImKR1BWUlthYlBZfoVcRIWJTVJTUE2LSUZASE1JSUF/Rk5GgXhETE1NSkNGQ35lZWhtb4F0fXBfb0ZSQGpkfY5MjYuSkIiSkomPiZCWVE1aWVRQhYeQTVFOjX6Dh4CDkI+dkJFNVFRXYWZhWVdaWFFTUVROloKLUV5XUI6NUVFUUkyPhI+Fam5ucXqCg42aUp6iZH51dGxnrVpdXVtZspN+cZt+mpCCdmZiW1ulp6eikpiao5+cXWFdqaFbYGJbq2JnXqy4tbNeaGC6rqmOjJSWlJ+srJ+ipK6uoa9bXICqpZ+Vf36NjoJ+bn2OnpaaoJaYUFCRl5GLTFhUf4pPSEqKg0dMSUtJRUlDRkVDR3x8QHlBR01STUdFRkRRWFlMS0hMUU9abVpPWVhPUEpNUF5gXHd5eGJbWmReYm1jX1VRTlFSUlVJSUxQWktEPG5AOklcWFtcZ2dfT0lOREaBSIB5Q0VDP0A/QEhYS0ZGTElJSYGFRE1PU1VQmpCUUk9RUFpTTkpSX1pXVktIhJVVXmFeXVdVT0eHiYR6iURSUUpRT1dbVk5HfEeLin+EikqKgXd8i4l4doJKVl9fbmVjWlxdaHJwb2lyamZZX19ZW1JTTU5RWVmjm1NbWaSSjYyQk4CMgHR5hZOlpVxrbnNRnpuTjJ2YTomNo1JSWVWan5WQVlxfX19YjpeknJuYj51TT1JUYFqYnqOoVU+WV1ZRTkqNSUlDRkdDOFJHUUxMSVRSWFxST0tLTJBPkI1OR0pIR0hIRUlSV01QSU1MS1JWWEl1gnV+en9NSVNia1RKT0tJRIBMT0tOUlZFRUhESEZBS0ZCSUE9QUpES09SU05NUEI9PjlBSElEPjk0PkRAPkhaWFpoXl1MTUpFQkNKUUtPTlNmX1JQTUtPRkJJREFBfW5rbmV6TkpOREJaYlxNS0tQUk5ZXFpWTlZaUEVAPUBFRUFGRkQ+RUBCSlJRYGVjYGRsbxFzcl5UU0xBOTtERjs8Ojs4OoR6gnnBegZ7e3p7e3u1eoZ7jHqCeYh6AXmSegJ5eoR5hnqFe616AXmdegJ5eoV5jXqCeYV6AXmIegZ5enp6eXmIeox5g3qEeQF6jHmGegZ5eXl6enqKeZB6g3mEeoJ5hXqNeQN6eXmGegF5hXqFeYl6inkFenp6eXmEegR5enp6hHmDepJ5gnqTeYJ6hHkKenp6eXl6enp5eYx6BHl5enm7egF5j3oDeXp5kHqCeYZ6g3mPeoJ5iXqFeYt6Anl6hXkBeol5nnoFeXl6enqOeYV6hnkEenl5eYR6hHmGeoh5hnqEeQN6enmFegF5lnoEeXp5eZV6hnnXeoZ5v3oCAgQAgJear6eVkpSjur27v7euqsjU8+vSs5yVkI+alrnngOG/q7W+0LSkoJWWmZ+go5iPiKCdo5qkqKukjYuWpaantcjV8pKNgb6m3s3Ki8aFj5uxtKOfmaKmn6GYmpGIg42foqGnq7u/waqpub2+uaiorLLBurS2wNrd18/BubaulIOCgIeLg4yNlZCWoK2mqJeK/4KJhYiQmZ2IipSRl6u4ubWrpZignqecjoCNjYWBh5SHgYKA9/iChPyDh4mYuvmI1a2dm66xraKfpqqqpaSdlJaum5OTioKEjZGYsa2qsLK1trSjo6Kvur2unaSioJKPg4P8/oaNmJmQkpqahYGHhoLngImcl4mBiYyLgoOBiZCO//H9gP3v/vaCiP+CgZWPjY6WnZCHi4GA7ebx8vzs/IWKg4eLioj264CH+JapnaCvkor99vaAgf7wgoKI+4meoZWKkpaGiJqeoJOTjITw8u+A+ujl9Orn5vaUioWBg6DL0a+bl5+UiPrtytrs6eT2hIWLgImVn56YoaCZl4T+/v73goL16s/IwM/W083bgOnJxry9wtnf2dDXz8PByOHogfv359jLz+fl3OXQz8jQ0MG8wMvh+oOMkJaciJGa9PP4+fL7hoiEgOrb08jJ29/Suay/xMrS3tbHts/i29bl6uvf3tbRzc3f3tbGx9Pf5/X3iJShgKGXj4uA+fT38PaIi4iEhIaJiYeChYyFgfv5gIWMkqCll4mGjpeWk5abpLG1u7S6r6OcnqKvtJ2IipKUl5KXoJ6E+pSXlpaVjYKGjJWbpaKfj5Kv1dSppKahpZqP/4CQhOzk0tz19IKNkKCsp7PSzN3KxsSlsqynnY2Hh4yPk4H+gPr+gIaLiY+Pmo6IjoWTl5COjZacjoyZgYP7gomZnaKjnI6MkJObmZGQj46Qj4SB7PDt7P2HiY+Dgfr6gvnw6dTAvr3OgJuqqJ+L8f7//vf+8v6AgZeXnZeHhoWLkY+TkJSIiIL7gZCVlpWfo5eLjIiHh4WB6snMzduBnY+M+vX/gPTs8fn519uAhYXy3u3k84aB+/H49/n2+oD6+u2Di4Dh5OLy6+rh5dXEzNXU3t3c3u3d6oWMkZKYj42NiICE++7zi43+hP3+gf6B7f6OjIeHhYSHiI+XoJmcmJmWlYqCgOyIjo+UjYuai4uTnpidn5STlJKQlJSMlZOPiIOHiYqRgJiXioOHiZKWlpaZlY6ShYOJlJyZkIyKjpOkuauupJ6ZmbKku7fc3rSaoaCWo6aVkJGJhY6Uk5majoiMiYSDh4aRnJ2ft7u1qaCgnJmfop2gmZGMiv2AhYKLjYqRmpmck4+Um56hmp2cjo+cnqalnJunpKCkq6qViImGiICHiJGagGdrfXJhYmVvgYmKi4N+e5KbtKSagGxmYmFraYapXZ6KeoKLnYV3eG1tb3V4f3RtaXpzfXeBhIh9aWlxenp+i5ipwW9sY4p1oJmWaJJZYGh8gXJvb3h7c3dyc2piW2NuamZpaXV1eGJhcXNybWNnbHF9dXJ1fpSWlY5/d3JnTkNDgEhMRUtMVU9UX21jY1lQlFBVUVJZYGNQU11cY3iPiYV9emtuaW9hVEpVVVFQVGFVUlNSmZ1XWq9eYWFujMBpo4NzcYCIiYF/goJ9d3l3dniIe3V0aWBeYWVxiIF6f36Eg316fHR9gIB9dHBmZVhaVVmsql1fX1xVYXd1YVpfWVaZgFxrbGhiZGRlXl5gZGNm1sjPaMzE18xpa8loZGlgYWhxbWBbYFVUn5aboKqYm1BTSUlNTk6Ef0hMhVhtZmNuVlGOhIdJRpCMTExPjVBhY1ZPV1pPUFpjZlhaU0yKjoxMloODkYWBfopbU1JPUWqTl3hkYGhgV5mObXiIhoKNTktSgFZbZGVhbG1lY1ShoKGdU1SXiXBqYmxzdGt1R4ZsaF9iaH6GhXyEfHNwcn+MU6OfjYB2e5KVkJmIiYKDhHt4fYmbs2Nqa3J3YWl0ramytquxZGhlYbKhm5SToqeYin6MkZuaopyQgJmsoZunpqWcm5ORi4iXk4p6eoGNl6KhV2BqgGxlXllPl5WZkJRTUExISU9WVlBKTlJMSoyRSk5UWGduYVRSWGFiXl9ma3V8gHqAdGxmZWx5eWJOUFhcYV5jbW1Rj11eXFxdVk1OVF5jbGljWVx2mJJua2tlaV1TjEZHRXt9dXqOjkxWWGRtaXKGhJSHfX5mcG1tZVNOTlFSVUiPgIyNR0tNTVNVX1hSWFRdXllXWWFpXl5tVliiVllia3JxbGRgYWRqZ2VmZWFlZFhZnp+Xl6NbX2FXU52aU6amno18dXGAWHF5dnBdoq+rn5GYkJlNTl1gaWpdW1VUW1tfYGNaXl2tVmFobG1sbWlcWlxeWFFQl317hJ9fbVxhrqu0gLWvo6iwnJ1ZWViklJyarV5asqilqLCzv2G7wrlhYleXm5+yqq2hpJuLiIR7hYaFiZeLklRYW15cVFZXVVNZq6eqXVqgWairW7ReqrljWlNTT01SVltjbGZpZmdnZ1tTVJZUXFteVFNjVlpkb2lqbF1aWlpXV1lSW1lXUUtSUU5VgFZXTUZITVhdXV1fW1VWUFBVWWJiXVtaXmR4inp/d3Bsa4F0ioWgpIFmaGllbnFjXWBbWGFpZ2trX1pdVlNUWVhkbnFyhIiFfHR0eXZ5fHR4dG1mZMBfXllgX1xhaWhqaGJkaWpqZ2poWltkaXFwZ2Vwbm5yeHRhVVpbV1FZWmNrgERHWE4/PDxGVVlaXFZSTmZpfXJoUUE8OjhCP1h2P2VXSVBYaVVGRT5AQUVFSkI9OkdAR0FISUtDNTU8RkpKU2BuekhHQ1pOZ1dQQ2U4QEVZXlNUTlZZU1ZTUkxGQUdRUU5TVV9fZlNTYF9fXFJVWV1sZ2RlbICCe3JjWFJJNS0tgC80LzU3QDpASVZPUEU9bztAPT9ESk48P0lITmBzbWhjXU9TT1dNQzlCQz87QE1BP0FAeHg+P3M8QEJPZohJcl1QTltfW1RTVlhYVldQTEtdU05JQTo7P0RMYF5aWVhdXFZSVlJbYGNhXFlQUkhLRkV+gklLTU5GTl9eSkVJR0d+gEpYWFJJTE5QSkhHSkpMk4iTTZaKmpJKTI9LSFBISVBVU0tKT0VIh31/foh6gkRGP0BFR0h5dURKgFRmYV5pVE2DfYJHRpCJS0pOjlBYVk1HTlJKTVpiZlpdWVuYlZBOnYeFlY2IhZJdV1NPUGSDiXJhXWNdV5iNbHeGg36HS0lPgFFWXl5aYV1YWUuQkZSPTU6NhG9qY211d3J9TI1zcGlqb4SKh3yFfHNxdIGMUZ2bjYF3fJKWkJeIjIKEhH14eoSUp1tkaW5xXmdyqqSssKawYWNgX6+dmJOToKSVhXuKj5eZpJyRgpmqnp+rqqmanZmRioeUkop7eX+IkZuaVmBqgGheWFRLkIuOiIxMSkZCRUlNTUlDRUhCP3h8QEJIS1heU0ZFS1JTT1BWW2VnbGhvY1pUVFhlZ1I/QUlNUk5QWFdBc05NTE1NSEBBRk5SW1lTREZefnpYVlhUWVBIeD5BO2ZmWl9ybzxERlJcVl5vboB3bXBcZWVkWUpFRktPUkWNgIuQSEtNS1FTW1FLUEhRUUxKTFJbUFBfSkyLSUpRVVhYVk9MTlBYVE5NTElMS0NFc3RxdYFKTVJHQn17Q4R8c2lZWFZiRl5mYFtMgYuNi4ONhY1ISVdXXFtSUk5RWFZXVFRPVlSZS1hiZ2VjZmNXVVRUUUxOl315fZNXZVNXm5mkgKCYj4+VhopOUVObkJiQnldWo5aYmZyjsVmhpJ1XV02Iioqcl52VlYd6e3hwd3V2e4l9g0pMT1BQSElKSEdPlY+SU1CRUJSVT5lRkJ1WUElIREFGR01TWlNVUVFPT0U/QHBESklORURXSElPVlFTU0dGSEZCQkM+RkRDPzk/QEFHB0pIPTg8PUSESHVFQEI6Oj5CSkhEQ0FESVhpXWBZUlBPZVZqZXuAYEpQUUpSVElERkE/R01MT09EQUM+PD1AP0pSVlZlaGRaVVZWVFhXUVVRS0RCej4/OkBAQENJSExIQkRLTEtJS0g+P0hKUFFJR09NTFBUU0U6PDw7NTo6QkmdegF7pHoJe3t7enp6e3t7w3oBeaN6BXl5enp5hnoBe7J6gnmNegF5jnoEeXl5eoR5A3p6eY16h3mHegV5eXp6eYd6C3l5eXp6eXl6enp5kHoEeXl5eoh5jnqIeY16hHmCeop5AXqReQF6lXmIeoZ5hHqpeYh6hXmOeoJ5p3oBeZp6BHl6enqGeZl6g3mXegF5lXqFeYV6A3l5eoh5hnqIeZJ6AXmPeoV5hHqKeYN6hXmCeod5B3p5eXl6enqUeYt6Dnl5eXp6eXp5eXp5enl5lHoBefN6AXmregICBACAu8e+xMnCtJuVrdDLo5agloWMkv3ogJ/L2dm4rLOqkoydn52ZpLK9v8LBwcW5sqWhoJWKhPT5+YKHlqWnnqOroq6/58q7r7izr6SemZGKh4eDhJmpmJKOjpaZoaelnKGjqqemqqqrpZiarKustLnBxcK8srDE2uHcyrKYjYiBjZeAoLG1qailn56coZSXmpOfsq+oqaafmpCJiJOel6mntrGxqaell5GPjYD8g42aoJmOh4SAhI+ViPTfgJWj0+7fxrCUkIbx9YGOlo2SjpKSh4CNkJ6nrr3a2+rbvM6rlYeJj5mcnZWVjYKGjYeHh5Tji7+RgoGOk5CPiIqHjJWUh4iAkZCMjIWHjoj2hYP4goGGgYuXg4mE7uLo+fiD/oCO+vaC6vyIgPSC+Ors8ICB9oGQkouJi/jzgP744Ob04+bw3ePz5P+AhYLh6vX9goiCgILy/4uPhoeEip6fjoeD+fHn59vRz9LX54GIhob7/4SFg4mSmJGGh4j86f76goOMipOAnqKmopmXkY6Ni5CMkI2OlJaXnbevnpuXjoyJ/4WGg/b6gPjn7efi1OT1/4CF8YWQ6N/n39PV3trd9oOKkICAg/6J4+nvg4773tLf4N/f3MC1uMTV08vN3dTq7ur29oCRmpOltr+mjIXv39Hk5ODvgPbl4ubl4ePt5dXT4efvgICA8N/m4NrS4urv7fyAgPHe3tja3PCFjYaUmqWbj4T5i5uglpKFgpSdnZmeqK+km5SMh4OA/veChoiJl5SHgImKk5SNl5mSlJeZq6qflZGagIeclY2Lh4eHipeVl5OI8/X/hJeWjZSan5GUmpORi4yGiPWAh/+BgoCJg4SCgImMiYmAjpSXkYqCio2iq66kmZKIiY6PhYT79YaMjpKSkZyhq5mZm5mhlJaempGNl6KYjIqNh5SNmKizrqicmJCNlJmYi/+IlqCtp6ukmp6hmpOTiYqJkZOQjYqJi5ubmJibmJKNhvjygIyMg4KMkIqOlImI8duFjPne6/jw5OTi84Lo8fuAgpGqpqKllpCKjYPg3vHv6vb0+YOOkIb/8N3b2tDV4PXi293DyODq0sXQzMPR5uPl7O2DgPDi7eTf/vuCgIX27u3x6oSI6N/X5vuLh4L25vXy8/WDiZGdnpGVkouRm5aOjITzgYeFhISCiY+XloaChpaXmpGOlYmMkoeMiYiJlJWAjoyHjJSVjZKPjJaSmqCinp2SjIyOlZKTho6Dio6Njo2MiYaJio+SlZONkYeF9uzy+v34jo6doaOajoaFiIqHjpKLiZONiYiMjZCXmpKQkZCJi5CTkZGKh4yYm5eYl4qQgIWZppmSl5+ioKSfnKGdp6Scn6CfqrevoKiypaadl6CAgYuEiIyIfWVhd5KUc2duZFhhZ6iaW3KTpKySg4qFbmZ0dXFueIqTl5aSlZqPg3t6dGhgWaKnplpcaHd+c3Z/doKTvp2Oi5eSjoWAe3Vua2liYXF4aGFaWF9gZGVkX2RkZ2pscG5wamBhaGxyeXyBhYF7cXGCmJ6ejnReU0xHUlmAXm1yaWpoZ2ViZmFiYVhdbG1qaWlkXVhWWmFraYR6gnx7dHFqX11aWU+cVF5qcmxhXVhWWWNpYKOUVWZ2pbWlmYhtaWGwtFtjbmhscHRxZ2BxdH2BiJm2uM60kaaBa1pWYnBwbWFkYFRUV01RVmSmZolnWVBTUlRfYmhjYGFhWl+AXFpgaWNhZV+sYV6yWVRianZ+aHBtvayuvcBox11lqJ1Vm6hZUZhSn5yfnFJPo1VfXFZUU4aASJGOeX2KeXeBc3aGepFKTkx7g4qUTE5JR0mBj1BUTk9MVGViU05LiIR9fHp0cXZ7ilJXUk+Ni0lKR1BYXVZLS0uFdImFSUhUVl+Aam9zaF9cV1NUTlFRVlRXXmZobYJ2aWVgV1NSlExPT5CRS4+AhoR+cH6Pm0tNl1ljkoeQj4OFiYSJpFtgYFZZWqpipKSqXmiyl46ZnJ+cl4Z7fY2dm5SRn5musay0tWFueHaBj5aCa2SzopampqavXbinpqypo6WvppeVoaewXVuAsKCgn5yRoKivq7dcXKqZlZCTk6NbYlllaXNqYViiX25wZ2RWUmJoZmNocnlrYV1WVVJQoJtTWV1cYl9XUFVYYmJYYGJeXFxdbG1nW1ZhR0xgW1lWVFZVVGJnZV9XlpecUFtbVFpgYVZZYFtYU1NPUYpJUZlNTkxSTUtKSUtMTEyAUVZaVE5IUVNlbGthWVdUVldZVFKXj1FVV1tdXmdpcF9dW1tpYGJqZF5bXmNdVFRXU15aYm10b25qZ1xcY2ZkWJ1UXWp4dXt4cW9pZV9dVllXWFpZW19gYWxkXGRoZGBbWKuqWFlYW1tgYF5hZltfpotUVpySnKWfnJaOmlecoKeAXGuAeXmCc2ZcXFialZ2hoaOns11iYV64rpydo52doauVio6AkJ+kjYWTkoSHjYqPlZNUVJuNk4l/l41KSU+VmpuioVhcmZCIlqdjY1uqnKmenJNMT1VZXFlbXVlcY2JdWlWaU1lZVlRRV1daW09MUF5iZ2JfYVNVWE5RU1FQVlWAT1BLUFZXVVpWVWBbXV1eY2ZdWllYYF5bU1xRVlpYV1paWVhbWV1hY2FfY1hZo5qeo6aeWlpnbW1iWFFRVFVTWVpVVV5XUVBUWV9iZFtbXV5XWlpcYWBYWF1jZmRoZ1tjVVhndGxgYGltbXJrY2dkbWljaWllbHBsZW1zaW1mY22AYmpjZmljXERBU2xtTkNKQzg+QWRbN0xseX5nWl5YRT5ISERBR09ZXV5bWl1VTkdGQzs0L1FWVS8zPEdMR0xXTVVgfmtiXmhjYFlVUUxGREM+Pk5YSkZBP0RGS05NR0xMUVJTV1ZXUk1MVVdbYGJkZmJeV1ZkdXl3bFpIQDo1QEWASlhcUlJPTk1KTkhIR0BFUVFNTlBPSkNAQUhSTGNeZmJkXl1YTklFRDx3P0ZRWldMRT86O0RLQ25dN0ZQdYN7bmFIQzxobDpCS0NGREdIQjxISFFXXWl8fod5YW9RQzY0PUhKSkNHRkBAQjxCR1WUW3dURkFFR0dOTlJNUVJRTFKAT01SWFBOUk+NUU2MR0JMUFliUFlWjX+CjYxNlkZPgnxEeoZJQ35Hi4iIhkZDhkhTUUpJSnp3QoWFdXuFdXaAcHZ/c4tHSkl6hIuQT1BJRkmAi0xQSktJT2JiVlRRn5eMi4Z+e31/jVJWVFKVlFBPTFJaXVVLTEyKe4+MTE1XVlyAZWlrZF1aVVJTTVFQVE9RVlxdYndvZGBdVFJSkkxPTo6PSo6BiYeCdoaVnk9Sm1pklo+Vj4KEiYWKoFdbXFJTVJ9blpqeWGGpkYaUlJiXkIB3eoeXlpCPm5Woq6iwsl5qdXB7h5R+Zl+qmo2bmJeiV6mZmJ2ZlpecloeHlJWfVlSAnI2Ni4eDio6XkppOTY5+fHV2d4VLUUlSVl9WTkZ8SVZZUU5DQU5TUlFUW2JaUUpFQz88d3E9QkVDSklDPUJETU1FTU5MTU9NW15ZTUlVPkJUUE1IRUZERE5QT0tCa2twO0RDPkVGSUJHTEdGQkRCQm47Q4BCQkJKRkNCQ0VFRUZnS05QSkU+R0hXXmBXTkxISk1OSkqFekJERUdISFBUXk9OTExVSUlPS0ZDSE5JQUJGRE9KUFlgXVpVUUpITU5QR3pDTFZhXGFdVldWU1BQSUtLT05LS05QUmBdVldbW1FMSpGTSkpLUIRTgFdcU1SPeE1Rl4+bnJORjoSHSoaPmFJecmppcWheVFZUlZKbmZaanqRVW15Vp6KRjIiEipGciICFc4CRmYN7hYR4fIaCg4mIUVKUhYl8cId+QkFGgoOJlI9OUoZ+eYiWV1VNkIKOhIR9QUNKT1BJTE1JTFNPSUZBdkBFRkRCP0RGgEpLPzs+TE1RTEhLP0NGPUBAPj1EQ0BAOkBGSEVJRkRNR0xNT1BRSEVEREpHRz5FPEJHRENFRURDRURHS01MSEtAQXNrb3V4b0NCTlFSS0I+P0FEQkhKQ0BIQz49QENHS0tGQ0VGQEJDREdGQD9FS01KTk1DSTw/TFhPR0hPUVFVFVBKT05RTklPUkxQVlJLU1hOUUtHT5N6gnmfeoN58noBeY16gnmLeoJ5qXoBe5h6BHl6enmJeoV5DXp5enp5eXp5eXp6eXqEeQN6enmGegN5eXqNeYN6hHmFeoJ5i3qKeYR6gnmKeoR5oHoHeXp6enl5eol5BXp6eXp6inmGegd5enl5eXp6l3mKeod5AXqOeYJ6i3mCeod5iXoBeZV6gnmoeoN5kHoEeXp6eaB6gnmqegF5oHqCeYx6BHl5enqJeQR6eXl5i3qIeYR6m3mCeod5g3qFeYJ6hXmDeoZ5j3oBecp6hnnNegICBACAi4uSkIuKk6CjsbKgkYX/+/f2j5+hp7XU4t+8opmboaOWiYqKmKm2tK6bmZaVm5SUj4yE8v+D/ICDhYmBhIyVnqqzs6yop6OYj5KhpaqxrKKRjo2Xt+Tt4LqVkJOLkqGrpZiUjoOElJujrLnMyNnaxLa6tZ+XpKmxsa6cnZSKjIeAj5uxr6OZmpeTm52jmZKMj4WBi6C1ubixnPnLyMvK3oGKjYuMipePj4yCh4mPjI6UmJ+Tgvr/j5SM7+Dd4cXe9OLw/PLl4vyJiYeDhP2GlqWwoZ6Th/6IlJKSjJvOpq+K9s+qpqueqb22o5GJgpSwysOnmaGwt6ukop2PkpWPiYWAhoqOg4WOj5SYiJCTi5uoqJaHgICF/P2EgoGPmp+RjYmKgIf98fPw6fKD4+L9goiUjYuC+/aBjI+MkY+XmZSMjoeRoaWXn662oIyHhIqEiIuHhYeNgv376+jr5end6trE0ufq9NzN0tHY4t/Zydryg/nx7u7m3uOAj5CQkpSbnKuAq5yVj4eBhPKDh4uE+4aI/oGFi4iOmpaHg5COmaCjq7Gzs7Swvbmqpqaxq5+Vl7TFrJiOoamWj4uGhoHx7/n8+Pf2+oHxgIWD9+/w9/zu59/X7P+FipCIioqQhvTwh4OEhYeFgfuCgOvU1NPSzNvdz83Jy8DM0NLZ6+Pe4Njh9N6Az9j6+vfx7+rd2Njo6+Lx7ubp/YaE/O36gYWHiILq9P+BgYWMj5iWm5umloeA7+77gof49v6HjpKZkYL69fH6goD9iIHy3u38ho2PiYiC/oWIiIuKi5mpwdbphOTSv6ujn52ckImIgIWH/+7m/oGOn56jsaGPnKmxo5yipqGhpJWAl5uUnKuur73FwcS80uGdnqGqwauQhIKIgYeLh4mIioqHgvGCjYiKlq+0pZqclJacpLS1t7myrqeiqrm0rK2mrriUjYGEiIGFhZGTlZiZnY+ElZacm5GRl5OQjI6Pi4rr5oKRk5COj5OamYb4gIWE/YSB/+7uhYP49u6ChIWEiJSAhoKC+v+D/oGKkZSA7OHg3fH77Ojwg4Tz8PDq8PL58efj4unh6+bXzdXd6uLN0OLq2c7i7Ozdw7fD3/P6/4iHhYGEgfzl2+Dk7vn+9fXj5OTl8O3s8/yBh4yRjo6Ok4eA+oD/hIaI/oGB+YWNhu7xgPmGgoaKkoqDipSUkZCRjoWAgoCGlpOHh4eEjZOYmJmZmZWdnpmPj5CNj4n2+4CGhYP9+oeRlpOKioSHh4SLiYiD/4qVlpOOlJSQkpGJ/vSHjo6Plpybl5GMlpSIi4WJipWNkpyVjIuQlpucnaKuoqKwsbCtrKacnZ6UjI+eqJiZoaejmJKToqifk5CKjI6coZiAVlRZWldVWmRteHFjXlGSk5SUWmNodoacp6eIc2tsc3lrWltebn6Ih4NvaW5vcmxva2pjsb5iuWJlZmxmaG95g5CYmZCLiYF1bXB+g4eKhXloZF9ohKuvooNkX2JcYWxzbWNgWk1LW2Frcn2PjJ+hjIGEgGtgamxvcXFeXFdOT02AVWB0cmZdW1hWXF1lX1tVWFFRXWx7fH13YYpqZGlse0tUWFhZV2ZkYl1RWF1mYmVqbnZqW66yY2dhoJORl3uRo5aktqygm69iZGJfYL9pd4KJdXJsYrNcZWdoYWySdXlmuZl7dXlwdoB6aVpZWGN3kJR7amlwdnN4e3hrZWReWliAWVldWGFnZWVoXWZpXWZtenVoXl1gxMVkYF1rd35uZV1ZT1Scj4yGhIxPio2bTlJgXVpPkZBPVlNPVldhXVlXWlFZZGVaYW98aVhPTFJLTlBNTU1QSo6IeXl7d312gnZkanp7gGpfZ2Jrdnd1aXqRT42EgHpxbnFBVV1bWVlcVmOAZVhZVEtHTIVLT1lTlVJUkklLUVBWXFdNTVdTWmRsdXl1dXdzfHlsamhycGRaWXiIcGJdbXVoYF5YWVWYmKKpqKioq1mjVldToqGhqqyjn5qRorReYGZeX11iX66mXlpeYWJeXbNeXqiOh4qPhpOZjIqDiYOHh5CVnpian5qYn46Ah5alqq2lp6aWiomampGdlZOUp1pXoJGfVFZZWlWXnaJUVVhfY21sb2prZlZNjYyTTlOYlJtUWVxjW0uVkYmVUE6XVE6Ofo6gV1xdWVVSrFdZWl1cXmt5kKe2ZrChjXZtamxsYFpbVVhbpJKMoFJcaGdqeGhVXGlzZ2NrcGpobF6AXmBWXmtucX2CgIR+i5RgYWFof2pPSkpPRkhNSk1OUU9OTIhJU1BSWWtuYVZaV1lcY3Fscnt6eHBqbHlzb3BtcnpaV1JZXFhZVFxdX15fZVhOWlppb2diX1lZVVVVU1SWm1tiY2FhY2VnYlilVlhWp1ZPnpmjXFGlr51UVFlbXGeAX1tVo6hZuF1hZGNVoJCOiqKonKGqWVikpayrrazDxLOelZySlpuck5CUnpmLkp6Vg3qLk46AbGJshpidnVBNS0xQTp+PipSYn6WjmpaLkZOVoKCgqKtUV1JRTlBSXVNOl06bUU5Qlk5Ol1VbVJ6ZTJhUUVRVYFpUWF1dWVZZWlKATUxMXVtNTk1LVFldW1tfYF5jZWJWVFRTWFKKjklQUE2Ti09aX1xWWFNYVlZcWltWpFljZGJcXlxaXF9YnJFVWlpdYmdjY15YXl9WWFBTVmBWVV5aUU9UWFhZXmh1ZWBwcXJvbWlfXVpZVlddYlpeYV9jY1tbZWlgWVZTVlliZGCAPjxAPzw8QkpPWVVJQjhlZWhpRExNVWJ4f39pUkpLUFRHOTk6RFBaWFZGQkZHSkJEQUA7Ym46bDg5O0A6PEFKU19lZl5aWFRMRkhUWV1iXVRIRkJKZYeIf2dKRkhDR1JZUkpGQTY0QURNU11saXp8amBiXE1EUVVVVldHRkA6OzmAQElaWE5FREE+RURKRkQ/RDw5QE1cXltVRl9FRk9PXDpAQkFDQk9MSkY+REZLSEpOUVhNQnx9RkhCZ15eZUxbaFxlcGtkYXE/Pz4+P3lFUFlgT0xFPG07Q0JBOkNaRERDhnBXVFpTWGZhU0dIR1JkentkV1hhZ2FkZ2JWVFFNTk+ATUpRTFFUUFRdUVpdVVthamFWTU1Rnp9RTk5ZYGVaVUxKQUiGfHhycHdGenyKRklUUE5FgH5ETExJUFFbWlhUVk5WYWFVXWt4ZlZPTVNOUVJOTU9TS5KMfHh7dHpzgnpqdIaJlIB0fHZ+iIR+cICWU5WMiYZ/d3hFVlpZWVpeXWuAal1cWFBLUI5OT1VRk1BSkklLUE9VWlRLSVJOVF1lbXFubnJveXZpaGVubGRaWXODbGBYZ3BiXFlTVFCRkJuenJ6foFOVT1JOl5WSmqGVkZGLm6dZXGJdXltfXaqiW1pcX2FcW7BcWqGNiImNhJKZi4Z+hHuAgIaLlpKQko2OlIOAeoWXmpiQj4t/eHiGhn+JgYGFj0tJinqCQ0VHSER1e39CQ0RKTVVVWFNVTkM+bWx3PkFxdHlBR0pQSjx1c297REOASEJ5aHWDSEtLR0NAgEFCREZDQ05Za36HTYV6aFhST1BRRkFEPUFBeG5meT9HU1NTX1ZFTVxkVlFZXllYXE6AT1FIT1tdX2tvbnBscXtTVlVWalY/PDtAOTs/PT49Pjw5N18zPDo9RFdbUEdLRkhJTlpXXGNhX1pVU1xaWFxaX2VEQTtBRUBDQktNT09QV0xCTUpVW1VUVFJRSklHQ0Z8gUxTUFBTVFRXVkyNS05Mk01IlZSXUkmWopVNSUtNUFqAUU5Ljo9LpFVWVFdQmZCNhJWdlZiaUVKZl5ubnZmmpJ6XjpCLkpGOh4qOl5WFiZWNe3B/hoN6a2NrgY2OjUhGRENHRYp8fIWHjJGSiIV9gH1+ioeDiY9FR0VFQUJDSkI9dT15QD5AdT5Ad0NKRHx4PXtDQEJFTUdAREpJRkVHRj+APDw9Skc8PT47Q0dKSUpMTkxSU1BHRkZFSUVwdDtBQD92b0BKTUlDRD9DQkFIRkVBfUZPT01ITEtISkxGenBDSEhHS1FPTEhFSEc/QTo8P0g/P0dEPTxAREZHS1NeUk5aW1pWVVBKSEZDQEFITUVIS0xOTUVFTlNLRkNAQUJKTUmOeoR5oXoEeXl6eeJ6hnmVegV5eXp6eo55hXoBeYh6AXmHeoN7tXqCeYx6hnkEenl5eYZ6gnmgepp5AXqHeZB6AXmEegR5enp5q3qIeQV6eXp6eot5iHqCeYd6A3l6eqx5BXp6eXl5hXqDeY16CHl5eXp6eXl5hnqEeQV6enl6eoR5hnoBeYt6AXuOeoR5tXoBebx6gnmKeg95enp6eXp6eXl5enp5eXmJegR5eXp5hXqJeYJ6pnmGepN5inoReXp5enp6eXp6eXp6enl5enmpeoJ5hHqCeY56AXmLeoJ5wnoCAgQAgK6koqasqaWqpqWpoqCioJ+elpOgpZeOjI2NprSznJ6isbO4tq6ik4mHjo6Li5KWkoyRmJSPhoWD/PLh5oGFjo6VmIqOkpmUjIiEgJG5ssHHw7OztLq9vrGhm42DgoeTnqOgmJWKhJGapqi+wbmwrq+loJqfpra7vr2sq6y6wMjIgL2rsLKwq6mwspqVkqCkqK63u6+nkP7r9IOVo6mjloyA8Pbv4OXn7fWD+PWE9urqgYGBhpaRi4SCgfLt/oH2gP71goOLgujm9vuC//WGjJOUi4T+9fmFlaWrp6eoqrbFxL++q6Ojsa2tr663vb+nk5mcpK+xr6SbkYHsg5WrsqaNgIeNlpH27/uEoKGMgv75hY2HgO7l2uDdzuLu8P6ChIv+8oeD/fXl9/2DhYiBiIj13O7484D34uri5YicmY6GiYiTh/fr8YGNjoWEjZH66e7h4uDO1cvX1uLe1NHV6O/449Hb4+rh7/3x7oCMiPnx7fHs9vDi8YSRiIaA/IOOlYSEgICCh4yEg4iKkZegpZOMhIj369zf2t3e3urp3dXt84eDhoaJiI6YjYmMl5eVlJ+ao6SYlZeXmLKpoJmVjYyPiouD//6AlZ2fnpaUj5Hoz9/q4+3v3dng6+bY3NXW0ujn3uLg09XP1N/U3unfzMTE2sfK2NPDz83W8f2CgPbw9f35gPj064aK/+ns+/399e/2gvzs7/n78OLQ2Ofx7PHx+P+RmZSJ/ICIhfSAhICD8vSHgvjt7+7m9vj6+f3479zT1dvu+Prx/YqOkJafnpyakYiPl52cl6CejpKZmZWVjIuTj4qSh4qI+oGFhoWRm6eip6ulm5OQio6SkIuGiI6Um5eKgJGg7Yzepp6cnqSnsLWtp6utoa60q6unm5mdpKyuraqjqamkmZWYkpOalZOSnaSdnpmTkJKTmJ2boZubo6qhj6nCrKmpp5uenpaapaegoqGUioSD/oCEmJOWm6OopaOXk5ybj5aOgIyTkIWJjYP7goyUl5GHiYiTi4SOlZqakI+PgIH1+PyMhPvs9oL1+f3n4en45/aHhfX5/fj58djh4t/a7enr5OTq/Pn89ejm6+fY1eL3g/nz//Tj0N708v3+hoiOi4iIhPP08O728/fv8/b4gfr7g/Dt8v3u8PWCg5GQhoaGi42HgP378fz/+vr5/YuKh4aIj4yMiYmPjIGMk4uIgIOCgP+B9/iKjIyFh5WWlZWYoZKTjJSPlZSRlZOTl5CQlZaOmZOXm5CMioj6g42Ng4D9goSHgIaSnJiZjoCBhoWCiIKOkpKMlaGlpKOUmLPLvqWWj46EgoD+hIuWmJ+blpGZmZuRjpiWjpCXn6mlo6epnpiYoaSdmpqMl7Kbnqe3gHBpaGxxb21ybm9xbGtuamdrZmVydmxhXV1ddYOAbXJ2gIOLioN3bGdjaWpsbXV5dXFzfXlzaWhoyL+vsWNmcG9zdGdqbnd1bGZhXmmMh5SbnZCQj5Wam4x2bV1TUlVgb29rZWNZU2BpdHOGiYJ2cW1lY15jZnR5fHhqbWt5fIF8gHFja2xsaGpvclxcWWNobWxueHFsWpiMmFVjZnBwZV9WnqGakZecoatarqBYqKKjXFxdYWpkYF1bXKyirFimWLezX15lXKGes8NqyblkZmhnYV62srRda3l5dXZxcX2NjoZ+cnBwdWxxdneGjo9zYWpucXV0alheYVmgWF9vdGtagFdSWV6el5tSZmlcVJ6XUFNRU5uPg4eFe5Gbl5tOU16wpFpRkop+jo5ISE1MVlaVeH6DhkuQfoaEhlNfXFNPVFNcUZaSkk1YU01NVFeSio1/f3lnaWBqbnp1a2dqeH2Kemx0e4Z/jJeKhUlRTIR7e4J/i4x/jlJeUlBLjEhOVklHgENER0xEQURHTVFUVU9NR0mAeW93dXp6eICIeHGEh0hGTkxOTFBZUE9QWFhbWmNeZ2hbV1hbXXRsZGFfWlteWltUoqNQX2txcmxqZGeciJaim6CklpSbpKCUl5CRkKaqoKSil5qRmKCWpq6fkouHoZGPnJyKk5OaqbRhX7SztbuzgLSzrWVov7G0vru7srC4XrGrsLW0q56LkZyhmpybpKpjZGBcpFFVUpZQUk9TlJNVUJmKj5GNn6Cjpqmemo2Cg4yfqa2krGBhY2dvbm5rYlpfZGhsZnBwYmVrbG5uYmJqZl5mXF9ZmVBTVFRcZXFrc3l1a2NeV1pdW1NMT1VaYl1PgFNfnmKXal5bX2Rmb3NrZmptYmppX2JiWFpfYmxuaGdhY2NbUlJUUE9VVFlYWl5aYV9cWVhZXWFeYl1eYWViWHKLenR0c2hsa2Npbm5ra2RaVlVWo1BOY1xcYGducXFqaXBrYGhhU1xgX1heYlyuV1tcYWFcYFpfXFpiZGlrZGJfgFWip6pdVKukplWUl6SajIujnadZWqmrpaCknoqWmp2gs6aimZWUoq+3sKKbl5CEipmnVp+WnIp7b3mMkJ6aUlVYVVRVU46Ij5KfoKeiqqWhU5mYUpOVm6WZn6JUUlxXS0lKUldWU6WnnKapop2YnVpWU1RXXVpXVVdbWFBbYVpZgFdXVaRTm59ZWVlTUlxfX11bYFZcVlxWXV1ZXFpcY15cXF9bYl5kZVNQU1OOS1NRSEiPS0xPSlFcZV9fWU5PVVVSV01WWVpVW2Zub2tcXXOKf2dZUk5CQkODR0pSVV1cV1BaW1ZQUFdWUlNXXmZjZGhpYF1dYWJcWlxUX3dianR8gFlSTlFYVFBVU1NWUFBRTk5QTU5XV0xIR0ZFWmdlVVlcZWdsaWVYSkVCR0hHSExOTUlKUE1MRERDgHptbkJES0pOUkhKTFVSSUM/PEZkX212eW5rZ25va19RTEA4ODpDTU9NSEc/O0VMVVRoaGFYVFJLSENHS1dcX11RU1JeX2JegFdLUlNWVlVbYElHQ0xPVFRVXFRRQ3JpckBMT1VVTUY+cXVvZWtvcXdAd24+c29wQUFCRU5IRUJAQnpwdzxsOnh5QT5DPGZkdH1Fg3hDREZFQD10cXc/S1NSUE9LTFRfYV5eU1NTWFJYXmBudnZeTVVYXGRjXlBSUUiDSlBcZF9RgE1JUlWLgYNFWV1UTpOMSktIR4V9cXV0aX2Gg4hFSFKWiU1Ef3tvf39BQERETk6Ib3Z7e0WFc3x5eUxZV1FKTk5ZUJSLjEtUT0xMU1aSh4yDhYFwdGlxc395cW5ygYaPgHJ5foeCkZ+VlFFaVpyZk5eSm5iKl1VjWFVQl01VXU1KgEZJTVBIRklMU1ZaX1RRSk2HfnN4c3V1cnqBdW6Ag0ZES0pMS05WTUtMVFRVVF5ZYGFWUlVWU2hjXVpXU1RXUlJMkpJIV19lZl9dWFyIdoSQi5GVh4SLl5KGioKDg5iYkZWTiIuDiZCIl5+ShH56kYGAjo57goCImJ9WVJ+eoqKbgJ2alFhZn5CTnZyfmZOXT5WKj5WVi35rcnt/eHp4f4ZQU09MhUNIRXtDRUFFenhGQ35vc3Jrdnh6fIB3cWRbXGJyfH92fUdISk5UUlFRRz9DR0xPSlNVR0tQUVFRSEhOSkVLREdDczxAQD9HTllUWl1XUk9MRkVIRkA7PkRKTkpAgEVPeUl8XlZTVlhZYGJaVlhaUltbVFZTSUpNT1ZYU1JMUFJORUJGQkNIRUVCRUlFTElGQ0NCREdESkdJTVBKPlVqWFVaWVBVVk9UVllZXFZKREJFiURDVlFPUVZaXF9bWVxXTVdUR0tPUUpNUE2VTVFTWVlUVU9TUVFYVlZWUVJRgEaCipBQRo6NkUh/iJiQhoaZjZZSUpWXlpebjHmKhoGEmJOTjYyLmaKqopWQkIyAgo+bUJCHjH1yaXaLi5SOS01RTktLSHp4fYCOjpOMkoyIR4WKS3x8hZF9gYhGQklEOzo7QkZEQYGCeYOGf3x5fklHRUZJTUlIRklKRj5HTEZFgERBPnY8cnZEQ0VBQkxOTUxLUEZLRUxGS0lESUhJTEdHSUtHTktQU0ZGRkR2P0dGPj14P0FDPkRPWFJQST8+Q0JART1HSkpFS1RaW1hJSl51aVJHQD40NDRkOD1CQklIRkBISEU/PURDPz9DSVBOTlFSSkdHTE1HRkhASGBNUltjuHqEedl6g3mIeoh5B3p5eXp5eXmKegh5eXl6eXp5eYR6hHkDenl5hnqDeaR6AXmKeoN5hXqCeYR6inkHenp6eXl6eoV5hnqFeQF6hXmJeoN5h3qdeYN6iXmFegF5lXqOeaN6gnmJeq15gnqIeYJ6iXkBepB5hHoFeXp6enmEegR5eXp6lXmgegF5nXoBe896AXmZegF5k3oJeXl5enp5eXl6iXmCep15AXqLeYd6i3kEenl5eod5i3qJeZR6BHl6eXmkegF5hXoBeaZ6AXmnegICBACAnqOkpqetpp2UlJOGgomPj5imoo+LkJSUguPc8PSGhYGA9v/16fKDiIX1gZWXk4yGiI6Qj4X6/f2GiIb/hYDo8Pj1hIuOio6XoJSQkpqjo6einZ2Uk5OXyt++rqCQjpiqvMTAvbu1vbKrqaypqrfEwMrEvbezv8zQys3Bv8K+tq6AtcDFzNHa5Ovhv7SxsK+roaCkp6Wnoq6spqKclJOPjYqA7u3pgImLjYz/7e3r+fns4Ofm9+ri6PuB8fqBgoKKmo6D+/Lx4uDu/oKIh4WPj5mYnqGgnJyYoaKzw76yq5iNgv2GkqKlop6Zj46HjaGioKWsrKaomZKdqbjFuamQgfpKgoOEg4eSm6GprKKWj4yLkp+VmY6Hi5WVlY+NhoiRjYL4gYaEhpKLh4mGlI+IjoWC9uj7iISEhYH/9ufS3uHU4dnY0dfd6unv8/WFgoCAhI6J/fb0++Pb0MvJyNnwjZWM9/Pq7vXy+/r46ef1/Pnj6/iD/Ory7ujtgPr+gP+BgYOLiImHj4eFhIOPhYOLjpieo52amJ2bmaOTjJKRl5mTkJaOiIqAgv2EgICDh46NjZehm56TmZeOk5qTjYyLipefmZ2alp6ooJCCg4P//ICHiYH/+4GBkJKDgPvv3+Pn8vLzgfnv7ejm4+ji2eXk5dvY5Obk3OHo6vH88OTp7v/w7f+Ih4L/goynqJSFgv+CgYH/+fDi8vmFgoP76ezs5vqA+vv694H8+Pj87/Dd4/eWn5yeoqink/+BiY6Zk42Wn6itqqyyta6wtaCMkIOAg4D+iZOOj4+RmJ2MlIGXqrmrmo+PkZCZnJONm6aPmZ+cmpOQkpuamJqViZCN+vPaztjf6pChr7u+yLqlpaCyp6eln5yfl5WUoqylqbfDyMnCws/BtqeTipOYkoWB9fH5io6Rmaetoaail5Wjr6+poJuYmJGLiIWHkqOvnZSZnZ+YioD34+bwg4uPkZKWjo2FgfX0+Y+jnp2gmpWJiIf07/z4hZGOiYH7hIiCgPfq94GDgPSBg4WA/vyGj5GH/4OHgOvq2+Hx7IOFgOnk4tje5fr36+Ls49jg4uHa6Ozr//v5gPb5gYCDhfvy/ezn6uLp5O3w8vuA7+ff6Ofv7uHb7fjm34Dd1ePk+oSE9e3/7N/g7PTY0t/yhYyIio6J/oOIi4eDgYSSlIaA+IKHiYKDhP+EiomMi4yNkZGPlZeanZuRjY6NioX1hoaMjIqCgYmGh4mUlZCMmaKnl5CJiIWMlJiNjY+LhYD7/PqAgYWBgIWIk5eVkYiIgvXl5vj4+f709oCIizGRj4yQj42J//b/+4KGhpGTmaKjnZyaoK2vqbWyt72+xMG3srrAuq+opJaEgISHi5KagGBoam5uc2llX15bU1JbYmBlcW5eX2hnaVyZjqOqYVxYWKiwqaOvXmJis2F1dnRuanR0dXJmv8XEZWhnxmZjsbzFxGppY2hxdXlxbG10fHl5dG9za2prbZejjX93bWhufIqOhoSDe4J5dHF0c3mAhIWPg394cXyHiX6CfHt5cW5tgHR+fYKIkpyekXZvbG5wcWxucXFrbWl1dnRubGhnY2NhXq2moFphYmRltaurrauuopiiobSqoZ6sXaqwXWJmbXBkWq2qr6KlsbddXl1eZWNwc3t8eHJwa3Nzf4eHfXdnXFKgV11oaWViYFxfWlxoa3B1cm1jYlxcbXuGiHlpU0WNgE9PTk1RWmNrc3RsZFhSVlxiWWRfV1hgYGZjYldWYFtPmFVbVlFMTlBRS1RRTFJNT5WDj05HSEpKlpaMfIqLen9zc3B1fYmPmZyZUk9LSk1MUl5dqqOenYd8bmhoaHmQX2FVlJGGiZGNlJWZjo6alo1+hI9Nj3yFgYSKTJmhT6BQgE9PU01OSk5GRkZFTURBSkpQVFtVU1FTU1FXUE1RUVhaVVVcVVBSSUmQTkpLTk9TU1NbZF5jWmJiWV1jXVVVVlVhbGdpZ2RrdG5gUlRWqaRZWlSopFVYZWhaW62glJuep6uuWqmpp6Cbm6Gckp2ioZaUnZ6hmKCprLC1q6OrrbisgKy1ZGhjvWBpgYRxYV63WVpduq+mm6myYFpZq5ydnZmrWbCrqKZXsa+qqpibjJOkaXJucHF4eWeqVl5lbmZganF3eXJ6hIiEi45yYmhcV1utYGdeYGVmaW1fZ1ZqeoV5bWNiZWZwdGxkbXRfZ2tkYl1aW2FjX19ZT1FPi4RwY2l1gH9UYXB6f4V1ZWNda15bX11dYVxbWWJpYWVtc3d4d3d8b2ddTkdNUk9FQn99hE5TVlxobV9iYFpZZXR6dnNtZ2pkXl5dW2Fue21lZWJfXViekJGTUldXWFlcWV5dW6+sqWJxbnFxbWdcXV+opbGpWV5XVlWtXFxUU56YoFRYWKNWgFVcWK6oV2BoYLBaWFKVkYGHm6JeW1OYlY+Ej5OhoZyXoJiUnJuSiZSTkKmzr1eenFJTV1qknaGRiId8gXqFioyVTo2MhYyGj4+FgIyUiomNjp6csF9bopejlYuQoayYk5qmW11WVFBKjEtPVlZVV1tjZlpWo1ZZWlVVVaZYV1dcgFlZW1xdW2Jna21pYV9iYFxYnllYXl1ZUlNXU1RVXWBfWGJpZ1tbV1ZQUlpfVFVZVE9Lj5GOSktPTExQVF5hXlhRUEiDenmGhY6RiY1LUlJVVFFTUlFNioONikhMTVZYWF1jYFxaYm9uZ3JtcXl8gH11dH6Ad25mYVRJRkdMUVdcgFJZWV1eYFdST05LQkBHT0xQXlxMTVNTVkhzan6DTEpGRICFfnV9RUhHfUVVV1RPS1FSUlBIhIeHR0lIh0pFdX+FgkhJRUhNUVdPS0tRWFVXU1BTSUlMTHF9aWBYTEdNWWdsZ2VkXWRcWFdZV1xjaGlyaGRfWGFqa2RoYWFiXVxbgGBmZmtxe359dV1YV1dYWVZWWVlTU05cXlxYVlFSTk9LRX14ckJKSktMh35/fn5/cmp0dYZ9dXN9QXJ3P0JESlBGPndydWxrdHk9Pjw7Q0JOTVRWVE9MR05LVV5fWFRHPzlxPkFKTEtMTElNRkhVVldcXl5WVlBNXWpzd2dXRj19gEVDQ0ZKUldaYGNcV1hUUVZdUVlUTU9YWV5bWE5MVVJJiUpPTElJSkpJRE5LR01JS4p7iUtFR0lIkI2Cc3+AcXpub2tye4iLkpOQTktKSUtKT1pZo56bnYiAdG5ubn2QXWJXl5SMkJWRmJmckZKhnJaJkqFYpZCakpGWVKmqUqNRgFBQVVJUT1FJSUdFUEVCSUtSVlxXVVJUUlReUUxRUVdXU1JYUUxORkaLTEhHSkxQUFJXX1ldVFtaUVRYUktKS0pUW1hdW1ZbZF5USEhJjopMTkmSjktNWVtOT5iMgYeKlJiYUJaXlo+LipCKgIuOjoWDjIuKgIePkZWbj4iQl5yOgI2YVFZRmU5Xbm5eUUyVS0tLlo6GeomQTklIi3t8fnqHRYiEgX5Dh4R/gHN0ZWx6UVlYWVpfXEx5PUNHT0lETFNaXVldZGllbXFaS1BGQ0aCSlJKTU5QVFdKUkFTYGldVU9OTUxTUkxIUVdGT1NOTEhGR01OTE1JP0JAbmxaUFRegGlHUl5naWtgVFRPW09QVFNRVE5NSlNaU1RYX2dqZ2hvZFpPQTo9PzsxL1hWYDxAQURMUUVJSERET1tdVlJNSk1HRUVFSE9XYlZRVFJOSkV5b3J1Q0dHSEhKRUlISZCGfkxdWVpbV1RLSkuCg5CLS1FOTUuYT0xGSI+Gh0RFRYFFgERJR5GNSU9XUZJJSkeFhHd8iYpRUEqHg353f4KOioJ6gnx4gYOBeYWIhJSbmk+RjUtMUVOUiIp6c3Fpb2t2fYCIR4OAdnx2e3ptaXmAd3h7eISBkExKhX2Je3J0gIl1cXmDSEhCQ0I8bDo/RURDREhTVklFg0ZKS0dFRodGRkVIgEVFRUZGRk1QVFhTS0lLSkdEeEZES0tIQEFHQkNGTU5NSFJYWE5MRkVBQ0lNREZLRkE9dnh2Pj9DQUFERk9RT0tDQz5vZWZyb3V5cHQ/RkZKSEVHRkRBdG12cjs/P0ZISk9ST01MUV1fWWNeYWhqbWxmZG1wa2FZVUpBPT9DR0tOmXqEeYR6hXkEenp6eYt6CXl5eXp6enl6eoR533qDeYV6j3kDenl5h3qHeZh6AXmdegF5oHoBeY96g3mFepJ5iXqMeYN6kXkBeoZ5BXp5eXp5qXoBeaR6B3l5enp6eXmGeoh5AXqfeQR6enp5h3oEeXp6eoZ5g3qGeQF6hHkBeol5iHoBeZd6AXmqeod5qXqDeaJ6hHmKeoN5inqEeYV6AXmEegd5eXl6enp5hHqCeYR6BHl6enqGeYN6l3kDenl5hHqNeQF6knmCeox5hnoBeYt6AXmGegF5lXoBeaB6g3mOeol5inqEeaZ6AgIEAIDw6+2BgoeD++Ti0MvU3eXt//78//r0gYOGjImC+/Xc0uj+8vLs+IWMi4yRkJKN/PuC/fmIjY+NkZKTkpKWjo2BgIKC9eTj+IOJjqqqhYaVjYaIj5GMj52jqLHBvrOsqqKinJCMkJGUmpaRj5Seo5qbn6enpaCZoKykpaKZkpKjqICkrNrv2tjHwr2wqKaop6OoqKaglpmNgfqIhoCGio2LjI+PjYXx+IGBgoiQhoCFh4KCgYCMh4SCg4eKlZaQiv3/hf2AhoGDiI6Mkp6enJ+YlJqho52TmKS0w87NxbG04OK5rq2loJ2akoqTopWNl5aZoq+9rpmNipuwvMXJysW6s4CroZuMiIeGi5iWlpGGio6QhfX9gP/7+oCA8tve7O7j///07e749ejc9IyVm5WL/fb+iYiIhISTnY6E9Oz5hID03cfC1Onx+fn25e72/vjx9eHe2Oji1tzl5Ojt9oiFhYT69/f0397b5ufo6/iBgoiH/+7k5Ovq9Pb54eDk1Nbl8IDv8eXd19m/tcO5u8DEzNbj9Ozj7/uHiIeSlJelqqqxur+3trGvtbKrmZCiqqyjnJCQkpeYl5OK7+vs/fT/g5mej//t5+rWx9HUzNPV19zV5e7j84SKhYqbmavJx8O7w8m0sLmlnoqIhYGG/o2smIqG/IL88enn9oGFiISGgYSGhoCD7vaFhYD7iYuE+YGFhoyXnqSqr6qcnqKXioGPkpyXjJWgopqOgu/y9/Dm7YGEho+TlpyglIeEjZGJgoKKi4WBioiEgYSJjIWHhIqG/uj6hoSAhY6WmJqgq6qnqKucmpmQj46drsbKt6GzrpGflYmBgpKI9t/p5Nv49PX56NjY4oD6gfyBkpWYhvTo3MW+wMjN2vX+iY+OmKORkpqloZabpKmnpaejnJiOho+YkJKNjZOgq7Gzur2tmY6XsrmyqqSdhYCAgf6Cj5mipp+WnqKbmJygoqOhmYiChYL47/aAhv34/YmE/+Pf3fb58fL39oWH+/Pg2+jn4u2Djo+Kh/L1/ICD++bw+YeM9veEg4Lv5+zr6vDv9Ong2NLWys3m5ef49vyB/+rp4eHa7IGB8eji19rzgP+Bgf38+vLy8/Ln8vmChomOiI2JiID5gIKNjoiB8u/m7PD36+3s+IH7g4mF8+/0g/r6gPuBgoCIhoeIhIWOj4yNh4uOgoP9+//4hIuSiYCEgoSDgPmBiICDlZKRhouNkpeioKCdm46OjY2TkIOHgICG/vzz+oWIhYSDhYSBg4uA7Obs7Ozp9PyAgoGGgIH7+u/qzsjW3s7I2en0+YCFj5KOjoiLi4mNkY6LjoWKjJCOj4yGiZCbnqKroZeSk5ePiI6NhISDhYOC8ubvgP2C/4CWkJZUVFhWo4+Of36FjZWapqSlqqijV1ZdZGNdtK+claKzq7K0wWZnZ2pxcXVvxMZoycVudHRxdHV0c3J1bG1jYmRjuquovWNnanZyYGNya2Rma2xmaHV3eoGLjoV+e3R1bmNcXV9fYl1YVlldY11dXmNjYl9aXWZfXl9XUE9WW4BeaY+fkJOFhH90bm90dG5sbW9rYGVbUZ5ZWlheXmNhYmRkZmKts2BgYWRpX1xkZmFiYFxmX11eX2Nmb2xlYa67ZbpfZV9cXl9cY3N1cXNsbHN5d29ma3V+hpCPjHx7naSDdnFqZmJfXlxlcF1XY2dobHN8bFpUV2mAiIuFgoB2cIBvamNWUk9PVmNfXlpSU1NWS4GISJWQjkdIiXd8iIl/nZmOiZCdnY15hlFZX1tSj42OTFBXVlNWVk9MjYycVVOooYd1gZSWoaKfj5eiq6Wcn4mCeYN/eYGNkp6enlpVUEmDiY6Yi42GjpCSlKBTU1ZUnpKKjJWVkpKYhYKGdHN7g4CHiIF6d3xrY25oaWtscHR6g3x2fH1DRD9FTE1VV1ZaaG5lZmJkamNdUk9eY2VhXVJSV2BiY2BXjo2Qn5eoWGNpYauclI+CdICAeH6Bh4iAj5yToFZbWF9vbHyYlpOQn6iTkJeBemdkYl5itGeCdGVguGC3s7Ctu2NjZGVnY2VnZYBltbRhZF+3ZmlkuF9iY2dwd3uCiIR2d3twZFpjZXFuZG10c2lfV5WTmZSLkFBRVF5hZm1wZVpXYGJZVFZbYFxXW1lYVlpbXl9iXF5bq5ioWFVUWmJrb3F1gYF/foBxbmxjYF5rfY+Tg3GCfWFsYFJMTVpQineBfXOMiImMf3JyeoCMSpJOXWBdS4N3cWBbXGBhaH+IS0xKUltJSU9aW1ZbYWRmZmRiYF5VTFNeVlNNTldfaG1tdX5yZF1ngIV9dW9qVVBRUqVTWl1gYWJibXJtamtsa2tpZFVQUlKppq1YWqenrmBZpY+UkqmtqKmnnlNTl5WMiZGNiJJWYFxYW6SoqIBWo5iko1ZbnaZZWFaSiZOSj5GRnJqSi4WHe3mKjY+bmqRWq52gmJOMmVZVl4+NiIiQUKBQUJ2enZOTlY+Ci41LT1RZVFpYWlSjVFZiZ2FXm5KMkJGbmJ2gsWC1W1tWnpudVay1XrVfYl9kYF1bVVNXV1hZU1ljV1mopqqgVV5lXIBYVltbVqRXXVZXZWBhWFhWWmFra25sbGFjY2NpZ1tgWVhesqyho1hZVVNOU1NQUldPkI2VlpWUm6RTU09RTE2Tk4qHcGt0enBqdoOKjEpOV1hSUFFXWVZYW1hXW1JXWV1bXlpWWV5mZ2x1bGNfXGJdV1xbV1dUVFFSmZCXUJ5TooB9eXtFRUdEgnBxZmZrcHV6hoODjY2ER0hOU09LkIt4cH+QiY6Kkk5QT1FUUlVQi4xJjIlNT09NUE5PUE9STUxCQkRDfnFxhEZKS1lYRUZTTklITExKTVVYXGJqa2JdXFVYVUpFRkZHSkZDQkVKT0lLTlNRT09NUFVOTUxFPz1GSoBKUXKAcndsamdeWFhaWVRWWFtXTVJKQHpGRkNHR0xJSkxOUUyCiEtKSU1US0hOUU1RTUhQSEZGRkpLVFJMSoqRT45GSkdEREVBR1NUUVVRTlVaV09GSVBYYGlpZ1tZcnZdVFBJSEZGRURMVUVBTU9QVWJlWElDQ1VudnZva2tlYYBeVlBHRkZDRlBOT0xFS05QSH6AQYR/fT9AfG91fnpviYiDfH6JjH9ue0tSV1NKgH+FSEtOTEpQVExJiYiRT0yXjnhrd4iLlZeVho2XoJiOkn56dIN8dXyIjJWVl1hWUk2MkJaYh4iDjY+Pkp5TVFhXopSLjJSUm5ech4OIeXqDjoCZnoqBfoJtYmxiY2Vma290fXVwd3pCREFITE5WWVhcaG5mZV9gaGRdUUxZXV1ZVktLT1ZWV1NMfH6CkYiXTllgVpeLhYR2anV0cHV1eXx0goyDkE1RTVJgW2h+eXt9ipGBfX5pZVZVU01Qk1JnXFJPl06SiYOCj0xMTk5QS05PTYBMhotNTUiITlJMiEhMTE9XW2BlamdbXWJXTENMTFdTS1JaXFRJQW5uc3Bobj4/QUpMUFZaUEZDTE9IQ0NIS0lESkhHRklLTktMSE5NjXmISUZDSE5UV1tjamdkY2daWFVOTUpWZnR2alhhXkpYT0ZCP0tCcmJraWJ2b25wZVtcYYBxPno/TVBPP3BmX09LS05QV2tyQUNCSVNDRElRUEpNUFFRUFBOS0tDPEFIPzw4Oj9HUFdXXWFVSEBJYWZgWVZSPzs8PXxASk9PT05NWF1WVVdYWFhVT0I+QEGFgIRDRYCDiUtFhHN2cYKFgYeIhEdGf4B4cnZwbXpKU05JSoCFg29CfXWFi0tOgYxMSkZ3c3t9foKAhX92c3BzY2J3eXmCfYNEiHh6eHhzgElJhHt4cXB+SJFJSI6Mi4B9e3ZqcHM+QkZMTFFPT0mNSUlSU0xGf3h0e32Gg4WFj02SSUpGiH58RIqTS45JTEpOS0hGQD6EQ4A9Q0o+QHl6f3hCS1RLRkRISEN/Q0hAQE5KS0FCQkZLVFRVU1RKTExNU1FGTEdGSoeDen9FR0VEQUZFQkRJQXNvdnZzcHyCP0A/QDw9dXVsalZSWl9VUmBqcHI9QUlMR0ZGSkxIS05LSU1ESEpNTVBNR0tPWVxdZl1VUlJZUktQTg1JS0pKR0iFfIJFiEiKg3mEeo95hnqKeYh6BXl5enl5kHqEedB6AXmMeoJ5mHoEeXl6ec96CHl5enl5eXp6kHmFeoN5iXoFeXl5enqdeYR6jHmEeqV5onqGeYR6knmXegF5hXoCeXqFeYp6Cnl5enp6eXp6enmbeoZ5oHqDeaR6jnkCenmFeot5sXoBeZV6Cnl5eXp6eXl5enqKeYJ6iHmFegR5eXl6hHkHenp5eXp6epV5AXqHeYJ6hnkEenl6eop5iXoBeYZ6inkNenl6enp5eXl6eXl6eZJ6hHmJegF5nHqEeYt6iHmGeo55rHoHeXl5enl6eQICBACAlZaRgff58/jm1uDz+O3p7PKBhv3t9ICIhP6IioiJhoP/8Oj0i5CRlJSL//2GgPfz/fby7IL/5urp8IaXko6Kh4aHg4X/7tzf4uPW2Of/ho2NlomC8fL2g4KF+Onl+Juqi4+bnJ+zvrKurKOlpKOoo6uroZedt7nBxsvMvr+9xcWAw8fXu52IiJCSk4uF/YeLg4OBg4eNkIqLkpGNjI6Lj4+LkI+MjYyJiPeCnYfy6vaFkYiIh/7w+fTs7evt2dXk++/ygYuMiP799v79/fPl6PX1gYqJjo2NlZWWmIuNj5+noJqgmpqbnJSSj4+PlI2WoJqeoqGgnY6Kkqixrayno6WAoJWQhYGPjP/2/IH/gIeA/IWD+/+Oj4qKj4qKkIf49/f3+fuDge3g0M7c9YmF/frzhpiSjJCTiIOIlaKvoZGJ94GIg/Pn5dTNytfq9PPt7Or1g4D8+ILy4suyprvS2dHAytjf9+7t9/T48+bk3MbP2OX09veBgIH/9fDt1sm7sriAv7Kopqq41eWA9/v17enk3tjt6N/r5+rzgImKgv6HkpKVmJSNiY2JiYeGhIWEiI+jsr6+sKSbnaSpp6OhnZeN/vzw+O7d3+X6+YKJgoLz3dXf7e/o6ODm3dzk7vPy8vSDhYiIioyNhPj59fD26e3r7/uAgoLy4+v39PaB9+rvgIaAi4v9+YqLkqCwp4+GhZKqt66iqp/+3vLw5uz4/oaToaKUjo+UjIuOmJqcnZCMj5CNh4WG9+fm+/uEhImC9/7/gIiRjZedqKyckImIiIH/+e7pgJKMg4GMmJ2Tk4yF+PiElaOst73CxaOOkYiGgP/29f2GjYmB9u/h7fqBhYiRlJWAoKCnl4iDhIKC/vv29+7t3dzg3OL3gJKXqrGtrKmysaSTj4uGj5KLi4uMi4qJk56amZyakI+ZoZmOg4OHgPyA+ff1/v+FkZSLioiGhvuCiZOanp2Wl4yGjof/+P2D+url4fj45vHq4d/g6PH09oOLhon66eyBkZ+aiYOGg/vt1tCA3OHc0+Lu+ujZ6+z2gICB++/1+ezm59fS2ej4gf6DhYqQiIODhfnl29nQ2+/3+fz8+/Dd0t3l4O768uz0hIX+iIiG/YCGiomEiYmJi5CRioeAj46IgoOLjI6KjpSUiYP/+4qPjIiB/YL4hIiIiYv5+vmEgYWGjo+LhYmDioyJio4Th4LzgPqA+/r37u7zg4uKgfyAioSHaYaJgYSDiIeQlo2JgoeKj42Igf768PP9+/by9fj4+fH8hoyHhomE+/jx5ev/+/z/6eP+iYf69POFioX++v387/X08fnp3+r0/oKEhf+Dh4uQlpeJgvnk4vfw9YP9/oKA9vLy69zg9Pf0i4Boa2dbr7CrsaaYnKGlp6uurl1htKiyYWhoyGxtam5sacO3vLxncXZ6eXLNy2tkw8PVy8jAas62vr3FcYN+eHRwbW1oaMSzp6yvrqKircFlaGhvZF6sq61bWVqhko6ea3hdX2Zna36IeXJvZWNjY2hla2hgVltydHx/gYN6gICMjYCMkqGJbVVTW1tbV1OcVVhTVlZYXF9iX2BmZWFjY2NnZ2FoaWVkY15frFxxYLGorVtlX19furO1sa2qq6+bna2/rahXXFxXoaWfpqesqJ2fpKJYYVxdWFZdYGFjWFhXZGlhXWNeX2NnZGBeXVpfW2FpZWZmYWFjX2Fnd3pwamNfZIBiYV9WUlxUlpScUaBQVE2XU0+Vl1pbV1ZbV1ZeWKCXk5uloEtJkYx9eombUU+goJhPX2FeXmJYUldjbnlqW1aXTFFVq6Ohj4aBiJKZlpWXlp9aWbCkVZmMcltPXnKDg3R+jJGgk5GZkpqVioqHeIGKlqCamlNRUaCXk5J/dmtjaIBvZFtYWWR6hE+XmZOOi4V+eIyLhIyMkJhQVlNNmFFYVlZVUEpHSkRAPTw+Pz9FSFhpdnhtY1paYWhmYmJeVk+Tk4mSjH2BhpueV19WVZ2LgYqanpqajI2NkZihop6gpFpdY2VoaWhfr7GsqaufpaOqtV1hYrCkrrOttWC5q61eZYBoZrezZmVodIZ+Z15bZn2KfnB1baKFlpaKipKaVWNub2JbXGFbWltlaGloW1VYW1lUUVCRh4eam1RTWliipKFSW2JgbHB6fW1jXl1aUqGZj4xRYFtVUVhlbF9bVlino1ZodHuGiYiCZlpdVVRNmpCRmFFUVFCUiH+NmU9UWmRkY4BvcnpoV05MRUaKh398b3JoaW9qa4BGWWBze3h0cXh4bFtXU1BYWlNRUlNRTk1WYF5gZWRfYGdrY1pPUFROnE+Zm5iZk0pSVE1QVVhYoFRaYGJlZ2NkW1ZgXrS0u1+snZeQop2OnJyYnKChoZ6XTk9PV6KZmVNZX2RbVlZTopqJhYCLjo2LmZ6ej4aWmKBRTlCelZSNiJGXjIiMl6BRnlZYWVpVVFVXpJSIhn6KnKCdmZqelYiCkZGBi5qSjJNVVaFXVFWbT1VYVlFWVVZaY2ZhXlZhYl9ZW15aX11fZGhhXLK1aGpmY1yvWaVcZWRiZbKxsGBeXlthYl9YWlVbYWBfYIBdWaJYrVippaCWlpxWXl1Wsl1jX2BgXlxfWV1bYVxcY19dV1lZY2NgWrCtpqmwrqynqLCysqanVl5eXFlWp6Sbj5qrpKSklI+jVlWgn5lTV1OamaSknKWnnaaYkp2kplNYWapZXWFiZGJaWKeUkZ+bpVuuqldWo6GflI2Wp6mnYmpWWFVKi4qDiH9ze4CCgoSFhEdLjICISE9Nl1NRT1FQTpKGiZBQVllbXFaXk09JioiYkY+JTpV/hoSLUV9aV1RQT05NT5WNe3x8fHJyfY1LT1BXTEiBgoRHRUeBc2x3VmJIS1JUVmVsX11bhFCAVVNYVU9GSV9gaGtsb2ZqaXN1dHiFblZBQEhLTklEfkdKREVEREdLTUpNVFJOTlNQUlJNUlJPT09NT41LYFCQhIVHUEpKS5KKjouJiIaKe3uIloSBRUlGQHF1cHd4fn51d3t1PkM/Qj89REhJSkJDQ0tNRD9FQkVJTkxJSEhGTUiATFJNUFJQUFFLTVJhY1pVUE9VU05JQUBMR3pzeD98QEZDhklHiIhNTklITUlKUk6Ph4KDiIRBQX51aGh4iUhGjIyBRFNUUVNXTklMVV5qX1RSjkhMT5mTkoV8dn2IkJGOjIqTUU6ck0+QgGtXTFptfXtud4SJm5GVmpKblIWDfnCAeYCMl5OVUk9Qn5aTkX5zaGBla2BXU1VfdHxJi5GMhoSAfHSIhn+IhomRTFNSS5FOVVNVVlBJRUdCQj88PkA/Q0ZVZXBvZFpSVFpdXFlaVk5Gf4F3fnltb3aHiEtTTEyMe3N8io6IiX6Cg4aOlpiWl5xWWVtZW11eV6GgnZmdkpiAlZqiUlRUl4yUmZOZUpuQk1BWWFabm1pXVmN0bVdPTllueW5gZFyHb4CAd3qEi0tWYGJZVFNXUVFSXF5eXVFNUFJPSkdGfXNyg4JGRExIhIeGRExSTldcZ2tcUk5PTUeKhXx3RVJPSkdMV11RUExNjopJV2FmamdrblVOUkpJRIeAfn6FR01LRoJ4cHqCRElQXFlWYmRtXU1GRD4+enhycmdoXWBoY2Z5QFBSYWVkamNnZVtMSERARkc/PT09Ozg3QktGRkpJRERLUk1FPD5CO3U7dHl2eXY8REU+QURHR35BRktNUFJOTkVCTUuJhY1HgXdzbX5+cXp1cHJyd3+CfkCAQkFJh316QUpVWk5JS0aBeGllZ2hoanuChXZsfX2CQT9Chn6Ae3R2eGxpb3uDQn9GSkxORkJCQ3xxa2dibYGGgoGEhntuZ3R5bHiEf3qASkaBRUJDdz1CRkZDSUlKT1RXU09FUFFNR0hLSU9MTlNXTkqOj1JRT0tFf0F4REpJR0uAgYKAR0VGREtLRD5CPUNHRERFQkBzQYBBfHp3cnR4REtLRItJTUhJSkhGR0JEQkdER01HRkJFR05PS0aJh36AiIWCfoKIhIN6gENKSEdGQ4B/eGx0hICChHVwhEhGgYF7RUlFf36Ghn2FhXyEeHF7g4hFSUmKSk5SVFZUS0qNe3gSiIKJTJCOSkmKiIZ9dHuPj4hRhHqNeQl6enl5eXp6enmGeoR5hnoEeXl6eoZ5AXqFeYp6inmGegZ5eXl6enqEea56AXmbegd5enp6eXl5hXqOeYR6i3m2eg15eXl6eXp6enl6enl5iXqGeYJ6hnkFenp5eXmPegR5enp6jnkFenp5eXqeeYN6kXkBeo95hHoBeaJ6inmEepJ5iHqKeYN6hnkEenl5eYR6gnmQeoh5l3qFeYR6g3mOeoR5jHqCeY56hHmEeoV5j3qMeah6Anl6hXmIegF5jHoEeXl5epB5hHqDeYh6kHmDeox5Anp5iHqXeQd6enl6enp5nHqCeYV6A3l6eYV6g3mRegR5enl6hnmEegF5mHqOeYZ6jHkIenp5eXl6enqOeQR6enp5iHqGeQV6eXl6eol5AXoCAgQAgPj8//7v9P2AhPr0gP349+/w9+fr8t7h4trb0tDW4d/Z3Nvi9fbw9PTw4ubk6OqFkIqKi46RjYmC7+Tk7Ovk8/399ouwx7aknJSbmYyNgfLt94aKiouLiYyOmZaTkYWVoaGvs52ZoKCgpaSmqaOXkpGls6qampSMkJKXlp6mpaWrgKSck5aVk5KPkI+Rj4mMj4qIhIuTmZ6YjIiKkZGPi5iekY6PjYT68fGBgon8g4eEhoT37/ju6eXi4/Hp7PWGiIf7/4eChpGXlJKaoquzqZ6Qh5KZlI6NiomKlJulq66vqquvt7aplY6OlZaQioqOjpins7m5tKeipKzAydDYyMDIgNy8n5aXnJiYk5OZnZOKiIj+8vWAgIGJiomIjoqD+/Dxg4qFhY+Ul4yFhYD6hpGZlpeps7KupJuWkoHv8OTe18/c4eLl0svR59bP3dzj5NfNzdHY1cW6t7bJ3O2IjIaF8uPv4dXi7f6CiouJg/ju7veEg4KGjIPs28vS5Ovj6OvxgO/v6Pjx2Mi8srG9vb6/yNbb4enm9fb0gYSRk5aTiIWJh4Tz6d/d3eLk7dzZ1uKBiJelpqGjs6+glYyUjoT99fj8g42JjpOcoZmiuLe3rZ+UjYySk5icmZWRmqGYi4OCgYOChoiKk4798P+GgoCEh4D1+/v07+vv9/uHl5CJiID4gPqFjIqOl5OMiYmPm5yZk4iFioPy48/M3ezu4eT/kZ6z0OPcybmztbe7vLStp6Ogm5mTjoeD+fT0hYWIj4mGhoKIhImLmpiakYiE/YSRh/be1ObugYCAiZWXoaiimJianZ2XlpGPkZCLkJudnp+hoJiN8ufy9faA/fb7/PT+8OLfgNzr6+Pa0tfi3+nzgoD96+fx9/6EjpeSjI6Mh5OUmZeOlpubm6KclZKQioaFiIaLh4+YlY+Ki4mNiYuKh4aE+/2Bhu/m493CvsfM2Nvlg4WBgoOHhoeMifPr8/Hs4/2IioiE++LU6vby/4eGhoyHioT1+fry/I2TjYmJ//iDg4P+gIH9gOrV0Nvq7vb3gIWJjImA+oKIhYD05+Tg6tvsi5GNjIKBioHw/4H3/oGJiYCEiYyJ/vP/iYyIh42RiYGGj5KQkZuYnJyTjY6PkpSWko2Ig4OA/IOJhoSCgYSCg4aGjI6Qj4aCgPj68fb49IOLjYyOhPPu6ezu8ISGg4OEhfn/gP6HjIeIh4eQl4+K+e6Ah4yNjYeGg4Ly7e7x8/+AjpeWkI6NlZOUk5aQjIqA6u2EgIH+gomQiPXo4u3y7eD09uvt2uD3hPjyhYqK++v1+e3u8u3u8/bz6OHk5tXg8Ovq5+vt5+zy//LV0uXs9PaBgu3j3dvd3uv69efh3drZ5oD3gMHGy83AyNNqbsvBZ83HxLzAx7vDzLWztLGxqKSquLmys7S/1d7c0srBs73CzMdpam10dXR1dHNtxLi3wcK2v8PHxXKUp5WGgHV7eGlpXKaiqmBlY15aWmJjZmBgYlhjbW16fGhiZWZnbGtub2pgXVtteXBkYl5aX19kZGtxcG9ygGxoXmNjYWBfZGZmZF5hZF9dV15kanNyZ2FgZmlrZmxwZWNlYVuxp6VaWV+tWl1bX16vq7GlpaShoq+xsLBeXl2oo1lWWmRqbG54eXV7eXVrYWlsZF5dW1paYmVpa29zcm9ze3t2a2diZGJcV1peXGRud3x+gHt4e4GOkJSbiHyBgJaCbmhqbWdlYWJmbWZfW1mdkJVSUVBVWVhYXFhRoJ2eWV5YWGJpb2dfXlqvXWVuaml3f395cWpkYlafoJGFe3F3en2BdXN4iYCBk5mjoYx9enyBfW1jY2Z4hpZcYFlVlYSKgHWBjKFXYGBdWaOYl51UVVNUW1SVin6EkZiWmJeagJSSi5SQfnFlXl9sbW9wdX6DhY6Ml5WSUFJcXF5aUVBXVFGVj4eEfoWDjoJ+gIxUWGR0enBrdG5jWlJXT0N8eoGHSFBOUldhaGRuenZ9e3NhV1lhY2Rqa2hjanBmW1ZWV1hUV1hbZWGmnapZVFJXW1ajqKeloZ+jqq1fbmhhYFqqgKlcZGFjZ2tiXlxjb21nYVxZXFSWi3p1gIaFgIOeYGyBna2mloV/gH+Cg3tzbGdiW1lVUUhGgH2BS0pNVVNQT0xRUFZXZWRmXlZTnFJaVp+KgZGZVFNSWmVibHNtZWZmZ2hjY2BeX15cYWpqamlra2NYlZWipaNTpaKpp5qdjn95gHiFgnlwa3B4dHuBR0WLgoGLk5lSXGNdWFtaVmBibGleZmtqZ2hjX1xbWFVVV1ZaV1xiW1ZTVldcV1ZXVVZSkYZCRXlzdXZmZ3FzgH+FT1BOUFNWVlpiZLSvta6mm7FeXVlVoJOTrrmwslpYVFJLUE+WoKmkqFxfWlxcoJpXWluvgFiqVZmQi42SlJmZUFZaWVZQnFBTU1GblpWSmYqTWF1YWlJQVk6RplajoU9VVk9UWVxYnJShWlpYW15eV1BUW11cXmJiaWhgW11bXF5kY2BcVldWqlpfYGRhXl5bXWFkZ2NhYFpZXK6xqbK6t2NpZGNnXqqopqajqWJhXVxbWaOrgKdaX1laWVpjZmRgq6FZX2RnamVhXV2vpZ6hsMRcaHNybGtqdHFwbW5pZmVcoqFbWlmpV11kXaGTj5qenJOlp5yfj5SrXaqmX2Vkr6CorKOlqamtsbCup6Ckppqksq2tpqqvq7GxtKKQlKu0urlhYbOpoqWsrLS/u7CrqqifrGPAgJWZnJ6SmKBQVZuST5yWlo+Rl4qRmoWHiIWGf32DjoyFiImSo6eloZyYjJKSmZlUVVVaW1lZWVhUl4yIjo+GkZednlx8jX9zbmVpZFdXTYqHkFFSUE9NTFFRVU9OUEZTW1pmaVZRVldXXFpbXFdPTEpbZFxQT01ITU1TU1tiYmJmgF9bUldWU1JQU1JTUUxPU1BPS1JYW19eVFBRVlhZUlldVlZWUkuMg4JIR02HRklHS0uKho6Eg4OBgY2Kh4hKSUh9ekM/RE1TVFVeYF9lYlxQRk1PSEVKSkdFS05UVVZWU09UYGRbUU9MT01JRUhLR09aY2prbGZjZWp1dXh/b2hxgINsV09RWFZTTU1TWE9IRkiFfIFHRUZHRkVGSkdCgoGIS1BJSE9WW1ROTkqSUFhgXVxpbmpkXVhVVEqHinpwZ11laXB7bmlugHR0g4aQkYF3dHZ5d2deXmBwf4xTWFJQjYGLgHaCi51TWllZV56Uk5tTVFFTWFGOgnR6jpGIi4yQgI2OiI+KeG1jW1tlZGRmbXJ0d4B+iYqKS0xWVlpXT05TUE2Lh399eIGQlHl1dYBNUVxsb2dkbmhdVU1TTEJ8eoGFRU1KT1NcY11ndnN3cmhbU1NZW1xhYV5aYGVgV09PUFFOUFJUXFiXjplRTkxQUkyRl5aPioiNlJdSYFpUVE+TgJFPVlNWW15WUVBVYGBbV1JOUUmAdmVga3R2cXOJVWBziZaTiHp0c3Jzcm1pY15bVlRSTkhDeHN2RURFTElHRkNJRktMWVVWUktJjUpQSol5cH6FSklJUVtZYmpmX2BfYWNcXVlYW1pWWF5eX11eX1hPh4eVmJlRnZWbmY6WiXl0OHF8enFpYmhwbHN4QT9+c3B4fYBDTFNNSU1LRlBRWFZLUlVTUFNPS0hGQz8/QEBGQkZNSUZDRUVKhESARUR5cDg7ZF1dXExMVVdhYWhAQD0/QUNCRk5Ph4GIg4F3iEhKSEeJeXOIkIaMSktOSkJGRIKKjYKJUldRUFGNh0pLS4xGhUR8dXZ+goOIikZJTEtJRIdFSUlFgHh3dYBzekhKR0tFQ0hBc4BAe39ASElBRktOTIp+h0pQS0xQU0xHRkpPUE9PU1FWVExHSUdJTFFPTUpFRkWOS05MS0pKSkZGSUtOS0xORkJDfoB4f4R9Q0pGRkpCcnFwc3J1RkdFRUZFeX98Q0iEQnlLT0tHenRBRktMT0lIR0Z/eHR2fo5FUVpaVlNQWFZVU1ROS0pBbm5AQEF6P0VOSHdraHN5eG6Agnh+bnOJTIqHTlNSkIKJjYSFiYeJjYyKgnt+gHSAi4aFgYaKhYqNlIRvcYWMkJFOT4uDfH2Cg46YkYeDgoB6hU6Vh3kFenp5eXqieYp6inmMeoN51HoHeXl5enp6eYV6jHkFenp6eXnOeoN5inqDeYt6AXmOeqF5hHqIeYV6hHmGeqF5i3qMeY96hHmmeoN5hnqJeYZ6gnmSeop5mHqDeZJ6BHl6enqFeZ56hXkBepR5gnqGeat6BHl5enqLeYp6h3mEeod5h3qFeYV6CXl5enp6eXp5eoh5hnoBeYR6h3mIegV5eXp5eYh6g3meegF5knqGeYZ6hnmGeoN5inqCeYl6hnmQegZ5eXp6enmEeo55Bnp5eXp6eqN5gnqPeQJ6eQICBACAiYTx9vDl7/Pt5uXt4trg8vzy7vj18/D3goWEhoeFh4WBiI6WqaWXk4Tz4u35/YiPj4yF4cnX8v+B++ve2dzQ0uvv5+uCk42Mj4yMioiGhIPz9Of0gZKLhfuBiY+Gg/b18f2LjYuMgoGD/oaLioaHjZKOi5CUkY6JkJKXoqmpo6qAr6ymo6afoKadmZWcm5WMgYSIjZKThvj9iIuSlYv80MW/yOTs7+3y9P7s5N/mgIL99/n6+ICIjJGVm5+io6Cgl5iem4+HjoqIh4iRl52jop2flYeQmZeRkZadn6ChnqKmn5OMg4CAhY6Rk4+MkpeXmJWZmZCPj5KcsMXS0tDGysqAs52SlJmfoKSen6eloJufoaKosbC2w7Wxua+xs62pno+EgYSKioqSno//5Ob39u33/fGAgoeLioyXjYeHgP/rzsXE1Nrr6+fc3tfR1+DgyMTK1NPj6OLu8vH3+fj7gP/9gIGHhI6O+t3Vz8TA0eT06+n/jJ6pn5KC8+rq5fyPjIuAg/f19/Xu5NbX3eXr5/qFjpiYj4j76+fugff29u7u8v+C/fDt4O37iJigpaKZnZqNkpWRkpKOi4eA+Obn5tzQ19fg7t3Y2d7p4OPr5t/k6u324NPY6oKFhYuMi4mMk5SWn6eglpafq7S/yr+rk46Sj4yJhIqOi4eIh4KGjIiA+P2Ag5CSkZuioZ6gnpSQjJKPg4GBgoOA+vLr9YKAhpCYnqCjprK6sqekoqi3uKijo5mOk5OFhIyJ/PPv6e3u6O/v4+Xh1cXJ1eX4ka2vq6KUkpOJ+O/q4ODs9oCAgIaKlpebno6Jg4Dy4+Lz9Ovq8Pb29PiAgIXy4drR1Ov76+rV0tWA0tje7fby8vD1+vmCipKE+YaB//Tx5+Pi9v2Hj4qOkY+RkpOUi4KDio2OiImSlZmZlY6KioT3+P745ebs8O7f2tjTxtHf84SF9Of7gfns59bc6uvr8e3m6PeFhYSIhoSFgP/36uf8goSIi4+JhIGA/PLt5+v1gIL99vD08Ovl5PGA8uHp793X0MjQ5fDt8/n29YSLge7w/oGCg4H5+IDz+4KDhoP48fr7g42Jgf2CjZSHg4iJhYOIg4OLkZCMiID/goWRmZmalpibm56foqGdmZqdoJSRjIf6/IH99+zh3drb2env/uzt4+f8hoSIgomLioX4gIeA+oKMkJaUhoSEgoKAh//v4e34i4yQmrCxpaKlp6WdlIiBg4KC/fPu6N/l8oOHj5SgtaGTjISB/4CE7+3m4fGB/v79gfXw+Pv7hYH39/+AgoSJg4KGg42Oio+Qj4Pn3uDe2Njf3M3L1tna2+Lt84CEgvyA/PX++eb4hZGXk5yViPLo9vn9gIGA+veEi4qAbGi7wLywuL25sq+0raasusG6uMC8urW7ZGlpampoamlpbXF1jY59eGvAsLbDx2x0d3RqqYyWs8BguK2jn6OWmbCvp6pgcW1nZ2RiYV9cWVmio5eiVWRfWqpXXWNaWKOhm51ZXl1eVVRZq1leX1tcYGVgXWJlYV9bYmRncHh4dHyAgoB6dHVucXZsamdrZGRgWmBlaWtpX7G1Y2Roa2W2kYB0fJWXmZuepLCjoJ+oYGG4t7y8vGBkZWttcHN1dnR2bG1ycWxla2dhXl5ncHV6d3BxamFmamljYWJlZmhraW1waV9bV1dYW2BiZGFfY2VmZ2NmZmNlZ213hpObnZmNjYmAdmhmbnx+dnVxdoN/d3J3enp/iYeOmYuFjIeJjIR+dGdgZGZnYl9odWaul6GyrKKqrqBWVllcWVhhW1dYVKeXgHl3g4SJgoJ7gHl0fYiId3d8goWVmZOam5aZoKKkU6WmVVVcWl1aln15c2hjcoWWkI+kXXF+dWlZn5GLhptdXF+AW6ahoqOgm5CQlJyinKlYXWVkWlOZj4yUUpubnJiZmaNUoZWShpGfXGhrbmtjZmZbYGJgYmNgXVlOkIWLiYN8gYCHj4WBg46dj4+Uj4yTnp6ajY2SllFRUVpcW1laX2Jna25mXV9ocHuJkoZwWFBTV1dVUlhcWVNTVE9UWFRMkZWATFdcW2BiXl1kZVxWUVZUS0tKSUlIi4N9hUhHTFZbX2FobHV5cWpsbHF8fGtgX15XW1tOTVVTlo2Jg4WJg4mLgIKAdWhqdYOUXnd2c21iXVxUlpCLgIOSn1VVVVxibW5ycGNjX1qomZekpZ6gpaajn6NVUlSRhH91dYeWiYd2cnSAdXqBkJyanZmeoaBTWGBZqlxXq5+emJWTpKtdYVldXllZWFpdWFNWXV1bVFNaWl5fXVxZWVSVk5aTgn6AfnlubG5va3qGlk5JiYKUTZOIhHZ/kJedrK+sqbBcWVhbWFNTUa+1sKu3XF1cW1tQSklMm5qblZSXUFKdnZudl5WTlKKAopaaoZOSjYGCkZaPlJ6fnlhdVJqYn1FVV1Wjp1qoqFRUUlGcl5uVTFdZVadSV11ST1NVUlNZWFRWXFxWVlKnVVdjaGVlZWdrbHByc3JuampscGZkX1ymq1zAtqaampudm6q0x62mm6GxWVVZVmBkZWGyXWJduGBobHFuYmFiYmCAYbiqm6WvZWVpcoeIfHp8d3VwaF9bXVxcs6qnn5aZolleZm13inlya2Fetlleo6Kal6pbsrW4XKipuL23XVepq61VX2NjVlhfXWRlY2hrbGCinJyalZWUk4iJlpmam6Glpl5jXrFdvK+0s6u/aXN3cHt5b8CzwcPFZWZpy71kbW2AW1aYnZiPmJ2WjouQioOJl5yVlJuXlpSbVFlYV1dZXVpVWV5nfXpoY1iglJqlqF1jZGNdkXiCnapVo5WKh4x/gZeViYhOXFdUVVJSUU9MS0yKjYCJR1ROSotIT1RLSYeFgINKTktNRkZLj0tPUExOVFhVUlVXU1JPVlZXXmdoYWeAbWxnYmReYmhfXltfWlhSSk5TWVpZT5GWVFZZXVaZdGhbYXV5fH2BhpSFgoGKUlOemp2cm1FUU1ZWWVtbW1haUVVbWVJMUlFOSkhPVlpdW1RWUElQVFFLSkpNTEtMSEtQSkI/Pj9AQ0lLTUxKTlFQUE1PT0pLTlNcaHJ6e3hwc3OAYlFMUV1hWVhUWWZjXFZaXFxhbGxzfW9nbWZpbGVhWk5IS01RUE5RXFGPe3uJiIOOk4dJSU5RTEhOR0VGQoh/bGVhbG10cXNvdXBsdH6AbW50fX+NkY2Wl5GUm5ydUZ+aUFBWUldXlYB7dWxqeIiYkY6hWmx2bmNVl4yIhJtbWFmAU5eUlpWPiX6AhYyRjZxTWGJiWFKWi4eLTpGSkYuKipFKkIaFeYONUV1iaGddX19UWltZW11bWVZOkYWOkIF2fH6Gj4N/f4iVi4ySjYiQmZmZioWHj09RUllaWVdZXV9kaW1lXV9nbneDjoVxWlJUVlVTTlNXU1FRTklMUU5IiouARlBWVltcWFheXlVQTlVNQ0NEQ0NBe3NveEJCRU5TV1pgYmxwaGFjYWNwcmRZV1RMUFFFRUxKhn97dnl8dnx/eHZzaFteaHaGVGxsamZdXFpSkIqEen2Jkk5PT1VZYmJnaVlXU1GZjIyanZeZmZ6dmZxRTlGNgX10dYWUiYd0cXWAc3Z8iIuHi4iMjotJTVVMjk5KkIOBfHl2hotLT0lOT0lIR0hKRUBDR0dHQkJJSU1OS0hFRUJ1dnt6amdraWNWVFZXTltmdT87bGV0PXRtallgb3R5g4N+fIVIR0VIRkZJRpOWkIiSSk1OTk5HREJEiomJgYCGSEuPjYiMiIV/fYmAiXqAiX+Cfnd6houEh4yJhUtSTIeEjEdJSkiIikyQkkdFQkB8e4B8P0dGQYBASE5FQ0dJRkVLSkZITUxHRkOIR0pUWVZYVlZYV1tbXFpWUVBSVk1LR0R3ekOMhXpvbW5wbnuEk3x5cHaGRUJGQkpNTEiBQ0hDhUVMT1RRRkVGRUWAR4R4bnqCTk5SWmxvZGFkYF9YUUdCREREg3t3cGdrdUJGTlNdbl1VT0ZEhEFHd3Zua3tEhIaHRXt5g4eERkOBgIJAR0pLQkRLSU9QTlJRUk2EfHx6dnR2d2xue319fYSJi09TUJtTo5OYloyeWWRmX2hmXJ2Qm56gU1VUn5lTW1qCepZ5kXqFeYV6hXkBeot5jHqEeYR6AXmFeoR5h3oBeax6gnmFepB5gnqFefV6iXmLeqB5A3p5eYZ6jHmGeoV5hHqNeYZ6hHkBeod5AXqGeZJ6nHmpeoJ5lXqEeZ16knmJeod5jXqMeYN6l3mEegN5enqIeZt6kXkGenp5eXl6jXmIeoV5iXqGeYJ6mXkGenp6eXl5hHoFeXl6eXmEeoR5hHoBeZJ6AXmXegN5eXqQeYh6BXl6enp5i3qFeZJ6h3mLegN5enqFeQV6eXl5eoV5BXp6eXl5j3qReQV6enp5eoZ5h3qFeQh6enp5eXp6egICBACAgYGDiYaA/P//+vDn3t3Y0dPc4ubu8Or39Ofv+YaL/O/28YCMiP353uH08vuJlJWIg4OHhIeGhIeHh4WKioaB8/Xy3uLn84SChYb47+Tr/oKDhP2CiYH59vfx/f7x8PT6goOEhPb1+/768/b/gID47vz3/omC9v6AgIOIjIuNg+Rs2eHu9YCB8uff3ujv9O/p9Pno4uv7gfz3goj88faD//3u3uH3+uXc3NXV3dTW09/r9fqCgfz/jIb8/ouRiYX749vh4+jy7e7t793c7O/z9uPZ4fL8gYOMlpmVnaOlp6yzt6idnJ6hoZeSlI+KhI2Aj5KQjYmFipSYk4iIlqm0s6WYlZmZl5OOi4uNjoqKjIeAgISFjJKTlZaVk4uEi5KUlJSVj4iJio+Sj4aIjouE/P6Eh4mRkYv98vnw+4P+gYT9h4iJjZaUkpKVmZCGhYmKiomSkYb36NjZ5N7Z19rZ2d/i5d/h6Pz26NfZ09Da1ueA5/Hh2d/V09HO0M3H1tXRys/T1N3r8PiBj42EiIuKgfyC/4OC+ufh1dTd7O7d1NXb1+b07Obu5NjrgoP/hIiA5+b3+/rs5/WBgeXZ2Nji6O/r6Ob0gP/1gfjo5vP38/GAgoOB/fn6gYWEgPj5+ICGhoWKjIqQlJyhnp2clouBgoOAgYSG/faAhP7y4+Ly//+Bio+TnaShnZSRkoyJio6TmJ6otMPQ08m0n5CJge7r/f+FjY2NkZSSl5yemZCSkY6KipCQkYuJiYuJj42OlpaSjYiJi4mNkIyVm56en6CejfDr+4CDhISQmJ2os7Gnop+bk46LhYWEjo+PjIqKjI2FgYiAjYmLi4f/g4WHiPvi2eDa2drW0+Xn6eTj7/H49vT5/ImE/YSHiI2Li4P/gf+A/YWH+vaAgoSFipWWnZqampCPj4SHh4eMj4eJkI2GiYWF/ISKjImIi5OXlI+LkZWOkZCHhoGChYmSmJmYk46NiYODg/Tr7O6Cio+PlpWNiYyQjouAjIqGg/bw6Orz6Obs6+nr9/Pg9YaChIuOj4Xx8PDh2tzg7PT7+erz/fT4gYSCgf3984CLkJOTlZGF/4WJkpGUlJCOkpaRhPqA/oGIi4aKhYaFh4Pz//fp4/L6ioWD/f2Hj4aEkZKPkZWUj5CblIuGjJCVl5eYmIyLkYj56+Tx/4OAgoSHipCTkJCNjIuHgZCPkpGQjo2Ngf2DgICBh5CTjpKSjoqHhf/6/e7h5PPz5vDv/ImKlKWqn5WMhPbx/ZSHi4X89ezz+vGBjIyLh4P+/oGIh4OEhYeB/Pbx6ODn+IOD9Ovt6+Pl5uvo7P+Dg4eHgoH58O3t7ebg6/fz7uzc19cQ0Nfq8evi5uPY3enx+IOGg4BhYmVqaWTCw8O+tK6mpaKbnqewt727s7zAt7zFam7FusG9ZG5qxMKuscDEz3F9fnJtbHBsbWtqbmxpZmprZF6zuLalqay1ZGBfX6unoai9YF9gtVpgW7Gxsqy1tKmoqa1eXlxbpqSmqqeipq9aXLKptrO5ZV6ttl5dXl9fXF9XkoCKj5aXUlaoopaRmqKmn5eZm5SUm6NUrKpaXqaaolqwrqWZnbC2p6Cgm56moKKdpbC6vmJfsrBkX6+wZGtnZr6spaejpq2qrKaklZSfnqCll4+WoqhTVFhfYV9ob3FydXh7d3V4dXFtZGJmYFhaXFtaW2FmZ2ZjZmxrZ11gbXp/fIBxaWlxdHVzbWdnamtmY2NfXF9kZ2xxbW1pZWdlY2hucXFxdnRwcnBvaGNdYGlnY77BYl9gbG1krqGknqpaqVVWpVtdYGNsbWxta2hbUFFWWFhYYGBbqZ+Rj5aOhoKGhouMiYZ9goiXmI5/fnl2fXqHhIuCfoR7d3VydXVxgIB8d4B7gIKGkZWfVGBeVVdYWFOoWq9XVKCSjoKAhZOXi4SFjIeOlZKPlIl/jU9PmlJUTomLnp6el5OXTlCJf35+iI6UjoyLl1GellGbjo+YmJKNSE5SUpuVkkxTVVGam5lPU1FRVVhZX2Rqb21rZ2FYT1BRTlJWn5lRUpyShYOQm5xQV4BaXmZsZ2JaVVZTUlZYXWNocnuGj5KMemdZUkyHhJSZUVdWVVtbWl9lZ2JZWllYVFVaV1dSUlFST1RVVlxbWlhWVVZUXGJeZmtucnZ4dmajn6tWWFlZZnB2gY2KfXdybmNeXFlaWGBgYFxZV1teWVZXWVdbXFmpWFtaWKGPjJaQjoCOioaUlZqZmqSmrq+rra5jXq1aW1laWFlSnlGiUaFXWJyWTk1MTlVeXWRkaGhcXFxRVVVTVVVOTlVSUVdTUpdTWlxXVlZdYV5bWF5jW19gXWBcXV1eZmpoZWVdWVVSVVitsLGrXGNjXl9aUk9VWltbXltXUpeRhYiVjYyTlJSUnoCdkKZdWVlcXV5Uk5aViYeMipGaoqCQlJ2YnFJST1CjqKBTXF5gX2BdUptSVl9eYWRkYWFiXFGWTp1RWFpWWVVXV1hWn62onZShp15YVqWlWmFaV2BjY2RoamdocmtjYGZpbnR2d3hsbHRrwri3x9BnZGdqa21ta25tbWxoY3FxdIBzcnFwbmPGZmFhYmZrbWZpamZjYV+vqKickZOipJympK1fX2l4fHFoX1ecna9sXmFcsrCnqa+kWGFgX1tYsLFaYWBbXFxcV7CtqaCbpLNhYKmgqKqmp6esq6/BZWVpaWZiubKwr6+rqbPAuKymm5icoam0tLGqqqiiqbe9wmdqZoBSUlZcWlSlq62onZSKiIWBhIySl52blqCgmaCqXWGqn6ejV19apqOQlKOkrF9rbGFcXWNeXVxaXVtaVltaU06VnZuJi4+YVVFRUpKMhYqdUE9QmE5UTpaVlpGdo5aPjo9NTU5NjpGVlIyGiZFJS5CIlI6TUkyKk0xMT1FTUldOf4B1eoGARUeIhHt1fYOGf3h9g3t7gYpHj41LUYx/hkuUlYh7gJedjomLhYWLgoN9hIqRlExIhYVOSYODTVROTZGCfoR/f4WBgXp4aWh0dHuBcWlueHw9PT9DREJITk5MT1RaVlVYV1VTTUxOSEFCQ0JBQkdJSUhGSlBPSkFET1thYIBYTkxRUlVTTUdHS0xIRkdDP0BCQkhTVFRQS0xJRkpRUU9QVVJNUFJTT0pDRktLSIyQS0tNWFlSjYOGgIhHhENFgkpNT1BYVlZXWFlSSUtRU1RWX15XoJSEhZCLhYKFg4aIh4eAg4aTkod8gHl2fHiHhI6Hg4d8eXl2enhygIB8d4B7gIKJlZieUl5bU1ZVVU6dU6BQTZKFhXl4fYqNgn5+gn+KlI6IjoR6iE5NmVBRSoSImJmZkIuQTEyBd3d5g4qUl5WQmlKin1aklZSfpKCcUVRUU6OkpFNZWleop6NTVVRUWVtbYGRqbWtpZmJaUlNUUlRVnJVPUJiOgX+KlJRLUYBTVFtgXVpTT1BMTE9SV1xgZ253f4V8bVxRTEZ9fY2RTFNSU1hZVlldX1tUVlVTT09UU1NOTk5PTFBPT1NRUVBOTlBNUlhWXmJjZGZnZ1mPi5hMT1BQXGhweoN/dW9qZVxXVlRUUVdXWFVTUlRWUU1QU1FUVlSbUFJSUpWDf4iBf4CBfHeEhYeEhI+Sl5aSk5VVUpVPUE1OTE1FhEOEQYFGR316QEBAQUdRT1NRU1NJSktBRUdFSkxDQkdEQkhDQnhCSEpGRUVMT0xJRkpNRkxPSkxISUlKUVVUUVBLSUhHSkuOjY2JTFVVUVNRSkdMUlNTVFJOTZSMf4CKgYCHiIeEi4CHeI5STk5UWFpSjY6OgXp5dnyEi4l8go2JjEdIRkaOkIZGTE1NTU9NQ39ER01KTU9OTU9STUJ5QYRESEtIS0ZGRUZDeYaBdXF/g0pGQ4B/R09HRE5PTU1RUk5QWlNNS1BTWF1fYGFVVVxUlouHlZ5PTU5RUlVVUlRTUlNQS1dXWYBXVVJRT0SISENDQ0dMUEpOUE1LSkmFfoB2a256em93doVQUVRgYldQSEFxb39TRkhDgH52eH50QUpJSkhEhIJCSElFRkZGQoSCgHhyeolLS4N5f4F9f4KJhoeWUFFVVVJRmpKRj46Jgo+dloyHfXt+f4WUmpOHhYN/h5SanlVZVoZ6lnmCeoR5g3qHeZN6h3mEeoV5B3p6enl6enqKeYR6iHmCeoV5BHp6eXmIeoV5gnqPeQl6eXl6enl5eXqUeQh6enl5enp5eYR6lnnbeoJ5hnqFeQV6eXp6eZR6snmIegV5enl6epV5Bnp6eXp6eoh5gnqLeQR6eXl6h3mEeoN5hHqDeZZ6BHl5enqHeZ16hHmveoN5pHoBeYR6lXkDenp5h3oJeXp5enl6enl5nHoBeaF6hHmQeo95h3qQeYR6g3mIegF5jHoDeXp5inqHeQV6enp5eZt6hXmXegF5jnqMeYl6g3mEeoZ5hnqCeYh6h3mCeot5hnqceYN6AgIEAICA/+rV1OmAgeLWz8K0rKuvt8no8+3o393TxLi0w9jl4eLk4tnk/4WG9O7y69/Pxs3Pztbd5u3z7fT6gvmCl6Omm5ego6CjmY6OiYqQiIaB+f79/vLv7enzhPzh6/P3/f6EgO3z+ICCgvyCiI+J+eLi8P7//IGDgP3+/IWIhIGDhYCHh4OCgIaLiYHx8ujj7enu6Nvn+4KC9/X48Oni7vT6gPnp6e3v9e/b2+Dk9vnw8Pj9gPz38unb1Nrk7vX4homHjYeC/Pv+hIiKjJmiop2bl5ulus3S19nR1Of98ejWuamgm5KJg4KE/fSAhIKGkZidoZmOjpaZlpebmpaUk5OhpIChoaCXlZiWj4mIioaDhomGhYyPk5WVl5OQj46Rl5mcmZaOi4+NkpONiIX/gYOHiYmGgfPm2Nji7/Dw94WKjpCPk5WUlpyiq6eqsrCmpqarpJqTkpOVlIuLkI+A5t/k3c7JxMbQzMnJ1t7c1tLT2cvGx8TNyMnX0dba19zi6eDWzIDMzMjEtK2yurO0s62wrai0usPCy9XNx87N0sbJ5/r3/ICJi4WB/evTycrKy9Df6u3p6ubp3s3Y6/Hv3tHZ5+vj6uzd09PRzcbEyM/Z1d7b2uXk4eHn8fLv9/b4/4qHg4aKjImC+vn++OTo+4OA8+jyg4WBgoWNjISA+/r+/ff8+YDw7fyDi5KTk5acnJeQiY2Vm6KnrbXA2OzXsqekmpSWjomIhYaIiIySnZqUkoqHgoSDhZCSk5aOjYyUnJ+bjoWJkZialZWUl5WNi5KWk5CLhYT/+P30gYaC/oGMi4mFhoSCgvn16u7/g4eTmJOPkJWOgoL++PX19PL1/PDh5fn46YDl7PWDgfv6goiMjYqHiP729e/6/P6ChISCgYGB/fj2+oKDhImJgYSFhYuFiI2VlZOXl5OSmZmanI2FhYSDh4yOjImGg4mIg4KAgoaNjYCAiImGhIWFgYCChYiG/fHZxsbM1eTu9vyBh4yKh4X+8fiHj4n56Ofx/4OC+u3n7fiBgTj97Nzb3+/8+PP3+fn8hoeA9/j1+YaPj5OTkJWZ4MG1tbvI4viDi42NlpqUl5ugpqOVkZGTkoaB/ISBgIaFgv2AgPrw7PL8hY2D9ODZ4o6C3+T38fP5+YWGgoaBgfTz9Ozn7vyGi4aA9/eAh42SjIH8/Prs6u3g6fr48u7u8/bz+v6DhYOBh4yQkJCTlpGF//Xzh4uTk5KVjY+F//zt5e/6+vv0gYyTjoiEgoGA+oGEgPv9gIOD+PqBgf6ARPv49fHp5O319Pb//f748+3m5vL3goD2gYGDkJuYk5CLjI6JipqhmZCJ/OPMy9DV4d3RzMrLzc7O0Nbq9e7r7evp6u70gGjUvaSkumlrtqmilouFhYmNnbe9ubiwrKKWkJGdr7vBxci9rbrPZWa8vMXBtqafpKOirLa9wcbDztJqxGd7hod7dn+Df4J5b3FubnFqaGK5v72+tbW1sbRjuaKttLe8vGJerLO0Wl1gtlxfZWCrmZuotLWzXF5btLW2YmRgXl9egF1bV1ZVWl9fWaaqo5+nnqWjmam3W1utr7WvqaW0ub5ivbW2t7vCv6umpqq7v7e2u75euLWxrKSgqLC4vbpiYl1jZWO+u7lgZmtueoF/eXl1dXyNmZmeo5+muMi5sKKKf3p1bGFZWFutpFdZVlljZ2hvb2xxeXlzcHBxcHNxcHhzgG1vd3Z2fHx2b2pqZGFkZ2NkbG9zeHt+eHFsaWtwcXV3eHNxc3F2eHNuasZiZGNgXllUpZyUk5umpaSpXF9jZmdscG1tc3iAe3yFgnh5eoF6bmZlZGNiWlhaWU6LiI+KfXhzcnp3c3J8gYF+eHd+cnBwaG5qaHRtcXNuc3mCfHNtgG9vamNXVVxkXV9fWl5bWGJnbm95g312e3l8cHCGlZijUVdaVlKhkn52dnh7gY6IhIaNjZKJfIWXnZ6PgIGJjYmRkoeAg4F8c29zeH53fXp3gH5/gIWLi4ePjo+VUk9LTlNTUEqNkJSNfH6PTEqHfoRKTUpLT1VTTEqRjpCPio6MgISBikpSV1ZVWFxcW1hSV1xhZ2xxd36PnIxwaWhgWl1WUVFOTlFRVFxmYl1bVFJOUE9RW11fYlpZV11lamZbVFhgZ2plZGFhYVxbZmpjXVdUVKekp5tTV1OhU15eW1ZYWFZXpaGYm6pZXWhqZmNma2ldWqumpqyrp6mtpJqhtLOmgKSstWBdsbBbYGNhXlpapqCem6SorFlbWlZUUVGenJueU1VXXl9XWFdWWlRUWF9eXGFiX2BnZmhrXlZVVFJVWFlXV1VQU1RTU1FTV2FkWlphX1xZWVdRTUtMUFSvsZ6OkJOTmZqbm01OTkpMUZ6UmVZcWJmKiZGfUlKck42QmlJRgJ+Vi42TnKShn6OhnJ5XWVSoqaSlW2NgYF5haW+JbWNlbHiSpFZbWlhgZF5fYGFoaGBeYWZoX1qoUlBRUFRVVZ1NT52am6OvXGNappeQmWhdmJ2spqirp1pZVVxaWqOhn5yeprFeYV9dtbdhaG9yamC7vb+2tLqut8nLxsTDxsjEgMrKZmRkY2twdHJydHNtY8S7uGdsdHVydm5wZbmypZ6lrKajnVRfa2ljXVlWVadXWVaqrFRUVKCpWl68W62rrayloKevsLC5u7+5tK6qq7W7Y2K6YmNlbHJzcW5rbHBraXV4cXRzxq2ZmZueqKWamZiZm5ycn6Ouub2/w8C9vb/DgFGllHx8klZXkoqFdWliYmRoeJOal5SLiYB0bm98kJqbn6KajZmuVVaalZ6el4uAhIOBiZObnqWkq69brFpqcXJnY2tuam1mXF5aW15WVlGYnJqdlJSTj5VSmoWPlpmfoFRQkZeYTE5QmE9SV1KSgoSPmJeVTVBOmpyXUVJPTE5NgE5NSUdGSk9PSIWKg4CHgIiEeYaTSkqMkJeSjYmVmZ5SnpSTkZGVkn96enqJioB/g4dEhIOAfXhzeoCIj5BOTUhNS0iJiIlJTlFUYGdlX1tWVltmcHR5fHh9ipSHgHdjW1hVTkU/PT95cT0/PD9JTk9TUUxPWVtWU1FQUFJPT1lWUlJVWlRRVFRRS0dFQT9CRUJAR0lLTk9SUE5NSkxQT1JSUUxLTkxQUk9OTY9HSEdGRD87cWpjZG9+gISNTlBSVFVZW1hXW19nZGZwcGhoaW9oYFuEXYBWVVpaTYeEioZ5dXBwend0dH6DgX14eIB2dHNtc25teHF1eHZ7f4R+d29xcG9pW1leZl9hYFxiYFxnbnRzfYaAeX9+gXZ3jZqYnk9XWFJQn5F8dHV2dnqGhYSFiYaMhniCk5aUiH6EjpCKkJOIg4SCf3h1eH6GgYmHhY2LjIyRmYCalp6cm6JZVlFUV1hWUZqcoZuLjZxTUZiNlVFTTk9VWldQTZmYm5iRlJKJhY5LUVtcWFZaWllXVFdbXmJlaW91hJKFamNiWlZZU05NSklNTlBUWldTUktLR0lHSFFSU1dQUU9VXGBcUktQVlxeWVhXWFdRUFldWFNPTU2WkZaLS4BQTJJLVVNRTU9OTU2RjoWJmFBUXmBbWFthXVBOl5WTlZGNjpSJf4OVlIqJkZhSUJaUTFFXWFNMTImEgn2GiIpIS0pGRENDg4F/gUNERUpKQ0RFRktGR0xUU09SU1BQVlVWWUxERURDRUhJR0VDP0JBP0BAREhPU0lHTEtJR0lHQoA+PT9CRo+Qf29wc3V9gomOR0hIRERIjYeNT1RQjoODjJVMTJGHgoaPS0mNg3p9hpCXlZadnpiZU1RMlpeSk1BXVVZXWmh0hF9TVFlkfI5KTkxJUFNOUFJWXV1TTk5QUktGhEFAQEFGSEeDQUOEgH6Di0pRSYJxa3RXTnd4iISFiYCHSktHS0lJhIKCfXuBikpOTEmOjUpQV1xUTZydmY+MkIOMnJuVj46TlpGVlUxKSkpRVVhXV1lYUkmNhoRMTlVVUlVQT0N9e25nbnVycW48Rk9NR0RBQD97QUNAf4JAQEB2fENFiUSAfX18d3R7gYCAioySjoiBfH2HjExLjUxNTipWXFtYVlRVWVVUYGRdXVybhXJxc3eBf3RzcnR1dXR2eYSQkpOVk5OVl5kBeoV5gnqeeYJ6knkCenmTeol5AXqHeQl6enl5eXp6enmEeod5Bnp6enl5eY96i3mCeol5AXqReQF6i3mGeoN5oXqCecF6AXmHeol5oHrFeYV6t3mIeod5BXp6eXl5iXqKec56hHkEenp6eYl6hXmLepF5BHp6eXmHeod5h3qEebt6i3mGegZ5eXl6enqFeYJ6hXmCeo15g3qEeYh6iHmTegF5h3oDeXp6hXmDeoR5gnqHeYZ6h3mEeoJ5hnqSeY16g3mJeol5iXoPeXp6enl5enp6eXl6enl6lHkDenp5knqbeQICBACAiJGXlo+JhYOEhoeJjI+RjYT++PXz59/f4+fw9PT37+bcz760sLW9vrW3wMa8sra9vLrE29/RyM3IyMbO0oCdpJ+fn5OJg4H3383O09To7+fm39TX1+Li5Ors8/yBgv7x7/yEhob9+PqAgIOKjZOWl5OKgP+Lj5SYm56akY+Pjo6AjoyLjY6JhYWJiYn76u7q4OHq6eTo6+LSytLX2+Dk6fSHjY+OioeC9/Dw8+3sgYeGhYKDgf788Ozs/fz3/IGGg/+DiIiEgISHgYGHjI+WmqKmrbSqpqWkp6mmpJ2anaKgoZ2bmJCG9ejo6+fe2t7i4ufp6+76hYWEhYb96uLl8fqA9/L29vfz7+3p5vGEj4+KiIeGj5ycko6Ul5SVmJyfnZuZmpudpaWbkY2Pk46A9fPo0cjN2NbX4ufh8Pv28+rm5OPe8fry7Ov3+e/t94GFiI+Pj5WZnp2gnZGPkYmB9fP7/4GEhID29uXj5/D7/oKE8+Tp+/bPutbk7oDjz8zP1eSA8vn38Orl4enp49fR5uPk64GNifzq4drc6+vf2tPOzdPU0srGvcC/t7nH2Ofk6ejs7/H59erXy8rL0M3L0tzW1s/M08rI0NDY5e3y6+TYzMbDxtXX0dfd2+b6/ffv5OPygYH17OHay8ve6faEj4+OioaGjJKWl5OI//789eTLuLOAtr7I0NLc9IePlJeXk42E/P+EiYmLiouMjo2LhoL57d3Z3eDa5e7w9PDs6eHi5+728/j+hIWFg4KAgoGCgfz3/4qLjYuJh4aFg4eOkpKKgPT3+/6ChP/9gfPu9f6B9/Hy9vb0+f72+IKJk4+E/vzu5uXg2MfL19na29/g5+b2hoaAi4+Mhvn39/Xq8/zy/IX+8/T2+PyCjJmUiISFiJKTi4iHipCanqOqp5eH/Pbz8PP7+4SIi4+OioSFhoiPk5iVko6JhoOFh4mKiouTjYGIl5eWmJqdoKSei+/d4uns7fL09vj38vT6gIKD/v3+hIL+gIKCgPby+YOG+vj7gIH7/f2Ag4P77+Pc4urs4+vz7OHi6efn6unj5+/48+zq8Ozj29nd5evs6Orn7e/p8/+Dh4yNioWFgfjy+v+BgoWFg4P18PuBhoOCgoD79vuAgISGiYj//Pf79uXo8PLu7u7o6PL8gP6BgID9+PD3gYOD9+7t7vz/+/Pv7+3p6ebt/YOB/PSA7e/r5fb46/H0+fPt6NXPycLE09zY297k4djd6ODf7+nj2tLX6oGB/fXu8YCC/oD8gIH09PmBhIuMjYyIhYeD+vaBg4P58ez2gfr2+ffq2tvo7d3V5O3q7Pf+goOA9Ono5ers9OrYzs7O09jZy8fNzs7K1enz+IL22dHQ09nn8f2AcHZ6dnBtbG5yc3Nyb2xtbm3W0dHRx8C+vby8v8nSy8W6r6GZl52jopaSlJSNiZGZl5ahub6xqK2qrKevs3OSlIqLjIB1bWjDsaSmqKW1urGwqZ+gnqWkpqyurrFbWrCjoKtZXF2tqKpYV1tiZGhnZF9cVq1iZmtwdnp2bmppaGqAa2tsbW1oZGRoaWm9sLa0ra63squzurCelp6kqrG1uL5obGxqaWhku7KusKurYGZnZ2NlY7+7rKentbKrsl1iYcFma2lkYGVpZWZqa2xxcnd6gYeAfXt5fX55dXJydnp4eHRzcWpfqJqYmpeOjI+Vnq22u7rBZWJcXF+4r6ypop2AlpWgrLe6vLu3tb1mbm1qaGdka3Z4cnB3eXR0d3l6d3R2en1+hYV6c3BydXFlxca3moeCh4WKmJ2aq7axrJ+ZlJCKm6OdmZijpZ+fqFpcXWNiYWRma21ybWJhYlpSmJWam01MTkuOlo+Ump2cnFJWnY6Pop54YneBjVGGc3B0eISAk5qWjYeDg4qFfXJugn5+hk1XU5GEfHd4hoZ8enhydHx9eXNwZ2lpYmJvf46NkI+RlZWbnJaDdXN0eHZ1e4N+fnl0e3NudHR7h5CVkId+d3Fvcn5/eX6CfYOPkZCOhoSSUVGZjoB3bHCBiZJPWVhYVlNUW2RoaWFVm5iWjn9rXVuAXWJqc3h/lFNXW15eXVhRl5xSV1VXVVZWWFlZVE6SiHp4fn15ho+Sk4qChYKHjJKYl56jVVZWVFRTVFNVVKKdoltcW1lYWVhXVVheXVtVUp+lqaRVV6mqWaaipq5arqilp6eosbausmBncGxgurmrpKKblISFkJKXmJiYnpunXV2AYWRfWqSipqOYoqqgqVurnp+goqZYXGJhXl5kZ2dkXlpZW2JrbW90c2ZXoqCfoaWrp1NXXGBeWldZWFlfZWtqZFtYWldbXF5fX2BoZFhda2hhYGBkZGpoW56WoqyurKmjnZqYkZGORUZJkpSYUlCbUFJSUZeSmVJRmpueUVGcnZ2AT06enpePjpKSjJSbk4iJkJGTlpaTlpympJ6ZnJiSi4eHipCTkpaXn5+XnKVUVVVWWVRST5eWpa9ZWl5dWViinqVUVlJRU1OkoKhXV1pcX1+ysaywrqGjq66rrK+noqq0XLVbWFasrKWwXmBhtK2tr8DHw7i0tLGtr6+0vGBgvbWAr7O0scLDt8HEyMO7tqKcl5GUo6+sq6mrqaCjrqaltrGso5qfsWRkxb2ys2BjwmLBYWG3t79nam5raWlnZ21mu7NeYWK7rqOrW6+ts7WrnJylqZ2ap6+qqr3MaGdju7Gxr7a9x72roJ6cn6Kjm5qgoqKfpbO6vGG8rq6wtLrIztSAW2JlYVpXVlZYWVpeXltZWFWkn56flpCRkpCPj5aemJOMhHhybXF2dm5sb3FqZm11dHR9kZOGfoSBgn+HiVp2e3NzdGtiXFmnlIaJioiYnpaYkYiLiZCNjpOVmJxQT5mMiJFMT1CTkJJLSk1TVVlXVVJOR45SVVlcX2JfWVhZWFmAWlpbXFtXU1RYWFmhlJqZkZCYk4yUnY97cnh8f4OEhopNUE1JR0dGg3x4eHR1RUtNT0pLSo+Ognx7h4N6f0NIRoZITU5JRkpNSElMTk5RUFRVWV1WVFRSVlZRT01PVVpXV1JQTkpCcmlpamVdWVteYWtzeHuDR0Q+PkB6cm5raGWAX15mcHh5eXl3dHlDSUdFRUVCR1BQSklQVFBRU1dZVlJRU1VYYGBXT0xMUExCgYV6Y1ZVWldaZWljc358fHV0dnhzgYiBfHqDg3t5g0dISlFRUlZZXV1hX1ZWWlRPkZGYmUxNT0yRl4+Rlpqdn1RZopKTo554YniCj1OKdnF0eIKAjpKNh4SCgomGgXh0iISDiExWUpCGf3p8iol/f314eIGCgHp4cHRzbGx6iZeWm5qcn5+nqKKPg4CAhYKAho6IiIJ+hHt3fXyCjJKYlI6Ee3d0doCAen+DfoSQkI6Kg4GOTk+UjIWAcXKCiJFPV1ZXVVNTWV9jYlxRlZSSi3xqXFqAWl5jam52iU1OUFRVU1BLjJFNT01NTE5PUFFQSkaDemxsc3VveoKDhX94fHl9gISKiZGWTk5OTEpJSklKSpCMkVJSUVBPTk1LSEpPT05JRYaLkI9KTJKRTI2Kj5VNkoyMkJCPlpuUlVBXYV5Snp2QiouHf3J0f4GEhIKBhIGMT06AUlRPSYKBhIF2f4Z9hkmIfX+AgYRFSE5LR0ZLT1NQSkdHSlFbXl9kY1ZHgX9+foGFgUJFS09OSkVHR0hMT1NRTEZERkVLT1FRUFBWUEVLWVdSUVFTVVxaTYJ6hI2OjYyKiIiIhIaFQEBChIeNTUyTS01MTJCMkk5Nj4+TTEyRkpKASkmSkY6JhoeLipeflYiIkY+LioiCho2UkIuHioiCfHd4fIOEgYODioqChItHRkZHSUVGRYF+iI5HR0lJRkeBfYVFSERERkaLh41JSUtNUFCTjYmMiHt+hoiGh4mDgImVTJRKR0eQkIiQTE1OkIqKi5mdl42JiIeFhoSIk01Nl4+Aio+Oipmaj5aXmpWPi3lzbWZnc315eHZ4dW5xe3NwgHp0bWRpeUZFhX93eENFhkSDQkN7eX5ER0pIR0hGRktGf3pCREWCenN6Qn1/hYZ9cXB4fHFufIN/gJCaTk5LjYWGhImNl45+dHJwc3d5cXB2eXl0eoeNj0uPgoGBgoeUnKaReqx5inqVeYJ6hHkGenp6eXl5i3oBeZd6lXmHeoZ5h3qJeQR6enp5pXqPeYV6kXmiep95kXqEeYR6iHmCeop5AXqWeYN6zXmCeol5jXqPeYh6gnmMepZ5inqDeY96hHkFenp5eXqEeQF6inmFepJ5hnqJeQF6hnmWeod5p3qOeQl6enp5eXl6enmEeg95eXl6enl5eXp6eXl5enqqeYh6hHmGeoN5hnqDeYZ6kHkFenl6enqEeYN6kHmCeqd5gnqEeQp6enl6eXp6eXl5inoFeXl6enqEeQF6kXmDepl5AXqJeQICBACAtsDT1dHT5Ozo4OPs9Pb5+fHv8+/n3NfT0NHMvLexqqqqqKy2v77AxMjCx8zT39zY4uTi1MbFx8vP09DV0c7e7PqGgOTg5ezv+YSEgYaEg4Dz8vGBh4WDg4eJkZmcnZmUlJaYkYqOkZKPi4uOj46Tk5CPjZCQiYDy7u3n5O7+hotqjImDiJKSkpidpKKbmpSNiIL99fH5gfXs6vT/hY6RjIqChJKYlZSQjYmOk5KUmZOUmJeVmJednJuWkJCPjIaChYuSk4+Mh4mJiImKjpOTkJGTmZycnaOmpqalo6SopqOckYmBgoaJiIWHiISLgImGgffv9fz59/b/+/bz8viAho2SjoTv4d7e3+6Ah4eIio+NiISAgY+ZmZeWkYqGhIWGi5KTl5yZlZOLgOzygYH68fXy9fLw9oCDiI2VlJebnJiYmZean52emZebn6KkoZ2Zmp2anZqVk5KLhYeKioL9+u3j3eLq59/d2t3s6uDvgIKEhIuUkZORjoqF/+3n4OTn4ubv8fWA/vH2/vn49/aChP719e7g3dzj5ezy7uTbzsnY3N7p9fv48/Hv9IGC+4GC/e/q3tLJw7+8v77Eys3T19bV0Nbl7OLf2tjVzsXAure4vsLIwsPCys7U3Obc2dLNw8LByNTd3uHg3drW2uTvgOvj4OPr7O/p49/i5NzTzc/T0s3IxsC7wtjn6efi2tbc7vXz49nY3d/Xy83Y8ICFhf3w5uj3hYmGiI2Nh//38vP7gIODg/7+gImSlpWSjoeB/fDx8vXz9/z/hIOEgoL38Ovs6uLq+fnv8ICJh4yan5iC6d3n4uOBjY+PkYyJi4yKgIeD++7n6uvo7fGBhoOA/YCFjpGLh4eEg4OCgfj5/4Dy7faEiYqLhIH/+vj69Ojq9fyBhIGAg4eFgfPv7u7w8Ons8e/4/fX7/e/v9/r08/X28+rr6/CCiouF+fT6goKCg4mMj4+IhoSFgoKEgoSGiIWEhYaGhYH28/Tw7ezo6vP5gIGEh4yLiYeGhoeG+/Hx9fn39vr+9/P09vPy+/v9g4yPkIyIhIH8/oOEgoD18PeGi4mC+fL4hYaEgYaGhYSA/f3+/oeMjo6SlZWbnZaSj4yLjIyOi4WFi46QkpaXlZKQjIuE/YKJiIeGh4iIjIuJiIP49PLw4dXRz9jh5fD45uHogPX57Ovs9vrq5fL39fLr6euBg/Tp38/K0N3i4OTl7vqCgfTu6+Tr5+iBhoL49vqBgYWJgoGA+YCIi4mSnJ6XlpGIg4aNk4+Mi4uA7fOC//z6gYWA9uzp7e/t8fft7vmFj5GQi4qJi42JiYqQkouJj5+rqZ+VkZKTkZCSko2E8d3TEtro7efg4uPn6uvr5NvQxsC+uICVnq6vrLDAx8S9wszV2tnVzcrMy8K4tLCutLGjn5eOjo6Pk52npaeqrqissLbBvLW+wL2uoJ+foqasqK6rqLW8xmtltLG2vLzEaGdjZ2NiXrCuqlpfYGBfYGJob3N1cWtqbXJtZmhpbGtnZ2ppZ2xtbGxpbG1mXrGvsKqnr7xla1RubWdsd3p7gIKGgnt7dnJuaszFxMpnv7Oyv8tqcHBqaGJlc3l3eXZxbXF2cnF3cnF0cW5wb3NxcG1rbm9uZ2NnbXR0cGxmaGlkYmFjaGlobG91dnKEboBvcHN4fXx6c2tlX15fX1tZW11gYWRmZ2lowbSwsamnp7C0t73HzGZmZmhlXq6nrK2ywmdsaWhqcHBtaGJhbXRzdHZzbGdlZmdqbmxtcW9qamVdrLhkYruztLCztLS4YGJlaG9sbG5yc3d3cnB0cXFqaWttcHBwbGhscnJ1dHBsZYBbVVVZWlSgnZCHgYeNiIB9e36LiHuGS09QVlxYWVpYV1SfjoeAh4qDh46Nj0uWj5iinp2amVJSmJCTjoOAf4aGio2HfndsbHp7eX6HjY6Mi4mHSUyRTE2Th4V+dm1nY2JlY2hucnh9fHt2fIiOh4N9e3l1b2llY2JmaXBqbW10d4B7gIR7e3Z0b29rbnZ9foGBgH95eYGKiYOCh5CQkYmEgoWHf3dzdHV0cXBxbGdreoaGhYN+en2JkZGDenl9f3lvcXqJSEtNlY2HiJVSVFFTV1lWoJyYl55RU1NVpKVUW2RpaGViW1apn5+hpKWutLZcWVxaWq+qpaajnaW1tKutXX1mZmt5gHtlraCpo6JgbW1qamVkZmhnZGCzo6CmqKmsrl9iXlqwWF1lamVhX1xbXV1crrC5XrCqrVxdXF9bWrKqoqKelJiiqVVYV1haXl5bqKSjoqSmnp+inqaqoqipnp6jpZ+gpKelnp2bnFVbXVuqo6FRT09QVVdbXVtcXoRiBV5eXVtWhFSAUUqIhoyNjY6MjJSaUFJTVVNPTk9QTkuVkpSXmJWUl5uYmZyem5qdm5pSWlxbV1JQU6SrWVlXVaGanFNYWVipoqRYV1RSVlVUVFCgoqSkWFxeXV5gXF9iXltbW1xeYmZmX2BlZ2ZmaGdjYV5cXVinWGBfXl5fYWFkZGRjXrCqqamAn5WTkpulqbO6qqSosrWqqaipqaKksLW0srGsql9ita+nmJScp66vsK2yvGFgta+spayqql5fXLjAymlnaGpjZGbGZ3J1doCMj4iGgXl1d36DfHVzc2vGz3Hg2tRsb2rHvLrAw8LH0MrM1nJ6enl2dXR1dXFxdHt7c3B2hpSWjIMefn59eXh7fXlwzLqwtsTIwbvByMnKyca+u7SqoqCYVG93hoeEh5admpSYoamurqqin6GgmY6KiIiNinx6dG1vcG5yfomHiYuNhomMkZyYlZ+fm46BgICChoyJj4uJlp6oW1WUkpabnKNXVlJWUlFOkI6KSoRPgFFSWF9iZGBaWlxfWlNWWFpZU1NVVVVaXFpZV1tdVk6Rjo+KiZShVlteXFdbZWZmaWtva2RkXlpXUpuSjpNMiX9/jJRNVFVQTkhLWV5bXVtYVFhcWFZZVFRYVVJUUlhWVVJPUVJRSkdJTVNTT0xISklFREJDR0ZER0pOTktISkxOgFBSVFdaWVdRSUQ/QURFQ0BBQUNERUVFR0eEe3x8dXJxeHh4e4CDQkNHS0hBcmpucXSCRktIRkZKSkdEQEBJT01NT05KSEdIR0lMSkxTVFJSTEJ0ekRDfXl8eHt8fYNFSEtMU1BQUlRSVlhWV11dX1tbXV9hYV9bVlhdW1xaV1RRG0tFR0pKRYaGfnl1fIN/ent7f46LfohMTlBZYYRegFpWo5OMhIiLhYmPj5BMlo2VnZualpNPUJaPkY2DgH+GhouOi4R8cW9/gYGIj5WVk5CMiUhKkE1Ql4uJg3tzbWpoa2pudnl+goGAe4GNkouKhYOCfnhybGpqbnF3cHNydnl+h5GJhX17dXVxdn+GhomHhoR/gIiRj4iGjJWUlIqESIKFhoF6dXd5d3FtbGZgZHOAgX97d3R3hYyMf3d5gIN5bW11hEVISoyEf4GMTU9MTlFTT5SRkJKXTE5OT5iZTVRcX15cWVJNmISOgI2Sl5pQT1NQT5eRi42KgoqZm5KST1ZVWGVsaFSPhY6JiFFcXFpaVVNTVVRRTpCCfYGDgYSGS1FMSI1HS1JWUU1MSkpLSUaDhY1Hgnx/RkhISkdFh4F/g4F4eICDQURCQkZLSkZ8eXp6fX95enx3foF5fX1zc3p9eX2AgoF8f36BgEdNTkqHgYNEQ0NDSEpNTkxNT1JRUVJPUVJTT0xMTE1LRn99gYGChISGjpNMTU9SUU9NTUxLSY2Ki4+RjoyOko+QlJiXlpuamlJZW1tWUk9QnaJVVVNRmpKTT1NTT5iUmFJST0tOTUxMSI+QkI5MT1FQUlNPVFdUUlBQUVFSVFNMgE1RUlJTVlZTUU9NTkmMS1JRUFFTVFJTUlFQTIyHhoZ9dHJxeYKGkJeGgIaRlYuKi42NhoeWnpmUkI2OVFabkIR0b3R+gX9/foWOSkmIgoB7goB/SElGio6WTk1PU05NTZJMVVdYYWxuZmVgWVRXXmNeWlhZT4uSUqOfmk9STZGHPYWJjIqPl5KVnlVcXFxZWVhYWFVWWV9fWFVaZ3Jza2JfYGBdXF5fXVabioKJl5uVjZGXmZmZmJGNh395d3HBeYJ6hnmHeoN5pHqHeZN6hHkBeoV51nqNeYZ6hnmgegR5eXp6iHmoepB5i3qLeQF6iHmCept5BXp6eXp67HmDeoV5h3qFeYR6gnmJeol5hXqLeYh6hXmMeoh5hHoBeYx6B3l5eXp5eXmGeol5iHqceYR6g3maeop5i3qSeYh6gnmEeoN5hHqDeYl6hHmgegF5jXqgeYJ6jXmCeod5Bnp6enl5eYd6AXmUegl5eXp5eXl6enqLeZ96lXkCAgQAgNzd4ufn4d3W1NPPztXf7v2A/vLj2NLY39vT0dTY1dfW0dLc3+bm8Pb38v2Agv3+hIiKioX57OPk7vD8goH07ObxgoSBg4KC/4KHioqIhoP76t/Y09/x/oqZpailmpWXl5CNkZCLh//49+7n5+jq6+rw+4OFh4iGgv+Bg4SDg46VgI2IkJSRlJyem5qirKaXjYuMhoSHgv6BhY2VmJqenJmRi4mOmaCgnZqXkouGhYSGi4+TkIaHioiHiYiE/fXx+oGKm7HAw7yrmouFhIaPl5+kpamtrKihmJWQi4iHh4ySlpGNioiGhYeJiIWDhoiE+/P09/2BhoqOkpKG+Oji5u35gICCgP778+ng3t3m7vP/iI6QjYyKioeFhYiMjI6Pjo+TlJOQjIqKiIWGiIeIj4yIi5ego6CenJqXjoT58u7s6Onu5tvZ4+vs6/b5go2Mg4Dw5uv0+oCGiYybp6+zsrCrpaaprKqfmpiWmKCosLS0t7OwsK6qqqmln5aQjY6Oj5KXgJaOjI2Hg4KBgYOFhIP//Pbn1tXd6vXu4trV3Onz8PH59+3q6unv8evw9O/s5+ft+oSGjJKTkJKSkZOPhvnz49vi39LGurKupbO9v8HIyc7EyMi/vsrc4dvW19nTy8jFx8zT2d7k5+fl6+3t29DN1dXV1+Dj4NrY3N7f4ufs6+TdgNjZ39/e3djV1trd3dna3+rv7/H3/ID+gYCEgPHn3+Hb0s/V4uXk08i9urexsa6po6SoqrCutLzI0M/Z5OPl6uXh4t3i5vD3gYSEhIOB9/b27evo9oKCgYCA/P379PP38vmDg4aQnp+em4z98e3w9Pj58+Xc4+nv+/n4g4D0+f36gPrw5+Xp6ePh3dvp/IGAg4mLhIGBg4OGiJCYl5ORi4KA//r17vf37OLl8vbz9PD3g4H17Ofd29fc3d/c5f6JioP/+fz8goSKjJCRjomB9+/m2NXS0dfa3d3b3+v69u7vgIOC//v3gISHhIODhoWCgICA/oCChIeKjIuG/4GEgoOEgIT++feBgPbu/42ZmpSOi4iDgYD//Pr+gIOHjJGXm56WjoeFiYiKjo+NkpOVkIyOi4eB+Ofg4+Xs9YGC/oCBgP6BgIGBh46Ri4D4/oGDiYiE/v+CgYGCgomOjZGVmJWOjI+LiIiGh4iFh4yLiYmMjIqMjIiLjouPlZWTj4mF+u7ygPeDiY2MjY6I+PL8+uvk6fr8+oSGg/307evn8YCEio6Lhvz1+fyDg4OFh4SBi5mcn56VkZGUm56ZkI2KiIeFiYmCgP6Ag4KAgPv9/PyBiIeIjI2MiYmDgfj18vjy8vb39vb08/P5gIL++PLu6u/x9ebbzsrS2t/e4+ft8vTz7enigLOzt7y7tLCqqaaioaixvstnysG1p6CmraujoaSnpKajm5mipKijrbS5tsJjZL69YmRkZGK8s6uqsLC3XVuuraq0YmRiZmZlxWRnaWdoaWfGuLCqpLDBzXGBjpGOgnx/fnZydXNuacO9urKqq6yusa2xu2NjZWdlYb1fYmNjZHB4gG9qcXNvcXh5c3B0enhwbG5uZ2VoY79iZGpxcnR5eXhwaWZqc3p4dHJvbGZhYWFjaG1xb2ZnamhoampoycC6wGNpd4qWl5CAcmVgYWJpb3Z5eHl5c2xnYWRkY2NlZ2tubmZgXFpXVlhcXV1dYmVjvrm5uLlbXF1dX2Bbraittbu5gFhUUJ+hoaOgnp6mrLC4YmZnZWNiY2BeXmFmZ2lqaWpubmtnYmFhX1xbXV1eZmdkZGxzdnd4eHNuZl6yq6ainJueloyLlZ2hn6mpV19cVlSbkJOanlFWWFpncnl8enh0cnV5fHlvaWViY2pye4B+fHVzdXVxcW9rZFtWVVhZWl1hgGFZVldQTUxLTE5PT02Xl5aJe3yFkZiSiYF9gIqUlpmin5KOj4+Sk42QlpKPi4qQnFVXXWJjYWNjYWJeVpuWiH+Hg3RoYV9cUlxkaWx2fYFydHFoZnODhoB8fIB7cm5rcHR6fH+FioqIjI2MfHRyenx9gImOioOBg4aIio2Rk46HgIODhoaHh4F9foWKjYmIi5WampyhpFSnWFhbWKCXkJKNhIKHkpaVhntzcm9nZ2ZiYGNnaW9ucneBiIeSnp2doZ+bm5Wan6itW1tYWFxcsK+vqKahrl5fXlxbs7SzrauuqbJhZWhud3d0cme6sq6vs7W0r6Odpq61v7q5Y2G5v8TCgMCzqaaopp6alpShslpaXGJkXltcX19iY2xycm9tZ2Ffu7Cooaqpn5WYpKmrramtXFmlnpqTkpCVl5iTm7BhYlqvpqagUlNWV1xgX1tWqqajmpeUkpORj4uFg4uVlY6NTE5PmpqaUlZXU1FSV1hUUE1LlUtMTVFVV1dVoU9PTk9RgFGbmZlUVaOZoFZcWlZTUlJQUVGlpKOqV1tfZGZqamtkXllYXF1hY2JfYWJkX1lbWVdSnY2Ii4yRlU1NlUpJSJJLTE5NUlleW1WnslxdY2JdsbFaWFhZWV9jYWVqbm5oZmdjYWJhYmNhY2loZmZpamhtb25xcm9yd3Z1cmxnv7GxgLVgZmpqa2xksamys6mkqrm6u2VqaMm/tbCtumVqcHVzb9HLztBsbGprbWpncH1+goF5dHR4f4J7cnBvbm1ucnJpZ8pmaGhnZ83Rzs1pb21vdHV1c3NubM3MytLO0NPV1NLNysnQa27X0szJxcjL0MK3q6mvtr29wsbM0tLPycO6gIyNkZSTjYmDg4F9fIKJlqNTpZyPgXp/hoN8en2AfYB9d3d/gYaEjJOYlaJTVaKhVFZXV1SdlYuKkZGaT02RjoyVUlNRVVVVpVRXWFdXV1Wjlo6Jg42cplxqdnl2a2VoaWFdYGBcV6GbmpKMjo6QkY6Rm1JTVFVSTpZLTU5NTVhfgFZRV1lVV15fW1hdZGFXUlNUTkxPS5FKTVNaW1xgX11WUU5QWV5cWFdUUUtGRkVHTFFVUklKS0lJS0xKjYR/hkRJVGRtbGVYTUNAQUJITlJTUlRXVVJPSktKR0VEREhMTkpHRERCQUJEQ0FAREdFgn+CholERUNERkdCe3V2en5/gD07OG9vbm5tbnB6gIOKSElKSUlJSkhGRUZKSUxNTlBWV1VQS0lIRkRER0dKUVBMTFJXWFdXVlNPSkWBfXh3cXFzbGNhaXBycHl5P0hGQD91b3V+g0RJS0xYYWVlX15dXF9iZWNZVVRSVFtiam5vc3JtbmtnZmVjXldTU1RWVlhcgFxVVFdRTk1MTU5PTkyVlZSIe3yFlJ6ViH96fYiRkpObmI2Ki4qOj4mNkYyJhYSIk09PVFhZVlhZV1lXUJKPgXqCfnFoYV1bUVpiZWducnhtcG9nZXCAhH56e353cG1pbnR8gYeNi4qJj5CQgHh1fH19f4iMiIB9gISHjJGVkoyEgH5+gYCAgHp2dnuAgX1+gouQj5CWmk+eVFVXU5mPiIqFfHh7hYeHeXBnZWJaWllVUlVZW2BeYmZvdXR9iIqMkY6KioOFiI+TTk5MTE5NkpGRiYeCjkxKSElLk5STi4iKg4pLTU9UXVxaWlCPiYaHiouLhnpzeX6DjYqKTEmIjZGOgI2Denh8fXdzb215iEZFR01PSUdISktNT1ZcW1dVUEpJj4Z/eYKCeG5ven59fXl7Q0J6dXNub21zdnhyeY1PUEmNhomFRERISExNTUpGiYaCeXd0dHd3eHdzd4ORj4mJSUpKjoyNS09RT05PUlFNSUdHjkhJS05RU1NPlkpKSktNgE2UkZBOT5qUnVZcWlZTUlFPT0+fnJqfUFJVW19kZmZfWFFQVVZaXFtYWlpcVlFTUUxHhXp2eHh8gENCgEBAP39BQkNCRkxQTEaIkElKT05JiYpGRURFRUtPTlJWW1pVVVZTUVJQUlJPUVZVUlJTVFJVVVNWV1VYXl1aWFNQlIuRgJlSVVNSU1ZQjIePkIaBhpWXllFST5mSi4mGjkxQVVpXU5mVmZtSVFRVVVBMVF9gZGRcV1hcY2ZgWVdVVFRVWVhQT5pOUE9OT5uem5pPVVNUWFlYV1hUU52cmqOho6WloqGcmZmgVFanoZqXlZqdoZaNhIGHjZKRlZido6WlnpqSkHkBepp5BHp6eXmFeod5gnqEeYZ6AXmHeoh5j3qMeYZ6AXmcegF5pXqEea96hXmHeoZ5g3qLeax6kHmFeoV5uXqjeYx62XkCenmEeqx5hnqHeYV6iHmJepB5gnqQeZR6j3mCeox5g3qEeYl6knkGenp6eXl5jHoBeYh6AXmGegh5eXl6enl5eYp6hHmbeod5B3p6eXp6enmJeoJ5hXqCeat6hHmHeop5g3qGeYZ6hHmdegF5hXqEeYt6jnmCepl5AgIEAIDe4uv1+v3x4tLL1uXw7erm5OHc2NTU09HLyMXEvbm7xMXFwsjW5vDx7Oru7/Dz+PX1+Pj6+f6Cg4KA9/f7+vv/hIWIiIWFhID4/v309vj/g4OC/v+DhYSGhoSBhIySko6MiYL9+fb6goaJjZWZlpOUk4uD/vv7gIaPkIyKiouLjYCPjYyMjo+OjY6Ulo6Fg4ODh4mNkJOVmJ2hoJ2ipqenpp+dnJiWmZmXlZeVkYyIiJCRk5ibnZ+ipKSlpaeloaOjoJ+hpaWemp2lp6qop6yxsaufkoqJi5CQkJGPjYiEhYSHhIL98+/w7u/s7Orp7fiBiY2UmJydoaiqqqOfmpqcnlefoaWpq6WXioGBhomLjo6QkZSTk4+Ni4X+8+rl1svGx8fIzdHR1tnY1dnj8fmBgYGC/4CGiIeJioqOj4qG/vbv6ePg7Ymbqq6yura2qZqUk5ebnp2eoqOEpoCssaugk4f58ezu/4mUorPF0NDKwry5uLe0rqmoo5eH9feHj4+NjYyJg/2CiYiFiIuGgYGChoqNjY2MjYyKio6Rj4yJh4WKkpmdmZOPkY+Lh4OFjpSTj4qD+PDx/ISIiImIhf/4+Orb0c/P1dro/4yVnZuYnZWNi42OjImIh4WHh4CFhIaIjIuJhYOA/ffv6+bs7fLz5Ojr8PeDhoiD/v39/4KGh4iIhoSChI6WpbHCyMnHzL+4pJmWmZ+hn5qTjYeIh4T6597Y3eTx/IKHioyIiIiFg4P+/Pj09v6BhoP78ene3+jo7fDs5+Li5u7r5eTr8/uDhoLu6u7q49nO0t3g3Qnf4Obz9viBhIWFhICCgP3/ho+Tk4uGg4CFi4uMioaEgvns6Ofn6+/q4+Tu+Pf094KHhoWHg/+GiYWDgPrz5tza3uXc1NHMzNLc6/Dp7YCKj4iFiY6LjI2Ki4eDhIOB/vTq5+zu6OTg19bW1tfd39zc3ePt8/iAhIOB+vf8/YKFhYaDgPn18Pb4/4SKkBCOiYmKjZCTlJWVk5ebmpiShZCAkZGRkIyLhoL99fLt7u7z+oCGh4SDgoOHjZGSjYqKj4+MiYqMioiGhIGBg4eJioeFhIODh46Rl52dmZaSj4yMjZKVkY6Nj5efnpmRjI2MkZqgoqGhoqSnpKOhm56or7O1vc3riZaXmJ6dkYj64NXLuJ2E8/Hp3uHr+v/58Ozs7fWA9/f8hYuOjYiFgv+DipGVmZmWjouIhYePlZiWjYSEgfry5+nl5+La3+Ti5/Dz+P6Bgf/58Onk39PLxcnIwsPGzs/T3ev0+4KIj5OQjIiFg/vz6+3o5ebo5+72gYeHhIH6/4GEhoWDgv/5/P2EiYmIio6QjYeGg//w5d3e39rTy84C0dqAtre9wsO+saSZmqWzvru4tbOwqqeioKCem5iWlpGNkJeZmJabp7S8vLe2uru8v8G9vMLHy83Ta2xracvJzMzN0W1ub25rbGxox8zLwsG/w2RlZcjNbG9vcW9qZmhwdHNwb21nyMTBxGZpamtxd3ZzdHJrZcTBv2BjamtoZmVkZGeAaWdmZ2lnZmRma2xkW1paWFlbXV9jZWhtcW9scXZ4eHZvbnBub3R2cm9ub21qZ2lxc3V7fH19f4GAf31+e3h6e3l4e4B+d3Fyd3d3c2xoZWhucG5tbm1vbm1tamZgW1taXFtZrKWmq6yur7KysrW+Y2hqbnF1eXt9fXx7e36BhIIBf4R6gHZtZWFiZ2prbGtramtpaGRhX1qroZiShnx6fH5+gIOChYiHhIaOmqBVVVRXqlZcXVxfYF9fXlpXpJ6WkYyJlFxufYKHj42NgHFraW1vcnF0d3l8fHt5fYB6cGVZoJePjpxWYG18jZaWk42JhoaEgHpzb2heUZCUVVxcWllXVVCWgE5VVVFUVVFNTVBSVFZYV1ZXVlNTVllZWFZUUldbYGRiXFdYV1dVUlRcY2JfW1agmZihVVlZWVhVop2fk4V9fH2Ch5OnX2lycXF0a2JgYmNiYWBfXV5eW1lbXmJgXVlWU6WelZCLj42MkIiOkpqiWFpcV6Wjo6VVWVpbW1lWVFdhgGl3hJSbnJqekYt7dHJzdnZ0b2lkX19fXKqYj4uQlqOtXGJkZmJiYV1bXbSxraqttVxgXbCppZyepqSoraijnp6ip6Kbm6Orsl5iXqiipZ6WjIeRn6Wmqqyttra2X2FjY2NlZmZjX7q6YmptbGVgXFhcYWFiYl5cXK+lo6Kjp6mldJ+ksLm2sbJeYWBfYmC8ZGhlYl6zq6CXlpqhmpKQi4mNl6atp6ZTVVlYWl9jYWRjYF9ZVFVTUZ+YkZCUmZWTko6QlJmWmpyal5SXm52fUlVUUp2cn59SU1NUUlCen52ioqZWXGJhXV1eYWNkY2FgXmJmZmdihWBQYWJhYF1bWFWnoZ6YlpGQlEtPUE9QUVNYX2VoZWRjZ2diXl5fX1xaWVdYWVpbXFxcWllYXGFjam9taWVgXFpbXmVpZGBeXmRqaWRdWVpaXWWEaoBsbnN0dnZwcXh/g4WOma5lbWxrb29mXqmXlJGEb1umqKKYmaKvtK2koaGjqq2utGFnamhlZGLBZGpwdnp7enV1c29wdXl7eXJqamfEu7CzsbOvqa6wrLG4u8LKZmbJwbewraylnpmgoZycoKipqbG8xc1rcnp+eXNva2jLxsDDwC7AxMPAxctqcHBtbNDWbW5sa2pq0s/U0mxsa2ttcnZzcG9u18e6sLCwraijpqmygIuNlJudmY2BdnV/i5WSkI6NjIiFgH59e3ZzcXFtamxyc3NwdYKPl5aQjZCSlJijqbGyqqmmqVZYWFempaioqa1bW11cWVpaV6arqqGioadWV1eqq1lbW11bV1RXXWFfW1tZU6GemZlPUlNUWl9bV1hXUUyTkZBITFNVUVBPT1BTgFVTUVNVVVNSUldZUUlISEdISUtNT1BSV1taV1hcXFxaVFNVU1NXWFhXVlJPTUlLUlNVWlxdXl9hYGBfYV9ZWllWVlpfX1hSU1hYWVZSUVBTVlRQTk5OT05MTkxKRUJDREZEQ310c3Z2eHh5enp/iUlPUlZYW11fYWFhX15gYmVkgGNhYmNlYFdNSElOUVNVVVZWV1VTT0xKRoR7c21jXFpdX19jZmVnamhlZmx2fEJDQ0WGRUtOTE5OTExMSEaDe3RtZ2ZwSFlmaWxzcHBmWVNRVlhaWVxfYWRlZWZqbmlgVkuHgHp6h0tTW2Vvc3Jxbm1sbGxqZWFfW1JGen5KUVFOgE5NS0aFRk1NSk5QTUlJS01PUFBPTU5NTExQVFRTUU9NUVVaXVtVUVJRUE9LTVRaWVZSTpKMjJVPUlFRT0yPi42Cdm9ubXF0f5FSWmBeXmNaUlFSUlFQUVBPUVFOTE1PU1JRTkxKk4+IhH6Afn6BeH2Aho5OUFBMkZGTlE1QUVFRgE5LSUtUW2dxfoGCgYZ8eGliX2FlZmNfWlRPUE9MjX53c3d8h5BMUVNUUE9OS0pMkY6Kh4mQSk5MjYeCenyFhImOiYSAf4OJhX9+hYyQTVBNh4GCe3RsZm16f31+foCJiopJS0xMTE1PT01JjIpKUVNTTUpHQ0ZLS0tKR0dIiX57gHp5fYOBenuBiYaBg0dKSklLSY1MT0tJRoV+dG1sb3ZvZ2VhYGRufIN9fkFFSkhITVJPUlJPTklERURCg3x1dHh8eHd2cXFzd3V5e3p5d3uBhIdGSkpJjY2RkEpLSktJSI6PjpWZn1JWWlhUUlJUVVdYV1dVWV1eXltZWVhWVVVVgFZYVVRRTpqWlJCPiomLR0pLSktLTE9TWFlWVVVaW1hWVldWUlBPTU5PUFJTUlFPT05SWFpfY2FcV1NQTU5QVVlUUU9OU1hXUkxJSUlNVVlZWFhaW2BgYF5YWF9maWlweoxRVVJQVFVOSIN2dXNnVEN6fXhvcHmEiIF4dnd5foCAgIVITVBPS0pIjUtSV1pdXlxXVlVUV15jZWNaUVFNkoqBhIOFgHuBiYmPl5eZnE9Pm5aQkZCJe3NucnJub3R8fX+HkZqjVVleYFxYVVNSnpiQko+Nj46Nk5lSWFhVU6CkU1VVVFRVpqGioFNVVFVYXWFfWlhWqJqPiImJhoB6fH+HtnmEeoZ5iHqHeQV6enp5eY96hHmMeoN57XqMeal6lXmEegF5i3qHeZ16hXmUeoJ5iHoBea56hHmGeox5nHqOeYR6hHmieoh5inqGeYN6lXmDepF5inqCeZB6j3mGegF5hXqSeZF6l3mEeoR5hnqGeaB6iHnTeoh7h3qReYd6AXmUepB5gnqVeYl6i3mFeoJ5hnqEeYt6jHkCAgQAFfL3+/z69fT1+fz8/P3/goeMkJCRk4WUgJaWlZSQjpKYnKCempuXkY6IiI2MjI2LiIWAgYaHhoOBgYKGi4uKjY2JhoaGiIuHgoD06+jk4uXo5urq4dXT2dzi6/Lx7/b8goeJioyIh4eIiYiIi46Lhf/59PT4/4D++/jz8PP2/ISLkZWaoqqtra2rpp+amZqZmZ2ipKSlo5yZd5mZmp2hnZCB7ev2g4WEg4WDgf37goSB/v+FjpWgq6ybh4GEio+Qj42NkJSTjYaB+/Ty7/P/hYuRkpGQjIf+8O7w+YKDhoiIh4P79Onp6uru6eXm6/L6gIOFhIKBgoH27+bi39zX2uz8iZCXmZqWkIqE/PPt6/H3hPyA+fPt4t/e3tjQzMbCwcXL0tfY09HNzM/U3+ry/YOEh4yPj42Hgffs6e3v9YKIipSw3YaJ/vHp4tzUw7ezraikn5uXkpGTlJORkZWeqbO9wsLAwcPBs6GWkI2MjI2QlJeepqikn5yZlpWUkYuGhISFhYWD//Pq6Ozx9/n5/IGFh4SA/fb39/X2+vr5/4SGhYSDhImPkIqEgfz3/oOEhoWFhYSFhYSDgoKAgPvz8PHy8/z++vj29PDl3+Dh5u7z9fqAgf77+fz/gIKFio6PjIyJhoL59PT7goWJiIWGiIyRk5KQi4iJi4yMioiHhIKAg4eLkZOTkI2QlJuen5+alpmepKYopp2Sh/36/fn07eXj3dvj6Ojp6uPc29jb4uPh4eHm6Ozr5eDg6PH4goSFgIKAgIODgfv6/Pr4+fb3+vn4/4CAg4aIiIWBg4P/gIKCgYD37ezu8fHy9vyDh4eIiYuPlJOOjIyKh4SCgPjx8vT3+f6DiYyOkJKTko2JipCYnJyXkYyLi46Slpmbn6Cfnp6gnJeUj4qHhYeIhoqOj5CLhYKBgv/9gP768efn6OjpgOHY1tjT0NPY3d3f6Ozy+O/p5+Tn5d7d4eTn49vY2+Hm7erj3dfY29/j6fH4goeJioqIiIiHhYH6gYaHh4aFg4KCg4SFhoqMj4+NjpCXoay3vMLI0tjZ2dnh7vr35MmtmYmA8ebYzcXExsrS2Oj2gYaHiouJiYuMiYiGhoiOk5aXgJmZlY6HiI6NioiGjJadoKWqs7u+wsbN2uXt8/iDjZagqrK2s6eUhe3VuqGQhYH68u3y/ICCg4SDgPv7/vv6gIWLkJSUlZiYk42MkpqempeYlpGOjo2KhoSC/vv/g4SKjI2LiIiKjZGRlZuen56Zko+PkpeVkI2Mjo2Mi4uKiYiGP4OEhYeLjIuHhoaFhoiIgv/69v2BgP71+4OFi4yKjpCOjIqKj5WXmJiWkYyIhIKAgYOGiIaFhIH//Pn18PDz8oDM0tfY1c3JyczPzs7N0GpucHN0dXl7e3p5ent7eXh1c3h/g4eFgYJ/eHNtbHBvbm5saWVhYWVoaGZkY2JlamtqbW5tbGxsbXBsZ2bBuri2tba2tLe3rqWlq66zub25tbm+YmZoaWxsa2ppaWhoamxoYbi0sLC0uVy2sq6opKWorRlcYmdqb3Z+gYB/e3ZwbG1ubm9yd3h4endzhHBcc3l1aVqhn6pbXVtbXFlXqKVWWFWmpVhfZG97gHNhW1lbXVpWUVBSVFRPS0qWlZeZoaxbYGdoaGZiXaqblZSbU1ZZXFxeXLGtpqeqrLCqpaSmp6tXWFdXVVVWVaWEpICin6GqtF9jaGlpZ2RhX7aupKOorK+sqKWgmZOHhISFg317dXFxdXyCiIiDgn58foGJkpqoWVtdYGRkYVxXpZqWmZ2iV1xcZHylZ2nBt7CqpZ+Ogn56d3Nua2hkZGZoaGdnbHV/h5CUk5CSlZSIeG5nZGNiYmNlZmtxcmxnZGNiYYBfXFdUUlNUVFRSn5WOjZGTlpSRlU9TVVOblJWUkpSYmJicUlNSUlFRVFhZVFBOlpOZT1BRUFBQT1FSUVBPT05OlY6Nj5CSmZqXl5WTkYeCg4SHkJebolRVopyZmptOUFRXWlpZWVhWU52ZmZ1RU1ZWVFRWW19hYmFdW1tdXV1bWkBZVlNRVFldZGZmZGFhZWtub25qZ2pwd3x8dGhcp6SopqSfl5WPjZOYmJmZk42LiY2WmJaWlZmcoqOem52jqa5chF6AWlhYW1xarq6wr6+wrKqrqamwWVhaW1xaVlJUVKNSVFVWV6acnaClp6mvtWBkZWZnaGhrbGtpaGdkYV9ds6urrbCxtFxdXmFpb3R1cm9xeICDgnxzbWtqbXBzd3p9fHdzcHBrZWNeWVdWV1VTVltdXltXVVRTpahYs7Con6ChoJ6AlYqLk5OOjIuKhoWLjpOYkY6OjI+OiYmNkpWTjo2PlZiem5ONioyPkpSXmp1TV1hZV1VWWFdXVKZWWltbWlhYV1laXF5fYmVoZ2RkZGhudX2AhImPlJibnqm6yMi5oYZ0ZV2uopOIgH16e4CFkp5UWVpdXVxcXV1cW1lYW2FlZWWAZ2hlYFpcYV9cWlhcY2doa293fH2Ag4iRmJyeoVVcYGVoamtrZl5WnpF/bmNdW7GsqK62XFxcXV1btbe5trVdYGNmaGdrcXNwamtxeX57eHl3cnBwbmtnY2G9ur9jZGltb25sbG5yd3h9hYqLiIJ8enl5fHx4dXZ4dXFvbWpoZ2Y/ZWdpbXN0dHJwbWtrbm5pzMfEzGhozMLDZmlvcW91en6Bfnt7fHt6eHRxbmxrbG1vcXJ0cnJxb9zX0s3Gx8nKgJ2hpaaloJ6foaOhoJ+fUVVZXV9hY2NiYWFiY2RjYl9dYmhrb21paWZgXFdWWlpZWVZTUEtMUFNTUU5OTlBUVVVYWVdVVVVWWFVRT5aPjouKjY6NkJCHf32DhouSl5WSl5tQU1RWWVhXVlZWVFNVV1VRmpaRkZSXS5OOi4WBgoSIgEhNUVRYXmVoZ2dkX1pVVldXV1peX19gXllWVlVVWV5bUEJycXxERkVERURCfntCREB8fENJTlhjZ1lIQkFESEhGQkFCRERAOzlyb3BwdX9ESE5QUE5KRn91c3Z/RERFR0ZGQ357dHZ6f4SAfX1/goZERUVEQkFCQoKAf39+fHh7gIaRT1NXWFdVUU9Mk46HhoqOkpCOjIeBfHNyc3VzbGliXFpbYGZrbWtubmxtcHd+gopJSkxPU1RSTUiHfHl8f4RITUxTaYpVU5aMh4WFg3lxbmhlYV1aWFNSVFZWVFRXX2hweHt5d3h7em5fVlJQUVFTVVZXXGFiXVdVVFRTUU9LgEdGRkdHR0aHfnh3e32BgH2AQ0dJSIZ/gIB+gISEg4hHSEdGRkZJTU9KRkSEgYdGR0hHRkZGSEhHRUREQ0OBfHp8fX+FhIGAfnx6cm5wcnV+g4aLSEiKhoWJi0VGSEtOTk1NTEtJiISEiUdJTEtJSUpOUlRUU1BPT1BQT05OTk1LX0lLTlBUVlVST1BTWVxdXFhUV1xjZ2hhVkuJh4qIhYF7eXRxdnp6e3x2cG9tcXl6eHd2eXt/fnp3d3yBhEZJSElJR0ZGSUtIi4qLi4qMioqLioiOR0dJS0xLR0RFRoZDhEULg3l5fICChIiNS06ET0tRVFRSUFBOTElHRYR9fH6AgoVFR0hKT1NXV1RRUldeYmJdVVBPT1JVWFteYmNhXVtcV1FQS0hGRUZFQ0dLTU9LRkRCQoOGR5GOh32EfIB1bG51dHFwcHFwcHZ5f4V/fX18f3x2dXh8f355en6FipCOh4B7e3t8e3x+g0dLTE5NS0xNTU1KkEtOTk9NS0pKTE5PUlNXWVxcWVlaXmVtdXd6f4SHiIiJkZ6rq5+IcGBTTI6FeW9paWhpbXB8hkhMTVBQTk5PT01MSUhLUFRVVYBXV1ROR0lPTUtIRklRVVVXW2JnaGptcXl+gICCRUlLTEtJSElIRUF2bF5QR0E/enRxdXw/Pz4+Pj14e316eD5DR0pMTE9UVVJNTVNbXltYWVlVVFVUUUxKSIqHi0lKT1FSUk9PUVVYWFtgZGRiXllYWVtfX1pYWVtZVlRTUlFSUj9RU1NVWFdWVFNRT1BTVE+alpWdUVKimpxSU1haWF1gYmNhX2BhYmJiX11bWFZXV1hZW11bW1pYramloJmanJyOeb16lnmQeoZ5AXqIeaJ6g3mHegd5eXp6enl5lnqGeYh6hXmHeo15iHqKeYl6pnmJeoZ5hnqCe796inmEeop5jHqDeY96lnmCeoV5i3qEebB6o3mLeox5inoBeYV6iXmReod5snoDeXl6tHmLegF5qnqMea56i3uHeoV5hnqFeZt6g3mzeoR5BXp6eXl5n3qIeQICBACA8PD19u/q6+zy9vTy8O3s9Pj29vf3+fv7+vfu6u3z+4CBgoOBgIH++v6Dh4aB/f359ff4+Pbr4t7Y1tjc5Onk3t/l7/f5+Pb4/oOGhoaFhYeIg/fv6N/Z2t3f4OLm5+jv9/fx7ezx/YWJiomHhIOFhYaJjZGPioaFg4D+gYOB/fsz/4SJjpSYmJWTkpOVlpWUlZebn6Gjpqmsr7CvsrGlloqCg4iMjo+NiYaHhYKBgP3/gYSJhYwLjpKWl5aSjYmIh4aEhYCGiIqNj46LioqKiIeIh4OBgIGA/v/+9/Hu6+3t6+7z9vj18PHy+ICChYWGiIqLjIyOj5CQj4yLioqKjI6SlZibnp+hoZ+cmZSPjIqKioiIi4uMjo+QkI6Mi4uKjZCOiYeKjIuNj4+MiYiGgPXr5uPf3N/n6enx9/n9gP76/ICChoCJioqJiIaB+PP2/oOHiYiIio2NiIWFhIH79vj/hImNjo2Mi4mHhP329PX2+Pr8/f79/Pz8+vbz8/b28Ojn7/6Eh4eFg4GBgoOCgICAgoOBgICChISDgoGBg4SGhoWDgYKGiouNjpGVmpycnaChnZiTjoeDg4SDgIGDhYiLjYyJhoCEgoSGhoWEhIWJjo+NiYSA+fTu7fDy9Pn59vb4+///gYKCg4WFhouOj4yJhf/39+3j4ebp8Pj7gIGCg4L69PDq5ubm5+z0+YGHjI+TlZaTjoiEg4OEhYeHhYOA/Pn7/YCCgoGEiYiFg4KDhISEhYeLjZCTlJKLhIKFiIiHh4mE/GP8gP/58u7v8/b08e3r6enq6eHY0s3Lz9XY2Nnb3N/n7+7n5Obq7O7x9fXw7vH3+/v9gIH88fD4gYSGiY2RkpKOi4mHhYH8/oKDgYD9+vj5/f/79/Px7+rq8v2CgoD+/4KHi4yGi4CJhP/07Ojl6PD1+oD88uvs8vf6+/fv6efm5+nr6+7x9PX08e/v7ejk5+fk4+Xj3drZ3ePl5ePe3d7l7fWAhIeJjI+RkpGPjYyKiIaGhoSBgPrx6N/Z0s/Q0tXV09XZ3eDm7O/09vqBhouRlJKRkY2HhIH+/f+AgIKEgoKC//v/gg2DgoOFhIiPk5WVk4+MhIuAiomHiImJhoOBg4eJiYiFgf359vTx6uXk5uvw8/Xy7enu9vv26+Pm7/qChYeKjIyLiIWEhYaIiYiHiYuOkJKSkI2Jh4mOk5mcmpeVlJSUkImEhIaJiYiGhoeLjIqJio6RkpCPjouKiYmHhIaKjY6Rk5CLiYyMiYeHhYSHiouMjI0/jYqJhoL++PLv7e3v9Pf6/oGA+vTy9PT2+v7/gP/+/v779vPx8fDv8fb/hYmJiIeGh4aGg4D9+PXx7uzp6OnsS8zN0dLLx8jHyczJxsO/vcTIxMPDwcPFxcTBt7S3vcRlZWdoZ2Zlxb/CZWhnY8DCwL2+vr25rqSfl5GSlZ6inZeXm6Ssr66rqKlWWIRZPFtdWaWfm5WQkZSXmJqdnqCnrq2npKSos2BkZWVkY2RmZmJeXF1bWFldXl68YGFfuba6YWVqcHNzcW9ub4VwbHJ0d3h6fH+Dh4eHiol+cGRaWVxeXmBgXltbWVZUUqGhUVRXWltZWFZXWl1fX1xZWFlaW1pbXFxdXmBiY2FdW1lZV1VXV1RSUlNTp6qsqKSkoqSjoKKnq6yooqCfoVJUVVVUVlhZWltcXWBkZYdmYmhpam1vb3JzcG1rZmNhYGJiYGBiYWBhYF9gXltaWllbXVtVU1ZZWVtdXltaWlhUn5aQjYmHiY+QkZebnaFRoJ2gUlRYW11dXFxaVqekpqtZW1pWVFVYV1NPT09NlpKTmlFWhVoIWVhWpJ+eoKSEpoCoq62trKmloaChoZyVlZ2rWlxcWlhWVFRUU1JSU1ZWVFNSU1VUU1JRUVJUVVVUUlFSVVhaW1xeYWZpaWloZWBcW1lVUlRWVVNRUlJTVFVWVFNRUFJVVVNSUFFUWFlXVE9Lk46JiIyOkZaXlJSWmJydTk5NTU5OUFRXWFVUU52XmAiRiIaJjJGYmIVKG5CPkpKSlZaTk5aXTVFVWFxfYF5aVlNSU1RVVoRYD7GytLJYWFZTVVlYVVRUVIRWgFdZWVxgYmNfWlpdYWJgYGFdr7Bbta+qp6eqq6mloZ6dnZ6clo+KhoWJjpKUlZaWlpyioJqXm6Glp6qsraelqK6zs7VcXbWppqxbXV1dYGJiYV9eXFpXU6CjVFZVVKejoaGkpqKcmJaUkJGZo1VVU6WoV11iZGVlZWRjY2Fds6qigJ+bmp6enE6XjoiLkpibnZqUkI6PkpaZmp6ipKWinpycmpeUlpWTkpOQjIqLj5OUlZONiYiLj5RNUFNVWFxfYGBdW1pZVlVUVFNRUJ2XkY6MiIaGh4mHhYaJjI2Rk5KRj45ISUxQU1NUVlZUVFSnqK1YWVpcWllZraisWVlWVVVUgFVbXmBgXltaW11gYWFfXVxdXVlWVFdbXl5cWVWmpKKhnpeSkJCTl5mZlY+MkZmdmJCKjJOaUFJTVVhYWFVSUVNUVldXWFpdYGJkZWRhXVtcX2NpbGtpZmZoaWdiXl9iZGVjYWBhZmhmZmdqbm5samlnZmVlZGNla25vb3BsZWNmS2dlZGVkZGdqbGxrampoaGdlycjFwb+9vL2+v8NiYb67u8DCxMrOz2jOzM3OzMbDv7u5t7W3vGJobG1wc3Rzc3Jv29fV0c7LyMfGyICVlZiXkpCRkpWZl5SSjYqRlZKRkZGTlpaWk4uIi5CYTk5PUE5OTpeRlU5RUE2VlpSRk5OTkYh/fHVxcnV9gn15e4GJj5CNioiKR0lKS0pKTE1Jh4B8d3N0d3l5e35+f4WMi4WBgISPTVBQUE5MTE5OTEpKS0lFRkhISI9JSkiMiguPS09UWVtbWVdWV4RYbFlbXmFkZWhqbXBwcHJyaVxRSEhLTU5PT0xJSkhFREODg0JESEtKSEdGRklNTk5LR0VFRkdGRkZHR0lKTE1MSUdHR0ZFR0ZDQUFCQYGDhIB8enh6e3h7gIWHhICBgoVFR0hHR0hJSkxMTk9SVYRWgFVVVFVXWFlcXl5gYF5bWFVUU1JUVFJSVFRUVlVVVVRSUVFQUlNRTUtNT05PUlJOS0tKR4iCf317eXyBgoKHi42QSI2JikZIS05PT05OTEiJhoiPS05OS0lKTUxIRUVEQoB8foRFSUxMS0tLSklHh4KAgoOFhYSDhISFhoWDf3x7gHx8eHFxeYVHSUlIRkRDQ0RDQkJDRUVDQkFDRUZFREJCQ0RGR0ZEQ0RIS0xMTU9TWFtbXFxaVVFQTEhFRUdGRERFR0hKTExKSUdFR0pKSEdFRklNTkxJRUF+enV0dnh7f4F+foCBhIVDQ0FBQ0NFSUxNS0pIiIKCe3FvcnR5fn8/gD9AQD95d3h2dXh4dnZ6ez9CRUhMTk9OSkZDQkNFRkdISEdIkJCTk0lJSEVHS0pHRUVGR0hHR0hJSkxPUVFNSEdKTk9OTlBMjYxIj4mDgICCg4B9eXh3d3l5c2tmYmBiZ2pqamxsbnR6eHJvcXZ5e32AgHp4e4CEhIdFRYZ7eH9DgEZGRklLTEtKSUhGREB7fUJEQkGAfnx7fX57eHZ2dnNyd4BDQ0GAgUNHS01NTlBRUVFOSYqCendzdXt8fD96cm1vdnyBg4B8eHd4en1/f4OGh4eFgoGCgoGBhIWCgYJ/eXV0dnl5eHVvamhscXY+QkVHSk1QUlJQTk1LSEdGRkVFgESFgXt0b2toaGptbGprb3J1eXx8fHp6PkBCRkhHR0hHREREh4eLR0dISUhHR4uIjUlKSUpKSUpPUlJSUE1LS01PT05MSkpLS0hEQkNHSUlHREF/fHp5dW9qaWpucXN0cGpna3N3c2pkZmxzPD0+QEJDQ0A9PT5AQUJCQkRGSEtMgE1MSUVCQ0ZJTlFQTUpKTE1LRkJCRUhIR0VERUpMSklKTVFRTk1NTEpKSklHSU5QUFFTUEtJTE1KSUlIR0pNT1BRUVBNTEtJkZCMiYeGh4mKi49JSIuIh4uMjpGSkUmQkJKUlJGQjouKiomLj0tQUlJUVVZVVlRRoJ6dmZaUkZCRAZOfeYd6g3mEepx5iXqVeZN6B3l6enp5eXmteoJ5qnqTecV6jnkEenl5eYp6hHmNeoR5inqZedF6j3mNeot5hXqLeZR6hHmgegN5eXqveYJ6hHmOeoJ5hHqPeQV6enp5eYx6iXkBerB5lHqWeYx6g3mHeoN5onqZedx6i3mCeol5AXqOeYt6inkCAgQAgNzh6Ovu8Ovi3NrX1tjc3uHk5ury/IGChIWHh4eGhoWB+fX4+/v6+fj18e7s7Ovs7Onl4+Tl5eXk5efn4+Hj5ufm5+zv7/D19/j5/P+AgoSGiYyNjIqKjJCTko+MiomJio2QkZGTlZSSj4uIh4mIh4eHhIOEiIqLioyPkY+Mhf71QfDy+YCEiIuNjIiGhYWIiouLjIyNjo2NjpCSkpGQj42Lh4SEhYeHiIeGh4uQlJWVlZaWk4+MiIWDhYiKi4uLjI6QhJJkk5SVl5eVj4iB+O7m39rV09PV2d/o8Pf7/P39/f6AgoSDgYOFh4mOlJaYmJeUkIqGgv75+Pr8/oKDhYiLi4uNjo2MjY2Lh4SCgf78/f369/b29vf8goaHh4eGh4qNjYyMjI2Mi4SKgImHhYOB/fjz8vf9gICAgoOCgoKB+/X0+f+ChYmOkZKSkZGRkJCPjo6PkpSVk5KRkpOUlJSVlpmbnZ2cmZeYmZqalo+Hgv78+vj4+vz9/4CDhoeHhoaGh4eHiIuQlpufo6WkoZ+foaOioZ6dm5uamZiZm5ycmpWRj42MjY+Tl5mZOJiZm5yen6Kkp6msrq+wsrW1tre3trSzsbGzsq+tra6ur7G0tre5urm3tbKurKytqqWjoqGfn56dhZwemZeXlpWVlZSUlJWWlpWRjYyJhYWFhoaD//z49PH0hfcI8+7s6ebo7fKE9QP08e6E7D7w+YGChISB+vXz7+3v9Pf18/Hs5uPh4OHm6err7vDx8/j9gIKDhISB/ffw7e7t5dzX2Nzh5OXl5Obr8/n9/4SAgIGDhIODhIWHiIiFg4KB//r07+vo6Ovx+P+BgPv39fLv7Ojm5OPj5ery+4GEhYWEg4OEhIOA+fTz9/r59/f6/f7+gYWJi4uLjI+RkI+Pjo2Mjo6NjY6NioiIiIeFg4ODgoKDhYeIiImKi4yMjI6Sl5mam5uamZiWko+Ni4qJiYmLA4yOkYSSgJGQj46NjY2LiYuPj4+Qk5WWlpeXlZGNi4mGhYL79/b29vf5/P39/oCBg4eLj5GUl5mam5yfoqSlpqempKKgm5iXlpSUlpmcnJydm5iTkI2Li4qJh4aHiYqKi4+Tl5qeo6ShnJiWk5OUlZKOiYaDgoKCgP779e/t8PiAgoH9/Pz9Kv39/4CBhIeJioqLjI+TlpeXlpicoqapqKajnpqXl5aWlpWTkpKTk5SWl4SYVJucnp+fnZubnJ+ipKmusK2opKCdm5qbnZ6dnJ2cmZOOi4uOlJmbmZWRjYiFhYeIh4SB//3/gICBhIeKjJCUlpWTkpKSkZGRj4yKi4uGgPjx6+fm5oTnCeXh393a2NjZ2YTYBNfX2NmApKuxtLe5tKqlop+dn6Gjpqeoqa+3XV5fYGJiYmNjYmG9u7q4trWzsKynpKGhoqWnpqOjpKWkoqCgoaGgn6CioqGipaamp6qrqqqssFhZW11hZGVjYmJkaGtqZ2VjYWBgYWRmZ2lqamhkYF1cXV1cXFxaWVpdYGBfYWVmZWFcraQOoaOpWFxgY2RhXFlWVleEWAFahVyAXmBgX15dW1lXVFRVV1hZWVhYXGFlZmZlZWRhXlxbW1teYGJhYWBgYWNkY2JiY2NkZWRiX1tXqKKcmJWSkJCTlpyjq7G2t7i4uLdbXFxbWFlaW1teY2VnaGdlYl5cWa6rqqqqrFhaW19iYmFhYWBeXl5bWFVSUZ+eoKGfnp+hoKAkpFVXWFdWVVZYWllYV1dXVlVTUlNTUlBPTk2Zl5SUmJ1QUVFShFMNUp2Xl5uiU1ZZXV9eXIRbRlxcXF5gY2ZmZGJhYWFiY2NjZmltb3FuamZlZmZmYVpTT5uamZeXl5iYmUxOT1BQT05OT1FRVFdcYmhscHJxb2xsbm9vbWyEai1paGpsbW1qZmJgXl1eYGRoamtrbW9ydHZ4e36Ag4WGhoeHhYSEg4KCgoGDhoeEhSWEg4SFhoaIioiFgn97eHh5dnNyc3Jwb29vcHBxcW9tamlnZmVlhGRpZWZlY2JhYF9eXV1dWKqnpJ+dn6Kio6OjnpmXlJKUmp6goaGgnpuZl5eYmJyjU1RVVVOfnZ2cm56kp6ShnpmUkY+NjY+RkpSXmpucoKRTVFRVVVOhnJeUl5eRi4eJjpSYmZiWlZmeoqSlhFJRU1VVVFNUVldYV1RRUFCdmZWRjYqKjZOan1FQnpqYlZKOiomIh4aHi5KaUVRWVldYWVtbWVemoaGlqaqnpaanp6ZUVlhYV1VVWFlZWVpaWVlahVsGWVdWV1ZVhVSAVlhaW1tcXV9gYGFkaG1wcHBvbWtqaGVjYWFgYF9gYWJiZGNhX19eXFtaWVlaWVdaXV5fYGJkZWRiYV9ZVVRTUlJSoqGioqOlqKusrK1XV1lcX2FiY2NiYV5cXV5eXV9hYmJkZWRiY2NiY2ZqbW1sbGpnY2BeXV1cW1paW1xcW1otXF9gYWNlZmJeW1pZWl5gX1xZV1ZVVldVqKWfmJSVmE5PTpeWmJqbm5xPUVRXhFg8Wl1iZGZmZmhscXZ4eHZybmpnZmVlZWRjY2RlZmdoaWpqaWlrbW1tbGlmZGVoam1yd3p5dXJubGtqa2xuhG0jamZhXVxfY2hqaGZkYV1cXF9hYV9dt7e4XFxcXl9gYmRnaWmGaBlpZ2VkZGRgXLOtqaemp6ipqauppqSioJ6ehqAEn5+gom1ydnp9gYJ+dnFvbGttcHF0d3h5foVEREVHSUlKSkpJR4iFhYSDgoB+e3d0c3Jzdnd2c3JzdHRycXFycnFwcnR1dHV4enp8f4GAgYOFQ0RFR0pNTkxLS05RVVRRT01MS0tNUFFSVFZWVFFOTExNhE4dTEtMTlFRUFJWV1ZTTpGIhYaMSU1QU1RTT0xKS02ET4VQgFFTVFNTUlBPTUlHRkhJSktLSkpNUVNUU1NTUlBOTEtKSkxOT09PTk5PUVJRUFFRUlNUVFNPS0eHgHt2c29tbW9xdn2EiY2PkJGRkklLS0pISEpKS05SVVdYV1VTUE1MlZOTk5SWTU5QUlRUU1NUUlBQUE5KR0VEhYSGh4aEhYeHD4eLSUxNTk5NTlBSUU9PT4RQEVFTVFJQTkxJjYiEhIiNSEdHhEgOSUiKhYSIjkpMT1NVVVOGUgtRUlNWWFlXVVVWV4RYXVpdX2FiYFtYV1dYWFRNRkKBgIB+fX5/gIBAQUNEQ0NCQ0RFRkdLT1VaXmBhX1tZWFtcXFtZWFhZWVhXWFpcXFlVUVBOTk5QU1ZYWFdYWVtdXmBjZmhrbW5vcHBvboVtBmxucHBubYRuOnB0dnd4eXdzb2xoZWZmZGBfYF9dXFxbW1xdXVxaWFdXVlZVVFRTU1RVVFFQT01LS0pKSkaFgn96eHuEfRB8eXRyb2xuc3d6enp5d3RyhHCAc3o/QEFBP3h1dXR0d3x/fXp4c25samhobG5ucHN1dnd6fkBBQUJCQHt1cW5xcWtkYGFmbHBycnFydXh7fHs9PTw8PT9APj4/QEFCQT89PDt1cm9raGVlaG51ez8+eXZ1cnBsaWdlZGRmanB4QEJDQ0NERUZGRUJ+enp9gYKAf4ALgoKCQ0ZJSUdGRkiFSgNJSUuFTHdKSEhIR0ZFRUZGRUZISkpLS01OT1BQUVZaXF1eXVtZWFZUUU9NTEtKSktLTE5OTEtMS0pJSEhISUhHSUxNTk9SVVZWVFNRTEdFRENDQoKBgoSGiIuPkJCQSEdISk1QUVNUVFNRUFFSUlJTVFRTVFVTUVFRT09SVYRXBVZUUE5MhEtKSkpLTE1NTE5RU1NWWFlWUk9NTExPUE9LSEVDQkJCQX98d3Fub3M8Pjx1dHZ4eXh4PD0/QkRFRkdJTVBSUlFPUFRYXF9eXVpWU1GEUAdOTUxNTU5PhFBCT09RU1RUVFJPTk9RU1VZXmBeW1dTUVBQUFJUVFRVVVJNSEVFR0tQUlBOS0hFREVHSUhGRIWEhUNCQ0RGR0lLTU9OhU0DTk5MhEoTR0SBfXl3dnd5eXp6eXZ0cm9uboVxBXBvb29wlXmLeqx5snqFect6lHmUeoZ5knqLeZl6hnmJeoV5rHqJef96iHqgeYV6m3mGepZ5knqLeYJ6j3mLeox54nqLect6h3mDeod53XqDeZl6m3kCAgQAKpOWmZygoqOkpqenpaGcmJaUko+NjIqJh4eHhoWCgICAgYGAgIGCg4WIioSLMoyMioeEgoKDhIWFhYaFhYSDgYD/gIKEhIOCg4WIioyNjYyMjI2PkZOUlZSTkI6Nj5GShJErkI+QkpWYmZqZl5aXmZqbmpmXlJKSk5WXmZucnJyamZeWlZWUk5OTlZeYmYqaCpmXlZOQjo2MjI6GjxSQkZKUlZaXmJeVk5GQkZKTk5KQjoSNHY6Ojo2LiYiIioqKiYeGhYSDg4KCgYGCgoGBgoOEhIWChoWHEImLjo+QkZKTlZWSj4yLioeEhS6GhoWDgf+AgYOEhoiKi4qIh4WFhoeHiIiJiouLiomIh4eGhIKA/v39/oCBgYGAhIEHgoOFh4mKi4SKEoiFgoGCg4SDgYD+/Pr39vb294X2gPf5+/+Bg4aJioqKiIiHhoaGh4iIiImKi46Sl5yipquxtrvAxs3U2+Ho7vT6/oGDhISDgPbq3dLKxL+8ubWyr62rqqmpqKempqeop6eoqqysrKqnpKKhoaCfnJuZmJeXlZOQjYuKi42QkI6MjI+UmJ6jqa61u769tKebk46Kh4aFGYaHiImJiYqMj5GSk5SUk5OSkY+PkJGSkpKEkTOQj46PkJCPjYqIh4aFhIKBgP359fPz9PX2+fz+/v+AgoH/+/r49PDs6+vr7fH2+4CChYeFiQ+IiYqMjY6NjY6Pj5CQj4+EkCuRk5OQjYqIiIeGhIKBgYGDhISEg4KDhIWGhoeHh4aEhIODhISFhoaGhYWGhYeAiIiJiIaGhYSDg4H/+vf19fb5+/3+gIGBgPz38u7q5+bm5+nt8/n+//79/f6AgoWHiIeEgf36+vz/goWHh4WB/Pn4+Pf18Oni3NnZ2tzf4uTl5+ru8PDw8fb9goWGh4eJi4yOj5GSkY+MioiGhIKB/v39/fz7/YCCg4OEhISDgf8D//79hfyA+/v7/P6AgYKDhomMjIyLiomIhoWDgPny7eni3NjX19jY2Nna29zc3t/g4+bq7/P3+Pj39vXz8fH0+f6ChIWGhoaFhIOCg4SEg4ODhISDgYCA///+/fz7/P6AgID+/Pv8/4GChIaJjZCSk5SVmJ2ip6ywtbm8vr68uLSxrquoo546mZaWl5qeo6qxucHHycW9trGsp6KdmJWSjYeA9/Hw8/j9gIGBgoOEhIWHiYyPkZOVl5qdoKCfnJqYmISZCpiXlpaWl5iZmZqFmxGamZeVk5KSlJaYmZmam5ydnYSeHJ2amJeWlZWVlpaWmJqcnJuZl5aVk5GRkJCPjYyEjQ2LiomJiYiIiYuNj4+RYWVmaGpsbWxtb3Fzcm9saWhmZGNiYmFgX15fX15cW1pZWFdWVVVWV1haW1xcW1tcW1pYVVNTU1RUVVVWVVVUVFJRoVFTVFRUU1RWWFtdXl5cXFtcXV9hYmNiYF5bW1tdXl2EXFxbXF5iZWdnZmVkZWZnaGdmZGFfX2BiZGZnaWloZmVjYmFgX15dXl9hYmRkZWVlZmdnZ2hoZ2VjYV9dXFtcXV5eXl1bWlpaXFxdXl9fX15dXFtcXl9gYF5dXF1eXoRfBV1bW1xdhF4GXV1cW1tbhFqFWYNahFkBWoRZJ1pcXmBhYmJkZWViX11cW1lXV1dYWVpZWFetVldXWVtdX19eXFlXV4ZWFFdYWFhXV1hYV1ZVU6WlpKRSUlJRhlCAUlRWV1hYV1dWVVNQTUtMTU5NTEuUlJKQj46PkJCRkZGQkJGTlEtMTlBSU1NSUlJTU1RVVldYWVpcX2NnbHF1en6ChoqPlJqfpKuxuL7DZGVmZ2ZkwLWqn5eRjYqIhIF+fHp6ent6eXh4eXl4d3d4eXp5d3VycG9ubWtpZ2ZkY2IzYV9cWVdXWFpcXFpYV1ldYWVqbnN5f4OCem9lXllWU1FRUlNVVlZVV1peYWJiYWBeXVtahFg6WVhXVlVUU1JRUVJTU1JRUVFSUVFQUE9PnJqXlpeYmJmbnJ2cnE5PT5uYl5aTkI2Li4uMj5OXTU9SVIVVBVRUVVdZhFotW1xdXFtbXF1dXmBhYmBcWVhYWFdWVFRTVFVXV1dWVVVWV1hYWFdXVVRTU1NShFNJUlJSU1RUVVVWV1dYV1ZWVVVUVFOinZqYmJmbnp+gUFFQT52ZlZCMiYiHh4mMkJaanJuZmJlNT1JUVlVTUJqWlpmcUFNVVVJPmISVMpOQioWBgIGDhoqMjY2OkJSWl5eZnqRVV1hZWltcXV5gYmNjYmFfXVxaWVerqainpaKhhlEFUE9OmpqEmyacnZ2cnJ2en1BQUFFSVFZWVlVUVFNSUlJQnJiWk46Kh4eHiIiJi4SMG42Njo+RlJeanJyamJaUkY+NjpGVTE5OT05NTIRLf0xNTE1OT09PTk1Nm5ubmpqZm55RUlGgnp6folJTVVZZXWBhYmNkZmpuc3d8gIOGiIiGgn98endzb2pmZGNlaGxxd36GjZKVkYqEf3p2cm1pZWJdV1GYkpGUmZ1PUFBQUVFSUlRXWVxeX2FjZmptbm1ramprbW1tbGtqaGdmZ2eFaBBpaGhoZ2ZkY2JjZWdpamtsh20Wbm5ta2loZ2ZnZ2hpamxubmxraWlpZ4RmFGVlZGVmZmVkYmJiYWFgYWJkZWVlF1FTVVdaW1tbXV9gX11ZV1VTUU9OTUxLhEoQSUhHRkZFRENDQkNERkhJSoVLX0pIRUREREVFRUZGRkVFRENBgkFDRERDQ0RGSEpMTU1MS0tMTk9RU1RTUk9NTU9RU1NTVFRTUlJTVVdYV1ZTU1NUVldXV1VSUFBRU1VXWFlaWVdWVVNTUlFQUFBSU1RVhFZKV1dYWFlZWFZUUlFPTk1OT1BRUE9OTk5PUFFSUlNUVFNSUVBRUlNUU1FPTk9PUFFRUVBOTExMTU5OTU1NTEtLSkpKSUpKSklJSUqFS4dMJk5QUlRWV1hbX2JjY2JfXFlXVlVVVVRTUU+bTU1NTlBSU1NRT0xLh0oES0xMTIVLC0pJR46Oj5FKS0tKhEkdSElKTE1OT09PTk5OTEpHR0hJSkpIR42MioiGhoaEhw2GhYSEhYdERUZJSkpJhEiASUpKS0xMTU5QUlZaX2NnbHB0d3uAhouQlZuhp62xWltcXFxZrKKYjoaAfHp3dHFubGppampoZ2VkZWRjYmJjZWZmZGFfXVxcW1pYVlVUU1JQTkxJR0ZHSUtLSkhIS05SVltfY2lucXBoXFJLR0RCQEFCQ0VFRURGSExOT09OTUxBS0pJSEhISUlJSEhHRkVERERFRkZFRENDQkJBQD8+PXl2c3Jyc3NzdXZ3dnY7PDx1cnFwbWlnZWVmZ2ltcTo8P0GEQixBQEBBQ0RFRUVGRkdIR0ZGR0dISEpMTUtIRkVFRURDQkFBQUJDQ0NCQUJCQ4VEb0NC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cXGFchVwb2xqaGdmZWNiYWFgYWFiYmJjY2OGYoNhh2ICYWCEXxtgYWNlZmdnZmVlZGNiYmBfXVtaWllZWFhYV1eIWBRXVlRTU1RVVlZXWFlZWlxdXl9gYIVhGGBgX15eXl9fYGFhYWJiYmFhYF9eXl5fYIRhgmCHXwVgYWJjZIRlG2RjYmFgX2BgYGFiYmRlZ2hqa2xtbm9wcHFxcYZwD29ubGtpZmRiYF5cW1pZWYVYhFcGVlZVVFNShFEUUlJRUE9PTk5NTU1OTk9PT05OmpmEmASZm5yehJ8Hnp2cnJydnYSeBJybmpmFmBKZmpyfo1NVVlZVVFRUVVVWV1eFWIRZDlhXVlZVVFNSUFBPTpybhJoCmZeElh2Vk5GOjImHhYSBgH9/fnx6eHd2d3h4eXp6ent7e4Z8Lnt5eHh4enx9fn+Bg4WHiIqLi4yMjY6QkZOVl5iam52go6apq6xWV1hYWVpbXF2EXgFdhFwBXYheBF1cW1qEWRdaW1xeX2FhYWBgXl1cXFtaWllZWFdWVYRUEFNTU1JSU1NUVFRVVldYWVqFWwFaiFkBWIRXhVYGV1lbXV9hhGOCYoRhDmJjZGZmZ2hoaWlqamprhGwDa2pphGgSaWpra2tqamlpaGZlY2JhYF9fhF4KX2BhYWJiYmNjY4liDmFgX11cW1paW1tbWlpZhlgHV1ZUUqGfnhV7enp6PT0+P0BAQUFCQ0RFRkhJSkuETYVMEk1NTUxMTU1PUFJTVFRTU1JQT4RNC05OT09QUVJTVVZXhVgLV1ZVVFNSUVFQT0+HToJPhE4TT09QUFFSU1RVVVVWVlZXVlZWVYVUQVVVVldYWlxeX19eXVtZWFdWVVRVVlhbXmBiZGRlZWRjYmFgX11bWllYWFdXV1hZWltdXl9gYF9fXVxaWVdWVVRThFITUVBPTUxLSklIR0ZFRENDREVGRohHgkiMSYpIBElKSkuETARNTk9PiFAFUVFRUlKFUQRSUlNThFSCVYVWCVdXVlZVU1FQT4ROJk9PUFFSUlNTU1RUVVZXWFpcXV9hYmNkZGVmZmhpa2xtbm9wcXN0hXVmdnh6fH+ChYmMkJSXmZqbm5ucm5ycnqGjp1VWWFpcXV5eXlxaVlOgmpSOiYSBfXt4d3V0cnFvbGpoZmVjYV9dXFpZWVlaWltcXF1eXl9gYGBhYWJiYWBfXVtZWFdWVVRTU1JSU1NThFSDU4xSH1FQT09OTk5PUVJUVlZXVlZVVFRTU1JQT01MTEtLSkqFSYZKFElIRkVFRkdHSElJSktLTE1OT09PhFAfT05NTExLS0tMTE1OTk5PT05OTUxLS0tMTU1OTk1NTYRMEE1NTk5PUVJTU1RUVFNSUVCFTyRQUFFSU1VWV1hZWltbXFxdXFxbW1taWlpZWFdVVFJQTkxKSUiHR4dIFEdGRkVERERFRUVERENCQkFBQUJChEMEQkGCgIR/LYCChIaHiImJiYqLjI2Oj4+PjoyJhoSCgH9/fn5+f4CDhkRFRUVERENDRERFRYlGDkVFRENDQkFBQD8+Pj16hHkCenmGeBh2dHFvbWtqaWdmZmZlZWNiYGBhYWJiY2OJZA9jYmFfXl9fYWJiY2RmZ2mEaiBra2xtb3Bxc3R1dnd4en2AgoSFQ0NERUVGR0hJSktLS4VKDUtLTExMTU1NTEtKSUiFRwRISUtMhE4UTUxLSkpJSUhIR0dGRUVERENDQ0KFQQlCQkJDQ0RFR0eFSAFHiUaJRRdGR0lMTlBRUlJRUE9PTk5OT1BRUlNUVIdVDVZXV1dWVVRTUlJTU1SEVRRUVFRTUlFQT09OTk1NTExMTU1OTotPClBQUVBQT01MS0qFSQNISEeERglHR0ZEQkB+fHuEef96/3roeo17/3r/ept6p3mietR5/3rReoN5AgIEAASlpaSkhqUSpKSko6OioqGhoKCfn56enZ2dhJyDnYScBJubmpqHmRWampqbm5ucnJydnZ6en5+goaGioqKGo4SkBaWlpqamiKeHpoWnD6ioqamqqqusra2urq+vr4SwELGxsrKzs7S0tbW2tra3t7eFuIq5AbqGuYa4Bbm5ubq6jbuHvIS9hb4Lv7+/wMDAwcHBwsKFw4fEhMWExoXHhMiEyQ7IyMjHxsXEw8LBwL+/v4m+Hr29vLu7urq5ubm4uLi3t7a2tbSzsrCvrq2sq6qqqoipD6iop6ampaSjoqKhoKCfn4SehJ2RnISbBpqampmZmYSYh5cLlpaWl5eXmJiZmZmJmhiZmZiYl5aVlZSTkpKRkJCPjo2NjIuLioqFiYuKg4mIiISHCoaGhYWFhISDg4OHgoiBhoAB/5SAiv+GgIWBBoKCgoOEhIWFDIaGhoeHh4iIiImJiYWIGIeHh4aGhoWEhIODgoKBgYCAgP/+/v39/YX8Bf39/f7+hP+OgIWBD4KCg4ODhISFhYaGhoeHh4mIhYeFhg2Hh4eIiImJioqLi4yMhI2EjoaNDIyMi4uKiomJiYiIiImHhIiHiQGKiYmPigSLi4yMiI0Kjo6Oj5CQkZGSkoSThZSFkwyUlJWVlpaXl5eYmJiLmYKahJkGmJiXl5aWjJUKlJSUk5KSkZCPj4SOAY2FjoePhI6EjYWOiI+CkIaPi44Lj4+QkJGRkpOTlJSKlYSWDJeXl5iYmZmampqbm4ScAZ2EnIybhZwcnZ2dnp6en6CgoaKjpKWmp6ipqqusra+vsLGysoazi7IHsbGwr66urYqsB6urq6qqqamEqIWniKaFpYx2DXV1dXR0dHNzcnJycXGLcAhvb29ubm1tbYZshG2Ebg1vb29wcHFxcnJyc3NziHQGdXV1dnZ2iXeMdiN3d3d4eXl6e3t8fX19fn5+f39/gICAgYGCg4OEhIWFhYaGhoSHhoiEiYeKhokGioqKi4uLkIyGjYaOhY+FkIeRhJCFj4iOi40MjIyMi4qJiIeGhYWEi4McgoKCgYGAgIB/f39+fn59fXx7e3p5d3Z1dHNycodxD3BwcG9vbm5tbGtqamloaIRnjGaDZ4pohGeEZoNlh2SFYwlkZGRlZWZmZ2eEaBxpaWloaGhnZ2ZlZWRjYmJhYGBfX15dXFtbWllZiViFWYVYilcIVlZWVVVVVFSGU41ShlEBoolRi1KCpYWkBKWlpVKIUwdUVFRVVVZWh1cGWFhYWVlZh1qEWRdYWFhXV1ZWVVVUVFNTUlJRUaKhoaCgoIafCKCgoKGhoaKiilGIUg1TU1NUVFVVVVZWV1dXiFiEVwhWVlVVVVRUVIZTC1RUVFVVVVZWV1dXhFiJWYNYhFeFVoVVhVYGV1dXWFhYilmHWotbg1yGXYZcg12KXoRdhFwJXV1dXl5eX19fh2CEYYtig2GIYIhhBmBgYF9eXohdhF6IX4RehF+DYIhhg2KNYQ9iYmJjY2RlZmdoaGlqa2uFbIRtDm5ub29vcHBwcXFxcnJyhHOLdIZziHSFdRV2dnZ3d3h4eXp7e3x8fX5+f3+AgICHgY2ABn9/fn59fYR8hn0GfHx8e3t7hXqHeYV4hHcBdoxiD2FhYWBgYF9fXl5dXVxcXIRbh1yDW4pag1uEXAtdXV5eXl9fYGFhYYliBmNjY2RkZIllhWSCY4dkC2VlZmZnaGlpamtrhGwUbW1tbm5ub29wcHFycnJzc3N0dHSEdYV2hXeEeIp3BXh4eXl5h3qHe4R8hH2Efgx/f3+AgICBgYGCgoKcg4iCCoGAgH9+fXx7enmLeAZ3d3Z2dXWFdBNzc3NycnFwb25ta2ppZ2ZlZWRkhmMPYmJhYWBgX15dXVxbW1paiVmDWIRZg1qHW4VahFmDWIVXhVaEVQZWVlZXV1eEWIZZGFhYV1dWVlVUVFNSUlFRUE9PTk1NTExLS4hKh0uFSohJCUhISEdHR0ZGRoZFA0RFRYxEhEMBh4ZDjkSJiAGJhUSHRQRGRkdHhkiESYNKiEuFShNJSUhIR0dGRkVFRUREQ0OFhYSEhoMGgoODhISEhIUBQpBDhEQIRUVGRkZHR0eESIZJhEgGR0dHRkZGiEULRkZGR0dISEhJSUmNSglJSUlISEhHR0eKRoRHg0iESYhKhkuITIRNhU6CT4hOg0+GUAFRjVAFUVFRUlKEU4ZUhFWHVgVVVVVUVIRTjVIJUVFQT09OTU1NhEyETYhOiE2ETo9Pi04NT09QUFFSUlNUVFVVVYlWEVdXV1hYWVlaWlpbW1xcXF1diV6NXYVehF8UYGBhYWJjY2RlZmZnaGlpamtrbGyUbQdsbGtramlphGiGaQhoaGdnZ2ZmZoVliGSFY4Ji/3r/ev963noBeZR6inm5epR5/3r/ev96vHo=","name":"moonless_golf_1k.hdr","id":271,"type":"FileEditor"},"272":{"outputLength":1,"height":null,"title":"File","id":272,"type":"TitleElement"},"274":{"value":"moonless_golf_1k.hdr","id":274,"type":"StringInput"},"275":{"inputs":[274],"height":null,"id":275,"type":"Element"},"279":{"x":1119,"y":902,"elements":[280,282],"autoResize":true,"source":"\nlayout = {\n\tname: \"Unreal Bloom\",\n\twidth: 300,\n\telements: [\n\t\t{ name: 'strength', inputType: 'Number', value: 1 },\n\t\t{ name: 'threshold', inputType: 'Number' },\n\t\t{ name: 'radius', inputType: 'Number' }\n\t]\n};\n\nfunction loadRenderPass() {\n\n\tasync function load() {\n\n\t\tconst { UnrealBloomPass } = await import( 'three/addons/postprocessing/UnrealBloomPass.js' );\n\n\t\tconst bloomPass = new UnrealBloomPass( new THREE.Vector2( window.innerWidth, window.innerHeight ), 1.5, 0.4, 0.85 );\n\n\t\tlocal.set( 'Pass', bloomPass );\n\n\t\trefresh();\n\n\t}\n\n\tload();\n\n\treturn null;\n\n}\n\nfunction main() {\n\n const renderPass = local.get( 'Pass', loadRenderPass )\n\n if ( renderPass ) {\n\n\t\trenderPass.strength = parameters.get( 'strength' );\n\t\trenderPass.threshold = parameters.get( 'threshold' );\n\t\trenderPass.radius = parameters.get( 'radius' );\n\n }\n \n return renderPass;\n\n}","id":279,"type":"NodePrototypeEditor"},"280":{"outputLength":1,"height":null,"title":"Node Prototype","icon":"ti ti-ti ti-components","id":280,"type":"TitleElement"},"282":{"height":500,"source":"\nlayout = {\n\tname: \"Unreal Bloom\",\n\twidth: 300,\n\telements: [\n\t\t{ name: 'strength', inputType: 'Number', value: 1 },\n\t\t{ name: 'threshold', inputType: 'Number' },\n\t\t{ name: 'radius', inputType: 'Number' }\n\t]\n};\n\nfunction loadRenderPass() {\n\n\tasync function load() {\n\n\t\tconst { UnrealBloomPass } = await import( 'three/addons/postprocessing/UnrealBloomPass.js' );\n\n\t\tconst bloomPass = new UnrealBloomPass( new THREE.Vector2( window.innerWidth, window.innerHeight ), 1.5, 0.4, 0.85 );\n\n\t\tlocal.set( 'Pass', bloomPass );\n\n\t\trefresh();\n\n\t}\n\n\tload();\n\n\treturn null;\n\n}\n\nfunction main() {\n\n const renderPass = local.get( 'Pass', loadRenderPass )\n\n if ( renderPass ) {\n\n\t\trenderPass.strength = parameters.get( 'strength' );\n\t\trenderPass.threshold = parameters.get( 'threshold' );\n\t\trenderPass.radius = parameters.get( 'radius' );\n\n }\n \n return renderPass;\n\n}","id":282,"type":"CodeEditorElement"},"285":{"x":-39,"y":-164,"elements":[286,427,429,431],"autoResize":false,"layoutJSON":"{\"name\":\"Unreal Bloom\",\"width\":300,\"elements\":[{\"name\":\"strength\",\"inputType\":\"Number\",\"value\":1},{\"name\":\"threshold\",\"inputType\":\"Number\"},{\"name\":\"radius\",\"inputType\":\"Number\"}]}","id":285,"type":"Unreal Bloom"},"286":{"height":null,"title":"Unreal Bloom","icon":"ti ti-ti ti-variable","id":286,"type":"TitleElement"},"295":{"x":533,"y":154,"elements":[296,434],"autoResize":false,"layoutJSON":"{\"name\":\"Ground Projected Skybox\",\"width\":300,\"elements\":[{\"name\":\"Texture\",\"inputType\":\"Texture\"}]}","id":295,"type":"Ground Projected Skybox"},"296":{"height":null,"title":"Ground Projected Skybox","icon":"ti ti-ti ti-variable","id":296,"type":"TitleElement"},"300":{"x":522,"y":-88,"elements":[301,441,442,443,445],"autoResize":false,"layoutJSON":"{\"name\":\"Environment\",\"width\":300,\"elements\":[{\"name\":\"Environment\",\"inputType\":\"Texture\"},{\"name\":\"Background\",\"inputType\":\"Texture\"},{\"name\":\"B. Blurriness\",\"inputType\":\"Number\"},{\"name\":\"B. Intensity\",\"inputType\":\"Number\"}]}","id":300,"type":"Environment"},"301":{"height":null,"title":"Environment","icon":"ti ti-ti ti-variable","id":301,"type":"TitleElement"},"310":{"x":-18,"y":37,"elements":[311,447],"autoResize":false,"layoutJSON":"{\"name\":\"RGBE Loader\",\"width\":300,\"outputType\":\"Texture\",\"elements\":[{\"name\":\"File\",\"inputType\":\"URL\"}]}","id":310,"type":"RGBE Loader"},"311":{"outputLength":1,"height":null,"title":"RGBE Loader","icon":"ti ti-ti ti-variable","id":311,"type":"TitleElement"},"315":{"x":-1043,"y":142,"elements":[316,452,453,455],"autoResize":false,"layoutJSON":"{\"name\":\"Replace Material By Name\",\"width\":300,\"elements\":[{\"name\":\"Source\",\"inputType\":\"Object3D\"},{\"name\":\"Name\",\"inputType\":\"String\"},{\"name\":\"Material\",\"inputType\":\"Material\"}]}","id":315,"type":"Replace Material By Name"},"316":{"height":null,"title":"Replace Material By Name","icon":"ti ti-ti ti-variable","id":316,"type":"TitleElement"},"323":{"x":-1026,"y":-175,"elements":[324,460,461,463],"autoResize":false,"layoutJSON":"{\"name\":\"Replace Material By Name\",\"width\":300,\"elements\":[{\"name\":\"Source\",\"inputType\":\"Object3D\"},{\"name\":\"Name\",\"inputType\":\"String\"},{\"name\":\"Material\",\"inputType\":\"Material\"}]}","id":323,"type":"Replace Material By Name"},"324":{"height":null,"title":"Replace Material By Name","icon":"ti ti-ti ti-variable","id":324,"type":"TitleElement"},"331":{"x":-1541,"y":-272,"elements":[332,464],"autoResize":false,"layoutJSON":"{\"name\":\"GLTF Loader\",\"width\":300,\"outputType\":\"Object3D\",\"elements\":[{\"name\":\"File\",\"inputType\":\"URL\"}]}","id":331,"type":"GLTF Loader"},"332":{"outputLength":1,"height":null,"title":"GLTF Loader","icon":"ti ti-ti ti-variable","id":332,"type":"TitleElement"},"337":{"inputLength":1,"inputs":[338,339],"links":[353],"height":null,"id":337,"type":"LabelElement"},"338":{"value":0,"id":338,"type":"NumberInput"},"339":{"value":0,"id":339,"type":"NumberInput"},"340":{"x":-2158,"y":-178,"elements":[341,337],"autoResize":false,"id":340,"type":"Checker"},"341":{"outputLength":1,"height":null,"title":"Checker","icon":"ti ti-border-all","id":341,"type":"TitleElement"},"348":{"inputLength":1,"inputs":[349],"links":[357],"height":null,"id":348,"type":"LabelElement"},"349":{"value":0,"id":349,"type":"NumberInput"},"350":{"inputLength":1,"inputs":[351],"height":null,"id":350,"type":"LabelElement"},"351":{"value":24.58,"id":351,"type":"NumberInput"},"352":{"x":-2568,"y":-250,"elements":[353,348,350],"autoResize":false,"id":352,"type":"Multiply"},"353":{"outputLength":1,"height":null,"title":"Multiply","icon":"ti ti-x","id":353,"type":"TitleElement"},"356":{"x":-3000,"y":-300,"elements":[357],"autoResize":false,"id":356,"type":"PositionWorld"},"357":{"outputLength":1,"height":null,"title":"Position World","icon":"ti ti-gizmo","id":357,"type":"TitleElement"},"360":{"x":-3316,"y":927,"elements":[361,363],"autoResize":true,"source":"// Simple Fresnel\n// Enjoy! :)\n\n// layout must be the first variable.\n\nlayout = {\n\tname: \"Fresnel\",\n\toutputType: 'node',\n\ticon: 'heart-plus',\n\twidth: 200,\n\telements: [\n\t\t{ name: 'Color A', inputType: 'node' },\n\t\t{ name: 'Color B', inputType: 'node' },\n\t\t{ name: 'Fresnel Factor', inputType: 'node' },\n\t]\n};\n\n// THREE and TSL (Three.js Shading Language) namespaces are available.\n\nconst { color, float, dot, vec3, normalView } = TSL;\n\nfunction main() {\n\n\tconst colorA = parameters.get( 'Color A' ) || color( 0xff0000 );\n\tconst colorB = parameters.get( 'Color B' ) || float( 0x0000ff );\n\tconst fresnelFactor = parameters.get( 'Fresnel Factor' ) || float( 1.3 );\n\n\tconst fresnel = dot( normalView, vec3( 0, 0, 1 ) ).oneMinus().pow( fresnelFactor );\n\n\treturn fresnel.mix( colorA, colorB );\n\n}\n","id":360,"type":"NodePrototypeEditor"},"361":{"outputLength":1,"height":null,"title":"Node Prototype","icon":"ti ti-ti ti-components","id":361,"type":"TitleElement"},"363":{"height":496,"source":"// Simple Fresnel\n// Enjoy! :)\n\n// layout must be the first variable.\n\nlayout = {\n\tname: \"Fresnel\",\n\toutputType: 'node',\n\ticon: 'heart-plus',\n\twidth: 200,\n\telements: [\n\t\t{ name: 'Color A', inputType: 'node' },\n\t\t{ name: 'Color B', inputType: 'node' },\n\t\t{ name: 'Fresnel Factor', inputType: 'node' },\n\t]\n};\n\n// THREE and TSL (Three.js Shading Language) namespaces are available.\n\nconst { color, float, dot, vec3, normalView } = TSL;\n\nfunction main() {\n\n\tconst colorA = parameters.get( 'Color A' ) || color( 0xff0000 );\n\tconst colorB = parameters.get( 'Color B' ) || float( 0x0000ff );\n\tconst fresnelFactor = parameters.get( 'Fresnel Factor' ) || float( 1.3 );\n\n\tconst fresnel = dot( normalView, vec3( 0, 0, 1 ) ).oneMinus().pow( fresnelFactor );\n\n\treturn fresnel.mix( colorA, colorB );\n\n}\n","id":363,"type":"CodeEditorElement"},"366":{"x":-1785,"y":40,"elements":[367,468,469,470],"autoResize":false,"layoutJSON":"{\"name\":\"Fresnel\",\"outputType\":\"node\",\"icon\":\"heart-plus\",\"width\":200,\"elements\":[{\"name\":\"Color A\",\"inputType\":\"node\"},{\"name\":\"Color B\",\"inputType\":\"node\"},{\"name\":\"Fresnel Factor\",\"inputType\":\"node\"}]}","id":366,"type":"Fresnel"},"367":{"outputLength":1,"height":null,"title":"Fresnel","icon":"ti ti-heart-plus","id":367,"type":"TitleElement"},"373":{"inputs":[374],"height":null,"id":373,"type":"Element"},"374":{"value":3.24,"id":374,"type":"NumberInput"},"375":{"x":-2072,"y":199,"elements":[376,373],"autoResize":false,"id":375,"type":"FloatEditor"},"376":{"outputLength":1,"height":null,"title":"Float","icon":"ti ti-ti ti-box-multiple-1","id":376,"type":"TitleElement"},"381":{"x":-2208,"y":-7,"elements":[382,389,390,391],"autoResize":false,"id":381,"type":"ColorEditor"},"382":{"outputLength":3,"height":null,"title":"Color","icon":"ti ti-ti ti-palette","id":382,"type":"TitleElement"},"384":{"value":16777215,"id":384,"type":"ColorInput"},"385":{"value":"#FFFFFF","id":385,"type":"StringInput"},"386":{"min":0,"max":1,"step":0.01,"value":1,"id":386,"type":"NumberInput"},"387":{"min":0,"max":1,"step":0.01,"value":1,"id":387,"type":"NumberInput"},"388":{"min":0,"max":1,"step":0.01,"value":1,"id":388,"type":"NumberInput"},"389":{"inputs":[384],"height":null,"id":389,"type":"Element"},"390":{"inputs":[385],"height":null,"id":390,"type":"LabelElement"},"391":{"inputs":[386,387,388],"height":null,"id":391,"type":"LabelElement"},"427":{"inputLength":1,"inputs":[428],"height":null,"id":427,"type":"LabelElement"},"428":{"value":0.51,"id":428,"type":"NumberInput"},"429":{"inputLength":1,"inputs":[430],"height":null,"id":429,"type":"LabelElement"},"430":{"value":0.88,"id":430,"type":"NumberInput"},"431":{"inputLength":1,"inputs":[432],"height":null,"id":431,"type":"LabelElement"},"432":{"value":0.79,"id":432,"type":"NumberInput"},"434":{"inputLength":1,"links":[311],"height":null,"id":434,"type":"LabelElement"},"441":{"inputLength":1,"links":[311],"height":null,"id":441,"type":"LabelElement"},"442":{"inputLength":1,"height":null,"id":442,"type":"LabelElement"},"443":{"inputLength":1,"inputs":[444],"height":null,"id":443,"type":"LabelElement"},"444":{"value":0,"id":444,"type":"NumberInput"},"445":{"inputLength":1,"inputs":[446],"height":null,"id":445,"type":"LabelElement"},"446":{"value":0,"id":446,"type":"NumberInput"},"447":{"inputLength":1,"links":[148],"height":null,"id":447,"type":"LabelElement"},"452":{"inputLength":1,"links":[332],"height":null,"id":452,"type":"LabelElement"},"453":{"inputLength":1,"inputs":[454],"height":null,"id":453,"type":"LabelElement"},"454":{"value":"Glass_Gray","id":454,"type":"StringInput"},"455":{"inputLength":1,"links":[232],"height":null,"id":455,"type":"LabelElement"},"460":{"inputLength":1,"links":[332],"height":null,"id":460,"type":"LabelElement"},"461":{"inputLength":1,"inputs":[462],"height":null,"id":461,"type":"LabelElement"},"462":{"value":"Body_Color","id":462,"type":"StringInput"},"463":{"inputLength":1,"links":[200],"height":null,"id":463,"type":"LabelElement"},"464":{"inputLength":1,"links":[188],"height":null,"id":464,"type":"LabelElement"},"468":{"inputLength":1,"links":[382],"height":null,"id":468,"type":"LabelElement"},"469":{"inputLength":1,"links":[341],"height":null,"id":469,"type":"LabelElement"},"470":{"inputLength":1,"links":[376],"height":null,"id":470,"type":"LabelElement"}},"nodes":[147,153,161,167,173,179,187,193,199,231,263,271,279,340,352,356,360,375,381,285,295,300,310,315,323,331,366],"id":2,"type":"Canvas"} \ No newline at end of file diff --git a/playground/examples/webgpu/particle.json b/playground/examples/webgpu/particle.json deleted file mode 100644 index 662950fd8d03ef..00000000000000 --- a/playground/examples/webgpu/particle.json +++ /dev/null @@ -1 +0,0 @@ -{"objects":{"71":{"x":2705,"y":459,"elements":[72,74],"autoResize":true,"source":"layout = {\n\tname: 'Emiter',\n\twidth: 300,\n\telements: [\n\t\t{ name: 'count', inputType: 'Number' },\n\t\t{ name: 'color', inputType: 'node' },\n\t\t{ name: 'opacity', inputType: 'node' },\n\t\t{ name: 'position', inputType: 'node' },\n\t\t{ name: 'rotation', inputType: 'node' },\n\t\t{ name: 'scale', inputType: 'node' }\n\t]\n};\n\nfunction load() {\n\n\tconst fireNodeMaterial = new TSL.SpriteNodeMaterial();\n\tfireNodeMaterial.blending = THREE.AdditiveBlending;\n\tfireNodeMaterial.transparent = true;\n\tfireNodeMaterial.depthWrite = false;\n\n\tconst fireInstancedSprite = new THREE.Mesh( new THREE.PlaneGeometry( 1, 1 ), fireNodeMaterial );\n\tfireInstancedSprite.isInstancedMesh = true;\n\tfireInstancedSprite.count = 100;\n\n\treturn fireInstancedSprite;\n\n}\n\nfunction main() {\n\n\tconst mesh = local.get( 'mesh', load );\n\n\tif ( mesh ) {\n\n\t\tmesh.count = Math.round( parameters.get( 'count' ) || 1 );\n\n\t\tmesh.material.colorNode = parameters.get( 'color' );\n\t\tmesh.material.opacityNode = parameters.get( 'opacity' );\n\t\tmesh.material.positionNode = parameters.get( 'position' );\n\t\tmesh.material.rotationNode = parameters.get( 'rotation' );\n\t\tmesh.material.scaleNode = parameters.get( 'scale' );\n\t\tmesh.material.dispose();\n\n\t}\n\n\treturn mesh;\n\n}\n","id":71,"type":"NodePrototypeEditor"},"72":{"outputLength":1,"height":null,"title":"Node Prototype","icon":"ti ti-ti ti-components","id":72,"type":"TitleElement"},"74":{"height":969,"source":"layout = {\n\tname: 'Emiter',\n\twidth: 300,\n\telements: [\n\t\t{ name: 'count', inputType: 'Number' },\n\t\t{ name: 'color', inputType: 'node' },\n\t\t{ name: 'opacity', inputType: 'node' },\n\t\t{ name: 'position', inputType: 'node' },\n\t\t{ name: 'rotation', inputType: 'node' },\n\t\t{ name: 'scale', inputType: 'node' }\n\t]\n};\n\nfunction load() {\n\n\tconst fireNodeMaterial = new TSL.SpriteNodeMaterial();\n\tfireNodeMaterial.blending = THREE.AdditiveBlending;\n\tfireNodeMaterial.transparent = true;\n\tfireNodeMaterial.depthWrite = false;\n\n\tconst fireInstancedSprite = new THREE.Mesh( new THREE.PlaneGeometry( 1, 1 ), fireNodeMaterial );\n\tfireInstancedSprite.isInstancedMesh = true;\n\tfireInstancedSprite.count = 100;\n\n\treturn fireInstancedSprite;\n\n}\n\nfunction main() {\n\n\tconst mesh = local.get( 'mesh', load );\n\n\tif ( mesh ) {\n\n\t\tmesh.count = Math.round( parameters.get( 'count' ) || 1 );\n\n\t\tmesh.material.colorNode = parameters.get( 'color' );\n\t\tmesh.material.opacityNode = parameters.get( 'opacity' );\n\t\tmesh.material.positionNode = parameters.get( 'position' );\n\t\tmesh.material.rotationNode = parameters.get( 'rotation' );\n\t\tmesh.material.scaleNode = parameters.get( 'scale' );\n\t\tmesh.material.dispose();\n\n\t}\n\n\treturn mesh;\n\n}\n","id":74,"type":"CodeEditorElement"},"77":{"x":2824,"y":-88,"elements":[78,282,284,285,286,287,288],"autoResize":false,"layoutJSON":"{\"name\":\"Emiter\",\"width\":300,\"elements\":[{\"name\":\"count\",\"inputType\":\"Number\"},{\"name\":\"color\",\"inputType\":\"node\"},{\"name\":\"opacity\",\"inputType\":\"node\"},{\"name\":\"position\",\"inputType\":\"node\"},{\"name\":\"rotation\",\"inputType\":\"node\"},{\"name\":\"scale\",\"inputType\":\"node\"}]}","id":77,"type":"Emiter"},"78":{"height":null,"title":"Emiter","icon":"ti ti-ti ti-variable","id":78,"type":"TitleElement"},"92":{"inputs":[93,94,95],"height":null,"id":92,"type":"Element"},"93":{"value":3,"id":93,"type":"NumberInput"},"94":{"value":3,"id":94,"type":"NumberInput"},"95":{"value":3,"id":95,"type":"NumberInput"},"96":{"x":270,"y":87,"elements":[97,92],"autoResize":false,"id":96,"type":"Vector3Editor"},"97":{"outputLength":3,"height":null,"title":"Vector 3","icon":"ti ti-ti ti-box-multiple-3","id":97,"type":"TitleElement"},"102":{"inputLength":1,"links":[117],"height":null,"id":102,"type":"LabelElement"},"103":{"inputLength":1,"links":[97],"height":null,"id":103,"type":"LabelElement"},"104":{"x":860,"y":-93,"elements":[105,102,103],"autoResize":false,"id":104,"type":"Range"},"105":{"outputLength":1,"height":null,"title":"Range","icon":"ti ti-sort-ascending-2","id":105,"type":"TitleElement"},"112":{"inputs":[113,114,115],"height":null,"id":112,"type":"Element"},"113":{"value":-3,"id":113,"type":"NumberInput"},"114":{"value":-3,"id":114,"type":"NumberInput"},"115":{"value":-3,"id":115,"type":"NumberInput"},"116":{"x":256,"y":-136,"elements":[117,112],"autoResize":false,"id":116,"type":"Vector3Editor"},"117":{"outputLength":3,"height":null,"title":"Vector 3","icon":"ti ti-ti ti-box-multiple-3","id":117,"type":"TitleElement"},"120":{"x":735,"y":90,"elements":[121,127,128,125],"autoResize":false,"id":120,"type":"TimerEditor"},"121":{"outputLength":1,"height":null,"title":"Timer","icon":"ti ti-clock","id":121,"type":"TitleElement"},"123":{"value":13.595,"id":123,"type":"NumberInput"},"124":{"value":1,"id":124,"type":"NumberInput"},"125":{"inputs":[126],"height":null,"id":125,"type":"Element"},"126":{"value":"Reset","id":126,"type":"ButtonInput"},"127":{"inputs":[123],"height":null,"id":127,"type":"Element"},"128":{"inputs":[124],"height":null,"id":128,"type":"LabelElement"},"140":{"inputLength":1,"inputs":[141],"links":[105],"height":null,"id":140,"type":"LabelElement"},"141":{"value":0,"id":141,"type":"NumberInput"},"142":{"inputLength":1,"inputs":[143],"links":[165],"height":null,"id":142,"type":"LabelElement"},"143":{"value":0,"id":143,"type":"NumberInput"},"144":{"x":1621,"y":77,"elements":[145,140,142],"autoResize":false,"id":144,"type":"Multiply"},"145":{"outputLength":1,"height":null,"title":"Multiply","icon":"ti ti-x","id":145,"type":"TitleElement"},"151":{"inputLength":1,"links":[209],"height":null,"id":151,"type":"LabelElement"},"152":{"inputLength":1,"links":[201],"height":null,"id":152,"type":"LabelElement"},"153":{"inputLength":1,"links":[217],"height":null,"id":153,"type":"LabelElement"},"154":{"x":1819,"y":301,"elements":[155,151,152,153],"autoResize":false,"id":154,"type":"Mix"},"155":{"outputLength":1,"height":null,"title":"Mix","icon":"ti ti-math-function","id":155,"type":"TitleElement"},"161":{"inputLength":1,"links":[121],"height":null,"id":161,"type":"LabelElement"},"162":{"inputLength":1,"inputs":[163],"links":[225],"height":null,"id":162,"type":"LabelElement"},"163":{"value":2.63,"id":163,"type":"NumberInput"},"164":{"x":1148,"y":167,"elements":[165,161,162],"autoResize":false,"id":164,"type":"Modulo"},"165":{"outputLength":1,"height":null,"title":"Modulo","icon":"ti ti-math-function","id":165,"type":"TitleElement"},"168":{"x":1600,"y":-374,"elements":[169,172],"autoResize":false,"buffer":"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","name":"Lovepik_com-611645078-Particle light effect.png","id":168,"type":"FileEditor"},"169":{"outputLength":1,"height":null,"title":"File","id":169,"type":"TitleElement"},"171":{"value":"Lovepik_com-611645078-Particle light effect.png","id":171,"type":"StringInput"},"172":{"inputs":[171],"height":null,"id":172,"type":"Element"},"176":{"x":1964,"y":-160,"elements":[177,179,180,184,185,186],"autoResize":false,"id":176,"type":"TextureEditor"},"177":{"outputLength":4,"height":null,"title":"Texture","icon":"ti ti-ti ti-photo","id":177,"type":"TitleElement"},"179":{"inputLength":1,"links":[169],"height":null,"id":179,"type":"LabelElement"},"180":{"inputLength":2,"height":null,"id":180,"type":"LabelElement"},"181":{"options":[{"name":"Repeat Wrapping","value":1000},{"name":"Clamp To Edge Wrapping","value":1001},{"name":"Mirrored Repeat Wrapping","value":1002}],"value":"1000","id":181,"type":"SelectInput"},"182":{"options":[{"name":"Repeat Wrapping","value":1000},{"name":"Clamp To Edge Wrapping","value":1001},{"name":"Mirrored Repeat Wrapping","value":1002}],"value":"1000","id":182,"type":"SelectInput"},"183":{"value":false,"id":183,"type":"ToggleInput"},"184":{"inputs":[181],"height":null,"id":184,"type":"LabelElement"},"185":{"inputs":[182],"height":null,"id":185,"type":"LabelElement"},"186":{"inputs":[183],"height":null,"id":186,"type":"LabelElement"},"198":{"inputs":[199],"height":null,"id":198,"type":"Element"},"199":{"value":0,"id":199,"type":"NumberInput"},"200":{"x":1248,"y":450,"elements":[201,198],"autoResize":false,"id":200,"type":"FloatEditor"},"201":{"outputLength":1,"height":null,"title":"Float","icon":"ti ti-ti ti-box-multiple-1","id":201,"type":"TitleElement"},"206":{"inputs":[207],"height":null,"id":206,"type":"Element"},"207":{"value":1.62,"id":207,"type":"NumberInput"},"208":{"x":1255,"y":342,"elements":[209,206],"autoResize":false,"id":208,"type":"FloatEditor"},"209":{"outputLength":1,"height":null,"title":"Float","icon":"ti ti-ti ti-box-multiple-1","id":209,"type":"TitleElement"},"214":{"inputLength":1,"links":[165],"height":null,"id":214,"type":"LabelElement"},"215":{"inputLength":1,"links":[225],"height":null,"id":215,"type":"LabelElement"},"216":{"x":1498,"y":538,"elements":[217,214,215],"autoResize":false,"id":216,"type":"Division"},"217":{"outputLength":1,"height":null,"title":"Division","icon":"ti ti-divide","id":217,"type":"TitleElement"},"222":{"inputs":[223],"height":null,"id":222,"type":"Element"},"223":{"value":2.79,"id":223,"type":"NumberInput"},"224":{"x":641,"y":528,"elements":[225,222],"autoResize":false,"id":224,"type":"FloatEditor"},"225":{"outputLength":1,"height":null,"title":"Float","icon":"ti ti-ti ti-box-multiple-1","id":225,"type":"TitleElement"},"230":{"inputLength":1,"height":null,"id":230,"type":"LabelElement"},"231":{"inputLength":1,"links":[241],"height":null,"id":231,"type":"LabelElement"},"232":{"x":2129,"y":-557,"elements":[233,230,231],"autoResize":false,"id":232,"type":"Range"},"233":{"outputLength":1,"height":null,"title":"Range","icon":"ti ti-sort-ascending-2","id":233,"type":"TitleElement"},"238":{"inputs":[239],"height":null,"id":238,"type":"Element"},"239":{"value":1,"id":239,"type":"NumberInput"},"240":{"x":1895,"y":-506,"elements":[241,238],"autoResize":false,"id":240,"type":"FloatEditor"},"241":{"outputLength":1,"height":null,"title":"Float","icon":"ti ti-ti ti-box-multiple-1","id":241,"type":"TitleElement"},"248":{"inputLength":1,"inputs":[249],"links":[233],"height":null,"id":248,"type":"LabelElement"},"249":{"value":0,"id":249,"type":"NumberInput"},"250":{"inputLength":1,"inputs":[251],"links":[272],"height":null,"id":250,"type":"LabelElement"},"251":{"value":0,"id":251,"type":"NumberInput"},"252":{"x":2527,"y":-425,"elements":[253,248,250],"autoResize":false,"id":252,"type":"Multiply"},"253":{"outputLength":1,"height":null,"title":"Multiply","icon":"ti ti-x","id":253,"type":"TitleElement"},"267":{"inputLength":1,"inputs":[268],"links":[165],"height":null,"id":267,"type":"LabelElement"},"268":{"value":0,"id":268,"type":"NumberInput"},"269":{"inputLength":1,"inputs":[270],"height":null,"id":269,"type":"LabelElement"},"270":{"value":10,"id":270,"type":"NumberInput"},"271":{"x":2122,"y":-371,"elements":[272,267,269],"autoResize":false,"id":271,"type":"Multiply"},"272":{"outputLength":1,"height":null,"title":"Multiply","icon":"ti ti-x","id":272,"type":"TitleElement"},"282":{"inputLength":1,"inputs":[283],"height":null,"id":282,"type":"LabelElement"},"283":{"value":500,"id":283,"type":"NumberInput"},"284":{"inputLength":1,"links":[177],"height":null,"id":284,"type":"LabelElement"},"285":{"inputLength":1,"height":null,"id":285,"type":"LabelElement"},"286":{"inputLength":1,"links":[145],"height":null,"id":286,"type":"LabelElement"},"287":{"inputLength":1,"links":[253],"height":null,"id":287,"type":"LabelElement"},"288":{"inputLength":1,"links":[155],"height":null,"id":288,"type":"LabelElement"}},"nodes":[71,96,104,116,120,144,154,164,168,176,200,208,216,224,232,240,252,271,77],"id":2,"type":"Canvas"} \ No newline at end of file diff --git a/playground/index.html b/playground/index.html index caf52018180e43..58322b859c6e25 100644 --- a/playground/index.html +++ b/playground/index.html @@ -17,6 +17,7 @@ margin: 0; position: fixed; overscroll-behavior: none; + background: #191919ed; } .renderer { @@ -35,6 +36,7 @@ width: 100%; box-shadow: inset 0 0 20px 0px #000000; pointer-events: none; + overflow: hidden; } flow > * { @@ -49,18 +51,56 @@ background: #191919ed; } + flow f-menu { + white-space: nowrap; + } + + node-editor { + position: relative; + width: 100%; + height: 100%; + } + + f-preview { + display: block; + position: relative; + width: 100%; + height: 100%; + } + + f-gutter { + position: absolute; + cursor: ew-resize; + height: 100%; + top: 0px; + width: 2px; + background-color: #191919ed; + border-style: none solid none solid; + border-width: 1px; + border-color: #aaaaaa; + box-shadow: 0 0 5px 0px #000000; + z-index: 30; + } + + .panel { + position: absolute; + overflow: visible; + float: left; + } + - + + + + + + + + + + + + + +
        + + + +
        + +
        +
        + +
        +
        +
        + +
        +
        +
        + +
        +
        +
        +

        + +
        + +
        +
        + + + + + + diff --git a/utils/docs/template/tmpl/mainpage.tmpl b/utils/docs/template/tmpl/mainpage.tmpl new file mode 100644 index 00000000000000..63499acecae8d3 --- /dev/null +++ b/utils/docs/template/tmpl/mainpage.tmpl @@ -0,0 +1,4 @@ + diff --git a/utils/docs/template/tmpl/members.tmpl b/utils/docs/template/tmpl/members.tmpl new file mode 100644 index 00000000000000..334bd53cb5beea --- /dev/null +++ b/utils/docs/template/tmpl/members.tmpl @@ -0,0 +1,33 @@ + +
        +

        + # +

        + + +

        + + + +
        + +
        + + + + + +
        Fires:
        +
          +
        • +
        + + + +
        Example 1? 's':'' ?>
        + + +
        \ No newline at end of file diff --git a/utils/docs/template/tmpl/method.tmpl b/utils/docs/template/tmpl/method.tmpl new file mode 100644 index 00000000000000..f34adc178c7a2c --- /dev/null +++ b/utils/docs/template/tmpl/method.tmpl @@ -0,0 +1,131 @@ + + + +

        Constructor

        + + + +

        + # +

        + + + +

        + + + +
        + +
        + +
        + + + +
        Extends:
        + + + + +
        Type:
        +
          +
        • + +
        • +
        + + + +
        This:
        +
        + + + +
        Parameters:
        + + + + + + +
        Requires:
        +
          +
        • +
        + + + +
        Fires:
        +
          +
        • +
        + + + +
        Listens to Events:
        +
          +
        • +
        + + + +
        Listeners of This Event:
        +
          +
        • +
        + + + +
        Modifies:
        + 1) { ?>
          +
        • +
        + + + + +
        Throws:
        + 1) { ?>
          +
        • +
        + + + + +
        Returns:
        + 1) { ?>
          +
        • +
        + + + + +
        Yields:
        + 1) { ?>
          +
        • +
        + + + +
        \ No newline at end of file diff --git a/utils/docs/template/tmpl/modifies.tmpl b/utils/docs/template/tmpl/modifies.tmpl new file mode 100644 index 00000000000000..16ccbf8d8b8f4e --- /dev/null +++ b/utils/docs/template/tmpl/modifies.tmpl @@ -0,0 +1,14 @@ + + + +
        +
        + Type +
        +
        + +
        +
        + diff --git a/utils/docs/template/tmpl/params.tmpl b/utils/docs/template/tmpl/params.tmpl new file mode 100644 index 00000000000000..1fb4049c1e1851 --- /dev/null +++ b/utils/docs/template/tmpl/params.tmpl @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        NameTypeAttributesDefaultDescription
        + + + + + + <optional>
        + + + + <nullable>
        + + + + <repeatable>
        + +
        + + + + +
        Properties
        + +
        diff --git a/utils/docs/template/tmpl/properties.tmpl b/utils/docs/template/tmpl/properties.tmpl new file mode 100644 index 00000000000000..40e09097127be3 --- /dev/null +++ b/utils/docs/template/tmpl/properties.tmpl @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        NameTypeAttributesDefaultDescription
        + + + + + + <optional>
        + + + + <nullable>
        + +
        + + + + +
        Properties
        +
        diff --git a/utils/docs/template/tmpl/returns.tmpl b/utils/docs/template/tmpl/returns.tmpl new file mode 100644 index 00000000000000..96779f8b94bba3 --- /dev/null +++ b/utils/docs/template/tmpl/returns.tmpl @@ -0,0 +1,8 @@ + +
        + +
        + \ No newline at end of file diff --git a/utils/docs/template/tmpl/search.tmpl b/utils/docs/template/tmpl/search.tmpl new file mode 100644 index 00000000000000..72735b2547b257 --- /dev/null +++ b/utils/docs/template/tmpl/search.tmpl @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/utils/docs/template/tmpl/source.tmpl b/utils/docs/template/tmpl/source.tmpl new file mode 100644 index 00000000000000..e559b5d1038845 --- /dev/null +++ b/utils/docs/template/tmpl/source.tmpl @@ -0,0 +1,8 @@ + +
        +
        +
        +
        +
        \ No newline at end of file diff --git a/utils/docs/template/tmpl/type.tmpl b/utils/docs/template/tmpl/type.tmpl new file mode 100644 index 00000000000000..ec2c6c0df7211a --- /dev/null +++ b/utils/docs/template/tmpl/type.tmpl @@ -0,0 +1,7 @@ + + +| + \ No newline at end of file diff --git a/utils/packLDrawModel.js b/utils/packLDrawModel.mjs similarity index 100% rename from utils/packLDrawModel.js rename to utils/packLDrawModel.mjs